From ddd86436f4e3643c04b797f858dab95d5f2e4de9 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 25 Dec 2015 15:00:15 +0000 Subject: fish --- drivers/net/ethernet/Kconfig | 189 + drivers/net/ethernet/Makefile | 76 + drivers/net/ethernet/atheros/Kconfig | 96 + drivers/net/ethernet/atheros/Makefile | 9 + drivers/net/ethernet/atheros/alx/Makefile | 3 + drivers/net/ethernet/atheros/alx/alx.h | 114 + drivers/net/ethernet/atheros/alx/ethtool.c | 272 + drivers/net/ethernet/atheros/alx/hw.c | 1226 + drivers/net/ethernet/atheros/alx/hw.h | 499 + drivers/net/ethernet/atheros/alx/main.c | 1657 ++ drivers/net/ethernet/atheros/alx/reg.h | 810 + drivers/net/ethernet/atheros/atl1c/Makefile | 2 + drivers/net/ethernet/atheros/atl1c/atl1c.h | 606 + drivers/net/ethernet/atheros/atl1c/atl1c_ethtool.c | 321 + drivers/net/ethernet/atheros/atl1c/atl1c_hw.c | 865 + drivers/net/ethernet/atheros/atl1c/atl1c_hw.h | 1024 + drivers/net/ethernet/atheros/atl1c/atl1c_main.c | 2857 ++ drivers/net/ethernet/atheros/atl1e/Makefile | 2 + drivers/net/ethernet/atheros/atl1e/atl1e.h | 508 + drivers/net/ethernet/atheros/atl1e/atl1e_ethtool.c | 397 + drivers/net/ethernet/atheros/atl1e/atl1e_hw.c | 651 + drivers/net/ethernet/atheros/atl1e/atl1e_hw.h | 690 + drivers/net/ethernet/atheros/atl1e/atl1e_main.c | 2576 ++ drivers/net/ethernet/atheros/atl1e/atl1e_param.c | 269 + drivers/net/ethernet/atheros/atlx/Makefile | 3 + drivers/net/ethernet/atheros/atlx/atl1.c | 3735 +++ drivers/net/ethernet/atheros/atlx/atl1.h | 802 + drivers/net/ethernet/atheros/atlx/atl2.c | 3129 ++ drivers/net/ethernet/atheros/atlx/atl2.h | 524 + drivers/net/ethernet/atheros/atlx/atlx.c | 285 + drivers/net/ethernet/atheros/atlx/atlx.h | 502 + drivers/net/ethernet/broadcom/Kconfig | 157 + drivers/net/ethernet/broadcom/Makefile | 12 + drivers/net/ethernet/broadcom/b44.c | 2400 ++ drivers/net/ethernet/broadcom/b44.h | 402 + drivers/net/mdio.c | 451 + drivers/net/usb/Kconfig | 596 + drivers/net/usb/Makefile | 36 + drivers/net/usb/cdc_ether.c | 757 + drivers/net/usb/rndis_host.c | 649 + drivers/net/usb/usbnet.c | 1979 ++ drivers/net/wireless/Kconfig | 312 + drivers/net/wireless/Makefile | 59 + drivers/net/wireless/adm8211.c | 2014 ++ drivers/net/wireless/adm8211.h | 602 + drivers/net/wireless/at76c50x-usb.c | 2504 ++ drivers/net/wireless/at76c50x-usb.h | 464 + drivers/net/wireless/ath/Kconfig | 37 + drivers/net/wireless/ath/Makefile | 16 + drivers/net/wireless/ath/ar5523/Kconfig | 9 + drivers/net/wireless/ath/ar5523/Makefile | 1 + drivers/net/wireless/ath/ar5523/ar5523.c | 1798 ++ drivers/net/wireless/ath/ar5523/ar5523.h | 152 + drivers/net/wireless/ath/ar5523/ar5523_hw.h | 431 + drivers/net/wireless/ath/ath.h | 293 + drivers/net/wireless/ath/ath5k/Kconfig | 76 + drivers/net/wireless/ath/ath5k/Makefile | 22 + drivers/net/wireless/ath/ath5k/ahb.c | 239 + drivers/net/wireless/ath/ath5k/ani.c | 754 + drivers/net/wireless/ath/ath5k/ani.h | 119 + drivers/net/wireless/ath/ath5k/ath5k.h | 1714 ++ drivers/net/wireless/ath/ath5k/attach.c | 360 + drivers/net/wireless/ath/ath5k/base.c | 3082 ++ drivers/net/wireless/ath/ath5k/base.h | 119 + drivers/net/wireless/ath/ath5k/caps.c | 154 + drivers/net/wireless/ath/ath5k/debug.c | 1029 + drivers/net/wireless/ath/ath5k/debug.h | 165 + drivers/net/wireless/ath/ath5k/desc.c | 786 + drivers/net/wireless/ath/ath5k/desc.h | 367 + drivers/net/wireless/ath/ath5k/dma.c | 919 + drivers/net/wireless/ath/ath5k/eeprom.c | 1796 ++ drivers/net/wireless/ath/ath5k/eeprom.h | 495 + drivers/net/wireless/ath/ath5k/gpio.c | 213 + drivers/net/wireless/ath/ath5k/initvals.c | 1605 ++ drivers/net/wireless/ath/ath5k/led.c | 202 + drivers/net/wireless/ath/ath5k/mac80211-ops.c | 854 + drivers/net/wireless/ath/ath5k/pci.c | 351 + drivers/net/wireless/ath/ath5k/pcu.c | 1007 + drivers/net/wireless/ath/ath5k/phy.c | 3965 +++ drivers/net/wireless/ath/ath5k/qcu.c | 714 + drivers/net/wireless/ath/ath5k/reg.h | 2604 ++ drivers/net/wireless/ath/ath5k/reset.c | 1404 + drivers/net/wireless/ath/ath5k/rfbuffer.h | 853 + drivers/net/wireless/ath/ath5k/rfgain.h | 534 + drivers/net/wireless/ath/ath5k/rfkill.c | 116 + drivers/net/wireless/ath/ath5k/sysfs.c | 122 + drivers/net/wireless/ath/ath5k/trace.h | 106 + drivers/net/wireless/ath/ath6kl/Kconfig | 53 + drivers/net/wireless/ath/ath6kl/Makefile | 49 + drivers/net/wireless/ath/ath6kl/bmi.c | 548 + drivers/net/wireless/ath/ath6kl/bmi.h | 270 + drivers/net/wireless/ath/ath6kl/cfg80211.c | 3852 +++ drivers/net/wireless/ath/ath6kl/cfg80211.h | 65 + drivers/net/wireless/ath/ath6kl/common.h | 86 + drivers/net/wireless/ath/ath6kl/core.c | 346 + drivers/net/wireless/ath/ath6kl/core.h | 964 + drivers/net/wireless/ath/ath6kl/debug.c | 1885 ++ drivers/net/wireless/ath/ath6kl/debug.h | 145 + drivers/net/wireless/ath/ath6kl/hif-ops.h | 187 + drivers/net/wireless/ath/ath6kl/hif.c | 705 + drivers/net/wireless/ath/ath6kl/hif.h | 282 + drivers/net/wireless/ath/ath6kl/htc-ops.h | 113 + drivers/net/wireless/ath/ath6kl/htc.h | 677 + drivers/net/wireless/ath/ath6kl/htc_mbox.c | 2946 ++ drivers/net/wireless/ath/ath6kl/htc_pipe.c | 1719 ++ drivers/net/wireless/ath/ath6kl/init.c | 1864 ++ drivers/net/wireless/ath/ath6kl/main.c | 1326 + drivers/net/wireless/ath/ath6kl/recovery.c | 160 + drivers/net/wireless/ath/ath6kl/sdio.c | 1477 + drivers/net/wireless/ath/ath6kl/target.h | 356 + drivers/net/wireless/ath/ath6kl/testmode.c | 101 + drivers/net/wireless/ath/ath6kl/testmode.h | 37 + drivers/net/wireless/ath/ath6kl/trace.c | 24 + drivers/net/wireless/ath/ath6kl/trace.h | 332 + drivers/net/wireless/ath/ath6kl/txrx.c | 1872 ++ drivers/net/wireless/ath/ath6kl/usb.c | 1250 + drivers/net/wireless/ath/ath6kl/wmi.c | 4134 +++ drivers/net/wireless/ath/ath6kl/wmi.h | 2732 ++ drivers/net/wireless/ath/ath9k/Kconfig | 132 + drivers/net/wireless/ath/ath9k/Makefile | 64 + drivers/net/wireless/ath/ath9k/ahb.c | 193 + drivers/net/wireless/ath/ath9k/ani.c | 532 + drivers/net/wireless/ath/ath9k/ani.h | 145 + drivers/net/wireless/ath/ath9k/antenna.c | 797 + drivers/net/wireless/ath/ath9k/ar5008_initvals.h | 674 + drivers/net/wireless/ath/ath9k/ar5008_phy.c | 1358 + drivers/net/wireless/ath/ath9k/ar9001_initvals.h | 1089 + drivers/net/wireless/ath/ath9k/ar9002_calib.c | 992 + drivers/net/wireless/ath/ath9k/ar9002_hw.c | 474 + drivers/net/wireless/ath/ath9k/ar9002_initvals.h | 3194 +++ drivers/net/wireless/ath/ath9k/ar9002_mac.c | 369 + drivers/net/wireless/ath/ath9k/ar9002_phy.c | 638 + drivers/net/wireless/ath/ath9k/ar9002_phy.h | 615 + .../net/wireless/ath/ath9k/ar9003_2p2_initvals.h | 1741 ++ drivers/net/wireless/ath/ath9k/ar9003_calib.c | 1187 + drivers/net/wireless/ath/ath9k/ar9003_eeprom.c | 5346 ++++ drivers/net/wireless/ath/ath9k/ar9003_eeprom.h | 345 + drivers/net/wireless/ath/ath9k/ar9003_hw.c | 715 + drivers/net/wireless/ath/ath9k/ar9003_mac.c | 589 + drivers/net/wireless/ath/ath9k/ar9003_mac.h | 123 + drivers/net/wireless/ath/ath9k/ar9003_mci.c | 1485 + drivers/net/wireless/ath/ath9k/ar9003_mci.h | 335 + drivers/net/wireless/ath/ath9k/ar9003_paprd.c | 1010 + drivers/net/wireless/ath/ath9k/ar9003_phy.c | 1685 ++ drivers/net/wireless/ath/ath9k/ar9003_phy.h | 1322 + drivers/net/wireless/ath/ath9k/ar9003_rtt.c | 208 + drivers/net/wireless/ath/ath9k/ar9003_rtt.h | 29 + .../net/wireless/ath/ath9k/ar9330_1p1_initvals.h | 1100 + .../net/wireless/ath/ath9k/ar9330_1p2_initvals.h | 242 + drivers/net/wireless/ath/ath9k/ar9340_initvals.h | 1450 + .../net/wireless/ath/ath9k/ar9462_2p0_initvals.h | 1452 + drivers/net/wireless/ath/ath9k/ar9485_initvals.h | 1211 + .../net/wireless/ath/ath9k/ar955x_1p0_initvals.h | 1284 + .../net/wireless/ath/ath9k/ar9565_1p0_initvals.h | 1233 + .../net/wireless/ath/ath9k/ar9580_1p0_initvals.h | 997 + drivers/net/wireless/ath/ath9k/ath9k.h | 945 + drivers/net/wireless/ath/ath9k/beacon.c | 781 + drivers/net/wireless/ath/ath9k/btcoex.c | 416 + drivers/net/wireless/ath/ath9k/btcoex.h | 124 + drivers/net/wireless/ath/ath9k/calib.c | 448 + drivers/net/wireless/ath/ath9k/calib.h | 122 + drivers/net/wireless/ath/ath9k/common.c | 197 + drivers/net/wireless/ath/ath9k/common.h | 56 + drivers/net/wireless/ath/ath9k/debug.c | 2118 ++ drivers/net/wireless/ath/ath9k/debug.h | 380 + drivers/net/wireless/ath/ath9k/dfs.c | 193 + drivers/net/wireless/ath/ath9k/dfs.h | 45 + drivers/net/wireless/ath/ath9k/dfs_debug.c | 140 + drivers/net/wireless/ath/ath9k/dfs_debug.h | 84 + .../net/wireless/ath/ath9k/dfs_pattern_detector.c | 311 + .../net/wireless/ath/ath9k/dfs_pattern_detector.h | 106 + drivers/net/wireless/ath/ath9k/dfs_pri_detector.c | 425 + drivers/net/wireless/ath/ath9k/dfs_pri_detector.h | 75 + drivers/net/wireless/ath/ath9k/eeprom.c | 586 + drivers/net/wireless/ath/ath9k/eeprom.h | 717 + drivers/net/wireless/ath/ath9k/eeprom_4k.c | 1106 + drivers/net/wireless/ath/ath9k/eeprom_9287.c | 1044 + drivers/net/wireless/ath/ath9k/eeprom_def.c | 1389 + drivers/net/wireless/ath/ath9k/gpio.c | 567 + drivers/net/wireless/ath/ath9k/hif_usb.c | 1391 + drivers/net/wireless/ath/ath9k/hif_usb.h | 116 + drivers/net/wireless/ath/ath9k/htc.h | 609 + drivers/net/wireless/ath/ath9k/htc_drv_beacon.c | 659 + drivers/net/wireless/ath/ath9k/htc_drv_debug.c | 951 + drivers/net/wireless/ath/ath9k/htc_drv_gpio.c | 321 + drivers/net/wireless/ath/ath9k/htc_drv_init.c | 1008 + drivers/net/wireless/ath/ath9k/htc_drv_main.c | 1789 ++ drivers/net/wireless/ath/ath9k/htc_drv_txrx.c | 1230 + drivers/net/wireless/ath/ath9k/htc_hst.c | 479 + drivers/net/wireless/ath/ath9k/htc_hst.h | 218 + drivers/net/wireless/ath/ath9k/hw-ops.h | 215 + drivers/net/wireless/ath/ath9k/hw.c | 3262 +++ drivers/net/wireless/ath/ath9k/hw.h | 1175 + drivers/net/wireless/ath/ath9k/init.c | 1011 + drivers/net/wireless/ath/ath9k/link.c | 571 + drivers/net/wireless/ath/ath9k/mac.c | 939 + drivers/net/wireless/ath/ath9k/mac.h | 747 + drivers/net/wireless/ath/ath9k/main.c | 2394 ++ drivers/net/wireless/ath/ath9k/mci.c | 768 + drivers/net/wireless/ath/ath9k/mci.h | 178 + drivers/net/wireless/ath/ath9k/pci.c | 350 + drivers/net/wireless/ath/ath9k/phy.h | 51 + drivers/net/wireless/ath/ath9k/rc.c | 1488 + drivers/net/wireless/ath/ath9k/rc.h | 248 + drivers/net/wireless/ath/ath9k/recv.c | 1401 + drivers/net/wireless/ath/ath9k/reg.h | 2377 ++ drivers/net/wireless/ath/ath9k/wmi.c | 349 + drivers/net/wireless/ath/ath9k/wmi.h | 191 + drivers/net/wireless/ath/ath9k/wow.c | 532 + drivers/net/wireless/ath/ath9k/xmit.c | 2426 ++ drivers/net/wireless/ath/carl9170/Kconfig | 58 + drivers/net/wireless/ath/carl9170/Makefile | 4 + drivers/net/wireless/ath/carl9170/carl9170.h | 666 + drivers/net/wireless/ath/carl9170/cmd.c | 222 + drivers/net/wireless/ath/carl9170/cmd.h | 174 + drivers/net/wireless/ath/carl9170/debug.c | 897 + drivers/net/wireless/ath/carl9170/debug.h | 134 + drivers/net/wireless/ath/carl9170/eeprom.h | 216 + drivers/net/wireless/ath/carl9170/fw.c | 447 + drivers/net/wireless/ath/carl9170/fwcmd.h | 326 + drivers/net/wireless/ath/carl9170/fwdesc.h | 277 + drivers/net/wireless/ath/carl9170/hw.h | 817 + drivers/net/wireless/ath/carl9170/led.c | 190 + drivers/net/wireless/ath/carl9170/mac.c | 538 + drivers/net/wireless/ath/carl9170/main.c | 2148 ++ drivers/net/wireless/ath/carl9170/phy.c | 1730 ++ drivers/net/wireless/ath/carl9170/phy.h | 564 + drivers/net/wireless/ath/carl9170/rx.c | 1016 + drivers/net/wireless/ath/carl9170/tx.c | 1686 ++ drivers/net/wireless/ath/carl9170/usb.c | 1174 + drivers/net/wireless/ath/carl9170/version.h | 7 + drivers/net/wireless/ath/carl9170/wlan.h | 435 + drivers/net/wireless/ath/debug.c | 47 + drivers/net/wireless/ath/hw.c | 190 + drivers/net/wireless/ath/key.c | 609 + drivers/net/wireless/ath/main.c | 102 + drivers/net/wireless/ath/reg.h | 65 + drivers/net/wireless/ath/regd.c | 661 + drivers/net/wireless/ath/regd.h | 263 + drivers/net/wireless/ath/regd_common.h | 478 + drivers/net/wireless/ath/wil6210/Kconfig | 31 + drivers/net/wireless/ath/wil6210/Makefile | 15 + drivers/net/wireless/ath/wil6210/cfg80211.c | 561 + drivers/net/wireless/ath/wil6210/debugfs.c | 639 + drivers/net/wireless/ath/wil6210/interrupt.c | 509 + drivers/net/wireless/ath/wil6210/main.c | 404 + drivers/net/wireless/ath/wil6210/netdev.c | 129 + drivers/net/wireless/ath/wil6210/pcie_bus.c | 220 + drivers/net/wireless/ath/wil6210/txrx.c | 832 + drivers/net/wireless/ath/wil6210/txrx.h | 397 + drivers/net/wireless/ath/wil6210/wil6210.h | 366 + drivers/net/wireless/ath/wil6210/wmi.c | 1088 + drivers/net/wireless/ath/wil6210/wmi.h | 1281 + drivers/net/wireless/b43/Kconfig | 178 + drivers/net/wireless/b43/Makefile | 30 + drivers/net/wireless/b43/b43.h | 1085 + drivers/net/wireless/b43/bus.c | 255 + drivers/net/wireless/b43/bus.h | 70 + drivers/net/wireless/b43/debugfs.c | 826 + drivers/net/wireless/b43/debugfs.h | 111 + drivers/net/wireless/b43/dma.c | 1834 ++ drivers/net/wireless/b43/dma.h | 305 + drivers/net/wireless/b43/leds.c | 359 + drivers/net/wireless/b43/leds.h | 94 + drivers/net/wireless/b43/lo.c | 1016 + drivers/net/wireless/b43/lo.h | 87 + drivers/net/wireless/b43/main.c | 5646 ++++ drivers/net/wireless/b43/main.h | 144 + drivers/net/wireless/b43/pcmcia.c | 180 + drivers/net/wireless/b43/pcmcia.h | 20 + drivers/net/wireless/b43/phy_a.c | 595 + drivers/net/wireless/b43/phy_a.h | 130 + drivers/net/wireless/b43/phy_common.c | 543 + drivers/net/wireless/b43/phy_common.h | 460 + drivers/net/wireless/b43/phy_g.c | 3055 ++ drivers/net/wireless/b43/phy_g.h | 208 + drivers/net/wireless/b43/phy_ht.c | 1140 + drivers/net/wireless/b43/phy_ht.h | 136 + drivers/net/wireless/b43/phy_lcn.c | 902 + drivers/net/wireless/b43/phy_lcn.h | 31 + drivers/net/wireless/b43/phy_lp.c | 2730 ++ drivers/net/wireless/b43/phy_lp.h | 912 + drivers/net/wireless/b43/phy_n.c | 5565 ++++ drivers/net/wireless/b43/phy_n.h | 989 + drivers/net/wireless/b43/pio.c | 834 + drivers/net/wireless/b43/pio.h | 165 + drivers/net/wireless/b43/radio_2055.c | 1335 + drivers/net/wireless/b43/radio_2055.h | 259 + drivers/net/wireless/b43/radio_2056.c | 9116 ++++++ drivers/net/wireless/b43/radio_2056.h | 1100 + drivers/net/wireless/b43/radio_2057.c | 141 + drivers/net/wireless/b43/radio_2057.h | 430 + drivers/net/wireless/b43/radio_2059.c | 155 + drivers/net/wireless/b43/radio_2059.h | 46 + drivers/net/wireless/b43/rfkill.c | 70 + drivers/net/wireless/b43/rfkill.h | 11 + drivers/net/wireless/b43/sdio.c | 207 + drivers/net/wireless/b43/sdio.h | 45 + drivers/net/wireless/b43/sysfs.c | 155 + drivers/net/wireless/b43/sysfs.h | 9 + drivers/net/wireless/b43/tables.c | 466 + drivers/net/wireless/b43/tables.h | 34 + drivers/net/wireless/b43/tables_lpphy.c | 2456 ++ drivers/net/wireless/b43/tables_lpphy.h | 44 + drivers/net/wireless/b43/tables_nphy.c | 3322 +++ drivers/net/wireless/b43/tables_nphy.h | 217 + drivers/net/wireless/b43/tables_phy_ht.c | 836 + drivers/net/wireless/b43/tables_phy_ht.h | 26 + drivers/net/wireless/b43/tables_phy_lcn.c | 724 + drivers/net/wireless/b43/tables_phy_lcn.h | 24 + drivers/net/wireless/b43/wa.c | 634 + drivers/net/wireless/b43/wa.h | 7 + drivers/net/wireless/b43/xmit.c | 942 + drivers/net/wireless/b43/xmit.h | 412 + drivers/net/wireless/b43legacy/Kconfig | 105 + drivers/net/wireless/b43legacy/Makefile | 19 + drivers/net/wireless/b43legacy/b43legacy.h | 858 + drivers/net/wireless/b43legacy/debugfs.c | 500 + drivers/net/wireless/b43legacy/debugfs.h | 89 + drivers/net/wireless/b43legacy/dma.c | 1459 + drivers/net/wireless/b43legacy/dma.h | 231 + drivers/net/wireless/b43legacy/ilt.c | 336 + drivers/net/wireless/b43legacy/ilt.h | 34 + drivers/net/wireless/b43legacy/leds.c | 243 + drivers/net/wireless/b43legacy/leds.h | 63 + drivers/net/wireless/b43legacy/main.c | 4064 +++ drivers/net/wireless/b43legacy/main.h | 127 + drivers/net/wireless/b43legacy/phy.c | 2258 ++ drivers/net/wireless/b43legacy/phy.h | 209 + drivers/net/wireless/b43legacy/pio.c | 695 + drivers/net/wireless/b43legacy/pio.h | 158 + drivers/net/wireless/b43legacy/radio.c | 2162 ++ drivers/net/wireless/b43legacy/radio.h | 98 + drivers/net/wireless/b43legacy/rfkill.c | 91 + drivers/net/wireless/b43legacy/rfkill.h | 11 + drivers/net/wireless/b43legacy/sysfs.c | 238 + drivers/net/wireless/b43legacy/sysfs.h | 9 + drivers/net/wireless/b43legacy/xmit.c | 664 + drivers/net/wireless/b43legacy/xmit.h | 261 + drivers/net/wireless/brcm80211/Kconfig | 70 + drivers/net/wireless/brcm80211/Makefile | 23 + drivers/net/wireless/brcm80211/brcmfmac/Makefile | 45 + drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c | 600 + .../net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c | 635 + drivers/net/wireless/brcm80211/brcmfmac/btcoex.c | 497 + drivers/net/wireless/brcm80211/brcmfmac/btcoex.h | 29 + drivers/net/wireless/brcm80211/brcmfmac/dhd.h | 631 + drivers/net/wireless/brcm80211/brcmfmac/dhd_bus.h | 164 + drivers/net/wireless/brcm80211/brcmfmac/dhd_cdc.c | 389 + .../net/wireless/brcm80211/brcmfmac/dhd_common.c | 390 + drivers/net/wireless/brcm80211/brcmfmac/dhd_dbg.c | 205 + drivers/net/wireless/brcm80211/brcmfmac/dhd_dbg.h | 194 + .../net/wireless/brcm80211/brcmfmac/dhd_linux.c | 1078 + .../net/wireless/brcm80211/brcmfmac/dhd_proto.h | 42 + drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c | 4043 +++ drivers/net/wireless/brcm80211/brcmfmac/fweh.c | 452 + drivers/net/wireless/brcm80211/brcmfmac/fweh.h | 219 + drivers/net/wireless/brcm80211/brcmfmac/fwil.c | 348 + drivers/net/wireless/brcm80211/brcmfmac/fwil.h | 39 + .../net/wireless/brcm80211/brcmfmac/fwil_types.h | 72 + drivers/net/wireless/brcm80211/brcmfmac/fwsignal.c | 2067 ++ drivers/net/wireless/brcm80211/brcmfmac/fwsignal.h | 33 + drivers/net/wireless/brcm80211/brcmfmac/p2p.c | 2449 ++ drivers/net/wireless/brcm80211/brcmfmac/p2p.h | 183 + .../net/wireless/brcm80211/brcmfmac/sdio_chip.c | 955 + .../net/wireless/brcm80211/brcmfmac/sdio_chip.h | 231 + .../net/wireless/brcm80211/brcmfmac/sdio_host.h | 294 + .../net/wireless/brcm80211/brcmfmac/tracepoint.c | 22 + .../net/wireless/brcm80211/brcmfmac/tracepoint.h | 101 + drivers/net/wireless/brcm80211/brcmfmac/usb.c | 1542 + drivers/net/wireless/brcm80211/brcmfmac/usb.h | 53 + drivers/net/wireless/brcm80211/brcmfmac/usb_rdl.h | 75 + .../net/wireless/brcm80211/brcmfmac/wl_cfg80211.c | 5417 ++++ .../net/wireless/brcm80211/brcmfmac/wl_cfg80211.h | 511 + drivers/net/wireless/brcm80211/brcmsmac/Makefile | 48 + drivers/net/wireless/brcm80211/brcmsmac/aiutils.c | 731 + drivers/net/wireless/brcm80211/brcmsmac/aiutils.h | 232 + drivers/net/wireless/brcm80211/brcmsmac/ampdu.c | 1144 + drivers/net/wireless/brcm80211/brcmsmac/ampdu.h | 53 + drivers/net/wireless/brcm80211/brcmsmac/antsel.c | 309 + drivers/net/wireless/brcm80211/brcmsmac/antsel.h | 29 + .../brcm80211/brcmsmac/brcms_trace_events.c | 23 + .../brcm80211/brcmsmac/brcms_trace_events.h | 255 + drivers/net/wireless/brcm80211/brcmsmac/channel.c | 782 + drivers/net/wireless/brcm80211/brcmsmac/channel.h | 51 + drivers/net/wireless/brcm80211/brcmsmac/d11.h | 1902 ++ drivers/net/wireless/brcm80211/brcmsmac/debug.c | 156 + drivers/net/wireless/brcm80211/brcmsmac/debug.h | 76 + drivers/net/wireless/brcm80211/brcmsmac/dma.c | 1564 + drivers/net/wireless/brcm80211/brcmsmac/dma.h | 125 + drivers/net/wireless/brcm80211/brcmsmac/led.c | 126 + drivers/net/wireless/brcm80211/brcmsmac/led.h | 36 + .../net/wireless/brcm80211/brcmsmac/mac80211_if.c | 1707 ++ .../net/wireless/brcm80211/brcmsmac/mac80211_if.h | 113 + drivers/net/wireless/brcm80211/brcmsmac/main.c | 8182 ++++++ drivers/net/wireless/brcm80211/brcmsmac/main.h | 681 + .../net/wireless/brcm80211/brcmsmac/phy/phy_cmn.c | 2957 ++ .../net/wireless/brcm80211/brcmsmac/phy/phy_hal.h | 299 + .../net/wireless/brcm80211/brcmsmac/phy/phy_int.h | 1161 + .../net/wireless/brcm80211/brcmsmac/phy/phy_lcn.c | 5148 ++++ .../net/wireless/brcm80211/brcmsmac/phy/phy_lcn.h | 121 + .../net/wireless/brcm80211/brcmsmac/phy/phy_n.c | 28729 +++++++++++++++++++ .../wireless/brcm80211/brcmsmac/phy/phy_qmath.c | 308 + .../wireless/brcm80211/brcmsmac/phy/phy_qmath.h | 42 + .../wireless/brcm80211/brcmsmac/phy/phy_radio.h | 1533 + .../net/wireless/brcm80211/brcmsmac/phy/phyreg_n.h | 167 + .../wireless/brcm80211/brcmsmac/phy/phytbl_lcn.c | 3250 +++ .../wireless/brcm80211/brcmsmac/phy/phytbl_lcn.h | 54 + .../net/wireless/brcm80211/brcmsmac/phy/phytbl_n.c | 10630 +++++++ .../net/wireless/brcm80211/brcmsmac/phy/phytbl_n.h | 50 + drivers/net/wireless/brcm80211/brcmsmac/phy_shim.c | 216 + drivers/net/wireless/brcm80211/brcmsmac/phy_shim.h | 179 + drivers/net/wireless/brcm80211/brcmsmac/pmu.c | 165 + drivers/net/wireless/brcm80211/brcmsmac/pmu.h | 26 + drivers/net/wireless/brcm80211/brcmsmac/pub.h | 348 + drivers/net/wireless/brcm80211/brcmsmac/rate.c | 514 + drivers/net/wireless/brcm80211/brcmsmac/rate.h | 249 + drivers/net/wireless/brcm80211/brcmsmac/scb.h | 81 + drivers/net/wireless/brcm80211/brcmsmac/stf.c | 440 + drivers/net/wireless/brcm80211/brcmsmac/stf.h | 42 + drivers/net/wireless/brcm80211/brcmsmac/types.h | 303 + .../net/wireless/brcm80211/brcmsmac/ucode_loader.c | 109 + .../net/wireless/brcm80211/brcmsmac/ucode_loader.h | 58 + drivers/net/wireless/brcm80211/brcmutil/Makefile | 23 + drivers/net/wireless/brcm80211/brcmutil/d11.c | 162 + drivers/net/wireless/brcm80211/brcmutil/utils.c | 295 + .../net/wireless/brcm80211/include/brcm_hw_ids.h | 45 + drivers/net/wireless/brcm80211/include/brcmu_d11.h | 145 + .../net/wireless/brcm80211/include/brcmu_utils.h | 223 + .../net/wireless/brcm80211/include/brcmu_wifi.h | 256 + .../net/wireless/brcm80211/include/chipcommon.h | 296 + drivers/net/wireless/brcm80211/include/defs.h | 108 + drivers/net/wireless/brcm80211/include/soc.h | 36 + drivers/net/wireless/ipw2x00/Kconfig | 201 + drivers/net/wireless/ipw2x00/Makefile | 14 + drivers/net/wireless/ipw2x00/ipw.h | 23 + drivers/net/wireless/ipw2x00/ipw2100.c | 8664 ++++++ drivers/net/wireless/ipw2x00/ipw2100.h | 1156 + drivers/net/wireless/ipw2x00/ipw2200.c | 12158 ++++++++ drivers/net/wireless/ipw2x00/ipw2200.h | 2002 ++ drivers/net/wireless/ipw2x00/libipw.h | 1024 + drivers/net/wireless/ipw2x00/libipw_geo.c | 193 + drivers/net/wireless/ipw2x00/libipw_module.c | 335 + drivers/net/wireless/ipw2x00/libipw_rx.c | 1797 ++ drivers/net/wireless/ipw2x00/libipw_tx.c | 536 + drivers/net/wireless/ipw2x00/libipw_wx.c | 744 + drivers/net/wireless/iwlegacy/3945-debug.c | 511 + drivers/net/wireless/iwlegacy/3945-mac.c | 3971 +++ drivers/net/wireless/iwlegacy/3945-rs.c | 980 + drivers/net/wireless/iwlegacy/3945.c | 2717 ++ drivers/net/wireless/iwlegacy/3945.h | 597 + drivers/net/wireless/iwlegacy/4965-calib.c | 934 + drivers/net/wireless/iwlegacy/4965-debug.c | 752 + drivers/net/wireless/iwlegacy/4965-mac.c | 6863 +++++ drivers/net/wireless/iwlegacy/4965-rs.c | 2836 ++ drivers/net/wireless/iwlegacy/4965.c | 1951 ++ drivers/net/wireless/iwlegacy/4965.h | 1284 + drivers/net/wireless/iwlegacy/Kconfig | 103 + drivers/net/wireless/iwlegacy/Makefile | 17 + drivers/net/wireless/iwlegacy/commands.h | 3377 +++ drivers/net/wireless/iwlegacy/common.c | 5533 ++++ drivers/net/wireless/iwlegacy/common.h | 3042 ++ drivers/net/wireless/iwlegacy/csr.h | 419 + drivers/net/wireless/iwlegacy/debug.c | 1430 + drivers/net/wireless/iwlegacy/iwl-spectrum.h | 92 + drivers/net/wireless/iwlegacy/prph.h | 522 + drivers/net/wireless/iwlwifi/Kconfig | 159 + drivers/net/wireless/iwlwifi/Makefile | 21 + drivers/net/wireless/iwlwifi/dvm/Makefile | 13 + drivers/net/wireless/iwlwifi/dvm/agn.h | 532 + drivers/net/wireless/iwlwifi/dvm/calib.c | 1114 + drivers/net/wireless/iwlwifi/dvm/calib.h | 74 + drivers/net/wireless/iwlwifi/dvm/commands.h | 4015 +++ drivers/net/wireless/iwlwifi/dvm/debugfs.c | 2461 ++ drivers/net/wireless/iwlwifi/dvm/dev.h | 917 + drivers/net/wireless/iwlwifi/dvm/devices.c | 599 + drivers/net/wireless/iwlwifi/dvm/led.c | 230 + drivers/net/wireless/iwlwifi/dvm/led.h | 43 + drivers/net/wireless/iwlwifi/dvm/lib.c | 1311 + drivers/net/wireless/iwlwifi/dvm/mac80211.c | 1788 ++ drivers/net/wireless/iwlwifi/dvm/main.c | 2170 ++ drivers/net/wireless/iwlwifi/dvm/power.c | 387 + drivers/net/wireless/iwlwifi/dvm/power.h | 47 + drivers/net/wireless/iwlwifi/dvm/rs.c | 3369 +++ drivers/net/wireless/iwlwifi/dvm/rs.h | 433 + drivers/net/wireless/iwlwifi/dvm/rx.c | 1151 + drivers/net/wireless/iwlwifi/dvm/rxon.c | 1575 + drivers/net/wireless/iwlwifi/dvm/scan.c | 1188 + drivers/net/wireless/iwlwifi/dvm/sta.c | 1477 + drivers/net/wireless/iwlwifi/dvm/testmode.c | 471 + drivers/net/wireless/iwlwifi/dvm/tt.c | 691 + drivers/net/wireless/iwlwifi/dvm/tt.h | 128 + drivers/net/wireless/iwlwifi/dvm/tx.c | 1453 + drivers/net/wireless/iwlwifi/dvm/ucode.c | 463 + drivers/net/wireless/iwlwifi/iwl-1000.c | 140 + drivers/net/wireless/iwlwifi/iwl-2000.c | 242 + drivers/net/wireless/iwlwifi/iwl-5000.c | 179 + drivers/net/wireless/iwlwifi/iwl-6000.c | 408 + drivers/net/wireless/iwlwifi/iwl-7000.c | 146 + drivers/net/wireless/iwlwifi/iwl-agn-hw.h | 119 + drivers/net/wireless/iwlwifi/iwl-config.h | 326 + drivers/net/wireless/iwlwifi/iwl-csr.h | 475 + drivers/net/wireless/iwlwifi/iwl-debug.c | 152 + drivers/net/wireless/iwlwifi/iwl-debug.h | 187 + drivers/net/wireless/iwlwifi/iwl-devtrace.c | 50 + drivers/net/wireless/iwlwifi/iwl-devtrace.h | 514 + drivers/net/wireless/iwlwifi/iwl-drv.c | 1276 + drivers/net/wireless/iwlwifi/iwl-drv.h | 141 + drivers/net/wireless/iwlwifi/iwl-eeprom-parse.c | 932 + drivers/net/wireless/iwlwifi/iwl-eeprom-parse.h | 138 + drivers/net/wireless/iwlwifi/iwl-eeprom-read.c | 464 + drivers/net/wireless/iwlwifi/iwl-eeprom-read.h | 70 + drivers/net/wireless/iwlwifi/iwl-fh.h | 540 + drivers/net/wireless/iwlwifi/iwl-fw-file.h | 156 + drivers/net/wireless/iwlwifi/iwl-fw.h | 218 + drivers/net/wireless/iwlwifi/iwl-io.c | 168 + drivers/net/wireless/iwlwifi/iwl-io.h | 80 + drivers/net/wireless/iwlwifi/iwl-modparams.h | 124 + drivers/net/wireless/iwlwifi/iwl-notif-wait.c | 191 + drivers/net/wireless/iwlwifi/iwl-notif-wait.h | 138 + drivers/net/wireless/iwlwifi/iwl-nvm-parse.c | 393 + drivers/net/wireless/iwlwifi/iwl-nvm-parse.h | 80 + drivers/net/wireless/iwlwifi/iwl-op-mode.h | 231 + drivers/net/wireless/iwlwifi/iwl-phy-db.c | 499 + drivers/net/wireless/iwlwifi/iwl-phy-db.h | 82 + drivers/net/wireless/iwlwifi/iwl-prph.h | 263 + drivers/net/wireless/iwlwifi/iwl-test.c | 852 + drivers/net/wireless/iwlwifi/iwl-test.h | 161 + drivers/net/wireless/iwlwifi/iwl-testmode.h | 309 + drivers/net/wireless/iwlwifi/iwl-trans.h | 813 + drivers/net/wireless/iwlwifi/mvm/Makefile | 10 + drivers/net/wireless/iwlwifi/mvm/binding.c | 197 + drivers/net/wireless/iwlwifi/mvm/bt-coex.c | 589 + drivers/net/wireless/iwlwifi/mvm/d3.c | 1270 + drivers/net/wireless/iwlwifi/mvm/debugfs.c | 615 + drivers/net/wireless/iwlwifi/mvm/fw-api-bt-coex.h | 319 + drivers/net/wireless/iwlwifi/mvm/fw-api-d3.h | 333 + drivers/net/wireless/iwlwifi/mvm/fw-api-mac.h | 375 + drivers/net/wireless/iwlwifi/mvm/fw-api-power.h | 120 + drivers/net/wireless/iwlwifi/mvm/fw-api-rs.h | 312 + drivers/net/wireless/iwlwifi/mvm/fw-api-scan.h | 560 + drivers/net/wireless/iwlwifi/mvm/fw-api-sta.h | 380 + drivers/net/wireless/iwlwifi/mvm/fw-api-tx.h | 586 + drivers/net/wireless/iwlwifi/mvm/fw-api.h | 978 + drivers/net/wireless/iwlwifi/mvm/fw.c | 528 + drivers/net/wireless/iwlwifi/mvm/led.c | 136 + drivers/net/wireless/iwlwifi/mvm/mac-ctxt.c | 1050 + drivers/net/wireless/iwlwifi/mvm/mac80211.c | 1429 + drivers/net/wireless/iwlwifi/mvm/mvm.h | 538 + drivers/net/wireless/iwlwifi/mvm/nvm.c | 238 + drivers/net/wireless/iwlwifi/mvm/ops.c | 677 + drivers/net/wireless/iwlwifi/mvm/phy-ctxt.c | 286 + drivers/net/wireless/iwlwifi/mvm/power.c | 180 + drivers/net/wireless/iwlwifi/mvm/quota.c | 198 + drivers/net/wireless/iwlwifi/mvm/rs.c | 3083 ++ drivers/net/wireless/iwlwifi/mvm/rs.h | 393 + drivers/net/wireless/iwlwifi/mvm/rx.c | 365 + drivers/net/wireless/iwlwifi/mvm/scan.c | 441 + drivers/net/wireless/iwlwifi/mvm/sta.c | 1333 + drivers/net/wireless/iwlwifi/mvm/sta.h | 376 + drivers/net/wireless/iwlwifi/mvm/time-event.c | 527 + drivers/net/wireless/iwlwifi/mvm/time-event.h | 215 + drivers/net/wireless/iwlwifi/mvm/tx.c | 939 + drivers/net/wireless/iwlwifi/mvm/utils.c | 473 + drivers/net/wireless/iwlwifi/pcie/drv.c | 402 + drivers/net/wireless/iwlwifi/pcie/internal.h | 482 + drivers/net/wireless/iwlwifi/pcie/rx.c | 1306 + drivers/net/wireless/iwlwifi/pcie/trans.c | 1607 ++ drivers/net/wireless/iwlwifi/pcie/tx.c | 1745 ++ drivers/net/wireless/libertas/Kconfig | 52 + drivers/net/wireless/libertas/LICENSE | 16 + drivers/net/wireless/libertas/Makefile | 21 + drivers/net/wireless/libertas/README | 240 + drivers/net/wireless/libertas/cfg.c | 2223 ++ drivers/net/wireless/libertas/cfg.h | 20 + drivers/net/wireless/libertas/cmd.c | 1724 ++ drivers/net/wireless/libertas/cmd.h | 140 + drivers/net/wireless/libertas/cmdresp.c | 350 + drivers/net/wireless/libertas/debugfs.c | 986 + drivers/net/wireless/libertas/debugfs.h | 10 + drivers/net/wireless/libertas/decl.h | 82 + drivers/net/wireless/libertas/defs.h | 401 + drivers/net/wireless/libertas/dev.h | 211 + drivers/net/wireless/libertas/ethtool.c | 120 + drivers/net/wireless/libertas/firmware.c | 222 + drivers/net/wireless/libertas/host.h | 978 + drivers/net/wireless/libertas/if_cs.c | 1090 + drivers/net/wireless/libertas/if_sdio.c | 1463 + drivers/net/wireless/libertas/if_sdio.h | 52 + drivers/net/wireless/libertas/if_spi.c | 1328 + drivers/net/wireless/libertas/if_spi.h | 206 + drivers/net/wireless/libertas/if_usb.c | 1025 + drivers/net/wireless/libertas/if_usb.h | 106 + drivers/net/wireless/libertas/main.c | 1245 + drivers/net/wireless/libertas/mesh.c | 1188 + drivers/net/wireless/libertas/mesh.h | 77 + drivers/net/wireless/libertas/radiotap.h | 44 + drivers/net/wireless/libertas/rx.c | 284 + drivers/net/wireless/libertas/tx.c | 207 + drivers/net/wireless/libertas/types.h | 268 + drivers/net/wireless/libertas_tf/Makefile | 6 + drivers/net/wireless/libertas_tf/cmd.c | 807 + drivers/net/wireless/libertas_tf/deb_defs.h | 104 + drivers/net/wireless/libertas_tf/if_usb.c | 932 + drivers/net/wireless/libertas_tf/if_usb.h | 98 + drivers/net/wireless/libertas_tf/libertas_tf.h | 519 + drivers/net/wireless/libertas_tf/main.c | 785 + drivers/net/wireless/mac80211_hwsim.c | 2582 ++ drivers/net/wireless/mac80211_hwsim.h | 133 + drivers/net/wireless/mwifiex/11ac.c | 302 + drivers/net/wireless/mwifiex/11ac.h | 43 + drivers/net/wireless/mwifiex/11n.c | 703 + drivers/net/wireless/mwifiex/11n.h | 168 + drivers/net/wireless/mwifiex/11n_aggr.c | 304 + drivers/net/wireless/mwifiex/11n_aggr.h | 33 + drivers/net/wireless/mwifiex/11n_rxreorder.c | 667 + drivers/net/wireless/mwifiex/11n_rxreorder.h | 82 + drivers/net/wireless/mwifiex/Kconfig | 47 + drivers/net/wireless/mwifiex/Makefile | 53 + drivers/net/wireless/mwifiex/README | 203 + drivers/net/wireless/mwifiex/cfg80211.c | 2529 ++ drivers/net/wireless/mwifiex/cfg80211.h | 29 + drivers/net/wireless/mwifiex/cfp.c | 504 + drivers/net/wireless/mwifiex/cmdevt.c | 1546 + drivers/net/wireless/mwifiex/debugfs.c | 761 + drivers/net/wireless/mwifiex/decl.h | 154 + drivers/net/wireless/mwifiex/ethtool.c | 70 + drivers/net/wireless/mwifiex/fw.h | 1645 ++ drivers/net/wireless/mwifiex/ie.c | 443 + drivers/net/wireless/mwifiex/init.c | 811 + drivers/net/wireless/mwifiex/ioctl.h | 400 + drivers/net/wireless/mwifiex/join.c | 1463 + drivers/net/wireless/mwifiex/main.c | 918 + drivers/net/wireless/mwifiex/main.h | 1128 + drivers/net/wireless/mwifiex/pcie.c | 2434 ++ drivers/net/wireless/mwifiex/pcie.h | 320 + drivers/net/wireless/mwifiex/scan.c | 2071 ++ drivers/net/wireless/mwifiex/sdio.c | 1843 ++ drivers/net/wireless/mwifiex/sdio.h | 328 + drivers/net/wireless/mwifiex/sta_cmd.c | 1507 + drivers/net/wireless/mwifiex/sta_cmdresp.c | 990 + drivers/net/wireless/mwifiex/sta_event.c | 437 + drivers/net/wireless/mwifiex/sta_ioctl.c | 1380 + drivers/net/wireless/mwifiex/sta_rx.c | 209 + drivers/net/wireless/mwifiex/sta_tx.c | 218 + drivers/net/wireless/mwifiex/txrx.c | 201 + drivers/net/wireless/mwifiex/uap_cmd.c | 720 + drivers/net/wireless/mwifiex/uap_event.c | 297 + drivers/net/wireless/mwifiex/uap_txrx.c | 333 + drivers/net/wireless/mwifiex/usb.c | 1034 + drivers/net/wireless/mwifiex/usb.h | 99 + drivers/net/wireless/mwifiex/util.c | 253 + drivers/net/wireless/mwifiex/util.h | 37 + drivers/net/wireless/mwifiex/wmm.c | 1265 + drivers/net/wireless/mwifiex/wmm.h | 115 + drivers/net/wireless/mwl8k.c | 6193 ++++ drivers/net/wireless/orinoco/Kconfig | 150 + drivers/net/wireless/orinoco/Makefile | 17 + drivers/net/wireless/orinoco/airport.c | 267 + drivers/net/wireless/orinoco/cfg.c | 290 + drivers/net/wireless/orinoco/cfg.h | 15 + drivers/net/wireless/orinoco/fw.c | 387 + drivers/net/wireless/orinoco/fw.h | 21 + drivers/net/wireless/orinoco/hermes.c | 778 + drivers/net/wireless/orinoco/hermes.h | 520 + drivers/net/wireless/orinoco/hermes_dld.c | 477 + drivers/net/wireless/orinoco/hermes_dld.h | 52 + drivers/net/wireless/orinoco/hermes_rid.h | 165 + drivers/net/wireless/orinoco/hw.c | 1360 + drivers/net/wireless/orinoco/hw.h | 62 + drivers/net/wireless/orinoco/main.c | 2435 ++ drivers/net/wireless/orinoco/main.h | 50 + drivers/net/wireless/orinoco/mic.c | 79 + drivers/net/wireless/orinoco/mic.h | 22 + drivers/net/wireless/orinoco/orinoco.h | 256 + drivers/net/wireless/orinoco/orinoco_cs.c | 472 + drivers/net/wireless/orinoco/orinoco_nortel.c | 322 + drivers/net/wireless/orinoco/orinoco_pci.c | 265 + drivers/net/wireless/orinoco/orinoco_pci.h | 68 + drivers/net/wireless/orinoco/orinoco_plx.c | 370 + drivers/net/wireless/orinoco/orinoco_tmd.c | 248 + drivers/net/wireless/orinoco/orinoco_usb.c | 1770 ++ drivers/net/wireless/orinoco/scan.c | 248 + drivers/net/wireless/orinoco/scan.h | 21 + drivers/net/wireless/orinoco/spectrum_cs.c | 448 + drivers/net/wireless/orinoco/wext.c | 1427 + drivers/net/wireless/orinoco/wext.h | 13 + drivers/net/wireless/p54/Kconfig | 76 + drivers/net/wireless/p54/Makefile | 7 + drivers/net/wireless/p54/eeprom.c | 983 + drivers/net/wireless/p54/eeprom.h | 245 + drivers/net/wireless/p54/fwio.c | 763 + drivers/net/wireless/p54/led.c | 162 + drivers/net/wireless/p54/lmac.h | 562 + drivers/net/wireless/p54/main.c | 885 + drivers/net/wireless/p54/net2280.h | 452 + drivers/net/wireless/p54/p54.h | 281 + drivers/net/wireless/p54/p54pci.c | 710 + drivers/net/wireless/p54/p54pci.h | 112 + drivers/net/wireless/p54/p54spi.c | 742 + drivers/net/wireless/p54/p54spi.h | 125 + drivers/net/wireless/p54/p54spi_eeprom.h | 679 + drivers/net/wireless/p54/p54usb.c | 1149 + drivers/net/wireless/p54/p54usb.h | 151 + drivers/net/wireless/p54/txrx.c | 941 + drivers/net/wireless/rndis_wlan.c | 3771 +++ drivers/net/wireless/rt2x00/Kconfig | 257 + drivers/net/wireless/rt2x00/Makefile | 23 + drivers/net/wireless/rt2x00/rt2400pci.c | 1847 ++ drivers/net/wireless/rt2x00/rt2400pci.h | 963 + drivers/net/wireless/rt2x00/rt2500pci.c | 2135 ++ drivers/net/wireless/rt2x00/rt2500pci.h | 1237 + drivers/net/wireless/rt2x00/rt2500usb.c | 1998 ++ drivers/net/wireless/rt2x00/rt2500usb.h | 857 + drivers/net/wireless/rt2x00/rt2800.h | 2787 ++ drivers/net/wireless/rt2x00/rt2800lib.c | 6426 +++++ drivers/net/wireless/rt2x00/rt2800lib.h | 229 + drivers/net/wireless/rt2x00/rt2800pci.c | 1361 + drivers/net/wireless/rt2x00/rt2800pci.h | 141 + drivers/net/wireless/rt2x00/rt2800usb.c | 1383 + drivers/net/wireless/rt2x00/rt2800usb.h | 112 + drivers/net/wireless/rt2x00/rt2x00.h | 1393 + drivers/net/wireless/rt2x00/rt2x00config.c | 287 + drivers/net/wireless/rt2x00/rt2x00crypto.c | 258 + drivers/net/wireless/rt2x00/rt2x00debug.c | 802 + drivers/net/wireless/rt2x00/rt2x00debug.h | 71 + drivers/net/wireless/rt2x00/rt2x00dev.c | 1516 + drivers/net/wireless/rt2x00/rt2x00dump.h | 129 + drivers/net/wireless/rt2x00/rt2x00firmware.c | 131 + drivers/net/wireless/rt2x00/rt2x00leds.c | 246 + drivers/net/wireless/rt2x00/rt2x00leds.h | 46 + drivers/net/wireless/rt2x00/rt2x00lib.h | 470 + drivers/net/wireless/rt2x00/rt2x00link.c | 520 + drivers/net/wireless/rt2x00/rt2x00mac.c | 855 + drivers/net/wireless/rt2x00/rt2x00mmio.c | 216 + drivers/net/wireless/rt2x00/rt2x00mmio.h | 119 + drivers/net/wireless/rt2x00/rt2x00pci.c | 226 + drivers/net/wireless/rt2x00/rt2x00pci.h | 51 + drivers/net/wireless/rt2x00/rt2x00queue.c | 1364 + drivers/net/wireless/rt2x00/rt2x00queue.h | 704 + drivers/net/wireless/rt2x00/rt2x00reg.h | 279 + drivers/net/wireless/rt2x00/rt2x00soc.c | 162 + drivers/net/wireless/rt2x00/rt2x00soc.h | 42 + drivers/net/wireless/rt2x00/rt2x00usb.c | 888 + drivers/net/wireless/rt2x00/rt2x00usb.h | 442 + drivers/net/wireless/rt2x00/rt61pci.c | 3106 ++ drivers/net/wireless/rt2x00/rt61pci.h | 1502 + drivers/net/wireless/rt2x00/rt73usb.c | 2545 ++ drivers/net/wireless/rt2x00/rt73usb.h | 1081 + drivers/net/wireless/rtl818x/Kconfig | 90 + drivers/net/wireless/rtl818x/Makefile | 2 + drivers/net/wireless/rtl818x/rtl8180/Makefile | 5 + drivers/net/wireless/rtl818x/rtl8180/dev.c | 1189 + drivers/net/wireless/rtl818x/rtl8180/grf5101.c | 191 + drivers/net/wireless/rtl818x/rtl8180/grf5101.h | 28 + drivers/net/wireless/rtl818x/rtl8180/max2820.c | 169 + drivers/net/wireless/rtl818x/rtl8180/max2820.h | 28 + drivers/net/wireless/rtl818x/rtl8180/rtl8180.h | 130 + drivers/net/wireless/rtl818x/rtl8180/rtl8225.c | 792 + drivers/net/wireless/rtl818x/rtl8180/rtl8225.h | 23 + drivers/net/wireless/rtl818x/rtl8180/sa2400.c | 229 + drivers/net/wireless/rtl818x/rtl8180/sa2400.h | 36 + drivers/net/wireless/rtl818x/rtl8187/Makefile | 5 + drivers/net/wireless/rtl818x/rtl8187/dev.c | 1681 ++ drivers/net/wireless/rtl818x/rtl8187/leds.c | 245 + drivers/net/wireless/rtl818x/rtl8187/leds.h | 59 + drivers/net/wireless/rtl818x/rtl8187/rfkill.c | 64 + drivers/net/wireless/rtl818x/rtl8187/rfkill.h | 8 + drivers/net/wireless/rtl818x/rtl8187/rtl8187.h | 282 + drivers/net/wireless/rtl818x/rtl8187/rtl8225.c | 962 + drivers/net/wireless/rtl818x/rtl8187/rtl8225.h | 44 + drivers/net/wireless/rtl818x/rtl818x.h | 244 + drivers/net/wireless/rtlwifi/Kconfig | 88 + drivers/net/wireless/rtlwifi/Makefile | 31 + drivers/net/wireless/rtlwifi/base.c | 1881 ++ drivers/net/wireless/rtlwifi/base.h | 157 + drivers/net/wireless/rtlwifi/cam.c | 348 + drivers/net/wireless/rtlwifi/cam.h | 56 + drivers/net/wireless/rtlwifi/core.c | 1332 + drivers/net/wireless/rtlwifi/core.h | 45 + drivers/net/wireless/rtlwifi/debug.c | 53 + drivers/net/wireless/rtlwifi/debug.h | 244 + drivers/net/wireless/rtlwifi/efuse.c | 1237 + drivers/net/wireless/rtlwifi/efuse.h | 123 + drivers/net/wireless/rtlwifi/pci.c | 2120 ++ drivers/net/wireless/rtlwifi/pci.h | 280 + drivers/net/wireless/rtlwifi/ps.c | 936 + drivers/net/wireless/rtlwifi/ps.h | 54 + drivers/net/wireless/rtlwifi/rc.c | 282 + drivers/net/wireless/rtlwifi/rc.h | 47 + drivers/net/wireless/rtlwifi/regd.c | 430 + drivers/net/wireless/rtlwifi/regd.h | 61 + drivers/net/wireless/rtlwifi/rtl8188ee/Makefile | 16 + drivers/net/wireless/rtlwifi/rtl8188ee/def.h | 324 + drivers/net/wireless/rtlwifi/rtl8188ee/dm.c | 1794 ++ drivers/net/wireless/rtlwifi/rtl8188ee/dm.h | 326 + drivers/net/wireless/rtlwifi/rtl8188ee/fw.c | 830 + drivers/net/wireless/rtlwifi/rtl8188ee/fw.h | 301 + drivers/net/wireless/rtlwifi/rtl8188ee/hw.c | 2530 ++ drivers/net/wireless/rtlwifi/rtl8188ee/hw.h | 68 + drivers/net/wireless/rtlwifi/rtl8188ee/led.c | 157 + drivers/net/wireless/rtlwifi/rtl8188ee/led.h | 38 + drivers/net/wireless/rtlwifi/rtl8188ee/phy.c | 2202 ++ drivers/net/wireless/rtlwifi/rtl8188ee/phy.h | 236 + drivers/net/wireless/rtlwifi/rtl8188ee/pwrseq.c | 109 + drivers/net/wireless/rtlwifi/rtl8188ee/pwrseq.h | 327 + drivers/net/wireless/rtlwifi/rtl8188ee/pwrseqcmd.c | 140 + drivers/net/wireless/rtlwifi/rtl8188ee/pwrseqcmd.h | 97 + drivers/net/wireless/rtlwifi/rtl8188ee/reg.h | 2258 ++ drivers/net/wireless/rtlwifi/rtl8188ee/rf.c | 467 + drivers/net/wireless/rtlwifi/rtl8188ee/rf.h | 46 + drivers/net/wireless/rtlwifi/rtl8188ee/sw.c | 408 + drivers/net/wireless/rtlwifi/rtl8188ee/sw.h | 36 + drivers/net/wireless/rtlwifi/rtl8188ee/table.c | 643 + drivers/net/wireless/rtlwifi/rtl8188ee/table.h | 47 + drivers/net/wireless/rtlwifi/rtl8188ee/trx.c | 817 + drivers/net/wireless/rtlwifi/rtl8188ee/trx.h | 795 + drivers/net/wireless/rtlwifi/rtl8192c/Makefile | 9 + drivers/net/wireless/rtlwifi/rtl8192c/dm_common.c | 1701 ++ drivers/net/wireless/rtlwifi/rtl8192c/dm_common.h | 171 + drivers/net/wireless/rtlwifi/rtl8192c/fw_common.c | 907 + drivers/net/wireless/rtlwifi/rtl8192c/fw_common.h | 103 + drivers/net/wireless/rtlwifi/rtl8192c/main.c | 40 + drivers/net/wireless/rtlwifi/rtl8192c/phy_common.c | 1953 ++ drivers/net/wireless/rtlwifi/rtl8192c/phy_common.h | 258 + drivers/net/wireless/rtlwifi/rtl8192ce/Makefile | 13 + drivers/net/wireless/rtlwifi/rtl8192ce/def.h | 253 + drivers/net/wireless/rtlwifi/rtl8192ce/dm.c | 109 + drivers/net/wireless/rtlwifi/rtl8192ce/dm.h | 163 + drivers/net/wireless/rtlwifi/rtl8192ce/hw.c | 2428 ++ drivers/net/wireless/rtlwifi/rtl8192ce/hw.h | 82 + drivers/net/wireless/rtlwifi/rtl8192ce/led.c | 151 + drivers/net/wireless/rtlwifi/rtl8192ce/led.h | 38 + drivers/net/wireless/rtlwifi/rtl8192ce/phy.c | 622 + drivers/net/wireless/rtlwifi/rtl8192ce/phy.h | 262 + drivers/net/wireless/rtlwifi/rtl8192ce/reg.h | 2092 ++ drivers/net/wireless/rtlwifi/rtl8192ce/rf.c | 509 + drivers/net/wireless/rtlwifi/rtl8192ce/rf.h | 44 + drivers/net/wireless/rtlwifi/rtl8192ce/sw.c | 395 + drivers/net/wireless/rtlwifi/rtl8192ce/sw.h | 41 + drivers/net/wireless/rtlwifi/rtl8192ce/table.c | 1224 + drivers/net/wireless/rtlwifi/rtl8192ce/table.h | 58 + drivers/net/wireless/rtlwifi/rtl8192ce/trx.c | 754 + drivers/net/wireless/rtlwifi/rtl8192ce/trx.h | 729 + drivers/net/wireless/rtlwifi/rtl8192cu/Makefile | 14 + drivers/net/wireless/rtlwifi/rtl8192cu/def.h | 58 + drivers/net/wireless/rtlwifi/rtl8192cu/dm.c | 107 + drivers/net/wireless/rtlwifi/rtl8192cu/dm.h | 32 + drivers/net/wireless/rtlwifi/rtl8192cu/hw.c | 2357 ++ drivers/net/wireless/rtlwifi/rtl8192cu/hw.h | 113 + drivers/net/wireless/rtlwifi/rtl8192cu/led.c | 141 + drivers/net/wireless/rtlwifi/rtl8192cu/led.h | 37 + drivers/net/wireless/rtlwifi/rtl8192cu/mac.c | 1127 + drivers/net/wireless/rtlwifi/rtl8192cu/mac.h | 174 + drivers/net/wireless/rtlwifi/rtl8192cu/phy.c | 593 + drivers/net/wireless/rtlwifi/rtl8192cu/phy.h | 50 + drivers/net/wireless/rtlwifi/rtl8192cu/reg.h | 30 + drivers/net/wireless/rtlwifi/rtl8192cu/rf.c | 486 + drivers/net/wireless/rtlwifi/rtl8192cu/rf.h | 51 + drivers/net/wireless/rtlwifi/rtl8192cu/sw.c | 394 + drivers/net/wireless/rtlwifi/rtl8192cu/sw.h | 56 + drivers/net/wireless/rtlwifi/rtl8192cu/table.c | 1888 ++ drivers/net/wireless/rtlwifi/rtl8192cu/table.h | 71 + drivers/net/wireless/rtlwifi/rtl8192cu/trx.c | 689 + drivers/net/wireless/rtlwifi/rtl8192cu/trx.h | 435 + drivers/net/wireless/rtlwifi/rtl8192de/Makefile | 14 + drivers/net/wireless/rtlwifi/rtl8192de/def.h | 271 + drivers/net/wireless/rtlwifi/rtl8192de/dm.c | 1338 + drivers/net/wireless/rtlwifi/rtl8192de/dm.h | 161 + drivers/net/wireless/rtlwifi/rtl8192de/fw.c | 780 + drivers/net/wireless/rtlwifi/rtl8192de/fw.h | 155 + drivers/net/wireless/rtlwifi/rtl8192de/hw.c | 2323 ++ drivers/net/wireless/rtlwifi/rtl8192de/hw.h | 66 + drivers/net/wireless/rtlwifi/rtl8192de/led.c | 159 + drivers/net/wireless/rtlwifi/rtl8192de/led.h | 38 + drivers/net/wireless/rtlwifi/rtl8192de/phy.c | 3695 +++ drivers/net/wireless/rtlwifi/rtl8192de/phy.h | 178 + drivers/net/wireless/rtlwifi/rtl8192de/reg.h | 1312 + drivers/net/wireless/rtlwifi/rtl8192de/rf.c | 623 + drivers/net/wireless/rtlwifi/rtl8192de/rf.h | 44 + drivers/net/wireless/rtlwifi/rtl8192de/sw.c | 425 + drivers/net/wireless/rtlwifi/rtl8192de/sw.h | 37 + drivers/net/wireless/rtlwifi/rtl8192de/table.c | 1690 ++ drivers/net/wireless/rtlwifi/rtl8192de/table.h | 57 + drivers/net/wireless/rtlwifi/rtl8192de/trx.c | 872 + drivers/net/wireless/rtlwifi/rtl8192de/trx.h | 747 + drivers/net/wireless/rtlwifi/rtl8192se/Makefile | 15 + drivers/net/wireless/rtlwifi/rtl8192se/def.h | 554 + drivers/net/wireless/rtlwifi/rtl8192se/dm.c | 742 + drivers/net/wireless/rtlwifi/rtl8192se/dm.h | 120 + drivers/net/wireless/rtlwifi/rtl8192se/fw.c | 656 + drivers/net/wireless/rtlwifi/rtl8192se/fw.h | 376 + drivers/net/wireless/rtlwifi/rtl8192se/hw.c | 2552 ++ drivers/net/wireless/rtlwifi/rtl8192se/hw.h | 80 + drivers/net/wireless/rtlwifi/rtl8192se/led.c | 151 + drivers/net/wireless/rtlwifi/rtl8192se/led.h | 37 + drivers/net/wireless/rtlwifi/rtl8192se/phy.c | 1727 ++ drivers/net/wireless/rtlwifi/rtl8192se/phy.h | 102 + drivers/net/wireless/rtlwifi/rtl8192se/reg.h | 1185 + drivers/net/wireless/rtlwifi/rtl8192se/rf.c | 535 + drivers/net/wireless/rtlwifi/rtl8192se/rf.h | 43 + drivers/net/wireless/rtlwifi/rtl8192se/sw.c | 451 + drivers/net/wireless/rtlwifi/rtl8192se/sw.h | 36 + drivers/net/wireless/rtlwifi/rtl8192se/table.c | 634 + drivers/net/wireless/rtlwifi/rtl8192se/table.h | 49 + drivers/net/wireless/rtlwifi/rtl8192se/trx.c | 654 + drivers/net/wireless/rtlwifi/rtl8192se/trx.h | 46 + drivers/net/wireless/rtlwifi/rtl8723ae/Makefile | 22 + drivers/net/wireless/rtlwifi/rtl8723ae/btc.h | 41 + drivers/net/wireless/rtlwifi/rtl8723ae/def.h | 163 + drivers/net/wireless/rtlwifi/rtl8723ae/dm.c | 994 + drivers/net/wireless/rtlwifi/rtl8723ae/dm.h | 155 + drivers/net/wireless/rtlwifi/rtl8723ae/fw.c | 838 + drivers/net/wireless/rtlwifi/rtl8723ae/fw.h | 104 + .../wireless/rtlwifi/rtl8723ae/hal_bt_coexist.c | 542 + .../wireless/rtlwifi/rtl8723ae/hal_bt_coexist.h | 160 + drivers/net/wireless/rtlwifi/rtl8723ae/hal_btc.c | 1784 ++ drivers/net/wireless/rtlwifi/rtl8723ae/hal_btc.h | 151 + drivers/net/wireless/rtlwifi/rtl8723ae/hw.c | 2399 ++ drivers/net/wireless/rtlwifi/rtl8723ae/hw.h | 73 + drivers/net/wireless/rtlwifi/rtl8723ae/led.c | 157 + drivers/net/wireless/rtlwifi/rtl8723ae/led.h | 39 + drivers/net/wireless/rtlwifi/rtl8723ae/phy.c | 2028 ++ drivers/net/wireless/rtlwifi/rtl8723ae/phy.h | 224 + drivers/net/wireless/rtlwifi/rtl8723ae/pwrseq.c | 109 + drivers/net/wireless/rtlwifi/rtl8723ae/pwrseq.h | 322 + drivers/net/wireless/rtlwifi/rtl8723ae/pwrseqcmd.c | 129 + drivers/net/wireless/rtlwifi/rtl8723ae/pwrseqcmd.h | 98 + drivers/net/wireless/rtlwifi/rtl8723ae/reg.h | 2097 ++ drivers/net/wireless/rtlwifi/rtl8723ae/rf.c | 505 + drivers/net/wireless/rtlwifi/rtl8723ae/rf.h | 43 + drivers/net/wireless/rtlwifi/rtl8723ae/sw.c | 388 + drivers/net/wireless/rtlwifi/rtl8723ae/sw.h | 37 + drivers/net/wireless/rtlwifi/rtl8723ae/table.c | 738 + drivers/net/wireless/rtlwifi/rtl8723ae/table.h | 50 + drivers/net/wireless/rtlwifi/rtl8723ae/trx.c | 681 + drivers/net/wireless/rtlwifi/rtl8723ae/trx.h | 725 + drivers/net/wireless/rtlwifi/stats.c | 268 + drivers/net/wireless/rtlwifi/stats.h | 46 + drivers/net/wireless/rtlwifi/usb.c | 1167 + drivers/net/wireless/rtlwifi/usb.h | 169 + drivers/net/wireless/rtlwifi/wifi.h | 2479 ++ drivers/net/wireless/ti/Kconfig | 25 + drivers/net/wireless/ti/Makefile | 7 + drivers/net/wireless/ti/wilink_platform_data.c | 49 + drivers/net/wireless/ti/wl1251/Kconfig | 39 + drivers/net/wireless/ti/wl1251/Makefile | 10 + drivers/net/wireless/ti/wl1251/acx.c | 1097 + drivers/net/wireless/ti/wl1251/acx.h | 1483 + drivers/net/wireless/ti/wl1251/boot.c | 554 + drivers/net/wireless/ti/wl1251/boot.h | 39 + drivers/net/wireless/ti/wl1251/cmd.c | 487 + drivers/net/wireless/ti/wl1251/cmd.h | 415 + drivers/net/wireless/ti/wl1251/debugfs.c | 539 + drivers/net/wireless/ti/wl1251/debugfs.h | 31 + drivers/net/wireless/ti/wl1251/event.c | 191 + drivers/net/wireless/ti/wl1251/event.h | 120 + drivers/net/wireless/ti/wl1251/init.c | 423 + drivers/net/wireless/ti/wl1251/init.h | 86 + drivers/net/wireless/ti/wl1251/io.c | 194 + drivers/net/wireless/ti/wl1251/io.h | 83 + drivers/net/wireless/ti/wl1251/main.c | 1483 + drivers/net/wireless/ti/wl1251/ps.c | 184 + drivers/net/wireless/ti/wl1251/ps.h | 35 + drivers/net/wireless/ti/wl1251/reg.h | 655 + drivers/net/wireless/ti/wl1251/rx.c | 235 + drivers/net/wireless/ti/wl1251/rx.h | 122 + drivers/net/wireless/ti/wl1251/sdio.c | 376 + drivers/net/wireless/ti/wl1251/spi.c | 361 + drivers/net/wireless/ti/wl1251/spi.h | 59 + drivers/net/wireless/ti/wl1251/tx.c | 560 + drivers/net/wireless/ti/wl1251/tx.h | 231 + drivers/net/wireless/ti/wl1251/wl1251.h | 445 + drivers/net/wireless/ti/wl1251/wl12xx_80211.h | 155 + drivers/net/wireless/ti/wl12xx/Kconfig | 10 + drivers/net/wireless/ti/wl12xx/Makefile | 3 + drivers/net/wireless/ti/wl12xx/acx.c | 53 + drivers/net/wireless/ti/wl12xx/acx.h | 273 + drivers/net/wireless/ti/wl12xx/cmd.c | 323 + drivers/net/wireless/ti/wl12xx/cmd.h | 132 + drivers/net/wireless/ti/wl12xx/conf.h | 50 + drivers/net/wireless/ti/wl12xx/debugfs.c | 243 + drivers/net/wireless/ti/wl12xx/debugfs.h | 28 + drivers/net/wireless/ti/wl12xx/event.c | 116 + drivers/net/wireless/ti/wl12xx/event.h | 111 + drivers/net/wireless/ti/wl12xx/main.c | 1854 ++ drivers/net/wireless/ti/wl12xx/reg.h | 556 + drivers/net/wireless/ti/wl12xx/scan.c | 501 + drivers/net/wireless/ti/wl12xx/scan.h | 140 + drivers/net/wireless/ti/wl12xx/wl12xx.h | 82 + drivers/net/wireless/ti/wl18xx/Kconfig | 8 + drivers/net/wireless/ti/wl18xx/Makefile | 3 + drivers/net/wireless/ti/wl18xx/acx.c | 196 + drivers/net/wireless/ti/wl18xx/acx.h | 340 + drivers/net/wireless/ti/wl18xx/cmd.c | 80 + drivers/net/wireless/ti/wl18xx/cmd.h | 52 + drivers/net/wireless/ti/wl18xx/conf.h | 121 + drivers/net/wireless/ti/wl18xx/debugfs.c | 403 + drivers/net/wireless/ti/wl18xx/debugfs.h | 28 + drivers/net/wireless/ti/wl18xx/event.c | 111 + drivers/net/wireless/ti/wl18xx/event.h | 77 + drivers/net/wireless/ti/wl18xx/io.c | 75 + drivers/net/wireless/ti/wl18xx/io.h | 28 + drivers/net/wireless/ti/wl18xx/main.c | 1800 ++ drivers/net/wireless/ti/wl18xx/reg.h | 220 + drivers/net/wireless/ti/wl18xx/scan.c | 326 + drivers/net/wireless/ti/wl18xx/scan.h | 127 + drivers/net/wireless/ti/wl18xx/tx.c | 171 + drivers/net/wireless/ti/wl18xx/tx.h | 46 + drivers/net/wireless/ti/wl18xx/wl18xx.h | 140 + drivers/net/wireless/ti/wlcore/Kconfig | 41 + drivers/net/wireless/ti/wlcore/Makefile | 12 + drivers/net/wireless/ti/wlcore/acx.c | 1848 ++ drivers/net/wireless/ti/wlcore/acx.h | 1134 + drivers/net/wireless/ti/wlcore/boot.c | 533 + drivers/net/wireless/ti/wlcore/boot.h | 55 + drivers/net/wireless/ti/wlcore/cmd.c | 2025 ++ drivers/net/wireless/ti/wlcore/cmd.h | 698 + drivers/net/wireless/ti/wlcore/conf.h | 1384 + drivers/net/wireless/ti/wlcore/debug.h | 112 + drivers/net/wireless/ti/wlcore/debugfs.c | 1330 + drivers/net/wireless/ti/wlcore/debugfs.h | 120 + drivers/net/wireless/ti/wlcore/event.c | 304 + drivers/net/wireless/ti/wlcore/event.h | 87 + drivers/net/wireless/ti/wlcore/hw_ops.h | 245 + drivers/net/wireless/ti/wlcore/ini.h | 232 + drivers/net/wireless/ti/wlcore/init.c | 750 + drivers/net/wireless/ti/wlcore/init.h | 39 + drivers/net/wireless/ti/wlcore/io.c | 200 + drivers/net/wireless/ti/wlcore/io.h | 234 + drivers/net/wireless/ti/wlcore/main.c | 6266 ++++ drivers/net/wireless/ti/wlcore/ps.c | 328 + drivers/net/wireless/ti/wlcore/ps.h | 41 + drivers/net/wireless/ti/wlcore/rx.c | 339 + drivers/net/wireless/ti/wlcore/rx.h | 152 + drivers/net/wireless/ti/wlcore/scan.c | 470 + drivers/net/wireless/ti/wlcore/scan.h | 172 + drivers/net/wireless/ti/wlcore/sdio.c | 419 + drivers/net/wireless/ti/wlcore/spi.c | 453 + drivers/net/wireless/ti/wlcore/testmode.c | 386 + drivers/net/wireless/ti/wlcore/testmode.h | 31 + drivers/net/wireless/ti/wlcore/tx.c | 1305 + drivers/net/wireless/ti/wlcore/tx.h | 283 + drivers/net/wireless/ti/wlcore/wl12xx_80211.h | 137 + drivers/net/wireless/ti/wlcore/wlcore.h | 618 + drivers/net/wireless/ti/wlcore/wlcore_i.h | 544 + drivers/net/wireless/zd1211rw/Kconfig | 21 + drivers/net/wireless/zd1211rw/Makefile | 9 + drivers/net/wireless/zd1211rw/zd_chip.c | 1561 + drivers/net/wireless/zd1211rw/zd_chip.h | 984 + drivers/net/wireless/zd1211rw/zd_def.h | 70 + drivers/net/wireless/zd1211rw/zd_mac.c | 1569 + drivers/net/wireless/zd1211rw/zd_mac.h | 328 + drivers/net/wireless/zd1211rw/zd_rf.c | 182 + drivers/net/wireless/zd1211rw/zd_rf.h | 111 + drivers/net/wireless/zd1211rw/zd_rf_al2230.c | 444 + drivers/net/wireless/zd1211rw/zd_rf_al7230b.c | 495 + drivers/net/wireless/zd1211rw/zd_rf_rf2959.c | 282 + drivers/net/wireless/zd1211rw/zd_rf_uw2453.c | 540 + drivers/net/wireless/zd1211rw/zd_usb.c | 2063 ++ drivers/net/wireless/zd1211rw/zd_usb.h | 293 + 1061 files changed, 780539 insertions(+) create mode 100644 drivers/net/ethernet/Kconfig create mode 100644 drivers/net/ethernet/Makefile create mode 100644 drivers/net/ethernet/atheros/Kconfig create mode 100644 drivers/net/ethernet/atheros/Makefile create mode 100644 drivers/net/ethernet/atheros/alx/Makefile create mode 100644 drivers/net/ethernet/atheros/alx/alx.h create mode 100644 drivers/net/ethernet/atheros/alx/ethtool.c create mode 100644 drivers/net/ethernet/atheros/alx/hw.c create mode 100644 drivers/net/ethernet/atheros/alx/hw.h create mode 100644 drivers/net/ethernet/atheros/alx/main.c create mode 100644 drivers/net/ethernet/atheros/alx/reg.h create mode 100644 drivers/net/ethernet/atheros/atl1c/Makefile create mode 100644 drivers/net/ethernet/atheros/atl1c/atl1c.h create mode 100644 drivers/net/ethernet/atheros/atl1c/atl1c_ethtool.c create mode 100644 drivers/net/ethernet/atheros/atl1c/atl1c_hw.c create mode 100644 drivers/net/ethernet/atheros/atl1c/atl1c_hw.h create mode 100644 drivers/net/ethernet/atheros/atl1c/atl1c_main.c create mode 100644 drivers/net/ethernet/atheros/atl1e/Makefile create mode 100644 drivers/net/ethernet/atheros/atl1e/atl1e.h create mode 100644 drivers/net/ethernet/atheros/atl1e/atl1e_ethtool.c create mode 100644 drivers/net/ethernet/atheros/atl1e/atl1e_hw.c create mode 100644 drivers/net/ethernet/atheros/atl1e/atl1e_hw.h create mode 100644 drivers/net/ethernet/atheros/atl1e/atl1e_main.c create mode 100644 drivers/net/ethernet/atheros/atl1e/atl1e_param.c create mode 100644 drivers/net/ethernet/atheros/atlx/Makefile create mode 100644 drivers/net/ethernet/atheros/atlx/atl1.c create mode 100644 drivers/net/ethernet/atheros/atlx/atl1.h create mode 100644 drivers/net/ethernet/atheros/atlx/atl2.c create mode 100644 drivers/net/ethernet/atheros/atlx/atl2.h create mode 100644 drivers/net/ethernet/atheros/atlx/atlx.c create mode 100644 drivers/net/ethernet/atheros/atlx/atlx.h create mode 100644 drivers/net/ethernet/broadcom/Kconfig create mode 100644 drivers/net/ethernet/broadcom/Makefile create mode 100644 drivers/net/ethernet/broadcom/b44.c create mode 100644 drivers/net/ethernet/broadcom/b44.h create mode 100644 drivers/net/mdio.c create mode 100644 drivers/net/usb/Kconfig create mode 100644 drivers/net/usb/Makefile create mode 100644 drivers/net/usb/cdc_ether.c create mode 100644 drivers/net/usb/rndis_host.c create mode 100644 drivers/net/usb/usbnet.c create mode 100644 drivers/net/wireless/Kconfig create mode 100644 drivers/net/wireless/Makefile create mode 100644 drivers/net/wireless/adm8211.c create mode 100644 drivers/net/wireless/adm8211.h create mode 100644 drivers/net/wireless/at76c50x-usb.c create mode 100644 drivers/net/wireless/at76c50x-usb.h create mode 100644 drivers/net/wireless/ath/Kconfig create mode 100644 drivers/net/wireless/ath/Makefile create mode 100644 drivers/net/wireless/ath/ar5523/Kconfig create mode 100644 drivers/net/wireless/ath/ar5523/Makefile create mode 100644 drivers/net/wireless/ath/ar5523/ar5523.c create mode 100644 drivers/net/wireless/ath/ar5523/ar5523.h create mode 100644 drivers/net/wireless/ath/ar5523/ar5523_hw.h create mode 100644 drivers/net/wireless/ath/ath.h create mode 100644 drivers/net/wireless/ath/ath5k/Kconfig create mode 100644 drivers/net/wireless/ath/ath5k/Makefile create mode 100644 drivers/net/wireless/ath/ath5k/ahb.c create mode 100644 drivers/net/wireless/ath/ath5k/ani.c create mode 100644 drivers/net/wireless/ath/ath5k/ani.h create mode 100644 drivers/net/wireless/ath/ath5k/ath5k.h create mode 100644 drivers/net/wireless/ath/ath5k/attach.c create mode 100644 drivers/net/wireless/ath/ath5k/base.c create mode 100644 drivers/net/wireless/ath/ath5k/base.h create mode 100644 drivers/net/wireless/ath/ath5k/caps.c create mode 100644 drivers/net/wireless/ath/ath5k/debug.c create mode 100644 drivers/net/wireless/ath/ath5k/debug.h create mode 100644 drivers/net/wireless/ath/ath5k/desc.c create mode 100644 drivers/net/wireless/ath/ath5k/desc.h create mode 100644 drivers/net/wireless/ath/ath5k/dma.c create mode 100644 drivers/net/wireless/ath/ath5k/eeprom.c create mode 100644 drivers/net/wireless/ath/ath5k/eeprom.h create mode 100644 drivers/net/wireless/ath/ath5k/gpio.c create mode 100644 drivers/net/wireless/ath/ath5k/initvals.c create mode 100644 drivers/net/wireless/ath/ath5k/led.c create mode 100644 drivers/net/wireless/ath/ath5k/mac80211-ops.c create mode 100644 drivers/net/wireless/ath/ath5k/pci.c create mode 100644 drivers/net/wireless/ath/ath5k/pcu.c create mode 100644 drivers/net/wireless/ath/ath5k/phy.c create mode 100644 drivers/net/wireless/ath/ath5k/qcu.c create mode 100644 drivers/net/wireless/ath/ath5k/reg.h create mode 100644 drivers/net/wireless/ath/ath5k/reset.c create mode 100644 drivers/net/wireless/ath/ath5k/rfbuffer.h create mode 100644 drivers/net/wireless/ath/ath5k/rfgain.h create mode 100644 drivers/net/wireless/ath/ath5k/rfkill.c create mode 100644 drivers/net/wireless/ath/ath5k/sysfs.c create mode 100644 drivers/net/wireless/ath/ath5k/trace.h create mode 100644 drivers/net/wireless/ath/ath6kl/Kconfig create mode 100644 drivers/net/wireless/ath/ath6kl/Makefile create mode 100644 drivers/net/wireless/ath/ath6kl/bmi.c create mode 100644 drivers/net/wireless/ath/ath6kl/bmi.h create mode 100644 drivers/net/wireless/ath/ath6kl/cfg80211.c create mode 100644 drivers/net/wireless/ath/ath6kl/cfg80211.h create mode 100644 drivers/net/wireless/ath/ath6kl/common.h create mode 100644 drivers/net/wireless/ath/ath6kl/core.c create mode 100644 drivers/net/wireless/ath/ath6kl/core.h create mode 100644 drivers/net/wireless/ath/ath6kl/debug.c create mode 100644 drivers/net/wireless/ath/ath6kl/debug.h create mode 100644 drivers/net/wireless/ath/ath6kl/hif-ops.h create mode 100644 drivers/net/wireless/ath/ath6kl/hif.c create mode 100644 drivers/net/wireless/ath/ath6kl/hif.h create mode 100644 drivers/net/wireless/ath/ath6kl/htc-ops.h create mode 100644 drivers/net/wireless/ath/ath6kl/htc.h create mode 100644 drivers/net/wireless/ath/ath6kl/htc_mbox.c create mode 100644 drivers/net/wireless/ath/ath6kl/htc_pipe.c create mode 100644 drivers/net/wireless/ath/ath6kl/init.c create mode 100644 drivers/net/wireless/ath/ath6kl/main.c create mode 100644 drivers/net/wireless/ath/ath6kl/recovery.c create mode 100644 drivers/net/wireless/ath/ath6kl/sdio.c create mode 100644 drivers/net/wireless/ath/ath6kl/target.h create mode 100644 drivers/net/wireless/ath/ath6kl/testmode.c create mode 100644 drivers/net/wireless/ath/ath6kl/testmode.h create mode 100644 drivers/net/wireless/ath/ath6kl/trace.c create mode 100644 drivers/net/wireless/ath/ath6kl/trace.h create mode 100644 drivers/net/wireless/ath/ath6kl/txrx.c create mode 100644 drivers/net/wireless/ath/ath6kl/usb.c create mode 100644 drivers/net/wireless/ath/ath6kl/wmi.c create mode 100644 drivers/net/wireless/ath/ath6kl/wmi.h create mode 100644 drivers/net/wireless/ath/ath9k/Kconfig create mode 100644 drivers/net/wireless/ath/ath9k/Makefile create mode 100644 drivers/net/wireless/ath/ath9k/ahb.c create mode 100644 drivers/net/wireless/ath/ath9k/ani.c create mode 100644 drivers/net/wireless/ath/ath9k/ani.h create mode 100644 drivers/net/wireless/ath/ath9k/antenna.c create mode 100644 drivers/net/wireless/ath/ath9k/ar5008_initvals.h create mode 100644 drivers/net/wireless/ath/ath9k/ar5008_phy.c create mode 100644 drivers/net/wireless/ath/ath9k/ar9001_initvals.h create mode 100644 drivers/net/wireless/ath/ath9k/ar9002_calib.c create mode 100644 drivers/net/wireless/ath/ath9k/ar9002_hw.c create mode 100644 drivers/net/wireless/ath/ath9k/ar9002_initvals.h create mode 100644 drivers/net/wireless/ath/ath9k/ar9002_mac.c create mode 100644 drivers/net/wireless/ath/ath9k/ar9002_phy.c create mode 100644 drivers/net/wireless/ath/ath9k/ar9002_phy.h create mode 100644 drivers/net/wireless/ath/ath9k/ar9003_2p2_initvals.h create mode 100644 drivers/net/wireless/ath/ath9k/ar9003_calib.c create mode 100644 drivers/net/wireless/ath/ath9k/ar9003_eeprom.c create mode 100644 drivers/net/wireless/ath/ath9k/ar9003_eeprom.h create mode 100644 drivers/net/wireless/ath/ath9k/ar9003_hw.c create mode 100644 drivers/net/wireless/ath/ath9k/ar9003_mac.c create mode 100644 drivers/net/wireless/ath/ath9k/ar9003_mac.h create mode 100644 drivers/net/wireless/ath/ath9k/ar9003_mci.c create mode 100644 drivers/net/wireless/ath/ath9k/ar9003_mci.h create mode 100644 drivers/net/wireless/ath/ath9k/ar9003_paprd.c create mode 100644 drivers/net/wireless/ath/ath9k/ar9003_phy.c create mode 100644 drivers/net/wireless/ath/ath9k/ar9003_phy.h create mode 100644 drivers/net/wireless/ath/ath9k/ar9003_rtt.c create mode 100644 drivers/net/wireless/ath/ath9k/ar9003_rtt.h create mode 100644 drivers/net/wireless/ath/ath9k/ar9330_1p1_initvals.h create mode 100644 drivers/net/wireless/ath/ath9k/ar9330_1p2_initvals.h create mode 100644 drivers/net/wireless/ath/ath9k/ar9340_initvals.h create mode 100644 drivers/net/wireless/ath/ath9k/ar9462_2p0_initvals.h create mode 100644 drivers/net/wireless/ath/ath9k/ar9485_initvals.h create mode 100644 drivers/net/wireless/ath/ath9k/ar955x_1p0_initvals.h create mode 100644 drivers/net/wireless/ath/ath9k/ar9565_1p0_initvals.h create mode 100644 drivers/net/wireless/ath/ath9k/ar9580_1p0_initvals.h create mode 100644 drivers/net/wireless/ath/ath9k/ath9k.h create mode 100644 drivers/net/wireless/ath/ath9k/beacon.c create mode 100644 drivers/net/wireless/ath/ath9k/btcoex.c create mode 100644 drivers/net/wireless/ath/ath9k/btcoex.h create mode 100644 drivers/net/wireless/ath/ath9k/calib.c create mode 100644 drivers/net/wireless/ath/ath9k/calib.h create mode 100644 drivers/net/wireless/ath/ath9k/common.c create mode 100644 drivers/net/wireless/ath/ath9k/common.h create mode 100644 drivers/net/wireless/ath/ath9k/debug.c create mode 100644 drivers/net/wireless/ath/ath9k/debug.h create mode 100644 drivers/net/wireless/ath/ath9k/dfs.c create mode 100644 drivers/net/wireless/ath/ath9k/dfs.h create mode 100644 drivers/net/wireless/ath/ath9k/dfs_debug.c create mode 100644 drivers/net/wireless/ath/ath9k/dfs_debug.h create mode 100644 drivers/net/wireless/ath/ath9k/dfs_pattern_detector.c create mode 100644 drivers/net/wireless/ath/ath9k/dfs_pattern_detector.h create mode 100644 drivers/net/wireless/ath/ath9k/dfs_pri_detector.c create mode 100644 drivers/net/wireless/ath/ath9k/dfs_pri_detector.h create mode 100644 drivers/net/wireless/ath/ath9k/eeprom.c create mode 100644 drivers/net/wireless/ath/ath9k/eeprom.h create mode 100644 drivers/net/wireless/ath/ath9k/eeprom_4k.c create mode 100644 drivers/net/wireless/ath/ath9k/eeprom_9287.c create mode 100644 drivers/net/wireless/ath/ath9k/eeprom_def.c create mode 100644 drivers/net/wireless/ath/ath9k/gpio.c create mode 100644 drivers/net/wireless/ath/ath9k/hif_usb.c create mode 100644 drivers/net/wireless/ath/ath9k/hif_usb.h create mode 100644 drivers/net/wireless/ath/ath9k/htc.h create mode 100644 drivers/net/wireless/ath/ath9k/htc_drv_beacon.c create mode 100644 drivers/net/wireless/ath/ath9k/htc_drv_debug.c create mode 100644 drivers/net/wireless/ath/ath9k/htc_drv_gpio.c create mode 100644 drivers/net/wireless/ath/ath9k/htc_drv_init.c create mode 100644 drivers/net/wireless/ath/ath9k/htc_drv_main.c create mode 100644 drivers/net/wireless/ath/ath9k/htc_drv_txrx.c create mode 100644 drivers/net/wireless/ath/ath9k/htc_hst.c create mode 100644 drivers/net/wireless/ath/ath9k/htc_hst.h create mode 100644 drivers/net/wireless/ath/ath9k/hw-ops.h create mode 100644 drivers/net/wireless/ath/ath9k/hw.c create mode 100644 drivers/net/wireless/ath/ath9k/hw.h create mode 100644 drivers/net/wireless/ath/ath9k/init.c create mode 100644 drivers/net/wireless/ath/ath9k/link.c create mode 100644 drivers/net/wireless/ath/ath9k/mac.c create mode 100644 drivers/net/wireless/ath/ath9k/mac.h create mode 100644 drivers/net/wireless/ath/ath9k/main.c create mode 100644 drivers/net/wireless/ath/ath9k/mci.c create mode 100644 drivers/net/wireless/ath/ath9k/mci.h create mode 100644 drivers/net/wireless/ath/ath9k/pci.c create mode 100644 drivers/net/wireless/ath/ath9k/phy.h create mode 100644 drivers/net/wireless/ath/ath9k/rc.c create mode 100644 drivers/net/wireless/ath/ath9k/rc.h create mode 100644 drivers/net/wireless/ath/ath9k/recv.c create mode 100644 drivers/net/wireless/ath/ath9k/reg.h create mode 100644 drivers/net/wireless/ath/ath9k/wmi.c create mode 100644 drivers/net/wireless/ath/ath9k/wmi.h create mode 100644 drivers/net/wireless/ath/ath9k/wow.c create mode 100644 drivers/net/wireless/ath/ath9k/xmit.c create mode 100644 drivers/net/wireless/ath/carl9170/Kconfig create mode 100644 drivers/net/wireless/ath/carl9170/Makefile create mode 100644 drivers/net/wireless/ath/carl9170/carl9170.h create mode 100644 drivers/net/wireless/ath/carl9170/cmd.c create mode 100644 drivers/net/wireless/ath/carl9170/cmd.h create mode 100644 drivers/net/wireless/ath/carl9170/debug.c create mode 100644 drivers/net/wireless/ath/carl9170/debug.h create mode 100644 drivers/net/wireless/ath/carl9170/eeprom.h create mode 100644 drivers/net/wireless/ath/carl9170/fw.c create mode 100644 drivers/net/wireless/ath/carl9170/fwcmd.h create mode 100644 drivers/net/wireless/ath/carl9170/fwdesc.h create mode 100644 drivers/net/wireless/ath/carl9170/hw.h create mode 100644 drivers/net/wireless/ath/carl9170/led.c create mode 100644 drivers/net/wireless/ath/carl9170/mac.c create mode 100644 drivers/net/wireless/ath/carl9170/main.c create mode 100644 drivers/net/wireless/ath/carl9170/phy.c create mode 100644 drivers/net/wireless/ath/carl9170/phy.h create mode 100644 drivers/net/wireless/ath/carl9170/rx.c create mode 100644 drivers/net/wireless/ath/carl9170/tx.c create mode 100644 drivers/net/wireless/ath/carl9170/usb.c create mode 100644 drivers/net/wireless/ath/carl9170/version.h create mode 100644 drivers/net/wireless/ath/carl9170/wlan.h create mode 100644 drivers/net/wireless/ath/debug.c create mode 100644 drivers/net/wireless/ath/hw.c create mode 100644 drivers/net/wireless/ath/key.c create mode 100644 drivers/net/wireless/ath/main.c create mode 100644 drivers/net/wireless/ath/reg.h create mode 100644 drivers/net/wireless/ath/regd.c create mode 100644 drivers/net/wireless/ath/regd.h create mode 100644 drivers/net/wireless/ath/regd_common.h create mode 100644 drivers/net/wireless/ath/wil6210/Kconfig create mode 100644 drivers/net/wireless/ath/wil6210/Makefile create mode 100644 drivers/net/wireless/ath/wil6210/cfg80211.c create mode 100644 drivers/net/wireless/ath/wil6210/debugfs.c create mode 100644 drivers/net/wireless/ath/wil6210/interrupt.c create mode 100644 drivers/net/wireless/ath/wil6210/main.c create mode 100644 drivers/net/wireless/ath/wil6210/netdev.c create mode 100644 drivers/net/wireless/ath/wil6210/pcie_bus.c create mode 100644 drivers/net/wireless/ath/wil6210/txrx.c create mode 100644 drivers/net/wireless/ath/wil6210/txrx.h create mode 100644 drivers/net/wireless/ath/wil6210/wil6210.h create mode 100644 drivers/net/wireless/ath/wil6210/wmi.c create mode 100644 drivers/net/wireless/ath/wil6210/wmi.h create mode 100644 drivers/net/wireless/b43/Kconfig create mode 100644 drivers/net/wireless/b43/Makefile create mode 100644 drivers/net/wireless/b43/b43.h create mode 100644 drivers/net/wireless/b43/bus.c create mode 100644 drivers/net/wireless/b43/bus.h create mode 100644 drivers/net/wireless/b43/debugfs.c create mode 100644 drivers/net/wireless/b43/debugfs.h create mode 100644 drivers/net/wireless/b43/dma.c create mode 100644 drivers/net/wireless/b43/dma.h create mode 100644 drivers/net/wireless/b43/leds.c create mode 100644 drivers/net/wireless/b43/leds.h create mode 100644 drivers/net/wireless/b43/lo.c create mode 100644 drivers/net/wireless/b43/lo.h create mode 100644 drivers/net/wireless/b43/main.c create mode 100644 drivers/net/wireless/b43/main.h create mode 100644 drivers/net/wireless/b43/pcmcia.c create mode 100644 drivers/net/wireless/b43/pcmcia.h create mode 100644 drivers/net/wireless/b43/phy_a.c create mode 100644 drivers/net/wireless/b43/phy_a.h create mode 100644 drivers/net/wireless/b43/phy_common.c create mode 100644 drivers/net/wireless/b43/phy_common.h create mode 100644 drivers/net/wireless/b43/phy_g.c create mode 100644 drivers/net/wireless/b43/phy_g.h create mode 100644 drivers/net/wireless/b43/phy_ht.c create mode 100644 drivers/net/wireless/b43/phy_ht.h create mode 100644 drivers/net/wireless/b43/phy_lcn.c create mode 100644 drivers/net/wireless/b43/phy_lcn.h create mode 100644 drivers/net/wireless/b43/phy_lp.c create mode 100644 drivers/net/wireless/b43/phy_lp.h create mode 100644 drivers/net/wireless/b43/phy_n.c create mode 100644 drivers/net/wireless/b43/phy_n.h create mode 100644 drivers/net/wireless/b43/pio.c create mode 100644 drivers/net/wireless/b43/pio.h create mode 100644 drivers/net/wireless/b43/radio_2055.c create mode 100644 drivers/net/wireless/b43/radio_2055.h create mode 100644 drivers/net/wireless/b43/radio_2056.c create mode 100644 drivers/net/wireless/b43/radio_2056.h create mode 100644 drivers/net/wireless/b43/radio_2057.c create mode 100644 drivers/net/wireless/b43/radio_2057.h create mode 100644 drivers/net/wireless/b43/radio_2059.c create mode 100644 drivers/net/wireless/b43/radio_2059.h create mode 100644 drivers/net/wireless/b43/rfkill.c create mode 100644 drivers/net/wireless/b43/rfkill.h create mode 100644 drivers/net/wireless/b43/sdio.c create mode 100644 drivers/net/wireless/b43/sdio.h create mode 100644 drivers/net/wireless/b43/sysfs.c create mode 100644 drivers/net/wireless/b43/sysfs.h create mode 100644 drivers/net/wireless/b43/tables.c create mode 100644 drivers/net/wireless/b43/tables.h create mode 100644 drivers/net/wireless/b43/tables_lpphy.c create mode 100644 drivers/net/wireless/b43/tables_lpphy.h create mode 100644 drivers/net/wireless/b43/tables_nphy.c create mode 100644 drivers/net/wireless/b43/tables_nphy.h create mode 100644 drivers/net/wireless/b43/tables_phy_ht.c create mode 100644 drivers/net/wireless/b43/tables_phy_ht.h create mode 100644 drivers/net/wireless/b43/tables_phy_lcn.c create mode 100644 drivers/net/wireless/b43/tables_phy_lcn.h create mode 100644 drivers/net/wireless/b43/wa.c create mode 100644 drivers/net/wireless/b43/wa.h create mode 100644 drivers/net/wireless/b43/xmit.c create mode 100644 drivers/net/wireless/b43/xmit.h create mode 100644 drivers/net/wireless/b43legacy/Kconfig create mode 100644 drivers/net/wireless/b43legacy/Makefile create mode 100644 drivers/net/wireless/b43legacy/b43legacy.h create mode 100644 drivers/net/wireless/b43legacy/debugfs.c create mode 100644 drivers/net/wireless/b43legacy/debugfs.h create mode 100644 drivers/net/wireless/b43legacy/dma.c create mode 100644 drivers/net/wireless/b43legacy/dma.h create mode 100644 drivers/net/wireless/b43legacy/ilt.c create mode 100644 drivers/net/wireless/b43legacy/ilt.h create mode 100644 drivers/net/wireless/b43legacy/leds.c create mode 100644 drivers/net/wireless/b43legacy/leds.h create mode 100644 drivers/net/wireless/b43legacy/main.c create mode 100644 drivers/net/wireless/b43legacy/main.h create mode 100644 drivers/net/wireless/b43legacy/phy.c create mode 100644 drivers/net/wireless/b43legacy/phy.h create mode 100644 drivers/net/wireless/b43legacy/pio.c create mode 100644 drivers/net/wireless/b43legacy/pio.h create mode 100644 drivers/net/wireless/b43legacy/radio.c create mode 100644 drivers/net/wireless/b43legacy/radio.h create mode 100644 drivers/net/wireless/b43legacy/rfkill.c create mode 100644 drivers/net/wireless/b43legacy/rfkill.h create mode 100644 drivers/net/wireless/b43legacy/sysfs.c create mode 100644 drivers/net/wireless/b43legacy/sysfs.h create mode 100644 drivers/net/wireless/b43legacy/xmit.c create mode 100644 drivers/net/wireless/b43legacy/xmit.h create mode 100644 drivers/net/wireless/brcm80211/Kconfig create mode 100644 drivers/net/wireless/brcm80211/Makefile create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/Makefile create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/btcoex.c create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/btcoex.h create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/dhd.h create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/dhd_bus.h create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/dhd_cdc.c create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/dhd_common.c create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/dhd_dbg.c create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/dhd_dbg.h create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/dhd_linux.c create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/dhd_proto.h create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/fweh.c create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/fweh.h create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/fwil.c create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/fwil.h create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/fwil_types.h create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/fwsignal.c create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/fwsignal.h create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/p2p.c create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/p2p.h create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/sdio_chip.c create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/sdio_chip.h create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/tracepoint.c create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/tracepoint.h create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/usb.c create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/usb.h create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/usb_rdl.h create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/wl_cfg80211.c create mode 100644 drivers/net/wireless/brcm80211/brcmfmac/wl_cfg80211.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/Makefile create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/aiutils.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/aiutils.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/ampdu.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/ampdu.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/antsel.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/antsel.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/brcms_trace_events.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/brcms_trace_events.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/channel.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/channel.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/d11.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/debug.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/debug.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/dma.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/dma.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/led.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/led.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/mac80211_if.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/mac80211_if.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/main.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/main.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/phy/phy_cmn.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/phy/phy_hal.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/phy/phy_int.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/phy/phy_lcn.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/phy/phy_lcn.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/phy/phy_n.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/phy/phy_qmath.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/phy/phy_qmath.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/phy/phy_radio.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/phy/phyreg_n.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/phy/phytbl_lcn.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/phy/phytbl_lcn.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/phy/phytbl_n.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/phy/phytbl_n.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/phy_shim.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/phy_shim.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/pmu.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/pmu.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/pub.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/rate.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/rate.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/scb.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/stf.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/stf.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/types.h create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/ucode_loader.c create mode 100644 drivers/net/wireless/brcm80211/brcmsmac/ucode_loader.h create mode 100644 drivers/net/wireless/brcm80211/brcmutil/Makefile create mode 100644 drivers/net/wireless/brcm80211/brcmutil/d11.c create mode 100644 drivers/net/wireless/brcm80211/brcmutil/utils.c create mode 100644 drivers/net/wireless/brcm80211/include/brcm_hw_ids.h create mode 100644 drivers/net/wireless/brcm80211/include/brcmu_d11.h create mode 100644 drivers/net/wireless/brcm80211/include/brcmu_utils.h create mode 100644 drivers/net/wireless/brcm80211/include/brcmu_wifi.h create mode 100644 drivers/net/wireless/brcm80211/include/chipcommon.h create mode 100644 drivers/net/wireless/brcm80211/include/defs.h create mode 100644 drivers/net/wireless/brcm80211/include/soc.h create mode 100644 drivers/net/wireless/ipw2x00/Kconfig create mode 100644 drivers/net/wireless/ipw2x00/Makefile create mode 100644 drivers/net/wireless/ipw2x00/ipw.h create mode 100644 drivers/net/wireless/ipw2x00/ipw2100.c create mode 100644 drivers/net/wireless/ipw2x00/ipw2100.h create mode 100644 drivers/net/wireless/ipw2x00/ipw2200.c create mode 100644 drivers/net/wireless/ipw2x00/ipw2200.h create mode 100644 drivers/net/wireless/ipw2x00/libipw.h create mode 100644 drivers/net/wireless/ipw2x00/libipw_geo.c create mode 100644 drivers/net/wireless/ipw2x00/libipw_module.c create mode 100644 drivers/net/wireless/ipw2x00/libipw_rx.c create mode 100644 drivers/net/wireless/ipw2x00/libipw_tx.c create mode 100644 drivers/net/wireless/ipw2x00/libipw_wx.c create mode 100644 drivers/net/wireless/iwlegacy/3945-debug.c create mode 100644 drivers/net/wireless/iwlegacy/3945-mac.c create mode 100644 drivers/net/wireless/iwlegacy/3945-rs.c create mode 100644 drivers/net/wireless/iwlegacy/3945.c create mode 100644 drivers/net/wireless/iwlegacy/3945.h create mode 100644 drivers/net/wireless/iwlegacy/4965-calib.c create mode 100644 drivers/net/wireless/iwlegacy/4965-debug.c create mode 100644 drivers/net/wireless/iwlegacy/4965-mac.c create mode 100644 drivers/net/wireless/iwlegacy/4965-rs.c create mode 100644 drivers/net/wireless/iwlegacy/4965.c create mode 100644 drivers/net/wireless/iwlegacy/4965.h create mode 100644 drivers/net/wireless/iwlegacy/Kconfig create mode 100644 drivers/net/wireless/iwlegacy/Makefile create mode 100644 drivers/net/wireless/iwlegacy/commands.h create mode 100644 drivers/net/wireless/iwlegacy/common.c create mode 100644 drivers/net/wireless/iwlegacy/common.h create mode 100644 drivers/net/wireless/iwlegacy/csr.h create mode 100644 drivers/net/wireless/iwlegacy/debug.c create mode 100644 drivers/net/wireless/iwlegacy/iwl-spectrum.h create mode 100644 drivers/net/wireless/iwlegacy/prph.h create mode 100644 drivers/net/wireless/iwlwifi/Kconfig create mode 100644 drivers/net/wireless/iwlwifi/Makefile create mode 100644 drivers/net/wireless/iwlwifi/dvm/Makefile create mode 100644 drivers/net/wireless/iwlwifi/dvm/agn.h create mode 100644 drivers/net/wireless/iwlwifi/dvm/calib.c create mode 100644 drivers/net/wireless/iwlwifi/dvm/calib.h create mode 100644 drivers/net/wireless/iwlwifi/dvm/commands.h create mode 100644 drivers/net/wireless/iwlwifi/dvm/debugfs.c create mode 100644 drivers/net/wireless/iwlwifi/dvm/dev.h create mode 100644 drivers/net/wireless/iwlwifi/dvm/devices.c create mode 100644 drivers/net/wireless/iwlwifi/dvm/led.c create mode 100644 drivers/net/wireless/iwlwifi/dvm/led.h create mode 100644 drivers/net/wireless/iwlwifi/dvm/lib.c create mode 100644 drivers/net/wireless/iwlwifi/dvm/mac80211.c create mode 100644 drivers/net/wireless/iwlwifi/dvm/main.c create mode 100644 drivers/net/wireless/iwlwifi/dvm/power.c create mode 100644 drivers/net/wireless/iwlwifi/dvm/power.h create mode 100644 drivers/net/wireless/iwlwifi/dvm/rs.c create mode 100644 drivers/net/wireless/iwlwifi/dvm/rs.h create mode 100644 drivers/net/wireless/iwlwifi/dvm/rx.c create mode 100644 drivers/net/wireless/iwlwifi/dvm/rxon.c create mode 100644 drivers/net/wireless/iwlwifi/dvm/scan.c create mode 100644 drivers/net/wireless/iwlwifi/dvm/sta.c create mode 100644 drivers/net/wireless/iwlwifi/dvm/testmode.c create mode 100644 drivers/net/wireless/iwlwifi/dvm/tt.c create mode 100644 drivers/net/wireless/iwlwifi/dvm/tt.h create mode 100644 drivers/net/wireless/iwlwifi/dvm/tx.c create mode 100644 drivers/net/wireless/iwlwifi/dvm/ucode.c create mode 100644 drivers/net/wireless/iwlwifi/iwl-1000.c create mode 100644 drivers/net/wireless/iwlwifi/iwl-2000.c create mode 100644 drivers/net/wireless/iwlwifi/iwl-5000.c create mode 100644 drivers/net/wireless/iwlwifi/iwl-6000.c create mode 100644 drivers/net/wireless/iwlwifi/iwl-7000.c create mode 100644 drivers/net/wireless/iwlwifi/iwl-agn-hw.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-config.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-csr.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-debug.c create mode 100644 drivers/net/wireless/iwlwifi/iwl-debug.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-devtrace.c create mode 100644 drivers/net/wireless/iwlwifi/iwl-devtrace.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-drv.c create mode 100644 drivers/net/wireless/iwlwifi/iwl-drv.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-eeprom-parse.c create mode 100644 drivers/net/wireless/iwlwifi/iwl-eeprom-parse.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-eeprom-read.c create mode 100644 drivers/net/wireless/iwlwifi/iwl-eeprom-read.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-fh.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-fw-file.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-fw.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-io.c create mode 100644 drivers/net/wireless/iwlwifi/iwl-io.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-modparams.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-notif-wait.c create mode 100644 drivers/net/wireless/iwlwifi/iwl-notif-wait.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-nvm-parse.c create mode 100644 drivers/net/wireless/iwlwifi/iwl-nvm-parse.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-op-mode.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-phy-db.c create mode 100644 drivers/net/wireless/iwlwifi/iwl-phy-db.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-prph.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-test.c create mode 100644 drivers/net/wireless/iwlwifi/iwl-test.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-testmode.h create mode 100644 drivers/net/wireless/iwlwifi/iwl-trans.h create mode 100644 drivers/net/wireless/iwlwifi/mvm/Makefile create mode 100644 drivers/net/wireless/iwlwifi/mvm/binding.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/bt-coex.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/d3.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/debugfs.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/fw-api-bt-coex.h create mode 100644 drivers/net/wireless/iwlwifi/mvm/fw-api-d3.h create mode 100644 drivers/net/wireless/iwlwifi/mvm/fw-api-mac.h create mode 100644 drivers/net/wireless/iwlwifi/mvm/fw-api-power.h create mode 100644 drivers/net/wireless/iwlwifi/mvm/fw-api-rs.h create mode 100644 drivers/net/wireless/iwlwifi/mvm/fw-api-scan.h create mode 100644 drivers/net/wireless/iwlwifi/mvm/fw-api-sta.h create mode 100644 drivers/net/wireless/iwlwifi/mvm/fw-api-tx.h create mode 100644 drivers/net/wireless/iwlwifi/mvm/fw-api.h create mode 100644 drivers/net/wireless/iwlwifi/mvm/fw.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/led.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/mac-ctxt.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/mac80211.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/mvm.h create mode 100644 drivers/net/wireless/iwlwifi/mvm/nvm.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/ops.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/phy-ctxt.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/power.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/quota.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/rs.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/rs.h create mode 100644 drivers/net/wireless/iwlwifi/mvm/rx.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/scan.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/sta.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/sta.h create mode 100644 drivers/net/wireless/iwlwifi/mvm/time-event.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/time-event.h create mode 100644 drivers/net/wireless/iwlwifi/mvm/tx.c create mode 100644 drivers/net/wireless/iwlwifi/mvm/utils.c create mode 100644 drivers/net/wireless/iwlwifi/pcie/drv.c create mode 100644 drivers/net/wireless/iwlwifi/pcie/internal.h create mode 100644 drivers/net/wireless/iwlwifi/pcie/rx.c create mode 100644 drivers/net/wireless/iwlwifi/pcie/trans.c create mode 100644 drivers/net/wireless/iwlwifi/pcie/tx.c create mode 100644 drivers/net/wireless/libertas/Kconfig create mode 100644 drivers/net/wireless/libertas/LICENSE create mode 100644 drivers/net/wireless/libertas/Makefile create mode 100644 drivers/net/wireless/libertas/README create mode 100644 drivers/net/wireless/libertas/cfg.c create mode 100644 drivers/net/wireless/libertas/cfg.h create mode 100644 drivers/net/wireless/libertas/cmd.c create mode 100644 drivers/net/wireless/libertas/cmd.h create mode 100644 drivers/net/wireless/libertas/cmdresp.c create mode 100644 drivers/net/wireless/libertas/debugfs.c create mode 100644 drivers/net/wireless/libertas/debugfs.h create mode 100644 drivers/net/wireless/libertas/decl.h create mode 100644 drivers/net/wireless/libertas/defs.h create mode 100644 drivers/net/wireless/libertas/dev.h create mode 100644 drivers/net/wireless/libertas/ethtool.c create mode 100644 drivers/net/wireless/libertas/firmware.c create mode 100644 drivers/net/wireless/libertas/host.h create mode 100644 drivers/net/wireless/libertas/if_cs.c create mode 100644 drivers/net/wireless/libertas/if_sdio.c create mode 100644 drivers/net/wireless/libertas/if_sdio.h create mode 100644 drivers/net/wireless/libertas/if_spi.c create mode 100644 drivers/net/wireless/libertas/if_spi.h create mode 100644 drivers/net/wireless/libertas/if_usb.c create mode 100644 drivers/net/wireless/libertas/if_usb.h create mode 100644 drivers/net/wireless/libertas/main.c create mode 100644 drivers/net/wireless/libertas/mesh.c create mode 100644 drivers/net/wireless/libertas/mesh.h create mode 100644 drivers/net/wireless/libertas/radiotap.h create mode 100644 drivers/net/wireless/libertas/rx.c create mode 100644 drivers/net/wireless/libertas/tx.c create mode 100644 drivers/net/wireless/libertas/types.h create mode 100644 drivers/net/wireless/libertas_tf/Makefile create mode 100644 drivers/net/wireless/libertas_tf/cmd.c create mode 100644 drivers/net/wireless/libertas_tf/deb_defs.h create mode 100644 drivers/net/wireless/libertas_tf/if_usb.c create mode 100644 drivers/net/wireless/libertas_tf/if_usb.h create mode 100644 drivers/net/wireless/libertas_tf/libertas_tf.h create mode 100644 drivers/net/wireless/libertas_tf/main.c create mode 100644 drivers/net/wireless/mac80211_hwsim.c create mode 100644 drivers/net/wireless/mac80211_hwsim.h create mode 100644 drivers/net/wireless/mwifiex/11ac.c create mode 100644 drivers/net/wireless/mwifiex/11ac.h create mode 100644 drivers/net/wireless/mwifiex/11n.c create mode 100644 drivers/net/wireless/mwifiex/11n.h create mode 100644 drivers/net/wireless/mwifiex/11n_aggr.c create mode 100644 drivers/net/wireless/mwifiex/11n_aggr.h create mode 100644 drivers/net/wireless/mwifiex/11n_rxreorder.c create mode 100644 drivers/net/wireless/mwifiex/11n_rxreorder.h create mode 100644 drivers/net/wireless/mwifiex/Kconfig create mode 100644 drivers/net/wireless/mwifiex/Makefile create mode 100644 drivers/net/wireless/mwifiex/README create mode 100644 drivers/net/wireless/mwifiex/cfg80211.c create mode 100644 drivers/net/wireless/mwifiex/cfg80211.h create mode 100644 drivers/net/wireless/mwifiex/cfp.c create mode 100644 drivers/net/wireless/mwifiex/cmdevt.c create mode 100644 drivers/net/wireless/mwifiex/debugfs.c create mode 100644 drivers/net/wireless/mwifiex/decl.h create mode 100644 drivers/net/wireless/mwifiex/ethtool.c create mode 100644 drivers/net/wireless/mwifiex/fw.h create mode 100644 drivers/net/wireless/mwifiex/ie.c create mode 100644 drivers/net/wireless/mwifiex/init.c create mode 100644 drivers/net/wireless/mwifiex/ioctl.h create mode 100644 drivers/net/wireless/mwifiex/join.c create mode 100644 drivers/net/wireless/mwifiex/main.c create mode 100644 drivers/net/wireless/mwifiex/main.h create mode 100644 drivers/net/wireless/mwifiex/pcie.c create mode 100644 drivers/net/wireless/mwifiex/pcie.h create mode 100644 drivers/net/wireless/mwifiex/scan.c create mode 100644 drivers/net/wireless/mwifiex/sdio.c create mode 100644 drivers/net/wireless/mwifiex/sdio.h create mode 100644 drivers/net/wireless/mwifiex/sta_cmd.c create mode 100644 drivers/net/wireless/mwifiex/sta_cmdresp.c create mode 100644 drivers/net/wireless/mwifiex/sta_event.c create mode 100644 drivers/net/wireless/mwifiex/sta_ioctl.c create mode 100644 drivers/net/wireless/mwifiex/sta_rx.c create mode 100644 drivers/net/wireless/mwifiex/sta_tx.c create mode 100644 drivers/net/wireless/mwifiex/txrx.c create mode 100644 drivers/net/wireless/mwifiex/uap_cmd.c create mode 100644 drivers/net/wireless/mwifiex/uap_event.c create mode 100644 drivers/net/wireless/mwifiex/uap_txrx.c create mode 100644 drivers/net/wireless/mwifiex/usb.c create mode 100644 drivers/net/wireless/mwifiex/usb.h create mode 100644 drivers/net/wireless/mwifiex/util.c create mode 100644 drivers/net/wireless/mwifiex/util.h create mode 100644 drivers/net/wireless/mwifiex/wmm.c create mode 100644 drivers/net/wireless/mwifiex/wmm.h create mode 100644 drivers/net/wireless/mwl8k.c create mode 100644 drivers/net/wireless/orinoco/Kconfig create mode 100644 drivers/net/wireless/orinoco/Makefile create mode 100644 drivers/net/wireless/orinoco/airport.c create mode 100644 drivers/net/wireless/orinoco/cfg.c create mode 100644 drivers/net/wireless/orinoco/cfg.h create mode 100644 drivers/net/wireless/orinoco/fw.c create mode 100644 drivers/net/wireless/orinoco/fw.h create mode 100644 drivers/net/wireless/orinoco/hermes.c create mode 100644 drivers/net/wireless/orinoco/hermes.h create mode 100644 drivers/net/wireless/orinoco/hermes_dld.c create mode 100644 drivers/net/wireless/orinoco/hermes_dld.h create mode 100644 drivers/net/wireless/orinoco/hermes_rid.h create mode 100644 drivers/net/wireless/orinoco/hw.c create mode 100644 drivers/net/wireless/orinoco/hw.h create mode 100644 drivers/net/wireless/orinoco/main.c create mode 100644 drivers/net/wireless/orinoco/main.h create mode 100644 drivers/net/wireless/orinoco/mic.c create mode 100644 drivers/net/wireless/orinoco/mic.h create mode 100644 drivers/net/wireless/orinoco/orinoco.h create mode 100644 drivers/net/wireless/orinoco/orinoco_cs.c create mode 100644 drivers/net/wireless/orinoco/orinoco_nortel.c create mode 100644 drivers/net/wireless/orinoco/orinoco_pci.c create mode 100644 drivers/net/wireless/orinoco/orinoco_pci.h create mode 100644 drivers/net/wireless/orinoco/orinoco_plx.c create mode 100644 drivers/net/wireless/orinoco/orinoco_tmd.c create mode 100644 drivers/net/wireless/orinoco/orinoco_usb.c create mode 100644 drivers/net/wireless/orinoco/scan.c create mode 100644 drivers/net/wireless/orinoco/scan.h create mode 100644 drivers/net/wireless/orinoco/spectrum_cs.c create mode 100644 drivers/net/wireless/orinoco/wext.c create mode 100644 drivers/net/wireless/orinoco/wext.h create mode 100644 drivers/net/wireless/p54/Kconfig create mode 100644 drivers/net/wireless/p54/Makefile create mode 100644 drivers/net/wireless/p54/eeprom.c create mode 100644 drivers/net/wireless/p54/eeprom.h create mode 100644 drivers/net/wireless/p54/fwio.c create mode 100644 drivers/net/wireless/p54/led.c create mode 100644 drivers/net/wireless/p54/lmac.h create mode 100644 drivers/net/wireless/p54/main.c create mode 100644 drivers/net/wireless/p54/net2280.h create mode 100644 drivers/net/wireless/p54/p54.h create mode 100644 drivers/net/wireless/p54/p54pci.c create mode 100644 drivers/net/wireless/p54/p54pci.h create mode 100644 drivers/net/wireless/p54/p54spi.c create mode 100644 drivers/net/wireless/p54/p54spi.h create mode 100644 drivers/net/wireless/p54/p54spi_eeprom.h create mode 100644 drivers/net/wireless/p54/p54usb.c create mode 100644 drivers/net/wireless/p54/p54usb.h create mode 100644 drivers/net/wireless/p54/txrx.c create mode 100644 drivers/net/wireless/rndis_wlan.c create mode 100644 drivers/net/wireless/rt2x00/Kconfig create mode 100644 drivers/net/wireless/rt2x00/Makefile create mode 100644 drivers/net/wireless/rt2x00/rt2400pci.c create mode 100644 drivers/net/wireless/rt2x00/rt2400pci.h create mode 100644 drivers/net/wireless/rt2x00/rt2500pci.c create mode 100644 drivers/net/wireless/rt2x00/rt2500pci.h create mode 100644 drivers/net/wireless/rt2x00/rt2500usb.c create mode 100644 drivers/net/wireless/rt2x00/rt2500usb.h create mode 100644 drivers/net/wireless/rt2x00/rt2800.h create mode 100644 drivers/net/wireless/rt2x00/rt2800lib.c create mode 100644 drivers/net/wireless/rt2x00/rt2800lib.h create mode 100644 drivers/net/wireless/rt2x00/rt2800pci.c create mode 100644 drivers/net/wireless/rt2x00/rt2800pci.h create mode 100644 drivers/net/wireless/rt2x00/rt2800usb.c create mode 100644 drivers/net/wireless/rt2x00/rt2800usb.h create mode 100644 drivers/net/wireless/rt2x00/rt2x00.h create mode 100644 drivers/net/wireless/rt2x00/rt2x00config.c create mode 100644 drivers/net/wireless/rt2x00/rt2x00crypto.c create mode 100644 drivers/net/wireless/rt2x00/rt2x00debug.c create mode 100644 drivers/net/wireless/rt2x00/rt2x00debug.h create mode 100644 drivers/net/wireless/rt2x00/rt2x00dev.c create mode 100644 drivers/net/wireless/rt2x00/rt2x00dump.h create mode 100644 drivers/net/wireless/rt2x00/rt2x00firmware.c create mode 100644 drivers/net/wireless/rt2x00/rt2x00leds.c create mode 100644 drivers/net/wireless/rt2x00/rt2x00leds.h create mode 100644 drivers/net/wireless/rt2x00/rt2x00lib.h create mode 100644 drivers/net/wireless/rt2x00/rt2x00link.c create mode 100644 drivers/net/wireless/rt2x00/rt2x00mac.c create mode 100644 drivers/net/wireless/rt2x00/rt2x00mmio.c create mode 100644 drivers/net/wireless/rt2x00/rt2x00mmio.h create mode 100644 drivers/net/wireless/rt2x00/rt2x00pci.c create mode 100644 drivers/net/wireless/rt2x00/rt2x00pci.h create mode 100644 drivers/net/wireless/rt2x00/rt2x00queue.c create mode 100644 drivers/net/wireless/rt2x00/rt2x00queue.h create mode 100644 drivers/net/wireless/rt2x00/rt2x00reg.h create mode 100644 drivers/net/wireless/rt2x00/rt2x00soc.c create mode 100644 drivers/net/wireless/rt2x00/rt2x00soc.h create mode 100644 drivers/net/wireless/rt2x00/rt2x00usb.c create mode 100644 drivers/net/wireless/rt2x00/rt2x00usb.h create mode 100644 drivers/net/wireless/rt2x00/rt61pci.c create mode 100644 drivers/net/wireless/rt2x00/rt61pci.h create mode 100644 drivers/net/wireless/rt2x00/rt73usb.c create mode 100644 drivers/net/wireless/rt2x00/rt73usb.h create mode 100644 drivers/net/wireless/rtl818x/Kconfig create mode 100644 drivers/net/wireless/rtl818x/Makefile create mode 100644 drivers/net/wireless/rtl818x/rtl8180/Makefile create mode 100644 drivers/net/wireless/rtl818x/rtl8180/dev.c create mode 100644 drivers/net/wireless/rtl818x/rtl8180/grf5101.c create mode 100644 drivers/net/wireless/rtl818x/rtl8180/grf5101.h create mode 100644 drivers/net/wireless/rtl818x/rtl8180/max2820.c create mode 100644 drivers/net/wireless/rtl818x/rtl8180/max2820.h create mode 100644 drivers/net/wireless/rtl818x/rtl8180/rtl8180.h create mode 100644 drivers/net/wireless/rtl818x/rtl8180/rtl8225.c create mode 100644 drivers/net/wireless/rtl818x/rtl8180/rtl8225.h create mode 100644 drivers/net/wireless/rtl818x/rtl8180/sa2400.c create mode 100644 drivers/net/wireless/rtl818x/rtl8180/sa2400.h create mode 100644 drivers/net/wireless/rtl818x/rtl8187/Makefile create mode 100644 drivers/net/wireless/rtl818x/rtl8187/dev.c create mode 100644 drivers/net/wireless/rtl818x/rtl8187/leds.c create mode 100644 drivers/net/wireless/rtl818x/rtl8187/leds.h create mode 100644 drivers/net/wireless/rtl818x/rtl8187/rfkill.c create mode 100644 drivers/net/wireless/rtl818x/rtl8187/rfkill.h create mode 100644 drivers/net/wireless/rtl818x/rtl8187/rtl8187.h create mode 100644 drivers/net/wireless/rtl818x/rtl8187/rtl8225.c create mode 100644 drivers/net/wireless/rtl818x/rtl8187/rtl8225.h create mode 100644 drivers/net/wireless/rtl818x/rtl818x.h create mode 100644 drivers/net/wireless/rtlwifi/Kconfig create mode 100644 drivers/net/wireless/rtlwifi/Makefile create mode 100644 drivers/net/wireless/rtlwifi/base.c create mode 100644 drivers/net/wireless/rtlwifi/base.h create mode 100644 drivers/net/wireless/rtlwifi/cam.c create mode 100644 drivers/net/wireless/rtlwifi/cam.h create mode 100644 drivers/net/wireless/rtlwifi/core.c create mode 100644 drivers/net/wireless/rtlwifi/core.h create mode 100644 drivers/net/wireless/rtlwifi/debug.c create mode 100644 drivers/net/wireless/rtlwifi/debug.h create mode 100644 drivers/net/wireless/rtlwifi/efuse.c create mode 100644 drivers/net/wireless/rtlwifi/efuse.h create mode 100644 drivers/net/wireless/rtlwifi/pci.c create mode 100644 drivers/net/wireless/rtlwifi/pci.h create mode 100644 drivers/net/wireless/rtlwifi/ps.c create mode 100644 drivers/net/wireless/rtlwifi/ps.h create mode 100644 drivers/net/wireless/rtlwifi/rc.c create mode 100644 drivers/net/wireless/rtlwifi/rc.h create mode 100644 drivers/net/wireless/rtlwifi/regd.c create mode 100644 drivers/net/wireless/rtlwifi/regd.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/Makefile create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/def.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/dm.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/dm.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/fw.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/fw.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/hw.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/hw.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/led.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/led.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/phy.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/phy.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/pwrseq.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/pwrseq.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/pwrseqcmd.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/pwrseqcmd.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/reg.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/rf.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/rf.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/sw.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/sw.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/table.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/table.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/trx.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8188ee/trx.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192c/Makefile create mode 100644 drivers/net/wireless/rtlwifi/rtl8192c/dm_common.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192c/dm_common.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192c/fw_common.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192c/fw_common.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192c/main.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192c/phy_common.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192c/phy_common.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/Makefile create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/def.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/dm.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/dm.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/hw.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/hw.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/led.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/led.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/phy.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/phy.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/reg.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/rf.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/rf.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/sw.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/sw.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/table.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/table.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/trx.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192ce/trx.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/Makefile create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/def.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/dm.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/dm.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/hw.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/hw.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/led.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/led.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/mac.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/mac.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/phy.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/phy.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/reg.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/rf.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/rf.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/sw.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/sw.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/table.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/table.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/trx.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192cu/trx.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/Makefile create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/def.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/dm.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/dm.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/fw.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/fw.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/hw.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/hw.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/led.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/led.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/phy.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/phy.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/reg.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/rf.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/rf.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/sw.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/sw.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/table.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/table.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/trx.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192de/trx.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/Makefile create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/def.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/dm.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/dm.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/fw.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/fw.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/hw.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/hw.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/led.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/led.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/phy.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/phy.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/reg.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/rf.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/rf.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/sw.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/sw.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/table.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/table.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/trx.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8192se/trx.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/Makefile create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/btc.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/def.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/dm.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/dm.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/fw.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/fw.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/hal_bt_coexist.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/hal_bt_coexist.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/hal_btc.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/hal_btc.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/hw.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/hw.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/led.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/led.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/phy.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/phy.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/pwrseq.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/pwrseq.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/pwrseqcmd.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/pwrseqcmd.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/reg.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/rf.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/rf.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/sw.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/sw.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/table.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/table.h create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/trx.c create mode 100644 drivers/net/wireless/rtlwifi/rtl8723ae/trx.h create mode 100644 drivers/net/wireless/rtlwifi/stats.c create mode 100644 drivers/net/wireless/rtlwifi/stats.h create mode 100644 drivers/net/wireless/rtlwifi/usb.c create mode 100644 drivers/net/wireless/rtlwifi/usb.h create mode 100644 drivers/net/wireless/rtlwifi/wifi.h create mode 100644 drivers/net/wireless/ti/Kconfig create mode 100644 drivers/net/wireless/ti/Makefile create mode 100644 drivers/net/wireless/ti/wilink_platform_data.c create mode 100644 drivers/net/wireless/ti/wl1251/Kconfig create mode 100644 drivers/net/wireless/ti/wl1251/Makefile create mode 100644 drivers/net/wireless/ti/wl1251/acx.c create mode 100644 drivers/net/wireless/ti/wl1251/acx.h create mode 100644 drivers/net/wireless/ti/wl1251/boot.c create mode 100644 drivers/net/wireless/ti/wl1251/boot.h create mode 100644 drivers/net/wireless/ti/wl1251/cmd.c create mode 100644 drivers/net/wireless/ti/wl1251/cmd.h create mode 100644 drivers/net/wireless/ti/wl1251/debugfs.c create mode 100644 drivers/net/wireless/ti/wl1251/debugfs.h create mode 100644 drivers/net/wireless/ti/wl1251/event.c create mode 100644 drivers/net/wireless/ti/wl1251/event.h create mode 100644 drivers/net/wireless/ti/wl1251/init.c create mode 100644 drivers/net/wireless/ti/wl1251/init.h create mode 100644 drivers/net/wireless/ti/wl1251/io.c create mode 100644 drivers/net/wireless/ti/wl1251/io.h create mode 100644 drivers/net/wireless/ti/wl1251/main.c create mode 100644 drivers/net/wireless/ti/wl1251/ps.c create mode 100644 drivers/net/wireless/ti/wl1251/ps.h create mode 100644 drivers/net/wireless/ti/wl1251/reg.h create mode 100644 drivers/net/wireless/ti/wl1251/rx.c create mode 100644 drivers/net/wireless/ti/wl1251/rx.h create mode 100644 drivers/net/wireless/ti/wl1251/sdio.c create mode 100644 drivers/net/wireless/ti/wl1251/spi.c create mode 100644 drivers/net/wireless/ti/wl1251/spi.h create mode 100644 drivers/net/wireless/ti/wl1251/tx.c create mode 100644 drivers/net/wireless/ti/wl1251/tx.h create mode 100644 drivers/net/wireless/ti/wl1251/wl1251.h create mode 100644 drivers/net/wireless/ti/wl1251/wl12xx_80211.h create mode 100644 drivers/net/wireless/ti/wl12xx/Kconfig create mode 100644 drivers/net/wireless/ti/wl12xx/Makefile create mode 100644 drivers/net/wireless/ti/wl12xx/acx.c create mode 100644 drivers/net/wireless/ti/wl12xx/acx.h create mode 100644 drivers/net/wireless/ti/wl12xx/cmd.c create mode 100644 drivers/net/wireless/ti/wl12xx/cmd.h create mode 100644 drivers/net/wireless/ti/wl12xx/conf.h create mode 100644 drivers/net/wireless/ti/wl12xx/debugfs.c create mode 100644 drivers/net/wireless/ti/wl12xx/debugfs.h create mode 100644 drivers/net/wireless/ti/wl12xx/event.c create mode 100644 drivers/net/wireless/ti/wl12xx/event.h create mode 100644 drivers/net/wireless/ti/wl12xx/main.c create mode 100644 drivers/net/wireless/ti/wl12xx/reg.h create mode 100644 drivers/net/wireless/ti/wl12xx/scan.c create mode 100644 drivers/net/wireless/ti/wl12xx/scan.h create mode 100644 drivers/net/wireless/ti/wl12xx/wl12xx.h create mode 100644 drivers/net/wireless/ti/wl18xx/Kconfig create mode 100644 drivers/net/wireless/ti/wl18xx/Makefile create mode 100644 drivers/net/wireless/ti/wl18xx/acx.c create mode 100644 drivers/net/wireless/ti/wl18xx/acx.h create mode 100644 drivers/net/wireless/ti/wl18xx/cmd.c create mode 100644 drivers/net/wireless/ti/wl18xx/cmd.h create mode 100644 drivers/net/wireless/ti/wl18xx/conf.h create mode 100644 drivers/net/wireless/ti/wl18xx/debugfs.c create mode 100644 drivers/net/wireless/ti/wl18xx/debugfs.h create mode 100644 drivers/net/wireless/ti/wl18xx/event.c create mode 100644 drivers/net/wireless/ti/wl18xx/event.h create mode 100644 drivers/net/wireless/ti/wl18xx/io.c create mode 100644 drivers/net/wireless/ti/wl18xx/io.h create mode 100644 drivers/net/wireless/ti/wl18xx/main.c create mode 100644 drivers/net/wireless/ti/wl18xx/reg.h create mode 100644 drivers/net/wireless/ti/wl18xx/scan.c create mode 100644 drivers/net/wireless/ti/wl18xx/scan.h create mode 100644 drivers/net/wireless/ti/wl18xx/tx.c create mode 100644 drivers/net/wireless/ti/wl18xx/tx.h create mode 100644 drivers/net/wireless/ti/wl18xx/wl18xx.h create mode 100644 drivers/net/wireless/ti/wlcore/Kconfig create mode 100644 drivers/net/wireless/ti/wlcore/Makefile create mode 100644 drivers/net/wireless/ti/wlcore/acx.c create mode 100644 drivers/net/wireless/ti/wlcore/acx.h create mode 100644 drivers/net/wireless/ti/wlcore/boot.c create mode 100644 drivers/net/wireless/ti/wlcore/boot.h create mode 100644 drivers/net/wireless/ti/wlcore/cmd.c create mode 100644 drivers/net/wireless/ti/wlcore/cmd.h create mode 100644 drivers/net/wireless/ti/wlcore/conf.h create mode 100644 drivers/net/wireless/ti/wlcore/debug.h create mode 100644 drivers/net/wireless/ti/wlcore/debugfs.c create mode 100644 drivers/net/wireless/ti/wlcore/debugfs.h create mode 100644 drivers/net/wireless/ti/wlcore/event.c create mode 100644 drivers/net/wireless/ti/wlcore/event.h create mode 100644 drivers/net/wireless/ti/wlcore/hw_ops.h create mode 100644 drivers/net/wireless/ti/wlcore/ini.h create mode 100644 drivers/net/wireless/ti/wlcore/init.c create mode 100644 drivers/net/wireless/ti/wlcore/init.h create mode 100644 drivers/net/wireless/ti/wlcore/io.c create mode 100644 drivers/net/wireless/ti/wlcore/io.h create mode 100644 drivers/net/wireless/ti/wlcore/main.c create mode 100644 drivers/net/wireless/ti/wlcore/ps.c create mode 100644 drivers/net/wireless/ti/wlcore/ps.h create mode 100644 drivers/net/wireless/ti/wlcore/rx.c create mode 100644 drivers/net/wireless/ti/wlcore/rx.h create mode 100644 drivers/net/wireless/ti/wlcore/scan.c create mode 100644 drivers/net/wireless/ti/wlcore/scan.h create mode 100644 drivers/net/wireless/ti/wlcore/sdio.c create mode 100644 drivers/net/wireless/ti/wlcore/spi.c create mode 100644 drivers/net/wireless/ti/wlcore/testmode.c create mode 100644 drivers/net/wireless/ti/wlcore/testmode.h create mode 100644 drivers/net/wireless/ti/wlcore/tx.c create mode 100644 drivers/net/wireless/ti/wlcore/tx.h create mode 100644 drivers/net/wireless/ti/wlcore/wl12xx_80211.h create mode 100644 drivers/net/wireless/ti/wlcore/wlcore.h create mode 100644 drivers/net/wireless/ti/wlcore/wlcore_i.h create mode 100644 drivers/net/wireless/zd1211rw/Kconfig create mode 100644 drivers/net/wireless/zd1211rw/Makefile create mode 100644 drivers/net/wireless/zd1211rw/zd_chip.c create mode 100644 drivers/net/wireless/zd1211rw/zd_chip.h create mode 100644 drivers/net/wireless/zd1211rw/zd_def.h create mode 100644 drivers/net/wireless/zd1211rw/zd_mac.c create mode 100644 drivers/net/wireless/zd1211rw/zd_mac.h create mode 100644 drivers/net/wireless/zd1211rw/zd_rf.c create mode 100644 drivers/net/wireless/zd1211rw/zd_rf.h create mode 100644 drivers/net/wireless/zd1211rw/zd_rf_al2230.c create mode 100644 drivers/net/wireless/zd1211rw/zd_rf_al7230b.c create mode 100644 drivers/net/wireless/zd1211rw/zd_rf_rf2959.c create mode 100644 drivers/net/wireless/zd1211rw/zd_rf_uw2453.c create mode 100644 drivers/net/wireless/zd1211rw/zd_usb.c create mode 100644 drivers/net/wireless/zd1211rw/zd_usb.h (limited to 'drivers/net') diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig new file mode 100644 index 0000000..fced295 --- /dev/null +++ b/drivers/net/ethernet/Kconfig @@ -0,0 +1,189 @@ +# +# Ethernet LAN device configuration +# + +menuconfig ETHERNET + bool "Ethernet driver support" + depends on NET + default y + ---help--- + This section contains all the Ethernet device drivers. + +if ETHERNET + +config MDIO + tristate + depends on m + +config SUNGEM_PHY + tristate + depends on m + +#source "drivers/net/ethernet/3com/Kconfig" +#source "drivers/net/ethernet/adaptec/Kconfig" +#source "drivers/net/ethernet/aeroflex/Kconfig" +#source "drivers/net/ethernet/alteon/Kconfig" +#source "drivers/net/ethernet/amd/Kconfig" +#source "drivers/net/ethernet/apple/Kconfig" +source "drivers/net/ethernet/atheros/Kconfig" +#source "drivers/net/ethernet/cadence/Kconfig" +#source "drivers/net/ethernet/adi/Kconfig" +source "drivers/net/ethernet/broadcom/Kconfig" +#source "drivers/net/ethernet/brocade/Kconfig" +#source "drivers/net/ethernet/calxeda/Kconfig" +#source "drivers/net/ethernet/chelsio/Kconfig" +#source "drivers/net/ethernet/cirrus/Kconfig" +#source "drivers/net/ethernet/cisco/Kconfig" +#source "drivers/net/ethernet/davicom/Kconfig" + +config DNET + depends on n + tristate "Dave ethernet support (DNET)" + depends on m + depends on HAS_IOMEM + depends on PHYLIB + ---help--- + The Dave ethernet interface (DNET) is found on Qong Board FPGA. + Say Y to include support for the DNET chip. + + To compile this driver as a module, choose M here: the module + will be called dnet. + +#source "drivers/net/ethernet/dec/Kconfig" +#source "drivers/net/ethernet/dlink/Kconfig" +#source "drivers/net/ethernet/emulex/Kconfig" +#source "drivers/net/ethernet/neterion/Kconfig" +#source "drivers/net/ethernet/faraday/Kconfig" +#source "drivers/net/ethernet/freescale/Kconfig" +#source "drivers/net/ethernet/fujitsu/Kconfig" +#source "drivers/net/ethernet/hp/Kconfig" +#source "drivers/net/ethernet/ibm/Kconfig" +#source "drivers/net/ethernet/intel/Kconfig" +#source "drivers/net/ethernet/i825xx/Kconfig" +#source "drivers/net/ethernet/xscale/Kconfig" +#source "drivers/net/ethernet/icplus/Kconfig" + +config JME + depends on n + tristate "JMicron(R) PCI-Express Gigabit Ethernet support" + depends on m + depends on PCI + depends on CRC32 + select NET_CORE + select BACKPORT_MII + ---help--- + This driver supports the PCI-Express gigabit ethernet adapters + based on JMicron JMC250 chipset. + + To compile this driver as a module, choose M here. The module + will be called jme. + +config KORINA + depends on n + tristate "Korina (IDT RC32434) Ethernet support" + depends on m + depends on MIKROTIK_RB532 + ---help--- + If you have a Mikrotik RouterBoard 500 or IDT RC32434 + based system say Y. Otherwise say N. + +config LANTIQ_ETOP + depends on n + tristate "Lantiq SoC ETOP driver" + depends on m + depends on SOC_TYPE_XWAY + ---help--- + Support for the MII0 inside the Lantiq SoC + +#source "drivers/net/ethernet/marvell/Kconfig" +#source "drivers/net/ethernet/mellanox/Kconfig" +#source "drivers/net/ethernet/micrel/Kconfig" +#source "drivers/net/ethernet/microchip/Kconfig" +#source "drivers/net/ethernet/myricom/Kconfig" + +config FEALNX + depends on n + tristate "Myson MTD-8xx PCI Ethernet support" + depends on m + depends on PCI + depends on CRC32 + select NET_CORE + select BACKPORT_MII + ---help--- + Say Y here to support the Myson MTD-800 family of PCI-based Ethernet + cards. + +#source "drivers/net/ethernet/natsemi/Kconfig" +#source "drivers/net/ethernet/8390/Kconfig" + +config NET_NETX + depends on n + tristate "NetX Ethernet support" + depends on m + select NET_CORE + select BACKPORT_MII + depends on ARCH_NETX + ---help--- + This is support for the Hilscher netX builtin Ethernet ports + + To compile this driver as a module, choose M here. The module + will be called netx-eth. + +#source "drivers/net/ethernet/nuvoton/Kconfig" +#source "drivers/net/ethernet/nvidia/Kconfig" +#source "drivers/net/ethernet/nxp/Kconfig" +#source "drivers/net/ethernet/octeon/Kconfig" +#source "drivers/net/ethernet/oki-semi/Kconfig" + +config ETHOC + depends on n + tristate "OpenCores 10/100 Mbps Ethernet MAC support" + depends on m + depends on HAS_IOMEM && HAS_DMA + select NET_CORE + select BACKPORT_MII + depends on PHYLIB + depends on CRC32 + depends on BITREVERSE + ---help--- + Say Y here if you want to use the OpenCores 10/100 Mbps Ethernet MAC. + +#source "drivers/net/ethernet/packetengines/Kconfig" +#source "drivers/net/ethernet/pasemi/Kconfig" +#source "drivers/net/ethernet/qlogic/Kconfig" +#source "drivers/net/ethernet/realtek/Kconfig" +#source "drivers/net/ethernet/renesas/Kconfig" +#source "drivers/net/ethernet/rdc/Kconfig" + +config S6GMAC + depends on n + tristate "S6105 GMAC ethernet support" + depends on m + depends on XTENSA_VARIANT_S6000 + depends on PHYLIB + ---help--- + This driver supports the on chip ethernet device on the + S6105 xtensa processor. + + To compile this driver as a module, choose M here. The module + will be called s6gmac. + +#source "drivers/net/ethernet/seeq/Kconfig" +#source "drivers/net/ethernet/silan/Kconfig" +#source "drivers/net/ethernet/sis/Kconfig" +#source "drivers/net/ethernet/sfc/Kconfig" +#source "drivers/net/ethernet/sgi/Kconfig" +#source "drivers/net/ethernet/smsc/Kconfig" +#source "drivers/net/ethernet/stmicro/Kconfig" +#source "drivers/net/ethernet/sun/Kconfig" +#source "drivers/net/ethernet/tehuti/Kconfig" +#source "drivers/net/ethernet/ti/Kconfig" +#source "drivers/net/ethernet/tile/Kconfig" +#source "drivers/net/ethernet/toshiba/Kconfig" +#source "drivers/net/ethernet/tundra/Kconfig" +#source "drivers/net/ethernet/via/Kconfig" +#source "drivers/net/ethernet/wiznet/Kconfig" +#source "drivers/net/ethernet/xilinx/Kconfig" +#source "drivers/net/ethernet/xircom/Kconfig" + +endif # ETHERNET diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile new file mode 100644 index 0000000..5653ad8 --- /dev/null +++ b/drivers/net/ethernet/Makefile @@ -0,0 +1,76 @@ +# +# Makefile for the Linux network Ethernet device drivers. +# +# +#obj-$(CONFIG_NET_VENDOR_3COM) += 3com/ +#obj-$(CONFIG_NET_VENDOR_8390) += 8390/ +#obj-$(CONFIG_NET_VENDOR_ADAPTEC) += adaptec/ +#obj-$(CONFIG_GRETH) += aeroflex/ +#obj-$(CONFIG_NET_VENDOR_ALTEON) += alteon/ +#obj-$(CONFIG_NET_VENDOR_AMD) += amd/ +#obj-$(CONFIG_NET_VENDOR_APPLE) += apple/ +obj-$(CPTCFG_NET_VENDOR_ATHEROS) += atheros/ +#obj-$(CONFIG_NET_CADENCE) += cadence/ +#obj-$(CONFIG_NET_BFIN) += adi/ +obj-$(CPTCFG_NET_VENDOR_BROADCOM) += broadcom/ +#obj-$(CONFIG_NET_VENDOR_BROCADE) += brocade/ +#obj-$(CONFIG_NET_CALXEDA_XGMAC) += calxeda/ +#obj-$(CONFIG_NET_VENDOR_CHELSIO) += chelsio/ +#obj-$(CONFIG_NET_VENDOR_CIRRUS) += cirrus/ +#obj-$(CONFIG_NET_VENDOR_CISCO) += cisco/ +#obj-$(CONFIG_DM9000) += davicom/ +#obj-$(CPTCFG_DNET) += dnet.o +#obj-$(CONFIG_NET_VENDOR_DEC) += dec/ +#obj-$(CONFIG_NET_VENDOR_DLINK) += dlink/ +#obj-$(CONFIG_NET_VENDOR_EMULEX) += emulex/ +#obj-$(CONFIG_NET_VENDOR_EXAR) += neterion/ +#obj-$(CONFIG_NET_VENDOR_FARADAY) += faraday/ +#obj-$(CONFIG_NET_VENDOR_FREESCALE) += freescale/ +#obj-$(CONFIG_NET_VENDOR_FUJITSU) += fujitsu/ +#obj-$(CONFIG_NET_VENDOR_HP) += hp/ +#obj-$(CONFIG_NET_VENDOR_IBM) += ibm/ +#obj-$(CONFIG_NET_VENDOR_INTEL) += intel/ +#obj-$(CONFIG_NET_VENDOR_I825XX) += i825xx/ +#obj-$(CONFIG_NET_VENDOR_XSCALE) += xscale/ +#obj-$(CONFIG_IP1000) += icplus/ +#obj-$(CPTCFG_JME) += jme.o +#obj-$(CPTCFG_KORINA) += korina.o +#obj-$(CPTCFG_LANTIQ_ETOP) += lantiq_etop.o +#obj-$(CONFIG_NET_VENDOR_MARVELL) += marvell/ +#obj-$(CONFIG_NET_VENDOR_MELLANOX) += mellanox/ +#obj-$(CONFIG_NET_VENDOR_MICREL) += micrel/ +#obj-$(CONFIG_NET_VENDOR_MICROCHIP) += microchip/ +#obj-$(CONFIG_NET_VENDOR_MYRI) += myricom/ +#obj-$(CPTCFG_FEALNX) += fealnx.o +#obj-$(CONFIG_NET_VENDOR_NATSEMI) += natsemi/ +#obj-$(CPTCFG_NET_NETX) += netx-eth.o +#obj-$(CONFIG_NET_VENDOR_NUVOTON) += nuvoton/ +#obj-$(CONFIG_NET_VENDOR_NVIDIA) += nvidia/ +#obj-$(CONFIG_LPC_ENET) += nxp/ +#obj-$(CONFIG_OCTEON_MGMT_ETHERNET) += octeon/ +#obj-$(CONFIG_NET_VENDOR_OKI) += oki-semi/ +#obj-$(CPTCFG_ETHOC) += ethoc.o +#obj-$(CONFIG_NET_PACKET_ENGINE) += packetengines/ +#obj-$(CONFIG_NET_VENDOR_PASEMI) += pasemi/ +#obj-$(CONFIG_NET_VENDOR_QLOGIC) += qlogic/ +#obj-$(CONFIG_NET_VENDOR_REALTEK) += realtek/ +#obj-$(CONFIG_SH_ETH) += renesas/ +#obj-$(CONFIG_NET_VENDOR_RDC) += rdc/ +#obj-$(CPTCFG_S6GMAC) += s6gmac.o +#obj-$(CONFIG_NET_VENDOR_SEEQ) += seeq/ +#obj-$(CONFIG_NET_VENDOR_SILAN) += silan/ +#obj-$(CONFIG_NET_VENDOR_SIS) += sis/ +#obj-$(CONFIG_SFC) += sfc/ +#obj-$(CONFIG_NET_VENDOR_SGI) += sgi/ +#obj-$(CONFIG_NET_VENDOR_SMSC) += smsc/ +#obj-$(CONFIG_NET_VENDOR_STMICRO) += stmicro/ +#obj-$(CONFIG_NET_VENDOR_SUN) += sun/ +#obj-$(CONFIG_NET_VENDOR_TEHUTI) += tehuti/ +#obj-$(CONFIG_NET_VENDOR_TI) += ti/ +#obj-$(CONFIG_TILE_NET) += tile/ +#obj-$(CONFIG_NET_VENDOR_TOSHIBA) += toshiba/ +#obj-$(CONFIG_NET_VENDOR_TUNDRA) += tundra/ +#obj-$(CONFIG_NET_VENDOR_VIA) += via/ +#obj-$(CONFIG_NET_VENDOR_WIZNET) += wiznet/ +#obj-$(CONFIG_NET_VENDOR_XILINX) += xilinx/ +#obj-$(CONFIG_NET_VENDOR_XIRCOM) += xircom/ diff --git a/drivers/net/ethernet/atheros/Kconfig b/drivers/net/ethernet/atheros/Kconfig new file mode 100644 index 0000000..78e9972 --- /dev/null +++ b/drivers/net/ethernet/atheros/Kconfig @@ -0,0 +1,96 @@ +# +# Atheros device configuration +# + +config NET_VENDOR_ATHEROS + bool "Atheros devices" + default y + depends on PCI + ---help--- + If you have a network (Ethernet) card belonging to this class, say Y + and read the Ethernet-HOWTO, available from + . + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about Atheros devices. If you say Y, you will be asked + for your specific card in the following questions. + +if NET_VENDOR_ATHEROS + +config ATL2 + tristate "Atheros L2 Fast Ethernet support" + depends on m + depends on PCI + depends on CRC32 + select NET_CORE + select BACKPORT_MII + ---help--- + This driver supports the Atheros L2 fast ethernet adapter. + + To compile this driver as a module, choose M here. The module + will be called atl2. + +config ATL1 + tristate "Atheros/Attansic L1 Gigabit Ethernet support" + depends on m + depends on PCI + depends on CRC32 + select NET_CORE + select BACKPORT_MII + ---help--- + This driver supports the Atheros/Attansic L1 gigabit ethernet + adapter. + + To compile this driver as a module, choose M here. The module + will be called atl1. + +config ATL1E + depends on !BACKPORT_KERNEL_2_6_26 + tristate "Atheros L1E Gigabit Ethernet support" + depends on m + depends on PCI + depends on CRC32 + select NET_CORE + select BACKPORT_MII + ---help--- + This driver supports the Atheros L1E gigabit ethernet adapter. + + To compile this driver as a module, choose M here. The module + will be called atl1e. + +config ATL1C + depends on !BACKPORT_KERNEL_3_5 + tristate "Atheros L1C Gigabit Ethernet support" + depends on m + depends on PCI + depends on CRC32 + select NET_CORE + select BACKPORT_MII + ---help--- + This driver supports the Atheros L1C gigabit ethernet adapter. + + To compile this driver as a module, choose M here. The module + will be called atl1c. + +config ALX + depends on !BACKPORT_KERNEL_2_6_31 + tristate "Qualcomm Atheros AR816x/AR817x support" + depends on m + depends on PCI + depends on CRC32 + select NET_CORE + select MDIO + help + This driver supports the Qualcomm Atheros L1F ethernet adapter, + i.e. the following chipsets: + + 1969:1091 - AR8161 Gigabit Ethernet + 1969:1090 - AR8162 Fast Ethernet + 1969:10A1 - AR8171 Gigabit Ethernet + 1969:10A0 - AR8172 Fast Ethernet + + To compile this driver as a module, choose M here. The module + will be called alx. + +endif # NET_VENDOR_ATHEROS diff --git a/drivers/net/ethernet/atheros/Makefile b/drivers/net/ethernet/atheros/Makefile new file mode 100644 index 0000000..2d63e12 --- /dev/null +++ b/drivers/net/ethernet/atheros/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the Atheros network device drivers. +# + +obj-$(CPTCFG_ATL1) += atlx/ +obj-$(CPTCFG_ATL2) += atlx/ +obj-$(CPTCFG_ATL1E) += atl1e/ +obj-$(CPTCFG_ATL1C) += atl1c/ +obj-$(CPTCFG_ALX) += alx/ diff --git a/drivers/net/ethernet/atheros/alx/Makefile b/drivers/net/ethernet/atheros/alx/Makefile new file mode 100644 index 0000000..e087cc3 --- /dev/null +++ b/drivers/net/ethernet/atheros/alx/Makefile @@ -0,0 +1,3 @@ +obj-$(CPTCFG_ALX) += alx.o +alx-objs := main.o ethtool.o hw.o +ccflags-y += -D__CHECK_ENDIAN__ diff --git a/drivers/net/ethernet/atheros/alx/alx.h b/drivers/net/ethernet/atheros/alx/alx.h new file mode 100644 index 0000000..50b3ae2 --- /dev/null +++ b/drivers/net/ethernet/atheros/alx/alx.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2013 Johannes Berg + * + * This file is free software: you may copy, redistribute and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _ALX_H_ +#define _ALX_H_ + +#include +#include +#include +#include +#include "hw.h" + +#define ALX_WATCHDOG_TIME (5 * HZ) + +struct alx_buffer { + struct sk_buff *skb; + DEFINE_DMA_UNMAP_ADDR(dma); + DEFINE_DMA_UNMAP_LEN(size); +}; + +struct alx_rx_queue { + struct alx_rrd *rrd; + dma_addr_t rrd_dma; + + struct alx_rfd *rfd; + dma_addr_t rfd_dma; + + struct alx_buffer *bufs; + + u16 write_idx, read_idx; + u16 rrd_read_idx; +}; +#define ALX_RX_ALLOC_THRESH 32 + +struct alx_tx_queue { + struct alx_txd *tpd; + dma_addr_t tpd_dma; + struct alx_buffer *bufs; + u16 write_idx, read_idx; +}; + +#define ALX_DEFAULT_TX_WORK 128 + +enum alx_device_quirks { + ALX_DEV_QUIRK_MSI_INTX_DISABLE_BUG = BIT(0), +}; + +struct alx_priv { + struct net_device *dev; + + struct alx_hw hw; + + /* all descriptor memory */ + struct { + dma_addr_t dma; + void *virt; + int size; + } descmem; + + /* protect int_mask updates */ + spinlock_t irq_lock; + u32 int_mask; + + int tx_ringsz; + int rx_ringsz; + int rxbuf_size; + + struct napi_struct napi; + struct alx_tx_queue txq; + struct alx_rx_queue rxq; + + struct work_struct link_check_wk; + struct work_struct reset_wk; + + u16 msg_enable; + + bool msi; +}; + +extern const struct ethtool_ops alx_ethtool_ops; +extern const char alx_drv_name[]; + +#endif diff --git a/drivers/net/ethernet/atheros/alx/ethtool.c b/drivers/net/ethernet/atheros/alx/ethtool.c new file mode 100644 index 0000000..6fa2aec --- /dev/null +++ b/drivers/net/ethernet/atheros/alx/ethtool.c @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2013 Johannes Berg + * + * This file is free software: you may copy, redistribute and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alx.h" +#include "reg.h" +#include "hw.h" + + +static int alx_get_settings(struct net_device *netdev, struct ethtool_cmd *ecmd) +{ + struct alx_priv *alx = netdev_priv(netdev); + struct alx_hw *hw = &alx->hw; + + ecmd->supported = SUPPORTED_10baseT_Half | + SUPPORTED_10baseT_Full | + SUPPORTED_100baseT_Half | + SUPPORTED_100baseT_Full | + SUPPORTED_Autoneg | + SUPPORTED_TP | + SUPPORTED_Pause; + if (alx_hw_giga(hw)) + ecmd->supported |= SUPPORTED_1000baseT_Full; + + ecmd->advertising = ADVERTISED_TP; + if (hw->adv_cfg & ADVERTISED_Autoneg) + ecmd->advertising |= hw->adv_cfg; + + ecmd->port = PORT_TP; + ecmd->phy_address = 0; + if (hw->adv_cfg & ADVERTISED_Autoneg) + ecmd->autoneg = AUTONEG_ENABLE; + else + ecmd->autoneg = AUTONEG_DISABLE; + ecmd->transceiver = XCVR_INTERNAL; + + if (hw->flowctrl & ALX_FC_ANEG && hw->adv_cfg & ADVERTISED_Autoneg) { + if (hw->flowctrl & ALX_FC_RX) { + ecmd->advertising |= ADVERTISED_Pause; + + if (!(hw->flowctrl & ALX_FC_TX)) + ecmd->advertising |= ADVERTISED_Asym_Pause; + } else if (hw->flowctrl & ALX_FC_TX) { + ecmd->advertising |= ADVERTISED_Asym_Pause; + } + } + + if (hw->link_speed != SPEED_UNKNOWN) { + ethtool_cmd_speed_set(ecmd, + hw->link_speed - hw->link_speed % 10); + ecmd->duplex = hw->link_speed % 10; + } else { + ethtool_cmd_speed_set(ecmd, SPEED_UNKNOWN); + ecmd->duplex = DUPLEX_UNKNOWN; + } + + return 0; +} + +static int alx_set_settings(struct net_device *netdev, struct ethtool_cmd *ecmd) +{ + struct alx_priv *alx = netdev_priv(netdev); + struct alx_hw *hw = &alx->hw; + u32 adv_cfg; + + ASSERT_RTNL(); + + if (ecmd->autoneg == AUTONEG_ENABLE) { + if (ecmd->advertising & ADVERTISED_1000baseT_Half) + return -EINVAL; + adv_cfg = ecmd->advertising | ADVERTISED_Autoneg; + } else { + int speed = ethtool_cmd_speed(ecmd); + + switch (speed + ecmd->duplex) { + case SPEED_10 + DUPLEX_HALF: + adv_cfg = ADVERTISED_10baseT_Half; + break; + case SPEED_10 + DUPLEX_FULL: + adv_cfg = ADVERTISED_10baseT_Full; + break; + case SPEED_100 + DUPLEX_HALF: + adv_cfg = ADVERTISED_100baseT_Half; + break; + case SPEED_100 + DUPLEX_FULL: + adv_cfg = ADVERTISED_100baseT_Full; + break; + default: + return -EINVAL; + } + } + + hw->adv_cfg = adv_cfg; + return alx_setup_speed_duplex(hw, adv_cfg, hw->flowctrl); +} + +static void alx_get_pauseparam(struct net_device *netdev, + struct ethtool_pauseparam *pause) +{ + struct alx_priv *alx = netdev_priv(netdev); + struct alx_hw *hw = &alx->hw; + + if (hw->flowctrl & ALX_FC_ANEG && + hw->adv_cfg & ADVERTISED_Autoneg) + pause->autoneg = AUTONEG_ENABLE; + else + pause->autoneg = AUTONEG_DISABLE; + + if (hw->flowctrl & ALX_FC_TX) + pause->tx_pause = 1; + else + pause->tx_pause = 0; + + if (hw->flowctrl & ALX_FC_RX) + pause->rx_pause = 1; + else + pause->rx_pause = 0; +} + + +static int alx_set_pauseparam(struct net_device *netdev, + struct ethtool_pauseparam *pause) +{ + struct alx_priv *alx = netdev_priv(netdev); + struct alx_hw *hw = &alx->hw; + int err = 0; + bool reconfig_phy = false; + u8 fc = 0; + + if (pause->tx_pause) + fc |= ALX_FC_TX; + if (pause->rx_pause) + fc |= ALX_FC_RX; + if (pause->autoneg) + fc |= ALX_FC_ANEG; + + ASSERT_RTNL(); + + /* restart auto-neg for auto-mode */ + if (hw->adv_cfg & ADVERTISED_Autoneg) { + if (!((fc ^ hw->flowctrl) & ALX_FC_ANEG)) + reconfig_phy = true; + if (fc & hw->flowctrl & ALX_FC_ANEG && + (fc ^ hw->flowctrl) & (ALX_FC_RX | ALX_FC_TX)) + reconfig_phy = true; + } + + if (reconfig_phy) { + err = alx_setup_speed_duplex(hw, hw->adv_cfg, fc); + return err; + } + + /* flow control on mac */ + if ((fc ^ hw->flowctrl) & (ALX_FC_RX | ALX_FC_TX)) + alx_cfg_mac_flowcontrol(hw, fc); + + hw->flowctrl = fc; + + return 0; +} + +static u32 alx_get_msglevel(struct net_device *netdev) +{ + struct alx_priv *alx = netdev_priv(netdev); + + return alx->msg_enable; +} + +static void alx_set_msglevel(struct net_device *netdev, u32 data) +{ + struct alx_priv *alx = netdev_priv(netdev); + + alx->msg_enable = data; +} + +static void alx_get_wol(struct net_device *netdev, struct ethtool_wolinfo *wol) +{ + struct alx_priv *alx = netdev_priv(netdev); + struct alx_hw *hw = &alx->hw; + + wol->supported = WAKE_MAGIC | WAKE_PHY; + wol->wolopts = 0; + + if (hw->sleep_ctrl & ALX_SLEEP_WOL_MAGIC) + wol->wolopts |= WAKE_MAGIC; + if (hw->sleep_ctrl & ALX_SLEEP_WOL_PHY) + wol->wolopts |= WAKE_PHY; +} + +static int alx_set_wol(struct net_device *netdev, struct ethtool_wolinfo *wol) +{ + struct alx_priv *alx = netdev_priv(netdev); + struct alx_hw *hw = &alx->hw; + + if (wol->wolopts & (WAKE_ARP | WAKE_MAGICSECURE | + WAKE_UCAST | WAKE_BCAST | WAKE_MCAST)) + return -EOPNOTSUPP; + + hw->sleep_ctrl = 0; + + if (wol->wolopts & WAKE_MAGIC) + hw->sleep_ctrl |= ALX_SLEEP_WOL_MAGIC; + if (wol->wolopts & WAKE_PHY) + hw->sleep_ctrl |= ALX_SLEEP_WOL_PHY; + + device_set_wakeup_enable(&alx->hw.pdev->dev, hw->sleep_ctrl); + + return 0; +} + +static void alx_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *drvinfo) +{ + struct alx_priv *alx = netdev_priv(netdev); + + strlcpy(drvinfo->driver, alx_drv_name, sizeof(drvinfo->driver)); + strlcpy(drvinfo->bus_info, pci_name(alx->hw.pdev), + sizeof(drvinfo->bus_info)); +} + +const struct ethtool_ops alx_ethtool_ops = { + .get_settings = alx_get_settings, + .set_settings = alx_set_settings, + .get_pauseparam = alx_get_pauseparam, + .set_pauseparam = alx_set_pauseparam, + .get_drvinfo = alx_get_drvinfo, + .get_msglevel = alx_get_msglevel, + .set_msglevel = alx_set_msglevel, + .get_wol = alx_get_wol, + .set_wol = alx_set_wol, + .get_link = ethtool_op_get_link, +}; diff --git a/drivers/net/ethernet/atheros/alx/hw.c b/drivers/net/ethernet/atheros/alx/hw.c new file mode 100644 index 0000000..220a16a --- /dev/null +++ b/drivers/net/ethernet/atheros/alx/hw.c @@ -0,0 +1,1226 @@ +/* + * Copyright (c) 2013 Johannes Berg + * + * This file is free software: you may copy, redistribute and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include +#include "reg.h" +#include "hw.h" + +static inline bool alx_is_rev_a(u8 rev) +{ + return rev == ALX_REV_A0 || rev == ALX_REV_A1; +} + +static int alx_wait_mdio_idle(struct alx_hw *hw) +{ + u32 val; + int i; + + for (i = 0; i < ALX_MDIO_MAX_AC_TO; i++) { + val = alx_read_mem32(hw, ALX_MDIO); + if (!(val & ALX_MDIO_BUSY)) + return 0; + udelay(10); + } + + return -ETIMEDOUT; +} + +static int alx_read_phy_core(struct alx_hw *hw, bool ext, u8 dev, + u16 reg, u16 *phy_data) +{ + u32 val, clk_sel; + int err; + + *phy_data = 0; + + /* use slow clock when it's in hibernation status */ + clk_sel = hw->link_speed != SPEED_UNKNOWN ? + ALX_MDIO_CLK_SEL_25MD4 : + ALX_MDIO_CLK_SEL_25MD128; + + if (ext) { + val = dev << ALX_MDIO_EXTN_DEVAD_SHIFT | + reg << ALX_MDIO_EXTN_REG_SHIFT; + alx_write_mem32(hw, ALX_MDIO_EXTN, val); + + val = ALX_MDIO_SPRES_PRMBL | ALX_MDIO_START | + ALX_MDIO_MODE_EXT | ALX_MDIO_OP_READ | + clk_sel << ALX_MDIO_CLK_SEL_SHIFT; + } else { + val = ALX_MDIO_SPRES_PRMBL | + clk_sel << ALX_MDIO_CLK_SEL_SHIFT | + reg << ALX_MDIO_REG_SHIFT | + ALX_MDIO_START | ALX_MDIO_OP_READ; + } + alx_write_mem32(hw, ALX_MDIO, val); + + err = alx_wait_mdio_idle(hw); + if (err) + return err; + val = alx_read_mem32(hw, ALX_MDIO); + *phy_data = ALX_GET_FIELD(val, ALX_MDIO_DATA); + return 0; +} + +static int alx_write_phy_core(struct alx_hw *hw, bool ext, u8 dev, + u16 reg, u16 phy_data) +{ + u32 val, clk_sel; + + /* use slow clock when it's in hibernation status */ + clk_sel = hw->link_speed != SPEED_UNKNOWN ? + ALX_MDIO_CLK_SEL_25MD4 : + ALX_MDIO_CLK_SEL_25MD128; + + if (ext) { + val = dev << ALX_MDIO_EXTN_DEVAD_SHIFT | + reg << ALX_MDIO_EXTN_REG_SHIFT; + alx_write_mem32(hw, ALX_MDIO_EXTN, val); + + val = ALX_MDIO_SPRES_PRMBL | + clk_sel << ALX_MDIO_CLK_SEL_SHIFT | + phy_data << ALX_MDIO_DATA_SHIFT | + ALX_MDIO_START | ALX_MDIO_MODE_EXT; + } else { + val = ALX_MDIO_SPRES_PRMBL | + clk_sel << ALX_MDIO_CLK_SEL_SHIFT | + reg << ALX_MDIO_REG_SHIFT | + phy_data << ALX_MDIO_DATA_SHIFT | + ALX_MDIO_START; + } + alx_write_mem32(hw, ALX_MDIO, val); + + return alx_wait_mdio_idle(hw); +} + +static int __alx_read_phy_reg(struct alx_hw *hw, u16 reg, u16 *phy_data) +{ + return alx_read_phy_core(hw, false, 0, reg, phy_data); +} + +static int __alx_write_phy_reg(struct alx_hw *hw, u16 reg, u16 phy_data) +{ + return alx_write_phy_core(hw, false, 0, reg, phy_data); +} + +static int __alx_read_phy_ext(struct alx_hw *hw, u8 dev, u16 reg, u16 *pdata) +{ + return alx_read_phy_core(hw, true, dev, reg, pdata); +} + +static int __alx_write_phy_ext(struct alx_hw *hw, u8 dev, u16 reg, u16 data) +{ + return alx_write_phy_core(hw, true, dev, reg, data); +} + +static int __alx_read_phy_dbg(struct alx_hw *hw, u16 reg, u16 *pdata) +{ + int err; + + err = __alx_write_phy_reg(hw, ALX_MII_DBG_ADDR, reg); + if (err) + return err; + + return __alx_read_phy_reg(hw, ALX_MII_DBG_DATA, pdata); +} + +static int __alx_write_phy_dbg(struct alx_hw *hw, u16 reg, u16 data) +{ + int err; + + err = __alx_write_phy_reg(hw, ALX_MII_DBG_ADDR, reg); + if (err) + return err; + + return __alx_write_phy_reg(hw, ALX_MII_DBG_DATA, data); +} + +int alx_read_phy_reg(struct alx_hw *hw, u16 reg, u16 *phy_data) +{ + int err; + + spin_lock(&hw->mdio_lock); + err = __alx_read_phy_reg(hw, reg, phy_data); + spin_unlock(&hw->mdio_lock); + + return err; +} + +int alx_write_phy_reg(struct alx_hw *hw, u16 reg, u16 phy_data) +{ + int err; + + spin_lock(&hw->mdio_lock); + err = __alx_write_phy_reg(hw, reg, phy_data); + spin_unlock(&hw->mdio_lock); + + return err; +} + +int alx_read_phy_ext(struct alx_hw *hw, u8 dev, u16 reg, u16 *pdata) +{ + int err; + + spin_lock(&hw->mdio_lock); + err = __alx_read_phy_ext(hw, dev, reg, pdata); + spin_unlock(&hw->mdio_lock); + + return err; +} + +int alx_write_phy_ext(struct alx_hw *hw, u8 dev, u16 reg, u16 data) +{ + int err; + + spin_lock(&hw->mdio_lock); + err = __alx_write_phy_ext(hw, dev, reg, data); + spin_unlock(&hw->mdio_lock); + + return err; +} + +static int alx_read_phy_dbg(struct alx_hw *hw, u16 reg, u16 *pdata) +{ + int err; + + spin_lock(&hw->mdio_lock); + err = __alx_read_phy_dbg(hw, reg, pdata); + spin_unlock(&hw->mdio_lock); + + return err; +} + +static int alx_write_phy_dbg(struct alx_hw *hw, u16 reg, u16 data) +{ + int err; + + spin_lock(&hw->mdio_lock); + err = __alx_write_phy_dbg(hw, reg, data); + spin_unlock(&hw->mdio_lock); + + return err; +} + +static u16 alx_get_phy_config(struct alx_hw *hw) +{ + u32 val; + u16 phy_val; + + val = alx_read_mem32(hw, ALX_PHY_CTRL); + /* phy in reset */ + if ((val & ALX_PHY_CTRL_DSPRST_OUT) == 0) + return ALX_DRV_PHY_UNKNOWN; + + val = alx_read_mem32(hw, ALX_DRV); + val = ALX_GET_FIELD(val, ALX_DRV_PHY); + if (ALX_DRV_PHY_UNKNOWN == val) + return ALX_DRV_PHY_UNKNOWN; + + alx_read_phy_reg(hw, ALX_MII_DBG_ADDR, &phy_val); + if (ALX_PHY_INITED == phy_val) + return val; + + return ALX_DRV_PHY_UNKNOWN; +} + +static bool alx_wait_reg(struct alx_hw *hw, u32 reg, u32 wait, u32 *val) +{ + u32 read; + int i; + + for (i = 0; i < ALX_SLD_MAX_TO; i++) { + read = alx_read_mem32(hw, reg); + if ((read & wait) == 0) { + if (val) + *val = read; + return true; + } + mdelay(1); + } + + return false; +} + +static bool alx_read_macaddr(struct alx_hw *hw, u8 *addr) +{ + u32 mac0, mac1; + + mac0 = alx_read_mem32(hw, ALX_STAD0); + mac1 = alx_read_mem32(hw, ALX_STAD1); + + /* addr should be big-endian */ + *(__be32 *)(addr + 2) = cpu_to_be32(mac0); + *(__be16 *)addr = cpu_to_be16(mac1); + + return is_valid_ether_addr(addr); +} + +int alx_get_perm_macaddr(struct alx_hw *hw, u8 *addr) +{ + u32 val; + + /* try to get it from register first */ + if (alx_read_macaddr(hw, addr)) + return 0; + + /* try to load from efuse */ + if (!alx_wait_reg(hw, ALX_SLD, ALX_SLD_STAT | ALX_SLD_START, &val)) + return -EIO; + alx_write_mem32(hw, ALX_SLD, val | ALX_SLD_START); + if (!alx_wait_reg(hw, ALX_SLD, ALX_SLD_START, NULL)) + return -EIO; + if (alx_read_macaddr(hw, addr)) + return 0; + + /* try to load from flash/eeprom (if present) */ + val = alx_read_mem32(hw, ALX_EFLD); + if (val & (ALX_EFLD_F_EXIST | ALX_EFLD_E_EXIST)) { + if (!alx_wait_reg(hw, ALX_EFLD, + ALX_EFLD_STAT | ALX_EFLD_START, &val)) + return -EIO; + alx_write_mem32(hw, ALX_EFLD, val | ALX_EFLD_START); + if (!alx_wait_reg(hw, ALX_EFLD, ALX_EFLD_START, NULL)) + return -EIO; + if (alx_read_macaddr(hw, addr)) + return 0; + } + + return -EIO; +} + +void alx_set_macaddr(struct alx_hw *hw, const u8 *addr) +{ + u32 val; + + /* for example: 00-0B-6A-F6-00-DC * STAD0=6AF600DC, STAD1=000B */ + val = be32_to_cpu(*(__be32 *)(addr + 2)); + alx_write_mem32(hw, ALX_STAD0, val); + val = be16_to_cpu(*(__be16 *)addr); + alx_write_mem32(hw, ALX_STAD1, val); +} + +static void alx_enable_osc(struct alx_hw *hw) +{ + u32 val; + + /* rising edge */ + val = alx_read_mem32(hw, ALX_MISC); + alx_write_mem32(hw, ALX_MISC, val & ~ALX_MISC_INTNLOSC_OPEN); + alx_write_mem32(hw, ALX_MISC, val | ALX_MISC_INTNLOSC_OPEN); +} + +static void alx_reset_osc(struct alx_hw *hw, u8 rev) +{ + u32 val, val2; + + /* clear Internal OSC settings, switching OSC by hw itself */ + val = alx_read_mem32(hw, ALX_MISC3); + alx_write_mem32(hw, ALX_MISC3, + (val & ~ALX_MISC3_25M_BY_SW) | + ALX_MISC3_25M_NOTO_INTNL); + + /* 25M clk from chipset may be unstable 1s after de-assert of + * PERST, driver need re-calibrate before enter Sleep for WoL + */ + val = alx_read_mem32(hw, ALX_MISC); + if (rev >= ALX_REV_B0) { + /* restore over current protection def-val, + * this val could be reset by MAC-RST + */ + ALX_SET_FIELD(val, ALX_MISC_PSW_OCP, ALX_MISC_PSW_OCP_DEF); + /* a 0->1 change will update the internal val of osc */ + val &= ~ALX_MISC_INTNLOSC_OPEN; + alx_write_mem32(hw, ALX_MISC, val); + alx_write_mem32(hw, ALX_MISC, val | ALX_MISC_INTNLOSC_OPEN); + /* hw will automatically dis OSC after cab. */ + val2 = alx_read_mem32(hw, ALX_MSIC2); + val2 &= ~ALX_MSIC2_CALB_START; + alx_write_mem32(hw, ALX_MSIC2, val2); + alx_write_mem32(hw, ALX_MSIC2, val2 | ALX_MSIC2_CALB_START); + } else { + val &= ~ALX_MISC_INTNLOSC_OPEN; + /* disable isolate for rev A devices */ + if (alx_is_rev_a(rev)) + val &= ~ALX_MISC_ISO_EN; + + alx_write_mem32(hw, ALX_MISC, val | ALX_MISC_INTNLOSC_OPEN); + alx_write_mem32(hw, ALX_MISC, val); + } + + udelay(20); +} + +static int alx_stop_mac(struct alx_hw *hw) +{ + u32 rxq, txq, val; + u16 i; + + rxq = alx_read_mem32(hw, ALX_RXQ0); + alx_write_mem32(hw, ALX_RXQ0, rxq & ~ALX_RXQ0_EN); + txq = alx_read_mem32(hw, ALX_TXQ0); + alx_write_mem32(hw, ALX_TXQ0, txq & ~ALX_TXQ0_EN); + + udelay(40); + + hw->rx_ctrl &= ~(ALX_MAC_CTRL_RX_EN | ALX_MAC_CTRL_TX_EN); + alx_write_mem32(hw, ALX_MAC_CTRL, hw->rx_ctrl); + + for (i = 0; i < ALX_DMA_MAC_RST_TO; i++) { + val = alx_read_mem32(hw, ALX_MAC_STS); + if (!(val & ALX_MAC_STS_IDLE)) + return 0; + udelay(10); + } + + return -ETIMEDOUT; +} + +int alx_reset_mac(struct alx_hw *hw) +{ + u32 val, pmctrl; + int i, ret; + u8 rev; + bool a_cr; + + pmctrl = 0; + rev = alx_hw_revision(hw); + a_cr = alx_is_rev_a(rev) && alx_hw_with_cr(hw); + + /* disable all interrupts, RXQ/TXQ */ + alx_write_mem32(hw, ALX_MSIX_MASK, 0xFFFFFFFF); + alx_write_mem32(hw, ALX_IMR, 0); + alx_write_mem32(hw, ALX_ISR, ALX_ISR_DIS); + + ret = alx_stop_mac(hw); + if (ret) + return ret; + + /* mac reset workaroud */ + alx_write_mem32(hw, ALX_RFD_PIDX, 1); + + /* dis l0s/l1 before mac reset */ + if (a_cr) { + pmctrl = alx_read_mem32(hw, ALX_PMCTRL); + if (pmctrl & (ALX_PMCTRL_L1_EN | ALX_PMCTRL_L0S_EN)) + alx_write_mem32(hw, ALX_PMCTRL, + pmctrl & ~(ALX_PMCTRL_L1_EN | + ALX_PMCTRL_L0S_EN)); + } + + /* reset whole mac safely */ + val = alx_read_mem32(hw, ALX_MASTER); + alx_write_mem32(hw, ALX_MASTER, + val | ALX_MASTER_DMA_MAC_RST | ALX_MASTER_OOB_DIS); + + /* make sure it's real idle */ + udelay(10); + for (i = 0; i < ALX_DMA_MAC_RST_TO; i++) { + val = alx_read_mem32(hw, ALX_RFD_PIDX); + if (val == 0) + break; + udelay(10); + } + for (; i < ALX_DMA_MAC_RST_TO; i++) { + val = alx_read_mem32(hw, ALX_MASTER); + if ((val & ALX_MASTER_DMA_MAC_RST) == 0) + break; + udelay(10); + } + if (i == ALX_DMA_MAC_RST_TO) + return -EIO; + udelay(10); + + if (a_cr) { + alx_write_mem32(hw, ALX_MASTER, val | ALX_MASTER_PCLKSEL_SRDS); + /* restore l0s / l1 */ + if (pmctrl & (ALX_PMCTRL_L1_EN | ALX_PMCTRL_L0S_EN)) + alx_write_mem32(hw, ALX_PMCTRL, pmctrl); + } + + alx_reset_osc(hw, rev); + + /* clear Internal OSC settings, switching OSC by hw itself, + * disable isolate for rev A devices + */ + val = alx_read_mem32(hw, ALX_MISC3); + alx_write_mem32(hw, ALX_MISC3, + (val & ~ALX_MISC3_25M_BY_SW) | + ALX_MISC3_25M_NOTO_INTNL); + val = alx_read_mem32(hw, ALX_MISC); + val &= ~ALX_MISC_INTNLOSC_OPEN; + if (alx_is_rev_a(rev)) + val &= ~ALX_MISC_ISO_EN; + alx_write_mem32(hw, ALX_MISC, val); + udelay(20); + + /* driver control speed/duplex, hash-alg */ + alx_write_mem32(hw, ALX_MAC_CTRL, hw->rx_ctrl); + + val = alx_read_mem32(hw, ALX_SERDES); + alx_write_mem32(hw, ALX_SERDES, + val | ALX_SERDES_MACCLK_SLWDWN | + ALX_SERDES_PHYCLK_SLWDWN); + + return 0; +} + +void alx_reset_phy(struct alx_hw *hw) +{ + int i; + u32 val; + u16 phy_val; + + /* (DSP)reset PHY core */ + val = alx_read_mem32(hw, ALX_PHY_CTRL); + val &= ~(ALX_PHY_CTRL_DSPRST_OUT | ALX_PHY_CTRL_IDDQ | + ALX_PHY_CTRL_GATE_25M | ALX_PHY_CTRL_POWER_DOWN | + ALX_PHY_CTRL_CLS); + val |= ALX_PHY_CTRL_RST_ANALOG; + + val |= (ALX_PHY_CTRL_HIB_PULSE | ALX_PHY_CTRL_HIB_EN); + alx_write_mem32(hw, ALX_PHY_CTRL, val); + udelay(10); + alx_write_mem32(hw, ALX_PHY_CTRL, val | ALX_PHY_CTRL_DSPRST_OUT); + + for (i = 0; i < ALX_PHY_CTRL_DSPRST_TO; i++) + udelay(10); + + /* phy power saving & hib */ + alx_write_phy_dbg(hw, ALX_MIIDBG_LEGCYPS, ALX_LEGCYPS_DEF); + alx_write_phy_dbg(hw, ALX_MIIDBG_SYSMODCTRL, + ALX_SYSMODCTRL_IECHOADJ_DEF); + alx_write_phy_ext(hw, ALX_MIIEXT_PCS, ALX_MIIEXT_VDRVBIAS, + ALX_VDRVBIAS_DEF); + + /* EEE advertisement */ + val = alx_read_mem32(hw, ALX_LPI_CTRL); + alx_write_mem32(hw, ALX_LPI_CTRL, val & ~ALX_LPI_CTRL_EN); + alx_write_phy_ext(hw, ALX_MIIEXT_ANEG, ALX_MIIEXT_LOCAL_EEEADV, 0); + + /* phy power saving */ + alx_write_phy_dbg(hw, ALX_MIIDBG_TST10BTCFG, ALX_TST10BTCFG_DEF); + alx_write_phy_dbg(hw, ALX_MIIDBG_SRDSYSMOD, ALX_SRDSYSMOD_DEF); + alx_write_phy_dbg(hw, ALX_MIIDBG_TST100BTCFG, ALX_TST100BTCFG_DEF); + alx_write_phy_dbg(hw, ALX_MIIDBG_ANACTRL, ALX_ANACTRL_DEF); + alx_read_phy_dbg(hw, ALX_MIIDBG_GREENCFG2, &phy_val); + alx_write_phy_dbg(hw, ALX_MIIDBG_GREENCFG2, + phy_val & ~ALX_GREENCFG2_GATE_DFSE_EN); + /* rtl8139c, 120m issue */ + alx_write_phy_ext(hw, ALX_MIIEXT_ANEG, ALX_MIIEXT_NLP78, + ALX_MIIEXT_NLP78_120M_DEF); + alx_write_phy_ext(hw, ALX_MIIEXT_ANEG, ALX_MIIEXT_S3DIG10, + ALX_MIIEXT_S3DIG10_DEF); + + if (hw->lnk_patch) { + /* Turn off half amplitude */ + alx_read_phy_ext(hw, ALX_MIIEXT_PCS, ALX_MIIEXT_CLDCTRL3, + &phy_val); + alx_write_phy_ext(hw, ALX_MIIEXT_PCS, ALX_MIIEXT_CLDCTRL3, + phy_val | ALX_CLDCTRL3_BP_CABLE1TH_DET_GT); + /* Turn off Green feature */ + alx_read_phy_dbg(hw, ALX_MIIDBG_GREENCFG2, &phy_val); + alx_write_phy_dbg(hw, ALX_MIIDBG_GREENCFG2, + phy_val | ALX_GREENCFG2_BP_GREEN); + /* Turn off half Bias */ + alx_read_phy_ext(hw, ALX_MIIEXT_PCS, ALX_MIIEXT_CLDCTRL5, + &phy_val); + alx_write_phy_ext(hw, ALX_MIIEXT_PCS, ALX_MIIEXT_CLDCTRL5, + phy_val | ALX_CLDCTRL5_BP_VD_HLFBIAS); + } + + /* set phy interrupt mask */ + alx_write_phy_reg(hw, ALX_MII_IER, ALX_IER_LINK_UP | ALX_IER_LINK_DOWN); +} + +#define ALX_PCI_CMD (PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY | PCI_COMMAND_IO) + +void alx_reset_pcie(struct alx_hw *hw) +{ + u8 rev = alx_hw_revision(hw); + u32 val; + u16 val16; + + /* Workaround for PCI problem when BIOS sets MMRBC incorrectly. */ + pci_read_config_word(hw->pdev, PCI_COMMAND, &val16); + if (!(val16 & ALX_PCI_CMD) || (val16 & PCI_COMMAND_INTX_DISABLE)) { + val16 = (val16 | ALX_PCI_CMD) & ~PCI_COMMAND_INTX_DISABLE; + pci_write_config_word(hw->pdev, PCI_COMMAND, val16); + } + + /* clear WoL setting/status */ + val = alx_read_mem32(hw, ALX_WOL0); + alx_write_mem32(hw, ALX_WOL0, 0); + + val = alx_read_mem32(hw, ALX_PDLL_TRNS1); + alx_write_mem32(hw, ALX_PDLL_TRNS1, val & ~ALX_PDLL_TRNS1_D3PLLOFF_EN); + + /* mask some pcie error bits */ + val = alx_read_mem32(hw, ALX_UE_SVRT); + val &= ~(ALX_UE_SVRT_DLPROTERR | ALX_UE_SVRT_FCPROTERR); + alx_write_mem32(hw, ALX_UE_SVRT, val); + + /* wol 25M & pclk */ + val = alx_read_mem32(hw, ALX_MASTER); + if (alx_is_rev_a(rev) && alx_hw_with_cr(hw)) { + if ((val & ALX_MASTER_WAKEN_25M) == 0 || + (val & ALX_MASTER_PCLKSEL_SRDS) == 0) + alx_write_mem32(hw, ALX_MASTER, + val | ALX_MASTER_PCLKSEL_SRDS | + ALX_MASTER_WAKEN_25M); + } else { + if ((val & ALX_MASTER_WAKEN_25M) == 0 || + (val & ALX_MASTER_PCLKSEL_SRDS) != 0) + alx_write_mem32(hw, ALX_MASTER, + (val & ~ALX_MASTER_PCLKSEL_SRDS) | + ALX_MASTER_WAKEN_25M); + } + + /* ASPM setting */ + alx_enable_aspm(hw, true, true); + + udelay(10); +} + +void alx_start_mac(struct alx_hw *hw) +{ + u32 mac, txq, rxq; + + rxq = alx_read_mem32(hw, ALX_RXQ0); + alx_write_mem32(hw, ALX_RXQ0, rxq | ALX_RXQ0_EN); + txq = alx_read_mem32(hw, ALX_TXQ0); + alx_write_mem32(hw, ALX_TXQ0, txq | ALX_TXQ0_EN); + + mac = hw->rx_ctrl; + if (hw->link_speed % 10 == DUPLEX_FULL) + mac |= ALX_MAC_CTRL_FULLD; + else + mac &= ~ALX_MAC_CTRL_FULLD; + ALX_SET_FIELD(mac, ALX_MAC_CTRL_SPEED, + hw->link_speed >= SPEED_1000 ? ALX_MAC_CTRL_SPEED_1000 : + ALX_MAC_CTRL_SPEED_10_100); + mac |= ALX_MAC_CTRL_TX_EN | ALX_MAC_CTRL_RX_EN; + hw->rx_ctrl = mac; + alx_write_mem32(hw, ALX_MAC_CTRL, mac); +} + +void alx_cfg_mac_flowcontrol(struct alx_hw *hw, u8 fc) +{ + if (fc & ALX_FC_RX) + hw->rx_ctrl |= ALX_MAC_CTRL_RXFC_EN; + else + hw->rx_ctrl &= ~ALX_MAC_CTRL_RXFC_EN; + + if (fc & ALX_FC_TX) + hw->rx_ctrl |= ALX_MAC_CTRL_TXFC_EN; + else + hw->rx_ctrl &= ~ALX_MAC_CTRL_TXFC_EN; + + alx_write_mem32(hw, ALX_MAC_CTRL, hw->rx_ctrl); +} + +void alx_enable_aspm(struct alx_hw *hw, bool l0s_en, bool l1_en) +{ + u32 pmctrl; + u8 rev = alx_hw_revision(hw); + + pmctrl = alx_read_mem32(hw, ALX_PMCTRL); + + ALX_SET_FIELD(pmctrl, ALX_PMCTRL_LCKDET_TIMER, + ALX_PMCTRL_LCKDET_TIMER_DEF); + pmctrl |= ALX_PMCTRL_RCVR_WT_1US | + ALX_PMCTRL_L1_CLKSW_EN | + ALX_PMCTRL_L1_SRDSRX_PWD; + ALX_SET_FIELD(pmctrl, ALX_PMCTRL_L1REQ_TO, ALX_PMCTRL_L1REG_TO_DEF); + ALX_SET_FIELD(pmctrl, ALX_PMCTRL_L1_TIMER, ALX_PMCTRL_L1_TIMER_16US); + pmctrl &= ~(ALX_PMCTRL_L1_SRDS_EN | + ALX_PMCTRL_L1_SRDSPLL_EN | + ALX_PMCTRL_L1_BUFSRX_EN | + ALX_PMCTRL_SADLY_EN | + ALX_PMCTRL_HOTRST_WTEN| + ALX_PMCTRL_L0S_EN | + ALX_PMCTRL_L1_EN | + ALX_PMCTRL_ASPM_FCEN | + ALX_PMCTRL_TXL1_AFTER_L0S | + ALX_PMCTRL_RXL1_AFTER_L0S); + if (alx_is_rev_a(rev) && alx_hw_with_cr(hw)) + pmctrl |= ALX_PMCTRL_L1_SRDS_EN | ALX_PMCTRL_L1_SRDSPLL_EN; + + if (l0s_en) + pmctrl |= (ALX_PMCTRL_L0S_EN | ALX_PMCTRL_ASPM_FCEN); + if (l1_en) + pmctrl |= (ALX_PMCTRL_L1_EN | ALX_PMCTRL_ASPM_FCEN); + + alx_write_mem32(hw, ALX_PMCTRL, pmctrl); +} + + +static u32 ethadv_to_hw_cfg(struct alx_hw *hw, u32 ethadv_cfg) +{ + u32 cfg = 0; + + if (ethadv_cfg & ADVERTISED_Autoneg) { + cfg |= ALX_DRV_PHY_AUTO; + if (ethadv_cfg & ADVERTISED_10baseT_Half) + cfg |= ALX_DRV_PHY_10; + if (ethadv_cfg & ADVERTISED_10baseT_Full) + cfg |= ALX_DRV_PHY_10 | ALX_DRV_PHY_DUPLEX; + if (ethadv_cfg & ADVERTISED_100baseT_Half) + cfg |= ALX_DRV_PHY_100; + if (ethadv_cfg & ADVERTISED_100baseT_Full) + cfg |= ALX_DRV_PHY_100 | ALX_DRV_PHY_DUPLEX; + if (ethadv_cfg & ADVERTISED_1000baseT_Half) + cfg |= ALX_DRV_PHY_1000; + if (ethadv_cfg & ADVERTISED_1000baseT_Full) + cfg |= ALX_DRV_PHY_100 | ALX_DRV_PHY_DUPLEX; + if (ethadv_cfg & ADVERTISED_Pause) + cfg |= ADVERTISE_PAUSE_CAP; + if (ethadv_cfg & ADVERTISED_Asym_Pause) + cfg |= ADVERTISE_PAUSE_ASYM; + } else { + switch (ethadv_cfg) { + case ADVERTISED_10baseT_Half: + cfg |= ALX_DRV_PHY_10; + break; + case ADVERTISED_100baseT_Half: + cfg |= ALX_DRV_PHY_100; + break; + case ADVERTISED_10baseT_Full: + cfg |= ALX_DRV_PHY_10 | ALX_DRV_PHY_DUPLEX; + break; + case ADVERTISED_100baseT_Full: + cfg |= ALX_DRV_PHY_100 | ALX_DRV_PHY_DUPLEX; + break; + } + } + + return cfg; +} + +int alx_setup_speed_duplex(struct alx_hw *hw, u32 ethadv, u8 flowctrl) +{ + u16 adv, giga, cr; + u32 val; + int err = 0; + + alx_write_phy_reg(hw, ALX_MII_DBG_ADDR, 0); + val = alx_read_mem32(hw, ALX_DRV); + ALX_SET_FIELD(val, ALX_DRV_PHY, 0); + + if (ethadv & ADVERTISED_Autoneg) { + adv = ADVERTISE_CSMA; + adv |= ethtool_adv_to_mii_adv_t(ethadv); + + if (flowctrl & ALX_FC_ANEG) { + if (flowctrl & ALX_FC_RX) { + adv |= ADVERTISED_Pause; + if (!(flowctrl & ALX_FC_TX)) + adv |= ADVERTISED_Asym_Pause; + } else if (flowctrl & ALX_FC_TX) { + adv |= ADVERTISED_Asym_Pause; + } + } + giga = 0; + if (alx_hw_giga(hw)) + giga = ethtool_adv_to_mii_ctrl1000_t(ethadv); + + cr = BMCR_RESET | BMCR_ANENABLE | BMCR_ANRESTART; + + if (alx_write_phy_reg(hw, MII_ADVERTISE, adv) || + alx_write_phy_reg(hw, MII_CTRL1000, giga) || + alx_write_phy_reg(hw, MII_BMCR, cr)) + err = -EBUSY; + } else { + cr = BMCR_RESET; + if (ethadv == ADVERTISED_100baseT_Half || + ethadv == ADVERTISED_100baseT_Full) + cr |= BMCR_SPEED100; + if (ethadv == ADVERTISED_10baseT_Full || + ethadv == ADVERTISED_100baseT_Full) + cr |= BMCR_FULLDPLX; + + err = alx_write_phy_reg(hw, MII_BMCR, cr); + } + + if (!err) { + alx_write_phy_reg(hw, ALX_MII_DBG_ADDR, ALX_PHY_INITED); + val |= ethadv_to_hw_cfg(hw, ethadv); + } + + alx_write_mem32(hw, ALX_DRV, val); + + return err; +} + + +void alx_post_phy_link(struct alx_hw *hw) +{ + u16 phy_val, len, agc; + u8 revid = alx_hw_revision(hw); + bool adj_th = revid == ALX_REV_B0; + int speed; + + if (hw->link_speed == SPEED_UNKNOWN) + speed = SPEED_UNKNOWN; + else + speed = hw->link_speed - hw->link_speed % 10; + + if (revid != ALX_REV_B0 && !alx_is_rev_a(revid)) + return; + + /* 1000BT/AZ, wrong cable length */ + if (speed != SPEED_UNKNOWN) { + alx_read_phy_ext(hw, ALX_MIIEXT_PCS, ALX_MIIEXT_CLDCTRL6, + &phy_val); + len = ALX_GET_FIELD(phy_val, ALX_CLDCTRL6_CAB_LEN); + alx_read_phy_dbg(hw, ALX_MIIDBG_AGC, &phy_val); + agc = ALX_GET_FIELD(phy_val, ALX_AGC_2_VGA); + + if ((speed == SPEED_1000 && + (len > ALX_CLDCTRL6_CAB_LEN_SHORT1G || + (len == 0 && agc > ALX_AGC_LONG1G_LIMT))) || + (speed == SPEED_100 && + (len > ALX_CLDCTRL6_CAB_LEN_SHORT100M || + (len == 0 && agc > ALX_AGC_LONG100M_LIMT)))) { + alx_write_phy_dbg(hw, ALX_MIIDBG_AZ_ANADECT, + ALX_AZ_ANADECT_LONG); + alx_read_phy_ext(hw, ALX_MIIEXT_ANEG, ALX_MIIEXT_AFE, + &phy_val); + alx_write_phy_ext(hw, ALX_MIIEXT_ANEG, ALX_MIIEXT_AFE, + phy_val | ALX_AFE_10BT_100M_TH); + } else { + alx_write_phy_dbg(hw, ALX_MIIDBG_AZ_ANADECT, + ALX_AZ_ANADECT_DEF); + alx_read_phy_ext(hw, ALX_MIIEXT_ANEG, + ALX_MIIEXT_AFE, &phy_val); + alx_write_phy_ext(hw, ALX_MIIEXT_ANEG, ALX_MIIEXT_AFE, + phy_val & ~ALX_AFE_10BT_100M_TH); + } + + /* threshold adjust */ + if (adj_th && hw->lnk_patch) { + if (speed == SPEED_100) { + alx_write_phy_dbg(hw, ALX_MIIDBG_MSE16DB, + ALX_MSE16DB_UP); + } else if (speed == SPEED_1000) { + /* + * Giga link threshold, raise the tolerance of + * noise 50% + */ + alx_read_phy_dbg(hw, ALX_MIIDBG_MSE20DB, + &phy_val); + ALX_SET_FIELD(phy_val, ALX_MSE20DB_TH, + ALX_MSE20DB_TH_HI); + alx_write_phy_dbg(hw, ALX_MIIDBG_MSE20DB, + phy_val); + } + } + } else { + alx_read_phy_ext(hw, ALX_MIIEXT_ANEG, ALX_MIIEXT_AFE, + &phy_val); + alx_write_phy_ext(hw, ALX_MIIEXT_ANEG, ALX_MIIEXT_AFE, + phy_val & ~ALX_AFE_10BT_100M_TH); + + if (adj_th && hw->lnk_patch) { + alx_write_phy_dbg(hw, ALX_MIIDBG_MSE16DB, + ALX_MSE16DB_DOWN); + alx_read_phy_dbg(hw, ALX_MIIDBG_MSE20DB, &phy_val); + ALX_SET_FIELD(phy_val, ALX_MSE20DB_TH, + ALX_MSE20DB_TH_DEF); + alx_write_phy_dbg(hw, ALX_MIIDBG_MSE20DB, phy_val); + } + } +} + + +/* NOTE: + * 1. phy link must be established before calling this function + * 2. wol option (pattern,magic,link,etc.) is configed before call it. + */ +int alx_pre_suspend(struct alx_hw *hw, int speed) +{ + u32 master, mac, phy, val; + int err = 0; + + master = alx_read_mem32(hw, ALX_MASTER); + master &= ~ALX_MASTER_PCLKSEL_SRDS; + mac = hw->rx_ctrl; + /* 10/100 half */ + ALX_SET_FIELD(mac, ALX_MAC_CTRL_SPEED, ALX_MAC_CTRL_SPEED_10_100); + mac &= ~(ALX_MAC_CTRL_FULLD | ALX_MAC_CTRL_RX_EN | ALX_MAC_CTRL_TX_EN); + + phy = alx_read_mem32(hw, ALX_PHY_CTRL); + phy &= ~(ALX_PHY_CTRL_DSPRST_OUT | ALX_PHY_CTRL_CLS); + phy |= ALX_PHY_CTRL_RST_ANALOG | ALX_PHY_CTRL_HIB_PULSE | + ALX_PHY_CTRL_HIB_EN; + + /* without any activity */ + if (!(hw->sleep_ctrl & ALX_SLEEP_ACTIVE)) { + err = alx_write_phy_reg(hw, ALX_MII_IER, 0); + if (err) + return err; + phy |= ALX_PHY_CTRL_IDDQ | ALX_PHY_CTRL_POWER_DOWN; + } else { + if (hw->sleep_ctrl & (ALX_SLEEP_WOL_MAGIC | ALX_SLEEP_CIFS)) + mac |= ALX_MAC_CTRL_RX_EN | ALX_MAC_CTRL_BRD_EN; + if (hw->sleep_ctrl & ALX_SLEEP_CIFS) + mac |= ALX_MAC_CTRL_TX_EN; + if (speed % 10 == DUPLEX_FULL) + mac |= ALX_MAC_CTRL_FULLD; + if (speed >= SPEED_1000) + ALX_SET_FIELD(mac, ALX_MAC_CTRL_SPEED, + ALX_MAC_CTRL_SPEED_1000); + phy |= ALX_PHY_CTRL_DSPRST_OUT; + err = alx_write_phy_ext(hw, ALX_MIIEXT_ANEG, + ALX_MIIEXT_S3DIG10, + ALX_MIIEXT_S3DIG10_SL); + if (err) + return err; + } + + alx_enable_osc(hw); + hw->rx_ctrl = mac; + alx_write_mem32(hw, ALX_MASTER, master); + alx_write_mem32(hw, ALX_MAC_CTRL, mac); + alx_write_mem32(hw, ALX_PHY_CTRL, phy); + + /* set val of PDLL D3PLLOFF */ + val = alx_read_mem32(hw, ALX_PDLL_TRNS1); + val |= ALX_PDLL_TRNS1_D3PLLOFF_EN; + alx_write_mem32(hw, ALX_PDLL_TRNS1, val); + + return 0; +} + +bool alx_phy_configured(struct alx_hw *hw) +{ + u32 cfg, hw_cfg; + + cfg = ethadv_to_hw_cfg(hw, hw->adv_cfg); + cfg = ALX_GET_FIELD(cfg, ALX_DRV_PHY); + hw_cfg = alx_get_phy_config(hw); + + if (hw_cfg == ALX_DRV_PHY_UNKNOWN) + return false; + + return cfg == hw_cfg; +} + +int alx_get_phy_link(struct alx_hw *hw, int *speed) +{ + struct pci_dev *pdev = hw->pdev; + u16 bmsr, giga; + int err; + + err = alx_read_phy_reg(hw, MII_BMSR, &bmsr); + if (err) + return err; + + err = alx_read_phy_reg(hw, MII_BMSR, &bmsr); + if (err) + return err; + + if (!(bmsr & BMSR_LSTATUS)) { + *speed = SPEED_UNKNOWN; + return 0; + } + + /* speed/duplex result is saved in PHY Specific Status Register */ + err = alx_read_phy_reg(hw, ALX_MII_GIGA_PSSR, &giga); + if (err) + return err; + + if (!(giga & ALX_GIGA_PSSR_SPD_DPLX_RESOLVED)) + goto wrong_speed; + + switch (giga & ALX_GIGA_PSSR_SPEED) { + case ALX_GIGA_PSSR_1000MBS: + *speed = SPEED_1000; + break; + case ALX_GIGA_PSSR_100MBS: + *speed = SPEED_100; + break; + case ALX_GIGA_PSSR_10MBS: + *speed = SPEED_10; + break; + default: + goto wrong_speed; + } + + *speed += (giga & ALX_GIGA_PSSR_DPLX) ? DUPLEX_FULL : DUPLEX_HALF; + return 1; + +wrong_speed: + dev_err(&pdev->dev, "invalid PHY speed/duplex: 0x%x\n", giga); + return -EINVAL; +} + +int alx_clear_phy_intr(struct alx_hw *hw) +{ + u16 isr; + + /* clear interrupt status by reading it */ + return alx_read_phy_reg(hw, ALX_MII_ISR, &isr); +} + +int alx_config_wol(struct alx_hw *hw) +{ + u32 wol = 0; + int err = 0; + + /* turn on magic packet event */ + if (hw->sleep_ctrl & ALX_SLEEP_WOL_MAGIC) + wol |= ALX_WOL0_MAGIC_EN | ALX_WOL0_PME_MAGIC_EN; + + /* turn on link up event */ + if (hw->sleep_ctrl & ALX_SLEEP_WOL_PHY) { + wol |= ALX_WOL0_LINK_EN | ALX_WOL0_PME_LINK; + /* only link up can wake up */ + err = alx_write_phy_reg(hw, ALX_MII_IER, ALX_IER_LINK_UP); + } + alx_write_mem32(hw, ALX_WOL0, wol); + + return err; +} + +void alx_disable_rss(struct alx_hw *hw) +{ + u32 ctrl = alx_read_mem32(hw, ALX_RXQ0); + + ctrl &= ~ALX_RXQ0_RSS_HASH_EN; + alx_write_mem32(hw, ALX_RXQ0, ctrl); +} + +void alx_configure_basic(struct alx_hw *hw) +{ + u32 val, raw_mtu, max_payload; + u16 val16; + u8 chip_rev = alx_hw_revision(hw); + + alx_set_macaddr(hw, hw->mac_addr); + + alx_write_mem32(hw, ALX_CLK_GATE, ALX_CLK_GATE_ALL); + + /* idle timeout to switch clk_125M */ + if (chip_rev >= ALX_REV_B0) + alx_write_mem32(hw, ALX_IDLE_DECISN_TIMER, + ALX_IDLE_DECISN_TIMER_DEF); + + alx_write_mem32(hw, ALX_SMB_TIMER, hw->smb_timer * 500UL); + + val = alx_read_mem32(hw, ALX_MASTER); + val |= ALX_MASTER_IRQMOD2_EN | + ALX_MASTER_IRQMOD1_EN | + ALX_MASTER_SYSALVTIMER_EN; + alx_write_mem32(hw, ALX_MASTER, val); + alx_write_mem32(hw, ALX_IRQ_MODU_TIMER, + (hw->imt >> 1) << ALX_IRQ_MODU_TIMER1_SHIFT); + /* intr re-trig timeout */ + alx_write_mem32(hw, ALX_INT_RETRIG, ALX_INT_RETRIG_TO); + /* tpd threshold to trig int */ + alx_write_mem32(hw, ALX_TINT_TPD_THRSHLD, hw->ith_tpd); + alx_write_mem32(hw, ALX_TINT_TIMER, hw->imt); + + raw_mtu = hw->mtu + ETH_HLEN; + alx_write_mem32(hw, ALX_MTU, raw_mtu + 8); + if (raw_mtu > ALX_MTU_JUMBO_TH) + hw->rx_ctrl &= ~ALX_MAC_CTRL_FAST_PAUSE; + + if ((raw_mtu + 8) < ALX_TXQ1_JUMBO_TSO_TH) + val = (raw_mtu + 8 + 7) >> 3; + else + val = ALX_TXQ1_JUMBO_TSO_TH >> 3; + alx_write_mem32(hw, ALX_TXQ1, val | ALX_TXQ1_ERRLGPKT_DROP_EN); + + max_payload = pcie_get_readrq(hw->pdev) >> 8; + /* + * if BIOS had changed the default dma read max length, + * restore it to default value + */ + if (max_payload < ALX_DEV_CTRL_MAXRRS_MIN) + pcie_set_readrq(hw->pdev, 128 << ALX_DEV_CTRL_MAXRRS_MIN); + + val = ALX_TXQ_TPD_BURSTPREF_DEF << ALX_TXQ0_TPD_BURSTPREF_SHIFT | + ALX_TXQ0_MODE_ENHANCE | ALX_TXQ0_LSO_8023_EN | + ALX_TXQ0_SUPT_IPOPT | + ALX_TXQ_TXF_BURST_PREF_DEF << ALX_TXQ0_TXF_BURST_PREF_SHIFT; + alx_write_mem32(hw, ALX_TXQ0, val); + val = ALX_TXQ_TPD_BURSTPREF_DEF << ALX_HQTPD_Q1_NUMPREF_SHIFT | + ALX_TXQ_TPD_BURSTPREF_DEF << ALX_HQTPD_Q2_NUMPREF_SHIFT | + ALX_TXQ_TPD_BURSTPREF_DEF << ALX_HQTPD_Q3_NUMPREF_SHIFT | + ALX_HQTPD_BURST_EN; + alx_write_mem32(hw, ALX_HQTPD, val); + + /* rxq, flow control */ + val = alx_read_mem32(hw, ALX_SRAM5); + val = ALX_GET_FIELD(val, ALX_SRAM_RXF_LEN) << 3; + if (val > ALX_SRAM_RXF_LEN_8K) { + val16 = ALX_MTU_STD_ALGN >> 3; + val = (val - ALX_RXQ2_RXF_FLOW_CTRL_RSVD) >> 3; + } else { + val16 = ALX_MTU_STD_ALGN >> 3; + val = (val - ALX_MTU_STD_ALGN) >> 3; + } + alx_write_mem32(hw, ALX_RXQ2, + val16 << ALX_RXQ2_RXF_XOFF_THRESH_SHIFT | + val << ALX_RXQ2_RXF_XON_THRESH_SHIFT); + val = ALX_RXQ0_NUM_RFD_PREF_DEF << ALX_RXQ0_NUM_RFD_PREF_SHIFT | + ALX_RXQ0_RSS_MODE_DIS << ALX_RXQ0_RSS_MODE_SHIFT | + ALX_RXQ0_IDT_TBL_SIZE_DEF << ALX_RXQ0_IDT_TBL_SIZE_SHIFT | + ALX_RXQ0_RSS_HSTYP_ALL | ALX_RXQ0_RSS_HASH_EN | + ALX_RXQ0_IPV6_PARSE_EN; + + if (alx_hw_giga(hw)) + ALX_SET_FIELD(val, ALX_RXQ0_ASPM_THRESH, + ALX_RXQ0_ASPM_THRESH_100M); + + alx_write_mem32(hw, ALX_RXQ0, val); + + val = alx_read_mem32(hw, ALX_DMA); + val = ALX_DMA_RORDER_MODE_OUT << ALX_DMA_RORDER_MODE_SHIFT | + ALX_DMA_RREQ_PRI_DATA | + max_payload << ALX_DMA_RREQ_BLEN_SHIFT | + ALX_DMA_WDLY_CNT_DEF << ALX_DMA_WDLY_CNT_SHIFT | + ALX_DMA_RDLY_CNT_DEF << ALX_DMA_RDLY_CNT_SHIFT | + (hw->dma_chnl - 1) << ALX_DMA_RCHNL_SEL_SHIFT; + alx_write_mem32(hw, ALX_DMA, val); + + /* default multi-tx-q weights */ + val = ALX_WRR_PRI_RESTRICT_NONE << ALX_WRR_PRI_SHIFT | + 4 << ALX_WRR_PRI0_SHIFT | + 4 << ALX_WRR_PRI1_SHIFT | + 4 << ALX_WRR_PRI2_SHIFT | + 4 << ALX_WRR_PRI3_SHIFT; + alx_write_mem32(hw, ALX_WRR, val); +} + +static inline u32 alx_speed_to_ethadv(int speed) +{ + switch (speed) { + case SPEED_1000 + DUPLEX_FULL: + return ADVERTISED_1000baseT_Full; + case SPEED_100 + DUPLEX_FULL: + return ADVERTISED_100baseT_Full; + case SPEED_100 + DUPLEX_HALF: + return ADVERTISED_10baseT_Half; + case SPEED_10 + DUPLEX_FULL: + return ADVERTISED_10baseT_Full; + case SPEED_10 + DUPLEX_HALF: + return ADVERTISED_10baseT_Half; + default: + return 0; + } +} + +int alx_select_powersaving_speed(struct alx_hw *hw, int *speed) +{ + int i, err, spd; + u16 lpa; + + err = alx_get_phy_link(hw, &spd); + if (err < 0) + return err; + + if (spd == SPEED_UNKNOWN) + return 0; + + err = alx_read_phy_reg(hw, MII_LPA, &lpa); + if (err) + return err; + + if (!(lpa & LPA_LPACK)) { + *speed = spd; + return 0; + } + + if (lpa & LPA_10FULL) + *speed = SPEED_10 + DUPLEX_FULL; + else if (lpa & LPA_10HALF) + *speed = SPEED_10 + DUPLEX_HALF; + else if (lpa & LPA_100FULL) + *speed = SPEED_100 + DUPLEX_FULL; + else + *speed = SPEED_100 + DUPLEX_HALF; + + if (*speed != spd) { + err = alx_write_phy_reg(hw, ALX_MII_IER, 0); + if (err) + return err; + err = alx_setup_speed_duplex(hw, + alx_speed_to_ethadv(*speed) | + ADVERTISED_Autoneg, + ALX_FC_ANEG | ALX_FC_RX | + ALX_FC_TX); + if (err) + return err; + + /* wait for linkup */ + for (i = 0; i < ALX_MAX_SETUP_LNK_CYCLE; i++) { + int speed2; + + msleep(100); + + err = alx_get_phy_link(hw, &speed2); + if (err < 0) + return err; + if (speed2 != SPEED_UNKNOWN) + break; + } + if (i == ALX_MAX_SETUP_LNK_CYCLE) + return -ETIMEDOUT; + } + + return 0; +} + +bool alx_get_phy_info(struct alx_hw *hw) +{ + u16 devs1, devs2; + + if (alx_read_phy_reg(hw, MII_PHYSID1, &hw->phy_id[0]) || + alx_read_phy_reg(hw, MII_PHYSID2, &hw->phy_id[1])) + return false; + + /* since we haven't PMA/PMD status2 register, we can't + * use mdio45_probe function for prtad and mmds. + * use fixed MMD3 to get mmds. + */ + if (alx_read_phy_ext(hw, 3, MDIO_DEVS1, &devs1) || + alx_read_phy_ext(hw, 3, MDIO_DEVS2, &devs2)) + return false; + hw->mdio.mmds = devs1 | devs2 << 16; + + return true; +} diff --git a/drivers/net/ethernet/atheros/alx/hw.h b/drivers/net/ethernet/atheros/alx/hw.h new file mode 100644 index 0000000..65e723d --- /dev/null +++ b/drivers/net/ethernet/atheros/alx/hw.h @@ -0,0 +1,499 @@ +/* + * Copyright (c) 2013 Johannes Berg + * + * This file is free software: you may copy, redistribute and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef ALX_HW_H_ +#define ALX_HW_H_ +#include +#include +#include +#include "reg.h" + +/* Transmit Packet Descriptor, contains 4 32-bit words. + * + * 31 16 0 + * +----------------+----------------+ + * | vlan-tag | buf length | + * +----------------+----------------+ + * | Word 1 | + * +----------------+----------------+ + * | Word 2: buf addr lo | + * +----------------+----------------+ + * | Word 3: buf addr hi | + * +----------------+----------------+ + * + * Word 2 and 3 combine to form a 64-bit buffer address + * + * Word 1 has three forms, depending on the state of bit 8/12/13: + * if bit8 =='1', the definition is just for custom checksum offload. + * if bit8 == '0' && bit12 == '1' && bit13 == '1', the *FIRST* descriptor + * for the skb is special for LSO V2, Word 2 become total skb length , + * Word 3 is meaningless. + * other condition, the definition is for general skb or ip/tcp/udp + * checksum or LSO(TSO) offload. + * + * Here is the depiction: + * + * 0-+ 0-+ + * 1 | 1 | + * 2 | 2 | + * 3 | Payload offset 3 | L4 header offset + * 4 | (7:0) 4 | (7:0) + * 5 | 5 | + * 6 | 6 | + * 7-+ 7-+ + * 8 Custom csum enable = 1 8 Custom csum enable = 0 + * 9 General IPv4 checksum 9 General IPv4 checksum + * 10 General TCP checksum 10 General TCP checksum + * 11 General UDP checksum 11 General UDP checksum + * 12 Large Send Segment enable 12 Large Send Segment enable + * 13 Large Send Segment type 13 Large Send Segment type + * 14 VLAN tagged 14 VLAN tagged + * 15 Insert VLAN tag 15 Insert VLAN tag + * 16 IPv4 packet 16 IPv4 packet + * 17 Ethernet frame type 17 Ethernet frame type + * 18-+ 18-+ + * 19 | 19 | + * 20 | 20 | + * 21 | Custom csum offset 21 | + * 22 | (25:18) 22 | + * 23 | 23 | MSS (30:18) + * 24 | 24 | + * 25-+ 25 | + * 26-+ 26 | + * 27 | 27 | + * 28 | Reserved 28 | + * 29 | 29 | + * 30-+ 30-+ + * 31 End of packet 31 End of packet + */ +struct alx_txd { + __le16 len; + __le16 vlan_tag; + __le32 word1; + union { + __le64 addr; + struct { + __le32 pkt_len; + __le32 resvd; + } l; + } adrl; +} __packed; + +/* tpd word 1 */ +#define TPD_CXSUMSTART_MASK 0x00FF +#define TPD_CXSUMSTART_SHIFT 0 +#define TPD_L4HDROFFSET_MASK 0x00FF +#define TPD_L4HDROFFSET_SHIFT 0 +#define TPD_CXSUM_EN_MASK 0x0001 +#define TPD_CXSUM_EN_SHIFT 8 +#define TPD_IP_XSUM_MASK 0x0001 +#define TPD_IP_XSUM_SHIFT 9 +#define TPD_TCP_XSUM_MASK 0x0001 +#define TPD_TCP_XSUM_SHIFT 10 +#define TPD_UDP_XSUM_MASK 0x0001 +#define TPD_UDP_XSUM_SHIFT 11 +#define TPD_LSO_EN_MASK 0x0001 +#define TPD_LSO_EN_SHIFT 12 +#define TPD_LSO_V2_MASK 0x0001 +#define TPD_LSO_V2_SHIFT 13 +#define TPD_VLTAGGED_MASK 0x0001 +#define TPD_VLTAGGED_SHIFT 14 +#define TPD_INS_VLTAG_MASK 0x0001 +#define TPD_INS_VLTAG_SHIFT 15 +#define TPD_IPV4_MASK 0x0001 +#define TPD_IPV4_SHIFT 16 +#define TPD_ETHTYPE_MASK 0x0001 +#define TPD_ETHTYPE_SHIFT 17 +#define TPD_CXSUMOFFSET_MASK 0x00FF +#define TPD_CXSUMOFFSET_SHIFT 18 +#define TPD_MSS_MASK 0x1FFF +#define TPD_MSS_SHIFT 18 +#define TPD_EOP_MASK 0x0001 +#define TPD_EOP_SHIFT 31 + +#define DESC_GET(_x, _name) ((_x) >> _name##SHIFT & _name##MASK) + +/* Receive Free Descriptor */ +struct alx_rfd { + __le64 addr; /* data buffer address, length is + * declared in register --- every + * buffer has the same size + */ +} __packed; + +/* Receive Return Descriptor, contains 4 32-bit words. + * + * 31 16 0 + * +----------------+----------------+ + * | Word 0 | + * +----------------+----------------+ + * | Word 1: RSS Hash value | + * +----------------+----------------+ + * | Word 2 | + * +----------------+----------------+ + * | Word 3 | + * +----------------+----------------+ + * + * Word 0 depiction & Word 2 depiction: + * + * 0--+ 0--+ + * 1 | 1 | + * 2 | 2 | + * 3 | 3 | + * 4 | 4 | + * 5 | 5 | + * 6 | 6 | + * 7 | IP payload checksum 7 | VLAN tag + * 8 | (15:0) 8 | (15:0) + * 9 | 9 | + * 10 | 10 | + * 11 | 11 | + * 12 | 12 | + * 13 | 13 | + * 14 | 14 | + * 15-+ 15-+ + * 16-+ 16-+ + * 17 | Number of RFDs 17 | + * 18 | (19:16) 18 | + * 19-+ 19 | Protocol ID + * 20-+ 20 | (23:16) + * 21 | 21 | + * 22 | 22 | + * 23 | 23-+ + * 24 | 24 | Reserved + * 25 | Start index of RFD-ring 25-+ + * 26 | (31:20) 26 | RSS Q-num (27:25) + * 27 | 27-+ + * 28 | 28-+ + * 29 | 29 | RSS Hash algorithm + * 30 | 30 | (31:28) + * 31-+ 31-+ + * + * Word 3 depiction: + * + * 0--+ + * 1 | + * 2 | + * 3 | + * 4 | + * 5 | + * 6 | + * 7 | Packet length (include FCS) + * 8 | (13:0) + * 9 | + * 10 | + * 11 | + * 12 | + * 13-+ + * 14 L4 Header checksum error + * 15 IPv4 checksum error + * 16 VLAN tagged + * 17-+ + * 18 | Protocol ID (19:17) + * 19-+ + * 20 Receive error summary + * 21 FCS(CRC) error + * 22 Frame alignment error + * 23 Truncated packet + * 24 Runt packet + * 25 Incomplete packet due to insufficient rx-desc + * 26 Broadcast packet + * 27 Multicast packet + * 28 Ethernet type (EII or 802.3) + * 29 FIFO overflow + * 30 Length error (for 802.3, length field mismatch with actual len) + * 31 Updated, indicate to driver that this RRD is refreshed. + */ +struct alx_rrd { + __le32 word0; + __le32 rss_hash; + __le32 word2; + __le32 word3; +} __packed; + +/* rrd word 0 */ +#define RRD_XSUM_MASK 0xFFFF +#define RRD_XSUM_SHIFT 0 +#define RRD_NOR_MASK 0x000F +#define RRD_NOR_SHIFT 16 +#define RRD_SI_MASK 0x0FFF +#define RRD_SI_SHIFT 20 + +/* rrd word 2 */ +#define RRD_VLTAG_MASK 0xFFFF +#define RRD_VLTAG_SHIFT 0 +#define RRD_PID_MASK 0x00FF +#define RRD_PID_SHIFT 16 +/* non-ip packet */ +#define RRD_PID_NONIP 0 +/* ipv4(only) */ +#define RRD_PID_IPV4 1 +/* tcp/ipv6 */ +#define RRD_PID_IPV6TCP 2 +/* tcp/ipv4 */ +#define RRD_PID_IPV4TCP 3 +/* udp/ipv6 */ +#define RRD_PID_IPV6UDP 4 +/* udp/ipv4 */ +#define RRD_PID_IPV4UDP 5 +/* ipv6(only) */ +#define RRD_PID_IPV6 6 +/* LLDP packet */ +#define RRD_PID_LLDP 7 +/* 1588 packet */ +#define RRD_PID_1588 8 +#define RRD_RSSQ_MASK 0x0007 +#define RRD_RSSQ_SHIFT 25 +#define RRD_RSSALG_MASK 0x000F +#define RRD_RSSALG_SHIFT 28 +#define RRD_RSSALG_TCPV6 0x1 +#define RRD_RSSALG_IPV6 0x2 +#define RRD_RSSALG_TCPV4 0x4 +#define RRD_RSSALG_IPV4 0x8 + +/* rrd word 3 */ +#define RRD_PKTLEN_MASK 0x3FFF +#define RRD_PKTLEN_SHIFT 0 +#define RRD_ERR_L4_MASK 0x0001 +#define RRD_ERR_L4_SHIFT 14 +#define RRD_ERR_IPV4_MASK 0x0001 +#define RRD_ERR_IPV4_SHIFT 15 +#define RRD_VLTAGGED_MASK 0x0001 +#define RRD_VLTAGGED_SHIFT 16 +#define RRD_OLD_PID_MASK 0x0007 +#define RRD_OLD_PID_SHIFT 17 +#define RRD_ERR_RES_MASK 0x0001 +#define RRD_ERR_RES_SHIFT 20 +#define RRD_ERR_FCS_MASK 0x0001 +#define RRD_ERR_FCS_SHIFT 21 +#define RRD_ERR_FAE_MASK 0x0001 +#define RRD_ERR_FAE_SHIFT 22 +#define RRD_ERR_TRUNC_MASK 0x0001 +#define RRD_ERR_TRUNC_SHIFT 23 +#define RRD_ERR_RUNT_MASK 0x0001 +#define RRD_ERR_RUNT_SHIFT 24 +#define RRD_ERR_ICMP_MASK 0x0001 +#define RRD_ERR_ICMP_SHIFT 25 +#define RRD_BCAST_MASK 0x0001 +#define RRD_BCAST_SHIFT 26 +#define RRD_MCAST_MASK 0x0001 +#define RRD_MCAST_SHIFT 27 +#define RRD_ETHTYPE_MASK 0x0001 +#define RRD_ETHTYPE_SHIFT 28 +#define RRD_ERR_FIFOV_MASK 0x0001 +#define RRD_ERR_FIFOV_SHIFT 29 +#define RRD_ERR_LEN_MASK 0x0001 +#define RRD_ERR_LEN_SHIFT 30 +#define RRD_UPDATED_MASK 0x0001 +#define RRD_UPDATED_SHIFT 31 + + +#define ALX_MAX_SETUP_LNK_CYCLE 50 + +/* for FlowControl */ +#define ALX_FC_RX 0x01 +#define ALX_FC_TX 0x02 +#define ALX_FC_ANEG 0x04 + +/* for sleep control */ +#define ALX_SLEEP_WOL_PHY 0x00000001 +#define ALX_SLEEP_WOL_MAGIC 0x00000002 +#define ALX_SLEEP_CIFS 0x00000004 +#define ALX_SLEEP_ACTIVE (ALX_SLEEP_WOL_PHY | \ + ALX_SLEEP_WOL_MAGIC | \ + ALX_SLEEP_CIFS) + +/* for RSS hash type */ +#define ALX_RSS_HASH_TYPE_IPV4 0x1 +#define ALX_RSS_HASH_TYPE_IPV4_TCP 0x2 +#define ALX_RSS_HASH_TYPE_IPV6 0x4 +#define ALX_RSS_HASH_TYPE_IPV6_TCP 0x8 +#define ALX_RSS_HASH_TYPE_ALL (ALX_RSS_HASH_TYPE_IPV4 | \ + ALX_RSS_HASH_TYPE_IPV4_TCP | \ + ALX_RSS_HASH_TYPE_IPV6 | \ + ALX_RSS_HASH_TYPE_IPV6_TCP) +#define ALX_DEF_RXBUF_SIZE 1536 +#define ALX_MAX_JUMBO_PKT_SIZE (9*1024) +#define ALX_MAX_TSO_PKT_SIZE (7*1024) +#define ALX_MAX_FRAME_SIZE ALX_MAX_JUMBO_PKT_SIZE +#define ALX_MIN_FRAME_SIZE 68 +#define ALX_RAW_MTU(_mtu) (_mtu + ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN) + +#define ALX_MAX_RX_QUEUES 8 +#define ALX_MAX_TX_QUEUES 4 +#define ALX_MAX_HANDLED_INTRS 5 + +#define ALX_ISR_MISC (ALX_ISR_PCIE_LNKDOWN | \ + ALX_ISR_DMAW | \ + ALX_ISR_DMAR | \ + ALX_ISR_SMB | \ + ALX_ISR_MANU | \ + ALX_ISR_TIMER) + +#define ALX_ISR_FATAL (ALX_ISR_PCIE_LNKDOWN | \ + ALX_ISR_DMAW | ALX_ISR_DMAR) + +#define ALX_ISR_ALERT (ALX_ISR_RXF_OV | \ + ALX_ISR_TXF_UR | \ + ALX_ISR_RFD_UR) + +#define ALX_ISR_ALL_QUEUES (ALX_ISR_TX_Q0 | \ + ALX_ISR_TX_Q1 | \ + ALX_ISR_TX_Q2 | \ + ALX_ISR_TX_Q3 | \ + ALX_ISR_RX_Q0 | \ + ALX_ISR_RX_Q1 | \ + ALX_ISR_RX_Q2 | \ + ALX_ISR_RX_Q3 | \ + ALX_ISR_RX_Q4 | \ + ALX_ISR_RX_Q5 | \ + ALX_ISR_RX_Q6 | \ + ALX_ISR_RX_Q7) + +/* maximum interrupt vectors for msix */ +#define ALX_MAX_MSIX_INTRS 16 + +#define ALX_GET_FIELD(_data, _field) \ + (((_data) >> _field ## _SHIFT) & _field ## _MASK) + +#define ALX_SET_FIELD(_data, _field, _value) do { \ + (_data) &= ~(_field ## _MASK << _field ## _SHIFT); \ + (_data) |= ((_value) & _field ## _MASK) << _field ## _SHIFT;\ + } while (0) + +struct alx_hw { + struct pci_dev *pdev; + u8 __iomem *hw_addr; + + /* current & permanent mac addr */ + u8 mac_addr[ETH_ALEN]; + u8 perm_addr[ETH_ALEN]; + + u16 mtu; + u16 imt; + u8 dma_chnl; + u8 max_dma_chnl; + /* tpd threshold to trig INT */ + u32 ith_tpd; + u32 rx_ctrl; + u32 mc_hash[2]; + + u32 smb_timer; + /* SPEED_* + DUPLEX_*, SPEED_UNKNOWN if link is down */ + int link_speed; + + /* auto-neg advertisement or force mode config */ + u32 adv_cfg; + u8 flowctrl; + + u32 sleep_ctrl; + + spinlock_t mdio_lock; + struct mdio_if_info mdio; + u16 phy_id[2]; + + /* PHY link patch flag */ + bool lnk_patch; +}; + +static inline int alx_hw_revision(struct alx_hw *hw) +{ + return hw->pdev->revision >> ALX_PCI_REVID_SHIFT; +} + +static inline bool alx_hw_with_cr(struct alx_hw *hw) +{ + return hw->pdev->revision & 1; +} + +static inline bool alx_hw_giga(struct alx_hw *hw) +{ + return hw->pdev->device & 1; +} + +static inline void alx_write_mem8(struct alx_hw *hw, u32 reg, u8 val) +{ + writeb(val, hw->hw_addr + reg); +} + +static inline void alx_write_mem16(struct alx_hw *hw, u32 reg, u16 val) +{ + writew(val, hw->hw_addr + reg); +} + +static inline u16 alx_read_mem16(struct alx_hw *hw, u32 reg) +{ + return readw(hw->hw_addr + reg); +} + +static inline void alx_write_mem32(struct alx_hw *hw, u32 reg, u32 val) +{ + writel(val, hw->hw_addr + reg); +} + +static inline u32 alx_read_mem32(struct alx_hw *hw, u32 reg) +{ + return readl(hw->hw_addr + reg); +} + +static inline void alx_post_write(struct alx_hw *hw) +{ + readl(hw->hw_addr); +} + +int alx_get_perm_macaddr(struct alx_hw *hw, u8 *addr); +void alx_reset_phy(struct alx_hw *hw); +void alx_reset_pcie(struct alx_hw *hw); +void alx_enable_aspm(struct alx_hw *hw, bool l0s_en, bool l1_en); +int alx_setup_speed_duplex(struct alx_hw *hw, u32 ethadv, u8 flowctrl); +void alx_post_phy_link(struct alx_hw *hw); +int alx_pre_suspend(struct alx_hw *hw, int speed); +int alx_read_phy_reg(struct alx_hw *hw, u16 reg, u16 *phy_data); +int alx_write_phy_reg(struct alx_hw *hw, u16 reg, u16 phy_data); +int alx_read_phy_ext(struct alx_hw *hw, u8 dev, u16 reg, u16 *pdata); +int alx_write_phy_ext(struct alx_hw *hw, u8 dev, u16 reg, u16 data); +int alx_get_phy_link(struct alx_hw *hw, int *speed); +int alx_clear_phy_intr(struct alx_hw *hw); +int alx_config_wol(struct alx_hw *hw); +void alx_cfg_mac_flowcontrol(struct alx_hw *hw, u8 fc); +void alx_start_mac(struct alx_hw *hw); +int alx_reset_mac(struct alx_hw *hw); +void alx_set_macaddr(struct alx_hw *hw, const u8 *addr); +bool alx_phy_configured(struct alx_hw *hw); +void alx_configure_basic(struct alx_hw *hw); +void alx_disable_rss(struct alx_hw *hw); +int alx_select_powersaving_speed(struct alx_hw *hw, int *speed); +bool alx_get_phy_info(struct alx_hw *hw); + +#endif diff --git a/drivers/net/ethernet/atheros/alx/main.c b/drivers/net/ethernet/atheros/alx/main.c new file mode 100644 index 0000000..798d6b6 --- /dev/null +++ b/drivers/net/ethernet/atheros/alx/main.c @@ -0,0 +1,1657 @@ +/* + * Copyright (c) 2013 Johannes Berg + * + * This file is free software: you may copy, redistribute and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "alx.h" +#include "hw.h" +#include "reg.h" + +const char alx_drv_name[] = "alx"; + + +static void alx_free_txbuf(struct alx_priv *alx, int entry) +{ + struct alx_buffer *txb = &alx->txq.bufs[entry]; + + if (dma_unmap_len(txb, size)) { + dma_unmap_single(&alx->hw.pdev->dev, + dma_unmap_addr(txb, dma), + dma_unmap_len(txb, size), + DMA_TO_DEVICE); + dma_unmap_len_set(txb, size, 0); + } + + if (txb->skb) { + dev_kfree_skb_any(txb->skb); + txb->skb = NULL; + } +} + +static int alx_refill_rx_ring(struct alx_priv *alx, gfp_t gfp) +{ + struct alx_rx_queue *rxq = &alx->rxq; + struct sk_buff *skb; + struct alx_buffer *cur_buf; + dma_addr_t dma; + u16 cur, next, count = 0; + + next = cur = rxq->write_idx; + if (++next == alx->rx_ringsz) + next = 0; + cur_buf = &rxq->bufs[cur]; + + while (!cur_buf->skb && next != rxq->read_idx) { + struct alx_rfd *rfd = &rxq->rfd[cur]; + + skb = __netdev_alloc_skb(alx->dev, alx->rxbuf_size, gfp); + if (!skb) + break; + dma = dma_map_single(&alx->hw.pdev->dev, + skb->data, alx->rxbuf_size, + DMA_FROM_DEVICE); + if (dma_mapping_error(&alx->hw.pdev->dev, dma)) { + dev_kfree_skb(skb); + break; + } + + /* Unfortunately, RX descriptor buffers must be 4-byte + * aligned, so we can't use IP alignment. + */ + if (WARN_ON(dma & 3)) { + dev_kfree_skb(skb); + break; + } + + cur_buf->skb = skb; + dma_unmap_len_set(cur_buf, size, alx->rxbuf_size); + dma_unmap_addr_set(cur_buf, dma, dma); + rfd->addr = cpu_to_le64(dma); + + cur = next; + if (++next == alx->rx_ringsz) + next = 0; + cur_buf = &rxq->bufs[cur]; + count++; + } + + if (count) { + /* flush all updates before updating hardware */ + wmb(); + rxq->write_idx = cur; + alx_write_mem16(&alx->hw, ALX_RFD_PIDX, cur); + } + + return count; +} + +static inline int alx_tpd_avail(struct alx_priv *alx) +{ + struct alx_tx_queue *txq = &alx->txq; + + if (txq->write_idx >= txq->read_idx) + return alx->tx_ringsz + txq->read_idx - txq->write_idx - 1; + return txq->read_idx - txq->write_idx - 1; +} + +static bool alx_clean_tx_irq(struct alx_priv *alx) +{ + struct alx_tx_queue *txq = &alx->txq; + u16 hw_read_idx, sw_read_idx; + unsigned int total_bytes = 0, total_packets = 0; + int budget = ALX_DEFAULT_TX_WORK; + + sw_read_idx = txq->read_idx; + hw_read_idx = alx_read_mem16(&alx->hw, ALX_TPD_PRI0_CIDX); + + if (sw_read_idx != hw_read_idx) { + while (sw_read_idx != hw_read_idx && budget > 0) { + struct sk_buff *skb; + + skb = txq->bufs[sw_read_idx].skb; + if (skb) { + total_bytes += skb->len; + total_packets++; + budget--; + } + + alx_free_txbuf(alx, sw_read_idx); + + if (++sw_read_idx == alx->tx_ringsz) + sw_read_idx = 0; + } + txq->read_idx = sw_read_idx; + + netdev_completed_queue(alx->dev, total_packets, total_bytes); + } + + if (netif_queue_stopped(alx->dev) && netif_carrier_ok(alx->dev) && + alx_tpd_avail(alx) > alx->tx_ringsz/4) + netif_wake_queue(alx->dev); + + return sw_read_idx == hw_read_idx; +} + +static void alx_schedule_link_check(struct alx_priv *alx) +{ + schedule_work(&alx->link_check_wk); +} + +static void alx_schedule_reset(struct alx_priv *alx) +{ + schedule_work(&alx->reset_wk); +} + +static bool alx_clean_rx_irq(struct alx_priv *alx, int budget) +{ + struct alx_rx_queue *rxq = &alx->rxq; + struct alx_rrd *rrd; + struct alx_buffer *rxb; + struct sk_buff *skb; + u16 length, rfd_cleaned = 0; + + while (budget > 0) { + rrd = &rxq->rrd[rxq->rrd_read_idx]; + if (!(rrd->word3 & cpu_to_le32(1 << RRD_UPDATED_SHIFT))) + break; + rrd->word3 &= ~cpu_to_le32(1 << RRD_UPDATED_SHIFT); + + if (ALX_GET_FIELD(le32_to_cpu(rrd->word0), + RRD_SI) != rxq->read_idx || + ALX_GET_FIELD(le32_to_cpu(rrd->word0), + RRD_NOR) != 1) { + alx_schedule_reset(alx); + return 0; + } + + rxb = &rxq->bufs[rxq->read_idx]; + dma_unmap_single(&alx->hw.pdev->dev, + dma_unmap_addr(rxb, dma), + dma_unmap_len(rxb, size), + DMA_FROM_DEVICE); + dma_unmap_len_set(rxb, size, 0); + skb = rxb->skb; + rxb->skb = NULL; + + if (rrd->word3 & cpu_to_le32(1 << RRD_ERR_RES_SHIFT) || + rrd->word3 & cpu_to_le32(1 << RRD_ERR_LEN_SHIFT)) { + rrd->word3 = 0; + dev_kfree_skb_any(skb); + goto next_pkt; + } + + length = ALX_GET_FIELD(le32_to_cpu(rrd->word3), + RRD_PKTLEN) - ETH_FCS_LEN; + skb_put(skb, length); + skb->protocol = eth_type_trans(skb, alx->dev); + + skb_checksum_none_assert(skb); + if (alx->dev->features & NETIF_F_RXCSUM && + !(rrd->word3 & (cpu_to_le32(1 << RRD_ERR_L4_SHIFT) | + cpu_to_le32(1 << RRD_ERR_IPV4_SHIFT)))) { + switch (ALX_GET_FIELD(le32_to_cpu(rrd->word2), + RRD_PID)) { + case RRD_PID_IPV6UDP: + case RRD_PID_IPV4UDP: + case RRD_PID_IPV4TCP: + case RRD_PID_IPV6TCP: + skb->ip_summed = CHECKSUM_UNNECESSARY; + break; + } + } + + napi_gro_receive(&alx->napi, skb); + budget--; + +next_pkt: + if (++rxq->read_idx == alx->rx_ringsz) + rxq->read_idx = 0; + if (++rxq->rrd_read_idx == alx->rx_ringsz) + rxq->rrd_read_idx = 0; + + if (++rfd_cleaned > ALX_RX_ALLOC_THRESH) + rfd_cleaned -= alx_refill_rx_ring(alx, GFP_ATOMIC); + } + + if (rfd_cleaned) + alx_refill_rx_ring(alx, GFP_ATOMIC); + + return budget > 0; +} + +static int alx_poll(struct napi_struct *napi, int budget) +{ + struct alx_priv *alx = container_of(napi, struct alx_priv, napi); + struct alx_hw *hw = &alx->hw; + bool complete = true; + unsigned long flags; + + complete = alx_clean_tx_irq(alx) && + alx_clean_rx_irq(alx, budget); + + if (!complete) + return 1; + + napi_complete(&alx->napi); + + /* enable interrupt */ + spin_lock_irqsave(&alx->irq_lock, flags); + alx->int_mask |= ALX_ISR_TX_Q0 | ALX_ISR_RX_Q0; + alx_write_mem32(hw, ALX_IMR, alx->int_mask); + spin_unlock_irqrestore(&alx->irq_lock, flags); + + alx_post_write(hw); + + return 0; +} + +static irqreturn_t alx_intr_handle(struct alx_priv *alx, u32 intr) +{ + struct alx_hw *hw = &alx->hw; + bool write_int_mask = false; + + spin_lock(&alx->irq_lock); + + /* ACK interrupt */ + alx_write_mem32(hw, ALX_ISR, intr | ALX_ISR_DIS); + intr &= alx->int_mask; + + if (intr & ALX_ISR_FATAL) { + netif_warn(alx, hw, alx->dev, + "fatal interrupt 0x%x, resetting\n", intr); + alx_schedule_reset(alx); + goto out; + } + + if (intr & ALX_ISR_ALERT) + netdev_warn(alx->dev, "alert interrupt: 0x%x\n", intr); + + if (intr & ALX_ISR_PHY) { + /* suppress PHY interrupt, because the source + * is from PHY internal. only the internal status + * is cleared, the interrupt status could be cleared. + */ + alx->int_mask &= ~ALX_ISR_PHY; + write_int_mask = true; + alx_schedule_link_check(alx); + } + + if (intr & (ALX_ISR_TX_Q0 | ALX_ISR_RX_Q0)) { + napi_schedule(&alx->napi); + /* mask rx/tx interrupt, enable them when napi complete */ + alx->int_mask &= ~ALX_ISR_ALL_QUEUES; + write_int_mask = true; + } + + if (write_int_mask) + alx_write_mem32(hw, ALX_IMR, alx->int_mask); + + alx_write_mem32(hw, ALX_ISR, 0); + + out: + spin_unlock(&alx->irq_lock); + return IRQ_HANDLED; +} + +static irqreturn_t alx_intr_msi(int irq, void *data) +{ + struct alx_priv *alx = data; + + return alx_intr_handle(alx, alx_read_mem32(&alx->hw, ALX_ISR)); +} + +static irqreturn_t alx_intr_legacy(int irq, void *data) +{ + struct alx_priv *alx = data; + struct alx_hw *hw = &alx->hw; + u32 intr; + + intr = alx_read_mem32(hw, ALX_ISR); + + if (intr & ALX_ISR_DIS || !(intr & alx->int_mask)) + return IRQ_NONE; + + return alx_intr_handle(alx, intr); +} + +static void alx_init_ring_ptrs(struct alx_priv *alx) +{ + struct alx_hw *hw = &alx->hw; + u32 addr_hi = ((u64)alx->descmem.dma) >> 32; + + alx->rxq.read_idx = 0; + alx->rxq.write_idx = 0; + alx->rxq.rrd_read_idx = 0; + alx_write_mem32(hw, ALX_RX_BASE_ADDR_HI, addr_hi); + alx_write_mem32(hw, ALX_RRD_ADDR_LO, alx->rxq.rrd_dma); + alx_write_mem32(hw, ALX_RRD_RING_SZ, alx->rx_ringsz); + alx_write_mem32(hw, ALX_RFD_ADDR_LO, alx->rxq.rfd_dma); + alx_write_mem32(hw, ALX_RFD_RING_SZ, alx->rx_ringsz); + alx_write_mem32(hw, ALX_RFD_BUF_SZ, alx->rxbuf_size); + + alx->txq.read_idx = 0; + alx->txq.write_idx = 0; + alx_write_mem32(hw, ALX_TX_BASE_ADDR_HI, addr_hi); + alx_write_mem32(hw, ALX_TPD_PRI0_ADDR_LO, alx->txq.tpd_dma); + alx_write_mem32(hw, ALX_TPD_RING_SZ, alx->tx_ringsz); + + /* load these pointers into the chip */ + alx_write_mem32(hw, ALX_SRAM9, ALX_SRAM_LOAD_PTR); +} + +static void alx_free_txring_buf(struct alx_priv *alx) +{ + struct alx_tx_queue *txq = &alx->txq; + int i; + + if (!txq->bufs) + return; + + for (i = 0; i < alx->tx_ringsz; i++) + alx_free_txbuf(alx, i); + + memset(txq->bufs, 0, alx->tx_ringsz * sizeof(struct alx_buffer)); + memset(txq->tpd, 0, alx->tx_ringsz * sizeof(struct alx_txd)); + txq->write_idx = 0; + txq->read_idx = 0; + + netdev_reset_queue(alx->dev); +} + +static void alx_free_rxring_buf(struct alx_priv *alx) +{ + struct alx_rx_queue *rxq = &alx->rxq; + struct alx_buffer *cur_buf; + u16 i; + + if (rxq == NULL) + return; + + for (i = 0; i < alx->rx_ringsz; i++) { + cur_buf = rxq->bufs + i; + if (cur_buf->skb) { + dma_unmap_single(&alx->hw.pdev->dev, + dma_unmap_addr(cur_buf, dma), + dma_unmap_len(cur_buf, size), + DMA_FROM_DEVICE); + dev_kfree_skb(cur_buf->skb); + cur_buf->skb = NULL; + dma_unmap_len_set(cur_buf, size, 0); + dma_unmap_addr_set(cur_buf, dma, 0); + } + } + + rxq->write_idx = 0; + rxq->read_idx = 0; + rxq->rrd_read_idx = 0; +} + +static void alx_free_buffers(struct alx_priv *alx) +{ + alx_free_txring_buf(alx); + alx_free_rxring_buf(alx); +} + +static int alx_reinit_rings(struct alx_priv *alx) +{ + alx_free_buffers(alx); + + alx_init_ring_ptrs(alx); + + if (!alx_refill_rx_ring(alx, GFP_KERNEL)) + return -ENOMEM; + + return 0; +} + +static void alx_add_mc_addr(struct alx_hw *hw, const u8 *addr, u32 *mc_hash) +{ + u32 crc32, bit, reg; + + crc32 = ether_crc(ETH_ALEN, addr); + reg = (crc32 >> 31) & 0x1; + bit = (crc32 >> 26) & 0x1F; + + mc_hash[reg] |= BIT(bit); +} + +static void __alx_set_rx_mode(struct net_device *netdev) +{ + struct alx_priv *alx = netdev_priv(netdev); + struct alx_hw *hw = &alx->hw; + struct netdev_hw_addr *ha; + u32 mc_hash[2] = {}; + + if (!(netdev->flags & IFF_ALLMULTI)) { + netdev_for_each_mc_addr(ha, netdev) +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + alx_add_mc_addr(hw, ha->addr, mc_hash); +#else + alx_add_mc_addr(hw, ha->dmi_addr, mc_hash); +#endif + + alx_write_mem32(hw, ALX_HASH_TBL0, mc_hash[0]); + alx_write_mem32(hw, ALX_HASH_TBL1, mc_hash[1]); + } + + hw->rx_ctrl &= ~(ALX_MAC_CTRL_MULTIALL_EN | ALX_MAC_CTRL_PROMISC_EN); + if (netdev->flags & IFF_PROMISC) + hw->rx_ctrl |= ALX_MAC_CTRL_PROMISC_EN; + if (netdev->flags & IFF_ALLMULTI) + hw->rx_ctrl |= ALX_MAC_CTRL_MULTIALL_EN; + + alx_write_mem32(hw, ALX_MAC_CTRL, hw->rx_ctrl); +} + +static void alx_set_rx_mode(struct net_device *netdev) +{ + __alx_set_rx_mode(netdev); +} + +static int alx_set_mac_address(struct net_device *netdev, void *data) +{ + struct alx_priv *alx = netdev_priv(netdev); + struct alx_hw *hw = &alx->hw; + struct sockaddr *addr = data; + + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)) + if (netdev->addr_assign_type & NET_ADDR_RANDOM) + netdev->addr_assign_type ^= NET_ADDR_RANDOM; +#endif + + memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len); + memcpy(hw->mac_addr, addr->sa_data, netdev->addr_len); + alx_set_macaddr(hw, hw->mac_addr); + + return 0; +} + +static int alx_alloc_descriptors(struct alx_priv *alx) +{ + alx->txq.bufs = kcalloc(alx->tx_ringsz, + sizeof(struct alx_buffer), + GFP_KERNEL); + if (!alx->txq.bufs) + return -ENOMEM; + + alx->rxq.bufs = kcalloc(alx->rx_ringsz, + sizeof(struct alx_buffer), + GFP_KERNEL); + if (!alx->rxq.bufs) + goto out_free; + + /* physical tx/rx ring descriptors + * + * Allocate them as a single chunk because they must not cross a + * 4G boundary (hardware has a single register for high 32 bits + * of addresses only) + */ + alx->descmem.size = sizeof(struct alx_txd) * alx->tx_ringsz + + sizeof(struct alx_rrd) * alx->rx_ringsz + + sizeof(struct alx_rfd) * alx->rx_ringsz; + alx->descmem.virt = dma_zalloc_coherent(&alx->hw.pdev->dev, + alx->descmem.size, + &alx->descmem.dma, + GFP_KERNEL); + if (!alx->descmem.virt) + goto out_free; + + alx->txq.tpd = (void *)alx->descmem.virt; + alx->txq.tpd_dma = alx->descmem.dma; + + /* alignment requirement for next block */ + BUILD_BUG_ON(sizeof(struct alx_txd) % 8); + + alx->rxq.rrd = + (void *)((u8 *)alx->descmem.virt + + sizeof(struct alx_txd) * alx->tx_ringsz); + alx->rxq.rrd_dma = alx->descmem.dma + + sizeof(struct alx_txd) * alx->tx_ringsz; + + /* alignment requirement for next block */ + BUILD_BUG_ON(sizeof(struct alx_rrd) % 8); + + alx->rxq.rfd = + (void *)((u8 *)alx->descmem.virt + + sizeof(struct alx_txd) * alx->tx_ringsz + + sizeof(struct alx_rrd) * alx->rx_ringsz); + alx->rxq.rfd_dma = alx->descmem.dma + + sizeof(struct alx_txd) * alx->tx_ringsz + + sizeof(struct alx_rrd) * alx->rx_ringsz; + + return 0; +out_free: + kfree(alx->txq.bufs); + kfree(alx->rxq.bufs); + return -ENOMEM; +} + +static int alx_alloc_rings(struct alx_priv *alx) +{ + int err; + + err = alx_alloc_descriptors(alx); + if (err) + return err; + + alx->int_mask &= ~ALX_ISR_ALL_QUEUES; + alx->int_mask |= ALX_ISR_TX_Q0 | ALX_ISR_RX_Q0; + alx->tx_ringsz = alx->tx_ringsz; + + netif_napi_add(alx->dev, &alx->napi, alx_poll, 64); + + alx_reinit_rings(alx); + return 0; +} + +static void alx_free_rings(struct alx_priv *alx) +{ + netif_napi_del(&alx->napi); + alx_free_buffers(alx); + + kfree(alx->txq.bufs); + kfree(alx->rxq.bufs); + + dma_free_coherent(&alx->hw.pdev->dev, + alx->descmem.size, + alx->descmem.virt, + alx->descmem.dma); +} + +static void alx_config_vector_mapping(struct alx_priv *alx) +{ + struct alx_hw *hw = &alx->hw; + + alx_write_mem32(hw, ALX_MSI_MAP_TBL1, 0); + alx_write_mem32(hw, ALX_MSI_MAP_TBL2, 0); + alx_write_mem32(hw, ALX_MSI_ID_MAP, 0); +} + +static void alx_irq_enable(struct alx_priv *alx) +{ + struct alx_hw *hw = &alx->hw; + + /* level-1 interrupt switch */ + alx_write_mem32(hw, ALX_ISR, 0); + alx_write_mem32(hw, ALX_IMR, alx->int_mask); + alx_post_write(hw); +} + +static void alx_irq_disable(struct alx_priv *alx) +{ + struct alx_hw *hw = &alx->hw; + + alx_write_mem32(hw, ALX_ISR, ALX_ISR_DIS); + alx_write_mem32(hw, ALX_IMR, 0); + alx_post_write(hw); + + synchronize_irq(alx->hw.pdev->irq); +} + +static int alx_request_irq(struct alx_priv *alx) +{ + struct pci_dev *pdev = alx->hw.pdev; + struct alx_hw *hw = &alx->hw; + int err; + u32 msi_ctrl; + + msi_ctrl = (hw->imt >> 1) << ALX_MSI_RETRANS_TM_SHIFT; + + if (!pci_enable_msi(alx->hw.pdev)) { + alx->msi = true; + + alx_write_mem32(hw, ALX_MSI_RETRANS_TIMER, + msi_ctrl | ALX_MSI_MASK_SEL_LINE); + err = request_irq(pdev->irq, alx_intr_msi, 0, + alx->dev->name, alx); + if (!err) + goto out; + /* fall back to legacy interrupt */ + pci_disable_msi(alx->hw.pdev); + } + + alx_write_mem32(hw, ALX_MSI_RETRANS_TIMER, 0); + err = request_irq(pdev->irq, alx_intr_legacy, IRQF_SHARED, + alx->dev->name, alx); +out: + if (!err) + alx_config_vector_mapping(alx); + return err; +} + +static void alx_free_irq(struct alx_priv *alx) +{ + struct pci_dev *pdev = alx->hw.pdev; + + free_irq(pdev->irq, alx); + + if (alx->msi) { + pci_disable_msi(alx->hw.pdev); + alx->msi = false; + } +} + +static int alx_identify_hw(struct alx_priv *alx) +{ + struct alx_hw *hw = &alx->hw; + int rev = alx_hw_revision(hw); + + if (rev > ALX_REV_C0) + return -EINVAL; + + hw->max_dma_chnl = rev >= ALX_REV_B0 ? 4 : 2; + + return 0; +} + +static int alx_init_sw(struct alx_priv *alx) +{ + struct pci_dev *pdev = alx->hw.pdev; + struct alx_hw *hw = &alx->hw; + int err; + + err = alx_identify_hw(alx); + if (err) { + dev_err(&pdev->dev, "unrecognized chip, aborting\n"); + return err; + } + + alx->hw.lnk_patch = + pdev->device == ALX_DEV_ID_AR8161 && + pdev->subsystem_vendor == PCI_VENDOR_ID_ATTANSIC && + pdev->subsystem_device == 0x0091 && + pdev->revision == 0; + + hw->smb_timer = 400; + hw->mtu = alx->dev->mtu; + alx->rxbuf_size = ALIGN(ALX_RAW_MTU(hw->mtu), 8); + alx->tx_ringsz = 256; + alx->rx_ringsz = 512; + hw->sleep_ctrl = ALX_SLEEP_WOL_MAGIC | ALX_SLEEP_WOL_PHY; + hw->imt = 200; + alx->int_mask = ALX_ISR_MISC; + hw->dma_chnl = hw->max_dma_chnl; + hw->ith_tpd = alx->tx_ringsz / 3; + hw->link_speed = SPEED_UNKNOWN; + hw->adv_cfg = ADVERTISED_Autoneg | + ADVERTISED_10baseT_Half | + ADVERTISED_10baseT_Full | + ADVERTISED_100baseT_Full | + ADVERTISED_100baseT_Half | + ADVERTISED_1000baseT_Full; + hw->flowctrl = ALX_FC_ANEG | ALX_FC_RX | ALX_FC_TX; + + hw->rx_ctrl = ALX_MAC_CTRL_WOLSPED_SWEN | + ALX_MAC_CTRL_MHASH_ALG_HI5B | + ALX_MAC_CTRL_BRD_EN | + ALX_MAC_CTRL_PCRCE | + ALX_MAC_CTRL_CRCE | + ALX_MAC_CTRL_RXFC_EN | + ALX_MAC_CTRL_TXFC_EN | + 7 << ALX_MAC_CTRL_PRMBLEN_SHIFT; + + return err; +} + + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) +static netdev_features_t alx_fix_features(struct net_device *netdev, + netdev_features_t features) +{ + if (netdev->mtu > ALX_MAX_TSO_PKT_SIZE) + features &= ~(NETIF_F_TSO | NETIF_F_TSO6); + + return features; +} +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */ + +static void alx_netif_stop(struct alx_priv *alx) +{ + alx->dev->trans_start = jiffies; + if (netif_carrier_ok(alx->dev)) { + netif_carrier_off(alx->dev); + netif_tx_disable(alx->dev); + napi_disable(&alx->napi); + } +} + +static void alx_halt(struct alx_priv *alx) +{ + struct alx_hw *hw = &alx->hw; + + alx_netif_stop(alx); + hw->link_speed = SPEED_UNKNOWN; + + alx_reset_mac(hw); + + /* disable l0s/l1 */ + alx_enable_aspm(hw, false, false); + alx_irq_disable(alx); + alx_free_buffers(alx); +} + +static void alx_configure(struct alx_priv *alx) +{ + struct alx_hw *hw = &alx->hw; + + alx_configure_basic(hw); + alx_disable_rss(hw); + __alx_set_rx_mode(alx->dev); + + alx_write_mem32(hw, ALX_MAC_CTRL, hw->rx_ctrl); +} + +static void alx_activate(struct alx_priv *alx) +{ + /* hardware setting lost, restore it */ + alx_reinit_rings(alx); + alx_configure(alx); + + /* clear old interrupts */ + alx_write_mem32(&alx->hw, ALX_ISR, ~(u32)ALX_ISR_DIS); + + alx_irq_enable(alx); + + alx_schedule_link_check(alx); +} + +static void alx_reinit(struct alx_priv *alx) +{ + ASSERT_RTNL(); + + alx_halt(alx); + alx_activate(alx); +} + +static int alx_change_mtu(struct net_device *netdev, int mtu) +{ + struct alx_priv *alx = netdev_priv(netdev); + int max_frame = mtu + ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN; + + if ((max_frame < ALX_MIN_FRAME_SIZE) || + (max_frame > ALX_MAX_FRAME_SIZE)) + return -EINVAL; + + if (netdev->mtu == mtu) + return 0; + + netdev->mtu = mtu; + alx->hw.mtu = mtu; + alx->rxbuf_size = mtu > ALX_DEF_RXBUF_SIZE ? + ALIGN(max_frame, 8) : ALX_DEF_RXBUF_SIZE; +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) + if (mtu > (7*1024)) { + netdev->features &= ~NETIF_F_TSO; + netdev->features &= ~NETIF_F_TSO6; + } else { + netdev->features |= NETIF_F_TSO; + netdev->features |= NETIF_F_TSO6; + } +#else + netdev_update_features(netdev); +#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) */ + if (netif_running(netdev)) + alx_reinit(alx); + return 0; +} + +static void alx_netif_start(struct alx_priv *alx) +{ + netif_tx_wake_all_queues(alx->dev); + napi_enable(&alx->napi); + netif_carrier_on(alx->dev); +} + +static int __alx_open(struct alx_priv *alx, bool resume) +{ + int err; + + if (!resume) + netif_carrier_off(alx->dev); + + err = alx_alloc_rings(alx); + if (err) + return err; + + alx_configure(alx); + + err = alx_request_irq(alx); + if (err) + goto out_free_rings; + + /* clear old interrupts */ + alx_write_mem32(&alx->hw, ALX_ISR, ~(u32)ALX_ISR_DIS); + + alx_irq_enable(alx); + + if (!resume) + netif_tx_start_all_queues(alx->dev); + + alx_schedule_link_check(alx); + return 0; + +out_free_rings: + alx_free_rings(alx); + return err; +} + +static void __alx_stop(struct alx_priv *alx) +{ + alx_halt(alx); + alx_free_irq(alx); + alx_free_rings(alx); +} + +static const char *alx_speed_desc(u16 speed) +{ + switch (speed) { + case SPEED_1000 + DUPLEX_FULL: + return "1 Gbps Full"; + case SPEED_100 + DUPLEX_FULL: + return "100 Mbps Full"; + case SPEED_100 + DUPLEX_HALF: + return "100 Mbps Half"; + case SPEED_10 + DUPLEX_FULL: + return "10 Mbps Full"; + case SPEED_10 + DUPLEX_HALF: + return "10 Mbps Half"; + default: + return "Unknown speed"; + } +} + +static void alx_check_link(struct alx_priv *alx) +{ + struct alx_hw *hw = &alx->hw; + unsigned long flags; + int speed, old_speed; + int err; + + /* clear PHY internal interrupt status, otherwise the main + * interrupt status will be asserted forever + */ + alx_clear_phy_intr(hw); + + err = alx_get_phy_link(hw, &speed); + if (err < 0) + goto reset; + + spin_lock_irqsave(&alx->irq_lock, flags); + alx->int_mask |= ALX_ISR_PHY; + alx_write_mem32(hw, ALX_IMR, alx->int_mask); + spin_unlock_irqrestore(&alx->irq_lock, flags); + + old_speed = hw->link_speed; + + if (old_speed == speed) + return; + hw->link_speed = speed; + + if (speed != SPEED_UNKNOWN) { + netif_info(alx, link, alx->dev, + "NIC Up: %s\n", alx_speed_desc(speed)); + alx_post_phy_link(hw); + alx_enable_aspm(hw, true, true); + alx_start_mac(hw); + + if (old_speed == SPEED_UNKNOWN) + alx_netif_start(alx); + } else { + /* link is now down */ + alx_netif_stop(alx); + netif_info(alx, link, alx->dev, "Link Down\n"); + err = alx_reset_mac(hw); + if (err) + goto reset; + alx_irq_disable(alx); + + /* MAC reset causes all HW settings to be lost, restore all */ + err = alx_reinit_rings(alx); + if (err) + goto reset; + alx_configure(alx); + alx_enable_aspm(hw, false, true); + alx_post_phy_link(hw); + alx_irq_enable(alx); + } + + return; + +reset: + alx_schedule_reset(alx); +} + +static int alx_open(struct net_device *netdev) +{ + return __alx_open(netdev_priv(netdev), false); +} + +static int alx_stop(struct net_device *netdev) +{ + __alx_stop(netdev_priv(netdev)); + return 0; +} + +static int __alx_shutdown(struct pci_dev *pdev, bool *wol_en) +{ + struct alx_priv *alx = pci_get_drvdata(pdev); + struct net_device *netdev = alx->dev; + struct alx_hw *hw = &alx->hw; + int err, speed; + + netif_device_detach(netdev); + + if (netif_running(netdev)) + __alx_stop(alx); + +#ifdef CONFIG_PM_SLEEP + err = pci_save_state(pdev); + if (err) + return err; +#endif + + err = alx_select_powersaving_speed(hw, &speed); + if (err) + return err; + err = alx_clear_phy_intr(hw); + if (err) + return err; + err = alx_pre_suspend(hw, speed); + if (err) + return err; + err = alx_config_wol(hw); + if (err) + return err; + + *wol_en = false; + if (hw->sleep_ctrl & ALX_SLEEP_ACTIVE) { + netif_info(alx, wol, netdev, + "wol: ctrl=%X, speed=%X\n", + hw->sleep_ctrl, speed); + device_set_wakeup_enable(&pdev->dev, true); + *wol_en = true; + } + + pci_disable_device(pdev); + + return 0; +} + +static void alx_shutdown(struct pci_dev *pdev) +{ + int err; + bool wol_en; + + err = __alx_shutdown(pdev, &wol_en); + if (!err) { + pci_wake_from_d3(pdev, wol_en); + pci_set_power_state(pdev, PCI_D3hot); + } else { + dev_err(&pdev->dev, "shutdown fail %d\n", err); + } +} + +static void alx_link_check(struct work_struct *work) +{ + struct alx_priv *alx; + + alx = container_of(work, struct alx_priv, link_check_wk); + + rtnl_lock(); + alx_check_link(alx); + rtnl_unlock(); +} + +static void alx_reset(struct work_struct *work) +{ + struct alx_priv *alx = container_of(work, struct alx_priv, reset_wk); + + rtnl_lock(); + alx_reinit(alx); + rtnl_unlock(); +} + +static int alx_tx_csum(struct sk_buff *skb, struct alx_txd *first) +{ + u8 cso, css; + + if (skb->ip_summed != CHECKSUM_PARTIAL) + return 0; + + cso = skb_checksum_start_offset(skb); + if (cso & 1) + return -EINVAL; + + css = cso + skb->csum_offset; + first->word1 |= cpu_to_le32((cso >> 1) << TPD_CXSUMSTART_SHIFT); + first->word1 |= cpu_to_le32((css >> 1) << TPD_CXSUMOFFSET_SHIFT); + first->word1 |= cpu_to_le32(1 << TPD_CXSUM_EN_SHIFT); + + return 0; +} + +static int alx_map_tx_skb(struct alx_priv *alx, struct sk_buff *skb) +{ + struct alx_tx_queue *txq = &alx->txq; + struct alx_txd *tpd, *first_tpd; + dma_addr_t dma; + int maplen, f, first_idx = txq->write_idx; + + first_tpd = &txq->tpd[txq->write_idx]; + tpd = first_tpd; + + maplen = skb_headlen(skb); + dma = dma_map_single(&alx->hw.pdev->dev, skb->data, maplen, + DMA_TO_DEVICE); + if (dma_mapping_error(&alx->hw.pdev->dev, dma)) + goto err_dma; + + dma_unmap_len_set(&txq->bufs[txq->write_idx], size, maplen); + dma_unmap_addr_set(&txq->bufs[txq->write_idx], dma, dma); + + tpd->adrl.addr = cpu_to_le64(dma); + tpd->len = cpu_to_le16(maplen); + + for (f = 0; f < skb_shinfo(skb)->nr_frags; f++) { + struct skb_frag_struct *frag; + + frag = &skb_shinfo(skb)->frags[f]; + + if (++txq->write_idx == alx->tx_ringsz) + txq->write_idx = 0; + tpd = &txq->tpd[txq->write_idx]; + + tpd->word1 = first_tpd->word1; + + maplen = skb_frag_size(frag); + dma = skb_frag_dma_map(&alx->hw.pdev->dev, frag, 0, + maplen, DMA_TO_DEVICE); + if (dma_mapping_error(&alx->hw.pdev->dev, dma)) + goto err_dma; + dma_unmap_len_set(&txq->bufs[txq->write_idx], size, maplen); + dma_unmap_addr_set(&txq->bufs[txq->write_idx], dma, dma); + + tpd->adrl.addr = cpu_to_le64(dma); + tpd->len = cpu_to_le16(maplen); + } + + /* last TPD, set EOP flag and store skb */ + tpd->word1 |= cpu_to_le32(1 << TPD_EOP_SHIFT); + txq->bufs[txq->write_idx].skb = skb; + + if (++txq->write_idx == alx->tx_ringsz) + txq->write_idx = 0; + + return 0; + +err_dma: + f = first_idx; + while (f != txq->write_idx) { + alx_free_txbuf(alx, f); + if (++f == alx->tx_ringsz) + f = 0; + } + return -ENOMEM; +} + +static netdev_tx_t alx_start_xmit(struct sk_buff *skb, + struct net_device *netdev) +{ + struct alx_priv *alx = netdev_priv(netdev); + struct alx_tx_queue *txq = &alx->txq; + struct alx_txd *first; + int tpdreq = skb_shinfo(skb)->nr_frags + 1; + + if (alx_tpd_avail(alx) < tpdreq) { + netif_stop_queue(alx->dev); + goto drop; + } + + first = &txq->tpd[txq->write_idx]; + memset(first, 0, sizeof(*first)); + + if (alx_tx_csum(skb, first)) + goto drop; + + if (alx_map_tx_skb(alx, skb) < 0) + goto drop; + + netdev_sent_queue(alx->dev, skb->len); + + /* flush updates before updating hardware */ + wmb(); + alx_write_mem16(&alx->hw, ALX_TPD_PRI0_PIDX, txq->write_idx); + + if (alx_tpd_avail(alx) < alx->tx_ringsz/8) + netif_stop_queue(alx->dev); + + return NETDEV_TX_OK; + +drop: + dev_kfree_skb(skb); + return NETDEV_TX_OK; +} + +static void alx_tx_timeout(struct net_device *dev) +{ + struct alx_priv *alx = netdev_priv(dev); + + alx_schedule_reset(alx); +} + +static int alx_mdio_read(struct net_device *netdev, + int prtad, int devad, u16 addr) +{ + struct alx_priv *alx = netdev_priv(netdev); + struct alx_hw *hw = &alx->hw; + u16 val; + int err; + + if (prtad != hw->mdio.prtad) + return -EINVAL; + + if (devad == MDIO_DEVAD_NONE) + err = alx_read_phy_reg(hw, addr, &val); + else + err = alx_read_phy_ext(hw, devad, addr, &val); + + if (err) + return err; + return val; +} + +static int alx_mdio_write(struct net_device *netdev, + int prtad, int devad, u16 addr, u16 val) +{ + struct alx_priv *alx = netdev_priv(netdev); + struct alx_hw *hw = &alx->hw; + + if (prtad != hw->mdio.prtad) + return -EINVAL; + + if (devad == MDIO_DEVAD_NONE) + return alx_write_phy_reg(hw, addr, val); + + return alx_write_phy_ext(hw, devad, addr, val); +} + +static int alx_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd) +{ + struct alx_priv *alx = netdev_priv(netdev); + + if (!netif_running(netdev)) + return -EAGAIN; + + return mdio_mii_ioctl(&alx->hw.mdio, if_mii(ifr), cmd); +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +static void alx_poll_controller(struct net_device *netdev) +{ + struct alx_priv *alx = netdev_priv(netdev); + + if (alx->msi) + alx_intr_msi(0, alx); + else + alx_intr_legacy(0, alx); +} +#endif + +static const struct net_device_ops alx_netdev_ops = { + .ndo_open = alx_open, + .ndo_stop = alx_stop, + .ndo_start_xmit = alx_start_xmit, + .ndo_set_rx_mode = alx_set_rx_mode, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_mac_address = alx_set_mac_address, + .ndo_change_mtu = alx_change_mtu, + .ndo_do_ioctl = alx_ioctl, + .ndo_tx_timeout = alx_tx_timeout, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + .ndo_fix_features = alx_fix_features, +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */ +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = alx_poll_controller, +#endif +}; + +static int alx_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct net_device *netdev; + struct alx_priv *alx; + struct alx_hw *hw; + bool phy_configured; + int bars, pm_cap, err; + + err = pci_enable_device_mem(pdev); + if (err) + return err; + + /* The alx chip can DMA to 64-bit addresses, but it uses a single + * shared register for the high 32 bits, so only a single, aligned, + * 4 GB physical address range can be used for descriptors. + */ + if (!dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)) && + !dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64))) { + dev_dbg(&pdev->dev, "DMA to 64-BIT addresses\n"); + } else { + err = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (err) { + err = dma_set_coherent_mask(&pdev->dev, + DMA_BIT_MASK(32)); + if (err) { + dev_err(&pdev->dev, + "No usable DMA config, aborting\n"); + goto out_pci_disable; + } + } + } + + bars = pci_select_bars(pdev, IORESOURCE_MEM); + err = pci_request_selected_regions(pdev, bars, alx_drv_name); + if (err) { + dev_err(&pdev->dev, + "pci_request_selected_regions failed(bars:%d)\n", bars); + goto out_pci_disable; + } + + pci_enable_pcie_error_reporting(pdev); + pci_set_master(pdev); + + pm_cap = pci_find_capability(pdev, PCI_CAP_ID_PM); + if (pm_cap == 0) { + dev_err(&pdev->dev, + "Can't find power management capability, aborting\n"); + err = -EIO; + goto out_pci_release; + } + + err = pci_set_power_state(pdev, PCI_D0); + if (err) + goto out_pci_release; + + netdev = alloc_etherdev(sizeof(*alx)); + if (!netdev) { + err = -ENOMEM; + goto out_pci_release; + } + + SET_NETDEV_DEV(netdev, &pdev->dev); + alx = netdev_priv(netdev); + spin_lock_init(&alx->hw.mdio_lock); + spin_lock_init(&alx->irq_lock); + alx->dev = netdev; + alx->hw.pdev = pdev; + alx->msg_enable = NETIF_MSG_LINK | NETIF_MSG_HW | NETIF_MSG_IFUP | + NETIF_MSG_TX_ERR | NETIF_MSG_RX_ERR | NETIF_MSG_WOL; + hw = &alx->hw; + pci_set_drvdata(pdev, alx); + + hw->hw_addr = pci_ioremap_bar(pdev, 0); + if (!hw->hw_addr) { + dev_err(&pdev->dev, "cannot map device registers\n"); + err = -EIO; + goto out_free_netdev; + } + + netdev_attach_ops(netdev, &alx_netdev_ops); + SET_ETHTOOL_OPS(netdev, &alx_ethtool_ops); + netdev->irq = pdev->irq; + netdev->watchdog_timeo = ALX_WATCHDOG_TIME; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) + if (ent->driver_data & ALX_DEV_QUIRK_MSI_INTX_DISABLE_BUG) + pdev->dev_flags |= PCI_DEV_FLAGS_MSI_INTX_DISABLE_BUG; +#endif + + err = alx_init_sw(alx); + if (err) { + dev_err(&pdev->dev, "net device private data init failed\n"); + goto out_unmap; + } + + alx_reset_pcie(hw); + + phy_configured = alx_phy_configured(hw); + + if (!phy_configured) + alx_reset_phy(hw); + + err = alx_reset_mac(hw); + if (err) { + dev_err(&pdev->dev, "MAC Reset failed, error = %d\n", err); + goto out_unmap; + } + + /* setup link to put it in a known good starting state */ + if (!phy_configured) { + err = alx_setup_speed_duplex(hw, hw->adv_cfg, hw->flowctrl); + if (err) { + dev_err(&pdev->dev, + "failed to configure PHY speed/duplex (err=%d)\n", + err); + goto out_unmap; + } + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + netdev->hw_features = NETIF_F_SG | NETIF_F_HW_CSUM; +#else + netdev->features = NETIF_F_SG | NETIF_F_HW_CSUM; +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */ + + if (alx_get_perm_macaddr(hw, hw->perm_addr)) { + dev_warn(&pdev->dev, + "Invalid permanent address programmed, using random one\n"); + eth_hw_addr_random(netdev); + memcpy(hw->perm_addr, netdev->dev_addr, netdev->addr_len); + } + + memcpy(hw->mac_addr, hw->perm_addr, ETH_ALEN); + memcpy(netdev->dev_addr, hw->mac_addr, ETH_ALEN); + memcpy(netdev->perm_addr, hw->perm_addr, ETH_ALEN); + + hw->mdio.prtad = 0; + hw->mdio.mmds = 0; + hw->mdio.dev = netdev; + hw->mdio.mode_support = MDIO_SUPPORTS_C45 | + MDIO_SUPPORTS_C22 | + MDIO_EMULATE_C22; + hw->mdio.mdio_read = alx_mdio_read; + hw->mdio.mdio_write = alx_mdio_write; + + if (!alx_get_phy_info(hw)) { + dev_err(&pdev->dev, "failed to identify PHY\n"); + err = -EIO; + goto out_unmap; + } + + INIT_WORK(&alx->link_check_wk, alx_link_check); + INIT_WORK(&alx->reset_wk, alx_reset); + netif_carrier_off(netdev); + + err = register_netdev(netdev); + if (err) { + dev_err(&pdev->dev, "register netdevice failed\n"); + goto out_unmap; + } + + device_set_wakeup_enable(&pdev->dev, hw->sleep_ctrl); + + netdev_info(netdev, + "Qualcomm Atheros AR816x/AR817x Ethernet [%pM]\n", + netdev->dev_addr); + + return 0; + +out_unmap: + iounmap(hw->hw_addr); +out_free_netdev: + free_netdev(netdev); +out_pci_release: + pci_release_selected_regions(pdev, bars); +out_pci_disable: + pci_disable_device(pdev); + return err; +} + +static void alx_remove(struct pci_dev *pdev) +{ + struct alx_priv *alx = pci_get_drvdata(pdev); + struct alx_hw *hw = &alx->hw; + + cancel_work_sync(&alx->link_check_wk); + cancel_work_sync(&alx->reset_wk); + + /* restore permanent mac address */ + alx_set_macaddr(hw, hw->perm_addr); + + unregister_netdev(alx->dev); + iounmap(hw->hw_addr); + pci_release_selected_regions(pdev, + pci_select_bars(pdev, IORESOURCE_MEM)); + + pci_disable_pcie_error_reporting(pdev); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); + + free_netdev(alx->dev); +} + +#ifdef CONFIG_PM_SLEEP +static int alx_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + int err; + bool wol_en; + + err = __alx_shutdown(pdev, &wol_en); + if (err) { + dev_err(&pdev->dev, "shutdown fail in suspend %d\n", err); + return err; + } + + if (wol_en) { + pci_prepare_to_sleep(pdev); + } else { + pci_wake_from_d3(pdev, false); + pci_set_power_state(pdev, PCI_D3hot); + } + + return 0; +} + +static int alx_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct alx_priv *alx = pci_get_drvdata(pdev); + struct net_device *netdev = alx->dev; + struct alx_hw *hw = &alx->hw; + int err; + + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + pci_save_state(pdev); + + pci_enable_wake(pdev, PCI_D3hot, 0); + pci_enable_wake(pdev, PCI_D3cold, 0); + + hw->link_speed = SPEED_UNKNOWN; + alx->int_mask = ALX_ISR_MISC; + + alx_reset_pcie(hw); + alx_reset_phy(hw); + + err = alx_reset_mac(hw); + if (err) { + netif_err(alx, hw, alx->dev, + "resume:reset_mac fail %d\n", err); + return -EIO; + } + + err = alx_setup_speed_duplex(hw, hw->adv_cfg, hw->flowctrl); + if (err) { + netif_err(alx, hw, alx->dev, + "resume:setup_speed_duplex fail %d\n", err); + return -EIO; + } + + if (netif_running(netdev)) { + err = __alx_open(alx, true); + if (err) + return err; + } + + netif_device_attach(netdev); + + return err; +} +#endif + +static pci_ers_result_t alx_pci_error_detected(struct pci_dev *pdev, + pci_channel_state_t state) +{ + struct alx_priv *alx = pci_get_drvdata(pdev); + struct net_device *netdev = alx->dev; + pci_ers_result_t rc = PCI_ERS_RESULT_NEED_RESET; + + dev_info(&pdev->dev, "pci error detected\n"); + + rtnl_lock(); + + if (netif_running(netdev)) { + netif_device_detach(netdev); + alx_halt(alx); + } + + if (state == pci_channel_io_perm_failure) + rc = PCI_ERS_RESULT_DISCONNECT; + else + pci_disable_device(pdev); + + rtnl_unlock(); + + return rc; +} + +static pci_ers_result_t alx_pci_error_slot_reset(struct pci_dev *pdev) +{ + struct alx_priv *alx = pci_get_drvdata(pdev); + struct alx_hw *hw = &alx->hw; + pci_ers_result_t rc = PCI_ERS_RESULT_DISCONNECT; + + dev_info(&pdev->dev, "pci error slot reset\n"); + + rtnl_lock(); + + if (pci_enable_device(pdev)) { + dev_err(&pdev->dev, "Failed to re-enable PCI device after reset\n"); + goto out; + } + + pci_set_master(pdev); + pci_enable_wake(pdev, PCI_D3hot, 0); + pci_enable_wake(pdev, PCI_D3cold, 0); + + alx_reset_pcie(hw); + if (!alx_reset_mac(hw)) + rc = PCI_ERS_RESULT_RECOVERED; +out: + pci_cleanup_aer_uncorrect_error_status(pdev); + + rtnl_unlock(); + + return rc; +} + +static void alx_pci_error_resume(struct pci_dev *pdev) +{ + struct alx_priv *alx = pci_get_drvdata(pdev); + struct net_device *netdev = alx->dev; + + dev_info(&pdev->dev, "pci error resume\n"); + + rtnl_lock(); + + if (netif_running(netdev)) { + alx_activate(alx); + netif_device_attach(netdev); + } + + rtnl_unlock(); +} + +static const struct pci_error_handlers alx_err_handlers = { + .error_detected = alx_pci_error_detected, + .slot_reset = alx_pci_error_slot_reset, + .resume = alx_pci_error_resume, +}; + +#ifdef CONFIG_PM_SLEEP +compat_pci_suspend(alx_suspend); +compat_pci_resume(alx_resume); +static SIMPLE_DEV_PM_OPS(alx_pm_ops, alx_suspend, alx_resume); +#define ALX_PM_OPS (&alx_pm_ops) +#else +#define ALX_PM_OPS NULL +#endif + +static DEFINE_PCI_DEVICE_TABLE(alx_pci_tbl) = { + { PCI_VDEVICE(ATTANSIC, ALX_DEV_ID_AR8161), + .driver_data = ALX_DEV_QUIRK_MSI_INTX_DISABLE_BUG }, + { PCI_VDEVICE(ATTANSIC, ALX_DEV_ID_E2200), + .driver_data = ALX_DEV_QUIRK_MSI_INTX_DISABLE_BUG }, + { PCI_VDEVICE(ATTANSIC, ALX_DEV_ID_AR8162), + .driver_data = ALX_DEV_QUIRK_MSI_INTX_DISABLE_BUG }, + { PCI_VDEVICE(ATTANSIC, ALX_DEV_ID_AR8171) }, + { PCI_VDEVICE(ATTANSIC, ALX_DEV_ID_AR8172) }, + {} +}; + +static struct pci_driver alx_driver = { + .name = alx_drv_name, + .id_table = alx_pci_tbl, + .probe = alx_probe, + .remove = alx_remove, + .shutdown = alx_shutdown, + .err_handler = &alx_err_handlers, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) + .driver.pm = ALX_PM_OPS, +#elif defined(CONFIG_PM_SLEEP) + .suspend = alx_suspend_compat, + .resume = alx_resume_compat, +#endif +}; + +module_pci_driver(alx_driver); +MODULE_DEVICE_TABLE(pci, alx_pci_tbl); +MODULE_AUTHOR("Johannes Berg "); +MODULE_AUTHOR("Qualcomm Corporation, "); +MODULE_DESCRIPTION( + "Qualcomm Atheros(R) AR816x/AR817x PCI-E Ethernet Network Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/atheros/alx/reg.h b/drivers/net/ethernet/atheros/alx/reg.h new file mode 100644 index 0000000..e4358c9 --- /dev/null +++ b/drivers/net/ethernet/atheros/alx/reg.h @@ -0,0 +1,810 @@ +/* + * Copyright (c) 2013 Johannes Berg + * + * This file is free software: you may copy, redistribute and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef ALX_REG_H +#define ALX_REG_H + +#define ALX_DEV_ID_AR8161 0x1091 +#define ALX_DEV_ID_E2200 0xe091 +#define ALX_DEV_ID_AR8162 0x1090 +#define ALX_DEV_ID_AR8171 0x10A1 +#define ALX_DEV_ID_AR8172 0x10A0 + +/* rev definition, + * bit(0): with xD support + * bit(1): with Card Reader function + * bit(7:2): real revision + */ +#define ALX_PCI_REVID_SHIFT 3 +#define ALX_REV_A0 0 +#define ALX_REV_A1 1 +#define ALX_REV_B0 2 +#define ALX_REV_C0 3 + +#define ALX_DEV_CTRL 0x0060 +#define ALX_DEV_CTRL_MAXRRS_MIN 2 + +#define ALX_MSIX_MASK 0x0090 + +#define ALX_UE_SVRT 0x010C +#define ALX_UE_SVRT_FCPROTERR BIT(13) +#define ALX_UE_SVRT_DLPROTERR BIT(4) + +/* eeprom & flash load register */ +#define ALX_EFLD 0x0204 +#define ALX_EFLD_F_EXIST BIT(10) +#define ALX_EFLD_E_EXIST BIT(9) +#define ALX_EFLD_STAT BIT(5) +#define ALX_EFLD_START BIT(0) + +/* eFuse load register */ +#define ALX_SLD 0x0218 +#define ALX_SLD_STAT BIT(12) +#define ALX_SLD_START BIT(11) +#define ALX_SLD_MAX_TO 100 + +#define ALX_PDLL_TRNS1 0x1104 +#define ALX_PDLL_TRNS1_D3PLLOFF_EN BIT(11) + +#define ALX_PMCTRL 0x12F8 +#define ALX_PMCTRL_HOTRST_WTEN BIT(31) +/* bit30: L0s/L1 controlled by MAC based on throughput(setting in 15A0) */ +#define ALX_PMCTRL_ASPM_FCEN BIT(30) +#define ALX_PMCTRL_SADLY_EN BIT(29) +#define ALX_PMCTRL_LCKDET_TIMER_MASK 0xF +#define ALX_PMCTRL_LCKDET_TIMER_SHIFT 24 +#define ALX_PMCTRL_LCKDET_TIMER_DEF 0xC +/* bit[23:20] if pm_request_l1 time > @, then enter L0s not L1 */ +#define ALX_PMCTRL_L1REQ_TO_MASK 0xF +#define ALX_PMCTRL_L1REQ_TO_SHIFT 20 +#define ALX_PMCTRL_L1REG_TO_DEF 0xF +#define ALX_PMCTRL_TXL1_AFTER_L0S BIT(19) +#define ALX_PMCTRL_L1_TIMER_MASK 0x7 +#define ALX_PMCTRL_L1_TIMER_SHIFT 16 +#define ALX_PMCTRL_L1_TIMER_16US 4 +#define ALX_PMCTRL_RCVR_WT_1US BIT(15) +/* bit13: enable pcie clk switch in L1 state */ +#define ALX_PMCTRL_L1_CLKSW_EN BIT(13) +#define ALX_PMCTRL_L0S_EN BIT(12) +#define ALX_PMCTRL_RXL1_AFTER_L0S BIT(11) +#define ALX_PMCTRL_L1_BUFSRX_EN BIT(7) +/* bit6: power down serdes RX */ +#define ALX_PMCTRL_L1_SRDSRX_PWD BIT(6) +#define ALX_PMCTRL_L1_SRDSPLL_EN BIT(5) +#define ALX_PMCTRL_L1_SRDS_EN BIT(4) +#define ALX_PMCTRL_L1_EN BIT(3) + +/*******************************************************/ +/* following registers are mapped only to memory space */ +/*******************************************************/ + +#define ALX_MASTER 0x1400 +/* bit12: 1:alwys select pclk from serdes, not sw to 25M */ +#define ALX_MASTER_PCLKSEL_SRDS BIT(12) +/* bit11: irq moduration for rx */ +#define ALX_MASTER_IRQMOD2_EN BIT(11) +/* bit10: irq moduration for tx/rx */ +#define ALX_MASTER_IRQMOD1_EN BIT(10) +#define ALX_MASTER_SYSALVTIMER_EN BIT(7) +#define ALX_MASTER_OOB_DIS BIT(6) +/* bit5: wakeup without pcie clk */ +#define ALX_MASTER_WAKEN_25M BIT(5) +/* bit0: MAC & DMA reset */ +#define ALX_MASTER_DMA_MAC_RST BIT(0) +#define ALX_DMA_MAC_RST_TO 50 + +#define ALX_IRQ_MODU_TIMER 0x1408 +#define ALX_IRQ_MODU_TIMER1_MASK 0xFFFF +#define ALX_IRQ_MODU_TIMER1_SHIFT 0 + +#define ALX_PHY_CTRL 0x140C +#define ALX_PHY_CTRL_100AB_EN BIT(17) +/* bit14: affect MAC & PHY, go to low power sts */ +#define ALX_PHY_CTRL_POWER_DOWN BIT(14) +/* bit13: 1:pll always ON, 0:can switch in lpw */ +#define ALX_PHY_CTRL_PLL_ON BIT(13) +#define ALX_PHY_CTRL_RST_ANALOG BIT(12) +#define ALX_PHY_CTRL_HIB_PULSE BIT(11) +#define ALX_PHY_CTRL_HIB_EN BIT(10) +#define ALX_PHY_CTRL_IDDQ BIT(7) +#define ALX_PHY_CTRL_GATE_25M BIT(5) +#define ALX_PHY_CTRL_LED_MODE BIT(2) +/* bit0: out of dsp RST state */ +#define ALX_PHY_CTRL_DSPRST_OUT BIT(0) +#define ALX_PHY_CTRL_DSPRST_TO 80 +#define ALX_PHY_CTRL_CLS (ALX_PHY_CTRL_LED_MODE | \ + ALX_PHY_CTRL_100AB_EN | \ + ALX_PHY_CTRL_PLL_ON) + +#define ALX_MAC_STS 0x1410 +#define ALX_MAC_STS_TXQ_BUSY BIT(3) +#define ALX_MAC_STS_RXQ_BUSY BIT(2) +#define ALX_MAC_STS_TXMAC_BUSY BIT(1) +#define ALX_MAC_STS_RXMAC_BUSY BIT(0) +#define ALX_MAC_STS_IDLE (ALX_MAC_STS_TXQ_BUSY | \ + ALX_MAC_STS_RXQ_BUSY | \ + ALX_MAC_STS_TXMAC_BUSY | \ + ALX_MAC_STS_RXMAC_BUSY) + +#define ALX_MDIO 0x1414 +#define ALX_MDIO_MODE_EXT BIT(30) +#define ALX_MDIO_BUSY BIT(27) +#define ALX_MDIO_CLK_SEL_MASK 0x7 +#define ALX_MDIO_CLK_SEL_SHIFT 24 +#define ALX_MDIO_CLK_SEL_25MD4 0 +#define ALX_MDIO_CLK_SEL_25MD128 7 +#define ALX_MDIO_START BIT(23) +#define ALX_MDIO_SPRES_PRMBL BIT(22) +/* bit21: 1:read,0:write */ +#define ALX_MDIO_OP_READ BIT(21) +#define ALX_MDIO_REG_MASK 0x1F +#define ALX_MDIO_REG_SHIFT 16 +#define ALX_MDIO_DATA_MASK 0xFFFF +#define ALX_MDIO_DATA_SHIFT 0 +#define ALX_MDIO_MAX_AC_TO 120 + +#define ALX_MDIO_EXTN 0x1448 +#define ALX_MDIO_EXTN_DEVAD_MASK 0x1F +#define ALX_MDIO_EXTN_DEVAD_SHIFT 16 +#define ALX_MDIO_EXTN_REG_MASK 0xFFFF +#define ALX_MDIO_EXTN_REG_SHIFT 0 + +#define ALX_SERDES 0x1424 +#define ALX_SERDES_PHYCLK_SLWDWN BIT(18) +#define ALX_SERDES_MACCLK_SLWDWN BIT(17) + +#define ALX_LPI_CTRL 0x1440 +#define ALX_LPI_CTRL_EN BIT(0) + +/* for B0+, bit[13..] for C0+ */ +#define ALX_HRTBT_EXT_CTRL 0x1AD0 +#define L1F_HRTBT_EXT_CTRL_PERIOD_HIGH_MASK 0x3F +#define L1F_HRTBT_EXT_CTRL_PERIOD_HIGH_SHIFT 24 +#define L1F_HRTBT_EXT_CTRL_SWOI_STARTUP_PKT_EN BIT(23) +#define L1F_HRTBT_EXT_CTRL_IOAC_2_FRAGMENTED BIT(22) +#define L1F_HRTBT_EXT_CTRL_IOAC_1_FRAGMENTED BIT(21) +#define L1F_HRTBT_EXT_CTRL_IOAC_1_KEEPALIVE_EN BIT(20) +#define L1F_HRTBT_EXT_CTRL_IOAC_1_HAS_VLAN BIT(19) +#define L1F_HRTBT_EXT_CTRL_IOAC_1_IS_8023 BIT(18) +#define L1F_HRTBT_EXT_CTRL_IOAC_1_IS_IPV6 BIT(17) +#define L1F_HRTBT_EXT_CTRL_IOAC_2_KEEPALIVE_EN BIT(16) +#define L1F_HRTBT_EXT_CTRL_IOAC_2_HAS_VLAN BIT(15) +#define L1F_HRTBT_EXT_CTRL_IOAC_2_IS_8023 BIT(14) +#define L1F_HRTBT_EXT_CTRL_IOAC_2_IS_IPV6 BIT(13) +#define ALX_HRTBT_EXT_CTRL_NS_EN BIT(12) +#define ALX_HRTBT_EXT_CTRL_FRAG_LEN_MASK 0xFF +#define ALX_HRTBT_EXT_CTRL_FRAG_LEN_SHIFT 4 +#define ALX_HRTBT_EXT_CTRL_IS_8023 BIT(3) +#define ALX_HRTBT_EXT_CTRL_IS_IPV6 BIT(2) +#define ALX_HRTBT_EXT_CTRL_WAKEUP_EN BIT(1) +#define ALX_HRTBT_EXT_CTRL_ARP_EN BIT(0) + +#define ALX_HRTBT_REM_IPV4_ADDR 0x1AD4 +#define ALX_HRTBT_HOST_IPV4_ADDR 0x1478 +#define ALX_HRTBT_REM_IPV6_ADDR3 0x1AD8 +#define ALX_HRTBT_REM_IPV6_ADDR2 0x1ADC +#define ALX_HRTBT_REM_IPV6_ADDR1 0x1AE0 +#define ALX_HRTBT_REM_IPV6_ADDR0 0x1AE4 + +/* 1B8C ~ 1B94 for C0+ */ +#define ALX_SWOI_ACER_CTRL 0x1B8C +#define ALX_SWOI_ORIG_ACK_NAK_EN BIT(20) +#define ALX_SWOI_ORIG_ACK_NAK_PKT_LEN_MASK 0XFF +#define ALX_SWOI_ORIG_ACK_NAK_PKT_LEN_SHIFT 12 +#define ALX_SWOI_ORIG_ACK_ADDR_MASK 0XFFF +#define ALX_SWOI_ORIG_ACK_ADDR_SHIFT 0 + +#define ALX_SWOI_IOAC_CTRL_2 0x1B90 +#define ALX_SWOI_IOAC_CTRL_2_SWOI_1_FRAG_LEN_MASK 0xFF +#define ALX_SWOI_IOAC_CTRL_2_SWOI_1_FRAG_LEN_SHIFT 24 +#define ALX_SWOI_IOAC_CTRL_2_SWOI_1_PKT_LEN_MASK 0xFFF +#define ALX_SWOI_IOAC_CTRL_2_SWOI_1_PKT_LEN_SHIFT 12 +#define ALX_SWOI_IOAC_CTRL_2_SWOI_1_HDR_ADDR_MASK 0xFFF +#define ALX_SWOI_IOAC_CTRL_2_SWOI_1_HDR_ADDR_SHIFT 0 + +#define ALX_SWOI_IOAC_CTRL_3 0x1B94 +#define ALX_SWOI_IOAC_CTRL_3_SWOI_2_FRAG_LEN_MASK 0xFF +#define ALX_SWOI_IOAC_CTRL_3_SWOI_2_FRAG_LEN_SHIFT 24 +#define ALX_SWOI_IOAC_CTRL_3_SWOI_2_PKT_LEN_MASK 0xFFF +#define ALX_SWOI_IOAC_CTRL_3_SWOI_2_PKT_LEN_SHIFT 12 +#define ALX_SWOI_IOAC_CTRL_3_SWOI_2_HDR_ADDR_MASK 0xFFF +#define ALX_SWOI_IOAC_CTRL_3_SWOI_2_HDR_ADDR_SHIFT 0 + +/* for B0 */ +#define ALX_IDLE_DECISN_TIMER 0x1474 +/* 1ms */ +#define ALX_IDLE_DECISN_TIMER_DEF 0x400 + +#define ALX_MAC_CTRL 0x1480 +#define ALX_MAC_CTRL_FAST_PAUSE BIT(31) +#define ALX_MAC_CTRL_WOLSPED_SWEN BIT(30) +/* bit29: 1:legacy(hi5b), 0:marvl(lo5b)*/ +#define ALX_MAC_CTRL_MHASH_ALG_HI5B BIT(29) +#define ALX_MAC_CTRL_BRD_EN BIT(26) +#define ALX_MAC_CTRL_MULTIALL_EN BIT(25) +#define ALX_MAC_CTRL_SPEED_MASK 0x3 +#define ALX_MAC_CTRL_SPEED_SHIFT 20 +#define ALX_MAC_CTRL_SPEED_10_100 1 +#define ALX_MAC_CTRL_SPEED_1000 2 +#define ALX_MAC_CTRL_PROMISC_EN BIT(15) +#define ALX_MAC_CTRL_VLANSTRIP BIT(14) +#define ALX_MAC_CTRL_PRMBLEN_MASK 0xF +#define ALX_MAC_CTRL_PRMBLEN_SHIFT 10 +#define ALX_MAC_CTRL_PCRCE BIT(7) +#define ALX_MAC_CTRL_CRCE BIT(6) +#define ALX_MAC_CTRL_FULLD BIT(5) +#define ALX_MAC_CTRL_RXFC_EN BIT(3) +#define ALX_MAC_CTRL_TXFC_EN BIT(2) +#define ALX_MAC_CTRL_RX_EN BIT(1) +#define ALX_MAC_CTRL_TX_EN BIT(0) + +#define ALX_STAD0 0x1488 +#define ALX_STAD1 0x148C + +#define ALX_HASH_TBL0 0x1490 +#define ALX_HASH_TBL1 0x1494 + +#define ALX_MTU 0x149C +#define ALX_MTU_JUMBO_TH 1514 +#define ALX_MTU_STD_ALGN 1536 + +#define ALX_SRAM5 0x1524 +#define ALX_SRAM_RXF_LEN_MASK 0xFFF +#define ALX_SRAM_RXF_LEN_SHIFT 0 +#define ALX_SRAM_RXF_LEN_8K (8*1024) + +#define ALX_SRAM9 0x1534 +#define ALX_SRAM_LOAD_PTR BIT(0) + +#define ALX_RX_BASE_ADDR_HI 0x1540 + +#define ALX_TX_BASE_ADDR_HI 0x1544 + +#define ALX_RFD_ADDR_LO 0x1550 +#define ALX_RFD_RING_SZ 0x1560 +#define ALX_RFD_BUF_SZ 0x1564 + +#define ALX_RRD_ADDR_LO 0x1568 +#define ALX_RRD_RING_SZ 0x1578 + +/* pri3: highest, pri0: lowest */ +#define ALX_TPD_PRI3_ADDR_LO 0x14E4 +#define ALX_TPD_PRI2_ADDR_LO 0x14E0 +#define ALX_TPD_PRI1_ADDR_LO 0x157C +#define ALX_TPD_PRI0_ADDR_LO 0x1580 + +/* producer index is 16bit */ +#define ALX_TPD_PRI3_PIDX 0x1618 +#define ALX_TPD_PRI2_PIDX 0x161A +#define ALX_TPD_PRI1_PIDX 0x15F0 +#define ALX_TPD_PRI0_PIDX 0x15F2 + +/* consumer index is 16bit */ +#define ALX_TPD_PRI3_CIDX 0x161C +#define ALX_TPD_PRI2_CIDX 0x161E +#define ALX_TPD_PRI1_CIDX 0x15F4 +#define ALX_TPD_PRI0_CIDX 0x15F6 + +#define ALX_TPD_RING_SZ 0x1584 + +#define ALX_TXQ0 0x1590 +#define ALX_TXQ0_TXF_BURST_PREF_MASK 0xFFFF +#define ALX_TXQ0_TXF_BURST_PREF_SHIFT 16 +#define ALX_TXQ_TXF_BURST_PREF_DEF 0x200 +#define ALX_TXQ0_LSO_8023_EN BIT(7) +#define ALX_TXQ0_MODE_ENHANCE BIT(6) +#define ALX_TXQ0_EN BIT(5) +#define ALX_TXQ0_SUPT_IPOPT BIT(4) +#define ALX_TXQ0_TPD_BURSTPREF_MASK 0xF +#define ALX_TXQ0_TPD_BURSTPREF_SHIFT 0 +#define ALX_TXQ_TPD_BURSTPREF_DEF 5 + +#define ALX_TXQ1 0x1594 +/* bit11: drop large packet, len > (rfd buf) */ +#define ALX_TXQ1_ERRLGPKT_DROP_EN BIT(11) +#define ALX_TXQ1_JUMBO_TSO_TH (7*1024) + +#define ALX_RXQ0 0x15A0 +#define ALX_RXQ0_EN BIT(31) +#define ALX_RXQ0_RSS_HASH_EN BIT(29) +#define ALX_RXQ0_RSS_MODE_MASK 0x3 +#define ALX_RXQ0_RSS_MODE_SHIFT 26 +#define ALX_RXQ0_RSS_MODE_DIS 0 +#define ALX_RXQ0_RSS_MODE_MQMI 3 +#define ALX_RXQ0_NUM_RFD_PREF_MASK 0x3F +#define ALX_RXQ0_NUM_RFD_PREF_SHIFT 20 +#define ALX_RXQ0_NUM_RFD_PREF_DEF 8 +#define ALX_RXQ0_IDT_TBL_SIZE_MASK 0x1FF +#define ALX_RXQ0_IDT_TBL_SIZE_SHIFT 8 +#define ALX_RXQ0_IDT_TBL_SIZE_DEF 0x100 +#define ALX_RXQ0_IDT_TBL_SIZE_NORMAL 128 +#define ALX_RXQ0_IPV6_PARSE_EN BIT(7) +#define ALX_RXQ0_RSS_HSTYP_MASK 0xF +#define ALX_RXQ0_RSS_HSTYP_SHIFT 2 +#define ALX_RXQ0_RSS_HSTYP_IPV6_TCP_EN BIT(5) +#define ALX_RXQ0_RSS_HSTYP_IPV6_EN BIT(4) +#define ALX_RXQ0_RSS_HSTYP_IPV4_TCP_EN BIT(3) +#define ALX_RXQ0_RSS_HSTYP_IPV4_EN BIT(2) +#define ALX_RXQ0_RSS_HSTYP_ALL (ALX_RXQ0_RSS_HSTYP_IPV6_TCP_EN | \ + ALX_RXQ0_RSS_HSTYP_IPV4_TCP_EN | \ + ALX_RXQ0_RSS_HSTYP_IPV6_EN | \ + ALX_RXQ0_RSS_HSTYP_IPV4_EN) +#define ALX_RXQ0_ASPM_THRESH_MASK 0x3 +#define ALX_RXQ0_ASPM_THRESH_SHIFT 0 +#define ALX_RXQ0_ASPM_THRESH_100M 3 + +#define ALX_RXQ2 0x15A8 +#define ALX_RXQ2_RXF_XOFF_THRESH_MASK 0xFFF +#define ALX_RXQ2_RXF_XOFF_THRESH_SHIFT 16 +#define ALX_RXQ2_RXF_XON_THRESH_MASK 0xFFF +#define ALX_RXQ2_RXF_XON_THRESH_SHIFT 0 +/* Size = tx-packet(1522) + IPG(12) + SOF(8) + 64(Pause) + IPG(12) + SOF(8) + + * rx-packet(1522) + delay-of-link(64) + * = 3212. + */ +#define ALX_RXQ2_RXF_FLOW_CTRL_RSVD 3212 + +#define ALX_DMA 0x15C0 +#define ALX_DMA_RCHNL_SEL_MASK 0x3 +#define ALX_DMA_RCHNL_SEL_SHIFT 26 +#define ALX_DMA_WDLY_CNT_MASK 0xF +#define ALX_DMA_WDLY_CNT_SHIFT 16 +#define ALX_DMA_WDLY_CNT_DEF 4 +#define ALX_DMA_RDLY_CNT_MASK 0x1F +#define ALX_DMA_RDLY_CNT_SHIFT 11 +#define ALX_DMA_RDLY_CNT_DEF 15 +/* bit10: 0:tpd with pri, 1: data */ +#define ALX_DMA_RREQ_PRI_DATA BIT(10) +#define ALX_DMA_RREQ_BLEN_MASK 0x7 +#define ALX_DMA_RREQ_BLEN_SHIFT 4 +#define ALX_DMA_RORDER_MODE_MASK 0x7 +#define ALX_DMA_RORDER_MODE_SHIFT 0 +#define ALX_DMA_RORDER_MODE_OUT 4 + +#define ALX_WOL0 0x14A0 +#define ALX_WOL0_PME_LINK BIT(5) +#define ALX_WOL0_LINK_EN BIT(4) +#define ALX_WOL0_PME_MAGIC_EN BIT(3) +#define ALX_WOL0_MAGIC_EN BIT(2) + +#define ALX_RFD_PIDX 0x15E0 + +#define ALX_RFD_CIDX 0x15F8 + +/* MIB */ +#define ALX_MIB_BASE 0x1700 +#define ALX_MIB_RX_OK (ALX_MIB_BASE + 0) +#define ALX_MIB_RX_ERRADDR (ALX_MIB_BASE + 92) +#define ALX_MIB_TX_OK (ALX_MIB_BASE + 96) +#define ALX_MIB_TX_MCCNT (ALX_MIB_BASE + 192) + +#define ALX_RX_STATS_BIN ALX_MIB_RX_OK +#define ALX_RX_STATS_END ALX_MIB_RX_ERRADDR +#define ALX_TX_STATS_BIN ALX_MIB_TX_OK +#define ALX_TX_STATS_END ALX_MIB_TX_MCCNT + +#define ALX_ISR 0x1600 +#define ALX_ISR_DIS BIT(31) +#define ALX_ISR_RX_Q7 BIT(30) +#define ALX_ISR_RX_Q6 BIT(29) +#define ALX_ISR_RX_Q5 BIT(28) +#define ALX_ISR_RX_Q4 BIT(27) +#define ALX_ISR_PCIE_LNKDOWN BIT(26) +#define ALX_ISR_RX_Q3 BIT(19) +#define ALX_ISR_RX_Q2 BIT(18) +#define ALX_ISR_RX_Q1 BIT(17) +#define ALX_ISR_RX_Q0 BIT(16) +#define ALX_ISR_TX_Q0 BIT(15) +#define ALX_ISR_PHY BIT(12) +#define ALX_ISR_DMAW BIT(10) +#define ALX_ISR_DMAR BIT(9) +#define ALX_ISR_TXF_UR BIT(8) +#define ALX_ISR_TX_Q3 BIT(7) +#define ALX_ISR_TX_Q2 BIT(6) +#define ALX_ISR_TX_Q1 BIT(5) +#define ALX_ISR_RFD_UR BIT(4) +#define ALX_ISR_RXF_OV BIT(3) +#define ALX_ISR_MANU BIT(2) +#define ALX_ISR_TIMER BIT(1) +#define ALX_ISR_SMB BIT(0) + +#define ALX_IMR 0x1604 + +/* re-send assert msg if SW no response */ +#define ALX_INT_RETRIG 0x1608 +/* 40ms */ +#define ALX_INT_RETRIG_TO 20000 + +#define ALX_SMB_TIMER 0x15C4 + +#define ALX_TINT_TPD_THRSHLD 0x15C8 + +#define ALX_TINT_TIMER 0x15CC + +#define ALX_CLK_GATE 0x1814 +#define ALX_CLK_GATE_RXMAC BIT(5) +#define ALX_CLK_GATE_TXMAC BIT(4) +#define ALX_CLK_GATE_RXQ BIT(3) +#define ALX_CLK_GATE_TXQ BIT(2) +#define ALX_CLK_GATE_DMAR BIT(1) +#define ALX_CLK_GATE_DMAW BIT(0) +#define ALX_CLK_GATE_ALL (ALX_CLK_GATE_RXMAC | \ + ALX_CLK_GATE_TXMAC | \ + ALX_CLK_GATE_RXQ | \ + ALX_CLK_GATE_TXQ | \ + ALX_CLK_GATE_DMAR | \ + ALX_CLK_GATE_DMAW) + +/* interop between drivers */ +#define ALX_DRV 0x1804 +#define ALX_DRV_PHY_AUTO BIT(28) +#define ALX_DRV_PHY_1000 BIT(27) +#define ALX_DRV_PHY_100 BIT(26) +#define ALX_DRV_PHY_10 BIT(25) +#define ALX_DRV_PHY_DUPLEX BIT(24) +/* bit23: adv Pause */ +#define ALX_DRV_PHY_PAUSE BIT(23) +/* bit22: adv Asym Pause */ +#define ALX_DRV_PHY_MASK 0xFF +#define ALX_DRV_PHY_SHIFT 21 +#define ALX_DRV_PHY_UNKNOWN 0 + +/* flag of phy inited */ +#define ALX_PHY_INITED 0x003F + +/* reg 1830 ~ 186C for C0+, 16 bit map patterns and wake packet detection */ +#define ALX_WOL_CTRL2 0x1830 +#define ALX_WOL_CTRL2_DATA_STORE BIT(3) +#define ALX_WOL_CTRL2_PTRN_EVT BIT(2) +#define ALX_WOL_CTRL2_PME_PTRN_EN BIT(1) +#define ALX_WOL_CTRL2_PTRN_EN BIT(0) + +#define ALX_WOL_CTRL3 0x1834 +#define ALX_WOL_CTRL3_PTRN_ADDR_MASK 0xFFFFF +#define ALX_WOL_CTRL3_PTRN_ADDR_SHIFT 0 + +#define ALX_WOL_CTRL4 0x1838 +#define ALX_WOL_CTRL4_PT15_MATCH BIT(31) +#define ALX_WOL_CTRL4_PT14_MATCH BIT(30) +#define ALX_WOL_CTRL4_PT13_MATCH BIT(29) +#define ALX_WOL_CTRL4_PT12_MATCH BIT(28) +#define ALX_WOL_CTRL4_PT11_MATCH BIT(27) +#define ALX_WOL_CTRL4_PT10_MATCH BIT(26) +#define ALX_WOL_CTRL4_PT9_MATCH BIT(25) +#define ALX_WOL_CTRL4_PT8_MATCH BIT(24) +#define ALX_WOL_CTRL4_PT7_MATCH BIT(23) +#define ALX_WOL_CTRL4_PT6_MATCH BIT(22) +#define ALX_WOL_CTRL4_PT5_MATCH BIT(21) +#define ALX_WOL_CTRL4_PT4_MATCH BIT(20) +#define ALX_WOL_CTRL4_PT3_MATCH BIT(19) +#define ALX_WOL_CTRL4_PT2_MATCH BIT(18) +#define ALX_WOL_CTRL4_PT1_MATCH BIT(17) +#define ALX_WOL_CTRL4_PT0_MATCH BIT(16) +#define ALX_WOL_CTRL4_PT15_EN BIT(15) +#define ALX_WOL_CTRL4_PT14_EN BIT(14) +#define ALX_WOL_CTRL4_PT13_EN BIT(13) +#define ALX_WOL_CTRL4_PT12_EN BIT(12) +#define ALX_WOL_CTRL4_PT11_EN BIT(11) +#define ALX_WOL_CTRL4_PT10_EN BIT(10) +#define ALX_WOL_CTRL4_PT9_EN BIT(9) +#define ALX_WOL_CTRL4_PT8_EN BIT(8) +#define ALX_WOL_CTRL4_PT7_EN BIT(7) +#define ALX_WOL_CTRL4_PT6_EN BIT(6) +#define ALX_WOL_CTRL4_PT5_EN BIT(5) +#define ALX_WOL_CTRL4_PT4_EN BIT(4) +#define ALX_WOL_CTRL4_PT3_EN BIT(3) +#define ALX_WOL_CTRL4_PT2_EN BIT(2) +#define ALX_WOL_CTRL4_PT1_EN BIT(1) +#define ALX_WOL_CTRL4_PT0_EN BIT(0) + +#define ALX_WOL_CTRL5 0x183C +#define ALX_WOL_CTRL5_PT3_LEN_MASK 0xFF +#define ALX_WOL_CTRL5_PT3_LEN_SHIFT 24 +#define ALX_WOL_CTRL5_PT2_LEN_MASK 0xFF +#define ALX_WOL_CTRL5_PT2_LEN_SHIFT 16 +#define ALX_WOL_CTRL5_PT1_LEN_MASK 0xFF +#define ALX_WOL_CTRL5_PT1_LEN_SHIFT 8 +#define ALX_WOL_CTRL5_PT0_LEN_MASK 0xFF +#define ALX_WOL_CTRL5_PT0_LEN_SHIFT 0 + +#define ALX_WOL_CTRL6 0x1840 +#define ALX_WOL_CTRL5_PT7_LEN_MASK 0xFF +#define ALX_WOL_CTRL5_PT7_LEN_SHIFT 24 +#define ALX_WOL_CTRL5_PT6_LEN_MASK 0xFF +#define ALX_WOL_CTRL5_PT6_LEN_SHIFT 16 +#define ALX_WOL_CTRL5_PT5_LEN_MASK 0xFF +#define ALX_WOL_CTRL5_PT5_LEN_SHIFT 8 +#define ALX_WOL_CTRL5_PT4_LEN_MASK 0xFF +#define ALX_WOL_CTRL5_PT4_LEN_SHIFT 0 + +#define ALX_WOL_CTRL7 0x1844 +#define ALX_WOL_CTRL5_PT11_LEN_MASK 0xFF +#define ALX_WOL_CTRL5_PT11_LEN_SHIFT 24 +#define ALX_WOL_CTRL5_PT10_LEN_MASK 0xFF +#define ALX_WOL_CTRL5_PT10_LEN_SHIFT 16 +#define ALX_WOL_CTRL5_PT9_LEN_MASK 0xFF +#define ALX_WOL_CTRL5_PT9_LEN_SHIFT 8 +#define ALX_WOL_CTRL5_PT8_LEN_MASK 0xFF +#define ALX_WOL_CTRL5_PT8_LEN_SHIFT 0 + +#define ALX_WOL_CTRL8 0x1848 +#define ALX_WOL_CTRL5_PT15_LEN_MASK 0xFF +#define ALX_WOL_CTRL5_PT15_LEN_SHIFT 24 +#define ALX_WOL_CTRL5_PT14_LEN_MASK 0xFF +#define ALX_WOL_CTRL5_PT14_LEN_SHIFT 16 +#define ALX_WOL_CTRL5_PT13_LEN_MASK 0xFF +#define ALX_WOL_CTRL5_PT13_LEN_SHIFT 8 +#define ALX_WOL_CTRL5_PT12_LEN_MASK 0xFF +#define ALX_WOL_CTRL5_PT12_LEN_SHIFT 0 + +#define ALX_ACER_FIXED_PTN0 0x1850 +#define ALX_ACER_FIXED_PTN0_MASK 0xFFFFFFFF +#define ALX_ACER_FIXED_PTN0_SHIFT 0 + +#define ALX_ACER_FIXED_PTN1 0x1854 +#define ALX_ACER_FIXED_PTN1_MASK 0xFFFF +#define ALX_ACER_FIXED_PTN1_SHIFT 0 + +#define ALX_ACER_RANDOM_NUM0 0x1858 +#define ALX_ACER_RANDOM_NUM0_MASK 0xFFFFFFFF +#define ALX_ACER_RANDOM_NUM0_SHIFT 0 + +#define ALX_ACER_RANDOM_NUM1 0x185C +#define ALX_ACER_RANDOM_NUM1_MASK 0xFFFFFFFF +#define ALX_ACER_RANDOM_NUM1_SHIFT 0 + +#define ALX_ACER_RANDOM_NUM2 0x1860 +#define ALX_ACER_RANDOM_NUM2_MASK 0xFFFFFFFF +#define ALX_ACER_RANDOM_NUM2_SHIFT 0 + +#define ALX_ACER_RANDOM_NUM3 0x1864 +#define ALX_ACER_RANDOM_NUM3_MASK 0xFFFFFFFF +#define ALX_ACER_RANDOM_NUM3_SHIFT 0 + +#define ALX_ACER_MAGIC 0x1868 +#define ALX_ACER_MAGIC_EN BIT(31) +#define ALX_ACER_MAGIC_PME_EN BIT(30) +#define ALX_ACER_MAGIC_MATCH BIT(29) +#define ALX_ACER_MAGIC_FF_CHECK BIT(10) +#define ALX_ACER_MAGIC_RAN_LEN_MASK 0x1F +#define ALX_ACER_MAGIC_RAN_LEN_SHIFT 5 +#define ALX_ACER_MAGIC_FIX_LEN_MASK 0x1F +#define ALX_ACER_MAGIC_FIX_LEN_SHIFT 0 + +#define ALX_ACER_TIMER 0x186C +#define ALX_ACER_TIMER_EN BIT(31) +#define ALX_ACER_TIMER_PME_EN BIT(30) +#define ALX_ACER_TIMER_MATCH BIT(29) +#define ALX_ACER_TIMER_THRES_MASK 0x1FFFF +#define ALX_ACER_TIMER_THRES_SHIFT 0 +#define ALX_ACER_TIMER_THRES_DEF 1 + +/* RSS definitions */ +#define ALX_RSS_KEY0 0x14B0 +#define ALX_RSS_KEY1 0x14B4 +#define ALX_RSS_KEY2 0x14B8 +#define ALX_RSS_KEY3 0x14BC +#define ALX_RSS_KEY4 0x14C0 +#define ALX_RSS_KEY5 0x14C4 +#define ALX_RSS_KEY6 0x14C8 +#define ALX_RSS_KEY7 0x14CC +#define ALX_RSS_KEY8 0x14D0 +#define ALX_RSS_KEY9 0x14D4 + +#define ALX_RSS_IDT_TBL0 0x1B00 + +#define ALX_MSI_MAP_TBL1 0x15D0 +#define ALX_MSI_MAP_TBL1_TXQ1_SHIFT 20 +#define ALX_MSI_MAP_TBL1_TXQ0_SHIFT 16 +#define ALX_MSI_MAP_TBL1_RXQ3_SHIFT 12 +#define ALX_MSI_MAP_TBL1_RXQ2_SHIFT 8 +#define ALX_MSI_MAP_TBL1_RXQ1_SHIFT 4 +#define ALX_MSI_MAP_TBL1_RXQ0_SHIFT 0 + +#define ALX_MSI_MAP_TBL2 0x15D8 +#define ALX_MSI_MAP_TBL2_TXQ3_SHIFT 20 +#define ALX_MSI_MAP_TBL2_TXQ2_SHIFT 16 +#define ALX_MSI_MAP_TBL2_RXQ7_SHIFT 12 +#define ALX_MSI_MAP_TBL2_RXQ6_SHIFT 8 +#define ALX_MSI_MAP_TBL2_RXQ5_SHIFT 4 +#define ALX_MSI_MAP_TBL2_RXQ4_SHIFT 0 + +#define ALX_MSI_ID_MAP 0x15D4 + +#define ALX_MSI_RETRANS_TIMER 0x1920 +/* bit16: 1:line,0:standard */ +#define ALX_MSI_MASK_SEL_LINE BIT(16) +#define ALX_MSI_RETRANS_TM_MASK 0xFFFF +#define ALX_MSI_RETRANS_TM_SHIFT 0 + +/* CR DMA ctrl */ + +/* TX QoS */ +#define ALX_WRR 0x1938 +#define ALX_WRR_PRI_MASK 0x3 +#define ALX_WRR_PRI_SHIFT 29 +#define ALX_WRR_PRI_RESTRICT_NONE 3 +#define ALX_WRR_PRI3_MASK 0x1F +#define ALX_WRR_PRI3_SHIFT 24 +#define ALX_WRR_PRI2_MASK 0x1F +#define ALX_WRR_PRI2_SHIFT 16 +#define ALX_WRR_PRI1_MASK 0x1F +#define ALX_WRR_PRI1_SHIFT 8 +#define ALX_WRR_PRI0_MASK 0x1F +#define ALX_WRR_PRI0_SHIFT 0 + +#define ALX_HQTPD 0x193C +#define ALX_HQTPD_BURST_EN BIT(31) +#define ALX_HQTPD_Q3_NUMPREF_MASK 0xF +#define ALX_HQTPD_Q3_NUMPREF_SHIFT 8 +#define ALX_HQTPD_Q2_NUMPREF_MASK 0xF +#define ALX_HQTPD_Q2_NUMPREF_SHIFT 4 +#define ALX_HQTPD_Q1_NUMPREF_MASK 0xF +#define ALX_HQTPD_Q1_NUMPREF_SHIFT 0 + +#define ALX_MISC 0x19C0 +#define ALX_MISC_PSW_OCP_MASK 0x7 +#define ALX_MISC_PSW_OCP_SHIFT 21 +#define ALX_MISC_PSW_OCP_DEF 0x7 +#define ALX_MISC_ISO_EN BIT(12) +#define ALX_MISC_INTNLOSC_OPEN BIT(3) + +#define ALX_MSIC2 0x19C8 +#define ALX_MSIC2_CALB_START BIT(0) + +#define ALX_MISC3 0x19CC +/* bit1: 1:Software control 25M */ +#define ALX_MISC3_25M_BY_SW BIT(1) +/* bit0: 25M switch to intnl OSC */ +#define ALX_MISC3_25M_NOTO_INTNL BIT(0) + +/* MSIX tbl in memory space */ +#define ALX_MSIX_ENTRY_BASE 0x2000 + +/********************* PHY regs definition ***************************/ + +/* PHY Specific Status Register */ +#define ALX_MII_GIGA_PSSR 0x11 +#define ALX_GIGA_PSSR_SPD_DPLX_RESOLVED 0x0800 +#define ALX_GIGA_PSSR_DPLX 0x2000 +#define ALX_GIGA_PSSR_SPEED 0xC000 +#define ALX_GIGA_PSSR_10MBS 0x0000 +#define ALX_GIGA_PSSR_100MBS 0x4000 +#define ALX_GIGA_PSSR_1000MBS 0x8000 + +/* PHY Interrupt Enable Register */ +#define ALX_MII_IER 0x12 +#define ALX_IER_LINK_UP 0x0400 +#define ALX_IER_LINK_DOWN 0x0800 + +/* PHY Interrupt Status Register */ +#define ALX_MII_ISR 0x13 + +#define ALX_MII_DBG_ADDR 0x1D +#define ALX_MII_DBG_DATA 0x1E + +/***************************** debug port *************************************/ + +#define ALX_MIIDBG_ANACTRL 0x00 +#define ALX_ANACTRL_DEF 0x02EF + +#define ALX_MIIDBG_SYSMODCTRL 0x04 +/* en half bias */ +#define ALX_SYSMODCTRL_IECHOADJ_DEF 0xBB8B + +#define ALX_MIIDBG_SRDSYSMOD 0x05 +#define ALX_SRDSYSMOD_DEEMP_EN 0x0040 +#define ALX_SRDSYSMOD_DEF 0x2C46 + +#define ALX_MIIDBG_HIBNEG 0x0B +#define ALX_HIBNEG_PSHIB_EN 0x8000 +#define ALX_HIBNEG_HIB_PSE 0x1000 +#define ALX_HIBNEG_DEF 0xBC40 +#define ALX_HIBNEG_NOHIB (ALX_HIBNEG_DEF & \ + ~(ALX_HIBNEG_PSHIB_EN | ALX_HIBNEG_HIB_PSE)) + +#define ALX_MIIDBG_TST10BTCFG 0x12 +#define ALX_TST10BTCFG_DEF 0x4C04 + +#define ALX_MIIDBG_AZ_ANADECT 0x15 +#define ALX_AZ_ANADECT_DEF 0x3220 +#define ALX_AZ_ANADECT_LONG 0x3210 + +#define ALX_MIIDBG_MSE16DB 0x18 +#define ALX_MSE16DB_UP 0x05EA +#define ALX_MSE16DB_DOWN 0x02EA + +#define ALX_MIIDBG_MSE20DB 0x1C +#define ALX_MSE20DB_TH_MASK 0x7F +#define ALX_MSE20DB_TH_SHIFT 2 +#define ALX_MSE20DB_TH_DEF 0x2E +#define ALX_MSE20DB_TH_HI 0x54 + +#define ALX_MIIDBG_AGC 0x23 +#define ALX_AGC_2_VGA_MASK 0x3FU +#define ALX_AGC_2_VGA_SHIFT 8 +#define ALX_AGC_LONG1G_LIMT 40 +#define ALX_AGC_LONG100M_LIMT 44 + +#define ALX_MIIDBG_LEGCYPS 0x29 +#define ALX_LEGCYPS_EN 0x8000 +#define ALX_LEGCYPS_DEF 0x129D + +#define ALX_MIIDBG_TST100BTCFG 0x36 +#define ALX_TST100BTCFG_DEF 0xE12C + +#define ALX_MIIDBG_GREENCFG 0x3B +#define ALX_GREENCFG_DEF 0x7078 + +#define ALX_MIIDBG_GREENCFG2 0x3D +#define ALX_GREENCFG2_BP_GREEN 0x8000 +#define ALX_GREENCFG2_GATE_DFSE_EN 0x0080 + +/******* dev 3 *********/ +#define ALX_MIIEXT_PCS 3 + +#define ALX_MIIEXT_CLDCTRL3 0x8003 +#define ALX_CLDCTRL3_BP_CABLE1TH_DET_GT 0x8000 + +#define ALX_MIIEXT_CLDCTRL5 0x8005 +#define ALX_CLDCTRL5_BP_VD_HLFBIAS 0x4000 + +#define ALX_MIIEXT_CLDCTRL6 0x8006 +#define ALX_CLDCTRL6_CAB_LEN_MASK 0xFF +#define ALX_CLDCTRL6_CAB_LEN_SHIFT 0 +#define ALX_CLDCTRL6_CAB_LEN_SHORT1G 116 +#define ALX_CLDCTRL6_CAB_LEN_SHORT100M 152 + +#define ALX_MIIEXT_VDRVBIAS 0x8062 +#define ALX_VDRVBIAS_DEF 0x3 + +/********* dev 7 **********/ +#define ALX_MIIEXT_ANEG 7 + +#define ALX_MIIEXT_LOCAL_EEEADV 0x3C +#define ALX_LOCAL_EEEADV_1000BT 0x0004 +#define ALX_LOCAL_EEEADV_100BT 0x0002 + +#define ALX_MIIEXT_AFE 0x801A +#define ALX_AFE_10BT_100M_TH 0x0040 + +#define ALX_MIIEXT_S3DIG10 0x8023 +/* bit0: 1:bypass 10BT rx fifo, 0:original 10BT rx */ +#define ALX_MIIEXT_S3DIG10_SL 0x0001 +#define ALX_MIIEXT_S3DIG10_DEF 0 + +#define ALX_MIIEXT_NLP78 0x8027 +#define ALX_MIIEXT_NLP78_120M_DEF 0x8A05 + +#endif diff --git a/drivers/net/ethernet/atheros/atl1c/Makefile b/drivers/net/ethernet/atheros/atl1c/Makefile new file mode 100644 index 0000000..ae09e39 --- /dev/null +++ b/drivers/net/ethernet/atheros/atl1c/Makefile @@ -0,0 +1,2 @@ +obj-$(CPTCFG_ATL1C) += atl1c.o +atl1c-objs := atl1c_main.o atl1c_hw.o atl1c_ethtool.o diff --git a/drivers/net/ethernet/atheros/atl1c/atl1c.h b/drivers/net/ethernet/atheros/atl1c/atl1c.h new file mode 100644 index 0000000..0f05565 --- /dev/null +++ b/drivers/net/ethernet/atheros/atl1c/atl1c.h @@ -0,0 +1,606 @@ +/* + * Copyright(c) 2008 - 2009 Atheros Corporation. All rights reserved. + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _ATL1C_H_ +#define _ATL1C_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atl1c_hw.h" + +/* Wake Up Filter Control */ +#define AT_WUFC_LNKC 0x00000001 /* Link Status Change Wakeup Enable */ +#define AT_WUFC_MAG 0x00000002 /* Magic Packet Wakeup Enable */ +#define AT_WUFC_EX 0x00000004 /* Directed Exact Wakeup Enable */ +#define AT_WUFC_MC 0x00000008 /* Multicast Wakeup Enable */ +#define AT_WUFC_BC 0x00000010 /* Broadcast Wakeup Enable */ + +#define AT_VLAN_TO_TAG(_vlan, _tag) \ + _tag = ((((_vlan) >> 8) & 0xFF) |\ + (((_vlan) & 0xFF) << 8)) + +#define AT_TAG_TO_VLAN(_tag, _vlan) \ + _vlan = ((((_tag) >> 8) & 0xFF) |\ + (((_tag) & 0xFF) << 8)) + +#define SPEED_0 0xffff +#define HALF_DUPLEX 1 +#define FULL_DUPLEX 2 + +#define AT_RX_BUF_SIZE (ETH_FRAME_LEN + VLAN_HLEN + ETH_FCS_LEN) +#define MAX_JUMBO_FRAME_SIZE (6*1024) + +#define AT_MAX_RECEIVE_QUEUE 4 +#define AT_DEF_RECEIVE_QUEUE 1 +#define AT_MAX_TRANSMIT_QUEUE 2 + +#define AT_DMA_HI_ADDR_MASK 0xffffffff00000000ULL +#define AT_DMA_LO_ADDR_MASK 0x00000000ffffffffULL + +#define AT_TX_WATCHDOG (5 * HZ) +#define AT_MAX_INT_WORK 5 +#define AT_TWSI_EEPROM_TIMEOUT 100 +#define AT_HW_MAX_IDLE_DELAY 10 +#define AT_SUSPEND_LINK_TIMEOUT 100 + +#define AT_ASPM_L0S_TIMER 6 +#define AT_ASPM_L1_TIMER 12 +#define AT_LCKDET_TIMER 12 + +#define ATL1C_PCIE_L0S_L1_DISABLE 0x01 +#define ATL1C_PCIE_PHY_RESET 0x02 + +#define ATL1C_ASPM_L0s_ENABLE 0x0001 +#define ATL1C_ASPM_L1_ENABLE 0x0002 + +#define AT_REGS_LEN (74 * sizeof(u32)) +#define AT_EEPROM_LEN 512 + +#define ATL1C_GET_DESC(R, i, type) (&(((type *)((R)->desc))[i])) +#define ATL1C_RFD_DESC(R, i) ATL1C_GET_DESC(R, i, struct atl1c_rx_free_desc) +#define ATL1C_TPD_DESC(R, i) ATL1C_GET_DESC(R, i, struct atl1c_tpd_desc) +#define ATL1C_RRD_DESC(R, i) ATL1C_GET_DESC(R, i, struct atl1c_recv_ret_status) + +/* tpd word 1 bit 0:7 General Checksum task offload */ +#define TPD_L4HDR_OFFSET_MASK 0x00FF +#define TPD_L4HDR_OFFSET_SHIFT 0 + +/* tpd word 1 bit 0:7 Large Send task offload (IPv4/IPV6) */ +#define TPD_TCPHDR_OFFSET_MASK 0x00FF +#define TPD_TCPHDR_OFFSET_SHIFT 0 + +/* tpd word 1 bit 0:7 Custom Checksum task offload */ +#define TPD_PLOADOFFSET_MASK 0x00FF +#define TPD_PLOADOFFSET_SHIFT 0 + +/* tpd word 1 bit 8:17 */ +#define TPD_CCSUM_EN_MASK 0x0001 +#define TPD_CCSUM_EN_SHIFT 8 +#define TPD_IP_CSUM_MASK 0x0001 +#define TPD_IP_CSUM_SHIFT 9 +#define TPD_TCP_CSUM_MASK 0x0001 +#define TPD_TCP_CSUM_SHIFT 10 +#define TPD_UDP_CSUM_MASK 0x0001 +#define TPD_UDP_CSUM_SHIFT 11 +#define TPD_LSO_EN_MASK 0x0001 /* TCP Large Send Offload */ +#define TPD_LSO_EN_SHIFT 12 +#define TPD_LSO_VER_MASK 0x0001 +#define TPD_LSO_VER_SHIFT 13 /* 0 : ipv4; 1 : ipv4/ipv6 */ +#define TPD_CON_VTAG_MASK 0x0001 +#define TPD_CON_VTAG_SHIFT 14 +#define TPD_INS_VTAG_MASK 0x0001 +#define TPD_INS_VTAG_SHIFT 15 +#define TPD_IPV4_PACKET_MASK 0x0001 /* valid when LSO VER is 1 */ +#define TPD_IPV4_PACKET_SHIFT 16 +#define TPD_ETH_TYPE_MASK 0x0001 +#define TPD_ETH_TYPE_SHIFT 17 /* 0 : 802.3 frame; 1 : Ethernet */ + +/* tpd word 18:25 Custom Checksum task offload */ +#define TPD_CCSUM_OFFSET_MASK 0x00FF +#define TPD_CCSUM_OFFSET_SHIFT 18 +#define TPD_CCSUM_EPAD_MASK 0x0001 +#define TPD_CCSUM_EPAD_SHIFT 30 + +/* tpd word 18:30 Large Send task offload (IPv4/IPV6) */ +#define TPD_MSS_MASK 0x1FFF +#define TPD_MSS_SHIFT 18 + +#define TPD_EOP_MASK 0x0001 +#define TPD_EOP_SHIFT 31 + +struct atl1c_tpd_desc { + __le16 buffer_len; /* include 4-byte CRC */ + __le16 vlan_tag; + __le32 word1; + __le64 buffer_addr; +}; + +struct atl1c_tpd_ext_desc { + u32 reservd_0; + __le32 word1; + __le32 pkt_len; + u32 reservd_1; +}; +/* rrs word 0 bit 0:31 */ +#define RRS_RX_CSUM_MASK 0xFFFF +#define RRS_RX_CSUM_SHIFT 0 +#define RRS_RX_RFD_CNT_MASK 0x000F +#define RRS_RX_RFD_CNT_SHIFT 16 +#define RRS_RX_RFD_INDEX_MASK 0x0FFF +#define RRS_RX_RFD_INDEX_SHIFT 20 + +/* rrs flag bit 0:16 */ +#define RRS_HEAD_LEN_MASK 0x00FF +#define RRS_HEAD_LEN_SHIFT 0 +#define RRS_HDS_TYPE_MASK 0x0003 +#define RRS_HDS_TYPE_SHIFT 8 +#define RRS_CPU_NUM_MASK 0x0003 +#define RRS_CPU_NUM_SHIFT 10 +#define RRS_HASH_FLG_MASK 0x000F +#define RRS_HASH_FLG_SHIFT 12 + +#define RRS_HDS_TYPE_HEAD 1 +#define RRS_HDS_TYPE_DATA 2 + +#define RRS_IS_NO_HDS_TYPE(flag) \ + ((((flag) >> (RRS_HDS_TYPE_SHIFT)) & RRS_HDS_TYPE_MASK) == 0) + +#define RRS_IS_HDS_HEAD(flag) \ + ((((flag) >> (RRS_HDS_TYPE_SHIFT)) & RRS_HDS_TYPE_MASK) == \ + RRS_HDS_TYPE_HEAD) + +#define RRS_IS_HDS_DATA(flag) \ + ((((flag) >> (RRS_HDS_TYPE_SHIFT)) & RRS_HDS_TYPE_MASK) == \ + RRS_HDS_TYPE_DATA) + +/* rrs word 3 bit 0:31 */ +#define RRS_PKT_SIZE_MASK 0x3FFF +#define RRS_PKT_SIZE_SHIFT 0 +#define RRS_ERR_L4_CSUM_MASK 0x0001 +#define RRS_ERR_L4_CSUM_SHIFT 14 +#define RRS_ERR_IP_CSUM_MASK 0x0001 +#define RRS_ERR_IP_CSUM_SHIFT 15 +#define RRS_VLAN_INS_MASK 0x0001 +#define RRS_VLAN_INS_SHIFT 16 +#define RRS_PROT_ID_MASK 0x0007 +#define RRS_PROT_ID_SHIFT 17 +#define RRS_RX_ERR_SUM_MASK 0x0001 +#define RRS_RX_ERR_SUM_SHIFT 20 +#define RRS_RX_ERR_CRC_MASK 0x0001 +#define RRS_RX_ERR_CRC_SHIFT 21 +#define RRS_RX_ERR_FAE_MASK 0x0001 +#define RRS_RX_ERR_FAE_SHIFT 22 +#define RRS_RX_ERR_TRUNC_MASK 0x0001 +#define RRS_RX_ERR_TRUNC_SHIFT 23 +#define RRS_RX_ERR_RUNC_MASK 0x0001 +#define RRS_RX_ERR_RUNC_SHIFT 24 +#define RRS_RX_ERR_ICMP_MASK 0x0001 +#define RRS_RX_ERR_ICMP_SHIFT 25 +#define RRS_PACKET_BCAST_MASK 0x0001 +#define RRS_PACKET_BCAST_SHIFT 26 +#define RRS_PACKET_MCAST_MASK 0x0001 +#define RRS_PACKET_MCAST_SHIFT 27 +#define RRS_PACKET_TYPE_MASK 0x0001 +#define RRS_PACKET_TYPE_SHIFT 28 +#define RRS_FIFO_FULL_MASK 0x0001 +#define RRS_FIFO_FULL_SHIFT 29 +#define RRS_802_3_LEN_ERR_MASK 0x0001 +#define RRS_802_3_LEN_ERR_SHIFT 30 +#define RRS_RXD_UPDATED_MASK 0x0001 +#define RRS_RXD_UPDATED_SHIFT 31 + +#define RRS_ERR_L4_CSUM 0x00004000 +#define RRS_ERR_IP_CSUM 0x00008000 +#define RRS_VLAN_INS 0x00010000 +#define RRS_RX_ERR_SUM 0x00100000 +#define RRS_RX_ERR_CRC 0x00200000 +#define RRS_802_3_LEN_ERR 0x40000000 +#define RRS_RXD_UPDATED 0x80000000 + +#define RRS_PACKET_TYPE_802_3 1 +#define RRS_PACKET_TYPE_ETH 0 +#define RRS_PACKET_IS_ETH(word) \ + ((((word) >> RRS_PACKET_TYPE_SHIFT) & RRS_PACKET_TYPE_MASK) == \ + RRS_PACKET_TYPE_ETH) +#define RRS_RXD_IS_VALID(word) \ + ((((word) >> RRS_RXD_UPDATED_SHIFT) & RRS_RXD_UPDATED_MASK) == 1) + +#define RRS_PACKET_PROT_IS_IPV4_ONLY(word) \ + ((((word) >> RRS_PROT_ID_SHIFT) & RRS_PROT_ID_MASK) == 1) +#define RRS_PACKET_PROT_IS_IPV6_ONLY(word) \ + ((((word) >> RRS_PROT_ID_SHIFT) & RRS_PROT_ID_MASK) == 6) + +struct atl1c_recv_ret_status { + __le32 word0; + __le32 rss_hash; + __le16 vlan_tag; + __le16 flag; + __le32 word3; +}; + +/* RFD descriptor */ +struct atl1c_rx_free_desc { + __le64 buffer_addr; +}; + +/* DMA Order Settings */ +enum atl1c_dma_order { + atl1c_dma_ord_in = 1, + atl1c_dma_ord_enh = 2, + atl1c_dma_ord_out = 4 +}; + +enum atl1c_dma_rcb { + atl1c_rcb_64 = 0, + atl1c_rcb_128 = 1 +}; + +enum atl1c_mac_speed { + atl1c_mac_speed_0 = 0, + atl1c_mac_speed_10_100 = 1, + atl1c_mac_speed_1000 = 2 +}; + +enum atl1c_dma_req_block { + atl1c_dma_req_128 = 0, + atl1c_dma_req_256 = 1, + atl1c_dma_req_512 = 2, + atl1c_dma_req_1024 = 3, + atl1c_dma_req_2048 = 4, + atl1c_dma_req_4096 = 5 +}; + + +enum atl1c_nic_type { + athr_l1c = 0, + athr_l2c = 1, + athr_l2c_b, + athr_l2c_b2, + athr_l1d, + athr_l1d_2, +}; + +enum atl1c_trans_queue { + atl1c_trans_normal = 0, + atl1c_trans_high = 1 +}; + +struct atl1c_hw_stats { + /* rx */ + unsigned long rx_ok; /* The number of good packet received. */ + unsigned long rx_bcast; /* The number of good broadcast packet received. */ + unsigned long rx_mcast; /* The number of good multicast packet received. */ + unsigned long rx_pause; /* The number of Pause packet received. */ + unsigned long rx_ctrl; /* The number of Control packet received other than Pause frame. */ + unsigned long rx_fcs_err; /* The number of packets with bad FCS. */ + unsigned long rx_len_err; /* The number of packets with mismatch of length field and actual size. */ + unsigned long rx_byte_cnt; /* The number of bytes of good packet received. FCS is NOT included. */ + unsigned long rx_runt; /* The number of packets received that are less than 64 byte long and with good FCS. */ + unsigned long rx_frag; /* The number of packets received that are less than 64 byte long and with bad FCS. */ + unsigned long rx_sz_64; /* The number of good and bad packets received that are 64 byte long. */ + unsigned long rx_sz_65_127; /* The number of good and bad packets received that are between 65 and 127-byte long. */ + unsigned long rx_sz_128_255; /* The number of good and bad packets received that are between 128 and 255-byte long. */ + unsigned long rx_sz_256_511; /* The number of good and bad packets received that are between 256 and 511-byte long. */ + unsigned long rx_sz_512_1023; /* The number of good and bad packets received that are between 512 and 1023-byte long. */ + unsigned long rx_sz_1024_1518; /* The number of good and bad packets received that are between 1024 and 1518-byte long. */ + unsigned long rx_sz_1519_max; /* The number of good and bad packets received that are between 1519-byte and MTU. */ + unsigned long rx_sz_ov; /* The number of good and bad packets received that are more than MTU size truncated by Selene. */ + unsigned long rx_rxf_ov; /* The number of frame dropped due to occurrence of RX FIFO overflow. */ + unsigned long rx_rrd_ov; /* The number of frame dropped due to occurrence of RRD overflow. */ + unsigned long rx_align_err; /* Alignment Error */ + unsigned long rx_bcast_byte_cnt; /* The byte count of broadcast packet received, excluding FCS. */ + unsigned long rx_mcast_byte_cnt; /* The byte count of multicast packet received, excluding FCS. */ + unsigned long rx_err_addr; /* The number of packets dropped due to address filtering. */ + + /* tx */ + unsigned long tx_ok; /* The number of good packet transmitted. */ + unsigned long tx_bcast; /* The number of good broadcast packet transmitted. */ + unsigned long tx_mcast; /* The number of good multicast packet transmitted. */ + unsigned long tx_pause; /* The number of Pause packet transmitted. */ + unsigned long tx_exc_defer; /* The number of packets transmitted with excessive deferral. */ + unsigned long tx_ctrl; /* The number of packets transmitted is a control frame, excluding Pause frame. */ + unsigned long tx_defer; /* The number of packets transmitted that is deferred. */ + unsigned long tx_byte_cnt; /* The number of bytes of data transmitted. FCS is NOT included. */ + unsigned long tx_sz_64; /* The number of good and bad packets transmitted that are 64 byte long. */ + unsigned long tx_sz_65_127; /* The number of good and bad packets transmitted that are between 65 and 127-byte long. */ + unsigned long tx_sz_128_255; /* The number of good and bad packets transmitted that are between 128 and 255-byte long. */ + unsigned long tx_sz_256_511; /* The number of good and bad packets transmitted that are between 256 and 511-byte long. */ + unsigned long tx_sz_512_1023; /* The number of good and bad packets transmitted that are between 512 and 1023-byte long. */ + unsigned long tx_sz_1024_1518; /* The number of good and bad packets transmitted that are between 1024 and 1518-byte long. */ + unsigned long tx_sz_1519_max; /* The number of good and bad packets transmitted that are between 1519-byte and MTU. */ + unsigned long tx_1_col; /* The number of packets subsequently transmitted successfully with a single prior collision. */ + unsigned long tx_2_col; /* The number of packets subsequently transmitted successfully with multiple prior collisions. */ + unsigned long tx_late_col; /* The number of packets transmitted with late collisions. */ + unsigned long tx_abort_col; /* The number of transmit packets aborted due to excessive collisions. */ + unsigned long tx_underrun; /* The number of transmit packets aborted due to transmit FIFO underrun, or TRD FIFO underrun */ + unsigned long tx_rd_eop; /* The number of times that read beyond the EOP into the next frame area when TRD was not written timely */ + unsigned long tx_len_err; /* The number of transmit packets with length field does NOT match the actual frame size. */ + unsigned long tx_trunc; /* The number of transmit packets truncated due to size exceeding MTU. */ + unsigned long tx_bcast_byte; /* The byte count of broadcast packet transmitted, excluding FCS. */ + unsigned long tx_mcast_byte; /* The byte count of multicast packet transmitted, excluding FCS. */ +}; + +struct atl1c_hw { + u8 __iomem *hw_addr; /* inner register address */ + struct atl1c_adapter *adapter; + enum atl1c_nic_type nic_type; + enum atl1c_dma_order dma_order; + enum atl1c_dma_rcb rcb_value; + enum atl1c_dma_req_block dmar_block; + + u16 device_id; + u16 vendor_id; + u16 subsystem_id; + u16 subsystem_vendor_id; + u8 revision_id; + u16 phy_id1; + u16 phy_id2; + + u32 intr_mask; + + u8 preamble_len; + u16 max_frame_size; + u16 min_frame_size; + + enum atl1c_mac_speed mac_speed; + bool mac_duplex; + bool hibernate; + u16 media_type; +#define MEDIA_TYPE_AUTO_SENSOR 0 +#define MEDIA_TYPE_100M_FULL 1 +#define MEDIA_TYPE_100M_HALF 2 +#define MEDIA_TYPE_10M_FULL 3 +#define MEDIA_TYPE_10M_HALF 4 + + u16 autoneg_advertised; + u16 mii_autoneg_adv_reg; + u16 mii_1000t_ctrl_reg; + + u16 tx_imt; /* TX Interrupt Moderator timer ( 2us resolution) */ + u16 rx_imt; /* RX Interrupt Moderator timer ( 2us resolution) */ + u16 ict; /* Interrupt Clear timer (2us resolution) */ + u16 ctrl_flags; +#define ATL1C_INTR_CLEAR_ON_READ 0x0001 +#define ATL1C_INTR_MODRT_ENABLE 0x0002 +#define ATL1C_CMB_ENABLE 0x0004 +#define ATL1C_SMB_ENABLE 0x0010 +#define ATL1C_TXQ_MODE_ENHANCE 0x0020 +#define ATL1C_RX_IPV6_CHKSUM 0x0040 +#define ATL1C_ASPM_L0S_SUPPORT 0x0080 +#define ATL1C_ASPM_L1_SUPPORT 0x0100 +#define ATL1C_ASPM_CTRL_MON 0x0200 +#define ATL1C_HIB_DISABLE 0x0400 +#define ATL1C_APS_MODE_ENABLE 0x0800 +#define ATL1C_LINK_EXT_SYNC 0x1000 +#define ATL1C_CLK_GATING_EN 0x2000 +#define ATL1C_FPGA_VERSION 0x8000 + u16 link_cap_flags; +#define ATL1C_LINK_CAP_1000M 0x0001 + u32 smb_timer; + + u16 rrd_thresh; /* Threshold of number of RRD produced to trigger + interrupt request */ + u16 tpd_thresh; + u8 tpd_burst; /* Number of TPD to prefetch in cache-aligned burst. */ + u8 rfd_burst; + u32 base_cpu; + u32 indirect_tab; + u8 mac_addr[ETH_ALEN]; + u8 perm_mac_addr[ETH_ALEN]; + + bool phy_configured; + bool re_autoneg; + bool emi_ca; + bool msi_lnkpatch; /* link patch for specific platforms */ +}; + +/* + * atl1c_ring_header represents a single, contiguous block of DMA space + * mapped for the three descriptor rings (tpd, rfd, rrd) described below + */ +struct atl1c_ring_header { + void *desc; /* virtual address */ + dma_addr_t dma; /* physical address*/ + unsigned int size; /* length in bytes */ +}; + +/* + * atl1c_buffer is wrapper around a pointer to a socket buffer + * so a DMA handle can be stored along with the skb + */ +struct atl1c_buffer { + struct sk_buff *skb; /* socket buffer */ + u16 length; /* rx buffer length */ + u16 flags; /* information of buffer */ +#define ATL1C_BUFFER_FREE 0x0001 +#define ATL1C_BUFFER_BUSY 0x0002 +#define ATL1C_BUFFER_STATE_MASK 0x0003 + +#define ATL1C_PCIMAP_SINGLE 0x0004 +#define ATL1C_PCIMAP_PAGE 0x0008 +#define ATL1C_PCIMAP_TYPE_MASK 0x000C + +#define ATL1C_PCIMAP_TODEVICE 0x0010 +#define ATL1C_PCIMAP_FROMDEVICE 0x0020 +#define ATL1C_PCIMAP_DIRECTION_MASK 0x0030 + dma_addr_t dma; +}; + +#define ATL1C_SET_BUFFER_STATE(buff, state) do { \ + ((buff)->flags) &= ~ATL1C_BUFFER_STATE_MASK; \ + ((buff)->flags) |= (state); \ + } while (0) + +#define ATL1C_SET_PCIMAP_TYPE(buff, type, direction) do { \ + ((buff)->flags) &= ~ATL1C_PCIMAP_TYPE_MASK; \ + ((buff)->flags) |= (type); \ + ((buff)->flags) &= ~ATL1C_PCIMAP_DIRECTION_MASK; \ + ((buff)->flags) |= (direction); \ + } while (0) + +/* transimit packet descriptor (tpd) ring */ +struct atl1c_tpd_ring { + void *desc; /* descriptor ring virtual address */ + dma_addr_t dma; /* descriptor ring physical address */ + u16 size; /* descriptor ring length in bytes */ + u16 count; /* number of descriptors in the ring */ + u16 next_to_use; /* this is protectd by adapter->tx_lock */ + atomic_t next_to_clean; + struct atl1c_buffer *buffer_info; +}; + +/* receive free descriptor (rfd) ring */ +struct atl1c_rfd_ring { + void *desc; /* descriptor ring virtual address */ + dma_addr_t dma; /* descriptor ring physical address */ + u16 size; /* descriptor ring length in bytes */ + u16 count; /* number of descriptors in the ring */ + u16 next_to_use; + u16 next_to_clean; + struct atl1c_buffer *buffer_info; +}; + +/* receive return descriptor (rrd) ring */ +struct atl1c_rrd_ring { + void *desc; /* descriptor ring virtual address */ + dma_addr_t dma; /* descriptor ring physical address */ + u16 size; /* descriptor ring length in bytes */ + u16 count; /* number of descriptors in the ring */ + u16 next_to_use; + u16 next_to_clean; +}; + +/* board specific private data structure */ +struct atl1c_adapter { + struct net_device *netdev; + struct pci_dev *pdev; + struct napi_struct napi; + struct page *rx_page; + unsigned int rx_page_offset; + unsigned int rx_frag_size; + struct atl1c_hw hw; + struct atl1c_hw_stats hw_stats; + struct mii_if_info mii; /* MII interface info */ + u16 rx_buffer_len; + + unsigned long flags; +#define __AT_TESTING 0x0001 +#define __AT_RESETTING 0x0002 +#define __AT_DOWN 0x0003 + unsigned long work_event; +#define ATL1C_WORK_EVENT_RESET 0 +#define ATL1C_WORK_EVENT_LINK_CHANGE 1 + u32 msg_enable; + + bool have_msi; + u32 wol; + u16 link_speed; + u16 link_duplex; + + spinlock_t mdio_lock; + spinlock_t tx_lock; + atomic_t irq_sem; + + struct work_struct common_task; + struct timer_list watchdog_timer; + struct timer_list phy_config_timer; + + /* All Descriptor memory */ + struct atl1c_ring_header ring_header; + struct atl1c_tpd_ring tpd_ring[AT_MAX_TRANSMIT_QUEUE]; + struct atl1c_rfd_ring rfd_ring; + struct atl1c_rrd_ring rrd_ring; + u32 bd_number; /* board number;*/ +}; + +#define AT_WRITE_REG(a, reg, value) ( \ + writel((value), ((a)->hw_addr + reg))) + +#define AT_WRITE_FLUSH(a) (\ + readl((a)->hw_addr)) + +#define AT_READ_REG(a, reg, pdata) do { \ + if (unlikely((a)->hibernate)) { \ + readl((a)->hw_addr + reg); \ + *(u32 *)pdata = readl((a)->hw_addr + reg); \ + } else { \ + *(u32 *)pdata = readl((a)->hw_addr + reg); \ + } \ + } while (0) + +#define AT_WRITE_REGB(a, reg, value) (\ + writeb((value), ((a)->hw_addr + reg))) + +#define AT_READ_REGB(a, reg) (\ + readb((a)->hw_addr + reg)) + +#define AT_WRITE_REGW(a, reg, value) (\ + writew((value), ((a)->hw_addr + reg))) + +#define AT_READ_REGW(a, reg, pdata) do { \ + if (unlikely((a)->hibernate)) { \ + readw((a)->hw_addr + reg); \ + *(u16 *)pdata = readw((a)->hw_addr + reg); \ + } else { \ + *(u16 *)pdata = readw((a)->hw_addr + reg); \ + } \ + } while (0) + +#define AT_WRITE_REG_ARRAY(a, reg, offset, value) ( \ + writel((value), (((a)->hw_addr + reg) + ((offset) << 2)))) + +#define AT_READ_REG_ARRAY(a, reg, offset) ( \ + readl(((a)->hw_addr + reg) + ((offset) << 2))) + +extern char atl1c_driver_name[]; +extern char atl1c_driver_version[]; + +extern void atl1c_reinit_locked(struct atl1c_adapter *adapter); +extern s32 atl1c_reset_hw(struct atl1c_hw *hw); +extern void atl1c_set_ethtool_ops(struct net_device *netdev); +#endif /* _ATL1C_H_ */ diff --git a/drivers/net/ethernet/atheros/atl1c/atl1c_ethtool.c b/drivers/net/ethernet/atheros/atl1c/atl1c_ethtool.c new file mode 100644 index 0000000..e65cd50 --- /dev/null +++ b/drivers/net/ethernet/atheros/atl1c/atl1c_ethtool.c @@ -0,0 +1,321 @@ +/* + * Copyright(c) 2009 - 2009 Atheros Corporation. All rights reserved. + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include +#include +#include + +#include "atl1c.h" + +static int atl1c_get_settings(struct net_device *netdev, + struct ethtool_cmd *ecmd) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + struct atl1c_hw *hw = &adapter->hw; + + ecmd->supported = (SUPPORTED_10baseT_Half | + SUPPORTED_10baseT_Full | + SUPPORTED_100baseT_Half | + SUPPORTED_100baseT_Full | + SUPPORTED_Autoneg | + SUPPORTED_TP); + if (hw->link_cap_flags & ATL1C_LINK_CAP_1000M) + ecmd->supported |= SUPPORTED_1000baseT_Full; + + ecmd->advertising = ADVERTISED_TP; + + ecmd->advertising |= hw->autoneg_advertised; + + ecmd->port = PORT_TP; + ecmd->phy_address = 0; + ecmd->transceiver = XCVR_INTERNAL; + + if (adapter->link_speed != SPEED_0) { + ethtool_cmd_speed_set(ecmd, adapter->link_speed); + if (adapter->link_duplex == FULL_DUPLEX) + ecmd->duplex = DUPLEX_FULL; + else + ecmd->duplex = DUPLEX_HALF; + } else { + ethtool_cmd_speed_set(ecmd, -1); + ecmd->duplex = -1; + } + + ecmd->autoneg = AUTONEG_ENABLE; + return 0; +} + +static int atl1c_set_settings(struct net_device *netdev, + struct ethtool_cmd *ecmd) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + struct atl1c_hw *hw = &adapter->hw; + u16 autoneg_advertised; + + while (test_and_set_bit(__AT_RESETTING, &adapter->flags)) + msleep(1); + + if (ecmd->autoneg == AUTONEG_ENABLE) { + autoneg_advertised = ADVERTISED_Autoneg; + } else { + u32 speed = ethtool_cmd_speed(ecmd); + if (speed == SPEED_1000) { + if (ecmd->duplex != DUPLEX_FULL) { + if (netif_msg_link(adapter)) + dev_warn(&adapter->pdev->dev, + "1000M half is invalid\n"); + clear_bit(__AT_RESETTING, &adapter->flags); + return -EINVAL; + } + autoneg_advertised = ADVERTISED_1000baseT_Full; + } else if (speed == SPEED_100) { + if (ecmd->duplex == DUPLEX_FULL) + autoneg_advertised = ADVERTISED_100baseT_Full; + else + autoneg_advertised = ADVERTISED_100baseT_Half; + } else { + if (ecmd->duplex == DUPLEX_FULL) + autoneg_advertised = ADVERTISED_10baseT_Full; + else + autoneg_advertised = ADVERTISED_10baseT_Half; + } + } + + if (hw->autoneg_advertised != autoneg_advertised) { + hw->autoneg_advertised = autoneg_advertised; + if (atl1c_restart_autoneg(hw) != 0) { + if (netif_msg_link(adapter)) + dev_warn(&adapter->pdev->dev, + "ethtool speed/duplex setting failed\n"); + clear_bit(__AT_RESETTING, &adapter->flags); + return -EINVAL; + } + } + clear_bit(__AT_RESETTING, &adapter->flags); + return 0; +} + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) +static u32 atl1c_get_tx_csum(struct net_device *netdev) +{ + return (netdev->features & NETIF_F_HW_CSUM) != 0; +} +#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) */ + +static u32 atl1c_get_msglevel(struct net_device *netdev) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + return adapter->msg_enable; +} + +static void atl1c_set_msglevel(struct net_device *netdev, u32 data) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + adapter->msg_enable = data; +} + +static int atl1c_get_regs_len(struct net_device *netdev) +{ + return AT_REGS_LEN; +} + +static void atl1c_get_regs(struct net_device *netdev, + struct ethtool_regs *regs, void *p) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + struct atl1c_hw *hw = &adapter->hw; + u32 *regs_buff = p; + u16 phy_data; + + memset(p, 0, AT_REGS_LEN); + + regs->version = 1; + AT_READ_REG(hw, REG_PM_CTRL, p++); + AT_READ_REG(hw, REG_MAC_HALF_DUPLX_CTRL, p++); + AT_READ_REG(hw, REG_TWSI_CTRL, p++); + AT_READ_REG(hw, REG_PCIE_DEV_MISC_CTRL, p++); + AT_READ_REG(hw, REG_MASTER_CTRL, p++); + AT_READ_REG(hw, REG_MANUAL_TIMER_INIT, p++); + AT_READ_REG(hw, REG_IRQ_MODRT_TIMER_INIT, p++); + AT_READ_REG(hw, REG_GPHY_CTRL, p++); + AT_READ_REG(hw, REG_LINK_CTRL, p++); + AT_READ_REG(hw, REG_IDLE_STATUS, p++); + AT_READ_REG(hw, REG_MDIO_CTRL, p++); + AT_READ_REG(hw, REG_SERDES, p++); + AT_READ_REG(hw, REG_MAC_CTRL, p++); + AT_READ_REG(hw, REG_MAC_IPG_IFG, p++); + AT_READ_REG(hw, REG_MAC_STA_ADDR, p++); + AT_READ_REG(hw, REG_MAC_STA_ADDR+4, p++); + AT_READ_REG(hw, REG_RX_HASH_TABLE, p++); + AT_READ_REG(hw, REG_RX_HASH_TABLE+4, p++); + AT_READ_REG(hw, REG_RXQ_CTRL, p++); + AT_READ_REG(hw, REG_TXQ_CTRL, p++); + AT_READ_REG(hw, REG_MTU, p++); + AT_READ_REG(hw, REG_WOL_CTRL, p++); + + atl1c_read_phy_reg(hw, MII_BMCR, &phy_data); + regs_buff[AT_REGS_LEN/sizeof(u32) - 2] = (u32) phy_data; + atl1c_read_phy_reg(hw, MII_BMSR, &phy_data); + regs_buff[AT_REGS_LEN/sizeof(u32) - 1] = (u32) phy_data; +} + +static int atl1c_get_eeprom_len(struct net_device *netdev) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + + if (atl1c_check_eeprom_exist(&adapter->hw)) + return AT_EEPROM_LEN; + else + return 0; +} + +static int atl1c_get_eeprom(struct net_device *netdev, + struct ethtool_eeprom *eeprom, u8 *bytes) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + struct atl1c_hw *hw = &adapter->hw; + u32 *eeprom_buff; + int first_dword, last_dword; + int ret_val = 0; + int i; + + if (eeprom->len == 0) + return -EINVAL; + + if (!atl1c_check_eeprom_exist(hw)) /* not exist */ + return -EINVAL; + + eeprom->magic = adapter->pdev->vendor | + (adapter->pdev->device << 16); + + first_dword = eeprom->offset >> 2; + last_dword = (eeprom->offset + eeprom->len - 1) >> 2; + + eeprom_buff = kmalloc(sizeof(u32) * + (last_dword - first_dword + 1), GFP_KERNEL); + if (eeprom_buff == NULL) + return -ENOMEM; + + for (i = first_dword; i < last_dword; i++) { + if (!atl1c_read_eeprom(hw, i * 4, &(eeprom_buff[i-first_dword]))) { + kfree(eeprom_buff); + return -EIO; + } + } + + memcpy(bytes, (u8 *)eeprom_buff + (eeprom->offset & 3), + eeprom->len); + kfree(eeprom_buff); + + return ret_val; + return 0; +} + +static void atl1c_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *drvinfo) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + + strlcpy(drvinfo->driver, atl1c_driver_name, sizeof(drvinfo->driver)); + strlcpy(drvinfo->version, atl1c_driver_version, + sizeof(drvinfo->version)); + strlcpy(drvinfo->bus_info, pci_name(adapter->pdev), + sizeof(drvinfo->bus_info)); + drvinfo->n_stats = 0; + drvinfo->testinfo_len = 0; + drvinfo->regdump_len = atl1c_get_regs_len(netdev); + drvinfo->eedump_len = atl1c_get_eeprom_len(netdev); +} + +static void atl1c_get_wol(struct net_device *netdev, + struct ethtool_wolinfo *wol) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + + wol->supported = WAKE_MAGIC | WAKE_PHY; + wol->wolopts = 0; + + if (adapter->wol & AT_WUFC_EX) + wol->wolopts |= WAKE_UCAST; + if (adapter->wol & AT_WUFC_MC) + wol->wolopts |= WAKE_MCAST; + if (adapter->wol & AT_WUFC_BC) + wol->wolopts |= WAKE_BCAST; + if (adapter->wol & AT_WUFC_MAG) + wol->wolopts |= WAKE_MAGIC; + if (adapter->wol & AT_WUFC_LNKC) + wol->wolopts |= WAKE_PHY; +} + +static int atl1c_set_wol(struct net_device *netdev, struct ethtool_wolinfo *wol) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + + if (wol->wolopts & (WAKE_ARP | WAKE_MAGICSECURE | + WAKE_UCAST | WAKE_BCAST | WAKE_MCAST)) + return -EOPNOTSUPP; + /* these settings will always override what we currently have */ + adapter->wol = 0; + + if (wol->wolopts & WAKE_MAGIC) + adapter->wol |= AT_WUFC_MAG; + if (wol->wolopts & WAKE_PHY) + adapter->wol |= AT_WUFC_LNKC; + + device_set_wakeup_enable(&adapter->pdev->dev, adapter->wol); + + return 0; +} + +static int atl1c_nway_reset(struct net_device *netdev) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + if (netif_running(netdev)) + atl1c_reinit_locked(adapter); + return 0; +} + +static const struct ethtool_ops atl1c_ethtool_ops = { + .get_settings = atl1c_get_settings, + .set_settings = atl1c_set_settings, + .get_drvinfo = atl1c_get_drvinfo, + .get_regs_len = atl1c_get_regs_len, + .get_regs = atl1c_get_regs, + .get_wol = atl1c_get_wol, + .set_wol = atl1c_set_wol, + .get_msglevel = atl1c_get_msglevel, + .set_msglevel = atl1c_set_msglevel, + .nway_reset = atl1c_nway_reset, + .get_link = ethtool_op_get_link, + .get_eeprom_len = atl1c_get_eeprom_len, + .get_eeprom = atl1c_get_eeprom, +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) + .get_tx_csum = atl1c_get_tx_csum, + .get_sg = ethtool_op_get_sg, + .set_sg = ethtool_op_set_sg, +#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) */ +}; + +void atl1c_set_ethtool_ops(struct net_device *netdev) +{ + SET_ETHTOOL_OPS(netdev, &atl1c_ethtool_ops); +} diff --git a/drivers/net/ethernet/atheros/atl1c/atl1c_hw.c b/drivers/net/ethernet/atheros/atl1c/atl1c_hw.c new file mode 100644 index 0000000..3ef7092 --- /dev/null +++ b/drivers/net/ethernet/atheros/atl1c/atl1c_hw.c @@ -0,0 +1,865 @@ +/* + * Copyright(c) 2007 Atheros Corporation. All rights reserved. + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include +#include +#include +#include + +#include "atl1c.h" + +/* + * check_eeprom_exist + * return 1 if eeprom exist + */ +int atl1c_check_eeprom_exist(struct atl1c_hw *hw) +{ + u32 data; + + AT_READ_REG(hw, REG_TWSI_DEBUG, &data); + if (data & TWSI_DEBUG_DEV_EXIST) + return 1; + + AT_READ_REG(hw, REG_MASTER_CTRL, &data); + if (data & MASTER_CTRL_OTP_SEL) + return 1; + return 0; +} + +void atl1c_hw_set_mac_addr(struct atl1c_hw *hw, u8 *mac_addr) +{ + u32 value; + /* + * 00-0B-6A-F6-00-DC + * 0: 6AF600DC 1: 000B + * low dword + */ + value = mac_addr[2] << 24 | + mac_addr[3] << 16 | + mac_addr[4] << 8 | + mac_addr[5]; + AT_WRITE_REG_ARRAY(hw, REG_MAC_STA_ADDR, 0, value); + /* hight dword */ + value = mac_addr[0] << 8 | + mac_addr[1]; + AT_WRITE_REG_ARRAY(hw, REG_MAC_STA_ADDR, 1, value); +} + +/* read mac address from hardware register */ +static bool atl1c_read_current_addr(struct atl1c_hw *hw, u8 *eth_addr) +{ + u32 addr[2]; + + AT_READ_REG(hw, REG_MAC_STA_ADDR, &addr[0]); + AT_READ_REG(hw, REG_MAC_STA_ADDR + 4, &addr[1]); + + *(u32 *) ð_addr[2] = htonl(addr[0]); + *(u16 *) ð_addr[0] = htons((u16)addr[1]); + + return is_valid_ether_addr(eth_addr); +} + +/* + * atl1c_get_permanent_address + * return 0 if get valid mac address, + */ +static int atl1c_get_permanent_address(struct atl1c_hw *hw) +{ + u32 i; + u32 otp_ctrl_data; + u32 twsi_ctrl_data; + u16 phy_data; + bool raise_vol = false; + + /* MAC-address from BIOS is the 1st priority */ + if (atl1c_read_current_addr(hw, hw->perm_mac_addr)) + return 0; + + /* init */ + AT_READ_REG(hw, REG_OTP_CTRL, &otp_ctrl_data); + if (atl1c_check_eeprom_exist(hw)) { + if (hw->nic_type == athr_l1c || hw->nic_type == athr_l2c) { + /* Enable OTP CLK */ + if (!(otp_ctrl_data & OTP_CTRL_CLK_EN)) { + otp_ctrl_data |= OTP_CTRL_CLK_EN; + AT_WRITE_REG(hw, REG_OTP_CTRL, otp_ctrl_data); + AT_WRITE_FLUSH(hw); + msleep(1); + } + } + /* raise voltage temporally for l2cb */ + if (hw->nic_type == athr_l2c_b || hw->nic_type == athr_l2c_b2) { + atl1c_read_phy_dbg(hw, MIIDBG_ANACTRL, &phy_data); + phy_data &= ~ANACTRL_HB_EN; + atl1c_write_phy_dbg(hw, MIIDBG_ANACTRL, phy_data); + atl1c_read_phy_dbg(hw, MIIDBG_VOLT_CTRL, &phy_data); + phy_data |= VOLT_CTRL_SWLOWEST; + atl1c_write_phy_dbg(hw, MIIDBG_VOLT_CTRL, phy_data); + udelay(20); + raise_vol = true; + } + + AT_READ_REG(hw, REG_TWSI_CTRL, &twsi_ctrl_data); + twsi_ctrl_data |= TWSI_CTRL_SW_LDSTART; + AT_WRITE_REG(hw, REG_TWSI_CTRL, twsi_ctrl_data); + for (i = 0; i < AT_TWSI_EEPROM_TIMEOUT; i++) { + msleep(10); + AT_READ_REG(hw, REG_TWSI_CTRL, &twsi_ctrl_data); + if ((twsi_ctrl_data & TWSI_CTRL_SW_LDSTART) == 0) + break; + } + if (i >= AT_TWSI_EEPROM_TIMEOUT) + return -1; + } + /* Disable OTP_CLK */ + if ((hw->nic_type == athr_l1c || hw->nic_type == athr_l2c)) { + otp_ctrl_data &= ~OTP_CTRL_CLK_EN; + AT_WRITE_REG(hw, REG_OTP_CTRL, otp_ctrl_data); + msleep(1); + } + if (raise_vol) { + atl1c_read_phy_dbg(hw, MIIDBG_ANACTRL, &phy_data); + phy_data |= ANACTRL_HB_EN; + atl1c_write_phy_dbg(hw, MIIDBG_ANACTRL, phy_data); + atl1c_read_phy_dbg(hw, MIIDBG_VOLT_CTRL, &phy_data); + phy_data &= ~VOLT_CTRL_SWLOWEST; + atl1c_write_phy_dbg(hw, MIIDBG_VOLT_CTRL, phy_data); + udelay(20); + } + + if (atl1c_read_current_addr(hw, hw->perm_mac_addr)) + return 0; + + return -1; +} + +bool atl1c_read_eeprom(struct atl1c_hw *hw, u32 offset, u32 *p_value) +{ + int i; + int ret = false; + u32 otp_ctrl_data; + u32 control; + u32 data; + + if (offset & 3) + return ret; /* address do not align */ + + AT_READ_REG(hw, REG_OTP_CTRL, &otp_ctrl_data); + if (!(otp_ctrl_data & OTP_CTRL_CLK_EN)) + AT_WRITE_REG(hw, REG_OTP_CTRL, + (otp_ctrl_data | OTP_CTRL_CLK_EN)); + + AT_WRITE_REG(hw, REG_EEPROM_DATA_LO, 0); + control = (offset & EEPROM_CTRL_ADDR_MASK) << EEPROM_CTRL_ADDR_SHIFT; + AT_WRITE_REG(hw, REG_EEPROM_CTRL, control); + + for (i = 0; i < 10; i++) { + udelay(100); + AT_READ_REG(hw, REG_EEPROM_CTRL, &control); + if (control & EEPROM_CTRL_RW) + break; + } + if (control & EEPROM_CTRL_RW) { + AT_READ_REG(hw, REG_EEPROM_CTRL, &data); + AT_READ_REG(hw, REG_EEPROM_DATA_LO, p_value); + data = data & 0xFFFF; + *p_value = swab32((data << 16) | (*p_value >> 16)); + ret = true; + } + if (!(otp_ctrl_data & OTP_CTRL_CLK_EN)) + AT_WRITE_REG(hw, REG_OTP_CTRL, otp_ctrl_data); + + return ret; +} +/* + * Reads the adapter's MAC address from the EEPROM + * + * hw - Struct containing variables accessed by shared code + */ +int atl1c_read_mac_addr(struct atl1c_hw *hw) +{ + int err = 0; + + err = atl1c_get_permanent_address(hw); + if (err) + eth_random_addr(hw->perm_mac_addr); + + memcpy(hw->mac_addr, hw->perm_mac_addr, sizeof(hw->perm_mac_addr)); + return err; +} + +/* + * atl1c_hash_mc_addr + * purpose + * set hash value for a multicast address + * hash calcu processing : + * 1. calcu 32bit CRC for multicast address + * 2. reverse crc with MSB to LSB + */ +u32 atl1c_hash_mc_addr(struct atl1c_hw *hw, u8 *mc_addr) +{ + u32 crc32; + u32 value = 0; + int i; + + crc32 = ether_crc_le(6, mc_addr); + for (i = 0; i < 32; i++) + value |= (((crc32 >> i) & 1) << (31 - i)); + + return value; +} + +/* + * Sets the bit in the multicast table corresponding to the hash value. + * hw - Struct containing variables accessed by shared code + * hash_value - Multicast address hash value + */ +void atl1c_hash_set(struct atl1c_hw *hw, u32 hash_value) +{ + u32 hash_bit, hash_reg; + u32 mta; + + /* + * The HASH Table is a register array of 2 32-bit registers. + * It is treated like an array of 64 bits. We want to set + * bit BitArray[hash_value]. So we figure out what register + * the bit is in, read it, OR in the new bit, then write + * back the new value. The register is determined by the + * upper bit of the hash value and the bit within that + * register are determined by the lower 5 bits of the value. + */ + hash_reg = (hash_value >> 31) & 0x1; + hash_bit = (hash_value >> 26) & 0x1F; + + mta = AT_READ_REG_ARRAY(hw, REG_RX_HASH_TABLE, hash_reg); + + mta |= (1 << hash_bit); + + AT_WRITE_REG_ARRAY(hw, REG_RX_HASH_TABLE, hash_reg, mta); +} + +/* + * wait mdio module be idle + * return true: idle + * false: still busy + */ +bool atl1c_wait_mdio_idle(struct atl1c_hw *hw) +{ + u32 val; + int i; + + for (i = 0; i < MDIO_MAX_AC_TO; i++) { + AT_READ_REG(hw, REG_MDIO_CTRL, &val); + if (!(val & (MDIO_CTRL_BUSY | MDIO_CTRL_START))) + break; + udelay(10); + } + + return i != MDIO_MAX_AC_TO; +} + +void atl1c_stop_phy_polling(struct atl1c_hw *hw) +{ + if (!(hw->ctrl_flags & ATL1C_FPGA_VERSION)) + return; + + AT_WRITE_REG(hw, REG_MDIO_CTRL, 0); + atl1c_wait_mdio_idle(hw); +} + +void atl1c_start_phy_polling(struct atl1c_hw *hw, u16 clk_sel) +{ + u32 val; + + if (!(hw->ctrl_flags & ATL1C_FPGA_VERSION)) + return; + + val = MDIO_CTRL_SPRES_PRMBL | + FIELDX(MDIO_CTRL_CLK_SEL, clk_sel) | + FIELDX(MDIO_CTRL_REG, 1) | + MDIO_CTRL_START | + MDIO_CTRL_OP_READ; + AT_WRITE_REG(hw, REG_MDIO_CTRL, val); + atl1c_wait_mdio_idle(hw); + val |= MDIO_CTRL_AP_EN; + val &= ~MDIO_CTRL_START; + AT_WRITE_REG(hw, REG_MDIO_CTRL, val); + udelay(30); +} + + +/* + * atl1c_read_phy_core + * core funtion to read register in PHY via MDIO control regsiter. + * ext: extension register (see IEEE 802.3) + * dev: device address (see IEEE 802.3 DEVAD, PRTAD is fixed to 0) + * reg: reg to read + */ +int atl1c_read_phy_core(struct atl1c_hw *hw, bool ext, u8 dev, + u16 reg, u16 *phy_data) +{ + u32 val; + u16 clk_sel = MDIO_CTRL_CLK_25_4; + + atl1c_stop_phy_polling(hw); + + *phy_data = 0; + + /* only l2c_b2 & l1d_2 could use slow clock */ + if ((hw->nic_type == athr_l2c_b2 || hw->nic_type == athr_l1d_2) && + hw->hibernate) + clk_sel = MDIO_CTRL_CLK_25_128; + if (ext) { + val = FIELDX(MDIO_EXTN_DEVAD, dev) | FIELDX(MDIO_EXTN_REG, reg); + AT_WRITE_REG(hw, REG_MDIO_EXTN, val); + val = MDIO_CTRL_SPRES_PRMBL | + FIELDX(MDIO_CTRL_CLK_SEL, clk_sel) | + MDIO_CTRL_START | + MDIO_CTRL_MODE_EXT | + MDIO_CTRL_OP_READ; + } else { + val = MDIO_CTRL_SPRES_PRMBL | + FIELDX(MDIO_CTRL_CLK_SEL, clk_sel) | + FIELDX(MDIO_CTRL_REG, reg) | + MDIO_CTRL_START | + MDIO_CTRL_OP_READ; + } + AT_WRITE_REG(hw, REG_MDIO_CTRL, val); + + if (!atl1c_wait_mdio_idle(hw)) + return -1; + + AT_READ_REG(hw, REG_MDIO_CTRL, &val); + *phy_data = (u16)FIELD_GETX(val, MDIO_CTRL_DATA); + + atl1c_start_phy_polling(hw, clk_sel); + + return 0; +} + +/* + * atl1c_write_phy_core + * core funtion to write to register in PHY via MDIO control regsiter. + * ext: extension register (see IEEE 802.3) + * dev: device address (see IEEE 802.3 DEVAD, PRTAD is fixed to 0) + * reg: reg to write + */ +int atl1c_write_phy_core(struct atl1c_hw *hw, bool ext, u8 dev, + u16 reg, u16 phy_data) +{ + u32 val; + u16 clk_sel = MDIO_CTRL_CLK_25_4; + + atl1c_stop_phy_polling(hw); + + + /* only l2c_b2 & l1d_2 could use slow clock */ + if ((hw->nic_type == athr_l2c_b2 || hw->nic_type == athr_l1d_2) && + hw->hibernate) + clk_sel = MDIO_CTRL_CLK_25_128; + + if (ext) { + val = FIELDX(MDIO_EXTN_DEVAD, dev) | FIELDX(MDIO_EXTN_REG, reg); + AT_WRITE_REG(hw, REG_MDIO_EXTN, val); + val = MDIO_CTRL_SPRES_PRMBL | + FIELDX(MDIO_CTRL_CLK_SEL, clk_sel) | + FIELDX(MDIO_CTRL_DATA, phy_data) | + MDIO_CTRL_START | + MDIO_CTRL_MODE_EXT; + } else { + val = MDIO_CTRL_SPRES_PRMBL | + FIELDX(MDIO_CTRL_CLK_SEL, clk_sel) | + FIELDX(MDIO_CTRL_DATA, phy_data) | + FIELDX(MDIO_CTRL_REG, reg) | + MDIO_CTRL_START; + } + AT_WRITE_REG(hw, REG_MDIO_CTRL, val); + + if (!atl1c_wait_mdio_idle(hw)) + return -1; + + atl1c_start_phy_polling(hw, clk_sel); + + return 0; +} + +/* + * Reads the value from a PHY register + * hw - Struct containing variables accessed by shared code + * reg_addr - address of the PHY register to read + */ +int atl1c_read_phy_reg(struct atl1c_hw *hw, u16 reg_addr, u16 *phy_data) +{ + return atl1c_read_phy_core(hw, false, 0, reg_addr, phy_data); +} + +/* + * Writes a value to a PHY register + * hw - Struct containing variables accessed by shared code + * reg_addr - address of the PHY register to write + * data - data to write to the PHY + */ +int atl1c_write_phy_reg(struct atl1c_hw *hw, u32 reg_addr, u16 phy_data) +{ + return atl1c_write_phy_core(hw, false, 0, reg_addr, phy_data); +} + +/* read from PHY extension register */ +int atl1c_read_phy_ext(struct atl1c_hw *hw, u8 dev_addr, + u16 reg_addr, u16 *phy_data) +{ + return atl1c_read_phy_core(hw, true, dev_addr, reg_addr, phy_data); +} + +/* write to PHY extension register */ +int atl1c_write_phy_ext(struct atl1c_hw *hw, u8 dev_addr, + u16 reg_addr, u16 phy_data) +{ + return atl1c_write_phy_core(hw, true, dev_addr, reg_addr, phy_data); +} + +int atl1c_read_phy_dbg(struct atl1c_hw *hw, u16 reg_addr, u16 *phy_data) +{ + int err; + + err = atl1c_write_phy_reg(hw, MII_DBG_ADDR, reg_addr); + if (unlikely(err)) + return err; + else + err = atl1c_read_phy_reg(hw, MII_DBG_DATA, phy_data); + + return err; +} + +int atl1c_write_phy_dbg(struct atl1c_hw *hw, u16 reg_addr, u16 phy_data) +{ + int err; + + err = atl1c_write_phy_reg(hw, MII_DBG_ADDR, reg_addr); + if (unlikely(err)) + return err; + else + err = atl1c_write_phy_reg(hw, MII_DBG_DATA, phy_data); + + return err; +} + +/* + * Configures PHY autoneg and flow control advertisement settings + * + * hw - Struct containing variables accessed by shared code + */ +static int atl1c_phy_setup_adv(struct atl1c_hw *hw) +{ + u16 mii_adv_data = ADVERTISE_DEFAULT_CAP & ~ADVERTISE_ALL; + u16 mii_giga_ctrl_data = GIGA_CR_1000T_DEFAULT_CAP & + ~GIGA_CR_1000T_SPEED_MASK; + + if (hw->autoneg_advertised & ADVERTISED_10baseT_Half) + mii_adv_data |= ADVERTISE_10HALF; + if (hw->autoneg_advertised & ADVERTISED_10baseT_Full) + mii_adv_data |= ADVERTISE_10FULL; + if (hw->autoneg_advertised & ADVERTISED_100baseT_Half) + mii_adv_data |= ADVERTISE_100HALF; + if (hw->autoneg_advertised & ADVERTISED_100baseT_Full) + mii_adv_data |= ADVERTISE_100FULL; + + if (hw->autoneg_advertised & ADVERTISED_Autoneg) + mii_adv_data |= ADVERTISE_10HALF | ADVERTISE_10FULL | + ADVERTISE_100HALF | ADVERTISE_100FULL; + + if (hw->link_cap_flags & ATL1C_LINK_CAP_1000M) { + if (hw->autoneg_advertised & ADVERTISED_1000baseT_Half) + mii_giga_ctrl_data |= ADVERTISE_1000HALF; + if (hw->autoneg_advertised & ADVERTISED_1000baseT_Full) + mii_giga_ctrl_data |= ADVERTISE_1000FULL; + if (hw->autoneg_advertised & ADVERTISED_Autoneg) + mii_giga_ctrl_data |= ADVERTISE_1000HALF | + ADVERTISE_1000FULL; + } + + if (atl1c_write_phy_reg(hw, MII_ADVERTISE, mii_adv_data) != 0 || + atl1c_write_phy_reg(hw, MII_CTRL1000, mii_giga_ctrl_data) != 0) + return -1; + return 0; +} + +void atl1c_phy_disable(struct atl1c_hw *hw) +{ + atl1c_power_saving(hw, 0); +} + + +int atl1c_phy_reset(struct atl1c_hw *hw) +{ + struct atl1c_adapter *adapter = hw->adapter; + struct pci_dev *pdev = adapter->pdev; + u16 phy_data; + u32 phy_ctrl_data, lpi_ctrl; + int err; + + /* reset PHY core */ + AT_READ_REG(hw, REG_GPHY_CTRL, &phy_ctrl_data); + phy_ctrl_data &= ~(GPHY_CTRL_EXT_RESET | GPHY_CTRL_PHY_IDDQ | + GPHY_CTRL_GATE_25M_EN | GPHY_CTRL_PWDOWN_HW | GPHY_CTRL_CLS); + phy_ctrl_data |= GPHY_CTRL_SEL_ANA_RST; + if (!(hw->ctrl_flags & ATL1C_HIB_DISABLE)) + phy_ctrl_data |= (GPHY_CTRL_HIB_EN | GPHY_CTRL_HIB_PULSE); + else + phy_ctrl_data &= ~(GPHY_CTRL_HIB_EN | GPHY_CTRL_HIB_PULSE); + AT_WRITE_REG(hw, REG_GPHY_CTRL, phy_ctrl_data); + AT_WRITE_FLUSH(hw); + udelay(10); + AT_WRITE_REG(hw, REG_GPHY_CTRL, phy_ctrl_data | GPHY_CTRL_EXT_RESET); + AT_WRITE_FLUSH(hw); + udelay(10 * GPHY_CTRL_EXT_RST_TO); /* delay 800us */ + + /* switch clock */ + if (hw->nic_type == athr_l2c_b) { + atl1c_read_phy_dbg(hw, MIIDBG_CFGLPSPD, &phy_data); + atl1c_write_phy_dbg(hw, MIIDBG_CFGLPSPD, + phy_data & ~CFGLPSPD_RSTCNT_CLK125SW); + } + + /* tx-half amplitude issue fix */ + if (hw->nic_type == athr_l2c_b || hw->nic_type == athr_l2c_b2) { + atl1c_read_phy_dbg(hw, MIIDBG_CABLE1TH_DET, &phy_data); + phy_data |= CABLE1TH_DET_EN; + atl1c_write_phy_dbg(hw, MIIDBG_CABLE1TH_DET, phy_data); + } + + /* clear bit3 of dbgport 3B to lower voltage */ + if (!(hw->ctrl_flags & ATL1C_HIB_DISABLE)) { + if (hw->nic_type == athr_l2c_b || hw->nic_type == athr_l2c_b2) { + atl1c_read_phy_dbg(hw, MIIDBG_VOLT_CTRL, &phy_data); + phy_data &= ~VOLT_CTRL_SWLOWEST; + atl1c_write_phy_dbg(hw, MIIDBG_VOLT_CTRL, phy_data); + } + /* power saving config */ + phy_data = + hw->nic_type == athr_l1d || hw->nic_type == athr_l1d_2 ? + L1D_LEGCYPS_DEF : L1C_LEGCYPS_DEF; + atl1c_write_phy_dbg(hw, MIIDBG_LEGCYPS, phy_data); + /* hib */ + atl1c_write_phy_dbg(hw, MIIDBG_SYSMODCTRL, + SYSMODCTRL_IECHOADJ_DEF); + } else { + /* disable pws */ + atl1c_read_phy_dbg(hw, MIIDBG_LEGCYPS, &phy_data); + atl1c_write_phy_dbg(hw, MIIDBG_LEGCYPS, + phy_data & ~LEGCYPS_EN); + /* disable hibernate */ + atl1c_read_phy_dbg(hw, MIIDBG_HIBNEG, &phy_data); + atl1c_write_phy_dbg(hw, MIIDBG_HIBNEG, + phy_data & HIBNEG_PSHIB_EN); + } + /* disable AZ(EEE) by default */ + if (hw->nic_type == athr_l1d || hw->nic_type == athr_l1d_2 || + hw->nic_type == athr_l2c_b2) { + AT_READ_REG(hw, REG_LPI_CTRL, &lpi_ctrl); + AT_WRITE_REG(hw, REG_LPI_CTRL, lpi_ctrl & ~LPI_CTRL_EN); + atl1c_write_phy_ext(hw, MIIEXT_ANEG, MIIEXT_LOCAL_EEEADV, 0); + atl1c_write_phy_ext(hw, MIIEXT_PCS, MIIEXT_CLDCTRL3, + L2CB_CLDCTRL3); + } + + /* other debug port to set */ + atl1c_write_phy_dbg(hw, MIIDBG_ANACTRL, ANACTRL_DEF); + atl1c_write_phy_dbg(hw, MIIDBG_SRDSYSMOD, SRDSYSMOD_DEF); + atl1c_write_phy_dbg(hw, MIIDBG_TST10BTCFG, TST10BTCFG_DEF); + /* UNH-IOL test issue, set bit7 */ + atl1c_write_phy_dbg(hw, MIIDBG_TST100BTCFG, + TST100BTCFG_DEF | TST100BTCFG_LITCH_EN); + + /* set phy interrupt mask */ + phy_data = IER_LINK_UP | IER_LINK_DOWN; + err = atl1c_write_phy_reg(hw, MII_IER, phy_data); + if (err) { + if (netif_msg_hw(adapter)) + dev_err(&pdev->dev, + "Error enable PHY linkChange Interrupt\n"); + return err; + } + return 0; +} + +int atl1c_phy_init(struct atl1c_hw *hw) +{ + struct atl1c_adapter *adapter = hw->adapter; + struct pci_dev *pdev = adapter->pdev; + int ret_val; + u16 mii_bmcr_data = BMCR_RESET; + + if ((atl1c_read_phy_reg(hw, MII_PHYSID1, &hw->phy_id1) != 0) || + (atl1c_read_phy_reg(hw, MII_PHYSID2, &hw->phy_id2) != 0)) { + dev_err(&pdev->dev, "Error get phy ID\n"); + return -1; + } + switch (hw->media_type) { + case MEDIA_TYPE_AUTO_SENSOR: + ret_val = atl1c_phy_setup_adv(hw); + if (ret_val) { + if (netif_msg_link(adapter)) + dev_err(&pdev->dev, + "Error Setting up Auto-Negotiation\n"); + return ret_val; + } + mii_bmcr_data |= BMCR_ANENABLE | BMCR_ANRESTART; + break; + case MEDIA_TYPE_100M_FULL: + mii_bmcr_data |= BMCR_SPEED100 | BMCR_FULLDPLX; + break; + case MEDIA_TYPE_100M_HALF: + mii_bmcr_data |= BMCR_SPEED100; + break; + case MEDIA_TYPE_10M_FULL: + mii_bmcr_data |= BMCR_FULLDPLX; + break; + case MEDIA_TYPE_10M_HALF: + break; + default: + if (netif_msg_link(adapter)) + dev_err(&pdev->dev, "Wrong Media type %d\n", + hw->media_type); + return -1; + break; + } + + ret_val = atl1c_write_phy_reg(hw, MII_BMCR, mii_bmcr_data); + if (ret_val) + return ret_val; + hw->phy_configured = true; + + return 0; +} + +/* + * Detects the current speed and duplex settings of the hardware. + * + * hw - Struct containing variables accessed by shared code + * speed - Speed of the connection + * duplex - Duplex setting of the connection + */ +int atl1c_get_speed_and_duplex(struct atl1c_hw *hw, u16 *speed, u16 *duplex) +{ + int err; + u16 phy_data; + + /* Read PHY Specific Status Register (17) */ + err = atl1c_read_phy_reg(hw, MII_GIGA_PSSR, &phy_data); + if (err) + return err; + + if (!(phy_data & GIGA_PSSR_SPD_DPLX_RESOLVED)) + return -1; + + switch (phy_data & GIGA_PSSR_SPEED) { + case GIGA_PSSR_1000MBS: + *speed = SPEED_1000; + break; + case GIGA_PSSR_100MBS: + *speed = SPEED_100; + break; + case GIGA_PSSR_10MBS: + *speed = SPEED_10; + break; + default: + return -1; + break; + } + + if (phy_data & GIGA_PSSR_DPLX) + *duplex = FULL_DUPLEX; + else + *duplex = HALF_DUPLEX; + + return 0; +} + +/* select one link mode to get lower power consumption */ +int atl1c_phy_to_ps_link(struct atl1c_hw *hw) +{ + struct atl1c_adapter *adapter = hw->adapter; + struct pci_dev *pdev = adapter->pdev; + int ret = 0; + u16 autoneg_advertised = ADVERTISED_10baseT_Half; + u16 save_autoneg_advertised; + u16 phy_data; + u16 mii_lpa_data; + u16 speed = SPEED_0; + u16 duplex = FULL_DUPLEX; + int i; + + atl1c_read_phy_reg(hw, MII_BMSR, &phy_data); + atl1c_read_phy_reg(hw, MII_BMSR, &phy_data); + if (phy_data & BMSR_LSTATUS) { + atl1c_read_phy_reg(hw, MII_LPA, &mii_lpa_data); + if (mii_lpa_data & LPA_10FULL) + autoneg_advertised = ADVERTISED_10baseT_Full; + else if (mii_lpa_data & LPA_10HALF) + autoneg_advertised = ADVERTISED_10baseT_Half; + else if (mii_lpa_data & LPA_100HALF) + autoneg_advertised = ADVERTISED_100baseT_Half; + else if (mii_lpa_data & LPA_100FULL) + autoneg_advertised = ADVERTISED_100baseT_Full; + + save_autoneg_advertised = hw->autoneg_advertised; + hw->phy_configured = false; + hw->autoneg_advertised = autoneg_advertised; + if (atl1c_restart_autoneg(hw) != 0) { + dev_dbg(&pdev->dev, "phy autoneg failed\n"); + ret = -1; + } + hw->autoneg_advertised = save_autoneg_advertised; + + if (mii_lpa_data) { + for (i = 0; i < AT_SUSPEND_LINK_TIMEOUT; i++) { + mdelay(100); + atl1c_read_phy_reg(hw, MII_BMSR, &phy_data); + atl1c_read_phy_reg(hw, MII_BMSR, &phy_data); + if (phy_data & BMSR_LSTATUS) { + if (atl1c_get_speed_and_duplex(hw, &speed, + &duplex) != 0) + dev_dbg(&pdev->dev, + "get speed and duplex failed\n"); + break; + } + } + } + } else { + speed = SPEED_10; + duplex = HALF_DUPLEX; + } + adapter->link_speed = speed; + adapter->link_duplex = duplex; + + return ret; +} + +int atl1c_restart_autoneg(struct atl1c_hw *hw) +{ + int err = 0; + u16 mii_bmcr_data = BMCR_RESET; + + err = atl1c_phy_setup_adv(hw); + if (err) + return err; + mii_bmcr_data |= BMCR_ANENABLE | BMCR_ANRESTART; + + return atl1c_write_phy_reg(hw, MII_BMCR, mii_bmcr_data); +} + +int atl1c_power_saving(struct atl1c_hw *hw, u32 wufc) +{ + struct atl1c_adapter *adapter = hw->adapter; + struct pci_dev *pdev = adapter->pdev; + u32 master_ctrl, mac_ctrl, phy_ctrl; + u32 wol_ctrl, speed; + u16 phy_data; + + wol_ctrl = 0; + speed = adapter->link_speed == SPEED_1000 ? + MAC_CTRL_SPEED_1000 : MAC_CTRL_SPEED_10_100; + + AT_READ_REG(hw, REG_MASTER_CTRL, &master_ctrl); + AT_READ_REG(hw, REG_MAC_CTRL, &mac_ctrl); + AT_READ_REG(hw, REG_GPHY_CTRL, &phy_ctrl); + + master_ctrl &= ~MASTER_CTRL_CLK_SEL_DIS; + mac_ctrl = FIELD_SETX(mac_ctrl, MAC_CTRL_SPEED, speed); + mac_ctrl &= ~(MAC_CTRL_DUPLX | MAC_CTRL_RX_EN | MAC_CTRL_TX_EN); + if (adapter->link_duplex == FULL_DUPLEX) + mac_ctrl |= MAC_CTRL_DUPLX; + phy_ctrl &= ~(GPHY_CTRL_EXT_RESET | GPHY_CTRL_CLS); + phy_ctrl |= GPHY_CTRL_SEL_ANA_RST | GPHY_CTRL_HIB_PULSE | + GPHY_CTRL_HIB_EN; + if (!wufc) { /* without WoL */ + master_ctrl |= MASTER_CTRL_CLK_SEL_DIS; + phy_ctrl |= GPHY_CTRL_PHY_IDDQ | GPHY_CTRL_PWDOWN_HW; + AT_WRITE_REG(hw, REG_MASTER_CTRL, master_ctrl); + AT_WRITE_REG(hw, REG_MAC_CTRL, mac_ctrl); + AT_WRITE_REG(hw, REG_GPHY_CTRL, phy_ctrl); + AT_WRITE_REG(hw, REG_WOL_CTRL, 0); + hw->phy_configured = false; /* re-init PHY when resume */ + return 0; + } + phy_ctrl |= GPHY_CTRL_EXT_RESET; + if (wufc & AT_WUFC_MAG) { + mac_ctrl |= MAC_CTRL_RX_EN | MAC_CTRL_BC_EN; + wol_ctrl |= WOL_MAGIC_EN | WOL_MAGIC_PME_EN; + if (hw->nic_type == athr_l2c_b && hw->revision_id == L2CB_V11) + wol_ctrl |= WOL_PATTERN_EN | WOL_PATTERN_PME_EN; + } + if (wufc & AT_WUFC_LNKC) { + wol_ctrl |= WOL_LINK_CHG_EN | WOL_LINK_CHG_PME_EN; + if (atl1c_write_phy_reg(hw, MII_IER, IER_LINK_UP) != 0) { + dev_dbg(&pdev->dev, "%s: write phy MII_IER failed.\n", + atl1c_driver_name); + } + } + /* clear PHY interrupt */ + atl1c_read_phy_reg(hw, MII_ISR, &phy_data); + + dev_dbg(&pdev->dev, "%s: suspend MAC=%x,MASTER=%x,PHY=0x%x,WOL=%x\n", + atl1c_driver_name, mac_ctrl, master_ctrl, phy_ctrl, wol_ctrl); + AT_WRITE_REG(hw, REG_MASTER_CTRL, master_ctrl); + AT_WRITE_REG(hw, REG_MAC_CTRL, mac_ctrl); + AT_WRITE_REG(hw, REG_GPHY_CTRL, phy_ctrl); + AT_WRITE_REG(hw, REG_WOL_CTRL, wol_ctrl); + + return 0; +} + + +/* configure phy after Link change Event */ +void atl1c_post_phy_linkchg(struct atl1c_hw *hw, u16 link_speed) +{ + u16 phy_val; + bool adj_thresh = false; + + if (hw->nic_type == athr_l2c_b || hw->nic_type == athr_l2c_b2 || + hw->nic_type == athr_l1d || hw->nic_type == athr_l1d_2) + adj_thresh = true; + + if (link_speed != SPEED_0) { /* link up */ + /* az with brcm, half-amp */ + if (hw->nic_type == athr_l1d_2) { + atl1c_read_phy_ext(hw, MIIEXT_PCS, MIIEXT_CLDCTRL6, + &phy_val); + phy_val = FIELD_GETX(phy_val, CLDCTRL6_CAB_LEN); + phy_val = phy_val > CLDCTRL6_CAB_LEN_SHORT ? + AZ_ANADECT_LONG : AZ_ANADECT_DEF; + atl1c_write_phy_dbg(hw, MIIDBG_AZ_ANADECT, phy_val); + } + /* threshold adjust */ + if (adj_thresh && link_speed == SPEED_100 && hw->msi_lnkpatch) { + atl1c_write_phy_dbg(hw, MIIDBG_MSE16DB, L1D_MSE16DB_UP); + atl1c_write_phy_dbg(hw, MIIDBG_SYSMODCTRL, + L1D_SYSMODCTRL_IECHOADJ_DEF); + } + } else { /* link down */ + if (adj_thresh && hw->msi_lnkpatch) { + atl1c_write_phy_dbg(hw, MIIDBG_SYSMODCTRL, + SYSMODCTRL_IECHOADJ_DEF); + atl1c_write_phy_dbg(hw, MIIDBG_MSE16DB, + L1D_MSE16DB_DOWN); + } + } +} diff --git a/drivers/net/ethernet/atheros/atl1c/atl1c_hw.h b/drivers/net/ethernet/atheros/atl1c/atl1c_hw.h new file mode 100644 index 0000000..21d8c4d --- /dev/null +++ b/drivers/net/ethernet/atheros/atl1c/atl1c_hw.h @@ -0,0 +1,1024 @@ +/* + * Copyright(c) 2008 - 2009 Atheros Corporation. All rights reserved. + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _ATL1C_HW_H_ +#define _ATL1C_HW_H_ + +#include +#include + +#define FIELD_GETX(_x, _name) ((_x) >> (_name##_SHIFT) & (_name##_MASK)) +#define FIELD_SETX(_x, _name, _v) \ +(((_x) & ~((_name##_MASK) << (_name##_SHIFT))) |\ +(((_v) & (_name##_MASK)) << (_name##_SHIFT))) +#define FIELDX(_name, _v) (((_v) & (_name##_MASK)) << (_name##_SHIFT)) + +struct atl1c_adapter; +struct atl1c_hw; + +/* function prototype */ +void atl1c_phy_disable(struct atl1c_hw *hw); +void atl1c_hw_set_mac_addr(struct atl1c_hw *hw, u8 *mac_addr); +int atl1c_phy_reset(struct atl1c_hw *hw); +int atl1c_read_mac_addr(struct atl1c_hw *hw); +int atl1c_get_speed_and_duplex(struct atl1c_hw *hw, u16 *speed, u16 *duplex); +u32 atl1c_hash_mc_addr(struct atl1c_hw *hw, u8 *mc_addr); +void atl1c_hash_set(struct atl1c_hw *hw, u32 hash_value); +int atl1c_read_phy_reg(struct atl1c_hw *hw, u16 reg_addr, u16 *phy_data); +int atl1c_write_phy_reg(struct atl1c_hw *hw, u32 reg_addr, u16 phy_data); +bool atl1c_read_eeprom(struct atl1c_hw *hw, u32 offset, u32 *p_value); +int atl1c_phy_init(struct atl1c_hw *hw); +int atl1c_check_eeprom_exist(struct atl1c_hw *hw); +int atl1c_restart_autoneg(struct atl1c_hw *hw); +int atl1c_phy_to_ps_link(struct atl1c_hw *hw); +int atl1c_power_saving(struct atl1c_hw *hw, u32 wufc); +bool atl1c_wait_mdio_idle(struct atl1c_hw *hw); +void atl1c_stop_phy_polling(struct atl1c_hw *hw); +void atl1c_start_phy_polling(struct atl1c_hw *hw, u16 clk_sel); +int atl1c_read_phy_core(struct atl1c_hw *hw, bool ext, u8 dev, + u16 reg, u16 *phy_data); +int atl1c_write_phy_core(struct atl1c_hw *hw, bool ext, u8 dev, + u16 reg, u16 phy_data); +int atl1c_read_phy_ext(struct atl1c_hw *hw, u8 dev_addr, + u16 reg_addr, u16 *phy_data); +int atl1c_write_phy_ext(struct atl1c_hw *hw, u8 dev_addr, + u16 reg_addr, u16 phy_data); +int atl1c_read_phy_dbg(struct atl1c_hw *hw, u16 reg_addr, u16 *phy_data); +int atl1c_write_phy_dbg(struct atl1c_hw *hw, u16 reg_addr, u16 phy_data); +void atl1c_post_phy_linkchg(struct atl1c_hw *hw, u16 link_speed); + +/* hw-ids */ +#define PCI_DEVICE_ID_ATTANSIC_L2C 0x1062 +#define PCI_DEVICE_ID_ATTANSIC_L1C 0x1063 +#define PCI_DEVICE_ID_ATHEROS_L2C_B 0x2060 /* AR8152 v1.1 Fast 10/100 */ +#define PCI_DEVICE_ID_ATHEROS_L2C_B2 0x2062 /* AR8152 v2.0 Fast 10/100 */ +#define PCI_DEVICE_ID_ATHEROS_L1D 0x1073 /* AR8151 v1.0 Gigabit 1000 */ +#define PCI_DEVICE_ID_ATHEROS_L1D_2_0 0x1083 /* AR8151 v2.0 Gigabit 1000 */ +#define L2CB_V10 0xc0 +#define L2CB_V11 0xc1 +#define L2CB_V20 0xc0 +#define L2CB_V21 0xc1 + +/* register definition */ +#define REG_DEVICE_CAP 0x5C +#define DEVICE_CAP_MAX_PAYLOAD_MASK 0x7 +#define DEVICE_CAP_MAX_PAYLOAD_SHIFT 0 + +#define DEVICE_CTRL_MAXRRS_MIN 2 + +#define REG_LINK_CTRL 0x68 +#define LINK_CTRL_L0S_EN 0x01 +#define LINK_CTRL_L1_EN 0x02 +#define LINK_CTRL_EXT_SYNC 0x80 + +#define REG_PCIE_IND_ACC_ADDR 0x80 +#define REG_PCIE_IND_ACC_DATA 0x84 + +#define REG_DEV_SERIALNUM_CTRL 0x200 +#define REG_DEV_MAC_SEL_MASK 0x0 /* 0:EUI; 1:MAC */ +#define REG_DEV_MAC_SEL_SHIFT 0 +#define REG_DEV_SERIAL_NUM_EN_MASK 0x1 +#define REG_DEV_SERIAL_NUM_EN_SHIFT 1 + +#define REG_TWSI_CTRL 0x218 +#define TWSI_CTLR_FREQ_MASK 0x3UL +#define TWSI_CTRL_FREQ_SHIFT 24 +#define TWSI_CTRL_FREQ_100K 0 +#define TWSI_CTRL_FREQ_200K 1 +#define TWSI_CTRL_FREQ_300K 2 +#define TWSI_CTRL_FREQ_400K 3 +#define TWSI_CTRL_LD_EXIST BIT(23) +#define TWSI_CTRL_HW_LDSTAT BIT(12) /* 0:finish,1:in progress */ +#define TWSI_CTRL_SW_LDSTART BIT(11) +#define TWSI_CTRL_LD_OFFSET_MASK 0xFF +#define TWSI_CTRL_LD_OFFSET_SHIFT 0 + +#define REG_PCIE_DEV_MISC_CTRL 0x21C +#define PCIE_DEV_MISC_EXT_PIPE 0x2 +#define PCIE_DEV_MISC_RETRY_BUFDIS 0x1 +#define PCIE_DEV_MISC_SPIROM_EXIST 0x4 +#define PCIE_DEV_MISC_SERDES_ENDIAN 0x8 +#define PCIE_DEV_MISC_SERDES_SEL_DIN 0x10 + +#define REG_PCIE_PHYMISC 0x1000 +#define PCIE_PHYMISC_FORCE_RCV_DET BIT(2) +#define PCIE_PHYMISC_NFTS_MASK 0xFFUL +#define PCIE_PHYMISC_NFTS_SHIFT 16 + +#define REG_PCIE_PHYMISC2 0x1004 +#define PCIE_PHYMISC2_L0S_TH_MASK 0x3UL +#define PCIE_PHYMISC2_L0S_TH_SHIFT 18 +#define L2CB1_PCIE_PHYMISC2_L0S_TH 3 +#define PCIE_PHYMISC2_CDR_BW_MASK 0x3UL +#define PCIE_PHYMISC2_CDR_BW_SHIFT 16 +#define L2CB1_PCIE_PHYMISC2_CDR_BW 3 + +#define REG_TWSI_DEBUG 0x1108 +#define TWSI_DEBUG_DEV_EXIST BIT(29) + +#define REG_DMA_DBG 0x1114 +#define DMA_DBG_VENDOR_MSG BIT(0) + +#define REG_EEPROM_CTRL 0x12C0 +#define EEPROM_CTRL_DATA_HI_MASK 0xFFFF +#define EEPROM_CTRL_DATA_HI_SHIFT 0 +#define EEPROM_CTRL_ADDR_MASK 0x3FF +#define EEPROM_CTRL_ADDR_SHIFT 16 +#define EEPROM_CTRL_ACK 0x40000000 +#define EEPROM_CTRL_RW 0x80000000 + +#define REG_EEPROM_DATA_LO 0x12C4 + +#define REG_OTP_CTRL 0x12F0 +#define OTP_CTRL_CLK_EN BIT(1) + +#define REG_PM_CTRL 0x12F8 +#define PM_CTRL_HOTRST BIT(31) +#define PM_CTRL_MAC_ASPM_CHK BIT(30) /* L0s/L1 dis by MAC based on + * thrghput(setting in 15A0) */ +#define PM_CTRL_SA_DLY_EN BIT(29) +#define PM_CTRL_L0S_BUFSRX_EN BIT(28) +#define PM_CTRL_LCKDET_TIMER_MASK 0xFUL +#define PM_CTRL_LCKDET_TIMER_SHIFT 24 +#define PM_CTRL_LCKDET_TIMER_DEF 0xC +#define PM_CTRL_PM_REQ_TIMER_MASK 0xFUL +#define PM_CTRL_PM_REQ_TIMER_SHIFT 20 /* pm_request_l1 time > @ + * ->L0s not L1 */ +#define PM_CTRL_PM_REQ_TO_DEF 0xF +#define PMCTRL_TXL1_AFTER_L0S BIT(19) /* l1dv2.0+ */ +#define L1D_PMCTRL_L1_ENTRY_TM_MASK 7UL /* l1dv2.0+, 3bits */ +#define L1D_PMCTRL_L1_ENTRY_TM_SHIFT 16 +#define L1D_PMCTRL_L1_ENTRY_TM_DIS 0 +#define L1D_PMCTRL_L1_ENTRY_TM_2US 1 +#define L1D_PMCTRL_L1_ENTRY_TM_4US 2 +#define L1D_PMCTRL_L1_ENTRY_TM_8US 3 +#define L1D_PMCTRL_L1_ENTRY_TM_16US 4 +#define L1D_PMCTRL_L1_ENTRY_TM_24US 5 +#define L1D_PMCTRL_L1_ENTRY_TM_32US 6 +#define L1D_PMCTRL_L1_ENTRY_TM_63US 7 +#define PM_CTRL_L1_ENTRY_TIMER_MASK 0xFUL /* l1C 4bits */ +#define PM_CTRL_L1_ENTRY_TIMER_SHIFT 16 +#define L2CB1_PM_CTRL_L1_ENTRY_TM 7 +#define L1C_PM_CTRL_L1_ENTRY_TM 0xF +#define PM_CTRL_RCVR_WT_TIMER BIT(15) /* 1:1us, 0:2ms */ +#define PM_CTRL_CLK_PWM_VER1_1 BIT(14) /* 0:1.0a,1:1.1 */ +#define PM_CTRL_CLK_SWH_L1 BIT(13) /* en pcie clk sw in L1 */ +#define PM_CTRL_ASPM_L0S_EN BIT(12) +#define PM_CTRL_RXL1_AFTER_L0S BIT(11) /* l1dv2.0+ */ +#define L1D_PMCTRL_L0S_TIMER_MASK 7UL /* l1d2.0+, 3bits*/ +#define L1D_PMCTRL_L0S_TIMER_SHIFT 8 +#define PM_CTRL_L0S_ENTRY_TIMER_MASK 0xFUL /* l1c, 4bits */ +#define PM_CTRL_L0S_ENTRY_TIMER_SHIFT 8 +#define PM_CTRL_SERDES_BUFS_RX_L1_EN BIT(7) +#define PM_CTRL_SERDES_PD_EX_L1 BIT(6) /* power down serdes rx */ +#define PM_CTRL_SERDES_PLL_L1_EN BIT(5) +#define PM_CTRL_SERDES_L1_EN BIT(4) +#define PM_CTRL_ASPM_L1_EN BIT(3) +#define PM_CTRL_CLK_REQ_EN BIT(2) +#define PM_CTRL_RBER_EN BIT(1) +#define PM_CTRL_SPRSDWER_EN BIT(0) + +#define REG_LTSSM_ID_CTRL 0x12FC +#define LTSSM_ID_EN_WRO 0x1000 + + +/* Selene Master Control Register */ +#define REG_MASTER_CTRL 0x1400 +#define MASTER_CTRL_OTP_SEL BIT(31) +#define MASTER_DEV_NUM_MASK 0x7FUL +#define MASTER_DEV_NUM_SHIFT 24 +#define MASTER_REV_NUM_MASK 0xFFUL +#define MASTER_REV_NUM_SHIFT 16 +#define MASTER_CTRL_INT_RDCLR BIT(14) +#define MASTER_CTRL_CLK_SEL_DIS BIT(12) /* 1:alwys sel pclk from + * serdes, not sw to 25M */ +#define MASTER_CTRL_RX_ITIMER_EN BIT(11) /* IRQ MODURATION FOR RX */ +#define MASTER_CTRL_TX_ITIMER_EN BIT(10) /* MODURATION FOR TX/RX */ +#define MASTER_CTRL_MANU_INT BIT(9) /* SOFT MANUAL INT */ +#define MASTER_CTRL_MANUTIMER_EN BIT(8) +#define MASTER_CTRL_SA_TIMER_EN BIT(7) /* SYS ALIVE TIMER EN */ +#define MASTER_CTRL_OOB_DIS BIT(6) /* OUT OF BOX DIS */ +#define MASTER_CTRL_WAKEN_25M BIT(5) /* WAKE WO. PCIE CLK */ +#define MASTER_CTRL_BERT_START BIT(4) +#define MASTER_PCIE_TSTMOD_MASK 3UL +#define MASTER_PCIE_TSTMOD_SHIFT 2 +#define MASTER_PCIE_RST BIT(1) +#define MASTER_CTRL_SOFT_RST BIT(0) /* RST MAC & DMA */ +#define DMA_MAC_RST_TO 50 + +/* Timer Initial Value Register */ +#define REG_MANUAL_TIMER_INIT 0x1404 + +/* IRQ ModeratorTimer Initial Value Register */ +#define REG_IRQ_MODRT_TIMER_INIT 0x1408 +#define IRQ_MODRT_TIMER_MASK 0xffff +#define IRQ_MODRT_TX_TIMER_SHIFT 0 +#define IRQ_MODRT_RX_TIMER_SHIFT 16 + +#define REG_GPHY_CTRL 0x140C +#define GPHY_CTRL_ADDR_MASK 0x1FUL +#define GPHY_CTRL_ADDR_SHIFT 19 +#define GPHY_CTRL_BP_VLTGSW BIT(18) +#define GPHY_CTRL_100AB_EN BIT(17) +#define GPHY_CTRL_10AB_EN BIT(16) +#define GPHY_CTRL_PHY_PLL_BYPASS BIT(15) +#define GPHY_CTRL_PWDOWN_HW BIT(14) /* affect MAC&PHY, to low pw */ +#define GPHY_CTRL_PHY_PLL_ON BIT(13) /* 1:pll always on, 0:can sw */ +#define GPHY_CTRL_SEL_ANA_RST BIT(12) +#define GPHY_CTRL_HIB_PULSE BIT(11) +#define GPHY_CTRL_HIB_EN BIT(10) +#define GPHY_CTRL_GIGA_DIS BIT(9) +#define GPHY_CTRL_PHY_IDDQ_DIS BIT(8) /* pw on RST */ +#define GPHY_CTRL_PHY_IDDQ BIT(7) /* bit8 affect bit7 while rb */ +#define GPHY_CTRL_LPW_EXIT BIT(6) +#define GPHY_CTRL_GATE_25M_EN BIT(5) +#define GPHY_CTRL_REV_ANEG BIT(4) +#define GPHY_CTRL_ANEG_NOW BIT(3) +#define GPHY_CTRL_LED_MODE BIT(2) +#define GPHY_CTRL_RTL_MODE BIT(1) +#define GPHY_CTRL_EXT_RESET BIT(0) /* 1:out of DSP RST status */ +#define GPHY_CTRL_EXT_RST_TO 80 /* 800us atmost */ +#define GPHY_CTRL_CLS (\ + GPHY_CTRL_LED_MODE |\ + GPHY_CTRL_100AB_EN |\ + GPHY_CTRL_PHY_PLL_ON) + +/* Block IDLE Status Register */ +#define REG_IDLE_STATUS 0x1410 +#define IDLE_STATUS_SFORCE_MASK 0xFUL +#define IDLE_STATUS_SFORCE_SHIFT 14 +#define IDLE_STATUS_CALIB_DONE BIT(13) +#define IDLE_STATUS_CALIB_RES_MASK 0x1FUL +#define IDLE_STATUS_CALIB_RES_SHIFT 8 +#define IDLE_STATUS_CALIBERR_MASK 0xFUL +#define IDLE_STATUS_CALIBERR_SHIFT 4 +#define IDLE_STATUS_TXQ_BUSY BIT(3) +#define IDLE_STATUS_RXQ_BUSY BIT(2) +#define IDLE_STATUS_TXMAC_BUSY BIT(1) +#define IDLE_STATUS_RXMAC_BUSY BIT(0) +#define IDLE_STATUS_MASK (\ + IDLE_STATUS_TXQ_BUSY |\ + IDLE_STATUS_RXQ_BUSY |\ + IDLE_STATUS_TXMAC_BUSY |\ + IDLE_STATUS_RXMAC_BUSY) + +/* MDIO Control Register */ +#define REG_MDIO_CTRL 0x1414 +#define MDIO_CTRL_MODE_EXT BIT(30) +#define MDIO_CTRL_POST_READ BIT(29) +#define MDIO_CTRL_AP_EN BIT(28) +#define MDIO_CTRL_BUSY BIT(27) +#define MDIO_CTRL_CLK_SEL_MASK 0x7UL +#define MDIO_CTRL_CLK_SEL_SHIFT 24 +#define MDIO_CTRL_CLK_25_4 0 /* 25MHz divide 4 */ +#define MDIO_CTRL_CLK_25_6 2 +#define MDIO_CTRL_CLK_25_8 3 +#define MDIO_CTRL_CLK_25_10 4 +#define MDIO_CTRL_CLK_25_32 5 +#define MDIO_CTRL_CLK_25_64 6 +#define MDIO_CTRL_CLK_25_128 7 +#define MDIO_CTRL_START BIT(23) +#define MDIO_CTRL_SPRES_PRMBL BIT(22) +#define MDIO_CTRL_OP_READ BIT(21) /* 1:read, 0:write */ +#define MDIO_CTRL_REG_MASK 0x1FUL +#define MDIO_CTRL_REG_SHIFT 16 +#define MDIO_CTRL_DATA_MASK 0xFFFFUL +#define MDIO_CTRL_DATA_SHIFT 0 +#define MDIO_MAX_AC_TO 120 /* 1.2ms timeout for slow clk */ + +/* for extension reg access */ +#define REG_MDIO_EXTN 0x1448 +#define MDIO_EXTN_PORTAD_MASK 0x1FUL +#define MDIO_EXTN_PORTAD_SHIFT 21 +#define MDIO_EXTN_DEVAD_MASK 0x1FUL +#define MDIO_EXTN_DEVAD_SHIFT 16 +#define MDIO_EXTN_REG_MASK 0xFFFFUL +#define MDIO_EXTN_REG_SHIFT 0 + +/* BIST Control and Status Register0 (for the Packet Memory) */ +#define REG_BIST0_CTRL 0x141c +#define BIST0_NOW 0x1 +#define BIST0_SRAM_FAIL 0x2 /* 1: The SRAM failure is + * un-repairable because + * it has address decoder + * failure or more than 1 cell + * stuck-to-x failure */ +#define BIST0_FUSE_FLAG 0x4 + +/* BIST Control and Status Register1(for the retry buffer of PCI Express) */ +#define REG_BIST1_CTRL 0x1420 +#define BIST1_NOW 0x1 +#define BIST1_SRAM_FAIL 0x2 +#define BIST1_FUSE_FLAG 0x4 + +/* SerDes Lock Detect Control and Status Register */ +#define REG_SERDES 0x1424 +#define SERDES_PHY_CLK_SLOWDOWN BIT(18) +#define SERDES_MAC_CLK_SLOWDOWN BIT(17) +#define SERDES_SELFB_PLL_MASK 0x3UL +#define SERDES_SELFB_PLL_SHIFT 14 +#define SERDES_PHYCLK_SEL_GTX BIT(13) /* 1:gtx_clk, 0:25M */ +#define SERDES_PCIECLK_SEL_SRDS BIT(12) /* 1:serdes,0:25M */ +#define SERDES_BUFS_RX_EN BIT(11) +#define SERDES_PD_RX BIT(10) +#define SERDES_PLL_EN BIT(9) +#define SERDES_EN BIT(8) +#define SERDES_SELFB_PLL_SEL_CSR BIT(6) /* 0:state-machine,1:csr */ +#define SERDES_SELFB_PLL_CSR_MASK 0x3UL +#define SERDES_SELFB_PLL_CSR_SHIFT 4 +#define SERDES_SELFB_PLL_CSR_4 3 /* 4-12% OV-CLK */ +#define SERDES_SELFB_PLL_CSR_0 2 /* 0-4% OV-CLK */ +#define SERDES_SELFB_PLL_CSR_12 1 /* 12-18% OV-CLK */ +#define SERDES_SELFB_PLL_CSR_18 0 /* 18-25% OV-CLK */ +#define SERDES_VCO_SLOW BIT(3) +#define SERDES_VCO_FAST BIT(2) +#define SERDES_LOCK_DETECT_EN BIT(1) +#define SERDES_LOCK_DETECT BIT(0) + +#define REG_LPI_DECISN_TIMER 0x143C +#define L2CB_LPI_DESISN_TIMER 0x7D00 + +#define REG_LPI_CTRL 0x1440 +#define LPI_CTRL_CHK_DA BIT(31) +#define LPI_CTRL_ENH_TO_MASK 0x1FFFUL +#define LPI_CTRL_ENH_TO_SHIFT 12 +#define LPI_CTRL_ENH_TH_MASK 0x1FUL +#define LPI_CTRL_ENH_TH_SHIFT 6 +#define LPI_CTRL_ENH_EN BIT(5) +#define LPI_CTRL_CHK_RX BIT(4) +#define LPI_CTRL_CHK_STATE BIT(3) +#define LPI_CTRL_GMII BIT(2) +#define LPI_CTRL_TO_PHY BIT(1) +#define LPI_CTRL_EN BIT(0) + +#define REG_LPI_WAIT 0x1444 +#define LPI_WAIT_TIMER_MASK 0xFFFFUL +#define LPI_WAIT_TIMER_SHIFT 0 + +/* MAC Control Register */ +#define REG_MAC_CTRL 0x1480 +#define MAC_CTRL_SPEED_MODE_SW BIT(30) /* 0:phy,1:sw */ +#define MAC_CTRL_HASH_ALG_CRC32 BIT(29) /* 1:legacy,0:lw_5b */ +#define MAC_CTRL_SINGLE_PAUSE_EN BIT(28) +#define MAC_CTRL_DBG BIT(27) +#define MAC_CTRL_BC_EN BIT(26) +#define MAC_CTRL_MC_ALL_EN BIT(25) +#define MAC_CTRL_RX_CHKSUM_EN BIT(24) +#define MAC_CTRL_TX_HUGE BIT(23) +#define MAC_CTRL_DBG_TX_BKPRESURE BIT(22) +#define MAC_CTRL_SPEED_MASK 3UL +#define MAC_CTRL_SPEED_SHIFT 20 +#define MAC_CTRL_SPEED_10_100 1 +#define MAC_CTRL_SPEED_1000 2 +#define MAC_CTRL_TX_SIMURST BIT(19) +#define MAC_CTRL_SCNT BIT(17) +#define MAC_CTRL_TX_PAUSE BIT(16) +#define MAC_CTRL_PROMIS_EN BIT(15) +#define MAC_CTRL_RMV_VLAN BIT(14) +#define MAC_CTRL_PRMLEN_MASK 0xFUL +#define MAC_CTRL_PRMLEN_SHIFT 10 +#define MAC_CTRL_HUGE_EN BIT(9) +#define MAC_CTRL_LENCHK BIT(8) +#define MAC_CTRL_PAD BIT(7) +#define MAC_CTRL_ADD_CRC BIT(6) +#define MAC_CTRL_DUPLX BIT(5) +#define MAC_CTRL_LOOPBACK BIT(4) +#define MAC_CTRL_RX_FLOW BIT(3) +#define MAC_CTRL_TX_FLOW BIT(2) +#define MAC_CTRL_RX_EN BIT(1) +#define MAC_CTRL_TX_EN BIT(0) + +/* MAC IPG/IFG Control Register */ +#define REG_MAC_IPG_IFG 0x1484 +#define MAC_IPG_IFG_IPGT_SHIFT 0 /* Desired back to back + * inter-packet gap. The + * default is 96-bit time */ +#define MAC_IPG_IFG_IPGT_MASK 0x7f +#define MAC_IPG_IFG_MIFG_SHIFT 8 /* Minimum number of IFG to + * enforce in between RX frames */ +#define MAC_IPG_IFG_MIFG_MASK 0xff /* Frame gap below such IFP is dropped */ +#define MAC_IPG_IFG_IPGR1_SHIFT 16 /* 64bit Carrier-Sense window */ +#define MAC_IPG_IFG_IPGR1_MASK 0x7f +#define MAC_IPG_IFG_IPGR2_SHIFT 24 /* 96-bit IPG window */ +#define MAC_IPG_IFG_IPGR2_MASK 0x7f + +/* MAC STATION ADDRESS */ +#define REG_MAC_STA_ADDR 0x1488 + +/* Hash table for multicast address */ +#define REG_RX_HASH_TABLE 0x1490 + +/* MAC Half-Duplex Control Register */ +#define REG_MAC_HALF_DUPLX_CTRL 0x1498 +#define MAC_HALF_DUPLX_CTRL_LCOL_SHIFT 0 /* Collision Window */ +#define MAC_HALF_DUPLX_CTRL_LCOL_MASK 0x3ff +#define MAC_HALF_DUPLX_CTRL_RETRY_SHIFT 12 +#define MAC_HALF_DUPLX_CTRL_RETRY_MASK 0xf +#define MAC_HALF_DUPLX_CTRL_EXC_DEF_EN 0x10000 +#define MAC_HALF_DUPLX_CTRL_NO_BACK_C 0x20000 +#define MAC_HALF_DUPLX_CTRL_NO_BACK_P 0x40000 /* No back-off on backpressure, + * immediately start the + * transmission after back pressure */ +#define MAC_HALF_DUPLX_CTRL_ABEBE 0x80000 /* 1: Alternative Binary Exponential Back-off Enabled */ +#define MAC_HALF_DUPLX_CTRL_ABEBT_SHIFT 20 /* Maximum binary exponential number */ +#define MAC_HALF_DUPLX_CTRL_ABEBT_MASK 0xf +#define MAC_HALF_DUPLX_CTRL_JAMIPG_SHIFT 24 /* IPG to start JAM for collision based flow control in half-duplex */ +#define MAC_HALF_DUPLX_CTRL_JAMIPG_MASK 0xf /* mode. In unit of 8-bit time */ + +/* Maximum Frame Length Control Register */ +#define REG_MTU 0x149c + +/* Wake-On-Lan control register */ +#define REG_WOL_CTRL 0x14a0 +#define WOL_PT7_MATCH BIT(31) +#define WOL_PT6_MATCH BIT(30) +#define WOL_PT5_MATCH BIT(29) +#define WOL_PT4_MATCH BIT(28) +#define WOL_PT3_MATCH BIT(27) +#define WOL_PT2_MATCH BIT(26) +#define WOL_PT1_MATCH BIT(25) +#define WOL_PT0_MATCH BIT(24) +#define WOL_PT7_EN BIT(23) +#define WOL_PT6_EN BIT(22) +#define WOL_PT5_EN BIT(21) +#define WOL_PT4_EN BIT(20) +#define WOL_PT3_EN BIT(19) +#define WOL_PT2_EN BIT(18) +#define WOL_PT1_EN BIT(17) +#define WOL_PT0_EN BIT(16) +#define WOL_LNKCHG_ST BIT(10) +#define WOL_MAGIC_ST BIT(9) +#define WOL_PATTERN_ST BIT(8) +#define WOL_OOB_EN BIT(6) +#define WOL_LINK_CHG_PME_EN BIT(5) +#define WOL_LINK_CHG_EN BIT(4) +#define WOL_MAGIC_PME_EN BIT(3) +#define WOL_MAGIC_EN BIT(2) +#define WOL_PATTERN_PME_EN BIT(1) +#define WOL_PATTERN_EN BIT(0) + +/* WOL Length ( 2 DWORD ) */ +#define REG_WOL_PTLEN1 0x14A4 +#define WOL_PTLEN1_3_MASK 0xFFUL +#define WOL_PTLEN1_3_SHIFT 24 +#define WOL_PTLEN1_2_MASK 0xFFUL +#define WOL_PTLEN1_2_SHIFT 16 +#define WOL_PTLEN1_1_MASK 0xFFUL +#define WOL_PTLEN1_1_SHIFT 8 +#define WOL_PTLEN1_0_MASK 0xFFUL +#define WOL_PTLEN1_0_SHIFT 0 + +#define REG_WOL_PTLEN2 0x14A8 +#define WOL_PTLEN2_7_MASK 0xFFUL +#define WOL_PTLEN2_7_SHIFT 24 +#define WOL_PTLEN2_6_MASK 0xFFUL +#define WOL_PTLEN2_6_SHIFT 16 +#define WOL_PTLEN2_5_MASK 0xFFUL +#define WOL_PTLEN2_5_SHIFT 8 +#define WOL_PTLEN2_4_MASK 0xFFUL +#define WOL_PTLEN2_4_SHIFT 0 + +/* Internal SRAM Partition Register */ +#define RFDX_HEAD_ADDR_MASK 0x03FF +#define RFDX_HARD_ADDR_SHIFT 0 +#define RFDX_TAIL_ADDR_MASK 0x03FF +#define RFDX_TAIL_ADDR_SHIFT 16 + +#define REG_SRAM_RFD0_INFO 0x1500 +#define REG_SRAM_RFD1_INFO 0x1504 +#define REG_SRAM_RFD2_INFO 0x1508 +#define REG_SRAM_RFD3_INFO 0x150C + +#define REG_RFD_NIC_LEN 0x1510 /* In 8-bytes */ +#define RFD_NIC_LEN_MASK 0x03FF + +#define REG_SRAM_TRD_ADDR 0x1518 +#define TPD_HEAD_ADDR_MASK 0x03FF +#define TPD_HEAD_ADDR_SHIFT 0 +#define TPD_TAIL_ADDR_MASK 0x03FF +#define TPD_TAIL_ADDR_SHIFT 16 + +#define REG_SRAM_TRD_LEN 0x151C /* In 8-bytes */ +#define TPD_NIC_LEN_MASK 0x03FF + +#define REG_SRAM_RXF_ADDR 0x1520 +#define REG_SRAM_RXF_LEN 0x1524 +#define REG_SRAM_TXF_ADDR 0x1528 +#define REG_SRAM_TXF_LEN 0x152C +#define REG_SRAM_TCPH_ADDR 0x1530 +#define REG_SRAM_PKTH_ADDR 0x1532 + +/* + * Load Ptr Register + * Software sets this bit after the initialization of the head and tail */ +#define REG_LOAD_PTR 0x1534 + +/* + * addresses of all descriptors, as well as the following descriptor + * control register, which triggers each function block to load the head + * pointer to prepare for the operation. This bit is then self-cleared + * after one cycle. + */ +#define REG_RX_BASE_ADDR_HI 0x1540 +#define REG_TX_BASE_ADDR_HI 0x1544 +#define REG_RFD0_HEAD_ADDR_LO 0x1550 +#define REG_RFD_RING_SIZE 0x1560 +#define RFD_RING_SIZE_MASK 0x0FFF +#define REG_RX_BUF_SIZE 0x1564 +#define RX_BUF_SIZE_MASK 0xFFFF +#define REG_RRD0_HEAD_ADDR_LO 0x1568 +#define REG_RRD_RING_SIZE 0x1578 +#define RRD_RING_SIZE_MASK 0x0FFF +#define REG_TPD_PRI1_ADDR_LO 0x157C +#define REG_TPD_PRI0_ADDR_LO 0x1580 +#define REG_TPD_RING_SIZE 0x1584 +#define TPD_RING_SIZE_MASK 0xFFFF + +/* TXQ Control Register */ +#define REG_TXQ_CTRL 0x1590 +#define TXQ_TXF_BURST_NUM_MASK 0xFFFFUL +#define TXQ_TXF_BURST_NUM_SHIFT 16 +#define L1C_TXQ_TXF_BURST_PREF 0x200 +#define L2CB_TXQ_TXF_BURST_PREF 0x40 +#define TXQ_CTRL_PEDING_CLR BIT(8) +#define TXQ_CTRL_LS_8023_EN BIT(7) +#define TXQ_CTRL_ENH_MODE BIT(6) +#define TXQ_CTRL_EN BIT(5) +#define TXQ_CTRL_IP_OPTION_EN BIT(4) +#define TXQ_NUM_TPD_BURST_MASK 0xFUL +#define TXQ_NUM_TPD_BURST_SHIFT 0 +#define TXQ_NUM_TPD_BURST_DEF 5 +#define TXQ_CFGV (\ + FIELDX(TXQ_NUM_TPD_BURST, TXQ_NUM_TPD_BURST_DEF) |\ + TXQ_CTRL_ENH_MODE |\ + TXQ_CTRL_LS_8023_EN |\ + TXQ_CTRL_IP_OPTION_EN) +#define L1C_TXQ_CFGV (\ + TXQ_CFGV |\ + FIELDX(TXQ_TXF_BURST_NUM, L1C_TXQ_TXF_BURST_PREF)) +#define L2CB_TXQ_CFGV (\ + TXQ_CFGV |\ + FIELDX(TXQ_TXF_BURST_NUM, L2CB_TXQ_TXF_BURST_PREF)) + + +/* Jumbo packet Threshold for task offload */ +#define REG_TX_TSO_OFFLOAD_THRESH 0x1594 /* In 8-bytes */ +#define TX_TSO_OFFLOAD_THRESH_MASK 0x07FF +#define MAX_TSO_FRAME_SIZE (7*1024) + +#define REG_TXF_WATER_MARK 0x1598 /* In 8-bytes */ +#define TXF_WATER_MARK_MASK 0x0FFF +#define TXF_LOW_WATER_MARK_SHIFT 0 +#define TXF_HIGH_WATER_MARK_SHIFT 16 +#define TXQ_CTRL_BURST_MODE_EN 0x80000000 + +#define REG_THRUPUT_MON_CTRL 0x159C +#define THRUPUT_MON_RATE_MASK 0x3 +#define THRUPUT_MON_RATE_SHIFT 0 +#define THRUPUT_MON_EN 0x80 + +/* RXQ Control Register */ +#define REG_RXQ_CTRL 0x15A0 +#define ASPM_THRUPUT_LIMIT_MASK 0x3 +#define ASPM_THRUPUT_LIMIT_SHIFT 0 +#define ASPM_THRUPUT_LIMIT_NO 0x00 +#define ASPM_THRUPUT_LIMIT_1M 0x01 +#define ASPM_THRUPUT_LIMIT_10M 0x02 +#define ASPM_THRUPUT_LIMIT_100M 0x03 +#define IPV6_CHKSUM_CTRL_EN BIT(7) +#define RXQ_RFD_BURST_NUM_MASK 0x003F +#define RXQ_RFD_BURST_NUM_SHIFT 20 +#define RXQ_NUM_RFD_PREF_DEF 8 +#define RSS_MODE_MASK 3UL +#define RSS_MODE_SHIFT 26 +#define RSS_MODE_DIS 0 +#define RSS_MODE_SQSI 1 +#define RSS_MODE_MQSI 2 +#define RSS_MODE_MQMI 3 +#define RSS_NIP_QUEUE_SEL BIT(28) /* 0:q0, 1:table */ +#define RRS_HASH_CTRL_EN BIT(29) +#define RX_CUT_THRU_EN BIT(30) +#define RXQ_CTRL_EN BIT(31) + +#define REG_RFD_FREE_THRESH 0x15A4 +#define RFD_FREE_THRESH_MASK 0x003F +#define RFD_FREE_HI_THRESH_SHIFT 0 +#define RFD_FREE_LO_THRESH_SHIFT 6 + +/* RXF flow control register */ +#define REG_RXQ_RXF_PAUSE_THRESH 0x15A8 +#define RXQ_RXF_PAUSE_TH_HI_SHIFT 0 +#define RXQ_RXF_PAUSE_TH_HI_MASK 0x0FFF +#define RXQ_RXF_PAUSE_TH_LO_SHIFT 16 +#define RXQ_RXF_PAUSE_TH_LO_MASK 0x0FFF + +#define REG_RXD_DMA_CTRL 0x15AC +#define RXD_DMA_THRESH_MASK 0x0FFF /* In 8-bytes */ +#define RXD_DMA_THRESH_SHIFT 0 +#define RXD_DMA_DOWN_TIMER_MASK 0xFFFF +#define RXD_DMA_DOWN_TIMER_SHIFT 16 + +/* DMA Engine Control Register */ +#define REG_DMA_CTRL 0x15C0 +#define DMA_CTRL_SMB_NOW BIT(31) +#define DMA_CTRL_WPEND_CLR BIT(30) +#define DMA_CTRL_RPEND_CLR BIT(29) +#define DMA_CTRL_WDLY_CNT_MASK 0xFUL +#define DMA_CTRL_WDLY_CNT_SHIFT 16 +#define DMA_CTRL_WDLY_CNT_DEF 4 +#define DMA_CTRL_RDLY_CNT_MASK 0x1FUL +#define DMA_CTRL_RDLY_CNT_SHIFT 11 +#define DMA_CTRL_RDLY_CNT_DEF 15 +#define DMA_CTRL_RREQ_PRI_DATA BIT(10) /* 0:tpd, 1:data */ +#define DMA_CTRL_WREQ_BLEN_MASK 7UL +#define DMA_CTRL_WREQ_BLEN_SHIFT 7 +#define DMA_CTRL_RREQ_BLEN_MASK 7UL +#define DMA_CTRL_RREQ_BLEN_SHIFT 4 +#define L1C_CTRL_DMA_RCB_LEN128 BIT(3) /* 0:64bytes,1:128bytes */ +#define DMA_CTRL_RORDER_MODE_MASK 7UL +#define DMA_CTRL_RORDER_MODE_SHIFT 0 +#define DMA_CTRL_RORDER_MODE_OUT 4 +#define DMA_CTRL_RORDER_MODE_ENHANCE 2 +#define DMA_CTRL_RORDER_MODE_IN 1 + +/* INT-triggle/SMB Control Register */ +#define REG_SMB_STAT_TIMER 0x15C4 /* 2us resolution */ +#define SMB_STAT_TIMER_MASK 0xFFFFFF +#define REG_TINT_TPD_THRESH 0x15C8 /* tpd th to trig intrrupt */ + +/* Mail box */ +#define MB_RFDX_PROD_IDX_MASK 0xFFFF +#define REG_MB_RFD0_PROD_IDX 0x15E0 + +#define REG_TPD_PRI1_PIDX 0x15F0 /* 16bit,hi-tpd producer idx */ +#define REG_TPD_PRI0_PIDX 0x15F2 /* 16bit,lo-tpd producer idx */ +#define REG_TPD_PRI1_CIDX 0x15F4 /* 16bit,hi-tpd consumer idx */ +#define REG_TPD_PRI0_CIDX 0x15F6 /* 16bit,lo-tpd consumer idx */ + +#define REG_MB_RFD01_CONS_IDX 0x15F8 +#define MB_RFD0_CONS_IDX_MASK 0x0000FFFF +#define MB_RFD1_CONS_IDX_MASK 0xFFFF0000 + +/* Interrupt Status Register */ +#define REG_ISR 0x1600 +#define ISR_SMB 0x00000001 +#define ISR_TIMER 0x00000002 +/* + * Software manual interrupt, for debug. Set when SW_MAN_INT_EN is set + * in Table 51 Selene Master Control Register (Offset 0x1400). + */ +#define ISR_MANUAL 0x00000004 +#define ISR_HW_RXF_OV 0x00000008 /* RXF overflow interrupt */ +#define ISR_RFD0_UR 0x00000010 /* RFD0 under run */ +#define ISR_RFD1_UR 0x00000020 +#define ISR_RFD2_UR 0x00000040 +#define ISR_RFD3_UR 0x00000080 +#define ISR_TXF_UR 0x00000100 +#define ISR_DMAR_TO_RST 0x00000200 +#define ISR_DMAW_TO_RST 0x00000400 +#define ISR_TX_CREDIT 0x00000800 +#define ISR_GPHY 0x00001000 +/* GPHY low power state interrupt */ +#define ISR_GPHY_LPW 0x00002000 +#define ISR_TXQ_TO_RST 0x00004000 +#define ISR_TX_PKT 0x00008000 +#define ISR_RX_PKT_0 0x00010000 +#define ISR_RX_PKT_1 0x00020000 +#define ISR_RX_PKT_2 0x00040000 +#define ISR_RX_PKT_3 0x00080000 +#define ISR_MAC_RX 0x00100000 +#define ISR_MAC_TX 0x00200000 +#define ISR_UR_DETECTED 0x00400000 +#define ISR_FERR_DETECTED 0x00800000 +#define ISR_NFERR_DETECTED 0x01000000 +#define ISR_CERR_DETECTED 0x02000000 +#define ISR_PHY_LINKDOWN 0x04000000 +#define ISR_DIS_INT 0x80000000 + +/* Interrupt Mask Register */ +#define REG_IMR 0x1604 + +#define IMR_NORMAL_MASK (\ + ISR_MANUAL |\ + ISR_HW_RXF_OV |\ + ISR_RFD0_UR |\ + ISR_TXF_UR |\ + ISR_DMAR_TO_RST |\ + ISR_TXQ_TO_RST |\ + ISR_DMAW_TO_RST |\ + ISR_GPHY |\ + ISR_TX_PKT |\ + ISR_RX_PKT_0 |\ + ISR_GPHY_LPW |\ + ISR_PHY_LINKDOWN) + +#define ISR_RX_PKT (\ + ISR_RX_PKT_0 |\ + ISR_RX_PKT_1 |\ + ISR_RX_PKT_2 |\ + ISR_RX_PKT_3) + +#define ISR_OVER (\ + ISR_RFD0_UR |\ + ISR_RFD1_UR |\ + ISR_RFD2_UR |\ + ISR_RFD3_UR |\ + ISR_HW_RXF_OV |\ + ISR_TXF_UR) + +#define ISR_ERROR (\ + ISR_DMAR_TO_RST |\ + ISR_TXQ_TO_RST |\ + ISR_DMAW_TO_RST |\ + ISR_PHY_LINKDOWN) + +#define REG_INT_RETRIG_TIMER 0x1608 +#define INT_RETRIG_TIMER_MASK 0xFFFF + +#define REG_MAC_RX_STATUS_BIN 0x1700 +#define REG_MAC_RX_STATUS_END 0x175c +#define REG_MAC_TX_STATUS_BIN 0x1760 +#define REG_MAC_TX_STATUS_END 0x17c0 + +#define REG_CLK_GATING_CTRL 0x1814 +#define CLK_GATING_DMAW_EN 0x0001 +#define CLK_GATING_DMAR_EN 0x0002 +#define CLK_GATING_TXQ_EN 0x0004 +#define CLK_GATING_RXQ_EN 0x0008 +#define CLK_GATING_TXMAC_EN 0x0010 +#define CLK_GATING_RXMAC_EN 0x0020 + +#define CLK_GATING_EN_ALL (CLK_GATING_DMAW_EN |\ + CLK_GATING_DMAR_EN |\ + CLK_GATING_TXQ_EN |\ + CLK_GATING_RXQ_EN |\ + CLK_GATING_TXMAC_EN|\ + CLK_GATING_RXMAC_EN) + +/* DEBUG ADDR */ +#define REG_DEBUG_DATA0 0x1900 +#define REG_DEBUG_DATA1 0x1904 + +#define L1D_MPW_PHYID1 0xD01C /* V7 */ +#define L1D_MPW_PHYID2 0xD01D /* V1-V6 */ +#define L1D_MPW_PHYID3 0xD01E /* V8 */ + + +/* Autoneg Advertisement Register */ +#define ADVERTISE_DEFAULT_CAP \ + (ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM) + +/* 1000BASE-T Control Register */ +#define GIGA_CR_1000T_REPEATER_DTE 0x0400 /* 1=Repeater/switch device port 0=DTE device */ + +#define GIGA_CR_1000T_MS_VALUE 0x0800 /* 1=Configure PHY as Master 0=Configure PHY as Slave */ +#define GIGA_CR_1000T_MS_ENABLE 0x1000 /* 1=Master/Slave manual config value 0=Automatic Master/Slave config */ +#define GIGA_CR_1000T_TEST_MODE_NORMAL 0x0000 /* Normal Operation */ +#define GIGA_CR_1000T_TEST_MODE_1 0x2000 /* Transmit Waveform test */ +#define GIGA_CR_1000T_TEST_MODE_2 0x4000 /* Master Transmit Jitter test */ +#define GIGA_CR_1000T_TEST_MODE_3 0x6000 /* Slave Transmit Jitter test */ +#define GIGA_CR_1000T_TEST_MODE_4 0x8000 /* Transmitter Distortion test */ +#define GIGA_CR_1000T_SPEED_MASK 0x0300 +#define GIGA_CR_1000T_DEFAULT_CAP 0x0300 + +/* PHY Specific Status Register */ +#define MII_GIGA_PSSR 0x11 +#define GIGA_PSSR_SPD_DPLX_RESOLVED 0x0800 /* 1=Speed & Duplex resolved */ +#define GIGA_PSSR_DPLX 0x2000 /* 1=Duplex 0=Half Duplex */ +#define GIGA_PSSR_SPEED 0xC000 /* Speed, bits 14:15 */ +#define GIGA_PSSR_10MBS 0x0000 /* 00=10Mbs */ +#define GIGA_PSSR_100MBS 0x4000 /* 01=100Mbs */ +#define GIGA_PSSR_1000MBS 0x8000 /* 10=1000Mbs */ + +/* PHY Interrupt Enable Register */ +#define MII_IER 0x12 +#define IER_LINK_UP 0x0400 +#define IER_LINK_DOWN 0x0800 + +/* PHY Interrupt Status Register */ +#define MII_ISR 0x13 +#define ISR_LINK_UP 0x0400 +#define ISR_LINK_DOWN 0x0800 + +/* Cable-Detect-Test Control Register */ +#define MII_CDTC 0x16 +#define CDTC_EN_OFF 0 /* sc */ +#define CDTC_EN_BITS 1 +#define CDTC_PAIR_OFF 8 +#define CDTC_PAIR_BIT 2 + +/* Cable-Detect-Test Status Register */ +#define MII_CDTS 0x1C +#define CDTS_STATUS_OFF 8 +#define CDTS_STATUS_BITS 2 +#define CDTS_STATUS_NORMAL 0 +#define CDTS_STATUS_SHORT 1 +#define CDTS_STATUS_OPEN 2 +#define CDTS_STATUS_INVALID 3 + +#define MII_DBG_ADDR 0x1D +#define MII_DBG_DATA 0x1E + +/***************************** debug port *************************************/ + +#define MIIDBG_ANACTRL 0x00 +#define ANACTRL_CLK125M_DELAY_EN 0x8000 +#define ANACTRL_VCO_FAST 0x4000 +#define ANACTRL_VCO_SLOW 0x2000 +#define ANACTRL_AFE_MODE_EN 0x1000 +#define ANACTRL_LCKDET_PHY 0x800 +#define ANACTRL_LCKDET_EN 0x400 +#define ANACTRL_OEN_125M 0x200 +#define ANACTRL_HBIAS_EN 0x100 +#define ANACTRL_HB_EN 0x80 +#define ANACTRL_SEL_HSP 0x40 +#define ANACTRL_CLASSA_EN 0x20 +#define ANACTRL_MANUSWON_SWR_MASK 3U +#define ANACTRL_MANUSWON_SWR_SHIFT 2 +#define ANACTRL_MANUSWON_SWR_2V 0 +#define ANACTRL_MANUSWON_SWR_1P9V 1 +#define ANACTRL_MANUSWON_SWR_1P8V 2 +#define ANACTRL_MANUSWON_SWR_1P7V 3 +#define ANACTRL_MANUSWON_BW3_4M 0x2 +#define ANACTRL_RESTART_CAL 0x1 +#define ANACTRL_DEF 0x02EF + +#define MIIDBG_SYSMODCTRL 0x04 +#define SYSMODCTRL_IECHOADJ_PFMH_PHY 0x8000 +#define SYSMODCTRL_IECHOADJ_BIASGEN 0x4000 +#define SYSMODCTRL_IECHOADJ_PFML_PHY 0x2000 +#define SYSMODCTRL_IECHOADJ_PS_MASK 3U +#define SYSMODCTRL_IECHOADJ_PS_SHIFT 10 +#define SYSMODCTRL_IECHOADJ_PS_40 3 +#define SYSMODCTRL_IECHOADJ_PS_20 2 +#define SYSMODCTRL_IECHOADJ_PS_0 1 +#define SYSMODCTRL_IECHOADJ_10BT_100MV 0x40 /* 1:100mv, 0:200mv */ +#define SYSMODCTRL_IECHOADJ_HLFAP_MASK 3U +#define SYSMODCTRL_IECHOADJ_HLFAP_SHIFT 4 +#define SYSMODCTRL_IECHOADJ_VDFULBW 0x8 +#define SYSMODCTRL_IECHOADJ_VDBIASHLF 0x4 +#define SYSMODCTRL_IECHOADJ_VDAMPHLF 0x2 +#define SYSMODCTRL_IECHOADJ_VDLANSW 0x1 +#define SYSMODCTRL_IECHOADJ_DEF 0x88BB /* ???? */ + +/* for l1d & l2cb */ +#define SYSMODCTRL_IECHOADJ_CUR_ADD 0x8000 +#define SYSMODCTRL_IECHOADJ_CUR_MASK 7U +#define SYSMODCTRL_IECHOADJ_CUR_SHIFT 12 +#define SYSMODCTRL_IECHOADJ_VOL_MASK 0xFU +#define SYSMODCTRL_IECHOADJ_VOL_SHIFT 8 +#define SYSMODCTRL_IECHOADJ_VOL_17ALL 3 +#define SYSMODCTRL_IECHOADJ_VOL_100M15 1 +#define SYSMODCTRL_IECHOADJ_VOL_10M17 0 +#define SYSMODCTRL_IECHOADJ_BIAS1_MASK 0xFU +#define SYSMODCTRL_IECHOADJ_BIAS1_SHIFT 4 +#define SYSMODCTRL_IECHOADJ_BIAS2_MASK 0xFU +#define SYSMODCTRL_IECHOADJ_BIAS2_SHIFT 0 +#define L1D_SYSMODCTRL_IECHOADJ_DEF 0x4FBB + +#define MIIDBG_SRDSYSMOD 0x05 +#define SRDSYSMOD_LCKDET_EN 0x2000 +#define SRDSYSMOD_PLL_EN 0x800 +#define SRDSYSMOD_SEL_HSP 0x400 +#define SRDSYSMOD_HLFTXDR 0x200 +#define SRDSYSMOD_TXCLK_DELAY_EN 0x100 +#define SRDSYSMOD_TXELECIDLE 0x80 +#define SRDSYSMOD_DEEMP_EN 0x40 +#define SRDSYSMOD_MS_PAD 0x4 +#define SRDSYSMOD_CDR_ADC_VLTG 0x2 +#define SRDSYSMOD_CDR_DAC_1MA 0x1 +#define SRDSYSMOD_DEF 0x2C46 + +#define MIIDBG_CFGLPSPD 0x0A +#define CFGLPSPD_RSTCNT_MASK 3U +#define CFGLPSPD_RSTCNT_SHIFT 14 +#define CFGLPSPD_RSTCNT_CLK125SW 0x2000 + +#define MIIDBG_HIBNEG 0x0B +#define HIBNEG_PSHIB_EN 0x8000 +#define HIBNEG_WAKE_BOTH 0x4000 +#define HIBNEG_ONOFF_ANACHG_SUDEN 0x2000 +#define HIBNEG_HIB_PULSE 0x1000 +#define HIBNEG_GATE_25M_EN 0x800 +#define HIBNEG_RST_80U 0x400 +#define HIBNEG_RST_TIMER_MASK 3U +#define HIBNEG_RST_TIMER_SHIFT 8 +#define HIBNEG_GTX_CLK_DELAY_MASK 3U +#define HIBNEG_GTX_CLK_DELAY_SHIFT 5 +#define HIBNEG_BYPSS_BRKTIMER 0x10 +#define HIBNEG_DEF 0xBC40 + +#define MIIDBG_TST10BTCFG 0x12 +#define TST10BTCFG_INTV_TIMER_MASK 3U +#define TST10BTCFG_INTV_TIMER_SHIFT 14 +#define TST10BTCFG_TRIGER_TIMER_MASK 3U +#define TST10BTCFG_TRIGER_TIMER_SHIFT 12 +#define TST10BTCFG_DIV_MAN_MLT3_EN 0x800 +#define TST10BTCFG_OFF_DAC_IDLE 0x400 +#define TST10BTCFG_LPBK_DEEP 0x4 /* 1:deep,0:shallow */ +#define TST10BTCFG_DEF 0x4C04 + +#define MIIDBG_AZ_ANADECT 0x15 +#define AZ_ANADECT_10BTRX_TH 0x8000 +#define AZ_ANADECT_BOTH_01CHNL 0x4000 +#define AZ_ANADECT_INTV_MASK 0x3FU +#define AZ_ANADECT_INTV_SHIFT 8 +#define AZ_ANADECT_THRESH_MASK 0xFU +#define AZ_ANADECT_THRESH_SHIFT 4 +#define AZ_ANADECT_CHNL_MASK 0xFU +#define AZ_ANADECT_CHNL_SHIFT 0 +#define AZ_ANADECT_DEF 0x3220 +#define AZ_ANADECT_LONG 0xb210 + +#define MIIDBG_MSE16DB 0x18 /* l1d */ +#define L1D_MSE16DB_UP 0x05EA +#define L1D_MSE16DB_DOWN 0x02EA + +#define MIIDBG_LEGCYPS 0x29 +#define LEGCYPS_EN 0x8000 +#define LEGCYPS_DAC_AMP1000_MASK 7U +#define LEGCYPS_DAC_AMP1000_SHIFT 12 +#define LEGCYPS_DAC_AMP100_MASK 7U +#define LEGCYPS_DAC_AMP100_SHIFT 9 +#define LEGCYPS_DAC_AMP10_MASK 7U +#define LEGCYPS_DAC_AMP10_SHIFT 6 +#define LEGCYPS_UNPLUG_TIMER_MASK 7U +#define LEGCYPS_UNPLUG_TIMER_SHIFT 3 +#define LEGCYPS_UNPLUG_DECT_EN 0x4 +#define LEGCYPS_ECNC_PS_EN 0x1 +#define L1D_LEGCYPS_DEF 0x129D +#define L1C_LEGCYPS_DEF 0x36DD + +#define MIIDBG_TST100BTCFG 0x36 +#define TST100BTCFG_NORMAL_BW_EN 0x8000 +#define TST100BTCFG_BADLNK_BYPASS 0x4000 +#define TST100BTCFG_SHORTCABL_TH_MASK 0x3FU +#define TST100BTCFG_SHORTCABL_TH_SHIFT 8 +#define TST100BTCFG_LITCH_EN 0x80 +#define TST100BTCFG_VLT_SW 0x40 +#define TST100BTCFG_LONGCABL_TH_MASK 0x3FU +#define TST100BTCFG_LONGCABL_TH_SHIFT 0 +#define TST100BTCFG_DEF 0xE12C + +#define MIIDBG_VOLT_CTRL 0x3B /* only for l2cb 1 & 2 */ +#define VOLT_CTRL_CABLE1TH_MASK 0x1FFU +#define VOLT_CTRL_CABLE1TH_SHIFT 7 +#define VOLT_CTRL_AMPCTRL_MASK 3U +#define VOLT_CTRL_AMPCTRL_SHIFT 5 +#define VOLT_CTRL_SW_BYPASS 0x10 +#define VOLT_CTRL_SWLOWEST 0x8 +#define VOLT_CTRL_DACAMP10_MASK 7U +#define VOLT_CTRL_DACAMP10_SHIFT 0 + +#define MIIDBG_CABLE1TH_DET 0x3E +#define CABLE1TH_DET_EN 0x8000 + + +/******* dev 3 *********/ +#define MIIEXT_PCS 3 + +#define MIIEXT_CLDCTRL3 0x8003 +#define CLDCTRL3_BP_CABLE1TH_DET_GT 0x8000 +#define CLDCTRL3_AZ_DISAMP 0x1000 +#define L2CB_CLDCTRL3 0x4D19 +#define L1D_CLDCTRL3 0xDD19 + +#define MIIEXT_CLDCTRL6 0x8006 +#define CLDCTRL6_CAB_LEN_MASK 0x1FFU +#define CLDCTRL6_CAB_LEN_SHIFT 0 +#define CLDCTRL6_CAB_LEN_SHORT 0x50 + +/********* dev 7 **********/ +#define MIIEXT_ANEG 7 + +#define MIIEXT_LOCAL_EEEADV 0x3C +#define LOCAL_EEEADV_1000BT 0x4 +#define LOCAL_EEEADV_100BT 0x2 + +#define MIIEXT_REMOTE_EEEADV 0x3D +#define REMOTE_EEEADV_1000BT 0x4 +#define REMOTE_EEEADV_100BT 0x2 + +#define MIIEXT_EEE_ANEG 0x8000 +#define EEE_ANEG_1000M 0x4 +#define EEE_ANEG_100M 0x2 + +#endif /*_ATL1C_HW_H_*/ diff --git a/drivers/net/ethernet/atheros/atl1c/atl1c_main.c b/drivers/net/ethernet/atheros/atl1c/atl1c_main.c new file mode 100644 index 0000000..1d5d9ac --- /dev/null +++ b/drivers/net/ethernet/atheros/atl1c/atl1c_main.c @@ -0,0 +1,2857 @@ +/* + * Copyright(c) 2008 - 2009 Atheros Corporation. All rights reserved. + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "atl1c.h" + +#define ATL1C_DRV_VERSION "1.0.1.1-NAPI" +char atl1c_driver_name[] = "atl1c"; +char atl1c_driver_version[] = ATL1C_DRV_VERSION; + +/* + * atl1c_pci_tbl - PCI Device ID Table + * + * Wildcard entries (PCI_ANY_ID) should come last + * Last entry must be all 0s + * + * { Vendor ID, Device ID, SubVendor ID, SubDevice ID, + * Class, Class Mask, private data (not used) } + */ +static DEFINE_PCI_DEVICE_TABLE(atl1c_pci_tbl) = { + {PCI_DEVICE(PCI_VENDOR_ID_ATTANSIC, PCI_DEVICE_ID_ATTANSIC_L1C)}, + {PCI_DEVICE(PCI_VENDOR_ID_ATTANSIC, PCI_DEVICE_ID_ATTANSIC_L2C)}, + {PCI_DEVICE(PCI_VENDOR_ID_ATTANSIC, PCI_DEVICE_ID_ATHEROS_L2C_B)}, + {PCI_DEVICE(PCI_VENDOR_ID_ATTANSIC, PCI_DEVICE_ID_ATHEROS_L2C_B2)}, + {PCI_DEVICE(PCI_VENDOR_ID_ATTANSIC, PCI_DEVICE_ID_ATHEROS_L1D)}, + {PCI_DEVICE(PCI_VENDOR_ID_ATTANSIC, PCI_DEVICE_ID_ATHEROS_L1D_2_0)}, + /* required last entry */ + { 0 } +}; +MODULE_DEVICE_TABLE(pci, atl1c_pci_tbl); + +MODULE_AUTHOR("Jie Yang"); +MODULE_AUTHOR("Qualcomm Atheros Inc., "); +MODULE_DESCRIPTION("Qualcom Atheros 100/1000M Ethernet Network Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(ATL1C_DRV_VERSION); + +static int atl1c_stop_mac(struct atl1c_hw *hw); +static void atl1c_disable_l0s_l1(struct atl1c_hw *hw); +static void atl1c_set_aspm(struct atl1c_hw *hw, u16 link_speed); +static void atl1c_start_mac(struct atl1c_adapter *adapter); +static void atl1c_clean_rx_irq(struct atl1c_adapter *adapter, + int *work_done, int work_to_do); +static int atl1c_up(struct atl1c_adapter *adapter); +static void atl1c_down(struct atl1c_adapter *adapter); +static int atl1c_reset_mac(struct atl1c_hw *hw); +static void atl1c_reset_dma_ring(struct atl1c_adapter *adapter); +static int atl1c_configure(struct atl1c_adapter *adapter); +static int atl1c_alloc_rx_buffer(struct atl1c_adapter *adapter); + +static const u16 atl1c_pay_load_size[] = { + 128, 256, 512, 1024, 2048, 4096, +}; + + +static const u32 atl1c_default_msg = NETIF_MSG_DRV | NETIF_MSG_PROBE | + NETIF_MSG_LINK | NETIF_MSG_TIMER | NETIF_MSG_IFDOWN | NETIF_MSG_IFUP; +static void atl1c_pcie_patch(struct atl1c_hw *hw) +{ + u32 mst_data, data; + + /* pclk sel could switch to 25M */ + AT_READ_REG(hw, REG_MASTER_CTRL, &mst_data); + mst_data &= ~MASTER_CTRL_CLK_SEL_DIS; + AT_WRITE_REG(hw, REG_MASTER_CTRL, mst_data); + + /* WoL/PCIE related settings */ + if (hw->nic_type == athr_l1c || hw->nic_type == athr_l2c) { + AT_READ_REG(hw, REG_PCIE_PHYMISC, &data); + data |= PCIE_PHYMISC_FORCE_RCV_DET; + AT_WRITE_REG(hw, REG_PCIE_PHYMISC, data); + } else { /* new dev set bit5 of MASTER */ + if (!(mst_data & MASTER_CTRL_WAKEN_25M)) + AT_WRITE_REG(hw, REG_MASTER_CTRL, + mst_data | MASTER_CTRL_WAKEN_25M); + } + /* aspm/PCIE setting only for l2cb 1.0 */ + if (hw->nic_type == athr_l2c_b && hw->revision_id == L2CB_V10) { + AT_READ_REG(hw, REG_PCIE_PHYMISC2, &data); + data = FIELD_SETX(data, PCIE_PHYMISC2_CDR_BW, + L2CB1_PCIE_PHYMISC2_CDR_BW); + data = FIELD_SETX(data, PCIE_PHYMISC2_L0S_TH, + L2CB1_PCIE_PHYMISC2_L0S_TH); + AT_WRITE_REG(hw, REG_PCIE_PHYMISC2, data); + /* extend L1 sync timer */ + AT_READ_REG(hw, REG_LINK_CTRL, &data); + data |= LINK_CTRL_EXT_SYNC; + AT_WRITE_REG(hw, REG_LINK_CTRL, data); + } + /* l2cb 1.x & l1d 1.x */ + if (hw->nic_type == athr_l2c_b || hw->nic_type == athr_l1d) { + AT_READ_REG(hw, REG_PM_CTRL, &data); + data |= PM_CTRL_L0S_BUFSRX_EN; + AT_WRITE_REG(hw, REG_PM_CTRL, data); + /* clear vendor msg */ + AT_READ_REG(hw, REG_DMA_DBG, &data); + AT_WRITE_REG(hw, REG_DMA_DBG, data & ~DMA_DBG_VENDOR_MSG); + } +} + +/* FIXME: no need any more ? */ +/* + * atl1c_init_pcie - init PCIE module + */ +static void atl1c_reset_pcie(struct atl1c_hw *hw, u32 flag) +{ + u32 data; + u32 pci_cmd; + struct pci_dev *pdev = hw->adapter->pdev; + int pos; + + AT_READ_REG(hw, PCI_COMMAND, &pci_cmd); + pci_cmd &= ~PCI_COMMAND_INTX_DISABLE; + pci_cmd |= (PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | + PCI_COMMAND_IO); + AT_WRITE_REG(hw, PCI_COMMAND, pci_cmd); + + /* + * Clear any PowerSaveing Settings + */ + pci_enable_wake(pdev, PCI_D3hot, 0); + pci_enable_wake(pdev, PCI_D3cold, 0); + /* wol sts read-clear */ + AT_READ_REG(hw, REG_WOL_CTRL, &data); + AT_WRITE_REG(hw, REG_WOL_CTRL, 0); + + /* + * Mask some pcie error bits + */ + pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ERR); + pci_read_config_dword(pdev, pos + PCI_ERR_UNCOR_SEVER, &data); + data &= ~(PCI_ERR_UNC_DLP | PCI_ERR_UNC_FCP); + pci_write_config_dword(pdev, pos + PCI_ERR_UNCOR_SEVER, data); + /* clear error status */ + pcie_capability_write_word(pdev, PCI_EXP_DEVSTA, + PCI_EXP_DEVSTA_NFED | + PCI_EXP_DEVSTA_FED | + PCI_EXP_DEVSTA_CED | + PCI_EXP_DEVSTA_URD); + + AT_READ_REG(hw, REG_LTSSM_ID_CTRL, &data); + data &= ~LTSSM_ID_EN_WRO; + AT_WRITE_REG(hw, REG_LTSSM_ID_CTRL, data); + + atl1c_pcie_patch(hw); + if (flag & ATL1C_PCIE_L0S_L1_DISABLE) + atl1c_disable_l0s_l1(hw); + + msleep(5); +} + +/** + * atl1c_irq_enable - Enable default interrupt generation settings + * @adapter: board private structure + */ +static inline void atl1c_irq_enable(struct atl1c_adapter *adapter) +{ + if (likely(atomic_dec_and_test(&adapter->irq_sem))) { + AT_WRITE_REG(&adapter->hw, REG_ISR, 0x7FFFFFFF); + AT_WRITE_REG(&adapter->hw, REG_IMR, adapter->hw.intr_mask); + AT_WRITE_FLUSH(&adapter->hw); + } +} + +/** + * atl1c_irq_disable - Mask off interrupt generation on the NIC + * @adapter: board private structure + */ +static inline void atl1c_irq_disable(struct atl1c_adapter *adapter) +{ + atomic_inc(&adapter->irq_sem); + AT_WRITE_REG(&adapter->hw, REG_IMR, 0); + AT_WRITE_REG(&adapter->hw, REG_ISR, ISR_DIS_INT); + AT_WRITE_FLUSH(&adapter->hw); + synchronize_irq(adapter->pdev->irq); +} + +/** + * atl1c_irq_reset - reset interrupt confiure on the NIC + * @adapter: board private structure + */ +static inline void atl1c_irq_reset(struct atl1c_adapter *adapter) +{ + atomic_set(&adapter->irq_sem, 1); + atl1c_irq_enable(adapter); +} + +/* + * atl1c_wait_until_idle - wait up to AT_HW_MAX_IDLE_DELAY reads + * of the idle status register until the device is actually idle + */ +static u32 atl1c_wait_until_idle(struct atl1c_hw *hw, u32 modu_ctrl) +{ + int timeout; + u32 data; + + for (timeout = 0; timeout < AT_HW_MAX_IDLE_DELAY; timeout++) { + AT_READ_REG(hw, REG_IDLE_STATUS, &data); + if ((data & modu_ctrl) == 0) + return 0; + msleep(1); + } + return data; +} + +/** + * atl1c_phy_config - Timer Call-back + * @data: pointer to netdev cast into an unsigned long + */ +static void atl1c_phy_config(unsigned long data) +{ + struct atl1c_adapter *adapter = (struct atl1c_adapter *) data; + struct atl1c_hw *hw = &adapter->hw; + unsigned long flags; + + spin_lock_irqsave(&adapter->mdio_lock, flags); + atl1c_restart_autoneg(hw); + spin_unlock_irqrestore(&adapter->mdio_lock, flags); +} + +void atl1c_reinit_locked(struct atl1c_adapter *adapter) +{ + WARN_ON(in_interrupt()); + atl1c_down(adapter); + atl1c_up(adapter); + clear_bit(__AT_RESETTING, &adapter->flags); +} + +static void atl1c_check_link_status(struct atl1c_adapter *adapter) +{ + struct atl1c_hw *hw = &adapter->hw; + struct net_device *netdev = adapter->netdev; + struct pci_dev *pdev = adapter->pdev; + int err; + unsigned long flags; + u16 speed, duplex, phy_data; + + spin_lock_irqsave(&adapter->mdio_lock, flags); + /* MII_BMSR must read twise */ + atl1c_read_phy_reg(hw, MII_BMSR, &phy_data); + atl1c_read_phy_reg(hw, MII_BMSR, &phy_data); + spin_unlock_irqrestore(&adapter->mdio_lock, flags); + + if ((phy_data & BMSR_LSTATUS) == 0) { + /* link down */ + netif_carrier_off(netdev); + hw->hibernate = true; + if (atl1c_reset_mac(hw) != 0) + if (netif_msg_hw(adapter)) + dev_warn(&pdev->dev, "reset mac failed\n"); + atl1c_set_aspm(hw, SPEED_0); + atl1c_post_phy_linkchg(hw, SPEED_0); + atl1c_reset_dma_ring(adapter); + atl1c_configure(adapter); + } else { + /* Link Up */ + hw->hibernate = false; + spin_lock_irqsave(&adapter->mdio_lock, flags); + err = atl1c_get_speed_and_duplex(hw, &speed, &duplex); + spin_unlock_irqrestore(&adapter->mdio_lock, flags); + if (unlikely(err)) + return; + /* link result is our setting */ + if (adapter->link_speed != speed || + adapter->link_duplex != duplex) { + adapter->link_speed = speed; + adapter->link_duplex = duplex; + atl1c_set_aspm(hw, speed); + atl1c_post_phy_linkchg(hw, speed); + atl1c_start_mac(adapter); + if (netif_msg_link(adapter)) + dev_info(&pdev->dev, + "%s: %s NIC Link is Up<%d Mbps %s>\n", + atl1c_driver_name, netdev->name, + adapter->link_speed, + adapter->link_duplex == FULL_DUPLEX ? + "Full Duplex" : "Half Duplex"); + } + if (!netif_carrier_ok(netdev)) + netif_carrier_on(netdev); + } +} + +static void atl1c_link_chg_event(struct atl1c_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + struct pci_dev *pdev = adapter->pdev; + u16 phy_data; + u16 link_up; + + spin_lock(&adapter->mdio_lock); + atl1c_read_phy_reg(&adapter->hw, MII_BMSR, &phy_data); + atl1c_read_phy_reg(&adapter->hw, MII_BMSR, &phy_data); + spin_unlock(&adapter->mdio_lock); + link_up = phy_data & BMSR_LSTATUS; + /* notify upper layer link down ASAP */ + if (!link_up) { + if (netif_carrier_ok(netdev)) { + /* old link state: Up */ + netif_carrier_off(netdev); + if (netif_msg_link(adapter)) + dev_info(&pdev->dev, + "%s: %s NIC Link is Down\n", + atl1c_driver_name, netdev->name); + adapter->link_speed = SPEED_0; + } + } + + set_bit(ATL1C_WORK_EVENT_LINK_CHANGE, &adapter->work_event); + schedule_work(&adapter->common_task); +} + +static void atl1c_common_task(struct work_struct *work) +{ + struct atl1c_adapter *adapter; + struct net_device *netdev; + + adapter = container_of(work, struct atl1c_adapter, common_task); + netdev = adapter->netdev; + + if (test_bit(__AT_DOWN, &adapter->flags)) + return; + + if (test_and_clear_bit(ATL1C_WORK_EVENT_RESET, &adapter->work_event)) { + netif_device_detach(netdev); + atl1c_down(adapter); + atl1c_up(adapter); + netif_device_attach(netdev); + } + + if (test_and_clear_bit(ATL1C_WORK_EVENT_LINK_CHANGE, + &adapter->work_event)) { + atl1c_irq_disable(adapter); + atl1c_check_link_status(adapter); + atl1c_irq_enable(adapter); + } +} + + +static void atl1c_del_timer(struct atl1c_adapter *adapter) +{ + del_timer_sync(&adapter->phy_config_timer); +} + + +/** + * atl1c_tx_timeout - Respond to a Tx Hang + * @netdev: network interface device structure + */ +static void atl1c_tx_timeout(struct net_device *netdev) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + + /* Do the reset outside of interrupt context */ + set_bit(ATL1C_WORK_EVENT_RESET, &adapter->work_event); + schedule_work(&adapter->common_task); +} + +/** + * atl1c_set_multi - Multicast and Promiscuous mode set + * @netdev: network interface device structure + * + * The set_multi entry point is called whenever the multicast address + * list or the network interface flags are updated. This routine is + * responsible for configuring the hardware for proper multicast, + * promiscuous mode, and all-multi behavior. + */ +static void atl1c_set_multi(struct net_device *netdev) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + struct atl1c_hw *hw = &adapter->hw; + struct netdev_hw_addr *ha; + u32 mac_ctrl_data; + u32 hash_value; + + /* Check for Promiscuous and All Multicast modes */ + AT_READ_REG(hw, REG_MAC_CTRL, &mac_ctrl_data); + + if (netdev->flags & IFF_PROMISC) { + mac_ctrl_data |= MAC_CTRL_PROMIS_EN; + } else if (netdev->flags & IFF_ALLMULTI) { + mac_ctrl_data |= MAC_CTRL_MC_ALL_EN; + mac_ctrl_data &= ~MAC_CTRL_PROMIS_EN; + } else { + mac_ctrl_data &= ~(MAC_CTRL_PROMIS_EN | MAC_CTRL_MC_ALL_EN); + } + + AT_WRITE_REG(hw, REG_MAC_CTRL, mac_ctrl_data); + + /* clear the old settings from the multicast hash table */ + AT_WRITE_REG(hw, REG_RX_HASH_TABLE, 0); + AT_WRITE_REG_ARRAY(hw, REG_RX_HASH_TABLE, 1, 0); + + /* comoute mc addresses' hash value ,and put it into hash table */ + netdev_for_each_mc_addr(ha, netdev) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + hash_value = atl1c_hash_mc_addr(hw, ha->addr); +#else + hash_value = atl1c_hash_mc_addr(hw, ha->dmi_addr); +#endif + atl1c_hash_set(hw, hash_value); + } +} + +static void __atl1c_vlan_mode(netdev_features_t features, u32 *mac_ctrl_data) +{ + if (features & NETIF_F_HW_VLAN_CTAG_RX) { + /* enable VLAN tag insert/strip */ + *mac_ctrl_data |= MAC_CTRL_RMV_VLAN; + } else { + /* disable VLAN tag insert/strip */ + *mac_ctrl_data &= ~MAC_CTRL_RMV_VLAN; + } +} + +static void atl1c_vlan_mode(struct net_device *netdev, + netdev_features_t features) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + struct pci_dev *pdev = adapter->pdev; + u32 mac_ctrl_data = 0; + + if (netif_msg_pktdata(adapter)) + dev_dbg(&pdev->dev, "atl1c_vlan_mode\n"); + + atl1c_irq_disable(adapter); + AT_READ_REG(&adapter->hw, REG_MAC_CTRL, &mac_ctrl_data); + __atl1c_vlan_mode(features, &mac_ctrl_data); + AT_WRITE_REG(&adapter->hw, REG_MAC_CTRL, mac_ctrl_data); + atl1c_irq_enable(adapter); +} + +static void atl1c_restore_vlan(struct atl1c_adapter *adapter) +{ + struct pci_dev *pdev = adapter->pdev; + + if (netif_msg_pktdata(adapter)) + dev_dbg(&pdev->dev, "atl1c_restore_vlan\n"); + atl1c_vlan_mode(adapter->netdev, adapter->netdev->features); +} + +/** + * atl1c_set_mac - Change the Ethernet Address of the NIC + * @netdev: network interface device structure + * @p: pointer to an address structure + * + * Returns 0 on success, negative on failure + */ +static int atl1c_set_mac_addr(struct net_device *netdev, void *p) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + struct sockaddr *addr = p; + + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + + if (netif_running(netdev)) + return -EBUSY; + + memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len); + memcpy(adapter->hw.mac_addr, addr->sa_data, netdev->addr_len); + + atl1c_hw_set_mac_addr(&adapter->hw, adapter->hw.mac_addr); + + return 0; +} + +static void atl1c_set_rxbufsize(struct atl1c_adapter *adapter, + struct net_device *dev) +{ + unsigned int head_size; + int mtu = dev->mtu; + + adapter->rx_buffer_len = mtu > AT_RX_BUF_SIZE ? + roundup(mtu + ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN, 8) : AT_RX_BUF_SIZE; + + head_size = SKB_DATA_ALIGN(adapter->rx_buffer_len + NET_SKB_PAD) + + SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); + adapter->rx_frag_size = roundup_pow_of_two(head_size); +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) +static netdev_features_t atl1c_fix_features(struct net_device *netdev, + netdev_features_t features) +{ + /* + * Since there is no support for separate rx/tx vlan accel + * enable/disable make sure tx flag is always in same state as rx. + */ + if (features & NETIF_F_HW_VLAN_CTAG_RX) + features |= NETIF_F_HW_VLAN_CTAG_TX; + else + features &= ~NETIF_F_HW_VLAN_CTAG_TX; + + if (netdev->mtu > MAX_TSO_FRAME_SIZE) + features &= ~(NETIF_F_TSO | NETIF_F_TSO6); + + return features; +} + +static int atl1c_set_features(struct net_device *netdev, + netdev_features_t features) +{ + netdev_features_t changed = netdev->features ^ features; + + if (changed & NETIF_F_HW_VLAN_CTAG_RX) + atl1c_vlan_mode(netdev, features); + + return 0; +} +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */ + +/** + * atl1c_change_mtu - Change the Maximum Transfer Unit + * @netdev: network interface device structure + * @new_mtu: new value for maximum frame size + * + * Returns 0 on success, negative on failure + */ +static int atl1c_change_mtu(struct net_device *netdev, int new_mtu) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + struct atl1c_hw *hw = &adapter->hw; + int old_mtu = netdev->mtu; + int max_frame = new_mtu + ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN; + + /* Fast Ethernet controller doesn't support jumbo packet */ + if (((hw->nic_type == athr_l2c || + hw->nic_type == athr_l2c_b || + hw->nic_type == athr_l2c_b2) && new_mtu > ETH_DATA_LEN) || + max_frame < ETH_ZLEN + ETH_FCS_LEN || + max_frame > MAX_JUMBO_FRAME_SIZE) { + if (netif_msg_link(adapter)) + dev_warn(&adapter->pdev->dev, "invalid MTU setting\n"); + return -EINVAL; + } + /* set MTU */ + if (old_mtu != new_mtu && netif_running(netdev)) { + while (test_and_set_bit(__AT_RESETTING, &adapter->flags)) + msleep(1); + netdev->mtu = new_mtu; + adapter->hw.max_frame_size = new_mtu; + atl1c_set_rxbufsize(adapter, netdev); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) + if (new_mtu > MAX_TSO_FRAME_SIZE) { + adapter->netdev->features &= ~NETIF_F_TSO; + adapter->netdev->features &= ~NETIF_F_TSO6; + } else { + adapter->netdev->features |= NETIF_F_TSO; + adapter->netdev->features |= NETIF_F_TSO6; + } +#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) */ + atl1c_down(adapter); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + netdev_update_features(netdev); +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */ + atl1c_up(adapter); + clear_bit(__AT_RESETTING, &adapter->flags); + } + return 0; +} + +/* + * caller should hold mdio_lock + */ +static int atl1c_mdio_read(struct net_device *netdev, int phy_id, int reg_num) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + u16 result; + + atl1c_read_phy_reg(&adapter->hw, reg_num, &result); + return result; +} + +static void atl1c_mdio_write(struct net_device *netdev, int phy_id, + int reg_num, int val) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + + atl1c_write_phy_reg(&adapter->hw, reg_num, val); +} + +static int atl1c_mii_ioctl(struct net_device *netdev, + struct ifreq *ifr, int cmd) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + struct pci_dev *pdev = adapter->pdev; + struct mii_ioctl_data *data = if_mii(ifr); + unsigned long flags; + int retval = 0; + + if (!netif_running(netdev)) + return -EINVAL; + + spin_lock_irqsave(&adapter->mdio_lock, flags); + switch (cmd) { + case SIOCGMIIPHY: + data->phy_id = 0; + break; + + case SIOCGMIIREG: + if (atl1c_read_phy_reg(&adapter->hw, data->reg_num & 0x1F, + &data->val_out)) { + retval = -EIO; + goto out; + } + break; + + case SIOCSMIIREG: + if (data->reg_num & ~(0x1F)) { + retval = -EFAULT; + goto out; + } + + dev_dbg(&pdev->dev, " write %x %x", + data->reg_num, data->val_in); + if (atl1c_write_phy_reg(&adapter->hw, + data->reg_num, data->val_in)) { + retval = -EIO; + goto out; + } + break; + + default: + retval = -EOPNOTSUPP; + break; + } +out: + spin_unlock_irqrestore(&adapter->mdio_lock, flags); + return retval; +} + +static int atl1c_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd) +{ + switch (cmd) { + case SIOCGMIIPHY: + case SIOCGMIIREG: + case SIOCSMIIREG: + return atl1c_mii_ioctl(netdev, ifr, cmd); + default: + return -EOPNOTSUPP; + } +} + +/** + * atl1c_alloc_queues - Allocate memory for all rings + * @adapter: board private structure to initialize + * + */ +static int atl1c_alloc_queues(struct atl1c_adapter *adapter) +{ + return 0; +} + +static void atl1c_set_mac_type(struct atl1c_hw *hw) +{ + switch (hw->device_id) { + case PCI_DEVICE_ID_ATTANSIC_L2C: + hw->nic_type = athr_l2c; + break; + case PCI_DEVICE_ID_ATTANSIC_L1C: + hw->nic_type = athr_l1c; + break; + case PCI_DEVICE_ID_ATHEROS_L2C_B: + hw->nic_type = athr_l2c_b; + break; + case PCI_DEVICE_ID_ATHEROS_L2C_B2: + hw->nic_type = athr_l2c_b2; + break; + case PCI_DEVICE_ID_ATHEROS_L1D: + hw->nic_type = athr_l1d; + break; + case PCI_DEVICE_ID_ATHEROS_L1D_2_0: + hw->nic_type = athr_l1d_2; + break; + default: + break; + } +} + +static int atl1c_setup_mac_funcs(struct atl1c_hw *hw) +{ + u32 link_ctrl_data; + + atl1c_set_mac_type(hw); + AT_READ_REG(hw, REG_LINK_CTRL, &link_ctrl_data); + + hw->ctrl_flags = ATL1C_INTR_MODRT_ENABLE | + ATL1C_TXQ_MODE_ENHANCE; + hw->ctrl_flags |= ATL1C_ASPM_L0S_SUPPORT | + ATL1C_ASPM_L1_SUPPORT; + hw->ctrl_flags |= ATL1C_ASPM_CTRL_MON; + + if (hw->nic_type == athr_l1c || + hw->nic_type == athr_l1d || + hw->nic_type == athr_l1d_2) + hw->link_cap_flags |= ATL1C_LINK_CAP_1000M; + return 0; +} + +struct atl1c_platform_patch { + u16 pci_did; + u8 pci_revid; + u16 subsystem_vid; + u16 subsystem_did; + u32 patch_flag; +#define ATL1C_LINK_PATCH 0x1 +}; +static const struct atl1c_platform_patch plats[] = { +{0x2060, 0xC1, 0x1019, 0x8152, 0x1}, +{0x2060, 0xC1, 0x1019, 0x2060, 0x1}, +{0x2060, 0xC1, 0x1019, 0xE000, 0x1}, +{0x2062, 0xC0, 0x1019, 0x8152, 0x1}, +{0x2062, 0xC0, 0x1019, 0x2062, 0x1}, +{0x2062, 0xC0, 0x1458, 0xE000, 0x1}, +{0x2062, 0xC1, 0x1019, 0x8152, 0x1}, +{0x2062, 0xC1, 0x1019, 0x2062, 0x1}, +{0x2062, 0xC1, 0x1458, 0xE000, 0x1}, +{0x2062, 0xC1, 0x1565, 0x2802, 0x1}, +{0x2062, 0xC1, 0x1565, 0x2801, 0x1}, +{0x1073, 0xC0, 0x1019, 0x8151, 0x1}, +{0x1073, 0xC0, 0x1019, 0x1073, 0x1}, +{0x1073, 0xC0, 0x1458, 0xE000, 0x1}, +{0x1083, 0xC0, 0x1458, 0xE000, 0x1}, +{0x1083, 0xC0, 0x1019, 0x8151, 0x1}, +{0x1083, 0xC0, 0x1019, 0x1083, 0x1}, +{0x1083, 0xC0, 0x1462, 0x7680, 0x1}, +{0x1083, 0xC0, 0x1565, 0x2803, 0x1}, +{0}, +}; + +static void atl1c_patch_assign(struct atl1c_hw *hw) +{ + struct pci_dev *pdev = hw->adapter->pdev; + u32 misc_ctrl; + int i = 0; + + hw->msi_lnkpatch = false; + + while (plats[i].pci_did != 0) { + if (plats[i].pci_did == hw->device_id && + plats[i].pci_revid == hw->revision_id && + plats[i].subsystem_vid == hw->subsystem_vendor_id && + plats[i].subsystem_did == hw->subsystem_id) { + if (plats[i].patch_flag & ATL1C_LINK_PATCH) + hw->msi_lnkpatch = true; + } + i++; + } + + if (hw->device_id == PCI_DEVICE_ID_ATHEROS_L2C_B2 && + hw->revision_id == L2CB_V21) { + /* config acess mode */ + pci_write_config_dword(pdev, REG_PCIE_IND_ACC_ADDR, + REG_PCIE_DEV_MISC_CTRL); + pci_read_config_dword(pdev, REG_PCIE_IND_ACC_DATA, &misc_ctrl); + misc_ctrl &= ~0x100; + pci_write_config_dword(pdev, REG_PCIE_IND_ACC_ADDR, + REG_PCIE_DEV_MISC_CTRL); + pci_write_config_dword(pdev, REG_PCIE_IND_ACC_DATA, misc_ctrl); + } +} +/** + * atl1c_sw_init - Initialize general software structures (struct atl1c_adapter) + * @adapter: board private structure to initialize + * + * atl1c_sw_init initializes the Adapter private data structure. + * Fields are initialized based on PCI device information and + * OS network device settings (MTU size). + */ +static int atl1c_sw_init(struct atl1c_adapter *adapter) +{ + struct atl1c_hw *hw = &adapter->hw; + struct pci_dev *pdev = adapter->pdev; + u32 revision; + + + adapter->wol = 0; + device_set_wakeup_enable(&pdev->dev, false); + adapter->link_speed = SPEED_0; + adapter->link_duplex = FULL_DUPLEX; + adapter->tpd_ring[0].count = 1024; + adapter->rfd_ring.count = 512; + + hw->vendor_id = pdev->vendor; + hw->device_id = pdev->device; + hw->subsystem_vendor_id = pdev->subsystem_vendor; + hw->subsystem_id = pdev->subsystem_device; + pci_read_config_dword(pdev, PCI_CLASS_REVISION, &revision); + hw->revision_id = revision & 0xFF; + /* before link up, we assume hibernate is true */ + hw->hibernate = true; + hw->media_type = MEDIA_TYPE_AUTO_SENSOR; + if (atl1c_setup_mac_funcs(hw) != 0) { + dev_err(&pdev->dev, "set mac function pointers failed\n"); + return -1; + } + atl1c_patch_assign(hw); + + hw->intr_mask = IMR_NORMAL_MASK; + hw->phy_configured = false; + hw->preamble_len = 7; + hw->max_frame_size = adapter->netdev->mtu; + hw->autoneg_advertised = ADVERTISED_Autoneg; + hw->indirect_tab = 0xE4E4E4E4; + hw->base_cpu = 0; + + hw->ict = 50000; /* 100ms */ + hw->smb_timer = 200000; /* 400ms */ + hw->rx_imt = 200; + hw->tx_imt = 1000; + + hw->tpd_burst = 5; + hw->rfd_burst = 8; + hw->dma_order = atl1c_dma_ord_out; + hw->dmar_block = atl1c_dma_req_1024; + + if (atl1c_alloc_queues(adapter)) { + dev_err(&pdev->dev, "Unable to allocate memory for queues\n"); + return -ENOMEM; + } + /* TODO */ + atl1c_set_rxbufsize(adapter, adapter->netdev); + atomic_set(&adapter->irq_sem, 1); + spin_lock_init(&adapter->mdio_lock); + spin_lock_init(&adapter->tx_lock); + set_bit(__AT_DOWN, &adapter->flags); + + return 0; +} + +static inline void atl1c_clean_buffer(struct pci_dev *pdev, + struct atl1c_buffer *buffer_info, int in_irq) +{ + u16 pci_driection; + if (buffer_info->flags & ATL1C_BUFFER_FREE) + return; + if (buffer_info->dma) { + if (buffer_info->flags & ATL1C_PCIMAP_FROMDEVICE) + pci_driection = PCI_DMA_FROMDEVICE; + else + pci_driection = PCI_DMA_TODEVICE; + + if (buffer_info->flags & ATL1C_PCIMAP_SINGLE) + pci_unmap_single(pdev, buffer_info->dma, + buffer_info->length, pci_driection); + else if (buffer_info->flags & ATL1C_PCIMAP_PAGE) + pci_unmap_page(pdev, buffer_info->dma, + buffer_info->length, pci_driection); + } + if (buffer_info->skb) { + if (in_irq) + dev_kfree_skb_irq(buffer_info->skb); + else + dev_kfree_skb(buffer_info->skb); + } + buffer_info->dma = 0; + buffer_info->skb = NULL; + ATL1C_SET_BUFFER_STATE(buffer_info, ATL1C_BUFFER_FREE); +} +/** + * atl1c_clean_tx_ring - Free Tx-skb + * @adapter: board private structure + */ +static void atl1c_clean_tx_ring(struct atl1c_adapter *adapter, + enum atl1c_trans_queue type) +{ + struct atl1c_tpd_ring *tpd_ring = &adapter->tpd_ring[type]; + struct atl1c_buffer *buffer_info; + struct pci_dev *pdev = adapter->pdev; + u16 index, ring_count; + + ring_count = tpd_ring->count; + for (index = 0; index < ring_count; index++) { + buffer_info = &tpd_ring->buffer_info[index]; + atl1c_clean_buffer(pdev, buffer_info, 0); + } + + /* Zero out Tx-buffers */ + memset(tpd_ring->desc, 0, sizeof(struct atl1c_tpd_desc) * + ring_count); + atomic_set(&tpd_ring->next_to_clean, 0); + tpd_ring->next_to_use = 0; +} + +/** + * atl1c_clean_rx_ring - Free rx-reservation skbs + * @adapter: board private structure + */ +static void atl1c_clean_rx_ring(struct atl1c_adapter *adapter) +{ + struct atl1c_rfd_ring *rfd_ring = &adapter->rfd_ring; + struct atl1c_rrd_ring *rrd_ring = &adapter->rrd_ring; + struct atl1c_buffer *buffer_info; + struct pci_dev *pdev = adapter->pdev; + int j; + + for (j = 0; j < rfd_ring->count; j++) { + buffer_info = &rfd_ring->buffer_info[j]; + atl1c_clean_buffer(pdev, buffer_info, 0); + } + /* zero out the descriptor ring */ + memset(rfd_ring->desc, 0, rfd_ring->size); + rfd_ring->next_to_clean = 0; + rfd_ring->next_to_use = 0; + rrd_ring->next_to_use = 0; + rrd_ring->next_to_clean = 0; +} + +/* + * Read / Write Ptr Initialize: + */ +static void atl1c_init_ring_ptrs(struct atl1c_adapter *adapter) +{ + struct atl1c_tpd_ring *tpd_ring = adapter->tpd_ring; + struct atl1c_rfd_ring *rfd_ring = &adapter->rfd_ring; + struct atl1c_rrd_ring *rrd_ring = &adapter->rrd_ring; + struct atl1c_buffer *buffer_info; + int i, j; + + for (i = 0; i < AT_MAX_TRANSMIT_QUEUE; i++) { + tpd_ring[i].next_to_use = 0; + atomic_set(&tpd_ring[i].next_to_clean, 0); + buffer_info = tpd_ring[i].buffer_info; + for (j = 0; j < tpd_ring->count; j++) + ATL1C_SET_BUFFER_STATE(&buffer_info[i], + ATL1C_BUFFER_FREE); + } + rfd_ring->next_to_use = 0; + rfd_ring->next_to_clean = 0; + rrd_ring->next_to_use = 0; + rrd_ring->next_to_clean = 0; + for (j = 0; j < rfd_ring->count; j++) { + buffer_info = &rfd_ring->buffer_info[j]; + ATL1C_SET_BUFFER_STATE(buffer_info, ATL1C_BUFFER_FREE); + } +} + +/** + * atl1c_free_ring_resources - Free Tx / RX descriptor Resources + * @adapter: board private structure + * + * Free all transmit software resources + */ +static void atl1c_free_ring_resources(struct atl1c_adapter *adapter) +{ + struct pci_dev *pdev = adapter->pdev; + + pci_free_consistent(pdev, adapter->ring_header.size, + adapter->ring_header.desc, + adapter->ring_header.dma); + adapter->ring_header.desc = NULL; + + /* Note: just free tdp_ring.buffer_info, + * it contain rfd_ring.buffer_info, do not double free */ + if (adapter->tpd_ring[0].buffer_info) { + kfree(adapter->tpd_ring[0].buffer_info); + adapter->tpd_ring[0].buffer_info = NULL; + } + if (adapter->rx_page) { + put_page(adapter->rx_page); + adapter->rx_page = NULL; + } +} + +/** + * atl1c_setup_mem_resources - allocate Tx / RX descriptor resources + * @adapter: board private structure + * + * Return 0 on success, negative on failure + */ +static int atl1c_setup_ring_resources(struct atl1c_adapter *adapter) +{ + struct pci_dev *pdev = adapter->pdev; + struct atl1c_tpd_ring *tpd_ring = adapter->tpd_ring; + struct atl1c_rfd_ring *rfd_ring = &adapter->rfd_ring; + struct atl1c_rrd_ring *rrd_ring = &adapter->rrd_ring; + struct atl1c_ring_header *ring_header = &adapter->ring_header; + int size; + int i; + int count = 0; + int rx_desc_count = 0; + u32 offset = 0; + + rrd_ring->count = rfd_ring->count; + for (i = 1; i < AT_MAX_TRANSMIT_QUEUE; i++) + tpd_ring[i].count = tpd_ring[0].count; + + /* 2 tpd queue, one high priority queue, + * another normal priority queue */ + size = sizeof(struct atl1c_buffer) * (tpd_ring->count * 2 + + rfd_ring->count); + tpd_ring->buffer_info = kzalloc(size, GFP_KERNEL); + if (unlikely(!tpd_ring->buffer_info)) + goto err_nomem; + + for (i = 0; i < AT_MAX_TRANSMIT_QUEUE; i++) { + tpd_ring[i].buffer_info = + (tpd_ring->buffer_info + count); + count += tpd_ring[i].count; + } + + rfd_ring->buffer_info = + (tpd_ring->buffer_info + count); + count += rfd_ring->count; + rx_desc_count += rfd_ring->count; + + /* + * real ring DMA buffer + * each ring/block may need up to 8 bytes for alignment, hence the + * additional bytes tacked onto the end. + */ + ring_header->size = size = + sizeof(struct atl1c_tpd_desc) * tpd_ring->count * 2 + + sizeof(struct atl1c_rx_free_desc) * rx_desc_count + + sizeof(struct atl1c_recv_ret_status) * rx_desc_count + + 8 * 4; + + ring_header->desc = pci_alloc_consistent(pdev, ring_header->size, + &ring_header->dma); + if (unlikely(!ring_header->desc)) { + dev_err(&pdev->dev, "pci_alloc_consistend failed\n"); + goto err_nomem; + } + memset(ring_header->desc, 0, ring_header->size); + /* init TPD ring */ + + tpd_ring[0].dma = roundup(ring_header->dma, 8); + offset = tpd_ring[0].dma - ring_header->dma; + for (i = 0; i < AT_MAX_TRANSMIT_QUEUE; i++) { + tpd_ring[i].dma = ring_header->dma + offset; + tpd_ring[i].desc = (u8 *) ring_header->desc + offset; + tpd_ring[i].size = + sizeof(struct atl1c_tpd_desc) * tpd_ring[i].count; + offset += roundup(tpd_ring[i].size, 8); + } + /* init RFD ring */ + rfd_ring->dma = ring_header->dma + offset; + rfd_ring->desc = (u8 *) ring_header->desc + offset; + rfd_ring->size = sizeof(struct atl1c_rx_free_desc) * rfd_ring->count; + offset += roundup(rfd_ring->size, 8); + + /* init RRD ring */ + rrd_ring->dma = ring_header->dma + offset; + rrd_ring->desc = (u8 *) ring_header->desc + offset; + rrd_ring->size = sizeof(struct atl1c_recv_ret_status) * + rrd_ring->count; + offset += roundup(rrd_ring->size, 8); + + return 0; + +err_nomem: + kfree(tpd_ring->buffer_info); + return -ENOMEM; +} + +static void atl1c_configure_des_ring(struct atl1c_adapter *adapter) +{ + struct atl1c_hw *hw = &adapter->hw; + struct atl1c_rfd_ring *rfd_ring = &adapter->rfd_ring; + struct atl1c_rrd_ring *rrd_ring = &adapter->rrd_ring; + struct atl1c_tpd_ring *tpd_ring = (struct atl1c_tpd_ring *) + adapter->tpd_ring; + + /* TPD */ + AT_WRITE_REG(hw, REG_TX_BASE_ADDR_HI, + (u32)((tpd_ring[atl1c_trans_normal].dma & + AT_DMA_HI_ADDR_MASK) >> 32)); + /* just enable normal priority TX queue */ + AT_WRITE_REG(hw, REG_TPD_PRI0_ADDR_LO, + (u32)(tpd_ring[atl1c_trans_normal].dma & + AT_DMA_LO_ADDR_MASK)); + AT_WRITE_REG(hw, REG_TPD_PRI1_ADDR_LO, + (u32)(tpd_ring[atl1c_trans_high].dma & + AT_DMA_LO_ADDR_MASK)); + AT_WRITE_REG(hw, REG_TPD_RING_SIZE, + (u32)(tpd_ring[0].count & TPD_RING_SIZE_MASK)); + + + /* RFD */ + AT_WRITE_REG(hw, REG_RX_BASE_ADDR_HI, + (u32)((rfd_ring->dma & AT_DMA_HI_ADDR_MASK) >> 32)); + AT_WRITE_REG(hw, REG_RFD0_HEAD_ADDR_LO, + (u32)(rfd_ring->dma & AT_DMA_LO_ADDR_MASK)); + + AT_WRITE_REG(hw, REG_RFD_RING_SIZE, + rfd_ring->count & RFD_RING_SIZE_MASK); + AT_WRITE_REG(hw, REG_RX_BUF_SIZE, + adapter->rx_buffer_len & RX_BUF_SIZE_MASK); + + /* RRD */ + AT_WRITE_REG(hw, REG_RRD0_HEAD_ADDR_LO, + (u32)(rrd_ring->dma & AT_DMA_LO_ADDR_MASK)); + AT_WRITE_REG(hw, REG_RRD_RING_SIZE, + (rrd_ring->count & RRD_RING_SIZE_MASK)); + + if (hw->nic_type == athr_l2c_b) { + AT_WRITE_REG(hw, REG_SRAM_RXF_LEN, 0x02a0L); + AT_WRITE_REG(hw, REG_SRAM_TXF_LEN, 0x0100L); + AT_WRITE_REG(hw, REG_SRAM_RXF_ADDR, 0x029f0000L); + AT_WRITE_REG(hw, REG_SRAM_RFD0_INFO, 0x02bf02a0L); + AT_WRITE_REG(hw, REG_SRAM_TXF_ADDR, 0x03bf02c0L); + AT_WRITE_REG(hw, REG_SRAM_TRD_ADDR, 0x03df03c0L); + AT_WRITE_REG(hw, REG_TXF_WATER_MARK, 0); /* TX watermark, to enter l1 state.*/ + AT_WRITE_REG(hw, REG_RXD_DMA_CTRL, 0); /* RXD threshold.*/ + } + /* Load all of base address above */ + AT_WRITE_REG(hw, REG_LOAD_PTR, 1); +} + +static void atl1c_configure_tx(struct atl1c_adapter *adapter) +{ + struct atl1c_hw *hw = &adapter->hw; + int max_pay_load; + u16 tx_offload_thresh; + u32 txq_ctrl_data; + + tx_offload_thresh = MAX_TSO_FRAME_SIZE; + AT_WRITE_REG(hw, REG_TX_TSO_OFFLOAD_THRESH, + (tx_offload_thresh >> 3) & TX_TSO_OFFLOAD_THRESH_MASK); + max_pay_load = pcie_get_readrq(adapter->pdev) >> 8; + hw->dmar_block = min_t(u32, max_pay_load, hw->dmar_block); + /* + * if BIOS had changed the dam-read-max-length to an invalid value, + * restore it to default value + */ + if (hw->dmar_block < DEVICE_CTRL_MAXRRS_MIN) { + pcie_set_readrq(adapter->pdev, 128 << DEVICE_CTRL_MAXRRS_MIN); + hw->dmar_block = DEVICE_CTRL_MAXRRS_MIN; + } + txq_ctrl_data = + hw->nic_type == athr_l2c_b || hw->nic_type == athr_l2c_b2 ? + L2CB_TXQ_CFGV : L1C_TXQ_CFGV; + + AT_WRITE_REG(hw, REG_TXQ_CTRL, txq_ctrl_data); +} + +static void atl1c_configure_rx(struct atl1c_adapter *adapter) +{ + struct atl1c_hw *hw = &adapter->hw; + u32 rxq_ctrl_data; + + rxq_ctrl_data = (hw->rfd_burst & RXQ_RFD_BURST_NUM_MASK) << + RXQ_RFD_BURST_NUM_SHIFT; + + if (hw->ctrl_flags & ATL1C_RX_IPV6_CHKSUM) + rxq_ctrl_data |= IPV6_CHKSUM_CTRL_EN; + + /* aspm for gigabit */ + if (hw->nic_type != athr_l1d_2 && (hw->device_id & 1) != 0) + rxq_ctrl_data = FIELD_SETX(rxq_ctrl_data, ASPM_THRUPUT_LIMIT, + ASPM_THRUPUT_LIMIT_100M); + + AT_WRITE_REG(hw, REG_RXQ_CTRL, rxq_ctrl_data); +} + +static void atl1c_configure_dma(struct atl1c_adapter *adapter) +{ + struct atl1c_hw *hw = &adapter->hw; + u32 dma_ctrl_data; + + dma_ctrl_data = FIELDX(DMA_CTRL_RORDER_MODE, DMA_CTRL_RORDER_MODE_OUT) | + DMA_CTRL_RREQ_PRI_DATA | + FIELDX(DMA_CTRL_RREQ_BLEN, hw->dmar_block) | + FIELDX(DMA_CTRL_WDLY_CNT, DMA_CTRL_WDLY_CNT_DEF) | + FIELDX(DMA_CTRL_RDLY_CNT, DMA_CTRL_RDLY_CNT_DEF); + + AT_WRITE_REG(hw, REG_DMA_CTRL, dma_ctrl_data); +} + +/* + * Stop the mac, transmit and receive units + * hw - Struct containing variables accessed by shared code + * return : 0 or idle status (if error) + */ +static int atl1c_stop_mac(struct atl1c_hw *hw) +{ + u32 data; + + AT_READ_REG(hw, REG_RXQ_CTRL, &data); + data &= ~RXQ_CTRL_EN; + AT_WRITE_REG(hw, REG_RXQ_CTRL, data); + + AT_READ_REG(hw, REG_TXQ_CTRL, &data); + data &= ~TXQ_CTRL_EN; + AT_WRITE_REG(hw, REG_TXQ_CTRL, data); + + atl1c_wait_until_idle(hw, IDLE_STATUS_RXQ_BUSY | IDLE_STATUS_TXQ_BUSY); + + AT_READ_REG(hw, REG_MAC_CTRL, &data); + data &= ~(MAC_CTRL_TX_EN | MAC_CTRL_RX_EN); + AT_WRITE_REG(hw, REG_MAC_CTRL, data); + + return (int)atl1c_wait_until_idle(hw, + IDLE_STATUS_TXMAC_BUSY | IDLE_STATUS_RXMAC_BUSY); +} + +static void atl1c_start_mac(struct atl1c_adapter *adapter) +{ + struct atl1c_hw *hw = &adapter->hw; + u32 mac, txq, rxq; + + hw->mac_duplex = adapter->link_duplex == FULL_DUPLEX ? true : false; + hw->mac_speed = adapter->link_speed == SPEED_1000 ? + atl1c_mac_speed_1000 : atl1c_mac_speed_10_100; + + AT_READ_REG(hw, REG_TXQ_CTRL, &txq); + AT_READ_REG(hw, REG_RXQ_CTRL, &rxq); + AT_READ_REG(hw, REG_MAC_CTRL, &mac); + + txq |= TXQ_CTRL_EN; + rxq |= RXQ_CTRL_EN; + mac |= MAC_CTRL_TX_EN | MAC_CTRL_TX_FLOW | + MAC_CTRL_RX_EN | MAC_CTRL_RX_FLOW | + MAC_CTRL_ADD_CRC | MAC_CTRL_PAD | + MAC_CTRL_BC_EN | MAC_CTRL_SINGLE_PAUSE_EN | + MAC_CTRL_HASH_ALG_CRC32; + if (hw->mac_duplex) + mac |= MAC_CTRL_DUPLX; + else + mac &= ~MAC_CTRL_DUPLX; + mac = FIELD_SETX(mac, MAC_CTRL_SPEED, hw->mac_speed); + mac = FIELD_SETX(mac, MAC_CTRL_PRMLEN, hw->preamble_len); + + AT_WRITE_REG(hw, REG_TXQ_CTRL, txq); + AT_WRITE_REG(hw, REG_RXQ_CTRL, rxq); + AT_WRITE_REG(hw, REG_MAC_CTRL, mac); +} + +/* + * Reset the transmit and receive units; mask and clear all interrupts. + * hw - Struct containing variables accessed by shared code + * return : 0 or idle status (if error) + */ +static int atl1c_reset_mac(struct atl1c_hw *hw) +{ + struct atl1c_adapter *adapter = hw->adapter; + struct pci_dev *pdev = adapter->pdev; + u32 ctrl_data = 0; + + atl1c_stop_mac(hw); + /* + * Issue Soft Reset to the MAC. This will reset the chip's + * transmit, receive, DMA. It will not effect + * the current PCI configuration. The global reset bit is self- + * clearing, and should clear within a microsecond. + */ + AT_READ_REG(hw, REG_MASTER_CTRL, &ctrl_data); + ctrl_data |= MASTER_CTRL_OOB_DIS; + AT_WRITE_REG(hw, REG_MASTER_CTRL, ctrl_data | MASTER_CTRL_SOFT_RST); + + AT_WRITE_FLUSH(hw); + msleep(10); + /* Wait at least 10ms for All module to be Idle */ + + if (atl1c_wait_until_idle(hw, IDLE_STATUS_MASK)) { + dev_err(&pdev->dev, + "MAC state machine can't be idle since" + " disabled for 10ms second\n"); + return -1; + } + AT_WRITE_REG(hw, REG_MASTER_CTRL, ctrl_data); + + /* driver control speed/duplex */ + AT_READ_REG(hw, REG_MAC_CTRL, &ctrl_data); + AT_WRITE_REG(hw, REG_MAC_CTRL, ctrl_data | MAC_CTRL_SPEED_MODE_SW); + + /* clk switch setting */ + AT_READ_REG(hw, REG_SERDES, &ctrl_data); + switch (hw->nic_type) { + case athr_l2c_b: + ctrl_data &= ~(SERDES_PHY_CLK_SLOWDOWN | + SERDES_MAC_CLK_SLOWDOWN); + AT_WRITE_REG(hw, REG_SERDES, ctrl_data); + break; + case athr_l2c_b2: + case athr_l1d_2: + ctrl_data |= SERDES_PHY_CLK_SLOWDOWN | SERDES_MAC_CLK_SLOWDOWN; + AT_WRITE_REG(hw, REG_SERDES, ctrl_data); + break; + default: + break; + } + + return 0; +} + +static void atl1c_disable_l0s_l1(struct atl1c_hw *hw) +{ + u16 ctrl_flags = hw->ctrl_flags; + + hw->ctrl_flags &= ~(ATL1C_ASPM_L0S_SUPPORT | ATL1C_ASPM_L1_SUPPORT); + atl1c_set_aspm(hw, SPEED_0); + hw->ctrl_flags = ctrl_flags; +} + +/* + * Set ASPM state. + * Enable/disable L0s/L1 depend on link state. + */ +static void atl1c_set_aspm(struct atl1c_hw *hw, u16 link_speed) +{ + u32 pm_ctrl_data; + u32 link_l1_timer; + + AT_READ_REG(hw, REG_PM_CTRL, &pm_ctrl_data); + pm_ctrl_data &= ~(PM_CTRL_ASPM_L1_EN | + PM_CTRL_ASPM_L0S_EN | + PM_CTRL_MAC_ASPM_CHK); + /* L1 timer */ + if (hw->nic_type == athr_l2c_b2 || hw->nic_type == athr_l1d_2) { + pm_ctrl_data &= ~PMCTRL_TXL1_AFTER_L0S; + link_l1_timer = + link_speed == SPEED_1000 || link_speed == SPEED_100 ? + L1D_PMCTRL_L1_ENTRY_TM_16US : 1; + pm_ctrl_data = FIELD_SETX(pm_ctrl_data, + L1D_PMCTRL_L1_ENTRY_TM, link_l1_timer); + } else { + link_l1_timer = hw->nic_type == athr_l2c_b ? + L2CB1_PM_CTRL_L1_ENTRY_TM : L1C_PM_CTRL_L1_ENTRY_TM; + if (link_speed != SPEED_1000 && link_speed != SPEED_100) + link_l1_timer = 1; + pm_ctrl_data = FIELD_SETX(pm_ctrl_data, + PM_CTRL_L1_ENTRY_TIMER, link_l1_timer); + } + + /* L0S/L1 enable */ + if ((hw->ctrl_flags & ATL1C_ASPM_L0S_SUPPORT) && link_speed != SPEED_0) + pm_ctrl_data |= PM_CTRL_ASPM_L0S_EN | PM_CTRL_MAC_ASPM_CHK; + if (hw->ctrl_flags & ATL1C_ASPM_L1_SUPPORT) + pm_ctrl_data |= PM_CTRL_ASPM_L1_EN | PM_CTRL_MAC_ASPM_CHK; + + /* l2cb & l1d & l2cb2 & l1d2 */ + if (hw->nic_type == athr_l2c_b || hw->nic_type == athr_l1d || + hw->nic_type == athr_l2c_b2 || hw->nic_type == athr_l1d_2) { + pm_ctrl_data = FIELD_SETX(pm_ctrl_data, + PM_CTRL_PM_REQ_TIMER, PM_CTRL_PM_REQ_TO_DEF); + pm_ctrl_data |= PM_CTRL_RCVR_WT_TIMER | + PM_CTRL_SERDES_PD_EX_L1 | + PM_CTRL_CLK_SWH_L1; + pm_ctrl_data &= ~(PM_CTRL_SERDES_L1_EN | + PM_CTRL_SERDES_PLL_L1_EN | + PM_CTRL_SERDES_BUFS_RX_L1_EN | + PM_CTRL_SA_DLY_EN | + PM_CTRL_HOTRST); + /* disable l0s if link down or l2cb */ + if (link_speed == SPEED_0 || hw->nic_type == athr_l2c_b) + pm_ctrl_data &= ~PM_CTRL_ASPM_L0S_EN; + } else { /* l1c */ + pm_ctrl_data = + FIELD_SETX(pm_ctrl_data, PM_CTRL_L1_ENTRY_TIMER, 0); + if (link_speed != SPEED_0) { + pm_ctrl_data |= PM_CTRL_SERDES_L1_EN | + PM_CTRL_SERDES_PLL_L1_EN | + PM_CTRL_SERDES_BUFS_RX_L1_EN; + pm_ctrl_data &= ~(PM_CTRL_SERDES_PD_EX_L1 | + PM_CTRL_CLK_SWH_L1 | + PM_CTRL_ASPM_L0S_EN | + PM_CTRL_ASPM_L1_EN); + } else { /* link down */ + pm_ctrl_data |= PM_CTRL_CLK_SWH_L1; + pm_ctrl_data &= ~(PM_CTRL_SERDES_L1_EN | + PM_CTRL_SERDES_PLL_L1_EN | + PM_CTRL_SERDES_BUFS_RX_L1_EN | + PM_CTRL_ASPM_L0S_EN); + } + } + AT_WRITE_REG(hw, REG_PM_CTRL, pm_ctrl_data); + + return; +} + +/** + * atl1c_configure - Configure Transmit&Receive Unit after Reset + * @adapter: board private structure + * + * Configure the Tx /Rx unit of the MAC after a reset. + */ +static int atl1c_configure_mac(struct atl1c_adapter *adapter) +{ + struct atl1c_hw *hw = &adapter->hw; + u32 master_ctrl_data = 0; + u32 intr_modrt_data; + u32 data; + + AT_READ_REG(hw, REG_MASTER_CTRL, &master_ctrl_data); + master_ctrl_data &= ~(MASTER_CTRL_TX_ITIMER_EN | + MASTER_CTRL_RX_ITIMER_EN | + MASTER_CTRL_INT_RDCLR); + /* clear interrupt status */ + AT_WRITE_REG(hw, REG_ISR, 0xFFFFFFFF); + /* Clear any WOL status */ + AT_WRITE_REG(hw, REG_WOL_CTRL, 0); + /* set Interrupt Clear Timer + * HW will enable self to assert interrupt event to system after + * waiting x-time for software to notify it accept interrupt. + */ + + data = CLK_GATING_EN_ALL; + if (hw->ctrl_flags & ATL1C_CLK_GATING_EN) { + if (hw->nic_type == athr_l2c_b) + data &= ~CLK_GATING_RXMAC_EN; + } else + data = 0; + AT_WRITE_REG(hw, REG_CLK_GATING_CTRL, data); + + AT_WRITE_REG(hw, REG_INT_RETRIG_TIMER, + hw->ict & INT_RETRIG_TIMER_MASK); + + atl1c_configure_des_ring(adapter); + + if (hw->ctrl_flags & ATL1C_INTR_MODRT_ENABLE) { + intr_modrt_data = (hw->tx_imt & IRQ_MODRT_TIMER_MASK) << + IRQ_MODRT_TX_TIMER_SHIFT; + intr_modrt_data |= (hw->rx_imt & IRQ_MODRT_TIMER_MASK) << + IRQ_MODRT_RX_TIMER_SHIFT; + AT_WRITE_REG(hw, REG_IRQ_MODRT_TIMER_INIT, intr_modrt_data); + master_ctrl_data |= + MASTER_CTRL_TX_ITIMER_EN | MASTER_CTRL_RX_ITIMER_EN; + } + + if (hw->ctrl_flags & ATL1C_INTR_CLEAR_ON_READ) + master_ctrl_data |= MASTER_CTRL_INT_RDCLR; + + master_ctrl_data |= MASTER_CTRL_SA_TIMER_EN; + AT_WRITE_REG(hw, REG_MASTER_CTRL, master_ctrl_data); + + AT_WRITE_REG(hw, REG_SMB_STAT_TIMER, + hw->smb_timer & SMB_STAT_TIMER_MASK); + + /* set MTU */ + AT_WRITE_REG(hw, REG_MTU, hw->max_frame_size + ETH_HLEN + + VLAN_HLEN + ETH_FCS_LEN); + + atl1c_configure_tx(adapter); + atl1c_configure_rx(adapter); + atl1c_configure_dma(adapter); + + return 0; +} + +static int atl1c_configure(struct atl1c_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + int num; + + atl1c_init_ring_ptrs(adapter); + atl1c_set_multi(netdev); + atl1c_restore_vlan(adapter); + + num = atl1c_alloc_rx_buffer(adapter); + if (unlikely(num == 0)) + return -ENOMEM; + + if (atl1c_configure_mac(adapter)) + return -EIO; + + return 0; +} + +static void atl1c_update_hw_stats(struct atl1c_adapter *adapter) +{ + u16 hw_reg_addr = 0; + unsigned long *stats_item = NULL; + u32 data; + + /* update rx status */ + hw_reg_addr = REG_MAC_RX_STATUS_BIN; + stats_item = &adapter->hw_stats.rx_ok; + while (hw_reg_addr <= REG_MAC_RX_STATUS_END) { + AT_READ_REG(&adapter->hw, hw_reg_addr, &data); + *stats_item += data; + stats_item++; + hw_reg_addr += 4; + } +/* update tx status */ + hw_reg_addr = REG_MAC_TX_STATUS_BIN; + stats_item = &adapter->hw_stats.tx_ok; + while (hw_reg_addr <= REG_MAC_TX_STATUS_END) { + AT_READ_REG(&adapter->hw, hw_reg_addr, &data); + *stats_item += data; + stats_item++; + hw_reg_addr += 4; + } +} + +/** + * atl1c_get_stats - Get System Network Statistics + * @netdev: network interface device structure + * + * Returns the address of the device statistics structure. + * The statistics are actually updated from the timer callback. + */ +static struct net_device_stats *atl1c_get_stats(struct net_device *netdev) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + struct atl1c_hw_stats *hw_stats = &adapter->hw_stats; + struct net_device_stats *net_stats = &netdev->stats; + + atl1c_update_hw_stats(adapter); + net_stats->rx_packets = hw_stats->rx_ok; + net_stats->tx_packets = hw_stats->tx_ok; + net_stats->rx_bytes = hw_stats->rx_byte_cnt; + net_stats->tx_bytes = hw_stats->tx_byte_cnt; + net_stats->multicast = hw_stats->rx_mcast; + net_stats->collisions = hw_stats->tx_1_col + + hw_stats->tx_2_col * 2 + + hw_stats->tx_late_col + hw_stats->tx_abort_col; + net_stats->rx_errors = hw_stats->rx_frag + hw_stats->rx_fcs_err + + hw_stats->rx_len_err + hw_stats->rx_sz_ov + + hw_stats->rx_rrd_ov + hw_stats->rx_align_err; + net_stats->rx_fifo_errors = hw_stats->rx_rxf_ov; + net_stats->rx_length_errors = hw_stats->rx_len_err; + net_stats->rx_crc_errors = hw_stats->rx_fcs_err; + net_stats->rx_frame_errors = hw_stats->rx_align_err; + net_stats->rx_over_errors = hw_stats->rx_rrd_ov + hw_stats->rx_rxf_ov; + + net_stats->rx_missed_errors = hw_stats->rx_rrd_ov + hw_stats->rx_rxf_ov; + + net_stats->tx_errors = hw_stats->tx_late_col + hw_stats->tx_abort_col + + hw_stats->tx_underrun + hw_stats->tx_trunc; + net_stats->tx_fifo_errors = hw_stats->tx_underrun; + net_stats->tx_aborted_errors = hw_stats->tx_abort_col; + net_stats->tx_window_errors = hw_stats->tx_late_col; + + return net_stats; +} + +static inline void atl1c_clear_phy_int(struct atl1c_adapter *adapter) +{ + u16 phy_data; + + spin_lock(&adapter->mdio_lock); + atl1c_read_phy_reg(&adapter->hw, MII_ISR, &phy_data); + spin_unlock(&adapter->mdio_lock); +} + +static bool atl1c_clean_tx_irq(struct atl1c_adapter *adapter, + enum atl1c_trans_queue type) +{ + struct atl1c_tpd_ring *tpd_ring = &adapter->tpd_ring[type]; + struct atl1c_buffer *buffer_info; + struct pci_dev *pdev = adapter->pdev; + u16 next_to_clean = atomic_read(&tpd_ring->next_to_clean); + u16 hw_next_to_clean; + u16 reg; + + reg = type == atl1c_trans_high ? REG_TPD_PRI1_CIDX : REG_TPD_PRI0_CIDX; + + AT_READ_REGW(&adapter->hw, reg, &hw_next_to_clean); + + while (next_to_clean != hw_next_to_clean) { + buffer_info = &tpd_ring->buffer_info[next_to_clean]; + atl1c_clean_buffer(pdev, buffer_info, 1); + if (++next_to_clean == tpd_ring->count) + next_to_clean = 0; + atomic_set(&tpd_ring->next_to_clean, next_to_clean); + } + + if (netif_queue_stopped(adapter->netdev) && + netif_carrier_ok(adapter->netdev)) { + netif_wake_queue(adapter->netdev); + } + + return true; +} + +/** + * atl1c_intr - Interrupt Handler + * @irq: interrupt number + * @data: pointer to a network interface device structure + */ +static irqreturn_t atl1c_intr(int irq, void *data) +{ + struct net_device *netdev = data; + struct atl1c_adapter *adapter = netdev_priv(netdev); + struct pci_dev *pdev = adapter->pdev; + struct atl1c_hw *hw = &adapter->hw; + int max_ints = AT_MAX_INT_WORK; + int handled = IRQ_NONE; + u32 status; + u32 reg_data; + + do { + AT_READ_REG(hw, REG_ISR, ®_data); + status = reg_data & hw->intr_mask; + + if (status == 0 || (status & ISR_DIS_INT) != 0) { + if (max_ints != AT_MAX_INT_WORK) + handled = IRQ_HANDLED; + break; + } + /* link event */ + if (status & ISR_GPHY) + atl1c_clear_phy_int(adapter); + /* Ack ISR */ + AT_WRITE_REG(hw, REG_ISR, status | ISR_DIS_INT); + if (status & ISR_RX_PKT) { + if (likely(napi_schedule_prep(&adapter->napi))) { + hw->intr_mask &= ~ISR_RX_PKT; + AT_WRITE_REG(hw, REG_IMR, hw->intr_mask); + __napi_schedule(&adapter->napi); + } + } + if (status & ISR_TX_PKT) + atl1c_clean_tx_irq(adapter, atl1c_trans_normal); + + handled = IRQ_HANDLED; + /* check if PCIE PHY Link down */ + if (status & ISR_ERROR) { + if (netif_msg_hw(adapter)) + dev_err(&pdev->dev, + "atl1c hardware error (status = 0x%x)\n", + status & ISR_ERROR); + /* reset MAC */ + set_bit(ATL1C_WORK_EVENT_RESET, &adapter->work_event); + schedule_work(&adapter->common_task); + return IRQ_HANDLED; + } + + if (status & ISR_OVER) + if (netif_msg_intr(adapter)) + dev_warn(&pdev->dev, + "TX/RX overflow (status = 0x%x)\n", + status & ISR_OVER); + + /* link event */ + if (status & (ISR_GPHY | ISR_MANUAL)) { + netdev->stats.tx_carrier_errors++; + atl1c_link_chg_event(adapter); + break; + } + + } while (--max_ints > 0); + /* re-enable Interrupt*/ + AT_WRITE_REG(&adapter->hw, REG_ISR, 0); + return handled; +} + +static inline void atl1c_rx_checksum(struct atl1c_adapter *adapter, + struct sk_buff *skb, struct atl1c_recv_ret_status *prrs) +{ + /* + * The pid field in RRS in not correct sometimes, so we + * cannot figure out if the packet is fragmented or not, + * so we tell the KERNEL CHECKSUM_NONE + */ + skb_checksum_none_assert(skb); +} + +static struct sk_buff *atl1c_alloc_skb(struct atl1c_adapter *adapter) +{ + struct sk_buff *skb; + struct page *page; + + if (adapter->rx_frag_size > PAGE_SIZE) + return netdev_alloc_skb(adapter->netdev, + adapter->rx_buffer_len); + + page = adapter->rx_page; + if (!page) { + adapter->rx_page = page = alloc_page(GFP_ATOMIC); + if (unlikely(!page)) + return NULL; + adapter->rx_page_offset = 0; + } + + skb = build_skb(page_address(page) + adapter->rx_page_offset, + adapter->rx_frag_size); + if (likely(skb)) { + adapter->rx_page_offset += adapter->rx_frag_size; + if (adapter->rx_page_offset >= PAGE_SIZE) + adapter->rx_page = NULL; + else + get_page(page); + } + return skb; +} + +static int atl1c_alloc_rx_buffer(struct atl1c_adapter *adapter) +{ + struct atl1c_rfd_ring *rfd_ring = &adapter->rfd_ring; + struct pci_dev *pdev = adapter->pdev; + struct atl1c_buffer *buffer_info, *next_info; + struct sk_buff *skb; + void *vir_addr = NULL; + u16 num_alloc = 0; + u16 rfd_next_to_use, next_next; + struct atl1c_rx_free_desc *rfd_desc; + dma_addr_t mapping; + + next_next = rfd_next_to_use = rfd_ring->next_to_use; + if (++next_next == rfd_ring->count) + next_next = 0; + buffer_info = &rfd_ring->buffer_info[rfd_next_to_use]; + next_info = &rfd_ring->buffer_info[next_next]; + + while (next_info->flags & ATL1C_BUFFER_FREE) { + rfd_desc = ATL1C_RFD_DESC(rfd_ring, rfd_next_to_use); + + skb = atl1c_alloc_skb(adapter); + if (unlikely(!skb)) { + if (netif_msg_rx_err(adapter)) + dev_warn(&pdev->dev, "alloc rx buffer failed\n"); + break; + } + + /* + * Make buffer alignment 2 beyond a 16 byte boundary + * this will result in a 16 byte aligned IP header after + * the 14 byte MAC header is removed + */ + vir_addr = skb->data; + ATL1C_SET_BUFFER_STATE(buffer_info, ATL1C_BUFFER_BUSY); + buffer_info->skb = skb; + buffer_info->length = adapter->rx_buffer_len; + mapping = pci_map_single(pdev, vir_addr, + buffer_info->length, + PCI_DMA_FROMDEVICE); + if (unlikely(pci_dma_mapping_error(pdev, mapping))) { + dev_kfree_skb(skb); + buffer_info->skb = NULL; + buffer_info->length = 0; + ATL1C_SET_BUFFER_STATE(buffer_info, ATL1C_BUFFER_FREE); + netif_warn(adapter, rx_err, adapter->netdev, "RX pci_map_single failed"); + break; + } + buffer_info->dma = mapping; + ATL1C_SET_PCIMAP_TYPE(buffer_info, ATL1C_PCIMAP_SINGLE, + ATL1C_PCIMAP_FROMDEVICE); + rfd_desc->buffer_addr = cpu_to_le64(buffer_info->dma); + rfd_next_to_use = next_next; + if (++next_next == rfd_ring->count) + next_next = 0; + buffer_info = &rfd_ring->buffer_info[rfd_next_to_use]; + next_info = &rfd_ring->buffer_info[next_next]; + num_alloc++; + } + + if (num_alloc) { + /* TODO: update mailbox here */ + wmb(); + rfd_ring->next_to_use = rfd_next_to_use; + AT_WRITE_REG(&adapter->hw, REG_MB_RFD0_PROD_IDX, + rfd_ring->next_to_use & MB_RFDX_PROD_IDX_MASK); + } + + return num_alloc; +} + +static void atl1c_clean_rrd(struct atl1c_rrd_ring *rrd_ring, + struct atl1c_recv_ret_status *rrs, u16 num) +{ + u16 i; + /* the relationship between rrd and rfd is one map one */ + for (i = 0; i < num; i++, rrs = ATL1C_RRD_DESC(rrd_ring, + rrd_ring->next_to_clean)) { + rrs->word3 &= ~RRS_RXD_UPDATED; + if (++rrd_ring->next_to_clean == rrd_ring->count) + rrd_ring->next_to_clean = 0; + } +} + +static void atl1c_clean_rfd(struct atl1c_rfd_ring *rfd_ring, + struct atl1c_recv_ret_status *rrs, u16 num) +{ + u16 i; + u16 rfd_index; + struct atl1c_buffer *buffer_info = rfd_ring->buffer_info; + + rfd_index = (rrs->word0 >> RRS_RX_RFD_INDEX_SHIFT) & + RRS_RX_RFD_INDEX_MASK; + for (i = 0; i < num; i++) { + buffer_info[rfd_index].skb = NULL; + ATL1C_SET_BUFFER_STATE(&buffer_info[rfd_index], + ATL1C_BUFFER_FREE); + if (++rfd_index == rfd_ring->count) + rfd_index = 0; + } + rfd_ring->next_to_clean = rfd_index; +} + +static void atl1c_clean_rx_irq(struct atl1c_adapter *adapter, + int *work_done, int work_to_do) +{ + u16 rfd_num, rfd_index; + u16 count = 0; + u16 length; + struct pci_dev *pdev = adapter->pdev; + struct net_device *netdev = adapter->netdev; + struct atl1c_rfd_ring *rfd_ring = &adapter->rfd_ring; + struct atl1c_rrd_ring *rrd_ring = &adapter->rrd_ring; + struct sk_buff *skb; + struct atl1c_recv_ret_status *rrs; + struct atl1c_buffer *buffer_info; + + while (1) { + if (*work_done >= work_to_do) + break; + rrs = ATL1C_RRD_DESC(rrd_ring, rrd_ring->next_to_clean); + if (likely(RRS_RXD_IS_VALID(rrs->word3))) { + rfd_num = (rrs->word0 >> RRS_RX_RFD_CNT_SHIFT) & + RRS_RX_RFD_CNT_MASK; + if (unlikely(rfd_num != 1)) + /* TODO support mul rfd*/ + if (netif_msg_rx_err(adapter)) + dev_warn(&pdev->dev, + "Multi rfd not support yet!\n"); + goto rrs_checked; + } else { + break; + } +rrs_checked: + atl1c_clean_rrd(rrd_ring, rrs, rfd_num); + if (rrs->word3 & (RRS_RX_ERR_SUM | RRS_802_3_LEN_ERR)) { + atl1c_clean_rfd(rfd_ring, rrs, rfd_num); + if (netif_msg_rx_err(adapter)) + dev_warn(&pdev->dev, + "wrong packet! rrs word3 is %x\n", + rrs->word3); + continue; + } + + length = le16_to_cpu((rrs->word3 >> RRS_PKT_SIZE_SHIFT) & + RRS_PKT_SIZE_MASK); + /* Good Receive */ + if (likely(rfd_num == 1)) { + rfd_index = (rrs->word0 >> RRS_RX_RFD_INDEX_SHIFT) & + RRS_RX_RFD_INDEX_MASK; + buffer_info = &rfd_ring->buffer_info[rfd_index]; + pci_unmap_single(pdev, buffer_info->dma, + buffer_info->length, PCI_DMA_FROMDEVICE); + skb = buffer_info->skb; + } else { + /* TODO */ + if (netif_msg_rx_err(adapter)) + dev_warn(&pdev->dev, + "Multi rfd not support yet!\n"); + break; + } + atl1c_clean_rfd(rfd_ring, rrs, rfd_num); + skb_put(skb, length - ETH_FCS_LEN); + skb->protocol = eth_type_trans(skb, netdev); + atl1c_rx_checksum(adapter, skb, rrs); + if (rrs->word3 & RRS_VLAN_INS) { + u16 vlan; + + AT_TAG_TO_VLAN(rrs->vlan_tag, vlan); + vlan = le16_to_cpu(vlan); + __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan); + } + netif_receive_skb(skb); + + (*work_done)++; + count++; + } + if (count) + atl1c_alloc_rx_buffer(adapter); +} + +/** + * atl1c_clean - NAPI Rx polling callback + */ +static int atl1c_clean(struct napi_struct *napi, int budget) +{ + struct atl1c_adapter *adapter = + container_of(napi, struct atl1c_adapter, napi); + int work_done = 0; + + /* Keep link state information with original netdev */ + if (!netif_carrier_ok(adapter->netdev)) + goto quit_polling; + /* just enable one RXQ */ + atl1c_clean_rx_irq(adapter, &work_done, budget); + + if (work_done < budget) { +quit_polling: + napi_complete(napi); + adapter->hw.intr_mask |= ISR_RX_PKT; + AT_WRITE_REG(&adapter->hw, REG_IMR, adapter->hw.intr_mask); + } + return work_done; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER + +/* + * Polling 'interrupt' - used by things like netconsole to send skbs + * without having to re-enable interrupts. It's not called while + * the interrupt routine is executing. + */ +static void atl1c_netpoll(struct net_device *netdev) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + + disable_irq(adapter->pdev->irq); + atl1c_intr(adapter->pdev->irq, netdev); + enable_irq(adapter->pdev->irq); +} +#endif + +static inline u16 atl1c_tpd_avail(struct atl1c_adapter *adapter, enum atl1c_trans_queue type) +{ + struct atl1c_tpd_ring *tpd_ring = &adapter->tpd_ring[type]; + u16 next_to_use = 0; + u16 next_to_clean = 0; + + next_to_clean = atomic_read(&tpd_ring->next_to_clean); + next_to_use = tpd_ring->next_to_use; + + return (u16)(next_to_clean > next_to_use) ? + (next_to_clean - next_to_use - 1) : + (tpd_ring->count + next_to_clean - next_to_use - 1); +} + +/* + * get next usable tpd + * Note: should call atl1c_tdp_avail to make sure + * there is enough tpd to use + */ +static struct atl1c_tpd_desc *atl1c_get_tpd(struct atl1c_adapter *adapter, + enum atl1c_trans_queue type) +{ + struct atl1c_tpd_ring *tpd_ring = &adapter->tpd_ring[type]; + struct atl1c_tpd_desc *tpd_desc; + u16 next_to_use = 0; + + next_to_use = tpd_ring->next_to_use; + if (++tpd_ring->next_to_use == tpd_ring->count) + tpd_ring->next_to_use = 0; + tpd_desc = ATL1C_TPD_DESC(tpd_ring, next_to_use); + memset(tpd_desc, 0, sizeof(struct atl1c_tpd_desc)); + return tpd_desc; +} + +static struct atl1c_buffer * +atl1c_get_tx_buffer(struct atl1c_adapter *adapter, struct atl1c_tpd_desc *tpd) +{ + struct atl1c_tpd_ring *tpd_ring = adapter->tpd_ring; + + return &tpd_ring->buffer_info[tpd - + (struct atl1c_tpd_desc *)tpd_ring->desc]; +} + +/* Calculate the transmit packet descript needed*/ +static u16 atl1c_cal_tpd_req(const struct sk_buff *skb) +{ + u16 tpd_req; + u16 proto_hdr_len = 0; + + tpd_req = skb_shinfo(skb)->nr_frags + 1; + + if (skb_is_gso(skb)) { + proto_hdr_len = skb_transport_offset(skb) + tcp_hdrlen(skb); + if (proto_hdr_len < skb_headlen(skb)) + tpd_req++; + if (skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6) + tpd_req++; + } + return tpd_req; +} + +static int atl1c_tso_csum(struct atl1c_adapter *adapter, + struct sk_buff *skb, + struct atl1c_tpd_desc **tpd, + enum atl1c_trans_queue type) +{ + struct pci_dev *pdev = adapter->pdev; + u8 hdr_len; + u32 real_len; + unsigned short offload_type; + int err; + + if (skb_is_gso(skb)) { + if (skb_header_cloned(skb)) { + err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC); + if (unlikely(err)) + return -1; + } + offload_type = skb_shinfo(skb)->gso_type; + + if (offload_type & SKB_GSO_TCPV4) { + real_len = (((unsigned char *)ip_hdr(skb) - skb->data) + + ntohs(ip_hdr(skb)->tot_len)); + + if (real_len < skb->len) + pskb_trim(skb, real_len); + + hdr_len = (skb_transport_offset(skb) + tcp_hdrlen(skb)); + if (unlikely(skb->len == hdr_len)) { + /* only xsum need */ + if (netif_msg_tx_queued(adapter)) + dev_warn(&pdev->dev, + "IPV4 tso with zero data??\n"); + goto check_sum; + } else { + ip_hdr(skb)->check = 0; + tcp_hdr(skb)->check = ~csum_tcpudp_magic( + ip_hdr(skb)->saddr, + ip_hdr(skb)->daddr, + 0, IPPROTO_TCP, 0); + (*tpd)->word1 |= 1 << TPD_IPV4_PACKET_SHIFT; + } + } + + if (offload_type & SKB_GSO_TCPV6) { + struct atl1c_tpd_ext_desc *etpd = + *(struct atl1c_tpd_ext_desc **)(tpd); + + memset(etpd, 0, sizeof(struct atl1c_tpd_ext_desc)); + *tpd = atl1c_get_tpd(adapter, type); + ipv6_hdr(skb)->payload_len = 0; + /* check payload == 0 byte ? */ + hdr_len = (skb_transport_offset(skb) + tcp_hdrlen(skb)); + if (unlikely(skb->len == hdr_len)) { + /* only xsum need */ + if (netif_msg_tx_queued(adapter)) + dev_warn(&pdev->dev, + "IPV6 tso with zero data??\n"); + goto check_sum; + } else + tcp_hdr(skb)->check = ~csum_ipv6_magic( + &ipv6_hdr(skb)->saddr, + &ipv6_hdr(skb)->daddr, + 0, IPPROTO_TCP, 0); + etpd->word1 |= 1 << TPD_LSO_EN_SHIFT; + etpd->word1 |= 1 << TPD_LSO_VER_SHIFT; + etpd->pkt_len = cpu_to_le32(skb->len); + (*tpd)->word1 |= 1 << TPD_LSO_VER_SHIFT; + } + + (*tpd)->word1 |= 1 << TPD_LSO_EN_SHIFT; + (*tpd)->word1 |= (skb_transport_offset(skb) & TPD_TCPHDR_OFFSET_MASK) << + TPD_TCPHDR_OFFSET_SHIFT; + (*tpd)->word1 |= (skb_shinfo(skb)->gso_size & TPD_MSS_MASK) << + TPD_MSS_SHIFT; + return 0; + } + +check_sum: + if (likely(skb->ip_summed == CHECKSUM_PARTIAL)) { + u8 css, cso; + cso = skb_checksum_start_offset(skb); + + if (unlikely(cso & 0x1)) { + if (netif_msg_tx_err(adapter)) + dev_err(&adapter->pdev->dev, + "payload offset should not an event number\n"); + return -1; + } else { + css = cso + skb->csum_offset; + + (*tpd)->word1 |= ((cso >> 1) & TPD_PLOADOFFSET_MASK) << + TPD_PLOADOFFSET_SHIFT; + (*tpd)->word1 |= ((css >> 1) & TPD_CCSUM_OFFSET_MASK) << + TPD_CCSUM_OFFSET_SHIFT; + (*tpd)->word1 |= 1 << TPD_CCSUM_EN_SHIFT; + } + } + return 0; +} + +static void atl1c_tx_rollback(struct atl1c_adapter *adpt, + struct atl1c_tpd_desc *first_tpd, + enum atl1c_trans_queue type) +{ + struct atl1c_tpd_ring *tpd_ring = &adpt->tpd_ring[type]; + struct atl1c_buffer *buffer_info; + struct atl1c_tpd_desc *tpd; + u16 first_index, index; + + first_index = first_tpd - (struct atl1c_tpd_desc *)tpd_ring->desc; + index = first_index; + while (index != tpd_ring->next_to_use) { + tpd = ATL1C_TPD_DESC(tpd_ring, index); + buffer_info = &tpd_ring->buffer_info[index]; + atl1c_clean_buffer(adpt->pdev, buffer_info, 0); + memset(tpd, 0, sizeof(struct atl1c_tpd_desc)); + if (++index == tpd_ring->count) + index = 0; + } + tpd_ring->next_to_use = first_index; +} + +static int atl1c_tx_map(struct atl1c_adapter *adapter, + struct sk_buff *skb, struct atl1c_tpd_desc *tpd, + enum atl1c_trans_queue type) +{ + struct atl1c_tpd_desc *use_tpd = NULL; + struct atl1c_buffer *buffer_info = NULL; + u16 buf_len = skb_headlen(skb); + u16 map_len = 0; + u16 mapped_len = 0; + u16 hdr_len = 0; + u16 nr_frags; + u16 f; + int tso; + + nr_frags = skb_shinfo(skb)->nr_frags; + tso = (tpd->word1 >> TPD_LSO_EN_SHIFT) & TPD_LSO_EN_MASK; + if (tso) { + /* TSO */ + map_len = hdr_len = skb_transport_offset(skb) + tcp_hdrlen(skb); + use_tpd = tpd; + + buffer_info = atl1c_get_tx_buffer(adapter, use_tpd); + buffer_info->length = map_len; + buffer_info->dma = pci_map_single(adapter->pdev, + skb->data, hdr_len, PCI_DMA_TODEVICE); + if (unlikely(pci_dma_mapping_error(adapter->pdev, + buffer_info->dma))) + goto err_dma; + ATL1C_SET_BUFFER_STATE(buffer_info, ATL1C_BUFFER_BUSY); + ATL1C_SET_PCIMAP_TYPE(buffer_info, ATL1C_PCIMAP_SINGLE, + ATL1C_PCIMAP_TODEVICE); + mapped_len += map_len; + use_tpd->buffer_addr = cpu_to_le64(buffer_info->dma); + use_tpd->buffer_len = cpu_to_le16(buffer_info->length); + } + + if (mapped_len < buf_len) { + /* mapped_len == 0, means we should use the first tpd, + which is given by caller */ + if (mapped_len == 0) + use_tpd = tpd; + else { + use_tpd = atl1c_get_tpd(adapter, type); + memcpy(use_tpd, tpd, sizeof(struct atl1c_tpd_desc)); + } + buffer_info = atl1c_get_tx_buffer(adapter, use_tpd); + buffer_info->length = buf_len - mapped_len; + buffer_info->dma = + pci_map_single(adapter->pdev, skb->data + mapped_len, + buffer_info->length, PCI_DMA_TODEVICE); + if (unlikely(pci_dma_mapping_error(adapter->pdev, + buffer_info->dma))) + goto err_dma; + + ATL1C_SET_BUFFER_STATE(buffer_info, ATL1C_BUFFER_BUSY); + ATL1C_SET_PCIMAP_TYPE(buffer_info, ATL1C_PCIMAP_SINGLE, + ATL1C_PCIMAP_TODEVICE); + use_tpd->buffer_addr = cpu_to_le64(buffer_info->dma); + use_tpd->buffer_len = cpu_to_le16(buffer_info->length); + } + + for (f = 0; f < nr_frags; f++) { + struct skb_frag_struct *frag; + + frag = &skb_shinfo(skb)->frags[f]; + + use_tpd = atl1c_get_tpd(adapter, type); + memcpy(use_tpd, tpd, sizeof(struct atl1c_tpd_desc)); + + buffer_info = atl1c_get_tx_buffer(adapter, use_tpd); + buffer_info->length = skb_frag_size(frag); + buffer_info->dma = skb_frag_dma_map(&adapter->pdev->dev, + frag, 0, + buffer_info->length, + DMA_TO_DEVICE); + if (dma_mapping_error(&adapter->pdev->dev, buffer_info->dma)) + goto err_dma; + + ATL1C_SET_BUFFER_STATE(buffer_info, ATL1C_BUFFER_BUSY); + ATL1C_SET_PCIMAP_TYPE(buffer_info, ATL1C_PCIMAP_PAGE, + ATL1C_PCIMAP_TODEVICE); + use_tpd->buffer_addr = cpu_to_le64(buffer_info->dma); + use_tpd->buffer_len = cpu_to_le16(buffer_info->length); + } + + /* The last tpd */ + use_tpd->word1 |= 1 << TPD_EOP_SHIFT; + /* The last buffer info contain the skb address, + so it will be free after unmap */ + buffer_info->skb = skb; + + return 0; + +err_dma: + buffer_info->dma = 0; + buffer_info->length = 0; + return -1; +} + +static void atl1c_tx_queue(struct atl1c_adapter *adapter, struct sk_buff *skb, + struct atl1c_tpd_desc *tpd, enum atl1c_trans_queue type) +{ + struct atl1c_tpd_ring *tpd_ring = &adapter->tpd_ring[type]; + u16 reg; + + reg = type == atl1c_trans_high ? REG_TPD_PRI1_PIDX : REG_TPD_PRI0_PIDX; + AT_WRITE_REGW(&adapter->hw, reg, tpd_ring->next_to_use); +} + +static netdev_tx_t atl1c_xmit_frame(struct sk_buff *skb, + struct net_device *netdev) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + unsigned long flags; + u16 tpd_req = 1; + struct atl1c_tpd_desc *tpd; + enum atl1c_trans_queue type = atl1c_trans_normal; + + if (test_bit(__AT_DOWN, &adapter->flags)) { + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + + tpd_req = atl1c_cal_tpd_req(skb); + if (!spin_trylock_irqsave(&adapter->tx_lock, flags)) { + if (netif_msg_pktdata(adapter)) + dev_info(&adapter->pdev->dev, "tx locked\n"); + return NETDEV_TX_LOCKED; + } + + if (atl1c_tpd_avail(adapter, type) < tpd_req) { + /* no enough descriptor, just stop queue */ + netif_stop_queue(netdev); + spin_unlock_irqrestore(&adapter->tx_lock, flags); + return NETDEV_TX_BUSY; + } + + tpd = atl1c_get_tpd(adapter, type); + + /* do TSO and check sum */ + if (atl1c_tso_csum(adapter, skb, &tpd, type) != 0) { + spin_unlock_irqrestore(&adapter->tx_lock, flags); + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + + if (unlikely(vlan_tx_tag_present(skb))) { + u16 vlan = vlan_tx_tag_get(skb); + __le16 tag; + + vlan = cpu_to_le16(vlan); + AT_VLAN_TO_TAG(vlan, tag); + tpd->word1 |= 1 << TPD_INS_VTAG_SHIFT; + tpd->vlan_tag = tag; + } + + if (skb_network_offset(skb) != ETH_HLEN) + tpd->word1 |= 1 << TPD_ETH_TYPE_SHIFT; /* Ethernet frame */ + + if (atl1c_tx_map(adapter, skb, tpd, type) < 0) { + netif_info(adapter, tx_done, adapter->netdev, + "tx-skb droppted due to dma error\n"); + /* roll back tpd/buffer */ + atl1c_tx_rollback(adapter, tpd, type); + spin_unlock_irqrestore(&adapter->tx_lock, flags); + dev_kfree_skb(skb); + } else { + atl1c_tx_queue(adapter, skb, tpd, type); + spin_unlock_irqrestore(&adapter->tx_lock, flags); + } + + return NETDEV_TX_OK; +} + +static void atl1c_free_irq(struct atl1c_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + + free_irq(adapter->pdev->irq, netdev); + + if (adapter->have_msi) + pci_disable_msi(adapter->pdev); +} + +static int atl1c_request_irq(struct atl1c_adapter *adapter) +{ + struct pci_dev *pdev = adapter->pdev; + struct net_device *netdev = adapter->netdev; + int flags = 0; + int err = 0; + + adapter->have_msi = true; + err = pci_enable_msi(adapter->pdev); + if (err) { + if (netif_msg_ifup(adapter)) + dev_err(&pdev->dev, + "Unable to allocate MSI interrupt Error: %d\n", + err); + adapter->have_msi = false; + } + + if (!adapter->have_msi) + flags |= IRQF_SHARED; + err = request_irq(adapter->pdev->irq, atl1c_intr, flags, + netdev->name, netdev); + if (err) { + if (netif_msg_ifup(adapter)) + dev_err(&pdev->dev, + "Unable to allocate interrupt Error: %d\n", + err); + if (adapter->have_msi) + pci_disable_msi(adapter->pdev); + return err; + } + if (netif_msg_ifup(adapter)) + dev_dbg(&pdev->dev, "atl1c_request_irq OK\n"); + return err; +} + + +static void atl1c_reset_dma_ring(struct atl1c_adapter *adapter) +{ + /* release tx-pending skbs and reset tx/rx ring index */ + atl1c_clean_tx_ring(adapter, atl1c_trans_normal); + atl1c_clean_tx_ring(adapter, atl1c_trans_high); + atl1c_clean_rx_ring(adapter); +} + +static int atl1c_up(struct atl1c_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + int err; + + netif_carrier_off(netdev); + + err = atl1c_configure(adapter); + if (unlikely(err)) + goto err_up; + + err = atl1c_request_irq(adapter); + if (unlikely(err)) + goto err_up; + + atl1c_check_link_status(adapter); + clear_bit(__AT_DOWN, &adapter->flags); + napi_enable(&adapter->napi); + atl1c_irq_enable(adapter); + netif_start_queue(netdev); + return err; + +err_up: + atl1c_clean_rx_ring(adapter); + return err; +} + +static void atl1c_down(struct atl1c_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + + atl1c_del_timer(adapter); + adapter->work_event = 0; /* clear all event */ + /* signal that we're down so the interrupt handler does not + * reschedule our watchdog timer */ + set_bit(__AT_DOWN, &adapter->flags); + netif_carrier_off(netdev); + napi_disable(&adapter->napi); + atl1c_irq_disable(adapter); + atl1c_free_irq(adapter); + /* disable ASPM if device inactive */ + atl1c_disable_l0s_l1(&adapter->hw); + /* reset MAC to disable all RX/TX */ + atl1c_reset_mac(&adapter->hw); + msleep(1); + + adapter->link_speed = SPEED_0; + adapter->link_duplex = -1; + atl1c_reset_dma_ring(adapter); +} + +/** + * atl1c_open - Called when a network interface is made active + * @netdev: network interface device structure + * + * Returns 0 on success, negative value on failure + * + * The open entry point is called when a network interface is made + * active by the system (IFF_UP). At this point all resources needed + * for transmit and receive operations are allocated, the interrupt + * handler is registered with the OS, the watchdog timer is started, + * and the stack is notified that the interface is ready. + */ +static int atl1c_open(struct net_device *netdev) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + int err; + + /* disallow open during test */ + if (test_bit(__AT_TESTING, &adapter->flags)) + return -EBUSY; + + /* allocate rx/tx dma buffer & descriptors */ + err = atl1c_setup_ring_resources(adapter); + if (unlikely(err)) + return err; + + err = atl1c_up(adapter); + if (unlikely(err)) + goto err_up; + + return 0; + +err_up: + atl1c_free_irq(adapter); + atl1c_free_ring_resources(adapter); + atl1c_reset_mac(&adapter->hw); + return err; +} + +/** + * atl1c_close - Disables a network interface + * @netdev: network interface device structure + * + * Returns 0, this is not allowed to fail + * + * The close entry point is called when an interface is de-activated + * by the OS. The hardware is still under the drivers control, but + * needs to be disabled. A global MAC reset is issued to stop the + * hardware, and all transmit and receive resources are freed. + */ +static int atl1c_close(struct net_device *netdev) +{ + struct atl1c_adapter *adapter = netdev_priv(netdev); + + WARN_ON(test_bit(__AT_RESETTING, &adapter->flags)); + set_bit(__AT_DOWN, &adapter->flags); + cancel_work_sync(&adapter->common_task); + atl1c_down(adapter); + atl1c_free_ring_resources(adapter); + return 0; +} + +static int atl1c_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1c_adapter *adapter = netdev_priv(netdev); + struct atl1c_hw *hw = &adapter->hw; + u32 wufc = adapter->wol; + + atl1c_disable_l0s_l1(hw); + if (netif_running(netdev)) { + WARN_ON(test_bit(__AT_RESETTING, &adapter->flags)); + atl1c_down(adapter); + } + netif_device_detach(netdev); + + if (wufc) + if (atl1c_phy_to_ps_link(hw) != 0) + dev_dbg(&pdev->dev, "phy power saving failed"); + + atl1c_power_saving(hw, wufc); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int atl1c_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1c_adapter *adapter = netdev_priv(netdev); + + AT_WRITE_REG(&adapter->hw, REG_WOL_CTRL, 0); + atl1c_reset_pcie(&adapter->hw, ATL1C_PCIE_L0S_L1_DISABLE); + + atl1c_phy_reset(&adapter->hw); + atl1c_reset_mac(&adapter->hw); + atl1c_phy_init(&adapter->hw); + +#if 0 + AT_READ_REG(&adapter->hw, REG_PM_CTRLSTAT, &pm_data); + pm_data &= ~PM_CTRLSTAT_PME_EN; + AT_WRITE_REG(&adapter->hw, REG_PM_CTRLSTAT, pm_data); +#endif + + netif_device_attach(netdev); + if (netif_running(netdev)) + atl1c_up(adapter); + + return 0; +} +#endif + +static void atl1c_shutdown(struct pci_dev *pdev) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1c_adapter *adapter = netdev_priv(netdev); + + atl1c_suspend(&pdev->dev); + pci_wake_from_d3(pdev, adapter->wol); + pci_set_power_state(pdev, PCI_D3hot); +} + +static const struct net_device_ops atl1c_netdev_ops = { + .ndo_open = atl1c_open, + .ndo_stop = atl1c_close, + .ndo_validate_addr = eth_validate_addr, + .ndo_start_xmit = atl1c_xmit_frame, + .ndo_set_mac_address = atl1c_set_mac_addr, + .ndo_set_rx_mode = atl1c_set_multi, + .ndo_change_mtu = atl1c_change_mtu, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + .ndo_fix_features = atl1c_fix_features, + .ndo_set_features = atl1c_set_features, +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */ + .ndo_do_ioctl = atl1c_ioctl, + .ndo_tx_timeout = atl1c_tx_timeout, + .ndo_get_stats = atl1c_get_stats, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = atl1c_netpoll, +#endif +}; + +static int atl1c_init_netdev(struct net_device *netdev, struct pci_dev *pdev) +{ + SET_NETDEV_DEV(netdev, &pdev->dev); + pci_set_drvdata(pdev, netdev); + + netdev_attach_ops(netdev, &atl1c_netdev_ops); + netdev->watchdog_timeo = AT_TX_WATCHDOG; + atl1c_set_ethtool_ops(netdev); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + /* TODO: add when ready */ + netdev->hw_features = NETIF_F_SG | + NETIF_F_HW_CSUM | + NETIF_F_HW_VLAN_CTAG_RX | + NETIF_F_TSO | + NETIF_F_TSO6; + netdev->features = netdev->hw_features | + NETIF_F_HW_VLAN_CTAG_TX; +#else + netdev->features = NETIF_F_SG | + NETIF_F_HW_CSUM | + NETIF_F_HW_VLAN_TX | + NETIF_F_HW_VLAN_RX | + NETIF_F_TSO | + NETIF_F_TSO6; +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */ + return 0; +} + +/** + * atl1c_probe - Device Initialization Routine + * @pdev: PCI device information struct + * @ent: entry in atl1c_pci_tbl + * + * Returns 0 on success, negative on failure + * + * atl1c_probe initializes an adapter identified by a pci_dev structure. + * The OS initialization, configuring of the adapter private structure, + * and a hardware reset occur. + */ +static int atl1c_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct net_device *netdev; + struct atl1c_adapter *adapter; + static int cards_found; + + int err = 0; + + /* enable device (incl. PCI PM wakeup and hotplug setup) */ + err = pci_enable_device_mem(pdev); + if (err) { + dev_err(&pdev->dev, "cannot enable PCI device\n"); + return err; + } + + /* + * The atl1c chip can DMA to 64-bit addresses, but it uses a single + * shared register for the high 32 bits, so only a single, aligned, + * 4 GB physical address range can be used at a time. + * + * Supporting 64-bit DMA on this hardware is more trouble than it's + * worth. It is far easier to limit to 32-bit DMA than update + * various kernel subsystems to support the mechanics required by a + * fixed-high-32-bit system. + */ + if ((pci_set_dma_mask(pdev, DMA_BIT_MASK(32)) != 0) || + (pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)) != 0)) { + dev_err(&pdev->dev, "No usable DMA configuration,aborting\n"); + goto err_dma; + } + + err = pci_request_regions(pdev, atl1c_driver_name); + if (err) { + dev_err(&pdev->dev, "cannot obtain PCI resources\n"); + goto err_pci_reg; + } + + pci_set_master(pdev); + + netdev = alloc_etherdev(sizeof(struct atl1c_adapter)); + if (netdev == NULL) { + err = -ENOMEM; + goto err_alloc_etherdev; + } + + err = atl1c_init_netdev(netdev, pdev); + if (err) { + dev_err(&pdev->dev, "init netdevice failed\n"); + goto err_init_netdev; + } + adapter = netdev_priv(netdev); + adapter->bd_number = cards_found; + adapter->netdev = netdev; + adapter->pdev = pdev; + adapter->hw.adapter = adapter; + adapter->msg_enable = netif_msg_init(-1, atl1c_default_msg); + adapter->hw.hw_addr = ioremap(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); + if (!adapter->hw.hw_addr) { + err = -EIO; + dev_err(&pdev->dev, "cannot map device registers\n"); + goto err_ioremap; + } + + /* init mii data */ + adapter->mii.dev = netdev; + adapter->mii.mdio_read = atl1c_mdio_read; + adapter->mii.mdio_write = atl1c_mdio_write; + adapter->mii.phy_id_mask = 0x1f; + adapter->mii.reg_num_mask = MDIO_CTRL_REG_MASK; + netif_napi_add(netdev, &adapter->napi, atl1c_clean, 64); + setup_timer(&adapter->phy_config_timer, atl1c_phy_config, + (unsigned long)adapter); + /* setup the private structure */ + err = atl1c_sw_init(adapter); + if (err) { + dev_err(&pdev->dev, "net device private data init failed\n"); + goto err_sw_init; + } + atl1c_reset_pcie(&adapter->hw, ATL1C_PCIE_L0S_L1_DISABLE); + + /* Init GPHY as early as possible due to power saving issue */ + atl1c_phy_reset(&adapter->hw); + + err = atl1c_reset_mac(&adapter->hw); + if (err) { + err = -EIO; + goto err_reset; + } + + /* reset the controller to + * put the device in a known good starting state */ + err = atl1c_phy_init(&adapter->hw); + if (err) { + err = -EIO; + goto err_reset; + } + if (atl1c_read_mac_addr(&adapter->hw)) { + /* got a random MAC address, set NET_ADDR_RANDOM to netdev */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)) + netdev->addr_assign_type = NET_ADDR_RANDOM; +#endif + } + memcpy(netdev->dev_addr, adapter->hw.mac_addr, netdev->addr_len); + if (netif_msg_probe(adapter)) + dev_dbg(&pdev->dev, "mac address : %pM\n", + adapter->hw.mac_addr); + + atl1c_hw_set_mac_addr(&adapter->hw, adapter->hw.mac_addr); + INIT_WORK(&adapter->common_task, atl1c_common_task); + adapter->work_event = 0; + err = register_netdev(netdev); + if (err) { + dev_err(&pdev->dev, "register netdevice failed\n"); + goto err_register; + } + + if (netif_msg_probe(adapter)) + dev_info(&pdev->dev, "version %s\n", ATL1C_DRV_VERSION); + cards_found++; + return 0; + +err_reset: +err_register: +err_sw_init: + iounmap(adapter->hw.hw_addr); +err_init_netdev: +err_ioremap: + free_netdev(netdev); +err_alloc_etherdev: + pci_release_regions(pdev); +err_pci_reg: +err_dma: + pci_disable_device(pdev); + return err; +} + +/** + * atl1c_remove - Device Removal Routine + * @pdev: PCI device information struct + * + * atl1c_remove is called by the PCI subsystem to alert the driver + * that it should release a PCI device. The could be caused by a + * Hot-Plug event, or because the driver is going to be removed from + * memory. + */ +static void atl1c_remove(struct pci_dev *pdev) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1c_adapter *adapter = netdev_priv(netdev); + + unregister_netdev(netdev); + /* restore permanent address */ + atl1c_hw_set_mac_addr(&adapter->hw, adapter->hw.perm_mac_addr); + atl1c_phy_disable(&adapter->hw); + + iounmap(adapter->hw.hw_addr); + + pci_release_regions(pdev); + pci_disable_device(pdev); + free_netdev(netdev); +} + +/** + * atl1c_io_error_detected - called when PCI error is detected + * @pdev: Pointer to PCI device + * @state: The current pci connection state + * + * This function is called after a PCI bus error affecting + * this device has been detected. + */ +static pci_ers_result_t atl1c_io_error_detected(struct pci_dev *pdev, + pci_channel_state_t state) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1c_adapter *adapter = netdev_priv(netdev); + + netif_device_detach(netdev); + + if (state == pci_channel_io_perm_failure) + return PCI_ERS_RESULT_DISCONNECT; + + if (netif_running(netdev)) + atl1c_down(adapter); + + pci_disable_device(pdev); + + /* Request a slot slot reset. */ + return PCI_ERS_RESULT_NEED_RESET; +} + +/** + * atl1c_io_slot_reset - called after the pci bus has been reset. + * @pdev: Pointer to PCI device + * + * Restart the card from scratch, as if from a cold-boot. Implementation + * resembles the first-half of the e1000_resume routine. + */ +static pci_ers_result_t atl1c_io_slot_reset(struct pci_dev *pdev) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1c_adapter *adapter = netdev_priv(netdev); + + if (pci_enable_device(pdev)) { + if (netif_msg_hw(adapter)) + dev_err(&pdev->dev, + "Cannot re-enable PCI device after reset\n"); + return PCI_ERS_RESULT_DISCONNECT; + } + pci_set_master(pdev); + + pci_enable_wake(pdev, PCI_D3hot, 0); + pci_enable_wake(pdev, PCI_D3cold, 0); + + atl1c_reset_mac(&adapter->hw); + + return PCI_ERS_RESULT_RECOVERED; +} + +/** + * atl1c_io_resume - called when traffic can start flowing again. + * @pdev: Pointer to PCI device + * + * This callback is called when the error recovery driver tells us that + * its OK to resume normal operation. Implementation resembles the + * second-half of the atl1c_resume routine. + */ +static void atl1c_io_resume(struct pci_dev *pdev) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1c_adapter *adapter = netdev_priv(netdev); + + if (netif_running(netdev)) { + if (atl1c_up(adapter)) { + if (netif_msg_hw(adapter)) + dev_err(&pdev->dev, + "Cannot bring device back up after reset\n"); + return; + } + } + + netif_device_attach(netdev); +} + +static const struct pci_error_handlers atl1c_err_handler = { + .error_detected = atl1c_io_error_detected, + .slot_reset = atl1c_io_slot_reset, + .resume = atl1c_io_resume, +}; + +compat_pci_suspend(atl1c_suspend) +compat_pci_resume(atl1c_resume) + +static SIMPLE_DEV_PM_OPS(atl1c_pm_ops, atl1c_suspend, atl1c_resume); + +static struct pci_driver atl1c_driver = { + .name = atl1c_driver_name, + .id_table = atl1c_pci_tbl, + .probe = atl1c_probe, + .remove = atl1c_remove, + .shutdown = atl1c_shutdown, + .err_handler = &atl1c_err_handler, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) + .driver.pm = &atl1c_pm_ops, +#elif defined(CONFIG_PM_SLEEP) + .suspend = atl1c_suspend_compat, + .resume = atl1c_resume_compat, +#endif +}; + +/** + * atl1c_init_module - Driver Registration Routine + * + * atl1c_init_module is the first routine called when the driver is + * loaded. All it does is register with the PCI subsystem. + */ +static int __init atl1c_init_module(void) +{ + return pci_register_driver(&atl1c_driver); +} + +/** + * atl1c_exit_module - Driver Exit Cleanup Routine + * + * atl1c_exit_module is called just before the driver is removed + * from memory. + */ +static void __exit atl1c_exit_module(void) +{ + pci_unregister_driver(&atl1c_driver); +} + +module_init(atl1c_init_module); +module_exit(atl1c_exit_module); diff --git a/drivers/net/ethernet/atheros/atl1e/Makefile b/drivers/net/ethernet/atheros/atl1e/Makefile new file mode 100644 index 0000000..81f7413 --- /dev/null +++ b/drivers/net/ethernet/atheros/atl1e/Makefile @@ -0,0 +1,2 @@ +obj-$(CPTCFG_ATL1E) += atl1e.o +atl1e-objs += atl1e_main.o atl1e_hw.o atl1e_ethtool.o atl1e_param.o diff --git a/drivers/net/ethernet/atheros/atl1e/atl1e.h b/drivers/net/ethernet/atheros/atl1e/atl1e.h new file mode 100644 index 0000000..b5fd934 --- /dev/null +++ b/drivers/net/ethernet/atheros/atl1e/atl1e.h @@ -0,0 +1,508 @@ +/* + * Copyright(c) 2007 Atheros Corporation. All rights reserved. + * Copyright(c) 2007 xiong huang + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _ATL1E_H_ +#define _ATL1E_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atl1e_hw.h" + +#define PCI_REG_COMMAND 0x04 /* PCI Command Register */ +#define CMD_IO_SPACE 0x0001 +#define CMD_MEMORY_SPACE 0x0002 +#define CMD_BUS_MASTER 0x0004 + +#define BAR_0 0 +#define BAR_1 1 +#define BAR_5 5 + +/* Wake Up Filter Control */ +#define AT_WUFC_LNKC 0x00000001 /* Link Status Change Wakeup Enable */ +#define AT_WUFC_MAG 0x00000002 /* Magic Packet Wakeup Enable */ +#define AT_WUFC_EX 0x00000004 /* Directed Exact Wakeup Enable */ +#define AT_WUFC_MC 0x00000008 /* Multicast Wakeup Enable */ +#define AT_WUFC_BC 0x00000010 /* Broadcast Wakeup Enable */ + +#define SPEED_0 0xffff +#define HALF_DUPLEX 1 +#define FULL_DUPLEX 2 + +/* Error Codes */ +#define AT_ERR_EEPROM 1 +#define AT_ERR_PHY 2 +#define AT_ERR_CONFIG 3 +#define AT_ERR_PARAM 4 +#define AT_ERR_MAC_TYPE 5 +#define AT_ERR_PHY_TYPE 6 +#define AT_ERR_PHY_SPEED 7 +#define AT_ERR_PHY_RES 8 +#define AT_ERR_TIMEOUT 9 + +#define MAX_JUMBO_FRAME_SIZE 0x2000 + +#define AT_VLAN_TAG_TO_TPD_TAG(_vlan, _tpd) \ + _tpd = (((_vlan) << (4)) | (((_vlan) >> 13) & 7) |\ + (((_vlan) >> 9) & 8)) + +#define AT_TPD_TAG_TO_VLAN_TAG(_tpd, _vlan) \ + _vlan = (((_tpd) >> 8) | (((_tpd) & 0x77) << 9) |\ + (((_tdp) & 0x88) << 5)) + +#define AT_MAX_RECEIVE_QUEUE 4 +#define AT_PAGE_NUM_PER_QUEUE 2 + +#define AT_DMA_HI_ADDR_MASK 0xffffffff00000000ULL +#define AT_DMA_LO_ADDR_MASK 0x00000000ffffffffULL + +#define AT_TX_WATCHDOG (5 * HZ) +#define AT_MAX_INT_WORK 10 +#define AT_TWSI_EEPROM_TIMEOUT 100 +#define AT_HW_MAX_IDLE_DELAY 10 +#define AT_SUSPEND_LINK_TIMEOUT 28 + +#define AT_REGS_LEN 75 +#define AT_EEPROM_LEN 512 +#define AT_ADV_MASK (ADVERTISE_10_HALF |\ + ADVERTISE_10_FULL |\ + ADVERTISE_100_HALF |\ + ADVERTISE_100_FULL |\ + ADVERTISE_1000_FULL) + +/* tpd word 2 */ +#define TPD_BUFLEN_MASK 0x3FFF +#define TPD_BUFLEN_SHIFT 0 +#define TPD_DMAINT_MASK 0x0001 +#define TPD_DMAINT_SHIFT 14 +#define TPD_PKTNT_MASK 0x0001 +#define TPD_PKTINT_SHIFT 15 +#define TPD_VLANTAG_MASK 0xFFFF +#define TPD_VLAN_SHIFT 16 + +/* tpd word 3 bits 0:4 */ +#define TPD_EOP_MASK 0x0001 +#define TPD_EOP_SHIFT 0 +#define TPD_IP_VERSION_MASK 0x0001 +#define TPD_IP_VERSION_SHIFT 1 /* 0 : IPV4, 1 : IPV6 */ +#define TPD_INS_VL_TAG_MASK 0x0001 +#define TPD_INS_VL_TAG_SHIFT 2 +#define TPD_CC_SEGMENT_EN_MASK 0x0001 +#define TPD_CC_SEGMENT_EN_SHIFT 3 +#define TPD_SEGMENT_EN_MASK 0x0001 +#define TPD_SEGMENT_EN_SHIFT 4 + +/* tdp word 3 bits 5:7 if ip version is 0 */ +#define TPD_IP_CSUM_MASK 0x0001 +#define TPD_IP_CSUM_SHIFT 5 +#define TPD_TCP_CSUM_MASK 0x0001 +#define TPD_TCP_CSUM_SHIFT 6 +#define TPD_UDP_CSUM_MASK 0x0001 +#define TPD_UDP_CSUM_SHIFT 7 + +/* tdp word 3 bits 5:7 if ip version is 1 */ +#define TPD_V6_IPHLLO_MASK 0x0007 +#define TPD_V6_IPHLLO_SHIFT 7 + +/* tpd word 3 bits 8:9 bit */ +#define TPD_VL_TAGGED_MASK 0x0001 +#define TPD_VL_TAGGED_SHIFT 8 +#define TPD_ETHTYPE_MASK 0x0001 +#define TPD_ETHTYPE_SHIFT 9 + +/* tdp word 3 bits 10:13 if ip version is 0 */ +#define TDP_V4_IPHL_MASK 0x000F +#define TPD_V4_IPHL_SHIFT 10 + +/* tdp word 3 bits 10:13 if ip version is 1 */ +#define TPD_V6_IPHLHI_MASK 0x000F +#define TPD_V6_IPHLHI_SHIFT 10 + +/* tpd word 3 bit 14:31 if segment enabled */ +#define TPD_TCPHDRLEN_MASK 0x000F +#define TPD_TCPHDRLEN_SHIFT 14 +#define TPD_HDRFLAG_MASK 0x0001 +#define TPD_HDRFLAG_SHIFT 18 +#define TPD_MSS_MASK 0x1FFF +#define TPD_MSS_SHIFT 19 + +/* tdp word 3 bit 16:31 if custom csum enabled */ +#define TPD_PLOADOFFSET_MASK 0x00FF +#define TPD_PLOADOFFSET_SHIFT 16 +#define TPD_CCSUMOFFSET_MASK 0x00FF +#define TPD_CCSUMOFFSET_SHIFT 24 + +struct atl1e_tpd_desc { + __le64 buffer_addr; + __le32 word2; + __le32 word3; +}; + +/* how about 0x2000 */ +#define MAX_TX_BUF_LEN 0x2000 +#define MAX_TX_BUF_SHIFT 13 +#define MAX_TSO_SEG_SIZE 0x3c00 + +/* rrs word 1 bit 0:31 */ +#define RRS_RX_CSUM_MASK 0xFFFF +#define RRS_RX_CSUM_SHIFT 0 +#define RRS_PKT_SIZE_MASK 0x3FFF +#define RRS_PKT_SIZE_SHIFT 16 +#define RRS_CPU_NUM_MASK 0x0003 +#define RRS_CPU_NUM_SHIFT 30 + +#define RRS_IS_RSS_IPV4 0x0001 +#define RRS_IS_RSS_IPV4_TCP 0x0002 +#define RRS_IS_RSS_IPV6 0x0004 +#define RRS_IS_RSS_IPV6_TCP 0x0008 +#define RRS_IS_IPV6 0x0010 +#define RRS_IS_IP_FRAG 0x0020 +#define RRS_IS_IP_DF 0x0040 +#define RRS_IS_802_3 0x0080 +#define RRS_IS_VLAN_TAG 0x0100 +#define RRS_IS_ERR_FRAME 0x0200 +#define RRS_IS_IPV4 0x0400 +#define RRS_IS_UDP 0x0800 +#define RRS_IS_TCP 0x1000 +#define RRS_IS_BCAST 0x2000 +#define RRS_IS_MCAST 0x4000 +#define RRS_IS_PAUSE 0x8000 + +#define RRS_ERR_BAD_CRC 0x0001 +#define RRS_ERR_CODE 0x0002 +#define RRS_ERR_DRIBBLE 0x0004 +#define RRS_ERR_RUNT 0x0008 +#define RRS_ERR_RX_OVERFLOW 0x0010 +#define RRS_ERR_TRUNC 0x0020 +#define RRS_ERR_IP_CSUM 0x0040 +#define RRS_ERR_L4_CSUM 0x0080 +#define RRS_ERR_LENGTH 0x0100 +#define RRS_ERR_DES_ADDR 0x0200 + +struct atl1e_recv_ret_status { + u16 seq_num; + u16 hash_lo; + __le32 word1; + u16 pkt_flag; + u16 err_flag; + u16 hash_hi; + u16 vtag; +}; + +enum atl1e_dma_req_block { + atl1e_dma_req_128 = 0, + atl1e_dma_req_256 = 1, + atl1e_dma_req_512 = 2, + atl1e_dma_req_1024 = 3, + atl1e_dma_req_2048 = 4, + atl1e_dma_req_4096 = 5 +}; + +enum atl1e_rrs_type { + atl1e_rrs_disable = 0, + atl1e_rrs_ipv4 = 1, + atl1e_rrs_ipv4_tcp = 2, + atl1e_rrs_ipv6 = 4, + atl1e_rrs_ipv6_tcp = 8 +}; + +enum atl1e_nic_type { + athr_l1e = 0, + athr_l2e_revA = 1, + athr_l2e_revB = 2 +}; + +struct atl1e_hw_stats { + /* rx */ + unsigned long rx_ok; /* The number of good packet received. */ + unsigned long rx_bcast; /* The number of good broadcast packet received. */ + unsigned long rx_mcast; /* The number of good multicast packet received. */ + unsigned long rx_pause; /* The number of Pause packet received. */ + unsigned long rx_ctrl; /* The number of Control packet received other than Pause frame. */ + unsigned long rx_fcs_err; /* The number of packets with bad FCS. */ + unsigned long rx_len_err; /* The number of packets with mismatch of length field and actual size. */ + unsigned long rx_byte_cnt; /* The number of bytes of good packet received. FCS is NOT included. */ + unsigned long rx_runt; /* The number of packets received that are less than 64 byte long and with good FCS. */ + unsigned long rx_frag; /* The number of packets received that are less than 64 byte long and with bad FCS. */ + unsigned long rx_sz_64; /* The number of good and bad packets received that are 64 byte long. */ + unsigned long rx_sz_65_127; /* The number of good and bad packets received that are between 65 and 127-byte long. */ + unsigned long rx_sz_128_255; /* The number of good and bad packets received that are between 128 and 255-byte long. */ + unsigned long rx_sz_256_511; /* The number of good and bad packets received that are between 256 and 511-byte long. */ + unsigned long rx_sz_512_1023; /* The number of good and bad packets received that are between 512 and 1023-byte long. */ + unsigned long rx_sz_1024_1518; /* The number of good and bad packets received that are between 1024 and 1518-byte long. */ + unsigned long rx_sz_1519_max; /* The number of good and bad packets received that are between 1519-byte and MTU. */ + unsigned long rx_sz_ov; /* The number of good and bad packets received that are more than MTU size truncated by Selene. */ + unsigned long rx_rxf_ov; /* The number of frame dropped due to occurrence of RX FIFO overflow. */ + unsigned long rx_rrd_ov; /* The number of frame dropped due to occurrence of RRD overflow. */ + unsigned long rx_align_err; /* Alignment Error */ + unsigned long rx_bcast_byte_cnt; /* The byte count of broadcast packet received, excluding FCS. */ + unsigned long rx_mcast_byte_cnt; /* The byte count of multicast packet received, excluding FCS. */ + unsigned long rx_err_addr; /* The number of packets dropped due to address filtering. */ + + /* tx */ + unsigned long tx_ok; /* The number of good packet transmitted. */ + unsigned long tx_bcast; /* The number of good broadcast packet transmitted. */ + unsigned long tx_mcast; /* The number of good multicast packet transmitted. */ + unsigned long tx_pause; /* The number of Pause packet transmitted. */ + unsigned long tx_exc_defer; /* The number of packets transmitted with excessive deferral. */ + unsigned long tx_ctrl; /* The number of packets transmitted is a control frame, excluding Pause frame. */ + unsigned long tx_defer; /* The number of packets transmitted that is deferred. */ + unsigned long tx_byte_cnt; /* The number of bytes of data transmitted. FCS is NOT included. */ + unsigned long tx_sz_64; /* The number of good and bad packets transmitted that are 64 byte long. */ + unsigned long tx_sz_65_127; /* The number of good and bad packets transmitted that are between 65 and 127-byte long. */ + unsigned long tx_sz_128_255; /* The number of good and bad packets transmitted that are between 128 and 255-byte long. */ + unsigned long tx_sz_256_511; /* The number of good and bad packets transmitted that are between 256 and 511-byte long. */ + unsigned long tx_sz_512_1023; /* The number of good and bad packets transmitted that are between 512 and 1023-byte long. */ + unsigned long tx_sz_1024_1518; /* The number of good and bad packets transmitted that are between 1024 and 1518-byte long. */ + unsigned long tx_sz_1519_max; /* The number of good and bad packets transmitted that are between 1519-byte and MTU. */ + unsigned long tx_1_col; /* The number of packets subsequently transmitted successfully with a single prior collision. */ + unsigned long tx_2_col; /* The number of packets subsequently transmitted successfully with multiple prior collisions. */ + unsigned long tx_late_col; /* The number of packets transmitted with late collisions. */ + unsigned long tx_abort_col; /* The number of transmit packets aborted due to excessive collisions. */ + unsigned long tx_underrun; /* The number of transmit packets aborted due to transmit FIFO underrun, or TRD FIFO underrun */ + unsigned long tx_rd_eop; /* The number of times that read beyond the EOP into the next frame area when TRD was not written timely */ + unsigned long tx_len_err; /* The number of transmit packets with length field does NOT match the actual frame size. */ + unsigned long tx_trunc; /* The number of transmit packets truncated due to size exceeding MTU. */ + unsigned long tx_bcast_byte; /* The byte count of broadcast packet transmitted, excluding FCS. */ + unsigned long tx_mcast_byte; /* The byte count of multicast packet transmitted, excluding FCS. */ +}; + +struct atl1e_hw { + u8 __iomem *hw_addr; /* inner register address */ + resource_size_t mem_rang; + struct atl1e_adapter *adapter; + enum atl1e_nic_type nic_type; + u16 device_id; + u16 vendor_id; + u16 subsystem_id; + u16 subsystem_vendor_id; + u8 revision_id; + u16 pci_cmd_word; + u8 mac_addr[ETH_ALEN]; + u8 perm_mac_addr[ETH_ALEN]; + u8 preamble_len; + u16 max_frame_size; + u16 rx_jumbo_th; + u16 tx_jumbo_th; + + u16 media_type; +#define MEDIA_TYPE_AUTO_SENSOR 0 +#define MEDIA_TYPE_100M_FULL 1 +#define MEDIA_TYPE_100M_HALF 2 +#define MEDIA_TYPE_10M_FULL 3 +#define MEDIA_TYPE_10M_HALF 4 + + u16 autoneg_advertised; +#define ADVERTISE_10_HALF 0x0001 +#define ADVERTISE_10_FULL 0x0002 +#define ADVERTISE_100_HALF 0x0004 +#define ADVERTISE_100_FULL 0x0008 +#define ADVERTISE_1000_HALF 0x0010 /* Not used, just FYI */ +#define ADVERTISE_1000_FULL 0x0020 + u16 mii_autoneg_adv_reg; + u16 mii_1000t_ctrl_reg; + + u16 imt; /* Interrupt Moderator timer ( 2us resolution) */ + u16 ict; /* Interrupt Clear timer (2us resolution) */ + u32 smb_timer; + u16 rrd_thresh; /* Threshold of number of RRD produced to trigger + interrupt request */ + u16 tpd_thresh; + u16 rx_count_down; /* 2us resolution */ + u16 tx_count_down; + + u8 tpd_burst; /* Number of TPD to prefetch in cache-aligned burst. */ + enum atl1e_rrs_type rrs_type; + u32 base_cpu; + u32 indirect_tab; + + enum atl1e_dma_req_block dmar_block; + enum atl1e_dma_req_block dmaw_block; + u8 dmaw_dly_cnt; + u8 dmar_dly_cnt; + + bool phy_configured; + bool re_autoneg; + bool emi_ca; +}; + +/* + * wrapper around a pointer to a socket buffer, + * so a DMA handle can be stored along with the buffer + */ +struct atl1e_tx_buffer { + struct sk_buff *skb; + u16 flags; +#define ATL1E_TX_PCIMAP_SINGLE 0x0001 +#define ATL1E_TX_PCIMAP_PAGE 0x0002 +#define ATL1E_TX_PCIMAP_TYPE_MASK 0x0003 + u16 length; + dma_addr_t dma; +}; + +#define ATL1E_SET_PCIMAP_TYPE(tx_buff, type) do { \ + ((tx_buff)->flags) &= ~ATL1E_TX_PCIMAP_TYPE_MASK; \ + ((tx_buff)->flags) |= (type); \ + } while (0) + +struct atl1e_rx_page { + dma_addr_t dma; /* receive rage DMA address */ + u8 *addr; /* receive rage virtual address */ + dma_addr_t write_offset_dma; /* the DMA address which contain the + receive data offset in the page */ + u32 *write_offset_addr; /* the virtaul address which contain + the receive data offset in the page */ + u32 read_offset; /* the offset where we have read */ +}; + +struct atl1e_rx_page_desc { + struct atl1e_rx_page rx_page[AT_PAGE_NUM_PER_QUEUE]; + u8 rx_using; + u16 rx_nxseq; +}; + +/* transmit packet descriptor (tpd) ring */ +struct atl1e_tx_ring { + struct atl1e_tpd_desc *desc; /* descriptor ring virtual address */ + dma_addr_t dma; /* descriptor ring physical address */ + u16 count; /* the count of transmit rings */ + rwlock_t tx_lock; + u16 next_to_use; + atomic_t next_to_clean; + struct atl1e_tx_buffer *tx_buffer; + dma_addr_t cmb_dma; + u32 *cmb; +}; + +/* receive packet descriptor ring */ +struct atl1e_rx_ring { + void *desc; + dma_addr_t dma; + int size; + u32 page_size; /* bytes length of rxf page */ + u32 real_page_size; /* real_page_size = page_size + jumbo + aliagn */ + struct atl1e_rx_page_desc rx_page_desc[AT_MAX_RECEIVE_QUEUE]; +}; + +/* board specific private data structure */ +struct atl1e_adapter { + struct net_device *netdev; + struct pci_dev *pdev; + struct napi_struct napi; + struct mii_if_info mii; /* MII interface info */ + struct atl1e_hw hw; + struct atl1e_hw_stats hw_stats; + + u32 wol; + u16 link_speed; + u16 link_duplex; + + spinlock_t mdio_lock; + spinlock_t tx_lock; + atomic_t irq_sem; + + struct work_struct reset_task; + struct work_struct link_chg_task; + struct timer_list watchdog_timer; + struct timer_list phy_config_timer; + + /* All Descriptor memory */ + dma_addr_t ring_dma; + void *ring_vir_addr; + u32 ring_size; + + struct atl1e_tx_ring tx_ring; + struct atl1e_rx_ring rx_ring; + int num_rx_queues; + unsigned long flags; +#define __AT_TESTING 0x0001 +#define __AT_RESETTING 0x0002 +#define __AT_DOWN 0x0003 + + u32 bd_number; /* board number;*/ + u32 pci_state[16]; + u32 *config_space; +}; + +#define AT_WRITE_REG(a, reg, value) ( \ + writel((value), ((a)->hw_addr + reg))) + +#define AT_WRITE_FLUSH(a) (\ + readl((a)->hw_addr)) + +#define AT_READ_REG(a, reg) ( \ + readl((a)->hw_addr + reg)) + +#define AT_WRITE_REGB(a, reg, value) (\ + writeb((value), ((a)->hw_addr + reg))) + +#define AT_READ_REGB(a, reg) (\ + readb((a)->hw_addr + reg)) + +#define AT_WRITE_REGW(a, reg, value) (\ + writew((value), ((a)->hw_addr + reg))) + +#define AT_READ_REGW(a, reg) (\ + readw((a)->hw_addr + reg)) + +#define AT_WRITE_REG_ARRAY(a, reg, offset, value) ( \ + writel((value), (((a)->hw_addr + reg) + ((offset) << 2)))) + +#define AT_READ_REG_ARRAY(a, reg, offset) ( \ + readl(((a)->hw_addr + reg) + ((offset) << 2))) + +extern char atl1e_driver_name[]; +extern char atl1e_driver_version[]; + +extern void atl1e_check_options(struct atl1e_adapter *adapter); +extern int atl1e_up(struct atl1e_adapter *adapter); +extern void atl1e_down(struct atl1e_adapter *adapter); +extern void atl1e_reinit_locked(struct atl1e_adapter *adapter); +extern s32 atl1e_reset_hw(struct atl1e_hw *hw); +extern void atl1e_set_ethtool_ops(struct net_device *netdev); +#endif /* _ATL1_E_H_ */ diff --git a/drivers/net/ethernet/atheros/atl1e/atl1e_ethtool.c b/drivers/net/ethernet/atheros/atl1e/atl1e_ethtool.c new file mode 100644 index 0000000..4d6f288 --- /dev/null +++ b/drivers/net/ethernet/atheros/atl1e/atl1e_ethtool.c @@ -0,0 +1,397 @@ +/* + * Copyright(c) 2007 Atheros Corporation. All rights reserved. + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include +#include +#include + +#include "atl1e.h" + +static int atl1e_get_settings(struct net_device *netdev, + struct ethtool_cmd *ecmd) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + struct atl1e_hw *hw = &adapter->hw; + + ecmd->supported = (SUPPORTED_10baseT_Half | + SUPPORTED_10baseT_Full | + SUPPORTED_100baseT_Half | + SUPPORTED_100baseT_Full | + SUPPORTED_Autoneg | + SUPPORTED_TP); + if (hw->nic_type == athr_l1e) + ecmd->supported |= SUPPORTED_1000baseT_Full; + + ecmd->advertising = ADVERTISED_TP; + + ecmd->advertising |= ADVERTISED_Autoneg; + ecmd->advertising |= hw->autoneg_advertised; + + ecmd->port = PORT_TP; + ecmd->phy_address = 0; + ecmd->transceiver = XCVR_INTERNAL; + + if (adapter->link_speed != SPEED_0) { + ethtool_cmd_speed_set(ecmd, adapter->link_speed); + if (adapter->link_duplex == FULL_DUPLEX) + ecmd->duplex = DUPLEX_FULL; + else + ecmd->duplex = DUPLEX_HALF; + } else { + ethtool_cmd_speed_set(ecmd, -1); + ecmd->duplex = -1; + } + + ecmd->autoneg = AUTONEG_ENABLE; + return 0; +} + +static int atl1e_set_settings(struct net_device *netdev, + struct ethtool_cmd *ecmd) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + struct atl1e_hw *hw = &adapter->hw; + + while (test_and_set_bit(__AT_RESETTING, &adapter->flags)) + msleep(1); + + if (ecmd->autoneg == AUTONEG_ENABLE) { + u16 adv4, adv9; + + if ((ecmd->advertising&ADVERTISE_1000_FULL)) { + if (hw->nic_type == athr_l1e) { + hw->autoneg_advertised = + ecmd->advertising & AT_ADV_MASK; + } else { + clear_bit(__AT_RESETTING, &adapter->flags); + return -EINVAL; + } + } else if (ecmd->advertising&ADVERTISE_1000_HALF) { + clear_bit(__AT_RESETTING, &adapter->flags); + return -EINVAL; + } else { + hw->autoneg_advertised = + ecmd->advertising & AT_ADV_MASK; + } + ecmd->advertising = hw->autoneg_advertised | + ADVERTISED_TP | ADVERTISED_Autoneg; + + adv4 = hw->mii_autoneg_adv_reg & ~ADVERTISE_ALL; + adv9 = hw->mii_1000t_ctrl_reg & ~MII_AT001_CR_1000T_SPEED_MASK; + if (hw->autoneg_advertised & ADVERTISE_10_HALF) + adv4 |= ADVERTISE_10HALF; + if (hw->autoneg_advertised & ADVERTISE_10_FULL) + adv4 |= ADVERTISE_10FULL; + if (hw->autoneg_advertised & ADVERTISE_100_HALF) + adv4 |= ADVERTISE_100HALF; + if (hw->autoneg_advertised & ADVERTISE_100_FULL) + adv4 |= ADVERTISE_100FULL; + if (hw->autoneg_advertised & ADVERTISE_1000_FULL) + adv9 |= ADVERTISE_1000FULL; + + if (adv4 != hw->mii_autoneg_adv_reg || + adv9 != hw->mii_1000t_ctrl_reg) { + hw->mii_autoneg_adv_reg = adv4; + hw->mii_1000t_ctrl_reg = adv9; + hw->re_autoneg = true; + } + + } else { + clear_bit(__AT_RESETTING, &adapter->flags); + return -EINVAL; + } + + /* reset the link */ + + if (netif_running(adapter->netdev)) { + atl1e_down(adapter); + atl1e_up(adapter); + } else + atl1e_reset_hw(&adapter->hw); + + clear_bit(__AT_RESETTING, &adapter->flags); + return 0; +} + +static u32 atl1e_get_msglevel(struct net_device *netdev) +{ +#ifdef DBG + return 1; +#else + return 0; +#endif +} + +static int atl1e_get_regs_len(struct net_device *netdev) +{ + return AT_REGS_LEN * sizeof(u32); +} + +static void atl1e_get_regs(struct net_device *netdev, + struct ethtool_regs *regs, void *p) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + struct atl1e_hw *hw = &adapter->hw; + u32 *regs_buff = p; + u16 phy_data; + + memset(p, 0, AT_REGS_LEN * sizeof(u32)); + + regs->version = (1 << 24) | (hw->revision_id << 16) | hw->device_id; + + regs_buff[0] = AT_READ_REG(hw, REG_VPD_CAP); + regs_buff[1] = AT_READ_REG(hw, REG_SPI_FLASH_CTRL); + regs_buff[2] = AT_READ_REG(hw, REG_SPI_FLASH_CONFIG); + regs_buff[3] = AT_READ_REG(hw, REG_TWSI_CTRL); + regs_buff[4] = AT_READ_REG(hw, REG_PCIE_DEV_MISC_CTRL); + regs_buff[5] = AT_READ_REG(hw, REG_MASTER_CTRL); + regs_buff[6] = AT_READ_REG(hw, REG_MANUAL_TIMER_INIT); + regs_buff[7] = AT_READ_REG(hw, REG_IRQ_MODU_TIMER_INIT); + regs_buff[8] = AT_READ_REG(hw, REG_GPHY_CTRL); + regs_buff[9] = AT_READ_REG(hw, REG_CMBDISDMA_TIMER); + regs_buff[10] = AT_READ_REG(hw, REG_IDLE_STATUS); + regs_buff[11] = AT_READ_REG(hw, REG_MDIO_CTRL); + regs_buff[12] = AT_READ_REG(hw, REG_SERDES_LOCK); + regs_buff[13] = AT_READ_REG(hw, REG_MAC_CTRL); + regs_buff[14] = AT_READ_REG(hw, REG_MAC_IPG_IFG); + regs_buff[15] = AT_READ_REG(hw, REG_MAC_STA_ADDR); + regs_buff[16] = AT_READ_REG(hw, REG_MAC_STA_ADDR+4); + regs_buff[17] = AT_READ_REG(hw, REG_RX_HASH_TABLE); + regs_buff[18] = AT_READ_REG(hw, REG_RX_HASH_TABLE+4); + regs_buff[19] = AT_READ_REG(hw, REG_MAC_HALF_DUPLX_CTRL); + regs_buff[20] = AT_READ_REG(hw, REG_MTU); + regs_buff[21] = AT_READ_REG(hw, REG_WOL_CTRL); + regs_buff[22] = AT_READ_REG(hw, REG_SRAM_TRD_ADDR); + regs_buff[23] = AT_READ_REG(hw, REG_SRAM_TRD_LEN); + regs_buff[24] = AT_READ_REG(hw, REG_SRAM_RXF_ADDR); + regs_buff[25] = AT_READ_REG(hw, REG_SRAM_RXF_LEN); + regs_buff[26] = AT_READ_REG(hw, REG_SRAM_TXF_ADDR); + regs_buff[27] = AT_READ_REG(hw, REG_SRAM_TXF_LEN); + regs_buff[28] = AT_READ_REG(hw, REG_SRAM_TCPH_ADDR); + regs_buff[29] = AT_READ_REG(hw, REG_SRAM_PKTH_ADDR); + + atl1e_read_phy_reg(hw, MII_BMCR, &phy_data); + regs_buff[73] = (u32)phy_data; + atl1e_read_phy_reg(hw, MII_BMSR, &phy_data); + regs_buff[74] = (u32)phy_data; +} + +static int atl1e_get_eeprom_len(struct net_device *netdev) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + + if (!atl1e_check_eeprom_exist(&adapter->hw)) + return AT_EEPROM_LEN; + else + return 0; +} + +static int atl1e_get_eeprom(struct net_device *netdev, + struct ethtool_eeprom *eeprom, u8 *bytes) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + struct atl1e_hw *hw = &adapter->hw; + u32 *eeprom_buff; + int first_dword, last_dword; + int ret_val = 0; + int i; + + if (eeprom->len == 0) + return -EINVAL; + + if (atl1e_check_eeprom_exist(hw)) /* not exist */ + return -EINVAL; + + eeprom->magic = hw->vendor_id | (hw->device_id << 16); + + first_dword = eeprom->offset >> 2; + last_dword = (eeprom->offset + eeprom->len - 1) >> 2; + + eeprom_buff = kmalloc(sizeof(u32) * + (last_dword - first_dword + 1), GFP_KERNEL); + if (eeprom_buff == NULL) + return -ENOMEM; + + for (i = first_dword; i < last_dword; i++) { + if (!atl1e_read_eeprom(hw, i * 4, &(eeprom_buff[i-first_dword]))) { + kfree(eeprom_buff); + return -EIO; + } + } + + memcpy(bytes, (u8 *)eeprom_buff + (eeprom->offset & 3), + eeprom->len); + kfree(eeprom_buff); + + return ret_val; +} + +static int atl1e_set_eeprom(struct net_device *netdev, + struct ethtool_eeprom *eeprom, u8 *bytes) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + struct atl1e_hw *hw = &adapter->hw; + u32 *eeprom_buff; + u32 *ptr; + int first_dword, last_dword; + int ret_val = 0; + int i; + + if (eeprom->len == 0) + return -EOPNOTSUPP; + + if (eeprom->magic != (hw->vendor_id | (hw->device_id << 16))) + return -EINVAL; + + first_dword = eeprom->offset >> 2; + last_dword = (eeprom->offset + eeprom->len - 1) >> 2; + eeprom_buff = kmalloc(AT_EEPROM_LEN, GFP_KERNEL); + if (eeprom_buff == NULL) + return -ENOMEM; + + ptr = eeprom_buff; + + if (eeprom->offset & 3) { + /* need read/modify/write of first changed EEPROM word */ + /* only the second byte of the word is being modified */ + if (!atl1e_read_eeprom(hw, first_dword * 4, &(eeprom_buff[0]))) { + ret_val = -EIO; + goto out; + } + ptr++; + } + if (((eeprom->offset + eeprom->len) & 3)) { + /* need read/modify/write of last changed EEPROM word */ + /* only the first byte of the word is being modified */ + + if (!atl1e_read_eeprom(hw, last_dword * 4, + &(eeprom_buff[last_dword - first_dword]))) { + ret_val = -EIO; + goto out; + } + } + + /* Device's eeprom is always little-endian, word addressable */ + memcpy(ptr, bytes, eeprom->len); + + for (i = 0; i < last_dword - first_dword + 1; i++) { + if (!atl1e_write_eeprom(hw, ((first_dword + i) * 4), + eeprom_buff[i])) { + ret_val = -EIO; + goto out; + } + } +out: + kfree(eeprom_buff); + return ret_val; +} + +static void atl1e_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *drvinfo) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + + strlcpy(drvinfo->driver, atl1e_driver_name, sizeof(drvinfo->driver)); + strlcpy(drvinfo->version, atl1e_driver_version, + sizeof(drvinfo->version)); + strlcpy(drvinfo->fw_version, "L1e", sizeof(drvinfo->fw_version)); + strlcpy(drvinfo->bus_info, pci_name(adapter->pdev), + sizeof(drvinfo->bus_info)); + drvinfo->n_stats = 0; + drvinfo->testinfo_len = 0; + drvinfo->regdump_len = atl1e_get_regs_len(netdev); + drvinfo->eedump_len = atl1e_get_eeprom_len(netdev); +} + +static void atl1e_get_wol(struct net_device *netdev, + struct ethtool_wolinfo *wol) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + + wol->supported = WAKE_MAGIC | WAKE_PHY; + wol->wolopts = 0; + + if (adapter->wol & AT_WUFC_EX) + wol->wolopts |= WAKE_UCAST; + if (adapter->wol & AT_WUFC_MC) + wol->wolopts |= WAKE_MCAST; + if (adapter->wol & AT_WUFC_BC) + wol->wolopts |= WAKE_BCAST; + if (adapter->wol & AT_WUFC_MAG) + wol->wolopts |= WAKE_MAGIC; + if (adapter->wol & AT_WUFC_LNKC) + wol->wolopts |= WAKE_PHY; +} + +static int atl1e_set_wol(struct net_device *netdev, struct ethtool_wolinfo *wol) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + + if (wol->wolopts & (WAKE_ARP | WAKE_MAGICSECURE | + WAKE_UCAST | WAKE_MCAST | WAKE_BCAST)) + return -EOPNOTSUPP; + /* these settings will always override what we currently have */ + adapter->wol = 0; + + if (wol->wolopts & WAKE_MAGIC) + adapter->wol |= AT_WUFC_MAG; + if (wol->wolopts & WAKE_PHY) + adapter->wol |= AT_WUFC_LNKC; + + device_set_wakeup_enable(&adapter->pdev->dev, adapter->wol); + + return 0; +} + +static int atl1e_nway_reset(struct net_device *netdev) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + if (netif_running(netdev)) + atl1e_reinit_locked(adapter); + return 0; +} + +static const struct ethtool_ops atl1e_ethtool_ops = { + .get_settings = atl1e_get_settings, + .set_settings = atl1e_set_settings, + .get_drvinfo = atl1e_get_drvinfo, + .get_regs_len = atl1e_get_regs_len, + .get_regs = atl1e_get_regs, + .get_wol = atl1e_get_wol, + .set_wol = atl1e_set_wol, + .get_msglevel = atl1e_get_msglevel, + .nway_reset = atl1e_nway_reset, + .get_link = ethtool_op_get_link, + .get_eeprom_len = atl1e_get_eeprom_len, + .get_eeprom = atl1e_get_eeprom, + .set_eeprom = atl1e_set_eeprom, +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) + .set_tx_csum = ethtool_op_set_tx_hw_csum, + .set_sg = ethtool_op_set_sg, + .set_tso = ethtool_op_set_tso, +#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) */ +}; + +void atl1e_set_ethtool_ops(struct net_device *netdev) +{ + SET_ETHTOOL_OPS(netdev, &atl1e_ethtool_ops); +} diff --git a/drivers/net/ethernet/atheros/atl1e/atl1e_hw.c b/drivers/net/ethernet/atheros/atl1e/atl1e_hw.c new file mode 100644 index 0000000..923063d --- /dev/null +++ b/drivers/net/ethernet/atheros/atl1e/atl1e_hw.c @@ -0,0 +1,651 @@ +/* + * Copyright(c) 2007 Atheros Corporation. All rights reserved. + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include +#include +#include +#include + +#include "atl1e.h" + +/* + * check_eeprom_exist + * return 0 if eeprom exist + */ +int atl1e_check_eeprom_exist(struct atl1e_hw *hw) +{ + u32 value; + + value = AT_READ_REG(hw, REG_SPI_FLASH_CTRL); + if (value & SPI_FLASH_CTRL_EN_VPD) { + value &= ~SPI_FLASH_CTRL_EN_VPD; + AT_WRITE_REG(hw, REG_SPI_FLASH_CTRL, value); + } + value = AT_READ_REGW(hw, REG_PCIE_CAP_LIST); + return ((value & 0xFF00) == 0x6C00) ? 0 : 1; +} + +void atl1e_hw_set_mac_addr(struct atl1e_hw *hw) +{ + u32 value; + /* + * 00-0B-6A-F6-00-DC + * 0: 6AF600DC 1: 000B + * low dword + */ + value = (((u32)hw->mac_addr[2]) << 24) | + (((u32)hw->mac_addr[3]) << 16) | + (((u32)hw->mac_addr[4]) << 8) | + (((u32)hw->mac_addr[5])) ; + AT_WRITE_REG_ARRAY(hw, REG_MAC_STA_ADDR, 0, value); + /* hight dword */ + value = (((u32)hw->mac_addr[0]) << 8) | + (((u32)hw->mac_addr[1])) ; + AT_WRITE_REG_ARRAY(hw, REG_MAC_STA_ADDR, 1, value); +} + +/* + * atl1e_get_permanent_address + * return 0 if get valid mac address, + */ +static int atl1e_get_permanent_address(struct atl1e_hw *hw) +{ + u32 addr[2]; + u32 i; + u32 twsi_ctrl_data; + u8 eth_addr[ETH_ALEN]; + + if (is_valid_ether_addr(hw->perm_mac_addr)) + return 0; + + /* init */ + addr[0] = addr[1] = 0; + + if (!atl1e_check_eeprom_exist(hw)) { + /* eeprom exist */ + twsi_ctrl_data = AT_READ_REG(hw, REG_TWSI_CTRL); + twsi_ctrl_data |= TWSI_CTRL_SW_LDSTART; + AT_WRITE_REG(hw, REG_TWSI_CTRL, twsi_ctrl_data); + for (i = 0; i < AT_TWSI_EEPROM_TIMEOUT; i++) { + msleep(10); + twsi_ctrl_data = AT_READ_REG(hw, REG_TWSI_CTRL); + if ((twsi_ctrl_data & TWSI_CTRL_SW_LDSTART) == 0) + break; + } + if (i >= AT_TWSI_EEPROM_TIMEOUT) + return AT_ERR_TIMEOUT; + } + + /* maybe MAC-address is from BIOS */ + addr[0] = AT_READ_REG(hw, REG_MAC_STA_ADDR); + addr[1] = AT_READ_REG(hw, REG_MAC_STA_ADDR + 4); + *(u32 *) ð_addr[2] = swab32(addr[0]); + *(u16 *) ð_addr[0] = swab16(*(u16 *)&addr[1]); + + if (is_valid_ether_addr(eth_addr)) { + memcpy(hw->perm_mac_addr, eth_addr, ETH_ALEN); + return 0; + } + + return AT_ERR_EEPROM; +} + +bool atl1e_write_eeprom(struct atl1e_hw *hw, u32 offset, u32 value) +{ + return true; +} + +bool atl1e_read_eeprom(struct atl1e_hw *hw, u32 offset, u32 *p_value) +{ + int i; + u32 control; + + if (offset & 3) + return false; /* address do not align */ + + AT_WRITE_REG(hw, REG_VPD_DATA, 0); + control = (offset & VPD_CAP_VPD_ADDR_MASK) << VPD_CAP_VPD_ADDR_SHIFT; + AT_WRITE_REG(hw, REG_VPD_CAP, control); + + for (i = 0; i < 10; i++) { + msleep(2); + control = AT_READ_REG(hw, REG_VPD_CAP); + if (control & VPD_CAP_VPD_FLAG) + break; + } + if (control & VPD_CAP_VPD_FLAG) { + *p_value = AT_READ_REG(hw, REG_VPD_DATA); + return true; + } + return false; /* timeout */ +} + +void atl1e_force_ps(struct atl1e_hw *hw) +{ + AT_WRITE_REGW(hw, REG_GPHY_CTRL, + GPHY_CTRL_PW_WOL_DIS | GPHY_CTRL_EXT_RESET); +} + +/* + * Reads the adapter's MAC address from the EEPROM + * + * hw - Struct containing variables accessed by shared code + */ +int atl1e_read_mac_addr(struct atl1e_hw *hw) +{ + int err = 0; + + err = atl1e_get_permanent_address(hw); + if (err) + return AT_ERR_EEPROM; + memcpy(hw->mac_addr, hw->perm_mac_addr, sizeof(hw->perm_mac_addr)); + return 0; +} + +/* + * atl1e_hash_mc_addr + * purpose + * set hash value for a multicast address + */ +u32 atl1e_hash_mc_addr(struct atl1e_hw *hw, u8 *mc_addr) +{ + u32 crc32; + u32 value = 0; + int i; + + crc32 = ether_crc_le(6, mc_addr); + for (i = 0; i < 32; i++) + value |= (((crc32 >> i) & 1) << (31 - i)); + + return value; +} + +/* + * Sets the bit in the multicast table corresponding to the hash value. + * hw - Struct containing variables accessed by shared code + * hash_value - Multicast address hash value + */ +void atl1e_hash_set(struct atl1e_hw *hw, u32 hash_value) +{ + u32 hash_bit, hash_reg; + u32 mta; + + /* + * The HASH Table is a register array of 2 32-bit registers. + * It is treated like an array of 64 bits. We want to set + * bit BitArray[hash_value]. So we figure out what register + * the bit is in, read it, OR in the new bit, then write + * back the new value. The register is determined by the + * upper 7 bits of the hash value and the bit within that + * register are determined by the lower 5 bits of the value. + */ + hash_reg = (hash_value >> 31) & 0x1; + hash_bit = (hash_value >> 26) & 0x1F; + + mta = AT_READ_REG_ARRAY(hw, REG_RX_HASH_TABLE, hash_reg); + + mta |= (1 << hash_bit); + + AT_WRITE_REG_ARRAY(hw, REG_RX_HASH_TABLE, hash_reg, mta); +} +/* + * Reads the value from a PHY register + * hw - Struct containing variables accessed by shared code + * reg_addr - address of the PHY register to read + */ +int atl1e_read_phy_reg(struct atl1e_hw *hw, u16 reg_addr, u16 *phy_data) +{ + u32 val; + int i; + + val = ((u32)(reg_addr & MDIO_REG_ADDR_MASK)) << MDIO_REG_ADDR_SHIFT | + MDIO_START | MDIO_SUP_PREAMBLE | MDIO_RW | + MDIO_CLK_25_4 << MDIO_CLK_SEL_SHIFT; + + AT_WRITE_REG(hw, REG_MDIO_CTRL, val); + + wmb(); + + for (i = 0; i < MDIO_WAIT_TIMES; i++) { + udelay(2); + val = AT_READ_REG(hw, REG_MDIO_CTRL); + if (!(val & (MDIO_START | MDIO_BUSY))) + break; + wmb(); + } + if (!(val & (MDIO_START | MDIO_BUSY))) { + *phy_data = (u16)val; + return 0; + } + + return AT_ERR_PHY; +} + +/* + * Writes a value to a PHY register + * hw - Struct containing variables accessed by shared code + * reg_addr - address of the PHY register to write + * data - data to write to the PHY + */ +int atl1e_write_phy_reg(struct atl1e_hw *hw, u32 reg_addr, u16 phy_data) +{ + int i; + u32 val; + + val = ((u32)(phy_data & MDIO_DATA_MASK)) << MDIO_DATA_SHIFT | + (reg_addr&MDIO_REG_ADDR_MASK) << MDIO_REG_ADDR_SHIFT | + MDIO_SUP_PREAMBLE | + MDIO_START | + MDIO_CLK_25_4 << MDIO_CLK_SEL_SHIFT; + + AT_WRITE_REG(hw, REG_MDIO_CTRL, val); + wmb(); + + for (i = 0; i < MDIO_WAIT_TIMES; i++) { + udelay(2); + val = AT_READ_REG(hw, REG_MDIO_CTRL); + if (!(val & (MDIO_START | MDIO_BUSY))) + break; + wmb(); + } + + if (!(val & (MDIO_START | MDIO_BUSY))) + return 0; + + return AT_ERR_PHY; +} + +/* + * atl1e_init_pcie - init PCIE module + */ +static void atl1e_init_pcie(struct atl1e_hw *hw) +{ + u32 value; + /* comment 2lines below to save more power when sususpend + value = LTSSM_TEST_MODE_DEF; + AT_WRITE_REG(hw, REG_LTSSM_TEST_MODE, value); + */ + + /* pcie flow control mode change */ + value = AT_READ_REG(hw, 0x1008); + value |= 0x8000; + AT_WRITE_REG(hw, 0x1008, value); +} +/* + * Configures PHY autoneg and flow control advertisement settings + * + * hw - Struct containing variables accessed by shared code + */ +static int atl1e_phy_setup_autoneg_adv(struct atl1e_hw *hw) +{ + s32 ret_val; + u16 mii_autoneg_adv_reg; + u16 mii_1000t_ctrl_reg; + + if (0 != hw->mii_autoneg_adv_reg) + return 0; + /* Read the MII Auto-Neg Advertisement Register (Address 4/9). */ + mii_autoneg_adv_reg = MII_AR_DEFAULT_CAP_MASK; + mii_1000t_ctrl_reg = MII_AT001_CR_1000T_DEFAULT_CAP_MASK; + + /* + * Need to parse autoneg_advertised and set up + * the appropriate PHY registers. First we will parse for + * autoneg_advertised software override. Since we can advertise + * a plethora of combinations, we need to check each bit + * individually. + */ + + /* + * First we clear all the 10/100 mb speed bits in the Auto-Neg + * Advertisement Register (Address 4) and the 1000 mb speed bits in + * the 1000Base-T control Register (Address 9). + */ + mii_autoneg_adv_reg &= ~ADVERTISE_ALL; + mii_1000t_ctrl_reg &= ~MII_AT001_CR_1000T_SPEED_MASK; + + /* + * Need to parse MediaType and setup the + * appropriate PHY registers. + */ + switch (hw->media_type) { + case MEDIA_TYPE_AUTO_SENSOR: + mii_autoneg_adv_reg |= ADVERTISE_ALL; + hw->autoneg_advertised = ADVERTISE_ALL; + if (hw->nic_type == athr_l1e) { + mii_1000t_ctrl_reg |= ADVERTISE_1000FULL; + hw->autoneg_advertised |= ADVERTISE_1000_FULL; + } + break; + + case MEDIA_TYPE_100M_FULL: + mii_autoneg_adv_reg |= ADVERTISE_100FULL; + hw->autoneg_advertised = ADVERTISE_100_FULL; + break; + + case MEDIA_TYPE_100M_HALF: + mii_autoneg_adv_reg |= ADVERTISE_100_HALF; + hw->autoneg_advertised = ADVERTISE_100_HALF; + break; + + case MEDIA_TYPE_10M_FULL: + mii_autoneg_adv_reg |= ADVERTISE_10_FULL; + hw->autoneg_advertised = ADVERTISE_10_FULL; + break; + + default: + mii_autoneg_adv_reg |= ADVERTISE_10_HALF; + hw->autoneg_advertised = ADVERTISE_10_HALF; + break; + } + + /* flow control fixed to enable all */ + mii_autoneg_adv_reg |= (ADVERTISE_PAUSE_ASYM | ADVERTISE_PAUSE_CAP); + + hw->mii_autoneg_adv_reg = mii_autoneg_adv_reg; + hw->mii_1000t_ctrl_reg = mii_1000t_ctrl_reg; + + ret_val = atl1e_write_phy_reg(hw, MII_ADVERTISE, mii_autoneg_adv_reg); + if (ret_val) + return ret_val; + + if (hw->nic_type == athr_l1e || hw->nic_type == athr_l2e_revA) { + ret_val = atl1e_write_phy_reg(hw, MII_CTRL1000, + mii_1000t_ctrl_reg); + if (ret_val) + return ret_val; + } + + return 0; +} + + +/* + * Resets the PHY and make all config validate + * + * hw - Struct containing variables accessed by shared code + * + * Sets bit 15 and 12 of the MII control regiser (for F001 bug) + */ +int atl1e_phy_commit(struct atl1e_hw *hw) +{ + struct atl1e_adapter *adapter = hw->adapter; + int ret_val; + u16 phy_data; + + phy_data = BMCR_RESET | BMCR_ANENABLE | BMCR_ANRESTART; + + ret_val = atl1e_write_phy_reg(hw, MII_BMCR, phy_data); + if (ret_val) { + u32 val; + int i; + /************************************** + * pcie serdes link may be down ! + **************************************/ + for (i = 0; i < 25; i++) { + msleep(1); + val = AT_READ_REG(hw, REG_MDIO_CTRL); + if (!(val & (MDIO_START | MDIO_BUSY))) + break; + } + + if (0 != (val & (MDIO_START | MDIO_BUSY))) { + netdev_err(adapter->netdev, + "pcie linkdown at least for 25ms\n"); + return ret_val; + } + + netdev_err(adapter->netdev, "pcie linkup after %d ms\n", i); + } + return 0; +} + +int atl1e_phy_init(struct atl1e_hw *hw) +{ + struct atl1e_adapter *adapter = hw->adapter; + s32 ret_val; + u16 phy_val; + + if (hw->phy_configured) { + if (hw->re_autoneg) { + hw->re_autoneg = false; + return atl1e_restart_autoneg(hw); + } + return 0; + } + + /* RESET GPHY Core */ + AT_WRITE_REGW(hw, REG_GPHY_CTRL, GPHY_CTRL_DEFAULT); + msleep(2); + AT_WRITE_REGW(hw, REG_GPHY_CTRL, GPHY_CTRL_DEFAULT | + GPHY_CTRL_EXT_RESET); + msleep(2); + + /* patches */ + /* p1. eable hibernation mode */ + ret_val = atl1e_write_phy_reg(hw, MII_DBG_ADDR, 0xB); + if (ret_val) + return ret_val; + ret_val = atl1e_write_phy_reg(hw, MII_DBG_DATA, 0xBC00); + if (ret_val) + return ret_val; + /* p2. set Class A/B for all modes */ + ret_val = atl1e_write_phy_reg(hw, MII_DBG_ADDR, 0); + if (ret_val) + return ret_val; + phy_val = 0x02ef; + /* remove Class AB */ + /* phy_val = hw->emi_ca ? 0x02ef : 0x02df; */ + ret_val = atl1e_write_phy_reg(hw, MII_DBG_DATA, phy_val); + if (ret_val) + return ret_val; + /* p3. 10B ??? */ + ret_val = atl1e_write_phy_reg(hw, MII_DBG_ADDR, 0x12); + if (ret_val) + return ret_val; + ret_val = atl1e_write_phy_reg(hw, MII_DBG_DATA, 0x4C04); + if (ret_val) + return ret_val; + /* p4. 1000T power */ + ret_val = atl1e_write_phy_reg(hw, MII_DBG_ADDR, 0x4); + if (ret_val) + return ret_val; + ret_val = atl1e_write_phy_reg(hw, MII_DBG_DATA, 0x8BBB); + if (ret_val) + return ret_val; + + ret_val = atl1e_write_phy_reg(hw, MII_DBG_ADDR, 0x5); + if (ret_val) + return ret_val; + ret_val = atl1e_write_phy_reg(hw, MII_DBG_DATA, 0x2C46); + if (ret_val) + return ret_val; + + msleep(1); + + /*Enable PHY LinkChange Interrupt */ + ret_val = atl1e_write_phy_reg(hw, MII_INT_CTRL, 0xC00); + if (ret_val) { + netdev_err(adapter->netdev, + "Error enable PHY linkChange Interrupt\n"); + return ret_val; + } + /* setup AutoNeg parameters */ + ret_val = atl1e_phy_setup_autoneg_adv(hw); + if (ret_val) { + netdev_err(adapter->netdev, + "Error Setting up Auto-Negotiation\n"); + return ret_val; + } + /* SW.Reset & En-Auto-Neg to restart Auto-Neg*/ + netdev_dbg(adapter->netdev, "Restarting Auto-Negotiation\n"); + ret_val = atl1e_phy_commit(hw); + if (ret_val) { + netdev_err(adapter->netdev, "Error resetting the phy\n"); + return ret_val; + } + + hw->phy_configured = true; + + return 0; +} + +/* + * Reset the transmit and receive units; mask and clear all interrupts. + * hw - Struct containing variables accessed by shared code + * return : 0 or idle status (if error) + */ +int atl1e_reset_hw(struct atl1e_hw *hw) +{ + struct atl1e_adapter *adapter = hw->adapter; + struct pci_dev *pdev = adapter->pdev; + + u32 idle_status_data = 0; + u16 pci_cfg_cmd_word = 0; + int timeout = 0; + + /* Workaround for PCI problem when BIOS sets MMRBC incorrectly. */ + pci_read_config_word(pdev, PCI_REG_COMMAND, &pci_cfg_cmd_word); + if ((pci_cfg_cmd_word & (CMD_IO_SPACE | + CMD_MEMORY_SPACE | CMD_BUS_MASTER)) + != (CMD_IO_SPACE | CMD_MEMORY_SPACE | CMD_BUS_MASTER)) { + pci_cfg_cmd_word |= (CMD_IO_SPACE | + CMD_MEMORY_SPACE | CMD_BUS_MASTER); + pci_write_config_word(pdev, PCI_REG_COMMAND, pci_cfg_cmd_word); + } + + /* + * Issue Soft Reset to the MAC. This will reset the chip's + * transmit, receive, DMA. It will not effect + * the current PCI configuration. The global reset bit is self- + * clearing, and should clear within a microsecond. + */ + AT_WRITE_REG(hw, REG_MASTER_CTRL, + MASTER_CTRL_LED_MODE | MASTER_CTRL_SOFT_RST); + wmb(); + msleep(1); + + /* Wait at least 10ms for All module to be Idle */ + for (timeout = 0; timeout < AT_HW_MAX_IDLE_DELAY; timeout++) { + idle_status_data = AT_READ_REG(hw, REG_IDLE_STATUS); + if (idle_status_data == 0) + break; + msleep(1); + cpu_relax(); + } + + if (timeout >= AT_HW_MAX_IDLE_DELAY) { + netdev_err(adapter->netdev, + "MAC state machine can't be idle since disabled for 10ms second\n"); + return AT_ERR_TIMEOUT; + } + + return 0; +} + + +/* + * Performs basic configuration of the adapter. + * + * hw - Struct containing variables accessed by shared code + * Assumes that the controller has previously been reset and is in a + * post-reset uninitialized state. Initializes multicast table, + * and Calls routines to setup link + * Leaves the transmit and receive units disabled and uninitialized. + */ +int atl1e_init_hw(struct atl1e_hw *hw) +{ + s32 ret_val = 0; + + atl1e_init_pcie(hw); + + /* Zero out the Multicast HASH table */ + /* clear the old settings from the multicast hash table */ + AT_WRITE_REG(hw, REG_RX_HASH_TABLE, 0); + AT_WRITE_REG_ARRAY(hw, REG_RX_HASH_TABLE, 1, 0); + + ret_val = atl1e_phy_init(hw); + + return ret_val; +} + +/* + * Detects the current speed and duplex settings of the hardware. + * + * hw - Struct containing variables accessed by shared code + * speed - Speed of the connection + * duplex - Duplex setting of the connection + */ +int atl1e_get_speed_and_duplex(struct atl1e_hw *hw, u16 *speed, u16 *duplex) +{ + int err; + u16 phy_data; + + /* Read PHY Specific Status Register (17) */ + err = atl1e_read_phy_reg(hw, MII_AT001_PSSR, &phy_data); + if (err) + return err; + + if (!(phy_data & MII_AT001_PSSR_SPD_DPLX_RESOLVED)) + return AT_ERR_PHY_RES; + + switch (phy_data & MII_AT001_PSSR_SPEED) { + case MII_AT001_PSSR_1000MBS: + *speed = SPEED_1000; + break; + case MII_AT001_PSSR_100MBS: + *speed = SPEED_100; + break; + case MII_AT001_PSSR_10MBS: + *speed = SPEED_10; + break; + default: + return AT_ERR_PHY_SPEED; + break; + } + + if (phy_data & MII_AT001_PSSR_DPLX) + *duplex = FULL_DUPLEX; + else + *duplex = HALF_DUPLEX; + + return 0; +} + +int atl1e_restart_autoneg(struct atl1e_hw *hw) +{ + int err = 0; + + err = atl1e_write_phy_reg(hw, MII_ADVERTISE, hw->mii_autoneg_adv_reg); + if (err) + return err; + + if (hw->nic_type == athr_l1e || hw->nic_type == athr_l2e_revA) { + err = atl1e_write_phy_reg(hw, MII_CTRL1000, + hw->mii_1000t_ctrl_reg); + if (err) + return err; + } + + err = atl1e_write_phy_reg(hw, MII_BMCR, + BMCR_RESET | BMCR_ANENABLE | BMCR_ANRESTART); + return err; +} + diff --git a/drivers/net/ethernet/atheros/atl1e/atl1e_hw.h b/drivers/net/ethernet/atheros/atl1e/atl1e_hw.h new file mode 100644 index 0000000..74df16a --- /dev/null +++ b/drivers/net/ethernet/atheros/atl1e/atl1e_hw.h @@ -0,0 +1,690 @@ +/* + * Copyright(c) 2007 Atheros Corporation. All rights reserved. + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _ATHL1E_HW_H_ +#define _ATHL1E_HW_H_ + +#include +#include + +struct atl1e_adapter; +struct atl1e_hw; + +/* function prototype */ +s32 atl1e_reset_hw(struct atl1e_hw *hw); +s32 atl1e_read_mac_addr(struct atl1e_hw *hw); +s32 atl1e_init_hw(struct atl1e_hw *hw); +s32 atl1e_phy_commit(struct atl1e_hw *hw); +s32 atl1e_get_speed_and_duplex(struct atl1e_hw *hw, u16 *speed, u16 *duplex); +u32 atl1e_auto_get_fc(struct atl1e_adapter *adapter, u16 duplex); +u32 atl1e_hash_mc_addr(struct atl1e_hw *hw, u8 *mc_addr); +void atl1e_hash_set(struct atl1e_hw *hw, u32 hash_value); +s32 atl1e_read_phy_reg(struct atl1e_hw *hw, u16 reg_addr, u16 *phy_data); +s32 atl1e_write_phy_reg(struct atl1e_hw *hw, u32 reg_addr, u16 phy_data); +s32 atl1e_validate_mdi_setting(struct atl1e_hw *hw); +void atl1e_hw_set_mac_addr(struct atl1e_hw *hw); +bool atl1e_read_eeprom(struct atl1e_hw *hw, u32 offset, u32 *p_value); +bool atl1e_write_eeprom(struct atl1e_hw *hw, u32 offset, u32 value); +s32 atl1e_phy_enter_power_saving(struct atl1e_hw *hw); +s32 atl1e_phy_leave_power_saving(struct atl1e_hw *hw); +s32 atl1e_phy_init(struct atl1e_hw *hw); +int atl1e_check_eeprom_exist(struct atl1e_hw *hw); +void atl1e_force_ps(struct atl1e_hw *hw); +s32 atl1e_restart_autoneg(struct atl1e_hw *hw); + +/* register definition */ +#define REG_PM_CTRLSTAT 0x44 + +#define REG_PCIE_CAP_LIST 0x58 + +#define REG_DEVICE_CAP 0x5C +#define DEVICE_CAP_MAX_PAYLOAD_MASK 0x7 +#define DEVICE_CAP_MAX_PAYLOAD_SHIFT 0 + +#define REG_DEVICE_CTRL 0x60 +#define DEVICE_CTRL_MAX_PAYLOAD_MASK 0x7 +#define DEVICE_CTRL_MAX_PAYLOAD_SHIFT 5 +#define DEVICE_CTRL_MAX_RREQ_SZ_MASK 0x7 +#define DEVICE_CTRL_MAX_RREQ_SZ_SHIFT 12 + +#define REG_VPD_CAP 0x6C +#define VPD_CAP_ID_MASK 0xff +#define VPD_CAP_ID_SHIFT 0 +#define VPD_CAP_NEXT_PTR_MASK 0xFF +#define VPD_CAP_NEXT_PTR_SHIFT 8 +#define VPD_CAP_VPD_ADDR_MASK 0x7FFF +#define VPD_CAP_VPD_ADDR_SHIFT 16 +#define VPD_CAP_VPD_FLAG 0x80000000 + +#define REG_VPD_DATA 0x70 + +#define REG_SPI_FLASH_CTRL 0x200 +#define SPI_FLASH_CTRL_STS_NON_RDY 0x1 +#define SPI_FLASH_CTRL_STS_WEN 0x2 +#define SPI_FLASH_CTRL_STS_WPEN 0x80 +#define SPI_FLASH_CTRL_DEV_STS_MASK 0xFF +#define SPI_FLASH_CTRL_DEV_STS_SHIFT 0 +#define SPI_FLASH_CTRL_INS_MASK 0x7 +#define SPI_FLASH_CTRL_INS_SHIFT 8 +#define SPI_FLASH_CTRL_START 0x800 +#define SPI_FLASH_CTRL_EN_VPD 0x2000 +#define SPI_FLASH_CTRL_LDSTART 0x8000 +#define SPI_FLASH_CTRL_CS_HI_MASK 0x3 +#define SPI_FLASH_CTRL_CS_HI_SHIFT 16 +#define SPI_FLASH_CTRL_CS_HOLD_MASK 0x3 +#define SPI_FLASH_CTRL_CS_HOLD_SHIFT 18 +#define SPI_FLASH_CTRL_CLK_LO_MASK 0x3 +#define SPI_FLASH_CTRL_CLK_LO_SHIFT 20 +#define SPI_FLASH_CTRL_CLK_HI_MASK 0x3 +#define SPI_FLASH_CTRL_CLK_HI_SHIFT 22 +#define SPI_FLASH_CTRL_CS_SETUP_MASK 0x3 +#define SPI_FLASH_CTRL_CS_SETUP_SHIFT 24 +#define SPI_FLASH_CTRL_EROM_PGSZ_MASK 0x3 +#define SPI_FLASH_CTRL_EROM_PGSZ_SHIFT 26 +#define SPI_FLASH_CTRL_WAIT_READY 0x10000000 + +#define REG_SPI_ADDR 0x204 + +#define REG_SPI_DATA 0x208 + +#define REG_SPI_FLASH_CONFIG 0x20C +#define SPI_FLASH_CONFIG_LD_ADDR_MASK 0xFFFFFF +#define SPI_FLASH_CONFIG_LD_ADDR_SHIFT 0 +#define SPI_FLASH_CONFIG_VPD_ADDR_MASK 0x3 +#define SPI_FLASH_CONFIG_VPD_ADDR_SHIFT 24 +#define SPI_FLASH_CONFIG_LD_EXIST 0x4000000 + + +#define REG_SPI_FLASH_OP_PROGRAM 0x210 +#define REG_SPI_FLASH_OP_SC_ERASE 0x211 +#define REG_SPI_FLASH_OP_CHIP_ERASE 0x212 +#define REG_SPI_FLASH_OP_RDID 0x213 +#define REG_SPI_FLASH_OP_WREN 0x214 +#define REG_SPI_FLASH_OP_RDSR 0x215 +#define REG_SPI_FLASH_OP_WRSR 0x216 +#define REG_SPI_FLASH_OP_READ 0x217 + +#define REG_TWSI_CTRL 0x218 +#define TWSI_CTRL_LD_OFFSET_MASK 0xFF +#define TWSI_CTRL_LD_OFFSET_SHIFT 0 +#define TWSI_CTRL_LD_SLV_ADDR_MASK 0x7 +#define TWSI_CTRL_LD_SLV_ADDR_SHIFT 8 +#define TWSI_CTRL_SW_LDSTART 0x800 +#define TWSI_CTRL_HW_LDSTART 0x1000 +#define TWSI_CTRL_SMB_SLV_ADDR_MASK 0x0x7F +#define TWSI_CTRL_SMB_SLV_ADDR_SHIFT 15 +#define TWSI_CTRL_LD_EXIST 0x400000 +#define TWSI_CTRL_READ_FREQ_SEL_MASK 0x3 +#define TWSI_CTRL_READ_FREQ_SEL_SHIFT 23 +#define TWSI_CTRL_FREQ_SEL_100K 0 +#define TWSI_CTRL_FREQ_SEL_200K 1 +#define TWSI_CTRL_FREQ_SEL_300K 2 +#define TWSI_CTRL_FREQ_SEL_400K 3 +#define TWSI_CTRL_SMB_SLV_ADDR +#define TWSI_CTRL_WRITE_FREQ_SEL_MASK 0x3 +#define TWSI_CTRL_WRITE_FREQ_SEL_SHIFT 24 + + +#define REG_PCIE_DEV_MISC_CTRL 0x21C +#define PCIE_DEV_MISC_CTRL_EXT_PIPE 0x2 +#define PCIE_DEV_MISC_CTRL_RETRY_BUFDIS 0x1 +#define PCIE_DEV_MISC_CTRL_SPIROM_EXIST 0x4 +#define PCIE_DEV_MISC_CTRL_SERDES_ENDIAN 0x8 +#define PCIE_DEV_MISC_CTRL_SERDES_SEL_DIN 0x10 + +#define REG_PCIE_PHYMISC 0x1000 +#define PCIE_PHYMISC_FORCE_RCV_DET 0x4 + +#define REG_LTSSM_TEST_MODE 0x12FC +#define LTSSM_TEST_MODE_DEF 0xE000 + +/* Selene Master Control Register */ +#define REG_MASTER_CTRL 0x1400 +#define MASTER_CTRL_SOFT_RST 0x1 +#define MASTER_CTRL_MTIMER_EN 0x2 +#define MASTER_CTRL_ITIMER_EN 0x4 +#define MASTER_CTRL_MANUAL_INT 0x8 +#define MASTER_CTRL_ITIMER2_EN 0x20 +#define MASTER_CTRL_INT_RDCLR 0x40 +#define MASTER_CTRL_LED_MODE 0x200 +#define MASTER_CTRL_REV_NUM_SHIFT 16 +#define MASTER_CTRL_REV_NUM_MASK 0xff +#define MASTER_CTRL_DEV_ID_SHIFT 24 +#define MASTER_CTRL_DEV_ID_MASK 0xff + +/* Timer Initial Value Register */ +#define REG_MANUAL_TIMER_INIT 0x1404 + + +/* IRQ ModeratorTimer Initial Value Register */ +#define REG_IRQ_MODU_TIMER_INIT 0x1408 /* w */ +#define REG_IRQ_MODU_TIMER2_INIT 0x140A /* w */ + + +#define REG_GPHY_CTRL 0x140C +#define GPHY_CTRL_EXT_RESET 1 +#define GPHY_CTRL_PIPE_MOD 2 +#define GPHY_CTRL_TEST_MODE_MASK 3 +#define GPHY_CTRL_TEST_MODE_SHIFT 2 +#define GPHY_CTRL_BERT_START 0x10 +#define GPHY_CTRL_GATE_25M_EN 0x20 +#define GPHY_CTRL_LPW_EXIT 0x40 +#define GPHY_CTRL_PHY_IDDQ 0x80 +#define GPHY_CTRL_PHY_IDDQ_DIS 0x100 +#define GPHY_CTRL_PCLK_SEL_DIS 0x200 +#define GPHY_CTRL_HIB_EN 0x400 +#define GPHY_CTRL_HIB_PULSE 0x800 +#define GPHY_CTRL_SEL_ANA_RST 0x1000 +#define GPHY_CTRL_PHY_PLL_ON 0x2000 +#define GPHY_CTRL_PWDOWN_HW 0x4000 +#define GPHY_CTRL_DEFAULT (\ + GPHY_CTRL_PHY_PLL_ON |\ + GPHY_CTRL_SEL_ANA_RST |\ + GPHY_CTRL_HIB_PULSE |\ + GPHY_CTRL_HIB_EN) + +#define GPHY_CTRL_PW_WOL_DIS (\ + GPHY_CTRL_PHY_PLL_ON |\ + GPHY_CTRL_SEL_ANA_RST |\ + GPHY_CTRL_HIB_PULSE |\ + GPHY_CTRL_HIB_EN |\ + GPHY_CTRL_PWDOWN_HW |\ + GPHY_CTRL_PCLK_SEL_DIS |\ + GPHY_CTRL_PHY_IDDQ) + +/* IRQ Anti-Lost Timer Initial Value Register */ +#define REG_CMBDISDMA_TIMER 0x140E + + +/* Block IDLE Status Register */ +#define REG_IDLE_STATUS 0x1410 +#define IDLE_STATUS_RXMAC 1 /* 1: RXMAC state machine is in non-IDLE state. 0: RXMAC is idling */ +#define IDLE_STATUS_TXMAC 2 /* 1: TXMAC state machine is in non-IDLE state. 0: TXMAC is idling */ +#define IDLE_STATUS_RXQ 4 /* 1: RXQ state machine is in non-IDLE state. 0: RXQ is idling */ +#define IDLE_STATUS_TXQ 8 /* 1: TXQ state machine is in non-IDLE state. 0: TXQ is idling */ +#define IDLE_STATUS_DMAR 0x10 /* 1: DMAR state machine is in non-IDLE state. 0: DMAR is idling */ +#define IDLE_STATUS_DMAW 0x20 /* 1: DMAW state machine is in non-IDLE state. 0: DMAW is idling */ +#define IDLE_STATUS_SMB 0x40 /* 1: SMB state machine is in non-IDLE state. 0: SMB is idling */ +#define IDLE_STATUS_CMB 0x80 /* 1: CMB state machine is in non-IDLE state. 0: CMB is idling */ + +/* MDIO Control Register */ +#define REG_MDIO_CTRL 0x1414 +#define MDIO_DATA_MASK 0xffff /* On MDIO write, the 16-bit control data to write to PHY MII management register */ +#define MDIO_DATA_SHIFT 0 /* On MDIO read, the 16-bit status data that was read from the PHY MII management register*/ +#define MDIO_REG_ADDR_MASK 0x1f /* MDIO register address */ +#define MDIO_REG_ADDR_SHIFT 16 +#define MDIO_RW 0x200000 /* 1: read, 0: write */ +#define MDIO_SUP_PREAMBLE 0x400000 /* Suppress preamble */ +#define MDIO_START 0x800000 /* Write 1 to initiate the MDIO master. And this bit is self cleared after one cycle*/ +#define MDIO_CLK_SEL_SHIFT 24 +#define MDIO_CLK_25_4 0 +#define MDIO_CLK_25_6 2 +#define MDIO_CLK_25_8 3 +#define MDIO_CLK_25_10 4 +#define MDIO_CLK_25_14 5 +#define MDIO_CLK_25_20 6 +#define MDIO_CLK_25_28 7 +#define MDIO_BUSY 0x8000000 +#define MDIO_AP_EN 0x10000000 +#define MDIO_WAIT_TIMES 10 + +/* MII PHY Status Register */ +#define REG_PHY_STATUS 0x1418 +#define PHY_STATUS_100M 0x20000 +#define PHY_STATUS_EMI_CA 0x40000 + +/* BIST Control and Status Register0 (for the Packet Memory) */ +#define REG_BIST0_CTRL 0x141c +#define BIST0_NOW 0x1 /* 1: To trigger BIST0 logic. This bit stays high during the */ +/* BIST process and reset to zero when BIST is done */ +#define BIST0_SRAM_FAIL 0x2 /* 1: The SRAM failure is un-repairable because it has address */ +/* decoder failure or more than 1 cell stuck-to-x failure */ +#define BIST0_FUSE_FLAG 0x4 /* 1: Indicating one cell has been fixed */ + +/* BIST Control and Status Register1(for the retry buffer of PCI Express) */ +#define REG_BIST1_CTRL 0x1420 +#define BIST1_NOW 0x1 /* 1: To trigger BIST0 logic. This bit stays high during the */ +/* BIST process and reset to zero when BIST is done */ +#define BIST1_SRAM_FAIL 0x2 /* 1: The SRAM failure is un-repairable because it has address */ +/* decoder failure or more than 1 cell stuck-to-x failure.*/ +#define BIST1_FUSE_FLAG 0x4 + +/* SerDes Lock Detect Control and Status Register */ +#define REG_SERDES_LOCK 0x1424 +#define SERDES_LOCK_DETECT 1 /* 1: SerDes lock detected . This signal comes from Analog SerDes */ +#define SERDES_LOCK_DETECT_EN 2 /* 1: Enable SerDes Lock detect function */ + +/* MAC Control Register */ +#define REG_MAC_CTRL 0x1480 +#define MAC_CTRL_TX_EN 1 /* 1: Transmit Enable */ +#define MAC_CTRL_RX_EN 2 /* 1: Receive Enable */ +#define MAC_CTRL_TX_FLOW 4 /* 1: Transmit Flow Control Enable */ +#define MAC_CTRL_RX_FLOW 8 /* 1: Receive Flow Control Enable */ +#define MAC_CTRL_LOOPBACK 0x10 /* 1: Loop back at G/MII Interface */ +#define MAC_CTRL_DUPLX 0x20 /* 1: Full-duplex mode 0: Half-duplex mode */ +#define MAC_CTRL_ADD_CRC 0x40 /* 1: Instruct MAC to attach CRC on all egress Ethernet frames */ +#define MAC_CTRL_PAD 0x80 /* 1: Instruct MAC to pad short frames to 60-bytes, and then attach CRC. This bit has higher priority over CRC_EN */ +#define MAC_CTRL_LENCHK 0x100 /* 1: Instruct MAC to check if length field matches the real packet length */ +#define MAC_CTRL_HUGE_EN 0x200 /* 1: receive Jumbo frame enable */ +#define MAC_CTRL_PRMLEN_SHIFT 10 /* Preamble length */ +#define MAC_CTRL_PRMLEN_MASK 0xf +#define MAC_CTRL_RMV_VLAN 0x4000 /* 1: to remove VLAN Tag automatically from all receive packets */ +#define MAC_CTRL_PROMIS_EN 0x8000 /* 1: Promiscuous Mode Enable */ +#define MAC_CTRL_TX_PAUSE 0x10000 /* 1: transmit test pause */ +#define MAC_CTRL_SCNT 0x20000 /* 1: shortcut slot time counter */ +#define MAC_CTRL_SRST_TX 0x40000 /* 1: synchronized reset Transmit MAC module */ +#define MAC_CTRL_TX_SIMURST 0x80000 /* 1: transmit simulation reset */ +#define MAC_CTRL_SPEED_SHIFT 20 /* 10: gigabit 01:10M/100M */ +#define MAC_CTRL_SPEED_MASK 0x300000 +#define MAC_CTRL_SPEED_1000 2 +#define MAC_CTRL_SPEED_10_100 1 +#define MAC_CTRL_DBG_TX_BKPRESURE 0x400000 /* 1: transmit maximum backoff (half-duplex test bit) */ +#define MAC_CTRL_TX_HUGE 0x800000 /* 1: transmit huge enable */ +#define MAC_CTRL_RX_CHKSUM_EN 0x1000000 /* 1: RX checksum enable */ +#define MAC_CTRL_MC_ALL_EN 0x2000000 /* 1: upload all multicast frame without error to system */ +#define MAC_CTRL_BC_EN 0x4000000 /* 1: upload all broadcast frame without error to system */ +#define MAC_CTRL_DBG 0x8000000 /* 1: upload all received frame to system (Debug Mode) */ + +/* MAC IPG/IFG Control Register */ +#define REG_MAC_IPG_IFG 0x1484 +#define MAC_IPG_IFG_IPGT_SHIFT 0 /* Desired back to back inter-packet gap. The default is 96-bit time */ +#define MAC_IPG_IFG_IPGT_MASK 0x7f +#define MAC_IPG_IFG_MIFG_SHIFT 8 /* Minimum number of IFG to enforce in between RX frames */ +#define MAC_IPG_IFG_MIFG_MASK 0xff /* Frame gap below such IFP is dropped */ +#define MAC_IPG_IFG_IPGR1_SHIFT 16 /* 64bit Carrier-Sense window */ +#define MAC_IPG_IFG_IPGR1_MASK 0x7f +#define MAC_IPG_IFG_IPGR2_SHIFT 24 /* 96-bit IPG window */ +#define MAC_IPG_IFG_IPGR2_MASK 0x7f + +/* MAC STATION ADDRESS */ +#define REG_MAC_STA_ADDR 0x1488 + +/* Hash table for multicast address */ +#define REG_RX_HASH_TABLE 0x1490 + + +/* MAC Half-Duplex Control Register */ +#define REG_MAC_HALF_DUPLX_CTRL 0x1498 +#define MAC_HALF_DUPLX_CTRL_LCOL_SHIFT 0 /* Collision Window */ +#define MAC_HALF_DUPLX_CTRL_LCOL_MASK 0x3ff +#define MAC_HALF_DUPLX_CTRL_RETRY_SHIFT 12 /* Retransmission maximum, afterwards the packet will be discarded */ +#define MAC_HALF_DUPLX_CTRL_RETRY_MASK 0xf +#define MAC_HALF_DUPLX_CTRL_EXC_DEF_EN 0x10000 /* 1: Allow the transmission of a packet which has been excessively deferred */ +#define MAC_HALF_DUPLX_CTRL_NO_BACK_C 0x20000 /* 1: No back-off on collision, immediately start the retransmission */ +#define MAC_HALF_DUPLX_CTRL_NO_BACK_P 0x40000 /* 1: No back-off on backpressure, immediately start the transmission after back pressure */ +#define MAC_HALF_DUPLX_CTRL_ABEBE 0x80000 /* 1: Alternative Binary Exponential Back-off Enabled */ +#define MAC_HALF_DUPLX_CTRL_ABEBT_SHIFT 20 /* Maximum binary exponential number */ +#define MAC_HALF_DUPLX_CTRL_ABEBT_MASK 0xf +#define MAC_HALF_DUPLX_CTRL_JAMIPG_SHIFT 24 /* IPG to start JAM for collision based flow control in half-duplex */ +#define MAC_HALF_DUPLX_CTRL_JAMIPG_MASK 0xf /* mode. In unit of 8-bit time */ + +/* Maximum Frame Length Control Register */ +#define REG_MTU 0x149c + +/* Wake-On-Lan control register */ +#define REG_WOL_CTRL 0x14a0 +#define WOL_PATTERN_EN 0x00000001 +#define WOL_PATTERN_PME_EN 0x00000002 +#define WOL_MAGIC_EN 0x00000004 +#define WOL_MAGIC_PME_EN 0x00000008 +#define WOL_LINK_CHG_EN 0x00000010 +#define WOL_LINK_CHG_PME_EN 0x00000020 +#define WOL_PATTERN_ST 0x00000100 +#define WOL_MAGIC_ST 0x00000200 +#define WOL_LINKCHG_ST 0x00000400 +#define WOL_CLK_SWITCH_EN 0x00008000 +#define WOL_PT0_EN 0x00010000 +#define WOL_PT1_EN 0x00020000 +#define WOL_PT2_EN 0x00040000 +#define WOL_PT3_EN 0x00080000 +#define WOL_PT4_EN 0x00100000 +#define WOL_PT5_EN 0x00200000 +#define WOL_PT6_EN 0x00400000 +/* WOL Length ( 2 DWORD ) */ +#define REG_WOL_PATTERN_LEN 0x14a4 +#define WOL_PT_LEN_MASK 0x7f +#define WOL_PT0_LEN_SHIFT 0 +#define WOL_PT1_LEN_SHIFT 8 +#define WOL_PT2_LEN_SHIFT 16 +#define WOL_PT3_LEN_SHIFT 24 +#define WOL_PT4_LEN_SHIFT 0 +#define WOL_PT5_LEN_SHIFT 8 +#define WOL_PT6_LEN_SHIFT 16 + +/* Internal SRAM Partition Register */ +#define REG_SRAM_TRD_ADDR 0x1518 +#define REG_SRAM_TRD_LEN 0x151C +#define REG_SRAM_RXF_ADDR 0x1520 +#define REG_SRAM_RXF_LEN 0x1524 +#define REG_SRAM_TXF_ADDR 0x1528 +#define REG_SRAM_TXF_LEN 0x152C +#define REG_SRAM_TCPH_ADDR 0x1530 +#define REG_SRAM_PKTH_ADDR 0x1532 + +/* Load Ptr Register */ +#define REG_LOAD_PTR 0x1534 /* Software sets this bit after the initialization of the head and tail */ + +/* + * addresses of all descriptors, as well as the following descriptor + * control register, which triggers each function block to load the head + * pointer to prepare for the operation. This bit is then self-cleared + * after one cycle. + */ + +/* Descriptor Control register */ +#define REG_RXF3_BASE_ADDR_HI 0x153C +#define REG_DESC_BASE_ADDR_HI 0x1540 +#define REG_RXF0_BASE_ADDR_HI 0x1540 /* share with DESC BASE ADDR HI */ +#define REG_HOST_RXF0_PAGE0_LO 0x1544 +#define REG_HOST_RXF0_PAGE1_LO 0x1548 +#define REG_TPD_BASE_ADDR_LO 0x154C +#define REG_RXF1_BASE_ADDR_HI 0x1550 +#define REG_RXF2_BASE_ADDR_HI 0x1554 +#define REG_HOST_RXFPAGE_SIZE 0x1558 +#define REG_TPD_RING_SIZE 0x155C +/* RSS about */ +#define REG_RSS_KEY0 0x14B0 +#define REG_RSS_KEY1 0x14B4 +#define REG_RSS_KEY2 0x14B8 +#define REG_RSS_KEY3 0x14BC +#define REG_RSS_KEY4 0x14C0 +#define REG_RSS_KEY5 0x14C4 +#define REG_RSS_KEY6 0x14C8 +#define REG_RSS_KEY7 0x14CC +#define REG_RSS_KEY8 0x14D0 +#define REG_RSS_KEY9 0x14D4 +#define REG_IDT_TABLE4 0x14E0 +#define REG_IDT_TABLE5 0x14E4 +#define REG_IDT_TABLE6 0x14E8 +#define REG_IDT_TABLE7 0x14EC +#define REG_IDT_TABLE0 0x1560 +#define REG_IDT_TABLE1 0x1564 +#define REG_IDT_TABLE2 0x1568 +#define REG_IDT_TABLE3 0x156C +#define REG_IDT_TABLE REG_IDT_TABLE0 +#define REG_RSS_HASH_VALUE 0x1570 +#define REG_RSS_HASH_FLAG 0x1574 +#define REG_BASE_CPU_NUMBER 0x157C + + +/* TXQ Control Register */ +#define REG_TXQ_CTRL 0x1580 +#define TXQ_CTRL_NUM_TPD_BURST_MASK 0xF +#define TXQ_CTRL_NUM_TPD_BURST_SHIFT 0 +#define TXQ_CTRL_EN 0x20 /* 1: Enable TXQ */ +#define TXQ_CTRL_ENH_MODE 0x40 /* Performance enhancement mode, in which up to two back-to-back DMA read commands might be dispatched. */ +#define TXQ_CTRL_TXF_BURST_NUM_SHIFT 16 /* Number of data byte to read in a cache-aligned burst. Each SRAM entry is 8-byte in length. */ +#define TXQ_CTRL_TXF_BURST_NUM_MASK 0xffff + +/* Jumbo packet Threshold for task offload */ +#define REG_TX_EARLY_TH 0x1584 /* Jumbo frame threshold in QWORD unit. Packet greater than */ +/* JUMBO_TASK_OFFLOAD_THRESHOLD will not be task offloaded. */ +#define TX_TX_EARLY_TH_MASK 0x7ff +#define TX_TX_EARLY_TH_SHIFT 0 + + +/* RXQ Control Register */ +#define REG_RXQ_CTRL 0x15A0 +#define RXQ_CTRL_PBA_ALIGN_32 0 /* rx-packet alignment */ +#define RXQ_CTRL_PBA_ALIGN_64 1 +#define RXQ_CTRL_PBA_ALIGN_128 2 +#define RXQ_CTRL_PBA_ALIGN_256 3 +#define RXQ_CTRL_Q1_EN 0x10 +#define RXQ_CTRL_Q2_EN 0x20 +#define RXQ_CTRL_Q3_EN 0x40 +#define RXQ_CTRL_IPV6_XSUM_VERIFY_EN 0x80 +#define RXQ_CTRL_HASH_TLEN_SHIFT 8 +#define RXQ_CTRL_HASH_TLEN_MASK 0xFF +#define RXQ_CTRL_HASH_TYPE_IPV4 0x10000 +#define RXQ_CTRL_HASH_TYPE_IPV4_TCP 0x20000 +#define RXQ_CTRL_HASH_TYPE_IPV6 0x40000 +#define RXQ_CTRL_HASH_TYPE_IPV6_TCP 0x80000 +#define RXQ_CTRL_RSS_MODE_DISABLE 0 +#define RXQ_CTRL_RSS_MODE_SQSINT 0x4000000 +#define RXQ_CTRL_RSS_MODE_MQUESINT 0x8000000 +#define RXQ_CTRL_RSS_MODE_MQUEMINT 0xC000000 +#define RXQ_CTRL_NIP_QUEUE_SEL_TBL 0x10000000 +#define RXQ_CTRL_HASH_ENABLE 0x20000000 +#define RXQ_CTRL_CUT_THRU_EN 0x40000000 +#define RXQ_CTRL_EN 0x80000000 + +/* Rx jumbo packet threshold and rrd retirement timer */ +#define REG_RXQ_JMBOSZ_RRDTIM 0x15A4 +/* + * Jumbo packet threshold for non-VLAN packet, in QWORD (64-bit) unit. + * When the packet length greater than or equal to this value, RXQ + * shall start cut-through forwarding of the received packet. + */ +#define RXQ_JMBOSZ_TH_MASK 0x7ff +#define RXQ_JMBOSZ_TH_SHIFT 0 /* RRD retirement timer. Decrement by 1 after every 512ns passes*/ +#define RXQ_JMBO_LKAH_MASK 0xf +#define RXQ_JMBO_LKAH_SHIFT 11 + +/* RXF flow control register */ +#define REG_RXQ_RXF_PAUSE_THRESH 0x15A8 +#define RXQ_RXF_PAUSE_TH_HI_SHIFT 0 +#define RXQ_RXF_PAUSE_TH_HI_MASK 0xfff +#define RXQ_RXF_PAUSE_TH_LO_SHIFT 16 +#define RXQ_RXF_PAUSE_TH_LO_MASK 0xfff + + +/* DMA Engine Control Register */ +#define REG_DMA_CTRL 0x15C0 +#define DMA_CTRL_DMAR_IN_ORDER 0x1 +#define DMA_CTRL_DMAR_ENH_ORDER 0x2 +#define DMA_CTRL_DMAR_OUT_ORDER 0x4 +#define DMA_CTRL_RCB_VALUE 0x8 +#define DMA_CTRL_DMAR_BURST_LEN_SHIFT 4 +#define DMA_CTRL_DMAR_BURST_LEN_MASK 7 +#define DMA_CTRL_DMAW_BURST_LEN_SHIFT 7 +#define DMA_CTRL_DMAW_BURST_LEN_MASK 7 +#define DMA_CTRL_DMAR_REQ_PRI 0x400 +#define DMA_CTRL_DMAR_DLY_CNT_MASK 0x1F +#define DMA_CTRL_DMAR_DLY_CNT_SHIFT 11 +#define DMA_CTRL_DMAW_DLY_CNT_MASK 0xF +#define DMA_CTRL_DMAW_DLY_CNT_SHIFT 16 +#define DMA_CTRL_TXCMB_EN 0x100000 +#define DMA_CTRL_RXCMB_EN 0x200000 + + +/* CMB/SMB Control Register */ +#define REG_SMB_STAT_TIMER 0x15C4 +#define REG_TRIG_RRD_THRESH 0x15CA +#define REG_TRIG_TPD_THRESH 0x15C8 +#define REG_TRIG_TXTIMER 0x15CC +#define REG_TRIG_RXTIMER 0x15CE + +/* HOST RXF Page 1,2,3 address */ +#define REG_HOST_RXF1_PAGE0_LO 0x15D0 +#define REG_HOST_RXF1_PAGE1_LO 0x15D4 +#define REG_HOST_RXF2_PAGE0_LO 0x15D8 +#define REG_HOST_RXF2_PAGE1_LO 0x15DC +#define REG_HOST_RXF3_PAGE0_LO 0x15E0 +#define REG_HOST_RXF3_PAGE1_LO 0x15E4 + +/* Mail box */ +#define REG_MB_RXF1_RADDR 0x15B4 +#define REG_MB_RXF2_RADDR 0x15B8 +#define REG_MB_RXF3_RADDR 0x15BC +#define REG_MB_TPD_PROD_IDX 0x15F0 + +/* RXF-Page 0-3 PageNo & Valid bit */ +#define REG_HOST_RXF0_PAGE0_VLD 0x15F4 +#define HOST_RXF_VALID 1 +#define HOST_RXF_PAGENO_SHIFT 1 +#define HOST_RXF_PAGENO_MASK 0x7F +#define REG_HOST_RXF0_PAGE1_VLD 0x15F5 +#define REG_HOST_RXF1_PAGE0_VLD 0x15F6 +#define REG_HOST_RXF1_PAGE1_VLD 0x15F7 +#define REG_HOST_RXF2_PAGE0_VLD 0x15F8 +#define REG_HOST_RXF2_PAGE1_VLD 0x15F9 +#define REG_HOST_RXF3_PAGE0_VLD 0x15FA +#define REG_HOST_RXF3_PAGE1_VLD 0x15FB + +/* Interrupt Status Register */ +#define REG_ISR 0x1600 +#define ISR_SMB 1 +#define ISR_TIMER 2 /* Interrupt when Timer is counted down to zero */ +/* + * Software manual interrupt, for debug. Set when SW_MAN_INT_EN is set + * in Table 51 Selene Master Control Register (Offset 0x1400). + */ +#define ISR_MANUAL 4 +#define ISR_HW_RXF_OV 8 /* RXF overflow interrupt */ +#define ISR_HOST_RXF0_OV 0x10 +#define ISR_HOST_RXF1_OV 0x20 +#define ISR_HOST_RXF2_OV 0x40 +#define ISR_HOST_RXF3_OV 0x80 +#define ISR_TXF_UN 0x100 +#define ISR_RX0_PAGE_FULL 0x200 +#define ISR_DMAR_TO_RST 0x400 +#define ISR_DMAW_TO_RST 0x800 +#define ISR_GPHY 0x1000 +#define ISR_TX_CREDIT 0x2000 +#define ISR_GPHY_LPW 0x4000 /* GPHY low power state interrupt */ +#define ISR_RX_PKT 0x10000 /* One packet received, triggered by RFD */ +#define ISR_TX_PKT 0x20000 /* One packet transmitted, triggered by TPD */ +#define ISR_TX_DMA 0x40000 +#define ISR_RX_PKT_1 0x80000 +#define ISR_RX_PKT_2 0x100000 +#define ISR_RX_PKT_3 0x200000 +#define ISR_MAC_RX 0x400000 +#define ISR_MAC_TX 0x800000 +#define ISR_UR_DETECTED 0x1000000 +#define ISR_FERR_DETECTED 0x2000000 +#define ISR_NFERR_DETECTED 0x4000000 +#define ISR_CERR_DETECTED 0x8000000 +#define ISR_PHY_LINKDOWN 0x10000000 +#define ISR_DIS_INT 0x80000000 + + +/* Interrupt Mask Register */ +#define REG_IMR 0x1604 + + +#define IMR_NORMAL_MASK (\ + ISR_SMB |\ + ISR_TXF_UN |\ + ISR_HW_RXF_OV |\ + ISR_HOST_RXF0_OV|\ + ISR_MANUAL |\ + ISR_GPHY |\ + ISR_GPHY_LPW |\ + ISR_DMAR_TO_RST |\ + ISR_DMAW_TO_RST |\ + ISR_PHY_LINKDOWN|\ + ISR_RX_PKT |\ + ISR_TX_PKT) + +#define ISR_TX_EVENT (ISR_TXF_UN | ISR_TX_PKT) +#define ISR_RX_EVENT (ISR_HOST_RXF0_OV | ISR_HW_RXF_OV | ISR_RX_PKT) + +#define REG_MAC_RX_STATUS_BIN 0x1700 +#define REG_MAC_RX_STATUS_END 0x175c +#define REG_MAC_TX_STATUS_BIN 0x1760 +#define REG_MAC_TX_STATUS_END 0x17c0 + +/* Hardware Offset Register */ +#define REG_HOST_RXF0_PAGEOFF 0x1800 +#define REG_TPD_CONS_IDX 0x1804 +#define REG_HOST_RXF1_PAGEOFF 0x1808 +#define REG_HOST_RXF2_PAGEOFF 0x180C +#define REG_HOST_RXF3_PAGEOFF 0x1810 + +/* RXF-Page 0-3 Offset DMA Address */ +#define REG_HOST_RXF0_MB0_LO 0x1820 +#define REG_HOST_RXF0_MB1_LO 0x1824 +#define REG_HOST_RXF1_MB0_LO 0x1828 +#define REG_HOST_RXF1_MB1_LO 0x182C +#define REG_HOST_RXF2_MB0_LO 0x1830 +#define REG_HOST_RXF2_MB1_LO 0x1834 +#define REG_HOST_RXF3_MB0_LO 0x1838 +#define REG_HOST_RXF3_MB1_LO 0x183C + +/* Tpd CMB DMA Address */ +#define REG_HOST_TX_CMB_LO 0x1840 +#define REG_HOST_SMB_ADDR_LO 0x1844 + +/* DEBUG ADDR */ +#define REG_DEBUG_DATA0 0x1900 +#define REG_DEBUG_DATA1 0x1904 + +/***************************** MII definition ***************************************/ +/* PHY Common Register */ +#define MII_AT001_PSCR 0x10 +#define MII_AT001_PSSR 0x11 +#define MII_INT_CTRL 0x12 +#define MII_INT_STATUS 0x13 +#define MII_SMARTSPEED 0x14 +#define MII_LBRERROR 0x18 +#define MII_RESV2 0x1a + +#define MII_DBG_ADDR 0x1D +#define MII_DBG_DATA 0x1E + +/* Autoneg Advertisement Register */ +#define MII_AR_DEFAULT_CAP_MASK 0 + +/* 1000BASE-T Control Register */ +#define MII_AT001_CR_1000T_SPEED_MASK \ + (ADVERTISE_1000FULL | ADVERTISE_1000HALF) +#define MII_AT001_CR_1000T_DEFAULT_CAP_MASK MII_AT001_CR_1000T_SPEED_MASK + +/* AT001 PHY Specific Control Register */ +#define MII_AT001_PSCR_JABBER_DISABLE 0x0001 /* 1=Jabber Function disabled */ +#define MII_AT001_PSCR_POLARITY_REVERSAL 0x0002 /* 1=Polarity Reversal enabled */ +#define MII_AT001_PSCR_SQE_TEST 0x0004 /* 1=SQE Test enabled */ +#define MII_AT001_PSCR_MAC_POWERDOWN 0x0008 +#define MII_AT001_PSCR_CLK125_DISABLE 0x0010 /* 1=CLK125 low, + * 0=CLK125 toggling + */ +#define MII_AT001_PSCR_MDI_MANUAL_MODE 0x0000 /* MDI Crossover Mode bits 6:5 */ +/* Manual MDI configuration */ +#define MII_AT001_PSCR_MDIX_MANUAL_MODE 0x0020 /* Manual MDIX configuration */ +#define MII_AT001_PSCR_AUTO_X_1000T 0x0040 /* 1000BASE-T: Auto crossover, + * 100BASE-TX/10BASE-T: + * MDI Mode + */ +#define MII_AT001_PSCR_AUTO_X_MODE 0x0060 /* Auto crossover enabled + * all speeds. + */ +#define MII_AT001_PSCR_10BT_EXT_DIST_ENABLE 0x0080 +/* 1=Enable Extended 10BASE-T distance + * (Lower 10BASE-T RX Threshold) + * 0=Normal 10BASE-T RX Threshold */ +#define MII_AT001_PSCR_MII_5BIT_ENABLE 0x0100 +/* 1=5-Bit interface in 100BASE-TX + * 0=MII interface in 100BASE-TX */ +#define MII_AT001_PSCR_SCRAMBLER_DISABLE 0x0200 /* 1=Scrambler disable */ +#define MII_AT001_PSCR_FORCE_LINK_GOOD 0x0400 /* 1=Force link good */ +#define MII_AT001_PSCR_ASSERT_CRS_ON_TX 0x0800 /* 1=Assert CRS on Transmit */ +#define MII_AT001_PSCR_POLARITY_REVERSAL_SHIFT 1 +#define MII_AT001_PSCR_AUTO_X_MODE_SHIFT 5 +#define MII_AT001_PSCR_10BT_EXT_DIST_ENABLE_SHIFT 7 +/* AT001 PHY Specific Status Register */ +#define MII_AT001_PSSR_SPD_DPLX_RESOLVED 0x0800 /* 1=Speed & Duplex resolved */ +#define MII_AT001_PSSR_DPLX 0x2000 /* 1=Duplex 0=Half Duplex */ +#define MII_AT001_PSSR_SPEED 0xC000 /* Speed, bits 14:15 */ +#define MII_AT001_PSSR_10MBS 0x0000 /* 00=10Mbs */ +#define MII_AT001_PSSR_100MBS 0x4000 /* 01=100Mbs */ +#define MII_AT001_PSSR_1000MBS 0x8000 /* 10=1000Mbs */ + +#endif /*_ATHL1E_HW_H_*/ diff --git a/drivers/net/ethernet/atheros/atl1e/atl1e_main.c b/drivers/net/ethernet/atheros/atl1e/atl1e_main.c new file mode 100644 index 0000000..e41de38 --- /dev/null +++ b/drivers/net/ethernet/atheros/atl1e/atl1e_main.c @@ -0,0 +1,2576 @@ +/* + * Copyright(c) 2007 Atheros Corporation. All rights reserved. + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "atl1e.h" + +#define DRV_VERSION "1.0.0.7-NAPI" + +char atl1e_driver_name[] = "ATL1E"; +char atl1e_driver_version[] = DRV_VERSION; +#define PCI_DEVICE_ID_ATTANSIC_L1E 0x1026 +/* + * atl1e_pci_tbl - PCI Device ID Table + * + * Wildcard entries (PCI_ANY_ID) should come last + * Last entry must be all 0s + * + * { Vendor ID, Device ID, SubVendor ID, SubDevice ID, + * Class, Class Mask, private data (not used) } + */ +static DEFINE_PCI_DEVICE_TABLE(atl1e_pci_tbl) = { + {PCI_DEVICE(PCI_VENDOR_ID_ATTANSIC, PCI_DEVICE_ID_ATTANSIC_L1E)}, + {PCI_DEVICE(PCI_VENDOR_ID_ATTANSIC, 0x1066)}, + /* required last entry */ + { 0 } +}; +MODULE_DEVICE_TABLE(pci, atl1e_pci_tbl); + +MODULE_AUTHOR("Atheros Corporation, , Jie Yang "); +MODULE_DESCRIPTION("Atheros 1000M Ethernet Network Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_VERSION); + +static void atl1e_setup_mac_ctrl(struct atl1e_adapter *adapter); + +static const u16 +atl1e_rx_page_vld_regs[AT_MAX_RECEIVE_QUEUE][AT_PAGE_NUM_PER_QUEUE] = +{ + {REG_HOST_RXF0_PAGE0_VLD, REG_HOST_RXF0_PAGE1_VLD}, + {REG_HOST_RXF1_PAGE0_VLD, REG_HOST_RXF1_PAGE1_VLD}, + {REG_HOST_RXF2_PAGE0_VLD, REG_HOST_RXF2_PAGE1_VLD}, + {REG_HOST_RXF3_PAGE0_VLD, REG_HOST_RXF3_PAGE1_VLD} +}; + +static const u16 atl1e_rx_page_hi_addr_regs[AT_MAX_RECEIVE_QUEUE] = +{ + REG_RXF0_BASE_ADDR_HI, + REG_RXF1_BASE_ADDR_HI, + REG_RXF2_BASE_ADDR_HI, + REG_RXF3_BASE_ADDR_HI +}; + +static const u16 +atl1e_rx_page_lo_addr_regs[AT_MAX_RECEIVE_QUEUE][AT_PAGE_NUM_PER_QUEUE] = +{ + {REG_HOST_RXF0_PAGE0_LO, REG_HOST_RXF0_PAGE1_LO}, + {REG_HOST_RXF1_PAGE0_LO, REG_HOST_RXF1_PAGE1_LO}, + {REG_HOST_RXF2_PAGE0_LO, REG_HOST_RXF2_PAGE1_LO}, + {REG_HOST_RXF3_PAGE0_LO, REG_HOST_RXF3_PAGE1_LO} +}; + +static const u16 +atl1e_rx_page_write_offset_regs[AT_MAX_RECEIVE_QUEUE][AT_PAGE_NUM_PER_QUEUE] = +{ + {REG_HOST_RXF0_MB0_LO, REG_HOST_RXF0_MB1_LO}, + {REG_HOST_RXF1_MB0_LO, REG_HOST_RXF1_MB1_LO}, + {REG_HOST_RXF2_MB0_LO, REG_HOST_RXF2_MB1_LO}, + {REG_HOST_RXF3_MB0_LO, REG_HOST_RXF3_MB1_LO} +}; + +static const u16 atl1e_pay_load_size[] = { + 128, 256, 512, 1024, 2048, 4096, +}; + +/** + * atl1e_irq_enable - Enable default interrupt generation settings + * @adapter: board private structure + */ +static inline void atl1e_irq_enable(struct atl1e_adapter *adapter) +{ + if (likely(atomic_dec_and_test(&adapter->irq_sem))) { + AT_WRITE_REG(&adapter->hw, REG_ISR, 0); + AT_WRITE_REG(&adapter->hw, REG_IMR, IMR_NORMAL_MASK); + AT_WRITE_FLUSH(&adapter->hw); + } +} + +/** + * atl1e_irq_disable - Mask off interrupt generation on the NIC + * @adapter: board private structure + */ +static inline void atl1e_irq_disable(struct atl1e_adapter *adapter) +{ + atomic_inc(&adapter->irq_sem); + AT_WRITE_REG(&adapter->hw, REG_IMR, 0); + AT_WRITE_FLUSH(&adapter->hw); + synchronize_irq(adapter->pdev->irq); +} + +/** + * atl1e_irq_reset - reset interrupt confiure on the NIC + * @adapter: board private structure + */ +static inline void atl1e_irq_reset(struct atl1e_adapter *adapter) +{ + atomic_set(&adapter->irq_sem, 0); + AT_WRITE_REG(&adapter->hw, REG_ISR, 0); + AT_WRITE_REG(&adapter->hw, REG_IMR, 0); + AT_WRITE_FLUSH(&adapter->hw); +} + +/** + * atl1e_phy_config - Timer Call-back + * @data: pointer to netdev cast into an unsigned long + */ +static void atl1e_phy_config(unsigned long data) +{ + struct atl1e_adapter *adapter = (struct atl1e_adapter *) data; + struct atl1e_hw *hw = &adapter->hw; + unsigned long flags; + + spin_lock_irqsave(&adapter->mdio_lock, flags); + atl1e_restart_autoneg(hw); + spin_unlock_irqrestore(&adapter->mdio_lock, flags); +} + +void atl1e_reinit_locked(struct atl1e_adapter *adapter) +{ + + WARN_ON(in_interrupt()); + while (test_and_set_bit(__AT_RESETTING, &adapter->flags)) + msleep(1); + atl1e_down(adapter); + atl1e_up(adapter); + clear_bit(__AT_RESETTING, &adapter->flags); +} + +static void atl1e_reset_task(struct work_struct *work) +{ + struct atl1e_adapter *adapter; + adapter = container_of(work, struct atl1e_adapter, reset_task); + + atl1e_reinit_locked(adapter); +} + +static int atl1e_check_link(struct atl1e_adapter *adapter) +{ + struct atl1e_hw *hw = &adapter->hw; + struct net_device *netdev = adapter->netdev; + int err = 0; + u16 speed, duplex, phy_data; + + /* MII_BMSR must read twice */ + atl1e_read_phy_reg(hw, MII_BMSR, &phy_data); + atl1e_read_phy_reg(hw, MII_BMSR, &phy_data); + if ((phy_data & BMSR_LSTATUS) == 0) { + /* link down */ + if (netif_carrier_ok(netdev)) { /* old link state: Up */ + u32 value; + /* disable rx */ + value = AT_READ_REG(hw, REG_MAC_CTRL); + value &= ~MAC_CTRL_RX_EN; + AT_WRITE_REG(hw, REG_MAC_CTRL, value); + adapter->link_speed = SPEED_0; + netif_carrier_off(netdev); + netif_stop_queue(netdev); + } + } else { + /* Link Up */ + err = atl1e_get_speed_and_duplex(hw, &speed, &duplex); + if (unlikely(err)) + return err; + + /* link result is our setting */ + if (adapter->link_speed != speed || + adapter->link_duplex != duplex) { + adapter->link_speed = speed; + adapter->link_duplex = duplex; + atl1e_setup_mac_ctrl(adapter); + netdev_info(netdev, + "NIC Link is Up <%d Mbps %s Duplex>\n", + adapter->link_speed, + adapter->link_duplex == FULL_DUPLEX ? + "Full" : "Half"); + } + + if (!netif_carrier_ok(netdev)) { + /* Link down -> Up */ + netif_carrier_on(netdev); + netif_wake_queue(netdev); + } + } + return 0; +} + +/** + * atl1e_link_chg_task - deal with link change event Out of interrupt context + * @netdev: network interface device structure + */ +static void atl1e_link_chg_task(struct work_struct *work) +{ + struct atl1e_adapter *adapter; + unsigned long flags; + + adapter = container_of(work, struct atl1e_adapter, link_chg_task); + spin_lock_irqsave(&adapter->mdio_lock, flags); + atl1e_check_link(adapter); + spin_unlock_irqrestore(&adapter->mdio_lock, flags); +} + +static void atl1e_link_chg_event(struct atl1e_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + u16 phy_data = 0; + u16 link_up = 0; + + spin_lock(&adapter->mdio_lock); + atl1e_read_phy_reg(&adapter->hw, MII_BMSR, &phy_data); + atl1e_read_phy_reg(&adapter->hw, MII_BMSR, &phy_data); + spin_unlock(&adapter->mdio_lock); + link_up = phy_data & BMSR_LSTATUS; + /* notify upper layer link down ASAP */ + if (!link_up) { + if (netif_carrier_ok(netdev)) { + /* old link state: Up */ + netdev_info(netdev, "NIC Link is Down\n"); + adapter->link_speed = SPEED_0; + netif_stop_queue(netdev); + } + } + schedule_work(&adapter->link_chg_task); +} + +static void atl1e_del_timer(struct atl1e_adapter *adapter) +{ + del_timer_sync(&adapter->phy_config_timer); +} + +static void atl1e_cancel_work(struct atl1e_adapter *adapter) +{ + cancel_work_sync(&adapter->reset_task); + cancel_work_sync(&adapter->link_chg_task); +} + +/** + * atl1e_tx_timeout - Respond to a Tx Hang + * @netdev: network interface device structure + */ +static void atl1e_tx_timeout(struct net_device *netdev) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + + /* Do the reset outside of interrupt context */ + schedule_work(&adapter->reset_task); +} + +/** + * atl1e_set_multi - Multicast and Promiscuous mode set + * @netdev: network interface device structure + * + * The set_multi entry point is called whenever the multicast address + * list or the network interface flags are updated. This routine is + * responsible for configuring the hardware for proper multicast, + * promiscuous mode, and all-multi behavior. + */ +static void atl1e_set_multi(struct net_device *netdev) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + struct atl1e_hw *hw = &adapter->hw; + struct netdev_hw_addr *ha; + u32 mac_ctrl_data = 0; + u32 hash_value; + + /* Check for Promiscuous and All Multicast modes */ + mac_ctrl_data = AT_READ_REG(hw, REG_MAC_CTRL); + + if (netdev->flags & IFF_PROMISC) { + mac_ctrl_data |= MAC_CTRL_PROMIS_EN; + } else if (netdev->flags & IFF_ALLMULTI) { + mac_ctrl_data |= MAC_CTRL_MC_ALL_EN; + mac_ctrl_data &= ~MAC_CTRL_PROMIS_EN; + } else { + mac_ctrl_data &= ~(MAC_CTRL_PROMIS_EN | MAC_CTRL_MC_ALL_EN); + } + + AT_WRITE_REG(hw, REG_MAC_CTRL, mac_ctrl_data); + + /* clear the old settings from the multicast hash table */ + AT_WRITE_REG(hw, REG_RX_HASH_TABLE, 0); + AT_WRITE_REG_ARRAY(hw, REG_RX_HASH_TABLE, 1, 0); + + /* comoute mc addresses' hash value ,and put it into hash table */ + netdev_for_each_mc_addr(ha, netdev) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + hash_value = atl1e_hash_mc_addr(hw, ha->addr); +#else + hash_value = atl1e_hash_mc_addr(hw, ha->dmi_addr); +#endif + atl1e_hash_set(hw, hash_value); + } +} + +static void __atl1e_vlan_mode(netdev_features_t features, u32 *mac_ctrl_data) +{ + if (features & NETIF_F_HW_VLAN_CTAG_RX) { + /* enable VLAN tag insert/strip */ + *mac_ctrl_data |= MAC_CTRL_RMV_VLAN; + } else { + /* disable VLAN tag insert/strip */ + *mac_ctrl_data &= ~MAC_CTRL_RMV_VLAN; + } +} + +static void atl1e_vlan_mode(struct net_device *netdev, + netdev_features_t features) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + u32 mac_ctrl_data = 0; + + netdev_dbg(adapter->netdev, "%s\n", __func__); + + atl1e_irq_disable(adapter); + mac_ctrl_data = AT_READ_REG(&adapter->hw, REG_MAC_CTRL); + __atl1e_vlan_mode(features, &mac_ctrl_data); + AT_WRITE_REG(&adapter->hw, REG_MAC_CTRL, mac_ctrl_data); + atl1e_irq_enable(adapter); +} + +static void atl1e_restore_vlan(struct atl1e_adapter *adapter) +{ + netdev_dbg(adapter->netdev, "%s\n", __func__); + atl1e_vlan_mode(adapter->netdev, adapter->netdev->features); +} + +/** + * atl1e_set_mac - Change the Ethernet Address of the NIC + * @netdev: network interface device structure + * @p: pointer to an address structure + * + * Returns 0 on success, negative on failure + */ +static int atl1e_set_mac_addr(struct net_device *netdev, void *p) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + struct sockaddr *addr = p; + + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + + if (netif_running(netdev)) + return -EBUSY; + + memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len); + memcpy(adapter->hw.mac_addr, addr->sa_data, netdev->addr_len); + + atl1e_hw_set_mac_addr(&adapter->hw); + + return 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) +static netdev_features_t atl1e_fix_features(struct net_device *netdev, + netdev_features_t features) +{ + /* + * Since there is no support for separate rx/tx vlan accel + * enable/disable make sure tx flag is always in same state as rx. + */ + if (features & NETIF_F_HW_VLAN_CTAG_RX) + features |= NETIF_F_HW_VLAN_CTAG_TX; + else + features &= ~NETIF_F_HW_VLAN_CTAG_TX; + + return features; +} + +static int atl1e_set_features(struct net_device *netdev, + netdev_features_t features) +{ + netdev_features_t changed = netdev->features ^ features; + + if (changed & NETIF_F_HW_VLAN_CTAG_RX) + atl1e_vlan_mode(netdev, features); + + return 0; +} +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */ + +/** + * atl1e_change_mtu - Change the Maximum Transfer Unit + * @netdev: network interface device structure + * @new_mtu: new value for maximum frame size + * + * Returns 0 on success, negative on failure + */ +static int atl1e_change_mtu(struct net_device *netdev, int new_mtu) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + int old_mtu = netdev->mtu; + int max_frame = new_mtu + ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN; + + if ((max_frame < ETH_ZLEN + ETH_FCS_LEN) || + (max_frame > MAX_JUMBO_FRAME_SIZE)) { + netdev_warn(adapter->netdev, "invalid MTU setting\n"); + return -EINVAL; + } + /* set MTU */ + if (old_mtu != new_mtu && netif_running(netdev)) { + while (test_and_set_bit(__AT_RESETTING, &adapter->flags)) + msleep(1); + netdev->mtu = new_mtu; + adapter->hw.max_frame_size = new_mtu; + adapter->hw.rx_jumbo_th = (max_frame + 7) >> 3; + atl1e_down(adapter); + atl1e_up(adapter); + clear_bit(__AT_RESETTING, &adapter->flags); + } + return 0; +} + +/* + * caller should hold mdio_lock + */ +static int atl1e_mdio_read(struct net_device *netdev, int phy_id, int reg_num) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + u16 result; + + atl1e_read_phy_reg(&adapter->hw, reg_num & MDIO_REG_ADDR_MASK, &result); + return result; +} + +static void atl1e_mdio_write(struct net_device *netdev, int phy_id, + int reg_num, int val) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + + atl1e_write_phy_reg(&adapter->hw, reg_num & MDIO_REG_ADDR_MASK, val); +} + +static int atl1e_mii_ioctl(struct net_device *netdev, + struct ifreq *ifr, int cmd) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + struct mii_ioctl_data *data = if_mii(ifr); + unsigned long flags; + int retval = 0; + + if (!netif_running(netdev)) + return -EINVAL; + + spin_lock_irqsave(&adapter->mdio_lock, flags); + switch (cmd) { + case SIOCGMIIPHY: + data->phy_id = 0; + break; + + case SIOCGMIIREG: + if (atl1e_read_phy_reg(&adapter->hw, data->reg_num & 0x1F, + &data->val_out)) { + retval = -EIO; + goto out; + } + break; + + case SIOCSMIIREG: + if (data->reg_num & ~(0x1F)) { + retval = -EFAULT; + goto out; + } + + netdev_dbg(adapter->netdev, " write %x %x\n", + data->reg_num, data->val_in); + if (atl1e_write_phy_reg(&adapter->hw, + data->reg_num, data->val_in)) { + retval = -EIO; + goto out; + } + break; + + default: + retval = -EOPNOTSUPP; + break; + } +out: + spin_unlock_irqrestore(&adapter->mdio_lock, flags); + return retval; + +} + +static int atl1e_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd) +{ + switch (cmd) { + case SIOCGMIIPHY: + case SIOCGMIIREG: + case SIOCSMIIREG: + return atl1e_mii_ioctl(netdev, ifr, cmd); + default: + return -EOPNOTSUPP; + } +} + +static void atl1e_setup_pcicmd(struct pci_dev *pdev) +{ + u16 cmd; + + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + cmd &= ~(PCI_COMMAND_INTX_DISABLE | PCI_COMMAND_IO); + cmd |= (PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER); + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + /* + * some motherboards BIOS(PXE/EFI) driver may set PME + * while they transfer control to OS (Windows/Linux) + * so we should clear this bit before NIC work normally + */ + pci_write_config_dword(pdev, REG_PM_CTRLSTAT, 0); + msleep(1); +} + +/** + * atl1e_alloc_queues - Allocate memory for all rings + * @adapter: board private structure to initialize + * + */ +static int atl1e_alloc_queues(struct atl1e_adapter *adapter) +{ + return 0; +} + +/** + * atl1e_sw_init - Initialize general software structures (struct atl1e_adapter) + * @adapter: board private structure to initialize + * + * atl1e_sw_init initializes the Adapter private data structure. + * Fields are initialized based on PCI device information and + * OS network device settings (MTU size). + */ +static int atl1e_sw_init(struct atl1e_adapter *adapter) +{ + struct atl1e_hw *hw = &adapter->hw; + struct pci_dev *pdev = adapter->pdev; + u32 phy_status_data = 0; + + adapter->wol = 0; + adapter->link_speed = SPEED_0; /* hardware init */ + adapter->link_duplex = FULL_DUPLEX; + adapter->num_rx_queues = 1; + + /* PCI config space info */ + hw->vendor_id = pdev->vendor; + hw->device_id = pdev->device; + hw->subsystem_vendor_id = pdev->subsystem_vendor; + hw->subsystem_id = pdev->subsystem_device; + hw->revision_id = pdev->revision; + + pci_read_config_word(pdev, PCI_COMMAND, &hw->pci_cmd_word); + + phy_status_data = AT_READ_REG(hw, REG_PHY_STATUS); + /* nic type */ + if (hw->revision_id >= 0xF0) { + hw->nic_type = athr_l2e_revB; + } else { + if (phy_status_data & PHY_STATUS_100M) + hw->nic_type = athr_l1e; + else + hw->nic_type = athr_l2e_revA; + } + + phy_status_data = AT_READ_REG(hw, REG_PHY_STATUS); + + if (phy_status_data & PHY_STATUS_EMI_CA) + hw->emi_ca = true; + else + hw->emi_ca = false; + + hw->phy_configured = false; + hw->preamble_len = 7; + hw->max_frame_size = adapter->netdev->mtu; + hw->rx_jumbo_th = (hw->max_frame_size + ETH_HLEN + + VLAN_HLEN + ETH_FCS_LEN + 7) >> 3; + + hw->rrs_type = atl1e_rrs_disable; + hw->indirect_tab = 0; + hw->base_cpu = 0; + + /* need confirm */ + + hw->ict = 50000; /* 100ms */ + hw->smb_timer = 200000; /* 200ms */ + hw->tpd_burst = 5; + hw->rrd_thresh = 1; + hw->tpd_thresh = adapter->tx_ring.count / 2; + hw->rx_count_down = 4; /* 2us resolution */ + hw->tx_count_down = hw->imt * 4 / 3; + hw->dmar_block = atl1e_dma_req_1024; + hw->dmaw_block = atl1e_dma_req_1024; + hw->dmar_dly_cnt = 15; + hw->dmaw_dly_cnt = 4; + + if (atl1e_alloc_queues(adapter)) { + netdev_err(adapter->netdev, "Unable to allocate memory for queues\n"); + return -ENOMEM; + } + + atomic_set(&adapter->irq_sem, 1); + spin_lock_init(&adapter->mdio_lock); + spin_lock_init(&adapter->tx_lock); + + set_bit(__AT_DOWN, &adapter->flags); + + return 0; +} + +/** + * atl1e_clean_tx_ring - Free Tx-skb + * @adapter: board private structure + */ +static void atl1e_clean_tx_ring(struct atl1e_adapter *adapter) +{ + struct atl1e_tx_ring *tx_ring = &adapter->tx_ring; + struct atl1e_tx_buffer *tx_buffer = NULL; + struct pci_dev *pdev = adapter->pdev; + u16 index, ring_count; + + if (tx_ring->desc == NULL || tx_ring->tx_buffer == NULL) + return; + + ring_count = tx_ring->count; + /* first unmmap dma */ + for (index = 0; index < ring_count; index++) { + tx_buffer = &tx_ring->tx_buffer[index]; + if (tx_buffer->dma) { + if (tx_buffer->flags & ATL1E_TX_PCIMAP_SINGLE) + pci_unmap_single(pdev, tx_buffer->dma, + tx_buffer->length, PCI_DMA_TODEVICE); + else if (tx_buffer->flags & ATL1E_TX_PCIMAP_PAGE) + pci_unmap_page(pdev, tx_buffer->dma, + tx_buffer->length, PCI_DMA_TODEVICE); + tx_buffer->dma = 0; + } + } + /* second free skb */ + for (index = 0; index < ring_count; index++) { + tx_buffer = &tx_ring->tx_buffer[index]; + if (tx_buffer->skb) { + dev_kfree_skb_any(tx_buffer->skb); + tx_buffer->skb = NULL; + } + } + /* Zero out Tx-buffers */ + memset(tx_ring->desc, 0, sizeof(struct atl1e_tpd_desc) * + ring_count); + memset(tx_ring->tx_buffer, 0, sizeof(struct atl1e_tx_buffer) * + ring_count); +} + +/** + * atl1e_clean_rx_ring - Free rx-reservation skbs + * @adapter: board private structure + */ +static void atl1e_clean_rx_ring(struct atl1e_adapter *adapter) +{ + struct atl1e_rx_ring *rx_ring = + &adapter->rx_ring; + struct atl1e_rx_page_desc *rx_page_desc = rx_ring->rx_page_desc; + u16 i, j; + + + if (adapter->ring_vir_addr == NULL) + return; + /* Zero out the descriptor ring */ + for (i = 0; i < adapter->num_rx_queues; i++) { + for (j = 0; j < AT_PAGE_NUM_PER_QUEUE; j++) { + if (rx_page_desc[i].rx_page[j].addr != NULL) { + memset(rx_page_desc[i].rx_page[j].addr, 0, + rx_ring->real_page_size); + } + } + } +} + +static void atl1e_cal_ring_size(struct atl1e_adapter *adapter, u32 *ring_size) +{ + *ring_size = ((u32)(adapter->tx_ring.count * + sizeof(struct atl1e_tpd_desc) + 7 + /* tx ring, qword align */ + + adapter->rx_ring.real_page_size * AT_PAGE_NUM_PER_QUEUE * + adapter->num_rx_queues + 31 + /* rx ring, 32 bytes align */ + + (1 + AT_PAGE_NUM_PER_QUEUE * adapter->num_rx_queues) * + sizeof(u32) + 3)); + /* tx, rx cmd, dword align */ +} + +static void atl1e_init_ring_resources(struct atl1e_adapter *adapter) +{ + struct atl1e_rx_ring *rx_ring = NULL; + + rx_ring = &adapter->rx_ring; + + rx_ring->real_page_size = adapter->rx_ring.page_size + + adapter->hw.max_frame_size + + ETH_HLEN + VLAN_HLEN + + ETH_FCS_LEN; + rx_ring->real_page_size = roundup(rx_ring->real_page_size, 32); + atl1e_cal_ring_size(adapter, &adapter->ring_size); + + adapter->ring_vir_addr = NULL; + adapter->rx_ring.desc = NULL; + rwlock_init(&adapter->tx_ring.tx_lock); +} + +/* + * Read / Write Ptr Initialize: + */ +static void atl1e_init_ring_ptrs(struct atl1e_adapter *adapter) +{ + struct atl1e_tx_ring *tx_ring = NULL; + struct atl1e_rx_ring *rx_ring = NULL; + struct atl1e_rx_page_desc *rx_page_desc = NULL; + int i, j; + + tx_ring = &adapter->tx_ring; + rx_ring = &adapter->rx_ring; + rx_page_desc = rx_ring->rx_page_desc; + + tx_ring->next_to_use = 0; + atomic_set(&tx_ring->next_to_clean, 0); + + for (i = 0; i < adapter->num_rx_queues; i++) { + rx_page_desc[i].rx_using = 0; + rx_page_desc[i].rx_nxseq = 0; + for (j = 0; j < AT_PAGE_NUM_PER_QUEUE; j++) { + *rx_page_desc[i].rx_page[j].write_offset_addr = 0; + rx_page_desc[i].rx_page[j].read_offset = 0; + } + } +} + +/** + * atl1e_free_ring_resources - Free Tx / RX descriptor Resources + * @adapter: board private structure + * + * Free all transmit software resources + */ +static void atl1e_free_ring_resources(struct atl1e_adapter *adapter) +{ + struct pci_dev *pdev = adapter->pdev; + + atl1e_clean_tx_ring(adapter); + atl1e_clean_rx_ring(adapter); + + if (adapter->ring_vir_addr) { + pci_free_consistent(pdev, adapter->ring_size, + adapter->ring_vir_addr, adapter->ring_dma); + adapter->ring_vir_addr = NULL; + } + + if (adapter->tx_ring.tx_buffer) { + kfree(adapter->tx_ring.tx_buffer); + adapter->tx_ring.tx_buffer = NULL; + } +} + +/** + * atl1e_setup_mem_resources - allocate Tx / RX descriptor resources + * @adapter: board private structure + * + * Return 0 on success, negative on failure + */ +static int atl1e_setup_ring_resources(struct atl1e_adapter *adapter) +{ + struct pci_dev *pdev = adapter->pdev; + struct atl1e_tx_ring *tx_ring; + struct atl1e_rx_ring *rx_ring; + struct atl1e_rx_page_desc *rx_page_desc; + int size, i, j; + u32 offset = 0; + int err = 0; + + if (adapter->ring_vir_addr != NULL) + return 0; /* alloced already */ + + tx_ring = &adapter->tx_ring; + rx_ring = &adapter->rx_ring; + + /* real ring DMA buffer */ + + size = adapter->ring_size; + adapter->ring_vir_addr = pci_alloc_consistent(pdev, + adapter->ring_size, &adapter->ring_dma); + + if (adapter->ring_vir_addr == NULL) { + netdev_err(adapter->netdev, + "pci_alloc_consistent failed, size = D%d\n", size); + return -ENOMEM; + } + + memset(adapter->ring_vir_addr, 0, adapter->ring_size); + + rx_page_desc = rx_ring->rx_page_desc; + + /* Init TPD Ring */ + tx_ring->dma = roundup(adapter->ring_dma, 8); + offset = tx_ring->dma - adapter->ring_dma; + tx_ring->desc = adapter->ring_vir_addr + offset; + size = sizeof(struct atl1e_tx_buffer) * (tx_ring->count); + tx_ring->tx_buffer = kzalloc(size, GFP_KERNEL); + if (tx_ring->tx_buffer == NULL) { + err = -ENOMEM; + goto failed; + } + + /* Init RXF-Pages */ + offset += (sizeof(struct atl1e_tpd_desc) * tx_ring->count); + offset = roundup(offset, 32); + + for (i = 0; i < adapter->num_rx_queues; i++) { + for (j = 0; j < AT_PAGE_NUM_PER_QUEUE; j++) { + rx_page_desc[i].rx_page[j].dma = + adapter->ring_dma + offset; + rx_page_desc[i].rx_page[j].addr = + adapter->ring_vir_addr + offset; + offset += rx_ring->real_page_size; + } + } + + /* Init CMB dma address */ + tx_ring->cmb_dma = adapter->ring_dma + offset; + tx_ring->cmb = adapter->ring_vir_addr + offset; + offset += sizeof(u32); + + for (i = 0; i < adapter->num_rx_queues; i++) { + for (j = 0; j < AT_PAGE_NUM_PER_QUEUE; j++) { + rx_page_desc[i].rx_page[j].write_offset_dma = + adapter->ring_dma + offset; + rx_page_desc[i].rx_page[j].write_offset_addr = + adapter->ring_vir_addr + offset; + offset += sizeof(u32); + } + } + + if (unlikely(offset > adapter->ring_size)) { + netdev_err(adapter->netdev, "offset(%d) > ring size(%d) !!\n", + offset, adapter->ring_size); + err = -1; + goto failed; + } + + return 0; +failed: + if (adapter->ring_vir_addr != NULL) { + pci_free_consistent(pdev, adapter->ring_size, + adapter->ring_vir_addr, adapter->ring_dma); + adapter->ring_vir_addr = NULL; + } + return err; +} + +static inline void atl1e_configure_des_ring(struct atl1e_adapter *adapter) +{ + + struct atl1e_hw *hw = &adapter->hw; + struct atl1e_rx_ring *rx_ring = &adapter->rx_ring; + struct atl1e_tx_ring *tx_ring = &adapter->tx_ring; + struct atl1e_rx_page_desc *rx_page_desc = NULL; + int i, j; + + AT_WRITE_REG(hw, REG_DESC_BASE_ADDR_HI, + (u32)((adapter->ring_dma & AT_DMA_HI_ADDR_MASK) >> 32)); + AT_WRITE_REG(hw, REG_TPD_BASE_ADDR_LO, + (u32)((tx_ring->dma) & AT_DMA_LO_ADDR_MASK)); + AT_WRITE_REG(hw, REG_TPD_RING_SIZE, (u16)(tx_ring->count)); + AT_WRITE_REG(hw, REG_HOST_TX_CMB_LO, + (u32)((tx_ring->cmb_dma) & AT_DMA_LO_ADDR_MASK)); + + rx_page_desc = rx_ring->rx_page_desc; + /* RXF Page Physical address / Page Length */ + for (i = 0; i < AT_MAX_RECEIVE_QUEUE; i++) { + AT_WRITE_REG(hw, atl1e_rx_page_hi_addr_regs[i], + (u32)((adapter->ring_dma & + AT_DMA_HI_ADDR_MASK) >> 32)); + for (j = 0; j < AT_PAGE_NUM_PER_QUEUE; j++) { + u32 page_phy_addr; + u32 offset_phy_addr; + + page_phy_addr = rx_page_desc[i].rx_page[j].dma; + offset_phy_addr = + rx_page_desc[i].rx_page[j].write_offset_dma; + + AT_WRITE_REG(hw, atl1e_rx_page_lo_addr_regs[i][j], + page_phy_addr & AT_DMA_LO_ADDR_MASK); + AT_WRITE_REG(hw, atl1e_rx_page_write_offset_regs[i][j], + offset_phy_addr & AT_DMA_LO_ADDR_MASK); + AT_WRITE_REGB(hw, atl1e_rx_page_vld_regs[i][j], 1); + } + } + /* Page Length */ + AT_WRITE_REG(hw, REG_HOST_RXFPAGE_SIZE, rx_ring->page_size); + /* Load all of base address above */ + AT_WRITE_REG(hw, REG_LOAD_PTR, 1); +} + +static inline void atl1e_configure_tx(struct atl1e_adapter *adapter) +{ + struct atl1e_hw *hw = &adapter->hw; + u32 dev_ctrl_data = 0; + u32 max_pay_load = 0; + u32 jumbo_thresh = 0; + u32 extra_size = 0; /* Jumbo frame threshold in QWORD unit */ + + /* configure TXQ param */ + if (hw->nic_type != athr_l2e_revB) { + extra_size = ETH_HLEN + VLAN_HLEN + ETH_FCS_LEN; + if (hw->max_frame_size <= 1500) { + jumbo_thresh = hw->max_frame_size + extra_size; + } else if (hw->max_frame_size < 6*1024) { + jumbo_thresh = + (hw->max_frame_size + extra_size) * 2 / 3; + } else { + jumbo_thresh = (hw->max_frame_size + extra_size) / 2; + } + AT_WRITE_REG(hw, REG_TX_EARLY_TH, (jumbo_thresh + 7) >> 3); + } + + dev_ctrl_data = AT_READ_REG(hw, REG_DEVICE_CTRL); + + max_pay_load = ((dev_ctrl_data >> DEVICE_CTRL_MAX_PAYLOAD_SHIFT)) & + DEVICE_CTRL_MAX_PAYLOAD_MASK; + + hw->dmaw_block = min_t(u32, max_pay_load, hw->dmaw_block); + + max_pay_load = ((dev_ctrl_data >> DEVICE_CTRL_MAX_RREQ_SZ_SHIFT)) & + DEVICE_CTRL_MAX_RREQ_SZ_MASK; + hw->dmar_block = min_t(u32, max_pay_load, hw->dmar_block); + + if (hw->nic_type != athr_l2e_revB) + AT_WRITE_REGW(hw, REG_TXQ_CTRL + 2, + atl1e_pay_load_size[hw->dmar_block]); + /* enable TXQ */ + AT_WRITE_REGW(hw, REG_TXQ_CTRL, + (((u16)hw->tpd_burst & TXQ_CTRL_NUM_TPD_BURST_MASK) + << TXQ_CTRL_NUM_TPD_BURST_SHIFT) + | TXQ_CTRL_ENH_MODE | TXQ_CTRL_EN); +} + +static inline void atl1e_configure_rx(struct atl1e_adapter *adapter) +{ + struct atl1e_hw *hw = &adapter->hw; + u32 rxf_len = 0; + u32 rxf_low = 0; + u32 rxf_high = 0; + u32 rxf_thresh_data = 0; + u32 rxq_ctrl_data = 0; + + if (hw->nic_type != athr_l2e_revB) { + AT_WRITE_REGW(hw, REG_RXQ_JMBOSZ_RRDTIM, + (u16)((hw->rx_jumbo_th & RXQ_JMBOSZ_TH_MASK) << + RXQ_JMBOSZ_TH_SHIFT | + (1 & RXQ_JMBO_LKAH_MASK) << + RXQ_JMBO_LKAH_SHIFT)); + + rxf_len = AT_READ_REG(hw, REG_SRAM_RXF_LEN); + rxf_high = rxf_len * 4 / 5; + rxf_low = rxf_len / 5; + rxf_thresh_data = ((rxf_high & RXQ_RXF_PAUSE_TH_HI_MASK) + << RXQ_RXF_PAUSE_TH_HI_SHIFT) | + ((rxf_low & RXQ_RXF_PAUSE_TH_LO_MASK) + << RXQ_RXF_PAUSE_TH_LO_SHIFT); + + AT_WRITE_REG(hw, REG_RXQ_RXF_PAUSE_THRESH, rxf_thresh_data); + } + + /* RRS */ + AT_WRITE_REG(hw, REG_IDT_TABLE, hw->indirect_tab); + AT_WRITE_REG(hw, REG_BASE_CPU_NUMBER, hw->base_cpu); + + if (hw->rrs_type & atl1e_rrs_ipv4) + rxq_ctrl_data |= RXQ_CTRL_HASH_TYPE_IPV4; + + if (hw->rrs_type & atl1e_rrs_ipv4_tcp) + rxq_ctrl_data |= RXQ_CTRL_HASH_TYPE_IPV4_TCP; + + if (hw->rrs_type & atl1e_rrs_ipv6) + rxq_ctrl_data |= RXQ_CTRL_HASH_TYPE_IPV6; + + if (hw->rrs_type & atl1e_rrs_ipv6_tcp) + rxq_ctrl_data |= RXQ_CTRL_HASH_TYPE_IPV6_TCP; + + if (hw->rrs_type != atl1e_rrs_disable) + rxq_ctrl_data |= + (RXQ_CTRL_HASH_ENABLE | RXQ_CTRL_RSS_MODE_MQUESINT); + + rxq_ctrl_data |= RXQ_CTRL_IPV6_XSUM_VERIFY_EN | RXQ_CTRL_PBA_ALIGN_32 | + RXQ_CTRL_CUT_THRU_EN | RXQ_CTRL_EN; + + AT_WRITE_REG(hw, REG_RXQ_CTRL, rxq_ctrl_data); +} + +static inline void atl1e_configure_dma(struct atl1e_adapter *adapter) +{ + struct atl1e_hw *hw = &adapter->hw; + u32 dma_ctrl_data = 0; + + dma_ctrl_data = DMA_CTRL_RXCMB_EN; + dma_ctrl_data |= (((u32)hw->dmar_block) & DMA_CTRL_DMAR_BURST_LEN_MASK) + << DMA_CTRL_DMAR_BURST_LEN_SHIFT; + dma_ctrl_data |= (((u32)hw->dmaw_block) & DMA_CTRL_DMAW_BURST_LEN_MASK) + << DMA_CTRL_DMAW_BURST_LEN_SHIFT; + dma_ctrl_data |= DMA_CTRL_DMAR_REQ_PRI | DMA_CTRL_DMAR_OUT_ORDER; + dma_ctrl_data |= (((u32)hw->dmar_dly_cnt) & DMA_CTRL_DMAR_DLY_CNT_MASK) + << DMA_CTRL_DMAR_DLY_CNT_SHIFT; + dma_ctrl_data |= (((u32)hw->dmaw_dly_cnt) & DMA_CTRL_DMAW_DLY_CNT_MASK) + << DMA_CTRL_DMAW_DLY_CNT_SHIFT; + + AT_WRITE_REG(hw, REG_DMA_CTRL, dma_ctrl_data); +} + +static void atl1e_setup_mac_ctrl(struct atl1e_adapter *adapter) +{ + u32 value; + struct atl1e_hw *hw = &adapter->hw; + struct net_device *netdev = adapter->netdev; + + /* Config MAC CTRL Register */ + value = MAC_CTRL_TX_EN | + MAC_CTRL_RX_EN ; + + if (FULL_DUPLEX == adapter->link_duplex) + value |= MAC_CTRL_DUPLX; + + value |= ((u32)((SPEED_1000 == adapter->link_speed) ? + MAC_CTRL_SPEED_1000 : MAC_CTRL_SPEED_10_100) << + MAC_CTRL_SPEED_SHIFT); + value |= (MAC_CTRL_TX_FLOW | MAC_CTRL_RX_FLOW); + + value |= (MAC_CTRL_ADD_CRC | MAC_CTRL_PAD); + value |= (((u32)adapter->hw.preamble_len & + MAC_CTRL_PRMLEN_MASK) << MAC_CTRL_PRMLEN_SHIFT); + + __atl1e_vlan_mode(netdev->features, &value); + + value |= MAC_CTRL_BC_EN; + if (netdev->flags & IFF_PROMISC) + value |= MAC_CTRL_PROMIS_EN; + if (netdev->flags & IFF_ALLMULTI) + value |= MAC_CTRL_MC_ALL_EN; + + AT_WRITE_REG(hw, REG_MAC_CTRL, value); +} + +/** + * atl1e_configure - Configure Transmit&Receive Unit after Reset + * @adapter: board private structure + * + * Configure the Tx /Rx unit of the MAC after a reset. + */ +static int atl1e_configure(struct atl1e_adapter *adapter) +{ + struct atl1e_hw *hw = &adapter->hw; + + u32 intr_status_data = 0; + + /* clear interrupt status */ + AT_WRITE_REG(hw, REG_ISR, ~0); + + /* 1. set MAC Address */ + atl1e_hw_set_mac_addr(hw); + + /* 2. Init the Multicast HASH table done by set_muti */ + + /* 3. Clear any WOL status */ + AT_WRITE_REG(hw, REG_WOL_CTRL, 0); + + /* 4. Descripter Ring BaseMem/Length/Read ptr/Write ptr + * TPD Ring/SMB/RXF0 Page CMBs, they use the same + * High 32bits memory */ + atl1e_configure_des_ring(adapter); + + /* 5. set Interrupt Moderator Timer */ + AT_WRITE_REGW(hw, REG_IRQ_MODU_TIMER_INIT, hw->imt); + AT_WRITE_REGW(hw, REG_IRQ_MODU_TIMER2_INIT, hw->imt); + AT_WRITE_REG(hw, REG_MASTER_CTRL, MASTER_CTRL_LED_MODE | + MASTER_CTRL_ITIMER_EN | MASTER_CTRL_ITIMER2_EN); + + /* 6. rx/tx threshold to trig interrupt */ + AT_WRITE_REGW(hw, REG_TRIG_RRD_THRESH, hw->rrd_thresh); + AT_WRITE_REGW(hw, REG_TRIG_TPD_THRESH, hw->tpd_thresh); + AT_WRITE_REGW(hw, REG_TRIG_RXTIMER, hw->rx_count_down); + AT_WRITE_REGW(hw, REG_TRIG_TXTIMER, hw->tx_count_down); + + /* 7. set Interrupt Clear Timer */ + AT_WRITE_REGW(hw, REG_CMBDISDMA_TIMER, hw->ict); + + /* 8. set MTU */ + AT_WRITE_REG(hw, REG_MTU, hw->max_frame_size + ETH_HLEN + + VLAN_HLEN + ETH_FCS_LEN); + + /* 9. config TXQ early tx threshold */ + atl1e_configure_tx(adapter); + + /* 10. config RXQ */ + atl1e_configure_rx(adapter); + + /* 11. config DMA Engine */ + atl1e_configure_dma(adapter); + + /* 12. smb timer to trig interrupt */ + AT_WRITE_REG(hw, REG_SMB_STAT_TIMER, hw->smb_timer); + + intr_status_data = AT_READ_REG(hw, REG_ISR); + if (unlikely((intr_status_data & ISR_PHY_LINKDOWN) != 0)) { + netdev_err(adapter->netdev, + "atl1e_configure failed, PCIE phy link down\n"); + return -1; + } + + AT_WRITE_REG(hw, REG_ISR, 0x7fffffff); + return 0; +} + +/** + * atl1e_get_stats - Get System Network Statistics + * @netdev: network interface device structure + * + * Returns the address of the device statistics structure. + * The statistics are actually updated from the timer callback. + */ +static struct net_device_stats *atl1e_get_stats(struct net_device *netdev) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + struct atl1e_hw_stats *hw_stats = &adapter->hw_stats; + struct net_device_stats *net_stats = &netdev->stats; + + net_stats->rx_packets = hw_stats->rx_ok; + net_stats->tx_packets = hw_stats->tx_ok; + net_stats->rx_bytes = hw_stats->rx_byte_cnt; + net_stats->tx_bytes = hw_stats->tx_byte_cnt; + net_stats->multicast = hw_stats->rx_mcast; + net_stats->collisions = hw_stats->tx_1_col + + hw_stats->tx_2_col * 2 + + hw_stats->tx_late_col + hw_stats->tx_abort_col; + + net_stats->rx_errors = hw_stats->rx_frag + hw_stats->rx_fcs_err + + hw_stats->rx_len_err + hw_stats->rx_sz_ov + + hw_stats->rx_rrd_ov + hw_stats->rx_align_err; + net_stats->rx_fifo_errors = hw_stats->rx_rxf_ov; + net_stats->rx_length_errors = hw_stats->rx_len_err; + net_stats->rx_crc_errors = hw_stats->rx_fcs_err; + net_stats->rx_frame_errors = hw_stats->rx_align_err; + net_stats->rx_over_errors = hw_stats->rx_rrd_ov + hw_stats->rx_rxf_ov; + + net_stats->rx_missed_errors = hw_stats->rx_rrd_ov + hw_stats->rx_rxf_ov; + + net_stats->tx_errors = hw_stats->tx_late_col + hw_stats->tx_abort_col + + hw_stats->tx_underrun + hw_stats->tx_trunc; + net_stats->tx_fifo_errors = hw_stats->tx_underrun; + net_stats->tx_aborted_errors = hw_stats->tx_abort_col; + net_stats->tx_window_errors = hw_stats->tx_late_col; + + return net_stats; +} + +static void atl1e_update_hw_stats(struct atl1e_adapter *adapter) +{ + u16 hw_reg_addr = 0; + unsigned long *stats_item = NULL; + + /* update rx status */ + hw_reg_addr = REG_MAC_RX_STATUS_BIN; + stats_item = &adapter->hw_stats.rx_ok; + while (hw_reg_addr <= REG_MAC_RX_STATUS_END) { + *stats_item += AT_READ_REG(&adapter->hw, hw_reg_addr); + stats_item++; + hw_reg_addr += 4; + } + /* update tx status */ + hw_reg_addr = REG_MAC_TX_STATUS_BIN; + stats_item = &adapter->hw_stats.tx_ok; + while (hw_reg_addr <= REG_MAC_TX_STATUS_END) { + *stats_item += AT_READ_REG(&adapter->hw, hw_reg_addr); + stats_item++; + hw_reg_addr += 4; + } +} + +static inline void atl1e_clear_phy_int(struct atl1e_adapter *adapter) +{ + u16 phy_data; + + spin_lock(&adapter->mdio_lock); + atl1e_read_phy_reg(&adapter->hw, MII_INT_STATUS, &phy_data); + spin_unlock(&adapter->mdio_lock); +} + +static bool atl1e_clean_tx_irq(struct atl1e_adapter *adapter) +{ + struct atl1e_tx_ring *tx_ring = &adapter->tx_ring; + struct atl1e_tx_buffer *tx_buffer = NULL; + u16 hw_next_to_clean = AT_READ_REGW(&adapter->hw, REG_TPD_CONS_IDX); + u16 next_to_clean = atomic_read(&tx_ring->next_to_clean); + + while (next_to_clean != hw_next_to_clean) { + tx_buffer = &tx_ring->tx_buffer[next_to_clean]; + if (tx_buffer->dma) { + if (tx_buffer->flags & ATL1E_TX_PCIMAP_SINGLE) + pci_unmap_single(adapter->pdev, tx_buffer->dma, + tx_buffer->length, PCI_DMA_TODEVICE); + else if (tx_buffer->flags & ATL1E_TX_PCIMAP_PAGE) + pci_unmap_page(adapter->pdev, tx_buffer->dma, + tx_buffer->length, PCI_DMA_TODEVICE); + tx_buffer->dma = 0; + } + + if (tx_buffer->skb) { + dev_kfree_skb_irq(tx_buffer->skb); + tx_buffer->skb = NULL; + } + + if (++next_to_clean == tx_ring->count) + next_to_clean = 0; + } + + atomic_set(&tx_ring->next_to_clean, next_to_clean); + + if (netif_queue_stopped(adapter->netdev) && + netif_carrier_ok(adapter->netdev)) { + netif_wake_queue(adapter->netdev); + } + + return true; +} + +/** + * atl1e_intr - Interrupt Handler + * @irq: interrupt number + * @data: pointer to a network interface device structure + */ +static irqreturn_t atl1e_intr(int irq, void *data) +{ + struct net_device *netdev = data; + struct atl1e_adapter *adapter = netdev_priv(netdev); + struct atl1e_hw *hw = &adapter->hw; + int max_ints = AT_MAX_INT_WORK; + int handled = IRQ_NONE; + u32 status; + + do { + status = AT_READ_REG(hw, REG_ISR); + if ((status & IMR_NORMAL_MASK) == 0 || + (status & ISR_DIS_INT) != 0) { + if (max_ints != AT_MAX_INT_WORK) + handled = IRQ_HANDLED; + break; + } + /* link event */ + if (status & ISR_GPHY) + atl1e_clear_phy_int(adapter); + /* Ack ISR */ + AT_WRITE_REG(hw, REG_ISR, status | ISR_DIS_INT); + + handled = IRQ_HANDLED; + /* check if PCIE PHY Link down */ + if (status & ISR_PHY_LINKDOWN) { + netdev_err(adapter->netdev, + "pcie phy linkdown %x\n", status); + if (netif_running(adapter->netdev)) { + /* reset MAC */ + atl1e_irq_reset(adapter); + schedule_work(&adapter->reset_task); + break; + } + } + + /* check if DMA read/write error */ + if (status & (ISR_DMAR_TO_RST | ISR_DMAW_TO_RST)) { + netdev_err(adapter->netdev, + "PCIE DMA RW error (status = 0x%x)\n", + status); + atl1e_irq_reset(adapter); + schedule_work(&adapter->reset_task); + break; + } + + if (status & ISR_SMB) + atl1e_update_hw_stats(adapter); + + /* link event */ + if (status & (ISR_GPHY | ISR_MANUAL)) { + netdev->stats.tx_carrier_errors++; + atl1e_link_chg_event(adapter); + break; + } + + /* transmit event */ + if (status & ISR_TX_EVENT) + atl1e_clean_tx_irq(adapter); + + if (status & ISR_RX_EVENT) { + /* + * disable rx interrupts, without + * the synchronize_irq bit + */ + AT_WRITE_REG(hw, REG_IMR, + IMR_NORMAL_MASK & ~ISR_RX_EVENT); + AT_WRITE_FLUSH(hw); + if (likely(napi_schedule_prep( + &adapter->napi))) + __napi_schedule(&adapter->napi); + } + } while (--max_ints > 0); + /* re-enable Interrupt*/ + AT_WRITE_REG(&adapter->hw, REG_ISR, 0); + + return handled; +} + +static inline void atl1e_rx_checksum(struct atl1e_adapter *adapter, + struct sk_buff *skb, struct atl1e_recv_ret_status *prrs) +{ + u8 *packet = (u8 *)(prrs + 1); + struct iphdr *iph; + u16 head_len = ETH_HLEN; + u16 pkt_flags; + u16 err_flags; + + skb_checksum_none_assert(skb); + pkt_flags = prrs->pkt_flag; + err_flags = prrs->err_flag; + if (((pkt_flags & RRS_IS_IPV4) || (pkt_flags & RRS_IS_IPV6)) && + ((pkt_flags & RRS_IS_TCP) || (pkt_flags & RRS_IS_UDP))) { + if (pkt_flags & RRS_IS_IPV4) { + if (pkt_flags & RRS_IS_802_3) + head_len += 8; + iph = (struct iphdr *) (packet + head_len); + if (iph->frag_off != 0 && !(pkt_flags & RRS_IS_IP_DF)) + goto hw_xsum; + } + if (!(err_flags & (RRS_ERR_IP_CSUM | RRS_ERR_L4_CSUM))) { + skb->ip_summed = CHECKSUM_UNNECESSARY; + return; + } + } + +hw_xsum : + return; +} + +static struct atl1e_rx_page *atl1e_get_rx_page(struct atl1e_adapter *adapter, + u8 que) +{ + struct atl1e_rx_page_desc *rx_page_desc = + (struct atl1e_rx_page_desc *) adapter->rx_ring.rx_page_desc; + u8 rx_using = rx_page_desc[que].rx_using; + + return &(rx_page_desc[que].rx_page[rx_using]); +} + +static void atl1e_clean_rx_irq(struct atl1e_adapter *adapter, u8 que, + int *work_done, int work_to_do) +{ + struct net_device *netdev = adapter->netdev; + struct atl1e_rx_ring *rx_ring = &adapter->rx_ring; + struct atl1e_rx_page_desc *rx_page_desc = + (struct atl1e_rx_page_desc *) rx_ring->rx_page_desc; + struct sk_buff *skb = NULL; + struct atl1e_rx_page *rx_page = atl1e_get_rx_page(adapter, que); + u32 packet_size, write_offset; + struct atl1e_recv_ret_status *prrs; + + write_offset = *(rx_page->write_offset_addr); + if (likely(rx_page->read_offset < write_offset)) { + do { + if (*work_done >= work_to_do) + break; + (*work_done)++; + /* get new packet's rrs */ + prrs = (struct atl1e_recv_ret_status *) (rx_page->addr + + rx_page->read_offset); + /* check sequence number */ + if (prrs->seq_num != rx_page_desc[que].rx_nxseq) { + netdev_err(netdev, + "rx sequence number error (rx=%d) (expect=%d)\n", + prrs->seq_num, + rx_page_desc[que].rx_nxseq); + rx_page_desc[que].rx_nxseq++; + /* just for debug use */ + AT_WRITE_REG(&adapter->hw, REG_DEBUG_DATA0, + (((u32)prrs->seq_num) << 16) | + rx_page_desc[que].rx_nxseq); + goto fatal_err; + } + rx_page_desc[que].rx_nxseq++; + + /* error packet */ + if (prrs->pkt_flag & RRS_IS_ERR_FRAME) { + if (prrs->err_flag & (RRS_ERR_BAD_CRC | + RRS_ERR_DRIBBLE | RRS_ERR_CODE | + RRS_ERR_TRUNC)) { + /* hardware error, discard this packet*/ + netdev_err(netdev, + "rx packet desc error %x\n", + *((u32 *)prrs + 1)); + goto skip_pkt; + } + } + + packet_size = ((prrs->word1 >> RRS_PKT_SIZE_SHIFT) & + RRS_PKT_SIZE_MASK) - 4; /* CRC */ + skb = netdev_alloc_skb_ip_align(netdev, packet_size); + if (skb == NULL) + goto skip_pkt; + + memcpy(skb->data, (u8 *)(prrs + 1), packet_size); + skb_put(skb, packet_size); + skb->protocol = eth_type_trans(skb, netdev); + atl1e_rx_checksum(adapter, skb, prrs); + + if (prrs->pkt_flag & RRS_IS_VLAN_TAG) { + u16 vlan_tag = (prrs->vtag >> 4) | + ((prrs->vtag & 7) << 13) | + ((prrs->vtag & 8) << 9); + netdev_dbg(netdev, + "RXD VLAN TAG=0x%04x\n", + prrs->vtag); + __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan_tag); + } + netif_receive_skb(skb); + +skip_pkt: + /* skip current packet whether it's ok or not. */ + rx_page->read_offset += + (((u32)((prrs->word1 >> RRS_PKT_SIZE_SHIFT) & + RRS_PKT_SIZE_MASK) + + sizeof(struct atl1e_recv_ret_status) + 31) & + 0xFFFFFFE0); + + if (rx_page->read_offset >= rx_ring->page_size) { + /* mark this page clean */ + u16 reg_addr; + u8 rx_using; + + rx_page->read_offset = + *(rx_page->write_offset_addr) = 0; + rx_using = rx_page_desc[que].rx_using; + reg_addr = + atl1e_rx_page_vld_regs[que][rx_using]; + AT_WRITE_REGB(&adapter->hw, reg_addr, 1); + rx_page_desc[que].rx_using ^= 1; + rx_page = atl1e_get_rx_page(adapter, que); + } + write_offset = *(rx_page->write_offset_addr); + } while (rx_page->read_offset < write_offset); + } + + return; + +fatal_err: + if (!test_bit(__AT_DOWN, &adapter->flags)) + schedule_work(&adapter->reset_task); +} + +/** + * atl1e_clean - NAPI Rx polling callback + */ +static int atl1e_clean(struct napi_struct *napi, int budget) +{ + struct atl1e_adapter *adapter = + container_of(napi, struct atl1e_adapter, napi); + u32 imr_data; + int work_done = 0; + + /* Keep link state information with original netdev */ + if (!netif_carrier_ok(adapter->netdev)) + goto quit_polling; + + atl1e_clean_rx_irq(adapter, 0, &work_done, budget); + + /* If no Tx and not enough Rx work done, exit the polling mode */ + if (work_done < budget) { +quit_polling: + napi_complete(napi); + imr_data = AT_READ_REG(&adapter->hw, REG_IMR); + AT_WRITE_REG(&adapter->hw, REG_IMR, imr_data | ISR_RX_EVENT); + /* test debug */ + if (test_bit(__AT_DOWN, &adapter->flags)) { + atomic_dec(&adapter->irq_sem); + netdev_err(adapter->netdev, + "atl1e_clean is called when AT_DOWN\n"); + } + /* reenable RX intr */ + /*atl1e_irq_enable(adapter); */ + + } + return work_done; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER + +/* + * Polling 'interrupt' - used by things like netconsole to send skbs + * without having to re-enable interrupts. It's not called while + * the interrupt routine is executing. + */ +static void atl1e_netpoll(struct net_device *netdev) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + + disable_irq(adapter->pdev->irq); + atl1e_intr(adapter->pdev->irq, netdev); + enable_irq(adapter->pdev->irq); +} +#endif + +static inline u16 atl1e_tpd_avail(struct atl1e_adapter *adapter) +{ + struct atl1e_tx_ring *tx_ring = &adapter->tx_ring; + u16 next_to_use = 0; + u16 next_to_clean = 0; + + next_to_clean = atomic_read(&tx_ring->next_to_clean); + next_to_use = tx_ring->next_to_use; + + return (u16)(next_to_clean > next_to_use) ? + (next_to_clean - next_to_use - 1) : + (tx_ring->count + next_to_clean - next_to_use - 1); +} + +/* + * get next usable tpd + * Note: should call atl1e_tdp_avail to make sure + * there is enough tpd to use + */ +static struct atl1e_tpd_desc *atl1e_get_tpd(struct atl1e_adapter *adapter) +{ + struct atl1e_tx_ring *tx_ring = &adapter->tx_ring; + u16 next_to_use = 0; + + next_to_use = tx_ring->next_to_use; + if (++tx_ring->next_to_use == tx_ring->count) + tx_ring->next_to_use = 0; + + memset(&tx_ring->desc[next_to_use], 0, sizeof(struct atl1e_tpd_desc)); + return &tx_ring->desc[next_to_use]; +} + +static struct atl1e_tx_buffer * +atl1e_get_tx_buffer(struct atl1e_adapter *adapter, struct atl1e_tpd_desc *tpd) +{ + struct atl1e_tx_ring *tx_ring = &adapter->tx_ring; + + return &tx_ring->tx_buffer[tpd - tx_ring->desc]; +} + +/* Calculate the transmit packet descript needed*/ +static u16 atl1e_cal_tdp_req(const struct sk_buff *skb) +{ + int i = 0; + u16 tpd_req = 1; + u16 fg_size = 0; + u16 proto_hdr_len = 0; + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + fg_size = skb_frag_size(&skb_shinfo(skb)->frags[i]); + tpd_req += ((fg_size + MAX_TX_BUF_LEN - 1) >> MAX_TX_BUF_SHIFT); + } + + if (skb_is_gso(skb)) { + if (skb->protocol == htons(ETH_P_IP) || + (skb_shinfo(skb)->gso_type == SKB_GSO_TCPV6)) { + proto_hdr_len = skb_transport_offset(skb) + + tcp_hdrlen(skb); + if (proto_hdr_len < skb_headlen(skb)) { + tpd_req += ((skb_headlen(skb) - proto_hdr_len + + MAX_TX_BUF_LEN - 1) >> + MAX_TX_BUF_SHIFT); + } + } + + } + return tpd_req; +} + +static int atl1e_tso_csum(struct atl1e_adapter *adapter, + struct sk_buff *skb, struct atl1e_tpd_desc *tpd) +{ + u8 hdr_len; + u32 real_len; + unsigned short offload_type; + int err; + + if (skb_is_gso(skb)) { + if (skb_header_cloned(skb)) { + err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC); + if (unlikely(err)) + return -1; + } + offload_type = skb_shinfo(skb)->gso_type; + + if (offload_type & SKB_GSO_TCPV4) { + real_len = (((unsigned char *)ip_hdr(skb) - skb->data) + + ntohs(ip_hdr(skb)->tot_len)); + + if (real_len < skb->len) + pskb_trim(skb, real_len); + + hdr_len = (skb_transport_offset(skb) + tcp_hdrlen(skb)); + if (unlikely(skb->len == hdr_len)) { + /* only xsum need */ + netdev_warn(adapter->netdev, + "IPV4 tso with zero data??\n"); + goto check_sum; + } else { + ip_hdr(skb)->check = 0; + ip_hdr(skb)->tot_len = 0; + tcp_hdr(skb)->check = ~csum_tcpudp_magic( + ip_hdr(skb)->saddr, + ip_hdr(skb)->daddr, + 0, IPPROTO_TCP, 0); + tpd->word3 |= (ip_hdr(skb)->ihl & + TDP_V4_IPHL_MASK) << + TPD_V4_IPHL_SHIFT; + tpd->word3 |= ((tcp_hdrlen(skb) >> 2) & + TPD_TCPHDRLEN_MASK) << + TPD_TCPHDRLEN_SHIFT; + tpd->word3 |= ((skb_shinfo(skb)->gso_size) & + TPD_MSS_MASK) << TPD_MSS_SHIFT; + tpd->word3 |= 1 << TPD_SEGMENT_EN_SHIFT; + } + return 0; + } + } + +check_sum: + if (likely(skb->ip_summed == CHECKSUM_PARTIAL)) { + u8 css, cso; + + cso = skb_checksum_start_offset(skb); + if (unlikely(cso & 0x1)) { + netdev_err(adapter->netdev, + "payload offset should not ant event number\n"); + return -1; + } else { + css = cso + skb->csum_offset; + tpd->word3 |= (cso & TPD_PLOADOFFSET_MASK) << + TPD_PLOADOFFSET_SHIFT; + tpd->word3 |= (css & TPD_CCSUMOFFSET_MASK) << + TPD_CCSUMOFFSET_SHIFT; + tpd->word3 |= 1 << TPD_CC_SEGMENT_EN_SHIFT; + } + } + + return 0; +} + +static int atl1e_tx_map(struct atl1e_adapter *adapter, + struct sk_buff *skb, struct atl1e_tpd_desc *tpd) +{ + struct atl1e_tpd_desc *use_tpd = NULL; + struct atl1e_tx_buffer *tx_buffer = NULL; + u16 buf_len = skb_headlen(skb); + u16 map_len = 0; + u16 mapped_len = 0; + u16 hdr_len = 0; + u16 nr_frags; + u16 f; + int segment; + int ring_start = adapter->tx_ring.next_to_use; + int ring_end; + + nr_frags = skb_shinfo(skb)->nr_frags; + segment = (tpd->word3 >> TPD_SEGMENT_EN_SHIFT) & TPD_SEGMENT_EN_MASK; + if (segment) { + /* TSO */ + map_len = hdr_len = skb_transport_offset(skb) + tcp_hdrlen(skb); + use_tpd = tpd; + + tx_buffer = atl1e_get_tx_buffer(adapter, use_tpd); + tx_buffer->length = map_len; + tx_buffer->dma = pci_map_single(adapter->pdev, + skb->data, hdr_len, PCI_DMA_TODEVICE); + if (dma_mapping_error(&adapter->pdev->dev, tx_buffer->dma)) + return -ENOSPC; + + ATL1E_SET_PCIMAP_TYPE(tx_buffer, ATL1E_TX_PCIMAP_SINGLE); + mapped_len += map_len; + use_tpd->buffer_addr = cpu_to_le64(tx_buffer->dma); + use_tpd->word2 = (use_tpd->word2 & (~TPD_BUFLEN_MASK)) | + ((cpu_to_le32(tx_buffer->length) & + TPD_BUFLEN_MASK) << TPD_BUFLEN_SHIFT); + } + + while (mapped_len < buf_len) { + /* mapped_len == 0, means we should use the first tpd, + which is given by caller */ + if (mapped_len == 0) { + use_tpd = tpd; + } else { + use_tpd = atl1e_get_tpd(adapter); + memcpy(use_tpd, tpd, sizeof(struct atl1e_tpd_desc)); + } + tx_buffer = atl1e_get_tx_buffer(adapter, use_tpd); + tx_buffer->skb = NULL; + + tx_buffer->length = map_len = + ((buf_len - mapped_len) >= MAX_TX_BUF_LEN) ? + MAX_TX_BUF_LEN : (buf_len - mapped_len); + tx_buffer->dma = + pci_map_single(adapter->pdev, skb->data + mapped_len, + map_len, PCI_DMA_TODEVICE); + + if (dma_mapping_error(&adapter->pdev->dev, tx_buffer->dma)) { + /* We need to unwind the mappings we've done */ + ring_end = adapter->tx_ring.next_to_use; + adapter->tx_ring.next_to_use = ring_start; + while (adapter->tx_ring.next_to_use != ring_end) { + tpd = atl1e_get_tpd(adapter); + tx_buffer = atl1e_get_tx_buffer(adapter, tpd); + pci_unmap_single(adapter->pdev, tx_buffer->dma, + tx_buffer->length, PCI_DMA_TODEVICE); + } + /* Reset the tx rings next pointer */ + adapter->tx_ring.next_to_use = ring_start; + return -ENOSPC; + } + + ATL1E_SET_PCIMAP_TYPE(tx_buffer, ATL1E_TX_PCIMAP_SINGLE); + mapped_len += map_len; + use_tpd->buffer_addr = cpu_to_le64(tx_buffer->dma); + use_tpd->word2 = (use_tpd->word2 & (~TPD_BUFLEN_MASK)) | + ((cpu_to_le32(tx_buffer->length) & + TPD_BUFLEN_MASK) << TPD_BUFLEN_SHIFT); + } + + for (f = 0; f < nr_frags; f++) { + const struct skb_frag_struct *frag; + u16 i; + u16 seg_num; + + frag = &skb_shinfo(skb)->frags[f]; + buf_len = skb_frag_size(frag); + + seg_num = (buf_len + MAX_TX_BUF_LEN - 1) / MAX_TX_BUF_LEN; + for (i = 0; i < seg_num; i++) { + use_tpd = atl1e_get_tpd(adapter); + memcpy(use_tpd, tpd, sizeof(struct atl1e_tpd_desc)); + + tx_buffer = atl1e_get_tx_buffer(adapter, use_tpd); + BUG_ON(tx_buffer->skb); + + tx_buffer->skb = NULL; + tx_buffer->length = + (buf_len > MAX_TX_BUF_LEN) ? + MAX_TX_BUF_LEN : buf_len; + buf_len -= tx_buffer->length; + + tx_buffer->dma = skb_frag_dma_map(&adapter->pdev->dev, + frag, + (i * MAX_TX_BUF_LEN), + tx_buffer->length, + DMA_TO_DEVICE); + + if (dma_mapping_error(&adapter->pdev->dev, tx_buffer->dma)) { + /* We need to unwind the mappings we've done */ + ring_end = adapter->tx_ring.next_to_use; + adapter->tx_ring.next_to_use = ring_start; + while (adapter->tx_ring.next_to_use != ring_end) { + tpd = atl1e_get_tpd(adapter); + tx_buffer = atl1e_get_tx_buffer(adapter, tpd); + dma_unmap_page(&adapter->pdev->dev, tx_buffer->dma, + tx_buffer->length, DMA_TO_DEVICE); + } + + /* Reset the ring next to use pointer */ + adapter->tx_ring.next_to_use = ring_start; + return -ENOSPC; + } + + ATL1E_SET_PCIMAP_TYPE(tx_buffer, ATL1E_TX_PCIMAP_PAGE); + use_tpd->buffer_addr = cpu_to_le64(tx_buffer->dma); + use_tpd->word2 = (use_tpd->word2 & (~TPD_BUFLEN_MASK)) | + ((cpu_to_le32(tx_buffer->length) & + TPD_BUFLEN_MASK) << TPD_BUFLEN_SHIFT); + } + } + + if ((tpd->word3 >> TPD_SEGMENT_EN_SHIFT) & TPD_SEGMENT_EN_MASK) + /* note this one is a tcp header */ + tpd->word3 |= 1 << TPD_HDRFLAG_SHIFT; + /* The last tpd */ + + use_tpd->word3 |= 1 << TPD_EOP_SHIFT; + /* The last buffer info contain the skb address, + so it will be free after unmap */ + tx_buffer->skb = skb; + return 0; +} + +static void atl1e_tx_queue(struct atl1e_adapter *adapter, u16 count, + struct atl1e_tpd_desc *tpd) +{ + struct atl1e_tx_ring *tx_ring = &adapter->tx_ring; + /* Force memory writes to complete before letting h/w + * know there are new descriptors to fetch. (Only + * applicable for weak-ordered memory model archs, + * such as IA-64). */ + wmb(); + AT_WRITE_REG(&adapter->hw, REG_MB_TPD_PROD_IDX, tx_ring->next_to_use); +} + +static netdev_tx_t atl1e_xmit_frame(struct sk_buff *skb, + struct net_device *netdev) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + unsigned long flags; + u16 tpd_req = 1; + struct atl1e_tpd_desc *tpd; + + if (test_bit(__AT_DOWN, &adapter->flags)) { + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + + if (unlikely(skb->len <= 0)) { + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + tpd_req = atl1e_cal_tdp_req(skb); + if (!spin_trylock_irqsave(&adapter->tx_lock, flags)) + return NETDEV_TX_LOCKED; + + if (atl1e_tpd_avail(adapter) < tpd_req) { + /* no enough descriptor, just stop queue */ + netif_stop_queue(netdev); + spin_unlock_irqrestore(&adapter->tx_lock, flags); + return NETDEV_TX_BUSY; + } + + tpd = atl1e_get_tpd(adapter); + + if (vlan_tx_tag_present(skb)) { + u16 vlan_tag = vlan_tx_tag_get(skb); + u16 atl1e_vlan_tag; + + tpd->word3 |= 1 << TPD_INS_VL_TAG_SHIFT; + AT_VLAN_TAG_TO_TPD_TAG(vlan_tag, atl1e_vlan_tag); + tpd->word2 |= (atl1e_vlan_tag & TPD_VLANTAG_MASK) << + TPD_VLAN_SHIFT; + } + + if (skb->protocol == htons(ETH_P_8021Q)) + tpd->word3 |= 1 << TPD_VL_TAGGED_SHIFT; + + if (skb_network_offset(skb) != ETH_HLEN) + tpd->word3 |= 1 << TPD_ETHTYPE_SHIFT; /* 802.3 frame */ + + /* do TSO and check sum */ + if (atl1e_tso_csum(adapter, skb, tpd) != 0) { + spin_unlock_irqrestore(&adapter->tx_lock, flags); + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + + if (atl1e_tx_map(adapter, skb, tpd)) { + dev_kfree_skb_any(skb); + goto out; + } + + atl1e_tx_queue(adapter, tpd_req, tpd); + + netdev->trans_start = jiffies; /* NETIF_F_LLTX driver :( */ +out: + spin_unlock_irqrestore(&adapter->tx_lock, flags); + return NETDEV_TX_OK; +} + +static void atl1e_free_irq(struct atl1e_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + + free_irq(adapter->pdev->irq, netdev); +} + +static int atl1e_request_irq(struct atl1e_adapter *adapter) +{ + struct pci_dev *pdev = adapter->pdev; + struct net_device *netdev = adapter->netdev; + int err = 0; + + err = request_irq(pdev->irq, atl1e_intr, IRQF_SHARED, netdev->name, + netdev); + if (err) { + netdev_dbg(adapter->netdev, + "Unable to allocate interrupt Error: %d\n", err); + return err; + } + netdev_dbg(netdev, "atl1e_request_irq OK\n"); + return err; +} + +int atl1e_up(struct atl1e_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + int err = 0; + u32 val; + + /* hardware has been reset, we need to reload some things */ + err = atl1e_init_hw(&adapter->hw); + if (err) { + err = -EIO; + return err; + } + atl1e_init_ring_ptrs(adapter); + atl1e_set_multi(netdev); + atl1e_restore_vlan(adapter); + + if (atl1e_configure(adapter)) { + err = -EIO; + goto err_up; + } + + clear_bit(__AT_DOWN, &adapter->flags); + napi_enable(&adapter->napi); + atl1e_irq_enable(adapter); + val = AT_READ_REG(&adapter->hw, REG_MASTER_CTRL); + AT_WRITE_REG(&adapter->hw, REG_MASTER_CTRL, + val | MASTER_CTRL_MANUAL_INT); + +err_up: + return err; +} + +void atl1e_down(struct atl1e_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + + /* signal that we're down so the interrupt handler does not + * reschedule our watchdog timer */ + set_bit(__AT_DOWN, &adapter->flags); + +#if defined(NETIF_F_LLTX) || (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + netif_stop_queue(netdev); +#else + netif_tx_disable(netdev); +#endif + + /* reset MAC to disable all RX/TX */ + atl1e_reset_hw(&adapter->hw); + msleep(1); + + napi_disable(&adapter->napi); + atl1e_del_timer(adapter); + atl1e_irq_disable(adapter); + + netif_carrier_off(netdev); + adapter->link_speed = SPEED_0; + adapter->link_duplex = -1; + atl1e_clean_tx_ring(adapter); + atl1e_clean_rx_ring(adapter); +} + +/** + * atl1e_open - Called when a network interface is made active + * @netdev: network interface device structure + * + * Returns 0 on success, negative value on failure + * + * The open entry point is called when a network interface is made + * active by the system (IFF_UP). At this point all resources needed + * for transmit and receive operations are allocated, the interrupt + * handler is registered with the OS, the watchdog timer is started, + * and the stack is notified that the interface is ready. + */ +static int atl1e_open(struct net_device *netdev) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + int err; + + /* disallow open during test */ + if (test_bit(__AT_TESTING, &adapter->flags)) + return -EBUSY; + + /* allocate rx/tx dma buffer & descriptors */ + atl1e_init_ring_resources(adapter); + err = atl1e_setup_ring_resources(adapter); + if (unlikely(err)) + return err; + + err = atl1e_request_irq(adapter); + if (unlikely(err)) + goto err_req_irq; + + err = atl1e_up(adapter); + if (unlikely(err)) + goto err_up; + + return 0; + +err_up: + atl1e_free_irq(adapter); +err_req_irq: + atl1e_free_ring_resources(adapter); + atl1e_reset_hw(&adapter->hw); + + return err; +} + +/** + * atl1e_close - Disables a network interface + * @netdev: network interface device structure + * + * Returns 0, this is not allowed to fail + * + * The close entry point is called when an interface is de-activated + * by the OS. The hardware is still under the drivers control, but + * needs to be disabled. A global MAC reset is issued to stop the + * hardware, and all transmit and receive resources are freed. + */ +static int atl1e_close(struct net_device *netdev) +{ + struct atl1e_adapter *adapter = netdev_priv(netdev); + + WARN_ON(test_bit(__AT_RESETTING, &adapter->flags)); + atl1e_down(adapter); + atl1e_free_irq(adapter); + atl1e_free_ring_resources(adapter); + + return 0; +} + +static int atl1e_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1e_adapter *adapter = netdev_priv(netdev); + struct atl1e_hw *hw = &adapter->hw; + u32 ctrl = 0; + u32 mac_ctrl_data = 0; + u32 wol_ctrl_data = 0; + u16 mii_advertise_data = 0; + u16 mii_bmsr_data = 0; + u16 mii_intr_status_data = 0; + u32 wufc = adapter->wol; + u32 i; +#ifdef CONFIG_PM + int retval = 0; +#endif + + if (netif_running(netdev)) { + WARN_ON(test_bit(__AT_RESETTING, &adapter->flags)); + atl1e_down(adapter); + } + netif_device_detach(netdev); + +#ifdef CONFIG_PM + retval = pci_save_state(pdev); + if (retval) + return retval; +#endif + + if (wufc) { + /* get link status */ + atl1e_read_phy_reg(hw, MII_BMSR, &mii_bmsr_data); + atl1e_read_phy_reg(hw, MII_BMSR, &mii_bmsr_data); + + mii_advertise_data = ADVERTISE_10HALF; + + if ((atl1e_write_phy_reg(hw, MII_CTRL1000, 0) != 0) || + (atl1e_write_phy_reg(hw, + MII_ADVERTISE, mii_advertise_data) != 0) || + (atl1e_phy_commit(hw)) != 0) { + netdev_dbg(adapter->netdev, "set phy register failed\n"); + goto wol_dis; + } + + hw->phy_configured = false; /* re-init PHY when resume */ + + /* turn on magic packet wol */ + if (wufc & AT_WUFC_MAG) + wol_ctrl_data |= WOL_MAGIC_EN | WOL_MAGIC_PME_EN; + + if (wufc & AT_WUFC_LNKC) { + /* if orignal link status is link, just wait for retrive link */ + if (mii_bmsr_data & BMSR_LSTATUS) { + for (i = 0; i < AT_SUSPEND_LINK_TIMEOUT; i++) { + msleep(100); + atl1e_read_phy_reg(hw, MII_BMSR, + &mii_bmsr_data); + if (mii_bmsr_data & BMSR_LSTATUS) + break; + } + + if ((mii_bmsr_data & BMSR_LSTATUS) == 0) + netdev_dbg(adapter->netdev, + "Link may change when suspend\n"); + } + wol_ctrl_data |= WOL_LINK_CHG_EN | WOL_LINK_CHG_PME_EN; + /* only link up can wake up */ + if (atl1e_write_phy_reg(hw, MII_INT_CTRL, 0x400) != 0) { + netdev_dbg(adapter->netdev, + "read write phy register failed\n"); + goto wol_dis; + } + } + /* clear phy interrupt */ + atl1e_read_phy_reg(hw, MII_INT_STATUS, &mii_intr_status_data); + /* Config MAC Ctrl register */ + mac_ctrl_data = MAC_CTRL_RX_EN; + /* set to 10/100M halt duplex */ + mac_ctrl_data |= MAC_CTRL_SPEED_10_100 << MAC_CTRL_SPEED_SHIFT; + mac_ctrl_data |= (((u32)adapter->hw.preamble_len & + MAC_CTRL_PRMLEN_MASK) << + MAC_CTRL_PRMLEN_SHIFT); + + __atl1e_vlan_mode(netdev->features, &mac_ctrl_data); + + /* magic packet maybe Broadcast&multicast&Unicast frame */ + if (wufc & AT_WUFC_MAG) + mac_ctrl_data |= MAC_CTRL_BC_EN; + + netdev_dbg(adapter->netdev, "suspend MAC=0x%x\n", + mac_ctrl_data); + + AT_WRITE_REG(hw, REG_WOL_CTRL, wol_ctrl_data); + AT_WRITE_REG(hw, REG_MAC_CTRL, mac_ctrl_data); + /* pcie patch */ + ctrl = AT_READ_REG(hw, REG_PCIE_PHYMISC); + ctrl |= PCIE_PHYMISC_FORCE_RCV_DET; + AT_WRITE_REG(hw, REG_PCIE_PHYMISC, ctrl); + pci_enable_wake(pdev, pci_choose_state(pdev, state), 1); + goto suspend_exit; + } +wol_dis: + + /* WOL disabled */ + AT_WRITE_REG(hw, REG_WOL_CTRL, 0); + + /* pcie patch */ + ctrl = AT_READ_REG(hw, REG_PCIE_PHYMISC); + ctrl |= PCIE_PHYMISC_FORCE_RCV_DET; + AT_WRITE_REG(hw, REG_PCIE_PHYMISC, ctrl); + + atl1e_force_ps(hw); + hw->phy_configured = false; /* re-init PHY when resume */ + + pci_enable_wake(pdev, pci_choose_state(pdev, state), 0); + +suspend_exit: + + if (netif_running(netdev)) + atl1e_free_irq(adapter); + + pci_disable_device(pdev); + + pci_set_power_state(pdev, pci_choose_state(pdev, state)); + + return 0; +} + +#ifdef CONFIG_PM +static int atl1e_resume(struct pci_dev *pdev) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1e_adapter *adapter = netdev_priv(netdev); + u32 err; + + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + + err = pci_enable_device(pdev); + if (err) { + netdev_err(adapter->netdev, + "Cannot enable PCI device from suspend\n"); + return err; + } + + pci_set_master(pdev); + + AT_READ_REG(&adapter->hw, REG_WOL_CTRL); /* clear WOL status */ + + pci_enable_wake(pdev, PCI_D3hot, 0); + pci_enable_wake(pdev, PCI_D3cold, 0); + + AT_WRITE_REG(&adapter->hw, REG_WOL_CTRL, 0); + + if (netif_running(netdev)) { + err = atl1e_request_irq(adapter); + if (err) + return err; + } + + atl1e_reset_hw(&adapter->hw); + + if (netif_running(netdev)) + atl1e_up(adapter); + + netif_device_attach(netdev); + + return 0; +} +#endif + +static void atl1e_shutdown(struct pci_dev *pdev) +{ + atl1e_suspend(pdev, PMSG_SUSPEND); +} + +static const struct net_device_ops atl1e_netdev_ops = { + .ndo_open = atl1e_open, + .ndo_stop = atl1e_close, + .ndo_start_xmit = atl1e_xmit_frame, + .ndo_get_stats = atl1e_get_stats, + .ndo_set_rx_mode = atl1e_set_multi, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_mac_address = atl1e_set_mac_addr, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + .ndo_fix_features = atl1e_fix_features, + .ndo_set_features = atl1e_set_features, +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */ + .ndo_change_mtu = atl1e_change_mtu, + .ndo_do_ioctl = atl1e_ioctl, + .ndo_tx_timeout = atl1e_tx_timeout, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = atl1e_netpoll, +#endif + +}; + +static int atl1e_init_netdev(struct net_device *netdev, struct pci_dev *pdev) +{ + SET_NETDEV_DEV(netdev, &pdev->dev); + pci_set_drvdata(pdev, netdev); + + netdev_attach_ops(netdev, &atl1e_netdev_ops); + + netdev->watchdog_timeo = AT_TX_WATCHDOG; + atl1e_set_ethtool_ops(netdev); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + netdev->hw_features = NETIF_F_SG | NETIF_F_HW_CSUM | NETIF_F_TSO | + NETIF_F_HW_VLAN_CTAG_RX; + netdev->features = netdev->hw_features | NETIF_F_LLTX | + NETIF_F_HW_VLAN_CTAG_TX; +#else + netdev->features = NETIF_F_SG | NETIF_F_HW_CSUM | NETIF_F_TSO | + NETIF_F_HW_VLAN_RX | NETIF_F_LLTX | NETIF_F_HW_VLAN_TX; +#endif + + return 0; +} + +/** + * atl1e_probe - Device Initialization Routine + * @pdev: PCI device information struct + * @ent: entry in atl1e_pci_tbl + * + * Returns 0 on success, negative on failure + * + * atl1e_probe initializes an adapter identified by a pci_dev structure. + * The OS initialization, configuring of the adapter private structure, + * and a hardware reset occur. + */ +static int atl1e_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct net_device *netdev; + struct atl1e_adapter *adapter = NULL; + static int cards_found; + + int err = 0; + + err = pci_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "cannot enable PCI device\n"); + return err; + } + + /* + * The atl1e chip can DMA to 64-bit addresses, but it uses a single + * shared register for the high 32 bits, so only a single, aligned, + * 4 GB physical address range can be used at a time. + * + * Supporting 64-bit DMA on this hardware is more trouble than it's + * worth. It is far easier to limit to 32-bit DMA than update + * various kernel subsystems to support the mechanics required by a + * fixed-high-32-bit system. + */ + if ((pci_set_dma_mask(pdev, DMA_BIT_MASK(32)) != 0) || + (pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)) != 0)) { + dev_err(&pdev->dev, "No usable DMA configuration,aborting\n"); + goto err_dma; + } + + err = pci_request_regions(pdev, atl1e_driver_name); + if (err) { + dev_err(&pdev->dev, "cannot obtain PCI resources\n"); + goto err_pci_reg; + } + + pci_set_master(pdev); + + netdev = alloc_etherdev(sizeof(struct atl1e_adapter)); + if (netdev == NULL) { + err = -ENOMEM; + goto err_alloc_etherdev; + } + + err = atl1e_init_netdev(netdev, pdev); + if (err) { + netdev_err(netdev, "init netdevice failed\n"); + goto err_init_netdev; + } + adapter = netdev_priv(netdev); + adapter->bd_number = cards_found; + adapter->netdev = netdev; + adapter->pdev = pdev; + adapter->hw.adapter = adapter; + adapter->hw.hw_addr = pci_iomap(pdev, BAR_0, 0); + if (!adapter->hw.hw_addr) { + err = -EIO; + netdev_err(netdev, "cannot map device registers\n"); + goto err_ioremap; + } + + /* init mii data */ + adapter->mii.dev = netdev; + adapter->mii.mdio_read = atl1e_mdio_read; + adapter->mii.mdio_write = atl1e_mdio_write; + adapter->mii.phy_id_mask = 0x1f; + adapter->mii.reg_num_mask = MDIO_REG_ADDR_MASK; + + netif_napi_add(netdev, &adapter->napi, atl1e_clean, 64); + + init_timer(&adapter->phy_config_timer); + adapter->phy_config_timer.function = atl1e_phy_config; + adapter->phy_config_timer.data = (unsigned long) adapter; + + /* get user settings */ + atl1e_check_options(adapter); + /* + * Mark all PCI regions associated with PCI device + * pdev as being reserved by owner atl1e_driver_name + * Enables bus-mastering on the device and calls + * pcibios_set_master to do the needed arch specific settings + */ + atl1e_setup_pcicmd(pdev); + /* setup the private structure */ + err = atl1e_sw_init(adapter); + if (err) { + netdev_err(netdev, "net device private data init failed\n"); + goto err_sw_init; + } + + /* Init GPHY as early as possible due to power saving issue */ + atl1e_phy_init(&adapter->hw); + /* reset the controller to + * put the device in a known good starting state */ + err = atl1e_reset_hw(&adapter->hw); + if (err) { + err = -EIO; + goto err_reset; + } + + if (atl1e_read_mac_addr(&adapter->hw) != 0) { + err = -EIO; + netdev_err(netdev, "get mac address failed\n"); + goto err_eeprom; + } + + memcpy(netdev->dev_addr, adapter->hw.mac_addr, netdev->addr_len); + netdev_dbg(netdev, "mac address : %pM\n", adapter->hw.mac_addr); + + INIT_WORK(&adapter->reset_task, atl1e_reset_task); + INIT_WORK(&adapter->link_chg_task, atl1e_link_chg_task); + netif_set_gso_max_size(netdev, MAX_TSO_SEG_SIZE); + err = register_netdev(netdev); + if (err) { + netdev_err(netdev, "register netdevice failed\n"); + goto err_register; + } + + /* assume we have no link for now */ + netif_stop_queue(netdev); + netif_carrier_off(netdev); + + cards_found++; + + return 0; + +err_reset: +err_register: +err_sw_init: +err_eeprom: + iounmap(adapter->hw.hw_addr); +err_init_netdev: +err_ioremap: + free_netdev(netdev); +err_alloc_etherdev: + pci_release_regions(pdev); +err_pci_reg: +err_dma: + pci_disable_device(pdev); + return err; +} + +/** + * atl1e_remove - Device Removal Routine + * @pdev: PCI device information struct + * + * atl1e_remove is called by the PCI subsystem to alert the driver + * that it should release a PCI device. The could be caused by a + * Hot-Plug event, or because the driver is going to be removed from + * memory. + */ +static void atl1e_remove(struct pci_dev *pdev) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1e_adapter *adapter = netdev_priv(netdev); + + /* + * flush_scheduled work may reschedule our watchdog task, so + * explicitly disable watchdog tasks from being rescheduled + */ + set_bit(__AT_DOWN, &adapter->flags); + + atl1e_del_timer(adapter); + atl1e_cancel_work(adapter); + + unregister_netdev(netdev); + atl1e_free_ring_resources(adapter); + atl1e_force_ps(&adapter->hw); + iounmap(adapter->hw.hw_addr); + pci_release_regions(pdev); + free_netdev(netdev); + pci_disable_device(pdev); +} + +/** + * atl1e_io_error_detected - called when PCI error is detected + * @pdev: Pointer to PCI device + * @state: The current pci connection state + * + * This function is called after a PCI bus error affecting + * this device has been detected. + */ +static pci_ers_result_t +atl1e_io_error_detected(struct pci_dev *pdev, pci_channel_state_t state) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1e_adapter *adapter = netdev_priv(netdev); + + netif_device_detach(netdev); + + if (state == pci_channel_io_perm_failure) + return PCI_ERS_RESULT_DISCONNECT; + + if (netif_running(netdev)) + atl1e_down(adapter); + + pci_disable_device(pdev); + + /* Request a slot slot reset. */ + return PCI_ERS_RESULT_NEED_RESET; +} + +/** + * atl1e_io_slot_reset - called after the pci bus has been reset. + * @pdev: Pointer to PCI device + * + * Restart the card from scratch, as if from a cold-boot. Implementation + * resembles the first-half of the e1000_resume routine. + */ +static pci_ers_result_t atl1e_io_slot_reset(struct pci_dev *pdev) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1e_adapter *adapter = netdev_priv(netdev); + + if (pci_enable_device(pdev)) { + netdev_err(adapter->netdev, + "Cannot re-enable PCI device after reset\n"); + return PCI_ERS_RESULT_DISCONNECT; + } + pci_set_master(pdev); + + pci_enable_wake(pdev, PCI_D3hot, 0); + pci_enable_wake(pdev, PCI_D3cold, 0); + + atl1e_reset_hw(&adapter->hw); + + return PCI_ERS_RESULT_RECOVERED; +} + +/** + * atl1e_io_resume - called when traffic can start flowing again. + * @pdev: Pointer to PCI device + * + * This callback is called when the error recovery driver tells us that + * its OK to resume normal operation. Implementation resembles the + * second-half of the atl1e_resume routine. + */ +static void atl1e_io_resume(struct pci_dev *pdev) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1e_adapter *adapter = netdev_priv(netdev); + + if (netif_running(netdev)) { + if (atl1e_up(adapter)) { + netdev_err(adapter->netdev, + "can't bring device back up after reset\n"); + return; + } + } + + netif_device_attach(netdev); +} + +static const struct pci_error_handlers atl1e_err_handler = { + .error_detected = atl1e_io_error_detected, + .slot_reset = atl1e_io_slot_reset, + .resume = atl1e_io_resume, +}; + +static struct pci_driver atl1e_driver = { + .name = atl1e_driver_name, + .id_table = atl1e_pci_tbl, + .probe = atl1e_probe, + .remove = atl1e_remove, + /* Power Management Hooks */ +#ifdef CONFIG_PM + .suspend = atl1e_suspend, + .resume = atl1e_resume, +#endif + .shutdown = atl1e_shutdown, + .err_handler = &atl1e_err_handler +}; + +/** + * atl1e_init_module - Driver Registration Routine + * + * atl1e_init_module is the first routine called when the driver is + * loaded. All it does is register with the PCI subsystem. + */ +static int __init atl1e_init_module(void) +{ + return pci_register_driver(&atl1e_driver); +} + +/** + * atl1e_exit_module - Driver Exit Cleanup Routine + * + * atl1e_exit_module is called just before the driver is removed + * from memory. + */ +static void __exit atl1e_exit_module(void) +{ + pci_unregister_driver(&atl1e_driver); +} + +module_init(atl1e_init_module); +module_exit(atl1e_exit_module); diff --git a/drivers/net/ethernet/atheros/atl1e/atl1e_param.c b/drivers/net/ethernet/atheros/atl1e/atl1e_param.c new file mode 100644 index 0000000..fa31428 --- /dev/null +++ b/drivers/net/ethernet/atheros/atl1e/atl1e_param.c @@ -0,0 +1,269 @@ +/* + * Copyright(c) 2007 Atheros Corporation. All rights reserved. + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include + +#include "atl1e.h" + +/* This is the only thing that needs to be changed to adjust the + * maximum number of ports that the driver can manage. + */ + +#define ATL1E_MAX_NIC 32 + +#define OPTION_UNSET -1 +#define OPTION_DISABLED 0 +#define OPTION_ENABLED 1 + +/* All parameters are treated the same, as an integer array of values. + * This macro just reduces the need to repeat the same declaration code + * over and over (plus this helps to avoid typo bugs). + */ +#define ATL1E_PARAM_INIT { [0 ... ATL1E_MAX_NIC] = OPTION_UNSET } + +#define ATL1E_PARAM(x, desc) \ + static int x[ATL1E_MAX_NIC + 1] = ATL1E_PARAM_INIT; \ + static unsigned int num_##x; \ + module_param_array_named(x, x, int, &num_##x, 0); \ + MODULE_PARM_DESC(x, desc); + +/* Transmit Memory count + * + * Valid Range: 64-2048 + * + * Default Value: 128 + */ +#define ATL1E_MIN_TX_DESC_CNT 32 +#define ATL1E_MAX_TX_DESC_CNT 1020 +#define ATL1E_DEFAULT_TX_DESC_CNT 128 +ATL1E_PARAM(tx_desc_cnt, "Transmit description count"); + +/* Receive Memory Block Count + * + * Valid Range: 16-512 + * + * Default Value: 128 + */ +#define ATL1E_MIN_RX_MEM_SIZE 8 /* 8KB */ +#define ATL1E_MAX_RX_MEM_SIZE 1024 /* 1MB */ +#define ATL1E_DEFAULT_RX_MEM_SIZE 256 /* 128KB */ +ATL1E_PARAM(rx_mem_size, "memory size of rx buffer(KB)"); + +/* User Specified MediaType Override + * + * Valid Range: 0-5 + * - 0 - auto-negotiate at all supported speeds + * - 1 - only link at 100Mbps Full Duplex + * - 2 - only link at 100Mbps Half Duplex + * - 3 - only link at 10Mbps Full Duplex + * - 4 - only link at 10Mbps Half Duplex + * Default Value: 0 + */ + +ATL1E_PARAM(media_type, "MediaType Select"); + +/* Interrupt Moderate Timer in units of 2 us + * + * Valid Range: 10-65535 + * + * Default Value: 45000(90ms) + */ +#define INT_MOD_DEFAULT_CNT 100 /* 200us */ +#define INT_MOD_MAX_CNT 65000 +#define INT_MOD_MIN_CNT 50 +ATL1E_PARAM(int_mod_timer, "Interrupt Moderator Timer"); + +#define AUTONEG_ADV_DEFAULT 0x2F +#define AUTONEG_ADV_MASK 0x2F +#define FLOW_CONTROL_DEFAULT FLOW_CONTROL_FULL + +#define FLASH_VENDOR_DEFAULT 0 +#define FLASH_VENDOR_MIN 0 +#define FLASH_VENDOR_MAX 2 + +struct atl1e_option { + enum { enable_option, range_option, list_option } type; + char *name; + char *err; + int def; + union { + struct { /* range_option info */ + int min; + int max; + } r; + struct { /* list_option info */ + int nr; + struct atl1e_opt_list { int i; char *str; } *p; + } l; + } arg; +}; + +static int atl1e_validate_option(int *value, struct atl1e_option *opt, + struct atl1e_adapter *adapter) +{ + if (*value == OPTION_UNSET) { + *value = opt->def; + return 0; + } + + switch (opt->type) { + case enable_option: + switch (*value) { + case OPTION_ENABLED: + netdev_info(adapter->netdev, + "%s Enabled\n", opt->name); + return 0; + case OPTION_DISABLED: + netdev_info(adapter->netdev, + "%s Disabled\n", opt->name); + return 0; + } + break; + case range_option: + if (*value >= opt->arg.r.min && *value <= opt->arg.r.max) { + netdev_info(adapter->netdev, "%s set to %i\n", + opt->name, *value); + return 0; + } + break; + case list_option:{ + int i; + struct atl1e_opt_list *ent; + + for (i = 0; i < opt->arg.l.nr; i++) { + ent = &opt->arg.l.p[i]; + if (*value == ent->i) { + if (ent->str[0] != '\0') + netdev_info(adapter->netdev, + "%s\n", ent->str); + return 0; + } + } + break; + } + default: + BUG(); + } + + netdev_info(adapter->netdev, "Invalid %s specified (%i) %s\n", + opt->name, *value, opt->err); + *value = opt->def; + return -1; +} + +/** + * atl1e_check_options - Range Checking for Command Line Parameters + * @adapter: board private structure + * + * This routine checks all command line parameters for valid user + * input. If an invalid value is given, or if no user specified + * value exists, a default value is used. The final value is stored + * in a variable in the adapter structure. + */ +void atl1e_check_options(struct atl1e_adapter *adapter) +{ + int bd = adapter->bd_number; + + if (bd >= ATL1E_MAX_NIC) { + netdev_notice(adapter->netdev, + "no configuration for board #%i\n", bd); + netdev_notice(adapter->netdev, + "Using defaults for all values\n"); + } + + { /* Transmit Ring Size */ + struct atl1e_option opt = { + .type = range_option, + .name = "Transmit Ddescription Count", + .err = "using default of " + __MODULE_STRING(ATL1E_DEFAULT_TX_DESC_CNT), + .def = ATL1E_DEFAULT_TX_DESC_CNT, + .arg = { .r = { .min = ATL1E_MIN_TX_DESC_CNT, + .max = ATL1E_MAX_TX_DESC_CNT} } + }; + int val; + if (num_tx_desc_cnt > bd) { + val = tx_desc_cnt[bd]; + atl1e_validate_option(&val, &opt, adapter); + adapter->tx_ring.count = (u16) val & 0xFFFC; + } else + adapter->tx_ring.count = (u16)opt.def; + } + + { /* Receive Memory Block Count */ + struct atl1e_option opt = { + .type = range_option, + .name = "Memory size of rx buffer(KB)", + .err = "using default of " + __MODULE_STRING(ATL1E_DEFAULT_RX_MEM_SIZE), + .def = ATL1E_DEFAULT_RX_MEM_SIZE, + .arg = { .r = { .min = ATL1E_MIN_RX_MEM_SIZE, + .max = ATL1E_MAX_RX_MEM_SIZE} } + }; + int val; + if (num_rx_mem_size > bd) { + val = rx_mem_size[bd]; + atl1e_validate_option(&val, &opt, adapter); + adapter->rx_ring.page_size = (u32)val * 1024; + } else { + adapter->rx_ring.page_size = (u32)opt.def * 1024; + } + } + + { /* Interrupt Moderate Timer */ + struct atl1e_option opt = { + .type = range_option, + .name = "Interrupt Moderate Timer", + .err = "using default of " + __MODULE_STRING(INT_MOD_DEFAULT_CNT), + .def = INT_MOD_DEFAULT_CNT, + .arg = { .r = { .min = INT_MOD_MIN_CNT, + .max = INT_MOD_MAX_CNT} } + } ; + int val; + if (num_int_mod_timer > bd) { + val = int_mod_timer[bd]; + atl1e_validate_option(&val, &opt, adapter); + adapter->hw.imt = (u16) val; + } else + adapter->hw.imt = (u16)(opt.def); + } + + { /* MediaType */ + struct atl1e_option opt = { + .type = range_option, + .name = "Speed/Duplex Selection", + .err = "using default of " + __MODULE_STRING(MEDIA_TYPE_AUTO_SENSOR), + .def = MEDIA_TYPE_AUTO_SENSOR, + .arg = { .r = { .min = MEDIA_TYPE_AUTO_SENSOR, + .max = MEDIA_TYPE_10M_HALF} } + } ; + int val; + if (num_media_type > bd) { + val = media_type[bd]; + atl1e_validate_option(&val, &opt, adapter); + adapter->hw.media_type = (u16) val; + } else + adapter->hw.media_type = (u16)(opt.def); + + } +} diff --git a/drivers/net/ethernet/atheros/atlx/Makefile b/drivers/net/ethernet/atheros/atlx/Makefile new file mode 100644 index 0000000..75054ed --- /dev/null +++ b/drivers/net/ethernet/atheros/atlx/Makefile @@ -0,0 +1,3 @@ +obj-$(CPTCFG_ATL1) += atl1.o +obj-$(CPTCFG_ATL2) += atl2.o + diff --git a/drivers/net/ethernet/atheros/atlx/atl1.c b/drivers/net/ethernet/atheros/atlx/atl1.c new file mode 100644 index 0000000..2267c4a --- /dev/null +++ b/drivers/net/ethernet/atheros/atlx/atl1.c @@ -0,0 +1,3735 @@ +/* + * Copyright(c) 2005 - 2006 Attansic Corporation. All rights reserved. + * Copyright(c) 2006 - 2007 Chris Snook + * Copyright(c) 2006 - 2008 Jay Cliburn + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The full GNU General Public License is included in this distribution in the + * file called COPYING. + * + * Contact Information: + * Xiong Huang + * Jie Yang + * Chris Snook + * Jay Cliburn + * + * This version is adapted from the Attansic reference driver. + * + * TODO: + * Add more ethtool functions. + * Fix abstruse irq enable/disable condition described here: + * http://marc.theaimsgroup.com/?l=linux-netdev&m=116398508500553&w=2 + * + * NEEDS TESTING: + * VLAN + * multicast + * promiscuous mode + * interrupt coalescing + * SMP torture testing + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "atl1.h" + +#define ATLX_DRIVER_VERSION "2.1.3" +MODULE_AUTHOR("Xiong Huang , " + "Chris Snook , " + "Jay Cliburn "); +MODULE_LICENSE("GPL"); +MODULE_VERSION(ATLX_DRIVER_VERSION); + +/* Temporary hack for merging atl1 and atl2 */ +#include "atlx.c" + +static const struct ethtool_ops atl1_ethtool_ops; + +/* + * This is the only thing that needs to be changed to adjust the + * maximum number of ports that the driver can manage. + */ +#define ATL1_MAX_NIC 4 + +#define OPTION_UNSET -1 +#define OPTION_DISABLED 0 +#define OPTION_ENABLED 1 + +#define ATL1_PARAM_INIT { [0 ... ATL1_MAX_NIC] = OPTION_UNSET } + +/* + * Interrupt Moderate Timer in units of 2 us + * + * Valid Range: 10-65535 + * + * Default Value: 100 (200us) + */ +static int int_mod_timer[ATL1_MAX_NIC+1] = ATL1_PARAM_INIT; +static unsigned int num_int_mod_timer; +module_param_array_named(int_mod_timer, int_mod_timer, int, + &num_int_mod_timer, 0); +MODULE_PARM_DESC(int_mod_timer, "Interrupt moderator timer"); + +#define DEFAULT_INT_MOD_CNT 100 /* 200us */ +#define MAX_INT_MOD_CNT 65000 +#define MIN_INT_MOD_CNT 50 + +struct atl1_option { + enum { enable_option, range_option, list_option } type; + char *name; + char *err; + int def; + union { + struct { /* range_option info */ + int min; + int max; + } r; + struct { /* list_option info */ + int nr; + struct atl1_opt_list { + int i; + char *str; + } *p; + } l; + } arg; +}; + +static int atl1_validate_option(int *value, struct atl1_option *opt, + struct pci_dev *pdev) +{ + if (*value == OPTION_UNSET) { + *value = opt->def; + return 0; + } + + switch (opt->type) { + case enable_option: + switch (*value) { + case OPTION_ENABLED: + dev_info(&pdev->dev, "%s enabled\n", opt->name); + return 0; + case OPTION_DISABLED: + dev_info(&pdev->dev, "%s disabled\n", opt->name); + return 0; + } + break; + case range_option: + if (*value >= opt->arg.r.min && *value <= opt->arg.r.max) { + dev_info(&pdev->dev, "%s set to %i\n", opt->name, + *value); + return 0; + } + break; + case list_option:{ + int i; + struct atl1_opt_list *ent; + + for (i = 0; i < opt->arg.l.nr; i++) { + ent = &opt->arg.l.p[i]; + if (*value == ent->i) { + if (ent->str[0] != '\0') + dev_info(&pdev->dev, "%s\n", + ent->str); + return 0; + } + } + } + break; + + default: + break; + } + + dev_info(&pdev->dev, "invalid %s specified (%i) %s\n", + opt->name, *value, opt->err); + *value = opt->def; + return -1; +} + +/** + * atl1_check_options - Range Checking for Command Line Parameters + * @adapter: board private structure + * + * This routine checks all command line parameters for valid user + * input. If an invalid value is given, or if no user specified + * value exists, a default value is used. The final value is stored + * in a variable in the adapter structure. + */ +static void atl1_check_options(struct atl1_adapter *adapter) +{ + struct pci_dev *pdev = adapter->pdev; + int bd = adapter->bd_number; + if (bd >= ATL1_MAX_NIC) { + dev_notice(&pdev->dev, "no configuration for board#%i\n", bd); + dev_notice(&pdev->dev, "using defaults for all values\n"); + } + { /* Interrupt Moderate Timer */ + struct atl1_option opt = { + .type = range_option, + .name = "Interrupt Moderator Timer", + .err = "using default of " + __MODULE_STRING(DEFAULT_INT_MOD_CNT), + .def = DEFAULT_INT_MOD_CNT, + .arg = {.r = {.min = MIN_INT_MOD_CNT, + .max = MAX_INT_MOD_CNT} } + }; + int val; + if (num_int_mod_timer > bd) { + val = int_mod_timer[bd]; + atl1_validate_option(&val, &opt, pdev); + adapter->imt = (u16) val; + } else + adapter->imt = (u16) (opt.def); + } +} + +/* + * atl1_pci_tbl - PCI Device ID Table + */ +static DEFINE_PCI_DEVICE_TABLE(atl1_pci_tbl) = { + {PCI_DEVICE(PCI_VENDOR_ID_ATTANSIC, PCI_DEVICE_ID_ATTANSIC_L1)}, + /* required last entry */ + {0,} +}; +MODULE_DEVICE_TABLE(pci, atl1_pci_tbl); + +static const u32 atl1_default_msg = NETIF_MSG_DRV | NETIF_MSG_PROBE | + NETIF_MSG_LINK | NETIF_MSG_TIMER | NETIF_MSG_IFDOWN | NETIF_MSG_IFUP; + +static int debug = -1; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Message level (0=none,...,16=all)"); + +/* + * Reset the transmit and receive units; mask and clear all interrupts. + * hw - Struct containing variables accessed by shared code + * return : 0 or idle status (if error) + */ +static s32 atl1_reset_hw(struct atl1_hw *hw) +{ + struct pci_dev *pdev = hw->back->pdev; + struct atl1_adapter *adapter = hw->back; + u32 icr; + int i; + + /* + * Clear Interrupt mask to stop board from generating + * interrupts & Clear any pending interrupt events + */ + /* + * atlx_irq_disable(adapter); + * iowrite32(0xffffffff, hw->hw_addr + REG_ISR); + */ + + /* + * Issue Soft Reset to the MAC. This will reset the chip's + * transmit, receive, DMA. It will not effect + * the current PCI configuration. The global reset bit is self- + * clearing, and should clear within a microsecond. + */ + iowrite32(MASTER_CTRL_SOFT_RST, hw->hw_addr + REG_MASTER_CTRL); + ioread32(hw->hw_addr + REG_MASTER_CTRL); + + iowrite16(1, hw->hw_addr + REG_PHY_ENABLE); + ioread16(hw->hw_addr + REG_PHY_ENABLE); + + /* delay about 1ms */ + msleep(1); + + /* Wait at least 10ms for All module to be Idle */ + for (i = 0; i < 10; i++) { + icr = ioread32(hw->hw_addr + REG_IDLE_STATUS); + if (!icr) + break; + /* delay 1 ms */ + msleep(1); + /* FIXME: still the right way to do this? */ + cpu_relax(); + } + + if (icr) { + if (netif_msg_hw(adapter)) + dev_dbg(&pdev->dev, "ICR = 0x%x\n", icr); + return icr; + } + + return 0; +} + +/* function about EEPROM + * + * check_eeprom_exist + * return 0 if eeprom exist + */ +static int atl1_check_eeprom_exist(struct atl1_hw *hw) +{ + u32 value; + value = ioread32(hw->hw_addr + REG_SPI_FLASH_CTRL); + if (value & SPI_FLASH_CTRL_EN_VPD) { + value &= ~SPI_FLASH_CTRL_EN_VPD; + iowrite32(value, hw->hw_addr + REG_SPI_FLASH_CTRL); + } + + value = ioread16(hw->hw_addr + REG_PCIE_CAP_LIST); + return ((value & 0xFF00) == 0x6C00) ? 0 : 1; +} + +static bool atl1_read_eeprom(struct atl1_hw *hw, u32 offset, u32 *p_value) +{ + int i; + u32 control; + + if (offset & 3) + /* address do not align */ + return false; + + iowrite32(0, hw->hw_addr + REG_VPD_DATA); + control = (offset & VPD_CAP_VPD_ADDR_MASK) << VPD_CAP_VPD_ADDR_SHIFT; + iowrite32(control, hw->hw_addr + REG_VPD_CAP); + ioread32(hw->hw_addr + REG_VPD_CAP); + + for (i = 0; i < 10; i++) { + msleep(2); + control = ioread32(hw->hw_addr + REG_VPD_CAP); + if (control & VPD_CAP_VPD_FLAG) + break; + } + if (control & VPD_CAP_VPD_FLAG) { + *p_value = ioread32(hw->hw_addr + REG_VPD_DATA); + return true; + } + /* timeout */ + return false; +} + +/* + * Reads the value from a PHY register + * hw - Struct containing variables accessed by shared code + * reg_addr - address of the PHY register to read + */ +static s32 atl1_read_phy_reg(struct atl1_hw *hw, u16 reg_addr, u16 *phy_data) +{ + u32 val; + int i; + + val = ((u32) (reg_addr & MDIO_REG_ADDR_MASK)) << MDIO_REG_ADDR_SHIFT | + MDIO_START | MDIO_SUP_PREAMBLE | MDIO_RW | MDIO_CLK_25_4 << + MDIO_CLK_SEL_SHIFT; + iowrite32(val, hw->hw_addr + REG_MDIO_CTRL); + ioread32(hw->hw_addr + REG_MDIO_CTRL); + + for (i = 0; i < MDIO_WAIT_TIMES; i++) { + udelay(2); + val = ioread32(hw->hw_addr + REG_MDIO_CTRL); + if (!(val & (MDIO_START | MDIO_BUSY))) + break; + } + if (!(val & (MDIO_START | MDIO_BUSY))) { + *phy_data = (u16) val; + return 0; + } + return ATLX_ERR_PHY; +} + +#define CUSTOM_SPI_CS_SETUP 2 +#define CUSTOM_SPI_CLK_HI 2 +#define CUSTOM_SPI_CLK_LO 2 +#define CUSTOM_SPI_CS_HOLD 2 +#define CUSTOM_SPI_CS_HI 3 + +static bool atl1_spi_read(struct atl1_hw *hw, u32 addr, u32 *buf) +{ + int i; + u32 value; + + iowrite32(0, hw->hw_addr + REG_SPI_DATA); + iowrite32(addr, hw->hw_addr + REG_SPI_ADDR); + + value = SPI_FLASH_CTRL_WAIT_READY | + (CUSTOM_SPI_CS_SETUP & SPI_FLASH_CTRL_CS_SETUP_MASK) << + SPI_FLASH_CTRL_CS_SETUP_SHIFT | (CUSTOM_SPI_CLK_HI & + SPI_FLASH_CTRL_CLK_HI_MASK) << + SPI_FLASH_CTRL_CLK_HI_SHIFT | (CUSTOM_SPI_CLK_LO & + SPI_FLASH_CTRL_CLK_LO_MASK) << + SPI_FLASH_CTRL_CLK_LO_SHIFT | (CUSTOM_SPI_CS_HOLD & + SPI_FLASH_CTRL_CS_HOLD_MASK) << + SPI_FLASH_CTRL_CS_HOLD_SHIFT | (CUSTOM_SPI_CS_HI & + SPI_FLASH_CTRL_CS_HI_MASK) << + SPI_FLASH_CTRL_CS_HI_SHIFT | (1 & SPI_FLASH_CTRL_INS_MASK) << + SPI_FLASH_CTRL_INS_SHIFT; + + iowrite32(value, hw->hw_addr + REG_SPI_FLASH_CTRL); + + value |= SPI_FLASH_CTRL_START; + iowrite32(value, hw->hw_addr + REG_SPI_FLASH_CTRL); + ioread32(hw->hw_addr + REG_SPI_FLASH_CTRL); + + for (i = 0; i < 10; i++) { + msleep(1); + value = ioread32(hw->hw_addr + REG_SPI_FLASH_CTRL); + if (!(value & SPI_FLASH_CTRL_START)) + break; + } + + if (value & SPI_FLASH_CTRL_START) + return false; + + *buf = ioread32(hw->hw_addr + REG_SPI_DATA); + + return true; +} + +/* + * get_permanent_address + * return 0 if get valid mac address, + */ +static int atl1_get_permanent_address(struct atl1_hw *hw) +{ + u32 addr[2]; + u32 i, control; + u16 reg; + u8 eth_addr[ETH_ALEN]; + bool key_valid; + + if (is_valid_ether_addr(hw->perm_mac_addr)) + return 0; + + /* init */ + addr[0] = addr[1] = 0; + + if (!atl1_check_eeprom_exist(hw)) { + reg = 0; + key_valid = false; + /* Read out all EEPROM content */ + i = 0; + while (1) { + if (atl1_read_eeprom(hw, i + 0x100, &control)) { + if (key_valid) { + if (reg == REG_MAC_STA_ADDR) + addr[0] = control; + else if (reg == (REG_MAC_STA_ADDR + 4)) + addr[1] = control; + key_valid = false; + } else if ((control & 0xff) == 0x5A) { + key_valid = true; + reg = (u16) (control >> 16); + } else + break; + } else + /* read error */ + break; + i += 4; + } + + *(u32 *) ð_addr[2] = swab32(addr[0]); + *(u16 *) ð_addr[0] = swab16(*(u16 *) &addr[1]); + if (is_valid_ether_addr(eth_addr)) { + memcpy(hw->perm_mac_addr, eth_addr, ETH_ALEN); + return 0; + } + } + + /* see if SPI FLAGS exist ? */ + addr[0] = addr[1] = 0; + reg = 0; + key_valid = false; + i = 0; + while (1) { + if (atl1_spi_read(hw, i + 0x1f000, &control)) { + if (key_valid) { + if (reg == REG_MAC_STA_ADDR) + addr[0] = control; + else if (reg == (REG_MAC_STA_ADDR + 4)) + addr[1] = control; + key_valid = false; + } else if ((control & 0xff) == 0x5A) { + key_valid = true; + reg = (u16) (control >> 16); + } else + /* data end */ + break; + } else + /* read error */ + break; + i += 4; + } + + *(u32 *) ð_addr[2] = swab32(addr[0]); + *(u16 *) ð_addr[0] = swab16(*(u16 *) &addr[1]); + if (is_valid_ether_addr(eth_addr)) { + memcpy(hw->perm_mac_addr, eth_addr, ETH_ALEN); + return 0; + } + + /* + * On some motherboards, the MAC address is written by the + * BIOS directly to the MAC register during POST, and is + * not stored in eeprom. If all else thus far has failed + * to fetch the permanent MAC address, try reading it directly. + */ + addr[0] = ioread32(hw->hw_addr + REG_MAC_STA_ADDR); + addr[1] = ioread16(hw->hw_addr + (REG_MAC_STA_ADDR + 4)); + *(u32 *) ð_addr[2] = swab32(addr[0]); + *(u16 *) ð_addr[0] = swab16(*(u16 *) &addr[1]); + if (is_valid_ether_addr(eth_addr)) { + memcpy(hw->perm_mac_addr, eth_addr, ETH_ALEN); + return 0; + } + + return 1; +} + +/* + * Reads the adapter's MAC address from the EEPROM + * hw - Struct containing variables accessed by shared code + */ +static s32 atl1_read_mac_addr(struct atl1_hw *hw) +{ + s32 ret = 0; + u16 i; + + if (atl1_get_permanent_address(hw)) { + eth_random_addr(hw->perm_mac_addr); + ret = 1; + } + + for (i = 0; i < ETH_ALEN; i++) + hw->mac_addr[i] = hw->perm_mac_addr[i]; + return ret; +} + +/* + * Hashes an address to determine its location in the multicast table + * hw - Struct containing variables accessed by shared code + * mc_addr - the multicast address to hash + * + * atl1_hash_mc_addr + * purpose + * set hash value for a multicast address + * hash calcu processing : + * 1. calcu 32bit CRC for multicast address + * 2. reverse crc with MSB to LSB + */ +static u32 atl1_hash_mc_addr(struct atl1_hw *hw, u8 *mc_addr) +{ + u32 crc32, value = 0; + int i; + + crc32 = ether_crc_le(6, mc_addr); + for (i = 0; i < 32; i++) + value |= (((crc32 >> i) & 1) << (31 - i)); + + return value; +} + +/* + * Sets the bit in the multicast table corresponding to the hash value. + * hw - Struct containing variables accessed by shared code + * hash_value - Multicast address hash value + */ +static void atl1_hash_set(struct atl1_hw *hw, u32 hash_value) +{ + u32 hash_bit, hash_reg; + u32 mta; + + /* + * The HASH Table is a register array of 2 32-bit registers. + * It is treated like an array of 64 bits. We want to set + * bit BitArray[hash_value]. So we figure out what register + * the bit is in, read it, OR in the new bit, then write + * back the new value. The register is determined by the + * upper 7 bits of the hash value and the bit within that + * register are determined by the lower 5 bits of the value. + */ + hash_reg = (hash_value >> 31) & 0x1; + hash_bit = (hash_value >> 26) & 0x1F; + mta = ioread32((hw->hw_addr + REG_RX_HASH_TABLE) + (hash_reg << 2)); + mta |= (1 << hash_bit); + iowrite32(mta, (hw->hw_addr + REG_RX_HASH_TABLE) + (hash_reg << 2)); +} + +/* + * Writes a value to a PHY register + * hw - Struct containing variables accessed by shared code + * reg_addr - address of the PHY register to write + * data - data to write to the PHY + */ +static s32 atl1_write_phy_reg(struct atl1_hw *hw, u32 reg_addr, u16 phy_data) +{ + int i; + u32 val; + + val = ((u32) (phy_data & MDIO_DATA_MASK)) << MDIO_DATA_SHIFT | + (reg_addr & MDIO_REG_ADDR_MASK) << MDIO_REG_ADDR_SHIFT | + MDIO_SUP_PREAMBLE | + MDIO_START | MDIO_CLK_25_4 << MDIO_CLK_SEL_SHIFT; + iowrite32(val, hw->hw_addr + REG_MDIO_CTRL); + ioread32(hw->hw_addr + REG_MDIO_CTRL); + + for (i = 0; i < MDIO_WAIT_TIMES; i++) { + udelay(2); + val = ioread32(hw->hw_addr + REG_MDIO_CTRL); + if (!(val & (MDIO_START | MDIO_BUSY))) + break; + } + + if (!(val & (MDIO_START | MDIO_BUSY))) + return 0; + + return ATLX_ERR_PHY; +} + +/* + * Make L001's PHY out of Power Saving State (bug) + * hw - Struct containing variables accessed by shared code + * when power on, L001's PHY always on Power saving State + * (Gigabit Link forbidden) + */ +static s32 atl1_phy_leave_power_saving(struct atl1_hw *hw) +{ + s32 ret; + ret = atl1_write_phy_reg(hw, 29, 0x0029); + if (ret) + return ret; + return atl1_write_phy_reg(hw, 30, 0); +} + +/* + * Resets the PHY and make all config validate + * hw - Struct containing variables accessed by shared code + * + * Sets bit 15 and 12 of the MII Control regiser (for F001 bug) + */ +static s32 atl1_phy_reset(struct atl1_hw *hw) +{ + struct pci_dev *pdev = hw->back->pdev; + struct atl1_adapter *adapter = hw->back; + s32 ret_val; + u16 phy_data; + + if (hw->media_type == MEDIA_TYPE_AUTO_SENSOR || + hw->media_type == MEDIA_TYPE_1000M_FULL) + phy_data = MII_CR_RESET | MII_CR_AUTO_NEG_EN; + else { + switch (hw->media_type) { + case MEDIA_TYPE_100M_FULL: + phy_data = + MII_CR_FULL_DUPLEX | MII_CR_SPEED_100 | + MII_CR_RESET; + break; + case MEDIA_TYPE_100M_HALF: + phy_data = MII_CR_SPEED_100 | MII_CR_RESET; + break; + case MEDIA_TYPE_10M_FULL: + phy_data = + MII_CR_FULL_DUPLEX | MII_CR_SPEED_10 | MII_CR_RESET; + break; + default: + /* MEDIA_TYPE_10M_HALF: */ + phy_data = MII_CR_SPEED_10 | MII_CR_RESET; + break; + } + } + + ret_val = atl1_write_phy_reg(hw, MII_BMCR, phy_data); + if (ret_val) { + u32 val; + int i; + /* pcie serdes link may be down! */ + if (netif_msg_hw(adapter)) + dev_dbg(&pdev->dev, "pcie phy link down\n"); + + for (i = 0; i < 25; i++) { + msleep(1); + val = ioread32(hw->hw_addr + REG_MDIO_CTRL); + if (!(val & (MDIO_START | MDIO_BUSY))) + break; + } + + if ((val & (MDIO_START | MDIO_BUSY)) != 0) { + if (netif_msg_hw(adapter)) + dev_warn(&pdev->dev, + "pcie link down at least 25ms\n"); + return ret_val; + } + } + return 0; +} + +/* + * Configures PHY autoneg and flow control advertisement settings + * hw - Struct containing variables accessed by shared code + */ +static s32 atl1_phy_setup_autoneg_adv(struct atl1_hw *hw) +{ + s32 ret_val; + s16 mii_autoneg_adv_reg; + s16 mii_1000t_ctrl_reg; + + /* Read the MII Auto-Neg Advertisement Register (Address 4). */ + mii_autoneg_adv_reg = MII_AR_DEFAULT_CAP_MASK; + + /* Read the MII 1000Base-T Control Register (Address 9). */ + mii_1000t_ctrl_reg = MII_ATLX_CR_1000T_DEFAULT_CAP_MASK; + + /* + * First we clear all the 10/100 mb speed bits in the Auto-Neg + * Advertisement Register (Address 4) and the 1000 mb speed bits in + * the 1000Base-T Control Register (Address 9). + */ + mii_autoneg_adv_reg &= ~MII_AR_SPEED_MASK; + mii_1000t_ctrl_reg &= ~MII_ATLX_CR_1000T_SPEED_MASK; + + /* + * Need to parse media_type and set up + * the appropriate PHY registers. + */ + switch (hw->media_type) { + case MEDIA_TYPE_AUTO_SENSOR: + mii_autoneg_adv_reg |= (MII_AR_10T_HD_CAPS | + MII_AR_10T_FD_CAPS | + MII_AR_100TX_HD_CAPS | + MII_AR_100TX_FD_CAPS); + mii_1000t_ctrl_reg |= MII_ATLX_CR_1000T_FD_CAPS; + break; + + case MEDIA_TYPE_1000M_FULL: + mii_1000t_ctrl_reg |= MII_ATLX_CR_1000T_FD_CAPS; + break; + + case MEDIA_TYPE_100M_FULL: + mii_autoneg_adv_reg |= MII_AR_100TX_FD_CAPS; + break; + + case MEDIA_TYPE_100M_HALF: + mii_autoneg_adv_reg |= MII_AR_100TX_HD_CAPS; + break; + + case MEDIA_TYPE_10M_FULL: + mii_autoneg_adv_reg |= MII_AR_10T_FD_CAPS; + break; + + default: + mii_autoneg_adv_reg |= MII_AR_10T_HD_CAPS; + break; + } + + /* flow control fixed to enable all */ + mii_autoneg_adv_reg |= (MII_AR_ASM_DIR | MII_AR_PAUSE); + + hw->mii_autoneg_adv_reg = mii_autoneg_adv_reg; + hw->mii_1000t_ctrl_reg = mii_1000t_ctrl_reg; + + ret_val = atl1_write_phy_reg(hw, MII_ADVERTISE, mii_autoneg_adv_reg); + if (ret_val) + return ret_val; + + ret_val = atl1_write_phy_reg(hw, MII_ATLX_CR, mii_1000t_ctrl_reg); + if (ret_val) + return ret_val; + + return 0; +} + +/* + * Configures link settings. + * hw - Struct containing variables accessed by shared code + * Assumes the hardware has previously been reset and the + * transmitter and receiver are not enabled. + */ +static s32 atl1_setup_link(struct atl1_hw *hw) +{ + struct pci_dev *pdev = hw->back->pdev; + struct atl1_adapter *adapter = hw->back; + s32 ret_val; + + /* + * Options: + * PHY will advertise value(s) parsed from + * autoneg_advertised and fc + * no matter what autoneg is , We will not wait link result. + */ + ret_val = atl1_phy_setup_autoneg_adv(hw); + if (ret_val) { + if (netif_msg_link(adapter)) + dev_dbg(&pdev->dev, + "error setting up autonegotiation\n"); + return ret_val; + } + /* SW.Reset , En-Auto-Neg if needed */ + ret_val = atl1_phy_reset(hw); + if (ret_val) { + if (netif_msg_link(adapter)) + dev_dbg(&pdev->dev, "error resetting phy\n"); + return ret_val; + } + hw->phy_configured = true; + return ret_val; +} + +static void atl1_init_flash_opcode(struct atl1_hw *hw) +{ + if (hw->flash_vendor >= ARRAY_SIZE(flash_table)) + /* Atmel */ + hw->flash_vendor = 0; + + /* Init OP table */ + iowrite8(flash_table[hw->flash_vendor].cmd_program, + hw->hw_addr + REG_SPI_FLASH_OP_PROGRAM); + iowrite8(flash_table[hw->flash_vendor].cmd_sector_erase, + hw->hw_addr + REG_SPI_FLASH_OP_SC_ERASE); + iowrite8(flash_table[hw->flash_vendor].cmd_chip_erase, + hw->hw_addr + REG_SPI_FLASH_OP_CHIP_ERASE); + iowrite8(flash_table[hw->flash_vendor].cmd_rdid, + hw->hw_addr + REG_SPI_FLASH_OP_RDID); + iowrite8(flash_table[hw->flash_vendor].cmd_wren, + hw->hw_addr + REG_SPI_FLASH_OP_WREN); + iowrite8(flash_table[hw->flash_vendor].cmd_rdsr, + hw->hw_addr + REG_SPI_FLASH_OP_RDSR); + iowrite8(flash_table[hw->flash_vendor].cmd_wrsr, + hw->hw_addr + REG_SPI_FLASH_OP_WRSR); + iowrite8(flash_table[hw->flash_vendor].cmd_read, + hw->hw_addr + REG_SPI_FLASH_OP_READ); +} + +/* + * Performs basic configuration of the adapter. + * hw - Struct containing variables accessed by shared code + * Assumes that the controller has previously been reset and is in a + * post-reset uninitialized state. Initializes multicast table, + * and Calls routines to setup link + * Leaves the transmit and receive units disabled and uninitialized. + */ +static s32 atl1_init_hw(struct atl1_hw *hw) +{ + u32 ret_val = 0; + + /* Zero out the Multicast HASH table */ + iowrite32(0, hw->hw_addr + REG_RX_HASH_TABLE); + /* clear the old settings from the multicast hash table */ + iowrite32(0, (hw->hw_addr + REG_RX_HASH_TABLE) + (1 << 2)); + + atl1_init_flash_opcode(hw); + + if (!hw->phy_configured) { + /* enable GPHY LinkChange Interrupt */ + ret_val = atl1_write_phy_reg(hw, 18, 0xC00); + if (ret_val) + return ret_val; + /* make PHY out of power-saving state */ + ret_val = atl1_phy_leave_power_saving(hw); + if (ret_val) + return ret_val; + /* Call a subroutine to configure the link */ + ret_val = atl1_setup_link(hw); + } + return ret_val; +} + +/* + * Detects the current speed and duplex settings of the hardware. + * hw - Struct containing variables accessed by shared code + * speed - Speed of the connection + * duplex - Duplex setting of the connection + */ +static s32 atl1_get_speed_and_duplex(struct atl1_hw *hw, u16 *speed, u16 *duplex) +{ + struct pci_dev *pdev = hw->back->pdev; + struct atl1_adapter *adapter = hw->back; + s32 ret_val; + u16 phy_data; + + /* ; --- Read PHY Specific Status Register (17) */ + ret_val = atl1_read_phy_reg(hw, MII_ATLX_PSSR, &phy_data); + if (ret_val) + return ret_val; + + if (!(phy_data & MII_ATLX_PSSR_SPD_DPLX_RESOLVED)) + return ATLX_ERR_PHY_RES; + + switch (phy_data & MII_ATLX_PSSR_SPEED) { + case MII_ATLX_PSSR_1000MBS: + *speed = SPEED_1000; + break; + case MII_ATLX_PSSR_100MBS: + *speed = SPEED_100; + break; + case MII_ATLX_PSSR_10MBS: + *speed = SPEED_10; + break; + default: + if (netif_msg_hw(adapter)) + dev_dbg(&pdev->dev, "error getting speed\n"); + return ATLX_ERR_PHY_SPEED; + break; + } + if (phy_data & MII_ATLX_PSSR_DPLX) + *duplex = FULL_DUPLEX; + else + *duplex = HALF_DUPLEX; + + return 0; +} + +static void atl1_set_mac_addr(struct atl1_hw *hw) +{ + u32 value; + /* + * 00-0B-6A-F6-00-DC + * 0: 6AF600DC 1: 000B + * low dword + */ + value = (((u32) hw->mac_addr[2]) << 24) | + (((u32) hw->mac_addr[3]) << 16) | + (((u32) hw->mac_addr[4]) << 8) | (((u32) hw->mac_addr[5])); + iowrite32(value, hw->hw_addr + REG_MAC_STA_ADDR); + /* high dword */ + value = (((u32) hw->mac_addr[0]) << 8) | (((u32) hw->mac_addr[1])); + iowrite32(value, (hw->hw_addr + REG_MAC_STA_ADDR) + (1 << 2)); +} + +/** + * atl1_sw_init - Initialize general software structures (struct atl1_adapter) + * @adapter: board private structure to initialize + * + * atl1_sw_init initializes the Adapter private data structure. + * Fields are initialized based on PCI device information and + * OS network device settings (MTU size). + */ +static int atl1_sw_init(struct atl1_adapter *adapter) +{ + struct atl1_hw *hw = &adapter->hw; + struct net_device *netdev = adapter->netdev; + + hw->max_frame_size = netdev->mtu + ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN; + hw->min_frame_size = ETH_ZLEN + ETH_FCS_LEN; + + adapter->wol = 0; + device_set_wakeup_enable(&adapter->pdev->dev, false); + adapter->rx_buffer_len = (hw->max_frame_size + 7) & ~7; + adapter->ict = 50000; /* 100ms */ + adapter->link_speed = SPEED_0; /* hardware init */ + adapter->link_duplex = FULL_DUPLEX; + + hw->phy_configured = false; + hw->preamble_len = 7; + hw->ipgt = 0x60; + hw->min_ifg = 0x50; + hw->ipgr1 = 0x40; + hw->ipgr2 = 0x60; + hw->max_retry = 0xf; + hw->lcol = 0x37; + hw->jam_ipg = 7; + hw->rfd_burst = 8; + hw->rrd_burst = 8; + hw->rfd_fetch_gap = 1; + hw->rx_jumbo_th = adapter->rx_buffer_len / 8; + hw->rx_jumbo_lkah = 1; + hw->rrd_ret_timer = 16; + hw->tpd_burst = 4; + hw->tpd_fetch_th = 16; + hw->txf_burst = 0x100; + hw->tx_jumbo_task_th = (hw->max_frame_size + 7) >> 3; + hw->tpd_fetch_gap = 1; + hw->rcb_value = atl1_rcb_64; + hw->dma_ord = atl1_dma_ord_enh; + hw->dmar_block = atl1_dma_req_256; + hw->dmaw_block = atl1_dma_req_256; + hw->cmb_rrd = 4; + hw->cmb_tpd = 4; + hw->cmb_rx_timer = 1; /* about 2us */ + hw->cmb_tx_timer = 1; /* about 2us */ + hw->smb_timer = 100000; /* about 200ms */ + + spin_lock_init(&adapter->lock); + spin_lock_init(&adapter->mb_lock); + + return 0; +} + +static int mdio_read(struct net_device *netdev, int phy_id, int reg_num) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + u16 result; + + atl1_read_phy_reg(&adapter->hw, reg_num & 0x1f, &result); + + return result; +} + +static void mdio_write(struct net_device *netdev, int phy_id, int reg_num, + int val) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + + atl1_write_phy_reg(&adapter->hw, reg_num, val); +} + +static int atl1_mii_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + unsigned long flags; + int retval; + + if (!netif_running(netdev)) + return -EINVAL; + + spin_lock_irqsave(&adapter->lock, flags); + retval = generic_mii_ioctl(&adapter->mii, if_mii(ifr), cmd, NULL); + spin_unlock_irqrestore(&adapter->lock, flags); + + return retval; +} + +/** + * atl1_setup_mem_resources - allocate Tx / RX descriptor resources + * @adapter: board private structure + * + * Return 0 on success, negative on failure + */ +static s32 atl1_setup_ring_resources(struct atl1_adapter *adapter) +{ + struct atl1_tpd_ring *tpd_ring = &adapter->tpd_ring; + struct atl1_rfd_ring *rfd_ring = &adapter->rfd_ring; + struct atl1_rrd_ring *rrd_ring = &adapter->rrd_ring; + struct atl1_ring_header *ring_header = &adapter->ring_header; + struct pci_dev *pdev = adapter->pdev; + int size; + u8 offset = 0; + + size = sizeof(struct atl1_buffer) * (tpd_ring->count + rfd_ring->count); + tpd_ring->buffer_info = kzalloc(size, GFP_KERNEL); + if (unlikely(!tpd_ring->buffer_info)) { + if (netif_msg_drv(adapter)) + dev_err(&pdev->dev, "kzalloc failed , size = D%d\n", + size); + goto err_nomem; + } + rfd_ring->buffer_info = + (tpd_ring->buffer_info + tpd_ring->count); + + /* + * real ring DMA buffer + * each ring/block may need up to 8 bytes for alignment, hence the + * additional 40 bytes tacked onto the end. + */ + ring_header->size = size = + sizeof(struct tx_packet_desc) * tpd_ring->count + + sizeof(struct rx_free_desc) * rfd_ring->count + + sizeof(struct rx_return_desc) * rrd_ring->count + + sizeof(struct coals_msg_block) + + sizeof(struct stats_msg_block) + + 40; + + ring_header->desc = pci_alloc_consistent(pdev, ring_header->size, + &ring_header->dma); + if (unlikely(!ring_header->desc)) { + if (netif_msg_drv(adapter)) + dev_err(&pdev->dev, "pci_alloc_consistent failed\n"); + goto err_nomem; + } + + memset(ring_header->desc, 0, ring_header->size); + + /* init TPD ring */ + tpd_ring->dma = ring_header->dma; + offset = (tpd_ring->dma & 0x7) ? (8 - (ring_header->dma & 0x7)) : 0; + tpd_ring->dma += offset; + tpd_ring->desc = (u8 *) ring_header->desc + offset; + tpd_ring->size = sizeof(struct tx_packet_desc) * tpd_ring->count; + + /* init RFD ring */ + rfd_ring->dma = tpd_ring->dma + tpd_ring->size; + offset = (rfd_ring->dma & 0x7) ? (8 - (rfd_ring->dma & 0x7)) : 0; + rfd_ring->dma += offset; + rfd_ring->desc = (u8 *) tpd_ring->desc + (tpd_ring->size + offset); + rfd_ring->size = sizeof(struct rx_free_desc) * rfd_ring->count; + + + /* init RRD ring */ + rrd_ring->dma = rfd_ring->dma + rfd_ring->size; + offset = (rrd_ring->dma & 0x7) ? (8 - (rrd_ring->dma & 0x7)) : 0; + rrd_ring->dma += offset; + rrd_ring->desc = (u8 *) rfd_ring->desc + (rfd_ring->size + offset); + rrd_ring->size = sizeof(struct rx_return_desc) * rrd_ring->count; + + + /* init CMB */ + adapter->cmb.dma = rrd_ring->dma + rrd_ring->size; + offset = (adapter->cmb.dma & 0x7) ? (8 - (adapter->cmb.dma & 0x7)) : 0; + adapter->cmb.dma += offset; + adapter->cmb.cmb = (struct coals_msg_block *) + ((u8 *) rrd_ring->desc + (rrd_ring->size + offset)); + + /* init SMB */ + adapter->smb.dma = adapter->cmb.dma + sizeof(struct coals_msg_block); + offset = (adapter->smb.dma & 0x7) ? (8 - (adapter->smb.dma & 0x7)) : 0; + adapter->smb.dma += offset; + adapter->smb.smb = (struct stats_msg_block *) + ((u8 *) adapter->cmb.cmb + + (sizeof(struct coals_msg_block) + offset)); + + return 0; + +err_nomem: + kfree(tpd_ring->buffer_info); + return -ENOMEM; +} + +static void atl1_init_ring_ptrs(struct atl1_adapter *adapter) +{ + struct atl1_tpd_ring *tpd_ring = &adapter->tpd_ring; + struct atl1_rfd_ring *rfd_ring = &adapter->rfd_ring; + struct atl1_rrd_ring *rrd_ring = &adapter->rrd_ring; + + atomic_set(&tpd_ring->next_to_use, 0); + atomic_set(&tpd_ring->next_to_clean, 0); + + rfd_ring->next_to_clean = 0; + atomic_set(&rfd_ring->next_to_use, 0); + + rrd_ring->next_to_use = 0; + atomic_set(&rrd_ring->next_to_clean, 0); +} + +/** + * atl1_clean_rx_ring - Free RFD Buffers + * @adapter: board private structure + */ +static void atl1_clean_rx_ring(struct atl1_adapter *adapter) +{ + struct atl1_rfd_ring *rfd_ring = &adapter->rfd_ring; + struct atl1_rrd_ring *rrd_ring = &adapter->rrd_ring; + struct atl1_buffer *buffer_info; + struct pci_dev *pdev = adapter->pdev; + unsigned long size; + unsigned int i; + + /* Free all the Rx ring sk_buffs */ + for (i = 0; i < rfd_ring->count; i++) { + buffer_info = &rfd_ring->buffer_info[i]; + if (buffer_info->dma) { + pci_unmap_page(pdev, buffer_info->dma, + buffer_info->length, PCI_DMA_FROMDEVICE); + buffer_info->dma = 0; + } + if (buffer_info->skb) { + dev_kfree_skb(buffer_info->skb); + buffer_info->skb = NULL; + } + } + + size = sizeof(struct atl1_buffer) * rfd_ring->count; + memset(rfd_ring->buffer_info, 0, size); + + /* Zero out the descriptor ring */ + memset(rfd_ring->desc, 0, rfd_ring->size); + + rfd_ring->next_to_clean = 0; + atomic_set(&rfd_ring->next_to_use, 0); + + rrd_ring->next_to_use = 0; + atomic_set(&rrd_ring->next_to_clean, 0); +} + +/** + * atl1_clean_tx_ring - Free Tx Buffers + * @adapter: board private structure + */ +static void atl1_clean_tx_ring(struct atl1_adapter *adapter) +{ + struct atl1_tpd_ring *tpd_ring = &adapter->tpd_ring; + struct atl1_buffer *buffer_info; + struct pci_dev *pdev = adapter->pdev; + unsigned long size; + unsigned int i; + + /* Free all the Tx ring sk_buffs */ + for (i = 0; i < tpd_ring->count; i++) { + buffer_info = &tpd_ring->buffer_info[i]; + if (buffer_info->dma) { + pci_unmap_page(pdev, buffer_info->dma, + buffer_info->length, PCI_DMA_TODEVICE); + buffer_info->dma = 0; + } + } + + for (i = 0; i < tpd_ring->count; i++) { + buffer_info = &tpd_ring->buffer_info[i]; + if (buffer_info->skb) { + dev_kfree_skb_any(buffer_info->skb); + buffer_info->skb = NULL; + } + } + + size = sizeof(struct atl1_buffer) * tpd_ring->count; + memset(tpd_ring->buffer_info, 0, size); + + /* Zero out the descriptor ring */ + memset(tpd_ring->desc, 0, tpd_ring->size); + + atomic_set(&tpd_ring->next_to_use, 0); + atomic_set(&tpd_ring->next_to_clean, 0); +} + +/** + * atl1_free_ring_resources - Free Tx / RX descriptor Resources + * @adapter: board private structure + * + * Free all transmit software resources + */ +static void atl1_free_ring_resources(struct atl1_adapter *adapter) +{ + struct pci_dev *pdev = adapter->pdev; + struct atl1_tpd_ring *tpd_ring = &adapter->tpd_ring; + struct atl1_rfd_ring *rfd_ring = &adapter->rfd_ring; + struct atl1_rrd_ring *rrd_ring = &adapter->rrd_ring; + struct atl1_ring_header *ring_header = &adapter->ring_header; + + atl1_clean_tx_ring(adapter); + atl1_clean_rx_ring(adapter); + + kfree(tpd_ring->buffer_info); + pci_free_consistent(pdev, ring_header->size, ring_header->desc, + ring_header->dma); + + tpd_ring->buffer_info = NULL; + tpd_ring->desc = NULL; + tpd_ring->dma = 0; + + rfd_ring->buffer_info = NULL; + rfd_ring->desc = NULL; + rfd_ring->dma = 0; + + rrd_ring->desc = NULL; + rrd_ring->dma = 0; + + adapter->cmb.dma = 0; + adapter->cmb.cmb = NULL; + + adapter->smb.dma = 0; + adapter->smb.smb = NULL; +} + +static void atl1_setup_mac_ctrl(struct atl1_adapter *adapter) +{ + u32 value; + struct atl1_hw *hw = &adapter->hw; + struct net_device *netdev = adapter->netdev; + /* Config MAC CTRL Register */ + value = MAC_CTRL_TX_EN | MAC_CTRL_RX_EN; + /* duplex */ + if (FULL_DUPLEX == adapter->link_duplex) + value |= MAC_CTRL_DUPLX; + /* speed */ + value |= ((u32) ((SPEED_1000 == adapter->link_speed) ? + MAC_CTRL_SPEED_1000 : MAC_CTRL_SPEED_10_100) << + MAC_CTRL_SPEED_SHIFT); + /* flow control */ + value |= (MAC_CTRL_TX_FLOW | MAC_CTRL_RX_FLOW); + /* PAD & CRC */ + value |= (MAC_CTRL_ADD_CRC | MAC_CTRL_PAD); + /* preamble length */ + value |= (((u32) adapter->hw.preamble_len + & MAC_CTRL_PRMLEN_MASK) << MAC_CTRL_PRMLEN_SHIFT); + /* vlan */ + __atlx_vlan_mode(netdev->features, &value); + /* rx checksum + if (adapter->rx_csum) + value |= MAC_CTRL_RX_CHKSUM_EN; + */ + /* filter mode */ + value |= MAC_CTRL_BC_EN; + if (netdev->flags & IFF_PROMISC) + value |= MAC_CTRL_PROMIS_EN; + else if (netdev->flags & IFF_ALLMULTI) + value |= MAC_CTRL_MC_ALL_EN; + /* value |= MAC_CTRL_LOOPBACK; */ + iowrite32(value, hw->hw_addr + REG_MAC_CTRL); +} + +static u32 atl1_check_link(struct atl1_adapter *adapter) +{ + struct atl1_hw *hw = &adapter->hw; + struct net_device *netdev = adapter->netdev; + u32 ret_val; + u16 speed, duplex, phy_data; + int reconfig = 0; + + /* MII_BMSR must read twice */ + atl1_read_phy_reg(hw, MII_BMSR, &phy_data); + atl1_read_phy_reg(hw, MII_BMSR, &phy_data); + if (!(phy_data & BMSR_LSTATUS)) { + /* link down */ + if (netif_carrier_ok(netdev)) { + /* old link state: Up */ + if (netif_msg_link(adapter)) + dev_info(&adapter->pdev->dev, "link is down\n"); + adapter->link_speed = SPEED_0; + netif_carrier_off(netdev); + } + return 0; + } + + /* Link Up */ + ret_val = atl1_get_speed_and_duplex(hw, &speed, &duplex); + if (ret_val) + return ret_val; + + switch (hw->media_type) { + case MEDIA_TYPE_1000M_FULL: + if (speed != SPEED_1000 || duplex != FULL_DUPLEX) + reconfig = 1; + break; + case MEDIA_TYPE_100M_FULL: + if (speed != SPEED_100 || duplex != FULL_DUPLEX) + reconfig = 1; + break; + case MEDIA_TYPE_100M_HALF: + if (speed != SPEED_100 || duplex != HALF_DUPLEX) + reconfig = 1; + break; + case MEDIA_TYPE_10M_FULL: + if (speed != SPEED_10 || duplex != FULL_DUPLEX) + reconfig = 1; + break; + case MEDIA_TYPE_10M_HALF: + if (speed != SPEED_10 || duplex != HALF_DUPLEX) + reconfig = 1; + break; + } + + /* link result is our setting */ + if (!reconfig) { + if (adapter->link_speed != speed || + adapter->link_duplex != duplex) { + adapter->link_speed = speed; + adapter->link_duplex = duplex; + atl1_setup_mac_ctrl(adapter); + if (netif_msg_link(adapter)) + dev_info(&adapter->pdev->dev, + "%s link is up %d Mbps %s\n", + netdev->name, adapter->link_speed, + adapter->link_duplex == FULL_DUPLEX ? + "full duplex" : "half duplex"); + } + if (!netif_carrier_ok(netdev)) { + /* Link down -> Up */ + netif_carrier_on(netdev); + } + return 0; + } + + /* change original link status */ + if (netif_carrier_ok(netdev)) { + adapter->link_speed = SPEED_0; + netif_carrier_off(netdev); + netif_stop_queue(netdev); + } + + if (hw->media_type != MEDIA_TYPE_AUTO_SENSOR && + hw->media_type != MEDIA_TYPE_1000M_FULL) { + switch (hw->media_type) { + case MEDIA_TYPE_100M_FULL: + phy_data = MII_CR_FULL_DUPLEX | MII_CR_SPEED_100 | + MII_CR_RESET; + break; + case MEDIA_TYPE_100M_HALF: + phy_data = MII_CR_SPEED_100 | MII_CR_RESET; + break; + case MEDIA_TYPE_10M_FULL: + phy_data = + MII_CR_FULL_DUPLEX | MII_CR_SPEED_10 | MII_CR_RESET; + break; + default: + /* MEDIA_TYPE_10M_HALF: */ + phy_data = MII_CR_SPEED_10 | MII_CR_RESET; + break; + } + atl1_write_phy_reg(hw, MII_BMCR, phy_data); + return 0; + } + + /* auto-neg, insert timer to re-config phy */ + if (!adapter->phy_timer_pending) { + adapter->phy_timer_pending = true; + mod_timer(&adapter->phy_config_timer, + round_jiffies(jiffies + 3 * HZ)); + } + + return 0; +} + +static void set_flow_ctrl_old(struct atl1_adapter *adapter) +{ + u32 hi, lo, value; + + /* RFD Flow Control */ + value = adapter->rfd_ring.count; + hi = value / 16; + if (hi < 2) + hi = 2; + lo = value * 7 / 8; + + value = ((hi & RXQ_RXF_PAUSE_TH_HI_MASK) << RXQ_RXF_PAUSE_TH_HI_SHIFT) | + ((lo & RXQ_RXF_PAUSE_TH_LO_MASK) << RXQ_RXF_PAUSE_TH_LO_SHIFT); + iowrite32(value, adapter->hw.hw_addr + REG_RXQ_RXF_PAUSE_THRESH); + + /* RRD Flow Control */ + value = adapter->rrd_ring.count; + lo = value / 16; + hi = value * 7 / 8; + if (lo < 2) + lo = 2; + value = ((hi & RXQ_RRD_PAUSE_TH_HI_MASK) << RXQ_RRD_PAUSE_TH_HI_SHIFT) | + ((lo & RXQ_RRD_PAUSE_TH_LO_MASK) << RXQ_RRD_PAUSE_TH_LO_SHIFT); + iowrite32(value, adapter->hw.hw_addr + REG_RXQ_RRD_PAUSE_THRESH); +} + +static void set_flow_ctrl_new(struct atl1_hw *hw) +{ + u32 hi, lo, value; + + /* RXF Flow Control */ + value = ioread32(hw->hw_addr + REG_SRAM_RXF_LEN); + lo = value / 16; + if (lo < 192) + lo = 192; + hi = value * 7 / 8; + if (hi < lo) + hi = lo + 16; + value = ((hi & RXQ_RXF_PAUSE_TH_HI_MASK) << RXQ_RXF_PAUSE_TH_HI_SHIFT) | + ((lo & RXQ_RXF_PAUSE_TH_LO_MASK) << RXQ_RXF_PAUSE_TH_LO_SHIFT); + iowrite32(value, hw->hw_addr + REG_RXQ_RXF_PAUSE_THRESH); + + /* RRD Flow Control */ + value = ioread32(hw->hw_addr + REG_SRAM_RRD_LEN); + lo = value / 8; + hi = value * 7 / 8; + if (lo < 2) + lo = 2; + if (hi < lo) + hi = lo + 3; + value = ((hi & RXQ_RRD_PAUSE_TH_HI_MASK) << RXQ_RRD_PAUSE_TH_HI_SHIFT) | + ((lo & RXQ_RRD_PAUSE_TH_LO_MASK) << RXQ_RRD_PAUSE_TH_LO_SHIFT); + iowrite32(value, hw->hw_addr + REG_RXQ_RRD_PAUSE_THRESH); +} + +/** + * atl1_configure - Configure Transmit&Receive Unit after Reset + * @adapter: board private structure + * + * Configure the Tx /Rx unit of the MAC after a reset. + */ +static u32 atl1_configure(struct atl1_adapter *adapter) +{ + struct atl1_hw *hw = &adapter->hw; + u32 value; + + /* clear interrupt status */ + iowrite32(0xffffffff, adapter->hw.hw_addr + REG_ISR); + + /* set MAC Address */ + value = (((u32) hw->mac_addr[2]) << 24) | + (((u32) hw->mac_addr[3]) << 16) | + (((u32) hw->mac_addr[4]) << 8) | + (((u32) hw->mac_addr[5])); + iowrite32(value, hw->hw_addr + REG_MAC_STA_ADDR); + value = (((u32) hw->mac_addr[0]) << 8) | (((u32) hw->mac_addr[1])); + iowrite32(value, hw->hw_addr + (REG_MAC_STA_ADDR + 4)); + + /* tx / rx ring */ + + /* HI base address */ + iowrite32((u32) ((adapter->tpd_ring.dma & 0xffffffff00000000ULL) >> 32), + hw->hw_addr + REG_DESC_BASE_ADDR_HI); + /* LO base address */ + iowrite32((u32) (adapter->rfd_ring.dma & 0x00000000ffffffffULL), + hw->hw_addr + REG_DESC_RFD_ADDR_LO); + iowrite32((u32) (adapter->rrd_ring.dma & 0x00000000ffffffffULL), + hw->hw_addr + REG_DESC_RRD_ADDR_LO); + iowrite32((u32) (adapter->tpd_ring.dma & 0x00000000ffffffffULL), + hw->hw_addr + REG_DESC_TPD_ADDR_LO); + iowrite32((u32) (adapter->cmb.dma & 0x00000000ffffffffULL), + hw->hw_addr + REG_DESC_CMB_ADDR_LO); + iowrite32((u32) (adapter->smb.dma & 0x00000000ffffffffULL), + hw->hw_addr + REG_DESC_SMB_ADDR_LO); + + /* element count */ + value = adapter->rrd_ring.count; + value <<= 16; + value += adapter->rfd_ring.count; + iowrite32(value, hw->hw_addr + REG_DESC_RFD_RRD_RING_SIZE); + iowrite32(adapter->tpd_ring.count, hw->hw_addr + + REG_DESC_TPD_RING_SIZE); + + /* Load Ptr */ + iowrite32(1, hw->hw_addr + REG_LOAD_PTR); + + /* config Mailbox */ + value = ((atomic_read(&adapter->tpd_ring.next_to_use) + & MB_TPD_PROD_INDX_MASK) << MB_TPD_PROD_INDX_SHIFT) | + ((atomic_read(&adapter->rrd_ring.next_to_clean) + & MB_RRD_CONS_INDX_MASK) << MB_RRD_CONS_INDX_SHIFT) | + ((atomic_read(&adapter->rfd_ring.next_to_use) + & MB_RFD_PROD_INDX_MASK) << MB_RFD_PROD_INDX_SHIFT); + iowrite32(value, hw->hw_addr + REG_MAILBOX); + + /* config IPG/IFG */ + value = (((u32) hw->ipgt & MAC_IPG_IFG_IPGT_MASK) + << MAC_IPG_IFG_IPGT_SHIFT) | + (((u32) hw->min_ifg & MAC_IPG_IFG_MIFG_MASK) + << MAC_IPG_IFG_MIFG_SHIFT) | + (((u32) hw->ipgr1 & MAC_IPG_IFG_IPGR1_MASK) + << MAC_IPG_IFG_IPGR1_SHIFT) | + (((u32) hw->ipgr2 & MAC_IPG_IFG_IPGR2_MASK) + << MAC_IPG_IFG_IPGR2_SHIFT); + iowrite32(value, hw->hw_addr + REG_MAC_IPG_IFG); + + /* config Half-Duplex Control */ + value = ((u32) hw->lcol & MAC_HALF_DUPLX_CTRL_LCOL_MASK) | + (((u32) hw->max_retry & MAC_HALF_DUPLX_CTRL_RETRY_MASK) + << MAC_HALF_DUPLX_CTRL_RETRY_SHIFT) | + MAC_HALF_DUPLX_CTRL_EXC_DEF_EN | + (0xa << MAC_HALF_DUPLX_CTRL_ABEBT_SHIFT) | + (((u32) hw->jam_ipg & MAC_HALF_DUPLX_CTRL_JAMIPG_MASK) + << MAC_HALF_DUPLX_CTRL_JAMIPG_SHIFT); + iowrite32(value, hw->hw_addr + REG_MAC_HALF_DUPLX_CTRL); + + /* set Interrupt Moderator Timer */ + iowrite16(adapter->imt, hw->hw_addr + REG_IRQ_MODU_TIMER_INIT); + iowrite32(MASTER_CTRL_ITIMER_EN, hw->hw_addr + REG_MASTER_CTRL); + + /* set Interrupt Clear Timer */ + iowrite16(adapter->ict, hw->hw_addr + REG_CMBDISDMA_TIMER); + + /* set max frame size hw will accept */ + iowrite32(hw->max_frame_size, hw->hw_addr + REG_MTU); + + /* jumbo size & rrd retirement timer */ + value = (((u32) hw->rx_jumbo_th & RXQ_JMBOSZ_TH_MASK) + << RXQ_JMBOSZ_TH_SHIFT) | + (((u32) hw->rx_jumbo_lkah & RXQ_JMBO_LKAH_MASK) + << RXQ_JMBO_LKAH_SHIFT) | + (((u32) hw->rrd_ret_timer & RXQ_RRD_TIMER_MASK) + << RXQ_RRD_TIMER_SHIFT); + iowrite32(value, hw->hw_addr + REG_RXQ_JMBOSZ_RRDTIM); + + /* Flow Control */ + switch (hw->dev_rev) { + case 0x8001: + case 0x9001: + case 0x9002: + case 0x9003: + set_flow_ctrl_old(adapter); + break; + default: + set_flow_ctrl_new(hw); + break; + } + + /* config TXQ */ + value = (((u32) hw->tpd_burst & TXQ_CTRL_TPD_BURST_NUM_MASK) + << TXQ_CTRL_TPD_BURST_NUM_SHIFT) | + (((u32) hw->txf_burst & TXQ_CTRL_TXF_BURST_NUM_MASK) + << TXQ_CTRL_TXF_BURST_NUM_SHIFT) | + (((u32) hw->tpd_fetch_th & TXQ_CTRL_TPD_FETCH_TH_MASK) + << TXQ_CTRL_TPD_FETCH_TH_SHIFT) | TXQ_CTRL_ENH_MODE | + TXQ_CTRL_EN; + iowrite32(value, hw->hw_addr + REG_TXQ_CTRL); + + /* min tpd fetch gap & tx jumbo packet size threshold for taskoffload */ + value = (((u32) hw->tx_jumbo_task_th & TX_JUMBO_TASK_TH_MASK) + << TX_JUMBO_TASK_TH_SHIFT) | + (((u32) hw->tpd_fetch_gap & TX_TPD_MIN_IPG_MASK) + << TX_TPD_MIN_IPG_SHIFT); + iowrite32(value, hw->hw_addr + REG_TX_JUMBO_TASK_TH_TPD_IPG); + + /* config RXQ */ + value = (((u32) hw->rfd_burst & RXQ_CTRL_RFD_BURST_NUM_MASK) + << RXQ_CTRL_RFD_BURST_NUM_SHIFT) | + (((u32) hw->rrd_burst & RXQ_CTRL_RRD_BURST_THRESH_MASK) + << RXQ_CTRL_RRD_BURST_THRESH_SHIFT) | + (((u32) hw->rfd_fetch_gap & RXQ_CTRL_RFD_PREF_MIN_IPG_MASK) + << RXQ_CTRL_RFD_PREF_MIN_IPG_SHIFT) | RXQ_CTRL_CUT_THRU_EN | + RXQ_CTRL_EN; + iowrite32(value, hw->hw_addr + REG_RXQ_CTRL); + + /* config DMA Engine */ + value = ((((u32) hw->dmar_block) & DMA_CTRL_DMAR_BURST_LEN_MASK) + << DMA_CTRL_DMAR_BURST_LEN_SHIFT) | + ((((u32) hw->dmaw_block) & DMA_CTRL_DMAW_BURST_LEN_MASK) + << DMA_CTRL_DMAW_BURST_LEN_SHIFT) | DMA_CTRL_DMAR_EN | + DMA_CTRL_DMAW_EN; + value |= (u32) hw->dma_ord; + if (atl1_rcb_128 == hw->rcb_value) + value |= DMA_CTRL_RCB_VALUE; + iowrite32(value, hw->hw_addr + REG_DMA_CTRL); + + /* config CMB / SMB */ + value = (hw->cmb_tpd > adapter->tpd_ring.count) ? + hw->cmb_tpd : adapter->tpd_ring.count; + value <<= 16; + value |= hw->cmb_rrd; + iowrite32(value, hw->hw_addr + REG_CMB_WRITE_TH); + value = hw->cmb_rx_timer | ((u32) hw->cmb_tx_timer << 16); + iowrite32(value, hw->hw_addr + REG_CMB_WRITE_TIMER); + iowrite32(hw->smb_timer, hw->hw_addr + REG_SMB_TIMER); + + /* --- enable CMB / SMB */ + value = CSMB_CTRL_CMB_EN | CSMB_CTRL_SMB_EN; + iowrite32(value, hw->hw_addr + REG_CSMB_CTRL); + + value = ioread32(adapter->hw.hw_addr + REG_ISR); + if (unlikely((value & ISR_PHY_LINKDOWN) != 0)) + value = 1; /* config failed */ + else + value = 0; + + /* clear all interrupt status */ + iowrite32(0x3fffffff, adapter->hw.hw_addr + REG_ISR); + iowrite32(0, adapter->hw.hw_addr + REG_ISR); + return value; +} + +/* + * atl1_pcie_patch - Patch for PCIE module + */ +static void atl1_pcie_patch(struct atl1_adapter *adapter) +{ + u32 value; + + /* much vendor magic here */ + value = 0x6500; + iowrite32(value, adapter->hw.hw_addr + 0x12FC); + /* pcie flow control mode change */ + value = ioread32(adapter->hw.hw_addr + 0x1008); + value |= 0x8000; + iowrite32(value, adapter->hw.hw_addr + 0x1008); +} + +/* + * When ACPI resume on some VIA MotherBoard, the Interrupt Disable bit/0x400 + * on PCI Command register is disable. + * The function enable this bit. + * Brackett, 2006/03/15 + */ +static void atl1_via_workaround(struct atl1_adapter *adapter) +{ + unsigned long value; + + value = ioread16(adapter->hw.hw_addr + PCI_COMMAND); + if (value & PCI_COMMAND_INTX_DISABLE) + value &= ~PCI_COMMAND_INTX_DISABLE; + iowrite32(value, adapter->hw.hw_addr + PCI_COMMAND); +} + +static void atl1_inc_smb(struct atl1_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + struct stats_msg_block *smb = adapter->smb.smb; + + /* Fill out the OS statistics structure */ + adapter->soft_stats.rx_packets += smb->rx_ok; + adapter->soft_stats.tx_packets += smb->tx_ok; + adapter->soft_stats.rx_bytes += smb->rx_byte_cnt; + adapter->soft_stats.tx_bytes += smb->tx_byte_cnt; + adapter->soft_stats.multicast += smb->rx_mcast; + adapter->soft_stats.collisions += (smb->tx_1_col + smb->tx_2_col * 2 + + smb->tx_late_col + smb->tx_abort_col * adapter->hw.max_retry); + + /* Rx Errors */ + adapter->soft_stats.rx_errors += (smb->rx_frag + smb->rx_fcs_err + + smb->rx_len_err + smb->rx_sz_ov + smb->rx_rxf_ov + + smb->rx_rrd_ov + smb->rx_align_err); + adapter->soft_stats.rx_fifo_errors += smb->rx_rxf_ov; + adapter->soft_stats.rx_length_errors += smb->rx_len_err; + adapter->soft_stats.rx_crc_errors += smb->rx_fcs_err; + adapter->soft_stats.rx_frame_errors += smb->rx_align_err; + adapter->soft_stats.rx_missed_errors += (smb->rx_rrd_ov + + smb->rx_rxf_ov); + + adapter->soft_stats.rx_pause += smb->rx_pause; + adapter->soft_stats.rx_rrd_ov += smb->rx_rrd_ov; + adapter->soft_stats.rx_trunc += smb->rx_sz_ov; + + /* Tx Errors */ + adapter->soft_stats.tx_errors += (smb->tx_late_col + + smb->tx_abort_col + smb->tx_underrun + smb->tx_trunc); + adapter->soft_stats.tx_fifo_errors += smb->tx_underrun; + adapter->soft_stats.tx_aborted_errors += smb->tx_abort_col; + adapter->soft_stats.tx_window_errors += smb->tx_late_col; + + adapter->soft_stats.excecol += smb->tx_abort_col; + adapter->soft_stats.deffer += smb->tx_defer; + adapter->soft_stats.scc += smb->tx_1_col; + adapter->soft_stats.mcc += smb->tx_2_col; + adapter->soft_stats.latecol += smb->tx_late_col; + adapter->soft_stats.tx_underun += smb->tx_underrun; + adapter->soft_stats.tx_trunc += smb->tx_trunc; + adapter->soft_stats.tx_pause += smb->tx_pause; + + netdev->stats.rx_packets = adapter->soft_stats.rx_packets; + netdev->stats.tx_packets = adapter->soft_stats.tx_packets; + netdev->stats.rx_bytes = adapter->soft_stats.rx_bytes; + netdev->stats.tx_bytes = adapter->soft_stats.tx_bytes; + netdev->stats.multicast = adapter->soft_stats.multicast; + netdev->stats.collisions = adapter->soft_stats.collisions; + netdev->stats.rx_errors = adapter->soft_stats.rx_errors; + netdev->stats.rx_over_errors = + adapter->soft_stats.rx_missed_errors; + netdev->stats.rx_length_errors = + adapter->soft_stats.rx_length_errors; + netdev->stats.rx_crc_errors = adapter->soft_stats.rx_crc_errors; + netdev->stats.rx_frame_errors = + adapter->soft_stats.rx_frame_errors; + netdev->stats.rx_fifo_errors = adapter->soft_stats.rx_fifo_errors; + netdev->stats.rx_missed_errors = + adapter->soft_stats.rx_missed_errors; + netdev->stats.tx_errors = adapter->soft_stats.tx_errors; + netdev->stats.tx_fifo_errors = adapter->soft_stats.tx_fifo_errors; + netdev->stats.tx_aborted_errors = + adapter->soft_stats.tx_aborted_errors; + netdev->stats.tx_window_errors = + adapter->soft_stats.tx_window_errors; + netdev->stats.tx_carrier_errors = + adapter->soft_stats.tx_carrier_errors; +} + +static void atl1_update_mailbox(struct atl1_adapter *adapter) +{ + unsigned long flags; + u32 tpd_next_to_use; + u32 rfd_next_to_use; + u32 rrd_next_to_clean; + u32 value; + + spin_lock_irqsave(&adapter->mb_lock, flags); + + tpd_next_to_use = atomic_read(&adapter->tpd_ring.next_to_use); + rfd_next_to_use = atomic_read(&adapter->rfd_ring.next_to_use); + rrd_next_to_clean = atomic_read(&adapter->rrd_ring.next_to_clean); + + value = ((rfd_next_to_use & MB_RFD_PROD_INDX_MASK) << + MB_RFD_PROD_INDX_SHIFT) | + ((rrd_next_to_clean & MB_RRD_CONS_INDX_MASK) << + MB_RRD_CONS_INDX_SHIFT) | + ((tpd_next_to_use & MB_TPD_PROD_INDX_MASK) << + MB_TPD_PROD_INDX_SHIFT); + iowrite32(value, adapter->hw.hw_addr + REG_MAILBOX); + + spin_unlock_irqrestore(&adapter->mb_lock, flags); +} + +static void atl1_clean_alloc_flag(struct atl1_adapter *adapter, + struct rx_return_desc *rrd, u16 offset) +{ + struct atl1_rfd_ring *rfd_ring = &adapter->rfd_ring; + + while (rfd_ring->next_to_clean != (rrd->buf_indx + offset)) { + rfd_ring->buffer_info[rfd_ring->next_to_clean].alloced = 0; + if (++rfd_ring->next_to_clean == rfd_ring->count) { + rfd_ring->next_to_clean = 0; + } + } +} + +static void atl1_update_rfd_index(struct atl1_adapter *adapter, + struct rx_return_desc *rrd) +{ + u16 num_buf; + + num_buf = (rrd->xsz.xsum_sz.pkt_size + adapter->rx_buffer_len - 1) / + adapter->rx_buffer_len; + if (rrd->num_buf == num_buf) + /* clean alloc flag for bad rrd */ + atl1_clean_alloc_flag(adapter, rrd, num_buf); +} + +static void atl1_rx_checksum(struct atl1_adapter *adapter, + struct rx_return_desc *rrd, struct sk_buff *skb) +{ + struct pci_dev *pdev = adapter->pdev; + + /* + * The L1 hardware contains a bug that erroneously sets the + * PACKET_FLAG_ERR and ERR_FLAG_L4_CHKSUM bits whenever a + * fragmented IP packet is received, even though the packet + * is perfectly valid and its checksum is correct. There's + * no way to distinguish between one of these good packets + * and a packet that actually contains a TCP/UDP checksum + * error, so all we can do is allow it to be handed up to + * the higher layers and let it be sorted out there. + */ + + skb_checksum_none_assert(skb); + + if (unlikely(rrd->pkt_flg & PACKET_FLAG_ERR)) { + if (rrd->err_flg & (ERR_FLAG_CRC | ERR_FLAG_TRUNC | + ERR_FLAG_CODE | ERR_FLAG_OV)) { + adapter->hw_csum_err++; + if (netif_msg_rx_err(adapter)) + dev_printk(KERN_DEBUG, &pdev->dev, + "rx checksum error\n"); + return; + } + } + + /* not IPv4 */ + if (!(rrd->pkt_flg & PACKET_FLAG_IPV4)) + /* checksum is invalid, but it's not an IPv4 pkt, so ok */ + return; + + /* IPv4 packet */ + if (likely(!(rrd->err_flg & + (ERR_FLAG_IP_CHKSUM | ERR_FLAG_L4_CHKSUM)))) { + skb->ip_summed = CHECKSUM_UNNECESSARY; + adapter->hw_csum_good++; + return; + } +} + +/** + * atl1_alloc_rx_buffers - Replace used receive buffers + * @adapter: address of board private structure + */ +static u16 atl1_alloc_rx_buffers(struct atl1_adapter *adapter) +{ + struct atl1_rfd_ring *rfd_ring = &adapter->rfd_ring; + struct pci_dev *pdev = adapter->pdev; + struct page *page; + unsigned long offset; + struct atl1_buffer *buffer_info, *next_info; + struct sk_buff *skb; + u16 num_alloc = 0; + u16 rfd_next_to_use, next_next; + struct rx_free_desc *rfd_desc; + + next_next = rfd_next_to_use = atomic_read(&rfd_ring->next_to_use); + if (++next_next == rfd_ring->count) + next_next = 0; + buffer_info = &rfd_ring->buffer_info[rfd_next_to_use]; + next_info = &rfd_ring->buffer_info[next_next]; + + while (!buffer_info->alloced && !next_info->alloced) { + if (buffer_info->skb) { + buffer_info->alloced = 1; + goto next; + } + + rfd_desc = ATL1_RFD_DESC(rfd_ring, rfd_next_to_use); + + skb = netdev_alloc_skb_ip_align(adapter->netdev, + adapter->rx_buffer_len); + if (unlikely(!skb)) { + /* Better luck next round */ + adapter->netdev->stats.rx_dropped++; + break; + } + + buffer_info->alloced = 1; + buffer_info->skb = skb; + buffer_info->length = (u16) adapter->rx_buffer_len; + page = virt_to_page(skb->data); + offset = (unsigned long)skb->data & ~PAGE_MASK; + buffer_info->dma = pci_map_page(pdev, page, offset, + adapter->rx_buffer_len, + PCI_DMA_FROMDEVICE); + rfd_desc->buffer_addr = cpu_to_le64(buffer_info->dma); + rfd_desc->buf_len = cpu_to_le16(adapter->rx_buffer_len); + rfd_desc->coalese = 0; + +next: + rfd_next_to_use = next_next; + if (unlikely(++next_next == rfd_ring->count)) + next_next = 0; + + buffer_info = &rfd_ring->buffer_info[rfd_next_to_use]; + next_info = &rfd_ring->buffer_info[next_next]; + num_alloc++; + } + + if (num_alloc) { + /* + * Force memory writes to complete before letting h/w + * know there are new descriptors to fetch. (Only + * applicable for weak-ordered memory model archs, + * such as IA-64). + */ + wmb(); + atomic_set(&rfd_ring->next_to_use, (int)rfd_next_to_use); + } + return num_alloc; +} + +static int atl1_intr_rx(struct atl1_adapter *adapter, int budget) +{ + int i, count; + u16 length; + u16 rrd_next_to_clean; + u32 value; + struct atl1_rfd_ring *rfd_ring = &adapter->rfd_ring; + struct atl1_rrd_ring *rrd_ring = &adapter->rrd_ring; + struct atl1_buffer *buffer_info; + struct rx_return_desc *rrd; + struct sk_buff *skb; + + count = 0; + + rrd_next_to_clean = atomic_read(&rrd_ring->next_to_clean); + + while (count < budget) { + rrd = ATL1_RRD_DESC(rrd_ring, rrd_next_to_clean); + i = 1; + if (likely(rrd->xsz.valid)) { /* packet valid */ +chk_rrd: + /* check rrd status */ + if (likely(rrd->num_buf == 1)) + goto rrd_ok; + else if (netif_msg_rx_err(adapter)) { + dev_printk(KERN_DEBUG, &adapter->pdev->dev, + "unexpected RRD buffer count\n"); + dev_printk(KERN_DEBUG, &adapter->pdev->dev, + "rx_buf_len = %d\n", + adapter->rx_buffer_len); + dev_printk(KERN_DEBUG, &adapter->pdev->dev, + "RRD num_buf = %d\n", + rrd->num_buf); + dev_printk(KERN_DEBUG, &adapter->pdev->dev, + "RRD pkt_len = %d\n", + rrd->xsz.xsum_sz.pkt_size); + dev_printk(KERN_DEBUG, &adapter->pdev->dev, + "RRD pkt_flg = 0x%08X\n", + rrd->pkt_flg); + dev_printk(KERN_DEBUG, &adapter->pdev->dev, + "RRD err_flg = 0x%08X\n", + rrd->err_flg); + dev_printk(KERN_DEBUG, &adapter->pdev->dev, + "RRD vlan_tag = 0x%08X\n", + rrd->vlan_tag); + } + + /* rrd seems to be bad */ + if (unlikely(i-- > 0)) { + /* rrd may not be DMAed completely */ + udelay(1); + goto chk_rrd; + } + /* bad rrd */ + if (netif_msg_rx_err(adapter)) + dev_printk(KERN_DEBUG, &adapter->pdev->dev, + "bad RRD\n"); + /* see if update RFD index */ + if (rrd->num_buf > 1) + atl1_update_rfd_index(adapter, rrd); + + /* update rrd */ + rrd->xsz.valid = 0; + if (++rrd_next_to_clean == rrd_ring->count) + rrd_next_to_clean = 0; + count++; + continue; + } else { /* current rrd still not be updated */ + + break; + } +rrd_ok: + /* clean alloc flag for bad rrd */ + atl1_clean_alloc_flag(adapter, rrd, 0); + + buffer_info = &rfd_ring->buffer_info[rrd->buf_indx]; + if (++rfd_ring->next_to_clean == rfd_ring->count) + rfd_ring->next_to_clean = 0; + + /* update rrd next to clean */ + if (++rrd_next_to_clean == rrd_ring->count) + rrd_next_to_clean = 0; + count++; + + if (unlikely(rrd->pkt_flg & PACKET_FLAG_ERR)) { + if (!(rrd->err_flg & + (ERR_FLAG_IP_CHKSUM | ERR_FLAG_L4_CHKSUM + | ERR_FLAG_LEN))) { + /* packet error, don't need upstream */ + buffer_info->alloced = 0; + rrd->xsz.valid = 0; + continue; + } + } + + /* Good Receive */ + pci_unmap_page(adapter->pdev, buffer_info->dma, + buffer_info->length, PCI_DMA_FROMDEVICE); + buffer_info->dma = 0; + skb = buffer_info->skb; + length = le16_to_cpu(rrd->xsz.xsum_sz.pkt_size); + + skb_put(skb, length - ETH_FCS_LEN); + + /* Receive Checksum Offload */ + atl1_rx_checksum(adapter, rrd, skb); + skb->protocol = eth_type_trans(skb, adapter->netdev); + + if (rrd->pkt_flg & PACKET_FLAG_VLAN_INS) { + u16 vlan_tag = (rrd->vlan_tag >> 4) | + ((rrd->vlan_tag & 7) << 13) | + ((rrd->vlan_tag & 8) << 9); + + __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan_tag); + } + netif_receive_skb(skb); + + /* let protocol layer free skb */ + buffer_info->skb = NULL; + buffer_info->alloced = 0; + rrd->xsz.valid = 0; + } + + atomic_set(&rrd_ring->next_to_clean, rrd_next_to_clean); + + atl1_alloc_rx_buffers(adapter); + + /* update mailbox ? */ + if (count) { + u32 tpd_next_to_use; + u32 rfd_next_to_use; + + spin_lock(&adapter->mb_lock); + + tpd_next_to_use = atomic_read(&adapter->tpd_ring.next_to_use); + rfd_next_to_use = + atomic_read(&adapter->rfd_ring.next_to_use); + rrd_next_to_clean = + atomic_read(&adapter->rrd_ring.next_to_clean); + value = ((rfd_next_to_use & MB_RFD_PROD_INDX_MASK) << + MB_RFD_PROD_INDX_SHIFT) | + ((rrd_next_to_clean & MB_RRD_CONS_INDX_MASK) << + MB_RRD_CONS_INDX_SHIFT) | + ((tpd_next_to_use & MB_TPD_PROD_INDX_MASK) << + MB_TPD_PROD_INDX_SHIFT); + iowrite32(value, adapter->hw.hw_addr + REG_MAILBOX); + spin_unlock(&adapter->mb_lock); + } + + return count; +} + +static int atl1_intr_tx(struct atl1_adapter *adapter) +{ + struct atl1_tpd_ring *tpd_ring = &adapter->tpd_ring; + struct atl1_buffer *buffer_info; + u16 sw_tpd_next_to_clean; + u16 cmb_tpd_next_to_clean; + int count = 0; + + sw_tpd_next_to_clean = atomic_read(&tpd_ring->next_to_clean); + cmb_tpd_next_to_clean = le16_to_cpu(adapter->cmb.cmb->tpd_cons_idx); + + while (cmb_tpd_next_to_clean != sw_tpd_next_to_clean) { + buffer_info = &tpd_ring->buffer_info[sw_tpd_next_to_clean]; + if (buffer_info->dma) { + pci_unmap_page(adapter->pdev, buffer_info->dma, + buffer_info->length, PCI_DMA_TODEVICE); + buffer_info->dma = 0; + } + + if (buffer_info->skb) { + dev_kfree_skb_irq(buffer_info->skb); + buffer_info->skb = NULL; + } + + if (++sw_tpd_next_to_clean == tpd_ring->count) + sw_tpd_next_to_clean = 0; + + count++; + } + atomic_set(&tpd_ring->next_to_clean, sw_tpd_next_to_clean); + + if (netif_queue_stopped(adapter->netdev) && + netif_carrier_ok(adapter->netdev)) + netif_wake_queue(adapter->netdev); + + return count; +} + +static u16 atl1_tpd_avail(struct atl1_tpd_ring *tpd_ring) +{ + u16 next_to_clean = atomic_read(&tpd_ring->next_to_clean); + u16 next_to_use = atomic_read(&tpd_ring->next_to_use); + return (next_to_clean > next_to_use) ? + next_to_clean - next_to_use - 1 : + tpd_ring->count + next_to_clean - next_to_use - 1; +} + +static int atl1_tso(struct atl1_adapter *adapter, struct sk_buff *skb, + struct tx_packet_desc *ptpd) +{ + u8 hdr_len, ip_off; + u32 real_len; + int err; + + if (skb_shinfo(skb)->gso_size) { + if (skb_header_cloned(skb)) { + err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC); + if (unlikely(err)) + return -1; + } + + if (skb->protocol == htons(ETH_P_IP)) { + struct iphdr *iph = ip_hdr(skb); + + real_len = (((unsigned char *)iph - skb->data) + + ntohs(iph->tot_len)); + if (real_len < skb->len) + pskb_trim(skb, real_len); + hdr_len = (skb_transport_offset(skb) + tcp_hdrlen(skb)); + if (skb->len == hdr_len) { + iph->check = 0; + tcp_hdr(skb)->check = + ~csum_tcpudp_magic(iph->saddr, + iph->daddr, tcp_hdrlen(skb), + IPPROTO_TCP, 0); + ptpd->word3 |= (iph->ihl & TPD_IPHL_MASK) << + TPD_IPHL_SHIFT; + ptpd->word3 |= ((tcp_hdrlen(skb) >> 2) & + TPD_TCPHDRLEN_MASK) << + TPD_TCPHDRLEN_SHIFT; + ptpd->word3 |= 1 << TPD_IP_CSUM_SHIFT; + ptpd->word3 |= 1 << TPD_TCP_CSUM_SHIFT; + return 1; + } + + iph->check = 0; + tcp_hdr(skb)->check = ~csum_tcpudp_magic(iph->saddr, + iph->daddr, 0, IPPROTO_TCP, 0); + ip_off = (unsigned char *)iph - + (unsigned char *) skb_network_header(skb); + if (ip_off == 8) /* 802.3-SNAP frame */ + ptpd->word3 |= 1 << TPD_ETHTYPE_SHIFT; + else if (ip_off != 0) + return -2; + + ptpd->word3 |= (iph->ihl & TPD_IPHL_MASK) << + TPD_IPHL_SHIFT; + ptpd->word3 |= ((tcp_hdrlen(skb) >> 2) & + TPD_TCPHDRLEN_MASK) << TPD_TCPHDRLEN_SHIFT; + ptpd->word3 |= (skb_shinfo(skb)->gso_size & + TPD_MSS_MASK) << TPD_MSS_SHIFT; + ptpd->word3 |= 1 << TPD_SEGMENT_EN_SHIFT; + return 3; + } + } + return false; +} + +static int atl1_tx_csum(struct atl1_adapter *adapter, struct sk_buff *skb, + struct tx_packet_desc *ptpd) +{ + u8 css, cso; + + if (likely(skb->ip_summed == CHECKSUM_PARTIAL)) { + css = skb_checksum_start_offset(skb); + cso = css + (u8) skb->csum_offset; + if (unlikely(css & 0x1)) { + /* L1 hardware requires an even number here */ + if (netif_msg_tx_err(adapter)) + dev_printk(KERN_DEBUG, &adapter->pdev->dev, + "payload offset not an even number\n"); + return -1; + } + ptpd->word3 |= (css & TPD_PLOADOFFSET_MASK) << + TPD_PLOADOFFSET_SHIFT; + ptpd->word3 |= (cso & TPD_CCSUMOFFSET_MASK) << + TPD_CCSUMOFFSET_SHIFT; + ptpd->word3 |= 1 << TPD_CUST_CSUM_EN_SHIFT; + return true; + } + return 0; +} + +static void atl1_tx_map(struct atl1_adapter *adapter, struct sk_buff *skb, + struct tx_packet_desc *ptpd) +{ + struct atl1_tpd_ring *tpd_ring = &adapter->tpd_ring; + struct atl1_buffer *buffer_info; + u16 buf_len = skb->len; + struct page *page; + unsigned long offset; + unsigned int nr_frags; + unsigned int f; + int retval; + u16 next_to_use; + u16 data_len; + u8 hdr_len; + + buf_len -= skb->data_len; + nr_frags = skb_shinfo(skb)->nr_frags; + next_to_use = atomic_read(&tpd_ring->next_to_use); + buffer_info = &tpd_ring->buffer_info[next_to_use]; + BUG_ON(buffer_info->skb); + /* put skb in last TPD */ + buffer_info->skb = NULL; + + retval = (ptpd->word3 >> TPD_SEGMENT_EN_SHIFT) & TPD_SEGMENT_EN_MASK; + if (retval) { + /* TSO */ + hdr_len = skb_transport_offset(skb) + tcp_hdrlen(skb); + buffer_info->length = hdr_len; + page = virt_to_page(skb->data); + offset = (unsigned long)skb->data & ~PAGE_MASK; + buffer_info->dma = pci_map_page(adapter->pdev, page, + offset, hdr_len, + PCI_DMA_TODEVICE); + + if (++next_to_use == tpd_ring->count) + next_to_use = 0; + + if (buf_len > hdr_len) { + int i, nseg; + + data_len = buf_len - hdr_len; + nseg = (data_len + ATL1_MAX_TX_BUF_LEN - 1) / + ATL1_MAX_TX_BUF_LEN; + for (i = 0; i < nseg; i++) { + buffer_info = + &tpd_ring->buffer_info[next_to_use]; + buffer_info->skb = NULL; + buffer_info->length = + (ATL1_MAX_TX_BUF_LEN >= + data_len) ? ATL1_MAX_TX_BUF_LEN : data_len; + data_len -= buffer_info->length; + page = virt_to_page(skb->data + + (hdr_len + i * ATL1_MAX_TX_BUF_LEN)); + offset = (unsigned long)(skb->data + + (hdr_len + i * ATL1_MAX_TX_BUF_LEN)) & + ~PAGE_MASK; + buffer_info->dma = pci_map_page(adapter->pdev, + page, offset, buffer_info->length, + PCI_DMA_TODEVICE); + if (++next_to_use == tpd_ring->count) + next_to_use = 0; + } + } + } else { + /* not TSO */ + buffer_info->length = buf_len; + page = virt_to_page(skb->data); + offset = (unsigned long)skb->data & ~PAGE_MASK; + buffer_info->dma = pci_map_page(adapter->pdev, page, + offset, buf_len, PCI_DMA_TODEVICE); + if (++next_to_use == tpd_ring->count) + next_to_use = 0; + } + + for (f = 0; f < nr_frags; f++) { + const struct skb_frag_struct *frag; + u16 i, nseg; + + frag = &skb_shinfo(skb)->frags[f]; + buf_len = skb_frag_size(frag); + + nseg = (buf_len + ATL1_MAX_TX_BUF_LEN - 1) / + ATL1_MAX_TX_BUF_LEN; + for (i = 0; i < nseg; i++) { + buffer_info = &tpd_ring->buffer_info[next_to_use]; + BUG_ON(buffer_info->skb); + + buffer_info->skb = NULL; + buffer_info->length = (buf_len > ATL1_MAX_TX_BUF_LEN) ? + ATL1_MAX_TX_BUF_LEN : buf_len; + buf_len -= buffer_info->length; + buffer_info->dma = skb_frag_dma_map(&adapter->pdev->dev, + frag, i * ATL1_MAX_TX_BUF_LEN, + buffer_info->length, DMA_TO_DEVICE); + + if (++next_to_use == tpd_ring->count) + next_to_use = 0; + } + } + + /* last tpd's buffer-info */ + buffer_info->skb = skb; +} + +static void atl1_tx_queue(struct atl1_adapter *adapter, u16 count, + struct tx_packet_desc *ptpd) +{ + struct atl1_tpd_ring *tpd_ring = &adapter->tpd_ring; + struct atl1_buffer *buffer_info; + struct tx_packet_desc *tpd; + u16 j; + u32 val; + u16 next_to_use = (u16) atomic_read(&tpd_ring->next_to_use); + + for (j = 0; j < count; j++) { + buffer_info = &tpd_ring->buffer_info[next_to_use]; + tpd = ATL1_TPD_DESC(&adapter->tpd_ring, next_to_use); + if (tpd != ptpd) + memcpy(tpd, ptpd, sizeof(struct tx_packet_desc)); + tpd->buffer_addr = cpu_to_le64(buffer_info->dma); + tpd->word2 &= ~(TPD_BUFLEN_MASK << TPD_BUFLEN_SHIFT); + tpd->word2 |= (cpu_to_le16(buffer_info->length) & + TPD_BUFLEN_MASK) << TPD_BUFLEN_SHIFT; + + /* + * if this is the first packet in a TSO chain, set + * TPD_HDRFLAG, otherwise, clear it. + */ + val = (tpd->word3 >> TPD_SEGMENT_EN_SHIFT) & + TPD_SEGMENT_EN_MASK; + if (val) { + if (!j) + tpd->word3 |= 1 << TPD_HDRFLAG_SHIFT; + else + tpd->word3 &= ~(1 << TPD_HDRFLAG_SHIFT); + } + + if (j == (count - 1)) + tpd->word3 |= 1 << TPD_EOP_SHIFT; + + if (++next_to_use == tpd_ring->count) + next_to_use = 0; + } + /* + * Force memory writes to complete before letting h/w + * know there are new descriptors to fetch. (Only + * applicable for weak-ordered memory model archs, + * such as IA-64). + */ + wmb(); + + atomic_set(&tpd_ring->next_to_use, next_to_use); +} + +static netdev_tx_t atl1_xmit_frame(struct sk_buff *skb, + struct net_device *netdev) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + struct atl1_tpd_ring *tpd_ring = &adapter->tpd_ring; + int len; + int tso; + int count = 1; + int ret_val; + struct tx_packet_desc *ptpd; + u16 vlan_tag; + unsigned int nr_frags = 0; + unsigned int mss = 0; + unsigned int f; + unsigned int proto_hdr_len; + + len = skb_headlen(skb); + + if (unlikely(skb->len <= 0)) { + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + + nr_frags = skb_shinfo(skb)->nr_frags; + for (f = 0; f < nr_frags; f++) { + unsigned int f_size = skb_frag_size(&skb_shinfo(skb)->frags[f]); + count += (f_size + ATL1_MAX_TX_BUF_LEN - 1) / + ATL1_MAX_TX_BUF_LEN; + } + + mss = skb_shinfo(skb)->gso_size; + if (mss) { + if (skb->protocol == htons(ETH_P_IP)) { + proto_hdr_len = (skb_transport_offset(skb) + + tcp_hdrlen(skb)); + if (unlikely(proto_hdr_len > len)) { + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + /* need additional TPD ? */ + if (proto_hdr_len != len) + count += (len - proto_hdr_len + + ATL1_MAX_TX_BUF_LEN - 1) / + ATL1_MAX_TX_BUF_LEN; + } + } + + if (atl1_tpd_avail(&adapter->tpd_ring) < count) { + /* not enough descriptors */ + netif_stop_queue(netdev); + if (netif_msg_tx_queued(adapter)) + dev_printk(KERN_DEBUG, &adapter->pdev->dev, + "tx busy\n"); + return NETDEV_TX_BUSY; + } + + ptpd = ATL1_TPD_DESC(tpd_ring, + (u16) atomic_read(&tpd_ring->next_to_use)); + memset(ptpd, 0, sizeof(struct tx_packet_desc)); + + if (vlan_tx_tag_present(skb)) { + vlan_tag = vlan_tx_tag_get(skb); + vlan_tag = (vlan_tag << 4) | (vlan_tag >> 13) | + ((vlan_tag >> 9) & 0x8); + ptpd->word3 |= 1 << TPD_INS_VL_TAG_SHIFT; + ptpd->word2 |= (vlan_tag & TPD_VLANTAG_MASK) << + TPD_VLANTAG_SHIFT; + } + + tso = atl1_tso(adapter, skb, ptpd); + if (tso < 0) { + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + + if (!tso) { + ret_val = atl1_tx_csum(adapter, skb, ptpd); + if (ret_val < 0) { + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + } + + atl1_tx_map(adapter, skb, ptpd); + atl1_tx_queue(adapter, count, ptpd); + atl1_update_mailbox(adapter); + mmiowb(); + return NETDEV_TX_OK; +} + +static int atl1_rings_clean(struct napi_struct *napi, int budget) +{ + struct atl1_adapter *adapter = container_of(napi, struct atl1_adapter, napi); + int work_done = atl1_intr_rx(adapter, budget); + + if (atl1_intr_tx(adapter)) + work_done = budget; + + /* Let's come again to process some more packets */ + if (work_done >= budget) + return work_done; + + napi_complete(napi); + /* re-enable Interrupt */ + if (likely(adapter->int_enabled)) + atlx_imr_set(adapter, IMR_NORMAL_MASK); + return work_done; +} + +static inline int atl1_sched_rings_clean(struct atl1_adapter* adapter) +{ + if (!napi_schedule_prep(&adapter->napi)) + /* It is possible in case even the RX/TX ints are disabled via IMR + * register the ISR bits are set anyway (but do not produce IRQ). + * To handle such situation the napi functions used to check is + * something scheduled or not. + */ + return 0; + + __napi_schedule(&adapter->napi); + + /* + * Disable RX/TX ints via IMR register if it is + * allowed. NAPI handler must reenable them in same + * way. + */ + if (!adapter->int_enabled) + return 1; + + atlx_imr_set(adapter, IMR_NORXTX_MASK); + return 1; +} + +/** + * atl1_intr - Interrupt Handler + * @irq: interrupt number + * @data: pointer to a network interface device structure + */ +static irqreturn_t atl1_intr(int irq, void *data) +{ + struct atl1_adapter *adapter = netdev_priv(data); + u32 status; + + status = adapter->cmb.cmb->int_stats; + if (!status) + return IRQ_NONE; + + /* clear CMB interrupt status at once, + * but leave rx/tx interrupt status in case it should be dropped + * only if rx/tx processing queued. In other case interrupt + * can be lost. + */ + adapter->cmb.cmb->int_stats = status & (ISR_CMB_TX | ISR_CMB_RX); + + if (status & ISR_GPHY) /* clear phy status */ + atlx_clear_phy_int(adapter); + + /* clear ISR status, and Enable CMB DMA/Disable Interrupt */ + iowrite32(status | ISR_DIS_INT, adapter->hw.hw_addr + REG_ISR); + + /* check if SMB intr */ + if (status & ISR_SMB) + atl1_inc_smb(adapter); + + /* check if PCIE PHY Link down */ + if (status & ISR_PHY_LINKDOWN) { + if (netif_msg_intr(adapter)) + dev_printk(KERN_DEBUG, &adapter->pdev->dev, + "pcie phy link down %x\n", status); + if (netif_running(adapter->netdev)) { /* reset MAC */ + atlx_irq_disable(adapter); + schedule_work(&adapter->reset_dev_task); + return IRQ_HANDLED; + } + } + + /* check if DMA read/write error ? */ + if (status & (ISR_DMAR_TO_RST | ISR_DMAW_TO_RST)) { + if (netif_msg_intr(adapter)) + dev_printk(KERN_DEBUG, &adapter->pdev->dev, + "pcie DMA r/w error (status = 0x%x)\n", + status); + atlx_irq_disable(adapter); + schedule_work(&adapter->reset_dev_task); + return IRQ_HANDLED; + } + + /* link event */ + if (status & ISR_GPHY) { + adapter->soft_stats.tx_carrier_errors++; + atl1_check_for_link(adapter); + } + + /* transmit or receive event */ + if (status & (ISR_CMB_TX | ISR_CMB_RX) && + atl1_sched_rings_clean(adapter)) + adapter->cmb.cmb->int_stats = adapter->cmb.cmb->int_stats & + ~(ISR_CMB_TX | ISR_CMB_RX); + + /* rx exception */ + if (unlikely(status & (ISR_RXF_OV | ISR_RFD_UNRUN | + ISR_RRD_OV | ISR_HOST_RFD_UNRUN | + ISR_HOST_RRD_OV))) { + if (netif_msg_intr(adapter)) + dev_printk(KERN_DEBUG, + &adapter->pdev->dev, + "rx exception, ISR = 0x%x\n", + status); + atl1_sched_rings_clean(adapter); + } + + /* re-enable Interrupt */ + iowrite32(ISR_DIS_SMB | ISR_DIS_DMA, adapter->hw.hw_addr + REG_ISR); + return IRQ_HANDLED; +} + + +/** + * atl1_phy_config - Timer Call-back + * @data: pointer to netdev cast into an unsigned long + */ +static void atl1_phy_config(unsigned long data) +{ + struct atl1_adapter *adapter = (struct atl1_adapter *)data; + struct atl1_hw *hw = &adapter->hw; + unsigned long flags; + + spin_lock_irqsave(&adapter->lock, flags); + adapter->phy_timer_pending = false; + atl1_write_phy_reg(hw, MII_ADVERTISE, hw->mii_autoneg_adv_reg); + atl1_write_phy_reg(hw, MII_ATLX_CR, hw->mii_1000t_ctrl_reg); + atl1_write_phy_reg(hw, MII_BMCR, MII_CR_RESET | MII_CR_AUTO_NEG_EN); + spin_unlock_irqrestore(&adapter->lock, flags); +} + +/* + * Orphaned vendor comment left intact here: + * + * If TPD Buffer size equal to 0, PCIE DMAR_TO_INT + * will assert. We do soft reset <0x1400=1> according + * with the SPEC. BUT, it seemes that PCIE or DMA + * state-machine will not be reset. DMAR_TO_INT will + * assert again and again. + * + */ + +static int atl1_reset(struct atl1_adapter *adapter) +{ + int ret; + ret = atl1_reset_hw(&adapter->hw); + if (ret) + return ret; + return atl1_init_hw(&adapter->hw); +} + +static s32 atl1_up(struct atl1_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + int err; + int irq_flags = 0; + + /* hardware has been reset, we need to reload some things */ + atlx_set_multi(netdev); + atl1_init_ring_ptrs(adapter); + atlx_restore_vlan(adapter); + err = atl1_alloc_rx_buffers(adapter); + if (unlikely(!err)) + /* no RX BUFFER allocated */ + return -ENOMEM; + + if (unlikely(atl1_configure(adapter))) { + err = -EIO; + goto err_up; + } + + err = pci_enable_msi(adapter->pdev); + if (err) { + if (netif_msg_ifup(adapter)) + dev_info(&adapter->pdev->dev, + "Unable to enable MSI: %d\n", err); + irq_flags |= IRQF_SHARED; + } + + err = request_irq(adapter->pdev->irq, atl1_intr, irq_flags, + netdev->name, netdev); + if (unlikely(err)) + goto err_up; + + napi_enable(&adapter->napi); + atlx_irq_enable(adapter); + atl1_check_link(adapter); + netif_start_queue(netdev); + return 0; + +err_up: + pci_disable_msi(adapter->pdev); + /* free rx_buffers */ + atl1_clean_rx_ring(adapter); + return err; +} + +static void atl1_down(struct atl1_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + + napi_disable(&adapter->napi); + netif_stop_queue(netdev); + del_timer_sync(&adapter->phy_config_timer); + adapter->phy_timer_pending = false; + + atlx_irq_disable(adapter); + free_irq(adapter->pdev->irq, netdev); + pci_disable_msi(adapter->pdev); + atl1_reset_hw(&adapter->hw); + adapter->cmb.cmb->int_stats = 0; + + adapter->link_speed = SPEED_0; + adapter->link_duplex = -1; + netif_carrier_off(netdev); + + atl1_clean_tx_ring(adapter); + atl1_clean_rx_ring(adapter); +} + +static void atl1_reset_dev_task(struct work_struct *work) +{ + struct atl1_adapter *adapter = + container_of(work, struct atl1_adapter, reset_dev_task); + struct net_device *netdev = adapter->netdev; + + netif_device_detach(netdev); + atl1_down(adapter); + atl1_up(adapter); + netif_device_attach(netdev); +} + +/** + * atl1_change_mtu - Change the Maximum Transfer Unit + * @netdev: network interface device structure + * @new_mtu: new value for maximum frame size + * + * Returns 0 on success, negative on failure + */ +static int atl1_change_mtu(struct net_device *netdev, int new_mtu) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + int old_mtu = netdev->mtu; + int max_frame = new_mtu + ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN; + + if ((max_frame < ETH_ZLEN + ETH_FCS_LEN) || + (max_frame > MAX_JUMBO_FRAME_SIZE)) { + if (netif_msg_link(adapter)) + dev_warn(&adapter->pdev->dev, "invalid MTU setting\n"); + return -EINVAL; + } + + adapter->hw.max_frame_size = max_frame; + adapter->hw.tx_jumbo_task_th = (max_frame + 7) >> 3; + adapter->rx_buffer_len = (max_frame + 7) & ~7; + adapter->hw.rx_jumbo_th = adapter->rx_buffer_len / 8; + + netdev->mtu = new_mtu; + if ((old_mtu != new_mtu) && netif_running(netdev)) { + atl1_down(adapter); + atl1_up(adapter); + } + + return 0; +} + +/** + * atl1_open - Called when a network interface is made active + * @netdev: network interface device structure + * + * Returns 0 on success, negative value on failure + * + * The open entry point is called when a network interface is made + * active by the system (IFF_UP). At this point all resources needed + * for transmit and receive operations are allocated, the interrupt + * handler is registered with the OS, the watchdog timer is started, + * and the stack is notified that the interface is ready. + */ +static int atl1_open(struct net_device *netdev) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + int err; + + netif_carrier_off(netdev); + + /* allocate transmit descriptors */ + err = atl1_setup_ring_resources(adapter); + if (err) + return err; + + err = atl1_up(adapter); + if (err) + goto err_up; + + return 0; + +err_up: + atl1_reset(adapter); + return err; +} + +/** + * atl1_close - Disables a network interface + * @netdev: network interface device structure + * + * Returns 0, this is not allowed to fail + * + * The close entry point is called when an interface is de-activated + * by the OS. The hardware is still under the drivers control, but + * needs to be disabled. A global MAC reset is issued to stop the + * hardware, and all transmit and receive resources are freed. + */ +static int atl1_close(struct net_device *netdev) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + atl1_down(adapter); + atl1_free_ring_resources(adapter); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int atl1_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1_adapter *adapter = netdev_priv(netdev); + struct atl1_hw *hw = &adapter->hw; + u32 ctrl = 0; + u32 wufc = adapter->wol; + u32 val; + u16 speed; + u16 duplex; + + netif_device_detach(netdev); + if (netif_running(netdev)) + atl1_down(adapter); + + atl1_read_phy_reg(hw, MII_BMSR, (u16 *) & ctrl); + atl1_read_phy_reg(hw, MII_BMSR, (u16 *) & ctrl); + val = ctrl & BMSR_LSTATUS; + if (val) + wufc &= ~ATLX_WUFC_LNKC; + if (!wufc) + goto disable_wol; + + if (val) { + val = atl1_get_speed_and_duplex(hw, &speed, &duplex); + if (val) { + if (netif_msg_ifdown(adapter)) + dev_printk(KERN_DEBUG, &pdev->dev, + "error getting speed/duplex\n"); + goto disable_wol; + } + + ctrl = 0; + + /* enable magic packet WOL */ + if (wufc & ATLX_WUFC_MAG) + ctrl |= (WOL_MAGIC_EN | WOL_MAGIC_PME_EN); + iowrite32(ctrl, hw->hw_addr + REG_WOL_CTRL); + ioread32(hw->hw_addr + REG_WOL_CTRL); + + /* configure the mac */ + ctrl = MAC_CTRL_RX_EN; + ctrl |= ((u32)((speed == SPEED_1000) ? MAC_CTRL_SPEED_1000 : + MAC_CTRL_SPEED_10_100) << MAC_CTRL_SPEED_SHIFT); + if (duplex == FULL_DUPLEX) + ctrl |= MAC_CTRL_DUPLX; + ctrl |= (((u32)adapter->hw.preamble_len & + MAC_CTRL_PRMLEN_MASK) << MAC_CTRL_PRMLEN_SHIFT); + __atlx_vlan_mode(netdev->features, &ctrl); + if (wufc & ATLX_WUFC_MAG) + ctrl |= MAC_CTRL_BC_EN; + iowrite32(ctrl, hw->hw_addr + REG_MAC_CTRL); + ioread32(hw->hw_addr + REG_MAC_CTRL); + + /* poke the PHY */ + ctrl = ioread32(hw->hw_addr + REG_PCIE_PHYMISC); + ctrl |= PCIE_PHYMISC_FORCE_RCV_DET; + iowrite32(ctrl, hw->hw_addr + REG_PCIE_PHYMISC); + ioread32(hw->hw_addr + REG_PCIE_PHYMISC); + } else { + ctrl |= (WOL_LINK_CHG_EN | WOL_LINK_CHG_PME_EN); + iowrite32(ctrl, hw->hw_addr + REG_WOL_CTRL); + ioread32(hw->hw_addr + REG_WOL_CTRL); + iowrite32(0, hw->hw_addr + REG_MAC_CTRL); + ioread32(hw->hw_addr + REG_MAC_CTRL); + hw->phy_configured = false; + } + + return 0; + + disable_wol: + iowrite32(0, hw->hw_addr + REG_WOL_CTRL); + ioread32(hw->hw_addr + REG_WOL_CTRL); + ctrl = ioread32(hw->hw_addr + REG_PCIE_PHYMISC); + ctrl |= PCIE_PHYMISC_FORCE_RCV_DET; + iowrite32(ctrl, hw->hw_addr + REG_PCIE_PHYMISC); + ioread32(hw->hw_addr + REG_PCIE_PHYMISC); + hw->phy_configured = false; + + return 0; +} + +static int atl1_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1_adapter *adapter = netdev_priv(netdev); + + iowrite32(0, adapter->hw.hw_addr + REG_WOL_CTRL); + + atl1_reset_hw(&adapter->hw); + + if (netif_running(netdev)) { + adapter->cmb.cmb->int_stats = 0; + atl1_up(adapter); + } + netif_device_attach(netdev); + + return 0; +} +#endif + +compat_pci_suspend(atl1_suspend) +compat_pci_resume(atl1_resume) + +static SIMPLE_DEV_PM_OPS(atl1_pm_ops, atl1_suspend, atl1_resume); + +static void atl1_shutdown(struct pci_dev *pdev) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1_adapter *adapter = netdev_priv(netdev); + +#ifdef CONFIG_PM_SLEEP + atl1_suspend(&pdev->dev); +#endif + pci_wake_from_d3(pdev, adapter->wol); + pci_set_power_state(pdev, PCI_D3hot); +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +static void atl1_poll_controller(struct net_device *netdev) +{ + disable_irq(netdev->irq); + atl1_intr(netdev->irq, netdev); + enable_irq(netdev->irq); +} +#endif + +static const struct net_device_ops atl1_netdev_ops = { + .ndo_open = atl1_open, + .ndo_stop = atl1_close, + .ndo_start_xmit = atl1_xmit_frame, + .ndo_set_rx_mode = atlx_set_multi, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_mac_address = atl1_set_mac, + .ndo_change_mtu = atl1_change_mtu, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + .ndo_fix_features = atlx_fix_features, + .ndo_set_features = atlx_set_features, +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */ + .ndo_do_ioctl = atlx_ioctl, + .ndo_tx_timeout = atlx_tx_timeout, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = atl1_poll_controller, +#endif +}; + +/** + * atl1_probe - Device Initialization Routine + * @pdev: PCI device information struct + * @ent: entry in atl1_pci_tbl + * + * Returns 0 on success, negative on failure + * + * atl1_probe initializes an adapter identified by a pci_dev structure. + * The OS initialization, configuring of the adapter private structure, + * and a hardware reset occur. + */ +static int atl1_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct net_device *netdev; + struct atl1_adapter *adapter; + static int cards_found = 0; + int err; + + err = pci_enable_device(pdev); + if (err) + return err; + + /* + * The atl1 chip can DMA to 64-bit addresses, but it uses a single + * shared register for the high 32 bits, so only a single, aligned, + * 4 GB physical address range can be used at a time. + * + * Supporting 64-bit DMA on this hardware is more trouble than it's + * worth. It is far easier to limit to 32-bit DMA than update + * various kernel subsystems to support the mechanics required by a + * fixed-high-32-bit system. + */ + err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + if (err) { + dev_err(&pdev->dev, "no usable DMA configuration\n"); + goto err_dma; + } + /* + * Mark all PCI regions associated with PCI device + * pdev as being reserved by owner atl1_driver_name + */ + err = pci_request_regions(pdev, ATLX_DRIVER_NAME); + if (err) + goto err_request_regions; + + /* + * Enables bus-mastering on the device and calls + * pcibios_set_master to do the needed arch specific settings + */ + pci_set_master(pdev); + + netdev = alloc_etherdev(sizeof(struct atl1_adapter)); + if (!netdev) { + err = -ENOMEM; + goto err_alloc_etherdev; + } + SET_NETDEV_DEV(netdev, &pdev->dev); + + pci_set_drvdata(pdev, netdev); + adapter = netdev_priv(netdev); + adapter->netdev = netdev; + adapter->pdev = pdev; + adapter->hw.back = adapter; + adapter->msg_enable = netif_msg_init(debug, atl1_default_msg); + + adapter->hw.hw_addr = pci_iomap(pdev, 0, 0); + if (!adapter->hw.hw_addr) { + err = -EIO; + goto err_pci_iomap; + } + /* get device revision number */ + adapter->hw.dev_rev = ioread16(adapter->hw.hw_addr + + (REG_MASTER_CTRL + 2)); + if (netif_msg_probe(adapter)) + dev_info(&pdev->dev, "version %s\n", ATLX_DRIVER_VERSION); + + /* set default ring resource counts */ + adapter->rfd_ring.count = adapter->rrd_ring.count = ATL1_DEFAULT_RFD; + adapter->tpd_ring.count = ATL1_DEFAULT_TPD; + + adapter->mii.dev = netdev; + adapter->mii.mdio_read = mdio_read; + adapter->mii.mdio_write = mdio_write; + adapter->mii.phy_id_mask = 0x1f; + adapter->mii.reg_num_mask = 0x1f; + + netdev_attach_ops(netdev, &atl1_netdev_ops); + netdev->watchdog_timeo = 5 * HZ; + netif_napi_add(netdev, &adapter->napi, atl1_rings_clean, 64); + + netdev->ethtool_ops = &atl1_ethtool_ops; + adapter->bd_number = cards_found; + + /* setup the private structure */ + err = atl1_sw_init(adapter); + if (err) + goto err_common; + + netdev->features = NETIF_F_HW_CSUM; + netdev->features |= NETIF_F_SG; + netdev->features |= (NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + netdev->hw_features = NETIF_F_HW_CSUM | NETIF_F_SG | NETIF_F_TSO | + NETIF_F_HW_VLAN_CTAG_RX; + + /* is this valid? see atl1_setup_mac_ctrl() */ + netdev->features |= NETIF_F_RXCSUM; +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */ + + /* + * patch for some L1 of old version, + * the final version of L1 may not need these + * patches + */ + /* atl1_pcie_patch(adapter); */ + + /* really reset GPHY core */ + iowrite16(0, adapter->hw.hw_addr + REG_PHY_ENABLE); + + /* + * reset the controller to + * put the device in a known good starting state + */ + if (atl1_reset_hw(&adapter->hw)) { + err = -EIO; + goto err_common; + } + + /* copy the MAC address out of the EEPROM */ + if (atl1_read_mac_addr(&adapter->hw)) { + /* mark random mac */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)) + netdev->addr_assign_type = NET_ADDR_RANDOM; +#endif + } + memcpy(netdev->dev_addr, adapter->hw.mac_addr, netdev->addr_len); + + if (!is_valid_ether_addr(netdev->dev_addr)) { + err = -EIO; + goto err_common; + } + + atl1_check_options(adapter); + + /* pre-init the MAC, and setup link */ + err = atl1_init_hw(&adapter->hw); + if (err) { + err = -EIO; + goto err_common; + } + + atl1_pcie_patch(adapter); + /* assume we have no link for now */ + netif_carrier_off(netdev); + + setup_timer(&adapter->phy_config_timer, atl1_phy_config, + (unsigned long)adapter); + adapter->phy_timer_pending = false; + + INIT_WORK(&adapter->reset_dev_task, atl1_reset_dev_task); + + INIT_WORK(&adapter->link_chg_task, atlx_link_chg_task); + + err = register_netdev(netdev); + if (err) + goto err_common; + + cards_found++; + atl1_via_workaround(adapter); + return 0; + +err_common: + pci_iounmap(pdev, adapter->hw.hw_addr); +err_pci_iomap: + free_netdev(netdev); +err_alloc_etherdev: + pci_release_regions(pdev); +err_dma: +err_request_regions: + pci_disable_device(pdev); + return err; +} + +/** + * atl1_remove - Device Removal Routine + * @pdev: PCI device information struct + * + * atl1_remove is called by the PCI subsystem to alert the driver + * that it should release a PCI device. The could be caused by a + * Hot-Plug event, or because the driver is going to be removed from + * memory. + */ +static void atl1_remove(struct pci_dev *pdev) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl1_adapter *adapter; + /* Device not available. Return. */ + if (!netdev) + return; + + adapter = netdev_priv(netdev); + + /* + * Some atl1 boards lack persistent storage for their MAC, and get it + * from the BIOS during POST. If we've been messing with the MAC + * address, we need to save the permanent one. + */ + if (memcmp(adapter->hw.mac_addr, adapter->hw.perm_mac_addr, ETH_ALEN)) { + memcpy(adapter->hw.mac_addr, adapter->hw.perm_mac_addr, + ETH_ALEN); + atl1_set_mac_addr(&adapter->hw); + } + + iowrite16(0, adapter->hw.hw_addr + REG_PHY_ENABLE); + unregister_netdev(netdev); + pci_iounmap(pdev, adapter->hw.hw_addr); + pci_release_regions(pdev); + free_netdev(netdev); + pci_disable_device(pdev); +} + +static struct pci_driver atl1_driver = { + .name = ATLX_DRIVER_NAME, + .id_table = atl1_pci_tbl, + .probe = atl1_probe, + .remove = atl1_remove, + .shutdown = atl1_shutdown, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) + .driver.pm = &atl1_pm_ops, +#elif defined(CONFIG_PM_SLEEP) + .suspend = atl1_suspend_compat, + .resume = atl1_resume_compat, +#endif +}; + +/** + * atl1_exit_module - Driver Exit Cleanup Routine + * + * atl1_exit_module is called just before the driver is removed + * from memory. + */ +static void __exit atl1_exit_module(void) +{ + pci_unregister_driver(&atl1_driver); +} + +/** + * atl1_init_module - Driver Registration Routine + * + * atl1_init_module is the first routine called when the driver is + * loaded. All it does is register with the PCI subsystem. + */ +static int __init atl1_init_module(void) +{ + return pci_register_driver(&atl1_driver); +} + +module_init(atl1_init_module); +module_exit(atl1_exit_module); + +struct atl1_stats { + char stat_string[ETH_GSTRING_LEN]; + int sizeof_stat; + int stat_offset; +}; + +#define ATL1_STAT(m) \ + sizeof(((struct atl1_adapter *)0)->m), offsetof(struct atl1_adapter, m) + +static struct atl1_stats atl1_gstrings_stats[] = { + {"rx_packets", ATL1_STAT(soft_stats.rx_packets)}, + {"tx_packets", ATL1_STAT(soft_stats.tx_packets)}, + {"rx_bytes", ATL1_STAT(soft_stats.rx_bytes)}, + {"tx_bytes", ATL1_STAT(soft_stats.tx_bytes)}, + {"rx_errors", ATL1_STAT(soft_stats.rx_errors)}, + {"tx_errors", ATL1_STAT(soft_stats.tx_errors)}, + {"multicast", ATL1_STAT(soft_stats.multicast)}, + {"collisions", ATL1_STAT(soft_stats.collisions)}, + {"rx_length_errors", ATL1_STAT(soft_stats.rx_length_errors)}, + {"rx_over_errors", ATL1_STAT(soft_stats.rx_missed_errors)}, + {"rx_crc_errors", ATL1_STAT(soft_stats.rx_crc_errors)}, + {"rx_frame_errors", ATL1_STAT(soft_stats.rx_frame_errors)}, + {"rx_fifo_errors", ATL1_STAT(soft_stats.rx_fifo_errors)}, + {"rx_missed_errors", ATL1_STAT(soft_stats.rx_missed_errors)}, + {"tx_aborted_errors", ATL1_STAT(soft_stats.tx_aborted_errors)}, + {"tx_carrier_errors", ATL1_STAT(soft_stats.tx_carrier_errors)}, + {"tx_fifo_errors", ATL1_STAT(soft_stats.tx_fifo_errors)}, + {"tx_window_errors", ATL1_STAT(soft_stats.tx_window_errors)}, + {"tx_abort_exce_coll", ATL1_STAT(soft_stats.excecol)}, + {"tx_abort_late_coll", ATL1_STAT(soft_stats.latecol)}, + {"tx_deferred_ok", ATL1_STAT(soft_stats.deffer)}, + {"tx_single_coll_ok", ATL1_STAT(soft_stats.scc)}, + {"tx_multi_coll_ok", ATL1_STAT(soft_stats.mcc)}, + {"tx_underun", ATL1_STAT(soft_stats.tx_underun)}, + {"tx_trunc", ATL1_STAT(soft_stats.tx_trunc)}, + {"tx_pause", ATL1_STAT(soft_stats.tx_pause)}, + {"rx_pause", ATL1_STAT(soft_stats.rx_pause)}, + {"rx_rrd_ov", ATL1_STAT(soft_stats.rx_rrd_ov)}, + {"rx_trunc", ATL1_STAT(soft_stats.rx_trunc)} +}; + +static void atl1_get_ethtool_stats(struct net_device *netdev, + struct ethtool_stats *stats, u64 *data) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + int i; + char *p; + + for (i = 0; i < ARRAY_SIZE(atl1_gstrings_stats); i++) { + p = (char *)adapter+atl1_gstrings_stats[i].stat_offset; + data[i] = (atl1_gstrings_stats[i].sizeof_stat == + sizeof(u64)) ? *(u64 *)p : *(u32 *)p; + } + +} + +static int atl1_get_sset_count(struct net_device *netdev, int sset) +{ + switch (sset) { + case ETH_SS_STATS: + return ARRAY_SIZE(atl1_gstrings_stats); + default: + return -EOPNOTSUPP; + } +} + +static int atl1_get_settings(struct net_device *netdev, + struct ethtool_cmd *ecmd) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + struct atl1_hw *hw = &adapter->hw; + + ecmd->supported = (SUPPORTED_10baseT_Half | + SUPPORTED_10baseT_Full | + SUPPORTED_100baseT_Half | + SUPPORTED_100baseT_Full | + SUPPORTED_1000baseT_Full | + SUPPORTED_Autoneg | SUPPORTED_TP); + ecmd->advertising = ADVERTISED_TP; + if (hw->media_type == MEDIA_TYPE_AUTO_SENSOR || + hw->media_type == MEDIA_TYPE_1000M_FULL) { + ecmd->advertising |= ADVERTISED_Autoneg; + if (hw->media_type == MEDIA_TYPE_AUTO_SENSOR) { + ecmd->advertising |= ADVERTISED_Autoneg; + ecmd->advertising |= + (ADVERTISED_10baseT_Half | + ADVERTISED_10baseT_Full | + ADVERTISED_100baseT_Half | + ADVERTISED_100baseT_Full | + ADVERTISED_1000baseT_Full); + } else + ecmd->advertising |= (ADVERTISED_1000baseT_Full); + } + ecmd->port = PORT_TP; + ecmd->phy_address = 0; + ecmd->transceiver = XCVR_INTERNAL; + + if (netif_carrier_ok(adapter->netdev)) { + u16 link_speed, link_duplex; + atl1_get_speed_and_duplex(hw, &link_speed, &link_duplex); + ethtool_cmd_speed_set(ecmd, link_speed); + if (link_duplex == FULL_DUPLEX) + ecmd->duplex = DUPLEX_FULL; + else + ecmd->duplex = DUPLEX_HALF; + } else { + ethtool_cmd_speed_set(ecmd, -1); + ecmd->duplex = -1; + } + if (hw->media_type == MEDIA_TYPE_AUTO_SENSOR || + hw->media_type == MEDIA_TYPE_1000M_FULL) + ecmd->autoneg = AUTONEG_ENABLE; + else + ecmd->autoneg = AUTONEG_DISABLE; + + return 0; +} + +static int atl1_set_settings(struct net_device *netdev, + struct ethtool_cmd *ecmd) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + struct atl1_hw *hw = &adapter->hw; + u16 phy_data; + int ret_val = 0; + u16 old_media_type = hw->media_type; + + if (netif_running(adapter->netdev)) { + if (netif_msg_link(adapter)) + dev_dbg(&adapter->pdev->dev, + "ethtool shutting down adapter\n"); + atl1_down(adapter); + } + + if (ecmd->autoneg == AUTONEG_ENABLE) + hw->media_type = MEDIA_TYPE_AUTO_SENSOR; + else { + u32 speed = ethtool_cmd_speed(ecmd); + if (speed == SPEED_1000) { + if (ecmd->duplex != DUPLEX_FULL) { + if (netif_msg_link(adapter)) + dev_warn(&adapter->pdev->dev, + "1000M half is invalid\n"); + ret_val = -EINVAL; + goto exit_sset; + } + hw->media_type = MEDIA_TYPE_1000M_FULL; + } else if (speed == SPEED_100) { + if (ecmd->duplex == DUPLEX_FULL) + hw->media_type = MEDIA_TYPE_100M_FULL; + else + hw->media_type = MEDIA_TYPE_100M_HALF; + } else { + if (ecmd->duplex == DUPLEX_FULL) + hw->media_type = MEDIA_TYPE_10M_FULL; + else + hw->media_type = MEDIA_TYPE_10M_HALF; + } + } + switch (hw->media_type) { + case MEDIA_TYPE_AUTO_SENSOR: + ecmd->advertising = + ADVERTISED_10baseT_Half | + ADVERTISED_10baseT_Full | + ADVERTISED_100baseT_Half | + ADVERTISED_100baseT_Full | + ADVERTISED_1000baseT_Full | + ADVERTISED_Autoneg | ADVERTISED_TP; + break; + case MEDIA_TYPE_1000M_FULL: + ecmd->advertising = + ADVERTISED_1000baseT_Full | + ADVERTISED_Autoneg | ADVERTISED_TP; + break; + default: + ecmd->advertising = 0; + break; + } + if (atl1_phy_setup_autoneg_adv(hw)) { + ret_val = -EINVAL; + if (netif_msg_link(adapter)) + dev_warn(&adapter->pdev->dev, + "invalid ethtool speed/duplex setting\n"); + goto exit_sset; + } + if (hw->media_type == MEDIA_TYPE_AUTO_SENSOR || + hw->media_type == MEDIA_TYPE_1000M_FULL) + phy_data = MII_CR_RESET | MII_CR_AUTO_NEG_EN; + else { + switch (hw->media_type) { + case MEDIA_TYPE_100M_FULL: + phy_data = + MII_CR_FULL_DUPLEX | MII_CR_SPEED_100 | + MII_CR_RESET; + break; + case MEDIA_TYPE_100M_HALF: + phy_data = MII_CR_SPEED_100 | MII_CR_RESET; + break; + case MEDIA_TYPE_10M_FULL: + phy_data = + MII_CR_FULL_DUPLEX | MII_CR_SPEED_10 | MII_CR_RESET; + break; + default: + /* MEDIA_TYPE_10M_HALF: */ + phy_data = MII_CR_SPEED_10 | MII_CR_RESET; + break; + } + } + atl1_write_phy_reg(hw, MII_BMCR, phy_data); +exit_sset: + if (ret_val) + hw->media_type = old_media_type; + + if (netif_running(adapter->netdev)) { + if (netif_msg_link(adapter)) + dev_dbg(&adapter->pdev->dev, + "ethtool starting adapter\n"); + atl1_up(adapter); + } else if (!ret_val) { + if (netif_msg_link(adapter)) + dev_dbg(&adapter->pdev->dev, + "ethtool resetting adapter\n"); + atl1_reset(adapter); + } + return ret_val; +} + +static void atl1_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *drvinfo) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + + strlcpy(drvinfo->driver, ATLX_DRIVER_NAME, sizeof(drvinfo->driver)); + strlcpy(drvinfo->version, ATLX_DRIVER_VERSION, + sizeof(drvinfo->version)); + strlcpy(drvinfo->bus_info, pci_name(adapter->pdev), + sizeof(drvinfo->bus_info)); + drvinfo->eedump_len = ATL1_EEDUMP_LEN; +} + +static void atl1_get_wol(struct net_device *netdev, + struct ethtool_wolinfo *wol) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + + wol->supported = WAKE_MAGIC; + wol->wolopts = 0; + if (adapter->wol & ATLX_WUFC_MAG) + wol->wolopts |= WAKE_MAGIC; +} + +static int atl1_set_wol(struct net_device *netdev, + struct ethtool_wolinfo *wol) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + + if (wol->wolopts & (WAKE_PHY | WAKE_UCAST | WAKE_MCAST | WAKE_BCAST | + WAKE_ARP | WAKE_MAGICSECURE)) + return -EOPNOTSUPP; + adapter->wol = 0; + if (wol->wolopts & WAKE_MAGIC) + adapter->wol |= ATLX_WUFC_MAG; + + device_set_wakeup_enable(&adapter->pdev->dev, adapter->wol); + + return 0; +} + +static u32 atl1_get_msglevel(struct net_device *netdev) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + return adapter->msg_enable; +} + +static void atl1_set_msglevel(struct net_device *netdev, u32 value) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + adapter->msg_enable = value; +} + +static int atl1_get_regs_len(struct net_device *netdev) +{ + return ATL1_REG_COUNT * sizeof(u32); +} + +static void atl1_get_regs(struct net_device *netdev, struct ethtool_regs *regs, + void *p) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + struct atl1_hw *hw = &adapter->hw; + unsigned int i; + u32 *regbuf = p; + + for (i = 0; i < ATL1_REG_COUNT; i++) { + /* + * This switch statement avoids reserved regions + * of register space. + */ + switch (i) { + case 6 ... 9: + case 14: + case 29 ... 31: + case 34 ... 63: + case 75 ... 127: + case 136 ... 1023: + case 1027 ... 1087: + case 1091 ... 1151: + case 1194 ... 1195: + case 1200 ... 1201: + case 1206 ... 1213: + case 1216 ... 1279: + case 1290 ... 1311: + case 1323 ... 1343: + case 1358 ... 1359: + case 1368 ... 1375: + case 1378 ... 1383: + case 1388 ... 1391: + case 1393 ... 1395: + case 1402 ... 1403: + case 1410 ... 1471: + case 1522 ... 1535: + /* reserved region; don't read it */ + regbuf[i] = 0; + break; + default: + /* unreserved region */ + regbuf[i] = ioread32(hw->hw_addr + (i * sizeof(u32))); + } + } +} + +static void atl1_get_ringparam(struct net_device *netdev, + struct ethtool_ringparam *ring) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + struct atl1_tpd_ring *txdr = &adapter->tpd_ring; + struct atl1_rfd_ring *rxdr = &adapter->rfd_ring; + + ring->rx_max_pending = ATL1_MAX_RFD; + ring->tx_max_pending = ATL1_MAX_TPD; + ring->rx_pending = rxdr->count; + ring->tx_pending = txdr->count; +} + +static int atl1_set_ringparam(struct net_device *netdev, + struct ethtool_ringparam *ring) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + struct atl1_tpd_ring *tpdr = &adapter->tpd_ring; + struct atl1_rrd_ring *rrdr = &adapter->rrd_ring; + struct atl1_rfd_ring *rfdr = &adapter->rfd_ring; + + struct atl1_tpd_ring tpd_old, tpd_new; + struct atl1_rfd_ring rfd_old, rfd_new; + struct atl1_rrd_ring rrd_old, rrd_new; + struct atl1_ring_header rhdr_old, rhdr_new; + struct atl1_smb smb; + struct atl1_cmb cmb; + int err; + + tpd_old = adapter->tpd_ring; + rfd_old = adapter->rfd_ring; + rrd_old = adapter->rrd_ring; + rhdr_old = adapter->ring_header; + + if (netif_running(adapter->netdev)) + atl1_down(adapter); + + rfdr->count = (u16) max(ring->rx_pending, (u32) ATL1_MIN_RFD); + rfdr->count = rfdr->count > ATL1_MAX_RFD ? ATL1_MAX_RFD : + rfdr->count; + rfdr->count = (rfdr->count + 3) & ~3; + rrdr->count = rfdr->count; + + tpdr->count = (u16) max(ring->tx_pending, (u32) ATL1_MIN_TPD); + tpdr->count = tpdr->count > ATL1_MAX_TPD ? ATL1_MAX_TPD : + tpdr->count; + tpdr->count = (tpdr->count + 3) & ~3; + + if (netif_running(adapter->netdev)) { + /* try to get new resources before deleting old */ + err = atl1_setup_ring_resources(adapter); + if (err) + goto err_setup_ring; + + /* + * save the new, restore the old in order to free it, + * then restore the new back again + */ + + rfd_new = adapter->rfd_ring; + rrd_new = adapter->rrd_ring; + tpd_new = adapter->tpd_ring; + rhdr_new = adapter->ring_header; + adapter->rfd_ring = rfd_old; + adapter->rrd_ring = rrd_old; + adapter->tpd_ring = tpd_old; + adapter->ring_header = rhdr_old; + /* + * Save SMB and CMB, since atl1_free_ring_resources + * will clear them. + */ + smb = adapter->smb; + cmb = adapter->cmb; + atl1_free_ring_resources(adapter); + adapter->rfd_ring = rfd_new; + adapter->rrd_ring = rrd_new; + adapter->tpd_ring = tpd_new; + adapter->ring_header = rhdr_new; + adapter->smb = smb; + adapter->cmb = cmb; + + err = atl1_up(adapter); + if (err) + return err; + } + return 0; + +err_setup_ring: + adapter->rfd_ring = rfd_old; + adapter->rrd_ring = rrd_old; + adapter->tpd_ring = tpd_old; + adapter->ring_header = rhdr_old; + atl1_up(adapter); + return err; +} + +static void atl1_get_pauseparam(struct net_device *netdev, + struct ethtool_pauseparam *epause) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + struct atl1_hw *hw = &adapter->hw; + + if (hw->media_type == MEDIA_TYPE_AUTO_SENSOR || + hw->media_type == MEDIA_TYPE_1000M_FULL) { + epause->autoneg = AUTONEG_ENABLE; + } else { + epause->autoneg = AUTONEG_DISABLE; + } + epause->rx_pause = 1; + epause->tx_pause = 1; +} + +static int atl1_set_pauseparam(struct net_device *netdev, + struct ethtool_pauseparam *epause) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + struct atl1_hw *hw = &adapter->hw; + + if (hw->media_type == MEDIA_TYPE_AUTO_SENSOR || + hw->media_type == MEDIA_TYPE_1000M_FULL) { + epause->autoneg = AUTONEG_ENABLE; + } else { + epause->autoneg = AUTONEG_DISABLE; + } + + epause->rx_pause = 1; + epause->tx_pause = 1; + + return 0; +} + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) +/* FIXME: is this right? -- CHS */ +static u32 atl1_get_rx_csum(struct net_device *netdev) +{ + return 1; +} +#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) */ + +static void atl1_get_strings(struct net_device *netdev, u32 stringset, + u8 *data) +{ + u8 *p = data; + int i; + + switch (stringset) { + case ETH_SS_STATS: + for (i = 0; i < ARRAY_SIZE(atl1_gstrings_stats); i++) { + memcpy(p, atl1_gstrings_stats[i].stat_string, + ETH_GSTRING_LEN); + p += ETH_GSTRING_LEN; + } + break; + } +} + +static int atl1_nway_reset(struct net_device *netdev) +{ + struct atl1_adapter *adapter = netdev_priv(netdev); + struct atl1_hw *hw = &adapter->hw; + + if (netif_running(netdev)) { + u16 phy_data; + atl1_down(adapter); + + if (hw->media_type == MEDIA_TYPE_AUTO_SENSOR || + hw->media_type == MEDIA_TYPE_1000M_FULL) { + phy_data = MII_CR_RESET | MII_CR_AUTO_NEG_EN; + } else { + switch (hw->media_type) { + case MEDIA_TYPE_100M_FULL: + phy_data = MII_CR_FULL_DUPLEX | + MII_CR_SPEED_100 | MII_CR_RESET; + break; + case MEDIA_TYPE_100M_HALF: + phy_data = MII_CR_SPEED_100 | MII_CR_RESET; + break; + case MEDIA_TYPE_10M_FULL: + phy_data = MII_CR_FULL_DUPLEX | + MII_CR_SPEED_10 | MII_CR_RESET; + break; + default: + /* MEDIA_TYPE_10M_HALF */ + phy_data = MII_CR_SPEED_10 | MII_CR_RESET; + } + } + atl1_write_phy_reg(hw, MII_BMCR, phy_data); + atl1_up(adapter); + } + return 0; +} + +static const struct ethtool_ops atl1_ethtool_ops = { + .get_settings = atl1_get_settings, + .set_settings = atl1_set_settings, + .get_drvinfo = atl1_get_drvinfo, + .get_wol = atl1_get_wol, + .set_wol = atl1_set_wol, + .get_msglevel = atl1_get_msglevel, + .set_msglevel = atl1_set_msglevel, + .get_regs_len = atl1_get_regs_len, + .get_regs = atl1_get_regs, + .get_ringparam = atl1_get_ringparam, + .set_ringparam = atl1_set_ringparam, + .get_pauseparam = atl1_get_pauseparam, + .set_pauseparam = atl1_set_pauseparam, + .get_link = ethtool_op_get_link, + .get_strings = atl1_get_strings, + .nway_reset = atl1_nway_reset, + .get_ethtool_stats = atl1_get_ethtool_stats, + .get_sset_count = atl1_get_sset_count, +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) + .get_rx_csum = atl1_get_rx_csum, + .set_tx_csum = ethtool_op_set_tx_hw_csum, + .set_sg = ethtool_op_set_sg, + .set_tso = ethtool_op_set_tso, +#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) */ +}; diff --git a/drivers/net/ethernet/atheros/atlx/atl1.h b/drivers/net/ethernet/atheros/atlx/atl1.h new file mode 100644 index 0000000..3bf79a5 --- /dev/null +++ b/drivers/net/ethernet/atheros/atlx/atl1.h @@ -0,0 +1,802 @@ +/* + * Copyright(c) 2005 - 2006 Attansic Corporation. All rights reserved. + * Copyright(c) 2006 - 2007 Chris Snook + * Copyright(c) 2006 - 2008 Jay Cliburn + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef ATL1_H +#define ATL1_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atlx.h" + +#define ATLX_DRIVER_NAME "atl1" + +MODULE_DESCRIPTION("Atheros L1 Gigabit Ethernet Driver"); + +#define atlx_adapter atl1_adapter +#define atlx_check_for_link atl1_check_for_link +#define atlx_check_link atl1_check_link +#define atlx_hash_mc_addr atl1_hash_mc_addr +#define atlx_hash_set atl1_hash_set +#define atlx_hw atl1_hw +#define atlx_mii_ioctl atl1_mii_ioctl +#define atlx_read_phy_reg atl1_read_phy_reg +#define atlx_set_mac atl1_set_mac +#define atlx_set_mac_addr atl1_set_mac_addr + +struct atl1_adapter; +struct atl1_hw; + +/* function prototypes needed by multiple files */ +static u32 atl1_hash_mc_addr(struct atl1_hw *hw, u8 *mc_addr); +static void atl1_hash_set(struct atl1_hw *hw, u32 hash_value); +static void atl1_set_mac_addr(struct atl1_hw *hw); +static int atl1_mii_ioctl(struct net_device *netdev, struct ifreq *ifr, + int cmd); +static u32 atl1_check_link(struct atl1_adapter *adapter); + +/* hardware definitions specific to L1 */ + +/* Block IDLE Status Register */ +#define IDLE_STATUS_RXMAC 0x1 +#define IDLE_STATUS_TXMAC 0x2 +#define IDLE_STATUS_RXQ 0x4 +#define IDLE_STATUS_TXQ 0x8 +#define IDLE_STATUS_DMAR 0x10 +#define IDLE_STATUS_DMAW 0x20 +#define IDLE_STATUS_SMB 0x40 +#define IDLE_STATUS_CMB 0x80 + +/* MDIO Control Register */ +#define MDIO_WAIT_TIMES 30 + +/* MAC Control Register */ +#define MAC_CTRL_TX_PAUSE 0x10000 +#define MAC_CTRL_SCNT 0x20000 +#define MAC_CTRL_SRST_TX 0x40000 +#define MAC_CTRL_TX_SIMURST 0x80000 +#define MAC_CTRL_SPEED_SHIFT 20 +#define MAC_CTRL_SPEED_MASK 0x300000 +#define MAC_CTRL_SPEED_1000 0x2 +#define MAC_CTRL_SPEED_10_100 0x1 +#define MAC_CTRL_DBG_TX_BKPRESURE 0x400000 +#define MAC_CTRL_TX_HUGE 0x800000 +#define MAC_CTRL_RX_CHKSUM_EN 0x1000000 +#define MAC_CTRL_DBG 0x8000000 + +/* Wake-On-Lan control register */ +#define WOL_CLK_SWITCH_EN 0x8000 +#define WOL_PT5_EN 0x200000 +#define WOL_PT6_EN 0x400000 +#define WOL_PT5_MATCH 0x8000000 +#define WOL_PT6_MATCH 0x10000000 + +/* WOL Length ( 2 DWORD ) */ +#define REG_WOL_PATTERN_LEN 0x14A4 +#define WOL_PT_LEN_MASK 0x7F +#define WOL_PT0_LEN_SHIFT 0 +#define WOL_PT1_LEN_SHIFT 8 +#define WOL_PT2_LEN_SHIFT 16 +#define WOL_PT3_LEN_SHIFT 24 +#define WOL_PT4_LEN_SHIFT 0 +#define WOL_PT5_LEN_SHIFT 8 +#define WOL_PT6_LEN_SHIFT 16 + +/* Internal SRAM Partition Registers, low 32 bits */ +#define REG_SRAM_RFD_LEN 0x1504 +#define REG_SRAM_RRD_ADDR 0x1508 +#define REG_SRAM_RRD_LEN 0x150C +#define REG_SRAM_TPD_ADDR 0x1510 +#define REG_SRAM_TPD_LEN 0x1514 +#define REG_SRAM_TRD_ADDR 0x1518 +#define REG_SRAM_TRD_LEN 0x151C +#define REG_SRAM_RXF_ADDR 0x1520 +#define REG_SRAM_RXF_LEN 0x1524 +#define REG_SRAM_TXF_ADDR 0x1528 +#define REG_SRAM_TXF_LEN 0x152C +#define REG_SRAM_TCPH_PATH_ADDR 0x1530 +#define SRAM_TCPH_ADDR_MASK 0xFFF +#define SRAM_TCPH_ADDR_SHIFT 0 +#define SRAM_PATH_ADDR_MASK 0xFFF +#define SRAM_PATH_ADDR_SHIFT 16 + +/* Load Ptr Register */ +#define REG_LOAD_PTR 0x1534 + +/* Descriptor Control registers, low 32 bits */ +#define REG_DESC_RFD_ADDR_LO 0x1544 +#define REG_DESC_RRD_ADDR_LO 0x1548 +#define REG_DESC_TPD_ADDR_LO 0x154C +#define REG_DESC_CMB_ADDR_LO 0x1550 +#define REG_DESC_SMB_ADDR_LO 0x1554 +#define REG_DESC_RFD_RRD_RING_SIZE 0x1558 +#define DESC_RFD_RING_SIZE_MASK 0x7FF +#define DESC_RFD_RING_SIZE_SHIFT 0 +#define DESC_RRD_RING_SIZE_MASK 0x7FF +#define DESC_RRD_RING_SIZE_SHIFT 16 +#define REG_DESC_TPD_RING_SIZE 0x155C +#define DESC_TPD_RING_SIZE_MASK 0x3FF +#define DESC_TPD_RING_SIZE_SHIFT 0 + +/* TXQ Control Register */ +#define REG_TXQ_CTRL 0x1580 +#define TXQ_CTRL_TPD_BURST_NUM_SHIFT 0 +#define TXQ_CTRL_TPD_BURST_NUM_MASK 0x1F +#define TXQ_CTRL_EN 0x20 +#define TXQ_CTRL_ENH_MODE 0x40 +#define TXQ_CTRL_TPD_FETCH_TH_SHIFT 8 +#define TXQ_CTRL_TPD_FETCH_TH_MASK 0x3F +#define TXQ_CTRL_TXF_BURST_NUM_SHIFT 16 +#define TXQ_CTRL_TXF_BURST_NUM_MASK 0xFFFF + +/* Jumbo packet Threshold for task offload */ +#define REG_TX_JUMBO_TASK_TH_TPD_IPG 0x1584 +#define TX_JUMBO_TASK_TH_MASK 0x7FF +#define TX_JUMBO_TASK_TH_SHIFT 0 +#define TX_TPD_MIN_IPG_MASK 0x1F +#define TX_TPD_MIN_IPG_SHIFT 16 + +/* RXQ Control Register */ +#define REG_RXQ_CTRL 0x15A0 +#define RXQ_CTRL_RFD_BURST_NUM_SHIFT 0 +#define RXQ_CTRL_RFD_BURST_NUM_MASK 0xFF +#define RXQ_CTRL_RRD_BURST_THRESH_SHIFT 8 +#define RXQ_CTRL_RRD_BURST_THRESH_MASK 0xFF +#define RXQ_CTRL_RFD_PREF_MIN_IPG_SHIFT 16 +#define RXQ_CTRL_RFD_PREF_MIN_IPG_MASK 0x1F +#define RXQ_CTRL_CUT_THRU_EN 0x40000000 +#define RXQ_CTRL_EN 0x80000000 + +/* Rx jumbo packet threshold and rrd retirement timer */ +#define REG_RXQ_JMBOSZ_RRDTIM 0x15A4 +#define RXQ_JMBOSZ_TH_MASK 0x7FF +#define RXQ_JMBOSZ_TH_SHIFT 0 +#define RXQ_JMBO_LKAH_MASK 0xF +#define RXQ_JMBO_LKAH_SHIFT 11 +#define RXQ_RRD_TIMER_MASK 0xFFFF +#define RXQ_RRD_TIMER_SHIFT 16 + +/* RFD flow control register */ +#define REG_RXQ_RXF_PAUSE_THRESH 0x15A8 +#define RXQ_RXF_PAUSE_TH_HI_SHIFT 16 +#define RXQ_RXF_PAUSE_TH_HI_MASK 0xFFF +#define RXQ_RXF_PAUSE_TH_LO_SHIFT 0 +#define RXQ_RXF_PAUSE_TH_LO_MASK 0xFFF + +/* RRD flow control register */ +#define REG_RXQ_RRD_PAUSE_THRESH 0x15AC +#define RXQ_RRD_PAUSE_TH_HI_SHIFT 0 +#define RXQ_RRD_PAUSE_TH_HI_MASK 0xFFF +#define RXQ_RRD_PAUSE_TH_LO_SHIFT 16 +#define RXQ_RRD_PAUSE_TH_LO_MASK 0xFFF + +/* DMA Engine Control Register */ +#define REG_DMA_CTRL 0x15C0 +#define DMA_CTRL_DMAR_IN_ORDER 0x1 +#define DMA_CTRL_DMAR_ENH_ORDER 0x2 +#define DMA_CTRL_DMAR_OUT_ORDER 0x4 +#define DMA_CTRL_RCB_VALUE 0x8 +#define DMA_CTRL_DMAR_BURST_LEN_SHIFT 4 +#define DMA_CTRL_DMAR_BURST_LEN_MASK 7 +#define DMA_CTRL_DMAW_BURST_LEN_SHIFT 7 +#define DMA_CTRL_DMAW_BURST_LEN_MASK 7 +#define DMA_CTRL_DMAR_EN 0x400 +#define DMA_CTRL_DMAW_EN 0x800 + +/* CMB/SMB Control Register */ +#define REG_CSMB_CTRL 0x15D0 +#define CSMB_CTRL_CMB_NOW 1 +#define CSMB_CTRL_SMB_NOW 2 +#define CSMB_CTRL_CMB_EN 4 +#define CSMB_CTRL_SMB_EN 8 + +/* CMB DMA Write Threshold Register */ +#define REG_CMB_WRITE_TH 0x15D4 +#define CMB_RRD_TH_SHIFT 0 +#define CMB_RRD_TH_MASK 0x7FF +#define CMB_TPD_TH_SHIFT 16 +#define CMB_TPD_TH_MASK 0x7FF + +/* RX/TX count-down timer to trigger CMB-write. 2us resolution. */ +#define REG_CMB_WRITE_TIMER 0x15D8 +#define CMB_RX_TM_SHIFT 0 +#define CMB_RX_TM_MASK 0xFFFF +#define CMB_TX_TM_SHIFT 16 +#define CMB_TX_TM_MASK 0xFFFF + +/* Number of packet received since last CMB write */ +#define REG_CMB_RX_PKT_CNT 0x15DC + +/* Number of packet transmitted since last CMB write */ +#define REG_CMB_TX_PKT_CNT 0x15E0 + +/* SMB auto DMA timer register */ +#define REG_SMB_TIMER 0x15E4 + +/* Mailbox Register */ +#define REG_MAILBOX 0x15F0 +#define MB_RFD_PROD_INDX_SHIFT 0 +#define MB_RFD_PROD_INDX_MASK 0x7FF +#define MB_RRD_CONS_INDX_SHIFT 11 +#define MB_RRD_CONS_INDX_MASK 0x7FF +#define MB_TPD_PROD_INDX_SHIFT 22 +#define MB_TPD_PROD_INDX_MASK 0x3FF + +/* Interrupt Status Register */ +#define ISR_SMB 0x1 +#define ISR_TIMER 0x2 +#define ISR_MANUAL 0x4 +#define ISR_RXF_OV 0x8 +#define ISR_RFD_UNRUN 0x10 +#define ISR_RRD_OV 0x20 +#define ISR_TXF_UNRUN 0x40 +#define ISR_LINK 0x80 +#define ISR_HOST_RFD_UNRUN 0x100 +#define ISR_HOST_RRD_OV 0x200 +#define ISR_DMAR_TO_RST 0x400 +#define ISR_DMAW_TO_RST 0x800 +#define ISR_GPHY 0x1000 +#define ISR_RX_PKT 0x10000 +#define ISR_TX_PKT 0x20000 +#define ISR_TX_DMA 0x40000 +#define ISR_RX_DMA 0x80000 +#define ISR_CMB_RX 0x100000 +#define ISR_CMB_TX 0x200000 +#define ISR_MAC_RX 0x400000 +#define ISR_MAC_TX 0x800000 +#define ISR_DIS_SMB 0x20000000 +#define ISR_DIS_DMA 0x40000000 + +/* Normal Interrupt mask without RX/TX enabled */ +#define IMR_NORXTX_MASK (\ + ISR_SMB |\ + ISR_GPHY |\ + ISR_PHY_LINKDOWN|\ + ISR_DMAR_TO_RST |\ + ISR_DMAW_TO_RST) + +/* Normal Interrupt mask */ +#define IMR_NORMAL_MASK (\ + IMR_NORXTX_MASK |\ + ISR_CMB_TX |\ + ISR_CMB_RX) + +/* Debug Interrupt Mask (enable all interrupt) */ +#define IMR_DEBUG_MASK (\ + ISR_SMB |\ + ISR_TIMER |\ + ISR_MANUAL |\ + ISR_RXF_OV |\ + ISR_RFD_UNRUN |\ + ISR_RRD_OV |\ + ISR_TXF_UNRUN |\ + ISR_LINK |\ + ISR_CMB_TX |\ + ISR_CMB_RX |\ + ISR_RX_PKT |\ + ISR_TX_PKT |\ + ISR_MAC_RX |\ + ISR_MAC_TX) + +#define MEDIA_TYPE_1000M_FULL 1 +#define MEDIA_TYPE_100M_FULL 2 +#define MEDIA_TYPE_100M_HALF 3 +#define MEDIA_TYPE_10M_FULL 4 +#define MEDIA_TYPE_10M_HALF 5 + +#define AUTONEG_ADVERTISE_SPEED_DEFAULT 0x002F /* All but 1000-Half */ + +#define MAX_JUMBO_FRAME_SIZE 10240 + +#define ATL1_EEDUMP_LEN 48 + +/* Statistics counters collected by the MAC */ +struct stats_msg_block { + /* rx */ + u32 rx_ok; /* good RX packets */ + u32 rx_bcast; /* good RX broadcast packets */ + u32 rx_mcast; /* good RX multicast packets */ + u32 rx_pause; /* RX pause frames */ + u32 rx_ctrl; /* RX control packets other than pause frames */ + u32 rx_fcs_err; /* RX packets with bad FCS */ + u32 rx_len_err; /* RX packets with length != actual size */ + u32 rx_byte_cnt; /* good bytes received. FCS is NOT included */ + u32 rx_runt; /* RX packets < 64 bytes with good FCS */ + u32 rx_frag; /* RX packets < 64 bytes with bad FCS */ + u32 rx_sz_64; /* 64 byte RX packets */ + u32 rx_sz_65_127; + u32 rx_sz_128_255; + u32 rx_sz_256_511; + u32 rx_sz_512_1023; + u32 rx_sz_1024_1518; + u32 rx_sz_1519_max; /* 1519 byte to MTU RX packets */ + u32 rx_sz_ov; /* truncated RX packets > MTU */ + u32 rx_rxf_ov; /* frames dropped due to RX FIFO overflow */ + u32 rx_rrd_ov; /* frames dropped due to RRD overflow */ + u32 rx_align_err; /* alignment errors */ + u32 rx_bcast_byte_cnt; /* RX broadcast bytes, excluding FCS */ + u32 rx_mcast_byte_cnt; /* RX multicast bytes, excluding FCS */ + u32 rx_err_addr; /* packets dropped due to address filtering */ + + /* tx */ + u32 tx_ok; /* good TX packets */ + u32 tx_bcast; /* good TX broadcast packets */ + u32 tx_mcast; /* good TX multicast packets */ + u32 tx_pause; /* TX pause frames */ + u32 tx_exc_defer; /* TX packets deferred excessively */ + u32 tx_ctrl; /* TX control frames, excluding pause frames */ + u32 tx_defer; /* TX packets deferred */ + u32 tx_byte_cnt; /* bytes transmitted, FCS is NOT included */ + u32 tx_sz_64; /* 64 byte TX packets */ + u32 tx_sz_65_127; + u32 tx_sz_128_255; + u32 tx_sz_256_511; + u32 tx_sz_512_1023; + u32 tx_sz_1024_1518; + u32 tx_sz_1519_max; /* 1519 byte to MTU TX packets */ + u32 tx_1_col; /* packets TX after a single collision */ + u32 tx_2_col; /* packets TX after multiple collisions */ + u32 tx_late_col; /* TX packets with late collisions */ + u32 tx_abort_col; /* TX packets aborted w/excessive collisions */ + u32 tx_underrun; /* TX packets aborted due to TX FIFO underrun + * or TRD FIFO underrun */ + u32 tx_rd_eop; /* reads beyond the EOP into the next frame + * when TRD was not written timely */ + u32 tx_len_err; /* TX packets where length != actual size */ + u32 tx_trunc; /* TX packets truncated due to size > MTU */ + u32 tx_bcast_byte; /* broadcast bytes transmitted, excluding FCS */ + u32 tx_mcast_byte; /* multicast bytes transmitted, excluding FCS */ + u32 smb_updated; /* 1: SMB Updated. This is used by software to + * indicate the statistics update. Software + * should clear this bit after retrieving the + * statistics information. */ +}; + +/* Coalescing Message Block */ +struct coals_msg_block { + u32 int_stats; /* interrupt status */ + u16 rrd_prod_idx; /* TRD Producer Index. */ + u16 rfd_cons_idx; /* RFD Consumer Index. */ + u16 update; /* Selene sets this bit every time it DMAs the + * CMB to host memory. Software should clear + * this bit when CMB info is processed. */ + u16 tpd_cons_idx; /* TPD Consumer Index. */ +}; + +/* RRD descriptor */ +struct rx_return_desc { + u8 num_buf; /* Number of RFD buffers used by the received packet */ + u8 resved; + u16 buf_indx; /* RFD Index of the first buffer */ + union { + u32 valid; + struct { + u16 rx_chksum; + u16 pkt_size; + } xsum_sz; + } xsz; + + u16 pkt_flg; /* Packet flags */ + u16 err_flg; /* Error flags */ + u16 resved2; + u16 vlan_tag; /* VLAN TAG */ +}; + +#define PACKET_FLAG_ETH_TYPE 0x0080 +#define PACKET_FLAG_VLAN_INS 0x0100 +#define PACKET_FLAG_ERR 0x0200 +#define PACKET_FLAG_IPV4 0x0400 +#define PACKET_FLAG_UDP 0x0800 +#define PACKET_FLAG_TCP 0x1000 +#define PACKET_FLAG_BCAST 0x2000 +#define PACKET_FLAG_MCAST 0x4000 +#define PACKET_FLAG_PAUSE 0x8000 + +#define ERR_FLAG_CRC 0x0001 +#define ERR_FLAG_CODE 0x0002 +#define ERR_FLAG_DRIBBLE 0x0004 +#define ERR_FLAG_RUNT 0x0008 +#define ERR_FLAG_OV 0x0010 +#define ERR_FLAG_TRUNC 0x0020 +#define ERR_FLAG_IP_CHKSUM 0x0040 +#define ERR_FLAG_L4_CHKSUM 0x0080 +#define ERR_FLAG_LEN 0x0100 +#define ERR_FLAG_DES_ADDR 0x0200 + +/* RFD descriptor */ +struct rx_free_desc { + __le64 buffer_addr; /* Address of the descriptor's data buffer */ + __le16 buf_len; /* Size of the receive buffer in host memory */ + u16 coalese; /* Update consumer index to host after the + * reception of this frame */ + /* __packed is required */ +} __packed; + +/* + * The L1 transmit packet descriptor is comprised of four 32-bit words. + * + * 31 0 + * +---------------------------------------+ + * | Word 0: Buffer addr lo | + * +---------------------------------------+ + * | Word 1: Buffer addr hi | + * +---------------------------------------+ + * | Word 2 | + * +---------------------------------------+ + * | Word 3 | + * +---------------------------------------+ + * + * Words 0 and 1 combine to form a 64-bit buffer address. + * + * Word 2 is self explanatory in the #define block below. + * + * Word 3 has two forms, depending upon the state of bits 3 and 4. + * If bits 3 and 4 are both zero, then bits 14:31 are unused by the + * hardware. Otherwise, if either bit 3 or 4 is set, the definition + * of bits 14:31 vary according to the following depiction. + * + * 0 End of packet 0 End of packet + * 1 Coalesce 1 Coalesce + * 2 Insert VLAN tag 2 Insert VLAN tag + * 3 Custom csum enable = 0 3 Custom csum enable = 1 + * 4 Segment enable = 1 4 Segment enable = 0 + * 5 Generate IP checksum 5 Generate IP checksum + * 6 Generate TCP checksum 6 Generate TCP checksum + * 7 Generate UDP checksum 7 Generate UDP checksum + * 8 VLAN tagged 8 VLAN tagged + * 9 Ethernet frame type 9 Ethernet frame type + * 10-+ 10-+ + * 11 | IP hdr length (10:13) 11 | IP hdr length (10:13) + * 12 | (num 32-bit words) 12 | (num 32-bit words) + * 13-+ 13-+ + * 14-+ 14 Unused + * 15 | TCP hdr length (14:17) 15 Unused + * 16 | (num 32-bit words) 16-+ + * 17-+ 17 | + * 18 Header TPD flag 18 | + * 19-+ 19 | Payload offset + * 20 | 20 | (16:23) + * 21 | 21 | + * 22 | 22 | + * 23 | 23-+ + * 24 | 24-+ + * 25 | MSS (19:31) 25 | + * 26 | 26 | + * 27 | 27 | Custom csum offset + * 28 | 28 | (24:31) + * 29 | 29 | + * 30 | 30 | + * 31-+ 31-+ + */ + +/* tpd word 2 */ +#define TPD_BUFLEN_MASK 0x3FFF +#define TPD_BUFLEN_SHIFT 0 +#define TPD_DMAINT_MASK 0x0001 +#define TPD_DMAINT_SHIFT 14 +#define TPD_PKTNT_MASK 0x0001 +#define TPD_PKTINT_SHIFT 15 +#define TPD_VLANTAG_MASK 0xFFFF +#define TPD_VLANTAG_SHIFT 16 + +/* tpd word 3 bits 0:13 */ +#define TPD_EOP_MASK 0x0001 +#define TPD_EOP_SHIFT 0 +#define TPD_COALESCE_MASK 0x0001 +#define TPD_COALESCE_SHIFT 1 +#define TPD_INS_VL_TAG_MASK 0x0001 +#define TPD_INS_VL_TAG_SHIFT 2 +#define TPD_CUST_CSUM_EN_MASK 0x0001 +#define TPD_CUST_CSUM_EN_SHIFT 3 +#define TPD_SEGMENT_EN_MASK 0x0001 +#define TPD_SEGMENT_EN_SHIFT 4 +#define TPD_IP_CSUM_MASK 0x0001 +#define TPD_IP_CSUM_SHIFT 5 +#define TPD_TCP_CSUM_MASK 0x0001 +#define TPD_TCP_CSUM_SHIFT 6 +#define TPD_UDP_CSUM_MASK 0x0001 +#define TPD_UDP_CSUM_SHIFT 7 +#define TPD_VL_TAGGED_MASK 0x0001 +#define TPD_VL_TAGGED_SHIFT 8 +#define TPD_ETHTYPE_MASK 0x0001 +#define TPD_ETHTYPE_SHIFT 9 +#define TPD_IPHL_MASK 0x000F +#define TPD_IPHL_SHIFT 10 + +/* tpd word 3 bits 14:31 if segment enabled */ +#define TPD_TCPHDRLEN_MASK 0x000F +#define TPD_TCPHDRLEN_SHIFT 14 +#define TPD_HDRFLAG_MASK 0x0001 +#define TPD_HDRFLAG_SHIFT 18 +#define TPD_MSS_MASK 0x1FFF +#define TPD_MSS_SHIFT 19 + +/* tpd word 3 bits 16:31 if custom csum enabled */ +#define TPD_PLOADOFFSET_MASK 0x00FF +#define TPD_PLOADOFFSET_SHIFT 16 +#define TPD_CCSUMOFFSET_MASK 0x00FF +#define TPD_CCSUMOFFSET_SHIFT 24 + +struct tx_packet_desc { + __le64 buffer_addr; + __le32 word2; + __le32 word3; +}; + +/* DMA Order Settings */ +enum atl1_dma_order { + atl1_dma_ord_in = 1, + atl1_dma_ord_enh = 2, + atl1_dma_ord_out = 4 +}; + +enum atl1_dma_rcb { + atl1_rcb_64 = 0, + atl1_rcb_128 = 1 +}; + +enum atl1_dma_req_block { + atl1_dma_req_128 = 0, + atl1_dma_req_256 = 1, + atl1_dma_req_512 = 2, + atl1_dma_req_1024 = 3, + atl1_dma_req_2048 = 4, + atl1_dma_req_4096 = 5 +}; + +#define ATL1_MAX_INTR 3 +#define ATL1_MAX_TX_BUF_LEN 0x3000 /* 12288 bytes */ + +#define ATL1_DEFAULT_TPD 256 +#define ATL1_MAX_TPD 1024 +#define ATL1_MIN_TPD 64 +#define ATL1_DEFAULT_RFD 512 +#define ATL1_MIN_RFD 128 +#define ATL1_MAX_RFD 2048 +#define ATL1_REG_COUNT 1538 + +#define ATL1_GET_DESC(R, i, type) (&(((type *)((R)->desc))[i])) +#define ATL1_RFD_DESC(R, i) ATL1_GET_DESC(R, i, struct rx_free_desc) +#define ATL1_TPD_DESC(R, i) ATL1_GET_DESC(R, i, struct tx_packet_desc) +#define ATL1_RRD_DESC(R, i) ATL1_GET_DESC(R, i, struct rx_return_desc) + +/* + * atl1_ring_header represents a single, contiguous block of DMA space + * mapped for the three descriptor rings (tpd, rfd, rrd) and the two + * message blocks (cmb, smb) described below + */ +struct atl1_ring_header { + void *desc; /* virtual address */ + dma_addr_t dma; /* physical address*/ + unsigned int size; /* length in bytes */ +}; + +/* + * atl1_buffer is wrapper around a pointer to a socket buffer + * so a DMA handle can be stored along with the skb + */ +struct atl1_buffer { + struct sk_buff *skb; /* socket buffer */ + u16 length; /* rx buffer length */ + u16 alloced; /* 1 if skb allocated */ + dma_addr_t dma; +}; + +/* transmit packet descriptor (tpd) ring */ +struct atl1_tpd_ring { + void *desc; /* descriptor ring virtual address */ + dma_addr_t dma; /* descriptor ring physical address */ + u16 size; /* descriptor ring length in bytes */ + u16 count; /* number of descriptors in the ring */ + u16 hw_idx; /* hardware index */ + atomic_t next_to_clean; + atomic_t next_to_use; + struct atl1_buffer *buffer_info; +}; + +/* receive free descriptor (rfd) ring */ +struct atl1_rfd_ring { + void *desc; /* descriptor ring virtual address */ + dma_addr_t dma; /* descriptor ring physical address */ + u16 size; /* descriptor ring length in bytes */ + u16 count; /* number of descriptors in the ring */ + atomic_t next_to_use; + u16 next_to_clean; + struct atl1_buffer *buffer_info; +}; + +/* receive return descriptor (rrd) ring */ +struct atl1_rrd_ring { + void *desc; /* descriptor ring virtual address */ + dma_addr_t dma; /* descriptor ring physical address */ + unsigned int size; /* descriptor ring length in bytes */ + u16 count; /* number of descriptors in the ring */ + u16 next_to_use; + atomic_t next_to_clean; +}; + +/* coalescing message block (cmb) */ +struct atl1_cmb { + struct coals_msg_block *cmb; + dma_addr_t dma; +}; + +/* statistics message block (smb) */ +struct atl1_smb { + struct stats_msg_block *smb; + dma_addr_t dma; +}; + +/* Statistics counters */ +struct atl1_sft_stats { + u64 rx_packets; + u64 tx_packets; + u64 rx_bytes; + u64 tx_bytes; + u64 multicast; + u64 collisions; + u64 rx_errors; + u64 rx_length_errors; + u64 rx_crc_errors; + u64 rx_frame_errors; + u64 rx_fifo_errors; + u64 rx_missed_errors; + u64 tx_errors; + u64 tx_fifo_errors; + u64 tx_aborted_errors; + u64 tx_window_errors; + u64 tx_carrier_errors; + u64 tx_pause; /* TX pause frames */ + u64 excecol; /* TX packets w/ excessive collisions */ + u64 deffer; /* TX packets deferred */ + u64 scc; /* packets TX after a single collision */ + u64 mcc; /* packets TX after multiple collisions */ + u64 latecol; /* TX packets w/ late collisions */ + u64 tx_underun; /* TX packets aborted due to TX FIFO underrun + * or TRD FIFO underrun */ + u64 tx_trunc; /* TX packets truncated due to size > MTU */ + u64 rx_pause; /* num Pause packets received. */ + u64 rx_rrd_ov; + u64 rx_trunc; +}; + +/* hardware structure */ +struct atl1_hw { + u8 __iomem *hw_addr; + struct atl1_adapter *back; + enum atl1_dma_order dma_ord; + enum atl1_dma_rcb rcb_value; + enum atl1_dma_req_block dmar_block; + enum atl1_dma_req_block dmaw_block; + u8 preamble_len; + u8 max_retry; + u8 jam_ipg; /* IPG to start JAM for collision based flow + * control in half-duplex mode. In units of + * 8-bit time */ + u8 ipgt; /* Desired back to back inter-packet gap. + * The default is 96-bit time */ + u8 min_ifg; /* Minimum number of IFG to enforce in between + * receive frames. Frame gap below such IFP + * is dropped */ + u8 ipgr1; /* 64bit Carrier-Sense window */ + u8 ipgr2; /* 96-bit IPG window */ + u8 tpd_burst; /* Number of TPD to prefetch in cache-aligned + * burst. Each TPD is 16 bytes long */ + u8 rfd_burst; /* Number of RFD to prefetch in cache-aligned + * burst. Each RFD is 12 bytes long */ + u8 rfd_fetch_gap; + u8 rrd_burst; /* Threshold number of RRDs that can be retired + * in a burst. Each RRD is 16 bytes long */ + u8 tpd_fetch_th; + u8 tpd_fetch_gap; + u16 tx_jumbo_task_th; + u16 txf_burst; /* Number of data bytes to read in a cache- + * aligned burst. Each SRAM entry is 8 bytes */ + u16 rx_jumbo_th; /* Jumbo packet size for non-VLAN packet. VLAN + * packets should add 4 bytes */ + u16 rx_jumbo_lkah; + u16 rrd_ret_timer; /* RRD retirement timer. Decrement by 1 after + * every 512ns passes. */ + u16 lcol; /* Collision Window */ + + u16 cmb_tpd; + u16 cmb_rrd; + u16 cmb_rx_timer; + u16 cmb_tx_timer; + u32 smb_timer; + u16 media_type; + u16 autoneg_advertised; + + u16 mii_autoneg_adv_reg; + u16 mii_1000t_ctrl_reg; + + u32 max_frame_size; + u32 min_frame_size; + + u16 dev_rev; + + /* spi flash */ + u8 flash_vendor; + + u8 mac_addr[ETH_ALEN]; + u8 perm_mac_addr[ETH_ALEN]; + + bool phy_configured; +}; + +struct atl1_adapter { + struct net_device *netdev; + struct pci_dev *pdev; + + struct atl1_sft_stats soft_stats; + u32 rx_buffer_len; + u32 wol; + u16 link_speed; + u16 link_duplex; + spinlock_t lock; + struct napi_struct napi; + struct work_struct reset_dev_task; + struct work_struct link_chg_task; + + struct timer_list phy_config_timer; + bool phy_timer_pending; + + /* all descriptor rings' memory */ + struct atl1_ring_header ring_header; + + /* TX */ + struct atl1_tpd_ring tpd_ring; + spinlock_t mb_lock; + + /* RX */ + struct atl1_rfd_ring rfd_ring; + struct atl1_rrd_ring rrd_ring; + u64 hw_csum_err; + u64 hw_csum_good; + u32 msg_enable; + u16 imt; /* interrupt moderator timer (2us resolution) */ + u16 ict; /* interrupt clear timer (2us resolution */ + struct mii_if_info mii; /* MII interface info */ + + /* + * Use this value to check is napi handler allowed to + * enable ints or not + */ + bool int_enabled; + + u32 bd_number; /* board number */ + bool pci_using_64; + struct atl1_hw hw; + struct atl1_smb smb; + struct atl1_cmb cmb; +}; + +#endif /* ATL1_H */ diff --git a/drivers/net/ethernet/atheros/atlx/atl2.c b/drivers/net/ethernet/atheros/atlx/atl2.c new file mode 100644 index 0000000..62f9c36 --- /dev/null +++ b/drivers/net/ethernet/atheros/atlx/atl2.c @@ -0,0 +1,3129 @@ +/* + * Copyright(c) 2006 - 2007 Atheros Corporation. All rights reserved. + * Copyright(c) 2007 - 2008 Chris Snook + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atl2.h" + +#define ATL2_DRV_VERSION "2.2.3" + +static const char atl2_driver_name[] = "atl2"; +static const char atl2_driver_string[] = "Atheros(R) L2 Ethernet Driver"; +static const char atl2_copyright[] = "Copyright (c) 2007 Atheros Corporation."; +static const char atl2_driver_version[] = ATL2_DRV_VERSION; + +MODULE_AUTHOR("Atheros Corporation , Chris Snook "); +MODULE_DESCRIPTION("Atheros Fast Ethernet Network Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(ATL2_DRV_VERSION); + +/* + * atl2_pci_tbl - PCI Device ID Table + */ +static DEFINE_PCI_DEVICE_TABLE(atl2_pci_tbl) = { + {PCI_DEVICE(PCI_VENDOR_ID_ATTANSIC, PCI_DEVICE_ID_ATTANSIC_L2)}, + /* required last entry */ + {0,} +}; +MODULE_DEVICE_TABLE(pci, atl2_pci_tbl); + +static void atl2_set_ethtool_ops(struct net_device *netdev); + +static void atl2_check_options(struct atl2_adapter *adapter); + +/** + * atl2_sw_init - Initialize general software structures (struct atl2_adapter) + * @adapter: board private structure to initialize + * + * atl2_sw_init initializes the Adapter private data structure. + * Fields are initialized based on PCI device information and + * OS network device settings (MTU size). + */ +static int atl2_sw_init(struct atl2_adapter *adapter) +{ + struct atl2_hw *hw = &adapter->hw; + struct pci_dev *pdev = adapter->pdev; + + /* PCI config space info */ + hw->vendor_id = pdev->vendor; + hw->device_id = pdev->device; + hw->subsystem_vendor_id = pdev->subsystem_vendor; + hw->subsystem_id = pdev->subsystem_device; + hw->revision_id = pdev->revision; + + pci_read_config_word(pdev, PCI_COMMAND, &hw->pci_cmd_word); + + adapter->wol = 0; + adapter->ict = 50000; /* ~100ms */ + adapter->link_speed = SPEED_0; /* hardware init */ + adapter->link_duplex = FULL_DUPLEX; + + hw->phy_configured = false; + hw->preamble_len = 7; + hw->ipgt = 0x60; + hw->min_ifg = 0x50; + hw->ipgr1 = 0x40; + hw->ipgr2 = 0x60; + hw->retry_buf = 2; + hw->max_retry = 0xf; + hw->lcol = 0x37; + hw->jam_ipg = 7; + hw->fc_rxd_hi = 0; + hw->fc_rxd_lo = 0; + hw->max_frame_size = adapter->netdev->mtu; + + spin_lock_init(&adapter->stats_lock); + + set_bit(__ATL2_DOWN, &adapter->flags); + + return 0; +} + +/** + * atl2_set_multi - Multicast and Promiscuous mode set + * @netdev: network interface device structure + * + * The set_multi entry point is called whenever the multicast address + * list or the network interface flags are updated. This routine is + * responsible for configuring the hardware for proper multicast, + * promiscuous mode, and all-multi behavior. + */ +static void atl2_set_multi(struct net_device *netdev) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + struct atl2_hw *hw = &adapter->hw; + struct netdev_hw_addr *ha; + u32 rctl; + u32 hash_value; + + /* Check for Promiscuous and All Multicast modes */ + rctl = ATL2_READ_REG(hw, REG_MAC_CTRL); + + if (netdev->flags & IFF_PROMISC) { + rctl |= MAC_CTRL_PROMIS_EN; + } else if (netdev->flags & IFF_ALLMULTI) { + rctl |= MAC_CTRL_MC_ALL_EN; + rctl &= ~MAC_CTRL_PROMIS_EN; + } else + rctl &= ~(MAC_CTRL_PROMIS_EN | MAC_CTRL_MC_ALL_EN); + + ATL2_WRITE_REG(hw, REG_MAC_CTRL, rctl); + + /* clear the old settings from the multicast hash table */ + ATL2_WRITE_REG(hw, REG_RX_HASH_TABLE, 0); + ATL2_WRITE_REG_ARRAY(hw, REG_RX_HASH_TABLE, 1, 0); + + /* comoute mc addresses' hash value ,and put it into hash table */ + netdev_for_each_mc_addr(ha, netdev) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + hash_value = atl2_hash_mc_addr(hw, ha->addr); +#else + hash_value = atl2_hash_mc_addr(hw, ha->dmi_addr); +#endif + atl2_hash_set(hw, hash_value); + } +} + +static void init_ring_ptrs(struct atl2_adapter *adapter) +{ + /* Read / Write Ptr Initialize: */ + adapter->txd_write_ptr = 0; + atomic_set(&adapter->txd_read_ptr, 0); + + adapter->rxd_read_ptr = 0; + adapter->rxd_write_ptr = 0; + + atomic_set(&adapter->txs_write_ptr, 0); + adapter->txs_next_clear = 0; +} + +/** + * atl2_configure - Configure Transmit&Receive Unit after Reset + * @adapter: board private structure + * + * Configure the Tx /Rx unit of the MAC after a reset. + */ +static int atl2_configure(struct atl2_adapter *adapter) +{ + struct atl2_hw *hw = &adapter->hw; + u32 value; + + /* clear interrupt status */ + ATL2_WRITE_REG(&adapter->hw, REG_ISR, 0xffffffff); + + /* set MAC Address */ + value = (((u32)hw->mac_addr[2]) << 24) | + (((u32)hw->mac_addr[3]) << 16) | + (((u32)hw->mac_addr[4]) << 8) | + (((u32)hw->mac_addr[5])); + ATL2_WRITE_REG(hw, REG_MAC_STA_ADDR, value); + value = (((u32)hw->mac_addr[0]) << 8) | + (((u32)hw->mac_addr[1])); + ATL2_WRITE_REG(hw, (REG_MAC_STA_ADDR+4), value); + + /* HI base address */ + ATL2_WRITE_REG(hw, REG_DESC_BASE_ADDR_HI, + (u32)((adapter->ring_dma & 0xffffffff00000000ULL) >> 32)); + + /* LO base address */ + ATL2_WRITE_REG(hw, REG_TXD_BASE_ADDR_LO, + (u32)(adapter->txd_dma & 0x00000000ffffffffULL)); + ATL2_WRITE_REG(hw, REG_TXS_BASE_ADDR_LO, + (u32)(adapter->txs_dma & 0x00000000ffffffffULL)); + ATL2_WRITE_REG(hw, REG_RXD_BASE_ADDR_LO, + (u32)(adapter->rxd_dma & 0x00000000ffffffffULL)); + + /* element count */ + ATL2_WRITE_REGW(hw, REG_TXD_MEM_SIZE, (u16)(adapter->txd_ring_size/4)); + ATL2_WRITE_REGW(hw, REG_TXS_MEM_SIZE, (u16)adapter->txs_ring_size); + ATL2_WRITE_REGW(hw, REG_RXD_BUF_NUM, (u16)adapter->rxd_ring_size); + + /* config Internal SRAM */ +/* + ATL2_WRITE_REGW(hw, REG_SRAM_TXRAM_END, sram_tx_end); + ATL2_WRITE_REGW(hw, REG_SRAM_TXRAM_END, sram_rx_end); +*/ + + /* config IPG/IFG */ + value = (((u32)hw->ipgt & MAC_IPG_IFG_IPGT_MASK) << + MAC_IPG_IFG_IPGT_SHIFT) | + (((u32)hw->min_ifg & MAC_IPG_IFG_MIFG_MASK) << + MAC_IPG_IFG_MIFG_SHIFT) | + (((u32)hw->ipgr1 & MAC_IPG_IFG_IPGR1_MASK) << + MAC_IPG_IFG_IPGR1_SHIFT)| + (((u32)hw->ipgr2 & MAC_IPG_IFG_IPGR2_MASK) << + MAC_IPG_IFG_IPGR2_SHIFT); + ATL2_WRITE_REG(hw, REG_MAC_IPG_IFG, value); + + /* config Half-Duplex Control */ + value = ((u32)hw->lcol & MAC_HALF_DUPLX_CTRL_LCOL_MASK) | + (((u32)hw->max_retry & MAC_HALF_DUPLX_CTRL_RETRY_MASK) << + MAC_HALF_DUPLX_CTRL_RETRY_SHIFT) | + MAC_HALF_DUPLX_CTRL_EXC_DEF_EN | + (0xa << MAC_HALF_DUPLX_CTRL_ABEBT_SHIFT) | + (((u32)hw->jam_ipg & MAC_HALF_DUPLX_CTRL_JAMIPG_MASK) << + MAC_HALF_DUPLX_CTRL_JAMIPG_SHIFT); + ATL2_WRITE_REG(hw, REG_MAC_HALF_DUPLX_CTRL, value); + + /* set Interrupt Moderator Timer */ + ATL2_WRITE_REGW(hw, REG_IRQ_MODU_TIMER_INIT, adapter->imt); + ATL2_WRITE_REG(hw, REG_MASTER_CTRL, MASTER_CTRL_ITIMER_EN); + + /* set Interrupt Clear Timer */ + ATL2_WRITE_REGW(hw, REG_CMBDISDMA_TIMER, adapter->ict); + + /* set MTU */ + ATL2_WRITE_REG(hw, REG_MTU, adapter->netdev->mtu + + ENET_HEADER_SIZE + VLAN_SIZE + ETHERNET_FCS_SIZE); + + /* 1590 */ + ATL2_WRITE_REG(hw, REG_TX_CUT_THRESH, 0x177); + + /* flow control */ + ATL2_WRITE_REGW(hw, REG_PAUSE_ON_TH, hw->fc_rxd_hi); + ATL2_WRITE_REGW(hw, REG_PAUSE_OFF_TH, hw->fc_rxd_lo); + + /* Init mailbox */ + ATL2_WRITE_REGW(hw, REG_MB_TXD_WR_IDX, (u16)adapter->txd_write_ptr); + ATL2_WRITE_REGW(hw, REG_MB_RXD_RD_IDX, (u16)adapter->rxd_read_ptr); + + /* enable DMA read/write */ + ATL2_WRITE_REGB(hw, REG_DMAR, DMAR_EN); + ATL2_WRITE_REGB(hw, REG_DMAW, DMAW_EN); + + value = ATL2_READ_REG(&adapter->hw, REG_ISR); + if ((value & ISR_PHY_LINKDOWN) != 0) + value = 1; /* config failed */ + else + value = 0; + + /* clear all interrupt status */ + ATL2_WRITE_REG(&adapter->hw, REG_ISR, 0x3fffffff); + ATL2_WRITE_REG(&adapter->hw, REG_ISR, 0); + return value; +} + +/** + * atl2_setup_ring_resources - allocate Tx / RX descriptor resources + * @adapter: board private structure + * + * Return 0 on success, negative on failure + */ +static s32 atl2_setup_ring_resources(struct atl2_adapter *adapter) +{ + struct pci_dev *pdev = adapter->pdev; + int size; + u8 offset = 0; + + /* real ring DMA buffer */ + adapter->ring_size = size = + adapter->txd_ring_size * 1 + 7 + /* dword align */ + adapter->txs_ring_size * 4 + 7 + /* dword align */ + adapter->rxd_ring_size * 1536 + 127; /* 128bytes align */ + + adapter->ring_vir_addr = pci_alloc_consistent(pdev, size, + &adapter->ring_dma); + if (!adapter->ring_vir_addr) + return -ENOMEM; + memset(adapter->ring_vir_addr, 0, adapter->ring_size); + + /* Init TXD Ring */ + adapter->txd_dma = adapter->ring_dma ; + offset = (adapter->txd_dma & 0x7) ? (8 - (adapter->txd_dma & 0x7)) : 0; + adapter->txd_dma += offset; + adapter->txd_ring = adapter->ring_vir_addr + offset; + + /* Init TXS Ring */ + adapter->txs_dma = adapter->txd_dma + adapter->txd_ring_size; + offset = (adapter->txs_dma & 0x7) ? (8 - (adapter->txs_dma & 0x7)) : 0; + adapter->txs_dma += offset; + adapter->txs_ring = (struct tx_pkt_status *) + (((u8 *)adapter->txd_ring) + (adapter->txd_ring_size + offset)); + + /* Init RXD Ring */ + adapter->rxd_dma = adapter->txs_dma + adapter->txs_ring_size * 4; + offset = (adapter->rxd_dma & 127) ? + (128 - (adapter->rxd_dma & 127)) : 0; + if (offset > 7) + offset -= 8; + else + offset += (128 - 8); + + adapter->rxd_dma += offset; + adapter->rxd_ring = (struct rx_desc *) (((u8 *)adapter->txs_ring) + + (adapter->txs_ring_size * 4 + offset)); + +/* + * Read / Write Ptr Initialize: + * init_ring_ptrs(adapter); + */ + return 0; +} + +/** + * atl2_irq_enable - Enable default interrupt generation settings + * @adapter: board private structure + */ +static inline void atl2_irq_enable(struct atl2_adapter *adapter) +{ + ATL2_WRITE_REG(&adapter->hw, REG_IMR, IMR_NORMAL_MASK); + ATL2_WRITE_FLUSH(&adapter->hw); +} + +/** + * atl2_irq_disable - Mask off interrupt generation on the NIC + * @adapter: board private structure + */ +static inline void atl2_irq_disable(struct atl2_adapter *adapter) +{ + ATL2_WRITE_REG(&adapter->hw, REG_IMR, 0); + ATL2_WRITE_FLUSH(&adapter->hw); + synchronize_irq(adapter->pdev->irq); +} + +#if defined(NETIF_F_HW_VLAN_TX) || (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) +static void __atl2_vlan_mode(netdev_features_t features, u32 *ctrl) +{ + if (features & NETIF_F_HW_VLAN_CTAG_RX) { + /* enable VLAN tag insert/strip */ + *ctrl |= MAC_CTRL_RMV_VLAN; + } else { + /* disable VLAN tag insert/strip */ + *ctrl &= ~MAC_CTRL_RMV_VLAN; + } +} + +static void atl2_vlan_mode(struct net_device *netdev, + netdev_features_t features) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + u32 ctrl; + + atl2_irq_disable(adapter); + + ctrl = ATL2_READ_REG(&adapter->hw, REG_MAC_CTRL); + __atl2_vlan_mode(features, &ctrl); + ATL2_WRITE_REG(&adapter->hw, REG_MAC_CTRL, ctrl); + + atl2_irq_enable(adapter); +} +#endif + +static void atl2_restore_vlan(struct atl2_adapter *adapter) +{ +#if defined(NETIF_F_HW_VLAN_TX) || (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + atl2_vlan_mode(adapter->netdev, adapter->netdev->features); +#endif +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) +static netdev_features_t atl2_fix_features(struct net_device *netdev, + netdev_features_t features) +{ + /* + * Since there is no support for separate rx/tx vlan accel + * enable/disable make sure tx flag is always in same state as rx. + */ + if (features & NETIF_F_HW_VLAN_CTAG_RX) + features |= NETIF_F_HW_VLAN_CTAG_TX; + else + features &= ~NETIF_F_HW_VLAN_CTAG_TX; + + return features; +} + +static int atl2_set_features(struct net_device *netdev, + netdev_features_t features) +{ + netdev_features_t changed = netdev->features ^ features; + + if (changed & NETIF_F_HW_VLAN_CTAG_RX) + atl2_vlan_mode(netdev, features); + + return 0; +} +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */ + +static void atl2_intr_rx(struct atl2_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + struct rx_desc *rxd; + struct sk_buff *skb; + + do { + rxd = adapter->rxd_ring+adapter->rxd_write_ptr; + if (!rxd->status.update) + break; /* end of tx */ + + /* clear this flag at once */ + rxd->status.update = 0; + + if (rxd->status.ok && rxd->status.pkt_size >= 60) { + int rx_size = (int)(rxd->status.pkt_size - 4); + /* alloc new buffer */ + skb = netdev_alloc_skb_ip_align(netdev, rx_size); + if (NULL == skb) { + /* + * Check that some rx space is free. If not, + * free one and mark stats->rx_dropped++. + */ + netdev->stats.rx_dropped++; + break; + } + memcpy(skb->data, rxd->packet, rx_size); + skb_put(skb, rx_size); + skb->protocol = eth_type_trans(skb, netdev); + if (rxd->status.vlan) { + u16 vlan_tag = (rxd->status.vtag>>4) | + ((rxd->status.vtag&7) << 13) | + ((rxd->status.vtag&8) << 9); + + __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan_tag); + } + netif_rx(skb); + netdev->stats.rx_bytes += rx_size; + netdev->stats.rx_packets++; + } else { + netdev->stats.rx_errors++; + + if (rxd->status.ok && rxd->status.pkt_size <= 60) + netdev->stats.rx_length_errors++; + if (rxd->status.mcast) + netdev->stats.multicast++; + if (rxd->status.crc) + netdev->stats.rx_crc_errors++; + if (rxd->status.align) + netdev->stats.rx_frame_errors++; + } + + /* advance write ptr */ + if (++adapter->rxd_write_ptr == adapter->rxd_ring_size) + adapter->rxd_write_ptr = 0; + } while (1); + + /* update mailbox? */ + adapter->rxd_read_ptr = adapter->rxd_write_ptr; + ATL2_WRITE_REGW(&adapter->hw, REG_MB_RXD_RD_IDX, adapter->rxd_read_ptr); +} + +static void atl2_intr_tx(struct atl2_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + u32 txd_read_ptr; + u32 txs_write_ptr; + struct tx_pkt_status *txs; + struct tx_pkt_header *txph; + int free_hole = 0; + + do { + txs_write_ptr = (u32) atomic_read(&adapter->txs_write_ptr); + txs = adapter->txs_ring + txs_write_ptr; + if (!txs->update) + break; /* tx stop here */ + + free_hole = 1; + txs->update = 0; + + if (++txs_write_ptr == adapter->txs_ring_size) + txs_write_ptr = 0; + atomic_set(&adapter->txs_write_ptr, (int)txs_write_ptr); + + txd_read_ptr = (u32) atomic_read(&adapter->txd_read_ptr); + txph = (struct tx_pkt_header *) + (((u8 *)adapter->txd_ring) + txd_read_ptr); + + if (txph->pkt_size != txs->pkt_size) { + struct tx_pkt_status *old_txs = txs; + printk(KERN_WARNING + "%s: txs packet size not consistent with txd" + " txd_:0x%08x, txs_:0x%08x!\n", + adapter->netdev->name, + *(u32 *)txph, *(u32 *)txs); + printk(KERN_WARNING + "txd read ptr: 0x%x\n", + txd_read_ptr); + txs = adapter->txs_ring + txs_write_ptr; + printk(KERN_WARNING + "txs-behind:0x%08x\n", + *(u32 *)txs); + if (txs_write_ptr < 2) { + txs = adapter->txs_ring + + (adapter->txs_ring_size + + txs_write_ptr - 2); + } else { + txs = adapter->txs_ring + (txs_write_ptr - 2); + } + printk(KERN_WARNING + "txs-before:0x%08x\n", + *(u32 *)txs); + txs = old_txs; + } + + /* 4for TPH */ + txd_read_ptr += (((u32)(txph->pkt_size) + 7) & ~3); + if (txd_read_ptr >= adapter->txd_ring_size) + txd_read_ptr -= adapter->txd_ring_size; + + atomic_set(&adapter->txd_read_ptr, (int)txd_read_ptr); + + /* tx statistics: */ + if (txs->ok) { + netdev->stats.tx_bytes += txs->pkt_size; + netdev->stats.tx_packets++; + } + else + netdev->stats.tx_errors++; + + if (txs->defer) + netdev->stats.collisions++; + if (txs->abort_col) + netdev->stats.tx_aborted_errors++; + if (txs->late_col) + netdev->stats.tx_window_errors++; + if (txs->underun) + netdev->stats.tx_fifo_errors++; + } while (1); + + if (free_hole) { + if (netif_queue_stopped(adapter->netdev) && + netif_carrier_ok(adapter->netdev)) + netif_wake_queue(adapter->netdev); + } +} + +static void atl2_check_for_link(struct atl2_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + u16 phy_data = 0; + + spin_lock(&adapter->stats_lock); + atl2_read_phy_reg(&adapter->hw, MII_BMSR, &phy_data); + atl2_read_phy_reg(&adapter->hw, MII_BMSR, &phy_data); + spin_unlock(&adapter->stats_lock); + + /* notify upper layer link down ASAP */ + if (!(phy_data & BMSR_LSTATUS)) { /* Link Down */ + if (netif_carrier_ok(netdev)) { /* old link state: Up */ + printk(KERN_INFO "%s: %s NIC Link is Down\n", + atl2_driver_name, netdev->name); + adapter->link_speed = SPEED_0; + netif_carrier_off(netdev); + netif_stop_queue(netdev); + } + } + schedule_work(&adapter->link_chg_task); +} + +static inline void atl2_clear_phy_int(struct atl2_adapter *adapter) +{ + u16 phy_data; + spin_lock(&adapter->stats_lock); + atl2_read_phy_reg(&adapter->hw, 19, &phy_data); + spin_unlock(&adapter->stats_lock); +} + +/** + * atl2_intr - Interrupt Handler + * @irq: interrupt number + * @data: pointer to a network interface device structure + */ +static irqreturn_t atl2_intr(int irq, void *data) +{ + struct atl2_adapter *adapter = netdev_priv(data); + struct atl2_hw *hw = &adapter->hw; + u32 status; + + status = ATL2_READ_REG(hw, REG_ISR); + if (0 == status) + return IRQ_NONE; + + /* link event */ + if (status & ISR_PHY) + atl2_clear_phy_int(adapter); + + /* clear ISR status, and Enable CMB DMA/Disable Interrupt */ + ATL2_WRITE_REG(hw, REG_ISR, status | ISR_DIS_INT); + + /* check if PCIE PHY Link down */ + if (status & ISR_PHY_LINKDOWN) { + if (netif_running(adapter->netdev)) { /* reset MAC */ + ATL2_WRITE_REG(hw, REG_ISR, 0); + ATL2_WRITE_REG(hw, REG_IMR, 0); + ATL2_WRITE_FLUSH(hw); + schedule_work(&adapter->reset_task); + return IRQ_HANDLED; + } + } + + /* check if DMA read/write error? */ + if (status & (ISR_DMAR_TO_RST | ISR_DMAW_TO_RST)) { + ATL2_WRITE_REG(hw, REG_ISR, 0); + ATL2_WRITE_REG(hw, REG_IMR, 0); + ATL2_WRITE_FLUSH(hw); + schedule_work(&adapter->reset_task); + return IRQ_HANDLED; + } + + /* link event */ + if (status & (ISR_PHY | ISR_MANUAL)) { + adapter->netdev->stats.tx_carrier_errors++; + atl2_check_for_link(adapter); + } + + /* transmit event */ + if (status & ISR_TX_EVENT) + atl2_intr_tx(adapter); + + /* rx exception */ + if (status & ISR_RX_EVENT) + atl2_intr_rx(adapter); + + /* re-enable Interrupt */ + ATL2_WRITE_REG(&adapter->hw, REG_ISR, 0); + return IRQ_HANDLED; +} + +static int atl2_request_irq(struct atl2_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + int flags, err = 0; + + flags = IRQF_SHARED; + adapter->have_msi = true; + err = pci_enable_msi(adapter->pdev); + if (err) + adapter->have_msi = false; + + if (adapter->have_msi) + flags &= ~IRQF_SHARED; + + return request_irq(adapter->pdev->irq, atl2_intr, flags, netdev->name, + netdev); +} + +/** + * atl2_free_ring_resources - Free Tx / RX descriptor Resources + * @adapter: board private structure + * + * Free all transmit software resources + */ +static void atl2_free_ring_resources(struct atl2_adapter *adapter) +{ + struct pci_dev *pdev = adapter->pdev; + pci_free_consistent(pdev, adapter->ring_size, adapter->ring_vir_addr, + adapter->ring_dma); +} + +/** + * atl2_open - Called when a network interface is made active + * @netdev: network interface device structure + * + * Returns 0 on success, negative value on failure + * + * The open entry point is called when a network interface is made + * active by the system (IFF_UP). At this point all resources needed + * for transmit and receive operations are allocated, the interrupt + * handler is registered with the OS, the watchdog timer is started, + * and the stack is notified that the interface is ready. + */ +static int atl2_open(struct net_device *netdev) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + int err; + u32 val; + + /* disallow open during test */ + if (test_bit(__ATL2_TESTING, &adapter->flags)) + return -EBUSY; + + /* allocate transmit descriptors */ + err = atl2_setup_ring_resources(adapter); + if (err) + return err; + + err = atl2_init_hw(&adapter->hw); + if (err) { + err = -EIO; + goto err_init_hw; + } + + /* hardware has been reset, we need to reload some things */ + atl2_set_multi(netdev); + init_ring_ptrs(adapter); + + atl2_restore_vlan(adapter); + + if (atl2_configure(adapter)) { + err = -EIO; + goto err_config; + } + + err = atl2_request_irq(adapter); + if (err) + goto err_req_irq; + + clear_bit(__ATL2_DOWN, &adapter->flags); + + mod_timer(&adapter->watchdog_timer, round_jiffies(jiffies + 4*HZ)); + + val = ATL2_READ_REG(&adapter->hw, REG_MASTER_CTRL); + ATL2_WRITE_REG(&adapter->hw, REG_MASTER_CTRL, + val | MASTER_CTRL_MANUAL_INT); + + atl2_irq_enable(adapter); + + return 0; + +err_init_hw: +err_req_irq: +err_config: + atl2_free_ring_resources(adapter); + atl2_reset_hw(&adapter->hw); + + return err; +} + +static void atl2_down(struct atl2_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + + /* signal that we're down so the interrupt handler does not + * reschedule our watchdog timer */ + set_bit(__ATL2_DOWN, &adapter->flags); + + netif_tx_disable(netdev); + + /* reset MAC to disable all RX/TX */ + atl2_reset_hw(&adapter->hw); + msleep(1); + + atl2_irq_disable(adapter); + + del_timer_sync(&adapter->watchdog_timer); + del_timer_sync(&adapter->phy_config_timer); + clear_bit(0, &adapter->cfg_phy); + + netif_carrier_off(netdev); + adapter->link_speed = SPEED_0; + adapter->link_duplex = -1; +} + +static void atl2_free_irq(struct atl2_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + + free_irq(adapter->pdev->irq, netdev); + +#ifdef CONFIG_PCI_MSI + if (adapter->have_msi) + pci_disable_msi(adapter->pdev); +#endif +} + +/** + * atl2_close - Disables a network interface + * @netdev: network interface device structure + * + * Returns 0, this is not allowed to fail + * + * The close entry point is called when an interface is de-activated + * by the OS. The hardware is still under the drivers control, but + * needs to be disabled. A global MAC reset is issued to stop the + * hardware, and all transmit and receive resources are freed. + */ +static int atl2_close(struct net_device *netdev) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + + WARN_ON(test_bit(__ATL2_RESETTING, &adapter->flags)); + + atl2_down(adapter); + atl2_free_irq(adapter); + atl2_free_ring_resources(adapter); + + return 0; +} + +static inline int TxsFreeUnit(struct atl2_adapter *adapter) +{ + u32 txs_write_ptr = (u32) atomic_read(&adapter->txs_write_ptr); + + return (adapter->txs_next_clear >= txs_write_ptr) ? + (int) (adapter->txs_ring_size - adapter->txs_next_clear + + txs_write_ptr - 1) : + (int) (txs_write_ptr - adapter->txs_next_clear - 1); +} + +static inline int TxdFreeBytes(struct atl2_adapter *adapter) +{ + u32 txd_read_ptr = (u32)atomic_read(&adapter->txd_read_ptr); + + return (adapter->txd_write_ptr >= txd_read_ptr) ? + (int) (adapter->txd_ring_size - adapter->txd_write_ptr + + txd_read_ptr - 1) : + (int) (txd_read_ptr - adapter->txd_write_ptr - 1); +} + +static netdev_tx_t atl2_xmit_frame(struct sk_buff *skb, + struct net_device *netdev) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + struct tx_pkt_header *txph; + u32 offset, copy_len; + int txs_unused; + int txbuf_unused; + + if (test_bit(__ATL2_DOWN, &adapter->flags)) { + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + + if (unlikely(skb->len <= 0)) { + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + + txs_unused = TxsFreeUnit(adapter); + txbuf_unused = TxdFreeBytes(adapter); + + if (skb->len + sizeof(struct tx_pkt_header) + 4 > txbuf_unused || + txs_unused < 1) { + /* not enough resources */ + netif_stop_queue(netdev); + return NETDEV_TX_BUSY; + } + + offset = adapter->txd_write_ptr; + + txph = (struct tx_pkt_header *) (((u8 *)adapter->txd_ring) + offset); + + *(u32 *)txph = 0; + txph->pkt_size = skb->len; + + offset += 4; + if (offset >= adapter->txd_ring_size) + offset -= adapter->txd_ring_size; + copy_len = adapter->txd_ring_size - offset; + if (copy_len >= skb->len) { + memcpy(((u8 *)adapter->txd_ring) + offset, skb->data, skb->len); + offset += ((u32)(skb->len + 3) & ~3); + } else { + memcpy(((u8 *)adapter->txd_ring)+offset, skb->data, copy_len); + memcpy((u8 *)adapter->txd_ring, skb->data+copy_len, + skb->len-copy_len); + offset = ((u32)(skb->len-copy_len + 3) & ~3); + } +#ifdef NETIF_F_HW_VLAN_CTAG_TX + if (vlan_tx_tag_present(skb)) { + u16 vlan_tag = vlan_tx_tag_get(skb); + vlan_tag = (vlan_tag << 4) | + (vlan_tag >> 13) | + ((vlan_tag >> 9) & 0x8); + txph->ins_vlan = 1; + txph->vlan = vlan_tag; + } +#endif + if (offset >= adapter->txd_ring_size) + offset -= adapter->txd_ring_size; + adapter->txd_write_ptr = offset; + + /* clear txs before send */ + adapter->txs_ring[adapter->txs_next_clear].update = 0; + if (++adapter->txs_next_clear == adapter->txs_ring_size) + adapter->txs_next_clear = 0; + + ATL2_WRITE_REGW(&adapter->hw, REG_MB_TXD_WR_IDX, + (adapter->txd_write_ptr >> 2)); + + mmiowb(); + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; +} + +/** + * atl2_change_mtu - Change the Maximum Transfer Unit + * @netdev: network interface device structure + * @new_mtu: new value for maximum frame size + * + * Returns 0 on success, negative on failure + */ +static int atl2_change_mtu(struct net_device *netdev, int new_mtu) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + struct atl2_hw *hw = &adapter->hw; + + if ((new_mtu < 40) || (new_mtu > (ETH_DATA_LEN + VLAN_SIZE))) + return -EINVAL; + + /* set MTU */ + if (hw->max_frame_size != new_mtu) { + netdev->mtu = new_mtu; + ATL2_WRITE_REG(hw, REG_MTU, new_mtu + ENET_HEADER_SIZE + + VLAN_SIZE + ETHERNET_FCS_SIZE); + } + + return 0; +} + +/** + * atl2_set_mac - Change the Ethernet Address of the NIC + * @netdev: network interface device structure + * @p: pointer to an address structure + * + * Returns 0 on success, negative on failure + */ +static int atl2_set_mac(struct net_device *netdev, void *p) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + struct sockaddr *addr = p; + + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + + if (netif_running(netdev)) + return -EBUSY; + + memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len); + memcpy(adapter->hw.mac_addr, addr->sa_data, netdev->addr_len); + + atl2_set_mac_addr(&adapter->hw); + + return 0; +} + +static int atl2_mii_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + struct mii_ioctl_data *data = if_mii(ifr); + unsigned long flags; + + switch (cmd) { + case SIOCGMIIPHY: + data->phy_id = 0; + break; + case SIOCGMIIREG: + spin_lock_irqsave(&adapter->stats_lock, flags); + if (atl2_read_phy_reg(&adapter->hw, + data->reg_num & 0x1F, &data->val_out)) { + spin_unlock_irqrestore(&adapter->stats_lock, flags); + return -EIO; + } + spin_unlock_irqrestore(&adapter->stats_lock, flags); + break; + case SIOCSMIIREG: + if (data->reg_num & ~(0x1F)) + return -EFAULT; + spin_lock_irqsave(&adapter->stats_lock, flags); + if (atl2_write_phy_reg(&adapter->hw, data->reg_num, + data->val_in)) { + spin_unlock_irqrestore(&adapter->stats_lock, flags); + return -EIO; + } + spin_unlock_irqrestore(&adapter->stats_lock, flags); + break; + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int atl2_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd) +{ + switch (cmd) { + case SIOCGMIIPHY: + case SIOCGMIIREG: + case SIOCSMIIREG: + return atl2_mii_ioctl(netdev, ifr, cmd); +#ifdef ETHTOOL_OPS_COMPAT + case SIOCETHTOOL: + return ethtool_ioctl(ifr); +#endif + default: + return -EOPNOTSUPP; + } +} + +/** + * atl2_tx_timeout - Respond to a Tx Hang + * @netdev: network interface device structure + */ +static void atl2_tx_timeout(struct net_device *netdev) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + + /* Do the reset outside of interrupt context */ + schedule_work(&adapter->reset_task); +} + +/** + * atl2_watchdog - Timer Call-back + * @data: pointer to netdev cast into an unsigned long + */ +static void atl2_watchdog(unsigned long data) +{ + struct atl2_adapter *adapter = (struct atl2_adapter *) data; + + if (!test_bit(__ATL2_DOWN, &adapter->flags)) { + u32 drop_rxd, drop_rxs; + unsigned long flags; + + spin_lock_irqsave(&adapter->stats_lock, flags); + drop_rxd = ATL2_READ_REG(&adapter->hw, REG_STS_RXD_OV); + drop_rxs = ATL2_READ_REG(&adapter->hw, REG_STS_RXS_OV); + spin_unlock_irqrestore(&adapter->stats_lock, flags); + + adapter->netdev->stats.rx_over_errors += drop_rxd + drop_rxs; + + /* Reset the timer */ + mod_timer(&adapter->watchdog_timer, + round_jiffies(jiffies + 4 * HZ)); + } +} + +/** + * atl2_phy_config - Timer Call-back + * @data: pointer to netdev cast into an unsigned long + */ +static void atl2_phy_config(unsigned long data) +{ + struct atl2_adapter *adapter = (struct atl2_adapter *) data; + struct atl2_hw *hw = &adapter->hw; + unsigned long flags; + + spin_lock_irqsave(&adapter->stats_lock, flags); + atl2_write_phy_reg(hw, MII_ADVERTISE, hw->mii_autoneg_adv_reg); + atl2_write_phy_reg(hw, MII_BMCR, MII_CR_RESET | MII_CR_AUTO_NEG_EN | + MII_CR_RESTART_AUTO_NEG); + spin_unlock_irqrestore(&adapter->stats_lock, flags); + clear_bit(0, &adapter->cfg_phy); +} + +static int atl2_up(struct atl2_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + int err = 0; + u32 val; + + /* hardware has been reset, we need to reload some things */ + + err = atl2_init_hw(&adapter->hw); + if (err) { + err = -EIO; + return err; + } + + atl2_set_multi(netdev); + init_ring_ptrs(adapter); + + atl2_restore_vlan(adapter); + + if (atl2_configure(adapter)) { + err = -EIO; + goto err_up; + } + + clear_bit(__ATL2_DOWN, &adapter->flags); + + val = ATL2_READ_REG(&adapter->hw, REG_MASTER_CTRL); + ATL2_WRITE_REG(&adapter->hw, REG_MASTER_CTRL, val | + MASTER_CTRL_MANUAL_INT); + + atl2_irq_enable(adapter); + +err_up: + return err; +} + +static void atl2_reinit_locked(struct atl2_adapter *adapter) +{ + WARN_ON(in_interrupt()); + while (test_and_set_bit(__ATL2_RESETTING, &adapter->flags)) + msleep(1); + atl2_down(adapter); + atl2_up(adapter); + clear_bit(__ATL2_RESETTING, &adapter->flags); +} + +static void atl2_reset_task(struct work_struct *work) +{ + struct atl2_adapter *adapter; + adapter = container_of(work, struct atl2_adapter, reset_task); + + atl2_reinit_locked(adapter); +} + +static void atl2_setup_mac_ctrl(struct atl2_adapter *adapter) +{ + u32 value; + struct atl2_hw *hw = &adapter->hw; + struct net_device *netdev = adapter->netdev; + + /* Config MAC CTRL Register */ + value = MAC_CTRL_TX_EN | MAC_CTRL_RX_EN | MAC_CTRL_MACLP_CLK_PHY; + + /* duplex */ + if (FULL_DUPLEX == adapter->link_duplex) + value |= MAC_CTRL_DUPLX; + + /* flow control */ + value |= (MAC_CTRL_TX_FLOW | MAC_CTRL_RX_FLOW); + + /* PAD & CRC */ + value |= (MAC_CTRL_ADD_CRC | MAC_CTRL_PAD); + + /* preamble length */ + value |= (((u32)adapter->hw.preamble_len & MAC_CTRL_PRMLEN_MASK) << + MAC_CTRL_PRMLEN_SHIFT); + + /* vlan */ +#if defined(NETIF_F_HW_VLAN_TX) || (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + __atl2_vlan_mode(netdev->features, &value); +#endif + + /* filter mode */ + value |= MAC_CTRL_BC_EN; + if (netdev->flags & IFF_PROMISC) + value |= MAC_CTRL_PROMIS_EN; + else if (netdev->flags & IFF_ALLMULTI) + value |= MAC_CTRL_MC_ALL_EN; + + /* half retry buffer */ + value |= (((u32)(adapter->hw.retry_buf & + MAC_CTRL_HALF_LEFT_BUF_MASK)) << MAC_CTRL_HALF_LEFT_BUF_SHIFT); + + ATL2_WRITE_REG(hw, REG_MAC_CTRL, value); +} + +static int atl2_check_link(struct atl2_adapter *adapter) +{ + struct atl2_hw *hw = &adapter->hw; + struct net_device *netdev = adapter->netdev; + int ret_val; + u16 speed, duplex, phy_data; + int reconfig = 0; + + /* MII_BMSR must read twise */ + atl2_read_phy_reg(hw, MII_BMSR, &phy_data); + atl2_read_phy_reg(hw, MII_BMSR, &phy_data); + if (!(phy_data&BMSR_LSTATUS)) { /* link down */ + if (netif_carrier_ok(netdev)) { /* old link state: Up */ + u32 value; + /* disable rx */ + value = ATL2_READ_REG(hw, REG_MAC_CTRL); + value &= ~MAC_CTRL_RX_EN; + ATL2_WRITE_REG(hw, REG_MAC_CTRL, value); + adapter->link_speed = SPEED_0; + netif_carrier_off(netdev); + netif_stop_queue(netdev); + } + return 0; + } + + /* Link Up */ + ret_val = atl2_get_speed_and_duplex(hw, &speed, &duplex); + if (ret_val) + return ret_val; + switch (hw->MediaType) { + case MEDIA_TYPE_100M_FULL: + if (speed != SPEED_100 || duplex != FULL_DUPLEX) + reconfig = 1; + break; + case MEDIA_TYPE_100M_HALF: + if (speed != SPEED_100 || duplex != HALF_DUPLEX) + reconfig = 1; + break; + case MEDIA_TYPE_10M_FULL: + if (speed != SPEED_10 || duplex != FULL_DUPLEX) + reconfig = 1; + break; + case MEDIA_TYPE_10M_HALF: + if (speed != SPEED_10 || duplex != HALF_DUPLEX) + reconfig = 1; + break; + } + /* link result is our setting */ + if (reconfig == 0) { + if (adapter->link_speed != speed || + adapter->link_duplex != duplex) { + adapter->link_speed = speed; + adapter->link_duplex = duplex; + atl2_setup_mac_ctrl(adapter); + printk(KERN_INFO "%s: %s NIC Link is Up<%d Mbps %s>\n", + atl2_driver_name, netdev->name, + adapter->link_speed, + adapter->link_duplex == FULL_DUPLEX ? + "Full Duplex" : "Half Duplex"); + } + + if (!netif_carrier_ok(netdev)) { /* Link down -> Up */ + netif_carrier_on(netdev); + netif_wake_queue(netdev); + } + return 0; + } + + /* change original link status */ + if (netif_carrier_ok(netdev)) { + u32 value; + /* disable rx */ + value = ATL2_READ_REG(hw, REG_MAC_CTRL); + value &= ~MAC_CTRL_RX_EN; + ATL2_WRITE_REG(hw, REG_MAC_CTRL, value); + + adapter->link_speed = SPEED_0; + netif_carrier_off(netdev); + netif_stop_queue(netdev); + } + + /* auto-neg, insert timer to re-config phy + * (if interval smaller than 5 seconds, something strange) */ + if (!test_bit(__ATL2_DOWN, &adapter->flags)) { + if (!test_and_set_bit(0, &adapter->cfg_phy)) + mod_timer(&adapter->phy_config_timer, + round_jiffies(jiffies + 5 * HZ)); + } + + return 0; +} + +/** + * atl2_link_chg_task - deal with link change event Out of interrupt context + */ +static void atl2_link_chg_task(struct work_struct *work) +{ + struct atl2_adapter *adapter; + unsigned long flags; + + adapter = container_of(work, struct atl2_adapter, link_chg_task); + + spin_lock_irqsave(&adapter->stats_lock, flags); + atl2_check_link(adapter); + spin_unlock_irqrestore(&adapter->stats_lock, flags); +} + +static void atl2_setup_pcicmd(struct pci_dev *pdev) +{ + u16 cmd; + + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + + if (cmd & PCI_COMMAND_INTX_DISABLE) + cmd &= ~PCI_COMMAND_INTX_DISABLE; + if (cmd & PCI_COMMAND_IO) + cmd &= ~PCI_COMMAND_IO; + if (0 == (cmd & PCI_COMMAND_MEMORY)) + cmd |= PCI_COMMAND_MEMORY; + if (0 == (cmd & PCI_COMMAND_MASTER)) + cmd |= PCI_COMMAND_MASTER; + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + /* + * some motherboards BIOS(PXE/EFI) driver may set PME + * while they transfer control to OS (Windows/Linux) + * so we should clear this bit before NIC work normally + */ + pci_write_config_dword(pdev, REG_PM_CTRLSTAT, 0); +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +static void atl2_poll_controller(struct net_device *netdev) +{ + disable_irq(netdev->irq); + atl2_intr(netdev->irq, netdev); + enable_irq(netdev->irq); +} +#endif + + +static const struct net_device_ops atl2_netdev_ops = { + .ndo_open = atl2_open, + .ndo_stop = atl2_close, + .ndo_start_xmit = atl2_xmit_frame, + .ndo_set_rx_mode = atl2_set_multi, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_mac_address = atl2_set_mac, + .ndo_change_mtu = atl2_change_mtu, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + .ndo_fix_features = atl2_fix_features, + .ndo_set_features = atl2_set_features, +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */ + .ndo_do_ioctl = atl2_ioctl, + .ndo_tx_timeout = atl2_tx_timeout, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = atl2_poll_controller, +#endif +}; + +/** + * atl2_probe - Device Initialization Routine + * @pdev: PCI device information struct + * @ent: entry in atl2_pci_tbl + * + * Returns 0 on success, negative on failure + * + * atl2_probe initializes an adapter identified by a pci_dev structure. + * The OS initialization, configuring of the adapter private structure, + * and a hardware reset occur. + */ +static int atl2_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct net_device *netdev; + struct atl2_adapter *adapter; + static int cards_found; + unsigned long mmio_start; + int mmio_len; + int err; + + cards_found = 0; + + err = pci_enable_device(pdev); + if (err) + return err; + + /* + * atl2 is a shared-high-32-bit device, so we're stuck with 32-bit DMA + * until the kernel has the proper infrastructure to support 64-bit DMA + * on these devices. + */ + if (pci_set_dma_mask(pdev, DMA_BIT_MASK(32)) && + pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32))) { + printk(KERN_ERR "atl2: No usable DMA configuration, aborting\n"); + goto err_dma; + } + + /* Mark all PCI regions associated with PCI device + * pdev as being reserved by owner atl2_driver_name */ + err = pci_request_regions(pdev, atl2_driver_name); + if (err) + goto err_pci_reg; + + /* Enables bus-mastering on the device and calls + * pcibios_set_master to do the needed arch specific settings */ + pci_set_master(pdev); + + err = -ENOMEM; + netdev = alloc_etherdev(sizeof(struct atl2_adapter)); + if (!netdev) + goto err_alloc_etherdev; + + SET_NETDEV_DEV(netdev, &pdev->dev); + + pci_set_drvdata(pdev, netdev); + adapter = netdev_priv(netdev); + adapter->netdev = netdev; + adapter->pdev = pdev; + adapter->hw.back = adapter; + + mmio_start = pci_resource_start(pdev, 0x0); + mmio_len = pci_resource_len(pdev, 0x0); + + adapter->hw.mem_rang = (u32)mmio_len; + adapter->hw.hw_addr = ioremap(mmio_start, mmio_len); + if (!adapter->hw.hw_addr) { + err = -EIO; + goto err_ioremap; + } + + atl2_setup_pcicmd(pdev); + + netdev_attach_ops(netdev, &atl2_netdev_ops); + atl2_set_ethtool_ops(netdev); + netdev->watchdog_timeo = 5 * HZ; + strncpy(netdev->name, pci_name(pdev), sizeof(netdev->name) - 1); + + netdev->mem_start = mmio_start; + netdev->mem_end = mmio_start + mmio_len; + adapter->bd_number = cards_found; + adapter->pci_using_64 = false; + + /* setup the private structure */ + err = atl2_sw_init(adapter); + if (err) + goto err_sw_init; + + err = -EIO; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + netdev->hw_features = NETIF_F_SG | NETIF_F_HW_VLAN_CTAG_RX; +#endif +#if defined(NETIF_F_HW_VLAN_TX) || (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + netdev->features |= (NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX); +#endif + + /* Init PHY as early as possible due to power saving issue */ + atl2_phy_init(&adapter->hw); + + /* reset the controller to + * put the device in a known good starting state */ + + if (atl2_reset_hw(&adapter->hw)) { + err = -EIO; + goto err_reset; + } + + /* copy the MAC address out of the EEPROM */ + atl2_read_mac_addr(&adapter->hw); + memcpy(netdev->dev_addr, adapter->hw.mac_addr, netdev->addr_len); + if (!is_valid_ether_addr(netdev->dev_addr)) { + err = -EIO; + goto err_eeprom; + } + + atl2_check_options(adapter); + + init_timer(&adapter->watchdog_timer); + adapter->watchdog_timer.function = atl2_watchdog; + adapter->watchdog_timer.data = (unsigned long) adapter; + + init_timer(&adapter->phy_config_timer); + adapter->phy_config_timer.function = atl2_phy_config; + adapter->phy_config_timer.data = (unsigned long) adapter; + + INIT_WORK(&adapter->reset_task, atl2_reset_task); + INIT_WORK(&adapter->link_chg_task, atl2_link_chg_task); + + strcpy(netdev->name, "eth%d"); /* ?? */ + err = register_netdev(netdev); + if (err) + goto err_register; + + /* assume we have no link for now */ + netif_carrier_off(netdev); + netif_stop_queue(netdev); + + cards_found++; + + return 0; + +err_reset: +err_register: +err_sw_init: +err_eeprom: + iounmap(adapter->hw.hw_addr); +err_ioremap: + free_netdev(netdev); +err_alloc_etherdev: + pci_release_regions(pdev); +err_pci_reg: +err_dma: + pci_disable_device(pdev); + return err; +} + +/** + * atl2_remove - Device Removal Routine + * @pdev: PCI device information struct + * + * atl2_remove is called by the PCI subsystem to alert the driver + * that it should release a PCI device. The could be caused by a + * Hot-Plug event, or because the driver is going to be removed from + * memory. + */ +/* FIXME: write the original MAC address back in case it was changed from a + * BIOS-set value, as in atl1 -- CHS */ +static void atl2_remove(struct pci_dev *pdev) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl2_adapter *adapter = netdev_priv(netdev); + + /* flush_scheduled work may reschedule our watchdog task, so + * explicitly disable watchdog tasks from being rescheduled */ + set_bit(__ATL2_DOWN, &adapter->flags); + + del_timer_sync(&adapter->watchdog_timer); + del_timer_sync(&adapter->phy_config_timer); + cancel_work_sync(&adapter->reset_task); + cancel_work_sync(&adapter->link_chg_task); + + unregister_netdev(netdev); + + atl2_force_ps(&adapter->hw); + + iounmap(adapter->hw.hw_addr); + pci_release_regions(pdev); + + free_netdev(netdev); + + pci_disable_device(pdev); +} + +static int atl2_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl2_adapter *adapter = netdev_priv(netdev); + struct atl2_hw *hw = &adapter->hw; + u16 speed, duplex; + u32 ctrl = 0; + u32 wufc = adapter->wol; + +#ifdef CONFIG_PM + int retval = 0; +#endif + + netif_device_detach(netdev); + + if (netif_running(netdev)) { + WARN_ON(test_bit(__ATL2_RESETTING, &adapter->flags)); + atl2_down(adapter); + } + +#ifdef CONFIG_PM + retval = pci_save_state(pdev); + if (retval) + return retval; +#endif + + atl2_read_phy_reg(hw, MII_BMSR, (u16 *)&ctrl); + atl2_read_phy_reg(hw, MII_BMSR, (u16 *)&ctrl); + if (ctrl & BMSR_LSTATUS) + wufc &= ~ATLX_WUFC_LNKC; + + if (0 != (ctrl & BMSR_LSTATUS) && 0 != wufc) { + u32 ret_val; + /* get current link speed & duplex */ + ret_val = atl2_get_speed_and_duplex(hw, &speed, &duplex); + if (ret_val) { + printk(KERN_DEBUG + "%s: get speed&duplex error while suspend\n", + atl2_driver_name); + goto wol_dis; + } + + ctrl = 0; + + /* turn on magic packet wol */ + if (wufc & ATLX_WUFC_MAG) + ctrl |= (WOL_MAGIC_EN | WOL_MAGIC_PME_EN); + + /* ignore Link Chg event when Link is up */ + ATL2_WRITE_REG(hw, REG_WOL_CTRL, ctrl); + + /* Config MAC CTRL Register */ + ctrl = MAC_CTRL_RX_EN | MAC_CTRL_MACLP_CLK_PHY; + if (FULL_DUPLEX == adapter->link_duplex) + ctrl |= MAC_CTRL_DUPLX; + ctrl |= (MAC_CTRL_ADD_CRC | MAC_CTRL_PAD); + ctrl |= (((u32)adapter->hw.preamble_len & + MAC_CTRL_PRMLEN_MASK) << MAC_CTRL_PRMLEN_SHIFT); + ctrl |= (((u32)(adapter->hw.retry_buf & + MAC_CTRL_HALF_LEFT_BUF_MASK)) << + MAC_CTRL_HALF_LEFT_BUF_SHIFT); + if (wufc & ATLX_WUFC_MAG) { + /* magic packet maybe Broadcast&multicast&Unicast */ + ctrl |= MAC_CTRL_BC_EN; + } + + ATL2_WRITE_REG(hw, REG_MAC_CTRL, ctrl); + + /* pcie patch */ + ctrl = ATL2_READ_REG(hw, REG_PCIE_PHYMISC); + ctrl |= PCIE_PHYMISC_FORCE_RCV_DET; + ATL2_WRITE_REG(hw, REG_PCIE_PHYMISC, ctrl); + ctrl = ATL2_READ_REG(hw, REG_PCIE_DLL_TX_CTRL1); + ctrl |= PCIE_DLL_TX_CTRL1_SEL_NOR_CLK; + ATL2_WRITE_REG(hw, REG_PCIE_DLL_TX_CTRL1, ctrl); + + pci_enable_wake(pdev, pci_choose_state(pdev, state), 1); + goto suspend_exit; + } + + if (0 == (ctrl&BMSR_LSTATUS) && 0 != (wufc&ATLX_WUFC_LNKC)) { + /* link is down, so only LINK CHG WOL event enable */ + ctrl |= (WOL_LINK_CHG_EN | WOL_LINK_CHG_PME_EN); + ATL2_WRITE_REG(hw, REG_WOL_CTRL, ctrl); + ATL2_WRITE_REG(hw, REG_MAC_CTRL, 0); + + /* pcie patch */ + ctrl = ATL2_READ_REG(hw, REG_PCIE_PHYMISC); + ctrl |= PCIE_PHYMISC_FORCE_RCV_DET; + ATL2_WRITE_REG(hw, REG_PCIE_PHYMISC, ctrl); + ctrl = ATL2_READ_REG(hw, REG_PCIE_DLL_TX_CTRL1); + ctrl |= PCIE_DLL_TX_CTRL1_SEL_NOR_CLK; + ATL2_WRITE_REG(hw, REG_PCIE_DLL_TX_CTRL1, ctrl); + + hw->phy_configured = false; /* re-init PHY when resume */ + + pci_enable_wake(pdev, pci_choose_state(pdev, state), 1); + + goto suspend_exit; + } + +wol_dis: + /* WOL disabled */ + ATL2_WRITE_REG(hw, REG_WOL_CTRL, 0); + + /* pcie patch */ + ctrl = ATL2_READ_REG(hw, REG_PCIE_PHYMISC); + ctrl |= PCIE_PHYMISC_FORCE_RCV_DET; + ATL2_WRITE_REG(hw, REG_PCIE_PHYMISC, ctrl); + ctrl = ATL2_READ_REG(hw, REG_PCIE_DLL_TX_CTRL1); + ctrl |= PCIE_DLL_TX_CTRL1_SEL_NOR_CLK; + ATL2_WRITE_REG(hw, REG_PCIE_DLL_TX_CTRL1, ctrl); + + atl2_force_ps(hw); + hw->phy_configured = false; /* re-init PHY when resume */ + + pci_enable_wake(pdev, pci_choose_state(pdev, state), 0); + +suspend_exit: + if (netif_running(netdev)) + atl2_free_irq(adapter); + + pci_disable_device(pdev); + + pci_set_power_state(pdev, pci_choose_state(pdev, state)); + + return 0; +} + +#ifdef CONFIG_PM +static int atl2_resume(struct pci_dev *pdev) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct atl2_adapter *adapter = netdev_priv(netdev); + u32 err; + + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + + err = pci_enable_device(pdev); + if (err) { + printk(KERN_ERR + "atl2: Cannot enable PCI device from suspend\n"); + return err; + } + + pci_set_master(pdev); + + ATL2_READ_REG(&adapter->hw, REG_WOL_CTRL); /* clear WOL status */ + + pci_enable_wake(pdev, PCI_D3hot, 0); + pci_enable_wake(pdev, PCI_D3cold, 0); + + ATL2_WRITE_REG(&adapter->hw, REG_WOL_CTRL, 0); + + if (netif_running(netdev)) { + err = atl2_request_irq(adapter); + if (err) + return err; + } + + atl2_reset_hw(&adapter->hw); + + if (netif_running(netdev)) + atl2_up(adapter); + + netif_device_attach(netdev); + + return 0; +} +#endif + +static void atl2_shutdown(struct pci_dev *pdev) +{ + atl2_suspend(pdev, PMSG_SUSPEND); +} + +static struct pci_driver atl2_driver = { + .name = atl2_driver_name, + .id_table = atl2_pci_tbl, + .probe = atl2_probe, + .remove = atl2_remove, + /* Power Management Hooks */ + .suspend = atl2_suspend, +#ifdef CONFIG_PM + .resume = atl2_resume, +#endif + .shutdown = atl2_shutdown, +}; + +/** + * atl2_init_module - Driver Registration Routine + * + * atl2_init_module is the first routine called when the driver is + * loaded. All it does is register with the PCI subsystem. + */ +static int __init atl2_init_module(void) +{ + printk(KERN_INFO "%s - version %s\n", atl2_driver_string, + atl2_driver_version); + printk(KERN_INFO "%s\n", atl2_copyright); + return pci_register_driver(&atl2_driver); +} +module_init(atl2_init_module); + +/** + * atl2_exit_module - Driver Exit Cleanup Routine + * + * atl2_exit_module is called just before the driver is removed + * from memory. + */ +static void __exit atl2_exit_module(void) +{ + pci_unregister_driver(&atl2_driver); +} +module_exit(atl2_exit_module); + +static void atl2_read_pci_cfg(struct atl2_hw *hw, u32 reg, u16 *value) +{ + struct atl2_adapter *adapter = hw->back; + pci_read_config_word(adapter->pdev, reg, value); +} + +static void atl2_write_pci_cfg(struct atl2_hw *hw, u32 reg, u16 *value) +{ + struct atl2_adapter *adapter = hw->back; + pci_write_config_word(adapter->pdev, reg, *value); +} + +static int atl2_get_settings(struct net_device *netdev, + struct ethtool_cmd *ecmd) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + struct atl2_hw *hw = &adapter->hw; + + ecmd->supported = (SUPPORTED_10baseT_Half | + SUPPORTED_10baseT_Full | + SUPPORTED_100baseT_Half | + SUPPORTED_100baseT_Full | + SUPPORTED_Autoneg | + SUPPORTED_TP); + ecmd->advertising = ADVERTISED_TP; + + ecmd->advertising |= ADVERTISED_Autoneg; + ecmd->advertising |= hw->autoneg_advertised; + + ecmd->port = PORT_TP; + ecmd->phy_address = 0; + ecmd->transceiver = XCVR_INTERNAL; + + if (adapter->link_speed != SPEED_0) { + ethtool_cmd_speed_set(ecmd, adapter->link_speed); + if (adapter->link_duplex == FULL_DUPLEX) + ecmd->duplex = DUPLEX_FULL; + else + ecmd->duplex = DUPLEX_HALF; + } else { + ethtool_cmd_speed_set(ecmd, -1); + ecmd->duplex = -1; + } + + ecmd->autoneg = AUTONEG_ENABLE; + return 0; +} + +static int atl2_set_settings(struct net_device *netdev, + struct ethtool_cmd *ecmd) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + struct atl2_hw *hw = &adapter->hw; + + while (test_and_set_bit(__ATL2_RESETTING, &adapter->flags)) + msleep(1); + + if (ecmd->autoneg == AUTONEG_ENABLE) { +#define MY_ADV_MASK (ADVERTISE_10_HALF | \ + ADVERTISE_10_FULL | \ + ADVERTISE_100_HALF| \ + ADVERTISE_100_FULL) + + if ((ecmd->advertising & MY_ADV_MASK) == MY_ADV_MASK) { + hw->MediaType = MEDIA_TYPE_AUTO_SENSOR; + hw->autoneg_advertised = MY_ADV_MASK; + } else if ((ecmd->advertising & MY_ADV_MASK) == + ADVERTISE_100_FULL) { + hw->MediaType = MEDIA_TYPE_100M_FULL; + hw->autoneg_advertised = ADVERTISE_100_FULL; + } else if ((ecmd->advertising & MY_ADV_MASK) == + ADVERTISE_100_HALF) { + hw->MediaType = MEDIA_TYPE_100M_HALF; + hw->autoneg_advertised = ADVERTISE_100_HALF; + } else if ((ecmd->advertising & MY_ADV_MASK) == + ADVERTISE_10_FULL) { + hw->MediaType = MEDIA_TYPE_10M_FULL; + hw->autoneg_advertised = ADVERTISE_10_FULL; + } else if ((ecmd->advertising & MY_ADV_MASK) == + ADVERTISE_10_HALF) { + hw->MediaType = MEDIA_TYPE_10M_HALF; + hw->autoneg_advertised = ADVERTISE_10_HALF; + } else { + clear_bit(__ATL2_RESETTING, &adapter->flags); + return -EINVAL; + } + ecmd->advertising = hw->autoneg_advertised | + ADVERTISED_TP | ADVERTISED_Autoneg; + } else { + clear_bit(__ATL2_RESETTING, &adapter->flags); + return -EINVAL; + } + + /* reset the link */ + if (netif_running(adapter->netdev)) { + atl2_down(adapter); + atl2_up(adapter); + } else + atl2_reset_hw(&adapter->hw); + + clear_bit(__ATL2_RESETTING, &adapter->flags); + return 0; +} + +static u32 atl2_get_msglevel(struct net_device *netdev) +{ + return 0; +} + +/* + * It's sane for this to be empty, but we might want to take advantage of this. + */ +static void atl2_set_msglevel(struct net_device *netdev, u32 data) +{ +} + +static int atl2_get_regs_len(struct net_device *netdev) +{ +#define ATL2_REGS_LEN 42 + return sizeof(u32) * ATL2_REGS_LEN; +} + +static void atl2_get_regs(struct net_device *netdev, + struct ethtool_regs *regs, void *p) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + struct atl2_hw *hw = &adapter->hw; + u32 *regs_buff = p; + u16 phy_data; + + memset(p, 0, sizeof(u32) * ATL2_REGS_LEN); + + regs->version = (1 << 24) | (hw->revision_id << 16) | hw->device_id; + + regs_buff[0] = ATL2_READ_REG(hw, REG_VPD_CAP); + regs_buff[1] = ATL2_READ_REG(hw, REG_SPI_FLASH_CTRL); + regs_buff[2] = ATL2_READ_REG(hw, REG_SPI_FLASH_CONFIG); + regs_buff[3] = ATL2_READ_REG(hw, REG_TWSI_CTRL); + regs_buff[4] = ATL2_READ_REG(hw, REG_PCIE_DEV_MISC_CTRL); + regs_buff[5] = ATL2_READ_REG(hw, REG_MASTER_CTRL); + regs_buff[6] = ATL2_READ_REG(hw, REG_MANUAL_TIMER_INIT); + regs_buff[7] = ATL2_READ_REG(hw, REG_IRQ_MODU_TIMER_INIT); + regs_buff[8] = ATL2_READ_REG(hw, REG_PHY_ENABLE); + regs_buff[9] = ATL2_READ_REG(hw, REG_CMBDISDMA_TIMER); + regs_buff[10] = ATL2_READ_REG(hw, REG_IDLE_STATUS); + regs_buff[11] = ATL2_READ_REG(hw, REG_MDIO_CTRL); + regs_buff[12] = ATL2_READ_REG(hw, REG_SERDES_LOCK); + regs_buff[13] = ATL2_READ_REG(hw, REG_MAC_CTRL); + regs_buff[14] = ATL2_READ_REG(hw, REG_MAC_IPG_IFG); + regs_buff[15] = ATL2_READ_REG(hw, REG_MAC_STA_ADDR); + regs_buff[16] = ATL2_READ_REG(hw, REG_MAC_STA_ADDR+4); + regs_buff[17] = ATL2_READ_REG(hw, REG_RX_HASH_TABLE); + regs_buff[18] = ATL2_READ_REG(hw, REG_RX_HASH_TABLE+4); + regs_buff[19] = ATL2_READ_REG(hw, REG_MAC_HALF_DUPLX_CTRL); + regs_buff[20] = ATL2_READ_REG(hw, REG_MTU); + regs_buff[21] = ATL2_READ_REG(hw, REG_WOL_CTRL); + regs_buff[22] = ATL2_READ_REG(hw, REG_SRAM_TXRAM_END); + regs_buff[23] = ATL2_READ_REG(hw, REG_DESC_BASE_ADDR_HI); + regs_buff[24] = ATL2_READ_REG(hw, REG_TXD_BASE_ADDR_LO); + regs_buff[25] = ATL2_READ_REG(hw, REG_TXD_MEM_SIZE); + regs_buff[26] = ATL2_READ_REG(hw, REG_TXS_BASE_ADDR_LO); + regs_buff[27] = ATL2_READ_REG(hw, REG_TXS_MEM_SIZE); + regs_buff[28] = ATL2_READ_REG(hw, REG_RXD_BASE_ADDR_LO); + regs_buff[29] = ATL2_READ_REG(hw, REG_RXD_BUF_NUM); + regs_buff[30] = ATL2_READ_REG(hw, REG_DMAR); + regs_buff[31] = ATL2_READ_REG(hw, REG_TX_CUT_THRESH); + regs_buff[32] = ATL2_READ_REG(hw, REG_DMAW); + regs_buff[33] = ATL2_READ_REG(hw, REG_PAUSE_ON_TH); + regs_buff[34] = ATL2_READ_REG(hw, REG_PAUSE_OFF_TH); + regs_buff[35] = ATL2_READ_REG(hw, REG_MB_TXD_WR_IDX); + regs_buff[36] = ATL2_READ_REG(hw, REG_MB_RXD_RD_IDX); + regs_buff[38] = ATL2_READ_REG(hw, REG_ISR); + regs_buff[39] = ATL2_READ_REG(hw, REG_IMR); + + atl2_read_phy_reg(hw, MII_BMCR, &phy_data); + regs_buff[40] = (u32)phy_data; + atl2_read_phy_reg(hw, MII_BMSR, &phy_data); + regs_buff[41] = (u32)phy_data; +} + +static int atl2_get_eeprom_len(struct net_device *netdev) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + + if (!atl2_check_eeprom_exist(&adapter->hw)) + return 512; + else + return 0; +} + +static int atl2_get_eeprom(struct net_device *netdev, + struct ethtool_eeprom *eeprom, u8 *bytes) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + struct atl2_hw *hw = &adapter->hw; + u32 *eeprom_buff; + int first_dword, last_dword; + int ret_val = 0; + int i; + + if (eeprom->len == 0) + return -EINVAL; + + if (atl2_check_eeprom_exist(hw)) + return -EINVAL; + + eeprom->magic = hw->vendor_id | (hw->device_id << 16); + + first_dword = eeprom->offset >> 2; + last_dword = (eeprom->offset + eeprom->len - 1) >> 2; + + eeprom_buff = kmalloc(sizeof(u32) * (last_dword - first_dword + 1), + GFP_KERNEL); + if (!eeprom_buff) + return -ENOMEM; + + for (i = first_dword; i < last_dword; i++) { + if (!atl2_read_eeprom(hw, i*4, &(eeprom_buff[i-first_dword]))) { + ret_val = -EIO; + goto free; + } + } + + memcpy(bytes, (u8 *)eeprom_buff + (eeprom->offset & 3), + eeprom->len); +free: + kfree(eeprom_buff); + + return ret_val; +} + +static int atl2_set_eeprom(struct net_device *netdev, + struct ethtool_eeprom *eeprom, u8 *bytes) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + struct atl2_hw *hw = &adapter->hw; + u32 *eeprom_buff; + u32 *ptr; + int max_len, first_dword, last_dword, ret_val = 0; + int i; + + if (eeprom->len == 0) + return -EOPNOTSUPP; + + if (eeprom->magic != (hw->vendor_id | (hw->device_id << 16))) + return -EFAULT; + + max_len = 512; + + first_dword = eeprom->offset >> 2; + last_dword = (eeprom->offset + eeprom->len - 1) >> 2; + eeprom_buff = kmalloc(max_len, GFP_KERNEL); + if (!eeprom_buff) + return -ENOMEM; + + ptr = eeprom_buff; + + if (eeprom->offset & 3) { + /* need read/modify/write of first changed EEPROM word */ + /* only the second byte of the word is being modified */ + if (!atl2_read_eeprom(hw, first_dword*4, &(eeprom_buff[0]))) { + ret_val = -EIO; + goto out; + } + ptr++; + } + if (((eeprom->offset + eeprom->len) & 3)) { + /* + * need read/modify/write of last changed EEPROM word + * only the first byte of the word is being modified + */ + if (!atl2_read_eeprom(hw, last_dword * 4, + &(eeprom_buff[last_dword - first_dword]))) { + ret_val = -EIO; + goto out; + } + } + + /* Device's eeprom is always little-endian, word addressable */ + memcpy(ptr, bytes, eeprom->len); + + for (i = 0; i < last_dword - first_dword + 1; i++) { + if (!atl2_write_eeprom(hw, ((first_dword+i)*4), eeprom_buff[i])) { + ret_val = -EIO; + goto out; + } + } + out: + kfree(eeprom_buff); + return ret_val; +} + +static void atl2_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *drvinfo) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + + strlcpy(drvinfo->driver, atl2_driver_name, sizeof(drvinfo->driver)); + strlcpy(drvinfo->version, atl2_driver_version, + sizeof(drvinfo->version)); + strlcpy(drvinfo->fw_version, "L2", sizeof(drvinfo->fw_version)); + strlcpy(drvinfo->bus_info, pci_name(adapter->pdev), + sizeof(drvinfo->bus_info)); + drvinfo->n_stats = 0; + drvinfo->testinfo_len = 0; + drvinfo->regdump_len = atl2_get_regs_len(netdev); + drvinfo->eedump_len = atl2_get_eeprom_len(netdev); +} + +static void atl2_get_wol(struct net_device *netdev, + struct ethtool_wolinfo *wol) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + + wol->supported = WAKE_MAGIC; + wol->wolopts = 0; + + if (adapter->wol & ATLX_WUFC_EX) + wol->wolopts |= WAKE_UCAST; + if (adapter->wol & ATLX_WUFC_MC) + wol->wolopts |= WAKE_MCAST; + if (adapter->wol & ATLX_WUFC_BC) + wol->wolopts |= WAKE_BCAST; + if (adapter->wol & ATLX_WUFC_MAG) + wol->wolopts |= WAKE_MAGIC; + if (adapter->wol & ATLX_WUFC_LNKC) + wol->wolopts |= WAKE_PHY; +} + +static int atl2_set_wol(struct net_device *netdev, struct ethtool_wolinfo *wol) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + + if (wol->wolopts & (WAKE_ARP | WAKE_MAGICSECURE)) + return -EOPNOTSUPP; + + if (wol->wolopts & (WAKE_UCAST | WAKE_BCAST | WAKE_MCAST)) + return -EOPNOTSUPP; + + /* these settings will always override what we currently have */ + adapter->wol = 0; + + if (wol->wolopts & WAKE_MAGIC) + adapter->wol |= ATLX_WUFC_MAG; + if (wol->wolopts & WAKE_PHY) + adapter->wol |= ATLX_WUFC_LNKC; + + return 0; +} + +static int atl2_nway_reset(struct net_device *netdev) +{ + struct atl2_adapter *adapter = netdev_priv(netdev); + if (netif_running(netdev)) + atl2_reinit_locked(adapter); + return 0; +} + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) +static u32 atl2_get_tx_csum(struct net_device *netdev) +{ + return (netdev->features & NETIF_F_HW_CSUM) != 0; +} +#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) */ + +static const struct ethtool_ops atl2_ethtool_ops = { + .get_settings = atl2_get_settings, + .set_settings = atl2_set_settings, + .get_drvinfo = atl2_get_drvinfo, + .get_regs_len = atl2_get_regs_len, + .get_regs = atl2_get_regs, + .get_wol = atl2_get_wol, + .set_wol = atl2_set_wol, + .get_msglevel = atl2_get_msglevel, + .set_msglevel = atl2_set_msglevel, + .nway_reset = atl2_nway_reset, + .get_link = ethtool_op_get_link, + .get_eeprom_len = atl2_get_eeprom_len, + .get_eeprom = atl2_get_eeprom, + .set_eeprom = atl2_set_eeprom, +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) + .get_tx_csum = atl2_get_tx_csum, + .get_sg = ethtool_op_get_sg, + .set_sg = ethtool_op_set_sg, +#ifdef NETIF_F_TSO + .get_tso = ethtool_op_get_tso, +#endif +#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,39)) */ +}; + +static void atl2_set_ethtool_ops(struct net_device *netdev) +{ + SET_ETHTOOL_OPS(netdev, &atl2_ethtool_ops); +} + +#define LBYTESWAP(a) ((((a) & 0x00ff00ff) << 8) | \ + (((a) & 0xff00ff00) >> 8)) +#define LONGSWAP(a) ((LBYTESWAP(a) << 16) | (LBYTESWAP(a) >> 16)) +#define SHORTSWAP(a) (((a) << 8) | ((a) >> 8)) + +/* + * Reset the transmit and receive units; mask and clear all interrupts. + * + * hw - Struct containing variables accessed by shared code + * return : 0 or idle status (if error) + */ +static s32 atl2_reset_hw(struct atl2_hw *hw) +{ + u32 icr; + u16 pci_cfg_cmd_word; + int i; + + /* Workaround for PCI problem when BIOS sets MMRBC incorrectly. */ + atl2_read_pci_cfg(hw, PCI_REG_COMMAND, &pci_cfg_cmd_word); + if ((pci_cfg_cmd_word & + (CMD_IO_SPACE|CMD_MEMORY_SPACE|CMD_BUS_MASTER)) != + (CMD_IO_SPACE|CMD_MEMORY_SPACE|CMD_BUS_MASTER)) { + pci_cfg_cmd_word |= + (CMD_IO_SPACE|CMD_MEMORY_SPACE|CMD_BUS_MASTER); + atl2_write_pci_cfg(hw, PCI_REG_COMMAND, &pci_cfg_cmd_word); + } + + /* Clear Interrupt mask to stop board from generating + * interrupts & Clear any pending interrupt events + */ + /* FIXME */ + /* ATL2_WRITE_REG(hw, REG_IMR, 0); */ + /* ATL2_WRITE_REG(hw, REG_ISR, 0xffffffff); */ + + /* Issue Soft Reset to the MAC. This will reset the chip's + * transmit, receive, DMA. It will not effect + * the current PCI configuration. The global reset bit is self- + * clearing, and should clear within a microsecond. + */ + ATL2_WRITE_REG(hw, REG_MASTER_CTRL, MASTER_CTRL_SOFT_RST); + wmb(); + msleep(1); /* delay about 1ms */ + + /* Wait at least 10ms for All module to be Idle */ + for (i = 0; i < 10; i++) { + icr = ATL2_READ_REG(hw, REG_IDLE_STATUS); + if (!icr) + break; + msleep(1); /* delay 1 ms */ + cpu_relax(); + } + + if (icr) + return icr; + + return 0; +} + +#define CUSTOM_SPI_CS_SETUP 2 +#define CUSTOM_SPI_CLK_HI 2 +#define CUSTOM_SPI_CLK_LO 2 +#define CUSTOM_SPI_CS_HOLD 2 +#define CUSTOM_SPI_CS_HI 3 + +static struct atl2_spi_flash_dev flash_table[] = +{ +/* MFR WRSR READ PROGRAM WREN WRDI RDSR RDID SECTOR_ERASE CHIP_ERASE */ +{"Atmel", 0x0, 0x03, 0x02, 0x06, 0x04, 0x05, 0x15, 0x52, 0x62 }, +{"SST", 0x01, 0x03, 0x02, 0x06, 0x04, 0x05, 0x90, 0x20, 0x60 }, +{"ST", 0x01, 0x03, 0x02, 0x06, 0x04, 0x05, 0xAB, 0xD8, 0xC7 }, +}; + +static bool atl2_spi_read(struct atl2_hw *hw, u32 addr, u32 *buf) +{ + int i; + u32 value; + + ATL2_WRITE_REG(hw, REG_SPI_DATA, 0); + ATL2_WRITE_REG(hw, REG_SPI_ADDR, addr); + + value = SPI_FLASH_CTRL_WAIT_READY | + (CUSTOM_SPI_CS_SETUP & SPI_FLASH_CTRL_CS_SETUP_MASK) << + SPI_FLASH_CTRL_CS_SETUP_SHIFT | + (CUSTOM_SPI_CLK_HI & SPI_FLASH_CTRL_CLK_HI_MASK) << + SPI_FLASH_CTRL_CLK_HI_SHIFT | + (CUSTOM_SPI_CLK_LO & SPI_FLASH_CTRL_CLK_LO_MASK) << + SPI_FLASH_CTRL_CLK_LO_SHIFT | + (CUSTOM_SPI_CS_HOLD & SPI_FLASH_CTRL_CS_HOLD_MASK) << + SPI_FLASH_CTRL_CS_HOLD_SHIFT | + (CUSTOM_SPI_CS_HI & SPI_FLASH_CTRL_CS_HI_MASK) << + SPI_FLASH_CTRL_CS_HI_SHIFT | + (0x1 & SPI_FLASH_CTRL_INS_MASK) << SPI_FLASH_CTRL_INS_SHIFT; + + ATL2_WRITE_REG(hw, REG_SPI_FLASH_CTRL, value); + + value |= SPI_FLASH_CTRL_START; + + ATL2_WRITE_REG(hw, REG_SPI_FLASH_CTRL, value); + + for (i = 0; i < 10; i++) { + msleep(1); + value = ATL2_READ_REG(hw, REG_SPI_FLASH_CTRL); + if (!(value & SPI_FLASH_CTRL_START)) + break; + } + + if (value & SPI_FLASH_CTRL_START) + return false; + + *buf = ATL2_READ_REG(hw, REG_SPI_DATA); + + return true; +} + +/* + * get_permanent_address + * return 0 if get valid mac address, + */ +static int get_permanent_address(struct atl2_hw *hw) +{ + u32 Addr[2]; + u32 i, Control; + u16 Register; + u8 EthAddr[ETH_ALEN]; + bool KeyValid; + + if (is_valid_ether_addr(hw->perm_mac_addr)) + return 0; + + Addr[0] = 0; + Addr[1] = 0; + + if (!atl2_check_eeprom_exist(hw)) { /* eeprom exists */ + Register = 0; + KeyValid = false; + + /* Read out all EEPROM content */ + i = 0; + while (1) { + if (atl2_read_eeprom(hw, i + 0x100, &Control)) { + if (KeyValid) { + if (Register == REG_MAC_STA_ADDR) + Addr[0] = Control; + else if (Register == + (REG_MAC_STA_ADDR + 4)) + Addr[1] = Control; + KeyValid = false; + } else if ((Control & 0xff) == 0x5A) { + KeyValid = true; + Register = (u16) (Control >> 16); + } else { + /* assume data end while encount an invalid KEYWORD */ + break; + } + } else { + break; /* read error */ + } + i += 4; + } + + *(u32 *) &EthAddr[2] = LONGSWAP(Addr[0]); + *(u16 *) &EthAddr[0] = SHORTSWAP(*(u16 *) &Addr[1]); + + if (is_valid_ether_addr(EthAddr)) { + memcpy(hw->perm_mac_addr, EthAddr, ETH_ALEN); + return 0; + } + return 1; + } + + /* see if SPI flash exists? */ + Addr[0] = 0; + Addr[1] = 0; + Register = 0; + KeyValid = false; + i = 0; + while (1) { + if (atl2_spi_read(hw, i + 0x1f000, &Control)) { + if (KeyValid) { + if (Register == REG_MAC_STA_ADDR) + Addr[0] = Control; + else if (Register == (REG_MAC_STA_ADDR + 4)) + Addr[1] = Control; + KeyValid = false; + } else if ((Control & 0xff) == 0x5A) { + KeyValid = true; + Register = (u16) (Control >> 16); + } else { + break; /* data end */ + } + } else { + break; /* read error */ + } + i += 4; + } + + *(u32 *) &EthAddr[2] = LONGSWAP(Addr[0]); + *(u16 *) &EthAddr[0] = SHORTSWAP(*(u16 *)&Addr[1]); + if (is_valid_ether_addr(EthAddr)) { + memcpy(hw->perm_mac_addr, EthAddr, ETH_ALEN); + return 0; + } + /* maybe MAC-address is from BIOS */ + Addr[0] = ATL2_READ_REG(hw, REG_MAC_STA_ADDR); + Addr[1] = ATL2_READ_REG(hw, REG_MAC_STA_ADDR + 4); + *(u32 *) &EthAddr[2] = LONGSWAP(Addr[0]); + *(u16 *) &EthAddr[0] = SHORTSWAP(*(u16 *) &Addr[1]); + + if (is_valid_ether_addr(EthAddr)) { + memcpy(hw->perm_mac_addr, EthAddr, ETH_ALEN); + return 0; + } + + return 1; +} + +/* + * Reads the adapter's MAC address from the EEPROM + * + * hw - Struct containing variables accessed by shared code + */ +static s32 atl2_read_mac_addr(struct atl2_hw *hw) +{ + if (get_permanent_address(hw)) { + /* for test */ + /* FIXME: shouldn't we use eth_random_addr() here? */ + hw->perm_mac_addr[0] = 0x00; + hw->perm_mac_addr[1] = 0x13; + hw->perm_mac_addr[2] = 0x74; + hw->perm_mac_addr[3] = 0x00; + hw->perm_mac_addr[4] = 0x5c; + hw->perm_mac_addr[5] = 0x38; + } + + memcpy(hw->mac_addr, hw->perm_mac_addr, ETH_ALEN); + + return 0; +} + +/* + * Hashes an address to determine its location in the multicast table + * + * hw - Struct containing variables accessed by shared code + * mc_addr - the multicast address to hash + * + * atl2_hash_mc_addr + * purpose + * set hash value for a multicast address + * hash calcu processing : + * 1. calcu 32bit CRC for multicast address + * 2. reverse crc with MSB to LSB + */ +static u32 atl2_hash_mc_addr(struct atl2_hw *hw, u8 *mc_addr) +{ + u32 crc32, value; + int i; + + value = 0; + crc32 = ether_crc_le(6, mc_addr); + + for (i = 0; i < 32; i++) + value |= (((crc32 >> i) & 1) << (31 - i)); + + return value; +} + +/* + * Sets the bit in the multicast table corresponding to the hash value. + * + * hw - Struct containing variables accessed by shared code + * hash_value - Multicast address hash value + */ +static void atl2_hash_set(struct atl2_hw *hw, u32 hash_value) +{ + u32 hash_bit, hash_reg; + u32 mta; + + /* The HASH Table is a register array of 2 32-bit registers. + * It is treated like an array of 64 bits. We want to set + * bit BitArray[hash_value]. So we figure out what register + * the bit is in, read it, OR in the new bit, then write + * back the new value. The register is determined by the + * upper 7 bits of the hash value and the bit within that + * register are determined by the lower 5 bits of the value. + */ + hash_reg = (hash_value >> 31) & 0x1; + hash_bit = (hash_value >> 26) & 0x1F; + + mta = ATL2_READ_REG_ARRAY(hw, REG_RX_HASH_TABLE, hash_reg); + + mta |= (1 << hash_bit); + + ATL2_WRITE_REG_ARRAY(hw, REG_RX_HASH_TABLE, hash_reg, mta); +} + +/* + * atl2_init_pcie - init PCIE module + */ +static void atl2_init_pcie(struct atl2_hw *hw) +{ + u32 value; + value = LTSSM_TEST_MODE_DEF; + ATL2_WRITE_REG(hw, REG_LTSSM_TEST_MODE, value); + + value = PCIE_DLL_TX_CTRL1_DEF; + ATL2_WRITE_REG(hw, REG_PCIE_DLL_TX_CTRL1, value); +} + +static void atl2_init_flash_opcode(struct atl2_hw *hw) +{ + if (hw->flash_vendor >= ARRAY_SIZE(flash_table)) + hw->flash_vendor = 0; /* ATMEL */ + + /* Init OP table */ + ATL2_WRITE_REGB(hw, REG_SPI_FLASH_OP_PROGRAM, + flash_table[hw->flash_vendor].cmdPROGRAM); + ATL2_WRITE_REGB(hw, REG_SPI_FLASH_OP_SC_ERASE, + flash_table[hw->flash_vendor].cmdSECTOR_ERASE); + ATL2_WRITE_REGB(hw, REG_SPI_FLASH_OP_CHIP_ERASE, + flash_table[hw->flash_vendor].cmdCHIP_ERASE); + ATL2_WRITE_REGB(hw, REG_SPI_FLASH_OP_RDID, + flash_table[hw->flash_vendor].cmdRDID); + ATL2_WRITE_REGB(hw, REG_SPI_FLASH_OP_WREN, + flash_table[hw->flash_vendor].cmdWREN); + ATL2_WRITE_REGB(hw, REG_SPI_FLASH_OP_RDSR, + flash_table[hw->flash_vendor].cmdRDSR); + ATL2_WRITE_REGB(hw, REG_SPI_FLASH_OP_WRSR, + flash_table[hw->flash_vendor].cmdWRSR); + ATL2_WRITE_REGB(hw, REG_SPI_FLASH_OP_READ, + flash_table[hw->flash_vendor].cmdREAD); +} + +/******************************************************************** +* Performs basic configuration of the adapter. +* +* hw - Struct containing variables accessed by shared code +* Assumes that the controller has previously been reset and is in a +* post-reset uninitialized state. Initializes multicast table, +* and Calls routines to setup link +* Leaves the transmit and receive units disabled and uninitialized. +********************************************************************/ +static s32 atl2_init_hw(struct atl2_hw *hw) +{ + u32 ret_val = 0; + + atl2_init_pcie(hw); + + /* Zero out the Multicast HASH table */ + /* clear the old settings from the multicast hash table */ + ATL2_WRITE_REG(hw, REG_RX_HASH_TABLE, 0); + ATL2_WRITE_REG_ARRAY(hw, REG_RX_HASH_TABLE, 1, 0); + + atl2_init_flash_opcode(hw); + + ret_val = atl2_phy_init(hw); + + return ret_val; +} + +/* + * Detects the current speed and duplex settings of the hardware. + * + * hw - Struct containing variables accessed by shared code + * speed - Speed of the connection + * duplex - Duplex setting of the connection + */ +static s32 atl2_get_speed_and_duplex(struct atl2_hw *hw, u16 *speed, + u16 *duplex) +{ + s32 ret_val; + u16 phy_data; + + /* Read PHY Specific Status Register (17) */ + ret_val = atl2_read_phy_reg(hw, MII_ATLX_PSSR, &phy_data); + if (ret_val) + return ret_val; + + if (!(phy_data & MII_ATLX_PSSR_SPD_DPLX_RESOLVED)) + return ATLX_ERR_PHY_RES; + + switch (phy_data & MII_ATLX_PSSR_SPEED) { + case MII_ATLX_PSSR_100MBS: + *speed = SPEED_100; + break; + case MII_ATLX_PSSR_10MBS: + *speed = SPEED_10; + break; + default: + return ATLX_ERR_PHY_SPEED; + break; + } + + if (phy_data & MII_ATLX_PSSR_DPLX) + *duplex = FULL_DUPLEX; + else + *duplex = HALF_DUPLEX; + + return 0; +} + +/* + * Reads the value from a PHY register + * hw - Struct containing variables accessed by shared code + * reg_addr - address of the PHY register to read + */ +static s32 atl2_read_phy_reg(struct atl2_hw *hw, u16 reg_addr, u16 *phy_data) +{ + u32 val; + int i; + + val = ((u32)(reg_addr & MDIO_REG_ADDR_MASK)) << MDIO_REG_ADDR_SHIFT | + MDIO_START | + MDIO_SUP_PREAMBLE | + MDIO_RW | + MDIO_CLK_25_4 << MDIO_CLK_SEL_SHIFT; + ATL2_WRITE_REG(hw, REG_MDIO_CTRL, val); + + wmb(); + + for (i = 0; i < MDIO_WAIT_TIMES; i++) { + udelay(2); + val = ATL2_READ_REG(hw, REG_MDIO_CTRL); + if (!(val & (MDIO_START | MDIO_BUSY))) + break; + wmb(); + } + if (!(val & (MDIO_START | MDIO_BUSY))) { + *phy_data = (u16)val; + return 0; + } + + return ATLX_ERR_PHY; +} + +/* + * Writes a value to a PHY register + * hw - Struct containing variables accessed by shared code + * reg_addr - address of the PHY register to write + * data - data to write to the PHY + */ +static s32 atl2_write_phy_reg(struct atl2_hw *hw, u32 reg_addr, u16 phy_data) +{ + int i; + u32 val; + + val = ((u32)(phy_data & MDIO_DATA_MASK)) << MDIO_DATA_SHIFT | + (reg_addr & MDIO_REG_ADDR_MASK) << MDIO_REG_ADDR_SHIFT | + MDIO_SUP_PREAMBLE | + MDIO_START | + MDIO_CLK_25_4 << MDIO_CLK_SEL_SHIFT; + ATL2_WRITE_REG(hw, REG_MDIO_CTRL, val); + + wmb(); + + for (i = 0; i < MDIO_WAIT_TIMES; i++) { + udelay(2); + val = ATL2_READ_REG(hw, REG_MDIO_CTRL); + if (!(val & (MDIO_START | MDIO_BUSY))) + break; + + wmb(); + } + + if (!(val & (MDIO_START | MDIO_BUSY))) + return 0; + + return ATLX_ERR_PHY; +} + +/* + * Configures PHY autoneg and flow control advertisement settings + * + * hw - Struct containing variables accessed by shared code + */ +static s32 atl2_phy_setup_autoneg_adv(struct atl2_hw *hw) +{ + s32 ret_val; + s16 mii_autoneg_adv_reg; + + /* Read the MII Auto-Neg Advertisement Register (Address 4). */ + mii_autoneg_adv_reg = MII_AR_DEFAULT_CAP_MASK; + + /* Need to parse autoneg_advertised and set up + * the appropriate PHY registers. First we will parse for + * autoneg_advertised software override. Since we can advertise + * a plethora of combinations, we need to check each bit + * individually. + */ + + /* First we clear all the 10/100 mb speed bits in the Auto-Neg + * Advertisement Register (Address 4) and the 1000 mb speed bits in + * the 1000Base-T Control Register (Address 9). */ + mii_autoneg_adv_reg &= ~MII_AR_SPEED_MASK; + + /* Need to parse MediaType and setup the + * appropriate PHY registers. */ + switch (hw->MediaType) { + case MEDIA_TYPE_AUTO_SENSOR: + mii_autoneg_adv_reg |= + (MII_AR_10T_HD_CAPS | + MII_AR_10T_FD_CAPS | + MII_AR_100TX_HD_CAPS| + MII_AR_100TX_FD_CAPS); + hw->autoneg_advertised = + ADVERTISE_10_HALF | + ADVERTISE_10_FULL | + ADVERTISE_100_HALF| + ADVERTISE_100_FULL; + break; + case MEDIA_TYPE_100M_FULL: + mii_autoneg_adv_reg |= MII_AR_100TX_FD_CAPS; + hw->autoneg_advertised = ADVERTISE_100_FULL; + break; + case MEDIA_TYPE_100M_HALF: + mii_autoneg_adv_reg |= MII_AR_100TX_HD_CAPS; + hw->autoneg_advertised = ADVERTISE_100_HALF; + break; + case MEDIA_TYPE_10M_FULL: + mii_autoneg_adv_reg |= MII_AR_10T_FD_CAPS; + hw->autoneg_advertised = ADVERTISE_10_FULL; + break; + default: + mii_autoneg_adv_reg |= MII_AR_10T_HD_CAPS; + hw->autoneg_advertised = ADVERTISE_10_HALF; + break; + } + + /* flow control fixed to enable all */ + mii_autoneg_adv_reg |= (MII_AR_ASM_DIR | MII_AR_PAUSE); + + hw->mii_autoneg_adv_reg = mii_autoneg_adv_reg; + + ret_val = atl2_write_phy_reg(hw, MII_ADVERTISE, mii_autoneg_adv_reg); + + if (ret_val) + return ret_val; + + return 0; +} + +/* + * Resets the PHY and make all config validate + * + * hw - Struct containing variables accessed by shared code + * + * Sets bit 15 and 12 of the MII Control regiser (for F001 bug) + */ +static s32 atl2_phy_commit(struct atl2_hw *hw) +{ + s32 ret_val; + u16 phy_data; + + phy_data = MII_CR_RESET | MII_CR_AUTO_NEG_EN | MII_CR_RESTART_AUTO_NEG; + ret_val = atl2_write_phy_reg(hw, MII_BMCR, phy_data); + if (ret_val) { + u32 val; + int i; + /* pcie serdes link may be down ! */ + for (i = 0; i < 25; i++) { + msleep(1); + val = ATL2_READ_REG(hw, REG_MDIO_CTRL); + if (!(val & (MDIO_START | MDIO_BUSY))) + break; + } + + if (0 != (val & (MDIO_START | MDIO_BUSY))) { + printk(KERN_ERR "atl2: PCIe link down for at least 25ms !\n"); + return ret_val; + } + } + return 0; +} + +static s32 atl2_phy_init(struct atl2_hw *hw) +{ + s32 ret_val; + u16 phy_val; + + if (hw->phy_configured) + return 0; + + /* Enable PHY */ + ATL2_WRITE_REGW(hw, REG_PHY_ENABLE, 1); + ATL2_WRITE_FLUSH(hw); + msleep(1); + + /* check if the PHY is in powersaving mode */ + atl2_write_phy_reg(hw, MII_DBG_ADDR, 0); + atl2_read_phy_reg(hw, MII_DBG_DATA, &phy_val); + + /* 024E / 124E 0r 0274 / 1274 ? */ + if (phy_val & 0x1000) { + phy_val &= ~0x1000; + atl2_write_phy_reg(hw, MII_DBG_DATA, phy_val); + } + + msleep(1); + + /*Enable PHY LinkChange Interrupt */ + ret_val = atl2_write_phy_reg(hw, 18, 0xC00); + if (ret_val) + return ret_val; + + /* setup AutoNeg parameters */ + ret_val = atl2_phy_setup_autoneg_adv(hw); + if (ret_val) + return ret_val; + + /* SW.Reset & En-Auto-Neg to restart Auto-Neg */ + ret_val = atl2_phy_commit(hw); + if (ret_val) + return ret_val; + + hw->phy_configured = true; + + return ret_val; +} + +static void atl2_set_mac_addr(struct atl2_hw *hw) +{ + u32 value; + /* 00-0B-6A-F6-00-DC + * 0: 6AF600DC 1: 000B + * low dword */ + value = (((u32)hw->mac_addr[2]) << 24) | + (((u32)hw->mac_addr[3]) << 16) | + (((u32)hw->mac_addr[4]) << 8) | + (((u32)hw->mac_addr[5])); + ATL2_WRITE_REG_ARRAY(hw, REG_MAC_STA_ADDR, 0, value); + /* hight dword */ + value = (((u32)hw->mac_addr[0]) << 8) | + (((u32)hw->mac_addr[1])); + ATL2_WRITE_REG_ARRAY(hw, REG_MAC_STA_ADDR, 1, value); +} + +/* + * check_eeprom_exist + * return 0 if eeprom exist + */ +static int atl2_check_eeprom_exist(struct atl2_hw *hw) +{ + u32 value; + + value = ATL2_READ_REG(hw, REG_SPI_FLASH_CTRL); + if (value & SPI_FLASH_CTRL_EN_VPD) { + value &= ~SPI_FLASH_CTRL_EN_VPD; + ATL2_WRITE_REG(hw, REG_SPI_FLASH_CTRL, value); + } + value = ATL2_READ_REGW(hw, REG_PCIE_CAP_LIST); + return ((value & 0xFF00) == 0x6C00) ? 0 : 1; +} + +/* FIXME: This doesn't look right. -- CHS */ +static bool atl2_write_eeprom(struct atl2_hw *hw, u32 offset, u32 value) +{ + return true; +} + +static bool atl2_read_eeprom(struct atl2_hw *hw, u32 Offset, u32 *pValue) +{ + int i; + u32 Control; + + if (Offset & 0x3) + return false; /* address do not align */ + + ATL2_WRITE_REG(hw, REG_VPD_DATA, 0); + Control = (Offset & VPD_CAP_VPD_ADDR_MASK) << VPD_CAP_VPD_ADDR_SHIFT; + ATL2_WRITE_REG(hw, REG_VPD_CAP, Control); + + for (i = 0; i < 10; i++) { + msleep(2); + Control = ATL2_READ_REG(hw, REG_VPD_CAP); + if (Control & VPD_CAP_VPD_FLAG) + break; + } + + if (Control & VPD_CAP_VPD_FLAG) { + *pValue = ATL2_READ_REG(hw, REG_VPD_DATA); + return true; + } + return false; /* timeout */ +} + +static void atl2_force_ps(struct atl2_hw *hw) +{ + u16 phy_val; + + atl2_write_phy_reg(hw, MII_DBG_ADDR, 0); + atl2_read_phy_reg(hw, MII_DBG_DATA, &phy_val); + atl2_write_phy_reg(hw, MII_DBG_DATA, phy_val | 0x1000); + + atl2_write_phy_reg(hw, MII_DBG_ADDR, 2); + atl2_write_phy_reg(hw, MII_DBG_DATA, 0x3000); + atl2_write_phy_reg(hw, MII_DBG_ADDR, 3); + atl2_write_phy_reg(hw, MII_DBG_DATA, 0); +} + +/* This is the only thing that needs to be changed to adjust the + * maximum number of ports that the driver can manage. + */ +#define ATL2_MAX_NIC 4 + +#define OPTION_UNSET -1 +#define OPTION_DISABLED 0 +#define OPTION_ENABLED 1 + +/* All parameters are treated the same, as an integer array of values. + * This macro just reduces the need to repeat the same declaration code + * over and over (plus this helps to avoid typo bugs). + */ +#define ATL2_PARAM_INIT {[0 ... ATL2_MAX_NIC] = OPTION_UNSET} +#ifndef module_param_array +/* Module Parameters are always initialized to -1, so that the driver + * can tell the difference between no user specified value or the + * user asking for the default value. + * The true default values are loaded in when atl2_check_options is called. + * + * This is a GCC extension to ANSI C. + * See the item "Labeled Elements in Initializers" in the section + * "Extensions to the C Language Family" of the GCC documentation. + */ + +#define ATL2_PARAM(X, desc) \ + static const int X[ATL2_MAX_NIC + 1] = ATL2_PARAM_INIT; \ + MODULE_PARM(X, "1-" __MODULE_STRING(ATL2_MAX_NIC) "i"); \ + MODULE_PARM_DESC(X, desc); +#else +#define ATL2_PARAM(X, desc) \ + static int X[ATL2_MAX_NIC+1] = ATL2_PARAM_INIT; \ + static unsigned int num_##X; \ + module_param_array_named(X, X, int, &num_##X, 0); \ + MODULE_PARM_DESC(X, desc); +#endif + +/* + * Transmit Memory Size + * Valid Range: 64-2048 + * Default Value: 128 + */ +#define ATL2_MIN_TX_MEMSIZE 4 /* 4KB */ +#define ATL2_MAX_TX_MEMSIZE 64 /* 64KB */ +#define ATL2_DEFAULT_TX_MEMSIZE 8 /* 8KB */ +ATL2_PARAM(TxMemSize, "Bytes of Transmit Memory"); + +/* + * Receive Memory Block Count + * Valid Range: 16-512 + * Default Value: 128 + */ +#define ATL2_MIN_RXD_COUNT 16 +#define ATL2_MAX_RXD_COUNT 512 +#define ATL2_DEFAULT_RXD_COUNT 64 +ATL2_PARAM(RxMemBlock, "Number of receive memory block"); + +/* + * User Specified MediaType Override + * + * Valid Range: 0-5 + * - 0 - auto-negotiate at all supported speeds + * - 1 - only link at 1000Mbps Full Duplex + * - 2 - only link at 100Mbps Full Duplex + * - 3 - only link at 100Mbps Half Duplex + * - 4 - only link at 10Mbps Full Duplex + * - 5 - only link at 10Mbps Half Duplex + * Default Value: 0 + */ +ATL2_PARAM(MediaType, "MediaType Select"); + +/* + * Interrupt Moderate Timer in units of 2048 ns (~2 us) + * Valid Range: 10-65535 + * Default Value: 45000(90ms) + */ +#define INT_MOD_DEFAULT_CNT 100 /* 200us */ +#define INT_MOD_MAX_CNT 65000 +#define INT_MOD_MIN_CNT 50 +ATL2_PARAM(IntModTimer, "Interrupt Moderator Timer"); + +/* + * FlashVendor + * Valid Range: 0-2 + * 0 - Atmel + * 1 - SST + * 2 - ST + */ +ATL2_PARAM(FlashVendor, "SPI Flash Vendor"); + +#define AUTONEG_ADV_DEFAULT 0x2F +#define AUTONEG_ADV_MASK 0x2F +#define FLOW_CONTROL_DEFAULT FLOW_CONTROL_FULL + +#define FLASH_VENDOR_DEFAULT 0 +#define FLASH_VENDOR_MIN 0 +#define FLASH_VENDOR_MAX 2 + +struct atl2_option { + enum { enable_option, range_option, list_option } type; + char *name; + char *err; + int def; + union { + struct { /* range_option info */ + int min; + int max; + } r; + struct { /* list_option info */ + int nr; + struct atl2_opt_list { int i; char *str; } *p; + } l; + } arg; +}; + +static int atl2_validate_option(int *value, struct atl2_option *opt) +{ + int i; + struct atl2_opt_list *ent; + + if (*value == OPTION_UNSET) { + *value = opt->def; + return 0; + } + + switch (opt->type) { + case enable_option: + switch (*value) { + case OPTION_ENABLED: + printk(KERN_INFO "%s Enabled\n", opt->name); + return 0; + break; + case OPTION_DISABLED: + printk(KERN_INFO "%s Disabled\n", opt->name); + return 0; + break; + } + break; + case range_option: + if (*value >= opt->arg.r.min && *value <= opt->arg.r.max) { + printk(KERN_INFO "%s set to %i\n", opt->name, *value); + return 0; + } + break; + case list_option: + for (i = 0; i < opt->arg.l.nr; i++) { + ent = &opt->arg.l.p[i]; + if (*value == ent->i) { + if (ent->str[0] != '\0') + printk(KERN_INFO "%s\n", ent->str); + return 0; + } + } + break; + default: + BUG(); + } + + printk(KERN_INFO "Invalid %s specified (%i) %s\n", + opt->name, *value, opt->err); + *value = opt->def; + return -1; +} + +/** + * atl2_check_options - Range Checking for Command Line Parameters + * @adapter: board private structure + * + * This routine checks all command line parameters for valid user + * input. If an invalid value is given, or if no user specified + * value exists, a default value is used. The final value is stored + * in a variable in the adapter structure. + */ +static void atl2_check_options(struct atl2_adapter *adapter) +{ + int val; + struct atl2_option opt; + int bd = adapter->bd_number; + if (bd >= ATL2_MAX_NIC) { + printk(KERN_NOTICE "Warning: no configuration for board #%i\n", + bd); + printk(KERN_NOTICE "Using defaults for all values\n"); +#ifndef module_param_array + bd = ATL2_MAX_NIC; +#endif + } + + /* Bytes of Transmit Memory */ + opt.type = range_option; + opt.name = "Bytes of Transmit Memory"; + opt.err = "using default of " __MODULE_STRING(ATL2_DEFAULT_TX_MEMSIZE); + opt.def = ATL2_DEFAULT_TX_MEMSIZE; + opt.arg.r.min = ATL2_MIN_TX_MEMSIZE; + opt.arg.r.max = ATL2_MAX_TX_MEMSIZE; +#ifdef module_param_array + if (num_TxMemSize > bd) { +#endif + val = TxMemSize[bd]; + atl2_validate_option(&val, &opt); + adapter->txd_ring_size = ((u32) val) * 1024; +#ifdef module_param_array + } else + adapter->txd_ring_size = ((u32)opt.def) * 1024; +#endif + /* txs ring size: */ + adapter->txs_ring_size = adapter->txd_ring_size / 128; + if (adapter->txs_ring_size > 160) + adapter->txs_ring_size = 160; + + /* Receive Memory Block Count */ + opt.type = range_option; + opt.name = "Number of receive memory block"; + opt.err = "using default of " __MODULE_STRING(ATL2_DEFAULT_RXD_COUNT); + opt.def = ATL2_DEFAULT_RXD_COUNT; + opt.arg.r.min = ATL2_MIN_RXD_COUNT; + opt.arg.r.max = ATL2_MAX_RXD_COUNT; +#ifdef module_param_array + if (num_RxMemBlock > bd) { +#endif + val = RxMemBlock[bd]; + atl2_validate_option(&val, &opt); + adapter->rxd_ring_size = (u32)val; + /* FIXME */ + /* ((u16)val)&~1; */ /* even number */ +#ifdef module_param_array + } else + adapter->rxd_ring_size = (u32)opt.def; +#endif + /* init RXD Flow control value */ + adapter->hw.fc_rxd_hi = (adapter->rxd_ring_size / 8) * 7; + adapter->hw.fc_rxd_lo = (ATL2_MIN_RXD_COUNT / 8) > + (adapter->rxd_ring_size / 12) ? (ATL2_MIN_RXD_COUNT / 8) : + (adapter->rxd_ring_size / 12); + + /* Interrupt Moderate Timer */ + opt.type = range_option; + opt.name = "Interrupt Moderate Timer"; + opt.err = "using default of " __MODULE_STRING(INT_MOD_DEFAULT_CNT); + opt.def = INT_MOD_DEFAULT_CNT; + opt.arg.r.min = INT_MOD_MIN_CNT; + opt.arg.r.max = INT_MOD_MAX_CNT; +#ifdef module_param_array + if (num_IntModTimer > bd) { +#endif + val = IntModTimer[bd]; + atl2_validate_option(&val, &opt); + adapter->imt = (u16) val; +#ifdef module_param_array + } else + adapter->imt = (u16)(opt.def); +#endif + /* Flash Vendor */ + opt.type = range_option; + opt.name = "SPI Flash Vendor"; + opt.err = "using default of " __MODULE_STRING(FLASH_VENDOR_DEFAULT); + opt.def = FLASH_VENDOR_DEFAULT; + opt.arg.r.min = FLASH_VENDOR_MIN; + opt.arg.r.max = FLASH_VENDOR_MAX; +#ifdef module_param_array + if (num_FlashVendor > bd) { +#endif + val = FlashVendor[bd]; + atl2_validate_option(&val, &opt); + adapter->hw.flash_vendor = (u8) val; +#ifdef module_param_array + } else + adapter->hw.flash_vendor = (u8)(opt.def); +#endif + /* MediaType */ + opt.type = range_option; + opt.name = "Speed/Duplex Selection"; + opt.err = "using default of " __MODULE_STRING(MEDIA_TYPE_AUTO_SENSOR); + opt.def = MEDIA_TYPE_AUTO_SENSOR; + opt.arg.r.min = MEDIA_TYPE_AUTO_SENSOR; + opt.arg.r.max = MEDIA_TYPE_10M_HALF; +#ifdef module_param_array + if (num_MediaType > bd) { +#endif + val = MediaType[bd]; + atl2_validate_option(&val, &opt); + adapter->hw.MediaType = (u16) val; +#ifdef module_param_array + } else + adapter->hw.MediaType = (u16)(opt.def); +#endif +} diff --git a/drivers/net/ethernet/atheros/atlx/atl2.h b/drivers/net/ethernet/atheros/atlx/atl2.h new file mode 100644 index 0000000..3ebe19f --- /dev/null +++ b/drivers/net/ethernet/atheros/atlx/atl2.h @@ -0,0 +1,524 @@ +/* atl2.h -- atl2 driver definitions + * + * Copyright(c) 2007 Atheros Corporation. All rights reserved. + * Copyright(c) 2006 xiong huang + * Copyright(c) 2007 Chris Snook + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _ATL2_H_ +#define _ATL2_H_ + +#include +#include + +#ifndef _ATL2_HW_H_ +#define _ATL2_HW_H_ + +#ifndef _ATL2_OSDEP_H_ +#define _ATL2_OSDEP_H_ + +#include +#include +#include +#include + +#include "atlx.h" + +#ifdef ETHTOOL_OPS_COMPAT +extern int ethtool_ioctl(struct ifreq *ifr); +#endif + +#define PCI_COMMAND_REGISTER PCI_COMMAND +#define CMD_MEM_WRT_INVALIDATE PCI_COMMAND_INVALIDATE + +#define ATL2_WRITE_REG(a, reg, value) (iowrite32((value), \ + ((a)->hw_addr + (reg)))) + +#define ATL2_WRITE_FLUSH(a) (ioread32((a)->hw_addr)) + +#define ATL2_READ_REG(a, reg) (ioread32((a)->hw_addr + (reg))) + +#define ATL2_WRITE_REGB(a, reg, value) (iowrite8((value), \ + ((a)->hw_addr + (reg)))) + +#define ATL2_READ_REGB(a, reg) (ioread8((a)->hw_addr + (reg))) + +#define ATL2_WRITE_REGW(a, reg, value) (iowrite16((value), \ + ((a)->hw_addr + (reg)))) + +#define ATL2_READ_REGW(a, reg) (ioread16((a)->hw_addr + (reg))) + +#define ATL2_WRITE_REG_ARRAY(a, reg, offset, value) \ + (iowrite32((value), (((a)->hw_addr + (reg)) + ((offset) << 2)))) + +#define ATL2_READ_REG_ARRAY(a, reg, offset) \ + (ioread32(((a)->hw_addr + (reg)) + ((offset) << 2))) + +#endif /* _ATL2_OSDEP_H_ */ + +struct atl2_adapter; +struct atl2_hw; + +/* function prototype */ +static s32 atl2_reset_hw(struct atl2_hw *hw); +static s32 atl2_read_mac_addr(struct atl2_hw *hw); +static s32 atl2_init_hw(struct atl2_hw *hw); +static s32 atl2_get_speed_and_duplex(struct atl2_hw *hw, u16 *speed, + u16 *duplex); +static u32 atl2_hash_mc_addr(struct atl2_hw *hw, u8 *mc_addr); +static void atl2_hash_set(struct atl2_hw *hw, u32 hash_value); +static s32 atl2_read_phy_reg(struct atl2_hw *hw, u16 reg_addr, u16 *phy_data); +static s32 atl2_write_phy_reg(struct atl2_hw *hw, u32 reg_addr, u16 phy_data); +static void atl2_read_pci_cfg(struct atl2_hw *hw, u32 reg, u16 *value); +static void atl2_write_pci_cfg(struct atl2_hw *hw, u32 reg, u16 *value); +static void atl2_set_mac_addr(struct atl2_hw *hw); +static bool atl2_read_eeprom(struct atl2_hw *hw, u32 Offset, u32 *pValue); +static bool atl2_write_eeprom(struct atl2_hw *hw, u32 offset, u32 value); +static s32 atl2_phy_init(struct atl2_hw *hw); +static int atl2_check_eeprom_exist(struct atl2_hw *hw); +static void atl2_force_ps(struct atl2_hw *hw); + +/* register definition */ + +/* Block IDLE Status Register */ +#define IDLE_STATUS_RXMAC 1 /* 1: RXMAC is non-IDLE */ +#define IDLE_STATUS_TXMAC 2 /* 1: TXMAC is non-IDLE */ +#define IDLE_STATUS_DMAR 8 /* 1: DMAR is non-IDLE */ +#define IDLE_STATUS_DMAW 4 /* 1: DMAW is non-IDLE */ + +/* MDIO Control Register */ +#define MDIO_WAIT_TIMES 10 + +/* MAC Control Register */ +#define MAC_CTRL_DBG_TX_BKPRESURE 0x100000 /* 1: TX max backoff */ +#define MAC_CTRL_MACLP_CLK_PHY 0x8000000 /* 1: 25MHz from phy */ +#define MAC_CTRL_HALF_LEFT_BUF_SHIFT 28 +#define MAC_CTRL_HALF_LEFT_BUF_MASK 0xF /* MAC retry buf x32B */ + +/* Internal SRAM Partition Register */ +#define REG_SRAM_TXRAM_END 0x1500 /* Internal tail address of TXRAM + * default: 2byte*1024 */ +#define REG_SRAM_RXRAM_END 0x1502 /* Internal tail address of RXRAM + * default: 2byte*1024 */ + +/* Descriptor Control register */ +#define REG_TXD_BASE_ADDR_LO 0x1544 /* The base address of the Transmit + * Data Mem low 32-bit(dword align) */ +#define REG_TXD_MEM_SIZE 0x1548 /* Transmit Data Memory size(by + * double word , max 256KB) */ +#define REG_TXS_BASE_ADDR_LO 0x154C /* The base address of the Transmit + * Status Memory low 32-bit(dword word + * align) */ +#define REG_TXS_MEM_SIZE 0x1550 /* double word unit, max 4*2047 + * bytes. */ +#define REG_RXD_BASE_ADDR_LO 0x1554 /* The base address of the Transmit + * Status Memory low 32-bit(unit 8 + * bytes) */ +#define REG_RXD_BUF_NUM 0x1558 /* Receive Data & Status Memory buffer + * number (unit 1536bytes, max + * 1536*2047) */ + +/* DMAR Control Register */ +#define REG_DMAR 0x1580 +#define DMAR_EN 0x1 /* 1: Enable DMAR */ + +/* TX Cur-Through (early tx threshold) Control Register */ +#define REG_TX_CUT_THRESH 0x1590 /* TxMac begin transmit packet + * threshold(unit word) */ + +/* DMAW Control Register */ +#define REG_DMAW 0x15A0 +#define DMAW_EN 0x1 + +/* Flow control register */ +#define REG_PAUSE_ON_TH 0x15A8 /* RXD high watermark of overflow + * threshold configuration register */ +#define REG_PAUSE_OFF_TH 0x15AA /* RXD lower watermark of overflow + * threshold configuration register */ + +/* Mailbox Register */ +#define REG_MB_TXD_WR_IDX 0x15f0 /* double word align */ +#define REG_MB_RXD_RD_IDX 0x15F4 /* RXD Read index (unit: 1536byets) */ + +/* Interrupt Status Register */ +#define ISR_TIMER 1 /* Interrupt when Timer counts down to zero */ +#define ISR_MANUAL 2 /* Software manual interrupt, for debug. Set + * when SW_MAN_INT_EN is set in Table 51 + * Selene Master Control Register + * (Offset 0x1400). */ +#define ISR_RXF_OV 4 /* RXF overflow interrupt */ +#define ISR_TXF_UR 8 /* TXF underrun interrupt */ +#define ISR_TXS_OV 0x10 /* Internal transmit status buffer full + * interrupt */ +#define ISR_RXS_OV 0x20 /* Internal receive status buffer full + * interrupt */ +#define ISR_LINK_CHG 0x40 /* Link Status Change Interrupt */ +#define ISR_HOST_TXD_UR 0x80 +#define ISR_HOST_RXD_OV 0x100 /* Host rx data memory full , one pulse */ +#define ISR_DMAR_TO_RST 0x200 /* DMAR op timeout interrupt. SW should + * do Reset */ +#define ISR_DMAW_TO_RST 0x400 +#define ISR_PHY 0x800 /* phy interrupt */ +#define ISR_TS_UPDATE 0x10000 /* interrupt after new tx pkt status written + * to host */ +#define ISR_RS_UPDATE 0x20000 /* interrupt ater new rx pkt status written + * to host. */ +#define ISR_TX_EARLY 0x40000 /* interrupt when txmac begin transmit one + * packet */ + +#define ISR_TX_EVENT (ISR_TXF_UR | ISR_TXS_OV | ISR_HOST_TXD_UR |\ + ISR_TS_UPDATE | ISR_TX_EARLY) +#define ISR_RX_EVENT (ISR_RXF_OV | ISR_RXS_OV | ISR_HOST_RXD_OV |\ + ISR_RS_UPDATE) + +#define IMR_NORMAL_MASK (\ + /*ISR_LINK_CHG |*/\ + ISR_MANUAL |\ + ISR_DMAR_TO_RST |\ + ISR_DMAW_TO_RST |\ + ISR_PHY |\ + ISR_PHY_LINKDOWN |\ + ISR_TS_UPDATE |\ + ISR_RS_UPDATE) + +/* Receive MAC Statistics Registers */ +#define REG_STS_RX_PAUSE 0x1700 /* Num pause packets received */ +#define REG_STS_RXD_OV 0x1704 /* Num frames dropped due to RX + * FIFO overflow */ +#define REG_STS_RXS_OV 0x1708 /* Num frames dropped due to RX + * Status Buffer Overflow */ +#define REG_STS_RX_FILTER 0x170C /* Num packets dropped due to + * address filtering */ + +/* MII definitions */ + +/* PHY Common Register */ +#define MII_SMARTSPEED 0x14 +#define MII_DBG_ADDR 0x1D +#define MII_DBG_DATA 0x1E + +/* PCI Command Register Bit Definitions */ +#define PCI_REG_COMMAND 0x04 +#define CMD_IO_SPACE 0x0001 +#define CMD_MEMORY_SPACE 0x0002 +#define CMD_BUS_MASTER 0x0004 + +#define MEDIA_TYPE_100M_FULL 1 +#define MEDIA_TYPE_100M_HALF 2 +#define MEDIA_TYPE_10M_FULL 3 +#define MEDIA_TYPE_10M_HALF 4 + +#define AUTONEG_ADVERTISE_SPEED_DEFAULT 0x000F /* Everything */ + +/* The size (in bytes) of a ethernet packet */ +#define ENET_HEADER_SIZE 14 +#define MAXIMUM_ETHERNET_FRAME_SIZE 1518 /* with FCS */ +#define MINIMUM_ETHERNET_FRAME_SIZE 64 /* with FCS */ +#define ETHERNET_FCS_SIZE 4 +#define MAX_JUMBO_FRAME_SIZE 0x2000 +#define VLAN_SIZE 4 + +struct tx_pkt_header { + unsigned pkt_size:11; + unsigned:4; /* reserved */ + unsigned ins_vlan:1; /* txmac should insert vlan */ + unsigned short vlan; /* vlan tag */ +}; +/* FIXME: replace above bitfields with MASK/SHIFT defines below */ +#define TX_PKT_HEADER_SIZE_MASK 0x7FF +#define TX_PKT_HEADER_SIZE_SHIFT 0 +#define TX_PKT_HEADER_INS_VLAN_MASK 0x1 +#define TX_PKT_HEADER_INS_VLAN_SHIFT 15 +#define TX_PKT_HEADER_VLAN_TAG_MASK 0xFFFF +#define TX_PKT_HEADER_VLAN_TAG_SHIFT 16 + +struct tx_pkt_status { + unsigned pkt_size:11; + unsigned:5; /* reserved */ + unsigned ok:1; /* current packet transmitted without error */ + unsigned bcast:1; /* broadcast packet */ + unsigned mcast:1; /* multicast packet */ + unsigned pause:1; /* transmiited a pause frame */ + unsigned ctrl:1; + unsigned defer:1; /* current packet is xmitted with defer */ + unsigned exc_defer:1; + unsigned single_col:1; + unsigned multi_col:1; + unsigned late_col:1; + unsigned abort_col:1; + unsigned underun:1; /* current packet is aborted + * due to txram underrun */ + unsigned:3; /* reserved */ + unsigned update:1; /* always 1'b1 in tx_status_buf */ +}; +/* FIXME: replace above bitfields with MASK/SHIFT defines below */ +#define TX_PKT_STATUS_SIZE_MASK 0x7FF +#define TX_PKT_STATUS_SIZE_SHIFT 0 +#define TX_PKT_STATUS_OK_MASK 0x1 +#define TX_PKT_STATUS_OK_SHIFT 16 +#define TX_PKT_STATUS_BCAST_MASK 0x1 +#define TX_PKT_STATUS_BCAST_SHIFT 17 +#define TX_PKT_STATUS_MCAST_MASK 0x1 +#define TX_PKT_STATUS_MCAST_SHIFT 18 +#define TX_PKT_STATUS_PAUSE_MASK 0x1 +#define TX_PKT_STATUS_PAUSE_SHIFT 19 +#define TX_PKT_STATUS_CTRL_MASK 0x1 +#define TX_PKT_STATUS_CTRL_SHIFT 20 +#define TX_PKT_STATUS_DEFER_MASK 0x1 +#define TX_PKT_STATUS_DEFER_SHIFT 21 +#define TX_PKT_STATUS_EXC_DEFER_MASK 0x1 +#define TX_PKT_STATUS_EXC_DEFER_SHIFT 22 +#define TX_PKT_STATUS_SINGLE_COL_MASK 0x1 +#define TX_PKT_STATUS_SINGLE_COL_SHIFT 23 +#define TX_PKT_STATUS_MULTI_COL_MASK 0x1 +#define TX_PKT_STATUS_MULTI_COL_SHIFT 24 +#define TX_PKT_STATUS_LATE_COL_MASK 0x1 +#define TX_PKT_STATUS_LATE_COL_SHIFT 25 +#define TX_PKT_STATUS_ABORT_COL_MASK 0x1 +#define TX_PKT_STATUS_ABORT_COL_SHIFT 26 +#define TX_PKT_STATUS_UNDERRUN_MASK 0x1 +#define TX_PKT_STATUS_UNDERRUN_SHIFT 27 +#define TX_PKT_STATUS_UPDATE_MASK 0x1 +#define TX_PKT_STATUS_UPDATE_SHIFT 31 + +struct rx_pkt_status { + unsigned pkt_size:11; /* packet size, max 2047 bytes */ + unsigned:5; /* reserved */ + unsigned ok:1; /* current packet received ok without error */ + unsigned bcast:1; /* current packet is broadcast */ + unsigned mcast:1; /* current packet is multicast */ + unsigned pause:1; + unsigned ctrl:1; + unsigned crc:1; /* received a packet with crc error */ + unsigned code:1; /* received a packet with code error */ + unsigned runt:1; /* received a packet less than 64 bytes + * with good crc */ + unsigned frag:1; /* received a packet less than 64 bytes + * with bad crc */ + unsigned trunc:1; /* current frame truncated due to rxram full */ + unsigned align:1; /* this packet is alignment error */ + unsigned vlan:1; /* this packet has vlan */ + unsigned:3; /* reserved */ + unsigned update:1; + unsigned short vtag; /* vlan tag */ + unsigned:16; +}; +/* FIXME: replace above bitfields with MASK/SHIFT defines below */ +#define RX_PKT_STATUS_SIZE_MASK 0x7FF +#define RX_PKT_STATUS_SIZE_SHIFT 0 +#define RX_PKT_STATUS_OK_MASK 0x1 +#define RX_PKT_STATUS_OK_SHIFT 16 +#define RX_PKT_STATUS_BCAST_MASK 0x1 +#define RX_PKT_STATUS_BCAST_SHIFT 17 +#define RX_PKT_STATUS_MCAST_MASK 0x1 +#define RX_PKT_STATUS_MCAST_SHIFT 18 +#define RX_PKT_STATUS_PAUSE_MASK 0x1 +#define RX_PKT_STATUS_PAUSE_SHIFT 19 +#define RX_PKT_STATUS_CTRL_MASK 0x1 +#define RX_PKT_STATUS_CTRL_SHIFT 20 +#define RX_PKT_STATUS_CRC_MASK 0x1 +#define RX_PKT_STATUS_CRC_SHIFT 21 +#define RX_PKT_STATUS_CODE_MASK 0x1 +#define RX_PKT_STATUS_CODE_SHIFT 22 +#define RX_PKT_STATUS_RUNT_MASK 0x1 +#define RX_PKT_STATUS_RUNT_SHIFT 23 +#define RX_PKT_STATUS_FRAG_MASK 0x1 +#define RX_PKT_STATUS_FRAG_SHIFT 24 +#define RX_PKT_STATUS_TRUNK_MASK 0x1 +#define RX_PKT_STATUS_TRUNK_SHIFT 25 +#define RX_PKT_STATUS_ALIGN_MASK 0x1 +#define RX_PKT_STATUS_ALIGN_SHIFT 26 +#define RX_PKT_STATUS_VLAN_MASK 0x1 +#define RX_PKT_STATUS_VLAN_SHIFT 27 +#define RX_PKT_STATUS_UPDATE_MASK 0x1 +#define RX_PKT_STATUS_UPDATE_SHIFT 31 +#define RX_PKT_STATUS_VLAN_TAG_MASK 0xFFFF +#define RX_PKT_STATUS_VLAN_TAG_SHIFT 32 + +struct rx_desc { + struct rx_pkt_status status; + unsigned char packet[1536-sizeof(struct rx_pkt_status)]; +}; + +enum atl2_speed_duplex { + atl2_10_half = 0, + atl2_10_full = 1, + atl2_100_half = 2, + atl2_100_full = 3 +}; + +struct atl2_spi_flash_dev { + const char *manu_name; /* manufacturer id */ + /* op-code */ + u8 cmdWRSR; + u8 cmdREAD; + u8 cmdPROGRAM; + u8 cmdWREN; + u8 cmdWRDI; + u8 cmdRDSR; + u8 cmdRDID; + u8 cmdSECTOR_ERASE; + u8 cmdCHIP_ERASE; +}; + +/* Structure containing variables used by the shared code (atl2_hw.c) */ +struct atl2_hw { + u8 __iomem *hw_addr; + void *back; + + u8 preamble_len; + u8 max_retry; /* Retransmission maximum, afterwards the + * packet will be discarded. */ + u8 jam_ipg; /* IPG to start JAM for collision based flow + * control in half-duplex mode. In unit of + * 8-bit time. */ + u8 ipgt; /* Desired back to back inter-packet gap. The + * default is 96-bit time. */ + u8 min_ifg; /* Minimum number of IFG to enforce in between + * RX frames. Frame gap below such IFP is + * dropped. */ + u8 ipgr1; /* 64bit Carrier-Sense window */ + u8 ipgr2; /* 96-bit IPG window */ + u8 retry_buf; /* When half-duplex mode, should hold some + * bytes for mac retry . (8*4bytes unit) */ + + u16 fc_rxd_hi; + u16 fc_rxd_lo; + u16 lcol; /* Collision Window */ + u16 max_frame_size; + + u16 MediaType; + u16 autoneg_advertised; + u16 pci_cmd_word; + + u16 mii_autoneg_adv_reg; + + u32 mem_rang; + u32 txcw; + u32 mc_filter_type; + u32 num_mc_addrs; + u32 collision_delta; + u32 tx_packet_delta; + u16 phy_spd_default; + + u16 device_id; + u16 vendor_id; + u16 subsystem_id; + u16 subsystem_vendor_id; + u8 revision_id; + + /* spi flash */ + u8 flash_vendor; + + u8 dma_fairness; + u8 mac_addr[ETH_ALEN]; + u8 perm_mac_addr[ETH_ALEN]; + + /* FIXME */ + /* bool phy_preamble_sup; */ + bool phy_configured; +}; + +#endif /* _ATL2_HW_H_ */ + +struct atl2_ring_header { + /* pointer to the descriptor ring memory */ + void *desc; + /* physical address of the descriptor ring */ + dma_addr_t dma; + /* length of descriptor ring in bytes */ + unsigned int size; +}; + +/* board specific private data structure */ +struct atl2_adapter { + /* OS defined structs */ + struct net_device *netdev; + struct pci_dev *pdev; + u32 wol; + u16 link_speed; + u16 link_duplex; + + spinlock_t stats_lock; + + struct work_struct reset_task; + struct work_struct link_chg_task; + struct timer_list watchdog_timer; + struct timer_list phy_config_timer; + + unsigned long cfg_phy; + bool mac_disabled; + + /* All Descriptor memory */ + dma_addr_t ring_dma; + void *ring_vir_addr; + int ring_size; + + struct tx_pkt_header *txd_ring; + dma_addr_t txd_dma; + + struct tx_pkt_status *txs_ring; + dma_addr_t txs_dma; + + struct rx_desc *rxd_ring; + dma_addr_t rxd_dma; + + u32 txd_ring_size; /* bytes per unit */ + u32 txs_ring_size; /* dwords per unit */ + u32 rxd_ring_size; /* 1536 bytes per unit */ + + /* read /write ptr: */ + /* host */ + u32 txd_write_ptr; + u32 txs_next_clear; + u32 rxd_read_ptr; + + /* nic */ + atomic_t txd_read_ptr; + atomic_t txs_write_ptr; + u32 rxd_write_ptr; + + /* Interrupt Moderator timer ( 2us resolution) */ + u16 imt; + /* Interrupt Clear timer (2us resolution) */ + u16 ict; + + unsigned long flags; + /* structs defined in atl2_hw.h */ + u32 bd_number; /* board number */ + bool pci_using_64; + bool have_msi; + struct atl2_hw hw; + + u32 usr_cmd; + /* FIXME */ + /* u32 regs_buff[ATL2_REGS_LEN]; */ + u32 pci_state[16]; + + u32 *config_space; +}; + +enum atl2_state_t { + __ATL2_TESTING, + __ATL2_RESETTING, + __ATL2_DOWN +}; + +#endif /* _ATL2_H_ */ diff --git a/drivers/net/ethernet/atheros/atlx/atlx.c b/drivers/net/ethernet/atheros/atlx/atlx.c new file mode 100644 index 0000000..13f5818 --- /dev/null +++ b/drivers/net/ethernet/atheros/atlx/atlx.c @@ -0,0 +1,285 @@ +/* atlx.c -- common functions for Attansic network drivers + * + * Copyright(c) 2005 - 2006 Attansic Corporation. All rights reserved. + * Copyright(c) 2006 - 2007 Chris Snook + * Copyright(c) 2006 - 2008 Jay Cliburn + * Copyright(c) 2007 Atheros Corporation. All rights reserved. + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Including this file like a header is a temporary hack, I promise. -- CHS */ +#ifndef ATLX_C +#define ATLX_C + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atlx.h" + +static s32 atlx_read_phy_reg(struct atl1_hw *hw, u16 reg_addr, u16 *phy_data); +static u32 atlx_hash_mc_addr(struct atl1_hw *hw, u8 *mc_addr); +static void atlx_set_mac_addr(struct atl1_hw *hw); + +static struct atlx_spi_flash_dev flash_table[] = { +/* MFR_NAME WRSR READ PRGM WREN WRDI RDSR RDID SEC_ERS CHIP_ERS */ + {"Atmel", 0x00, 0x03, 0x02, 0x06, 0x04, 0x05, 0x15, 0x52, 0x62}, + {"SST", 0x01, 0x03, 0x02, 0x06, 0x04, 0x05, 0x90, 0x20, 0x60}, + {"ST", 0x01, 0x03, 0x02, 0x06, 0x04, 0x05, 0xAB, 0xD8, 0xC7}, +}; + +static int atlx_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd) +{ + switch (cmd) { + case SIOCGMIIPHY: + case SIOCGMIIREG: + case SIOCSMIIREG: + return atlx_mii_ioctl(netdev, ifr, cmd); + default: + return -EOPNOTSUPP; + } +} + +/** + * atlx_set_mac - Change the Ethernet Address of the NIC + * @netdev: network interface device structure + * @p: pointer to an address structure + * + * Returns 0 on success, negative on failure + */ +static int atlx_set_mac(struct net_device *netdev, void *p) +{ + struct atlx_adapter *adapter = netdev_priv(netdev); + struct sockaddr *addr = p; + + if (netif_running(netdev)) + return -EBUSY; + + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + + memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len); + memcpy(adapter->hw.mac_addr, addr->sa_data, netdev->addr_len); + + atlx_set_mac_addr(&adapter->hw); + return 0; +} + +static void atlx_check_for_link(struct atlx_adapter *adapter) +{ + struct net_device *netdev = adapter->netdev; + u16 phy_data = 0; + + spin_lock(&adapter->lock); + adapter->phy_timer_pending = false; + atlx_read_phy_reg(&adapter->hw, MII_BMSR, &phy_data); + atlx_read_phy_reg(&adapter->hw, MII_BMSR, &phy_data); + spin_unlock(&adapter->lock); + + /* notify upper layer link down ASAP */ + if (!(phy_data & BMSR_LSTATUS)) { + /* Link Down */ + if (netif_carrier_ok(netdev)) { + /* old link state: Up */ + dev_info(&adapter->pdev->dev, "%s link is down\n", + netdev->name); + adapter->link_speed = SPEED_0; + netif_carrier_off(netdev); + } + } + schedule_work(&adapter->link_chg_task); +} + +/** + * atlx_set_multi - Multicast and Promiscuous mode set + * @netdev: network interface device structure + * + * The set_multi entry point is called whenever the multicast address + * list or the network interface flags are updated. This routine is + * responsible for configuring the hardware for proper multicast, + * promiscuous mode, and all-multi behavior. + */ +static void atlx_set_multi(struct net_device *netdev) +{ + struct atlx_adapter *adapter = netdev_priv(netdev); + struct atlx_hw *hw = &adapter->hw; + struct netdev_hw_addr *ha; + u32 rctl; + u32 hash_value; + + /* Check for Promiscuous and All Multicast modes */ + rctl = ioread32(hw->hw_addr + REG_MAC_CTRL); + if (netdev->flags & IFF_PROMISC) + rctl |= MAC_CTRL_PROMIS_EN; + else if (netdev->flags & IFF_ALLMULTI) { + rctl |= MAC_CTRL_MC_ALL_EN; + rctl &= ~MAC_CTRL_PROMIS_EN; + } else + rctl &= ~(MAC_CTRL_PROMIS_EN | MAC_CTRL_MC_ALL_EN); + + iowrite32(rctl, hw->hw_addr + REG_MAC_CTRL); + + /* clear the old settings from the multicast hash table */ + iowrite32(0, hw->hw_addr + REG_RX_HASH_TABLE); + iowrite32(0, (hw->hw_addr + REG_RX_HASH_TABLE) + (1 << 2)); + + /* compute mc addresses' hash value ,and put it into hash table */ + netdev_for_each_mc_addr(ha, netdev) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + hash_value = atlx_hash_mc_addr(hw, ha->addr); +#else + hash_value = atlx_hash_mc_addr(hw, ha->dmi_addr); +#endif + atlx_hash_set(hw, hash_value); + } +} + +static inline void atlx_imr_set(struct atlx_adapter *adapter, + unsigned int imr) +{ + iowrite32(imr, adapter->hw.hw_addr + REG_IMR); + ioread32(adapter->hw.hw_addr + REG_IMR); +} + +/** + * atlx_irq_enable - Enable default interrupt generation settings + * @adapter: board private structure + */ +static void atlx_irq_enable(struct atlx_adapter *adapter) +{ + atlx_imr_set(adapter, IMR_NORMAL_MASK); + adapter->int_enabled = true; +} + +/** + * atlx_irq_disable - Mask off interrupt generation on the NIC + * @adapter: board private structure + */ +static void atlx_irq_disable(struct atlx_adapter *adapter) +{ + adapter->int_enabled = false; + atlx_imr_set(adapter, 0); + synchronize_irq(adapter->pdev->irq); +} + +static void atlx_clear_phy_int(struct atlx_adapter *adapter) +{ + u16 phy_data; + unsigned long flags; + + spin_lock_irqsave(&adapter->lock, flags); + atlx_read_phy_reg(&adapter->hw, 19, &phy_data); + spin_unlock_irqrestore(&adapter->lock, flags); +} + +/** + * atlx_tx_timeout - Respond to a Tx Hang + * @netdev: network interface device structure + */ +static void atlx_tx_timeout(struct net_device *netdev) +{ + struct atlx_adapter *adapter = netdev_priv(netdev); + /* Do the reset outside of interrupt context */ + schedule_work(&adapter->reset_dev_task); +} + +/* + * atlx_link_chg_task - deal with link change event Out of interrupt context + */ +static void atlx_link_chg_task(struct work_struct *work) +{ + struct atlx_adapter *adapter; + unsigned long flags; + + adapter = container_of(work, struct atlx_adapter, link_chg_task); + + spin_lock_irqsave(&adapter->lock, flags); + atlx_check_link(adapter); + spin_unlock_irqrestore(&adapter->lock, flags); +} + +static void __atlx_vlan_mode(netdev_features_t features, u32 *ctrl) +{ + if (features & NETIF_F_HW_VLAN_CTAG_RX) { + /* enable VLAN tag insert/strip */ + *ctrl |= MAC_CTRL_RMV_VLAN; + } else { + /* disable VLAN tag insert/strip */ + *ctrl &= ~MAC_CTRL_RMV_VLAN; + } +} + +static void atlx_vlan_mode(struct net_device *netdev, + netdev_features_t features) +{ + struct atlx_adapter *adapter = netdev_priv(netdev); + unsigned long flags; + u32 ctrl; + + spin_lock_irqsave(&adapter->lock, flags); + /* atlx_irq_disable(adapter); FIXME: confirm/remove */ + ctrl = ioread32(adapter->hw.hw_addr + REG_MAC_CTRL); + __atlx_vlan_mode(features, &ctrl); + iowrite32(ctrl, adapter->hw.hw_addr + REG_MAC_CTRL); + /* atlx_irq_enable(adapter); FIXME */ + spin_unlock_irqrestore(&adapter->lock, flags); +} + +static void atlx_restore_vlan(struct atlx_adapter *adapter) +{ + atlx_vlan_mode(adapter->netdev, adapter->netdev->features); +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) +static netdev_features_t atlx_fix_features(struct net_device *netdev, + netdev_features_t features) +{ + /* + * Since there is no support for separate rx/tx vlan accel + * enable/disable make sure tx flag is always in same state as rx. + */ + if (features & NETIF_F_HW_VLAN_CTAG_RX) + features |= NETIF_F_HW_VLAN_CTAG_TX; + else + features &= ~NETIF_F_HW_VLAN_CTAG_TX; + + return features; +} + +static int atlx_set_features(struct net_device *netdev, + netdev_features_t features) +{ + netdev_features_t changed = netdev->features ^ features; + + if (changed & NETIF_F_HW_VLAN_CTAG_RX) + atlx_vlan_mode(netdev, features); + + return 0; +} +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) */ + +#endif /* ATLX_C */ diff --git a/drivers/net/ethernet/atheros/atlx/atlx.h b/drivers/net/ethernet/atheros/atlx/atlx.h new file mode 100644 index 0000000..448f5dc --- /dev/null +++ b/drivers/net/ethernet/atheros/atlx/atlx.h @@ -0,0 +1,502 @@ +/* atlx_hw.h -- common hardware definitions for Attansic network drivers + * + * Copyright(c) 2005 - 2006 Attansic Corporation. All rights reserved. + * Copyright(c) 2006 - 2007 Chris Snook + * Copyright(c) 2006 - 2008 Jay Cliburn + * Copyright(c) 2007 Atheros Corporation. All rights reserved. + * + * Derived from Intel e1000 driver + * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef ATLX_H +#define ATLX_H + +#include +#include + +#define ATLX_ERR_PHY 2 +#define ATLX_ERR_PHY_SPEED 7 +#define ATLX_ERR_PHY_RES 8 + +#define SPEED_0 0xffff +#define SPEED_10 10 +#define SPEED_100 100 +#define SPEED_1000 1000 +#define HALF_DUPLEX 1 +#define FULL_DUPLEX 2 + +#define MEDIA_TYPE_AUTO_SENSOR 0 + +/* register definitions */ +#define REG_PM_CTRLSTAT 0x44 + +#define REG_PCIE_CAP_LIST 0x58 + +#define REG_VPD_CAP 0x6C +#define VPD_CAP_ID_MASK 0xFF +#define VPD_CAP_ID_SHIFT 0 +#define VPD_CAP_NEXT_PTR_MASK 0xFF +#define VPD_CAP_NEXT_PTR_SHIFT 8 +#define VPD_CAP_VPD_ADDR_MASK 0x7FFF +#define VPD_CAP_VPD_ADDR_SHIFT 16 +#define VPD_CAP_VPD_FLAG 0x80000000 + +#define REG_VPD_DATA 0x70 + +#define REG_SPI_FLASH_CTRL 0x200 +#define SPI_FLASH_CTRL_STS_NON_RDY 0x1 +#define SPI_FLASH_CTRL_STS_WEN 0x2 +#define SPI_FLASH_CTRL_STS_WPEN 0x80 +#define SPI_FLASH_CTRL_DEV_STS_MASK 0xFF +#define SPI_FLASH_CTRL_DEV_STS_SHIFT 0 +#define SPI_FLASH_CTRL_INS_MASK 0x7 +#define SPI_FLASH_CTRL_INS_SHIFT 8 +#define SPI_FLASH_CTRL_START 0x800 +#define SPI_FLASH_CTRL_EN_VPD 0x2000 +#define SPI_FLASH_CTRL_LDSTART 0x8000 +#define SPI_FLASH_CTRL_CS_HI_MASK 0x3 +#define SPI_FLASH_CTRL_CS_HI_SHIFT 16 +#define SPI_FLASH_CTRL_CS_HOLD_MASK 0x3 +#define SPI_FLASH_CTRL_CS_HOLD_SHIFT 18 +#define SPI_FLASH_CTRL_CLK_LO_MASK 0x3 +#define SPI_FLASH_CTRL_CLK_LO_SHIFT 20 +#define SPI_FLASH_CTRL_CLK_HI_MASK 0x3 +#define SPI_FLASH_CTRL_CLK_HI_SHIFT 22 +#define SPI_FLASH_CTRL_CS_SETUP_MASK 0x3 +#define SPI_FLASH_CTRL_CS_SETUP_SHIFT 24 +#define SPI_FLASH_CTRL_EROM_PGSZ_MASK 0x3 +#define SPI_FLASH_CTRL_EROM_PGSZ_SHIFT 26 +#define SPI_FLASH_CTRL_WAIT_READY 0x10000000 + +#define REG_SPI_ADDR 0x204 + +#define REG_SPI_DATA 0x208 + +#define REG_SPI_FLASH_CONFIG 0x20C +#define SPI_FLASH_CONFIG_LD_ADDR_MASK 0xFFFFFF +#define SPI_FLASH_CONFIG_LD_ADDR_SHIFT 0 +#define SPI_FLASH_CONFIG_VPD_ADDR_MASK 0x3 +#define SPI_FLASH_CONFIG_VPD_ADDR_SHIFT 24 +#define SPI_FLASH_CONFIG_LD_EXIST 0x4000000 + +#define REG_SPI_FLASH_OP_PROGRAM 0x210 +#define REG_SPI_FLASH_OP_SC_ERASE 0x211 +#define REG_SPI_FLASH_OP_CHIP_ERASE 0x212 +#define REG_SPI_FLASH_OP_RDID 0x213 +#define REG_SPI_FLASH_OP_WREN 0x214 +#define REG_SPI_FLASH_OP_RDSR 0x215 +#define REG_SPI_FLASH_OP_WRSR 0x216 +#define REG_SPI_FLASH_OP_READ 0x217 + +#define REG_TWSI_CTRL 0x218 +#define TWSI_CTRL_LD_OFFSET_MASK 0xFF +#define TWSI_CTRL_LD_OFFSET_SHIFT 0 +#define TWSI_CTRL_LD_SLV_ADDR_MASK 0x7 +#define TWSI_CTRL_LD_SLV_ADDR_SHIFT 8 +#define TWSI_CTRL_SW_LDSTART 0x800 +#define TWSI_CTRL_HW_LDSTART 0x1000 +#define TWSI_CTRL_SMB_SLV_ADDR_MASK 0x7F +#define TWSI_CTRL_SMB_SLV_ADDR_SHIFT 15 +#define TWSI_CTRL_LD_EXIST 0x400000 +#define TWSI_CTRL_READ_FREQ_SEL_MASK 0x3 +#define TWSI_CTRL_READ_FREQ_SEL_SHIFT 23 +#define TWSI_CTRL_FREQ_SEL_100K 0 +#define TWSI_CTRL_FREQ_SEL_200K 1 +#define TWSI_CTRL_FREQ_SEL_300K 2 +#define TWSI_CTRL_FREQ_SEL_400K 3 +#define TWSI_CTRL_SMB_SLV_ADDR /* FIXME: define or remove */ +#define TWSI_CTRL_WRITE_FREQ_SEL_MASK 0x3 +#define TWSI_CTRL_WRITE_FREQ_SEL_SHIFT 24 + +#define REG_PCIE_DEV_MISC_CTRL 0x21C +#define PCIE_DEV_MISC_CTRL_EXT_PIPE 0x2 +#define PCIE_DEV_MISC_CTRL_RETRY_BUFDIS 0x1 +#define PCIE_DEV_MISC_CTRL_SPIROM_EXIST 0x4 +#define PCIE_DEV_MISC_CTRL_SERDES_ENDIAN 0x8 +#define PCIE_DEV_MISC_CTRL_SERDES_SEL_DIN 0x10 + +#define REG_PCIE_PHYMISC 0x1000 +#define PCIE_PHYMISC_FORCE_RCV_DET 0x4 + +#define REG_PCIE_DLL_TX_CTRL1 0x1104 +#define PCIE_DLL_TX_CTRL1_SEL_NOR_CLK 0x400 +#define PCIE_DLL_TX_CTRL1_DEF 0x568 + +#define REG_LTSSM_TEST_MODE 0x12FC +#define LTSSM_TEST_MODE_DEF 0x6500 + +/* Master Control Register */ +#define REG_MASTER_CTRL 0x1400 +#define MASTER_CTRL_SOFT_RST 0x1 +#define MASTER_CTRL_MTIMER_EN 0x2 +#define MASTER_CTRL_ITIMER_EN 0x4 +#define MASTER_CTRL_MANUAL_INT 0x8 +#define MASTER_CTRL_REV_NUM_SHIFT 16 +#define MASTER_CTRL_REV_NUM_MASK 0xFF +#define MASTER_CTRL_DEV_ID_SHIFT 24 +#define MASTER_CTRL_DEV_ID_MASK 0xFF + +/* Timer Initial Value Register */ +#define REG_MANUAL_TIMER_INIT 0x1404 + +/* IRQ Moderator Timer Initial Value Register */ +#define REG_IRQ_MODU_TIMER_INIT 0x1408 + +#define REG_PHY_ENABLE 0x140C + +/* IRQ Anti-Lost Timer Initial Value Register */ +#define REG_CMBDISDMA_TIMER 0x140E + +/* Block IDLE Status Register */ +#define REG_IDLE_STATUS 0x1410 + +/* MDIO Control Register */ +#define REG_MDIO_CTRL 0x1414 +#define MDIO_DATA_MASK 0xFFFF +#define MDIO_DATA_SHIFT 0 +#define MDIO_REG_ADDR_MASK 0x1F +#define MDIO_REG_ADDR_SHIFT 16 +#define MDIO_RW 0x200000 +#define MDIO_SUP_PREAMBLE 0x400000 +#define MDIO_START 0x800000 +#define MDIO_CLK_SEL_SHIFT 24 +#define MDIO_CLK_25_4 0 +#define MDIO_CLK_25_6 2 +#define MDIO_CLK_25_8 3 +#define MDIO_CLK_25_10 4 +#define MDIO_CLK_25_14 5 +#define MDIO_CLK_25_20 6 +#define MDIO_CLK_25_28 7 +#define MDIO_BUSY 0x8000000 + +/* MII PHY Status Register */ +#define REG_PHY_STATUS 0x1418 + +/* BIST Control and Status Register0 (for the Packet Memory) */ +#define REG_BIST0_CTRL 0x141C +#define BIST0_NOW 0x1 +#define BIST0_SRAM_FAIL 0x2 +#define BIST0_FUSE_FLAG 0x4 +#define REG_BIST1_CTRL 0x1420 +#define BIST1_NOW 0x1 +#define BIST1_SRAM_FAIL 0x2 +#define BIST1_FUSE_FLAG 0x4 + +/* SerDes Lock Detect Control and Status Register */ +#define REG_SERDES_LOCK 0x1424 +#define SERDES_LOCK_DETECT 1 +#define SERDES_LOCK_DETECT_EN 2 + +/* MAC Control Register */ +#define REG_MAC_CTRL 0x1480 +#define MAC_CTRL_TX_EN 1 +#define MAC_CTRL_RX_EN 2 +#define MAC_CTRL_TX_FLOW 4 +#define MAC_CTRL_RX_FLOW 8 +#define MAC_CTRL_LOOPBACK 0x10 +#define MAC_CTRL_DUPLX 0x20 +#define MAC_CTRL_ADD_CRC 0x40 +#define MAC_CTRL_PAD 0x80 +#define MAC_CTRL_LENCHK 0x100 +#define MAC_CTRL_HUGE_EN 0x200 +#define MAC_CTRL_PRMLEN_SHIFT 10 +#define MAC_CTRL_PRMLEN_MASK 0xF +#define MAC_CTRL_RMV_VLAN 0x4000 +#define MAC_CTRL_PROMIS_EN 0x8000 +#define MAC_CTRL_MC_ALL_EN 0x2000000 +#define MAC_CTRL_BC_EN 0x4000000 + +/* MAC IPG/IFG Control Register */ +#define REG_MAC_IPG_IFG 0x1484 +#define MAC_IPG_IFG_IPGT_SHIFT 0 +#define MAC_IPG_IFG_IPGT_MASK 0x7F +#define MAC_IPG_IFG_MIFG_SHIFT 8 +#define MAC_IPG_IFG_MIFG_MASK 0xFF +#define MAC_IPG_IFG_IPGR1_SHIFT 16 +#define MAC_IPG_IFG_IPGR1_MASK 0x7F +#define MAC_IPG_IFG_IPGR2_SHIFT 24 +#define MAC_IPG_IFG_IPGR2_MASK 0x7F + +/* MAC STATION ADDRESS */ +#define REG_MAC_STA_ADDR 0x1488 + +/* Hash table for multicast address */ +#define REG_RX_HASH_TABLE 0x1490 + +/* MAC Half-Duplex Control Register */ +#define REG_MAC_HALF_DUPLX_CTRL 0x1498 +#define MAC_HALF_DUPLX_CTRL_LCOL_SHIFT 0 +#define MAC_HALF_DUPLX_CTRL_LCOL_MASK 0x3FF +#define MAC_HALF_DUPLX_CTRL_RETRY_SHIFT 12 +#define MAC_HALF_DUPLX_CTRL_RETRY_MASK 0xF +#define MAC_HALF_DUPLX_CTRL_EXC_DEF_EN 0x10000 +#define MAC_HALF_DUPLX_CTRL_NO_BACK_C 0x20000 +#define MAC_HALF_DUPLX_CTRL_NO_BACK_P 0x40000 +#define MAC_HALF_DUPLX_CTRL_ABEBE 0x80000 +#define MAC_HALF_DUPLX_CTRL_ABEBT_SHIFT 20 +#define MAC_HALF_DUPLX_CTRL_ABEBT_MASK 0xF +#define MAC_HALF_DUPLX_CTRL_JAMIPG_SHIFT 24 +#define MAC_HALF_DUPLX_CTRL_JAMIPG_MASK 0xF + +/* Maximum Frame Length Control Register */ +#define REG_MTU 0x149C + +/* Wake-On-Lan control register */ +#define REG_WOL_CTRL 0x14A0 +#define WOL_PATTERN_EN 0x1 +#define WOL_PATTERN_PME_EN 0x2 +#define WOL_MAGIC_EN 0x4 +#define WOL_MAGIC_PME_EN 0x8 +#define WOL_LINK_CHG_EN 0x10 +#define WOL_LINK_CHG_PME_EN 0x20 +#define WOL_PATTERN_ST 0x100 +#define WOL_MAGIC_ST 0x200 +#define WOL_LINKCHG_ST 0x400 +#define WOL_PT0_EN 0x10000 +#define WOL_PT1_EN 0x20000 +#define WOL_PT2_EN 0x40000 +#define WOL_PT3_EN 0x80000 +#define WOL_PT4_EN 0x100000 +#define WOL_PT0_MATCH 0x1000000 +#define WOL_PT1_MATCH 0x2000000 +#define WOL_PT2_MATCH 0x4000000 +#define WOL_PT3_MATCH 0x8000000 +#define WOL_PT4_MATCH 0x10000000 + +/* Internal SRAM Partition Register, high 32 bits */ +#define REG_SRAM_RFD_ADDR 0x1500 + +/* Descriptor Control register, high 32 bits */ +#define REG_DESC_BASE_ADDR_HI 0x1540 + +/* Interrupt Status Register */ +#define REG_ISR 0x1600 +#define ISR_UR_DETECTED 0x1000000 +#define ISR_FERR_DETECTED 0x2000000 +#define ISR_NFERR_DETECTED 0x4000000 +#define ISR_CERR_DETECTED 0x8000000 +#define ISR_PHY_LINKDOWN 0x10000000 +#define ISR_DIS_INT 0x80000000 + +/* Interrupt Mask Register */ +#define REG_IMR 0x1604 + +#define REG_RFD_RRD_IDX 0x1800 +#define REG_TPD_IDX 0x1804 + +/* MII definitions */ + +/* PHY Common Register */ +#define MII_ATLX_CR 0x09 +#define MII_ATLX_SR 0x0A +#define MII_ATLX_ESR 0x0F +#define MII_ATLX_PSCR 0x10 +#define MII_ATLX_PSSR 0x11 + +/* PHY Control Register */ +#define MII_CR_SPEED_SELECT_MSB 0x0040 /* bits 6,13: 10=1000, 01=100, + * 00=10 + */ +#define MII_CR_COLL_TEST_ENABLE 0x0080 /* Collision test enable */ +#define MII_CR_FULL_DUPLEX 0x0100 /* FDX =1, half duplex =0 */ +#define MII_CR_RESTART_AUTO_NEG 0x0200 /* Restart auto negotiation */ +#define MII_CR_ISOLATE 0x0400 /* Isolate PHY from MII */ +#define MII_CR_POWER_DOWN 0x0800 /* Power down */ +#define MII_CR_AUTO_NEG_EN 0x1000 /* Auto Neg Enable */ +#define MII_CR_SPEED_SELECT_LSB 0x2000 /* bits 6,13: 10=1000, 01=100, + * 00=10 + */ +#define MII_CR_LOOPBACK 0x4000 /* 0 = normal, 1 = loopback */ +#define MII_CR_RESET 0x8000 /* 0 = normal, 1 = PHY reset */ +#define MII_CR_SPEED_MASK 0x2040 +#define MII_CR_SPEED_1000 0x0040 +#define MII_CR_SPEED_100 0x2000 +#define MII_CR_SPEED_10 0x0000 + +/* PHY Status Register */ +#define MII_SR_EXTENDED_CAPS 0x0001 /* Ext register capabilities */ +#define MII_SR_JABBER_DETECT 0x0002 /* Jabber Detected */ +#define MII_SR_LINK_STATUS 0x0004 /* Link Status 1 = link */ +#define MII_SR_AUTONEG_CAPS 0x0008 /* Auto Neg Capable */ +#define MII_SR_REMOTE_FAULT 0x0010 /* Remote Fault Detect */ +#define MII_SR_AUTONEG_COMPLETE 0x0020 /* Auto Neg Complete */ +#define MII_SR_PREAMBLE_SUPPRESS 0x0040 /* Preamble may be suppressed */ +#define MII_SR_EXTENDED_STATUS 0x0100 /* Ext stat info in Reg 0x0F */ +#define MII_SR_100T2_HD_CAPS 0x0200 /* 100T2 Half Duplex Capable */ +#define MII_SR_100T2_FD_CAPS 0x0400 /* 100T2 Full Duplex Capable */ +#define MII_SR_10T_HD_CAPS 0x0800 /* 10T Half Duplex Capable */ +#define MII_SR_10T_FD_CAPS 0x1000 /* 10T Full Duplex Capable */ +#define MII_SR_100X_HD_CAPS 0x2000 /* 100X Half Duplex Capable */ +#define MII_SR_100X_FD_CAPS 0x4000 /* 100X Full Duplex Capable */ +#define MII_SR_100T4_CAPS 0x8000 /* 100T4 Capable */ + +/* Link partner ability register */ +#define MII_LPA_SLCT 0x001f /* Same as advertise selector */ +#define MII_LPA_10HALF 0x0020 /* Can do 10mbps half-duplex */ +#define MII_LPA_10FULL 0x0040 /* Can do 10mbps full-duplex */ +#define MII_LPA_100HALF 0x0080 /* Can do 100mbps half-duplex */ +#define MII_LPA_100FULL 0x0100 /* Can do 100mbps full-duplex */ +#define MII_LPA_100BASE4 0x0200 /* 100BASE-T4 */ +#define MII_LPA_PAUSE 0x0400 /* PAUSE */ +#define MII_LPA_ASYPAUSE 0x0800 /* Asymmetrical PAUSE */ +#define MII_LPA_RFAULT 0x2000 /* Link partner faulted */ +#define MII_LPA_LPACK 0x4000 /* Link partner acked us */ +#define MII_LPA_NPAGE 0x8000 /* Next page bit */ + +/* Autoneg Advertisement Register */ +#define MII_AR_SELECTOR_FIELD 0x0001 /* IEEE 802.3 CSMA/CD */ +#define MII_AR_10T_HD_CAPS 0x0020 /* 10T Half Duplex Capable */ +#define MII_AR_10T_FD_CAPS 0x0040 /* 10T Full Duplex Capable */ +#define MII_AR_100TX_HD_CAPS 0x0080 /* 100TX Half Duplex Capable */ +#define MII_AR_100TX_FD_CAPS 0x0100 /* 100TX Full Duplex Capable */ +#define MII_AR_100T4_CAPS 0x0200 /* 100T4 Capable */ +#define MII_AR_PAUSE 0x0400 /* Pause operation desired */ +#define MII_AR_ASM_DIR 0x0800 /* Asymmetric Pause Dir bit */ +#define MII_AR_REMOTE_FAULT 0x2000 /* Remote Fault detected */ +#define MII_AR_NEXT_PAGE 0x8000 /* Next Page ability support */ +#define MII_AR_SPEED_MASK 0x01E0 +#define MII_AR_DEFAULT_CAP_MASK 0x0DE0 + +/* 1000BASE-T Control Register */ +#define MII_ATLX_CR_1000T_HD_CAPS 0x0100 /* Adv 1000T HD cap */ +#define MII_ATLX_CR_1000T_FD_CAPS 0x0200 /* Adv 1000T FD cap */ +#define MII_ATLX_CR_1000T_REPEATER_DTE 0x0400 /* 1=Repeater/switch device, + * 0=DTE device */ +#define MII_ATLX_CR_1000T_MS_VALUE 0x0800 /* 1=Config PHY as Master, + * 0=Configure PHY as Slave */ +#define MII_ATLX_CR_1000T_MS_ENABLE 0x1000 /* 1=Man Master/Slave config, + * 0=Auto Master/Slave config + */ +#define MII_ATLX_CR_1000T_TEST_MODE_NORMAL 0x0000 /* Normal Operation */ +#define MII_ATLX_CR_1000T_TEST_MODE_1 0x2000 /* Transmit Waveform test */ +#define MII_ATLX_CR_1000T_TEST_MODE_2 0x4000 /* Master Xmit Jitter test */ +#define MII_ATLX_CR_1000T_TEST_MODE_3 0x6000 /* Slave Xmit Jitter test */ +#define MII_ATLX_CR_1000T_TEST_MODE_4 0x8000 /* Xmitter Distortion test */ +#define MII_ATLX_CR_1000T_SPEED_MASK 0x0300 +#define MII_ATLX_CR_1000T_DEFAULT_CAP_MASK 0x0300 + +/* 1000BASE-T Status Register */ +#define MII_ATLX_SR_1000T_LP_HD_CAPS 0x0400 /* LP is 1000T HD capable */ +#define MII_ATLX_SR_1000T_LP_FD_CAPS 0x0800 /* LP is 1000T FD capable */ +#define MII_ATLX_SR_1000T_REMOTE_RX_STATUS 0x1000 /* Remote receiver OK */ +#define MII_ATLX_SR_1000T_LOCAL_RX_STATUS 0x2000 /* Local receiver OK */ +#define MII_ATLX_SR_1000T_MS_CONFIG_RES 0x4000 /* 1=Local TX is Master + * 0=Slave + */ +#define MII_ATLX_SR_1000T_MS_CONFIG_FAULT 0x8000 /* Master/Slave config + * fault */ +#define MII_ATLX_SR_1000T_REMOTE_RX_STATUS_SHIFT 12 +#define MII_ATLX_SR_1000T_LOCAL_RX_STATUS_SHIFT 13 + +/* Extended Status Register */ +#define MII_ATLX_ESR_1000T_HD_CAPS 0x1000 /* 1000T HD capable */ +#define MII_ATLX_ESR_1000T_FD_CAPS 0x2000 /* 1000T FD capable */ +#define MII_ATLX_ESR_1000X_HD_CAPS 0x4000 /* 1000X HD capable */ +#define MII_ATLX_ESR_1000X_FD_CAPS 0x8000 /* 1000X FD capable */ + +/* ATLX PHY Specific Control Register */ +#define MII_ATLX_PSCR_JABBER_DISABLE 0x0001 /* 1=Jabber Func disabled */ +#define MII_ATLX_PSCR_POLARITY_REVERSAL 0x0002 /* 1=Polarity Reversal enbld */ +#define MII_ATLX_PSCR_SQE_TEST 0x0004 /* 1=SQE Test enabled */ +#define MII_ATLX_PSCR_MAC_POWERDOWN 0x0008 +#define MII_ATLX_PSCR_CLK125_DISABLE 0x0010 /* 1=CLK125 low + * 0=CLK125 toggling + */ +#define MII_ATLX_PSCR_MDI_MANUAL_MODE 0x0000 /* MDI Crossover Mode bits 6:5, + * Manual MDI configuration + */ +#define MII_ATLX_PSCR_MDIX_MANUAL_MODE 0x0020 /* Manual MDIX configuration */ +#define MII_ATLX_PSCR_AUTO_X_1000T 0x0040 /* 1000BASE-T: Auto crossover + * 100BASE-TX/10BASE-T: MDI + * Mode */ +#define MII_ATLX_PSCR_AUTO_X_MODE 0x0060 /* Auto crossover enabled + * all speeds. + */ +#define MII_ATLX_PSCR_10BT_EXT_DIST_ENABLE 0x0080 /* 1=Enable Extended + * 10BASE-T distance + * (Lower 10BASE-T RX + * Threshold) + * 0=Normal 10BASE-T RX + * Threshold + */ +#define MII_ATLX_PSCR_MII_5BIT_ENABLE 0x0100 /* 1=5-Bit interface in + * 100BASE-TX + * 0=MII interface in + * 100BASE-TX + */ +#define MII_ATLX_PSCR_SCRAMBLER_DISABLE 0x0200 /* 1=Scrambler dsbl */ +#define MII_ATLX_PSCR_FORCE_LINK_GOOD 0x0400 /* 1=Force link good */ +#define MII_ATLX_PSCR_ASSERT_CRS_ON_TX 0x0800 /* 1=Assert CRS on Transmit */ +#define MII_ATLX_PSCR_POLARITY_REVERSAL_SHIFT 1 +#define MII_ATLX_PSCR_AUTO_X_MODE_SHIFT 5 +#define MII_ATLX_PSCR_10BT_EXT_DIST_ENABLE_SHIFT 7 + +/* ATLX PHY Specific Status Register */ +#define MII_ATLX_PSSR_SPD_DPLX_RESOLVED 0x0800 /* 1=Speed & Duplex resolved */ +#define MII_ATLX_PSSR_DPLX 0x2000 /* 1=Duplex 0=Half Duplex */ +#define MII_ATLX_PSSR_SPEED 0xC000 /* Speed, bits 14:15 */ +#define MII_ATLX_PSSR_10MBS 0x0000 /* 00=10Mbs */ +#define MII_ATLX_PSSR_100MBS 0x4000 /* 01=100Mbs */ +#define MII_ATLX_PSSR_1000MBS 0x8000 /* 10=1000Mbs */ + +#define MII_DBG_ADDR 0x1D +#define MII_DBG_DATA 0x1E + +/* PCI Command Register Bit Definitions */ +#define PCI_REG_COMMAND 0x04 /* PCI Command Register */ +#define CMD_IO_SPACE 0x0001 +#define CMD_MEMORY_SPACE 0x0002 +#define CMD_BUS_MASTER 0x0004 + +/* Wake Up Filter Control */ +#define ATLX_WUFC_LNKC 0x00000001 /* Link Status Change Wakeup Enable */ +#define ATLX_WUFC_MAG 0x00000002 /* Magic Packet Wakeup Enable */ +#define ATLX_WUFC_EX 0x00000004 /* Directed Exact Wakeup Enable */ +#define ATLX_WUFC_MC 0x00000008 /* Multicast Wakeup Enable */ +#define ATLX_WUFC_BC 0x00000010 /* Broadcast Wakeup Enable */ + +#define ADVERTISE_10_HALF 0x0001 +#define ADVERTISE_10_FULL 0x0002 +#define ADVERTISE_100_HALF 0x0004 +#define ADVERTISE_100_FULL 0x0008 +#define ADVERTISE_1000_HALF 0x0010 +#define ADVERTISE_1000_FULL 0x0020 +#define AUTONEG_ADVERTISE_10_100_ALL 0x000F /* All 10/100 speeds */ +#define AUTONEG_ADVERTISE_10_ALL 0x0003 /* 10Mbps Full & Half speeds */ + +#define PHY_AUTO_NEG_TIME 45 /* 4.5 Seconds */ +#define PHY_FORCE_TIME 20 /* 2.0 Seconds */ + +/* For checksumming, the sum of all words in the EEPROM should equal 0xBABA */ +#define EEPROM_SUM 0xBABA + +struct atlx_spi_flash_dev { + const char *manu_name; /* manufacturer id */ + /* op-code */ + u8 cmd_wrsr; + u8 cmd_read; + u8 cmd_program; + u8 cmd_wren; + u8 cmd_wrdi; + u8 cmd_rdsr; + u8 cmd_rdid; + u8 cmd_sector_erase; + u8 cmd_chip_erase; +}; + +#endif /* ATLX_H */ diff --git a/drivers/net/ethernet/broadcom/Kconfig b/drivers/net/ethernet/broadcom/Kconfig new file mode 100644 index 0000000..492e505 --- /dev/null +++ b/drivers/net/ethernet/broadcom/Kconfig @@ -0,0 +1,157 @@ +# +# Broadcom device configuration +# + +config NET_VENDOR_BROADCOM + bool "Broadcom devices" + default y + depends on (SSB_POSSIBLE && HAS_DMA) || PCI || BCM63XX || \ + SIBYTE_SB1xxx_SOC + ---help--- + If you have a network (Ethernet) chipset belonging to this class, + say Y. + + Note that the answer to this question does not directly affect + the kernel: saying N will just case the configurator to skip all + the questions regarding AMD chipsets. If you say Y, you will be asked + for your specific chipset/driver in the following questions. + +if NET_VENDOR_BROADCOM + +config B44 + tristate "Broadcom 440x/47xx ethernet support" + depends on m + depends on SSB_POSSIBLE && HAS_DMA + select SSB + select NET_CORE + select BACKPORT_MII + ---help--- + If you have a network (Ethernet) controller of this type, say Y + or M and read the Ethernet-HOWTO, available from + . + + To compile this driver as a module, choose M here. The module + will be called b44. + +# Auto-select SSB PCI-HOST support, if possible +config B44_PCI_AUTOSELECT + bool + depends on B44 && SSB_PCIHOST_POSSIBLE + select SSB_PCIHOST + default y + +# Auto-select SSB PCICORE driver, if possible +config B44_PCICORE_AUTOSELECT + bool + depends on B44 && SSB_DRIVER_PCICORE_POSSIBLE + select SSB_DRIVER_PCICORE + default y + +config B44_PCI + bool + depends on B44_PCI_AUTOSELECT && B44_PCICORE_AUTOSELECT + default y + +config BCM63XX_ENET + depends on n + tristate "Broadcom 63xx internal mac support" + depends on m + depends on BCM63XX + select NET_CORE + select BACKPORT_MII + depends on PHYLIB + help + This driver supports the ethernet MACs in the Broadcom 63xx + MIPS chipset family (BCM63XX). + +config BNX2 + depends on n + tristate "Broadcom NetXtremeII support" + depends on m + depends on PCI + depends on CRC32 + select BACKPORT_FW_LOADER + ---help--- + This driver supports Broadcom NetXtremeII gigabit Ethernet cards. + + To compile this driver as a module, choose M here: the module + will be called bnx2. This is recommended. + +config CNIC + depends on n + tristate "Broadcom CNIC support" + depends on m + depends on PCI + select BNX2 + depends on UIO + ---help--- + This driver supports offload features of Broadcom NetXtremeII + gigabit Ethernet cards. + + To compile this driver as a module, choose M here: the module + will be called cnic. This is recommended. + +config SB1250_MAC + depends on n + tristate "SB1250 Gigabit Ethernet support" + depends on m + depends on SIBYTE_SB1xxx_SOC + depends on PHYLIB + ---help--- + This driver supports Gigabit Ethernet interfaces based on the + Broadcom SiByte family of System-On-a-Chip parts. They include + the BCM1120, BCM1125, BCM1125H, BCM1250, BCM1255, BCM1280, BCM1455 + and BCM1480 chips. + + To compile this driver as a module, choose M here: the module + will be called sb1250-mac. + +config TIGON3 + depends on n + tristate "Broadcom Tigon3 support" + depends on m + depends on PCI + depends on PHYLIB + depends on HWMON + depends on PTP_1588_CLOCK + ---help--- + This driver supports Broadcom Tigon3 based gigabit Ethernet cards. + + To compile this driver as a module, choose M here: the module + will be called tg3. This is recommended. + +config BNX2X + depends on n + tristate "Broadcom NetXtremeII 10Gb support" + depends on m + depends on PCI + select BACKPORT_FW_LOADER + depends on ZLIB_INFLATE + depends on LIBCRC32C + select MDIO + ---help--- + This driver supports Broadcom NetXtremeII 10 gigabit Ethernet cards. + To compile this driver as a module, choose M here: the module + will be called bnx2x. This is recommended. + +config BNX2X_SRIOV + bool "Broadcom 578xx and 57712 SR-IOV support" + depends on BNX2X && PCI_IOV + default y + ---help--- + This configuration parameter enables Single Root Input Output + Virtualization support in the 578xx and 57712 products. This + allows for virtual function acceleration in virtual environments. + +config BGMAC + depends on n + tristate "BCMA bus GBit core support" + depends on m + depends on BCMA_HOST_SOC && HAS_DMA + ---help--- + This driver supports GBit MAC and BCM4706 GBit MAC cores on BCMA bus. + They can be found on BCM47xx SoCs and provide gigabit ethernet. + In case of using this driver on BCM4706 it's also requires to enable + BCMA_DRIVER_GMAC_CMN to make it work. + +endif # NET_VENDOR_BROADCOM diff --git a/drivers/net/ethernet/broadcom/Makefile b/drivers/net/ethernet/broadcom/Makefile new file mode 100644 index 0000000..0f866df --- /dev/null +++ b/drivers/net/ethernet/broadcom/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for the Broadcom network device drivers. +# + +obj-$(CPTCFG_B44) += b44.o +#obj-$(CPTCFG_BCM63XX_ENET) += bcm63xx_enet.o +#obj-$(CPTCFG_BNX2) += bnx2.o +#obj-$(CPTCFG_CNIC) += cnic.o +#obj-$(CPTCFG_BNX2X) += bnx2x/ +#obj-$(CPTCFG_SB1250_MAC) += sb1250-mac.o +#obj-$(CPTCFG_TIGON3) += tg3.o +#obj-$(CPTCFG_BGMAC) += bgmac.o diff --git a/drivers/net/ethernet/broadcom/b44.c b/drivers/net/ethernet/broadcom/b44.c new file mode 100644 index 0000000..becdc6a --- /dev/null +++ b/drivers/net/ethernet/broadcom/b44.c @@ -0,0 +1,2400 @@ +/* b44.c: Broadcom 44xx/47xx Fast Ethernet device driver. + * + * Copyright (C) 2002 David S. Miller (davem@redhat.com) + * Copyright (C) 2004 Pekka Pietikainen (pp@ee.oulu.fi) + * Copyright (C) 2004 Florian Schirmer (jolt@tuxbox.org) + * Copyright (C) 2006 Felix Fietkau (nbd@openwrt.org) + * Copyright (C) 2006 Broadcom Corporation. + * Copyright (C) 2007 Michael Buesch + * + * Distribute under GPL. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +#include "b44.h" + +#define DRV_MODULE_NAME "b44" +#define DRV_MODULE_VERSION "2.0" +#define DRV_DESCRIPTION "Broadcom 44xx/47xx 10/100 PCI ethernet driver" + +#define B44_DEF_MSG_ENABLE \ + (NETIF_MSG_DRV | \ + NETIF_MSG_PROBE | \ + NETIF_MSG_LINK | \ + NETIF_MSG_TIMER | \ + NETIF_MSG_IFDOWN | \ + NETIF_MSG_IFUP | \ + NETIF_MSG_RX_ERR | \ + NETIF_MSG_TX_ERR) + +/* length of time before we decide the hardware is borked, + * and dev->tx_timeout() should be called to fix the problem + */ +#define B44_TX_TIMEOUT (5 * HZ) + +/* hardware minimum and maximum for a single frame's data payload */ +#define B44_MIN_MTU 60 +#define B44_MAX_MTU 1500 + +#define B44_RX_RING_SIZE 512 +#define B44_DEF_RX_RING_PENDING 200 +#define B44_RX_RING_BYTES (sizeof(struct dma_desc) * \ + B44_RX_RING_SIZE) +#define B44_TX_RING_SIZE 512 +#define B44_DEF_TX_RING_PENDING (B44_TX_RING_SIZE - 1) +#define B44_TX_RING_BYTES (sizeof(struct dma_desc) * \ + B44_TX_RING_SIZE) + +#define TX_RING_GAP(BP) \ + (B44_TX_RING_SIZE - (BP)->tx_pending) +#define TX_BUFFS_AVAIL(BP) \ + (((BP)->tx_cons <= (BP)->tx_prod) ? \ + (BP)->tx_cons + (BP)->tx_pending - (BP)->tx_prod : \ + (BP)->tx_cons - (BP)->tx_prod - TX_RING_GAP(BP)) +#define NEXT_TX(N) (((N) + 1) & (B44_TX_RING_SIZE - 1)) + +#define RX_PKT_OFFSET (RX_HEADER_LEN + 2) +#define RX_PKT_BUF_SZ (1536 + RX_PKT_OFFSET) + +/* minimum number of free TX descriptors required to wake up TX process */ +#define B44_TX_WAKEUP_THRESH (B44_TX_RING_SIZE / 4) + +/* b44 internal pattern match filter info */ +#define B44_PATTERN_BASE 0x400 +#define B44_PATTERN_SIZE 0x80 +#define B44_PMASK_BASE 0x600 +#define B44_PMASK_SIZE 0x10 +#define B44_MAX_PATTERNS 16 +#define B44_ETHIPV6UDP_HLEN 62 +#define B44_ETHIPV4UDP_HLEN 42 + +MODULE_AUTHOR("Felix Fietkau, Florian Schirmer, Pekka Pietikainen, David S. Miller"); +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_MODULE_VERSION); + +static int b44_debug = -1; /* -1 == use B44_DEF_MSG_ENABLE as value */ +module_param(b44_debug, int, 0); +MODULE_PARM_DESC(b44_debug, "B44 bitmapped debugging message enable value"); + + +#ifdef CPTCFG_B44_PCI +static DEFINE_PCI_DEVICE_TABLE(b44_pci_tbl) = { + { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_BCM4401) }, + { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_BCM4401B0) }, + { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_BCM4401B1) }, + { 0 } /* terminate list with empty entry */ +}; +MODULE_DEVICE_TABLE(pci, b44_pci_tbl); + +static struct pci_driver b44_pci_driver = { + .name = DRV_MODULE_NAME, + .id_table = b44_pci_tbl, +}; +#endif /* CPTCFG_B44_PCI */ + +static const struct ssb_device_id b44_ssb_tbl[] = { + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_ETHERNET, SSB_ANY_REV), + SSB_DEVTABLE_END +}; +MODULE_DEVICE_TABLE(ssb, b44_ssb_tbl); + +static void b44_halt(struct b44 *); +static void b44_init_rings(struct b44 *); + +#define B44_FULL_RESET 1 +#define B44_FULL_RESET_SKIP_PHY 2 +#define B44_PARTIAL_RESET 3 +#define B44_CHIP_RESET_FULL 4 +#define B44_CHIP_RESET_PARTIAL 5 + +static void b44_init_hw(struct b44 *, int); + +static int dma_desc_sync_size; +static int instance; + +static const char b44_gstrings[][ETH_GSTRING_LEN] = { +#define _B44(x...) # x, +B44_STAT_REG_DECLARE +#undef _B44 +}; + +static inline void b44_sync_dma_desc_for_device(struct ssb_device *sdev, + dma_addr_t dma_base, + unsigned long offset, + enum dma_data_direction dir) +{ + dma_sync_single_for_device(sdev->dma_dev, dma_base + offset, + dma_desc_sync_size, dir); +} + +static inline void b44_sync_dma_desc_for_cpu(struct ssb_device *sdev, + dma_addr_t dma_base, + unsigned long offset, + enum dma_data_direction dir) +{ + dma_sync_single_for_cpu(sdev->dma_dev, dma_base + offset, + dma_desc_sync_size, dir); +} + +static inline unsigned long br32(const struct b44 *bp, unsigned long reg) +{ + return ssb_read32(bp->sdev, reg); +} + +static inline void bw32(const struct b44 *bp, + unsigned long reg, unsigned long val) +{ + ssb_write32(bp->sdev, reg, val); +} + +static int b44_wait_bit(struct b44 *bp, unsigned long reg, + u32 bit, unsigned long timeout, const int clear) +{ + unsigned long i; + + for (i = 0; i < timeout; i++) { + u32 val = br32(bp, reg); + + if (clear && !(val & bit)) + break; + if (!clear && (val & bit)) + break; + udelay(10); + } + if (i == timeout) { + if (net_ratelimit()) + netdev_err(bp->dev, "BUG! Timeout waiting for bit %08x of register %lx to %s\n", + bit, reg, clear ? "clear" : "set"); + + return -ENODEV; + } + return 0; +} + +static inline void __b44_cam_read(struct b44 *bp, unsigned char *data, int index) +{ + u32 val; + + bw32(bp, B44_CAM_CTRL, (CAM_CTRL_READ | + (index << CAM_CTRL_INDEX_SHIFT))); + + b44_wait_bit(bp, B44_CAM_CTRL, CAM_CTRL_BUSY, 100, 1); + + val = br32(bp, B44_CAM_DATA_LO); + + data[2] = (val >> 24) & 0xFF; + data[3] = (val >> 16) & 0xFF; + data[4] = (val >> 8) & 0xFF; + data[5] = (val >> 0) & 0xFF; + + val = br32(bp, B44_CAM_DATA_HI); + + data[0] = (val >> 8) & 0xFF; + data[1] = (val >> 0) & 0xFF; +} + +static inline void __b44_cam_write(struct b44 *bp, unsigned char *data, int index) +{ + u32 val; + + val = ((u32) data[2]) << 24; + val |= ((u32) data[3]) << 16; + val |= ((u32) data[4]) << 8; + val |= ((u32) data[5]) << 0; + bw32(bp, B44_CAM_DATA_LO, val); + val = (CAM_DATA_HI_VALID | + (((u32) data[0]) << 8) | + (((u32) data[1]) << 0)); + bw32(bp, B44_CAM_DATA_HI, val); + bw32(bp, B44_CAM_CTRL, (CAM_CTRL_WRITE | + (index << CAM_CTRL_INDEX_SHIFT))); + b44_wait_bit(bp, B44_CAM_CTRL, CAM_CTRL_BUSY, 100, 1); +} + +static inline void __b44_disable_ints(struct b44 *bp) +{ + bw32(bp, B44_IMASK, 0); +} + +static void b44_disable_ints(struct b44 *bp) +{ + __b44_disable_ints(bp); + + /* Flush posted writes. */ + br32(bp, B44_IMASK); +} + +static void b44_enable_ints(struct b44 *bp) +{ + bw32(bp, B44_IMASK, bp->imask); +} + +static int __b44_readphy(struct b44 *bp, int phy_addr, int reg, u32 *val) +{ + int err; + + bw32(bp, B44_EMAC_ISTAT, EMAC_INT_MII); + bw32(bp, B44_MDIO_DATA, (MDIO_DATA_SB_START | + (MDIO_OP_READ << MDIO_DATA_OP_SHIFT) | + (phy_addr << MDIO_DATA_PMD_SHIFT) | + (reg << MDIO_DATA_RA_SHIFT) | + (MDIO_TA_VALID << MDIO_DATA_TA_SHIFT))); + err = b44_wait_bit(bp, B44_EMAC_ISTAT, EMAC_INT_MII, 100, 0); + *val = br32(bp, B44_MDIO_DATA) & MDIO_DATA_DATA; + + return err; +} + +static int __b44_writephy(struct b44 *bp, int phy_addr, int reg, u32 val) +{ + bw32(bp, B44_EMAC_ISTAT, EMAC_INT_MII); + bw32(bp, B44_MDIO_DATA, (MDIO_DATA_SB_START | + (MDIO_OP_WRITE << MDIO_DATA_OP_SHIFT) | + (phy_addr << MDIO_DATA_PMD_SHIFT) | + (reg << MDIO_DATA_RA_SHIFT) | + (MDIO_TA_VALID << MDIO_DATA_TA_SHIFT) | + (val & MDIO_DATA_DATA))); + return b44_wait_bit(bp, B44_EMAC_ISTAT, EMAC_INT_MII, 100, 0); +} + +static inline int b44_readphy(struct b44 *bp, int reg, u32 *val) +{ + if (bp->phy_addr == B44_PHY_ADDR_NO_PHY) + return 0; + + return __b44_readphy(bp, bp->phy_addr, reg, val); +} + +static inline int b44_writephy(struct b44 *bp, int reg, u32 val) +{ + if (bp->phy_addr == B44_PHY_ADDR_NO_PHY) + return 0; + + return __b44_writephy(bp, bp->phy_addr, reg, val); +} + +/* miilib interface */ +static int b44_mii_read(struct net_device *dev, int phy_id, int location) +{ + u32 val; + struct b44 *bp = netdev_priv(dev); + int rc = __b44_readphy(bp, phy_id, location, &val); + if (rc) + return 0xffffffff; + return val; +} + +static void b44_mii_write(struct net_device *dev, int phy_id, int location, + int val) +{ + struct b44 *bp = netdev_priv(dev); + __b44_writephy(bp, phy_id, location, val); +} + +static int b44_phy_reset(struct b44 *bp) +{ + u32 val; + int err; + + if (bp->phy_addr == B44_PHY_ADDR_NO_PHY) + return 0; + err = b44_writephy(bp, MII_BMCR, BMCR_RESET); + if (err) + return err; + udelay(100); + err = b44_readphy(bp, MII_BMCR, &val); + if (!err) { + if (val & BMCR_RESET) { + netdev_err(bp->dev, "PHY Reset would not complete\n"); + err = -ENODEV; + } + } + + return err; +} + +static void __b44_set_flow_ctrl(struct b44 *bp, u32 pause_flags) +{ + u32 val; + + bp->flags &= ~(B44_FLAG_TX_PAUSE | B44_FLAG_RX_PAUSE); + bp->flags |= pause_flags; + + val = br32(bp, B44_RXCONFIG); + if (pause_flags & B44_FLAG_RX_PAUSE) + val |= RXCONFIG_FLOW; + else + val &= ~RXCONFIG_FLOW; + bw32(bp, B44_RXCONFIG, val); + + val = br32(bp, B44_MAC_FLOW); + if (pause_flags & B44_FLAG_TX_PAUSE) + val |= (MAC_FLOW_PAUSE_ENAB | + (0xc0 & MAC_FLOW_RX_HI_WATER)); + else + val &= ~MAC_FLOW_PAUSE_ENAB; + bw32(bp, B44_MAC_FLOW, val); +} + +static void b44_set_flow_ctrl(struct b44 *bp, u32 local, u32 remote) +{ + u32 pause_enab = 0; + + /* The driver supports only rx pause by default because + the b44 mac tx pause mechanism generates excessive + pause frames. + Use ethtool to turn on b44 tx pause if necessary. + */ + if ((local & ADVERTISE_PAUSE_CAP) && + (local & ADVERTISE_PAUSE_ASYM)){ + if ((remote & LPA_PAUSE_ASYM) && + !(remote & LPA_PAUSE_CAP)) + pause_enab |= B44_FLAG_RX_PAUSE; + } + + __b44_set_flow_ctrl(bp, pause_enab); +} + +#ifdef CONFIG_BCM47XX +#include +static void b44_wap54g10_workaround(struct b44 *bp) +{ + char buf[20]; + u32 val; + int err; + + /* + * workaround for bad hardware design in Linksys WAP54G v1.0 + * see https://dev.openwrt.org/ticket/146 + * check and reset bit "isolate" + */ + if (bcm47xx_nvram_getenv("boardnum", buf, sizeof(buf)) < 0) + return; + if (simple_strtoul(buf, NULL, 0) == 2) { + err = __b44_readphy(bp, 0, MII_BMCR, &val); + if (err) + goto error; + if (!(val & BMCR_ISOLATE)) + return; + val &= ~BMCR_ISOLATE; + err = __b44_writephy(bp, 0, MII_BMCR, val); + if (err) + goto error; + } + return; +error: + pr_warning("PHY: cannot reset MII transceiver isolate bit\n"); +} +#else +static inline void b44_wap54g10_workaround(struct b44 *bp) +{ +} +#endif + +static int b44_setup_phy(struct b44 *bp) +{ + u32 val; + int err; + + b44_wap54g10_workaround(bp); + + if (bp->phy_addr == B44_PHY_ADDR_NO_PHY) + return 0; + if ((err = b44_readphy(bp, B44_MII_ALEDCTRL, &val)) != 0) + goto out; + if ((err = b44_writephy(bp, B44_MII_ALEDCTRL, + val & MII_ALEDCTRL_ALLMSK)) != 0) + goto out; + if ((err = b44_readphy(bp, B44_MII_TLEDCTRL, &val)) != 0) + goto out; + if ((err = b44_writephy(bp, B44_MII_TLEDCTRL, + val | MII_TLEDCTRL_ENABLE)) != 0) + goto out; + + if (!(bp->flags & B44_FLAG_FORCE_LINK)) { + u32 adv = ADVERTISE_CSMA; + + if (bp->flags & B44_FLAG_ADV_10HALF) + adv |= ADVERTISE_10HALF; + if (bp->flags & B44_FLAG_ADV_10FULL) + adv |= ADVERTISE_10FULL; + if (bp->flags & B44_FLAG_ADV_100HALF) + adv |= ADVERTISE_100HALF; + if (bp->flags & B44_FLAG_ADV_100FULL) + adv |= ADVERTISE_100FULL; + + if (bp->flags & B44_FLAG_PAUSE_AUTO) + adv |= ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM; + + if ((err = b44_writephy(bp, MII_ADVERTISE, adv)) != 0) + goto out; + if ((err = b44_writephy(bp, MII_BMCR, (BMCR_ANENABLE | + BMCR_ANRESTART))) != 0) + goto out; + } else { + u32 bmcr; + + if ((err = b44_readphy(bp, MII_BMCR, &bmcr)) != 0) + goto out; + bmcr &= ~(BMCR_FULLDPLX | BMCR_ANENABLE | BMCR_SPEED100); + if (bp->flags & B44_FLAG_100_BASE_T) + bmcr |= BMCR_SPEED100; + if (bp->flags & B44_FLAG_FULL_DUPLEX) + bmcr |= BMCR_FULLDPLX; + if ((err = b44_writephy(bp, MII_BMCR, bmcr)) != 0) + goto out; + + /* Since we will not be negotiating there is no safe way + * to determine if the link partner supports flow control + * or not. So just disable it completely in this case. + */ + b44_set_flow_ctrl(bp, 0, 0); + } + +out: + return err; +} + +static void b44_stats_update(struct b44 *bp) +{ + unsigned long reg; + u64 *val; + + val = &bp->hw_stats.tx_good_octets; + u64_stats_update_begin(&bp->hw_stats.syncp); + + for (reg = B44_TX_GOOD_O; reg <= B44_TX_PAUSE; reg += 4UL) { + *val++ += br32(bp, reg); + } + + /* Pad */ + reg += 8*4UL; + + for (reg = B44_RX_GOOD_O; reg <= B44_RX_NPAUSE; reg += 4UL) { + *val++ += br32(bp, reg); + } + + u64_stats_update_end(&bp->hw_stats.syncp); +} + +static void b44_link_report(struct b44 *bp) +{ + if (!netif_carrier_ok(bp->dev)) { + netdev_info(bp->dev, "Link is down\n"); + } else { + netdev_info(bp->dev, "Link is up at %d Mbps, %s duplex\n", + (bp->flags & B44_FLAG_100_BASE_T) ? 100 : 10, + (bp->flags & B44_FLAG_FULL_DUPLEX) ? "full" : "half"); + + netdev_info(bp->dev, "Flow control is %s for TX and %s for RX\n", + (bp->flags & B44_FLAG_TX_PAUSE) ? "on" : "off", + (bp->flags & B44_FLAG_RX_PAUSE) ? "on" : "off"); + } +} + +static void b44_check_phy(struct b44 *bp) +{ + u32 bmsr, aux; + + if (bp->phy_addr == B44_PHY_ADDR_NO_PHY) { + bp->flags |= B44_FLAG_100_BASE_T; + bp->flags |= B44_FLAG_FULL_DUPLEX; + if (!netif_carrier_ok(bp->dev)) { + u32 val = br32(bp, B44_TX_CTRL); + val |= TX_CTRL_DUPLEX; + bw32(bp, B44_TX_CTRL, val); + netif_carrier_on(bp->dev); + b44_link_report(bp); + } + return; + } + + if (!b44_readphy(bp, MII_BMSR, &bmsr) && + !b44_readphy(bp, B44_MII_AUXCTRL, &aux) && + (bmsr != 0xffff)) { + if (aux & MII_AUXCTRL_SPEED) + bp->flags |= B44_FLAG_100_BASE_T; + else + bp->flags &= ~B44_FLAG_100_BASE_T; + if (aux & MII_AUXCTRL_DUPLEX) + bp->flags |= B44_FLAG_FULL_DUPLEX; + else + bp->flags &= ~B44_FLAG_FULL_DUPLEX; + + if (!netif_carrier_ok(bp->dev) && + (bmsr & BMSR_LSTATUS)) { + u32 val = br32(bp, B44_TX_CTRL); + u32 local_adv, remote_adv; + + if (bp->flags & B44_FLAG_FULL_DUPLEX) + val |= TX_CTRL_DUPLEX; + else + val &= ~TX_CTRL_DUPLEX; + bw32(bp, B44_TX_CTRL, val); + + if (!(bp->flags & B44_FLAG_FORCE_LINK) && + !b44_readphy(bp, MII_ADVERTISE, &local_adv) && + !b44_readphy(bp, MII_LPA, &remote_adv)) + b44_set_flow_ctrl(bp, local_adv, remote_adv); + + /* Link now up */ + netif_carrier_on(bp->dev); + b44_link_report(bp); + } else if (netif_carrier_ok(bp->dev) && !(bmsr & BMSR_LSTATUS)) { + /* Link now down */ + netif_carrier_off(bp->dev); + b44_link_report(bp); + } + + if (bmsr & BMSR_RFAULT) + netdev_warn(bp->dev, "Remote fault detected in PHY\n"); + if (bmsr & BMSR_JCD) + netdev_warn(bp->dev, "Jabber detected in PHY\n"); + } +} + +static void b44_timer(unsigned long __opaque) +{ + struct b44 *bp = (struct b44 *) __opaque; + + spin_lock_irq(&bp->lock); + + b44_check_phy(bp); + + b44_stats_update(bp); + + spin_unlock_irq(&bp->lock); + + mod_timer(&bp->timer, round_jiffies(jiffies + HZ)); +} + +static void b44_tx(struct b44 *bp) +{ + u32 cur, cons; + + cur = br32(bp, B44_DMATX_STAT) & DMATX_STAT_CDMASK; + cur /= sizeof(struct dma_desc); + + /* XXX needs updating when NETIF_F_SG is supported */ + for (cons = bp->tx_cons; cons != cur; cons = NEXT_TX(cons)) { + struct ring_info *rp = &bp->tx_buffers[cons]; + struct sk_buff *skb = rp->skb; + + BUG_ON(skb == NULL); + + dma_unmap_single(bp->sdev->dma_dev, + rp->mapping, + skb->len, + DMA_TO_DEVICE); + rp->skb = NULL; + dev_kfree_skb_irq(skb); + } + + bp->tx_cons = cons; + if (netif_queue_stopped(bp->dev) && + TX_BUFFS_AVAIL(bp) > B44_TX_WAKEUP_THRESH) + netif_wake_queue(bp->dev); + + bw32(bp, B44_GPTIMER, 0); +} + +/* Works like this. This chip writes a 'struct rx_header" 30 bytes + * before the DMA address you give it. So we allocate 30 more bytes + * for the RX buffer, DMA map all of it, skb_reserve the 30 bytes, then + * point the chip at 30 bytes past where the rx_header will go. + */ +static int b44_alloc_rx_skb(struct b44 *bp, int src_idx, u32 dest_idx_unmasked) +{ + struct dma_desc *dp; + struct ring_info *src_map, *map; + struct rx_header *rh; + struct sk_buff *skb; + dma_addr_t mapping; + int dest_idx; + u32 ctrl; + + src_map = NULL; + if (src_idx >= 0) + src_map = &bp->rx_buffers[src_idx]; + dest_idx = dest_idx_unmasked & (B44_RX_RING_SIZE - 1); + map = &bp->rx_buffers[dest_idx]; + skb = netdev_alloc_skb(bp->dev, RX_PKT_BUF_SZ); + if (skb == NULL) + return -ENOMEM; + + mapping = dma_map_single(bp->sdev->dma_dev, skb->data, + RX_PKT_BUF_SZ, + DMA_FROM_DEVICE); + + /* Hardware bug work-around, the chip is unable to do PCI DMA + to/from anything above 1GB :-( */ + if (dma_mapping_error(bp->sdev->dma_dev, mapping) || + mapping + RX_PKT_BUF_SZ > DMA_BIT_MASK(30)) { + /* Sigh... */ + if (!dma_mapping_error(bp->sdev->dma_dev, mapping)) + dma_unmap_single(bp->sdev->dma_dev, mapping, + RX_PKT_BUF_SZ, DMA_FROM_DEVICE); + dev_kfree_skb_any(skb); + skb = alloc_skb(RX_PKT_BUF_SZ, GFP_ATOMIC | GFP_DMA); + if (skb == NULL) + return -ENOMEM; + mapping = dma_map_single(bp->sdev->dma_dev, skb->data, + RX_PKT_BUF_SZ, + DMA_FROM_DEVICE); + if (dma_mapping_error(bp->sdev->dma_dev, mapping) || + mapping + RX_PKT_BUF_SZ > DMA_BIT_MASK(30)) { + if (!dma_mapping_error(bp->sdev->dma_dev, mapping)) + dma_unmap_single(bp->sdev->dma_dev, mapping, RX_PKT_BUF_SZ,DMA_FROM_DEVICE); + dev_kfree_skb_any(skb); + return -ENOMEM; + } + bp->force_copybreak = 1; + } + + rh = (struct rx_header *) skb->data; + + rh->len = 0; + rh->flags = 0; + + map->skb = skb; + map->mapping = mapping; + + if (src_map != NULL) + src_map->skb = NULL; + + ctrl = (DESC_CTRL_LEN & RX_PKT_BUF_SZ); + if (dest_idx == (B44_RX_RING_SIZE - 1)) + ctrl |= DESC_CTRL_EOT; + + dp = &bp->rx_ring[dest_idx]; + dp->ctrl = cpu_to_le32(ctrl); + dp->addr = cpu_to_le32((u32) mapping + bp->dma_offset); + + if (bp->flags & B44_FLAG_RX_RING_HACK) + b44_sync_dma_desc_for_device(bp->sdev, bp->rx_ring_dma, + dest_idx * sizeof(*dp), + DMA_BIDIRECTIONAL); + + return RX_PKT_BUF_SZ; +} + +static void b44_recycle_rx(struct b44 *bp, int src_idx, u32 dest_idx_unmasked) +{ + struct dma_desc *src_desc, *dest_desc; + struct ring_info *src_map, *dest_map; + struct rx_header *rh; + int dest_idx; + __le32 ctrl; + + dest_idx = dest_idx_unmasked & (B44_RX_RING_SIZE - 1); + dest_desc = &bp->rx_ring[dest_idx]; + dest_map = &bp->rx_buffers[dest_idx]; + src_desc = &bp->rx_ring[src_idx]; + src_map = &bp->rx_buffers[src_idx]; + + dest_map->skb = src_map->skb; + rh = (struct rx_header *) src_map->skb->data; + rh->len = 0; + rh->flags = 0; + dest_map->mapping = src_map->mapping; + + if (bp->flags & B44_FLAG_RX_RING_HACK) + b44_sync_dma_desc_for_cpu(bp->sdev, bp->rx_ring_dma, + src_idx * sizeof(*src_desc), + DMA_BIDIRECTIONAL); + + ctrl = src_desc->ctrl; + if (dest_idx == (B44_RX_RING_SIZE - 1)) + ctrl |= cpu_to_le32(DESC_CTRL_EOT); + else + ctrl &= cpu_to_le32(~DESC_CTRL_EOT); + + dest_desc->ctrl = ctrl; + dest_desc->addr = src_desc->addr; + + src_map->skb = NULL; + + if (bp->flags & B44_FLAG_RX_RING_HACK) + b44_sync_dma_desc_for_device(bp->sdev, bp->rx_ring_dma, + dest_idx * sizeof(*dest_desc), + DMA_BIDIRECTIONAL); + + dma_sync_single_for_device(bp->sdev->dma_dev, dest_map->mapping, + RX_PKT_BUF_SZ, + DMA_FROM_DEVICE); +} + +static int b44_rx(struct b44 *bp, int budget) +{ + int received; + u32 cons, prod; + + received = 0; + prod = br32(bp, B44_DMARX_STAT) & DMARX_STAT_CDMASK; + prod /= sizeof(struct dma_desc); + cons = bp->rx_cons; + + while (cons != prod && budget > 0) { + struct ring_info *rp = &bp->rx_buffers[cons]; + struct sk_buff *skb = rp->skb; + dma_addr_t map = rp->mapping; + struct rx_header *rh; + u16 len; + + dma_sync_single_for_cpu(bp->sdev->dma_dev, map, + RX_PKT_BUF_SZ, + DMA_FROM_DEVICE); + rh = (struct rx_header *) skb->data; + len = le16_to_cpu(rh->len); + if ((len > (RX_PKT_BUF_SZ - RX_PKT_OFFSET)) || + (rh->flags & cpu_to_le16(RX_FLAG_ERRORS))) { + drop_it: + b44_recycle_rx(bp, cons, bp->rx_prod); + drop_it_no_recycle: + bp->dev->stats.rx_dropped++; + goto next_pkt; + } + + if (len == 0) { + int i = 0; + + do { + udelay(2); + barrier(); + len = le16_to_cpu(rh->len); + } while (len == 0 && i++ < 5); + if (len == 0) + goto drop_it; + } + + /* Omit CRC. */ + len -= 4; + + if (!bp->force_copybreak && len > RX_COPY_THRESHOLD) { + int skb_size; + skb_size = b44_alloc_rx_skb(bp, cons, bp->rx_prod); + if (skb_size < 0) + goto drop_it; + dma_unmap_single(bp->sdev->dma_dev, map, + skb_size, DMA_FROM_DEVICE); + /* Leave out rx_header */ + skb_put(skb, len + RX_PKT_OFFSET); + skb_pull(skb, RX_PKT_OFFSET); + } else { + struct sk_buff *copy_skb; + + b44_recycle_rx(bp, cons, bp->rx_prod); + copy_skb = netdev_alloc_skb_ip_align(bp->dev, len); + if (copy_skb == NULL) + goto drop_it_no_recycle; + + skb_put(copy_skb, len); + /* DMA sync done above, copy just the actual packet */ + skb_copy_from_linear_data_offset(skb, RX_PKT_OFFSET, + copy_skb->data, len); + skb = copy_skb; + } + skb_checksum_none_assert(skb); + skb->protocol = eth_type_trans(skb, bp->dev); + netif_receive_skb(skb); + received++; + budget--; + next_pkt: + bp->rx_prod = (bp->rx_prod + 1) & + (B44_RX_RING_SIZE - 1); + cons = (cons + 1) & (B44_RX_RING_SIZE - 1); + } + + bp->rx_cons = cons; + bw32(bp, B44_DMARX_PTR, cons * sizeof(struct dma_desc)); + + return received; +} + +static int b44_poll(struct napi_struct *napi, int budget) +{ + struct b44 *bp = container_of(napi, struct b44, napi); + int work_done; + unsigned long flags; + + spin_lock_irqsave(&bp->lock, flags); + + if (bp->istat & (ISTAT_TX | ISTAT_TO)) { + /* spin_lock(&bp->tx_lock); */ + b44_tx(bp); + /* spin_unlock(&bp->tx_lock); */ + } + if (bp->istat & ISTAT_RFO) { /* fast recovery, in ~20msec */ + bp->istat &= ~ISTAT_RFO; + b44_disable_ints(bp); + ssb_device_enable(bp->sdev, 0); /* resets ISTAT_RFO */ + b44_init_rings(bp); + b44_init_hw(bp, B44_FULL_RESET_SKIP_PHY); + netif_wake_queue(bp->dev); + } + + spin_unlock_irqrestore(&bp->lock, flags); + + work_done = 0; + if (bp->istat & ISTAT_RX) + work_done += b44_rx(bp, budget); + + if (bp->istat & ISTAT_ERRORS) { + spin_lock_irqsave(&bp->lock, flags); + b44_halt(bp); + b44_init_rings(bp); + b44_init_hw(bp, B44_FULL_RESET_SKIP_PHY); + netif_wake_queue(bp->dev); + spin_unlock_irqrestore(&bp->lock, flags); + work_done = 0; + } + + if (work_done < budget) { + napi_complete(napi); + b44_enable_ints(bp); + } + + return work_done; +} + +static irqreturn_t b44_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct b44 *bp = netdev_priv(dev); + u32 istat, imask; + int handled = 0; + + spin_lock(&bp->lock); + + istat = br32(bp, B44_ISTAT); + imask = br32(bp, B44_IMASK); + + /* The interrupt mask register controls which interrupt bits + * will actually raise an interrupt to the CPU when set by hw/firmware, + * but doesn't mask off the bits. + */ + istat &= imask; + if (istat) { + handled = 1; + + if (unlikely(!netif_running(dev))) { + netdev_info(dev, "late interrupt\n"); + goto irq_ack; + } + + if (napi_schedule_prep(&bp->napi)) { + /* NOTE: These writes are posted by the readback of + * the ISTAT register below. + */ + bp->istat = istat; + __b44_disable_ints(bp); + __napi_schedule(&bp->napi); + } + +irq_ack: + bw32(bp, B44_ISTAT, istat); + br32(bp, B44_ISTAT); + } + spin_unlock(&bp->lock); + return IRQ_RETVAL(handled); +} + +static void b44_tx_timeout(struct net_device *dev) +{ + struct b44 *bp = netdev_priv(dev); + + netdev_err(dev, "transmit timed out, resetting\n"); + + spin_lock_irq(&bp->lock); + + b44_halt(bp); + b44_init_rings(bp); + b44_init_hw(bp, B44_FULL_RESET); + + spin_unlock_irq(&bp->lock); + + b44_enable_ints(bp); + + netif_wake_queue(dev); +} + +static netdev_tx_t b44_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct b44 *bp = netdev_priv(dev); + int rc = NETDEV_TX_OK; + dma_addr_t mapping; + u32 len, entry, ctrl; + unsigned long flags; + + len = skb->len; + spin_lock_irqsave(&bp->lock, flags); + + /* This is a hard error, log it. */ + if (unlikely(TX_BUFFS_AVAIL(bp) < 1)) { + netif_stop_queue(dev); + netdev_err(dev, "BUG! Tx Ring full when queue awake!\n"); + goto err_out; + } + + mapping = dma_map_single(bp->sdev->dma_dev, skb->data, len, DMA_TO_DEVICE); + if (dma_mapping_error(bp->sdev->dma_dev, mapping) || mapping + len > DMA_BIT_MASK(30)) { + struct sk_buff *bounce_skb; + + /* Chip can't handle DMA to/from >1GB, use bounce buffer */ + if (!dma_mapping_error(bp->sdev->dma_dev, mapping)) + dma_unmap_single(bp->sdev->dma_dev, mapping, len, + DMA_TO_DEVICE); + + bounce_skb = alloc_skb(len, GFP_ATOMIC | GFP_DMA); + if (!bounce_skb) + goto err_out; + + mapping = dma_map_single(bp->sdev->dma_dev, bounce_skb->data, + len, DMA_TO_DEVICE); + if (dma_mapping_error(bp->sdev->dma_dev, mapping) || mapping + len > DMA_BIT_MASK(30)) { + if (!dma_mapping_error(bp->sdev->dma_dev, mapping)) + dma_unmap_single(bp->sdev->dma_dev, mapping, + len, DMA_TO_DEVICE); + dev_kfree_skb_any(bounce_skb); + goto err_out; + } + + skb_copy_from_linear_data(skb, skb_put(bounce_skb, len), len); + dev_kfree_skb_any(skb); + skb = bounce_skb; + } + + entry = bp->tx_prod; + bp->tx_buffers[entry].skb = skb; + bp->tx_buffers[entry].mapping = mapping; + + ctrl = (len & DESC_CTRL_LEN); + ctrl |= DESC_CTRL_IOC | DESC_CTRL_SOF | DESC_CTRL_EOF; + if (entry == (B44_TX_RING_SIZE - 1)) + ctrl |= DESC_CTRL_EOT; + + bp->tx_ring[entry].ctrl = cpu_to_le32(ctrl); + bp->tx_ring[entry].addr = cpu_to_le32((u32) mapping+bp->dma_offset); + + if (bp->flags & B44_FLAG_TX_RING_HACK) + b44_sync_dma_desc_for_device(bp->sdev, bp->tx_ring_dma, + entry * sizeof(bp->tx_ring[0]), + DMA_TO_DEVICE); + + entry = NEXT_TX(entry); + + bp->tx_prod = entry; + + wmb(); + + bw32(bp, B44_DMATX_PTR, entry * sizeof(struct dma_desc)); + if (bp->flags & B44_FLAG_BUGGY_TXPTR) + bw32(bp, B44_DMATX_PTR, entry * sizeof(struct dma_desc)); + if (bp->flags & B44_FLAG_REORDER_BUG) + br32(bp, B44_DMATX_PTR); + + if (TX_BUFFS_AVAIL(bp) < 1) + netif_stop_queue(dev); + +out_unlock: + spin_unlock_irqrestore(&bp->lock, flags); + + return rc; + +err_out: + rc = NETDEV_TX_BUSY; + goto out_unlock; +} + +static int b44_change_mtu(struct net_device *dev, int new_mtu) +{ + struct b44 *bp = netdev_priv(dev); + + if (new_mtu < B44_MIN_MTU || new_mtu > B44_MAX_MTU) + return -EINVAL; + + if (!netif_running(dev)) { + /* We'll just catch it later when the + * device is up'd. + */ + dev->mtu = new_mtu; + return 0; + } + + spin_lock_irq(&bp->lock); + b44_halt(bp); + dev->mtu = new_mtu; + b44_init_rings(bp); + b44_init_hw(bp, B44_FULL_RESET); + spin_unlock_irq(&bp->lock); + + b44_enable_ints(bp); + + return 0; +} + +/* Free up pending packets in all rx/tx rings. + * + * The chip has been shut down and the driver detached from + * the networking, so no interrupts or new tx packets will + * end up in the driver. bp->lock is not held and we are not + * in an interrupt context and thus may sleep. + */ +static void b44_free_rings(struct b44 *bp) +{ + struct ring_info *rp; + int i; + + for (i = 0; i < B44_RX_RING_SIZE; i++) { + rp = &bp->rx_buffers[i]; + + if (rp->skb == NULL) + continue; + dma_unmap_single(bp->sdev->dma_dev, rp->mapping, RX_PKT_BUF_SZ, + DMA_FROM_DEVICE); + dev_kfree_skb_any(rp->skb); + rp->skb = NULL; + } + + /* XXX needs changes once NETIF_F_SG is set... */ + for (i = 0; i < B44_TX_RING_SIZE; i++) { + rp = &bp->tx_buffers[i]; + + if (rp->skb == NULL) + continue; + dma_unmap_single(bp->sdev->dma_dev, rp->mapping, rp->skb->len, + DMA_TO_DEVICE); + dev_kfree_skb_any(rp->skb); + rp->skb = NULL; + } +} + +/* Initialize tx/rx rings for packet processing. + * + * The chip has been shut down and the driver detached from + * the networking, so no interrupts or new tx packets will + * end up in the driver. + */ +static void b44_init_rings(struct b44 *bp) +{ + int i; + + b44_free_rings(bp); + + memset(bp->rx_ring, 0, B44_RX_RING_BYTES); + memset(bp->tx_ring, 0, B44_TX_RING_BYTES); + + if (bp->flags & B44_FLAG_RX_RING_HACK) + dma_sync_single_for_device(bp->sdev->dma_dev, bp->rx_ring_dma, + DMA_TABLE_BYTES, DMA_BIDIRECTIONAL); + + if (bp->flags & B44_FLAG_TX_RING_HACK) + dma_sync_single_for_device(bp->sdev->dma_dev, bp->tx_ring_dma, + DMA_TABLE_BYTES, DMA_TO_DEVICE); + + for (i = 0; i < bp->rx_pending; i++) { + if (b44_alloc_rx_skb(bp, -1, i) < 0) + break; + } +} + +/* + * Must not be invoked with interrupt sources disabled and + * the hardware shutdown down. + */ +static void b44_free_consistent(struct b44 *bp) +{ + kfree(bp->rx_buffers); + bp->rx_buffers = NULL; + kfree(bp->tx_buffers); + bp->tx_buffers = NULL; + if (bp->rx_ring) { + if (bp->flags & B44_FLAG_RX_RING_HACK) { + dma_unmap_single(bp->sdev->dma_dev, bp->rx_ring_dma, + DMA_TABLE_BYTES, DMA_BIDIRECTIONAL); + kfree(bp->rx_ring); + } else + dma_free_coherent(bp->sdev->dma_dev, DMA_TABLE_BYTES, + bp->rx_ring, bp->rx_ring_dma); + bp->rx_ring = NULL; + bp->flags &= ~B44_FLAG_RX_RING_HACK; + } + if (bp->tx_ring) { + if (bp->flags & B44_FLAG_TX_RING_HACK) { + dma_unmap_single(bp->sdev->dma_dev, bp->tx_ring_dma, + DMA_TABLE_BYTES, DMA_TO_DEVICE); + kfree(bp->tx_ring); + } else + dma_free_coherent(bp->sdev->dma_dev, DMA_TABLE_BYTES, + bp->tx_ring, bp->tx_ring_dma); + bp->tx_ring = NULL; + bp->flags &= ~B44_FLAG_TX_RING_HACK; + } +} + +/* + * Must not be invoked with interrupt sources disabled and + * the hardware shutdown down. Can sleep. + */ +static int b44_alloc_consistent(struct b44 *bp, gfp_t gfp) +{ + int size; + + size = B44_RX_RING_SIZE * sizeof(struct ring_info); + bp->rx_buffers = kzalloc(size, gfp); + if (!bp->rx_buffers) + goto out_err; + + size = B44_TX_RING_SIZE * sizeof(struct ring_info); + bp->tx_buffers = kzalloc(size, gfp); + if (!bp->tx_buffers) + goto out_err; + + size = DMA_TABLE_BYTES; + bp->rx_ring = dma_alloc_coherent(bp->sdev->dma_dev, size, + &bp->rx_ring_dma, gfp); + if (!bp->rx_ring) { + /* Allocation may have failed due to pci_alloc_consistent + insisting on use of GFP_DMA, which is more restrictive + than necessary... */ + struct dma_desc *rx_ring; + dma_addr_t rx_ring_dma; + + rx_ring = kzalloc(size, gfp); + if (!rx_ring) + goto out_err; + + rx_ring_dma = dma_map_single(bp->sdev->dma_dev, rx_ring, + DMA_TABLE_BYTES, + DMA_BIDIRECTIONAL); + + if (dma_mapping_error(bp->sdev->dma_dev, rx_ring_dma) || + rx_ring_dma + size > DMA_BIT_MASK(30)) { + kfree(rx_ring); + goto out_err; + } + + bp->rx_ring = rx_ring; + bp->rx_ring_dma = rx_ring_dma; + bp->flags |= B44_FLAG_RX_RING_HACK; + } + + bp->tx_ring = dma_alloc_coherent(bp->sdev->dma_dev, size, + &bp->tx_ring_dma, gfp); + if (!bp->tx_ring) { + /* Allocation may have failed due to ssb_dma_alloc_consistent + insisting on use of GFP_DMA, which is more restrictive + than necessary... */ + struct dma_desc *tx_ring; + dma_addr_t tx_ring_dma; + + tx_ring = kzalloc(size, gfp); + if (!tx_ring) + goto out_err; + + tx_ring_dma = dma_map_single(bp->sdev->dma_dev, tx_ring, + DMA_TABLE_BYTES, + DMA_TO_DEVICE); + + if (dma_mapping_error(bp->sdev->dma_dev, tx_ring_dma) || + tx_ring_dma + size > DMA_BIT_MASK(30)) { + kfree(tx_ring); + goto out_err; + } + + bp->tx_ring = tx_ring; + bp->tx_ring_dma = tx_ring_dma; + bp->flags |= B44_FLAG_TX_RING_HACK; + } + + return 0; + +out_err: + b44_free_consistent(bp); + return -ENOMEM; +} + +/* bp->lock is held. */ +static void b44_clear_stats(struct b44 *bp) +{ + unsigned long reg; + + bw32(bp, B44_MIB_CTRL, MIB_CTRL_CLR_ON_READ); + for (reg = B44_TX_GOOD_O; reg <= B44_TX_PAUSE; reg += 4UL) + br32(bp, reg); + for (reg = B44_RX_GOOD_O; reg <= B44_RX_NPAUSE; reg += 4UL) + br32(bp, reg); +} + +/* bp->lock is held. */ +static void b44_chip_reset(struct b44 *bp, int reset_kind) +{ + struct ssb_device *sdev = bp->sdev; + bool was_enabled; + + was_enabled = ssb_device_is_enabled(bp->sdev); + + ssb_device_enable(bp->sdev, 0); + ssb_pcicore_dev_irqvecs_enable(&sdev->bus->pcicore, sdev); + + if (was_enabled) { + bw32(bp, B44_RCV_LAZY, 0); + bw32(bp, B44_ENET_CTRL, ENET_CTRL_DISABLE); + b44_wait_bit(bp, B44_ENET_CTRL, ENET_CTRL_DISABLE, 200, 1); + bw32(bp, B44_DMATX_CTRL, 0); + bp->tx_prod = bp->tx_cons = 0; + if (br32(bp, B44_DMARX_STAT) & DMARX_STAT_EMASK) { + b44_wait_bit(bp, B44_DMARX_STAT, DMARX_STAT_SIDLE, + 100, 0); + } + bw32(bp, B44_DMARX_CTRL, 0); + bp->rx_prod = bp->rx_cons = 0; + } + + b44_clear_stats(bp); + + /* + * Don't enable PHY if we are doing a partial reset + * we are probably going to power down + */ + if (reset_kind == B44_CHIP_RESET_PARTIAL) + return; + + switch (sdev->bus->bustype) { + case SSB_BUSTYPE_SSB: + bw32(bp, B44_MDIO_CTRL, (MDIO_CTRL_PREAMBLE | + (DIV_ROUND_CLOSEST(ssb_clockspeed(sdev->bus), + B44_MDC_RATIO) + & MDIO_CTRL_MAXF_MASK))); + break; + case SSB_BUSTYPE_PCI: + bw32(bp, B44_MDIO_CTRL, (MDIO_CTRL_PREAMBLE | + (0x0d & MDIO_CTRL_MAXF_MASK))); + break; + case SSB_BUSTYPE_PCMCIA: + case SSB_BUSTYPE_SDIO: + WARN_ON(1); /* A device with this bus does not exist. */ + break; + } + + br32(bp, B44_MDIO_CTRL); + + if (!(br32(bp, B44_DEVCTRL) & DEVCTRL_IPP)) { + bw32(bp, B44_ENET_CTRL, ENET_CTRL_EPSEL); + br32(bp, B44_ENET_CTRL); + bp->flags &= ~B44_FLAG_INTERNAL_PHY; + } else { + u32 val = br32(bp, B44_DEVCTRL); + + if (val & DEVCTRL_EPR) { + bw32(bp, B44_DEVCTRL, (val & ~DEVCTRL_EPR)); + br32(bp, B44_DEVCTRL); + udelay(100); + } + bp->flags |= B44_FLAG_INTERNAL_PHY; + } +} + +/* bp->lock is held. */ +static void b44_halt(struct b44 *bp) +{ + b44_disable_ints(bp); + /* reset PHY */ + b44_phy_reset(bp); + /* power down PHY */ + netdev_info(bp->dev, "powering down PHY\n"); + bw32(bp, B44_MAC_CTRL, MAC_CTRL_PHY_PDOWN); + /* now reset the chip, but without enabling the MAC&PHY + * part of it. This has to be done _after_ we shut down the PHY */ + b44_chip_reset(bp, B44_CHIP_RESET_PARTIAL); +} + +/* bp->lock is held. */ +static void __b44_set_mac_addr(struct b44 *bp) +{ + bw32(bp, B44_CAM_CTRL, 0); + if (!(bp->dev->flags & IFF_PROMISC)) { + u32 val; + + __b44_cam_write(bp, bp->dev->dev_addr, 0); + val = br32(bp, B44_CAM_CTRL); + bw32(bp, B44_CAM_CTRL, val | CAM_CTRL_ENABLE); + } +} + +static int b44_set_mac_addr(struct net_device *dev, void *p) +{ + struct b44 *bp = netdev_priv(dev); + struct sockaddr *addr = p; + u32 val; + + if (netif_running(dev)) + return -EBUSY; + + if (!is_valid_ether_addr(addr->sa_data)) + return -EINVAL; + + memcpy(dev->dev_addr, addr->sa_data, dev->addr_len); + + spin_lock_irq(&bp->lock); + + val = br32(bp, B44_RXCONFIG); + if (!(val & RXCONFIG_CAM_ABSENT)) + __b44_set_mac_addr(bp); + + spin_unlock_irq(&bp->lock); + + return 0; +} + +/* Called at device open time to get the chip ready for + * packet processing. Invoked with bp->lock held. + */ +static void __b44_set_rx_mode(struct net_device *); +static void b44_init_hw(struct b44 *bp, int reset_kind) +{ + u32 val; + + b44_chip_reset(bp, B44_CHIP_RESET_FULL); + if (reset_kind == B44_FULL_RESET) { + b44_phy_reset(bp); + b44_setup_phy(bp); + } + + /* Enable CRC32, set proper LED modes and power on PHY */ + bw32(bp, B44_MAC_CTRL, MAC_CTRL_CRC32_ENAB | MAC_CTRL_PHY_LEDCTRL); + bw32(bp, B44_RCV_LAZY, (1 << RCV_LAZY_FC_SHIFT)); + + /* This sets the MAC address too. */ + __b44_set_rx_mode(bp->dev); + + /* MTU + eth header + possible VLAN tag + struct rx_header */ + bw32(bp, B44_RXMAXLEN, bp->dev->mtu + ETH_HLEN + 8 + RX_HEADER_LEN); + bw32(bp, B44_TXMAXLEN, bp->dev->mtu + ETH_HLEN + 8 + RX_HEADER_LEN); + + bw32(bp, B44_TX_WMARK, 56); /* XXX magic */ + if (reset_kind == B44_PARTIAL_RESET) { + bw32(bp, B44_DMARX_CTRL, (DMARX_CTRL_ENABLE | + (RX_PKT_OFFSET << DMARX_CTRL_ROSHIFT))); + } else { + bw32(bp, B44_DMATX_CTRL, DMATX_CTRL_ENABLE); + bw32(bp, B44_DMATX_ADDR, bp->tx_ring_dma + bp->dma_offset); + bw32(bp, B44_DMARX_CTRL, (DMARX_CTRL_ENABLE | + (RX_PKT_OFFSET << DMARX_CTRL_ROSHIFT))); + bw32(bp, B44_DMARX_ADDR, bp->rx_ring_dma + bp->dma_offset); + + bw32(bp, B44_DMARX_PTR, bp->rx_pending); + bp->rx_prod = bp->rx_pending; + + bw32(bp, B44_MIB_CTRL, MIB_CTRL_CLR_ON_READ); + } + + val = br32(bp, B44_ENET_CTRL); + bw32(bp, B44_ENET_CTRL, (val | ENET_CTRL_ENABLE)); +} + +static int b44_open(struct net_device *dev) +{ + struct b44 *bp = netdev_priv(dev); + int err; + + err = b44_alloc_consistent(bp, GFP_KERNEL); + if (err) + goto out; + + napi_enable(&bp->napi); + + b44_init_rings(bp); + b44_init_hw(bp, B44_FULL_RESET); + + b44_check_phy(bp); + + err = request_irq(dev->irq, b44_interrupt, IRQF_SHARED, dev->name, dev); + if (unlikely(err < 0)) { + napi_disable(&bp->napi); + b44_chip_reset(bp, B44_CHIP_RESET_PARTIAL); + b44_free_rings(bp); + b44_free_consistent(bp); + goto out; + } + + init_timer(&bp->timer); + bp->timer.expires = jiffies + HZ; + bp->timer.data = (unsigned long) bp; + bp->timer.function = b44_timer; + add_timer(&bp->timer); + + b44_enable_ints(bp); + netif_start_queue(dev); +out: + return err; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +/* + * Polling receive - used by netconsole and other diagnostic tools + * to allow network i/o with interrupts disabled. + */ +static void b44_poll_controller(struct net_device *dev) +{ + disable_irq(dev->irq); + b44_interrupt(dev->irq, dev); + enable_irq(dev->irq); +} +#endif + +static void bwfilter_table(struct b44 *bp, u8 *pp, u32 bytes, u32 table_offset) +{ + u32 i; + u32 *pattern = (u32 *) pp; + + for (i = 0; i < bytes; i += sizeof(u32)) { + bw32(bp, B44_FILT_ADDR, table_offset + i); + bw32(bp, B44_FILT_DATA, pattern[i / sizeof(u32)]); + } +} + +static int b44_magic_pattern(u8 *macaddr, u8 *ppattern, u8 *pmask, int offset) +{ + int magicsync = 6; + int k, j, len = offset; + int ethaddr_bytes = ETH_ALEN; + + memset(ppattern + offset, 0xff, magicsync); + for (j = 0; j < magicsync; j++) + set_bit(len++, (unsigned long *) pmask); + + for (j = 0; j < B44_MAX_PATTERNS; j++) { + if ((B44_PATTERN_SIZE - len) >= ETH_ALEN) + ethaddr_bytes = ETH_ALEN; + else + ethaddr_bytes = B44_PATTERN_SIZE - len; + if (ethaddr_bytes <=0) + break; + for (k = 0; k< ethaddr_bytes; k++) { + ppattern[offset + magicsync + + (j * ETH_ALEN) + k] = macaddr[k]; + set_bit(len++, (unsigned long *) pmask); + } + } + return len - 1; +} + +/* Setup magic packet patterns in the b44 WOL + * pattern matching filter. + */ +static void b44_setup_pseudo_magicp(struct b44 *bp) +{ + + u32 val; + int plen0, plen1, plen2; + u8 *pwol_pattern; + u8 pwol_mask[B44_PMASK_SIZE]; + + pwol_pattern = kzalloc(B44_PATTERN_SIZE, GFP_KERNEL); + if (!pwol_pattern) + return; + + /* Ipv4 magic packet pattern - pattern 0.*/ + memset(pwol_mask, 0, B44_PMASK_SIZE); + plen0 = b44_magic_pattern(bp->dev->dev_addr, pwol_pattern, pwol_mask, + B44_ETHIPV4UDP_HLEN); + + bwfilter_table(bp, pwol_pattern, B44_PATTERN_SIZE, B44_PATTERN_BASE); + bwfilter_table(bp, pwol_mask, B44_PMASK_SIZE, B44_PMASK_BASE); + + /* Raw ethernet II magic packet pattern - pattern 1 */ + memset(pwol_pattern, 0, B44_PATTERN_SIZE); + memset(pwol_mask, 0, B44_PMASK_SIZE); + plen1 = b44_magic_pattern(bp->dev->dev_addr, pwol_pattern, pwol_mask, + ETH_HLEN); + + bwfilter_table(bp, pwol_pattern, B44_PATTERN_SIZE, + B44_PATTERN_BASE + B44_PATTERN_SIZE); + bwfilter_table(bp, pwol_mask, B44_PMASK_SIZE, + B44_PMASK_BASE + B44_PMASK_SIZE); + + /* Ipv6 magic packet pattern - pattern 2 */ + memset(pwol_pattern, 0, B44_PATTERN_SIZE); + memset(pwol_mask, 0, B44_PMASK_SIZE); + plen2 = b44_magic_pattern(bp->dev->dev_addr, pwol_pattern, pwol_mask, + B44_ETHIPV6UDP_HLEN); + + bwfilter_table(bp, pwol_pattern, B44_PATTERN_SIZE, + B44_PATTERN_BASE + B44_PATTERN_SIZE + B44_PATTERN_SIZE); + bwfilter_table(bp, pwol_mask, B44_PMASK_SIZE, + B44_PMASK_BASE + B44_PMASK_SIZE + B44_PMASK_SIZE); + + kfree(pwol_pattern); + + /* set these pattern's lengths: one less than each real length */ + val = plen0 | (plen1 << 8) | (plen2 << 16) | WKUP_LEN_ENABLE_THREE; + bw32(bp, B44_WKUP_LEN, val); + + /* enable wakeup pattern matching */ + val = br32(bp, B44_DEVCTRL); + bw32(bp, B44_DEVCTRL, val | DEVCTRL_PFE); + +} + +#ifdef CPTCFG_B44_PCI +static void b44_setup_wol_pci(struct b44 *bp) +{ + u16 val; + + if (bp->sdev->bus->bustype != SSB_BUSTYPE_SSB) { + bw32(bp, SSB_TMSLOW, br32(bp, SSB_TMSLOW) | SSB_TMSLOW_PE); + pci_read_config_word(bp->sdev->bus->host_pci, SSB_PMCSR, &val); + pci_write_config_word(bp->sdev->bus->host_pci, SSB_PMCSR, val | SSB_PE); + } +} +#else +static inline void b44_setup_wol_pci(struct b44 *bp) { } +#endif /* CPTCFG_B44_PCI */ + +static void b44_setup_wol(struct b44 *bp) +{ + u32 val; + + bw32(bp, B44_RXCONFIG, RXCONFIG_ALLMULTI); + + if (bp->flags & B44_FLAG_B0_ANDLATER) { + + bw32(bp, B44_WKUP_LEN, WKUP_LEN_DISABLE); + + val = bp->dev->dev_addr[2] << 24 | + bp->dev->dev_addr[3] << 16 | + bp->dev->dev_addr[4] << 8 | + bp->dev->dev_addr[5]; + bw32(bp, B44_ADDR_LO, val); + + val = bp->dev->dev_addr[0] << 8 | + bp->dev->dev_addr[1]; + bw32(bp, B44_ADDR_HI, val); + + val = br32(bp, B44_DEVCTRL); + bw32(bp, B44_DEVCTRL, val | DEVCTRL_MPM | DEVCTRL_PFE); + + } else { + b44_setup_pseudo_magicp(bp); + } + b44_setup_wol_pci(bp); +} + +static int b44_close(struct net_device *dev) +{ + struct b44 *bp = netdev_priv(dev); + + netif_stop_queue(dev); + + napi_disable(&bp->napi); + + del_timer_sync(&bp->timer); + + spin_lock_irq(&bp->lock); + + b44_halt(bp); + b44_free_rings(bp); + netif_carrier_off(dev); + + spin_unlock_irq(&bp->lock); + + free_irq(dev->irq, dev); + + if (bp->flags & B44_FLAG_WOL_ENABLE) { + b44_init_hw(bp, B44_PARTIAL_RESET); + b44_setup_wol(bp); + } + + b44_free_consistent(bp); + + return 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)) +static struct rtnl_link_stats64 *b44_get_stats64(struct net_device *dev, + struct rtnl_link_stats64 *nstat) +#else +static struct net_device_stats *b44_get_stats(struct net_device *dev) +#endif +{ + struct b44 *bp = netdev_priv(dev); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36)) + struct net_device_stats *nstat = &dev->stats; +#endif + struct b44_hw_stats *hwstat = &bp->hw_stats; + unsigned int start; + + do { + start = u64_stats_fetch_begin_bh(&hwstat->syncp); + + /* Convert HW stats into rtnl_link_stats64 stats. */ + nstat->rx_packets = hwstat->rx_pkts; + nstat->tx_packets = hwstat->tx_pkts; + nstat->rx_bytes = hwstat->rx_octets; + nstat->tx_bytes = hwstat->tx_octets; + nstat->tx_errors = (hwstat->tx_jabber_pkts + + hwstat->tx_oversize_pkts + + hwstat->tx_underruns + + hwstat->tx_excessive_cols + + hwstat->tx_late_cols); + nstat->multicast = hwstat->tx_multicast_pkts; + nstat->collisions = hwstat->tx_total_cols; + + nstat->rx_length_errors = (hwstat->rx_oversize_pkts + + hwstat->rx_undersize); + nstat->rx_over_errors = hwstat->rx_missed_pkts; + nstat->rx_frame_errors = hwstat->rx_align_errs; + nstat->rx_crc_errors = hwstat->rx_crc_errs; + nstat->rx_errors = (hwstat->rx_jabber_pkts + + hwstat->rx_oversize_pkts + + hwstat->rx_missed_pkts + + hwstat->rx_crc_align_errs + + hwstat->rx_undersize + + hwstat->rx_crc_errs + + hwstat->rx_align_errs + + hwstat->rx_symbol_errs); + + nstat->tx_aborted_errors = hwstat->tx_underruns; +#if 0 + /* Carrier lost counter seems to be broken for some devices */ + nstat->tx_carrier_errors = hwstat->tx_carrier_lost; +#endif + } while (u64_stats_fetch_retry_bh(&hwstat->syncp, start)); + + return nstat; +} + +static int __b44_load_mcast(struct b44 *bp, struct net_device *dev) +{ + struct netdev_hw_addr *ha; + int i, num_ents; + + num_ents = min_t(int, netdev_mc_count(dev), B44_MCAST_TABLE_SIZE); + i = 0; + netdev_for_each_mc_addr(ha, dev) { + if (i == num_ents) + break; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + __b44_cam_write(bp, ha->addr, i++ + 1); +#else + __b44_cam_write(bp, ha->dmi_addr, i++ + 1); +#endif + } + return i+1; +} + +static void __b44_set_rx_mode(struct net_device *dev) +{ + struct b44 *bp = netdev_priv(dev); + u32 val; + + val = br32(bp, B44_RXCONFIG); + val &= ~(RXCONFIG_PROMISC | RXCONFIG_ALLMULTI); + if ((dev->flags & IFF_PROMISC) || (val & RXCONFIG_CAM_ABSENT)) { + val |= RXCONFIG_PROMISC; + bw32(bp, B44_RXCONFIG, val); + } else { + unsigned char zero[6] = {0, 0, 0, 0, 0, 0}; + int i = 1; + + __b44_set_mac_addr(bp); + + if ((dev->flags & IFF_ALLMULTI) || + (netdev_mc_count(dev) > B44_MCAST_TABLE_SIZE)) + val |= RXCONFIG_ALLMULTI; + else + i = __b44_load_mcast(bp, dev); + + for (; i < 64; i++) + __b44_cam_write(bp, zero, i); + + bw32(bp, B44_RXCONFIG, val); + val = br32(bp, B44_CAM_CTRL); + bw32(bp, B44_CAM_CTRL, val | CAM_CTRL_ENABLE); + } +} + +static void b44_set_rx_mode(struct net_device *dev) +{ + struct b44 *bp = netdev_priv(dev); + + spin_lock_irq(&bp->lock); + __b44_set_rx_mode(dev); + spin_unlock_irq(&bp->lock); +} + +static u32 b44_get_msglevel(struct net_device *dev) +{ + struct b44 *bp = netdev_priv(dev); + return bp->msg_enable; +} + +static void b44_set_msglevel(struct net_device *dev, u32 value) +{ + struct b44 *bp = netdev_priv(dev); + bp->msg_enable = value; +} + +static void b44_get_drvinfo (struct net_device *dev, struct ethtool_drvinfo *info) +{ + struct b44 *bp = netdev_priv(dev); + struct ssb_bus *bus = bp->sdev->bus; + + strlcpy(info->driver, DRV_MODULE_NAME, sizeof(info->driver)); + strlcpy(info->version, DRV_MODULE_VERSION, sizeof(info->version)); + switch (bus->bustype) { + case SSB_BUSTYPE_PCI: + strlcpy(info->bus_info, pci_name(bus->host_pci), sizeof(info->bus_info)); + break; + case SSB_BUSTYPE_SSB: + strlcpy(info->bus_info, "SSB", sizeof(info->bus_info)); + break; + case SSB_BUSTYPE_PCMCIA: + case SSB_BUSTYPE_SDIO: + WARN_ON(1); /* A device with this bus does not exist. */ + break; + } +} + +static int b44_nway_reset(struct net_device *dev) +{ + struct b44 *bp = netdev_priv(dev); + u32 bmcr; + int r; + + spin_lock_irq(&bp->lock); + b44_readphy(bp, MII_BMCR, &bmcr); + b44_readphy(bp, MII_BMCR, &bmcr); + r = -EINVAL; + if (bmcr & BMCR_ANENABLE) { + b44_writephy(bp, MII_BMCR, + bmcr | BMCR_ANRESTART); + r = 0; + } + spin_unlock_irq(&bp->lock); + + return r; +} + +static int b44_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct b44 *bp = netdev_priv(dev); + + cmd->supported = (SUPPORTED_Autoneg); + cmd->supported |= (SUPPORTED_100baseT_Half | + SUPPORTED_100baseT_Full | + SUPPORTED_10baseT_Half | + SUPPORTED_10baseT_Full | + SUPPORTED_MII); + + cmd->advertising = 0; + if (bp->flags & B44_FLAG_ADV_10HALF) + cmd->advertising |= ADVERTISED_10baseT_Half; + if (bp->flags & B44_FLAG_ADV_10FULL) + cmd->advertising |= ADVERTISED_10baseT_Full; + if (bp->flags & B44_FLAG_ADV_100HALF) + cmd->advertising |= ADVERTISED_100baseT_Half; + if (bp->flags & B44_FLAG_ADV_100FULL) + cmd->advertising |= ADVERTISED_100baseT_Full; + cmd->advertising |= ADVERTISED_Pause | ADVERTISED_Asym_Pause; + ethtool_cmd_speed_set(cmd, ((bp->flags & B44_FLAG_100_BASE_T) ? + SPEED_100 : SPEED_10)); + cmd->duplex = (bp->flags & B44_FLAG_FULL_DUPLEX) ? + DUPLEX_FULL : DUPLEX_HALF; + cmd->port = 0; + cmd->phy_address = bp->phy_addr; + cmd->transceiver = (bp->flags & B44_FLAG_INTERNAL_PHY) ? + XCVR_INTERNAL : XCVR_EXTERNAL; + cmd->autoneg = (bp->flags & B44_FLAG_FORCE_LINK) ? + AUTONEG_DISABLE : AUTONEG_ENABLE; + if (cmd->autoneg == AUTONEG_ENABLE) + cmd->advertising |= ADVERTISED_Autoneg; + if (!netif_running(dev)){ + ethtool_cmd_speed_set(cmd, 0); + cmd->duplex = 0xff; + } + cmd->maxtxpkt = 0; + cmd->maxrxpkt = 0; + return 0; +} + +static int b44_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct b44 *bp = netdev_priv(dev); + u32 speed = ethtool_cmd_speed(cmd); + + /* We do not support gigabit. */ + if (cmd->autoneg == AUTONEG_ENABLE) { + if (cmd->advertising & + (ADVERTISED_1000baseT_Half | + ADVERTISED_1000baseT_Full)) + return -EINVAL; + } else if ((speed != SPEED_100 && + speed != SPEED_10) || + (cmd->duplex != DUPLEX_HALF && + cmd->duplex != DUPLEX_FULL)) { + return -EINVAL; + } + + spin_lock_irq(&bp->lock); + + if (cmd->autoneg == AUTONEG_ENABLE) { + bp->flags &= ~(B44_FLAG_FORCE_LINK | + B44_FLAG_100_BASE_T | + B44_FLAG_FULL_DUPLEX | + B44_FLAG_ADV_10HALF | + B44_FLAG_ADV_10FULL | + B44_FLAG_ADV_100HALF | + B44_FLAG_ADV_100FULL); + if (cmd->advertising == 0) { + bp->flags |= (B44_FLAG_ADV_10HALF | + B44_FLAG_ADV_10FULL | + B44_FLAG_ADV_100HALF | + B44_FLAG_ADV_100FULL); + } else { + if (cmd->advertising & ADVERTISED_10baseT_Half) + bp->flags |= B44_FLAG_ADV_10HALF; + if (cmd->advertising & ADVERTISED_10baseT_Full) + bp->flags |= B44_FLAG_ADV_10FULL; + if (cmd->advertising & ADVERTISED_100baseT_Half) + bp->flags |= B44_FLAG_ADV_100HALF; + if (cmd->advertising & ADVERTISED_100baseT_Full) + bp->flags |= B44_FLAG_ADV_100FULL; + } + } else { + bp->flags |= B44_FLAG_FORCE_LINK; + bp->flags &= ~(B44_FLAG_100_BASE_T | B44_FLAG_FULL_DUPLEX); + if (speed == SPEED_100) + bp->flags |= B44_FLAG_100_BASE_T; + if (cmd->duplex == DUPLEX_FULL) + bp->flags |= B44_FLAG_FULL_DUPLEX; + } + + if (netif_running(dev)) + b44_setup_phy(bp); + + spin_unlock_irq(&bp->lock); + + return 0; +} + +static void b44_get_ringparam(struct net_device *dev, + struct ethtool_ringparam *ering) +{ + struct b44 *bp = netdev_priv(dev); + + ering->rx_max_pending = B44_RX_RING_SIZE - 1; + ering->rx_pending = bp->rx_pending; + + /* XXX ethtool lacks a tx_max_pending, oops... */ +} + +static int b44_set_ringparam(struct net_device *dev, + struct ethtool_ringparam *ering) +{ + struct b44 *bp = netdev_priv(dev); + + if ((ering->rx_pending > B44_RX_RING_SIZE - 1) || + (ering->rx_mini_pending != 0) || + (ering->rx_jumbo_pending != 0) || + (ering->tx_pending > B44_TX_RING_SIZE - 1)) + return -EINVAL; + + spin_lock_irq(&bp->lock); + + bp->rx_pending = ering->rx_pending; + bp->tx_pending = ering->tx_pending; + + b44_halt(bp); + b44_init_rings(bp); + b44_init_hw(bp, B44_FULL_RESET); + netif_wake_queue(bp->dev); + spin_unlock_irq(&bp->lock); + + b44_enable_ints(bp); + + return 0; +} + +static void b44_get_pauseparam(struct net_device *dev, + struct ethtool_pauseparam *epause) +{ + struct b44 *bp = netdev_priv(dev); + + epause->autoneg = + (bp->flags & B44_FLAG_PAUSE_AUTO) != 0; + epause->rx_pause = + (bp->flags & B44_FLAG_RX_PAUSE) != 0; + epause->tx_pause = + (bp->flags & B44_FLAG_TX_PAUSE) != 0; +} + +static int b44_set_pauseparam(struct net_device *dev, + struct ethtool_pauseparam *epause) +{ + struct b44 *bp = netdev_priv(dev); + + spin_lock_irq(&bp->lock); + if (epause->autoneg) + bp->flags |= B44_FLAG_PAUSE_AUTO; + else + bp->flags &= ~B44_FLAG_PAUSE_AUTO; + if (epause->rx_pause) + bp->flags |= B44_FLAG_RX_PAUSE; + else + bp->flags &= ~B44_FLAG_RX_PAUSE; + if (epause->tx_pause) + bp->flags |= B44_FLAG_TX_PAUSE; + else + bp->flags &= ~B44_FLAG_TX_PAUSE; + if (bp->flags & B44_FLAG_PAUSE_AUTO) { + b44_halt(bp); + b44_init_rings(bp); + b44_init_hw(bp, B44_FULL_RESET); + } else { + __b44_set_flow_ctrl(bp, bp->flags); + } + spin_unlock_irq(&bp->lock); + + b44_enable_ints(bp); + + return 0; +} + +static void b44_get_strings(struct net_device *dev, u32 stringset, u8 *data) +{ + switch(stringset) { + case ETH_SS_STATS: + memcpy(data, *b44_gstrings, sizeof(b44_gstrings)); + break; + } +} + +static int b44_get_sset_count(struct net_device *dev, int sset) +{ + switch (sset) { + case ETH_SS_STATS: + return ARRAY_SIZE(b44_gstrings); + default: + return -EOPNOTSUPP; + } +} + +static void b44_get_ethtool_stats(struct net_device *dev, + struct ethtool_stats *stats, u64 *data) +{ + struct b44 *bp = netdev_priv(dev); + struct b44_hw_stats *hwstat = &bp->hw_stats; + u64 *data_src, *data_dst; + unsigned int start; + u32 i; + + spin_lock_irq(&bp->lock); + b44_stats_update(bp); + spin_unlock_irq(&bp->lock); + + do { + data_src = &hwstat->tx_good_octets; + data_dst = data; + start = u64_stats_fetch_begin_bh(&hwstat->syncp); + + for (i = 0; i < ARRAY_SIZE(b44_gstrings); i++) + *data_dst++ = *data_src++; + + } while (u64_stats_fetch_retry_bh(&hwstat->syncp, start)); +} + +static void b44_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol) +{ + struct b44 *bp = netdev_priv(dev); + + wol->supported = WAKE_MAGIC; + if (bp->flags & B44_FLAG_WOL_ENABLE) + wol->wolopts = WAKE_MAGIC; + else + wol->wolopts = 0; + memset(&wol->sopass, 0, sizeof(wol->sopass)); +} + +static int b44_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol) +{ + struct b44 *bp = netdev_priv(dev); + + spin_lock_irq(&bp->lock); + if (wol->wolopts & WAKE_MAGIC) + bp->flags |= B44_FLAG_WOL_ENABLE; + else + bp->flags &= ~B44_FLAG_WOL_ENABLE; + spin_unlock_irq(&bp->lock); + + return 0; +} + +static const struct ethtool_ops b44_ethtool_ops = { + .get_drvinfo = b44_get_drvinfo, + .get_settings = b44_get_settings, + .set_settings = b44_set_settings, + .nway_reset = b44_nway_reset, + .get_link = ethtool_op_get_link, + .get_wol = b44_get_wol, + .set_wol = b44_set_wol, + .get_ringparam = b44_get_ringparam, + .set_ringparam = b44_set_ringparam, + .get_pauseparam = b44_get_pauseparam, + .set_pauseparam = b44_set_pauseparam, + .get_msglevel = b44_get_msglevel, + .set_msglevel = b44_set_msglevel, + .get_strings = b44_get_strings, + .get_sset_count = b44_get_sset_count, + .get_ethtool_stats = b44_get_ethtool_stats, +}; + +static int b44_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct mii_ioctl_data *data = if_mii(ifr); + struct b44 *bp = netdev_priv(dev); + int err = -EINVAL; + + if (!netif_running(dev)) + goto out; + + spin_lock_irq(&bp->lock); + err = generic_mii_ioctl(&bp->mii_if, data, cmd, NULL); + spin_unlock_irq(&bp->lock); +out: + return err; +} + +static int b44_get_invariants(struct b44 *bp) +{ + struct ssb_device *sdev = bp->sdev; + int err = 0; + u8 *addr; + + bp->dma_offset = ssb_dma_translation(sdev); + + if (sdev->bus->bustype == SSB_BUSTYPE_SSB && + instance > 1) { + addr = sdev->bus->sprom.et1mac; + bp->phy_addr = sdev->bus->sprom.et1phyaddr; + } else { + addr = sdev->bus->sprom.et0mac; + bp->phy_addr = sdev->bus->sprom.et0phyaddr; + } + /* Some ROMs have buggy PHY addresses with the high + * bits set (sign extension?). Truncate them to a + * valid PHY address. */ + bp->phy_addr &= 0x1F; + + memcpy(bp->dev->dev_addr, addr, 6); + + if (!is_valid_ether_addr(&bp->dev->dev_addr[0])){ + pr_err("Invalid MAC address found in EEPROM\n"); + return -EINVAL; + } + + bp->imask = IMASK_DEF; + + /* XXX - really required? + bp->flags |= B44_FLAG_BUGGY_TXPTR; + */ + + if (bp->sdev->id.revision >= 7) + bp->flags |= B44_FLAG_B0_ANDLATER; + + return err; +} + +static const struct net_device_ops b44_netdev_ops = { + .ndo_open = b44_open, + .ndo_stop = b44_close, + .ndo_start_xmit = b44_start_xmit, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)) + .ndo_get_stats64 = b44_get_stats64, +#else + .ndo_get_stats = b44_get_stats, +#endif + .ndo_set_rx_mode = b44_set_rx_mode, + .ndo_set_mac_address = b44_set_mac_addr, + .ndo_validate_addr = eth_validate_addr, + .ndo_do_ioctl = b44_ioctl, + .ndo_tx_timeout = b44_tx_timeout, + .ndo_change_mtu = b44_change_mtu, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = b44_poll_controller, +#endif +}; + +static int b44_init_one(struct ssb_device *sdev, + const struct ssb_device_id *ent) +{ + struct net_device *dev; + struct b44 *bp; + int err; + + instance++; + + pr_info_once("%s version %s\n", DRV_DESCRIPTION, DRV_MODULE_VERSION); + + dev = alloc_etherdev(sizeof(*bp)); + if (!dev) { + err = -ENOMEM; + goto out; + } + + SET_NETDEV_DEV(dev, sdev->dev); + + /* No interesting netdevice features in this card... */ + dev->features |= 0; + + bp = netdev_priv(dev); + bp->sdev = sdev; + bp->dev = dev; + bp->force_copybreak = 0; + + bp->msg_enable = netif_msg_init(b44_debug, B44_DEF_MSG_ENABLE); + + spin_lock_init(&bp->lock); + + bp->rx_pending = B44_DEF_RX_RING_PENDING; + bp->tx_pending = B44_DEF_TX_RING_PENDING; + + netdev_attach_ops(dev, &b44_netdev_ops); + netif_napi_add(dev, &bp->napi, b44_poll, 64); + dev->watchdog_timeo = B44_TX_TIMEOUT; + dev->irq = sdev->irq; + SET_ETHTOOL_OPS(dev, &b44_ethtool_ops); + + err = ssb_bus_powerup(sdev->bus, 0); + if (err) { + dev_err(sdev->dev, + "Failed to powerup the bus\n"); + goto err_out_free_dev; + } + + if (dma_set_mask(sdev->dma_dev, DMA_BIT_MASK(30)) || + dma_set_coherent_mask(sdev->dma_dev, DMA_BIT_MASK(30))) { + dev_err(sdev->dev, + "Required 30BIT DMA mask unsupported by the system\n"); + goto err_out_powerdown; + } + + err = b44_get_invariants(bp); + if (err) { + dev_err(sdev->dev, + "Problem fetching invariants of chip, aborting\n"); + goto err_out_powerdown; + } + + bp->mii_if.dev = dev; + bp->mii_if.mdio_read = b44_mii_read; + bp->mii_if.mdio_write = b44_mii_write; + bp->mii_if.phy_id = bp->phy_addr; + bp->mii_if.phy_id_mask = 0x1f; + bp->mii_if.reg_num_mask = 0x1f; + + /* By default, advertise all speed/duplex settings. */ + bp->flags |= (B44_FLAG_ADV_10HALF | B44_FLAG_ADV_10FULL | + B44_FLAG_ADV_100HALF | B44_FLAG_ADV_100FULL); + + /* By default, auto-negotiate PAUSE. */ + bp->flags |= B44_FLAG_PAUSE_AUTO; + + err = register_netdev(dev); + if (err) { + dev_err(sdev->dev, "Cannot register net device, aborting\n"); + goto err_out_powerdown; + } + + netif_carrier_off(dev); + + ssb_set_drvdata(sdev, dev); + + /* Chip reset provides power to the b44 MAC & PCI cores, which + * is necessary for MAC register access. + */ + b44_chip_reset(bp, B44_CHIP_RESET_FULL); + + /* do a phy reset to test if there is an active phy */ + if (b44_phy_reset(bp) < 0) + bp->phy_addr = B44_PHY_ADDR_NO_PHY; + + netdev_info(dev, "%s %pM\n", DRV_DESCRIPTION, dev->dev_addr); + + return 0; + +err_out_powerdown: + ssb_bus_may_powerdown(sdev->bus); + +err_out_free_dev: + free_netdev(dev); + +out: + return err; +} + +static void b44_remove_one(struct ssb_device *sdev) +{ + struct net_device *dev = ssb_get_drvdata(sdev); + + unregister_netdev(dev); + ssb_device_disable(sdev, 0); + ssb_bus_may_powerdown(sdev->bus); + free_netdev(dev); + ssb_pcihost_set_power_state(sdev, PCI_D3hot); + ssb_set_drvdata(sdev, NULL); +} + +static int b44_suspend(struct ssb_device *sdev, pm_message_t state) +{ + struct net_device *dev = ssb_get_drvdata(sdev); + struct b44 *bp = netdev_priv(dev); + + if (!netif_running(dev)) + return 0; + + del_timer_sync(&bp->timer); + + spin_lock_irq(&bp->lock); + + b44_halt(bp); + netif_carrier_off(bp->dev); + netif_device_detach(bp->dev); + b44_free_rings(bp); + + spin_unlock_irq(&bp->lock); + + free_irq(dev->irq, dev); + if (bp->flags & B44_FLAG_WOL_ENABLE) { + b44_init_hw(bp, B44_PARTIAL_RESET); + b44_setup_wol(bp); + } + + ssb_pcihost_set_power_state(sdev, PCI_D3hot); + return 0; +} + +static int b44_resume(struct ssb_device *sdev) +{ + struct net_device *dev = ssb_get_drvdata(sdev); + struct b44 *bp = netdev_priv(dev); + int rc = 0; + + rc = ssb_bus_powerup(sdev->bus, 0); + if (rc) { + dev_err(sdev->dev, + "Failed to powerup the bus\n"); + return rc; + } + + if (!netif_running(dev)) + return 0; + + spin_lock_irq(&bp->lock); + b44_init_rings(bp); + b44_init_hw(bp, B44_FULL_RESET); + spin_unlock_irq(&bp->lock); + + /* + * As a shared interrupt, the handler can be called immediately. To be + * able to check the interrupt status the hardware must already be + * powered back on (b44_init_hw). + */ + rc = request_irq(dev->irq, b44_interrupt, IRQF_SHARED, dev->name, dev); + if (rc) { + netdev_err(dev, "request_irq failed\n"); + spin_lock_irq(&bp->lock); + b44_halt(bp); + b44_free_rings(bp); + spin_unlock_irq(&bp->lock); + return rc; + } + + netif_device_attach(bp->dev); + + b44_enable_ints(bp); + netif_wake_queue(dev); + + mod_timer(&bp->timer, jiffies + 1); + + return 0; +} + +static struct ssb_driver b44_ssb_driver = { + .name = DRV_MODULE_NAME, + .id_table = b44_ssb_tbl, + .probe = b44_init_one, + .remove = b44_remove_one, + .suspend = b44_suspend, + .resume = b44_resume, +}; + +static inline int __init b44_pci_init(void) +{ + int err = 0; +#ifdef CPTCFG_B44_PCI + err = ssb_pcihost_register(&b44_pci_driver); +#endif + return err; +} + +static inline void b44_pci_exit(void) +{ +#ifdef CPTCFG_B44_PCI + ssb_pcihost_unregister(&b44_pci_driver); +#endif +} + +static int __init b44_init(void) +{ + unsigned int dma_desc_align_size = dma_get_cache_alignment(); + int err; + + /* Setup paramaters for syncing RX/TX DMA descriptors */ + dma_desc_sync_size = max_t(unsigned int, dma_desc_align_size, sizeof(struct dma_desc)); + + err = b44_pci_init(); + if (err) + return err; + err = ssb_driver_register(&b44_ssb_driver); + if (err) + b44_pci_exit(); + return err; +} + +static void __exit b44_cleanup(void) +{ + ssb_driver_unregister(&b44_ssb_driver); + b44_pci_exit(); +} + +module_init(b44_init); +module_exit(b44_cleanup); + diff --git a/drivers/net/ethernet/broadcom/b44.h b/drivers/net/ethernet/broadcom/b44.h new file mode 100644 index 0000000..8993d72 --- /dev/null +++ b/drivers/net/ethernet/broadcom/b44.h @@ -0,0 +1,402 @@ +#ifndef _B44_H +#define _B44_H + +/* Register layout. (These correspond to struct _bcmenettregs in bcm4400.) */ +#define B44_DEVCTRL 0x0000UL /* Device Control */ +#define DEVCTRL_MPM 0x00000040 /* Magic Packet PME Enable (B0 only) */ +#define DEVCTRL_PFE 0x00000080 /* Pattern Filtering Enable */ +#define DEVCTRL_IPP 0x00000400 /* Internal EPHY Present */ +#define DEVCTRL_EPR 0x00008000 /* EPHY Reset */ +#define DEVCTRL_PME 0x00001000 /* PHY Mode Enable */ +#define DEVCTRL_PMCE 0x00002000 /* PHY Mode Clocks Enable */ +#define DEVCTRL_PADDR 0x0007c000 /* PHY Address */ +#define DEVCTRL_PADDR_SHIFT 18 +#define B44_BIST_STAT 0x000CUL /* Built-In Self-Test Status */ +#define B44_WKUP_LEN 0x0010UL /* Wakeup Length */ +#define WKUP_LEN_P0_MASK 0x0000007f /* Pattern 0 */ +#define WKUP_LEN_D0 0x00000080 +#define WKUP_LEN_P1_MASK 0x00007f00 /* Pattern 1 */ +#define WKUP_LEN_P1_SHIFT 8 +#define WKUP_LEN_D1 0x00008000 +#define WKUP_LEN_P2_MASK 0x007f0000 /* Pattern 2 */ +#define WKUP_LEN_P2_SHIFT 16 +#define WKUP_LEN_D2 0x00000000 +#define WKUP_LEN_P3_MASK 0x7f000000 /* Pattern 3 */ +#define WKUP_LEN_P3_SHIFT 24 +#define WKUP_LEN_D3 0x80000000 +#define WKUP_LEN_DISABLE 0x80808080 +#define WKUP_LEN_ENABLE_TWO 0x80800000 +#define WKUP_LEN_ENABLE_THREE 0x80000000 +#define B44_ISTAT 0x0020UL /* Interrupt Status */ +#define ISTAT_LS 0x00000020 /* Link Change (B0 only) */ +#define ISTAT_PME 0x00000040 /* Power Management Event */ +#define ISTAT_TO 0x00000080 /* General Purpose Timeout */ +#define ISTAT_DSCE 0x00000400 /* Descriptor Error */ +#define ISTAT_DATAE 0x00000800 /* Data Error */ +#define ISTAT_DPE 0x00001000 /* Descr. Protocol Error */ +#define ISTAT_RDU 0x00002000 /* Receive Descr. Underflow */ +#define ISTAT_RFO 0x00004000 /* Receive FIFO Overflow */ +#define ISTAT_TFU 0x00008000 /* Transmit FIFO Underflow */ +#define ISTAT_RX 0x00010000 /* RX Interrupt */ +#define ISTAT_TX 0x01000000 /* TX Interrupt */ +#define ISTAT_EMAC 0x04000000 /* EMAC Interrupt */ +#define ISTAT_MII_WRITE 0x08000000 /* MII Write Interrupt */ +#define ISTAT_MII_READ 0x10000000 /* MII Read Interrupt */ +#define ISTAT_ERRORS (ISTAT_DSCE|ISTAT_DATAE|ISTAT_DPE|ISTAT_RDU|ISTAT_RFO|ISTAT_TFU) +#define B44_IMASK 0x0024UL /* Interrupt Mask */ +#define IMASK_DEF (ISTAT_ERRORS | ISTAT_TO | ISTAT_RX | ISTAT_TX) +#define B44_GPTIMER 0x0028UL /* General Purpose Timer */ +#define B44_ADDR_LO 0x0088UL /* ENET Address Lo (B0 only) */ +#define B44_ADDR_HI 0x008CUL /* ENET Address Hi (B0 only) */ +#define B44_FILT_ADDR 0x0090UL /* ENET Filter Address */ +#define B44_FILT_DATA 0x0094UL /* ENET Filter Data */ +#define B44_TXBURST 0x00A0UL /* TX Max Burst Length */ +#define B44_RXBURST 0x00A4UL /* RX Max Burst Length */ +#define B44_MAC_CTRL 0x00A8UL /* MAC Control */ +#define MAC_CTRL_CRC32_ENAB 0x00000001 /* CRC32 Generation Enable */ +#define MAC_CTRL_PHY_PDOWN 0x00000004 /* Onchip EPHY Powerdown */ +#define MAC_CTRL_PHY_EDET 0x00000008 /* Onchip EPHY Energy Detected */ +#define MAC_CTRL_PHY_LEDCTRL 0x000000e0 /* Onchip EPHY LED Control */ +#define MAC_CTRL_PHY_LEDCTRL_SHIFT 5 +#define B44_MAC_FLOW 0x00ACUL /* MAC Flow Control */ +#define MAC_FLOW_RX_HI_WATER 0x000000ff /* Receive FIFO HI Water Mark */ +#define MAC_FLOW_PAUSE_ENAB 0x00008000 /* Enable Pause Frame Generation */ +#define B44_RCV_LAZY 0x0100UL /* Lazy Interrupt Control */ +#define RCV_LAZY_TO_MASK 0x00ffffff /* Timeout */ +#define RCV_LAZY_FC_MASK 0xff000000 /* Frame Count */ +#define RCV_LAZY_FC_SHIFT 24 +#define B44_DMATX_CTRL 0x0200UL /* DMA TX Control */ +#define DMATX_CTRL_ENABLE 0x00000001 /* Enable */ +#define DMATX_CTRL_SUSPEND 0x00000002 /* Suepend Request */ +#define DMATX_CTRL_LPBACK 0x00000004 /* Loopback Enable */ +#define DMATX_CTRL_FAIRPRIOR 0x00000008 /* Fair Priority */ +#define DMATX_CTRL_FLUSH 0x00000010 /* Flush Request */ +#define B44_DMATX_ADDR 0x0204UL /* DMA TX Descriptor Ring Address */ +#define B44_DMATX_PTR 0x0208UL /* DMA TX Last Posted Descriptor */ +#define B44_DMATX_STAT 0x020CUL /* DMA TX Current Active Desc. + Status */ +#define DMATX_STAT_CDMASK 0x00000fff /* Current Descriptor Mask */ +#define DMATX_STAT_SMASK 0x0000f000 /* State Mask */ +#define DMATX_STAT_SDISABLED 0x00000000 /* State Disabled */ +#define DMATX_STAT_SACTIVE 0x00001000 /* State Active */ +#define DMATX_STAT_SIDLE 0x00002000 /* State Idle Wait */ +#define DMATX_STAT_SSTOPPED 0x00003000 /* State Stopped */ +#define DMATX_STAT_SSUSP 0x00004000 /* State Suspend Pending */ +#define DMATX_STAT_EMASK 0x000f0000 /* Error Mask */ +#define DMATX_STAT_ENONE 0x00000000 /* Error None */ +#define DMATX_STAT_EDPE 0x00010000 /* Error Desc. Protocol Error */ +#define DMATX_STAT_EDFU 0x00020000 /* Error Data FIFO Underrun */ +#define DMATX_STAT_EBEBR 0x00030000 /* Error Bus Error on Buffer Read */ +#define DMATX_STAT_EBEDA 0x00040000 /* Error Bus Error on Desc. Access */ +#define DMATX_STAT_FLUSHED 0x00100000 /* Flushed */ +#define B44_DMARX_CTRL 0x0210UL /* DMA RX Control */ +#define DMARX_CTRL_ENABLE 0x00000001 /* Enable */ +#define DMARX_CTRL_ROMASK 0x000000fe /* Receive Offset Mask */ +#define DMARX_CTRL_ROSHIFT 1 /* Receive Offset Shift */ +#define B44_DMARX_ADDR 0x0214UL /* DMA RX Descriptor Ring Address */ +#define B44_DMARX_PTR 0x0218UL /* DMA RX Last Posted Descriptor */ +#define B44_DMARX_STAT 0x021CUL /* DMA RX Current Active Desc. + Status */ +#define DMARX_STAT_CDMASK 0x00000fff /* Current Descriptor Mask */ +#define DMARX_STAT_SMASK 0x0000f000 /* State Mask */ +#define DMARX_STAT_SDISABLED 0x00000000 /* State Disabled */ +#define DMARX_STAT_SACTIVE 0x00001000 /* State Active */ +#define DMARX_STAT_SIDLE 0x00002000 /* State Idle Wait */ +#define DMARX_STAT_SSTOPPED 0x00003000 /* State Stopped */ +#define DMARX_STAT_EMASK 0x000f0000 /* Error Mask */ +#define DMARX_STAT_ENONE 0x00000000 /* Error None */ +#define DMARX_STAT_EDPE 0x00010000 /* Error Desc. Protocol Error */ +#define DMARX_STAT_EDFO 0x00020000 /* Error Data FIFO Overflow */ +#define DMARX_STAT_EBEBW 0x00030000 /* Error Bus Error on Buffer Write */ +#define DMARX_STAT_EBEDA 0x00040000 /* Error Bus Error on Desc. Access */ +#define B44_DMAFIFO_AD 0x0220UL /* DMA FIFO Diag Address */ +#define DMAFIFO_AD_OMASK 0x0000ffff /* Offset Mask */ +#define DMAFIFO_AD_SMASK 0x000f0000 /* Select Mask */ +#define DMAFIFO_AD_SXDD 0x00000000 /* Select Transmit DMA Data */ +#define DMAFIFO_AD_SXDP 0x00010000 /* Select Transmit DMA Pointers */ +#define DMAFIFO_AD_SRDD 0x00040000 /* Select Receive DMA Data */ +#define DMAFIFO_AD_SRDP 0x00050000 /* Select Receive DMA Pointers */ +#define DMAFIFO_AD_SXFD 0x00080000 /* Select Transmit FIFO Data */ +#define DMAFIFO_AD_SXFP 0x00090000 /* Select Transmit FIFO Pointers */ +#define DMAFIFO_AD_SRFD 0x000c0000 /* Select Receive FIFO Data */ +#define DMAFIFO_AD_SRFP 0x000c0000 /* Select Receive FIFO Pointers */ +#define B44_DMAFIFO_LO 0x0224UL /* DMA FIFO Diag Low Data */ +#define B44_DMAFIFO_HI 0x0228UL /* DMA FIFO Diag High Data */ +#define B44_RXCONFIG 0x0400UL /* EMAC RX Config */ +#define RXCONFIG_DBCAST 0x00000001 /* Disable Broadcast */ +#define RXCONFIG_ALLMULTI 0x00000002 /* Accept All Multicast */ +#define RXCONFIG_NORX_WHILE_TX 0x00000004 /* Receive Disable While Transmitting */ +#define RXCONFIG_PROMISC 0x00000008 /* Promiscuous Enable */ +#define RXCONFIG_LPBACK 0x00000010 /* Loopback Enable */ +#define RXCONFIG_FLOW 0x00000020 /* Flow Control Enable */ +#define RXCONFIG_FLOW_ACCEPT 0x00000040 /* Accept Unicast Flow Control Frame */ +#define RXCONFIG_RFILT 0x00000080 /* Reject Filter */ +#define RXCONFIG_CAM_ABSENT 0x00000100 /* CAM Absent */ +#define B44_RXMAXLEN 0x0404UL /* EMAC RX Max Packet Length */ +#define B44_TXMAXLEN 0x0408UL /* EMAC TX Max Packet Length */ +#define B44_MDIO_CTRL 0x0410UL /* EMAC MDIO Control */ +#define MDIO_CTRL_MAXF_MASK 0x0000007f /* MDC Frequency */ +#define MDIO_CTRL_PREAMBLE 0x00000080 /* MII Preamble Enable */ +#define B44_MDIO_DATA 0x0414UL /* EMAC MDIO Data */ +#define MDIO_DATA_DATA 0x0000ffff /* R/W Data */ +#define MDIO_DATA_TA_MASK 0x00030000 /* Turnaround Value */ +#define MDIO_DATA_TA_SHIFT 16 +#define MDIO_TA_VALID 2 +#define MDIO_DATA_RA_MASK 0x007c0000 /* Register Address */ +#define MDIO_DATA_RA_SHIFT 18 +#define MDIO_DATA_PMD_MASK 0x0f800000 /* Physical Media Device */ +#define MDIO_DATA_PMD_SHIFT 23 +#define MDIO_DATA_OP_MASK 0x30000000 /* Opcode */ +#define MDIO_DATA_OP_SHIFT 28 +#define MDIO_OP_WRITE 1 +#define MDIO_OP_READ 2 +#define MDIO_DATA_SB_MASK 0xc0000000 /* Start Bits */ +#define MDIO_DATA_SB_SHIFT 30 +#define MDIO_DATA_SB_START 0x40000000 /* Start Of Frame */ +#define B44_EMAC_IMASK 0x0418UL /* EMAC Interrupt Mask */ +#define B44_EMAC_ISTAT 0x041CUL /* EMAC Interrupt Status */ +#define EMAC_INT_MII 0x00000001 /* MII MDIO Interrupt */ +#define EMAC_INT_MIB 0x00000002 /* MIB Interrupt */ +#define EMAC_INT_FLOW 0x00000003 /* Flow Control Interrupt */ +#define B44_CAM_DATA_LO 0x0420UL /* EMAC CAM Data Low */ +#define B44_CAM_DATA_HI 0x0424UL /* EMAC CAM Data High */ +#define CAM_DATA_HI_VALID 0x00010000 /* Valid Bit */ +#define B44_CAM_CTRL 0x0428UL /* EMAC CAM Control */ +#define CAM_CTRL_ENABLE 0x00000001 /* CAM Enable */ +#define CAM_CTRL_MSEL 0x00000002 /* Mask Select */ +#define CAM_CTRL_READ 0x00000004 /* Read */ +#define CAM_CTRL_WRITE 0x00000008 /* Read */ +#define CAM_CTRL_INDEX_MASK 0x003f0000 /* Index Mask */ +#define CAM_CTRL_INDEX_SHIFT 16 +#define CAM_CTRL_BUSY 0x80000000 /* CAM Busy */ +#define B44_ENET_CTRL 0x042CUL /* EMAC ENET Control */ +#define ENET_CTRL_ENABLE 0x00000001 /* EMAC Enable */ +#define ENET_CTRL_DISABLE 0x00000002 /* EMAC Disable */ +#define ENET_CTRL_SRST 0x00000004 /* EMAC Soft Reset */ +#define ENET_CTRL_EPSEL 0x00000008 /* External PHY Select */ +#define B44_TX_CTRL 0x0430UL /* EMAC TX Control */ +#define TX_CTRL_DUPLEX 0x00000001 /* Full Duplex */ +#define TX_CTRL_FMODE 0x00000002 /* Flow Mode */ +#define TX_CTRL_SBENAB 0x00000004 /* Single Backoff Enable */ +#define TX_CTRL_SMALL_SLOT 0x00000008 /* Small Slottime */ +#define B44_TX_WMARK 0x0434UL /* EMAC TX Watermark */ +#define B44_MIB_CTRL 0x0438UL /* EMAC MIB Control */ +#define MIB_CTRL_CLR_ON_READ 0x00000001 /* Autoclear on Read */ +#define B44_TX_GOOD_O 0x0500UL /* MIB TX Good Octets */ +#define B44_TX_GOOD_P 0x0504UL /* MIB TX Good Packets */ +#define B44_TX_O 0x0508UL /* MIB TX Octets */ +#define B44_TX_P 0x050CUL /* MIB TX Packets */ +#define B44_TX_BCAST 0x0510UL /* MIB TX Broadcast Packets */ +#define B44_TX_MCAST 0x0514UL /* MIB TX Multicast Packets */ +#define B44_TX_64 0x0518UL /* MIB TX <= 64 byte Packets */ +#define B44_TX_65_127 0x051CUL /* MIB TX 65 to 127 byte Packets */ +#define B44_TX_128_255 0x0520UL /* MIB TX 128 to 255 byte Packets */ +#define B44_TX_256_511 0x0524UL /* MIB TX 256 to 511 byte Packets */ +#define B44_TX_512_1023 0x0528UL /* MIB TX 512 to 1023 byte Packets */ +#define B44_TX_1024_MAX 0x052CUL /* MIB TX 1024 to max byte Packets */ +#define B44_TX_JABBER 0x0530UL /* MIB TX Jabber Packets */ +#define B44_TX_OSIZE 0x0534UL /* MIB TX Oversize Packets */ +#define B44_TX_FRAG 0x0538UL /* MIB TX Fragment Packets */ +#define B44_TX_URUNS 0x053CUL /* MIB TX Underruns */ +#define B44_TX_TCOLS 0x0540UL /* MIB TX Total Collisions */ +#define B44_TX_SCOLS 0x0544UL /* MIB TX Single Collisions */ +#define B44_TX_MCOLS 0x0548UL /* MIB TX Multiple Collisions */ +#define B44_TX_ECOLS 0x054CUL /* MIB TX Excessive Collisions */ +#define B44_TX_LCOLS 0x0550UL /* MIB TX Late Collisions */ +#define B44_TX_DEFERED 0x0554UL /* MIB TX Defered Packets */ +#define B44_TX_CLOST 0x0558UL /* MIB TX Carrier Lost */ +#define B44_TX_PAUSE 0x055CUL /* MIB TX Pause Packets */ +#define B44_RX_GOOD_O 0x0580UL /* MIB RX Good Octets */ +#define B44_RX_GOOD_P 0x0584UL /* MIB RX Good Packets */ +#define B44_RX_O 0x0588UL /* MIB RX Octets */ +#define B44_RX_P 0x058CUL /* MIB RX Packets */ +#define B44_RX_BCAST 0x0590UL /* MIB RX Broadcast Packets */ +#define B44_RX_MCAST 0x0594UL /* MIB RX Multicast Packets */ +#define B44_RX_64 0x0598UL /* MIB RX <= 64 byte Packets */ +#define B44_RX_65_127 0x059CUL /* MIB RX 65 to 127 byte Packets */ +#define B44_RX_128_255 0x05A0UL /* MIB RX 128 to 255 byte Packets */ +#define B44_RX_256_511 0x05A4UL /* MIB RX 256 to 511 byte Packets */ +#define B44_RX_512_1023 0x05A8UL /* MIB RX 512 to 1023 byte Packets */ +#define B44_RX_1024_MAX 0x05ACUL /* MIB RX 1024 to max byte Packets */ +#define B44_RX_JABBER 0x05B0UL /* MIB RX Jabber Packets */ +#define B44_RX_OSIZE 0x05B4UL /* MIB RX Oversize Packets */ +#define B44_RX_FRAG 0x05B8UL /* MIB RX Fragment Packets */ +#define B44_RX_MISS 0x05BCUL /* MIB RX Missed Packets */ +#define B44_RX_CRCA 0x05C0UL /* MIB RX CRC Align Errors */ +#define B44_RX_USIZE 0x05C4UL /* MIB RX Undersize Packets */ +#define B44_RX_CRC 0x05C8UL /* MIB RX CRC Errors */ +#define B44_RX_ALIGN 0x05CCUL /* MIB RX Align Errors */ +#define B44_RX_SYM 0x05D0UL /* MIB RX Symbol Errors */ +#define B44_RX_PAUSE 0x05D4UL /* MIB RX Pause Packets */ +#define B44_RX_NPAUSE 0x05D8UL /* MIB RX Non-Pause Packets */ + +/* 4400 PHY registers */ +#define B44_MII_AUXCTRL 24 /* Auxiliary Control */ +#define MII_AUXCTRL_DUPLEX 0x0001 /* Full Duplex */ +#define MII_AUXCTRL_SPEED 0x0002 /* 1=100Mbps, 0=10Mbps */ +#define MII_AUXCTRL_FORCED 0x0004 /* Forced 10/100 */ +#define B44_MII_ALEDCTRL 26 /* Activity LED */ +#define MII_ALEDCTRL_ALLMSK 0x7fff +#define B44_MII_TLEDCTRL 27 /* Traffic Meter LED */ +#define MII_TLEDCTRL_ENABLE 0x0040 + +struct dma_desc { + __le32 ctrl; + __le32 addr; +}; + +/* There are only 12 bits in the DMA engine for descriptor offsetting + * so the table must be aligned on a boundary of this. + */ +#define DMA_TABLE_BYTES 4096 + +#define DESC_CTRL_LEN 0x00001fff +#define DESC_CTRL_CMASK 0x0ff00000 /* Core specific bits */ +#define DESC_CTRL_EOT 0x10000000 /* End of Table */ +#define DESC_CTRL_IOC 0x20000000 /* Interrupt On Completion */ +#define DESC_CTRL_EOF 0x40000000 /* End of Frame */ +#define DESC_CTRL_SOF 0x80000000 /* Start of Frame */ + +#define RX_COPY_THRESHOLD 256 + +struct rx_header { + __le16 len; + __le16 flags; + __le16 pad[12]; +}; +#define RX_HEADER_LEN 28 + +#define RX_FLAG_OFIFO 0x00000001 /* FIFO Overflow */ +#define RX_FLAG_CRCERR 0x00000002 /* CRC Error */ +#define RX_FLAG_SERR 0x00000004 /* Receive Symbol Error */ +#define RX_FLAG_ODD 0x00000008 /* Frame has odd number of nibbles */ +#define RX_FLAG_LARGE 0x00000010 /* Frame is > RX MAX Length */ +#define RX_FLAG_MCAST 0x00000020 /* Dest is Multicast Address */ +#define RX_FLAG_BCAST 0x00000040 /* Dest is Broadcast Address */ +#define RX_FLAG_MISS 0x00000080 /* Received due to promisc mode */ +#define RX_FLAG_LAST 0x00000800 /* Last buffer in frame */ +#define RX_FLAG_ERRORS (RX_FLAG_ODD | RX_FLAG_SERR | RX_FLAG_CRCERR | RX_FLAG_OFIFO) + +struct ring_info { + struct sk_buff *skb; + dma_addr_t mapping; +}; + +#define B44_MCAST_TABLE_SIZE 32 +#define B44_PHY_ADDR_NO_PHY 30 +#define B44_MDC_RATIO 5000000 + +#define B44_STAT_REG_DECLARE \ + _B44(tx_good_octets) \ + _B44(tx_good_pkts) \ + _B44(tx_octets) \ + _B44(tx_pkts) \ + _B44(tx_broadcast_pkts) \ + _B44(tx_multicast_pkts) \ + _B44(tx_len_64) \ + _B44(tx_len_65_to_127) \ + _B44(tx_len_128_to_255) \ + _B44(tx_len_256_to_511) \ + _B44(tx_len_512_to_1023) \ + _B44(tx_len_1024_to_max) \ + _B44(tx_jabber_pkts) \ + _B44(tx_oversize_pkts) \ + _B44(tx_fragment_pkts) \ + _B44(tx_underruns) \ + _B44(tx_total_cols) \ + _B44(tx_single_cols) \ + _B44(tx_multiple_cols) \ + _B44(tx_excessive_cols) \ + _B44(tx_late_cols) \ + _B44(tx_defered) \ + _B44(tx_carrier_lost) \ + _B44(tx_pause_pkts) \ + _B44(rx_good_octets) \ + _B44(rx_good_pkts) \ + _B44(rx_octets) \ + _B44(rx_pkts) \ + _B44(rx_broadcast_pkts) \ + _B44(rx_multicast_pkts) \ + _B44(rx_len_64) \ + _B44(rx_len_65_to_127) \ + _B44(rx_len_128_to_255) \ + _B44(rx_len_256_to_511) \ + _B44(rx_len_512_to_1023) \ + _B44(rx_len_1024_to_max) \ + _B44(rx_jabber_pkts) \ + _B44(rx_oversize_pkts) \ + _B44(rx_fragment_pkts) \ + _B44(rx_missed_pkts) \ + _B44(rx_crc_align_errs) \ + _B44(rx_undersize) \ + _B44(rx_crc_errs) \ + _B44(rx_align_errs) \ + _B44(rx_symbol_errs) \ + _B44(rx_pause_pkts) \ + _B44(rx_nonpause_pkts) + +/* SW copy of device statistics, kept up to date by periodic timer + * which probes HW values. Check b44_stats_update if you mess with + * the layout + */ +struct b44_hw_stats { +#define _B44(x) u64 x; +B44_STAT_REG_DECLARE +#undef _B44 + struct u64_stats_sync syncp; +}; + +struct ssb_device; + +struct b44 { + spinlock_t lock; + + u32 imask, istat; + + struct dma_desc *rx_ring, *tx_ring; + + u32 tx_prod, tx_cons; + u32 rx_prod, rx_cons; + + struct ring_info *rx_buffers; + struct ring_info *tx_buffers; + + struct napi_struct napi; + + u32 dma_offset; + u32 flags; +#define B44_FLAG_B0_ANDLATER 0x00000001 +#define B44_FLAG_BUGGY_TXPTR 0x00000002 +#define B44_FLAG_REORDER_BUG 0x00000004 +#define B44_FLAG_PAUSE_AUTO 0x00008000 +#define B44_FLAG_FULL_DUPLEX 0x00010000 +#define B44_FLAG_100_BASE_T 0x00020000 +#define B44_FLAG_TX_PAUSE 0x00040000 +#define B44_FLAG_RX_PAUSE 0x00080000 +#define B44_FLAG_FORCE_LINK 0x00100000 +#define B44_FLAG_ADV_10HALF 0x01000000 +#define B44_FLAG_ADV_10FULL 0x02000000 +#define B44_FLAG_ADV_100HALF 0x04000000 +#define B44_FLAG_ADV_100FULL 0x08000000 +#define B44_FLAG_INTERNAL_PHY 0x10000000 +#define B44_FLAG_RX_RING_HACK 0x20000000 +#define B44_FLAG_TX_RING_HACK 0x40000000 +#define B44_FLAG_WOL_ENABLE 0x80000000 + + u32 msg_enable; + + struct timer_list timer; + + struct b44_hw_stats hw_stats; + + struct ssb_device *sdev; + struct net_device *dev; + + dma_addr_t rx_ring_dma, tx_ring_dma; + + u32 rx_pending; + u32 tx_pending; + u8 phy_addr; + u8 force_copybreak; + struct mii_if_info mii_if; +}; + +#endif /* _B44_H */ diff --git a/drivers/net/mdio.c b/drivers/net/mdio.c new file mode 100644 index 0000000..8403316 --- /dev/null +++ b/drivers/net/mdio.c @@ -0,0 +1,451 @@ +/* + * mdio.c: Generic support for MDIO-compatible transceivers + * Copyright 2006-2009 Solarflare Communications Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation, incorporated herein by reference. + */ + +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("Generic support for MDIO-compatible transceivers"); +MODULE_AUTHOR("Copyright 2006-2009 Solarflare Communications Inc."); +MODULE_LICENSE("GPL"); + +/** + * mdio45_probe - probe for an MDIO (clause 45) device + * @mdio: MDIO interface + * @prtad: Expected PHY address + * + * This sets @prtad and @mmds in the MDIO interface if successful. + * Returns 0 on success, negative on error. + */ +int mdio45_probe(struct mdio_if_info *mdio, int prtad) +{ + int mmd, stat2, devs1, devs2; + + /* Assume PHY must have at least one of PMA/PMD, WIS, PCS, PHY + * XS or DTE XS; give up if none is present. */ + for (mmd = 1; mmd <= 5; mmd++) { + /* Is this MMD present? */ + stat2 = mdio->mdio_read(mdio->dev, prtad, mmd, MDIO_STAT2); + if (stat2 < 0 || + (stat2 & MDIO_STAT2_DEVPRST) != MDIO_STAT2_DEVPRST_VAL) + continue; + + /* It should tell us about all the other MMDs */ + devs1 = mdio->mdio_read(mdio->dev, prtad, mmd, MDIO_DEVS1); + devs2 = mdio->mdio_read(mdio->dev, prtad, mmd, MDIO_DEVS2); + if (devs1 < 0 || devs2 < 0) + continue; + + mdio->prtad = prtad; + mdio->mmds = devs1 | (devs2 << 16); + return 0; + } + + return -ENODEV; +} +EXPORT_SYMBOL(mdio45_probe); + +/** + * mdio_set_flag - set or clear flag in an MDIO register + * @mdio: MDIO interface + * @prtad: PHY address + * @devad: MMD address + * @addr: Register address + * @mask: Mask for flag (single bit set) + * @sense: New value of flag + * + * This debounces changes: it does not write the register if the flag + * already has the proper value. Returns 0 on success, negative on error. + */ +int mdio_set_flag(const struct mdio_if_info *mdio, + int prtad, int devad, u16 addr, int mask, + bool sense) +{ + int old_val = mdio->mdio_read(mdio->dev, prtad, devad, addr); + int new_val; + + if (old_val < 0) + return old_val; + if (sense) + new_val = old_val | mask; + else + new_val = old_val & ~mask; + if (old_val == new_val) + return 0; + return mdio->mdio_write(mdio->dev, prtad, devad, addr, new_val); +} +EXPORT_SYMBOL(mdio_set_flag); + +/** + * mdio_link_ok - is link status up/OK + * @mdio: MDIO interface + * @mmd_mask: Mask for MMDs to check + * + * Returns 1 if the PHY reports link status up/OK, 0 otherwise. + * @mmd_mask is normally @mdio->mmds, but if loopback is enabled + * the MMDs being bypassed should be excluded from the mask. + */ +int mdio45_links_ok(const struct mdio_if_info *mdio, u32 mmd_mask) +{ + int devad, reg; + + if (!mmd_mask) { + /* Use absence of XGMII faults in lieu of link state */ + reg = mdio->mdio_read(mdio->dev, mdio->prtad, + MDIO_MMD_PHYXS, MDIO_STAT2); + return reg >= 0 && !(reg & MDIO_STAT2_RXFAULT); + } + + for (devad = 0; mmd_mask; devad++) { + if (mmd_mask & (1 << devad)) { + mmd_mask &= ~(1 << devad); + + /* Reset the latched status and fault flags */ + mdio->mdio_read(mdio->dev, mdio->prtad, + devad, MDIO_STAT1); + if (devad == MDIO_MMD_PMAPMD || devad == MDIO_MMD_PCS || + devad == MDIO_MMD_PHYXS || devad == MDIO_MMD_DTEXS) + mdio->mdio_read(mdio->dev, mdio->prtad, + devad, MDIO_STAT2); + + /* Check the current status and fault flags */ + reg = mdio->mdio_read(mdio->dev, mdio->prtad, + devad, MDIO_STAT1); + if (reg < 0 || + (reg & (MDIO_STAT1_FAULT | MDIO_STAT1_LSTATUS)) != + MDIO_STAT1_LSTATUS) + return false; + } + } + + return true; +} +EXPORT_SYMBOL(mdio45_links_ok); + +/** + * mdio45_nway_restart - restart auto-negotiation for this interface + * @mdio: MDIO interface + * + * Returns 0 on success, negative on error. + */ +int mdio45_nway_restart(const struct mdio_if_info *mdio) +{ + if (!(mdio->mmds & MDIO_DEVS_AN)) + return -EOPNOTSUPP; + + mdio_set_flag(mdio, mdio->prtad, MDIO_MMD_AN, MDIO_CTRL1, + MDIO_AN_CTRL1_RESTART, true); + return 0; +} +EXPORT_SYMBOL(mdio45_nway_restart); + +static u32 mdio45_get_an(const struct mdio_if_info *mdio, u16 addr) +{ + u32 result = 0; + int reg; + + reg = mdio->mdio_read(mdio->dev, mdio->prtad, MDIO_MMD_AN, addr); + if (reg & ADVERTISE_10HALF) + result |= ADVERTISED_10baseT_Half; + if (reg & ADVERTISE_10FULL) + result |= ADVERTISED_10baseT_Full; + if (reg & ADVERTISE_100HALF) + result |= ADVERTISED_100baseT_Half; + if (reg & ADVERTISE_100FULL) + result |= ADVERTISED_100baseT_Full; + if (reg & ADVERTISE_PAUSE_CAP) + result |= ADVERTISED_Pause; + if (reg & ADVERTISE_PAUSE_ASYM) + result |= ADVERTISED_Asym_Pause; + return result; +} + +/** + * mdio45_ethtool_gset_npage - get settings for ETHTOOL_GSET + * @mdio: MDIO interface + * @ecmd: Ethtool request structure + * @npage_adv: Modes currently advertised on next pages + * @npage_lpa: Modes advertised by link partner on next pages + * + * The @ecmd parameter is expected to have been cleared before calling + * mdio45_ethtool_gset_npage(). + * + * Since the CSRs for auto-negotiation using next pages are not fully + * standardised, this function does not attempt to decode them. The + * caller must pass them in. + */ +void mdio45_ethtool_gset_npage(const struct mdio_if_info *mdio, + struct ethtool_cmd *ecmd, + u32 npage_adv, u32 npage_lpa) +{ + int reg; + u32 speed; + + BUILD_BUG_ON(MDIO_SUPPORTS_C22 != ETH_MDIO_SUPPORTS_C22); + BUILD_BUG_ON(MDIO_SUPPORTS_C45 != ETH_MDIO_SUPPORTS_C45); + + ecmd->transceiver = XCVR_INTERNAL; + ecmd->phy_address = mdio->prtad; + ecmd->mdio_support = + mdio->mode_support & (MDIO_SUPPORTS_C45 | MDIO_SUPPORTS_C22); + + reg = mdio->mdio_read(mdio->dev, mdio->prtad, MDIO_MMD_PMAPMD, + MDIO_CTRL2); + switch (reg & MDIO_PMA_CTRL2_TYPE) { + case MDIO_PMA_CTRL2_10GBT: + case MDIO_PMA_CTRL2_1000BT: + case MDIO_PMA_CTRL2_100BTX: + case MDIO_PMA_CTRL2_10BT: + ecmd->port = PORT_TP; + ecmd->supported = SUPPORTED_TP; + reg = mdio->mdio_read(mdio->dev, mdio->prtad, MDIO_MMD_PMAPMD, + MDIO_SPEED); + if (reg & MDIO_SPEED_10G) + ecmd->supported |= SUPPORTED_10000baseT_Full; + if (reg & MDIO_PMA_SPEED_1000) + ecmd->supported |= (SUPPORTED_1000baseT_Full | + SUPPORTED_1000baseT_Half); + if (reg & MDIO_PMA_SPEED_100) + ecmd->supported |= (SUPPORTED_100baseT_Full | + SUPPORTED_100baseT_Half); + if (reg & MDIO_PMA_SPEED_10) + ecmd->supported |= (SUPPORTED_10baseT_Full | + SUPPORTED_10baseT_Half); + ecmd->advertising = ADVERTISED_TP; + break; + + case MDIO_PMA_CTRL2_10GBCX4: + ecmd->port = PORT_OTHER; + ecmd->supported = 0; + ecmd->advertising = 0; + break; + + case MDIO_PMA_CTRL2_10GBKX4: + case MDIO_PMA_CTRL2_10GBKR: + case MDIO_PMA_CTRL2_1000BKX: + ecmd->port = PORT_OTHER; + ecmd->supported = SUPPORTED_Backplane; + reg = mdio->mdio_read(mdio->dev, mdio->prtad, MDIO_MMD_PMAPMD, + MDIO_PMA_EXTABLE); + if (reg & MDIO_PMA_EXTABLE_10GBKX4) + ecmd->supported |= SUPPORTED_10000baseKX4_Full; + if (reg & MDIO_PMA_EXTABLE_10GBKR) + ecmd->supported |= SUPPORTED_10000baseKR_Full; + if (reg & MDIO_PMA_EXTABLE_1000BKX) + ecmd->supported |= SUPPORTED_1000baseKX_Full; + reg = mdio->mdio_read(mdio->dev, mdio->prtad, MDIO_MMD_PMAPMD, + MDIO_PMA_10GBR_FECABLE); + if (reg & MDIO_PMA_10GBR_FECABLE_ABLE) + ecmd->supported |= SUPPORTED_10000baseR_FEC; + ecmd->advertising = ADVERTISED_Backplane; + break; + + /* All the other defined modes are flavours of optical */ + default: + ecmd->port = PORT_FIBRE; + ecmd->supported = SUPPORTED_FIBRE; + ecmd->advertising = ADVERTISED_FIBRE; + break; + } + + if (mdio->mmds & MDIO_DEVS_AN) { + ecmd->supported |= SUPPORTED_Autoneg; + reg = mdio->mdio_read(mdio->dev, mdio->prtad, MDIO_MMD_AN, + MDIO_CTRL1); + if (reg & MDIO_AN_CTRL1_ENABLE) { + ecmd->autoneg = AUTONEG_ENABLE; + ecmd->advertising |= + ADVERTISED_Autoneg | + mdio45_get_an(mdio, MDIO_AN_ADVERTISE) | + npage_adv; + } else { + ecmd->autoneg = AUTONEG_DISABLE; + } + } else { + ecmd->autoneg = AUTONEG_DISABLE; + } + + if (ecmd->autoneg) { + u32 modes = 0; + int an_stat = mdio->mdio_read(mdio->dev, mdio->prtad, + MDIO_MMD_AN, MDIO_STAT1); + + /* If AN is complete and successful, report best common + * mode, otherwise report best advertised mode. */ + if (an_stat & MDIO_AN_STAT1_COMPLETE) { + ecmd->lp_advertising = + mdio45_get_an(mdio, MDIO_AN_LPA) | npage_lpa; + if (an_stat & MDIO_AN_STAT1_LPABLE) + ecmd->lp_advertising |= ADVERTISED_Autoneg; + modes = ecmd->advertising & ecmd->lp_advertising; + } + if ((modes & ~ADVERTISED_Autoneg) == 0) + modes = ecmd->advertising; + + if (modes & (ADVERTISED_10000baseT_Full | + ADVERTISED_10000baseKX4_Full | + ADVERTISED_10000baseKR_Full)) { + speed = SPEED_10000; + ecmd->duplex = DUPLEX_FULL; + } else if (modes & (ADVERTISED_1000baseT_Full | + ADVERTISED_1000baseT_Half | + ADVERTISED_1000baseKX_Full)) { + speed = SPEED_1000; + ecmd->duplex = !(modes & ADVERTISED_1000baseT_Half); + } else if (modes & (ADVERTISED_100baseT_Full | + ADVERTISED_100baseT_Half)) { + speed = SPEED_100; + ecmd->duplex = !!(modes & ADVERTISED_100baseT_Full); + } else { + speed = SPEED_10; + ecmd->duplex = !!(modes & ADVERTISED_10baseT_Full); + } + } else { + /* Report forced settings */ + reg = mdio->mdio_read(mdio->dev, mdio->prtad, MDIO_MMD_PMAPMD, + MDIO_CTRL1); + speed = (((reg & MDIO_PMA_CTRL1_SPEED1000) ? 100 : 1) + * ((reg & MDIO_PMA_CTRL1_SPEED100) ? 100 : 10)); + ecmd->duplex = (reg & MDIO_CTRL1_FULLDPLX || + speed == SPEED_10000); + } + + ethtool_cmd_speed_set(ecmd, speed); + + /* 10GBASE-T MDI/MDI-X */ + if (ecmd->port == PORT_TP + && (ethtool_cmd_speed(ecmd) == SPEED_10000)) { + switch (mdio->mdio_read(mdio->dev, mdio->prtad, MDIO_MMD_PMAPMD, + MDIO_PMA_10GBT_SWAPPOL)) { + case MDIO_PMA_10GBT_SWAPPOL_ABNX | MDIO_PMA_10GBT_SWAPPOL_CDNX: + ecmd->eth_tp_mdix = ETH_TP_MDI; + break; + case 0: + ecmd->eth_tp_mdix = ETH_TP_MDI_X; + break; + default: + /* It's complicated... */ + ecmd->eth_tp_mdix = ETH_TP_MDI_INVALID; + break; + } + } +} +EXPORT_SYMBOL(mdio45_ethtool_gset_npage); + +/** + * mdio45_ethtool_spauseparam_an - set auto-negotiated pause parameters + * @mdio: MDIO interface + * @ecmd: Ethtool request structure + * + * This function assumes that the PHY has an auto-negotiation MMD. It + * will enable and disable advertising of flow control as appropriate. + */ +void mdio45_ethtool_spauseparam_an(const struct mdio_if_info *mdio, + const struct ethtool_pauseparam *ecmd) +{ + int adv, old_adv; + + WARN_ON(!(mdio->mmds & MDIO_DEVS_AN)); + + old_adv = mdio->mdio_read(mdio->dev, mdio->prtad, MDIO_MMD_AN, + MDIO_AN_ADVERTISE); + adv = ((old_adv & ~(ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM)) | + mii_advertise_flowctrl((ecmd->rx_pause ? FLOW_CTRL_RX : 0) | + (ecmd->tx_pause ? FLOW_CTRL_TX : 0))); + if (adv != old_adv) { + mdio->mdio_write(mdio->dev, mdio->prtad, MDIO_MMD_AN, + MDIO_AN_ADVERTISE, adv); + mdio45_nway_restart(mdio); + } +} +EXPORT_SYMBOL(mdio45_ethtool_spauseparam_an); + +/** + * mdio_mii_ioctl - MII ioctl interface for MDIO (clause 22 or 45) PHYs + * @mdio: MDIO interface + * @mii_data: MII ioctl data structure + * @cmd: MII ioctl command + * + * Returns 0 on success, negative on error. + */ +int mdio_mii_ioctl(const struct mdio_if_info *mdio, + struct mii_ioctl_data *mii_data, int cmd) +{ + int prtad, devad; + u16 addr = mii_data->reg_num; + + /* Validate/convert cmd to one of SIOC{G,S}MIIREG */ + switch (cmd) { + case SIOCGMIIPHY: + if (mdio->prtad == MDIO_PRTAD_NONE) + return -EOPNOTSUPP; + mii_data->phy_id = mdio->prtad; + cmd = SIOCGMIIREG; + break; + case SIOCGMIIREG: + case SIOCSMIIREG: + break; + default: + return -EOPNOTSUPP; + } + + /* Validate/convert phy_id */ + if ((mdio->mode_support & MDIO_SUPPORTS_C45) && + mdio_phy_id_is_c45(mii_data->phy_id)) { + prtad = mdio_phy_id_prtad(mii_data->phy_id); + devad = mdio_phy_id_devad(mii_data->phy_id); + } else if ((mdio->mode_support & MDIO_SUPPORTS_C22) && + mii_data->phy_id < 0x20) { + prtad = mii_data->phy_id; + devad = MDIO_DEVAD_NONE; + addr &= 0x1f; + } else if ((mdio->mode_support & MDIO_EMULATE_C22) && + mdio->prtad != MDIO_PRTAD_NONE && + mii_data->phy_id == mdio->prtad) { + /* Remap commonly-used MII registers. */ + prtad = mdio->prtad; + switch (addr) { + case MII_BMCR: + case MII_BMSR: + case MII_PHYSID1: + case MII_PHYSID2: + devad = __ffs(mdio->mmds); + break; + case MII_ADVERTISE: + case MII_LPA: + if (!(mdio->mmds & MDIO_DEVS_AN)) + return -EINVAL; + devad = MDIO_MMD_AN; + if (addr == MII_ADVERTISE) + addr = MDIO_AN_ADVERTISE; + else + addr = MDIO_AN_LPA; + break; + default: + return -EINVAL; + } + } else { + return -EINVAL; + } + + if (cmd == SIOCGMIIREG) { + int rc = mdio->mdio_read(mdio->dev, prtad, devad, addr); + if (rc < 0) + return rc; + mii_data->val_out = rc; + return 0; + } else { + return mdio->mdio_write(mdio->dev, prtad, devad, addr, + mii_data->val_in); + } +} +EXPORT_SYMBOL(mdio_mii_ioctl); diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig new file mode 100644 index 0000000..3783529 --- /dev/null +++ b/drivers/net/usb/Kconfig @@ -0,0 +1,596 @@ +# +# USB Network devices configuration +# +comment "Networking support is needed for USB Network Adapter support" + depends on USB && !NET + +menu "USB Network Adapters" + depends on USB && NET + +config USB_CATC + depends on n + tristate "USB CATC NetMate-based Ethernet device support" + depends on m + depends on CRC32 + ---help--- + Say Y if you want to use one of the following 10Mbps USB Ethernet + device based on the EL1210A chip. Supported devices are: + Belkin F5U011 + Belkin F5U111 + CATC NetMate + CATC NetMate II + smartBridges smartNIC + + This driver makes the adapter appear as a normal Ethernet interface, + typically on eth0, if it is the only ethernet device, or perhaps on + eth1, if you have a PCI or ISA ethernet card installed. + + To compile this driver as a module, choose M here: the + module will be called catc. + +config USB_KAWETH + depends on n + tristate "USB KLSI KL5USB101-based ethernet device support" + depends on m + ---help--- + Say Y here if you want to use one of the following 10Mbps only + USB Ethernet adapters based on the KLSI KL5KUSB101B chipset: + 3Com 3C19250 + ADS USB-10BT + ATEN USB Ethernet + ASANTE USB To Ethernet Adapter + AOX Endpoints USB Ethernet + Correga K.K. + D-Link DSB-650C and DU-E10 + Entrega / Portgear E45 + I-O DATA USB-ET/T + Jaton USB Ethernet Device Adapter + Kingston Technology USB Ethernet Adapter + Linksys USB10T + Mobility USB-Ethernet Adapter + NetGear EA-101 + Peracom Enet and Enet2 + Portsmith Express Ethernet Adapter + Shark Pocket Adapter + SMC 2202USB + Sony Vaio port extender + + This driver is likely to work with most 10Mbps only USB Ethernet + adapters, including some "no brand" devices. It does NOT work on + SmartBridges smartNIC or on Belkin F5U111 devices - you should use + the CATC NetMate driver for those. If you are not sure which one + you need, select both, and the correct one should be selected for + you. + + This driver makes the adapter appear as a normal Ethernet interface, + typically on eth0, if it is the only ethernet device, or perhaps on + eth1, if you have a PCI or ISA ethernet card installed. + + To compile this driver as a module, choose M here: the + module will be called kaweth. + +config USB_PEGASUS + depends on n + tristate "USB Pegasus/Pegasus-II based ethernet device support" + depends on m + select NET_CORE + select BACKPORT_MII + ---help--- + Say Y here if you know you have Pegasus or Pegasus-II based adapter. + If in doubt then look at for the + complete list of supported devices. + + If your particular adapter is not in the list and you are _sure_ it + is Pegasus or Pegasus II based then send me + vendor and device IDs. + + To compile this driver as a module, choose M here: the + module will be called pegasus. + +config USB_RTL8150 + depends on n + tristate "USB RTL8150 based ethernet device support" + depends on m + select NET_CORE + select BACKPORT_MII + help + Say Y here if you have RTL8150 based usb-ethernet adapter. + Send me any comments you may have. + You can also check for updates at . + + To compile this driver as a module, choose M here: the + module will be called rtl8150. + +config USB_RTL8152 + depends on n + tristate "Realtek RTL8152 Based USB 2.0 Ethernet Adapters" + depends on m + select NET_CORE + select BACKPORT_MII + help + This option adds support for Realtek RTL8152 based USB 2.0 + 10/100 Ethernet adapters. + + To compile this driver as a module, choose M here: the + module will be called r8152. + +config USB_USBNET + tristate "Multi-purpose USB Networking Framework" + depends on m + select NET_CORE + select BACKPORT_MII + ---help--- + This driver supports several kinds of network links over USB, + with "minidrivers" built around a common network driver core + that supports deep queues for efficient transfers. (This gives + better performance with small packets and at high speeds). + + The USB host runs "usbnet", and the other end of the link might be: + + - Another USB host, when using USB "network" or "data transfer" + cables. These are often used to network laptops to PCs, like + "Laplink" parallel cables or some motherboards. These rely + on specialized chips from many suppliers. + + - An intelligent USB gadget, perhaps embedding a Linux system. + These include PDAs running Linux (iPaq, Yopy, Zaurus, and + others), and devices that interoperate using the standard + CDC-Ethernet specification (including many cable modems). + + - Network adapter hardware (like those for 10/100 Ethernet) which + uses this driver framework. + + The link will appear with a name like "usb0", when the link is + a two-node link, or "eth0" for most CDC-Ethernet devices. Those + two-node links are most easily managed with Ethernet Bridging + (CONFIG_BRIDGE) instead of routing. + + For more information see . + + To compile this driver as a module, choose M here: the + module will be called usbnet. + +config USB_NET_AX8817X + depends on n + tristate "ASIX AX88xxx Based USB 2.0 Ethernet Adapters" + depends on m + depends on USB_USBNET + depends on CRC32 + depends on PHYLIB + default y + help + This option adds support for ASIX AX88xxx based USB 2.0 + 10/100 Ethernet adapters. + + This driver should work with at least the following devices: + * Aten UC210T + * ASIX AX88172 + * Billionton Systems, USB2AR + * Buffalo LUA-U2-KTX + * Corega FEther USB2-TX + * D-Link DUB-E100 + * Hawking UF200 + * Linksys USB200M + * Netgear FA120 + * Sitecom LN-029 + * Intellinet USB 2.0 Ethernet + * ST Lab USB 2.0 Ethernet + * TrendNet TU2-ET100 + + This driver creates an interface named "ethX", where X depends on + what other networking devices you have in use. + +config USB_NET_AX88179_178A + depends on n + tristate "ASIX AX88179/178A USB 3.0/2.0 to Gigabit Ethernet" + depends on m + depends on USB_USBNET + depends on CRC32 + depends on PHYLIB + default y + help + This option adds support for ASIX AX88179 based USB 3.0/2.0 + to Gigabit Ethernet adapters. + + This driver should work with at least the following devices: + * ASIX AX88179 + * ASIX AX88178A + * Sitcomm LN-032 + + This driver creates an interface named "ethX", where X depends on + what other networking devices you have in use. + +config USB_NET_CDCETHER + tristate "CDC Ethernet support (smart devices such as cable modems)" + depends on m + depends on USB_USBNET + default y + help + This option supports devices conforming to the Communication Device + Class (CDC) Ethernet Control Model, a specification that's easy to + implement in device firmware. The CDC specifications are available + from . + + CDC Ethernet is an implementation option for DOCSIS cable modems + that support USB connectivity, used for non-Microsoft USB hosts. + The Linux-USB CDC Ethernet Gadget driver is an open implementation. + This driver should work with at least the following devices: + + * Dell Wireless 5530 HSPA + * Ericsson PipeRider (all variants) + * Ericsson Mobile Broadband Module (all variants) + * Motorola (DM100 and SB4100) + * Broadcom Cable Modem (reference design) + * Toshiba (PCX1100U and F3507g/F3607gw) + * ... + + This driver creates an interface named "ethX", where X depends on + what other networking devices you have in use. However, if the + IEEE 802 "local assignment" bit is set in the address, a "usbX" + name is used instead. + +config USB_NET_CDC_EEM + depends on n + tristate "CDC EEM support" + depends on m + depends on USB_USBNET + help + This option supports devices conforming to the Communication Device + Class (CDC) Ethernet Emulation Model, a specification that's easy to + implement in device firmware. The CDC EEM specifications are available + from . + + This driver creates an interface named "ethX", where X depends on + what other networking devices you have in use. However, if the + IEEE 802 "local assignment" bit is set in the address, a "usbX" + name is used instead. + +config USB_NET_CDC_NCM + depends on n + tristate "CDC NCM support" + depends on m + depends on USB_USBNET + default y + help + This driver provides support for CDC NCM (Network Control Model + Device USB Class Specification). The CDC NCM specification is + available from . + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module. + + This driver should work with at least the following devices: + * ST-Ericsson M700 LTE FDD/TDD Mobile Broadband Modem (ref. design) + * ST-Ericsson M5730 HSPA+ Mobile Broadband Modem (reference design) + * ST-Ericsson M570 HSPA+ Mobile Broadband Modem (reference design) + * ST-Ericsson M343 HSPA Mobile Broadband Modem (reference design) + * Ericsson F5521gw Mobile Broadband Module + +config USB_NET_CDC_MBIM + depends on n + tristate "CDC MBIM support" + depends on m + depends on USB_USBNET + depends on USB_WDM + select USB_NET_CDC_NCM + help + This driver provides support for CDC MBIM (Mobile Broadband + Interface Model) devices. The CDC MBIM specification is + available from . + + MBIM devices require configuration using the management + protocol defined by the MBIM specification. This driver + provides unfiltered access to the MBIM control channel + through the associated /dev/cdc-wdmx character device. + + To compile this driver as a module, choose M here: the + module will be called cdc_mbim. + +config USB_NET_DM9601 + depends on n + tristate "Davicom DM9601 based USB 1.1 10/100 ethernet devices" + depends on m + depends on USB_USBNET + depends on CRC32 + help + This option adds support for Davicom DM9601 based USB 1.1 + 10/100 Ethernet adapters. + +config USB_NET_SMSC75XX + depends on n + tristate "SMSC LAN75XX based USB 2.0 gigabit ethernet devices" + depends on m + depends on USB_USBNET + depends on BITREVERSE + depends on CRC16 + depends on CRC32 + help + This option adds support for SMSC LAN75XX based USB 2.0 + Gigabit Ethernet adapters. + +config USB_NET_SMSC95XX + depends on n + tristate "SMSC LAN95XX based USB 2.0 10/100 ethernet devices" + depends on m + depends on USB_USBNET + depends on BITREVERSE + depends on CRC16 + depends on CRC32 + help + This option adds support for SMSC LAN95XX based USB 2.0 + 10/100 Ethernet adapters. + +config USB_NET_GL620A + depends on n + tristate "GeneSys GL620USB-A based cables" + depends on m + depends on USB_USBNET + help + Choose this option if you're using a host-to-host cable, + or PC2PC motherboard, with this chip. + + Note that the half-duplex "GL620USB" is not supported. + +config USB_NET_NET1080 + depends on n + tristate "NetChip 1080 based cables (Laplink, ...)" + depends on m + default y + depends on USB_USBNET + help + Choose this option if you're using a host-to-host cable based + on this design: one NetChip 1080 chip and supporting logic, + optionally with LEDs that indicate traffic + +config USB_NET_PLUSB + depends on n + tristate "Prolific PL-2301/2302/25A1 based cables" + depends on m + # if the handshake/init/reset problems, from original 'plusb', + # are ever resolved ... then remove "experimental" + depends on USB_USBNET + help + Choose this option if you're using a host-to-host cable + with one of these chips. + +config USB_NET_MCS7830 + depends on n + tristate "MosChip MCS7830 based Ethernet adapters" + depends on m + depends on USB_USBNET + help + Choose this option if you're using a 10/100 Ethernet USB2 + adapter based on the MosChip 7830 controller. This includes + adapters marketed under the DeLOCK brand. + +config USB_NET_RNDIS_HOST + tristate "Host for RNDIS and ActiveSync devices" + depends on m + depends on USB_USBNET + select USB_NET_CDCETHER + help + This option enables hosting "Remote NDIS" USB networking links, + as encouraged by Microsoft (instead of CDC Ethernet!) for use in + various devices that may only support this protocol. A variant + of this protocol (with even less public documentation) seems to + be at the root of Microsoft's "ActiveSync" too. + + Avoid using this protocol unless you have no better options. + The protocol specification is incomplete, and is controlled by + (and for) Microsoft; it isn't an "Open" ecosystem or market. + +config USB_NET_CDC_SUBSET + depends on n + tristate "Simple USB Network Links (CDC Ethernet subset)" + depends on m + depends on USB_USBNET + default y + help + This driver module supports USB network devices that can work + without any device-specific information. Select it if you have + one of these drivers. + + Note that while many USB host-to-host cables can work in this mode, + that may mean not being able to talk to Win32 systems or more + commonly not being able to handle certain events (like replugging + the host on the other end) very well. Also, these devices will + not generally have permanently assigned Ethernet addresses. + +config USB_ALI_M5632 + boolean "ALi M5632 based 'USB 2.0 Data Link' cables" + depends on USB_NET_CDC_SUBSET + help + Choose this option if you're using a host-to-host cable + based on this design, which supports USB 2.0 high speed. + +config USB_AN2720 + boolean "AnchorChips 2720 based cables (Xircom PGUNET, ...)" + depends on USB_NET_CDC_SUBSET + help + Choose this option if you're using a host-to-host cable + based on this design. Note that AnchorChips is now a + Cypress brand. + +config USB_BELKIN + boolean "eTEK based host-to-host cables (Advance, Belkin, ...)" + depends on USB_NET_CDC_SUBSET + default y + help + Choose this option if you're using a host-to-host cable + based on this design: two NetChip 2890 chips and an Atmel + microcontroller, with LEDs that indicate traffic. + +config USB_ARMLINUX + boolean "Embedded ARM Linux links (iPaq, ...)" + depends on USB_NET_CDC_SUBSET + default y + help + Choose this option to support the "usb-eth" networking driver + used by most of the ARM Linux community with device controllers + such as the SA-11x0 and PXA-25x UDCs, or the tftp capabilities + in some PXA versions of the "blob" boot loader. + + Linux-based "Gumstix" PXA-25x based systems use this protocol + to talk with other Linux systems. + + Although the ROMs shipped with Sharp Zaurus products use a + different link level framing protocol, you can have them use + this simpler protocol by installing a different kernel. + +config USB_EPSON2888 + boolean "Epson 2888 based firmware (DEVELOPMENT)" + depends on USB_NET_CDC_SUBSET + help + Choose this option to support the usb networking links used + by some sample firmware from Epson. + +config USB_KC2190 + boolean "KT Technology KC2190 based cables (InstaNet)" + depends on USB_NET_CDC_SUBSET + help + Choose this option if you're using a host-to-host cable + with one of these chips. + +config USB_NET_ZAURUS + depends on n + tristate "Sharp Zaurus (stock ROMs) and compatible" + depends on m + depends on USB_USBNET + select USB_NET_CDCETHER + depends on CRC32 + default y + help + Choose this option to support the usb networking links used by + Zaurus models like the SL-5000D, SL-5500, SL-5600, A-300, B-500. + This also supports some related device firmware, as used in some + PDAs from Olympus and some cell phones from Motorola. + + If you install an alternate image, such as the Linux 2.6 based + versions of OpenZaurus, you should no longer need to support this + protocol. Only the "eth-fd" or "net_fd" drivers in these devices + really need this non-conformant variant of CDC Ethernet (or in + some cases CDC MDLM) protocol, not "g_ether". + +config USB_NET_CX82310_ETH + depends on n + tristate "Conexant CX82310 USB ethernet port" + depends on m + depends on USB_USBNET + help + Choose this option if you're using a Conexant CX82310-based ADSL + router with USB ethernet port. This driver is for routers only, + it will not work with ADSL modems (use cxacru driver instead). + +config USB_NET_KALMIA + depends on n + tristate "Samsung Kalmia based LTE USB modem" + depends on m + depends on USB_USBNET + help + Choose this option if you have a Samsung Kalmia based USB modem + as Samsung GT-B3730. + + To compile this driver as a module, choose M here: the + module will be called kalmia. + +config USB_NET_QMI_WWAN + depends on n + tristate "QMI WWAN driver for Qualcomm MSM based 3G and LTE modems" + depends on m + depends on USB_USBNET + depends on USB_WDM + help + Support WWAN LTE/3G devices based on Qualcomm Mobile Data Modem + (MDM) chipsets. Examples of such devices are + * Huawei E392/E398 + + This driver will only drive the ethernet part of the chips. + The devices require additional configuration to be usable. + Multiple management interfaces with linux drivers are + available: + + * option: AT commands on /dev/ttyUSBx + * cdc-wdm: Qualcomm MSM Interface (QMI) protocol on /dev/cdc-wdmx + + A modem manager with support for QMI is recommended. + + To compile this driver as a module, choose M here: the + module will be called qmi_wwan. + +config USB_HSO + depends on n + tristate "Option USB High Speed Mobile Devices" + depends on m + depends on USB && RFKILL && TTY + default n + help + Choose this option if you have an Option HSDPA/HSUPA card. + These cards support downlink speeds of 7.2Mbps or greater. + + To compile this driver as a module, choose M here: the + module will be called hso. + +config USB_NET_INT51X1 + depends on n + tristate "Intellon PLC based usb adapter" + depends on m + depends on USB_USBNET + help + Choose this option if you're using a 14Mb USB-based PLC + (Powerline Communications) solution with an Intellon + INT51x1/INT5200 chip, like the "devolo dLan duo". + +config USB_CDC_PHONET + depends on n + tristate "CDC Phonet support" + depends on m + depends on PHONET + help + Choose this option to support the Phonet interface to a Nokia + cellular modem, as found on most Nokia handsets with the + "PC suite" USB profile. + +config USB_IPHETH + depends on n + tristate "Apple iPhone USB Ethernet driver" + depends on m + default n + ---help--- + Module used to share Internet connection (tethering) from your + iPhone (Original, 3G and 3GS) to your system. + Note that you need userspace libraries and programs that are needed + to pair your device with your system and that understand the iPhone + protocol. + + For more information: http://giagio.com/wiki/moin.cgi/iPhoneEthernetDriver + +config USB_SIERRA_NET + depends on n + tristate "USB-to-WWAN Driver for Sierra Wireless modems" + depends on m + depends on USB_USBNET + help + Choose this option if you have a Sierra Wireless USB-to-WWAN device. + + To compile this driver as a module, choose M here: the + module will be called sierra_net. + +config USB_VL600 + depends on n + tristate "LG VL600 modem dongle" + depends on m + depends on USB_NET_CDCETHER && TTY + depends on USB_ACM + help + Select this if you want to use an LG Electronics 4G/LTE usb modem + called VL600. This driver only handles the ethernet + interface exposed by the modem firmware. To establish a connection + you will first need a userspace program that sends the right + command to the modem through its CDC ACM port, and most + likely also a DHCP client. See this thread about using the + 4G modem from Verizon: + + http://ubuntuforums.org/showpost.php?p=10589647&postcount=17 + + +endmenu diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile new file mode 100644 index 0000000..5b83842 --- /dev/null +++ b/drivers/net/usb/Makefile @@ -0,0 +1,36 @@ +# +# Makefile for USB Network drivers +# +# +#obj-$(CPTCFG_USB_CATC) += catc.o +#obj-$(CPTCFG_USB_KAWETH) += kaweth.o +#obj-$(CPTCFG_USB_PEGASUS) += pegasus.o +#obj-$(CPTCFG_USB_RTL8150) += rtl8150.o +#obj-$(CPTCFG_USB_RTL8152) += r8152.o +#obj-$(CPTCFG_USB_HSO) += hso.o +#obj-$(CPTCFG_USB_NET_AX8817X) += asix.o +#obj-$(CPTCFG_USB_NET_AX88179_178A) += ax88179_178a.o +obj-$(CPTCFG_USB_NET_CDCETHER) += cdc_ether.o +#obj-$(CPTCFG_USB_NET_CDC_EEM) += cdc_eem.o +#obj-$(CPTCFG_USB_NET_DM9601) += dm9601.o +#obj-$(CPTCFG_USB_NET_SMSC75XX) += smsc75xx.o +#obj-$(CPTCFG_USB_NET_SMSC95XX) += smsc95xx.o +#obj-$(CPTCFG_USB_NET_GL620A) += gl620a.o +#obj-$(CPTCFG_USB_NET_NET1080) += net1080.o +#obj-$(CPTCFG_USB_NET_PLUSB) += plusb.o +obj-$(CPTCFG_USB_NET_RNDIS_HOST) += rndis_host.o +#obj-$(CPTCFG_USB_NET_CDC_SUBSET) += cdc_subset.o +#obj-$(CPTCFG_USB_NET_ZAURUS) += zaurus.o +#obj-$(CPTCFG_USB_NET_MCS7830) += mcs7830.o +obj-$(CPTCFG_USB_USBNET) += usbnet.o +#obj-$(CPTCFG_USB_NET_INT51X1) += int51x1.o +#obj-$(CPTCFG_USB_CDC_PHONET) += cdc-phonet.o +#obj-$(CPTCFG_USB_NET_KALMIA) += kalmia.o +#obj-$(CPTCFG_USB_IPHETH) += ipheth.o +#obj-$(CPTCFG_USB_SIERRA_NET) += sierra_net.o +#obj-$(CPTCFG_USB_NET_CX82310_ETH) += cx82310_eth.o +#obj-$(CPTCFG_USB_NET_CDC_NCM) += cdc_ncm.o +#obj-$(CPTCFG_USB_VL600) += lg-vl600.o +#obj-$(CPTCFG_USB_NET_QMI_WWAN) += qmi_wwan.o +#obj-$(CPTCFG_USB_NET_CDC_MBIM) += cdc_mbim.o + diff --git a/drivers/net/usb/cdc_ether.c b/drivers/net/usb/cdc_ether.c new file mode 100644 index 0000000..d36ab61 --- /dev/null +++ b/drivers/net/usb/cdc_ether.c @@ -0,0 +1,757 @@ +/* + * CDC Ethernet based networking peripherals + * Copyright (C) 2003-2005 by David Brownell + * Copyright (C) 2006 by Ole Andre Vadla Ravnas (ActiveSync) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// #define DEBUG // error path messages, extra info +// #define VERBOSE // more; success messages + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#if defined(CPTCFG_USB_NET_RNDIS_HOST) || defined(CPTCFG_USB_NET_RNDIS_HOST_MODULE) + +static int is_rndis(struct usb_interface_descriptor *desc) +{ + return (desc->bInterfaceClass == USB_CLASS_COMM && + desc->bInterfaceSubClass == 2 && + desc->bInterfaceProtocol == 0xff); +} + +static int is_activesync(struct usb_interface_descriptor *desc) +{ + return (desc->bInterfaceClass == USB_CLASS_MISC && + desc->bInterfaceSubClass == 1 && + desc->bInterfaceProtocol == 1); +} + +static int is_wireless_rndis(struct usb_interface_descriptor *desc) +{ + return (desc->bInterfaceClass == USB_CLASS_WIRELESS_CONTROLLER && + desc->bInterfaceSubClass == 1 && + desc->bInterfaceProtocol == 3); +} + +#else + +#define is_rndis(desc) 0 +#define is_activesync(desc) 0 +#define is_wireless_rndis(desc) 0 + +#endif + +static const u8 mbm_guid[16] = { + 0xa3, 0x17, 0xa8, 0x8b, 0x04, 0x5e, 0x4f, 0x01, + 0xa6, 0x07, 0xc0, 0xff, 0xcb, 0x7e, 0x39, 0x2a, +}; + +/* + * probes control interface, claims data interface, collects the bulk + * endpoints, activates data interface (if needed), maybe sets MTU. + * all pure cdc, except for certain firmware workarounds, and knowing + * that rndis uses one different rule. + */ +int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf) +{ + u8 *buf = intf->cur_altsetting->extra; + int len = intf->cur_altsetting->extralen; + struct usb_interface_descriptor *d; + struct cdc_state *info = (void *) &dev->data; + int status; + int rndis; + bool android_rndis_quirk = false; + struct usb_driver *driver = driver_of(intf); + struct usb_cdc_mdlm_desc *desc = NULL; + struct usb_cdc_mdlm_detail_desc *detail = NULL; + + if (sizeof dev->data < sizeof *info) + return -EDOM; + + /* expect strict spec conformance for the descriptors, but + * cope with firmware which stores them in the wrong place + */ + if (len == 0 && dev->udev->actconfig->extralen) { + /* Motorola SB4100 (and others: Brad Hards says it's + * from a Broadcom design) put CDC descriptors here + */ + buf = dev->udev->actconfig->extra; + len = dev->udev->actconfig->extralen; + dev_dbg(&intf->dev, "CDC descriptors on config\n"); + } + + /* Maybe CDC descriptors are after the endpoint? This bug has + * been seen on some 2Wire Inc RNDIS-ish products. + */ + if (len == 0) { + struct usb_host_endpoint *hep; + + hep = intf->cur_altsetting->endpoint; + if (hep) { + buf = hep->extra; + len = hep->extralen; + } + if (len) + dev_dbg(&intf->dev, + "CDC descriptors on endpoint\n"); + } + + /* this assumes that if there's a non-RNDIS vendor variant + * of cdc-acm, it'll fail RNDIS requests cleanly. + */ + rndis = (is_rndis(&intf->cur_altsetting->desc) || + is_activesync(&intf->cur_altsetting->desc) || + is_wireless_rndis(&intf->cur_altsetting->desc)); + + memset(info, 0, sizeof *info); + info->control = intf; + while (len > 3) { + if (buf [1] != USB_DT_CS_INTERFACE) + goto next_desc; + + /* use bDescriptorSubType to identify the CDC descriptors. + * We expect devices with CDC header and union descriptors. + * For CDC Ethernet we need the ethernet descriptor. + * For RNDIS, ignore two (pointless) CDC modem descriptors + * in favor of a complicated OID-based RPC scheme doing what + * CDC Ethernet achieves with a simple descriptor. + */ + switch (buf [2]) { + case USB_CDC_HEADER_TYPE: + if (info->header) { + dev_dbg(&intf->dev, "extra CDC header\n"); + goto bad_desc; + } + info->header = (void *) buf; + if (info->header->bLength != sizeof *info->header) { + dev_dbg(&intf->dev, "CDC header len %u\n", + info->header->bLength); + goto bad_desc; + } + break; + case USB_CDC_ACM_TYPE: + /* paranoia: disambiguate a "real" vendor-specific + * modem interface from an RNDIS non-modem. + */ + if (rndis) { + struct usb_cdc_acm_descriptor *acm; + + acm = (void *) buf; + if (acm->bmCapabilities) { + dev_dbg(&intf->dev, + "ACM capabilities %02x, " + "not really RNDIS?\n", + acm->bmCapabilities); + goto bad_desc; + } + } + break; + case USB_CDC_UNION_TYPE: + if (info->u) { + dev_dbg(&intf->dev, "extra CDC union\n"); + goto bad_desc; + } + info->u = (void *) buf; + if (info->u->bLength != sizeof *info->u) { + dev_dbg(&intf->dev, "CDC union len %u\n", + info->u->bLength); + goto bad_desc; + } + + /* we need a master/control interface (what we're + * probed with) and a slave/data interface; union + * descriptors sort this all out. + */ + info->control = usb_ifnum_to_if(dev->udev, + info->u->bMasterInterface0); + info->data = usb_ifnum_to_if(dev->udev, + info->u->bSlaveInterface0); + if (!info->control || !info->data) { + dev_dbg(&intf->dev, + "master #%u/%p slave #%u/%p\n", + info->u->bMasterInterface0, + info->control, + info->u->bSlaveInterface0, + info->data); + /* fall back to hard-wiring for RNDIS */ + if (rndis) { + android_rndis_quirk = true; + goto next_desc; + } + goto bad_desc; + } + if (info->control != intf) { + dev_dbg(&intf->dev, "bogus CDC Union\n"); + /* Ambit USB Cable Modem (and maybe others) + * interchanges master and slave interface. + */ + if (info->data == intf) { + info->data = info->control; + info->control = intf; + } else + goto bad_desc; + } + + /* a data interface altsetting does the real i/o */ + d = &info->data->cur_altsetting->desc; + if (d->bInterfaceClass != USB_CLASS_CDC_DATA) { + dev_dbg(&intf->dev, "slave class %u\n", + d->bInterfaceClass); + goto bad_desc; + } + break; + case USB_CDC_ETHERNET_TYPE: + if (info->ether) { + dev_dbg(&intf->dev, "extra CDC ether\n"); + goto bad_desc; + } + info->ether = (void *) buf; + if (info->ether->bLength != sizeof *info->ether) { + dev_dbg(&intf->dev, "CDC ether len %u\n", + info->ether->bLength); + goto bad_desc; + } + dev->hard_mtu = le16_to_cpu( + info->ether->wMaxSegmentSize); + /* because of Zaurus, we may be ignoring the host + * side link address we were given. + */ + break; + case USB_CDC_MDLM_TYPE: + if (desc) { + dev_dbg(&intf->dev, "extra MDLM descriptor\n"); + goto bad_desc; + } + + desc = (void *)buf; + + if (desc->bLength != sizeof(*desc)) + goto bad_desc; + + if (memcmp(&desc->bGUID, mbm_guid, 16)) + goto bad_desc; + break; + case USB_CDC_MDLM_DETAIL_TYPE: + if (detail) { + dev_dbg(&intf->dev, "extra MDLM detail descriptor\n"); + goto bad_desc; + } + + detail = (void *)buf; + + if (detail->bGuidDescriptorType == 0) { + if (detail->bLength < (sizeof(*detail) + 1)) + goto bad_desc; + } else + goto bad_desc; + break; + } +next_desc: + len -= buf [0]; /* bLength */ + buf += buf [0]; + } + + /* Microsoft ActiveSync based and some regular RNDIS devices lack the + * CDC descriptors, so we'll hard-wire the interfaces and not check + * for descriptors. + * + * Some Android RNDIS devices have a CDC Union descriptor pointing + * to non-existing interfaces. Ignore that and attempt the same + * hard-wired 0 and 1 interfaces. + */ + if (rndis && (!info->u || android_rndis_quirk)) { + info->control = usb_ifnum_to_if(dev->udev, 0); + info->data = usb_ifnum_to_if(dev->udev, 1); + if (!info->control || !info->data || info->control != intf) { + dev_dbg(&intf->dev, + "rndis: master #0/%p slave #1/%p\n", + info->control, + info->data); + goto bad_desc; + } + + } else if (!info->header || !info->u || (!rndis && !info->ether)) { + dev_dbg(&intf->dev, "missing cdc %s%s%sdescriptor\n", + info->header ? "" : "header ", + info->u ? "" : "union ", + info->ether ? "" : "ether "); + goto bad_desc; + } + + /* claim data interface and set it up ... with side effects. + * network traffic can't flow until an altsetting is enabled. + */ + status = usb_driver_claim_interface(driver, info->data, dev); + if (status < 0) + return status; + status = usbnet_get_endpoints(dev, info->data); + if (status < 0) { + /* ensure immediate exit from usbnet_disconnect */ + usb_set_intfdata(info->data, NULL); + usb_driver_release_interface(driver, info->data); + return status; + } + + /* status endpoint: optional for CDC Ethernet, not RNDIS (or ACM) */ + dev->status = NULL; + if (info->control->cur_altsetting->desc.bNumEndpoints == 1) { + struct usb_endpoint_descriptor *desc; + + dev->status = &info->control->cur_altsetting->endpoint [0]; + desc = &dev->status->desc; + if (!usb_endpoint_is_int_in(desc) || + (le16_to_cpu(desc->wMaxPacketSize) + < sizeof(struct usb_cdc_notification)) || + !desc->bInterval) { + dev_dbg(&intf->dev, "bad notification endpoint\n"); + dev->status = NULL; + } + } + if (rndis && !dev->status) { + dev_dbg(&intf->dev, "missing RNDIS status endpoint\n"); + usb_set_intfdata(info->data, NULL); + usb_driver_release_interface(driver, info->data); + return -ENODEV; + } + return 0; + +bad_desc: + dev_info(&dev->udev->dev, "bad CDC descriptors\n"); + return -ENODEV; +} +EXPORT_SYMBOL_GPL(usbnet_generic_cdc_bind); + +void usbnet_cdc_unbind(struct usbnet *dev, struct usb_interface *intf) +{ + struct cdc_state *info = (void *) &dev->data; + struct usb_driver *driver = driver_of(intf); + + /* disconnect master --> disconnect slave */ + if (intf == info->control && info->data) { + /* ensure immediate exit from usbnet_disconnect */ + usb_set_intfdata(info->data, NULL); + usb_driver_release_interface(driver, info->data); + info->data = NULL; + } + + /* and vice versa (just in case) */ + else if (intf == info->data && info->control) { + /* ensure immediate exit from usbnet_disconnect */ + usb_set_intfdata(info->control, NULL); + usb_driver_release_interface(driver, info->control); + info->control = NULL; + } +} +EXPORT_SYMBOL_GPL(usbnet_cdc_unbind); + +/*------------------------------------------------------------------------- + * + * Communications Device Class, Ethernet Control model + * + * Takes two interfaces. The DATA interface is inactive till an altsetting + * is selected. Configuration data includes class descriptors. There's + * an optional status endpoint on the control interface. + * + * This should interop with whatever the 2.4 "CDCEther.c" driver + * (by Brad Hards) talked with, with more functionality. + * + *-------------------------------------------------------------------------*/ + +static void dumpspeed(struct usbnet *dev, __le32 *speeds) +{ + netif_info(dev, timer, dev->net, + "link speeds: %u kbps up, %u kbps down\n", + __le32_to_cpu(speeds[0]) / 1000, + __le32_to_cpu(speeds[1]) / 1000); +} + +void usbnet_cdc_status(struct usbnet *dev, struct urb *urb) +{ + struct usb_cdc_notification *event; + + if (urb->actual_length < sizeof *event) + return; + + /* SPEED_CHANGE can get split into two 8-byte packets */ + if (test_and_clear_bit(EVENT_STS_SPLIT, &dev->flags)) { + dumpspeed(dev, (__le32 *) urb->transfer_buffer); + return; + } + + event = urb->transfer_buffer; + switch (event->bNotificationType) { + case USB_CDC_NOTIFY_NETWORK_CONNECTION: + netif_dbg(dev, timer, dev->net, "CDC: carrier %s\n", + event->wValue ? "on" : "off"); + usbnet_link_change(dev, !!event->wValue, 0); + break; + case USB_CDC_NOTIFY_SPEED_CHANGE: /* tx/rx rates */ + netif_dbg(dev, timer, dev->net, "CDC: speed change (len %d)\n", + urb->actual_length); + if (urb->actual_length != (sizeof *event + 8)) + set_bit(EVENT_STS_SPLIT, &dev->flags); + else + dumpspeed(dev, (__le32 *) &event[1]); + break; + /* USB_CDC_NOTIFY_RESPONSE_AVAILABLE can happen too (e.g. RNDIS), + * but there are no standard formats for the response data. + */ + default: + netdev_err(dev->net, "CDC: unexpected notification %02x!\n", + event->bNotificationType); + break; + } +} +EXPORT_SYMBOL_GPL(usbnet_cdc_status); + +int usbnet_cdc_bind(struct usbnet *dev, struct usb_interface *intf) +{ + int status; + struct cdc_state *info = (void *) &dev->data; + + BUILD_BUG_ON((sizeof(((struct usbnet *)0)->data) + < sizeof(struct cdc_state))); + + status = usbnet_generic_cdc_bind(dev, intf); + if (status < 0) + return status; + + status = usbnet_get_ethernet_addr(dev, info->ether->iMACAddress); + if (status < 0) { + usb_set_intfdata(info->data, NULL); + usb_driver_release_interface(driver_of(intf), info->data); + return status; + } + + /* FIXME cdc-ether has some multicast code too, though it complains + * in routine cases. info->ether describes the multicast support. + * Implement that here, manipulating the cdc filter as needed. + */ + return 0; +} +EXPORT_SYMBOL_GPL(usbnet_cdc_bind); + +static const struct driver_info cdc_info = { + .description = "CDC Ethernet Device", + .flags = FLAG_ETHER | FLAG_POINTTOPOINT, + // .check_connect = cdc_check_connect, + .bind = usbnet_cdc_bind, + .unbind = usbnet_cdc_unbind, + .status = usbnet_cdc_status, + .manage_power = usbnet_manage_power, +}; + +static const struct driver_info wwan_info = { + .description = "Mobile Broadband Network Device", + .flags = FLAG_WWAN, + .bind = usbnet_cdc_bind, + .unbind = usbnet_cdc_unbind, + .status = usbnet_cdc_status, + .manage_power = usbnet_manage_power, +}; + +/*-------------------------------------------------------------------------*/ + +#define HUAWEI_VENDOR_ID 0x12D1 +#define NOVATEL_VENDOR_ID 0x1410 +#define ZTE_VENDOR_ID 0x19D2 +#define DELL_VENDOR_ID 0x413C +#define REALTEK_VENDOR_ID 0x0bda + +static const struct usb_device_id products [] = { +/* + * BLACKLIST !! + * + * First blacklist any products that are egregiously nonconformant + * with the CDC Ethernet specs. Minor braindamage we cope with; when + * they're not even trying, needing a separate driver is only the first + * of the differences to show up. + */ + +#define ZAURUS_MASTER_INTERFACE \ + .bInterfaceClass = USB_CLASS_COMM, \ + .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, \ + .bInterfaceProtocol = USB_CDC_PROTO_NONE + +/* SA-1100 based Sharp Zaurus ("collie"), or compatible; + * wire-incompatible with true CDC Ethernet implementations. + * (And, it seems, needlessly so...) + */ +{ + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x04DD, + .idProduct = 0x8004, + ZAURUS_MASTER_INTERFACE, + .driver_info = 0, +}, + +/* PXA-25x based Sharp Zaurii. Note that it seems some of these + * (later models especially) may have shipped only with firmware + * advertising false "CDC MDLM" compatibility ... but we're not + * clear which models did that, so for now let's assume the worst. + */ +{ + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x04DD, + .idProduct = 0x8005, /* A-300 */ + ZAURUS_MASTER_INTERFACE, + .driver_info = 0, +}, { + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x04DD, + .idProduct = 0x8006, /* B-500/SL-5600 */ + ZAURUS_MASTER_INTERFACE, + .driver_info = 0, +}, { + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x04DD, + .idProduct = 0x8007, /* C-700 */ + ZAURUS_MASTER_INTERFACE, + .driver_info = 0, +}, { + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x04DD, + .idProduct = 0x9031, /* C-750 C-760 */ + ZAURUS_MASTER_INTERFACE, + .driver_info = 0, +}, { + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x04DD, + .idProduct = 0x9032, /* SL-6000 */ + ZAURUS_MASTER_INTERFACE, + .driver_info = 0, +}, { + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x04DD, + /* reported with some C860 units */ + .idProduct = 0x9050, /* C-860 */ + ZAURUS_MASTER_INTERFACE, + .driver_info = 0, +}, + +/* Olympus has some models with a Zaurus-compatible option. + * R-1000 uses a FreeScale i.MXL cpu (ARMv4T) + */ +{ + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x07B4, + .idProduct = 0x0F02, /* R-1000 */ + ZAURUS_MASTER_INTERFACE, + .driver_info = 0, +}, + +/* LG Electronics VL600 wants additional headers on every frame */ +{ + USB_DEVICE_AND_INTERFACE_INFO(0x1004, 0x61aa, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* Logitech Harmony 900 - uses the pseudo-MDLM (BLAN) driver */ +{ + USB_DEVICE_AND_INTERFACE_INFO(0x046d, 0xc11f, USB_CLASS_COMM, + USB_CDC_SUBCLASS_MDLM, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* Novatel USB551L and MC551 - handled by qmi_wwan */ +{ + USB_DEVICE_AND_INTERFACE_INFO(NOVATEL_VENDOR_ID, 0xB001, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* Novatel E362 - handled by qmi_wwan */ +{ + USB_DEVICE_AND_INTERFACE_INFO(NOVATEL_VENDOR_ID, 0x9010, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* Dell Wireless 5800 (Novatel E362) - handled by qmi_wwan */ +{ + USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, 0x8195, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* Dell Wireless 5800 (Novatel E362) - handled by qmi_wwan */ +{ + USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, 0x8196, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* Dell Wireless 5804 (Novatel E371) - handled by qmi_wwan */ +{ + USB_DEVICE_AND_INTERFACE_INFO(DELL_VENDOR_ID, 0x819b, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* AnyDATA ADU960S - handled by qmi_wwan */ +{ + USB_DEVICE_AND_INTERFACE_INFO(0x16d5, 0x650a, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, + +/* Huawei E1820 - handled by qmi_wwan */ +{ + USB_DEVICE_INTERFACE_NUMBER(HUAWEI_VENDOR_ID, 0x14ac, 1), + .driver_info = 0, +}, + +/* Realtek RTL8152 Based USB 2.0 Ethernet Adapters */ +#if defined(CPTCFG_USB_RTL8152) || defined(CPTCFG_USB_RTL8152_MODULE) +{ + USB_DEVICE_AND_INTERFACE_INFO(REALTEK_VENDOR_ID, 0x8152, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = 0, +}, +#endif + +/* + * WHITELIST!!! + * + * CDC Ether uses two interfaces, not necessarily consecutive. + * We match the main interface, ignoring the optional device + * class so we could handle devices that aren't exclusively + * CDC ether. + * + * NOTE: this match must come AFTER entries blacklisting devices + * because of bugs/quirks in a given product (like Zaurus, above). + */ +{ + /* ZTE (Vodafone) K3805-Z */ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR + | USB_DEVICE_ID_MATCH_PRODUCT + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = ZTE_VENDOR_ID, + .idProduct = 0x1003, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, + .bInterfaceProtocol = USB_CDC_PROTO_NONE, + .driver_info = (unsigned long)&wwan_info, +}, { + /* ZTE (Vodafone) K3806-Z */ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR + | USB_DEVICE_ID_MATCH_PRODUCT + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = ZTE_VENDOR_ID, + .idProduct = 0x1015, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, + .bInterfaceProtocol = USB_CDC_PROTO_NONE, + .driver_info = (unsigned long)&wwan_info, +}, { + /* ZTE (Vodafone) K4510-Z */ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR + | USB_DEVICE_ID_MATCH_PRODUCT + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = ZTE_VENDOR_ID, + .idProduct = 0x1173, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, + .bInterfaceProtocol = USB_CDC_PROTO_NONE, + .driver_info = (unsigned long)&wwan_info, +}, { + /* ZTE (Vodafone) K3770-Z */ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR + | USB_DEVICE_ID_MATCH_PRODUCT + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = ZTE_VENDOR_ID, + .idProduct = 0x1177, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, + .bInterfaceProtocol = USB_CDC_PROTO_NONE, + .driver_info = (unsigned long)&wwan_info, +}, { + /* ZTE (Vodafone) K3772-Z */ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR + | USB_DEVICE_ID_MATCH_PRODUCT + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = ZTE_VENDOR_ID, + .idProduct = 0x1181, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, + .bInterfaceProtocol = USB_CDC_PROTO_NONE, + .driver_info = (unsigned long)&wwan_info, +}, { + /* Telit modules */ + USB_VENDOR_AND_INTERFACE_INFO(0x1bc7, USB_CLASS_COMM, + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), + .driver_info = (kernel_ulong_t) &wwan_info, +}, { + USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ETHERNET, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long) &cdc_info, +}, { + USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_MDLM, + USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&wwan_info, + +}, { + /* Various Huawei modems with a network port like the UMG1831 */ + .match_flags = USB_DEVICE_ID_MATCH_VENDOR + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = HUAWEI_VENDOR_ID, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, + .bInterfaceProtocol = 255, + .driver_info = (unsigned long)&wwan_info, +}, + { }, // END +}; +MODULE_DEVICE_TABLE(usb, products); + +static struct usb_driver cdc_driver = { + .name = "cdc_ether", + .id_table = products, + .probe = usbnet_probe, + .disconnect = usbnet_disconnect, + .suspend = usbnet_suspend, + .resume = usbnet_resume, + .reset_resume = usbnet_resume, + .supports_autosuspend = 1, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +module_usb_driver(cdc_driver); + +MODULE_AUTHOR("David Brownell"); +MODULE_DESCRIPTION("USB CDC Ethernet devices"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/rndis_host.c b/drivers/net/usb/rndis_host.c new file mode 100644 index 0000000..7beff72 --- /dev/null +++ b/drivers/net/usb/rndis_host.c @@ -0,0 +1,649 @@ +/* + * Host Side support for RNDIS Networking Links + * Copyright (C) 2005 by David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * RNDIS is NDIS remoted over USB. It's a MSFT variant of CDC ACM ... of + * course ACM was intended for modems, not Ethernet links! USB's standard + * for Ethernet links is "CDC Ethernet", which is significantly simpler. + * + * NOTE that Microsoft's "RNDIS 1.0" specification is incomplete. Issues + * include: + * - Power management in particular relies on information that's scattered + * through other documentation, and which is incomplete or incorrect even + * there. + * - There are various undocumented protocol requirements, such as the + * need to send unused garbage in control-OUT messages. + * - In some cases, MS-Windows will emit undocumented requests; this + * matters more to peripheral implementations than host ones. + * + * Moreover there's a no-open-specs variant of RNDIS called "ActiveSync". + * + * For these reasons and others, ** USE OF RNDIS IS STRONGLY DISCOURAGED ** in + * favor of such non-proprietary alternatives as CDC Ethernet or the newer (and + * currently rare) "Ethernet Emulation Model" (EEM). + */ + +/* + * RNDIS notifications from device: command completion; "reverse" + * keepalives; etc + */ +void rndis_status(struct usbnet *dev, struct urb *urb) +{ + netdev_dbg(dev->net, "rndis status urb, len %d stat %d\n", + urb->actual_length, urb->status); + // FIXME for keepalives, respond immediately (asynchronously) + // if not an RNDIS status, do like cdc_status(dev,urb) does +} +EXPORT_SYMBOL_GPL(rndis_status); + +/* + * RNDIS indicate messages. + */ +static void rndis_msg_indicate(struct usbnet *dev, struct rndis_indicate *msg, + int buflen) +{ + struct cdc_state *info = (void *)&dev->data; + struct device *udev = &info->control->dev; + + if (dev->driver_info->indication) { + dev->driver_info->indication(dev, msg, buflen); + } else { + u32 status = le32_to_cpu(msg->status); + + switch (status) { + case RNDIS_STATUS_MEDIA_CONNECT: + dev_info(udev, "rndis media connect\n"); + break; + case RNDIS_STATUS_MEDIA_DISCONNECT: + dev_info(udev, "rndis media disconnect\n"); + break; + default: + dev_info(udev, "rndis indication: 0x%08x\n", status); + } + } +} + +/* + * RPC done RNDIS-style. Caller guarantees: + * - message is properly byteswapped + * - there's no other request pending + * - buf can hold up to 1KB response (required by RNDIS spec) + * On return, the first few entries are already byteswapped. + * + * Call context is likely probe(), before interface name is known, + * which is why we won't try to use it in the diagnostics. + */ +int rndis_command(struct usbnet *dev, struct rndis_msg_hdr *buf, int buflen) +{ + struct cdc_state *info = (void *) &dev->data; + struct usb_cdc_notification notification; + int master_ifnum; + int retval; + int partial; + unsigned count; + u32 xid = 0, msg_len, request_id, msg_type, rsp, + status; + + /* REVISIT when this gets called from contexts other than probe() or + * disconnect(): either serialize, or dispatch responses on xid + */ + + msg_type = le32_to_cpu(buf->msg_type); + + /* Issue the request; xid is unique, don't bother byteswapping it */ + if (likely(msg_type != RNDIS_MSG_HALT && msg_type != RNDIS_MSG_RESET)) { + xid = dev->xid++; + if (!xid) + xid = dev->xid++; + buf->request_id = (__force __le32) xid; + } + master_ifnum = info->control->cur_altsetting->desc.bInterfaceNumber; + retval = usb_control_msg(dev->udev, + usb_sndctrlpipe(dev->udev, 0), + USB_CDC_SEND_ENCAPSULATED_COMMAND, + USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, master_ifnum, + buf, le32_to_cpu(buf->msg_len), + RNDIS_CONTROL_TIMEOUT_MS); + if (unlikely(retval < 0 || xid == 0)) + return retval; + + /* Some devices don't respond on the control channel until + * polled on the status channel, so do that first. */ + if (dev->driver_info->data & RNDIS_DRIVER_DATA_POLL_STATUS) { + retval = usb_interrupt_msg( + dev->udev, + usb_rcvintpipe(dev->udev, + dev->status->desc.bEndpointAddress), + ¬ification, sizeof(notification), &partial, + RNDIS_CONTROL_TIMEOUT_MS); + if (unlikely(retval < 0)) + return retval; + } + + /* Poll the control channel; the request probably completed immediately */ + rsp = le32_to_cpu(buf->msg_type) | RNDIS_MSG_COMPLETION; + for (count = 0; count < 10; count++) { + memset(buf, 0, CONTROL_BUFFER_SIZE); + retval = usb_control_msg(dev->udev, + usb_rcvctrlpipe(dev->udev, 0), + USB_CDC_GET_ENCAPSULATED_RESPONSE, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, master_ifnum, + buf, buflen, + RNDIS_CONTROL_TIMEOUT_MS); + if (likely(retval >= 8)) { + msg_type = le32_to_cpu(buf->msg_type); + msg_len = le32_to_cpu(buf->msg_len); + status = le32_to_cpu(buf->status); + request_id = (__force u32) buf->request_id; + if (likely(msg_type == rsp)) { + if (likely(request_id == xid)) { + if (unlikely(rsp == RNDIS_MSG_RESET_C)) + return 0; + if (likely(RNDIS_STATUS_SUCCESS == + status)) + return 0; + dev_dbg(&info->control->dev, + "rndis reply status %08x\n", + status); + return -EL3RST; + } + dev_dbg(&info->control->dev, + "rndis reply id %d expected %d\n", + request_id, xid); + /* then likely retry */ + } else switch (msg_type) { + case RNDIS_MSG_INDICATE: /* fault/event */ + rndis_msg_indicate(dev, (void *)buf, buflen); + break; + case RNDIS_MSG_KEEPALIVE: { /* ping */ + struct rndis_keepalive_c *msg = (void *)buf; + + msg->msg_type = cpu_to_le32(RNDIS_MSG_KEEPALIVE_C); + msg->msg_len = cpu_to_le32(sizeof *msg); + msg->status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + retval = usb_control_msg(dev->udev, + usb_sndctrlpipe(dev->udev, 0), + USB_CDC_SEND_ENCAPSULATED_COMMAND, + USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, master_ifnum, + msg, sizeof *msg, + RNDIS_CONTROL_TIMEOUT_MS); + if (unlikely(retval < 0)) + dev_dbg(&info->control->dev, + "rndis keepalive err %d\n", + retval); + } + break; + default: + dev_dbg(&info->control->dev, + "unexpected rndis msg %08x len %d\n", + le32_to_cpu(buf->msg_type), msg_len); + } + } else { + /* device probably issued a protocol stall; ignore */ + dev_dbg(&info->control->dev, + "rndis response error, code %d\n", retval); + } + msleep(20); + } + dev_dbg(&info->control->dev, "rndis response timeout\n"); + return -ETIMEDOUT; +} +EXPORT_SYMBOL_GPL(rndis_command); + +/* + * rndis_query: + * + * Performs a query for @oid along with 0 or more bytes of payload as + * specified by @in_len. If @reply_len is not set to -1 then the reply + * length is checked against this value, resulting in an error if it + * doesn't match. + * + * NOTE: Adding a payload exactly or greater than the size of the expected + * response payload is an evident requirement MSFT added for ActiveSync. + * + * The only exception is for OIDs that return a variably sized response, + * in which case no payload should be added. This undocumented (and + * nonsensical!) issue was found by sniffing protocol requests from the + * ActiveSync 4.1 Windows driver. + */ +static int rndis_query(struct usbnet *dev, struct usb_interface *intf, + void *buf, u32 oid, u32 in_len, + void **reply, int *reply_len) +{ + int retval; + union { + void *buf; + struct rndis_msg_hdr *header; + struct rndis_query *get; + struct rndis_query_c *get_c; + } u; + u32 off, len; + + u.buf = buf; + + memset(u.get, 0, sizeof *u.get + in_len); + u.get->msg_type = cpu_to_le32(RNDIS_MSG_QUERY); + u.get->msg_len = cpu_to_le32(sizeof *u.get + in_len); + u.get->oid = cpu_to_le32(oid); + u.get->len = cpu_to_le32(in_len); + u.get->offset = cpu_to_le32(20); + + retval = rndis_command(dev, u.header, CONTROL_BUFFER_SIZE); + if (unlikely(retval < 0)) { + dev_err(&intf->dev, "RNDIS_MSG_QUERY(0x%08x) failed, %d\n", + oid, retval); + return retval; + } + + off = le32_to_cpu(u.get_c->offset); + len = le32_to_cpu(u.get_c->len); + if (unlikely((8 + off + len) > CONTROL_BUFFER_SIZE)) + goto response_error; + + if (*reply_len != -1 && len != *reply_len) + goto response_error; + + *reply = (unsigned char *) &u.get_c->request_id + off; + *reply_len = len; + + return retval; + +response_error: + dev_err(&intf->dev, "RNDIS_MSG_QUERY(0x%08x) " + "invalid response - off %d len %d\n", + oid, off, len); + return -EDOM; +} + +/* same as usbnet_netdev_ops but MTU change not allowed */ +static const struct net_device_ops rndis_netdev_ops = { + .ndo_open = usbnet_open, + .ndo_stop = usbnet_stop, + .ndo_start_xmit = usbnet_start_xmit, + .ndo_tx_timeout = usbnet_tx_timeout, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +int +generic_rndis_bind(struct usbnet *dev, struct usb_interface *intf, int flags) +{ + int retval; + struct net_device *net = dev->net; + struct cdc_state *info = (void *) &dev->data; + union { + void *buf; + struct rndis_msg_hdr *header; + struct rndis_init *init; + struct rndis_init_c *init_c; + struct rndis_query *get; + struct rndis_query_c *get_c; + struct rndis_set *set; + struct rndis_set_c *set_c; + struct rndis_halt *halt; + } u; + u32 tmp; + __le32 phym_unspec, *phym; + int reply_len; + unsigned char *bp; + + /* we can't rely on i/o from stack working, or stack allocation */ + u.buf = kmalloc(CONTROL_BUFFER_SIZE, GFP_KERNEL); + if (!u.buf) + return -ENOMEM; + retval = usbnet_generic_cdc_bind(dev, intf); + if (retval < 0) + goto fail; + + u.init->msg_type = cpu_to_le32(RNDIS_MSG_INIT); + u.init->msg_len = cpu_to_le32(sizeof *u.init); + u.init->major_version = cpu_to_le32(1); + u.init->minor_version = cpu_to_le32(0); + + /* max transfer (in spec) is 0x4000 at full speed, but for + * TX we'll stick to one Ethernet packet plus RNDIS framing. + * For RX we handle drivers that zero-pad to end-of-packet. + * Don't let userspace change these settings. + * + * NOTE: there still seems to be wierdness here, as if we need + * to do some more things to make sure WinCE targets accept this. + * They default to jumbograms of 8KB or 16KB, which is absurd + * for such low data rates and which is also more than Linux + * can usually expect to allocate for SKB data... + */ + net->hard_header_len += sizeof (struct rndis_data_hdr); + dev->hard_mtu = net->mtu + net->hard_header_len; + + dev->maxpacket = usb_maxpacket(dev->udev, dev->out, 1); + if (dev->maxpacket == 0) { + netif_dbg(dev, probe, dev->net, + "dev->maxpacket can't be 0\n"); + retval = -EINVAL; + goto fail_and_release; + } + + dev->rx_urb_size = dev->hard_mtu + (dev->maxpacket + 1); + dev->rx_urb_size &= ~(dev->maxpacket - 1); + u.init->max_transfer_size = cpu_to_le32(dev->rx_urb_size); + + netdev_attach_ops(net, &rndis_netdev_ops); + + retval = rndis_command(dev, u.header, CONTROL_BUFFER_SIZE); + if (unlikely(retval < 0)) { + /* it might not even be an RNDIS device!! */ + dev_err(&intf->dev, "RNDIS init failed, %d\n", retval); + goto fail_and_release; + } + tmp = le32_to_cpu(u.init_c->max_transfer_size); + if (tmp < dev->hard_mtu) { + if (tmp <= net->hard_header_len) { + dev_err(&intf->dev, + "dev can't take %u byte packets (max %u)\n", + dev->hard_mtu, tmp); + retval = -EINVAL; + goto halt_fail_and_release; + } + dev_warn(&intf->dev, + "dev can't take %u byte packets (max %u), " + "adjusting MTU to %u\n", + dev->hard_mtu, tmp, tmp - net->hard_header_len); + dev->hard_mtu = tmp; + net->mtu = dev->hard_mtu - net->hard_header_len; + } + + /* REVISIT: peripheral "alignment" request is ignored ... */ + dev_dbg(&intf->dev, + "hard mtu %u (%u from dev), rx buflen %Zu, align %d\n", + dev->hard_mtu, tmp, dev->rx_urb_size, + 1 << le32_to_cpu(u.init_c->packet_alignment)); + + /* module has some device initialization code needs to be done right + * after RNDIS_INIT */ + if (dev->driver_info->early_init && + dev->driver_info->early_init(dev) != 0) + goto halt_fail_and_release; + + /* Check physical medium */ + phym = NULL; + reply_len = sizeof *phym; + retval = rndis_query(dev, intf, u.buf, + RNDIS_OID_GEN_PHYSICAL_MEDIUM, + 0, (void **) &phym, &reply_len); + if (retval != 0 || !phym) { + /* OID is optional so don't fail here. */ + phym_unspec = cpu_to_le32(RNDIS_PHYSICAL_MEDIUM_UNSPECIFIED); + phym = &phym_unspec; + } + if ((flags & FLAG_RNDIS_PHYM_WIRELESS) && + le32_to_cpup(phym) != RNDIS_PHYSICAL_MEDIUM_WIRELESS_LAN) { + netif_dbg(dev, probe, dev->net, + "driver requires wireless physical medium, but device is not\n"); + retval = -ENODEV; + goto halt_fail_and_release; + } + if ((flags & FLAG_RNDIS_PHYM_NOT_WIRELESS) && + le32_to_cpup(phym) == RNDIS_PHYSICAL_MEDIUM_WIRELESS_LAN) { + netif_dbg(dev, probe, dev->net, + "driver requires non-wireless physical medium, but device is wireless.\n"); + retval = -ENODEV; + goto halt_fail_and_release; + } + + /* Get designated host ethernet address */ + reply_len = ETH_ALEN; + retval = rndis_query(dev, intf, u.buf, + RNDIS_OID_802_3_PERMANENT_ADDRESS, + 48, (void **) &bp, &reply_len); + if (unlikely(retval< 0)) { + dev_err(&intf->dev, "rndis get ethaddr, %d\n", retval); + goto halt_fail_and_release; + } + memcpy(net->dev_addr, bp, ETH_ALEN); + + /* set a nonzero filter to enable data transfers */ + memset(u.set, 0, sizeof *u.set); + u.set->msg_type = cpu_to_le32(RNDIS_MSG_SET); + u.set->msg_len = cpu_to_le32(4 + sizeof *u.set); + u.set->oid = cpu_to_le32(RNDIS_OID_GEN_CURRENT_PACKET_FILTER); + u.set->len = cpu_to_le32(4); + u.set->offset = cpu_to_le32((sizeof *u.set) - 8); + *(__le32 *)(u.buf + sizeof *u.set) = cpu_to_le32(RNDIS_DEFAULT_FILTER); + + retval = rndis_command(dev, u.header, CONTROL_BUFFER_SIZE); + if (unlikely(retval < 0)) { + dev_err(&intf->dev, "rndis set packet filter, %d\n", retval); + goto halt_fail_and_release; + } + + retval = 0; + + kfree(u.buf); + return retval; + +halt_fail_and_release: + memset(u.halt, 0, sizeof *u.halt); + u.halt->msg_type = cpu_to_le32(RNDIS_MSG_HALT); + u.halt->msg_len = cpu_to_le32(sizeof *u.halt); + (void) rndis_command(dev, (void *)u.halt, CONTROL_BUFFER_SIZE); +fail_and_release: + usb_set_intfdata(info->data, NULL); + usb_driver_release_interface(driver_of(intf), info->data); + info->data = NULL; +fail: + kfree(u.buf); + return retval; +} +EXPORT_SYMBOL_GPL(generic_rndis_bind); + +static int rndis_bind(struct usbnet *dev, struct usb_interface *intf) +{ + return generic_rndis_bind(dev, intf, FLAG_RNDIS_PHYM_NOT_WIRELESS); +} + +void rndis_unbind(struct usbnet *dev, struct usb_interface *intf) +{ + struct rndis_halt *halt; + + /* try to clear any rndis state/activity (no i/o from stack!) */ + halt = kzalloc(CONTROL_BUFFER_SIZE, GFP_KERNEL); + if (halt) { + halt->msg_type = cpu_to_le32(RNDIS_MSG_HALT); + halt->msg_len = cpu_to_le32(sizeof *halt); + (void) rndis_command(dev, (void *)halt, CONTROL_BUFFER_SIZE); + kfree(halt); + } + + usbnet_cdc_unbind(dev, intf); +} +EXPORT_SYMBOL_GPL(rndis_unbind); + +/* + * DATA -- host must not write zlps + */ +int rndis_rx_fixup(struct usbnet *dev, struct sk_buff *skb) +{ + /* peripheral may have batched packets to us... */ + while (likely(skb->len)) { + struct rndis_data_hdr *hdr = (void *)skb->data; + struct sk_buff *skb2; + u32 msg_type, msg_len, data_offset, data_len; + + msg_type = le32_to_cpu(hdr->msg_type); + msg_len = le32_to_cpu(hdr->msg_len); + data_offset = le32_to_cpu(hdr->data_offset); + data_len = le32_to_cpu(hdr->data_len); + + /* don't choke if we see oob, per-packet data, etc */ + if (unlikely(msg_type != RNDIS_MSG_PACKET || skb->len < msg_len + || (data_offset + data_len + 8) > msg_len)) { + dev->net->stats.rx_frame_errors++; + netdev_dbg(dev->net, "bad rndis message %d/%d/%d/%d, len %d\n", + le32_to_cpu(hdr->msg_type), + msg_len, data_offset, data_len, skb->len); + return 0; + } + skb_pull(skb, 8 + data_offset); + + /* at most one packet left? */ + if (likely((data_len - skb->len) <= sizeof *hdr)) { + skb_trim(skb, data_len); + break; + } + + /* try to return all the packets in the batch */ + skb2 = skb_clone(skb, GFP_ATOMIC); + if (unlikely(!skb2)) + break; + skb_pull(skb, msg_len - sizeof *hdr); + skb_trim(skb2, data_len); + usbnet_skb_return(dev, skb2); + } + + /* caller will usbnet_skb_return the remaining packet */ + return 1; +} +EXPORT_SYMBOL_GPL(rndis_rx_fixup); + +struct sk_buff * +rndis_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) +{ + struct rndis_data_hdr *hdr; + struct sk_buff *skb2; + unsigned len = skb->len; + + if (likely(!skb_cloned(skb))) { + int room = skb_headroom(skb); + + /* enough head room as-is? */ + if (unlikely((sizeof *hdr) <= room)) + goto fill; + + /* enough room, but needs to be readjusted? */ + room += skb_tailroom(skb); + if (likely((sizeof *hdr) <= room)) { + skb->data = memmove(skb->head + sizeof *hdr, + skb->data, len); + skb_set_tail_pointer(skb, len); + goto fill; + } + } + + /* create a new skb, with the correct size (and tailpad) */ + skb2 = skb_copy_expand(skb, sizeof *hdr, 1, flags); + dev_kfree_skb_any(skb); + if (unlikely(!skb2)) + return skb2; + skb = skb2; + + /* fill out the RNDIS header. we won't bother trying to batch + * packets; Linux minimizes wasted bandwidth through tx queues. + */ +fill: + hdr = (void *) __skb_push(skb, sizeof *hdr); + memset(hdr, 0, sizeof *hdr); + hdr->msg_type = cpu_to_le32(RNDIS_MSG_PACKET); + hdr->msg_len = cpu_to_le32(skb->len); + hdr->data_offset = cpu_to_le32(sizeof(*hdr) - 8); + hdr->data_len = cpu_to_le32(len); + + /* FIXME make the last packet always be short ... */ + return skb; +} +EXPORT_SYMBOL_GPL(rndis_tx_fixup); + + +static const struct driver_info rndis_info = { + .description = "RNDIS device", + .flags = FLAG_ETHER | FLAG_POINTTOPOINT | FLAG_FRAMING_RN | FLAG_NO_SETINT, + .bind = rndis_bind, + .unbind = rndis_unbind, + .status = rndis_status, + .rx_fixup = rndis_rx_fixup, + .tx_fixup = rndis_tx_fixup, +}; + +static const struct driver_info rndis_poll_status_info = { + .description = "RNDIS device (poll status before control)", + .flags = FLAG_ETHER | FLAG_POINTTOPOINT | FLAG_FRAMING_RN | FLAG_NO_SETINT, + .data = RNDIS_DRIVER_DATA_POLL_STATUS, + .bind = rndis_bind, + .unbind = rndis_unbind, + .status = rndis_status, + .rx_fixup = rndis_rx_fixup, + .tx_fixup = rndis_tx_fixup, +}; + +/*-------------------------------------------------------------------------*/ + +static const struct usb_device_id products [] = { +{ + /* 2Wire HomePortal 1000SW */ + USB_DEVICE_AND_INTERFACE_INFO(0x1630, 0x0042, + USB_CLASS_COMM, 2 /* ACM */, 0x0ff), + .driver_info = (unsigned long) &rndis_poll_status_info, +}, { + /* RNDIS is MSFT's un-official variant of CDC ACM */ + USB_INTERFACE_INFO(USB_CLASS_COMM, 2 /* ACM */, 0x0ff), + .driver_info = (unsigned long) &rndis_info, +}, { + /* "ActiveSync" is an undocumented variant of RNDIS, used in WM5 */ + USB_INTERFACE_INFO(USB_CLASS_MISC, 1, 1), + .driver_info = (unsigned long) &rndis_poll_status_info, +}, { + /* RNDIS for tethering */ + USB_INTERFACE_INFO(USB_CLASS_WIRELESS_CONTROLLER, 1, 3), + .driver_info = (unsigned long) &rndis_info, +}, + { }, // END +}; +MODULE_DEVICE_TABLE(usb, products); + +static struct usb_driver rndis_driver = { + .name = "rndis_host", + .id_table = products, + .probe = usbnet_probe, + .disconnect = usbnet_disconnect, + .suspend = usbnet_suspend, + .resume = usbnet_resume, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +module_usb_driver(rndis_driver); + +MODULE_AUTHOR("David Brownell"); +MODULE_DESCRIPTION("USB Host side RNDIS driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c new file mode 100644 index 0000000..c7eef00 --- /dev/null +++ b/drivers/net/usb/usbnet.c @@ -0,0 +1,1979 @@ +/* + * USB Network driver infrastructure + * Copyright (C) 2000-2005 by David Brownell + * Copyright (C) 2003-2005 David Hollis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * This is a generic "USB networking" framework that works with several + * kinds of full and high speed networking devices: host-to-host cables, + * smart usb peripherals, and actual Ethernet adapters. + * + * These devices usually differ in terms of control protocols (if they + * even have one!) and sometimes they define new framing to wrap or batch + * Ethernet packets. Otherwise, they talk to USB pretty much the same, + * so interface (un)binding, endpoint I/O queues, fault handling, and other + * issues can usefully be addressed by this framework. + */ + +// #define DEBUG // error path messages, extra info +// #define VERBOSE // more; success messages + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_VERSION "22-Aug-2005" + + +/*-------------------------------------------------------------------------*/ + +/* + * Nineteen USB 1.1 max size bulk transactions per frame (ms), max. + * Several dozen bytes of IPv4 data can fit in two such transactions. + * One maximum size Ethernet packet takes twenty four of them. + * For high speed, each frame comfortably fits almost 36 max size + * Ethernet packets (so queues should be bigger). + * + * REVISIT qlens should be members of 'struct usbnet'; the goal is to + * let the USB host controller be busy for 5msec or more before an irq + * is required, under load. Jumbograms change the equation. + */ +#define RX_MAX_QUEUE_MEMORY (60 * 1518) +#define RX_QLEN(dev) (((dev)->udev->speed == USB_SPEED_HIGH) ? \ + (RX_MAX_QUEUE_MEMORY/(dev)->rx_urb_size) : 4) +#define TX_QLEN(dev) (((dev)->udev->speed == USB_SPEED_HIGH) ? \ + (RX_MAX_QUEUE_MEMORY/(dev)->hard_mtu) : 4) + +// reawaken network queue this soon after stopping; else watchdog barks +#define TX_TIMEOUT_JIFFIES (5*HZ) + +// throttle rx/tx briefly after some faults, so khubd might disconnect() +// us (it polls at HZ/4 usually) before we report too many false errors. +#define THROTTLE_JIFFIES (HZ/8) + +// between wakeups +#define UNLINK_TIMEOUT_MS 3 + +/*-------------------------------------------------------------------------*/ + +// randomly generated ethernet address +static u8 node_id [ETH_ALEN]; + +static const char driver_name [] = "usbnet"; + +/* use ethtool to change the level for any given device */ +static int msg_level = -1; +module_param (msg_level, int, 0); +MODULE_PARM_DESC (msg_level, "Override default message level"); + +/*-------------------------------------------------------------------------*/ + +/* handles CDC Ethernet and many other network "bulk data" interfaces */ +int usbnet_get_endpoints(struct usbnet *dev, struct usb_interface *intf) +{ + int tmp; + struct usb_host_interface *alt = NULL; + struct usb_host_endpoint *in = NULL, *out = NULL; + struct usb_host_endpoint *status = NULL; + + for (tmp = 0; tmp < intf->num_altsetting; tmp++) { + unsigned ep; + + in = out = status = NULL; + alt = intf->altsetting + tmp; + + /* take the first altsetting with in-bulk + out-bulk; + * remember any status endpoint, just in case; + * ignore other endpoints and altsettings. + */ + for (ep = 0; ep < alt->desc.bNumEndpoints; ep++) { + struct usb_host_endpoint *e; + int intr = 0; + + e = alt->endpoint + ep; + switch (e->desc.bmAttributes) { + case USB_ENDPOINT_XFER_INT: + if (!usb_endpoint_dir_in(&e->desc)) + continue; + intr = 1; + /* FALLTHROUGH */ + case USB_ENDPOINT_XFER_BULK: + break; + default: + continue; + } + if (usb_endpoint_dir_in(&e->desc)) { + if (!intr && !in) + in = e; + else if (intr && !status) + status = e; + } else { + if (!out) + out = e; + } + } + if (in && out) + break; + } + if (!alt || !in || !out) + return -EINVAL; + + if (alt->desc.bAlternateSetting != 0 || + !(dev->driver_info->flags & FLAG_NO_SETINT)) { + tmp = usb_set_interface (dev->udev, alt->desc.bInterfaceNumber, + alt->desc.bAlternateSetting); + if (tmp < 0) + return tmp; + } + + dev->in = usb_rcvbulkpipe (dev->udev, + in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + dev->out = usb_sndbulkpipe (dev->udev, + out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + dev->status = status; + return 0; +} +EXPORT_SYMBOL_GPL(usbnet_get_endpoints); + +int usbnet_get_ethernet_addr(struct usbnet *dev, int iMACAddress) +{ + int tmp, i; + unsigned char buf [13]; + + tmp = usb_string(dev->udev, iMACAddress, buf, sizeof buf); + if (tmp != 12) { + dev_dbg(&dev->udev->dev, + "bad MAC string %d fetch, %d\n", iMACAddress, tmp); + if (tmp >= 0) + tmp = -EINVAL; + return tmp; + } + for (i = tmp = 0; i < 6; i++, tmp += 2) + dev->net->dev_addr [i] = + (hex_to_bin(buf[tmp]) << 4) + hex_to_bin(buf[tmp + 1]); + return 0; +} +EXPORT_SYMBOL_GPL(usbnet_get_ethernet_addr); + +static void intr_complete (struct urb *urb) +{ + struct usbnet *dev = urb->context; + int status = urb->status; + + switch (status) { + /* success */ + case 0: + dev->driver_info->status(dev, urb); + break; + + /* software-driven interface shutdown */ + case -ENOENT: /* urb killed */ + case -ESHUTDOWN: /* hardware gone */ + netif_dbg(dev, ifdown, dev->net, + "intr shutdown, code %d\n", status); + return; + + /* NOTE: not throttling like RX/TX, since this endpoint + * already polls infrequently + */ + default: + netdev_dbg(dev->net, "intr status %d\n", status); + break; + } + + if (!netif_running (dev->net)) + return; + + status = usb_submit_urb (urb, GFP_ATOMIC); + if (status != 0) + netif_err(dev, timer, dev->net, + "intr resubmit --> %d\n", status); +} + +static int init_status (struct usbnet *dev, struct usb_interface *intf) +{ + char *buf = NULL; + unsigned pipe = 0; + unsigned maxp; + unsigned period; + + if (!dev->driver_info->status) + return 0; + + pipe = usb_rcvintpipe (dev->udev, + dev->status->desc.bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK); + maxp = usb_maxpacket (dev->udev, pipe, 0); + + /* avoid 1 msec chatter: min 8 msec poll rate */ + period = max ((int) dev->status->desc.bInterval, + (dev->udev->speed == USB_SPEED_HIGH) ? 7 : 3); + + buf = kmalloc (maxp, GFP_KERNEL); + if (buf) { + dev->interrupt = usb_alloc_urb (0, GFP_KERNEL); + if (!dev->interrupt) { + kfree (buf); + return -ENOMEM; + } else { + usb_fill_int_urb(dev->interrupt, dev->udev, pipe, + buf, maxp, intr_complete, dev, period); + dev->interrupt->transfer_flags |= URB_FREE_BUFFER; + dev_dbg(&intf->dev, + "status ep%din, %d bytes period %d\n", + usb_pipeendpoint(pipe), maxp, period); + } + } + return 0; +} + +/* Submit the interrupt URB if not previously submitted, increasing refcount */ +int usbnet_status_start(struct usbnet *dev, gfp_t mem_flags) +{ + int ret = 0; + + WARN_ON_ONCE(dev->interrupt == NULL); + if (dev->interrupt) { + mutex_lock(&dev->interrupt_mutex); + + if (++dev->interrupt_count == 1) + ret = usb_submit_urb(dev->interrupt, mem_flags); + + dev_dbg(&dev->udev->dev, "incremented interrupt URB count to %d\n", + dev->interrupt_count); + mutex_unlock(&dev->interrupt_mutex); + } + return ret; +} +EXPORT_SYMBOL_GPL(usbnet_status_start); + +/* For resume; submit interrupt URB if previously submitted */ +static int __usbnet_status_start_force(struct usbnet *dev, gfp_t mem_flags) +{ + int ret = 0; + + mutex_lock(&dev->interrupt_mutex); + if (dev->interrupt_count) { + ret = usb_submit_urb(dev->interrupt, mem_flags); + dev_dbg(&dev->udev->dev, + "submitted interrupt URB for resume\n"); + } + mutex_unlock(&dev->interrupt_mutex); + return ret; +} + +/* Kill the interrupt URB if all submitters want it killed */ +void usbnet_status_stop(struct usbnet *dev) +{ + if (dev->interrupt) { + mutex_lock(&dev->interrupt_mutex); + WARN_ON(dev->interrupt_count == 0); + + if (dev->interrupt_count && --dev->interrupt_count == 0) + usb_kill_urb(dev->interrupt); + + dev_dbg(&dev->udev->dev, + "decremented interrupt URB count to %d\n", + dev->interrupt_count); + mutex_unlock(&dev->interrupt_mutex); + } +} +EXPORT_SYMBOL_GPL(usbnet_status_stop); + +/* For suspend; always kill interrupt URB */ +static void __usbnet_status_stop_force(struct usbnet *dev) +{ + if (dev->interrupt) { + mutex_lock(&dev->interrupt_mutex); + usb_kill_urb(dev->interrupt); + dev_dbg(&dev->udev->dev, "killed interrupt URB for suspend\n"); + mutex_unlock(&dev->interrupt_mutex); + } +} + +/* Passes this packet up the stack, updating its accounting. + * Some link protocols batch packets, so their rx_fixup paths + * can return clones as well as just modify the original skb. + */ +void usbnet_skb_return (struct usbnet *dev, struct sk_buff *skb) +{ + int status; + + if (test_bit(EVENT_RX_PAUSED, &dev->flags)) { + skb_queue_tail(&dev->rxq_pause, skb); + return; + } + + skb->protocol = eth_type_trans (skb, dev->net); + dev->net->stats.rx_packets++; + dev->net->stats.rx_bytes += skb->len; + + netif_dbg(dev, rx_status, dev->net, "< rx, len %zu, type 0x%x\n", + skb->len + sizeof (struct ethhdr), skb->protocol); + memset (skb->cb, 0, sizeof (struct skb_data)); + + if (skb_defer_rx_timestamp(skb)) + return; + + status = netif_rx (skb); + if (status != NET_RX_SUCCESS) + netif_dbg(dev, rx_err, dev->net, + "netif_rx status %d\n", status); +} +EXPORT_SYMBOL_GPL(usbnet_skb_return); + + +/*------------------------------------------------------------------------- + * + * Network Device Driver (peer link to "Host Device", from USB host) + * + *-------------------------------------------------------------------------*/ + +int usbnet_change_mtu (struct net_device *net, int new_mtu) +{ + struct usbnet *dev = netdev_priv(net); + int ll_mtu = new_mtu + net->hard_header_len; + int old_hard_mtu = dev->hard_mtu; + int old_rx_urb_size = dev->rx_urb_size; + + if (new_mtu <= 0) + return -EINVAL; + // no second zero-length packet read wanted after mtu-sized packets + if ((ll_mtu % dev->maxpacket) == 0) + return -EDOM; + net->mtu = new_mtu; + + dev->hard_mtu = net->mtu + net->hard_header_len; + if (dev->rx_urb_size == old_hard_mtu) { + dev->rx_urb_size = dev->hard_mtu; + if (dev->rx_urb_size > old_rx_urb_size) + usbnet_unlink_rx_urbs(dev); + } + + return 0; +} +EXPORT_SYMBOL_GPL(usbnet_change_mtu); + +/* The caller must hold list->lock */ +static void __usbnet_queue_skb(struct sk_buff_head *list, + struct sk_buff *newsk, enum skb_state state) +{ + struct skb_data *entry = (struct skb_data *) newsk->cb; + + __skb_queue_tail(list, newsk); + entry->state = state; +} + +/*-------------------------------------------------------------------------*/ + +/* some LK 2.4 HCDs oopsed if we freed or resubmitted urbs from + * completion callbacks. 2.5 should have fixed those bugs... + */ + +static enum skb_state defer_bh(struct usbnet *dev, struct sk_buff *skb, + struct sk_buff_head *list, enum skb_state state) +{ + unsigned long flags; + enum skb_state old_state; + struct skb_data *entry = (struct skb_data *) skb->cb; + + spin_lock_irqsave(&list->lock, flags); + old_state = entry->state; + entry->state = state; + __skb_unlink(skb, list); + spin_unlock(&list->lock); + spin_lock(&dev->done.lock); + __skb_queue_tail(&dev->done, skb); + if (dev->done.qlen == 1) + tasklet_schedule(&dev->bh); + spin_unlock_irqrestore(&dev->done.lock, flags); + return old_state; +} + +/* some work can't be done in tasklets, so we use keventd + * + * NOTE: annoying asymmetry: if it's active, schedule_work() fails, + * but tasklet_schedule() doesn't. hope the failure is rare. + */ +void usbnet_defer_kevent (struct usbnet *dev, int work) +{ + set_bit (work, &dev->flags); + if (!schedule_work (&dev->kevent)) { + if (net_ratelimit()) + netdev_err(dev->net, "kevent %d may have been dropped\n", work); + } else { + netdev_dbg(dev->net, "kevent %d scheduled\n", work); + } +} +EXPORT_SYMBOL_GPL(usbnet_defer_kevent); + +/*-------------------------------------------------------------------------*/ + +static void rx_complete (struct urb *urb); + +static int rx_submit (struct usbnet *dev, struct urb *urb, gfp_t flags) +{ + struct sk_buff *skb; + struct skb_data *entry; + int retval = 0; + unsigned long lockflags; + size_t size = dev->rx_urb_size; + + /* prevent rx skb allocation when error ratio is high */ + if (test_bit(EVENT_RX_KILL, &dev->flags)) { + usb_free_urb(urb); + return -ENOLINK; + } + + skb = __netdev_alloc_skb_ip_align(dev->net, size, flags); + if (!skb) { + netif_dbg(dev, rx_err, dev->net, "no rx skb\n"); + usbnet_defer_kevent (dev, EVENT_RX_MEMORY); + usb_free_urb (urb); + return -ENOMEM; + } + + entry = (struct skb_data *) skb->cb; + entry->urb = urb; + entry->dev = dev; + entry->length = 0; + + usb_fill_bulk_urb (urb, dev->udev, dev->in, + skb->data, size, rx_complete, skb); + + spin_lock_irqsave (&dev->rxq.lock, lockflags); + + if (netif_running (dev->net) && + netif_device_present (dev->net) && + !test_bit (EVENT_RX_HALT, &dev->flags) && + !test_bit (EVENT_DEV_ASLEEP, &dev->flags)) { + switch (retval = usb_submit_urb (urb, GFP_ATOMIC)) { + case -EPIPE: + usbnet_defer_kevent (dev, EVENT_RX_HALT); + break; + case -ENOMEM: + usbnet_defer_kevent (dev, EVENT_RX_MEMORY); + break; + case -ENODEV: + netif_dbg(dev, ifdown, dev->net, "device gone\n"); + netif_device_detach (dev->net); + break; + case -EHOSTUNREACH: + retval = -ENOLINK; + break; + default: + netif_dbg(dev, rx_err, dev->net, + "rx submit, %d\n", retval); + tasklet_schedule (&dev->bh); + break; + case 0: + __usbnet_queue_skb(&dev->rxq, skb, rx_start); + } + } else { + netif_dbg(dev, ifdown, dev->net, "rx: stopped\n"); + retval = -ENOLINK; + } + spin_unlock_irqrestore (&dev->rxq.lock, lockflags); + if (retval) { + dev_kfree_skb_any (skb); + usb_free_urb (urb); + } + return retval; +} + + +/*-------------------------------------------------------------------------*/ + +static inline void rx_process (struct usbnet *dev, struct sk_buff *skb) +{ + if (dev->driver_info->rx_fixup && + !dev->driver_info->rx_fixup (dev, skb)) { + /* With RX_ASSEMBLE, rx_fixup() must update counters */ + if (!(dev->driver_info->flags & FLAG_RX_ASSEMBLE)) + dev->net->stats.rx_errors++; + goto done; + } + // else network stack removes extra byte if we forced a short packet + + if (skb->len) { + /* all data was already cloned from skb inside the driver */ + if (dev->driver_info->flags & FLAG_MULTI_PACKET) + dev_kfree_skb_any(skb); + else + usbnet_skb_return(dev, skb); + return; + } + + netif_dbg(dev, rx_err, dev->net, "drop\n"); + dev->net->stats.rx_errors++; +done: + skb_queue_tail(&dev->done, skb); +} + +/*-------------------------------------------------------------------------*/ + +static void rx_complete (struct urb *urb) +{ + struct sk_buff *skb = (struct sk_buff *) urb->context; + struct skb_data *entry = (struct skb_data *) skb->cb; + struct usbnet *dev = entry->dev; + int urb_status = urb->status; + enum skb_state state; + + skb_put (skb, urb->actual_length); + state = rx_done; + entry->urb = NULL; + + switch (urb_status) { + /* success */ + case 0: + if (skb->len < dev->net->hard_header_len) { + state = rx_cleanup; + dev->net->stats.rx_errors++; + dev->net->stats.rx_length_errors++; + netif_dbg(dev, rx_err, dev->net, + "rx length %d\n", skb->len); + } + break; + + /* stalls need manual reset. this is rare ... except that + * when going through USB 2.0 TTs, unplug appears this way. + * we avoid the highspeed version of the ETIMEDOUT/EILSEQ + * storm, recovering as needed. + */ + case -EPIPE: + dev->net->stats.rx_errors++; + usbnet_defer_kevent (dev, EVENT_RX_HALT); + // FALLTHROUGH + + /* software-driven interface shutdown */ + case -ECONNRESET: /* async unlink */ + case -ESHUTDOWN: /* hardware gone */ + netif_dbg(dev, ifdown, dev->net, + "rx shutdown, code %d\n", urb_status); + goto block; + + /* we get controller i/o faults during khubd disconnect() delays. + * throttle down resubmits, to avoid log floods; just temporarily, + * so we still recover when the fault isn't a khubd delay. + */ + case -EPROTO: + case -ETIME: + case -EILSEQ: + dev->net->stats.rx_errors++; + if (!timer_pending (&dev->delay)) { + mod_timer (&dev->delay, jiffies + THROTTLE_JIFFIES); + netif_dbg(dev, link, dev->net, + "rx throttle %d\n", urb_status); + } +block: + state = rx_cleanup; + entry->urb = urb; + urb = NULL; + break; + + /* data overrun ... flush fifo? */ + case -EOVERFLOW: + dev->net->stats.rx_over_errors++; + // FALLTHROUGH + + default: + state = rx_cleanup; + dev->net->stats.rx_errors++; + netif_dbg(dev, rx_err, dev->net, "rx status %d\n", urb_status); + break; + } + + /* stop rx if packet error rate is high */ + if (++dev->pkt_cnt > 30) { + dev->pkt_cnt = 0; + dev->pkt_err = 0; + } else { + if (state == rx_cleanup) + dev->pkt_err++; + if (dev->pkt_err > 20) + set_bit(EVENT_RX_KILL, &dev->flags); + } + + state = defer_bh(dev, skb, &dev->rxq, state); + + if (urb) { + if (netif_running (dev->net) && + !test_bit (EVENT_RX_HALT, &dev->flags) && + state != unlink_start) { + rx_submit (dev, urb, GFP_ATOMIC); + usb_mark_last_busy(dev->udev); + return; + } + usb_free_urb (urb); + } + netif_dbg(dev, rx_err, dev->net, "no read resubmitted\n"); +} + +/*-------------------------------------------------------------------------*/ +void usbnet_pause_rx(struct usbnet *dev) +{ + set_bit(EVENT_RX_PAUSED, &dev->flags); + + netif_dbg(dev, rx_status, dev->net, "paused rx queue enabled\n"); +} +EXPORT_SYMBOL_GPL(usbnet_pause_rx); + +void usbnet_resume_rx(struct usbnet *dev) +{ + struct sk_buff *skb; + int num = 0; + + clear_bit(EVENT_RX_PAUSED, &dev->flags); + + while ((skb = skb_dequeue(&dev->rxq_pause)) != NULL) { + usbnet_skb_return(dev, skb); + num++; + } + + tasklet_schedule(&dev->bh); + + netif_dbg(dev, rx_status, dev->net, + "paused rx queue disabled, %d skbs requeued\n", num); +} +EXPORT_SYMBOL_GPL(usbnet_resume_rx); + +void usbnet_purge_paused_rxq(struct usbnet *dev) +{ + skb_queue_purge(&dev->rxq_pause); +} +EXPORT_SYMBOL_GPL(usbnet_purge_paused_rxq); + +/*-------------------------------------------------------------------------*/ + +// unlink pending rx/tx; completion handlers do all other cleanup + +static int unlink_urbs (struct usbnet *dev, struct sk_buff_head *q) +{ + unsigned long flags; + struct sk_buff *skb; + int count = 0; + + spin_lock_irqsave (&q->lock, flags); + while (!skb_queue_empty(q)) { + struct skb_data *entry; + struct urb *urb; + int retval; + + skb_queue_walk(q, skb) { + entry = (struct skb_data *) skb->cb; + if (entry->state != unlink_start) + goto found; + } + break; +found: + entry->state = unlink_start; + urb = entry->urb; + + /* + * Get reference count of the URB to avoid it to be + * freed during usb_unlink_urb, which may trigger + * use-after-free problem inside usb_unlink_urb since + * usb_unlink_urb is always racing with .complete + * handler(include defer_bh). + */ + usb_get_urb(urb); + spin_unlock_irqrestore(&q->lock, flags); + // during some PM-driven resume scenarios, + // these (async) unlinks complete immediately + retval = usb_unlink_urb (urb); + if (retval != -EINPROGRESS && retval != 0) + netdev_dbg(dev->net, "unlink urb err, %d\n", retval); + else + count++; + usb_put_urb(urb); + spin_lock_irqsave(&q->lock, flags); + } + spin_unlock_irqrestore (&q->lock, flags); + return count; +} + +// Flush all pending rx urbs +// minidrivers may need to do this when the MTU changes + +void usbnet_unlink_rx_urbs(struct usbnet *dev) +{ + if (netif_running(dev->net)) { + (void) unlink_urbs (dev, &dev->rxq); + tasklet_schedule(&dev->bh); + } +} +EXPORT_SYMBOL_GPL(usbnet_unlink_rx_urbs); + +/*-------------------------------------------------------------------------*/ + +// precondition: never called in_interrupt +static void usbnet_terminate_urbs(struct usbnet *dev) +{ + DECLARE_WAIT_QUEUE_HEAD_ONSTACK(unlink_wakeup); + DECLARE_WAITQUEUE(wait, current); + int temp; + + /* ensure there are no more active urbs */ + add_wait_queue(&unlink_wakeup, &wait); + set_current_state(TASK_UNINTERRUPTIBLE); + dev->wait = &unlink_wakeup; + temp = unlink_urbs(dev, &dev->txq) + + unlink_urbs(dev, &dev->rxq); + + /* maybe wait for deletions to finish. */ + while (!skb_queue_empty(&dev->rxq) + && !skb_queue_empty(&dev->txq) + && !skb_queue_empty(&dev->done)) { + schedule_timeout(msecs_to_jiffies(UNLINK_TIMEOUT_MS)); + set_current_state(TASK_UNINTERRUPTIBLE); + netif_dbg(dev, ifdown, dev->net, + "waited for %d urb completions\n", temp); + } + set_current_state(TASK_RUNNING); + dev->wait = NULL; + remove_wait_queue(&unlink_wakeup, &wait); +} + +int usbnet_stop (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + struct driver_info *info = dev->driver_info; + int retval; + + clear_bit(EVENT_DEV_OPEN, &dev->flags); + netif_stop_queue (net); + + netif_info(dev, ifdown, dev->net, + "stop stats: rx/tx %lu/%lu, errs %lu/%lu\n", + net->stats.rx_packets, net->stats.tx_packets, + net->stats.rx_errors, net->stats.tx_errors); + + /* allow minidriver to stop correctly (wireless devices to turn off + * radio etc) */ + if (info->stop) { + retval = info->stop(dev); + if (retval < 0) + netif_info(dev, ifdown, dev->net, + "stop fail (%d) usbnet usb-%s-%s, %s\n", + retval, + dev->udev->bus->bus_name, dev->udev->devpath, + info->description); + } + + if (!(info->flags & FLAG_AVOID_UNLINK_URBS)) + usbnet_terminate_urbs(dev); + + usbnet_status_stop(dev); + + usbnet_purge_paused_rxq(dev); + + /* deferred work (task, timer, softirq) must also stop. + * can't flush_scheduled_work() until we drop rtnl (later), + * else workers could deadlock; so make workers a NOP. + */ + dev->flags = 0; + del_timer_sync (&dev->delay); + tasklet_kill (&dev->bh); + if (info->manage_power && + !test_and_clear_bit(EVENT_NO_RUNTIME_PM, &dev->flags)) + info->manage_power(dev, 0); + else + usb_autopm_put_interface(dev->intf); + + return 0; +} +EXPORT_SYMBOL_GPL(usbnet_stop); + +/*-------------------------------------------------------------------------*/ + +// posts reads, and enables write queuing + +// precondition: never called in_interrupt + +int usbnet_open (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + int retval; + struct driver_info *info = dev->driver_info; + + if ((retval = usb_autopm_get_interface(dev->intf)) < 0) { + netif_info(dev, ifup, dev->net, + "resumption fail (%d) usbnet usb-%s-%s, %s\n", + retval, + dev->udev->bus->bus_name, + dev->udev->devpath, + info->description); + goto done_nopm; + } + + // put into "known safe" state + if (info->reset && (retval = info->reset (dev)) < 0) { + netif_info(dev, ifup, dev->net, + "open reset fail (%d) usbnet usb-%s-%s, %s\n", + retval, + dev->udev->bus->bus_name, + dev->udev->devpath, + info->description); + goto done; + } + + // insist peer be connected + if (info->check_connect && (retval = info->check_connect (dev)) < 0) { + netif_dbg(dev, ifup, dev->net, "can't open; %d\n", retval); + goto done; + } + + /* start any status interrupt transfer */ + if (dev->interrupt) { + retval = usbnet_status_start(dev, GFP_KERNEL); + if (retval < 0) { + netif_err(dev, ifup, dev->net, + "intr submit %d\n", retval); + goto done; + } + } + + set_bit(EVENT_DEV_OPEN, &dev->flags); + netif_start_queue (net); + netif_info(dev, ifup, dev->net, + "open: enable queueing (rx %d, tx %d) mtu %d %s framing\n", + (int)RX_QLEN(dev), (int)TX_QLEN(dev), + dev->net->mtu, + (dev->driver_info->flags & FLAG_FRAMING_NC) ? "NetChip" : + (dev->driver_info->flags & FLAG_FRAMING_GL) ? "GeneSys" : + (dev->driver_info->flags & FLAG_FRAMING_Z) ? "Zaurus" : + (dev->driver_info->flags & FLAG_FRAMING_RN) ? "RNDIS" : + (dev->driver_info->flags & FLAG_FRAMING_AX) ? "ASIX" : + "simple"); + + /* reset rx error state */ + dev->pkt_cnt = 0; + dev->pkt_err = 0; + clear_bit(EVENT_RX_KILL, &dev->flags); + + // delay posting reads until we're fully open + tasklet_schedule (&dev->bh); + if (info->manage_power) { + retval = info->manage_power(dev, 1); + if (retval < 0) { + retval = 0; + set_bit(EVENT_NO_RUNTIME_PM, &dev->flags); + } else { + usb_autopm_put_interface(dev->intf); + } + } + return retval; +done: + usb_autopm_put_interface(dev->intf); +done_nopm: + return retval; +} +EXPORT_SYMBOL_GPL(usbnet_open); + +/*-------------------------------------------------------------------------*/ + +/* ethtool methods; minidrivers may need to add some more, but + * they'll probably want to use this base set. + */ + +int usbnet_get_settings (struct net_device *net, struct ethtool_cmd *cmd) +{ + struct usbnet *dev = netdev_priv(net); + + if (!dev->mii.mdio_read) + return -EOPNOTSUPP; + + return mii_ethtool_gset(&dev->mii, cmd); +} +EXPORT_SYMBOL_GPL(usbnet_get_settings); + +int usbnet_set_settings (struct net_device *net, struct ethtool_cmd *cmd) +{ + struct usbnet *dev = netdev_priv(net); + int retval; + + if (!dev->mii.mdio_write) + return -EOPNOTSUPP; + + retval = mii_ethtool_sset(&dev->mii, cmd); + + /* link speed/duplex might have changed */ + if (dev->driver_info->link_reset) + dev->driver_info->link_reset(dev); + + return retval; + +} +EXPORT_SYMBOL_GPL(usbnet_set_settings); + +u32 usbnet_get_link (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + + /* If a check_connect is defined, return its result */ + if (dev->driver_info->check_connect) + return dev->driver_info->check_connect (dev) == 0; + + /* if the device has mii operations, use those */ + if (dev->mii.mdio_read) + return mii_link_ok(&dev->mii); + + /* Otherwise, dtrt for drivers calling netif_carrier_{on,off} */ + return ethtool_op_get_link(net); +} +EXPORT_SYMBOL_GPL(usbnet_get_link); + +int usbnet_nway_reset(struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + + if (!dev->mii.mdio_write) + return -EOPNOTSUPP; + + return mii_nway_restart(&dev->mii); +} +EXPORT_SYMBOL_GPL(usbnet_nway_reset); + +void usbnet_get_drvinfo (struct net_device *net, struct ethtool_drvinfo *info) +{ + struct usbnet *dev = netdev_priv(net); + + strlcpy (info->driver, dev->driver_name, sizeof info->driver); + strlcpy (info->version, DRIVER_VERSION, sizeof info->version); + strlcpy (info->fw_version, dev->driver_info->description, + sizeof info->fw_version); + usb_make_path (dev->udev, info->bus_info, sizeof info->bus_info); +} +EXPORT_SYMBOL_GPL(usbnet_get_drvinfo); + +u32 usbnet_get_msglevel (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + + return dev->msg_enable; +} +EXPORT_SYMBOL_GPL(usbnet_get_msglevel); + +void usbnet_set_msglevel (struct net_device *net, u32 level) +{ + struct usbnet *dev = netdev_priv(net); + + dev->msg_enable = level; +} +EXPORT_SYMBOL_GPL(usbnet_set_msglevel); + +/* drivers may override default ethtool_ops in their bind() routine */ +static const struct ethtool_ops usbnet_ethtool_ops = { + .get_settings = usbnet_get_settings, + .set_settings = usbnet_set_settings, + .get_link = usbnet_get_link, + .nway_reset = usbnet_nway_reset, + .get_drvinfo = usbnet_get_drvinfo, + .get_msglevel = usbnet_get_msglevel, + .set_msglevel = usbnet_set_msglevel, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .get_ts_info = ethtool_op_get_ts_info, +#endif +}; + +/*-------------------------------------------------------------------------*/ + +static void __handle_link_change(struct usbnet *dev) +{ + if (!test_bit(EVENT_DEV_OPEN, &dev->flags)) + return; + + if (!netif_carrier_ok(dev->net)) { + /* kill URBs for reading packets to save bus bandwidth */ + unlink_urbs(dev, &dev->rxq); + + /* + * tx_timeout will unlink URBs for sending packets and + * tx queue is stopped by netcore after link becomes off + */ + } else { + /* submitting URBs for reading packets */ + tasklet_schedule(&dev->bh); + } + + clear_bit(EVENT_LINK_CHANGE, &dev->flags); +} + +/* work that cannot be done in interrupt context uses keventd. + * + * NOTE: with 2.5 we could do more of this using completion callbacks, + * especially now that control transfers can be queued. + */ +static void +kevent (struct work_struct *work) +{ + struct usbnet *dev = + container_of(work, struct usbnet, kevent); + int status; + + /* usb_clear_halt() needs a thread context */ + if (test_bit (EVENT_TX_HALT, &dev->flags)) { + unlink_urbs (dev, &dev->txq); + status = usb_autopm_get_interface(dev->intf); + if (status < 0) + goto fail_pipe; + status = usb_clear_halt (dev->udev, dev->out); + usb_autopm_put_interface(dev->intf); + if (status < 0 && + status != -EPIPE && + status != -ESHUTDOWN) { + if (netif_msg_tx_err (dev)) +fail_pipe: + netdev_err(dev->net, "can't clear tx halt, status %d\n", + status); + } else { + clear_bit (EVENT_TX_HALT, &dev->flags); + if (status != -ESHUTDOWN) + netif_wake_queue (dev->net); + } + } + if (test_bit (EVENT_RX_HALT, &dev->flags)) { + unlink_urbs (dev, &dev->rxq); + status = usb_autopm_get_interface(dev->intf); + if (status < 0) + goto fail_halt; + status = usb_clear_halt (dev->udev, dev->in); + usb_autopm_put_interface(dev->intf); + if (status < 0 && + status != -EPIPE && + status != -ESHUTDOWN) { + if (netif_msg_rx_err (dev)) +fail_halt: + netdev_err(dev->net, "can't clear rx halt, status %d\n", + status); + } else { + clear_bit (EVENT_RX_HALT, &dev->flags); + tasklet_schedule (&dev->bh); + } + } + + /* tasklet could resubmit itself forever if memory is tight */ + if (test_bit (EVENT_RX_MEMORY, &dev->flags)) { + struct urb *urb = NULL; + int resched = 1; + + if (netif_running (dev->net)) + urb = usb_alloc_urb (0, GFP_KERNEL); + else + clear_bit (EVENT_RX_MEMORY, &dev->flags); + if (urb != NULL) { + clear_bit (EVENT_RX_MEMORY, &dev->flags); + status = usb_autopm_get_interface(dev->intf); + if (status < 0) { + usb_free_urb(urb); + goto fail_lowmem; + } + if (rx_submit (dev, urb, GFP_KERNEL) == -ENOLINK) + resched = 0; + usb_autopm_put_interface(dev->intf); +fail_lowmem: + if (resched) + tasklet_schedule (&dev->bh); + } + } + + if (test_bit (EVENT_LINK_RESET, &dev->flags)) { + struct driver_info *info = dev->driver_info; + int retval = 0; + + clear_bit (EVENT_LINK_RESET, &dev->flags); + status = usb_autopm_get_interface(dev->intf); + if (status < 0) + goto skip_reset; + if(info->link_reset && (retval = info->link_reset(dev)) < 0) { + usb_autopm_put_interface(dev->intf); +skip_reset: + netdev_info(dev->net, "link reset failed (%d) usbnet usb-%s-%s, %s\n", + retval, + dev->udev->bus->bus_name, + dev->udev->devpath, + info->description); + } else { + usb_autopm_put_interface(dev->intf); + } + + /* handle link change from link resetting */ + __handle_link_change(dev); + } + + if (test_bit (EVENT_LINK_CHANGE, &dev->flags)) + __handle_link_change(dev); + + if (dev->flags) + netdev_dbg(dev->net, "kevent done, flags = 0x%lx\n", dev->flags); +} + +/*-------------------------------------------------------------------------*/ + +static void tx_complete (struct urb *urb) +{ + struct sk_buff *skb = (struct sk_buff *) urb->context; + struct skb_data *entry = (struct skb_data *) skb->cb; + struct usbnet *dev = entry->dev; + + if (urb->status == 0) { + if (!(dev->driver_info->flags & FLAG_MULTI_PACKET)) + dev->net->stats.tx_packets++; + dev->net->stats.tx_bytes += entry->length; + } else { + dev->net->stats.tx_errors++; + + switch (urb->status) { + case -EPIPE: + usbnet_defer_kevent (dev, EVENT_TX_HALT); + break; + + /* software-driven interface shutdown */ + case -ECONNRESET: // async unlink + case -ESHUTDOWN: // hardware gone + break; + + // like rx, tx gets controller i/o faults during khubd delays + // and so it uses the same throttling mechanism. + case -EPROTO: + case -ETIME: + case -EILSEQ: + usb_mark_last_busy(dev->udev); + if (!timer_pending (&dev->delay)) { + mod_timer (&dev->delay, + jiffies + THROTTLE_JIFFIES); + netif_dbg(dev, link, dev->net, + "tx throttle %d\n", urb->status); + } + netif_stop_queue (dev->net); + break; + default: + netif_dbg(dev, tx_err, dev->net, + "tx err %d\n", entry->urb->status); + break; + } + } + + usb_autopm_put_interface_async(dev->intf); + (void) defer_bh(dev, skb, &dev->txq, tx_done); +} + +/*-------------------------------------------------------------------------*/ + +void usbnet_tx_timeout (struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + + unlink_urbs (dev, &dev->txq); + tasklet_schedule (&dev->bh); + + // FIXME: device recovery -- reset? +} +EXPORT_SYMBOL_GPL(usbnet_tx_timeout); + +/*-------------------------------------------------------------------------*/ + +netdev_tx_t usbnet_start_xmit (struct sk_buff *skb, + struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + int length; + struct urb *urb = NULL; + struct skb_data *entry; + struct driver_info *info = dev->driver_info; + unsigned long flags; + int retval; + + if (skb) + skb_tx_timestamp(skb); + + // some devices want funky USB-level framing, for + // win32 driver (usually) and/or hardware quirks + if (info->tx_fixup) { + skb = info->tx_fixup (dev, skb, GFP_ATOMIC); + if (!skb) { + /* packet collected; minidriver waiting for more */ + if (info->flags & FLAG_MULTI_PACKET) + goto not_drop; + netif_dbg(dev, tx_err, dev->net, "can't tx_fixup skb\n"); + goto drop; + } + } + length = skb->len; + + if (!(urb = usb_alloc_urb (0, GFP_ATOMIC))) { + netif_dbg(dev, tx_err, dev->net, "no urb\n"); + goto drop; + } + + entry = (struct skb_data *) skb->cb; + entry->urb = urb; + entry->dev = dev; + entry->length = length; + + usb_fill_bulk_urb (urb, dev->udev, dev->out, + skb->data, skb->len, tx_complete, skb); + + /* don't assume the hardware handles USB_ZERO_PACKET + * NOTE: strictly conforming cdc-ether devices should expect + * the ZLP here, but ignore the one-byte packet. + * NOTE2: CDC NCM specification is different from CDC ECM when + * handling ZLP/short packets, so cdc_ncm driver will make short + * packet itself if needed. + */ + if (length % dev->maxpacket == 0) { + if (!(info->flags & FLAG_SEND_ZLP)) { + if (!(info->flags & FLAG_MULTI_PACKET)) { + urb->transfer_buffer_length++; + if (skb_tailroom(skb)) { + skb->data[skb->len] = 0; + __skb_put(skb, 1); + } + } + } else + urb->transfer_flags |= URB_ZERO_PACKET; + } + + spin_lock_irqsave(&dev->txq.lock, flags); + retval = usb_autopm_get_interface_async(dev->intf); + if (retval < 0) { + spin_unlock_irqrestore(&dev->txq.lock, flags); + goto drop; + } + +#ifdef CONFIG_PM + /* if this triggers the device is still a sleep */ + if (test_bit(EVENT_DEV_ASLEEP, &dev->flags)) { + /* transmission will be done in resume */ + usb_anchor_urb(urb, &dev->deferred); + /* no use to process more packets */ + netif_stop_queue(net); + usb_put_urb(urb); + spin_unlock_irqrestore(&dev->txq.lock, flags); + netdev_dbg(dev->net, "Delaying transmission for resumption\n"); + goto deferred; + } +#endif + + switch ((retval = usb_submit_urb (urb, GFP_ATOMIC))) { + case -EPIPE: + netif_stop_queue (net); + usbnet_defer_kevent (dev, EVENT_TX_HALT); + usb_autopm_put_interface_async(dev->intf); + break; + default: + usb_autopm_put_interface_async(dev->intf); + netif_dbg(dev, tx_err, dev->net, + "tx: submit urb err %d\n", retval); + break; + case 0: + net->trans_start = jiffies; + __usbnet_queue_skb(&dev->txq, skb, tx_start); + if (dev->txq.qlen >= TX_QLEN (dev)) + netif_stop_queue (net); + } + spin_unlock_irqrestore (&dev->txq.lock, flags); + + if (retval) { + netif_dbg(dev, tx_err, dev->net, "drop, code %d\n", retval); +drop: + dev->net->stats.tx_dropped++; +not_drop: + if (skb) + dev_kfree_skb_any (skb); + usb_free_urb (urb); + } else + netif_dbg(dev, tx_queued, dev->net, + "> tx, len %d, type 0x%x\n", length, skb->protocol); +#ifdef CONFIG_PM +deferred: +#endif + return NETDEV_TX_OK; +} +EXPORT_SYMBOL_GPL(usbnet_start_xmit); + +static int rx_alloc_submit(struct usbnet *dev, gfp_t flags) +{ + struct urb *urb; + int i; + int ret = 0; + + /* don't refill the queue all at once */ + for (i = 0; i < 10 && dev->rxq.qlen < RX_QLEN(dev); i++) { + urb = usb_alloc_urb(0, flags); + if (urb != NULL) { + ret = rx_submit(dev, urb, flags); + if (ret) + goto err; + } else { + ret = -ENOMEM; + goto err; + } + } +err: + return ret; +} + +/*-------------------------------------------------------------------------*/ + +// tasklet (work deferred from completions, in_irq) or timer + +static void usbnet_bh (unsigned long param) +{ + struct usbnet *dev = (struct usbnet *) param; + struct sk_buff *skb; + struct skb_data *entry; + + while ((skb = skb_dequeue (&dev->done))) { + entry = (struct skb_data *) skb->cb; + switch (entry->state) { + case rx_done: + entry->state = rx_cleanup; + rx_process (dev, skb); + continue; + case tx_done: + case rx_cleanup: + usb_free_urb (entry->urb); + dev_kfree_skb (skb); + continue; + default: + netdev_dbg(dev->net, "bogus skb state %d\n", entry->state); + } + } + + /* restart RX again after disabling due to high error rate */ + clear_bit(EVENT_RX_KILL, &dev->flags); + + // waiting for all pending urbs to complete? + if (dev->wait) { + if ((dev->txq.qlen + dev->rxq.qlen + dev->done.qlen) == 0) { + wake_up (dev->wait); + } + + // or are we maybe short a few urbs? + } else if (netif_running (dev->net) && + netif_device_present (dev->net) && + netif_carrier_ok(dev->net) && + !timer_pending (&dev->delay) && + !test_bit (EVENT_RX_HALT, &dev->flags)) { + int temp = dev->rxq.qlen; + + if (temp < RX_QLEN(dev)) { + if (rx_alloc_submit(dev, GFP_ATOMIC) == -ENOLINK) + return; + if (temp != dev->rxq.qlen) + netif_dbg(dev, link, dev->net, + "rxqlen %d --> %d\n", + temp, dev->rxq.qlen); + if (dev->rxq.qlen < RX_QLEN(dev)) + tasklet_schedule (&dev->bh); + } + if (dev->txq.qlen < TX_QLEN (dev)) + netif_wake_queue (dev->net); + } +} + + +/*------------------------------------------------------------------------- + * + * USB Device Driver support + * + *-------------------------------------------------------------------------*/ + +// precondition: never called in_interrupt + +void usbnet_disconnect (struct usb_interface *intf) +{ + struct usbnet *dev; + struct usb_device *xdev; + struct net_device *net; + + dev = usb_get_intfdata(intf); + usb_set_intfdata(intf, NULL); + if (!dev) + return; + + xdev = interface_to_usbdev (intf); + + netif_info(dev, probe, dev->net, "unregister '%s' usb-%s-%s, %s\n", + intf->dev.driver->name, + xdev->bus->bus_name, xdev->devpath, + dev->driver_info->description); + + net = dev->net; + unregister_netdev (net); + + cancel_work_sync(&dev->kevent); + + usb_scuttle_anchored_urbs(&dev->deferred); + + if (dev->driver_info->unbind) + dev->driver_info->unbind (dev, intf); + + usb_kill_urb(dev->interrupt); + usb_free_urb(dev->interrupt); + + free_netdev(net); +} +EXPORT_SYMBOL_GPL(usbnet_disconnect); + +static const struct net_device_ops usbnet_netdev_ops = { + .ndo_open = usbnet_open, + .ndo_stop = usbnet_stop, + .ndo_start_xmit = usbnet_start_xmit, + .ndo_tx_timeout = usbnet_tx_timeout, + .ndo_change_mtu = usbnet_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +/*-------------------------------------------------------------------------*/ + +// precondition: never called in_interrupt + +static struct device_type wlan_type = { + .name = "wlan", +}; + +static struct device_type wwan_type = { + .name = "wwan", +}; + +int +usbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod) +{ + struct usbnet *dev; + struct net_device *net; + struct usb_host_interface *interface; + struct driver_info *info; + struct usb_device *xdev; + int status; + const char *name; + struct usb_driver *driver = to_usb_driver(udev->dev.driver); + + /* usbnet already took usb runtime pm, so have to enable the feature + * for usb interface, otherwise usb_autopm_get_interface may return + * failure if RUNTIME_PM is enabled. + */ + if (!driver->supports_autosuspend) { + driver->supports_autosuspend = 1; + pm_runtime_enable(&udev->dev); + } + + name = udev->dev.driver->name; + info = (struct driver_info *) prod->driver_info; + if (!info) { + dev_dbg (&udev->dev, "blacklisted by %s\n", name); + return -ENODEV; + } + xdev = interface_to_usbdev (udev); + interface = udev->cur_altsetting; + + status = -ENOMEM; + + // set up our own records + net = alloc_etherdev(sizeof(*dev)); + if (!net) + goto out; + + /* netdev_printk() needs this so do it as early as possible */ + SET_NETDEV_DEV(net, &udev->dev); + + dev = netdev_priv(net); + dev->udev = xdev; + dev->intf = udev; + dev->driver_info = info; + dev->driver_name = name; + dev->msg_enable = netif_msg_init (msg_level, NETIF_MSG_DRV + | NETIF_MSG_PROBE | NETIF_MSG_LINK); + skb_queue_head_init (&dev->rxq); + skb_queue_head_init (&dev->txq); + skb_queue_head_init (&dev->done); + skb_queue_head_init(&dev->rxq_pause); + dev->bh.func = usbnet_bh; + dev->bh.data = (unsigned long) dev; + INIT_WORK (&dev->kevent, kevent); + init_usb_anchor(&dev->deferred); + dev->delay.function = usbnet_bh; + dev->delay.data = (unsigned long) dev; + init_timer (&dev->delay); + mutex_init (&dev->phy_mutex); + mutex_init(&dev->interrupt_mutex); + dev->interrupt_count = 0; + + dev->net = net; + strcpy (net->name, "usb%d"); + memcpy (net->dev_addr, node_id, sizeof node_id); + + /* rx and tx sides can use different message sizes; + * bind() should set rx_urb_size in that case. + */ + dev->hard_mtu = net->mtu + net->hard_header_len; +#if 0 +// dma_supported() is deeply broken on almost all architectures + // possible with some EHCI controllers + if (dma_supported (&udev->dev, DMA_BIT_MASK(64))) + net->features |= NETIF_F_HIGHDMA; +#endif + + netdev_attach_ops(net, &usbnet_netdev_ops); + net->watchdog_timeo = TX_TIMEOUT_JIFFIES; + net->ethtool_ops = &usbnet_ethtool_ops; + + // allow device-specific bind/init procedures + // NOTE net->name still not usable ... + if (info->bind) { + status = info->bind (dev, udev); + if (status < 0) + goto out1; + + // heuristic: "usb%d" for links we know are two-host, + // else "eth%d" when there's reasonable doubt. userspace + // can rename the link if it knows better. + if ((dev->driver_info->flags & FLAG_ETHER) != 0 && + ((dev->driver_info->flags & FLAG_POINTTOPOINT) == 0 || + (net->dev_addr [0] & 0x02) == 0)) + strcpy (net->name, "eth%d"); + /* WLAN devices should always be named "wlan%d" */ + if ((dev->driver_info->flags & FLAG_WLAN) != 0) + strcpy(net->name, "wlan%d"); + /* WWAN devices should always be named "wwan%d" */ + if ((dev->driver_info->flags & FLAG_WWAN) != 0) + strcpy(net->name, "wwan%d"); + + /* devices that cannot do ARP */ + if ((dev->driver_info->flags & FLAG_NOARP) != 0) + net->flags |= IFF_NOARP; + + /* maybe the remote can't receive an Ethernet MTU */ + if (net->mtu > (dev->hard_mtu - net->hard_header_len)) + net->mtu = dev->hard_mtu - net->hard_header_len; + } else if (!info->in || !info->out) + status = usbnet_get_endpoints (dev, udev); + else { + dev->in = usb_rcvbulkpipe (xdev, info->in); + dev->out = usb_sndbulkpipe (xdev, info->out); + if (!(info->flags & FLAG_NO_SETINT)) + status = usb_set_interface (xdev, + interface->desc.bInterfaceNumber, + interface->desc.bAlternateSetting); + else + status = 0; + + } + if (status >= 0 && dev->status) + status = init_status (dev, udev); + if (status < 0) + goto out3; + + if (!dev->rx_urb_size) + dev->rx_urb_size = dev->hard_mtu; + dev->maxpacket = usb_maxpacket (dev->udev, dev->out, 1); + + if ((dev->driver_info->flags & FLAG_WLAN) != 0) + SET_NETDEV_DEVTYPE(net, &wlan_type); + if ((dev->driver_info->flags & FLAG_WWAN) != 0) + SET_NETDEV_DEVTYPE(net, &wwan_type); + + status = register_netdev (net); + if (status) + goto out4; + netif_info(dev, probe, dev->net, + "register '%s' at usb-%s-%s, %s, %pM\n", + udev->dev.driver->name, + xdev->bus->bus_name, xdev->devpath, + dev->driver_info->description, + net->dev_addr); + + // ok, it's ready to go. + usb_set_intfdata (udev, dev); + + netif_device_attach (net); + + if (dev->driver_info->flags & FLAG_LINK_INTR) + usbnet_link_change(dev, 0, 0); + + return 0; + +out4: + usb_free_urb(dev->interrupt); +out3: + if (info->unbind) + info->unbind (dev, udev); +out1: + free_netdev(net); +out: + return status; +} +EXPORT_SYMBOL_GPL(usbnet_probe); + +/*-------------------------------------------------------------------------*/ + +/* + * suspend the whole driver as soon as the first interface is suspended + * resume only when the last interface is resumed + */ + +int usbnet_suspend (struct usb_interface *intf, pm_message_t message) +{ + struct usbnet *dev = usb_get_intfdata(intf); + + if (!dev->suspend_count++) { + spin_lock_irq(&dev->txq.lock); + /* don't autosuspend while transmitting */ + if (dev->txq.qlen && PMSG_IS_AUTO(message)) { + dev->suspend_count--; + spin_unlock_irq(&dev->txq.lock); + return -EBUSY; + } else { + set_bit(EVENT_DEV_ASLEEP, &dev->flags); + spin_unlock_irq(&dev->txq.lock); + } + /* + * accelerate emptying of the rx and queues, to avoid + * having everything error out. + */ + netif_device_detach (dev->net); + usbnet_terminate_urbs(dev); + __usbnet_status_stop_force(dev); + + /* + * reattach so runtime management can use and + * wake the device + */ + netif_device_attach (dev->net); + } + return 0; +} +EXPORT_SYMBOL_GPL(usbnet_suspend); + +int usbnet_resume (struct usb_interface *intf) +{ + struct usbnet *dev = usb_get_intfdata(intf); + struct sk_buff *skb; + struct urb *res; + int retval; + + if (!--dev->suspend_count) { + /* resume interrupt URB if it was previously submitted */ + __usbnet_status_start_force(dev, GFP_NOIO); + + spin_lock_irq(&dev->txq.lock); + while ((res = usb_get_from_anchor(&dev->deferred))) { + + skb = (struct sk_buff *)res->context; + retval = usb_submit_urb(res, GFP_ATOMIC); + if (retval < 0) { + dev_kfree_skb_any(skb); + usb_free_urb(res); + usb_autopm_put_interface_async(dev->intf); + } else { + dev->net->trans_start = jiffies; + __skb_queue_tail(&dev->txq, skb); + } + } + + smp_mb(); + clear_bit(EVENT_DEV_ASLEEP, &dev->flags); + spin_unlock_irq(&dev->txq.lock); + + if (test_bit(EVENT_DEV_OPEN, &dev->flags)) { + /* handle remote wakeup ASAP */ + if (!dev->wait && + netif_device_present(dev->net) && + !timer_pending(&dev->delay) && + !test_bit(EVENT_RX_HALT, &dev->flags)) + rx_alloc_submit(dev, GFP_NOIO); + + if (!(dev->txq.qlen >= TX_QLEN(dev))) + netif_tx_wake_all_queues(dev->net); + tasklet_schedule (&dev->bh); + } + } + + if (test_and_clear_bit(EVENT_DEVICE_REPORT_IDLE, &dev->flags)) + usb_autopm_get_interface_no_resume(intf); + + return 0; +} +EXPORT_SYMBOL_GPL(usbnet_resume); + +/* + * Either a subdriver implements manage_power, then it is assumed to always + * be ready to be suspended or it reports the readiness to be suspended + * explicitly + */ +void usbnet_device_suggests_idle(struct usbnet *dev) +{ + if (!test_and_set_bit(EVENT_DEVICE_REPORT_IDLE, &dev->flags)) { + dev->intf->needs_remote_wakeup = 1; + usb_autopm_put_interface_async(dev->intf); + } +} +EXPORT_SYMBOL(usbnet_device_suggests_idle); + +/* + * For devices that can do without special commands + */ +int usbnet_manage_power(struct usbnet *dev, int on) +{ + dev->intf->needs_remote_wakeup = on; + return 0; +} +EXPORT_SYMBOL(usbnet_manage_power); + +void usbnet_link_change(struct usbnet *dev, bool link, bool need_reset) +{ + /* update link after link is reseted */ + if (link && !need_reset) + netif_carrier_on(dev->net); + else + netif_carrier_off(dev->net); + + if (need_reset && link) + usbnet_defer_kevent(dev, EVENT_LINK_RESET); + else + usbnet_defer_kevent(dev, EVENT_LINK_CHANGE); +} +EXPORT_SYMBOL(usbnet_link_change); + +/*-------------------------------------------------------------------------*/ +static int __usbnet_read_cmd(struct usbnet *dev, u8 cmd, u8 reqtype, + u16 value, u16 index, void *data, u16 size) +{ + void *buf = NULL; + int err = -ENOMEM; + + netdev_dbg(dev->net, "usbnet_read_cmd cmd=0x%02x reqtype=%02x" + " value=0x%04x index=0x%04x size=%d\n", + cmd, reqtype, value, index, size); + + if (data) { + buf = kmalloc(size, GFP_KERNEL); + if (!buf) + goto out; + } + + err = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), + cmd, reqtype, value, index, buf, size, + USB_CTRL_GET_TIMEOUT); + if (err > 0 && err <= size) + memcpy(data, buf, err); + kfree(buf); +out: + return err; +} + +static int __usbnet_write_cmd(struct usbnet *dev, u8 cmd, u8 reqtype, + u16 value, u16 index, const void *data, + u16 size) +{ + void *buf = NULL; + int err = -ENOMEM; + + netdev_dbg(dev->net, "usbnet_write_cmd cmd=0x%02x reqtype=%02x" + " value=0x%04x index=0x%04x size=%d\n", + cmd, reqtype, value, index, size); + + if (data) { + buf = kmemdup(data, size, GFP_KERNEL); + if (!buf) + goto out; + } + + err = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), + cmd, reqtype, value, index, buf, size, + USB_CTRL_SET_TIMEOUT); + kfree(buf); + +out: + return err; +} + +/* + * The function can't be called inside suspend/resume callback, + * otherwise deadlock will be caused. + */ +int usbnet_read_cmd(struct usbnet *dev, u8 cmd, u8 reqtype, + u16 value, u16 index, void *data, u16 size) +{ + int ret; + + if (usb_autopm_get_interface(dev->intf) < 0) + return -ENODEV; + ret = __usbnet_read_cmd(dev, cmd, reqtype, value, index, + data, size); + usb_autopm_put_interface(dev->intf); + return ret; +} +EXPORT_SYMBOL_GPL(usbnet_read_cmd); + +/* + * The function can't be called inside suspend/resume callback, + * otherwise deadlock will be caused. + */ +int usbnet_write_cmd(struct usbnet *dev, u8 cmd, u8 reqtype, + u16 value, u16 index, const void *data, u16 size) +{ + int ret; + + if (usb_autopm_get_interface(dev->intf) < 0) + return -ENODEV; + ret = __usbnet_write_cmd(dev, cmd, reqtype, value, index, + data, size); + usb_autopm_put_interface(dev->intf); + return ret; +} +EXPORT_SYMBOL_GPL(usbnet_write_cmd); + +/* + * The function can be called inside suspend/resume callback safely + * and should only be called by suspend/resume callback generally. + */ +int usbnet_read_cmd_nopm(struct usbnet *dev, u8 cmd, u8 reqtype, + u16 value, u16 index, void *data, u16 size) +{ + return __usbnet_read_cmd(dev, cmd, reqtype, value, index, + data, size); +} +EXPORT_SYMBOL_GPL(usbnet_read_cmd_nopm); + +/* + * The function can be called inside suspend/resume callback safely + * and should only be called by suspend/resume callback generally. + */ +int usbnet_write_cmd_nopm(struct usbnet *dev, u8 cmd, u8 reqtype, + u16 value, u16 index, const void *data, + u16 size) +{ + return __usbnet_write_cmd(dev, cmd, reqtype, value, index, + data, size); +} +EXPORT_SYMBOL_GPL(usbnet_write_cmd_nopm); + +static void usbnet_async_cmd_cb(struct urb *urb) +{ + struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)urb->context; + int status = urb->status; + + if (status < 0) + dev_dbg(&urb->dev->dev, "%s failed with %d", + __func__, status); + + kfree(req); + usb_free_urb(urb); +} + +/* + * The caller must make sure that device can't be put into suspend + * state until the control URB completes. + */ +int usbnet_write_cmd_async(struct usbnet *dev, u8 cmd, u8 reqtype, + u16 value, u16 index, const void *data, u16 size) +{ + struct usb_ctrlrequest *req = NULL; + struct urb *urb; + int err = -ENOMEM; + void *buf = NULL; + + netdev_dbg(dev->net, "usbnet_write_cmd cmd=0x%02x reqtype=%02x" + " value=0x%04x index=0x%04x size=%d\n", + cmd, reqtype, value, index, size); + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + netdev_err(dev->net, "Error allocating URB in" + " %s!\n", __func__); + goto fail; + } + + if (data) { + buf = kmemdup(data, size, GFP_ATOMIC); + if (!buf) { + netdev_err(dev->net, "Error allocating buffer" + " in %s!\n", __func__); + goto fail_free; + } + } + + req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_ATOMIC); + if (!req) + goto fail_free_buf; + + req->bRequestType = reqtype; + req->bRequest = cmd; + req->wValue = cpu_to_le16(value); + req->wIndex = cpu_to_le16(index); + req->wLength = cpu_to_le16(size); + + usb_fill_control_urb(urb, dev->udev, + usb_sndctrlpipe(dev->udev, 0), + (void *)req, buf, size, + usbnet_async_cmd_cb, req); + urb->transfer_flags |= URB_FREE_BUFFER; + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + netdev_err(dev->net, "Error submitting the control" + " message: status=%d\n", err); + goto fail_free; + } + return 0; + +fail_free_buf: + kfree(buf); +fail_free: + kfree(req); + usb_free_urb(urb); +fail: + return err; + +} +EXPORT_SYMBOL_GPL(usbnet_write_cmd_async); +/*-------------------------------------------------------------------------*/ + +static int __init usbnet_init(void) +{ + /* Compiler should optimize this out. */ + BUILD_BUG_ON( + FIELD_SIZEOF(struct sk_buff, cb) < sizeof(struct skb_data)); + + eth_random_addr(node_id); + return 0; +} +module_init(usbnet_init); + +static void __exit usbnet_exit(void) +{ +} +module_exit(usbnet_exit); + +MODULE_AUTHOR("David Brownell"); +MODULE_DESCRIPTION("USB network driver framework"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig new file mode 100644 index 0000000..7948a36 --- /dev/null +++ b/drivers/net/wireless/Kconfig @@ -0,0 +1,312 @@ +# +# Wireless LAN device configuration +# + +menuconfig WLAN + bool "Wireless LAN" + depends on !S390 + depends on NET + select WIRELESS + default y + ---help--- + This section contains all the pre 802.11 and 802.11 wireless + device drivers. For a complete list of drivers and documentation + on them refer to the wireless wiki: + + http://wireless.kernel.org/en/users/Drivers + +if WLAN + +config PCMCIA_RAYCS + depends on n + tristate "Aviator/Raytheon 2.4GHz wireless support" + depends on m + depends on PCMCIA + depends on WIRELESS_EXT + depends on WEXT_SPY + depends on WEXT_PRIV + ---help--- + Say Y here if you intend to attach an Aviator/Raytheon PCMCIA + (PC-card) wireless Ethernet networking card to your computer. + Please read the file for + details. + + To compile this driver as a module, choose M here: the module will be + called ray_cs. If unsure, say N. + +config LIBERTAS_THINFIRM + depends on !BACKPORT_KERNEL_2_6_27 + tristate "Marvell 8xxx Libertas WLAN driver support with thin firmware" + depends on m + depends on MAC80211 + select BACKPORT_FW_LOADER + ---help--- + A library for Marvell Libertas 8xxx devices using thinfirm. + +config LIBERTAS_THINFIRM_DEBUG + bool "Enable full debugging output in the Libertas thin firmware module." + depends on LIBERTAS_THINFIRM + ---help--- + Debugging support. + +config LIBERTAS_THINFIRM_USB + tristate "Marvell Libertas 8388 USB 802.11b/g cards with thin firmware" + depends on m + depends on LIBERTAS_THINFIRM && USB + ---help--- + A driver for Marvell Libertas 8388 USB devices using thinfirm. + +config AIRO + depends on n + tristate "Cisco/Aironet 34X/35X/4500/4800 ISA and PCI cards" + depends on m + depends on ISA_DMA_API && (PCI || BROKEN) + depends on WIRELESS_EXT + depends on CRYPTO + depends on WEXT_SPY + depends on WEXT_PRIV + ---help--- + This is the standard Linux driver to support Cisco/Aironet ISA and + PCI 802.11 wireless cards. + It supports the new 802.11b cards from Cisco (Cisco 34X, Cisco 35X + - with or without encryption) as well as card before the Cisco + acquisition (Aironet 4500, Aironet 4800, Aironet 4800B). + + This driver support both the standard Linux Wireless Extensions + and Cisco proprietary API, so both the Linux Wireless Tools and the + Cisco Linux utilities can be used to configure the card. + + The driver can be compiled as a module and will be named "airo". + +config ATMEL + depends on n + tristate "Atmel at76c50x chipset 802.11b support" + depends on m + depends on (PCI || PCMCIA) + depends on WIRELESS_EXT + depends on WEXT_PRIV + select BACKPORT_FW_LOADER + depends on CRC32 + ---help--- + A driver 802.11b wireless cards based on the Atmel fast-vnet + chips. This driver supports standard Linux wireless extensions. + + Many cards based on this chipset do not have flash memory + and need their firmware loaded at start-up. If yours is + one of these, you will need to provide a firmware image + to be loaded into the card by the driver. The Atmel + firmware package can be downloaded from + + +config PCI_ATMEL + depends on n + tristate "Atmel at76c506 PCI cards" + depends on m + depends on ATMEL && PCI + ---help--- + Enable support for PCI and mini-PCI cards containing the + Atmel at76c506 chip. + +config PCMCIA_ATMEL + depends on n + tristate "Atmel at76c502/at76c504 PCMCIA cards" + depends on m + depends on ATMEL && PCMCIA + depends on WIRELESS_EXT + select BACKPORT_FW_LOADER + depends on CRC32 + ---help--- + Enable support for PCMCIA cards containing the + Atmel at76c502 and at76c504 chips. + +config AT76C50X_USB + tristate "Atmel at76c503/at76c505/at76c505a USB cards" + depends on m + depends on MAC80211 && USB + select BACKPORT_FW_LOADER + ---help--- + Enable support for USB Wireless devices using Atmel at76c503, + at76c505 or at76c505a chips. + +config AIRO_CS + depends on n + tristate "Cisco/Aironet 34X/35X/4500/4800 PCMCIA cards" + depends on m + depends on PCMCIA && (BROKEN || !M32R) + depends on WIRELESS_EXT + depends on WEXT_SPY + depends on WEXT_PRIV + depends on CRYPTO + depends on CRYPTO_AES + ---help--- + This is the standard Linux driver to support Cisco/Aironet PCMCIA + 802.11 wireless cards. This driver is the same as the Aironet + driver part of the Linux Pcmcia package. + It supports the new 802.11b cards from Cisco (Cisco 34X, Cisco 35X + - with or without encryption) as well as card before the Cisco + acquisition (Aironet 4500, Aironet 4800, Aironet 4800B). It also + supports OEM of Cisco such as the DELL TrueMobile 4800 and Xircom + 802.11b cards. + + This driver support both the standard Linux Wireless Extensions + and Cisco proprietary API, so both the Linux Wireless Tools and the + Cisco Linux utilities can be used to configure the card. + +config PCMCIA_WL3501 + depends on n + tristate "Planet WL3501 PCMCIA cards" + depends on m + depends on PCMCIA + depends on WIRELESS_EXT + depends on WEXT_SPY + help + A driver for WL3501 PCMCIA 802.11 wireless cards made by Planet. + It has basic support for Linux wireless extensions and initial + micro support for ethtool. + +config PRISM54 + depends on n + tristate 'Intersil Prism GT/Duette/Indigo PCI/Cardbus (DEPRECATED)' + depends on m + depends on PCI + depends on WIRELESS_EXT + depends on WEXT_SPY + depends on WEXT_PRIV + select BACKPORT_FW_LOADER + ---help--- + This enables support for FullMAC PCI/Cardbus prism54 devices. This + driver is now deprecated in favor for the SoftMAC driver, p54pci. + p54pci supports FullMAC PCI/Cardbus devices as well. + + For more information refer to the p54 wiki: + + http://wireless.kernel.org/en/users/Drivers/p54 + + Note: You need a motherboard with DMA support to use any of these cards + + When built as module you get the module prism54 + +config USB_ZD1201 + depends on n + tristate "USB ZD1201 based Wireless device support" + depends on m + depends on USB + depends on WIRELESS_EXT + depends on WEXT_PRIV + select BACKPORT_FW_LOADER + ---help--- + Say Y if you want to use wireless LAN adapters based on the ZyDAS + ZD1201 chip. + + This driver makes the adapter appear as a normal Ethernet interface, + typically on wlan0. + + The zd1201 device requires external firmware to be loaded. + This can be found at http://linux-lc100020.sourceforge.net/ + + To compile this driver as a module, choose M here: the + module will be called zd1201. + +config USB_NET_RNDIS_WLAN + depends on !BACKPORT_KERNEL_2_6_29 + tristate "Wireless RNDIS USB support" + depends on m + depends on USB + depends on CFG80211 + select USB_USBNET + select USB_NET_CDCETHER + select USB_NET_RNDIS_HOST + ---help--- + This is a driver for wireless RNDIS devices. + These are USB based adapters found in devices such as: + + Buffalo WLI-U2-KG125S + U.S. Robotics USR5421 + Belkin F5D7051 + Linksys WUSB54GSv2 + Linksys WUSB54GSC + Asus WL169gE + Eminent EM4045 + BT Voyager 1055 + Linksys WUSB54GSv1 + U.S. Robotics USR5420 + BUFFALO WLI-USB-G54 + + All of these devices are based on Broadcom 4320 chip which is the + only wireless RNDIS chip known to date. + + If you choose to build a module, it'll be called rndis_wlan. + +source "drivers/net/wireless/rtl818x/Kconfig" + +config ADM8211 + tristate "ADMtek ADM8211 support" + depends on m + depends on MAC80211 && PCI + depends on CRC32 + depends on EEPROM_93CX6 + ---help--- + This driver is for ADM8211A, ADM8211B, and ADM8211C based cards. + These are PCI/mini-PCI/Cardbus 802.11b chips found in cards such as: + + Xterasys Cardbus XN-2411b + Blitz NetWave Point PC + TrendNet 221pc + Belkin F5D6001 + SMC 2635W + Linksys WPC11 v1 + Fiberline FL-WL-200X + 3com Office Connect (3CRSHPW796) + Corega WLPCIB-11 + SMC 2602W V2 EU + D-Link DWL-520 Revision C + + However, some of these cards have been replaced with other chips + like the RTL8180L (Xterasys Cardbus XN-2411b, Belkin F5D6001) or + the Ralink RT2400 (SMC2635W) without a model number change. + + Thanks to Infineon-ADMtek for their support of this driver. + +config MAC80211_HWSIM + depends on !BACKPORT_KERNEL_2_6_31 + tristate "Simulated radio testing tool for mac80211" + depends on m + depends on MAC80211 + ---help--- + This driver is a developer testing tool that can be used to test + IEEE 802.11 networking stack (mac80211) functionality. This is not + needed for normal wireless LAN usage and is only for testing. See + Documentation/networking/mac80211_hwsim for more information on how + to use this tool. + + To compile this driver as a module, choose M here: the module will be + called mac80211_hwsim. If unsure, say N. + +config MWL8K + tristate "Marvell 88W8xxx PCI/PCIe Wireless support" + depends on m + depends on MAC80211 && PCI + ---help--- + This driver supports Marvell TOPDOG 802.11 wireless cards. + + To compile this driver as a module, choose M here: the module + will be called mwl8k. If unsure, say N. + +source "drivers/net/wireless/ath/Kconfig" +source "drivers/net/wireless/b43/Kconfig" +source "drivers/net/wireless/b43legacy/Kconfig" +source "drivers/net/wireless/brcm80211/Kconfig" +#source "drivers/net/wireless/hostap/Kconfig" +source "drivers/net/wireless/ipw2x00/Kconfig" +source "drivers/net/wireless/iwlwifi/Kconfig" +source "drivers/net/wireless/iwlegacy/Kconfig" +source "drivers/net/wireless/libertas/Kconfig" +source "drivers/net/wireless/orinoco/Kconfig" +source "drivers/net/wireless/p54/Kconfig" +source "drivers/net/wireless/rt2x00/Kconfig" +source "drivers/net/wireless/rtlwifi/Kconfig" +source "drivers/net/wireless/ti/Kconfig" +source "drivers/net/wireless/zd1211rw/Kconfig" +source "drivers/net/wireless/mwifiex/Kconfig" + +endif # WLAN diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile new file mode 100644 index 0000000..f473467 --- /dev/null +++ b/drivers/net/wireless/Makefile @@ -0,0 +1,59 @@ +# +# Makefile for the Linux Wireless network device drivers. +# + +obj-$(CPTCFG_IPW2100) += ipw2x00/ +obj-$(CPTCFG_IPW2200) += ipw2x00/ + +obj-$(CPTCFG_HERMES) += orinoco/ +# +#obj-$(CPTCFG_AIRO) += airo.o +#obj-$(CPTCFG_AIRO_CS) += airo_cs.o airo.o +# +#obj-$(CPTCFG_ATMEL) += atmel.o +#obj-$(CPTCFG_PCI_ATMEL) += atmel_pci.o +#obj-$(CPTCFG_PCMCIA_ATMEL) += atmel_cs.o + +obj-$(CPTCFG_AT76C50X_USB) += at76c50x-usb.o +# +#obj-$(CPTCFG_PRISM54) += prism54/ +# +#obj-$(CONFIG_HOSTAP) += hostap/ +obj-$(CPTCFG_B43) += b43/ +obj-$(CPTCFG_B43LEGACY) += b43legacy/ +obj-$(CPTCFG_ZD1211RW) += zd1211rw/ +obj-$(CPTCFG_RTL8180) += rtl818x/ +obj-$(CPTCFG_RTL8187) += rtl818x/ +obj-$(CPTCFG_RTLWIFI) += rtlwifi/ + +# 16-bit wireless PCMCIA client drivers +#obj-$(CPTCFG_PCMCIA_RAYCS) += ray_cs.o +#obj-$(CPTCFG_PCMCIA_WL3501) += wl3501_cs.o + +obj-$(CPTCFG_USB_NET_RNDIS_WLAN) += rndis_wlan.o +# +#obj-$(CPTCFG_USB_ZD1201) += zd1201.o +obj-$(CPTCFG_LIBERTAS) += libertas/ + +obj-$(CPTCFG_LIBERTAS_THINFIRM) += libertas_tf/ + +obj-$(CPTCFG_ADM8211) += adm8211.o + +obj-$(CPTCFG_MWL8K) += mwl8k.o + +obj-$(CPTCFG_IWLWIFI) += iwlwifi/ +obj-$(CPTCFG_IWLEGACY) += iwlegacy/ +obj-$(CPTCFG_RT2X00) += rt2x00/ + +obj-$(CPTCFG_P54_COMMON) += p54/ + +obj-$(CPTCFG_ATH_CARDS) += ath/ + +obj-$(CPTCFG_MAC80211_HWSIM) += mac80211_hwsim.o + +obj-$(CPTCFG_WL_TI) += ti/ + +obj-$(CPTCFG_MWIFIEX) += mwifiex/ + +obj-$(CPTCFG_BRCMFMAC) += brcm80211/ +obj-$(CPTCFG_BRCMSMAC) += brcm80211/ diff --git a/drivers/net/wireless/adm8211.c b/drivers/net/wireless/adm8211.c new file mode 100644 index 0000000..d4238ed --- /dev/null +++ b/drivers/net/wireless/adm8211.c @@ -0,0 +1,2014 @@ + +/* + * Linux device driver for ADMtek ADM8211 (IEEE 802.11b MAC/BBP) + * + * Copyright (c) 2003, Jouni Malinen + * Copyright (c) 2004-2007, Michael Wu + * Some parts copyright (c) 2003 by David Young + * and used with permission. + * + * Much thanks to Infineon-ADMtek for their support of this driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See README and COPYING for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "adm8211.h" + +MODULE_AUTHOR("Michael Wu "); +MODULE_AUTHOR("Jouni Malinen "); +MODULE_DESCRIPTION("Driver for IEEE 802.11b wireless cards based on ADMtek ADM8211"); +MODULE_SUPPORTED_DEVICE("ADM8211"); +MODULE_LICENSE("GPL"); + +static unsigned int tx_ring_size __read_mostly = 16; +static unsigned int rx_ring_size __read_mostly = 16; + +module_param(tx_ring_size, uint, 0); +module_param(rx_ring_size, uint, 0); + +static DEFINE_PCI_DEVICE_TABLE(adm8211_pci_id_table) = { + /* ADMtek ADM8211 */ + { PCI_DEVICE(0x10B7, 0x6000) }, /* 3Com 3CRSHPW796 */ + { PCI_DEVICE(0x1200, 0x8201) }, /* ? */ + { PCI_DEVICE(0x1317, 0x8201) }, /* ADM8211A */ + { PCI_DEVICE(0x1317, 0x8211) }, /* ADM8211B/C */ + { 0 } +}; + +static struct ieee80211_rate adm8211_rates[] = { + { .bitrate = 10, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 20, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 55, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 110, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 220, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, /* XX ?? */ +}; + +static const struct ieee80211_channel adm8211_channels[] = { + { .center_freq = 2412}, + { .center_freq = 2417}, + { .center_freq = 2422}, + { .center_freq = 2427}, + { .center_freq = 2432}, + { .center_freq = 2437}, + { .center_freq = 2442}, + { .center_freq = 2447}, + { .center_freq = 2452}, + { .center_freq = 2457}, + { .center_freq = 2462}, + { .center_freq = 2467}, + { .center_freq = 2472}, + { .center_freq = 2484}, +}; + + +static void adm8211_eeprom_register_read(struct eeprom_93cx6 *eeprom) +{ + struct adm8211_priv *priv = eeprom->data; + u32 reg = ADM8211_CSR_READ(SPR); + + eeprom->reg_data_in = reg & ADM8211_SPR_SDI; + eeprom->reg_data_out = reg & ADM8211_SPR_SDO; + eeprom->reg_data_clock = reg & ADM8211_SPR_SCLK; + eeprom->reg_chip_select = reg & ADM8211_SPR_SCS; +} + +static void adm8211_eeprom_register_write(struct eeprom_93cx6 *eeprom) +{ + struct adm8211_priv *priv = eeprom->data; + u32 reg = 0x4000 | ADM8211_SPR_SRS; + + if (eeprom->reg_data_in) + reg |= ADM8211_SPR_SDI; + if (eeprom->reg_data_out) + reg |= ADM8211_SPR_SDO; + if (eeprom->reg_data_clock) + reg |= ADM8211_SPR_SCLK; + if (eeprom->reg_chip_select) + reg |= ADM8211_SPR_SCS; + + ADM8211_CSR_WRITE(SPR, reg); + ADM8211_CSR_READ(SPR); /* eeprom_delay */ +} + +static int adm8211_read_eeprom(struct ieee80211_hw *dev) +{ + struct adm8211_priv *priv = dev->priv; + unsigned int words, i; + struct ieee80211_chan_range chan_range; + u16 cr49; + struct eeprom_93cx6 eeprom = { + .data = priv, + .register_read = adm8211_eeprom_register_read, + .register_write = adm8211_eeprom_register_write + }; + + if (ADM8211_CSR_READ(CSR_TEST0) & ADM8211_CSR_TEST0_EPTYP) { + /* 256 * 16-bit = 512 bytes */ + eeprom.width = PCI_EEPROM_WIDTH_93C66; + words = 256; + } else { + /* 64 * 16-bit = 128 bytes */ + eeprom.width = PCI_EEPROM_WIDTH_93C46; + words = 64; + } + + priv->eeprom_len = words * 2; + priv->eeprom = kmalloc(priv->eeprom_len, GFP_KERNEL); + if (!priv->eeprom) + return -ENOMEM; + + eeprom_93cx6_multiread(&eeprom, 0, (__le16 *)priv->eeprom, words); + + cr49 = le16_to_cpu(priv->eeprom->cr49); + priv->rf_type = (cr49 >> 3) & 0x7; + switch (priv->rf_type) { + case ADM8211_TYPE_INTERSIL: + case ADM8211_TYPE_RFMD: + case ADM8211_TYPE_MARVEL: + case ADM8211_TYPE_AIROHA: + case ADM8211_TYPE_ADMTEK: + break; + + default: + if (priv->pdev->revision < ADM8211_REV_CA) + priv->rf_type = ADM8211_TYPE_RFMD; + else + priv->rf_type = ADM8211_TYPE_AIROHA; + + printk(KERN_WARNING "%s (adm8211): Unknown RFtype %d\n", + pci_name(priv->pdev), (cr49 >> 3) & 0x7); + } + + priv->bbp_type = cr49 & 0x7; + switch (priv->bbp_type) { + case ADM8211_TYPE_INTERSIL: + case ADM8211_TYPE_RFMD: + case ADM8211_TYPE_MARVEL: + case ADM8211_TYPE_AIROHA: + case ADM8211_TYPE_ADMTEK: + break; + default: + if (priv->pdev->revision < ADM8211_REV_CA) + priv->bbp_type = ADM8211_TYPE_RFMD; + else + priv->bbp_type = ADM8211_TYPE_ADMTEK; + + printk(KERN_WARNING "%s (adm8211): Unknown BBPtype: %d\n", + pci_name(priv->pdev), cr49 >> 3); + } + + if (priv->eeprom->country_code >= ARRAY_SIZE(cranges)) { + printk(KERN_WARNING "%s (adm8211): Invalid country code (%d)\n", + pci_name(priv->pdev), priv->eeprom->country_code); + + chan_range = cranges[2]; + } else + chan_range = cranges[priv->eeprom->country_code]; + + printk(KERN_DEBUG "%s (adm8211): Channel range: %d - %d\n", + pci_name(priv->pdev), (int)chan_range.min, (int)chan_range.max); + + BUILD_BUG_ON(sizeof(priv->channels) != sizeof(adm8211_channels)); + + memcpy(priv->channels, adm8211_channels, sizeof(priv->channels)); + priv->band.channels = priv->channels; + priv->band.n_channels = ARRAY_SIZE(adm8211_channels); + priv->band.bitrates = adm8211_rates; + priv->band.n_bitrates = ARRAY_SIZE(adm8211_rates); + + for (i = 1; i <= ARRAY_SIZE(adm8211_channels); i++) + if (i < chan_range.min || i > chan_range.max) + priv->channels[i - 1].flags |= IEEE80211_CHAN_DISABLED; + + switch (priv->eeprom->specific_bbptype) { + case ADM8211_BBP_RFMD3000: + case ADM8211_BBP_RFMD3002: + case ADM8211_BBP_ADM8011: + priv->specific_bbptype = priv->eeprom->specific_bbptype; + break; + + default: + if (priv->pdev->revision < ADM8211_REV_CA) + priv->specific_bbptype = ADM8211_BBP_RFMD3000; + else + priv->specific_bbptype = ADM8211_BBP_ADM8011; + + printk(KERN_WARNING "%s (adm8211): Unknown specific BBP: %d\n", + pci_name(priv->pdev), priv->eeprom->specific_bbptype); + } + + switch (priv->eeprom->specific_rftype) { + case ADM8211_RFMD2948: + case ADM8211_RFMD2958: + case ADM8211_RFMD2958_RF3000_CONTROL_POWER: + case ADM8211_MAX2820: + case ADM8211_AL2210L: + priv->transceiver_type = priv->eeprom->specific_rftype; + break; + + default: + if (priv->pdev->revision == ADM8211_REV_BA) + priv->transceiver_type = ADM8211_RFMD2958_RF3000_CONTROL_POWER; + else if (priv->pdev->revision == ADM8211_REV_CA) + priv->transceiver_type = ADM8211_AL2210L; + else if (priv->pdev->revision == ADM8211_REV_AB) + priv->transceiver_type = ADM8211_RFMD2948; + + printk(KERN_WARNING "%s (adm8211): Unknown transceiver: %d\n", + pci_name(priv->pdev), priv->eeprom->specific_rftype); + + break; + } + + printk(KERN_DEBUG "%s (adm8211): RFtype=%d BBPtype=%d Specific BBP=%d " + "Transceiver=%d\n", pci_name(priv->pdev), priv->rf_type, + priv->bbp_type, priv->specific_bbptype, priv->transceiver_type); + + return 0; +} + +static inline void adm8211_write_sram(struct ieee80211_hw *dev, + u32 addr, u32 data) +{ + struct adm8211_priv *priv = dev->priv; + + ADM8211_CSR_WRITE(WEPCTL, addr | ADM8211_WEPCTL_TABLE_WR | + (priv->pdev->revision < ADM8211_REV_BA ? + 0 : ADM8211_WEPCTL_SEL_WEPTABLE )); + ADM8211_CSR_READ(WEPCTL); + msleep(1); + + ADM8211_CSR_WRITE(WESK, data); + ADM8211_CSR_READ(WESK); + msleep(1); +} + +static void adm8211_write_sram_bytes(struct ieee80211_hw *dev, + unsigned int addr, u8 *buf, + unsigned int len) +{ + struct adm8211_priv *priv = dev->priv; + u32 reg = ADM8211_CSR_READ(WEPCTL); + unsigned int i; + + if (priv->pdev->revision < ADM8211_REV_BA) { + for (i = 0; i < len; i += 2) { + u16 val = buf[i] | (buf[i + 1] << 8); + adm8211_write_sram(dev, addr + i / 2, val); + } + } else { + for (i = 0; i < len; i += 4) { + u32 val = (buf[i + 0] << 0 ) | (buf[i + 1] << 8 ) | + (buf[i + 2] << 16) | (buf[i + 3] << 24); + adm8211_write_sram(dev, addr + i / 4, val); + } + } + + ADM8211_CSR_WRITE(WEPCTL, reg); +} + +static void adm8211_clear_sram(struct ieee80211_hw *dev) +{ + struct adm8211_priv *priv = dev->priv; + u32 reg = ADM8211_CSR_READ(WEPCTL); + unsigned int addr; + + for (addr = 0; addr < ADM8211_SRAM_SIZE; addr++) + adm8211_write_sram(dev, addr, 0); + + ADM8211_CSR_WRITE(WEPCTL, reg); +} + +static int adm8211_get_stats(struct ieee80211_hw *dev, + struct ieee80211_low_level_stats *stats) +{ + struct adm8211_priv *priv = dev->priv; + + memcpy(stats, &priv->stats, sizeof(*stats)); + + return 0; +} + +static void adm8211_interrupt_tci(struct ieee80211_hw *dev) +{ + struct adm8211_priv *priv = dev->priv; + unsigned int dirty_tx; + + spin_lock(&priv->lock); + + for (dirty_tx = priv->dirty_tx; priv->cur_tx - dirty_tx; dirty_tx++) { + unsigned int entry = dirty_tx % priv->tx_ring_size; + u32 status = le32_to_cpu(priv->tx_ring[entry].status); + struct ieee80211_tx_info *txi; + struct adm8211_tx_ring_info *info; + struct sk_buff *skb; + + if (status & TDES0_CONTROL_OWN || + !(status & TDES0_CONTROL_DONE)) + break; + + info = &priv->tx_buffers[entry]; + skb = info->skb; + txi = IEEE80211_SKB_CB(skb); + + /* TODO: check TDES0_STATUS_TUF and TDES0_STATUS_TRO */ + + pci_unmap_single(priv->pdev, info->mapping, + info->skb->len, PCI_DMA_TODEVICE); + + ieee80211_tx_info_clear_status(txi); + + skb_pull(skb, sizeof(struct adm8211_tx_hdr)); + memcpy(skb_push(skb, info->hdrlen), skb->cb, info->hdrlen); + if (!(txi->flags & IEEE80211_TX_CTL_NO_ACK) && + !(status & TDES0_STATUS_ES)) + txi->flags |= IEEE80211_TX_STAT_ACK; + + ieee80211_tx_status_irqsafe(dev, skb); + + info->skb = NULL; + } + + if (priv->cur_tx - dirty_tx < priv->tx_ring_size - 2) + ieee80211_wake_queue(dev, 0); + + priv->dirty_tx = dirty_tx; + spin_unlock(&priv->lock); +} + + +static void adm8211_interrupt_rci(struct ieee80211_hw *dev) +{ + struct adm8211_priv *priv = dev->priv; + unsigned int entry = priv->cur_rx % priv->rx_ring_size; + u32 status; + unsigned int pktlen; + struct sk_buff *skb, *newskb; + unsigned int limit = priv->rx_ring_size; + u8 rssi, rate; + + while (!(priv->rx_ring[entry].status & cpu_to_le32(RDES0_STATUS_OWN))) { + if (!limit--) + break; + + status = le32_to_cpu(priv->rx_ring[entry].status); + rate = (status & RDES0_STATUS_RXDR) >> 12; + rssi = le32_to_cpu(priv->rx_ring[entry].length) & + RDES1_STATUS_RSSI; + + pktlen = status & RDES0_STATUS_FL; + if (pktlen > RX_PKT_SIZE) { + if (net_ratelimit()) + wiphy_debug(dev->wiphy, "frame too long (%d)\n", + pktlen); + pktlen = RX_PKT_SIZE; + } + + if (!priv->soft_rx_crc && status & RDES0_STATUS_ES) { + skb = NULL; /* old buffer will be reused */ + /* TODO: update RX error stats */ + /* TODO: check RDES0_STATUS_CRC*E */ + } else if (pktlen < RX_COPY_BREAK) { + skb = dev_alloc_skb(pktlen); + if (skb) { + pci_dma_sync_single_for_cpu( + priv->pdev, + priv->rx_buffers[entry].mapping, + pktlen, PCI_DMA_FROMDEVICE); + memcpy(skb_put(skb, pktlen), + skb_tail_pointer(priv->rx_buffers[entry].skb), + pktlen); + pci_dma_sync_single_for_device( + priv->pdev, + priv->rx_buffers[entry].mapping, + RX_PKT_SIZE, PCI_DMA_FROMDEVICE); + } + } else { + newskb = dev_alloc_skb(RX_PKT_SIZE); + if (newskb) { + skb = priv->rx_buffers[entry].skb; + skb_put(skb, pktlen); + pci_unmap_single( + priv->pdev, + priv->rx_buffers[entry].mapping, + RX_PKT_SIZE, PCI_DMA_FROMDEVICE); + priv->rx_buffers[entry].skb = newskb; + priv->rx_buffers[entry].mapping = + pci_map_single(priv->pdev, + skb_tail_pointer(newskb), + RX_PKT_SIZE, + PCI_DMA_FROMDEVICE); + } else { + skb = NULL; + /* TODO: update rx dropped stats */ + } + + priv->rx_ring[entry].buffer1 = + cpu_to_le32(priv->rx_buffers[entry].mapping); + } + + priv->rx_ring[entry].status = cpu_to_le32(RDES0_STATUS_OWN | + RDES0_STATUS_SQL); + priv->rx_ring[entry].length = + cpu_to_le32(RX_PKT_SIZE | + (entry == priv->rx_ring_size - 1 ? + RDES1_CONTROL_RER : 0)); + + if (skb) { + struct ieee80211_rx_status rx_status = {0}; + + if (priv->pdev->revision < ADM8211_REV_CA) + rx_status.signal = rssi; + else + rx_status.signal = 100 - rssi; + + rx_status.rate_idx = rate; + + rx_status.freq = adm8211_channels[priv->channel - 1].center_freq; + rx_status.band = IEEE80211_BAND_2GHZ; + + memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); + ieee80211_rx_irqsafe(dev, skb); + } + + entry = (++priv->cur_rx) % priv->rx_ring_size; + } + + /* TODO: check LPC and update stats? */ +} + + +static irqreturn_t adm8211_interrupt(int irq, void *dev_id) +{ +#define ADM8211_INT(x) \ +do { \ + if (unlikely(stsr & ADM8211_STSR_ ## x)) \ + wiphy_debug(dev->wiphy, "%s\n", #x); \ +} while (0) + + struct ieee80211_hw *dev = dev_id; + struct adm8211_priv *priv = dev->priv; + u32 stsr = ADM8211_CSR_READ(STSR); + ADM8211_CSR_WRITE(STSR, stsr); + if (stsr == 0xffffffff) + return IRQ_HANDLED; + + if (!(stsr & (ADM8211_STSR_NISS | ADM8211_STSR_AISS))) + return IRQ_HANDLED; + + if (stsr & ADM8211_STSR_RCI) + adm8211_interrupt_rci(dev); + if (stsr & ADM8211_STSR_TCI) + adm8211_interrupt_tci(dev); + + ADM8211_INT(PCF); + ADM8211_INT(BCNTC); + ADM8211_INT(GPINT); + ADM8211_INT(ATIMTC); + ADM8211_INT(TSFTF); + ADM8211_INT(TSCZ); + ADM8211_INT(SQL); + ADM8211_INT(WEPTD); + ADM8211_INT(ATIME); + ADM8211_INT(TEIS); + ADM8211_INT(FBE); + ADM8211_INT(REIS); + ADM8211_INT(GPTT); + ADM8211_INT(RPS); + ADM8211_INT(RDU); + ADM8211_INT(TUF); + ADM8211_INT(TPS); + + return IRQ_HANDLED; + +#undef ADM8211_INT +} + +#define WRITE_SYN(name,v_mask,v_shift,a_mask,a_shift,bits,prewrite,postwrite)\ +static void adm8211_rf_write_syn_ ## name (struct ieee80211_hw *dev, \ + u16 addr, u32 value) { \ + struct adm8211_priv *priv = dev->priv; \ + unsigned int i; \ + u32 reg, bitbuf; \ + \ + value &= v_mask; \ + addr &= a_mask; \ + bitbuf = (value << v_shift) | (addr << a_shift); \ + \ + ADM8211_CSR_WRITE(SYNRF, ADM8211_SYNRF_IF_SELECT_1); \ + ADM8211_CSR_READ(SYNRF); \ + ADM8211_CSR_WRITE(SYNRF, ADM8211_SYNRF_IF_SELECT_0); \ + ADM8211_CSR_READ(SYNRF); \ + \ + if (prewrite) { \ + ADM8211_CSR_WRITE(SYNRF, ADM8211_SYNRF_WRITE_SYNDATA_0); \ + ADM8211_CSR_READ(SYNRF); \ + } \ + \ + for (i = 0; i <= bits; i++) { \ + if (bitbuf & (1 << (bits - i))) \ + reg = ADM8211_SYNRF_WRITE_SYNDATA_1; \ + else \ + reg = ADM8211_SYNRF_WRITE_SYNDATA_0; \ + \ + ADM8211_CSR_WRITE(SYNRF, reg); \ + ADM8211_CSR_READ(SYNRF); \ + \ + ADM8211_CSR_WRITE(SYNRF, reg | ADM8211_SYNRF_WRITE_CLOCK_1); \ + ADM8211_CSR_READ(SYNRF); \ + ADM8211_CSR_WRITE(SYNRF, reg | ADM8211_SYNRF_WRITE_CLOCK_0); \ + ADM8211_CSR_READ(SYNRF); \ + } \ + \ + if (postwrite == 1) { \ + ADM8211_CSR_WRITE(SYNRF, reg | ADM8211_SYNRF_IF_SELECT_0); \ + ADM8211_CSR_READ(SYNRF); \ + } \ + if (postwrite == 2) { \ + ADM8211_CSR_WRITE(SYNRF, reg | ADM8211_SYNRF_IF_SELECT_1); \ + ADM8211_CSR_READ(SYNRF); \ + } \ + \ + ADM8211_CSR_WRITE(SYNRF, 0); \ + ADM8211_CSR_READ(SYNRF); \ +} + +WRITE_SYN(max2820, 0x00FFF, 0, 0x0F, 12, 15, 1, 1) +WRITE_SYN(al2210l, 0xFFFFF, 4, 0x0F, 0, 23, 1, 1) +WRITE_SYN(rfmd2958, 0x3FFFF, 0, 0x1F, 18, 23, 0, 1) +WRITE_SYN(rfmd2948, 0x0FFFF, 4, 0x0F, 0, 21, 0, 2) + +#undef WRITE_SYN + +static int adm8211_write_bbp(struct ieee80211_hw *dev, u8 addr, u8 data) +{ + struct adm8211_priv *priv = dev->priv; + unsigned int timeout; + u32 reg; + + timeout = 10; + while (timeout > 0) { + reg = ADM8211_CSR_READ(BBPCTL); + if (!(reg & (ADM8211_BBPCTL_WR | ADM8211_BBPCTL_RD))) + break; + timeout--; + msleep(2); + } + + if (timeout == 0) { + wiphy_debug(dev->wiphy, + "adm8211_write_bbp(%d,%d) failed prewrite (reg=0x%08x)\n", + addr, data, reg); + return -ETIMEDOUT; + } + + switch (priv->bbp_type) { + case ADM8211_TYPE_INTERSIL: + reg = ADM8211_BBPCTL_MMISEL; /* three wire interface */ + break; + case ADM8211_TYPE_RFMD: + reg = (0x20 << 24) | ADM8211_BBPCTL_TXCE | ADM8211_BBPCTL_CCAP | + (0x01 << 18); + break; + case ADM8211_TYPE_ADMTEK: + reg = (0x20 << 24) | ADM8211_BBPCTL_TXCE | ADM8211_BBPCTL_CCAP | + (0x05 << 18); + break; + } + reg |= ADM8211_BBPCTL_WR | (addr << 8) | data; + + ADM8211_CSR_WRITE(BBPCTL, reg); + + timeout = 10; + while (timeout > 0) { + reg = ADM8211_CSR_READ(BBPCTL); + if (!(reg & ADM8211_BBPCTL_WR)) + break; + timeout--; + msleep(2); + } + + if (timeout == 0) { + ADM8211_CSR_WRITE(BBPCTL, ADM8211_CSR_READ(BBPCTL) & + ~ADM8211_BBPCTL_WR); + wiphy_debug(dev->wiphy, + "adm8211_write_bbp(%d,%d) failed postwrite (reg=0x%08x)\n", + addr, data, reg); + return -ETIMEDOUT; + } + + return 0; +} + +static int adm8211_rf_set_channel(struct ieee80211_hw *dev, unsigned int chan) +{ + static const u32 adm8211_rfmd2958_reg5[] = + {0x22BD, 0x22D2, 0x22E8, 0x22FE, 0x2314, 0x232A, 0x2340, + 0x2355, 0x236B, 0x2381, 0x2397, 0x23AD, 0x23C2, 0x23F7}; + static const u32 adm8211_rfmd2958_reg6[] = + {0x05D17, 0x3A2E8, 0x2E8BA, 0x22E8B, 0x1745D, 0x0BA2E, 0x00000, + 0x345D1, 0x28BA2, 0x1D174, 0x11745, 0x05D17, 0x3A2E8, 0x11745}; + + struct adm8211_priv *priv = dev->priv; + u8 ant_power = priv->ant_power > 0x3F ? + priv->eeprom->antenna_power[chan - 1] : priv->ant_power; + u8 tx_power = priv->tx_power > 0x3F ? + priv->eeprom->tx_power[chan - 1] : priv->tx_power; + u8 lpf_cutoff = priv->lpf_cutoff == 0xFF ? + priv->eeprom->lpf_cutoff[chan - 1] : priv->lpf_cutoff; + u8 lnags_thresh = priv->lnags_threshold == 0xFF ? + priv->eeprom->lnags_threshold[chan - 1] : priv->lnags_threshold; + u32 reg; + + ADM8211_IDLE(); + + /* Program synthesizer to new channel */ + switch (priv->transceiver_type) { + case ADM8211_RFMD2958: + case ADM8211_RFMD2958_RF3000_CONTROL_POWER: + adm8211_rf_write_syn_rfmd2958(dev, 0x00, 0x04007); + adm8211_rf_write_syn_rfmd2958(dev, 0x02, 0x00033); + + adm8211_rf_write_syn_rfmd2958(dev, 0x05, + adm8211_rfmd2958_reg5[chan - 1]); + adm8211_rf_write_syn_rfmd2958(dev, 0x06, + adm8211_rfmd2958_reg6[chan - 1]); + break; + + case ADM8211_RFMD2948: + adm8211_rf_write_syn_rfmd2948(dev, SI4126_MAIN_CONF, + SI4126_MAIN_XINDIV2); + adm8211_rf_write_syn_rfmd2948(dev, SI4126_POWERDOWN, + SI4126_POWERDOWN_PDIB | + SI4126_POWERDOWN_PDRB); + adm8211_rf_write_syn_rfmd2948(dev, SI4126_PHASE_DET_GAIN, 0); + adm8211_rf_write_syn_rfmd2948(dev, SI4126_RF2_N_DIV, + (chan == 14 ? + 2110 : (2033 + (chan * 5)))); + adm8211_rf_write_syn_rfmd2948(dev, SI4126_IF_N_DIV, 1496); + adm8211_rf_write_syn_rfmd2948(dev, SI4126_RF2_R_DIV, 44); + adm8211_rf_write_syn_rfmd2948(dev, SI4126_IF_R_DIV, 44); + break; + + case ADM8211_MAX2820: + adm8211_rf_write_syn_max2820(dev, 0x3, + (chan == 14 ? 0x054 : (0x7 + (chan * 5)))); + break; + + case ADM8211_AL2210L: + adm8211_rf_write_syn_al2210l(dev, 0x0, + (chan == 14 ? 0x229B4 : (0x22967 + (chan * 5)))); + break; + + default: + wiphy_debug(dev->wiphy, "unsupported transceiver type %d\n", + priv->transceiver_type); + break; + } + + /* write BBP regs */ + if (priv->bbp_type == ADM8211_TYPE_RFMD) { + + /* SMC 2635W specific? adm8211b doesn't use the 2948 though.. */ + /* TODO: remove if SMC 2635W doesn't need this */ + if (priv->transceiver_type == ADM8211_RFMD2948) { + reg = ADM8211_CSR_READ(GPIO); + reg &= 0xfffc0000; + reg |= ADM8211_CSR_GPIO_EN0; + if (chan != 14) + reg |= ADM8211_CSR_GPIO_O0; + ADM8211_CSR_WRITE(GPIO, reg); + } + + if (priv->transceiver_type == ADM8211_RFMD2958) { + /* set PCNT2 */ + adm8211_rf_write_syn_rfmd2958(dev, 0x0B, 0x07100); + /* set PCNT1 P_DESIRED/MID_BIAS */ + reg = le16_to_cpu(priv->eeprom->cr49); + reg >>= 13; + reg <<= 15; + reg |= ant_power << 9; + adm8211_rf_write_syn_rfmd2958(dev, 0x0A, reg); + /* set TXRX TX_GAIN */ + adm8211_rf_write_syn_rfmd2958(dev, 0x09, 0x00050 | + (priv->pdev->revision < ADM8211_REV_CA ? tx_power : 0)); + } else { + reg = ADM8211_CSR_READ(PLCPHD); + reg &= 0xff00ffff; + reg |= tx_power << 18; + ADM8211_CSR_WRITE(PLCPHD, reg); + } + + ADM8211_CSR_WRITE(SYNRF, ADM8211_SYNRF_SELRF | + ADM8211_SYNRF_PE1 | ADM8211_SYNRF_PHYRST); + ADM8211_CSR_READ(SYNRF); + msleep(30); + + /* RF3000 BBP */ + if (priv->transceiver_type != ADM8211_RFMD2958) + adm8211_write_bbp(dev, RF3000_TX_VAR_GAIN__TX_LEN_EXT, + tx_power<<2); + adm8211_write_bbp(dev, RF3000_LOW_GAIN_CALIB, lpf_cutoff); + adm8211_write_bbp(dev, RF3000_HIGH_GAIN_CALIB, lnags_thresh); + adm8211_write_bbp(dev, 0x1c, priv->pdev->revision == ADM8211_REV_BA ? + priv->eeprom->cr28 : 0); + adm8211_write_bbp(dev, 0x1d, priv->eeprom->cr29); + + ADM8211_CSR_WRITE(SYNRF, 0); + + /* Nothing to do for ADMtek BBP */ + } else if (priv->bbp_type != ADM8211_TYPE_ADMTEK) + wiphy_debug(dev->wiphy, "unsupported BBP type %d\n", + priv->bbp_type); + + ADM8211_RESTORE(); + + /* update current channel for adhoc (and maybe AP mode) */ + reg = ADM8211_CSR_READ(CAP0); + reg &= ~0xF; + reg |= chan; + ADM8211_CSR_WRITE(CAP0, reg); + + return 0; +} + +static void adm8211_update_mode(struct ieee80211_hw *dev) +{ + struct adm8211_priv *priv = dev->priv; + + ADM8211_IDLE(); + + priv->soft_rx_crc = 0; + switch (priv->mode) { + case NL80211_IFTYPE_STATION: + priv->nar &= ~(ADM8211_NAR_PR | ADM8211_NAR_EA); + priv->nar |= ADM8211_NAR_ST | ADM8211_NAR_SR; + break; + case NL80211_IFTYPE_ADHOC: + priv->nar &= ~ADM8211_NAR_PR; + priv->nar |= ADM8211_NAR_EA | ADM8211_NAR_ST | ADM8211_NAR_SR; + + /* don't trust the error bits on rev 0x20 and up in adhoc */ + if (priv->pdev->revision >= ADM8211_REV_BA) + priv->soft_rx_crc = 1; + break; + case NL80211_IFTYPE_MONITOR: + priv->nar &= ~(ADM8211_NAR_EA | ADM8211_NAR_ST); + priv->nar |= ADM8211_NAR_PR | ADM8211_NAR_SR; + break; + } + + ADM8211_RESTORE(); +} + +static void adm8211_hw_init_syn(struct ieee80211_hw *dev) +{ + struct adm8211_priv *priv = dev->priv; + + switch (priv->transceiver_type) { + case ADM8211_RFMD2958: + case ADM8211_RFMD2958_RF3000_CONTROL_POWER: + /* comments taken from ADMtek vendor driver */ + + /* Reset RF2958 after power on */ + adm8211_rf_write_syn_rfmd2958(dev, 0x1F, 0x00000); + /* Initialize RF VCO Core Bias to maximum */ + adm8211_rf_write_syn_rfmd2958(dev, 0x0C, 0x3001F); + /* Initialize IF PLL */ + adm8211_rf_write_syn_rfmd2958(dev, 0x01, 0x29C03); + /* Initialize IF PLL Coarse Tuning */ + adm8211_rf_write_syn_rfmd2958(dev, 0x03, 0x1FF6F); + /* Initialize RF PLL */ + adm8211_rf_write_syn_rfmd2958(dev, 0x04, 0x29403); + /* Initialize RF PLL Coarse Tuning */ + adm8211_rf_write_syn_rfmd2958(dev, 0x07, 0x1456F); + /* Initialize TX gain and filter BW (R9) */ + adm8211_rf_write_syn_rfmd2958(dev, 0x09, + (priv->transceiver_type == ADM8211_RFMD2958 ? + 0x10050 : 0x00050)); + /* Initialize CAL register */ + adm8211_rf_write_syn_rfmd2958(dev, 0x08, 0x3FFF8); + break; + + case ADM8211_MAX2820: + adm8211_rf_write_syn_max2820(dev, 0x1, 0x01E); + adm8211_rf_write_syn_max2820(dev, 0x2, 0x001); + adm8211_rf_write_syn_max2820(dev, 0x3, 0x054); + adm8211_rf_write_syn_max2820(dev, 0x4, 0x310); + adm8211_rf_write_syn_max2820(dev, 0x5, 0x000); + break; + + case ADM8211_AL2210L: + adm8211_rf_write_syn_al2210l(dev, 0x0, 0x0196C); + adm8211_rf_write_syn_al2210l(dev, 0x1, 0x007CB); + adm8211_rf_write_syn_al2210l(dev, 0x2, 0x3582F); + adm8211_rf_write_syn_al2210l(dev, 0x3, 0x010A9); + adm8211_rf_write_syn_al2210l(dev, 0x4, 0x77280); + adm8211_rf_write_syn_al2210l(dev, 0x5, 0x45641); + adm8211_rf_write_syn_al2210l(dev, 0x6, 0xEA130); + adm8211_rf_write_syn_al2210l(dev, 0x7, 0x80000); + adm8211_rf_write_syn_al2210l(dev, 0x8, 0x7850F); + adm8211_rf_write_syn_al2210l(dev, 0x9, 0xF900C); + adm8211_rf_write_syn_al2210l(dev, 0xA, 0x00000); + adm8211_rf_write_syn_al2210l(dev, 0xB, 0x00000); + break; + + case ADM8211_RFMD2948: + default: + break; + } +} + +static int adm8211_hw_init_bbp(struct ieee80211_hw *dev) +{ + struct adm8211_priv *priv = dev->priv; + u32 reg; + + /* write addresses */ + if (priv->bbp_type == ADM8211_TYPE_INTERSIL) { + ADM8211_CSR_WRITE(MMIWA, 0x100E0C0A); + ADM8211_CSR_WRITE(MMIRD0, 0x00007C7E); + ADM8211_CSR_WRITE(MMIRD1, 0x00100000); + } else if (priv->bbp_type == ADM8211_TYPE_RFMD || + priv->bbp_type == ADM8211_TYPE_ADMTEK) { + /* check specific BBP type */ + switch (priv->specific_bbptype) { + case ADM8211_BBP_RFMD3000: + case ADM8211_BBP_RFMD3002: + ADM8211_CSR_WRITE(MMIWA, 0x00009101); + ADM8211_CSR_WRITE(MMIRD0, 0x00000301); + break; + + case ADM8211_BBP_ADM8011: + ADM8211_CSR_WRITE(MMIWA, 0x00008903); + ADM8211_CSR_WRITE(MMIRD0, 0x00001716); + + reg = ADM8211_CSR_READ(BBPCTL); + reg &= ~ADM8211_BBPCTL_TYPE; + reg |= 0x5 << 18; + ADM8211_CSR_WRITE(BBPCTL, reg); + break; + } + + switch (priv->pdev->revision) { + case ADM8211_REV_CA: + if (priv->transceiver_type == ADM8211_RFMD2958 || + priv->transceiver_type == ADM8211_RFMD2958_RF3000_CONTROL_POWER || + priv->transceiver_type == ADM8211_RFMD2948) + ADM8211_CSR_WRITE(SYNCTL, 0x1 << 22); + else if (priv->transceiver_type == ADM8211_MAX2820 || + priv->transceiver_type == ADM8211_AL2210L) + ADM8211_CSR_WRITE(SYNCTL, 0x3 << 22); + break; + + case ADM8211_REV_BA: + reg = ADM8211_CSR_READ(MMIRD1); + reg &= 0x0000FFFF; + reg |= 0x7e100000; + ADM8211_CSR_WRITE(MMIRD1, reg); + break; + + case ADM8211_REV_AB: + case ADM8211_REV_AF: + default: + ADM8211_CSR_WRITE(MMIRD1, 0x7e100000); + break; + } + + /* For RFMD */ + ADM8211_CSR_WRITE(MACTEST, 0x800); + } + + adm8211_hw_init_syn(dev); + + /* Set RF Power control IF pin to PE1+PHYRST# */ + ADM8211_CSR_WRITE(SYNRF, ADM8211_SYNRF_SELRF | + ADM8211_SYNRF_PE1 | ADM8211_SYNRF_PHYRST); + ADM8211_CSR_READ(SYNRF); + msleep(20); + + /* write BBP regs */ + if (priv->bbp_type == ADM8211_TYPE_RFMD) { + /* RF3000 BBP */ + /* another set: + * 11: c8 + * 14: 14 + * 15: 50 (chan 1..13; chan 14: d0) + * 1c: 00 + * 1d: 84 + */ + adm8211_write_bbp(dev, RF3000_CCA_CTRL, 0x80); + /* antenna selection: diversity */ + adm8211_write_bbp(dev, RF3000_DIVERSITY__RSSI, 0x80); + adm8211_write_bbp(dev, RF3000_TX_VAR_GAIN__TX_LEN_EXT, 0x74); + adm8211_write_bbp(dev, RF3000_LOW_GAIN_CALIB, 0x38); + adm8211_write_bbp(dev, RF3000_HIGH_GAIN_CALIB, 0x40); + + if (priv->eeprom->major_version < 2) { + adm8211_write_bbp(dev, 0x1c, 0x00); + adm8211_write_bbp(dev, 0x1d, 0x80); + } else { + if (priv->pdev->revision == ADM8211_REV_BA) + adm8211_write_bbp(dev, 0x1c, priv->eeprom->cr28); + else + adm8211_write_bbp(dev, 0x1c, 0x00); + + adm8211_write_bbp(dev, 0x1d, priv->eeprom->cr29); + } + } else if (priv->bbp_type == ADM8211_TYPE_ADMTEK) { + /* reset baseband */ + adm8211_write_bbp(dev, 0x00, 0xFF); + /* antenna selection: diversity */ + adm8211_write_bbp(dev, 0x07, 0x0A); + + /* TODO: find documentation for this */ + switch (priv->transceiver_type) { + case ADM8211_RFMD2958: + case ADM8211_RFMD2958_RF3000_CONTROL_POWER: + adm8211_write_bbp(dev, 0x00, 0x00); + adm8211_write_bbp(dev, 0x01, 0x00); + adm8211_write_bbp(dev, 0x02, 0x00); + adm8211_write_bbp(dev, 0x03, 0x00); + adm8211_write_bbp(dev, 0x06, 0x0f); + adm8211_write_bbp(dev, 0x09, 0x00); + adm8211_write_bbp(dev, 0x0a, 0x00); + adm8211_write_bbp(dev, 0x0b, 0x00); + adm8211_write_bbp(dev, 0x0c, 0x00); + adm8211_write_bbp(dev, 0x0f, 0xAA); + adm8211_write_bbp(dev, 0x10, 0x8c); + adm8211_write_bbp(dev, 0x11, 0x43); + adm8211_write_bbp(dev, 0x18, 0x40); + adm8211_write_bbp(dev, 0x20, 0x23); + adm8211_write_bbp(dev, 0x21, 0x02); + adm8211_write_bbp(dev, 0x22, 0x28); + adm8211_write_bbp(dev, 0x23, 0x30); + adm8211_write_bbp(dev, 0x24, 0x2d); + adm8211_write_bbp(dev, 0x28, 0x35); + adm8211_write_bbp(dev, 0x2a, 0x8c); + adm8211_write_bbp(dev, 0x2b, 0x81); + adm8211_write_bbp(dev, 0x2c, 0x44); + adm8211_write_bbp(dev, 0x2d, 0x0A); + adm8211_write_bbp(dev, 0x29, 0x40); + adm8211_write_bbp(dev, 0x60, 0x08); + adm8211_write_bbp(dev, 0x64, 0x01); + break; + + case ADM8211_MAX2820: + adm8211_write_bbp(dev, 0x00, 0x00); + adm8211_write_bbp(dev, 0x01, 0x00); + adm8211_write_bbp(dev, 0x02, 0x00); + adm8211_write_bbp(dev, 0x03, 0x00); + adm8211_write_bbp(dev, 0x06, 0x0f); + adm8211_write_bbp(dev, 0x09, 0x05); + adm8211_write_bbp(dev, 0x0a, 0x02); + adm8211_write_bbp(dev, 0x0b, 0x00); + adm8211_write_bbp(dev, 0x0c, 0x0f); + adm8211_write_bbp(dev, 0x0f, 0x55); + adm8211_write_bbp(dev, 0x10, 0x8d); + adm8211_write_bbp(dev, 0x11, 0x43); + adm8211_write_bbp(dev, 0x18, 0x4a); + adm8211_write_bbp(dev, 0x20, 0x20); + adm8211_write_bbp(dev, 0x21, 0x02); + adm8211_write_bbp(dev, 0x22, 0x23); + adm8211_write_bbp(dev, 0x23, 0x30); + adm8211_write_bbp(dev, 0x24, 0x2d); + adm8211_write_bbp(dev, 0x2a, 0x8c); + adm8211_write_bbp(dev, 0x2b, 0x81); + adm8211_write_bbp(dev, 0x2c, 0x44); + adm8211_write_bbp(dev, 0x29, 0x4a); + adm8211_write_bbp(dev, 0x60, 0x2b); + adm8211_write_bbp(dev, 0x64, 0x01); + break; + + case ADM8211_AL2210L: + adm8211_write_bbp(dev, 0x00, 0x00); + adm8211_write_bbp(dev, 0x01, 0x00); + adm8211_write_bbp(dev, 0x02, 0x00); + adm8211_write_bbp(dev, 0x03, 0x00); + adm8211_write_bbp(dev, 0x06, 0x0f); + adm8211_write_bbp(dev, 0x07, 0x05); + adm8211_write_bbp(dev, 0x08, 0x03); + adm8211_write_bbp(dev, 0x09, 0x00); + adm8211_write_bbp(dev, 0x0a, 0x00); + adm8211_write_bbp(dev, 0x0b, 0x00); + adm8211_write_bbp(dev, 0x0c, 0x10); + adm8211_write_bbp(dev, 0x0f, 0x55); + adm8211_write_bbp(dev, 0x10, 0x8d); + adm8211_write_bbp(dev, 0x11, 0x43); + adm8211_write_bbp(dev, 0x18, 0x4a); + adm8211_write_bbp(dev, 0x20, 0x20); + adm8211_write_bbp(dev, 0x21, 0x02); + adm8211_write_bbp(dev, 0x22, 0x23); + adm8211_write_bbp(dev, 0x23, 0x30); + adm8211_write_bbp(dev, 0x24, 0x2d); + adm8211_write_bbp(dev, 0x2a, 0xaa); + adm8211_write_bbp(dev, 0x2b, 0x81); + adm8211_write_bbp(dev, 0x2c, 0x44); + adm8211_write_bbp(dev, 0x29, 0xfa); + adm8211_write_bbp(dev, 0x60, 0x2d); + adm8211_write_bbp(dev, 0x64, 0x01); + break; + + case ADM8211_RFMD2948: + break; + + default: + wiphy_debug(dev->wiphy, "unsupported transceiver %d\n", + priv->transceiver_type); + break; + } + } else + wiphy_debug(dev->wiphy, "unsupported BBP %d\n", priv->bbp_type); + + ADM8211_CSR_WRITE(SYNRF, 0); + + /* Set RF CAL control source to MAC control */ + reg = ADM8211_CSR_READ(SYNCTL); + reg |= ADM8211_SYNCTL_SELCAL; + ADM8211_CSR_WRITE(SYNCTL, reg); + + return 0; +} + +/* configures hw beacons/probe responses */ +static int adm8211_set_rate(struct ieee80211_hw *dev) +{ + struct adm8211_priv *priv = dev->priv; + u32 reg; + int i = 0; + u8 rate_buf[12] = {0}; + + /* write supported rates */ + if (priv->pdev->revision != ADM8211_REV_BA) { + rate_buf[0] = ARRAY_SIZE(adm8211_rates); + for (i = 0; i < ARRAY_SIZE(adm8211_rates); i++) + rate_buf[i + 1] = (adm8211_rates[i].bitrate / 5) | 0x80; + } else { + /* workaround for rev BA specific bug */ + rate_buf[0] = 0x04; + rate_buf[1] = 0x82; + rate_buf[2] = 0x04; + rate_buf[3] = 0x0b; + rate_buf[4] = 0x16; + } + + adm8211_write_sram_bytes(dev, ADM8211_SRAM_SUPP_RATE, rate_buf, + ARRAY_SIZE(adm8211_rates) + 1); + + reg = ADM8211_CSR_READ(PLCPHD) & 0x00FFFFFF; /* keep bits 0-23 */ + reg |= 1 << 15; /* short preamble */ + reg |= 110 << 24; + ADM8211_CSR_WRITE(PLCPHD, reg); + + /* MTMLT = 512 TU (max TX MSDU lifetime) + * BCNTSIG = plcp_signal (beacon, probe resp, and atim TX rate) + * SRTYLIM = 224 (short retry limit, TX header value is default) */ + ADM8211_CSR_WRITE(TXLMT, (512 << 16) | (110 << 8) | (224 << 0)); + + return 0; +} + +static void adm8211_hw_init(struct ieee80211_hw *dev) +{ + struct adm8211_priv *priv = dev->priv; + u32 reg; + u8 cline; + + reg = ADM8211_CSR_READ(PAR); + reg |= ADM8211_PAR_MRLE | ADM8211_PAR_MRME; + reg &= ~(ADM8211_PAR_BAR | ADM8211_PAR_CAL); + + if (!pci_set_mwi(priv->pdev)) { + reg |= 0x1 << 24; + pci_read_config_byte(priv->pdev, PCI_CACHE_LINE_SIZE, &cline); + + switch (cline) { + case 0x8: reg |= (0x1 << 14); + break; + case 0x16: reg |= (0x2 << 14); + break; + case 0x32: reg |= (0x3 << 14); + break; + default: reg |= (0x0 << 14); + break; + } + } + + ADM8211_CSR_WRITE(PAR, reg); + + reg = ADM8211_CSR_READ(CSR_TEST1); + reg &= ~(0xF << 28); + reg |= (1 << 28) | (1 << 31); + ADM8211_CSR_WRITE(CSR_TEST1, reg); + + /* lose link after 4 lost beacons */ + reg = (0x04 << 21) | ADM8211_WCSR_TSFTWE | ADM8211_WCSR_LSOE; + ADM8211_CSR_WRITE(WCSR, reg); + + /* Disable APM, enable receive FIFO threshold, and set drain receive + * threshold to store-and-forward */ + reg = ADM8211_CSR_READ(CMDR); + reg &= ~(ADM8211_CMDR_APM | ADM8211_CMDR_DRT); + reg |= ADM8211_CMDR_RTE | ADM8211_CMDR_DRT_SF; + ADM8211_CSR_WRITE(CMDR, reg); + + adm8211_set_rate(dev); + + /* 4-bit values: + * PWR1UP = 8 * 2 ms + * PWR0PAPE = 8 us or 5 us + * PWR1PAPE = 1 us or 3 us + * PWR0TRSW = 5 us + * PWR1TRSW = 12 us + * PWR0PE2 = 13 us + * PWR1PE2 = 1 us + * PWR0TXPE = 8 or 6 */ + if (priv->pdev->revision < ADM8211_REV_CA) + ADM8211_CSR_WRITE(TOFS2, 0x8815cd18); + else + ADM8211_CSR_WRITE(TOFS2, 0x8535cd16); + + /* Enable store and forward for transmit */ + priv->nar = ADM8211_NAR_SF | ADM8211_NAR_PB; + ADM8211_CSR_WRITE(NAR, priv->nar); + + /* Reset RF */ + ADM8211_CSR_WRITE(SYNRF, ADM8211_SYNRF_RADIO); + ADM8211_CSR_READ(SYNRF); + msleep(10); + ADM8211_CSR_WRITE(SYNRF, 0); + ADM8211_CSR_READ(SYNRF); + msleep(5); + + /* Set CFP Max Duration to 0x10 TU */ + reg = ADM8211_CSR_READ(CFPP); + reg &= ~(0xffff << 8); + reg |= 0x0010 << 8; + ADM8211_CSR_WRITE(CFPP, reg); + + /* USCNT = 0x16 (number of system clocks, 22 MHz, in 1us + * TUCNT = 0x3ff - Tu counter 1024 us */ + ADM8211_CSR_WRITE(TOFS0, (0x16 << 24) | 0x3ff); + + /* SLOT=20 us, SIFS=110 cycles of 22 MHz (5 us), + * DIFS=50 us, EIFS=100 us */ + if (priv->pdev->revision < ADM8211_REV_CA) + ADM8211_CSR_WRITE(IFST, (20 << 23) | (110 << 15) | + (50 << 9) | 100); + else + ADM8211_CSR_WRITE(IFST, (20 << 23) | (24 << 15) | + (50 << 9) | 100); + + /* PCNT = 1 (MAC idle time awake/sleep, unit S) + * RMRD = 2346 * 8 + 1 us (max RX duration) */ + ADM8211_CSR_WRITE(RMD, (1 << 16) | 18769); + + /* MART=65535 us, MIRT=256 us, TSFTOFST=0 us */ + ADM8211_CSR_WRITE(RSPT, 0xffffff00); + + /* Initialize BBP (and SYN) */ + adm8211_hw_init_bbp(dev); + + /* make sure interrupts are off */ + ADM8211_CSR_WRITE(IER, 0); + + /* ACK interrupts */ + ADM8211_CSR_WRITE(STSR, ADM8211_CSR_READ(STSR)); + + /* Setup WEP (turns it off for now) */ + reg = ADM8211_CSR_READ(MACTEST); + reg &= ~(7 << 20); + ADM8211_CSR_WRITE(MACTEST, reg); + + reg = ADM8211_CSR_READ(WEPCTL); + reg &= ~ADM8211_WEPCTL_WEPENABLE; + reg |= ADM8211_WEPCTL_WEPRXBYP; + ADM8211_CSR_WRITE(WEPCTL, reg); + + /* Clear the missed-packet counter. */ + ADM8211_CSR_READ(LPC); +} + +static int adm8211_hw_reset(struct ieee80211_hw *dev) +{ + struct adm8211_priv *priv = dev->priv; + u32 reg, tmp; + int timeout = 100; + + /* Power-on issue */ + /* TODO: check if this is necessary */ + ADM8211_CSR_WRITE(FRCTL, 0); + + /* Reset the chip */ + tmp = ADM8211_CSR_READ(PAR); + ADM8211_CSR_WRITE(PAR, ADM8211_PAR_SWR); + + while ((ADM8211_CSR_READ(PAR) & ADM8211_PAR_SWR) && timeout--) + msleep(50); + + if (timeout <= 0) + return -ETIMEDOUT; + + ADM8211_CSR_WRITE(PAR, tmp); + + if (priv->pdev->revision == ADM8211_REV_BA && + (priv->transceiver_type == ADM8211_RFMD2958_RF3000_CONTROL_POWER || + priv->transceiver_type == ADM8211_RFMD2958)) { + reg = ADM8211_CSR_READ(CSR_TEST1); + reg |= (1 << 4) | (1 << 5); + ADM8211_CSR_WRITE(CSR_TEST1, reg); + } else if (priv->pdev->revision == ADM8211_REV_CA) { + reg = ADM8211_CSR_READ(CSR_TEST1); + reg &= ~((1 << 4) | (1 << 5)); + ADM8211_CSR_WRITE(CSR_TEST1, reg); + } + + ADM8211_CSR_WRITE(FRCTL, 0); + + reg = ADM8211_CSR_READ(CSR_TEST0); + reg |= ADM8211_CSR_TEST0_EPRLD; /* EEPROM Recall */ + ADM8211_CSR_WRITE(CSR_TEST0, reg); + + adm8211_clear_sram(dev); + + return 0; +} + +static u64 adm8211_get_tsft(struct ieee80211_hw *dev, + struct ieee80211_vif *vif) +{ + struct adm8211_priv *priv = dev->priv; + u32 tsftl; + u64 tsft; + + tsftl = ADM8211_CSR_READ(TSFTL); + tsft = ADM8211_CSR_READ(TSFTH); + tsft <<= 32; + tsft |= tsftl; + + return tsft; +} + +static void adm8211_set_interval(struct ieee80211_hw *dev, + unsigned short bi, unsigned short li) +{ + struct adm8211_priv *priv = dev->priv; + u32 reg; + + /* BP (beacon interval) = data->beacon_interval + * LI (listen interval) = data->listen_interval (in beacon intervals) */ + reg = (bi << 16) | li; + ADM8211_CSR_WRITE(BPLI, reg); +} + +static void adm8211_set_bssid(struct ieee80211_hw *dev, const u8 *bssid) +{ + struct adm8211_priv *priv = dev->priv; + u32 reg; + + ADM8211_CSR_WRITE(BSSID0, le32_to_cpu(*(__le32 *)bssid)); + reg = ADM8211_CSR_READ(ABDA1); + reg &= 0x0000ffff; + reg |= (bssid[4] << 16) | (bssid[5] << 24); + ADM8211_CSR_WRITE(ABDA1, reg); +} + +static int adm8211_config(struct ieee80211_hw *dev, u32 changed) +{ + struct adm8211_priv *priv = dev->priv; + struct ieee80211_conf *conf = &dev->conf; + int channel = + ieee80211_frequency_to_channel(conf->chandef.chan->center_freq); + + if (channel != priv->channel) { + priv->channel = channel; + adm8211_rf_set_channel(dev, priv->channel); + } + + return 0; +} + +static void adm8211_bss_info_changed(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *conf, + u32 changes) +{ + struct adm8211_priv *priv = dev->priv; + + if (!(changes & BSS_CHANGED_BSSID)) + return; + + if (memcmp(conf->bssid, priv->bssid, ETH_ALEN)) { + adm8211_set_bssid(dev, conf->bssid); + memcpy(priv->bssid, conf->bssid, ETH_ALEN); + } +} + +static u64 adm8211_prepare_multicast(struct ieee80211_hw *hw, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + struct netdev_hw_addr_list *mc_list) +#else + int mc_count, struct dev_addr_list *ha) +#endif +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + unsigned int bit_nr; + struct netdev_hw_addr *ha; +#else + unsigned int bit_nr, i; +#endif + u32 mc_filter[2]; + + mc_filter[1] = mc_filter[0] = 0; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + netdev_hw_addr_list_for_each(ha, mc_list) { + bit_nr = ether_crc(ETH_ALEN, ha->addr) >> 26; +#else + for (i = 0; i < mc_count; i++) { + if (!ha) + break; + bit_nr = ether_crc(ETH_ALEN, ha->dmi_addr) >> 26; +#endif + + bit_nr &= 0x3F; + mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)) + ha = ha->next; +#endif + } + + return mc_filter[0] | ((u64)(mc_filter[1]) << 32); +} + +static void adm8211_configure_filter(struct ieee80211_hw *dev, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + static const u8 bcast[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + struct adm8211_priv *priv = dev->priv; + unsigned int new_flags; + u32 mc_filter[2]; + + mc_filter[0] = multicast; + mc_filter[1] = multicast >> 32; + + new_flags = 0; + + if (*total_flags & FIF_PROMISC_IN_BSS) { + new_flags |= FIF_PROMISC_IN_BSS; + priv->nar |= ADM8211_NAR_PR; + priv->nar &= ~ADM8211_NAR_MM; + mc_filter[1] = mc_filter[0] = ~0; + } else if (*total_flags & FIF_ALLMULTI || multicast == ~(0ULL)) { + new_flags |= FIF_ALLMULTI; + priv->nar &= ~ADM8211_NAR_PR; + priv->nar |= ADM8211_NAR_MM; + mc_filter[1] = mc_filter[0] = ~0; + } else { + priv->nar &= ~(ADM8211_NAR_MM | ADM8211_NAR_PR); + } + + ADM8211_IDLE_RX(); + + ADM8211_CSR_WRITE(MAR0, mc_filter[0]); + ADM8211_CSR_WRITE(MAR1, mc_filter[1]); + ADM8211_CSR_READ(NAR); + + if (priv->nar & ADM8211_NAR_PR) + dev->flags |= IEEE80211_HW_RX_INCLUDES_FCS; + else + dev->flags &= ~IEEE80211_HW_RX_INCLUDES_FCS; + + if (*total_flags & FIF_BCN_PRBRESP_PROMISC) + adm8211_set_bssid(dev, bcast); + else + adm8211_set_bssid(dev, priv->bssid); + + ADM8211_RESTORE(); + + *total_flags = new_flags; +} + +static int adm8211_add_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif) +{ + struct adm8211_priv *priv = dev->priv; + if (priv->mode != NL80211_IFTYPE_MONITOR) + return -EOPNOTSUPP; + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + priv->mode = vif->type; + break; + default: + return -EOPNOTSUPP; + } + + ADM8211_IDLE(); + + ADM8211_CSR_WRITE(PAR0, le32_to_cpu(*(__le32 *)vif->addr)); + ADM8211_CSR_WRITE(PAR1, le16_to_cpu(*(__le16 *)(vif->addr + 4))); + + adm8211_update_mode(dev); + + ADM8211_RESTORE(); + + return 0; +} + +static void adm8211_remove_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif) +{ + struct adm8211_priv *priv = dev->priv; + priv->mode = NL80211_IFTYPE_MONITOR; +} + +static int adm8211_init_rings(struct ieee80211_hw *dev) +{ + struct adm8211_priv *priv = dev->priv; + struct adm8211_desc *desc = NULL; + struct adm8211_rx_ring_info *rx_info; + struct adm8211_tx_ring_info *tx_info; + unsigned int i; + + for (i = 0; i < priv->rx_ring_size; i++) { + desc = &priv->rx_ring[i]; + desc->status = 0; + desc->length = cpu_to_le32(RX_PKT_SIZE); + priv->rx_buffers[i].skb = NULL; + } + /* Mark the end of RX ring; hw returns to base address after this + * descriptor */ + desc->length |= cpu_to_le32(RDES1_CONTROL_RER); + + for (i = 0; i < priv->rx_ring_size; i++) { + desc = &priv->rx_ring[i]; + rx_info = &priv->rx_buffers[i]; + + rx_info->skb = dev_alloc_skb(RX_PKT_SIZE); + if (rx_info->skb == NULL) + break; + rx_info->mapping = pci_map_single(priv->pdev, + skb_tail_pointer(rx_info->skb), + RX_PKT_SIZE, + PCI_DMA_FROMDEVICE); + desc->buffer1 = cpu_to_le32(rx_info->mapping); + desc->status = cpu_to_le32(RDES0_STATUS_OWN | RDES0_STATUS_SQL); + } + + /* Setup TX ring. TX buffers descriptors will be filled in as needed */ + for (i = 0; i < priv->tx_ring_size; i++) { + desc = &priv->tx_ring[i]; + tx_info = &priv->tx_buffers[i]; + + tx_info->skb = NULL; + tx_info->mapping = 0; + desc->status = 0; + } + desc->length = cpu_to_le32(TDES1_CONTROL_TER); + + priv->cur_rx = priv->cur_tx = priv->dirty_tx = 0; + ADM8211_CSR_WRITE(RDB, priv->rx_ring_dma); + ADM8211_CSR_WRITE(TDBD, priv->tx_ring_dma); + + return 0; +} + +static void adm8211_free_rings(struct ieee80211_hw *dev) +{ + struct adm8211_priv *priv = dev->priv; + unsigned int i; + + for (i = 0; i < priv->rx_ring_size; i++) { + if (!priv->rx_buffers[i].skb) + continue; + + pci_unmap_single( + priv->pdev, + priv->rx_buffers[i].mapping, + RX_PKT_SIZE, PCI_DMA_FROMDEVICE); + + dev_kfree_skb(priv->rx_buffers[i].skb); + } + + for (i = 0; i < priv->tx_ring_size; i++) { + if (!priv->tx_buffers[i].skb) + continue; + + pci_unmap_single(priv->pdev, + priv->tx_buffers[i].mapping, + priv->tx_buffers[i].skb->len, + PCI_DMA_TODEVICE); + + dev_kfree_skb(priv->tx_buffers[i].skb); + } +} + +static int adm8211_start(struct ieee80211_hw *dev) +{ + struct adm8211_priv *priv = dev->priv; + int retval; + + /* Power up MAC and RF chips */ + retval = adm8211_hw_reset(dev); + if (retval) { + wiphy_err(dev->wiphy, "hardware reset failed\n"); + goto fail; + } + + retval = adm8211_init_rings(dev); + if (retval) { + wiphy_err(dev->wiphy, "failed to initialize rings\n"); + goto fail; + } + + /* Init hardware */ + adm8211_hw_init(dev); + adm8211_rf_set_channel(dev, priv->channel); + + retval = request_irq(priv->pdev->irq, adm8211_interrupt, + IRQF_SHARED, "adm8211", dev); + if (retval) { + wiphy_err(dev->wiphy, "failed to register IRQ handler\n"); + goto fail; + } + + ADM8211_CSR_WRITE(IER, ADM8211_IER_NIE | ADM8211_IER_AIE | + ADM8211_IER_RCIE | ADM8211_IER_TCIE | + ADM8211_IER_TDUIE | ADM8211_IER_GPTIE); + priv->mode = NL80211_IFTYPE_MONITOR; + adm8211_update_mode(dev); + ADM8211_CSR_WRITE(RDR, 0); + + adm8211_set_interval(dev, 100, 10); + return 0; + +fail: + return retval; +} + +static void adm8211_stop(struct ieee80211_hw *dev) +{ + struct adm8211_priv *priv = dev->priv; + + priv->mode = NL80211_IFTYPE_UNSPECIFIED; + priv->nar = 0; + ADM8211_CSR_WRITE(NAR, 0); + ADM8211_CSR_WRITE(IER, 0); + ADM8211_CSR_READ(NAR); + + free_irq(priv->pdev->irq, dev); + + adm8211_free_rings(dev); +} + +static void adm8211_calc_durations(int *dur, int *plcp, size_t payload_len, int len, + int plcp_signal, int short_preamble) +{ + /* Alternative calculation from NetBSD: */ + +/* IEEE 802.11b durations for DSSS PHY in microseconds */ +#define IEEE80211_DUR_DS_LONG_PREAMBLE 144 +#define IEEE80211_DUR_DS_SHORT_PREAMBLE 72 +#define IEEE80211_DUR_DS_FAST_PLCPHDR 24 +#define IEEE80211_DUR_DS_SLOW_PLCPHDR 48 +#define IEEE80211_DUR_DS_SLOW_ACK 112 +#define IEEE80211_DUR_DS_FAST_ACK 56 +#define IEEE80211_DUR_DS_SLOW_CTS 112 +#define IEEE80211_DUR_DS_FAST_CTS 56 +#define IEEE80211_DUR_DS_SLOT 20 +#define IEEE80211_DUR_DS_SIFS 10 + + int remainder; + + *dur = (80 * (24 + payload_len) + plcp_signal - 1) + / plcp_signal; + + if (plcp_signal <= PLCP_SIGNAL_2M) + /* 1-2Mbps WLAN: send ACK/CTS at 1Mbps */ + *dur += 3 * (IEEE80211_DUR_DS_SIFS + + IEEE80211_DUR_DS_SHORT_PREAMBLE + + IEEE80211_DUR_DS_FAST_PLCPHDR) + + IEEE80211_DUR_DS_SLOW_CTS + IEEE80211_DUR_DS_SLOW_ACK; + else + /* 5-11Mbps WLAN: send ACK/CTS at 2Mbps */ + *dur += 3 * (IEEE80211_DUR_DS_SIFS + + IEEE80211_DUR_DS_SHORT_PREAMBLE + + IEEE80211_DUR_DS_FAST_PLCPHDR) + + IEEE80211_DUR_DS_FAST_CTS + IEEE80211_DUR_DS_FAST_ACK; + + /* lengthen duration if long preamble */ + if (!short_preamble) + *dur += 3 * (IEEE80211_DUR_DS_LONG_PREAMBLE - + IEEE80211_DUR_DS_SHORT_PREAMBLE) + + 3 * (IEEE80211_DUR_DS_SLOW_PLCPHDR - + IEEE80211_DUR_DS_FAST_PLCPHDR); + + + *plcp = (80 * len) / plcp_signal; + remainder = (80 * len) % plcp_signal; + if (plcp_signal == PLCP_SIGNAL_11M && + remainder <= 30 && remainder > 0) + *plcp = (*plcp | 0x8000) + 1; + else if (remainder) + (*plcp)++; +} + +/* Transmit skb w/adm8211_tx_hdr (802.11 header created by hardware) */ +static void adm8211_tx_raw(struct ieee80211_hw *dev, struct sk_buff *skb, + u16 plcp_signal, + size_t hdrlen) +{ + struct adm8211_priv *priv = dev->priv; + unsigned long flags; + dma_addr_t mapping; + unsigned int entry; + u32 flag; + + mapping = pci_map_single(priv->pdev, skb->data, skb->len, + PCI_DMA_TODEVICE); + + spin_lock_irqsave(&priv->lock, flags); + + if (priv->cur_tx - priv->dirty_tx == priv->tx_ring_size / 2) + flag = TDES1_CONTROL_IC | TDES1_CONTROL_LS | TDES1_CONTROL_FS; + else + flag = TDES1_CONTROL_LS | TDES1_CONTROL_FS; + + if (priv->cur_tx - priv->dirty_tx == priv->tx_ring_size - 2) + ieee80211_stop_queue(dev, 0); + + entry = priv->cur_tx % priv->tx_ring_size; + + priv->tx_buffers[entry].skb = skb; + priv->tx_buffers[entry].mapping = mapping; + priv->tx_buffers[entry].hdrlen = hdrlen; + priv->tx_ring[entry].buffer1 = cpu_to_le32(mapping); + + if (entry == priv->tx_ring_size - 1) + flag |= TDES1_CONTROL_TER; + priv->tx_ring[entry].length = cpu_to_le32(flag | skb->len); + + /* Set TX rate (SIGNAL field in PLCP PPDU format) */ + flag = TDES0_CONTROL_OWN | (plcp_signal << 20) | 8 /* ? */; + priv->tx_ring[entry].status = cpu_to_le32(flag); + + priv->cur_tx++; + + spin_unlock_irqrestore(&priv->lock, flags); + + /* Trigger transmit poll */ + ADM8211_CSR_WRITE(TDR, 0); +} + +/* Put adm8211_tx_hdr on skb and transmit */ +static void adm8211_tx(struct ieee80211_hw *dev, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct adm8211_tx_hdr *txhdr; + size_t payload_len, hdrlen; + int plcp, dur, len, plcp_signal, short_preamble; + struct ieee80211_hdr *hdr; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_rate *txrate = ieee80211_get_tx_rate(dev, info); + u8 rc_flags; + + rc_flags = info->control.rates[0].flags; + short_preamble = !!(rc_flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE); + plcp_signal = txrate->bitrate; + + hdr = (struct ieee80211_hdr *)skb->data; + hdrlen = ieee80211_hdrlen(hdr->frame_control); + memcpy(skb->cb, skb->data, hdrlen); + hdr = (struct ieee80211_hdr *)skb->cb; + skb_pull(skb, hdrlen); + payload_len = skb->len; + + txhdr = (struct adm8211_tx_hdr *) skb_push(skb, sizeof(*txhdr)); + memset(txhdr, 0, sizeof(*txhdr)); + memcpy(txhdr->da, ieee80211_get_DA(hdr), ETH_ALEN); + txhdr->signal = plcp_signal; + txhdr->frame_body_size = cpu_to_le16(payload_len); + txhdr->frame_control = hdr->frame_control; + + len = hdrlen + payload_len + FCS_LEN; + + txhdr->frag = cpu_to_le16(0x0FFF); + adm8211_calc_durations(&dur, &plcp, payload_len, + len, plcp_signal, short_preamble); + txhdr->plcp_frag_head_len = cpu_to_le16(plcp); + txhdr->plcp_frag_tail_len = cpu_to_le16(plcp); + txhdr->dur_frag_head = cpu_to_le16(dur); + txhdr->dur_frag_tail = cpu_to_le16(dur); + + txhdr->header_control = cpu_to_le16(ADM8211_TXHDRCTL_ENABLE_EXTEND_HEADER); + + if (short_preamble) + txhdr->header_control |= cpu_to_le16(ADM8211_TXHDRCTL_SHORT_PREAMBLE); + + if (rc_flags & IEEE80211_TX_RC_USE_RTS_CTS) + txhdr->header_control |= cpu_to_le16(ADM8211_TXHDRCTL_ENABLE_RTS); + + txhdr->retry_limit = info->control.rates[0].count; + + adm8211_tx_raw(dev, skb, plcp_signal, hdrlen); +} + +static int adm8211_alloc_rings(struct ieee80211_hw *dev) +{ + struct adm8211_priv *priv = dev->priv; + unsigned int ring_size; + + priv->rx_buffers = kmalloc(sizeof(*priv->rx_buffers) * priv->rx_ring_size + + sizeof(*priv->tx_buffers) * priv->tx_ring_size, GFP_KERNEL); + if (!priv->rx_buffers) + return -ENOMEM; + + priv->tx_buffers = (void *)priv->rx_buffers + + sizeof(*priv->rx_buffers) * priv->rx_ring_size; + + /* Allocate TX/RX descriptors */ + ring_size = sizeof(struct adm8211_desc) * priv->rx_ring_size + + sizeof(struct adm8211_desc) * priv->tx_ring_size; + priv->rx_ring = pci_alloc_consistent(priv->pdev, ring_size, + &priv->rx_ring_dma); + + if (!priv->rx_ring) { + kfree(priv->rx_buffers); + priv->rx_buffers = NULL; + priv->tx_buffers = NULL; + return -ENOMEM; + } + + priv->tx_ring = priv->rx_ring + priv->rx_ring_size; + priv->tx_ring_dma = priv->rx_ring_dma + + sizeof(struct adm8211_desc) * priv->rx_ring_size; + + return 0; +} + +static const struct ieee80211_ops adm8211_ops = { + .tx = adm8211_tx, + .start = adm8211_start, + .stop = adm8211_stop, + .add_interface = adm8211_add_interface, + .remove_interface = adm8211_remove_interface, + .config = adm8211_config, + .bss_info_changed = adm8211_bss_info_changed, + .prepare_multicast = adm8211_prepare_multicast, + .configure_filter = adm8211_configure_filter, + .get_stats = adm8211_get_stats, + .get_tsf = adm8211_get_tsft +}; + +static int adm8211_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct ieee80211_hw *dev; + struct adm8211_priv *priv; + unsigned long mem_addr, mem_len; + unsigned int io_addr, io_len; + int err; + u32 reg; + u8 perm_addr[ETH_ALEN]; + + err = pci_enable_device(pdev); + if (err) { + printk(KERN_ERR "%s (adm8211): Cannot enable new PCI device\n", + pci_name(pdev)); + return err; + } + + io_addr = pci_resource_start(pdev, 0); + io_len = pci_resource_len(pdev, 0); + mem_addr = pci_resource_start(pdev, 1); + mem_len = pci_resource_len(pdev, 1); + if (io_len < 256 || mem_len < 1024) { + printk(KERN_ERR "%s (adm8211): Too short PCI resources\n", + pci_name(pdev)); + goto err_disable_pdev; + } + + + /* check signature */ + pci_read_config_dword(pdev, 0x80 /* CR32 */, ®); + if (reg != ADM8211_SIG1 && reg != ADM8211_SIG2) { + printk(KERN_ERR "%s (adm8211): Invalid signature (0x%x)\n", + pci_name(pdev), reg); + goto err_disable_pdev; + } + + err = pci_request_regions(pdev, "adm8211"); + if (err) { + printk(KERN_ERR "%s (adm8211): Cannot obtain PCI resources\n", + pci_name(pdev)); + return err; /* someone else grabbed it? don't disable it */ + } + + if (pci_set_dma_mask(pdev, DMA_BIT_MASK(32)) || + pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32))) { + printk(KERN_ERR "%s (adm8211): No suitable DMA available\n", + pci_name(pdev)); + goto err_free_reg; + } + + pci_set_master(pdev); + + dev = ieee80211_alloc_hw(sizeof(*priv), &adm8211_ops); + if (!dev) { + printk(KERN_ERR "%s (adm8211): ieee80211 alloc failed\n", + pci_name(pdev)); + err = -ENOMEM; + goto err_free_reg; + } + priv = dev->priv; + priv->pdev = pdev; + + spin_lock_init(&priv->lock); + + SET_IEEE80211_DEV(dev, &pdev->dev); + + pci_set_drvdata(pdev, dev); + + priv->map = pci_iomap(pdev, 1, mem_len); + if (!priv->map) + priv->map = pci_iomap(pdev, 0, io_len); + + if (!priv->map) { + printk(KERN_ERR "%s (adm8211): Cannot map device memory\n", + pci_name(pdev)); + goto err_free_dev; + } + + priv->rx_ring_size = rx_ring_size; + priv->tx_ring_size = tx_ring_size; + + if (adm8211_alloc_rings(dev)) { + printk(KERN_ERR "%s (adm8211): Cannot allocate TX/RX ring\n", + pci_name(pdev)); + goto err_iounmap; + } + + *(__le32 *)perm_addr = cpu_to_le32(ADM8211_CSR_READ(PAR0)); + *(__le16 *)&perm_addr[4] = + cpu_to_le16(ADM8211_CSR_READ(PAR1) & 0xFFFF); + + if (!is_valid_ether_addr(perm_addr)) { + printk(KERN_WARNING "%s (adm8211): Invalid hwaddr in EEPROM!\n", + pci_name(pdev)); + eth_random_addr(perm_addr); + } + SET_IEEE80211_PERM_ADDR(dev, perm_addr); + + dev->extra_tx_headroom = sizeof(struct adm8211_tx_hdr); + /* dev->flags = IEEE80211_HW_RX_INCLUDES_FCS in promisc mode */ + dev->flags = IEEE80211_HW_SIGNAL_UNSPEC; + dev->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION); + + dev->channel_change_time = 1000; + dev->max_signal = 100; /* FIXME: find better value */ + + dev->queues = 1; /* ADM8211C supports more, maybe ADM8211B too */ + + priv->retry_limit = 3; + priv->ant_power = 0x40; + priv->tx_power = 0x40; + priv->lpf_cutoff = 0xFF; + priv->lnags_threshold = 0xFF; + priv->mode = NL80211_IFTYPE_UNSPECIFIED; + + /* Power-on issue. EEPROM won't read correctly without */ + if (pdev->revision >= ADM8211_REV_BA) { + ADM8211_CSR_WRITE(FRCTL, 0); + ADM8211_CSR_READ(FRCTL); + ADM8211_CSR_WRITE(FRCTL, 1); + ADM8211_CSR_READ(FRCTL); + msleep(100); + } + + err = adm8211_read_eeprom(dev); + if (err) { + printk(KERN_ERR "%s (adm8211): Can't alloc eeprom buffer\n", + pci_name(pdev)); + goto err_free_desc; + } + + priv->channel = 1; + + dev->wiphy->bands[IEEE80211_BAND_2GHZ] = &priv->band; + + err = ieee80211_register_hw(dev); + if (err) { + printk(KERN_ERR "%s (adm8211): Cannot register device\n", + pci_name(pdev)); + goto err_free_eeprom; + } + + wiphy_info(dev->wiphy, "hwaddr %pM, Rev 0x%02x\n", + dev->wiphy->perm_addr, pdev->revision); + + return 0; + + err_free_eeprom: + kfree(priv->eeprom); + + err_free_desc: + pci_free_consistent(pdev, + sizeof(struct adm8211_desc) * priv->rx_ring_size + + sizeof(struct adm8211_desc) * priv->tx_ring_size, + priv->rx_ring, priv->rx_ring_dma); + kfree(priv->rx_buffers); + + err_iounmap: + pci_iounmap(pdev, priv->map); + + err_free_dev: + pci_set_drvdata(pdev, NULL); + ieee80211_free_hw(dev); + + err_free_reg: + pci_release_regions(pdev); + + err_disable_pdev: + pci_disable_device(pdev); + return err; +} + + +static void adm8211_remove(struct pci_dev *pdev) +{ + struct ieee80211_hw *dev = pci_get_drvdata(pdev); + struct adm8211_priv *priv; + + if (!dev) + return; + + ieee80211_unregister_hw(dev); + + priv = dev->priv; + + pci_free_consistent(pdev, + sizeof(struct adm8211_desc) * priv->rx_ring_size + + sizeof(struct adm8211_desc) * priv->tx_ring_size, + priv->rx_ring, priv->rx_ring_dma); + + kfree(priv->rx_buffers); + kfree(priv->eeprom); + pci_iounmap(pdev, priv->map); + pci_release_regions(pdev); + pci_disable_device(pdev); + ieee80211_free_hw(dev); +} + + +#ifdef CONFIG_PM +static int adm8211_suspend(struct pci_dev *pdev, pm_message_t state) +{ + pci_save_state(pdev); + pci_set_power_state(pdev, pci_choose_state(pdev, state)); + return 0; +} + +static int adm8211_resume(struct pci_dev *pdev) +{ + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + return 0; +} +#endif /* CONFIG_PM */ + + +MODULE_DEVICE_TABLE(pci, adm8211_pci_id_table); + +/* TODO: implement enable_wake */ +static struct pci_driver adm8211_driver = { + .name = "adm8211", + .id_table = adm8211_pci_id_table, + .probe = adm8211_probe, + .remove = adm8211_remove, +#ifdef CONFIG_PM + .suspend = adm8211_suspend, + .resume = adm8211_resume, +#endif /* CONFIG_PM */ +}; + +module_pci_driver(adm8211_driver); diff --git a/drivers/net/wireless/adm8211.h b/drivers/net/wireless/adm8211.h new file mode 100644 index 0000000..bbc10b1 --- /dev/null +++ b/drivers/net/wireless/adm8211.h @@ -0,0 +1,602 @@ +#ifndef ADM8211_H +#define ADM8211_H + +/* ADM8211 Registers */ + +/* CR32 (SIG) signature */ +#define ADM8211_SIG1 0x82011317 /* ADM8211A */ +#define ADM8211_SIG2 0x82111317 /* ADM8211B/ADM8211C */ + +#define ADM8211_CSR_READ(r) ioread32(&priv->map->r) +#define ADM8211_CSR_WRITE(r, val) iowrite32((val), &priv->map->r) + +/* CSR (Host Control and Status Registers) */ +struct adm8211_csr { + __le32 PAR; /* 0x00 CSR0 */ + __le32 FRCTL; /* 0x04 CSR0A */ + __le32 TDR; /* 0x08 CSR1 */ + __le32 WTDP; /* 0x0C CSR1A */ + __le32 RDR; /* 0x10 CSR2 */ + __le32 WRDP; /* 0x14 CSR2A */ + __le32 RDB; /* 0x18 CSR3 */ + __le32 TDBH; /* 0x1C CSR3A */ + __le32 TDBD; /* 0x20 CSR4 */ + __le32 TDBP; /* 0x24 CSR4A */ + __le32 STSR; /* 0x28 CSR5 */ + __le32 TDBB; /* 0x2C CSR5A */ + __le32 NAR; /* 0x30 CSR6 */ + __le32 CSR6A; /* reserved */ + __le32 IER; /* 0x38 CSR7 */ + __le32 TKIPSCEP; /* 0x3C CSR7A */ + __le32 LPC; /* 0x40 CSR8 */ + __le32 CSR_TEST1; /* 0x44 CSR8A */ + __le32 SPR; /* 0x48 CSR9 */ + __le32 CSR_TEST0; /* 0x4C CSR9A */ + __le32 WCSR; /* 0x50 CSR10 */ + __le32 WPDR; /* 0x54 CSR10A */ + __le32 GPTMR; /* 0x58 CSR11 */ + __le32 GPIO; /* 0x5C CSR11A */ + __le32 BBPCTL; /* 0x60 CSR12 */ + __le32 SYNCTL; /* 0x64 CSR12A */ + __le32 PLCPHD; /* 0x68 CSR13 */ + __le32 MMIWA; /* 0x6C CSR13A */ + __le32 MMIRD0; /* 0x70 CSR14 */ + __le32 MMIRD1; /* 0x74 CSR14A */ + __le32 TXBR; /* 0x78 CSR15 */ + __le32 SYNDATA; /* 0x7C CSR15A */ + __le32 ALCS; /* 0x80 CSR16 */ + __le32 TOFS2; /* 0x84 CSR17 */ + __le32 CMDR; /* 0x88 CSR18 */ + __le32 PCIC; /* 0x8C CSR19 */ + __le32 PMCSR; /* 0x90 CSR20 */ + __le32 PAR0; /* 0x94 CSR21 */ + __le32 PAR1; /* 0x98 CSR22 */ + __le32 MAR0; /* 0x9C CSR23 */ + __le32 MAR1; /* 0xA0 CSR24 */ + __le32 ATIMDA0; /* 0xA4 CSR25 */ + __le32 ABDA1; /* 0xA8 CSR26 */ + __le32 BSSID0; /* 0xAC CSR27 */ + __le32 TXLMT; /* 0xB0 CSR28 */ + __le32 MIBCNT; /* 0xB4 CSR29 */ + __le32 BCNT; /* 0xB8 CSR30 */ + __le32 TSFTH; /* 0xBC CSR31 */ + __le32 TSC; /* 0xC0 CSR32 */ + __le32 SYNRF; /* 0xC4 CSR33 */ + __le32 BPLI; /* 0xC8 CSR34 */ + __le32 CAP0; /* 0xCC CSR35 */ + __le32 CAP1; /* 0xD0 CSR36 */ + __le32 RMD; /* 0xD4 CSR37 */ + __le32 CFPP; /* 0xD8 CSR38 */ + __le32 TOFS0; /* 0xDC CSR39 */ + __le32 TOFS1; /* 0xE0 CSR40 */ + __le32 IFST; /* 0xE4 CSR41 */ + __le32 RSPT; /* 0xE8 CSR42 */ + __le32 TSFTL; /* 0xEC CSR43 */ + __le32 WEPCTL; /* 0xF0 CSR44 */ + __le32 WESK; /* 0xF4 CSR45 */ + __le32 WEPCNT; /* 0xF8 CSR46 */ + __le32 MACTEST; /* 0xFC CSR47 */ + __le32 FER; /* 0x100 */ + __le32 FEMR; /* 0x104 */ + __le32 FPSR; /* 0x108 */ + __le32 FFER; /* 0x10C */ +} __packed; + +/* CSR0 - PAR (PCI Address Register) */ +#define ADM8211_PAR_MWIE (1 << 24) +#define ADM8211_PAR_MRLE (1 << 23) +#define ADM8211_PAR_MRME (1 << 21) +#define ADM8211_PAR_RAP ((1 << 18) | (1 << 17)) +#define ADM8211_PAR_CAL ((1 << 15) | (1 << 14)) +#define ADM8211_PAR_PBL 0x00003f00 +#define ADM8211_PAR_BLE (1 << 7) +#define ADM8211_PAR_DSL 0x0000007c +#define ADM8211_PAR_BAR (1 << 1) +#define ADM8211_PAR_SWR (1 << 0) + +/* CSR1 - FRCTL (Frame Control Register) */ +#define ADM8211_FRCTL_PWRMGT (1 << 31) +#define ADM8211_FRCTL_MAXPSP (1 << 27) +#define ADM8211_FRCTL_DRVPRSP (1 << 26) +#define ADM8211_FRCTL_DRVBCON (1 << 25) +#define ADM8211_FRCTL_AID 0x0000ffff +#define ADM8211_FRCTL_AID_ON 0x0000c000 + +/* CSR5 - STSR (Status Register) */ +#define ADM8211_STSR_PCF (1 << 31) +#define ADM8211_STSR_BCNTC (1 << 30) +#define ADM8211_STSR_GPINT (1 << 29) +#define ADM8211_STSR_LinkOff (1 << 28) +#define ADM8211_STSR_ATIMTC (1 << 27) +#define ADM8211_STSR_TSFTF (1 << 26) +#define ADM8211_STSR_TSCZ (1 << 25) +#define ADM8211_STSR_LinkOn (1 << 24) +#define ADM8211_STSR_SQL (1 << 23) +#define ADM8211_STSR_WEPTD (1 << 22) +#define ADM8211_STSR_ATIME (1 << 21) +#define ADM8211_STSR_TBTT (1 << 20) +#define ADM8211_STSR_NISS (1 << 16) +#define ADM8211_STSR_AISS (1 << 15) +#define ADM8211_STSR_TEIS (1 << 14) +#define ADM8211_STSR_FBE (1 << 13) +#define ADM8211_STSR_REIS (1 << 12) +#define ADM8211_STSR_GPTT (1 << 11) +#define ADM8211_STSR_RPS (1 << 8) +#define ADM8211_STSR_RDU (1 << 7) +#define ADM8211_STSR_RCI (1 << 6) +#define ADM8211_STSR_TUF (1 << 5) +#define ADM8211_STSR_TRT (1 << 4) +#define ADM8211_STSR_TLT (1 << 3) +#define ADM8211_STSR_TDU (1 << 2) +#define ADM8211_STSR_TPS (1 << 1) +#define ADM8211_STSR_TCI (1 << 0) + +/* CSR6 - NAR (Network Access Register) */ +#define ADM8211_NAR_TXCF (1 << 31) +#define ADM8211_NAR_HF (1 << 30) +#define ADM8211_NAR_UTR (1 << 29) +#define ADM8211_NAR_SQ (1 << 28) +#define ADM8211_NAR_CFP (1 << 27) +#define ADM8211_NAR_SF (1 << 21) +#define ADM8211_NAR_TR ((1 << 15) | (1 << 14)) +#define ADM8211_NAR_ST (1 << 13) +#define ADM8211_NAR_OM ((1 << 11) | (1 << 10)) +#define ADM8211_NAR_MM (1 << 7) +#define ADM8211_NAR_PR (1 << 6) +#define ADM8211_NAR_EA (1 << 5) +#define ADM8211_NAR_PB (1 << 3) +#define ADM8211_NAR_STPDMA (1 << 2) +#define ADM8211_NAR_SR (1 << 1) +#define ADM8211_NAR_CTX (1 << 0) + +#define ADM8211_IDLE() \ +do { \ + if (priv->nar & (ADM8211_NAR_SR | ADM8211_NAR_ST)) { \ + ADM8211_CSR_WRITE(NAR, priv->nar & \ + ~(ADM8211_NAR_SR | ADM8211_NAR_ST));\ + ADM8211_CSR_READ(NAR); \ + msleep(20); \ + } \ +} while (0) + +#define ADM8211_IDLE_RX() \ +do { \ + if (priv->nar & ADM8211_NAR_SR) { \ + ADM8211_CSR_WRITE(NAR, priv->nar & ~ADM8211_NAR_SR); \ + ADM8211_CSR_READ(NAR); \ + mdelay(20); \ + } \ +} while (0) + +#define ADM8211_RESTORE() \ +do { \ + if (priv->nar & (ADM8211_NAR_SR | ADM8211_NAR_ST)) \ + ADM8211_CSR_WRITE(NAR, priv->nar); \ +} while (0) + +/* CSR7 - IER (Interrupt Enable Register) */ +#define ADM8211_IER_PCFIE (1 << 31) +#define ADM8211_IER_BCNTCIE (1 << 30) +#define ADM8211_IER_GPIE (1 << 29) +#define ADM8211_IER_LinkOffIE (1 << 28) +#define ADM8211_IER_ATIMTCIE (1 << 27) +#define ADM8211_IER_TSFTFIE (1 << 26) +#define ADM8211_IER_TSCZE (1 << 25) +#define ADM8211_IER_LinkOnIE (1 << 24) +#define ADM8211_IER_SQLIE (1 << 23) +#define ADM8211_IER_WEPIE (1 << 22) +#define ADM8211_IER_ATIMEIE (1 << 21) +#define ADM8211_IER_TBTTIE (1 << 20) +#define ADM8211_IER_NIE (1 << 16) +#define ADM8211_IER_AIE (1 << 15) +#define ADM8211_IER_TEIE (1 << 14) +#define ADM8211_IER_FBEIE (1 << 13) +#define ADM8211_IER_REIE (1 << 12) +#define ADM8211_IER_GPTIE (1 << 11) +#define ADM8211_IER_RSIE (1 << 8) +#define ADM8211_IER_RUIE (1 << 7) +#define ADM8211_IER_RCIE (1 << 6) +#define ADM8211_IER_TUIE (1 << 5) +#define ADM8211_IER_TRTIE (1 << 4) +#define ADM8211_IER_TLTTIE (1 << 3) +#define ADM8211_IER_TDUIE (1 << 2) +#define ADM8211_IER_TPSIE (1 << 1) +#define ADM8211_IER_TCIE (1 << 0) + +/* CSR9 - SPR (Serial Port Register) */ +#define ADM8211_SPR_SRS (1 << 11) +#define ADM8211_SPR_SDO (1 << 3) +#define ADM8211_SPR_SDI (1 << 2) +#define ADM8211_SPR_SCLK (1 << 1) +#define ADM8211_SPR_SCS (1 << 0) + +/* CSR9A - CSR_TEST0 */ +#define ADM8211_CSR_TEST0_EPNE (1 << 18) +#define ADM8211_CSR_TEST0_EPSNM (1 << 17) +#define ADM8211_CSR_TEST0_EPTYP (1 << 16) +#define ADM8211_CSR_TEST0_EPRLD (1 << 15) + +/* CSR10 - WCSR (Wake-up Control/Status Register) */ +#define ADM8211_WCSR_CRCT (1 << 30) +#define ADM8211_WCSR_TSFTWE (1 << 20) +#define ADM8211_WCSR_TIMWE (1 << 19) +#define ADM8211_WCSR_ATIMWE (1 << 18) +#define ADM8211_WCSR_KEYWE (1 << 17) +#define ADM8211_WCSR_MPRE (1 << 9) +#define ADM8211_WCSR_LSOE (1 << 8) +#define ADM8211_WCSR_KEYUP (1 << 6) +#define ADM8211_WCSR_TSFTW (1 << 5) +#define ADM8211_WCSR_TIMW (1 << 4) +#define ADM8211_WCSR_ATIMW (1 << 3) +#define ADM8211_WCSR_MPR (1 << 1) +#define ADM8211_WCSR_LSO (1 << 0) + +/* CSR11A - GPIO */ +#define ADM8211_CSR_GPIO_EN5 (1 << 17) +#define ADM8211_CSR_GPIO_EN4 (1 << 16) +#define ADM8211_CSR_GPIO_EN3 (1 << 15) +#define ADM8211_CSR_GPIO_EN2 (1 << 14) +#define ADM8211_CSR_GPIO_EN1 (1 << 13) +#define ADM8211_CSR_GPIO_EN0 (1 << 12) +#define ADM8211_CSR_GPIO_O5 (1 << 11) +#define ADM8211_CSR_GPIO_O4 (1 << 10) +#define ADM8211_CSR_GPIO_O3 (1 << 9) +#define ADM8211_CSR_GPIO_O2 (1 << 8) +#define ADM8211_CSR_GPIO_O1 (1 << 7) +#define ADM8211_CSR_GPIO_O0 (1 << 6) +#define ADM8211_CSR_GPIO_IN 0x0000003f + +/* CSR12 - BBPCTL (BBP Control port) */ +#define ADM8211_BBPCTL_MMISEL (1 << 31) +#define ADM8211_BBPCTL_SPICADD (0x7F << 24) +#define ADM8211_BBPCTL_RF3000 (0x20 << 24) +#define ADM8211_BBPCTL_TXCE (1 << 23) +#define ADM8211_BBPCTL_RXCE (1 << 22) +#define ADM8211_BBPCTL_CCAP (1 << 21) +#define ADM8211_BBPCTL_TYPE 0x001c0000 +#define ADM8211_BBPCTL_WR (1 << 17) +#define ADM8211_BBPCTL_RD (1 << 16) +#define ADM8211_BBPCTL_ADDR 0x0000ff00 +#define ADM8211_BBPCTL_DATA 0x000000ff + +/* CSR12A - SYNCTL (Synthesizer Control port) */ +#define ADM8211_SYNCTL_WR (1 << 31) +#define ADM8211_SYNCTL_RD (1 << 30) +#define ADM8211_SYNCTL_CS0 (1 << 29) +#define ADM8211_SYNCTL_CS1 (1 << 28) +#define ADM8211_SYNCTL_CAL (1 << 27) +#define ADM8211_SYNCTL_SELCAL (1 << 26) +#define ADM8211_SYNCTL_RFtype ((1 << 24) | (1 << 23) | (1 << 22)) +#define ADM8211_SYNCTL_RFMD (1 << 22) +#define ADM8211_SYNCTL_GENERAL (0x7 << 22) +/* SYNCTL 21:0 Data (Si4126: 18-bit data, 4-bit address) */ + +/* CSR18 - CMDR (Command Register) */ +#define ADM8211_CMDR_PM (1 << 19) +#define ADM8211_CMDR_APM (1 << 18) +#define ADM8211_CMDR_RTE (1 << 4) +#define ADM8211_CMDR_DRT ((1 << 3) | (1 << 2)) +#define ADM8211_CMDR_DRT_8DW (0x0 << 2) +#define ADM8211_CMDR_DRT_16DW (0x1 << 2) +#define ADM8211_CMDR_DRT_SF (0x2 << 2) + +/* CSR33 - SYNRF (SYNRF direct control) */ +#define ADM8211_SYNRF_SELSYN (1 << 31) +#define ADM8211_SYNRF_SELRF (1 << 30) +#define ADM8211_SYNRF_LERF (1 << 29) +#define ADM8211_SYNRF_LEIF (1 << 28) +#define ADM8211_SYNRF_SYNCLK (1 << 27) +#define ADM8211_SYNRF_SYNDATA (1 << 26) +#define ADM8211_SYNRF_PE1 (1 << 25) +#define ADM8211_SYNRF_PE2 (1 << 24) +#define ADM8211_SYNRF_PA_PE (1 << 23) +#define ADM8211_SYNRF_TR_SW (1 << 22) +#define ADM8211_SYNRF_TR_SWN (1 << 21) +#define ADM8211_SYNRF_RADIO (1 << 20) +#define ADM8211_SYNRF_CAL_EN (1 << 19) +#define ADM8211_SYNRF_PHYRST (1 << 18) + +#define ADM8211_SYNRF_IF_SELECT_0 (1 << 31) +#define ADM8211_SYNRF_IF_SELECT_1 ((1 << 31) | (1 << 28)) +#define ADM8211_SYNRF_WRITE_SYNDATA_0 (1 << 31) +#define ADM8211_SYNRF_WRITE_SYNDATA_1 ((1 << 31) | (1 << 26)) +#define ADM8211_SYNRF_WRITE_CLOCK_0 (1 << 31) +#define ADM8211_SYNRF_WRITE_CLOCK_1 ((1 << 31) | (1 << 27)) + +/* CSR44 - WEPCTL (WEP Control) */ +#define ADM8211_WEPCTL_WEPENABLE (1 << 31) +#define ADM8211_WEPCTL_WPAENABLE (1 << 30) +#define ADM8211_WEPCTL_CURRENT_TABLE (1 << 29) +#define ADM8211_WEPCTL_TABLE_WR (1 << 28) +#define ADM8211_WEPCTL_TABLE_RD (1 << 27) +#define ADM8211_WEPCTL_WEPRXBYP (1 << 25) +#define ADM8211_WEPCTL_SEL_WEPTABLE (1 << 23) +#define ADM8211_WEPCTL_ADDR (0x000001ff) + +/* CSR45 - WESK (Data Entry for Share/Individual Key) */ +#define ADM8211_WESK_DATA (0x0000ffff) + +/* FER (Function Event Register) */ +#define ADM8211_FER_INTR_EV_ENT (1 << 15) + + +/* Si4126 RF Synthesizer - Control Registers */ +#define SI4126_MAIN_CONF 0 +#define SI4126_PHASE_DET_GAIN 1 +#define SI4126_POWERDOWN 2 +#define SI4126_RF1_N_DIV 3 /* only Si4136 */ +#define SI4126_RF2_N_DIV 4 +#define SI4126_IF_N_DIV 5 +#define SI4126_RF1_R_DIV 6 /* only Si4136 */ +#define SI4126_RF2_R_DIV 7 +#define SI4126_IF_R_DIV 8 + +/* Main Configuration */ +#define SI4126_MAIN_XINDIV2 (1 << 6) +#define SI4126_MAIN_IFDIV ((1 << 11) | (1 << 10)) +/* Powerdown */ +#define SI4126_POWERDOWN_PDIB (1 << 1) +#define SI4126_POWERDOWN_PDRB (1 << 0) + + +/* RF3000 BBP - Control Port Registers */ +/* 0x00 - reserved */ +#define RF3000_MODEM_CTRL__RX_STATUS 0x01 +#define RF3000_CCA_CTRL 0x02 +#define RF3000_DIVERSITY__RSSI 0x03 +#define RF3000_RX_SIGNAL_FIELD 0x04 +#define RF3000_RX_LEN_MSB 0x05 +#define RF3000_RX_LEN_LSB 0x06 +#define RF3000_RX_SERVICE_FIELD 0x07 +#define RF3000_TX_VAR_GAIN__TX_LEN_EXT 0x11 +#define RF3000_TX_LEN_MSB 0x12 +#define RF3000_TX_LEN_LSB 0x13 +#define RF3000_LOW_GAIN_CALIB 0x14 +#define RF3000_HIGH_GAIN_CALIB 0x15 + +/* ADM8211 revisions */ +#define ADM8211_REV_AB 0x11 +#define ADM8211_REV_AF 0x15 +#define ADM8211_REV_BA 0x20 +#define ADM8211_REV_CA 0x30 + +struct adm8211_desc { + __le32 status; + __le32 length; + __le32 buffer1; + __le32 buffer2; +}; + +#define RDES0_STATUS_OWN (1 << 31) +#define RDES0_STATUS_ES (1 << 30) +#define RDES0_STATUS_SQL (1 << 29) +#define RDES0_STATUS_DE (1 << 28) +#define RDES0_STATUS_FS (1 << 27) +#define RDES0_STATUS_LS (1 << 26) +#define RDES0_STATUS_PCF (1 << 25) +#define RDES0_STATUS_SFDE (1 << 24) +#define RDES0_STATUS_SIGE (1 << 23) +#define RDES0_STATUS_CRC16E (1 << 22) +#define RDES0_STATUS_RXTOE (1 << 21) +#define RDES0_STATUS_CRC32E (1 << 20) +#define RDES0_STATUS_ICVE (1 << 19) +#define RDES0_STATUS_DA1 (1 << 17) +#define RDES0_STATUS_DA0 (1 << 16) +#define RDES0_STATUS_RXDR ((1 << 15) | (1 << 14) | (1 << 13) | (1 << 12)) +#define RDES0_STATUS_FL (0x00000fff) + +#define RDES1_CONTROL_RER (1 << 25) +#define RDES1_CONTROL_RCH (1 << 24) +#define RDES1_CONTROL_RBS2 (0x00fff000) +#define RDES1_CONTROL_RBS1 (0x00000fff) + +#define RDES1_STATUS_RSSI (0x0000007f) + + +#define TDES0_CONTROL_OWN (1 << 31) +#define TDES0_CONTROL_DONE (1 << 30) +#define TDES0_CONTROL_TXDR (0x0ff00000) + +#define TDES0_STATUS_OWN (1 << 31) +#define TDES0_STATUS_DONE (1 << 30) +#define TDES0_STATUS_ES (1 << 29) +#define TDES0_STATUS_TLT (1 << 28) +#define TDES0_STATUS_TRT (1 << 27) +#define TDES0_STATUS_TUF (1 << 26) +#define TDES0_STATUS_TRO (1 << 25) +#define TDES0_STATUS_SOFBR (1 << 24) +#define TDES0_STATUS_ACR (0x00000fff) + +#define TDES1_CONTROL_IC (1 << 31) +#define TDES1_CONTROL_LS (1 << 30) +#define TDES1_CONTROL_FS (1 << 29) +#define TDES1_CONTROL_TER (1 << 25) +#define TDES1_CONTROL_TCH (1 << 24) +#define TDES1_CONTROL_RBS2 (0x00fff000) +#define TDES1_CONTROL_RBS1 (0x00000fff) + +/* SRAM offsets */ +#define ADM8211_SRAM(x) (priv->pdev->revision < ADM8211_REV_BA ? \ + ADM8211_SRAM_A_ ## x : ADM8211_SRAM_B_ ## x) + +#define ADM8211_SRAM_INDIV_KEY 0x0000 +#define ADM8211_SRAM_A_SHARE_KEY 0x0160 +#define ADM8211_SRAM_B_SHARE_KEY 0x00c0 + +#define ADM8211_SRAM_A_SSID 0x0180 +#define ADM8211_SRAM_B_SSID 0x00d4 +#define ADM8211_SRAM_SSID ADM8211_SRAM(SSID) + +#define ADM8211_SRAM_A_SUPP_RATE 0x0191 +#define ADM8211_SRAM_B_SUPP_RATE 0x00dd +#define ADM8211_SRAM_SUPP_RATE ADM8211_SRAM(SUPP_RATE) + +#define ADM8211_SRAM_A_SIZE 0x0200 +#define ADM8211_SRAM_B_SIZE 0x01c0 +#define ADM8211_SRAM_SIZE ADM8211_SRAM(SIZE) + +struct adm8211_rx_ring_info { + struct sk_buff *skb; + dma_addr_t mapping; +}; + +struct adm8211_tx_ring_info { + struct sk_buff *skb; + dma_addr_t mapping; + size_t hdrlen; +}; + +#define PLCP_SIGNAL_1M 0x0a +#define PLCP_SIGNAL_2M 0x14 +#define PLCP_SIGNAL_5M5 0x37 +#define PLCP_SIGNAL_11M 0x6e + +struct adm8211_tx_hdr { + u8 da[6]; + u8 signal; /* PLCP signal / TX rate in 100 Kbps */ + u8 service; + __le16 frame_body_size; + __le16 frame_control; + __le16 plcp_frag_tail_len; + __le16 plcp_frag_head_len; + __le16 dur_frag_tail; + __le16 dur_frag_head; + u8 addr4[6]; + +#define ADM8211_TXHDRCTL_SHORT_PREAMBLE (1 << 0) +#define ADM8211_TXHDRCTL_MORE_FRAG (1 << 1) +#define ADM8211_TXHDRCTL_MORE_DATA (1 << 2) +#define ADM8211_TXHDRCTL_FRAG_NO (1 << 3) /* ? */ +#define ADM8211_TXHDRCTL_ENABLE_RTS (1 << 4) +#define ADM8211_TXHDRCTL_ENABLE_WEP_ENGINE (1 << 5) +#define ADM8211_TXHDRCTL_ENABLE_EXTEND_HEADER (1 << 15) /* ? */ + __le16 header_control; + __le16 frag; + u8 reserved_0; + u8 retry_limit; + + u32 wep2key0; + u32 wep2key1; + u32 wep2key2; + u32 wep2key3; + + u8 keyid; + u8 entry_control; // huh?? + u16 reserved_1; + u32 reserved_2; +} __packed; + + +#define RX_COPY_BREAK 128 +#define RX_PKT_SIZE 2500 + +struct adm8211_eeprom { + __le16 signature; /* 0x00 */ + u8 major_version; /* 0x02 */ + u8 minor_version; /* 0x03 */ + u8 reserved_1[4]; /* 0x04 */ + u8 hwaddr[6]; /* 0x08 */ + u8 reserved_2[8]; /* 0x1E */ + __le16 cr49; /* 0x16 */ + u8 cr03; /* 0x18 */ + u8 cr28; /* 0x19 */ + u8 cr29; /* 0x1A */ + u8 country_code; /* 0x1B */ + +/* specific bbp types */ +#define ADM8211_BBP_RFMD3000 0x00 +#define ADM8211_BBP_RFMD3002 0x01 +#define ADM8211_BBP_ADM8011 0x04 + u8 specific_bbptype; /* 0x1C */ + u8 specific_rftype; /* 0x1D */ + u8 reserved_3[2]; /* 0x1E */ + __le16 device_id; /* 0x20 */ + __le16 vendor_id; /* 0x22 */ + __le16 subsystem_id; /* 0x24 */ + __le16 subsystem_vendor_id; /* 0x26 */ + u8 maxlat; /* 0x28 */ + u8 mingnt; /* 0x29 */ + __le16 cis_pointer_low; /* 0x2A */ + __le16 cis_pointer_high; /* 0x2C */ + __le16 csr18; /* 0x2E */ + u8 reserved_4[16]; /* 0x30 */ + u8 d1_pwrdara; /* 0x40 */ + u8 d0_pwrdara; /* 0x41 */ + u8 d3_pwrdara; /* 0x42 */ + u8 d2_pwrdara; /* 0x43 */ + u8 antenna_power[14]; /* 0x44 */ + __le16 cis_wordcnt; /* 0x52 */ + u8 tx_power[14]; /* 0x54 */ + u8 lpf_cutoff[14]; /* 0x62 */ + u8 lnags_threshold[14]; /* 0x70 */ + __le16 checksum; /* 0x7E */ + u8 cis_data[0]; /* 0x80, 384 bytes */ +} __packed; + +struct adm8211_priv { + struct pci_dev *pdev; + spinlock_t lock; + struct adm8211_csr __iomem *map; + struct adm8211_desc *rx_ring; + struct adm8211_desc *tx_ring; + dma_addr_t rx_ring_dma; + dma_addr_t tx_ring_dma; + struct adm8211_rx_ring_info *rx_buffers; + struct adm8211_tx_ring_info *tx_buffers; + unsigned int rx_ring_size, tx_ring_size; + unsigned int cur_tx, dirty_tx, cur_rx; + + struct ieee80211_low_level_stats stats; + struct ieee80211_supported_band band; + struct ieee80211_channel channels[14]; + int mode; + + int channel; + u8 bssid[ETH_ALEN]; + + u8 soft_rx_crc; + u8 retry_limit; + + u8 ant_power; + u8 tx_power; + u8 lpf_cutoff; + u8 lnags_threshold; + struct adm8211_eeprom *eeprom; + size_t eeprom_len; + + u32 nar; + +#define ADM8211_TYPE_INTERSIL 0x00 +#define ADM8211_TYPE_RFMD 0x01 +#define ADM8211_TYPE_MARVEL 0x02 +#define ADM8211_TYPE_AIROHA 0x03 +#define ADM8211_TYPE_ADMTEK 0x05 + unsigned int rf_type:3; + unsigned int bbp_type:3; + + u8 specific_bbptype; + enum { + ADM8211_RFMD2948 = 0x0, + ADM8211_RFMD2958 = 0x1, + ADM8211_RFMD2958_RF3000_CONTROL_POWER = 0x2, + ADM8211_MAX2820 = 0x8, + ADM8211_AL2210L = 0xC, /* Airoha */ + } transceiver_type; +}; + +struct ieee80211_chan_range { + u8 min; + u8 max; +}; + +static const struct ieee80211_chan_range cranges[] = { + {1, 11}, /* FCC */ + {1, 11}, /* IC */ + {1, 13}, /* ETSI */ + {10, 11}, /* SPAIN */ + {10, 13}, /* FRANCE */ + {14, 14}, /* MMK */ + {1, 14}, /* MMK2 */ +}; + +#endif /* ADM8211_H */ diff --git a/drivers/net/wireless/at76c50x-usb.c b/drivers/net/wireless/at76c50x-usb.c new file mode 100644 index 0000000..6ffab79 --- /dev/null +++ b/drivers/net/wireless/at76c50x-usb.c @@ -0,0 +1,2504 @@ +/* + * at76c503/at76c505 USB driver + * + * Copyright (c) 2002 - 2003 Oliver Kurth + * Copyright (c) 2004 Joerg Albert + * Copyright (c) 2004 Nick Jones + * Copyright (c) 2004 Balint Seeber + * Copyright (c) 2007 Guido Guenther + * Copyright (c) 2007 Kalle Valo + * Copyright (c) 2010 Sebastian Smolorz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This file is part of the Berlios driver for WLAN USB devices based on the + * Atmel AT76C503A/505/505A. + * + * Some iw_handler code was taken from airo.c, (C) 1999 Benjamin Reed + * + * TODO list is at the wiki: + * + * http://wireless.kernel.org/en/users/Drivers/at76c50x-usb#TODO + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "at76c50x-usb.h" + +/* Version information */ +#define DRIVER_NAME "at76c50x-usb" +#define DRIVER_VERSION "0.17" +#define DRIVER_DESC "Atmel at76x USB Wireless LAN Driver" + +/* at76_debug bits */ +#define DBG_PROGRESS 0x00000001 /* authentication/accociation */ +#define DBG_BSS_TABLE 0x00000002 /* show BSS table after scans */ +#define DBG_IOCTL 0x00000004 /* ioctl calls / settings */ +#define DBG_MAC_STATE 0x00000008 /* MAC state transitions */ +#define DBG_TX_DATA 0x00000010 /* tx header */ +#define DBG_TX_DATA_CONTENT 0x00000020 /* tx content */ +#define DBG_TX_MGMT 0x00000040 /* tx management */ +#define DBG_RX_DATA 0x00000080 /* rx data header */ +#define DBG_RX_DATA_CONTENT 0x00000100 /* rx data content */ +#define DBG_RX_MGMT 0x00000200 /* rx mgmt frame headers */ +#define DBG_RX_BEACON 0x00000400 /* rx beacon */ +#define DBG_RX_CTRL 0x00000800 /* rx control */ +#define DBG_RX_MGMT_CONTENT 0x00001000 /* rx mgmt content */ +#define DBG_RX_FRAGS 0x00002000 /* rx data fragment handling */ +#define DBG_DEVSTART 0x00004000 /* fw download, device start */ +#define DBG_URB 0x00008000 /* rx urb status, ... */ +#define DBG_RX_ATMEL_HDR 0x00010000 /* Atmel-specific Rx headers */ +#define DBG_PROC_ENTRY 0x00020000 /* procedure entries/exits */ +#define DBG_PM 0x00040000 /* power management settings */ +#define DBG_BSS_MATCH 0x00080000 /* BSS match failures */ +#define DBG_PARAMS 0x00100000 /* show configured parameters */ +#define DBG_WAIT_COMPLETE 0x00200000 /* command completion */ +#define DBG_RX_FRAGS_SKB 0x00400000 /* skb header of Rx fragments */ +#define DBG_BSS_TABLE_RM 0x00800000 /* purging bss table entries */ +#define DBG_MONITOR_MODE 0x01000000 /* monitor mode */ +#define DBG_MIB 0x02000000 /* dump all MIBs on startup */ +#define DBG_MGMT_TIMER 0x04000000 /* dump mgmt_timer ops */ +#define DBG_WE_EVENTS 0x08000000 /* dump wireless events */ +#define DBG_FW 0x10000000 /* firmware download */ +#define DBG_DFU 0x20000000 /* device firmware upgrade */ +#define DBG_CMD 0x40000000 +#define DBG_MAC80211 0x80000000 + +#define DBG_DEFAULTS 0 + +/* Use our own dbg macro */ +#define at76_dbg(bits, format, arg...) \ +do { \ + if (at76_debug & (bits)) \ + printk(KERN_DEBUG DRIVER_NAME ": " format "\n", ##arg); \ +} while (0) + +#define at76_dbg_dump(bits, buf, len, format, arg...) \ +do { \ + if (at76_debug & (bits)) { \ + printk(KERN_DEBUG DRIVER_NAME ": " format "\n", ##arg); \ + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buf, len); \ + } \ +} while (0) + +static uint at76_debug = DBG_DEFAULTS; + +/* Protect against concurrent firmware loading and parsing */ +static struct mutex fw_mutex; + +static struct fwentry firmwares[] = { + [0] = { "" }, + [BOARD_503_ISL3861] = { "atmel_at76c503-i3861.bin" }, + [BOARD_503_ISL3863] = { "atmel_at76c503-i3863.bin" }, + [BOARD_503] = { "atmel_at76c503-rfmd.bin" }, + [BOARD_503_ACC] = { "atmel_at76c503-rfmd-acc.bin" }, + [BOARD_505] = { "atmel_at76c505-rfmd.bin" }, + [BOARD_505_2958] = { "atmel_at76c505-rfmd2958.bin" }, + [BOARD_505A] = { "atmel_at76c505a-rfmd2958.bin" }, + [BOARD_505AMX] = { "atmel_at76c505amx-rfmd.bin" }, +}; +MODULE_FIRMWARE("atmel_at76c503-i3861.bin"); +MODULE_FIRMWARE("atmel_at76c503-i3863.bin"); +MODULE_FIRMWARE("atmel_at76c503-rfmd.bin"); +MODULE_FIRMWARE("atmel_at76c503-rfmd-acc.bin"); +MODULE_FIRMWARE("atmel_at76c505-rfmd.bin"); +MODULE_FIRMWARE("atmel_at76c505-rfmd2958.bin"); +MODULE_FIRMWARE("atmel_at76c505a-rfmd2958.bin"); +MODULE_FIRMWARE("atmel_at76c505amx-rfmd.bin"); + +#define USB_DEVICE_DATA(__ops) .driver_info = (kernel_ulong_t)(__ops) + +static struct usb_device_id dev_table[] = { + /* + * at76c503-i3861 + */ + /* Generic AT76C503/3861 device */ + { USB_DEVICE(0x03eb, 0x7603), USB_DEVICE_DATA(BOARD_503_ISL3861) }, + /* Linksys WUSB11 v2.1/v2.6 */ + { USB_DEVICE(0x066b, 0x2211), USB_DEVICE_DATA(BOARD_503_ISL3861) }, + /* Netgear MA101 rev. A */ + { USB_DEVICE(0x0864, 0x4100), USB_DEVICE_DATA(BOARD_503_ISL3861) }, + /* Tekram U300C / Allnet ALL0193 */ + { USB_DEVICE(0x0b3b, 0x1612), USB_DEVICE_DATA(BOARD_503_ISL3861) }, + /* HP HN210W J7801A */ + { USB_DEVICE(0x03f0, 0x011c), USB_DEVICE_DATA(BOARD_503_ISL3861) }, + /* Sitecom/Z-Com/Zyxel M4Y-750 */ + { USB_DEVICE(0x0cde, 0x0001), USB_DEVICE_DATA(BOARD_503_ISL3861) }, + /* Dynalink/Askey WLL013 (intersil) */ + { USB_DEVICE(0x069a, 0x0320), USB_DEVICE_DATA(BOARD_503_ISL3861) }, + /* EZ connect 11Mpbs Wireless USB Adapter SMC2662W v1 */ + { USB_DEVICE(0x0d5c, 0xa001), USB_DEVICE_DATA(BOARD_503_ISL3861) }, + /* BenQ AWL300 */ + { USB_DEVICE(0x04a5, 0x9000), USB_DEVICE_DATA(BOARD_503_ISL3861) }, + /* Addtron AWU-120, Compex WLU11 */ + { USB_DEVICE(0x05dd, 0xff31), USB_DEVICE_DATA(BOARD_503_ISL3861) }, + /* Intel AP310 AnyPoint II USB */ + { USB_DEVICE(0x8086, 0x0200), USB_DEVICE_DATA(BOARD_503_ISL3861) }, + /* Dynalink L11U */ + { USB_DEVICE(0x0d8e, 0x7100), USB_DEVICE_DATA(BOARD_503_ISL3861) }, + /* Arescom WL-210, FCC id 07J-GL2411USB */ + { USB_DEVICE(0x0d8e, 0x7110), USB_DEVICE_DATA(BOARD_503_ISL3861) }, + /* I-O DATA WN-B11/USB */ + { USB_DEVICE(0x04bb, 0x0919), USB_DEVICE_DATA(BOARD_503_ISL3861) }, + /* BT Voyager 1010 */ + { USB_DEVICE(0x069a, 0x0821), USB_DEVICE_DATA(BOARD_503_ISL3861) }, + /* + * at76c503-i3863 + */ + /* Generic AT76C503/3863 device */ + { USB_DEVICE(0x03eb, 0x7604), USB_DEVICE_DATA(BOARD_503_ISL3863) }, + /* Samsung SWL-2100U */ + { USB_DEVICE(0x055d, 0xa000), USB_DEVICE_DATA(BOARD_503_ISL3863) }, + /* + * at76c503-rfmd + */ + /* Generic AT76C503/RFMD device */ + { USB_DEVICE(0x03eb, 0x7605), USB_DEVICE_DATA(BOARD_503) }, + /* Dynalink/Askey WLL013 (rfmd) */ + { USB_DEVICE(0x069a, 0x0321), USB_DEVICE_DATA(BOARD_503) }, + /* Linksys WUSB11 v2.6 */ + { USB_DEVICE(0x077b, 0x2219), USB_DEVICE_DATA(BOARD_503) }, + /* Network Everywhere NWU11B */ + { USB_DEVICE(0x077b, 0x2227), USB_DEVICE_DATA(BOARD_503) }, + /* Netgear MA101 rev. B */ + { USB_DEVICE(0x0864, 0x4102), USB_DEVICE_DATA(BOARD_503) }, + /* D-Link DWL-120 rev. E */ + { USB_DEVICE(0x2001, 0x3200), USB_DEVICE_DATA(BOARD_503) }, + /* Actiontec 802UAT1, HWU01150-01UK */ + { USB_DEVICE(0x1668, 0x7605), USB_DEVICE_DATA(BOARD_503) }, + /* AirVast W-Buddie WN210 */ + { USB_DEVICE(0x03eb, 0x4102), USB_DEVICE_DATA(BOARD_503) }, + /* Dick Smith Electronics XH1153 802.11b USB adapter */ + { USB_DEVICE(0x1371, 0x5743), USB_DEVICE_DATA(BOARD_503) }, + /* CNet CNUSB611 */ + { USB_DEVICE(0x1371, 0x0001), USB_DEVICE_DATA(BOARD_503) }, + /* FiberLine FL-WL200U */ + { USB_DEVICE(0x1371, 0x0002), USB_DEVICE_DATA(BOARD_503) }, + /* BenQ AWL400 USB stick */ + { USB_DEVICE(0x04a5, 0x9001), USB_DEVICE_DATA(BOARD_503) }, + /* 3Com 3CRSHEW696 */ + { USB_DEVICE(0x0506, 0x0a01), USB_DEVICE_DATA(BOARD_503) }, + /* Siemens Santis ADSL WLAN USB adapter WLL 013 */ + { USB_DEVICE(0x0681, 0x001b), USB_DEVICE_DATA(BOARD_503) }, + /* Belkin F5D6050, version 2 */ + { USB_DEVICE(0x050d, 0x0050), USB_DEVICE_DATA(BOARD_503) }, + /* iBlitzz, BWU613 (not *B or *SB) */ + { USB_DEVICE(0x07b8, 0xb000), USB_DEVICE_DATA(BOARD_503) }, + /* Gigabyte GN-WLBM101 */ + { USB_DEVICE(0x1044, 0x8003), USB_DEVICE_DATA(BOARD_503) }, + /* Planex GW-US11S */ + { USB_DEVICE(0x2019, 0x3220), USB_DEVICE_DATA(BOARD_503) }, + /* Internal WLAN adapter in h5[4,5]xx series iPAQs */ + { USB_DEVICE(0x049f, 0x0032), USB_DEVICE_DATA(BOARD_503) }, + /* Corega Wireless LAN USB-11 mini */ + { USB_DEVICE(0x07aa, 0x0011), USB_DEVICE_DATA(BOARD_503) }, + /* Corega Wireless LAN USB-11 mini2 */ + { USB_DEVICE(0x07aa, 0x0018), USB_DEVICE_DATA(BOARD_503) }, + /* Uniden PCW100 */ + { USB_DEVICE(0x05dd, 0xff35), USB_DEVICE_DATA(BOARD_503) }, + /* + * at76c503-rfmd-acc + */ + /* SMC2664W */ + { USB_DEVICE(0x083a, 0x3501), USB_DEVICE_DATA(BOARD_503_ACC) }, + /* Belkin F5D6050, SMC2662W v2, SMC2662W-AR */ + { USB_DEVICE(0x0d5c, 0xa002), USB_DEVICE_DATA(BOARD_503_ACC) }, + /* + * at76c505-rfmd + */ + /* Generic AT76C505/RFMD */ + { USB_DEVICE(0x03eb, 0x7606), USB_DEVICE_DATA(BOARD_505) }, + /* + * at76c505-rfmd2958 + */ + /* Generic AT76C505/RFMD, OvisLink WL-1130USB */ + { USB_DEVICE(0x03eb, 0x7613), USB_DEVICE_DATA(BOARD_505_2958) }, + /* Fiberline FL-WL240U */ + { USB_DEVICE(0x1371, 0x0014), USB_DEVICE_DATA(BOARD_505_2958) }, + /* CNet CNUSB-611G */ + { USB_DEVICE(0x1371, 0x0013), USB_DEVICE_DATA(BOARD_505_2958) }, + /* Linksys WUSB11 v2.8 */ + { USB_DEVICE(0x1915, 0x2233), USB_DEVICE_DATA(BOARD_505_2958) }, + /* Xterasys XN-2122B, IBlitzz BWU613B/BWU613SB */ + { USB_DEVICE(0x12fd, 0x1001), USB_DEVICE_DATA(BOARD_505_2958) }, + /* Corega WLAN USB Stick 11 */ + { USB_DEVICE(0x07aa, 0x7613), USB_DEVICE_DATA(BOARD_505_2958) }, + /* Microstar MSI Box MS6978 */ + { USB_DEVICE(0x0db0, 0x1020), USB_DEVICE_DATA(BOARD_505_2958) }, + /* + * at76c505a-rfmd2958 + */ + /* Generic AT76C505A device */ + { USB_DEVICE(0x03eb, 0x7614), USB_DEVICE_DATA(BOARD_505A) }, + /* Generic AT76C505AS device */ + { USB_DEVICE(0x03eb, 0x7617), USB_DEVICE_DATA(BOARD_505A) }, + /* Siemens Gigaset USB WLAN Adapter 11 */ + { USB_DEVICE(0x1690, 0x0701), USB_DEVICE_DATA(BOARD_505A) }, + /* OQO Model 01+ Internal Wi-Fi */ + { USB_DEVICE(0x1557, 0x0002), USB_DEVICE_DATA(BOARD_505A) }, + /* + * at76c505amx-rfmd + */ + /* Generic AT76C505AMX device */ + { USB_DEVICE(0x03eb, 0x7615), USB_DEVICE_DATA(BOARD_505AMX) }, + { } +}; + +MODULE_DEVICE_TABLE(usb, dev_table); + +/* Supported rates of this hardware, bit 7 marks basic rates */ +static const u8 hw_rates[] = { 0x82, 0x84, 0x0b, 0x16 }; + +static const char *const preambles[] = { "long", "short", "auto" }; + +/* Firmware download */ +/* DFU states */ +#define STATE_IDLE 0x00 +#define STATE_DETACH 0x01 +#define STATE_DFU_IDLE 0x02 +#define STATE_DFU_DOWNLOAD_SYNC 0x03 +#define STATE_DFU_DOWNLOAD_BUSY 0x04 +#define STATE_DFU_DOWNLOAD_IDLE 0x05 +#define STATE_DFU_MANIFEST_SYNC 0x06 +#define STATE_DFU_MANIFEST 0x07 +#define STATE_DFU_MANIFEST_WAIT_RESET 0x08 +#define STATE_DFU_UPLOAD_IDLE 0x09 +#define STATE_DFU_ERROR 0x0a + +/* DFU commands */ +#define DFU_DETACH 0 +#define DFU_DNLOAD 1 +#define DFU_UPLOAD 2 +#define DFU_GETSTATUS 3 +#define DFU_CLRSTATUS 4 +#define DFU_GETSTATE 5 +#define DFU_ABORT 6 + +#define FW_BLOCK_SIZE 1024 + +struct dfu_status { + unsigned char status; + unsigned char poll_timeout[3]; + unsigned char state; + unsigned char string; +} __packed; + +static inline int at76_is_intersil(enum board_type board) +{ + return (board == BOARD_503_ISL3861 || board == BOARD_503_ISL3863); +} + +static inline int at76_is_503rfmd(enum board_type board) +{ + return (board == BOARD_503 || board == BOARD_503_ACC); +} + +static inline int at76_is_505a(enum board_type board) +{ + return (board == BOARD_505A || board == BOARD_505AMX); +} + +/* Load a block of the first (internal) part of the firmware */ +static int at76_load_int_fw_block(struct usb_device *udev, int blockno, + void *block, int size) +{ + return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), DFU_DNLOAD, + USB_TYPE_CLASS | USB_DIR_OUT | + USB_RECIP_INTERFACE, blockno, 0, block, size, + USB_CTRL_GET_TIMEOUT); +} + +static int at76_dfu_get_status(struct usb_device *udev, + struct dfu_status *status) +{ + int ret; + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), DFU_GETSTATUS, + USB_TYPE_CLASS | USB_DIR_IN | USB_RECIP_INTERFACE, + 0, 0, status, sizeof(struct dfu_status), + USB_CTRL_GET_TIMEOUT); + return ret; +} + +static int at76_dfu_get_state(struct usb_device *udev, u8 *state) +{ + int ret; + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), DFU_GETSTATE, + USB_TYPE_CLASS | USB_DIR_IN | USB_RECIP_INTERFACE, + 0, 0, state, 1, USB_CTRL_GET_TIMEOUT); + return ret; +} + +/* Convert timeout from the DFU status to jiffies */ +static inline unsigned long at76_get_timeout(struct dfu_status *s) +{ + return msecs_to_jiffies((s->poll_timeout[2] << 16) + | (s->poll_timeout[1] << 8) + | (s->poll_timeout[0])); +} + +/* Load internal firmware from the buffer. If manifest_sync_timeout > 0, use + * its value in jiffies in the MANIFEST_SYNC state. */ +static int at76_usbdfu_download(struct usb_device *udev, u8 *buf, u32 size, + int manifest_sync_timeout) +{ + u8 *block; + struct dfu_status dfu_stat_buf; + int ret = 0; + int need_dfu_state = 1; + int is_done = 0; + u8 dfu_state = 0; + u32 dfu_timeout = 0; + int bsize = 0; + int blockno = 0; + + at76_dbg(DBG_DFU, "%s( %p, %u, %d)", __func__, buf, size, + manifest_sync_timeout); + + if (!size) { + dev_err(&udev->dev, "FW buffer length invalid!\n"); + return -EINVAL; + } + + block = kmalloc(FW_BLOCK_SIZE, GFP_KERNEL); + if (!block) + return -ENOMEM; + + do { + if (need_dfu_state) { + ret = at76_dfu_get_state(udev, &dfu_state); + if (ret < 0) { + dev_err(&udev->dev, + "cannot get DFU state: %d\n", ret); + goto exit; + } + need_dfu_state = 0; + } + + switch (dfu_state) { + case STATE_DFU_DOWNLOAD_SYNC: + at76_dbg(DBG_DFU, "STATE_DFU_DOWNLOAD_SYNC"); + ret = at76_dfu_get_status(udev, &dfu_stat_buf); + if (ret >= 0) { + dfu_state = dfu_stat_buf.state; + dfu_timeout = at76_get_timeout(&dfu_stat_buf); + need_dfu_state = 0; + } else + dev_err(&udev->dev, + "at76_dfu_get_status returned %d\n", + ret); + break; + + case STATE_DFU_DOWNLOAD_BUSY: + at76_dbg(DBG_DFU, "STATE_DFU_DOWNLOAD_BUSY"); + need_dfu_state = 1; + + at76_dbg(DBG_DFU, "DFU: Resetting device"); + schedule_timeout_interruptible(dfu_timeout); + break; + + case STATE_DFU_DOWNLOAD_IDLE: + at76_dbg(DBG_DFU, "DOWNLOAD..."); + /* fall through */ + case STATE_DFU_IDLE: + at76_dbg(DBG_DFU, "DFU IDLE"); + + bsize = min_t(int, size, FW_BLOCK_SIZE); + memcpy(block, buf, bsize); + at76_dbg(DBG_DFU, "int fw, size left = %5d, " + "bsize = %4d, blockno = %2d", size, bsize, + blockno); + ret = + at76_load_int_fw_block(udev, blockno, block, bsize); + buf += bsize; + size -= bsize; + blockno++; + + if (ret != bsize) + dev_err(&udev->dev, + "at76_load_int_fw_block returned %d\n", + ret); + need_dfu_state = 1; + break; + + case STATE_DFU_MANIFEST_SYNC: + at76_dbg(DBG_DFU, "STATE_DFU_MANIFEST_SYNC"); + + ret = at76_dfu_get_status(udev, &dfu_stat_buf); + if (ret < 0) + break; + + dfu_state = dfu_stat_buf.state; + dfu_timeout = at76_get_timeout(&dfu_stat_buf); + need_dfu_state = 0; + + /* override the timeout from the status response, + needed for AT76C505A */ + if (manifest_sync_timeout > 0) + dfu_timeout = manifest_sync_timeout; + + at76_dbg(DBG_DFU, "DFU: Waiting for manifest phase"); + schedule_timeout_interruptible(dfu_timeout); + break; + + case STATE_DFU_MANIFEST: + at76_dbg(DBG_DFU, "STATE_DFU_MANIFEST"); + is_done = 1; + break; + + case STATE_DFU_MANIFEST_WAIT_RESET: + at76_dbg(DBG_DFU, "STATE_DFU_MANIFEST_WAIT_RESET"); + is_done = 1; + break; + + case STATE_DFU_UPLOAD_IDLE: + at76_dbg(DBG_DFU, "STATE_DFU_UPLOAD_IDLE"); + break; + + case STATE_DFU_ERROR: + at76_dbg(DBG_DFU, "STATE_DFU_ERROR"); + ret = -EPIPE; + break; + + default: + at76_dbg(DBG_DFU, "DFU UNKNOWN STATE (%d)", dfu_state); + ret = -EINVAL; + break; + } + } while (!is_done && (ret >= 0)); + +exit: + kfree(block); + if (ret >= 0) + ret = 0; + + return ret; +} + +/* LED trigger */ +static int tx_activity; +static void at76_ledtrig_tx_timerfunc(unsigned long data); +static DEFINE_TIMER(ledtrig_tx_timer, at76_ledtrig_tx_timerfunc, 0, 0); +DEFINE_LED_TRIGGER(ledtrig_tx); + +static void at76_ledtrig_tx_timerfunc(unsigned long data) +{ + static int tx_lastactivity; + + if (tx_lastactivity != tx_activity) { + tx_lastactivity = tx_activity; + led_trigger_event(ledtrig_tx, LED_FULL); + mod_timer(&ledtrig_tx_timer, jiffies + HZ / 4); + } else + led_trigger_event(ledtrig_tx, LED_OFF); +} + +static void at76_ledtrig_tx_activity(void) +{ + tx_activity++; + if (!timer_pending(&ledtrig_tx_timer)) + mod_timer(&ledtrig_tx_timer, jiffies + HZ / 4); +} + +static int at76_remap(struct usb_device *udev) +{ + int ret; + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x0a, + USB_TYPE_VENDOR | USB_DIR_OUT | + USB_RECIP_INTERFACE, 0, 0, NULL, 0, + USB_CTRL_GET_TIMEOUT); + if (ret < 0) + return ret; + return 0; +} + +static int at76_get_op_mode(struct usb_device *udev) +{ + int ret; + u8 saved; + u8 *op_mode; + + op_mode = kmalloc(1, GFP_NOIO); + if (!op_mode) + return -ENOMEM; + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x33, + USB_TYPE_VENDOR | USB_DIR_IN | + USB_RECIP_INTERFACE, 0x01, 0, op_mode, 1, + USB_CTRL_GET_TIMEOUT); + saved = *op_mode; + kfree(op_mode); + + if (ret < 0) + return ret; + else if (ret < 1) + return -EIO; + else + return saved; +} + +/* Load a block of the second ("external") part of the firmware */ +static inline int at76_load_ext_fw_block(struct usb_device *udev, int blockno, + void *block, int size) +{ + return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x0e, + USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_DEVICE, + 0x0802, blockno, block, size, + USB_CTRL_GET_TIMEOUT); +} + +static inline int at76_get_hw_cfg(struct usb_device *udev, + union at76_hwcfg *buf, int buf_size) +{ + return usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x33, + USB_TYPE_VENDOR | USB_DIR_IN | + USB_RECIP_INTERFACE, 0x0a02, 0, + buf, buf_size, USB_CTRL_GET_TIMEOUT); +} + +/* Intersil boards use a different "value" for GetHWConfig requests */ +static inline int at76_get_hw_cfg_intersil(struct usb_device *udev, + union at76_hwcfg *buf, int buf_size) +{ + return usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x33, + USB_TYPE_VENDOR | USB_DIR_IN | + USB_RECIP_INTERFACE, 0x0902, 0, + buf, buf_size, USB_CTRL_GET_TIMEOUT); +} + +/* Get the hardware configuration for the adapter and put it to the appropriate + * fields of 'priv' (the GetHWConfig request and interpretation of the result + * depends on the board type) */ +static int at76_get_hw_config(struct at76_priv *priv) +{ + int ret; + union at76_hwcfg *hwcfg = kmalloc(sizeof(*hwcfg), GFP_KERNEL); + + if (!hwcfg) + return -ENOMEM; + + if (at76_is_intersil(priv->board_type)) { + ret = at76_get_hw_cfg_intersil(priv->udev, hwcfg, + sizeof(hwcfg->i)); + if (ret < 0) + goto exit; + memcpy(priv->mac_addr, hwcfg->i.mac_addr, ETH_ALEN); + priv->regulatory_domain = hwcfg->i.regulatory_domain; + } else if (at76_is_503rfmd(priv->board_type)) { + ret = at76_get_hw_cfg(priv->udev, hwcfg, sizeof(hwcfg->r3)); + if (ret < 0) + goto exit; + memcpy(priv->mac_addr, hwcfg->r3.mac_addr, ETH_ALEN); + priv->regulatory_domain = hwcfg->r3.regulatory_domain; + } else { + ret = at76_get_hw_cfg(priv->udev, hwcfg, sizeof(hwcfg->r5)); + if (ret < 0) + goto exit; + memcpy(priv->mac_addr, hwcfg->r5.mac_addr, ETH_ALEN); + priv->regulatory_domain = hwcfg->r5.regulatory_domain; + } + +exit: + kfree(hwcfg); + if (ret < 0) + wiphy_err(priv->hw->wiphy, "cannot get HW Config (error %d)\n", + ret); + + return ret; +} + +static struct reg_domain const *at76_get_reg_domain(u16 code) +{ + int i; + static struct reg_domain const fd_tab[] = { + { 0x10, "FCC (USA)", 0x7ff }, /* ch 1-11 */ + { 0x20, "IC (Canada)", 0x7ff }, /* ch 1-11 */ + { 0x30, "ETSI (most of Europe)", 0x1fff }, /* ch 1-13 */ + { 0x31, "Spain", 0x600 }, /* ch 10-11 */ + { 0x32, "France", 0x1e00 }, /* ch 10-13 */ + { 0x40, "MKK (Japan)", 0x2000 }, /* ch 14 */ + { 0x41, "MKK1 (Japan)", 0x3fff }, /* ch 1-14 */ + { 0x50, "Israel", 0x3fc }, /* ch 3-9 */ + { 0x00, "", 0xffffffff } /* ch 1-32 */ + }; + + /* Last entry is fallback for unknown domain code */ + for (i = 0; i < ARRAY_SIZE(fd_tab) - 1; i++) + if (code == fd_tab[i].code) + break; + + return &fd_tab[i]; +} + +static inline int at76_get_mib(struct usb_device *udev, u16 mib, void *buf, + int buf_size) +{ + int ret; + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x33, + USB_TYPE_VENDOR | USB_DIR_IN | + USB_RECIP_INTERFACE, mib << 8, 0, buf, buf_size, + USB_CTRL_GET_TIMEOUT); + if (ret >= 0 && ret != buf_size) + return -EIO; + return ret; +} + +/* Return positive number for status, negative for an error */ +static inline int at76_get_cmd_status(struct usb_device *udev, u8 cmd) +{ + u8 *stat_buf; + int ret; + + stat_buf = kmalloc(40, GFP_NOIO); + if (!stat_buf) + return -ENOMEM; + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x22, + USB_TYPE_VENDOR | USB_DIR_IN | + USB_RECIP_INTERFACE, cmd, 0, stat_buf, + 40, USB_CTRL_GET_TIMEOUT); + if (ret >= 0) + ret = stat_buf[5]; + kfree(stat_buf); + + return ret; +} + +#define MAKE_CMD_CASE(c) case (c): return #c +static const char *at76_get_cmd_string(u8 cmd_status) +{ + switch (cmd_status) { + MAKE_CMD_CASE(CMD_SET_MIB); + MAKE_CMD_CASE(CMD_GET_MIB); + MAKE_CMD_CASE(CMD_SCAN); + MAKE_CMD_CASE(CMD_JOIN); + MAKE_CMD_CASE(CMD_START_IBSS); + MAKE_CMD_CASE(CMD_RADIO_ON); + MAKE_CMD_CASE(CMD_RADIO_OFF); + MAKE_CMD_CASE(CMD_STARTUP); + } + + return "UNKNOWN"; +} + +static int at76_set_card_command(struct usb_device *udev, u8 cmd, void *buf, + int buf_size) +{ + int ret; + struct at76_command *cmd_buf = kmalloc(sizeof(struct at76_command) + + buf_size, GFP_KERNEL); + + if (!cmd_buf) + return -ENOMEM; + + cmd_buf->cmd = cmd; + cmd_buf->reserved = 0; + cmd_buf->size = cpu_to_le16(buf_size); + memcpy(cmd_buf->data, buf, buf_size); + + at76_dbg_dump(DBG_CMD, cmd_buf, sizeof(struct at76_command) + buf_size, + "issuing command %s (0x%02x)", + at76_get_cmd_string(cmd), cmd); + + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x0e, + USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_DEVICE, + 0, 0, cmd_buf, + sizeof(struct at76_command) + buf_size, + USB_CTRL_GET_TIMEOUT); + kfree(cmd_buf); + return ret; +} + +#define MAKE_CMD_STATUS_CASE(c) case (c): return #c +static const char *at76_get_cmd_status_string(u8 cmd_status) +{ + switch (cmd_status) { + MAKE_CMD_STATUS_CASE(CMD_STATUS_IDLE); + MAKE_CMD_STATUS_CASE(CMD_STATUS_COMPLETE); + MAKE_CMD_STATUS_CASE(CMD_STATUS_UNKNOWN); + MAKE_CMD_STATUS_CASE(CMD_STATUS_INVALID_PARAMETER); + MAKE_CMD_STATUS_CASE(CMD_STATUS_FUNCTION_NOT_SUPPORTED); + MAKE_CMD_STATUS_CASE(CMD_STATUS_TIME_OUT); + MAKE_CMD_STATUS_CASE(CMD_STATUS_IN_PROGRESS); + MAKE_CMD_STATUS_CASE(CMD_STATUS_HOST_FAILURE); + MAKE_CMD_STATUS_CASE(CMD_STATUS_SCAN_FAILED); + } + + return "UNKNOWN"; +} + +/* Wait until the command is completed */ +static int at76_wait_completion(struct at76_priv *priv, int cmd) +{ + int status = 0; + unsigned long timeout = jiffies + CMD_COMPLETION_TIMEOUT; + + do { + status = at76_get_cmd_status(priv->udev, cmd); + if (status < 0) { + wiphy_err(priv->hw->wiphy, + "at76_get_cmd_status failed: %d\n", + status); + break; + } + + at76_dbg(DBG_WAIT_COMPLETE, + "%s: Waiting on cmd %d, status = %d (%s)", + wiphy_name(priv->hw->wiphy), cmd, status, + at76_get_cmd_status_string(status)); + + if (status != CMD_STATUS_IN_PROGRESS + && status != CMD_STATUS_IDLE) + break; + + schedule_timeout_interruptible(HZ / 10); /* 100 ms */ + if (time_after(jiffies, timeout)) { + wiphy_err(priv->hw->wiphy, + "completion timeout for command %d\n", cmd); + status = -ETIMEDOUT; + break; + } + } while (1); + + return status; +} + +static int at76_set_mib(struct at76_priv *priv, struct set_mib_buffer *buf) +{ + int ret; + + ret = at76_set_card_command(priv->udev, CMD_SET_MIB, buf, + offsetof(struct set_mib_buffer, + data) + buf->size); + if (ret < 0) + return ret; + + ret = at76_wait_completion(priv, CMD_SET_MIB); + if (ret != CMD_STATUS_COMPLETE) { + wiphy_info(priv->hw->wiphy, + "set_mib: at76_wait_completion failed with %d\n", + ret); + ret = -EIO; + } + + return ret; +} + +/* Return < 0 on error, == 0 if no command sent, == 1 if cmd sent */ +static int at76_set_radio(struct at76_priv *priv, int enable) +{ + int ret; + int cmd; + + if (priv->radio_on == enable) + return 0; + + cmd = enable ? CMD_RADIO_ON : CMD_RADIO_OFF; + + ret = at76_set_card_command(priv->udev, cmd, NULL, 0); + if (ret < 0) + wiphy_err(priv->hw->wiphy, + "at76_set_card_command(%d) failed: %d\n", cmd, ret); + else + ret = 1; + + priv->radio_on = enable; + return ret; +} + +/* Set current power save mode (AT76_PM_OFF/AT76_PM_ON/AT76_PM_SMART) */ +static int at76_set_pm_mode(struct at76_priv *priv) +{ + int ret = 0; + + priv->mib_buf.type = MIB_MAC_MGMT; + priv->mib_buf.size = 1; + priv->mib_buf.index = offsetof(struct mib_mac_mgmt, power_mgmt_mode); + priv->mib_buf.data.byte = priv->pm_mode; + + ret = at76_set_mib(priv, &priv->mib_buf); + if (ret < 0) + wiphy_err(priv->hw->wiphy, "set_mib (pm_mode) failed: %d\n", + ret); + + return ret; +} + +static int at76_set_preamble(struct at76_priv *priv, u8 type) +{ + int ret = 0; + + priv->mib_buf.type = MIB_LOCAL; + priv->mib_buf.size = 1; + priv->mib_buf.index = offsetof(struct mib_local, preamble_type); + priv->mib_buf.data.byte = type; + + ret = at76_set_mib(priv, &priv->mib_buf); + if (ret < 0) + wiphy_err(priv->hw->wiphy, "set_mib (preamble) failed: %d\n", + ret); + + return ret; +} + +static int at76_set_frag(struct at76_priv *priv, u16 size) +{ + int ret = 0; + + priv->mib_buf.type = MIB_MAC; + priv->mib_buf.size = 2; + priv->mib_buf.index = offsetof(struct mib_mac, frag_threshold); + priv->mib_buf.data.word = cpu_to_le16(size); + + ret = at76_set_mib(priv, &priv->mib_buf); + if (ret < 0) + wiphy_err(priv->hw->wiphy, + "set_mib (frag threshold) failed: %d\n", ret); + + return ret; +} + +static int at76_set_rts(struct at76_priv *priv, u16 size) +{ + int ret = 0; + + priv->mib_buf.type = MIB_MAC; + priv->mib_buf.size = 2; + priv->mib_buf.index = offsetof(struct mib_mac, rts_threshold); + priv->mib_buf.data.word = cpu_to_le16(size); + + ret = at76_set_mib(priv, &priv->mib_buf); + if (ret < 0) + wiphy_err(priv->hw->wiphy, "set_mib (rts) failed: %d\n", ret); + + return ret; +} + +static int at76_set_autorate_fallback(struct at76_priv *priv, int onoff) +{ + int ret = 0; + + priv->mib_buf.type = MIB_LOCAL; + priv->mib_buf.size = 1; + priv->mib_buf.index = offsetof(struct mib_local, txautorate_fallback); + priv->mib_buf.data.byte = onoff; + + ret = at76_set_mib(priv, &priv->mib_buf); + if (ret < 0) + wiphy_err(priv->hw->wiphy, + "set_mib (autorate fallback) failed: %d\n", ret); + + return ret; +} + +static void at76_dump_mib_mac_addr(struct at76_priv *priv) +{ + int i; + int ret; + struct mib_mac_addr *m = kmalloc(sizeof(struct mib_mac_addr), + GFP_KERNEL); + + if (!m) + return; + + ret = at76_get_mib(priv->udev, MIB_MAC_ADDR, m, + sizeof(struct mib_mac_addr)); + if (ret < 0) { + wiphy_err(priv->hw->wiphy, + "at76_get_mib (MAC_ADDR) failed: %d\n", ret); + goto exit; + } + + at76_dbg(DBG_MIB, "%s: MIB MAC_ADDR: mac_addr %pM res 0x%x 0x%x", + wiphy_name(priv->hw->wiphy), + m->mac_addr, m->res[0], m->res[1]); + for (i = 0; i < ARRAY_SIZE(m->group_addr); i++) + at76_dbg(DBG_MIB, "%s: MIB MAC_ADDR: group addr %d: %pM, " + "status %d", wiphy_name(priv->hw->wiphy), i, + m->group_addr[i], m->group_addr_status[i]); +exit: + kfree(m); +} + +static void at76_dump_mib_mac_wep(struct at76_priv *priv) +{ + int i; + int ret; + int key_len; + struct mib_mac_wep *m = kmalloc(sizeof(struct mib_mac_wep), GFP_KERNEL); + + if (!m) + return; + + ret = at76_get_mib(priv->udev, MIB_MAC_WEP, m, + sizeof(struct mib_mac_wep)); + if (ret < 0) { + wiphy_err(priv->hw->wiphy, + "at76_get_mib (MAC_WEP) failed: %d\n", ret); + goto exit; + } + + at76_dbg(DBG_MIB, "%s: MIB MAC_WEP: priv_invoked %u def_key_id %u " + "key_len %u excl_unencr %u wep_icv_err %u wep_excluded %u " + "encr_level %u key %d", wiphy_name(priv->hw->wiphy), + m->privacy_invoked, m->wep_default_key_id, + m->wep_key_mapping_len, m->exclude_unencrypted, + le32_to_cpu(m->wep_icv_error_count), + le32_to_cpu(m->wep_excluded_count), m->encryption_level, + m->wep_default_key_id); + + key_len = (m->encryption_level == 1) ? + WEP_SMALL_KEY_LEN : WEP_LARGE_KEY_LEN; + + for (i = 0; i < WEP_KEYS; i++) + at76_dbg(DBG_MIB, "%s: MIB MAC_WEP: key %d: %*phD", + wiphy_name(priv->hw->wiphy), i, + key_len, m->wep_default_keyvalue[i]); +exit: + kfree(m); +} + +static void at76_dump_mib_mac_mgmt(struct at76_priv *priv) +{ + int ret; + struct mib_mac_mgmt *m = kmalloc(sizeof(struct mib_mac_mgmt), + GFP_KERNEL); + + if (!m) + return; + + ret = at76_get_mib(priv->udev, MIB_MAC_MGMT, m, + sizeof(struct mib_mac_mgmt)); + if (ret < 0) { + wiphy_err(priv->hw->wiphy, + "at76_get_mib (MAC_MGMT) failed: %d\n", ret); + goto exit; + } + + at76_dbg(DBG_MIB, "%s: MIB MAC_MGMT: beacon_period %d CFP_max_duration " + "%d medium_occupancy_limit %d station_id 0x%x ATIM_window %d " + "CFP_mode %d privacy_opt_impl %d DTIM_period %d CFP_period %d " + "current_bssid %pM current_essid %*phD current_bss_type %d " + "pm_mode %d ibss_change %d res %d " + "multi_domain_capability_implemented %d " + "international_roaming %d country_string %.3s", + wiphy_name(priv->hw->wiphy), le16_to_cpu(m->beacon_period), + le16_to_cpu(m->CFP_max_duration), + le16_to_cpu(m->medium_occupancy_limit), + le16_to_cpu(m->station_id), le16_to_cpu(m->ATIM_window), + m->CFP_mode, m->privacy_option_implemented, m->DTIM_period, + m->CFP_period, m->current_bssid, + IW_ESSID_MAX_SIZE, m->current_essid, + m->current_bss_type, m->power_mgmt_mode, m->ibss_change, + m->res, m->multi_domain_capability_implemented, + m->multi_domain_capability_enabled, m->country_string); +exit: + kfree(m); +} + +static void at76_dump_mib_mac(struct at76_priv *priv) +{ + int ret; + struct mib_mac *m = kmalloc(sizeof(struct mib_mac), GFP_KERNEL); + + if (!m) + return; + + ret = at76_get_mib(priv->udev, MIB_MAC, m, sizeof(struct mib_mac)); + if (ret < 0) { + wiphy_err(priv->hw->wiphy, + "at76_get_mib (MAC) failed: %d\n", ret); + goto exit; + } + + at76_dbg(DBG_MIB, "%s: MIB MAC: max_tx_msdu_lifetime %d " + "max_rx_lifetime %d frag_threshold %d rts_threshold %d " + "cwmin %d cwmax %d short_retry_time %d long_retry_time %d " + "scan_type %d scan_channel %d probe_delay %u " + "min_channel_time %d max_channel_time %d listen_int %d " + "desired_ssid %*phD desired_bssid %pM desired_bsstype %d", + wiphy_name(priv->hw->wiphy), + le32_to_cpu(m->max_tx_msdu_lifetime), + le32_to_cpu(m->max_rx_lifetime), + le16_to_cpu(m->frag_threshold), le16_to_cpu(m->rts_threshold), + le16_to_cpu(m->cwmin), le16_to_cpu(m->cwmax), + m->short_retry_time, m->long_retry_time, m->scan_type, + m->scan_channel, le16_to_cpu(m->probe_delay), + le16_to_cpu(m->min_channel_time), + le16_to_cpu(m->max_channel_time), + le16_to_cpu(m->listen_interval), + IW_ESSID_MAX_SIZE, m->desired_ssid, + m->desired_bssid, m->desired_bsstype); +exit: + kfree(m); +} + +static void at76_dump_mib_phy(struct at76_priv *priv) +{ + int ret; + struct mib_phy *m = kmalloc(sizeof(struct mib_phy), GFP_KERNEL); + + if (!m) + return; + + ret = at76_get_mib(priv->udev, MIB_PHY, m, sizeof(struct mib_phy)); + if (ret < 0) { + wiphy_err(priv->hw->wiphy, + "at76_get_mib (PHY) failed: %d\n", ret); + goto exit; + } + + at76_dbg(DBG_MIB, "%s: MIB PHY: ed_threshold %d slot_time %d " + "sifs_time %d preamble_length %d plcp_header_length %d " + "mpdu_max_length %d cca_mode_supported %d operation_rate_set " + "0x%x 0x%x 0x%x 0x%x channel_id %d current_cca_mode %d " + "phy_type %d current_reg_domain %d", + wiphy_name(priv->hw->wiphy), le32_to_cpu(m->ed_threshold), + le16_to_cpu(m->slot_time), le16_to_cpu(m->sifs_time), + le16_to_cpu(m->preamble_length), + le16_to_cpu(m->plcp_header_length), + le16_to_cpu(m->mpdu_max_length), + le16_to_cpu(m->cca_mode_supported), m->operation_rate_set[0], + m->operation_rate_set[1], m->operation_rate_set[2], + m->operation_rate_set[3], m->channel_id, m->current_cca_mode, + m->phy_type, m->current_reg_domain); +exit: + kfree(m); +} + +static void at76_dump_mib_local(struct at76_priv *priv) +{ + int ret; + struct mib_local *m = kmalloc(sizeof(*m), GFP_KERNEL); + + if (!m) + return; + + ret = at76_get_mib(priv->udev, MIB_LOCAL, m, sizeof(*m)); + if (ret < 0) { + wiphy_err(priv->hw->wiphy, + "at76_get_mib (LOCAL) failed: %d\n", ret); + goto exit; + } + + at76_dbg(DBG_MIB, "%s: MIB LOCAL: beacon_enable %d " + "txautorate_fallback %d ssid_size %d promiscuous_mode %d " + "preamble_type %d", wiphy_name(priv->hw->wiphy), + m->beacon_enable, + m->txautorate_fallback, m->ssid_size, m->promiscuous_mode, + m->preamble_type); +exit: + kfree(m); +} + +static void at76_dump_mib_mdomain(struct at76_priv *priv) +{ + int ret; + struct mib_mdomain *m = kmalloc(sizeof(struct mib_mdomain), GFP_KERNEL); + + if (!m) + return; + + ret = at76_get_mib(priv->udev, MIB_MDOMAIN, m, + sizeof(struct mib_mdomain)); + if (ret < 0) { + wiphy_err(priv->hw->wiphy, + "at76_get_mib (MDOMAIN) failed: %d\n", ret); + goto exit; + } + + at76_dbg(DBG_MIB, "%s: MIB MDOMAIN: channel_list %*phD", + wiphy_name(priv->hw->wiphy), + (int)sizeof(m->channel_list), m->channel_list); + + at76_dbg(DBG_MIB, "%s: MIB MDOMAIN: tx_powerlevel %*phD", + wiphy_name(priv->hw->wiphy), + (int)sizeof(m->tx_powerlevel), m->tx_powerlevel); +exit: + kfree(m); +} + +/* Enable monitor mode */ +static int at76_start_monitor(struct at76_priv *priv) +{ + struct at76_req_scan scan; + int ret; + + memset(&scan, 0, sizeof(struct at76_req_scan)); + memset(scan.bssid, 0xff, ETH_ALEN); + + scan.channel = priv->channel; + scan.scan_type = SCAN_TYPE_PASSIVE; + scan.international_scan = 0; + scan.min_channel_time = cpu_to_le16(priv->scan_min_time); + scan.max_channel_time = cpu_to_le16(priv->scan_max_time); + scan.probe_delay = cpu_to_le16(0); + + ret = at76_set_card_command(priv->udev, CMD_SCAN, &scan, sizeof(scan)); + if (ret >= 0) + ret = at76_get_cmd_status(priv->udev, CMD_SCAN); + + return ret; +} + +/* Calculate padding from txbuf->wlength (which excludes the USB TX header), + likely to compensate a flaw in the AT76C503A USB part ... */ +static inline int at76_calc_padding(int wlen) +{ + /* add the USB TX header */ + wlen += AT76_TX_HDRLEN; + + wlen = wlen % 64; + + if (wlen < 50) + return 50 - wlen; + + if (wlen >= 61) + return 64 + 50 - wlen; + + return 0; +} + +static void at76_rx_callback(struct urb *urb) +{ + struct at76_priv *priv = urb->context; + + priv->rx_tasklet.data = (unsigned long)urb; + tasklet_schedule(&priv->rx_tasklet); +} + +static int at76_submit_rx_urb(struct at76_priv *priv) +{ + int ret; + int size; + struct sk_buff *skb = priv->rx_skb; + + if (!priv->rx_urb) { + wiphy_err(priv->hw->wiphy, "%s: priv->rx_urb is NULL\n", + __func__); + return -EFAULT; + } + + if (!skb) { + skb = dev_alloc_skb(sizeof(struct at76_rx_buffer)); + if (!skb) { + wiphy_err(priv->hw->wiphy, + "cannot allocate rx skbuff\n"); + ret = -ENOMEM; + goto exit; + } + priv->rx_skb = skb; + } else { + skb_push(skb, skb_headroom(skb)); + skb_trim(skb, 0); + } + + size = skb_tailroom(skb); + usb_fill_bulk_urb(priv->rx_urb, priv->udev, priv->rx_pipe, + skb_put(skb, size), size, at76_rx_callback, priv); + ret = usb_submit_urb(priv->rx_urb, GFP_ATOMIC); + if (ret < 0) { + if (ret == -ENODEV) + at76_dbg(DBG_DEVSTART, + "usb_submit_urb returned -ENODEV"); + else + wiphy_err(priv->hw->wiphy, + "rx, usb_submit_urb failed: %d\n", ret); + } + +exit: + if (ret < 0 && ret != -ENODEV) + wiphy_err(priv->hw->wiphy, + "cannot submit rx urb - please unload the driver and/or power cycle the device\n"); + + return ret; +} + +/* Download external firmware */ +static int at76_load_external_fw(struct usb_device *udev, struct fwentry *fwe) +{ + int ret; + int op_mode; + int blockno = 0; + int bsize; + u8 *block; + u8 *buf = fwe->extfw; + int size = fwe->extfw_size; + + if (!buf || !size) + return -ENOENT; + + op_mode = at76_get_op_mode(udev); + at76_dbg(DBG_DEVSTART, "opmode %d", op_mode); + + if (op_mode != OPMODE_NORMAL_NIC_WITHOUT_FLASH) { + dev_err(&udev->dev, "unexpected opmode %d\n", op_mode); + return -EINVAL; + } + + block = kmalloc(FW_BLOCK_SIZE, GFP_KERNEL); + if (!block) + return -ENOMEM; + + at76_dbg(DBG_DEVSTART, "downloading external firmware"); + + /* for fw >= 0.100, the device needs an extra empty block */ + do { + bsize = min_t(int, size, FW_BLOCK_SIZE); + memcpy(block, buf, bsize); + at76_dbg(DBG_DEVSTART, + "ext fw, size left = %5d, bsize = %4d, blockno = %2d", + size, bsize, blockno); + ret = at76_load_ext_fw_block(udev, blockno, block, bsize); + if (ret != bsize) { + dev_err(&udev->dev, + "loading %dth firmware block failed: %d\n", + blockno, ret); + goto exit; + } + buf += bsize; + size -= bsize; + blockno++; + } while (bsize > 0); + + if (at76_is_505a(fwe->board_type)) { + at76_dbg(DBG_DEVSTART, "200 ms delay for 505a"); + schedule_timeout_interruptible(HZ / 5 + 1); + } + +exit: + kfree(block); + if (ret < 0) + dev_err(&udev->dev, + "downloading external firmware failed: %d\n", ret); + return ret; +} + +/* Download internal firmware */ +static int at76_load_internal_fw(struct usb_device *udev, struct fwentry *fwe) +{ + int ret; + int need_remap = !at76_is_505a(fwe->board_type); + + ret = at76_usbdfu_download(udev, fwe->intfw, fwe->intfw_size, + need_remap ? 0 : 2 * HZ); + + if (ret < 0) { + dev_err(&udev->dev, + "downloading internal fw failed with %d\n", ret); + goto exit; + } + + at76_dbg(DBG_DEVSTART, "sending REMAP"); + + /* no REMAP for 505A (see SF driver) */ + if (need_remap) { + ret = at76_remap(udev); + if (ret < 0) { + dev_err(&udev->dev, + "sending REMAP failed with %d\n", ret); + goto exit; + } + } + + at76_dbg(DBG_DEVSTART, "sleeping for 2 seconds"); + schedule_timeout_interruptible(2 * HZ + 1); + usb_reset_device(udev); + +exit: + return ret; +} + +static int at76_startup_device(struct at76_priv *priv) +{ + struct at76_card_config *ccfg = &priv->card_config; + int ret; + + at76_dbg(DBG_PARAMS, + "%s param: ssid %.*s (%*phD) mode %s ch %d wep %s key %d " + "keylen %d", wiphy_name(priv->hw->wiphy), priv->essid_size, + priv->essid, IW_ESSID_MAX_SIZE, priv->essid, + priv->iw_mode == IW_MODE_ADHOC ? "adhoc" : "infra", + priv->channel, priv->wep_enabled ? "enabled" : "disabled", + priv->wep_key_id, priv->wep_keys_len[priv->wep_key_id]); + at76_dbg(DBG_PARAMS, + "%s param: preamble %s rts %d retry %d frag %d " + "txrate %s auth_mode %d", wiphy_name(priv->hw->wiphy), + preambles[priv->preamble_type], priv->rts_threshold, + priv->short_retry_limit, priv->frag_threshold, + priv->txrate == TX_RATE_1MBIT ? "1MBit" : priv->txrate == + TX_RATE_2MBIT ? "2MBit" : priv->txrate == + TX_RATE_5_5MBIT ? "5.5MBit" : priv->txrate == + TX_RATE_11MBIT ? "11MBit" : priv->txrate == + TX_RATE_AUTO ? "auto" : "", priv->auth_mode); + at76_dbg(DBG_PARAMS, + "%s param: pm_mode %d pm_period %d auth_mode %s " + "scan_times %d %d scan_mode %s", + wiphy_name(priv->hw->wiphy), priv->pm_mode, priv->pm_period, + priv->auth_mode == WLAN_AUTH_OPEN ? "open" : "shared_secret", + priv->scan_min_time, priv->scan_max_time, + priv->scan_mode == SCAN_TYPE_ACTIVE ? "active" : "passive"); + + memset(ccfg, 0, sizeof(struct at76_card_config)); + ccfg->promiscuous_mode = 0; + ccfg->short_retry_limit = priv->short_retry_limit; + + if (priv->wep_enabled) { + if (priv->wep_keys_len[priv->wep_key_id] > WEP_SMALL_KEY_LEN) + ccfg->encryption_type = 2; + else + ccfg->encryption_type = 1; + + /* jal: always exclude unencrypted if WEP is active */ + ccfg->exclude_unencrypted = 1; + } else { + ccfg->exclude_unencrypted = 0; + ccfg->encryption_type = 0; + } + + ccfg->rts_threshold = cpu_to_le16(priv->rts_threshold); + ccfg->fragmentation_threshold = cpu_to_le16(priv->frag_threshold); + + memcpy(ccfg->basic_rate_set, hw_rates, 4); + /* jal: really needed, we do a set_mib for autorate later ??? */ + ccfg->auto_rate_fallback = (priv->txrate == TX_RATE_AUTO ? 1 : 0); + ccfg->channel = priv->channel; + ccfg->privacy_invoked = priv->wep_enabled; + memcpy(ccfg->current_ssid, priv->essid, IW_ESSID_MAX_SIZE); + ccfg->ssid_len = priv->essid_size; + + ccfg->wep_default_key_id = priv->wep_key_id; + memcpy(ccfg->wep_default_key_value, priv->wep_keys, + sizeof(priv->wep_keys)); + + ccfg->short_preamble = priv->preamble_type; + ccfg->beacon_period = cpu_to_le16(priv->beacon_period); + + ret = at76_set_card_command(priv->udev, CMD_STARTUP, &priv->card_config, + sizeof(struct at76_card_config)); + if (ret < 0) { + wiphy_err(priv->hw->wiphy, "at76_set_card_command failed: %d\n", + ret); + return ret; + } + + at76_wait_completion(priv, CMD_STARTUP); + + /* remove BSSID from previous run */ + memset(priv->bssid, 0, ETH_ALEN); + + if (at76_set_radio(priv, 1) == 1) + at76_wait_completion(priv, CMD_RADIO_ON); + + ret = at76_set_preamble(priv, priv->preamble_type); + if (ret < 0) + return ret; + + ret = at76_set_frag(priv, priv->frag_threshold); + if (ret < 0) + return ret; + + ret = at76_set_rts(priv, priv->rts_threshold); + if (ret < 0) + return ret; + + ret = at76_set_autorate_fallback(priv, + priv->txrate == TX_RATE_AUTO ? 1 : 0); + if (ret < 0) + return ret; + + ret = at76_set_pm_mode(priv); + if (ret < 0) + return ret; + + if (at76_debug & DBG_MIB) { + at76_dump_mib_mac(priv); + at76_dump_mib_mac_addr(priv); + at76_dump_mib_mac_mgmt(priv); + at76_dump_mib_mac_wep(priv); + at76_dump_mib_mdomain(priv); + at76_dump_mib_phy(priv); + at76_dump_mib_local(priv); + } + + return 0; +} + +/* Enable or disable promiscuous mode */ +static void at76_work_set_promisc(struct work_struct *work) +{ + struct at76_priv *priv = container_of(work, struct at76_priv, + work_set_promisc); + int ret = 0; + + if (priv->device_unplugged) + return; + + mutex_lock(&priv->mtx); + + priv->mib_buf.type = MIB_LOCAL; + priv->mib_buf.size = 1; + priv->mib_buf.index = offsetof(struct mib_local, promiscuous_mode); + priv->mib_buf.data.byte = priv->promisc ? 1 : 0; + + ret = at76_set_mib(priv, &priv->mib_buf); + if (ret < 0) + wiphy_err(priv->hw->wiphy, + "set_mib (promiscuous_mode) failed: %d\n", ret); + + mutex_unlock(&priv->mtx); +} + +/* Submit Rx urb back to the device */ +static void at76_work_submit_rx(struct work_struct *work) +{ + struct at76_priv *priv = container_of(work, struct at76_priv, + work_submit_rx); + + mutex_lock(&priv->mtx); + at76_submit_rx_urb(priv); + mutex_unlock(&priv->mtx); +} + +static void at76_rx_tasklet(unsigned long param) +{ + struct urb *urb = (struct urb *)param; + struct at76_priv *priv = urb->context; + struct at76_rx_buffer *buf; + struct ieee80211_rx_status rx_status = { 0 }; + + if (priv->device_unplugged) { + at76_dbg(DBG_DEVSTART, "device unplugged"); + at76_dbg(DBG_DEVSTART, "urb status %d", urb->status); + return; + } + + if (!priv->rx_skb || !priv->rx_skb->data) + return; + + buf = (struct at76_rx_buffer *)priv->rx_skb->data; + + if (urb->status != 0) { + if (urb->status != -ENOENT && urb->status != -ECONNRESET) + at76_dbg(DBG_URB, + "%s %s: - nonzero Rx bulk status received: %d", + __func__, wiphy_name(priv->hw->wiphy), + urb->status); + return; + } + + at76_dbg(DBG_RX_ATMEL_HDR, + "%s: rx frame: rate %d rssi %d noise %d link %d", + wiphy_name(priv->hw->wiphy), buf->rx_rate, buf->rssi, + buf->noise_level, buf->link_quality); + + skb_pull(priv->rx_skb, AT76_RX_HDRLEN); + skb_trim(priv->rx_skb, le16_to_cpu(buf->wlength)); + at76_dbg_dump(DBG_RX_DATA, priv->rx_skb->data, + priv->rx_skb->len, "RX: len=%d", priv->rx_skb->len); + + rx_status.signal = buf->rssi; + rx_status.flag |= RX_FLAG_DECRYPTED; + rx_status.flag |= RX_FLAG_IV_STRIPPED; + + at76_dbg(DBG_MAC80211, "calling ieee80211_rx_irqsafe(): %d/%d", + priv->rx_skb->len, priv->rx_skb->data_len); + memcpy(IEEE80211_SKB_RXCB(priv->rx_skb), &rx_status, sizeof(rx_status)); + ieee80211_rx_irqsafe(priv->hw, priv->rx_skb); + + /* Use a new skb for the next receive */ + priv->rx_skb = NULL; + + at76_submit_rx_urb(priv); +} + +/* Load firmware into kernel memory and parse it */ +static struct fwentry *at76_load_firmware(struct usb_device *udev, + enum board_type board_type) +{ + int ret; + char *str; + struct at76_fw_header *fwh; + struct fwentry *fwe = &firmwares[board_type]; + + mutex_lock(&fw_mutex); + + if (fwe->loaded) { + at76_dbg(DBG_FW, "re-using previously loaded fw"); + goto exit; + } + + at76_dbg(DBG_FW, "downloading firmware %s", fwe->fwname); + ret = request_firmware(&fwe->fw, fwe->fwname, &udev->dev); + if (ret < 0) { + dev_err(&udev->dev, "firmware %s not found!\n", + fwe->fwname); + dev_err(&udev->dev, + "you may need to download the firmware from http://developer.berlios.de/projects/at76c503a/\n"); + goto exit; + } + + at76_dbg(DBG_FW, "got it."); + fwh = (struct at76_fw_header *)(fwe->fw->data); + + if (fwe->fw->size <= sizeof(*fwh)) { + dev_err(&udev->dev, + "firmware is too short (0x%zx)\n", fwe->fw->size); + goto exit; + } + + /* CRC currently not checked */ + fwe->board_type = le32_to_cpu(fwh->board_type); + if (fwe->board_type != board_type) { + dev_err(&udev->dev, + "board type mismatch, requested %u, got %u\n", + board_type, fwe->board_type); + goto exit; + } + + fwe->fw_version.major = fwh->major; + fwe->fw_version.minor = fwh->minor; + fwe->fw_version.patch = fwh->patch; + fwe->fw_version.build = fwh->build; + + str = (char *)fwh + le32_to_cpu(fwh->str_offset); + fwe->intfw = (u8 *)fwh + le32_to_cpu(fwh->int_fw_offset); + fwe->intfw_size = le32_to_cpu(fwh->int_fw_len); + fwe->extfw = (u8 *)fwh + le32_to_cpu(fwh->ext_fw_offset); + fwe->extfw_size = le32_to_cpu(fwh->ext_fw_len); + + fwe->loaded = 1; + + dev_printk(KERN_DEBUG, &udev->dev, + "using firmware %s (version %d.%d.%d-%d)\n", + fwe->fwname, fwh->major, fwh->minor, fwh->patch, fwh->build); + + at76_dbg(DBG_DEVSTART, "board %u, int %d:%d, ext %d:%d", board_type, + le32_to_cpu(fwh->int_fw_offset), le32_to_cpu(fwh->int_fw_len), + le32_to_cpu(fwh->ext_fw_offset), le32_to_cpu(fwh->ext_fw_len)); + at76_dbg(DBG_DEVSTART, "firmware id %s", str); + +exit: + mutex_unlock(&fw_mutex); + + if (fwe->loaded) + return fwe; + else + return NULL; +} + +static int at76_join(struct at76_priv *priv) +{ + struct at76_req_join join; + int ret; + + memset(&join, 0, sizeof(struct at76_req_join)); + memcpy(join.essid, priv->essid, priv->essid_size); + join.essid_size = priv->essid_size; + memcpy(join.bssid, priv->bssid, ETH_ALEN); + join.bss_type = INFRASTRUCTURE_MODE; + join.channel = priv->channel; + join.timeout = cpu_to_le16(2000); + + at76_dbg(DBG_MAC80211, "%s: sending CMD_JOIN", __func__); + ret = at76_set_card_command(priv->udev, CMD_JOIN, &join, + sizeof(struct at76_req_join)); + + if (ret < 0) { + wiphy_err(priv->hw->wiphy, "at76_set_card_command failed: %d\n", + ret); + return 0; + } + + ret = at76_wait_completion(priv, CMD_JOIN); + at76_dbg(DBG_MAC80211, "%s: CMD_JOIN returned: 0x%02x", __func__, ret); + if (ret != CMD_STATUS_COMPLETE) { + wiphy_err(priv->hw->wiphy, "at76_wait_completion failed: %d\n", + ret); + return 0; + } + + at76_set_pm_mode(priv); + + return 0; +} + +static void at76_work_join_bssid(struct work_struct *work) +{ + struct at76_priv *priv = container_of(work, struct at76_priv, + work_join_bssid); + + if (priv->device_unplugged) + return; + + mutex_lock(&priv->mtx); + + if (is_valid_ether_addr(priv->bssid)) + at76_join(priv); + + mutex_unlock(&priv->mtx); +} + +static void at76_mac80211_tx_callback(struct urb *urb) +{ + struct at76_priv *priv = urb->context; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(priv->tx_skb); + + at76_dbg(DBG_MAC80211, "%s()", __func__); + + switch (urb->status) { + case 0: + /* success */ + info->flags |= IEEE80211_TX_STAT_ACK; + break; + case -ENOENT: + case -ECONNRESET: + /* fail, urb has been unlinked */ + /* FIXME: add error message */ + break; + default: + at76_dbg(DBG_URB, "%s - nonzero tx status received: %d", + __func__, urb->status); + break; + } + + memset(&info->status, 0, sizeof(info->status)); + + ieee80211_tx_status_irqsafe(priv->hw, priv->tx_skb); + + priv->tx_skb = NULL; + + ieee80211_wake_queues(priv->hw); +} + +static void at76_mac80211_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct at76_priv *priv = hw->priv; + struct at76_tx_buffer *tx_buffer = priv->bulk_out_buffer; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data; + int padding, submit_len, ret; + + at76_dbg(DBG_MAC80211, "%s()", __func__); + + if (priv->tx_urb->status == -EINPROGRESS) { + wiphy_err(priv->hw->wiphy, + "%s called while tx urb is pending\n", __func__); + dev_kfree_skb_any(skb); + return; + } + + /* The following code lines are important when the device is going to + * authenticate with a new bssid. The driver must send CMD_JOIN before + * an authentication frame is transmitted. For this to succeed, the + * correct bssid of the AP must be known. As mac80211 does not inform + * drivers about the bssid prior to the authentication process the + * following workaround is necessary. If the TX frame is an + * authentication frame extract the bssid and send the CMD_JOIN. */ + if (mgmt->frame_control & cpu_to_le16(IEEE80211_STYPE_AUTH)) { + if (!ether_addr_equal(priv->bssid, mgmt->bssid)) { + memcpy(priv->bssid, mgmt->bssid, ETH_ALEN); + ieee80211_queue_work(hw, &priv->work_join_bssid); + dev_kfree_skb_any(skb); + return; + } + } + + ieee80211_stop_queues(hw); + + at76_ledtrig_tx_activity(); /* tell ledtrigger we send a packet */ + + WARN_ON(priv->tx_skb != NULL); + + priv->tx_skb = skb; + padding = at76_calc_padding(skb->len); + submit_len = AT76_TX_HDRLEN + skb->len + padding; + + /* setup 'Atmel' header */ + memset(tx_buffer, 0, sizeof(*tx_buffer)); + tx_buffer->padding = padding; + tx_buffer->wlength = cpu_to_le16(skb->len); + tx_buffer->tx_rate = ieee80211_get_tx_rate(hw, info)->hw_value; + memset(tx_buffer->reserved, 0, sizeof(tx_buffer->reserved)); + memcpy(tx_buffer->packet, skb->data, skb->len); + + at76_dbg(DBG_TX_DATA, "%s tx: wlen 0x%x pad 0x%x rate %d hdr", + wiphy_name(priv->hw->wiphy), le16_to_cpu(tx_buffer->wlength), + tx_buffer->padding, tx_buffer->tx_rate); + + /* send stuff */ + at76_dbg_dump(DBG_TX_DATA_CONTENT, tx_buffer, submit_len, + "%s(): tx_buffer %d bytes:", __func__, submit_len); + usb_fill_bulk_urb(priv->tx_urb, priv->udev, priv->tx_pipe, tx_buffer, + submit_len, at76_mac80211_tx_callback, priv); + ret = usb_submit_urb(priv->tx_urb, GFP_ATOMIC); + if (ret) { + wiphy_err(priv->hw->wiphy, "error in tx submit urb: %d\n", ret); + if (ret == -EINVAL) + wiphy_err(priv->hw->wiphy, + "-EINVAL: tx urb %p hcpriv %p complete %p\n", + priv->tx_urb, + priv->tx_urb->hcpriv, priv->tx_urb->complete); + } +} + +static int at76_mac80211_start(struct ieee80211_hw *hw) +{ + struct at76_priv *priv = hw->priv; + int ret; + + at76_dbg(DBG_MAC80211, "%s()", __func__); + + mutex_lock(&priv->mtx); + + ret = at76_submit_rx_urb(priv); + if (ret < 0) { + wiphy_err(priv->hw->wiphy, "open: submit_rx_urb failed: %d\n", + ret); + goto error; + } + + at76_startup_device(priv); + + at76_start_monitor(priv); + +error: + mutex_unlock(&priv->mtx); + + return 0; +} + +static void at76_mac80211_stop(struct ieee80211_hw *hw) +{ + struct at76_priv *priv = hw->priv; + + at76_dbg(DBG_MAC80211, "%s()", __func__); + + cancel_delayed_work(&priv->dwork_hw_scan); + cancel_work_sync(&priv->work_join_bssid); + cancel_work_sync(&priv->work_set_promisc); + + mutex_lock(&priv->mtx); + + if (!priv->device_unplugged) { + /* We are called by "ifconfig ethX down", not because the + * device is not available anymore. */ + at76_set_radio(priv, 0); + + /* We unlink rx_urb because at76_open() re-submits it. + * If unplugged, at76_delete_device() takes care of it. */ + usb_kill_urb(priv->rx_urb); + } + + mutex_unlock(&priv->mtx); +} + +static int at76_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct at76_priv *priv = hw->priv; + int ret = 0; + + at76_dbg(DBG_MAC80211, "%s()", __func__); + + mutex_lock(&priv->mtx); + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + priv->iw_mode = IW_MODE_INFRA; + break; + default: + ret = -EOPNOTSUPP; + goto exit; + } + +exit: + mutex_unlock(&priv->mtx); + + return ret; +} + +static void at76_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + at76_dbg(DBG_MAC80211, "%s()", __func__); +} + +static void at76_dwork_hw_scan(struct work_struct *work) +{ + struct at76_priv *priv = container_of(work, struct at76_priv, + dwork_hw_scan.work); + int ret; + + if (priv->device_unplugged) + return; + + mutex_lock(&priv->mtx); + + ret = at76_get_cmd_status(priv->udev, CMD_SCAN); + at76_dbg(DBG_MAC80211, "%s: CMD_SCAN status 0x%02x", __func__, ret); + + /* FIXME: add maximum time for scan to complete */ + + if (ret != CMD_STATUS_COMPLETE) { + ieee80211_queue_delayed_work(priv->hw, &priv->dwork_hw_scan, + SCAN_POLL_INTERVAL); + mutex_unlock(&priv->mtx); + return; + } + + if (is_valid_ether_addr(priv->bssid)) + at76_join(priv); + + mutex_unlock(&priv->mtx); + + ieee80211_scan_completed(priv->hw, false); + + ieee80211_wake_queues(priv->hw); +} + +static int at76_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_scan_request *req) +{ + struct at76_priv *priv = hw->priv; + struct at76_req_scan scan; + u8 *ssid = NULL; + int ret, len = 0; + + at76_dbg(DBG_MAC80211, "%s():", __func__); + + if (priv->device_unplugged) + return 0; + + mutex_lock(&priv->mtx); + + ieee80211_stop_queues(hw); + + memset(&scan, 0, sizeof(struct at76_req_scan)); + memset(scan.bssid, 0xFF, ETH_ALEN); + + if (req->n_ssids) { + scan.scan_type = SCAN_TYPE_ACTIVE; + ssid = req->ssids[0].ssid; + len = req->ssids[0].ssid_len; + } else { + scan.scan_type = SCAN_TYPE_PASSIVE; + } + + if (len) { + memcpy(scan.essid, ssid, len); + scan.essid_size = len; + } + + scan.min_channel_time = cpu_to_le16(priv->scan_min_time); + scan.max_channel_time = cpu_to_le16(priv->scan_max_time); + scan.probe_delay = cpu_to_le16(priv->scan_min_time * 1000); + scan.international_scan = 0; + + at76_dbg(DBG_MAC80211, "%s: sending CMD_SCAN", __func__); + ret = at76_set_card_command(priv->udev, CMD_SCAN, &scan, sizeof(scan)); + + if (ret < 0) { + wiphy_err(priv->hw->wiphy, "CMD_SCAN failed: %d\n", ret); + goto exit; + } + + ieee80211_queue_delayed_work(priv->hw, &priv->dwork_hw_scan, + SCAN_POLL_INTERVAL); + +exit: + mutex_unlock(&priv->mtx); + + return 0; +} + +static int at76_config(struct ieee80211_hw *hw, u32 changed) +{ + struct at76_priv *priv = hw->priv; + + at76_dbg(DBG_MAC80211, "%s(): channel %d", + __func__, hw->conf.chandef.chan->hw_value); + at76_dbg_dump(DBG_MAC80211, priv->bssid, ETH_ALEN, "bssid:"); + + mutex_lock(&priv->mtx); + + priv->channel = hw->conf.chandef.chan->hw_value; + + if (is_valid_ether_addr(priv->bssid)) + at76_join(priv); + else + at76_start_monitor(priv); + + mutex_unlock(&priv->mtx); + + return 0; +} + +static void at76_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *conf, + u32 changed) +{ + struct at76_priv *priv = hw->priv; + + at76_dbg(DBG_MAC80211, "%s():", __func__); + + if (!(changed & BSS_CHANGED_BSSID)) + return; + + at76_dbg_dump(DBG_MAC80211, conf->bssid, ETH_ALEN, "bssid:"); + + mutex_lock(&priv->mtx); + + memcpy(priv->bssid, conf->bssid, ETH_ALEN); + + if (is_valid_ether_addr(priv->bssid)) + /* mac80211 is joining a bss */ + at76_join(priv); + + mutex_unlock(&priv->mtx); +} + +/* must be atomic */ +static void at76_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, u64 multicast) +{ + struct at76_priv *priv = hw->priv; + int flags; + + at76_dbg(DBG_MAC80211, "%s(): changed_flags=0x%08x " + "total_flags=0x%08x", + __func__, changed_flags, *total_flags); + + flags = changed_flags & AT76_SUPPORTED_FILTERS; + *total_flags = AT76_SUPPORTED_FILTERS; + + /* Bail out after updating flags to prevent a WARN_ON in mac80211. */ + if (priv->device_unplugged) + return; + + /* FIXME: access to priv->promisc should be protected with + * priv->mtx, but it's impossible because this function needs to be + * atomic */ + + if (flags && !priv->promisc) { + /* mac80211 wants us to enable promiscuous mode */ + priv->promisc = 1; + } else if (!flags && priv->promisc) { + /* we need to disable promiscuous mode */ + priv->promisc = 0; + } else + return; + + ieee80211_queue_work(hw, &priv->work_set_promisc); +} + +static int at76_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct at76_priv *priv = hw->priv; + + int i; + + at76_dbg(DBG_MAC80211, "%s(): cmd %d key->cipher %d key->keyidx %d " + "key->keylen %d", + __func__, cmd, key->cipher, key->keyidx, key->keylen); + + if ((key->cipher != WLAN_CIPHER_SUITE_WEP40) && + (key->cipher != WLAN_CIPHER_SUITE_WEP104)) + return -EOPNOTSUPP; + + key->hw_key_idx = key->keyidx; + + mutex_lock(&priv->mtx); + + switch (cmd) { + case SET_KEY: + memcpy(priv->wep_keys[key->keyidx], key->key, key->keylen); + priv->wep_keys_len[key->keyidx] = key->keylen; + + /* FIXME: find out how to do this properly */ + priv->wep_key_id = key->keyidx; + + break; + case DISABLE_KEY: + default: + priv->wep_keys_len[key->keyidx] = 0; + break; + } + + priv->wep_enabled = 0; + + for (i = 0; i < WEP_KEYS; i++) { + if (priv->wep_keys_len[i] != 0) + priv->wep_enabled = 1; + } + + at76_startup_device(priv); + + mutex_unlock(&priv->mtx); + + return 0; +} + +static const struct ieee80211_ops at76_ops = { + .tx = at76_mac80211_tx, + .add_interface = at76_add_interface, + .remove_interface = at76_remove_interface, + .config = at76_config, + .bss_info_changed = at76_bss_info_changed, + .configure_filter = at76_configure_filter, + .start = at76_mac80211_start, + .stop = at76_mac80211_stop, + .hw_scan = at76_hw_scan, + .set_key = at76_set_key, +}; + +/* Allocate network device and initialize private data */ +static struct at76_priv *at76_alloc_new_device(struct usb_device *udev) +{ + struct ieee80211_hw *hw; + struct at76_priv *priv; + + hw = ieee80211_alloc_hw(sizeof(struct at76_priv), &at76_ops); + if (!hw) { + printk(KERN_ERR DRIVER_NAME ": could not register" + " ieee80211_hw\n"); + return NULL; + } + + priv = hw->priv; + priv->hw = hw; + + priv->udev = udev; + + mutex_init(&priv->mtx); + INIT_WORK(&priv->work_set_promisc, at76_work_set_promisc); + INIT_WORK(&priv->work_submit_rx, at76_work_submit_rx); + INIT_WORK(&priv->work_join_bssid, at76_work_join_bssid); + INIT_DELAYED_WORK(&priv->dwork_hw_scan, at76_dwork_hw_scan); + + tasklet_init(&priv->rx_tasklet, at76_rx_tasklet, 0); + + priv->pm_mode = AT76_PM_OFF; + priv->pm_period = 0; + + /* unit us */ + priv->hw->channel_change_time = 100000; + + return priv; +} + +static int at76_alloc_urbs(struct at76_priv *priv, + struct usb_interface *interface) +{ + struct usb_endpoint_descriptor *endpoint, *ep_in, *ep_out; + int i; + int buffer_size; + struct usb_host_interface *iface_desc; + + at76_dbg(DBG_PROC_ENTRY, "%s: ENTER", __func__); + + at76_dbg(DBG_URB, "%s: NumEndpoints %d ", __func__, + interface->altsetting[0].desc.bNumEndpoints); + + ep_in = NULL; + ep_out = NULL; + iface_desc = interface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { + endpoint = &iface_desc->endpoint[i].desc; + + at76_dbg(DBG_URB, "%s: %d. endpoint: addr 0x%x attr 0x%x", + __func__, i, endpoint->bEndpointAddress, + endpoint->bmAttributes); + + if (!ep_in && usb_endpoint_is_bulk_in(endpoint)) + ep_in = endpoint; + + if (!ep_out && usb_endpoint_is_bulk_out(endpoint)) + ep_out = endpoint; + } + + if (!ep_in || !ep_out) { + dev_err(&interface->dev, "bulk endpoints missing\n"); + return -ENXIO; + } + + priv->rx_pipe = usb_rcvbulkpipe(priv->udev, ep_in->bEndpointAddress); + priv->tx_pipe = usb_sndbulkpipe(priv->udev, ep_out->bEndpointAddress); + + priv->rx_urb = usb_alloc_urb(0, GFP_KERNEL); + priv->tx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!priv->rx_urb || !priv->tx_urb) { + dev_err(&interface->dev, "cannot allocate URB\n"); + return -ENOMEM; + } + + buffer_size = sizeof(struct at76_tx_buffer) + MAX_PADDING_SIZE; + priv->bulk_out_buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!priv->bulk_out_buffer) + return -ENOMEM; + + at76_dbg(DBG_PROC_ENTRY, "%s: EXIT", __func__); + + return 0; +} + +static struct ieee80211_rate at76_rates[] = { + { .bitrate = 10, .hw_value = TX_RATE_1MBIT, }, + { .bitrate = 20, .hw_value = TX_RATE_2MBIT, }, + { .bitrate = 55, .hw_value = TX_RATE_5_5MBIT, }, + { .bitrate = 110, .hw_value = TX_RATE_11MBIT, }, +}; + +static struct ieee80211_channel at76_channels[] = { + { .center_freq = 2412, .hw_value = 1 }, + { .center_freq = 2417, .hw_value = 2 }, + { .center_freq = 2422, .hw_value = 3 }, + { .center_freq = 2427, .hw_value = 4 }, + { .center_freq = 2432, .hw_value = 5 }, + { .center_freq = 2437, .hw_value = 6 }, + { .center_freq = 2442, .hw_value = 7 }, + { .center_freq = 2447, .hw_value = 8 }, + { .center_freq = 2452, .hw_value = 9 }, + { .center_freq = 2457, .hw_value = 10 }, + { .center_freq = 2462, .hw_value = 11 }, + { .center_freq = 2467, .hw_value = 12 }, + { .center_freq = 2472, .hw_value = 13 }, + { .center_freq = 2484, .hw_value = 14 } +}; + +static struct ieee80211_supported_band at76_supported_band = { + .channels = at76_channels, + .n_channels = ARRAY_SIZE(at76_channels), + .bitrates = at76_rates, + .n_bitrates = ARRAY_SIZE(at76_rates), +}; + +/* Register network device and initialize the hardware */ +static int at76_init_new_device(struct at76_priv *priv, + struct usb_interface *interface) +{ + struct wiphy *wiphy; + size_t len; + int ret; + + /* set up the endpoint information */ + /* check out the endpoints */ + + at76_dbg(DBG_DEVSTART, "USB interface: %d endpoints", + interface->cur_altsetting->desc.bNumEndpoints); + + ret = at76_alloc_urbs(priv, interface); + if (ret < 0) + goto exit; + + /* MAC address */ + ret = at76_get_hw_config(priv); + if (ret < 0) { + dev_err(&interface->dev, "cannot get MAC address\n"); + goto exit; + } + + priv->domain = at76_get_reg_domain(priv->regulatory_domain); + + priv->channel = DEF_CHANNEL; + priv->iw_mode = IW_MODE_INFRA; + priv->rts_threshold = DEF_RTS_THRESHOLD; + priv->frag_threshold = DEF_FRAG_THRESHOLD; + priv->short_retry_limit = DEF_SHORT_RETRY_LIMIT; + priv->txrate = TX_RATE_AUTO; + priv->preamble_type = PREAMBLE_TYPE_LONG; + priv->beacon_period = 100; + priv->auth_mode = WLAN_AUTH_OPEN; + priv->scan_min_time = DEF_SCAN_MIN_TIME; + priv->scan_max_time = DEF_SCAN_MAX_TIME; + priv->scan_mode = SCAN_TYPE_ACTIVE; + priv->device_unplugged = 0; + + /* mac80211 initialisation */ + wiphy = priv->hw->wiphy; + priv->hw->wiphy->max_scan_ssids = 1; + priv->hw->wiphy->max_scan_ie_len = 0; + priv->hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION); + priv->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &at76_supported_band; + priv->hw->flags = IEEE80211_HW_RX_INCLUDES_FCS | + IEEE80211_HW_SIGNAL_UNSPEC; + priv->hw->max_signal = 100; + + SET_IEEE80211_DEV(priv->hw, &interface->dev); + SET_IEEE80211_PERM_ADDR(priv->hw, priv->mac_addr); + + len = sizeof(wiphy->fw_version); + snprintf(wiphy->fw_version, len, "%d.%d.%d-%d", + priv->fw_version.major, priv->fw_version.minor, + priv->fw_version.patch, priv->fw_version.build); + + wiphy->hw_version = priv->board_type; + + ret = ieee80211_register_hw(priv->hw); + if (ret) { + printk(KERN_ERR "cannot register mac80211 hw (status %d)!\n", + ret); + goto exit; + } + + priv->mac80211_registered = 1; + + wiphy_info(priv->hw->wiphy, "USB %s, MAC %pM, firmware %d.%d.%d-%d\n", + dev_name(&interface->dev), priv->mac_addr, + priv->fw_version.major, priv->fw_version.minor, + priv->fw_version.patch, priv->fw_version.build); + wiphy_info(priv->hw->wiphy, "regulatory domain 0x%02x: %s\n", + priv->regulatory_domain, priv->domain->name); + +exit: + return ret; +} + +static void at76_delete_device(struct at76_priv *priv) +{ + at76_dbg(DBG_PROC_ENTRY, "%s: ENTER", __func__); + + /* The device is gone, don't bother turning it off */ + priv->device_unplugged = 1; + + tasklet_kill(&priv->rx_tasklet); + + if (priv->mac80211_registered) + ieee80211_unregister_hw(priv->hw); + + if (priv->tx_urb) { + usb_kill_urb(priv->tx_urb); + usb_free_urb(priv->tx_urb); + } + if (priv->rx_urb) { + usb_kill_urb(priv->rx_urb); + usb_free_urb(priv->rx_urb); + } + + at76_dbg(DBG_PROC_ENTRY, "%s: unlinked urbs", __func__); + + kfree(priv->bulk_out_buffer); + + del_timer_sync(&ledtrig_tx_timer); + + kfree_skb(priv->rx_skb); + + usb_put_dev(priv->udev); + + at76_dbg(DBG_PROC_ENTRY, "%s: before freeing priv/ieee80211_hw", + __func__); + ieee80211_free_hw(priv->hw); + + at76_dbg(DBG_PROC_ENTRY, "%s: EXIT", __func__); +} + +static int at76_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + int ret; + struct at76_priv *priv; + struct fwentry *fwe; + struct usb_device *udev; + int op_mode; + int need_ext_fw = 0; + struct mib_fw_version fwv; + int board_type = (int)id->driver_info; + + udev = usb_get_dev(interface_to_usbdev(interface)); + + /* Load firmware into kernel memory */ + fwe = at76_load_firmware(udev, board_type); + if (!fwe) { + ret = -ENOENT; + goto error; + } + + op_mode = at76_get_op_mode(udev); + + at76_dbg(DBG_DEVSTART, "opmode %d", op_mode); + + /* we get OPMODE_NONE with 2.4.23, SMC2662W-AR ??? + we get 204 with 2.4.23, Fiberline FL-WL240u (505A+RFMD2958) ??? */ + + if (op_mode == OPMODE_HW_CONFIG_MODE) { + dev_err(&interface->dev, + "cannot handle a device in HW_CONFIG_MODE\n"); + ret = -EBUSY; + goto error; + } + + if (op_mode != OPMODE_NORMAL_NIC_WITH_FLASH + && op_mode != OPMODE_NORMAL_NIC_WITHOUT_FLASH) { + /* download internal firmware part */ + dev_printk(KERN_DEBUG, &interface->dev, + "downloading internal firmware\n"); + ret = at76_load_internal_fw(udev, fwe); + if (ret < 0) { + dev_err(&interface->dev, + "error %d downloading internal firmware\n", + ret); + goto error; + } + usb_put_dev(udev); + return ret; + } + + /* Internal firmware already inside the device. Get firmware + * version to test if external firmware is loaded. + * This works only for newer firmware, e.g. the Intersil 0.90.x + * says "control timeout on ep0in" and subsequent + * at76_get_op_mode() fail too :-( */ + + /* if version >= 0.100.x.y or device with built-in flash we can + * query the device for the fw version */ + if ((fwe->fw_version.major > 0 || fwe->fw_version.minor >= 100) + || (op_mode == OPMODE_NORMAL_NIC_WITH_FLASH)) { + ret = at76_get_mib(udev, MIB_FW_VERSION, &fwv, sizeof(fwv)); + if (ret < 0 || (fwv.major | fwv.minor) == 0) + need_ext_fw = 1; + } else + /* No way to check firmware version, reload to be sure */ + need_ext_fw = 1; + + if (need_ext_fw) { + dev_printk(KERN_DEBUG, &interface->dev, + "downloading external firmware\n"); + + ret = at76_load_external_fw(udev, fwe); + if (ret) + goto error; + + /* Re-check firmware version */ + ret = at76_get_mib(udev, MIB_FW_VERSION, &fwv, sizeof(fwv)); + if (ret < 0) { + dev_err(&interface->dev, + "error %d getting firmware version\n", ret); + goto error; + } + } + + priv = at76_alloc_new_device(udev); + if (!priv) { + ret = -ENOMEM; + goto error; + } + + usb_set_intfdata(interface, priv); + + memcpy(&priv->fw_version, &fwv, sizeof(struct mib_fw_version)); + priv->board_type = board_type; + + ret = at76_init_new_device(priv, interface); + if (ret < 0) + at76_delete_device(priv); + + return ret; + +error: + usb_put_dev(udev); + return ret; +} + +static void at76_disconnect(struct usb_interface *interface) +{ + struct at76_priv *priv; + + priv = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + /* Disconnect after loading internal firmware */ + if (!priv) + return; + + wiphy_info(priv->hw->wiphy, "disconnecting\n"); + at76_delete_device(priv); + dev_info(&interface->dev, "disconnected\n"); +} + +/* Structure for registering this driver with the USB subsystem */ +static struct usb_driver at76_driver = { + .name = DRIVER_NAME, + .probe = at76_probe, + .disconnect = at76_disconnect, + .id_table = dev_table, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +static int __init at76_mod_init(void) +{ + int result; + + printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION " loading\n"); + + mutex_init(&fw_mutex); + + /* register this driver with the USB subsystem */ + result = usb_register(&at76_driver); + if (result < 0) + printk(KERN_ERR DRIVER_NAME + ": usb_register failed (status %d)\n", result); + + led_trigger_register_simple("at76_usb-tx", &ledtrig_tx); + return result; +} + +static void __exit at76_mod_exit(void) +{ + int i; + + printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION " unloading\n"); + usb_deregister(&at76_driver); + for (i = 0; i < ARRAY_SIZE(firmwares); i++) + release_firmware(firmwares[i].fw); + led_trigger_unregister_simple(ledtrig_tx); +} + +module_param_named(debug, at76_debug, uint, 0600); +MODULE_PARM_DESC(debug, "Debugging level"); + +module_init(at76_mod_init); +module_exit(at76_mod_exit); + +MODULE_AUTHOR("Oliver Kurth "); +MODULE_AUTHOR("Joerg Albert "); +MODULE_AUTHOR("Alex "); +MODULE_AUTHOR("Nick Jones"); +MODULE_AUTHOR("Balint Seeber "); +MODULE_AUTHOR("Pavel Roskin "); +MODULE_AUTHOR("Guido Guenther "); +MODULE_AUTHOR("Kalle Valo "); +MODULE_AUTHOR("Sebastian Smolorz "); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/at76c50x-usb.h b/drivers/net/wireless/at76c50x-usb.h new file mode 100644 index 0000000..f14a654 --- /dev/null +++ b/drivers/net/wireless/at76c50x-usb.h @@ -0,0 +1,464 @@ +/* + * Copyright (c) 2002,2003 Oliver Kurth + * (c) 2003,2004 Joerg Albert + * (c) 2007 Guido Guenther + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This driver was based on information from the Sourceforge driver + * released and maintained by Atmel: + * + * http://sourceforge.net/projects/atmelwlandriver/ + * + * Although the code was completely re-written, + * it would have been impossible without Atmel's decision to + * release an Open Source driver (unfortunately the firmware was + * kept binary only). Thanks for that decision to Atmel! + */ + +#ifndef _AT76_USB_H +#define _AT76_USB_H + +/* Board types */ +enum board_type { + BOARD_503_ISL3861 = 1, + BOARD_503_ISL3863 = 2, + BOARD_503 = 3, + BOARD_503_ACC = 4, + BOARD_505 = 5, + BOARD_505_2958 = 6, + BOARD_505A = 7, + BOARD_505AMX = 8 +}; + +#define CMD_STATUS_IDLE 0x00 +#define CMD_STATUS_COMPLETE 0x01 +#define CMD_STATUS_UNKNOWN 0x02 +#define CMD_STATUS_INVALID_PARAMETER 0x03 +#define CMD_STATUS_FUNCTION_NOT_SUPPORTED 0x04 +#define CMD_STATUS_TIME_OUT 0x07 +#define CMD_STATUS_IN_PROGRESS 0x08 +#define CMD_STATUS_HOST_FAILURE 0xff +#define CMD_STATUS_SCAN_FAILED 0xf0 + +/* answers to get op mode */ +#define OPMODE_NONE 0x00 +#define OPMODE_NORMAL_NIC_WITH_FLASH 0x01 +#define OPMODE_HW_CONFIG_MODE 0x02 +#define OPMODE_DFU_MODE_WITH_FLASH 0x03 +#define OPMODE_NORMAL_NIC_WITHOUT_FLASH 0x04 + +#define CMD_SET_MIB 0x01 +#define CMD_GET_MIB 0x02 +#define CMD_SCAN 0x03 +#define CMD_JOIN 0x04 +#define CMD_START_IBSS 0x05 +#define CMD_RADIO_ON 0x06 +#define CMD_RADIO_OFF 0x07 +#define CMD_STARTUP 0x0B + +#define MIB_LOCAL 0x01 +#define MIB_MAC_ADDR 0x02 +#define MIB_MAC 0x03 +#define MIB_MAC_MGMT 0x05 +#define MIB_MAC_WEP 0x06 +#define MIB_PHY 0x07 +#define MIB_FW_VERSION 0x08 +#define MIB_MDOMAIN 0x09 + +#define ADHOC_MODE 1 +#define INFRASTRUCTURE_MODE 2 + +/* values for struct mib_local, field preamble_type */ +#define PREAMBLE_TYPE_LONG 0 +#define PREAMBLE_TYPE_SHORT 1 +#define PREAMBLE_TYPE_AUTO 2 + +/* values for tx_rate */ +#define TX_RATE_1MBIT 0 +#define TX_RATE_2MBIT 1 +#define TX_RATE_5_5MBIT 2 +#define TX_RATE_11MBIT 3 +#define TX_RATE_AUTO 4 + +/* power management modes */ +#define AT76_PM_OFF 1 +#define AT76_PM_ON 2 +#define AT76_PM_SMART 3 + +struct hwcfg_r505 { + u8 cr39_values[14]; + u8 reserved1[14]; + u8 bb_cr[14]; + u8 pidvid[4]; + u8 mac_addr[ETH_ALEN]; + u8 regulatory_domain; + u8 reserved2[14]; + u8 cr15_values[14]; + u8 reserved3[3]; +} __packed; + +struct hwcfg_rfmd { + u8 cr20_values[14]; + u8 cr21_values[14]; + u8 bb_cr[14]; + u8 pidvid[4]; + u8 mac_addr[ETH_ALEN]; + u8 regulatory_domain; + u8 low_power_values[14]; + u8 normal_power_values[14]; + u8 reserved1[3]; +} __packed; + +struct hwcfg_intersil { + u8 mac_addr[ETH_ALEN]; + u8 cr31_values[14]; + u8 cr58_values[14]; + u8 pidvid[4]; + u8 regulatory_domain; + u8 reserved[1]; +} __packed; + +union at76_hwcfg { + struct hwcfg_intersil i; + struct hwcfg_rfmd r3; + struct hwcfg_r505 r5; +}; + +#define WEP_SMALL_KEY_LEN (40 / 8) +#define WEP_LARGE_KEY_LEN (104 / 8) +#define WEP_KEYS (4) + +struct at76_card_config { + u8 exclude_unencrypted; + u8 promiscuous_mode; + u8 short_retry_limit; + u8 encryption_type; + __le16 rts_threshold; + __le16 fragmentation_threshold; /* 256..2346 */ + u8 basic_rate_set[4]; + u8 auto_rate_fallback; /* 0,1 */ + u8 channel; + u8 privacy_invoked; + u8 wep_default_key_id; /* 0..3 */ + u8 current_ssid[32]; + u8 wep_default_key_value[4][WEP_LARGE_KEY_LEN]; + u8 ssid_len; + u8 short_preamble; + __le16 beacon_period; +} __packed; + +struct at76_command { + u8 cmd; + u8 reserved; + __le16 size; + u8 data[0]; +} __packed; + +/* Length of Atmel-specific Rx header before 802.11 frame */ +#define AT76_RX_HDRLEN offsetof(struct at76_rx_buffer, packet) + +struct at76_rx_buffer { + __le16 wlength; + u8 rx_rate; + u8 newbss; + u8 fragmentation; + u8 rssi; + u8 link_quality; + u8 noise_level; + __le32 rx_time; + u8 packet[IEEE80211_MAX_FRAG_THRESHOLD]; +} __packed; + +/* Length of Atmel-specific Tx header before 802.11 frame */ +#define AT76_TX_HDRLEN offsetof(struct at76_tx_buffer, packet) + +struct at76_tx_buffer { + __le16 wlength; + u8 tx_rate; + u8 padding; + u8 reserved[4]; + u8 packet[IEEE80211_MAX_FRAG_THRESHOLD]; +} __packed; + +/* defines for scan_type below */ +#define SCAN_TYPE_ACTIVE 0 +#define SCAN_TYPE_PASSIVE 1 + +struct at76_req_scan { + u8 bssid[ETH_ALEN]; + u8 essid[32]; + u8 scan_type; + u8 channel; + __le16 probe_delay; + __le16 min_channel_time; + __le16 max_channel_time; + u8 essid_size; + u8 international_scan; +} __packed; + +struct at76_req_ibss { + u8 bssid[ETH_ALEN]; + u8 essid[32]; + u8 bss_type; + u8 channel; + u8 essid_size; + u8 reserved[3]; +} __packed; + +struct at76_req_join { + u8 bssid[ETH_ALEN]; + u8 essid[32]; + u8 bss_type; + u8 channel; + __le16 timeout; + u8 essid_size; + u8 reserved; +} __packed; + +struct set_mib_buffer { + u8 type; + u8 size; + u8 index; + u8 reserved; + union { + u8 byte; + __le16 word; + u8 addr[ETH_ALEN]; + } data; +} __packed; + +struct mib_local { + u16 reserved0; + u8 beacon_enable; + u8 txautorate_fallback; + u8 reserved1; + u8 ssid_size; + u8 promiscuous_mode; + u16 reserved2; + u8 preamble_type; + u16 reserved3; +} __packed; + +struct mib_mac_addr { + u8 mac_addr[ETH_ALEN]; + u8 res[2]; /* ??? */ + u8 group_addr[4][ETH_ALEN]; + u8 group_addr_status[4]; +} __packed; + +struct mib_mac { + __le32 max_tx_msdu_lifetime; + __le32 max_rx_lifetime; + __le16 frag_threshold; + __le16 rts_threshold; + __le16 cwmin; + __le16 cwmax; + u8 short_retry_time; + u8 long_retry_time; + u8 scan_type; /* active or passive */ + u8 scan_channel; + __le16 probe_delay; /* delay before ProbeReq in active scan, RO */ + __le16 min_channel_time; + __le16 max_channel_time; + __le16 listen_interval; + u8 desired_ssid[32]; + u8 desired_bssid[ETH_ALEN]; + u8 desired_bsstype; /* ad-hoc or infrastructure */ + u8 reserved2; +} __packed; + +struct mib_mac_mgmt { + __le16 beacon_period; + __le16 CFP_max_duration; + __le16 medium_occupancy_limit; + __le16 station_id; /* assoc id */ + __le16 ATIM_window; + u8 CFP_mode; + u8 privacy_option_implemented; + u8 DTIM_period; + u8 CFP_period; + u8 current_bssid[ETH_ALEN]; + u8 current_essid[32]; + u8 current_bss_type; + u8 power_mgmt_mode; + /* rfmd and 505 */ + u8 ibss_change; + u8 res; + u8 multi_domain_capability_implemented; + u8 multi_domain_capability_enabled; + u8 country_string[IEEE80211_COUNTRY_STRING_LEN]; + u8 reserved[3]; +} __packed; + +struct mib_mac_wep { + u8 privacy_invoked; /* 0 disable encr., 1 enable encr */ + u8 wep_default_key_id; + u8 wep_key_mapping_len; + u8 exclude_unencrypted; + __le32 wep_icv_error_count; + __le32 wep_excluded_count; + u8 wep_default_keyvalue[WEP_KEYS][WEP_LARGE_KEY_LEN]; + u8 encryption_level; /* 1 for 40bit, 2 for 104bit encryption */ +} __packed; + +struct mib_phy { + __le32 ed_threshold; + + __le16 slot_time; + __le16 sifs_time; + __le16 preamble_length; + __le16 plcp_header_length; + __le16 mpdu_max_length; + __le16 cca_mode_supported; + + u8 operation_rate_set[4]; + u8 channel_id; + u8 current_cca_mode; + u8 phy_type; + u8 current_reg_domain; +} __packed; + +struct mib_fw_version { + u8 major; + u8 minor; + u8 patch; + u8 build; +} __packed; + +struct mib_mdomain { + u8 tx_powerlevel[14]; + u8 channel_list[14]; /* 0 for invalid channels */ +} __packed; + +struct at76_fw_header { + __le32 crc; /* CRC32 of the whole image */ + __le32 board_type; /* firmware compatibility code */ + u8 build; /* firmware build number */ + u8 patch; /* firmware patch level */ + u8 minor; /* firmware minor version */ + u8 major; /* firmware major version */ + __le32 str_offset; /* offset of the copyright string */ + __le32 int_fw_offset; /* internal firmware image offset */ + __le32 int_fw_len; /* internal firmware image length */ + __le32 ext_fw_offset; /* external firmware image offset */ + __le32 ext_fw_len; /* external firmware image length */ +} __packed; + +/* a description of a regulatory domain and the allowed channels */ +struct reg_domain { + u16 code; + char const *name; + u32 channel_map; /* if bit N is set, channel (N+1) is allowed */ +}; + +/* Data for one loaded firmware file */ +struct fwentry { + const char *const fwname; + const struct firmware *fw; + int extfw_size; + int intfw_size; + /* pointer to loaded firmware, no need to free */ + u8 *extfw; /* external firmware, extfw_size bytes long */ + u8 *intfw; /* internal firmware, intfw_size bytes long */ + enum board_type board_type; /* board type */ + struct mib_fw_version fw_version; + int loaded; /* Loaded and parsed successfully */ +}; + +struct at76_priv { + struct usb_device *udev; /* USB device pointer */ + + struct sk_buff *rx_skb; /* skbuff for receiving data */ + struct sk_buff *tx_skb; /* skbuff for transmitting data */ + void *bulk_out_buffer; /* buffer for sending data */ + + struct urb *tx_urb; /* URB for sending data */ + struct urb *rx_urb; /* URB for receiving data */ + + unsigned int tx_pipe; /* bulk out pipe */ + unsigned int rx_pipe; /* bulk in pipe */ + + struct mutex mtx; /* locks this structure */ + + /* work queues */ + struct work_struct work_set_promisc; + struct work_struct work_submit_rx; + struct work_struct work_join_bssid; + struct delayed_work dwork_hw_scan; + + struct tasklet_struct rx_tasklet; + + /* the WEP stuff */ + int wep_enabled; /* 1 if WEP is enabled */ + int wep_key_id; /* key id to be used */ + u8 wep_keys[WEP_KEYS][WEP_LARGE_KEY_LEN]; /* WEP keys */ + u8 wep_keys_len[WEP_KEYS]; /* length of WEP keys */ + + int channel; + int iw_mode; + u8 bssid[ETH_ALEN]; + u8 essid[IW_ESSID_MAX_SIZE]; + int essid_size; + int radio_on; + int promisc; + + int preamble_type; /* 0 - long, 1 - short, 2 - auto */ + int auth_mode; /* authentication type: 0 open, 1 shared key */ + int txrate; /* 0,1,2,3 = 1,2,5.5,11 Mbps, 4 is auto */ + int frag_threshold; /* threshold for fragmentation of tx packets */ + int rts_threshold; /* threshold for RTS mechanism */ + int short_retry_limit; + + int scan_min_time; /* scan min channel time */ + int scan_max_time; /* scan max channel time */ + int scan_mode; /* SCAN_TYPE_ACTIVE, SCAN_TYPE_PASSIVE */ + int scan_need_any; /* if set, need to scan for any ESSID */ + + u16 assoc_id; /* current association ID, if associated */ + + u8 pm_mode; /* power management mode */ + u32 pm_period; /* power management period in microseconds */ + + struct reg_domain const *domain; /* reg domain description */ + + /* These fields contain HW config provided by the device (not all of + * these fields are used by all board types) */ + u8 mac_addr[ETH_ALEN]; + u8 regulatory_domain; + + struct at76_card_config card_config; + + enum board_type board_type; + struct mib_fw_version fw_version; + + unsigned int device_unplugged:1; + unsigned int netdev_registered:1; + struct set_mib_buffer mib_buf; /* global buffer for set_mib calls */ + + int beacon_period; /* period of mgmt beacons, Kus */ + + struct ieee80211_hw *hw; + int mac80211_registered; +}; + +#define AT76_SUPPORTED_FILTERS FIF_PROMISC_IN_BSS + +#define SCAN_POLL_INTERVAL (HZ / 4) + +#define CMD_COMPLETION_TIMEOUT (5 * HZ) + +#define DEF_RTS_THRESHOLD 1536 +#define DEF_FRAG_THRESHOLD 1536 +#define DEF_SHORT_RETRY_LIMIT 8 +#define DEF_CHANNEL 10 +#define DEF_SCAN_MIN_TIME 10 +#define DEF_SCAN_MAX_TIME 120 + +/* the max padding size for tx in bytes (see calc_padding) */ +#define MAX_PADDING_SIZE 53 + +#endif /* _AT76_USB_H */ diff --git a/drivers/net/wireless/ath/Kconfig b/drivers/net/wireless/ath/Kconfig new file mode 100644 index 0000000..d1b837b --- /dev/null +++ b/drivers/net/wireless/ath/Kconfig @@ -0,0 +1,37 @@ +config ATH_COMMON + tristate + depends on m + +menuconfig ATH_CARDS + tristate "Atheros Wireless Cards" + depends on m + depends on CFG80211 && (!UML || BROKEN) + ---help--- + This will enable the support for the Atheros wireless drivers. + ath5k, ath9k, ath9k_htc and ar9170 drivers share some common code, this option + enables the common ath.ko module which shares common helpers. + + For more information and documentation on this module you can visit: + + http://wireless.kernel.org/en/users/Drivers/ath + + For information on all Atheros wireless drivers visit: + + http://wireless.kernel.org/en/users/Drivers/Atheros + +if ATH_CARDS + +config ATH_DEBUG + bool "Atheros wireless debugging" + ---help--- + Say Y, if you want to debug atheros wireless drivers. + Right now only ath9k makes use of this. + +source "drivers/net/wireless/ath/ath5k/Kconfig" +source "drivers/net/wireless/ath/ath9k/Kconfig" +source "drivers/net/wireless/ath/carl9170/Kconfig" +source "drivers/net/wireless/ath/ath6kl/Kconfig" +source "drivers/net/wireless/ath/ar5523/Kconfig" +source "drivers/net/wireless/ath/wil6210/Kconfig" + +endif diff --git a/drivers/net/wireless/ath/Makefile b/drivers/net/wireless/ath/Makefile new file mode 100644 index 0000000..2efeb02 --- /dev/null +++ b/drivers/net/wireless/ath/Makefile @@ -0,0 +1,16 @@ +obj-$(CPTCFG_ATH5K) += ath5k/ +obj-$(CPTCFG_ATH9K_HW) += ath9k/ +obj-$(CPTCFG_CARL9170) += carl9170/ +obj-$(CPTCFG_ATH6KL) += ath6kl/ +obj-$(CPTCFG_AR5523) += ar5523/ +obj-$(CPTCFG_WIL6210) += wil6210/ + +obj-$(CPTCFG_ATH_COMMON) += ath.o + +ath-objs := main.o \ + regd.o \ + hw.o \ + key.o + +ath-$(CPTCFG_ATH_DEBUG) += debug.o +ccflags-y += -D__CHECK_ENDIAN__ diff --git a/drivers/net/wireless/ath/ar5523/Kconfig b/drivers/net/wireless/ath/ar5523/Kconfig new file mode 100644 index 0000000..ec0383e --- /dev/null +++ b/drivers/net/wireless/ath/ar5523/Kconfig @@ -0,0 +1,9 @@ +config AR5523 + tristate "Atheros AR5523 wireless driver support" + depends on m + depends on MAC80211 && USB + select ATH_COMMON + select BACKPORT_FW_LOADER + ---help--- + This module add support for AR5523 based USB dongles such as D-Link + DWL-G132, Netgear WPN111 and many more. diff --git a/drivers/net/wireless/ath/ar5523/Makefile b/drivers/net/wireless/ath/ar5523/Makefile new file mode 100644 index 0000000..a90e00a --- /dev/null +++ b/drivers/net/wireless/ath/ar5523/Makefile @@ -0,0 +1 @@ +obj-$(CPTCFG_AR5523) := ar5523.o diff --git a/drivers/net/wireless/ath/ar5523/ar5523.c b/drivers/net/wireless/ath/ar5523/ar5523.c new file mode 100644 index 0000000..17d7fec --- /dev/null +++ b/drivers/net/wireless/ath/ar5523/ar5523.c @@ -0,0 +1,1798 @@ +/* + * Copyright (c) 2006 Damien Bergamini + * Copyright (c) 2006 Sam Leffler, Errno Consulting + * Copyright (c) 2007 Christoph Hellwig + * Copyright (c) 2008-2009 Weongyo Jeong + * Copyright (c) 2012 Pontus Fuchs + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This driver is based on the uath driver written by Damien Bergamini for + * OpenBSD, who did black-box analysis of the Windows binary driver to find + * out how the hardware works. It contains a lot magic numbers because of + * that and only has minimal functionality. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ar5523.h" +#include "ar5523_hw.h" + +/* + * Various supported device vendors/products. + * UB51: AR5005UG 802.11b/g, UB52: AR5005UX 802.11a/b/g + */ + +static int ar5523_submit_rx_cmd(struct ar5523 *ar); +static void ar5523_data_tx_pkt_put(struct ar5523 *ar); + +static void ar5523_read_reply(struct ar5523 *ar, struct ar5523_cmd_hdr *hdr, + struct ar5523_tx_cmd *cmd) +{ + int dlen, olen; + __be32 *rp; + + dlen = be32_to_cpu(hdr->len) - sizeof(*hdr); + + if (dlen < 0) { + WARN_ON(1); + goto out; + } + + ar5523_dbg(ar, "Code = %d len = %d\n", be32_to_cpu(hdr->code) & 0xff, + dlen); + + rp = (__be32 *)(hdr + 1); + if (dlen >= sizeof(u32)) { + olen = be32_to_cpu(rp[0]); + dlen -= sizeof(u32); + if (olen == 0) { + /* convention is 0 =>'s one word */ + olen = sizeof(u32); + } + } else + olen = 0; + + if (cmd->odata) { + if (cmd->olen < olen) { + ar5523_err(ar, "olen to small %d < %d\n", + cmd->olen, olen); + cmd->olen = 0; + cmd->res = -EOVERFLOW; + } else { + cmd->olen = olen; + memcpy(cmd->odata, &rp[1], olen); + cmd->res = 0; + } + } + +out: + complete(&cmd->done); +} + +static void ar5523_cmd_rx_cb(struct urb *urb) +{ + struct ar5523 *ar = urb->context; + struct ar5523_tx_cmd *cmd = &ar->tx_cmd; + struct ar5523_cmd_hdr *hdr = ar->rx_cmd_buf; + int dlen; + u32 code, hdrlen; + + if (urb->status) { + if (urb->status != -ESHUTDOWN) + ar5523_err(ar, "RX USB error %d.\n", urb->status); + goto skip; + } + + if (urb->actual_length < sizeof(struct ar5523_cmd_hdr)) { + ar5523_err(ar, "RX USB to short.\n"); + goto skip; + } + + ar5523_dbg(ar, "%s code %02x priv %d\n", __func__, + be32_to_cpu(hdr->code) & 0xff, hdr->priv); + + code = be32_to_cpu(hdr->code); + hdrlen = be32_to_cpu(hdr->len); + + switch (code & 0xff) { + default: + /* reply to a read command */ + if (hdr->priv != AR5523_CMD_ID) { + ar5523_err(ar, "Unexpected command id: %02x\n", + code & 0xff); + goto skip; + } + ar5523_read_reply(ar, hdr, cmd); + break; + + case WDCMSG_DEVICE_AVAIL: + ar5523_dbg(ar, "WDCMSG_DEVICE_AVAIL\n"); + cmd->res = 0; + cmd->olen = 0; + complete(&cmd->done); + break; + + case WDCMSG_SEND_COMPLETE: + ar5523_dbg(ar, "WDCMSG_SEND_COMPLETE: %d pending\n", + atomic_read(&ar->tx_nr_pending)); + if (!test_bit(AR5523_HW_UP, &ar->flags)) + ar5523_dbg(ar, "Unexpected WDCMSG_SEND_COMPLETE\n"); + else { + mod_timer(&ar->tx_wd_timer, + jiffies + AR5523_TX_WD_TIMEOUT); + ar5523_data_tx_pkt_put(ar); + + } + break; + + case WDCMSG_TARGET_START: + /* This command returns a bogus id so it needs special + handling */ + dlen = hdrlen - sizeof(*hdr); + if (dlen != (int)sizeof(u32)) { + ar5523_err(ar, "Invalid reply to WDCMSG_TARGET_START"); + return; + } + memcpy(cmd->odata, hdr + 1, sizeof(u32)); + cmd->olen = sizeof(u32); + cmd->res = 0; + complete(&cmd->done); + break; + + case WDCMSG_STATS_UPDATE: + ar5523_dbg(ar, "WDCMSG_STATS_UPDATE\n"); + break; + } + +skip: + ar5523_submit_rx_cmd(ar); +} + +static int ar5523_alloc_rx_cmd(struct ar5523 *ar) +{ + ar->rx_cmd_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!ar->rx_cmd_urb) + return -ENOMEM; + + ar->rx_cmd_buf = usb_alloc_coherent(ar->dev, AR5523_MAX_RXCMDSZ, + GFP_KERNEL, + &ar->rx_cmd_urb->transfer_dma); + if (!ar->rx_cmd_buf) { + usb_free_urb(ar->rx_cmd_urb); + return -ENOMEM; + } + return 0; +} + +static void ar5523_cancel_rx_cmd(struct ar5523 *ar) +{ + usb_kill_urb(ar->rx_cmd_urb); +} + +static void ar5523_free_rx_cmd(struct ar5523 *ar) +{ + usb_free_coherent(ar->dev, AR5523_MAX_RXCMDSZ, + ar->rx_cmd_buf, ar->rx_cmd_urb->transfer_dma); + usb_free_urb(ar->rx_cmd_urb); +} + +static int ar5523_submit_rx_cmd(struct ar5523 *ar) +{ + int error; + + usb_fill_bulk_urb(ar->rx_cmd_urb, ar->dev, + ar5523_cmd_rx_pipe(ar->dev), ar->rx_cmd_buf, + AR5523_MAX_RXCMDSZ, ar5523_cmd_rx_cb, ar); + ar->rx_cmd_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + error = usb_submit_urb(ar->rx_cmd_urb, GFP_ATOMIC); + if (error) { + if (error != -ENODEV) + ar5523_err(ar, "error %d when submitting rx urb\n", + error); + return error; + } + return 0; +} + +/* + * Command submitted cb + */ +static void ar5523_cmd_tx_cb(struct urb *urb) +{ + struct ar5523_tx_cmd *cmd = urb->context; + struct ar5523 *ar = cmd->ar; + + if (urb->status) { + ar5523_err(ar, "Failed to TX command. Status = %d\n", + urb->status); + cmd->res = urb->status; + complete(&cmd->done); + return; + } + + if (!(cmd->flags & AR5523_CMD_FLAG_READ)) { + cmd->res = 0; + complete(&cmd->done); + } +} + +static int ar5523_cmd(struct ar5523 *ar, u32 code, const void *idata, + int ilen, void *odata, int olen, int flags) +{ + struct ar5523_cmd_hdr *hdr; + struct ar5523_tx_cmd *cmd = &ar->tx_cmd; + int xferlen, error; + + /* always bulk-out a multiple of 4 bytes */ + xferlen = (sizeof(struct ar5523_cmd_hdr) + ilen + 3) & ~3; + + hdr = (struct ar5523_cmd_hdr *)cmd->buf_tx; + memset(hdr, 0, sizeof(struct ar5523_cmd_hdr)); + hdr->len = cpu_to_be32(xferlen); + hdr->code = cpu_to_be32(code); + hdr->priv = AR5523_CMD_ID; + + if (flags & AR5523_CMD_FLAG_MAGIC) + hdr->magic = cpu_to_be32(1 << 24); + memcpy(hdr + 1, idata, ilen); + + cmd->odata = odata; + cmd->olen = olen; + cmd->flags = flags; + + ar5523_dbg(ar, "do cmd %02x\n", code); + + usb_fill_bulk_urb(cmd->urb_tx, ar->dev, ar5523_cmd_tx_pipe(ar->dev), + cmd->buf_tx, xferlen, ar5523_cmd_tx_cb, cmd); + cmd->urb_tx->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + error = usb_submit_urb(cmd->urb_tx, GFP_KERNEL); + if (error) { + ar5523_err(ar, "could not send command 0x%x, error=%d\n", + code, error); + return error; + } + + if (!wait_for_completion_timeout(&cmd->done, 2 * HZ)) { + cmd->odata = NULL; + ar5523_err(ar, "timeout waiting for command %02x reply\n", + code); + cmd->res = -ETIMEDOUT; + } + return cmd->res; +} + +static int ar5523_cmd_write(struct ar5523 *ar, u32 code, const void *data, + int len, int flags) +{ + flags &= ~AR5523_CMD_FLAG_READ; + return ar5523_cmd(ar, code, data, len, NULL, 0, flags); +} + +static int ar5523_cmd_read(struct ar5523 *ar, u32 code, const void *idata, + int ilen, void *odata, int olen, int flags) +{ + flags |= AR5523_CMD_FLAG_READ; + return ar5523_cmd(ar, code, idata, ilen, odata, olen, flags); +} + +static int ar5523_config(struct ar5523 *ar, u32 reg, u32 val) +{ + struct ar5523_write_mac write; + int error; + + write.reg = cpu_to_be32(reg); + write.len = cpu_to_be32(0); /* 0 = single write */ + *(__be32 *)write.data = cpu_to_be32(val); + + error = ar5523_cmd_write(ar, WDCMSG_TARGET_SET_CONFIG, &write, + 3 * sizeof(u32), 0); + if (error != 0) + ar5523_err(ar, "could not write register 0x%02x\n", reg); + return error; +} + +static int ar5523_config_multi(struct ar5523 *ar, u32 reg, const void *data, + int len) +{ + struct ar5523_write_mac write; + int error; + + write.reg = cpu_to_be32(reg); + write.len = cpu_to_be32(len); + memcpy(write.data, data, len); + + /* properly handle the case where len is zero (reset) */ + error = ar5523_cmd_write(ar, WDCMSG_TARGET_SET_CONFIG, &write, + (len == 0) ? sizeof(u32) : 2 * sizeof(u32) + len, 0); + if (error != 0) + ar5523_err(ar, "could not write %d bytes to register 0x%02x\n", + len, reg); + return error; +} + +static int ar5523_get_status(struct ar5523 *ar, u32 which, void *odata, + int olen) +{ + int error; + __be32 which_be; + + which_be = cpu_to_be32(which); + error = ar5523_cmd_read(ar, WDCMSG_TARGET_GET_STATUS, + &which_be, sizeof(which_be), odata, olen, AR5523_CMD_FLAG_MAGIC); + if (error != 0) + ar5523_err(ar, "could not read EEPROM offset 0x%02x\n", which); + return error; +} + +static int ar5523_get_capability(struct ar5523 *ar, u32 cap, u32 *val) +{ + int error; + __be32 cap_be, val_be; + + cap_be = cpu_to_be32(cap); + error = ar5523_cmd_read(ar, WDCMSG_TARGET_GET_CAPABILITY, &cap_be, + sizeof(cap_be), &val_be, sizeof(__be32), + AR5523_CMD_FLAG_MAGIC); + if (error != 0) { + ar5523_err(ar, "could not read capability %u\n", cap); + return error; + } + *val = be32_to_cpu(val_be); + return error; +} + +static int ar5523_get_devcap(struct ar5523 *ar) +{ +#define GETCAP(x) do { \ + error = ar5523_get_capability(ar, x, &cap); \ + if (error != 0) \ + return error; \ + ar5523_info(ar, "Cap: " \ + "%s=0x%08x\n", #x, cap); \ +} while (0) + int error; + u32 cap; + + /* collect device capabilities */ + GETCAP(CAP_TARGET_VERSION); + GETCAP(CAP_TARGET_REVISION); + GETCAP(CAP_MAC_VERSION); + GETCAP(CAP_MAC_REVISION); + GETCAP(CAP_PHY_REVISION); + GETCAP(CAP_ANALOG_5GHz_REVISION); + GETCAP(CAP_ANALOG_2GHz_REVISION); + + GETCAP(CAP_REG_DOMAIN); + GETCAP(CAP_REG_CAP_BITS); + GETCAP(CAP_WIRELESS_MODES); + GETCAP(CAP_CHAN_SPREAD_SUPPORT); + GETCAP(CAP_COMPRESS_SUPPORT); + GETCAP(CAP_BURST_SUPPORT); + GETCAP(CAP_FAST_FRAMES_SUPPORT); + GETCAP(CAP_CHAP_TUNING_SUPPORT); + GETCAP(CAP_TURBOG_SUPPORT); + GETCAP(CAP_TURBO_PRIME_SUPPORT); + GETCAP(CAP_DEVICE_TYPE); + GETCAP(CAP_WME_SUPPORT); + GETCAP(CAP_TOTAL_QUEUES); + GETCAP(CAP_CONNECTION_ID_MAX); + + GETCAP(CAP_LOW_5GHZ_CHAN); + GETCAP(CAP_HIGH_5GHZ_CHAN); + GETCAP(CAP_LOW_2GHZ_CHAN); + GETCAP(CAP_HIGH_2GHZ_CHAN); + GETCAP(CAP_TWICE_ANTENNAGAIN_5G); + GETCAP(CAP_TWICE_ANTENNAGAIN_2G); + + GETCAP(CAP_CIPHER_AES_CCM); + GETCAP(CAP_CIPHER_TKIP); + GETCAP(CAP_MIC_TKIP); + return 0; +} + +static int ar5523_set_ledsteady(struct ar5523 *ar, int lednum, int ledmode) +{ + struct ar5523_cmd_ledsteady led; + + led.lednum = cpu_to_be32(lednum); + led.ledmode = cpu_to_be32(ledmode); + + ar5523_dbg(ar, "set %s led %s (steady)\n", + (lednum == UATH_LED_LINK) ? "link" : "activity", + ledmode ? "on" : "off"); + return ar5523_cmd_write(ar, WDCMSG_SET_LED_STEADY, &led, sizeof(led), + 0); +} + +static int ar5523_set_rxfilter(struct ar5523 *ar, u32 bits, u32 op) +{ + struct ar5523_cmd_rx_filter rxfilter; + + rxfilter.bits = cpu_to_be32(bits); + rxfilter.op = cpu_to_be32(op); + + ar5523_dbg(ar, "setting Rx filter=0x%x flags=0x%x\n", bits, op); + return ar5523_cmd_write(ar, WDCMSG_RX_FILTER, &rxfilter, + sizeof(rxfilter), 0); +} + +static int ar5523_reset_tx_queues(struct ar5523 *ar) +{ + __be32 qid = cpu_to_be32(0); + + ar5523_dbg(ar, "resetting Tx queue\n"); + return ar5523_cmd_write(ar, WDCMSG_RELEASE_TX_QUEUE, + &qid, sizeof(qid), 0); +} + +static int ar5523_set_chan(struct ar5523 *ar) +{ + struct ieee80211_conf *conf = &ar->hw->conf; + + struct ar5523_cmd_reset reset; + + memset(&reset, 0, sizeof(reset)); + reset.flags |= cpu_to_be32(UATH_CHAN_2GHZ); + reset.flags |= cpu_to_be32(UATH_CHAN_OFDM); + reset.freq = cpu_to_be32(conf->chandef.chan->center_freq); + reset.maxrdpower = cpu_to_be32(50); /* XXX */ + reset.channelchange = cpu_to_be32(1); + reset.keeprccontent = cpu_to_be32(0); + + ar5523_dbg(ar, "set chan flags 0x%x freq %d\n", + be32_to_cpu(reset.flags), + conf->chandef.chan->center_freq); + return ar5523_cmd_write(ar, WDCMSG_RESET, &reset, sizeof(reset), 0); +} + +static int ar5523_queue_init(struct ar5523 *ar) +{ + struct ar5523_cmd_txq_setup qinfo; + + ar5523_dbg(ar, "setting up Tx queue\n"); + qinfo.qid = cpu_to_be32(0); + qinfo.len = cpu_to_be32(sizeof(qinfo.attr)); + qinfo.attr.priority = cpu_to_be32(0); /* XXX */ + qinfo.attr.aifs = cpu_to_be32(3); + qinfo.attr.logcwmin = cpu_to_be32(4); + qinfo.attr.logcwmax = cpu_to_be32(10); + qinfo.attr.bursttime = cpu_to_be32(0); + qinfo.attr.mode = cpu_to_be32(0); + qinfo.attr.qflags = cpu_to_be32(1); /* XXX? */ + return ar5523_cmd_write(ar, WDCMSG_SETUP_TX_QUEUE, &qinfo, + sizeof(qinfo), 0); +} + +static int ar5523_switch_chan(struct ar5523 *ar) +{ + int error; + + error = ar5523_set_chan(ar); + if (error) { + ar5523_err(ar, "could not set chan, error %d\n", error); + goto out_err; + } + + /* reset Tx rings */ + error = ar5523_reset_tx_queues(ar); + if (error) { + ar5523_err(ar, "could not reset Tx queues, error %d\n", + error); + goto out_err; + } + /* set Tx rings WME properties */ + error = ar5523_queue_init(ar); + if (error) + ar5523_err(ar, "could not init wme, error %d\n", error); + +out_err: + return error; +} + +static void ar5523_rx_data_put(struct ar5523 *ar, + struct ar5523_rx_data *data) +{ + unsigned long flags; + spin_lock_irqsave(&ar->rx_data_list_lock, flags); + list_move(&data->list, &ar->rx_data_free); + spin_unlock_irqrestore(&ar->rx_data_list_lock, flags); +} + +static void ar5523_data_rx_cb(struct urb *urb) +{ + struct ar5523_rx_data *data = urb->context; + struct ar5523 *ar = data->ar; + struct ar5523_rx_desc *desc; + struct ar5523_chunk *chunk; + struct ieee80211_hw *hw = ar->hw; + struct ieee80211_rx_status *rx_status; + u32 rxlen; + int usblen = urb->actual_length; + int hdrlen, pad; + + ar5523_dbg(ar, "%s\n", __func__); + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (urb->status != -ESHUTDOWN) + ar5523_err(ar, "%s: USB err: %d\n", __func__, + urb->status); + goto skip; + } + + if (usblen < AR5523_MIN_RXBUFSZ) { + ar5523_err(ar, "RX: wrong xfer size (usblen=%d)\n", usblen); + goto skip; + } + + chunk = (struct ar5523_chunk *) data->skb->data; + + if (((chunk->flags & UATH_CFLAGS_FINAL) == 0) || + chunk->seqnum != 0) { + ar5523_dbg(ar, "RX: No final flag. s: %d f: %02x l: %d\n", + chunk->seqnum, chunk->flags, + be16_to_cpu(chunk->length)); + goto skip; + } + + /* Rx descriptor is located at the end, 32-bit aligned */ + desc = (struct ar5523_rx_desc *) + (data->skb->data + usblen - sizeof(struct ar5523_rx_desc)); + + rxlen = be32_to_cpu(desc->len); + if (rxlen > ar->rxbufsz) { + ar5523_dbg(ar, "RX: Bad descriptor (len=%d)\n", + be32_to_cpu(desc->len)); + goto skip; + } + + if (!rxlen) { + ar5523_dbg(ar, "RX: rxlen is 0\n"); + goto skip; + } + + if (be32_to_cpu(desc->status) != 0) { + ar5523_dbg(ar, "Bad RX status (0x%x len = %d). Skip\n", + be32_to_cpu(desc->status), be32_to_cpu(desc->len)); + goto skip; + } + + skb_reserve(data->skb, sizeof(*chunk)); + skb_put(data->skb, rxlen - sizeof(struct ar5523_rx_desc)); + + hdrlen = ieee80211_get_hdrlen_from_skb(data->skb); + if (!IS_ALIGNED(hdrlen, 4)) { + ar5523_dbg(ar, "eek, alignment workaround activated\n"); + pad = ALIGN(hdrlen, 4) - hdrlen; + memmove(data->skb->data + pad, data->skb->data, hdrlen); + skb_pull(data->skb, pad); + skb_put(data->skb, pad); + } + + rx_status = IEEE80211_SKB_RXCB(data->skb); + memset(rx_status, 0, sizeof(*rx_status)); + rx_status->freq = be32_to_cpu(desc->channel); + rx_status->band = hw->conf.chandef.chan->band; + rx_status->signal = -95 + be32_to_cpu(desc->rssi); + + ieee80211_rx_irqsafe(hw, data->skb); + data->skb = NULL; + +skip: + if (data->skb) { + dev_kfree_skb_irq(data->skb); + data->skb = NULL; + } + + ar5523_rx_data_put(ar, data); + if (atomic_inc_return(&ar->rx_data_free_cnt) >= + AR5523_RX_DATA_REFILL_COUNT && + test_bit(AR5523_HW_UP, &ar->flags)) + queue_work(ar->wq, &ar->rx_refill_work); +} + +static void ar5523_rx_refill_work(struct work_struct *work) +{ + struct ar5523 *ar = container_of(work, struct ar5523, rx_refill_work); + struct ar5523_rx_data *data; + unsigned long flags; + int error; + + ar5523_dbg(ar, "%s\n", __func__); + do { + spin_lock_irqsave(&ar->rx_data_list_lock, flags); + + if (!list_empty(&ar->rx_data_free)) + data = (struct ar5523_rx_data *) ar->rx_data_free.next; + else + data = NULL; + spin_unlock_irqrestore(&ar->rx_data_list_lock, flags); + + if (!data) + goto done; + + data->skb = alloc_skb(ar->rxbufsz, GFP_KERNEL); + if (!data->skb) { + ar5523_err(ar, "could not allocate rx skbuff\n"); + return; + } + + usb_fill_bulk_urb(data->urb, ar->dev, + ar5523_data_rx_pipe(ar->dev), data->skb->data, + ar->rxbufsz, ar5523_data_rx_cb, data); + + spin_lock_irqsave(&ar->rx_data_list_lock, flags); + list_move(&data->list, &ar->rx_data_used); + spin_unlock_irqrestore(&ar->rx_data_list_lock, flags); + atomic_dec(&ar->rx_data_free_cnt); + + error = usb_submit_urb(data->urb, GFP_KERNEL); + if (error) { + kfree_skb(data->skb); + if (error != -ENODEV) + ar5523_err(ar, "Err sending rx data urb %d\n", + error); + ar5523_rx_data_put(ar, data); + atomic_inc(&ar->rx_data_free_cnt); + return; + } + + } while (true); +done: + return; +} + +static void ar5523_cancel_rx_bufs(struct ar5523 *ar) +{ + struct ar5523_rx_data *data; + unsigned long flags; + + do { + spin_lock_irqsave(&ar->rx_data_list_lock, flags); + if (!list_empty(&ar->rx_data_used)) + data = (struct ar5523_rx_data *) ar->rx_data_used.next; + else + data = NULL; + spin_unlock_irqrestore(&ar->rx_data_list_lock, flags); + + if (!data) + break; + + usb_kill_urb(data->urb); + list_move(&data->list, &ar->rx_data_free); + atomic_inc(&ar->rx_data_free_cnt); + } while (data); +} + +static void ar5523_free_rx_bufs(struct ar5523 *ar) +{ + struct ar5523_rx_data *data; + + ar5523_cancel_rx_bufs(ar); + while (!list_empty(&ar->rx_data_free)) { + data = (struct ar5523_rx_data *) ar->rx_data_free.next; + list_del(&data->list); + usb_free_urb(data->urb); + } +} + +static int ar5523_alloc_rx_bufs(struct ar5523 *ar) +{ + int i; + + for (i = 0; i < AR5523_RX_DATA_COUNT; i++) { + struct ar5523_rx_data *data = &ar->rx_data[i]; + + data->ar = ar; + data->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!data->urb) { + ar5523_err(ar, "could not allocate rx data urb\n"); + goto err; + } + list_add_tail(&data->list, &ar->rx_data_free); + atomic_inc(&ar->rx_data_free_cnt); + } + return 0; + +err: + ar5523_free_rx_bufs(ar); + return -ENOMEM; +} + +static void ar5523_data_tx_pkt_put(struct ar5523 *ar) +{ + atomic_dec(&ar->tx_nr_total); + if (!atomic_dec_return(&ar->tx_nr_pending)) { + del_timer(&ar->tx_wd_timer); + wake_up(&ar->tx_flush_waitq); + } + + if (atomic_read(&ar->tx_nr_total) < AR5523_TX_DATA_RESTART_COUNT) { + ar5523_dbg(ar, "restart tx queue\n"); + ieee80211_wake_queues(ar->hw); + } +} + +static void ar5523_data_tx_cb(struct urb *urb) +{ + struct sk_buff *skb = urb->context; + struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb); + struct ar5523_tx_data *data = (struct ar5523_tx_data *) + txi->driver_data; + struct ar5523 *ar = data->ar; + unsigned long flags; + + ar5523_dbg(ar, "data tx urb completed: %d\n", urb->status); + + spin_lock_irqsave(&ar->tx_data_list_lock, flags); + list_del(&data->list); + spin_unlock_irqrestore(&ar->tx_data_list_lock, flags); + + if (urb->status) { + ar5523_dbg(ar, "%s: urb status: %d\n", __func__, urb->status); + ar5523_data_tx_pkt_put(ar); + ieee80211_free_txskb(ar->hw, skb); + } else { + skb_pull(skb, sizeof(struct ar5523_tx_desc) + sizeof(__be32)); + ieee80211_tx_status_irqsafe(ar->hw, skb); + } + usb_free_urb(urb); +} + +static void ar5523_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb); + struct ar5523_tx_data *data = (struct ar5523_tx_data *) + txi->driver_data; + struct ar5523 *ar = hw->priv; + unsigned long flags; + + ar5523_dbg(ar, "tx called\n"); + if (atomic_inc_return(&ar->tx_nr_total) >= AR5523_TX_DATA_COUNT) { + ar5523_dbg(ar, "tx queue full\n"); + ar5523_dbg(ar, "stop queues (tot %d pend %d)\n", + atomic_read(&ar->tx_nr_total), + atomic_read(&ar->tx_nr_pending)); + ieee80211_stop_queues(hw); + } + + data->skb = skb; + + spin_lock_irqsave(&ar->tx_data_list_lock, flags); + list_add_tail(&data->list, &ar->tx_queue_pending); + spin_unlock_irqrestore(&ar->tx_data_list_lock, flags); + + ieee80211_queue_work(ar->hw, &ar->tx_work); +} + +static void ar5523_tx_work_locked(struct ar5523 *ar) +{ + struct ar5523_tx_data *data; + struct ar5523_tx_desc *desc; + struct ar5523_chunk *chunk; + struct ieee80211_tx_info *txi; + struct urb *urb; + struct sk_buff *skb; + int error = 0, paylen; + u32 txqid; + unsigned long flags; + + BUILD_BUG_ON(sizeof(struct ar5523_tx_data) > + IEEE80211_TX_INFO_DRIVER_DATA_SIZE); + + ar5523_dbg(ar, "%s\n", __func__); + do { + spin_lock_irqsave(&ar->tx_data_list_lock, flags); + if (!list_empty(&ar->tx_queue_pending)) { + data = (struct ar5523_tx_data *) + ar->tx_queue_pending.next; + list_del(&data->list); + } else + data = NULL; + spin_unlock_irqrestore(&ar->tx_data_list_lock, flags); + + if (!data) + break; + + skb = data->skb; + txqid = 0; + txi = IEEE80211_SKB_CB(skb); + paylen = skb->len; + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + ar5523_err(ar, "Failed to allocate TX urb\n"); + ieee80211_free_txskb(ar->hw, skb); + continue; + } + + data->ar = ar; + data->urb = urb; + + desc = (struct ar5523_tx_desc *)skb_push(skb, sizeof(*desc)); + chunk = (struct ar5523_chunk *)skb_push(skb, sizeof(*chunk)); + + chunk->seqnum = 0; + chunk->flags = UATH_CFLAGS_FINAL; + chunk->length = cpu_to_be16(skb->len); + + desc->msglen = cpu_to_be32(skb->len); + desc->msgid = AR5523_DATA_ID; + desc->buflen = cpu_to_be32(paylen); + desc->type = cpu_to_be32(WDCMSG_SEND); + desc->flags = cpu_to_be32(UATH_TX_NOTIFY); + + if (test_bit(AR5523_CONNECTED, &ar->flags)) + desc->connid = cpu_to_be32(AR5523_ID_BSS); + else + desc->connid = cpu_to_be32(AR5523_ID_BROADCAST); + + if (txi->flags & IEEE80211_TX_CTL_USE_MINRATE) + txqid |= UATH_TXQID_MINRATE; + + desc->txqid = cpu_to_be32(txqid); + + urb->transfer_flags = URB_ZERO_PACKET; + usb_fill_bulk_urb(urb, ar->dev, ar5523_data_tx_pipe(ar->dev), + skb->data, skb->len, ar5523_data_tx_cb, skb); + + spin_lock_irqsave(&ar->tx_data_list_lock, flags); + list_add_tail(&data->list, &ar->tx_queue_submitted); + spin_unlock_irqrestore(&ar->tx_data_list_lock, flags); + mod_timer(&ar->tx_wd_timer, jiffies + AR5523_TX_WD_TIMEOUT); + atomic_inc(&ar->tx_nr_pending); + + ar5523_dbg(ar, "TX Frame (%d pending)\n", + atomic_read(&ar->tx_nr_pending)); + error = usb_submit_urb(urb, GFP_KERNEL); + if (error) { + ar5523_err(ar, "error %d when submitting tx urb\n", + error); + spin_lock_irqsave(&ar->tx_data_list_lock, flags); + list_del(&data->list); + spin_unlock_irqrestore(&ar->tx_data_list_lock, flags); + atomic_dec(&ar->tx_nr_pending); + ar5523_data_tx_pkt_put(ar); + usb_free_urb(urb); + ieee80211_free_txskb(ar->hw, skb); + } + } while (true); +} + +static void ar5523_tx_work(struct work_struct *work) +{ + struct ar5523 *ar = container_of(work, struct ar5523, tx_work); + + ar5523_dbg(ar, "%s\n", __func__); + mutex_lock(&ar->mutex); + ar5523_tx_work_locked(ar); + mutex_unlock(&ar->mutex); +} + +static void ar5523_tx_wd_timer(unsigned long arg) +{ + struct ar5523 *ar = (struct ar5523 *) arg; + + ar5523_dbg(ar, "TX watchdog timer triggered\n"); + ieee80211_queue_work(ar->hw, &ar->tx_wd_work); +} + +static void ar5523_tx_wd_work(struct work_struct *work) +{ + struct ar5523 *ar = container_of(work, struct ar5523, tx_wd_work); + + /* Occasionally the TX queues stop responding. The only way to + * recover seems to be to reset the dongle. + */ + + mutex_lock(&ar->mutex); + ar5523_err(ar, "TX queue stuck (tot %d pend %d)\n", + atomic_read(&ar->tx_nr_total), + atomic_read(&ar->tx_nr_pending)); + + ar5523_err(ar, "Will restart dongle.\n"); + ar5523_cmd_write(ar, WDCMSG_TARGET_RESET, NULL, 0, 0); + mutex_unlock(&ar->mutex); +} + +static void ar5523_flush_tx(struct ar5523 *ar) +{ + ar5523_tx_work_locked(ar); + + /* Don't waste time trying to flush if USB is disconnected */ + if (test_bit(AR5523_USB_DISCONNECTED, &ar->flags)) + return; + if (!wait_event_timeout(ar->tx_flush_waitq, + !atomic_read(&ar->tx_nr_pending), AR5523_FLUSH_TIMEOUT)) + ar5523_err(ar, "flush timeout (tot %d pend %d)\n", + atomic_read(&ar->tx_nr_total), + atomic_read(&ar->tx_nr_pending)); +} + +static void ar5523_free_tx_cmd(struct ar5523 *ar) +{ + struct ar5523_tx_cmd *cmd = &ar->tx_cmd; + + usb_free_coherent(ar->dev, AR5523_MAX_RXCMDSZ, cmd->buf_tx, + cmd->urb_tx->transfer_dma); + usb_free_urb(cmd->urb_tx); +} + +static int ar5523_alloc_tx_cmd(struct ar5523 *ar) +{ + struct ar5523_tx_cmd *cmd = &ar->tx_cmd; + + cmd->ar = ar; + init_completion(&cmd->done); + + cmd->urb_tx = usb_alloc_urb(0, GFP_KERNEL); + if (!cmd->urb_tx) { + ar5523_err(ar, "could not allocate urb\n"); + return -ENOMEM; + } + cmd->buf_tx = usb_alloc_coherent(ar->dev, AR5523_MAX_TXCMDSZ, + GFP_KERNEL, + &cmd->urb_tx->transfer_dma); + if (!cmd->buf_tx) { + usb_free_urb(cmd->urb_tx); + return -ENOMEM; + } + return 0; +} + +/* + * This function is called periodically (every second) when associated to + * query device statistics. + */ +static void ar5523_stat_work(struct work_struct *work) +{ + struct ar5523 *ar = container_of(work, struct ar5523, stat_work.work); + int error; + + ar5523_dbg(ar, "%s\n", __func__); + mutex_lock(&ar->mutex); + + /* + * Send request for statistics asynchronously once a second. This + * seems to be important. Throughput is a lot better if this is done. + */ + error = ar5523_cmd_write(ar, WDCMSG_TARGET_GET_STATS, NULL, 0, 0); + if (error) + ar5523_err(ar, "could not query stats, error %d\n", error); + mutex_unlock(&ar->mutex); + ieee80211_queue_delayed_work(ar->hw, &ar->stat_work, HZ); +} + +/* + * Interface routines to the mac80211 stack. + */ +static int ar5523_start(struct ieee80211_hw *hw) +{ + struct ar5523 *ar = hw->priv; + int error; + __be32 val; + + ar5523_dbg(ar, "start called\n"); + + mutex_lock(&ar->mutex); + val = cpu_to_be32(0); + ar5523_cmd_write(ar, WDCMSG_BIND, &val, sizeof(val), 0); + + /* set MAC address */ + ar5523_config_multi(ar, CFG_MAC_ADDR, &ar->hw->wiphy->perm_addr, + ETH_ALEN); + + /* XXX honor net80211 state */ + ar5523_config(ar, CFG_RATE_CONTROL_ENABLE, 0x00000001); + ar5523_config(ar, CFG_DIVERSITY_CTL, 0x00000001); + ar5523_config(ar, CFG_ABOLT, 0x0000003f); + ar5523_config(ar, CFG_WME_ENABLED, 0x00000000); + + ar5523_config(ar, CFG_SERVICE_TYPE, 1); + ar5523_config(ar, CFG_TP_SCALE, 0x00000000); + ar5523_config(ar, CFG_TPC_HALF_DBM5, 0x0000003c); + ar5523_config(ar, CFG_TPC_HALF_DBM2, 0x0000003c); + ar5523_config(ar, CFG_OVERRD_TX_POWER, 0x00000000); + ar5523_config(ar, CFG_GMODE_PROTECTION, 0x00000000); + ar5523_config(ar, CFG_GMODE_PROTECT_RATE_INDEX, 0x00000003); + ar5523_config(ar, CFG_PROTECTION_TYPE, 0x00000000); + ar5523_config(ar, CFG_MODE_CTS, 0x00000002); + + error = ar5523_cmd_read(ar, WDCMSG_TARGET_START, NULL, 0, + &val, sizeof(val), AR5523_CMD_FLAG_MAGIC); + if (error) { + ar5523_dbg(ar, "could not start target, error %d\n", error); + goto err; + } + ar5523_dbg(ar, "WDCMSG_TARGET_START returns handle: 0x%x\n", + be32_to_cpu(val)); + + ar5523_switch_chan(ar); + + val = cpu_to_be32(TARGET_DEVICE_AWAKE); + ar5523_cmd_write(ar, WDCMSG_SET_PWR_MODE, &val, sizeof(val), 0); + /* XXX? check */ + ar5523_cmd_write(ar, WDCMSG_RESET_KEY_CACHE, NULL, 0, 0); + + set_bit(AR5523_HW_UP, &ar->flags); + queue_work(ar->wq, &ar->rx_refill_work); + + /* enable Rx */ + ar5523_set_rxfilter(ar, 0, UATH_FILTER_OP_INIT); + ar5523_set_rxfilter(ar, + UATH_FILTER_RX_UCAST | UATH_FILTER_RX_MCAST | + UATH_FILTER_RX_BCAST | UATH_FILTER_RX_BEACON, + UATH_FILTER_OP_SET); + + ar5523_set_ledsteady(ar, UATH_LED_ACTIVITY, UATH_LED_ON); + ar5523_dbg(ar, "start OK\n"); + +err: + mutex_unlock(&ar->mutex); + return error; +} + +static void ar5523_stop(struct ieee80211_hw *hw) +{ + struct ar5523 *ar = hw->priv; + + ar5523_dbg(ar, "stop called\n"); + + cancel_delayed_work_sync(&ar->stat_work); + mutex_lock(&ar->mutex); + clear_bit(AR5523_HW_UP, &ar->flags); + + ar5523_set_ledsteady(ar, UATH_LED_LINK, UATH_LED_OFF); + ar5523_set_ledsteady(ar, UATH_LED_ACTIVITY, UATH_LED_OFF); + + ar5523_cmd_write(ar, WDCMSG_TARGET_STOP, NULL, 0, 0); + + del_timer_sync(&ar->tx_wd_timer); + cancel_work_sync(&ar->tx_wd_work); + cancel_work_sync(&ar->rx_refill_work); + ar5523_cancel_rx_bufs(ar); + mutex_unlock(&ar->mutex); +} + +static int ar5523_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +{ + struct ar5523 *ar = hw->priv; + int ret; + + ar5523_dbg(ar, "set_rts_threshold called\n"); + mutex_lock(&ar->mutex); + + ret = ar5523_config(ar, CFG_USER_RTS_THRESHOLD, value); + + mutex_unlock(&ar->mutex); + return ret; +} + +static void ar5523_flush(struct ieee80211_hw *hw, u32 queues, bool drop) +{ + struct ar5523 *ar = hw->priv; + + ar5523_dbg(ar, "flush called\n"); + ar5523_flush_tx(ar); +} + +static int ar5523_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct ar5523 *ar = hw->priv; + + ar5523_dbg(ar, "add interface called\n"); + + if (ar->vif) { + ar5523_dbg(ar, "invalid add_interface\n"); + return -EOPNOTSUPP; + } + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + ar->vif = vif; + break; + default: + return -EOPNOTSUPP; + } + return 0; +} + +static void ar5523_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct ar5523 *ar = hw->priv; + + ar5523_dbg(ar, "remove interface called\n"); + ar->vif = NULL; +} + +static int ar5523_hwconfig(struct ieee80211_hw *hw, u32 changed) +{ + struct ar5523 *ar = hw->priv; + + ar5523_dbg(ar, "config called\n"); + mutex_lock(&ar->mutex); + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + ar5523_dbg(ar, "Do channel switch\n"); + ar5523_flush_tx(ar); + ar5523_switch_chan(ar); + } + mutex_unlock(&ar->mutex); + return 0; +} + +static int ar5523_get_wlan_mode(struct ar5523 *ar, + struct ieee80211_bss_conf *bss_conf) +{ + struct ieee80211_supported_band *band; + int bit; + struct ieee80211_sta *sta; + u32 sta_rate_set; + + band = ar->hw->wiphy->bands[ar->hw->conf.chandef.chan->band]; + sta = ieee80211_find_sta(ar->vif, bss_conf->bssid); + if (!sta) { + ar5523_info(ar, "STA not found!\n"); + return WLAN_MODE_11b; + } + sta_rate_set = sta->supp_rates[ar->hw->conf.chandef.chan->band]; + + for (bit = 0; bit < band->n_bitrates; bit++) { + if (sta_rate_set & 1) { + int rate = band->bitrates[bit].bitrate; + switch (rate) { + case 60: + case 90: + case 120: + case 180: + case 240: + case 360: + case 480: + case 540: + return WLAN_MODE_11g; + } + } + sta_rate_set >>= 1; + } + return WLAN_MODE_11b; +} + +static void ar5523_create_rateset(struct ar5523 *ar, + struct ieee80211_bss_conf *bss_conf, + struct ar5523_cmd_rateset *rs, + bool basic) +{ + struct ieee80211_supported_band *band; + struct ieee80211_sta *sta; + int bit, i = 0; + u32 sta_rate_set, basic_rate_set; + + sta = ieee80211_find_sta(ar->vif, bss_conf->bssid); + basic_rate_set = bss_conf->basic_rates; + if (!sta) { + ar5523_info(ar, "STA not found. Cannot set rates\n"); + sta_rate_set = bss_conf->basic_rates; + } else + sta_rate_set = sta->supp_rates[ar->hw->conf.chandef.chan->band]; + + ar5523_dbg(ar, "sta rate_set = %08x\n", sta_rate_set); + + band = ar->hw->wiphy->bands[ar->hw->conf.chandef.chan->band]; + for (bit = 0; bit < band->n_bitrates; bit++) { + BUG_ON(i >= AR5523_MAX_NRATES); + ar5523_dbg(ar, "Considering rate %d : %d\n", + band->bitrates[bit].hw_value, sta_rate_set & 1); + if (sta_rate_set & 1) { + rs->set[i] = band->bitrates[bit].hw_value; + if (basic_rate_set & 1 && basic) + rs->set[i] |= 0x80; + i++; + } + sta_rate_set >>= 1; + basic_rate_set >>= 1; + } + + rs->length = i; +} + +static int ar5523_set_basic_rates(struct ar5523 *ar, + struct ieee80211_bss_conf *bss) +{ + struct ar5523_cmd_rates rates; + + memset(&rates, 0, sizeof(rates)); + rates.connid = cpu_to_be32(2); /* XXX */ + rates.size = cpu_to_be32(sizeof(struct ar5523_cmd_rateset)); + ar5523_create_rateset(ar, bss, &rates.rateset, true); + + return ar5523_cmd_write(ar, WDCMSG_SET_BASIC_RATE, &rates, + sizeof(rates), 0); +} + +static int ar5523_create_connection(struct ar5523 *ar, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss) +{ + struct ar5523_cmd_create_connection create; + int wlan_mode; + + memset(&create, 0, sizeof(create)); + create.connid = cpu_to_be32(2); + create.bssid = cpu_to_be32(0); + /* XXX packed or not? */ + create.size = cpu_to_be32(sizeof(struct ar5523_cmd_rateset)); + + ar5523_create_rateset(ar, bss, &create.connattr.rateset, false); + + wlan_mode = ar5523_get_wlan_mode(ar, bss); + create.connattr.wlanmode = cpu_to_be32(wlan_mode); + + return ar5523_cmd_write(ar, WDCMSG_CREATE_CONNECTION, &create, + sizeof(create), 0); +} + +static int ar5523_write_associd(struct ar5523 *ar, + struct ieee80211_bss_conf *bss) +{ + struct ar5523_cmd_set_associd associd; + + memset(&associd, 0, sizeof(associd)); + associd.defaultrateix = cpu_to_be32(0); /* XXX */ + associd.associd = cpu_to_be32(bss->aid); + associd.timoffset = cpu_to_be32(0x3b); /* XXX */ + memcpy(associd.bssid, bss->bssid, ETH_ALEN); + return ar5523_cmd_write(ar, WDCMSG_WRITE_ASSOCID, &associd, + sizeof(associd), 0); +} + +static void ar5523_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss, + u32 changed) +{ + struct ar5523 *ar = hw->priv; + int error; + + ar5523_dbg(ar, "bss_info_changed called\n"); + mutex_lock(&ar->mutex); + + if (!(changed & BSS_CHANGED_ASSOC)) + goto out_unlock; + + if (bss->assoc) { + error = ar5523_create_connection(ar, vif, bss); + if (error) { + ar5523_err(ar, "could not create connection\n"); + goto out_unlock; + } + + error = ar5523_set_basic_rates(ar, bss); + if (error) { + ar5523_err(ar, "could not set negotiated rate set\n"); + goto out_unlock; + } + + error = ar5523_write_associd(ar, bss); + if (error) { + ar5523_err(ar, "could not set association\n"); + goto out_unlock; + } + + /* turn link LED on */ + ar5523_set_ledsteady(ar, UATH_LED_LINK, UATH_LED_ON); + set_bit(AR5523_CONNECTED, &ar->flags); + ieee80211_queue_delayed_work(hw, &ar->stat_work, HZ); + + } else { + cancel_delayed_work(&ar->stat_work); + clear_bit(AR5523_CONNECTED, &ar->flags); + ar5523_set_ledsteady(ar, UATH_LED_LINK, UATH_LED_OFF); + } + +out_unlock: + mutex_unlock(&ar->mutex); + +} + +#define AR5523_SUPPORTED_FILTERS (FIF_PROMISC_IN_BSS | \ + FIF_ALLMULTI | \ + FIF_FCSFAIL | \ + FIF_OTHER_BSS) + +static void ar5523_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + struct ar5523 *ar = hw->priv; + u32 filter = 0; + + ar5523_dbg(ar, "configure_filter called\n"); + mutex_lock(&ar->mutex); + ar5523_flush_tx(ar); + + *total_flags &= AR5523_SUPPORTED_FILTERS; + + /* The filters seems strange. UATH_FILTER_RX_BCAST and + * UATH_FILTER_RX_MCAST does not result in those frames being RXed. + * The only way I have found to get [mb]cast frames seems to be + * to set UATH_FILTER_RX_PROM. */ + filter |= UATH_FILTER_RX_UCAST | UATH_FILTER_RX_MCAST | + UATH_FILTER_RX_BCAST | UATH_FILTER_RX_BEACON | + UATH_FILTER_RX_PROM; + + ar5523_set_rxfilter(ar, 0, UATH_FILTER_OP_INIT); + ar5523_set_rxfilter(ar, filter, UATH_FILTER_OP_SET); + + mutex_unlock(&ar->mutex); +} + +static const struct ieee80211_ops ar5523_ops = { + .start = ar5523_start, + .stop = ar5523_stop, + .tx = ar5523_tx, + .set_rts_threshold = ar5523_set_rts_threshold, + .add_interface = ar5523_add_interface, + .remove_interface = ar5523_remove_interface, + .config = ar5523_hwconfig, + .bss_info_changed = ar5523_bss_info_changed, + .configure_filter = ar5523_configure_filter, + .flush = ar5523_flush, +}; + +static int ar5523_host_available(struct ar5523 *ar) +{ + struct ar5523_cmd_host_available setup; + + /* inform target the host is available */ + setup.sw_ver_major = cpu_to_be32(ATH_SW_VER_MAJOR); + setup.sw_ver_minor = cpu_to_be32(ATH_SW_VER_MINOR); + setup.sw_ver_patch = cpu_to_be32(ATH_SW_VER_PATCH); + setup.sw_ver_build = cpu_to_be32(ATH_SW_VER_BUILD); + return ar5523_cmd_read(ar, WDCMSG_HOST_AVAILABLE, + &setup, sizeof(setup), NULL, 0, 0); +} + +static int ar5523_get_devstatus(struct ar5523 *ar) +{ + u8 macaddr[ETH_ALEN]; + int error; + + /* retrieve MAC address */ + error = ar5523_get_status(ar, ST_MAC_ADDR, macaddr, ETH_ALEN); + if (error) { + ar5523_err(ar, "could not read MAC address\n"); + return error; + } + + SET_IEEE80211_PERM_ADDR(ar->hw, macaddr); + + error = ar5523_get_status(ar, ST_SERIAL_NUMBER, + &ar->serial[0], sizeof(ar->serial)); + if (error) { + ar5523_err(ar, "could not read device serial number\n"); + return error; + } + return 0; +} + +#define AR5523_SANE_RXBUFSZ 2000 + +static int ar5523_get_max_rxsz(struct ar5523 *ar) +{ + int error; + __be32 rxsize; + + /* Get max rx size */ + error = ar5523_get_status(ar, ST_WDC_TRANSPORT_CHUNK_SIZE, &rxsize, + sizeof(rxsize)); + if (error != 0) { + ar5523_err(ar, "could not read max RX size\n"); + return error; + } + + ar->rxbufsz = be32_to_cpu(rxsize); + + if (!ar->rxbufsz || ar->rxbufsz > AR5523_SANE_RXBUFSZ) { + ar5523_err(ar, "Bad rxbufsz from device. Using %d instead\n", + AR5523_SANE_RXBUFSZ); + ar->rxbufsz = AR5523_SANE_RXBUFSZ; + } + + ar5523_dbg(ar, "Max RX buf size: %d\n", ar->rxbufsz); + return 0; +} + +/* + * This is copied from rtl818x, but we should probably move this + * to common code as in OpenBSD. + */ +static const struct ieee80211_rate ar5523_rates[] = { + { .bitrate = 10, .hw_value = 2, }, + { .bitrate = 20, .hw_value = 4 }, + { .bitrate = 55, .hw_value = 11, }, + { .bitrate = 110, .hw_value = 22, }, + { .bitrate = 60, .hw_value = 12, }, + { .bitrate = 90, .hw_value = 18, }, + { .bitrate = 120, .hw_value = 24, }, + { .bitrate = 180, .hw_value = 36, }, + { .bitrate = 240, .hw_value = 48, }, + { .bitrate = 360, .hw_value = 72, }, + { .bitrate = 480, .hw_value = 96, }, + { .bitrate = 540, .hw_value = 108, }, +}; + +static const struct ieee80211_channel ar5523_channels[] = { + { .center_freq = 2412 }, + { .center_freq = 2417 }, + { .center_freq = 2422 }, + { .center_freq = 2427 }, + { .center_freq = 2432 }, + { .center_freq = 2437 }, + { .center_freq = 2442 }, + { .center_freq = 2447 }, + { .center_freq = 2452 }, + { .center_freq = 2457 }, + { .center_freq = 2462 }, + { .center_freq = 2467 }, + { .center_freq = 2472 }, + { .center_freq = 2484 }, +}; + +static int ar5523_init_modes(struct ar5523 *ar) +{ + BUILD_BUG_ON(sizeof(ar->channels) != sizeof(ar5523_channels)); + BUILD_BUG_ON(sizeof(ar->rates) != sizeof(ar5523_rates)); + + memcpy(ar->channels, ar5523_channels, sizeof(ar5523_channels)); + memcpy(ar->rates, ar5523_rates, sizeof(ar5523_rates)); + + ar->band.band = IEEE80211_BAND_2GHZ; + ar->band.channels = ar->channels; + ar->band.n_channels = ARRAY_SIZE(ar5523_channels); + ar->band.bitrates = ar->rates; + ar->band.n_bitrates = ARRAY_SIZE(ar5523_rates); + ar->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &ar->band; + return 0; +} + +/* + * Load the MIPS R4000 microcode into the device. Once the image is loaded, + * the device will detach itself from the bus and reattach later with a new + * product Id (a la ezusb). + */ +static int ar5523_load_firmware(struct usb_device *dev) +{ + struct ar5523_fwblock *txblock, *rxblock; + const struct firmware *fw; + void *fwbuf; + int len, offset; + int foolen; /* XXX(hch): handle short transfers */ + int error = -ENXIO; + + if (request_firmware(&fw, AR5523_FIRMWARE_FILE, &dev->dev)) { + dev_err(&dev->dev, "no firmware found: %s\n", + AR5523_FIRMWARE_FILE); + return -ENOENT; + } + + txblock = kmalloc(sizeof(*txblock), GFP_KERNEL); + if (!txblock) + goto out; + + rxblock = kmalloc(sizeof(*rxblock), GFP_KERNEL); + if (!rxblock) + goto out_free_txblock; + + fwbuf = kmalloc(AR5523_MAX_FWBLOCK_SIZE, GFP_KERNEL); + if (!fwbuf) + goto out_free_rxblock; + + memset(txblock, 0, sizeof(struct ar5523_fwblock)); + txblock->flags = cpu_to_be32(AR5523_WRITE_BLOCK); + txblock->total = cpu_to_be32(fw->size); + + offset = 0; + len = fw->size; + while (len > 0) { + int mlen = min(len, AR5523_MAX_FWBLOCK_SIZE); + + txblock->remain = cpu_to_be32(len - mlen); + txblock->len = cpu_to_be32(mlen); + + /* send firmware block meta-data */ + error = usb_bulk_msg(dev, ar5523_cmd_tx_pipe(dev), + txblock, sizeof(*txblock), &foolen, + AR5523_CMD_TIMEOUT); + if (error) { + dev_err(&dev->dev, + "could not send firmware block info\n"); + goto out_free_fwbuf; + } + + /* send firmware block data */ + memcpy(fwbuf, fw->data + offset, mlen); + error = usb_bulk_msg(dev, ar5523_data_tx_pipe(dev), + fwbuf, mlen, &foolen, + AR5523_DATA_TIMEOUT); + if (error) { + dev_err(&dev->dev, + "could not send firmware block data\n"); + goto out_free_fwbuf; + } + + /* wait for ack from firmware */ + error = usb_bulk_msg(dev, ar5523_cmd_rx_pipe(dev), + rxblock, sizeof(*rxblock), &foolen, + AR5523_CMD_TIMEOUT); + if (error) { + dev_err(&dev->dev, + "could not read firmware answer\n"); + goto out_free_fwbuf; + } + + len -= mlen; + offset += mlen; + } + + /* + * Set the error to -ENXIO to make sure we continue probing for + * a driver. + */ + error = -ENXIO; + + out_free_fwbuf: + kfree(fwbuf); + out_free_rxblock: + kfree(rxblock); + out_free_txblock: + kfree(txblock); + out: + release_firmware(fw); + return error; +} + +static int ar5523_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct ieee80211_hw *hw; + struct ar5523 *ar; + int error = -ENOMEM; + + /* + * Load firmware if the device requires it. This will return + * -ENXIO on success and we'll get called back afer the usb + * id changes to indicate that the firmware is present. + */ + if (id->driver_info & AR5523_FLAG_PRE_FIRMWARE) + return ar5523_load_firmware(dev); + + + hw = ieee80211_alloc_hw(sizeof(*ar), &ar5523_ops); + if (!hw) + goto out; + SET_IEEE80211_DEV(hw, &intf->dev); + + ar = hw->priv; + ar->hw = hw; + ar->dev = dev; + mutex_init(&ar->mutex); + + INIT_DELAYED_WORK(&ar->stat_work, ar5523_stat_work); + init_timer(&ar->tx_wd_timer); + setup_timer(&ar->tx_wd_timer, ar5523_tx_wd_timer, (unsigned long) ar); + INIT_WORK(&ar->tx_wd_work, ar5523_tx_wd_work); + INIT_WORK(&ar->tx_work, ar5523_tx_work); + INIT_LIST_HEAD(&ar->tx_queue_pending); + INIT_LIST_HEAD(&ar->tx_queue_submitted); + spin_lock_init(&ar->tx_data_list_lock); + atomic_set(&ar->tx_nr_total, 0); + atomic_set(&ar->tx_nr_pending, 0); + init_waitqueue_head(&ar->tx_flush_waitq); + + atomic_set(&ar->rx_data_free_cnt, 0); + INIT_WORK(&ar->rx_refill_work, ar5523_rx_refill_work); + INIT_LIST_HEAD(&ar->rx_data_free); + INIT_LIST_HEAD(&ar->rx_data_used); + spin_lock_init(&ar->rx_data_list_lock); + + ar->wq = create_singlethread_workqueue("ar5523"); + if (!ar->wq) { + ar5523_err(ar, "Could not create wq\n"); + goto out_free_ar; + } + + error = ar5523_alloc_rx_bufs(ar); + if (error) { + ar5523_err(ar, "Could not allocate rx buffers\n"); + goto out_free_wq; + } + + error = ar5523_alloc_rx_cmd(ar); + if (error) { + ar5523_err(ar, "Could not allocate rx command buffers\n"); + goto out_free_rx_bufs; + } + + error = ar5523_alloc_tx_cmd(ar); + if (error) { + ar5523_err(ar, "Could not allocate tx command buffers\n"); + goto out_free_rx_cmd; + } + + error = ar5523_submit_rx_cmd(ar); + if (error) { + ar5523_err(ar, "Failed to submit rx cmd\n"); + goto out_free_tx_cmd; + } + + /* + * We're now ready to send/receive firmware commands. + */ + error = ar5523_host_available(ar); + if (error) { + ar5523_err(ar, "could not initialize adapter\n"); + goto out_cancel_rx_cmd; + } + + error = ar5523_get_max_rxsz(ar); + if (error) { + ar5523_err(ar, "could not get caps from adapter\n"); + goto out_cancel_rx_cmd; + } + + error = ar5523_get_devcap(ar); + if (error) { + ar5523_err(ar, "could not get caps from adapter\n"); + goto out_cancel_rx_cmd; + } + + error = ar5523_get_devstatus(ar); + if (error != 0) { + ar5523_err(ar, "could not get device status\n"); + goto out_cancel_rx_cmd; + } + + ar5523_info(ar, "MAC/BBP AR5523, RF AR%c112\n", + (id->driver_info & AR5523_FLAG_ABG) ? '5' : '2'); + + ar->vif = NULL; + hw->flags = IEEE80211_HW_RX_INCLUDES_FCS | + IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_HAS_RATE_CONTROL; + hw->extra_tx_headroom = sizeof(struct ar5523_tx_desc) + + sizeof(struct ar5523_chunk); + hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION); + hw->queues = 1; + + error = ar5523_init_modes(ar); + if (error) + goto out_cancel_rx_cmd; + + usb_set_intfdata(intf, hw); + + error = ieee80211_register_hw(hw); + if (error) { + ar5523_err(ar, "could not register device\n"); + goto out_cancel_rx_cmd; + } + + ar5523_info(ar, "Found and initialized AR5523 device\n"); + return 0; + +out_cancel_rx_cmd: + ar5523_cancel_rx_cmd(ar); +out_free_tx_cmd: + ar5523_free_tx_cmd(ar); +out_free_rx_cmd: + ar5523_free_rx_cmd(ar); +out_free_rx_bufs: + ar5523_free_rx_bufs(ar); +out_free_wq: + destroy_workqueue(ar->wq); +out_free_ar: + ieee80211_free_hw(hw); +out: + return error; +} + +static void ar5523_disconnect(struct usb_interface *intf) +{ + struct ieee80211_hw *hw = usb_get_intfdata(intf); + struct ar5523 *ar = hw->priv; + + ar5523_dbg(ar, "detaching\n"); + set_bit(AR5523_USB_DISCONNECTED, &ar->flags); + + ieee80211_unregister_hw(hw); + + ar5523_cancel_rx_cmd(ar); + ar5523_free_tx_cmd(ar); + ar5523_free_rx_cmd(ar); + ar5523_free_rx_bufs(ar); + + destroy_workqueue(ar->wq); + + ieee80211_free_hw(hw); + usb_set_intfdata(intf, NULL); +} + +#define AR5523_DEVICE_UG(vendor, device) \ + { USB_DEVICE((vendor), (device)) }, \ + { USB_DEVICE((vendor), (device) + 1), \ + .driver_info = AR5523_FLAG_PRE_FIRMWARE } +#define AR5523_DEVICE_UX(vendor, device) \ + { USB_DEVICE((vendor), (device)), \ + .driver_info = AR5523_FLAG_ABG }, \ + { USB_DEVICE((vendor), (device) + 1), \ + .driver_info = AR5523_FLAG_ABG|AR5523_FLAG_PRE_FIRMWARE } + +static struct usb_device_id ar5523_id_table[] = { + AR5523_DEVICE_UG(0x168c, 0x0001), /* Atheros / AR5523 */ + AR5523_DEVICE_UG(0x0cf3, 0x0001), /* Atheros2 / AR5523_1 */ + AR5523_DEVICE_UG(0x0cf3, 0x0003), /* Atheros2 / AR5523_2 */ + AR5523_DEVICE_UX(0x0cf3, 0x0005), /* Atheros2 / AR5523_3 */ + AR5523_DEVICE_UG(0x0d8e, 0x7801), /* Conceptronic / AR5523_1 */ + AR5523_DEVICE_UX(0x0d8e, 0x7811), /* Conceptronic / AR5523_2 */ + AR5523_DEVICE_UX(0x2001, 0x3a00), /* Dlink / DWLAG132 */ + AR5523_DEVICE_UG(0x2001, 0x3a02), /* Dlink / DWLG132 */ + AR5523_DEVICE_UX(0x2001, 0x3a04), /* Dlink / DWLAG122 */ + AR5523_DEVICE_UG(0x1690, 0x0712), /* Gigaset / AR5523 */ + AR5523_DEVICE_UG(0x1690, 0x0710), /* Gigaset / SMCWUSBTG */ + AR5523_DEVICE_UG(0x129b, 0x160c), /* Gigaset / USB stick 108 + (CyberTAN Technology) */ + AR5523_DEVICE_UG(0x16ab, 0x7801), /* Globalsun / AR5523_1 */ + AR5523_DEVICE_UX(0x16ab, 0x7811), /* Globalsun / AR5523_2 */ + AR5523_DEVICE_UG(0x0d8e, 0x7802), /* Globalsun / AR5523_3 */ + AR5523_DEVICE_UX(0x0846, 0x4300), /* Netgear / WG111U */ + AR5523_DEVICE_UG(0x0846, 0x4250), /* Netgear / WG111T */ + AR5523_DEVICE_UG(0x0846, 0x5f00), /* Netgear / WPN111 */ + AR5523_DEVICE_UG(0x157e, 0x3006), /* Umedia / AR5523_1 */ + AR5523_DEVICE_UX(0x157e, 0x3205), /* Umedia / AR5523_2 */ + AR5523_DEVICE_UG(0x157e, 0x3006), /* Umedia / TEW444UBEU */ + AR5523_DEVICE_UG(0x1435, 0x0826), /* Wistronneweb / AR5523_1 */ + AR5523_DEVICE_UX(0x1435, 0x0828), /* Wistronneweb / AR5523_2 */ + AR5523_DEVICE_UG(0x0cde, 0x0012), /* Zcom / AR5523 */ + AR5523_DEVICE_UG(0x1385, 0x4250), /* Netgear3 / WG111T (2) */ + AR5523_DEVICE_UG(0x1385, 0x5f00), /* Netgear / WPN111 */ + AR5523_DEVICE_UG(0x1385, 0x5f02), /* Netgear / WPN111 */ + { } +}; +MODULE_DEVICE_TABLE(usb, ar5523_id_table); + +static struct usb_driver ar5523_driver = { + .name = "ar5523", + .id_table = ar5523_id_table, + .probe = ar5523_probe, + .disconnect = ar5523_disconnect, +}; + +module_usb_driver(ar5523_driver); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_FIRMWARE(AR5523_FIRMWARE_FILE); diff --git a/drivers/net/wireless/ath/ar5523/ar5523.h b/drivers/net/wireless/ath/ar5523/ar5523.h new file mode 100644 index 0000000..00c6fd3 --- /dev/null +++ b/drivers/net/wireless/ath/ar5523/ar5523.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2006 Damien Bergamini + * Copyright (c) 2006 Sam Leffler, Errno Consulting + * Copyright (c) 2007 Christoph Hellwig + * Copyright (c) 2008-2009 Weongyo Jeong + * Copyright (c) 2012 Pontus Fuchs + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define AR5523_FLAG_PRE_FIRMWARE (1 << 0) +#define AR5523_FLAG_ABG (1 << 1) + +#define AR5523_FIRMWARE_FILE "ar5523.bin" + +#define AR5523_CMD_TX_PIPE 0x01 +#define AR5523_DATA_TX_PIPE 0x02 +#define AR5523_CMD_RX_PIPE 0x81 +#define AR5523_DATA_RX_PIPE 0x82 + +#define ar5523_cmd_tx_pipe(dev) \ + usb_sndbulkpipe((dev), AR5523_CMD_TX_PIPE) +#define ar5523_data_tx_pipe(dev) \ + usb_sndbulkpipe((dev), AR5523_DATA_TX_PIPE) +#define ar5523_cmd_rx_pipe(dev) \ + usb_rcvbulkpipe((dev), AR5523_CMD_RX_PIPE) +#define ar5523_data_rx_pipe(dev) \ + usb_rcvbulkpipe((dev), AR5523_DATA_RX_PIPE) + +#define AR5523_DATA_TIMEOUT 10000 +#define AR5523_CMD_TIMEOUT 1000 + +#define AR5523_TX_DATA_COUNT 8 +#define AR5523_TX_DATA_RESTART_COUNT 2 +#define AR5523_RX_DATA_COUNT 16 +#define AR5523_RX_DATA_REFILL_COUNT 8 + +#define AR5523_CMD_ID 1 +#define AR5523_DATA_ID 2 + +#define AR5523_TX_WD_TIMEOUT (HZ * 2) +#define AR5523_FLUSH_TIMEOUT (HZ * 3) + +enum AR5523_flags { + AR5523_HW_UP, + AR5523_USB_DISCONNECTED, + AR5523_CONNECTED +}; + +struct ar5523_tx_cmd { + struct ar5523 *ar; + struct urb *urb_tx; + void *buf_tx; + void *odata; + int olen; + int flags; + int res; + struct completion done; +}; + +/* This struct is placed in tx_info->driver_data. It must not be larger + * than IEEE80211_TX_INFO_DRIVER_DATA_SIZE. + */ +struct ar5523_tx_data { + struct list_head list; + struct ar5523 *ar; + struct sk_buff *skb; + struct urb *urb; +}; + +struct ar5523_rx_data { + struct list_head list; + struct ar5523 *ar; + struct urb *urb; + struct sk_buff *skb; +}; + +struct ar5523 { + struct usb_device *dev; + struct ieee80211_hw *hw; + + unsigned long flags; + struct mutex mutex; + struct workqueue_struct *wq; + + struct ar5523_tx_cmd tx_cmd; + + struct delayed_work stat_work; + + struct timer_list tx_wd_timer; + struct work_struct tx_wd_work; + struct work_struct tx_work; + struct list_head tx_queue_pending; + struct list_head tx_queue_submitted; + spinlock_t tx_data_list_lock; + wait_queue_head_t tx_flush_waitq; + + /* Queued + Submitted TX frames */ + atomic_t tx_nr_total; + + /* Submitted TX frames */ + atomic_t tx_nr_pending; + + void *rx_cmd_buf; + struct urb *rx_cmd_urb; + + struct ar5523_rx_data rx_data[AR5523_RX_DATA_COUNT]; + spinlock_t rx_data_list_lock; + struct list_head rx_data_free; + struct list_head rx_data_used; + atomic_t rx_data_free_cnt; + + struct work_struct rx_refill_work; + + unsigned int rxbufsz; + u8 serial[16]; + + struct ieee80211_channel channels[14]; + struct ieee80211_rate rates[12]; + struct ieee80211_supported_band band; + struct ieee80211_vif *vif; +}; + +/* flags for sending firmware commands */ +#define AR5523_CMD_FLAG_READ (1 << 1) +#define AR5523_CMD_FLAG_MAGIC (1 << 2) + +#define ar5523_dbg(ar, format, arg...) \ + dev_dbg(&(ar)->dev->dev, format, ## arg) + +/* On USB hot-unplug there can be a lot of URBs in flight and they'll all + * fail. Instead of dealing with them in every possible place just surpress + * any messages on USB disconnect. + */ +#define ar5523_err(ar, format, arg...) \ +do { \ + if (!test_bit(AR5523_USB_DISCONNECTED, &ar->flags)) { \ + dev_err(&(ar)->dev->dev, format, ## arg); \ + } \ +} while (0) +#define ar5523_info(ar, format, arg...) \ + dev_info(&(ar)->dev->dev, format, ## arg) diff --git a/drivers/net/wireless/ath/ar5523/ar5523_hw.h b/drivers/net/wireless/ath/ar5523/ar5523_hw.h new file mode 100644 index 0000000..0fe2c80 --- /dev/null +++ b/drivers/net/wireless/ath/ar5523/ar5523_hw.h @@ -0,0 +1,431 @@ +/* + * Copyright (c) 2006 Damien Bergamini + * Copyright (c) 2006 Sam Leffler, Errno Consulting + * Copyright (c) 2007 Christoph Hellwig + * Copyright (c) 2008-2009 Weongyo Jeong + * Copyright (c) 2012 Pontus Fuchs + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* all fields are big endian */ +struct ar5523_fwblock { + __be32 flags; +#define AR5523_WRITE_BLOCK (1 << 4) + + __be32 len; +#define AR5523_MAX_FWBLOCK_SIZE 2048 + + __be32 total; + __be32 remain; + __be32 rxtotal; + __be32 pad[123]; +} __packed; + +#define AR5523_MAX_RXCMDSZ 1024 +#define AR5523_MAX_TXCMDSZ 1024 + +struct ar5523_cmd_hdr { + __be32 len; + __be32 code; +/* NB: these are defined for rev 1.5 firmware; rev 1.6 is different */ +/* messages from Host -> Target */ +#define WDCMSG_HOST_AVAILABLE 0x01 +#define WDCMSG_BIND 0x02 +#define WDCMSG_TARGET_RESET 0x03 +#define WDCMSG_TARGET_GET_CAPABILITY 0x04 +#define WDCMSG_TARGET_SET_CONFIG 0x05 +#define WDCMSG_TARGET_GET_STATUS 0x06 +#define WDCMSG_TARGET_GET_STATS 0x07 +#define WDCMSG_TARGET_START 0x08 +#define WDCMSG_TARGET_STOP 0x09 +#define WDCMSG_TARGET_ENABLE 0x0a +#define WDCMSG_TARGET_DISABLE 0x0b +#define WDCMSG_CREATE_CONNECTION 0x0c +#define WDCMSG_UPDATE_CONNECT_ATTR 0x0d +#define WDCMSG_DELETE_CONNECT 0x0e +#define WDCMSG_SEND 0x0f +#define WDCMSG_FLUSH 0x10 +/* messages from Target -> Host */ +#define WDCMSG_STATS_UPDATE 0x11 +#define WDCMSG_BMISS 0x12 +#define WDCMSG_DEVICE_AVAIL 0x13 +#define WDCMSG_SEND_COMPLETE 0x14 +#define WDCMSG_DATA_AVAIL 0x15 +#define WDCMSG_SET_PWR_MODE 0x16 +#define WDCMSG_BMISS_ACK 0x17 +#define WDCMSG_SET_LED_STEADY 0x18 +#define WDCMSG_SET_LED_BLINK 0x19 +/* more messages */ +#define WDCMSG_SETUP_BEACON_DESC 0x1a +#define WDCMSG_BEACON_INIT 0x1b +#define WDCMSG_RESET_KEY_CACHE 0x1c +#define WDCMSG_RESET_KEY_CACHE_ENTRY 0x1d +#define WDCMSG_SET_KEY_CACHE_ENTRY 0x1e +#define WDCMSG_SET_DECOMP_MASK 0x1f +#define WDCMSG_SET_REGULATORY_DOMAIN 0x20 +#define WDCMSG_SET_LED_STATE 0x21 +#define WDCMSG_WRITE_ASSOCID 0x22 +#define WDCMSG_SET_STA_BEACON_TIMERS 0x23 +#define WDCMSG_GET_TSF 0x24 +#define WDCMSG_RESET_TSF 0x25 +#define WDCMSG_SET_ADHOC_MODE 0x26 +#define WDCMSG_SET_BASIC_RATE 0x27 +#define WDCMSG_MIB_CONTROL 0x28 +#define WDCMSG_GET_CHANNEL_DATA 0x29 +#define WDCMSG_GET_CUR_RSSI 0x2a +#define WDCMSG_SET_ANTENNA_SWITCH 0x2b +#define WDCMSG_USE_SHORT_SLOT_TIME 0x2f +#define WDCMSG_SET_POWER_MODE 0x30 +#define WDCMSG_SETUP_PSPOLL_DESC 0x31 +#define WDCMSG_SET_RX_MULTICAST_FILTER 0x32 +#define WDCMSG_RX_FILTER 0x33 +#define WDCMSG_PER_CALIBRATION 0x34 +#define WDCMSG_RESET 0x35 +#define WDCMSG_DISABLE 0x36 +#define WDCMSG_PHY_DISABLE 0x37 +#define WDCMSG_SET_TX_POWER_LIMIT 0x38 +#define WDCMSG_SET_TX_QUEUE_PARAMS 0x39 +#define WDCMSG_SETUP_TX_QUEUE 0x3a +#define WDCMSG_RELEASE_TX_QUEUE 0x3b +#define WDCMSG_SET_DEFAULT_KEY 0x43 + + __u32 priv; /* driver private data, + don't care about endianess */ + __be32 magic; + __be32 reserved2[4]; +}; + +struct ar5523_cmd_host_available { + __be32 sw_ver_major; + __be32 sw_ver_minor; + __be32 sw_ver_patch; + __be32 sw_ver_build; +} __packed; + +#define ATH_SW_VER_MAJOR 1 +#define ATH_SW_VER_MINOR 5 +#define ATH_SW_VER_PATCH 0 +#define ATH_SW_VER_BUILD 9999 + +struct ar5523_chunk { + u8 seqnum; /* sequence number for ordering */ + u8 flags; +#define UATH_CFLAGS_FINAL 0x01 /* final chunk of a msg */ +#define UATH_CFLAGS_RXMSG 0x02 /* chunk contains rx completion */ +#define UATH_CFLAGS_DEBUG 0x04 /* for debugging */ + __be16 length; /* chunk size in bytes */ + /* chunk data follows */ +} __packed; + +/* + * Message format for a WDCMSG_DATA_AVAIL message from Target to Host. + */ +struct ar5523_rx_desc { + __be32 len; /* msg length including header */ + __be32 code; /* WDCMSG_DATA_AVAIL */ + __be32 gennum; /* generation number */ + __be32 status; /* start of RECEIVE_INFO */ +#define UATH_STATUS_OK 0 +#define UATH_STATUS_STOP_IN_PROGRESS 1 +#define UATH_STATUS_CRC_ERR 2 +#define UATH_STATUS_PHY_ERR 3 +#define UATH_STATUS_DECRYPT_CRC_ERR 4 +#define UATH_STATUS_DECRYPT_MIC_ERR 5 +#define UATH_STATUS_DECOMP_ERR 6 +#define UATH_STATUS_KEY_ERR 7 +#define UATH_STATUS_ERR 8 + __be32 tstamp_low; /* low-order 32-bits of rx timestamp */ + __be32 tstamp_high; /* high-order 32-bits of rx timestamp */ + __be32 framelen; /* frame length */ + __be32 rate; /* rx rate code */ + __be32 antenna; + __be32 rssi; + __be32 channel; + __be32 phyerror; + __be32 connix; /* key table ix for bss traffic */ + __be32 decrypterror; + __be32 keycachemiss; + __be32 pad; /* XXX? */ +} __packed; + +struct ar5523_tx_desc { + __be32 msglen; + u32 msgid; /* msg id (supplied by host) */ + __be32 type; /* opcode: WDMSG_SEND or WDCMSG_FLUSH */ + __be32 txqid; /* tx queue id and flags */ +#define UATH_TXQID_MASK 0x0f +#define UATH_TXQID_MINRATE 0x10 /* use min tx rate */ +#define UATH_TXQID_FF 0x20 /* content is fast frame */ + __be32 connid; /* tx connection id */ +#define UATH_ID_INVALID 0xffffffff /* for sending prior to connection */ + __be32 flags; /* non-zero if response desired */ +#define UATH_TX_NOTIFY (1 << 24) /* f/w will send a UATH_NOTIF_TX */ + __be32 buflen; /* payload length */ +} __packed; + + +#define AR5523_ID_BSS 2 +#define AR5523_ID_BROADCAST 0xffffffff + +/* structure for command UATH_CMD_WRITE_MAC */ +struct ar5523_write_mac { + __be32 reg; + __be32 len; + u8 data[32]; +} __packed; + +struct ar5523_cmd_rateset { + __u8 length; +#define AR5523_MAX_NRATES 32 + __u8 set[AR5523_MAX_NRATES]; +}; + +struct ar5523_cmd_set_associd { /* AR5523_WRITE_ASSOCID */ + __be32 defaultrateix; + __be32 associd; + __be32 timoffset; + __be32 turboprime; + __u8 bssid[6]; +} __packed; + +/* structure for command WDCMSG_RESET */ +struct ar5523_cmd_reset { + __be32 flags; /* channel flags */ +#define UATH_CHAN_TURBO 0x0100 +#define UATH_CHAN_CCK 0x0200 +#define UATH_CHAN_OFDM 0x0400 +#define UATH_CHAN_2GHZ 0x1000 +#define UATH_CHAN_5GHZ 0x2000 + __be32 freq; /* channel frequency */ + __be32 maxrdpower; + __be32 cfgctl; + __be32 twiceantennareduction; + __be32 channelchange; + __be32 keeprccontent; +} __packed; + +/* structure for command WDCMSG_SET_BASIC_RATE */ +struct ar5523_cmd_rates { + __be32 connid; + __be32 keeprccontent; + __be32 size; + struct ar5523_cmd_rateset rateset; +} __packed; + +enum { + WLAN_MODE_NONE = 0, + WLAN_MODE_11b, + WLAN_MODE_11a, + WLAN_MODE_11g, + WLAN_MODE_11a_TURBO, + WLAN_MODE_11g_TURBO, + WLAN_MODE_11a_TURBO_PRIME, + WLAN_MODE_11g_TURBO_PRIME, + WLAN_MODE_11a_XR, + WLAN_MODE_11g_XR, +}; + +struct ar5523_cmd_connection_attr { + __be32 longpreambleonly; + struct ar5523_cmd_rateset rateset; + __be32 wlanmode; +} __packed; + +/* structure for command AR5523_CREATE_CONNECTION */ +struct ar5523_cmd_create_connection { + __be32 connid; + __be32 bssid; + __be32 size; + struct ar5523_cmd_connection_attr connattr; +} __packed; + +struct ar5523_cmd_ledsteady { /* WDCMSG_SET_LED_STEADY */ + __be32 lednum; +#define UATH_LED_LINK 0 +#define UATH_LED_ACTIVITY 1 + __be32 ledmode; +#define UATH_LED_OFF 0 +#define UATH_LED_ON 1 +} __packed; + +struct ar5523_cmd_ledblink { /* WDCMSG_SET_LED_BLINK */ + __be32 lednum; + __be32 ledmode; + __be32 blinkrate; + __be32 slowmode; +} __packed; + +struct ar5523_cmd_ledstate { /* WDCMSG_SET_LED_STATE */ + __be32 connected; +} __packed; + +struct ar5523_cmd_txq_attr { + __be32 priority; + __be32 aifs; + __be32 logcwmin; + __be32 logcwmax; + __be32 bursttime; + __be32 mode; + __be32 qflags; +} __packed; + +struct ar5523_cmd_txq_setup { /* WDCMSG_SETUP_TX_QUEUE */ + __be32 qid; + __be32 len; + struct ar5523_cmd_txq_attr attr; +} __packed; + +struct ar5523_cmd_rx_filter { /* WDCMSG_RX_FILTER */ + __be32 bits; +#define UATH_FILTER_RX_UCAST 0x00000001 +#define UATH_FILTER_RX_MCAST 0x00000002 +#define UATH_FILTER_RX_BCAST 0x00000004 +#define UATH_FILTER_RX_CONTROL 0x00000008 +#define UATH_FILTER_RX_BEACON 0x00000010 /* beacon frames */ +#define UATH_FILTER_RX_PROM 0x00000020 /* promiscuous mode */ +#define UATH_FILTER_RX_PHY_ERR 0x00000040 /* phy errors */ +#define UATH_FILTER_RX_PHY_RADAR 0x00000080 /* radar phy errors */ +#define UATH_FILTER_RX_XR_POOL 0x00000400 /* XR group polls */ +#define UATH_FILTER_RX_PROBE_REQ 0x00000800 + __be32 op; +#define UATH_FILTER_OP_INIT 0x0 +#define UATH_FILTER_OP_SET 0x1 +#define UATH_FILTER_OP_CLEAR 0x2 +#define UATH_FILTER_OP_TEMP 0x3 +#define UATH_FILTER_OP_RESTORE 0x4 +} __packed; + +enum { + CFG_NONE, /* Sentinal to indicate "no config" */ + CFG_REG_DOMAIN, /* Regulatory Domain */ + CFG_RATE_CONTROL_ENABLE, + CFG_DEF_XMIT_DATA_RATE, /* NB: if rate control is not enabled */ + CFG_HW_TX_RETRIES, + CFG_SW_TX_RETRIES, + CFG_SLOW_CLOCK_ENABLE, + CFG_COMP_PROC, + CFG_USER_RTS_THRESHOLD, + CFG_XR2NORM_RATE_THRESHOLD, + CFG_XRMODE_SWITCH_COUNT, + CFG_PROTECTION_TYPE, + CFG_BURST_SEQ_THRESHOLD, + CFG_ABOLT, + CFG_IQ_LOG_COUNT_MAX, + CFG_MODE_CTS, + CFG_WME_ENABLED, + CFG_GPRS_CBR_PERIOD, + CFG_SERVICE_TYPE, + /* MAC Address to use. Overrides EEPROM */ + CFG_MAC_ADDR, + CFG_DEBUG_EAR, + CFG_INIT_REGS, + /* An ID for use in error & debug messages */ + CFG_DEBUG_ID, + CFG_COMP_WIN_SZ, + CFG_DIVERSITY_CTL, + CFG_TP_SCALE, + CFG_TPC_HALF_DBM5, + CFG_TPC_HALF_DBM2, + CFG_OVERRD_TX_POWER, + CFG_USE_32KHZ_CLOCK, + CFG_GMODE_PROTECTION, + CFG_GMODE_PROTECT_RATE_INDEX, + CFG_GMODE_NON_ERP_PREAMBLE, + CFG_WDC_TRANSPORT_CHUNK_SIZE, +}; + +enum { + /* Sentinal to indicate "no capability" */ + CAP_NONE, + CAP_ALL, /* ALL capabilities */ + CAP_TARGET_VERSION, + CAP_TARGET_REVISION, + CAP_MAC_VERSION, + CAP_MAC_REVISION, + CAP_PHY_REVISION, + CAP_ANALOG_5GHz_REVISION, + CAP_ANALOG_2GHz_REVISION, + /* Target supports WDC message debug features */ + CAP_DEBUG_WDCMSG_SUPPORT, + + CAP_REG_DOMAIN, + CAP_COUNTRY_CODE, + CAP_REG_CAP_BITS, + + CAP_WIRELESS_MODES, + CAP_CHAN_SPREAD_SUPPORT, + CAP_SLEEP_AFTER_BEACON_BROKEN, + CAP_COMPRESS_SUPPORT, + CAP_BURST_SUPPORT, + CAP_FAST_FRAMES_SUPPORT, + CAP_CHAP_TUNING_SUPPORT, + CAP_TURBOG_SUPPORT, + CAP_TURBO_PRIME_SUPPORT, + CAP_DEVICE_TYPE, + CAP_XR_SUPPORT, + CAP_WME_SUPPORT, + CAP_TOTAL_QUEUES, + CAP_CONNECTION_ID_MAX, /* Should absorb CAP_KEY_CACHE_SIZE */ + + CAP_LOW_5GHZ_CHAN, + CAP_HIGH_5GHZ_CHAN, + CAP_LOW_2GHZ_CHAN, + CAP_HIGH_2GHZ_CHAN, + + CAP_MIC_AES_CCM, + CAP_MIC_CKIP, + CAP_MIC_TKIP, + CAP_MIC_TKIP_WME, + CAP_CIPHER_AES_CCM, + CAP_CIPHER_CKIP, + CAP_CIPHER_TKIP, + + CAP_TWICE_ANTENNAGAIN_5G, + CAP_TWICE_ANTENNAGAIN_2G, +}; + +enum { + ST_NONE, /* Sentinal to indicate "no status" */ + ST_ALL, + ST_SERVICE_TYPE, + ST_WLAN_MODE, + ST_FREQ, + ST_BAND, + ST_LAST_RSSI, + ST_PS_FRAMES_DROPPED, + ST_CACHED_DEF_ANT, + ST_COUNT_OTHER_RX_ANT, + ST_USE_FAST_DIVERSITY, + ST_MAC_ADDR, + ST_RX_GENERATION_NUM, + ST_TX_QUEUE_DEPTH, + ST_SERIAL_NUMBER, + ST_WDC_TRANSPORT_CHUNK_SIZE, +}; + +enum { + TARGET_DEVICE_AWAKE, + TARGET_DEVICE_SLEEP, + TARGET_DEVICE_PWRDN, + TARGET_DEVICE_PWRSAVE, + TARGET_DEVICE_SUSPEND, + TARGET_DEVICE_RESUME, +}; + +/* this is in net/ieee80211.h, but that conflicts with the mac80211 headers */ +#define IEEE80211_2ADDR_LEN 16 + +#define AR5523_MIN_RXBUFSZ \ + (((sizeof(__be32) + IEEE80211_2ADDR_LEN + \ + sizeof(struct ar5523_rx_desc)) + 3) & ~3) diff --git a/drivers/net/wireless/ath/ath.h b/drivers/net/wireless/ath/ath.h new file mode 100644 index 0000000..8cc84d1 --- /dev/null +++ b/drivers/net/wireless/ath/ath.h @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2008-2009 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef ATH_H +#define ATH_H + +#include +#include +#include +#include + +/* + * The key cache is used for h/w cipher state and also for + * tracking station state such as the current tx antenna. + * We also setup a mapping table between key cache slot indices + * and station state to short-circuit node lookups on rx. + * Different parts have different size key caches. We handle + * up to ATH_KEYMAX entries (could dynamically allocate state). + */ +#define ATH_KEYMAX 128 /* max key cache size we handle */ + +static const u8 ath_bcast_mac[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + +struct ath_ani { + bool caldone; + unsigned int longcal_timer; + unsigned int shortcal_timer; + unsigned int resetcal_timer; + unsigned int checkani_timer; + struct timer_list timer; +}; + +struct ath_cycle_counters { + u32 cycles; + u32 rx_busy; + u32 rx_frame; + u32 tx_frame; +}; + +enum ath_device_state { + ATH_HW_UNAVAILABLE, + ATH_HW_INITIALIZED, +}; + +enum ath_bus_type { + ATH_PCI, + ATH_AHB, + ATH_USB, +}; + +struct reg_dmn_pair_mapping { + u16 regDmnEnum; + u16 reg_5ghz_ctl; + u16 reg_2ghz_ctl; +}; + +struct ath_regulatory { + char alpha2[2]; + u16 country_code; + u16 max_power_level; + u16 current_rd; + int16_t power_limit; + struct reg_dmn_pair_mapping *regpair; +}; + +enum ath_crypt_caps { + ATH_CRYPT_CAP_CIPHER_AESCCM = BIT(0), + ATH_CRYPT_CAP_MIC_COMBINED = BIT(1), +}; + +struct ath_keyval { + u8 kv_type; + u8 kv_pad; + u16 kv_len; + u8 kv_val[16]; /* TK */ + u8 kv_mic[8]; /* Michael MIC key */ + u8 kv_txmic[8]; /* Michael MIC TX key (used only if the hardware + * supports both MIC keys in the same key cache entry; + * in that case, kv_mic is the RX key) */ +}; + +enum ath_cipher { + ATH_CIPHER_WEP = 0, + ATH_CIPHER_AES_OCB = 1, + ATH_CIPHER_AES_CCM = 2, + ATH_CIPHER_CKIP = 3, + ATH_CIPHER_TKIP = 4, + ATH_CIPHER_CLR = 5, + ATH_CIPHER_MIC = 127 +}; + +/** + * struct ath_ops - Register read/write operations + * + * @read: Register read + * @multi_read: Multiple register read + * @write: Register write + * @enable_write_buffer: Enable multiple register writes + * @write_flush: flush buffered register writes and disable buffering + */ +struct ath_ops { + unsigned int (*read)(void *, u32 reg_offset); + void (*multi_read)(void *, u32 *addr, u32 *val, u16 count); + void (*write)(void *, u32 val, u32 reg_offset); + void (*enable_write_buffer)(void *); + void (*write_flush) (void *); + u32 (*rmw)(void *, u32 reg_offset, u32 set, u32 clr); +}; + +struct ath_common; +struct ath_bus_ops; + +struct ath_common { + void *ah; + void *priv; + struct ieee80211_hw *hw; + int debug_mask; + enum ath_device_state state; + + struct ath_ani ani; + + u16 cachelsz; + u16 curaid; + u8 macaddr[ETH_ALEN]; + u8 curbssid[ETH_ALEN]; + u8 bssidmask[ETH_ALEN]; + + u32 rx_bufsize; + + u32 keymax; + DECLARE_BITMAP(keymap, ATH_KEYMAX); + DECLARE_BITMAP(tkip_keymap, ATH_KEYMAX); + DECLARE_BITMAP(ccmp_keymap, ATH_KEYMAX); + enum ath_crypt_caps crypt_caps; + + unsigned int clockrate; + + spinlock_t cc_lock; + struct ath_cycle_counters cc_ani; + struct ath_cycle_counters cc_survey; + + struct ath_regulatory regulatory; + struct ath_regulatory reg_world_copy; + const struct ath_ops *ops; + const struct ath_bus_ops *bus_ops; + + bool btcoex_enabled; + bool disable_ani; + bool antenna_diversity; +}; + +struct sk_buff *ath_rxbuf_alloc(struct ath_common *common, + u32 len, + gfp_t gfp_mask); + +void ath_hw_setbssidmask(struct ath_common *common); +void ath_key_delete(struct ath_common *common, struct ieee80211_key_conf *key); +int ath_key_config(struct ath_common *common, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key); +bool ath_hw_keyreset(struct ath_common *common, u16 entry); +void ath_hw_cycle_counters_update(struct ath_common *common); +int32_t ath_hw_get_listen_time(struct ath_common *common); + +__printf(3, 4) +void ath_printk(const char *level, const struct ath_common *common, + const char *fmt, ...); + +#define ath_emerg(common, fmt, ...) \ + ath_printk(KERN_EMERG, common, fmt, ##__VA_ARGS__) +#define ath_alert(common, fmt, ...) \ + ath_printk(KERN_ALERT, common, fmt, ##__VA_ARGS__) +#define ath_crit(common, fmt, ...) \ + ath_printk(KERN_CRIT, common, fmt, ##__VA_ARGS__) +#define ath_err(common, fmt, ...) \ + ath_printk(KERN_ERR, common, fmt, ##__VA_ARGS__) +#define ath_warn(common, fmt, ...) \ + ath_printk(KERN_WARNING, common, fmt, ##__VA_ARGS__) +#define ath_notice(common, fmt, ...) \ + ath_printk(KERN_NOTICE, common, fmt, ##__VA_ARGS__) +#define ath_info(common, fmt, ...) \ + ath_printk(KERN_INFO, common, fmt, ##__VA_ARGS__) + +/** + * enum ath_debug_level - atheros wireless debug level + * + * @ATH_DBG_RESET: reset processing + * @ATH_DBG_QUEUE: hardware queue management + * @ATH_DBG_EEPROM: eeprom processing + * @ATH_DBG_CALIBRATE: periodic calibration + * @ATH_DBG_INTERRUPT: interrupt processing + * @ATH_DBG_REGULATORY: regulatory processing + * @ATH_DBG_ANI: adaptive noise immunitive processing + * @ATH_DBG_XMIT: basic xmit operation + * @ATH_DBG_BEACON: beacon handling + * @ATH_DBG_CONFIG: configuration of the hardware + * @ATH_DBG_FATAL: fatal errors, this is the default, DBG_DEFAULT + * @ATH_DBG_PS: power save processing + * @ATH_DBG_HWTIMER: hardware timer handling + * @ATH_DBG_BTCOEX: bluetooth coexistance + * @ATH_DBG_BSTUCK: stuck beacons + * @ATH_DBG_MCI: Message Coexistence Interface, a private protocol + * used exclusively for WLAN-BT coexistence starting from + * AR9462. + * @ATH_DBG_DFS: radar datection + * @ATH_DBG_WOW: Wake on Wireless + * @ATH_DBG_ANY: enable all debugging + * + * The debug level is used to control the amount and type of debugging output + * we want to see. Each driver has its own method for enabling debugging and + * modifying debug level states -- but this is typically done through a + * module parameter 'debug' along with a respective 'debug' debugfs file + * entry. + */ +enum ATH_DEBUG { + ATH_DBG_RESET = 0x00000001, + ATH_DBG_QUEUE = 0x00000002, + ATH_DBG_EEPROM = 0x00000004, + ATH_DBG_CALIBRATE = 0x00000008, + ATH_DBG_INTERRUPT = 0x00000010, + ATH_DBG_REGULATORY = 0x00000020, + ATH_DBG_ANI = 0x00000040, + ATH_DBG_XMIT = 0x00000080, + ATH_DBG_BEACON = 0x00000100, + ATH_DBG_CONFIG = 0x00000200, + ATH_DBG_FATAL = 0x00000400, + ATH_DBG_PS = 0x00000800, + ATH_DBG_HWTIMER = 0x00001000, + ATH_DBG_BTCOEX = 0x00002000, + ATH_DBG_WMI = 0x00004000, + ATH_DBG_BSTUCK = 0x00008000, + ATH_DBG_MCI = 0x00010000, + ATH_DBG_DFS = 0x00020000, + ATH_DBG_WOW = 0x00040000, + ATH_DBG_ANY = 0xffffffff +}; + +#define ATH_DBG_DEFAULT (ATH_DBG_FATAL) + +#ifdef CPTCFG_ATH_DEBUG + +#define ath_dbg(common, dbg_mask, fmt, ...) \ +do { \ + if ((common)->debug_mask & ATH_DBG_##dbg_mask) \ + ath_printk(KERN_DEBUG, common, fmt, ##__VA_ARGS__); \ +} while (0) + +#define ATH_DBG_WARN(foo, arg...) WARN(foo, arg) +#define ATH_DBG_WARN_ON_ONCE(foo) WARN_ON_ONCE(foo) + +#else + +static inline __attribute__ ((format (printf, 3, 4))) +void _ath_dbg(struct ath_common *common, enum ATH_DEBUG dbg_mask, + const char *fmt, ...) +{ +} +#define ath_dbg(common, dbg_mask, fmt, ...) \ + _ath_dbg(common, ATH_DBG_##dbg_mask, fmt, ##__VA_ARGS__) + +#define ATH_DBG_WARN(foo, arg...) do {} while (0) +#define ATH_DBG_WARN_ON_ONCE(foo) ({ \ + int __ret_warn_once = !!(foo); \ + unlikely(__ret_warn_once); \ +}) + +#endif /* CPTCFG_ATH_DEBUG */ + +/** Returns string describing opmode, or NULL if unknown mode. */ +#ifdef CPTCFG_ATH_DEBUG +const char *ath_opmode_to_string(enum nl80211_iftype opmode); +#else +static inline const char *ath_opmode_to_string(enum nl80211_iftype opmode) +{ + return "UNKNOWN"; +} +#endif + +#endif /* ATH_H */ diff --git a/drivers/net/wireless/ath/ath5k/Kconfig b/drivers/net/wireless/ath/ath5k/Kconfig new file mode 100644 index 0000000..7a8b3ef --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/Kconfig @@ -0,0 +1,76 @@ +config ATH5K + tristate "Atheros 5xxx wireless cards support" + depends on m + depends on (PCI || ATHEROS_AR231X) && MAC80211 + select ATH_COMMON + select MAC80211_LEDS + select BACKPORT_LEDS_CLASS + select BACKPORT_NEW_LEDS + select BACKPORT_AVERAGE + select ATH5K_AHB if (ATHEROS_AR231X && !PCI) + select ATH5K_PCI if (!ATHEROS_AR231X && PCI) + ---help--- + This module adds support for wireless adapters based on + Atheros 5xxx chipset. + + Currently the following chip versions are supported: + + MAC: AR5211 AR5212 + PHY: RF5111/2111 RF5112/2112 RF5413/2413 + + This driver uses the kernel's mac80211 subsystem. + + If you choose to build a module, it'll be called ath5k. Say M if + unsure. + +config ATH5K_DEBUG + bool "Atheros 5xxx debugging" + depends on ATH5K + ---help--- + Atheros 5xxx debugging messages. + + Say Y, if and you will get debug options for ath5k. + To use this, you need to mount debugfs: + + mount -t debugfs debug /sys/kernel/debug + + You will get access to files under: + /sys/kernel/debug/ath5k/phy0/ + + To enable debug, pass the debug level to the debug module + parameter. For example: + + modprobe ath5k debug=0x00000400 + +config ATH5K_TRACER + bool "Atheros 5xxx tracer" + depends on ATH5K + depends on EVENT_TRACING + ---help--- + Say Y here to enable tracepoints for the ath5k driver + using the kernel tracing infrastructure. Select this + option if you are interested in debugging the driver. + + If unsure, say N. + +config ATH5K_AHB + bool "Atheros 5xxx AHB bus support" + depends on (ATHEROS_AR231X && !PCI) + ---help--- + This adds support for WiSoC type chipsets of the 5xxx Atheros + family. + +config ATH5K_PCI + bool "Atheros 5xxx PCI bus support" + depends on (!ATHEROS_AR231X && PCI) + ---help--- + This adds support for PCI type chipsets of the 5xxx Atheros + family. + +config ATH5K_TEST_CHANNELS + bool "Enables testing channels on ath5k" + depends on ATH5K && CFG80211_CERTIFICATION_ONUS + ---help--- + This enables non-standard IEEE 802.11 channels on ath5k, which + can be used for research purposes. This option should be disabled + unless doing research. diff --git a/drivers/net/wireless/ath/ath5k/Makefile b/drivers/net/wireless/ath/ath5k/Makefile new file mode 100644 index 0000000..32a0f67 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/Makefile @@ -0,0 +1,22 @@ +ath5k-y += caps.o +ath5k-y += initvals.o +ath5k-y += eeprom.o +ath5k-y += gpio.o +ath5k-y += desc.o +ath5k-y += dma.o +ath5k-y += qcu.o +ath5k-y += pcu.o +ath5k-y += phy.o +ath5k-y += reset.o +ath5k-y += attach.o +ath5k-y += base.o +CFLAGS_base.o += -I$(src) +ath5k-y += led.o +ath5k-y += rfkill.o +ath5k-y += ani.o +ath5k-y += sysfs.o +ath5k-y += mac80211-ops.o +ath5k-$(CPTCFG_ATH5K_DEBUG) += debug.o +ath5k-$(CPTCFG_ATH5K_AHB) += ahb.o +ath5k-$(CPTCFG_ATH5K_PCI) += pci.o +obj-$(CPTCFG_ATH5K) += ath5k.o diff --git a/drivers/net/wireless/ath/ath5k/ahb.c b/drivers/net/wireless/ath/ath5k/ahb.c new file mode 100644 index 0000000..8e8bcc7 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/ahb.c @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2008-2009 Atheros Communications Inc. + * Copyright (c) 2009 Gabor Juhos + * Copyright (c) 2009 Imre Kaloz + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include "ath5k.h" +#include "debug.h" +#include "base.h" +#include "reg.h" + +/* return bus cachesize in 4B word units */ +static void ath5k_ahb_read_cachesize(struct ath_common *common, int *csz) +{ + *csz = L1_CACHE_BYTES >> 2; +} + +static bool +ath5k_ahb_eeprom_read(struct ath_common *common, u32 off, u16 *data) +{ + struct ath5k_hw *ah = common->priv; + struct platform_device *pdev = to_platform_device(ah->dev); + struct ar231x_board_config *bcfg = pdev->dev.platform_data; + u16 *eeprom, *eeprom_end; + + + + bcfg = pdev->dev.platform_data; + eeprom = (u16 *) bcfg->radio; + eeprom_end = ((void *) bcfg->config) + BOARD_CONFIG_BUFSZ; + + eeprom += off; + if (eeprom > eeprom_end) + return false; + + *data = *eeprom; + return true; +} + +int ath5k_hw_read_srev(struct ath5k_hw *ah) +{ + struct platform_device *pdev = to_platform_device(ah->dev); + struct ar231x_board_config *bcfg = pdev->dev.platform_data; + ah->ah_mac_srev = bcfg->devid; + return 0; +} + +static int ath5k_ahb_eeprom_read_mac(struct ath5k_hw *ah, u8 *mac) +{ + struct platform_device *pdev = to_platform_device(ah->dev); + struct ar231x_board_config *bcfg = pdev->dev.platform_data; + u8 *cfg_mac; + + if (to_platform_device(ah->dev)->id == 0) + cfg_mac = bcfg->config->wlan0_mac; + else + cfg_mac = bcfg->config->wlan1_mac; + + memcpy(mac, cfg_mac, ETH_ALEN); + return 0; +} + +static const struct ath_bus_ops ath_ahb_bus_ops = { + .ath_bus_type = ATH_AHB, + .read_cachesize = ath5k_ahb_read_cachesize, + .eeprom_read = ath5k_ahb_eeprom_read, + .eeprom_read_mac = ath5k_ahb_eeprom_read_mac, +}; + +/*Initialization*/ +static int ath_ahb_probe(struct platform_device *pdev) +{ + struct ar231x_board_config *bcfg = pdev->dev.platform_data; + struct ath5k_hw *ah; + struct ieee80211_hw *hw; + struct resource *res; + void __iomem *mem; + int irq; + int ret = 0; + u32 reg; + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "no platform data specified\n"); + ret = -EINVAL; + goto err_out; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "no memory resource found\n"); + ret = -ENXIO; + goto err_out; + } + + mem = ioremap_nocache(res->start, resource_size(res)); + if (mem == NULL) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err_out; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + dev_err(&pdev->dev, "no IRQ resource found\n"); + ret = -ENXIO; + goto err_iounmap; + } + + irq = res->start; + + hw = ieee80211_alloc_hw(sizeof(struct ath5k_hw), &ath5k_hw_ops); + if (hw == NULL) { + dev_err(&pdev->dev, "no memory for ieee80211_hw\n"); + ret = -ENOMEM; + goto err_iounmap; + } + + ah = hw->priv; + ah->hw = hw; + ah->dev = &pdev->dev; + ah->iobase = mem; + ah->irq = irq; + ah->devid = bcfg->devid; + + if (bcfg->devid >= AR5K_SREV_AR2315_R6) { + /* Enable WMAC AHB arbitration */ + reg = ioread32((void __iomem *) AR5K_AR2315_AHB_ARB_CTL); + reg |= AR5K_AR2315_AHB_ARB_CTL_WLAN; + iowrite32(reg, (void __iomem *) AR5K_AR2315_AHB_ARB_CTL); + + /* Enable global WMAC swapping */ + reg = ioread32((void __iomem *) AR5K_AR2315_BYTESWAP); + reg |= AR5K_AR2315_BYTESWAP_WMAC; + iowrite32(reg, (void __iomem *) AR5K_AR2315_BYTESWAP); + } else { + /* Enable WMAC DMA access (assuming 5312 or 231x*/ + /* TODO: check other platforms */ + reg = ioread32((void __iomem *) AR5K_AR5312_ENABLE); + if (to_platform_device(ah->dev)->id == 0) + reg |= AR5K_AR5312_ENABLE_WLAN0; + else + reg |= AR5K_AR5312_ENABLE_WLAN1; + iowrite32(reg, (void __iomem *) AR5K_AR5312_ENABLE); + + /* + * On a dual-band AR5312, the multiband radio is only + * used as pass-through. Disable 2 GHz support in the + * driver for it + */ + if (to_platform_device(ah->dev)->id == 0 && + (bcfg->config->flags & (BD_WLAN0 | BD_WLAN1)) == + (BD_WLAN1 | BD_WLAN0)) + ah->ah_capabilities.cap_needs_2GHz_ovr = true; + else + ah->ah_capabilities.cap_needs_2GHz_ovr = false; + } + + ret = ath5k_init_ah(ah, &ath_ahb_bus_ops); + if (ret != 0) { + dev_err(&pdev->dev, "failed to attach device, err=%d\n", ret); + ret = -ENODEV; + goto err_free_hw; + } + + platform_set_drvdata(pdev, hw); + + return 0; + + err_free_hw: + ieee80211_free_hw(hw); + platform_set_drvdata(pdev, NULL); + err_iounmap: + iounmap(mem); + err_out: + return ret; +} + +static int ath_ahb_remove(struct platform_device *pdev) +{ + struct ar231x_board_config *bcfg = pdev->dev.platform_data; + struct ieee80211_hw *hw = platform_get_drvdata(pdev); + struct ath5k_hw *ah; + u32 reg; + + if (!hw) + return 0; + + ah = hw->priv; + + if (bcfg->devid >= AR5K_SREV_AR2315_R6) { + /* Disable WMAC AHB arbitration */ + reg = ioread32((void __iomem *) AR5K_AR2315_AHB_ARB_CTL); + reg &= ~AR5K_AR2315_AHB_ARB_CTL_WLAN; + iowrite32(reg, (void __iomem *) AR5K_AR2315_AHB_ARB_CTL); + } else { + /*Stop DMA access */ + reg = ioread32((void __iomem *) AR5K_AR5312_ENABLE); + if (to_platform_device(ah->dev)->id == 0) + reg &= ~AR5K_AR5312_ENABLE_WLAN0; + else + reg &= ~AR5K_AR5312_ENABLE_WLAN1; + iowrite32(reg, (void __iomem *) AR5K_AR5312_ENABLE); + } + + ath5k_deinit_ah(ah); + iounmap(ah->iobase); + platform_set_drvdata(pdev, NULL); + ieee80211_free_hw(hw); + + return 0; +} + +static struct platform_driver ath_ahb_driver = { + .probe = ath_ahb_probe, + .remove = ath_ahb_remove, + .driver = { + .name = "ar231x-wmac", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(ath_ahb_driver); diff --git a/drivers/net/wireless/ath/ath5k/ani.c b/drivers/net/wireless/ath/ath5k/ani.c new file mode 100644 index 0000000..cc52deb --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/ani.c @@ -0,0 +1,754 @@ +/* + * Copyright (C) 2010 Bruno Randolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "ath5k.h" +#include "reg.h" +#include "debug.h" +#include "ani.h" + +/** + * DOC: Basic ANI Operation + * + * Adaptive Noise Immunity (ANI) controls five noise immunity parameters + * depending on the amount of interference in the environment, increasing + * or reducing sensitivity as necessary. + * + * The parameters are: + * + * - "noise immunity" + * + * - "spur immunity" + * + * - "firstep level" + * + * - "OFDM weak signal detection" + * + * - "CCK weak signal detection" + * + * Basically we look at the amount of ODFM and CCK timing errors we get and then + * raise or lower immunity accordingly by setting one or more of these + * parameters. + * + * Newer chipsets have PHY error counters in hardware which will generate a MIB + * interrupt when they overflow. Older hardware has too enable PHY error frames + * by setting a RX flag and then count every single PHY error. When a specified + * threshold of errors has been reached we will raise immunity. + * Also we regularly check the amount of errors and lower or raise immunity as + * necessary. + */ + + +/***********************\ +* ANI parameter control * +\***********************/ + +/** + * ath5k_ani_set_noise_immunity_level() - Set noise immunity level + * @ah: The &struct ath5k_hw + * @level: level between 0 and @ATH5K_ANI_MAX_NOISE_IMM_LVL + */ +void +ath5k_ani_set_noise_immunity_level(struct ath5k_hw *ah, int level) +{ + /* TODO: + * ANI documents suggest the following five levels to use, but the HAL + * and ath9k use only the last two levels, making this + * essentially an on/off option. There *may* be a reason for this (???), + * so i stick with the HAL version for now... + */ +#if 0 + static const s8 lo[] = { -52, -56, -60, -64, -70 }; + static const s8 hi[] = { -18, -18, -16, -14, -12 }; + static const s8 sz[] = { -34, -41, -48, -55, -62 }; + static const s8 fr[] = { -70, -72, -75, -78, -80 }; +#else + static const s8 lo[] = { -64, -70 }; + static const s8 hi[] = { -14, -12 }; + static const s8 sz[] = { -55, -62 }; + static const s8 fr[] = { -78, -80 }; +#endif + if (level < 0 || level >= ARRAY_SIZE(sz)) { + ATH5K_ERR(ah, "noise immunity level %d out of range", + level); + return; + } + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_DESIRED_SIZE, + AR5K_PHY_DESIRED_SIZE_TOT, sz[level]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_AGCCOARSE, + AR5K_PHY_AGCCOARSE_LO, lo[level]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_AGCCOARSE, + AR5K_PHY_AGCCOARSE_HI, hi[level]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_SIG, + AR5K_PHY_SIG_FIRPWR, fr[level]); + + ah->ani_state.noise_imm_level = level; + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "new level %d", level); +} + +/** + * ath5k_ani_set_spur_immunity_level() - Set spur immunity level + * @ah: The &struct ath5k_hw + * @level: level between 0 and @max_spur_level (the maximum level is dependent + * on the chip revision). + */ +void +ath5k_ani_set_spur_immunity_level(struct ath5k_hw *ah, int level) +{ + static const int val[] = { 2, 4, 6, 8, 10, 12, 14, 16 }; + + if (level < 0 || level >= ARRAY_SIZE(val) || + level > ah->ani_state.max_spur_level) { + ATH5K_ERR(ah, "spur immunity level %d out of range", + level); + return; + } + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_OFDM_SELFCORR, + AR5K_PHY_OFDM_SELFCORR_CYPWR_THR1, val[level]); + + ah->ani_state.spur_level = level; + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "new level %d", level); +} + +/** + * ath5k_ani_set_firstep_level() - Set "firstep" level + * @ah: The &struct ath5k_hw + * @level: level between 0 and @ATH5K_ANI_MAX_FIRSTEP_LVL + */ +void +ath5k_ani_set_firstep_level(struct ath5k_hw *ah, int level) +{ + static const int val[] = { 0, 4, 8 }; + + if (level < 0 || level >= ARRAY_SIZE(val)) { + ATH5K_ERR(ah, "firstep level %d out of range", level); + return; + } + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_SIG, + AR5K_PHY_SIG_FIRSTEP, val[level]); + + ah->ani_state.firstep_level = level; + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "new level %d", level); +} + +/** + * ath5k_ani_set_ofdm_weak_signal_detection() - Set OFDM weak signal detection + * @ah: The &struct ath5k_hw + * @on: turn on or off + */ +void +ath5k_ani_set_ofdm_weak_signal_detection(struct ath5k_hw *ah, bool on) +{ + static const int m1l[] = { 127, 50 }; + static const int m2l[] = { 127, 40 }; + static const int m1[] = { 127, 0x4d }; + static const int m2[] = { 127, 0x40 }; + static const int m2cnt[] = { 31, 16 }; + static const int m2lcnt[] = { 63, 48 }; + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR, + AR5K_PHY_WEAK_OFDM_LOW_THR_M1, m1l[on]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR, + AR5K_PHY_WEAK_OFDM_LOW_THR_M2, m2l[on]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_HIGH_THR, + AR5K_PHY_WEAK_OFDM_HIGH_THR_M1, m1[on]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_HIGH_THR, + AR5K_PHY_WEAK_OFDM_HIGH_THR_M2, m2[on]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_HIGH_THR, + AR5K_PHY_WEAK_OFDM_HIGH_THR_M2_COUNT, m2cnt[on]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR, + AR5K_PHY_WEAK_OFDM_LOW_THR_M2_COUNT, m2lcnt[on]); + + if (on) + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR, + AR5K_PHY_WEAK_OFDM_LOW_THR_SELFCOR_EN); + else + AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_WEAK_OFDM_LOW_THR, + AR5K_PHY_WEAK_OFDM_LOW_THR_SELFCOR_EN); + + ah->ani_state.ofdm_weak_sig = on; + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "turned %s", + on ? "on" : "off"); +} + +/** + * ath5k_ani_set_cck_weak_signal_detection() - Set CCK weak signal detection + * @ah: The &struct ath5k_hw + * @on: turn on or off + */ +void +ath5k_ani_set_cck_weak_signal_detection(struct ath5k_hw *ah, bool on) +{ + static const int val[] = { 8, 6 }; + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_CCK_CROSSCORR, + AR5K_PHY_CCK_CROSSCORR_WEAK_SIG_THR, val[on]); + ah->ani_state.cck_weak_sig = on; + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "turned %s", + on ? "on" : "off"); +} + + +/***************\ +* ANI algorithm * +\***************/ + +/** + * ath5k_ani_raise_immunity() - Increase noise immunity + * @ah: The &struct ath5k_hw + * @as: The &struct ath5k_ani_state + * @ofdm_trigger: If this is true we are called because of too many OFDM errors, + * the algorithm will tune more parameters then. + * + * Try to raise noise immunity (=decrease sensitivity) in several steps + * depending on the average RSSI of the beacons we received. + */ +static void +ath5k_ani_raise_immunity(struct ath5k_hw *ah, struct ath5k_ani_state *as, + bool ofdm_trigger) +{ + int rssi = ewma_read(&ah->ah_beacon_rssi_avg); + + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "raise immunity (%s)", + ofdm_trigger ? "ODFM" : "CCK"); + + /* first: raise noise immunity */ + if (as->noise_imm_level < ATH5K_ANI_MAX_NOISE_IMM_LVL) { + ath5k_ani_set_noise_immunity_level(ah, as->noise_imm_level + 1); + return; + } + + /* only OFDM: raise spur immunity level */ + if (ofdm_trigger && + as->spur_level < ah->ani_state.max_spur_level) { + ath5k_ani_set_spur_immunity_level(ah, as->spur_level + 1); + return; + } + + /* AP mode */ + if (ah->opmode == NL80211_IFTYPE_AP) { + if (as->firstep_level < ATH5K_ANI_MAX_FIRSTEP_LVL) + ath5k_ani_set_firstep_level(ah, as->firstep_level + 1); + return; + } + + /* STA and IBSS mode */ + + /* TODO: for IBSS mode it would be better to keep a beacon RSSI average + * per each neighbour node and use the minimum of these, to make sure we + * don't shut out a remote node by raising immunity too high. */ + + if (rssi > ATH5K_ANI_RSSI_THR_HIGH) { + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, + "beacon RSSI high"); + /* only OFDM: beacon RSSI is high, we can disable ODFM weak + * signal detection */ + if (ofdm_trigger && as->ofdm_weak_sig) { + ath5k_ani_set_ofdm_weak_signal_detection(ah, false); + ath5k_ani_set_spur_immunity_level(ah, 0); + return; + } + /* as a last resort or CCK: raise firstep level */ + if (as->firstep_level < ATH5K_ANI_MAX_FIRSTEP_LVL) { + ath5k_ani_set_firstep_level(ah, as->firstep_level + 1); + return; + } + } else if (rssi > ATH5K_ANI_RSSI_THR_LOW) { + /* beacon RSSI in mid range, we need OFDM weak signal detect, + * but can raise firstep level */ + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, + "beacon RSSI mid"); + if (ofdm_trigger && !as->ofdm_weak_sig) + ath5k_ani_set_ofdm_weak_signal_detection(ah, true); + if (as->firstep_level < ATH5K_ANI_MAX_FIRSTEP_LVL) + ath5k_ani_set_firstep_level(ah, as->firstep_level + 1); + return; + } else if (ah->ah_current_channel->band == IEEE80211_BAND_2GHZ) { + /* beacon RSSI is low. in B/G mode turn of OFDM weak signal + * detect and zero firstep level to maximize CCK sensitivity */ + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, + "beacon RSSI low, 2GHz"); + if (ofdm_trigger && as->ofdm_weak_sig) + ath5k_ani_set_ofdm_weak_signal_detection(ah, false); + if (as->firstep_level > 0) + ath5k_ani_set_firstep_level(ah, 0); + return; + } + + /* TODO: why not?: + if (as->cck_weak_sig == true) { + ath5k_ani_set_cck_weak_signal_detection(ah, false); + } + */ +} + +/** + * ath5k_ani_lower_immunity() - Decrease noise immunity + * @ah: The &struct ath5k_hw + * @as: The &struct ath5k_ani_state + * + * Try to lower noise immunity (=increase sensitivity) in several steps + * depending on the average RSSI of the beacons we received. + */ +static void +ath5k_ani_lower_immunity(struct ath5k_hw *ah, struct ath5k_ani_state *as) +{ + int rssi = ewma_read(&ah->ah_beacon_rssi_avg); + + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "lower immunity"); + + if (ah->opmode == NL80211_IFTYPE_AP) { + /* AP mode */ + if (as->firstep_level > 0) { + ath5k_ani_set_firstep_level(ah, as->firstep_level - 1); + return; + } + } else { + /* STA and IBSS mode (see TODO above) */ + if (rssi > ATH5K_ANI_RSSI_THR_HIGH) { + /* beacon signal is high, leave OFDM weak signal + * detection off or it may oscillate + * TODO: who said it's off??? */ + } else if (rssi > ATH5K_ANI_RSSI_THR_LOW) { + /* beacon RSSI is mid-range: turn on ODFM weak signal + * detection and next, lower firstep level */ + if (!as->ofdm_weak_sig) { + ath5k_ani_set_ofdm_weak_signal_detection(ah, + true); + return; + } + if (as->firstep_level > 0) { + ath5k_ani_set_firstep_level(ah, + as->firstep_level - 1); + return; + } + } else { + /* beacon signal is low: only reduce firstep level */ + if (as->firstep_level > 0) { + ath5k_ani_set_firstep_level(ah, + as->firstep_level - 1); + return; + } + } + } + + /* all modes */ + if (as->spur_level > 0) { + ath5k_ani_set_spur_immunity_level(ah, as->spur_level - 1); + return; + } + + /* finally, reduce noise immunity */ + if (as->noise_imm_level > 0) { + ath5k_ani_set_noise_immunity_level(ah, as->noise_imm_level - 1); + return; + } +} + +/** + * ath5k_hw_ani_get_listen_time() - Update counters and return listening time + * @ah: The &struct ath5k_hw + * @as: The &struct ath5k_ani_state + * + * Return an approximation of the time spent "listening" in milliseconds (ms) + * since the last call of this function. + * Save a snapshot of the counter values for debugging/statistics. + */ +static int +ath5k_hw_ani_get_listen_time(struct ath5k_hw *ah, struct ath5k_ani_state *as) +{ + struct ath_common *common = ath5k_hw_common(ah); + int listen; + + spin_lock_bh(&common->cc_lock); + + ath_hw_cycle_counters_update(common); + memcpy(&as->last_cc, &common->cc_ani, sizeof(as->last_cc)); + + /* clears common->cc_ani */ + listen = ath_hw_get_listen_time(common); + + spin_unlock_bh(&common->cc_lock); + + return listen; +} + +/** + * ath5k_ani_save_and_clear_phy_errors() - Clear and save PHY error counters + * @ah: The &struct ath5k_hw + * @as: The &struct ath5k_ani_state + * + * Clear the PHY error counters as soon as possible, since this might be called + * from a MIB interrupt and we want to make sure we don't get interrupted again. + * Add the count of CCK and OFDM errors to our internal state, so it can be used + * by the algorithm later. + * + * Will be called from interrupt and tasklet context. + * Returns 0 if both counters are zero. + */ +static int +ath5k_ani_save_and_clear_phy_errors(struct ath5k_hw *ah, + struct ath5k_ani_state *as) +{ + unsigned int ofdm_err, cck_err; + + if (!ah->ah_capabilities.cap_has_phyerr_counters) + return 0; + + ofdm_err = ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT1); + cck_err = ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT2); + + /* reset counters first, we might be in a hurry (interrupt) */ + ath5k_hw_reg_write(ah, ATH5K_PHYERR_CNT_MAX - ATH5K_ANI_OFDM_TRIG_HIGH, + AR5K_PHYERR_CNT1); + ath5k_hw_reg_write(ah, ATH5K_PHYERR_CNT_MAX - ATH5K_ANI_CCK_TRIG_HIGH, + AR5K_PHYERR_CNT2); + + ofdm_err = ATH5K_ANI_OFDM_TRIG_HIGH - (ATH5K_PHYERR_CNT_MAX - ofdm_err); + cck_err = ATH5K_ANI_CCK_TRIG_HIGH - (ATH5K_PHYERR_CNT_MAX - cck_err); + + /* sometimes both can be zero, especially when there is a superfluous + * second interrupt. detect that here and return an error. */ + if (ofdm_err <= 0 && cck_err <= 0) + return 0; + + /* avoid negative values should one of the registers overflow */ + if (ofdm_err > 0) { + as->ofdm_errors += ofdm_err; + as->sum_ofdm_errors += ofdm_err; + } + if (cck_err > 0) { + as->cck_errors += cck_err; + as->sum_cck_errors += cck_err; + } + return 1; +} + +/** + * ath5k_ani_period_restart() - Restart ANI period + * @as: The &struct ath5k_ani_state + * + * Just reset counters, so they are clear for the next "ani period". + */ +static void +ath5k_ani_period_restart(struct ath5k_ani_state *as) +{ + /* keep last values for debugging */ + as->last_ofdm_errors = as->ofdm_errors; + as->last_cck_errors = as->cck_errors; + as->last_listen = as->listen_time; + + as->ofdm_errors = 0; + as->cck_errors = 0; + as->listen_time = 0; +} + +/** + * ath5k_ani_calibration() - The main ANI calibration function + * @ah: The &struct ath5k_hw + * + * We count OFDM and CCK errors relative to the time where we did not send or + * receive ("listen" time) and raise or lower immunity accordingly. + * This is called regularly (every second) from the calibration timer, but also + * when an error threshold has been reached. + * + * In order to synchronize access from different contexts, this should be + * called only indirectly by scheduling the ANI tasklet! + */ +void +ath5k_ani_calibration(struct ath5k_hw *ah) +{ + struct ath5k_ani_state *as = &ah->ani_state; + int listen, ofdm_high, ofdm_low, cck_high, cck_low; + + /* get listen time since last call and add it to the counter because we + * might not have restarted the "ani period" last time. + * always do this to calculate the busy time also in manual mode */ + listen = ath5k_hw_ani_get_listen_time(ah, as); + as->listen_time += listen; + + if (as->ani_mode != ATH5K_ANI_MODE_AUTO) + return; + + ath5k_ani_save_and_clear_phy_errors(ah, as); + + ofdm_high = as->listen_time * ATH5K_ANI_OFDM_TRIG_HIGH / 1000; + cck_high = as->listen_time * ATH5K_ANI_CCK_TRIG_HIGH / 1000; + ofdm_low = as->listen_time * ATH5K_ANI_OFDM_TRIG_LOW / 1000; + cck_low = as->listen_time * ATH5K_ANI_CCK_TRIG_LOW / 1000; + + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, + "listen %d (now %d)", as->listen_time, listen); + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, + "check high ofdm %d/%d cck %d/%d", + as->ofdm_errors, ofdm_high, as->cck_errors, cck_high); + + if (as->ofdm_errors > ofdm_high || as->cck_errors > cck_high) { + /* too many PHY errors - we have to raise immunity */ + bool ofdm_flag = as->ofdm_errors > ofdm_high ? true : false; + ath5k_ani_raise_immunity(ah, as, ofdm_flag); + ath5k_ani_period_restart(as); + + } else if (as->listen_time > 5 * ATH5K_ANI_LISTEN_PERIOD) { + /* If more than 5 (TODO: why 5?) periods have passed and we got + * relatively little errors we can try to lower immunity */ + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, + "check low ofdm %d/%d cck %d/%d", + as->ofdm_errors, ofdm_low, as->cck_errors, cck_low); + + if (as->ofdm_errors <= ofdm_low && as->cck_errors <= cck_low) + ath5k_ani_lower_immunity(ah, as); + + ath5k_ani_period_restart(as); + } +} + + +/*******************\ +* Interrupt handler * +\*******************/ + +/** + * ath5k_ani_mib_intr() - Interrupt handler for ANI MIB counters + * @ah: The &struct ath5k_hw + * + * Just read & reset the registers quickly, so they don't generate more + * interrupts, save the counters and schedule the tasklet to decide whether + * to raise immunity or not. + * + * We just need to handle PHY error counters, ath5k_hw_update_mib_counters() + * should take care of all "normal" MIB interrupts. + */ +void +ath5k_ani_mib_intr(struct ath5k_hw *ah) +{ + struct ath5k_ani_state *as = &ah->ani_state; + + /* nothing to do here if HW does not have PHY error counters - they + * can't be the reason for the MIB interrupt then */ + if (!ah->ah_capabilities.cap_has_phyerr_counters) + return; + + /* not in use but clear anyways */ + ath5k_hw_reg_write(ah, 0, AR5K_OFDM_FIL_CNT); + ath5k_hw_reg_write(ah, 0, AR5K_CCK_FIL_CNT); + + if (ah->ani_state.ani_mode != ATH5K_ANI_MODE_AUTO) + return; + + /* If one of the errors triggered, we can get a superfluous second + * interrupt, even though we have already reset the register. The + * function detects that so we can return early. */ + if (ath5k_ani_save_and_clear_phy_errors(ah, as) == 0) + return; + + if (as->ofdm_errors > ATH5K_ANI_OFDM_TRIG_HIGH || + as->cck_errors > ATH5K_ANI_CCK_TRIG_HIGH) + tasklet_schedule(&ah->ani_tasklet); +} + +/** + * ath5k_ani_phy_error_report - Used by older HW to report PHY errors + * + * @ah: The &struct ath5k_hw + * @phyerr: One of enum ath5k_phy_error_code + * + * This is used by hardware without PHY error counters to report PHY errors + * on a frame-by-frame basis, instead of the interrupt. + */ +void +ath5k_ani_phy_error_report(struct ath5k_hw *ah, + enum ath5k_phy_error_code phyerr) +{ + struct ath5k_ani_state *as = &ah->ani_state; + + if (phyerr == AR5K_RX_PHY_ERROR_OFDM_TIMING) { + as->ofdm_errors++; + if (as->ofdm_errors > ATH5K_ANI_OFDM_TRIG_HIGH) + tasklet_schedule(&ah->ani_tasklet); + } else if (phyerr == AR5K_RX_PHY_ERROR_CCK_TIMING) { + as->cck_errors++; + if (as->cck_errors > ATH5K_ANI_CCK_TRIG_HIGH) + tasklet_schedule(&ah->ani_tasklet); + } +} + + +/****************\ +* Initialization * +\****************/ + +/** + * ath5k_enable_phy_err_counters() - Enable PHY error counters + * @ah: The &struct ath5k_hw + * + * Enable PHY error counters for OFDM and CCK timing errors. + */ +static void +ath5k_enable_phy_err_counters(struct ath5k_hw *ah) +{ + ath5k_hw_reg_write(ah, ATH5K_PHYERR_CNT_MAX - ATH5K_ANI_OFDM_TRIG_HIGH, + AR5K_PHYERR_CNT1); + ath5k_hw_reg_write(ah, ATH5K_PHYERR_CNT_MAX - ATH5K_ANI_CCK_TRIG_HIGH, + AR5K_PHYERR_CNT2); + ath5k_hw_reg_write(ah, AR5K_PHY_ERR_FIL_OFDM, AR5K_PHYERR_CNT1_MASK); + ath5k_hw_reg_write(ah, AR5K_PHY_ERR_FIL_CCK, AR5K_PHYERR_CNT2_MASK); + + /* not in use */ + ath5k_hw_reg_write(ah, 0, AR5K_OFDM_FIL_CNT); + ath5k_hw_reg_write(ah, 0, AR5K_CCK_FIL_CNT); +} + +/** + * ath5k_disable_phy_err_counters() - Disable PHY error counters + * @ah: The &struct ath5k_hw + * + * Disable PHY error counters for OFDM and CCK timing errors. + */ +static void +ath5k_disable_phy_err_counters(struct ath5k_hw *ah) +{ + ath5k_hw_reg_write(ah, 0, AR5K_PHYERR_CNT1); + ath5k_hw_reg_write(ah, 0, AR5K_PHYERR_CNT2); + ath5k_hw_reg_write(ah, 0, AR5K_PHYERR_CNT1_MASK); + ath5k_hw_reg_write(ah, 0, AR5K_PHYERR_CNT2_MASK); + + /* not in use */ + ath5k_hw_reg_write(ah, 0, AR5K_OFDM_FIL_CNT); + ath5k_hw_reg_write(ah, 0, AR5K_CCK_FIL_CNT); +} + +/** + * ath5k_ani_init() - Initialize ANI + * @ah: The &struct ath5k_hw + * @mode: One of enum ath5k_ani_mode + * + * Initialize ANI according to mode. + */ +void +ath5k_ani_init(struct ath5k_hw *ah, enum ath5k_ani_mode mode) +{ + /* ANI is only possible on 5212 and newer */ + if (ah->ah_version < AR5K_AR5212) + return; + + if (mode < ATH5K_ANI_MODE_OFF || mode > ATH5K_ANI_MODE_AUTO) { + ATH5K_ERR(ah, "ANI mode %d out of range", mode); + return; + } + + /* clear old state information */ + memset(&ah->ani_state, 0, sizeof(ah->ani_state)); + + /* older hardware has more spur levels than newer */ + if (ah->ah_mac_srev < AR5K_SREV_AR2414) + ah->ani_state.max_spur_level = 7; + else + ah->ani_state.max_spur_level = 2; + + /* initial values for our ani parameters */ + if (mode == ATH5K_ANI_MODE_OFF) { + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "ANI off\n"); + } else if (mode == ATH5K_ANI_MODE_MANUAL_LOW) { + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, + "ANI manual low -> high sensitivity\n"); + ath5k_ani_set_noise_immunity_level(ah, 0); + ath5k_ani_set_spur_immunity_level(ah, 0); + ath5k_ani_set_firstep_level(ah, 0); + ath5k_ani_set_ofdm_weak_signal_detection(ah, true); + ath5k_ani_set_cck_weak_signal_detection(ah, true); + } else if (mode == ATH5K_ANI_MODE_MANUAL_HIGH) { + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, + "ANI manual high -> low sensitivity\n"); + ath5k_ani_set_noise_immunity_level(ah, + ATH5K_ANI_MAX_NOISE_IMM_LVL); + ath5k_ani_set_spur_immunity_level(ah, + ah->ani_state.max_spur_level); + ath5k_ani_set_firstep_level(ah, ATH5K_ANI_MAX_FIRSTEP_LVL); + ath5k_ani_set_ofdm_weak_signal_detection(ah, false); + ath5k_ani_set_cck_weak_signal_detection(ah, false); + } else if (mode == ATH5K_ANI_MODE_AUTO) { + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_ANI, "ANI auto\n"); + ath5k_ani_set_noise_immunity_level(ah, 0); + ath5k_ani_set_spur_immunity_level(ah, 0); + ath5k_ani_set_firstep_level(ah, 0); + ath5k_ani_set_ofdm_weak_signal_detection(ah, true); + ath5k_ani_set_cck_weak_signal_detection(ah, false); + } + + /* newer hardware has PHY error counter registers which we can use to + * get OFDM and CCK error counts. older hardware has to set rxfilter and + * report every single PHY error by calling ath5k_ani_phy_error_report() + */ + if (mode == ATH5K_ANI_MODE_AUTO) { + if (ah->ah_capabilities.cap_has_phyerr_counters) + ath5k_enable_phy_err_counters(ah); + else + ath5k_hw_set_rx_filter(ah, ath5k_hw_get_rx_filter(ah) | + AR5K_RX_FILTER_PHYERR); + } else { + if (ah->ah_capabilities.cap_has_phyerr_counters) + ath5k_disable_phy_err_counters(ah); + else + ath5k_hw_set_rx_filter(ah, ath5k_hw_get_rx_filter(ah) & + ~AR5K_RX_FILTER_PHYERR); + } + + ah->ani_state.ani_mode = mode; +} + + +/**************\ +* Debug output * +\**************/ + +#ifdef CPTCFG_ATH5K_DEBUG + +/** + * ath5k_ani_print_counters() - Print ANI counters + * @ah: The &struct ath5k_hw + * + * Used for debugging ANI + */ +void +ath5k_ani_print_counters(struct ath5k_hw *ah) +{ + /* clears too */ + pr_notice("ACK fail\t%d\n", ath5k_hw_reg_read(ah, AR5K_ACK_FAIL)); + pr_notice("RTS fail\t%d\n", ath5k_hw_reg_read(ah, AR5K_RTS_FAIL)); + pr_notice("RTS success\t%d\n", ath5k_hw_reg_read(ah, AR5K_RTS_OK)); + pr_notice("FCS error\t%d\n", ath5k_hw_reg_read(ah, AR5K_FCS_FAIL)); + + /* no clear */ + pr_notice("tx\t%d\n", ath5k_hw_reg_read(ah, AR5K_PROFCNT_TX)); + pr_notice("rx\t%d\n", ath5k_hw_reg_read(ah, AR5K_PROFCNT_RX)); + pr_notice("busy\t%d\n", ath5k_hw_reg_read(ah, AR5K_PROFCNT_RXCLR)); + pr_notice("cycles\t%d\n", ath5k_hw_reg_read(ah, AR5K_PROFCNT_CYCLE)); + + pr_notice("AR5K_PHYERR_CNT1\t%d\n", + ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT1)); + pr_notice("AR5K_PHYERR_CNT2\t%d\n", + ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT2)); + pr_notice("AR5K_OFDM_FIL_CNT\t%d\n", + ath5k_hw_reg_read(ah, AR5K_OFDM_FIL_CNT)); + pr_notice("AR5K_CCK_FIL_CNT\t%d\n", + ath5k_hw_reg_read(ah, AR5K_CCK_FIL_CNT)); +} + +#endif diff --git a/drivers/net/wireless/ath/ath5k/ani.h b/drivers/net/wireless/ath/ath5k/ani.h new file mode 100644 index 0000000..21aa355 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/ani.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2010 Bruno Randolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef ANI_H +#define ANI_H + +#include "../ath.h" + +enum ath5k_phy_error_code; + +/* these thresholds are relative to the ATH5K_ANI_LISTEN_PERIOD */ +#define ATH5K_ANI_LISTEN_PERIOD 100 +#define ATH5K_ANI_OFDM_TRIG_HIGH 500 +#define ATH5K_ANI_OFDM_TRIG_LOW 200 +#define ATH5K_ANI_CCK_TRIG_HIGH 200 +#define ATH5K_ANI_CCK_TRIG_LOW 100 + +/* average beacon RSSI thresholds */ +#define ATH5K_ANI_RSSI_THR_HIGH 40 +#define ATH5K_ANI_RSSI_THR_LOW 7 + +/* maximum available levels */ +#define ATH5K_ANI_MAX_FIRSTEP_LVL 2 +#define ATH5K_ANI_MAX_NOISE_IMM_LVL 1 + + +/** + * enum ath5k_ani_mode - mode for ANI / noise sensitivity + * + * @ATH5K_ANI_MODE_OFF: Turn ANI off. This can be useful to just stop the ANI + * algorithm after it has been on auto mode. + * @ATH5K_ANI_MODE_MANUAL_LOW: Manually set all immunity parameters to low, + * maximizing sensitivity. ANI will not run. + * @ATH5K_ANI_MODE_MANUAL_HIGH: Manually set all immunity parameters to high, + * minimizing sensitivity. ANI will not run. + * @ATH5K_ANI_MODE_AUTO: Automatically control immunity parameters based on the + * amount of OFDM and CCK frame errors (default). + */ +enum ath5k_ani_mode { + ATH5K_ANI_MODE_OFF = 0, + ATH5K_ANI_MODE_MANUAL_LOW = 1, + ATH5K_ANI_MODE_MANUAL_HIGH = 2, + ATH5K_ANI_MODE_AUTO = 3 +}; + + +/** + * struct ath5k_ani_state - ANI state and associated counters + * @ani_mode: One of enum ath5k_ani_mode + * @noise_imm_level: Noise immunity level + * @spur_level: Spur immunity level + * @firstep_level: FIRstep level + * @ofdm_weak_sig: OFDM weak signal detection state (on/off) + * @cck_weak_sig: CCK weak signal detection state (on/off) + * @max_spur_level: Max spur immunity level (chip specific) + * @listen_time: Listen time + * @ofdm_errors: OFDM timing error count + * @cck_errors: CCK timing error count + * @last_cc: The &struct ath_cycle_counters (for stats) + * @last_listen: Listen time from previous run (for stats) + * @last_ofdm_errors: OFDM timing error count from previous run (for tats) + * @last_cck_errors: CCK timing error count from previous run (for stats) + * @sum_ofdm_errors: Sum of OFDM timing errors (for stats) + * @sum_cck_errors: Sum of all CCK timing errors (for stats) + */ +struct ath5k_ani_state { + enum ath5k_ani_mode ani_mode; + + /* state */ + int noise_imm_level; + int spur_level; + int firstep_level; + bool ofdm_weak_sig; + bool cck_weak_sig; + + int max_spur_level; + + /* used by the algorithm */ + unsigned int listen_time; + unsigned int ofdm_errors; + unsigned int cck_errors; + + /* debug/statistics only: numbers from last ANI calibration */ + struct ath_cycle_counters last_cc; + unsigned int last_listen; + unsigned int last_ofdm_errors; + unsigned int last_cck_errors; + unsigned int sum_ofdm_errors; + unsigned int sum_cck_errors; +}; + +void ath5k_ani_init(struct ath5k_hw *ah, enum ath5k_ani_mode mode); +void ath5k_ani_mib_intr(struct ath5k_hw *ah); +void ath5k_ani_calibration(struct ath5k_hw *ah); +void ath5k_ani_phy_error_report(struct ath5k_hw *ah, + enum ath5k_phy_error_code phyerr); + +/* for manual control */ +void ath5k_ani_set_noise_immunity_level(struct ath5k_hw *ah, int level); +void ath5k_ani_set_spur_immunity_level(struct ath5k_hw *ah, int level); +void ath5k_ani_set_firstep_level(struct ath5k_hw *ah, int level); +void ath5k_ani_set_ofdm_weak_signal_detection(struct ath5k_hw *ah, bool on); +void ath5k_ani_set_cck_weak_signal_detection(struct ath5k_hw *ah, bool on); + +void ath5k_ani_print_counters(struct ath5k_hw *ah); + +#endif /* ANI_H */ diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h b/drivers/net/wireless/ath/ath5k/ath5k.h new file mode 100644 index 0000000..730d722 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/ath5k.h @@ -0,0 +1,1714 @@ +/* + * Copyright (c) 2004-2007 Reyk Floeter + * Copyright (c) 2006-2007 Nick Kossifidis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _ATH5K_H +#define _ATH5K_H + +/* TODO: Clean up channel debugging (doesn't work anyway) and start + * working on reg. control code using all available eeprom information + * (rev. engineering needed) */ +#define CHAN_DEBUG 0 + +#include +#include +#include +#include +#include +#include + +/* RX/TX descriptor hw structs + * TODO: Driver part should only see sw structs */ +#include "desc.h" + +/* EEPROM structs/offsets + * TODO: Make a more generic struct (eg. add more stuff to ath5k_capabilities) + * and clean up common bits, then introduce set/get functions in eeprom.c */ +#include "eeprom.h" +#include "debug.h" +#include "../ath.h" +#include "ani.h" + +/* PCI IDs */ +#define PCI_DEVICE_ID_ATHEROS_AR5210 0x0007 /* AR5210 */ +#define PCI_DEVICE_ID_ATHEROS_AR5311 0x0011 /* AR5311 */ +#define PCI_DEVICE_ID_ATHEROS_AR5211 0x0012 /* AR5211 */ +#define PCI_DEVICE_ID_ATHEROS_AR5212 0x0013 /* AR5212 */ +#define PCI_DEVICE_ID_3COM_3CRDAG675 0x0013 /* 3CRDAG675 (Atheros AR5212) */ +#define PCI_DEVICE_ID_3COM_2_3CRPAG175 0x0013 /* 3CRPAG175 (Atheros AR5212) */ +#define PCI_DEVICE_ID_ATHEROS_AR5210_AP 0x0207 /* AR5210 (Early) */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_IBM 0x1014 /* AR5212 (IBM MiniPCI) */ +#define PCI_DEVICE_ID_ATHEROS_AR5210_DEFAULT 0x1107 /* AR5210 (no eeprom) */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_DEFAULT 0x1113 /* AR5212 (no eeprom) */ +#define PCI_DEVICE_ID_ATHEROS_AR5211_DEFAULT 0x1112 /* AR5211 (no eeprom) */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_FPGA 0xf013 /* AR5212 (emulation board) */ +#define PCI_DEVICE_ID_ATHEROS_AR5211_LEGACY 0xff12 /* AR5211 (emulation board) */ +#define PCI_DEVICE_ID_ATHEROS_AR5211_FPGA11B 0xf11b /* AR5211 (emulation board) */ +#define PCI_DEVICE_ID_ATHEROS_AR5312_REV2 0x0052 /* AR5312 WMAC (AP31) */ +#define PCI_DEVICE_ID_ATHEROS_AR5312_REV7 0x0057 /* AR5312 WMAC (AP30-040) */ +#define PCI_DEVICE_ID_ATHEROS_AR5312_REV8 0x0058 /* AR5312 WMAC (AP43-030) */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_0014 0x0014 /* AR5212 compatible */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_0015 0x0015 /* AR5212 compatible */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_0016 0x0016 /* AR5212 compatible */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_0017 0x0017 /* AR5212 compatible */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_0018 0x0018 /* AR5212 compatible */ +#define PCI_DEVICE_ID_ATHEROS_AR5212_0019 0x0019 /* AR5212 compatible */ +#define PCI_DEVICE_ID_ATHEROS_AR2413 0x001a /* AR2413 (Griffin-lite) */ +#define PCI_DEVICE_ID_ATHEROS_AR5413 0x001b /* AR5413 (Eagle) */ +#define PCI_DEVICE_ID_ATHEROS_AR5424 0x001c /* AR5424 (Condor PCI-E) */ +#define PCI_DEVICE_ID_ATHEROS_AR5416 0x0023 /* AR5416 */ +#define PCI_DEVICE_ID_ATHEROS_AR5418 0x0024 /* AR5418 */ + +/****************************\ + GENERIC DRIVER DEFINITIONS +\****************************/ + +#define ATH5K_PRINTF(fmt, ...) \ + pr_warn("%s: " fmt, __func__, ##__VA_ARGS__) + +void __printf(3, 4) +_ath5k_printk(const struct ath5k_hw *ah, const char *level, + const char *fmt, ...); + +#define ATH5K_PRINTK(_sc, _level, _fmt, ...) \ + _ath5k_printk(_sc, _level, _fmt, ##__VA_ARGS__) + +#define ATH5K_PRINTK_LIMIT(_sc, _level, _fmt, ...) \ +do { \ + if (net_ratelimit()) \ + ATH5K_PRINTK(_sc, _level, _fmt, ##__VA_ARGS__); \ +} while (0) + +#define ATH5K_INFO(_sc, _fmt, ...) \ + ATH5K_PRINTK(_sc, KERN_INFO, _fmt, ##__VA_ARGS__) + +#define ATH5K_WARN(_sc, _fmt, ...) \ + ATH5K_PRINTK_LIMIT(_sc, KERN_WARNING, _fmt, ##__VA_ARGS__) + +#define ATH5K_ERR(_sc, _fmt, ...) \ + ATH5K_PRINTK_LIMIT(_sc, KERN_ERR, _fmt, ##__VA_ARGS__) + +/* + * AR5K REGISTER ACCESS + */ + +/* Some macros to read/write fields */ + +/* First shift, then mask */ +#define AR5K_REG_SM(_val, _flags) \ + (((_val) << _flags##_S) & (_flags)) + +/* First mask, then shift */ +#define AR5K_REG_MS(_val, _flags) \ + (((_val) & (_flags)) >> _flags##_S) + +/* Some registers can hold multiple values of interest. For this + * reason when we want to write to these registers we must first + * retrieve the values which we do not want to clear (lets call this + * old_data) and then set the register with this and our new_value: + * ( old_data | new_value) */ +#define AR5K_REG_WRITE_BITS(ah, _reg, _flags, _val) \ + ath5k_hw_reg_write(ah, (ath5k_hw_reg_read(ah, _reg) & ~(_flags)) | \ + (((_val) << _flags##_S) & (_flags)), _reg) + +#define AR5K_REG_MASKED_BITS(ah, _reg, _flags, _mask) \ + ath5k_hw_reg_write(ah, (ath5k_hw_reg_read(ah, _reg) & \ + (_mask)) | (_flags), _reg) + +#define AR5K_REG_ENABLE_BITS(ah, _reg, _flags) \ + ath5k_hw_reg_write(ah, ath5k_hw_reg_read(ah, _reg) | (_flags), _reg) + +#define AR5K_REG_DISABLE_BITS(ah, _reg, _flags) \ + ath5k_hw_reg_write(ah, ath5k_hw_reg_read(ah, _reg) & ~(_flags), _reg) + +/* Access QCU registers per queue */ +#define AR5K_REG_READ_Q(ah, _reg, _queue) \ + (ath5k_hw_reg_read(ah, _reg) & (1 << _queue)) \ + +#define AR5K_REG_WRITE_Q(ah, _reg, _queue) \ + ath5k_hw_reg_write(ah, (1 << _queue), _reg) + +#define AR5K_Q_ENABLE_BITS(_reg, _queue) do { \ + _reg |= 1 << _queue; \ +} while (0) + +#define AR5K_Q_DISABLE_BITS(_reg, _queue) do { \ + _reg &= ~(1 << _queue); \ +} while (0) + +/* Used while writing initvals */ +#define AR5K_REG_WAIT(_i) do { \ + if (_i % 64) \ + udelay(1); \ +} while (0) + +/* + * Some tunable values (these should be changeable by the user) + * TODO: Make use of them and add more options OR use debug/configfs + */ +#define AR5K_TUNE_DMA_BEACON_RESP 2 +#define AR5K_TUNE_SW_BEACON_RESP 10 +#define AR5K_TUNE_ADDITIONAL_SWBA_BACKOFF 0 +#define AR5K_TUNE_MIN_TX_FIFO_THRES 1 +#define AR5K_TUNE_MAX_TX_FIFO_THRES ((IEEE80211_MAX_FRAME_LEN / 64) + 1) +#define AR5K_TUNE_REGISTER_TIMEOUT 20000 +/* Register for RSSI threshold has a mask of 0xff, so 255 seems to + * be the max value. */ +#define AR5K_TUNE_RSSI_THRES 129 +/* This must be set when setting the RSSI threshold otherwise it can + * prevent a reset. If AR5K_RSSI_THR is read after writing to it + * the BMISS_THRES will be seen as 0, seems hardware doesn't keep + * track of it. Max value depends on hardware. For AR5210 this is just 7. + * For AR5211+ this seems to be up to 255. */ +#define AR5K_TUNE_BMISS_THRES 7 +#define AR5K_TUNE_REGISTER_DWELL_TIME 20000 +#define AR5K_TUNE_BEACON_INTERVAL 100 +#define AR5K_TUNE_AIFS 2 +#define AR5K_TUNE_AIFS_11B 2 +#define AR5K_TUNE_AIFS_XR 0 +#define AR5K_TUNE_CWMIN 15 +#define AR5K_TUNE_CWMIN_11B 31 +#define AR5K_TUNE_CWMIN_XR 3 +#define AR5K_TUNE_CWMAX 1023 +#define AR5K_TUNE_CWMAX_11B 1023 +#define AR5K_TUNE_CWMAX_XR 7 +#define AR5K_TUNE_NOISE_FLOOR -72 +#define AR5K_TUNE_CCA_MAX_GOOD_VALUE -95 +#define AR5K_TUNE_MAX_TXPOWER 63 +#define AR5K_TUNE_DEFAULT_TXPOWER 25 +#define AR5K_TUNE_TPC_TXPOWER false +#define ATH5K_TUNE_CALIBRATION_INTERVAL_FULL 60000 /* 60 sec */ +#define ATH5K_TUNE_CALIBRATION_INTERVAL_SHORT 10000 /* 10 sec */ +#define ATH5K_TUNE_CALIBRATION_INTERVAL_ANI 1000 /* 1 sec */ +#define ATH5K_TX_COMPLETE_POLL_INT 3000 /* 3 sec */ + +#define AR5K_INIT_CARR_SENSE_EN 1 + +/*Swap RX/TX Descriptor for big endian archs*/ +#if defined(__BIG_ENDIAN) +#define AR5K_INIT_CFG ( \ + AR5K_CFG_SWTD | AR5K_CFG_SWRD \ +) +#else +#define AR5K_INIT_CFG 0x00000000 +#endif + +/* Initial values */ +#define AR5K_INIT_CYCRSSI_THR1 2 + +/* Tx retry limit defaults from standard */ +#define AR5K_INIT_RETRY_SHORT 7 +#define AR5K_INIT_RETRY_LONG 4 + +/* Slot time */ +#define AR5K_INIT_SLOT_TIME_TURBO 6 +#define AR5K_INIT_SLOT_TIME_DEFAULT 9 +#define AR5K_INIT_SLOT_TIME_HALF_RATE 13 +#define AR5K_INIT_SLOT_TIME_QUARTER_RATE 21 +#define AR5K_INIT_SLOT_TIME_B 20 +#define AR5K_SLOT_TIME_MAX 0xffff + +/* SIFS */ +#define AR5K_INIT_SIFS_TURBO 6 +#define AR5K_INIT_SIFS_DEFAULT_BG 10 +#define AR5K_INIT_SIFS_DEFAULT_A 16 +#define AR5K_INIT_SIFS_HALF_RATE 32 +#define AR5K_INIT_SIFS_QUARTER_RATE 64 + +/* Used to calculate tx time for non 5/10/40MHz + * operation */ +/* It's preamble time + signal time (16 + 4) */ +#define AR5K_INIT_OFDM_PREAMPLE_TIME 20 +/* Preamble time for 40MHz (turbo) operation (min ?) */ +#define AR5K_INIT_OFDM_PREAMBLE_TIME_MIN 14 +#define AR5K_INIT_OFDM_SYMBOL_TIME 4 +#define AR5K_INIT_OFDM_PLCP_BITS 22 + +/* Rx latency for 5 and 10MHz operation (max ?) */ +#define AR5K_INIT_RX_LAT_MAX 63 +/* Tx latencies from initvals (5212 only but no problem + * because we only tweak them on 5212) */ +#define AR5K_INIT_TX_LAT_A 54 +#define AR5K_INIT_TX_LAT_BG 384 +/* Tx latency for 40MHz (turbo) operation (min ?) */ +#define AR5K_INIT_TX_LAT_MIN 32 +/* Default Tx/Rx latencies (same for 5211)*/ +#define AR5K_INIT_TX_LATENCY_5210 54 +#define AR5K_INIT_RX_LATENCY_5210 29 + +/* Tx frame to Tx data start delay */ +#define AR5K_INIT_TXF2TXD_START_DEFAULT 14 +#define AR5K_INIT_TXF2TXD_START_DELAY_10MHZ 12 +#define AR5K_INIT_TXF2TXD_START_DELAY_5MHZ 13 + +/* We need to increase PHY switch and agc settling time + * on turbo mode */ +#define AR5K_SWITCH_SETTLING 5760 +#define AR5K_SWITCH_SETTLING_TURBO 7168 + +#define AR5K_AGC_SETTLING 28 +/* 38 on 5210 but shouldn't matter */ +#define AR5K_AGC_SETTLING_TURBO 37 + + + +/*****************************\ +* GENERIC CHIPSET DEFINITIONS * +\*****************************/ + +/** + * enum ath5k_version - MAC Chips + * @AR5K_AR5210: AR5210 (Crete) + * @AR5K_AR5211: AR5211 (Oahu/Maui) + * @AR5K_AR5212: AR5212 (Venice) and newer + */ +enum ath5k_version { + AR5K_AR5210 = 0, + AR5K_AR5211 = 1, + AR5K_AR5212 = 2, +}; + +/** + * enum ath5k_radio - PHY Chips + * @AR5K_RF5110: RF5110 (Fez) + * @AR5K_RF5111: RF5111 (Sombrero) + * @AR5K_RF5112: RF2112/5112(A) (Derby/Derby2) + * @AR5K_RF2413: RF2413/2414 (Griffin/Griffin-Lite) + * @AR5K_RF5413: RF5413/5414/5424 (Eagle/Condor) + * @AR5K_RF2316: RF2315/2316 (Cobra SoC) + * @AR5K_RF2317: RF2317 (Spider SoC) + * @AR5K_RF2425: RF2425/2417 (Swan/Nalla) + */ +enum ath5k_radio { + AR5K_RF5110 = 0, + AR5K_RF5111 = 1, + AR5K_RF5112 = 2, + AR5K_RF2413 = 3, + AR5K_RF5413 = 4, + AR5K_RF2316 = 5, + AR5K_RF2317 = 6, + AR5K_RF2425 = 7, +}; + +/* + * Common silicon revision/version values + */ + +#define AR5K_SREV_UNKNOWN 0xffff + +#define AR5K_SREV_AR5210 0x00 /* Crete */ +#define AR5K_SREV_AR5311 0x10 /* Maui 1 */ +#define AR5K_SREV_AR5311A 0x20 /* Maui 2 */ +#define AR5K_SREV_AR5311B 0x30 /* Spirit */ +#define AR5K_SREV_AR5211 0x40 /* Oahu */ +#define AR5K_SREV_AR5212 0x50 /* Venice */ +#define AR5K_SREV_AR5312_R2 0x52 /* AP31 */ +#define AR5K_SREV_AR5212_V4 0x54 /* ??? */ +#define AR5K_SREV_AR5213 0x55 /* ??? */ +#define AR5K_SREV_AR5312_R7 0x57 /* AP30 */ +#define AR5K_SREV_AR2313_R8 0x58 /* AP43 */ +#define AR5K_SREV_AR5213A 0x59 /* Hainan */ +#define AR5K_SREV_AR2413 0x78 /* Griffin lite */ +#define AR5K_SREV_AR2414 0x70 /* Griffin */ +#define AR5K_SREV_AR2315_R6 0x86 /* AP51-Light */ +#define AR5K_SREV_AR2315_R7 0x87 /* AP51-Full */ +#define AR5K_SREV_AR5424 0x90 /* Condor */ +#define AR5K_SREV_AR2317_R1 0x90 /* AP61-Light */ +#define AR5K_SREV_AR2317_R2 0x91 /* AP61-Full */ +#define AR5K_SREV_AR5413 0xa4 /* Eagle lite */ +#define AR5K_SREV_AR5414 0xa0 /* Eagle */ +#define AR5K_SREV_AR2415 0xb0 /* Talon */ +#define AR5K_SREV_AR5416 0xc0 /* PCI-E */ +#define AR5K_SREV_AR5418 0xca /* PCI-E */ +#define AR5K_SREV_AR2425 0xe0 /* Swan */ +#define AR5K_SREV_AR2417 0xf0 /* Nala */ + +#define AR5K_SREV_RAD_5110 0x00 +#define AR5K_SREV_RAD_5111 0x10 +#define AR5K_SREV_RAD_5111A 0x15 +#define AR5K_SREV_RAD_2111 0x20 +#define AR5K_SREV_RAD_5112 0x30 +#define AR5K_SREV_RAD_5112A 0x35 +#define AR5K_SREV_RAD_5112B 0x36 +#define AR5K_SREV_RAD_2112 0x40 +#define AR5K_SREV_RAD_2112A 0x45 +#define AR5K_SREV_RAD_2112B 0x46 +#define AR5K_SREV_RAD_2413 0x50 +#define AR5K_SREV_RAD_5413 0x60 +#define AR5K_SREV_RAD_2316 0x70 /* Cobra SoC */ +#define AR5K_SREV_RAD_2317 0x80 +#define AR5K_SREV_RAD_5424 0xa0 /* Mostly same as 5413 */ +#define AR5K_SREV_RAD_2425 0xa2 +#define AR5K_SREV_RAD_5133 0xc0 + +#define AR5K_SREV_PHY_5211 0x30 +#define AR5K_SREV_PHY_5212 0x41 +#define AR5K_SREV_PHY_5212A 0x42 +#define AR5K_SREV_PHY_5212B 0x43 +#define AR5K_SREV_PHY_2413 0x45 +#define AR5K_SREV_PHY_5413 0x61 +#define AR5K_SREV_PHY_2425 0x70 + +/* TODO add support to mac80211 for vendor-specific rates and modes */ + +/** + * DOC: Atheros XR + * + * Some of this information is based on Documentation from: + * + * http://madwifi-project.org/wiki/ChipsetFeatures/SuperAG + * + * Atheros' eXtended Range - range enhancing extension is a modulation scheme + * that is supposed to double the link distance between an Atheros XR-enabled + * client device with an Atheros XR-enabled access point. This is achieved + * by increasing the receiver sensitivity up to, -105dBm, which is about 20dB + * above what the 802.11 specifications demand. In addition, new (proprietary) + * data rates are introduced: 3, 2, 1, 0.5 and 0.25 MBit/s. + * + * Please note that can you either use XR or TURBO but you cannot use both, + * they are exclusive. + * + * Also note that we do not plan to support XR mode at least for now. You can + * get a mode similar to XR by using 5MHz bwmode. + */ + + +/** + * DOC: Atheros SuperAG + * + * In addition to XR we have another modulation scheme called TURBO mode + * that is supposed to provide a throughput transmission speed up to 40Mbit/s + * -60Mbit/s at a 108Mbit/s signaling rate achieved through the bonding of two + * 54Mbit/s 802.11g channels. To use this feature both ends must support it. + * There is also a distinction between "static" and "dynamic" turbo modes: + * + * - Static: is the dumb version: devices set to this mode stick to it until + * the mode is turned off. + * + * - Dynamic: is the intelligent version, the network decides itself if it + * is ok to use turbo. As soon as traffic is detected on adjacent channels + * (which would get used in turbo mode), or when a non-turbo station joins + * the network, turbo mode won't be used until the situation changes again. + * Dynamic mode is achieved by Atheros' Adaptive Radio (AR) feature which + * monitors the used radio band in order to decide whether turbo mode may + * be used or not. + * + * This article claims Super G sticks to bonding of channels 5 and 6 for + * USA: + * + * http://www.pcworld.com/article/id,113428-page,1/article.html + * + * The channel bonding seems to be driver specific though. + * + * In addition to TURBO modes we also have the following features for even + * greater speed-up: + * + * - Bursting: allows multiple frames to be sent at once, rather than pausing + * after each frame. Bursting is a standards-compliant feature that can be + * used with any Access Point. + * + * - Fast frames: increases the amount of information that can be sent per + * frame, also resulting in a reduction of transmission overhead. It is a + * proprietary feature that needs to be supported by the Access Point. + * + * - Compression: data frames are compressed in real time using a Lempel Ziv + * algorithm. This is done transparently. Once this feature is enabled, + * compression and decompression takes place inside the chipset, without + * putting additional load on the host CPU. + * + * As with XR we also don't plan to support SuperAG features for now. You can + * get a mode similar to TURBO by using 40MHz bwmode. + */ + + +/** + * enum ath5k_driver_mode - PHY operation mode + * @AR5K_MODE_11A: 802.11a + * @AR5K_MODE_11B: 802.11b + * @AR5K_MODE_11G: 801.11g + * @AR5K_MODE_MAX: Used for boundary checks + * + * Do not change the order here, we use these as + * array indices and it also maps EEPROM structures. + */ +enum ath5k_driver_mode { + AR5K_MODE_11A = 0, + AR5K_MODE_11B = 1, + AR5K_MODE_11G = 2, + AR5K_MODE_MAX = 3 +}; + +/** + * enum ath5k_ant_mode - Antenna operation mode + * @AR5K_ANTMODE_DEFAULT: Default antenna setup + * @AR5K_ANTMODE_FIXED_A: Only antenna A is present + * @AR5K_ANTMODE_FIXED_B: Only antenna B is present + * @AR5K_ANTMODE_SINGLE_AP: STA locked on a single ap + * @AR5K_ANTMODE_SECTOR_AP: AP with tx antenna set on tx desc + * @AR5K_ANTMODE_SECTOR_STA: STA with tx antenna set on tx desc + * @AR5K_ANTMODE_DEBUG: Debug mode -A -> Rx, B-> Tx- + * @AR5K_ANTMODE_MAX: Used for boundary checks + * + * For more infos on antenna control check out phy.c + */ +enum ath5k_ant_mode { + AR5K_ANTMODE_DEFAULT = 0, + AR5K_ANTMODE_FIXED_A = 1, + AR5K_ANTMODE_FIXED_B = 2, + AR5K_ANTMODE_SINGLE_AP = 3, + AR5K_ANTMODE_SECTOR_AP = 4, + AR5K_ANTMODE_SECTOR_STA = 5, + AR5K_ANTMODE_DEBUG = 6, + AR5K_ANTMODE_MAX, +}; + +/** + * enum ath5k_bw_mode - Bandwidth operation mode + * @AR5K_BWMODE_DEFAULT: 20MHz, default operation + * @AR5K_BWMODE_5MHZ: Quarter rate + * @AR5K_BWMODE_10MHZ: Half rate + * @AR5K_BWMODE_40MHZ: Turbo + */ +enum ath5k_bw_mode { + AR5K_BWMODE_DEFAULT = 0, + AR5K_BWMODE_5MHZ = 1, + AR5K_BWMODE_10MHZ = 2, + AR5K_BWMODE_40MHZ = 3 +}; + + + +/****************\ + TX DEFINITIONS +\****************/ + +/** + * struct ath5k_tx_status - TX Status descriptor + * @ts_seqnum: Sequence number + * @ts_tstamp: Timestamp + * @ts_status: Status code + * @ts_final_idx: Final transmission series index + * @ts_final_retry: Final retry count + * @ts_rssi: RSSI for received ACK + * @ts_shortretry: Short retry count + * @ts_virtcol: Virtual collision count + * @ts_antenna: Antenna used + * + * TX status descriptor gets filled by the hw + * on each transmission attempt. + */ +struct ath5k_tx_status { + u16 ts_seqnum; + u16 ts_tstamp; + u8 ts_status; + u8 ts_final_idx; + u8 ts_final_retry; + s8 ts_rssi; + u8 ts_shortretry; + u8 ts_virtcol; + u8 ts_antenna; +}; + +#define AR5K_TXSTAT_ALTRATE 0x80 +#define AR5K_TXERR_XRETRY 0x01 +#define AR5K_TXERR_FILT 0x02 +#define AR5K_TXERR_FIFO 0x04 + +/** + * enum ath5k_tx_queue - Queue types used to classify tx queues. + * @AR5K_TX_QUEUE_INACTIVE: q is unused -- see ath5k_hw_release_tx_queue + * @AR5K_TX_QUEUE_DATA: A normal data queue + * @AR5K_TX_QUEUE_BEACON: The beacon queue + * @AR5K_TX_QUEUE_CAB: The after-beacon queue + * @AR5K_TX_QUEUE_UAPSD: Unscheduled Automatic Power Save Delivery queue + */ +enum ath5k_tx_queue { + AR5K_TX_QUEUE_INACTIVE = 0, + AR5K_TX_QUEUE_DATA, + AR5K_TX_QUEUE_BEACON, + AR5K_TX_QUEUE_CAB, + AR5K_TX_QUEUE_UAPSD, +}; + +#define AR5K_NUM_TX_QUEUES 10 +#define AR5K_NUM_TX_QUEUES_NOQCU 2 + +/** + * enum ath5k_tx_queue_subtype - Queue sub-types to classify normal data queues + * @AR5K_WME_AC_BK: Background traffic + * @AR5K_WME_AC_BE: Best-effort (normal) traffic + * @AR5K_WME_AC_VI: Video traffic + * @AR5K_WME_AC_VO: Voice traffic + * + * These are the 4 Access Categories as defined in + * WME spec. 0 is the lowest priority and 4 is the + * highest. Normal data that hasn't been classified + * goes to the Best Effort AC. + */ +enum ath5k_tx_queue_subtype { + AR5K_WME_AC_BK = 0, + AR5K_WME_AC_BE, + AR5K_WME_AC_VI, + AR5K_WME_AC_VO, +}; + +/** + * enum ath5k_tx_queue_id - Queue ID numbers as returned by the hw functions + * @AR5K_TX_QUEUE_ID_NOQCU_DATA: Data queue on AR5210 (no QCU available) + * @AR5K_TX_QUEUE_ID_NOQCU_BEACON: Beacon queue on AR5210 (no QCU available) + * @AR5K_TX_QUEUE_ID_DATA_MIN: Data queue min index + * @AR5K_TX_QUEUE_ID_DATA_MAX: Data queue max index + * @AR5K_TX_QUEUE_ID_CAB: Content after beacon queue + * @AR5K_TX_QUEUE_ID_BEACON: Beacon queue + * @AR5K_TX_QUEUE_ID_UAPSD: Urgent Automatic Power Save Delivery, + * + * Each number represents a hw queue. If hw does not support hw queues + * (eg 5210) all data goes in one queue. + */ +enum ath5k_tx_queue_id { + AR5K_TX_QUEUE_ID_NOQCU_DATA = 0, + AR5K_TX_QUEUE_ID_NOQCU_BEACON = 1, + AR5K_TX_QUEUE_ID_DATA_MIN = 0, + AR5K_TX_QUEUE_ID_DATA_MAX = 3, + AR5K_TX_QUEUE_ID_UAPSD = 7, + AR5K_TX_QUEUE_ID_CAB = 8, + AR5K_TX_QUEUE_ID_BEACON = 9, +}; + +/* + * Flags to set hw queue's parameters... + */ +#define AR5K_TXQ_FLAG_TXOKINT_ENABLE 0x0001 /* Enable TXOK interrupt */ +#define AR5K_TXQ_FLAG_TXERRINT_ENABLE 0x0002 /* Enable TXERR interrupt */ +#define AR5K_TXQ_FLAG_TXEOLINT_ENABLE 0x0004 /* Enable TXEOL interrupt -not used- */ +#define AR5K_TXQ_FLAG_TXDESCINT_ENABLE 0x0008 /* Enable TXDESC interrupt -not used- */ +#define AR5K_TXQ_FLAG_TXURNINT_ENABLE 0x0010 /* Enable TXURN interrupt */ +#define AR5K_TXQ_FLAG_CBRORNINT_ENABLE 0x0020 /* Enable CBRORN interrupt */ +#define AR5K_TXQ_FLAG_CBRURNINT_ENABLE 0x0040 /* Enable CBRURN interrupt */ +#define AR5K_TXQ_FLAG_QTRIGINT_ENABLE 0x0080 /* Enable QTRIG interrupt */ +#define AR5K_TXQ_FLAG_TXNOFRMINT_ENABLE 0x0100 /* Enable TXNOFRM interrupt */ +#define AR5K_TXQ_FLAG_BACKOFF_DISABLE 0x0200 /* Disable random post-backoff */ +#define AR5K_TXQ_FLAG_RDYTIME_EXP_POLICY_ENABLE 0x0300 /* Enable ready time expiry policy (?)*/ +#define AR5K_TXQ_FLAG_FRAG_BURST_BACKOFF_ENABLE 0x0800 /* Enable backoff while bursting */ +#define AR5K_TXQ_FLAG_POST_FR_BKOFF_DIS 0x1000 /* Disable backoff while bursting */ +#define AR5K_TXQ_FLAG_COMPRESSION_ENABLE 0x2000 /* Enable hw compression -not implemented-*/ + +/** + * struct ath5k_txq - Transmit queue state + * @qnum: Hardware q number + * @link: Link ptr in last TX desc + * @q: Transmit queue (&struct list_head) + * @lock: Lock on q and link + * @setup: Is the queue configured + * @txq_len:Number of queued buffers + * @txq_max: Max allowed num of queued buffers + * @txq_poll_mark: Used to check if queue got stuck + * @txq_stuck: Queue stuck counter + * + * One of these exists for each hardware transmit queue. + * Packets sent to us from above are assigned to queues based + * on their priority. Not all devices support a complete set + * of hardware transmit queues. For those devices the array + * sc_ac2q will map multiple priorities to fewer hardware queues + * (typically all to one hardware queue). + */ +struct ath5k_txq { + unsigned int qnum; + u32 *link; + struct list_head q; + spinlock_t lock; + bool setup; + int txq_len; + int txq_max; + bool txq_poll_mark; + unsigned int txq_stuck; +}; + +/** + * struct ath5k_txq_info - A struct to hold TX queue's parameters + * @tqi_type: One of enum ath5k_tx_queue + * @tqi_subtype: One of enum ath5k_tx_queue_subtype + * @tqi_flags: TX queue flags (see above) + * @tqi_aifs: Arbitrated Inter-frame Space + * @tqi_cw_min: Minimum Contention Window + * @tqi_cw_max: Maximum Contention Window + * @tqi_cbr_period: Constant bit rate period + * @tqi_ready_time: Time queue waits after an event when RDYTIME is enabled + */ +struct ath5k_txq_info { + enum ath5k_tx_queue tqi_type; + enum ath5k_tx_queue_subtype tqi_subtype; + u16 tqi_flags; + u8 tqi_aifs; + u16 tqi_cw_min; + u16 tqi_cw_max; + u32 tqi_cbr_period; + u32 tqi_cbr_overflow_limit; + u32 tqi_burst_time; + u32 tqi_ready_time; +}; + +/** + * enum ath5k_pkt_type - Transmit packet types + * @AR5K_PKT_TYPE_NORMAL: Normal data + * @AR5K_PKT_TYPE_ATIM: ATIM + * @AR5K_PKT_TYPE_PSPOLL: PS-Poll + * @AR5K_PKT_TYPE_BEACON: Beacon + * @AR5K_PKT_TYPE_PROBE_RESP: Probe response + * @AR5K_PKT_TYPE_PIFS: PIFS + * Used on tx control descriptor + */ +enum ath5k_pkt_type { + AR5K_PKT_TYPE_NORMAL = 0, + AR5K_PKT_TYPE_ATIM = 1, + AR5K_PKT_TYPE_PSPOLL = 2, + AR5K_PKT_TYPE_BEACON = 3, + AR5K_PKT_TYPE_PROBE_RESP = 4, + AR5K_PKT_TYPE_PIFS = 5, +}; + +/* + * TX power and TPC settings + */ +#define AR5K_TXPOWER_OFDM(_r, _v) ( \ + ((0 & 1) << ((_v) + 6)) | \ + (((ah->ah_txpower.txp_rates_power_table[(_r)]) & 0x3f) << (_v)) \ +) + +#define AR5K_TXPOWER_CCK(_r, _v) ( \ + (ah->ah_txpower.txp_rates_power_table[(_r)] & 0x3f) << (_v) \ +) + + + +/****************\ + RX DEFINITIONS +\****************/ + +/** + * struct ath5k_rx_status - RX Status descriptor + * @rs_datalen: Data length + * @rs_tstamp: Timestamp + * @rs_status: Status code + * @rs_phyerr: PHY error mask + * @rs_rssi: RSSI in 0.5dbm units + * @rs_keyix: Index to the key used for decrypting + * @rs_rate: Rate used to decode the frame + * @rs_antenna: Antenna used to receive the frame + * @rs_more: Indicates this is a frame fragment (Fast frames) + */ +struct ath5k_rx_status { + u16 rs_datalen; + u16 rs_tstamp; + u8 rs_status; + u8 rs_phyerr; + s8 rs_rssi; + u8 rs_keyix; + u8 rs_rate; + u8 rs_antenna; + u8 rs_more; +}; + +#define AR5K_RXERR_CRC 0x01 +#define AR5K_RXERR_PHY 0x02 +#define AR5K_RXERR_FIFO 0x04 +#define AR5K_RXERR_DECRYPT 0x08 +#define AR5K_RXERR_MIC 0x10 +#define AR5K_RXKEYIX_INVALID ((u8) -1) +#define AR5K_TXKEYIX_INVALID ((u32) -1) + + +/**************************\ + BEACON TIMERS DEFINITIONS +\**************************/ + +#define AR5K_BEACON_PERIOD 0x0000ffff +#define AR5K_BEACON_ENA 0x00800000 /*enable beacon xmit*/ +#define AR5K_BEACON_RESET_TSF 0x01000000 /*force a TSF reset*/ + + +/* + * TSF to TU conversion: + * + * TSF is a 64bit value in usec (microseconds). + * TU is a 32bit value and defined by IEEE802.11 (page 6) as "A measurement of + * time equal to 1024 usec", so it's roughly milliseconds (usec / 1024). + */ +#define TSF_TO_TU(_tsf) (u32)((_tsf) >> 10) + + + +/*******************************\ + GAIN OPTIMIZATION DEFINITIONS +\*******************************/ + +/** + * enum ath5k_rfgain - RF Gain optimization engine state + * @AR5K_RFGAIN_INACTIVE: Engine disabled + * @AR5K_RFGAIN_ACTIVE: Probe active + * @AR5K_RFGAIN_READ_REQUESTED: Probe requested + * @AR5K_RFGAIN_NEED_CHANGE: Gain_F needs change + */ +enum ath5k_rfgain { + AR5K_RFGAIN_INACTIVE = 0, + AR5K_RFGAIN_ACTIVE, + AR5K_RFGAIN_READ_REQUESTED, + AR5K_RFGAIN_NEED_CHANGE, +}; + +/** + * struct ath5k_gain - RF Gain optimization engine state data + * @g_step_idx: Current step index + * @g_current: Current gain + * @g_target: Target gain + * @g_low: Low gain boundary + * @g_high: High gain boundary + * @g_f_corr: Gain_F correction + * @g_state: One of enum ath5k_rfgain + */ +struct ath5k_gain { + u8 g_step_idx; + u8 g_current; + u8 g_target; + u8 g_low; + u8 g_high; + u8 g_f_corr; + u8 g_state; +}; + + + +/********************\ + COMMON DEFINITIONS +\********************/ + +#define AR5K_SLOT_TIME_9 396 +#define AR5K_SLOT_TIME_20 880 +#define AR5K_SLOT_TIME_MAX 0xffff + +/** + * struct ath5k_athchan_2ghz - 2GHz to 5GHZ map for RF5111 + * @a2_flags: Channel flags (internal) + * @a2_athchan: HW channel number (internal) + * + * This structure is used to map 2GHz channels to + * 5GHz Atheros channels on 2111 frequency converter + * that comes together with RF5111 + * TODO: Clean up + */ +struct ath5k_athchan_2ghz { + u32 a2_flags; + u16 a2_athchan; +}; + +/** + * enum ath5k_dmasize - DMA size definitions (2^(n+2)) + * @AR5K_DMASIZE_4B: 4Bytes + * @AR5K_DMASIZE_8B: 8Bytes + * @AR5K_DMASIZE_16B: 16Bytes + * @AR5K_DMASIZE_32B: 32Bytes + * @AR5K_DMASIZE_64B: 64Bytes (Default) + * @AR5K_DMASIZE_128B: 128Bytes + * @AR5K_DMASIZE_256B: 256Bytes + * @AR5K_DMASIZE_512B: 512Bytes + * + * These are used to set DMA burst size on hw + * + * Note: Some platforms can't handle more than 4Bytes + * be careful on embedded boards. + */ +enum ath5k_dmasize { + AR5K_DMASIZE_4B = 0, + AR5K_DMASIZE_8B, + AR5K_DMASIZE_16B, + AR5K_DMASIZE_32B, + AR5K_DMASIZE_64B, + AR5K_DMASIZE_128B, + AR5K_DMASIZE_256B, + AR5K_DMASIZE_512B +}; + + + +/******************\ + RATE DEFINITIONS +\******************/ + +/** + * DOC: Rate codes + * + * Seems the ar5xxx hardware supports up to 32 rates, indexed by 1-32. + * + * The rate code is used to get the RX rate or set the TX rate on the + * hardware descriptors. It is also used for internal modulation control + * and settings. + * + * This is the hardware rate map we are aware of (html unfriendly): + * + * Rate code Rate (Kbps) + * --------- ----------- + * 0x01 3000 (XR) + * 0x02 1000 (XR) + * 0x03 250 (XR) + * 0x04 - 05 -Reserved- + * 0x06 2000 (XR) + * 0x07 500 (XR) + * 0x08 48000 (OFDM) + * 0x09 24000 (OFDM) + * 0x0A 12000 (OFDM) + * 0x0B 6000 (OFDM) + * 0x0C 54000 (OFDM) + * 0x0D 36000 (OFDM) + * 0x0E 18000 (OFDM) + * 0x0F 9000 (OFDM) + * 0x10 - 17 -Reserved- + * 0x18 11000L (CCK) + * 0x19 5500L (CCK) + * 0x1A 2000L (CCK) + * 0x1B 1000L (CCK) + * 0x1C 11000S (CCK) + * 0x1D 5500S (CCK) + * 0x1E 2000S (CCK) + * 0x1F -Reserved- + * + * "S" indicates CCK rates with short preamble and "L" with long preamble. + * + * AR5211 has different rate codes for CCK (802.11B) rates. It only uses the + * lowest 4 bits, so they are the same as above with a 0xF mask. + * (0xB, 0xA, 0x9 and 0x8 for 1M, 2M, 5.5M and 11M). + * We handle this in ath5k_setup_bands(). + */ +#define AR5K_MAX_RATES 32 + +/* B */ +#define ATH5K_RATE_CODE_1M 0x1B +#define ATH5K_RATE_CODE_2M 0x1A +#define ATH5K_RATE_CODE_5_5M 0x19 +#define ATH5K_RATE_CODE_11M 0x18 +/* A and G */ +#define ATH5K_RATE_CODE_6M 0x0B +#define ATH5K_RATE_CODE_9M 0x0F +#define ATH5K_RATE_CODE_12M 0x0A +#define ATH5K_RATE_CODE_18M 0x0E +#define ATH5K_RATE_CODE_24M 0x09 +#define ATH5K_RATE_CODE_36M 0x0D +#define ATH5K_RATE_CODE_48M 0x08 +#define ATH5K_RATE_CODE_54M 0x0C + +/* Adding this flag to rate_code on B rates + * enables short preamble */ +#define AR5K_SET_SHORT_PREAMBLE 0x04 + +/* + * Crypto definitions + */ + +#define AR5K_KEYCACHE_SIZE 8 +extern bool ath5k_modparam_nohwcrypt; + +/***********************\ + HW RELATED DEFINITIONS +\***********************/ + +/* + * Misc definitions + */ +#define AR5K_RSSI_EP_MULTIPLIER (1 << 7) + +#define AR5K_ASSERT_ENTRY(_e, _s) do { \ + if (_e >= _s) \ + return false; \ +} while (0) + +/* + * Hardware interrupt abstraction + */ + +/** + * enum ath5k_int - Hardware interrupt masks helpers + * @AR5K_INT_RXOK: Frame successfully received + * @AR5K_INT_RXDESC: Request RX descriptor/Read RX descriptor + * @AR5K_INT_RXERR: Frame reception failed + * @AR5K_INT_RXNOFRM: No frame received within a specified time period + * @AR5K_INT_RXEOL: Reached "End Of List", means we need more RX descriptors + * @AR5K_INT_RXORN: Indicates we got RX FIFO overrun. Note that Rx overrun is + * not always fatal, on some chips we can continue operation + * without resetting the card, that's why %AR5K_INT_FATAL is not + * common for all chips. + * @AR5K_INT_RX_ALL: Mask to identify all RX related interrupts + * + * @AR5K_INT_TXOK: Frame transmission success + * @AR5K_INT_TXDESC: Request TX descriptor/Read TX status descriptor + * @AR5K_INT_TXERR: Frame transmission failure + * @AR5K_INT_TXEOL: Received End Of List for VEOL (Virtual End Of List). The + * Queue Control Unit (QCU) signals an EOL interrupt only if a + * descriptor's LinkPtr is NULL. For more details, refer to: + * "http://www.freepatentsonline.com/20030225739.html" + * @AR5K_INT_TXNOFRM: No frame was transmitted within a specified time period + * @AR5K_INT_TXURN: Indicates we got TX FIFO underrun. In such case we should + * increase the TX trigger threshold. + * @AR5K_INT_TX_ALL: Mask to identify all TX related interrupts + * + * @AR5K_INT_MIB: Indicates the either Management Information Base counters or + * one of the PHY error counters reached the maximum value and + * should be read and cleared. + * @AR5K_INT_SWI: Software triggered interrupt. + * @AR5K_INT_RXPHY: RX PHY Error + * @AR5K_INT_RXKCM: RX Key cache miss + * @AR5K_INT_SWBA: SoftWare Beacon Alert - indicates its time to send a + * beacon that must be handled in software. The alternative is if + * you have VEOL support, in that case you let the hardware deal + * with things. + * @AR5K_INT_BRSSI: Beacon received with an RSSI value below our threshold + * @AR5K_INT_BMISS: If in STA mode this indicates we have stopped seeing + * beacons from the AP have associated with, we should probably + * try to reassociate. When in IBSS mode this might mean we have + * not received any beacons from any local stations. Note that + * every station in an IBSS schedules to send beacons at the + * Target Beacon Transmission Time (TBTT) with a random backoff. + * @AR5K_INT_BNR: Beacon queue got triggered (DMA beacon alert) while empty. + * @AR5K_INT_TIM: Beacon with local station's TIM bit set + * @AR5K_INT_DTIM: Beacon with DTIM bit and zero DTIM count received + * @AR5K_INT_DTIM_SYNC: DTIM sync lost + * @AR5K_INT_GPIO: GPIO interrupt is used for RF Kill switches connected to + * our GPIO pins. + * @AR5K_INT_BCN_TIMEOUT: Beacon timeout, we waited after TBTT but got noting + * @AR5K_INT_CAB_TIMEOUT: We waited for CAB traffic after the beacon but got + * nothing or an incomplete CAB frame sequence. + * @AR5K_INT_QCBRORN: A queue got it's CBR counter expired + * @AR5K_INT_QCBRURN: A queue got triggered wile empty + * @AR5K_INT_QTRIG: A queue got triggered + * + * @AR5K_INT_FATAL: Fatal errors were encountered, typically caused by bus/DMA + * errors. Indicates we need to reset the card. + * @AR5K_INT_GLOBAL: Used to clear and set the IER + * @AR5K_INT_NOCARD: Signals the card has been removed + * @AR5K_INT_COMMON: Common interrupts shared among MACs with the same + * bit value + * + * These are mapped to take advantage of some common bits + * between the MACs, to be able to set intr properties + * easier. Some of them are not used yet inside hw.c. Most map + * to the respective hw interrupt value as they are common among different + * MACs. + */ +enum ath5k_int { + AR5K_INT_RXOK = 0x00000001, + AR5K_INT_RXDESC = 0x00000002, + AR5K_INT_RXERR = 0x00000004, + AR5K_INT_RXNOFRM = 0x00000008, + AR5K_INT_RXEOL = 0x00000010, + AR5K_INT_RXORN = 0x00000020, + AR5K_INT_TXOK = 0x00000040, + AR5K_INT_TXDESC = 0x00000080, + AR5K_INT_TXERR = 0x00000100, + AR5K_INT_TXNOFRM = 0x00000200, + AR5K_INT_TXEOL = 0x00000400, + AR5K_INT_TXURN = 0x00000800, + AR5K_INT_MIB = 0x00001000, + AR5K_INT_SWI = 0x00002000, + AR5K_INT_RXPHY = 0x00004000, + AR5K_INT_RXKCM = 0x00008000, + AR5K_INT_SWBA = 0x00010000, + AR5K_INT_BRSSI = 0x00020000, + AR5K_INT_BMISS = 0x00040000, + AR5K_INT_FATAL = 0x00080000, /* Non common */ + AR5K_INT_BNR = 0x00100000, /* Non common */ + AR5K_INT_TIM = 0x00200000, /* Non common */ + AR5K_INT_DTIM = 0x00400000, /* Non common */ + AR5K_INT_DTIM_SYNC = 0x00800000, /* Non common */ + AR5K_INT_GPIO = 0x01000000, + AR5K_INT_BCN_TIMEOUT = 0x02000000, /* Non common */ + AR5K_INT_CAB_TIMEOUT = 0x04000000, /* Non common */ + AR5K_INT_QCBRORN = 0x08000000, /* Non common */ + AR5K_INT_QCBRURN = 0x10000000, /* Non common */ + AR5K_INT_QTRIG = 0x20000000, /* Non common */ + AR5K_INT_GLOBAL = 0x80000000, + + AR5K_INT_TX_ALL = AR5K_INT_TXOK + | AR5K_INT_TXDESC + | AR5K_INT_TXERR + | AR5K_INT_TXNOFRM + | AR5K_INT_TXEOL + | AR5K_INT_TXURN, + + AR5K_INT_RX_ALL = AR5K_INT_RXOK + | AR5K_INT_RXDESC + | AR5K_INT_RXERR + | AR5K_INT_RXNOFRM + | AR5K_INT_RXEOL + | AR5K_INT_RXORN, + + AR5K_INT_COMMON = AR5K_INT_RXOK + | AR5K_INT_RXDESC + | AR5K_INT_RXERR + | AR5K_INT_RXNOFRM + | AR5K_INT_RXEOL + | AR5K_INT_RXORN + | AR5K_INT_TXOK + | AR5K_INT_TXDESC + | AR5K_INT_TXERR + | AR5K_INT_TXNOFRM + | AR5K_INT_TXEOL + | AR5K_INT_TXURN + | AR5K_INT_MIB + | AR5K_INT_SWI + | AR5K_INT_RXPHY + | AR5K_INT_RXKCM + | AR5K_INT_SWBA + | AR5K_INT_BRSSI + | AR5K_INT_BMISS + | AR5K_INT_GPIO + | AR5K_INT_GLOBAL, + + AR5K_INT_NOCARD = 0xffffffff +}; + +/** + * enum ath5k_calibration_mask - Mask which calibration is active at the moment + * @AR5K_CALIBRATION_FULL: Full calibration (AGC + SHORT) + * @AR5K_CALIBRATION_SHORT: Short calibration (NF + I/Q) + * @AR5K_CALIBRATION_NF: Noise Floor calibration + * @AR5K_CALIBRATION_ANI: Adaptive Noise Immunity + */ +enum ath5k_calibration_mask { + AR5K_CALIBRATION_FULL = 0x01, + AR5K_CALIBRATION_SHORT = 0x02, + AR5K_CALIBRATION_NF = 0x04, + AR5K_CALIBRATION_ANI = 0x08, +}; + +/** + * enum ath5k_power_mode - Power management modes + * @AR5K_PM_UNDEFINED: Undefined + * @AR5K_PM_AUTO: Allow card to sleep if possible + * @AR5K_PM_AWAKE: Force card to wake up + * @AR5K_PM_FULL_SLEEP: Force card to full sleep (DANGEROUS) + * @AR5K_PM_NETWORK_SLEEP: Allow to sleep for a specified duration + * + * Currently only PM_AWAKE is used, FULL_SLEEP and NETWORK_SLEEP/AUTO + * are also known to have problems on some cards. This is not a big + * problem though because we can have almost the same effect as + * FULL_SLEEP by putting card on warm reset (it's almost powered down). + */ +enum ath5k_power_mode { + AR5K_PM_UNDEFINED = 0, + AR5K_PM_AUTO, + AR5K_PM_AWAKE, + AR5K_PM_FULL_SLEEP, + AR5K_PM_NETWORK_SLEEP, +}; + +/* + * These match net80211 definitions (not used in + * mac80211). + * TODO: Clean this up + */ +#define AR5K_LED_INIT 0 /*IEEE80211_S_INIT*/ +#define AR5K_LED_SCAN 1 /*IEEE80211_S_SCAN*/ +#define AR5K_LED_AUTH 2 /*IEEE80211_S_AUTH*/ +#define AR5K_LED_ASSOC 3 /*IEEE80211_S_ASSOC*/ +#define AR5K_LED_RUN 4 /*IEEE80211_S_RUN*/ + +/* GPIO-controlled software LED */ +#define AR5K_SOFTLED_PIN 0 +#define AR5K_SOFTLED_ON 0 +#define AR5K_SOFTLED_OFF 1 + + +/* XXX: we *may* move cap_range stuff to struct wiphy */ +struct ath5k_capabilities { + /* + * Supported PHY modes + * (ie. AR5K_MODE_11A, AR5K_MODE_11B, ...) + */ + DECLARE_BITMAP(cap_mode, AR5K_MODE_MAX); + + /* + * Frequency range (without regulation restrictions) + */ + struct { + u16 range_2ghz_min; + u16 range_2ghz_max; + u16 range_5ghz_min; + u16 range_5ghz_max; + } cap_range; + + /* + * Values stored in the EEPROM (some of them...) + */ + struct ath5k_eeprom_info cap_eeprom; + + /* + * Queue information + */ + struct { + u8 q_tx_num; + } cap_queues; + + bool cap_has_phyerr_counters; + bool cap_has_mrr_support; + bool cap_needs_2GHz_ovr; +}; + +/* size of noise floor history (keep it a power of two) */ +#define ATH5K_NF_CAL_HIST_MAX 8 +struct ath5k_nfcal_hist { + s16 index; /* current index into nfval */ + s16 nfval[ATH5K_NF_CAL_HIST_MAX]; /* last few noise floors */ +}; + +#define ATH5K_LED_MAX_NAME_LEN 31 + +/* + * State for LED triggers + */ +struct ath5k_led { + char name[ATH5K_LED_MAX_NAME_LEN + 1]; /* name of the LED in sysfs */ + struct ath5k_hw *ah; /* driver state */ + struct led_classdev led_dev; /* led classdev */ +}; + +/* Rfkill */ +struct ath5k_rfkill { + /* GPIO PIN for rfkill */ + u16 gpio; + /* polarity of rfkill GPIO PIN */ + bool polarity; + /* RFKILL toggle tasklet */ + struct tasklet_struct toggleq; +}; + +/* statistics */ +struct ath5k_statistics { + /* antenna use */ + unsigned int antenna_rx[5]; /* frames count per antenna RX */ + unsigned int antenna_tx[5]; /* frames count per antenna TX */ + + /* frame errors */ + unsigned int rx_all_count; /* all RX frames, including errors */ + unsigned int tx_all_count; /* all TX frames, including errors */ + unsigned int rx_bytes_count; /* all RX bytes, including errored pkts + * and the MAC headers for each packet + */ + unsigned int tx_bytes_count; /* all TX bytes, including errored pkts + * and the MAC headers and padding for + * each packet. + */ + unsigned int rxerr_crc; + unsigned int rxerr_phy; + unsigned int rxerr_phy_code[32]; + unsigned int rxerr_fifo; + unsigned int rxerr_decrypt; + unsigned int rxerr_mic; + unsigned int rxerr_proc; + unsigned int rxerr_jumbo; + unsigned int txerr_retry; + unsigned int txerr_fifo; + unsigned int txerr_filt; + + /* MIB counters */ + unsigned int ack_fail; + unsigned int rts_fail; + unsigned int rts_ok; + unsigned int fcs_error; + unsigned int beacons; + + unsigned int mib_intr; + unsigned int rxorn_intr; + unsigned int rxeol_intr; +}; + +/* + * Misc defines + */ + +#define AR5K_MAX_GPIO 10 +#define AR5K_MAX_RF_BANKS 8 + +#if CHAN_DEBUG +#define ATH_CHAN_MAX (26 + 26 + 26 + 200 + 200) +#else +#define ATH_CHAN_MAX (14 + 14 + 14 + 252 + 20) +#endif + +#define ATH_RXBUF 40 /* number of RX buffers */ +#define ATH_TXBUF 200 /* number of TX buffers */ +#define ATH_BCBUF 4 /* number of beacon buffers */ +#define ATH5K_TXQ_LEN_MAX (ATH_TXBUF / 4) /* bufs per queue */ +#define ATH5K_TXQ_LEN_LOW (ATH5K_TXQ_LEN_MAX / 2) /* low mark */ + +/* Driver state associated with an instance of a device */ +struct ath5k_hw { + struct ath_common common; + + struct pci_dev *pdev; + struct device *dev; /* for dma mapping */ + int irq; + u16 devid; + void __iomem *iobase; /* address of the device */ + struct mutex lock; /* dev-level lock */ + struct ieee80211_hw *hw; /* IEEE 802.11 common */ + struct ieee80211_supported_band sbands[IEEE80211_NUM_BANDS]; + struct ieee80211_channel channels[ATH_CHAN_MAX]; + struct ieee80211_rate rates[IEEE80211_NUM_BANDS][AR5K_MAX_RATES]; + s8 rate_idx[IEEE80211_NUM_BANDS][AR5K_MAX_RATES]; + enum nl80211_iftype opmode; + +#ifdef CPTCFG_ATH5K_DEBUG + struct ath5k_dbg_info debug; /* debug info */ +#endif /* CPTCFG_ATH5K_DEBUG */ + + struct ath5k_buf *bufptr; /* allocated buffer ptr */ + struct ath5k_desc *desc; /* TX/RX descriptors */ + dma_addr_t desc_daddr; /* DMA (physical) address */ + size_t desc_len; /* size of TX/RX descriptors */ + + DECLARE_BITMAP(status, 4); +#define ATH_STAT_INVALID 0 /* disable hardware accesses */ +#define ATH_STAT_PROMISC 1 +#define ATH_STAT_LEDSOFT 2 /* enable LED gpio status */ +#define ATH_STAT_STARTED 3 /* opened & irqs enabled */ + + unsigned int filter_flags; /* HW flags, AR5K_RX_FILTER_* */ + struct ieee80211_channel *curchan; /* current h/w channel */ + + u16 nvifs; + + enum ath5k_int imask; /* interrupt mask copy */ + + spinlock_t irqlock; + bool rx_pending; /* rx tasklet pending */ + bool tx_pending; /* tx tasklet pending */ + + u8 bssidmask[ETH_ALEN]; + + unsigned int led_pin, /* GPIO pin for driving LED */ + led_on; /* pin setting for LED on */ + + struct work_struct reset_work; /* deferred chip reset */ + struct work_struct calib_work; /* deferred phy calibration */ + + struct list_head rxbuf; /* receive buffer */ + spinlock_t rxbuflock; + u32 *rxlink; /* link ptr in last RX desc */ + struct tasklet_struct rxtq; /* rx intr tasklet */ + struct ath5k_led rx_led; /* rx led */ + + struct list_head txbuf; /* transmit buffer */ + spinlock_t txbuflock; + unsigned int txbuf_len; /* buf count in txbuf list */ + struct ath5k_txq txqs[AR5K_NUM_TX_QUEUES]; /* tx queues */ + struct tasklet_struct txtq; /* tx intr tasklet */ + struct ath5k_led tx_led; /* tx led */ + + struct ath5k_rfkill rf_kill; + + spinlock_t block; /* protects beacon */ + struct tasklet_struct beacontq; /* beacon intr tasklet */ + struct list_head bcbuf; /* beacon buffer */ + struct ieee80211_vif *bslot[ATH_BCBUF]; + u16 num_ap_vifs; + u16 num_adhoc_vifs; + u16 num_mesh_vifs; + unsigned int bhalq, /* SW q for outgoing beacons */ + bmisscount, /* missed beacon transmits */ + bintval, /* beacon interval in TU */ + bsent; + unsigned int nexttbtt; /* next beacon time in TU */ + struct ath5k_txq *cabq; /* content after beacon */ + + bool assoc; /* associate state */ + bool enable_beacon; /* true if beacons are on */ + + struct ath5k_statistics stats; + + struct ath5k_ani_state ani_state; + struct tasklet_struct ani_tasklet; /* ANI calibration */ + + struct delayed_work tx_complete_work; + + struct survey_info survey; /* collected survey info */ + + enum ath5k_int ah_imr; + + struct ieee80211_channel *ah_current_channel; + bool ah_iq_cal_needed; + bool ah_single_chip; + + enum ath5k_version ah_version; + enum ath5k_radio ah_radio; + u32 ah_mac_srev; + u16 ah_mac_version; + u16 ah_phy_revision; + u16 ah_radio_5ghz_revision; + u16 ah_radio_2ghz_revision; + +#define ah_modes ah_capabilities.cap_mode +#define ah_ee_version ah_capabilities.cap_eeprom.ee_version + + u8 ah_retry_long; + u8 ah_retry_short; + + u32 ah_use_32khz_clock; + + u8 ah_coverage_class; + bool ah_ack_bitrate_high; + u8 ah_bwmode; + bool ah_short_slot; + + /* Antenna Control */ + u32 ah_ant_ctl[AR5K_EEPROM_N_MODES][AR5K_ANT_MAX]; + u8 ah_ant_mode; + u8 ah_tx_ant; + u8 ah_def_ant; + + struct ath5k_capabilities ah_capabilities; + + struct ath5k_txq_info ah_txq[AR5K_NUM_TX_QUEUES]; + u32 ah_txq_status; + u32 ah_txq_imr_txok; + u32 ah_txq_imr_txerr; + u32 ah_txq_imr_txurn; + u32 ah_txq_imr_txdesc; + u32 ah_txq_imr_txeol; + u32 ah_txq_imr_cbrorn; + u32 ah_txq_imr_cbrurn; + u32 ah_txq_imr_qtrig; + u32 ah_txq_imr_nofrm; + + u32 ah_txq_isr_txok_all; + u32 ah_txq_isr_txurn; + u32 ah_txq_isr_qcborn; + u32 ah_txq_isr_qcburn; + u32 ah_txq_isr_qtrig; + + u32 *ah_rf_banks; + size_t ah_rf_banks_size; + size_t ah_rf_regs_count; + struct ath5k_gain ah_gain; + u8 ah_offset[AR5K_MAX_RF_BANKS]; + + + struct { + /* Temporary tables used for interpolation */ + u8 tmpL[AR5K_EEPROM_N_PD_GAINS] + [AR5K_EEPROM_POWER_TABLE_SIZE]; + u8 tmpR[AR5K_EEPROM_N_PD_GAINS] + [AR5K_EEPROM_POWER_TABLE_SIZE]; + u8 txp_pd_table[AR5K_EEPROM_POWER_TABLE_SIZE * 2]; + u16 txp_rates_power_table[AR5K_MAX_RATES]; + u8 txp_min_idx; + bool txp_tpc; + /* Values in 0.25dB units */ + s16 txp_min_pwr; + s16 txp_max_pwr; + s16 txp_cur_pwr; + /* Values in 0.5dB units */ + s16 txp_offset; + s16 txp_ofdm; + s16 txp_cck_ofdm_gainf_delta; + /* Value in dB units */ + s16 txp_cck_ofdm_pwr_delta; + bool txp_setup; + int txp_requested; /* Requested tx power in dBm */ + } ah_txpower; + + struct ath5k_nfcal_hist ah_nfcal_hist; + + /* average beacon RSSI in our BSS (used by ANI) */ + struct ewma ah_beacon_rssi_avg; + + /* noise floor from last periodic calibration */ + s32 ah_noise_floor; + + /* Calibration timestamp */ + unsigned long ah_cal_next_full; + unsigned long ah_cal_next_short; + unsigned long ah_cal_next_ani; + + /* Calibration mask */ + u8 ah_cal_mask; + + /* + * Function pointers + */ + int (*ah_setup_tx_desc)(struct ath5k_hw *, struct ath5k_desc *, + unsigned int, unsigned int, int, enum ath5k_pkt_type, + unsigned int, unsigned int, unsigned int, unsigned int, + unsigned int, unsigned int, unsigned int, unsigned int); + int (*ah_proc_tx_desc)(struct ath5k_hw *, struct ath5k_desc *, + struct ath5k_tx_status *); + int (*ah_proc_rx_desc)(struct ath5k_hw *, struct ath5k_desc *, + struct ath5k_rx_status *); +}; + +struct ath_bus_ops { + enum ath_bus_type ath_bus_type; + void (*read_cachesize)(struct ath_common *common, int *csz); + bool (*eeprom_read)(struct ath_common *common, u32 off, u16 *data); + int (*eeprom_read_mac)(struct ath5k_hw *ah, u8 *mac); +}; + +/* + * Prototypes + */ +extern const struct ieee80211_ops ath5k_hw_ops; + +/* Initialization and detach functions */ +int ath5k_hw_init(struct ath5k_hw *ah); +void ath5k_hw_deinit(struct ath5k_hw *ah); + +int ath5k_sysfs_register(struct ath5k_hw *ah); +void ath5k_sysfs_unregister(struct ath5k_hw *ah); + +/*Chip id helper functions */ +int ath5k_hw_read_srev(struct ath5k_hw *ah); + +/* LED functions */ +int ath5k_init_leds(struct ath5k_hw *ah); +void ath5k_led_enable(struct ath5k_hw *ah); +void ath5k_led_off(struct ath5k_hw *ah); +void ath5k_unregister_leds(struct ath5k_hw *ah); + + +/* Reset Functions */ +int ath5k_hw_nic_wakeup(struct ath5k_hw *ah, struct ieee80211_channel *channel); +int ath5k_hw_on_hold(struct ath5k_hw *ah); +int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode, + struct ieee80211_channel *channel, bool fast, bool skip_pcu); +int ath5k_hw_register_timeout(struct ath5k_hw *ah, u32 reg, u32 flag, u32 val, + bool is_set); +/* Power management functions */ + + +/* Clock rate related functions */ +unsigned int ath5k_hw_htoclock(struct ath5k_hw *ah, unsigned int usec); +unsigned int ath5k_hw_clocktoh(struct ath5k_hw *ah, unsigned int clock); +void ath5k_hw_set_clockrate(struct ath5k_hw *ah); + + +/* DMA Related Functions */ +void ath5k_hw_start_rx_dma(struct ath5k_hw *ah); +u32 ath5k_hw_get_rxdp(struct ath5k_hw *ah); +int ath5k_hw_set_rxdp(struct ath5k_hw *ah, u32 phys_addr); +int ath5k_hw_start_tx_dma(struct ath5k_hw *ah, unsigned int queue); +int ath5k_hw_stop_beacon_queue(struct ath5k_hw *ah, unsigned int queue); +u32 ath5k_hw_get_txdp(struct ath5k_hw *ah, unsigned int queue); +int ath5k_hw_set_txdp(struct ath5k_hw *ah, unsigned int queue, + u32 phys_addr); +int ath5k_hw_update_tx_triglevel(struct ath5k_hw *ah, bool increase); +/* Interrupt handling */ +bool ath5k_hw_is_intr_pending(struct ath5k_hw *ah); +int ath5k_hw_get_isr(struct ath5k_hw *ah, enum ath5k_int *interrupt_mask); +enum ath5k_int ath5k_hw_set_imr(struct ath5k_hw *ah, enum ath5k_int new_mask); +void ath5k_hw_update_mib_counters(struct ath5k_hw *ah); +/* Init/Stop functions */ +void ath5k_hw_dma_init(struct ath5k_hw *ah); +int ath5k_hw_dma_stop(struct ath5k_hw *ah); + +/* EEPROM access functions */ +int ath5k_eeprom_init(struct ath5k_hw *ah); +void ath5k_eeprom_detach(struct ath5k_hw *ah); +int ath5k_eeprom_mode_from_channel(struct ath5k_hw *ah, + struct ieee80211_channel *channel); + +/* Protocol Control Unit Functions */ +/* Helpers */ +int ath5k_hw_get_frame_duration(struct ath5k_hw *ah, enum ieee80211_band band, + int len, struct ieee80211_rate *rate, bool shortpre); +unsigned int ath5k_hw_get_default_slottime(struct ath5k_hw *ah); +unsigned int ath5k_hw_get_default_sifs(struct ath5k_hw *ah); +int ath5k_hw_set_opmode(struct ath5k_hw *ah, enum nl80211_iftype opmode); +void ath5k_hw_set_coverage_class(struct ath5k_hw *ah, u8 coverage_class); +/* RX filter control*/ +int ath5k_hw_set_lladdr(struct ath5k_hw *ah, const u8 *mac); +void ath5k_hw_set_bssid(struct ath5k_hw *ah); +void ath5k_hw_set_bssid_mask(struct ath5k_hw *ah, const u8 *mask); +void ath5k_hw_set_mcast_filter(struct ath5k_hw *ah, u32 filter0, u32 filter1); +u32 ath5k_hw_get_rx_filter(struct ath5k_hw *ah); +void ath5k_hw_set_rx_filter(struct ath5k_hw *ah, u32 filter); +/* Receive (DRU) start/stop functions */ +void ath5k_hw_start_rx_pcu(struct ath5k_hw *ah); +void ath5k_hw_stop_rx_pcu(struct ath5k_hw *ah); +/* Beacon control functions */ +u64 ath5k_hw_get_tsf64(struct ath5k_hw *ah); +void ath5k_hw_set_tsf64(struct ath5k_hw *ah, u64 tsf64); +void ath5k_hw_reset_tsf(struct ath5k_hw *ah); +void ath5k_hw_init_beacon_timers(struct ath5k_hw *ah, u32 next_beacon, + u32 interval); +bool ath5k_hw_check_beacon_timers(struct ath5k_hw *ah, int intval); +/* Init function */ +void ath5k_hw_pcu_init(struct ath5k_hw *ah, enum nl80211_iftype op_mode); + +/* Queue Control Unit, DFS Control Unit Functions */ +int ath5k_hw_get_tx_queueprops(struct ath5k_hw *ah, int queue, + struct ath5k_txq_info *queue_info); +int ath5k_hw_set_tx_queueprops(struct ath5k_hw *ah, int queue, + const struct ath5k_txq_info *queue_info); +int ath5k_hw_setup_tx_queue(struct ath5k_hw *ah, + enum ath5k_tx_queue queue_type, + struct ath5k_txq_info *queue_info); +void ath5k_hw_set_tx_retry_limits(struct ath5k_hw *ah, + unsigned int queue); +u32 ath5k_hw_num_tx_pending(struct ath5k_hw *ah, unsigned int queue); +void ath5k_hw_release_tx_queue(struct ath5k_hw *ah, unsigned int queue); +int ath5k_hw_reset_tx_queue(struct ath5k_hw *ah, unsigned int queue); +int ath5k_hw_set_ifs_intervals(struct ath5k_hw *ah, unsigned int slot_time); +/* Init function */ +int ath5k_hw_init_queues(struct ath5k_hw *ah); + +/* Hardware Descriptor Functions */ +int ath5k_hw_init_desc_functions(struct ath5k_hw *ah); +int ath5k_hw_setup_rx_desc(struct ath5k_hw *ah, struct ath5k_desc *desc, + u32 size, unsigned int flags); +int ath5k_hw_setup_mrr_tx_desc(struct ath5k_hw *ah, struct ath5k_desc *desc, + unsigned int tx_rate1, u_int tx_tries1, u_int tx_rate2, + u_int tx_tries2, unsigned int tx_rate3, u_int tx_tries3); + + +/* GPIO Functions */ +void ath5k_hw_set_ledstate(struct ath5k_hw *ah, unsigned int state); +int ath5k_hw_set_gpio_input(struct ath5k_hw *ah, u32 gpio); +int ath5k_hw_set_gpio_output(struct ath5k_hw *ah, u32 gpio); +u32 ath5k_hw_get_gpio(struct ath5k_hw *ah, u32 gpio); +int ath5k_hw_set_gpio(struct ath5k_hw *ah, u32 gpio, u32 val); +void ath5k_hw_set_gpio_intr(struct ath5k_hw *ah, unsigned int gpio, + u32 interrupt_level); + + +/* RFkill Functions */ +void ath5k_rfkill_hw_start(struct ath5k_hw *ah); +void ath5k_rfkill_hw_stop(struct ath5k_hw *ah); + + +/* Misc functions TODO: Cleanup */ +int ath5k_hw_set_capabilities(struct ath5k_hw *ah); +int ath5k_hw_enable_pspoll(struct ath5k_hw *ah, u8 *bssid, u16 assoc_id); +int ath5k_hw_disable_pspoll(struct ath5k_hw *ah); + + +/* Initial register settings functions */ +int ath5k_hw_write_initvals(struct ath5k_hw *ah, u8 mode, bool change_channel); + + +/* PHY functions */ +/* Misc PHY functions */ +u16 ath5k_hw_radio_revision(struct ath5k_hw *ah, enum ieee80211_band band); +int ath5k_hw_phy_disable(struct ath5k_hw *ah); +/* Gain_F optimization */ +enum ath5k_rfgain ath5k_hw_gainf_calibrate(struct ath5k_hw *ah); +int ath5k_hw_rfgain_opt_init(struct ath5k_hw *ah); +/* PHY/RF channel functions */ +bool ath5k_channel_ok(struct ath5k_hw *ah, struct ieee80211_channel *channel); +/* PHY calibration */ +void ath5k_hw_init_nfcal_hist(struct ath5k_hw *ah); +int ath5k_hw_phy_calibrate(struct ath5k_hw *ah, + struct ieee80211_channel *channel); +void ath5k_hw_update_noise_floor(struct ath5k_hw *ah); +/* Spur mitigation */ +bool ath5k_hw_chan_has_spur_noise(struct ath5k_hw *ah, + struct ieee80211_channel *channel); +/* Antenna control */ +void ath5k_hw_set_antenna_mode(struct ath5k_hw *ah, u8 ant_mode); +void ath5k_hw_set_antenna_switch(struct ath5k_hw *ah, u8 ee_mode); +/* TX power setup */ +int ath5k_hw_set_txpower_limit(struct ath5k_hw *ah, u8 txpower); +/* Init function */ +int ath5k_hw_phy_init(struct ath5k_hw *ah, struct ieee80211_channel *channel, + u8 mode, bool fast); + +/* + * Functions used internally + */ + +static inline struct ath_common *ath5k_hw_common(struct ath5k_hw *ah) +{ + return &ah->common; +} + +static inline struct ath_regulatory *ath5k_hw_regulatory(struct ath5k_hw *ah) +{ + return &(ath5k_hw_common(ah)->regulatory); +} + +#ifdef CONFIG_ATHEROS_AR231X +#define AR5K_AR2315_PCI_BASE ((void __iomem *)0xb0100000) + +static inline void __iomem *ath5k_ahb_reg(struct ath5k_hw *ah, u16 reg) +{ + /* On AR2315 and AR2317 the PCI clock domain registers + * are outside of the WMAC register space */ + if (unlikely((reg >= 0x4000) && (reg < 0x5000) && + (ah->ah_mac_srev >= AR5K_SREV_AR2315_R6))) + return AR5K_AR2315_PCI_BASE + reg; + + return ah->iobase + reg; +} + +static inline u32 ath5k_hw_reg_read(struct ath5k_hw *ah, u16 reg) +{ + return ioread32(ath5k_ahb_reg(ah, reg)); +} + +static inline void ath5k_hw_reg_write(struct ath5k_hw *ah, u32 val, u16 reg) +{ + iowrite32(val, ath5k_ahb_reg(ah, reg)); +} + +#else + +static inline u32 ath5k_hw_reg_read(struct ath5k_hw *ah, u16 reg) +{ + return ioread32(ah->iobase + reg); +} + +static inline void ath5k_hw_reg_write(struct ath5k_hw *ah, u32 val, u16 reg) +{ + iowrite32(val, ah->iobase + reg); +} + +#endif + +static inline enum ath_bus_type ath5k_get_bus_type(struct ath5k_hw *ah) +{ + return ath5k_hw_common(ah)->bus_ops->ath_bus_type; +} + +static inline void ath5k_read_cachesize(struct ath_common *common, int *csz) +{ + common->bus_ops->read_cachesize(common, csz); +} + +static inline bool ath5k_hw_nvram_read(struct ath5k_hw *ah, u32 off, u16 *data) +{ + struct ath_common *common = ath5k_hw_common(ah); + return common->bus_ops->eeprom_read(common, off, data); +} + +static inline u32 ath5k_hw_bitswap(u32 val, unsigned int bits) +{ + u32 retval = 0, bit, i; + + for (i = 0; i < bits; i++) { + bit = (val >> i) & 1; + retval = (retval << 1) | bit; + } + + return retval; +} + +#endif diff --git a/drivers/net/wireless/ath/ath5k/attach.c b/drivers/net/wireless/ath/ath5k/attach.c new file mode 100644 index 0000000..7106547 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/attach.c @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter + * Copyright (c) 2006-2008 Nick Kossifidis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/*************************************\ +* Attach/Detach Functions and helpers * +\*************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include "ath5k.h" +#include "reg.h" +#include "debug.h" + +/** + * ath5k_hw_post() - Power On Self Test helper function + * @ah: The &struct ath5k_hw + */ +static int ath5k_hw_post(struct ath5k_hw *ah) +{ + + static const u32 static_pattern[4] = { + 0x55555555, 0xaaaaaaaa, + 0x66666666, 0x99999999 + }; + static const u16 regs[2] = { AR5K_STA_ID0, AR5K_PHY(8) }; + int i, c; + u16 cur_reg; + u32 var_pattern; + u32 init_val; + u32 cur_val; + + for (c = 0; c < 2; c++) { + + cur_reg = regs[c]; + + /* Save previous value */ + init_val = ath5k_hw_reg_read(ah, cur_reg); + + for (i = 0; i < 256; i++) { + var_pattern = i << 16 | i; + ath5k_hw_reg_write(ah, var_pattern, cur_reg); + cur_val = ath5k_hw_reg_read(ah, cur_reg); + + if (cur_val != var_pattern) { + ATH5K_ERR(ah, "POST Failed !!!\n"); + return -EAGAIN; + } + + /* Found on ndiswrapper dumps */ + var_pattern = 0x0039080f; + ath5k_hw_reg_write(ah, var_pattern, cur_reg); + } + + for (i = 0; i < 4; i++) { + var_pattern = static_pattern[i]; + ath5k_hw_reg_write(ah, var_pattern, cur_reg); + cur_val = ath5k_hw_reg_read(ah, cur_reg); + + if (cur_val != var_pattern) { + ATH5K_ERR(ah, "POST Failed !!!\n"); + return -EAGAIN; + } + + /* Found on ndiswrapper dumps */ + var_pattern = 0x003b080f; + ath5k_hw_reg_write(ah, var_pattern, cur_reg); + } + + /* Restore previous value */ + ath5k_hw_reg_write(ah, init_val, cur_reg); + + } + + return 0; + +} + +/** + * ath5k_hw_init() - Check if hw is supported and init the needed structs + * @ah: The &struct ath5k_hw associated with the device + * + * Check if the device is supported, perform a POST and initialize the needed + * structs. Returns -ENOMEM if we don't have memory for the needed structs, + * -ENODEV if the device is not supported or prints an error msg if something + * else went wrong. + */ +int ath5k_hw_init(struct ath5k_hw *ah) +{ + static const u8 zero_mac[ETH_ALEN] = { }; + struct ath_common *common = ath5k_hw_common(ah); + struct pci_dev *pdev = ah->pdev; + struct ath5k_eeprom_info *ee; + int ret; + u32 srev; + + /* + * HW information + */ + ah->ah_bwmode = AR5K_BWMODE_DEFAULT; + ah->ah_txpower.txp_tpc = AR5K_TUNE_TPC_TXPOWER; + ah->ah_imr = 0; + ah->ah_retry_short = AR5K_INIT_RETRY_SHORT; + ah->ah_retry_long = AR5K_INIT_RETRY_LONG; + ah->ah_ant_mode = AR5K_ANTMODE_DEFAULT; + ah->ah_noise_floor = -95; /* until first NF calibration is run */ + ah->ani_state.ani_mode = ATH5K_ANI_MODE_AUTO; + ah->ah_current_channel = &ah->channels[0]; + + /* + * Find the mac version + */ + ath5k_hw_read_srev(ah); + srev = ah->ah_mac_srev; + if (srev < AR5K_SREV_AR5311) + ah->ah_version = AR5K_AR5210; + else if (srev < AR5K_SREV_AR5212) + ah->ah_version = AR5K_AR5211; + else + ah->ah_version = AR5K_AR5212; + + /* Get the MAC version */ + ah->ah_mac_version = AR5K_REG_MS(srev, AR5K_SREV_VER); + + /* Fill the ath5k_hw struct with the needed functions */ + ret = ath5k_hw_init_desc_functions(ah); + if (ret) + goto err; + + /* Bring device out of sleep and reset its units */ + ret = ath5k_hw_nic_wakeup(ah, NULL); + if (ret) + goto err; + + /* Get PHY and RADIO revisions */ + ah->ah_phy_revision = ath5k_hw_reg_read(ah, AR5K_PHY_CHIP_ID) & + 0xffffffff; + ah->ah_radio_5ghz_revision = ath5k_hw_radio_revision(ah, + IEEE80211_BAND_5GHZ); + + /* Try to identify radio chip based on its srev */ + switch (ah->ah_radio_5ghz_revision & 0xf0) { + case AR5K_SREV_RAD_5111: + ah->ah_radio = AR5K_RF5111; + ah->ah_single_chip = false; + ah->ah_radio_2ghz_revision = ath5k_hw_radio_revision(ah, + IEEE80211_BAND_2GHZ); + break; + case AR5K_SREV_RAD_5112: + case AR5K_SREV_RAD_2112: + ah->ah_radio = AR5K_RF5112; + ah->ah_single_chip = false; + ah->ah_radio_2ghz_revision = ath5k_hw_radio_revision(ah, + IEEE80211_BAND_2GHZ); + break; + case AR5K_SREV_RAD_2413: + ah->ah_radio = AR5K_RF2413; + ah->ah_single_chip = true; + break; + case AR5K_SREV_RAD_5413: + ah->ah_radio = AR5K_RF5413; + ah->ah_single_chip = true; + break; + case AR5K_SREV_RAD_2316: + ah->ah_radio = AR5K_RF2316; + ah->ah_single_chip = true; + break; + case AR5K_SREV_RAD_2317: + ah->ah_radio = AR5K_RF2317; + ah->ah_single_chip = true; + break; + case AR5K_SREV_RAD_5424: + if (ah->ah_mac_version == AR5K_SREV_AR2425 || + ah->ah_mac_version == AR5K_SREV_AR2417) { + ah->ah_radio = AR5K_RF2425; + ah->ah_single_chip = true; + } else { + ah->ah_radio = AR5K_RF5413; + ah->ah_single_chip = true; + } + break; + default: + /* Identify radio based on mac/phy srev */ + if (ah->ah_version == AR5K_AR5210) { + ah->ah_radio = AR5K_RF5110; + ah->ah_single_chip = false; + } else if (ah->ah_version == AR5K_AR5211) { + ah->ah_radio = AR5K_RF5111; + ah->ah_single_chip = false; + ah->ah_radio_2ghz_revision = ath5k_hw_radio_revision(ah, + IEEE80211_BAND_2GHZ); + } else if (ah->ah_mac_version == (AR5K_SREV_AR2425 >> 4) || + ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4) || + ah->ah_phy_revision == AR5K_SREV_PHY_2425) { + ah->ah_radio = AR5K_RF2425; + ah->ah_single_chip = true; + ah->ah_radio_5ghz_revision = AR5K_SREV_RAD_2425; + } else if (srev == AR5K_SREV_AR5213A && + ah->ah_phy_revision == AR5K_SREV_PHY_5212B) { + ah->ah_radio = AR5K_RF5112; + ah->ah_single_chip = false; + ah->ah_radio_5ghz_revision = AR5K_SREV_RAD_5112B; + } else if (ah->ah_mac_version == (AR5K_SREV_AR2415 >> 4) || + ah->ah_mac_version == (AR5K_SREV_AR2315_R6 >> 4)) { + ah->ah_radio = AR5K_RF2316; + ah->ah_single_chip = true; + ah->ah_radio_5ghz_revision = AR5K_SREV_RAD_2316; + } else if (ah->ah_mac_version == (AR5K_SREV_AR5414 >> 4) || + ah->ah_phy_revision == AR5K_SREV_PHY_5413) { + ah->ah_radio = AR5K_RF5413; + ah->ah_single_chip = true; + ah->ah_radio_5ghz_revision = AR5K_SREV_RAD_5413; + } else if (ah->ah_mac_version == (AR5K_SREV_AR2414 >> 4) || + ah->ah_phy_revision == AR5K_SREV_PHY_2413) { + ah->ah_radio = AR5K_RF2413; + ah->ah_single_chip = true; + ah->ah_radio_5ghz_revision = AR5K_SREV_RAD_2413; + } else { + ATH5K_ERR(ah, "Couldn't identify radio revision.\n"); + ret = -ENODEV; + goto err; + } + } + + + /* Return on unsupported chips (unsupported eeprom etc) */ + if ((srev >= AR5K_SREV_AR5416) && (srev < AR5K_SREV_AR2425)) { + ATH5K_ERR(ah, "Device not yet supported.\n"); + ret = -ENODEV; + goto err; + } + + /* + * POST + */ + ret = ath5k_hw_post(ah); + if (ret) + goto err; + + /* Enable pci core retry fix on Hainan (5213A) and later chips */ + if (srev >= AR5K_SREV_AR5213A) + AR5K_REG_ENABLE_BITS(ah, AR5K_PCICFG, AR5K_PCICFG_RETRY_FIX); + + /* + * Get card capabilities, calibration values etc + * TODO: EEPROM work + */ + ret = ath5k_eeprom_init(ah); + if (ret) { + ATH5K_ERR(ah, "unable to init EEPROM\n"); + goto err; + } + + ee = &ah->ah_capabilities.cap_eeprom; + + /* + * Write PCI-E power save settings + */ + if ((ah->ah_version == AR5K_AR5212) && pdev && (pci_is_pcie(pdev))) { + ath5k_hw_reg_write(ah, 0x9248fc00, AR5K_PCIE_SERDES); + ath5k_hw_reg_write(ah, 0x24924924, AR5K_PCIE_SERDES); + + /* Shut off RX when elecidle is asserted */ + ath5k_hw_reg_write(ah, 0x28000039, AR5K_PCIE_SERDES); + ath5k_hw_reg_write(ah, 0x53160824, AR5K_PCIE_SERDES); + + /* If serdes programming is enabled, increase PCI-E + * tx power for systems with long trace from host + * to minicard connector. */ + if (ee->ee_serdes) + ath5k_hw_reg_write(ah, 0xe5980579, AR5K_PCIE_SERDES); + else + ath5k_hw_reg_write(ah, 0xf6800579, AR5K_PCIE_SERDES); + + /* Shut off PLL and CLKREQ active in L1 */ + ath5k_hw_reg_write(ah, 0x001defff, AR5K_PCIE_SERDES); + + /* Preserve other settings */ + ath5k_hw_reg_write(ah, 0x1aaabe40, AR5K_PCIE_SERDES); + ath5k_hw_reg_write(ah, 0xbe105554, AR5K_PCIE_SERDES); + ath5k_hw_reg_write(ah, 0x000e3007, AR5K_PCIE_SERDES); + + /* Reset SERDES to load new settings */ + ath5k_hw_reg_write(ah, 0x00000000, AR5K_PCIE_SERDES_RESET); + usleep_range(1000, 1500); + } + + /* Get misc capabilities */ + ret = ath5k_hw_set_capabilities(ah); + if (ret) { + ATH5K_ERR(ah, "unable to get device capabilities\n"); + goto err; + } + + /* Crypto settings */ + common->keymax = (ah->ah_version == AR5K_AR5210 ? + AR5K_KEYTABLE_SIZE_5210 : AR5K_KEYTABLE_SIZE_5211); + + if (srev >= AR5K_SREV_AR5212_V4 && + (ee->ee_version < AR5K_EEPROM_VERSION_5_0 || + !AR5K_EEPROM_AES_DIS(ee->ee_misc5))) + common->crypt_caps |= ATH_CRYPT_CAP_CIPHER_AESCCM; + + if (srev >= AR5K_SREV_AR2414) { + common->crypt_caps |= ATH_CRYPT_CAP_MIC_COMBINED; + AR5K_REG_ENABLE_BITS(ah, AR5K_MISC_MODE, + AR5K_MISC_MODE_COMBINED_MIC); + } + + /* MAC address is cleared until add_interface */ + ath5k_hw_set_lladdr(ah, zero_mac); + + /* Set BSSID to bcast address: ff:ff:ff:ff:ff:ff for now */ + memcpy(common->curbssid, ath_bcast_mac, ETH_ALEN); + ath5k_hw_set_bssid(ah); + ath5k_hw_set_opmode(ah, ah->opmode); + + ath5k_hw_rfgain_opt_init(ah); + + ath5k_hw_init_nfcal_hist(ah); + + /* turn on HW LEDs */ + ath5k_hw_set_ledstate(ah, AR5K_LED_INIT); + + return 0; +err: + return ret; +} + +/** + * ath5k_hw_deinit() - Free the &struct ath5k_hw + * @ah: The &struct ath5k_hw + */ +void ath5k_hw_deinit(struct ath5k_hw *ah) +{ + __set_bit(ATH_STAT_INVALID, ah->status); + + if (ah->ah_rf_banks != NULL) + kfree(ah->ah_rf_banks); + + ath5k_eeprom_detach(ah); + + /* assume interrupts are down */ +} diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c new file mode 100644 index 0000000..8c144f4 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/base.c @@ -0,0 +1,3082 @@ +/*- + * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting + * Copyright (c) 2004-2005 Atheros Communications, Inc. + * Copyright (c) 2006 Devicescape Software, Inc. + * Copyright (c) 2007 Jiri Slaby + * Copyright (c) 2007 Luis R. Rodriguez + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "base.h" +#include "reg.h" +#include "debug.h" +#include "ani.h" +#include "ath5k.h" +#include "../regd.h" + +#define CREATE_TRACE_POINTS +#include "trace.h" + +bool ath5k_modparam_nohwcrypt; +module_param_named(nohwcrypt, ath5k_modparam_nohwcrypt, bool, S_IRUGO); +MODULE_PARM_DESC(nohwcrypt, "Disable hardware encryption."); + +static bool modparam_fastchanswitch; +module_param_named(fastchanswitch, modparam_fastchanswitch, bool, S_IRUGO); +MODULE_PARM_DESC(fastchanswitch, "Enable fast channel switching for AR2413/AR5413 radios."); + +static bool ath5k_modparam_no_hw_rfkill_switch; +module_param_named(no_hw_rfkill_switch, ath5k_modparam_no_hw_rfkill_switch, + bool, S_IRUGO); +MODULE_PARM_DESC(no_hw_rfkill_switch, "Ignore the GPIO RFKill switch state"); + + +/* Module info */ +MODULE_AUTHOR("Jiri Slaby"); +MODULE_AUTHOR("Nick Kossifidis"); +MODULE_DESCRIPTION("Support for 5xxx series of Atheros 802.11 wireless LAN cards."); +MODULE_SUPPORTED_DEVICE("Atheros 5xxx WLAN cards"); +MODULE_LICENSE("Dual BSD/GPL"); + +static int ath5k_init(struct ieee80211_hw *hw); +static int ath5k_reset(struct ath5k_hw *ah, struct ieee80211_channel *chan, + bool skip_pcu); + +/* Known SREVs */ +static const struct ath5k_srev_name srev_names[] = { +#ifdef CONFIG_ATHEROS_AR231X + { "5312", AR5K_VERSION_MAC, AR5K_SREV_AR5312_R2 }, + { "5312", AR5K_VERSION_MAC, AR5K_SREV_AR5312_R7 }, + { "2313", AR5K_VERSION_MAC, AR5K_SREV_AR2313_R8 }, + { "2315", AR5K_VERSION_MAC, AR5K_SREV_AR2315_R6 }, + { "2315", AR5K_VERSION_MAC, AR5K_SREV_AR2315_R7 }, + { "2317", AR5K_VERSION_MAC, AR5K_SREV_AR2317_R1 }, + { "2317", AR5K_VERSION_MAC, AR5K_SREV_AR2317_R2 }, +#else + { "5210", AR5K_VERSION_MAC, AR5K_SREV_AR5210 }, + { "5311", AR5K_VERSION_MAC, AR5K_SREV_AR5311 }, + { "5311A", AR5K_VERSION_MAC, AR5K_SREV_AR5311A }, + { "5311B", AR5K_VERSION_MAC, AR5K_SREV_AR5311B }, + { "5211", AR5K_VERSION_MAC, AR5K_SREV_AR5211 }, + { "5212", AR5K_VERSION_MAC, AR5K_SREV_AR5212 }, + { "5213", AR5K_VERSION_MAC, AR5K_SREV_AR5213 }, + { "5213A", AR5K_VERSION_MAC, AR5K_SREV_AR5213A }, + { "2413", AR5K_VERSION_MAC, AR5K_SREV_AR2413 }, + { "2414", AR5K_VERSION_MAC, AR5K_SREV_AR2414 }, + { "5424", AR5K_VERSION_MAC, AR5K_SREV_AR5424 }, + { "5413", AR5K_VERSION_MAC, AR5K_SREV_AR5413 }, + { "5414", AR5K_VERSION_MAC, AR5K_SREV_AR5414 }, + { "2415", AR5K_VERSION_MAC, AR5K_SREV_AR2415 }, + { "5416", AR5K_VERSION_MAC, AR5K_SREV_AR5416 }, + { "5418", AR5K_VERSION_MAC, AR5K_SREV_AR5418 }, + { "2425", AR5K_VERSION_MAC, AR5K_SREV_AR2425 }, + { "2417", AR5K_VERSION_MAC, AR5K_SREV_AR2417 }, +#endif + { "xxxxx", AR5K_VERSION_MAC, AR5K_SREV_UNKNOWN }, + { "5110", AR5K_VERSION_RAD, AR5K_SREV_RAD_5110 }, + { "5111", AR5K_VERSION_RAD, AR5K_SREV_RAD_5111 }, + { "5111A", AR5K_VERSION_RAD, AR5K_SREV_RAD_5111A }, + { "2111", AR5K_VERSION_RAD, AR5K_SREV_RAD_2111 }, + { "5112", AR5K_VERSION_RAD, AR5K_SREV_RAD_5112 }, + { "5112A", AR5K_VERSION_RAD, AR5K_SREV_RAD_5112A }, + { "5112B", AR5K_VERSION_RAD, AR5K_SREV_RAD_5112B }, + { "2112", AR5K_VERSION_RAD, AR5K_SREV_RAD_2112 }, + { "2112A", AR5K_VERSION_RAD, AR5K_SREV_RAD_2112A }, + { "2112B", AR5K_VERSION_RAD, AR5K_SREV_RAD_2112B }, + { "2413", AR5K_VERSION_RAD, AR5K_SREV_RAD_2413 }, + { "5413", AR5K_VERSION_RAD, AR5K_SREV_RAD_5413 }, + { "5424", AR5K_VERSION_RAD, AR5K_SREV_RAD_5424 }, + { "5133", AR5K_VERSION_RAD, AR5K_SREV_RAD_5133 }, +#ifdef CONFIG_ATHEROS_AR231X + { "2316", AR5K_VERSION_RAD, AR5K_SREV_RAD_2316 }, + { "2317", AR5K_VERSION_RAD, AR5K_SREV_RAD_2317 }, +#endif + { "xxxxx", AR5K_VERSION_RAD, AR5K_SREV_UNKNOWN }, +}; + +static const struct ieee80211_rate ath5k_rates[] = { + { .bitrate = 10, + .hw_value = ATH5K_RATE_CODE_1M, }, + { .bitrate = 20, + .hw_value = ATH5K_RATE_CODE_2M, + .hw_value_short = ATH5K_RATE_CODE_2M | AR5K_SET_SHORT_PREAMBLE, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 55, + .hw_value = ATH5K_RATE_CODE_5_5M, + .hw_value_short = ATH5K_RATE_CODE_5_5M | AR5K_SET_SHORT_PREAMBLE, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 110, + .hw_value = ATH5K_RATE_CODE_11M, + .hw_value_short = ATH5K_RATE_CODE_11M | AR5K_SET_SHORT_PREAMBLE, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 60, + .hw_value = ATH5K_RATE_CODE_6M, + .flags = 0 }, + { .bitrate = 90, + .hw_value = ATH5K_RATE_CODE_9M, + .flags = 0 }, + { .bitrate = 120, + .hw_value = ATH5K_RATE_CODE_12M, + .flags = 0 }, + { .bitrate = 180, + .hw_value = ATH5K_RATE_CODE_18M, + .flags = 0 }, + { .bitrate = 240, + .hw_value = ATH5K_RATE_CODE_24M, + .flags = 0 }, + { .bitrate = 360, + .hw_value = ATH5K_RATE_CODE_36M, + .flags = 0 }, + { .bitrate = 480, + .hw_value = ATH5K_RATE_CODE_48M, + .flags = 0 }, + { .bitrate = 540, + .hw_value = ATH5K_RATE_CODE_54M, + .flags = 0 }, +}; + +static inline u64 ath5k_extend_tsf(struct ath5k_hw *ah, u32 rstamp) +{ + u64 tsf = ath5k_hw_get_tsf64(ah); + + if ((tsf & 0x7fff) < rstamp) + tsf -= 0x8000; + + return (tsf & ~0x7fff) | rstamp; +} + +const char * +ath5k_chip_name(enum ath5k_srev_type type, u_int16_t val) +{ + const char *name = "xxxxx"; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(srev_names); i++) { + if (srev_names[i].sr_type != type) + continue; + + if ((val & 0xf0) == srev_names[i].sr_val) + name = srev_names[i].sr_name; + + if ((val & 0xff) == srev_names[i].sr_val) { + name = srev_names[i].sr_name; + break; + } + } + + return name; +} +static unsigned int ath5k_ioread32(void *hw_priv, u32 reg_offset) +{ + struct ath5k_hw *ah = (struct ath5k_hw *) hw_priv; + return ath5k_hw_reg_read(ah, reg_offset); +} + +static void ath5k_iowrite32(void *hw_priv, u32 val, u32 reg_offset) +{ + struct ath5k_hw *ah = (struct ath5k_hw *) hw_priv; + ath5k_hw_reg_write(ah, val, reg_offset); +} + +static const struct ath_ops ath5k_common_ops = { + .read = ath5k_ioread32, + .write = ath5k_iowrite32, +}; + +/***********************\ +* Driver Initialization * +\***********************/ + +static void ath5k_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct ath5k_hw *ah = hw->priv; + struct ath_regulatory *regulatory = ath5k_hw_regulatory(ah); + + ath_reg_notifier_apply(wiphy, request, regulatory); +} + +/********************\ +* Channel/mode setup * +\********************/ + +/* + * Returns true for the channel numbers used. + */ +#ifdef CPTCFG_ATH5K_TEST_CHANNELS +static bool ath5k_is_standard_channel(short chan, enum ieee80211_band band) +{ + return true; +} + +#else +static bool ath5k_is_standard_channel(short chan, enum ieee80211_band band) +{ + if (band == IEEE80211_BAND_2GHZ && chan <= 14) + return true; + + return /* UNII 1,2 */ + (((chan & 3) == 0 && chan >= 36 && chan <= 64) || + /* midband */ + ((chan & 3) == 0 && chan >= 100 && chan <= 140) || + /* UNII-3 */ + ((chan & 3) == 1 && chan >= 149 && chan <= 165) || + /* 802.11j 5.030-5.080 GHz (20MHz) */ + (chan == 8 || chan == 12 || chan == 16) || + /* 802.11j 4.9GHz (20MHz) */ + (chan == 184 || chan == 188 || chan == 192 || chan == 196)); +} +#endif + +static unsigned int +ath5k_setup_channels(struct ath5k_hw *ah, struct ieee80211_channel *channels, + unsigned int mode, unsigned int max) +{ + unsigned int count, size, freq, ch; + enum ieee80211_band band; + + switch (mode) { + case AR5K_MODE_11A: + /* 1..220, but 2GHz frequencies are filtered by check_channel */ + size = 220; + band = IEEE80211_BAND_5GHZ; + break; + case AR5K_MODE_11B: + case AR5K_MODE_11G: + size = 26; + band = IEEE80211_BAND_2GHZ; + break; + default: + ATH5K_WARN(ah, "bad mode, not copying channels\n"); + return 0; + } + + count = 0; + for (ch = 1; ch <= size && count < max; ch++) { + freq = ieee80211_channel_to_frequency(ch, band); + + if (freq == 0) /* mapping failed - not a standard channel */ + continue; + + /* Write channel info, needed for ath5k_channel_ok() */ + channels[count].center_freq = freq; + channels[count].band = band; + channels[count].hw_value = mode; + + /* Check if channel is supported by the chipset */ + if (!ath5k_channel_ok(ah, &channels[count])) + continue; + + if (!ath5k_is_standard_channel(ch, band)) + continue; + + count++; + } + + return count; +} + +static void +ath5k_setup_rate_idx(struct ath5k_hw *ah, struct ieee80211_supported_band *b) +{ + u8 i; + + for (i = 0; i < AR5K_MAX_RATES; i++) + ah->rate_idx[b->band][i] = -1; + + for (i = 0; i < b->n_bitrates; i++) { + ah->rate_idx[b->band][b->bitrates[i].hw_value] = i; + if (b->bitrates[i].hw_value_short) + ah->rate_idx[b->band][b->bitrates[i].hw_value_short] = i; + } +} + +static int +ath5k_setup_bands(struct ieee80211_hw *hw) +{ + struct ath5k_hw *ah = hw->priv; + struct ieee80211_supported_band *sband; + int max_c, count_c = 0; + int i; + + BUILD_BUG_ON(ARRAY_SIZE(ah->sbands) < IEEE80211_NUM_BANDS); + max_c = ARRAY_SIZE(ah->channels); + + /* 2GHz band */ + sband = &ah->sbands[IEEE80211_BAND_2GHZ]; + sband->band = IEEE80211_BAND_2GHZ; + sband->bitrates = &ah->rates[IEEE80211_BAND_2GHZ][0]; + + if (test_bit(AR5K_MODE_11G, ah->ah_capabilities.cap_mode)) { + /* G mode */ + memcpy(sband->bitrates, &ath5k_rates[0], + sizeof(struct ieee80211_rate) * 12); + sband->n_bitrates = 12; + + sband->channels = ah->channels; + sband->n_channels = ath5k_setup_channels(ah, sband->channels, + AR5K_MODE_11G, max_c); + + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = sband; + count_c = sband->n_channels; + max_c -= count_c; + } else if (test_bit(AR5K_MODE_11B, ah->ah_capabilities.cap_mode)) { + /* B mode */ + memcpy(sband->bitrates, &ath5k_rates[0], + sizeof(struct ieee80211_rate) * 4); + sband->n_bitrates = 4; + + /* 5211 only supports B rates and uses 4bit rate codes + * (e.g normally we have 0x1B for 1M, but on 5211 we have 0x0B) + * fix them up here: + */ + if (ah->ah_version == AR5K_AR5211) { + for (i = 0; i < 4; i++) { + sband->bitrates[i].hw_value = + sband->bitrates[i].hw_value & 0xF; + sband->bitrates[i].hw_value_short = + sband->bitrates[i].hw_value_short & 0xF; + } + } + + sband->channels = ah->channels; + sband->n_channels = ath5k_setup_channels(ah, sband->channels, + AR5K_MODE_11B, max_c); + + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = sband; + count_c = sband->n_channels; + max_c -= count_c; + } + ath5k_setup_rate_idx(ah, sband); + + /* 5GHz band, A mode */ + if (test_bit(AR5K_MODE_11A, ah->ah_capabilities.cap_mode)) { + sband = &ah->sbands[IEEE80211_BAND_5GHZ]; + sband->band = IEEE80211_BAND_5GHZ; + sband->bitrates = &ah->rates[IEEE80211_BAND_5GHZ][0]; + + memcpy(sband->bitrates, &ath5k_rates[4], + sizeof(struct ieee80211_rate) * 8); + sband->n_bitrates = 8; + + sband->channels = &ah->channels[count_c]; + sband->n_channels = ath5k_setup_channels(ah, sband->channels, + AR5K_MODE_11A, max_c); + + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = sband; + } + ath5k_setup_rate_idx(ah, sband); + + ath5k_debug_dump_bands(ah); + + return 0; +} + +/* + * Set/change channels. We always reset the chip. + * To accomplish this we must first cleanup any pending DMA, + * then restart stuff after a la ath5k_init. + * + * Called with ah->lock. + */ +int +ath5k_chan_set(struct ath5k_hw *ah, struct ieee80211_channel *chan) +{ + ATH5K_DBG(ah, ATH5K_DEBUG_RESET, + "channel set, resetting (%u -> %u MHz)\n", + ah->curchan->center_freq, chan->center_freq); + + /* + * To switch channels clear any pending DMA operations; + * wait long enough for the RX fifo to drain, reset the + * hardware at the new frequency, and then re-enable + * the relevant bits of the h/w. + */ + return ath5k_reset(ah, chan, true); +} + +void ath5k_vif_iter(void *data, u8 *mac, struct ieee80211_vif *vif) +{ + struct ath5k_vif_iter_data *iter_data = data; + int i; + struct ath5k_vif *avf = (void *)vif->drv_priv; + + if (iter_data->hw_macaddr) + for (i = 0; i < ETH_ALEN; i++) + iter_data->mask[i] &= + ~(iter_data->hw_macaddr[i] ^ mac[i]); + + if (!iter_data->found_active) { + iter_data->found_active = true; + memcpy(iter_data->active_mac, mac, ETH_ALEN); + } + + if (iter_data->need_set_hw_addr && iter_data->hw_macaddr) + if (ether_addr_equal(iter_data->hw_macaddr, mac)) + iter_data->need_set_hw_addr = false; + + if (!iter_data->any_assoc) { + if (avf->assoc) + iter_data->any_assoc = true; + } + + /* Calculate combined mode - when APs are active, operate in AP mode. + * Otherwise use the mode of the new interface. This can currently + * only deal with combinations of APs and STAs. Only one ad-hoc + * interfaces is allowed. + */ + if (avf->opmode == NL80211_IFTYPE_AP) + iter_data->opmode = NL80211_IFTYPE_AP; + else { + if (avf->opmode == NL80211_IFTYPE_STATION) + iter_data->n_stas++; + if (iter_data->opmode == NL80211_IFTYPE_UNSPECIFIED) + iter_data->opmode = avf->opmode; + } +} + +void +ath5k_update_bssid_mask_and_opmode(struct ath5k_hw *ah, + struct ieee80211_vif *vif) +{ + struct ath_common *common = ath5k_hw_common(ah); + struct ath5k_vif_iter_data iter_data; + u32 rfilt; + + /* + * Use the hardware MAC address as reference, the hardware uses it + * together with the BSSID mask when matching addresses. + */ + iter_data.hw_macaddr = common->macaddr; + memset(&iter_data.mask, 0xff, ETH_ALEN); + iter_data.found_active = false; + iter_data.need_set_hw_addr = true; + iter_data.opmode = NL80211_IFTYPE_UNSPECIFIED; + iter_data.n_stas = 0; + + if (vif) + ath5k_vif_iter(&iter_data, vif->addr, vif); + + /* Get list of all active MAC addresses */ + ieee80211_iterate_active_interfaces_atomic( + ah->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + ath5k_vif_iter, &iter_data); + memcpy(ah->bssidmask, iter_data.mask, ETH_ALEN); + + ah->opmode = iter_data.opmode; + if (ah->opmode == NL80211_IFTYPE_UNSPECIFIED) + /* Nothing active, default to station mode */ + ah->opmode = NL80211_IFTYPE_STATION; + + ath5k_hw_set_opmode(ah, ah->opmode); + ATH5K_DBG(ah, ATH5K_DEBUG_MODE, "mode setup opmode %d (%s)\n", + ah->opmode, ath_opmode_to_string(ah->opmode)); + + if (iter_data.need_set_hw_addr && iter_data.found_active) + ath5k_hw_set_lladdr(ah, iter_data.active_mac); + + if (ath5k_hw_hasbssidmask(ah)) + ath5k_hw_set_bssid_mask(ah, ah->bssidmask); + + /* Set up RX Filter */ + if (iter_data.n_stas > 1) { + /* If you have multiple STA interfaces connected to + * different APs, ARPs are not received (most of the time?) + * Enabling PROMISC appears to fix that problem. + */ + ah->filter_flags |= AR5K_RX_FILTER_PROM; + } + + rfilt = ah->filter_flags; + ath5k_hw_set_rx_filter(ah, rfilt); + ATH5K_DBG(ah, ATH5K_DEBUG_MODE, "RX filter 0x%x\n", rfilt); +} + +static inline int +ath5k_hw_to_driver_rix(struct ath5k_hw *ah, int hw_rix) +{ + int rix; + + /* return base rate on errors */ + if (WARN(hw_rix < 0 || hw_rix >= AR5K_MAX_RATES, + "hw_rix out of bounds: %x\n", hw_rix)) + return 0; + + rix = ah->rate_idx[ah->curchan->band][hw_rix]; + if (WARN(rix < 0, "invalid hw_rix: %x\n", hw_rix)) + rix = 0; + + return rix; +} + +/***************\ +* Buffers setup * +\***************/ + +static +struct sk_buff *ath5k_rx_skb_alloc(struct ath5k_hw *ah, dma_addr_t *skb_addr) +{ + struct ath_common *common = ath5k_hw_common(ah); + struct sk_buff *skb; + + /* + * Allocate buffer with headroom_needed space for the + * fake physical layer header at the start. + */ + skb = ath_rxbuf_alloc(common, + common->rx_bufsize, + GFP_ATOMIC); + + if (!skb) { + ATH5K_ERR(ah, "can't alloc skbuff of size %u\n", + common->rx_bufsize); + return NULL; + } + + *skb_addr = dma_map_single(ah->dev, + skb->data, common->rx_bufsize, + DMA_FROM_DEVICE); + + if (unlikely(dma_mapping_error(ah->dev, *skb_addr))) { + ATH5K_ERR(ah, "%s: DMA mapping failed\n", __func__); + dev_kfree_skb(skb); + return NULL; + } + return skb; +} + +static int +ath5k_rxbuf_setup(struct ath5k_hw *ah, struct ath5k_buf *bf) +{ + struct sk_buff *skb = bf->skb; + struct ath5k_desc *ds; + int ret; + + if (!skb) { + skb = ath5k_rx_skb_alloc(ah, &bf->skbaddr); + if (!skb) + return -ENOMEM; + bf->skb = skb; + } + + /* + * Setup descriptors. For receive we always terminate + * the descriptor list with a self-linked entry so we'll + * not get overrun under high load (as can happen with a + * 5212 when ANI processing enables PHY error frames). + * + * To ensure the last descriptor is self-linked we create + * each descriptor as self-linked and add it to the end. As + * each additional descriptor is added the previous self-linked + * entry is "fixed" naturally. This should be safe even + * if DMA is happening. When processing RX interrupts we + * never remove/process the last, self-linked, entry on the + * descriptor list. This ensures the hardware always has + * someplace to write a new frame. + */ + ds = bf->desc; + ds->ds_link = bf->daddr; /* link to self */ + ds->ds_data = bf->skbaddr; + ret = ath5k_hw_setup_rx_desc(ah, ds, ah->common.rx_bufsize, 0); + if (ret) { + ATH5K_ERR(ah, "%s: could not setup RX desc\n", __func__); + return ret; + } + + if (ah->rxlink != NULL) + *ah->rxlink = bf->daddr; + ah->rxlink = &ds->ds_link; + return 0; +} + +static enum ath5k_pkt_type get_hw_packet_type(struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr; + enum ath5k_pkt_type htype; + __le16 fc; + + hdr = (struct ieee80211_hdr *)skb->data; + fc = hdr->frame_control; + + if (ieee80211_is_beacon(fc)) + htype = AR5K_PKT_TYPE_BEACON; + else if (ieee80211_is_probe_resp(fc)) + htype = AR5K_PKT_TYPE_PROBE_RESP; + else if (ieee80211_is_atim(fc)) + htype = AR5K_PKT_TYPE_ATIM; + else if (ieee80211_is_pspoll(fc)) + htype = AR5K_PKT_TYPE_PSPOLL; + else + htype = AR5K_PKT_TYPE_NORMAL; + + return htype; +} + +static int +ath5k_txbuf_setup(struct ath5k_hw *ah, struct ath5k_buf *bf, + struct ath5k_txq *txq, int padsize) +{ + struct ath5k_desc *ds = bf->desc; + struct sk_buff *skb = bf->skb; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + unsigned int pktlen, flags, keyidx = AR5K_TXKEYIX_INVALID; + struct ieee80211_rate *rate; + unsigned int mrr_rate[3], mrr_tries[3]; + int i, ret; + u16 hw_rate; + u16 cts_rate = 0; + u16 duration = 0; + u8 rc_flags; + + flags = AR5K_TXDESC_INTREQ | AR5K_TXDESC_CLRDMASK; + + /* XXX endianness */ + bf->skbaddr = dma_map_single(ah->dev, skb->data, skb->len, + DMA_TO_DEVICE); + + rate = ieee80211_get_tx_rate(ah->hw, info); + if (!rate) { + ret = -EINVAL; + goto err_unmap; + } + + if (info->flags & IEEE80211_TX_CTL_NO_ACK) + flags |= AR5K_TXDESC_NOACK; + + rc_flags = info->control.rates[0].flags; + hw_rate = (rc_flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE) ? + rate->hw_value_short : rate->hw_value; + + pktlen = skb->len; + + /* FIXME: If we are in g mode and rate is a CCK rate + * subtract ah->ah_txpower.txp_cck_ofdm_pwr_delta + * from tx power (value is in dB units already) */ + if (info->control.hw_key) { + keyidx = info->control.hw_key->hw_key_idx; + pktlen += info->control.hw_key->icv_len; + } + if (rc_flags & IEEE80211_TX_RC_USE_RTS_CTS) { + flags |= AR5K_TXDESC_RTSENA; + cts_rate = ieee80211_get_rts_cts_rate(ah->hw, info)->hw_value; + duration = le16_to_cpu(ieee80211_rts_duration(ah->hw, + info->control.vif, pktlen, info)); + } + if (rc_flags & IEEE80211_TX_RC_USE_CTS_PROTECT) { + flags |= AR5K_TXDESC_CTSENA; + cts_rate = ieee80211_get_rts_cts_rate(ah->hw, info)->hw_value; + duration = le16_to_cpu(ieee80211_ctstoself_duration(ah->hw, + info->control.vif, pktlen, info)); + } + ret = ah->ah_setup_tx_desc(ah, ds, pktlen, + ieee80211_get_hdrlen_from_skb(skb), padsize, + get_hw_packet_type(skb), + (ah->ah_txpower.txp_requested * 2), + hw_rate, + info->control.rates[0].count, keyidx, ah->ah_tx_ant, flags, + cts_rate, duration); + if (ret) + goto err_unmap; + + /* Set up MRR descriptor */ + if (ah->ah_capabilities.cap_has_mrr_support) { + memset(mrr_rate, 0, sizeof(mrr_rate)); + memset(mrr_tries, 0, sizeof(mrr_tries)); + for (i = 0; i < 3; i++) { + rate = ieee80211_get_alt_retry_rate(ah->hw, info, i); + if (!rate) + break; + + mrr_rate[i] = rate->hw_value; + mrr_tries[i] = info->control.rates[i + 1].count; + } + + ath5k_hw_setup_mrr_tx_desc(ah, ds, + mrr_rate[0], mrr_tries[0], + mrr_rate[1], mrr_tries[1], + mrr_rate[2], mrr_tries[2]); + } + + ds->ds_link = 0; + ds->ds_data = bf->skbaddr; + + spin_lock_bh(&txq->lock); + list_add_tail(&bf->list, &txq->q); + txq->txq_len++; + if (txq->link == NULL) /* is this first packet? */ + ath5k_hw_set_txdp(ah, txq->qnum, bf->daddr); + else /* no, so only link it */ + *txq->link = bf->daddr; + + txq->link = &ds->ds_link; + ath5k_hw_start_tx_dma(ah, txq->qnum); + mmiowb(); + spin_unlock_bh(&txq->lock); + + return 0; +err_unmap: + dma_unmap_single(ah->dev, bf->skbaddr, skb->len, DMA_TO_DEVICE); + return ret; +} + +/*******************\ +* Descriptors setup * +\*******************/ + +static int +ath5k_desc_alloc(struct ath5k_hw *ah) +{ + struct ath5k_desc *ds; + struct ath5k_buf *bf; + dma_addr_t da; + unsigned int i; + int ret; + + /* allocate descriptors */ + ah->desc_len = sizeof(struct ath5k_desc) * + (ATH_TXBUF + ATH_RXBUF + ATH_BCBUF + 1); + + ah->desc = dma_alloc_coherent(ah->dev, ah->desc_len, + &ah->desc_daddr, GFP_KERNEL); + if (ah->desc == NULL) { + ATH5K_ERR(ah, "can't allocate descriptors\n"); + ret = -ENOMEM; + goto err; + } + ds = ah->desc; + da = ah->desc_daddr; + ATH5K_DBG(ah, ATH5K_DEBUG_ANY, "DMA map: %p (%zu) -> %llx\n", + ds, ah->desc_len, (unsigned long long)ah->desc_daddr); + + bf = kcalloc(1 + ATH_TXBUF + ATH_RXBUF + ATH_BCBUF, + sizeof(struct ath5k_buf), GFP_KERNEL); + if (bf == NULL) { + ATH5K_ERR(ah, "can't allocate bufptr\n"); + ret = -ENOMEM; + goto err_free; + } + ah->bufptr = bf; + + INIT_LIST_HEAD(&ah->rxbuf); + for (i = 0; i < ATH_RXBUF; i++, bf++, ds++, da += sizeof(*ds)) { + bf->desc = ds; + bf->daddr = da; + list_add_tail(&bf->list, &ah->rxbuf); + } + + INIT_LIST_HEAD(&ah->txbuf); + ah->txbuf_len = ATH_TXBUF; + for (i = 0; i < ATH_TXBUF; i++, bf++, ds++, da += sizeof(*ds)) { + bf->desc = ds; + bf->daddr = da; + list_add_tail(&bf->list, &ah->txbuf); + } + + /* beacon buffers */ + INIT_LIST_HEAD(&ah->bcbuf); + for (i = 0; i < ATH_BCBUF; i++, bf++, ds++, da += sizeof(*ds)) { + bf->desc = ds; + bf->daddr = da; + list_add_tail(&bf->list, &ah->bcbuf); + } + + return 0; +err_free: + dma_free_coherent(ah->dev, ah->desc_len, ah->desc, ah->desc_daddr); +err: + ah->desc = NULL; + return ret; +} + +void +ath5k_txbuf_free_skb(struct ath5k_hw *ah, struct ath5k_buf *bf) +{ + BUG_ON(!bf); + if (!bf->skb) + return; + dma_unmap_single(ah->dev, bf->skbaddr, bf->skb->len, + DMA_TO_DEVICE); + ieee80211_free_txskb(ah->hw, bf->skb); + bf->skb = NULL; + bf->skbaddr = 0; + bf->desc->ds_data = 0; +} + +void +ath5k_rxbuf_free_skb(struct ath5k_hw *ah, struct ath5k_buf *bf) +{ + struct ath_common *common = ath5k_hw_common(ah); + + BUG_ON(!bf); + if (!bf->skb) + return; + dma_unmap_single(ah->dev, bf->skbaddr, common->rx_bufsize, + DMA_FROM_DEVICE); + dev_kfree_skb_any(bf->skb); + bf->skb = NULL; + bf->skbaddr = 0; + bf->desc->ds_data = 0; +} + +static void +ath5k_desc_free(struct ath5k_hw *ah) +{ + struct ath5k_buf *bf; + + list_for_each_entry(bf, &ah->txbuf, list) + ath5k_txbuf_free_skb(ah, bf); + list_for_each_entry(bf, &ah->rxbuf, list) + ath5k_rxbuf_free_skb(ah, bf); + list_for_each_entry(bf, &ah->bcbuf, list) + ath5k_txbuf_free_skb(ah, bf); + + /* Free memory associated with all descriptors */ + dma_free_coherent(ah->dev, ah->desc_len, ah->desc, ah->desc_daddr); + ah->desc = NULL; + ah->desc_daddr = 0; + + kfree(ah->bufptr); + ah->bufptr = NULL; +} + + +/**************\ +* Queues setup * +\**************/ + +static struct ath5k_txq * +ath5k_txq_setup(struct ath5k_hw *ah, + int qtype, int subtype) +{ + struct ath5k_txq *txq; + struct ath5k_txq_info qi = { + .tqi_subtype = subtype, + /* XXX: default values not correct for B and XR channels, + * but who cares? */ + .tqi_aifs = AR5K_TUNE_AIFS, + .tqi_cw_min = AR5K_TUNE_CWMIN, + .tqi_cw_max = AR5K_TUNE_CWMAX + }; + int qnum; + + /* + * Enable interrupts only for EOL and DESC conditions. + * We mark tx descriptors to receive a DESC interrupt + * when a tx queue gets deep; otherwise we wait for the + * EOL to reap descriptors. Note that this is done to + * reduce interrupt load and this only defers reaping + * descriptors, never transmitting frames. Aside from + * reducing interrupts this also permits more concurrency. + * The only potential downside is if the tx queue backs + * up in which case the top half of the kernel may backup + * due to a lack of tx descriptors. + */ + qi.tqi_flags = AR5K_TXQ_FLAG_TXEOLINT_ENABLE | + AR5K_TXQ_FLAG_TXDESCINT_ENABLE; + qnum = ath5k_hw_setup_tx_queue(ah, qtype, &qi); + if (qnum < 0) { + /* + * NB: don't print a message, this happens + * normally on parts with too few tx queues + */ + return ERR_PTR(qnum); + } + txq = &ah->txqs[qnum]; + if (!txq->setup) { + txq->qnum = qnum; + txq->link = NULL; + INIT_LIST_HEAD(&txq->q); + spin_lock_init(&txq->lock); + txq->setup = true; + txq->txq_len = 0; + txq->txq_max = ATH5K_TXQ_LEN_MAX; + txq->txq_poll_mark = false; + txq->txq_stuck = 0; + } + return &ah->txqs[qnum]; +} + +static int +ath5k_beaconq_setup(struct ath5k_hw *ah) +{ + struct ath5k_txq_info qi = { + /* XXX: default values not correct for B and XR channels, + * but who cares? */ + .tqi_aifs = AR5K_TUNE_AIFS, + .tqi_cw_min = AR5K_TUNE_CWMIN, + .tqi_cw_max = AR5K_TUNE_CWMAX, + /* NB: for dynamic turbo, don't enable any other interrupts */ + .tqi_flags = AR5K_TXQ_FLAG_TXDESCINT_ENABLE + }; + + return ath5k_hw_setup_tx_queue(ah, AR5K_TX_QUEUE_BEACON, &qi); +} + +static int +ath5k_beaconq_config(struct ath5k_hw *ah) +{ + struct ath5k_txq_info qi; + int ret; + + ret = ath5k_hw_get_tx_queueprops(ah, ah->bhalq, &qi); + if (ret) + goto err; + + if (ah->opmode == NL80211_IFTYPE_AP || + ah->opmode == NL80211_IFTYPE_MESH_POINT) { + /* + * Always burst out beacon and CAB traffic + * (aifs = cwmin = cwmax = 0) + */ + qi.tqi_aifs = 0; + qi.tqi_cw_min = 0; + qi.tqi_cw_max = 0; + } else if (ah->opmode == NL80211_IFTYPE_ADHOC) { + /* + * Adhoc mode; backoff between 0 and (2 * cw_min). + */ + qi.tqi_aifs = 0; + qi.tqi_cw_min = 0; + qi.tqi_cw_max = 2 * AR5K_TUNE_CWMIN; + } + + ATH5K_DBG(ah, ATH5K_DEBUG_BEACON, + "beacon queueprops tqi_aifs:%d tqi_cw_min:%d tqi_cw_max:%d\n", + qi.tqi_aifs, qi.tqi_cw_min, qi.tqi_cw_max); + + ret = ath5k_hw_set_tx_queueprops(ah, ah->bhalq, &qi); + if (ret) { + ATH5K_ERR(ah, "%s: unable to update parameters for beacon " + "hardware queue!\n", __func__); + goto err; + } + ret = ath5k_hw_reset_tx_queue(ah, ah->bhalq); /* push to h/w */ + if (ret) + goto err; + + /* reconfigure cabq with ready time to 80% of beacon_interval */ + ret = ath5k_hw_get_tx_queueprops(ah, AR5K_TX_QUEUE_ID_CAB, &qi); + if (ret) + goto err; + + qi.tqi_ready_time = (ah->bintval * 80) / 100; + ret = ath5k_hw_set_tx_queueprops(ah, AR5K_TX_QUEUE_ID_CAB, &qi); + if (ret) + goto err; + + ret = ath5k_hw_reset_tx_queue(ah, AR5K_TX_QUEUE_ID_CAB); +err: + return ret; +} + +/** + * ath5k_drain_tx_buffs - Empty tx buffers + * + * @ah The &struct ath5k_hw + * + * Empty tx buffers from all queues in preparation + * of a reset or during shutdown. + * + * NB: this assumes output has been stopped and + * we do not need to block ath5k_tx_tasklet + */ +static void +ath5k_drain_tx_buffs(struct ath5k_hw *ah) +{ + struct ath5k_txq *txq; + struct ath5k_buf *bf, *bf0; + int i; + + for (i = 0; i < ARRAY_SIZE(ah->txqs); i++) { + if (ah->txqs[i].setup) { + txq = &ah->txqs[i]; + spin_lock_bh(&txq->lock); + list_for_each_entry_safe(bf, bf0, &txq->q, list) { + ath5k_debug_printtxbuf(ah, bf); + + ath5k_txbuf_free_skb(ah, bf); + + spin_lock(&ah->txbuflock); + list_move_tail(&bf->list, &ah->txbuf); + ah->txbuf_len++; + txq->txq_len--; + spin_unlock(&ah->txbuflock); + } + txq->link = NULL; + txq->txq_poll_mark = false; + spin_unlock_bh(&txq->lock); + } + } +} + +static void +ath5k_txq_release(struct ath5k_hw *ah) +{ + struct ath5k_txq *txq = ah->txqs; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ah->txqs); i++, txq++) + if (txq->setup) { + ath5k_hw_release_tx_queue(ah, txq->qnum); + txq->setup = false; + } +} + + +/*************\ +* RX Handling * +\*************/ + +/* + * Enable the receive h/w following a reset. + */ +static int +ath5k_rx_start(struct ath5k_hw *ah) +{ + struct ath_common *common = ath5k_hw_common(ah); + struct ath5k_buf *bf; + int ret; + + common->rx_bufsize = roundup(IEEE80211_MAX_FRAME_LEN, common->cachelsz); + + ATH5K_DBG(ah, ATH5K_DEBUG_RESET, "cachelsz %u rx_bufsize %u\n", + common->cachelsz, common->rx_bufsize); + + spin_lock_bh(&ah->rxbuflock); + ah->rxlink = NULL; + list_for_each_entry(bf, &ah->rxbuf, list) { + ret = ath5k_rxbuf_setup(ah, bf); + if (ret != 0) { + spin_unlock_bh(&ah->rxbuflock); + goto err; + } + } + bf = list_first_entry(&ah->rxbuf, struct ath5k_buf, list); + ath5k_hw_set_rxdp(ah, bf->daddr); + spin_unlock_bh(&ah->rxbuflock); + + ath5k_hw_start_rx_dma(ah); /* enable recv descriptors */ + ath5k_update_bssid_mask_and_opmode(ah, NULL); /* set filters, etc. */ + ath5k_hw_start_rx_pcu(ah); /* re-enable PCU/DMA engine */ + + return 0; +err: + return ret; +} + +/* + * Disable the receive logic on PCU (DRU) + * In preparation for a shutdown. + * + * Note: Doesn't stop rx DMA, ath5k_hw_dma_stop + * does. + */ +static void +ath5k_rx_stop(struct ath5k_hw *ah) +{ + + ath5k_hw_set_rx_filter(ah, 0); /* clear recv filter */ + ath5k_hw_stop_rx_pcu(ah); /* disable PCU */ + + ath5k_debug_printrxbuffs(ah); +} + +static unsigned int +ath5k_rx_decrypted(struct ath5k_hw *ah, struct sk_buff *skb, + struct ath5k_rx_status *rs) +{ + struct ath_common *common = ath5k_hw_common(ah); + struct ieee80211_hdr *hdr = (void *)skb->data; + unsigned int keyix, hlen; + + if (!(rs->rs_status & AR5K_RXERR_DECRYPT) && + rs->rs_keyix != AR5K_RXKEYIX_INVALID) + return RX_FLAG_DECRYPTED; + + /* Apparently when a default key is used to decrypt the packet + the hw does not set the index used to decrypt. In such cases + get the index from the packet. */ + hlen = ieee80211_hdrlen(hdr->frame_control); + if (ieee80211_has_protected(hdr->frame_control) && + !(rs->rs_status & AR5K_RXERR_DECRYPT) && + skb->len >= hlen + 4) { + keyix = skb->data[hlen + 3] >> 6; + + if (test_bit(keyix, common->keymap)) + return RX_FLAG_DECRYPTED; + } + + return 0; +} + + +static void +ath5k_check_ibss_tsf(struct ath5k_hw *ah, struct sk_buff *skb, + struct ieee80211_rx_status *rxs) +{ + struct ath_common *common = ath5k_hw_common(ah); + u64 tsf, bc_tstamp; + u32 hw_tu; + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data; + + if (ieee80211_is_beacon(mgmt->frame_control) && + le16_to_cpu(mgmt->u.beacon.capab_info) & WLAN_CAPABILITY_IBSS && + ether_addr_equal(mgmt->bssid, common->curbssid)) { + /* + * Received an IBSS beacon with the same BSSID. Hardware *must* + * have updated the local TSF. We have to work around various + * hardware bugs, though... + */ + tsf = ath5k_hw_get_tsf64(ah); + bc_tstamp = le64_to_cpu(mgmt->u.beacon.timestamp); + hw_tu = TSF_TO_TU(tsf); + + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_BEACON, + "beacon %llx mactime %llx (diff %lld) tsf now %llx\n", + (unsigned long long)bc_tstamp, + (unsigned long long)rxs->mactime, + (unsigned long long)(rxs->mactime - bc_tstamp), + (unsigned long long)tsf); + + /* + * Sometimes the HW will give us a wrong tstamp in the rx + * status, causing the timestamp extension to go wrong. + * (This seems to happen especially with beacon frames bigger + * than 78 byte (incl. FCS)) + * But we know that the receive timestamp must be later than the + * timestamp of the beacon since HW must have synced to that. + * + * NOTE: here we assume mactime to be after the frame was + * received, not like mac80211 which defines it at the start. + */ + if (bc_tstamp > rxs->mactime) { + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_BEACON, + "fixing mactime from %llx to %llx\n", + (unsigned long long)rxs->mactime, + (unsigned long long)tsf); + rxs->mactime = tsf; + } + + /* + * Local TSF might have moved higher than our beacon timers, + * in that case we have to update them to continue sending + * beacons. This also takes care of synchronizing beacon sending + * times with other stations. + */ + if (hw_tu >= ah->nexttbtt) + ath5k_beacon_update_timers(ah, bc_tstamp); + + /* Check if the beacon timers are still correct, because a TSF + * update might have created a window between them - for a + * longer description see the comment of this function: */ + if (!ath5k_hw_check_beacon_timers(ah, ah->bintval)) { + ath5k_beacon_update_timers(ah, bc_tstamp); + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_BEACON, + "fixed beacon timers after beacon receive\n"); + } + } +} + +static void +ath5k_update_beacon_rssi(struct ath5k_hw *ah, struct sk_buff *skb, int rssi) +{ + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data; + struct ath_common *common = ath5k_hw_common(ah); + + /* only beacons from our BSSID */ + if (!ieee80211_is_beacon(mgmt->frame_control) || + !ether_addr_equal(mgmt->bssid, common->curbssid)) + return; + + ewma_add(&ah->ah_beacon_rssi_avg, rssi); + + /* in IBSS mode we should keep RSSI statistics per neighbour */ + /* le16_to_cpu(mgmt->u.beacon.capab_info) & WLAN_CAPABILITY_IBSS */ +} + +/* + * Compute padding position. skb must contain an IEEE 802.11 frame + */ +static int ath5k_common_padpos(struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + __le16 frame_control = hdr->frame_control; + int padpos = 24; + + if (ieee80211_has_a4(frame_control)) + padpos += ETH_ALEN; + + if (ieee80211_is_data_qos(frame_control)) + padpos += IEEE80211_QOS_CTL_LEN; + + return padpos; +} + +/* + * This function expects an 802.11 frame and returns the number of + * bytes added, or -1 if we don't have enough header room. + */ +static int ath5k_add_padding(struct sk_buff *skb) +{ + int padpos = ath5k_common_padpos(skb); + int padsize = padpos & 3; + + if (padsize && skb->len > padpos) { + + if (skb_headroom(skb) < padsize) + return -1; + + skb_push(skb, padsize); + memmove(skb->data, skb->data + padsize, padpos); + return padsize; + } + + return 0; +} + +/* + * The MAC header is padded to have 32-bit boundary if the + * packet payload is non-zero. The general calculation for + * padsize would take into account odd header lengths: + * padsize = 4 - (hdrlen & 3); however, since only + * even-length headers are used, padding can only be 0 or 2 + * bytes and we can optimize this a bit. We must not try to + * remove padding from short control frames that do not have a + * payload. + * + * This function expects an 802.11 frame and returns the number of + * bytes removed. + */ +static int ath5k_remove_padding(struct sk_buff *skb) +{ + int padpos = ath5k_common_padpos(skb); + int padsize = padpos & 3; + + if (padsize && skb->len >= padpos + padsize) { + memmove(skb->data + padsize, skb->data, padpos); + skb_pull(skb, padsize); + return padsize; + } + + return 0; +} + +static void +ath5k_receive_frame(struct ath5k_hw *ah, struct sk_buff *skb, + struct ath5k_rx_status *rs) +{ + struct ieee80211_rx_status *rxs; + + ath5k_remove_padding(skb); + + rxs = IEEE80211_SKB_RXCB(skb); + + rxs->flag = 0; + if (unlikely(rs->rs_status & AR5K_RXERR_MIC)) + rxs->flag |= RX_FLAG_MMIC_ERROR; + + /* + * always extend the mac timestamp, since this information is + * also needed for proper IBSS merging. + * + * XXX: it might be too late to do it here, since rs_tstamp is + * 15bit only. that means TSF extension has to be done within + * 32768usec (about 32ms). it might be necessary to move this to + * the interrupt handler, like it is done in madwifi. + */ + rxs->mactime = ath5k_extend_tsf(ah, rs->rs_tstamp); + rxs->flag |= RX_FLAG_MACTIME_END; + + rxs->freq = ah->curchan->center_freq; + rxs->band = ah->curchan->band; + + rxs->signal = ah->ah_noise_floor + rs->rs_rssi; + + rxs->antenna = rs->rs_antenna; + + if (rs->rs_antenna > 0 && rs->rs_antenna < 5) + ah->stats.antenna_rx[rs->rs_antenna]++; + else + ah->stats.antenna_rx[0]++; /* invalid */ + + rxs->rate_idx = ath5k_hw_to_driver_rix(ah, rs->rs_rate); + rxs->flag |= ath5k_rx_decrypted(ah, skb, rs); + + if (rxs->rate_idx >= 0 && rs->rs_rate == + ah->sbands[ah->curchan->band].bitrates[rxs->rate_idx].hw_value_short) + rxs->flag |= RX_FLAG_SHORTPRE; + + trace_ath5k_rx(ah, skb); + + ath5k_update_beacon_rssi(ah, skb, rs->rs_rssi); + + /* check beacons in IBSS mode */ + if (ah->opmode == NL80211_IFTYPE_ADHOC) + ath5k_check_ibss_tsf(ah, skb, rxs); + + ieee80211_rx(ah->hw, skb); +} + +/** ath5k_frame_receive_ok() - Do we want to receive this frame or not? + * + * Check if we want to further process this frame or not. Also update + * statistics. Return true if we want this frame, false if not. + */ +static bool +ath5k_receive_frame_ok(struct ath5k_hw *ah, struct ath5k_rx_status *rs) +{ + ah->stats.rx_all_count++; + ah->stats.rx_bytes_count += rs->rs_datalen; + + if (unlikely(rs->rs_status)) { + if (rs->rs_status & AR5K_RXERR_CRC) + ah->stats.rxerr_crc++; + if (rs->rs_status & AR5K_RXERR_FIFO) + ah->stats.rxerr_fifo++; + if (rs->rs_status & AR5K_RXERR_PHY) { + ah->stats.rxerr_phy++; + if (rs->rs_phyerr > 0 && rs->rs_phyerr < 32) + ah->stats.rxerr_phy_code[rs->rs_phyerr]++; + return false; + } + if (rs->rs_status & AR5K_RXERR_DECRYPT) { + /* + * Decrypt error. If the error occurred + * because there was no hardware key, then + * let the frame through so the upper layers + * can process it. This is necessary for 5210 + * parts which have no way to setup a ``clear'' + * key cache entry. + * + * XXX do key cache faulting + */ + ah->stats.rxerr_decrypt++; + if (rs->rs_keyix == AR5K_RXKEYIX_INVALID && + !(rs->rs_status & AR5K_RXERR_CRC)) + return true; + } + if (rs->rs_status & AR5K_RXERR_MIC) { + ah->stats.rxerr_mic++; + return true; + } + + /* reject any frames with non-crypto errors */ + if (rs->rs_status & ~(AR5K_RXERR_DECRYPT)) + return false; + } + + if (unlikely(rs->rs_more)) { + ah->stats.rxerr_jumbo++; + return false; + } + return true; +} + +static void +ath5k_set_current_imask(struct ath5k_hw *ah) +{ + enum ath5k_int imask; + unsigned long flags; + + spin_lock_irqsave(&ah->irqlock, flags); + imask = ah->imask; + if (ah->rx_pending) + imask &= ~AR5K_INT_RX_ALL; + if (ah->tx_pending) + imask &= ~AR5K_INT_TX_ALL; + ath5k_hw_set_imr(ah, imask); + spin_unlock_irqrestore(&ah->irqlock, flags); +} + +static void +ath5k_tasklet_rx(unsigned long data) +{ + struct ath5k_rx_status rs = {}; + struct sk_buff *skb, *next_skb; + dma_addr_t next_skb_addr; + struct ath5k_hw *ah = (void *)data; + struct ath_common *common = ath5k_hw_common(ah); + struct ath5k_buf *bf; + struct ath5k_desc *ds; + int ret; + + spin_lock(&ah->rxbuflock); + if (list_empty(&ah->rxbuf)) { + ATH5K_WARN(ah, "empty rx buf pool\n"); + goto unlock; + } + do { + bf = list_first_entry(&ah->rxbuf, struct ath5k_buf, list); + BUG_ON(bf->skb == NULL); + skb = bf->skb; + ds = bf->desc; + + /* bail if HW is still using self-linked descriptor */ + if (ath5k_hw_get_rxdp(ah) == bf->daddr) + break; + + ret = ah->ah_proc_rx_desc(ah, ds, &rs); + if (unlikely(ret == -EINPROGRESS)) + break; + else if (unlikely(ret)) { + ATH5K_ERR(ah, "error in processing rx descriptor\n"); + ah->stats.rxerr_proc++; + break; + } + + if (ath5k_receive_frame_ok(ah, &rs)) { + next_skb = ath5k_rx_skb_alloc(ah, &next_skb_addr); + + /* + * If we can't replace bf->skb with a new skb under + * memory pressure, just skip this packet + */ + if (!next_skb) + goto next; + + dma_unmap_single(ah->dev, bf->skbaddr, + common->rx_bufsize, + DMA_FROM_DEVICE); + + skb_put(skb, rs.rs_datalen); + + ath5k_receive_frame(ah, skb, &rs); + + bf->skb = next_skb; + bf->skbaddr = next_skb_addr; + } +next: + list_move_tail(&bf->list, &ah->rxbuf); + } while (ath5k_rxbuf_setup(ah, bf) == 0); +unlock: + spin_unlock(&ah->rxbuflock); + ah->rx_pending = false; + ath5k_set_current_imask(ah); +} + + +/*************\ +* TX Handling * +\*************/ + +void +ath5k_tx_queue(struct ieee80211_hw *hw, struct sk_buff *skb, + struct ath5k_txq *txq) +{ + struct ath5k_hw *ah = hw->priv; + struct ath5k_buf *bf; + unsigned long flags; + int padsize; + + trace_ath5k_tx(ah, skb, txq); + + /* + * The hardware expects the header padded to 4 byte boundaries. + * If this is not the case, we add the padding after the header. + */ + padsize = ath5k_add_padding(skb); + if (padsize < 0) { + ATH5K_ERR(ah, "tx hdrlen not %%4: not enough" + " headroom to pad"); + goto drop_packet; + } + + if (txq->txq_len >= txq->txq_max && + txq->qnum <= AR5K_TX_QUEUE_ID_DATA_MAX) + ieee80211_stop_queue(hw, txq->qnum); + + spin_lock_irqsave(&ah->txbuflock, flags); + if (list_empty(&ah->txbuf)) { + ATH5K_ERR(ah, "no further txbuf available, dropping packet\n"); + spin_unlock_irqrestore(&ah->txbuflock, flags); + ieee80211_stop_queues(hw); + goto drop_packet; + } + bf = list_first_entry(&ah->txbuf, struct ath5k_buf, list); + list_del(&bf->list); + ah->txbuf_len--; + if (list_empty(&ah->txbuf)) + ieee80211_stop_queues(hw); + spin_unlock_irqrestore(&ah->txbuflock, flags); + + bf->skb = skb; + + if (ath5k_txbuf_setup(ah, bf, txq, padsize)) { + bf->skb = NULL; + spin_lock_irqsave(&ah->txbuflock, flags); + list_add_tail(&bf->list, &ah->txbuf); + ah->txbuf_len++; + spin_unlock_irqrestore(&ah->txbuflock, flags); + goto drop_packet; + } + return; + +drop_packet: + ieee80211_free_txskb(hw, skb); +} + +static void +ath5k_tx_frame_completed(struct ath5k_hw *ah, struct sk_buff *skb, + struct ath5k_txq *txq, struct ath5k_tx_status *ts) +{ + struct ieee80211_tx_info *info; + u8 tries[3]; + int i; + + ah->stats.tx_all_count++; + ah->stats.tx_bytes_count += skb->len; + info = IEEE80211_SKB_CB(skb); + + tries[0] = info->status.rates[0].count; + tries[1] = info->status.rates[1].count; + tries[2] = info->status.rates[2].count; + + ieee80211_tx_info_clear_status(info); + + for (i = 0; i < ts->ts_final_idx; i++) { + struct ieee80211_tx_rate *r = + &info->status.rates[i]; + + r->count = tries[i]; + } + + info->status.rates[ts->ts_final_idx].count = ts->ts_final_retry; + info->status.rates[ts->ts_final_idx + 1].idx = -1; + + if (unlikely(ts->ts_status)) { + ah->stats.ack_fail++; + if (ts->ts_status & AR5K_TXERR_FILT) { + info->flags |= IEEE80211_TX_STAT_TX_FILTERED; + ah->stats.txerr_filt++; + } + if (ts->ts_status & AR5K_TXERR_XRETRY) + ah->stats.txerr_retry++; + if (ts->ts_status & AR5K_TXERR_FIFO) + ah->stats.txerr_fifo++; + } else { + info->flags |= IEEE80211_TX_STAT_ACK; + info->status.ack_signal = ts->ts_rssi; + + /* count the successful attempt as well */ + info->status.rates[ts->ts_final_idx].count++; + } + + /* + * Remove MAC header padding before giving the frame + * back to mac80211. + */ + ath5k_remove_padding(skb); + + if (ts->ts_antenna > 0 && ts->ts_antenna < 5) + ah->stats.antenna_tx[ts->ts_antenna]++; + else + ah->stats.antenna_tx[0]++; /* invalid */ + + trace_ath5k_tx_complete(ah, skb, txq, ts); + ieee80211_tx_status(ah->hw, skb); +} + +static void +ath5k_tx_processq(struct ath5k_hw *ah, struct ath5k_txq *txq) +{ + struct ath5k_tx_status ts = {}; + struct ath5k_buf *bf, *bf0; + struct ath5k_desc *ds; + struct sk_buff *skb; + int ret; + + spin_lock(&txq->lock); + list_for_each_entry_safe(bf, bf0, &txq->q, list) { + + txq->txq_poll_mark = false; + + /* skb might already have been processed last time. */ + if (bf->skb != NULL) { + ds = bf->desc; + + ret = ah->ah_proc_tx_desc(ah, ds, &ts); + if (unlikely(ret == -EINPROGRESS)) + break; + else if (unlikely(ret)) { + ATH5K_ERR(ah, + "error %d while processing " + "queue %u\n", ret, txq->qnum); + break; + } + + skb = bf->skb; + bf->skb = NULL; + + dma_unmap_single(ah->dev, bf->skbaddr, skb->len, + DMA_TO_DEVICE); + ath5k_tx_frame_completed(ah, skb, txq, &ts); + } + + /* + * It's possible that the hardware can say the buffer is + * completed when it hasn't yet loaded the ds_link from + * host memory and moved on. + * Always keep the last descriptor to avoid HW races... + */ + if (ath5k_hw_get_txdp(ah, txq->qnum) != bf->daddr) { + spin_lock(&ah->txbuflock); + list_move_tail(&bf->list, &ah->txbuf); + ah->txbuf_len++; + txq->txq_len--; + spin_unlock(&ah->txbuflock); + } + } + spin_unlock(&txq->lock); + if (txq->txq_len < ATH5K_TXQ_LEN_LOW && txq->qnum < 4) + ieee80211_wake_queue(ah->hw, txq->qnum); +} + +static void +ath5k_tasklet_tx(unsigned long data) +{ + int i; + struct ath5k_hw *ah = (void *)data; + + for (i = 0; i < AR5K_NUM_TX_QUEUES; i++) + if (ah->txqs[i].setup && (ah->ah_txq_isr_txok_all & BIT(i))) + ath5k_tx_processq(ah, &ah->txqs[i]); + + ah->tx_pending = false; + ath5k_set_current_imask(ah); +} + + +/*****************\ +* Beacon handling * +\*****************/ + +/* + * Setup the beacon frame for transmit. + */ +static int +ath5k_beacon_setup(struct ath5k_hw *ah, struct ath5k_buf *bf) +{ + struct sk_buff *skb = bf->skb; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ath5k_desc *ds; + int ret = 0; + u8 antenna; + u32 flags; + const int padsize = 0; + + bf->skbaddr = dma_map_single(ah->dev, skb->data, skb->len, + DMA_TO_DEVICE); + ATH5K_DBG(ah, ATH5K_DEBUG_BEACON, "skb %p [data %p len %u] " + "skbaddr %llx\n", skb, skb->data, skb->len, + (unsigned long long)bf->skbaddr); + + if (dma_mapping_error(ah->dev, bf->skbaddr)) { + ATH5K_ERR(ah, "beacon DMA mapping failed\n"); + dev_kfree_skb_any(skb); + bf->skb = NULL; + return -EIO; + } + + ds = bf->desc; + antenna = ah->ah_tx_ant; + + flags = AR5K_TXDESC_NOACK; + if (ah->opmode == NL80211_IFTYPE_ADHOC && ath5k_hw_hasveol(ah)) { + ds->ds_link = bf->daddr; /* self-linked */ + flags |= AR5K_TXDESC_VEOL; + } else + ds->ds_link = 0; + + /* + * If we use multiple antennas on AP and use + * the Sectored AP scenario, switch antenna every + * 4 beacons to make sure everybody hears our AP. + * When a client tries to associate, hw will keep + * track of the tx antenna to be used for this client + * automatically, based on ACKed packets. + * + * Note: AP still listens and transmits RTS on the + * default antenna which is supposed to be an omni. + * + * Note2: On sectored scenarios it's possible to have + * multiple antennas (1 omni -- the default -- and 14 + * sectors), so if we choose to actually support this + * mode, we need to allow the user to set how many antennas + * we have and tweak the code below to send beacons + * on all of them. + */ + if (ah->ah_ant_mode == AR5K_ANTMODE_SECTOR_AP) + antenna = ah->bsent & 4 ? 2 : 1; + + + /* FIXME: If we are in g mode and rate is a CCK rate + * subtract ah->ah_txpower.txp_cck_ofdm_pwr_delta + * from tx power (value is in dB units already) */ + ds->ds_data = bf->skbaddr; + ret = ah->ah_setup_tx_desc(ah, ds, skb->len, + ieee80211_get_hdrlen_from_skb(skb), padsize, + AR5K_PKT_TYPE_BEACON, + (ah->ah_txpower.txp_requested * 2), + ieee80211_get_tx_rate(ah->hw, info)->hw_value, + 1, AR5K_TXKEYIX_INVALID, + antenna, flags, 0, 0); + if (ret) + goto err_unmap; + + return 0; +err_unmap: + dma_unmap_single(ah->dev, bf->skbaddr, skb->len, DMA_TO_DEVICE); + return ret; +} + +/* + * Updates the beacon that is sent by ath5k_beacon_send. For adhoc, + * this is called only once at config_bss time, for AP we do it every + * SWBA interrupt so that the TIM will reflect buffered frames. + * + * Called with the beacon lock. + */ +int +ath5k_beacon_update(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + int ret; + struct ath5k_hw *ah = hw->priv; + struct ath5k_vif *avf; + struct sk_buff *skb; + + if (WARN_ON(!vif)) { + ret = -EINVAL; + goto out; + } + + skb = ieee80211_beacon_get(hw, vif); + + if (!skb) { + ret = -ENOMEM; + goto out; + } + + avf = (void *)vif->drv_priv; + ath5k_txbuf_free_skb(ah, avf->bbuf); + avf->bbuf->skb = skb; + ret = ath5k_beacon_setup(ah, avf->bbuf); +out: + return ret; +} + +/* + * Transmit a beacon frame at SWBA. Dynamic updates to the + * frame contents are done as needed and the slot time is + * also adjusted based on current state. + * + * This is called from software irq context (beacontq tasklets) + * or user context from ath5k_beacon_config. + */ +static void +ath5k_beacon_send(struct ath5k_hw *ah) +{ + struct ieee80211_vif *vif; + struct ath5k_vif *avf; + struct ath5k_buf *bf; + struct sk_buff *skb; + int err; + + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_BEACON, "in beacon_send\n"); + + /* + * Check if the previous beacon has gone out. If + * not, don't don't try to post another: skip this + * period and wait for the next. Missed beacons + * indicate a problem and should not occur. If we + * miss too many consecutive beacons reset the device. + */ + if (unlikely(ath5k_hw_num_tx_pending(ah, ah->bhalq) != 0)) { + ah->bmisscount++; + ATH5K_DBG(ah, ATH5K_DEBUG_BEACON, + "missed %u consecutive beacons\n", ah->bmisscount); + if (ah->bmisscount > 10) { /* NB: 10 is a guess */ + ATH5K_DBG(ah, ATH5K_DEBUG_BEACON, + "stuck beacon time (%u missed)\n", + ah->bmisscount); + ATH5K_DBG(ah, ATH5K_DEBUG_RESET, + "stuck beacon, resetting\n"); + ieee80211_queue_work(ah->hw, &ah->reset_work); + } + return; + } + if (unlikely(ah->bmisscount != 0)) { + ATH5K_DBG(ah, ATH5K_DEBUG_BEACON, + "resume beacon xmit after %u misses\n", + ah->bmisscount); + ah->bmisscount = 0; + } + + if ((ah->opmode == NL80211_IFTYPE_AP && ah->num_ap_vifs + + ah->num_mesh_vifs > 1) || + ah->opmode == NL80211_IFTYPE_MESH_POINT) { + u64 tsf = ath5k_hw_get_tsf64(ah); + u32 tsftu = TSF_TO_TU(tsf); + int slot = ((tsftu % ah->bintval) * ATH_BCBUF) / ah->bintval; + vif = ah->bslot[(slot + 1) % ATH_BCBUF]; + ATH5K_DBG(ah, ATH5K_DEBUG_BEACON, + "tsf %llx tsftu %x intval %u slot %u vif %p\n", + (unsigned long long)tsf, tsftu, ah->bintval, slot, vif); + } else /* only one interface */ + vif = ah->bslot[0]; + + if (!vif) + return; + + avf = (void *)vif->drv_priv; + bf = avf->bbuf; + + /* + * Stop any current dma and put the new frame on the queue. + * This should never fail since we check above that no frames + * are still pending on the queue. + */ + if (unlikely(ath5k_hw_stop_beacon_queue(ah, ah->bhalq))) { + ATH5K_WARN(ah, "beacon queue %u didn't start/stop ?\n", ah->bhalq); + /* NB: hw still stops DMA, so proceed */ + } + + /* refresh the beacon for AP or MESH mode */ + if (ah->opmode == NL80211_IFTYPE_AP || + ah->opmode == NL80211_IFTYPE_MESH_POINT) { + err = ath5k_beacon_update(ah->hw, vif); + if (err) + return; + } + + if (unlikely(bf->skb == NULL || ah->opmode == NL80211_IFTYPE_STATION || + ah->opmode == NL80211_IFTYPE_MONITOR)) { + ATH5K_WARN(ah, "bf=%p bf_skb=%p\n", bf, bf->skb); + return; + } + + trace_ath5k_tx(ah, bf->skb, &ah->txqs[ah->bhalq]); + + ath5k_hw_set_txdp(ah, ah->bhalq, bf->daddr); + ath5k_hw_start_tx_dma(ah, ah->bhalq); + ATH5K_DBG(ah, ATH5K_DEBUG_BEACON, "TXDP[%u] = %llx (%p)\n", + ah->bhalq, (unsigned long long)bf->daddr, bf->desc); + + skb = ieee80211_get_buffered_bc(ah->hw, vif); + while (skb) { + ath5k_tx_queue(ah->hw, skb, ah->cabq); + + if (ah->cabq->txq_len >= ah->cabq->txq_max) + break; + + skb = ieee80211_get_buffered_bc(ah->hw, vif); + } + + ah->bsent++; +} + +/** + * ath5k_beacon_update_timers - update beacon timers + * + * @ah: struct ath5k_hw pointer we are operating on + * @bc_tsf: the timestamp of the beacon. 0 to reset the TSF. -1 to perform a + * beacon timer update based on the current HW TSF. + * + * Calculate the next target beacon transmit time (TBTT) based on the timestamp + * of a received beacon or the current local hardware TSF and write it to the + * beacon timer registers. + * + * This is called in a variety of situations, e.g. when a beacon is received, + * when a TSF update has been detected, but also when an new IBSS is created or + * when we otherwise know we have to update the timers, but we keep it in this + * function to have it all together in one place. + */ +void +ath5k_beacon_update_timers(struct ath5k_hw *ah, u64 bc_tsf) +{ + u32 nexttbtt, intval, hw_tu, bc_tu; + u64 hw_tsf; + + intval = ah->bintval & AR5K_BEACON_PERIOD; + if (ah->opmode == NL80211_IFTYPE_AP && ah->num_ap_vifs + + ah->num_mesh_vifs > 1) { + intval /= ATH_BCBUF; /* staggered multi-bss beacons */ + if (intval < 15) + ATH5K_WARN(ah, "intval %u is too low, min 15\n", + intval); + } + if (WARN_ON(!intval)) + return; + + /* beacon TSF converted to TU */ + bc_tu = TSF_TO_TU(bc_tsf); + + /* current TSF converted to TU */ + hw_tsf = ath5k_hw_get_tsf64(ah); + hw_tu = TSF_TO_TU(hw_tsf); + +#define FUDGE (AR5K_TUNE_SW_BEACON_RESP + 3) + /* We use FUDGE to make sure the next TBTT is ahead of the current TU. + * Since we later subtract AR5K_TUNE_SW_BEACON_RESP (10) in the timer + * configuration we need to make sure it is bigger than that. */ + + if (bc_tsf == -1) { + /* + * no beacons received, called internally. + * just need to refresh timers based on HW TSF. + */ + nexttbtt = roundup(hw_tu + FUDGE, intval); + } else if (bc_tsf == 0) { + /* + * no beacon received, probably called by ath5k_reset_tsf(). + * reset TSF to start with 0. + */ + nexttbtt = intval; + intval |= AR5K_BEACON_RESET_TSF; + } else if (bc_tsf > hw_tsf) { + /* + * beacon received, SW merge happened but HW TSF not yet updated. + * not possible to reconfigure timers yet, but next time we + * receive a beacon with the same BSSID, the hardware will + * automatically update the TSF and then we need to reconfigure + * the timers. + */ + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_BEACON, + "need to wait for HW TSF sync\n"); + return; + } else { + /* + * most important case for beacon synchronization between STA. + * + * beacon received and HW TSF has been already updated by HW. + * update next TBTT based on the TSF of the beacon, but make + * sure it is ahead of our local TSF timer. + */ + nexttbtt = bc_tu + roundup(hw_tu + FUDGE - bc_tu, intval); + } +#undef FUDGE + + ah->nexttbtt = nexttbtt; + + intval |= AR5K_BEACON_ENA; + ath5k_hw_init_beacon_timers(ah, nexttbtt, intval); + + /* + * debugging output last in order to preserve the time critical aspect + * of this function + */ + if (bc_tsf == -1) + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_BEACON, + "reconfigured timers based on HW TSF\n"); + else if (bc_tsf == 0) + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_BEACON, + "reset HW TSF and timers\n"); + else + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_BEACON, + "updated timers based on beacon TSF\n"); + + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_BEACON, + "bc_tsf %llx hw_tsf %llx bc_tu %u hw_tu %u nexttbtt %u\n", + (unsigned long long) bc_tsf, + (unsigned long long) hw_tsf, bc_tu, hw_tu, nexttbtt); + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_BEACON, "intval %u %s %s\n", + intval & AR5K_BEACON_PERIOD, + intval & AR5K_BEACON_ENA ? "AR5K_BEACON_ENA" : "", + intval & AR5K_BEACON_RESET_TSF ? "AR5K_BEACON_RESET_TSF" : ""); +} + +/** + * ath5k_beacon_config - Configure the beacon queues and interrupts + * + * @ah: struct ath5k_hw pointer we are operating on + * + * In IBSS mode we use a self-linked tx descriptor if possible. We enable SWBA + * interrupts to detect TSF updates only. + */ +void +ath5k_beacon_config(struct ath5k_hw *ah) +{ + spin_lock_bh(&ah->block); + ah->bmisscount = 0; + ah->imask &= ~(AR5K_INT_BMISS | AR5K_INT_SWBA); + + if (ah->enable_beacon) { + /* + * In IBSS mode we use a self-linked tx descriptor and let the + * hardware send the beacons automatically. We have to load it + * only once here. + * We use the SWBA interrupt only to keep track of the beacon + * timers in order to detect automatic TSF updates. + */ + ath5k_beaconq_config(ah); + + ah->imask |= AR5K_INT_SWBA; + + if (ah->opmode == NL80211_IFTYPE_ADHOC) { + if (ath5k_hw_hasveol(ah)) + ath5k_beacon_send(ah); + } else + ath5k_beacon_update_timers(ah, -1); + } else { + ath5k_hw_stop_beacon_queue(ah, ah->bhalq); + } + + ath5k_hw_set_imr(ah, ah->imask); + mmiowb(); + spin_unlock_bh(&ah->block); +} + +static void ath5k_tasklet_beacon(unsigned long data) +{ + struct ath5k_hw *ah = (struct ath5k_hw *) data; + + /* + * Software beacon alert--time to send a beacon. + * + * In IBSS mode we use this interrupt just to + * keep track of the next TBTT (target beacon + * transmission time) in order to detect whether + * automatic TSF updates happened. + */ + if (ah->opmode == NL80211_IFTYPE_ADHOC) { + /* XXX: only if VEOL supported */ + u64 tsf = ath5k_hw_get_tsf64(ah); + ah->nexttbtt += ah->bintval; + ATH5K_DBG(ah, ATH5K_DEBUG_BEACON, + "SWBA nexttbtt: %x hw_tu: %x " + "TSF: %llx\n", + ah->nexttbtt, + TSF_TO_TU(tsf), + (unsigned long long) tsf); + } else { + spin_lock(&ah->block); + ath5k_beacon_send(ah); + spin_unlock(&ah->block); + } +} + + +/********************\ +* Interrupt handling * +\********************/ + +static void +ath5k_intr_calibration_poll(struct ath5k_hw *ah) +{ + if (time_is_before_eq_jiffies(ah->ah_cal_next_ani) && + !(ah->ah_cal_mask & AR5K_CALIBRATION_FULL) && + !(ah->ah_cal_mask & AR5K_CALIBRATION_SHORT)) { + + /* Run ANI only when calibration is not active */ + + ah->ah_cal_next_ani = jiffies + + msecs_to_jiffies(ATH5K_TUNE_CALIBRATION_INTERVAL_ANI); + tasklet_schedule(&ah->ani_tasklet); + + } else if (time_is_before_eq_jiffies(ah->ah_cal_next_short) && + !(ah->ah_cal_mask & AR5K_CALIBRATION_FULL) && + !(ah->ah_cal_mask & AR5K_CALIBRATION_SHORT)) { + + /* Run calibration only when another calibration + * is not running. + * + * Note: This is for both full/short calibration, + * if it's time for a full one, ath5k_calibrate_work will deal + * with it. */ + + ah->ah_cal_next_short = jiffies + + msecs_to_jiffies(ATH5K_TUNE_CALIBRATION_INTERVAL_SHORT); + ieee80211_queue_work(ah->hw, &ah->calib_work); + } + /* we could use SWI to generate enough interrupts to meet our + * calibration interval requirements, if necessary: + * AR5K_REG_ENABLE_BITS(ah, AR5K_CR, AR5K_CR_SWI); */ +} + +static void +ath5k_schedule_rx(struct ath5k_hw *ah) +{ + ah->rx_pending = true; + tasklet_schedule(&ah->rxtq); +} + +static void +ath5k_schedule_tx(struct ath5k_hw *ah) +{ + ah->tx_pending = true; + tasklet_schedule(&ah->txtq); +} + +static irqreturn_t +ath5k_intr(int irq, void *dev_id) +{ + struct ath5k_hw *ah = dev_id; + enum ath5k_int status; + unsigned int counter = 1000; + + + /* + * If hw is not ready (or detached) and we get an + * interrupt, or if we have no interrupts pending + * (that means it's not for us) skip it. + * + * NOTE: Group 0/1 PCI interface registers are not + * supported on WiSOCs, so we can't check for pending + * interrupts (ISR belongs to another register group + * so we are ok). + */ + if (unlikely(test_bit(ATH_STAT_INVALID, ah->status) || + ((ath5k_get_bus_type(ah) != ATH_AHB) && + !ath5k_hw_is_intr_pending(ah)))) + return IRQ_NONE; + + /** Main loop **/ + do { + ath5k_hw_get_isr(ah, &status); /* NB: clears IRQ too */ + + ATH5K_DBG(ah, ATH5K_DEBUG_INTR, "status 0x%x/0x%x\n", + status, ah->imask); + + /* + * Fatal hw error -> Log and reset + * + * Fatal errors are unrecoverable so we have to + * reset the card. These errors include bus and + * dma errors. + */ + if (unlikely(status & AR5K_INT_FATAL)) { + + ATH5K_DBG(ah, ATH5K_DEBUG_RESET, + "fatal int, resetting\n"); + ieee80211_queue_work(ah->hw, &ah->reset_work); + + /* + * RX Overrun -> Count and reset if needed + * + * Receive buffers are full. Either the bus is busy or + * the CPU is not fast enough to process all received + * frames. + */ + } else if (unlikely(status & AR5K_INT_RXORN)) { + + /* + * Older chipsets need a reset to come out of this + * condition, but we treat it as RX for newer chips. + * We don't know exactly which versions need a reset + * this guess is copied from the HAL. + */ + ah->stats.rxorn_intr++; + + if (ah->ah_mac_srev < AR5K_SREV_AR5212) { + ATH5K_DBG(ah, ATH5K_DEBUG_RESET, + "rx overrun, resetting\n"); + ieee80211_queue_work(ah->hw, &ah->reset_work); + } else + ath5k_schedule_rx(ah); + + } else { + + /* Software Beacon Alert -> Schedule beacon tasklet */ + if (status & AR5K_INT_SWBA) + tasklet_hi_schedule(&ah->beacontq); + + /* + * No more RX descriptors -> Just count + * + * NB: the hardware should re-read the link when + * RXE bit is written, but it doesn't work at + * least on older hardware revs. + */ + if (status & AR5K_INT_RXEOL) + ah->stats.rxeol_intr++; + + + /* TX Underrun -> Bump tx trigger level */ + if (status & AR5K_INT_TXURN) + ath5k_hw_update_tx_triglevel(ah, true); + + /* RX -> Schedule rx tasklet */ + if (status & (AR5K_INT_RXOK | AR5K_INT_RXERR)) + ath5k_schedule_rx(ah); + + /* TX -> Schedule tx tasklet */ + if (status & (AR5K_INT_TXOK + | AR5K_INT_TXDESC + | AR5K_INT_TXERR + | AR5K_INT_TXEOL)) + ath5k_schedule_tx(ah); + + /* Missed beacon -> TODO + if (status & AR5K_INT_BMISS) + */ + + /* MIB event -> Update counters and notify ANI */ + if (status & AR5K_INT_MIB) { + ah->stats.mib_intr++; + ath5k_hw_update_mib_counters(ah); + ath5k_ani_mib_intr(ah); + } + + /* GPIO -> Notify RFKill layer */ + if (status & AR5K_INT_GPIO) + tasklet_schedule(&ah->rf_kill.toggleq); + + } + + if (ath5k_get_bus_type(ah) == ATH_AHB) + break; + + } while (ath5k_hw_is_intr_pending(ah) && --counter > 0); + + /* + * Until we handle rx/tx interrupts mask them on IMR + * + * NOTE: ah->(rx/tx)_pending are set when scheduling the tasklets + * and unset after we 've handled the interrupts. + */ + if (ah->rx_pending || ah->tx_pending) + ath5k_set_current_imask(ah); + + if (unlikely(!counter)) + ATH5K_WARN(ah, "too many interrupts, giving up for now\n"); + + /* Fire up calibration poll */ + ath5k_intr_calibration_poll(ah); + + return IRQ_HANDLED; +} + +/* + * Periodically recalibrate the PHY to account + * for temperature/environment changes. + */ +static void +ath5k_calibrate_work(struct work_struct *work) +{ + struct ath5k_hw *ah = container_of(work, struct ath5k_hw, + calib_work); + + /* Should we run a full calibration ? */ + if (time_is_before_eq_jiffies(ah->ah_cal_next_full)) { + + ah->ah_cal_next_full = jiffies + + msecs_to_jiffies(ATH5K_TUNE_CALIBRATION_INTERVAL_FULL); + ah->ah_cal_mask |= AR5K_CALIBRATION_FULL; + + ATH5K_DBG(ah, ATH5K_DEBUG_CALIBRATE, + "running full calibration\n"); + + if (ath5k_hw_gainf_calibrate(ah) == AR5K_RFGAIN_NEED_CHANGE) { + /* + * Rfgain is out of bounds, reset the chip + * to load new gain values. + */ + ATH5K_DBG(ah, ATH5K_DEBUG_RESET, + "got new rfgain, resetting\n"); + ieee80211_queue_work(ah->hw, &ah->reset_work); + } + } else + ah->ah_cal_mask |= AR5K_CALIBRATION_SHORT; + + + ATH5K_DBG(ah, ATH5K_DEBUG_CALIBRATE, "channel %u/%x\n", + ieee80211_frequency_to_channel(ah->curchan->center_freq), + ah->curchan->hw_value); + + if (ath5k_hw_phy_calibrate(ah, ah->curchan)) + ATH5K_ERR(ah, "calibration of channel %u failed\n", + ieee80211_frequency_to_channel( + ah->curchan->center_freq)); + + /* Clear calibration flags */ + if (ah->ah_cal_mask & AR5K_CALIBRATION_FULL) + ah->ah_cal_mask &= ~AR5K_CALIBRATION_FULL; + else if (ah->ah_cal_mask & AR5K_CALIBRATION_SHORT) + ah->ah_cal_mask &= ~AR5K_CALIBRATION_SHORT; +} + + +static void +ath5k_tasklet_ani(unsigned long data) +{ + struct ath5k_hw *ah = (void *)data; + + ah->ah_cal_mask |= AR5K_CALIBRATION_ANI; + ath5k_ani_calibration(ah); + ah->ah_cal_mask &= ~AR5K_CALIBRATION_ANI; +} + + +static void +ath5k_tx_complete_poll_work(struct work_struct *work) +{ + struct ath5k_hw *ah = container_of(work, struct ath5k_hw, + tx_complete_work.work); + struct ath5k_txq *txq; + int i; + bool needreset = false; + + if (!test_bit(ATH_STAT_STARTED, ah->status)) + return; + + mutex_lock(&ah->lock); + + for (i = 0; i < ARRAY_SIZE(ah->txqs); i++) { + if (ah->txqs[i].setup) { + txq = &ah->txqs[i]; + spin_lock_bh(&txq->lock); + if (txq->txq_len > 1) { + if (txq->txq_poll_mark) { + ATH5K_DBG(ah, ATH5K_DEBUG_XMIT, + "TX queue stuck %d\n", + txq->qnum); + needreset = true; + txq->txq_stuck++; + spin_unlock_bh(&txq->lock); + break; + } else { + txq->txq_poll_mark = true; + } + } + spin_unlock_bh(&txq->lock); + } + } + + if (needreset) { + ATH5K_DBG(ah, ATH5K_DEBUG_RESET, + "TX queues stuck, resetting\n"); + ath5k_reset(ah, NULL, true); + } + + mutex_unlock(&ah->lock); + + ieee80211_queue_delayed_work(ah->hw, &ah->tx_complete_work, + msecs_to_jiffies(ATH5K_TX_COMPLETE_POLL_INT)); +} + + +/*************************\ +* Initialization routines * +\*************************/ + +static const struct ieee80211_iface_limit if_limits[] = { + { .max = 2048, .types = BIT(NL80211_IFTYPE_STATION) }, + { .max = 4, .types = +#ifdef CPTCFG_MAC80211_MESH + BIT(NL80211_IFTYPE_MESH_POINT) | +#endif + BIT(NL80211_IFTYPE_AP) }, +}; + +static const struct ieee80211_iface_combination if_comb = { + .limits = if_limits, + .n_limits = ARRAY_SIZE(if_limits), + .max_interfaces = 2048, + .num_different_channels = 1, +}; + +int +ath5k_init_ah(struct ath5k_hw *ah, const struct ath_bus_ops *bus_ops) +{ + struct ieee80211_hw *hw = ah->hw; + struct ath_common *common; + int ret; + int csz; + + /* Initialize driver private data */ + SET_IEEE80211_DEV(hw, ah->dev); + hw->flags = IEEE80211_HW_RX_INCLUDES_FCS | + IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING | + IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_MFP_CAPABLE | + IEEE80211_HW_REPORTS_TX_ACK_STATUS; + + hw->wiphy->interface_modes = + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_MESH_POINT); + + hw->wiphy->iface_combinations = &if_comb; + hw->wiphy->n_iface_combinations = 1; + + /* SW support for IBSS_RSN is provided by mac80211 */ + hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN; + + /* both antennas can be configured as RX or TX */ + hw->wiphy->available_antennas_tx = 0x3; + hw->wiphy->available_antennas_rx = 0x3; + + hw->extra_tx_headroom = 2; + hw->channel_change_time = 5000; + + /* + * Mark the device as detached to avoid processing + * interrupts until setup is complete. + */ + __set_bit(ATH_STAT_INVALID, ah->status); + + ah->opmode = NL80211_IFTYPE_STATION; + ah->bintval = 1000; + mutex_init(&ah->lock); + spin_lock_init(&ah->rxbuflock); + spin_lock_init(&ah->txbuflock); + spin_lock_init(&ah->block); + spin_lock_init(&ah->irqlock); + + /* Setup interrupt handler */ + ret = request_irq(ah->irq, ath5k_intr, IRQF_SHARED, "ath", ah); + if (ret) { + ATH5K_ERR(ah, "request_irq failed\n"); + goto err; + } + + common = ath5k_hw_common(ah); + common->ops = &ath5k_common_ops; + common->bus_ops = bus_ops; + common->ah = ah; + common->hw = hw; + common->priv = ah; + common->clockrate = 40; + + /* + * Cache line size is used to size and align various + * structures used to communicate with the hardware. + */ + ath5k_read_cachesize(common, &csz); + common->cachelsz = csz << 2; /* convert to bytes */ + + spin_lock_init(&common->cc_lock); + + /* Initialize device */ + ret = ath5k_hw_init(ah); + if (ret) + goto err_irq; + + /* Set up multi-rate retry capabilities */ + if (ah->ah_capabilities.cap_has_mrr_support) { + hw->max_rates = 4; + hw->max_rate_tries = max(AR5K_INIT_RETRY_SHORT, + AR5K_INIT_RETRY_LONG); + } + + hw->vif_data_size = sizeof(struct ath5k_vif); + + /* Finish private driver data initialization */ + ret = ath5k_init(hw); + if (ret) + goto err_ah; + + ATH5K_INFO(ah, "Atheros AR%s chip found (MAC: 0x%x, PHY: 0x%x)\n", + ath5k_chip_name(AR5K_VERSION_MAC, ah->ah_mac_srev), + ah->ah_mac_srev, + ah->ah_phy_revision); + + if (!ah->ah_single_chip) { + /* Single chip radio (!RF5111) */ + if (ah->ah_radio_5ghz_revision && + !ah->ah_radio_2ghz_revision) { + /* No 5GHz support -> report 2GHz radio */ + if (!test_bit(AR5K_MODE_11A, + ah->ah_capabilities.cap_mode)) { + ATH5K_INFO(ah, "RF%s 2GHz radio found (0x%x)\n", + ath5k_chip_name(AR5K_VERSION_RAD, + ah->ah_radio_5ghz_revision), + ah->ah_radio_5ghz_revision); + /* No 2GHz support (5110 and some + * 5GHz only cards) -> report 5GHz radio */ + } else if (!test_bit(AR5K_MODE_11B, + ah->ah_capabilities.cap_mode)) { + ATH5K_INFO(ah, "RF%s 5GHz radio found (0x%x)\n", + ath5k_chip_name(AR5K_VERSION_RAD, + ah->ah_radio_5ghz_revision), + ah->ah_radio_5ghz_revision); + /* Multiband radio */ + } else { + ATH5K_INFO(ah, "RF%s multiband radio found" + " (0x%x)\n", + ath5k_chip_name(AR5K_VERSION_RAD, + ah->ah_radio_5ghz_revision), + ah->ah_radio_5ghz_revision); + } + } + /* Multi chip radio (RF5111 - RF2111) -> + * report both 2GHz/5GHz radios */ + else if (ah->ah_radio_5ghz_revision && + ah->ah_radio_2ghz_revision) { + ATH5K_INFO(ah, "RF%s 5GHz radio found (0x%x)\n", + ath5k_chip_name(AR5K_VERSION_RAD, + ah->ah_radio_5ghz_revision), + ah->ah_radio_5ghz_revision); + ATH5K_INFO(ah, "RF%s 2GHz radio found (0x%x)\n", + ath5k_chip_name(AR5K_VERSION_RAD, + ah->ah_radio_2ghz_revision), + ah->ah_radio_2ghz_revision); + } + } + + ath5k_debug_init_device(ah); + + /* ready to process interrupts */ + __clear_bit(ATH_STAT_INVALID, ah->status); + + return 0; +err_ah: + ath5k_hw_deinit(ah); +err_irq: + free_irq(ah->irq, ah); +err: + return ret; +} + +static int +ath5k_stop_locked(struct ath5k_hw *ah) +{ + + ATH5K_DBG(ah, ATH5K_DEBUG_RESET, "invalid %u\n", + test_bit(ATH_STAT_INVALID, ah->status)); + + /* + * Shutdown the hardware and driver: + * stop output from above + * disable interrupts + * turn off timers + * turn off the radio + * clear transmit machinery + * clear receive machinery + * drain and release tx queues + * reclaim beacon resources + * power down hardware + * + * Note that some of this work is not possible if the + * hardware is gone (invalid). + */ + ieee80211_stop_queues(ah->hw); + + if (!test_bit(ATH_STAT_INVALID, ah->status)) { + ath5k_led_off(ah); + ath5k_hw_set_imr(ah, 0); + synchronize_irq(ah->irq); + ath5k_rx_stop(ah); + ath5k_hw_dma_stop(ah); + ath5k_drain_tx_buffs(ah); + ath5k_hw_phy_disable(ah); + } + + return 0; +} + +int ath5k_start(struct ieee80211_hw *hw) +{ + struct ath5k_hw *ah = hw->priv; + struct ath_common *common = ath5k_hw_common(ah); + int ret, i; + + mutex_lock(&ah->lock); + + ATH5K_DBG(ah, ATH5K_DEBUG_RESET, "mode %d\n", ah->opmode); + + /* + * Stop anything previously setup. This is safe + * no matter this is the first time through or not. + */ + ath5k_stop_locked(ah); + + /* + * The basic interface to setting the hardware in a good + * state is ``reset''. On return the hardware is known to + * be powered up and with interrupts disabled. This must + * be followed by initialization of the appropriate bits + * and then setup of the interrupt mask. + */ + ah->curchan = ah->hw->conf.chandef.chan; + ah->imask = AR5K_INT_RXOK + | AR5K_INT_RXERR + | AR5K_INT_RXEOL + | AR5K_INT_RXORN + | AR5K_INT_TXDESC + | AR5K_INT_TXEOL + | AR5K_INT_FATAL + | AR5K_INT_GLOBAL + | AR5K_INT_MIB; + + ret = ath5k_reset(ah, NULL, false); + if (ret) + goto done; + + if (!ath5k_modparam_no_hw_rfkill_switch) + ath5k_rfkill_hw_start(ah); + + /* + * Reset the key cache since some parts do not reset the + * contents on initial power up or resume from suspend. + */ + for (i = 0; i < common->keymax; i++) + ath_hw_keyreset(common, (u16) i); + + /* Use higher rates for acks instead of base + * rate */ + ah->ah_ack_bitrate_high = true; + + for (i = 0; i < ARRAY_SIZE(ah->bslot); i++) + ah->bslot[i] = NULL; + + ret = 0; +done: + mmiowb(); + mutex_unlock(&ah->lock); + + set_bit(ATH_STAT_STARTED, ah->status); + ieee80211_queue_delayed_work(ah->hw, &ah->tx_complete_work, + msecs_to_jiffies(ATH5K_TX_COMPLETE_POLL_INT)); + + return ret; +} + +static void ath5k_stop_tasklets(struct ath5k_hw *ah) +{ + ah->rx_pending = false; + ah->tx_pending = false; + tasklet_kill(&ah->rxtq); + tasklet_kill(&ah->txtq); + tasklet_kill(&ah->beacontq); + tasklet_kill(&ah->ani_tasklet); +} + +/* + * Stop the device, grabbing the top-level lock to protect + * against concurrent entry through ath5k_init (which can happen + * if another thread does a system call and the thread doing the + * stop is preempted). + */ +void ath5k_stop(struct ieee80211_hw *hw) +{ + struct ath5k_hw *ah = hw->priv; + int ret; + + mutex_lock(&ah->lock); + ret = ath5k_stop_locked(ah); + if (ret == 0 && !test_bit(ATH_STAT_INVALID, ah->status)) { + /* + * Don't set the card in full sleep mode! + * + * a) When the device is in this state it must be carefully + * woken up or references to registers in the PCI clock + * domain may freeze the bus (and system). This varies + * by chip and is mostly an issue with newer parts + * (madwifi sources mentioned srev >= 0x78) that go to + * sleep more quickly. + * + * b) On older chips full sleep results a weird behaviour + * during wakeup. I tested various cards with srev < 0x78 + * and they don't wake up after module reload, a second + * module reload is needed to bring the card up again. + * + * Until we figure out what's going on don't enable + * full chip reset on any chip (this is what Legacy HAL + * and Sam's HAL do anyway). Instead Perform a full reset + * on the device (same as initial state after attach) and + * leave it idle (keep MAC/BB on warm reset) */ + ret = ath5k_hw_on_hold(ah); + + ATH5K_DBG(ah, ATH5K_DEBUG_RESET, + "putting device to sleep\n"); + } + + mmiowb(); + mutex_unlock(&ah->lock); + + ath5k_stop_tasklets(ah); + + clear_bit(ATH_STAT_STARTED, ah->status); + cancel_delayed_work_sync(&ah->tx_complete_work); + + if (!ath5k_modparam_no_hw_rfkill_switch) + ath5k_rfkill_hw_stop(ah); +} + +/* + * Reset the hardware. If chan is not NULL, then also pause rx/tx + * and change to the given channel. + * + * This should be called with ah->lock. + */ +static int +ath5k_reset(struct ath5k_hw *ah, struct ieee80211_channel *chan, + bool skip_pcu) +{ + struct ath_common *common = ath5k_hw_common(ah); + int ret, ani_mode; + bool fast; + + ATH5K_DBG(ah, ATH5K_DEBUG_RESET, "resetting\n"); + + ath5k_hw_set_imr(ah, 0); + synchronize_irq(ah->irq); + ath5k_stop_tasklets(ah); + + /* Save ani mode and disable ANI during + * reset. If we don't we might get false + * PHY error interrupts. */ + ani_mode = ah->ani_state.ani_mode; + ath5k_ani_init(ah, ATH5K_ANI_MODE_OFF); + + /* We are going to empty hw queues + * so we should also free any remaining + * tx buffers */ + ath5k_drain_tx_buffs(ah); + if (chan) + ah->curchan = chan; + + fast = ((chan != NULL) && modparam_fastchanswitch) ? 1 : 0; + + ret = ath5k_hw_reset(ah, ah->opmode, ah->curchan, fast, skip_pcu); + if (ret) { + ATH5K_ERR(ah, "can't reset hardware (%d)\n", ret); + goto err; + } + + ret = ath5k_rx_start(ah); + if (ret) { + ATH5K_ERR(ah, "can't start recv logic\n"); + goto err; + } + + ath5k_ani_init(ah, ani_mode); + + /* + * Set calibration intervals + * + * Note: We don't need to run calibration imediately + * since some initial calibration is done on reset + * even for fast channel switching. Also on scanning + * this will get set again and again and it won't get + * executed unless we connect somewhere and spend some + * time on the channel (that's what calibration needs + * anyway to be accurate). + */ + ah->ah_cal_next_full = jiffies + + msecs_to_jiffies(ATH5K_TUNE_CALIBRATION_INTERVAL_FULL); + ah->ah_cal_next_ani = jiffies + + msecs_to_jiffies(ATH5K_TUNE_CALIBRATION_INTERVAL_ANI); + ah->ah_cal_next_short = jiffies + + msecs_to_jiffies(ATH5K_TUNE_CALIBRATION_INTERVAL_SHORT); + + ewma_init(&ah->ah_beacon_rssi_avg, 1024, 8); + + /* clear survey data and cycle counters */ + memset(&ah->survey, 0, sizeof(ah->survey)); + spin_lock_bh(&common->cc_lock); + ath_hw_cycle_counters_update(common); + memset(&common->cc_survey, 0, sizeof(common->cc_survey)); + memset(&common->cc_ani, 0, sizeof(common->cc_ani)); + spin_unlock_bh(&common->cc_lock); + + /* + * Change channels and update the h/w rate map if we're switching; + * e.g. 11a to 11b/g. + * + * We may be doing a reset in response to an ioctl that changes the + * channel so update any state that might change as a result. + * + * XXX needed? + */ +/* ath5k_chan_change(ah, c); */ + + ath5k_beacon_config(ah); + /* intrs are enabled by ath5k_beacon_config */ + + ieee80211_wake_queues(ah->hw); + + return 0; +err: + return ret; +} + +static void ath5k_reset_work(struct work_struct *work) +{ + struct ath5k_hw *ah = container_of(work, struct ath5k_hw, + reset_work); + + mutex_lock(&ah->lock); + ath5k_reset(ah, NULL, true); + mutex_unlock(&ah->lock); +} + +static int +ath5k_init(struct ieee80211_hw *hw) +{ + + struct ath5k_hw *ah = hw->priv; + struct ath_regulatory *regulatory = ath5k_hw_regulatory(ah); + struct ath5k_txq *txq; + u8 mac[ETH_ALEN] = {}; + int ret; + + + /* + * Collect the channel list. The 802.11 layer + * is responsible for filtering this list based + * on settings like the phy mode and regulatory + * domain restrictions. + */ + ret = ath5k_setup_bands(hw); + if (ret) { + ATH5K_ERR(ah, "can't get channels\n"); + goto err; + } + + /* + * Allocate tx+rx descriptors and populate the lists. + */ + ret = ath5k_desc_alloc(ah); + if (ret) { + ATH5K_ERR(ah, "can't allocate descriptors\n"); + goto err; + } + + /* + * Allocate hardware transmit queues: one queue for + * beacon frames and one data queue for each QoS + * priority. Note that hw functions handle resetting + * these queues at the needed time. + */ + ret = ath5k_beaconq_setup(ah); + if (ret < 0) { + ATH5K_ERR(ah, "can't setup a beacon xmit queue\n"); + goto err_desc; + } + ah->bhalq = ret; + ah->cabq = ath5k_txq_setup(ah, AR5K_TX_QUEUE_CAB, 0); + if (IS_ERR(ah->cabq)) { + ATH5K_ERR(ah, "can't setup cab queue\n"); + ret = PTR_ERR(ah->cabq); + goto err_bhal; + } + + /* 5211 and 5212 usually support 10 queues but we better rely on the + * capability information */ + if (ah->ah_capabilities.cap_queues.q_tx_num >= 6) { + /* This order matches mac80211's queue priority, so we can + * directly use the mac80211 queue number without any mapping */ + txq = ath5k_txq_setup(ah, AR5K_TX_QUEUE_DATA, AR5K_WME_AC_VO); + if (IS_ERR(txq)) { + ATH5K_ERR(ah, "can't setup xmit queue\n"); + ret = PTR_ERR(txq); + goto err_queues; + } + txq = ath5k_txq_setup(ah, AR5K_TX_QUEUE_DATA, AR5K_WME_AC_VI); + if (IS_ERR(txq)) { + ATH5K_ERR(ah, "can't setup xmit queue\n"); + ret = PTR_ERR(txq); + goto err_queues; + } + txq = ath5k_txq_setup(ah, AR5K_TX_QUEUE_DATA, AR5K_WME_AC_BE); + if (IS_ERR(txq)) { + ATH5K_ERR(ah, "can't setup xmit queue\n"); + ret = PTR_ERR(txq); + goto err_queues; + } + txq = ath5k_txq_setup(ah, AR5K_TX_QUEUE_DATA, AR5K_WME_AC_BK); + if (IS_ERR(txq)) { + ATH5K_ERR(ah, "can't setup xmit queue\n"); + ret = PTR_ERR(txq); + goto err_queues; + } + hw->queues = 4; + } else { + /* older hardware (5210) can only support one data queue */ + txq = ath5k_txq_setup(ah, AR5K_TX_QUEUE_DATA, AR5K_WME_AC_BE); + if (IS_ERR(txq)) { + ATH5K_ERR(ah, "can't setup xmit queue\n"); + ret = PTR_ERR(txq); + goto err_queues; + } + hw->queues = 1; + } + + tasklet_init(&ah->rxtq, ath5k_tasklet_rx, (unsigned long)ah); + tasklet_init(&ah->txtq, ath5k_tasklet_tx, (unsigned long)ah); + tasklet_init(&ah->beacontq, ath5k_tasklet_beacon, (unsigned long)ah); + tasklet_init(&ah->ani_tasklet, ath5k_tasklet_ani, (unsigned long)ah); + + INIT_WORK(&ah->reset_work, ath5k_reset_work); + INIT_WORK(&ah->calib_work, ath5k_calibrate_work); + INIT_DELAYED_WORK(&ah->tx_complete_work, ath5k_tx_complete_poll_work); + + ret = ath5k_hw_common(ah)->bus_ops->eeprom_read_mac(ah, mac); + if (ret) { + ATH5K_ERR(ah, "unable to read address from EEPROM\n"); + goto err_queues; + } + + SET_IEEE80211_PERM_ADDR(hw, mac); + /* All MAC address bits matter for ACKs */ + ath5k_update_bssid_mask_and_opmode(ah, NULL); + + regulatory->current_rd = ah->ah_capabilities.cap_eeprom.ee_regdomain; + ret = ath_regd_init(regulatory, hw->wiphy, ath5k_reg_notifier); + if (ret) { + ATH5K_ERR(ah, "can't initialize regulatory system\n"); + goto err_queues; + } + + ret = ieee80211_register_hw(hw); + if (ret) { + ATH5K_ERR(ah, "can't register ieee80211 hw\n"); + goto err_queues; + } + + if (!ath_is_world_regd(regulatory)) + regulatory_hint(hw->wiphy, regulatory->alpha2); + + ath5k_init_leds(ah); + + ath5k_sysfs_register(ah); + + return 0; +err_queues: + ath5k_txq_release(ah); +err_bhal: + ath5k_hw_release_tx_queue(ah, ah->bhalq); +err_desc: + ath5k_desc_free(ah); +err: + return ret; +} + +void +ath5k_deinit_ah(struct ath5k_hw *ah) +{ + struct ieee80211_hw *hw = ah->hw; + + /* + * NB: the order of these is important: + * o call the 802.11 layer before detaching ath5k_hw to + * ensure callbacks into the driver to delete global + * key cache entries can be handled + * o reclaim the tx queue data structures after calling + * the 802.11 layer as we'll get called back to reclaim + * node state and potentially want to use them + * o to cleanup the tx queues the hal is called, so detach + * it last + * XXX: ??? detach ath5k_hw ??? + * Other than that, it's straightforward... + */ + ieee80211_unregister_hw(hw); + ath5k_desc_free(ah); + ath5k_txq_release(ah); + ath5k_hw_release_tx_queue(ah, ah->bhalq); + ath5k_unregister_leds(ah); + + ath5k_sysfs_unregister(ah); + /* + * NB: can't reclaim these until after ieee80211_ifdetach + * returns because we'll get called back to reclaim node + * state and potentially want to use them. + */ + ath5k_hw_deinit(ah); + free_irq(ah->irq, ah); +} + +bool +ath5k_any_vif_assoc(struct ath5k_hw *ah) +{ + struct ath5k_vif_iter_data iter_data; + iter_data.hw_macaddr = NULL; + iter_data.any_assoc = false; + iter_data.need_set_hw_addr = false; + iter_data.found_active = true; + + ieee80211_iterate_active_interfaces_atomic( + ah->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + ath5k_vif_iter, &iter_data); + return iter_data.any_assoc; +} + +void +ath5k_set_beacon_filter(struct ieee80211_hw *hw, bool enable) +{ + struct ath5k_hw *ah = hw->priv; + u32 rfilt; + rfilt = ath5k_hw_get_rx_filter(ah); + if (enable) + rfilt |= AR5K_RX_FILTER_BEACON; + else + rfilt &= ~AR5K_RX_FILTER_BEACON; + ath5k_hw_set_rx_filter(ah, rfilt); + ah->filter_flags = rfilt; +} + +void _ath5k_printk(const struct ath5k_hw *ah, const char *level, + const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + if (ah && ah->hw) + printk("%s" pr_fmt("%s: %pV"), + level, wiphy_name(ah->hw->wiphy), &vaf); + else + printk("%s" pr_fmt("%pV"), level, &vaf); + + va_end(args); +} diff --git a/drivers/net/wireless/ath/ath5k/base.h b/drivers/net/wireless/ath/ath5k/base.h new file mode 100644 index 0000000..6c94c7f --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/base.h @@ -0,0 +1,119 @@ +/*- + * Copyright (c) 2002-2007 Sam Leffler, Errno Consulting + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + * + */ + +/* + * Definitions for the Atheros Wireless LAN controller driver. + */ +#ifndef _DEV_ATH5K_BASE_H +#define _DEV_ATH5K_BASE_H + +struct ieee80211_vif; +struct ieee80211_hw; +struct ath5k_hw; +struct ath5k_txq; +struct ieee80211_channel; +struct ath_bus_ops; +enum nl80211_iftype; + +enum ath5k_srev_type { + AR5K_VERSION_MAC, + AR5K_VERSION_RAD, +}; + +struct ath5k_srev_name { + const char *sr_name; + enum ath5k_srev_type sr_type; + u_int sr_val; +}; + +struct ath5k_buf { + struct list_head list; + struct ath5k_desc *desc; /* virtual addr of desc */ + dma_addr_t daddr; /* physical addr of desc */ + struct sk_buff *skb; /* skbuff for buf */ + dma_addr_t skbaddr;/* physical addr of skb data */ +}; + +struct ath5k_vif { + bool assoc; /* are we associated or not */ + enum nl80211_iftype opmode; + int bslot; + struct ath5k_buf *bbuf; /* beacon buffer */ +}; + +struct ath5k_vif_iter_data { + const u8 *hw_macaddr; + u8 mask[ETH_ALEN]; + u8 active_mac[ETH_ALEN]; /* first active MAC */ + bool need_set_hw_addr; + bool found_active; + bool any_assoc; + enum nl80211_iftype opmode; + int n_stas; +}; + +void ath5k_vif_iter(void *data, u8 *mac, struct ieee80211_vif *vif); +bool ath5k_any_vif_assoc(struct ath5k_hw *ah); + +int ath5k_start(struct ieee80211_hw *hw); +void ath5k_stop(struct ieee80211_hw *hw); + +void ath5k_beacon_update_timers(struct ath5k_hw *ah, u64 bc_tsf); +int ath5k_beacon_update(struct ieee80211_hw *hw, struct ieee80211_vif *vif); +void ath5k_beacon_config(struct ath5k_hw *ah); +void ath5k_set_beacon_filter(struct ieee80211_hw *hw, bool enable); + +void ath5k_update_bssid_mask_and_opmode(struct ath5k_hw *ah, + struct ieee80211_vif *vif); +int ath5k_chan_set(struct ath5k_hw *ah, struct ieee80211_channel *chan); +void ath5k_txbuf_free_skb(struct ath5k_hw *ah, struct ath5k_buf *bf); +void ath5k_rxbuf_free_skb(struct ath5k_hw *ah, struct ath5k_buf *bf); +void ath5k_tx_queue(struct ieee80211_hw *hw, struct sk_buff *skb, + struct ath5k_txq *txq); + +const char *ath5k_chip_name(enum ath5k_srev_type type, u_int16_t val); + +int ath5k_init_ah(struct ath5k_hw *ah, const struct ath_bus_ops *bus_ops); +void ath5k_deinit_ah(struct ath5k_hw *ah); + +/* Check whether BSSID mask is supported */ +#define ath5k_hw_hasbssidmask(_ah) (ah->ah_version == AR5K_AR5212) + +/* Check whether virtual EOL is supported */ +#define ath5k_hw_hasveol(_ah) (ah->ah_version != AR5K_AR5210) + +#endif /* _DEV_ATH5K_BASE_H */ diff --git a/drivers/net/wireless/ath/ath5k/caps.c b/drivers/net/wireless/ath/ath5k/caps.c new file mode 100644 index 0000000..994169a --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/caps.c @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter + * Copyright (c) 2006-2008 Nick Kossifidis + * Copyright (c) 2007-2008 Jiri Slaby + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/**************\ +* Capabilities * +\**************/ + +#include "ath5k.h" +#include "reg.h" +#include "debug.h" +#include "../regd.h" + +/* + * Fill the capabilities struct + * TODO: Merge this with EEPROM code when we are done with it + */ +int ath5k_hw_set_capabilities(struct ath5k_hw *ah) +{ + struct ath5k_capabilities *caps = &ah->ah_capabilities; + u16 ee_header; + + /* Capabilities stored in the EEPROM */ + ee_header = caps->cap_eeprom.ee_header; + + if (ah->ah_version == AR5K_AR5210) { + /* + * Set radio capabilities + * (The AR5110 only supports the middle 5GHz band) + */ + caps->cap_range.range_5ghz_min = 5120; + caps->cap_range.range_5ghz_max = 5430; + caps->cap_range.range_2ghz_min = 0; + caps->cap_range.range_2ghz_max = 0; + + /* Set supported modes */ + __set_bit(AR5K_MODE_11A, caps->cap_mode); + } else { + /* + * XXX The transceiver supports frequencies from 4920 to 6100MHz + * XXX and from 2312 to 2732MHz. There are problems with the + * XXX current ieee80211 implementation because the IEEE + * XXX channel mapping does not support negative channel + * XXX numbers (2312MHz is channel -19). Of course, this + * XXX doesn't matter because these channels are out of the + * XXX legal range. + */ + + /* + * Set radio capabilities + */ + + if (AR5K_EEPROM_HDR_11A(ee_header)) { + if (ath_is_49ghz_allowed(caps->cap_eeprom.ee_regdomain)) + caps->cap_range.range_5ghz_min = 4920; + else + caps->cap_range.range_5ghz_min = 5005; + caps->cap_range.range_5ghz_max = 6100; + + /* Set supported modes */ + __set_bit(AR5K_MODE_11A, caps->cap_mode); + } + + /* Enable 802.11b if a 2GHz capable radio (2111/5112) is + * connected */ + if (AR5K_EEPROM_HDR_11B(ee_header) || + (AR5K_EEPROM_HDR_11G(ee_header) && + ah->ah_version != AR5K_AR5211)) { + /* 2312 */ + caps->cap_range.range_2ghz_min = 2412; + caps->cap_range.range_2ghz_max = 2732; + + /* Override 2GHz modes on SoCs that need it + * NOTE: cap_needs_2GHz_ovr gets set from + * ath_ahb_probe */ + if (!caps->cap_needs_2GHz_ovr) { + if (AR5K_EEPROM_HDR_11B(ee_header)) + __set_bit(AR5K_MODE_11B, + caps->cap_mode); + + if (AR5K_EEPROM_HDR_11G(ee_header) && + ah->ah_version != AR5K_AR5211) + __set_bit(AR5K_MODE_11G, + caps->cap_mode); + } + } + } + + if ((ah->ah_radio_5ghz_revision & 0xf0) == AR5K_SREV_RAD_2112) + __clear_bit(AR5K_MODE_11A, caps->cap_mode); + + /* Set number of supported TX queues */ + if (ah->ah_version == AR5K_AR5210) + caps->cap_queues.q_tx_num = AR5K_NUM_TX_QUEUES_NOQCU; + else + caps->cap_queues.q_tx_num = AR5K_NUM_TX_QUEUES; + + /* Newer hardware has PHY error counters */ + if (ah->ah_mac_srev >= AR5K_SREV_AR5213A) + caps->cap_has_phyerr_counters = true; + else + caps->cap_has_phyerr_counters = false; + + /* MACs since AR5212 have MRR support */ + if (ah->ah_version == AR5K_AR5212) + caps->cap_has_mrr_support = true; + else + caps->cap_has_mrr_support = false; + + return 0; +} + +/* + * TODO: Following functions should be part of a new function + * set_capability + */ + +int ath5k_hw_enable_pspoll(struct ath5k_hw *ah, u8 *bssid, + u16 assoc_id) +{ + if (ah->ah_version == AR5K_AR5210) { + AR5K_REG_DISABLE_BITS(ah, AR5K_STA_ID1, + AR5K_STA_ID1_NO_PSPOLL | AR5K_STA_ID1_DEFAULT_ANTENNA); + return 0; + } + + return -EIO; +} + +int ath5k_hw_disable_pspoll(struct ath5k_hw *ah) +{ + if (ah->ah_version == AR5K_AR5210) { + AR5K_REG_ENABLE_BITS(ah, AR5K_STA_ID1, + AR5K_STA_ID1_NO_PSPOLL | AR5K_STA_ID1_DEFAULT_ANTENNA); + return 0; + } + + return -EIO; +} diff --git a/drivers/net/wireless/ath/ath5k/debug.c b/drivers/net/wireless/ath/ath5k/debug.c new file mode 100644 index 0000000..9d00dab --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/debug.c @@ -0,0 +1,1029 @@ +/* + * Copyright (c) 2007-2008 Bruno Randolf + * + * This file is free software: you may copy, redistribute and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting + * Copyright (c) 2004-2005 Atheros Communications, Inc. + * Copyright (c) 2006 Devicescape Software, Inc. + * Copyright (c) 2007 Jiri Slaby + * Copyright (c) 2007 Luis R. Rodriguez + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include + +#include +#include +#include "debug.h" +#include "ath5k.h" +#include "reg.h" +#include "base.h" + +static unsigned int ath5k_debug; +module_param_named(debug, ath5k_debug, uint, 0); + + +/* debugfs: registers */ + +struct reg { + const char *name; + int addr; +}; + +#define REG_STRUCT_INIT(r) { #r, r } + +/* just a few random registers, might want to add more */ +static const struct reg regs[] = { + REG_STRUCT_INIT(AR5K_CR), + REG_STRUCT_INIT(AR5K_RXDP), + REG_STRUCT_INIT(AR5K_CFG), + REG_STRUCT_INIT(AR5K_IER), + REG_STRUCT_INIT(AR5K_BCR), + REG_STRUCT_INIT(AR5K_RTSD0), + REG_STRUCT_INIT(AR5K_RTSD1), + REG_STRUCT_INIT(AR5K_TXCFG), + REG_STRUCT_INIT(AR5K_RXCFG), + REG_STRUCT_INIT(AR5K_RXJLA), + REG_STRUCT_INIT(AR5K_MIBC), + REG_STRUCT_INIT(AR5K_TOPS), + REG_STRUCT_INIT(AR5K_RXNOFRM), + REG_STRUCT_INIT(AR5K_TXNOFRM), + REG_STRUCT_INIT(AR5K_RPGTO), + REG_STRUCT_INIT(AR5K_RFCNT), + REG_STRUCT_INIT(AR5K_MISC), + REG_STRUCT_INIT(AR5K_QCUDCU_CLKGT), + REG_STRUCT_INIT(AR5K_ISR), + REG_STRUCT_INIT(AR5K_PISR), + REG_STRUCT_INIT(AR5K_SISR0), + REG_STRUCT_INIT(AR5K_SISR1), + REG_STRUCT_INIT(AR5K_SISR2), + REG_STRUCT_INIT(AR5K_SISR3), + REG_STRUCT_INIT(AR5K_SISR4), + REG_STRUCT_INIT(AR5K_IMR), + REG_STRUCT_INIT(AR5K_PIMR), + REG_STRUCT_INIT(AR5K_SIMR0), + REG_STRUCT_INIT(AR5K_SIMR1), + REG_STRUCT_INIT(AR5K_SIMR2), + REG_STRUCT_INIT(AR5K_SIMR3), + REG_STRUCT_INIT(AR5K_SIMR4), + REG_STRUCT_INIT(AR5K_DCM_ADDR), + REG_STRUCT_INIT(AR5K_DCCFG), + REG_STRUCT_INIT(AR5K_CCFG), + REG_STRUCT_INIT(AR5K_CPC0), + REG_STRUCT_INIT(AR5K_CPC1), + REG_STRUCT_INIT(AR5K_CPC2), + REG_STRUCT_INIT(AR5K_CPC3), + REG_STRUCT_INIT(AR5K_CPCOVF), + REG_STRUCT_INIT(AR5K_RESET_CTL), + REG_STRUCT_INIT(AR5K_SLEEP_CTL), + REG_STRUCT_INIT(AR5K_INTPEND), + REG_STRUCT_INIT(AR5K_SFR), + REG_STRUCT_INIT(AR5K_PCICFG), + REG_STRUCT_INIT(AR5K_GPIOCR), + REG_STRUCT_INIT(AR5K_GPIODO), + REG_STRUCT_INIT(AR5K_SREV), +}; + +static void *reg_start(struct seq_file *seq, loff_t *pos) +{ + return *pos < ARRAY_SIZE(regs) ? (void *)®s[*pos] : NULL; +} + +static void reg_stop(struct seq_file *seq, void *p) +{ + /* nothing to do */ +} + +static void *reg_next(struct seq_file *seq, void *p, loff_t *pos) +{ + ++*pos; + return *pos < ARRAY_SIZE(regs) ? (void *)®s[*pos] : NULL; +} + +static int reg_show(struct seq_file *seq, void *p) +{ + struct ath5k_hw *ah = seq->private; + struct reg *r = p; + seq_printf(seq, "%-25s0x%08x\n", r->name, + ath5k_hw_reg_read(ah, r->addr)); + return 0; +} + +static const struct seq_operations register_seq_ops = { + .start = reg_start, + .next = reg_next, + .stop = reg_stop, + .show = reg_show +}; + +static int open_file_registers(struct inode *inode, struct file *file) +{ + struct seq_file *s; + int res; + res = seq_open(file, ®ister_seq_ops); + if (res == 0) { + s = file->private_data; + s->private = inode->i_private; + } + return res; +} + +static const struct file_operations fops_registers = { + .open = open_file_registers, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, + .owner = THIS_MODULE, +}; + + +/* debugfs: beacons */ + +static ssize_t read_file_beacon(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath5k_hw *ah = file->private_data; + char buf[500]; + unsigned int len = 0; + unsigned int v; + u64 tsf; + + v = ath5k_hw_reg_read(ah, AR5K_BEACON); + len += snprintf(buf + len, sizeof(buf) - len, + "%-24s0x%08x\tintval: %d\tTIM: 0x%x\n", + "AR5K_BEACON", v, v & AR5K_BEACON_PERIOD, + (v & AR5K_BEACON_TIM) >> AR5K_BEACON_TIM_S); + + len += snprintf(buf + len, sizeof(buf) - len, "%-24s0x%08x\n", + "AR5K_LAST_TSTP", ath5k_hw_reg_read(ah, AR5K_LAST_TSTP)); + + len += snprintf(buf + len, sizeof(buf) - len, "%-24s0x%08x\n\n", + "AR5K_BEACON_CNT", ath5k_hw_reg_read(ah, AR5K_BEACON_CNT)); + + v = ath5k_hw_reg_read(ah, AR5K_TIMER0); + len += snprintf(buf + len, sizeof(buf) - len, "%-24s0x%08x\tTU: %08x\n", + "AR5K_TIMER0 (TBTT)", v, v); + + v = ath5k_hw_reg_read(ah, AR5K_TIMER1); + len += snprintf(buf + len, sizeof(buf) - len, "%-24s0x%08x\tTU: %08x\n", + "AR5K_TIMER1 (DMA)", v, v >> 3); + + v = ath5k_hw_reg_read(ah, AR5K_TIMER2); + len += snprintf(buf + len, sizeof(buf) - len, "%-24s0x%08x\tTU: %08x\n", + "AR5K_TIMER2 (SWBA)", v, v >> 3); + + v = ath5k_hw_reg_read(ah, AR5K_TIMER3); + len += snprintf(buf + len, sizeof(buf) - len, "%-24s0x%08x\tTU: %08x\n", + "AR5K_TIMER3 (ATIM)", v, v); + + tsf = ath5k_hw_get_tsf64(ah); + len += snprintf(buf + len, sizeof(buf) - len, + "TSF\t\t0x%016llx\tTU: %08x\n", + (unsigned long long)tsf, TSF_TO_TU(tsf)); + + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_beacon(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct ath5k_hw *ah = file->private_data; + char buf[20]; + + if (copy_from_user(buf, userbuf, min(count, sizeof(buf)))) + return -EFAULT; + + if (strncmp(buf, "disable", 7) == 0) { + AR5K_REG_DISABLE_BITS(ah, AR5K_BEACON, AR5K_BEACON_ENABLE); + pr_info("debugfs disable beacons\n"); + } else if (strncmp(buf, "enable", 6) == 0) { + AR5K_REG_ENABLE_BITS(ah, AR5K_BEACON, AR5K_BEACON_ENABLE); + pr_info("debugfs enable beacons\n"); + } + return count; +} + +static const struct file_operations fops_beacon = { + .read = read_file_beacon, + .write = write_file_beacon, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + + +/* debugfs: reset */ + +static ssize_t write_file_reset(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct ath5k_hw *ah = file->private_data; + ATH5K_DBG(ah, ATH5K_DEBUG_RESET, "debug file triggered reset\n"); + ieee80211_queue_work(ah->hw, &ah->reset_work); + return count; +} + +static const struct file_operations fops_reset = { + .write = write_file_reset, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = noop_llseek, +}; + + +/* debugfs: debug level */ + +static const struct { + enum ath5k_debug_level level; + const char *name; + const char *desc; +} dbg_info[] = { + { ATH5K_DEBUG_RESET, "reset", "reset and initialization" }, + { ATH5K_DEBUG_INTR, "intr", "interrupt handling" }, + { ATH5K_DEBUG_MODE, "mode", "mode init/setup" }, + { ATH5K_DEBUG_XMIT, "xmit", "basic xmit operation" }, + { ATH5K_DEBUG_BEACON, "beacon", "beacon handling" }, + { ATH5K_DEBUG_CALIBRATE, "calib", "periodic calibration" }, + { ATH5K_DEBUG_TXPOWER, "txpower", "transmit power setting" }, + { ATH5K_DEBUG_LED, "led", "LED management" }, + { ATH5K_DEBUG_DUMPBANDS, "dumpbands", "dump bands" }, + { ATH5K_DEBUG_DMA, "dma", "dma start/stop" }, + { ATH5K_DEBUG_ANI, "ani", "adaptive noise immunity" }, + { ATH5K_DEBUG_DESC, "desc", "descriptor chains" }, + { ATH5K_DEBUG_ANY, "all", "show all debug levels" }, +}; + +static ssize_t read_file_debug(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath5k_hw *ah = file->private_data; + char buf[700]; + unsigned int len = 0; + unsigned int i; + + len += snprintf(buf + len, sizeof(buf) - len, + "DEBUG LEVEL: 0x%08x\n\n", ah->debug.level); + + for (i = 0; i < ARRAY_SIZE(dbg_info) - 1; i++) { + len += snprintf(buf + len, sizeof(buf) - len, + "%10s %c 0x%08x - %s\n", dbg_info[i].name, + ah->debug.level & dbg_info[i].level ? '+' : ' ', + dbg_info[i].level, dbg_info[i].desc); + } + len += snprintf(buf + len, sizeof(buf) - len, + "%10s %c 0x%08x - %s\n", dbg_info[i].name, + ah->debug.level == dbg_info[i].level ? '+' : ' ', + dbg_info[i].level, dbg_info[i].desc); + + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_debug(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct ath5k_hw *ah = file->private_data; + unsigned int i; + char buf[20]; + + if (copy_from_user(buf, userbuf, min(count, sizeof(buf)))) + return -EFAULT; + + for (i = 0; i < ARRAY_SIZE(dbg_info); i++) { + if (strncmp(buf, dbg_info[i].name, + strlen(dbg_info[i].name)) == 0) { + ah->debug.level ^= dbg_info[i].level; /* toggle bit */ + break; + } + } + return count; +} + +static const struct file_operations fops_debug = { + .read = read_file_debug, + .write = write_file_debug, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + + +/* debugfs: antenna */ + +static ssize_t read_file_antenna(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath5k_hw *ah = file->private_data; + char buf[700]; + unsigned int len = 0; + unsigned int i; + unsigned int v; + + len += snprintf(buf + len, sizeof(buf) - len, "antenna mode\t%d\n", + ah->ah_ant_mode); + len += snprintf(buf + len, sizeof(buf) - len, "default antenna\t%d\n", + ah->ah_def_ant); + len += snprintf(buf + len, sizeof(buf) - len, "tx antenna\t%d\n", + ah->ah_tx_ant); + + len += snprintf(buf + len, sizeof(buf) - len, "\nANTENNA\t\tRX\tTX\n"); + for (i = 1; i < ARRAY_SIZE(ah->stats.antenna_rx); i++) { + len += snprintf(buf + len, sizeof(buf) - len, + "[antenna %d]\t%d\t%d\n", + i, ah->stats.antenna_rx[i], ah->stats.antenna_tx[i]); + } + len += snprintf(buf + len, sizeof(buf) - len, "[invalid]\t%d\t%d\n", + ah->stats.antenna_rx[0], ah->stats.antenna_tx[0]); + + v = ath5k_hw_reg_read(ah, AR5K_DEFAULT_ANTENNA); + len += snprintf(buf + len, sizeof(buf) - len, + "\nAR5K_DEFAULT_ANTENNA\t0x%08x\n", v); + + v = ath5k_hw_reg_read(ah, AR5K_STA_ID1); + len += snprintf(buf + len, sizeof(buf) - len, + "AR5K_STA_ID1_DEFAULT_ANTENNA\t%d\n", + (v & AR5K_STA_ID1_DEFAULT_ANTENNA) != 0); + len += snprintf(buf + len, sizeof(buf) - len, + "AR5K_STA_ID1_DESC_ANTENNA\t%d\n", + (v & AR5K_STA_ID1_DESC_ANTENNA) != 0); + len += snprintf(buf + len, sizeof(buf) - len, + "AR5K_STA_ID1_RTS_DEF_ANTENNA\t%d\n", + (v & AR5K_STA_ID1_RTS_DEF_ANTENNA) != 0); + len += snprintf(buf + len, sizeof(buf) - len, + "AR5K_STA_ID1_SELFGEN_DEF_ANT\t%d\n", + (v & AR5K_STA_ID1_SELFGEN_DEF_ANT) != 0); + + v = ath5k_hw_reg_read(ah, AR5K_PHY_AGCCTL); + len += snprintf(buf + len, sizeof(buf) - len, + "\nAR5K_PHY_AGCCTL_OFDM_DIV_DIS\t%d\n", + (v & AR5K_PHY_AGCCTL_OFDM_DIV_DIS) != 0); + + v = ath5k_hw_reg_read(ah, AR5K_PHY_RESTART); + len += snprintf(buf + len, sizeof(buf) - len, + "AR5K_PHY_RESTART_DIV_GC\t\t%x\n", + (v & AR5K_PHY_RESTART_DIV_GC) >> AR5K_PHY_RESTART_DIV_GC_S); + + v = ath5k_hw_reg_read(ah, AR5K_PHY_FAST_ANT_DIV); + len += snprintf(buf + len, sizeof(buf) - len, + "AR5K_PHY_FAST_ANT_DIV_EN\t%d\n", + (v & AR5K_PHY_FAST_ANT_DIV_EN) != 0); + + v = ath5k_hw_reg_read(ah, AR5K_PHY_ANT_SWITCH_TABLE_0); + len += snprintf(buf + len, sizeof(buf) - len, + "\nAR5K_PHY_ANT_SWITCH_TABLE_0\t0x%08x\n", v); + v = ath5k_hw_reg_read(ah, AR5K_PHY_ANT_SWITCH_TABLE_1); + len += snprintf(buf + len, sizeof(buf) - len, + "AR5K_PHY_ANT_SWITCH_TABLE_1\t0x%08x\n", v); + + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_antenna(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct ath5k_hw *ah = file->private_data; + unsigned int i; + char buf[20]; + + if (copy_from_user(buf, userbuf, min(count, sizeof(buf)))) + return -EFAULT; + + if (strncmp(buf, "diversity", 9) == 0) { + ath5k_hw_set_antenna_mode(ah, AR5K_ANTMODE_DEFAULT); + pr_info("debug: enable diversity\n"); + } else if (strncmp(buf, "fixed-a", 7) == 0) { + ath5k_hw_set_antenna_mode(ah, AR5K_ANTMODE_FIXED_A); + pr_info("debug: fixed antenna A\n"); + } else if (strncmp(buf, "fixed-b", 7) == 0) { + ath5k_hw_set_antenna_mode(ah, AR5K_ANTMODE_FIXED_B); + pr_info("debug: fixed antenna B\n"); + } else if (strncmp(buf, "clear", 5) == 0) { + for (i = 0; i < ARRAY_SIZE(ah->stats.antenna_rx); i++) { + ah->stats.antenna_rx[i] = 0; + ah->stats.antenna_tx[i] = 0; + } + pr_info("debug: cleared antenna stats\n"); + } + return count; +} + +static const struct file_operations fops_antenna = { + .read = read_file_antenna, + .write = write_file_antenna, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +/* debugfs: misc */ + +static ssize_t read_file_misc(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath5k_hw *ah = file->private_data; + char buf[700]; + unsigned int len = 0; + u32 filt = ath5k_hw_get_rx_filter(ah); + + len += snprintf(buf + len, sizeof(buf) - len, "bssid-mask: %pM\n", + ah->bssidmask); + len += snprintf(buf + len, sizeof(buf) - len, "filter-flags: 0x%x ", + filt); + if (filt & AR5K_RX_FILTER_UCAST) + len += snprintf(buf + len, sizeof(buf) - len, " UCAST"); + if (filt & AR5K_RX_FILTER_MCAST) + len += snprintf(buf + len, sizeof(buf) - len, " MCAST"); + if (filt & AR5K_RX_FILTER_BCAST) + len += snprintf(buf + len, sizeof(buf) - len, " BCAST"); + if (filt & AR5K_RX_FILTER_CONTROL) + len += snprintf(buf + len, sizeof(buf) - len, " CONTROL"); + if (filt & AR5K_RX_FILTER_BEACON) + len += snprintf(buf + len, sizeof(buf) - len, " BEACON"); + if (filt & AR5K_RX_FILTER_PROM) + len += snprintf(buf + len, sizeof(buf) - len, " PROM"); + if (filt & AR5K_RX_FILTER_XRPOLL) + len += snprintf(buf + len, sizeof(buf) - len, " XRPOLL"); + if (filt & AR5K_RX_FILTER_PROBEREQ) + len += snprintf(buf + len, sizeof(buf) - len, " PROBEREQ"); + if (filt & AR5K_RX_FILTER_PHYERR_5212) + len += snprintf(buf + len, sizeof(buf) - len, " PHYERR-5212"); + if (filt & AR5K_RX_FILTER_RADARERR_5212) + len += snprintf(buf + len, sizeof(buf) - len, " RADARERR-5212"); + if (filt & AR5K_RX_FILTER_PHYERR_5211) + snprintf(buf + len, sizeof(buf) - len, " PHYERR-5211"); + if (filt & AR5K_RX_FILTER_RADARERR_5211) + len += snprintf(buf + len, sizeof(buf) - len, " RADARERR-5211"); + + len += snprintf(buf + len, sizeof(buf) - len, "\nopmode: %s (%d)\n", + ath_opmode_to_string(ah->opmode), ah->opmode); + + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_misc = { + .read = read_file_misc, + .open = simple_open, + .owner = THIS_MODULE, +}; + + +/* debugfs: frameerrors */ + +static ssize_t read_file_frameerrors(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath5k_hw *ah = file->private_data; + struct ath5k_statistics *st = &ah->stats; + char buf[700]; + unsigned int len = 0; + int i; + + len += snprintf(buf + len, sizeof(buf) - len, + "RX\n---------------------\n"); + len += snprintf(buf + len, sizeof(buf) - len, "CRC\t%u\t(%u%%)\n", + st->rxerr_crc, + st->rx_all_count > 0 ? + st->rxerr_crc * 100 / st->rx_all_count : 0); + len += snprintf(buf + len, sizeof(buf) - len, "PHY\t%u\t(%u%%)\n", + st->rxerr_phy, + st->rx_all_count > 0 ? + st->rxerr_phy * 100 / st->rx_all_count : 0); + for (i = 0; i < 32; i++) { + if (st->rxerr_phy_code[i]) + len += snprintf(buf + len, sizeof(buf) - len, + " phy_err[%u]\t%u\n", + i, st->rxerr_phy_code[i]); + } + + len += snprintf(buf + len, sizeof(buf) - len, "FIFO\t%u\t(%u%%)\n", + st->rxerr_fifo, + st->rx_all_count > 0 ? + st->rxerr_fifo * 100 / st->rx_all_count : 0); + len += snprintf(buf + len, sizeof(buf) - len, "decrypt\t%u\t(%u%%)\n", + st->rxerr_decrypt, + st->rx_all_count > 0 ? + st->rxerr_decrypt * 100 / st->rx_all_count : 0); + len += snprintf(buf + len, sizeof(buf) - len, "MIC\t%u\t(%u%%)\n", + st->rxerr_mic, + st->rx_all_count > 0 ? + st->rxerr_mic * 100 / st->rx_all_count : 0); + len += snprintf(buf + len, sizeof(buf) - len, "process\t%u\t(%u%%)\n", + st->rxerr_proc, + st->rx_all_count > 0 ? + st->rxerr_proc * 100 / st->rx_all_count : 0); + len += snprintf(buf + len, sizeof(buf) - len, "jumbo\t%u\t(%u%%)\n", + st->rxerr_jumbo, + st->rx_all_count > 0 ? + st->rxerr_jumbo * 100 / st->rx_all_count : 0); + len += snprintf(buf + len, sizeof(buf) - len, "[RX all\t%u]\n", + st->rx_all_count); + len += snprintf(buf + len, sizeof(buf) - len, "RX-all-bytes\t%u\n", + st->rx_bytes_count); + + len += snprintf(buf + len, sizeof(buf) - len, + "\nTX\n---------------------\n"); + len += snprintf(buf + len, sizeof(buf) - len, "retry\t%u\t(%u%%)\n", + st->txerr_retry, + st->tx_all_count > 0 ? + st->txerr_retry * 100 / st->tx_all_count : 0); + len += snprintf(buf + len, sizeof(buf) - len, "FIFO\t%u\t(%u%%)\n", + st->txerr_fifo, + st->tx_all_count > 0 ? + st->txerr_fifo * 100 / st->tx_all_count : 0); + len += snprintf(buf + len, sizeof(buf) - len, "filter\t%u\t(%u%%)\n", + st->txerr_filt, + st->tx_all_count > 0 ? + st->txerr_filt * 100 / st->tx_all_count : 0); + len += snprintf(buf + len, sizeof(buf) - len, "[TX all\t%u]\n", + st->tx_all_count); + len += snprintf(buf + len, sizeof(buf) - len, "TX-all-bytes\t%u\n", + st->tx_bytes_count); + + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_frameerrors(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct ath5k_hw *ah = file->private_data; + struct ath5k_statistics *st = &ah->stats; + char buf[20]; + + if (copy_from_user(buf, userbuf, min(count, sizeof(buf)))) + return -EFAULT; + + if (strncmp(buf, "clear", 5) == 0) { + st->rxerr_crc = 0; + st->rxerr_phy = 0; + st->rxerr_fifo = 0; + st->rxerr_decrypt = 0; + st->rxerr_mic = 0; + st->rxerr_proc = 0; + st->rxerr_jumbo = 0; + st->rx_all_count = 0; + st->txerr_retry = 0; + st->txerr_fifo = 0; + st->txerr_filt = 0; + st->tx_all_count = 0; + pr_info("debug: cleared frameerrors stats\n"); + } + return count; +} + +static const struct file_operations fops_frameerrors = { + .read = read_file_frameerrors, + .write = write_file_frameerrors, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + + +/* debugfs: ani */ + +static ssize_t read_file_ani(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath5k_hw *ah = file->private_data; + struct ath5k_statistics *st = &ah->stats; + struct ath5k_ani_state *as = &ah->ani_state; + + char buf[700]; + unsigned int len = 0; + + len += snprintf(buf + len, sizeof(buf) - len, + "HW has PHY error counters:\t%s\n", + ah->ah_capabilities.cap_has_phyerr_counters ? + "yes" : "no"); + len += snprintf(buf + len, sizeof(buf) - len, + "HW max spur immunity level:\t%d\n", + as->max_spur_level); + len += snprintf(buf + len, sizeof(buf) - len, + "\nANI state\n--------------------------------------------\n"); + len += snprintf(buf + len, sizeof(buf) - len, "operating mode:\t\t\t"); + switch (as->ani_mode) { + case ATH5K_ANI_MODE_OFF: + len += snprintf(buf + len, sizeof(buf) - len, "OFF\n"); + break; + case ATH5K_ANI_MODE_MANUAL_LOW: + len += snprintf(buf + len, sizeof(buf) - len, + "MANUAL LOW\n"); + break; + case ATH5K_ANI_MODE_MANUAL_HIGH: + len += snprintf(buf + len, sizeof(buf) - len, + "MANUAL HIGH\n"); + break; + case ATH5K_ANI_MODE_AUTO: + len += snprintf(buf + len, sizeof(buf) - len, "AUTO\n"); + break; + default: + len += snprintf(buf + len, sizeof(buf) - len, + "??? (not good)\n"); + break; + } + len += snprintf(buf + len, sizeof(buf) - len, + "noise immunity level:\t\t%d\n", + as->noise_imm_level); + len += snprintf(buf + len, sizeof(buf) - len, + "spur immunity level:\t\t%d\n", + as->spur_level); + len += snprintf(buf + len, sizeof(buf) - len, + "firstep level:\t\t\t%d\n", + as->firstep_level); + len += snprintf(buf + len, sizeof(buf) - len, + "OFDM weak signal detection:\t%s\n", + as->ofdm_weak_sig ? "on" : "off"); + len += snprintf(buf + len, sizeof(buf) - len, + "CCK weak signal detection:\t%s\n", + as->cck_weak_sig ? "on" : "off"); + + len += snprintf(buf + len, sizeof(buf) - len, + "\nMIB INTERRUPTS:\t\t%u\n", + st->mib_intr); + len += snprintf(buf + len, sizeof(buf) - len, + "beacon RSSI average:\t%d\n", + (int)ewma_read(&ah->ah_beacon_rssi_avg)); + +#define CC_PRINT(_struct, _field) \ + _struct._field, \ + _struct.cycles > 0 ? \ + _struct._field * 100 / _struct.cycles : 0 + + len += snprintf(buf + len, sizeof(buf) - len, + "profcnt tx\t\t%u\t(%d%%)\n", + CC_PRINT(as->last_cc, tx_frame)); + len += snprintf(buf + len, sizeof(buf) - len, + "profcnt rx\t\t%u\t(%d%%)\n", + CC_PRINT(as->last_cc, rx_frame)); + len += snprintf(buf + len, sizeof(buf) - len, + "profcnt busy\t\t%u\t(%d%%)\n", + CC_PRINT(as->last_cc, rx_busy)); +#undef CC_PRINT + len += snprintf(buf + len, sizeof(buf) - len, "profcnt cycles\t\t%u\n", + as->last_cc.cycles); + len += snprintf(buf + len, sizeof(buf) - len, + "listen time\t\t%d\tlast: %d\n", + as->listen_time, as->last_listen); + len += snprintf(buf + len, sizeof(buf) - len, + "OFDM errors\t\t%u\tlast: %u\tsum: %u\n", + as->ofdm_errors, as->last_ofdm_errors, + as->sum_ofdm_errors); + len += snprintf(buf + len, sizeof(buf) - len, + "CCK errors\t\t%u\tlast: %u\tsum: %u\n", + as->cck_errors, as->last_cck_errors, + as->sum_cck_errors); + len += snprintf(buf + len, sizeof(buf) - len, + "AR5K_PHYERR_CNT1\t%x\t(=%d)\n", + ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT1), + ATH5K_ANI_OFDM_TRIG_HIGH - (ATH5K_PHYERR_CNT_MAX - + ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT1))); + len += snprintf(buf + len, sizeof(buf) - len, + "AR5K_PHYERR_CNT2\t%x\t(=%d)\n", + ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT2), + ATH5K_ANI_CCK_TRIG_HIGH - (ATH5K_PHYERR_CNT_MAX - + ath5k_hw_reg_read(ah, AR5K_PHYERR_CNT2))); + + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_ani(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct ath5k_hw *ah = file->private_data; + char buf[20]; + + if (copy_from_user(buf, userbuf, min(count, sizeof(buf)))) + return -EFAULT; + + if (strncmp(buf, "sens-low", 8) == 0) { + ath5k_ani_init(ah, ATH5K_ANI_MODE_MANUAL_HIGH); + } else if (strncmp(buf, "sens-high", 9) == 0) { + ath5k_ani_init(ah, ATH5K_ANI_MODE_MANUAL_LOW); + } else if (strncmp(buf, "ani-off", 7) == 0) { + ath5k_ani_init(ah, ATH5K_ANI_MODE_OFF); + } else if (strncmp(buf, "ani-on", 6) == 0) { + ath5k_ani_init(ah, ATH5K_ANI_MODE_AUTO); + } else if (strncmp(buf, "noise-low", 9) == 0) { + ath5k_ani_set_noise_immunity_level(ah, 0); + } else if (strncmp(buf, "noise-high", 10) == 0) { + ath5k_ani_set_noise_immunity_level(ah, + ATH5K_ANI_MAX_NOISE_IMM_LVL); + } else if (strncmp(buf, "spur-low", 8) == 0) { + ath5k_ani_set_spur_immunity_level(ah, 0); + } else if (strncmp(buf, "spur-high", 9) == 0) { + ath5k_ani_set_spur_immunity_level(ah, + ah->ani_state.max_spur_level); + } else if (strncmp(buf, "fir-low", 7) == 0) { + ath5k_ani_set_firstep_level(ah, 0); + } else if (strncmp(buf, "fir-high", 8) == 0) { + ath5k_ani_set_firstep_level(ah, ATH5K_ANI_MAX_FIRSTEP_LVL); + } else if (strncmp(buf, "ofdm-off", 8) == 0) { + ath5k_ani_set_ofdm_weak_signal_detection(ah, false); + } else if (strncmp(buf, "ofdm-on", 7) == 0) { + ath5k_ani_set_ofdm_weak_signal_detection(ah, true); + } else if (strncmp(buf, "cck-off", 7) == 0) { + ath5k_ani_set_cck_weak_signal_detection(ah, false); + } else if (strncmp(buf, "cck-on", 6) == 0) { + ath5k_ani_set_cck_weak_signal_detection(ah, true); + } + return count; +} + +static const struct file_operations fops_ani = { + .read = read_file_ani, + .write = write_file_ani, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + + +/* debugfs: queues etc */ + +static ssize_t read_file_queue(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath5k_hw *ah = file->private_data; + char buf[700]; + unsigned int len = 0; + + struct ath5k_txq *txq; + struct ath5k_buf *bf, *bf0; + int i, n; + + len += snprintf(buf + len, sizeof(buf) - len, + "available txbuffers: %d\n", ah->txbuf_len); + + for (i = 0; i < ARRAY_SIZE(ah->txqs); i++) { + txq = &ah->txqs[i]; + + len += snprintf(buf + len, sizeof(buf) - len, + "%02d: %ssetup\n", i, txq->setup ? "" : "not "); + + if (!txq->setup) + continue; + + n = 0; + spin_lock_bh(&txq->lock); + list_for_each_entry_safe(bf, bf0, &txq->q, list) + n++; + spin_unlock_bh(&txq->lock); + + len += snprintf(buf + len, sizeof(buf) - len, + " len: %d bufs: %d\n", txq->txq_len, n); + len += snprintf(buf + len, sizeof(buf) - len, + " stuck: %d\n", txq->txq_stuck); + } + + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_queue(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct ath5k_hw *ah = file->private_data; + char buf[20]; + + if (copy_from_user(buf, userbuf, min(count, sizeof(buf)))) + return -EFAULT; + + if (strncmp(buf, "start", 5) == 0) + ieee80211_wake_queues(ah->hw); + else if (strncmp(buf, "stop", 4) == 0) + ieee80211_stop_queues(ah->hw); + + return count; +} + + +static const struct file_operations fops_queue = { + .read = read_file_queue, + .write = write_file_queue, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + + +void +ath5k_debug_init_device(struct ath5k_hw *ah) +{ + struct dentry *phydir; + + ah->debug.level = ath5k_debug; + + phydir = debugfs_create_dir("ath5k", ah->hw->wiphy->debugfsdir); + if (!phydir) + return; + + debugfs_create_file("debug", S_IWUSR | S_IRUSR, phydir, ah, + &fops_debug); + + debugfs_create_file("registers", S_IRUSR, phydir, ah, &fops_registers); + + debugfs_create_file("beacon", S_IWUSR | S_IRUSR, phydir, ah, + &fops_beacon); + + debugfs_create_file("reset", S_IWUSR, phydir, ah, &fops_reset); + + debugfs_create_file("antenna", S_IWUSR | S_IRUSR, phydir, ah, + &fops_antenna); + + debugfs_create_file("misc", S_IRUSR, phydir, ah, &fops_misc); + + debugfs_create_file("frameerrors", S_IWUSR | S_IRUSR, phydir, ah, + &fops_frameerrors); + + debugfs_create_file("ani", S_IWUSR | S_IRUSR, phydir, ah, &fops_ani); + + debugfs_create_file("queue", S_IWUSR | S_IRUSR, phydir, ah, + &fops_queue); + + debugfs_create_bool("32khz_clock", S_IWUSR | S_IRUSR, phydir, + &ah->ah_use_32khz_clock); +} + +/* functions used in other places */ + +void +ath5k_debug_dump_bands(struct ath5k_hw *ah) +{ + unsigned int b, i; + + if (likely(!(ah->debug.level & ATH5K_DEBUG_DUMPBANDS))) + return; + + BUG_ON(!ah->sbands); + + for (b = 0; b < IEEE80211_NUM_BANDS; b++) { + struct ieee80211_supported_band *band = &ah->sbands[b]; + char bname[6]; + switch (band->band) { + case IEEE80211_BAND_2GHZ: + strcpy(bname, "2 GHz"); + break; + case IEEE80211_BAND_5GHZ: + strcpy(bname, "5 GHz"); + break; + default: + printk(KERN_DEBUG "Band not supported: %d\n", + band->band); + return; + } + printk(KERN_DEBUG "Band %s: channels %d, rates %d\n", bname, + band->n_channels, band->n_bitrates); + printk(KERN_DEBUG " channels:\n"); + for (i = 0; i < band->n_channels; i++) + printk(KERN_DEBUG " %3d %d %.4x %.4x\n", + ieee80211_frequency_to_channel( + band->channels[i].center_freq), + band->channels[i].center_freq, + band->channels[i].hw_value, + band->channels[i].flags); + printk(KERN_DEBUG " rates:\n"); + for (i = 0; i < band->n_bitrates; i++) + printk(KERN_DEBUG " %4d %.4x %.4x %.4x\n", + band->bitrates[i].bitrate, + band->bitrates[i].hw_value, + band->bitrates[i].flags, + band->bitrates[i].hw_value_short); + } +} + +static inline void +ath5k_debug_printrxbuf(struct ath5k_buf *bf, int done, + struct ath5k_rx_status *rs) +{ + struct ath5k_desc *ds = bf->desc; + struct ath5k_hw_all_rx_desc *rd = &ds->ud.ds_rx; + + printk(KERN_DEBUG "R (%p %llx) %08x %08x %08x %08x %08x %08x %c\n", + ds, (unsigned long long)bf->daddr, + ds->ds_link, ds->ds_data, + rd->rx_ctl.rx_control_0, rd->rx_ctl.rx_control_1, + rd->rx_stat.rx_status_0, rd->rx_stat.rx_status_1, + !done ? ' ' : (rs->rs_status == 0) ? '*' : '!'); +} + +void +ath5k_debug_printrxbuffs(struct ath5k_hw *ah) +{ + struct ath5k_desc *ds; + struct ath5k_buf *bf; + struct ath5k_rx_status rs = {}; + int status; + + if (likely(!(ah->debug.level & ATH5K_DEBUG_DESC))) + return; + + printk(KERN_DEBUG "rxdp %x, rxlink %p\n", + ath5k_hw_get_rxdp(ah), ah->rxlink); + + spin_lock_bh(&ah->rxbuflock); + list_for_each_entry(bf, &ah->rxbuf, list) { + ds = bf->desc; + status = ah->ah_proc_rx_desc(ah, ds, &rs); + if (!status) + ath5k_debug_printrxbuf(bf, status == 0, &rs); + } + spin_unlock_bh(&ah->rxbuflock); +} + +void +ath5k_debug_printtxbuf(struct ath5k_hw *ah, struct ath5k_buf *bf) +{ + struct ath5k_desc *ds = bf->desc; + struct ath5k_hw_5212_tx_desc *td = &ds->ud.ds_tx5212; + struct ath5k_tx_status ts = {}; + int done; + + if (likely(!(ah->debug.level & ATH5K_DEBUG_DESC))) + return; + + done = ah->ah_proc_tx_desc(ah, bf->desc, &ts); + + printk(KERN_DEBUG "T (%p %llx) %08x %08x %08x %08x %08x %08x %08x " + "%08x %c\n", ds, (unsigned long long)bf->daddr, ds->ds_link, + ds->ds_data, td->tx_ctl.tx_control_0, td->tx_ctl.tx_control_1, + td->tx_ctl.tx_control_2, td->tx_ctl.tx_control_3, + td->tx_stat.tx_status_0, td->tx_stat.tx_status_1, + done ? ' ' : (ts.ts_status == 0) ? '*' : '!'); +} diff --git a/drivers/net/wireless/ath/ath5k/debug.h b/drivers/net/wireless/ath/ath5k/debug.h new file mode 100644 index 0000000..a3f964b --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/debug.h @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2007 Bruno Randolf + * + * This file is free software: you may copy, redistribute and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting + * Copyright (c) 2004-2005 Atheros Communications, Inc. + * Copyright (c) 2006 Devicescape Software, Inc. + * Copyright (c) 2007 Jiri Slaby + * Copyright (c) 2007 Luis R. Rodriguez + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + */ + +#ifndef _ATH5K_DEBUG_H +#define _ATH5K_DEBUG_H + +struct ath5k_hw; +struct sk_buff; +struct ath5k_buf; + +struct ath5k_dbg_info { + unsigned int level; /* debug level */ +}; + +/** + * enum ath5k_debug_level - ath5k debug level + * + * @ATH5K_DEBUG_RESET: reset processing + * @ATH5K_DEBUG_INTR: interrupt handling + * @ATH5K_DEBUG_MODE: mode init/setup + * @ATH5K_DEBUG_XMIT: basic xmit operation + * @ATH5K_DEBUG_BEACON: beacon handling + * @ATH5K_DEBUG_CALIBRATE: periodic calibration + * @ATH5K_DEBUG_TXPOWER: transmit power setting + * @ATH5K_DEBUG_LED: led management + * @ATH5K_DEBUG_DUMP_RX: print received skb content + * @ATH5K_DEBUG_DUMP_TX: print transmit skb content + * @ATH5K_DEBUG_DUMPBANDS: dump bands + * @ATH5K_DEBUG_DMA: debug dma start/stop + * @ATH5K_DEBUG_TRACE: trace function calls + * @ATH5K_DEBUG_DESC: descriptor setup + * @ATH5K_DEBUG_ANY: show at any debug level + * + * The debug level is used to control the amount and type of debugging output + * we want to see. The debug level is given in calls to ATH5K_DBG to specify + * where the message should appear, and the user can control the debugging + * messages he wants to see, either by the module parameter 'debug' on module + * load, or dynamically by using debugfs 'ath5k/phyX/debug'. these levels can + * be combined together by bitwise OR. + */ +enum ath5k_debug_level { + ATH5K_DEBUG_RESET = 0x00000001, + ATH5K_DEBUG_INTR = 0x00000002, + ATH5K_DEBUG_MODE = 0x00000004, + ATH5K_DEBUG_XMIT = 0x00000008, + ATH5K_DEBUG_BEACON = 0x00000010, + ATH5K_DEBUG_CALIBRATE = 0x00000020, + ATH5K_DEBUG_TXPOWER = 0x00000040, + ATH5K_DEBUG_LED = 0x00000080, + ATH5K_DEBUG_DUMPBANDS = 0x00000400, + ATH5K_DEBUG_DMA = 0x00000800, + ATH5K_DEBUG_ANI = 0x00002000, + ATH5K_DEBUG_DESC = 0x00004000, + ATH5K_DEBUG_ANY = 0xffffffff +}; + +#ifdef CPTCFG_ATH5K_DEBUG + +#define ATH5K_DBG(_sc, _m, _fmt, ...) do { \ + if (unlikely((_sc)->debug.level & (_m) && net_ratelimit())) \ + ATH5K_PRINTK(_sc, KERN_DEBUG, "(%s:%d): " _fmt, \ + __func__, __LINE__, ##__VA_ARGS__); \ + } while (0) + +#define ATH5K_DBG_UNLIMIT(_sc, _m, _fmt, ...) do { \ + if (unlikely((_sc)->debug.level & (_m))) \ + ATH5K_PRINTK(_sc, KERN_DEBUG, "(%s:%d): " _fmt, \ + __func__, __LINE__, ##__VA_ARGS__); \ + } while (0) + +void +ath5k_debug_init_device(struct ath5k_hw *ah); + +void +ath5k_debug_printrxbuffs(struct ath5k_hw *ah); + +void +ath5k_debug_dump_bands(struct ath5k_hw *ah); + +void +ath5k_debug_printtxbuf(struct ath5k_hw *ah, struct ath5k_buf *bf); + +#else /* no debugging */ + +#include + +static inline __printf(3, 4) void +ATH5K_DBG(struct ath5k_hw *ah, unsigned int m, const char *fmt, ...) {} + +static inline __printf(3, 4) void +ATH5K_DBG_UNLIMIT(struct ath5k_hw *ah, unsigned int m, const char *fmt, ...) +{} + +static inline void +ath5k_debug_init_device(struct ath5k_hw *ah) {} + +static inline void +ath5k_debug_printrxbuffs(struct ath5k_hw *ah) {} + +static inline void +ath5k_debug_dump_bands(struct ath5k_hw *ah) {} + +static inline void +ath5k_debug_printtxbuf(struct ath5k_hw *ah, struct ath5k_buf *bf) {} + +#endif /* ifdef CPTCFG_ATH5K_DEBUG */ + +#endif /* ifndef _ATH5K_DEBUG_H */ diff --git a/drivers/net/wireless/ath/ath5k/desc.c b/drivers/net/wireless/ath/ath5k/desc.c new file mode 100644 index 0000000..bd8d439 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/desc.c @@ -0,0 +1,786 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter + * Copyright (c) 2006-2008 Nick Kossifidis + * Copyright (c) 2007-2008 Pavel Roskin + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/******************************\ + Hardware Descriptor Functions +\******************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "ath5k.h" +#include "reg.h" +#include "debug.h" + + +/** + * DOC: Hardware descriptor functions + * + * Here we handle the processing of the low-level hw descriptors + * that hw reads and writes via DMA for each TX and RX attempt (that means + * we can also have descriptors for failed TX/RX tries). We have two kind of + * descriptors for RX and TX, control descriptors tell the hw how to send or + * receive a packet where to read/write it from/to etc and status descriptors + * that contain information about how the packet was sent or received (errors + * included). + * + * Descriptor format is not exactly the same for each MAC chip version so we + * have function pointers on &struct ath5k_hw we initialize at runtime based on + * the chip used. + */ + + +/************************\ +* TX Control descriptors * +\************************/ + +/** + * ath5k_hw_setup_2word_tx_desc() - Initialize a 2-word tx control descriptor + * @ah: The &struct ath5k_hw + * @desc: The &struct ath5k_desc + * @pkt_len: Frame length in bytes + * @hdr_len: Header length in bytes (only used on AR5210) + * @padsize: Any padding we've added to the frame length + * @type: One of enum ath5k_pkt_type + * @tx_power: Tx power in 0.5dB steps + * @tx_rate0: HW idx for transmission rate + * @tx_tries0: Max number of retransmissions + * @key_index: Index on key table to use for encryption + * @antenna_mode: Which antenna to use (0 for auto) + * @flags: One of AR5K_TXDESC_* flags (desc.h) + * @rtscts_rate: HW idx for RTS/CTS transmission rate + * @rtscts_duration: What to put on duration field on the header of RTS/CTS + * + * Internal function to initialize a 2-Word TX control descriptor + * found on AR5210 and AR5211 MACs chips. + * + * Returns 0 on success or -EINVAL on false input + */ +static int +ath5k_hw_setup_2word_tx_desc(struct ath5k_hw *ah, + struct ath5k_desc *desc, + unsigned int pkt_len, unsigned int hdr_len, + int padsize, + enum ath5k_pkt_type type, + unsigned int tx_power, + unsigned int tx_rate0, unsigned int tx_tries0, + unsigned int key_index, + unsigned int antenna_mode, + unsigned int flags, + unsigned int rtscts_rate, unsigned int rtscts_duration) +{ + u32 frame_type; + struct ath5k_hw_2w_tx_ctl *tx_ctl; + unsigned int frame_len; + + tx_ctl = &desc->ud.ds_tx5210.tx_ctl; + + /* + * Validate input + * - Zero retries don't make sense. + * - A zero rate will put the HW into a mode where it continuously sends + * noise on the channel, so it is important to avoid this. + */ + if (unlikely(tx_tries0 == 0)) { + ATH5K_ERR(ah, "zero retries\n"); + WARN_ON(1); + return -EINVAL; + } + if (unlikely(tx_rate0 == 0)) { + ATH5K_ERR(ah, "zero rate\n"); + WARN_ON(1); + return -EINVAL; + } + + /* Clear descriptor */ + memset(&desc->ud.ds_tx5210, 0, sizeof(struct ath5k_hw_5210_tx_desc)); + + /* Setup control descriptor */ + + /* Verify and set frame length */ + + /* remove padding we might have added before */ + frame_len = pkt_len - padsize + FCS_LEN; + + if (frame_len & ~AR5K_2W_TX_DESC_CTL0_FRAME_LEN) + return -EINVAL; + + tx_ctl->tx_control_0 = frame_len & AR5K_2W_TX_DESC_CTL0_FRAME_LEN; + + /* Verify and set buffer length */ + + /* NB: beacon's BufLen must be a multiple of 4 bytes */ + if (type == AR5K_PKT_TYPE_BEACON) + pkt_len = roundup(pkt_len, 4); + + if (pkt_len & ~AR5K_2W_TX_DESC_CTL1_BUF_LEN) + return -EINVAL; + + tx_ctl->tx_control_1 = pkt_len & AR5K_2W_TX_DESC_CTL1_BUF_LEN; + + /* + * Verify and set header length (only 5210) + */ + if (ah->ah_version == AR5K_AR5210) { + if (hdr_len & ~AR5K_2W_TX_DESC_CTL0_HEADER_LEN_5210) + return -EINVAL; + tx_ctl->tx_control_0 |= + AR5K_REG_SM(hdr_len, AR5K_2W_TX_DESC_CTL0_HEADER_LEN_5210); + } + + /*Differences between 5210-5211*/ + if (ah->ah_version == AR5K_AR5210) { + switch (type) { + case AR5K_PKT_TYPE_BEACON: + case AR5K_PKT_TYPE_PROBE_RESP: + frame_type = AR5K_AR5210_TX_DESC_FRAME_TYPE_NO_DELAY; + break; + case AR5K_PKT_TYPE_PIFS: + frame_type = AR5K_AR5210_TX_DESC_FRAME_TYPE_PIFS; + break; + default: + frame_type = type; + break; + } + + tx_ctl->tx_control_0 |= + AR5K_REG_SM(frame_type, AR5K_2W_TX_DESC_CTL0_FRAME_TYPE_5210) | + AR5K_REG_SM(tx_rate0, AR5K_2W_TX_DESC_CTL0_XMIT_RATE); + + } else { + tx_ctl->tx_control_0 |= + AR5K_REG_SM(tx_rate0, AR5K_2W_TX_DESC_CTL0_XMIT_RATE) | + AR5K_REG_SM(antenna_mode, + AR5K_2W_TX_DESC_CTL0_ANT_MODE_XMIT); + tx_ctl->tx_control_1 |= + AR5K_REG_SM(type, AR5K_2W_TX_DESC_CTL1_FRAME_TYPE_5211); + } + +#define _TX_FLAGS(_c, _flag) \ + if (flags & AR5K_TXDESC_##_flag) { \ + tx_ctl->tx_control_##_c |= \ + AR5K_2W_TX_DESC_CTL##_c##_##_flag; \ + } +#define _TX_FLAGS_5211(_c, _flag) \ + if (flags & AR5K_TXDESC_##_flag) { \ + tx_ctl->tx_control_##_c |= \ + AR5K_2W_TX_DESC_CTL##_c##_##_flag##_5211; \ + } + _TX_FLAGS(0, CLRDMASK); + _TX_FLAGS(0, INTREQ); + _TX_FLAGS(0, RTSENA); + + if (ah->ah_version == AR5K_AR5211) { + _TX_FLAGS_5211(0, VEOL); + _TX_FLAGS_5211(1, NOACK); + } + +#undef _TX_FLAGS +#undef _TX_FLAGS_5211 + + /* + * WEP crap + */ + if (key_index != AR5K_TXKEYIX_INVALID) { + tx_ctl->tx_control_0 |= + AR5K_2W_TX_DESC_CTL0_ENCRYPT_KEY_VALID; + tx_ctl->tx_control_1 |= + AR5K_REG_SM(key_index, + AR5K_2W_TX_DESC_CTL1_ENC_KEY_IDX); + } + + /* + * RTS/CTS Duration [5210 ?] + */ + if ((ah->ah_version == AR5K_AR5210) && + (flags & (AR5K_TXDESC_RTSENA | AR5K_TXDESC_CTSENA))) + tx_ctl->tx_control_1 |= rtscts_duration & + AR5K_2W_TX_DESC_CTL1_RTS_DURATION_5210; + + return 0; +} + +/** + * ath5k_hw_setup_4word_tx_desc() - Initialize a 4-word tx control descriptor + * @ah: The &struct ath5k_hw + * @desc: The &struct ath5k_desc + * @pkt_len: Frame length in bytes + * @hdr_len: Header length in bytes (only used on AR5210) + * @padsize: Any padding we've added to the frame length + * @type: One of enum ath5k_pkt_type + * @tx_power: Tx power in 0.5dB steps + * @tx_rate0: HW idx for transmission rate + * @tx_tries0: Max number of retransmissions + * @key_index: Index on key table to use for encryption + * @antenna_mode: Which antenna to use (0 for auto) + * @flags: One of AR5K_TXDESC_* flags (desc.h) + * @rtscts_rate: HW idx for RTS/CTS transmission rate + * @rtscts_duration: What to put on duration field on the header of RTS/CTS + * + * Internal function to initialize a 4-Word TX control descriptor + * found on AR5212 and later MACs chips. + * + * Returns 0 on success or -EINVAL on false input + */ +static int +ath5k_hw_setup_4word_tx_desc(struct ath5k_hw *ah, + struct ath5k_desc *desc, + unsigned int pkt_len, unsigned int hdr_len, + int padsize, + enum ath5k_pkt_type type, + unsigned int tx_power, + unsigned int tx_rate0, unsigned int tx_tries0, + unsigned int key_index, + unsigned int antenna_mode, + unsigned int flags, + unsigned int rtscts_rate, unsigned int rtscts_duration) +{ + struct ath5k_hw_4w_tx_ctl *tx_ctl; + unsigned int frame_len; + + /* + * Use local variables for these to reduce load/store access on + * uncached memory + */ + u32 txctl0 = 0, txctl1 = 0, txctl2 = 0, txctl3 = 0; + + tx_ctl = &desc->ud.ds_tx5212.tx_ctl; + + /* + * Validate input + * - Zero retries don't make sense. + * - A zero rate will put the HW into a mode where it continuously sends + * noise on the channel, so it is important to avoid this. + */ + if (unlikely(tx_tries0 == 0)) { + ATH5K_ERR(ah, "zero retries\n"); + WARN_ON(1); + return -EINVAL; + } + if (unlikely(tx_rate0 == 0)) { + ATH5K_ERR(ah, "zero rate\n"); + WARN_ON(1); + return -EINVAL; + } + + tx_power += ah->ah_txpower.txp_offset; + if (tx_power > AR5K_TUNE_MAX_TXPOWER) + tx_power = AR5K_TUNE_MAX_TXPOWER; + + /* Clear descriptor status area */ + memset(&desc->ud.ds_tx5212.tx_stat, 0, + sizeof(desc->ud.ds_tx5212.tx_stat)); + + /* Setup control descriptor */ + + /* Verify and set frame length */ + + /* remove padding we might have added before */ + frame_len = pkt_len - padsize + FCS_LEN; + + if (frame_len & ~AR5K_4W_TX_DESC_CTL0_FRAME_LEN) + return -EINVAL; + + txctl0 = frame_len & AR5K_4W_TX_DESC_CTL0_FRAME_LEN; + + /* Verify and set buffer length */ + + /* NB: beacon's BufLen must be a multiple of 4 bytes */ + if (type == AR5K_PKT_TYPE_BEACON) + pkt_len = roundup(pkt_len, 4); + + if (pkt_len & ~AR5K_4W_TX_DESC_CTL1_BUF_LEN) + return -EINVAL; + + txctl1 = pkt_len & AR5K_4W_TX_DESC_CTL1_BUF_LEN; + + txctl0 |= AR5K_REG_SM(tx_power, AR5K_4W_TX_DESC_CTL0_XMIT_POWER) | + AR5K_REG_SM(antenna_mode, AR5K_4W_TX_DESC_CTL0_ANT_MODE_XMIT); + txctl1 |= AR5K_REG_SM(type, AR5K_4W_TX_DESC_CTL1_FRAME_TYPE); + txctl2 = AR5K_REG_SM(tx_tries0, AR5K_4W_TX_DESC_CTL2_XMIT_TRIES0); + txctl3 = tx_rate0 & AR5K_4W_TX_DESC_CTL3_XMIT_RATE0; + +#define _TX_FLAGS(_c, _flag) \ + if (flags & AR5K_TXDESC_##_flag) { \ + txctl##_c |= AR5K_4W_TX_DESC_CTL##_c##_##_flag; \ + } + + _TX_FLAGS(0, CLRDMASK); + _TX_FLAGS(0, VEOL); + _TX_FLAGS(0, INTREQ); + _TX_FLAGS(0, RTSENA); + _TX_FLAGS(0, CTSENA); + _TX_FLAGS(1, NOACK); + +#undef _TX_FLAGS + + /* + * WEP crap + */ + if (key_index != AR5K_TXKEYIX_INVALID) { + txctl0 |= AR5K_4W_TX_DESC_CTL0_ENCRYPT_KEY_VALID; + txctl1 |= AR5K_REG_SM(key_index, + AR5K_4W_TX_DESC_CTL1_ENCRYPT_KEY_IDX); + } + + /* + * RTS/CTS + */ + if (flags & (AR5K_TXDESC_RTSENA | AR5K_TXDESC_CTSENA)) { + if ((flags & AR5K_TXDESC_RTSENA) && + (flags & AR5K_TXDESC_CTSENA)) + return -EINVAL; + txctl2 |= rtscts_duration & AR5K_4W_TX_DESC_CTL2_RTS_DURATION; + txctl3 |= AR5K_REG_SM(rtscts_rate, + AR5K_4W_TX_DESC_CTL3_RTS_CTS_RATE); + } + + tx_ctl->tx_control_0 = txctl0; + tx_ctl->tx_control_1 = txctl1; + tx_ctl->tx_control_2 = txctl2; + tx_ctl->tx_control_3 = txctl3; + + return 0; +} + +/** + * ath5k_hw_setup_mrr_tx_desc() - Initialize an MRR tx control descriptor + * @ah: The &struct ath5k_hw + * @desc: The &struct ath5k_desc + * @tx_rate1: HW idx for rate used on transmission series 1 + * @tx_tries1: Max number of retransmissions for transmission series 1 + * @tx_rate2: HW idx for rate used on transmission series 2 + * @tx_tries2: Max number of retransmissions for transmission series 2 + * @tx_rate3: HW idx for rate used on transmission series 3 + * @tx_tries3: Max number of retransmissions for transmission series 3 + * + * Multi rate retry (MRR) tx control descriptors are available only on AR5212 + * MACs, they are part of the normal 4-word tx control descriptor (see above) + * but we handle them through a separate function for better abstraction. + * + * Returns 0 on success or -EINVAL on invalid input + */ +int +ath5k_hw_setup_mrr_tx_desc(struct ath5k_hw *ah, + struct ath5k_desc *desc, + u_int tx_rate1, u_int tx_tries1, + u_int tx_rate2, u_int tx_tries2, + u_int tx_rate3, u_int tx_tries3) +{ + struct ath5k_hw_4w_tx_ctl *tx_ctl; + + /* no mrr support for cards older than 5212 */ + if (ah->ah_version < AR5K_AR5212) + return 0; + + /* + * Rates can be 0 as long as the retry count is 0 too. + * A zero rate and nonzero retry count will put the HW into a mode where + * it continuously sends noise on the channel, so it is important to + * avoid this. + */ + if (unlikely((tx_rate1 == 0 && tx_tries1 != 0) || + (tx_rate2 == 0 && tx_tries2 != 0) || + (tx_rate3 == 0 && tx_tries3 != 0))) { + ATH5K_ERR(ah, "zero rate\n"); + WARN_ON(1); + return -EINVAL; + } + + if (ah->ah_version == AR5K_AR5212) { + tx_ctl = &desc->ud.ds_tx5212.tx_ctl; + +#define _XTX_TRIES(_n) \ + if (tx_tries##_n) { \ + tx_ctl->tx_control_2 |= \ + AR5K_REG_SM(tx_tries##_n, \ + AR5K_4W_TX_DESC_CTL2_XMIT_TRIES##_n); \ + tx_ctl->tx_control_3 |= \ + AR5K_REG_SM(tx_rate##_n, \ + AR5K_4W_TX_DESC_CTL3_XMIT_RATE##_n); \ + } + + _XTX_TRIES(1); + _XTX_TRIES(2); + _XTX_TRIES(3); + +#undef _XTX_TRIES + + return 1; + } + + return 0; +} + + +/***********************\ +* TX Status descriptors * +\***********************/ + +/** + * ath5k_hw_proc_2word_tx_status() - Process a tx status descriptor on 5210/1 + * @ah: The &struct ath5k_hw + * @desc: The &struct ath5k_desc + * @ts: The &struct ath5k_tx_status + */ +static int +ath5k_hw_proc_2word_tx_status(struct ath5k_hw *ah, + struct ath5k_desc *desc, + struct ath5k_tx_status *ts) +{ + struct ath5k_hw_tx_status *tx_status; + + tx_status = &desc->ud.ds_tx5210.tx_stat; + + /* No frame has been send or error */ + if (unlikely((tx_status->tx_status_1 & AR5K_DESC_TX_STATUS1_DONE) == 0)) + return -EINPROGRESS; + + /* + * Get descriptor status + */ + ts->ts_tstamp = AR5K_REG_MS(tx_status->tx_status_0, + AR5K_DESC_TX_STATUS0_SEND_TIMESTAMP); + ts->ts_shortretry = AR5K_REG_MS(tx_status->tx_status_0, + AR5K_DESC_TX_STATUS0_SHORT_RETRY_COUNT); + ts->ts_final_retry = AR5K_REG_MS(tx_status->tx_status_0, + AR5K_DESC_TX_STATUS0_LONG_RETRY_COUNT); + /*TODO: ts->ts_virtcol + test*/ + ts->ts_seqnum = AR5K_REG_MS(tx_status->tx_status_1, + AR5K_DESC_TX_STATUS1_SEQ_NUM); + ts->ts_rssi = AR5K_REG_MS(tx_status->tx_status_1, + AR5K_DESC_TX_STATUS1_ACK_SIG_STRENGTH); + ts->ts_antenna = 1; + ts->ts_status = 0; + ts->ts_final_idx = 0; + + if (!(tx_status->tx_status_0 & AR5K_DESC_TX_STATUS0_FRAME_XMIT_OK)) { + if (tx_status->tx_status_0 & + AR5K_DESC_TX_STATUS0_EXCESSIVE_RETRIES) + ts->ts_status |= AR5K_TXERR_XRETRY; + + if (tx_status->tx_status_0 & AR5K_DESC_TX_STATUS0_FIFO_UNDERRUN) + ts->ts_status |= AR5K_TXERR_FIFO; + + if (tx_status->tx_status_0 & AR5K_DESC_TX_STATUS0_FILTERED) + ts->ts_status |= AR5K_TXERR_FILT; + } + + return 0; +} + +/** + * ath5k_hw_proc_4word_tx_status() - Process a tx status descriptor on 5212 + * @ah: The &struct ath5k_hw + * @desc: The &struct ath5k_desc + * @ts: The &struct ath5k_tx_status + */ +static int +ath5k_hw_proc_4word_tx_status(struct ath5k_hw *ah, + struct ath5k_desc *desc, + struct ath5k_tx_status *ts) +{ + struct ath5k_hw_tx_status *tx_status; + u32 txstat0, txstat1; + + tx_status = &desc->ud.ds_tx5212.tx_stat; + + txstat1 = ACCESS_ONCE(tx_status->tx_status_1); + + /* No frame has been send or error */ + if (unlikely(!(txstat1 & AR5K_DESC_TX_STATUS1_DONE))) + return -EINPROGRESS; + + txstat0 = ACCESS_ONCE(tx_status->tx_status_0); + + /* + * Get descriptor status + */ + ts->ts_tstamp = AR5K_REG_MS(txstat0, + AR5K_DESC_TX_STATUS0_SEND_TIMESTAMP); + ts->ts_shortretry = AR5K_REG_MS(txstat0, + AR5K_DESC_TX_STATUS0_SHORT_RETRY_COUNT); + ts->ts_final_retry = AR5K_REG_MS(txstat0, + AR5K_DESC_TX_STATUS0_LONG_RETRY_COUNT); + ts->ts_seqnum = AR5K_REG_MS(txstat1, + AR5K_DESC_TX_STATUS1_SEQ_NUM); + ts->ts_rssi = AR5K_REG_MS(txstat1, + AR5K_DESC_TX_STATUS1_ACK_SIG_STRENGTH); + ts->ts_antenna = (txstat1 & + AR5K_DESC_TX_STATUS1_XMIT_ANTENNA_5212) ? 2 : 1; + ts->ts_status = 0; + + ts->ts_final_idx = AR5K_REG_MS(txstat1, + AR5K_DESC_TX_STATUS1_FINAL_TS_IX_5212); + + /* TX error */ + if (!(txstat0 & AR5K_DESC_TX_STATUS0_FRAME_XMIT_OK)) { + if (txstat0 & AR5K_DESC_TX_STATUS0_EXCESSIVE_RETRIES) + ts->ts_status |= AR5K_TXERR_XRETRY; + + if (txstat0 & AR5K_DESC_TX_STATUS0_FIFO_UNDERRUN) + ts->ts_status |= AR5K_TXERR_FIFO; + + if (txstat0 & AR5K_DESC_TX_STATUS0_FILTERED) + ts->ts_status |= AR5K_TXERR_FILT; + } + + return 0; +} + + +/****************\ +* RX Descriptors * +\****************/ + +/** + * ath5k_hw_setup_rx_desc() - Initialize an rx control descriptor + * @ah: The &struct ath5k_hw + * @desc: The &struct ath5k_desc + * @size: RX buffer length in bytes + * @flags: One of AR5K_RXDESC_* flags + */ +int +ath5k_hw_setup_rx_desc(struct ath5k_hw *ah, + struct ath5k_desc *desc, + u32 size, unsigned int flags) +{ + struct ath5k_hw_rx_ctl *rx_ctl; + + rx_ctl = &desc->ud.ds_rx.rx_ctl; + + /* + * Clear the descriptor + * If we don't clean the status descriptor, + * while scanning we get too many results, + * most of them virtual, after some secs + * of scanning system hangs. M.F. + */ + memset(&desc->ud.ds_rx, 0, sizeof(struct ath5k_hw_all_rx_desc)); + + if (unlikely(size & ~AR5K_DESC_RX_CTL1_BUF_LEN)) + return -EINVAL; + + /* Setup descriptor */ + rx_ctl->rx_control_1 = size & AR5K_DESC_RX_CTL1_BUF_LEN; + + if (flags & AR5K_RXDESC_INTREQ) + rx_ctl->rx_control_1 |= AR5K_DESC_RX_CTL1_INTREQ; + + return 0; +} + +/** + * ath5k_hw_proc_5210_rx_status() - Process the rx status descriptor on 5210/1 + * @ah: The &struct ath5k_hw + * @desc: The &struct ath5k_desc + * @rs: The &struct ath5k_rx_status + * + * Internal function used to process an RX status descriptor + * on AR5210/5211 MAC. + * + * Returns 0 on success or -EINPROGRESS in case we haven't received the who;e + * frame yet. + */ +static int +ath5k_hw_proc_5210_rx_status(struct ath5k_hw *ah, + struct ath5k_desc *desc, + struct ath5k_rx_status *rs) +{ + struct ath5k_hw_rx_status *rx_status; + + rx_status = &desc->ud.ds_rx.rx_stat; + + /* No frame received / not ready */ + if (unlikely(!(rx_status->rx_status_1 & + AR5K_5210_RX_DESC_STATUS1_DONE))) + return -EINPROGRESS; + + memset(rs, 0, sizeof(struct ath5k_rx_status)); + + /* + * Frame receive status + */ + rs->rs_datalen = rx_status->rx_status_0 & + AR5K_5210_RX_DESC_STATUS0_DATA_LEN; + rs->rs_rssi = AR5K_REG_MS(rx_status->rx_status_0, + AR5K_5210_RX_DESC_STATUS0_RECEIVE_SIGNAL); + rs->rs_rate = AR5K_REG_MS(rx_status->rx_status_0, + AR5K_5210_RX_DESC_STATUS0_RECEIVE_RATE); + rs->rs_more = !!(rx_status->rx_status_0 & + AR5K_5210_RX_DESC_STATUS0_MORE); + /* TODO: this timestamp is 13 bit, later on we assume 15 bit! + * also the HAL code for 5210 says the timestamp is bits [10..22] of the + * TSF, and extends the timestamp here to 15 bit. + * we need to check on 5210... + */ + rs->rs_tstamp = AR5K_REG_MS(rx_status->rx_status_1, + AR5K_5210_RX_DESC_STATUS1_RECEIVE_TIMESTAMP); + + if (ah->ah_version == AR5K_AR5211) + rs->rs_antenna = AR5K_REG_MS(rx_status->rx_status_0, + AR5K_5210_RX_DESC_STATUS0_RECEIVE_ANT_5211); + else + rs->rs_antenna = (rx_status->rx_status_0 & + AR5K_5210_RX_DESC_STATUS0_RECEIVE_ANT_5210) + ? 2 : 1; + + /* + * Key table status + */ + if (rx_status->rx_status_1 & AR5K_5210_RX_DESC_STATUS1_KEY_INDEX_VALID) + rs->rs_keyix = AR5K_REG_MS(rx_status->rx_status_1, + AR5K_5210_RX_DESC_STATUS1_KEY_INDEX); + else + rs->rs_keyix = AR5K_RXKEYIX_INVALID; + + /* + * Receive/descriptor errors + */ + if (!(rx_status->rx_status_1 & + AR5K_5210_RX_DESC_STATUS1_FRAME_RECEIVE_OK)) { + if (rx_status->rx_status_1 & + AR5K_5210_RX_DESC_STATUS1_CRC_ERROR) + rs->rs_status |= AR5K_RXERR_CRC; + + /* only on 5210 */ + if ((ah->ah_version == AR5K_AR5210) && + (rx_status->rx_status_1 & + AR5K_5210_RX_DESC_STATUS1_FIFO_OVERRUN_5210)) + rs->rs_status |= AR5K_RXERR_FIFO; + + if (rx_status->rx_status_1 & + AR5K_5210_RX_DESC_STATUS1_PHY_ERROR) { + rs->rs_status |= AR5K_RXERR_PHY; + rs->rs_phyerr = AR5K_REG_MS(rx_status->rx_status_1, + AR5K_5210_RX_DESC_STATUS1_PHY_ERROR); + } + + if (rx_status->rx_status_1 & + AR5K_5210_RX_DESC_STATUS1_DECRYPT_CRC_ERROR) + rs->rs_status |= AR5K_RXERR_DECRYPT; + } + + return 0; +} + +/** + * ath5k_hw_proc_5212_rx_status() - Process the rx status descriptor on 5212 + * @ah: The &struct ath5k_hw + * @desc: The &struct ath5k_desc + * @rs: The &struct ath5k_rx_status + * + * Internal function used to process an RX status descriptor + * on AR5212 and later MAC. + * + * Returns 0 on success or -EINPROGRESS in case we haven't received the who;e + * frame yet. + */ +static int +ath5k_hw_proc_5212_rx_status(struct ath5k_hw *ah, + struct ath5k_desc *desc, + struct ath5k_rx_status *rs) +{ + struct ath5k_hw_rx_status *rx_status; + u32 rxstat0, rxstat1; + + rx_status = &desc->ud.ds_rx.rx_stat; + rxstat1 = ACCESS_ONCE(rx_status->rx_status_1); + + /* No frame received / not ready */ + if (unlikely(!(rxstat1 & AR5K_5212_RX_DESC_STATUS1_DONE))) + return -EINPROGRESS; + + memset(rs, 0, sizeof(struct ath5k_rx_status)); + rxstat0 = ACCESS_ONCE(rx_status->rx_status_0); + + /* + * Frame receive status + */ + rs->rs_datalen = rxstat0 & AR5K_5212_RX_DESC_STATUS0_DATA_LEN; + rs->rs_rssi = AR5K_REG_MS(rxstat0, + AR5K_5212_RX_DESC_STATUS0_RECEIVE_SIGNAL); + rs->rs_rate = AR5K_REG_MS(rxstat0, + AR5K_5212_RX_DESC_STATUS0_RECEIVE_RATE); + rs->rs_antenna = AR5K_REG_MS(rxstat0, + AR5K_5212_RX_DESC_STATUS0_RECEIVE_ANTENNA); + rs->rs_more = !!(rxstat0 & AR5K_5212_RX_DESC_STATUS0_MORE); + rs->rs_tstamp = AR5K_REG_MS(rxstat1, + AR5K_5212_RX_DESC_STATUS1_RECEIVE_TIMESTAMP); + + /* + * Key table status + */ + if (rxstat1 & AR5K_5212_RX_DESC_STATUS1_KEY_INDEX_VALID) + rs->rs_keyix = AR5K_REG_MS(rxstat1, + AR5K_5212_RX_DESC_STATUS1_KEY_INDEX); + else + rs->rs_keyix = AR5K_RXKEYIX_INVALID; + + /* + * Receive/descriptor errors + */ + if (!(rxstat1 & AR5K_5212_RX_DESC_STATUS1_FRAME_RECEIVE_OK)) { + if (rxstat1 & AR5K_5212_RX_DESC_STATUS1_CRC_ERROR) + rs->rs_status |= AR5K_RXERR_CRC; + + if (rxstat1 & AR5K_5212_RX_DESC_STATUS1_PHY_ERROR) { + rs->rs_status |= AR5K_RXERR_PHY; + rs->rs_phyerr = AR5K_REG_MS(rxstat1, + AR5K_5212_RX_DESC_STATUS1_PHY_ERROR_CODE); + if (!ah->ah_capabilities.cap_has_phyerr_counters) + ath5k_ani_phy_error_report(ah, rs->rs_phyerr); + } + + if (rxstat1 & AR5K_5212_RX_DESC_STATUS1_DECRYPT_CRC_ERROR) + rs->rs_status |= AR5K_RXERR_DECRYPT; + + if (rxstat1 & AR5K_5212_RX_DESC_STATUS1_MIC_ERROR) + rs->rs_status |= AR5K_RXERR_MIC; + } + return 0; +} + + +/********\ +* Attach * +\********/ + +/** + * ath5k_hw_init_desc_functions() - Init function pointers inside ah + * @ah: The &struct ath5k_hw + * + * Maps the internal descriptor functions to the function pointers on ah, used + * from above. This is used as an abstraction layer to handle the various chips + * the same way. + */ +int +ath5k_hw_init_desc_functions(struct ath5k_hw *ah) +{ + if (ah->ah_version == AR5K_AR5212) { + ah->ah_setup_tx_desc = ath5k_hw_setup_4word_tx_desc; + ah->ah_proc_tx_desc = ath5k_hw_proc_4word_tx_status; + ah->ah_proc_rx_desc = ath5k_hw_proc_5212_rx_status; + } else if (ah->ah_version <= AR5K_AR5211) { + ah->ah_setup_tx_desc = ath5k_hw_setup_2word_tx_desc; + ah->ah_proc_tx_desc = ath5k_hw_proc_2word_tx_status; + ah->ah_proc_rx_desc = ath5k_hw_proc_5210_rx_status; + } else + return -ENOTSUPP; + return 0; +} diff --git a/drivers/net/wireless/ath/ath5k/desc.h b/drivers/net/wireless/ath/ath5k/desc.h new file mode 100644 index 0000000..8d6c01a --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/desc.h @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter + * Copyright (c) 2006-2008 Nick Kossifidis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/* + * RX/TX descriptor structures + */ + +/** + * struct ath5k_hw_rx_ctl - Common hardware RX control descriptor + * @rx_control_0: RX control word 0 + * @rx_control_1: RX control word 1 + */ +struct ath5k_hw_rx_ctl { + u32 rx_control_0; + u32 rx_control_1; +} __packed __aligned(4); + +/* RX control word 1 fields/flags */ +#define AR5K_DESC_RX_CTL1_BUF_LEN 0x00000fff /* data buffer length */ +#define AR5K_DESC_RX_CTL1_INTREQ 0x00002000 /* RX interrupt request */ + +/** + * struct ath5k_hw_rx_status - Common hardware RX status descriptor + * @rx_status_0: RX status word 0 + * @rx_status_1: RX status word 1 + * + * 5210, 5211 and 5212 differ only in the fields and flags defined below + */ +struct ath5k_hw_rx_status { + u32 rx_status_0; + u32 rx_status_1; +} __packed __aligned(4); + +/* 5210/5211 */ +/* RX status word 0 fields/flags */ +#define AR5K_5210_RX_DESC_STATUS0_DATA_LEN 0x00000fff /* RX data length */ +#define AR5K_5210_RX_DESC_STATUS0_MORE 0x00001000 /* more desc for this frame */ +#define AR5K_5210_RX_DESC_STATUS0_RECEIVE_ANT_5210 0x00004000 /* [5210] receive on ant 1 */ +#define AR5K_5210_RX_DESC_STATUS0_RECEIVE_RATE 0x00078000 /* reception rate */ +#define AR5K_5210_RX_DESC_STATUS0_RECEIVE_RATE_S 15 +#define AR5K_5210_RX_DESC_STATUS0_RECEIVE_SIGNAL 0x07f80000 /* rssi */ +#define AR5K_5210_RX_DESC_STATUS0_RECEIVE_SIGNAL_S 19 +#define AR5K_5210_RX_DESC_STATUS0_RECEIVE_ANT_5211 0x38000000 /* [5211] receive antenna */ +#define AR5K_5210_RX_DESC_STATUS0_RECEIVE_ANT_5211_S 27 + +/* RX status word 1 fields/flags */ +#define AR5K_5210_RX_DESC_STATUS1_DONE 0x00000001 /* descriptor complete */ +#define AR5K_5210_RX_DESC_STATUS1_FRAME_RECEIVE_OK 0x00000002 /* reception success */ +#define AR5K_5210_RX_DESC_STATUS1_CRC_ERROR 0x00000004 /* CRC error */ +#define AR5K_5210_RX_DESC_STATUS1_FIFO_OVERRUN_5210 0x00000008 /* [5210] FIFO overrun */ +#define AR5K_5210_RX_DESC_STATUS1_DECRYPT_CRC_ERROR 0x00000010 /* decryption CRC failure */ +#define AR5K_5210_RX_DESC_STATUS1_PHY_ERROR 0x000000e0 /* PHY error */ +#define AR5K_5210_RX_DESC_STATUS1_PHY_ERROR_S 5 +#define AR5K_5210_RX_DESC_STATUS1_KEY_INDEX_VALID 0x00000100 /* key index valid */ +#define AR5K_5210_RX_DESC_STATUS1_KEY_INDEX 0x00007e00 /* decryption key index */ +#define AR5K_5210_RX_DESC_STATUS1_KEY_INDEX_S 9 +#define AR5K_5210_RX_DESC_STATUS1_RECEIVE_TIMESTAMP 0x0fff8000 /* 13 bit of TSF */ +#define AR5K_5210_RX_DESC_STATUS1_RECEIVE_TIMESTAMP_S 15 +#define AR5K_5210_RX_DESC_STATUS1_KEY_CACHE_MISS 0x10000000 /* key cache miss */ + +/* 5212 */ +/* RX status word 0 fields/flags */ +#define AR5K_5212_RX_DESC_STATUS0_DATA_LEN 0x00000fff /* RX data length */ +#define AR5K_5212_RX_DESC_STATUS0_MORE 0x00001000 /* more desc for this frame */ +#define AR5K_5212_RX_DESC_STATUS0_DECOMP_CRC_ERROR 0x00002000 /* decompression CRC error */ +#define AR5K_5212_RX_DESC_STATUS0_RECEIVE_RATE 0x000f8000 /* reception rate */ +#define AR5K_5212_RX_DESC_STATUS0_RECEIVE_RATE_S 15 +#define AR5K_5212_RX_DESC_STATUS0_RECEIVE_SIGNAL 0x0ff00000 /* rssi */ +#define AR5K_5212_RX_DESC_STATUS0_RECEIVE_SIGNAL_S 20 +#define AR5K_5212_RX_DESC_STATUS0_RECEIVE_ANTENNA 0xf0000000 /* receive antenna */ +#define AR5K_5212_RX_DESC_STATUS0_RECEIVE_ANTENNA_S 28 + +/* RX status word 1 fields/flags */ +#define AR5K_5212_RX_DESC_STATUS1_DONE 0x00000001 /* descriptor complete */ +#define AR5K_5212_RX_DESC_STATUS1_FRAME_RECEIVE_OK 0x00000002 /* frame reception success */ +#define AR5K_5212_RX_DESC_STATUS1_CRC_ERROR 0x00000004 /* CRC error */ +#define AR5K_5212_RX_DESC_STATUS1_DECRYPT_CRC_ERROR 0x00000008 /* decryption CRC failure */ +#define AR5K_5212_RX_DESC_STATUS1_PHY_ERROR 0x00000010 /* PHY error */ +#define AR5K_5212_RX_DESC_STATUS1_MIC_ERROR 0x00000020 /* MIC decrypt error */ +#define AR5K_5212_RX_DESC_STATUS1_KEY_INDEX_VALID 0x00000100 /* key index valid */ +#define AR5K_5212_RX_DESC_STATUS1_KEY_INDEX 0x0000fe00 /* decryption key index */ +#define AR5K_5212_RX_DESC_STATUS1_KEY_INDEX_S 9 +#define AR5K_5212_RX_DESC_STATUS1_RECEIVE_TIMESTAMP 0x7fff0000 /* first 15bit of the TSF */ +#define AR5K_5212_RX_DESC_STATUS1_RECEIVE_TIMESTAMP_S 16 +#define AR5K_5212_RX_DESC_STATUS1_KEY_CACHE_MISS 0x80000000 /* key cache miss */ +#define AR5K_5212_RX_DESC_STATUS1_PHY_ERROR_CODE 0x0000ff00 /* phy error code overlays key index and valid fields */ +#define AR5K_5212_RX_DESC_STATUS1_PHY_ERROR_CODE_S 8 + +/** + * enum ath5k_phy_error_code - PHY Error codes + * @AR5K_RX_PHY_ERROR_UNDERRUN: Transmit underrun, [5210] No error + * @AR5K_RX_PHY_ERROR_TIMING: Timing error + * @AR5K_RX_PHY_ERROR_PARITY: Illegal parity + * @AR5K_RX_PHY_ERROR_RATE: Illegal rate + * @AR5K_RX_PHY_ERROR_LENGTH: Illegal length + * @AR5K_RX_PHY_ERROR_RADAR: Radar detect, [5210] 64 QAM rate + * @AR5K_RX_PHY_ERROR_SERVICE: Illegal service + * @AR5K_RX_PHY_ERROR_TOR: Transmit override receive + * @AR5K_RX_PHY_ERROR_OFDM_TIMING: OFDM Timing error [5212+] + * @AR5K_RX_PHY_ERROR_OFDM_SIGNAL_PARITY: OFDM Signal parity error [5212+] + * @AR5K_RX_PHY_ERROR_OFDM_RATE_ILLEGAL: OFDM Illegal rate [5212+] + * @AR5K_RX_PHY_ERROR_OFDM_LENGTH_ILLEGAL: OFDM Illegal length [5212+] + * @AR5K_RX_PHY_ERROR_OFDM_POWER_DROP: OFDM Power drop [5212+] + * @AR5K_RX_PHY_ERROR_OFDM_SERVICE: OFDM Service (?) [5212+] + * @AR5K_RX_PHY_ERROR_OFDM_RESTART: OFDM Restart (?) [5212+] + * @AR5K_RX_PHY_ERROR_CCK_TIMING: CCK Timing error [5212+] + * @AR5K_RX_PHY_ERROR_CCK_HEADER_CRC: Header CRC error [5212+] + * @AR5K_RX_PHY_ERROR_CCK_RATE_ILLEGAL: Illegal rate [5212+] + * @AR5K_RX_PHY_ERROR_CCK_SERVICE: CCK Service (?) [5212+] + * @AR5K_RX_PHY_ERROR_CCK_RESTART: CCK Restart (?) [5212+] + */ +enum ath5k_phy_error_code { + AR5K_RX_PHY_ERROR_UNDERRUN = 0, + AR5K_RX_PHY_ERROR_TIMING = 1, + AR5K_RX_PHY_ERROR_PARITY = 2, + AR5K_RX_PHY_ERROR_RATE = 3, + AR5K_RX_PHY_ERROR_LENGTH = 4, + AR5K_RX_PHY_ERROR_RADAR = 5, + AR5K_RX_PHY_ERROR_SERVICE = 6, + AR5K_RX_PHY_ERROR_TOR = 7, + AR5K_RX_PHY_ERROR_OFDM_TIMING = 17, + AR5K_RX_PHY_ERROR_OFDM_SIGNAL_PARITY = 18, + AR5K_RX_PHY_ERROR_OFDM_RATE_ILLEGAL = 19, + AR5K_RX_PHY_ERROR_OFDM_LENGTH_ILLEGAL = 20, + AR5K_RX_PHY_ERROR_OFDM_POWER_DROP = 21, + AR5K_RX_PHY_ERROR_OFDM_SERVICE = 22, + AR5K_RX_PHY_ERROR_OFDM_RESTART = 23, + AR5K_RX_PHY_ERROR_CCK_TIMING = 25, + AR5K_RX_PHY_ERROR_CCK_HEADER_CRC = 26, + AR5K_RX_PHY_ERROR_CCK_RATE_ILLEGAL = 27, + AR5K_RX_PHY_ERROR_CCK_SERVICE = 30, + AR5K_RX_PHY_ERROR_CCK_RESTART = 31, +}; + +/** + * struct ath5k_hw_2w_tx_ctl - 5210/5211 hardware 2-word TX control descriptor + * @tx_control_0: TX control word 0 + * @tx_control_1: TX control word 1 + */ +struct ath5k_hw_2w_tx_ctl { + u32 tx_control_0; + u32 tx_control_1; +} __packed __aligned(4); + +/* TX control word 0 fields/flags */ +#define AR5K_2W_TX_DESC_CTL0_FRAME_LEN 0x00000fff /* frame length */ +#define AR5K_2W_TX_DESC_CTL0_HEADER_LEN_5210 0x0003f000 /* [5210] header length */ +#define AR5K_2W_TX_DESC_CTL0_HEADER_LEN_5210_S 12 +#define AR5K_2W_TX_DESC_CTL0_XMIT_RATE 0x003c0000 /* tx rate */ +#define AR5K_2W_TX_DESC_CTL0_XMIT_RATE_S 18 +#define AR5K_2W_TX_DESC_CTL0_RTSENA 0x00400000 /* RTS/CTS enable */ +#define AR5K_2W_TX_DESC_CTL0_LONG_PACKET_5210 0x00800000 /* [5210] long packet */ +#define AR5K_2W_TX_DESC_CTL0_VEOL_5211 0x00800000 /* [5211] virtual end-of-list */ +#define AR5K_2W_TX_DESC_CTL0_CLRDMASK 0x01000000 /* clear destination mask */ +#define AR5K_2W_TX_DESC_CTL0_ANT_MODE_XMIT_5210 0x02000000 /* [5210] antenna selection */ +#define AR5K_2W_TX_DESC_CTL0_ANT_MODE_XMIT_5211 0x1e000000 /* [5211] antenna selection */ +#define AR5K_2W_TX_DESC_CTL0_ANT_MODE_XMIT \ + (ah->ah_version == AR5K_AR5210 ? \ + AR5K_2W_TX_DESC_CTL0_ANT_MODE_XMIT_5210 : \ + AR5K_2W_TX_DESC_CTL0_ANT_MODE_XMIT_5211) +#define AR5K_2W_TX_DESC_CTL0_ANT_MODE_XMIT_S 25 +#define AR5K_2W_TX_DESC_CTL0_FRAME_TYPE_5210 0x1c000000 /* [5210] frame type */ +#define AR5K_2W_TX_DESC_CTL0_FRAME_TYPE_5210_S 26 +#define AR5K_2W_TX_DESC_CTL0_INTREQ 0x20000000 /* TX interrupt request */ +#define AR5K_2W_TX_DESC_CTL0_ENCRYPT_KEY_VALID 0x40000000 /* key is valid */ + +/* TX control word 1 fields/flags */ +#define AR5K_2W_TX_DESC_CTL1_BUF_LEN 0x00000fff /* data buffer length */ +#define AR5K_2W_TX_DESC_CTL1_MORE 0x00001000 /* more desc for this frame */ +#define AR5K_2W_TX_DESC_CTL1_ENC_KEY_IDX_5210 0x0007e000 /* [5210] key table index */ +#define AR5K_2W_TX_DESC_CTL1_ENC_KEY_IDX_5211 0x000fe000 /* [5211] key table index */ +#define AR5K_2W_TX_DESC_CTL1_ENC_KEY_IDX \ + (ah->ah_version == AR5K_AR5210 ? \ + AR5K_2W_TX_DESC_CTL1_ENC_KEY_IDX_5210 : \ + AR5K_2W_TX_DESC_CTL1_ENC_KEY_IDX_5211) +#define AR5K_2W_TX_DESC_CTL1_ENC_KEY_IDX_S 13 +#define AR5K_2W_TX_DESC_CTL1_FRAME_TYPE_5211 0x00700000 /* [5211] frame type */ +#define AR5K_2W_TX_DESC_CTL1_FRAME_TYPE_5211_S 20 +#define AR5K_2W_TX_DESC_CTL1_NOACK_5211 0x00800000 /* [5211] no ACK */ +#define AR5K_2W_TX_DESC_CTL1_RTS_DURATION_5210 0xfff80000 /* [5210] lower 13 bit of duration */ + +/* Frame types */ +#define AR5K_AR5210_TX_DESC_FRAME_TYPE_NORMAL 0 +#define AR5K_AR5210_TX_DESC_FRAME_TYPE_ATIM 1 +#define AR5K_AR5210_TX_DESC_FRAME_TYPE_PSPOLL 2 +#define AR5K_AR5210_TX_DESC_FRAME_TYPE_NO_DELAY 3 +#define AR5K_AR5211_TX_DESC_FRAME_TYPE_BEACON 3 +#define AR5K_AR5210_TX_DESC_FRAME_TYPE_PIFS 4 +#define AR5K_AR5211_TX_DESC_FRAME_TYPE_PRESP 4 + +/** + * struct ath5k_hw_4w_tx_ctl - 5212 hardware 4-word TX control descriptor + * @tx_control_0: TX control word 0 + * @tx_control_1: TX control word 1 + * @tx_control_2: TX control word 2 + * @tx_control_3: TX control word 3 + */ +struct ath5k_hw_4w_tx_ctl { + u32 tx_control_0; + u32 tx_control_1; + u32 tx_control_2; + u32 tx_control_3; +} __packed __aligned(4); + +/* TX control word 0 fields/flags */ +#define AR5K_4W_TX_DESC_CTL0_FRAME_LEN 0x00000fff /* frame length */ +#define AR5K_4W_TX_DESC_CTL0_XMIT_POWER 0x003f0000 /* transmit power */ +#define AR5K_4W_TX_DESC_CTL0_XMIT_POWER_S 16 +#define AR5K_4W_TX_DESC_CTL0_RTSENA 0x00400000 /* RTS/CTS enable */ +#define AR5K_4W_TX_DESC_CTL0_VEOL 0x00800000 /* virtual end-of-list */ +#define AR5K_4W_TX_DESC_CTL0_CLRDMASK 0x01000000 /* clear destination mask */ +#define AR5K_4W_TX_DESC_CTL0_ANT_MODE_XMIT 0x1e000000 /* TX antenna selection */ +#define AR5K_4W_TX_DESC_CTL0_ANT_MODE_XMIT_S 25 +#define AR5K_4W_TX_DESC_CTL0_INTREQ 0x20000000 /* TX interrupt request */ +#define AR5K_4W_TX_DESC_CTL0_ENCRYPT_KEY_VALID 0x40000000 /* destination index valid */ +#define AR5K_4W_TX_DESC_CTL0_CTSENA 0x80000000 /* precede frame with CTS */ + +/* TX control word 1 fields/flags */ +#define AR5K_4W_TX_DESC_CTL1_BUF_LEN 0x00000fff /* data buffer length */ +#define AR5K_4W_TX_DESC_CTL1_MORE 0x00001000 /* more desc for this frame */ +#define AR5K_4W_TX_DESC_CTL1_ENCRYPT_KEY_IDX 0x000fe000 /* destination table index */ +#define AR5K_4W_TX_DESC_CTL1_ENCRYPT_KEY_IDX_S 13 +#define AR5K_4W_TX_DESC_CTL1_FRAME_TYPE 0x00f00000 /* frame type */ +#define AR5K_4W_TX_DESC_CTL1_FRAME_TYPE_S 20 +#define AR5K_4W_TX_DESC_CTL1_NOACK 0x01000000 /* no ACK */ +#define AR5K_4W_TX_DESC_CTL1_COMP_PROC 0x06000000 /* compression processing */ +#define AR5K_4W_TX_DESC_CTL1_COMP_PROC_S 25 +#define AR5K_4W_TX_DESC_CTL1_COMP_IV_LEN 0x18000000 /* length of frame IV */ +#define AR5K_4W_TX_DESC_CTL1_COMP_IV_LEN_S 27 +#define AR5K_4W_TX_DESC_CTL1_COMP_ICV_LEN 0x60000000 /* length of frame ICV */ +#define AR5K_4W_TX_DESC_CTL1_COMP_ICV_LEN_S 29 + +/* TX control word 2 fields/flags */ +#define AR5K_4W_TX_DESC_CTL2_RTS_DURATION 0x00007fff /* RTS/CTS duration */ +#define AR5K_4W_TX_DESC_CTL2_DURATION_UPD_EN 0x00008000 /* frame duration update */ +#define AR5K_4W_TX_DESC_CTL2_XMIT_TRIES0 0x000f0000 /* series 0 max attempts */ +#define AR5K_4W_TX_DESC_CTL2_XMIT_TRIES0_S 16 +#define AR5K_4W_TX_DESC_CTL2_XMIT_TRIES1 0x00f00000 /* series 1 max attempts */ +#define AR5K_4W_TX_DESC_CTL2_XMIT_TRIES1_S 20 +#define AR5K_4W_TX_DESC_CTL2_XMIT_TRIES2 0x0f000000 /* series 2 max attempts */ +#define AR5K_4W_TX_DESC_CTL2_XMIT_TRIES2_S 24 +#define AR5K_4W_TX_DESC_CTL2_XMIT_TRIES3 0xf0000000 /* series 3 max attempts */ +#define AR5K_4W_TX_DESC_CTL2_XMIT_TRIES3_S 28 + +/* TX control word 3 fields/flags */ +#define AR5K_4W_TX_DESC_CTL3_XMIT_RATE0 0x0000001f /* series 0 tx rate */ +#define AR5K_4W_TX_DESC_CTL3_XMIT_RATE1 0x000003e0 /* series 1 tx rate */ +#define AR5K_4W_TX_DESC_CTL3_XMIT_RATE1_S 5 +#define AR5K_4W_TX_DESC_CTL3_XMIT_RATE2 0x00007c00 /* series 2 tx rate */ +#define AR5K_4W_TX_DESC_CTL3_XMIT_RATE2_S 10 +#define AR5K_4W_TX_DESC_CTL3_XMIT_RATE3 0x000f8000 /* series 3 tx rate */ +#define AR5K_4W_TX_DESC_CTL3_XMIT_RATE3_S 15 +#define AR5K_4W_TX_DESC_CTL3_RTS_CTS_RATE 0x01f00000 /* RTS or CTS rate */ +#define AR5K_4W_TX_DESC_CTL3_RTS_CTS_RATE_S 20 + +/** + * struct ath5k_hw_tx_status - Common TX status descriptor + * @tx_status_0: TX status word 0 + * @tx_status_1: TX status word 1 + */ +struct ath5k_hw_tx_status { + u32 tx_status_0; + u32 tx_status_1; +} __packed __aligned(4); + +/* TX status word 0 fields/flags */ +#define AR5K_DESC_TX_STATUS0_FRAME_XMIT_OK 0x00000001 /* TX success */ +#define AR5K_DESC_TX_STATUS0_EXCESSIVE_RETRIES 0x00000002 /* excessive retries */ +#define AR5K_DESC_TX_STATUS0_FIFO_UNDERRUN 0x00000004 /* FIFO underrun */ +#define AR5K_DESC_TX_STATUS0_FILTERED 0x00000008 /* TX filter indication */ +/* according to the HAL sources the spec has short/long retry counts reversed. + * we have it reversed to the HAL sources as well, for 5210 and 5211. + * For 5212 these fields are defined as RTS_FAIL_COUNT and DATA_FAIL_COUNT, + * but used respectively as SHORT and LONG retry count in the code later. This + * is consistent with the definitions here... TODO: check */ +#define AR5K_DESC_TX_STATUS0_SHORT_RETRY_COUNT 0x000000f0 /* short retry count */ +#define AR5K_DESC_TX_STATUS0_SHORT_RETRY_COUNT_S 4 +#define AR5K_DESC_TX_STATUS0_LONG_RETRY_COUNT 0x00000f00 /* long retry count */ +#define AR5K_DESC_TX_STATUS0_LONG_RETRY_COUNT_S 8 +#define AR5K_DESC_TX_STATUS0_VIRTCOLL_CT_5211 0x0000f000 /* [5211+] virtual collision count */ +#define AR5K_DESC_TX_STATUS0_VIRTCOLL_CT_5212_S 12 +#define AR5K_DESC_TX_STATUS0_SEND_TIMESTAMP 0xffff0000 /* TX timestamp */ +#define AR5K_DESC_TX_STATUS0_SEND_TIMESTAMP_S 16 + +/* TX status word 1 fields/flags */ +#define AR5K_DESC_TX_STATUS1_DONE 0x00000001 /* descriptor complete */ +#define AR5K_DESC_TX_STATUS1_SEQ_NUM 0x00001ffe /* TX sequence number */ +#define AR5K_DESC_TX_STATUS1_SEQ_NUM_S 1 +#define AR5K_DESC_TX_STATUS1_ACK_SIG_STRENGTH 0x001fe000 /* signal strength of ACK */ +#define AR5K_DESC_TX_STATUS1_ACK_SIG_STRENGTH_S 13 +#define AR5K_DESC_TX_STATUS1_FINAL_TS_IX_5212 0x00600000 /* [5212] final TX attempt series ix */ +#define AR5K_DESC_TX_STATUS1_FINAL_TS_IX_5212_S 21 +#define AR5K_DESC_TX_STATUS1_COMP_SUCCESS_5212 0x00800000 /* [5212] compression status */ +#define AR5K_DESC_TX_STATUS1_XMIT_ANTENNA_5212 0x01000000 /* [5212] transmit antenna */ + +/** + * struct ath5k_hw_5210_tx_desc - 5210/5211 hardware TX descriptor + * @tx_ctl: The &struct ath5k_hw_2w_tx_ctl + * @tx_stat: The &struct ath5k_hw_tx_status + */ +struct ath5k_hw_5210_tx_desc { + struct ath5k_hw_2w_tx_ctl tx_ctl; + struct ath5k_hw_tx_status tx_stat; +} __packed __aligned(4); + +/** + * struct ath5k_hw_5212_tx_desc - 5212 hardware TX descriptor + * @tx_ctl: The &struct ath5k_hw_4w_tx_ctl + * @tx_stat: The &struct ath5k_hw_tx_status + */ +struct ath5k_hw_5212_tx_desc { + struct ath5k_hw_4w_tx_ctl tx_ctl; + struct ath5k_hw_tx_status tx_stat; +} __packed __aligned(4); + +/** + * struct ath5k_hw_all_rx_desc - Common hardware RX descriptor + * @rx_ctl: The &struct ath5k_hw_rx_ctl + * @rx_stat: The &struct ath5k_hw_rx_status + */ +struct ath5k_hw_all_rx_desc { + struct ath5k_hw_rx_ctl rx_ctl; + struct ath5k_hw_rx_status rx_stat; +} __packed __aligned(4); + +/** + * struct ath5k_desc - Atheros hardware DMA descriptor + * @ds_link: Physical address of the next descriptor + * @ds_data: Physical address of data buffer (skb) + * @ud: Union containing hw_5xxx_tx_desc structs and hw_all_rx_desc + * + * This is read and written to by the hardware + */ +struct ath5k_desc { + u32 ds_link; + u32 ds_data; + + union { + struct ath5k_hw_5210_tx_desc ds_tx5210; + struct ath5k_hw_5212_tx_desc ds_tx5212; + struct ath5k_hw_all_rx_desc ds_rx; + } ud; +} __packed __aligned(4); + +#define AR5K_RXDESC_INTREQ 0x0020 + +#define AR5K_TXDESC_CLRDMASK 0x0001 +#define AR5K_TXDESC_NOACK 0x0002 /*[5211+]*/ +#define AR5K_TXDESC_RTSENA 0x0004 +#define AR5K_TXDESC_CTSENA 0x0008 +#define AR5K_TXDESC_INTREQ 0x0010 +#define AR5K_TXDESC_VEOL 0x0020 /*[5211+]*/ diff --git a/drivers/net/wireless/ath/ath5k/dma.c b/drivers/net/wireless/ath/ath5k/dma.c new file mode 100644 index 0000000..ce86f15 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/dma.c @@ -0,0 +1,919 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter + * Copyright (c) 2006-2008 Nick Kossifidis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/*************************************\ +* DMA and interrupt masking functions * +\*************************************/ + +/** + * DOC: DMA and interrupt masking functions + * + * Here we setup descriptor pointers (rxdp/txdp) start/stop dma engine and + * handle queue setup for 5210 chipset (rest are handled on qcu.c). + * Also we setup interrupt mask register (IMR) and read the various interrupt + * status registers (ISR). + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "ath5k.h" +#include "reg.h" +#include "debug.h" + + +/*********\ +* Receive * +\*********/ + +/** + * ath5k_hw_start_rx_dma() - Start DMA receive + * @ah: The &struct ath5k_hw + */ +void +ath5k_hw_start_rx_dma(struct ath5k_hw *ah) +{ + ath5k_hw_reg_write(ah, AR5K_CR_RXE, AR5K_CR); + ath5k_hw_reg_read(ah, AR5K_CR); +} + +/** + * ath5k_hw_stop_rx_dma() - Stop DMA receive + * @ah: The &struct ath5k_hw + */ +static int +ath5k_hw_stop_rx_dma(struct ath5k_hw *ah) +{ + unsigned int i; + + ath5k_hw_reg_write(ah, AR5K_CR_RXD, AR5K_CR); + + /* + * It may take some time to disable the DMA receive unit + */ + for (i = 1000; i > 0 && + (ath5k_hw_reg_read(ah, AR5K_CR) & AR5K_CR_RXE) != 0; + i--) + udelay(100); + + if (!i) + ATH5K_DBG(ah, ATH5K_DEBUG_DMA, + "failed to stop RX DMA !\n"); + + return i ? 0 : -EBUSY; +} + +/** + * ath5k_hw_get_rxdp() - Get RX Descriptor's address + * @ah: The &struct ath5k_hw + */ +u32 +ath5k_hw_get_rxdp(struct ath5k_hw *ah) +{ + return ath5k_hw_reg_read(ah, AR5K_RXDP); +} + +/** + * ath5k_hw_set_rxdp() - Set RX Descriptor's address + * @ah: The &struct ath5k_hw + * @phys_addr: RX descriptor address + * + * Returns -EIO if rx is active + */ +int +ath5k_hw_set_rxdp(struct ath5k_hw *ah, u32 phys_addr) +{ + if (ath5k_hw_reg_read(ah, AR5K_CR) & AR5K_CR_RXE) { + ATH5K_DBG(ah, ATH5K_DEBUG_DMA, + "tried to set RXDP while rx was active !\n"); + return -EIO; + } + + ath5k_hw_reg_write(ah, phys_addr, AR5K_RXDP); + return 0; +} + + +/**********\ +* Transmit * +\**********/ + +/** + * ath5k_hw_start_tx_dma() - Start DMA transmit for a specific queue + * @ah: The &struct ath5k_hw + * @queue: The hw queue number + * + * Start DMA transmit for a specific queue and since 5210 doesn't have + * QCU/DCU, set up queue parameters for 5210 here based on queue type (one + * queue for normal data and one queue for beacons). For queue setup + * on newer chips check out qcu.c. Returns -EINVAL if queue number is out + * of range or if queue is already disabled. + * + * NOTE: Must be called after setting up tx control descriptor for that + * queue (see below). + */ +int +ath5k_hw_start_tx_dma(struct ath5k_hw *ah, unsigned int queue) +{ + u32 tx_queue; + + AR5K_ASSERT_ENTRY(queue, ah->ah_capabilities.cap_queues.q_tx_num); + + /* Return if queue is declared inactive */ + if (ah->ah_txq[queue].tqi_type == AR5K_TX_QUEUE_INACTIVE) + return -EINVAL; + + if (ah->ah_version == AR5K_AR5210) { + tx_queue = ath5k_hw_reg_read(ah, AR5K_CR); + + /* + * Set the queue by type on 5210 + */ + switch (ah->ah_txq[queue].tqi_type) { + case AR5K_TX_QUEUE_DATA: + tx_queue |= AR5K_CR_TXE0 & ~AR5K_CR_TXD0; + break; + case AR5K_TX_QUEUE_BEACON: + tx_queue |= AR5K_CR_TXE1 & ~AR5K_CR_TXD1; + ath5k_hw_reg_write(ah, AR5K_BCR_TQ1V | AR5K_BCR_BDMAE, + AR5K_BSR); + break; + case AR5K_TX_QUEUE_CAB: + tx_queue |= AR5K_CR_TXE1 & ~AR5K_CR_TXD1; + ath5k_hw_reg_write(ah, AR5K_BCR_TQ1FV | AR5K_BCR_TQ1V | + AR5K_BCR_BDMAE, AR5K_BSR); + break; + default: + return -EINVAL; + } + /* Start queue */ + ath5k_hw_reg_write(ah, tx_queue, AR5K_CR); + ath5k_hw_reg_read(ah, AR5K_CR); + } else { + /* Return if queue is disabled */ + if (AR5K_REG_READ_Q(ah, AR5K_QCU_TXD, queue)) + return -EIO; + + /* Start queue */ + AR5K_REG_WRITE_Q(ah, AR5K_QCU_TXE, queue); + } + + return 0; +} + +/** + * ath5k_hw_stop_tx_dma() - Stop DMA transmit on a specific queue + * @ah: The &struct ath5k_hw + * @queue: The hw queue number + * + * Stop DMA transmit on a specific hw queue and drain queue so we don't + * have any pending frames. Returns -EBUSY if we still have pending frames, + * -EINVAL if queue number is out of range or inactive. + */ +static int +ath5k_hw_stop_tx_dma(struct ath5k_hw *ah, unsigned int queue) +{ + unsigned int i = 40; + u32 tx_queue, pending; + + AR5K_ASSERT_ENTRY(queue, ah->ah_capabilities.cap_queues.q_tx_num); + + /* Return if queue is declared inactive */ + if (ah->ah_txq[queue].tqi_type == AR5K_TX_QUEUE_INACTIVE) + return -EINVAL; + + if (ah->ah_version == AR5K_AR5210) { + tx_queue = ath5k_hw_reg_read(ah, AR5K_CR); + + /* + * Set by queue type + */ + switch (ah->ah_txq[queue].tqi_type) { + case AR5K_TX_QUEUE_DATA: + tx_queue |= AR5K_CR_TXD0 & ~AR5K_CR_TXE0; + break; + case AR5K_TX_QUEUE_BEACON: + case AR5K_TX_QUEUE_CAB: + /* XXX Fix me... */ + tx_queue |= AR5K_CR_TXD1 & ~AR5K_CR_TXD1; + ath5k_hw_reg_write(ah, 0, AR5K_BSR); + break; + default: + return -EINVAL; + } + + /* Stop queue */ + ath5k_hw_reg_write(ah, tx_queue, AR5K_CR); + ath5k_hw_reg_read(ah, AR5K_CR); + } else { + + /* + * Enable DCU early termination to quickly + * flush any pending frames from QCU + */ + AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_MISC(queue), + AR5K_QCU_MISC_DCU_EARLY); + + /* + * Schedule TX disable and wait until queue is empty + */ + AR5K_REG_WRITE_Q(ah, AR5K_QCU_TXD, queue); + + /* Wait for queue to stop */ + for (i = 1000; i > 0 && + (AR5K_REG_READ_Q(ah, AR5K_QCU_TXE, queue) != 0); + i--) + udelay(100); + + if (AR5K_REG_READ_Q(ah, AR5K_QCU_TXE, queue)) + ATH5K_DBG(ah, ATH5K_DEBUG_DMA, + "queue %i didn't stop !\n", queue); + + /* Check for pending frames */ + i = 1000; + do { + pending = ath5k_hw_reg_read(ah, + AR5K_QUEUE_STATUS(queue)) & + AR5K_QCU_STS_FRMPENDCNT; + udelay(100); + } while (--i && pending); + + /* For 2413+ order PCU to drop packets using + * QUIET mechanism */ + if (ah->ah_mac_version >= (AR5K_SREV_AR2414 >> 4) && + pending) { + /* Set periodicity and duration */ + ath5k_hw_reg_write(ah, + AR5K_REG_SM(100, AR5K_QUIET_CTL2_QT_PER)| + AR5K_REG_SM(10, AR5K_QUIET_CTL2_QT_DUR), + AR5K_QUIET_CTL2); + + /* Enable quiet period for current TSF */ + ath5k_hw_reg_write(ah, + AR5K_QUIET_CTL1_QT_EN | + AR5K_REG_SM(ath5k_hw_reg_read(ah, + AR5K_TSF_L32_5211) >> 10, + AR5K_QUIET_CTL1_NEXT_QT_TSF), + AR5K_QUIET_CTL1); + + /* Force channel idle high */ + AR5K_REG_ENABLE_BITS(ah, AR5K_DIAG_SW_5211, + AR5K_DIAG_SW_CHANNEL_IDLE_HIGH); + + /* Wait a while and disable mechanism */ + udelay(400); + AR5K_REG_DISABLE_BITS(ah, AR5K_QUIET_CTL1, + AR5K_QUIET_CTL1_QT_EN); + + /* Re-check for pending frames */ + i = 100; + do { + pending = ath5k_hw_reg_read(ah, + AR5K_QUEUE_STATUS(queue)) & + AR5K_QCU_STS_FRMPENDCNT; + udelay(100); + } while (--i && pending); + + AR5K_REG_DISABLE_BITS(ah, AR5K_DIAG_SW_5211, + AR5K_DIAG_SW_CHANNEL_IDLE_HIGH); + + if (pending) + ATH5K_DBG(ah, ATH5K_DEBUG_DMA, + "quiet mechanism didn't work q:%i !\n", + queue); + } + + /* + * Disable DCU early termination + */ + AR5K_REG_DISABLE_BITS(ah, AR5K_QUEUE_MISC(queue), + AR5K_QCU_MISC_DCU_EARLY); + + /* Clear register */ + ath5k_hw_reg_write(ah, 0, AR5K_QCU_TXD); + if (pending) { + ATH5K_DBG(ah, ATH5K_DEBUG_DMA, + "tx dma didn't stop (q:%i, frm:%i) !\n", + queue, pending); + return -EBUSY; + } + } + + /* TODO: Check for success on 5210 else return error */ + return 0; +} + +/** + * ath5k_hw_stop_beacon_queue() - Stop beacon queue + * @ah: The &struct ath5k_hw + * @queue: The queue number + * + * Returns -EIO if queue didn't stop + */ +int +ath5k_hw_stop_beacon_queue(struct ath5k_hw *ah, unsigned int queue) +{ + int ret; + ret = ath5k_hw_stop_tx_dma(ah, queue); + if (ret) { + ATH5K_DBG(ah, ATH5K_DEBUG_DMA, + "beacon queue didn't stop !\n"); + return -EIO; + } + return 0; +} + +/** + * ath5k_hw_get_txdp() - Get TX Descriptor's address for a specific queue + * @ah: The &struct ath5k_hw + * @queue: The hw queue number + * + * Get TX descriptor's address for a specific queue. For 5210 we ignore + * the queue number and use tx queue type since we only have 2 queues. + * We use TXDP0 for normal data queue and TXDP1 for beacon queue. + * For newer chips with QCU/DCU we just read the corresponding TXDP register. + * + * XXX: Is TXDP read and clear ? + */ +u32 +ath5k_hw_get_txdp(struct ath5k_hw *ah, unsigned int queue) +{ + u16 tx_reg; + + AR5K_ASSERT_ENTRY(queue, ah->ah_capabilities.cap_queues.q_tx_num); + + /* + * Get the transmit queue descriptor pointer from the selected queue + */ + /*5210 doesn't have QCU*/ + if (ah->ah_version == AR5K_AR5210) { + switch (ah->ah_txq[queue].tqi_type) { + case AR5K_TX_QUEUE_DATA: + tx_reg = AR5K_NOQCU_TXDP0; + break; + case AR5K_TX_QUEUE_BEACON: + case AR5K_TX_QUEUE_CAB: + tx_reg = AR5K_NOQCU_TXDP1; + break; + default: + return 0xffffffff; + } + } else { + tx_reg = AR5K_QUEUE_TXDP(queue); + } + + return ath5k_hw_reg_read(ah, tx_reg); +} + +/** + * ath5k_hw_set_txdp() - Set TX Descriptor's address for a specific queue + * @ah: The &struct ath5k_hw + * @queue: The hw queue number + * @phys_addr: The physical address + * + * Set TX descriptor's address for a specific queue. For 5210 we ignore + * the queue number and we use tx queue type since we only have 2 queues + * so as above we use TXDP0 for normal data queue and TXDP1 for beacon queue. + * For newer chips with QCU/DCU we just set the corresponding TXDP register. + * Returns -EINVAL if queue type is invalid for 5210 and -EIO if queue is still + * active. + */ +int +ath5k_hw_set_txdp(struct ath5k_hw *ah, unsigned int queue, u32 phys_addr) +{ + u16 tx_reg; + + AR5K_ASSERT_ENTRY(queue, ah->ah_capabilities.cap_queues.q_tx_num); + + /* + * Set the transmit queue descriptor pointer register by type + * on 5210 + */ + if (ah->ah_version == AR5K_AR5210) { + switch (ah->ah_txq[queue].tqi_type) { + case AR5K_TX_QUEUE_DATA: + tx_reg = AR5K_NOQCU_TXDP0; + break; + case AR5K_TX_QUEUE_BEACON: + case AR5K_TX_QUEUE_CAB: + tx_reg = AR5K_NOQCU_TXDP1; + break; + default: + return -EINVAL; + } + } else { + /* + * Set the transmit queue descriptor pointer for + * the selected queue on QCU for 5211+ + * (this won't work if the queue is still active) + */ + if (AR5K_REG_READ_Q(ah, AR5K_QCU_TXE, queue)) + return -EIO; + + tx_reg = AR5K_QUEUE_TXDP(queue); + } + + /* Set descriptor pointer */ + ath5k_hw_reg_write(ah, phys_addr, tx_reg); + + return 0; +} + +/** + * ath5k_hw_update_tx_triglevel() - Update tx trigger level + * @ah: The &struct ath5k_hw + * @increase: Flag to force increase of trigger level + * + * This function increases/decreases the tx trigger level for the tx fifo + * buffer (aka FIFO threshold) that is used to indicate when PCU flushes + * the buffer and transmits its data. Lowering this results sending small + * frames more quickly but can lead to tx underruns, raising it a lot can + * result other problems. Right now we start with the lowest possible + * (64Bytes) and if we get tx underrun we increase it using the increase + * flag. Returns -EIO if we have reached maximum/minimum. + * + * XXX: Link this with tx DMA size ? + * XXX2: Use it to save interrupts ? + */ +int +ath5k_hw_update_tx_triglevel(struct ath5k_hw *ah, bool increase) +{ + u32 trigger_level, imr; + int ret = -EIO; + + /* + * Disable interrupts by setting the mask + */ + imr = ath5k_hw_set_imr(ah, ah->ah_imr & ~AR5K_INT_GLOBAL); + + trigger_level = AR5K_REG_MS(ath5k_hw_reg_read(ah, AR5K_TXCFG), + AR5K_TXCFG_TXFULL); + + if (!increase) { + if (--trigger_level < AR5K_TUNE_MIN_TX_FIFO_THRES) + goto done; + } else + trigger_level += + ((AR5K_TUNE_MAX_TX_FIFO_THRES - trigger_level) / 2); + + /* + * Update trigger level on success + */ + if (ah->ah_version == AR5K_AR5210) + ath5k_hw_reg_write(ah, trigger_level, AR5K_TRIG_LVL); + else + AR5K_REG_WRITE_BITS(ah, AR5K_TXCFG, + AR5K_TXCFG_TXFULL, trigger_level); + + ret = 0; + +done: + /* + * Restore interrupt mask + */ + ath5k_hw_set_imr(ah, imr); + + return ret; +} + + +/*******************\ +* Interrupt masking * +\*******************/ + +/** + * ath5k_hw_is_intr_pending() - Check if we have pending interrupts + * @ah: The &struct ath5k_hw + * + * Check if we have pending interrupts to process. Returns 1 if we + * have pending interrupts and 0 if we haven't. + */ +bool +ath5k_hw_is_intr_pending(struct ath5k_hw *ah) +{ + return ath5k_hw_reg_read(ah, AR5K_INTPEND) == 1 ? 1 : 0; +} + +/** + * ath5k_hw_get_isr() - Get interrupt status + * @ah: The @struct ath5k_hw + * @interrupt_mask: Driver's interrupt mask used to filter out + * interrupts in sw. + * + * This function is used inside our interrupt handler to determine the reason + * for the interrupt by reading Primary Interrupt Status Register. Returns an + * abstract interrupt status mask which is mostly ISR with some uncommon bits + * being mapped on some standard non hw-specific positions + * (check out &ath5k_int). + * + * NOTE: We do write-to-clear, so the active PISR/SISR bits at the time this + * function gets called are cleared on return. + */ +int +ath5k_hw_get_isr(struct ath5k_hw *ah, enum ath5k_int *interrupt_mask) +{ + u32 data = 0; + + /* + * Read interrupt status from Primary Interrupt + * Register. + * + * Note: PISR/SISR Not available on 5210 + */ + if (ah->ah_version == AR5K_AR5210) { + u32 isr = 0; + isr = ath5k_hw_reg_read(ah, AR5K_ISR); + if (unlikely(isr == AR5K_INT_NOCARD)) { + *interrupt_mask = isr; + return -ENODEV; + } + + /* + * Filter out the non-common bits from the interrupt + * status. + */ + *interrupt_mask = (isr & AR5K_INT_COMMON) & ah->ah_imr; + + /* Hanlde INT_FATAL */ + if (unlikely(isr & (AR5K_ISR_SSERR | AR5K_ISR_MCABT + | AR5K_ISR_DPERR))) + *interrupt_mask |= AR5K_INT_FATAL; + + /* + * XXX: BMISS interrupts may occur after association. + * I found this on 5210 code but it needs testing. If this is + * true we should disable them before assoc and re-enable them + * after a successful assoc + some jiffies. + interrupt_mask &= ~AR5K_INT_BMISS; + */ + + data = isr; + } else { + u32 pisr = 0; + u32 pisr_clear = 0; + u32 sisr0 = 0; + u32 sisr1 = 0; + u32 sisr2 = 0; + u32 sisr3 = 0; + u32 sisr4 = 0; + + /* Read PISR and SISRs... */ + pisr = ath5k_hw_reg_read(ah, AR5K_PISR); + if (unlikely(pisr == AR5K_INT_NOCARD)) { + *interrupt_mask = pisr; + return -ENODEV; + } + + sisr0 = ath5k_hw_reg_read(ah, AR5K_SISR0); + sisr1 = ath5k_hw_reg_read(ah, AR5K_SISR1); + sisr2 = ath5k_hw_reg_read(ah, AR5K_SISR2); + sisr3 = ath5k_hw_reg_read(ah, AR5K_SISR3); + sisr4 = ath5k_hw_reg_read(ah, AR5K_SISR4); + + /* + * PISR holds the logical OR of interrupt bits + * from SISR registers: + * + * TXOK and TXDESC -> Logical OR of TXOK and TXDESC + * per-queue bits on SISR0 + * + * TXERR and TXEOL -> Logical OR of TXERR and TXEOL + * per-queue bits on SISR1 + * + * TXURN -> Logical OR of TXURN per-queue bits on SISR2 + * + * HIUERR -> Logical OR of MCABT, SSERR and DPER bits on SISR2 + * + * BCNMISC -> Logical OR of TIM, CAB_END, DTIM_SYNC + * BCN_TIMEOUT, CAB_TIMEOUT and DTIM + * (and TSFOOR ?) bits on SISR2 + * + * QCBRORN and QCBRURN -> Logical OR of QCBRORN and + * QCBRURN per-queue bits on SISR3 + * QTRIG -> Logical OR of QTRIG per-queue bits on SISR4 + * + * If we clean these bits on PISR we 'll also clear all + * related bits from SISRs, e.g. if we write the TXOK bit on + * PISR we 'll clean all TXOK bits from SISR0 so if a new TXOK + * interrupt got fired for another queue while we were reading + * the interrupt registers and we write back the TXOK bit on + * PISR we 'll lose it. So make sure that we don't write back + * on PISR any bits that come from SISRs. Clearing them from + * SISRs will also clear PISR so no need to worry here. + */ + + pisr_clear = pisr & ~AR5K_ISR_BITS_FROM_SISRS; + + /* + * Write to clear them... + * Note: This means that each bit we write back + * to the registers will get cleared, leaving the + * rest unaffected. So this won't affect new interrupts + * we didn't catch while reading/processing, we 'll get + * them next time get_isr gets called. + */ + ath5k_hw_reg_write(ah, sisr0, AR5K_SISR0); + ath5k_hw_reg_write(ah, sisr1, AR5K_SISR1); + ath5k_hw_reg_write(ah, sisr2, AR5K_SISR2); + ath5k_hw_reg_write(ah, sisr3, AR5K_SISR3); + ath5k_hw_reg_write(ah, sisr4, AR5K_SISR4); + ath5k_hw_reg_write(ah, pisr_clear, AR5K_PISR); + /* Flush previous write */ + ath5k_hw_reg_read(ah, AR5K_PISR); + + /* + * Filter out the non-common bits from the interrupt + * status. + */ + *interrupt_mask = (pisr & AR5K_INT_COMMON) & ah->ah_imr; + + + /* We treat TXOK,TXDESC, TXERR and TXEOL + * the same way (schedule the tx tasklet) + * so we track them all together per queue */ + if (pisr & AR5K_ISR_TXOK) + ah->ah_txq_isr_txok_all |= AR5K_REG_MS(sisr0, + AR5K_SISR0_QCU_TXOK); + + if (pisr & AR5K_ISR_TXDESC) + ah->ah_txq_isr_txok_all |= AR5K_REG_MS(sisr0, + AR5K_SISR0_QCU_TXDESC); + + if (pisr & AR5K_ISR_TXERR) + ah->ah_txq_isr_txok_all |= AR5K_REG_MS(sisr1, + AR5K_SISR1_QCU_TXERR); + + if (pisr & AR5K_ISR_TXEOL) + ah->ah_txq_isr_txok_all |= AR5K_REG_MS(sisr1, + AR5K_SISR1_QCU_TXEOL); + + /* Currently this is not much usefull since we treat + * all queues the same way if we get a TXURN (update + * tx trigger level) but we might need it later on*/ + if (pisr & AR5K_ISR_TXURN) + ah->ah_txq_isr_txurn |= AR5K_REG_MS(sisr2, + AR5K_SISR2_QCU_TXURN); + + /* Misc Beacon related interrupts */ + + /* For AR5211 */ + if (pisr & AR5K_ISR_TIM) + *interrupt_mask |= AR5K_INT_TIM; + + /* For AR5212+ */ + if (pisr & AR5K_ISR_BCNMISC) { + if (sisr2 & AR5K_SISR2_TIM) + *interrupt_mask |= AR5K_INT_TIM; + if (sisr2 & AR5K_SISR2_DTIM) + *interrupt_mask |= AR5K_INT_DTIM; + if (sisr2 & AR5K_SISR2_DTIM_SYNC) + *interrupt_mask |= AR5K_INT_DTIM_SYNC; + if (sisr2 & AR5K_SISR2_BCN_TIMEOUT) + *interrupt_mask |= AR5K_INT_BCN_TIMEOUT; + if (sisr2 & AR5K_SISR2_CAB_TIMEOUT) + *interrupt_mask |= AR5K_INT_CAB_TIMEOUT; + } + + /* Below interrupts are unlikely to happen */ + + /* HIU = Host Interface Unit (PCI etc) + * Can be one of MCABT, SSERR, DPERR from SISR2 */ + if (unlikely(pisr & (AR5K_ISR_HIUERR))) + *interrupt_mask |= AR5K_INT_FATAL; + + /*Beacon Not Ready*/ + if (unlikely(pisr & (AR5K_ISR_BNR))) + *interrupt_mask |= AR5K_INT_BNR; + + /* A queue got CBR overrun */ + if (unlikely(pisr & (AR5K_ISR_QCBRORN))) { + *interrupt_mask |= AR5K_INT_QCBRORN; + ah->ah_txq_isr_qcborn |= AR5K_REG_MS(sisr3, + AR5K_SISR3_QCBRORN); + } + + /* A queue got CBR underrun */ + if (unlikely(pisr & (AR5K_ISR_QCBRURN))) { + *interrupt_mask |= AR5K_INT_QCBRURN; + ah->ah_txq_isr_qcburn |= AR5K_REG_MS(sisr3, + AR5K_SISR3_QCBRURN); + } + + /* A queue got triggered */ + if (unlikely(pisr & (AR5K_ISR_QTRIG))) { + *interrupt_mask |= AR5K_INT_QTRIG; + ah->ah_txq_isr_qtrig |= AR5K_REG_MS(sisr4, + AR5K_SISR4_QTRIG); + } + + data = pisr; + } + + /* + * In case we didn't handle anything, + * print the register value. + */ + if (unlikely(*interrupt_mask == 0 && net_ratelimit())) + ATH5K_PRINTF("ISR: 0x%08x IMR: 0x%08x\n", data, ah->ah_imr); + + return 0; +} + +/** + * ath5k_hw_set_imr() - Set interrupt mask + * @ah: The &struct ath5k_hw + * @new_mask: The new interrupt mask to be set + * + * Set the interrupt mask in hw to save interrupts. We do that by mapping + * ath5k_int bits to hw-specific bits to remove abstraction and writing + * Interrupt Mask Register. + */ +enum ath5k_int +ath5k_hw_set_imr(struct ath5k_hw *ah, enum ath5k_int new_mask) +{ + enum ath5k_int old_mask, int_mask; + + old_mask = ah->ah_imr; + + /* + * Disable card interrupts to prevent any race conditions + * (they will be re-enabled afterwards if AR5K_INT GLOBAL + * is set again on the new mask). + */ + if (old_mask & AR5K_INT_GLOBAL) { + ath5k_hw_reg_write(ah, AR5K_IER_DISABLE, AR5K_IER); + ath5k_hw_reg_read(ah, AR5K_IER); + } + + /* + * Add additional, chipset-dependent interrupt mask flags + * and write them to the IMR (interrupt mask register). + */ + int_mask = new_mask & AR5K_INT_COMMON; + + if (ah->ah_version != AR5K_AR5210) { + /* Preserve per queue TXURN interrupt mask */ + u32 simr2 = ath5k_hw_reg_read(ah, AR5K_SIMR2) + & AR5K_SIMR2_QCU_TXURN; + + /* Fatal interrupt abstraction for 5211+ */ + if (new_mask & AR5K_INT_FATAL) { + int_mask |= AR5K_IMR_HIUERR; + simr2 |= (AR5K_SIMR2_MCABT | AR5K_SIMR2_SSERR + | AR5K_SIMR2_DPERR); + } + + /* Misc beacon related interrupts */ + if (new_mask & AR5K_INT_TIM) + int_mask |= AR5K_IMR_TIM; + + if (new_mask & AR5K_INT_TIM) + simr2 |= AR5K_SISR2_TIM; + if (new_mask & AR5K_INT_DTIM) + simr2 |= AR5K_SISR2_DTIM; + if (new_mask & AR5K_INT_DTIM_SYNC) + simr2 |= AR5K_SISR2_DTIM_SYNC; + if (new_mask & AR5K_INT_BCN_TIMEOUT) + simr2 |= AR5K_SISR2_BCN_TIMEOUT; + if (new_mask & AR5K_INT_CAB_TIMEOUT) + simr2 |= AR5K_SISR2_CAB_TIMEOUT; + + /*Beacon Not Ready*/ + if (new_mask & AR5K_INT_BNR) + int_mask |= AR5K_INT_BNR; + + /* Note: Per queue interrupt masks + * are set via ath5k_hw_reset_tx_queue() (qcu.c) */ + ath5k_hw_reg_write(ah, int_mask, AR5K_PIMR); + ath5k_hw_reg_write(ah, simr2, AR5K_SIMR2); + + } else { + /* Fatal interrupt abstraction for 5210 */ + if (new_mask & AR5K_INT_FATAL) + int_mask |= (AR5K_IMR_SSERR | AR5K_IMR_MCABT + | AR5K_IMR_HIUERR | AR5K_IMR_DPERR); + + /* Only common interrupts left for 5210 (no SIMRs) */ + ath5k_hw_reg_write(ah, int_mask, AR5K_IMR); + } + + /* If RXNOFRM interrupt is masked disable it + * by setting AR5K_RXNOFRM to zero */ + if (!(new_mask & AR5K_INT_RXNOFRM)) + ath5k_hw_reg_write(ah, 0, AR5K_RXNOFRM); + + /* Store new interrupt mask */ + ah->ah_imr = new_mask; + + /* ..re-enable interrupts if AR5K_INT_GLOBAL is set */ + if (new_mask & AR5K_INT_GLOBAL) { + ath5k_hw_reg_write(ah, AR5K_IER_ENABLE, AR5K_IER); + ath5k_hw_reg_read(ah, AR5K_IER); + } + + return old_mask; +} + + +/********************\ + Init/Stop functions +\********************/ + +/** + * ath5k_hw_dma_init() - Initialize DMA unit + * @ah: The &struct ath5k_hw + * + * Set DMA size and pre-enable interrupts + * (driver handles tx/rx buffer setup and + * dma start/stop) + * + * XXX: Save/restore RXDP/TXDP registers ? + */ +void +ath5k_hw_dma_init(struct ath5k_hw *ah) +{ + /* + * Set Rx/Tx DMA Configuration + * + * Set standard DMA size (128). Note that + * a DMA size of 512 causes rx overruns and tx errors + * on pci-e cards (tested on 5424 but since rx overruns + * also occur on 5416/5418 with madwifi we set 128 + * for all PCI-E cards to be safe). + * + * XXX: need to check 5210 for this + * TODO: Check out tx trigger level, it's always 64 on dumps but I + * guess we can tweak it and see how it goes ;-) + */ + if (ah->ah_version != AR5K_AR5210) { + AR5K_REG_WRITE_BITS(ah, AR5K_TXCFG, + AR5K_TXCFG_SDMAMR, AR5K_DMASIZE_128B); + AR5K_REG_WRITE_BITS(ah, AR5K_RXCFG, + AR5K_RXCFG_SDMAMW, AR5K_DMASIZE_128B); + } + + /* Pre-enable interrupts on 5211/5212*/ + if (ah->ah_version != AR5K_AR5210) + ath5k_hw_set_imr(ah, ah->ah_imr); + +} + +/** + * ath5k_hw_dma_stop() - stop DMA unit + * @ah: The &struct ath5k_hw + * + * Stop tx/rx DMA and interrupts. Returns + * -EBUSY if tx or rx dma failed to stop. + * + * XXX: Sometimes DMA unit hangs and we have + * stuck frames on tx queues, only a reset + * can fix that. + */ +int +ath5k_hw_dma_stop(struct ath5k_hw *ah) +{ + int i, qmax, err; + err = 0; + + /* Disable interrupts */ + ath5k_hw_set_imr(ah, 0); + + /* Stop rx dma */ + err = ath5k_hw_stop_rx_dma(ah); + if (err) + return err; + + /* Clear any pending interrupts + * and disable tx dma */ + if (ah->ah_version != AR5K_AR5210) { + ath5k_hw_reg_write(ah, 0xffffffff, AR5K_PISR); + qmax = AR5K_NUM_TX_QUEUES; + } else { + /* PISR/SISR Not available on 5210 */ + ath5k_hw_reg_read(ah, AR5K_ISR); + qmax = AR5K_NUM_TX_QUEUES_NOQCU; + } + + for (i = 0; i < qmax; i++) { + err = ath5k_hw_stop_tx_dma(ah, i); + /* -EINVAL -> queue inactive */ + if (err && err != -EINVAL) + return err; + } + + return 0; +} diff --git a/drivers/net/wireless/ath/ath5k/eeprom.c b/drivers/net/wireless/ath/ath5k/eeprom.c new file mode 100644 index 0000000..94d34ee --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/eeprom.c @@ -0,0 +1,1796 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter + * Copyright (c) 2006-2009 Nick Kossifidis + * Copyright (c) 2008-2009 Felix Fietkau + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/*************************************\ +* EEPROM access functions and helpers * +\*************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include + +#include "ath5k.h" +#include "reg.h" +#include "debug.h" + + +/******************\ +* Helper functions * +\******************/ + +/* + * Translate binary channel representation in EEPROM to frequency + */ +static u16 ath5k_eeprom_bin2freq(struct ath5k_eeprom_info *ee, u16 bin, + unsigned int mode) +{ + u16 val; + + if (bin == AR5K_EEPROM_CHANNEL_DIS) + return bin; + + if (mode == AR5K_EEPROM_MODE_11A) { + if (ee->ee_version > AR5K_EEPROM_VERSION_3_2) + val = (5 * bin) + 4800; + else + val = bin > 62 ? (10 * 62) + (5 * (bin - 62)) + 5100 : + (bin * 10) + 5100; + } else { + if (ee->ee_version > AR5K_EEPROM_VERSION_3_2) + val = bin + 2300; + else + val = bin + 2400; + } + + return val; +} + + +/*********\ +* Parsers * +\*********/ + +/* + * Initialize eeprom & capabilities structs + */ +static int +ath5k_eeprom_init_header(struct ath5k_hw *ah) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u16 val; + u32 cksum, offset, eep_max = AR5K_EEPROM_INFO_MAX; + + /* + * Read values from EEPROM and store them in the capability structure + */ + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MAGIC, ee_magic); + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_PROTECT, ee_protect); + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_REG_DOMAIN, ee_regdomain); + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_VERSION, ee_version); + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_HDR, ee_header); + + /* Return if we have an old EEPROM */ + if (ah->ah_ee_version < AR5K_EEPROM_VERSION_3_0) + return 0; + + /* + * Validate the checksum of the EEPROM date. There are some + * devices with invalid EEPROMs. + */ + AR5K_EEPROM_READ(AR5K_EEPROM_SIZE_UPPER, val); + if (val) { + eep_max = (val & AR5K_EEPROM_SIZE_UPPER_MASK) << + AR5K_EEPROM_SIZE_ENDLOC_SHIFT; + AR5K_EEPROM_READ(AR5K_EEPROM_SIZE_LOWER, val); + eep_max = (eep_max | val) - AR5K_EEPROM_INFO_BASE; + + /* + * Fail safe check to prevent stupid loops due + * to busted EEPROMs. XXX: This value is likely too + * big still, waiting on a better value. + */ + if (eep_max > (3 * AR5K_EEPROM_INFO_MAX)) { + ATH5K_ERR(ah, "Invalid max custom EEPROM size: " + "%d (0x%04x) max expected: %d (0x%04x)\n", + eep_max, eep_max, + 3 * AR5K_EEPROM_INFO_MAX, + 3 * AR5K_EEPROM_INFO_MAX); + return -EIO; + } + } + + for (cksum = 0, offset = 0; offset < eep_max; offset++) { + AR5K_EEPROM_READ(AR5K_EEPROM_INFO(offset), val); + cksum ^= val; + } + if (cksum != AR5K_EEPROM_INFO_CKSUM) { + ATH5K_ERR(ah, "Invalid EEPROM " + "checksum: 0x%04x eep_max: 0x%04x (%s)\n", + cksum, eep_max, + eep_max == AR5K_EEPROM_INFO_MAX ? + "default size" : "custom size"); + return -EIO; + } + + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_ANT_GAIN(ah->ah_ee_version), + ee_ant_gain); + + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_0) { + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MISC0, ee_misc0); + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MISC1, ee_misc1); + + /* XXX: Don't know which versions include these two */ + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MISC2, ee_misc2); + + if (ee->ee_version >= AR5K_EEPROM_VERSION_4_3) + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MISC3, ee_misc3); + + if (ee->ee_version >= AR5K_EEPROM_VERSION_5_0) { + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MISC4, ee_misc4); + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MISC5, ee_misc5); + AR5K_EEPROM_READ_HDR(AR5K_EEPROM_MISC6, ee_misc6); + } + } + + if (ah->ah_ee_version < AR5K_EEPROM_VERSION_3_3) { + AR5K_EEPROM_READ(AR5K_EEPROM_OBDB0_2GHZ, val); + ee->ee_ob[AR5K_EEPROM_MODE_11B][0] = val & 0x7; + ee->ee_db[AR5K_EEPROM_MODE_11B][0] = (val >> 3) & 0x7; + + AR5K_EEPROM_READ(AR5K_EEPROM_OBDB1_2GHZ, val); + ee->ee_ob[AR5K_EEPROM_MODE_11G][0] = val & 0x7; + ee->ee_db[AR5K_EEPROM_MODE_11G][0] = (val >> 3) & 0x7; + } + + AR5K_EEPROM_READ(AR5K_EEPROM_IS_HB63, val); + + if ((ah->ah_mac_version == (AR5K_SREV_AR2425 >> 4)) && val) + ee->ee_is_hb63 = true; + else + ee->ee_is_hb63 = false; + + AR5K_EEPROM_READ(AR5K_EEPROM_RFKILL, val); + ee->ee_rfkill_pin = (u8) AR5K_REG_MS(val, AR5K_EEPROM_RFKILL_GPIO_SEL); + ee->ee_rfkill_pol = val & AR5K_EEPROM_RFKILL_POLARITY ? true : false; + + /* Check if PCIE_OFFSET points to PCIE_SERDES_SECTION + * and enable serdes programming if needed. + * + * XXX: Serdes values seem to be fixed so + * no need to read them here, we write them + * during ath5k_hw_init */ + AR5K_EEPROM_READ(AR5K_EEPROM_PCIE_OFFSET, val); + ee->ee_serdes = (val == AR5K_EEPROM_PCIE_SERDES_SECTION) ? + true : false; + + return 0; +} + + +/* + * Read antenna infos from eeprom + */ +static int ath5k_eeprom_read_ants(struct ath5k_hw *ah, u32 *offset, + unsigned int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u32 o = *offset; + u16 val; + int i = 0; + + AR5K_EEPROM_READ(o++, val); + ee->ee_switch_settling[mode] = (val >> 8) & 0x7f; + ee->ee_atn_tx_rx[mode] = (val >> 2) & 0x3f; + ee->ee_ant_control[mode][i] = (val << 4) & 0x3f; + + AR5K_EEPROM_READ(o++, val); + ee->ee_ant_control[mode][i++] |= (val >> 12) & 0xf; + ee->ee_ant_control[mode][i++] = (val >> 6) & 0x3f; + ee->ee_ant_control[mode][i++] = val & 0x3f; + + AR5K_EEPROM_READ(o++, val); + ee->ee_ant_control[mode][i++] = (val >> 10) & 0x3f; + ee->ee_ant_control[mode][i++] = (val >> 4) & 0x3f; + ee->ee_ant_control[mode][i] = (val << 2) & 0x3f; + + AR5K_EEPROM_READ(o++, val); + ee->ee_ant_control[mode][i++] |= (val >> 14) & 0x3; + ee->ee_ant_control[mode][i++] = (val >> 8) & 0x3f; + ee->ee_ant_control[mode][i++] = (val >> 2) & 0x3f; + ee->ee_ant_control[mode][i] = (val << 4) & 0x3f; + + AR5K_EEPROM_READ(o++, val); + ee->ee_ant_control[mode][i++] |= (val >> 12) & 0xf; + ee->ee_ant_control[mode][i++] = (val >> 6) & 0x3f; + ee->ee_ant_control[mode][i++] = val & 0x3f; + + /* Get antenna switch tables */ + ah->ah_ant_ctl[mode][AR5K_ANT_CTL] = + (ee->ee_ant_control[mode][0] << 4); + ah->ah_ant_ctl[mode][AR5K_ANT_SWTABLE_A] = + ee->ee_ant_control[mode][1] | + (ee->ee_ant_control[mode][2] << 6) | + (ee->ee_ant_control[mode][3] << 12) | + (ee->ee_ant_control[mode][4] << 18) | + (ee->ee_ant_control[mode][5] << 24); + ah->ah_ant_ctl[mode][AR5K_ANT_SWTABLE_B] = + ee->ee_ant_control[mode][6] | + (ee->ee_ant_control[mode][7] << 6) | + (ee->ee_ant_control[mode][8] << 12) | + (ee->ee_ant_control[mode][9] << 18) | + (ee->ee_ant_control[mode][10] << 24); + + /* return new offset */ + *offset = o; + + return 0; +} + +/* + * Read supported modes and some mode-specific calibration data + * from eeprom + */ +static int ath5k_eeprom_read_modes(struct ath5k_hw *ah, u32 *offset, + unsigned int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u32 o = *offset; + u16 val; + + ee->ee_n_piers[mode] = 0; + AR5K_EEPROM_READ(o++, val); + ee->ee_adc_desired_size[mode] = (s8)((val >> 8) & 0xff); + switch (mode) { + case AR5K_EEPROM_MODE_11A: + ee->ee_ob[mode][3] = (val >> 5) & 0x7; + ee->ee_db[mode][3] = (val >> 2) & 0x7; + ee->ee_ob[mode][2] = (val << 1) & 0x7; + + AR5K_EEPROM_READ(o++, val); + ee->ee_ob[mode][2] |= (val >> 15) & 0x1; + ee->ee_db[mode][2] = (val >> 12) & 0x7; + ee->ee_ob[mode][1] = (val >> 9) & 0x7; + ee->ee_db[mode][1] = (val >> 6) & 0x7; + ee->ee_ob[mode][0] = (val >> 3) & 0x7; + ee->ee_db[mode][0] = val & 0x7; + break; + case AR5K_EEPROM_MODE_11G: + case AR5K_EEPROM_MODE_11B: + ee->ee_ob[mode][1] = (val >> 4) & 0x7; + ee->ee_db[mode][1] = val & 0x7; + break; + } + + AR5K_EEPROM_READ(o++, val); + ee->ee_tx_end2xlna_enable[mode] = (val >> 8) & 0xff; + ee->ee_thr_62[mode] = val & 0xff; + + if (ah->ah_ee_version <= AR5K_EEPROM_VERSION_3_2) + ee->ee_thr_62[mode] = mode == AR5K_EEPROM_MODE_11A ? 15 : 28; + + AR5K_EEPROM_READ(o++, val); + ee->ee_tx_end2xpa_disable[mode] = (val >> 8) & 0xff; + ee->ee_tx_frm2xpa_enable[mode] = val & 0xff; + + AR5K_EEPROM_READ(o++, val); + ee->ee_pga_desired_size[mode] = (val >> 8) & 0xff; + + if ((val & 0xff) & 0x80) + ee->ee_noise_floor_thr[mode] = -((((val & 0xff) ^ 0xff)) + 1); + else + ee->ee_noise_floor_thr[mode] = val & 0xff; + + if (ah->ah_ee_version <= AR5K_EEPROM_VERSION_3_2) + ee->ee_noise_floor_thr[mode] = + mode == AR5K_EEPROM_MODE_11A ? -54 : -1; + + AR5K_EEPROM_READ(o++, val); + ee->ee_xlna_gain[mode] = (val >> 5) & 0xff; + ee->ee_x_gain[mode] = (val >> 1) & 0xf; + ee->ee_xpd[mode] = val & 0x1; + + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_0 && + mode != AR5K_EEPROM_MODE_11B) + ee->ee_fixed_bias[mode] = (val >> 13) & 0x1; + + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_3_3) { + AR5K_EEPROM_READ(o++, val); + ee->ee_false_detect[mode] = (val >> 6) & 0x7f; + + if (mode == AR5K_EEPROM_MODE_11A) + ee->ee_xr_power[mode] = val & 0x3f; + else { + /* b_DB_11[bg] and b_OB_11[bg] */ + ee->ee_ob[mode][0] = val & 0x7; + ee->ee_db[mode][0] = (val >> 3) & 0x7; + } + } + + if (ah->ah_ee_version < AR5K_EEPROM_VERSION_3_4) { + ee->ee_i_gain[mode] = AR5K_EEPROM_I_GAIN; + ee->ee_cck_ofdm_power_delta = AR5K_EEPROM_CCK_OFDM_DELTA; + } else { + ee->ee_i_gain[mode] = (val >> 13) & 0x7; + + AR5K_EEPROM_READ(o++, val); + ee->ee_i_gain[mode] |= (val << 3) & 0x38; + + if (mode == AR5K_EEPROM_MODE_11G) { + ee->ee_cck_ofdm_power_delta = (val >> 3) & 0xff; + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_6) + ee->ee_scaled_cck_delta = (val >> 11) & 0x1f; + } + } + + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_0 && + mode == AR5K_EEPROM_MODE_11A) { + ee->ee_i_cal[mode] = (val >> 8) & 0x3f; + ee->ee_q_cal[mode] = (val >> 3) & 0x1f; + } + + if (ah->ah_ee_version < AR5K_EEPROM_VERSION_4_0) + goto done; + + /* Note: >= v5 have bg freq piers on another location + * so these freq piers are ignored for >= v5 (should be 0xff + * anyway) */ + switch (mode) { + case AR5K_EEPROM_MODE_11A: + if (ah->ah_ee_version < AR5K_EEPROM_VERSION_4_1) + break; + + AR5K_EEPROM_READ(o++, val); + ee->ee_margin_tx_rx[mode] = val & 0x3f; + break; + case AR5K_EEPROM_MODE_11B: + AR5K_EEPROM_READ(o++, val); + + ee->ee_pwr_cal_b[0].freq = + ath5k_eeprom_bin2freq(ee, val & 0xff, mode); + if (ee->ee_pwr_cal_b[0].freq != AR5K_EEPROM_CHANNEL_DIS) + ee->ee_n_piers[mode]++; + + ee->ee_pwr_cal_b[1].freq = + ath5k_eeprom_bin2freq(ee, (val >> 8) & 0xff, mode); + if (ee->ee_pwr_cal_b[1].freq != AR5K_EEPROM_CHANNEL_DIS) + ee->ee_n_piers[mode]++; + + AR5K_EEPROM_READ(o++, val); + ee->ee_pwr_cal_b[2].freq = + ath5k_eeprom_bin2freq(ee, val & 0xff, mode); + if (ee->ee_pwr_cal_b[2].freq != AR5K_EEPROM_CHANNEL_DIS) + ee->ee_n_piers[mode]++; + + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_1) + ee->ee_margin_tx_rx[mode] = (val >> 8) & 0x3f; + break; + case AR5K_EEPROM_MODE_11G: + AR5K_EEPROM_READ(o++, val); + + ee->ee_pwr_cal_g[0].freq = + ath5k_eeprom_bin2freq(ee, val & 0xff, mode); + if (ee->ee_pwr_cal_g[0].freq != AR5K_EEPROM_CHANNEL_DIS) + ee->ee_n_piers[mode]++; + + ee->ee_pwr_cal_g[1].freq = + ath5k_eeprom_bin2freq(ee, (val >> 8) & 0xff, mode); + if (ee->ee_pwr_cal_g[1].freq != AR5K_EEPROM_CHANNEL_DIS) + ee->ee_n_piers[mode]++; + + AR5K_EEPROM_READ(o++, val); + ee->ee_turbo_max_power[mode] = val & 0x7f; + ee->ee_xr_power[mode] = (val >> 7) & 0x3f; + + AR5K_EEPROM_READ(o++, val); + ee->ee_pwr_cal_g[2].freq = + ath5k_eeprom_bin2freq(ee, val & 0xff, mode); + if (ee->ee_pwr_cal_g[2].freq != AR5K_EEPROM_CHANNEL_DIS) + ee->ee_n_piers[mode]++; + + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_1) + ee->ee_margin_tx_rx[mode] = (val >> 8) & 0x3f; + + AR5K_EEPROM_READ(o++, val); + ee->ee_i_cal[mode] = (val >> 5) & 0x3f; + ee->ee_q_cal[mode] = val & 0x1f; + + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_2) { + AR5K_EEPROM_READ(o++, val); + ee->ee_cck_ofdm_gain_delta = val & 0xff; + } + break; + } + + /* + * Read turbo mode information on newer EEPROM versions + */ + if (ee->ee_version < AR5K_EEPROM_VERSION_5_0) + goto done; + + switch (mode) { + case AR5K_EEPROM_MODE_11A: + ee->ee_switch_settling_turbo[mode] = (val >> 6) & 0x7f; + + ee->ee_atn_tx_rx_turbo[mode] = (val >> 13) & 0x7; + AR5K_EEPROM_READ(o++, val); + ee->ee_atn_tx_rx_turbo[mode] |= (val & 0x7) << 3; + ee->ee_margin_tx_rx_turbo[mode] = (val >> 3) & 0x3f; + + ee->ee_adc_desired_size_turbo[mode] = (val >> 9) & 0x7f; + AR5K_EEPROM_READ(o++, val); + ee->ee_adc_desired_size_turbo[mode] |= (val & 0x1) << 7; + ee->ee_pga_desired_size_turbo[mode] = (val >> 1) & 0xff; + + if (AR5K_EEPROM_EEMAP(ee->ee_misc0) >= 2) + ee->ee_pd_gain_overlap = (val >> 9) & 0xf; + break; + case AR5K_EEPROM_MODE_11G: + ee->ee_switch_settling_turbo[mode] = (val >> 8) & 0x7f; + + ee->ee_atn_tx_rx_turbo[mode] = (val >> 15) & 0x7; + AR5K_EEPROM_READ(o++, val); + ee->ee_atn_tx_rx_turbo[mode] |= (val & 0x1f) << 1; + ee->ee_margin_tx_rx_turbo[mode] = (val >> 5) & 0x3f; + + ee->ee_adc_desired_size_turbo[mode] = (val >> 11) & 0x7f; + AR5K_EEPROM_READ(o++, val); + ee->ee_adc_desired_size_turbo[mode] |= (val & 0x7) << 5; + ee->ee_pga_desired_size_turbo[mode] = (val >> 3) & 0xff; + break; + } + +done: + /* return new offset */ + *offset = o; + + return 0; +} + +/* Read mode-specific data (except power calibration data) */ +static int +ath5k_eeprom_init_modes(struct ath5k_hw *ah) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u32 mode_offset[3]; + unsigned int mode; + u32 offset; + int ret; + + /* + * Get values for all modes + */ + mode_offset[AR5K_EEPROM_MODE_11A] = AR5K_EEPROM_MODES_11A(ah->ah_ee_version); + mode_offset[AR5K_EEPROM_MODE_11B] = AR5K_EEPROM_MODES_11B(ah->ah_ee_version); + mode_offset[AR5K_EEPROM_MODE_11G] = AR5K_EEPROM_MODES_11G(ah->ah_ee_version); + + ee->ee_turbo_max_power[AR5K_EEPROM_MODE_11A] = + AR5K_EEPROM_HDR_T_5GHZ_DBM(ee->ee_header); + + for (mode = AR5K_EEPROM_MODE_11A; mode <= AR5K_EEPROM_MODE_11G; mode++) { + offset = mode_offset[mode]; + + ret = ath5k_eeprom_read_ants(ah, &offset, mode); + if (ret) + return ret; + + ret = ath5k_eeprom_read_modes(ah, &offset, mode); + if (ret) + return ret; + } + + /* override for older eeprom versions for better performance */ + if (ah->ah_ee_version <= AR5K_EEPROM_VERSION_3_2) { + ee->ee_thr_62[AR5K_EEPROM_MODE_11A] = 15; + ee->ee_thr_62[AR5K_EEPROM_MODE_11B] = 28; + ee->ee_thr_62[AR5K_EEPROM_MODE_11G] = 28; + } + + return 0; +} + +/* Read the frequency piers for each mode (mostly used on newer eeproms with 0xff + * frequency mask) */ +static inline int +ath5k_eeprom_read_freq_list(struct ath5k_hw *ah, int *offset, int max, + struct ath5k_chan_pcal_info *pc, unsigned int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + int o = *offset; + int i = 0; + u8 freq1, freq2; + u16 val; + + ee->ee_n_piers[mode] = 0; + while (i < max) { + AR5K_EEPROM_READ(o++, val); + + freq1 = val & 0xff; + if (!freq1) + break; + + pc[i++].freq = ath5k_eeprom_bin2freq(ee, + freq1, mode); + ee->ee_n_piers[mode]++; + + freq2 = (val >> 8) & 0xff; + if (!freq2) + break; + + pc[i++].freq = ath5k_eeprom_bin2freq(ee, + freq2, mode); + ee->ee_n_piers[mode]++; + } + + /* return new offset */ + *offset = o; + + return 0; +} + +/* Read frequency piers for 802.11a */ +static int +ath5k_eeprom_init_11a_pcal_freq(struct ath5k_hw *ah, int offset) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info *pcal = ee->ee_pwr_cal_a; + int i; + u16 val; + u8 mask; + + if (ee->ee_version >= AR5K_EEPROM_VERSION_3_3) { + ath5k_eeprom_read_freq_list(ah, &offset, + AR5K_EEPROM_N_5GHZ_CHAN, pcal, + AR5K_EEPROM_MODE_11A); + } else { + mask = AR5K_EEPROM_FREQ_M(ah->ah_ee_version); + + AR5K_EEPROM_READ(offset++, val); + pcal[0].freq = (val >> 9) & mask; + pcal[1].freq = (val >> 2) & mask; + pcal[2].freq = (val << 5) & mask; + + AR5K_EEPROM_READ(offset++, val); + pcal[2].freq |= (val >> 11) & 0x1f; + pcal[3].freq = (val >> 4) & mask; + pcal[4].freq = (val << 3) & mask; + + AR5K_EEPROM_READ(offset++, val); + pcal[4].freq |= (val >> 13) & 0x7; + pcal[5].freq = (val >> 6) & mask; + pcal[6].freq = (val << 1) & mask; + + AR5K_EEPROM_READ(offset++, val); + pcal[6].freq |= (val >> 15) & 0x1; + pcal[7].freq = (val >> 8) & mask; + pcal[8].freq = (val >> 1) & mask; + pcal[9].freq = (val << 6) & mask; + + AR5K_EEPROM_READ(offset++, val); + pcal[9].freq |= (val >> 10) & 0x3f; + + /* Fixed number of piers */ + ee->ee_n_piers[AR5K_EEPROM_MODE_11A] = 10; + + for (i = 0; i < AR5K_EEPROM_N_5GHZ_CHAN; i++) { + pcal[i].freq = ath5k_eeprom_bin2freq(ee, + pcal[i].freq, AR5K_EEPROM_MODE_11A); + } + } + + return 0; +} + +/* Read frequency piers for 802.11bg on eeprom versions >= 5 and eemap >= 2 */ +static inline int +ath5k_eeprom_init_11bg_2413(struct ath5k_hw *ah, unsigned int mode, int offset) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info *pcal; + + switch (mode) { + case AR5K_EEPROM_MODE_11B: + pcal = ee->ee_pwr_cal_b; + break; + case AR5K_EEPROM_MODE_11G: + pcal = ee->ee_pwr_cal_g; + break; + default: + return -EINVAL; + } + + ath5k_eeprom_read_freq_list(ah, &offset, + AR5K_EEPROM_N_2GHZ_CHAN_2413, pcal, + mode); + + return 0; +} + + +/* + * Read power calibration for RF5111 chips + * + * For RF5111 we have an XPD -eXternal Power Detector- curve + * for each calibrated channel. Each curve has 0,5dB Power steps + * on x axis and PCDAC steps (offsets) on y axis and looks like an + * exponential function. To recreate the curve we read 11 points + * here and interpolate later. + */ + +/* Used to match PCDAC steps with power values on RF5111 chips + * (eeprom versions < 4). For RF5111 we have 11 pre-defined PCDAC + * steps that match with the power values we read from eeprom. On + * older eeprom versions (< 3.2) these steps are equally spaced at + * 10% of the pcdac curve -until the curve reaches its maximum- + * (11 steps from 0 to 100%) but on newer eeprom versions (>= 3.2) + * these 11 steps are spaced in a different way. This function returns + * the pcdac steps based on eeprom version and curve min/max so that we + * can have pcdac/pwr points. + */ +static inline void +ath5k_get_pcdac_intercepts(struct ath5k_hw *ah, u8 min, u8 max, u8 *vp) +{ + static const u16 intercepts3[] = { + 0, 5, 10, 20, 30, 50, 70, 85, 90, 95, 100 + }; + static const u16 intercepts3_2[] = { + 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 + }; + const u16 *ip; + int i; + + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_3_2) + ip = intercepts3_2; + else + ip = intercepts3; + + for (i = 0; i < ARRAY_SIZE(intercepts3); i++) + vp[i] = (ip[i] * max + (100 - ip[i]) * min) / 100; +} + +static int +ath5k_eeprom_free_pcal_info(struct ath5k_hw *ah, int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info *chinfo; + u8 pier, pdg; + + switch (mode) { + case AR5K_EEPROM_MODE_11A: + if (!AR5K_EEPROM_HDR_11A(ee->ee_header)) + return 0; + chinfo = ee->ee_pwr_cal_a; + break; + case AR5K_EEPROM_MODE_11B: + if (!AR5K_EEPROM_HDR_11B(ee->ee_header)) + return 0; + chinfo = ee->ee_pwr_cal_b; + break; + case AR5K_EEPROM_MODE_11G: + if (!AR5K_EEPROM_HDR_11G(ee->ee_header)) + return 0; + chinfo = ee->ee_pwr_cal_g; + break; + default: + return -EINVAL; + } + + for (pier = 0; pier < ee->ee_n_piers[mode]; pier++) { + if (!chinfo[pier].pd_curves) + continue; + + for (pdg = 0; pdg < AR5K_EEPROM_N_PD_CURVES; pdg++) { + struct ath5k_pdgain_info *pd = + &chinfo[pier].pd_curves[pdg]; + + kfree(pd->pd_step); + kfree(pd->pd_pwr); + } + + kfree(chinfo[pier].pd_curves); + } + + return 0; +} + +/* Convert RF5111 specific data to generic raw data + * used by interpolation code */ +static int +ath5k_eeprom_convert_pcal_info_5111(struct ath5k_hw *ah, int mode, + struct ath5k_chan_pcal_info *chinfo) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info_rf5111 *pcinfo; + struct ath5k_pdgain_info *pd; + u8 pier, point, idx; + u8 *pdgain_idx = ee->ee_pdc_to_idx[mode]; + + /* Fill raw data for each calibration pier */ + for (pier = 0; pier < ee->ee_n_piers[mode]; pier++) { + + pcinfo = &chinfo[pier].rf5111_info; + + /* Allocate pd_curves for this cal pier */ + chinfo[pier].pd_curves = + kcalloc(AR5K_EEPROM_N_PD_CURVES, + sizeof(struct ath5k_pdgain_info), + GFP_KERNEL); + + if (!chinfo[pier].pd_curves) + goto err_out; + + /* Only one curve for RF5111 + * find out which one and place + * in pd_curves. + * Note: ee_x_gain is reversed here */ + for (idx = 0; idx < AR5K_EEPROM_N_PD_CURVES; idx++) { + + if (!((ee->ee_x_gain[mode] >> idx) & 0x1)) { + pdgain_idx[0] = idx; + break; + } + } + + ee->ee_pd_gains[mode] = 1; + + pd = &chinfo[pier].pd_curves[idx]; + + pd->pd_points = AR5K_EEPROM_N_PWR_POINTS_5111; + + /* Allocate pd points for this curve */ + pd->pd_step = kcalloc(AR5K_EEPROM_N_PWR_POINTS_5111, + sizeof(u8), GFP_KERNEL); + if (!pd->pd_step) + goto err_out; + + pd->pd_pwr = kcalloc(AR5K_EEPROM_N_PWR_POINTS_5111, + sizeof(s16), GFP_KERNEL); + if (!pd->pd_pwr) + goto err_out; + + /* Fill raw dataset + * (convert power to 0.25dB units + * for RF5112 compatibility) */ + for (point = 0; point < pd->pd_points; point++) { + + /* Absolute values */ + pd->pd_pwr[point] = 2 * pcinfo->pwr[point]; + + /* Already sorted */ + pd->pd_step[point] = pcinfo->pcdac[point]; + } + + /* Set min/max pwr */ + chinfo[pier].min_pwr = pd->pd_pwr[0]; + chinfo[pier].max_pwr = pd->pd_pwr[10]; + + } + + return 0; + +err_out: + ath5k_eeprom_free_pcal_info(ah, mode); + return -ENOMEM; +} + +/* Parse EEPROM data */ +static int +ath5k_eeprom_read_pcal_info_5111(struct ath5k_hw *ah, int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info *pcal; + int offset, ret; + int i; + u16 val; + + offset = AR5K_EEPROM_GROUPS_START(ee->ee_version); + switch (mode) { + case AR5K_EEPROM_MODE_11A: + if (!AR5K_EEPROM_HDR_11A(ee->ee_header)) + return 0; + + ret = ath5k_eeprom_init_11a_pcal_freq(ah, + offset + AR5K_EEPROM_GROUP1_OFFSET); + if (ret < 0) + return ret; + + offset += AR5K_EEPROM_GROUP2_OFFSET; + pcal = ee->ee_pwr_cal_a; + break; + case AR5K_EEPROM_MODE_11B: + if (!AR5K_EEPROM_HDR_11B(ee->ee_header) && + !AR5K_EEPROM_HDR_11G(ee->ee_header)) + return 0; + + pcal = ee->ee_pwr_cal_b; + offset += AR5K_EEPROM_GROUP3_OFFSET; + + /* fixed piers */ + pcal[0].freq = 2412; + pcal[1].freq = 2447; + pcal[2].freq = 2484; + ee->ee_n_piers[mode] = 3; + break; + case AR5K_EEPROM_MODE_11G: + if (!AR5K_EEPROM_HDR_11G(ee->ee_header)) + return 0; + + pcal = ee->ee_pwr_cal_g; + offset += AR5K_EEPROM_GROUP4_OFFSET; + + /* fixed piers */ + pcal[0].freq = 2312; + pcal[1].freq = 2412; + pcal[2].freq = 2484; + ee->ee_n_piers[mode] = 3; + break; + default: + return -EINVAL; + } + + for (i = 0; i < ee->ee_n_piers[mode]; i++) { + struct ath5k_chan_pcal_info_rf5111 *cdata = + &pcal[i].rf5111_info; + + AR5K_EEPROM_READ(offset++, val); + cdata->pcdac_max = ((val >> 10) & AR5K_EEPROM_PCDAC_M); + cdata->pcdac_min = ((val >> 4) & AR5K_EEPROM_PCDAC_M); + cdata->pwr[0] = ((val << 2) & AR5K_EEPROM_POWER_M); + + AR5K_EEPROM_READ(offset++, val); + cdata->pwr[0] |= ((val >> 14) & 0x3); + cdata->pwr[1] = ((val >> 8) & AR5K_EEPROM_POWER_M); + cdata->pwr[2] = ((val >> 2) & AR5K_EEPROM_POWER_M); + cdata->pwr[3] = ((val << 4) & AR5K_EEPROM_POWER_M); + + AR5K_EEPROM_READ(offset++, val); + cdata->pwr[3] |= ((val >> 12) & 0xf); + cdata->pwr[4] = ((val >> 6) & AR5K_EEPROM_POWER_M); + cdata->pwr[5] = (val & AR5K_EEPROM_POWER_M); + + AR5K_EEPROM_READ(offset++, val); + cdata->pwr[6] = ((val >> 10) & AR5K_EEPROM_POWER_M); + cdata->pwr[7] = ((val >> 4) & AR5K_EEPROM_POWER_M); + cdata->pwr[8] = ((val << 2) & AR5K_EEPROM_POWER_M); + + AR5K_EEPROM_READ(offset++, val); + cdata->pwr[8] |= ((val >> 14) & 0x3); + cdata->pwr[9] = ((val >> 8) & AR5K_EEPROM_POWER_M); + cdata->pwr[10] = ((val >> 2) & AR5K_EEPROM_POWER_M); + + ath5k_get_pcdac_intercepts(ah, cdata->pcdac_min, + cdata->pcdac_max, cdata->pcdac); + } + + return ath5k_eeprom_convert_pcal_info_5111(ah, mode, pcal); +} + + +/* + * Read power calibration for RF5112 chips + * + * For RF5112 we have 4 XPD -eXternal Power Detector- curves + * for each calibrated channel on 0, -6, -12 and -18dBm but we only + * use the higher (3) and the lower (0) curves. Each curve has 0.5dB + * power steps on x axis and PCDAC steps on y axis and looks like a + * linear function. To recreate the curve and pass the power values + * on hw, we read 4 points for xpd 0 (lower gain -> max power) + * and 3 points for xpd 3 (higher gain -> lower power) here and + * interpolate later. + * + * Note: Many vendors just use xpd 0 so xpd 3 is zeroed. + */ + +/* Convert RF5112 specific data to generic raw data + * used by interpolation code */ +static int +ath5k_eeprom_convert_pcal_info_5112(struct ath5k_hw *ah, int mode, + struct ath5k_chan_pcal_info *chinfo) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info_rf5112 *pcinfo; + u8 *pdgain_idx = ee->ee_pdc_to_idx[mode]; + unsigned int pier, pdg, point; + + /* Fill raw data for each calibration pier */ + for (pier = 0; pier < ee->ee_n_piers[mode]; pier++) { + + pcinfo = &chinfo[pier].rf5112_info; + + /* Allocate pd_curves for this cal pier */ + chinfo[pier].pd_curves = + kcalloc(AR5K_EEPROM_N_PD_CURVES, + sizeof(struct ath5k_pdgain_info), + GFP_KERNEL); + + if (!chinfo[pier].pd_curves) + goto err_out; + + /* Fill pd_curves */ + for (pdg = 0; pdg < ee->ee_pd_gains[mode]; pdg++) { + + u8 idx = pdgain_idx[pdg]; + struct ath5k_pdgain_info *pd = + &chinfo[pier].pd_curves[idx]; + + /* Lowest gain curve (max power) */ + if (pdg == 0) { + /* One more point for better accuracy */ + pd->pd_points = AR5K_EEPROM_N_XPD0_POINTS; + + /* Allocate pd points for this curve */ + pd->pd_step = kcalloc(pd->pd_points, + sizeof(u8), GFP_KERNEL); + + if (!pd->pd_step) + goto err_out; + + pd->pd_pwr = kcalloc(pd->pd_points, + sizeof(s16), GFP_KERNEL); + + if (!pd->pd_pwr) + goto err_out; + + /* Fill raw dataset + * (all power levels are in 0.25dB units) */ + pd->pd_step[0] = pcinfo->pcdac_x0[0]; + pd->pd_pwr[0] = pcinfo->pwr_x0[0]; + + for (point = 1; point < pd->pd_points; + point++) { + /* Absolute values */ + pd->pd_pwr[point] = + pcinfo->pwr_x0[point]; + + /* Deltas */ + pd->pd_step[point] = + pd->pd_step[point - 1] + + pcinfo->pcdac_x0[point]; + } + + /* Set min power for this frequency */ + chinfo[pier].min_pwr = pd->pd_pwr[0]; + + /* Highest gain curve (min power) */ + } else if (pdg == 1) { + + pd->pd_points = AR5K_EEPROM_N_XPD3_POINTS; + + /* Allocate pd points for this curve */ + pd->pd_step = kcalloc(pd->pd_points, + sizeof(u8), GFP_KERNEL); + + if (!pd->pd_step) + goto err_out; + + pd->pd_pwr = kcalloc(pd->pd_points, + sizeof(s16), GFP_KERNEL); + + if (!pd->pd_pwr) + goto err_out; + + /* Fill raw dataset + * (all power levels are in 0.25dB units) */ + for (point = 0; point < pd->pd_points; + point++) { + /* Absolute values */ + pd->pd_pwr[point] = + pcinfo->pwr_x3[point]; + + /* Fixed points */ + pd->pd_step[point] = + pcinfo->pcdac_x3[point]; + } + + /* Since we have a higher gain curve + * override min power */ + chinfo[pier].min_pwr = pd->pd_pwr[0]; + } + } + } + + return 0; + +err_out: + ath5k_eeprom_free_pcal_info(ah, mode); + return -ENOMEM; +} + +/* Parse EEPROM data */ +static int +ath5k_eeprom_read_pcal_info_5112(struct ath5k_hw *ah, int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info_rf5112 *chan_pcal_info; + struct ath5k_chan_pcal_info *gen_chan_info; + u8 *pdgain_idx = ee->ee_pdc_to_idx[mode]; + u32 offset; + u8 i, c; + u16 val; + u8 pd_gains = 0; + + /* Count how many curves we have and + * identify them (which one of the 4 + * available curves we have on each count). + * Curves are stored from lower (x0) to + * higher (x3) gain */ + for (i = 0; i < AR5K_EEPROM_N_PD_CURVES; i++) { + /* ee_x_gain[mode] is x gain mask */ + if ((ee->ee_x_gain[mode] >> i) & 0x1) + pdgain_idx[pd_gains++] = i; + } + ee->ee_pd_gains[mode] = pd_gains; + + if (pd_gains == 0 || pd_gains > 2) + return -EINVAL; + + switch (mode) { + case AR5K_EEPROM_MODE_11A: + /* + * Read 5GHz EEPROM channels + */ + offset = AR5K_EEPROM_GROUPS_START(ee->ee_version); + ath5k_eeprom_init_11a_pcal_freq(ah, offset); + + offset += AR5K_EEPROM_GROUP2_OFFSET; + gen_chan_info = ee->ee_pwr_cal_a; + break; + case AR5K_EEPROM_MODE_11B: + offset = AR5K_EEPROM_GROUPS_START(ee->ee_version); + if (AR5K_EEPROM_HDR_11A(ee->ee_header)) + offset += AR5K_EEPROM_GROUP3_OFFSET; + + /* NB: frequency piers parsed during mode init */ + gen_chan_info = ee->ee_pwr_cal_b; + break; + case AR5K_EEPROM_MODE_11G: + offset = AR5K_EEPROM_GROUPS_START(ee->ee_version); + if (AR5K_EEPROM_HDR_11A(ee->ee_header)) + offset += AR5K_EEPROM_GROUP4_OFFSET; + else if (AR5K_EEPROM_HDR_11B(ee->ee_header)) + offset += AR5K_EEPROM_GROUP2_OFFSET; + + /* NB: frequency piers parsed during mode init */ + gen_chan_info = ee->ee_pwr_cal_g; + break; + default: + return -EINVAL; + } + + for (i = 0; i < ee->ee_n_piers[mode]; i++) { + chan_pcal_info = &gen_chan_info[i].rf5112_info; + + /* Power values in quarter dB + * for the lower xpd gain curve + * (0 dBm -> higher output power) */ + for (c = 0; c < AR5K_EEPROM_N_XPD0_POINTS; c++) { + AR5K_EEPROM_READ(offset++, val); + chan_pcal_info->pwr_x0[c] = (s8) (val & 0xff); + chan_pcal_info->pwr_x0[++c] = (s8) ((val >> 8) & 0xff); + } + + /* PCDAC steps + * corresponding to the above power + * measurements */ + AR5K_EEPROM_READ(offset++, val); + chan_pcal_info->pcdac_x0[1] = (val & 0x1f); + chan_pcal_info->pcdac_x0[2] = ((val >> 5) & 0x1f); + chan_pcal_info->pcdac_x0[3] = ((val >> 10) & 0x1f); + + /* Power values in quarter dB + * for the higher xpd gain curve + * (18 dBm -> lower output power) */ + AR5K_EEPROM_READ(offset++, val); + chan_pcal_info->pwr_x3[0] = (s8) (val & 0xff); + chan_pcal_info->pwr_x3[1] = (s8) ((val >> 8) & 0xff); + + AR5K_EEPROM_READ(offset++, val); + chan_pcal_info->pwr_x3[2] = (val & 0xff); + + /* PCDAC steps + * corresponding to the above power + * measurements (fixed) */ + chan_pcal_info->pcdac_x3[0] = 20; + chan_pcal_info->pcdac_x3[1] = 35; + chan_pcal_info->pcdac_x3[2] = 63; + + if (ee->ee_version >= AR5K_EEPROM_VERSION_4_3) { + chan_pcal_info->pcdac_x0[0] = ((val >> 8) & 0x3f); + + /* Last xpd0 power level is also channel maximum */ + gen_chan_info[i].max_pwr = chan_pcal_info->pwr_x0[3]; + } else { + chan_pcal_info->pcdac_x0[0] = 1; + gen_chan_info[i].max_pwr = (s8) ((val >> 8) & 0xff); + } + + } + + return ath5k_eeprom_convert_pcal_info_5112(ah, mode, gen_chan_info); +} + + +/* + * Read power calibration for RF2413 chips + * + * For RF2413 we have a Power to PDDAC table (Power Detector) + * instead of a PCDAC and 4 pd gain curves for each calibrated channel. + * Each curve has power on x axis in 0.5 db steps and PDDADC steps on y + * axis and looks like an exponential function like the RF5111 curve. + * + * To recreate the curves we read here the points and interpolate + * later. Note that in most cases only 2 (higher and lower) curves are + * used (like RF5112) but vendors have the opportunity to include all + * 4 curves on eeprom. The final curve (higher power) has an extra + * point for better accuracy like RF5112. + */ + +/* For RF2413 power calibration data doesn't start on a fixed location and + * if a mode is not supported, its section is missing -not zeroed-. + * So we need to calculate the starting offset for each section by using + * these two functions */ + +/* Return the size of each section based on the mode and the number of pd + * gains available (maximum 4). */ +static inline unsigned int +ath5k_pdgains_size_2413(struct ath5k_eeprom_info *ee, unsigned int mode) +{ + static const unsigned int pdgains_size[] = { 4, 6, 9, 12 }; + unsigned int sz; + + sz = pdgains_size[ee->ee_pd_gains[mode] - 1]; + sz *= ee->ee_n_piers[mode]; + + return sz; +} + +/* Return the starting offset for a section based on the modes supported + * and each section's size. */ +static unsigned int +ath5k_cal_data_offset_2413(struct ath5k_eeprom_info *ee, int mode) +{ + u32 offset = AR5K_EEPROM_CAL_DATA_START(ee->ee_misc4); + + switch (mode) { + case AR5K_EEPROM_MODE_11G: + if (AR5K_EEPROM_HDR_11B(ee->ee_header)) + offset += ath5k_pdgains_size_2413(ee, + AR5K_EEPROM_MODE_11B) + + AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2; + /* fall through */ + case AR5K_EEPROM_MODE_11B: + if (AR5K_EEPROM_HDR_11A(ee->ee_header)) + offset += ath5k_pdgains_size_2413(ee, + AR5K_EEPROM_MODE_11A) + + AR5K_EEPROM_N_5GHZ_CHAN / 2; + /* fall through */ + case AR5K_EEPROM_MODE_11A: + break; + default: + break; + } + + return offset; +} + +/* Convert RF2413 specific data to generic raw data + * used by interpolation code */ +static int +ath5k_eeprom_convert_pcal_info_2413(struct ath5k_hw *ah, int mode, + struct ath5k_chan_pcal_info *chinfo) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info_rf2413 *pcinfo; + u8 *pdgain_idx = ee->ee_pdc_to_idx[mode]; + unsigned int pier, pdg, point; + + /* Fill raw data for each calibration pier */ + for (pier = 0; pier < ee->ee_n_piers[mode]; pier++) { + + pcinfo = &chinfo[pier].rf2413_info; + + /* Allocate pd_curves for this cal pier */ + chinfo[pier].pd_curves = + kcalloc(AR5K_EEPROM_N_PD_CURVES, + sizeof(struct ath5k_pdgain_info), + GFP_KERNEL); + + if (!chinfo[pier].pd_curves) + goto err_out; + + /* Fill pd_curves */ + for (pdg = 0; pdg < ee->ee_pd_gains[mode]; pdg++) { + + u8 idx = pdgain_idx[pdg]; + struct ath5k_pdgain_info *pd = + &chinfo[pier].pd_curves[idx]; + + /* One more point for the highest power + * curve (lowest gain) */ + if (pdg == ee->ee_pd_gains[mode] - 1) + pd->pd_points = AR5K_EEPROM_N_PD_POINTS; + else + pd->pd_points = AR5K_EEPROM_N_PD_POINTS - 1; + + /* Allocate pd points for this curve */ + pd->pd_step = kcalloc(pd->pd_points, + sizeof(u8), GFP_KERNEL); + + if (!pd->pd_step) + goto err_out; + + pd->pd_pwr = kcalloc(pd->pd_points, + sizeof(s16), GFP_KERNEL); + + if (!pd->pd_pwr) + goto err_out; + + /* Fill raw dataset + * convert all pwr levels to + * quarter dB for RF5112 compatibility */ + pd->pd_step[0] = pcinfo->pddac_i[pdg]; + pd->pd_pwr[0] = 4 * pcinfo->pwr_i[pdg]; + + for (point = 1; point < pd->pd_points; point++) { + + pd->pd_pwr[point] = pd->pd_pwr[point - 1] + + 2 * pcinfo->pwr[pdg][point - 1]; + + pd->pd_step[point] = pd->pd_step[point - 1] + + pcinfo->pddac[pdg][point - 1]; + + } + + /* Highest gain curve -> min power */ + if (pdg == 0) + chinfo[pier].min_pwr = pd->pd_pwr[0]; + + /* Lowest gain curve -> max power */ + if (pdg == ee->ee_pd_gains[mode] - 1) + chinfo[pier].max_pwr = + pd->pd_pwr[pd->pd_points - 1]; + } + } + + return 0; + +err_out: + ath5k_eeprom_free_pcal_info(ah, mode); + return -ENOMEM; +} + +/* Parse EEPROM data */ +static int +ath5k_eeprom_read_pcal_info_2413(struct ath5k_hw *ah, int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info_rf2413 *pcinfo; + struct ath5k_chan_pcal_info *chinfo; + u8 *pdgain_idx = ee->ee_pdc_to_idx[mode]; + u32 offset; + int idx, i; + u16 val; + u8 pd_gains = 0; + + /* Count how many curves we have and + * identify them (which one of the 4 + * available curves we have on each count). + * Curves are stored from higher to + * lower gain so we go backwards */ + for (idx = AR5K_EEPROM_N_PD_CURVES - 1; idx >= 0; idx--) { + /* ee_x_gain[mode] is x gain mask */ + if ((ee->ee_x_gain[mode] >> idx) & 0x1) + pdgain_idx[pd_gains++] = idx; + + } + ee->ee_pd_gains[mode] = pd_gains; + + if (pd_gains == 0) + return -EINVAL; + + offset = ath5k_cal_data_offset_2413(ee, mode); + switch (mode) { + case AR5K_EEPROM_MODE_11A: + if (!AR5K_EEPROM_HDR_11A(ee->ee_header)) + return 0; + + ath5k_eeprom_init_11a_pcal_freq(ah, offset); + offset += AR5K_EEPROM_N_5GHZ_CHAN / 2; + chinfo = ee->ee_pwr_cal_a; + break; + case AR5K_EEPROM_MODE_11B: + if (!AR5K_EEPROM_HDR_11B(ee->ee_header)) + return 0; + + ath5k_eeprom_init_11bg_2413(ah, mode, offset); + offset += AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2; + chinfo = ee->ee_pwr_cal_b; + break; + case AR5K_EEPROM_MODE_11G: + if (!AR5K_EEPROM_HDR_11G(ee->ee_header)) + return 0; + + ath5k_eeprom_init_11bg_2413(ah, mode, offset); + offset += AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2; + chinfo = ee->ee_pwr_cal_g; + break; + default: + return -EINVAL; + } + + for (i = 0; i < ee->ee_n_piers[mode]; i++) { + pcinfo = &chinfo[i].rf2413_info; + + /* + * Read pwr_i, pddac_i and the first + * 2 pd points (pwr, pddac) + */ + AR5K_EEPROM_READ(offset++, val); + pcinfo->pwr_i[0] = val & 0x1f; + pcinfo->pddac_i[0] = (val >> 5) & 0x7f; + pcinfo->pwr[0][0] = (val >> 12) & 0xf; + + AR5K_EEPROM_READ(offset++, val); + pcinfo->pddac[0][0] = val & 0x3f; + pcinfo->pwr[0][1] = (val >> 6) & 0xf; + pcinfo->pddac[0][1] = (val >> 10) & 0x3f; + + AR5K_EEPROM_READ(offset++, val); + pcinfo->pwr[0][2] = val & 0xf; + pcinfo->pddac[0][2] = (val >> 4) & 0x3f; + + pcinfo->pwr[0][3] = 0; + pcinfo->pddac[0][3] = 0; + + if (pd_gains > 1) { + /* + * Pd gain 0 is not the last pd gain + * so it only has 2 pd points. + * Continue with pd gain 1. + */ + pcinfo->pwr_i[1] = (val >> 10) & 0x1f; + + pcinfo->pddac_i[1] = (val >> 15) & 0x1; + AR5K_EEPROM_READ(offset++, val); + pcinfo->pddac_i[1] |= (val & 0x3F) << 1; + + pcinfo->pwr[1][0] = (val >> 6) & 0xf; + pcinfo->pddac[1][0] = (val >> 10) & 0x3f; + + AR5K_EEPROM_READ(offset++, val); + pcinfo->pwr[1][1] = val & 0xf; + pcinfo->pddac[1][1] = (val >> 4) & 0x3f; + pcinfo->pwr[1][2] = (val >> 10) & 0xf; + + pcinfo->pddac[1][2] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); + pcinfo->pddac[1][2] |= (val & 0xF) << 2; + + pcinfo->pwr[1][3] = 0; + pcinfo->pddac[1][3] = 0; + } else if (pd_gains == 1) { + /* + * Pd gain 0 is the last one so + * read the extra point. + */ + pcinfo->pwr[0][3] = (val >> 10) & 0xf; + + pcinfo->pddac[0][3] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); + pcinfo->pddac[0][3] |= (val & 0xF) << 2; + } + + /* + * Proceed with the other pd_gains + * as above. + */ + if (pd_gains > 2) { + pcinfo->pwr_i[2] = (val >> 4) & 0x1f; + pcinfo->pddac_i[2] = (val >> 9) & 0x7f; + + AR5K_EEPROM_READ(offset++, val); + pcinfo->pwr[2][0] = (val >> 0) & 0xf; + pcinfo->pddac[2][0] = (val >> 4) & 0x3f; + pcinfo->pwr[2][1] = (val >> 10) & 0xf; + + pcinfo->pddac[2][1] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); + pcinfo->pddac[2][1] |= (val & 0xF) << 2; + + pcinfo->pwr[2][2] = (val >> 4) & 0xf; + pcinfo->pddac[2][2] = (val >> 8) & 0x3f; + + pcinfo->pwr[2][3] = 0; + pcinfo->pddac[2][3] = 0; + } else if (pd_gains == 2) { + pcinfo->pwr[1][3] = (val >> 4) & 0xf; + pcinfo->pddac[1][3] = (val >> 8) & 0x3f; + } + + if (pd_gains > 3) { + pcinfo->pwr_i[3] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); + pcinfo->pwr_i[3] |= ((val >> 0) & 0x7) << 2; + + pcinfo->pddac_i[3] = (val >> 3) & 0x7f; + pcinfo->pwr[3][0] = (val >> 10) & 0xf; + pcinfo->pddac[3][0] = (val >> 14) & 0x3; + + AR5K_EEPROM_READ(offset++, val); + pcinfo->pddac[3][0] |= (val & 0xF) << 2; + pcinfo->pwr[3][1] = (val >> 4) & 0xf; + pcinfo->pddac[3][1] = (val >> 8) & 0x3f; + + pcinfo->pwr[3][2] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); + pcinfo->pwr[3][2] |= ((val >> 0) & 0x3) << 2; + + pcinfo->pddac[3][2] = (val >> 2) & 0x3f; + pcinfo->pwr[3][3] = (val >> 8) & 0xf; + + pcinfo->pddac[3][3] = (val >> 12) & 0xF; + AR5K_EEPROM_READ(offset++, val); + pcinfo->pddac[3][3] |= ((val >> 0) & 0x3) << 4; + } else if (pd_gains == 3) { + pcinfo->pwr[2][3] = (val >> 14) & 0x3; + AR5K_EEPROM_READ(offset++, val); + pcinfo->pwr[2][3] |= ((val >> 0) & 0x3) << 2; + + pcinfo->pddac[2][3] = (val >> 2) & 0x3f; + } + } + + return ath5k_eeprom_convert_pcal_info_2413(ah, mode, chinfo); +} + + +/* + * Read per rate target power (this is the maximum tx power + * supported by the card). This info is used when setting + * tx power, no matter the channel. + * + * This also works for v5 EEPROMs. + */ +static int +ath5k_eeprom_read_target_rate_pwr_info(struct ath5k_hw *ah, unsigned int mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_rate_pcal_info *rate_pcal_info; + u8 *rate_target_pwr_num; + u32 offset; + u16 val; + int i; + + offset = AR5K_EEPROM_TARGET_PWRSTART(ee->ee_misc1); + rate_target_pwr_num = &ee->ee_rate_target_pwr_num[mode]; + switch (mode) { + case AR5K_EEPROM_MODE_11A: + offset += AR5K_EEPROM_TARGET_PWR_OFF_11A(ee->ee_version); + rate_pcal_info = ee->ee_rate_tpwr_a; + ee->ee_rate_target_pwr_num[mode] = AR5K_EEPROM_N_5GHZ_RATE_CHAN; + break; + case AR5K_EEPROM_MODE_11B: + offset += AR5K_EEPROM_TARGET_PWR_OFF_11B(ee->ee_version); + rate_pcal_info = ee->ee_rate_tpwr_b; + ee->ee_rate_target_pwr_num[mode] = 2; /* 3rd is g mode's 1st */ + break; + case AR5K_EEPROM_MODE_11G: + offset += AR5K_EEPROM_TARGET_PWR_OFF_11G(ee->ee_version); + rate_pcal_info = ee->ee_rate_tpwr_g; + ee->ee_rate_target_pwr_num[mode] = AR5K_EEPROM_N_2GHZ_CHAN; + break; + default: + return -EINVAL; + } + + /* Different freq mask for older eeproms (<= v3.2) */ + if (ee->ee_version <= AR5K_EEPROM_VERSION_3_2) { + for (i = 0; i < (*rate_target_pwr_num); i++) { + AR5K_EEPROM_READ(offset++, val); + rate_pcal_info[i].freq = + ath5k_eeprom_bin2freq(ee, (val >> 9) & 0x7f, mode); + + rate_pcal_info[i].target_power_6to24 = ((val >> 3) & 0x3f); + rate_pcal_info[i].target_power_36 = (val << 3) & 0x3f; + + AR5K_EEPROM_READ(offset++, val); + + if (rate_pcal_info[i].freq == AR5K_EEPROM_CHANNEL_DIS || + val == 0) { + (*rate_target_pwr_num) = i; + break; + } + + rate_pcal_info[i].target_power_36 |= ((val >> 13) & 0x7); + rate_pcal_info[i].target_power_48 = ((val >> 7) & 0x3f); + rate_pcal_info[i].target_power_54 = ((val >> 1) & 0x3f); + } + } else { + for (i = 0; i < (*rate_target_pwr_num); i++) { + AR5K_EEPROM_READ(offset++, val); + rate_pcal_info[i].freq = + ath5k_eeprom_bin2freq(ee, (val >> 8) & 0xff, mode); + + rate_pcal_info[i].target_power_6to24 = ((val >> 2) & 0x3f); + rate_pcal_info[i].target_power_36 = (val << 4) & 0x3f; + + AR5K_EEPROM_READ(offset++, val); + + if (rate_pcal_info[i].freq == AR5K_EEPROM_CHANNEL_DIS || + val == 0) { + (*rate_target_pwr_num) = i; + break; + } + + rate_pcal_info[i].target_power_36 |= (val >> 12) & 0xf; + rate_pcal_info[i].target_power_48 = ((val >> 6) & 0x3f); + rate_pcal_info[i].target_power_54 = (val & 0x3f); + } + } + + return 0; +} + + +/* + * Read per channel calibration info from EEPROM + * + * This info is used to calibrate the baseband power table. Imagine + * that for each channel there is a power curve that's hw specific + * (depends on amplifier etc) and we try to "correct" this curve using + * offsets we pass on to phy chip (baseband -> before amplifier) so that + * it can use accurate power values when setting tx power (takes amplifier's + * performance on each channel into account). + * + * EEPROM provides us with the offsets for some pre-calibrated channels + * and we have to interpolate to create the full table for these channels and + * also the table for any channel. + */ +static int +ath5k_eeprom_read_pcal_info(struct ath5k_hw *ah) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + int (*read_pcal)(struct ath5k_hw *hw, int mode); + int mode; + int err; + + if ((ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_0) && + (AR5K_EEPROM_EEMAP(ee->ee_misc0) == 1)) + read_pcal = ath5k_eeprom_read_pcal_info_5112; + else if ((ah->ah_ee_version >= AR5K_EEPROM_VERSION_5_0) && + (AR5K_EEPROM_EEMAP(ee->ee_misc0) == 2)) + read_pcal = ath5k_eeprom_read_pcal_info_2413; + else + read_pcal = ath5k_eeprom_read_pcal_info_5111; + + + for (mode = AR5K_EEPROM_MODE_11A; mode <= AR5K_EEPROM_MODE_11G; + mode++) { + err = read_pcal(ah, mode); + if (err) + return err; + + err = ath5k_eeprom_read_target_rate_pwr_info(ah, mode); + if (err < 0) + return err; + } + + return 0; +} + +/* Read conformance test limits used for regulatory control */ +static int +ath5k_eeprom_read_ctl_info(struct ath5k_hw *ah) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_edge_power *rep; + unsigned int fmask, pmask; + unsigned int ctl_mode; + int i, j; + u32 offset; + u16 val; + + pmask = AR5K_EEPROM_POWER_M; + fmask = AR5K_EEPROM_FREQ_M(ee->ee_version); + offset = AR5K_EEPROM_CTL(ee->ee_version); + ee->ee_ctls = AR5K_EEPROM_N_CTLS(ee->ee_version); + for (i = 0; i < ee->ee_ctls; i += 2) { + AR5K_EEPROM_READ(offset++, val); + ee->ee_ctl[i] = (val >> 8) & 0xff; + ee->ee_ctl[i + 1] = val & 0xff; + } + + offset = AR5K_EEPROM_GROUP8_OFFSET; + if (ee->ee_version >= AR5K_EEPROM_VERSION_4_0) + offset += AR5K_EEPROM_TARGET_PWRSTART(ee->ee_misc1) - + AR5K_EEPROM_GROUP5_OFFSET; + else + offset += AR5K_EEPROM_GROUPS_START(ee->ee_version); + + rep = ee->ee_ctl_pwr; + for (i = 0; i < ee->ee_ctls; i++) { + switch (ee->ee_ctl[i] & AR5K_CTL_MODE_M) { + case AR5K_CTL_11A: + case AR5K_CTL_TURBO: + ctl_mode = AR5K_EEPROM_MODE_11A; + break; + default: + ctl_mode = AR5K_EEPROM_MODE_11G; + break; + } + if (ee->ee_ctl[i] == 0) { + if (ee->ee_version >= AR5K_EEPROM_VERSION_3_3) + offset += 8; + else + offset += 7; + rep += AR5K_EEPROM_N_EDGES; + continue; + } + if (ee->ee_version >= AR5K_EEPROM_VERSION_3_3) { + for (j = 0; j < AR5K_EEPROM_N_EDGES; j += 2) { + AR5K_EEPROM_READ(offset++, val); + rep[j].freq = (val >> 8) & fmask; + rep[j + 1].freq = val & fmask; + } + for (j = 0; j < AR5K_EEPROM_N_EDGES; j += 2) { + AR5K_EEPROM_READ(offset++, val); + rep[j].edge = (val >> 8) & pmask; + rep[j].flag = (val >> 14) & 1; + rep[j + 1].edge = val & pmask; + rep[j + 1].flag = (val >> 6) & 1; + } + } else { + AR5K_EEPROM_READ(offset++, val); + rep[0].freq = (val >> 9) & fmask; + rep[1].freq = (val >> 2) & fmask; + rep[2].freq = (val << 5) & fmask; + + AR5K_EEPROM_READ(offset++, val); + rep[2].freq |= (val >> 11) & 0x1f; + rep[3].freq = (val >> 4) & fmask; + rep[4].freq = (val << 3) & fmask; + + AR5K_EEPROM_READ(offset++, val); + rep[4].freq |= (val >> 13) & 0x7; + rep[5].freq = (val >> 6) & fmask; + rep[6].freq = (val << 1) & fmask; + + AR5K_EEPROM_READ(offset++, val); + rep[6].freq |= (val >> 15) & 0x1; + rep[7].freq = (val >> 8) & fmask; + + rep[0].edge = (val >> 2) & pmask; + rep[1].edge = (val << 4) & pmask; + + AR5K_EEPROM_READ(offset++, val); + rep[1].edge |= (val >> 12) & 0xf; + rep[2].edge = (val >> 6) & pmask; + rep[3].edge = val & pmask; + + AR5K_EEPROM_READ(offset++, val); + rep[4].edge = (val >> 10) & pmask; + rep[5].edge = (val >> 4) & pmask; + rep[6].edge = (val << 2) & pmask; + + AR5K_EEPROM_READ(offset++, val); + rep[6].edge |= (val >> 14) & 0x3; + rep[7].edge = (val >> 8) & pmask; + } + for (j = 0; j < AR5K_EEPROM_N_EDGES; j++) { + rep[j].freq = ath5k_eeprom_bin2freq(ee, + rep[j].freq, ctl_mode); + } + rep += AR5K_EEPROM_N_EDGES; + } + + return 0; +} + +static int +ath5k_eeprom_read_spur_chans(struct ath5k_hw *ah) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u32 offset; + u16 val; + int ret = 0, i; + + offset = AR5K_EEPROM_CTL(ee->ee_version) + + AR5K_EEPROM_N_CTLS(ee->ee_version); + + if (ee->ee_version < AR5K_EEPROM_VERSION_5_3) { + /* No spur info for 5GHz */ + ee->ee_spur_chans[0][0] = AR5K_EEPROM_NO_SPUR; + /* 2 channels for 2GHz (2464/2420) */ + ee->ee_spur_chans[0][1] = AR5K_EEPROM_5413_SPUR_CHAN_1; + ee->ee_spur_chans[1][1] = AR5K_EEPROM_5413_SPUR_CHAN_2; + ee->ee_spur_chans[2][1] = AR5K_EEPROM_NO_SPUR; + } else if (ee->ee_version >= AR5K_EEPROM_VERSION_5_3) { + for (i = 0; i < AR5K_EEPROM_N_SPUR_CHANS; i++) { + AR5K_EEPROM_READ(offset, val); + ee->ee_spur_chans[i][0] = val; + AR5K_EEPROM_READ(offset + AR5K_EEPROM_N_SPUR_CHANS, + val); + ee->ee_spur_chans[i][1] = val; + offset++; + } + } + + return ret; +} + + +/***********************\ +* Init/Detach functions * +\***********************/ + +/* + * Initialize eeprom data structure + */ +int +ath5k_eeprom_init(struct ath5k_hw *ah) +{ + int err; + + err = ath5k_eeprom_init_header(ah); + if (err < 0) + return err; + + err = ath5k_eeprom_init_modes(ah); + if (err < 0) + return err; + + err = ath5k_eeprom_read_pcal_info(ah); + if (err < 0) + return err; + + err = ath5k_eeprom_read_ctl_info(ah); + if (err < 0) + return err; + + err = ath5k_eeprom_read_spur_chans(ah); + if (err < 0) + return err; + + return 0; +} + +void +ath5k_eeprom_detach(struct ath5k_hw *ah) +{ + u8 mode; + + for (mode = AR5K_EEPROM_MODE_11A; mode <= AR5K_EEPROM_MODE_11G; mode++) + ath5k_eeprom_free_pcal_info(ah, mode); +} + +int +ath5k_eeprom_mode_from_channel(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + switch (channel->hw_value) { + case AR5K_MODE_11A: + return AR5K_EEPROM_MODE_11A; + case AR5K_MODE_11G: + return AR5K_EEPROM_MODE_11G; + case AR5K_MODE_11B: + return AR5K_EEPROM_MODE_11B; + default: + ATH5K_WARN(ah, "channel is not A/B/G!"); + return AR5K_EEPROM_MODE_11A; + } +} diff --git a/drivers/net/wireless/ath/ath5k/eeprom.h b/drivers/net/wireless/ath/ath5k/eeprom.h new file mode 100644 index 0000000..693296e --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/eeprom.h @@ -0,0 +1,495 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter + * Copyright (c) 2006-2008 Nick Kossifidis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/* + * Common ar5xxx EEPROM data offsets (set these on AR5K_EEPROM_BASE) + */ +#define AR5K_EEPROM_PCIE_OFFSET 0x02 /* Contains offset to PCI-E infos */ +#define AR5K_EEPROM_PCIE_SERDES_SECTION 0x40 /* PCIE_OFFSET points here when + * SERDES infos are present */ +#define AR5K_EEPROM_MAGIC 0x003d /* EEPROM Magic number */ +#define AR5K_EEPROM_MAGIC_VALUE 0x5aa5 /* Default - found on EEPROM */ + +#define AR5K_EEPROM_IS_HB63 0x000b /* Talon detect */ + +#define AR5K_EEPROM_RFKILL 0x0f +#define AR5K_EEPROM_RFKILL_GPIO_SEL 0x0000001c +#define AR5K_EEPROM_RFKILL_GPIO_SEL_S 2 +#define AR5K_EEPROM_RFKILL_POLARITY 0x00000002 +#define AR5K_EEPROM_RFKILL_POLARITY_S 1 + +#define AR5K_EEPROM_REG_DOMAIN 0x00bf /* EEPROM regdom */ + +/* FLASH(EEPROM) Defines for AR531X chips */ +#define AR5K_EEPROM_SIZE_LOWER 0x1b /* size info -- lower */ +#define AR5K_EEPROM_SIZE_UPPER 0x1c /* size info -- upper */ +#define AR5K_EEPROM_SIZE_UPPER_MASK 0xfff0 +#define AR5K_EEPROM_SIZE_UPPER_SHIFT 4 +#define AR5K_EEPROM_SIZE_ENDLOC_SHIFT 12 + +#define AR5K_EEPROM_CHECKSUM 0x00c0 /* EEPROM checksum */ +#define AR5K_EEPROM_INFO_BASE 0x00c0 /* EEPROM header */ +#define AR5K_EEPROM_INFO_MAX (0x400 - AR5K_EEPROM_INFO_BASE) +#define AR5K_EEPROM_INFO_CKSUM 0xffff +#define AR5K_EEPROM_INFO(_n) (AR5K_EEPROM_INFO_BASE + (_n)) + +#define AR5K_EEPROM_VERSION AR5K_EEPROM_INFO(1) /* EEPROM Version */ +#define AR5K_EEPROM_VERSION_3_0 0x3000 /* No idea what's going on before this version */ +#define AR5K_EEPROM_VERSION_3_1 0x3001 /* ob/db values for 2GHz (ar5211_rfregs) */ +#define AR5K_EEPROM_VERSION_3_2 0x3002 /* different frequency representation (eeprom_bin2freq) */ +#define AR5K_EEPROM_VERSION_3_3 0x3003 /* offsets changed, has 32 CTLs (see below) and ee_false_detect (eeprom_read_modes) */ +#define AR5K_EEPROM_VERSION_3_4 0x3004 /* has ee_i_gain, ee_cck_ofdm_power_delta (eeprom_read_modes) */ +#define AR5K_EEPROM_VERSION_4_0 0x4000 /* has ee_misc, ee_cal_pier, ee_turbo_max_power and ee_xr_power (eeprom_init) */ +#define AR5K_EEPROM_VERSION_4_1 0x4001 /* has ee_margin_tx_rx (eeprom_init) */ +#define AR5K_EEPROM_VERSION_4_2 0x4002 /* has ee_cck_ofdm_gain_delta (eeprom_init) */ +#define AR5K_EEPROM_VERSION_4_3 0x4003 /* power calibration changes */ +#define AR5K_EEPROM_VERSION_4_4 0x4004 +#define AR5K_EEPROM_VERSION_4_5 0x4005 +#define AR5K_EEPROM_VERSION_4_6 0x4006 /* has ee_scaled_cck_delta */ +#define AR5K_EEPROM_VERSION_4_7 0x3007 /* 4007 ? */ +#define AR5K_EEPROM_VERSION_4_9 0x4009 /* EAR futureproofing */ +#define AR5K_EEPROM_VERSION_5_0 0x5000 /* Has 2413 PDADC calibration etc */ +#define AR5K_EEPROM_VERSION_5_1 0x5001 /* Has capability values */ +#define AR5K_EEPROM_VERSION_5_3 0x5003 /* Has spur mitigation tables */ + +#define AR5K_EEPROM_MODE_11A 0 +#define AR5K_EEPROM_MODE_11B 1 +#define AR5K_EEPROM_MODE_11G 2 + +#define AR5K_EEPROM_HDR AR5K_EEPROM_INFO(2) /* Header that contains the device caps */ +#define AR5K_EEPROM_HDR_11A(_v) (((_v) >> AR5K_EEPROM_MODE_11A) & 0x1) +#define AR5K_EEPROM_HDR_11B(_v) (((_v) >> AR5K_EEPROM_MODE_11B) & 0x1) +#define AR5K_EEPROM_HDR_11G(_v) (((_v) >> AR5K_EEPROM_MODE_11G) & 0x1) +#define AR5K_EEPROM_HDR_T_2GHZ_DIS(_v) (((_v) >> 3) & 0x1) /* Disable turbo for 2GHz */ +#define AR5K_EEPROM_HDR_T_5GHZ_DBM(_v) (((_v) >> 4) & 0x7f) /* Max turbo power for < 2W power consumption */ +#define AR5K_EEPROM_HDR_DEVICE(_v) (((_v) >> 11) & 0x7) /* Device type (1 Cardbus, 2 PCI, 3 MiniPCI, 4 AP) */ +#define AR5K_EEPROM_HDR_RFKILL(_v) (((_v) >> 14) & 0x1) /* Device has RFKill support */ +#define AR5K_EEPROM_HDR_T_5GHZ_DIS(_v) (((_v) >> 15) & 0x1) /* Disable turbo for 5GHz */ + +/* Newer EEPROMs are using a different offset */ +#define AR5K_EEPROM_OFF(_v, _v3_0, _v3_3) \ + (((_v) >= AR5K_EEPROM_VERSION_3_3) ? _v3_3 : _v3_0) + +#define AR5K_EEPROM_ANT_GAIN(_v) AR5K_EEPROM_OFF(_v, 0x00c4, 0x00c3) +#define AR5K_EEPROM_ANT_GAIN_5GHZ(_v) ((s8)(((_v) >> 8) & 0xff)) +#define AR5K_EEPROM_ANT_GAIN_2GHZ(_v) ((s8)((_v) & 0xff)) + +/* Misc values available since EEPROM 4.0 */ +#define AR5K_EEPROM_MISC0 AR5K_EEPROM_INFO(4) +#define AR5K_EEPROM_EARSTART(_v) ((_v) & 0xfff) +#define AR5K_EEPROM_HDR_XR2_DIS(_v) (((_v) >> 12) & 0x1) +#define AR5K_EEPROM_HDR_XR5_DIS(_v) (((_v) >> 13) & 0x1) +#define AR5K_EEPROM_EEMAP(_v) (((_v) >> 14) & 0x3) + +#define AR5K_EEPROM_MISC1 AR5K_EEPROM_INFO(5) +#define AR5K_EEPROM_TARGET_PWRSTART(_v) ((_v) & 0xfff) +#define AR5K_EEPROM_HAS32KHZCRYSTAL(_v) (((_v) >> 14) & 0x1) /* has 32KHz crystal for sleep mode */ +#define AR5K_EEPROM_HAS32KHZCRYSTAL_OLD(_v) (((_v) >> 15) & 0x1) + +#define AR5K_EEPROM_MISC2 AR5K_EEPROM_INFO(6) +#define AR5K_EEPROM_EEP_FILE_VERSION(_v) (((_v) >> 8) & 0xff) +#define AR5K_EEPROM_EAR_FILE_VERSION(_v) ((_v) & 0xff) + +#define AR5K_EEPROM_MISC3 AR5K_EEPROM_INFO(7) +#define AR5K_EEPROM_ART_BUILD_NUM(_v) (((_v) >> 10) & 0x3f) +#define AR5K_EEPROM_EAR_FILE_ID(_v) ((_v) & 0xff) + +#define AR5K_EEPROM_MISC4 AR5K_EEPROM_INFO(8) +#define AR5K_EEPROM_CAL_DATA_START(_v) (((_v) >> 4) & 0xfff) +#define AR5K_EEPROM_MASK_R0(_v) (((_v) >> 2) & 0x3) /* modes supported by radio 0 (bit 1: G, bit 2: A) */ +#define AR5K_EEPROM_MASK_R1(_v) ((_v) & 0x3) /* modes supported by radio 1 (bit 1: G, bit 2: A) */ + +#define AR5K_EEPROM_MISC5 AR5K_EEPROM_INFO(9) +#define AR5K_EEPROM_COMP_DIS(_v) ((_v) & 0x1) /* disable compression */ +#define AR5K_EEPROM_AES_DIS(_v) (((_v) >> 1) & 0x1) /* disable AES */ +#define AR5K_EEPROM_FF_DIS(_v) (((_v) >> 2) & 0x1) /* disable fast frames */ +#define AR5K_EEPROM_BURST_DIS(_v) (((_v) >> 3) & 0x1) /* disable bursting */ +#define AR5K_EEPROM_MAX_QCU(_v) (((_v) >> 4) & 0xf) /* max number of QCUs. defaults to 10 */ +#define AR5K_EEPROM_HEAVY_CLIP_EN(_v) (((_v) >> 8) & 0x1) /* enable heavy clipping */ +#define AR5K_EEPROM_KEY_CACHE_SIZE(_v) (((_v) >> 12) & 0xf) /* key cache size. defaults to 128 */ + +#define AR5K_EEPROM_MISC6 AR5K_EEPROM_INFO(10) +#define AR5K_EEPROM_TX_CHAIN_DIS ((_v) & 0x7) /* MIMO chains disabled for TX bitmask */ +#define AR5K_EEPROM_RX_CHAIN_DIS (((_v) >> 3) & 0x7) /* MIMO chains disabled for RX bitmask */ +#define AR5K_EEPROM_FCC_MID_EN (((_v) >> 6) & 0x1) /* 5.47-5.7GHz supported */ +#define AR5K_EEPROM_JAP_U1EVEN_EN (((_v) >> 7) & 0x1) /* Japan UNII1 band (5.15-5.25GHz) on even channels (5180, 5200, 5220, 5240) supported */ +#define AR5K_EEPROM_JAP_U2_EN (((_v) >> 8) & 0x1) /* Japan UNII2 band (5.25-5.35GHz) supported */ +#define AR5K_EEPROM_JAP_MID_EN (((_v) >> 9) & 0x1) /* Japan band from 5.47-5.7GHz supported */ +#define AR5K_EEPROM_JAP_U1ODD_EN (((_v) >> 10) & 0x1) /* Japan UNII2 band (5.15-5.25GHz) on odd channels (5170, 5190, 5210, 5230) supported */ +#define AR5K_EEPROM_JAP_11A_NEW_EN (((_v) >> 11) & 0x1) /* Japan A mode enabled (using even channels) */ + +/* calibration settings */ +#define AR5K_EEPROM_MODES_11A(_v) AR5K_EEPROM_OFF(_v, 0x00c5, 0x00d4) +#define AR5K_EEPROM_MODES_11B(_v) AR5K_EEPROM_OFF(_v, 0x00d0, 0x00f2) +#define AR5K_EEPROM_MODES_11G(_v) AR5K_EEPROM_OFF(_v, 0x00da, 0x010d) +#define AR5K_EEPROM_CTL(_v) AR5K_EEPROM_OFF(_v, 0x00e4, 0x0128) /* Conformance test limits */ +#define AR5K_EEPROM_GROUPS_START(_v) AR5K_EEPROM_OFF(_v, 0x0100, 0x0150) /* Start of Groups */ +#define AR5K_EEPROM_GROUP1_OFFSET 0x0 +#define AR5K_EEPROM_GROUP2_OFFSET 0x5 +#define AR5K_EEPROM_GROUP3_OFFSET 0x37 +#define AR5K_EEPROM_GROUP4_OFFSET 0x46 +#define AR5K_EEPROM_GROUP5_OFFSET 0x55 +#define AR5K_EEPROM_GROUP6_OFFSET 0x65 +#define AR5K_EEPROM_GROUP7_OFFSET 0x69 +#define AR5K_EEPROM_GROUP8_OFFSET 0x6f + +#define AR5K_EEPROM_TARGET_PWR_OFF_11A(_v) AR5K_EEPROM_OFF(_v, AR5K_EEPROM_GROUPS_START(_v) + \ + AR5K_EEPROM_GROUP5_OFFSET, 0x0000) +#define AR5K_EEPROM_TARGET_PWR_OFF_11B(_v) AR5K_EEPROM_OFF(_v, AR5K_EEPROM_GROUPS_START(_v) + \ + AR5K_EEPROM_GROUP6_OFFSET, 0x0010) +#define AR5K_EEPROM_TARGET_PWR_OFF_11G(_v) AR5K_EEPROM_OFF(_v, AR5K_EEPROM_GROUPS_START(_v) + \ + AR5K_EEPROM_GROUP7_OFFSET, 0x0014) + +/* [3.1 - 3.3] */ +#define AR5K_EEPROM_OBDB0_2GHZ 0x00ec +#define AR5K_EEPROM_OBDB1_2GHZ 0x00ed + +#define AR5K_EEPROM_PROTECT 0x003f /* EEPROM protect status */ +#define AR5K_EEPROM_PROTECT_RD_0_31 0x0001 /* Read protection bit for offsets 0x0 - 0x1f */ +#define AR5K_EEPROM_PROTECT_WR_0_31 0x0002 /* Write protection bit for offsets 0x0 - 0x1f */ +#define AR5K_EEPROM_PROTECT_RD_32_63 0x0004 /* 0x20 - 0x3f */ +#define AR5K_EEPROM_PROTECT_WR_32_63 0x0008 +#define AR5K_EEPROM_PROTECT_RD_64_127 0x0010 /* 0x40 - 0x7f */ +#define AR5K_EEPROM_PROTECT_WR_64_127 0x0020 +#define AR5K_EEPROM_PROTECT_RD_128_191 0x0040 /* 0x80 - 0xbf (regdom) */ +#define AR5K_EEPROM_PROTECT_WR_128_191 0x0080 +#define AR5K_EEPROM_PROTECT_RD_192_207 0x0100 /* 0xc0 - 0xcf */ +#define AR5K_EEPROM_PROTECT_WR_192_207 0x0200 +#define AR5K_EEPROM_PROTECT_RD_208_223 0x0400 /* 0xd0 - 0xdf */ +#define AR5K_EEPROM_PROTECT_WR_208_223 0x0800 +#define AR5K_EEPROM_PROTECT_RD_224_239 0x1000 /* 0xe0 - 0xef */ +#define AR5K_EEPROM_PROTECT_WR_224_239 0x2000 +#define AR5K_EEPROM_PROTECT_RD_240_255 0x4000 /* 0xf0 - 0xff */ +#define AR5K_EEPROM_PROTECT_WR_240_255 0x8000 + +/* Some EEPROM defines */ +#define AR5K_EEPROM_EEP_SCALE 100 +#define AR5K_EEPROM_EEP_DELTA 10 +#define AR5K_EEPROM_N_MODES 3 +#define AR5K_EEPROM_N_5GHZ_CHAN 10 +#define AR5K_EEPROM_N_5GHZ_RATE_CHAN 8 +#define AR5K_EEPROM_N_2GHZ_CHAN 3 +#define AR5K_EEPROM_N_2GHZ_CHAN_2413 4 +#define AR5K_EEPROM_N_2GHZ_CHAN_MAX 4 +#define AR5K_EEPROM_MAX_CHAN 10 +#define AR5K_EEPROM_N_PWR_POINTS_5111 11 +#define AR5K_EEPROM_N_PCDAC 11 +#define AR5K_EEPROM_N_PHASE_CAL 5 +#define AR5K_EEPROM_N_TEST_FREQ 8 +#define AR5K_EEPROM_N_EDGES 8 +#define AR5K_EEPROM_N_INTERCEPTS 11 +#define AR5K_EEPROM_FREQ_M(_v) AR5K_EEPROM_OFF(_v, 0x7f, 0xff) +#define AR5K_EEPROM_PCDAC_M 0x3f +#define AR5K_EEPROM_PCDAC_START 1 +#define AR5K_EEPROM_PCDAC_STOP 63 +#define AR5K_EEPROM_PCDAC_STEP 1 +#define AR5K_EEPROM_NON_EDGE_M 0x40 +#define AR5K_EEPROM_CHANNEL_POWER 8 +#define AR5K_EEPROM_N_OBDB 4 +#define AR5K_EEPROM_OBDB_DIS 0xffff +#define AR5K_EEPROM_CHANNEL_DIS 0xff +#define AR5K_EEPROM_SCALE_OC_DELTA(_x) (((_x) * 2) / 10) +#define AR5K_EEPROM_N_CTLS(_v) AR5K_EEPROM_OFF(_v, 16, 32) +#define AR5K_EEPROM_MAX_CTLS 32 +#define AR5K_EEPROM_N_PD_CURVES 4 +#define AR5K_EEPROM_N_XPD0_POINTS 4 +#define AR5K_EEPROM_N_XPD3_POINTS 3 +#define AR5K_EEPROM_N_PD_GAINS 4 +#define AR5K_EEPROM_N_PD_POINTS 5 +#define AR5K_EEPROM_N_INTERCEPT_10_2GHZ 35 +#define AR5K_EEPROM_N_INTERCEPT_10_5GHZ 55 +#define AR5K_EEPROM_POWER_M 0x3f +#define AR5K_EEPROM_POWER_MIN 0 +#define AR5K_EEPROM_POWER_MAX 3150 +#define AR5K_EEPROM_POWER_STEP 50 +#define AR5K_EEPROM_POWER_TABLE_SIZE 64 +#define AR5K_EEPROM_N_POWER_LOC_11B 4 +#define AR5K_EEPROM_N_POWER_LOC_11G 6 +#define AR5K_EEPROM_I_GAIN 10 +#define AR5K_EEPROM_CCK_OFDM_DELTA 15 +#define AR5K_EEPROM_N_IQ_CAL 2 +/* 5GHz/2GHz */ +enum ath5k_eeprom_freq_bands { + AR5K_EEPROM_BAND_5GHZ = 0, + AR5K_EEPROM_BAND_2GHZ = 1, + AR5K_EEPROM_N_FREQ_BANDS, +}; +/* Spur chans per freq band */ +#define AR5K_EEPROM_N_SPUR_CHANS 5 +/* fbin value for chan 2464 x2 */ +#define AR5K_EEPROM_5413_SPUR_CHAN_1 1640 +/* fbin value for chan 2420 x2 */ +#define AR5K_EEPROM_5413_SPUR_CHAN_2 1200 +#define AR5K_EEPROM_SPUR_CHAN_MASK 0x3FFF +#define AR5K_EEPROM_NO_SPUR 0x8000 +#define AR5K_SPUR_CHAN_WIDTH 87 +#define AR5K_SPUR_SYMBOL_WIDTH_BASE_100Hz 3125 +#define AR5K_SPUR_SYMBOL_WIDTH_TURBO_100Hz 6250 + +#define AR5K_EEPROM_READ(_o, _v) do { \ + if (!ath5k_hw_nvram_read(ah, (_o), &(_v))) \ + return -EIO; \ +} while (0) + +#define AR5K_EEPROM_READ_HDR(_o, _v) \ + AR5K_EEPROM_READ(_o, ah->ah_capabilities.cap_eeprom._v); \ + +enum ath5k_ant_table { + AR5K_ANT_CTL = 0, /* Idle switch table settings */ + AR5K_ANT_SWTABLE_A = 1, /* Switch table for antenna A */ + AR5K_ANT_SWTABLE_B = 2, /* Switch table for antenna B */ + AR5K_ANT_MAX, +}; + +enum ath5k_ctl_mode { + AR5K_CTL_11A = 0, + AR5K_CTL_11B = 1, + AR5K_CTL_11G = 2, + AR5K_CTL_TURBO = 3, + AR5K_CTL_TURBOG = 4, + AR5K_CTL_2GHT20 = 5, + AR5K_CTL_5GHT20 = 6, + AR5K_CTL_2GHT40 = 7, + AR5K_CTL_5GHT40 = 8, + AR5K_CTL_MODE_M = 15, +}; + +/* Per channel calibration data, used for power table setup */ +struct ath5k_chan_pcal_info_rf5111 { + /* Power levels in half dBm units + * for one power curve. */ + u8 pwr[AR5K_EEPROM_N_PWR_POINTS_5111]; + /* PCDAC table steps + * for the above values */ + u8 pcdac[AR5K_EEPROM_N_PWR_POINTS_5111]; + /* Starting PCDAC step */ + u8 pcdac_min; + /* Final PCDAC step */ + u8 pcdac_max; +}; + +struct ath5k_chan_pcal_info_rf5112 { + /* Power levels in quarter dBm units + * for lower (0) and higher (3) + * level curves in 0.25dB units */ + s8 pwr_x0[AR5K_EEPROM_N_XPD0_POINTS]; + s8 pwr_x3[AR5K_EEPROM_N_XPD3_POINTS]; + /* PCDAC table steps + * for the above values */ + u8 pcdac_x0[AR5K_EEPROM_N_XPD0_POINTS]; + u8 pcdac_x3[AR5K_EEPROM_N_XPD3_POINTS]; +}; + +struct ath5k_chan_pcal_info_rf2413 { + /* Starting pwr/pddac values */ + s8 pwr_i[AR5K_EEPROM_N_PD_GAINS]; + u8 pddac_i[AR5K_EEPROM_N_PD_GAINS]; + /* (pwr,pddac) points + * power levels in 0.5dB units */ + s8 pwr[AR5K_EEPROM_N_PD_GAINS] + [AR5K_EEPROM_N_PD_POINTS]; + u8 pddac[AR5K_EEPROM_N_PD_GAINS] + [AR5K_EEPROM_N_PD_POINTS]; +}; + +enum ath5k_powertable_type { + AR5K_PWRTABLE_PWR_TO_PCDAC = 0, + AR5K_PWRTABLE_LINEAR_PCDAC = 1, + AR5K_PWRTABLE_PWR_TO_PDADC = 2, +}; + +struct ath5k_pdgain_info { + u8 pd_points; + u8 *pd_step; + /* Power values are in + * 0.25dB units */ + s16 *pd_pwr; +}; + +struct ath5k_chan_pcal_info { + /* Frequency */ + u16 freq; + /* Tx power boundaries */ + s16 max_pwr; + s16 min_pwr; + union { + struct ath5k_chan_pcal_info_rf5111 rf5111_info; + struct ath5k_chan_pcal_info_rf5112 rf5112_info; + struct ath5k_chan_pcal_info_rf2413 rf2413_info; + }; + /* Raw values used by phy code + * Curves are stored in order from lower + * gain to higher gain (max txpower -> min txpower) */ + struct ath5k_pdgain_info *pd_curves; +}; + +/* Per rate calibration data for each mode, + * used for rate power table setup. + * Note: Values in 0.5dB units */ +struct ath5k_rate_pcal_info { + u16 freq; /* Frequency */ + /* Power level for 6-24Mbit/s rates or + * 1Mb rate */ + u16 target_power_6to24; + /* Power level for 36Mbit rate or + * 2Mb rate */ + u16 target_power_36; + /* Power level for 48Mbit rate or + * 5.5Mbit rate */ + u16 target_power_48; + /* Power level for 54Mbit rate or + * 11Mbit rate */ + u16 target_power_54; +}; + +/* Power edges for conformance test limits */ +struct ath5k_edge_power { + u16 freq; + u16 edge; /* in half dBm */ + bool flag; +}; + +/** + * struct ath5k_eeprom_info - EEPROM calibration data + * + * @ee_regdomain: ath/regd.c takes care of COUNTRY_ERD and WORLDWIDE_ROAMING + * flags + * @ee_ant_gain: Antenna gain in 0.5dB steps signed [5211 only?] + * @ee_cck_ofdm_gain_delta: difference in gainF to output the same power for + * OFDM and CCK packets + * @ee_cck_ofdm_power_delta: power difference between OFDM (6Mbps) and CCK + * (11Mbps) rate in G mode. 0.1dB steps + * @ee_scaled_cck_delta: for Japan Channel 14: 0.1dB resolution + * + * @ee_i_cal: Initial I coefficient to correct I/Q mismatch in the receive path + * @ee_q_cal: Initial Q coefficient to correct I/Q mismatch in the receive path + * @ee_fixed_bias: use ee_ob and ee_db settings or use automatic control + * @ee_switch_settling: RX/TX Switch settling time + * @ee_atn_tx_rx: Difference in attenuation between TX and RX in 1dB steps + * @ee_ant_control: Antenna Control Settings + * @ee_ob: Bias current for Output stage of PA + * B/G mode: Index [0] is used for AR2112/5112, otherwise [1] + * A mode: [0] 5.15-5.25 [1] 5.25-5.50 [2] 5.50-5.70 [3] 5.70-5.85 GHz + * @ee_db: Bias current for Output stage of PA. see @ee_ob + * @ee_tx_end2xlna_enable: Time difference from when BB finishes sending a frame + * to when the external LNA is activated + * @ee_tx_end2xpa_disable: Time difference from when BB finishes sending a frame + * to when the external PA switch is deactivated + * @ee_tx_frm2xpa_enable: Time difference from when MAC sends frame to when + * external PA switch is activated + * @ee_thr_62: Clear Channel Assessment (CCA) sensitivity + * (IEEE802.11a section 17.3.10.5 ) + * @ee_xlna_gain: Total gain of the LNA (information only) + * @ee_xpd: Use external (1) or internal power detector + * @ee_x_gain: Gain for external power detector output (differences in EEMAP + * versions!) + * @ee_i_gain: Initial gain value after reset + * @ee_margin_tx_rx: Margin in dB when final attenuation stage should be used + * + * @ee_false_detect: Backoff in Sensitivity (dB) on channels with spur signals + * @ee_noise_floor_thr: Noise floor threshold in 1dB steps + * @ee_adc_desired_size: Desired amplitude for ADC, used by AGC; in 0.5 dB steps + * @ee_pga_desired_size: Desired output of PGA (for BB gain) in 0.5 dB steps + * @ee_pd_gain_overlap: PD ADC curves need to overlap in 0.5dB steps (ee_map>=2) + */ +struct ath5k_eeprom_info { + + /* Header information */ + u16 ee_magic; + u16 ee_protect; + u16 ee_regdomain; + u16 ee_version; + u16 ee_header; + u16 ee_ant_gain; + u8 ee_rfkill_pin; + bool ee_rfkill_pol; + bool ee_is_hb63; + bool ee_serdes; + u16 ee_misc0; + u16 ee_misc1; + u16 ee_misc2; + u16 ee_misc3; + u16 ee_misc4; + u16 ee_misc5; + u16 ee_misc6; + u16 ee_cck_ofdm_gain_delta; + u16 ee_cck_ofdm_power_delta; + u16 ee_scaled_cck_delta; + + /* RF Calibration settings (reset, rfregs) */ + u16 ee_i_cal[AR5K_EEPROM_N_MODES]; + u16 ee_q_cal[AR5K_EEPROM_N_MODES]; + u16 ee_fixed_bias[AR5K_EEPROM_N_MODES]; + u16 ee_turbo_max_power[AR5K_EEPROM_N_MODES]; + u16 ee_xr_power[AR5K_EEPROM_N_MODES]; + u16 ee_switch_settling[AR5K_EEPROM_N_MODES]; + u16 ee_atn_tx_rx[AR5K_EEPROM_N_MODES]; + u16 ee_ant_control[AR5K_EEPROM_N_MODES][AR5K_EEPROM_N_PCDAC]; + u16 ee_ob[AR5K_EEPROM_N_MODES][AR5K_EEPROM_N_OBDB]; + u16 ee_db[AR5K_EEPROM_N_MODES][AR5K_EEPROM_N_OBDB]; + u16 ee_tx_end2xlna_enable[AR5K_EEPROM_N_MODES]; + u16 ee_tx_end2xpa_disable[AR5K_EEPROM_N_MODES]; + u16 ee_tx_frm2xpa_enable[AR5K_EEPROM_N_MODES]; + u16 ee_thr_62[AR5K_EEPROM_N_MODES]; + u16 ee_xlna_gain[AR5K_EEPROM_N_MODES]; + u16 ee_xpd[AR5K_EEPROM_N_MODES]; + u16 ee_x_gain[AR5K_EEPROM_N_MODES]; + u16 ee_i_gain[AR5K_EEPROM_N_MODES]; + u16 ee_margin_tx_rx[AR5K_EEPROM_N_MODES]; + u16 ee_switch_settling_turbo[AR5K_EEPROM_N_MODES]; + u16 ee_margin_tx_rx_turbo[AR5K_EEPROM_N_MODES]; + u16 ee_atn_tx_rx_turbo[AR5K_EEPROM_N_MODES]; + + /* Power calibration data */ + u16 ee_false_detect[AR5K_EEPROM_N_MODES]; + + /* Number of pd gain curves per mode */ + u8 ee_pd_gains[AR5K_EEPROM_N_MODES]; + /* Back mapping pdcurve number -> pdcurve index in pd->pd_curves */ + u8 ee_pdc_to_idx[AR5K_EEPROM_N_MODES][AR5K_EEPROM_N_PD_GAINS]; + + u8 ee_n_piers[AR5K_EEPROM_N_MODES]; + struct ath5k_chan_pcal_info ee_pwr_cal_a[AR5K_EEPROM_N_5GHZ_CHAN]; + struct ath5k_chan_pcal_info ee_pwr_cal_b[AR5K_EEPROM_N_2GHZ_CHAN_MAX]; + struct ath5k_chan_pcal_info ee_pwr_cal_g[AR5K_EEPROM_N_2GHZ_CHAN_MAX]; + + /* Per rate target power levels */ + u8 ee_rate_target_pwr_num[AR5K_EEPROM_N_MODES]; + struct ath5k_rate_pcal_info ee_rate_tpwr_a[AR5K_EEPROM_N_5GHZ_CHAN]; + struct ath5k_rate_pcal_info ee_rate_tpwr_b[AR5K_EEPROM_N_2GHZ_CHAN_MAX]; + struct ath5k_rate_pcal_info ee_rate_tpwr_g[AR5K_EEPROM_N_2GHZ_CHAN_MAX]; + + /* Conformance test limits (Unused) */ + u8 ee_ctls; + u8 ee_ctl[AR5K_EEPROM_MAX_CTLS]; + struct ath5k_edge_power ee_ctl_pwr[AR5K_EEPROM_N_EDGES * AR5K_EEPROM_MAX_CTLS]; + + /* Noise Floor Calibration settings */ + s16 ee_noise_floor_thr[AR5K_EEPROM_N_MODES]; + s8 ee_adc_desired_size[AR5K_EEPROM_N_MODES]; + s8 ee_pga_desired_size[AR5K_EEPROM_N_MODES]; + s8 ee_adc_desired_size_turbo[AR5K_EEPROM_N_MODES]; + s8 ee_pga_desired_size_turbo[AR5K_EEPROM_N_MODES]; + s8 ee_pd_gain_overlap; + + /* Spur mitigation data (fbin values for spur channels) */ + u16 ee_spur_chans[AR5K_EEPROM_N_SPUR_CHANS][AR5K_EEPROM_N_FREQ_BANDS]; + + /* Antenna raw switch tables */ + u32 ee_antenna[AR5K_EEPROM_N_MODES][AR5K_ANT_MAX]; +}; diff --git a/drivers/net/wireless/ath/ath5k/gpio.c b/drivers/net/wireless/ath/ath5k/gpio.c new file mode 100644 index 0000000..73d3dd8 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/gpio.c @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter + * Copyright (c) 2006-2008 Nick Kossifidis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/****************\ + GPIO Functions +\****************/ + +#include "ath5k.h" +#include "reg.h" +#include "debug.h" + + +/** + * DOC: GPIO/LED functions + * + * Here we control the 6 bidirectional GPIO pins provided by the hw. + * We can set a GPIO pin to be an input or an output pin on GPIO control + * register and then read or set its status from GPIO data input/output + * registers. + * + * We also control the two LED pins provided by the hw, LED_0 is our + * "power" LED and LED_1 is our "network activity" LED but many scenarios + * are available from hw. Vendors might also provide LEDs connected to the + * GPIO pins, we handle them through the LED subsystem on led.c + */ + + +/** + * ath5k_hw_set_ledstate() - Set led state + * @ah: The &struct ath5k_hw + * @state: One of AR5K_LED_* + * + * Used to set the LED blinking state. This only + * works for the LED connected to the LED_0, LED_1 pins, + * not the GPIO based. + */ +void +ath5k_hw_set_ledstate(struct ath5k_hw *ah, unsigned int state) +{ + u32 led; + /*5210 has different led mode handling*/ + u32 led_5210; + + /*Reset led status*/ + if (ah->ah_version != AR5K_AR5210) + AR5K_REG_DISABLE_BITS(ah, AR5K_PCICFG, + AR5K_PCICFG_LEDMODE | AR5K_PCICFG_LED); + else + AR5K_REG_DISABLE_BITS(ah, AR5K_PCICFG, AR5K_PCICFG_LED); + + /* + * Some blinking values, define at your wish + */ + switch (state) { + case AR5K_LED_SCAN: + case AR5K_LED_AUTH: + led = AR5K_PCICFG_LEDMODE_PROP | AR5K_PCICFG_LED_PEND; + led_5210 = AR5K_PCICFG_LED_PEND | AR5K_PCICFG_LED_BCTL; + break; + + case AR5K_LED_INIT: + led = AR5K_PCICFG_LEDMODE_PROP | AR5K_PCICFG_LED_NONE; + led_5210 = AR5K_PCICFG_LED_PEND; + break; + + case AR5K_LED_ASSOC: + case AR5K_LED_RUN: + led = AR5K_PCICFG_LEDMODE_PROP | AR5K_PCICFG_LED_ASSOC; + led_5210 = AR5K_PCICFG_LED_ASSOC; + break; + + default: + led = AR5K_PCICFG_LEDMODE_PROM | AR5K_PCICFG_LED_NONE; + led_5210 = AR5K_PCICFG_LED_PEND; + break; + } + + /*Write new status to the register*/ + if (ah->ah_version != AR5K_AR5210) + AR5K_REG_ENABLE_BITS(ah, AR5K_PCICFG, led); + else + AR5K_REG_ENABLE_BITS(ah, AR5K_PCICFG, led_5210); +} + +/** + * ath5k_hw_set_gpio_input() - Set GPIO inputs + * @ah: The &struct ath5k_hw + * @gpio: GPIO pin to set as input + */ +int +ath5k_hw_set_gpio_input(struct ath5k_hw *ah, u32 gpio) +{ + if (gpio >= AR5K_NUM_GPIO) + return -EINVAL; + + ath5k_hw_reg_write(ah, + (ath5k_hw_reg_read(ah, AR5K_GPIOCR) & ~AR5K_GPIOCR_OUT(gpio)) + | AR5K_GPIOCR_IN(gpio), AR5K_GPIOCR); + + return 0; +} + +/** + * ath5k_hw_set_gpio_output() - Set GPIO outputs + * @ah: The &struct ath5k_hw + * @gpio: The GPIO pin to set as output + */ +int +ath5k_hw_set_gpio_output(struct ath5k_hw *ah, u32 gpio) +{ + if (gpio >= AR5K_NUM_GPIO) + return -EINVAL; + + ath5k_hw_reg_write(ah, + (ath5k_hw_reg_read(ah, AR5K_GPIOCR) & ~AR5K_GPIOCR_OUT(gpio)) + | AR5K_GPIOCR_OUT(gpio), AR5K_GPIOCR); + + return 0; +} + +/** + * ath5k_hw_get_gpio() - Get GPIO state + * @ah: The &struct ath5k_hw + * @gpio: The GPIO pin to read + */ +u32 +ath5k_hw_get_gpio(struct ath5k_hw *ah, u32 gpio) +{ + if (gpio >= AR5K_NUM_GPIO) + return 0xffffffff; + + /* GPIO input magic */ + return ((ath5k_hw_reg_read(ah, AR5K_GPIODI) & AR5K_GPIODI_M) >> gpio) & + 0x1; +} + +/** + * ath5k_hw_set_gpio() - Set GPIO state + * @ah: The &struct ath5k_hw + * @gpio: The GPIO pin to set + * @val: Value to set (boolean) + */ +int +ath5k_hw_set_gpio(struct ath5k_hw *ah, u32 gpio, u32 val) +{ + u32 data; + + if (gpio >= AR5K_NUM_GPIO) + return -EINVAL; + + /* GPIO output magic */ + data = ath5k_hw_reg_read(ah, AR5K_GPIODO); + + data &= ~(1 << gpio); + data |= (val & 1) << gpio; + + ath5k_hw_reg_write(ah, data, AR5K_GPIODO); + + return 0; +} + +/** + * ath5k_hw_set_gpio_intr() - Initialize the GPIO interrupt (RFKill switch) + * @ah: The &struct ath5k_hw + * @gpio: The GPIO pin to use + * @interrupt_level: True to generate interrupt on active pin (high) + * + * This function is used to set up the GPIO interrupt for the hw RFKill switch. + * That switch is connected to a GPIO pin and it's number is stored on EEPROM. + * It can either open or close the circuit to indicate that we should disable + * RF/Wireless to save power (we also get that from EEPROM). + */ +void +ath5k_hw_set_gpio_intr(struct ath5k_hw *ah, unsigned int gpio, + u32 interrupt_level) +{ + u32 data; + + if (gpio >= AR5K_NUM_GPIO) + return; + + /* + * Set the GPIO interrupt + */ + data = (ath5k_hw_reg_read(ah, AR5K_GPIOCR) & + ~(AR5K_GPIOCR_INT_SEL(gpio) | AR5K_GPIOCR_INT_SELH | + AR5K_GPIOCR_INT_ENA | AR5K_GPIOCR_OUT(gpio))) | + (AR5K_GPIOCR_INT_SEL(gpio) | AR5K_GPIOCR_INT_ENA); + + ath5k_hw_reg_write(ah, interrupt_level ? data : + (data | AR5K_GPIOCR_INT_SELH), AR5K_GPIOCR); + + ah->ah_imr |= AR5K_IMR_GPIO; + + /* Enable GPIO interrupts */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PIMR, AR5K_IMR_GPIO); +} + diff --git a/drivers/net/wireless/ath/ath5k/initvals.c b/drivers/net/wireless/ath/ath5k/initvals.c new file mode 100644 index 0000000..ee1c2fa --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/initvals.c @@ -0,0 +1,1605 @@ +/* + * Initial register settings functions + * + * Copyright (c) 2004-2007 Reyk Floeter + * Copyright (c) 2006-2009 Nick Kossifidis + * Copyright (c) 2007-2008 Jiri Slaby + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "ath5k.h" +#include "reg.h" +#include "debug.h" + +/** + * struct ath5k_ini - Mode-independent initial register writes + * @ini_register: Register address + * @ini_value: Default value + * @ini_mode: 0 to write 1 to read (and clear) + */ +struct ath5k_ini { + u16 ini_register; + u32 ini_value; + + enum { + AR5K_INI_WRITE = 0, /* Default */ + AR5K_INI_READ = 1, + } ini_mode; +}; + +/** + * struct ath5k_ini_mode - Mode specific initial register values + * @mode_register: Register address + * @mode_value: Set of values for each enum ath5k_driver_mode + */ +struct ath5k_ini_mode { + u16 mode_register; + u32 mode_value[3]; +}; + +/* Initial register settings for AR5210 */ +static const struct ath5k_ini ar5210_ini[] = { + /* PCU and MAC registers */ + { AR5K_NOQCU_TXDP0, 0 }, + { AR5K_NOQCU_TXDP1, 0 }, + { AR5K_RXDP, 0 }, + { AR5K_CR, 0 }, + { AR5K_ISR, 0, AR5K_INI_READ }, + { AR5K_IMR, 0 }, + { AR5K_IER, AR5K_IER_DISABLE }, + { AR5K_BSR, 0, AR5K_INI_READ }, + { AR5K_TXCFG, AR5K_DMASIZE_128B }, + { AR5K_RXCFG, AR5K_DMASIZE_128B }, + { AR5K_CFG, AR5K_INIT_CFG }, + { AR5K_TOPS, 8 }, + { AR5K_RXNOFRM, 8 }, + { AR5K_RPGTO, 0 }, + { AR5K_TXNOFRM, 0 }, + { AR5K_SFR, 0 }, + { AR5K_MIBC, 0 }, + { AR5K_MISC, 0 }, + { AR5K_RX_FILTER_5210, 0 }, + { AR5K_MCAST_FILTER0_5210, 0 }, + { AR5K_MCAST_FILTER1_5210, 0 }, + { AR5K_TX_MASK0, 0 }, + { AR5K_TX_MASK1, 0 }, + { AR5K_CLR_TMASK, 0 }, + { AR5K_TRIG_LVL, AR5K_TUNE_MIN_TX_FIFO_THRES }, + { AR5K_DIAG_SW_5210, 0 }, + { AR5K_RSSI_THR, AR5K_TUNE_RSSI_THRES }, + { AR5K_TSF_L32_5210, 0 }, + { AR5K_TIMER0_5210, 0 }, + { AR5K_TIMER1_5210, 0xffffffff }, + { AR5K_TIMER2_5210, 0xffffffff }, + { AR5K_TIMER3_5210, 1 }, + { AR5K_CFP_DUR_5210, 0 }, + { AR5K_CFP_PERIOD_5210, 0 }, + /* PHY registers */ + { AR5K_PHY(0), 0x00000047 }, + { AR5K_PHY_AGC, 0x00000000 }, + { AR5K_PHY(3), 0x09848ea6 }, + { AR5K_PHY(4), 0x3d32e000 }, + { AR5K_PHY(5), 0x0000076b }, + { AR5K_PHY_ACT, AR5K_PHY_ACT_DISABLE }, + { AR5K_PHY(8), 0x02020200 }, + { AR5K_PHY(9), 0x00000e0e }, + { AR5K_PHY(10), 0x0a020201 }, + { AR5K_PHY(11), 0x00036ffc }, + { AR5K_PHY(12), 0x00000000 }, + { AR5K_PHY(13), 0x00000e0e }, + { AR5K_PHY(14), 0x00000007 }, + { AR5K_PHY(15), 0x00020100 }, + { AR5K_PHY(16), 0x89630000 }, + { AR5K_PHY(17), 0x1372169c }, + { AR5K_PHY(18), 0x0018b633 }, + { AR5K_PHY(19), 0x1284613c }, + { AR5K_PHY(20), 0x0de8b8e0 }, + { AR5K_PHY(21), 0x00074859 }, + { AR5K_PHY(22), 0x7e80beba }, + { AR5K_PHY(23), 0x313a665e }, + { AR5K_PHY_AGCCTL, 0x00001d08 }, + { AR5K_PHY(25), 0x0001ce00 }, + { AR5K_PHY(26), 0x409a4190 }, + { AR5K_PHY(28), 0x0000000f }, + { AR5K_PHY(29), 0x00000080 }, + { AR5K_PHY(30), 0x00000004 }, + { AR5K_PHY(31), 0x00000018 }, /* 0x987c */ + { AR5K_PHY(64), 0x00000000 }, /* 0x9900 */ + { AR5K_PHY(65), 0x00000000 }, + { AR5K_PHY(66), 0x00000000 }, + { AR5K_PHY(67), 0x00800000 }, + { AR5K_PHY(68), 0x00000003 }, + /* BB gain table (64bytes) */ + { AR5K_BB_GAIN(0), 0x00000000 }, + { AR5K_BB_GAIN(1), 0x00000020 }, + { AR5K_BB_GAIN(2), 0x00000010 }, + { AR5K_BB_GAIN(3), 0x00000030 }, + { AR5K_BB_GAIN(4), 0x00000008 }, + { AR5K_BB_GAIN(5), 0x00000028 }, + { AR5K_BB_GAIN(6), 0x00000028 }, + { AR5K_BB_GAIN(7), 0x00000004 }, + { AR5K_BB_GAIN(8), 0x00000024 }, + { AR5K_BB_GAIN(9), 0x00000014 }, + { AR5K_BB_GAIN(10), 0x00000034 }, + { AR5K_BB_GAIN(11), 0x0000000c }, + { AR5K_BB_GAIN(12), 0x0000002c }, + { AR5K_BB_GAIN(13), 0x00000002 }, + { AR5K_BB_GAIN(14), 0x00000022 }, + { AR5K_BB_GAIN(15), 0x00000012 }, + { AR5K_BB_GAIN(16), 0x00000032 }, + { AR5K_BB_GAIN(17), 0x0000000a }, + { AR5K_BB_GAIN(18), 0x0000002a }, + { AR5K_BB_GAIN(19), 0x00000001 }, + { AR5K_BB_GAIN(20), 0x00000021 }, + { AR5K_BB_GAIN(21), 0x00000011 }, + { AR5K_BB_GAIN(22), 0x00000031 }, + { AR5K_BB_GAIN(23), 0x00000009 }, + { AR5K_BB_GAIN(24), 0x00000029 }, + { AR5K_BB_GAIN(25), 0x00000005 }, + { AR5K_BB_GAIN(26), 0x00000025 }, + { AR5K_BB_GAIN(27), 0x00000015 }, + { AR5K_BB_GAIN(28), 0x00000035 }, + { AR5K_BB_GAIN(29), 0x0000000d }, + { AR5K_BB_GAIN(30), 0x0000002d }, + { AR5K_BB_GAIN(31), 0x00000003 }, + { AR5K_BB_GAIN(32), 0x00000023 }, + { AR5K_BB_GAIN(33), 0x00000013 }, + { AR5K_BB_GAIN(34), 0x00000033 }, + { AR5K_BB_GAIN(35), 0x0000000b }, + { AR5K_BB_GAIN(36), 0x0000002b }, + { AR5K_BB_GAIN(37), 0x00000007 }, + { AR5K_BB_GAIN(38), 0x00000027 }, + { AR5K_BB_GAIN(39), 0x00000017 }, + { AR5K_BB_GAIN(40), 0x00000037 }, + { AR5K_BB_GAIN(41), 0x0000000f }, + { AR5K_BB_GAIN(42), 0x0000002f }, + { AR5K_BB_GAIN(43), 0x0000002f }, + { AR5K_BB_GAIN(44), 0x0000002f }, + { AR5K_BB_GAIN(45), 0x0000002f }, + { AR5K_BB_GAIN(46), 0x0000002f }, + { AR5K_BB_GAIN(47), 0x0000002f }, + { AR5K_BB_GAIN(48), 0x0000002f }, + { AR5K_BB_GAIN(49), 0x0000002f }, + { AR5K_BB_GAIN(50), 0x0000002f }, + { AR5K_BB_GAIN(51), 0x0000002f }, + { AR5K_BB_GAIN(52), 0x0000002f }, + { AR5K_BB_GAIN(53), 0x0000002f }, + { AR5K_BB_GAIN(54), 0x0000002f }, + { AR5K_BB_GAIN(55), 0x0000002f }, + { AR5K_BB_GAIN(56), 0x0000002f }, + { AR5K_BB_GAIN(57), 0x0000002f }, + { AR5K_BB_GAIN(58), 0x0000002f }, + { AR5K_BB_GAIN(59), 0x0000002f }, + { AR5K_BB_GAIN(60), 0x0000002f }, + { AR5K_BB_GAIN(61), 0x0000002f }, + { AR5K_BB_GAIN(62), 0x0000002f }, + { AR5K_BB_GAIN(63), 0x0000002f }, + /* 5110 RF gain table (64btes) */ + { AR5K_RF_GAIN(0), 0x0000001d }, + { AR5K_RF_GAIN(1), 0x0000005d }, + { AR5K_RF_GAIN(2), 0x0000009d }, + { AR5K_RF_GAIN(3), 0x000000dd }, + { AR5K_RF_GAIN(4), 0x0000011d }, + { AR5K_RF_GAIN(5), 0x00000021 }, + { AR5K_RF_GAIN(6), 0x00000061 }, + { AR5K_RF_GAIN(7), 0x000000a1 }, + { AR5K_RF_GAIN(8), 0x000000e1 }, + { AR5K_RF_GAIN(9), 0x00000031 }, + { AR5K_RF_GAIN(10), 0x00000071 }, + { AR5K_RF_GAIN(11), 0x000000b1 }, + { AR5K_RF_GAIN(12), 0x0000001c }, + { AR5K_RF_GAIN(13), 0x0000005c }, + { AR5K_RF_GAIN(14), 0x00000029 }, + { AR5K_RF_GAIN(15), 0x00000069 }, + { AR5K_RF_GAIN(16), 0x000000a9 }, + { AR5K_RF_GAIN(17), 0x00000020 }, + { AR5K_RF_GAIN(18), 0x00000019 }, + { AR5K_RF_GAIN(19), 0x00000059 }, + { AR5K_RF_GAIN(20), 0x00000099 }, + { AR5K_RF_GAIN(21), 0x00000030 }, + { AR5K_RF_GAIN(22), 0x00000005 }, + { AR5K_RF_GAIN(23), 0x00000025 }, + { AR5K_RF_GAIN(24), 0x00000065 }, + { AR5K_RF_GAIN(25), 0x000000a5 }, + { AR5K_RF_GAIN(26), 0x00000028 }, + { AR5K_RF_GAIN(27), 0x00000068 }, + { AR5K_RF_GAIN(28), 0x0000001f }, + { AR5K_RF_GAIN(29), 0x0000001e }, + { AR5K_RF_GAIN(30), 0x00000018 }, + { AR5K_RF_GAIN(31), 0x00000058 }, + { AR5K_RF_GAIN(32), 0x00000098 }, + { AR5K_RF_GAIN(33), 0x00000003 }, + { AR5K_RF_GAIN(34), 0x00000004 }, + { AR5K_RF_GAIN(35), 0x00000044 }, + { AR5K_RF_GAIN(36), 0x00000084 }, + { AR5K_RF_GAIN(37), 0x00000013 }, + { AR5K_RF_GAIN(38), 0x00000012 }, + { AR5K_RF_GAIN(39), 0x00000052 }, + { AR5K_RF_GAIN(40), 0x00000092 }, + { AR5K_RF_GAIN(41), 0x000000d2 }, + { AR5K_RF_GAIN(42), 0x0000002b }, + { AR5K_RF_GAIN(43), 0x0000002a }, + { AR5K_RF_GAIN(44), 0x0000006a }, + { AR5K_RF_GAIN(45), 0x000000aa }, + { AR5K_RF_GAIN(46), 0x0000001b }, + { AR5K_RF_GAIN(47), 0x0000001a }, + { AR5K_RF_GAIN(48), 0x0000005a }, + { AR5K_RF_GAIN(49), 0x0000009a }, + { AR5K_RF_GAIN(50), 0x000000da }, + { AR5K_RF_GAIN(51), 0x00000006 }, + { AR5K_RF_GAIN(52), 0x00000006 }, + { AR5K_RF_GAIN(53), 0x00000006 }, + { AR5K_RF_GAIN(54), 0x00000006 }, + { AR5K_RF_GAIN(55), 0x00000006 }, + { AR5K_RF_GAIN(56), 0x00000006 }, + { AR5K_RF_GAIN(57), 0x00000006 }, + { AR5K_RF_GAIN(58), 0x00000006 }, + { AR5K_RF_GAIN(59), 0x00000006 }, + { AR5K_RF_GAIN(60), 0x00000006 }, + { AR5K_RF_GAIN(61), 0x00000006 }, + { AR5K_RF_GAIN(62), 0x00000006 }, + { AR5K_RF_GAIN(63), 0x00000006 }, + /* PHY activation */ + { AR5K_PHY(53), 0x00000020 }, + { AR5K_PHY(51), 0x00000004 }, + { AR5K_PHY(50), 0x00060106 }, + { AR5K_PHY(39), 0x0000006d }, + { AR5K_PHY(48), 0x00000000 }, + { AR5K_PHY(52), 0x00000014 }, + { AR5K_PHY_ACT, AR5K_PHY_ACT_ENABLE }, +}; + +/* Initial register settings for AR5211 */ +static const struct ath5k_ini ar5211_ini[] = { + { AR5K_RXDP, 0x00000000 }, + { AR5K_RTSD0, 0x84849c9c }, + { AR5K_RTSD1, 0x7c7c7c7c }, + { AR5K_RXCFG, 0x00000005 }, + { AR5K_MIBC, 0x00000000 }, + { AR5K_TOPS, 0x00000008 }, + { AR5K_RXNOFRM, 0x00000008 }, + { AR5K_TXNOFRM, 0x00000010 }, + { AR5K_RPGTO, 0x00000000 }, + { AR5K_RFCNT, 0x0000001f }, + { AR5K_QUEUE_TXDP(0), 0x00000000 }, + { AR5K_QUEUE_TXDP(1), 0x00000000 }, + { AR5K_QUEUE_TXDP(2), 0x00000000 }, + { AR5K_QUEUE_TXDP(3), 0x00000000 }, + { AR5K_QUEUE_TXDP(4), 0x00000000 }, + { AR5K_QUEUE_TXDP(5), 0x00000000 }, + { AR5K_QUEUE_TXDP(6), 0x00000000 }, + { AR5K_QUEUE_TXDP(7), 0x00000000 }, + { AR5K_QUEUE_TXDP(8), 0x00000000 }, + { AR5K_QUEUE_TXDP(9), 0x00000000 }, + { AR5K_DCU_FP, 0x00000000 }, + { AR5K_STA_ID1, 0x00000000 }, + { AR5K_BSS_ID0, 0x00000000 }, + { AR5K_BSS_ID1, 0x00000000 }, + { AR5K_RSSI_THR, 0x00000000 }, + { AR5K_CFP_PERIOD_5211, 0x00000000 }, + { AR5K_TIMER0_5211, 0x00000030 }, + { AR5K_TIMER1_5211, 0x0007ffff }, + { AR5K_TIMER2_5211, 0x01ffffff }, + { AR5K_TIMER3_5211, 0x00000031 }, + { AR5K_CFP_DUR_5211, 0x00000000 }, + { AR5K_RX_FILTER_5211, 0x00000000 }, + { AR5K_MCAST_FILTER0_5211, 0x00000000 }, + { AR5K_MCAST_FILTER1_5211, 0x00000002 }, + { AR5K_DIAG_SW_5211, 0x00000000 }, + { AR5K_ADDAC_TEST, 0x00000000 }, + { AR5K_DEFAULT_ANTENNA, 0x00000000 }, + /* PHY registers */ + { AR5K_PHY_AGC, 0x00000000 }, + { AR5K_PHY(3), 0x2d849093 }, + { AR5K_PHY(4), 0x7d32e000 }, + { AR5K_PHY(5), 0x00000f6b }, + { AR5K_PHY_ACT, 0x00000000 }, + { AR5K_PHY(11), 0x00026ffe }, + { AR5K_PHY(12), 0x00000000 }, + { AR5K_PHY(15), 0x00020100 }, + { AR5K_PHY(16), 0x206a017a }, + { AR5K_PHY(19), 0x1284613c }, + { AR5K_PHY(21), 0x00000859 }, + { AR5K_PHY(26), 0x409a4190 }, /* 0x9868 */ + { AR5K_PHY(27), 0x050cb081 }, + { AR5K_PHY(28), 0x0000000f }, + { AR5K_PHY(29), 0x00000080 }, + { AR5K_PHY(30), 0x0000000c }, + { AR5K_PHY(64), 0x00000000 }, + { AR5K_PHY(65), 0x00000000 }, + { AR5K_PHY(66), 0x00000000 }, + { AR5K_PHY(67), 0x00800000 }, + { AR5K_PHY(68), 0x00000001 }, + { AR5K_PHY(71), 0x0000092a }, + { AR5K_PHY_IQ, 0x00000000 }, + { AR5K_PHY(73), 0x00058a05 }, + { AR5K_PHY(74), 0x00000001 }, + { AR5K_PHY(75), 0x00000000 }, + { AR5K_PHY_PAPD_PROBE, 0x00000000 }, + { AR5K_PHY(77), 0x00000000 }, /* 0x9934 */ + { AR5K_PHY(78), 0x00000000 }, /* 0x9938 */ + { AR5K_PHY(79), 0x0000003f }, /* 0x993c */ + { AR5K_PHY(80), 0x00000004 }, + { AR5K_PHY(82), 0x00000000 }, + { AR5K_PHY(83), 0x00000000 }, + { AR5K_PHY(84), 0x00000000 }, + { AR5K_PHY_RADAR, 0x5d50f14c }, + { AR5K_PHY(86), 0x00000018 }, + { AR5K_PHY(87), 0x004b6a8e }, + /* Initial Power table (32bytes) + * common on all cards/modes. + * Note: Table is rewritten during + * txpower setup later using calibration + * data etc. so next write is non-common */ + { AR5K_PHY_PCDAC_TXPOWER(1), 0x06ff05ff }, + { AR5K_PHY_PCDAC_TXPOWER(2), 0x07ff07ff }, + { AR5K_PHY_PCDAC_TXPOWER(3), 0x08ff08ff }, + { AR5K_PHY_PCDAC_TXPOWER(4), 0x09ff09ff }, + { AR5K_PHY_PCDAC_TXPOWER(5), 0x0aff0aff }, + { AR5K_PHY_PCDAC_TXPOWER(6), 0x0bff0bff }, + { AR5K_PHY_PCDAC_TXPOWER(7), 0x0cff0cff }, + { AR5K_PHY_PCDAC_TXPOWER(8), 0x0dff0dff }, + { AR5K_PHY_PCDAC_TXPOWER(9), 0x0fff0eff }, + { AR5K_PHY_PCDAC_TXPOWER(10), 0x12ff12ff }, + { AR5K_PHY_PCDAC_TXPOWER(11), 0x14ff13ff }, + { AR5K_PHY_PCDAC_TXPOWER(12), 0x16ff15ff }, + { AR5K_PHY_PCDAC_TXPOWER(13), 0x19ff17ff }, + { AR5K_PHY_PCDAC_TXPOWER(14), 0x1bff1aff }, + { AR5K_PHY_PCDAC_TXPOWER(15), 0x1eff1dff }, + { AR5K_PHY_PCDAC_TXPOWER(16), 0x23ff20ff }, + { AR5K_PHY_PCDAC_TXPOWER(17), 0x27ff25ff }, + { AR5K_PHY_PCDAC_TXPOWER(18), 0x2cff29ff }, + { AR5K_PHY_PCDAC_TXPOWER(19), 0x31ff2fff }, + { AR5K_PHY_PCDAC_TXPOWER(20), 0x37ff34ff }, + { AR5K_PHY_PCDAC_TXPOWER(21), 0x3aff3aff }, + { AR5K_PHY_PCDAC_TXPOWER(22), 0x3aff3aff }, + { AR5K_PHY_PCDAC_TXPOWER(23), 0x3aff3aff }, + { AR5K_PHY_PCDAC_TXPOWER(24), 0x3aff3aff }, + { AR5K_PHY_PCDAC_TXPOWER(25), 0x3aff3aff }, + { AR5K_PHY_PCDAC_TXPOWER(26), 0x3aff3aff }, + { AR5K_PHY_PCDAC_TXPOWER(27), 0x3aff3aff }, + { AR5K_PHY_PCDAC_TXPOWER(28), 0x3aff3aff }, + { AR5K_PHY_PCDAC_TXPOWER(29), 0x3aff3aff }, + { AR5K_PHY_PCDAC_TXPOWER(30), 0x3aff3aff }, + { AR5K_PHY_PCDAC_TXPOWER(31), 0x3aff3aff }, + { AR5K_PHY_CCKTXCTL, 0x00000000 }, + { AR5K_PHY(642), 0x503e4646 }, + { AR5K_PHY_GAIN_2GHZ, 0x6480416c }, + { AR5K_PHY(644), 0x0199a003 }, + { AR5K_PHY(645), 0x044cd610 }, + { AR5K_PHY(646), 0x13800040 }, + { AR5K_PHY(647), 0x1be00060 }, + { AR5K_PHY(648), 0x0c53800a }, + { AR5K_PHY(649), 0x0014df3b }, + { AR5K_PHY(650), 0x000001b5 }, + { AR5K_PHY(651), 0x00000020 }, +}; + +/* Initial mode-specific settings for AR5211 + * 5211 supports OFDM-only g (draft g) but we + * need to test it ! */ +static const struct ath5k_ini_mode ar5211_ini_mode[] = { + { AR5K_TXCFG, + /* A B G */ + { 0x00000015, 0x0000001d, 0x00000015 } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(0), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(1), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(2), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(3), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(4), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(5), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(6), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(7), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(8), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(9), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_DCU_GBL_IFS_SLOT, + { 0x00000168, 0x000001b8, 0x00000168 } }, + { AR5K_DCU_GBL_IFS_SIFS, + { 0x00000230, 0x000000b0, 0x00000230 } }, + { AR5K_DCU_GBL_IFS_EIFS, + { 0x00000d98, 0x00001f48, 0x00000d98 } }, + { AR5K_DCU_GBL_IFS_MISC, + { 0x0000a0e0, 0x00005880, 0x0000a0e0 } }, + { AR5K_TIME_OUT, + { 0x04000400, 0x20003000, 0x04000400 } }, + { AR5K_USEC_5211, + { 0x0e8d8fa7, 0x01608f95, 0x0e8d8fa7 } }, + { AR5K_PHY(8), + { 0x02020200, 0x02010200, 0x02020200 } }, + { AR5K_PHY_RF_CTL2, + { 0x00000e0e, 0x00000707, 0x00000e0e } }, + { AR5K_PHY_RF_CTL3, + { 0x0a020001, 0x05010000, 0x0a020001 } }, + { AR5K_PHY_RF_CTL4, + { 0x00000e0e, 0x00000e0e, 0x00000e0e } }, + { AR5K_PHY_PA_CTL, + { 0x00000007, 0x0000000b, 0x0000000b } }, + { AR5K_PHY_SETTLING, + { 0x1372169c, 0x137216a8, 0x1372169c } }, + { AR5K_PHY_GAIN, + { 0x0018ba67, 0x0018ba69, 0x0018ba69 } }, + { AR5K_PHY_DESIRED_SIZE, + { 0x0c28b4e0, 0x0c28b4e0, 0x0c28b4e0 } }, + { AR5K_PHY_SIG, + { 0x7e800d2e, 0x7ec00d2e, 0x7e800d2e } }, + { AR5K_PHY_AGCCOARSE, + { 0x31375d5e, 0x313a5d5e, 0x31375d5e } }, + { AR5K_PHY_AGCCTL, + { 0x0000bd10, 0x0000bd38, 0x0000bd10 } }, + { AR5K_PHY_NF, + { 0x0001ce00, 0x0001ce00, 0x0001ce00 } }, + { AR5K_PHY_RX_DELAY, + { 0x00002710, 0x0000157c, 0x00002710 } }, + { AR5K_PHY(70), + { 0x00000190, 0x00000084, 0x00000190 } }, + { AR5K_PHY_FRAME_CTL_5211, + { 0x6fe01020, 0x6fe00920, 0x6fe01020 } }, + { AR5K_PHY_PCDAC_TXPOWER_BASE, + { 0x05ff14ff, 0x05ff14ff, 0x05ff19ff } }, + { AR5K_RF_BUFFER_CONTROL_4, + { 0x00000010, 0x00000010, 0x00000010 } }, +}; + +/* Initial register settings for AR5212 and newer chips */ +static const struct ath5k_ini ar5212_ini_common_start[] = { + { AR5K_RXDP, 0x00000000 }, + { AR5K_RXCFG, 0x00000005 }, + { AR5K_MIBC, 0x00000000 }, + { AR5K_TOPS, 0x00000008 }, + { AR5K_RXNOFRM, 0x00000008 }, + { AR5K_TXNOFRM, 0x00000010 }, + { AR5K_RPGTO, 0x00000000 }, + { AR5K_RFCNT, 0x0000001f }, + { AR5K_QUEUE_TXDP(0), 0x00000000 }, + { AR5K_QUEUE_TXDP(1), 0x00000000 }, + { AR5K_QUEUE_TXDP(2), 0x00000000 }, + { AR5K_QUEUE_TXDP(3), 0x00000000 }, + { AR5K_QUEUE_TXDP(4), 0x00000000 }, + { AR5K_QUEUE_TXDP(5), 0x00000000 }, + { AR5K_QUEUE_TXDP(6), 0x00000000 }, + { AR5K_QUEUE_TXDP(7), 0x00000000 }, + { AR5K_QUEUE_TXDP(8), 0x00000000 }, + { AR5K_QUEUE_TXDP(9), 0x00000000 }, + { AR5K_DCU_FP, 0x00000000 }, + { AR5K_DCU_TXP, 0x00000000 }, + /* Tx filter table 0 (32 entries) */ + { AR5K_DCU_TX_FILTER_0(0), 0x00000000 }, /* DCU 0 */ + { AR5K_DCU_TX_FILTER_0(1), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(2), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(3), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(4), 0x00000000 }, /* DCU 1 */ + { AR5K_DCU_TX_FILTER_0(5), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(6), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(7), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(8), 0x00000000 }, /* DCU 2 */ + { AR5K_DCU_TX_FILTER_0(9), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(10), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(11), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(12), 0x00000000 }, /* DCU 3 */ + { AR5K_DCU_TX_FILTER_0(13), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(14), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(15), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(16), 0x00000000 }, /* DCU 4 */ + { AR5K_DCU_TX_FILTER_0(17), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(18), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(19), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(20), 0x00000000 }, /* DCU 5 */ + { AR5K_DCU_TX_FILTER_0(21), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(22), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(23), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(24), 0x00000000 }, /* DCU 6 */ + { AR5K_DCU_TX_FILTER_0(25), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(26), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(27), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(28), 0x00000000 }, /* DCU 7 */ + { AR5K_DCU_TX_FILTER_0(29), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(30), 0x00000000 }, + { AR5K_DCU_TX_FILTER_0(31), 0x00000000 }, + /* Tx filter table 1 (16 entries) */ + { AR5K_DCU_TX_FILTER_1(0), 0x00000000 }, + { AR5K_DCU_TX_FILTER_1(1), 0x00000000 }, + { AR5K_DCU_TX_FILTER_1(2), 0x00000000 }, + { AR5K_DCU_TX_FILTER_1(3), 0x00000000 }, + { AR5K_DCU_TX_FILTER_1(4), 0x00000000 }, + { AR5K_DCU_TX_FILTER_1(5), 0x00000000 }, + { AR5K_DCU_TX_FILTER_1(6), 0x00000000 }, + { AR5K_DCU_TX_FILTER_1(7), 0x00000000 }, + { AR5K_DCU_TX_FILTER_1(8), 0x00000000 }, + { AR5K_DCU_TX_FILTER_1(9), 0x00000000 }, + { AR5K_DCU_TX_FILTER_1(10), 0x00000000 }, + { AR5K_DCU_TX_FILTER_1(11), 0x00000000 }, + { AR5K_DCU_TX_FILTER_1(12), 0x00000000 }, + { AR5K_DCU_TX_FILTER_1(13), 0x00000000 }, + { AR5K_DCU_TX_FILTER_1(14), 0x00000000 }, + { AR5K_DCU_TX_FILTER_1(15), 0x00000000 }, + { AR5K_DCU_TX_FILTER_CLR, 0x00000000 }, + { AR5K_DCU_TX_FILTER_SET, 0x00000000 }, + { AR5K_STA_ID1, 0x00000000 }, + { AR5K_BSS_ID0, 0x00000000 }, + { AR5K_BSS_ID1, 0x00000000 }, + { AR5K_BEACON_5211, 0x00000000 }, + { AR5K_CFP_PERIOD_5211, 0x00000000 }, + { AR5K_TIMER0_5211, 0x00000030 }, + { AR5K_TIMER1_5211, 0x0007ffff }, + { AR5K_TIMER2_5211, 0x01ffffff }, + { AR5K_TIMER3_5211, 0x00000031 }, + { AR5K_CFP_DUR_5211, 0x00000000 }, + { AR5K_RX_FILTER_5211, 0x00000000 }, + { AR5K_DIAG_SW_5211, 0x00000000 }, + { AR5K_ADDAC_TEST, 0x00000000 }, + { AR5K_DEFAULT_ANTENNA, 0x00000000 }, + { AR5K_FRAME_CTL_QOSM, 0x000fc78f }, + { AR5K_XRMODE, 0x2a82301a }, + { AR5K_XRDELAY, 0x05dc01e0 }, + { AR5K_XRTIMEOUT, 0x1f402710 }, + { AR5K_XRCHIRP, 0x01f40000 }, + { AR5K_XRSTOMP, 0x00001e1c }, + { AR5K_SLEEP0, 0x0002aaaa }, + { AR5K_SLEEP1, 0x02005555 }, + { AR5K_SLEEP2, 0x00000000 }, + { AR_BSSMSKL, 0xffffffff }, + { AR_BSSMSKU, 0x0000ffff }, + { AR5K_TXPC, 0x00000000 }, + { AR5K_PROFCNT_TX, 0x00000000 }, + { AR5K_PROFCNT_RX, 0x00000000 }, + { AR5K_PROFCNT_RXCLR, 0x00000000 }, + { AR5K_PROFCNT_CYCLE, 0x00000000 }, + { AR5K_QUIET_CTL1, 0x00000088 }, + /* Initial rate duration table (32 entries )*/ + { AR5K_RATE_DUR(0), 0x00000000 }, + { AR5K_RATE_DUR(1), 0x0000008c }, + { AR5K_RATE_DUR(2), 0x000000e4 }, + { AR5K_RATE_DUR(3), 0x000002d5 }, + { AR5K_RATE_DUR(4), 0x00000000 }, + { AR5K_RATE_DUR(5), 0x00000000 }, + { AR5K_RATE_DUR(6), 0x000000a0 }, + { AR5K_RATE_DUR(7), 0x000001c9 }, + { AR5K_RATE_DUR(8), 0x0000002c }, + { AR5K_RATE_DUR(9), 0x0000002c }, + { AR5K_RATE_DUR(10), 0x00000030 }, + { AR5K_RATE_DUR(11), 0x0000003c }, + { AR5K_RATE_DUR(12), 0x0000002c }, + { AR5K_RATE_DUR(13), 0x0000002c }, + { AR5K_RATE_DUR(14), 0x00000030 }, + { AR5K_RATE_DUR(15), 0x0000003c }, + { AR5K_RATE_DUR(16), 0x00000000 }, + { AR5K_RATE_DUR(17), 0x00000000 }, + { AR5K_RATE_DUR(18), 0x00000000 }, + { AR5K_RATE_DUR(19), 0x00000000 }, + { AR5K_RATE_DUR(20), 0x00000000 }, + { AR5K_RATE_DUR(21), 0x00000000 }, + { AR5K_RATE_DUR(22), 0x00000000 }, + { AR5K_RATE_DUR(23), 0x00000000 }, + { AR5K_RATE_DUR(24), 0x000000d5 }, + { AR5K_RATE_DUR(25), 0x000000df }, + { AR5K_RATE_DUR(26), 0x00000102 }, + { AR5K_RATE_DUR(27), 0x0000013a }, + { AR5K_RATE_DUR(28), 0x00000075 }, + { AR5K_RATE_DUR(29), 0x0000007f }, + { AR5K_RATE_DUR(30), 0x000000a2 }, + { AR5K_RATE_DUR(31), 0x00000000 }, + { AR5K_QUIET_CTL2, 0x00010002 }, + { AR5K_TSF_PARM, 0x00000001 }, + { AR5K_QOS_NOACK, 0x000000c0 }, + { AR5K_PHY_ERR_FIL, 0x00000000 }, + { AR5K_XRLAT_TX, 0x00000168 }, + { AR5K_ACKSIFS, 0x00000000 }, + /* Rate -> db table + * notice ...03<-02<-01<-00 ! */ + { AR5K_RATE2DB(0), 0x03020100 }, + { AR5K_RATE2DB(1), 0x07060504 }, + { AR5K_RATE2DB(2), 0x0b0a0908 }, + { AR5K_RATE2DB(3), 0x0f0e0d0c }, + { AR5K_RATE2DB(4), 0x13121110 }, + { AR5K_RATE2DB(5), 0x17161514 }, + { AR5K_RATE2DB(6), 0x1b1a1918 }, + { AR5K_RATE2DB(7), 0x1f1e1d1c }, + /* Db -> Rate table */ + { AR5K_DB2RATE(0), 0x03020100 }, + { AR5K_DB2RATE(1), 0x07060504 }, + { AR5K_DB2RATE(2), 0x0b0a0908 }, + { AR5K_DB2RATE(3), 0x0f0e0d0c }, + { AR5K_DB2RATE(4), 0x13121110 }, + { AR5K_DB2RATE(5), 0x17161514 }, + { AR5K_DB2RATE(6), 0x1b1a1918 }, + { AR5K_DB2RATE(7), 0x1f1e1d1c }, + /* PHY registers (Common settings + * for all chips/modes) */ + { AR5K_PHY(3), 0xad848e19 }, + { AR5K_PHY(4), 0x7d28e000 }, + { AR5K_PHY_TIMING_3, 0x9c0a9f6b }, + { AR5K_PHY_ACT, 0x00000000 }, + { AR5K_PHY(16), 0x206a017a }, + { AR5K_PHY(21), 0x00000859 }, + { AR5K_PHY_BIN_MASK_1, 0x00000000 }, + { AR5K_PHY_BIN_MASK_2, 0x00000000 }, + { AR5K_PHY_BIN_MASK_3, 0x00000000 }, + { AR5K_PHY_BIN_MASK_CTL, 0x00800000 }, + { AR5K_PHY_ANT_CTL, 0x00000001 }, + /*{ AR5K_PHY(71), 0x0000092a },*/ /* Old value */ + { AR5K_PHY_MAX_RX_LEN, 0x00000c80 }, + { AR5K_PHY_IQ, 0x05100000 }, + { AR5K_PHY_WARM_RESET, 0x00000001 }, + { AR5K_PHY_CTL, 0x00000004 }, + { AR5K_PHY_TXPOWER_RATE1, 0x1e1f2022 }, + { AR5K_PHY_TXPOWER_RATE2, 0x0a0b0c0d }, + { AR5K_PHY_TXPOWER_RATE_MAX, 0x0000003f }, + { AR5K_PHY(82), 0x9280b212 }, + { AR5K_PHY_RADAR, 0x5d50e188 }, + /*{ AR5K_PHY(86), 0x000000ff },*/ + { AR5K_PHY(87), 0x004b6a8e }, + { AR5K_PHY_NFTHRES, 0x000003ce }, + { AR5K_PHY_RESTART, 0x192fb515 }, + { AR5K_PHY(94), 0x00000001 }, + { AR5K_PHY_RFBUS_REQ, 0x00000000 }, + /*{ AR5K_PHY(644), 0x0080a333 },*/ /* Old value */ + /*{ AR5K_PHY(645), 0x00206c10 },*/ /* Old value */ + { AR5K_PHY(644), 0x00806333 }, + { AR5K_PHY(645), 0x00106c10 }, + { AR5K_PHY(646), 0x009c4060 }, + /* { AR5K_PHY(647), 0x1483800a }, */ + /* { AR5K_PHY(648), 0x01831061 }, */ /* Old value */ + { AR5K_PHY(648), 0x018830c6 }, + { AR5K_PHY(649), 0x00000400 }, + /*{ AR5K_PHY(650), 0x000001b5 },*/ + { AR5K_PHY(651), 0x00000000 }, + { AR5K_PHY_TXPOWER_RATE3, 0x20202020 }, + { AR5K_PHY_TXPOWER_RATE4, 0x20202020 }, + /*{ AR5K_PHY(655), 0x13c889af },*/ + { AR5K_PHY(656), 0x38490a20 }, + { AR5K_PHY(657), 0x00007bb6 }, + { AR5K_PHY(658), 0x0fff3ffc }, +}; + +/* Initial mode-specific settings for AR5212 (Written before ar5212_ini) */ +static const struct ath5k_ini_mode ar5212_ini_mode_start[] = { + { AR5K_QUEUE_DFS_LOCAL_IFS(0), + /* A/XR B G */ + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(1), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(2), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(3), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(4), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(5), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(6), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(7), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(8), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_QUEUE_DFS_LOCAL_IFS(9), + { 0x002ffc0f, 0x002ffc1f, 0x002ffc0f } }, + { AR5K_DCU_GBL_IFS_SIFS, + { 0x00000230, 0x000000b0, 0x00000160 } }, + { AR5K_DCU_GBL_IFS_SLOT, + { 0x00000168, 0x000001b8, 0x0000018c } }, + { AR5K_DCU_GBL_IFS_EIFS, + { 0x00000e60, 0x00001f1c, 0x00003e38 } }, + { AR5K_DCU_GBL_IFS_MISC, + { 0x0000a0e0, 0x00005880, 0x0000b0e0 } }, + { AR5K_TIME_OUT, + { 0x03e803e8, 0x04200420, 0x08400840 } }, + { AR5K_PHY(8), + { 0x02020200, 0x02010200, 0x02020200 } }, + { AR5K_PHY_RF_CTL2, + { 0x00000e0e, 0x00000707, 0x00000e0e } }, + { AR5K_PHY_SETTLING, + { 0x1372161c, 0x13721722, 0x137216a2 } }, + { AR5K_PHY_AGCCTL, + { 0x00009d10, 0x00009d18, 0x00009d18 } }, + { AR5K_PHY_NF, + { 0x0001ce00, 0x0001ce00, 0x0001ce00 } }, + { AR5K_PHY_WEAK_OFDM_HIGH_THR, + { 0x409a4190, 0x409a4190, 0x409a4190 } }, + { AR5K_PHY(70), + { 0x000001b8, 0x00000084, 0x00000108 } }, + { AR5K_PHY_OFDM_SELFCORR, + { 0x10058a05, 0x10058a05, 0x10058a05 } }, + { 0xa230, + { 0x00000000, 0x00000000, 0x00000108 } }, +}; + +/* Initial mode-specific settings for AR5212 + RF5111 + * (Written after ar5212_ini) */ +static const struct ath5k_ini_mode rf5111_ini_mode_end[] = { + { AR5K_TXCFG, + /* A/XR B G */ + { 0x00008015, 0x00008015, 0x00008015 } }, + { AR5K_USEC_5211, + { 0x128d8fa7, 0x04e00f95, 0x12e00fab } }, + { AR5K_PHY_RF_CTL3, + { 0x0a020001, 0x05010100, 0x0a020001 } }, + { AR5K_PHY_RF_CTL4, + { 0x00000e0e, 0x00000e0e, 0x00000e0e } }, + { AR5K_PHY_PA_CTL, + { 0x00000007, 0x0000000b, 0x0000000b } }, + { AR5K_PHY_GAIN, + { 0x0018da5a, 0x0018ca69, 0x0018ca69 } }, + { AR5K_PHY_DESIRED_SIZE, + { 0x0de8b4e0, 0x0de8b4e0, 0x0de8b4e0 } }, + { AR5K_PHY_SIG, + { 0x7e800d2e, 0x7ee84d2e, 0x7ee84d2e } }, + { AR5K_PHY_AGCCOARSE, + { 0x3137665e, 0x3137665e, 0x3137665e } }, + { AR5K_PHY_WEAK_OFDM_LOW_THR, + { 0x050cb081, 0x050cb081, 0x050cb080 } }, + { AR5K_PHY_RX_DELAY, + { 0x00002710, 0x0000157c, 0x00002af8 } }, + { AR5K_PHY_FRAME_CTL_5211, + { 0xf7b81020, 0xf7b80d20, 0xf7b81020 } }, + { AR5K_PHY_GAIN_2GHZ, + { 0x642c416a, 0x6440416a, 0x6440416a } }, + { AR5K_PHY_CCK_RX_CTL_4, + { 0x1883800a, 0x1873800a, 0x1883800a } }, +}; + +/* Common for all modes */ +static const struct ath5k_ini rf5111_ini_common_end[] = { + { AR5K_DCU_FP, 0x00000000 }, + { AR5K_PHY_AGC, 0x00000000 }, + { AR5K_PHY_ADC_CTL, 0x00022ffe }, + { 0x983c, 0x00020100 }, + { AR5K_PHY_GAIN_OFFSET, 0x1284613c }, + { AR5K_PHY_PAPD_PROBE, 0x00004883 }, + { 0x9940, 0x00000004 }, + { 0x9958, 0x000000ff }, + { 0x9974, 0x00000000 }, + { AR5K_PHY_SPENDING, 0x00000018 }, + { AR5K_PHY_CCKTXCTL, 0x00000000 }, + { AR5K_PHY_CCK_CROSSCORR, 0xd03e6788 }, + { AR5K_PHY_DAG_CCK_CTL, 0x000001b5 }, + { 0xa23c, 0x13c889af }, +}; + + +/* Initial mode-specific settings for AR5212 + RF5112 + * (Written after ar5212_ini) */ +static const struct ath5k_ini_mode rf5112_ini_mode_end[] = { + { AR5K_TXCFG, + /* A/XR B G */ + { 0x00008015, 0x00008015, 0x00008015 } }, + { AR5K_USEC_5211, + { 0x128d93a7, 0x04e01395, 0x12e013ab } }, + { AR5K_PHY_RF_CTL3, + { 0x0a020001, 0x05020100, 0x0a020001 } }, + { AR5K_PHY_RF_CTL4, + { 0x00000e0e, 0x00000e0e, 0x00000e0e } }, + { AR5K_PHY_PA_CTL, + { 0x00000007, 0x0000000b, 0x0000000b } }, + { AR5K_PHY_GAIN, + { 0x0018da6d, 0x0018ca75, 0x0018ca75 } }, + { AR5K_PHY_DESIRED_SIZE, + { 0x0de8b4e0, 0x0de8b4e0, 0x0de8b4e0 } }, + { AR5K_PHY_SIG, + { 0x7e800d2e, 0x7ee80d2e, 0x7ee80d2e } }, + { AR5K_PHY_AGCCOARSE, + { 0x3137665e, 0x3137665e, 0x3137665e } }, + { AR5K_PHY_WEAK_OFDM_LOW_THR, + { 0x050cb081, 0x050cb081, 0x050cb081 } }, + { AR5K_PHY_RX_DELAY, + { 0x000007d0, 0x0000044c, 0x00000898 } }, + { AR5K_PHY_FRAME_CTL_5211, + { 0xf7b81020, 0xf7b80d10, 0xf7b81010 } }, + { AR5K_PHY_CCKTXCTL, + { 0x00000000, 0x00000008, 0x00000008 } }, + { AR5K_PHY_CCK_CROSSCORR, + { 0xd6be6788, 0xd03e6788, 0xd03e6788 } }, + { AR5K_PHY_GAIN_2GHZ, + { 0x642c0140, 0x6442c160, 0x6442c160 } }, + { AR5K_PHY_CCK_RX_CTL_4, + { 0x1883800a, 0x1873800a, 0x1883800a } }, +}; + +static const struct ath5k_ini rf5112_ini_common_end[] = { + { AR5K_DCU_FP, 0x00000000 }, + { AR5K_PHY_AGC, 0x00000000 }, + { AR5K_PHY_ADC_CTL, 0x00022ffe }, + { 0x983c, 0x00020100 }, + { AR5K_PHY_GAIN_OFFSET, 0x1284613c }, + { AR5K_PHY_PAPD_PROBE, 0x00004882 }, + { 0x9940, 0x00000004 }, + { 0x9958, 0x000000ff }, + { 0x9974, 0x00000000 }, + { AR5K_PHY_DAG_CCK_CTL, 0x000001b5 }, + { 0xa23c, 0x13c889af }, +}; + + +/* Initial mode-specific settings for RF5413/5414 + * (Written after ar5212_ini) */ +static const struct ath5k_ini_mode rf5413_ini_mode_end[] = { + { AR5K_TXCFG, + /* A/XR B G */ + { 0x00000015, 0x00000015, 0x00000015 } }, + { AR5K_USEC_5211, + { 0x128d93a7, 0x04e01395, 0x12e013ab } }, + { AR5K_PHY_RF_CTL3, + { 0x0a020001, 0x05020100, 0x0a020001 } }, + { AR5K_PHY_RF_CTL4, + { 0x00000e0e, 0x00000e0e, 0x00000e0e } }, + { AR5K_PHY_PA_CTL, + { 0x00000007, 0x0000000b, 0x0000000b } }, + { AR5K_PHY_GAIN, + { 0x0018fa61, 0x001a1a63, 0x001a1a63 } }, + { AR5K_PHY_DESIRED_SIZE, + { 0x0c98b4e0, 0x0c98b0da, 0x0c98b0da } }, + { AR5K_PHY_SIG, + { 0x7ec80d2e, 0x7ec80d2e, 0x7ec80d2e } }, + { AR5K_PHY_AGCCOARSE, + { 0x3139605e, 0x3139605e, 0x3139605e } }, + { AR5K_PHY_WEAK_OFDM_LOW_THR, + { 0x050cb081, 0x050cb081, 0x050cb081 } }, + { AR5K_PHY_RX_DELAY, + { 0x000007d0, 0x0000044c, 0x00000898 } }, + { AR5K_PHY_FRAME_CTL_5211, + { 0xf7b81000, 0xf7b80d00, 0xf7b81000 } }, + { AR5K_PHY_CCKTXCTL, + { 0x00000000, 0x00000000, 0x00000000 } }, + { AR5K_PHY_CCK_CROSSCORR, + { 0xd6be6788, 0xd03e6788, 0xd03e6788 } }, + { AR5K_PHY_GAIN_2GHZ, + { 0x002ec1e0, 0x002ac120, 0x002ac120 } }, + { AR5K_PHY_CCK_RX_CTL_4, + { 0x1883800a, 0x1863800a, 0x1883800a } }, + { 0xa300, + { 0x18010000, 0x18010000, 0x18010000 } }, + { 0xa304, + { 0x30032602, 0x30032602, 0x30032602 } }, + { 0xa308, + { 0x48073e06, 0x48073e06, 0x48073e06 } }, + { 0xa30c, + { 0x560b4c0a, 0x560b4c0a, 0x560b4c0a } }, + { 0xa310, + { 0x641a600f, 0x641a600f, 0x641a600f } }, + { 0xa314, + { 0x784f6e1b, 0x784f6e1b, 0x784f6e1b } }, + { 0xa318, + { 0x868f7c5a, 0x868f7c5a, 0x868f7c5a } }, + { 0xa31c, + { 0x90cf865b, 0x8ecf865b, 0x8ecf865b } }, + { 0xa320, + { 0x9d4f970f, 0x9b4f970f, 0x9b4f970f } }, + { 0xa324, + { 0xa7cfa38f, 0xa3cf9f8f, 0xa3cf9f8f } }, + { 0xa328, + { 0xb55faf1f, 0xb35faf1f, 0xb35faf1f } }, + { 0xa32c, + { 0xbddfb99f, 0xbbdfb99f, 0xbbdfb99f } }, + { 0xa330, + { 0xcb7fc53f, 0xcb7fc73f, 0xcb7fc73f } }, + { 0xa334, + { 0xd5ffd1bf, 0xd3ffd1bf, 0xd3ffd1bf } }, +}; + +static const struct ath5k_ini rf5413_ini_common_end[] = { + { AR5K_DCU_FP, 0x000003e0 }, + { AR5K_5414_CBCFG, 0x00000010 }, + { AR5K_SEQ_MASK, 0x0000000f }, + { 0x809c, 0x00000000 }, + { 0x80a0, 0x00000000 }, + { AR5K_MIC_QOS_CTL, 0x00000000 }, + { AR5K_MIC_QOS_SEL, 0x00000000 }, + { AR5K_MISC_MODE, 0x00000000 }, + { AR5K_OFDM_FIL_CNT, 0x00000000 }, + { AR5K_CCK_FIL_CNT, 0x00000000 }, + { AR5K_PHYERR_CNT1, 0x00000000 }, + { AR5K_PHYERR_CNT1_MASK, 0x00000000 }, + { AR5K_PHYERR_CNT2, 0x00000000 }, + { AR5K_PHYERR_CNT2_MASK, 0x00000000 }, + { AR5K_TSF_THRES, 0x00000000 }, + { 0x8140, 0x800003f9 }, + { 0x8144, 0x00000000 }, + { AR5K_PHY_AGC, 0x00000000 }, + { AR5K_PHY_ADC_CTL, 0x0000a000 }, + { 0x983c, 0x00200400 }, + { AR5K_PHY_GAIN_OFFSET, 0x1284233c }, + { AR5K_PHY_SCR, 0x0000001f }, + { AR5K_PHY_SLMT, 0x00000080 }, + { AR5K_PHY_SCAL, 0x0000000e }, + { 0x9958, 0x00081fff }, + { AR5K_PHY_TIMING_7, 0x00000000 }, + { AR5K_PHY_TIMING_8, 0x02800000 }, + { AR5K_PHY_TIMING_11, 0x00000000 }, + { AR5K_PHY_HEAVY_CLIP_ENABLE, 0x00000000 }, + { 0x99e4, 0xaaaaaaaa }, + { 0x99e8, 0x3c466478 }, + { 0x99ec, 0x000000aa }, + { AR5K_PHY_SCLOCK, 0x0000000c }, + { AR5K_PHY_SDELAY, 0x000000ff }, + { AR5K_PHY_SPENDING, 0x00000014 }, + { AR5K_PHY_DAG_CCK_CTL, 0x000009b5 }, + { 0xa23c, 0x93c889af }, + { AR5K_PHY_FAST_ADC, 0x00000001 }, + { 0xa250, 0x0000a000 }, + { AR5K_PHY_BLUETOOTH, 0x00000000 }, + { AR5K_PHY_TPC_RG1, 0x0cc75380 }, + { 0xa25c, 0x0f0f0f01 }, + { 0xa260, 0x5f690f01 }, + { 0xa264, 0x00418a11 }, + { 0xa268, 0x00000000 }, + { AR5K_PHY_TPC_RG5, 0x0c30c16a }, + { 0xa270, 0x00820820 }, + { 0xa274, 0x081b7caa }, + { 0xa278, 0x1ce739ce }, + { 0xa27c, 0x051701ce }, + { 0xa338, 0x00000000 }, + { 0xa33c, 0x00000000 }, + { 0xa340, 0x00000000 }, + { 0xa344, 0x00000000 }, + { 0xa348, 0x3fffffff }, + { 0xa34c, 0x3fffffff }, + { 0xa350, 0x3fffffff }, + { 0xa354, 0x0003ffff }, + { 0xa358, 0x79a8aa1f }, + { 0xa35c, 0x066c420f }, + { 0xa360, 0x0f282207 }, + { 0xa364, 0x17601685 }, + { 0xa368, 0x1f801104 }, + { 0xa36c, 0x37a00c03 }, + { 0xa370, 0x3fc40883 }, + { 0xa374, 0x57c00803 }, + { 0xa378, 0x5fd80682 }, + { 0xa37c, 0x7fe00482 }, + { 0xa380, 0x7f3c7bba }, + { 0xa384, 0xf3307ff0 }, +}; + +/* Initial mode-specific settings for RF2413/2414 + * (Written after ar5212_ini) */ +/* XXX: a mode ? */ +static const struct ath5k_ini_mode rf2413_ini_mode_end[] = { + { AR5K_TXCFG, + /* A/XR B G */ + { 0x00000015, 0x00000015, 0x00000015 } }, + { AR5K_USEC_5211, + { 0x128d93a7, 0x04e01395, 0x12e013ab } }, + { AR5K_PHY_RF_CTL3, + { 0x0a020001, 0x05020000, 0x0a020001 } }, + { AR5K_PHY_RF_CTL4, + { 0x00000e00, 0x00000e00, 0x00000e00 } }, + { AR5K_PHY_PA_CTL, + { 0x00000002, 0x0000000a, 0x0000000a } }, + { AR5K_PHY_GAIN, + { 0x0018da6d, 0x001a6a64, 0x001a6a64 } }, + { AR5K_PHY_DESIRED_SIZE, + { 0x0de8b4e0, 0x0de8b0da, 0x0c98b0da } }, + { AR5K_PHY_SIG, + { 0x7e800d2e, 0x7ee80d2e, 0x7ec80d2e } }, + { AR5K_PHY_AGCCOARSE, + { 0x3137665e, 0x3137665e, 0x3139605e } }, + { AR5K_PHY_WEAK_OFDM_LOW_THR, + { 0x050cb081, 0x050cb081, 0x050cb081 } }, + { AR5K_PHY_RX_DELAY, + { 0x000007d0, 0x0000044c, 0x00000898 } }, + { AR5K_PHY_FRAME_CTL_5211, + { 0xf7b81000, 0xf7b80d00, 0xf7b81000 } }, + { AR5K_PHY_CCKTXCTL, + { 0x00000000, 0x00000000, 0x00000000 } }, + { AR5K_PHY_CCK_CROSSCORR, + { 0xd6be6788, 0xd03e6788, 0xd03e6788 } }, + { AR5K_PHY_GAIN_2GHZ, + { 0x002c0140, 0x0042c140, 0x0042c140 } }, + { AR5K_PHY_CCK_RX_CTL_4, + { 0x1883800a, 0x1863800a, 0x1883800a } }, +}; + +static const struct ath5k_ini rf2413_ini_common_end[] = { + { AR5K_DCU_FP, 0x000003e0 }, + { AR5K_SEQ_MASK, 0x0000000f }, + { AR5K_MIC_QOS_CTL, 0x00000000 }, + { AR5K_MIC_QOS_SEL, 0x00000000 }, + { AR5K_MISC_MODE, 0x00000000 }, + { AR5K_OFDM_FIL_CNT, 0x00000000 }, + { AR5K_CCK_FIL_CNT, 0x00000000 }, + { AR5K_PHYERR_CNT1, 0x00000000 }, + { AR5K_PHYERR_CNT1_MASK, 0x00000000 }, + { AR5K_PHYERR_CNT2, 0x00000000 }, + { AR5K_PHYERR_CNT2_MASK, 0x00000000 }, + { AR5K_TSF_THRES, 0x00000000 }, + { 0x8140, 0x800000a8 }, + { 0x8144, 0x00000000 }, + { AR5K_PHY_AGC, 0x00000000 }, + { AR5K_PHY_ADC_CTL, 0x0000a000 }, + { 0x983c, 0x00200400 }, + { AR5K_PHY_GAIN_OFFSET, 0x1284233c }, + { AR5K_PHY_SCR, 0x0000001f }, + { AR5K_PHY_SLMT, 0x00000080 }, + { AR5K_PHY_SCAL, 0x0000000e }, + { 0x9958, 0x000000ff }, + { AR5K_PHY_TIMING_7, 0x00000000 }, + { AR5K_PHY_TIMING_8, 0x02800000 }, + { AR5K_PHY_TIMING_11, 0x00000000 }, + { AR5K_PHY_HEAVY_CLIP_ENABLE, 0x00000000 }, + { 0x99e4, 0xaaaaaaaa }, + { 0x99e8, 0x3c466478 }, + { 0x99ec, 0x000000aa }, + { AR5K_PHY_SCLOCK, 0x0000000c }, + { AR5K_PHY_SDELAY, 0x000000ff }, + { AR5K_PHY_SPENDING, 0x00000014 }, + { AR5K_PHY_DAG_CCK_CTL, 0x000009b5 }, + { 0xa23c, 0x93c889af }, + { AR5K_PHY_FAST_ADC, 0x00000001 }, + { 0xa250, 0x0000a000 }, + { AR5K_PHY_BLUETOOTH, 0x00000000 }, + { AR5K_PHY_TPC_RG1, 0x0cc75380 }, + { 0xa25c, 0x0f0f0f01 }, + { 0xa260, 0x5f690f01 }, + { 0xa264, 0x00418a11 }, + { 0xa268, 0x00000000 }, + { AR5K_PHY_TPC_RG5, 0x0c30c16a }, + { 0xa270, 0x00820820 }, + { 0xa274, 0x001b7caa }, + { 0xa278, 0x1ce739ce }, + { 0xa27c, 0x051701ce }, + { 0xa300, 0x18010000 }, + { 0xa304, 0x30032602 }, + { 0xa308, 0x48073e06 }, + { 0xa30c, 0x560b4c0a }, + { 0xa310, 0x641a600f }, + { 0xa314, 0x784f6e1b }, + { 0xa318, 0x868f7c5a }, + { 0xa31c, 0x8ecf865b }, + { 0xa320, 0x9d4f970f }, + { 0xa324, 0xa5cfa18f }, + { 0xa328, 0xb55faf1f }, + { 0xa32c, 0xbddfb99f }, + { 0xa330, 0xcd7fc73f }, + { 0xa334, 0xd5ffd1bf }, + { 0xa338, 0x00000000 }, + { 0xa33c, 0x00000000 }, + { 0xa340, 0x00000000 }, + { 0xa344, 0x00000000 }, + { 0xa348, 0x3fffffff }, + { 0xa34c, 0x3fffffff }, + { 0xa350, 0x3fffffff }, + { 0xa354, 0x0003ffff }, + { 0xa358, 0x79a8aa1f }, + { 0xa35c, 0x066c420f }, + { 0xa360, 0x0f282207 }, + { 0xa364, 0x17601685 }, + { 0xa368, 0x1f801104 }, + { 0xa36c, 0x37a00c03 }, + { 0xa370, 0x3fc40883 }, + { 0xa374, 0x57c00803 }, + { 0xa378, 0x5fd80682 }, + { 0xa37c, 0x7fe00482 }, + { 0xa380, 0x7f3c7bba }, + { 0xa384, 0xf3307ff0 }, +}; + +/* Initial mode-specific settings for RF2425 + * (Written after ar5212_ini) */ +/* XXX: a mode ? */ +static const struct ath5k_ini_mode rf2425_ini_mode_end[] = { + { AR5K_TXCFG, + /* A/XR B G */ + { 0x00000015, 0x00000015, 0x00000015 } }, + { AR5K_USEC_5211, + { 0x128d93a7, 0x04e01395, 0x12e013ab } }, + { AR5K_PHY_RF_CTL3, + { 0x0a020001, 0x05020100, 0x0a020001 } }, + { AR5K_PHY_RF_CTL4, + { 0x00000e0e, 0x00000e0e, 0x00000e0e } }, + { AR5K_PHY_PA_CTL, + { 0x00000003, 0x0000000b, 0x0000000b } }, + { AR5K_PHY_SETTLING, + { 0x1372161c, 0x13721722, 0x13721422 } }, + { AR5K_PHY_GAIN, + { 0x0018fa61, 0x00199a65, 0x00199a65 } }, + { AR5K_PHY_DESIRED_SIZE, + { 0x0c98b4e0, 0x0c98b0da, 0x0c98b0da } }, + { AR5K_PHY_SIG, + { 0x7ec80d2e, 0x7ec80d2e, 0x7ec80d2e } }, + { AR5K_PHY_AGCCOARSE, + { 0x3139605e, 0x3139605e, 0x3139605e } }, + { AR5K_PHY_WEAK_OFDM_LOW_THR, + { 0x050cb081, 0x050cb081, 0x050cb081 } }, + { AR5K_PHY_RX_DELAY, + { 0x000007d0, 0x0000044c, 0x00000898 } }, + { AR5K_PHY_FRAME_CTL_5211, + { 0xf7b81000, 0xf7b80d00, 0xf7b81000 } }, + { AR5K_PHY_CCKTXCTL, + { 0x00000000, 0x00000000, 0x00000000 } }, + { AR5K_PHY_CCK_CROSSCORR, + { 0xd6be6788, 0xd03e6788, 0xd03e6788 } }, + { AR5K_PHY_GAIN_2GHZ, + { 0x00000140, 0x0052c140, 0x0052c140 } }, + { AR5K_PHY_CCK_RX_CTL_4, + { 0x1883800a, 0x1863800a, 0x1883800a } }, + { 0xa324, + { 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf } }, + { 0xa328, + { 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf } }, + { 0xa32c, + { 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf } }, + { 0xa330, + { 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf } }, + { 0xa334, + { 0xa7cfa7cf, 0xa7cfa7cf, 0xa7cfa7cf } }, +}; + +static const struct ath5k_ini rf2425_ini_common_end[] = { + { AR5K_DCU_FP, 0x000003e0 }, + { AR5K_SEQ_MASK, 0x0000000f }, + { 0x809c, 0x00000000 }, + { 0x80a0, 0x00000000 }, + { AR5K_MIC_QOS_CTL, 0x00000000 }, + { AR5K_MIC_QOS_SEL, 0x00000000 }, + { AR5K_MISC_MODE, 0x00000000 }, + { AR5K_OFDM_FIL_CNT, 0x00000000 }, + { AR5K_CCK_FIL_CNT, 0x00000000 }, + { AR5K_PHYERR_CNT1, 0x00000000 }, + { AR5K_PHYERR_CNT1_MASK, 0x00000000 }, + { AR5K_PHYERR_CNT2, 0x00000000 }, + { AR5K_PHYERR_CNT2_MASK, 0x00000000 }, + { AR5K_TSF_THRES, 0x00000000 }, + { 0x8140, 0x800003f9 }, + { 0x8144, 0x00000000 }, + { AR5K_PHY_AGC, 0x00000000 }, + { AR5K_PHY_ADC_CTL, 0x0000a000 }, + { 0x983c, 0x00200400 }, + { AR5K_PHY_GAIN_OFFSET, 0x1284233c }, + { AR5K_PHY_SCR, 0x0000001f }, + { AR5K_PHY_SLMT, 0x00000080 }, + { AR5K_PHY_SCAL, 0x0000000e }, + { 0x9958, 0x00081fff }, + { AR5K_PHY_TIMING_7, 0x00000000 }, + { AR5K_PHY_TIMING_8, 0x02800000 }, + { AR5K_PHY_TIMING_11, 0x00000000 }, + { 0x99dc, 0xfebadbe8 }, + { AR5K_PHY_HEAVY_CLIP_ENABLE, 0x00000000 }, + { 0x99e4, 0xaaaaaaaa }, + { 0x99e8, 0x3c466478 }, + { 0x99ec, 0x000000aa }, + { AR5K_PHY_SCLOCK, 0x0000000c }, + { AR5K_PHY_SDELAY, 0x000000ff }, + { AR5K_PHY_SPENDING, 0x00000014 }, + { AR5K_PHY_DAG_CCK_CTL, 0x000009b5 }, + { AR5K_PHY_TXPOWER_RATE3, 0x20202020 }, + { AR5K_PHY_TXPOWER_RATE4, 0x20202020 }, + { 0xa23c, 0x93c889af }, + { AR5K_PHY_FAST_ADC, 0x00000001 }, + { 0xa250, 0x0000a000 }, + { AR5K_PHY_BLUETOOTH, 0x00000000 }, + { AR5K_PHY_TPC_RG1, 0x0cc75380 }, + { 0xa25c, 0x0f0f0f01 }, + { 0xa260, 0x5f690f01 }, + { 0xa264, 0x00418a11 }, + { 0xa268, 0x00000000 }, + { AR5K_PHY_TPC_RG5, 0x0c30c166 }, + { 0xa270, 0x00820820 }, + { 0xa274, 0x081a3caa }, + { 0xa278, 0x1ce739ce }, + { 0xa27c, 0x051701ce }, + { 0xa300, 0x16010000 }, + { 0xa304, 0x2c032402 }, + { 0xa308, 0x48433e42 }, + { 0xa30c, 0x5a0f500b }, + { 0xa310, 0x6c4b624a }, + { 0xa314, 0x7e8b748a }, + { 0xa318, 0x96cf8ccb }, + { 0xa31c, 0xa34f9d0f }, + { 0xa320, 0xa7cfa58f }, + { 0xa348, 0x3fffffff }, + { 0xa34c, 0x3fffffff }, + { 0xa350, 0x3fffffff }, + { 0xa354, 0x0003ffff }, + { 0xa358, 0x79a8aa1f }, + { 0xa35c, 0x066c420f }, + { 0xa360, 0x0f282207 }, + { 0xa364, 0x17601685 }, + { 0xa368, 0x1f801104 }, + { 0xa36c, 0x37a00c03 }, + { 0xa370, 0x3fc40883 }, + { 0xa374, 0x57c00803 }, + { 0xa378, 0x5fd80682 }, + { 0xa37c, 0x7fe00482 }, + { 0xa380, 0x7f3c7bba }, + { 0xa384, 0xf3307ff0 }, +}; + +/* + * Initial BaseBand Gain settings for RF5111/5112 (AR5210 comes with + * RF5110 only so initial BB Gain settings are included in AR5K_AR5210_INI) + */ + +/* RF5111 Initial BaseBand Gain settings */ +static const struct ath5k_ini rf5111_ini_bbgain[] = { + { AR5K_BB_GAIN(0), 0x00000000 }, + { AR5K_BB_GAIN(1), 0x00000020 }, + { AR5K_BB_GAIN(2), 0x00000010 }, + { AR5K_BB_GAIN(3), 0x00000030 }, + { AR5K_BB_GAIN(4), 0x00000008 }, + { AR5K_BB_GAIN(5), 0x00000028 }, + { AR5K_BB_GAIN(6), 0x00000004 }, + { AR5K_BB_GAIN(7), 0x00000024 }, + { AR5K_BB_GAIN(8), 0x00000014 }, + { AR5K_BB_GAIN(9), 0x00000034 }, + { AR5K_BB_GAIN(10), 0x0000000c }, + { AR5K_BB_GAIN(11), 0x0000002c }, + { AR5K_BB_GAIN(12), 0x00000002 }, + { AR5K_BB_GAIN(13), 0x00000022 }, + { AR5K_BB_GAIN(14), 0x00000012 }, + { AR5K_BB_GAIN(15), 0x00000032 }, + { AR5K_BB_GAIN(16), 0x0000000a }, + { AR5K_BB_GAIN(17), 0x0000002a }, + { AR5K_BB_GAIN(18), 0x00000006 }, + { AR5K_BB_GAIN(19), 0x00000026 }, + { AR5K_BB_GAIN(20), 0x00000016 }, + { AR5K_BB_GAIN(21), 0x00000036 }, + { AR5K_BB_GAIN(22), 0x0000000e }, + { AR5K_BB_GAIN(23), 0x0000002e }, + { AR5K_BB_GAIN(24), 0x00000001 }, + { AR5K_BB_GAIN(25), 0x00000021 }, + { AR5K_BB_GAIN(26), 0x00000011 }, + { AR5K_BB_GAIN(27), 0x00000031 }, + { AR5K_BB_GAIN(28), 0x00000009 }, + { AR5K_BB_GAIN(29), 0x00000029 }, + { AR5K_BB_GAIN(30), 0x00000005 }, + { AR5K_BB_GAIN(31), 0x00000025 }, + { AR5K_BB_GAIN(32), 0x00000015 }, + { AR5K_BB_GAIN(33), 0x00000035 }, + { AR5K_BB_GAIN(34), 0x0000000d }, + { AR5K_BB_GAIN(35), 0x0000002d }, + { AR5K_BB_GAIN(36), 0x00000003 }, + { AR5K_BB_GAIN(37), 0x00000023 }, + { AR5K_BB_GAIN(38), 0x00000013 }, + { AR5K_BB_GAIN(39), 0x00000033 }, + { AR5K_BB_GAIN(40), 0x0000000b }, + { AR5K_BB_GAIN(41), 0x0000002b }, + { AR5K_BB_GAIN(42), 0x0000002b }, + { AR5K_BB_GAIN(43), 0x0000002b }, + { AR5K_BB_GAIN(44), 0x0000002b }, + { AR5K_BB_GAIN(45), 0x0000002b }, + { AR5K_BB_GAIN(46), 0x0000002b }, + { AR5K_BB_GAIN(47), 0x0000002b }, + { AR5K_BB_GAIN(48), 0x0000002b }, + { AR5K_BB_GAIN(49), 0x0000002b }, + { AR5K_BB_GAIN(50), 0x0000002b }, + { AR5K_BB_GAIN(51), 0x0000002b }, + { AR5K_BB_GAIN(52), 0x0000002b }, + { AR5K_BB_GAIN(53), 0x0000002b }, + { AR5K_BB_GAIN(54), 0x0000002b }, + { AR5K_BB_GAIN(55), 0x0000002b }, + { AR5K_BB_GAIN(56), 0x0000002b }, + { AR5K_BB_GAIN(57), 0x0000002b }, + { AR5K_BB_GAIN(58), 0x0000002b }, + { AR5K_BB_GAIN(59), 0x0000002b }, + { AR5K_BB_GAIN(60), 0x0000002b }, + { AR5K_BB_GAIN(61), 0x0000002b }, + { AR5K_BB_GAIN(62), 0x00000002 }, + { AR5K_BB_GAIN(63), 0x00000016 }, +}; + +/* RF5112 Initial BaseBand Gain settings (Same for RF5413/5414+) */ +static const struct ath5k_ini rf5112_ini_bbgain[] = { + { AR5K_BB_GAIN(0), 0x00000000 }, + { AR5K_BB_GAIN(1), 0x00000001 }, + { AR5K_BB_GAIN(2), 0x00000002 }, + { AR5K_BB_GAIN(3), 0x00000003 }, + { AR5K_BB_GAIN(4), 0x00000004 }, + { AR5K_BB_GAIN(5), 0x00000005 }, + { AR5K_BB_GAIN(6), 0x00000008 }, + { AR5K_BB_GAIN(7), 0x00000009 }, + { AR5K_BB_GAIN(8), 0x0000000a }, + { AR5K_BB_GAIN(9), 0x0000000b }, + { AR5K_BB_GAIN(10), 0x0000000c }, + { AR5K_BB_GAIN(11), 0x0000000d }, + { AR5K_BB_GAIN(12), 0x00000010 }, + { AR5K_BB_GAIN(13), 0x00000011 }, + { AR5K_BB_GAIN(14), 0x00000012 }, + { AR5K_BB_GAIN(15), 0x00000013 }, + { AR5K_BB_GAIN(16), 0x00000014 }, + { AR5K_BB_GAIN(17), 0x00000015 }, + { AR5K_BB_GAIN(18), 0x00000018 }, + { AR5K_BB_GAIN(19), 0x00000019 }, + { AR5K_BB_GAIN(20), 0x0000001a }, + { AR5K_BB_GAIN(21), 0x0000001b }, + { AR5K_BB_GAIN(22), 0x0000001c }, + { AR5K_BB_GAIN(23), 0x0000001d }, + { AR5K_BB_GAIN(24), 0x00000020 }, + { AR5K_BB_GAIN(25), 0x00000021 }, + { AR5K_BB_GAIN(26), 0x00000022 }, + { AR5K_BB_GAIN(27), 0x00000023 }, + { AR5K_BB_GAIN(28), 0x00000024 }, + { AR5K_BB_GAIN(29), 0x00000025 }, + { AR5K_BB_GAIN(30), 0x00000028 }, + { AR5K_BB_GAIN(31), 0x00000029 }, + { AR5K_BB_GAIN(32), 0x0000002a }, + { AR5K_BB_GAIN(33), 0x0000002b }, + { AR5K_BB_GAIN(34), 0x0000002c }, + { AR5K_BB_GAIN(35), 0x0000002d }, + { AR5K_BB_GAIN(36), 0x00000030 }, + { AR5K_BB_GAIN(37), 0x00000031 }, + { AR5K_BB_GAIN(38), 0x00000032 }, + { AR5K_BB_GAIN(39), 0x00000033 }, + { AR5K_BB_GAIN(40), 0x00000034 }, + { AR5K_BB_GAIN(41), 0x00000035 }, + { AR5K_BB_GAIN(42), 0x00000035 }, + { AR5K_BB_GAIN(43), 0x00000035 }, + { AR5K_BB_GAIN(44), 0x00000035 }, + { AR5K_BB_GAIN(45), 0x00000035 }, + { AR5K_BB_GAIN(46), 0x00000035 }, + { AR5K_BB_GAIN(47), 0x00000035 }, + { AR5K_BB_GAIN(48), 0x00000035 }, + { AR5K_BB_GAIN(49), 0x00000035 }, + { AR5K_BB_GAIN(50), 0x00000035 }, + { AR5K_BB_GAIN(51), 0x00000035 }, + { AR5K_BB_GAIN(52), 0x00000035 }, + { AR5K_BB_GAIN(53), 0x00000035 }, + { AR5K_BB_GAIN(54), 0x00000035 }, + { AR5K_BB_GAIN(55), 0x00000035 }, + { AR5K_BB_GAIN(56), 0x00000035 }, + { AR5K_BB_GAIN(57), 0x00000035 }, + { AR5K_BB_GAIN(58), 0x00000035 }, + { AR5K_BB_GAIN(59), 0x00000035 }, + { AR5K_BB_GAIN(60), 0x00000035 }, + { AR5K_BB_GAIN(61), 0x00000035 }, + { AR5K_BB_GAIN(62), 0x00000010 }, + { AR5K_BB_GAIN(63), 0x0000001a }, +}; + + +/** + * ath5k_hw_ini_registers() - Write initial register dump common for all modes + * @ah: The &struct ath5k_hw + * @size: Dump size + * @ini_regs: The array of &struct ath5k_ini + * @skip_pcu: Skip PCU registers + */ +static void +ath5k_hw_ini_registers(struct ath5k_hw *ah, unsigned int size, + const struct ath5k_ini *ini_regs, bool skip_pcu) +{ + unsigned int i; + + /* Write initial registers */ + for (i = 0; i < size; i++) { + /* Skip PCU registers if + * requested */ + if (skip_pcu && + ini_regs[i].ini_register >= AR5K_PCU_MIN && + ini_regs[i].ini_register <= AR5K_PCU_MAX) + continue; + + switch (ini_regs[i].ini_mode) { + case AR5K_INI_READ: + /* Cleared on read */ + ath5k_hw_reg_read(ah, ini_regs[i].ini_register); + break; + case AR5K_INI_WRITE: + default: + AR5K_REG_WAIT(i); + ath5k_hw_reg_write(ah, ini_regs[i].ini_value, + ini_regs[i].ini_register); + } + } +} + +/** + * ath5k_hw_ini_mode_registers() - Write initial mode-specific register dump + * @ah: The &struct ath5k_hw + * @size: Dump size + * @ini_mode: The array of &struct ath5k_ini_mode + * @mode: One of enum ath5k_driver_mode + */ +static void +ath5k_hw_ini_mode_registers(struct ath5k_hw *ah, + unsigned int size, const struct ath5k_ini_mode *ini_mode, + u8 mode) +{ + unsigned int i; + + for (i = 0; i < size; i++) { + AR5K_REG_WAIT(i); + ath5k_hw_reg_write(ah, ini_mode[i].mode_value[mode], + (u32)ini_mode[i].mode_register); + } + +} + +/** + * ath5k_hw_write_initvals() - Write initial chip-specific register dump + * @ah: The &struct ath5k_hw + * @mode: One of enum ath5k_driver_mode + * @skip_pcu: Skip PCU registers + * + * Write initial chip-specific register dump, to get the chipset on a + * clean and ready-to-work state after warm reset. + */ +int +ath5k_hw_write_initvals(struct ath5k_hw *ah, u8 mode, bool skip_pcu) +{ + /* + * Write initial register settings + */ + + /* For AR5212 and compatible */ + if (ah->ah_version == AR5K_AR5212) { + + /* First set of mode-specific settings */ + ath5k_hw_ini_mode_registers(ah, + ARRAY_SIZE(ar5212_ini_mode_start), + ar5212_ini_mode_start, mode); + + /* + * Write initial settings common for all modes + */ + ath5k_hw_ini_registers(ah, ARRAY_SIZE(ar5212_ini_common_start), + ar5212_ini_common_start, skip_pcu); + + /* Second set of mode-specific settings */ + switch (ah->ah_radio) { + case AR5K_RF5111: + + ath5k_hw_ini_mode_registers(ah, + ARRAY_SIZE(rf5111_ini_mode_end), + rf5111_ini_mode_end, mode); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5111_ini_common_end), + rf5111_ini_common_end, skip_pcu); + + /* Baseband gain table */ + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5111_ini_bbgain), + rf5111_ini_bbgain, skip_pcu); + + break; + case AR5K_RF5112: + + ath5k_hw_ini_mode_registers(ah, + ARRAY_SIZE(rf5112_ini_mode_end), + rf5112_ini_mode_end, mode); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5112_ini_common_end), + rf5112_ini_common_end, skip_pcu); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5112_ini_bbgain), + rf5112_ini_bbgain, skip_pcu); + + break; + case AR5K_RF5413: + + ath5k_hw_ini_mode_registers(ah, + ARRAY_SIZE(rf5413_ini_mode_end), + rf5413_ini_mode_end, mode); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5413_ini_common_end), + rf5413_ini_common_end, skip_pcu); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5112_ini_bbgain), + rf5112_ini_bbgain, skip_pcu); + + break; + case AR5K_RF2316: + case AR5K_RF2413: + + ath5k_hw_ini_mode_registers(ah, + ARRAY_SIZE(rf2413_ini_mode_end), + rf2413_ini_mode_end, mode); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf2413_ini_common_end), + rf2413_ini_common_end, skip_pcu); + + /* Override settings from rf2413_ini_common_end */ + if (ah->ah_radio == AR5K_RF2316) { + ath5k_hw_reg_write(ah, 0x00004000, + AR5K_PHY_AGC); + ath5k_hw_reg_write(ah, 0x081b7caa, + 0xa274); + } + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5112_ini_bbgain), + rf5112_ini_bbgain, skip_pcu); + break; + case AR5K_RF2317: + + ath5k_hw_ini_mode_registers(ah, + ARRAY_SIZE(rf2413_ini_mode_end), + rf2413_ini_mode_end, mode); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf2425_ini_common_end), + rf2425_ini_common_end, skip_pcu); + + /* Override settings from rf2413_ini_mode_end */ + ath5k_hw_reg_write(ah, 0x00180a65, AR5K_PHY_GAIN); + + /* Override settings from rf2413_ini_common_end */ + ath5k_hw_reg_write(ah, 0x00004000, AR5K_PHY_AGC); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TPC_RG5, + AR5K_PHY_TPC_RG5_PD_GAIN_OVERLAP, 0xa); + ath5k_hw_reg_write(ah, 0x800000a8, 0x8140); + ath5k_hw_reg_write(ah, 0x000000ff, 0x9958); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5112_ini_bbgain), + rf5112_ini_bbgain, skip_pcu); + break; + case AR5K_RF2425: + + ath5k_hw_ini_mode_registers(ah, + ARRAY_SIZE(rf2425_ini_mode_end), + rf2425_ini_mode_end, mode); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf2425_ini_common_end), + rf2425_ini_common_end, skip_pcu); + + ath5k_hw_ini_registers(ah, + ARRAY_SIZE(rf5112_ini_bbgain), + rf5112_ini_bbgain, skip_pcu); + break; + default: + return -EINVAL; + + } + + /* For AR5211 */ + } else if (ah->ah_version == AR5K_AR5211) { + + /* AR5K_MODE_11B */ + if (mode > 2) { + ATH5K_ERR(ah, "unsupported channel mode: %d\n", mode); + return -EINVAL; + } + + /* Mode-specific settings */ + ath5k_hw_ini_mode_registers(ah, ARRAY_SIZE(ar5211_ini_mode), + ar5211_ini_mode, mode); + + /* + * Write initial settings common for all modes + */ + ath5k_hw_ini_registers(ah, ARRAY_SIZE(ar5211_ini), + ar5211_ini, skip_pcu); + + /* AR5211 only comes with 5111 */ + + /* Baseband gain table */ + ath5k_hw_ini_registers(ah, ARRAY_SIZE(rf5111_ini_bbgain), + rf5111_ini_bbgain, skip_pcu); + /* For AR5210 (for mode settings check out ath5k_hw_reset_tx_queue) */ + } else if (ah->ah_version == AR5K_AR5210) { + ath5k_hw_ini_registers(ah, ARRAY_SIZE(ar5210_ini), + ar5210_ini, skip_pcu); + } + + return 0; +} diff --git a/drivers/net/wireless/ath/ath5k/led.c b/drivers/net/wireless/ath/ath5k/led.c new file mode 100644 index 0000000..48a6a69 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/led.c @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting + * Copyright (c) 2004-2005 Atheros Communications, Inc. + * Copyright (c) 2007 Jiri Slaby + * Copyright (c) 2009 Bob Copeland + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include "ath5k.h" + +#define ATH_SDEVICE(subv, subd) \ + .vendor = PCI_ANY_ID, .device = PCI_ANY_ID, \ + .subvendor = (subv), .subdevice = (subd) + +#define ATH_LED(pin, polarity) .driver_data = (((pin) << 8) | (polarity)) +#define ATH_PIN(data) ((data) >> 8) +#define ATH_POLARITY(data) ((data) & 0xff) + +/* Devices we match on for LED config info (typically laptops) */ +static const struct pci_device_id ath5k_led_devices[] = { + /* AR5211 */ + { PCI_VDEVICE(ATHEROS, PCI_DEVICE_ID_ATHEROS_AR5211), ATH_LED(0, 0) }, + /* HP Compaq nc6xx, nc4000, nx6000 */ + { ATH_SDEVICE(PCI_VENDOR_ID_COMPAQ, PCI_ANY_ID), ATH_LED(1, 1) }, + /* Acer Aspire One A150 (maximlevitsky@gmail.com) */ + { ATH_SDEVICE(PCI_VENDOR_ID_FOXCONN, 0xe008), ATH_LED(3, 0) }, + /* Acer Aspire One AO531h AO751h (keng-yu.lin@canonical.com) */ + { ATH_SDEVICE(PCI_VENDOR_ID_FOXCONN, 0xe00d), ATH_LED(3, 0) }, + /* Acer Ferrari 5000 (russ.dill@gmail.com) */ + { ATH_SDEVICE(PCI_VENDOR_ID_AMBIT, 0x0422), ATH_LED(1, 1) }, + /* E-machines E510 (tuliom@gmail.com) */ + { ATH_SDEVICE(PCI_VENDOR_ID_AMBIT, 0x0428), ATH_LED(3, 0) }, + /* BenQ Joybook R55v (nowymarluk@wp.pl) */ + { ATH_SDEVICE(PCI_VENDOR_ID_QMI, 0x0100), ATH_LED(1, 0) }, + /* Acer Extensa 5620z (nekoreeve@gmail.com) */ + { ATH_SDEVICE(PCI_VENDOR_ID_QMI, 0x0105), ATH_LED(3, 0) }, + /* Fukato Datacask Jupiter 1014a (mrb74@gmx.at) */ + { ATH_SDEVICE(PCI_VENDOR_ID_AZWAVE, 0x1026), ATH_LED(3, 0) }, + /* IBM ThinkPad AR5BXB6 (legovini@spiro.fisica.unipd.it) */ + { ATH_SDEVICE(PCI_VENDOR_ID_IBM, 0x058a), ATH_LED(1, 0) }, + /* HP Compaq CQ60-206US (ddreggors@jumptv.com) */ + { ATH_SDEVICE(PCI_VENDOR_ID_HP, 0x0137a), ATH_LED(3, 1) }, + /* HP Compaq C700 (nitrousnrg@gmail.com) */ + { ATH_SDEVICE(PCI_VENDOR_ID_HP, 0x0137b), ATH_LED(3, 1) }, + /* LiteOn AR5BXB63 (magooz@salug.it) */ + { ATH_SDEVICE(PCI_VENDOR_ID_ATHEROS, 0x3067), ATH_LED(3, 0) }, + /* IBM-specific AR5212 (all others) */ + { PCI_VDEVICE(ATHEROS, PCI_DEVICE_ID_ATHEROS_AR5212_IBM), ATH_LED(0, 0) }, + /* Dell Vostro A860 (shahar@shahar-or.co.il) */ + { ATH_SDEVICE(PCI_VENDOR_ID_QMI, 0x0112), ATH_LED(3, 0) }, + { } +}; + +void ath5k_led_enable(struct ath5k_hw *ah) +{ + if (test_bit(ATH_STAT_LEDSOFT, ah->status)) { + ath5k_hw_set_gpio_output(ah, ah->led_pin); + ath5k_led_off(ah); + } +} + +static void ath5k_led_on(struct ath5k_hw *ah) +{ + if (!test_bit(ATH_STAT_LEDSOFT, ah->status)) + return; + ath5k_hw_set_gpio(ah, ah->led_pin, ah->led_on); +} + +void ath5k_led_off(struct ath5k_hw *ah) +{ + if (!test_bit(ATH_STAT_LEDSOFT, ah->status)) + return; + ath5k_hw_set_gpio(ah, ah->led_pin, !ah->led_on); +} + +static void +ath5k_led_brightness_set(struct led_classdev *led_dev, + enum led_brightness brightness) +{ + struct ath5k_led *led = container_of(led_dev, struct ath5k_led, + led_dev); + + if (brightness == LED_OFF) + ath5k_led_off(led->ah); + else + ath5k_led_on(led->ah); +} + +static int +ath5k_register_led(struct ath5k_hw *ah, struct ath5k_led *led, + const char *name, char *trigger) +{ + int err; + + led->ah = ah; + strncpy(led->name, name, sizeof(led->name)); + led->led_dev.name = led->name; + led->led_dev.default_trigger = trigger; + led->led_dev.brightness_set = ath5k_led_brightness_set; + + err = led_classdev_register(ah->dev, &led->led_dev); + if (err) { + ATH5K_WARN(ah, "could not register LED %s\n", name); + led->ah = NULL; + } + return err; +} + +static void +ath5k_unregister_led(struct ath5k_led *led) +{ + if (!led->ah) + return; + led_classdev_unregister(&led->led_dev); + ath5k_led_off(led->ah); + led->ah = NULL; +} + +void ath5k_unregister_leds(struct ath5k_hw *ah) +{ + ath5k_unregister_led(&ah->rx_led); + ath5k_unregister_led(&ah->tx_led); +} + +int ath5k_init_leds(struct ath5k_hw *ah) +{ + int ret = 0; + struct ieee80211_hw *hw = ah->hw; +#ifndef CONFIG_ATHEROS_AR231X + struct pci_dev *pdev = ah->pdev; +#endif + char name[ATH5K_LED_MAX_NAME_LEN + 1]; + const struct pci_device_id *match; + + if (!ah->pdev) + return 0; + +#ifdef CONFIG_ATHEROS_AR231X + match = NULL; +#else + match = pci_match_id(&ath5k_led_devices[0], pdev); +#endif + if (match) { + __set_bit(ATH_STAT_LEDSOFT, ah->status); + ah->led_pin = ATH_PIN(match->driver_data); + ah->led_on = ATH_POLARITY(match->driver_data); + } + + if (!test_bit(ATH_STAT_LEDSOFT, ah->status)) + goto out; + + ath5k_led_enable(ah); + + snprintf(name, sizeof(name), "ath5k-%s::rx", wiphy_name(hw->wiphy)); + ret = ath5k_register_led(ah, &ah->rx_led, name, + ieee80211_get_rx_led_name(hw)); + if (ret) + goto out; + + snprintf(name, sizeof(name), "ath5k-%s::tx", wiphy_name(hw->wiphy)); + ret = ath5k_register_led(ah, &ah->tx_led, name, + ieee80211_get_tx_led_name(hw)); +out: + return ret; +} + diff --git a/drivers/net/wireless/ath/ath5k/mac80211-ops.c b/drivers/net/wireless/ath/ath5k/mac80211-ops.c new file mode 100644 index 0000000..dad82ca --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/mac80211-ops.c @@ -0,0 +1,854 @@ +/*- + * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting + * Copyright (c) 2004-2005 Atheros Communications, Inc. + * Copyright (c) 2006 Devicescape Software, Inc. + * Copyright (c) 2007 Jiri Slaby + * Copyright (c) 2007 Luis R. Rodriguez + * Copyright (c) 2010 Bruno Randolf + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include + +#include "ath5k.h" +#include "base.h" +#include "reg.h" + +/********************\ +* Mac80211 functions * +\********************/ + +static void +ath5k_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct ath5k_hw *ah = hw->priv; + u16 qnum = skb_get_queue_mapping(skb); + + if (WARN_ON(qnum >= ah->ah_capabilities.cap_queues.q_tx_num)) { + ieee80211_free_txskb(hw, skb); + return; + } + + ath5k_tx_queue(hw, skb, &ah->txqs[qnum]); +} + + +static int +ath5k_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct ath5k_hw *ah = hw->priv; + int ret; + struct ath5k_vif *avf = (void *)vif->drv_priv; + + mutex_lock(&ah->lock); + + if ((vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC) + && (ah->num_ap_vifs + ah->num_adhoc_vifs) >= ATH_BCBUF) { + ret = -ELNRNG; + goto end; + } + + /* Don't allow other interfaces if one ad-hoc is configured. + * TODO: Fix the problems with ad-hoc and multiple other interfaces. + * We would need to operate the HW in ad-hoc mode to allow TSF updates + * for the IBSS, but this breaks with additional AP or STA interfaces + * at the moment. */ + if (ah->num_adhoc_vifs || + (ah->nvifs && vif->type == NL80211_IFTYPE_ADHOC)) { + ATH5K_ERR(ah, "Only one single ad-hoc interface is allowed.\n"); + ret = -ELNRNG; + goto end; + } + + switch (vif->type) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_MESH_POINT: + avf->opmode = vif->type; + break; + default: + ret = -EOPNOTSUPP; + goto end; + } + + ah->nvifs++; + ATH5K_DBG(ah, ATH5K_DEBUG_MODE, "add interface mode %d\n", avf->opmode); + + /* Assign the vap/adhoc to a beacon xmit slot. */ + if ((avf->opmode == NL80211_IFTYPE_AP) || + (avf->opmode == NL80211_IFTYPE_ADHOC) || + (avf->opmode == NL80211_IFTYPE_MESH_POINT)) { + int slot; + + WARN_ON(list_empty(&ah->bcbuf)); + avf->bbuf = list_first_entry(&ah->bcbuf, struct ath5k_buf, + list); + list_del(&avf->bbuf->list); + + avf->bslot = 0; + for (slot = 0; slot < ATH_BCBUF; slot++) { + if (!ah->bslot[slot]) { + avf->bslot = slot; + break; + } + } + BUG_ON(ah->bslot[avf->bslot] != NULL); + ah->bslot[avf->bslot] = vif; + if (avf->opmode == NL80211_IFTYPE_AP) + ah->num_ap_vifs++; + else if (avf->opmode == NL80211_IFTYPE_ADHOC) + ah->num_adhoc_vifs++; + else if (avf->opmode == NL80211_IFTYPE_MESH_POINT) + ah->num_mesh_vifs++; + } + + /* Any MAC address is fine, all others are included through the + * filter. + */ + ath5k_hw_set_lladdr(ah, vif->addr); + + ath5k_update_bssid_mask_and_opmode(ah, vif); + ret = 0; +end: + mutex_unlock(&ah->lock); + return ret; +} + + +static void +ath5k_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct ath5k_hw *ah = hw->priv; + struct ath5k_vif *avf = (void *)vif->drv_priv; + unsigned int i; + + mutex_lock(&ah->lock); + ah->nvifs--; + + if (avf->bbuf) { + ath5k_txbuf_free_skb(ah, avf->bbuf); + list_add_tail(&avf->bbuf->list, &ah->bcbuf); + for (i = 0; i < ATH_BCBUF; i++) { + if (ah->bslot[i] == vif) { + ah->bslot[i] = NULL; + break; + } + } + avf->bbuf = NULL; + } + if (avf->opmode == NL80211_IFTYPE_AP) + ah->num_ap_vifs--; + else if (avf->opmode == NL80211_IFTYPE_ADHOC) + ah->num_adhoc_vifs--; + else if (avf->opmode == NL80211_IFTYPE_MESH_POINT) + ah->num_mesh_vifs--; + + ath5k_update_bssid_mask_and_opmode(ah, NULL); + mutex_unlock(&ah->lock); +} + + +/* + * TODO: Phy disable/diversity etc + */ +static int +ath5k_config(struct ieee80211_hw *hw, u32 changed) +{ + struct ath5k_hw *ah = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + int ret = 0; + int i; + + mutex_lock(&ah->lock); + + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + ret = ath5k_chan_set(ah, conf->chandef.chan); + if (ret < 0) + goto unlock; + } + + if ((changed & IEEE80211_CONF_CHANGE_POWER) && + (ah->ah_txpower.txp_requested != conf->power_level)) { + ah->ah_txpower.txp_requested = conf->power_level; + + /* Half dB steps */ + ath5k_hw_set_txpower_limit(ah, (conf->power_level * 2)); + } + + if (changed & IEEE80211_CONF_CHANGE_RETRY_LIMITS) { + ah->ah_retry_long = conf->long_frame_max_tx_count; + ah->ah_retry_short = conf->short_frame_max_tx_count; + + for (i = 0; i < ah->ah_capabilities.cap_queues.q_tx_num; i++) + ath5k_hw_set_tx_retry_limits(ah, i); + } + + /* TODO: + * 1) Move this on config_interface and handle each case + * separately eg. when we have only one STA vif, use + * AR5K_ANTMODE_SINGLE_AP + * + * 2) Allow the user to change antenna mode eg. when only + * one antenna is present + * + * 3) Allow the user to set default/tx antenna when possible + * + * 4) Default mode should handle 90% of the cases, together + * with fixed a/b and single AP modes we should be able to + * handle 99%. Sectored modes are extreme cases and i still + * haven't found a usage for them. If we decide to support them, + * then we must allow the user to set how many tx antennas we + * have available + */ + ath5k_hw_set_antenna_mode(ah, ah->ah_ant_mode); + +unlock: + mutex_unlock(&ah->lock); + return ret; +} + + +static void +ath5k_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, u32 changes) +{ + struct ath5k_vif *avf = (void *)vif->drv_priv; + struct ath5k_hw *ah = hw->priv; + struct ath_common *common = ath5k_hw_common(ah); + + mutex_lock(&ah->lock); + + if (changes & BSS_CHANGED_BSSID) { + /* Cache for later use during resets */ + memcpy(common->curbssid, bss_conf->bssid, ETH_ALEN); + common->curaid = 0; + ath5k_hw_set_bssid(ah); + mmiowb(); + } + + if (changes & BSS_CHANGED_BEACON_INT) + ah->bintval = bss_conf->beacon_int; + + if (changes & BSS_CHANGED_ERP_SLOT) { + int slot_time; + + ah->ah_short_slot = bss_conf->use_short_slot; + slot_time = ath5k_hw_get_default_slottime(ah) + + 3 * ah->ah_coverage_class; + ath5k_hw_set_ifs_intervals(ah, slot_time); + } + + if (changes & BSS_CHANGED_ASSOC) { + avf->assoc = bss_conf->assoc; + if (bss_conf->assoc) + ah->assoc = bss_conf->assoc; + else + ah->assoc = ath5k_any_vif_assoc(ah); + + if (ah->opmode == NL80211_IFTYPE_STATION) + ath5k_set_beacon_filter(hw, ah->assoc); + ath5k_hw_set_ledstate(ah, ah->assoc ? + AR5K_LED_ASSOC : AR5K_LED_INIT); + if (bss_conf->assoc) { + ATH5K_DBG(ah, ATH5K_DEBUG_ANY, + "Bss Info ASSOC %d, bssid: %pM\n", + bss_conf->aid, common->curbssid); + common->curaid = bss_conf->aid; + ath5k_hw_set_bssid(ah); + /* Once ANI is available you would start it here */ + } + } + + if (changes & BSS_CHANGED_BEACON) { + spin_lock_bh(&ah->block); + ath5k_beacon_update(hw, vif); + spin_unlock_bh(&ah->block); + } + + if (changes & BSS_CHANGED_BEACON_ENABLED) + ah->enable_beacon = bss_conf->enable_beacon; + + if (changes & (BSS_CHANGED_BEACON | BSS_CHANGED_BEACON_ENABLED | + BSS_CHANGED_BEACON_INT)) + ath5k_beacon_config(ah); + + mutex_unlock(&ah->lock); +} + + +static u64 +ath5k_prepare_multicast(struct ieee80211_hw *hw, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + struct netdev_hw_addr_list *mc_list) +#else + int mc_count, struct dev_addr_list *ha) +#endif +{ + u32 mfilt[2], val; + u8 pos; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + struct netdev_hw_addr *ha; +#else + int i; +#endif + + mfilt[0] = 0; + mfilt[1] = 1; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + netdev_hw_addr_list_for_each(ha, mc_list) { +#else + for (i = 0; i < mc_count; i++) { + if (!ha) + break; +#endif + /* calculate XOR of eight 6-bit values */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + val = get_unaligned_le32(ha->addr + 0); +#else + val = get_unaligned_le32(ha->dmi_addr + 0); +#endif + pos = (val >> 18) ^ (val >> 12) ^ (val >> 6) ^ val; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + val = get_unaligned_le32(ha->addr + 3); +#else + val = get_unaligned_le32(ha->dmi_addr + 3); +#endif + pos ^= (val >> 18) ^ (val >> 12) ^ (val >> 6) ^ val; + pos &= 0x3f; + mfilt[pos / 32] |= (1 << (pos % 32)); + /* XXX: we might be able to just do this instead, + * but not sure, needs testing, if we do use this we'd + * need to inform below not to reset the mcast */ + /* ath5k_hw_set_mcast_filterindex(ah, + * ha->addr[5]); */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)) + ha = ha->next; +#endif + } + + return ((u64)(mfilt[1]) << 32) | mfilt[0]; +} + + +/* + * o always accept unicast, broadcast, and multicast traffic + * o multicast traffic for all BSSIDs will be enabled if mac80211 + * says it should be + * o maintain current state of phy ofdm or phy cck error reception. + * If the hardware detects any of these type of errors then + * ath5k_hw_get_rx_filter() will pass to us the respective + * hardware filters to be able to receive these type of frames. + * o probe request frames are accepted only when operating in + * hostap, adhoc, or monitor modes + * o enable promiscuous mode according to the interface state + * o accept beacons: + * - when operating in adhoc mode so the 802.11 layer creates + * node table entries for peers, + * - when operating in station mode for collecting rssi data when + * the station is otherwise quiet, or + * - when scanning + */ +static void +ath5k_configure_filter(struct ieee80211_hw *hw, unsigned int changed_flags, + unsigned int *new_flags, u64 multicast) +{ +#define SUPPORTED_FIF_FLAGS \ + (FIF_PROMISC_IN_BSS | FIF_ALLMULTI | FIF_FCSFAIL | \ + FIF_PLCPFAIL | FIF_CONTROL | FIF_OTHER_BSS | \ + FIF_BCN_PRBRESP_PROMISC) + + struct ath5k_hw *ah = hw->priv; + u32 mfilt[2], rfilt; + struct ath5k_vif_iter_data iter_data; /* to count STA interfaces */ + + mutex_lock(&ah->lock); + + mfilt[0] = multicast; + mfilt[1] = multicast >> 32; + + /* Only deal with supported flags */ + changed_flags &= SUPPORTED_FIF_FLAGS; + *new_flags &= SUPPORTED_FIF_FLAGS; + + /* If HW detects any phy or radar errors, leave those filters on. + * Also, always enable Unicast, Broadcasts and Multicast + * XXX: move unicast, bssid broadcasts and multicast to mac80211 */ + rfilt = (ath5k_hw_get_rx_filter(ah) & (AR5K_RX_FILTER_PHYERR)) | + (AR5K_RX_FILTER_UCAST | AR5K_RX_FILTER_BCAST | + AR5K_RX_FILTER_MCAST); + + if (changed_flags & (FIF_PROMISC_IN_BSS | FIF_OTHER_BSS)) { + if (*new_flags & FIF_PROMISC_IN_BSS) + __set_bit(ATH_STAT_PROMISC, ah->status); + else + __clear_bit(ATH_STAT_PROMISC, ah->status); + } + + if (test_bit(ATH_STAT_PROMISC, ah->status)) + rfilt |= AR5K_RX_FILTER_PROM; + + /* Note, AR5K_RX_FILTER_MCAST is already enabled */ + if (*new_flags & FIF_ALLMULTI) { + mfilt[0] = ~0; + mfilt[1] = ~0; + } + + /* This is the best we can do */ + if (*new_flags & (FIF_FCSFAIL | FIF_PLCPFAIL)) + rfilt |= AR5K_RX_FILTER_PHYERR; + + /* FIF_BCN_PRBRESP_PROMISC really means to enable beacons + * and probes for any BSSID */ + if ((*new_flags & FIF_BCN_PRBRESP_PROMISC) || (ah->nvifs > 1)) + rfilt |= AR5K_RX_FILTER_BEACON; + + /* FIF_CONTROL doc says that if FIF_PROMISC_IN_BSS is not + * set we should only pass on control frames for this + * station. This needs testing. I believe right now this + * enables *all* control frames, which is OK.. but + * but we should see if we can improve on granularity */ + if (*new_flags & FIF_CONTROL) + rfilt |= AR5K_RX_FILTER_CONTROL; + + /* Additional settings per mode -- this is per ath5k */ + + /* XXX move these to mac80211, and add a beacon IFF flag to mac80211 */ + + switch (ah->opmode) { + case NL80211_IFTYPE_MESH_POINT: + rfilt |= AR5K_RX_FILTER_CONTROL | + AR5K_RX_FILTER_BEACON | + AR5K_RX_FILTER_PROBEREQ | + AR5K_RX_FILTER_PROM; + break; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_ADHOC: + rfilt |= AR5K_RX_FILTER_PROBEREQ | + AR5K_RX_FILTER_BEACON; + break; + case NL80211_IFTYPE_STATION: + if (ah->assoc) + rfilt |= AR5K_RX_FILTER_BEACON; + default: + break; + } + + iter_data.hw_macaddr = NULL; + iter_data.n_stas = 0; + iter_data.need_set_hw_addr = false; + ieee80211_iterate_active_interfaces_atomic( + ah->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + ath5k_vif_iter, &iter_data); + + /* Set up RX Filter */ + if (iter_data.n_stas > 1) { + /* If you have multiple STA interfaces connected to + * different APs, ARPs are not received (most of the time?) + * Enabling PROMISC appears to fix that problem. + */ + rfilt |= AR5K_RX_FILTER_PROM; + } + + /* Set filters */ + ath5k_hw_set_rx_filter(ah, rfilt); + + /* Set multicast bits */ + ath5k_hw_set_mcast_filter(ah, mfilt[0], mfilt[1]); + /* Set the cached hw filter flags, this will later actually + * be set in HW */ + ah->filter_flags = rfilt; + + mutex_unlock(&ah->lock); +} + + +static int +ath5k_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct ath5k_hw *ah = hw->priv; + struct ath_common *common = ath5k_hw_common(ah); + int ret = 0; + + if (ath5k_modparam_nohwcrypt) + return -EOPNOTSUPP; + + if (key->flags & IEEE80211_KEY_FLAG_RX_MGMT) + return -EOPNOTSUPP; + + if (vif->type == NL80211_IFTYPE_ADHOC && + (key->cipher == WLAN_CIPHER_SUITE_TKIP || + key->cipher == WLAN_CIPHER_SUITE_CCMP) && + !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) { + /* don't program group keys when using IBSS_RSN */ + return -EOPNOTSUPP; + } + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + case WLAN_CIPHER_SUITE_TKIP: + break; + case WLAN_CIPHER_SUITE_CCMP: + if (common->crypt_caps & ATH_CRYPT_CAP_CIPHER_AESCCM) + break; + return -EOPNOTSUPP; + default: + WARN_ON(1); + return -EINVAL; + } + + mutex_lock(&ah->lock); + + switch (cmd) { + case SET_KEY: + ret = ath_key_config(common, vif, sta, key); + if (ret >= 0) { + key->hw_key_idx = ret; + /* push IV and Michael MIC generation to stack */ + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + if (key->cipher == WLAN_CIPHER_SUITE_TKIP) + key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC; + if (key->cipher == WLAN_CIPHER_SUITE_CCMP) + key->flags |= IEEE80211_KEY_FLAG_SW_MGMT_TX; + ret = 0; + } + break; + case DISABLE_KEY: + ath_key_delete(common, key); + break; + default: + ret = -EINVAL; + } + + mmiowb(); + mutex_unlock(&ah->lock); + return ret; +} + + +static void +ath5k_sw_scan_start(struct ieee80211_hw *hw) +{ + struct ath5k_hw *ah = hw->priv; + if (!ah->assoc) + ath5k_hw_set_ledstate(ah, AR5K_LED_SCAN); +} + + +static void +ath5k_sw_scan_complete(struct ieee80211_hw *hw) +{ + struct ath5k_hw *ah = hw->priv; + ath5k_hw_set_ledstate(ah, ah->assoc ? + AR5K_LED_ASSOC : AR5K_LED_INIT); +} + + +static int +ath5k_get_stats(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + struct ath5k_hw *ah = hw->priv; + + /* Force update */ + ath5k_hw_update_mib_counters(ah); + + stats->dot11ACKFailureCount = ah->stats.ack_fail; + stats->dot11RTSFailureCount = ah->stats.rts_fail; + stats->dot11RTSSuccessCount = ah->stats.rts_ok; + stats->dot11FCSErrorCount = ah->stats.fcs_error; + + return 0; +} + + +static int +ath5k_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct ath5k_hw *ah = hw->priv; + struct ath5k_txq_info qi; + int ret = 0; + + if (queue >= ah->ah_capabilities.cap_queues.q_tx_num) + return 0; + + mutex_lock(&ah->lock); + + ath5k_hw_get_tx_queueprops(ah, queue, &qi); + + qi.tqi_aifs = params->aifs; + qi.tqi_cw_min = params->cw_min; + qi.tqi_cw_max = params->cw_max; + qi.tqi_burst_time = params->txop * 32; + + ATH5K_DBG(ah, ATH5K_DEBUG_ANY, + "Configure tx [queue %d], " + "aifs: %d, cw_min: %d, cw_max: %d, txop: %d\n", + queue, params->aifs, params->cw_min, + params->cw_max, params->txop); + + if (ath5k_hw_set_tx_queueprops(ah, queue, &qi)) { + ATH5K_ERR(ah, + "Unable to update hardware queue %u!\n", queue); + ret = -EIO; + } else + ath5k_hw_reset_tx_queue(ah, queue); + + mutex_unlock(&ah->lock); + + return ret; +} + + +static u64 +ath5k_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct ath5k_hw *ah = hw->priv; + + return ath5k_hw_get_tsf64(ah); +} + + +static void +ath5k_set_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u64 tsf) +{ + struct ath5k_hw *ah = hw->priv; + + ath5k_hw_set_tsf64(ah, tsf); +} + + +static void +ath5k_reset_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct ath5k_hw *ah = hw->priv; + + /* + * in IBSS mode we need to update the beacon timers too. + * this will also reset the TSF if we call it with 0 + */ + if (ah->opmode == NL80211_IFTYPE_ADHOC) + ath5k_beacon_update_timers(ah, 0); + else + ath5k_hw_reset_tsf(ah); +} + + +static int +ath5k_get_survey(struct ieee80211_hw *hw, int idx, struct survey_info *survey) +{ + struct ath5k_hw *ah = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + struct ath_common *common = ath5k_hw_common(ah); + struct ath_cycle_counters *cc = &common->cc_survey; + unsigned int div = common->clockrate * 1000; + + if (idx != 0) + return -ENOENT; + + spin_lock_bh(&common->cc_lock); + ath_hw_cycle_counters_update(common); + if (cc->cycles > 0) { + ah->survey.channel_time += cc->cycles / div; + ah->survey.channel_time_busy += cc->rx_busy / div; + ah->survey.channel_time_rx += cc->rx_frame / div; + ah->survey.channel_time_tx += cc->tx_frame / div; + } + memset(cc, 0, sizeof(*cc)); + spin_unlock_bh(&common->cc_lock); + + memcpy(survey, &ah->survey, sizeof(*survey)); + + survey->channel = conf->chandef.chan; + survey->noise = ah->ah_noise_floor; + survey->filled = SURVEY_INFO_NOISE_DBM | + SURVEY_INFO_CHANNEL_TIME | + SURVEY_INFO_CHANNEL_TIME_BUSY | + SURVEY_INFO_CHANNEL_TIME_RX | + SURVEY_INFO_CHANNEL_TIME_TX; + + return 0; +} + + +/** + * ath5k_set_coverage_class - Set IEEE 802.11 coverage class + * + * @hw: struct ieee80211_hw pointer + * @coverage_class: IEEE 802.11 coverage class number + * + * Mac80211 callback. Sets slot time, ACK timeout and CTS timeout for given + * coverage class. The values are persistent, they are restored after device + * reset. + */ +static void +ath5k_set_coverage_class(struct ieee80211_hw *hw, u8 coverage_class) +{ + struct ath5k_hw *ah = hw->priv; + + mutex_lock(&ah->lock); + ath5k_hw_set_coverage_class(ah, coverage_class); + mutex_unlock(&ah->lock); +} + + +static int +ath5k_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +{ + struct ath5k_hw *ah = hw->priv; + + if (tx_ant == 1 && rx_ant == 1) + ath5k_hw_set_antenna_mode(ah, AR5K_ANTMODE_FIXED_A); + else if (tx_ant == 2 && rx_ant == 2) + ath5k_hw_set_antenna_mode(ah, AR5K_ANTMODE_FIXED_B); + else if ((tx_ant & 3) == 3 && (rx_ant & 3) == 3) + ath5k_hw_set_antenna_mode(ah, AR5K_ANTMODE_DEFAULT); + else + return -EINVAL; + return 0; +} + + +static int +ath5k_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) +{ + struct ath5k_hw *ah = hw->priv; + + switch (ah->ah_ant_mode) { + case AR5K_ANTMODE_FIXED_A: + *tx_ant = 1; *rx_ant = 1; break; + case AR5K_ANTMODE_FIXED_B: + *tx_ant = 2; *rx_ant = 2; break; + case AR5K_ANTMODE_DEFAULT: + *tx_ant = 3; *rx_ant = 3; break; + } + return 0; +} + + +static void ath5k_get_ringparam(struct ieee80211_hw *hw, + u32 *tx, u32 *tx_max, u32 *rx, u32 *rx_max) +{ + struct ath5k_hw *ah = hw->priv; + + *tx = ah->txqs[AR5K_TX_QUEUE_ID_DATA_MIN].txq_max; + + *tx_max = ATH5K_TXQ_LEN_MAX; + *rx = *rx_max = ATH_RXBUF; +} + + +static int ath5k_set_ringparam(struct ieee80211_hw *hw, u32 tx, u32 rx) +{ + struct ath5k_hw *ah = hw->priv; + u16 qnum; + + /* only support setting tx ring size for now */ + if (rx != ATH_RXBUF) + return -EINVAL; + + /* restrict tx ring size min/max */ + if (!tx || tx > ATH5K_TXQ_LEN_MAX) + return -EINVAL; + + for (qnum = 0; qnum < ARRAY_SIZE(ah->txqs); qnum++) { + if (!ah->txqs[qnum].setup) + continue; + if (ah->txqs[qnum].qnum < AR5K_TX_QUEUE_ID_DATA_MIN || + ah->txqs[qnum].qnum > AR5K_TX_QUEUE_ID_DATA_MAX) + continue; + + ah->txqs[qnum].txq_max = tx; + if (ah->txqs[qnum].txq_len >= ah->txqs[qnum].txq_max) + ieee80211_stop_queue(hw, ah->txqs[qnum].qnum); + } + + return 0; +} + + +const struct ieee80211_ops ath5k_hw_ops = { + .tx = ath5k_tx, + .start = ath5k_start, + .stop = ath5k_stop, + .add_interface = ath5k_add_interface, + /* .change_interface = not implemented */ + .remove_interface = ath5k_remove_interface, + .config = ath5k_config, + .bss_info_changed = ath5k_bss_info_changed, + .prepare_multicast = ath5k_prepare_multicast, + .configure_filter = ath5k_configure_filter, + /* .set_tim = not implemented */ + .set_key = ath5k_set_key, + /* .update_tkip_key = not implemented */ + /* .hw_scan = not implemented */ + .sw_scan_start = ath5k_sw_scan_start, + .sw_scan_complete = ath5k_sw_scan_complete, + .get_stats = ath5k_get_stats, + /* .get_tkip_seq = not implemented */ + /* .set_frag_threshold = not implemented */ + /* .set_rts_threshold = not implemented */ + /* .sta_add = not implemented */ + /* .sta_remove = not implemented */ + /* .sta_notify = not implemented */ + .conf_tx = ath5k_conf_tx, + .get_tsf = ath5k_get_tsf, + .set_tsf = ath5k_set_tsf, + .reset_tsf = ath5k_reset_tsf, + /* .tx_last_beacon = not implemented */ + /* .ampdu_action = not needed */ + .get_survey = ath5k_get_survey, + .set_coverage_class = ath5k_set_coverage_class, + /* .rfkill_poll = not implemented */ + /* .flush = not implemented */ + /* .channel_switch = not implemented */ + /* .napi_poll = not implemented */ + .set_antenna = ath5k_set_antenna, + .get_antenna = ath5k_get_antenna, + .set_ringparam = ath5k_set_ringparam, + .get_ringparam = ath5k_get_ringparam, +}; diff --git a/drivers/net/wireless/ath/ath5k/pci.c b/drivers/net/wireless/ath/ath5k/pci.c new file mode 100644 index 0000000..73bb604 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/pci.c @@ -0,0 +1,351 @@ +/* + * Copyright (c) 2008-2009 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include "../ath.h" +#include "ath5k.h" +#include "debug.h" +#include "base.h" +#include "reg.h" + +/* Known PCI ids */ +static DEFINE_PCI_DEVICE_TABLE(ath5k_pci_id_table) = { + { PCI_VDEVICE(ATHEROS, 0x0207) }, /* 5210 early */ + { PCI_VDEVICE(ATHEROS, 0x0007) }, /* 5210 */ + { PCI_VDEVICE(ATHEROS, 0x0011) }, /* 5311 - this is on AHB bus !*/ + { PCI_VDEVICE(ATHEROS, 0x0012) }, /* 5211 */ + { PCI_VDEVICE(ATHEROS, 0x0013) }, /* 5212 */ + { PCI_VDEVICE(3COM_2, 0x0013) }, /* 3com 5212 */ + { PCI_VDEVICE(3COM, 0x0013) }, /* 3com 3CRDAG675 5212 */ + { PCI_VDEVICE(ATHEROS, 0x1014) }, /* IBM minipci 5212 */ + { PCI_VDEVICE(ATHEROS, 0x0014) }, /* 5212 compatible */ + { PCI_VDEVICE(ATHEROS, 0x0015) }, /* 5212 compatible */ + { PCI_VDEVICE(ATHEROS, 0x0016) }, /* 5212 compatible */ + { PCI_VDEVICE(ATHEROS, 0x0017) }, /* 5212 compatible */ + { PCI_VDEVICE(ATHEROS, 0x0018) }, /* 5212 compatible */ + { PCI_VDEVICE(ATHEROS, 0x0019) }, /* 5212 compatible */ + { PCI_VDEVICE(ATHEROS, 0x001a) }, /* 2413 Griffin-lite */ + { PCI_VDEVICE(ATHEROS, 0x001b) }, /* 5413 Eagle */ + { PCI_VDEVICE(ATHEROS, 0x001c) }, /* PCI-E cards */ + { PCI_VDEVICE(ATHEROS, 0x001d) }, /* 2417 Nala */ + { PCI_VDEVICE(ATHEROS, 0xff1b) }, /* AR5BXB63 */ + { 0 } +}; +MODULE_DEVICE_TABLE(pci, ath5k_pci_id_table); + +/* return bus cachesize in 4B word units */ +static void ath5k_pci_read_cachesize(struct ath_common *common, int *csz) +{ + struct ath5k_hw *ah = (struct ath5k_hw *) common->priv; + u8 u8tmp; + + pci_read_config_byte(ah->pdev, PCI_CACHE_LINE_SIZE, &u8tmp); + *csz = (int)u8tmp; + + /* + * This check was put in to avoid "unpleasant" consequences if + * the bootrom has not fully initialized all PCI devices. + * Sometimes the cache line size register is not set + */ + + if (*csz == 0) + *csz = L1_CACHE_BYTES >> 2; /* Use the default size */ +} + +/* + * Read from eeprom + */ +static bool +ath5k_pci_eeprom_read(struct ath_common *common, u32 offset, u16 *data) +{ + struct ath5k_hw *ah = (struct ath5k_hw *) common->ah; + u32 status, timeout; + + /* + * Initialize EEPROM access + */ + if (ah->ah_version == AR5K_AR5210) { + AR5K_REG_ENABLE_BITS(ah, AR5K_PCICFG, AR5K_PCICFG_EEAE); + (void)ath5k_hw_reg_read(ah, AR5K_EEPROM_BASE + (4 * offset)); + } else { + ath5k_hw_reg_write(ah, offset, AR5K_EEPROM_BASE); + AR5K_REG_ENABLE_BITS(ah, AR5K_EEPROM_CMD, + AR5K_EEPROM_CMD_READ); + } + + for (timeout = AR5K_TUNE_REGISTER_TIMEOUT; timeout > 0; timeout--) { + status = ath5k_hw_reg_read(ah, AR5K_EEPROM_STATUS); + if (status & AR5K_EEPROM_STAT_RDDONE) { + if (status & AR5K_EEPROM_STAT_RDERR) + return false; + *data = (u16)(ath5k_hw_reg_read(ah, AR5K_EEPROM_DATA) & + 0xffff); + return true; + } + usleep_range(15, 20); + } + + return false; +} + +int ath5k_hw_read_srev(struct ath5k_hw *ah) +{ + ah->ah_mac_srev = ath5k_hw_reg_read(ah, AR5K_SREV); + return 0; +} + +/* + * Read the MAC address from eeprom or platform_data + */ +static int ath5k_pci_eeprom_read_mac(struct ath5k_hw *ah, u8 *mac) +{ + u8 mac_d[ETH_ALEN] = {}; + u32 total, offset; + u16 data; + int octet; + + AR5K_EEPROM_READ(0x20, data); + + for (offset = 0x1f, octet = 0, total = 0; offset >= 0x1d; offset--) { + AR5K_EEPROM_READ(offset, data); + + total += data; + mac_d[octet + 1] = data & 0xff; + mac_d[octet] = data >> 8; + octet += 2; + } + + if (!total || total == 3 * 0xffff) + return -EINVAL; + + memcpy(mac, mac_d, ETH_ALEN); + + return 0; +} + + +/* Common ath_bus_opts structure */ +static const struct ath_bus_ops ath_pci_bus_ops = { + .ath_bus_type = ATH_PCI, + .read_cachesize = ath5k_pci_read_cachesize, + .eeprom_read = ath5k_pci_eeprom_read, + .eeprom_read_mac = ath5k_pci_eeprom_read_mac, +}; + +/********************\ +* PCI Initialization * +\********************/ + +static int +ath5k_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + void __iomem *mem; + struct ath5k_hw *ah; + struct ieee80211_hw *hw; + int ret; + u8 csz; + + /* + * L0s needs to be disabled on all ath5k cards. + * + * For distributions shipping with CONFIG_PCIEASPM (this will be enabled + * by default in the future in 2.6.36) this will also mean both L1 and + * L0s will be disabled when a pre 1.1 PCIe device is detected. We do + * know L1 works correctly even for all ath5k pre 1.1 PCIe devices + * though but cannot currently undue the effect of a blacklist, for + * details you can read pcie_aspm_sanity_check() and see how it adjusts + * the device link capability. + * + * It may be possible in the future to implement some PCI API to allow + * drivers to override blacklists for pre 1.1 PCIe but for now it is + * best to accept that both L0s and L1 will be disabled completely for + * distributions shipping with CONFIG_PCIEASPM rather than having this + * issue present. Motivation for adding this new API will be to help + * with power consumption for some of these devices. + */ + pci_disable_link_state(pdev, PCIE_LINK_STATE_L0S); + + ret = pci_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "can't enable device\n"); + goto err; + } + + /* XXX 32-bit addressing only */ + ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(&pdev->dev, "32-bit DMA not available\n"); + goto err_dis; + } + + /* + * Cache line size is used to size and align various + * structures used to communicate with the hardware. + */ + pci_read_config_byte(pdev, PCI_CACHE_LINE_SIZE, &csz); + if (csz == 0) { + /* + * Linux 2.4.18 (at least) writes the cache line size + * register as a 16-bit wide register which is wrong. + * We must have this setup properly for rx buffer + * DMA to work so force a reasonable value here if it + * comes up zero. + */ + csz = L1_CACHE_BYTES >> 2; + pci_write_config_byte(pdev, PCI_CACHE_LINE_SIZE, csz); + } + /* + * The default setting of latency timer yields poor results, + * set it to the value used by other systems. It may be worth + * tweaking this setting more. + */ + pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 0xa8); + + /* Enable bus mastering */ + pci_set_master(pdev); + + /* + * Disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state. + */ + pci_write_config_byte(pdev, 0x41, 0); + + ret = pci_request_region(pdev, 0, "ath5k"); + if (ret) { + dev_err(&pdev->dev, "cannot reserve PCI memory region\n"); + goto err_dis; + } + + mem = pci_iomap(pdev, 0, 0); + if (!mem) { + dev_err(&pdev->dev, "cannot remap PCI memory region\n"); + ret = -EIO; + goto err_reg; + } + + /* + * Allocate hw (mac80211 main struct) + * and hw->priv (driver private data) + */ + hw = ieee80211_alloc_hw(sizeof(*ah), &ath5k_hw_ops); + if (hw == NULL) { + dev_err(&pdev->dev, "cannot allocate ieee80211_hw\n"); + ret = -ENOMEM; + goto err_map; + } + + dev_info(&pdev->dev, "registered as '%s'\n", wiphy_name(hw->wiphy)); + + ah = hw->priv; + ah->hw = hw; + ah->pdev = pdev; + ah->dev = &pdev->dev; + ah->irq = pdev->irq; + ah->devid = id->device; + ah->iobase = mem; /* So we can unmap it on detach */ + + /* Initialize */ + ret = ath5k_init_ah(ah, &ath_pci_bus_ops); + if (ret) + goto err_free; + + /* Set private data */ + pci_set_drvdata(pdev, hw); + + return 0; +err_free: + ieee80211_free_hw(hw); +err_map: + pci_iounmap(pdev, mem); +err_reg: + pci_release_region(pdev, 0); +err_dis: + pci_disable_device(pdev); +err: + return ret; +} + +static void +ath5k_pci_remove(struct pci_dev *pdev) +{ + struct ieee80211_hw *hw = pci_get_drvdata(pdev); + struct ath5k_hw *ah = hw->priv; + + ath5k_deinit_ah(ah); + pci_iounmap(pdev, ah->iobase); + pci_release_region(pdev, 0); + pci_disable_device(pdev); + ieee80211_free_hw(hw); +} + +#ifdef CONFIG_PM_SLEEP +static int ath5k_pci_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct ieee80211_hw *hw = pci_get_drvdata(pdev); + struct ath5k_hw *ah = hw->priv; + + ath5k_led_off(ah); + return 0; +} + +static int ath5k_pci_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct ieee80211_hw *hw = pci_get_drvdata(pdev); + struct ath5k_hw *ah = hw->priv; + + /* + * Suspend/Resume resets the PCI configuration space, so we have to + * re-disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state + */ + pci_write_config_byte(pdev, 0x41, 0); + + ath5k_led_enable(ah); + return 0; +} + +compat_pci_suspend(ath5k_pci_suspend) +compat_pci_resume(ath5k_pci_resume) + +static SIMPLE_DEV_PM_OPS(ath5k_pm_ops, ath5k_pci_suspend, ath5k_pci_resume); +#define ATH5K_PM_OPS (&ath5k_pm_ops) +#else +#define ATH5K_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + +static struct pci_driver ath5k_pci_driver = { + .name = KBUILD_MODNAME, + .id_table = ath5k_pci_id_table, + .probe = ath5k_pci_probe, + .remove = ath5k_pci_remove, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) + .driver.pm = ATH5K_PM_OPS, +#elif defined(CONFIG_PM_SLEEP) + .suspend = ath5k_pci_suspend_compat, + .resume = ath5k_pci_resume_compat, +#endif +}; + +module_pci_driver(ath5k_pci_driver); diff --git a/drivers/net/wireless/ath/ath5k/pcu.c b/drivers/net/wireless/ath/ath5k/pcu.c new file mode 100644 index 0000000..1f16b42 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/pcu.c @@ -0,0 +1,1007 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter + * Copyright (c) 2006-2008 Nick Kossifidis + * Copyright (c) 2007-2008 Matthew W. S. Bell + * Copyright (c) 2007-2008 Luis Rodriguez + * Copyright (c) 2007-2008 Pavel Roskin + * Copyright (c) 2007-2008 Jiri Slaby + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/*********************************\ +* Protocol Control Unit Functions * +\*********************************/ + +#include + +#include "ath5k.h" +#include "reg.h" +#include "debug.h" + +/** + * DOC: Protocol Control Unit (PCU) functions + * + * Protocol control unit is responsible to maintain various protocol + * properties before a frame is send and after a frame is received to/from + * baseband. To be more specific, PCU handles: + * + * - Buffering of RX and TX frames (after QCU/DCUs) + * + * - Encrypting and decrypting (using the built-in engine) + * + * - Generating ACKs, RTS/CTS frames + * + * - Maintaining TSF + * + * - FCS + * + * - Updating beacon data (with TSF etc) + * + * - Generating virtual CCA + * + * - RX/Multicast filtering + * + * - BSSID filtering + * + * - Various statistics + * + * -Different operating modes: AP, STA, IBSS + * + * Note: Most of these functions can be tweaked/bypassed so you can do + * them on sw above for debugging or research. For more infos check out PCU + * registers on reg.h. + */ + +/** + * DOC: ACK rates + * + * AR5212+ can use higher rates for ack transmission + * based on current tx rate instead of the base rate. + * It does this to better utilize channel usage. + * There is a mapping between G rates (that cover both + * CCK and OFDM) and ack rates that we use when setting + * rate -> duration table. This mapping is hw-based so + * don't change anything. + * + * To enable this functionality we must set + * ah->ah_ack_bitrate_high to true else base rate is + * used (1Mb for CCK, 6Mb for OFDM). + */ +static const unsigned int ack_rates_high[] = +/* Tx -> ACK */ +/* 1Mb -> 1Mb */ { 0, +/* 2MB -> 2Mb */ 1, +/* 5.5Mb -> 2Mb */ 1, +/* 11Mb -> 2Mb */ 1, +/* 6Mb -> 6Mb */ 4, +/* 9Mb -> 6Mb */ 4, +/* 12Mb -> 12Mb */ 6, +/* 18Mb -> 12Mb */ 6, +/* 24Mb -> 24Mb */ 8, +/* 36Mb -> 24Mb */ 8, +/* 48Mb -> 24Mb */ 8, +/* 54Mb -> 24Mb */ 8 }; + +/*******************\ +* Helper functions * +\*******************/ + +/** + * ath5k_hw_get_frame_duration() - Get tx time of a frame + * @ah: The &struct ath5k_hw + * @len: Frame's length in bytes + * @rate: The @struct ieee80211_rate + * @shortpre: Indicate short preample + * + * Calculate tx duration of a frame given it's rate and length + * It extends ieee80211_generic_frame_duration for non standard + * bwmodes. + */ +int +ath5k_hw_get_frame_duration(struct ath5k_hw *ah, enum ieee80211_band band, + int len, struct ieee80211_rate *rate, bool shortpre) +{ + int sifs, preamble, plcp_bits, sym_time; + int bitrate, bits, symbols, symbol_bits; + int dur; + + /* Fallback */ + if (!ah->ah_bwmode) { + __le16 raw_dur = ieee80211_generic_frame_duration(ah->hw, + NULL, band, len, rate); + + /* subtract difference between long and short preamble */ + dur = le16_to_cpu(raw_dur); + if (shortpre) + dur -= 96; + + return dur; + } + + bitrate = rate->bitrate; + preamble = AR5K_INIT_OFDM_PREAMPLE_TIME; + plcp_bits = AR5K_INIT_OFDM_PLCP_BITS; + sym_time = AR5K_INIT_OFDM_SYMBOL_TIME; + + switch (ah->ah_bwmode) { + case AR5K_BWMODE_40MHZ: + sifs = AR5K_INIT_SIFS_TURBO; + preamble = AR5K_INIT_OFDM_PREAMBLE_TIME_MIN; + break; + case AR5K_BWMODE_10MHZ: + sifs = AR5K_INIT_SIFS_HALF_RATE; + preamble *= 2; + sym_time *= 2; + break; + case AR5K_BWMODE_5MHZ: + sifs = AR5K_INIT_SIFS_QUARTER_RATE; + preamble *= 4; + sym_time *= 4; + break; + default: + sifs = AR5K_INIT_SIFS_DEFAULT_BG; + break; + } + + bits = plcp_bits + (len << 3); + /* Bit rate is in 100Kbits */ + symbol_bits = bitrate * sym_time; + symbols = DIV_ROUND_UP(bits * 10, symbol_bits); + + dur = sifs + preamble + (sym_time * symbols); + + return dur; +} + +/** + * ath5k_hw_get_default_slottime() - Get the default slot time for current mode + * @ah: The &struct ath5k_hw + */ +unsigned int +ath5k_hw_get_default_slottime(struct ath5k_hw *ah) +{ + struct ieee80211_channel *channel = ah->ah_current_channel; + unsigned int slot_time; + + switch (ah->ah_bwmode) { + case AR5K_BWMODE_40MHZ: + slot_time = AR5K_INIT_SLOT_TIME_TURBO; + break; + case AR5K_BWMODE_10MHZ: + slot_time = AR5K_INIT_SLOT_TIME_HALF_RATE; + break; + case AR5K_BWMODE_5MHZ: + slot_time = AR5K_INIT_SLOT_TIME_QUARTER_RATE; + break; + case AR5K_BWMODE_DEFAULT: + default: + slot_time = AR5K_INIT_SLOT_TIME_DEFAULT; + if ((channel->hw_value == AR5K_MODE_11B) && !ah->ah_short_slot) + slot_time = AR5K_INIT_SLOT_TIME_B; + break; + } + + return slot_time; +} + +/** + * ath5k_hw_get_default_sifs() - Get the default SIFS for current mode + * @ah: The &struct ath5k_hw + */ +unsigned int +ath5k_hw_get_default_sifs(struct ath5k_hw *ah) +{ + struct ieee80211_channel *channel = ah->ah_current_channel; + unsigned int sifs; + + switch (ah->ah_bwmode) { + case AR5K_BWMODE_40MHZ: + sifs = AR5K_INIT_SIFS_TURBO; + break; + case AR5K_BWMODE_10MHZ: + sifs = AR5K_INIT_SIFS_HALF_RATE; + break; + case AR5K_BWMODE_5MHZ: + sifs = AR5K_INIT_SIFS_QUARTER_RATE; + break; + case AR5K_BWMODE_DEFAULT: + sifs = AR5K_INIT_SIFS_DEFAULT_BG; + default: + if (channel->band == IEEE80211_BAND_5GHZ) + sifs = AR5K_INIT_SIFS_DEFAULT_A; + break; + } + + return sifs; +} + +/** + * ath5k_hw_update_mib_counters() - Update MIB counters (mac layer statistics) + * @ah: The &struct ath5k_hw + * + * Reads MIB counters from PCU and updates sw statistics. Is called after a + * MIB interrupt, because one of these counters might have reached their maximum + * and triggered the MIB interrupt, to let us read and clear the counter. + * + * NOTE: Is called in interrupt context! + */ +void +ath5k_hw_update_mib_counters(struct ath5k_hw *ah) +{ + struct ath5k_statistics *stats = &ah->stats; + + /* Read-And-Clear */ + stats->ack_fail += ath5k_hw_reg_read(ah, AR5K_ACK_FAIL); + stats->rts_fail += ath5k_hw_reg_read(ah, AR5K_RTS_FAIL); + stats->rts_ok += ath5k_hw_reg_read(ah, AR5K_RTS_OK); + stats->fcs_error += ath5k_hw_reg_read(ah, AR5K_FCS_FAIL); + stats->beacons += ath5k_hw_reg_read(ah, AR5K_BEACON_CNT); +} + + +/******************\ +* ACK/CTS Timeouts * +\******************/ + +/** + * ath5k_hw_write_rate_duration() - Fill rate code to duration table + * @ah: The &struct ath5k_hw + * + * Write the rate code to duration table upon hw reset. This is a helper for + * ath5k_hw_pcu_init(). It seems all this is doing is setting an ACK timeout on + * the hardware, based on current mode, for each rate. The rates which are + * capable of short preamble (802.11b rates 2Mbps, 5.5Mbps, and 11Mbps) have + * different rate code so we write their value twice (one for long preamble + * and one for short). + * + * Note: Band doesn't matter here, if we set the values for OFDM it works + * on both a and g modes. So all we have to do is set values for all g rates + * that include all OFDM and CCK rates. + * + */ +static inline void +ath5k_hw_write_rate_duration(struct ath5k_hw *ah) +{ + struct ieee80211_rate *rate; + unsigned int i; + /* 802.11g covers both OFDM and CCK */ + u8 band = IEEE80211_BAND_2GHZ; + + /* Write rate duration table */ + for (i = 0; i < ah->sbands[band].n_bitrates; i++) { + u32 reg; + u16 tx_time; + + if (ah->ah_ack_bitrate_high) + rate = &ah->sbands[band].bitrates[ack_rates_high[i]]; + /* CCK -> 1Mb */ + else if (i < 4) + rate = &ah->sbands[band].bitrates[0]; + /* OFDM -> 6Mb */ + else + rate = &ah->sbands[band].bitrates[4]; + + /* Set ACK timeout */ + reg = AR5K_RATE_DUR(rate->hw_value); + + /* An ACK frame consists of 10 bytes. If you add the FCS, + * which ieee80211_generic_frame_duration() adds, + * its 14 bytes. Note we use the control rate and not the + * actual rate for this rate. See mac80211 tx.c + * ieee80211_duration() for a brief description of + * what rate we should choose to TX ACKs. */ + tx_time = ath5k_hw_get_frame_duration(ah, band, 10, + rate, false); + + ath5k_hw_reg_write(ah, tx_time, reg); + + if (!(rate->flags & IEEE80211_RATE_SHORT_PREAMBLE)) + continue; + + tx_time = ath5k_hw_get_frame_duration(ah, band, 10, rate, true); + ath5k_hw_reg_write(ah, tx_time, + reg + (AR5K_SET_SHORT_PREAMBLE << 2)); + } +} + +/** + * ath5k_hw_set_ack_timeout() - Set ACK timeout on PCU + * @ah: The &struct ath5k_hw + * @timeout: Timeout in usec + */ +static int +ath5k_hw_set_ack_timeout(struct ath5k_hw *ah, unsigned int timeout) +{ + if (ath5k_hw_clocktoh(ah, AR5K_REG_MS(0xffffffff, AR5K_TIME_OUT_ACK)) + <= timeout) + return -EINVAL; + + AR5K_REG_WRITE_BITS(ah, AR5K_TIME_OUT, AR5K_TIME_OUT_ACK, + ath5k_hw_htoclock(ah, timeout)); + + return 0; +} + +/** + * ath5k_hw_set_cts_timeout() - Set CTS timeout on PCU + * @ah: The &struct ath5k_hw + * @timeout: Timeout in usec + */ +static int +ath5k_hw_set_cts_timeout(struct ath5k_hw *ah, unsigned int timeout) +{ + if (ath5k_hw_clocktoh(ah, AR5K_REG_MS(0xffffffff, AR5K_TIME_OUT_CTS)) + <= timeout) + return -EINVAL; + + AR5K_REG_WRITE_BITS(ah, AR5K_TIME_OUT, AR5K_TIME_OUT_CTS, + ath5k_hw_htoclock(ah, timeout)); + + return 0; +} + + +/*******************\ +* RX filter Control * +\*******************/ + +/** + * ath5k_hw_set_lladdr() - Set station id + * @ah: The &struct ath5k_hw + * @mac: The card's mac address (array of octets) + * + * Set station id on hw using the provided mac address + */ +int +ath5k_hw_set_lladdr(struct ath5k_hw *ah, const u8 *mac) +{ + struct ath_common *common = ath5k_hw_common(ah); + u32 low_id, high_id; + u32 pcu_reg; + + /* Set new station ID */ + memcpy(common->macaddr, mac, ETH_ALEN); + + pcu_reg = ath5k_hw_reg_read(ah, AR5K_STA_ID1) & 0xffff0000; + + low_id = get_unaligned_le32(mac); + high_id = get_unaligned_le16(mac + 4); + + ath5k_hw_reg_write(ah, low_id, AR5K_STA_ID0); + ath5k_hw_reg_write(ah, pcu_reg | high_id, AR5K_STA_ID1); + + return 0; +} + +/** + * ath5k_hw_set_bssid() - Set current BSSID on hw + * @ah: The &struct ath5k_hw + * + * Sets the current BSSID and BSSID mask we have from the + * common struct into the hardware + */ +void +ath5k_hw_set_bssid(struct ath5k_hw *ah) +{ + struct ath_common *common = ath5k_hw_common(ah); + u16 tim_offset = 0; + + /* + * Set BSSID mask on 5212 + */ + if (ah->ah_version == AR5K_AR5212) + ath_hw_setbssidmask(common); + + /* + * Set BSSID + */ + ath5k_hw_reg_write(ah, + get_unaligned_le32(common->curbssid), + AR5K_BSS_ID0); + ath5k_hw_reg_write(ah, + get_unaligned_le16(common->curbssid + 4) | + ((common->curaid & 0x3fff) << AR5K_BSS_ID1_AID_S), + AR5K_BSS_ID1); + + if (common->curaid == 0) { + ath5k_hw_disable_pspoll(ah); + return; + } + + AR5K_REG_WRITE_BITS(ah, AR5K_BEACON, AR5K_BEACON_TIM, + tim_offset ? tim_offset + 4 : 0); + + ath5k_hw_enable_pspoll(ah, NULL, 0); +} + +/** + * ath5k_hw_set_bssid_mask() - Filter out bssids we listen + * @ah: The &struct ath5k_hw + * @mask: The BSSID mask to set (array of octets) + * + * BSSID masking is a method used by AR5212 and newer hardware to inform PCU + * which bits of the interface's MAC address should be looked at when trying + * to decide which packets to ACK. In station mode and AP mode with a single + * BSS every bit matters since we lock to only one BSS. In AP mode with + * multiple BSSes (virtual interfaces) not every bit matters because hw must + * accept frames for all BSSes and so we tweak some bits of our mac address + * in order to have multiple BSSes. + * + * For more information check out ../hw.c of the common ath module. + */ +void +ath5k_hw_set_bssid_mask(struct ath5k_hw *ah, const u8 *mask) +{ + struct ath_common *common = ath5k_hw_common(ah); + + /* Cache bssid mask so that we can restore it + * on reset */ + memcpy(common->bssidmask, mask, ETH_ALEN); + if (ah->ah_version == AR5K_AR5212) + ath_hw_setbssidmask(common); +} + +/** + * ath5k_hw_set_mcast_filter() - Set multicast filter + * @ah: The &struct ath5k_hw + * @filter0: Lower 32bits of muticast filter + * @filter1: Higher 16bits of multicast filter + */ +void +ath5k_hw_set_mcast_filter(struct ath5k_hw *ah, u32 filter0, u32 filter1) +{ + ath5k_hw_reg_write(ah, filter0, AR5K_MCAST_FILTER0); + ath5k_hw_reg_write(ah, filter1, AR5K_MCAST_FILTER1); +} + +/** + * ath5k_hw_get_rx_filter() - Get current rx filter + * @ah: The &struct ath5k_hw + * + * Returns the RX filter by reading rx filter and + * phy error filter registers. RX filter is used + * to set the allowed frame types that PCU will accept + * and pass to the driver. For a list of frame types + * check out reg.h. + */ +u32 +ath5k_hw_get_rx_filter(struct ath5k_hw *ah) +{ + u32 data, filter = 0; + + filter = ath5k_hw_reg_read(ah, AR5K_RX_FILTER); + + /*Radar detection for 5212*/ + if (ah->ah_version == AR5K_AR5212) { + data = ath5k_hw_reg_read(ah, AR5K_PHY_ERR_FIL); + + if (data & AR5K_PHY_ERR_FIL_RADAR) + filter |= AR5K_RX_FILTER_RADARERR; + if (data & (AR5K_PHY_ERR_FIL_OFDM | AR5K_PHY_ERR_FIL_CCK)) + filter |= AR5K_RX_FILTER_PHYERR; + } + + return filter; +} + +/** + * ath5k_hw_set_rx_filter() - Set rx filter + * @ah: The &struct ath5k_hw + * @filter: RX filter mask (see reg.h) + * + * Sets RX filter register and also handles PHY error filter + * register on 5212 and newer chips so that we have proper PHY + * error reporting. + */ +void +ath5k_hw_set_rx_filter(struct ath5k_hw *ah, u32 filter) +{ + u32 data = 0; + + /* Set PHY error filter register on 5212*/ + if (ah->ah_version == AR5K_AR5212) { + if (filter & AR5K_RX_FILTER_RADARERR) + data |= AR5K_PHY_ERR_FIL_RADAR; + if (filter & AR5K_RX_FILTER_PHYERR) + data |= AR5K_PHY_ERR_FIL_OFDM | AR5K_PHY_ERR_FIL_CCK; + } + + /* + * The AR5210 uses promiscuous mode to detect radar activity + */ + if (ah->ah_version == AR5K_AR5210 && + (filter & AR5K_RX_FILTER_RADARERR)) { + filter &= ~AR5K_RX_FILTER_RADARERR; + filter |= AR5K_RX_FILTER_PROM; + } + + /*Zero length DMA (phy error reporting) */ + if (data) + AR5K_REG_ENABLE_BITS(ah, AR5K_RXCFG, AR5K_RXCFG_ZLFDMA); + else + AR5K_REG_DISABLE_BITS(ah, AR5K_RXCFG, AR5K_RXCFG_ZLFDMA); + + /*Write RX Filter register*/ + ath5k_hw_reg_write(ah, filter & 0xff, AR5K_RX_FILTER); + + /*Write PHY error filter register on 5212*/ + if (ah->ah_version == AR5K_AR5212) + ath5k_hw_reg_write(ah, data, AR5K_PHY_ERR_FIL); + +} + + +/****************\ +* Beacon control * +\****************/ + +#define ATH5K_MAX_TSF_READ 10 + +/** + * ath5k_hw_get_tsf64() - Get the full 64bit TSF + * @ah: The &struct ath5k_hw + * + * Returns the current TSF + */ +u64 +ath5k_hw_get_tsf64(struct ath5k_hw *ah) +{ + u32 tsf_lower, tsf_upper1, tsf_upper2; + int i; + unsigned long flags; + + /* This code is time critical - we don't want to be interrupted here */ + local_irq_save(flags); + + /* + * While reading TSF upper and then lower part, the clock is still + * counting (or jumping in case of IBSS merge) so we might get + * inconsistent values. To avoid this, we read the upper part again + * and check it has not been changed. We make the hypothesis that a + * maximum of 3 changes can happens in a row (we use 10 as a safe + * value). + * + * Impact on performance is pretty small, since in most cases, only + * 3 register reads are needed. + */ + + tsf_upper1 = ath5k_hw_reg_read(ah, AR5K_TSF_U32); + for (i = 0; i < ATH5K_MAX_TSF_READ; i++) { + tsf_lower = ath5k_hw_reg_read(ah, AR5K_TSF_L32); + tsf_upper2 = ath5k_hw_reg_read(ah, AR5K_TSF_U32); + if (tsf_upper2 == tsf_upper1) + break; + tsf_upper1 = tsf_upper2; + } + + local_irq_restore(flags); + + WARN_ON(i == ATH5K_MAX_TSF_READ); + + return ((u64)tsf_upper1 << 32) | tsf_lower; +} + +#undef ATH5K_MAX_TSF_READ + +/** + * ath5k_hw_set_tsf64() - Set a new 64bit TSF + * @ah: The &struct ath5k_hw + * @tsf64: The new 64bit TSF + * + * Sets the new TSF + */ +void +ath5k_hw_set_tsf64(struct ath5k_hw *ah, u64 tsf64) +{ + ath5k_hw_reg_write(ah, tsf64 & 0xffffffff, AR5K_TSF_L32); + ath5k_hw_reg_write(ah, (tsf64 >> 32) & 0xffffffff, AR5K_TSF_U32); +} + +/** + * ath5k_hw_reset_tsf() - Force a TSF reset + * @ah: The &struct ath5k_hw + * + * Forces a TSF reset on PCU + */ +void +ath5k_hw_reset_tsf(struct ath5k_hw *ah) +{ + u32 val; + + val = ath5k_hw_reg_read(ah, AR5K_BEACON) | AR5K_BEACON_RESET_TSF; + + /* + * Each write to the RESET_TSF bit toggles a hardware internal + * signal to reset TSF, but if left high it will cause a TSF reset + * on the next chip reset as well. Thus we always write the value + * twice to clear the signal. + */ + ath5k_hw_reg_write(ah, val, AR5K_BEACON); + ath5k_hw_reg_write(ah, val, AR5K_BEACON); +} + +/** + * ath5k_hw_init_beacon_timers() - Initialize beacon timers + * @ah: The &struct ath5k_hw + * @next_beacon: Next TBTT + * @interval: Current beacon interval + * + * This function is used to initialize beacon timers based on current + * operation mode and settings. + */ +void +ath5k_hw_init_beacon_timers(struct ath5k_hw *ah, u32 next_beacon, u32 interval) +{ + u32 timer1, timer2, timer3; + + /* + * Set the additional timers by mode + */ + switch (ah->opmode) { + case NL80211_IFTYPE_MONITOR: + case NL80211_IFTYPE_STATION: + /* In STA mode timer1 is used as next wakeup + * timer and timer2 as next CFP duration start + * timer. Both in 1/8TUs. */ + /* TODO: PCF handling */ + if (ah->ah_version == AR5K_AR5210) { + timer1 = 0xffffffff; + timer2 = 0xffffffff; + } else { + timer1 = 0x0000ffff; + timer2 = 0x0007ffff; + } + /* Mark associated AP as PCF incapable for now */ + AR5K_REG_DISABLE_BITS(ah, AR5K_STA_ID1, AR5K_STA_ID1_PCF); + break; + case NL80211_IFTYPE_ADHOC: + AR5K_REG_ENABLE_BITS(ah, AR5K_TXCFG, AR5K_TXCFG_ADHOC_BCN_ATIM); + default: + /* On non-STA modes timer1 is used as next DMA + * beacon alert (DBA) timer and timer2 as next + * software beacon alert. Both in 1/8TUs. */ + timer1 = (next_beacon - AR5K_TUNE_DMA_BEACON_RESP) << 3; + timer2 = (next_beacon - AR5K_TUNE_SW_BEACON_RESP) << 3; + break; + } + + /* Timer3 marks the end of our ATIM window + * a zero length window is not allowed because + * we 'll get no beacons */ + timer3 = next_beacon + 1; + + /* + * Set the beacon register and enable all timers. + */ + /* When in AP or Mesh Point mode zero timer0 to start TSF */ + if (ah->opmode == NL80211_IFTYPE_AP || + ah->opmode == NL80211_IFTYPE_MESH_POINT) + ath5k_hw_reg_write(ah, 0, AR5K_TIMER0); + + ath5k_hw_reg_write(ah, next_beacon, AR5K_TIMER0); + ath5k_hw_reg_write(ah, timer1, AR5K_TIMER1); + ath5k_hw_reg_write(ah, timer2, AR5K_TIMER2); + ath5k_hw_reg_write(ah, timer3, AR5K_TIMER3); + + /* Force a TSF reset if requested and enable beacons */ + if (interval & AR5K_BEACON_RESET_TSF) + ath5k_hw_reset_tsf(ah); + + ath5k_hw_reg_write(ah, interval & (AR5K_BEACON_PERIOD | + AR5K_BEACON_ENABLE), + AR5K_BEACON); + + /* Flush any pending BMISS interrupts on ISR by + * performing a clear-on-write operation on PISR + * register for the BMISS bit (writing a bit on + * ISR toggles a reset for that bit and leaves + * the remaining bits intact) */ + if (ah->ah_version == AR5K_AR5210) + ath5k_hw_reg_write(ah, AR5K_ISR_BMISS, AR5K_ISR); + else + ath5k_hw_reg_write(ah, AR5K_ISR_BMISS, AR5K_PISR); + + /* TODO: Set enhanced sleep registers on AR5212 + * based on vif->bss_conf params, until then + * disable power save reporting.*/ + AR5K_REG_DISABLE_BITS(ah, AR5K_STA_ID1, AR5K_STA_ID1_PWR_SV); + +} + +/** + * ath5k_check_timer_win() - Check if timer B is timer A + window + * @a: timer a (before b) + * @b: timer b (after a) + * @window: difference between a and b + * @intval: timers are increased by this interval + * + * This helper function checks if timer B is timer A + window and covers + * cases where timer A or B might have already been updated or wrapped + * around (Timers are 16 bit). + * + * Returns true if O.K. + */ +static inline bool +ath5k_check_timer_win(int a, int b, int window, int intval) +{ + /* + * 1.) usually B should be A + window + * 2.) A already updated, B not updated yet + * 3.) A already updated and has wrapped around + * 4.) B has wrapped around + */ + if ((b - a == window) || /* 1.) */ + (a - b == intval - window) || /* 2.) */ + ((a | 0x10000) - b == intval - window) || /* 3.) */ + ((b | 0x10000) - a == window)) /* 4.) */ + return true; /* O.K. */ + return false; +} + +/** + * ath5k_hw_check_beacon_timers() - Check if the beacon timers are correct + * @ah: The &struct ath5k_hw + * @intval: beacon interval + * + * This is a workaround for IBSS mode + * + * The need for this function arises from the fact that we have 4 separate + * HW timer registers (TIMER0 - TIMER3), which are closely related to the + * next beacon target time (NBTT), and that the HW updates these timers + * separately based on the current TSF value. The hardware increments each + * timer by the beacon interval, when the local TSF converted to TU is equal + * to the value stored in the timer. + * + * The reception of a beacon with the same BSSID can update the local HW TSF + * at any time - this is something we can't avoid. If the TSF jumps to a + * time which is later than the time stored in a timer, this timer will not + * be updated until the TSF in TU wraps around at 16 bit (the size of the + * timers) and reaches the time which is stored in the timer. + * + * The problem is that these timers are closely related to TIMER0 (NBTT) and + * that they define a time "window". When the TSF jumps between two timers + * (e.g. ATIM and NBTT), the one in the past will be left behind (not + * updated), while the one in the future will be updated every beacon + * interval. This causes the window to get larger, until the TSF wraps + * around as described above and the timer which was left behind gets + * updated again. But - because the beacon interval is usually not an exact + * divisor of the size of the timers (16 bit), an unwanted "window" between + * these timers has developed! + * + * This is especially important with the ATIM window, because during + * the ATIM window only ATIM frames and no data frames are allowed to be + * sent, which creates transmission pauses after each beacon. This symptom + * has been described as "ramping ping" because ping times increase linearly + * for some time and then drop down again. A wrong window on the DMA beacon + * timer has the same effect, so we check for these two conditions. + * + * Returns true if O.K. + */ +bool +ath5k_hw_check_beacon_timers(struct ath5k_hw *ah, int intval) +{ + unsigned int nbtt, atim, dma; + + nbtt = ath5k_hw_reg_read(ah, AR5K_TIMER0); + atim = ath5k_hw_reg_read(ah, AR5K_TIMER3); + dma = ath5k_hw_reg_read(ah, AR5K_TIMER1) >> 3; + + /* NOTE: SWBA is different. Having a wrong window there does not + * stop us from sending data and this condition is caught by + * other means (SWBA interrupt) */ + + if (ath5k_check_timer_win(nbtt, atim, 1, intval) && + ath5k_check_timer_win(dma, nbtt, AR5K_TUNE_DMA_BEACON_RESP, + intval)) + return true; /* O.K. */ + return false; +} + +/** + * ath5k_hw_set_coverage_class() - Set IEEE 802.11 coverage class + * @ah: The &struct ath5k_hw + * @coverage_class: IEEE 802.11 coverage class number + * + * Sets IFS intervals and ACK/CTS timeouts for given coverage class. + */ +void +ath5k_hw_set_coverage_class(struct ath5k_hw *ah, u8 coverage_class) +{ + /* As defined by IEEE 802.11-2007 17.3.8.6 */ + int slot_time = ath5k_hw_get_default_slottime(ah) + 3 * coverage_class; + int ack_timeout = ath5k_hw_get_default_sifs(ah) + slot_time; + int cts_timeout = ack_timeout; + + ath5k_hw_set_ifs_intervals(ah, slot_time); + ath5k_hw_set_ack_timeout(ah, ack_timeout); + ath5k_hw_set_cts_timeout(ah, cts_timeout); + + ah->ah_coverage_class = coverage_class; +} + +/***************************\ +* Init/Start/Stop functions * +\***************************/ + +/** + * ath5k_hw_start_rx_pcu() - Start RX engine + * @ah: The &struct ath5k_hw + * + * Starts RX engine on PCU so that hw can process RXed frames + * (ACK etc). + * + * NOTE: RX DMA should be already enabled using ath5k_hw_start_rx_dma + */ +void +ath5k_hw_start_rx_pcu(struct ath5k_hw *ah) +{ + AR5K_REG_DISABLE_BITS(ah, AR5K_DIAG_SW, AR5K_DIAG_SW_DIS_RX); +} + +/** + * at5k_hw_stop_rx_pcu() - Stop RX engine + * @ah: The &struct ath5k_hw + * + * Stops RX engine on PCU + */ +void +ath5k_hw_stop_rx_pcu(struct ath5k_hw *ah) +{ + AR5K_REG_ENABLE_BITS(ah, AR5K_DIAG_SW, AR5K_DIAG_SW_DIS_RX); +} + +/** + * ath5k_hw_set_opmode() - Set PCU operating mode + * @ah: The &struct ath5k_hw + * @op_mode: One of enum nl80211_iftype + * + * Configure PCU for the various operating modes (AP/STA etc) + */ +int +ath5k_hw_set_opmode(struct ath5k_hw *ah, enum nl80211_iftype op_mode) +{ + struct ath_common *common = ath5k_hw_common(ah); + u32 pcu_reg, beacon_reg, low_id, high_id; + + ATH5K_DBG(ah, ATH5K_DEBUG_MODE, "mode %d\n", op_mode); + + /* Preserve rest settings */ + pcu_reg = ath5k_hw_reg_read(ah, AR5K_STA_ID1) & 0xffff0000; + pcu_reg &= ~(AR5K_STA_ID1_ADHOC | AR5K_STA_ID1_AP + | AR5K_STA_ID1_KEYSRCH_MODE + | (ah->ah_version == AR5K_AR5210 ? + (AR5K_STA_ID1_PWR_SV | AR5K_STA_ID1_NO_PSPOLL) : 0)); + + beacon_reg = 0; + + switch (op_mode) { + case NL80211_IFTYPE_ADHOC: + pcu_reg |= AR5K_STA_ID1_ADHOC | AR5K_STA_ID1_KEYSRCH_MODE; + beacon_reg |= AR5K_BCR_ADHOC; + if (ah->ah_version == AR5K_AR5210) + pcu_reg |= AR5K_STA_ID1_NO_PSPOLL; + else + AR5K_REG_ENABLE_BITS(ah, AR5K_CFG, AR5K_CFG_IBSS); + break; + + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_MESH_POINT: + pcu_reg |= AR5K_STA_ID1_AP | AR5K_STA_ID1_KEYSRCH_MODE; + beacon_reg |= AR5K_BCR_AP; + if (ah->ah_version == AR5K_AR5210) + pcu_reg |= AR5K_STA_ID1_NO_PSPOLL; + else + AR5K_REG_DISABLE_BITS(ah, AR5K_CFG, AR5K_CFG_IBSS); + break; + + case NL80211_IFTYPE_STATION: + pcu_reg |= AR5K_STA_ID1_KEYSRCH_MODE + | (ah->ah_version == AR5K_AR5210 ? + AR5K_STA_ID1_PWR_SV : 0); + case NL80211_IFTYPE_MONITOR: + pcu_reg |= AR5K_STA_ID1_KEYSRCH_MODE + | (ah->ah_version == AR5K_AR5210 ? + AR5K_STA_ID1_NO_PSPOLL : 0); + break; + + default: + return -EINVAL; + } + + /* + * Set PCU registers + */ + low_id = get_unaligned_le32(common->macaddr); + high_id = get_unaligned_le16(common->macaddr + 4); + ath5k_hw_reg_write(ah, low_id, AR5K_STA_ID0); + ath5k_hw_reg_write(ah, pcu_reg | high_id, AR5K_STA_ID1); + + /* + * Set Beacon Control Register on 5210 + */ + if (ah->ah_version == AR5K_AR5210) + ath5k_hw_reg_write(ah, beacon_reg, AR5K_BCR); + + return 0; +} + +/** + * ath5k_hw_pcu_init() - Initialize PCU + * @ah: The &struct ath5k_hw + * @op_mode: One of enum nl80211_iftype + * @mode: One of enum ath5k_driver_mode + * + * This function is used to initialize PCU by setting current + * operation mode and various other settings. + */ +void +ath5k_hw_pcu_init(struct ath5k_hw *ah, enum nl80211_iftype op_mode) +{ + /* Set bssid and bssid mask */ + ath5k_hw_set_bssid(ah); + + /* Set PCU config */ + ath5k_hw_set_opmode(ah, op_mode); + + /* Write rate duration table only on AR5212 and if + * virtual interface has already been brought up + * XXX: rethink this after new mode changes to + * mac80211 are integrated */ + if (ah->ah_version == AR5K_AR5212 && + ah->nvifs) + ath5k_hw_write_rate_duration(ah); + + /* Set RSSI/BRSSI thresholds + * + * Note: If we decide to set this value + * dynamically, have in mind that when AR5K_RSSI_THR + * register is read it might return 0x40 if we haven't + * wrote anything to it plus BMISS RSSI threshold is zeroed. + * So doing a save/restore procedure here isn't the right + * choice. Instead store it on ath5k_hw */ + ath5k_hw_reg_write(ah, (AR5K_TUNE_RSSI_THRES | + AR5K_TUNE_BMISS_THRES << + AR5K_RSSI_THR_BMISS_S), + AR5K_RSSI_THR); + + /* MIC QoS support */ + if (ah->ah_mac_srev >= AR5K_SREV_AR2413) { + ath5k_hw_reg_write(ah, 0x000100aa, AR5K_MIC_QOS_CTL); + ath5k_hw_reg_write(ah, 0x00003210, AR5K_MIC_QOS_SEL); + } + + /* QoS NOACK Policy */ + if (ah->ah_version == AR5K_AR5212) { + ath5k_hw_reg_write(ah, + AR5K_REG_SM(2, AR5K_QOS_NOACK_2BIT_VALUES) | + AR5K_REG_SM(5, AR5K_QOS_NOACK_BIT_OFFSET) | + AR5K_REG_SM(0, AR5K_QOS_NOACK_BYTE_OFFSET), + AR5K_QOS_NOACK); + } + + /* Restore slot time and ACK timeouts */ + if (ah->ah_coverage_class > 0) + ath5k_hw_set_coverage_class(ah, ah->ah_coverage_class); + + /* Set ACK bitrate mode (see ack_rates_high) */ + if (ah->ah_version == AR5K_AR5212) { + u32 val = AR5K_STA_ID1_BASE_RATE_11B | AR5K_STA_ID1_ACKCTS_6MB; + if (ah->ah_ack_bitrate_high) + AR5K_REG_DISABLE_BITS(ah, AR5K_STA_ID1, val); + else + AR5K_REG_ENABLE_BITS(ah, AR5K_STA_ID1, val); + } + return; +} diff --git a/drivers/net/wireless/ath/ath5k/phy.c b/drivers/net/wireless/ath/ath5k/phy.c new file mode 100644 index 0000000..d6bc7cb --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/phy.c @@ -0,0 +1,3965 @@ +/* + * Copyright (c) 2004-2007 Reyk Floeter + * Copyright (c) 2006-2009 Nick Kossifidis + * Copyright (c) 2007-2008 Jiri Slaby + * Copyright (c) 2008-2009 Felix Fietkau + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/***********************\ +* PHY related functions * +\***********************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include + +#include "ath5k.h" +#include "reg.h" +#include "rfbuffer.h" +#include "rfgain.h" +#include "../regd.h" + + +/** + * DOC: PHY related functions + * + * Here we handle the low-level functions related to baseband + * and analog frontend (RF) parts. This is by far the most complex + * part of the hw code so make sure you know what you are doing. + * + * Here is a list of what this is all about: + * + * - Channel setting/switching + * + * - Automatic Gain Control (AGC) calibration + * + * - Noise Floor calibration + * + * - I/Q imbalance calibration (QAM correction) + * + * - Calibration due to thermal changes (gain_F) + * + * - Spur noise mitigation + * + * - RF/PHY initialization for the various operating modes and bwmodes + * + * - Antenna control + * + * - TX power control per channel/rate/packet type + * + * Also have in mind we never got documentation for most of these + * functions, what we have comes mostly from Atheros's code, reverse + * engineering and patent docs/presentations etc. + */ + + +/******************\ +* Helper functions * +\******************/ + +/** + * ath5k_hw_radio_revision() - Get the PHY Chip revision + * @ah: The &struct ath5k_hw + * @band: One of enum ieee80211_band + * + * Returns the revision number of a 2GHz, 5GHz or single chip + * radio. + */ +u16 +ath5k_hw_radio_revision(struct ath5k_hw *ah, enum ieee80211_band band) +{ + unsigned int i; + u32 srev; + u16 ret; + + /* + * Set the radio chip access register + */ + switch (band) { + case IEEE80211_BAND_2GHZ: + ath5k_hw_reg_write(ah, AR5K_PHY_SHIFT_2GHZ, AR5K_PHY(0)); + break; + case IEEE80211_BAND_5GHZ: + ath5k_hw_reg_write(ah, AR5K_PHY_SHIFT_5GHZ, AR5K_PHY(0)); + break; + default: + return 0; + } + + usleep_range(2000, 2500); + + /* ...wait until PHY is ready and read the selected radio revision */ + ath5k_hw_reg_write(ah, 0x00001c16, AR5K_PHY(0x34)); + + for (i = 0; i < 8; i++) + ath5k_hw_reg_write(ah, 0x00010000, AR5K_PHY(0x20)); + + if (ah->ah_version == AR5K_AR5210) { + srev = ath5k_hw_reg_read(ah, AR5K_PHY(256) >> 28) & 0xf; + ret = (u16)ath5k_hw_bitswap(srev, 4) + 1; + } else { + srev = (ath5k_hw_reg_read(ah, AR5K_PHY(0x100)) >> 24) & 0xff; + ret = (u16)ath5k_hw_bitswap(((srev & 0xf0) >> 4) | + ((srev & 0x0f) << 4), 8); + } + + /* Reset to the 5GHz mode */ + ath5k_hw_reg_write(ah, AR5K_PHY_SHIFT_5GHZ, AR5K_PHY(0)); + + return ret; +} + +/** + * ath5k_channel_ok() - Check if a channel is supported by the hw + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + * + * Note: We don't do any regulatory domain checks here, it's just + * a sanity check. + */ +bool +ath5k_channel_ok(struct ath5k_hw *ah, struct ieee80211_channel *channel) +{ + u16 freq = channel->center_freq; + + /* Check if the channel is in our supported range */ + if (channel->band == IEEE80211_BAND_2GHZ) { + if ((freq >= ah->ah_capabilities.cap_range.range_2ghz_min) && + (freq <= ah->ah_capabilities.cap_range.range_2ghz_max)) + return true; + } else if (channel->band == IEEE80211_BAND_5GHZ) + if ((freq >= ah->ah_capabilities.cap_range.range_5ghz_min) && + (freq <= ah->ah_capabilities.cap_range.range_5ghz_max)) + return true; + + return false; +} + +/** + * ath5k_hw_chan_has_spur_noise() - Check if channel is sensitive to spur noise + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + */ +bool +ath5k_hw_chan_has_spur_noise(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + u8 refclk_freq; + + if ((ah->ah_radio == AR5K_RF5112) || + (ah->ah_radio == AR5K_RF5413) || + (ah->ah_radio == AR5K_RF2413) || + (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4))) + refclk_freq = 40; + else + refclk_freq = 32; + + if ((channel->center_freq % refclk_freq != 0) && + ((channel->center_freq % refclk_freq < 10) || + (channel->center_freq % refclk_freq > 22))) + return true; + else + return false; +} + +/** + * ath5k_hw_rfb_op() - Perform an operation on the given RF Buffer + * @ah: The &struct ath5k_hw + * @rf_regs: The struct ath5k_rf_reg + * @val: New value + * @reg_id: RF register ID + * @set: Indicate we need to swap data + * + * This is an internal function used to modify RF Banks before + * writing them to AR5K_RF_BUFFER. Check out rfbuffer.h for more + * infos. + */ +static unsigned int +ath5k_hw_rfb_op(struct ath5k_hw *ah, const struct ath5k_rf_reg *rf_regs, + u32 val, u8 reg_id, bool set) +{ + const struct ath5k_rf_reg *rfreg = NULL; + u8 offset, bank, num_bits, col, position; + u16 entry; + u32 mask, data, last_bit, bits_shifted, first_bit; + u32 *rfb; + s32 bits_left; + int i; + + data = 0; + rfb = ah->ah_rf_banks; + + for (i = 0; i < ah->ah_rf_regs_count; i++) { + if (rf_regs[i].index == reg_id) { + rfreg = &rf_regs[i]; + break; + } + } + + if (rfb == NULL || rfreg == NULL) { + ATH5K_PRINTF("Rf register not found!\n"); + /* should not happen */ + return 0; + } + + bank = rfreg->bank; + num_bits = rfreg->field.len; + first_bit = rfreg->field.pos; + col = rfreg->field.col; + + /* first_bit is an offset from bank's + * start. Since we have all banks on + * the same array, we use this offset + * to mark each bank's start */ + offset = ah->ah_offset[bank]; + + /* Boundary check */ + if (!(col <= 3 && num_bits <= 32 && first_bit + num_bits <= 319)) { + ATH5K_PRINTF("invalid values at offset %u\n", offset); + return 0; + } + + entry = ((first_bit - 1) / 8) + offset; + position = (first_bit - 1) % 8; + + if (set) + data = ath5k_hw_bitswap(val, num_bits); + + for (bits_shifted = 0, bits_left = num_bits; bits_left > 0; + position = 0, entry++) { + + last_bit = (position + bits_left > 8) ? 8 : + position + bits_left; + + mask = (((1 << last_bit) - 1) ^ ((1 << position) - 1)) << + (col * 8); + + if (set) { + rfb[entry] &= ~mask; + rfb[entry] |= ((data << position) << (col * 8)) & mask; + data >>= (8 - position); + } else { + data |= (((rfb[entry] & mask) >> (col * 8)) >> position) + << bits_shifted; + bits_shifted += last_bit - position; + } + + bits_left -= 8 - position; + } + + data = set ? 1 : ath5k_hw_bitswap(data, num_bits); + + return data; +} + +/** + * ath5k_hw_write_ofdm_timings() - set OFDM timings on AR5212 + * @ah: the &struct ath5k_hw + * @channel: the currently set channel upon reset + * + * Write the delta slope coefficient (used on pilot tracking ?) for OFDM + * operation on the AR5212 upon reset. This is a helper for ath5k_hw_phy_init. + * + * Since delta slope is floating point we split it on its exponent and + * mantissa and provide these values on hw. + * + * For more infos i think this patent is related + * "http://www.freepatentsonline.com/7184495.html" + */ +static inline int +ath5k_hw_write_ofdm_timings(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + /* Get exponent and mantissa and set it */ + u32 coef_scaled, coef_exp, coef_man, + ds_coef_exp, ds_coef_man, clock; + + BUG_ON(!(ah->ah_version == AR5K_AR5212) || + (channel->hw_value == AR5K_MODE_11B)); + + /* Get coefficient + * ALGO: coef = (5 * clock / carrier_freq) / 2 + * we scale coef by shifting clock value by 24 for + * better precision since we use integers */ + switch (ah->ah_bwmode) { + case AR5K_BWMODE_40MHZ: + clock = 40 * 2; + break; + case AR5K_BWMODE_10MHZ: + clock = 40 / 2; + break; + case AR5K_BWMODE_5MHZ: + clock = 40 / 4; + break; + default: + clock = 40; + break; + } + coef_scaled = ((5 * (clock << 24)) / 2) / channel->center_freq; + + /* Get exponent + * ALGO: coef_exp = 14 - highest set bit position */ + coef_exp = ilog2(coef_scaled); + + /* Doesn't make sense if it's zero*/ + if (!coef_scaled || !coef_exp) + return -EINVAL; + + /* Note: we've shifted coef_scaled by 24 */ + coef_exp = 14 - (coef_exp - 24); + + + /* Get mantissa (significant digits) + * ALGO: coef_mant = floor(coef_scaled* 2^coef_exp+0.5) */ + coef_man = coef_scaled + + (1 << (24 - coef_exp - 1)); + + /* Calculate delta slope coefficient exponent + * and mantissa (remove scaling) and set them on hw */ + ds_coef_man = coef_man >> (24 - coef_exp); + ds_coef_exp = coef_exp - 16; + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TIMING_3, + AR5K_PHY_TIMING_3_DSC_MAN, ds_coef_man); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TIMING_3, + AR5K_PHY_TIMING_3_DSC_EXP, ds_coef_exp); + + return 0; +} + +/** + * ath5k_hw_phy_disable() - Disable PHY + * @ah: The &struct ath5k_hw + */ +int ath5k_hw_phy_disable(struct ath5k_hw *ah) +{ + /*Just a try M.F.*/ + ath5k_hw_reg_write(ah, AR5K_PHY_ACT_DISABLE, AR5K_PHY_ACT); + + return 0; +} + +/** + * ath5k_hw_wait_for_synth() - Wait for synth to settle + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + */ +static void +ath5k_hw_wait_for_synth(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + /* + * On 5211+ read activation -> rx delay + * and use it (100ns steps). + */ + if (ah->ah_version != AR5K_AR5210) { + u32 delay; + delay = ath5k_hw_reg_read(ah, AR5K_PHY_RX_DELAY) & + AR5K_PHY_RX_DELAY_M; + delay = (channel->hw_value == AR5K_MODE_11B) ? + ((delay << 2) / 22) : (delay / 10); + if (ah->ah_bwmode == AR5K_BWMODE_10MHZ) + delay = delay << 1; + if (ah->ah_bwmode == AR5K_BWMODE_5MHZ) + delay = delay << 2; + /* XXX: /2 on turbo ? Let's be safe + * for now */ + usleep_range(100 + delay, 100 + (2 * delay)); + } else { + usleep_range(1000, 1500); + } +} + + +/**********************\ +* RF Gain optimization * +\**********************/ + +/** + * DOC: RF Gain optimization + * + * This code is used to optimize RF gain on different environments + * (temperature mostly) based on feedback from a power detector. + * + * It's only used on RF5111 and RF5112, later RF chips seem to have + * auto adjustment on hw -notice they have a much smaller BANK 7 and + * no gain optimization ladder-. + * + * For more infos check out this patent doc + * "http://www.freepatentsonline.com/7400691.html" + * + * This paper describes power drops as seen on the receiver due to + * probe packets + * "http://www.cnri.dit.ie/publications/ICT08%20-%20Practical%20Issues + * %20of%20Power%20Control.pdf" + * + * And this is the MadWiFi bug entry related to the above + * "http://madwifi-project.org/ticket/1659" + * with various measurements and diagrams + */ + +/** + * ath5k_hw_rfgain_opt_init() - Initialize ah_gain during attach + * @ah: The &struct ath5k_hw + */ +int ath5k_hw_rfgain_opt_init(struct ath5k_hw *ah) +{ + /* Initialize the gain optimization values */ + switch (ah->ah_radio) { + case AR5K_RF5111: + ah->ah_gain.g_step_idx = rfgain_opt_5111.go_default; + ah->ah_gain.g_low = 20; + ah->ah_gain.g_high = 35; + ah->ah_gain.g_state = AR5K_RFGAIN_ACTIVE; + break; + case AR5K_RF5112: + ah->ah_gain.g_step_idx = rfgain_opt_5112.go_default; + ah->ah_gain.g_low = 20; + ah->ah_gain.g_high = 85; + ah->ah_gain.g_state = AR5K_RFGAIN_ACTIVE; + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * ath5k_hw_request_rfgain_probe() - Request a PAPD probe packet + * @ah: The &struct ath5k_hw + * + * Schedules a gain probe check on the next transmitted packet. + * That means our next packet is going to be sent with lower + * tx power and a Peak to Average Power Detector (PAPD) will try + * to measure the gain. + * + * TODO: Force a tx packet (bypassing PCU arbitrator etc) + * just after we enable the probe so that we don't mess with + * standard traffic. + */ +static void +ath5k_hw_request_rfgain_probe(struct ath5k_hw *ah) +{ + + /* Skip if gain calibration is inactive or + * we already handle a probe request */ + if (ah->ah_gain.g_state != AR5K_RFGAIN_ACTIVE) + return; + + /* Send the packet with 2dB below max power as + * patent doc suggest */ + ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txpower.txp_ofdm - 4, + AR5K_PHY_PAPD_PROBE_TXPOWER) | + AR5K_PHY_PAPD_PROBE_TX_NEXT, AR5K_PHY_PAPD_PROBE); + + ah->ah_gain.g_state = AR5K_RFGAIN_READ_REQUESTED; + +} + +/** + * ath5k_hw_rf_gainf_corr() - Calculate Gain_F measurement correction + * @ah: The &struct ath5k_hw + * + * Calculate Gain_F measurement correction + * based on the current step for RF5112 rev. 2 + */ +static u32 +ath5k_hw_rf_gainf_corr(struct ath5k_hw *ah) +{ + u32 mix, step; + u32 *rf; + const struct ath5k_gain_opt *go; + const struct ath5k_gain_opt_step *g_step; + const struct ath5k_rf_reg *rf_regs; + + /* Only RF5112 Rev. 2 supports it */ + if ((ah->ah_radio != AR5K_RF5112) || + (ah->ah_radio_5ghz_revision <= AR5K_SREV_RAD_5112A)) + return 0; + + go = &rfgain_opt_5112; + rf_regs = rf_regs_5112a; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5112a); + + g_step = &go->go_step[ah->ah_gain.g_step_idx]; + + if (ah->ah_rf_banks == NULL) + return 0; + + rf = ah->ah_rf_banks; + ah->ah_gain.g_f_corr = 0; + + /* No VGA (Variable Gain Amplifier) override, skip */ + if (ath5k_hw_rfb_op(ah, rf_regs, 0, AR5K_RF_MIXVGA_OVR, false) != 1) + return 0; + + /* Mix gain stepping */ + step = ath5k_hw_rfb_op(ah, rf_regs, 0, AR5K_RF_MIXGAIN_STEP, false); + + /* Mix gain override */ + mix = g_step->gos_param[0]; + + switch (mix) { + case 3: + ah->ah_gain.g_f_corr = step * 2; + break; + case 2: + ah->ah_gain.g_f_corr = (step - 5) * 2; + break; + case 1: + ah->ah_gain.g_f_corr = step; + break; + default: + ah->ah_gain.g_f_corr = 0; + break; + } + + return ah->ah_gain.g_f_corr; +} + +/** + * ath5k_hw_rf_check_gainf_readback() - Validate Gain_F feedback from detector + * @ah: The &struct ath5k_hw + * + * Check if current gain_F measurement is in the range of our + * power detector windows. If we get a measurement outside range + * we know it's not accurate (detectors can't measure anything outside + * their detection window) so we must ignore it. + * + * Returns true if readback was O.K. or false on failure + */ +static bool +ath5k_hw_rf_check_gainf_readback(struct ath5k_hw *ah) +{ + const struct ath5k_rf_reg *rf_regs; + u32 step, mix_ovr, level[4]; + u32 *rf; + + if (ah->ah_rf_banks == NULL) + return false; + + rf = ah->ah_rf_banks; + + if (ah->ah_radio == AR5K_RF5111) { + + rf_regs = rf_regs_5111; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5111); + + step = ath5k_hw_rfb_op(ah, rf_regs, 0, AR5K_RF_RFGAIN_STEP, + false); + + level[0] = 0; + level[1] = (step == 63) ? 50 : step + 4; + level[2] = (step != 63) ? 64 : level[0]; + level[3] = level[2] + 50; + + ah->ah_gain.g_high = level[3] - + (step == 63 ? AR5K_GAIN_DYN_ADJUST_HI_MARGIN : -5); + ah->ah_gain.g_low = level[0] + + (step == 63 ? AR5K_GAIN_DYN_ADJUST_LO_MARGIN : 0); + } else { + + rf_regs = rf_regs_5112; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5112); + + mix_ovr = ath5k_hw_rfb_op(ah, rf_regs, 0, AR5K_RF_MIXVGA_OVR, + false); + + level[0] = level[2] = 0; + + if (mix_ovr == 1) { + level[1] = level[3] = 83; + } else { + level[1] = level[3] = 107; + ah->ah_gain.g_high = 55; + } + } + + return (ah->ah_gain.g_current >= level[0] && + ah->ah_gain.g_current <= level[1]) || + (ah->ah_gain.g_current >= level[2] && + ah->ah_gain.g_current <= level[3]); +} + +/** + * ath5k_hw_rf_gainf_adjust() - Perform Gain_F adjustment + * @ah: The &struct ath5k_hw + * + * Choose the right target gain based on current gain + * and RF gain optimization ladder + */ +static s8 +ath5k_hw_rf_gainf_adjust(struct ath5k_hw *ah) +{ + const struct ath5k_gain_opt *go; + const struct ath5k_gain_opt_step *g_step; + int ret = 0; + + switch (ah->ah_radio) { + case AR5K_RF5111: + go = &rfgain_opt_5111; + break; + case AR5K_RF5112: + go = &rfgain_opt_5112; + break; + default: + return 0; + } + + g_step = &go->go_step[ah->ah_gain.g_step_idx]; + + if (ah->ah_gain.g_current >= ah->ah_gain.g_high) { + + /* Reached maximum */ + if (ah->ah_gain.g_step_idx == 0) + return -1; + + for (ah->ah_gain.g_target = ah->ah_gain.g_current; + ah->ah_gain.g_target >= ah->ah_gain.g_high && + ah->ah_gain.g_step_idx > 0; + g_step = &go->go_step[ah->ah_gain.g_step_idx]) + ah->ah_gain.g_target -= 2 * + (go->go_step[--(ah->ah_gain.g_step_idx)].gos_gain - + g_step->gos_gain); + + ret = 1; + goto done; + } + + if (ah->ah_gain.g_current <= ah->ah_gain.g_low) { + + /* Reached minimum */ + if (ah->ah_gain.g_step_idx == (go->go_steps_count - 1)) + return -2; + + for (ah->ah_gain.g_target = ah->ah_gain.g_current; + ah->ah_gain.g_target <= ah->ah_gain.g_low && + ah->ah_gain.g_step_idx < go->go_steps_count - 1; + g_step = &go->go_step[ah->ah_gain.g_step_idx]) + ah->ah_gain.g_target -= 2 * + (go->go_step[++ah->ah_gain.g_step_idx].gos_gain - + g_step->gos_gain); + + ret = 2; + goto done; + } + +done: + ATH5K_DBG(ah, ATH5K_DEBUG_CALIBRATE, + "ret %d, gain step %u, current gain %u, target gain %u\n", + ret, ah->ah_gain.g_step_idx, ah->ah_gain.g_current, + ah->ah_gain.g_target); + + return ret; +} + +/** + * ath5k_hw_gainf_calibrate() - Do a gain_F calibration + * @ah: The &struct ath5k_hw + * + * Main callback for thermal RF gain calibration engine + * Check for a new gain reading and schedule an adjustment + * if needed. + * + * Returns one of enum ath5k_rfgain codes + */ +enum ath5k_rfgain +ath5k_hw_gainf_calibrate(struct ath5k_hw *ah) +{ + u32 data, type; + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + + if (ah->ah_rf_banks == NULL || + ah->ah_gain.g_state == AR5K_RFGAIN_INACTIVE) + return AR5K_RFGAIN_INACTIVE; + + /* No check requested, either engine is inactive + * or an adjustment is already requested */ + if (ah->ah_gain.g_state != AR5K_RFGAIN_READ_REQUESTED) + goto done; + + /* Read the PAPD (Peak to Average Power Detector) + * register */ + data = ath5k_hw_reg_read(ah, AR5K_PHY_PAPD_PROBE); + + /* No probe is scheduled, read gain_F measurement */ + if (!(data & AR5K_PHY_PAPD_PROBE_TX_NEXT)) { + ah->ah_gain.g_current = data >> AR5K_PHY_PAPD_PROBE_GAINF_S; + type = AR5K_REG_MS(data, AR5K_PHY_PAPD_PROBE_TYPE); + + /* If tx packet is CCK correct the gain_F measurement + * by cck ofdm gain delta */ + if (type == AR5K_PHY_PAPD_PROBE_TYPE_CCK) { + if (ah->ah_radio_5ghz_revision >= AR5K_SREV_RAD_5112A) + ah->ah_gain.g_current += + ee->ee_cck_ofdm_gain_delta; + else + ah->ah_gain.g_current += + AR5K_GAIN_CCK_PROBE_CORR; + } + + /* Further correct gain_F measurement for + * RF5112A radios */ + if (ah->ah_radio_5ghz_revision >= AR5K_SREV_RAD_5112A) { + ath5k_hw_rf_gainf_corr(ah); + ah->ah_gain.g_current = + ah->ah_gain.g_current >= ah->ah_gain.g_f_corr ? + (ah->ah_gain.g_current - ah->ah_gain.g_f_corr) : + 0; + } + + /* Check if measurement is ok and if we need + * to adjust gain, schedule a gain adjustment, + * else switch back to the active state */ + if (ath5k_hw_rf_check_gainf_readback(ah) && + AR5K_GAIN_CHECK_ADJUST(&ah->ah_gain) && + ath5k_hw_rf_gainf_adjust(ah)) { + ah->ah_gain.g_state = AR5K_RFGAIN_NEED_CHANGE; + } else { + ah->ah_gain.g_state = AR5K_RFGAIN_ACTIVE; + } + } + +done: + return ah->ah_gain.g_state; +} + +/** + * ath5k_hw_rfgain_init() - Write initial RF gain settings to hw + * @ah: The &struct ath5k_hw + * @band: One of enum ieee80211_band + * + * Write initial RF gain table to set the RF sensitivity. + * + * NOTE: This one works on all RF chips and has nothing to do + * with Gain_F calibration + */ +static int +ath5k_hw_rfgain_init(struct ath5k_hw *ah, enum ieee80211_band band) +{ + const struct ath5k_ini_rfgain *ath5k_rfg; + unsigned int i, size, index; + + switch (ah->ah_radio) { + case AR5K_RF5111: + ath5k_rfg = rfgain_5111; + size = ARRAY_SIZE(rfgain_5111); + break; + case AR5K_RF5112: + ath5k_rfg = rfgain_5112; + size = ARRAY_SIZE(rfgain_5112); + break; + case AR5K_RF2413: + ath5k_rfg = rfgain_2413; + size = ARRAY_SIZE(rfgain_2413); + break; + case AR5K_RF2316: + ath5k_rfg = rfgain_2316; + size = ARRAY_SIZE(rfgain_2316); + break; + case AR5K_RF5413: + ath5k_rfg = rfgain_5413; + size = ARRAY_SIZE(rfgain_5413); + break; + case AR5K_RF2317: + case AR5K_RF2425: + ath5k_rfg = rfgain_2425; + size = ARRAY_SIZE(rfgain_2425); + break; + default: + return -EINVAL; + } + + index = (band == IEEE80211_BAND_2GHZ) ? 1 : 0; + + for (i = 0; i < size; i++) { + AR5K_REG_WAIT(i); + ath5k_hw_reg_write(ah, ath5k_rfg[i].rfg_value[index], + (u32)ath5k_rfg[i].rfg_register); + } + + return 0; +} + + +/********************\ +* RF Registers setup * +\********************/ + +/** + * ath5k_hw_rfregs_init() - Initialize RF register settings + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + * @mode: One of enum ath5k_driver_mode + * + * Setup RF registers by writing RF buffer on hw. For + * more infos on this, check out rfbuffer.h + */ +static int +ath5k_hw_rfregs_init(struct ath5k_hw *ah, + struct ieee80211_channel *channel, + unsigned int mode) +{ + const struct ath5k_rf_reg *rf_regs; + const struct ath5k_ini_rfbuffer *ini_rfb; + const struct ath5k_gain_opt *go = NULL; + const struct ath5k_gain_opt_step *g_step; + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u8 ee_mode = 0; + u32 *rfb; + int i, obdb = -1, bank = -1; + + switch (ah->ah_radio) { + case AR5K_RF5111: + rf_regs = rf_regs_5111; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5111); + ini_rfb = rfb_5111; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_5111); + go = &rfgain_opt_5111; + break; + case AR5K_RF5112: + if (ah->ah_radio_5ghz_revision >= AR5K_SREV_RAD_5112A) { + rf_regs = rf_regs_5112a; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5112a); + ini_rfb = rfb_5112a; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_5112a); + } else { + rf_regs = rf_regs_5112; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5112); + ini_rfb = rfb_5112; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_5112); + } + go = &rfgain_opt_5112; + break; + case AR5K_RF2413: + rf_regs = rf_regs_2413; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_2413); + ini_rfb = rfb_2413; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_2413); + break; + case AR5K_RF2316: + rf_regs = rf_regs_2316; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_2316); + ini_rfb = rfb_2316; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_2316); + break; + case AR5K_RF5413: + rf_regs = rf_regs_5413; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_5413); + ini_rfb = rfb_5413; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_5413); + break; + case AR5K_RF2317: + rf_regs = rf_regs_2425; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_2425); + ini_rfb = rfb_2317; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_2317); + break; + case AR5K_RF2425: + rf_regs = rf_regs_2425; + ah->ah_rf_regs_count = ARRAY_SIZE(rf_regs_2425); + if (ah->ah_mac_srev < AR5K_SREV_AR2417) { + ini_rfb = rfb_2425; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_2425); + } else { + ini_rfb = rfb_2417; + ah->ah_rf_banks_size = ARRAY_SIZE(rfb_2417); + } + break; + default: + return -EINVAL; + } + + /* If it's the first time we set RF buffer, allocate + * ah->ah_rf_banks based on ah->ah_rf_banks_size + * we set above */ + if (ah->ah_rf_banks == NULL) { + ah->ah_rf_banks = kmalloc(sizeof(u32) * ah->ah_rf_banks_size, + GFP_KERNEL); + if (ah->ah_rf_banks == NULL) { + ATH5K_ERR(ah, "out of memory\n"); + return -ENOMEM; + } + } + + /* Copy values to modify them */ + rfb = ah->ah_rf_banks; + + for (i = 0; i < ah->ah_rf_banks_size; i++) { + if (ini_rfb[i].rfb_bank >= AR5K_MAX_RF_BANKS) { + ATH5K_ERR(ah, "invalid bank\n"); + return -EINVAL; + } + + /* Bank changed, write down the offset */ + if (bank != ini_rfb[i].rfb_bank) { + bank = ini_rfb[i].rfb_bank; + ah->ah_offset[bank] = i; + } + + rfb[i] = ini_rfb[i].rfb_mode_data[mode]; + } + + /* Set Output and Driver bias current (OB/DB) */ + if (channel->band == IEEE80211_BAND_2GHZ) { + + if (channel->hw_value == AR5K_MODE_11B) + ee_mode = AR5K_EEPROM_MODE_11B; + else + ee_mode = AR5K_EEPROM_MODE_11G; + + /* For RF511X/RF211X combination we + * use b_OB and b_DB parameters stored + * in eeprom on ee->ee_ob[ee_mode][0] + * + * For all other chips we use OB/DB for 2GHz + * stored in the b/g modal section just like + * 802.11a on ee->ee_ob[ee_mode][1] */ + if ((ah->ah_radio == AR5K_RF5111) || + (ah->ah_radio == AR5K_RF5112)) + obdb = 0; + else + obdb = 1; + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_ob[ee_mode][obdb], + AR5K_RF_OB_2GHZ, true); + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_db[ee_mode][obdb], + AR5K_RF_DB_2GHZ, true); + + /* RF5111 always needs OB/DB for 5GHz, even if we use 2GHz */ + } else if ((channel->band == IEEE80211_BAND_5GHZ) || + (ah->ah_radio == AR5K_RF5111)) { + + /* For 11a, Turbo and XR we need to choose + * OB/DB based on frequency range */ + ee_mode = AR5K_EEPROM_MODE_11A; + obdb = channel->center_freq >= 5725 ? 3 : + (channel->center_freq >= 5500 ? 2 : + (channel->center_freq >= 5260 ? 1 : + (channel->center_freq > 4000 ? 0 : -1))); + + if (obdb < 0) + return -EINVAL; + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_ob[ee_mode][obdb], + AR5K_RF_OB_5GHZ, true); + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_db[ee_mode][obdb], + AR5K_RF_DB_5GHZ, true); + } + + g_step = &go->go_step[ah->ah_gain.g_step_idx]; + + /* Set turbo mode (N/A on RF5413) */ + if ((ah->ah_bwmode == AR5K_BWMODE_40MHZ) && + (ah->ah_radio != AR5K_RF5413)) + ath5k_hw_rfb_op(ah, rf_regs, 1, AR5K_RF_TURBO, false); + + /* Bank Modifications (chip-specific) */ + if (ah->ah_radio == AR5K_RF5111) { + + /* Set gain_F settings according to current step */ + if (channel->hw_value != AR5K_MODE_11B) { + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_FRAME_CTL, + AR5K_PHY_FRAME_CTL_TX_CLIP, + g_step->gos_param[0]); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[1], + AR5K_RF_PWD_90, true); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[2], + AR5K_RF_PWD_84, true); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[3], + AR5K_RF_RFGAIN_SEL, true); + + /* We programmed gain_F parameters, switch back + * to active state */ + ah->ah_gain.g_state = AR5K_RFGAIN_ACTIVE; + + } + + /* Bank 6/7 setup */ + + ath5k_hw_rfb_op(ah, rf_regs, !ee->ee_xpd[ee_mode], + AR5K_RF_PWD_XPD, true); + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_x_gain[ee_mode], + AR5K_RF_XPD_GAIN, true); + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_i_gain[ee_mode], + AR5K_RF_GAIN_I, true); + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_xpd[ee_mode], + AR5K_RF_PLO_SEL, true); + + /* Tweak power detectors for half/quarter rate support */ + if (ah->ah_bwmode == AR5K_BWMODE_5MHZ || + ah->ah_bwmode == AR5K_BWMODE_10MHZ) { + u8 wait_i; + + ath5k_hw_rfb_op(ah, rf_regs, 0x1f, + AR5K_RF_WAIT_S, true); + + wait_i = (ah->ah_bwmode == AR5K_BWMODE_5MHZ) ? + 0x1f : 0x10; + + ath5k_hw_rfb_op(ah, rf_regs, wait_i, + AR5K_RF_WAIT_I, true); + ath5k_hw_rfb_op(ah, rf_regs, 3, + AR5K_RF_MAX_TIME, true); + + } + } + + if (ah->ah_radio == AR5K_RF5112) { + + /* Set gain_F settings according to current step */ + if (channel->hw_value != AR5K_MODE_11B) { + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[0], + AR5K_RF_MIXGAIN_OVR, true); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[1], + AR5K_RF_PWD_138, true); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[2], + AR5K_RF_PWD_137, true); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[3], + AR5K_RF_PWD_136, true); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[4], + AR5K_RF_PWD_132, true); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[5], + AR5K_RF_PWD_131, true); + + ath5k_hw_rfb_op(ah, rf_regs, g_step->gos_param[6], + AR5K_RF_PWD_130, true); + + /* We programmed gain_F parameters, switch back + * to active state */ + ah->ah_gain.g_state = AR5K_RFGAIN_ACTIVE; + } + + /* Bank 6/7 setup */ + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_xpd[ee_mode], + AR5K_RF_XPD_SEL, true); + + if (ah->ah_radio_5ghz_revision < AR5K_SREV_RAD_5112A) { + /* Rev. 1 supports only one xpd */ + ath5k_hw_rfb_op(ah, rf_regs, + ee->ee_x_gain[ee_mode], + AR5K_RF_XPD_GAIN, true); + + } else { + u8 *pdg_curve_to_idx = ee->ee_pdc_to_idx[ee_mode]; + if (ee->ee_pd_gains[ee_mode] > 1) { + ath5k_hw_rfb_op(ah, rf_regs, + pdg_curve_to_idx[0], + AR5K_RF_PD_GAIN_LO, true); + ath5k_hw_rfb_op(ah, rf_regs, + pdg_curve_to_idx[1], + AR5K_RF_PD_GAIN_HI, true); + } else { + ath5k_hw_rfb_op(ah, rf_regs, + pdg_curve_to_idx[0], + AR5K_RF_PD_GAIN_LO, true); + ath5k_hw_rfb_op(ah, rf_regs, + pdg_curve_to_idx[0], + AR5K_RF_PD_GAIN_HI, true); + } + + /* Lower synth voltage on Rev 2 */ + if (ah->ah_radio == AR5K_RF5112 && + (ah->ah_radio_5ghz_revision & AR5K_SREV_REV) > 0) { + ath5k_hw_rfb_op(ah, rf_regs, 2, + AR5K_RF_HIGH_VC_CP, true); + + ath5k_hw_rfb_op(ah, rf_regs, 2, + AR5K_RF_MID_VC_CP, true); + + ath5k_hw_rfb_op(ah, rf_regs, 2, + AR5K_RF_LOW_VC_CP, true); + + ath5k_hw_rfb_op(ah, rf_regs, 2, + AR5K_RF_PUSH_UP, true); + } + + /* Decrease power consumption on 5213+ BaseBand */ + if (ah->ah_phy_revision >= AR5K_SREV_PHY_5212A) { + ath5k_hw_rfb_op(ah, rf_regs, 1, + AR5K_RF_PAD2GND, true); + + ath5k_hw_rfb_op(ah, rf_regs, 1, + AR5K_RF_XB2_LVL, true); + + ath5k_hw_rfb_op(ah, rf_regs, 1, + AR5K_RF_XB5_LVL, true); + + ath5k_hw_rfb_op(ah, rf_regs, 1, + AR5K_RF_PWD_167, true); + + ath5k_hw_rfb_op(ah, rf_regs, 1, + AR5K_RF_PWD_166, true); + } + } + + ath5k_hw_rfb_op(ah, rf_regs, ee->ee_i_gain[ee_mode], + AR5K_RF_GAIN_I, true); + + /* Tweak power detector for half/quarter rates */ + if (ah->ah_bwmode == AR5K_BWMODE_5MHZ || + ah->ah_bwmode == AR5K_BWMODE_10MHZ) { + u8 pd_delay; + + pd_delay = (ah->ah_bwmode == AR5K_BWMODE_5MHZ) ? + 0xf : 0x8; + + ath5k_hw_rfb_op(ah, rf_regs, pd_delay, + AR5K_RF_PD_PERIOD_A, true); + ath5k_hw_rfb_op(ah, rf_regs, 0xf, + AR5K_RF_PD_DELAY_A, true); + + } + } + + if (ah->ah_radio == AR5K_RF5413 && + channel->band == IEEE80211_BAND_2GHZ) { + + ath5k_hw_rfb_op(ah, rf_regs, 1, AR5K_RF_DERBY_CHAN_SEL_MODE, + true); + + /* Set optimum value for early revisions (on pci-e chips) */ + if (ah->ah_mac_srev >= AR5K_SREV_AR5424 && + ah->ah_mac_srev < AR5K_SREV_AR5413) + ath5k_hw_rfb_op(ah, rf_regs, ath5k_hw_bitswap(6, 3), + AR5K_RF_PWD_ICLOBUF_2G, true); + + } + + /* Write RF banks on hw */ + for (i = 0; i < ah->ah_rf_banks_size; i++) { + AR5K_REG_WAIT(i); + ath5k_hw_reg_write(ah, rfb[i], ini_rfb[i].rfb_ctrl_register); + } + + return 0; +} + + +/**************************\ + PHY/RF channel functions +\**************************/ + +/** + * ath5k_hw_rf5110_chan2athchan() - Convert channel freq on RF5110 + * @channel: The &struct ieee80211_channel + * + * Map channel frequency to IEEE channel number and convert it + * to an internal channel value used by the RF5110 chipset. + */ +static u32 +ath5k_hw_rf5110_chan2athchan(struct ieee80211_channel *channel) +{ + u32 athchan; + + athchan = (ath5k_hw_bitswap( + (ieee80211_frequency_to_channel( + channel->center_freq) - 24) / 2, 5) + << 1) | (1 << 6) | 0x1; + return athchan; +} + +/** + * ath5k_hw_rf5110_channel() - Set channel frequency on RF5110 + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + */ +static int +ath5k_hw_rf5110_channel(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + u32 data; + + /* + * Set the channel and wait + */ + data = ath5k_hw_rf5110_chan2athchan(channel); + ath5k_hw_reg_write(ah, data, AR5K_RF_BUFFER); + ath5k_hw_reg_write(ah, 0, AR5K_RF_BUFFER_CONTROL_0); + usleep_range(1000, 1500); + + return 0; +} + +/** + * ath5k_hw_rf5111_chan2athchan() - Handle 2GHz channels on RF5111/2111 + * @ieee: IEEE channel number + * @athchan: The &struct ath5k_athchan_2ghz + * + * In order to enable the RF2111 frequency converter on RF5111/2111 setups + * we need to add some offsets and extra flags to the data values we pass + * on to the PHY. So for every 2GHz channel this function gets called + * to do the conversion. + */ +static int +ath5k_hw_rf5111_chan2athchan(unsigned int ieee, + struct ath5k_athchan_2ghz *athchan) +{ + int channel; + + /* Cast this value to catch negative channel numbers (>= -19) */ + channel = (int)ieee; + + /* + * Map 2GHz IEEE channel to 5GHz Atheros channel + */ + if (channel <= 13) { + athchan->a2_athchan = 115 + channel; + athchan->a2_flags = 0x46; + } else if (channel == 14) { + athchan->a2_athchan = 124; + athchan->a2_flags = 0x44; + } else if (channel >= 15 && channel <= 26) { + athchan->a2_athchan = ((channel - 14) * 4) + 132; + athchan->a2_flags = 0x46; + } else + return -EINVAL; + + return 0; +} + +/** + * ath5k_hw_rf5111_channel() - Set channel frequency on RF5111/2111 + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + */ +static int +ath5k_hw_rf5111_channel(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + struct ath5k_athchan_2ghz ath5k_channel_2ghz; + unsigned int ath5k_channel = + ieee80211_frequency_to_channel(channel->center_freq); + u32 data0, data1, clock; + int ret; + + /* + * Set the channel on the RF5111 radio + */ + data0 = data1 = 0; + + if (channel->band == IEEE80211_BAND_2GHZ) { + /* Map 2GHz channel to 5GHz Atheros channel ID */ + ret = ath5k_hw_rf5111_chan2athchan( + ieee80211_frequency_to_channel(channel->center_freq), + &ath5k_channel_2ghz); + if (ret) + return ret; + + ath5k_channel = ath5k_channel_2ghz.a2_athchan; + data0 = ((ath5k_hw_bitswap(ath5k_channel_2ghz.a2_flags, 8) & 0xff) + << 5) | (1 << 4); + } + + if (ath5k_channel < 145 || !(ath5k_channel & 1)) { + clock = 1; + data1 = ((ath5k_hw_bitswap(ath5k_channel - 24, 8) & 0xff) << 2) | + (clock << 1) | (1 << 10) | 1; + } else { + clock = 0; + data1 = ((ath5k_hw_bitswap((ath5k_channel - 24) / 2, 8) & 0xff) + << 2) | (clock << 1) | (1 << 10) | 1; + } + + ath5k_hw_reg_write(ah, (data1 & 0xff) | ((data0 & 0xff) << 8), + AR5K_RF_BUFFER); + ath5k_hw_reg_write(ah, ((data1 >> 8) & 0xff) | (data0 & 0xff00), + AR5K_RF_BUFFER_CONTROL_3); + + return 0; +} + +/** + * ath5k_hw_rf5112_channel() - Set channel frequency on 5112 and newer + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + * + * On RF5112/2112 and newer we don't need to do any conversion. + * We pass the frequency value after a few modifications to the + * chip directly. + * + * NOTE: Make sure channel frequency given is within our range or else + * we might damage the chip ! Use ath5k_channel_ok before calling this one. + */ +static int +ath5k_hw_rf5112_channel(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + u32 data, data0, data1, data2; + u16 c; + + data = data0 = data1 = data2 = 0; + c = channel->center_freq; + + /* My guess based on code: + * 2GHz RF has 2 synth modes, one with a Local Oscillator + * at 2224Hz and one with a LO at 2192Hz. IF is 1520Hz + * (3040/2). data0 is used to set the PLL divider and data1 + * selects synth mode. */ + if (c < 4800) { + /* Channel 14 and all frequencies with 2Hz spacing + * below/above (non-standard channels) */ + if (!((c - 2224) % 5)) { + /* Same as (c - 2224) / 5 */ + data0 = ((2 * (c - 704)) - 3040) / 10; + data1 = 1; + /* Channel 1 and all frequencies with 5Hz spacing + * below/above (standard channels without channel 14) */ + } else if (!((c - 2192) % 5)) { + /* Same as (c - 2192) / 5 */ + data0 = ((2 * (c - 672)) - 3040) / 10; + data1 = 0; + } else + return -EINVAL; + + data0 = ath5k_hw_bitswap((data0 << 2) & 0xff, 8); + /* This is more complex, we have a single synthesizer with + * 4 reference clock settings (?) based on frequency spacing + * and set using data2. LO is at 4800Hz and data0 is again used + * to set some divider. + * + * NOTE: There is an old atheros presentation at Stanford + * that mentions a method called dual direct conversion + * with 1GHz sliding IF for RF5110. Maybe that's what we + * have here, or an updated version. */ + } else if ((c % 5) != 2 || c > 5435) { + if (!(c % 20) && c >= 5120) { + data0 = ath5k_hw_bitswap(((c - 4800) / 20 << 2), 8); + data2 = ath5k_hw_bitswap(3, 2); + } else if (!(c % 10)) { + data0 = ath5k_hw_bitswap(((c - 4800) / 10 << 1), 8); + data2 = ath5k_hw_bitswap(2, 2); + } else if (!(c % 5)) { + data0 = ath5k_hw_bitswap((c - 4800) / 5, 8); + data2 = ath5k_hw_bitswap(1, 2); + } else + return -EINVAL; + } else { + data0 = ath5k_hw_bitswap((10 * (c - 2 - 4800)) / 25 + 1, 8); + data2 = ath5k_hw_bitswap(0, 2); + } + + data = (data0 << 4) | (data1 << 1) | (data2 << 2) | 0x1001; + + ath5k_hw_reg_write(ah, data & 0xff, AR5K_RF_BUFFER); + ath5k_hw_reg_write(ah, (data >> 8) & 0x7f, AR5K_RF_BUFFER_CONTROL_5); + + return 0; +} + +/** + * ath5k_hw_rf2425_channel() - Set channel frequency on RF2425 + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + * + * AR2425/2417 have a different 2GHz RF so code changes + * a little bit from RF5112. + */ +static int +ath5k_hw_rf2425_channel(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + u32 data, data0, data2; + u16 c; + + data = data0 = data2 = 0; + c = channel->center_freq; + + if (c < 4800) { + data0 = ath5k_hw_bitswap((c - 2272), 8); + data2 = 0; + /* ? 5GHz ? */ + } else if ((c % 5) != 2 || c > 5435) { + if (!(c % 20) && c < 5120) + data0 = ath5k_hw_bitswap(((c - 4800) / 20 << 2), 8); + else if (!(c % 10)) + data0 = ath5k_hw_bitswap(((c - 4800) / 10 << 1), 8); + else if (!(c % 5)) + data0 = ath5k_hw_bitswap((c - 4800) / 5, 8); + else + return -EINVAL; + data2 = ath5k_hw_bitswap(1, 2); + } else { + data0 = ath5k_hw_bitswap((10 * (c - 2 - 4800)) / 25 + 1, 8); + data2 = ath5k_hw_bitswap(0, 2); + } + + data = (data0 << 4) | data2 << 2 | 0x1001; + + ath5k_hw_reg_write(ah, data & 0xff, AR5K_RF_BUFFER); + ath5k_hw_reg_write(ah, (data >> 8) & 0x7f, AR5K_RF_BUFFER_CONTROL_5); + + return 0; +} + +/** + * ath5k_hw_channel() - Set a channel on the radio chip + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + * + * This is the main function called to set a channel on the + * radio chip based on the radio chip version. + */ +static int +ath5k_hw_channel(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + int ret; + /* + * Check bounds supported by the PHY (we don't care about regulatory + * restrictions at this point). + */ + if (!ath5k_channel_ok(ah, channel)) { + ATH5K_ERR(ah, + "channel frequency (%u MHz) out of supported " + "band range\n", + channel->center_freq); + return -EINVAL; + } + + /* + * Set the channel and wait + */ + switch (ah->ah_radio) { + case AR5K_RF5110: + ret = ath5k_hw_rf5110_channel(ah, channel); + break; + case AR5K_RF5111: + ret = ath5k_hw_rf5111_channel(ah, channel); + break; + case AR5K_RF2317: + case AR5K_RF2425: + ret = ath5k_hw_rf2425_channel(ah, channel); + break; + default: + ret = ath5k_hw_rf5112_channel(ah, channel); + break; + } + + if (ret) + return ret; + + /* Set JAPAN setting for channel 14 */ + if (channel->center_freq == 2484) { + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_CCKTXCTL, + AR5K_PHY_CCKTXCTL_JAPAN); + } else { + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_CCKTXCTL, + AR5K_PHY_CCKTXCTL_WORLD); + } + + ah->ah_current_channel = channel; + + return 0; +} + + +/*****************\ + PHY calibration +\*****************/ + +/** + * DOC: PHY Calibration routines + * + * Noise floor calibration: When we tell the hardware to + * perform a noise floor calibration by setting the + * AR5K_PHY_AGCCTL_NF bit on AR5K_PHY_AGCCTL, it will periodically + * sample-and-hold the minimum noise level seen at the antennas. + * This value is then stored in a ring buffer of recently measured + * noise floor values so we have a moving window of the last few + * samples. The median of the values in the history is then loaded + * into the hardware for its own use for RSSI and CCA measurements. + * This type of calibration doesn't interfere with traffic. + * + * AGC calibration: When we tell the hardware to perform + * an AGC (Automatic Gain Control) calibration by setting the + * AR5K_PHY_AGCCTL_CAL, hw disconnects the antennas and does + * a calibration on the DC offsets of ADCs. During this period + * rx/tx gets disabled so we have to deal with it on the driver + * part. + * + * I/Q calibration: When we tell the hardware to perform + * an I/Q calibration, it tries to correct I/Q imbalance and + * fix QAM constellation by sampling data from rxed frames. + * It doesn't interfere with traffic. + * + * For more infos on AGC and I/Q calibration check out patent doc + * #03/094463. + */ + +/** + * ath5k_hw_read_measured_noise_floor() - Read measured NF from hw + * @ah: The &struct ath5k_hw + */ +static s32 +ath5k_hw_read_measured_noise_floor(struct ath5k_hw *ah) +{ + s32 val; + + val = ath5k_hw_reg_read(ah, AR5K_PHY_NF); + return sign_extend32(AR5K_REG_MS(val, AR5K_PHY_NF_MINCCA_PWR), 8); +} + +/** + * ath5k_hw_init_nfcal_hist() - Initialize NF calibration history buffer + * @ah: The &struct ath5k_hw + */ +void +ath5k_hw_init_nfcal_hist(struct ath5k_hw *ah) +{ + int i; + + ah->ah_nfcal_hist.index = 0; + for (i = 0; i < ATH5K_NF_CAL_HIST_MAX; i++) + ah->ah_nfcal_hist.nfval[i] = AR5K_TUNE_CCA_MAX_GOOD_VALUE; +} + +/** + * ath5k_hw_update_nfcal_hist() - Update NF calibration history buffer + * @ah: The &struct ath5k_hw + * @noise_floor: The NF we got from hw + */ +static void ath5k_hw_update_nfcal_hist(struct ath5k_hw *ah, s16 noise_floor) +{ + struct ath5k_nfcal_hist *hist = &ah->ah_nfcal_hist; + hist->index = (hist->index + 1) & (ATH5K_NF_CAL_HIST_MAX - 1); + hist->nfval[hist->index] = noise_floor; +} + +/** + * ath5k_hw_get_median_noise_floor() - Get median NF from history buffer + * @ah: The &struct ath5k_hw + */ +static s16 +ath5k_hw_get_median_noise_floor(struct ath5k_hw *ah) +{ + s16 sort[ATH5K_NF_CAL_HIST_MAX]; + s16 tmp; + int i, j; + + memcpy(sort, ah->ah_nfcal_hist.nfval, sizeof(sort)); + for (i = 0; i < ATH5K_NF_CAL_HIST_MAX - 1; i++) { + for (j = 1; j < ATH5K_NF_CAL_HIST_MAX - i; j++) { + if (sort[j] > sort[j - 1]) { + tmp = sort[j]; + sort[j] = sort[j - 1]; + sort[j - 1] = tmp; + } + } + } + for (i = 0; i < ATH5K_NF_CAL_HIST_MAX; i++) { + ATH5K_DBG(ah, ATH5K_DEBUG_CALIBRATE, + "cal %d:%d\n", i, sort[i]); + } + return sort[(ATH5K_NF_CAL_HIST_MAX - 1) / 2]; +} + +/** + * ath5k_hw_update_noise_floor() - Update NF on hardware + * @ah: The &struct ath5k_hw + * + * This is the main function we call to perform a NF calibration, + * it reads NF from hardware, calculates the median and updates + * NF on hw. + */ +void +ath5k_hw_update_noise_floor(struct ath5k_hw *ah) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u32 val; + s16 nf, threshold; + u8 ee_mode; + + /* keep last value if calibration hasn't completed */ + if (ath5k_hw_reg_read(ah, AR5K_PHY_AGCCTL) & AR5K_PHY_AGCCTL_NF) { + ATH5K_DBG(ah, ATH5K_DEBUG_CALIBRATE, + "NF did not complete in calibration window\n"); + + return; + } + + ah->ah_cal_mask |= AR5K_CALIBRATION_NF; + + ee_mode = ath5k_eeprom_mode_from_channel(ah, ah->ah_current_channel); + + /* completed NF calibration, test threshold */ + nf = ath5k_hw_read_measured_noise_floor(ah); + threshold = ee->ee_noise_floor_thr[ee_mode]; + + if (nf > threshold) { + ATH5K_DBG(ah, ATH5K_DEBUG_CALIBRATE, + "noise floor failure detected; " + "read %d, threshold %d\n", + nf, threshold); + + nf = AR5K_TUNE_CCA_MAX_GOOD_VALUE; + } + + ath5k_hw_update_nfcal_hist(ah, nf); + nf = ath5k_hw_get_median_noise_floor(ah); + + /* load noise floor (in .5 dBm) so the hardware will use it */ + val = ath5k_hw_reg_read(ah, AR5K_PHY_NF) & ~AR5K_PHY_NF_M; + val |= (nf * 2) & AR5K_PHY_NF_M; + ath5k_hw_reg_write(ah, val, AR5K_PHY_NF); + + AR5K_REG_MASKED_BITS(ah, AR5K_PHY_AGCCTL, AR5K_PHY_AGCCTL_NF, + ~(AR5K_PHY_AGCCTL_NF_EN | AR5K_PHY_AGCCTL_NF_NOUPDATE)); + + ath5k_hw_register_timeout(ah, AR5K_PHY_AGCCTL, AR5K_PHY_AGCCTL_NF, + 0, false); + + /* + * Load a high max CCA Power value (-50 dBm in .5 dBm units) + * so that we're not capped by the median we just loaded. + * This will be used as the initial value for the next noise + * floor calibration. + */ + val = (val & ~AR5K_PHY_NF_M) | ((-50 * 2) & AR5K_PHY_NF_M); + ath5k_hw_reg_write(ah, val, AR5K_PHY_NF); + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL, + AR5K_PHY_AGCCTL_NF_EN | + AR5K_PHY_AGCCTL_NF_NOUPDATE | + AR5K_PHY_AGCCTL_NF); + + ah->ah_noise_floor = nf; + + ah->ah_cal_mask &= ~AR5K_CALIBRATION_NF; + + ATH5K_DBG(ah, ATH5K_DEBUG_CALIBRATE, + "noise floor calibrated: %d\n", nf); +} + +/** + * ath5k_hw_rf5110_calibrate() - Perform a PHY calibration on RF5110 + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + * + * Do a complete PHY calibration (AGC + NF + I/Q) on RF5110 + */ +static int +ath5k_hw_rf5110_calibrate(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + u32 phy_sig, phy_agc, phy_sat, beacon; + int ret; + + if (!(ah->ah_cal_mask & AR5K_CALIBRATION_FULL)) + return 0; + + /* + * Disable beacons and RX/TX queues, wait + */ + AR5K_REG_ENABLE_BITS(ah, AR5K_DIAG_SW_5210, + AR5K_DIAG_SW_DIS_TX_5210 | AR5K_DIAG_SW_DIS_RX_5210); + beacon = ath5k_hw_reg_read(ah, AR5K_BEACON_5210); + ath5k_hw_reg_write(ah, beacon & ~AR5K_BEACON_ENABLE, AR5K_BEACON_5210); + + usleep_range(2000, 2500); + + /* + * Set the channel (with AGC turned off) + */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGC, AR5K_PHY_AGC_DISABLE); + udelay(10); + ret = ath5k_hw_channel(ah, channel); + + /* + * Activate PHY and wait + */ + ath5k_hw_reg_write(ah, AR5K_PHY_ACT_ENABLE, AR5K_PHY_ACT); + usleep_range(1000, 1500); + + AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_AGC, AR5K_PHY_AGC_DISABLE); + + if (ret) + return ret; + + /* + * Calibrate the radio chip + */ + + /* Remember normal state */ + phy_sig = ath5k_hw_reg_read(ah, AR5K_PHY_SIG); + phy_agc = ath5k_hw_reg_read(ah, AR5K_PHY_AGCCOARSE); + phy_sat = ath5k_hw_reg_read(ah, AR5K_PHY_ADCSAT); + + /* Update radio registers */ + ath5k_hw_reg_write(ah, (phy_sig & ~(AR5K_PHY_SIG_FIRPWR)) | + AR5K_REG_SM(-1, AR5K_PHY_SIG_FIRPWR), AR5K_PHY_SIG); + + ath5k_hw_reg_write(ah, (phy_agc & ~(AR5K_PHY_AGCCOARSE_HI | + AR5K_PHY_AGCCOARSE_LO)) | + AR5K_REG_SM(-1, AR5K_PHY_AGCCOARSE_HI) | + AR5K_REG_SM(-127, AR5K_PHY_AGCCOARSE_LO), AR5K_PHY_AGCCOARSE); + + ath5k_hw_reg_write(ah, (phy_sat & ~(AR5K_PHY_ADCSAT_ICNT | + AR5K_PHY_ADCSAT_THR)) | + AR5K_REG_SM(2, AR5K_PHY_ADCSAT_ICNT) | + AR5K_REG_SM(12, AR5K_PHY_ADCSAT_THR), AR5K_PHY_ADCSAT); + + udelay(20); + + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGC, AR5K_PHY_AGC_DISABLE); + udelay(10); + ath5k_hw_reg_write(ah, AR5K_PHY_RFSTG_DISABLE, AR5K_PHY_RFSTG); + AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_AGC, AR5K_PHY_AGC_DISABLE); + + usleep_range(1000, 1500); + + /* + * Enable calibration and wait until completion + */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL, AR5K_PHY_AGCCTL_CAL); + + ret = ath5k_hw_register_timeout(ah, AR5K_PHY_AGCCTL, + AR5K_PHY_AGCCTL_CAL, 0, false); + + /* Reset to normal state */ + ath5k_hw_reg_write(ah, phy_sig, AR5K_PHY_SIG); + ath5k_hw_reg_write(ah, phy_agc, AR5K_PHY_AGCCOARSE); + ath5k_hw_reg_write(ah, phy_sat, AR5K_PHY_ADCSAT); + + if (ret) { + ATH5K_ERR(ah, "calibration timeout (%uMHz)\n", + channel->center_freq); + return ret; + } + + /* + * Re-enable RX/TX and beacons + */ + AR5K_REG_DISABLE_BITS(ah, AR5K_DIAG_SW_5210, + AR5K_DIAG_SW_DIS_TX_5210 | AR5K_DIAG_SW_DIS_RX_5210); + ath5k_hw_reg_write(ah, beacon, AR5K_BEACON_5210); + + return 0; +} + +/** + * ath5k_hw_rf511x_iq_calibrate() - Perform I/Q calibration on RF5111 and newer + * @ah: The &struct ath5k_hw + */ +static int +ath5k_hw_rf511x_iq_calibrate(struct ath5k_hw *ah) +{ + u32 i_pwr, q_pwr; + s32 iq_corr, i_coff, i_coffd, q_coff, q_coffd; + int i; + + /* Skip if I/Q calibration is not needed or if it's still running */ + if (!ah->ah_iq_cal_needed) + return -EINVAL; + else if (ath5k_hw_reg_read(ah, AR5K_PHY_IQ) & AR5K_PHY_IQ_RUN) { + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_CALIBRATE, + "I/Q calibration still running"); + return -EBUSY; + } + + /* Calibration has finished, get the results and re-run */ + + /* Work around for empty results which can apparently happen on 5212: + * Read registers up to 10 times until we get both i_pr and q_pwr */ + for (i = 0; i <= 10; i++) { + iq_corr = ath5k_hw_reg_read(ah, AR5K_PHY_IQRES_CAL_CORR); + i_pwr = ath5k_hw_reg_read(ah, AR5K_PHY_IQRES_CAL_PWR_I); + q_pwr = ath5k_hw_reg_read(ah, AR5K_PHY_IQRES_CAL_PWR_Q); + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_CALIBRATE, + "iq_corr:%x i_pwr:%x q_pwr:%x", iq_corr, i_pwr, q_pwr); + if (i_pwr && q_pwr) + break; + } + + i_coffd = ((i_pwr >> 1) + (q_pwr >> 1)) >> 7; + + if (ah->ah_version == AR5K_AR5211) + q_coffd = q_pwr >> 6; + else + q_coffd = q_pwr >> 7; + + /* In case i_coffd became zero, cancel calibration + * not only it's too small, it'll also result a divide + * by zero later on. */ + if (i_coffd == 0 || q_coffd < 2) + return -ECANCELED; + + /* Protect against loss of sign bits */ + + i_coff = (-iq_corr) / i_coffd; + i_coff = clamp(i_coff, -32, 31); /* signed 6 bit */ + + if (ah->ah_version == AR5K_AR5211) + q_coff = (i_pwr / q_coffd) - 64; + else + q_coff = (i_pwr / q_coffd) - 128; + q_coff = clamp(q_coff, -16, 15); /* signed 5 bit */ + + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_CALIBRATE, + "new I:%d Q:%d (i_coffd:%x q_coffd:%x)", + i_coff, q_coff, i_coffd, q_coffd); + + /* Commit new I/Q values (set enable bit last to match HAL sources) */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_IQ, AR5K_PHY_IQ_CORR_Q_I_COFF, i_coff); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_IQ, AR5K_PHY_IQ_CORR_Q_Q_COFF, q_coff); + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_IQ, AR5K_PHY_IQ_CORR_ENABLE); + + /* Re-enable calibration -if we don't we'll commit + * the same values again and again */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_IQ, + AR5K_PHY_IQ_CAL_NUM_LOG_MAX, 15); + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_IQ, AR5K_PHY_IQ_RUN); + + return 0; +} + +/** + * ath5k_hw_phy_calibrate() - Perform a PHY calibration + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + * + * The main function we call from above to perform + * a short or full PHY calibration based on RF chip + * and current channel + */ +int +ath5k_hw_phy_calibrate(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + int ret; + + if (ah->ah_radio == AR5K_RF5110) + return ath5k_hw_rf5110_calibrate(ah, channel); + + ret = ath5k_hw_rf511x_iq_calibrate(ah); + if (ret) { + ATH5K_DBG_UNLIMIT(ah, ATH5K_DEBUG_CALIBRATE, + "No I/Q correction performed (%uMHz)\n", + channel->center_freq); + + /* Happens all the time if there is not much + * traffic, consider it normal behaviour. */ + ret = 0; + } + + /* On full calibration request a PAPD probe for + * gainf calibration if needed */ + if ((ah->ah_cal_mask & AR5K_CALIBRATION_FULL) && + (ah->ah_radio == AR5K_RF5111 || + ah->ah_radio == AR5K_RF5112) && + channel->hw_value != AR5K_MODE_11B) + ath5k_hw_request_rfgain_probe(ah); + + /* Update noise floor */ + if (!(ah->ah_cal_mask & AR5K_CALIBRATION_NF)) + ath5k_hw_update_noise_floor(ah); + + return ret; +} + + +/***************************\ +* Spur mitigation functions * +\***************************/ + +/** + * ath5k_hw_set_spur_mitigation_filter() - Configure SPUR filter + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + * + * This function gets called during PHY initialization to + * configure the spur filter for the given channel. Spur is noise + * generated due to "reflection" effects, for more information on this + * method check out patent US7643810 + */ +static void +ath5k_hw_set_spur_mitigation_filter(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u32 mag_mask[4] = {0, 0, 0, 0}; + u32 pilot_mask[2] = {0, 0}; + /* Note: fbin values are scaled up by 2 */ + u16 spur_chan_fbin, chan_fbin, symbol_width, spur_detection_window; + s32 spur_delta_phase, spur_freq_sigma_delta; + s32 spur_offset, num_symbols_x16; + u8 num_symbol_offsets, i, freq_band; + + /* Convert current frequency to fbin value (the same way channels + * are stored on EEPROM, check out ath5k_eeprom_bin2freq) and scale + * up by 2 so we can compare it later */ + if (channel->band == IEEE80211_BAND_2GHZ) { + chan_fbin = (channel->center_freq - 2300) * 10; + freq_band = AR5K_EEPROM_BAND_2GHZ; + } else { + chan_fbin = (channel->center_freq - 4900) * 10; + freq_band = AR5K_EEPROM_BAND_5GHZ; + } + + /* Check if any spur_chan_fbin from EEPROM is + * within our current channel's spur detection range */ + spur_chan_fbin = AR5K_EEPROM_NO_SPUR; + spur_detection_window = AR5K_SPUR_CHAN_WIDTH; + /* XXX: Half/Quarter channels ?*/ + if (ah->ah_bwmode == AR5K_BWMODE_40MHZ) + spur_detection_window *= 2; + + for (i = 0; i < AR5K_EEPROM_N_SPUR_CHANS; i++) { + spur_chan_fbin = ee->ee_spur_chans[i][freq_band]; + + /* Note: mask cleans AR5K_EEPROM_NO_SPUR flag + * so it's zero if we got nothing from EEPROM */ + if (spur_chan_fbin == AR5K_EEPROM_NO_SPUR) { + spur_chan_fbin &= AR5K_EEPROM_SPUR_CHAN_MASK; + break; + } + + if ((chan_fbin - spur_detection_window <= + (spur_chan_fbin & AR5K_EEPROM_SPUR_CHAN_MASK)) && + (chan_fbin + spur_detection_window >= + (spur_chan_fbin & AR5K_EEPROM_SPUR_CHAN_MASK))) { + spur_chan_fbin &= AR5K_EEPROM_SPUR_CHAN_MASK; + break; + } + } + + /* We need to enable spur filter for this channel */ + if (spur_chan_fbin) { + spur_offset = spur_chan_fbin - chan_fbin; + /* + * Calculate deltas: + * spur_freq_sigma_delta -> spur_offset / sample_freq << 21 + * spur_delta_phase -> spur_offset / chip_freq << 11 + * Note: Both values have 100Hz resolution + */ + switch (ah->ah_bwmode) { + case AR5K_BWMODE_40MHZ: + /* Both sample_freq and chip_freq are 80MHz */ + spur_delta_phase = (spur_offset << 16) / 25; + spur_freq_sigma_delta = (spur_delta_phase >> 10); + symbol_width = AR5K_SPUR_SYMBOL_WIDTH_BASE_100Hz * 2; + break; + case AR5K_BWMODE_10MHZ: + /* Both sample_freq and chip_freq are 20MHz (?) */ + spur_delta_phase = (spur_offset << 18) / 25; + spur_freq_sigma_delta = (spur_delta_phase >> 10); + symbol_width = AR5K_SPUR_SYMBOL_WIDTH_BASE_100Hz / 2; + break; + case AR5K_BWMODE_5MHZ: + /* Both sample_freq and chip_freq are 10MHz (?) */ + spur_delta_phase = (spur_offset << 19) / 25; + spur_freq_sigma_delta = (spur_delta_phase >> 10); + symbol_width = AR5K_SPUR_SYMBOL_WIDTH_BASE_100Hz / 4; + break; + default: + if (channel->band == IEEE80211_BAND_5GHZ) { + /* Both sample_freq and chip_freq are 40MHz */ + spur_delta_phase = (spur_offset << 17) / 25; + spur_freq_sigma_delta = + (spur_delta_phase >> 10); + symbol_width = + AR5K_SPUR_SYMBOL_WIDTH_BASE_100Hz; + } else { + /* sample_freq -> 40MHz chip_freq -> 44MHz + * (for b compatibility) */ + spur_delta_phase = (spur_offset << 17) / 25; + spur_freq_sigma_delta = + (spur_offset << 8) / 55; + symbol_width = + AR5K_SPUR_SYMBOL_WIDTH_BASE_100Hz; + } + break; + } + + /* Calculate pilot and magnitude masks */ + + /* Scale up spur_offset by 1000 to switch to 100HZ resolution + * and divide by symbol_width to find how many symbols we have + * Note: number of symbols is scaled up by 16 */ + num_symbols_x16 = ((spur_offset * 1000) << 4) / symbol_width; + + /* Spur is on a symbol if num_symbols_x16 % 16 is zero */ + if (!(num_symbols_x16 & 0xF)) + /* _X_ */ + num_symbol_offsets = 3; + else + /* _xx_ */ + num_symbol_offsets = 4; + + for (i = 0; i < num_symbol_offsets; i++) { + + /* Calculate pilot mask */ + s32 curr_sym_off = + (num_symbols_x16 / 16) + i + 25; + + /* Pilot magnitude mask seems to be a way to + * declare the boundaries for our detection + * window or something, it's 2 for the middle + * value(s) where the symbol is expected to be + * and 1 on the boundary values */ + u8 plt_mag_map = + (i == 0 || i == (num_symbol_offsets - 1)) + ? 1 : 2; + + if (curr_sym_off >= 0 && curr_sym_off <= 32) { + if (curr_sym_off <= 25) + pilot_mask[0] |= 1 << curr_sym_off; + else if (curr_sym_off >= 27) + pilot_mask[0] |= 1 << (curr_sym_off - 1); + } else if (curr_sym_off >= 33 && curr_sym_off <= 52) + pilot_mask[1] |= 1 << (curr_sym_off - 33); + + /* Calculate magnitude mask (for viterbi decoder) */ + if (curr_sym_off >= -1 && curr_sym_off <= 14) + mag_mask[0] |= + plt_mag_map << (curr_sym_off + 1) * 2; + else if (curr_sym_off >= 15 && curr_sym_off <= 30) + mag_mask[1] |= + plt_mag_map << (curr_sym_off - 15) * 2; + else if (curr_sym_off >= 31 && curr_sym_off <= 46) + mag_mask[2] |= + plt_mag_map << (curr_sym_off - 31) * 2; + else if (curr_sym_off >= 47 && curr_sym_off <= 53) + mag_mask[3] |= + plt_mag_map << (curr_sym_off - 47) * 2; + + } + + /* Write settings on hw to enable spur filter */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK_CTL, + AR5K_PHY_BIN_MASK_CTL_RATE, 0xff); + /* XXX: Self correlator also ? */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_IQ, + AR5K_PHY_IQ_PILOT_MASK_EN | + AR5K_PHY_IQ_CHAN_MASK_EN | + AR5K_PHY_IQ_SPUR_FILT_EN); + + /* Set delta phase and freq sigma delta */ + ath5k_hw_reg_write(ah, + AR5K_REG_SM(spur_delta_phase, + AR5K_PHY_TIMING_11_SPUR_DELTA_PHASE) | + AR5K_REG_SM(spur_freq_sigma_delta, + AR5K_PHY_TIMING_11_SPUR_FREQ_SD) | + AR5K_PHY_TIMING_11_USE_SPUR_IN_AGC, + AR5K_PHY_TIMING_11); + + /* Write pilot masks */ + ath5k_hw_reg_write(ah, pilot_mask[0], AR5K_PHY_TIMING_7); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TIMING_8, + AR5K_PHY_TIMING_8_PILOT_MASK_2, + pilot_mask[1]); + + ath5k_hw_reg_write(ah, pilot_mask[0], AR5K_PHY_TIMING_9); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TIMING_10, + AR5K_PHY_TIMING_10_PILOT_MASK_2, + pilot_mask[1]); + + /* Write magnitude masks */ + ath5k_hw_reg_write(ah, mag_mask[0], AR5K_PHY_BIN_MASK_1); + ath5k_hw_reg_write(ah, mag_mask[1], AR5K_PHY_BIN_MASK_2); + ath5k_hw_reg_write(ah, mag_mask[2], AR5K_PHY_BIN_MASK_3); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK_CTL, + AR5K_PHY_BIN_MASK_CTL_MASK_4, + mag_mask[3]); + + ath5k_hw_reg_write(ah, mag_mask[0], AR5K_PHY_BIN_MASK2_1); + ath5k_hw_reg_write(ah, mag_mask[1], AR5K_PHY_BIN_MASK2_2); + ath5k_hw_reg_write(ah, mag_mask[2], AR5K_PHY_BIN_MASK2_3); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK2_4, + AR5K_PHY_BIN_MASK2_4_MASK_4, + mag_mask[3]); + + } else if (ath5k_hw_reg_read(ah, AR5K_PHY_IQ) & + AR5K_PHY_IQ_SPUR_FILT_EN) { + /* Clean up spur mitigation settings and disable filter */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK_CTL, + AR5K_PHY_BIN_MASK_CTL_RATE, 0); + AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_IQ, + AR5K_PHY_IQ_PILOT_MASK_EN | + AR5K_PHY_IQ_CHAN_MASK_EN | + AR5K_PHY_IQ_SPUR_FILT_EN); + ath5k_hw_reg_write(ah, 0, AR5K_PHY_TIMING_11); + + /* Clear pilot masks */ + ath5k_hw_reg_write(ah, 0, AR5K_PHY_TIMING_7); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TIMING_8, + AR5K_PHY_TIMING_8_PILOT_MASK_2, + 0); + + ath5k_hw_reg_write(ah, 0, AR5K_PHY_TIMING_9); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_TIMING_10, + AR5K_PHY_TIMING_10_PILOT_MASK_2, + 0); + + /* Clear magnitude masks */ + ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK_1); + ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK_2); + ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK_3); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK_CTL, + AR5K_PHY_BIN_MASK_CTL_MASK_4, + 0); + + ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK2_1); + ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK2_2); + ath5k_hw_reg_write(ah, 0, AR5K_PHY_BIN_MASK2_3); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_BIN_MASK2_4, + AR5K_PHY_BIN_MASK2_4_MASK_4, + 0); + } +} + + +/*****************\ +* Antenna control * +\*****************/ + +/** + * DOC: Antenna control + * + * Hw supports up to 14 antennas ! I haven't found any card that implements + * that. The maximum number of antennas I've seen is up to 4 (2 for 2GHz and 2 + * for 5GHz). Antenna 1 (MAIN) should be omnidirectional, 2 (AUX) + * omnidirectional or sectorial and antennas 3-14 sectorial (or directional). + * + * We can have a single antenna for RX and multiple antennas for TX. + * RX antenna is our "default" antenna (usually antenna 1) set on + * DEFAULT_ANTENNA register and TX antenna is set on each TX control descriptor + * (0 for automatic selection, 1 - 14 antenna number). + * + * We can let hw do all the work doing fast antenna diversity for both + * tx and rx or we can do things manually. Here are the options we have + * (all are bits of STA_ID1 register): + * + * AR5K_STA_ID1_DEFAULT_ANTENNA -> When 0 is set as the TX antenna on TX + * control descriptor, use the default antenna to transmit or else use the last + * antenna on which we received an ACK. + * + * AR5K_STA_ID1_DESC_ANTENNA -> Update default antenna after each TX frame to + * the antenna on which we got the ACK for that frame. + * + * AR5K_STA_ID1_RTS_DEF_ANTENNA -> Use default antenna for RTS or else use the + * one on the TX descriptor. + * + * AR5K_STA_ID1_SELFGEN_DEF_ANT -> Use default antenna for self generated frames + * (ACKs etc), or else use current antenna (the one we just used for TX). + * + * Using the above we support the following scenarios: + * + * AR5K_ANTMODE_DEFAULT -> Hw handles antenna diversity etc automatically + * + * AR5K_ANTMODE_FIXED_A -> Only antenna A (MAIN) is present + * + * AR5K_ANTMODE_FIXED_B -> Only antenna B (AUX) is present + * + * AR5K_ANTMODE_SINGLE_AP -> Sta locked on a single ap + * + * AR5K_ANTMODE_SECTOR_AP -> AP with tx antenna set on tx desc + * + * AR5K_ANTMODE_SECTOR_STA -> STA with tx antenna set on tx desc + * + * AR5K_ANTMODE_DEBUG Debug mode -A -> Rx, B-> Tx- + * + * Also note that when setting antenna to F on tx descriptor card inverts + * current tx antenna. + */ + +/** + * ath5k_hw_set_def_antenna() - Set default rx antenna on AR5211/5212 and newer + * @ah: The &struct ath5k_hw + * @ant: Antenna number + */ +static void +ath5k_hw_set_def_antenna(struct ath5k_hw *ah, u8 ant) +{ + if (ah->ah_version != AR5K_AR5210) + ath5k_hw_reg_write(ah, ant & 0x7, AR5K_DEFAULT_ANTENNA); +} + +/** + * ath5k_hw_set_fast_div() - Enable/disable fast rx antenna diversity + * @ah: The &struct ath5k_hw + * @ee_mode: One of enum ath5k_driver_mode + * @enable: True to enable, false to disable + */ +static void +ath5k_hw_set_fast_div(struct ath5k_hw *ah, u8 ee_mode, bool enable) +{ + switch (ee_mode) { + case AR5K_EEPROM_MODE_11G: + /* XXX: This is set to + * disabled on initvals !!! */ + case AR5K_EEPROM_MODE_11A: + if (enable) + AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_AGCCTL, + AR5K_PHY_AGCCTL_OFDM_DIV_DIS); + else + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL, + AR5K_PHY_AGCCTL_OFDM_DIV_DIS); + break; + case AR5K_EEPROM_MODE_11B: + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL, + AR5K_PHY_AGCCTL_OFDM_DIV_DIS); + break; + default: + return; + } + + if (enable) { + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_RESTART, + AR5K_PHY_RESTART_DIV_GC, 4); + + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_FAST_ANT_DIV, + AR5K_PHY_FAST_ANT_DIV_EN); + } else { + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_RESTART, + AR5K_PHY_RESTART_DIV_GC, 0); + + AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_FAST_ANT_DIV, + AR5K_PHY_FAST_ANT_DIV_EN); + } +} + +/** + * ath5k_hw_set_antenna_switch() - Set up antenna switch table + * @ah: The &struct ath5k_hw + * @ee_mode: One of enum ath5k_driver_mode + * + * Switch table comes from EEPROM and includes information on controlling + * the 2 antenna RX attenuators + */ +void +ath5k_hw_set_antenna_switch(struct ath5k_hw *ah, u8 ee_mode) +{ + u8 ant0, ant1; + + /* + * In case a fixed antenna was set as default + * use the same switch table twice. + */ + if (ah->ah_ant_mode == AR5K_ANTMODE_FIXED_A) + ant0 = ant1 = AR5K_ANT_SWTABLE_A; + else if (ah->ah_ant_mode == AR5K_ANTMODE_FIXED_B) + ant0 = ant1 = AR5K_ANT_SWTABLE_B; + else { + ant0 = AR5K_ANT_SWTABLE_A; + ant1 = AR5K_ANT_SWTABLE_B; + } + + /* Set antenna idle switch table */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_ANT_CTL, + AR5K_PHY_ANT_CTL_SWTABLE_IDLE, + (ah->ah_ant_ctl[ee_mode][AR5K_ANT_CTL] | + AR5K_PHY_ANT_CTL_TXRX_EN)); + + /* Set antenna switch tables */ + ath5k_hw_reg_write(ah, ah->ah_ant_ctl[ee_mode][ant0], + AR5K_PHY_ANT_SWITCH_TABLE_0); + ath5k_hw_reg_write(ah, ah->ah_ant_ctl[ee_mode][ant1], + AR5K_PHY_ANT_SWITCH_TABLE_1); +} + +/** + * ath5k_hw_set_antenna_mode() - Set antenna operating mode + * @ah: The &struct ath5k_hw + * @ant_mode: One of enum ath5k_ant_mode + */ +void +ath5k_hw_set_antenna_mode(struct ath5k_hw *ah, u8 ant_mode) +{ + struct ieee80211_channel *channel = ah->ah_current_channel; + bool use_def_for_tx, update_def_on_tx, use_def_for_rts, fast_div; + bool use_def_for_sg; + int ee_mode; + u8 def_ant, tx_ant; + u32 sta_id1 = 0; + + /* if channel is not initialized yet we can't set the antennas + * so just store the mode. it will be set on the next reset */ + if (channel == NULL) { + ah->ah_ant_mode = ant_mode; + return; + } + + def_ant = ah->ah_def_ant; + + ee_mode = ath5k_eeprom_mode_from_channel(ah, channel); + + switch (ant_mode) { + case AR5K_ANTMODE_DEFAULT: + tx_ant = 0; + use_def_for_tx = false; + update_def_on_tx = false; + use_def_for_rts = false; + use_def_for_sg = false; + fast_div = true; + break; + case AR5K_ANTMODE_FIXED_A: + def_ant = 1; + tx_ant = 1; + use_def_for_tx = true; + update_def_on_tx = false; + use_def_for_rts = true; + use_def_for_sg = true; + fast_div = false; + break; + case AR5K_ANTMODE_FIXED_B: + def_ant = 2; + tx_ant = 2; + use_def_for_tx = true; + update_def_on_tx = false; + use_def_for_rts = true; + use_def_for_sg = true; + fast_div = false; + break; + case AR5K_ANTMODE_SINGLE_AP: + def_ant = 1; /* updated on tx */ + tx_ant = 0; + use_def_for_tx = true; + update_def_on_tx = true; + use_def_for_rts = true; + use_def_for_sg = true; + fast_div = true; + break; + case AR5K_ANTMODE_SECTOR_AP: + tx_ant = 1; /* variable */ + use_def_for_tx = false; + update_def_on_tx = false; + use_def_for_rts = true; + use_def_for_sg = false; + fast_div = false; + break; + case AR5K_ANTMODE_SECTOR_STA: + tx_ant = 1; /* variable */ + use_def_for_tx = true; + update_def_on_tx = false; + use_def_for_rts = true; + use_def_for_sg = false; + fast_div = true; + break; + case AR5K_ANTMODE_DEBUG: + def_ant = 1; + tx_ant = 2; + use_def_for_tx = false; + update_def_on_tx = false; + use_def_for_rts = false; + use_def_for_sg = false; + fast_div = false; + break; + default: + return; + } + + ah->ah_tx_ant = tx_ant; + ah->ah_ant_mode = ant_mode; + ah->ah_def_ant = def_ant; + + sta_id1 |= use_def_for_tx ? AR5K_STA_ID1_DEFAULT_ANTENNA : 0; + sta_id1 |= update_def_on_tx ? AR5K_STA_ID1_DESC_ANTENNA : 0; + sta_id1 |= use_def_for_rts ? AR5K_STA_ID1_RTS_DEF_ANTENNA : 0; + sta_id1 |= use_def_for_sg ? AR5K_STA_ID1_SELFGEN_DEF_ANT : 0; + + AR5K_REG_DISABLE_BITS(ah, AR5K_STA_ID1, AR5K_STA_ID1_ANTENNA_SETTINGS); + + if (sta_id1) + AR5K_REG_ENABLE_BITS(ah, AR5K_STA_ID1, sta_id1); + + ath5k_hw_set_antenna_switch(ah, ee_mode); + /* Note: set diversity before default antenna + * because it won't work correctly */ + ath5k_hw_set_fast_div(ah, ee_mode, fast_div); + ath5k_hw_set_def_antenna(ah, def_ant); +} + + +/****************\ +* TX power setup * +\****************/ + +/* + * Helper functions + */ + +/** + * ath5k_get_interpolated_value() - Get interpolated Y val between two points + * @target: X value of the middle point + * @x_left: X value of the left point + * @x_right: X value of the right point + * @y_left: Y value of the left point + * @y_right: Y value of the right point + */ +static s16 +ath5k_get_interpolated_value(s16 target, s16 x_left, s16 x_right, + s16 y_left, s16 y_right) +{ + s16 ratio, result; + + /* Avoid divide by zero and skip interpolation + * if we have the same point */ + if ((x_left == x_right) || (y_left == y_right)) + return y_left; + + /* + * Since we use ints and not fps, we need to scale up in + * order to get a sane ratio value (or else we 'll eg. get + * always 1 instead of 1.25, 1.75 etc). We scale up by 100 + * to have some accuracy both for 0.5 and 0.25 steps. + */ + ratio = ((100 * y_right - 100 * y_left) / (x_right - x_left)); + + /* Now scale down to be in range */ + result = y_left + (ratio * (target - x_left) / 100); + + return result; +} + +/** + * ath5k_get_linear_pcdac_min() - Find vertical boundary (min pwr) for the + * linear PCDAC curve + * @stepL: Left array with y values (pcdac steps) + * @stepR: Right array with y values (pcdac steps) + * @pwrL: Left array with x values (power steps) + * @pwrR: Right array with x values (power steps) + * + * Since we have the top of the curve and we draw the line below + * until we reach 1 (1 pcdac step) we need to know which point + * (x value) that is so that we don't go below x axis and have negative + * pcdac values when creating the curve, or fill the table with zeros. + */ +static s16 +ath5k_get_linear_pcdac_min(const u8 *stepL, const u8 *stepR, + const s16 *pwrL, const s16 *pwrR) +{ + s8 tmp; + s16 min_pwrL, min_pwrR; + s16 pwr_i; + + /* Some vendors write the same pcdac value twice !!! */ + if (stepL[0] == stepL[1] || stepR[0] == stepR[1]) + return max(pwrL[0], pwrR[0]); + + if (pwrL[0] == pwrL[1]) + min_pwrL = pwrL[0]; + else { + pwr_i = pwrL[0]; + do { + pwr_i--; + tmp = (s8) ath5k_get_interpolated_value(pwr_i, + pwrL[0], pwrL[1], + stepL[0], stepL[1]); + } while (tmp > 1); + + min_pwrL = pwr_i; + } + + if (pwrR[0] == pwrR[1]) + min_pwrR = pwrR[0]; + else { + pwr_i = pwrR[0]; + do { + pwr_i--; + tmp = (s8) ath5k_get_interpolated_value(pwr_i, + pwrR[0], pwrR[1], + stepR[0], stepR[1]); + } while (tmp > 1); + + min_pwrR = pwr_i; + } + + /* Keep the right boundary so that it works for both curves */ + return max(min_pwrL, min_pwrR); +} + +/** + * ath5k_create_power_curve() - Create a Power to PDADC or PCDAC curve + * @pmin: Minimum power value (xmin) + * @pmax: Maximum power value (xmax) + * @pwr: Array of power steps (x values) + * @vpd: Array of matching PCDAC/PDADC steps (y values) + * @num_points: Number of provided points + * @vpd_table: Array to fill with the full PCDAC/PDADC values (y values) + * @type: One of enum ath5k_powertable_type (eeprom.h) + * + * Interpolate (pwr,vpd) points to create a Power to PDADC or a + * Power to PCDAC curve. + * + * Each curve has power on x axis (in 0.5dB units) and PCDAC/PDADC + * steps (offsets) on y axis. Power can go up to 31.5dB and max + * PCDAC/PDADC step for each curve is 64 but we can write more than + * one curves on hw so we can go up to 128 (which is the max step we + * can write on the final table). + * + * We write y values (PCDAC/PDADC steps) on hw. + */ +static void +ath5k_create_power_curve(s16 pmin, s16 pmax, + const s16 *pwr, const u8 *vpd, + u8 num_points, + u8 *vpd_table, u8 type) +{ + u8 idx[2] = { 0, 1 }; + s16 pwr_i = 2 * pmin; + int i; + + if (num_points < 2) + return; + + /* We want the whole line, so adjust boundaries + * to cover the entire power range. Note that + * power values are already 0.25dB so no need + * to multiply pwr_i by 2 */ + if (type == AR5K_PWRTABLE_LINEAR_PCDAC) { + pwr_i = pmin; + pmin = 0; + pmax = 63; + } + + /* Find surrounding turning points (TPs) + * and interpolate between them */ + for (i = 0; (i <= (u16) (pmax - pmin)) && + (i < AR5K_EEPROM_POWER_TABLE_SIZE); i++) { + + /* We passed the right TP, move to the next set of TPs + * if we pass the last TP, extrapolate above using the last + * two TPs for ratio */ + if ((pwr_i > pwr[idx[1]]) && (idx[1] < num_points - 1)) { + idx[0]++; + idx[1]++; + } + + vpd_table[i] = (u8) ath5k_get_interpolated_value(pwr_i, + pwr[idx[0]], pwr[idx[1]], + vpd[idx[0]], vpd[idx[1]]); + + /* Increase by 0.5dB + * (0.25 dB units) */ + pwr_i += 2; + } +} + +/** + * ath5k_get_chan_pcal_surrounding_piers() - Get surrounding calibration piers + * for a given channel. + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + * @pcinfo_l: The &struct ath5k_chan_pcal_info to put the left cal. pier + * @pcinfo_r: The &struct ath5k_chan_pcal_info to put the right cal. pier + * + * Get the surrounding per-channel power calibration piers + * for a given frequency so that we can interpolate between + * them and come up with an appropriate dataset for our current + * channel. + */ +static void +ath5k_get_chan_pcal_surrounding_piers(struct ath5k_hw *ah, + struct ieee80211_channel *channel, + struct ath5k_chan_pcal_info **pcinfo_l, + struct ath5k_chan_pcal_info **pcinfo_r) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_chan_pcal_info *pcinfo; + u8 idx_l, idx_r; + u8 mode, max, i; + u32 target = channel->center_freq; + + idx_l = 0; + idx_r = 0; + + switch (channel->hw_value) { + case AR5K_EEPROM_MODE_11A: + pcinfo = ee->ee_pwr_cal_a; + mode = AR5K_EEPROM_MODE_11A; + break; + case AR5K_EEPROM_MODE_11B: + pcinfo = ee->ee_pwr_cal_b; + mode = AR5K_EEPROM_MODE_11B; + break; + case AR5K_EEPROM_MODE_11G: + default: + pcinfo = ee->ee_pwr_cal_g; + mode = AR5K_EEPROM_MODE_11G; + break; + } + max = ee->ee_n_piers[mode] - 1; + + /* Frequency is below our calibrated + * range. Use the lowest power curve + * we have */ + if (target < pcinfo[0].freq) { + idx_l = idx_r = 0; + goto done; + } + + /* Frequency is above our calibrated + * range. Use the highest power curve + * we have */ + if (target > pcinfo[max].freq) { + idx_l = idx_r = max; + goto done; + } + + /* Frequency is inside our calibrated + * channel range. Pick the surrounding + * calibration piers so that we can + * interpolate */ + for (i = 0; i <= max; i++) { + + /* Frequency matches one of our calibration + * piers, no need to interpolate, just use + * that calibration pier */ + if (pcinfo[i].freq == target) { + idx_l = idx_r = i; + goto done; + } + + /* We found a calibration pier that's above + * frequency, use this pier and the previous + * one to interpolate */ + if (target < pcinfo[i].freq) { + idx_r = i; + idx_l = idx_r - 1; + goto done; + } + } + +done: + *pcinfo_l = &pcinfo[idx_l]; + *pcinfo_r = &pcinfo[idx_r]; +} + +/** + * ath5k_get_rate_pcal_data() - Get the interpolated per-rate power + * calibration data + * @ah: The &struct ath5k_hw *ah, + * @channel: The &struct ieee80211_channel + * @rates: The &struct ath5k_rate_pcal_info to fill + * + * Get the surrounding per-rate power calibration data + * for a given frequency and interpolate between power + * values to set max target power supported by hw for + * each rate on this frequency. + */ +static void +ath5k_get_rate_pcal_data(struct ath5k_hw *ah, + struct ieee80211_channel *channel, + struct ath5k_rate_pcal_info *rates) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_rate_pcal_info *rpinfo; + u8 idx_l, idx_r; + u8 mode, max, i; + u32 target = channel->center_freq; + + idx_l = 0; + idx_r = 0; + + switch (channel->hw_value) { + case AR5K_MODE_11A: + rpinfo = ee->ee_rate_tpwr_a; + mode = AR5K_EEPROM_MODE_11A; + break; + case AR5K_MODE_11B: + rpinfo = ee->ee_rate_tpwr_b; + mode = AR5K_EEPROM_MODE_11B; + break; + case AR5K_MODE_11G: + default: + rpinfo = ee->ee_rate_tpwr_g; + mode = AR5K_EEPROM_MODE_11G; + break; + } + max = ee->ee_rate_target_pwr_num[mode] - 1; + + /* Get the surrounding calibration + * piers - same as above */ + if (target < rpinfo[0].freq) { + idx_l = idx_r = 0; + goto done; + } + + if (target > rpinfo[max].freq) { + idx_l = idx_r = max; + goto done; + } + + for (i = 0; i <= max; i++) { + + if (rpinfo[i].freq == target) { + idx_l = idx_r = i; + goto done; + } + + if (target < rpinfo[i].freq) { + idx_r = i; + idx_l = idx_r - 1; + goto done; + } + } + +done: + /* Now interpolate power value, based on the frequency */ + rates->freq = target; + + rates->target_power_6to24 = + ath5k_get_interpolated_value(target, rpinfo[idx_l].freq, + rpinfo[idx_r].freq, + rpinfo[idx_l].target_power_6to24, + rpinfo[idx_r].target_power_6to24); + + rates->target_power_36 = + ath5k_get_interpolated_value(target, rpinfo[idx_l].freq, + rpinfo[idx_r].freq, + rpinfo[idx_l].target_power_36, + rpinfo[idx_r].target_power_36); + + rates->target_power_48 = + ath5k_get_interpolated_value(target, rpinfo[idx_l].freq, + rpinfo[idx_r].freq, + rpinfo[idx_l].target_power_48, + rpinfo[idx_r].target_power_48); + + rates->target_power_54 = + ath5k_get_interpolated_value(target, rpinfo[idx_l].freq, + rpinfo[idx_r].freq, + rpinfo[idx_l].target_power_54, + rpinfo[idx_r].target_power_54); +} + +/** + * ath5k_get_max_ctl_power() - Get max edge power for a given frequency + * @ah: the &struct ath5k_hw + * @channel: The &struct ieee80211_channel + * + * Get the max edge power for this channel if + * we have such data from EEPROM's Conformance Test + * Limits (CTL), and limit max power if needed. + */ +static void +ath5k_get_max_ctl_power(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + struct ath_regulatory *regulatory = ath5k_hw_regulatory(ah); + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + struct ath5k_edge_power *rep = ee->ee_ctl_pwr; + u8 *ctl_val = ee->ee_ctl; + s16 max_chan_pwr = ah->ah_txpower.txp_max_pwr / 4; + s16 edge_pwr = 0; + u8 rep_idx; + u8 i, ctl_mode; + u8 ctl_idx = 0xFF; + u32 target = channel->center_freq; + + ctl_mode = ath_regd_get_band_ctl(regulatory, channel->band); + + switch (channel->hw_value) { + case AR5K_MODE_11A: + if (ah->ah_bwmode == AR5K_BWMODE_40MHZ) + ctl_mode |= AR5K_CTL_TURBO; + else + ctl_mode |= AR5K_CTL_11A; + break; + case AR5K_MODE_11G: + if (ah->ah_bwmode == AR5K_BWMODE_40MHZ) + ctl_mode |= AR5K_CTL_TURBOG; + else + ctl_mode |= AR5K_CTL_11G; + break; + case AR5K_MODE_11B: + ctl_mode |= AR5K_CTL_11B; + break; + default: + return; + } + + for (i = 0; i < ee->ee_ctls; i++) { + if (ctl_val[i] == ctl_mode) { + ctl_idx = i; + break; + } + } + + /* If we have a CTL dataset available grab it and find the + * edge power for our frequency */ + if (ctl_idx == 0xFF) + return; + + /* Edge powers are sorted by frequency from lower + * to higher. Each CTL corresponds to 8 edge power + * measurements. */ + rep_idx = ctl_idx * AR5K_EEPROM_N_EDGES; + + /* Don't do boundaries check because we + * might have more that one bands defined + * for this mode */ + + /* Get the edge power that's closer to our + * frequency */ + for (i = 0; i < AR5K_EEPROM_N_EDGES; i++) { + rep_idx += i; + if (target <= rep[rep_idx].freq) + edge_pwr = (s16) rep[rep_idx].edge; + } + + if (edge_pwr) + ah->ah_txpower.txp_max_pwr = 4 * min(edge_pwr, max_chan_pwr); +} + + +/* + * Power to PCDAC table functions + */ + +/** + * DOC: Power to PCDAC table functions + * + * For RF5111 we have an XPD -eXternal Power Detector- curve + * for each calibrated channel. Each curve has 0,5dB Power steps + * on x axis and PCDAC steps (offsets) on y axis and looks like an + * exponential function. To recreate the curve we read 11 points + * from eeprom (eeprom.c) and interpolate here. + * + * For RF5112 we have 4 XPD -eXternal Power Detector- curves + * for each calibrated channel on 0, -6, -12 and -18dBm but we only + * use the higher (3) and the lower (0) curves. Each curve again has 0.5dB + * power steps on x axis and PCDAC steps on y axis and looks like a + * linear function. To recreate the curve and pass the power values + * on hw, we get 4 points for xpd 0 (lower gain -> max power) + * and 3 points for xpd 3 (higher gain -> lower power) from eeprom (eeprom.c) + * and interpolate here. + * + * For a given channel we get the calibrated points (piers) for it or + * -if we don't have calibration data for this specific channel- from the + * available surrounding channels we have calibration data for, after we do a + * linear interpolation between them. Then since we have our calibrated points + * for this channel, we do again a linear interpolation between them to get the + * whole curve. + * + * We finally write the Y values of the curve(s) (the PCDAC values) on hw + */ + +/** + * ath5k_fill_pwr_to_pcdac_table() - Fill Power to PCDAC table on RF5111 + * @ah: The &struct ath5k_hw + * @table_min: Minimum power (x min) + * @table_max: Maximum power (x max) + * + * No further processing is needed for RF5111, the only thing we have to + * do is fill the values below and above calibration range since eeprom data + * may not cover the entire PCDAC table. + */ +static void +ath5k_fill_pwr_to_pcdac_table(struct ath5k_hw *ah, s16* table_min, + s16 *table_max) +{ + u8 *pcdac_out = ah->ah_txpower.txp_pd_table; + u8 *pcdac_tmp = ah->ah_txpower.tmpL[0]; + u8 pcdac_0, pcdac_n, pcdac_i, pwr_idx, i; + s16 min_pwr, max_pwr; + + /* Get table boundaries */ + min_pwr = table_min[0]; + pcdac_0 = pcdac_tmp[0]; + + max_pwr = table_max[0]; + pcdac_n = pcdac_tmp[table_max[0] - table_min[0]]; + + /* Extrapolate below minimum using pcdac_0 */ + pcdac_i = 0; + for (i = 0; i < min_pwr; i++) + pcdac_out[pcdac_i++] = pcdac_0; + + /* Copy values from pcdac_tmp */ + pwr_idx = min_pwr; + for (i = 0; pwr_idx <= max_pwr && + pcdac_i < AR5K_EEPROM_POWER_TABLE_SIZE; i++) { + pcdac_out[pcdac_i++] = pcdac_tmp[i]; + pwr_idx++; + } + + /* Extrapolate above maximum */ + while (pcdac_i < AR5K_EEPROM_POWER_TABLE_SIZE) + pcdac_out[pcdac_i++] = pcdac_n; + +} + +/** + * ath5k_combine_linear_pcdac_curves() - Combine available PCDAC Curves + * @ah: The &struct ath5k_hw + * @table_min: Minimum power (x min) + * @table_max: Maximum power (x max) + * @pdcurves: Number of pd curves + * + * Combine available XPD Curves and fill Linear Power to PCDAC table on RF5112 + * RFX112 can have up to 2 curves (one for low txpower range and one for + * higher txpower range). We need to put them both on pcdac_out and place + * them in the correct location. In case we only have one curve available + * just fit it on pcdac_out (it's supposed to cover the entire range of + * available pwr levels since it's always the higher power curve). Extrapolate + * below and above final table if needed. + */ +static void +ath5k_combine_linear_pcdac_curves(struct ath5k_hw *ah, s16* table_min, + s16 *table_max, u8 pdcurves) +{ + u8 *pcdac_out = ah->ah_txpower.txp_pd_table; + u8 *pcdac_low_pwr; + u8 *pcdac_high_pwr; + u8 *pcdac_tmp; + u8 pwr; + s16 max_pwr_idx; + s16 min_pwr_idx; + s16 mid_pwr_idx = 0; + /* Edge flag turns on the 7nth bit on the PCDAC + * to declare the higher power curve (force values + * to be greater than 64). If we only have one curve + * we don't need to set this, if we have 2 curves and + * fill the table backwards this can also be used to + * switch from higher power curve to lower power curve */ + u8 edge_flag; + int i; + + /* When we have only one curve available + * that's the higher power curve. If we have + * two curves the first is the high power curve + * and the next is the low power curve. */ + if (pdcurves > 1) { + pcdac_low_pwr = ah->ah_txpower.tmpL[1]; + pcdac_high_pwr = ah->ah_txpower.tmpL[0]; + mid_pwr_idx = table_max[1] - table_min[1] - 1; + max_pwr_idx = (table_max[0] - table_min[0]) / 2; + + /* If table size goes beyond 31.5dB, keep the + * upper 31.5dB range when setting tx power. + * Note: 126 = 31.5 dB in quarter dB steps */ + if (table_max[0] - table_min[1] > 126) + min_pwr_idx = table_max[0] - 126; + else + min_pwr_idx = table_min[1]; + + /* Since we fill table backwards + * start from high power curve */ + pcdac_tmp = pcdac_high_pwr; + + edge_flag = 0x40; + } else { + pcdac_low_pwr = ah->ah_txpower.tmpL[1]; /* Zeroed */ + pcdac_high_pwr = ah->ah_txpower.tmpL[0]; + min_pwr_idx = table_min[0]; + max_pwr_idx = (table_max[0] - table_min[0]) / 2; + pcdac_tmp = pcdac_high_pwr; + edge_flag = 0; + } + + /* This is used when setting tx power*/ + ah->ah_txpower.txp_min_idx = min_pwr_idx / 2; + + /* Fill Power to PCDAC table backwards */ + pwr = max_pwr_idx; + for (i = 63; i >= 0; i--) { + /* Entering lower power range, reset + * edge flag and set pcdac_tmp to lower + * power curve.*/ + if (edge_flag == 0x40 && + (2 * pwr <= (table_max[1] - table_min[0]) || pwr == 0)) { + edge_flag = 0x00; + pcdac_tmp = pcdac_low_pwr; + pwr = mid_pwr_idx / 2; + } + + /* Don't go below 1, extrapolate below if we have + * already switched to the lower power curve -or + * we only have one curve and edge_flag is zero + * anyway */ + if (pcdac_tmp[pwr] < 1 && (edge_flag == 0x00)) { + while (i >= 0) { + pcdac_out[i] = pcdac_out[i + 1]; + i--; + } + break; + } + + pcdac_out[i] = pcdac_tmp[pwr] | edge_flag; + + /* Extrapolate above if pcdac is greater than + * 126 -this can happen because we OR pcdac_out + * value with edge_flag on high power curve */ + if (pcdac_out[i] > 126) + pcdac_out[i] = 126; + + /* Decrease by a 0.5dB step */ + pwr--; + } +} + +/** + * ath5k_write_pcdac_table() - Write the PCDAC values on hw + * @ah: The &struct ath5k_hw + */ +static void +ath5k_write_pcdac_table(struct ath5k_hw *ah) +{ + u8 *pcdac_out = ah->ah_txpower.txp_pd_table; + int i; + + /* + * Write TX power values + */ + for (i = 0; i < (AR5K_EEPROM_POWER_TABLE_SIZE / 2); i++) { + ath5k_hw_reg_write(ah, + (((pcdac_out[2 * i + 0] << 8 | 0xff) & 0xffff) << 0) | + (((pcdac_out[2 * i + 1] << 8 | 0xff) & 0xffff) << 16), + AR5K_PHY_PCDAC_TXPOWER(i)); + } +} + + +/* + * Power to PDADC table functions + */ + +/** + * DOC: Power to PDADC table functions + * + * For RF2413 and later we have a Power to PDADC table (Power Detector) + * instead of a PCDAC (Power Control) and 4 pd gain curves for each + * calibrated channel. Each curve has power on x axis in 0.5 db steps and + * PDADC steps on y axis and looks like an exponential function like the + * RF5111 curve. + * + * To recreate the curves we read the points from eeprom (eeprom.c) + * and interpolate here. Note that in most cases only 2 (higher and lower) + * curves are used (like RF5112) but vendors have the opportunity to include + * all 4 curves on eeprom. The final curve (higher power) has an extra + * point for better accuracy like RF5112. + * + * The process is similar to what we do above for RF5111/5112 + */ + +/** + * ath5k_combine_pwr_to_pdadc_curves() - Combine the various PDADC curves + * @ah: The &struct ath5k_hw + * @pwr_min: Minimum power (x min) + * @pwr_max: Maximum power (x max) + * @pdcurves: Number of available curves + * + * Combine the various pd curves and create the final Power to PDADC table + * We can have up to 4 pd curves, we need to do a similar process + * as we do for RF5112. This time we don't have an edge_flag but we + * set the gain boundaries on a separate register. + */ +static void +ath5k_combine_pwr_to_pdadc_curves(struct ath5k_hw *ah, + s16 *pwr_min, s16 *pwr_max, u8 pdcurves) +{ + u8 gain_boundaries[AR5K_EEPROM_N_PD_GAINS]; + u8 *pdadc_out = ah->ah_txpower.txp_pd_table; + u8 *pdadc_tmp; + s16 pdadc_0; + u8 pdadc_i, pdadc_n, pwr_step, pdg, max_idx, table_size; + u8 pd_gain_overlap; + + /* Note: Register value is initialized on initvals + * there is no feedback from hw. + * XXX: What about pd_gain_overlap from EEPROM ? */ + pd_gain_overlap = (u8) ath5k_hw_reg_read(ah, AR5K_PHY_TPC_RG5) & + AR5K_PHY_TPC_RG5_PD_GAIN_OVERLAP; + + /* Create final PDADC table */ + for (pdg = 0, pdadc_i = 0; pdg < pdcurves; pdg++) { + pdadc_tmp = ah->ah_txpower.tmpL[pdg]; + + if (pdg == pdcurves - 1) + /* 2 dB boundary stretch for last + * (higher power) curve */ + gain_boundaries[pdg] = pwr_max[pdg] + 4; + else + /* Set gain boundary in the middle + * between this curve and the next one */ + gain_boundaries[pdg] = + (pwr_max[pdg] + pwr_min[pdg + 1]) / 2; + + /* Sanity check in case our 2 db stretch got out of + * range. */ + if (gain_boundaries[pdg] > AR5K_TUNE_MAX_TXPOWER) + gain_boundaries[pdg] = AR5K_TUNE_MAX_TXPOWER; + + /* For the first curve (lower power) + * start from 0 dB */ + if (pdg == 0) + pdadc_0 = 0; + else + /* For the other curves use the gain overlap */ + pdadc_0 = (gain_boundaries[pdg - 1] - pwr_min[pdg]) - + pd_gain_overlap; + + /* Force each power step to be at least 0.5 dB */ + if ((pdadc_tmp[1] - pdadc_tmp[0]) > 1) + pwr_step = pdadc_tmp[1] - pdadc_tmp[0]; + else + pwr_step = 1; + + /* If pdadc_0 is negative, we need to extrapolate + * below this pdgain by a number of pwr_steps */ + while ((pdadc_0 < 0) && (pdadc_i < 128)) { + s16 tmp = pdadc_tmp[0] + pdadc_0 * pwr_step; + pdadc_out[pdadc_i++] = (tmp < 0) ? 0 : (u8) tmp; + pdadc_0++; + } + + /* Set last pwr level, using gain boundaries */ + pdadc_n = gain_boundaries[pdg] + pd_gain_overlap - pwr_min[pdg]; + /* Limit it to be inside pwr range */ + table_size = pwr_max[pdg] - pwr_min[pdg]; + max_idx = (pdadc_n < table_size) ? pdadc_n : table_size; + + /* Fill pdadc_out table */ + while (pdadc_0 < max_idx && pdadc_i < 128) + pdadc_out[pdadc_i++] = pdadc_tmp[pdadc_0++]; + + /* Need to extrapolate above this pdgain? */ + if (pdadc_n <= max_idx) + continue; + + /* Force each power step to be at least 0.5 dB */ + if ((pdadc_tmp[table_size - 1] - pdadc_tmp[table_size - 2]) > 1) + pwr_step = pdadc_tmp[table_size - 1] - + pdadc_tmp[table_size - 2]; + else + pwr_step = 1; + + /* Extrapolate above */ + while ((pdadc_0 < (s16) pdadc_n) && + (pdadc_i < AR5K_EEPROM_POWER_TABLE_SIZE * 2)) { + s16 tmp = pdadc_tmp[table_size - 1] + + (pdadc_0 - max_idx) * pwr_step; + pdadc_out[pdadc_i++] = (tmp > 127) ? 127 : (u8) tmp; + pdadc_0++; + } + } + + while (pdg < AR5K_EEPROM_N_PD_GAINS) { + gain_boundaries[pdg] = gain_boundaries[pdg - 1]; + pdg++; + } + + while (pdadc_i < AR5K_EEPROM_POWER_TABLE_SIZE * 2) { + pdadc_out[pdadc_i] = pdadc_out[pdadc_i - 1]; + pdadc_i++; + } + + /* Set gain boundaries */ + ath5k_hw_reg_write(ah, + AR5K_REG_SM(pd_gain_overlap, + AR5K_PHY_TPC_RG5_PD_GAIN_OVERLAP) | + AR5K_REG_SM(gain_boundaries[0], + AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_1) | + AR5K_REG_SM(gain_boundaries[1], + AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_2) | + AR5K_REG_SM(gain_boundaries[2], + AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_3) | + AR5K_REG_SM(gain_boundaries[3], + AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_4), + AR5K_PHY_TPC_RG5); + + /* Used for setting rate power table */ + ah->ah_txpower.txp_min_idx = pwr_min[0]; + +} + +/** + * ath5k_write_pwr_to_pdadc_table() - Write the PDADC values on hw + * @ah: The &struct ath5k_hw + * @ee_mode: One of enum ath5k_driver_mode + */ +static void +ath5k_write_pwr_to_pdadc_table(struct ath5k_hw *ah, u8 ee_mode) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u8 *pdadc_out = ah->ah_txpower.txp_pd_table; + u8 *pdg_to_idx = ee->ee_pdc_to_idx[ee_mode]; + u8 pdcurves = ee->ee_pd_gains[ee_mode]; + u32 reg; + u8 i; + + /* Select the right pdgain curves */ + + /* Clear current settings */ + reg = ath5k_hw_reg_read(ah, AR5K_PHY_TPC_RG1); + reg &= ~(AR5K_PHY_TPC_RG1_PDGAIN_1 | + AR5K_PHY_TPC_RG1_PDGAIN_2 | + AR5K_PHY_TPC_RG1_PDGAIN_3 | + AR5K_PHY_TPC_RG1_NUM_PD_GAIN); + + /* + * Use pd_gains curve from eeprom + * + * This overrides the default setting from initvals + * in case some vendors (e.g. Zcomax) don't use the default + * curves. If we don't honor their settings we 'll get a + * 5dB (1 * gain overlap ?) drop. + */ + reg |= AR5K_REG_SM(pdcurves, AR5K_PHY_TPC_RG1_NUM_PD_GAIN); + + switch (pdcurves) { + case 3: + reg |= AR5K_REG_SM(pdg_to_idx[2], AR5K_PHY_TPC_RG1_PDGAIN_3); + /* Fall through */ + case 2: + reg |= AR5K_REG_SM(pdg_to_idx[1], AR5K_PHY_TPC_RG1_PDGAIN_2); + /* Fall through */ + case 1: + reg |= AR5K_REG_SM(pdg_to_idx[0], AR5K_PHY_TPC_RG1_PDGAIN_1); + break; + } + ath5k_hw_reg_write(ah, reg, AR5K_PHY_TPC_RG1); + + /* + * Write TX power values + */ + for (i = 0; i < (AR5K_EEPROM_POWER_TABLE_SIZE / 2); i++) { + u32 val = get_unaligned_le32(&pdadc_out[4 * i]); + ath5k_hw_reg_write(ah, val, AR5K_PHY_PDADC_TXPOWER(i)); + } +} + + +/* + * Common code for PCDAC/PDADC tables + */ + +/** + * ath5k_setup_channel_powertable() - Set up power table for this channel + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + * @ee_mode: One of enum ath5k_driver_mode + * @type: One of enum ath5k_powertable_type (eeprom.h) + * + * This is the main function that uses all of the above + * to set PCDAC/PDADC table on hw for the current channel. + * This table is used for tx power calibration on the baseband, + * without it we get weird tx power levels and in some cases + * distorted spectral mask + */ +static int +ath5k_setup_channel_powertable(struct ath5k_hw *ah, + struct ieee80211_channel *channel, + u8 ee_mode, u8 type) +{ + struct ath5k_pdgain_info *pdg_L, *pdg_R; + struct ath5k_chan_pcal_info *pcinfo_L; + struct ath5k_chan_pcal_info *pcinfo_R; + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u8 *pdg_curve_to_idx = ee->ee_pdc_to_idx[ee_mode]; + s16 table_min[AR5K_EEPROM_N_PD_GAINS]; + s16 table_max[AR5K_EEPROM_N_PD_GAINS]; + u8 *tmpL; + u8 *tmpR; + u32 target = channel->center_freq; + int pdg, i; + + /* Get surrounding freq piers for this channel */ + ath5k_get_chan_pcal_surrounding_piers(ah, channel, + &pcinfo_L, + &pcinfo_R); + + /* Loop over pd gain curves on + * surrounding freq piers by index */ + for (pdg = 0; pdg < ee->ee_pd_gains[ee_mode]; pdg++) { + + /* Fill curves in reverse order + * from lower power (max gain) + * to higher power. Use curve -> idx + * backmapping we did on eeprom init */ + u8 idx = pdg_curve_to_idx[pdg]; + + /* Grab the needed curves by index */ + pdg_L = &pcinfo_L->pd_curves[idx]; + pdg_R = &pcinfo_R->pd_curves[idx]; + + /* Initialize the temp tables */ + tmpL = ah->ah_txpower.tmpL[pdg]; + tmpR = ah->ah_txpower.tmpR[pdg]; + + /* Set curve's x boundaries and create + * curves so that they cover the same + * range (if we don't do that one table + * will have values on some range and the + * other one won't have any so interpolation + * will fail) */ + table_min[pdg] = min(pdg_L->pd_pwr[0], + pdg_R->pd_pwr[0]) / 2; + + table_max[pdg] = max(pdg_L->pd_pwr[pdg_L->pd_points - 1], + pdg_R->pd_pwr[pdg_R->pd_points - 1]) / 2; + + /* Now create the curves on surrounding channels + * and interpolate if needed to get the final + * curve for this gain on this channel */ + switch (type) { + case AR5K_PWRTABLE_LINEAR_PCDAC: + /* Override min/max so that we don't loose + * accuracy (don't divide by 2) */ + table_min[pdg] = min(pdg_L->pd_pwr[0], + pdg_R->pd_pwr[0]); + + table_max[pdg] = + max(pdg_L->pd_pwr[pdg_L->pd_points - 1], + pdg_R->pd_pwr[pdg_R->pd_points - 1]); + + /* Override minimum so that we don't get + * out of bounds while extrapolating + * below. Don't do this when we have 2 + * curves and we are on the high power curve + * because table_min is ok in this case */ + if (!(ee->ee_pd_gains[ee_mode] > 1 && pdg == 0)) { + + table_min[pdg] = + ath5k_get_linear_pcdac_min(pdg_L->pd_step, + pdg_R->pd_step, + pdg_L->pd_pwr, + pdg_R->pd_pwr); + + /* Don't go too low because we will + * miss the upper part of the curve. + * Note: 126 = 31.5dB (max power supported) + * in 0.25dB units */ + if (table_max[pdg] - table_min[pdg] > 126) + table_min[pdg] = table_max[pdg] - 126; + } + + /* Fall through */ + case AR5K_PWRTABLE_PWR_TO_PCDAC: + case AR5K_PWRTABLE_PWR_TO_PDADC: + + ath5k_create_power_curve(table_min[pdg], + table_max[pdg], + pdg_L->pd_pwr, + pdg_L->pd_step, + pdg_L->pd_points, tmpL, type); + + /* We are in a calibration + * pier, no need to interpolate + * between freq piers */ + if (pcinfo_L == pcinfo_R) + continue; + + ath5k_create_power_curve(table_min[pdg], + table_max[pdg], + pdg_R->pd_pwr, + pdg_R->pd_step, + pdg_R->pd_points, tmpR, type); + break; + default: + return -EINVAL; + } + + /* Interpolate between curves + * of surrounding freq piers to + * get the final curve for this + * pd gain. Re-use tmpL for interpolation + * output */ + for (i = 0; (i < (u16) (table_max[pdg] - table_min[pdg])) && + (i < AR5K_EEPROM_POWER_TABLE_SIZE); i++) { + tmpL[i] = (u8) ath5k_get_interpolated_value(target, + (s16) pcinfo_L->freq, + (s16) pcinfo_R->freq, + (s16) tmpL[i], + (s16) tmpR[i]); + } + } + + /* Now we have a set of curves for this + * channel on tmpL (x range is table_max - table_min + * and y values are tmpL[pdg][]) sorted in the same + * order as EEPROM (because we've used the backmapping). + * So for RF5112 it's from higher power to lower power + * and for RF2413 it's from lower power to higher power. + * For RF5111 we only have one curve. */ + + /* Fill min and max power levels for this + * channel by interpolating the values on + * surrounding channels to complete the dataset */ + ah->ah_txpower.txp_min_pwr = ath5k_get_interpolated_value(target, + (s16) pcinfo_L->freq, + (s16) pcinfo_R->freq, + pcinfo_L->min_pwr, pcinfo_R->min_pwr); + + ah->ah_txpower.txp_max_pwr = ath5k_get_interpolated_value(target, + (s16) pcinfo_L->freq, + (s16) pcinfo_R->freq, + pcinfo_L->max_pwr, pcinfo_R->max_pwr); + + /* Fill PCDAC/PDADC table */ + switch (type) { + case AR5K_PWRTABLE_LINEAR_PCDAC: + /* For RF5112 we can have one or two curves + * and each curve covers a certain power lvl + * range so we need to do some more processing */ + ath5k_combine_linear_pcdac_curves(ah, table_min, table_max, + ee->ee_pd_gains[ee_mode]); + + /* Set txp.offset so that we can + * match max power value with max + * table index */ + ah->ah_txpower.txp_offset = 64 - (table_max[0] / 2); + break; + case AR5K_PWRTABLE_PWR_TO_PCDAC: + /* We are done for RF5111 since it has only + * one curve, just fit the curve on the table */ + ath5k_fill_pwr_to_pcdac_table(ah, table_min, table_max); + + /* No rate powertable adjustment for RF5111 */ + ah->ah_txpower.txp_min_idx = 0; + ah->ah_txpower.txp_offset = 0; + break; + case AR5K_PWRTABLE_PWR_TO_PDADC: + /* Set PDADC boundaries and fill + * final PDADC table */ + ath5k_combine_pwr_to_pdadc_curves(ah, table_min, table_max, + ee->ee_pd_gains[ee_mode]); + + /* Set txp.offset, note that table_min + * can be negative */ + ah->ah_txpower.txp_offset = table_min[0]; + break; + default: + return -EINVAL; + } + + ah->ah_txpower.txp_setup = true; + + return 0; +} + +/** + * ath5k_write_channel_powertable() - Set power table for current channel on hw + * @ah: The &struct ath5k_hw + * @ee_mode: One of enum ath5k_driver_mode + * @type: One of enum ath5k_powertable_type (eeprom.h) + */ +static void +ath5k_write_channel_powertable(struct ath5k_hw *ah, u8 ee_mode, u8 type) +{ + if (type == AR5K_PWRTABLE_PWR_TO_PDADC) + ath5k_write_pwr_to_pdadc_table(ah, ee_mode); + else + ath5k_write_pcdac_table(ah); +} + + +/** + * DOC: Per-rate tx power setting + * + * This is the code that sets the desired tx power limit (below + * maximum) on hw for each rate (we also have TPC that sets + * power per packet type). We do that by providing an index on the + * PCDAC/PDADC table we set up above, for each rate. + * + * For now we only limit txpower based on maximum tx power + * supported by hw (what's inside rate_info) + conformance test + * limits. We need to limit this even more, based on regulatory domain + * etc to be safe. Normally this is done from above so we don't care + * here, all we care is that the tx power we set will be O.K. + * for the hw (e.g. won't create noise on PA etc). + * + * Rate power table contains indices to PCDAC/PDADC table (0.5dB steps - + * x values) and is indexed as follows: + * rates[0] - rates[7] -> OFDM rates + * rates[8] - rates[14] -> CCK rates + * rates[15] -> XR rates (they all have the same power) + */ + +/** + * ath5k_setup_rate_powertable() - Set up rate power table for a given tx power + * @ah: The &struct ath5k_hw + * @max_pwr: The maximum tx power requested in 0.5dB steps + * @rate_info: The &struct ath5k_rate_pcal_info to fill + * @ee_mode: One of enum ath5k_driver_mode + */ +static void +ath5k_setup_rate_powertable(struct ath5k_hw *ah, u16 max_pwr, + struct ath5k_rate_pcal_info *rate_info, + u8 ee_mode) +{ + unsigned int i; + u16 *rates; + s16 rate_idx_scaled = 0; + + /* max_pwr is power level we got from driver/user in 0.5dB + * units, switch to 0.25dB units so we can compare */ + max_pwr *= 2; + max_pwr = min(max_pwr, (u16) ah->ah_txpower.txp_max_pwr) / 2; + + /* apply rate limits */ + rates = ah->ah_txpower.txp_rates_power_table; + + /* OFDM rates 6 to 24Mb/s */ + for (i = 0; i < 5; i++) + rates[i] = min(max_pwr, rate_info->target_power_6to24); + + /* Rest OFDM rates */ + rates[5] = min(rates[0], rate_info->target_power_36); + rates[6] = min(rates[0], rate_info->target_power_48); + rates[7] = min(rates[0], rate_info->target_power_54); + + /* CCK rates */ + /* 1L */ + rates[8] = min(rates[0], rate_info->target_power_6to24); + /* 2L */ + rates[9] = min(rates[0], rate_info->target_power_36); + /* 2S */ + rates[10] = min(rates[0], rate_info->target_power_36); + /* 5L */ + rates[11] = min(rates[0], rate_info->target_power_48); + /* 5S */ + rates[12] = min(rates[0], rate_info->target_power_48); + /* 11L */ + rates[13] = min(rates[0], rate_info->target_power_54); + /* 11S */ + rates[14] = min(rates[0], rate_info->target_power_54); + + /* XR rates */ + rates[15] = min(rates[0], rate_info->target_power_6to24); + + /* CCK rates have different peak to average ratio + * so we have to tweak their power so that gainf + * correction works ok. For this we use OFDM to + * CCK delta from eeprom */ + if ((ee_mode == AR5K_EEPROM_MODE_11G) && + (ah->ah_phy_revision < AR5K_SREV_PHY_5212A)) + for (i = 8; i <= 15; i++) + rates[i] -= ah->ah_txpower.txp_cck_ofdm_gainf_delta; + + /* Save min/max and current tx power for this channel + * in 0.25dB units. + * + * Note: We use rates[0] for current tx power because + * it covers most of the rates, in most cases. It's our + * tx power limit and what the user expects to see. */ + ah->ah_txpower.txp_min_pwr = 2 * rates[7]; + ah->ah_txpower.txp_cur_pwr = 2 * rates[0]; + + /* Set max txpower for correct OFDM operation on all rates + * -that is the txpower for 54Mbit-, it's used for the PAPD + * gain probe and it's in 0.5dB units */ + ah->ah_txpower.txp_ofdm = rates[7]; + + /* Now that we have all rates setup use table offset to + * match the power range set by user with the power indices + * on PCDAC/PDADC table */ + for (i = 0; i < 16; i++) { + rate_idx_scaled = rates[i] + ah->ah_txpower.txp_offset; + /* Don't get out of bounds */ + if (rate_idx_scaled > 63) + rate_idx_scaled = 63; + if (rate_idx_scaled < 0) + rate_idx_scaled = 0; + rates[i] = rate_idx_scaled; + } +} + + +/** + * ath5k_hw_txpower() - Set transmission power limit for a given channel + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + * @txpower: Requested tx power in 0.5dB steps + * + * Combines all of the above to set the requested tx power limit + * on hw. + */ +static int +ath5k_hw_txpower(struct ath5k_hw *ah, struct ieee80211_channel *channel, + u8 txpower) +{ + struct ath5k_rate_pcal_info rate_info; + struct ieee80211_channel *curr_channel = ah->ah_current_channel; + int ee_mode; + u8 type; + int ret; + + if (txpower > AR5K_TUNE_MAX_TXPOWER) { + ATH5K_ERR(ah, "invalid tx power: %u\n", txpower); + return -EINVAL; + } + + ee_mode = ath5k_eeprom_mode_from_channel(ah, channel); + + /* Initialize TX power table */ + switch (ah->ah_radio) { + case AR5K_RF5110: + /* TODO */ + return 0; + case AR5K_RF5111: + type = AR5K_PWRTABLE_PWR_TO_PCDAC; + break; + case AR5K_RF5112: + type = AR5K_PWRTABLE_LINEAR_PCDAC; + break; + case AR5K_RF2413: + case AR5K_RF5413: + case AR5K_RF2316: + case AR5K_RF2317: + case AR5K_RF2425: + type = AR5K_PWRTABLE_PWR_TO_PDADC; + break; + default: + return -EINVAL; + } + + /* + * If we don't change channel/mode skip tx powertable calculation + * and use the cached one. + */ + if (!ah->ah_txpower.txp_setup || + (channel->hw_value != curr_channel->hw_value) || + (channel->center_freq != curr_channel->center_freq)) { + /* Reset TX power values but preserve requested + * tx power from above */ + int requested_txpower = ah->ah_txpower.txp_requested; + + memset(&ah->ah_txpower, 0, sizeof(ah->ah_txpower)); + + /* Restore TPC setting and requested tx power */ + ah->ah_txpower.txp_tpc = AR5K_TUNE_TPC_TXPOWER; + + ah->ah_txpower.txp_requested = requested_txpower; + + /* Calculate the powertable */ + ret = ath5k_setup_channel_powertable(ah, channel, + ee_mode, type); + if (ret) + return ret; + } + + /* Write table on hw */ + ath5k_write_channel_powertable(ah, ee_mode, type); + + /* Limit max power if we have a CTL available */ + ath5k_get_max_ctl_power(ah, channel); + + /* FIXME: Antenna reduction stuff */ + + /* FIXME: Limit power on turbo modes */ + + /* FIXME: TPC scale reduction */ + + /* Get surrounding channels for per-rate power table + * calibration */ + ath5k_get_rate_pcal_data(ah, channel, &rate_info); + + /* Setup rate power table */ + ath5k_setup_rate_powertable(ah, txpower, &rate_info, ee_mode); + + /* Write rate power table on hw */ + ath5k_hw_reg_write(ah, AR5K_TXPOWER_OFDM(3, 24) | + AR5K_TXPOWER_OFDM(2, 16) | AR5K_TXPOWER_OFDM(1, 8) | + AR5K_TXPOWER_OFDM(0, 0), AR5K_PHY_TXPOWER_RATE1); + + ath5k_hw_reg_write(ah, AR5K_TXPOWER_OFDM(7, 24) | + AR5K_TXPOWER_OFDM(6, 16) | AR5K_TXPOWER_OFDM(5, 8) | + AR5K_TXPOWER_OFDM(4, 0), AR5K_PHY_TXPOWER_RATE2); + + ath5k_hw_reg_write(ah, AR5K_TXPOWER_CCK(10, 24) | + AR5K_TXPOWER_CCK(9, 16) | AR5K_TXPOWER_CCK(15, 8) | + AR5K_TXPOWER_CCK(8, 0), AR5K_PHY_TXPOWER_RATE3); + + ath5k_hw_reg_write(ah, AR5K_TXPOWER_CCK(14, 24) | + AR5K_TXPOWER_CCK(13, 16) | AR5K_TXPOWER_CCK(12, 8) | + AR5K_TXPOWER_CCK(11, 0), AR5K_PHY_TXPOWER_RATE4); + + /* FIXME: TPC support */ + if (ah->ah_txpower.txp_tpc) { + ath5k_hw_reg_write(ah, AR5K_PHY_TXPOWER_RATE_MAX_TPC_ENABLE | + AR5K_TUNE_MAX_TXPOWER, AR5K_PHY_TXPOWER_RATE_MAX); + + ath5k_hw_reg_write(ah, + AR5K_REG_MS(AR5K_TUNE_MAX_TXPOWER, AR5K_TPC_ACK) | + AR5K_REG_MS(AR5K_TUNE_MAX_TXPOWER, AR5K_TPC_CTS) | + AR5K_REG_MS(AR5K_TUNE_MAX_TXPOWER, AR5K_TPC_CHIRP), + AR5K_TPC); + } else { + ath5k_hw_reg_write(ah, AR5K_PHY_TXPOWER_RATE_MAX | + AR5K_TUNE_MAX_TXPOWER, AR5K_PHY_TXPOWER_RATE_MAX); + } + + return 0; +} + +/** + * ath5k_hw_set_txpower_limit() - Set txpower limit for the current channel + * @ah: The &struct ath5k_hw + * @txpower: The requested tx power limit in 0.5dB steps + * + * This function provides access to ath5k_hw_txpower to the driver in + * case user or an application changes it while PHY is running. + */ +int +ath5k_hw_set_txpower_limit(struct ath5k_hw *ah, u8 txpower) +{ + ATH5K_DBG(ah, ATH5K_DEBUG_TXPOWER, + "changing txpower to %d\n", txpower); + + return ath5k_hw_txpower(ah, ah->ah_current_channel, txpower); +} + + +/*************\ + Init function +\*************/ + +/** + * ath5k_hw_phy_init() - Initialize PHY + * @ah: The &struct ath5k_hw + * @channel: The @struct ieee80211_channel + * @mode: One of enum ath5k_driver_mode + * @fast: Try a fast channel switch instead + * + * This is the main function used during reset to initialize PHY + * or do a fast channel change if possible. + * + * NOTE: Do not call this one from the driver, it assumes PHY is in a + * warm reset state ! + */ +int +ath5k_hw_phy_init(struct ath5k_hw *ah, struct ieee80211_channel *channel, + u8 mode, bool fast) +{ + struct ieee80211_channel *curr_channel; + int ret, i; + u32 phy_tst1; + ret = 0; + + /* + * Sanity check for fast flag + * Don't try fast channel change when changing modulation + * mode/band. We check for chip compatibility on + * ath5k_hw_reset. + */ + curr_channel = ah->ah_current_channel; + if (fast && (channel->hw_value != curr_channel->hw_value)) + return -EINVAL; + + /* + * On fast channel change we only set the synth parameters + * while PHY is running, enable calibration and skip the rest. + */ + if (fast) { + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_RFBUS_REQ, + AR5K_PHY_RFBUS_REQ_REQUEST); + for (i = 0; i < 100; i++) { + if (ath5k_hw_reg_read(ah, AR5K_PHY_RFBUS_GRANT)) + break; + udelay(5); + } + /* Failed */ + if (i >= 100) + return -EIO; + + /* Set channel and wait for synth */ + ret = ath5k_hw_channel(ah, channel); + if (ret) + return ret; + + ath5k_hw_wait_for_synth(ah, channel); + } + + /* + * Set TX power + * + * Note: We need to do that before we set + * RF buffer settings on 5211/5212+ so that we + * properly set curve indices. + */ + ret = ath5k_hw_txpower(ah, channel, ah->ah_txpower.txp_requested ? + ah->ah_txpower.txp_requested * 2 : + AR5K_TUNE_MAX_TXPOWER); + if (ret) + return ret; + + /* Write OFDM timings on 5212*/ + if (ah->ah_version == AR5K_AR5212 && + channel->hw_value != AR5K_MODE_11B) { + + ret = ath5k_hw_write_ofdm_timings(ah, channel); + if (ret) + return ret; + + /* Spur info is available only from EEPROM versions + * greater than 5.3, but the EEPROM routines will use + * static values for older versions */ + if (ah->ah_mac_srev >= AR5K_SREV_AR5424) + ath5k_hw_set_spur_mitigation_filter(ah, + channel); + } + + /* If we used fast channel switching + * we are done, release RF bus and + * fire up NF calibration. + * + * Note: Only NF calibration due to + * channel change, not AGC calibration + * since AGC is still running ! + */ + if (fast) { + /* + * Release RF Bus grant + */ + AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_RFBUS_REQ, + AR5K_PHY_RFBUS_REQ_REQUEST); + + /* + * Start NF calibration + */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL, + AR5K_PHY_AGCCTL_NF); + + return ret; + } + + /* + * For 5210 we do all initialization using + * initvals, so we don't have to modify + * any settings (5210 also only supports + * a/aturbo modes) + */ + if (ah->ah_version != AR5K_AR5210) { + + /* + * Write initial RF gain settings + * This should work for both 5111/5112 + */ + ret = ath5k_hw_rfgain_init(ah, channel->band); + if (ret) + return ret; + + usleep_range(1000, 1500); + + /* + * Write RF buffer + */ + ret = ath5k_hw_rfregs_init(ah, channel, mode); + if (ret) + return ret; + + /*Enable/disable 802.11b mode on 5111 + (enable 2111 frequency converter + CCK)*/ + if (ah->ah_radio == AR5K_RF5111) { + if (mode == AR5K_MODE_11B) + AR5K_REG_ENABLE_BITS(ah, AR5K_TXCFG, + AR5K_TXCFG_B_MODE); + else + AR5K_REG_DISABLE_BITS(ah, AR5K_TXCFG, + AR5K_TXCFG_B_MODE); + } + + } else if (ah->ah_version == AR5K_AR5210) { + usleep_range(1000, 1500); + /* Disable phy and wait */ + ath5k_hw_reg_write(ah, AR5K_PHY_ACT_DISABLE, AR5K_PHY_ACT); + usleep_range(1000, 1500); + } + + /* Set channel on PHY */ + ret = ath5k_hw_channel(ah, channel); + if (ret) + return ret; + + /* + * Enable the PHY and wait until completion + * This includes BaseBand and Synthesizer + * activation. + */ + ath5k_hw_reg_write(ah, AR5K_PHY_ACT_ENABLE, AR5K_PHY_ACT); + + ath5k_hw_wait_for_synth(ah, channel); + + /* + * Perform ADC test to see if baseband is ready + * Set tx hold and check adc test register + */ + phy_tst1 = ath5k_hw_reg_read(ah, AR5K_PHY_TST1); + ath5k_hw_reg_write(ah, AR5K_PHY_TST1_TXHOLD, AR5K_PHY_TST1); + for (i = 0; i <= 20; i++) { + if (!(ath5k_hw_reg_read(ah, AR5K_PHY_ADC_TEST) & 0x10)) + break; + usleep_range(200, 250); + } + ath5k_hw_reg_write(ah, phy_tst1, AR5K_PHY_TST1); + + /* + * Start automatic gain control calibration + * + * During AGC calibration RX path is re-routed to + * a power detector so we don't receive anything. + * + * This method is used to calibrate some static offsets + * used together with on-the fly I/Q calibration (the + * one performed via ath5k_hw_phy_calibrate), which doesn't + * interrupt rx path. + * + * While rx path is re-routed to the power detector we also + * start a noise floor calibration to measure the + * card's noise floor (the noise we measure when we are not + * transmitting or receiving anything). + * + * If we are in a noisy environment, AGC calibration may time + * out and/or noise floor calibration might timeout. + */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_AGCCTL, + AR5K_PHY_AGCCTL_CAL | AR5K_PHY_AGCCTL_NF); + + /* At the same time start I/Q calibration for QAM constellation + * -no need for CCK- */ + ah->ah_iq_cal_needed = false; + if (!(mode == AR5K_MODE_11B)) { + ah->ah_iq_cal_needed = true; + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_IQ, + AR5K_PHY_IQ_CAL_NUM_LOG_MAX, 15); + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_IQ, + AR5K_PHY_IQ_RUN); + } + + /* Wait for gain calibration to finish (we check for I/Q calibration + * during ath5k_phy_calibrate) */ + if (ath5k_hw_register_timeout(ah, AR5K_PHY_AGCCTL, + AR5K_PHY_AGCCTL_CAL, 0, false)) { + ATH5K_ERR(ah, "gain calibration timeout (%uMHz)\n", + channel->center_freq); + } + + /* Restore antenna mode */ + ath5k_hw_set_antenna_mode(ah, ah->ah_ant_mode); + + return ret; +} diff --git a/drivers/net/wireless/ath/ath5k/qcu.c b/drivers/net/wireless/ath/ath5k/qcu.c new file mode 100644 index 0000000..65fe929 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/qcu.c @@ -0,0 +1,714 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter + * Copyright (c) 2006-2008 Nick Kossifidis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/********************************************\ +Queue Control Unit, DCF Control Unit Functions +\********************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "ath5k.h" +#include "reg.h" +#include "debug.h" +#include + +/** + * DOC: Queue Control Unit (QCU)/DCF Control Unit (DCU) functions + * + * Here we setup parameters for the 12 available TX queues. Note that + * on the various registers we can usually only map the first 10 of them so + * basically we have 10 queues to play with. Each queue has a matching + * QCU that controls when the queue will get triggered and multiple QCUs + * can be mapped to a single DCU that controls the various DFS parameters + * for the various queues. In our setup we have a 1:1 mapping between QCUs + * and DCUs allowing us to have different DFS settings for each queue. + * + * When a frame goes into a TX queue, QCU decides when it'll trigger a + * transmission based on various criteria (such as how many data we have inside + * it's buffer or -if it's a beacon queue- if it's time to fire up the queue + * based on TSF etc), DCU adds backoff, IFSes etc and then a scheduler + * (arbitrator) decides the priority of each QCU based on it's configuration + * (e.g. beacons are always transmitted when they leave DCU bypassing all other + * frames from other queues waiting to be transmitted). After a frame leaves + * the DCU it goes to PCU for further processing and then to PHY for + * the actual transmission. + */ + + +/******************\ +* Helper functions * +\******************/ + +/** + * ath5k_hw_num_tx_pending() - Get number of pending frames for a given queue + * @ah: The &struct ath5k_hw + * @queue: One of enum ath5k_tx_queue_id + */ +u32 +ath5k_hw_num_tx_pending(struct ath5k_hw *ah, unsigned int queue) +{ + u32 pending; + AR5K_ASSERT_ENTRY(queue, ah->ah_capabilities.cap_queues.q_tx_num); + + /* Return if queue is declared inactive */ + if (ah->ah_txq[queue].tqi_type == AR5K_TX_QUEUE_INACTIVE) + return false; + + /* XXX: How about AR5K_CFG_TXCNT ? */ + if (ah->ah_version == AR5K_AR5210) + return false; + + pending = ath5k_hw_reg_read(ah, AR5K_QUEUE_STATUS(queue)); + pending &= AR5K_QCU_STS_FRMPENDCNT; + + /* It's possible to have no frames pending even if TXE + * is set. To indicate that q has not stopped return + * true */ + if (!pending && AR5K_REG_READ_Q(ah, AR5K_QCU_TXE, queue)) + return true; + + return pending; +} + +/** + * ath5k_hw_release_tx_queue() - Set a transmit queue inactive + * @ah: The &struct ath5k_hw + * @queue: One of enum ath5k_tx_queue_id + */ +void +ath5k_hw_release_tx_queue(struct ath5k_hw *ah, unsigned int queue) +{ + if (WARN_ON(queue >= ah->ah_capabilities.cap_queues.q_tx_num)) + return; + + /* This queue will be skipped in further operations */ + ah->ah_txq[queue].tqi_type = AR5K_TX_QUEUE_INACTIVE; + /*For SIMR setup*/ + AR5K_Q_DISABLE_BITS(ah->ah_txq_status, queue); +} + +/** + * ath5k_cw_validate() - Make sure the given cw is valid + * @cw_req: The contention window value to check + * + * Make sure cw is a power of 2 minus 1 and smaller than 1024 + */ +static u16 +ath5k_cw_validate(u16 cw_req) +{ + cw_req = min(cw_req, (u16)1023); + + /* Check if cw_req + 1 a power of 2 */ + if (is_power_of_2(cw_req + 1)) + return cw_req; + + /* Check if cw_req is a power of 2 */ + if (is_power_of_2(cw_req)) + return cw_req - 1; + + /* If none of the above is correct + * find the closest power of 2 */ + cw_req = (u16) roundup_pow_of_two(cw_req) - 1; + + return cw_req; +} + +/** + * ath5k_hw_get_tx_queueprops() - Get properties for a transmit queue + * @ah: The &struct ath5k_hw + * @queue: One of enum ath5k_tx_queue_id + * @queue_info: The &struct ath5k_txq_info to fill + */ +int +ath5k_hw_get_tx_queueprops(struct ath5k_hw *ah, int queue, + struct ath5k_txq_info *queue_info) +{ + memcpy(queue_info, &ah->ah_txq[queue], sizeof(struct ath5k_txq_info)); + return 0; +} + +/** + * ath5k_hw_set_tx_queueprops() - Set properties for a transmit queue + * @ah: The &struct ath5k_hw + * @queue: One of enum ath5k_tx_queue_id + * @qinfo: The &struct ath5k_txq_info to use + * + * Returns 0 on success or -EIO if queue is inactive + */ +int +ath5k_hw_set_tx_queueprops(struct ath5k_hw *ah, int queue, + const struct ath5k_txq_info *qinfo) +{ + struct ath5k_txq_info *qi; + + AR5K_ASSERT_ENTRY(queue, ah->ah_capabilities.cap_queues.q_tx_num); + + qi = &ah->ah_txq[queue]; + + if (qi->tqi_type == AR5K_TX_QUEUE_INACTIVE) + return -EIO; + + /* copy and validate values */ + qi->tqi_type = qinfo->tqi_type; + qi->tqi_subtype = qinfo->tqi_subtype; + qi->tqi_flags = qinfo->tqi_flags; + /* + * According to the docs: Although the AIFS field is 8 bit wide, + * the maximum supported value is 0xFC. Setting it higher than that + * will cause the DCU to hang. + */ + qi->tqi_aifs = min(qinfo->tqi_aifs, (u8)0xFC); + qi->tqi_cw_min = ath5k_cw_validate(qinfo->tqi_cw_min); + qi->tqi_cw_max = ath5k_cw_validate(qinfo->tqi_cw_max); + qi->tqi_cbr_period = qinfo->tqi_cbr_period; + qi->tqi_cbr_overflow_limit = qinfo->tqi_cbr_overflow_limit; + qi->tqi_burst_time = qinfo->tqi_burst_time; + qi->tqi_ready_time = qinfo->tqi_ready_time; + + /*XXX: Is this supported on 5210 ?*/ + /*XXX: Is this correct for AR5K_WME_AC_VI,VO ???*/ + if ((qinfo->tqi_type == AR5K_TX_QUEUE_DATA && + ((qinfo->tqi_subtype == AR5K_WME_AC_VI) || + (qinfo->tqi_subtype == AR5K_WME_AC_VO))) || + qinfo->tqi_type == AR5K_TX_QUEUE_UAPSD) + qi->tqi_flags |= AR5K_TXQ_FLAG_POST_FR_BKOFF_DIS; + + return 0; +} + +/** + * ath5k_hw_setup_tx_queue() - Initialize a transmit queue + * @ah: The &struct ath5k_hw + * @queue_type: One of enum ath5k_tx_queue + * @queue_info: The &struct ath5k_txq_info to use + * + * Returns 0 on success, -EINVAL on invalid arguments + */ +int +ath5k_hw_setup_tx_queue(struct ath5k_hw *ah, enum ath5k_tx_queue queue_type, + struct ath5k_txq_info *queue_info) +{ + unsigned int queue; + int ret; + + /* + * Get queue by type + */ + /* 5210 only has 2 queues */ + if (ah->ah_capabilities.cap_queues.q_tx_num == 2) { + switch (queue_type) { + case AR5K_TX_QUEUE_DATA: + queue = AR5K_TX_QUEUE_ID_NOQCU_DATA; + break; + case AR5K_TX_QUEUE_BEACON: + case AR5K_TX_QUEUE_CAB: + queue = AR5K_TX_QUEUE_ID_NOQCU_BEACON; + break; + default: + return -EINVAL; + } + } else { + switch (queue_type) { + case AR5K_TX_QUEUE_DATA: + for (queue = AR5K_TX_QUEUE_ID_DATA_MIN; + ah->ah_txq[queue].tqi_type != + AR5K_TX_QUEUE_INACTIVE; queue++) { + + if (queue > AR5K_TX_QUEUE_ID_DATA_MAX) + return -EINVAL; + } + break; + case AR5K_TX_QUEUE_UAPSD: + queue = AR5K_TX_QUEUE_ID_UAPSD; + break; + case AR5K_TX_QUEUE_BEACON: + queue = AR5K_TX_QUEUE_ID_BEACON; + break; + case AR5K_TX_QUEUE_CAB: + queue = AR5K_TX_QUEUE_ID_CAB; + break; + default: + return -EINVAL; + } + } + + /* + * Setup internal queue structure + */ + memset(&ah->ah_txq[queue], 0, sizeof(struct ath5k_txq_info)); + ah->ah_txq[queue].tqi_type = queue_type; + + if (queue_info != NULL) { + queue_info->tqi_type = queue_type; + ret = ath5k_hw_set_tx_queueprops(ah, queue, queue_info); + if (ret) + return ret; + } + + /* + * We use ah_txq_status to hold a temp value for + * the Secondary interrupt mask registers on 5211+ + * check out ath5k_hw_reset_tx_queue + */ + AR5K_Q_ENABLE_BITS(ah->ah_txq_status, queue); + + return queue; +} + + +/*******************************\ +* Single QCU/DCU initialization * +\*******************************/ + +/** + * ath5k_hw_set_tx_retry_limits() - Set tx retry limits on DCU + * @ah: The &struct ath5k_hw + * @queue: One of enum ath5k_tx_queue_id + * + * This function is used when initializing a queue, to set + * retry limits based on ah->ah_retry_* and the chipset used. + */ +void +ath5k_hw_set_tx_retry_limits(struct ath5k_hw *ah, + unsigned int queue) +{ + /* Single data queue on AR5210 */ + if (ah->ah_version == AR5K_AR5210) { + struct ath5k_txq_info *tq = &ah->ah_txq[queue]; + + if (queue > 0) + return; + + ath5k_hw_reg_write(ah, + (tq->tqi_cw_min << AR5K_NODCU_RETRY_LMT_CW_MIN_S) + | AR5K_REG_SM(ah->ah_retry_long, + AR5K_NODCU_RETRY_LMT_SLG_RETRY) + | AR5K_REG_SM(ah->ah_retry_short, + AR5K_NODCU_RETRY_LMT_SSH_RETRY) + | AR5K_REG_SM(ah->ah_retry_long, + AR5K_NODCU_RETRY_LMT_LG_RETRY) + | AR5K_REG_SM(ah->ah_retry_short, + AR5K_NODCU_RETRY_LMT_SH_RETRY), + AR5K_NODCU_RETRY_LMT); + /* DCU on AR5211+ */ + } else { + ath5k_hw_reg_write(ah, + AR5K_REG_SM(ah->ah_retry_long, + AR5K_DCU_RETRY_LMT_RTS) + | AR5K_REG_SM(ah->ah_retry_long, + AR5K_DCU_RETRY_LMT_STA_RTS) + | AR5K_REG_SM(max(ah->ah_retry_long, ah->ah_retry_short), + AR5K_DCU_RETRY_LMT_STA_DATA), + AR5K_QUEUE_DFS_RETRY_LIMIT(queue)); + } +} + +/** + * ath5k_hw_reset_tx_queue() - Initialize a single hw queue + * @ah: The &struct ath5k_hw + * @queue: One of enum ath5k_tx_queue_id + * + * Set DCF properties for the given transmit queue on DCU + * and configures all queue-specific parameters. + */ +int +ath5k_hw_reset_tx_queue(struct ath5k_hw *ah, unsigned int queue) +{ + struct ath5k_txq_info *tq = &ah->ah_txq[queue]; + + AR5K_ASSERT_ENTRY(queue, ah->ah_capabilities.cap_queues.q_tx_num); + + tq = &ah->ah_txq[queue]; + + /* Skip if queue inactive or if we are on AR5210 + * that doesn't have QCU/DCU */ + if ((ah->ah_version == AR5K_AR5210) || + (tq->tqi_type == AR5K_TX_QUEUE_INACTIVE)) + return 0; + + /* + * Set contention window (cw_min/cw_max) + * and arbitrated interframe space (aifs)... + */ + ath5k_hw_reg_write(ah, + AR5K_REG_SM(tq->tqi_cw_min, AR5K_DCU_LCL_IFS_CW_MIN) | + AR5K_REG_SM(tq->tqi_cw_max, AR5K_DCU_LCL_IFS_CW_MAX) | + AR5K_REG_SM(tq->tqi_aifs, AR5K_DCU_LCL_IFS_AIFS), + AR5K_QUEUE_DFS_LOCAL_IFS(queue)); + + /* + * Set tx retry limits for this queue + */ + ath5k_hw_set_tx_retry_limits(ah, queue); + + + /* + * Set misc registers + */ + + /* Enable DCU to wait for next fragment from QCU */ + AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_DFS_MISC(queue), + AR5K_DCU_MISC_FRAG_WAIT); + + /* On Maui and Spirit use the global seqnum on DCU */ + if (ah->ah_mac_version < AR5K_SREV_AR5211) + AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_DFS_MISC(queue), + AR5K_DCU_MISC_SEQNUM_CTL); + + /* Constant bit rate period */ + if (tq->tqi_cbr_period) { + ath5k_hw_reg_write(ah, AR5K_REG_SM(tq->tqi_cbr_period, + AR5K_QCU_CBRCFG_INTVAL) | + AR5K_REG_SM(tq->tqi_cbr_overflow_limit, + AR5K_QCU_CBRCFG_ORN_THRES), + AR5K_QUEUE_CBRCFG(queue)); + + AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_MISC(queue), + AR5K_QCU_MISC_FRSHED_CBR); + + if (tq->tqi_cbr_overflow_limit) + AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_MISC(queue), + AR5K_QCU_MISC_CBR_THRES_ENABLE); + } + + /* Ready time interval */ + if (tq->tqi_ready_time && (tq->tqi_type != AR5K_TX_QUEUE_CAB)) + ath5k_hw_reg_write(ah, AR5K_REG_SM(tq->tqi_ready_time, + AR5K_QCU_RDYTIMECFG_INTVAL) | + AR5K_QCU_RDYTIMECFG_ENABLE, + AR5K_QUEUE_RDYTIMECFG(queue)); + + if (tq->tqi_burst_time) { + ath5k_hw_reg_write(ah, AR5K_REG_SM(tq->tqi_burst_time, + AR5K_DCU_CHAN_TIME_DUR) | + AR5K_DCU_CHAN_TIME_ENABLE, + AR5K_QUEUE_DFS_CHANNEL_TIME(queue)); + + if (tq->tqi_flags & AR5K_TXQ_FLAG_RDYTIME_EXP_POLICY_ENABLE) + AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_MISC(queue), + AR5K_QCU_MISC_RDY_VEOL_POLICY); + } + + /* Enable/disable Post frame backoff */ + if (tq->tqi_flags & AR5K_TXQ_FLAG_BACKOFF_DISABLE) + ath5k_hw_reg_write(ah, AR5K_DCU_MISC_POST_FR_BKOFF_DIS, + AR5K_QUEUE_DFS_MISC(queue)); + + /* Enable/disable fragmentation burst backoff */ + if (tq->tqi_flags & AR5K_TXQ_FLAG_FRAG_BURST_BACKOFF_ENABLE) + ath5k_hw_reg_write(ah, AR5K_DCU_MISC_BACKOFF_FRAG, + AR5K_QUEUE_DFS_MISC(queue)); + + /* + * Set registers by queue type + */ + switch (tq->tqi_type) { + case AR5K_TX_QUEUE_BEACON: + AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_MISC(queue), + AR5K_QCU_MISC_FRSHED_DBA_GT | + AR5K_QCU_MISC_CBREXP_BCN_DIS | + AR5K_QCU_MISC_BCN_ENABLE); + + AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_DFS_MISC(queue), + (AR5K_DCU_MISC_ARBLOCK_CTL_GLOBAL << + AR5K_DCU_MISC_ARBLOCK_CTL_S) | + AR5K_DCU_MISC_ARBLOCK_IGNORE | + AR5K_DCU_MISC_POST_FR_BKOFF_DIS | + AR5K_DCU_MISC_BCN_ENABLE); + break; + + case AR5K_TX_QUEUE_CAB: + /* XXX: use BCN_SENT_GT, if we can figure out how */ + AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_MISC(queue), + AR5K_QCU_MISC_FRSHED_DBA_GT | + AR5K_QCU_MISC_CBREXP_DIS | + AR5K_QCU_MISC_CBREXP_BCN_DIS); + + ath5k_hw_reg_write(ah, ((tq->tqi_ready_time - + (AR5K_TUNE_SW_BEACON_RESP - + AR5K_TUNE_DMA_BEACON_RESP) - + AR5K_TUNE_ADDITIONAL_SWBA_BACKOFF) * 1024) | + AR5K_QCU_RDYTIMECFG_ENABLE, + AR5K_QUEUE_RDYTIMECFG(queue)); + + AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_DFS_MISC(queue), + (AR5K_DCU_MISC_ARBLOCK_CTL_GLOBAL << + AR5K_DCU_MISC_ARBLOCK_CTL_S)); + break; + + case AR5K_TX_QUEUE_UAPSD: + AR5K_REG_ENABLE_BITS(ah, AR5K_QUEUE_MISC(queue), + AR5K_QCU_MISC_CBREXP_DIS); + break; + + case AR5K_TX_QUEUE_DATA: + default: + break; + } + + /* TODO: Handle frame compression */ + + /* + * Enable interrupts for this tx queue + * in the secondary interrupt mask registers + */ + if (tq->tqi_flags & AR5K_TXQ_FLAG_TXOKINT_ENABLE) + AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_txok, queue); + + if (tq->tqi_flags & AR5K_TXQ_FLAG_TXERRINT_ENABLE) + AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_txerr, queue); + + if (tq->tqi_flags & AR5K_TXQ_FLAG_TXURNINT_ENABLE) + AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_txurn, queue); + + if (tq->tqi_flags & AR5K_TXQ_FLAG_TXDESCINT_ENABLE) + AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_txdesc, queue); + + if (tq->tqi_flags & AR5K_TXQ_FLAG_TXEOLINT_ENABLE) + AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_txeol, queue); + + if (tq->tqi_flags & AR5K_TXQ_FLAG_CBRORNINT_ENABLE) + AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_cbrorn, queue); + + if (tq->tqi_flags & AR5K_TXQ_FLAG_CBRURNINT_ENABLE) + AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_cbrurn, queue); + + if (tq->tqi_flags & AR5K_TXQ_FLAG_QTRIGINT_ENABLE) + AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_qtrig, queue); + + if (tq->tqi_flags & AR5K_TXQ_FLAG_TXNOFRMINT_ENABLE) + AR5K_Q_ENABLE_BITS(ah->ah_txq_imr_nofrm, queue); + + /* Update secondary interrupt mask registers */ + + /* Filter out inactive queues */ + ah->ah_txq_imr_txok &= ah->ah_txq_status; + ah->ah_txq_imr_txerr &= ah->ah_txq_status; + ah->ah_txq_imr_txurn &= ah->ah_txq_status; + ah->ah_txq_imr_txdesc &= ah->ah_txq_status; + ah->ah_txq_imr_txeol &= ah->ah_txq_status; + ah->ah_txq_imr_cbrorn &= ah->ah_txq_status; + ah->ah_txq_imr_cbrurn &= ah->ah_txq_status; + ah->ah_txq_imr_qtrig &= ah->ah_txq_status; + ah->ah_txq_imr_nofrm &= ah->ah_txq_status; + + ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txq_imr_txok, + AR5K_SIMR0_QCU_TXOK) | + AR5K_REG_SM(ah->ah_txq_imr_txdesc, + AR5K_SIMR0_QCU_TXDESC), + AR5K_SIMR0); + + ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txq_imr_txerr, + AR5K_SIMR1_QCU_TXERR) | + AR5K_REG_SM(ah->ah_txq_imr_txeol, + AR5K_SIMR1_QCU_TXEOL), + AR5K_SIMR1); + + /* Update SIMR2 but don't overwrite rest simr2 settings */ + AR5K_REG_DISABLE_BITS(ah, AR5K_SIMR2, AR5K_SIMR2_QCU_TXURN); + AR5K_REG_ENABLE_BITS(ah, AR5K_SIMR2, + AR5K_REG_SM(ah->ah_txq_imr_txurn, + AR5K_SIMR2_QCU_TXURN)); + + ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txq_imr_cbrorn, + AR5K_SIMR3_QCBRORN) | + AR5K_REG_SM(ah->ah_txq_imr_cbrurn, + AR5K_SIMR3_QCBRURN), + AR5K_SIMR3); + + ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txq_imr_qtrig, + AR5K_SIMR4_QTRIG), AR5K_SIMR4); + + /* Set TXNOFRM_QCU for the queues with TXNOFRM enabled */ + ath5k_hw_reg_write(ah, AR5K_REG_SM(ah->ah_txq_imr_nofrm, + AR5K_TXNOFRM_QCU), AR5K_TXNOFRM); + + /* No queue has TXNOFRM enabled, disable the interrupt + * by setting AR5K_TXNOFRM to zero */ + if (ah->ah_txq_imr_nofrm == 0) + ath5k_hw_reg_write(ah, 0, AR5K_TXNOFRM); + + /* Set QCU mask for this DCU to save power */ + AR5K_REG_WRITE_Q(ah, AR5K_QUEUE_QCUMASK(queue), queue); + + return 0; +} + + +/**************************\ +* Global QCU/DCU functions * +\**************************/ + +/** + * ath5k_hw_set_ifs_intervals() - Set global inter-frame spaces on DCU + * @ah: The &struct ath5k_hw + * @slot_time: Slot time in us + * + * Sets the global IFS intervals on DCU (also works on AR5210) for + * the given slot time and the current bwmode. + */ +int ath5k_hw_set_ifs_intervals(struct ath5k_hw *ah, unsigned int slot_time) +{ + struct ieee80211_channel *channel = ah->ah_current_channel; + enum ieee80211_band band; + struct ieee80211_rate *rate; + u32 ack_tx_time, eifs, eifs_clock, sifs, sifs_clock; + u32 slot_time_clock = ath5k_hw_htoclock(ah, slot_time); + + if (slot_time < 6 || slot_time_clock > AR5K_SLOT_TIME_MAX) + return -EINVAL; + + sifs = ath5k_hw_get_default_sifs(ah); + sifs_clock = ath5k_hw_htoclock(ah, sifs - 2); + + /* EIFS + * Txtime of ack at lowest rate + SIFS + DIFS + * (DIFS = SIFS + 2 * Slot time) + * + * Note: HAL has some predefined values for EIFS + * Turbo: (37 + 2 * 6) + * Default: (74 + 2 * 9) + * Half: (149 + 2 * 13) + * Quarter: (298 + 2 * 21) + * + * (74 + 2 * 6) for AR5210 default and turbo ! + * + * According to the formula we have + * ack_tx_time = 25 for turbo and + * ack_tx_time = 42.5 * clock multiplier + * for default/half/quarter. + * + * This can't be right, 42 is what we would get + * from ath5k_hw_get_frame_dur_for_bwmode or + * ieee80211_generic_frame_duration for zero frame + * length and without SIFS ! + * + * Also we have different lowest rate for 802.11a + */ + if (channel->band == IEEE80211_BAND_5GHZ) + band = IEEE80211_BAND_5GHZ; + else + band = IEEE80211_BAND_2GHZ; + + rate = &ah->sbands[band].bitrates[0]; + ack_tx_time = ath5k_hw_get_frame_duration(ah, band, 10, rate, false); + + /* ack_tx_time includes an SIFS already */ + eifs = ack_tx_time + sifs + 2 * slot_time; + eifs_clock = ath5k_hw_htoclock(ah, eifs); + + /* Set IFS settings on AR5210 */ + if (ah->ah_version == AR5K_AR5210) { + u32 pifs, pifs_clock, difs, difs_clock; + + /* Set slot time */ + ath5k_hw_reg_write(ah, slot_time_clock, AR5K_SLOT_TIME); + + /* Set EIFS */ + eifs_clock = AR5K_REG_SM(eifs_clock, AR5K_IFS1_EIFS); + + /* PIFS = Slot time + SIFS */ + pifs = slot_time + sifs; + pifs_clock = ath5k_hw_htoclock(ah, pifs); + pifs_clock = AR5K_REG_SM(pifs_clock, AR5K_IFS1_PIFS); + + /* DIFS = SIFS + 2 * Slot time */ + difs = sifs + 2 * slot_time; + difs_clock = ath5k_hw_htoclock(ah, difs); + + /* Set SIFS/DIFS */ + ath5k_hw_reg_write(ah, (difs_clock << + AR5K_IFS0_DIFS_S) | sifs_clock, + AR5K_IFS0); + + /* Set PIFS/EIFS and preserve AR5K_INIT_CARR_SENSE_EN */ + ath5k_hw_reg_write(ah, pifs_clock | eifs_clock | + (AR5K_INIT_CARR_SENSE_EN << AR5K_IFS1_CS_EN_S), + AR5K_IFS1); + + return 0; + } + + /* Set IFS slot time */ + ath5k_hw_reg_write(ah, slot_time_clock, AR5K_DCU_GBL_IFS_SLOT); + + /* Set EIFS interval */ + ath5k_hw_reg_write(ah, eifs_clock, AR5K_DCU_GBL_IFS_EIFS); + + /* Set SIFS interval in usecs */ + AR5K_REG_WRITE_BITS(ah, AR5K_DCU_GBL_IFS_MISC, + AR5K_DCU_GBL_IFS_MISC_SIFS_DUR_USEC, + sifs); + + /* Set SIFS interval in clock cycles */ + ath5k_hw_reg_write(ah, sifs_clock, AR5K_DCU_GBL_IFS_SIFS); + + return 0; +} + + +/** + * ath5k_hw_init_queues() - Initialize tx queues + * @ah: The &struct ath5k_hw + * + * Initializes all tx queues based on information on + * ah->ah_txq* set by the driver + */ +int +ath5k_hw_init_queues(struct ath5k_hw *ah) +{ + int i, ret; + + /* TODO: HW Compression support for data queues */ + /* TODO: Burst prefetch for data queues */ + + /* + * Reset queues and start beacon timers at the end of the reset routine + * This also sets QCU mask on each DCU for 1:1 qcu to dcu mapping + * Note: If we want we can assign multiple qcus on one dcu. + */ + if (ah->ah_version != AR5K_AR5210) + for (i = 0; i < ah->ah_capabilities.cap_queues.q_tx_num; i++) { + ret = ath5k_hw_reset_tx_queue(ah, i); + if (ret) { + ATH5K_ERR(ah, + "failed to reset TX queue #%d\n", i); + return ret; + } + } + else + /* No QCU/DCU on AR5210, just set tx + * retry limits. We set IFS parameters + * on ath5k_hw_set_ifs_intervals */ + ath5k_hw_set_tx_retry_limits(ah, 0); + + /* Set the turbo flag when operating on 40MHz */ + if (ah->ah_bwmode == AR5K_BWMODE_40MHZ) + AR5K_REG_ENABLE_BITS(ah, AR5K_DCU_GBL_IFS_MISC, + AR5K_DCU_GBL_IFS_MISC_TURBO_MODE); + + /* If we didn't set IFS timings through + * ath5k_hw_set_coverage_class make sure + * we set them here */ + if (!ah->ah_coverage_class) { + unsigned int slot_time = ath5k_hw_get_default_slottime(ah); + ath5k_hw_set_ifs_intervals(ah, slot_time); + } + + return 0; +} diff --git a/drivers/net/wireless/ath/ath5k/reg.h b/drivers/net/wireless/ath/ath5k/reg.h new file mode 100644 index 0000000..0ea1608 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/reg.h @@ -0,0 +1,2604 @@ +/* + * Copyright (c) 2006-2008 Nick Kossifidis + * Copyright (c) 2004-2008 Reyk Floeter + * Copyright (c) 2007-2008 Michael Taylor + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/* + * Register values for Atheros 5210/5211/5212 cards from OpenBSD's ar5k + * maintained by Reyk Floeter + * + * I tried to document those registers by looking at ar5k code, some + * 802.11 (802.11e mostly) papers and by reading various public available + * Atheros presentations and papers like these: + * + * 5210 - http://nova.stanford.edu/~bbaas/ps/isscc2002_slides.pdf + * + * 5211 - http://www.hotchips.org/archives/hc14/3_Tue/16_mcfarland.pdf + * + * This file also contains register values found on a memory dump of + * Atheros's ART program (Atheros Radio Test), on ath9k, on legacy-hal + * released by Atheros and on various debug messages found on the net. + */ + +#include "../reg.h" + +/*====MAC DMA REGISTERS====*/ + +/* + * AR5210-Specific TXDP registers + * 5210 has only 2 transmit queues so no DCU/QCU, just + * 2 transmit descriptor pointers... + */ +#define AR5K_NOQCU_TXDP0 0x0000 /* Queue 0 - data */ +#define AR5K_NOQCU_TXDP1 0x0004 /* Queue 1 - beacons */ + +/* + * Mac Control Register + */ +#define AR5K_CR 0x0008 /* Register Address */ +#define AR5K_CR_TXE0 0x00000001 /* TX Enable for queue 0 on 5210 */ +#define AR5K_CR_TXE1 0x00000002 /* TX Enable for queue 1 on 5210 */ +#define AR5K_CR_RXE 0x00000004 /* RX Enable */ +#define AR5K_CR_TXD0 0x00000008 /* TX Disable for queue 0 on 5210 */ +#define AR5K_CR_TXD1 0x00000010 /* TX Disable for queue 1 on 5210 */ +#define AR5K_CR_RXD 0x00000020 /* RX Disable */ +#define AR5K_CR_SWI 0x00000040 /* Software Interrupt */ + +/* + * RX Descriptor Pointer register + */ +#define AR5K_RXDP 0x000c + +/* + * Configuration and status register + */ +#define AR5K_CFG 0x0014 /* Register Address */ +#define AR5K_CFG_SWTD 0x00000001 /* Byte-swap TX descriptor (for big endian archs) */ +#define AR5K_CFG_SWTB 0x00000002 /* Byte-swap TX buffer */ +#define AR5K_CFG_SWRD 0x00000004 /* Byte-swap RX descriptor */ +#define AR5K_CFG_SWRB 0x00000008 /* Byte-swap RX buffer */ +#define AR5K_CFG_SWRG 0x00000010 /* Byte-swap Register access */ +#define AR5K_CFG_IBSS 0x00000020 /* 0-BSS, 1-IBSS [5211+] */ +#define AR5K_CFG_PHY_OK 0x00000100 /* [5211+] */ +#define AR5K_CFG_EEBS 0x00000200 /* EEPROM is busy */ +#define AR5K_CFG_CLKGD 0x00000400 /* Clock gated (Disable dynamic clock) */ +#define AR5K_CFG_TXCNT 0x00007800 /* Tx frame count (?) [5210] */ +#define AR5K_CFG_TXCNT_S 11 +#define AR5K_CFG_TXFSTAT 0x00008000 /* Tx frame status (?) [5210] */ +#define AR5K_CFG_TXFSTRT 0x00010000 /* [5210] */ +#define AR5K_CFG_PCI_THRES 0x00060000 /* PCI Master req q threshold [5211+] */ +#define AR5K_CFG_PCI_THRES_S 17 + +/* + * Interrupt enable register + */ +#define AR5K_IER 0x0024 /* Register Address */ +#define AR5K_IER_DISABLE 0x00000000 /* Disable card interrupts */ +#define AR5K_IER_ENABLE 0x00000001 /* Enable card interrupts */ + + +/* + * 0x0028 is Beacon Control Register on 5210 + * and first RTS duration register on 5211 + */ + +/* + * Beacon control register [5210] + */ +#define AR5K_BCR 0x0028 /* Register Address */ +#define AR5K_BCR_AP 0x00000000 /* AP mode */ +#define AR5K_BCR_ADHOC 0x00000001 /* Ad-Hoc mode */ +#define AR5K_BCR_BDMAE 0x00000002 /* DMA enable */ +#define AR5K_BCR_TQ1FV 0x00000004 /* Use Queue1 for CAB traffic */ +#define AR5K_BCR_TQ1V 0x00000008 /* Use Queue1 for Beacon traffic */ +#define AR5K_BCR_BCGET 0x00000010 + +/* + * First RTS duration register [5211] + */ +#define AR5K_RTSD0 0x0028 /* Register Address */ +#define AR5K_RTSD0_6 0x000000ff /* 6Mb RTS duration mask (?) */ +#define AR5K_RTSD0_6_S 0 /* 6Mb RTS duration shift (?) */ +#define AR5K_RTSD0_9 0x0000ff00 /* 9Mb*/ +#define AR5K_RTSD0_9_S 8 +#define AR5K_RTSD0_12 0x00ff0000 /* 12Mb*/ +#define AR5K_RTSD0_12_S 16 +#define AR5K_RTSD0_18 0xff000000 /* 16Mb*/ +#define AR5K_RTSD0_18_S 24 + + +/* + * 0x002c is Beacon Status Register on 5210 + * and second RTS duration register on 5211 + */ + +/* + * Beacon status register [5210] + * + * As i can see in ar5k_ar5210_tx_start Reyk uses some of the values of BCR + * for this register, so i guess TQ1V,TQ1FV and BDMAE have the same meaning + * here and SNP/SNAP means "snapshot" (so this register gets synced with BCR). + * So SNAPPEDBCRVALID should also stand for "snapped BCR -values- valid", so i + * renamed it to SNAPSHOTSVALID to make more sense. I really have no idea what + * else can it be. I also renamed SNPBCMD to SNPADHOC to match BCR. + */ +#define AR5K_BSR 0x002c /* Register Address */ +#define AR5K_BSR_BDLYSW 0x00000001 /* SW Beacon delay (?) */ +#define AR5K_BSR_BDLYDMA 0x00000002 /* DMA Beacon delay (?) */ +#define AR5K_BSR_TXQ1F 0x00000004 /* Beacon queue (1) finished */ +#define AR5K_BSR_ATIMDLY 0x00000008 /* ATIM delay (?) */ +#define AR5K_BSR_SNPADHOC 0x00000100 /* Ad-hoc mode set (?) */ +#define AR5K_BSR_SNPBDMAE 0x00000200 /* Beacon DMA enabled (?) */ +#define AR5K_BSR_SNPTQ1FV 0x00000400 /* Queue1 is used for CAB traffic (?) */ +#define AR5K_BSR_SNPTQ1V 0x00000800 /* Queue1 is used for Beacon traffic (?) */ +#define AR5K_BSR_SNAPSHOTSVALID 0x00001000 /* BCR snapshots are valid (?) */ +#define AR5K_BSR_SWBA_CNT 0x00ff0000 + +/* + * Second RTS duration register [5211] + */ +#define AR5K_RTSD1 0x002c /* Register Address */ +#define AR5K_RTSD1_24 0x000000ff /* 24Mb */ +#define AR5K_RTSD1_24_S 0 +#define AR5K_RTSD1_36 0x0000ff00 /* 36Mb */ +#define AR5K_RTSD1_36_S 8 +#define AR5K_RTSD1_48 0x00ff0000 /* 48Mb */ +#define AR5K_RTSD1_48_S 16 +#define AR5K_RTSD1_54 0xff000000 /* 54Mb */ +#define AR5K_RTSD1_54_S 24 + + +/* + * Transmit configuration register + */ +#define AR5K_TXCFG 0x0030 /* Register Address */ +#define AR5K_TXCFG_SDMAMR 0x00000007 /* DMA size (read) */ +#define AR5K_TXCFG_SDMAMR_S 0 +#define AR5K_TXCFG_B_MODE 0x00000008 /* Set b mode for 5111 (enable 2111) */ +#define AR5K_TXCFG_TXFSTP 0x00000008 /* TX DMA full Stop [5210] */ +#define AR5K_TXCFG_TXFULL 0x000003f0 /* TX Trigger level mask */ +#define AR5K_TXCFG_TXFULL_S 4 +#define AR5K_TXCFG_TXFULL_0B 0x00000000 +#define AR5K_TXCFG_TXFULL_64B 0x00000010 +#define AR5K_TXCFG_TXFULL_128B 0x00000020 +#define AR5K_TXCFG_TXFULL_192B 0x00000030 +#define AR5K_TXCFG_TXFULL_256B 0x00000040 +#define AR5K_TXCFG_TXCONT_EN 0x00000080 +#define AR5K_TXCFG_DMASIZE 0x00000100 /* Flag for passing DMA size [5210] */ +#define AR5K_TXCFG_JUMBO_DESC_EN 0x00000400 /* Enable jumbo tx descriptors [5211+] */ +#define AR5K_TXCFG_ADHOC_BCN_ATIM 0x00000800 /* Adhoc Beacon ATIM Policy */ +#define AR5K_TXCFG_ATIM_WINDOW_DEF_DIS 0x00001000 /* Disable ATIM window defer [5211+] */ +#define AR5K_TXCFG_RTSRND 0x00001000 /* [5211+] */ +#define AR5K_TXCFG_FRMPAD_DIS 0x00002000 /* [5211+] */ +#define AR5K_TXCFG_RDY_CBR_DIS 0x00004000 /* Ready time CBR disable [5211+] */ +#define AR5K_TXCFG_JUMBO_FRM_MODE 0x00008000 /* Jumbo frame mode [5211+] */ +#define AR5K_TXCFG_DCU_DBL_BUF_DIS 0x00008000 /* Disable double buffering on DCU */ +#define AR5K_TXCFG_DCU_CACHING_DIS 0x00010000 /* Disable DCU caching */ + +/* + * Receive configuration register + */ +#define AR5K_RXCFG 0x0034 /* Register Address */ +#define AR5K_RXCFG_SDMAMW 0x00000007 /* DMA size (write) */ +#define AR5K_RXCFG_SDMAMW_S 0 +#define AR5K_RXCFG_ZLFDMA 0x00000008 /* Enable Zero-length frame DMA */ +#define AR5K_RXCFG_DEF_ANTENNA 0x00000010 /* Default antenna (?) */ +#define AR5K_RXCFG_JUMBO_RXE 0x00000020 /* Enable jumbo rx descriptors [5211+] */ +#define AR5K_RXCFG_JUMBO_WRAP 0x00000040 /* Wrap jumbo frames [5211+] */ +#define AR5K_RXCFG_SLE_ENTRY 0x00000080 /* Sleep entry policy */ + +/* + * Receive jumbo descriptor last address register + * Only found in 5211 (?) + */ +#define AR5K_RXJLA 0x0038 + +/* + * MIB control register + */ +#define AR5K_MIBC 0x0040 /* Register Address */ +#define AR5K_MIBC_COW 0x00000001 /* Counter Overflow Warning */ +#define AR5K_MIBC_FMC 0x00000002 /* Freeze MIB Counters */ +#define AR5K_MIBC_CMC 0x00000004 /* Clear MIB Counters */ +#define AR5K_MIBC_MCS 0x00000008 /* MIB counter strobe, increment all */ + +/* + * Timeout prescale register + */ +#define AR5K_TOPS 0x0044 +#define AR5K_TOPS_M 0x0000ffff + +/* + * Receive timeout register (no frame received) + */ +#define AR5K_RXNOFRM 0x0048 +#define AR5K_RXNOFRM_M 0x000003ff + +/* + * Transmit timeout register (no frame sent) + */ +#define AR5K_TXNOFRM 0x004c +#define AR5K_TXNOFRM_M 0x000003ff +#define AR5K_TXNOFRM_QCU 0x000ffc00 +#define AR5K_TXNOFRM_QCU_S 10 + +/* + * Receive frame gap timeout register + */ +#define AR5K_RPGTO 0x0050 +#define AR5K_RPGTO_M 0x000003ff + +/* + * Receive frame count limit register + */ +#define AR5K_RFCNT 0x0054 +#define AR5K_RFCNT_M 0x0000001f /* [5211+] (?) */ +#define AR5K_RFCNT_RFCL 0x0000000f /* [5210] */ + +/* + * Misc settings register + * (reserved0-3) + */ +#define AR5K_MISC 0x0058 /* Register Address */ +#define AR5K_MISC_DMA_OBS_M 0x000001e0 +#define AR5K_MISC_DMA_OBS_S 5 +#define AR5K_MISC_MISC_OBS_M 0x00000e00 +#define AR5K_MISC_MISC_OBS_S 9 +#define AR5K_MISC_MAC_OBS_LSB_M 0x00007000 +#define AR5K_MISC_MAC_OBS_LSB_S 12 +#define AR5K_MISC_MAC_OBS_MSB_M 0x00038000 +#define AR5K_MISC_MAC_OBS_MSB_S 15 +#define AR5K_MISC_LED_DECAY 0x001c0000 /* [5210] */ +#define AR5K_MISC_LED_BLINK 0x00e00000 /* [5210] */ + +/* + * QCU/DCU clock gating register (5311) + * (reserved4-5) + */ +#define AR5K_QCUDCU_CLKGT 0x005c /* Register Address (?) */ +#define AR5K_QCUDCU_CLKGT_QCU 0x0000ffff /* Mask for QCU clock */ +#define AR5K_QCUDCU_CLKGT_DCU 0x07ff0000 /* Mask for DCU clock */ + +/* + * Interrupt Status Registers + * + * For 5210 there is only one status register but for + * 5211/5212 we have one primary and 4 secondary registers. + * So we have AR5K_ISR for 5210 and AR5K_PISR /SISRx for 5211/5212. + * Most of these bits are common for all chipsets. + * + * NOTE: On 5211+ TXOK, TXDESC, TXERR, TXEOL and TXURN contain + * the logical OR from per-queue interrupt bits found on SISR registers + * (see below). + */ +#define AR5K_ISR 0x001c /* Register Address [5210] */ +#define AR5K_PISR 0x0080 /* Register Address [5211+] */ +#define AR5K_ISR_RXOK 0x00000001 /* Frame successfully received */ +#define AR5K_ISR_RXDESC 0x00000002 /* RX descriptor request */ +#define AR5K_ISR_RXERR 0x00000004 /* Receive error */ +#define AR5K_ISR_RXNOFRM 0x00000008 /* No frame received (receive timeout) */ +#define AR5K_ISR_RXEOL 0x00000010 /* Empty RX descriptor */ +#define AR5K_ISR_RXORN 0x00000020 /* Receive FIFO overrun */ +#define AR5K_ISR_TXOK 0x00000040 /* Frame successfully transmitted */ +#define AR5K_ISR_TXDESC 0x00000080 /* TX descriptor request */ +#define AR5K_ISR_TXERR 0x00000100 /* Transmit error */ +#define AR5K_ISR_TXNOFRM 0x00000200 /* No frame transmitted (transmit timeout) + * NOTE: We don't have per-queue info for this + * one, but we can enable it per-queue through + * TXNOFRM_QCU field on TXNOFRM register */ +#define AR5K_ISR_TXEOL 0x00000400 /* Empty TX descriptor */ +#define AR5K_ISR_TXURN 0x00000800 /* Transmit FIFO underrun */ +#define AR5K_ISR_MIB 0x00001000 /* Update MIB counters */ +#define AR5K_ISR_SWI 0x00002000 /* Software interrupt */ +#define AR5K_ISR_RXPHY 0x00004000 /* PHY error */ +#define AR5K_ISR_RXKCM 0x00008000 /* RX Key cache miss */ +#define AR5K_ISR_SWBA 0x00010000 /* Software beacon alert */ +#define AR5K_ISR_BRSSI 0x00020000 /* Beacon rssi below threshold (?) */ +#define AR5K_ISR_BMISS 0x00040000 /* Beacon missed */ +#define AR5K_ISR_HIUERR 0x00080000 /* Host Interface Unit error [5211+] + * 'or' of MCABT, SSERR, DPERR from SISR2 */ +#define AR5K_ISR_BNR 0x00100000 /* Beacon not ready [5211+] */ +#define AR5K_ISR_MCABT 0x00100000 /* Master Cycle Abort [5210] */ +#define AR5K_ISR_RXCHIRP 0x00200000 /* CHIRP Received [5212+] */ +#define AR5K_ISR_SSERR 0x00200000 /* Signaled System Error [5210] */ +#define AR5K_ISR_DPERR 0x00400000 /* Bus parity error [5210] */ +#define AR5K_ISR_RXDOPPLER 0x00400000 /* Doppler chirp received [5212+] */ +#define AR5K_ISR_TIM 0x00800000 /* [5211+] */ +#define AR5K_ISR_BCNMISC 0x00800000 /* Misc beacon related interrupt + * 'or' of TIM, CAB_END, DTIM_SYNC, BCN_TIMEOUT, + * CAB_TIMEOUT and DTIM bits from SISR2 [5212+] */ +#define AR5K_ISR_GPIO 0x01000000 /* GPIO (rf kill) */ +#define AR5K_ISR_QCBRORN 0x02000000 /* QCU CBR overrun [5211+] */ +#define AR5K_ISR_QCBRURN 0x04000000 /* QCU CBR underrun [5211+] */ +#define AR5K_ISR_QTRIG 0x08000000 /* QCU scheduling trigger [5211+] */ + +#define AR5K_ISR_BITS_FROM_SISRS (AR5K_ISR_TXOK | AR5K_ISR_TXDESC |\ + AR5K_ISR_TXERR | AR5K_ISR_TXEOL |\ + AR5K_ISR_TXURN | AR5K_ISR_HIUERR |\ + AR5K_ISR_BCNMISC | AR5K_ISR_QCBRORN |\ + AR5K_ISR_QCBRURN | AR5K_ISR_QTRIG) + +/* + * Secondary status registers [5211+] (0 - 4) + * + * These give the status for each QCU, only QCUs 0-9 are + * represented. + */ +#define AR5K_SISR0 0x0084 /* Register Address [5211+] */ +#define AR5K_SISR0_QCU_TXOK 0x000003ff /* Mask for QCU_TXOK */ +#define AR5K_SISR0_QCU_TXOK_S 0 +#define AR5K_SISR0_QCU_TXDESC 0x03ff0000 /* Mask for QCU_TXDESC */ +#define AR5K_SISR0_QCU_TXDESC_S 16 + +#define AR5K_SISR1 0x0088 /* Register Address [5211+] */ +#define AR5K_SISR1_QCU_TXERR 0x000003ff /* Mask for QCU_TXERR */ +#define AR5K_SISR1_QCU_TXERR_S 0 +#define AR5K_SISR1_QCU_TXEOL 0x03ff0000 /* Mask for QCU_TXEOL */ +#define AR5K_SISR1_QCU_TXEOL_S 16 + +#define AR5K_SISR2 0x008c /* Register Address [5211+] */ +#define AR5K_SISR2_QCU_TXURN 0x000003ff /* Mask for QCU_TXURN */ +#define AR5K_SISR2_QCU_TXURN_S 0 +#define AR5K_SISR2_MCABT 0x00010000 /* Master Cycle Abort */ +#define AR5K_SISR2_SSERR 0x00020000 /* Signaled System Error */ +#define AR5K_SISR2_DPERR 0x00040000 /* Bus parity error */ +#define AR5K_SISR2_TIM 0x01000000 /* [5212+] */ +#define AR5K_SISR2_CAB_END 0x02000000 /* [5212+] */ +#define AR5K_SISR2_DTIM_SYNC 0x04000000 /* DTIM sync lost [5212+] */ +#define AR5K_SISR2_BCN_TIMEOUT 0x08000000 /* Beacon Timeout [5212+] */ +#define AR5K_SISR2_CAB_TIMEOUT 0x10000000 /* CAB Timeout [5212+] */ +#define AR5K_SISR2_DTIM 0x20000000 /* [5212+] */ +#define AR5K_SISR2_TSFOOR 0x80000000 /* TSF Out of range */ + +#define AR5K_SISR3 0x0090 /* Register Address [5211+] */ +#define AR5K_SISR3_QCBRORN 0x000003ff /* Mask for QCBRORN */ +#define AR5K_SISR3_QCBRORN_S 0 +#define AR5K_SISR3_QCBRURN 0x03ff0000 /* Mask for QCBRURN */ +#define AR5K_SISR3_QCBRURN_S 16 + +#define AR5K_SISR4 0x0094 /* Register Address [5211+] */ +#define AR5K_SISR4_QTRIG 0x000003ff /* Mask for QTRIG */ +#define AR5K_SISR4_QTRIG_S 0 + +/* + * Shadow read-and-clear interrupt status registers [5211+] + */ +#define AR5K_RAC_PISR 0x00c0 /* Read and clear PISR */ +#define AR5K_RAC_SISR0 0x00c4 /* Read and clear SISR0 */ +#define AR5K_RAC_SISR1 0x00c8 /* Read and clear SISR1 */ +#define AR5K_RAC_SISR2 0x00cc /* Read and clear SISR2 */ +#define AR5K_RAC_SISR3 0x00d0 /* Read and clear SISR3 */ +#define AR5K_RAC_SISR4 0x00d4 /* Read and clear SISR4 */ + +/* + * Interrupt Mask Registers + * + * As with ISRs 5210 has one IMR (AR5K_IMR) and 5211/5212 has one primary + * (AR5K_PIMR) and 4 secondary IMRs (AR5K_SIMRx). Note that ISR/IMR flags match. + */ +#define AR5K_IMR 0x0020 /* Register Address [5210] */ +#define AR5K_PIMR 0x00a0 /* Register Address [5211+] */ +#define AR5K_IMR_RXOK 0x00000001 /* Frame successfully received*/ +#define AR5K_IMR_RXDESC 0x00000002 /* RX descriptor request*/ +#define AR5K_IMR_RXERR 0x00000004 /* Receive error*/ +#define AR5K_IMR_RXNOFRM 0x00000008 /* No frame received (receive timeout)*/ +#define AR5K_IMR_RXEOL 0x00000010 /* Empty RX descriptor*/ +#define AR5K_IMR_RXORN 0x00000020 /* Receive FIFO overrun*/ +#define AR5K_IMR_TXOK 0x00000040 /* Frame successfully transmitted*/ +#define AR5K_IMR_TXDESC 0x00000080 /* TX descriptor request*/ +#define AR5K_IMR_TXERR 0x00000100 /* Transmit error*/ +#define AR5K_IMR_TXNOFRM 0x00000200 /* No frame transmitted (transmit timeout)*/ +#define AR5K_IMR_TXEOL 0x00000400 /* Empty TX descriptor*/ +#define AR5K_IMR_TXURN 0x00000800 /* Transmit FIFO underrun*/ +#define AR5K_IMR_MIB 0x00001000 /* Update MIB counters*/ +#define AR5K_IMR_SWI 0x00002000 /* Software interrupt */ +#define AR5K_IMR_RXPHY 0x00004000 /* PHY error*/ +#define AR5K_IMR_RXKCM 0x00008000 /* RX Key cache miss */ +#define AR5K_IMR_SWBA 0x00010000 /* Software beacon alert*/ +#define AR5K_IMR_BRSSI 0x00020000 /* Beacon rssi below threshold (?) */ +#define AR5K_IMR_BMISS 0x00040000 /* Beacon missed*/ +#define AR5K_IMR_HIUERR 0x00080000 /* Host Interface Unit error [5211+] */ +#define AR5K_IMR_BNR 0x00100000 /* Beacon not ready [5211+] */ +#define AR5K_IMR_MCABT 0x00100000 /* Master Cycle Abort [5210] */ +#define AR5K_IMR_RXCHIRP 0x00200000 /* CHIRP Received [5212+]*/ +#define AR5K_IMR_SSERR 0x00200000 /* Signaled System Error [5210] */ +#define AR5K_IMR_DPERR 0x00400000 /* Det par Error (?) [5210] */ +#define AR5K_IMR_RXDOPPLER 0x00400000 /* Doppler chirp received [5212+] */ +#define AR5K_IMR_TIM 0x00800000 /* [5211+] */ +#define AR5K_IMR_BCNMISC 0x00800000 /* 'or' of TIM, CAB_END, DTIM_SYNC, BCN_TIMEOUT, + CAB_TIMEOUT and DTIM bits from SISR2 [5212+] */ +#define AR5K_IMR_GPIO 0x01000000 /* GPIO (rf kill)*/ +#define AR5K_IMR_QCBRORN 0x02000000 /* QCU CBR overrun (?) [5211+] */ +#define AR5K_IMR_QCBRURN 0x04000000 /* QCU CBR underrun (?) [5211+] */ +#define AR5K_IMR_QTRIG 0x08000000 /* QCU scheduling trigger [5211+] */ + +/* + * Secondary interrupt mask registers [5211+] (0 - 4) + */ +#define AR5K_SIMR0 0x00a4 /* Register Address [5211+] */ +#define AR5K_SIMR0_QCU_TXOK 0x000003ff /* Mask for QCU_TXOK */ +#define AR5K_SIMR0_QCU_TXOK_S 0 +#define AR5K_SIMR0_QCU_TXDESC 0x03ff0000 /* Mask for QCU_TXDESC */ +#define AR5K_SIMR0_QCU_TXDESC_S 16 + +#define AR5K_SIMR1 0x00a8 /* Register Address [5211+] */ +#define AR5K_SIMR1_QCU_TXERR 0x000003ff /* Mask for QCU_TXERR */ +#define AR5K_SIMR1_QCU_TXERR_S 0 +#define AR5K_SIMR1_QCU_TXEOL 0x03ff0000 /* Mask for QCU_TXEOL */ +#define AR5K_SIMR1_QCU_TXEOL_S 16 + +#define AR5K_SIMR2 0x00ac /* Register Address [5211+] */ +#define AR5K_SIMR2_QCU_TXURN 0x000003ff /* Mask for QCU_TXURN */ +#define AR5K_SIMR2_QCU_TXURN_S 0 +#define AR5K_SIMR2_MCABT 0x00010000 /* Master Cycle Abort */ +#define AR5K_SIMR2_SSERR 0x00020000 /* Signaled System Error */ +#define AR5K_SIMR2_DPERR 0x00040000 /* Bus parity error */ +#define AR5K_SIMR2_TIM 0x01000000 /* [5212+] */ +#define AR5K_SIMR2_CAB_END 0x02000000 /* [5212+] */ +#define AR5K_SIMR2_DTIM_SYNC 0x04000000 /* DTIM Sync lost [5212+] */ +#define AR5K_SIMR2_BCN_TIMEOUT 0x08000000 /* Beacon Timeout [5212+] */ +#define AR5K_SIMR2_CAB_TIMEOUT 0x10000000 /* CAB Timeout [5212+] */ +#define AR5K_SIMR2_DTIM 0x20000000 /* [5212+] */ +#define AR5K_SIMR2_TSFOOR 0x80000000 /* TSF OOR (?) */ + +#define AR5K_SIMR3 0x00b0 /* Register Address [5211+] */ +#define AR5K_SIMR3_QCBRORN 0x000003ff /* Mask for QCBRORN */ +#define AR5K_SIMR3_QCBRORN_S 0 +#define AR5K_SIMR3_QCBRURN 0x03ff0000 /* Mask for QCBRURN */ +#define AR5K_SIMR3_QCBRURN_S 16 + +#define AR5K_SIMR4 0x00b4 /* Register Address [5211+] */ +#define AR5K_SIMR4_QTRIG 0x000003ff /* Mask for QTRIG */ +#define AR5K_SIMR4_QTRIG_S 0 + +/* + * DMA Debug registers 0-7 + * 0xe0 - 0xfc + */ + +/* + * Decompression mask registers [5212+] + */ +#define AR5K_DCM_ADDR 0x0400 /*Decompression mask address (index) */ +#define AR5K_DCM_DATA 0x0404 /*Decompression mask data */ + +/* + * Wake On Wireless pattern control register [5212+] + */ +#define AR5K_WOW_PCFG 0x0410 /* Register Address */ +#define AR5K_WOW_PCFG_PAT_MATCH_EN 0x00000001 /* Pattern match enable */ +#define AR5K_WOW_PCFG_LONG_FRAME_POL 0x00000002 /* Long frame policy */ +#define AR5K_WOW_PCFG_WOBMISS 0x00000004 /* Wake on bea(con) miss (?) */ +#define AR5K_WOW_PCFG_PAT_0_EN 0x00000100 /* Enable pattern 0 */ +#define AR5K_WOW_PCFG_PAT_1_EN 0x00000200 /* Enable pattern 1 */ +#define AR5K_WOW_PCFG_PAT_2_EN 0x00000400 /* Enable pattern 2 */ +#define AR5K_WOW_PCFG_PAT_3_EN 0x00000800 /* Enable pattern 3 */ +#define AR5K_WOW_PCFG_PAT_4_EN 0x00001000 /* Enable pattern 4 */ +#define AR5K_WOW_PCFG_PAT_5_EN 0x00002000 /* Enable pattern 5 */ + +/* + * Wake On Wireless pattern index register (?) [5212+] + */ +#define AR5K_WOW_PAT_IDX 0x0414 + +/* + * Wake On Wireless pattern data register [5212+] + */ +#define AR5K_WOW_PAT_DATA 0x0418 /* Register Address */ +#define AR5K_WOW_PAT_DATA_0_3_V 0x00000001 /* Pattern 0, 3 value */ +#define AR5K_WOW_PAT_DATA_1_4_V 0x00000100 /* Pattern 1, 4 value */ +#define AR5K_WOW_PAT_DATA_2_5_V 0x00010000 /* Pattern 2, 5 value */ +#define AR5K_WOW_PAT_DATA_0_3_M 0x01000000 /* Pattern 0, 3 mask */ +#define AR5K_WOW_PAT_DATA_1_4_M 0x04000000 /* Pattern 1, 4 mask */ +#define AR5K_WOW_PAT_DATA_2_5_M 0x10000000 /* Pattern 2, 5 mask */ + +/* + * Decompression configuration registers [5212+] + */ +#define AR5K_DCCFG 0x0420 /* Register Address */ +#define AR5K_DCCFG_GLOBAL_EN 0x00000001 /* Enable decompression on all queues */ +#define AR5K_DCCFG_BYPASS_EN 0x00000002 /* Bypass decompression */ +#define AR5K_DCCFG_BCAST_EN 0x00000004 /* Enable decompression for bcast frames */ +#define AR5K_DCCFG_MCAST_EN 0x00000008 /* Enable decompression for mcast frames */ + +/* + * Compression configuration registers [5212+] + */ +#define AR5K_CCFG 0x0600 /* Register Address */ +#define AR5K_CCFG_WINDOW_SIZE 0x00000007 /* Compression window size */ +#define AR5K_CCFG_CPC_EN 0x00000008 /* Enable performance counters */ + +#define AR5K_CCFG_CCU 0x0604 /* Register Address */ +#define AR5K_CCFG_CCU_CUP_EN 0x00000001 /* CCU Catchup enable */ +#define AR5K_CCFG_CCU_CREDIT 0x00000002 /* CCU Credit (field) */ +#define AR5K_CCFG_CCU_CD_THRES 0x00000080 /* CCU Cyc(lic?) debt threshold (field) */ +#define AR5K_CCFG_CCU_CUP_LCNT 0x00010000 /* CCU Catchup lit(?) count */ +#define AR5K_CCFG_CCU_INIT 0x00100200 /* Initial value during reset */ + +/* + * Compression performance counter registers [5212+] + */ +#define AR5K_CPC0 0x0610 /* Compression performance counter 0 */ +#define AR5K_CPC1 0x0614 /* Compression performance counter 1*/ +#define AR5K_CPC2 0x0618 /* Compression performance counter 2 */ +#define AR5K_CPC3 0x061c /* Compression performance counter 3 */ +#define AR5K_CPCOVF 0x0620 /* Compression performance overflow */ + + +/* + * Queue control unit (QCU) registers [5211+] + * + * Card has 12 TX Queues but i see that only 0-9 are used (?) + * both in binary HAL (see ah.h) and ar5k. Each queue has it's own + * TXDP at addresses 0x0800 - 0x082c, a CBR (Constant Bit Rate) + * configuration register (0x08c0 - 0x08ec), a ready time configuration + * register (0x0900 - 0x092c), a misc configuration register (0x09c0 - + * 0x09ec) and a status register (0x0a00 - 0x0a2c). We also have some + * global registers, QCU transmit enable/disable and "one shot arm (?)" + * set/clear, which contain status for all queues (we shift by 1 for each + * queue). To access these registers easily we define some macros here + * that are used inside HAL. For more infos check out *_tx_queue functs. + */ + +/* + * Generic QCU Register access macros + */ +#define AR5K_QUEUE_REG(_r, _q) (((_q) << 2) + _r) +#define AR5K_QCU_GLOBAL_READ(_r, _q) (AR5K_REG_READ(_r) & (1 << _q)) +#define AR5K_QCU_GLOBAL_WRITE(_r, _q) AR5K_REG_WRITE(_r, (1 << _q)) + +/* + * QCU Transmit descriptor pointer registers + */ +#define AR5K_QCU_TXDP_BASE 0x0800 /* Register Address - Queue0 TXDP */ +#define AR5K_QUEUE_TXDP(_q) AR5K_QUEUE_REG(AR5K_QCU_TXDP_BASE, _q) + +/* + * QCU Transmit enable register + */ +#define AR5K_QCU_TXE 0x0840 +#define AR5K_ENABLE_QUEUE(_q) AR5K_QCU_GLOBAL_WRITE(AR5K_QCU_TXE, _q) +#define AR5K_QUEUE_ENABLED(_q) AR5K_QCU_GLOBAL_READ(AR5K_QCU_TXE, _q) + +/* + * QCU Transmit disable register + */ +#define AR5K_QCU_TXD 0x0880 +#define AR5K_DISABLE_QUEUE(_q) AR5K_QCU_GLOBAL_WRITE(AR5K_QCU_TXD, _q) +#define AR5K_QUEUE_DISABLED(_q) AR5K_QCU_GLOBAL_READ(AR5K_QCU_TXD, _q) + +/* + * QCU Constant Bit Rate configuration registers + */ +#define AR5K_QCU_CBRCFG_BASE 0x08c0 /* Register Address - Queue0 CBRCFG */ +#define AR5K_QCU_CBRCFG_INTVAL 0x00ffffff /* CBR Interval mask */ +#define AR5K_QCU_CBRCFG_INTVAL_S 0 +#define AR5K_QCU_CBRCFG_ORN_THRES 0xff000000 /* CBR overrun threshold mask */ +#define AR5K_QCU_CBRCFG_ORN_THRES_S 24 +#define AR5K_QUEUE_CBRCFG(_q) AR5K_QUEUE_REG(AR5K_QCU_CBRCFG_BASE, _q) + +/* + * QCU Ready time configuration registers + */ +#define AR5K_QCU_RDYTIMECFG_BASE 0x0900 /* Register Address - Queue0 RDYTIMECFG */ +#define AR5K_QCU_RDYTIMECFG_INTVAL 0x00ffffff /* Ready time interval mask */ +#define AR5K_QCU_RDYTIMECFG_INTVAL_S 0 +#define AR5K_QCU_RDYTIMECFG_ENABLE 0x01000000 /* Ready time enable mask */ +#define AR5K_QUEUE_RDYTIMECFG(_q) AR5K_QUEUE_REG(AR5K_QCU_RDYTIMECFG_BASE, _q) + +/* + * QCU one shot arm set registers + */ +#define AR5K_QCU_ONESHOTARM_SET 0x0940 /* Register Address -QCU "one shot arm set (?)" */ +#define AR5K_QCU_ONESHOTARM_SET_M 0x0000ffff + +/* + * QCU one shot arm clear registers + */ +#define AR5K_QCU_ONESHOTARM_CLEAR 0x0980 /* Register Address -QCU "one shot arm clear (?)" */ +#define AR5K_QCU_ONESHOTARM_CLEAR_M 0x0000ffff + +/* + * QCU misc registers + */ +#define AR5K_QCU_MISC_BASE 0x09c0 /* Register Address -Queue0 MISC */ +#define AR5K_QCU_MISC_FRSHED_M 0x0000000f /* Frame scheduling mask */ +#define AR5K_QCU_MISC_FRSHED_ASAP 0 /* ASAP */ +#define AR5K_QCU_MISC_FRSHED_CBR 1 /* Constant Bit Rate */ +#define AR5K_QCU_MISC_FRSHED_DBA_GT 2 /* DMA Beacon alert gated */ +#define AR5K_QCU_MISC_FRSHED_TIM_GT 3 /* TIMT gated */ +#define AR5K_QCU_MISC_FRSHED_BCN_SENT_GT 4 /* Beacon sent gated */ +#define AR5K_QCU_MISC_ONESHOT_ENABLE 0x00000010 /* Oneshot enable */ +#define AR5K_QCU_MISC_CBREXP_DIS 0x00000020 /* Disable CBR expired counter (normal queue) */ +#define AR5K_QCU_MISC_CBREXP_BCN_DIS 0x00000040 /* Disable CBR expired counter (beacon queue) */ +#define AR5K_QCU_MISC_BCN_ENABLE 0x00000080 /* Enable Beacon use */ +#define AR5K_QCU_MISC_CBR_THRES_ENABLE 0x00000100 /* CBR expired threshold enabled */ +#define AR5K_QCU_MISC_RDY_VEOL_POLICY 0x00000200 /* TXE reset when RDYTIME expired or VEOL */ +#define AR5K_QCU_MISC_CBR_RESET_CNT 0x00000400 /* CBR threshold (counter) reset */ +#define AR5K_QCU_MISC_DCU_EARLY 0x00000800 /* DCU early termination */ +#define AR5K_QCU_MISC_DCU_CMP_EN 0x00001000 /* Enable frame compression */ +#define AR5K_QUEUE_MISC(_q) AR5K_QUEUE_REG(AR5K_QCU_MISC_BASE, _q) + + +/* + * QCU status registers + */ +#define AR5K_QCU_STS_BASE 0x0a00 /* Register Address - Queue0 STS */ +#define AR5K_QCU_STS_FRMPENDCNT 0x00000003 /* Frames pending counter */ +#define AR5K_QCU_STS_CBREXPCNT 0x0000ff00 /* CBR expired counter */ +#define AR5K_QUEUE_STATUS(_q) AR5K_QUEUE_REG(AR5K_QCU_STS_BASE, _q) + +/* + * QCU ready time shutdown register + */ +#define AR5K_QCU_RDYTIMESHDN 0x0a40 +#define AR5K_QCU_RDYTIMESHDN_M 0x000003ff + +/* + * QCU compression buffer base registers [5212+] + */ +#define AR5K_QCU_CBB_SELECT 0x0b00 +#define AR5K_QCU_CBB_ADDR 0x0b04 +#define AR5K_QCU_CBB_ADDR_S 9 + +/* + * QCU compression buffer configuration register [5212+] + * (buffer size) + */ +#define AR5K_QCU_CBCFG 0x0b08 + + + +/* + * Distributed Coordination Function (DCF) control unit (DCU) + * registers [5211+] + * + * These registers control the various characteristics of each queue + * for 802.11e (WME) compatibility so they go together with + * QCU registers in pairs. For each queue we have a QCU mask register, + * (0x1000 - 0x102c), a local-IFS settings register (0x1040 - 0x106c), + * a retry limit register (0x1080 - 0x10ac), a channel time register + * (0x10c0 - 0x10ec), a misc-settings register (0x1100 - 0x112c) and + * a sequence number register (0x1140 - 0x116c). It seems that "global" + * registers here affect all queues (see use of DCU_GBL_IFS_SLOT in ar5k). + * We use the same macros here for easier register access. + * + */ + +/* + * DCU QCU mask registers + */ +#define AR5K_DCU_QCUMASK_BASE 0x1000 /* Register Address -Queue0 DCU_QCUMASK */ +#define AR5K_DCU_QCUMASK_M 0x000003ff +#define AR5K_QUEUE_QCUMASK(_q) AR5K_QUEUE_REG(AR5K_DCU_QCUMASK_BASE, _q) + +/* + * DCU local Inter Frame Space settings register + */ +#define AR5K_DCU_LCL_IFS_BASE 0x1040 /* Register Address -Queue0 DCU_LCL_IFS */ +#define AR5K_DCU_LCL_IFS_CW_MIN 0x000003ff /* Minimum Contention Window */ +#define AR5K_DCU_LCL_IFS_CW_MIN_S 0 +#define AR5K_DCU_LCL_IFS_CW_MAX 0x000ffc00 /* Maximum Contention Window */ +#define AR5K_DCU_LCL_IFS_CW_MAX_S 10 +#define AR5K_DCU_LCL_IFS_AIFS 0x0ff00000 /* Arbitrated Interframe Space */ +#define AR5K_DCU_LCL_IFS_AIFS_S 20 +#define AR5K_DCU_LCL_IFS_AIFS_MAX 0xfc /* Anything above that can cause DCU to hang */ +#define AR5K_QUEUE_DFS_LOCAL_IFS(_q) AR5K_QUEUE_REG(AR5K_DCU_LCL_IFS_BASE, _q) + +/* + * DCU retry limit registers + * all these fields don't allow zero values + */ +#define AR5K_DCU_RETRY_LMT_BASE 0x1080 /* Register Address -Queue0 DCU_RETRY_LMT */ +#define AR5K_DCU_RETRY_LMT_RTS 0x0000000f /* RTS failure limit. Transmission fails if no CTS is received for this number of times */ +#define AR5K_DCU_RETRY_LMT_RTS_S 0 +#define AR5K_DCU_RETRY_LMT_STA_RTS 0x00003f00 /* STA RTS failure limit. If exceeded CW reset */ +#define AR5K_DCU_RETRY_LMT_STA_RTS_S 8 +#define AR5K_DCU_RETRY_LMT_STA_DATA 0x000fc000 /* STA data failure limit. If exceeded CW reset. */ +#define AR5K_DCU_RETRY_LMT_STA_DATA_S 14 +#define AR5K_QUEUE_DFS_RETRY_LIMIT(_q) AR5K_QUEUE_REG(AR5K_DCU_RETRY_LMT_BASE, _q) + +/* + * DCU channel time registers + */ +#define AR5K_DCU_CHAN_TIME_BASE 0x10c0 /* Register Address -Queue0 DCU_CHAN_TIME */ +#define AR5K_DCU_CHAN_TIME_DUR 0x000fffff /* Channel time duration */ +#define AR5K_DCU_CHAN_TIME_DUR_S 0 +#define AR5K_DCU_CHAN_TIME_ENABLE 0x00100000 /* Enable channel time */ +#define AR5K_QUEUE_DFS_CHANNEL_TIME(_q) AR5K_QUEUE_REG(AR5K_DCU_CHAN_TIME_BASE, _q) + +/* + * DCU misc registers [5211+] + * + * Note: Arbiter lockout control controls the + * behaviour on low priority queues when we have multiple queues + * with pending frames. Intra-frame lockout means we wait until + * the queue's current frame transmits (with post frame backoff and bursting) + * before we transmit anything else and global lockout means we + * wait for the whole queue to finish before higher priority queues + * can transmit (this is used on beacon and CAB queues). + * No lockout means there is no special handling. + */ +#define AR5K_DCU_MISC_BASE 0x1100 /* Register Address -Queue0 DCU_MISC */ +#define AR5K_DCU_MISC_BACKOFF 0x0000003f /* Mask for backoff threshold */ +#define AR5K_DCU_MISC_ETS_RTS_POL 0x00000040 /* End of transmission series + station RTS/data failure count + reset policy (?) */ +#define AR5K_DCU_MISC_ETS_CW_POL 0x00000080 /* End of transmission series + CW reset policy */ +#define AR5K_DCU_MISC_FRAG_WAIT 0x00000100 /* Wait for next fragment */ +#define AR5K_DCU_MISC_BACKOFF_FRAG 0x00000200 /* Enable backoff while bursting */ +#define AR5K_DCU_MISC_HCFPOLL_ENABLE 0x00000800 /* CF - Poll enable */ +#define AR5K_DCU_MISC_BACKOFF_PERSIST 0x00001000 /* Persistent backoff */ +#define AR5K_DCU_MISC_FRMPRFTCH_ENABLE 0x00002000 /* Enable frame pre-fetch */ +#define AR5K_DCU_MISC_VIRTCOL 0x0000c000 /* Mask for Virtual Collision (?) */ +#define AR5K_DCU_MISC_VIRTCOL_NORMAL 0 +#define AR5K_DCU_MISC_VIRTCOL_IGNORE 1 +#define AR5K_DCU_MISC_BCN_ENABLE 0x00010000 /* Enable Beacon use */ +#define AR5K_DCU_MISC_ARBLOCK_CTL 0x00060000 /* Arbiter lockout control mask */ +#define AR5K_DCU_MISC_ARBLOCK_CTL_S 17 +#define AR5K_DCU_MISC_ARBLOCK_CTL_NONE 0 /* No arbiter lockout */ +#define AR5K_DCU_MISC_ARBLOCK_CTL_INTFRM 1 /* Intra-frame lockout */ +#define AR5K_DCU_MISC_ARBLOCK_CTL_GLOBAL 2 /* Global lockout */ +#define AR5K_DCU_MISC_ARBLOCK_IGNORE 0x00080000 /* Ignore Arbiter lockout */ +#define AR5K_DCU_MISC_SEQ_NUM_INCR_DIS 0x00100000 /* Disable sequence number increment */ +#define AR5K_DCU_MISC_POST_FR_BKOFF_DIS 0x00200000 /* Disable post-frame backoff */ +#define AR5K_DCU_MISC_VIRT_COLL_POLICY 0x00400000 /* Virtual Collision cw policy */ +#define AR5K_DCU_MISC_BLOWN_IFS_POLICY 0x00800000 /* Blown IFS policy (?) */ +#define AR5K_DCU_MISC_SEQNUM_CTL 0x01000000 /* Sequence number control (?) */ +#define AR5K_QUEUE_DFS_MISC(_q) AR5K_QUEUE_REG(AR5K_DCU_MISC_BASE, _q) + +/* + * DCU frame sequence number registers + */ +#define AR5K_DCU_SEQNUM_BASE 0x1140 +#define AR5K_DCU_SEQNUM_M 0x00000fff +#define AR5K_QUEUE_DCU_SEQNUM(_q) AR5K_QUEUE_REG(AR5K_DCU_SEQNUM_BASE, _q) + +/* + * DCU global IFS SIFS register + */ +#define AR5K_DCU_GBL_IFS_SIFS 0x1030 +#define AR5K_DCU_GBL_IFS_SIFS_M 0x0000ffff + +/* + * DCU global IFS slot interval register + */ +#define AR5K_DCU_GBL_IFS_SLOT 0x1070 +#define AR5K_DCU_GBL_IFS_SLOT_M 0x0000ffff + +/* + * DCU global IFS EIFS register + */ +#define AR5K_DCU_GBL_IFS_EIFS 0x10b0 +#define AR5K_DCU_GBL_IFS_EIFS_M 0x0000ffff + +/* + * DCU global IFS misc register + * + * LFSR stands for Linear Feedback Shift Register + * and it's used for generating pseudo-random + * number sequences. + * + * (If i understand correctly, random numbers are + * used for idle sensing -multiplied with cwmin/max etc-) + */ +#define AR5K_DCU_GBL_IFS_MISC 0x10f0 /* Register Address */ +#define AR5K_DCU_GBL_IFS_MISC_LFSR_SLICE 0x00000007 /* LFSR Slice Select */ +#define AR5K_DCU_GBL_IFS_MISC_TURBO_MODE 0x00000008 /* Turbo mode */ +#define AR5K_DCU_GBL_IFS_MISC_SIFS_DUR_USEC 0x000003f0 /* SIFS Duration mask */ +#define AR5K_DCU_GBL_IFS_MISC_SIFS_DUR_USEC_S 4 +#define AR5K_DCU_GBL_IFS_MISC_USEC_DUR 0x000ffc00 /* USEC Duration mask */ +#define AR5K_DCU_GBL_IFS_MISC_USEC_DUR_S 10 +#define AR5K_DCU_GBL_IFS_MISC_DCU_ARB_DELAY 0x00300000 /* DCU Arbiter delay mask */ +#define AR5K_DCU_GBL_IFS_MISC_SIFS_CNT_RST 0x00400000 /* SIFS cnt reset policy (?) */ +#define AR5K_DCU_GBL_IFS_MISC_AIFS_CNT_RST 0x00800000 /* AIFS cnt reset policy (?) */ +#define AR5K_DCU_GBL_IFS_MISC_RND_LFSR_SL_DIS 0x01000000 /* Disable random LFSR slice */ + +/* + * DCU frame prefetch control register + */ +#define AR5K_DCU_FP 0x1230 /* Register Address */ +#define AR5K_DCU_FP_NOBURST_DCU_EN 0x00000001 /* Enable non-burst prefetch on DCU (?) */ +#define AR5K_DCU_FP_NOBURST_EN 0x00000010 /* Enable non-burst prefetch (?) */ +#define AR5K_DCU_FP_BURST_DCU_EN 0x00000020 /* Enable burst prefetch on DCU (?) */ + +/* + * DCU transmit pause control/status register + */ +#define AR5K_DCU_TXP 0x1270 /* Register Address */ +#define AR5K_DCU_TXP_M 0x000003ff /* Tx pause mask */ +#define AR5K_DCU_TXP_STATUS 0x00010000 /* Tx pause status */ + +/* + * DCU transmit filter table 0 (32 entries) + * each entry contains a 32bit slice of the + * 128bit tx filter for each DCU (4 slices per DCU) + */ +#define AR5K_DCU_TX_FILTER_0_BASE 0x1038 +#define AR5K_DCU_TX_FILTER_0(_n) (AR5K_DCU_TX_FILTER_0_BASE + (_n * 64)) + +/* + * DCU transmit filter table 1 (16 entries) + */ +#define AR5K_DCU_TX_FILTER_1_BASE 0x103c +#define AR5K_DCU_TX_FILTER_1(_n) (AR5K_DCU_TX_FILTER_1_BASE + (_n * 64)) + +/* + * DCU clear transmit filter register + */ +#define AR5K_DCU_TX_FILTER_CLR 0x143c + +/* + * DCU set transmit filter register + */ +#define AR5K_DCU_TX_FILTER_SET 0x147c + +/* + * Reset control register + */ +#define AR5K_RESET_CTL 0x4000 /* Register Address */ +#define AR5K_RESET_CTL_PCU 0x00000001 /* Protocol Control Unit reset */ +#define AR5K_RESET_CTL_DMA 0x00000002 /* DMA (Rx/Tx) reset [5210] */ +#define AR5K_RESET_CTL_BASEBAND 0x00000002 /* Baseband reset [5211+] */ +#define AR5K_RESET_CTL_MAC 0x00000004 /* MAC reset (PCU+Baseband ?) [5210] */ +#define AR5K_RESET_CTL_PHY 0x00000008 /* PHY reset [5210] */ +#define AR5K_RESET_CTL_PCI 0x00000010 /* PCI Core reset (interrupts etc) */ + +/* + * Sleep control register + */ +#define AR5K_SLEEP_CTL 0x4004 /* Register Address */ +#define AR5K_SLEEP_CTL_SLDUR 0x0000ffff /* Sleep duration mask */ +#define AR5K_SLEEP_CTL_SLDUR_S 0 +#define AR5K_SLEEP_CTL_SLE 0x00030000 /* Sleep enable mask */ +#define AR5K_SLEEP_CTL_SLE_S 16 +#define AR5K_SLEEP_CTL_SLE_WAKE 0x00000000 /* Force chip awake */ +#define AR5K_SLEEP_CTL_SLE_SLP 0x00010000 /* Force chip sleep */ +#define AR5K_SLEEP_CTL_SLE_ALLOW 0x00020000 /* Normal sleep policy */ +#define AR5K_SLEEP_CTL_SLE_UNITS 0x00000008 /* [5211+] */ +#define AR5K_SLEEP_CTL_DUR_TIM_POL 0x00040000 /* Sleep duration timing policy */ +#define AR5K_SLEEP_CTL_DUR_WRITE_POL 0x00080000 /* Sleep duration write policy */ +#define AR5K_SLEEP_CTL_SLE_POL 0x00100000 /* Sleep policy mode */ + +/* + * Interrupt pending register + */ +#define AR5K_INTPEND 0x4008 +#define AR5K_INTPEND_M 0x00000001 + +/* + * Sleep force register + */ +#define AR5K_SFR 0x400c +#define AR5K_SFR_EN 0x00000001 + +/* + * PCI configuration register + * TODO: Fix LED stuff + */ +#define AR5K_PCICFG 0x4010 /* Register Address */ +#define AR5K_PCICFG_EEAE 0x00000001 /* Eeprom access enable [5210] */ +#define AR5K_PCICFG_SLEEP_CLOCK_EN 0x00000002 /* Enable sleep clock */ +#define AR5K_PCICFG_CLKRUNEN 0x00000004 /* CLKRUN enable [5211+] */ +#define AR5K_PCICFG_EESIZE 0x00000018 /* Mask for EEPROM size [5211+] */ +#define AR5K_PCICFG_EESIZE_S 3 +#define AR5K_PCICFG_EESIZE_4K 0 /* 4K */ +#define AR5K_PCICFG_EESIZE_8K 1 /* 8K */ +#define AR5K_PCICFG_EESIZE_16K 2 /* 16K */ +#define AR5K_PCICFG_EESIZE_FAIL 3 /* Failed to get size [5211+] */ +#define AR5K_PCICFG_LED 0x00000060 /* Led status [5211+] */ +#define AR5K_PCICFG_LED_NONE 0x00000000 /* Default [5211+] */ +#define AR5K_PCICFG_LED_PEND 0x00000020 /* Scan / Auth pending */ +#define AR5K_PCICFG_LED_ASSOC 0x00000040 /* Associated */ +#define AR5K_PCICFG_BUS_SEL 0x00000380 /* Mask for "bus select" [5211+] (?) */ +#define AR5K_PCICFG_CBEFIX_DIS 0x00000400 /* Disable CBE fix */ +#define AR5K_PCICFG_SL_INTEN 0x00000800 /* Enable interrupts when asleep */ +#define AR5K_PCICFG_LED_BCTL 0x00001000 /* Led blink (?) [5210] */ +#define AR5K_PCICFG_RETRY_FIX 0x00001000 /* Enable pci core retry fix */ +#define AR5K_PCICFG_SL_INPEN 0x00002000 /* Sleep even with pending interrupts*/ +#define AR5K_PCICFG_SPWR_DN 0x00010000 /* Mask for power status */ +#define AR5K_PCICFG_LEDMODE 0x000e0000 /* Ledmode [5211+] */ +#define AR5K_PCICFG_LEDMODE_PROP 0x00000000 /* Blink on standard traffic [5211+] */ +#define AR5K_PCICFG_LEDMODE_PROM 0x00020000 /* Default mode (blink on any traffic) [5211+] */ +#define AR5K_PCICFG_LEDMODE_PWR 0x00040000 /* Some other blinking mode (?) [5211+] */ +#define AR5K_PCICFG_LEDMODE_RAND 0x00060000 /* Random blinking (?) [5211+] */ +#define AR5K_PCICFG_LEDBLINK 0x00700000 /* Led blink rate */ +#define AR5K_PCICFG_LEDBLINK_S 20 +#define AR5K_PCICFG_LEDSLOW 0x00800000 /* Slowest led blink rate [5211+] */ +#define AR5K_PCICFG_LEDSTATE \ + (AR5K_PCICFG_LED | AR5K_PCICFG_LEDMODE | \ + AR5K_PCICFG_LEDBLINK | AR5K_PCICFG_LEDSLOW) +#define AR5K_PCICFG_SLEEP_CLOCK_RATE 0x03000000 /* Sleep clock rate */ +#define AR5K_PCICFG_SLEEP_CLOCK_RATE_S 24 + +/* + * "General Purpose Input/Output" (GPIO) control register + * + * I'm not sure about this but after looking at the code + * for all chipsets here is what i got. + * + * We have 6 GPIOs (pins), each GPIO has 4 modes (2 bits) + * Mode 0 -> always input + * Mode 1 -> output when GPIODO for this GPIO is set to 0 + * Mode 2 -> output when GPIODO for this GPIO is set to 1 + * Mode 3 -> always output + * + * For more infos check out get_gpio/set_gpio and + * set_gpio_input/set_gpio_output functs. + * For more infos on gpio interrupt check out set_gpio_intr. + */ +#define AR5K_NUM_GPIO 6 + +#define AR5K_GPIOCR 0x4014 /* Register Address */ +#define AR5K_GPIOCR_INT_ENA 0x00008000 /* Enable GPIO interrupt */ +#define AR5K_GPIOCR_INT_SELL 0x00000000 /* Generate interrupt when pin is low */ +#define AR5K_GPIOCR_INT_SELH 0x00010000 /* Generate interrupt when pin is high */ +#define AR5K_GPIOCR_IN(n) (0 << ((n) * 2)) /* Mode 0 for pin n */ +#define AR5K_GPIOCR_OUT0(n) (1 << ((n) * 2)) /* Mode 1 for pin n */ +#define AR5K_GPIOCR_OUT1(n) (2 << ((n) * 2)) /* Mode 2 for pin n */ +#define AR5K_GPIOCR_OUT(n) (3 << ((n) * 2)) /* Mode 3 for pin n */ +#define AR5K_GPIOCR_INT_SEL(n) ((n) << 12) /* Interrupt for GPIO pin n */ + +/* + * "General Purpose Input/Output" (GPIO) data output register + */ +#define AR5K_GPIODO 0x4018 + +/* + * "General Purpose Input/Output" (GPIO) data input register + */ +#define AR5K_GPIODI 0x401c +#define AR5K_GPIODI_M 0x0000002f + +/* + * Silicon revision register + */ +#define AR5K_SREV 0x4020 /* Register Address */ +#define AR5K_SREV_REV 0x0000000f /* Mask for revision */ +#define AR5K_SREV_REV_S 0 +#define AR5K_SREV_VER 0x000000ff /* Mask for version */ +#define AR5K_SREV_VER_S 4 + +/* + * TXE write posting register + */ +#define AR5K_TXEPOST 0x4028 + +/* + * QCU sleep mask + */ +#define AR5K_QCU_SLEEP_MASK 0x402c + +/* 0x4068 is compression buffer configuration + * register on 5414 and pm configuration register + * on 5424 and newer pci-e chips. */ + +/* + * Compression buffer configuration + * register (enable/disable) [5414] + */ +#define AR5K_5414_CBCFG 0x4068 +#define AR5K_5414_CBCFG_BUF_DIS 0x10 /* Disable buffer */ + +/* + * PCI-E Power management configuration + * and status register [5424+] + */ +#define AR5K_PCIE_PM_CTL 0x4068 /* Register address */ +/* Only 5424 */ +#define AR5K_PCIE_PM_CTL_L1_WHEN_D2 0x00000001 /* enable PCIe core enter L1 + when d2_sleep_en is asserted */ +#define AR5K_PCIE_PM_CTL_L0_L0S_CLEAR 0x00000002 /* Clear L0 and L0S counters */ +#define AR5K_PCIE_PM_CTL_L0_L0S_EN 0x00000004 /* Start L0 nd L0S counters */ +#define AR5K_PCIE_PM_CTL_LDRESET_EN 0x00000008 /* Enable reset when link goes + down */ +/* Wake On Wireless */ +#define AR5K_PCIE_PM_CTL_PME_EN 0x00000010 /* PME Enable */ +#define AR5K_PCIE_PM_CTL_AUX_PWR_DET 0x00000020 /* Aux power detect */ +#define AR5K_PCIE_PM_CTL_PME_CLEAR 0x00000040 /* Clear PME */ +#define AR5K_PCIE_PM_CTL_PSM_D0 0x00000080 +#define AR5K_PCIE_PM_CTL_PSM_D1 0x00000100 +#define AR5K_PCIE_PM_CTL_PSM_D2 0x00000200 +#define AR5K_PCIE_PM_CTL_PSM_D3 0x00000400 + +/* + * PCI-E Workaround enable register + */ +#define AR5K_PCIE_WAEN 0x407c + +/* + * PCI-E Serializer/Deserializer + * registers + */ +#define AR5K_PCIE_SERDES 0x4080 +#define AR5K_PCIE_SERDES_RESET 0x4084 + +/*====EEPROM REGISTERS====*/ + +/* + * EEPROM access registers + * + * Here we got a difference between 5210/5211-12 + * read data register for 5210 is at 0x6800 and + * status register is at 0x6c00. There is also + * no eeprom command register on 5210 and the + * offsets are different. + * + * To read eeprom data for a specific offset: + * 5210 - enable eeprom access (AR5K_PCICFG_EEAE) + * read AR5K_EEPROM_BASE +(4 * offset) + * check the eeprom status register + * and read eeprom data register. + * + * 5211 - write offset to AR5K_EEPROM_BASE + * 5212 write AR5K_EEPROM_CMD_READ on AR5K_EEPROM_CMD + * check the eeprom status register + * and read eeprom data register. + * + * To write eeprom data for a specific offset: + * 5210 - enable eeprom access (AR5K_PCICFG_EEAE) + * write data to AR5K_EEPROM_BASE +(4 * offset) + * check the eeprom status register + * 5211 - write AR5K_EEPROM_CMD_RESET on AR5K_EEPROM_CMD + * 5212 write offset to AR5K_EEPROM_BASE + * write data to data register + * write AR5K_EEPROM_CMD_WRITE on AR5K_EEPROM_CMD + * check the eeprom status register + * + * For more infos check eeprom_* functs and the ar5k.c + * file posted in madwifi-devel mailing list. + * http://sourceforge.net/mailarchive/message.php?msg_id=8966525 + * + */ +#define AR5K_EEPROM_BASE 0x6000 + +/* + * EEPROM data register + */ +#define AR5K_EEPROM_DATA_5211 0x6004 +#define AR5K_EEPROM_DATA_5210 0x6800 +#define AR5K_EEPROM_DATA (ah->ah_version == AR5K_AR5210 ? \ + AR5K_EEPROM_DATA_5210 : AR5K_EEPROM_DATA_5211) + +/* + * EEPROM command register + */ +#define AR5K_EEPROM_CMD 0x6008 /* Register Address */ +#define AR5K_EEPROM_CMD_READ 0x00000001 /* EEPROM read */ +#define AR5K_EEPROM_CMD_WRITE 0x00000002 /* EEPROM write */ +#define AR5K_EEPROM_CMD_RESET 0x00000004 /* EEPROM reset */ + +/* + * EEPROM status register + */ +#define AR5K_EEPROM_STAT_5210 0x6c00 /* Register Address [5210] */ +#define AR5K_EEPROM_STAT_5211 0x600c /* Register Address [5211+] */ +#define AR5K_EEPROM_STATUS (ah->ah_version == AR5K_AR5210 ? \ + AR5K_EEPROM_STAT_5210 : AR5K_EEPROM_STAT_5211) +#define AR5K_EEPROM_STAT_RDERR 0x00000001 /* EEPROM read failed */ +#define AR5K_EEPROM_STAT_RDDONE 0x00000002 /* EEPROM read successful */ +#define AR5K_EEPROM_STAT_WRERR 0x00000004 /* EEPROM write failed */ +#define AR5K_EEPROM_STAT_WRDONE 0x00000008 /* EEPROM write successful */ + +/* + * EEPROM config register + */ +#define AR5K_EEPROM_CFG 0x6010 /* Register Address */ +#define AR5K_EEPROM_CFG_SIZE 0x00000003 /* Size determination override */ +#define AR5K_EEPROM_CFG_SIZE_AUTO 0 +#define AR5K_EEPROM_CFG_SIZE_4KBIT 1 +#define AR5K_EEPROM_CFG_SIZE_8KBIT 2 +#define AR5K_EEPROM_CFG_SIZE_16KBIT 3 +#define AR5K_EEPROM_CFG_WR_WAIT_DIS 0x00000004 /* Disable write wait */ +#define AR5K_EEPROM_CFG_CLK_RATE 0x00000018 /* Clock rate */ +#define AR5K_EEPROM_CFG_CLK_RATE_S 3 +#define AR5K_EEPROM_CFG_CLK_RATE_156KHZ 0 +#define AR5K_EEPROM_CFG_CLK_RATE_312KHZ 1 +#define AR5K_EEPROM_CFG_CLK_RATE_625KHZ 2 +#define AR5K_EEPROM_CFG_PROT_KEY 0x00ffff00 /* Protection key */ +#define AR5K_EEPROM_CFG_PROT_KEY_S 8 +#define AR5K_EEPROM_CFG_LIND_EN 0x01000000 /* Enable length indicator (?) */ + + +/* + * TODO: Wake On Wireless registers + * Range 0x7000 - 0x7ce0 + */ + +/* + * Protocol Control Unit (PCU) registers + */ +/* + * Used for checking initial register writes + * during channel reset (see reset func) + */ +#define AR5K_PCU_MIN 0x8000 +#define AR5K_PCU_MAX 0x8fff + +/* + * First station id register (Lower 32 bits of MAC address) + */ +#define AR5K_STA_ID0 0x8000 +#define AR5K_STA_ID0_ARRD_L32 0xffffffff + +/* + * Second station id register (Upper 16 bits of MAC address + PCU settings) + */ +#define AR5K_STA_ID1 0x8004 /* Register Address */ +#define AR5K_STA_ID1_ADDR_U16 0x0000ffff /* Upper 16 bits of MAC address */ +#define AR5K_STA_ID1_AP 0x00010000 /* Set AP mode */ +#define AR5K_STA_ID1_ADHOC 0x00020000 /* Set Ad-Hoc mode */ +#define AR5K_STA_ID1_PWR_SV 0x00040000 /* Power save reporting */ +#define AR5K_STA_ID1_NO_KEYSRCH 0x00080000 /* No key search */ +#define AR5K_STA_ID1_NO_PSPOLL 0x00100000 /* No power save polling [5210] */ +#define AR5K_STA_ID1_PCF_5211 0x00100000 /* Enable PCF on [5211+] */ +#define AR5K_STA_ID1_PCF_5210 0x00200000 /* Enable PCF on [5210]*/ +#define AR5K_STA_ID1_PCF (ah->ah_version == AR5K_AR5210 ? \ + AR5K_STA_ID1_PCF_5210 : AR5K_STA_ID1_PCF_5211) +#define AR5K_STA_ID1_DEFAULT_ANTENNA 0x00200000 /* Use default antenna */ +#define AR5K_STA_ID1_DESC_ANTENNA 0x00400000 /* Update antenna from descriptor */ +#define AR5K_STA_ID1_RTS_DEF_ANTENNA 0x00800000 /* Use default antenna for RTS */ +#define AR5K_STA_ID1_ACKCTS_6MB 0x01000000 /* Rate to use for ACK/CTS. 0: highest mandatory rate <= RX rate; 1: 1Mbps in B mode */ +#define AR5K_STA_ID1_BASE_RATE_11B 0x02000000 /* 802.11b base rate. 0: 1, 2, 5.5 and 11Mbps; 1: 1 and 2Mbps. [5211+] */ +#define AR5K_STA_ID1_SELFGEN_DEF_ANT 0x04000000 /* Use def. antenna for self generated frames */ +#define AR5K_STA_ID1_CRYPT_MIC_EN 0x08000000 /* Enable MIC */ +#define AR5K_STA_ID1_KEYSRCH_MODE 0x10000000 /* Look up key when key id != 0 */ +#define AR5K_STA_ID1_PRESERVE_SEQ_NUM 0x20000000 /* Preserve sequence number */ +#define AR5K_STA_ID1_CBCIV_ENDIAN 0x40000000 /* ??? */ +#define AR5K_STA_ID1_KEYSRCH_MCAST 0x80000000 /* Do key cache search for mcast frames */ + +#define AR5K_STA_ID1_ANTENNA_SETTINGS (AR5K_STA_ID1_DEFAULT_ANTENNA | \ + AR5K_STA_ID1_DESC_ANTENNA | \ + AR5K_STA_ID1_RTS_DEF_ANTENNA | \ + AR5K_STA_ID1_SELFGEN_DEF_ANT) + +/* + * First BSSID register (MAC address, lower 32bits) + */ +#define AR5K_BSS_ID0 0x8008 + +/* + * Second BSSID register (MAC address in upper 16 bits) + * + * AID: Association ID + */ +#define AR5K_BSS_ID1 0x800c +#define AR5K_BSS_ID1_AID 0xffff0000 +#define AR5K_BSS_ID1_AID_S 16 + +/* + * Backoff slot time register + */ +#define AR5K_SLOT_TIME 0x8010 + +/* + * ACK/CTS timeout register + */ +#define AR5K_TIME_OUT 0x8014 /* Register Address */ +#define AR5K_TIME_OUT_ACK 0x00001fff /* ACK timeout mask */ +#define AR5K_TIME_OUT_ACK_S 0 +#define AR5K_TIME_OUT_CTS 0x1fff0000 /* CTS timeout mask */ +#define AR5K_TIME_OUT_CTS_S 16 + +/* + * RSSI threshold register + */ +#define AR5K_RSSI_THR 0x8018 /* Register Address */ +#define AR5K_RSSI_THR_M 0x000000ff /* Mask for RSSI threshold [5211+] */ +#define AR5K_RSSI_THR_BMISS_5210 0x00000700 /* Mask for Beacon Missed threshold [5210] */ +#define AR5K_RSSI_THR_BMISS_5210_S 8 +#define AR5K_RSSI_THR_BMISS_5211 0x0000ff00 /* Mask for Beacon Missed threshold [5211+] */ +#define AR5K_RSSI_THR_BMISS_5211_S 8 +#define AR5K_RSSI_THR_BMISS (ah->ah_version == AR5K_AR5210 ? \ + AR5K_RSSI_THR_BMISS_5210 : AR5K_RSSI_THR_BMISS_5211) +#define AR5K_RSSI_THR_BMISS_S 8 + +/* + * 5210 has more PCU registers because there is no QCU/DCU + * so queue parameters are set here, this way a lot common + * registers have different address for 5210. To make things + * easier we define a macro based on ah->ah_version for common + * registers with different addresses and common flags. + */ + +/* + * Retry limit register + * + * Retry limit register for 5210 (no QCU/DCU so it's done in PCU) + */ +#define AR5K_NODCU_RETRY_LMT 0x801c /* Register Address */ +#define AR5K_NODCU_RETRY_LMT_SH_RETRY 0x0000000f /* Short retry limit mask */ +#define AR5K_NODCU_RETRY_LMT_SH_RETRY_S 0 +#define AR5K_NODCU_RETRY_LMT_LG_RETRY 0x000000f0 /* Long retry mask */ +#define AR5K_NODCU_RETRY_LMT_LG_RETRY_S 4 +#define AR5K_NODCU_RETRY_LMT_SSH_RETRY 0x00003f00 /* Station short retry limit mask */ +#define AR5K_NODCU_RETRY_LMT_SSH_RETRY_S 8 +#define AR5K_NODCU_RETRY_LMT_SLG_RETRY 0x000fc000 /* Station long retry limit mask */ +#define AR5K_NODCU_RETRY_LMT_SLG_RETRY_S 14 +#define AR5K_NODCU_RETRY_LMT_CW_MIN 0x3ff00000 /* Minimum contention window mask */ +#define AR5K_NODCU_RETRY_LMT_CW_MIN_S 20 + +/* + * Transmit latency register + */ +#define AR5K_USEC_5210 0x8020 /* Register Address [5210] */ +#define AR5K_USEC_5211 0x801c /* Register Address [5211+] */ +#define AR5K_USEC (ah->ah_version == AR5K_AR5210 ? \ + AR5K_USEC_5210 : AR5K_USEC_5211) +#define AR5K_USEC_1 0x0000007f /* clock cycles for 1us */ +#define AR5K_USEC_1_S 0 +#define AR5K_USEC_32 0x00003f80 /* clock cycles for 1us while on 32MHz clock */ +#define AR5K_USEC_32_S 7 +#define AR5K_USEC_TX_LATENCY_5211 0x007fc000 +#define AR5K_USEC_TX_LATENCY_5211_S 14 +#define AR5K_USEC_RX_LATENCY_5211 0x1f800000 +#define AR5K_USEC_RX_LATENCY_5211_S 23 +#define AR5K_USEC_TX_LATENCY_5210 0x000fc000 /* also for 5311 */ +#define AR5K_USEC_TX_LATENCY_5210_S 14 +#define AR5K_USEC_RX_LATENCY_5210 0x03f00000 /* also for 5311 */ +#define AR5K_USEC_RX_LATENCY_5210_S 20 + +/* + * PCU beacon control register + */ +#define AR5K_BEACON_5210 0x8024 /*Register Address [5210] */ +#define AR5K_BEACON_5211 0x8020 /*Register Address [5211+] */ +#define AR5K_BEACON (ah->ah_version == AR5K_AR5210 ? \ + AR5K_BEACON_5210 : AR5K_BEACON_5211) +#define AR5K_BEACON_PERIOD 0x0000ffff /* Mask for beacon period */ +#define AR5K_BEACON_PERIOD_S 0 +#define AR5K_BEACON_TIM 0x007f0000 /* Mask for TIM offset */ +#define AR5K_BEACON_TIM_S 16 +#define AR5K_BEACON_ENABLE 0x00800000 /* Enable beacons */ +#define AR5K_BEACON_RESET_TSF 0x01000000 /* Force TSF reset */ + +/* + * CFP period register + */ +#define AR5K_CFP_PERIOD_5210 0x8028 +#define AR5K_CFP_PERIOD_5211 0x8024 +#define AR5K_CFP_PERIOD (ah->ah_version == AR5K_AR5210 ? \ + AR5K_CFP_PERIOD_5210 : AR5K_CFP_PERIOD_5211) + +/* + * Next beacon time register + */ +#define AR5K_TIMER0_5210 0x802c +#define AR5K_TIMER0_5211 0x8028 +#define AR5K_TIMER0 (ah->ah_version == AR5K_AR5210 ? \ + AR5K_TIMER0_5210 : AR5K_TIMER0_5211) + +/* + * Next DMA beacon alert register + */ +#define AR5K_TIMER1_5210 0x8030 +#define AR5K_TIMER1_5211 0x802c +#define AR5K_TIMER1 (ah->ah_version == AR5K_AR5210 ? \ + AR5K_TIMER1_5210 : AR5K_TIMER1_5211) + +/* + * Next software beacon alert register + */ +#define AR5K_TIMER2_5210 0x8034 +#define AR5K_TIMER2_5211 0x8030 +#define AR5K_TIMER2 (ah->ah_version == AR5K_AR5210 ? \ + AR5K_TIMER2_5210 : AR5K_TIMER2_5211) + +/* + * Next ATIM window time register + */ +#define AR5K_TIMER3_5210 0x8038 +#define AR5K_TIMER3_5211 0x8034 +#define AR5K_TIMER3 (ah->ah_version == AR5K_AR5210 ? \ + AR5K_TIMER3_5210 : AR5K_TIMER3_5211) + + +/* + * 5210 First inter frame spacing register (IFS) + */ +#define AR5K_IFS0 0x8040 +#define AR5K_IFS0_SIFS 0x000007ff +#define AR5K_IFS0_SIFS_S 0 +#define AR5K_IFS0_DIFS 0x007ff800 +#define AR5K_IFS0_DIFS_S 11 + +/* + * 5210 Second inter frame spacing register (IFS) + */ +#define AR5K_IFS1 0x8044 +#define AR5K_IFS1_PIFS 0x00000fff +#define AR5K_IFS1_PIFS_S 0 +#define AR5K_IFS1_EIFS 0x03fff000 +#define AR5K_IFS1_EIFS_S 12 +#define AR5K_IFS1_CS_EN 0x04000000 +#define AR5K_IFS1_CS_EN_S 26 + +/* + * CFP duration register + */ +#define AR5K_CFP_DUR_5210 0x8048 +#define AR5K_CFP_DUR_5211 0x8038 +#define AR5K_CFP_DUR (ah->ah_version == AR5K_AR5210 ? \ + AR5K_CFP_DUR_5210 : AR5K_CFP_DUR_5211) + +/* + * Receive filter register + */ +#define AR5K_RX_FILTER_5210 0x804c /* Register Address [5210] */ +#define AR5K_RX_FILTER_5211 0x803c /* Register Address [5211+] */ +#define AR5K_RX_FILTER (ah->ah_version == AR5K_AR5210 ? \ + AR5K_RX_FILTER_5210 : AR5K_RX_FILTER_5211) +#define AR5K_RX_FILTER_UCAST 0x00000001 /* Don't filter unicast frames */ +#define AR5K_RX_FILTER_MCAST 0x00000002 /* Don't filter multicast frames */ +#define AR5K_RX_FILTER_BCAST 0x00000004 /* Don't filter broadcast frames */ +#define AR5K_RX_FILTER_CONTROL 0x00000008 /* Don't filter control frames */ +#define AR5K_RX_FILTER_BEACON 0x00000010 /* Don't filter beacon frames */ +#define AR5K_RX_FILTER_PROM 0x00000020 /* Set promiscuous mode */ +#define AR5K_RX_FILTER_XRPOLL 0x00000040 /* Don't filter XR poll frame [5212+] */ +#define AR5K_RX_FILTER_PROBEREQ 0x00000080 /* Don't filter probe requests [5212+] */ +#define AR5K_RX_FILTER_PHYERR_5212 0x00000100 /* Don't filter phy errors [5212+] */ +#define AR5K_RX_FILTER_RADARERR_5212 0x00000200 /* Don't filter phy radar errors [5212+] */ +#define AR5K_RX_FILTER_PHYERR_5211 0x00000040 /* [5211] */ +#define AR5K_RX_FILTER_RADARERR_5211 0x00000080 /* [5211] */ +#define AR5K_RX_FILTER_PHYERR \ + ((ah->ah_version == AR5K_AR5211 ? \ + AR5K_RX_FILTER_PHYERR_5211 : AR5K_RX_FILTER_PHYERR_5212)) +#define AR5K_RX_FILTER_RADARERR \ + ((ah->ah_version == AR5K_AR5211 ? \ + AR5K_RX_FILTER_RADARERR_5211 : AR5K_RX_FILTER_RADARERR_5212)) + +/* + * Multicast filter register (lower 32 bits) + */ +#define AR5K_MCAST_FILTER0_5210 0x8050 +#define AR5K_MCAST_FILTER0_5211 0x8040 +#define AR5K_MCAST_FILTER0 (ah->ah_version == AR5K_AR5210 ? \ + AR5K_MCAST_FILTER0_5210 : AR5K_MCAST_FILTER0_5211) + +/* + * Multicast filter register (higher 16 bits) + */ +#define AR5K_MCAST_FILTER1_5210 0x8054 +#define AR5K_MCAST_FILTER1_5211 0x8044 +#define AR5K_MCAST_FILTER1 (ah->ah_version == AR5K_AR5210 ? \ + AR5K_MCAST_FILTER1_5210 : AR5K_MCAST_FILTER1_5211) + + +/* + * Transmit mask register (lower 32 bits) [5210] + */ +#define AR5K_TX_MASK0 0x8058 + +/* + * Transmit mask register (higher 16 bits) [5210] + */ +#define AR5K_TX_MASK1 0x805c + +/* + * Clear transmit mask [5210] + */ +#define AR5K_CLR_TMASK 0x8060 + +/* + * Trigger level register (before transmission) [5210] + */ +#define AR5K_TRIG_LVL 0x8064 + + +/* + * PCU Diagnostic register + * + * Used for tweaking/diagnostics. + */ +#define AR5K_DIAG_SW_5210 0x8068 /* Register Address [5210] */ +#define AR5K_DIAG_SW_5211 0x8048 /* Register Address [5211+] */ +#define AR5K_DIAG_SW (ah->ah_version == AR5K_AR5210 ? \ + AR5K_DIAG_SW_5210 : AR5K_DIAG_SW_5211) +#define AR5K_DIAG_SW_DIS_WEP_ACK 0x00000001 /* Disable ACKs if WEP key is invalid */ +#define AR5K_DIAG_SW_DIS_ACK 0x00000002 /* Disable ACKs */ +#define AR5K_DIAG_SW_DIS_CTS 0x00000004 /* Disable CTSs */ +#define AR5K_DIAG_SW_DIS_ENC 0x00000008 /* Disable HW encryption */ +#define AR5K_DIAG_SW_DIS_DEC 0x00000010 /* Disable HW decryption */ +#define AR5K_DIAG_SW_DIS_TX_5210 0x00000020 /* Disable transmit [5210] */ +#define AR5K_DIAG_SW_DIS_RX_5210 0x00000040 /* Disable receive */ +#define AR5K_DIAG_SW_DIS_RX_5211 0x00000020 +#define AR5K_DIAG_SW_DIS_RX (ah->ah_version == AR5K_AR5210 ? \ + AR5K_DIAG_SW_DIS_RX_5210 : AR5K_DIAG_SW_DIS_RX_5211) +#define AR5K_DIAG_SW_LOOP_BACK_5210 0x00000080 /* TX Data Loopback (i guess it goes with DIS_TX) [5210] */ +#define AR5K_DIAG_SW_LOOP_BACK_5211 0x00000040 +#define AR5K_DIAG_SW_LOOP_BACK (ah->ah_version == AR5K_AR5210 ? \ + AR5K_DIAG_SW_LOOP_BACK_5210 : AR5K_DIAG_SW_LOOP_BACK_5211) +#define AR5K_DIAG_SW_CORR_FCS_5210 0x00000100 /* Generate invalid TX FCS */ +#define AR5K_DIAG_SW_CORR_FCS_5211 0x00000080 +#define AR5K_DIAG_SW_CORR_FCS (ah->ah_version == AR5K_AR5210 ? \ + AR5K_DIAG_SW_CORR_FCS_5210 : AR5K_DIAG_SW_CORR_FCS_5211) +#define AR5K_DIAG_SW_CHAN_INFO_5210 0x00000200 /* Add 56 bytes of channel info before the frame data in the RX buffer */ +#define AR5K_DIAG_SW_CHAN_INFO_5211 0x00000100 +#define AR5K_DIAG_SW_CHAN_INFO (ah->ah_version == AR5K_AR5210 ? \ + AR5K_DIAG_SW_CHAN_INFO_5210 : AR5K_DIAG_SW_CHAN_INFO_5211) +#define AR5K_DIAG_SW_EN_SCRAM_SEED_5210 0x00000400 /* Enable fixed scrambler seed */ +#define AR5K_DIAG_SW_EN_SCRAM_SEED_5211 0x00000200 +#define AR5K_DIAG_SW_EN_SCRAM_SEED (ah->ah_version == AR5K_AR5210 ? \ + AR5K_DIAG_SW_EN_SCRAM_SEED_5210 : AR5K_DIAG_SW_EN_SCRAM_SEED_5211) +#define AR5K_DIAG_SW_ECO_ENABLE 0x00000400 /* [5211+] */ +#define AR5K_DIAG_SW_SCVRAM_SEED 0x0003f800 /* [5210] */ +#define AR5K_DIAG_SW_SCRAM_SEED_M 0x0001fc00 /* Scrambler seed mask */ +#define AR5K_DIAG_SW_SCRAM_SEED_S 10 +#define AR5K_DIAG_SW_DIS_SEQ_INC_5210 0x00040000 /* Disable seqnum increment (?)[5210] */ +#define AR5K_DIAG_SW_FRAME_NV0_5210 0x00080000 +#define AR5K_DIAG_SW_FRAME_NV0_5211 0x00020000 /* Accept frames of non-zero protocol number */ +#define AR5K_DIAG_SW_FRAME_NV0 (ah->ah_version == AR5K_AR5210 ? \ + AR5K_DIAG_SW_FRAME_NV0_5210 : AR5K_DIAG_SW_FRAME_NV0_5211) +#define AR5K_DIAG_SW_OBSPT_M 0x000c0000 /* Observation point select (?) */ +#define AR5K_DIAG_SW_OBSPT_S 18 +#define AR5K_DIAG_SW_RX_CLEAR_HIGH 0x00100000 /* Ignore carrier sense */ +#define AR5K_DIAG_SW_IGNORE_CARR_SENSE 0x00200000 /* Ignore virtual carrier sense */ +#define AR5K_DIAG_SW_CHANNEL_IDLE_HIGH 0x00400000 /* Force channel idle high */ +#define AR5K_DIAG_SW_PHEAR_ME 0x00800000 /* ??? */ + +/* + * TSF (clock) register (lower 32 bits) + */ +#define AR5K_TSF_L32_5210 0x806c +#define AR5K_TSF_L32_5211 0x804c +#define AR5K_TSF_L32 (ah->ah_version == AR5K_AR5210 ? \ + AR5K_TSF_L32_5210 : AR5K_TSF_L32_5211) + +/* + * TSF (clock) register (higher 32 bits) + */ +#define AR5K_TSF_U32_5210 0x8070 +#define AR5K_TSF_U32_5211 0x8050 +#define AR5K_TSF_U32 (ah->ah_version == AR5K_AR5210 ? \ + AR5K_TSF_U32_5210 : AR5K_TSF_U32_5211) + +/* + * Last beacon timestamp register (Read Only) + */ +#define AR5K_LAST_TSTP 0x8080 + +/* + * ADDAC test register [5211+] + */ +#define AR5K_ADDAC_TEST 0x8054 /* Register Address */ +#define AR5K_ADDAC_TEST_TXCONT 0x00000001 /* Test continuous tx */ +#define AR5K_ADDAC_TEST_TST_MODE 0x00000002 /* Test mode */ +#define AR5K_ADDAC_TEST_LOOP_EN 0x00000004 /* Enable loop */ +#define AR5K_ADDAC_TEST_LOOP_LEN 0x00000008 /* Loop length (field) */ +#define AR5K_ADDAC_TEST_USE_U8 0x00004000 /* Use upper 8 bits */ +#define AR5K_ADDAC_TEST_MSB 0x00008000 /* State of MSB */ +#define AR5K_ADDAC_TEST_TRIG_SEL 0x00010000 /* Trigger select */ +#define AR5K_ADDAC_TEST_TRIG_PTY 0x00020000 /* Trigger polarity */ +#define AR5K_ADDAC_TEST_RXCONT 0x00040000 /* Continuous capture */ +#define AR5K_ADDAC_TEST_CAPTURE 0x00080000 /* Begin capture */ +#define AR5K_ADDAC_TEST_TST_ARM 0x00100000 /* ARM rx buffer for capture */ + +/* + * Default antenna register [5211+] + */ +#define AR5K_DEFAULT_ANTENNA 0x8058 + +/* + * Frame control QoS mask register (?) [5211+] + * (FC_QOS_MASK) + */ +#define AR5K_FRAME_CTL_QOSM 0x805c + +/* + * Seq mask register (?) [5211+] + */ +#define AR5K_SEQ_MASK 0x8060 + +/* + * Retry count register [5210] + */ +#define AR5K_RETRY_CNT 0x8084 /* Register Address [5210] */ +#define AR5K_RETRY_CNT_SSH 0x0000003f /* Station short retry count (?) */ +#define AR5K_RETRY_CNT_SLG 0x00000fc0 /* Station long retry count (?) */ + +/* + * Back-off status register [5210] + */ +#define AR5K_BACKOFF 0x8088 /* Register Address [5210] */ +#define AR5K_BACKOFF_CW 0x000003ff /* Backoff Contention Window (?) */ +#define AR5K_BACKOFF_CNT 0x03ff0000 /* Backoff count (?) */ + + + +/* + * NAV register (current) + */ +#define AR5K_NAV_5210 0x808c +#define AR5K_NAV_5211 0x8084 +#define AR5K_NAV (ah->ah_version == AR5K_AR5210 ? \ + AR5K_NAV_5210 : AR5K_NAV_5211) + +/* + * MIB counters: + * + * max value is 0xc000, if this is reached we get a MIB interrupt. + * they can be controlled via AR5K_MIBC and are cleared on read. + */ + +/* + * RTS success (MIB counter) + */ +#define AR5K_RTS_OK_5210 0x8090 +#define AR5K_RTS_OK_5211 0x8088 +#define AR5K_RTS_OK (ah->ah_version == AR5K_AR5210 ? \ + AR5K_RTS_OK_5210 : AR5K_RTS_OK_5211) + +/* + * RTS failure (MIB counter) + */ +#define AR5K_RTS_FAIL_5210 0x8094 +#define AR5K_RTS_FAIL_5211 0x808c +#define AR5K_RTS_FAIL (ah->ah_version == AR5K_AR5210 ? \ + AR5K_RTS_FAIL_5210 : AR5K_RTS_FAIL_5211) + +/* + * ACK failure (MIB counter) + */ +#define AR5K_ACK_FAIL_5210 0x8098 +#define AR5K_ACK_FAIL_5211 0x8090 +#define AR5K_ACK_FAIL (ah->ah_version == AR5K_AR5210 ? \ + AR5K_ACK_FAIL_5210 : AR5K_ACK_FAIL_5211) + +/* + * FCS failure (MIB counter) + */ +#define AR5K_FCS_FAIL_5210 0x809c +#define AR5K_FCS_FAIL_5211 0x8094 +#define AR5K_FCS_FAIL (ah->ah_version == AR5K_AR5210 ? \ + AR5K_FCS_FAIL_5210 : AR5K_FCS_FAIL_5211) + +/* + * Beacon count register + */ +#define AR5K_BEACON_CNT_5210 0x80a0 +#define AR5K_BEACON_CNT_5211 0x8098 +#define AR5K_BEACON_CNT (ah->ah_version == AR5K_AR5210 ? \ + AR5K_BEACON_CNT_5210 : AR5K_BEACON_CNT_5211) + + +/*===5212 Specific PCU registers===*/ + +/* + * Transmit power control register + */ +#define AR5K_TPC 0x80e8 +#define AR5K_TPC_ACK 0x0000003f /* ack frames */ +#define AR5K_TPC_ACK_S 0 +#define AR5K_TPC_CTS 0x00003f00 /* cts frames */ +#define AR5K_TPC_CTS_S 8 +#define AR5K_TPC_CHIRP 0x003f0000 /* chirp frames */ +#define AR5K_TPC_CHIRP_S 16 +#define AR5K_TPC_DOPPLER 0x0f000000 /* doppler chirp span */ +#define AR5K_TPC_DOPPLER_S 24 + +/* + * XR (eXtended Range) mode register + */ +#define AR5K_XRMODE 0x80c0 /* Register Address */ +#define AR5K_XRMODE_POLL_TYPE_M 0x0000003f /* Mask for Poll type (?) */ +#define AR5K_XRMODE_POLL_TYPE_S 0 +#define AR5K_XRMODE_POLL_SUBTYPE_M 0x0000003c /* Mask for Poll subtype (?) */ +#define AR5K_XRMODE_POLL_SUBTYPE_S 2 +#define AR5K_XRMODE_POLL_WAIT_ALL 0x00000080 /* Wait for poll */ +#define AR5K_XRMODE_SIFS_DELAY 0x000fff00 /* Mask for SIFS delay */ +#define AR5K_XRMODE_FRAME_HOLD_M 0xfff00000 /* Mask for frame hold (?) */ +#define AR5K_XRMODE_FRAME_HOLD_S 20 + +/* + * XR delay register + */ +#define AR5K_XRDELAY 0x80c4 /* Register Address */ +#define AR5K_XRDELAY_SLOT_DELAY_M 0x0000ffff /* Mask for slot delay */ +#define AR5K_XRDELAY_SLOT_DELAY_S 0 +#define AR5K_XRDELAY_CHIRP_DELAY_M 0xffff0000 /* Mask for CHIRP data delay */ +#define AR5K_XRDELAY_CHIRP_DELAY_S 16 + +/* + * XR timeout register + */ +#define AR5K_XRTIMEOUT 0x80c8 /* Register Address */ +#define AR5K_XRTIMEOUT_CHIRP_M 0x0000ffff /* Mask for CHIRP timeout */ +#define AR5K_XRTIMEOUT_CHIRP_S 0 +#define AR5K_XRTIMEOUT_POLL_M 0xffff0000 /* Mask for Poll timeout */ +#define AR5K_XRTIMEOUT_POLL_S 16 + +/* + * XR chirp register + */ +#define AR5K_XRCHIRP 0x80cc /* Register Address */ +#define AR5K_XRCHIRP_SEND 0x00000001 /* Send CHIRP */ +#define AR5K_XRCHIRP_GAP 0xffff0000 /* Mask for CHIRP gap (?) */ + +/* + * XR stomp register + */ +#define AR5K_XRSTOMP 0x80d0 /* Register Address */ +#define AR5K_XRSTOMP_TX 0x00000001 /* Stomp Tx (?) */ +#define AR5K_XRSTOMP_RX 0x00000002 /* Stomp Rx (?) */ +#define AR5K_XRSTOMP_TX_RSSI 0x00000004 /* Stomp Tx RSSI (?) */ +#define AR5K_XRSTOMP_TX_BSSID 0x00000008 /* Stomp Tx BSSID (?) */ +#define AR5K_XRSTOMP_DATA 0x00000010 /* Stomp data (?)*/ +#define AR5K_XRSTOMP_RSSI_THRES 0x0000ff00 /* Mask for XR RSSI threshold */ + +/* + * First enhanced sleep register + */ +#define AR5K_SLEEP0 0x80d4 /* Register Address */ +#define AR5K_SLEEP0_NEXT_DTIM 0x0007ffff /* Mask for next DTIM (?) */ +#define AR5K_SLEEP0_NEXT_DTIM_S 0 +#define AR5K_SLEEP0_ASSUME_DTIM 0x00080000 /* Assume DTIM */ +#define AR5K_SLEEP0_ENH_SLEEP_EN 0x00100000 /* Enable enhanced sleep control */ +#define AR5K_SLEEP0_CABTO 0xff000000 /* Mask for CAB Time Out */ +#define AR5K_SLEEP0_CABTO_S 24 + +/* + * Second enhanced sleep register + */ +#define AR5K_SLEEP1 0x80d8 /* Register Address */ +#define AR5K_SLEEP1_NEXT_TIM 0x0007ffff /* Mask for next TIM (?) */ +#define AR5K_SLEEP1_NEXT_TIM_S 0 +#define AR5K_SLEEP1_BEACON_TO 0xff000000 /* Mask for Beacon Time Out */ +#define AR5K_SLEEP1_BEACON_TO_S 24 + +/* + * Third enhanced sleep register + */ +#define AR5K_SLEEP2 0x80dc /* Register Address */ +#define AR5K_SLEEP2_TIM_PER 0x0000ffff /* Mask for TIM period (?) */ +#define AR5K_SLEEP2_TIM_PER_S 0 +#define AR5K_SLEEP2_DTIM_PER 0xffff0000 /* Mask for DTIM period (?) */ +#define AR5K_SLEEP2_DTIM_PER_S 16 + +/* + * TX power control (TPC) register + * + * XXX: PCDAC steps (0.5dBm) or dBm ? + * + */ +#define AR5K_TXPC 0x80e8 /* Register Address */ +#define AR5K_TXPC_ACK_M 0x0000003f /* ACK tx power */ +#define AR5K_TXPC_ACK_S 0 +#define AR5K_TXPC_CTS_M 0x00003f00 /* CTS tx power */ +#define AR5K_TXPC_CTS_S 8 +#define AR5K_TXPC_CHIRP_M 0x003f0000 /* CHIRP tx power */ +#define AR5K_TXPC_CHIRP_S 16 +#define AR5K_TXPC_DOPPLER 0x0f000000 /* Doppler chirp span (?) */ +#define AR5K_TXPC_DOPPLER_S 24 + +/* + * Profile count registers + * + * These registers can be cleared and frozen with ATH5K_MIBC, but they do not + * generate a MIB interrupt. + * Instead of overflowing, they shift by one bit to the right. All registers + * shift together, i.e. when one reaches the max, all shift at the same time by + * one bit to the right. This way we should always get consistent values. + */ +#define AR5K_PROFCNT_TX 0x80ec /* Tx count */ +#define AR5K_PROFCNT_RX 0x80f0 /* Rx count */ +#define AR5K_PROFCNT_RXCLR 0x80f4 /* Busy count */ +#define AR5K_PROFCNT_CYCLE 0x80f8 /* Cycle counter */ + +/* + * Quiet period control registers + */ +#define AR5K_QUIET_CTL1 0x80fc /* Register Address */ +#define AR5K_QUIET_CTL1_NEXT_QT_TSF 0x0000ffff /* Next quiet period TSF (TU) */ +#define AR5K_QUIET_CTL1_NEXT_QT_TSF_S 0 +#define AR5K_QUIET_CTL1_QT_EN 0x00010000 /* Enable quiet period */ +#define AR5K_QUIET_CTL1_ACK_CTS_EN 0x00020000 /* Send ACK/CTS during quiet period */ + +#define AR5K_QUIET_CTL2 0x8100 /* Register Address */ +#define AR5K_QUIET_CTL2_QT_PER 0x0000ffff /* Mask for quiet period periodicity */ +#define AR5K_QUIET_CTL2_QT_PER_S 0 +#define AR5K_QUIET_CTL2_QT_DUR 0xffff0000 /* Mask for quiet period duration */ +#define AR5K_QUIET_CTL2_QT_DUR_S 16 + +/* + * TSF parameter register + */ +#define AR5K_TSF_PARM 0x8104 /* Register Address */ +#define AR5K_TSF_PARM_INC 0x000000ff /* Mask for TSF increment */ +#define AR5K_TSF_PARM_INC_S 0 + +/* + * QoS NOACK policy + */ +#define AR5K_QOS_NOACK 0x8108 /* Register Address */ +#define AR5K_QOS_NOACK_2BIT_VALUES 0x0000000f /* ??? */ +#define AR5K_QOS_NOACK_2BIT_VALUES_S 0 +#define AR5K_QOS_NOACK_BIT_OFFSET 0x00000070 /* ??? */ +#define AR5K_QOS_NOACK_BIT_OFFSET_S 4 +#define AR5K_QOS_NOACK_BYTE_OFFSET 0x00000180 /* ??? */ +#define AR5K_QOS_NOACK_BYTE_OFFSET_S 7 + +/* + * PHY error filter register + */ +#define AR5K_PHY_ERR_FIL 0x810c +#define AR5K_PHY_ERR_FIL_RADAR 0x00000020 /* Radar signal */ +#define AR5K_PHY_ERR_FIL_OFDM 0x00020000 /* OFDM false detect (ANI) */ +#define AR5K_PHY_ERR_FIL_CCK 0x02000000 /* CCK false detect (ANI) */ + +/* + * XR latency register + */ +#define AR5K_XRLAT_TX 0x8110 + +/* + * ACK SIFS register + */ +#define AR5K_ACKSIFS 0x8114 /* Register Address */ +#define AR5K_ACKSIFS_INC 0x00000000 /* ACK SIFS Increment (field) */ + +/* + * MIC QoS control register (?) + */ +#define AR5K_MIC_QOS_CTL 0x8118 /* Register Address */ +#define AR5K_MIC_QOS_CTL_OFF(_n) (1 << (_n * 2)) +#define AR5K_MIC_QOS_CTL_MQ_EN 0x00010000 /* Enable MIC QoS */ + +/* + * MIC QoS select register (?) + */ +#define AR5K_MIC_QOS_SEL 0x811c +#define AR5K_MIC_QOS_SEL_OFF(_n) (1 << (_n * 4)) + +/* + * Misc mode control register (?) + */ +#define AR5K_MISC_MODE 0x8120 /* Register Address */ +#define AR5K_MISC_MODE_FBSSID_MATCH 0x00000001 /* Force BSSID match */ +#define AR5K_MISC_MODE_ACKSIFS_MEM 0x00000002 /* ACK SIFS memory (?) */ +#define AR5K_MISC_MODE_COMBINED_MIC 0x00000004 /* use rx/tx MIC key */ +/* more bits */ + +/* + * OFDM Filter counter + */ +#define AR5K_OFDM_FIL_CNT 0x8124 + +/* + * CCK Filter counter + */ +#define AR5K_CCK_FIL_CNT 0x8128 + +/* + * PHY Error Counters (same masks as AR5K_PHY_ERR_FIL) + */ +#define AR5K_PHYERR_CNT1 0x812c +#define AR5K_PHYERR_CNT1_MASK 0x8130 + +#define AR5K_PHYERR_CNT2 0x8134 +#define AR5K_PHYERR_CNT2_MASK 0x8138 + +/* if the PHY Error Counters reach this maximum, we get MIB interrupts */ +#define ATH5K_PHYERR_CNT_MAX 0x00c00000 + +/* + * TSF Threshold register (?) + */ +#define AR5K_TSF_THRES 0x813c + +/* + * TODO: Wake On Wireless registers + * Range: 0x8147 - 0x818c + */ + +/* + * Rate -> ACK SIFS mapping table (32 entries) + */ +#define AR5K_RATE_ACKSIFS_BASE 0x8680 /* Register Address */ +#define AR5K_RATE_ACKSIFS(_n) (AR5K_RATE_ACKSIFS_BSE + ((_n) << 2)) +#define AR5K_RATE_ACKSIFS_NORMAL 0x00000001 /* Normal SIFS (field) */ +#define AR5K_RATE_ACKSIFS_TURBO 0x00000400 /* Turbo SIFS (field) */ + +/* + * Rate -> duration mapping table (32 entries) + */ +#define AR5K_RATE_DUR_BASE 0x8700 +#define AR5K_RATE_DUR(_n) (AR5K_RATE_DUR_BASE + ((_n) << 2)) + +/* + * Rate -> db mapping table + * (8 entries, each one has 4 8bit fields) + */ +#define AR5K_RATE2DB_BASE 0x87c0 +#define AR5K_RATE2DB(_n) (AR5K_RATE2DB_BASE + ((_n) << 2)) + +/* + * db -> Rate mapping table + * (8 entries, each one has 4 8bit fields) + */ +#define AR5K_DB2RATE_BASE 0x87e0 +#define AR5K_DB2RATE(_n) (AR5K_DB2RATE_BASE + ((_n) << 2)) + +/*===5212 end===*/ + +#define AR5K_KEYTABLE_SIZE_5210 64 +#define AR5K_KEYTABLE_SIZE_5211 128 + +/*===PHY REGISTERS===*/ + +/* + * PHY registers start + */ +#define AR5K_PHY_BASE 0x9800 +#define AR5K_PHY(_n) (AR5K_PHY_BASE + ((_n) << 2)) + +/* + * TST_2 (Misc config parameters) + */ +#define AR5K_PHY_TST2 0x9800 /* Register Address */ +#define AR5K_PHY_TST2_TRIG_SEL 0x00000007 /* Trigger select (?)*/ +#define AR5K_PHY_TST2_TRIG 0x00000010 /* Trigger (?) */ +#define AR5K_PHY_TST2_CBUS_MODE 0x00000060 /* Cardbus mode (?) */ +#define AR5K_PHY_TST2_CLK32 0x00000400 /* CLK_OUT is CLK32 (32kHz external) */ +#define AR5K_PHY_TST2_CHANCOR_DUMP_EN 0x00000800 /* Enable Chancor dump (?) */ +#define AR5K_PHY_TST2_EVEN_CHANCOR_DUMP 0x00001000 /* Even Chancor dump (?) */ +#define AR5K_PHY_TST2_RFSILENT_EN 0x00002000 /* Enable RFSILENT */ +#define AR5K_PHY_TST2_ALT_RFDATA 0x00004000 /* Alternate RFDATA (5-2GHz switch ?) */ +#define AR5K_PHY_TST2_MINI_OBS_EN 0x00008000 /* Enable mini OBS (?) */ +#define AR5K_PHY_TST2_RX2_IS_RX5_INV 0x00010000 /* 2GHz rx path is the 5GHz path inverted (?) */ +#define AR5K_PHY_TST2_SLOW_CLK160 0x00020000 /* Slow CLK160 (?) */ +#define AR5K_PHY_TST2_AGC_OBS_SEL_3 0x00040000 /* AGC OBS Select 3 (?) */ +#define AR5K_PHY_TST2_BBB_OBS_SEL 0x00080000 /* BB OBS Select (field ?) */ +#define AR5K_PHY_TST2_ADC_OBS_SEL 0x00800000 /* ADC OBS Select (field ?) */ +#define AR5K_PHY_TST2_RX_CLR_SEL 0x08000000 /* RX Clear Select (?) */ +#define AR5K_PHY_TST2_FORCE_AGC_CLR 0x10000000 /* Force AGC clear (?) */ +#define AR5K_PHY_SHIFT_2GHZ 0x00004007 /* Used to access 2GHz radios */ +#define AR5K_PHY_SHIFT_5GHZ 0x00000007 /* Used to access 5GHz radios (default) */ + +/* + * PHY frame control register [5110] /turbo mode register [5111+] + * + * There is another frame control register for [5111+] + * at address 0x9944 (see below) but the 2 first flags + * are common here between 5110 frame control register + * and [5111+] turbo mode register, so this also works as + * a "turbo mode register" for 5110. We treat this one as + * a frame control register for 5110 below. + */ +#define AR5K_PHY_TURBO 0x9804 /* Register Address */ +#define AR5K_PHY_TURBO_MODE 0x00000001 /* Enable turbo mode */ +#define AR5K_PHY_TURBO_SHORT 0x00000002 /* Set short symbols to turbo mode */ +#define AR5K_PHY_TURBO_MIMO 0x00000004 /* Set turbo for mimo */ + +/* + * PHY agility command register + * (aka TST_1) + */ +#define AR5K_PHY_AGC 0x9808 /* Register Address */ +#define AR5K_PHY_TST1 0x9808 +#define AR5K_PHY_AGC_DISABLE 0x08000000 /* Disable AGC to A2 (?)*/ +#define AR5K_PHY_TST1_TXHOLD 0x00003800 /* Set tx hold (?) */ +#define AR5K_PHY_TST1_TXSRC_SRC 0x00000002 /* Used with bit 7 (?) */ +#define AR5K_PHY_TST1_TXSRC_SRC_S 1 +#define AR5K_PHY_TST1_TXSRC_ALT 0x00000080 /* Set input to tsdac (?) */ +#define AR5K_PHY_TST1_TXSRC_ALT_S 7 + + +/* + * PHY timing register 3 [5112+] + */ +#define AR5K_PHY_TIMING_3 0x9814 +#define AR5K_PHY_TIMING_3_DSC_MAN 0xfffe0000 +#define AR5K_PHY_TIMING_3_DSC_MAN_S 17 +#define AR5K_PHY_TIMING_3_DSC_EXP 0x0001e000 +#define AR5K_PHY_TIMING_3_DSC_EXP_S 13 + +/* + * PHY chip revision register + */ +#define AR5K_PHY_CHIP_ID 0x9818 + +/* + * PHY activation register + */ +#define AR5K_PHY_ACT 0x981c /* Register Address */ +#define AR5K_PHY_ACT_ENABLE 0x00000001 /* Activate PHY */ +#define AR5K_PHY_ACT_DISABLE 0x00000002 /* Deactivate PHY */ + +/* + * PHY RF control registers + */ +#define AR5K_PHY_RF_CTL2 0x9824 /* Register Address */ +#define AR5K_PHY_RF_CTL2_TXF2TXD_START 0x0000000f /* TX frame to TX data start */ +#define AR5K_PHY_RF_CTL2_TXF2TXD_START_S 0 + +#define AR5K_PHY_RF_CTL3 0x9828 /* Register Address */ +#define AR5K_PHY_RF_CTL3_TXE2XLNA_ON 0x0000ff00 /* TX end to XLNA on */ +#define AR5K_PHY_RF_CTL3_TXE2XLNA_ON_S 8 + +#define AR5K_PHY_ADC_CTL 0x982c +#define AR5K_PHY_ADC_CTL_INBUFGAIN_OFF 0x00000003 +#define AR5K_PHY_ADC_CTL_INBUFGAIN_OFF_S 0 +#define AR5K_PHY_ADC_CTL_PWD_DAC_OFF 0x00002000 +#define AR5K_PHY_ADC_CTL_PWD_BAND_GAP_OFF 0x00004000 +#define AR5K_PHY_ADC_CTL_PWD_ADC_OFF 0x00008000 +#define AR5K_PHY_ADC_CTL_INBUFGAIN_ON 0x00030000 +#define AR5K_PHY_ADC_CTL_INBUFGAIN_ON_S 16 + +#define AR5K_PHY_RF_CTL4 0x9834 /* Register Address */ +#define AR5K_PHY_RF_CTL4_TXF2XPA_A_ON 0x00000001 /* TX frame to XPA A on (field) */ +#define AR5K_PHY_RF_CTL4_TXF2XPA_B_ON 0x00000100 /* TX frame to XPA B on (field) */ +#define AR5K_PHY_RF_CTL4_TXE2XPA_A_OFF 0x00010000 /* TX end to XPA A off (field) */ +#define AR5K_PHY_RF_CTL4_TXE2XPA_B_OFF 0x01000000 /* TX end to XPA B off (field) */ + +/* + * Pre-Amplifier control register + * (XPA -> external pre-amplifier) + */ +#define AR5K_PHY_PA_CTL 0x9838 /* Register Address */ +#define AR5K_PHY_PA_CTL_XPA_A_HI 0x00000001 /* XPA A high (?) */ +#define AR5K_PHY_PA_CTL_XPA_B_HI 0x00000002 /* XPA B high (?) */ +#define AR5K_PHY_PA_CTL_XPA_A_EN 0x00000004 /* Enable XPA A */ +#define AR5K_PHY_PA_CTL_XPA_B_EN 0x00000008 /* Enable XPA B */ + +/* + * PHY settling register + */ +#define AR5K_PHY_SETTLING 0x9844 /* Register Address */ +#define AR5K_PHY_SETTLING_AGC 0x0000007f /* AGC settling time */ +#define AR5K_PHY_SETTLING_AGC_S 0 +#define AR5K_PHY_SETTLING_SWITCH 0x00003f80 /* Switch settling time */ +#define AR5K_PHY_SETTLING_SWITCH_S 7 + +/* + * PHY Gain registers + */ +#define AR5K_PHY_GAIN 0x9848 /* Register Address */ +#define AR5K_PHY_GAIN_TXRX_ATTEN 0x0003f000 /* TX-RX Attenuation */ +#define AR5K_PHY_GAIN_TXRX_ATTEN_S 12 +#define AR5K_PHY_GAIN_TXRX_RF_MAX 0x007c0000 +#define AR5K_PHY_GAIN_TXRX_RF_MAX_S 18 + +#define AR5K_PHY_GAIN_OFFSET 0x984c /* Register Address */ +#define AR5K_PHY_GAIN_OFFSET_RXTX_FLAG 0x00020000 /* RX-TX flag (?) */ + +/* + * Desired ADC/PGA size register + * (for more infos read ANI patent) + */ +#define AR5K_PHY_DESIRED_SIZE 0x9850 /* Register Address */ +#define AR5K_PHY_DESIRED_SIZE_ADC 0x000000ff /* ADC desired size */ +#define AR5K_PHY_DESIRED_SIZE_ADC_S 0 +#define AR5K_PHY_DESIRED_SIZE_PGA 0x0000ff00 /* PGA desired size */ +#define AR5K_PHY_DESIRED_SIZE_PGA_S 8 +#define AR5K_PHY_DESIRED_SIZE_TOT 0x0ff00000 /* Total desired size */ +#define AR5K_PHY_DESIRED_SIZE_TOT_S 20 + +/* + * PHY signal register + * (for more infos read ANI patent) + */ +#define AR5K_PHY_SIG 0x9858 /* Register Address */ +#define AR5K_PHY_SIG_FIRSTEP 0x0003f000 /* FIRSTEP */ +#define AR5K_PHY_SIG_FIRSTEP_S 12 +#define AR5K_PHY_SIG_FIRPWR 0x03fc0000 /* FIPWR */ +#define AR5K_PHY_SIG_FIRPWR_S 18 + +/* + * PHY coarse agility control register + * (for more infos read ANI patent) + */ +#define AR5K_PHY_AGCCOARSE 0x985c /* Register Address */ +#define AR5K_PHY_AGCCOARSE_LO 0x00007f80 /* AGC Coarse low */ +#define AR5K_PHY_AGCCOARSE_LO_S 7 +#define AR5K_PHY_AGCCOARSE_HI 0x003f8000 /* AGC Coarse high */ +#define AR5K_PHY_AGCCOARSE_HI_S 15 + +/* + * PHY agility control register + */ +#define AR5K_PHY_AGCCTL 0x9860 /* Register address */ +#define AR5K_PHY_AGCCTL_CAL 0x00000001 /* Enable PHY calibration */ +#define AR5K_PHY_AGCCTL_NF 0x00000002 /* Enable Noise Floor calibration */ +#define AR5K_PHY_AGCCTL_OFDM_DIV_DIS 0x00000008 /* Disable antenna diversity on OFDM modes */ +#define AR5K_PHY_AGCCTL_NF_EN 0x00008000 /* Enable nf calibration to happen (?) */ +#define AR5K_PHY_AGCTL_FLTR_CAL 0x00010000 /* Allow filter calibration (?) */ +#define AR5K_PHY_AGCCTL_NF_NOUPDATE 0x00020000 /* Don't update nf automatically */ + +/* + * PHY noise floor status register (CCA = Clear Channel Assessment) + */ +#define AR5K_PHY_NF 0x9864 /* Register address */ +#define AR5K_PHY_NF_M 0x000001ff /* Noise floor, written to hardware in 1/2 dBm units */ +#define AR5K_PHY_NF_SVAL(_n) (((_n) & AR5K_PHY_NF_M) | (1 << 9)) +#define AR5K_PHY_NF_THRESH62 0x0007f000 /* Thresh62 -check ANI patent- (field) */ +#define AR5K_PHY_NF_THRESH62_S 12 +#define AR5K_PHY_NF_MINCCA_PWR 0x0ff80000 /* Minimum measured noise level, read from hardware in 1 dBm units */ +#define AR5K_PHY_NF_MINCCA_PWR_S 19 + +/* + * PHY ADC saturation register [5110] + */ +#define AR5K_PHY_ADCSAT 0x9868 +#define AR5K_PHY_ADCSAT_ICNT 0x0001f800 +#define AR5K_PHY_ADCSAT_ICNT_S 11 +#define AR5K_PHY_ADCSAT_THR 0x000007e0 +#define AR5K_PHY_ADCSAT_THR_S 5 + +/* + * PHY Weak ofdm signal detection threshold registers (ANI) [5212+] + */ + +/* High thresholds */ +#define AR5K_PHY_WEAK_OFDM_HIGH_THR 0x9868 +#define AR5K_PHY_WEAK_OFDM_HIGH_THR_M2_COUNT 0x0000001f +#define AR5K_PHY_WEAK_OFDM_HIGH_THR_M2_COUNT_S 0 +#define AR5K_PHY_WEAK_OFDM_HIGH_THR_M1 0x00fe0000 +#define AR5K_PHY_WEAK_OFDM_HIGH_THR_M1_S 17 +#define AR5K_PHY_WEAK_OFDM_HIGH_THR_M2 0x7f000000 +#define AR5K_PHY_WEAK_OFDM_HIGH_THR_M2_S 24 + +/* Low thresholds */ +#define AR5K_PHY_WEAK_OFDM_LOW_THR 0x986c +#define AR5K_PHY_WEAK_OFDM_LOW_THR_SELFCOR_EN 0x00000001 +#define AR5K_PHY_WEAK_OFDM_LOW_THR_M2_COUNT 0x00003f00 +#define AR5K_PHY_WEAK_OFDM_LOW_THR_M2_COUNT_S 8 +#define AR5K_PHY_WEAK_OFDM_LOW_THR_M1 0x001fc000 +#define AR5K_PHY_WEAK_OFDM_LOW_THR_M1_S 14 +#define AR5K_PHY_WEAK_OFDM_LOW_THR_M2 0x0fe00000 +#define AR5K_PHY_WEAK_OFDM_LOW_THR_M2_S 21 + + +/* + * PHY sleep registers [5112+] + */ +#define AR5K_PHY_SCR 0x9870 + +#define AR5K_PHY_SLMT 0x9874 +#define AR5K_PHY_SLMT_32MHZ 0x0000007f + +#define AR5K_PHY_SCAL 0x9878 +#define AR5K_PHY_SCAL_32MHZ 0x0000000e +#define AR5K_PHY_SCAL_32MHZ_5311 0x00000008 +#define AR5K_PHY_SCAL_32MHZ_2417 0x0000000a +#define AR5K_PHY_SCAL_32MHZ_HB63 0x00000032 + +/* + * PHY PLL (Phase Locked Loop) control register + */ +#define AR5K_PHY_PLL 0x987c +#define AR5K_PHY_PLL_20MHZ 0x00000013 /* For half rate (?) */ +/* 40MHz -> 5GHz band */ +#define AR5K_PHY_PLL_40MHZ_5211 0x00000018 +#define AR5K_PHY_PLL_40MHZ_5212 0x000000aa +#define AR5K_PHY_PLL_40MHZ_5413 0x00000004 +#define AR5K_PHY_PLL_40MHZ (ah->ah_version == AR5K_AR5211 ? \ + AR5K_PHY_PLL_40MHZ_5211 : AR5K_PHY_PLL_40MHZ_5212) +/* 44MHz -> 2.4GHz band */ +#define AR5K_PHY_PLL_44MHZ_5211 0x00000019 +#define AR5K_PHY_PLL_44MHZ_5212 0x000000ab +#define AR5K_PHY_PLL_44MHZ (ah->ah_version == AR5K_AR5211 ? \ + AR5K_PHY_PLL_44MHZ_5211 : AR5K_PHY_PLL_44MHZ_5212) + +#define AR5K_PHY_PLL_RF5111 0x00000000 +#define AR5K_PHY_PLL_RF5112 0x00000040 +#define AR5K_PHY_PLL_HALF_RATE 0x00000100 +#define AR5K_PHY_PLL_QUARTER_RATE 0x00000200 + +/* + * RF Buffer register + * + * It's obvious from the code that 0x989c is the buffer register but + * for the other special registers that we write to after sending each + * packet, i have no idea. So I'll name them BUFFER_CONTROL_X registers + * for now. It's interesting that they are also used for some other operations. + */ + +#define AR5K_RF_BUFFER 0x989c +#define AR5K_RF_BUFFER_CONTROL_0 0x98c0 /* Channel on 5110 */ +#define AR5K_RF_BUFFER_CONTROL_1 0x98c4 /* Bank 7 on 5112 */ +#define AR5K_RF_BUFFER_CONTROL_2 0x98cc /* Bank 7 on 5111 */ + +#define AR5K_RF_BUFFER_CONTROL_3 0x98d0 /* Bank 2 on 5112 */ + /* Channel set on 5111 */ + /* Used to read radio revision*/ + +#define AR5K_RF_BUFFER_CONTROL_4 0x98d4 /* RF Stage register on 5110 */ + /* Bank 0,1,2,6 on 5111 */ + /* Bank 1 on 5112 */ + /* Used during activation on 5111 */ + +#define AR5K_RF_BUFFER_CONTROL_5 0x98d8 /* Bank 3 on 5111 */ + /* Used during activation on 5111 */ + /* Channel on 5112 */ + /* Bank 6 on 5112 */ + +#define AR5K_RF_BUFFER_CONTROL_6 0x98dc /* Bank 3 on 5112 */ + +/* + * PHY RF stage register [5210] + */ +#define AR5K_PHY_RFSTG 0x98d4 +#define AR5K_PHY_RFSTG_DISABLE 0x00000021 + +/* + * BIN masks (?) + */ +#define AR5K_PHY_BIN_MASK_1 0x9900 +#define AR5K_PHY_BIN_MASK_2 0x9904 +#define AR5K_PHY_BIN_MASK_3 0x9908 + +#define AR5K_PHY_BIN_MASK_CTL 0x990c +#define AR5K_PHY_BIN_MASK_CTL_MASK_4 0x00003fff +#define AR5K_PHY_BIN_MASK_CTL_MASK_4_S 0 +#define AR5K_PHY_BIN_MASK_CTL_RATE 0xff000000 +#define AR5K_PHY_BIN_MASK_CTL_RATE_S 24 + +/* + * PHY Antenna control register + */ +#define AR5K_PHY_ANT_CTL 0x9910 /* Register Address */ +#define AR5K_PHY_ANT_CTL_TXRX_EN 0x00000001 /* Enable TX/RX (?) */ +#define AR5K_PHY_ANT_CTL_SECTORED_ANT 0x00000004 /* Sectored Antenna */ +#define AR5K_PHY_ANT_CTL_HITUNE5 0x00000008 /* Hitune5 (?) */ +#define AR5K_PHY_ANT_CTL_SWTABLE_IDLE 0x000003f0 /* Switch table idle (?) */ +#define AR5K_PHY_ANT_CTL_SWTABLE_IDLE_S 4 + +/* + * PHY receiver delay register [5111+] + */ +#define AR5K_PHY_RX_DELAY 0x9914 /* Register Address */ +#define AR5K_PHY_RX_DELAY_M 0x00003fff /* Mask for RX activate to receive delay (/100ns) */ + +/* + * PHY max rx length register (?) [5111] + */ +#define AR5K_PHY_MAX_RX_LEN 0x991c + +/* + * PHY timing register 4 + * I(nphase)/Q(adrature) calibration register [5111+] + */ +#define AR5K_PHY_IQ 0x9920 /* Register Address */ +#define AR5K_PHY_IQ_CORR_Q_Q_COFF 0x0000001f /* Mask for q correction info */ +#define AR5K_PHY_IQ_CORR_Q_Q_COFF_S 0 +#define AR5K_PHY_IQ_CORR_Q_I_COFF 0x000007e0 /* Mask for i correction info */ +#define AR5K_PHY_IQ_CORR_Q_I_COFF_S 5 +#define AR5K_PHY_IQ_CORR_ENABLE 0x00000800 /* Enable i/q correction */ +#define AR5K_PHY_IQ_CAL_NUM_LOG_MAX 0x0000f000 /* Mask for max number of samples in log scale */ +#define AR5K_PHY_IQ_CAL_NUM_LOG_MAX_S 12 +#define AR5K_PHY_IQ_RUN 0x00010000 /* Run i/q calibration */ +#define AR5K_PHY_IQ_USE_PT_DF 0x00020000 /* Use pilot track df (?) */ +#define AR5K_PHY_IQ_EARLY_TRIG_THR 0x00200000 /* Early trigger threshold (?) (field) */ +#define AR5K_PHY_IQ_PILOT_MASK_EN 0x10000000 /* Enable pilot mask (?) */ +#define AR5K_PHY_IQ_CHAN_MASK_EN 0x20000000 /* Enable channel mask (?) */ +#define AR5K_PHY_IQ_SPUR_FILT_EN 0x40000000 /* Enable spur filter */ +#define AR5K_PHY_IQ_SPUR_RSSI_EN 0x80000000 /* Enable spur rssi */ + +/* + * PHY timing register 5 + * OFDM Self-correlator Cyclic RSSI threshold params + * (Check out bb_cycpwr_thr1 on ANI patent) + */ +#define AR5K_PHY_OFDM_SELFCORR 0x9924 /* Register Address */ +#define AR5K_PHY_OFDM_SELFCORR_CYPWR_THR1_EN 0x00000001 /* Enable cyclic RSSI thr 1 */ +#define AR5K_PHY_OFDM_SELFCORR_CYPWR_THR1 0x000000fe /* Mask for Cyclic RSSI threshold 1 */ +#define AR5K_PHY_OFDM_SELFCORR_CYPWR_THR1_S 1 +#define AR5K_PHY_OFDM_SELFCORR_CYPWR_THR3 0x00000100 /* Cyclic RSSI threshold 3 (field) (?) */ +#define AR5K_PHY_OFDM_SELFCORR_RSSI_1ATHR_EN 0x00008000 /* Enable 1A RSSI threshold (?) */ +#define AR5K_PHY_OFDM_SELFCORR_RSSI_1ATHR 0x00010000 /* 1A RSSI threshold (field) (?) */ +#define AR5K_PHY_OFDM_SELFCORR_LSCTHR_HIRSSI 0x00800000 /* Long sc threshold hi rssi (?) */ + +/* + * PHY-only warm reset register + */ +#define AR5K_PHY_WARM_RESET 0x9928 + +/* + * PHY-only control register + */ +#define AR5K_PHY_CTL 0x992c /* Register Address */ +#define AR5K_PHY_CTL_RX_DRAIN_RATE 0x00000001 /* RX drain rate (?) */ +#define AR5K_PHY_CTL_LATE_TX_SIG_SYM 0x00000002 /* Late tx signal symbol (?) */ +#define AR5K_PHY_CTL_GEN_SCRAMBLER 0x00000004 /* Generate scrambler */ +#define AR5K_PHY_CTL_TX_ANT_SEL 0x00000008 /* TX antenna select */ +#define AR5K_PHY_CTL_TX_ANT_STATIC 0x00000010 /* Static TX antenna */ +#define AR5K_PHY_CTL_RX_ANT_SEL 0x00000020 /* RX antenna select */ +#define AR5K_PHY_CTL_RX_ANT_STATIC 0x00000040 /* Static RX antenna */ +#define AR5K_PHY_CTL_LOW_FREQ_SLE_EN 0x00000080 /* Enable low freq sleep */ + +/* + * PHY PAPD probe register [5111+] + */ +#define AR5K_PHY_PAPD_PROBE 0x9930 +#define AR5K_PHY_PAPD_PROBE_SH_HI_PAR 0x00000001 +#define AR5K_PHY_PAPD_PROBE_PCDAC_BIAS 0x00000002 +#define AR5K_PHY_PAPD_PROBE_COMP_GAIN 0x00000040 +#define AR5K_PHY_PAPD_PROBE_TXPOWER 0x00007e00 +#define AR5K_PHY_PAPD_PROBE_TXPOWER_S 9 +#define AR5K_PHY_PAPD_PROBE_TX_NEXT 0x00008000 +#define AR5K_PHY_PAPD_PROBE_PREDIST_EN 0x00010000 +#define AR5K_PHY_PAPD_PROBE_TYPE 0x01800000 /* [5112+] */ +#define AR5K_PHY_PAPD_PROBE_TYPE_S 23 +#define AR5K_PHY_PAPD_PROBE_TYPE_OFDM 0 +#define AR5K_PHY_PAPD_PROBE_TYPE_XR 1 +#define AR5K_PHY_PAPD_PROBE_TYPE_CCK 2 +#define AR5K_PHY_PAPD_PROBE_GAINF 0xfe000000 +#define AR5K_PHY_PAPD_PROBE_GAINF_S 25 +#define AR5K_PHY_PAPD_PROBE_INI_5111 0x00004883 /* [5212+] */ +#define AR5K_PHY_PAPD_PROBE_INI_5112 0x00004882 /* [5212+] */ + +/* + * PHY TX rate power registers [5112+] + */ +#define AR5K_PHY_TXPOWER_RATE1 0x9934 +#define AR5K_PHY_TXPOWER_RATE2 0x9938 +#define AR5K_PHY_TXPOWER_RATE_MAX 0x993c +#define AR5K_PHY_TXPOWER_RATE_MAX_TPC_ENABLE 0x00000040 +#define AR5K_PHY_TXPOWER_RATE3 0xa234 +#define AR5K_PHY_TXPOWER_RATE4 0xa238 + +/* + * PHY frame control register [5111+] + */ +#define AR5K_PHY_FRAME_CTL_5210 0x9804 +#define AR5K_PHY_FRAME_CTL_5211 0x9944 +#define AR5K_PHY_FRAME_CTL (ah->ah_version == AR5K_AR5210 ? \ + AR5K_PHY_FRAME_CTL_5210 : AR5K_PHY_FRAME_CTL_5211) +/*---[5111+]---*/ +#define AR5K_PHY_FRAME_CTL_WIN_LEN 0x00000003 /* Force window length (?) */ +#define AR5K_PHY_FRAME_CTL_WIN_LEN_S 0 +#define AR5K_PHY_FRAME_CTL_TX_CLIP 0x00000038 /* Mask for tx clip (?) */ +#define AR5K_PHY_FRAME_CTL_TX_CLIP_S 3 +#define AR5K_PHY_FRAME_CTL_PREP_CHINFO 0x00010000 /* Prepend chan info */ +#define AR5K_PHY_FRAME_CTL_EMU 0x80000000 +#define AR5K_PHY_FRAME_CTL_EMU_S 31 +/*---[5110/5111]---*/ +#define AR5K_PHY_FRAME_CTL_TIMING_ERR 0x01000000 /* PHY timing error */ +#define AR5K_PHY_FRAME_CTL_PARITY_ERR 0x02000000 /* Parity error */ +#define AR5K_PHY_FRAME_CTL_ILLRATE_ERR 0x04000000 /* Illegal rate */ +#define AR5K_PHY_FRAME_CTL_ILLLEN_ERR 0x08000000 /* Illegal length */ +#define AR5K_PHY_FRAME_CTL_SERVICE_ERR 0x20000000 +#define AR5K_PHY_FRAME_CTL_TXURN_ERR 0x40000000 /* TX underrun */ +#define AR5K_PHY_FRAME_CTL_INI \ + (AR5K_PHY_FRAME_CTL_SERVICE_ERR | \ + AR5K_PHY_FRAME_CTL_TXURN_ERR | \ + AR5K_PHY_FRAME_CTL_ILLLEN_ERR | \ + AR5K_PHY_FRAME_CTL_ILLRATE_ERR | \ + AR5K_PHY_FRAME_CTL_PARITY_ERR | \ + AR5K_PHY_FRAME_CTL_TIMING_ERR) + +/* + * PHY Tx Power adjustment register [5212A+] + */ +#define AR5K_PHY_TX_PWR_ADJ 0x994c +#define AR5K_PHY_TX_PWR_ADJ_CCK_GAIN_DELTA 0x00000fc0 +#define AR5K_PHY_TX_PWR_ADJ_CCK_GAIN_DELTA_S 6 +#define AR5K_PHY_TX_PWR_ADJ_CCK_PCDAC_INDEX 0x00fc0000 +#define AR5K_PHY_TX_PWR_ADJ_CCK_PCDAC_INDEX_S 18 + +/* + * PHY radar detection register [5111+] + */ +#define AR5K_PHY_RADAR 0x9954 +#define AR5K_PHY_RADAR_ENABLE 0x00000001 +#define AR5K_PHY_RADAR_DISABLE 0x00000000 +#define AR5K_PHY_RADAR_INBANDTHR 0x0000003e /* Inband threshold + 5-bits, units unknown {0..31} + (? MHz ?) */ +#define AR5K_PHY_RADAR_INBANDTHR_S 1 + +#define AR5K_PHY_RADAR_PRSSI_THR 0x00000fc0 /* Pulse RSSI/SNR threshold + 6-bits, dBm range {0..63} + in dBm units. */ +#define AR5K_PHY_RADAR_PRSSI_THR_S 6 + +#define AR5K_PHY_RADAR_PHEIGHT_THR 0x0003f000 /* Pulse height threshold + 6-bits, dBm range {0..63} + in dBm units. */ +#define AR5K_PHY_RADAR_PHEIGHT_THR_S 12 + +#define AR5K_PHY_RADAR_RSSI_THR 0x00fc0000 /* Radar RSSI/SNR threshold. + 6-bits, dBm range {0..63} + in dBm units. */ +#define AR5K_PHY_RADAR_RSSI_THR_S 18 + +#define AR5K_PHY_RADAR_FIRPWR_THR 0x7f000000 /* Finite Impulse Response + filter power out threshold. + 7-bits, standard power range + {0..127} in 1/2 dBm units. */ +#define AR5K_PHY_RADAR_FIRPWR_THRS 24 + +/* + * PHY antenna switch table registers + */ +#define AR5K_PHY_ANT_SWITCH_TABLE_0 0x9960 +#define AR5K_PHY_ANT_SWITCH_TABLE_1 0x9964 + +/* + * PHY Noise floor threshold + */ +#define AR5K_PHY_NFTHRES 0x9968 + +/* + * Sigma Delta register (?) [5213] + */ +#define AR5K_PHY_SIGMA_DELTA 0x996C +#define AR5K_PHY_SIGMA_DELTA_ADC_SEL 0x00000003 +#define AR5K_PHY_SIGMA_DELTA_ADC_SEL_S 0 +#define AR5K_PHY_SIGMA_DELTA_FILT2 0x000000f8 +#define AR5K_PHY_SIGMA_DELTA_FILT2_S 3 +#define AR5K_PHY_SIGMA_DELTA_FILT1 0x00001f00 +#define AR5K_PHY_SIGMA_DELTA_FILT1_S 8 +#define AR5K_PHY_SIGMA_DELTA_ADC_CLIP 0x01ffe000 +#define AR5K_PHY_SIGMA_DELTA_ADC_CLIP_S 13 + +/* + * RF restart register [5112+] (?) + */ +#define AR5K_PHY_RESTART 0x9970 /* restart */ +#define AR5K_PHY_RESTART_DIV_GC 0x001c0000 /* Fast diversity gc_limit (?) */ +#define AR5K_PHY_RESTART_DIV_GC_S 18 + +/* + * RF Bus access request register (for synth-only channel switching) + */ +#define AR5K_PHY_RFBUS_REQ 0x997C +#define AR5K_PHY_RFBUS_REQ_REQUEST 0x00000001 + +/* + * Spur mitigation masks (?) + */ +#define AR5K_PHY_TIMING_7 0x9980 +#define AR5K_PHY_TIMING_8 0x9984 +#define AR5K_PHY_TIMING_8_PILOT_MASK_2 0x000fffff +#define AR5K_PHY_TIMING_8_PILOT_MASK_2_S 0 + +#define AR5K_PHY_BIN_MASK2_1 0x9988 +#define AR5K_PHY_BIN_MASK2_2 0x998c +#define AR5K_PHY_BIN_MASK2_3 0x9990 + +#define AR5K_PHY_BIN_MASK2_4 0x9994 +#define AR5K_PHY_BIN_MASK2_4_MASK_4 0x00003fff +#define AR5K_PHY_BIN_MASK2_4_MASK_4_S 0 + +#define AR5K_PHY_TIMING_9 0x9998 +#define AR5K_PHY_TIMING_10 0x999c +#define AR5K_PHY_TIMING_10_PILOT_MASK_2 0x000fffff +#define AR5K_PHY_TIMING_10_PILOT_MASK_2_S 0 + +/* + * Spur mitigation control + */ +#define AR5K_PHY_TIMING_11 0x99a0 /* Register address */ +#define AR5K_PHY_TIMING_11_SPUR_DELTA_PHASE 0x000fffff /* Spur delta phase */ +#define AR5K_PHY_TIMING_11_SPUR_DELTA_PHASE_S 0 +#define AR5K_PHY_TIMING_11_SPUR_FREQ_SD 0x3ff00000 /* Freq sigma delta */ +#define AR5K_PHY_TIMING_11_SPUR_FREQ_SD_S 20 +#define AR5K_PHY_TIMING_11_USE_SPUR_IN_AGC 0x40000000 /* Spur filter in AGC detector */ +#define AR5K_PHY_TIMING_11_USE_SPUR_IN_SELFCOR 0x80000000 /* Spur filter in OFDM self correlator */ + +/* + * Gain tables + */ +#define AR5K_BB_GAIN_BASE 0x9b00 /* BaseBand Amplifier Gain table base address */ +#define AR5K_BB_GAIN(_n) (AR5K_BB_GAIN_BASE + ((_n) << 2)) +#define AR5K_RF_GAIN_BASE 0x9a00 /* RF Amplifier Gain table base address */ +#define AR5K_RF_GAIN(_n) (AR5K_RF_GAIN_BASE + ((_n) << 2)) + +/* + * PHY timing IQ calibration result register [5111+] + */ +#define AR5K_PHY_IQRES_CAL_PWR_I 0x9c10 /* I (Inphase) power value */ +#define AR5K_PHY_IQRES_CAL_PWR_Q 0x9c14 /* Q (Quadrature) power value */ +#define AR5K_PHY_IQRES_CAL_CORR 0x9c18 /* I/Q Correlation */ + +/* + * PHY current RSSI register [5111+] + */ +#define AR5K_PHY_CURRENT_RSSI 0x9c1c + +/* + * PHY RF Bus grant register + */ +#define AR5K_PHY_RFBUS_GRANT 0x9c20 +#define AR5K_PHY_RFBUS_GRANT_OK 0x00000001 + +/* + * PHY ADC test register + */ +#define AR5K_PHY_ADC_TEST 0x9c24 +#define AR5K_PHY_ADC_TEST_I 0x00000001 +#define AR5K_PHY_ADC_TEST_Q 0x00000200 + +/* + * PHY DAC test register + */ +#define AR5K_PHY_DAC_TEST 0x9c28 +#define AR5K_PHY_DAC_TEST_I 0x00000001 +#define AR5K_PHY_DAC_TEST_Q 0x00000200 + +/* + * PHY PTAT register (?) + */ +#define AR5K_PHY_PTAT 0x9c2c + +/* + * PHY Illegal TX rate register [5112+] + */ +#define AR5K_PHY_BAD_TX_RATE 0x9c30 + +/* + * PHY SPUR Power register [5112+] + */ +#define AR5K_PHY_SPUR_PWR 0x9c34 /* Register Address */ +#define AR5K_PHY_SPUR_PWR_I 0x00000001 /* SPUR Power estimate for I (field) */ +#define AR5K_PHY_SPUR_PWR_Q 0x00000100 /* SPUR Power estimate for Q (field) */ +#define AR5K_PHY_SPUR_PWR_FILT 0x00010000 /* Power with SPUR removed (field) */ + +/* + * PHY Channel status register [5112+] (?) + */ +#define AR5K_PHY_CHAN_STATUS 0x9c38 +#define AR5K_PHY_CHAN_STATUS_BT_ACT 0x00000001 +#define AR5K_PHY_CHAN_STATUS_RX_CLR_RAW 0x00000002 +#define AR5K_PHY_CHAN_STATUS_RX_CLR_MAC 0x00000004 +#define AR5K_PHY_CHAN_STATUS_RX_CLR_PAP 0x00000008 + +/* + * Heavy clip enable register + */ +#define AR5K_PHY_HEAVY_CLIP_ENABLE 0x99e0 + +/* + * PHY clock sleep registers [5112+] + */ +#define AR5K_PHY_SCLOCK 0x99f0 +#define AR5K_PHY_SCLOCK_32MHZ 0x0000000c +#define AR5K_PHY_SDELAY 0x99f4 +#define AR5K_PHY_SDELAY_32MHZ 0x000000ff +#define AR5K_PHY_SPENDING 0x99f8 + + +/* + * PHY PAPD I (power?) table (?) + * (92! entries) + */ +#define AR5K_PHY_PAPD_I_BASE 0xa000 +#define AR5K_PHY_PAPD_I(_n) (AR5K_PHY_PAPD_I_BASE + ((_n) << 2)) + +/* + * PHY PCDAC TX power table + */ +#define AR5K_PHY_PCDAC_TXPOWER_BASE 0xa180 +#define AR5K_PHY_PCDAC_TXPOWER(_n) (AR5K_PHY_PCDAC_TXPOWER_BASE + ((_n) << 2)) + +/* + * PHY mode register [5111+] + */ +#define AR5K_PHY_MODE 0x0a200 /* Register Address */ +#define AR5K_PHY_MODE_MOD 0x00000001 /* PHY Modulation bit */ +#define AR5K_PHY_MODE_MOD_OFDM 0 +#define AR5K_PHY_MODE_MOD_CCK 1 +#define AR5K_PHY_MODE_FREQ 0x00000002 /* Freq mode bit */ +#define AR5K_PHY_MODE_FREQ_5GHZ 0 +#define AR5K_PHY_MODE_FREQ_2GHZ 2 +#define AR5K_PHY_MODE_MOD_DYN 0x00000004 /* Enable Dynamic OFDM/CCK mode [5112+] */ +#define AR5K_PHY_MODE_RAD 0x00000008 /* [5212+] */ +#define AR5K_PHY_MODE_RAD_RF5111 0 +#define AR5K_PHY_MODE_RAD_RF5112 8 +#define AR5K_PHY_MODE_XR 0x00000010 /* Enable XR mode [5112+] */ +#define AR5K_PHY_MODE_HALF_RATE 0x00000020 /* Enable Half rate (test) */ +#define AR5K_PHY_MODE_QUARTER_RATE 0x00000040 /* Enable Quarter rat (test) */ + +/* + * PHY CCK transmit control register [5111+ (?)] + */ +#define AR5K_PHY_CCKTXCTL 0xa204 +#define AR5K_PHY_CCKTXCTL_WORLD 0x00000000 +#define AR5K_PHY_CCKTXCTL_JAPAN 0x00000010 +#define AR5K_PHY_CCKTXCTL_SCRAMBLER_DIS 0x00000001 +#define AR5K_PHY_CCKTXCTK_DAC_SCALE 0x00000004 + +/* + * PHY CCK Cross-correlator Barker RSSI threshold register [5212+] + */ +#define AR5K_PHY_CCK_CROSSCORR 0xa208 +#define AR5K_PHY_CCK_CROSSCORR_WEAK_SIG_THR 0x0000003f +#define AR5K_PHY_CCK_CROSSCORR_WEAK_SIG_THR_S 0 + +/* Same address is used for antenna diversity activation */ +#define AR5K_PHY_FAST_ANT_DIV 0xa208 +#define AR5K_PHY_FAST_ANT_DIV_EN 0x00002000 + +/* + * PHY 2GHz gain register [5111+] + */ +#define AR5K_PHY_GAIN_2GHZ 0xa20c +#define AR5K_PHY_GAIN_2GHZ_MARGIN_TXRX 0x00fc0000 +#define AR5K_PHY_GAIN_2GHZ_MARGIN_TXRX_S 18 +#define AR5K_PHY_GAIN_2GHZ_INI_5111 0x6480416c + +#define AR5K_PHY_CCK_RX_CTL_4 0xa21c +#define AR5K_PHY_CCK_RX_CTL_4_FREQ_EST_SHORT 0x01f80000 +#define AR5K_PHY_CCK_RX_CTL_4_FREQ_EST_SHORT_S 19 + +#define AR5K_PHY_DAG_CCK_CTL 0xa228 +#define AR5K_PHY_DAG_CCK_CTL_EN_RSSI_THR 0x00000200 +#define AR5K_PHY_DAG_CCK_CTL_RSSI_THR 0x0001fc00 +#define AR5K_PHY_DAG_CCK_CTL_RSSI_THR_S 10 + +#define AR5K_PHY_FAST_ADC 0xa24c + +#define AR5K_PHY_BLUETOOTH 0xa254 + +/* + * Transmit Power Control register + * [2413+] + */ +#define AR5K_PHY_TPC_RG1 0xa258 +#define AR5K_PHY_TPC_RG1_NUM_PD_GAIN 0x0000c000 +#define AR5K_PHY_TPC_RG1_NUM_PD_GAIN_S 14 +#define AR5K_PHY_TPC_RG1_PDGAIN_1 0x00030000 +#define AR5K_PHY_TPC_RG1_PDGAIN_1_S 16 +#define AR5K_PHY_TPC_RG1_PDGAIN_2 0x000c0000 +#define AR5K_PHY_TPC_RG1_PDGAIN_2_S 18 +#define AR5K_PHY_TPC_RG1_PDGAIN_3 0x00300000 +#define AR5K_PHY_TPC_RG1_PDGAIN_3_S 20 + +#define AR5K_PHY_TPC_RG5 0xa26C +#define AR5K_PHY_TPC_RG5_PD_GAIN_OVERLAP 0x0000000F +#define AR5K_PHY_TPC_RG5_PD_GAIN_OVERLAP_S 0 +#define AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_1 0x000003F0 +#define AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_1_S 4 +#define AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_2 0x0000FC00 +#define AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_2_S 10 +#define AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_3 0x003F0000 +#define AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_3_S 16 +#define AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_4 0x0FC00000 +#define AR5K_PHY_TPC_RG5_PD_GAIN_BOUNDARY_4_S 22 + +/* + * PHY PDADC Tx power table + */ +#define AR5K_PHY_PDADC_TXPOWER_BASE 0xa280 +#define AR5K_PHY_PDADC_TXPOWER(_n) (AR5K_PHY_PDADC_TXPOWER_BASE + ((_n) << 2)) + +/* + * Platform registers for WiSoC + */ +#define AR5K_AR5312_RESET 0xbc003020 +#define AR5K_AR5312_RESET_BB0_COLD 0x00000004 +#define AR5K_AR5312_RESET_BB1_COLD 0x00000200 +#define AR5K_AR5312_RESET_WMAC0 0x00002000 +#define AR5K_AR5312_RESET_BB0_WARM 0x00004000 +#define AR5K_AR5312_RESET_WMAC1 0x00020000 +#define AR5K_AR5312_RESET_BB1_WARM 0x00040000 + +#define AR5K_AR5312_ENABLE 0xbc003080 +#define AR5K_AR5312_ENABLE_WLAN0 0x00000001 +#define AR5K_AR5312_ENABLE_WLAN1 0x00000008 + +#define AR5K_AR2315_RESET 0xb1000004 +#define AR5K_AR2315_RESET_WMAC 0x00000001 +#define AR5K_AR2315_RESET_BB_WARM 0x00000002 + +#define AR5K_AR2315_AHB_ARB_CTL 0xb1000008 +#define AR5K_AR2315_AHB_ARB_CTL_WLAN 0x00000002 + +#define AR5K_AR2315_BYTESWAP 0xb100000c +#define AR5K_AR2315_BYTESWAP_WMAC 0x00000002 diff --git a/drivers/net/wireless/ath/ath5k/reset.c b/drivers/net/wireless/ath/ath5k/reset.c new file mode 100644 index 0000000..a3399c4 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/reset.c @@ -0,0 +1,1404 @@ +/* + * Copyright (c) 2004-2008 Reyk Floeter + * Copyright (c) 2006-2008 Nick Kossifidis + * Copyright (c) 2007-2008 Luis Rodriguez + * Copyright (c) 2007-2008 Pavel Roskin + * Copyright (c) 2007-2008 Jiri Slaby + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/****************************\ + Reset function and helpers +\****************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include + +#include /* To determine if a card is pci-e */ +#include +#include +#include "ath5k.h" +#include "reg.h" +#include "debug.h" + + +/** + * DOC: Reset function and helpers + * + * Here we implement the main reset routine, used to bring the card + * to a working state and ready to receive. We also handle routines + * that don't fit on other places such as clock, sleep and power control + */ + + +/******************\ +* Helper functions * +\******************/ + +/** + * ath5k_hw_register_timeout() - Poll a register for a flag/field change + * @ah: The &struct ath5k_hw + * @reg: The register to read + * @flag: The flag/field to check on the register + * @val: The field value we expect (if we check a field) + * @is_set: Instead of checking if the flag got cleared, check if it got set + * + * Some registers contain flags that indicate that an operation is + * running. We use this function to poll these registers and check + * if these flags get cleared. We also use it to poll a register + * field (containing multiple flags) until it gets a specific value. + * + * Returns -EAGAIN if we exceeded AR5K_TUNE_REGISTER_TIMEOUT * 15us or 0 + */ +int +ath5k_hw_register_timeout(struct ath5k_hw *ah, u32 reg, u32 flag, u32 val, + bool is_set) +{ + int i; + u32 data; + + for (i = AR5K_TUNE_REGISTER_TIMEOUT; i > 0; i--) { + data = ath5k_hw_reg_read(ah, reg); + if (is_set && (data & flag)) + break; + else if ((data & flag) == val) + break; + udelay(15); + } + + return (i <= 0) ? -EAGAIN : 0; +} + + +/*************************\ +* Clock related functions * +\*************************/ + +/** + * ath5k_hw_htoclock() - Translate usec to hw clock units + * @ah: The &struct ath5k_hw + * @usec: value in microseconds + * + * Translate usecs to hw clock units based on the current + * hw clock rate. + * + * Returns number of clock units + */ +unsigned int +ath5k_hw_htoclock(struct ath5k_hw *ah, unsigned int usec) +{ + struct ath_common *common = ath5k_hw_common(ah); + return usec * common->clockrate; +} + +/** + * ath5k_hw_clocktoh() - Translate hw clock units to usec + * @ah: The &struct ath5k_hw + * @clock: value in hw clock units + * + * Translate hw clock units to usecs based on the current + * hw clock rate. + * + * Returns number of usecs + */ +unsigned int +ath5k_hw_clocktoh(struct ath5k_hw *ah, unsigned int clock) +{ + struct ath_common *common = ath5k_hw_common(ah); + return clock / common->clockrate; +} + +/** + * ath5k_hw_init_core_clock() - Initialize core clock + * @ah: The &struct ath5k_hw + * + * Initialize core clock parameters (usec, usec32, latencies etc), + * based on current bwmode and chipset properties. + */ +static void +ath5k_hw_init_core_clock(struct ath5k_hw *ah) +{ + struct ieee80211_channel *channel = ah->ah_current_channel; + struct ath_common *common = ath5k_hw_common(ah); + u32 usec_reg, txlat, rxlat, usec, clock, sclock, txf2txs; + + /* + * Set core clock frequency + */ + switch (channel->hw_value) { + case AR5K_MODE_11A: + clock = 40; + break; + case AR5K_MODE_11B: + clock = 22; + break; + case AR5K_MODE_11G: + default: + clock = 44; + break; + } + + /* Use clock multiplier for non-default + * bwmode */ + switch (ah->ah_bwmode) { + case AR5K_BWMODE_40MHZ: + clock *= 2; + break; + case AR5K_BWMODE_10MHZ: + clock /= 2; + break; + case AR5K_BWMODE_5MHZ: + clock /= 4; + break; + default: + break; + } + + common->clockrate = clock; + + /* + * Set USEC parameters + */ + /* Set USEC counter on PCU*/ + usec = clock - 1; + usec = AR5K_REG_SM(usec, AR5K_USEC_1); + + /* Set usec duration on DCU */ + if (ah->ah_version != AR5K_AR5210) + AR5K_REG_WRITE_BITS(ah, AR5K_DCU_GBL_IFS_MISC, + AR5K_DCU_GBL_IFS_MISC_USEC_DUR, + clock); + + /* Set 32MHz USEC counter */ + if ((ah->ah_radio == AR5K_RF5112) || + (ah->ah_radio == AR5K_RF2413) || + (ah->ah_radio == AR5K_RF5413) || + (ah->ah_radio == AR5K_RF2316) || + (ah->ah_radio == AR5K_RF2317)) + /* Remain on 40MHz clock ? */ + sclock = 40 - 1; + else + sclock = 32 - 1; + sclock = AR5K_REG_SM(sclock, AR5K_USEC_32); + + /* + * Set tx/rx latencies + */ + usec_reg = ath5k_hw_reg_read(ah, AR5K_USEC_5211); + txlat = AR5K_REG_MS(usec_reg, AR5K_USEC_TX_LATENCY_5211); + rxlat = AR5K_REG_MS(usec_reg, AR5K_USEC_RX_LATENCY_5211); + + /* + * Set default Tx frame to Tx data start delay + */ + txf2txs = AR5K_INIT_TXF2TXD_START_DEFAULT; + + /* + * 5210 initvals don't include usec settings + * so we need to use magic values here for + * tx/rx latencies + */ + if (ah->ah_version == AR5K_AR5210) { + /* same for turbo */ + txlat = AR5K_INIT_TX_LATENCY_5210; + rxlat = AR5K_INIT_RX_LATENCY_5210; + } + + if (ah->ah_mac_srev < AR5K_SREV_AR5211) { + /* 5311 has different tx/rx latency masks + * from 5211, since we deal 5311 the same + * as 5211 when setting initvals, shift + * values here to their proper locations + * + * Note: Initvals indicate tx/rx/ latencies + * are the same for turbo mode */ + txlat = AR5K_REG_SM(txlat, AR5K_USEC_TX_LATENCY_5210); + rxlat = AR5K_REG_SM(rxlat, AR5K_USEC_RX_LATENCY_5210); + } else + switch (ah->ah_bwmode) { + case AR5K_BWMODE_10MHZ: + txlat = AR5K_REG_SM(txlat * 2, + AR5K_USEC_TX_LATENCY_5211); + rxlat = AR5K_REG_SM(AR5K_INIT_RX_LAT_MAX, + AR5K_USEC_RX_LATENCY_5211); + txf2txs = AR5K_INIT_TXF2TXD_START_DELAY_10MHZ; + break; + case AR5K_BWMODE_5MHZ: + txlat = AR5K_REG_SM(txlat * 4, + AR5K_USEC_TX_LATENCY_5211); + rxlat = AR5K_REG_SM(AR5K_INIT_RX_LAT_MAX, + AR5K_USEC_RX_LATENCY_5211); + txf2txs = AR5K_INIT_TXF2TXD_START_DELAY_5MHZ; + break; + case AR5K_BWMODE_40MHZ: + txlat = AR5K_INIT_TX_LAT_MIN; + rxlat = AR5K_REG_SM(rxlat / 2, + AR5K_USEC_RX_LATENCY_5211); + txf2txs = AR5K_INIT_TXF2TXD_START_DEFAULT; + break; + default: + break; + } + + usec_reg = (usec | sclock | txlat | rxlat); + ath5k_hw_reg_write(ah, usec_reg, AR5K_USEC); + + /* On 5112 set tx frame to tx data start delay */ + if (ah->ah_radio == AR5K_RF5112) { + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_RF_CTL2, + AR5K_PHY_RF_CTL2_TXF2TXD_START, + txf2txs); + } +} + +/** + * ath5k_hw_set_sleep_clock() - Setup sleep clock operation + * @ah: The &struct ath5k_hw + * @enable: Enable sleep clock operation (false to disable) + * + * If there is an external 32KHz crystal available, use it + * as ref. clock instead of 32/40MHz clock and baseband clocks + * to save power during sleep or restore normal 32/40MHz + * operation. + * + * NOTE: When operating on 32KHz certain PHY registers (27 - 31, + * 123 - 127) require delay on access. + */ +static void +ath5k_hw_set_sleep_clock(struct ath5k_hw *ah, bool enable) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + u32 scal, spending, sclock; + + /* Only set 32KHz settings if we have an external + * 32KHz crystal present */ + if ((AR5K_EEPROM_HAS32KHZCRYSTAL(ee->ee_misc1) || + AR5K_EEPROM_HAS32KHZCRYSTAL_OLD(ee->ee_misc1)) && + enable) { + + /* 1 usec/cycle */ + AR5K_REG_WRITE_BITS(ah, AR5K_USEC_5211, AR5K_USEC_32, 1); + /* Set up tsf increment on each cycle */ + AR5K_REG_WRITE_BITS(ah, AR5K_TSF_PARM, AR5K_TSF_PARM_INC, 61); + + /* Set baseband sleep control registers + * and sleep control rate */ + ath5k_hw_reg_write(ah, 0x1f, AR5K_PHY_SCR); + + if ((ah->ah_radio == AR5K_RF5112) || + (ah->ah_radio == AR5K_RF5413) || + (ah->ah_radio == AR5K_RF2316) || + (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4))) + spending = 0x14; + else + spending = 0x18; + ath5k_hw_reg_write(ah, spending, AR5K_PHY_SPENDING); + + if ((ah->ah_radio == AR5K_RF5112) || + (ah->ah_radio == AR5K_RF5413) || + (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4))) { + ath5k_hw_reg_write(ah, 0x26, AR5K_PHY_SLMT); + ath5k_hw_reg_write(ah, 0x0d, AR5K_PHY_SCAL); + ath5k_hw_reg_write(ah, 0x07, AR5K_PHY_SCLOCK); + ath5k_hw_reg_write(ah, 0x3f, AR5K_PHY_SDELAY); + AR5K_REG_WRITE_BITS(ah, AR5K_PCICFG, + AR5K_PCICFG_SLEEP_CLOCK_RATE, 0x02); + } else { + ath5k_hw_reg_write(ah, 0x0a, AR5K_PHY_SLMT); + ath5k_hw_reg_write(ah, 0x0c, AR5K_PHY_SCAL); + ath5k_hw_reg_write(ah, 0x03, AR5K_PHY_SCLOCK); + ath5k_hw_reg_write(ah, 0x20, AR5K_PHY_SDELAY); + AR5K_REG_WRITE_BITS(ah, AR5K_PCICFG, + AR5K_PCICFG_SLEEP_CLOCK_RATE, 0x03); + } + + /* Enable sleep clock operation */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PCICFG, + AR5K_PCICFG_SLEEP_CLOCK_EN); + + } else { + + /* Disable sleep clock operation and + * restore default parameters */ + AR5K_REG_DISABLE_BITS(ah, AR5K_PCICFG, + AR5K_PCICFG_SLEEP_CLOCK_EN); + + AR5K_REG_WRITE_BITS(ah, AR5K_PCICFG, + AR5K_PCICFG_SLEEP_CLOCK_RATE, 0); + + /* Set DAC/ADC delays */ + ath5k_hw_reg_write(ah, 0x1f, AR5K_PHY_SCR); + ath5k_hw_reg_write(ah, AR5K_PHY_SLMT_32MHZ, AR5K_PHY_SLMT); + + if (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4)) + scal = AR5K_PHY_SCAL_32MHZ_2417; + else if (ee->ee_is_hb63) + scal = AR5K_PHY_SCAL_32MHZ_HB63; + else + scal = AR5K_PHY_SCAL_32MHZ; + ath5k_hw_reg_write(ah, scal, AR5K_PHY_SCAL); + + ath5k_hw_reg_write(ah, AR5K_PHY_SCLOCK_32MHZ, AR5K_PHY_SCLOCK); + ath5k_hw_reg_write(ah, AR5K_PHY_SDELAY_32MHZ, AR5K_PHY_SDELAY); + + if ((ah->ah_radio == AR5K_RF5112) || + (ah->ah_radio == AR5K_RF5413) || + (ah->ah_radio == AR5K_RF2316) || + (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4))) + spending = 0x14; + else + spending = 0x18; + ath5k_hw_reg_write(ah, spending, AR5K_PHY_SPENDING); + + /* Set up tsf increment on each cycle */ + AR5K_REG_WRITE_BITS(ah, AR5K_TSF_PARM, AR5K_TSF_PARM_INC, 1); + + if ((ah->ah_radio == AR5K_RF5112) || + (ah->ah_radio == AR5K_RF5413) || + (ah->ah_radio == AR5K_RF2316) || + (ah->ah_radio == AR5K_RF2317)) + sclock = 40 - 1; + else + sclock = 32 - 1; + AR5K_REG_WRITE_BITS(ah, AR5K_USEC_5211, AR5K_USEC_32, sclock); + } +} + + +/*********************\ +* Reset/Sleep control * +\*********************/ + +/** + * ath5k_hw_nic_reset() - Reset the various chipset units + * @ah: The &struct ath5k_hw + * @val: Mask to indicate what units to reset + * + * To reset the various chipset units we need to write + * the mask to AR5K_RESET_CTL and poll the register until + * all flags are cleared. + * + * Returns 0 if we are O.K. or -EAGAIN (from athk5_hw_register_timeout) + */ +static int +ath5k_hw_nic_reset(struct ath5k_hw *ah, u32 val) +{ + int ret; + u32 mask = val ? val : ~0U; + + /* Read-and-clear RX Descriptor Pointer*/ + ath5k_hw_reg_read(ah, AR5K_RXDP); + + /* + * Reset the device and wait until success + */ + ath5k_hw_reg_write(ah, val, AR5K_RESET_CTL); + + /* Wait at least 128 PCI clocks */ + usleep_range(15, 20); + + if (ah->ah_version == AR5K_AR5210) { + val &= AR5K_RESET_CTL_PCU | AR5K_RESET_CTL_DMA + | AR5K_RESET_CTL_MAC | AR5K_RESET_CTL_PHY; + mask &= AR5K_RESET_CTL_PCU | AR5K_RESET_CTL_DMA + | AR5K_RESET_CTL_MAC | AR5K_RESET_CTL_PHY; + } else { + val &= AR5K_RESET_CTL_PCU | AR5K_RESET_CTL_BASEBAND; + mask &= AR5K_RESET_CTL_PCU | AR5K_RESET_CTL_BASEBAND; + } + + ret = ath5k_hw_register_timeout(ah, AR5K_RESET_CTL, mask, val, false); + + /* + * Reset configuration register (for hw byte-swap). Note that this + * is only set for big endian. We do the necessary magic in + * AR5K_INIT_CFG. + */ + if ((val & AR5K_RESET_CTL_PCU) == 0) + ath5k_hw_reg_write(ah, AR5K_INIT_CFG, AR5K_CFG); + + return ret; +} + +/** + * ath5k_hw_wisoc_reset() - Reset AHB chipset + * @ah: The &struct ath5k_hw + * @flags: Mask to indicate what units to reset + * + * Same as ath5k_hw_nic_reset but for AHB based devices + * + * Returns 0 if we are O.K. or -EAGAIN (from athk5_hw_register_timeout) + */ +static int +ath5k_hw_wisoc_reset(struct ath5k_hw *ah, u32 flags) +{ + u32 mask = flags ? flags : ~0U; + u32 __iomem *reg; + u32 regval; + u32 val = 0; + + /* ah->ah_mac_srev is not available at this point yet */ + if (ah->devid >= AR5K_SREV_AR2315_R6) { + reg = (u32 __iomem *) AR5K_AR2315_RESET; + if (mask & AR5K_RESET_CTL_PCU) + val |= AR5K_AR2315_RESET_WMAC; + if (mask & AR5K_RESET_CTL_BASEBAND) + val |= AR5K_AR2315_RESET_BB_WARM; + } else { + reg = (u32 __iomem *) AR5K_AR5312_RESET; + if (to_platform_device(ah->dev)->id == 0) { + if (mask & AR5K_RESET_CTL_PCU) + val |= AR5K_AR5312_RESET_WMAC0; + if (mask & AR5K_RESET_CTL_BASEBAND) + val |= AR5K_AR5312_RESET_BB0_COLD | + AR5K_AR5312_RESET_BB0_WARM; + } else { + if (mask & AR5K_RESET_CTL_PCU) + val |= AR5K_AR5312_RESET_WMAC1; + if (mask & AR5K_RESET_CTL_BASEBAND) + val |= AR5K_AR5312_RESET_BB1_COLD | + AR5K_AR5312_RESET_BB1_WARM; + } + } + + /* Put BB/MAC into reset */ + regval = ioread32(reg); + iowrite32(regval | val, reg); + regval = ioread32(reg); + usleep_range(100, 150); + + /* Bring BB/MAC out of reset */ + iowrite32(regval & ~val, reg); + regval = ioread32(reg); + + /* + * Reset configuration register (for hw byte-swap). Note that this + * is only set for big endian. We do the necessary magic in + * AR5K_INIT_CFG. + */ + if ((flags & AR5K_RESET_CTL_PCU) == 0) + ath5k_hw_reg_write(ah, AR5K_INIT_CFG, AR5K_CFG); + + return 0; +} + +/** + * ath5k_hw_set_power_mode() - Set power mode + * @ah: The &struct ath5k_hw + * @mode: One of enum ath5k_power_mode + * @set_chip: Set to true to write sleep control register + * @sleep_duration: How much time the device is allowed to sleep + * when sleep logic is enabled (in 128 microsecond increments). + * + * This function is used to configure sleep policy and allowed + * sleep modes. For more information check out the sleep control + * register on reg.h and STA_ID1. + * + * Returns 0 on success, -EIO if chip didn't wake up or -EINVAL if an invalid + * mode is requested. + */ +static int +ath5k_hw_set_power_mode(struct ath5k_hw *ah, enum ath5k_power_mode mode, + bool set_chip, u16 sleep_duration) +{ + unsigned int i; + u32 staid, data; + + staid = ath5k_hw_reg_read(ah, AR5K_STA_ID1); + + switch (mode) { + case AR5K_PM_AUTO: + staid &= ~AR5K_STA_ID1_DEFAULT_ANTENNA; + /* fallthrough */ + case AR5K_PM_NETWORK_SLEEP: + if (set_chip) + ath5k_hw_reg_write(ah, + AR5K_SLEEP_CTL_SLE_ALLOW | + sleep_duration, + AR5K_SLEEP_CTL); + + staid |= AR5K_STA_ID1_PWR_SV; + break; + + case AR5K_PM_FULL_SLEEP: + if (set_chip) + ath5k_hw_reg_write(ah, AR5K_SLEEP_CTL_SLE_SLP, + AR5K_SLEEP_CTL); + + staid |= AR5K_STA_ID1_PWR_SV; + break; + + case AR5K_PM_AWAKE: + + staid &= ~AR5K_STA_ID1_PWR_SV; + + if (!set_chip) + goto commit; + + data = ath5k_hw_reg_read(ah, AR5K_SLEEP_CTL); + + /* If card is down we 'll get 0xffff... so we + * need to clean this up before we write the register + */ + if (data & 0xffc00000) + data = 0; + else + /* Preserve sleep duration etc */ + data = data & ~AR5K_SLEEP_CTL_SLE; + + ath5k_hw_reg_write(ah, data | AR5K_SLEEP_CTL_SLE_WAKE, + AR5K_SLEEP_CTL); + usleep_range(15, 20); + + for (i = 200; i > 0; i--) { + /* Check if the chip did wake up */ + if ((ath5k_hw_reg_read(ah, AR5K_PCICFG) & + AR5K_PCICFG_SPWR_DN) == 0) + break; + + /* Wait a bit and retry */ + usleep_range(50, 75); + ath5k_hw_reg_write(ah, data | AR5K_SLEEP_CTL_SLE_WAKE, + AR5K_SLEEP_CTL); + } + + /* Fail if the chip didn't wake up */ + if (i == 0) + return -EIO; + + break; + + default: + return -EINVAL; + } + +commit: + ath5k_hw_reg_write(ah, staid, AR5K_STA_ID1); + + return 0; +} + +/** + * ath5k_hw_on_hold() - Put device on hold + * @ah: The &struct ath5k_hw + * + * Put MAC and Baseband on warm reset and keep that state + * (don't clean sleep control register). After this MAC + * and Baseband are disabled and a full reset is needed + * to come back. This way we save as much power as possible + * without putting the card on full sleep. + * + * Returns 0 on success or -EIO on error + */ +int +ath5k_hw_on_hold(struct ath5k_hw *ah) +{ + struct pci_dev *pdev = ah->pdev; + u32 bus_flags; + int ret; + + if (ath5k_get_bus_type(ah) == ATH_AHB) + return 0; + + /* Make sure device is awake */ + ret = ath5k_hw_set_power_mode(ah, AR5K_PM_AWAKE, true, 0); + if (ret) { + ATH5K_ERR(ah, "failed to wakeup the MAC Chip\n"); + return ret; + } + + /* + * Put chipset on warm reset... + * + * Note: putting PCI core on warm reset on PCI-E cards + * results card to hang and always return 0xffff... so + * we ignore that flag for PCI-E cards. On PCI cards + * this flag gets cleared after 64 PCI clocks. + */ + bus_flags = (pdev && pci_is_pcie(pdev)) ? 0 : AR5K_RESET_CTL_PCI; + + if (ah->ah_version == AR5K_AR5210) { + ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU | + AR5K_RESET_CTL_MAC | AR5K_RESET_CTL_DMA | + AR5K_RESET_CTL_PHY | AR5K_RESET_CTL_PCI); + usleep_range(2000, 2500); + } else { + ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU | + AR5K_RESET_CTL_BASEBAND | bus_flags); + } + + if (ret) { + ATH5K_ERR(ah, "failed to put device on warm reset\n"); + return -EIO; + } + + /* ...wakeup again!*/ + ret = ath5k_hw_set_power_mode(ah, AR5K_PM_AWAKE, true, 0); + if (ret) { + ATH5K_ERR(ah, "failed to put device on hold\n"); + return ret; + } + + return ret; +} + +/** + * ath5k_hw_nic_wakeup() - Force card out of sleep + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + * + * Bring up MAC + PHY Chips and program PLL + * NOTE: Channel is NULL for the initial wakeup. + * + * Returns 0 on success, -EIO on hw failure or -EINVAL for false channel infos + */ +int +ath5k_hw_nic_wakeup(struct ath5k_hw *ah, struct ieee80211_channel *channel) +{ + struct pci_dev *pdev = ah->pdev; + u32 turbo, mode, clock, bus_flags; + int ret; + + turbo = 0; + mode = 0; + clock = 0; + + if ((ath5k_get_bus_type(ah) != ATH_AHB) || channel) { + /* Wakeup the device */ + ret = ath5k_hw_set_power_mode(ah, AR5K_PM_AWAKE, true, 0); + if (ret) { + ATH5K_ERR(ah, "failed to wakeup the MAC Chip\n"); + return ret; + } + } + + /* + * Put chipset on warm reset... + * + * Note: putting PCI core on warm reset on PCI-E cards + * results card to hang and always return 0xffff... so + * we ignore that flag for PCI-E cards. On PCI cards + * this flag gets cleared after 64 PCI clocks. + */ + bus_flags = (pdev && pci_is_pcie(pdev)) ? 0 : AR5K_RESET_CTL_PCI; + + if (ah->ah_version == AR5K_AR5210) { + ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU | + AR5K_RESET_CTL_MAC | AR5K_RESET_CTL_DMA | + AR5K_RESET_CTL_PHY | AR5K_RESET_CTL_PCI); + usleep_range(2000, 2500); + } else { + if (ath5k_get_bus_type(ah) == ATH_AHB) + ret = ath5k_hw_wisoc_reset(ah, AR5K_RESET_CTL_PCU | + AR5K_RESET_CTL_BASEBAND); + else + ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU | + AR5K_RESET_CTL_BASEBAND | bus_flags); + } + + if (ret) { + ATH5K_ERR(ah, "failed to reset the MAC Chip\n"); + return -EIO; + } + + /* ...wakeup again!...*/ + ret = ath5k_hw_set_power_mode(ah, AR5K_PM_AWAKE, true, 0); + if (ret) { + ATH5K_ERR(ah, "failed to resume the MAC Chip\n"); + return ret; + } + + /* ...reset configuration register on Wisoc ... + * ...clear reset control register and pull device out of + * warm reset on others */ + if (ath5k_get_bus_type(ah) == ATH_AHB) + ret = ath5k_hw_wisoc_reset(ah, 0); + else + ret = ath5k_hw_nic_reset(ah, 0); + + if (ret) { + ATH5K_ERR(ah, "failed to warm reset the MAC Chip\n"); + return -EIO; + } + + /* On initialization skip PLL programming since we don't have + * a channel / mode set yet */ + if (!channel) + return 0; + + if (ah->ah_version != AR5K_AR5210) { + /* + * Get channel mode flags + */ + + if (ah->ah_radio >= AR5K_RF5112) { + mode = AR5K_PHY_MODE_RAD_RF5112; + clock = AR5K_PHY_PLL_RF5112; + } else { + mode = AR5K_PHY_MODE_RAD_RF5111; /*Zero*/ + clock = AR5K_PHY_PLL_RF5111; /*Zero*/ + } + + if (channel->band == IEEE80211_BAND_2GHZ) { + mode |= AR5K_PHY_MODE_FREQ_2GHZ; + clock |= AR5K_PHY_PLL_44MHZ; + + if (channel->hw_value == AR5K_MODE_11B) { + mode |= AR5K_PHY_MODE_MOD_CCK; + } else { + /* XXX Dynamic OFDM/CCK is not supported by the + * AR5211 so we set MOD_OFDM for plain g (no + * CCK headers) operation. We need to test + * this, 5211 might support ofdm-only g after + * all, there are also initial register values + * in the code for g mode (see initvals.c). + */ + if (ah->ah_version == AR5K_AR5211) + mode |= AR5K_PHY_MODE_MOD_OFDM; + else + mode |= AR5K_PHY_MODE_MOD_DYN; + } + } else if (channel->band == IEEE80211_BAND_5GHZ) { + mode |= (AR5K_PHY_MODE_FREQ_5GHZ | + AR5K_PHY_MODE_MOD_OFDM); + + /* Different PLL setting for 5413 */ + if (ah->ah_radio == AR5K_RF5413) + clock = AR5K_PHY_PLL_40MHZ_5413; + else + clock |= AR5K_PHY_PLL_40MHZ; + } else { + ATH5K_ERR(ah, "invalid radio frequency mode\n"); + return -EINVAL; + } + + /*XXX: Can bwmode be used with dynamic mode ? + * (I don't think it supports 44MHz) */ + /* On 2425 initvals TURBO_SHORT is not present */ + if (ah->ah_bwmode == AR5K_BWMODE_40MHZ) { + turbo = AR5K_PHY_TURBO_MODE; + if (ah->ah_radio != AR5K_RF2425) + turbo |= AR5K_PHY_TURBO_SHORT; + } else if (ah->ah_bwmode != AR5K_BWMODE_DEFAULT) { + if (ah->ah_radio == AR5K_RF5413) { + mode |= (ah->ah_bwmode == AR5K_BWMODE_10MHZ) ? + AR5K_PHY_MODE_HALF_RATE : + AR5K_PHY_MODE_QUARTER_RATE; + } else if (ah->ah_version == AR5K_AR5212) { + clock |= (ah->ah_bwmode == AR5K_BWMODE_10MHZ) ? + AR5K_PHY_PLL_HALF_RATE : + AR5K_PHY_PLL_QUARTER_RATE; + } + } + + } else { /* Reset the device */ + + /* ...enable Atheros turbo mode if requested */ + if (ah->ah_bwmode == AR5K_BWMODE_40MHZ) + ath5k_hw_reg_write(ah, AR5K_PHY_TURBO_MODE, + AR5K_PHY_TURBO); + } + + if (ah->ah_version != AR5K_AR5210) { + + /* ...update PLL if needed */ + if (ath5k_hw_reg_read(ah, AR5K_PHY_PLL) != clock) { + ath5k_hw_reg_write(ah, clock, AR5K_PHY_PLL); + usleep_range(300, 350); + } + + /* ...set the PHY operating mode */ + ath5k_hw_reg_write(ah, mode, AR5K_PHY_MODE); + ath5k_hw_reg_write(ah, turbo, AR5K_PHY_TURBO); + } + + return 0; +} + + +/**************************************\ +* Post-initvals register modifications * +\**************************************/ + +/** + * ath5k_hw_tweak_initval_settings() - Tweak initial settings + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + * + * Some settings are not handled on initvals, e.g. bwmode + * settings, some phy settings, workarounds etc that in general + * don't fit anywhere else or are too small to introduce a separate + * function for each one. So we have this function to handle + * them all during reset and complete card's initialization. + */ +static void +ath5k_hw_tweak_initval_settings(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + if (ah->ah_version == AR5K_AR5212 && + ah->ah_phy_revision >= AR5K_SREV_PHY_5212A) { + + /* Setup ADC control */ + ath5k_hw_reg_write(ah, + (AR5K_REG_SM(2, + AR5K_PHY_ADC_CTL_INBUFGAIN_OFF) | + AR5K_REG_SM(2, + AR5K_PHY_ADC_CTL_INBUFGAIN_ON) | + AR5K_PHY_ADC_CTL_PWD_DAC_OFF | + AR5K_PHY_ADC_CTL_PWD_ADC_OFF), + AR5K_PHY_ADC_CTL); + + + + /* Disable barker RSSI threshold */ + AR5K_REG_DISABLE_BITS(ah, AR5K_PHY_DAG_CCK_CTL, + AR5K_PHY_DAG_CCK_CTL_EN_RSSI_THR); + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_DAG_CCK_CTL, + AR5K_PHY_DAG_CCK_CTL_RSSI_THR, 2); + + /* Set the mute mask */ + ath5k_hw_reg_write(ah, 0x0000000f, AR5K_SEQ_MASK); + } + + /* Clear PHY_BLUETOOTH to allow RX_CLEAR line debug */ + if (ah->ah_phy_revision >= AR5K_SREV_PHY_5212B) + ath5k_hw_reg_write(ah, 0, AR5K_PHY_BLUETOOTH); + + /* Enable DCU double buffering */ + if (ah->ah_phy_revision > AR5K_SREV_PHY_5212B) + AR5K_REG_DISABLE_BITS(ah, AR5K_TXCFG, + AR5K_TXCFG_DCU_DBL_BUF_DIS); + + /* Set fast ADC */ + if ((ah->ah_radio == AR5K_RF5413) || + (ah->ah_radio == AR5K_RF2317) || + (ah->ah_mac_version == (AR5K_SREV_AR2417 >> 4))) { + u32 fast_adc = true; + + if (channel->center_freq == 2462 || + channel->center_freq == 2467) + fast_adc = 0; + + /* Only update if needed */ + if (ath5k_hw_reg_read(ah, AR5K_PHY_FAST_ADC) != fast_adc) + ath5k_hw_reg_write(ah, fast_adc, + AR5K_PHY_FAST_ADC); + } + + /* Fix for first revision of the RF5112 RF chipset */ + if (ah->ah_radio == AR5K_RF5112 && + ah->ah_radio_5ghz_revision < + AR5K_SREV_RAD_5112A) { + u32 data; + ath5k_hw_reg_write(ah, AR5K_PHY_CCKTXCTL_WORLD, + AR5K_PHY_CCKTXCTL); + if (channel->band == IEEE80211_BAND_5GHZ) + data = 0xffb81020; + else + data = 0xffb80d20; + ath5k_hw_reg_write(ah, data, AR5K_PHY_FRAME_CTL); + } + + if (ah->ah_mac_srev < AR5K_SREV_AR5211) { + /* Clear QCU/DCU clock gating register */ + ath5k_hw_reg_write(ah, 0, AR5K_QCUDCU_CLKGT); + /* Set DAC/ADC delays */ + ath5k_hw_reg_write(ah, AR5K_PHY_SCAL_32MHZ_5311, + AR5K_PHY_SCAL); + /* Enable PCU FIFO corruption ECO */ + AR5K_REG_ENABLE_BITS(ah, AR5K_DIAG_SW_5211, + AR5K_DIAG_SW_ECO_ENABLE); + } + + if (ah->ah_bwmode) { + /* Increase PHY switch and AGC settling time + * on turbo mode (ath5k_hw_commit_eeprom_settings + * will override settling time if available) */ + if (ah->ah_bwmode == AR5K_BWMODE_40MHZ) { + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_SETTLING, + AR5K_PHY_SETTLING_AGC, + AR5K_AGC_SETTLING_TURBO); + + /* XXX: Initvals indicate we only increase + * switch time on AR5212, 5211 and 5210 + * only change agc time (bug?) */ + if (ah->ah_version == AR5K_AR5212) + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_SETTLING, + AR5K_PHY_SETTLING_SWITCH, + AR5K_SWITCH_SETTLING_TURBO); + + if (ah->ah_version == AR5K_AR5210) { + /* Set Frame Control Register */ + ath5k_hw_reg_write(ah, + (AR5K_PHY_FRAME_CTL_INI | + AR5K_PHY_TURBO_MODE | + AR5K_PHY_TURBO_SHORT | 0x2020), + AR5K_PHY_FRAME_CTL_5210); + } + /* On 5413 PHY force window length for half/quarter rate*/ + } else if ((ah->ah_mac_srev >= AR5K_SREV_AR5424) && + (ah->ah_mac_srev <= AR5K_SREV_AR5414)) { + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_FRAME_CTL_5211, + AR5K_PHY_FRAME_CTL_WIN_LEN, + 3); + } + } else if (ah->ah_version == AR5K_AR5210) { + /* Set Frame Control Register for normal operation */ + ath5k_hw_reg_write(ah, (AR5K_PHY_FRAME_CTL_INI | 0x1020), + AR5K_PHY_FRAME_CTL_5210); + } +} + +/** + * ath5k_hw_commit_eeprom_settings() - Commit settings from EEPROM + * @ah: The &struct ath5k_hw + * @channel: The &struct ieee80211_channel + * + * Use settings stored on EEPROM to properly initialize the card + * based on various infos and per-mode calibration data. + */ +static void +ath5k_hw_commit_eeprom_settings(struct ath5k_hw *ah, + struct ieee80211_channel *channel) +{ + struct ath5k_eeprom_info *ee = &ah->ah_capabilities.cap_eeprom; + s16 cck_ofdm_pwr_delta; + u8 ee_mode; + + /* TODO: Add support for AR5210 EEPROM */ + if (ah->ah_version == AR5K_AR5210) + return; + + ee_mode = ath5k_eeprom_mode_from_channel(ah, channel); + + /* Adjust power delta for channel 14 */ + if (channel->center_freq == 2484) + cck_ofdm_pwr_delta = + ((ee->ee_cck_ofdm_power_delta - + ee->ee_scaled_cck_delta) * 2) / 10; + else + cck_ofdm_pwr_delta = + (ee->ee_cck_ofdm_power_delta * 2) / 10; + + /* Set CCK to OFDM power delta on tx power + * adjustment register */ + if (ah->ah_phy_revision >= AR5K_SREV_PHY_5212A) { + if (channel->hw_value == AR5K_MODE_11G) + ath5k_hw_reg_write(ah, + AR5K_REG_SM((ee->ee_cck_ofdm_gain_delta * -1), + AR5K_PHY_TX_PWR_ADJ_CCK_GAIN_DELTA) | + AR5K_REG_SM((cck_ofdm_pwr_delta * -1), + AR5K_PHY_TX_PWR_ADJ_CCK_PCDAC_INDEX), + AR5K_PHY_TX_PWR_ADJ); + else + ath5k_hw_reg_write(ah, 0, AR5K_PHY_TX_PWR_ADJ); + } else { + /* For older revs we scale power on sw during tx power + * setup */ + ah->ah_txpower.txp_cck_ofdm_pwr_delta = cck_ofdm_pwr_delta; + ah->ah_txpower.txp_cck_ofdm_gainf_delta = + ee->ee_cck_ofdm_gain_delta; + } + + /* XXX: necessary here? is called from ath5k_hw_set_antenna_mode() + * too */ + ath5k_hw_set_antenna_switch(ah, ee_mode); + + /* Noise floor threshold */ + ath5k_hw_reg_write(ah, + AR5K_PHY_NF_SVAL(ee->ee_noise_floor_thr[ee_mode]), + AR5K_PHY_NFTHRES); + + if ((ah->ah_bwmode == AR5K_BWMODE_40MHZ) && + (ah->ah_ee_version >= AR5K_EEPROM_VERSION_5_0)) { + /* Switch settling time (Turbo) */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_SETTLING, + AR5K_PHY_SETTLING_SWITCH, + ee->ee_switch_settling_turbo[ee_mode]); + + /* Tx/Rx attenuation (Turbo) */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_GAIN, + AR5K_PHY_GAIN_TXRX_ATTEN, + ee->ee_atn_tx_rx_turbo[ee_mode]); + + /* ADC/PGA desired size (Turbo) */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_DESIRED_SIZE, + AR5K_PHY_DESIRED_SIZE_ADC, + ee->ee_adc_desired_size_turbo[ee_mode]); + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_DESIRED_SIZE, + AR5K_PHY_DESIRED_SIZE_PGA, + ee->ee_pga_desired_size_turbo[ee_mode]); + + /* Tx/Rx margin (Turbo) */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_GAIN_2GHZ, + AR5K_PHY_GAIN_2GHZ_MARGIN_TXRX, + ee->ee_margin_tx_rx_turbo[ee_mode]); + + } else { + /* Switch settling time */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_SETTLING, + AR5K_PHY_SETTLING_SWITCH, + ee->ee_switch_settling[ee_mode]); + + /* Tx/Rx attenuation */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_GAIN, + AR5K_PHY_GAIN_TXRX_ATTEN, + ee->ee_atn_tx_rx[ee_mode]); + + /* ADC/PGA desired size */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_DESIRED_SIZE, + AR5K_PHY_DESIRED_SIZE_ADC, + ee->ee_adc_desired_size[ee_mode]); + + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_DESIRED_SIZE, + AR5K_PHY_DESIRED_SIZE_PGA, + ee->ee_pga_desired_size[ee_mode]); + + /* Tx/Rx margin */ + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_1) + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_GAIN_2GHZ, + AR5K_PHY_GAIN_2GHZ_MARGIN_TXRX, + ee->ee_margin_tx_rx[ee_mode]); + } + + /* XPA delays */ + ath5k_hw_reg_write(ah, + (ee->ee_tx_end2xpa_disable[ee_mode] << 24) | + (ee->ee_tx_end2xpa_disable[ee_mode] << 16) | + (ee->ee_tx_frm2xpa_enable[ee_mode] << 8) | + (ee->ee_tx_frm2xpa_enable[ee_mode]), AR5K_PHY_RF_CTL4); + + /* XLNA delay */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_RF_CTL3, + AR5K_PHY_RF_CTL3_TXE2XLNA_ON, + ee->ee_tx_end2xlna_enable[ee_mode]); + + /* Thresh64 (ANI) */ + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_NF, + AR5K_PHY_NF_THRESH62, + ee->ee_thr_62[ee_mode]); + + /* False detect backoff for channels + * that have spur noise. Write the new + * cyclic power RSSI threshold. */ + if (ath5k_hw_chan_has_spur_noise(ah, channel)) + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_OFDM_SELFCORR, + AR5K_PHY_OFDM_SELFCORR_CYPWR_THR1, + AR5K_INIT_CYCRSSI_THR1 + + ee->ee_false_detect[ee_mode]); + else + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_OFDM_SELFCORR, + AR5K_PHY_OFDM_SELFCORR_CYPWR_THR1, + AR5K_INIT_CYCRSSI_THR1); + + /* I/Q correction (set enable bit last to match HAL sources) */ + /* TODO: Per channel i/q infos ? */ + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_4_0) { + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_IQ, AR5K_PHY_IQ_CORR_Q_I_COFF, + ee->ee_i_cal[ee_mode]); + AR5K_REG_WRITE_BITS(ah, AR5K_PHY_IQ, AR5K_PHY_IQ_CORR_Q_Q_COFF, + ee->ee_q_cal[ee_mode]); + AR5K_REG_ENABLE_BITS(ah, AR5K_PHY_IQ, AR5K_PHY_IQ_CORR_ENABLE); + } + + /* Heavy clipping -disable for now */ + if (ah->ah_ee_version >= AR5K_EEPROM_VERSION_5_1) + ath5k_hw_reg_write(ah, 0, AR5K_PHY_HEAVY_CLIP_ENABLE); +} + + +/*********************\ +* Main reset function * +\*********************/ + +/** + * ath5k_hw_reset() - The main reset function + * @ah: The &struct ath5k_hw + * @op_mode: One of enum nl80211_iftype + * @channel: The &struct ieee80211_channel + * @fast: Enable fast channel switching + * @skip_pcu: Skip pcu initialization + * + * This is the function we call each time we want to (re)initialize the + * card and pass new settings to hw. We also call it when hw runs into + * trouble to make it come back to a working state. + * + * Returns 0 on success, -EINVAL on false op_mode or channel infos, or -EIO + * on failure. + */ +int +ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode, + struct ieee80211_channel *channel, bool fast, bool skip_pcu) +{ + u32 s_seq[10], s_led[3], tsf_up, tsf_lo; + u8 mode; + int i, ret; + + tsf_up = 0; + tsf_lo = 0; + mode = 0; + + /* + * Sanity check for fast flag + * Fast channel change only available + * on AR2413/AR5413. + */ + if (fast && (ah->ah_radio != AR5K_RF2413) && + (ah->ah_radio != AR5K_RF5413)) + fast = false; + + /* Disable sleep clock operation + * to avoid register access delay on certain + * PHY registers */ + if (ah->ah_version == AR5K_AR5212) + ath5k_hw_set_sleep_clock(ah, false); + + /* + * Stop PCU + */ + ath5k_hw_stop_rx_pcu(ah); + + /* + * Stop DMA + * + * Note: If DMA didn't stop continue + * since only a reset will fix it. + */ + ret = ath5k_hw_dma_stop(ah); + + /* RF Bus grant won't work if we have pending + * frames */ + if (ret && fast) { + ATH5K_DBG(ah, ATH5K_DEBUG_RESET, + "DMA didn't stop, falling back to normal reset\n"); + fast = false; + /* Non fatal, just continue with + * normal reset */ + ret = 0; + } + + mode = channel->hw_value; + switch (mode) { + case AR5K_MODE_11A: + break; + case AR5K_MODE_11G: + if (ah->ah_version <= AR5K_AR5211) { + ATH5K_ERR(ah, + "G mode not available on 5210/5211"); + return -EINVAL; + } + break; + case AR5K_MODE_11B: + if (ah->ah_version < AR5K_AR5211) { + ATH5K_ERR(ah, + "B mode not available on 5210"); + return -EINVAL; + } + break; + default: + ATH5K_ERR(ah, + "invalid channel: %d\n", channel->center_freq); + return -EINVAL; + } + + /* + * If driver requested fast channel change and DMA has stopped + * go on. If it fails continue with a normal reset. + */ + if (fast) { + ret = ath5k_hw_phy_init(ah, channel, mode, true); + if (ret) { + ATH5K_DBG(ah, ATH5K_DEBUG_RESET, + "fast chan change failed, falling back to normal reset\n"); + /* Non fatal, can happen eg. + * on mode change */ + ret = 0; + } else { + ATH5K_DBG(ah, ATH5K_DEBUG_RESET, + "fast chan change successful\n"); + return 0; + } + } + + /* + * Save some registers before a reset + */ + if (ah->ah_version != AR5K_AR5210) { + /* + * Save frame sequence count + * For revs. after Oahu, only save + * seq num for DCU 0 (Global seq num) + */ + if (ah->ah_mac_srev < AR5K_SREV_AR5211) { + + for (i = 0; i < 10; i++) + s_seq[i] = ath5k_hw_reg_read(ah, + AR5K_QUEUE_DCU_SEQNUM(i)); + + } else { + s_seq[0] = ath5k_hw_reg_read(ah, + AR5K_QUEUE_DCU_SEQNUM(0)); + } + + /* TSF accelerates on AR5211 during reset + * As a workaround save it here and restore + * it later so that it's back in time after + * reset. This way it'll get re-synced on the + * next beacon without breaking ad-hoc. + * + * On AR5212 TSF is almost preserved across a + * reset so it stays back in time anyway and + * we don't have to save/restore it. + * + * XXX: Since this breaks power saving we have + * to disable power saving until we receive the + * next beacon, so we can resync beacon timers */ + if (ah->ah_version == AR5K_AR5211) { + tsf_up = ath5k_hw_reg_read(ah, AR5K_TSF_U32); + tsf_lo = ath5k_hw_reg_read(ah, AR5K_TSF_L32); + } + } + + + /*GPIOs*/ + s_led[0] = ath5k_hw_reg_read(ah, AR5K_PCICFG) & + AR5K_PCICFG_LEDSTATE; + s_led[1] = ath5k_hw_reg_read(ah, AR5K_GPIOCR); + s_led[2] = ath5k_hw_reg_read(ah, AR5K_GPIODO); + + + /* + * Since we are going to write rf buffer + * check if we have any pending gain_F + * optimization settings + */ + if (ah->ah_version == AR5K_AR5212 && + (ah->ah_radio <= AR5K_RF5112)) { + if (!fast && ah->ah_rf_banks != NULL) + ath5k_hw_gainf_calibrate(ah); + } + + /* Wakeup the device */ + ret = ath5k_hw_nic_wakeup(ah, channel); + if (ret) + return ret; + + /* PHY access enable */ + if (ah->ah_mac_srev >= AR5K_SREV_AR5211) + ath5k_hw_reg_write(ah, AR5K_PHY_SHIFT_5GHZ, AR5K_PHY(0)); + else + ath5k_hw_reg_write(ah, AR5K_PHY_SHIFT_5GHZ | 0x40, + AR5K_PHY(0)); + + /* Write initial settings */ + ret = ath5k_hw_write_initvals(ah, mode, skip_pcu); + if (ret) + return ret; + + /* Initialize core clock settings */ + ath5k_hw_init_core_clock(ah); + + /* + * Tweak initval settings for revised + * chipsets and add some more config + * bits + */ + ath5k_hw_tweak_initval_settings(ah, channel); + + /* Commit values from EEPROM */ + ath5k_hw_commit_eeprom_settings(ah, channel); + + + /* + * Restore saved values + */ + + /* Seqnum, TSF */ + if (ah->ah_version != AR5K_AR5210) { + if (ah->ah_mac_srev < AR5K_SREV_AR5211) { + for (i = 0; i < 10; i++) + ath5k_hw_reg_write(ah, s_seq[i], + AR5K_QUEUE_DCU_SEQNUM(i)); + } else { + ath5k_hw_reg_write(ah, s_seq[0], + AR5K_QUEUE_DCU_SEQNUM(0)); + } + + if (ah->ah_version == AR5K_AR5211) { + ath5k_hw_reg_write(ah, tsf_up, AR5K_TSF_U32); + ath5k_hw_reg_write(ah, tsf_lo, AR5K_TSF_L32); + } + } + + /* Ledstate */ + AR5K_REG_ENABLE_BITS(ah, AR5K_PCICFG, s_led[0]); + + /* Gpio settings */ + ath5k_hw_reg_write(ah, s_led[1], AR5K_GPIOCR); + ath5k_hw_reg_write(ah, s_led[2], AR5K_GPIODO); + + /* + * Initialize PCU + */ + ath5k_hw_pcu_init(ah, op_mode); + + /* + * Initialize PHY + */ + ret = ath5k_hw_phy_init(ah, channel, mode, false); + if (ret) { + ATH5K_ERR(ah, + "failed to initialize PHY (%i) !\n", ret); + return ret; + } + + /* + * Configure QCUs/DCUs + */ + ret = ath5k_hw_init_queues(ah); + if (ret) + return ret; + + + /* + * Initialize DMA/Interrupts + */ + ath5k_hw_dma_init(ah); + + + /* + * Enable 32KHz clock function for AR5212+ chips + * Set clocks to 32KHz operation and use an + * external 32KHz crystal when sleeping if one + * exists. + * Disabled by default because it is also disabled in + * other drivers and it is known to cause stability + * issues on some devices + */ + if (ah->ah_use_32khz_clock && ah->ah_version == AR5K_AR5212 && + op_mode != NL80211_IFTYPE_AP) + ath5k_hw_set_sleep_clock(ah, true); + + /* + * Disable beacons and reset the TSF + */ + AR5K_REG_DISABLE_BITS(ah, AR5K_BEACON, AR5K_BEACON_ENABLE); + ath5k_hw_reset_tsf(ah); + return 0; +} diff --git a/drivers/net/wireless/ath/ath5k/rfbuffer.h b/drivers/net/wireless/ath/ath5k/rfbuffer.h new file mode 100644 index 0000000..aed34d9 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/rfbuffer.h @@ -0,0 +1,853 @@ +/* + * RF Buffer handling functions + * + * Copyright (c) 2009 Nick Kossifidis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + + +/** + * DOC: RF Buffer registers + * + * There are some special registers on the RF chip + * that control various operation settings related mostly to + * the analog parts (channel, gain adjustment etc). + * + * We don't write on those registers directly but + * we send a data packet on the chip, using a special register, + * that holds all the settings we need. After we've sent the + * data packet, we write on another special register to notify hw + * to apply the settings. This is done so that control registers + * can be dynamically programmed during operation and the settings + * are applied faster on the hw. + * + * We call each data packet an "RF Bank" and all the data we write + * (all RF Banks) "RF Buffer". This file holds initial RF Buffer + * data for the different RF chips, and various info to match RF + * Buffer offsets with specific RF registers so that we can access + * them. We tweak these settings on rfregs_init function. + * + * Also check out reg.h and U.S. Patent 6677779 B1 (about buffer + * registers and control registers): + * + * http://www.google.com/patents?id=qNURAAAAEBAJ + */ + + +/** + * struct ath5k_ini_rfbuffer - Initial RF Buffer settings + * @rfb_bank: RF Bank number + * @rfb_ctrl_register: RF Buffer control register + * @rfb_mode_data: RF Buffer data for each mode + * + * Struct to hold default mode specific RF + * register values (RF Banks) for each chip. + */ +struct ath5k_ini_rfbuffer { + u8 rfb_bank; + u16 rfb_ctrl_register; + u32 rfb_mode_data[3]; +}; + +/** + * struct ath5k_rfb_field - An RF Buffer field (register/value) + * @len: Field length + * @pos: Offset on the raw packet + * @col: Used for shifting + * + * Struct to hold RF Buffer field + * infos used to access certain RF + * analog registers + */ +struct ath5k_rfb_field { + u8 len; + u16 pos; + u8 col; +}; + +/** + * struct ath5k_rf_reg - RF analog register definition + * @bank: RF Buffer Bank number + * @index: Register's index on ath5k_rf_regx_idx + * @field: The &struct ath5k_rfb_field + * + * We use this struct to define the set of RF registers + * on each chip that we want to tweak. Some RF registers + * are common between different chip versions so this saves + * us space and complexity because we can refer to an rf + * register by it's index no matter what chip we work with + * as long as it has that register. + */ +struct ath5k_rf_reg { + u8 bank; + u8 index; + struct ath5k_rfb_field field; +}; + +/** + * enum ath5k_rf_regs_idx - Map RF registers to indexes + * + * We do this to handle common bits and make our + * life easier by using an index for each register + * instead of a full rfb_field + */ +enum ath5k_rf_regs_idx { + /* BANK 2 */ + AR5K_RF_TURBO = 0, + /* BANK 6 */ + AR5K_RF_OB_2GHZ, + AR5K_RF_OB_5GHZ, + AR5K_RF_DB_2GHZ, + AR5K_RF_DB_5GHZ, + AR5K_RF_FIXED_BIAS_A, + AR5K_RF_FIXED_BIAS_B, + AR5K_RF_PWD_XPD, + AR5K_RF_XPD_SEL, + AR5K_RF_XPD_GAIN, + AR5K_RF_PD_GAIN_LO, + AR5K_RF_PD_GAIN_HI, + AR5K_RF_HIGH_VC_CP, + AR5K_RF_MID_VC_CP, + AR5K_RF_LOW_VC_CP, + AR5K_RF_PUSH_UP, + AR5K_RF_PAD2GND, + AR5K_RF_XB2_LVL, + AR5K_RF_XB5_LVL, + AR5K_RF_PWD_ICLOBUF_2G, + AR5K_RF_PWD_84, + AR5K_RF_PWD_90, + AR5K_RF_PWD_130, + AR5K_RF_PWD_131, + AR5K_RF_PWD_132, + AR5K_RF_PWD_136, + AR5K_RF_PWD_137, + AR5K_RF_PWD_138, + AR5K_RF_PWD_166, + AR5K_RF_PWD_167, + AR5K_RF_DERBY_CHAN_SEL_MODE, + /* BANK 7 */ + AR5K_RF_GAIN_I, + AR5K_RF_PLO_SEL, + AR5K_RF_RFGAIN_SEL, + AR5K_RF_RFGAIN_STEP, + AR5K_RF_WAIT_S, + AR5K_RF_WAIT_I, + AR5K_RF_MAX_TIME, + AR5K_RF_MIXVGA_OVR, + AR5K_RF_MIXGAIN_OVR, + AR5K_RF_MIXGAIN_STEP, + AR5K_RF_PD_DELAY_A, + AR5K_RF_PD_DELAY_B, + AR5K_RF_PD_DELAY_XR, + AR5K_RF_PD_PERIOD_A, + AR5K_RF_PD_PERIOD_B, + AR5K_RF_PD_PERIOD_XR, +}; + + +/*******************\ +* RF5111 (Sombrero) * +\*******************/ + +/* BANK 2 len pos col */ +#define AR5K_RF5111_RF_TURBO { 1, 3, 0 } + +/* BANK 6 len pos col */ +#define AR5K_RF5111_OB_2GHZ { 3, 119, 0 } +#define AR5K_RF5111_DB_2GHZ { 3, 122, 0 } + +#define AR5K_RF5111_OB_5GHZ { 3, 104, 0 } +#define AR5K_RF5111_DB_5GHZ { 3, 107, 0 } + +#define AR5K_RF5111_PWD_XPD { 1, 95, 0 } +#define AR5K_RF5111_XPD_GAIN { 4, 96, 0 } + +/* Access to PWD registers */ +#define AR5K_RF5111_PWD(_n) { 1, (135 - _n), 3 } + +/* BANK 7 len pos col */ +#define AR5K_RF5111_GAIN_I { 6, 29, 0 } +#define AR5K_RF5111_PLO_SEL { 1, 4, 0 } +#define AR5K_RF5111_RFGAIN_SEL { 1, 36, 0 } +#define AR5K_RF5111_RFGAIN_STEP { 6, 37, 0 } +/* Only on AR5212 BaseBand and up */ +#define AR5K_RF5111_WAIT_S { 5, 19, 0 } +#define AR5K_RF5111_WAIT_I { 5, 24, 0 } +#define AR5K_RF5111_MAX_TIME { 2, 49, 0 } + +static const struct ath5k_rf_reg rf_regs_5111[] = { + {2, AR5K_RF_TURBO, AR5K_RF5111_RF_TURBO}, + {6, AR5K_RF_OB_2GHZ, AR5K_RF5111_OB_2GHZ}, + {6, AR5K_RF_DB_2GHZ, AR5K_RF5111_DB_2GHZ}, + {6, AR5K_RF_OB_5GHZ, AR5K_RF5111_OB_5GHZ}, + {6, AR5K_RF_DB_5GHZ, AR5K_RF5111_DB_5GHZ}, + {6, AR5K_RF_PWD_XPD, AR5K_RF5111_PWD_XPD}, + {6, AR5K_RF_XPD_GAIN, AR5K_RF5111_XPD_GAIN}, + {6, AR5K_RF_PWD_84, AR5K_RF5111_PWD(84)}, + {6, AR5K_RF_PWD_90, AR5K_RF5111_PWD(90)}, + {7, AR5K_RF_GAIN_I, AR5K_RF5111_GAIN_I}, + {7, AR5K_RF_PLO_SEL, AR5K_RF5111_PLO_SEL}, + {7, AR5K_RF_RFGAIN_SEL, AR5K_RF5111_RFGAIN_SEL}, + {7, AR5K_RF_RFGAIN_STEP, AR5K_RF5111_RFGAIN_STEP}, + {7, AR5K_RF_WAIT_S, AR5K_RF5111_WAIT_S}, + {7, AR5K_RF_WAIT_I, AR5K_RF5111_WAIT_I}, + {7, AR5K_RF_MAX_TIME, AR5K_RF5111_MAX_TIME} +}; + +/* Default mode specific settings */ +static const struct ath5k_ini_rfbuffer rfb_5111[] = { + /* BANK / C.R. A/XR B G */ + { 0, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 0, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 0, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 0, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 0, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 0, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 0, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 0, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 0, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 0, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 0, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 0, 0x989c, { 0x00380000, 0x00380000, 0x00380000 } }, + { 0, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 0, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 0, 0x989c, { 0x00000000, 0x000000c0, 0x00000080 } }, + { 0, 0x989c, { 0x000400f9, 0x000400ff, 0x000400fd } }, + { 0, 0x98d4, { 0x00000000, 0x00000004, 0x00000004 } }, + { 1, 0x98d4, { 0x00000020, 0x00000020, 0x00000020 } }, + { 2, 0x98d4, { 0x00000010, 0x00000010, 0x00000010 } }, + { 3, 0x98d8, { 0x00601068, 0x00601068, 0x00601068 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x10000000, 0x10000000, 0x10000000 } }, + { 6, 0x989c, { 0x04000000, 0x04000000, 0x04000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x0a000000, 0x00000000 } }, + { 6, 0x989c, { 0x003800c0, 0x023800c0, 0x003800c0 } }, + { 6, 0x989c, { 0x00020006, 0x00000006, 0x00020006 } }, + { 6, 0x989c, { 0x00000089, 0x00000089, 0x00000089 } }, + { 6, 0x989c, { 0x000000a0, 0x000000a0, 0x000000a0 } }, + { 6, 0x989c, { 0x00040007, 0x00040007, 0x00040007 } }, + { 6, 0x98d4, { 0x0000001a, 0x0000001a, 0x0000001a } }, + { 7, 0x989c, { 0x00000040, 0x00000040, 0x00000040 } }, + { 7, 0x989c, { 0x00000010, 0x00000010, 0x00000010 } }, + { 7, 0x989c, { 0x00000008, 0x00000008, 0x00000008 } }, + { 7, 0x989c, { 0x0000004f, 0x0000004f, 0x0000004f } }, + { 7, 0x989c, { 0x000000f1, 0x00000061, 0x000000f1 } }, + { 7, 0x989c, { 0x0000904f, 0x0000904c, 0x0000904f } }, + { 7, 0x989c, { 0x0000125a, 0x0000129a, 0x0000125a } }, + { 7, 0x98cc, { 0x0000000e, 0x0000000f, 0x0000000e } }, +}; + + + +/***********************\ +* RF5112/RF2112 (Derby) * +\***********************/ + +/* BANK 2 (Common) len pos col */ +#define AR5K_RF5112X_RF_TURBO { 1, 1, 2 } + +/* BANK 7 (Common) len pos col */ +#define AR5K_RF5112X_GAIN_I { 6, 14, 0 } +#define AR5K_RF5112X_MIXVGA_OVR { 1, 36, 0 } +#define AR5K_RF5112X_MIXGAIN_OVR { 2, 37, 0 } +#define AR5K_RF5112X_MIXGAIN_STEP { 4, 32, 0 } +#define AR5K_RF5112X_PD_DELAY_A { 4, 58, 0 } +#define AR5K_RF5112X_PD_DELAY_B { 4, 62, 0 } +#define AR5K_RF5112X_PD_DELAY_XR { 4, 66, 0 } +#define AR5K_RF5112X_PD_PERIOD_A { 4, 70, 0 } +#define AR5K_RF5112X_PD_PERIOD_B { 4, 74, 0 } +#define AR5K_RF5112X_PD_PERIOD_XR { 4, 78, 0 } + +/* RFX112 (Derby 1) */ + +/* BANK 6 len pos col */ +#define AR5K_RF5112_OB_2GHZ { 3, 269, 0 } +#define AR5K_RF5112_DB_2GHZ { 3, 272, 0 } + +#define AR5K_RF5112_OB_5GHZ { 3, 261, 0 } +#define AR5K_RF5112_DB_5GHZ { 3, 264, 0 } + +#define AR5K_RF5112_FIXED_BIAS_A { 1, 260, 0 } +#define AR5K_RF5112_FIXED_BIAS_B { 1, 259, 0 } + +#define AR5K_RF5112_XPD_SEL { 1, 284, 0 } +#define AR5K_RF5112_XPD_GAIN { 2, 252, 0 } + +/* Access to PWD registers */ +#define AR5K_RF5112_PWD(_n) { 1, (302 - _n), 3 } + +static const struct ath5k_rf_reg rf_regs_5112[] = { + {2, AR5K_RF_TURBO, AR5K_RF5112X_RF_TURBO}, + {6, AR5K_RF_OB_2GHZ, AR5K_RF5112_OB_2GHZ}, + {6, AR5K_RF_DB_2GHZ, AR5K_RF5112_DB_2GHZ}, + {6, AR5K_RF_OB_5GHZ, AR5K_RF5112_OB_5GHZ}, + {6, AR5K_RF_DB_5GHZ, AR5K_RF5112_DB_5GHZ}, + {6, AR5K_RF_FIXED_BIAS_A, AR5K_RF5112_FIXED_BIAS_A}, + {6, AR5K_RF_FIXED_BIAS_B, AR5K_RF5112_FIXED_BIAS_B}, + {6, AR5K_RF_XPD_SEL, AR5K_RF5112_XPD_SEL}, + {6, AR5K_RF_XPD_GAIN, AR5K_RF5112_XPD_GAIN}, + {6, AR5K_RF_PWD_130, AR5K_RF5112_PWD(130)}, + {6, AR5K_RF_PWD_131, AR5K_RF5112_PWD(131)}, + {6, AR5K_RF_PWD_132, AR5K_RF5112_PWD(132)}, + {6, AR5K_RF_PWD_136, AR5K_RF5112_PWD(136)}, + {6, AR5K_RF_PWD_137, AR5K_RF5112_PWD(137)}, + {6, AR5K_RF_PWD_138, AR5K_RF5112_PWD(138)}, + {7, AR5K_RF_GAIN_I, AR5K_RF5112X_GAIN_I}, + {7, AR5K_RF_MIXVGA_OVR, AR5K_RF5112X_MIXVGA_OVR}, + {7, AR5K_RF_MIXGAIN_OVR, AR5K_RF5112X_MIXGAIN_OVR}, + {7, AR5K_RF_MIXGAIN_STEP, AR5K_RF5112X_MIXGAIN_STEP}, + {7, AR5K_RF_PD_DELAY_A, AR5K_RF5112X_PD_DELAY_A}, + {7, AR5K_RF_PD_DELAY_B, AR5K_RF5112X_PD_DELAY_B}, + {7, AR5K_RF_PD_DELAY_XR, AR5K_RF5112X_PD_DELAY_XR}, + {7, AR5K_RF_PD_PERIOD_A, AR5K_RF5112X_PD_PERIOD_A}, + {7, AR5K_RF_PD_PERIOD_B, AR5K_RF5112X_PD_PERIOD_B}, + {7, AR5K_RF_PD_PERIOD_XR, AR5K_RF5112X_PD_PERIOD_XR}, +}; + +/* Default mode specific settings */ +static const struct ath5k_ini_rfbuffer rfb_5112[] = { + /* BANK / C.R. A/XR B G */ + { 1, 0x98d4, { 0x00000020, 0x00000020, 0x00000020 } }, + { 2, 0x98d0, { 0x03060408, 0x03060408, 0x03060408 } }, + { 3, 0x98dc, { 0x00a0c0c0, 0x00e0c0c0, 0x00e0c0c0 } }, + { 6, 0x989c, { 0x00a00000, 0x00a00000, 0x00a00000 } }, + { 6, 0x989c, { 0x000a0000, 0x000a0000, 0x000a0000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00660000, 0x00660000, 0x00660000 } }, + { 6, 0x989c, { 0x00db0000, 0x00db0000, 0x00db0000 } }, + { 6, 0x989c, { 0x00f10000, 0x00f10000, 0x00f10000 } }, + { 6, 0x989c, { 0x00120000, 0x00120000, 0x00120000 } }, + { 6, 0x989c, { 0x00120000, 0x00120000, 0x00120000 } }, + { 6, 0x989c, { 0x00730000, 0x00730000, 0x00730000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x000c0000, 0x000c0000, 0x000c0000 } }, + { 6, 0x989c, { 0x00ff0000, 0x00ff0000, 0x00ff0000 } }, + { 6, 0x989c, { 0x00ff0000, 0x00ff0000, 0x00ff0000 } }, + { 6, 0x989c, { 0x008b0000, 0x008b0000, 0x008b0000 } }, + { 6, 0x989c, { 0x00600000, 0x00600000, 0x00600000 } }, + { 6, 0x989c, { 0x000c0000, 0x000c0000, 0x000c0000 } }, + { 6, 0x989c, { 0x00840000, 0x00840000, 0x00840000 } }, + { 6, 0x989c, { 0x00640000, 0x00640000, 0x00640000 } }, + { 6, 0x989c, { 0x00200000, 0x00200000, 0x00200000 } }, + { 6, 0x989c, { 0x00240000, 0x00240000, 0x00240000 } }, + { 6, 0x989c, { 0x00250000, 0x00250000, 0x00250000 } }, + { 6, 0x989c, { 0x00110000, 0x00110000, 0x00110000 } }, + { 6, 0x989c, { 0x00110000, 0x00110000, 0x00110000 } }, + { 6, 0x989c, { 0x00510000, 0x00510000, 0x00510000 } }, + { 6, 0x989c, { 0x1c040000, 0x1c040000, 0x1c040000 } }, + { 6, 0x989c, { 0x000a0000, 0x000a0000, 0x000a0000 } }, + { 6, 0x989c, { 0x00a10000, 0x00a10000, 0x00a10000 } }, + { 6, 0x989c, { 0x00400000, 0x00400000, 0x00400000 } }, + { 6, 0x989c, { 0x03090000, 0x03090000, 0x03090000 } }, + { 6, 0x989c, { 0x06000000, 0x06000000, 0x06000000 } }, + { 6, 0x989c, { 0x000000b0, 0x000000a8, 0x000000a8 } }, + { 6, 0x989c, { 0x0000002e, 0x0000002e, 0x0000002e } }, + { 6, 0x989c, { 0x006c4a41, 0x006c4af1, 0x006c4a61 } }, + { 6, 0x989c, { 0x0050892a, 0x0050892b, 0x0050892b } }, + { 6, 0x989c, { 0x00842400, 0x00842400, 0x00842400 } }, + { 6, 0x989c, { 0x00c69200, 0x00c69200, 0x00c69200 } }, + { 6, 0x98d0, { 0x0002000c, 0x0002000c, 0x0002000c } }, + { 7, 0x989c, { 0x00000094, 0x00000094, 0x00000094 } }, + { 7, 0x989c, { 0x00000091, 0x00000091, 0x00000091 } }, + { 7, 0x989c, { 0x0000000a, 0x00000012, 0x00000012 } }, + { 7, 0x989c, { 0x00000080, 0x00000080, 0x00000080 } }, + { 7, 0x989c, { 0x000000c1, 0x000000c1, 0x000000c1 } }, + { 7, 0x989c, { 0x00000060, 0x00000060, 0x00000060 } }, + { 7, 0x989c, { 0x000000f0, 0x000000f0, 0x000000f0 } }, + { 7, 0x989c, { 0x00000022, 0x00000022, 0x00000022 } }, + { 7, 0x989c, { 0x00000092, 0x00000092, 0x00000092 } }, + { 7, 0x989c, { 0x000000d4, 0x000000d4, 0x000000d4 } }, + { 7, 0x989c, { 0x000014cc, 0x000014cc, 0x000014cc } }, + { 7, 0x989c, { 0x0000048c, 0x0000048c, 0x0000048c } }, + { 7, 0x98c4, { 0x00000003, 0x00000003, 0x00000003 } }, +}; + +/* RFX112A (Derby 2) */ + +/* BANK 6 len pos col */ +#define AR5K_RF5112A_OB_2GHZ { 3, 287, 0 } +#define AR5K_RF5112A_DB_2GHZ { 3, 290, 0 } + +#define AR5K_RF5112A_OB_5GHZ { 3, 279, 0 } +#define AR5K_RF5112A_DB_5GHZ { 3, 282, 0 } + +#define AR5K_RF5112A_FIXED_BIAS_A { 1, 278, 0 } +#define AR5K_RF5112A_FIXED_BIAS_B { 1, 277, 0 } + +#define AR5K_RF5112A_XPD_SEL { 1, 302, 0 } +#define AR5K_RF5112A_PDGAINLO { 2, 270, 0 } +#define AR5K_RF5112A_PDGAINHI { 2, 257, 0 } + +/* Access to PWD registers */ +#define AR5K_RF5112A_PWD(_n) { 1, (306 - _n), 3 } + +/* Voltage regulators */ +#define AR5K_RF5112A_HIGH_VC_CP { 2, 90, 2 } +#define AR5K_RF5112A_MID_VC_CP { 2, 92, 2 } +#define AR5K_RF5112A_LOW_VC_CP { 2, 94, 2 } +#define AR5K_RF5112A_PUSH_UP { 1, 254, 2 } + +/* Power consumption */ +#define AR5K_RF5112A_PAD2GND { 1, 281, 1 } +#define AR5K_RF5112A_XB2_LVL { 2, 1, 3 } +#define AR5K_RF5112A_XB5_LVL { 2, 3, 3 } + +static const struct ath5k_rf_reg rf_regs_5112a[] = { + {2, AR5K_RF_TURBO, AR5K_RF5112X_RF_TURBO}, + {6, AR5K_RF_OB_2GHZ, AR5K_RF5112A_OB_2GHZ}, + {6, AR5K_RF_DB_2GHZ, AR5K_RF5112A_DB_2GHZ}, + {6, AR5K_RF_OB_5GHZ, AR5K_RF5112A_OB_5GHZ}, + {6, AR5K_RF_DB_5GHZ, AR5K_RF5112A_DB_5GHZ}, + {6, AR5K_RF_FIXED_BIAS_A, AR5K_RF5112A_FIXED_BIAS_A}, + {6, AR5K_RF_FIXED_BIAS_B, AR5K_RF5112A_FIXED_BIAS_B}, + {6, AR5K_RF_XPD_SEL, AR5K_RF5112A_XPD_SEL}, + {6, AR5K_RF_PD_GAIN_LO, AR5K_RF5112A_PDGAINLO}, + {6, AR5K_RF_PD_GAIN_HI, AR5K_RF5112A_PDGAINHI}, + {6, AR5K_RF_PWD_130, AR5K_RF5112A_PWD(130)}, + {6, AR5K_RF_PWD_131, AR5K_RF5112A_PWD(131)}, + {6, AR5K_RF_PWD_132, AR5K_RF5112A_PWD(132)}, + {6, AR5K_RF_PWD_136, AR5K_RF5112A_PWD(136)}, + {6, AR5K_RF_PWD_137, AR5K_RF5112A_PWD(137)}, + {6, AR5K_RF_PWD_138, AR5K_RF5112A_PWD(138)}, + {6, AR5K_RF_PWD_166, AR5K_RF5112A_PWD(166)}, + {6, AR5K_RF_PWD_167, AR5K_RF5112A_PWD(167)}, + {6, AR5K_RF_HIGH_VC_CP, AR5K_RF5112A_HIGH_VC_CP}, + {6, AR5K_RF_MID_VC_CP, AR5K_RF5112A_MID_VC_CP}, + {6, AR5K_RF_LOW_VC_CP, AR5K_RF5112A_LOW_VC_CP}, + {6, AR5K_RF_PUSH_UP, AR5K_RF5112A_PUSH_UP}, + {6, AR5K_RF_PAD2GND, AR5K_RF5112A_PAD2GND}, + {6, AR5K_RF_XB2_LVL, AR5K_RF5112A_XB2_LVL}, + {6, AR5K_RF_XB5_LVL, AR5K_RF5112A_XB5_LVL}, + {7, AR5K_RF_GAIN_I, AR5K_RF5112X_GAIN_I}, + {7, AR5K_RF_MIXVGA_OVR, AR5K_RF5112X_MIXVGA_OVR}, + {7, AR5K_RF_MIXGAIN_OVR, AR5K_RF5112X_MIXGAIN_OVR}, + {7, AR5K_RF_MIXGAIN_STEP, AR5K_RF5112X_MIXGAIN_STEP}, + {7, AR5K_RF_PD_DELAY_A, AR5K_RF5112X_PD_DELAY_A}, + {7, AR5K_RF_PD_DELAY_B, AR5K_RF5112X_PD_DELAY_B}, + {7, AR5K_RF_PD_DELAY_XR, AR5K_RF5112X_PD_DELAY_XR}, + {7, AR5K_RF_PD_PERIOD_A, AR5K_RF5112X_PD_PERIOD_A}, + {7, AR5K_RF_PD_PERIOD_B, AR5K_RF5112X_PD_PERIOD_B}, + {7, AR5K_RF_PD_PERIOD_XR, AR5K_RF5112X_PD_PERIOD_XR}, +}; + +/* Default mode specific settings */ +static const struct ath5k_ini_rfbuffer rfb_5112a[] = { + /* BANK / C.R. A/XR B G */ + { 1, 0x98d4, { 0x00000020, 0x00000020, 0x00000020 } }, + { 2, 0x98d0, { 0x03060408, 0x03060408, 0x03060408 } }, + { 3, 0x98dc, { 0x00a020c0, 0x00e020c0, 0x00e020c0 } }, + { 6, 0x989c, { 0x0f000000, 0x0f000000, 0x0f000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00800000, 0x00800000, 0x00800000 } }, + { 6, 0x989c, { 0x002a0000, 0x002a0000, 0x002a0000 } }, + { 6, 0x989c, { 0x00010000, 0x00010000, 0x00010000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00180000, 0x00180000, 0x00180000 } }, + { 6, 0x989c, { 0x00600000, 0x006e0000, 0x006e0000 } }, + { 6, 0x989c, { 0x00c70000, 0x00c70000, 0x00c70000 } }, + { 6, 0x989c, { 0x004b0000, 0x004b0000, 0x004b0000 } }, + { 6, 0x989c, { 0x04480000, 0x04480000, 0x04480000 } }, + { 6, 0x989c, { 0x004c0000, 0x004c0000, 0x004c0000 } }, + { 6, 0x989c, { 0x00e40000, 0x00e40000, 0x00e40000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00fc0000, 0x00fc0000, 0x00fc0000 } }, + { 6, 0x989c, { 0x00ff0000, 0x00ff0000, 0x00ff0000 } }, + { 6, 0x989c, { 0x043f0000, 0x043f0000, 0x043f0000 } }, + { 6, 0x989c, { 0x000c0000, 0x000c0000, 0x000c0000 } }, + { 6, 0x989c, { 0x02190000, 0x02190000, 0x02190000 } }, + { 6, 0x989c, { 0x00240000, 0x00240000, 0x00240000 } }, + { 6, 0x989c, { 0x00b40000, 0x00b40000, 0x00b40000 } }, + { 6, 0x989c, { 0x00990000, 0x00990000, 0x00990000 } }, + { 6, 0x989c, { 0x00500000, 0x00500000, 0x00500000 } }, + { 6, 0x989c, { 0x002a0000, 0x002a0000, 0x002a0000 } }, + { 6, 0x989c, { 0x00120000, 0x00120000, 0x00120000 } }, + { 6, 0x989c, { 0xc0320000, 0xc0320000, 0xc0320000 } }, + { 6, 0x989c, { 0x01740000, 0x01740000, 0x01740000 } }, + { 6, 0x989c, { 0x00110000, 0x00110000, 0x00110000 } }, + { 6, 0x989c, { 0x86280000, 0x86280000, 0x86280000 } }, + { 6, 0x989c, { 0x31840000, 0x31840000, 0x31840000 } }, + { 6, 0x989c, { 0x00f20080, 0x00f20080, 0x00f20080 } }, + { 6, 0x989c, { 0x00270019, 0x00270019, 0x00270019 } }, + { 6, 0x989c, { 0x00000003, 0x00000003, 0x00000003 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x000000b2, 0x000000b2, 0x000000b2 } }, + { 6, 0x989c, { 0x00b02084, 0x00b02084, 0x00b02084 } }, + { 6, 0x989c, { 0x004125a4, 0x004125a4, 0x004125a4 } }, + { 6, 0x989c, { 0x00119220, 0x00119220, 0x00119220 } }, + { 6, 0x989c, { 0x001a4800, 0x001a4800, 0x001a4800 } }, + { 6, 0x98d8, { 0x000b0230, 0x000b0230, 0x000b0230 } }, + { 7, 0x989c, { 0x00000094, 0x00000094, 0x00000094 } }, + { 7, 0x989c, { 0x00000091, 0x00000091, 0x00000091 } }, + { 7, 0x989c, { 0x00000012, 0x00000012, 0x00000012 } }, + { 7, 0x989c, { 0x00000080, 0x00000080, 0x00000080 } }, + { 7, 0x989c, { 0x000000d9, 0x000000d9, 0x000000d9 } }, + { 7, 0x989c, { 0x00000060, 0x00000060, 0x00000060 } }, + { 7, 0x989c, { 0x000000f0, 0x000000f0, 0x000000f0 } }, + { 7, 0x989c, { 0x000000a2, 0x000000a2, 0x000000a2 } }, + { 7, 0x989c, { 0x00000052, 0x00000052, 0x00000052 } }, + { 7, 0x989c, { 0x000000d4, 0x000000d4, 0x000000d4 } }, + { 7, 0x989c, { 0x000014cc, 0x000014cc, 0x000014cc } }, + { 7, 0x989c, { 0x0000048c, 0x0000048c, 0x0000048c } }, + { 7, 0x98c4, { 0x00000003, 0x00000003, 0x00000003 } }, +}; + + + +/******************\ +* RF2413 (Griffin) * +\******************/ + +/* BANK 2 len pos col */ +#define AR5K_RF2413_RF_TURBO { 1, 1, 2 } + +/* BANK 6 len pos col */ +#define AR5K_RF2413_OB_2GHZ { 3, 168, 0 } +#define AR5K_RF2413_DB_2GHZ { 3, 165, 0 } + +static const struct ath5k_rf_reg rf_regs_2413[] = { + {2, AR5K_RF_TURBO, AR5K_RF2413_RF_TURBO}, + {6, AR5K_RF_OB_2GHZ, AR5K_RF2413_OB_2GHZ}, + {6, AR5K_RF_DB_2GHZ, AR5K_RF2413_DB_2GHZ}, +}; + +/* Default mode specific settings + * XXX: a/aTurbo ??? + */ +static const struct ath5k_ini_rfbuffer rfb_2413[] = { + /* BANK / C.R. A/XR B G */ + { 1, 0x98d4, { 0x00000020, 0x00000020, 0x00000020 } }, + { 2, 0x98d0, { 0x02001408, 0x02001408, 0x02001408 } }, + { 3, 0x98dc, { 0x00a020c0, 0x00e020c0, 0x00e020c0 } }, + { 6, 0x989c, { 0xf0000000, 0xf0000000, 0xf0000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x03000000, 0x03000000, 0x03000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x40400000, 0x40400000, 0x40400000 } }, + { 6, 0x989c, { 0x65050000, 0x65050000, 0x65050000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00420000, 0x00420000, 0x00420000 } }, + { 6, 0x989c, { 0x00b50000, 0x00b50000, 0x00b50000 } }, + { 6, 0x989c, { 0x00030000, 0x00030000, 0x00030000 } }, + { 6, 0x989c, { 0x00f70000, 0x00f70000, 0x00f70000 } }, + { 6, 0x989c, { 0x009d0000, 0x009d0000, 0x009d0000 } }, + { 6, 0x989c, { 0x00220000, 0x00220000, 0x00220000 } }, + { 6, 0x989c, { 0x04220000, 0x04220000, 0x04220000 } }, + { 6, 0x989c, { 0x00230018, 0x00230018, 0x00230018 } }, + { 6, 0x989c, { 0x00280000, 0x00280060, 0x00280060 } }, + { 6, 0x989c, { 0x005000c0, 0x005000c3, 0x005000c3 } }, + { 6, 0x989c, { 0x0004007f, 0x0004007f, 0x0004007f } }, + { 6, 0x989c, { 0x00000458, 0x00000458, 0x00000458 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x0000c000, 0x0000c000, 0x0000c000 } }, + { 6, 0x98d8, { 0x00400230, 0x00400230, 0x00400230 } }, + { 7, 0x989c, { 0x00006400, 0x00006400, 0x00006400 } }, + { 7, 0x989c, { 0x00000800, 0x00000800, 0x00000800 } }, + { 7, 0x98cc, { 0x0000000e, 0x0000000e, 0x0000000e } }, +}; + + + +/***************************\ +* RF2315/RF2316 (Cobra SoC) * +\***************************/ + +/* BANK 2 len pos col */ +#define AR5K_RF2316_RF_TURBO { 1, 1, 2 } + +/* BANK 6 len pos col */ +#define AR5K_RF2316_OB_2GHZ { 3, 178, 0 } +#define AR5K_RF2316_DB_2GHZ { 3, 175, 0 } + +static const struct ath5k_rf_reg rf_regs_2316[] = { + {2, AR5K_RF_TURBO, AR5K_RF2316_RF_TURBO}, + {6, AR5K_RF_OB_2GHZ, AR5K_RF2316_OB_2GHZ}, + {6, AR5K_RF_DB_2GHZ, AR5K_RF2316_DB_2GHZ}, +}; + +/* Default mode specific settings */ +static const struct ath5k_ini_rfbuffer rfb_2316[] = { + /* BANK / C.R. A/XR B G */ + { 1, 0x98d4, { 0x00000020, 0x00000020, 0x00000020 } }, + { 2, 0x98d0, { 0x02001408, 0x02001408, 0x02001408 } }, + { 3, 0x98dc, { 0x00a020c0, 0x00e020c0, 0x00e020c0 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0xc0000000, 0xc0000000, 0xc0000000 } }, + { 6, 0x989c, { 0x0f000000, 0x0f000000, 0x0f000000 } }, + { 6, 0x989c, { 0x02000000, 0x02000000, 0x02000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0xf8000000, 0xf8000000, 0xf8000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x95150000, 0x95150000, 0x95150000 } }, + { 6, 0x989c, { 0xc1000000, 0xc1000000, 0xc1000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00080000, 0x00080000, 0x00080000 } }, + { 6, 0x989c, { 0x00d50000, 0x00d50000, 0x00d50000 } }, + { 6, 0x989c, { 0x000e0000, 0x000e0000, 0x000e0000 } }, + { 6, 0x989c, { 0x00dc0000, 0x00dc0000, 0x00dc0000 } }, + { 6, 0x989c, { 0x00770000, 0x00770000, 0x00770000 } }, + { 6, 0x989c, { 0x008a0000, 0x008a0000, 0x008a0000 } }, + { 6, 0x989c, { 0x10880000, 0x10880000, 0x10880000 } }, + { 6, 0x989c, { 0x008c0060, 0x008c0060, 0x008c0060 } }, + { 6, 0x989c, { 0x00a00000, 0x00a00080, 0x00a00080 } }, + { 6, 0x989c, { 0x00400000, 0x0040000d, 0x0040000d } }, + { 6, 0x989c, { 0x00110400, 0x00110400, 0x00110400 } }, + { 6, 0x989c, { 0x00000060, 0x00000060, 0x00000060 } }, + { 6, 0x989c, { 0x00000001, 0x00000001, 0x00000001 } }, + { 6, 0x989c, { 0x00000b00, 0x00000b00, 0x00000b00 } }, + { 6, 0x989c, { 0x00000be8, 0x00000be8, 0x00000be8 } }, + { 6, 0x98c0, { 0x00010000, 0x00010000, 0x00010000 } }, + { 7, 0x989c, { 0x00006400, 0x00006400, 0x00006400 } }, + { 7, 0x989c, { 0x00000800, 0x00000800, 0x00000800 } }, + { 7, 0x98cc, { 0x0000000e, 0x0000000e, 0x0000000e } }, +}; + + + +/******************************\ +* RF5413/RF5424 (Eagle/Condor) * +\******************************/ + +/* BANK 6 len pos col */ +#define AR5K_RF5413_OB_2GHZ { 3, 241, 0 } +#define AR5K_RF5413_DB_2GHZ { 3, 238, 0 } + +#define AR5K_RF5413_OB_5GHZ { 3, 247, 0 } +#define AR5K_RF5413_DB_5GHZ { 3, 244, 0 } + +#define AR5K_RF5413_PWD_ICLOBUF2G { 3, 131, 3 } +#define AR5K_RF5413_DERBY_CHAN_SEL_MODE { 1, 291, 2 } + +static const struct ath5k_rf_reg rf_regs_5413[] = { + {6, AR5K_RF_OB_2GHZ, AR5K_RF5413_OB_2GHZ}, + {6, AR5K_RF_DB_2GHZ, AR5K_RF5413_DB_2GHZ}, + {6, AR5K_RF_OB_5GHZ, AR5K_RF5413_OB_5GHZ}, + {6, AR5K_RF_DB_5GHZ, AR5K_RF5413_DB_5GHZ}, + {6, AR5K_RF_PWD_ICLOBUF_2G, AR5K_RF5413_PWD_ICLOBUF2G}, + {6, AR5K_RF_DERBY_CHAN_SEL_MODE, AR5K_RF5413_DERBY_CHAN_SEL_MODE}, +}; + +/* Default mode specific settings */ +static const struct ath5k_ini_rfbuffer rfb_5413[] = { + /* BANK / C.R. A/XR B G */ + { 1, 0x98d4, { 0x00000020, 0x00000020, 0x00000020 } }, + { 2, 0x98d0, { 0x00000008, 0x00000008, 0x00000008 } }, + { 3, 0x98dc, { 0x00a000c0, 0x00e000c0, 0x00e000c0 } }, + { 6, 0x989c, { 0x33000000, 0x33000000, 0x33000000 } }, + { 6, 0x989c, { 0x01000000, 0x01000000, 0x01000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x1f000000, 0x1f000000, 0x1f000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00b80000, 0x00b80000, 0x00b80000 } }, + { 6, 0x989c, { 0x00b70000, 0x00b70000, 0x00b70000 } }, + { 6, 0x989c, { 0x00840000, 0x00840000, 0x00840000 } }, + { 6, 0x989c, { 0x00980000, 0x00980000, 0x00980000 } }, + { 6, 0x989c, { 0x00c00000, 0x00c00000, 0x00c00000 } }, + { 6, 0x989c, { 0x00ff0000, 0x00ff0000, 0x00ff0000 } }, + { 6, 0x989c, { 0x00ff0000, 0x00ff0000, 0x00ff0000 } }, + { 6, 0x989c, { 0x00ff0000, 0x00ff0000, 0x00ff0000 } }, + { 6, 0x989c, { 0x00ff0000, 0x00ff0000, 0x00ff0000 } }, + { 6, 0x989c, { 0x00d70000, 0x00d70000, 0x00d70000 } }, + { 6, 0x989c, { 0x00610000, 0x00610000, 0x00610000 } }, + { 6, 0x989c, { 0x00fe0000, 0x00fe0000, 0x00fe0000 } }, + { 6, 0x989c, { 0x00de0000, 0x00de0000, 0x00de0000 } }, + { 6, 0x989c, { 0x007f0000, 0x007f0000, 0x007f0000 } }, + { 6, 0x989c, { 0x043d0000, 0x043d0000, 0x043d0000 } }, + { 6, 0x989c, { 0x00770000, 0x00770000, 0x00770000 } }, + { 6, 0x989c, { 0x00440000, 0x00440000, 0x00440000 } }, + { 6, 0x989c, { 0x00980000, 0x00980000, 0x00980000 } }, + { 6, 0x989c, { 0x00100080, 0x00100080, 0x00100080 } }, + { 6, 0x989c, { 0x0005c034, 0x0005c034, 0x0005c034 } }, + { 6, 0x989c, { 0x003100f0, 0x003100f0, 0x003100f0 } }, + { 6, 0x989c, { 0x000c011f, 0x000c011f, 0x000c011f } }, + { 6, 0x989c, { 0x00510040, 0x00510040, 0x00510040 } }, + { 6, 0x989c, { 0x005000da, 0x005000da, 0x005000da } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00004044, 0x00004044, 0x00004044 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x000060c0, 0x000060c0, 0x000060c0 } }, + { 6, 0x989c, { 0x00002c00, 0x00003600, 0x00003600 } }, + { 6, 0x98c8, { 0x00000403, 0x00040403, 0x00040403 } }, + { 7, 0x989c, { 0x00006400, 0x00006400, 0x00006400 } }, + { 7, 0x989c, { 0x00000800, 0x00000800, 0x00000800 } }, + { 7, 0x98cc, { 0x0000000e, 0x0000000e, 0x0000000e } }, +}; + + + +/***************************\ +* RF2425/RF2417 (Swan/Nala) * +* AR2317 (Spider SoC) * +\***************************/ + +/* BANK 2 len pos col */ +#define AR5K_RF2425_RF_TURBO { 1, 1, 2 } + +/* BANK 6 len pos col */ +#define AR5K_RF2425_OB_2GHZ { 3, 193, 0 } +#define AR5K_RF2425_DB_2GHZ { 3, 190, 0 } + +static const struct ath5k_rf_reg rf_regs_2425[] = { + {2, AR5K_RF_TURBO, AR5K_RF2425_RF_TURBO}, + {6, AR5K_RF_OB_2GHZ, AR5K_RF2425_OB_2GHZ}, + {6, AR5K_RF_DB_2GHZ, AR5K_RF2425_DB_2GHZ}, +}; + +/* Default mode specific settings + */ +static const struct ath5k_ini_rfbuffer rfb_2425[] = { + /* BANK / C.R. A/XR B G */ + { 1, 0x98d4, { 0x00000020, 0x00000020, 0x00000020 } }, + { 2, 0x98d0, { 0x02001408, 0x02001408, 0x02001408 } }, + { 3, 0x98dc, { 0x00a020c0, 0x00e020c0, 0x00e020c0 } }, + { 6, 0x989c, { 0x10000000, 0x10000000, 0x10000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x002a0000, 0x002a0000, 0x002a0000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00100000, 0x00100000, 0x00100000 } }, + { 6, 0x989c, { 0x00020000, 0x00020000, 0x00020000 } }, + { 6, 0x989c, { 0x00730000, 0x00730000, 0x00730000 } }, + { 6, 0x989c, { 0x00f80000, 0x00f80000, 0x00f80000 } }, + { 6, 0x989c, { 0x00e70000, 0x00e70000, 0x00e70000 } }, + { 6, 0x989c, { 0x00140000, 0x00140000, 0x00140000 } }, + { 6, 0x989c, { 0x00910040, 0x00910040, 0x00910040 } }, + { 6, 0x989c, { 0x0007001a, 0x0007001a, 0x0007001a } }, + { 6, 0x989c, { 0x00410000, 0x00410000, 0x00410000 } }, + { 6, 0x989c, { 0x00810000, 0x00810060, 0x00810060 } }, + { 6, 0x989c, { 0x00020800, 0x00020803, 0x00020803 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00001660, 0x00001660, 0x00001660 } }, + { 6, 0x989c, { 0x00001688, 0x00001688, 0x00001688 } }, + { 6, 0x98c4, { 0x00000001, 0x00000001, 0x00000001 } }, + { 7, 0x989c, { 0x00006400, 0x00006400, 0x00006400 } }, + { 7, 0x989c, { 0x00000800, 0x00000800, 0x00000800 } }, + { 7, 0x98cc, { 0x0000000e, 0x0000000e, 0x0000000e } }, +}; + +/* + * TODO: Handle the few differences with swan during + * bank modification and get rid of this + */ +static const struct ath5k_ini_rfbuffer rfb_2317[] = { + /* BANK / C.R. A/XR B G */ + { 1, 0x98d4, { 0x00000020, 0x00000020, 0x00000020 } }, + { 2, 0x98d0, { 0x02001408, 0x02001408, 0x02001408 } }, + { 3, 0x98dc, { 0x00a020c0, 0x00e020c0, 0x00e020c0 } }, + { 6, 0x989c, { 0x10000000, 0x10000000, 0x10000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x002a0000, 0x002a0000, 0x002a0000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00100000, 0x00100000, 0x00100000 } }, + { 6, 0x989c, { 0x00020000, 0x00020000, 0x00020000 } }, + { 6, 0x989c, { 0x00730000, 0x00730000, 0x00730000 } }, + { 6, 0x989c, { 0x00f80000, 0x00f80000, 0x00f80000 } }, + { 6, 0x989c, { 0x00e70000, 0x00e70000, 0x00e70000 } }, + { 6, 0x989c, { 0x00140100, 0x00140100, 0x00140100 } }, + { 6, 0x989c, { 0x00910040, 0x00910040, 0x00910040 } }, + { 6, 0x989c, { 0x0007001a, 0x0007001a, 0x0007001a } }, + { 6, 0x989c, { 0x00410000, 0x00410000, 0x00410000 } }, + { 6, 0x989c, { 0x00810000, 0x00810060, 0x00810060 } }, + { 6, 0x989c, { 0x00020800, 0x00020803, 0x00020803 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00001660, 0x00001660, 0x00001660 } }, + { 6, 0x989c, { 0x00009688, 0x00009688, 0x00009688 } }, + { 6, 0x98c4, { 0x00000001, 0x00000001, 0x00000001 } }, + { 7, 0x989c, { 0x00006400, 0x00006400, 0x00006400 } }, + { 7, 0x989c, { 0x00000800, 0x00000800, 0x00000800 } }, + { 7, 0x98cc, { 0x0000000e, 0x0000000e, 0x0000000e } }, +}; + +/* + * TODO: Handle the few differences with swan during + * bank modification and get rid of this + */ +static const struct ath5k_ini_rfbuffer rfb_2417[] = { + /* BANK / C.R. A/XR B G */ + { 1, 0x98d4, { 0x00000020, 0x00000020, 0x00000020 } }, + { 2, 0x98d0, { 0x02001408, 0x02001408, 0x02001408 } }, + { 3, 0x98dc, { 0x00a020c0, 0x00e020c0, 0x00e020c0 } }, + { 6, 0x989c, { 0x10000000, 0x10000000, 0x10000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x002a0000, 0x002a0000, 0x002a0000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00100000, 0x00100000, 0x00100000 } }, + { 6, 0x989c, { 0x00020000, 0x00020000, 0x00020000 } }, + { 6, 0x989c, { 0x00730000, 0x00730000, 0x00730000 } }, + { 6, 0x989c, { 0x00f80000, 0x00f80000, 0x00f80000 } }, + { 6, 0x989c, { 0x00e70000, 0x80e70000, 0x80e70000 } }, + { 6, 0x989c, { 0x00140000, 0x00140000, 0x00140000 } }, + { 6, 0x989c, { 0x00910040, 0x00910040, 0x00910040 } }, + { 6, 0x989c, { 0x0007001a, 0x0207001a, 0x0207001a } }, + { 6, 0x989c, { 0x00410000, 0x00410000, 0x00410000 } }, + { 6, 0x989c, { 0x00810000, 0x00810060, 0x00810060 } }, + { 6, 0x989c, { 0x00020800, 0x00020803, 0x00020803 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00000000, 0x00000000, 0x00000000 } }, + { 6, 0x989c, { 0x00001660, 0x00001660, 0x00001660 } }, + { 6, 0x989c, { 0x00001688, 0x00001688, 0x00001688 } }, + { 6, 0x98c4, { 0x00000001, 0x00000001, 0x00000001 } }, + { 7, 0x989c, { 0x00006400, 0x00006400, 0x00006400 } }, + { 7, 0x989c, { 0x00000800, 0x00000800, 0x00000800 } }, + { 7, 0x98cc, { 0x0000000e, 0x0000000e, 0x0000000e } }, +}; diff --git a/drivers/net/wireless/ath/ath5k/rfgain.h b/drivers/net/wireless/ath/ath5k/rfgain.h new file mode 100644 index 0000000..4d21df0 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/rfgain.h @@ -0,0 +1,534 @@ +/* + * RF Gain optimization + * + * Copyright (c) 2004-2009 Reyk Floeter + * Copyright (c) 2006-2009 Nick Kossifidis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +/** + * struct ath5k_ini_rfgain - RF Gain table + * @rfg_register: RF Gain register address + * @rfg_value: Register value for 5 and 2GHz + * + * Mode-specific RF Gain table (64bytes) for RF5111/5112 + * (RF5110 only comes with AR5210 and only supports a/turbo a mode so initial + * RF Gain values are included in AR5K_AR5210_INI) + */ +struct ath5k_ini_rfgain { + u16 rfg_register; + u32 rfg_value[2]; /* [freq (see below)] */ +}; + +/* Initial RF Gain settings for RF5111 */ +static const struct ath5k_ini_rfgain rfgain_5111[] = { + /* 5GHz 2GHz */ + { AR5K_RF_GAIN(0), { 0x000001a9, 0x00000000 } }, + { AR5K_RF_GAIN(1), { 0x000001e9, 0x00000040 } }, + { AR5K_RF_GAIN(2), { 0x00000029, 0x00000080 } }, + { AR5K_RF_GAIN(3), { 0x00000069, 0x00000150 } }, + { AR5K_RF_GAIN(4), { 0x00000199, 0x00000190 } }, + { AR5K_RF_GAIN(5), { 0x000001d9, 0x000001d0 } }, + { AR5K_RF_GAIN(6), { 0x00000019, 0x00000010 } }, + { AR5K_RF_GAIN(7), { 0x00000059, 0x00000044 } }, + { AR5K_RF_GAIN(8), { 0x00000099, 0x00000084 } }, + { AR5K_RF_GAIN(9), { 0x000001a5, 0x00000148 } }, + { AR5K_RF_GAIN(10), { 0x000001e5, 0x00000188 } }, + { AR5K_RF_GAIN(11), { 0x00000025, 0x000001c8 } }, + { AR5K_RF_GAIN(12), { 0x000001c8, 0x00000014 } }, + { AR5K_RF_GAIN(13), { 0x00000008, 0x00000042 } }, + { AR5K_RF_GAIN(14), { 0x00000048, 0x00000082 } }, + { AR5K_RF_GAIN(15), { 0x00000088, 0x00000178 } }, + { AR5K_RF_GAIN(16), { 0x00000198, 0x000001b8 } }, + { AR5K_RF_GAIN(17), { 0x000001d8, 0x000001f8 } }, + { AR5K_RF_GAIN(18), { 0x00000018, 0x00000012 } }, + { AR5K_RF_GAIN(19), { 0x00000058, 0x00000052 } }, + { AR5K_RF_GAIN(20), { 0x00000098, 0x00000092 } }, + { AR5K_RF_GAIN(21), { 0x000001a4, 0x0000017c } }, + { AR5K_RF_GAIN(22), { 0x000001e4, 0x000001bc } }, + { AR5K_RF_GAIN(23), { 0x00000024, 0x000001fc } }, + { AR5K_RF_GAIN(24), { 0x00000064, 0x0000000a } }, + { AR5K_RF_GAIN(25), { 0x000000a4, 0x0000004a } }, + { AR5K_RF_GAIN(26), { 0x000000e4, 0x0000008a } }, + { AR5K_RF_GAIN(27), { 0x0000010a, 0x0000015a } }, + { AR5K_RF_GAIN(28), { 0x0000014a, 0x0000019a } }, + { AR5K_RF_GAIN(29), { 0x0000018a, 0x000001da } }, + { AR5K_RF_GAIN(30), { 0x000001ca, 0x0000000e } }, + { AR5K_RF_GAIN(31), { 0x0000000a, 0x0000004e } }, + { AR5K_RF_GAIN(32), { 0x0000004a, 0x0000008e } }, + { AR5K_RF_GAIN(33), { 0x0000008a, 0x0000015e } }, + { AR5K_RF_GAIN(34), { 0x000001ba, 0x0000019e } }, + { AR5K_RF_GAIN(35), { 0x000001fa, 0x000001de } }, + { AR5K_RF_GAIN(36), { 0x0000003a, 0x00000009 } }, + { AR5K_RF_GAIN(37), { 0x0000007a, 0x00000049 } }, + { AR5K_RF_GAIN(38), { 0x00000186, 0x00000089 } }, + { AR5K_RF_GAIN(39), { 0x000001c6, 0x00000179 } }, + { AR5K_RF_GAIN(40), { 0x00000006, 0x000001b9 } }, + { AR5K_RF_GAIN(41), { 0x00000046, 0x000001f9 } }, + { AR5K_RF_GAIN(42), { 0x00000086, 0x00000039 } }, + { AR5K_RF_GAIN(43), { 0x000000c6, 0x00000079 } }, + { AR5K_RF_GAIN(44), { 0x000000c6, 0x000000b9 } }, + { AR5K_RF_GAIN(45), { 0x000000c6, 0x000001bd } }, + { AR5K_RF_GAIN(46), { 0x000000c6, 0x000001fd } }, + { AR5K_RF_GAIN(47), { 0x000000c6, 0x0000003d } }, + { AR5K_RF_GAIN(48), { 0x000000c6, 0x0000007d } }, + { AR5K_RF_GAIN(49), { 0x000000c6, 0x000000bd } }, + { AR5K_RF_GAIN(50), { 0x000000c6, 0x000000fd } }, + { AR5K_RF_GAIN(51), { 0x000000c6, 0x000000fd } }, + { AR5K_RF_GAIN(52), { 0x000000c6, 0x000000fd } }, + { AR5K_RF_GAIN(53), { 0x000000c6, 0x000000fd } }, + { AR5K_RF_GAIN(54), { 0x000000c6, 0x000000fd } }, + { AR5K_RF_GAIN(55), { 0x000000c6, 0x000000fd } }, + { AR5K_RF_GAIN(56), { 0x000000c6, 0x000000fd } }, + { AR5K_RF_GAIN(57), { 0x000000c6, 0x000000fd } }, + { AR5K_RF_GAIN(58), { 0x000000c6, 0x000000fd } }, + { AR5K_RF_GAIN(59), { 0x000000c6, 0x000000fd } }, + { AR5K_RF_GAIN(60), { 0x000000c6, 0x000000fd } }, + { AR5K_RF_GAIN(61), { 0x000000c6, 0x000000fd } }, + { AR5K_RF_GAIN(62), { 0x000000c6, 0x000000fd } }, + { AR5K_RF_GAIN(63), { 0x000000c6, 0x000000fd } }, +}; + +/* Initial RF Gain settings for RF5112 */ +static const struct ath5k_ini_rfgain rfgain_5112[] = { + /* 5GHz 2GHz */ + { AR5K_RF_GAIN(0), { 0x00000007, 0x00000007 } }, + { AR5K_RF_GAIN(1), { 0x00000047, 0x00000047 } }, + { AR5K_RF_GAIN(2), { 0x00000087, 0x00000087 } }, + { AR5K_RF_GAIN(3), { 0x000001a0, 0x000001a0 } }, + { AR5K_RF_GAIN(4), { 0x000001e0, 0x000001e0 } }, + { AR5K_RF_GAIN(5), { 0x00000020, 0x00000020 } }, + { AR5K_RF_GAIN(6), { 0x00000060, 0x00000060 } }, + { AR5K_RF_GAIN(7), { 0x000001a1, 0x000001a1 } }, + { AR5K_RF_GAIN(8), { 0x000001e1, 0x000001e1 } }, + { AR5K_RF_GAIN(9), { 0x00000021, 0x00000021 } }, + { AR5K_RF_GAIN(10), { 0x00000061, 0x00000061 } }, + { AR5K_RF_GAIN(11), { 0x00000162, 0x00000162 } }, + { AR5K_RF_GAIN(12), { 0x000001a2, 0x000001a2 } }, + { AR5K_RF_GAIN(13), { 0x000001e2, 0x000001e2 } }, + { AR5K_RF_GAIN(14), { 0x00000022, 0x00000022 } }, + { AR5K_RF_GAIN(15), { 0x00000062, 0x00000062 } }, + { AR5K_RF_GAIN(16), { 0x00000163, 0x00000163 } }, + { AR5K_RF_GAIN(17), { 0x000001a3, 0x000001a3 } }, + { AR5K_RF_GAIN(18), { 0x000001e3, 0x000001e3 } }, + { AR5K_RF_GAIN(19), { 0x00000023, 0x00000023 } }, + { AR5K_RF_GAIN(20), { 0x00000063, 0x00000063 } }, + { AR5K_RF_GAIN(21), { 0x00000184, 0x00000184 } }, + { AR5K_RF_GAIN(22), { 0x000001c4, 0x000001c4 } }, + { AR5K_RF_GAIN(23), { 0x00000004, 0x00000004 } }, + { AR5K_RF_GAIN(24), { 0x000001ea, 0x0000000b } }, + { AR5K_RF_GAIN(25), { 0x0000002a, 0x0000004b } }, + { AR5K_RF_GAIN(26), { 0x0000006a, 0x0000008b } }, + { AR5K_RF_GAIN(27), { 0x000000aa, 0x000001ac } }, + { AR5K_RF_GAIN(28), { 0x000001ab, 0x000001ec } }, + { AR5K_RF_GAIN(29), { 0x000001eb, 0x0000002c } }, + { AR5K_RF_GAIN(30), { 0x0000002b, 0x00000012 } }, + { AR5K_RF_GAIN(31), { 0x0000006b, 0x00000052 } }, + { AR5K_RF_GAIN(32), { 0x000000ab, 0x00000092 } }, + { AR5K_RF_GAIN(33), { 0x000001ac, 0x00000193 } }, + { AR5K_RF_GAIN(34), { 0x000001ec, 0x000001d3 } }, + { AR5K_RF_GAIN(35), { 0x0000002c, 0x00000013 } }, + { AR5K_RF_GAIN(36), { 0x0000003a, 0x00000053 } }, + { AR5K_RF_GAIN(37), { 0x0000007a, 0x00000093 } }, + { AR5K_RF_GAIN(38), { 0x000000ba, 0x00000194 } }, + { AR5K_RF_GAIN(39), { 0x000001bb, 0x000001d4 } }, + { AR5K_RF_GAIN(40), { 0x000001fb, 0x00000014 } }, + { AR5K_RF_GAIN(41), { 0x0000003b, 0x0000003a } }, + { AR5K_RF_GAIN(42), { 0x0000007b, 0x0000007a } }, + { AR5K_RF_GAIN(43), { 0x000000bb, 0x000000ba } }, + { AR5K_RF_GAIN(44), { 0x000001bc, 0x000001bb } }, + { AR5K_RF_GAIN(45), { 0x000001fc, 0x000001fb } }, + { AR5K_RF_GAIN(46), { 0x0000003c, 0x0000003b } }, + { AR5K_RF_GAIN(47), { 0x0000007c, 0x0000007b } }, + { AR5K_RF_GAIN(48), { 0x000000bc, 0x000000bb } }, + { AR5K_RF_GAIN(49), { 0x000000fc, 0x000001bc } }, + { AR5K_RF_GAIN(50), { 0x000000fc, 0x000001fc } }, + { AR5K_RF_GAIN(51), { 0x000000fc, 0x0000003c } }, + { AR5K_RF_GAIN(52), { 0x000000fc, 0x0000007c } }, + { AR5K_RF_GAIN(53), { 0x000000fc, 0x000000bc } }, + { AR5K_RF_GAIN(54), { 0x000000fc, 0x000000fc } }, + { AR5K_RF_GAIN(55), { 0x000000fc, 0x000000fc } }, + { AR5K_RF_GAIN(56), { 0x000000fc, 0x000000fc } }, + { AR5K_RF_GAIN(57), { 0x000000fc, 0x000000fc } }, + { AR5K_RF_GAIN(58), { 0x000000fc, 0x000000fc } }, + { AR5K_RF_GAIN(59), { 0x000000fc, 0x000000fc } }, + { AR5K_RF_GAIN(60), { 0x000000fc, 0x000000fc } }, + { AR5K_RF_GAIN(61), { 0x000000fc, 0x000000fc } }, + { AR5K_RF_GAIN(62), { 0x000000fc, 0x000000fc } }, + { AR5K_RF_GAIN(63), { 0x000000fc, 0x000000fc } }, +}; + +/* Initial RF Gain settings for RF2413 */ +static const struct ath5k_ini_rfgain rfgain_2413[] = { + { AR5K_RF_GAIN(0), { 0x00000000, 0x00000000 } }, + { AR5K_RF_GAIN(1), { 0x00000000, 0x00000040 } }, + { AR5K_RF_GAIN(2), { 0x00000000, 0x00000080 } }, + { AR5K_RF_GAIN(3), { 0x00000000, 0x00000181 } }, + { AR5K_RF_GAIN(4), { 0x00000000, 0x000001c1 } }, + { AR5K_RF_GAIN(5), { 0x00000000, 0x00000001 } }, + { AR5K_RF_GAIN(6), { 0x00000000, 0x00000041 } }, + { AR5K_RF_GAIN(7), { 0x00000000, 0x00000081 } }, + { AR5K_RF_GAIN(8), { 0x00000000, 0x00000168 } }, + { AR5K_RF_GAIN(9), { 0x00000000, 0x000001a8 } }, + { AR5K_RF_GAIN(10), { 0x00000000, 0x000001e8 } }, + { AR5K_RF_GAIN(11), { 0x00000000, 0x00000028 } }, + { AR5K_RF_GAIN(12), { 0x00000000, 0x00000068 } }, + { AR5K_RF_GAIN(13), { 0x00000000, 0x00000189 } }, + { AR5K_RF_GAIN(14), { 0x00000000, 0x000001c9 } }, + { AR5K_RF_GAIN(15), { 0x00000000, 0x00000009 } }, + { AR5K_RF_GAIN(16), { 0x00000000, 0x00000049 } }, + { AR5K_RF_GAIN(17), { 0x00000000, 0x00000089 } }, + { AR5K_RF_GAIN(18), { 0x00000000, 0x00000190 } }, + { AR5K_RF_GAIN(19), { 0x00000000, 0x000001d0 } }, + { AR5K_RF_GAIN(20), { 0x00000000, 0x00000010 } }, + { AR5K_RF_GAIN(21), { 0x00000000, 0x00000050 } }, + { AR5K_RF_GAIN(22), { 0x00000000, 0x00000090 } }, + { AR5K_RF_GAIN(23), { 0x00000000, 0x00000191 } }, + { AR5K_RF_GAIN(24), { 0x00000000, 0x000001d1 } }, + { AR5K_RF_GAIN(25), { 0x00000000, 0x00000011 } }, + { AR5K_RF_GAIN(26), { 0x00000000, 0x00000051 } }, + { AR5K_RF_GAIN(27), { 0x00000000, 0x00000091 } }, + { AR5K_RF_GAIN(28), { 0x00000000, 0x00000178 } }, + { AR5K_RF_GAIN(29), { 0x00000000, 0x000001b8 } }, + { AR5K_RF_GAIN(30), { 0x00000000, 0x000001f8 } }, + { AR5K_RF_GAIN(31), { 0x00000000, 0x00000038 } }, + { AR5K_RF_GAIN(32), { 0x00000000, 0x00000078 } }, + { AR5K_RF_GAIN(33), { 0x00000000, 0x00000199 } }, + { AR5K_RF_GAIN(34), { 0x00000000, 0x000001d9 } }, + { AR5K_RF_GAIN(35), { 0x00000000, 0x00000019 } }, + { AR5K_RF_GAIN(36), { 0x00000000, 0x00000059 } }, + { AR5K_RF_GAIN(37), { 0x00000000, 0x00000099 } }, + { AR5K_RF_GAIN(38), { 0x00000000, 0x000000d9 } }, + { AR5K_RF_GAIN(39), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(40), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(41), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(42), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(43), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(44), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(45), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(46), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(47), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(48), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(49), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(50), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(51), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(52), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(53), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(54), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(55), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(56), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(57), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(58), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(59), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(60), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(61), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(62), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(63), { 0x00000000, 0x000000f9 } }, +}; + +/* Initial RF Gain settings for AR2316 */ +static const struct ath5k_ini_rfgain rfgain_2316[] = { + { AR5K_RF_GAIN(0), { 0x00000000, 0x00000000 } }, + { AR5K_RF_GAIN(1), { 0x00000000, 0x00000040 } }, + { AR5K_RF_GAIN(2), { 0x00000000, 0x00000080 } }, + { AR5K_RF_GAIN(3), { 0x00000000, 0x000000c0 } }, + { AR5K_RF_GAIN(4), { 0x00000000, 0x000000e0 } }, + { AR5K_RF_GAIN(5), { 0x00000000, 0x000000e0 } }, + { AR5K_RF_GAIN(6), { 0x00000000, 0x00000128 } }, + { AR5K_RF_GAIN(7), { 0x00000000, 0x00000128 } }, + { AR5K_RF_GAIN(8), { 0x00000000, 0x00000128 } }, + { AR5K_RF_GAIN(9), { 0x00000000, 0x00000168 } }, + { AR5K_RF_GAIN(10), { 0x00000000, 0x000001a8 } }, + { AR5K_RF_GAIN(11), { 0x00000000, 0x000001e8 } }, + { AR5K_RF_GAIN(12), { 0x00000000, 0x00000028 } }, + { AR5K_RF_GAIN(13), { 0x00000000, 0x00000068 } }, + { AR5K_RF_GAIN(14), { 0x00000000, 0x000000a8 } }, + { AR5K_RF_GAIN(15), { 0x00000000, 0x000000e8 } }, + { AR5K_RF_GAIN(16), { 0x00000000, 0x000000e8 } }, + { AR5K_RF_GAIN(17), { 0x00000000, 0x00000130 } }, + { AR5K_RF_GAIN(18), { 0x00000000, 0x00000130 } }, + { AR5K_RF_GAIN(19), { 0x00000000, 0x00000170 } }, + { AR5K_RF_GAIN(20), { 0x00000000, 0x000001b0 } }, + { AR5K_RF_GAIN(21), { 0x00000000, 0x000001f0 } }, + { AR5K_RF_GAIN(22), { 0x00000000, 0x00000030 } }, + { AR5K_RF_GAIN(23), { 0x00000000, 0x00000070 } }, + { AR5K_RF_GAIN(24), { 0x00000000, 0x000000b0 } }, + { AR5K_RF_GAIN(25), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(26), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(27), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(28), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(29), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(30), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(31), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(32), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(33), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(34), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(35), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(36), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(37), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(38), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(39), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(40), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(41), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(42), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(43), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(44), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(45), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(46), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(47), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(48), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(49), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(50), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(51), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(52), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(53), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(54), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(55), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(56), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(57), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(58), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(59), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(60), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(61), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(62), { 0x00000000, 0x000000f0 } }, + { AR5K_RF_GAIN(63), { 0x00000000, 0x000000f0 } }, +}; + + +/* Initial RF Gain settings for RF5413 */ +static const struct ath5k_ini_rfgain rfgain_5413[] = { + /* 5GHz 2GHz */ + { AR5K_RF_GAIN(0), { 0x00000000, 0x00000000 } }, + { AR5K_RF_GAIN(1), { 0x00000040, 0x00000040 } }, + { AR5K_RF_GAIN(2), { 0x00000080, 0x00000080 } }, + { AR5K_RF_GAIN(3), { 0x000001a1, 0x00000161 } }, + { AR5K_RF_GAIN(4), { 0x000001e1, 0x000001a1 } }, + { AR5K_RF_GAIN(5), { 0x00000021, 0x000001e1 } }, + { AR5K_RF_GAIN(6), { 0x00000061, 0x00000021 } }, + { AR5K_RF_GAIN(7), { 0x00000188, 0x00000061 } }, + { AR5K_RF_GAIN(8), { 0x000001c8, 0x00000188 } }, + { AR5K_RF_GAIN(9), { 0x00000008, 0x000001c8 } }, + { AR5K_RF_GAIN(10), { 0x00000048, 0x00000008 } }, + { AR5K_RF_GAIN(11), { 0x00000088, 0x00000048 } }, + { AR5K_RF_GAIN(12), { 0x000001a9, 0x00000088 } }, + { AR5K_RF_GAIN(13), { 0x000001e9, 0x00000169 } }, + { AR5K_RF_GAIN(14), { 0x00000029, 0x000001a9 } }, + { AR5K_RF_GAIN(15), { 0x00000069, 0x000001e9 } }, + { AR5K_RF_GAIN(16), { 0x000001d0, 0x00000029 } }, + { AR5K_RF_GAIN(17), { 0x00000010, 0x00000069 } }, + { AR5K_RF_GAIN(18), { 0x00000050, 0x00000190 } }, + { AR5K_RF_GAIN(19), { 0x00000090, 0x000001d0 } }, + { AR5K_RF_GAIN(20), { 0x000001b1, 0x00000010 } }, + { AR5K_RF_GAIN(21), { 0x000001f1, 0x00000050 } }, + { AR5K_RF_GAIN(22), { 0x00000031, 0x00000090 } }, + { AR5K_RF_GAIN(23), { 0x00000071, 0x00000171 } }, + { AR5K_RF_GAIN(24), { 0x000001b8, 0x000001b1 } }, + { AR5K_RF_GAIN(25), { 0x000001f8, 0x000001f1 } }, + { AR5K_RF_GAIN(26), { 0x00000038, 0x00000031 } }, + { AR5K_RF_GAIN(27), { 0x00000078, 0x00000071 } }, + { AR5K_RF_GAIN(28), { 0x00000199, 0x00000198 } }, + { AR5K_RF_GAIN(29), { 0x000001d9, 0x000001d8 } }, + { AR5K_RF_GAIN(30), { 0x00000019, 0x00000018 } }, + { AR5K_RF_GAIN(31), { 0x00000059, 0x00000058 } }, + { AR5K_RF_GAIN(32), { 0x00000099, 0x00000098 } }, + { AR5K_RF_GAIN(33), { 0x000000d9, 0x00000179 } }, + { AR5K_RF_GAIN(34), { 0x000000f9, 0x000001b9 } }, + { AR5K_RF_GAIN(35), { 0x000000f9, 0x000001f9 } }, + { AR5K_RF_GAIN(36), { 0x000000f9, 0x00000039 } }, + { AR5K_RF_GAIN(37), { 0x000000f9, 0x00000079 } }, + { AR5K_RF_GAIN(38), { 0x000000f9, 0x000000b9 } }, + { AR5K_RF_GAIN(39), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(40), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(41), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(42), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(43), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(44), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(45), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(46), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(47), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(48), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(49), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(50), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(51), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(52), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(53), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(54), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(55), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(56), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(57), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(58), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(59), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(60), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(61), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(62), { 0x000000f9, 0x000000f9 } }, + { AR5K_RF_GAIN(63), { 0x000000f9, 0x000000f9 } }, +}; + + +/* Initial RF Gain settings for RF2425 */ +static const struct ath5k_ini_rfgain rfgain_2425[] = { + { AR5K_RF_GAIN(0), { 0x00000000, 0x00000000 } }, + { AR5K_RF_GAIN(1), { 0x00000000, 0x00000040 } }, + { AR5K_RF_GAIN(2), { 0x00000000, 0x00000080 } }, + { AR5K_RF_GAIN(3), { 0x00000000, 0x00000181 } }, + { AR5K_RF_GAIN(4), { 0x00000000, 0x000001c1 } }, + { AR5K_RF_GAIN(5), { 0x00000000, 0x00000001 } }, + { AR5K_RF_GAIN(6), { 0x00000000, 0x00000041 } }, + { AR5K_RF_GAIN(7), { 0x00000000, 0x00000081 } }, + { AR5K_RF_GAIN(8), { 0x00000000, 0x00000188 } }, + { AR5K_RF_GAIN(9), { 0x00000000, 0x000001c8 } }, + { AR5K_RF_GAIN(10), { 0x00000000, 0x00000008 } }, + { AR5K_RF_GAIN(11), { 0x00000000, 0x00000048 } }, + { AR5K_RF_GAIN(12), { 0x00000000, 0x00000088 } }, + { AR5K_RF_GAIN(13), { 0x00000000, 0x00000189 } }, + { AR5K_RF_GAIN(14), { 0x00000000, 0x000001c9 } }, + { AR5K_RF_GAIN(15), { 0x00000000, 0x00000009 } }, + { AR5K_RF_GAIN(16), { 0x00000000, 0x00000049 } }, + { AR5K_RF_GAIN(17), { 0x00000000, 0x00000089 } }, + { AR5K_RF_GAIN(18), { 0x00000000, 0x000001b0 } }, + { AR5K_RF_GAIN(19), { 0x00000000, 0x000001f0 } }, + { AR5K_RF_GAIN(20), { 0x00000000, 0x00000030 } }, + { AR5K_RF_GAIN(21), { 0x00000000, 0x00000070 } }, + { AR5K_RF_GAIN(22), { 0x00000000, 0x00000171 } }, + { AR5K_RF_GAIN(23), { 0x00000000, 0x000001b1 } }, + { AR5K_RF_GAIN(24), { 0x00000000, 0x000001f1 } }, + { AR5K_RF_GAIN(25), { 0x00000000, 0x00000031 } }, + { AR5K_RF_GAIN(26), { 0x00000000, 0x00000071 } }, + { AR5K_RF_GAIN(27), { 0x00000000, 0x000001b8 } }, + { AR5K_RF_GAIN(28), { 0x00000000, 0x000001f8 } }, + { AR5K_RF_GAIN(29), { 0x00000000, 0x00000038 } }, + { AR5K_RF_GAIN(30), { 0x00000000, 0x00000078 } }, + { AR5K_RF_GAIN(31), { 0x00000000, 0x000000b8 } }, + { AR5K_RF_GAIN(32), { 0x00000000, 0x000001b9 } }, + { AR5K_RF_GAIN(33), { 0x00000000, 0x000001f9 } }, + { AR5K_RF_GAIN(34), { 0x00000000, 0x00000039 } }, + { AR5K_RF_GAIN(35), { 0x00000000, 0x00000079 } }, + { AR5K_RF_GAIN(36), { 0x00000000, 0x000000b9 } }, + { AR5K_RF_GAIN(37), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(38), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(39), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(40), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(41), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(42), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(43), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(44), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(45), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(46), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(47), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(48), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(49), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(50), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(51), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(52), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(53), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(54), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(55), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(56), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(57), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(58), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(59), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(60), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(61), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(62), { 0x00000000, 0x000000f9 } }, + { AR5K_RF_GAIN(63), { 0x00000000, 0x000000f9 } }, +}; + +#define AR5K_GAIN_CRN_FIX_BITS_5111 4 +#define AR5K_GAIN_CRN_FIX_BITS_5112 7 +#define AR5K_GAIN_CRN_MAX_FIX_BITS AR5K_GAIN_CRN_FIX_BITS_5112 +#define AR5K_GAIN_DYN_ADJUST_HI_MARGIN 15 +#define AR5K_GAIN_DYN_ADJUST_LO_MARGIN 20 +#define AR5K_GAIN_CCK_PROBE_CORR 5 +#define AR5K_GAIN_CCK_OFDM_GAIN_DELTA 15 +#define AR5K_GAIN_STEP_COUNT 10 + +/* Check if our current measurement is inside our + * current variable attenuation window */ +#define AR5K_GAIN_CHECK_ADJUST(_g) \ + ((_g)->g_current <= (_g)->g_low || (_g)->g_current >= (_g)->g_high) + +/** + * struct ath5k_gain_opt_step - An RF gain optimization step + * @gos_param: Set of parameters + * @gos_gain: Gain + */ +struct ath5k_gain_opt_step { + s8 gos_param[AR5K_GAIN_CRN_MAX_FIX_BITS]; + s8 gos_gain; +}; + +/** + * struct ath5k_gain_opt - RF Gain optimization ladder + * @go_default: The default step + * @go_steps_count: How many optimization steps + * @go_step: Array of &struct ath5k_gain_opt_step + */ +struct ath5k_gain_opt { + u8 go_default; + u8 go_steps_count; + const struct ath5k_gain_opt_step go_step[AR5K_GAIN_STEP_COUNT]; +}; + + +/* + * RF5111 + * Parameters on gos_param: + * 1) Tx clip PHY register + * 2) PWD 90 RF register + * 3) PWD 84 RF register + * 4) RFGainSel RF register + */ +static const struct ath5k_gain_opt rfgain_opt_5111 = { + 4, + 9, + { + { { 4, 1, 1, 1 }, 6 }, + { { 4, 0, 1, 1 }, 4 }, + { { 3, 1, 1, 1 }, 3 }, + { { 4, 0, 0, 1 }, 1 }, + { { 4, 1, 1, 0 }, 0 }, + { { 4, 0, 1, 0 }, -2 }, + { { 3, 1, 1, 0 }, -3 }, + { { 4, 0, 0, 0 }, -4 }, + { { 2, 1, 1, 0 }, -6 } + } +}; + +/* + * RF5112 + * Parameters on gos_param: + * 1) Mixgain ovr RF register + * 2) PWD 138 RF register + * 3) PWD 137 RF register + * 4) PWD 136 RF register + * 5) PWD 132 RF register + * 6) PWD 131 RF register + * 7) PWD 130 RF register + */ +static const struct ath5k_gain_opt rfgain_opt_5112 = { + 1, + 8, + { + { { 3, 0, 0, 0, 0, 0, 0 }, 6 }, + { { 2, 0, 0, 0, 0, 0, 0 }, 0 }, + { { 1, 0, 0, 0, 0, 0, 0 }, -3 }, + { { 0, 0, 0, 0, 0, 0, 0 }, -6 }, + { { 0, 1, 1, 0, 0, 0, 0 }, -8 }, + { { 0, 1, 1, 0, 1, 1, 0 }, -10 }, + { { 0, 1, 0, 1, 1, 1, 0 }, -13 }, + { { 0, 1, 0, 1, 1, 0, 1 }, -16 }, + } +}; + diff --git a/drivers/net/wireless/ath/ath5k/rfkill.c b/drivers/net/wireless/ath/ath5k/rfkill.c new file mode 100644 index 0000000..270a319 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/rfkill.c @@ -0,0 +1,116 @@ +/* + * RFKILL support for ath5k + * + * Copyright (c) 2009 Tobias Doerffel + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + */ + +#include "ath5k.h" + + +static inline void ath5k_rfkill_disable(struct ath5k_hw *ah) +{ + ATH5K_DBG(ah, ATH5K_DEBUG_ANY, "rfkill disable (gpio:%d polarity:%d)\n", + ah->rf_kill.gpio, ah->rf_kill.polarity); + ath5k_hw_set_gpio_output(ah, ah->rf_kill.gpio); + ath5k_hw_set_gpio(ah, ah->rf_kill.gpio, !ah->rf_kill.polarity); +} + + +static inline void ath5k_rfkill_enable(struct ath5k_hw *ah) +{ + ATH5K_DBG(ah, ATH5K_DEBUG_ANY, "rfkill enable (gpio:%d polarity:%d)\n", + ah->rf_kill.gpio, ah->rf_kill.polarity); + ath5k_hw_set_gpio_output(ah, ah->rf_kill.gpio); + ath5k_hw_set_gpio(ah, ah->rf_kill.gpio, ah->rf_kill.polarity); +} + +static inline void ath5k_rfkill_set_intr(struct ath5k_hw *ah, bool enable) +{ + u32 curval; + + ath5k_hw_set_gpio_input(ah, ah->rf_kill.gpio); + curval = ath5k_hw_get_gpio(ah, ah->rf_kill.gpio); + ath5k_hw_set_gpio_intr(ah, ah->rf_kill.gpio, enable ? + !!curval : !curval); +} + +static bool +ath5k_is_rfkill_set(struct ath5k_hw *ah) +{ + /* configuring GPIO for input for some reason disables rfkill */ + /*ath5k_hw_set_gpio_input(ah, ah->rf_kill.gpio);*/ + return ath5k_hw_get_gpio(ah, ah->rf_kill.gpio) == + ah->rf_kill.polarity; +} + +static void +ath5k_tasklet_rfkill_toggle(unsigned long data) +{ + struct ath5k_hw *ah = (void *)data; + bool blocked; + + blocked = ath5k_is_rfkill_set(ah); + wiphy_rfkill_set_hw_state(ah->hw->wiphy, blocked); +} + + +void +ath5k_rfkill_hw_start(struct ath5k_hw *ah) +{ + /* read rfkill GPIO configuration from EEPROM header */ + ah->rf_kill.gpio = ah->ah_capabilities.cap_eeprom.ee_rfkill_pin; + ah->rf_kill.polarity = ah->ah_capabilities.cap_eeprom.ee_rfkill_pol; + + tasklet_init(&ah->rf_kill.toggleq, ath5k_tasklet_rfkill_toggle, + (unsigned long)ah); + + ath5k_rfkill_disable(ah); + + /* enable interrupt for rfkill switch */ + if (AR5K_EEPROM_HDR_RFKILL(ah->ah_capabilities.cap_eeprom.ee_header)) + ath5k_rfkill_set_intr(ah, true); +} + + +void +ath5k_rfkill_hw_stop(struct ath5k_hw *ah) +{ + /* disable interrupt for rfkill switch */ + if (AR5K_EEPROM_HDR_RFKILL(ah->ah_capabilities.cap_eeprom.ee_header)) + ath5k_rfkill_set_intr(ah, false); + + tasklet_kill(&ah->rf_kill.toggleq); + + /* enable RFKILL when stopping HW so Wifi LED is turned off */ + ath5k_rfkill_enable(ah); +} + diff --git a/drivers/net/wireless/ath/ath5k/sysfs.c b/drivers/net/wireless/ath/ath5k/sysfs.c new file mode 100644 index 0000000..04cf0ca --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/sysfs.c @@ -0,0 +1,122 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include + +#include "ath5k.h" +#include "reg.h" + +#define SIMPLE_SHOW_STORE(name, get, set) \ +static ssize_t ath5k_attr_show_##name(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct ieee80211_hw *hw = dev_get_drvdata(dev); \ + struct ath5k_hw *ah = hw->priv; \ + return snprintf(buf, PAGE_SIZE, "%d\n", get); \ +} \ + \ +static ssize_t ath5k_attr_store_##name(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct ieee80211_hw *hw = dev_get_drvdata(dev); \ + struct ath5k_hw *ah = hw->priv; \ + int val, ret; \ + \ + ret = kstrtoint(buf, 10, &val); \ + if (ret < 0) \ + return ret; \ + set(ah, val); \ + return count; \ +} \ +static DEVICE_ATTR(name, S_IRUGO | S_IWUSR, \ + ath5k_attr_show_##name, ath5k_attr_store_##name) + +#define SIMPLE_SHOW(name, get) \ +static ssize_t ath5k_attr_show_##name(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct ieee80211_hw *hw = dev_get_drvdata(dev); \ + struct ath5k_hw *ah = hw->priv; \ + return snprintf(buf, PAGE_SIZE, "%d\n", get); \ +} \ +static DEVICE_ATTR(name, S_IRUGO, ath5k_attr_show_##name, NULL) + +/*** ANI ***/ + +SIMPLE_SHOW_STORE(ani_mode, ah->ani_state.ani_mode, ath5k_ani_init); +SIMPLE_SHOW_STORE(noise_immunity_level, ah->ani_state.noise_imm_level, + ath5k_ani_set_noise_immunity_level); +SIMPLE_SHOW_STORE(spur_level, ah->ani_state.spur_level, + ath5k_ani_set_spur_immunity_level); +SIMPLE_SHOW_STORE(firstep_level, ah->ani_state.firstep_level, + ath5k_ani_set_firstep_level); +SIMPLE_SHOW_STORE(ofdm_weak_signal_detection, ah->ani_state.ofdm_weak_sig, + ath5k_ani_set_ofdm_weak_signal_detection); +SIMPLE_SHOW_STORE(cck_weak_signal_detection, ah->ani_state.cck_weak_sig, + ath5k_ani_set_cck_weak_signal_detection); +SIMPLE_SHOW(spur_level_max, ah->ani_state.max_spur_level); + +static ssize_t ath5k_attr_show_noise_immunity_level_max(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", ATH5K_ANI_MAX_NOISE_IMM_LVL); +} +static DEVICE_ATTR(noise_immunity_level_max, S_IRUGO, + ath5k_attr_show_noise_immunity_level_max, NULL); + +static ssize_t ath5k_attr_show_firstep_level_max(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", ATH5K_ANI_MAX_FIRSTEP_LVL); +} +static DEVICE_ATTR(firstep_level_max, S_IRUGO, + ath5k_attr_show_firstep_level_max, NULL); + +static struct attribute *ath5k_sysfs_entries_ani[] = { + &dev_attr_ani_mode.attr, + &dev_attr_noise_immunity_level.attr, + &dev_attr_spur_level.attr, + &dev_attr_firstep_level.attr, + &dev_attr_ofdm_weak_signal_detection.attr, + &dev_attr_cck_weak_signal_detection.attr, + &dev_attr_noise_immunity_level_max.attr, + &dev_attr_spur_level_max.attr, + &dev_attr_firstep_level_max.attr, + NULL +}; + +static struct attribute_group ath5k_attribute_group_ani = { + .name = "ani", + .attrs = ath5k_sysfs_entries_ani, +}; + + +/*** register / unregister ***/ + +int +ath5k_sysfs_register(struct ath5k_hw *ah) +{ + struct device *dev = ah->dev; + int err; + + err = sysfs_create_group(&dev->kobj, &ath5k_attribute_group_ani); + if (err) { + ATH5K_ERR(ah, "failed to create sysfs group\n"); + return err; + } + + return 0; +} + +void +ath5k_sysfs_unregister(struct ath5k_hw *ah) +{ + struct device *dev = ah->dev; + + sysfs_remove_group(&dev->kobj, &ath5k_attribute_group_ani); +} diff --git a/drivers/net/wireless/ath/ath5k/trace.h b/drivers/net/wireless/ath/ath5k/trace.h new file mode 100644 index 0000000..8053402 --- /dev/null +++ b/drivers/net/wireless/ath/ath5k/trace.h @@ -0,0 +1,106 @@ +#if !defined(__TRACE_ATH5K_H) || defined(TRACE_HEADER_MULTI_READ) +#define __TRACE_ATH5K_H + +#include + + +#if !defined(CPTCFG_ATH5K_TRACER) || defined(__CHECKER__) +#undef TRACE_EVENT +#define TRACE_EVENT(name, proto, ...) \ +static inline void trace_ ## name(proto) {} +#endif + +struct sk_buff; +struct ath5k_txq; +struct ath5k_tx_status; + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM ath5k + +TRACE_EVENT(ath5k_rx, + TP_PROTO(struct ath5k_hw *priv, struct sk_buff *skb), + TP_ARGS(priv, skb), + TP_STRUCT__entry( + __field(struct ath5k_hw *, priv) + __field(unsigned long, skbaddr) + __dynamic_array(u8, frame, skb->len) + ), + TP_fast_assign( + __entry->priv = priv; + __entry->skbaddr = (unsigned long) skb; + memcpy(__get_dynamic_array(frame), skb->data, skb->len); + ), + TP_printk( + "[%p] RX skb=%lx", __entry->priv, __entry->skbaddr + ) +); + +TRACE_EVENT(ath5k_tx, + TP_PROTO(struct ath5k_hw *priv, struct sk_buff *skb, + struct ath5k_txq *q), + + TP_ARGS(priv, skb, q), + + TP_STRUCT__entry( + __field(struct ath5k_hw *, priv) + __field(unsigned long, skbaddr) + __field(u8, qnum) + __dynamic_array(u8, frame, skb->len) + ), + + TP_fast_assign( + __entry->priv = priv; + __entry->skbaddr = (unsigned long) skb; + __entry->qnum = (u8) q->qnum; + memcpy(__get_dynamic_array(frame), skb->data, skb->len); + ), + + TP_printk( + "[%p] TX skb=%lx q=%d", __entry->priv, __entry->skbaddr, + __entry->qnum + ) +); + +TRACE_EVENT(ath5k_tx_complete, + TP_PROTO(struct ath5k_hw *priv, struct sk_buff *skb, + struct ath5k_txq *q, struct ath5k_tx_status *ts), + + TP_ARGS(priv, skb, q, ts), + + TP_STRUCT__entry( + __field(struct ath5k_hw *, priv) + __field(unsigned long, skbaddr) + __field(u8, qnum) + __field(u8, ts_status) + __field(s8, ts_rssi) + __field(u8, ts_antenna) + ), + + TP_fast_assign( + __entry->priv = priv; + __entry->skbaddr = (unsigned long) skb; + __entry->qnum = (u8) q->qnum; + __entry->ts_status = ts->ts_status; + __entry->ts_rssi = ts->ts_rssi; + __entry->ts_antenna = ts->ts_antenna; + ), + + TP_printk( + "[%p] TX end skb=%lx q=%d stat=%x rssi=%d ant=%x", + __entry->priv, __entry->skbaddr, __entry->qnum, + __entry->ts_status, __entry->ts_rssi, __entry->ts_antenna + ) +); + +#endif /* __TRACE_ATH5K_H */ + +#if defined(CPTCFG_ATH5K_TRACER) && !defined(__CHECKER__) + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +#include + +#endif diff --git a/drivers/net/wireless/ath/ath6kl/Kconfig b/drivers/net/wireless/ath/ath6kl/Kconfig new file mode 100644 index 0000000..cb5dab5 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/Kconfig @@ -0,0 +1,53 @@ +config ATH6KL + depends on !BACKPORT_KERNEL_2_6_27 + tristate "Atheros mobile chipsets support" + depends on m + +config ATH6KL_SDIO + tristate "Atheros ath6kl SDIO support" + depends on m + depends on ATH6KL + depends on MMC + depends on CFG80211 + ---help--- + This module adds support for wireless adapters based on + Atheros AR6003 and AR6004 chipsets running over SDIO. If you + choose to build it as a module, it will be called ath6kl_sdio. + Please note that AR6002 and AR6001 are not supported by this + driver. + +config ATH6KL_USB + tristate "Atheros ath6kl USB support" + depends on m + depends on ATH6KL + depends on USB + depends on CFG80211 + ---help--- + This module adds support for wireless adapters based on + Atheros AR6004 chipset running over USB. This is still under + implementation and it isn't functional. If you choose to + build it as a module, it will be called ath6kl_usb. + +config ATH6KL_DEBUG + bool "Atheros ath6kl debugging" + depends on ATH6KL + ---help--- + Enables debug support + +config ATH6KL_TRACING + bool "Atheros ath6kl tracing support" + depends on ATH6KL + depends on EVENT_TRACING + ---help--- + Select this to ath6kl use tracing infrastructure. + + If unsure, say Y to make it easier to debug problems. + +config ATH6KL_REGDOMAIN + bool "Atheros ath6kl regdomain support" + depends on ATH6KL + depends on CFG80211_CERTIFICATION_ONUS + ---help--- + Enabling this makes it possible to change the regdomain in + the firmware. This can be only enabled if regulatory requirements + are taken into account. diff --git a/drivers/net/wireless/ath/ath6kl/Makefile b/drivers/net/wireless/ath/ath6kl/Makefile new file mode 100644 index 0000000..e79afd5 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/Makefile @@ -0,0 +1,49 @@ +#------------------------------------------------------------------------------ +# Copyright (c) 2004-2011 Atheros Communications Inc. +# Copyright (c) 2011-2012 Qualcomm Atheros, Inc. +# All rights reserved. +# +# +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +# +# +# Author(s): ="Atheros" +#------------------------------------------------------------------------------ + +obj-$(CPTCFG_ATH6KL) += ath6kl_core.o +ath6kl_core-y += debug.o +ath6kl_core-y += hif.o +ath6kl_core-y += htc_mbox.o +ath6kl_core-y += htc_pipe.o +ath6kl_core-y += bmi.o +ath6kl_core-y += cfg80211.o +ath6kl_core-y += init.o +ath6kl_core-y += main.o +ath6kl_core-y += txrx.o +ath6kl_core-y += wmi.o +ath6kl_core-y += core.o +ath6kl_core-y += recovery.o + +ath6kl_core-$(CPTCFG_NL80211_TESTMODE) += testmode.o +ath6kl_core-$(CPTCFG_ATH6KL_TRACING) += trace.o + +obj-$(CPTCFG_ATH6KL_SDIO) += ath6kl_sdio.o +ath6kl_sdio-y += sdio.o + +obj-$(CPTCFG_ATH6KL_USB) += ath6kl_usb.o +ath6kl_usb-y += usb.o + +# for tracing framework to find trace.h +CFLAGS_trace.o := -I$(src) diff --git a/drivers/net/wireless/ath/ath6kl/bmi.c b/drivers/net/wireless/ath/ath6kl/bmi.c new file mode 100644 index 0000000..334dbd8 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/bmi.c @@ -0,0 +1,548 @@ +/* + * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "core.h" +#include "hif-ops.h" +#include "target.h" +#include "debug.h" + +int ath6kl_bmi_done(struct ath6kl *ar) +{ + int ret; + u32 cid = BMI_DONE; + + if (ar->bmi.done_sent) { + ath6kl_dbg(ATH6KL_DBG_BMI, "bmi done skipped\n"); + return 0; + } + + ar->bmi.done_sent = true; + + ret = ath6kl_hif_bmi_write(ar, (u8 *)&cid, sizeof(cid)); + if (ret) { + ath6kl_err("Unable to send bmi done: %d\n", ret); + return ret; + } + + return 0; +} + +int ath6kl_bmi_get_target_info(struct ath6kl *ar, + struct ath6kl_bmi_target_info *targ_info) +{ + int ret; + u32 cid = BMI_GET_TARGET_INFO; + + if (ar->bmi.done_sent) { + ath6kl_err("bmi done sent already, cmd %d disallowed\n", cid); + return -EACCES; + } + + ret = ath6kl_hif_bmi_write(ar, (u8 *)&cid, sizeof(cid)); + if (ret) { + ath6kl_err("Unable to send get target info: %d\n", ret); + return ret; + } + + if (ar->hif_type == ATH6KL_HIF_TYPE_USB) { + ret = ath6kl_hif_bmi_read(ar, (u8 *)targ_info, + sizeof(*targ_info)); + } else { + ret = ath6kl_hif_bmi_read(ar, (u8 *)&targ_info->version, + sizeof(targ_info->version)); + } + + if (ret) { + ath6kl_err("Unable to recv target info: %d\n", ret); + return ret; + } + + if (le32_to_cpu(targ_info->version) == TARGET_VERSION_SENTINAL) { + /* Determine how many bytes are in the Target's targ_info */ + ret = ath6kl_hif_bmi_read(ar, + (u8 *)&targ_info->byte_count, + sizeof(targ_info->byte_count)); + if (ret) { + ath6kl_err("unable to read target info byte count: %d\n", + ret); + return ret; + } + + /* + * The target's targ_info doesn't match the host's targ_info. + * We need to do some backwards compatibility to make this work. + */ + if (le32_to_cpu(targ_info->byte_count) != sizeof(*targ_info)) { + WARN_ON(1); + return -EINVAL; + } + + /* Read the remainder of the targ_info */ + ret = ath6kl_hif_bmi_read(ar, + ((u8 *)targ_info) + + sizeof(targ_info->byte_count), + sizeof(*targ_info) - + sizeof(targ_info->byte_count)); + + if (ret) { + ath6kl_err("Unable to read target info (%d bytes): %d\n", + targ_info->byte_count, ret); + return ret; + } + } + + ath6kl_dbg(ATH6KL_DBG_BMI, "target info (ver: 0x%x type: 0x%x)\n", + targ_info->version, targ_info->type); + + return 0; +} + +int ath6kl_bmi_read(struct ath6kl *ar, u32 addr, u8 *buf, u32 len) +{ + u32 cid = BMI_READ_MEMORY; + int ret; + u32 offset; + u32 len_remain, rx_len; + u16 size; + + if (ar->bmi.done_sent) { + ath6kl_err("bmi done sent already, cmd %d disallowed\n", cid); + return -EACCES; + } + + size = ar->bmi.max_data_size + sizeof(cid) + sizeof(addr) + sizeof(len); + if (size > ar->bmi.max_cmd_size) { + WARN_ON(1); + return -EINVAL; + } + memset(ar->bmi.cmd_buf, 0, size); + + ath6kl_dbg(ATH6KL_DBG_BMI, + "bmi read memory: device: addr: 0x%x, len: %d\n", + addr, len); + + len_remain = len; + + while (len_remain) { + rx_len = (len_remain < ar->bmi.max_data_size) ? + len_remain : ar->bmi.max_data_size; + offset = 0; + memcpy(&(ar->bmi.cmd_buf[offset]), &cid, sizeof(cid)); + offset += sizeof(cid); + memcpy(&(ar->bmi.cmd_buf[offset]), &addr, sizeof(addr)); + offset += sizeof(addr); + memcpy(&(ar->bmi.cmd_buf[offset]), &rx_len, sizeof(rx_len)); + offset += sizeof(len); + + ret = ath6kl_hif_bmi_write(ar, ar->bmi.cmd_buf, offset); + if (ret) { + ath6kl_err("Unable to write to the device: %d\n", + ret); + return ret; + } + ret = ath6kl_hif_bmi_read(ar, ar->bmi.cmd_buf, rx_len); + if (ret) { + ath6kl_err("Unable to read from the device: %d\n", + ret); + return ret; + } + memcpy(&buf[len - len_remain], ar->bmi.cmd_buf, rx_len); + len_remain -= rx_len; addr += rx_len; + } + + return 0; +} + +int ath6kl_bmi_write(struct ath6kl *ar, u32 addr, u8 *buf, u32 len) +{ + u32 cid = BMI_WRITE_MEMORY; + int ret; + u32 offset; + u32 len_remain, tx_len; + const u32 header = sizeof(cid) + sizeof(addr) + sizeof(len); + u8 aligned_buf[400]; + u8 *src; + + if (ar->bmi.done_sent) { + ath6kl_err("bmi done sent already, cmd %d disallowed\n", cid); + return -EACCES; + } + + if ((ar->bmi.max_data_size + header) > ar->bmi.max_cmd_size) { + WARN_ON(1); + return -EINVAL; + } + + if (WARN_ON(ar->bmi.max_data_size > sizeof(aligned_buf))) + return -E2BIG; + + memset(ar->bmi.cmd_buf, 0, ar->bmi.max_data_size + header); + + ath6kl_dbg(ATH6KL_DBG_BMI, + "bmi write memory: addr: 0x%x, len: %d\n", addr, len); + + len_remain = len; + while (len_remain) { + src = &buf[len - len_remain]; + + if (len_remain < (ar->bmi.max_data_size - header)) { + if (len_remain & 3) { + /* align it with 4 bytes */ + len_remain = len_remain + + (4 - (len_remain & 3)); + memcpy(aligned_buf, src, len_remain); + src = aligned_buf; + } + tx_len = len_remain; + } else { + tx_len = (ar->bmi.max_data_size - header); + } + + offset = 0; + memcpy(&(ar->bmi.cmd_buf[offset]), &cid, sizeof(cid)); + offset += sizeof(cid); + memcpy(&(ar->bmi.cmd_buf[offset]), &addr, sizeof(addr)); + offset += sizeof(addr); + memcpy(&(ar->bmi.cmd_buf[offset]), &tx_len, sizeof(tx_len)); + offset += sizeof(tx_len); + memcpy(&(ar->bmi.cmd_buf[offset]), src, tx_len); + offset += tx_len; + + ret = ath6kl_hif_bmi_write(ar, ar->bmi.cmd_buf, offset); + if (ret) { + ath6kl_err("Unable to write to the device: %d\n", + ret); + return ret; + } + len_remain -= tx_len; addr += tx_len; + } + + return 0; +} + +int ath6kl_bmi_execute(struct ath6kl *ar, u32 addr, u32 *param) +{ + u32 cid = BMI_EXECUTE; + int ret; + u32 offset; + u16 size; + + if (ar->bmi.done_sent) { + ath6kl_err("bmi done sent already, cmd %d disallowed\n", cid); + return -EACCES; + } + + size = sizeof(cid) + sizeof(addr) + sizeof(param); + if (size > ar->bmi.max_cmd_size) { + WARN_ON(1); + return -EINVAL; + } + memset(ar->bmi.cmd_buf, 0, size); + + ath6kl_dbg(ATH6KL_DBG_BMI, "bmi execute: addr: 0x%x, param: %d)\n", + addr, *param); + + offset = 0; + memcpy(&(ar->bmi.cmd_buf[offset]), &cid, sizeof(cid)); + offset += sizeof(cid); + memcpy(&(ar->bmi.cmd_buf[offset]), &addr, sizeof(addr)); + offset += sizeof(addr); + memcpy(&(ar->bmi.cmd_buf[offset]), param, sizeof(*param)); + offset += sizeof(*param); + + ret = ath6kl_hif_bmi_write(ar, ar->bmi.cmd_buf, offset); + if (ret) { + ath6kl_err("Unable to write to the device: %d\n", ret); + return ret; + } + + ret = ath6kl_hif_bmi_read(ar, ar->bmi.cmd_buf, sizeof(*param)); + if (ret) { + ath6kl_err("Unable to read from the device: %d\n", ret); + return ret; + } + + memcpy(param, ar->bmi.cmd_buf, sizeof(*param)); + + return 0; +} + +int ath6kl_bmi_set_app_start(struct ath6kl *ar, u32 addr) +{ + u32 cid = BMI_SET_APP_START; + int ret; + u32 offset; + u16 size; + + if (ar->bmi.done_sent) { + ath6kl_err("bmi done sent already, cmd %d disallowed\n", cid); + return -EACCES; + } + + size = sizeof(cid) + sizeof(addr); + if (size > ar->bmi.max_cmd_size) { + WARN_ON(1); + return -EINVAL; + } + memset(ar->bmi.cmd_buf, 0, size); + + ath6kl_dbg(ATH6KL_DBG_BMI, "bmi set app start: addr: 0x%x\n", addr); + + offset = 0; + memcpy(&(ar->bmi.cmd_buf[offset]), &cid, sizeof(cid)); + offset += sizeof(cid); + memcpy(&(ar->bmi.cmd_buf[offset]), &addr, sizeof(addr)); + offset += sizeof(addr); + + ret = ath6kl_hif_bmi_write(ar, ar->bmi.cmd_buf, offset); + if (ret) { + ath6kl_err("Unable to write to the device: %d\n", ret); + return ret; + } + + return 0; +} + +int ath6kl_bmi_reg_read(struct ath6kl *ar, u32 addr, u32 *param) +{ + u32 cid = BMI_READ_SOC_REGISTER; + int ret; + u32 offset; + u16 size; + + if (ar->bmi.done_sent) { + ath6kl_err("bmi done sent already, cmd %d disallowed\n", cid); + return -EACCES; + } + + size = sizeof(cid) + sizeof(addr); + if (size > ar->bmi.max_cmd_size) { + WARN_ON(1); + return -EINVAL; + } + memset(ar->bmi.cmd_buf, 0, size); + + ath6kl_dbg(ATH6KL_DBG_BMI, "bmi read SOC reg: addr: 0x%x\n", addr); + + offset = 0; + memcpy(&(ar->bmi.cmd_buf[offset]), &cid, sizeof(cid)); + offset += sizeof(cid); + memcpy(&(ar->bmi.cmd_buf[offset]), &addr, sizeof(addr)); + offset += sizeof(addr); + + ret = ath6kl_hif_bmi_write(ar, ar->bmi.cmd_buf, offset); + if (ret) { + ath6kl_err("Unable to write to the device: %d\n", ret); + return ret; + } + + ret = ath6kl_hif_bmi_read(ar, ar->bmi.cmd_buf, sizeof(*param)); + if (ret) { + ath6kl_err("Unable to read from the device: %d\n", ret); + return ret; + } + memcpy(param, ar->bmi.cmd_buf, sizeof(*param)); + + return 0; +} + +int ath6kl_bmi_reg_write(struct ath6kl *ar, u32 addr, u32 param) +{ + u32 cid = BMI_WRITE_SOC_REGISTER; + int ret; + u32 offset; + u16 size; + + if (ar->bmi.done_sent) { + ath6kl_err("bmi done sent already, cmd %d disallowed\n", cid); + return -EACCES; + } + + size = sizeof(cid) + sizeof(addr) + sizeof(param); + if (size > ar->bmi.max_cmd_size) { + WARN_ON(1); + return -EINVAL; + } + memset(ar->bmi.cmd_buf, 0, size); + + ath6kl_dbg(ATH6KL_DBG_BMI, + "bmi write SOC reg: addr: 0x%x, param: %d\n", + addr, param); + + offset = 0; + memcpy(&(ar->bmi.cmd_buf[offset]), &cid, sizeof(cid)); + offset += sizeof(cid); + memcpy(&(ar->bmi.cmd_buf[offset]), &addr, sizeof(addr)); + offset += sizeof(addr); + memcpy(&(ar->bmi.cmd_buf[offset]), ¶m, sizeof(param)); + offset += sizeof(param); + + ret = ath6kl_hif_bmi_write(ar, ar->bmi.cmd_buf, offset); + if (ret) { + ath6kl_err("Unable to write to the device: %d\n", ret); + return ret; + } + + return 0; +} + +int ath6kl_bmi_lz_data(struct ath6kl *ar, u8 *buf, u32 len) +{ + u32 cid = BMI_LZ_DATA; + int ret; + u32 offset; + u32 len_remain, tx_len; + const u32 header = sizeof(cid) + sizeof(len); + u16 size; + + if (ar->bmi.done_sent) { + ath6kl_err("bmi done sent already, cmd %d disallowed\n", cid); + return -EACCES; + } + + size = ar->bmi.max_data_size + header; + if (size > ar->bmi.max_cmd_size) { + WARN_ON(1); + return -EINVAL; + } + memset(ar->bmi.cmd_buf, 0, size); + + ath6kl_dbg(ATH6KL_DBG_BMI, "bmi send LZ data: len: %d)\n", + len); + + len_remain = len; + while (len_remain) { + tx_len = (len_remain < (ar->bmi.max_data_size - header)) ? + len_remain : (ar->bmi.max_data_size - header); + + offset = 0; + memcpy(&(ar->bmi.cmd_buf[offset]), &cid, sizeof(cid)); + offset += sizeof(cid); + memcpy(&(ar->bmi.cmd_buf[offset]), &tx_len, sizeof(tx_len)); + offset += sizeof(tx_len); + memcpy(&(ar->bmi.cmd_buf[offset]), &buf[len - len_remain], + tx_len); + offset += tx_len; + + ret = ath6kl_hif_bmi_write(ar, ar->bmi.cmd_buf, offset); + if (ret) { + ath6kl_err("Unable to write to the device: %d\n", + ret); + return ret; + } + + len_remain -= tx_len; + } + + return 0; +} + +int ath6kl_bmi_lz_stream_start(struct ath6kl *ar, u32 addr) +{ + u32 cid = BMI_LZ_STREAM_START; + int ret; + u32 offset; + u16 size; + + if (ar->bmi.done_sent) { + ath6kl_err("bmi done sent already, cmd %d disallowed\n", cid); + return -EACCES; + } + + size = sizeof(cid) + sizeof(addr); + if (size > ar->bmi.max_cmd_size) { + WARN_ON(1); + return -EINVAL; + } + memset(ar->bmi.cmd_buf, 0, size); + + ath6kl_dbg(ATH6KL_DBG_BMI, + "bmi LZ stream start: addr: 0x%x)\n", + addr); + + offset = 0; + memcpy(&(ar->bmi.cmd_buf[offset]), &cid, sizeof(cid)); + offset += sizeof(cid); + memcpy(&(ar->bmi.cmd_buf[offset]), &addr, sizeof(addr)); + offset += sizeof(addr); + + ret = ath6kl_hif_bmi_write(ar, ar->bmi.cmd_buf, offset); + if (ret) { + ath6kl_err("Unable to start LZ stream to the device: %d\n", + ret); + return ret; + } + + return 0; +} + +int ath6kl_bmi_fast_download(struct ath6kl *ar, u32 addr, u8 *buf, u32 len) +{ + int ret; + u32 last_word = 0; + u32 last_word_offset = len & ~0x3; + u32 unaligned_bytes = len & 0x3; + + ret = ath6kl_bmi_lz_stream_start(ar, addr); + if (ret) + return ret; + + if (unaligned_bytes) { + /* copy the last word into a zero padded buffer */ + memcpy(&last_word, &buf[last_word_offset], unaligned_bytes); + } + + ret = ath6kl_bmi_lz_data(ar, buf, last_word_offset); + if (ret) + return ret; + + if (unaligned_bytes) + ret = ath6kl_bmi_lz_data(ar, (u8 *)&last_word, 4); + + if (!ret) { + /* Close compressed stream and open a new (fake) one. + * This serves mainly to flush Target caches. */ + ret = ath6kl_bmi_lz_stream_start(ar, 0x00); + } + return ret; +} + +void ath6kl_bmi_reset(struct ath6kl *ar) +{ + ar->bmi.done_sent = false; +} + +int ath6kl_bmi_init(struct ath6kl *ar) +{ + if (WARN_ON(ar->bmi.max_data_size == 0)) + return -EINVAL; + + /* cmd + addr + len + data_size */ + ar->bmi.max_cmd_size = ar->bmi.max_data_size + (sizeof(u32) * 3); + + ar->bmi.cmd_buf = kzalloc(ar->bmi.max_cmd_size, GFP_ATOMIC); + if (!ar->bmi.cmd_buf) + return -ENOMEM; + + return 0; +} + +void ath6kl_bmi_cleanup(struct ath6kl *ar) +{ + kfree(ar->bmi.cmd_buf); + ar->bmi.cmd_buf = NULL; +} diff --git a/drivers/net/wireless/ath/ath6kl/bmi.h b/drivers/net/wireless/ath/ath6kl/bmi.h new file mode 100644 index 0000000..18fdd69 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/bmi.h @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef BMI_H +#define BMI_H + +/* + * Bootloader Messaging Interface (BMI) + * + * BMI is a very simple messaging interface used during initialization + * to read memory, write memory, execute code, and to define an + * application entry PC. + * + * It is used to download an application to ATH6KL, to provide + * patches to code that is already resident on ATH6KL, and generally + * to examine and modify state. The Host has an opportunity to use + * BMI only once during bootup. Once the Host issues a BMI_DONE + * command, this opportunity ends. + * + * The Host writes BMI requests to mailbox0, and reads BMI responses + * from mailbox0. BMI requests all begin with a command + * (see below for specific commands), and are followed by + * command-specific data. + * + * Flow control: + * The Host can only issue a command once the Target gives it a + * "BMI Command Credit", using ATH6KL Counter #4. As soon as the + * Target has completed a command, it issues another BMI Command + * Credit (so the Host can issue the next command). + * + * BMI handles all required Target-side cache flushing. + */ + +/* BMI Commands */ + +#define BMI_NO_COMMAND 0 + +#define BMI_DONE 1 +/* + * Semantics: Host is done using BMI + * Request format: + * u32 command (BMI_DONE) + * Response format: none + */ + +#define BMI_READ_MEMORY 2 +/* + * Semantics: Host reads ATH6KL memory + * Request format: + * u32 command (BMI_READ_MEMORY) + * u32 address + * u32 length, at most BMI_DATASZ_MAX + * Response format: + * u8 data[length] + */ + +#define BMI_WRITE_MEMORY 3 +/* + * Semantics: Host writes ATH6KL memory + * Request format: + * u32 command (BMI_WRITE_MEMORY) + * u32 address + * u32 length, at most BMI_DATASZ_MAX + * u8 data[length] + * Response format: none + */ + +#define BMI_EXECUTE 4 +/* + * Semantics: Causes ATH6KL to execute code + * Request format: + * u32 command (BMI_EXECUTE) + * u32 address + * u32 parameter + * Response format: + * u32 return value + */ + +#define BMI_SET_APP_START 5 +/* + * Semantics: Set Target application starting address + * Request format: + * u32 command (BMI_SET_APP_START) + * u32 address + * Response format: none + */ + +#define BMI_READ_SOC_REGISTER 6 +/* + * Semantics: Read a 32-bit Target SOC register. + * Request format: + * u32 command (BMI_READ_REGISTER) + * u32 address + * Response format: + * u32 value + */ + +#define BMI_WRITE_SOC_REGISTER 7 +/* + * Semantics: Write a 32-bit Target SOC register. + * Request format: + * u32 command (BMI_WRITE_REGISTER) + * u32 address + * u32 value + * + * Response format: none + */ + +#define BMI_GET_TARGET_ID 8 +#define BMI_GET_TARGET_INFO 8 +/* + * Semantics: Fetch the 4-byte Target information + * Request format: + * u32 command (BMI_GET_TARGET_ID/INFO) + * Response format1 (old firmware): + * u32 TargetVersionID + * Response format2 (newer firmware): + * u32 TARGET_VERSION_SENTINAL + * struct bmi_target_info; + */ + +#define TARGET_VERSION_SENTINAL 0xffffffff +#define TARGET_TYPE_AR6003 3 +#define TARGET_TYPE_AR6004 5 +#define BMI_ROMPATCH_INSTALL 9 +/* + * Semantics: Install a ROM Patch. + * Request format: + * u32 command (BMI_ROMPATCH_INSTALL) + * u32 Target ROM Address + * u32 Target RAM Address or Value (depending on Target Type) + * u32 Size, in bytes + * u32 Activate? 1-->activate; + * 0-->install but do not activate + * Response format: + * u32 PatchID + */ + +#define BMI_ROMPATCH_UNINSTALL 10 +/* + * Semantics: Uninstall a previously-installed ROM Patch, + * automatically deactivating, if necessary. + * Request format: + * u32 command (BMI_ROMPATCH_UNINSTALL) + * u32 PatchID + * + * Response format: none + */ + +#define BMI_ROMPATCH_ACTIVATE 11 +/* + * Semantics: Activate a list of previously-installed ROM Patches. + * Request format: + * u32 command (BMI_ROMPATCH_ACTIVATE) + * u32 rompatch_count + * u32 PatchID[rompatch_count] + * + * Response format: none + */ + +#define BMI_ROMPATCH_DEACTIVATE 12 +/* + * Semantics: Deactivate a list of active ROM Patches. + * Request format: + * u32 command (BMI_ROMPATCH_DEACTIVATE) + * u32 rompatch_count + * u32 PatchID[rompatch_count] + * + * Response format: none + */ + + +#define BMI_LZ_STREAM_START 13 +/* + * Semantics: Begin an LZ-compressed stream of input + * which is to be uncompressed by the Target to an + * output buffer at address. The output buffer must + * be sufficiently large to hold the uncompressed + * output from the compressed input stream. This BMI + * command should be followed by a series of 1 or more + * BMI_LZ_DATA commands. + * u32 command (BMI_LZ_STREAM_START) + * u32 address + * Note: Not supported on all versions of ROM firmware. + */ + +#define BMI_LZ_DATA 14 +/* + * Semantics: Host writes ATH6KL memory with LZ-compressed + * data which is uncompressed by the Target. This command + * must be preceded by a BMI_LZ_STREAM_START command. A series + * of BMI_LZ_DATA commands are considered part of a single + * input stream until another BMI_LZ_STREAM_START is issued. + * Request format: + * u32 command (BMI_LZ_DATA) + * u32 length (of compressed data), + * at most BMI_DATASZ_MAX + * u8 CompressedData[length] + * Response format: none + * Note: Not supported on all versions of ROM firmware. + */ + +#define BMI_COMMUNICATION_TIMEOUT 1000 /* in msec */ + +struct ath6kl; +struct ath6kl_bmi_target_info { + __le32 byte_count; /* size of this structure */ + __le32 version; /* target version id */ + __le32 type; /* target type */ +} __packed; + +#define ath6kl_bmi_write_hi32(ar, item, val) \ + ({ \ + u32 addr; \ + __le32 v; \ + \ + addr = ath6kl_get_hi_item_addr(ar, HI_ITEM(item)); \ + v = cpu_to_le32(val); \ + ath6kl_bmi_write(ar, addr, (u8 *) &v, sizeof(v)); \ + }) + +#define ath6kl_bmi_read_hi32(ar, item, val) \ + ({ \ + u32 addr, *check_type = val; \ + __le32 tmp; \ + int ret; \ + \ + (void) (check_type == val); \ + addr = ath6kl_get_hi_item_addr(ar, HI_ITEM(item)); \ + ret = ath6kl_bmi_read(ar, addr, (u8 *) &tmp, 4); \ + *val = le32_to_cpu(tmp); \ + ret; \ + }) + +int ath6kl_bmi_init(struct ath6kl *ar); +void ath6kl_bmi_cleanup(struct ath6kl *ar); +void ath6kl_bmi_reset(struct ath6kl *ar); + +int ath6kl_bmi_done(struct ath6kl *ar); +int ath6kl_bmi_get_target_info(struct ath6kl *ar, + struct ath6kl_bmi_target_info *targ_info); +int ath6kl_bmi_read(struct ath6kl *ar, u32 addr, u8 *buf, u32 len); +int ath6kl_bmi_write(struct ath6kl *ar, u32 addr, u8 *buf, u32 len); +int ath6kl_bmi_execute(struct ath6kl *ar, + u32 addr, u32 *param); +int ath6kl_bmi_set_app_start(struct ath6kl *ar, + u32 addr); +int ath6kl_bmi_reg_read(struct ath6kl *ar, u32 addr, u32 *param); +int ath6kl_bmi_reg_write(struct ath6kl *ar, u32 addr, u32 param); +int ath6kl_bmi_lz_data(struct ath6kl *ar, + u8 *buf, u32 len); +int ath6kl_bmi_lz_stream_start(struct ath6kl *ar, + u32 addr); +int ath6kl_bmi_fast_download(struct ath6kl *ar, + u32 addr, u8 *buf, u32 len); +#endif diff --git a/drivers/net/wireless/ath/ath6kl/cfg80211.c b/drivers/net/wireless/ath/ath6kl/cfg80211.c new file mode 100644 index 0000000..b5e00f2 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/cfg80211.c @@ -0,0 +1,3852 @@ +/* + * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include + +#include "core.h" +#include "cfg80211.h" +#include "debug.h" +#include "hif-ops.h" +#include "testmode.h" + +#define RATETAB_ENT(_rate, _rateid, _flags) { \ + .bitrate = (_rate), \ + .flags = (_flags), \ + .hw_value = (_rateid), \ +} + +#define CHAN2G(_channel, _freq, _flags) { \ + .band = IEEE80211_BAND_2GHZ, \ + .hw_value = (_channel), \ + .center_freq = (_freq), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +#define CHAN5G(_channel, _flags) { \ + .band = IEEE80211_BAND_5GHZ, \ + .hw_value = (_channel), \ + .center_freq = 5000 + (5 * (_channel)), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +#define DEFAULT_BG_SCAN_PERIOD 60 + +struct ath6kl_cfg80211_match_probe_ssid { + struct cfg80211_ssid ssid; + u8 flag; +}; + +static struct ieee80211_rate ath6kl_rates[] = { + RATETAB_ENT(10, 0x1, 0), + RATETAB_ENT(20, 0x2, 0), + RATETAB_ENT(55, 0x4, 0), + RATETAB_ENT(110, 0x8, 0), + RATETAB_ENT(60, 0x10, 0), + RATETAB_ENT(90, 0x20, 0), + RATETAB_ENT(120, 0x40, 0), + RATETAB_ENT(180, 0x80, 0), + RATETAB_ENT(240, 0x100, 0), + RATETAB_ENT(360, 0x200, 0), + RATETAB_ENT(480, 0x400, 0), + RATETAB_ENT(540, 0x800, 0), +}; + +#define ath6kl_a_rates (ath6kl_rates + 4) +#define ath6kl_a_rates_size 8 +#define ath6kl_g_rates (ath6kl_rates + 0) +#define ath6kl_g_rates_size 12 + +#define ath6kl_g_htcap IEEE80211_HT_CAP_SGI_20 +#define ath6kl_a_htcap (IEEE80211_HT_CAP_SUP_WIDTH_20_40 | \ + IEEE80211_HT_CAP_SGI_20 | \ + IEEE80211_HT_CAP_SGI_40) + +static struct ieee80211_channel ath6kl_2ghz_channels[] = { + CHAN2G(1, 2412, 0), + CHAN2G(2, 2417, 0), + CHAN2G(3, 2422, 0), + CHAN2G(4, 2427, 0), + CHAN2G(5, 2432, 0), + CHAN2G(6, 2437, 0), + CHAN2G(7, 2442, 0), + CHAN2G(8, 2447, 0), + CHAN2G(9, 2452, 0), + CHAN2G(10, 2457, 0), + CHAN2G(11, 2462, 0), + CHAN2G(12, 2467, 0), + CHAN2G(13, 2472, 0), + CHAN2G(14, 2484, 0), +}; + +static struct ieee80211_channel ath6kl_5ghz_a_channels[] = { + CHAN5G(34, 0), CHAN5G(36, 0), + CHAN5G(38, 0), CHAN5G(40, 0), + CHAN5G(42, 0), CHAN5G(44, 0), + CHAN5G(46, 0), CHAN5G(48, 0), + CHAN5G(52, 0), CHAN5G(56, 0), + CHAN5G(60, 0), CHAN5G(64, 0), + CHAN5G(100, 0), CHAN5G(104, 0), + CHAN5G(108, 0), CHAN5G(112, 0), + CHAN5G(116, 0), CHAN5G(120, 0), + CHAN5G(124, 0), CHAN5G(128, 0), + CHAN5G(132, 0), CHAN5G(136, 0), + CHAN5G(140, 0), CHAN5G(149, 0), + CHAN5G(153, 0), CHAN5G(157, 0), + CHAN5G(161, 0), CHAN5G(165, 0), + CHAN5G(184, 0), CHAN5G(188, 0), + CHAN5G(192, 0), CHAN5G(196, 0), + CHAN5G(200, 0), CHAN5G(204, 0), + CHAN5G(208, 0), CHAN5G(212, 0), + CHAN5G(216, 0), +}; + +static struct ieee80211_supported_band ath6kl_band_2ghz = { + .n_channels = ARRAY_SIZE(ath6kl_2ghz_channels), + .channels = ath6kl_2ghz_channels, + .n_bitrates = ath6kl_g_rates_size, + .bitrates = ath6kl_g_rates, + .ht_cap.cap = ath6kl_g_htcap, + .ht_cap.ht_supported = true, +}; + +static struct ieee80211_supported_band ath6kl_band_5ghz = { + .n_channels = ARRAY_SIZE(ath6kl_5ghz_a_channels), + .channels = ath6kl_5ghz_a_channels, + .n_bitrates = ath6kl_a_rates_size, + .bitrates = ath6kl_a_rates, + .ht_cap.cap = ath6kl_a_htcap, + .ht_cap.ht_supported = true, +}; + +#define CCKM_KRK_CIPHER_SUITE 0x004096ff /* use for KRK */ + +/* returns true if scheduled scan was stopped */ +static bool __ath6kl_cfg80211_sscan_stop(struct ath6kl_vif *vif) +{ + struct ath6kl *ar = vif->ar; + + if (!test_and_clear_bit(SCHED_SCANNING, &vif->flags)) + return false; + + del_timer_sync(&vif->sched_scan_timer); + + if (ar->state == ATH6KL_STATE_RECOVERY) + return true; + + ath6kl_wmi_enable_sched_scan_cmd(ar->wmi, vif->fw_vif_idx, false); + + return true; +} + +static void ath6kl_cfg80211_sscan_disable(struct ath6kl_vif *vif) +{ + struct ath6kl *ar = vif->ar; + bool stopped; + + stopped = __ath6kl_cfg80211_sscan_stop(vif); + + if (!stopped) + return; + + cfg80211_sched_scan_stopped(ar->wiphy); +} + +static int ath6kl_set_wpa_version(struct ath6kl_vif *vif, + enum nl80211_wpa_versions wpa_version) +{ + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: %u\n", __func__, wpa_version); + + if (!wpa_version) { + vif->auth_mode = NONE_AUTH; + } else if (wpa_version & NL80211_WPA_VERSION_2) { + vif->auth_mode = WPA2_AUTH; + } else if (wpa_version & NL80211_WPA_VERSION_1) { + vif->auth_mode = WPA_AUTH; + } else { + ath6kl_err("%s: %u not supported\n", __func__, wpa_version); + return -ENOTSUPP; + } + + return 0; +} + +static int ath6kl_set_auth_type(struct ath6kl_vif *vif, + enum nl80211_auth_type auth_type) +{ + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: 0x%x\n", __func__, auth_type); + + switch (auth_type) { + case NL80211_AUTHTYPE_OPEN_SYSTEM: + vif->dot11_auth_mode = OPEN_AUTH; + break; + case NL80211_AUTHTYPE_SHARED_KEY: + vif->dot11_auth_mode = SHARED_AUTH; + break; + case NL80211_AUTHTYPE_NETWORK_EAP: + vif->dot11_auth_mode = LEAP_AUTH; + break; + + case NL80211_AUTHTYPE_AUTOMATIC: + vif->dot11_auth_mode = OPEN_AUTH | SHARED_AUTH; + break; + + default: + ath6kl_err("%s: 0x%x not supported\n", __func__, auth_type); + return -ENOTSUPP; + } + + return 0; +} + +static int ath6kl_set_cipher(struct ath6kl_vif *vif, u32 cipher, bool ucast) +{ + u8 *ar_cipher = ucast ? &vif->prwise_crypto : &vif->grp_crypto; + u8 *ar_cipher_len = ucast ? &vif->prwise_crypto_len : + &vif->grp_crypto_len; + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: cipher 0x%x, ucast %u\n", + __func__, cipher, ucast); + + switch (cipher) { + case 0: + /* our own hack to use value 0 as no crypto used */ + *ar_cipher = NONE_CRYPT; + *ar_cipher_len = 0; + break; + case WLAN_CIPHER_SUITE_WEP40: + *ar_cipher = WEP_CRYPT; + *ar_cipher_len = 5; + break; + case WLAN_CIPHER_SUITE_WEP104: + *ar_cipher = WEP_CRYPT; + *ar_cipher_len = 13; + break; + case WLAN_CIPHER_SUITE_TKIP: + *ar_cipher = TKIP_CRYPT; + *ar_cipher_len = 0; + break; + case WLAN_CIPHER_SUITE_CCMP: + *ar_cipher = AES_CRYPT; + *ar_cipher_len = 0; + break; + case WLAN_CIPHER_SUITE_SMS4: + *ar_cipher = WAPI_CRYPT; + *ar_cipher_len = 0; + break; + default: + ath6kl_err("cipher 0x%x not supported\n", cipher); + return -ENOTSUPP; + } + + return 0; +} + +static void ath6kl_set_key_mgmt(struct ath6kl_vif *vif, u32 key_mgmt) +{ + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: 0x%x\n", __func__, key_mgmt); + + if (key_mgmt == WLAN_AKM_SUITE_PSK) { + if (vif->auth_mode == WPA_AUTH) + vif->auth_mode = WPA_PSK_AUTH; + else if (vif->auth_mode == WPA2_AUTH) + vif->auth_mode = WPA2_PSK_AUTH; + } else if (key_mgmt == 0x00409600) { + if (vif->auth_mode == WPA_AUTH) + vif->auth_mode = WPA_AUTH_CCKM; + else if (vif->auth_mode == WPA2_AUTH) + vif->auth_mode = WPA2_AUTH_CCKM; + } else if (key_mgmt != WLAN_AKM_SUITE_8021X) { + vif->auth_mode = NONE_AUTH; + } +} + +static bool ath6kl_cfg80211_ready(struct ath6kl_vif *vif) +{ + struct ath6kl *ar = vif->ar; + + if (!test_bit(WMI_READY, &ar->flag)) { + ath6kl_err("wmi is not ready\n"); + return false; + } + + if (!test_bit(WLAN_ENABLED, &vif->flags)) { + ath6kl_err("wlan disabled\n"); + return false; + } + + return true; +} + +static bool ath6kl_is_wpa_ie(const u8 *pos) +{ + return pos[0] == WLAN_EID_VENDOR_SPECIFIC && pos[1] >= 4 && + pos[2] == 0x00 && pos[3] == 0x50 && + pos[4] == 0xf2 && pos[5] == 0x01; +} + +static bool ath6kl_is_rsn_ie(const u8 *pos) +{ + return pos[0] == WLAN_EID_RSN; +} + +static bool ath6kl_is_wps_ie(const u8 *pos) +{ + return (pos[0] == WLAN_EID_VENDOR_SPECIFIC && + pos[1] >= 4 && + pos[2] == 0x00 && pos[3] == 0x50 && pos[4] == 0xf2 && + pos[5] == 0x04); +} + +static int ath6kl_set_assoc_req_ies(struct ath6kl_vif *vif, const u8 *ies, + size_t ies_len) +{ + struct ath6kl *ar = vif->ar; + const u8 *pos; + u8 *buf = NULL; + size_t len = 0; + int ret; + + /* + * Clear previously set flag + */ + + ar->connect_ctrl_flags &= ~CONNECT_WPS_FLAG; + + /* + * Filter out RSN/WPA IE(s) + */ + + if (ies && ies_len) { + buf = kmalloc(ies_len, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + pos = ies; + + while (pos + 1 < ies + ies_len) { + if (pos + 2 + pos[1] > ies + ies_len) + break; + if (!(ath6kl_is_wpa_ie(pos) || ath6kl_is_rsn_ie(pos))) { + memcpy(buf + len, pos, 2 + pos[1]); + len += 2 + pos[1]; + } + + if (ath6kl_is_wps_ie(pos)) + ar->connect_ctrl_flags |= CONNECT_WPS_FLAG; + + pos += 2 + pos[1]; + } + } + + ret = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, + WMI_FRAME_ASSOC_REQ, buf, len); + kfree(buf); + return ret; +} + +static int ath6kl_nliftype_to_drv_iftype(enum nl80211_iftype type, u8 *nw_type) +{ + switch (type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + *nw_type = INFRA_NETWORK; + break; + case NL80211_IFTYPE_ADHOC: + *nw_type = ADHOC_NETWORK; + break; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + *nw_type = AP_NETWORK; + break; + default: + ath6kl_err("invalid interface type %u\n", type); + return -ENOTSUPP; + } + + return 0; +} + +static bool ath6kl_is_valid_iftype(struct ath6kl *ar, enum nl80211_iftype type, + u8 *if_idx, u8 *nw_type) +{ + int i; + + if (ath6kl_nliftype_to_drv_iftype(type, nw_type)) + return false; + + if (ar->ibss_if_active || ((type == NL80211_IFTYPE_ADHOC) && + ar->num_vif)) + return false; + + if (type == NL80211_IFTYPE_STATION || + type == NL80211_IFTYPE_AP || type == NL80211_IFTYPE_ADHOC) { + for (i = 0; i < ar->vif_max; i++) { + if ((ar->avail_idx_map) & BIT(i)) { + *if_idx = i; + return true; + } + } + } + + if (type == NL80211_IFTYPE_P2P_CLIENT || + type == NL80211_IFTYPE_P2P_GO) { + for (i = ar->max_norm_iface; i < ar->vif_max; i++) { + if ((ar->avail_idx_map) & BIT(i)) { + *if_idx = i; + return true; + } + } + } + + return false; +} + +static bool ath6kl_is_tx_pending(struct ath6kl *ar) +{ + return ar->tx_pending[ath6kl_wmi_get_control_ep(ar->wmi)] == 0; +} + +static void ath6kl_cfg80211_sta_bmiss_enhance(struct ath6kl_vif *vif, + bool enable) +{ + int err; + + if (WARN_ON(!test_bit(WMI_READY, &vif->ar->flag))) + return; + + if (vif->nw_type != INFRA_NETWORK) + return; + + if (!test_bit(ATH6KL_FW_CAPABILITY_BMISS_ENHANCE, + vif->ar->fw_capabilities)) + return; + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s fw bmiss enhance\n", + enable ? "enable" : "disable"); + + err = ath6kl_wmi_sta_bmiss_enhance_cmd(vif->ar->wmi, + vif->fw_vif_idx, enable); + if (err) + ath6kl_err("failed to %s enhanced bmiss detection: %d\n", + enable ? "enable" : "disable", err); +} + +static int ath6kl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_connect_params *sme) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct ath6kl_vif *vif = netdev_priv(dev); + int status; + u8 nw_subtype = (ar->p2p) ? SUBTYPE_P2PDEV : SUBTYPE_NONE; + u16 interval; + + ath6kl_cfg80211_sscan_disable(vif); + + vif->sme_state = SME_CONNECTING; + + if (!ath6kl_cfg80211_ready(vif)) + return -EIO; + + if (test_bit(DESTROY_IN_PROGRESS, &ar->flag)) { + ath6kl_err("destroy in progress\n"); + return -EBUSY; + } + + if (test_bit(SKIP_SCAN, &ar->flag) && + ((sme->channel && sme->channel->center_freq == 0) || + (sme->bssid && is_zero_ether_addr(sme->bssid)))) { + ath6kl_err("SkipScan: channel or bssid invalid\n"); + return -EINVAL; + } + + if (down_interruptible(&ar->sem)) { + ath6kl_err("busy, couldn't get access\n"); + return -ERESTARTSYS; + } + + if (test_bit(DESTROY_IN_PROGRESS, &ar->flag)) { + ath6kl_err("busy, destroy in progress\n"); + up(&ar->sem); + return -EBUSY; + } + + if (ar->tx_pending[ath6kl_wmi_get_control_ep(ar->wmi)]) { + /* + * sleep until the command queue drains + */ + wait_event_interruptible_timeout(ar->event_wq, + ath6kl_is_tx_pending(ar), + WMI_TIMEOUT); + if (signal_pending(current)) { + ath6kl_err("cmd queue drain timeout\n"); + up(&ar->sem); + return -EINTR; + } + } + + status = ath6kl_set_assoc_req_ies(vif, sme->ie, sme->ie_len); + if (status) { + up(&ar->sem); + return status; + } + + if (sme->ie == NULL || sme->ie_len == 0) + ar->connect_ctrl_flags &= ~CONNECT_WPS_FLAG; + + if (test_bit(CONNECTED, &vif->flags) && + vif->ssid_len == sme->ssid_len && + !memcmp(vif->ssid, sme->ssid, vif->ssid_len)) { + vif->reconnect_flag = true; + status = ath6kl_wmi_reconnect_cmd(ar->wmi, vif->fw_vif_idx, + vif->req_bssid, + vif->ch_hint); + + up(&ar->sem); + if (status) { + ath6kl_err("wmi_reconnect_cmd failed\n"); + return -EIO; + } + return 0; + } else if (vif->ssid_len == sme->ssid_len && + !memcmp(vif->ssid, sme->ssid, vif->ssid_len)) { + ath6kl_disconnect(vif); + } + + memset(vif->ssid, 0, sizeof(vif->ssid)); + vif->ssid_len = sme->ssid_len; + memcpy(vif->ssid, sme->ssid, sme->ssid_len); + + if (sme->channel) + vif->ch_hint = sme->channel->center_freq; + + memset(vif->req_bssid, 0, sizeof(vif->req_bssid)); + if (sme->bssid && !is_broadcast_ether_addr(sme->bssid)) + memcpy(vif->req_bssid, sme->bssid, sizeof(vif->req_bssid)); + + ath6kl_set_wpa_version(vif, sme->crypto.wpa_versions); + + status = ath6kl_set_auth_type(vif, sme->auth_type); + if (status) { + up(&ar->sem); + return status; + } + + if (sme->crypto.n_ciphers_pairwise) + ath6kl_set_cipher(vif, sme->crypto.ciphers_pairwise[0], true); + else + ath6kl_set_cipher(vif, 0, true); + + ath6kl_set_cipher(vif, sme->crypto.cipher_group, false); + + if (sme->crypto.n_akm_suites) + ath6kl_set_key_mgmt(vif, sme->crypto.akm_suites[0]); + + if ((sme->key_len) && + (vif->auth_mode == NONE_AUTH) && + (vif->prwise_crypto == WEP_CRYPT)) { + struct ath6kl_key *key = NULL; + + if (sme->key_idx > WMI_MAX_KEY_INDEX) { + ath6kl_err("key index %d out of bounds\n", + sme->key_idx); + up(&ar->sem); + return -ENOENT; + } + + key = &vif->keys[sme->key_idx]; + key->key_len = sme->key_len; + memcpy(key->key, sme->key, key->key_len); + key->cipher = vif->prwise_crypto; + vif->def_txkey_index = sme->key_idx; + + ath6kl_wmi_addkey_cmd(ar->wmi, vif->fw_vif_idx, sme->key_idx, + vif->prwise_crypto, + GROUP_USAGE | TX_USAGE, + key->key_len, + NULL, 0, + key->key, KEY_OP_INIT_VAL, NULL, + NO_SYNC_WMIFLAG); + } + + if (!ar->usr_bss_filter) { + clear_bit(CLEAR_BSSFILTER_ON_BEACON, &vif->flags); + if (ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx, + ALL_BSS_FILTER, 0) != 0) { + ath6kl_err("couldn't set bss filtering\n"); + up(&ar->sem); + return -EIO; + } + } + + vif->nw_type = vif->next_mode; + + /* enable enhanced bmiss detection if applicable */ + ath6kl_cfg80211_sta_bmiss_enhance(vif, true); + + if (vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) + nw_subtype = SUBTYPE_P2PCLIENT; + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "%s: connect called with authmode %d dot11 auth %d" + " PW crypto %d PW crypto len %d GRP crypto %d" + " GRP crypto len %d channel hint %u\n", + __func__, + vif->auth_mode, vif->dot11_auth_mode, vif->prwise_crypto, + vif->prwise_crypto_len, vif->grp_crypto, + vif->grp_crypto_len, vif->ch_hint); + + vif->reconnect_flag = 0; + + if (vif->nw_type == INFRA_NETWORK) { + interval = max_t(u16, vif->listen_intvl_t, + ATH6KL_MAX_WOW_LISTEN_INTL); + status = ath6kl_wmi_listeninterval_cmd(ar->wmi, vif->fw_vif_idx, + interval, + 0); + if (status) { + ath6kl_err("couldn't set listen intervel\n"); + up(&ar->sem); + return status; + } + } + + status = ath6kl_wmi_connect_cmd(ar->wmi, vif->fw_vif_idx, vif->nw_type, + vif->dot11_auth_mode, vif->auth_mode, + vif->prwise_crypto, + vif->prwise_crypto_len, + vif->grp_crypto, vif->grp_crypto_len, + vif->ssid_len, vif->ssid, + vif->req_bssid, vif->ch_hint, + ar->connect_ctrl_flags, nw_subtype); + + if (sme->bg_scan_period == 0) { + /* disable background scan if period is 0 */ + sme->bg_scan_period = 0xffff; + } else if (sme->bg_scan_period == -1) { + /* configure default value if not specified */ + sme->bg_scan_period = DEFAULT_BG_SCAN_PERIOD; + } + + ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx, 0, 0, + sme->bg_scan_period, 0, 0, 0, 3, 0, 0, 0); + + up(&ar->sem); + + if (status == -EINVAL) { + memset(vif->ssid, 0, sizeof(vif->ssid)); + vif->ssid_len = 0; + ath6kl_err("invalid request\n"); + return -ENOENT; + } else if (status) { + ath6kl_err("ath6kl_wmi_connect_cmd failed\n"); + return -EIO; + } + + if ((!(ar->connect_ctrl_flags & CONNECT_DO_WPA_OFFLOAD)) && + ((vif->auth_mode == WPA_PSK_AUTH) || + (vif->auth_mode == WPA2_PSK_AUTH))) { + mod_timer(&vif->disconnect_timer, + jiffies + msecs_to_jiffies(DISCON_TIMER_INTVAL)); + } + + ar->connect_ctrl_flags &= ~CONNECT_DO_WPA_OFFLOAD; + set_bit(CONNECT_PEND, &vif->flags); + + return 0; +} + +static struct cfg80211_bss * +ath6kl_add_bss_if_needed(struct ath6kl_vif *vif, + enum network_type nw_type, + const u8 *bssid, + struct ieee80211_channel *chan, + const u8 *beacon_ie, + size_t beacon_ie_len) +{ + struct ath6kl *ar = vif->ar; + struct cfg80211_bss *bss; + u16 cap_mask, cap_val; + u8 *ie; + + if (nw_type & ADHOC_NETWORK) { + cap_mask = WLAN_CAPABILITY_IBSS; + cap_val = WLAN_CAPABILITY_IBSS; + } else { + cap_mask = WLAN_CAPABILITY_ESS; + cap_val = WLAN_CAPABILITY_ESS; + } + + bss = cfg80211_get_bss(ar->wiphy, chan, bssid, + vif->ssid, vif->ssid_len, + cap_mask, cap_val); + if (bss == NULL) { + /* + * Since cfg80211 may not yet know about the BSS, + * generate a partial entry until the first BSS info + * event becomes available. + * + * Prepend SSID element since it is not included in the Beacon + * IEs from the target. + */ + ie = kmalloc(2 + vif->ssid_len + beacon_ie_len, GFP_KERNEL); + if (ie == NULL) + return NULL; + ie[0] = WLAN_EID_SSID; + ie[1] = vif->ssid_len; + memcpy(ie + 2, vif->ssid, vif->ssid_len); + memcpy(ie + 2 + vif->ssid_len, beacon_ie, beacon_ie_len); + bss = cfg80211_inform_bss(ar->wiphy, chan, + bssid, 0, cap_val, 100, + ie, 2 + vif->ssid_len + beacon_ie_len, + 0, GFP_KERNEL); + if (bss) + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "added bss %pM to cfg80211\n", bssid); + kfree(ie); + } else + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "cfg80211 already has a bss\n"); + + return bss; +} + +void ath6kl_cfg80211_connect_event(struct ath6kl_vif *vif, u16 channel, + u8 *bssid, u16 listen_intvl, + u16 beacon_intvl, + enum network_type nw_type, + u8 beacon_ie_len, u8 assoc_req_len, + u8 assoc_resp_len, u8 *assoc_info) +{ + struct ieee80211_channel *chan; + struct ath6kl *ar = vif->ar; + struct cfg80211_bss *bss; + + /* capinfo + listen interval */ + u8 assoc_req_ie_offset = sizeof(u16) + sizeof(u16); + + /* capinfo + status code + associd */ + u8 assoc_resp_ie_offset = sizeof(u16) + sizeof(u16) + sizeof(u16); + + u8 *assoc_req_ie = assoc_info + beacon_ie_len + assoc_req_ie_offset; + u8 *assoc_resp_ie = assoc_info + beacon_ie_len + assoc_req_len + + assoc_resp_ie_offset; + + assoc_req_len -= assoc_req_ie_offset; + assoc_resp_len -= assoc_resp_ie_offset; + + /* + * Store Beacon interval here; DTIM period will be available only once + * a Beacon frame from the AP is seen. + */ + vif->assoc_bss_beacon_int = beacon_intvl; + clear_bit(DTIM_PERIOD_AVAIL, &vif->flags); + + if (nw_type & ADHOC_NETWORK) { + if (vif->wdev.iftype != NL80211_IFTYPE_ADHOC) { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "%s: ath6k not in ibss mode\n", __func__); + return; + } + } + + if (nw_type & INFRA_NETWORK) { + if (vif->wdev.iftype != NL80211_IFTYPE_STATION && + vif->wdev.iftype != NL80211_IFTYPE_P2P_CLIENT) { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "%s: ath6k not in station mode\n", __func__); + return; + } + } + + chan = ieee80211_get_channel(ar->wiphy, (int) channel); + + bss = ath6kl_add_bss_if_needed(vif, nw_type, bssid, chan, + assoc_info, beacon_ie_len); + if (!bss) { + ath6kl_err("could not add cfg80211 bss entry\n"); + return; + } + + if (nw_type & ADHOC_NETWORK) { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "ad-hoc %s selected\n", + nw_type & ADHOC_CREATOR ? "creator" : "joiner"); + cfg80211_ibss_joined(vif->ndev, bssid, GFP_KERNEL); + cfg80211_put_bss(ar->wiphy, bss); + return; + } + + if (vif->sme_state == SME_CONNECTING) { + /* inform connect result to cfg80211 */ + vif->sme_state = SME_CONNECTED; + cfg80211_connect_result(vif->ndev, bssid, + assoc_req_ie, assoc_req_len, + assoc_resp_ie, assoc_resp_len, + WLAN_STATUS_SUCCESS, GFP_KERNEL); + cfg80211_put_bss(ar->wiphy, bss); + } else if (vif->sme_state == SME_CONNECTED) { + /* inform roam event to cfg80211 */ + cfg80211_roamed_bss(vif->ndev, bss, assoc_req_ie, assoc_req_len, + assoc_resp_ie, assoc_resp_len, GFP_KERNEL); + } +} + +static int ath6kl_cfg80211_disconnect(struct wiphy *wiphy, + struct net_device *dev, u16 reason_code) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct ath6kl_vif *vif = netdev_priv(dev); + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: reason=%u\n", __func__, + reason_code); + + ath6kl_cfg80211_sscan_disable(vif); + + if (!ath6kl_cfg80211_ready(vif)) + return -EIO; + + if (test_bit(DESTROY_IN_PROGRESS, &ar->flag)) { + ath6kl_err("busy, destroy in progress\n"); + return -EBUSY; + } + + if (down_interruptible(&ar->sem)) { + ath6kl_err("busy, couldn't get access\n"); + return -ERESTARTSYS; + } + + vif->reconnect_flag = 0; + ath6kl_disconnect(vif); + memset(vif->ssid, 0, sizeof(vif->ssid)); + vif->ssid_len = 0; + + if (!test_bit(SKIP_SCAN, &ar->flag)) + memset(vif->req_bssid, 0, sizeof(vif->req_bssid)); + + up(&ar->sem); + + vif->sme_state = SME_DISCONNECTED; + + return 0; +} + +void ath6kl_cfg80211_disconnect_event(struct ath6kl_vif *vif, u8 reason, + u8 *bssid, u8 assoc_resp_len, + u8 *assoc_info, u16 proto_reason) +{ + struct ath6kl *ar = vif->ar; + + if (vif->scan_req) { + cfg80211_scan_done(vif->scan_req, true); + vif->scan_req = NULL; + } + + if (vif->nw_type & ADHOC_NETWORK) { + if (vif->wdev.iftype != NL80211_IFTYPE_ADHOC) { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "%s: ath6k not in ibss mode\n", __func__); + return; + } + memset(bssid, 0, ETH_ALEN); + cfg80211_ibss_joined(vif->ndev, bssid, GFP_KERNEL); + return; + } + + if (vif->nw_type & INFRA_NETWORK) { + if (vif->wdev.iftype != NL80211_IFTYPE_STATION && + vif->wdev.iftype != NL80211_IFTYPE_P2P_CLIENT) { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "%s: ath6k not in station mode\n", __func__); + return; + } + } + + clear_bit(CONNECT_PEND, &vif->flags); + + if (vif->sme_state == SME_CONNECTING) { + cfg80211_connect_result(vif->ndev, + bssid, NULL, 0, + NULL, 0, + WLAN_STATUS_UNSPECIFIED_FAILURE, + GFP_KERNEL); + } else if (vif->sme_state == SME_CONNECTED) { + cfg80211_disconnected(vif->ndev, proto_reason, + NULL, 0, GFP_KERNEL); + } + + vif->sme_state = SME_DISCONNECTED; + + /* + * Send a disconnect command to target when a disconnect event is + * received with reason code other than 3 (DISCONNECT_CMD - disconnect + * request from host) to make the firmware stop trying to connect even + * after giving disconnect event. There will be one more disconnect + * event for this disconnect command with reason code DISCONNECT_CMD + * which won't be notified to cfg80211. + */ + if (reason != DISCONNECT_CMD) + ath6kl_wmi_disconnect_cmd(ar->wmi, vif->fw_vif_idx); +} + +static int ath6kl_set_probed_ssids(struct ath6kl *ar, + struct ath6kl_vif *vif, + struct cfg80211_ssid *ssids, int n_ssids, + struct cfg80211_match_set *match_set, + int n_match_ssid) +{ + u8 i, j, index_to_add, ssid_found = false; + struct ath6kl_cfg80211_match_probe_ssid ssid_list[MAX_PROBED_SSIDS]; + + memset(ssid_list, 0, sizeof(ssid_list)); + + if (n_ssids > MAX_PROBED_SSIDS || + n_match_ssid > MAX_PROBED_SSIDS) + return -EINVAL; + + for (i = 0; i < n_ssids; i++) { + memcpy(ssid_list[i].ssid.ssid, + ssids[i].ssid, + ssids[i].ssid_len); + ssid_list[i].ssid.ssid_len = ssids[i].ssid_len; + + if (ssids[i].ssid_len) + ssid_list[i].flag = SPECIFIC_SSID_FLAG; + else + ssid_list[i].flag = ANY_SSID_FLAG; + + if (n_match_ssid == 0) + ssid_list[i].flag |= MATCH_SSID_FLAG; + } + + index_to_add = i; + + for (i = 0; i < n_match_ssid; i++) { + ssid_found = false; + + for (j = 0; j < n_ssids; j++) { + if ((match_set[i].ssid.ssid_len == + ssid_list[j].ssid.ssid_len) && + (!memcmp(ssid_list[j].ssid.ssid, + match_set[i].ssid.ssid, + match_set[i].ssid.ssid_len))) { + ssid_list[j].flag |= MATCH_SSID_FLAG; + ssid_found = true; + break; + } + } + + if (ssid_found) + continue; + + if (index_to_add >= MAX_PROBED_SSIDS) + continue; + + ssid_list[index_to_add].ssid.ssid_len = + match_set[i].ssid.ssid_len; + memcpy(ssid_list[index_to_add].ssid.ssid, + match_set[i].ssid.ssid, + match_set[i].ssid.ssid_len); + ssid_list[index_to_add].flag |= MATCH_SSID_FLAG; + index_to_add++; + } + + for (i = 0; i < index_to_add; i++) { + ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx, i, + ssid_list[i].flag, + ssid_list[i].ssid.ssid_len, + ssid_list[i].ssid.ssid); + + } + + /* Make sure no old entries are left behind */ + for (i = index_to_add; i < MAX_PROBED_SSIDS; i++) { + ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx, i, + DISABLE_SSID_FLAG, 0, NULL); + } + + return 0; +} + +static int ath6kl_cfg80211_scan(struct wiphy *wiphy, + struct cfg80211_scan_request *request) +{ + struct ath6kl_vif *vif = ath6kl_vif_from_wdev(request->wdev); + struct ath6kl *ar = ath6kl_priv(vif->ndev); + s8 n_channels = 0; + u16 *channels = NULL; + int ret = 0; + u32 force_fg_scan = 0; + + if (!ath6kl_cfg80211_ready(vif)) + return -EIO; + + ath6kl_cfg80211_sscan_disable(vif); + + if (!ar->usr_bss_filter) { + clear_bit(CLEAR_BSSFILTER_ON_BEACON, &vif->flags); + ret = ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx, + ALL_BSS_FILTER, 0); + if (ret) { + ath6kl_err("couldn't set bss filtering\n"); + return ret; + } + } + + ret = ath6kl_set_probed_ssids(ar, vif, request->ssids, + request->n_ssids, NULL, 0); + if (ret < 0) + return ret; + + /* this also clears IE in fw if it's not set */ + ret = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, + WMI_FRAME_PROBE_REQ, + request->ie, request->ie_len); + if (ret) { + ath6kl_err("failed to set Probe Request appie for scan\n"); + return ret; + } + + /* + * Scan only the requested channels if the request specifies a set of + * channels. If the list is longer than the target supports, do not + * configure the list and instead, scan all available channels. + */ + if (request->n_channels > 0 && + request->n_channels <= WMI_MAX_CHANNELS) { + u8 i; + + n_channels = request->n_channels; + + channels = kzalloc(n_channels * sizeof(u16), GFP_KERNEL); + if (channels == NULL) { + ath6kl_warn("failed to set scan channels, scan all channels"); + n_channels = 0; + } + + for (i = 0; i < n_channels; i++) + channels[i] = request->channels[i]->center_freq; + } + + if (test_bit(CONNECTED, &vif->flags)) + force_fg_scan = 1; + + vif->scan_req = request; + + ret = ath6kl_wmi_beginscan_cmd(ar->wmi, vif->fw_vif_idx, + WMI_LONG_SCAN, force_fg_scan, + false, 0, + ATH6KL_FG_SCAN_INTERVAL, + n_channels, channels, + request->no_cck, + request->rates); + if (ret) { + ath6kl_err("failed to start scan: %d\n", ret); + vif->scan_req = NULL; + } + + kfree(channels); + + return ret; +} + +void ath6kl_cfg80211_scan_complete_event(struct ath6kl_vif *vif, bool aborted) +{ + struct ath6kl *ar = vif->ar; + int i; + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: status%s\n", __func__, + aborted ? " aborted" : ""); + + if (!vif->scan_req) + return; + + if (aborted) + goto out; + + if (vif->scan_req->n_ssids && vif->scan_req->ssids[0].ssid_len) { + for (i = 0; i < vif->scan_req->n_ssids; i++) { + ath6kl_wmi_probedssid_cmd(ar->wmi, vif->fw_vif_idx, + i + 1, DISABLE_SSID_FLAG, + 0, NULL); + } + } + +out: + cfg80211_scan_done(vif->scan_req, aborted); + vif->scan_req = NULL; +} + +void ath6kl_cfg80211_ch_switch_notify(struct ath6kl_vif *vif, int freq, + enum wmi_phy_mode mode) +{ + struct cfg80211_chan_def chandef; + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "channel switch notify nw_type %d freq %d mode %d\n", + vif->nw_type, freq, mode); + + cfg80211_chandef_create(&chandef, + ieee80211_get_channel(vif->ar->wiphy, freq), + (mode == WMI_11G_HT20) ? + NL80211_CHAN_HT20 : NL80211_CHAN_NO_HT); + + cfg80211_ch_switch_notify(vif->ndev, &chandef); +} + +static int ath6kl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, + u8 key_index, bool pairwise, + const u8 *mac_addr, + struct key_params *params) +{ + struct ath6kl *ar = ath6kl_priv(ndev); + struct ath6kl_vif *vif = netdev_priv(ndev); + struct ath6kl_key *key = NULL; + int seq_len; + u8 key_usage; + u8 key_type; + + if (!ath6kl_cfg80211_ready(vif)) + return -EIO; + + if (params->cipher == CCKM_KRK_CIPHER_SUITE) { + if (params->key_len != WMI_KRK_LEN) + return -EINVAL; + return ath6kl_wmi_add_krk_cmd(ar->wmi, vif->fw_vif_idx, + params->key); + } + + if (key_index > WMI_MAX_KEY_INDEX) { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "%s: key index %d out of bounds\n", __func__, + key_index); + return -ENOENT; + } + + key = &vif->keys[key_index]; + memset(key, 0, sizeof(struct ath6kl_key)); + + if (pairwise) + key_usage = PAIRWISE_USAGE; + else + key_usage = GROUP_USAGE; + + seq_len = params->seq_len; + if (params->cipher == WLAN_CIPHER_SUITE_SMS4 && + seq_len > ATH6KL_KEY_SEQ_LEN) { + /* Only first half of the WPI PN is configured */ + seq_len = ATH6KL_KEY_SEQ_LEN; + } + if (params->key_len > WLAN_MAX_KEY_LEN || + seq_len > sizeof(key->seq)) + return -EINVAL; + + key->key_len = params->key_len; + memcpy(key->key, params->key, key->key_len); + key->seq_len = seq_len; + memcpy(key->seq, params->seq, key->seq_len); + key->cipher = params->cipher; + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + key_type = WEP_CRYPT; + break; + + case WLAN_CIPHER_SUITE_TKIP: + key_type = TKIP_CRYPT; + break; + + case WLAN_CIPHER_SUITE_CCMP: + key_type = AES_CRYPT; + break; + case WLAN_CIPHER_SUITE_SMS4: + key_type = WAPI_CRYPT; + break; + + default: + return -ENOTSUPP; + } + + if (((vif->auth_mode == WPA_PSK_AUTH) || + (vif->auth_mode == WPA2_PSK_AUTH)) && + (key_usage & GROUP_USAGE)) + del_timer(&vif->disconnect_timer); + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "%s: index %d, key_len %d, key_type 0x%x, key_usage 0x%x, seq_len %d\n", + __func__, key_index, key->key_len, key_type, + key_usage, key->seq_len); + + if (vif->nw_type == AP_NETWORK && !pairwise && + (key_type == TKIP_CRYPT || key_type == AES_CRYPT || + key_type == WAPI_CRYPT)) { + ar->ap_mode_bkey.valid = true; + ar->ap_mode_bkey.key_index = key_index; + ar->ap_mode_bkey.key_type = key_type; + ar->ap_mode_bkey.key_len = key->key_len; + memcpy(ar->ap_mode_bkey.key, key->key, key->key_len); + if (!test_bit(CONNECTED, &vif->flags)) { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "Delay initial group key configuration until AP mode has been started\n"); + /* + * The key will be set in ath6kl_connect_ap_mode() once + * the connected event is received from the target. + */ + return 0; + } + } + + if (vif->next_mode == AP_NETWORK && key_type == WEP_CRYPT && + !test_bit(CONNECTED, &vif->flags)) { + /* + * Store the key locally so that it can be re-configured after + * the AP mode has properly started + * (ath6kl_install_statioc_wep_keys). + */ + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "Delay WEP key configuration until AP mode has been started\n"); + vif->wep_key_list[key_index].key_len = key->key_len; + memcpy(vif->wep_key_list[key_index].key, key->key, + key->key_len); + return 0; + } + + return ath6kl_wmi_addkey_cmd(ar->wmi, vif->fw_vif_idx, key_index, + key_type, key_usage, key->key_len, + key->seq, key->seq_len, key->key, + KEY_OP_INIT_VAL, + (u8 *) mac_addr, SYNC_BOTH_WMIFLAG); +} + +static int ath6kl_cfg80211_del_key(struct wiphy *wiphy, struct net_device *ndev, + u8 key_index, bool pairwise, + const u8 *mac_addr) +{ + struct ath6kl *ar = ath6kl_priv(ndev); + struct ath6kl_vif *vif = netdev_priv(ndev); + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: index %d\n", __func__, key_index); + + if (!ath6kl_cfg80211_ready(vif)) + return -EIO; + + if (key_index > WMI_MAX_KEY_INDEX) { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "%s: key index %d out of bounds\n", __func__, + key_index); + return -ENOENT; + } + + if (!vif->keys[key_index].key_len) { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "%s: index %d is empty\n", __func__, key_index); + return 0; + } + + vif->keys[key_index].key_len = 0; + + return ath6kl_wmi_deletekey_cmd(ar->wmi, vif->fw_vif_idx, key_index); +} + +static int ath6kl_cfg80211_get_key(struct wiphy *wiphy, struct net_device *ndev, + u8 key_index, bool pairwise, + const u8 *mac_addr, void *cookie, + void (*callback) (void *cookie, + struct key_params *)) +{ + struct ath6kl_vif *vif = netdev_priv(ndev); + struct ath6kl_key *key = NULL; + struct key_params params; + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: index %d\n", __func__, key_index); + + if (!ath6kl_cfg80211_ready(vif)) + return -EIO; + + if (key_index > WMI_MAX_KEY_INDEX) { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "%s: key index %d out of bounds\n", __func__, + key_index); + return -ENOENT; + } + + key = &vif->keys[key_index]; + memset(¶ms, 0, sizeof(params)); + params.cipher = key->cipher; + params.key_len = key->key_len; + params.seq_len = key->seq_len; + params.seq = key->seq; + params.key = key->key; + + callback(cookie, ¶ms); + + return key->key_len ? 0 : -ENOENT; +} + +static int ath6kl_cfg80211_set_default_key(struct wiphy *wiphy, + struct net_device *ndev, + u8 key_index, bool unicast, + bool multicast) +{ + struct ath6kl *ar = ath6kl_priv(ndev); + struct ath6kl_vif *vif = netdev_priv(ndev); + struct ath6kl_key *key = NULL; + u8 key_usage; + enum crypto_type key_type = NONE_CRYPT; + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: index %d\n", __func__, key_index); + + if (!ath6kl_cfg80211_ready(vif)) + return -EIO; + + if (key_index > WMI_MAX_KEY_INDEX) { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "%s: key index %d out of bounds\n", + __func__, key_index); + return -ENOENT; + } + + if (!vif->keys[key_index].key_len) { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: invalid key index %d\n", + __func__, key_index); + return -EINVAL; + } + + vif->def_txkey_index = key_index; + key = &vif->keys[vif->def_txkey_index]; + key_usage = GROUP_USAGE; + if (vif->prwise_crypto == WEP_CRYPT) + key_usage |= TX_USAGE; + if (unicast) + key_type = vif->prwise_crypto; + if (multicast) + key_type = vif->grp_crypto; + + if (vif->next_mode == AP_NETWORK && !test_bit(CONNECTED, &vif->flags)) + return 0; /* Delay until AP mode has been started */ + + return ath6kl_wmi_addkey_cmd(ar->wmi, vif->fw_vif_idx, + vif->def_txkey_index, + key_type, key_usage, + key->key_len, key->seq, key->seq_len, + key->key, + KEY_OP_INIT_VAL, NULL, + SYNC_BOTH_WMIFLAG); +} + +void ath6kl_cfg80211_tkip_micerr_event(struct ath6kl_vif *vif, u8 keyid, + bool ismcast) +{ + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "%s: keyid %d, ismcast %d\n", __func__, keyid, ismcast); + + cfg80211_michael_mic_failure(vif->ndev, vif->bssid, + (ismcast ? NL80211_KEYTYPE_GROUP : + NL80211_KEYTYPE_PAIRWISE), keyid, NULL, + GFP_KERNEL); +} + +static int ath6kl_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) +{ + struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy); + struct ath6kl_vif *vif; + int ret; + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: changed 0x%x\n", __func__, + changed); + + vif = ath6kl_vif_first(ar); + if (!vif) + return -EIO; + + if (!ath6kl_cfg80211_ready(vif)) + return -EIO; + + if (changed & WIPHY_PARAM_RTS_THRESHOLD) { + ret = ath6kl_wmi_set_rts_cmd(ar->wmi, wiphy->rts_threshold); + if (ret != 0) { + ath6kl_err("ath6kl_wmi_set_rts_cmd failed\n"); + return -EIO; + } + } + + return 0; +} + +static int ath6kl_cfg80211_set_txpower(struct wiphy *wiphy, + struct wireless_dev *wdev, + enum nl80211_tx_power_setting type, + int mbm) +{ + struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy); + struct ath6kl_vif *vif; + int dbm = MBM_TO_DBM(mbm); + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: type 0x%x, dbm %d\n", __func__, + type, dbm); + + vif = ath6kl_vif_first(ar); + if (!vif) + return -EIO; + + if (!ath6kl_cfg80211_ready(vif)) + return -EIO; + + switch (type) { + case NL80211_TX_POWER_AUTOMATIC: + return 0; + case NL80211_TX_POWER_LIMITED: + ar->tx_pwr = dbm; + break; + default: + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: type 0x%x not supported\n", + __func__, type); + return -EOPNOTSUPP; + } + + ath6kl_wmi_set_tx_pwr_cmd(ar->wmi, vif->fw_vif_idx, dbm); + + return 0; +} + +static int ath6kl_cfg80211_get_txpower(struct wiphy *wiphy, + struct wireless_dev *wdev, + int *dbm) +{ + struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy); + struct ath6kl_vif *vif; + + vif = ath6kl_vif_first(ar); + if (!vif) + return -EIO; + + if (!ath6kl_cfg80211_ready(vif)) + return -EIO; + + if (test_bit(CONNECTED, &vif->flags)) { + ar->tx_pwr = 0; + + if (ath6kl_wmi_get_tx_pwr_cmd(ar->wmi, vif->fw_vif_idx) != 0) { + ath6kl_err("ath6kl_wmi_get_tx_pwr_cmd failed\n"); + return -EIO; + } + + wait_event_interruptible_timeout(ar->event_wq, ar->tx_pwr != 0, + 5 * HZ); + + if (signal_pending(current)) { + ath6kl_err("target did not respond\n"); + return -EINTR; + } + } + + *dbm = ar->tx_pwr; + return 0; +} + +static int ath6kl_cfg80211_set_power_mgmt(struct wiphy *wiphy, + struct net_device *dev, + bool pmgmt, int timeout) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct wmi_power_mode_cmd mode; + struct ath6kl_vif *vif = netdev_priv(dev); + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: pmgmt %d, timeout %d\n", + __func__, pmgmt, timeout); + + if (!ath6kl_cfg80211_ready(vif)) + return -EIO; + + if (pmgmt) { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: rec power\n", __func__); + mode.pwr_mode = REC_POWER; + } else { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: max perf\n", __func__); + mode.pwr_mode = MAX_PERF_POWER; + } + + if (ath6kl_wmi_powermode_cmd(ar->wmi, vif->fw_vif_idx, + mode.pwr_mode) != 0) { + ath6kl_err("wmi_powermode_cmd failed\n"); + return -EIO; + } + + return 0; +} + +static struct wireless_dev *ath6kl_cfg80211_add_iface(struct wiphy *wiphy, + const char *name, + enum nl80211_iftype type, + u32 *flags, + struct vif_params *params) +{ + struct ath6kl *ar = wiphy_priv(wiphy); + struct wireless_dev *wdev; + u8 if_idx, nw_type; + + if (ar->num_vif == ar->vif_max) { + ath6kl_err("Reached maximum number of supported vif\n"); + return ERR_PTR(-EINVAL); + } + + if (!ath6kl_is_valid_iftype(ar, type, &if_idx, &nw_type)) { + ath6kl_err("Not a supported interface type\n"); + return ERR_PTR(-EINVAL); + } + + wdev = ath6kl_interface_add(ar, name, type, if_idx, nw_type); + if (!wdev) + return ERR_PTR(-ENOMEM); + + ar->num_vif++; + + return wdev; +} + +static int ath6kl_cfg80211_del_iface(struct wiphy *wiphy, + struct wireless_dev *wdev) +{ + struct ath6kl *ar = wiphy_priv(wiphy); + struct ath6kl_vif *vif = netdev_priv(wdev->netdev); + + spin_lock_bh(&ar->list_lock); + list_del(&vif->list); + spin_unlock_bh(&ar->list_lock); + + ath6kl_cfg80211_vif_stop(vif, test_bit(WMI_READY, &ar->flag)); + + rtnl_lock(); + ath6kl_cfg80211_vif_cleanup(vif); + rtnl_unlock(); + + return 0; +} + +static int ath6kl_cfg80211_change_iface(struct wiphy *wiphy, + struct net_device *ndev, + enum nl80211_iftype type, u32 *flags, + struct vif_params *params) +{ + struct ath6kl_vif *vif = netdev_priv(ndev); + int i; + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: type %u\n", __func__, type); + + /* + * Don't bring up p2p on an interface which is not initialized + * for p2p operation where fw does not have capability to switch + * dynamically between non-p2p and p2p type interface. + */ + if (!test_bit(ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX, + vif->ar->fw_capabilities) && + (type == NL80211_IFTYPE_P2P_CLIENT || + type == NL80211_IFTYPE_P2P_GO)) { + if (vif->ar->vif_max == 1) { + if (vif->fw_vif_idx != 0) + return -EINVAL; + else + goto set_iface_type; + } + + for (i = vif->ar->max_norm_iface; i < vif->ar->vif_max; i++) { + if (i == vif->fw_vif_idx) + break; + } + + if (i == vif->ar->vif_max) { + ath6kl_err("Invalid interface to bring up P2P\n"); + return -EINVAL; + } + } + + /* need to clean up enhanced bmiss detection fw state */ + ath6kl_cfg80211_sta_bmiss_enhance(vif, false); + +set_iface_type: + switch (type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + vif->next_mode = INFRA_NETWORK; + break; + case NL80211_IFTYPE_ADHOC: + vif->next_mode = ADHOC_NETWORK; + break; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + vif->next_mode = AP_NETWORK; + break; + default: + ath6kl_err("invalid interface type %u\n", type); + return -EOPNOTSUPP; + } + + vif->wdev.iftype = type; + + return 0; +} + +static int ath6kl_cfg80211_join_ibss(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_ibss_params *ibss_param) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct ath6kl_vif *vif = netdev_priv(dev); + int status; + + if (!ath6kl_cfg80211_ready(vif)) + return -EIO; + + vif->ssid_len = ibss_param->ssid_len; + memcpy(vif->ssid, ibss_param->ssid, vif->ssid_len); + + if (ibss_param->chandef.chan) + vif->ch_hint = ibss_param->chandef.chan->center_freq; + + if (ibss_param->channel_fixed) { + /* + * TODO: channel_fixed: The channel should be fixed, do not + * search for IBSSs to join on other channels. Target + * firmware does not support this feature, needs to be + * updated. + */ + return -EOPNOTSUPP; + } + + memset(vif->req_bssid, 0, sizeof(vif->req_bssid)); + if (ibss_param->bssid && !is_broadcast_ether_addr(ibss_param->bssid)) + memcpy(vif->req_bssid, ibss_param->bssid, + sizeof(vif->req_bssid)); + + ath6kl_set_wpa_version(vif, 0); + + status = ath6kl_set_auth_type(vif, NL80211_AUTHTYPE_OPEN_SYSTEM); + if (status) + return status; + + if (ibss_param->privacy) { + ath6kl_set_cipher(vif, WLAN_CIPHER_SUITE_WEP40, true); + ath6kl_set_cipher(vif, WLAN_CIPHER_SUITE_WEP40, false); + } else { + ath6kl_set_cipher(vif, 0, true); + ath6kl_set_cipher(vif, 0, false); + } + + vif->nw_type = vif->next_mode; + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "%s: connect called with authmode %d dot11 auth %d" + " PW crypto %d PW crypto len %d GRP crypto %d" + " GRP crypto len %d channel hint %u\n", + __func__, + vif->auth_mode, vif->dot11_auth_mode, vif->prwise_crypto, + vif->prwise_crypto_len, vif->grp_crypto, + vif->grp_crypto_len, vif->ch_hint); + + status = ath6kl_wmi_connect_cmd(ar->wmi, vif->fw_vif_idx, vif->nw_type, + vif->dot11_auth_mode, vif->auth_mode, + vif->prwise_crypto, + vif->prwise_crypto_len, + vif->grp_crypto, vif->grp_crypto_len, + vif->ssid_len, vif->ssid, + vif->req_bssid, vif->ch_hint, + ar->connect_ctrl_flags, SUBTYPE_NONE); + set_bit(CONNECT_PEND, &vif->flags); + + return 0; +} + +static int ath6kl_cfg80211_leave_ibss(struct wiphy *wiphy, + struct net_device *dev) +{ + struct ath6kl_vif *vif = netdev_priv(dev); + + if (!ath6kl_cfg80211_ready(vif)) + return -EIO; + + ath6kl_disconnect(vif); + memset(vif->ssid, 0, sizeof(vif->ssid)); + vif->ssid_len = 0; + + return 0; +} + +static const u32 cipher_suites[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, + CCKM_KRK_CIPHER_SUITE, + WLAN_CIPHER_SUITE_SMS4, +}; + +static bool is_rate_legacy(s32 rate) +{ + static const s32 legacy[] = { 1000, 2000, 5500, 11000, + 6000, 9000, 12000, 18000, 24000, + 36000, 48000, 54000 + }; + u8 i; + + for (i = 0; i < ARRAY_SIZE(legacy); i++) + if (rate == legacy[i]) + return true; + + return false; +} + +static bool is_rate_ht20(s32 rate, u8 *mcs, bool *sgi) +{ + static const s32 ht20[] = { 6500, 13000, 19500, 26000, 39000, + 52000, 58500, 65000, 72200 + }; + u8 i; + + for (i = 0; i < ARRAY_SIZE(ht20); i++) { + if (rate == ht20[i]) { + if (i == ARRAY_SIZE(ht20) - 1) + /* last rate uses sgi */ + *sgi = true; + else + *sgi = false; + + *mcs = i; + return true; + } + } + return false; +} + +static bool is_rate_ht40(s32 rate, u8 *mcs, bool *sgi) +{ + static const s32 ht40[] = { 13500, 27000, 40500, 54000, + 81000, 108000, 121500, 135000, + 150000 + }; + u8 i; + + for (i = 0; i < ARRAY_SIZE(ht40); i++) { + if (rate == ht40[i]) { + if (i == ARRAY_SIZE(ht40) - 1) + /* last rate uses sgi */ + *sgi = true; + else + *sgi = false; + + *mcs = i; + return true; + } + } + + return false; +} + +static int ath6kl_get_station(struct wiphy *wiphy, struct net_device *dev, + u8 *mac, struct station_info *sinfo) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct ath6kl_vif *vif = netdev_priv(dev); + long left; + bool sgi; + s32 rate; + int ret; + u8 mcs; + + if (memcmp(mac, vif->bssid, ETH_ALEN) != 0) + return -ENOENT; + + if (down_interruptible(&ar->sem)) + return -EBUSY; + + set_bit(STATS_UPDATE_PEND, &vif->flags); + + ret = ath6kl_wmi_get_stats_cmd(ar->wmi, vif->fw_vif_idx); + + if (ret != 0) { + up(&ar->sem); + return -EIO; + } + + left = wait_event_interruptible_timeout(ar->event_wq, + !test_bit(STATS_UPDATE_PEND, + &vif->flags), + WMI_TIMEOUT); + + up(&ar->sem); + + if (left == 0) + return -ETIMEDOUT; + else if (left < 0) + return left; + + if (vif->target_stats.rx_byte) { + sinfo->rx_bytes = vif->target_stats.rx_byte; + sinfo->filled |= STATION_INFO_RX_BYTES64; + sinfo->rx_packets = vif->target_stats.rx_pkt; + sinfo->filled |= STATION_INFO_RX_PACKETS; + } + + if (vif->target_stats.tx_byte) { + sinfo->tx_bytes = vif->target_stats.tx_byte; + sinfo->filled |= STATION_INFO_TX_BYTES64; + sinfo->tx_packets = vif->target_stats.tx_pkt; + sinfo->filled |= STATION_INFO_TX_PACKETS; + } + + sinfo->signal = vif->target_stats.cs_rssi; + sinfo->filled |= STATION_INFO_SIGNAL; + + rate = vif->target_stats.tx_ucast_rate; + + if (is_rate_legacy(rate)) { + sinfo->txrate.legacy = rate / 100; + } else if (is_rate_ht20(rate, &mcs, &sgi)) { + if (sgi) { + sinfo->txrate.flags |= RATE_INFO_FLAGS_SHORT_GI; + sinfo->txrate.mcs = mcs - 1; + } else { + sinfo->txrate.mcs = mcs; + } + + sinfo->txrate.flags |= RATE_INFO_FLAGS_MCS; + } else if (is_rate_ht40(rate, &mcs, &sgi)) { + if (sgi) { + sinfo->txrate.flags |= RATE_INFO_FLAGS_SHORT_GI; + sinfo->txrate.mcs = mcs - 1; + } else { + sinfo->txrate.mcs = mcs; + } + + sinfo->txrate.flags |= RATE_INFO_FLAGS_40_MHZ_WIDTH; + sinfo->txrate.flags |= RATE_INFO_FLAGS_MCS; + } else { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "invalid rate from stats: %d\n", rate); + ath6kl_debug_war(ar, ATH6KL_WAR_INVALID_RATE); + return 0; + } + + sinfo->filled |= STATION_INFO_TX_BITRATE; + + if (test_bit(CONNECTED, &vif->flags) && + test_bit(DTIM_PERIOD_AVAIL, &vif->flags) && + vif->nw_type == INFRA_NETWORK) { + sinfo->filled |= STATION_INFO_BSS_PARAM; + sinfo->bss_param.flags = 0; + sinfo->bss_param.dtim_period = vif->assoc_bss_dtim_period; + sinfo->bss_param.beacon_interval = vif->assoc_bss_beacon_int; + } + + return 0; +} + +static int ath6kl_set_pmksa(struct wiphy *wiphy, struct net_device *netdev, + struct cfg80211_pmksa *pmksa) +{ + struct ath6kl *ar = ath6kl_priv(netdev); + struct ath6kl_vif *vif = netdev_priv(netdev); + + return ath6kl_wmi_setpmkid_cmd(ar->wmi, vif->fw_vif_idx, pmksa->bssid, + pmksa->pmkid, true); +} + +static int ath6kl_del_pmksa(struct wiphy *wiphy, struct net_device *netdev, + struct cfg80211_pmksa *pmksa) +{ + struct ath6kl *ar = ath6kl_priv(netdev); + struct ath6kl_vif *vif = netdev_priv(netdev); + + return ath6kl_wmi_setpmkid_cmd(ar->wmi, vif->fw_vif_idx, pmksa->bssid, + pmksa->pmkid, false); +} + +static int ath6kl_flush_pmksa(struct wiphy *wiphy, struct net_device *netdev) +{ + struct ath6kl *ar = ath6kl_priv(netdev); + struct ath6kl_vif *vif = netdev_priv(netdev); + + if (test_bit(CONNECTED, &vif->flags)) + return ath6kl_wmi_setpmkid_cmd(ar->wmi, vif->fw_vif_idx, + vif->bssid, NULL, false); + return 0; +} + +static int ath6kl_wow_usr(struct ath6kl *ar, struct ath6kl_vif *vif, + struct cfg80211_wowlan *wow, u32 *filter) +{ + int ret, pos; + u8 mask[WOW_PATTERN_SIZE]; + u16 i; + + /* Configure the patterns that we received from the user. */ + for (i = 0; i < wow->n_patterns; i++) { + + /* + * Convert given nl80211 specific mask value to equivalent + * driver specific mask value and send it to the chip along + * with patterns. For example, If the mask value defined in + * struct cfg80211_wowlan is 0xA (equivalent binary is 1010), + * then equivalent driver specific mask value is + * "0xFF 0x00 0xFF 0x00". + */ + memset(&mask, 0, sizeof(mask)); + for (pos = 0; pos < wow->patterns[i].pattern_len; pos++) { + if (wow->patterns[i].mask[pos / 8] & (0x1 << (pos % 8))) + mask[pos] = 0xFF; + } + /* + * Note: Pattern's offset is not passed as part of wowlan + * parameter from CFG layer. So it's always passed as ZERO + * to the firmware. It means, given WOW patterns are always + * matched from the first byte of received pkt in the firmware. + */ + ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi, + vif->fw_vif_idx, WOW_LIST_ID, + wow->patterns[i].pattern_len, + 0 /* pattern offset */, + wow->patterns[i].pattern, mask); + if (ret) + return ret; + } + + if (wow->disconnect) + *filter |= WOW_FILTER_OPTION_NWK_DISASSOC; + + if (wow->magic_pkt) + *filter |= WOW_FILTER_OPTION_MAGIC_PACKET; + + if (wow->gtk_rekey_failure) + *filter |= WOW_FILTER_OPTION_GTK_ERROR; + + if (wow->eap_identity_req) + *filter |= WOW_FILTER_OPTION_EAP_REQ; + + if (wow->four_way_handshake) + *filter |= WOW_FILTER_OPTION_8021X_4WAYHS; + + return 0; +} + +static int ath6kl_wow_ap(struct ath6kl *ar, struct ath6kl_vif *vif) +{ + static const u8 unicst_pattern[] = { 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08 }; + static const u8 unicst_mask[] = { 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x7f }; + u8 unicst_offset = 0; + static const u8 arp_pattern[] = { 0x08, 0x06 }; + static const u8 arp_mask[] = { 0xff, 0xff }; + u8 arp_offset = 20; + static const u8 discvr_pattern[] = { 0xe0, 0x00, 0x00, 0xf8 }; + static const u8 discvr_mask[] = { 0xf0, 0x00, 0x00, 0xf8 }; + u8 discvr_offset = 38; + static const u8 dhcp_pattern[] = { 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x43 /* port 67 */ }; + static const u8 dhcp_mask[] = { 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff /* port 67 */ }; + u8 dhcp_offset = 0; + int ret; + + /* Setup unicast IP, EAPOL-like and ARP pkt pattern */ + ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi, + vif->fw_vif_idx, WOW_LIST_ID, + sizeof(unicst_pattern), unicst_offset, + unicst_pattern, unicst_mask); + if (ret) { + ath6kl_err("failed to add WOW unicast IP pattern\n"); + return ret; + } + + /* Setup all ARP pkt pattern */ + ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi, + vif->fw_vif_idx, WOW_LIST_ID, + sizeof(arp_pattern), arp_offset, + arp_pattern, arp_mask); + if (ret) { + ath6kl_err("failed to add WOW ARP pattern\n"); + return ret; + } + + /* + * Setup multicast pattern for mDNS 224.0.0.251, + * SSDP 239.255.255.250 and LLMNR 224.0.0.252 + */ + ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi, + vif->fw_vif_idx, WOW_LIST_ID, + sizeof(discvr_pattern), discvr_offset, + discvr_pattern, discvr_mask); + if (ret) { + ath6kl_err("failed to add WOW mDNS/SSDP/LLMNR pattern\n"); + return ret; + } + + /* Setup all DHCP broadcast pkt pattern */ + ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi, + vif->fw_vif_idx, WOW_LIST_ID, + sizeof(dhcp_pattern), dhcp_offset, + dhcp_pattern, dhcp_mask); + if (ret) { + ath6kl_err("failed to add WOW DHCP broadcast pattern\n"); + return ret; + } + + return 0; +} + +static int ath6kl_wow_sta(struct ath6kl *ar, struct ath6kl_vif *vif) +{ + struct net_device *ndev = vif->ndev; + static const u8 discvr_pattern[] = { 0xe0, 0x00, 0x00, 0xf8 }; + static const u8 discvr_mask[] = { 0xf0, 0x00, 0x00, 0xf8 }; + u8 discvr_offset = 38; + u8 mac_mask[ETH_ALEN]; + int ret; + + /* Setup unicast pkt pattern */ + memset(mac_mask, 0xff, ETH_ALEN); + ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi, + vif->fw_vif_idx, WOW_LIST_ID, + ETH_ALEN, 0, ndev->dev_addr, + mac_mask); + if (ret) { + ath6kl_err("failed to add WOW unicast pattern\n"); + return ret; + } + + /* + * Setup multicast pattern for mDNS 224.0.0.251, + * SSDP 239.255.255.250 and LLMNR 224.0.0.252 + */ + if ((ndev->flags & IFF_ALLMULTI) || + (ndev->flags & IFF_MULTICAST && netdev_mc_count(ndev) > 0)) { + ret = ath6kl_wmi_add_wow_pattern_cmd(ar->wmi, + vif->fw_vif_idx, WOW_LIST_ID, + sizeof(discvr_pattern), discvr_offset, + discvr_pattern, discvr_mask); + if (ret) { + ath6kl_err("failed to add WOW mDNS/SSDP/LLMNR pattern\n"); + return ret; + } + } + + return 0; +} + +static int is_hsleep_mode_procsed(struct ath6kl_vif *vif) +{ + return test_bit(HOST_SLEEP_MODE_CMD_PROCESSED, &vif->flags); +} + +static bool is_ctrl_ep_empty(struct ath6kl *ar) +{ + return !ar->tx_pending[ar->ctrl_ep]; +} + +static int ath6kl_cfg80211_host_sleep(struct ath6kl *ar, struct ath6kl_vif *vif) +{ + int ret, left; + + clear_bit(HOST_SLEEP_MODE_CMD_PROCESSED, &vif->flags); + + ret = ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx, + ATH6KL_HOST_MODE_ASLEEP); + if (ret) + return ret; + + left = wait_event_interruptible_timeout(ar->event_wq, + is_hsleep_mode_procsed(vif), + WMI_TIMEOUT); + if (left == 0) { + ath6kl_warn("timeout, didn't get host sleep cmd processed event\n"); + ret = -ETIMEDOUT; + } else if (left < 0) { + ath6kl_warn("error while waiting for host sleep cmd processed event %d\n", + left); + ret = left; + } + + if (ar->tx_pending[ar->ctrl_ep]) { + left = wait_event_interruptible_timeout(ar->event_wq, + is_ctrl_ep_empty(ar), + WMI_TIMEOUT); + if (left == 0) { + ath6kl_warn("clear wmi ctrl data timeout\n"); + ret = -ETIMEDOUT; + } else if (left < 0) { + ath6kl_warn("clear wmi ctrl data failed: %d\n", left); + ret = left; + } + } + + return ret; +} + +static int ath6kl_wow_suspend_vif(struct ath6kl_vif *vif, + struct cfg80211_wowlan *wow, u32 *filter) +{ + struct ath6kl *ar = vif->ar; + struct in_device *in_dev; + struct in_ifaddr *ifa; + int ret; + u16 i, bmiss_time; + __be32 ips[MAX_IP_ADDRS]; + u8 index = 0; + + if (!test_bit(NETDEV_MCAST_ALL_ON, &vif->flags) && + test_bit(ATH6KL_FW_CAPABILITY_WOW_MULTICAST_FILTER, + ar->fw_capabilities)) { + ret = ath6kl_wmi_mcast_filter_cmd(vif->ar->wmi, + vif->fw_vif_idx, false); + if (ret) + return ret; + } + + /* Clear existing WOW patterns */ + for (i = 0; i < WOW_MAX_FILTERS_PER_LIST; i++) + ath6kl_wmi_del_wow_pattern_cmd(ar->wmi, vif->fw_vif_idx, + WOW_LIST_ID, i); + + /* + * Skip the default WOW pattern configuration + * if the driver receives any WOW patterns from + * the user. + */ + if (wow) + ret = ath6kl_wow_usr(ar, vif, wow, filter); + else if (vif->nw_type == AP_NETWORK) + ret = ath6kl_wow_ap(ar, vif); + else + ret = ath6kl_wow_sta(ar, vif); + + if (ret) + return ret; + + netif_stop_queue(vif->ndev); + + if (vif->nw_type != AP_NETWORK) { + ret = ath6kl_wmi_listeninterval_cmd(ar->wmi, vif->fw_vif_idx, + ATH6KL_MAX_WOW_LISTEN_INTL, + 0); + if (ret) + return ret; + + /* Set listen interval x 15 times as bmiss time */ + bmiss_time = ATH6KL_MAX_WOW_LISTEN_INTL * 15; + if (bmiss_time > ATH6KL_MAX_BMISS_TIME) + bmiss_time = ATH6KL_MAX_BMISS_TIME; + + ret = ath6kl_wmi_bmisstime_cmd(ar->wmi, vif->fw_vif_idx, + bmiss_time, 0); + if (ret) + return ret; + + ret = ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx, + 0xFFFF, 0, 0xFFFF, 0, 0, 0, + 0, 0, 0, 0); + if (ret) + return ret; + } + + /* Setup own IP addr for ARP agent. */ + in_dev = __in_dev_get_rtnl(vif->ndev); + if (!in_dev) + return 0; + + ifa = in_dev->ifa_list; + memset(&ips, 0, sizeof(ips)); + + /* Configure IP addr only if IP address count < MAX_IP_ADDRS */ + while (index < MAX_IP_ADDRS && ifa) { + ips[index] = ifa->ifa_local; + ifa = ifa->ifa_next; + index++; + } + + if (ifa) { + ath6kl_err("total IP addr count is exceeding fw limit\n"); + return -EINVAL; + } + + ret = ath6kl_wmi_set_ip_cmd(ar->wmi, vif->fw_vif_idx, ips[0], ips[1]); + if (ret) { + ath6kl_err("fail to setup ip for arp agent\n"); + return ret; + } + + return ret; +} + +static int ath6kl_wow_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow) +{ + struct ath6kl_vif *first_vif, *vif; + int ret = 0; + u32 filter = 0; + bool connected = false; + + /* enter / leave wow suspend on first vif always */ + first_vif = ath6kl_vif_first(ar); + if (WARN_ON(unlikely(!first_vif)) || + !ath6kl_cfg80211_ready(first_vif)) + return -EIO; + + if (wow && (wow->n_patterns > WOW_MAX_FILTERS_PER_LIST)) + return -EINVAL; + + /* install filters for each connected vif */ + spin_lock_bh(&ar->list_lock); + list_for_each_entry(vif, &ar->vif_list, list) { + if (!test_bit(CONNECTED, &vif->flags) || + !ath6kl_cfg80211_ready(vif)) + continue; + connected = true; + + ret = ath6kl_wow_suspend_vif(vif, wow, &filter); + if (ret) + break; + } + spin_unlock_bh(&ar->list_lock); + + if (!connected) + return -ENOTCONN; + else if (ret) + return ret; + + ar->state = ATH6KL_STATE_SUSPENDING; + + ret = ath6kl_wmi_set_wow_mode_cmd(ar->wmi, first_vif->fw_vif_idx, + ATH6KL_WOW_MODE_ENABLE, + filter, + WOW_HOST_REQ_DELAY); + if (ret) + return ret; + + return ath6kl_cfg80211_host_sleep(ar, first_vif); +} + +static int ath6kl_wow_resume_vif(struct ath6kl_vif *vif) +{ + struct ath6kl *ar = vif->ar; + int ret; + + if (vif->nw_type != AP_NETWORK) { + ret = ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0); + if (ret) + return ret; + + ret = ath6kl_wmi_listeninterval_cmd(ar->wmi, vif->fw_vif_idx, + vif->listen_intvl_t, 0); + if (ret) + return ret; + + ret = ath6kl_wmi_bmisstime_cmd(ar->wmi, vif->fw_vif_idx, + vif->bmiss_time_t, 0); + if (ret) + return ret; + } + + if (!test_bit(NETDEV_MCAST_ALL_OFF, &vif->flags) && + test_bit(ATH6KL_FW_CAPABILITY_WOW_MULTICAST_FILTER, + ar->fw_capabilities)) { + ret = ath6kl_wmi_mcast_filter_cmd(vif->ar->wmi, + vif->fw_vif_idx, true); + if (ret) + return ret; + } + + netif_wake_queue(vif->ndev); + + return 0; +} + +static int ath6kl_wow_resume(struct ath6kl *ar) +{ + struct ath6kl_vif *vif; + int ret; + + vif = ath6kl_vif_first(ar); + if (WARN_ON(unlikely(!vif)) || + !ath6kl_cfg80211_ready(vif)) + return -EIO; + + ar->state = ATH6KL_STATE_RESUMING; + + ret = ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx, + ATH6KL_HOST_MODE_AWAKE); + if (ret) { + ath6kl_warn("Failed to configure host sleep mode for wow resume: %d\n", + ret); + goto cleanup; + } + + spin_lock_bh(&ar->list_lock); + list_for_each_entry(vif, &ar->vif_list, list) { + if (!test_bit(CONNECTED, &vif->flags) || + !ath6kl_cfg80211_ready(vif)) + continue; + ret = ath6kl_wow_resume_vif(vif); + if (ret) + break; + } + spin_unlock_bh(&ar->list_lock); + + if (ret) + goto cleanup; + + ar->state = ATH6KL_STATE_ON; + return 0; + +cleanup: + ar->state = ATH6KL_STATE_WOW; + return ret; +} + +static int ath6kl_cfg80211_deepsleep_suspend(struct ath6kl *ar) +{ + struct ath6kl_vif *vif; + int ret; + + vif = ath6kl_vif_first(ar); + if (!vif) + return -EIO; + + if (!test_bit(WMI_READY, &ar->flag)) { + ath6kl_err("deepsleep failed as wmi is not ready\n"); + return -EIO; + } + + ath6kl_cfg80211_stop_all(ar); + + /* Save the current power mode before enabling power save */ + ar->wmi->saved_pwr_mode = ar->wmi->pwr_mode; + + ret = ath6kl_wmi_powermode_cmd(ar->wmi, 0, REC_POWER); + if (ret) + return ret; + + /* Disable WOW mode */ + ret = ath6kl_wmi_set_wow_mode_cmd(ar->wmi, vif->fw_vif_idx, + ATH6KL_WOW_MODE_DISABLE, + 0, 0); + if (ret) + return ret; + + /* Flush all non control pkts in TX path */ + ath6kl_tx_data_cleanup(ar); + + ret = ath6kl_cfg80211_host_sleep(ar, vif); + if (ret) + return ret; + + return 0; +} + +static int ath6kl_cfg80211_deepsleep_resume(struct ath6kl *ar) +{ + struct ath6kl_vif *vif; + int ret; + + vif = ath6kl_vif_first(ar); + + if (!vif) + return -EIO; + + if (ar->wmi->pwr_mode != ar->wmi->saved_pwr_mode) { + ret = ath6kl_wmi_powermode_cmd(ar->wmi, 0, + ar->wmi->saved_pwr_mode); + if (ret) + return ret; + } + + ret = ath6kl_wmi_set_host_sleep_mode_cmd(ar->wmi, vif->fw_vif_idx, + ATH6KL_HOST_MODE_AWAKE); + if (ret) + return ret; + + ar->state = ATH6KL_STATE_ON; + + /* Reset scan parameter to default values */ + ret = ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0); + if (ret) + return ret; + + return 0; +} + +int ath6kl_cfg80211_suspend(struct ath6kl *ar, + enum ath6kl_cfg_suspend_mode mode, + struct cfg80211_wowlan *wow) +{ + struct ath6kl_vif *vif; + enum ath6kl_state prev_state; + int ret; + + switch (mode) { + case ATH6KL_CFG_SUSPEND_WOW: + + ath6kl_dbg(ATH6KL_DBG_SUSPEND, "wow mode suspend\n"); + + /* Flush all non control pkts in TX path */ + ath6kl_tx_data_cleanup(ar); + + prev_state = ar->state; + + ret = ath6kl_wow_suspend(ar, wow); + if (ret) { + ar->state = prev_state; + return ret; + } + + ar->state = ATH6KL_STATE_WOW; + break; + + case ATH6KL_CFG_SUSPEND_DEEPSLEEP: + + ath6kl_dbg(ATH6KL_DBG_SUSPEND, "deep sleep suspend\n"); + + ret = ath6kl_cfg80211_deepsleep_suspend(ar); + if (ret) { + ath6kl_err("deepsleep suspend failed: %d\n", ret); + return ret; + } + + ar->state = ATH6KL_STATE_DEEPSLEEP; + + break; + + case ATH6KL_CFG_SUSPEND_CUTPOWER: + + ath6kl_cfg80211_stop_all(ar); + + if (ar->state == ATH6KL_STATE_OFF) { + ath6kl_dbg(ATH6KL_DBG_SUSPEND, + "suspend hw off, no action for cutpower\n"); + break; + } + + ath6kl_dbg(ATH6KL_DBG_SUSPEND, "suspend cutting power\n"); + + ret = ath6kl_init_hw_stop(ar); + if (ret) { + ath6kl_warn("failed to stop hw during suspend: %d\n", + ret); + } + + ar->state = ATH6KL_STATE_CUTPOWER; + + break; + + default: + break; + } + + list_for_each_entry(vif, &ar->vif_list, list) + ath6kl_cfg80211_scan_complete_event(vif, true); + + return 0; +} +EXPORT_SYMBOL(ath6kl_cfg80211_suspend); + +int ath6kl_cfg80211_resume(struct ath6kl *ar) +{ + int ret; + + switch (ar->state) { + case ATH6KL_STATE_WOW: + ath6kl_dbg(ATH6KL_DBG_SUSPEND, "wow mode resume\n"); + + ret = ath6kl_wow_resume(ar); + if (ret) { + ath6kl_warn("wow mode resume failed: %d\n", ret); + return ret; + } + + break; + + case ATH6KL_STATE_DEEPSLEEP: + ath6kl_dbg(ATH6KL_DBG_SUSPEND, "deep sleep resume\n"); + + ret = ath6kl_cfg80211_deepsleep_resume(ar); + if (ret) { + ath6kl_warn("deep sleep resume failed: %d\n", ret); + return ret; + } + break; + + case ATH6KL_STATE_CUTPOWER: + ath6kl_dbg(ATH6KL_DBG_SUSPEND, "resume restoring power\n"); + + ret = ath6kl_init_hw_start(ar); + if (ret) { + ath6kl_warn("Failed to boot hw in resume: %d\n", ret); + return ret; + } + break; + + default: + break; + } + + return 0; +} +EXPORT_SYMBOL(ath6kl_cfg80211_resume); + +#ifdef CONFIG_PM + +/* hif layer decides what suspend mode to use */ +static int __ath6kl_cfg80211_suspend(struct wiphy *wiphy, + struct cfg80211_wowlan *wow) +{ + struct ath6kl *ar = wiphy_priv(wiphy); + + ath6kl_recovery_suspend(ar); + + return ath6kl_hif_suspend(ar, wow); +} + +static int __ath6kl_cfg80211_resume(struct wiphy *wiphy) +{ + struct ath6kl *ar = wiphy_priv(wiphy); + int err; + + err = ath6kl_hif_resume(ar); + if (err) + return err; + + ath6kl_recovery_resume(ar); + + return 0; +} + +/* + * FIXME: WOW suspend mode is selected if the host sdio controller supports + * both sdio irq wake up and keep power. The target pulls sdio data line to + * wake up the host when WOW pattern matches. This causes sdio irq handler + * is being called in the host side which internally hits ath6kl's RX path. + * + * Since sdio interrupt is not disabled, RX path executes even before + * the host executes the actual resume operation from PM module. + * + * In the current scenario, WOW resume should happen before start processing + * any data from the target. So It's required to perform WOW resume in RX path. + * Ideally we should perform WOW resume only in the actual platform + * resume path. This area needs bit rework to avoid WOW resume in RX path. + * + * ath6kl_check_wow_status() is called from ath6kl_rx(). + */ +void ath6kl_check_wow_status(struct ath6kl *ar) +{ + if (ar->state == ATH6KL_STATE_SUSPENDING) + return; + + if (ar->state == ATH6KL_STATE_WOW) + ath6kl_cfg80211_resume(ar); +} + +#else + +void ath6kl_check_wow_status(struct ath6kl *ar) +{ +} +#endif + +static int ath6kl_set_htcap(struct ath6kl_vif *vif, enum ieee80211_band band, + bool ht_enable) +{ + struct ath6kl_htcap *htcap = &vif->htcap[band]; + + if (htcap->ht_enable == ht_enable) + return 0; + + if (ht_enable) { + /* Set default ht capabilities */ + htcap->ht_enable = true; + htcap->cap_info = (band == IEEE80211_BAND_2GHZ) ? + ath6kl_g_htcap : ath6kl_a_htcap; + htcap->ampdu_factor = IEEE80211_HT_MAX_AMPDU_16K; + } else /* Disable ht */ + memset(htcap, 0, sizeof(*htcap)); + + return ath6kl_wmi_set_htcap_cmd(vif->ar->wmi, vif->fw_vif_idx, + band, htcap); +} + +static int ath6kl_restore_htcap(struct ath6kl_vif *vif) +{ + struct wiphy *wiphy = vif->ar->wiphy; + int band, ret = 0; + + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { + if (!wiphy->bands[band]) + continue; + + ret = ath6kl_set_htcap(vif, band, + wiphy->bands[band]->ht_cap.ht_supported); + if (ret) + return ret; + } + + return ret; +} + +static bool ath6kl_is_p2p_ie(const u8 *pos) +{ + return pos[0] == WLAN_EID_VENDOR_SPECIFIC && pos[1] >= 4 && + pos[2] == 0x50 && pos[3] == 0x6f && + pos[4] == 0x9a && pos[5] == 0x09; +} + +static int ath6kl_set_ap_probe_resp_ies(struct ath6kl_vif *vif, + const u8 *ies, size_t ies_len) +{ + struct ath6kl *ar = vif->ar; + const u8 *pos; + u8 *buf = NULL; + size_t len = 0; + int ret; + + /* + * Filter out P2P IE(s) since they will be included depending on + * the Probe Request frame in ath6kl_send_go_probe_resp(). + */ + + if (ies && ies_len) { + buf = kmalloc(ies_len, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + pos = ies; + while (pos + 1 < ies + ies_len) { + if (pos + 2 + pos[1] > ies + ies_len) + break; + if (!ath6kl_is_p2p_ie(pos)) { + memcpy(buf + len, pos, 2 + pos[1]); + len += 2 + pos[1]; + } + pos += 2 + pos[1]; + } + } + + ret = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, + WMI_FRAME_PROBE_RESP, buf, len); + kfree(buf); + return ret; +} + +static int ath6kl_set_ies(struct ath6kl_vif *vif, + struct cfg80211_beacon_data *info) +{ + struct ath6kl *ar = vif->ar; + int res; + + /* this also clears IE in fw if it's not set */ + res = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, + WMI_FRAME_BEACON, + info->beacon_ies, + info->beacon_ies_len); + if (res) + return res; + + /* this also clears IE in fw if it's not set */ + res = ath6kl_set_ap_probe_resp_ies(vif, info->proberesp_ies, + info->proberesp_ies_len); + if (res) + return res; + + /* this also clears IE in fw if it's not set */ + res = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, + WMI_FRAME_ASSOC_RESP, + info->assocresp_ies, + info->assocresp_ies_len); + if (res) + return res; + + return 0; +} + +static int ath6kl_get_rsn_capab(struct cfg80211_beacon_data *beacon, + u8 *rsn_capab) +{ + const u8 *rsn_ie; + size_t rsn_ie_len; + u16 cnt; + + if (!beacon->tail) + return -EINVAL; + + rsn_ie = cfg80211_find_ie(WLAN_EID_RSN, beacon->tail, beacon->tail_len); + if (!rsn_ie) + return -EINVAL; + + rsn_ie_len = *(rsn_ie + 1); + /* skip element id and length */ + rsn_ie += 2; + + /* skip version */ + if (rsn_ie_len < 2) + return -EINVAL; + rsn_ie += 2; + rsn_ie_len -= 2; + + /* skip group cipher suite */ + if (rsn_ie_len < 4) + return 0; + rsn_ie += 4; + rsn_ie_len -= 4; + + /* skip pairwise cipher suite */ + if (rsn_ie_len < 2) + return 0; + cnt = get_unaligned_le16(rsn_ie); + rsn_ie += (2 + cnt * 4); + rsn_ie_len -= (2 + cnt * 4); + + /* skip akm suite */ + if (rsn_ie_len < 2) + return 0; + cnt = get_unaligned_le16(rsn_ie); + rsn_ie += (2 + cnt * 4); + rsn_ie_len -= (2 + cnt * 4); + + if (rsn_ie_len < 2) + return 0; + + memcpy(rsn_capab, rsn_ie, 2); + + return 0; +} + +static int ath6kl_start_ap(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_ap_settings *info) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct ath6kl_vif *vif = netdev_priv(dev); + struct ieee80211_mgmt *mgmt; + bool hidden = false; + u8 *ies; + int ies_len; + struct wmi_connect_cmd p; + int res; + int i, ret; + u16 rsn_capab = 0; + int inactivity_timeout = 0; + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s:\n", __func__); + + if (!ath6kl_cfg80211_ready(vif)) + return -EIO; + + if (vif->next_mode != AP_NETWORK) + return -EOPNOTSUPP; + + res = ath6kl_set_ies(vif, &info->beacon); + + ar->ap_mode_bkey.valid = false; + + ret = ath6kl_wmi_ap_set_beacon_intvl_cmd(ar->wmi, vif->fw_vif_idx, + info->beacon_interval); + + if (ret) + ath6kl_warn("Failed to set beacon interval: %d\n", ret); + + ret = ath6kl_wmi_ap_set_dtim_cmd(ar->wmi, vif->fw_vif_idx, + info->dtim_period); + + /* ignore error, just print a warning and continue normally */ + if (ret) + ath6kl_warn("Failed to set dtim_period in beacon: %d\n", ret); + + if (info->beacon.head == NULL) + return -EINVAL; + mgmt = (struct ieee80211_mgmt *) info->beacon.head; + ies = mgmt->u.beacon.variable; + if (ies > info->beacon.head + info->beacon.head_len) + return -EINVAL; + ies_len = info->beacon.head + info->beacon.head_len - ies; + + if (info->ssid == NULL) + return -EINVAL; + memcpy(vif->ssid, info->ssid, info->ssid_len); + vif->ssid_len = info->ssid_len; + if (info->hidden_ssid != NL80211_HIDDEN_SSID_NOT_IN_USE) + hidden = true; + + res = ath6kl_wmi_ap_hidden_ssid(ar->wmi, vif->fw_vif_idx, hidden); + if (res) + return res; + + ret = ath6kl_set_auth_type(vif, info->auth_type); + if (ret) + return ret; + + memset(&p, 0, sizeof(p)); + + for (i = 0; i < info->crypto.n_akm_suites; i++) { + switch (info->crypto.akm_suites[i]) { + case WLAN_AKM_SUITE_8021X: + if (info->crypto.wpa_versions & NL80211_WPA_VERSION_1) + p.auth_mode |= WPA_AUTH; + if (info->crypto.wpa_versions & NL80211_WPA_VERSION_2) + p.auth_mode |= WPA2_AUTH; + break; + case WLAN_AKM_SUITE_PSK: + if (info->crypto.wpa_versions & NL80211_WPA_VERSION_1) + p.auth_mode |= WPA_PSK_AUTH; + if (info->crypto.wpa_versions & NL80211_WPA_VERSION_2) + p.auth_mode |= WPA2_PSK_AUTH; + break; + } + } + if (p.auth_mode == 0) + p.auth_mode = NONE_AUTH; + vif->auth_mode = p.auth_mode; + + for (i = 0; i < info->crypto.n_ciphers_pairwise; i++) { + switch (info->crypto.ciphers_pairwise[i]) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + p.prwise_crypto_type |= WEP_CRYPT; + break; + case WLAN_CIPHER_SUITE_TKIP: + p.prwise_crypto_type |= TKIP_CRYPT; + break; + case WLAN_CIPHER_SUITE_CCMP: + p.prwise_crypto_type |= AES_CRYPT; + break; + case WLAN_CIPHER_SUITE_SMS4: + p.prwise_crypto_type |= WAPI_CRYPT; + break; + } + } + if (p.prwise_crypto_type == 0) { + p.prwise_crypto_type = NONE_CRYPT; + ath6kl_set_cipher(vif, 0, true); + } else if (info->crypto.n_ciphers_pairwise == 1) + ath6kl_set_cipher(vif, info->crypto.ciphers_pairwise[0], true); + + switch (info->crypto.cipher_group) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + p.grp_crypto_type = WEP_CRYPT; + break; + case WLAN_CIPHER_SUITE_TKIP: + p.grp_crypto_type = TKIP_CRYPT; + break; + case WLAN_CIPHER_SUITE_CCMP: + p.grp_crypto_type = AES_CRYPT; + break; + case WLAN_CIPHER_SUITE_SMS4: + p.grp_crypto_type = WAPI_CRYPT; + break; + default: + p.grp_crypto_type = NONE_CRYPT; + break; + } + ath6kl_set_cipher(vif, info->crypto.cipher_group, false); + + p.nw_type = AP_NETWORK; + vif->nw_type = vif->next_mode; + + p.ssid_len = vif->ssid_len; + memcpy(p.ssid, vif->ssid, vif->ssid_len); + p.dot11_auth_mode = vif->dot11_auth_mode; + p.ch = cpu_to_le16(info->chandef.chan->center_freq); + + /* Enable uAPSD support by default */ + res = ath6kl_wmi_ap_set_apsd(ar->wmi, vif->fw_vif_idx, true); + if (res < 0) + return res; + + if (vif->wdev.iftype == NL80211_IFTYPE_P2P_GO) { + p.nw_subtype = SUBTYPE_P2PGO; + } else { + /* + * Due to firmware limitation, it is not possible to + * do P2P mgmt operations in AP mode + */ + p.nw_subtype = SUBTYPE_NONE; + } + + if (info->inactivity_timeout) { + + inactivity_timeout = info->inactivity_timeout; + + if (ar->hw.flags & ATH6KL_HW_AP_INACTIVITY_MINS) + inactivity_timeout = DIV_ROUND_UP(inactivity_timeout, + 60); + + res = ath6kl_wmi_set_inact_period(ar->wmi, vif->fw_vif_idx, + inactivity_timeout); + if (res < 0) + return res; + } + + if (ath6kl_set_htcap(vif, info->chandef.chan->band, + cfg80211_get_chandef_type(&info->chandef) + != NL80211_CHAN_NO_HT)) + return -EIO; + + /* + * Get the PTKSA replay counter in the RSN IE. Supplicant + * will use the RSN IE in M3 message and firmware has to + * advertise the same in beacon/probe response. Send + * the complete RSN IE capability field to firmware + */ + if (!ath6kl_get_rsn_capab(&info->beacon, (u8 *) &rsn_capab) && + test_bit(ATH6KL_FW_CAPABILITY_RSN_CAP_OVERRIDE, + ar->fw_capabilities)) { + res = ath6kl_wmi_set_ie_cmd(ar->wmi, vif->fw_vif_idx, + WLAN_EID_RSN, WMI_RSN_IE_CAPB, + (const u8 *) &rsn_capab, + sizeof(rsn_capab)); + vif->rsn_capab = rsn_capab; + if (res < 0) + return res; + } + + memcpy(&vif->profile, &p, sizeof(p)); + res = ath6kl_wmi_ap_profile_commit(ar->wmi, vif->fw_vif_idx, &p); + if (res < 0) + return res; + + return 0; +} + +static int ath6kl_change_beacon(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_beacon_data *beacon) +{ + struct ath6kl_vif *vif = netdev_priv(dev); + + if (!ath6kl_cfg80211_ready(vif)) + return -EIO; + + if (vif->next_mode != AP_NETWORK) + return -EOPNOTSUPP; + + return ath6kl_set_ies(vif, beacon); +} + +static int ath6kl_stop_ap(struct wiphy *wiphy, struct net_device *dev) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct ath6kl_vif *vif = netdev_priv(dev); + + if (vif->nw_type != AP_NETWORK) + return -EOPNOTSUPP; + if (!test_bit(CONNECTED, &vif->flags)) + return -ENOTCONN; + + ath6kl_wmi_disconnect_cmd(ar->wmi, vif->fw_vif_idx); + clear_bit(CONNECTED, &vif->flags); + + /* Restore ht setting in firmware */ + return ath6kl_restore_htcap(vif); +} + +static const u8 bcast_addr[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +static int ath6kl_del_station(struct wiphy *wiphy, struct net_device *dev, + u8 *mac) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct ath6kl_vif *vif = netdev_priv(dev); + const u8 *addr = mac ? mac : bcast_addr; + + return ath6kl_wmi_ap_set_mlme(ar->wmi, vif->fw_vif_idx, WMI_AP_DEAUTH, + addr, WLAN_REASON_PREV_AUTH_NOT_VALID); +} + +static int ath6kl_change_station(struct wiphy *wiphy, struct net_device *dev, + u8 *mac, struct station_parameters *params) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct ath6kl_vif *vif = netdev_priv(dev); + int err; + + if (vif->nw_type != AP_NETWORK) + return -EOPNOTSUPP; + + err = cfg80211_check_station_change(wiphy, params, + CFG80211_STA_AP_MLME_CLIENT); + if (err) + return err; + + if (params->sta_flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED)) + return ath6kl_wmi_ap_set_mlme(ar->wmi, vif->fw_vif_idx, + WMI_AP_MLME_AUTHORIZE, mac, 0); + return ath6kl_wmi_ap_set_mlme(ar->wmi, vif->fw_vif_idx, + WMI_AP_MLME_UNAUTHORIZE, mac, 0); +} + +static int ath6kl_remain_on_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + struct ieee80211_channel *chan, + unsigned int duration, + u64 *cookie) +{ + struct ath6kl_vif *vif = ath6kl_vif_from_wdev(wdev); + struct ath6kl *ar = ath6kl_priv(vif->ndev); + u32 id; + + /* TODO: if already pending or ongoing remain-on-channel, + * return -EBUSY */ + id = ++vif->last_roc_id; + if (id == 0) { + /* Do not use 0 as the cookie value */ + id = ++vif->last_roc_id; + } + *cookie = id; + + return ath6kl_wmi_remain_on_chnl_cmd(ar->wmi, vif->fw_vif_idx, + chan->center_freq, duration); +} + +static int ath6kl_cancel_remain_on_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + u64 cookie) +{ + struct ath6kl_vif *vif = ath6kl_vif_from_wdev(wdev); + struct ath6kl *ar = ath6kl_priv(vif->ndev); + + if (cookie != vif->last_roc_id) + return -ENOENT; + vif->last_cancel_roc_id = cookie; + + return ath6kl_wmi_cancel_remain_on_chnl_cmd(ar->wmi, vif->fw_vif_idx); +} + +static int ath6kl_send_go_probe_resp(struct ath6kl_vif *vif, + const u8 *buf, size_t len, + unsigned int freq) +{ + struct ath6kl *ar = vif->ar; + const u8 *pos; + u8 *p2p; + int p2p_len; + int ret; + const struct ieee80211_mgmt *mgmt; + + mgmt = (const struct ieee80211_mgmt *) buf; + + /* Include P2P IE(s) from the frame generated in user space. */ + + p2p = kmalloc(len, GFP_KERNEL); + if (p2p == NULL) + return -ENOMEM; + p2p_len = 0; + + pos = mgmt->u.probe_resp.variable; + while (pos + 1 < buf + len) { + if (pos + 2 + pos[1] > buf + len) + break; + if (ath6kl_is_p2p_ie(pos)) { + memcpy(p2p + p2p_len, pos, 2 + pos[1]); + p2p_len += 2 + pos[1]; + } + pos += 2 + pos[1]; + } + + ret = ath6kl_wmi_send_probe_response_cmd(ar->wmi, vif->fw_vif_idx, freq, + mgmt->da, p2p, p2p_len); + kfree(p2p); + return ret; +} + +static bool ath6kl_mgmt_powersave_ap(struct ath6kl_vif *vif, + u32 id, + u32 freq, + u32 wait, + const u8 *buf, + size_t len, + bool *more_data, + bool no_cck) +{ + struct ieee80211_mgmt *mgmt; + struct ath6kl_sta *conn; + bool is_psq_empty = false; + struct ath6kl_mgmt_buff *mgmt_buf; + size_t mgmt_buf_size; + struct ath6kl *ar = vif->ar; + + mgmt = (struct ieee80211_mgmt *) buf; + if (is_multicast_ether_addr(mgmt->da)) + return false; + + conn = ath6kl_find_sta(vif, mgmt->da); + if (!conn) + return false; + + if (conn->sta_flags & STA_PS_SLEEP) { + if (!(conn->sta_flags & STA_PS_POLLED)) { + /* Queue the frames if the STA is sleeping */ + mgmt_buf_size = len + sizeof(struct ath6kl_mgmt_buff); + mgmt_buf = kmalloc(mgmt_buf_size, GFP_KERNEL); + if (!mgmt_buf) + return false; + + INIT_LIST_HEAD(&mgmt_buf->list); + mgmt_buf->id = id; + mgmt_buf->freq = freq; + mgmt_buf->wait = wait; + mgmt_buf->len = len; + mgmt_buf->no_cck = no_cck; + memcpy(mgmt_buf->buf, buf, len); + spin_lock_bh(&conn->psq_lock); + is_psq_empty = skb_queue_empty(&conn->psq) && + (conn->mgmt_psq_len == 0); + list_add_tail(&mgmt_buf->list, &conn->mgmt_psq); + conn->mgmt_psq_len++; + spin_unlock_bh(&conn->psq_lock); + + /* + * If this is the first pkt getting queued + * for this STA, update the PVB for this + * STA. + */ + if (is_psq_empty) + ath6kl_wmi_set_pvb_cmd(ar->wmi, vif->fw_vif_idx, + conn->aid, 1); + return true; + } + + /* + * This tx is because of a PsPoll. + * Determine if MoreData bit has to be set. + */ + spin_lock_bh(&conn->psq_lock); + if (!skb_queue_empty(&conn->psq) || (conn->mgmt_psq_len != 0)) + *more_data = true; + spin_unlock_bh(&conn->psq_lock); + } + + return false; +} + +/* Check if SSID length is greater than DIRECT- */ +static bool ath6kl_is_p2p_go_ssid(const u8 *buf, size_t len) +{ + const struct ieee80211_mgmt *mgmt; + mgmt = (const struct ieee80211_mgmt *) buf; + + /* variable[1] contains the SSID tag length */ + if (buf + len >= &mgmt->u.probe_resp.variable[1] && + (mgmt->u.probe_resp.variable[1] > P2P_WILDCARD_SSID_LEN)) { + return true; + } + + return false; +} + +static int ath6kl_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, + struct ieee80211_channel *chan, bool offchan, + unsigned int wait, const u8 *buf, size_t len, + bool no_cck, bool dont_wait_for_ack, u64 *cookie) +{ + struct ath6kl_vif *vif = ath6kl_vif_from_wdev(wdev); + struct ath6kl *ar = ath6kl_priv(vif->ndev); + u32 id; + const struct ieee80211_mgmt *mgmt; + bool more_data, queued; + + mgmt = (const struct ieee80211_mgmt *) buf; + if (vif->nw_type == AP_NETWORK && test_bit(CONNECTED, &vif->flags) && + ieee80211_is_probe_resp(mgmt->frame_control) && + ath6kl_is_p2p_go_ssid(buf, len)) { + /* + * Send Probe Response frame in GO mode using a separate WMI + * command to allow the target to fill in the generic IEs. + */ + *cookie = 0; /* TX status not supported */ + return ath6kl_send_go_probe_resp(vif, buf, len, + chan->center_freq); + } + + id = vif->send_action_id++; + if (id == 0) { + /* + * 0 is a reserved value in the WMI command and shall not be + * used for the command. + */ + id = vif->send_action_id++; + } + + *cookie = id; + + /* AP mode Power saving processing */ + if (vif->nw_type == AP_NETWORK) { + queued = ath6kl_mgmt_powersave_ap(vif, + id, chan->center_freq, + wait, buf, + len, &more_data, no_cck); + if (queued) + return 0; + } + + return ath6kl_wmi_send_mgmt_cmd(ar->wmi, vif->fw_vif_idx, id, + chan->center_freq, wait, + buf, len, no_cck); +} + +static void ath6kl_mgmt_frame_register(struct wiphy *wiphy, + struct wireless_dev *wdev, + u16 frame_type, bool reg) +{ + struct ath6kl_vif *vif = ath6kl_vif_from_wdev(wdev); + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "%s: frame_type=0x%x reg=%d\n", + __func__, frame_type, reg); + if (frame_type == IEEE80211_STYPE_PROBE_REQ) { + /* + * Note: This notification callback is not allowed to sleep, so + * we cannot send WMI_PROBE_REQ_REPORT_CMD here. Instead, we + * hardcode target to report Probe Request frames all the time. + */ + vif->probe_req_report = reg; + } +} + +static int ath6kl_cfg80211_sscan_start(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_sched_scan_request *request) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct ath6kl_vif *vif = netdev_priv(dev); + u16 interval; + int ret, rssi_thold; + + if (ar->state != ATH6KL_STATE_ON) + return -EIO; + + if (vif->sme_state != SME_DISCONNECTED) + return -EBUSY; + + ath6kl_cfg80211_scan_complete_event(vif, true); + + ret = ath6kl_set_probed_ssids(ar, vif, request->ssids, + request->n_ssids, + request->match_sets, + request->n_match_sets); + if (ret < 0) + return ret; + + if (!request->n_match_sets) { + ret = ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx, + ALL_BSS_FILTER, 0); + if (ret < 0) + return ret; + } else { + ret = ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx, + MATCHED_SSID_FILTER, 0); + if (ret < 0) + return ret; + } + + if (test_bit(ATH6KL_FW_CAPABILITY_RSSI_SCAN_THOLD, + ar->fw_capabilities)) { + if (request->rssi_thold <= NL80211_SCAN_RSSI_THOLD_OFF) + rssi_thold = 0; + else if (request->rssi_thold < -127) + rssi_thold = -127; + else + rssi_thold = request->rssi_thold; + + ret = ath6kl_wmi_set_rssi_filter_cmd(ar->wmi, vif->fw_vif_idx, + rssi_thold); + if (ret) { + ath6kl_err("failed to set RSSI threshold for scan\n"); + return ret; + } + } + + /* fw uses seconds, also make sure that it's >0 */ + interval = max_t(u16, 1, request->interval / 1000); + + ath6kl_wmi_scanparams_cmd(ar->wmi, vif->fw_vif_idx, + interval, interval, + vif->bg_scan_period, 0, 0, 0, 3, 0, 0, 0); + + /* this also clears IE in fw if it's not set */ + ret = ath6kl_wmi_set_appie_cmd(ar->wmi, vif->fw_vif_idx, + WMI_FRAME_PROBE_REQ, + request->ie, request->ie_len); + if (ret) { + ath6kl_warn("Failed to set probe request IE for scheduled scan: %d\n", + ret); + return ret; + } + + ret = ath6kl_wmi_enable_sched_scan_cmd(ar->wmi, vif->fw_vif_idx, true); + if (ret) + return ret; + + set_bit(SCHED_SCANNING, &vif->flags); + + return 0; +} + +static int ath6kl_cfg80211_sscan_stop(struct wiphy *wiphy, + struct net_device *dev) +{ + struct ath6kl_vif *vif = netdev_priv(dev); + bool stopped; + + stopped = __ath6kl_cfg80211_sscan_stop(vif); + + if (!stopped) + return -EIO; + + return 0; +} + +static int ath6kl_cfg80211_set_bitrate(struct wiphy *wiphy, + struct net_device *dev, + const u8 *addr, + const struct cfg80211_bitrate_mask *mask) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct ath6kl_vif *vif = netdev_priv(dev); + + return ath6kl_wmi_set_bitrate_mask(ar->wmi, vif->fw_vif_idx, + mask); +} + +static int ath6kl_cfg80211_set_txe_config(struct wiphy *wiphy, + struct net_device *dev, + u32 rate, u32 pkts, u32 intvl) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct ath6kl_vif *vif = netdev_priv(dev); + + if (vif->nw_type != INFRA_NETWORK || + !test_bit(ATH6KL_FW_CAPABILITY_TX_ERR_NOTIFY, ar->fw_capabilities)) + return -EOPNOTSUPP; + + if (vif->sme_state != SME_CONNECTED) + return -ENOTCONN; + + /* save this since the firmware won't report the interval */ + vif->txe_intvl = intvl; + + return ath6kl_wmi_set_txe_notify(ar->wmi, vif->fw_vif_idx, + rate, pkts, intvl); +} + +static const struct ieee80211_txrx_stypes +ath6kl_mgmt_stypes[NUM_NL80211_IFTYPES] = { + [NL80211_IFTYPE_STATION] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + }, + [NL80211_IFTYPE_AP] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + }, + [NL80211_IFTYPE_P2P_CLIENT] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + }, + [NL80211_IFTYPE_P2P_GO] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + }, +}; + +static struct cfg80211_ops ath6kl_cfg80211_ops = { + .add_virtual_intf = ath6kl_cfg80211_add_iface, + .del_virtual_intf = ath6kl_cfg80211_del_iface, + .change_virtual_intf = ath6kl_cfg80211_change_iface, + .scan = ath6kl_cfg80211_scan, + .connect = ath6kl_cfg80211_connect, + .disconnect = ath6kl_cfg80211_disconnect, + .add_key = ath6kl_cfg80211_add_key, + .get_key = ath6kl_cfg80211_get_key, + .del_key = ath6kl_cfg80211_del_key, + .set_default_key = ath6kl_cfg80211_set_default_key, + .set_wiphy_params = ath6kl_cfg80211_set_wiphy_params, + .set_tx_power = ath6kl_cfg80211_set_txpower, + .get_tx_power = ath6kl_cfg80211_get_txpower, + .set_power_mgmt = ath6kl_cfg80211_set_power_mgmt, + .join_ibss = ath6kl_cfg80211_join_ibss, + .leave_ibss = ath6kl_cfg80211_leave_ibss, + .get_station = ath6kl_get_station, + .set_pmksa = ath6kl_set_pmksa, + .del_pmksa = ath6kl_del_pmksa, + .flush_pmksa = ath6kl_flush_pmksa, + CFG80211_TESTMODE_CMD(ath6kl_tm_cmd) +#ifdef CONFIG_PM + .suspend = __ath6kl_cfg80211_suspend, + .resume = __ath6kl_cfg80211_resume, +#endif + .start_ap = ath6kl_start_ap, + .change_beacon = ath6kl_change_beacon, + .stop_ap = ath6kl_stop_ap, + .del_station = ath6kl_del_station, + .change_station = ath6kl_change_station, + .remain_on_channel = ath6kl_remain_on_channel, + .cancel_remain_on_channel = ath6kl_cancel_remain_on_channel, + .mgmt_tx = ath6kl_mgmt_tx, + .mgmt_frame_register = ath6kl_mgmt_frame_register, + .sched_scan_start = ath6kl_cfg80211_sscan_start, + .sched_scan_stop = ath6kl_cfg80211_sscan_stop, + .set_bitrate_mask = ath6kl_cfg80211_set_bitrate, + .set_cqm_txe_config = ath6kl_cfg80211_set_txe_config, +}; + +void ath6kl_cfg80211_stop(struct ath6kl_vif *vif) +{ + ath6kl_cfg80211_sscan_disable(vif); + + switch (vif->sme_state) { + case SME_DISCONNECTED: + break; + case SME_CONNECTING: + cfg80211_connect_result(vif->ndev, vif->bssid, NULL, 0, + NULL, 0, + WLAN_STATUS_UNSPECIFIED_FAILURE, + GFP_KERNEL); + break; + case SME_CONNECTED: + cfg80211_disconnected(vif->ndev, 0, NULL, 0, GFP_KERNEL); + break; + } + + if (vif->ar->state != ATH6KL_STATE_RECOVERY && + (test_bit(CONNECTED, &vif->flags) || + test_bit(CONNECT_PEND, &vif->flags))) + ath6kl_wmi_disconnect_cmd(vif->ar->wmi, vif->fw_vif_idx); + + vif->sme_state = SME_DISCONNECTED; + clear_bit(CONNECTED, &vif->flags); + clear_bit(CONNECT_PEND, &vif->flags); + + /* Stop netdev queues, needed during recovery */ + netif_stop_queue(vif->ndev); + netif_carrier_off(vif->ndev); + + /* disable scanning */ + if (vif->ar->state != ATH6KL_STATE_RECOVERY && + ath6kl_wmi_scanparams_cmd(vif->ar->wmi, vif->fw_vif_idx, 0xFFFF, + 0, 0, 0, 0, 0, 0, 0, 0, 0) != 0) + ath6kl_warn("failed to disable scan during stop\n"); + + ath6kl_cfg80211_scan_complete_event(vif, true); +} + +void ath6kl_cfg80211_stop_all(struct ath6kl *ar) +{ + struct ath6kl_vif *vif; + + vif = ath6kl_vif_first(ar); + if (!vif && ar->state != ATH6KL_STATE_RECOVERY) { + /* save the current power mode before enabling power save */ + ar->wmi->saved_pwr_mode = ar->wmi->pwr_mode; + + if (ath6kl_wmi_powermode_cmd(ar->wmi, 0, REC_POWER) != 0) + ath6kl_warn("ath6kl_deep_sleep_enable: wmi_powermode_cmd failed\n"); + return; + } + + /* + * FIXME: we should take ar->list_lock to protect changes in the + * vif_list, but that's not trivial to do as ath6kl_cfg80211_stop() + * sleeps. + */ + list_for_each_entry(vif, &ar->vif_list, list) + ath6kl_cfg80211_stop(vif); +} + +static void ath6kl_cfg80211_reg_notify(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct ath6kl *ar = wiphy_priv(wiphy); + u32 rates[IEEE80211_NUM_BANDS]; + int ret, i; + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "cfg reg_notify %c%c%s%s initiator %d hint_type %d\n", + request->alpha2[0], request->alpha2[1], + request->intersect ? " intersect" : "", + request->processed ? " processed" : "", + request->initiator, request->user_reg_hint_type); + + if (request->user_reg_hint_type != NL80211_USER_REG_HINT_CELL_BASE) + return; + + ret = ath6kl_wmi_set_regdomain_cmd(ar->wmi, request->alpha2); + if (ret) { + ath6kl_err("failed to set regdomain: %d\n", ret); + return; + } + + /* + * Firmware will apply the regdomain change only after a scan is + * issued and it will send a WMI_REGDOMAIN_EVENTID when it has been + * changed. + */ + + for (i = 0; i < IEEE80211_NUM_BANDS; i++) + if (wiphy->bands[i]) + rates[i] = (1 << wiphy->bands[i]->n_bitrates) - 1; + + + ret = ath6kl_wmi_beginscan_cmd(ar->wmi, 0, WMI_LONG_SCAN, false, + false, 0, ATH6KL_FG_SCAN_INTERVAL, + 0, NULL, false, rates); + if (ret) { + ath6kl_err("failed to start scan for a regdomain change: %d\n", + ret); + return; + } +} + +static int ath6kl_cfg80211_vif_init(struct ath6kl_vif *vif) +{ + vif->aggr_cntxt = aggr_init(vif); + if (!vif->aggr_cntxt) { + ath6kl_err("failed to initialize aggr\n"); + return -ENOMEM; + } + + setup_timer(&vif->disconnect_timer, disconnect_timer_handler, + (unsigned long) vif->ndev); + setup_timer(&vif->sched_scan_timer, ath6kl_wmi_sscan_timer, + (unsigned long) vif); + + set_bit(WMM_ENABLED, &vif->flags); + spin_lock_init(&vif->if_lock); + + INIT_LIST_HEAD(&vif->mc_filter); + + return 0; +} + +void ath6kl_cfg80211_vif_stop(struct ath6kl_vif *vif, bool wmi_ready) +{ + static u8 bcast_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + bool discon_issued; + + netif_stop_queue(vif->ndev); + + clear_bit(WLAN_ENABLED, &vif->flags); + + if (wmi_ready) { + discon_issued = test_bit(CONNECTED, &vif->flags) || + test_bit(CONNECT_PEND, &vif->flags); + ath6kl_disconnect(vif); + del_timer(&vif->disconnect_timer); + + if (discon_issued) + ath6kl_disconnect_event(vif, DISCONNECT_CMD, + (vif->nw_type & AP_NETWORK) ? + bcast_mac : vif->bssid, + 0, NULL, 0); + } + + if (vif->scan_req) { + cfg80211_scan_done(vif->scan_req, true); + vif->scan_req = NULL; + } + + /* need to clean up enhanced bmiss detection fw state */ + ath6kl_cfg80211_sta_bmiss_enhance(vif, false); +} + +void ath6kl_cfg80211_vif_cleanup(struct ath6kl_vif *vif) +{ + struct ath6kl *ar = vif->ar; + struct ath6kl_mc_filter *mc_filter, *tmp; + + aggr_module_destroy(vif->aggr_cntxt); + + ar->avail_idx_map |= BIT(vif->fw_vif_idx); + + if (vif->nw_type == ADHOC_NETWORK) + ar->ibss_if_active = false; + + list_for_each_entry_safe(mc_filter, tmp, &vif->mc_filter, list) { + list_del(&mc_filter->list); + kfree(mc_filter); + } + + unregister_netdevice(vif->ndev); + + ar->num_vif--; +} + +struct wireless_dev *ath6kl_interface_add(struct ath6kl *ar, const char *name, + enum nl80211_iftype type, + u8 fw_vif_idx, u8 nw_type) +{ + struct net_device *ndev; + struct ath6kl_vif *vif; + + ndev = alloc_netdev(sizeof(*vif), name, ether_setup); + if (!ndev) + return NULL; + + vif = netdev_priv(ndev); + ndev->ieee80211_ptr = &vif->wdev; + vif->wdev.wiphy = ar->wiphy; + vif->ar = ar; + vif->ndev = ndev; + SET_NETDEV_DEV(ndev, wiphy_dev(vif->wdev.wiphy)); + vif->wdev.netdev = ndev; + vif->wdev.iftype = type; + vif->fw_vif_idx = fw_vif_idx; + vif->nw_type = nw_type; + vif->next_mode = nw_type; + vif->listen_intvl_t = ATH6KL_DEFAULT_LISTEN_INTVAL; + vif->bmiss_time_t = ATH6KL_DEFAULT_BMISS_TIME; + vif->bg_scan_period = 0; + vif->htcap[IEEE80211_BAND_2GHZ].ht_enable = true; + vif->htcap[IEEE80211_BAND_5GHZ].ht_enable = true; + + memcpy(ndev->dev_addr, ar->mac_addr, ETH_ALEN); + if (fw_vif_idx != 0) { + ndev->dev_addr[0] = (ndev->dev_addr[0] ^ (1 << fw_vif_idx)) | + 0x2; + if (test_bit(ATH6KL_FW_CAPABILITY_CUSTOM_MAC_ADDR, + ar->fw_capabilities)) + ndev->dev_addr[4] ^= 0x80; + } + + init_netdev(ndev); + + ath6kl_init_control_info(vif); + + if (ath6kl_cfg80211_vif_init(vif)) + goto err; + + if (register_netdevice(ndev)) + goto err; + + ar->avail_idx_map &= ~BIT(fw_vif_idx); + vif->sme_state = SME_DISCONNECTED; + set_bit(WLAN_ENABLED, &vif->flags); + ar->wlan_pwr_state = WLAN_POWER_STATE_ON; + + if (type == NL80211_IFTYPE_ADHOC) + ar->ibss_if_active = true; + + spin_lock_bh(&ar->list_lock); + list_add_tail(&vif->list, &ar->vif_list); + spin_unlock_bh(&ar->list_lock); + + return &vif->wdev; + +err: + aggr_module_destroy(vif->aggr_cntxt); + free_netdev(ndev); + return NULL; +} + +int ath6kl_cfg80211_init(struct ath6kl *ar) +{ + struct wiphy *wiphy = ar->wiphy; + bool band_2gig = false, band_5gig = false, ht = false; + int ret; + + wiphy->mgmt_stypes = ath6kl_mgmt_stypes; + + wiphy->max_remain_on_channel_duration = 5000; + + /* set device pointer for wiphy */ + set_wiphy_dev(wiphy, ar->dev); + + wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_AP); + if (ar->p2p) { + wiphy->interface_modes |= BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_P2P_CLIENT); + } + + if (config_enabled(CPTCFG_ATH6KL_REGDOMAIN) && + test_bit(ATH6KL_FW_CAPABILITY_REGDOMAIN, ar->fw_capabilities)) { + wiphy->reg_notifier = ath6kl_cfg80211_reg_notify; + ar->wiphy->features |= NL80211_FEATURE_CELL_BASE_REG_HINTS; + } + + /* max num of ssids that can be probed during scanning */ + wiphy->max_scan_ssids = MAX_PROBED_SSIDS; + + /* max num of ssids that can be matched after scan */ + if (test_bit(ATH6KL_FW_CAPABILITY_SCHED_SCAN_MATCH_LIST, + ar->fw_capabilities)) + wiphy->max_match_sets = MAX_PROBED_SSIDS; + + wiphy->max_scan_ie_len = 1000; /* FIX: what is correct limit? */ + switch (ar->hw.cap) { + case WMI_11AN_CAP: + ht = true; + case WMI_11A_CAP: + band_5gig = true; + break; + case WMI_11GN_CAP: + ht = true; + case WMI_11G_CAP: + band_2gig = true; + break; + case WMI_11AGN_CAP: + ht = true; + case WMI_11AG_CAP: + band_2gig = true; + band_5gig = true; + break; + default: + ath6kl_err("invalid phy capability!\n"); + return -EINVAL; + } + + /* + * Even if the fw has HT support, advertise HT cap only when + * the firmware has support to override RSN capability, otherwise + * 4-way handshake would fail. + */ + if (!(ht && + test_bit(ATH6KL_FW_CAPABILITY_RSN_CAP_OVERRIDE, + ar->fw_capabilities))) { + ath6kl_band_2ghz.ht_cap.cap = 0; + ath6kl_band_2ghz.ht_cap.ht_supported = false; + ath6kl_band_5ghz.ht_cap.cap = 0; + ath6kl_band_5ghz.ht_cap.ht_supported = false; + } + + if (ar->hw.flags & ATH6KL_HW_64BIT_RATES) { + ath6kl_band_2ghz.ht_cap.mcs.rx_mask[0] = 0xff; + ath6kl_band_5ghz.ht_cap.mcs.rx_mask[0] = 0xff; + ath6kl_band_2ghz.ht_cap.mcs.rx_mask[1] = 0xff; + ath6kl_band_5ghz.ht_cap.mcs.rx_mask[1] = 0xff; + } else { + ath6kl_band_2ghz.ht_cap.mcs.rx_mask[0] = 0xff; + ath6kl_band_5ghz.ht_cap.mcs.rx_mask[0] = 0xff; + } + + if (band_2gig) + wiphy->bands[IEEE80211_BAND_2GHZ] = &ath6kl_band_2ghz; + if (band_5gig) + wiphy->bands[IEEE80211_BAND_5GHZ] = &ath6kl_band_5ghz; + + wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + + wiphy->cipher_suites = cipher_suites; + wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites); + +#ifdef CONFIG_PM + wiphy->wowlan.flags = WIPHY_WOWLAN_MAGIC_PKT | + WIPHY_WOWLAN_DISCONNECT | + WIPHY_WOWLAN_GTK_REKEY_FAILURE | + WIPHY_WOWLAN_SUPPORTS_GTK_REKEY | + WIPHY_WOWLAN_EAP_IDENTITY_REQ | + WIPHY_WOWLAN_4WAY_HANDSHAKE; + wiphy->wowlan.n_patterns = WOW_MAX_FILTERS_PER_LIST; + wiphy->wowlan.pattern_min_len = 1; + wiphy->wowlan.pattern_max_len = WOW_PATTERN_SIZE; +#endif + + wiphy->max_sched_scan_ssids = MAX_PROBED_SSIDS; + + ar->wiphy->flags |= WIPHY_FLAG_SUPPORTS_FW_ROAM | + WIPHY_FLAG_HAVE_AP_SME | + WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL | + WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD; + + if (test_bit(ATH6KL_FW_CAPABILITY_SCHED_SCAN_V2, ar->fw_capabilities)) + ar->wiphy->flags |= WIPHY_FLAG_SUPPORTS_SCHED_SCAN; + + if (test_bit(ATH6KL_FW_CAPABILITY_INACTIVITY_TIMEOUT, + ar->fw_capabilities)) + ar->wiphy->features |= NL80211_FEATURE_INACTIVITY_TIMER; + + ar->wiphy->probe_resp_offload = + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS | + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 | + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P; + + ret = wiphy_register(wiphy); + if (ret < 0) { + ath6kl_err("couldn't register wiphy device\n"); + return ret; + } + + ar->wiphy_registered = true; + + return 0; +} + +void ath6kl_cfg80211_cleanup(struct ath6kl *ar) +{ + wiphy_unregister(ar->wiphy); + + ar->wiphy_registered = false; +} + +struct ath6kl *ath6kl_cfg80211_create(void) +{ + struct ath6kl *ar; + struct wiphy *wiphy; + + /* create a new wiphy for use with cfg80211 */ + wiphy = wiphy_new(&ath6kl_cfg80211_ops, sizeof(struct ath6kl)); + + if (!wiphy) { + ath6kl_err("couldn't allocate wiphy device\n"); + return NULL; + } + + ar = wiphy_priv(wiphy); + ar->wiphy = wiphy; + + return ar; +} + +/* Note: ar variable must not be accessed after calling this! */ +void ath6kl_cfg80211_destroy(struct ath6kl *ar) +{ + int i; + + for (i = 0; i < AP_MAX_NUM_STA; i++) + kfree(ar->sta_list[i].aggr_conn); + + wiphy_free(ar->wiphy); +} + diff --git a/drivers/net/wireless/ath/ath6kl/cfg80211.h b/drivers/net/wireless/ath/ath6kl/cfg80211.h new file mode 100644 index 0000000..b59becd --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/cfg80211.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef ATH6KL_CFG80211_H +#define ATH6KL_CFG80211_H + +enum ath6kl_cfg_suspend_mode { + ATH6KL_CFG_SUSPEND_DEEPSLEEP, + ATH6KL_CFG_SUSPEND_CUTPOWER, + ATH6KL_CFG_SUSPEND_WOW, +}; + +struct wireless_dev *ath6kl_interface_add(struct ath6kl *ar, const char *name, + enum nl80211_iftype type, + u8 fw_vif_idx, u8 nw_type); +void ath6kl_cfg80211_ch_switch_notify(struct ath6kl_vif *vif, int freq, + enum wmi_phy_mode mode); +void ath6kl_cfg80211_scan_complete_event(struct ath6kl_vif *vif, bool aborted); + +void ath6kl_cfg80211_connect_event(struct ath6kl_vif *vif, u16 channel, + u8 *bssid, u16 listen_intvl, + u16 beacon_intvl, + enum network_type nw_type, + u8 beacon_ie_len, u8 assoc_req_len, + u8 assoc_resp_len, u8 *assoc_info); + +void ath6kl_cfg80211_disconnect_event(struct ath6kl_vif *vif, u8 reason, + u8 *bssid, u8 assoc_resp_len, + u8 *assoc_info, u16 proto_reason); + +void ath6kl_cfg80211_tkip_micerr_event(struct ath6kl_vif *vif, u8 keyid, + bool ismcast); + +int ath6kl_cfg80211_suspend(struct ath6kl *ar, + enum ath6kl_cfg_suspend_mode mode, + struct cfg80211_wowlan *wow); + +int ath6kl_cfg80211_resume(struct ath6kl *ar); + +void ath6kl_cfg80211_vif_cleanup(struct ath6kl_vif *vif); + +void ath6kl_cfg80211_stop(struct ath6kl_vif *vif); +void ath6kl_cfg80211_stop_all(struct ath6kl *ar); + +int ath6kl_cfg80211_init(struct ath6kl *ar); +void ath6kl_cfg80211_cleanup(struct ath6kl *ar); + +struct ath6kl *ath6kl_cfg80211_create(void); +void ath6kl_cfg80211_destroy(struct ath6kl *ar); + +#endif /* ATH6KL_CFG80211_H */ diff --git a/drivers/net/wireless/ath/ath6kl/common.h b/drivers/net/wireless/ath/ath6kl/common.h new file mode 100644 index 0000000..98a8861 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/common.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef COMMON_H +#define COMMON_H + +#include + +#define ATH6KL_MAX_IE 256 + +extern __printf(2, 3) +int ath6kl_printk(const char *level, const char *fmt, ...); + +/* + * Reflects the version of binary interface exposed by ATH6KL target + * firmware. Needs to be incremented by 1 for any change in the firmware + * that requires upgrade of the driver on the host side for the change to + * work correctly + */ +#define ATH6KL_ABI_VERSION 1 + +#define SIGNAL_QUALITY_METRICS_NUM_MAX 2 + +enum { + SIGNAL_QUALITY_METRICS_SNR = 0, + SIGNAL_QUALITY_METRICS_RSSI, + SIGNAL_QUALITY_METRICS_ALL, +}; + +/* + * Data Path + */ + +#define WMI_MAX_TX_DATA_FRAME_LENGTH \ + (1500 + sizeof(struct wmi_data_hdr) + \ + sizeof(struct ethhdr) + \ + sizeof(struct ath6kl_llc_snap_hdr)) + +/* An AMSDU frame */ /* The MAX AMSDU length of AR6003 is 3839 */ +#define WMI_MAX_AMSDU_RX_DATA_FRAME_LENGTH \ + (3840 + sizeof(struct wmi_data_hdr) + \ + sizeof(struct ethhdr) + \ + sizeof(struct ath6kl_llc_snap_hdr)) + +#define EPPING_ALIGNMENT_PAD \ + (((sizeof(struct htc_frame_hdr) + 3) & (~0x3)) \ + - sizeof(struct htc_frame_hdr)) + +struct ath6kl_llc_snap_hdr { + u8 dsap; + u8 ssap; + u8 cntl; + u8 org_code[3]; + __be16 eth_type; +} __packed; + +enum crypto_type { + NONE_CRYPT = 0x01, + WEP_CRYPT = 0x02, + TKIP_CRYPT = 0x04, + AES_CRYPT = 0x08, + WAPI_CRYPT = 0x10, +}; + +struct htc_endpoint_credit_dist; +struct ath6kl; +struct ath6kl_htcap; +enum htc_credit_dist_reason; +struct ath6kl_htc_credit_info; + +struct sk_buff *ath6kl_buf_alloc(int size); +#endif /* COMMON_H */ diff --git a/drivers/net/wireless/ath/ath6kl/core.c b/drivers/net/wireless/ath/ath6kl/core.c new file mode 100644 index 0000000..4b46adb --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/core.c @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "core.h" + +#include +#include +#include +#include + +#include "debug.h" +#include "hif-ops.h" +#include "htc-ops.h" +#include "cfg80211.h" + +unsigned int debug_mask; +static unsigned int suspend_mode; +static unsigned int wow_mode; +static unsigned int uart_debug; +static unsigned int ath6kl_p2p; +static unsigned int testmode; +static unsigned int recovery_enable; +static unsigned int heart_beat_poll; + +module_param(debug_mask, uint, 0644); +module_param(suspend_mode, uint, 0644); +module_param(wow_mode, uint, 0644); +module_param(uart_debug, uint, 0644); +module_param(ath6kl_p2p, uint, 0644); +module_param(testmode, uint, 0644); +module_param(recovery_enable, uint, 0644); +module_param(heart_beat_poll, uint, 0644); +MODULE_PARM_DESC(recovery_enable, "Enable recovery from firmware error"); +MODULE_PARM_DESC(heart_beat_poll, "Enable fw error detection periodic" \ + "polling. This also specifies the polling interval in" \ + "msecs. Set reocvery_enable for this to be effective"); + +void ath6kl_core_tx_complete(struct ath6kl *ar, struct sk_buff *skb) +{ + ath6kl_htc_tx_complete(ar, skb); +} +EXPORT_SYMBOL(ath6kl_core_tx_complete); + +void ath6kl_core_rx_complete(struct ath6kl *ar, struct sk_buff *skb, u8 pipe) +{ + ath6kl_htc_rx_complete(ar, skb, pipe); +} +EXPORT_SYMBOL(ath6kl_core_rx_complete); + +int ath6kl_core_init(struct ath6kl *ar, enum ath6kl_htc_type htc_type) +{ + struct ath6kl_bmi_target_info targ_info; + struct wireless_dev *wdev; + int ret = 0, i; + + switch (htc_type) { + case ATH6KL_HTC_TYPE_MBOX: + ath6kl_htc_mbox_attach(ar); + break; + case ATH6KL_HTC_TYPE_PIPE: + ath6kl_htc_pipe_attach(ar); + break; + default: + WARN_ON(1); + return -ENOMEM; + } + + ar->ath6kl_wq = create_singlethread_workqueue("ath6kl"); + if (!ar->ath6kl_wq) + return -ENOMEM; + + ret = ath6kl_bmi_init(ar); + if (ret) + goto err_wq; + + /* + * Turn on power to get hardware (target) version and leave power + * on delibrately as we will boot the hardware anyway within few + * seconds. + */ + ret = ath6kl_hif_power_on(ar); + if (ret) + goto err_bmi_cleanup; + + ret = ath6kl_bmi_get_target_info(ar, &targ_info); + if (ret) + goto err_power_off; + + ar->version.target_ver = le32_to_cpu(targ_info.version); + ar->target_type = le32_to_cpu(targ_info.type); + ar->wiphy->hw_version = le32_to_cpu(targ_info.version); + + ret = ath6kl_init_hw_params(ar); + if (ret) + goto err_power_off; + + ar->htc_target = ath6kl_htc_create(ar); + + if (!ar->htc_target) { + ret = -ENOMEM; + goto err_power_off; + } + + ar->testmode = testmode; + + ret = ath6kl_init_fetch_firmwares(ar); + if (ret) + goto err_htc_cleanup; + + /* FIXME: we should free all firmwares in the error cases below */ + + /* Indicate that WMI is enabled (although not ready yet) */ + set_bit(WMI_ENABLED, &ar->flag); + ar->wmi = ath6kl_wmi_init(ar); + if (!ar->wmi) { + ath6kl_err("failed to initialize wmi\n"); + ret = -EIO; + goto err_htc_cleanup; + } + + ath6kl_dbg(ATH6KL_DBG_TRC, "%s: got wmi @ 0x%p.\n", __func__, ar->wmi); + + /* setup access class priority mappings */ + ar->ac_stream_pri_map[WMM_AC_BK] = 0; /* lowest */ + ar->ac_stream_pri_map[WMM_AC_BE] = 1; + ar->ac_stream_pri_map[WMM_AC_VI] = 2; + ar->ac_stream_pri_map[WMM_AC_VO] = 3; /* highest */ + + /* allocate some buffers that handle larger AMSDU frames */ + ath6kl_refill_amsdu_rxbufs(ar, ATH6KL_MAX_AMSDU_RX_BUFFERS); + + ath6kl_cookie_init(ar); + + ar->conf_flags = ATH6KL_CONF_IGNORE_ERP_BARKER | + ATH6KL_CONF_ENABLE_11N | ATH6KL_CONF_ENABLE_TX_BURST; + + if (suspend_mode && + suspend_mode >= WLAN_POWER_STATE_CUT_PWR && + suspend_mode <= WLAN_POWER_STATE_WOW) + ar->suspend_mode = suspend_mode; + else + ar->suspend_mode = 0; + + if (suspend_mode == WLAN_POWER_STATE_WOW && + (wow_mode == WLAN_POWER_STATE_CUT_PWR || + wow_mode == WLAN_POWER_STATE_DEEP_SLEEP)) + ar->wow_suspend_mode = wow_mode; + else + ar->wow_suspend_mode = 0; + + if (uart_debug) + ar->conf_flags |= ATH6KL_CONF_UART_DEBUG; + + set_bit(FIRST_BOOT, &ar->flag); + + ath6kl_debug_init(ar); + + ret = ath6kl_init_hw_start(ar); + if (ret) { + ath6kl_err("Failed to start hardware: %d\n", ret); + goto err_rxbuf_cleanup; + } + + /* give our connected endpoints some buffers */ + ath6kl_rx_refill(ar->htc_target, ar->ctrl_ep); + ath6kl_rx_refill(ar->htc_target, ar->ac2ep_map[WMM_AC_BE]); + + ret = ath6kl_cfg80211_init(ar); + if (ret) + goto err_rxbuf_cleanup; + + ret = ath6kl_debug_init_fs(ar); + if (ret) { + wiphy_unregister(ar->wiphy); + goto err_rxbuf_cleanup; + } + + for (i = 0; i < ar->vif_max; i++) + ar->avail_idx_map |= BIT(i); + + rtnl_lock(); + + /* Add an initial station interface */ + wdev = ath6kl_interface_add(ar, "wlan%d", NL80211_IFTYPE_STATION, 0, + INFRA_NETWORK); + + rtnl_unlock(); + + if (!wdev) { + ath6kl_err("Failed to instantiate a network device\n"); + ret = -ENOMEM; + wiphy_unregister(ar->wiphy); + goto err_rxbuf_cleanup; + } + + ath6kl_dbg(ATH6KL_DBG_TRC, "%s: name=%s dev=0x%p, ar=0x%p\n", + __func__, wdev->netdev->name, wdev->netdev, ar); + + ar->fw_recovery.enable = !!recovery_enable; + if (!ar->fw_recovery.enable) + return ret; + + if (heart_beat_poll && + test_bit(ATH6KL_FW_CAPABILITY_HEART_BEAT_POLL, + ar->fw_capabilities)) + ar->fw_recovery.hb_poll = heart_beat_poll; + + ath6kl_recovery_init(ar); + + return ret; + +err_rxbuf_cleanup: + ath6kl_debug_cleanup(ar); + ath6kl_htc_flush_rx_buf(ar->htc_target); + ath6kl_cleanup_amsdu_rxbufs(ar); + ath6kl_wmi_shutdown(ar->wmi); + clear_bit(WMI_ENABLED, &ar->flag); + ar->wmi = NULL; +err_htc_cleanup: + ath6kl_htc_cleanup(ar->htc_target); +err_power_off: + ath6kl_hif_power_off(ar); +err_bmi_cleanup: + ath6kl_bmi_cleanup(ar); +err_wq: + destroy_workqueue(ar->ath6kl_wq); + + return ret; +} +EXPORT_SYMBOL(ath6kl_core_init); + +struct ath6kl *ath6kl_core_create(struct device *dev) +{ + struct ath6kl *ar; + u8 ctr; + + ar = ath6kl_cfg80211_create(); + if (!ar) + return NULL; + + ar->p2p = !!ath6kl_p2p; + ar->dev = dev; + + ar->vif_max = 1; + + ar->max_norm_iface = 1; + + spin_lock_init(&ar->lock); + spin_lock_init(&ar->mcastpsq_lock); + spin_lock_init(&ar->list_lock); + + init_waitqueue_head(&ar->event_wq); + sema_init(&ar->sem, 1); + + INIT_LIST_HEAD(&ar->amsdu_rx_buffer_queue); + INIT_LIST_HEAD(&ar->vif_list); + + clear_bit(WMI_ENABLED, &ar->flag); + clear_bit(SKIP_SCAN, &ar->flag); + clear_bit(DESTROY_IN_PROGRESS, &ar->flag); + + ar->tx_pwr = 0; + ar->intra_bss = 1; + ar->lrssi_roam_threshold = DEF_LRSSI_ROAM_THRESHOLD; + + ar->state = ATH6KL_STATE_OFF; + + memset((u8 *)ar->sta_list, 0, + AP_MAX_NUM_STA * sizeof(struct ath6kl_sta)); + + /* Init the PS queues */ + for (ctr = 0; ctr < AP_MAX_NUM_STA; ctr++) { + spin_lock_init(&ar->sta_list[ctr].psq_lock); + skb_queue_head_init(&ar->sta_list[ctr].psq); + skb_queue_head_init(&ar->sta_list[ctr].apsdq); + ar->sta_list[ctr].mgmt_psq_len = 0; + INIT_LIST_HEAD(&ar->sta_list[ctr].mgmt_psq); + ar->sta_list[ctr].aggr_conn = + kzalloc(sizeof(struct aggr_info_conn), GFP_KERNEL); + if (!ar->sta_list[ctr].aggr_conn) { + ath6kl_err("Failed to allocate memory for sta aggregation information\n"); + ath6kl_core_destroy(ar); + return NULL; + } + } + + skb_queue_head_init(&ar->mcastpsq); + + memcpy(ar->ap_country_code, DEF_AP_COUNTRY_CODE, 3); + + return ar; +} +EXPORT_SYMBOL(ath6kl_core_create); + +void ath6kl_core_cleanup(struct ath6kl *ar) +{ + ath6kl_hif_power_off(ar); + + ath6kl_recovery_cleanup(ar); + + destroy_workqueue(ar->ath6kl_wq); + + if (ar->htc_target) + ath6kl_htc_cleanup(ar->htc_target); + + ath6kl_cookie_cleanup(ar); + + ath6kl_cleanup_amsdu_rxbufs(ar); + + ath6kl_bmi_cleanup(ar); + + ath6kl_debug_cleanup(ar); + + kfree(ar->fw_board); + kfree(ar->fw_otp); + vfree(ar->fw); + kfree(ar->fw_patch); + kfree(ar->fw_testscript); + + ath6kl_cfg80211_cleanup(ar); +} +EXPORT_SYMBOL(ath6kl_core_cleanup); + +void ath6kl_core_destroy(struct ath6kl *ar) +{ + ath6kl_cfg80211_destroy(ar); +} +EXPORT_SYMBOL(ath6kl_core_destroy); + +MODULE_AUTHOR("Qualcomm Atheros"); +MODULE_DESCRIPTION("Core module for AR600x SDIO and USB devices."); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/wireless/ath/ath6kl/core.h b/drivers/net/wireless/ath/ath6kl/core.h new file mode 100644 index 0000000..6901688 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/core.h @@ -0,0 +1,964 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef CORE_H +#define CORE_H + +#include +#include +#include +#include +#include +#include +#include "htc.h" +#include "wmi.h" +#include "bmi.h" +#include "target.h" + +#define MAX_ATH6KL 1 +#define ATH6KL_MAX_RX_BUFFERS 16 +#define ATH6KL_BUFFER_SIZE 1664 +#define ATH6KL_MAX_AMSDU_RX_BUFFERS 4 +#define ATH6KL_AMSDU_REFILL_THRESHOLD 3 +#define ATH6KL_AMSDU_BUFFER_SIZE (WMI_MAX_AMSDU_RX_DATA_FRAME_LENGTH + 128) +#define MAX_MSDU_SUBFRAME_PAYLOAD_LEN 1508 +#define MIN_MSDU_SUBFRAME_PAYLOAD_LEN 46 + +#define USER_SAVEDKEYS_STAT_INIT 0 +#define USER_SAVEDKEYS_STAT_RUN 1 + +#define ATH6KL_TX_TIMEOUT 10 +#define ATH6KL_MAX_ENDPOINTS 4 +#define MAX_NODE_NUM 15 + +#define ATH6KL_APSD_ALL_FRAME 0xFFFF +#define ATH6KL_APSD_NUM_OF_AC 0x4 +#define ATH6KL_APSD_FRAME_MASK 0xF + +/* Extra bytes for htc header alignment */ +#define ATH6KL_HTC_ALIGN_BYTES 3 + +/* MAX_HI_COOKIE_NUM are reserved for high priority traffic */ +#define MAX_DEF_COOKIE_NUM 180 +#define MAX_HI_COOKIE_NUM 18 /* 10% of MAX_COOKIE_NUM */ +#define MAX_COOKIE_NUM (MAX_DEF_COOKIE_NUM + MAX_HI_COOKIE_NUM) + +#define MAX_DEFAULT_SEND_QUEUE_DEPTH (MAX_DEF_COOKIE_NUM / WMM_NUM_AC) + +#define DISCON_TIMER_INTVAL 10000 /* in msec */ + +/* Channel dwell time in fg scan */ +#define ATH6KL_FG_SCAN_INTERVAL 50 /* in ms */ + +/* includes also the null byte */ +#define ATH6KL_FIRMWARE_MAGIC "QCA-ATH6KL" + +enum ath6kl_fw_ie_type { + ATH6KL_FW_IE_FW_VERSION = 0, + ATH6KL_FW_IE_TIMESTAMP = 1, + ATH6KL_FW_IE_OTP_IMAGE = 2, + ATH6KL_FW_IE_FW_IMAGE = 3, + ATH6KL_FW_IE_PATCH_IMAGE = 4, + ATH6KL_FW_IE_RESERVED_RAM_SIZE = 5, + ATH6KL_FW_IE_CAPABILITIES = 6, + ATH6KL_FW_IE_PATCH_ADDR = 7, + ATH6KL_FW_IE_BOARD_ADDR = 8, + ATH6KL_FW_IE_VIF_MAX = 9, +}; + +enum ath6kl_fw_capability { + ATH6KL_FW_CAPABILITY_HOST_P2P = 0, + ATH6KL_FW_CAPABILITY_SCHED_SCAN = 1, + + /* + * Firmware is capable of supporting P2P mgmt operations on a + * station interface. After group formation, the station + * interface will become a P2P client/GO interface as the case may be + */ + ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX, + + /* + * Firmware has support to cleanup inactive stations + * in AP mode. + */ + ATH6KL_FW_CAPABILITY_INACTIVITY_TIMEOUT, + + /* Firmware has support to override rsn cap of rsn ie */ + ATH6KL_FW_CAPABILITY_RSN_CAP_OVERRIDE, + + /* + * Multicast support in WOW and host awake mode. + * Allow all multicast in host awake mode. + * Apply multicast filter in WOW mode. + */ + ATH6KL_FW_CAPABILITY_WOW_MULTICAST_FILTER, + + /* Firmware supports enhanced bmiss detection */ + ATH6KL_FW_CAPABILITY_BMISS_ENHANCE, + + /* + * FW supports matching of ssid in schedule scan + */ + ATH6KL_FW_CAPABILITY_SCHED_SCAN_MATCH_LIST, + + /* Firmware supports filtering BSS results by RSSI */ + ATH6KL_FW_CAPABILITY_RSSI_SCAN_THOLD, + + /* FW sets mac_addr[4] ^= 0x80 for newly created interfaces */ + ATH6KL_FW_CAPABILITY_CUSTOM_MAC_ADDR, + + /* Firmware supports TX error rate notification */ + ATH6KL_FW_CAPABILITY_TX_ERR_NOTIFY, + + /* supports WMI_SET_REGDOMAIN_CMDID command */ + ATH6KL_FW_CAPABILITY_REGDOMAIN, + + /* Firmware supports sched scan decoupled from host sleep */ + ATH6KL_FW_CAPABILITY_SCHED_SCAN_V2, + + /* + * Firmware capability for hang detection through heart beat + * challenge messages. + */ + ATH6KL_FW_CAPABILITY_HEART_BEAT_POLL, + + /* this needs to be last */ + ATH6KL_FW_CAPABILITY_MAX, +}; + +#define ATH6KL_CAPABILITY_LEN (ALIGN(ATH6KL_FW_CAPABILITY_MAX, 32) / 32) + +struct ath6kl_fw_ie { + __le32 id; + __le32 len; + u8 data[0]; +}; + +enum ath6kl_hw_flags { + ATH6KL_HW_64BIT_RATES = BIT(0), + ATH6KL_HW_AP_INACTIVITY_MINS = BIT(1), + ATH6KL_HW_MAP_LP_ENDPOINT = BIT(2), + ATH6KL_HW_SDIO_CRC_ERROR_WAR = BIT(3), +}; + +#define ATH6KL_FW_API2_FILE "fw-2.bin" +#define ATH6KL_FW_API3_FILE "fw-3.bin" +#define ATH6KL_FW_API4_FILE "fw-4.bin" + +/* AR6003 1.0 definitions */ +#define AR6003_HW_1_0_VERSION 0x300002ba + +/* AR6003 2.0 definitions */ +#define AR6003_HW_2_0_VERSION 0x30000384 +#define AR6003_HW_2_0_PATCH_DOWNLOAD_ADDRESS 0x57e910 +#define AR6003_HW_2_0_FW_DIR "ath6k/AR6003/hw2.0" +#define AR6003_HW_2_0_OTP_FILE "otp.bin.z77" +#define AR6003_HW_2_0_FIRMWARE_FILE "athwlan.bin.z77" +#define AR6003_HW_2_0_TCMD_FIRMWARE_FILE "athtcmd_ram.bin" +#define AR6003_HW_2_0_PATCH_FILE "data.patch.bin" +#define AR6003_HW_2_0_BOARD_DATA_FILE AR6003_HW_2_0_FW_DIR "/bdata.bin" +#define AR6003_HW_2_0_DEFAULT_BOARD_DATA_FILE \ + AR6003_HW_2_0_FW_DIR "/bdata.SD31.bin" + +/* AR6003 3.0 definitions */ +#define AR6003_HW_2_1_1_VERSION 0x30000582 +#define AR6003_HW_2_1_1_FW_DIR "ath6k/AR6003/hw2.1.1" +#define AR6003_HW_2_1_1_OTP_FILE "otp.bin" +#define AR6003_HW_2_1_1_FIRMWARE_FILE "athwlan.bin" +#define AR6003_HW_2_1_1_TCMD_FIRMWARE_FILE "athtcmd_ram.bin" +#define AR6003_HW_2_1_1_UTF_FIRMWARE_FILE "utf.bin" +#define AR6003_HW_2_1_1_TESTSCRIPT_FILE "nullTestFlow.bin" +#define AR6003_HW_2_1_1_PATCH_FILE "data.patch.bin" +#define AR6003_HW_2_1_1_BOARD_DATA_FILE AR6003_HW_2_1_1_FW_DIR "/bdata.bin" +#define AR6003_HW_2_1_1_DEFAULT_BOARD_DATA_FILE \ + AR6003_HW_2_1_1_FW_DIR "/bdata.SD31.bin" + +/* AR6004 1.0 definitions */ +#define AR6004_HW_1_0_VERSION 0x30000623 +#define AR6004_HW_1_0_FW_DIR "ath6k/AR6004/hw1.0" +#define AR6004_HW_1_0_FIRMWARE_FILE "fw.ram.bin" +#define AR6004_HW_1_0_BOARD_DATA_FILE AR6004_HW_1_0_FW_DIR "/bdata.bin" +#define AR6004_HW_1_0_DEFAULT_BOARD_DATA_FILE \ + AR6004_HW_1_0_FW_DIR "/bdata.DB132.bin" + +/* AR6004 1.1 definitions */ +#define AR6004_HW_1_1_VERSION 0x30000001 +#define AR6004_HW_1_1_FW_DIR "ath6k/AR6004/hw1.1" +#define AR6004_HW_1_1_FIRMWARE_FILE "fw.ram.bin" +#define AR6004_HW_1_1_BOARD_DATA_FILE AR6004_HW_1_1_FW_DIR "/bdata.bin" +#define AR6004_HW_1_1_DEFAULT_BOARD_DATA_FILE \ + AR6004_HW_1_1_FW_DIR "/bdata.DB132.bin" + +/* AR6004 1.2 definitions */ +#define AR6004_HW_1_2_VERSION 0x300007e8 +#define AR6004_HW_1_2_FW_DIR "ath6k/AR6004/hw1.2" +#define AR6004_HW_1_2_FIRMWARE_FILE "fw.ram.bin" +#define AR6004_HW_1_2_BOARD_DATA_FILE AR6004_HW_1_2_FW_DIR "/bdata.bin" +#define AR6004_HW_1_2_DEFAULT_BOARD_DATA_FILE \ + AR6004_HW_1_2_FW_DIR "/bdata.bin" + +/* AR6004 1.3 definitions */ +#define AR6004_HW_1_3_VERSION 0x31c8088a +#define AR6004_HW_1_3_FW_DIR "ath6k/AR6004/hw1.3" +#define AR6004_HW_1_3_FIRMWARE_FILE "fw.ram.bin" +#define AR6004_HW_1_3_BOARD_DATA_FILE "ath6k/AR6004/hw1.3/bdata.bin" +#define AR6004_HW_1_3_DEFAULT_BOARD_DATA_FILE "ath6k/AR6004/hw1.3/bdata.bin" + +/* Per STA data, used in AP mode */ +#define STA_PS_AWAKE BIT(0) +#define STA_PS_SLEEP BIT(1) +#define STA_PS_POLLED BIT(2) +#define STA_PS_APSD_TRIGGER BIT(3) +#define STA_PS_APSD_EOSP BIT(4) + +/* HTC TX packet tagging definitions */ +#define ATH6KL_CONTROL_PKT_TAG HTC_TX_PACKET_TAG_USER_DEFINED +#define ATH6KL_DATA_PKT_TAG (ATH6KL_CONTROL_PKT_TAG + 1) + +#define AR6003_CUST_DATA_SIZE 16 + +#define AGGR_WIN_IDX(x, y) ((x) % (y)) +#define AGGR_INCR_IDX(x, y) AGGR_WIN_IDX(((x) + 1), (y)) +#define AGGR_DCRM_IDX(x, y) AGGR_WIN_IDX(((x) - 1), (y)) +#define ATH6KL_MAX_SEQ_NO 0xFFF +#define ATH6KL_NEXT_SEQ_NO(x) (((x) + 1) & ATH6KL_MAX_SEQ_NO) + +#define NUM_OF_TIDS 8 +#define AGGR_SZ_DEFAULT 8 + +#define AGGR_WIN_SZ_MIN 2 +#define AGGR_WIN_SZ_MAX 8 + +#define TID_WINDOW_SZ(_x) ((_x) << 1) + +#define AGGR_NUM_OF_FREE_NETBUFS 16 + +#define AGGR_RX_TIMEOUT 100 /* in ms */ + +#define WMI_TIMEOUT (2 * HZ) + +#define MBOX_YIELD_LIMIT 99 + +#define ATH6KL_DEFAULT_LISTEN_INTVAL 100 /* in TUs */ +#define ATH6KL_DEFAULT_BMISS_TIME 1500 +#define ATH6KL_MAX_WOW_LISTEN_INTL 300 /* in TUs */ +#define ATH6KL_MAX_BMISS_TIME 5000 + +/* configuration lags */ +/* + * ATH6KL_CONF_IGNORE_ERP_BARKER: Ignore the barker premable in + * ERP IE of beacon to determine the short premable support when + * sending (Re)Assoc req. + * ATH6KL_CONF_IGNORE_PS_FAIL_EVT_IN_SCAN: Don't send the power + * module state transition failure events which happen during + * scan, to the host. + */ +#define ATH6KL_CONF_IGNORE_ERP_BARKER BIT(0) +#define ATH6KL_CONF_IGNORE_PS_FAIL_EVT_IN_SCAN BIT(1) +#define ATH6KL_CONF_ENABLE_11N BIT(2) +#define ATH6KL_CONF_ENABLE_TX_BURST BIT(3) +#define ATH6KL_CONF_UART_DEBUG BIT(4) + +#define P2P_WILDCARD_SSID_LEN 7 /* DIRECT- */ + +enum wlan_low_pwr_state { + WLAN_POWER_STATE_ON, + WLAN_POWER_STATE_CUT_PWR, + WLAN_POWER_STATE_DEEP_SLEEP, + WLAN_POWER_STATE_WOW +}; + +enum sme_state { + SME_DISCONNECTED, + SME_CONNECTING, + SME_CONNECTED +}; + +struct skb_hold_q { + struct sk_buff *skb; + bool is_amsdu; + u16 seq_no; +}; + +struct rxtid { + bool aggr; + bool timer_mon; + u16 win_sz; + u16 seq_next; + u32 hold_q_sz; + struct skb_hold_q *hold_q; + struct sk_buff_head q; + + /* + * lock mainly protects seq_next and hold_q. Movement of seq_next + * needs to be protected between aggr_timeout() and + * aggr_process_recv_frm(). hold_q will be holding the pending + * reorder frames and it's access should also be protected. + * Some of the other fields like hold_q_sz, win_sz and aggr are + * initialized/reset when receiving addba/delba req, also while + * deleting aggr state all the pending buffers are flushed before + * resetting these fields, so there should not be any race in accessing + * these fields. + */ + spinlock_t lock; +}; + +struct rxtid_stats { + u32 num_into_aggr; + u32 num_dups; + u32 num_oow; + u32 num_mpdu; + u32 num_amsdu; + u32 num_delivered; + u32 num_timeouts; + u32 num_hole; + u32 num_bar; +}; + +struct aggr_info_conn { + u8 aggr_sz; + u8 timer_scheduled; + struct timer_list timer; + struct net_device *dev; + struct rxtid rx_tid[NUM_OF_TIDS]; + struct rxtid_stats stat[NUM_OF_TIDS]; + struct aggr_info *aggr_info; +}; + +struct aggr_info { + struct aggr_info_conn *aggr_conn; + struct sk_buff_head rx_amsdu_freeq; +}; + +struct ath6kl_wep_key { + u8 key_index; + u8 key_len; + u8 key[64]; +}; + +#define ATH6KL_KEY_SEQ_LEN 8 + +struct ath6kl_key { + u8 key[WLAN_MAX_KEY_LEN]; + u8 key_len; + u8 seq[ATH6KL_KEY_SEQ_LEN]; + u8 seq_len; + u32 cipher; +}; + +struct ath6kl_node_mapping { + u8 mac_addr[ETH_ALEN]; + u8 ep_id; + u8 tx_pend; +}; + +struct ath6kl_cookie { + struct sk_buff *skb; + u32 map_no; + struct htc_packet htc_pkt; + struct ath6kl_cookie *arc_list_next; +}; + +struct ath6kl_mgmt_buff { + struct list_head list; + u32 freq; + u32 wait; + u32 id; + bool no_cck; + size_t len; + u8 buf[0]; +}; + +struct ath6kl_sta { + u16 sta_flags; + u8 mac[ETH_ALEN]; + u8 aid; + u8 keymgmt; + u8 ucipher; + u8 auth; + u8 wpa_ie[ATH6KL_MAX_IE]; + struct sk_buff_head psq; + + /* protects psq, mgmt_psq, apsdq, and mgmt_psq_len fields */ + spinlock_t psq_lock; + + struct list_head mgmt_psq; + size_t mgmt_psq_len; + u8 apsd_info; + struct sk_buff_head apsdq; + struct aggr_info_conn *aggr_conn; +}; + +struct ath6kl_version { + u32 target_ver; + u32 wlan_ver; + u32 abi_ver; +}; + +struct ath6kl_bmi { + u32 cmd_credits; + bool done_sent; + u8 *cmd_buf; + u32 max_data_size; + u32 max_cmd_size; +}; + +struct target_stats { + u64 tx_pkt; + u64 tx_byte; + u64 tx_ucast_pkt; + u64 tx_ucast_byte; + u64 tx_mcast_pkt; + u64 tx_mcast_byte; + u64 tx_bcast_pkt; + u64 tx_bcast_byte; + u64 tx_rts_success_cnt; + u64 tx_pkt_per_ac[4]; + + u64 tx_err; + u64 tx_fail_cnt; + u64 tx_retry_cnt; + u64 tx_mult_retry_cnt; + u64 tx_rts_fail_cnt; + + u64 rx_pkt; + u64 rx_byte; + u64 rx_ucast_pkt; + u64 rx_ucast_byte; + u64 rx_mcast_pkt; + u64 rx_mcast_byte; + u64 rx_bcast_pkt; + u64 rx_bcast_byte; + u64 rx_frgment_pkt; + + u64 rx_err; + u64 rx_crc_err; + u64 rx_key_cache_miss; + u64 rx_decrypt_err; + u64 rx_dupl_frame; + + u64 tkip_local_mic_fail; + u64 tkip_cnter_measures_invoked; + u64 tkip_replays; + u64 tkip_fmt_err; + u64 ccmp_fmt_err; + u64 ccmp_replays; + + u64 pwr_save_fail_cnt; + + u64 cs_bmiss_cnt; + u64 cs_low_rssi_cnt; + u64 cs_connect_cnt; + u64 cs_discon_cnt; + + s32 tx_ucast_rate; + s32 rx_ucast_rate; + + u32 lq_val; + + u32 wow_pkt_dropped; + u16 wow_evt_discarded; + + s16 noise_floor_calib; + s16 cs_rssi; + s16 cs_ave_beacon_rssi; + u8 cs_ave_beacon_snr; + u8 cs_last_roam_msec; + u8 cs_snr; + + u8 wow_host_pkt_wakeups; + u8 wow_host_evt_wakeups; + + u32 arp_received; + u32 arp_matched; + u32 arp_replied; +}; + +struct ath6kl_mbox_info { + u32 htc_addr; + u32 htc_ext_addr; + u32 htc_ext_sz; + + u32 block_size; + + u32 gmbox_addr; + + u32 gmbox_sz; +}; + +/* + * 802.11i defines an extended IV for use with non-WEP ciphers. + * When the EXTIV bit is set in the key id byte an additional + * 4 bytes immediately follow the IV for TKIP. For CCMP the + * EXTIV bit is likewise set but the 8 bytes represent the + * CCMP header rather than IV+extended-IV. + */ + +#define ATH6KL_KEYBUF_SIZE 16 +#define ATH6KL_MICBUF_SIZE (8+8) /* space for both tx and rx */ + +#define ATH6KL_KEY_XMIT 0x01 +#define ATH6KL_KEY_RECV 0x02 +#define ATH6KL_KEY_DEFAULT 0x80 /* default xmit key */ + +/* Initial group key for AP mode */ +struct ath6kl_req_key { + bool valid; + u8 key_index; + int key_type; + u8 key[WLAN_MAX_KEY_LEN]; + u8 key_len; +}; + +enum ath6kl_hif_type { + ATH6KL_HIF_TYPE_SDIO, + ATH6KL_HIF_TYPE_USB, +}; + +enum ath6kl_htc_type { + ATH6KL_HTC_TYPE_MBOX, + ATH6KL_HTC_TYPE_PIPE, +}; + +/* Max number of filters that hw supports */ +#define ATH6K_MAX_MC_FILTERS_PER_LIST 7 +struct ath6kl_mc_filter { + struct list_head list; + char hw_addr[ATH6KL_MCAST_FILTER_MAC_ADDR_SIZE]; +}; + +struct ath6kl_htcap { + bool ht_enable; + u8 ampdu_factor; + unsigned short cap_info; +}; + +/* + * Driver's maximum limit, note that some firmwares support only one vif + * and the runtime (current) limit must be checked from ar->vif_max. + */ +#define ATH6KL_VIF_MAX 3 + +/* vif flags info */ +enum ath6kl_vif_state { + CONNECTED, + CONNECT_PEND, + WMM_ENABLED, + NETQ_STOPPED, + DTIM_EXPIRED, + CLEAR_BSSFILTER_ON_BEACON, + DTIM_PERIOD_AVAIL, + WLAN_ENABLED, + STATS_UPDATE_PEND, + HOST_SLEEP_MODE_CMD_PROCESSED, + NETDEV_MCAST_ALL_ON, + NETDEV_MCAST_ALL_OFF, + SCHED_SCANNING, +}; + +struct ath6kl_vif { + struct list_head list; + struct wireless_dev wdev; + struct net_device *ndev; + struct ath6kl *ar; + /* Lock to protect vif specific net_stats and flags */ + spinlock_t if_lock; + u8 fw_vif_idx; + unsigned long flags; + int ssid_len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 dot11_auth_mode; + u8 auth_mode; + u8 prwise_crypto; + u8 prwise_crypto_len; + u8 grp_crypto; + u8 grp_crypto_len; + u8 def_txkey_index; + u8 next_mode; + u8 nw_type; + u8 bssid[ETH_ALEN]; + u8 req_bssid[ETH_ALEN]; + u16 ch_hint; + u16 bss_ch; + struct ath6kl_wep_key wep_key_list[WMI_MAX_KEY_INDEX + 1]; + struct ath6kl_key keys[WMI_MAX_KEY_INDEX + 1]; + struct aggr_info *aggr_cntxt; + struct ath6kl_htcap htcap[IEEE80211_NUM_BANDS]; + + struct timer_list disconnect_timer; + struct timer_list sched_scan_timer; + + struct cfg80211_scan_request *scan_req; + enum sme_state sme_state; + int reconnect_flag; + u32 last_roc_id; + u32 last_cancel_roc_id; + u32 send_action_id; + bool probe_req_report; + u16 assoc_bss_beacon_int; + u16 listen_intvl_t; + u16 bmiss_time_t; + u32 txe_intvl; + u16 bg_scan_period; + u8 assoc_bss_dtim_period; + struct net_device_stats net_stats; + struct target_stats target_stats; + struct wmi_connect_cmd profile; + u16 rsn_capab; + + struct list_head mc_filter; +}; + +static inline struct ath6kl_vif *ath6kl_vif_from_wdev(struct wireless_dev *wdev) +{ + return container_of(wdev, struct ath6kl_vif, wdev); +} + +#define WOW_LIST_ID 0 +#define WOW_HOST_REQ_DELAY 500 /* ms */ + +#define ATH6KL_SCHED_SCAN_RESULT_DELAY 5000 /* ms */ + +/* Flag info */ +enum ath6kl_dev_state { + WMI_ENABLED, + WMI_READY, + WMI_CTRL_EP_FULL, + TESTMODE, + DESTROY_IN_PROGRESS, + SKIP_SCAN, + ROAM_TBL_PEND, + FIRST_BOOT, + RECOVERY_CLEANUP, +}; + +enum ath6kl_state { + ATH6KL_STATE_OFF, + ATH6KL_STATE_ON, + ATH6KL_STATE_SUSPENDING, + ATH6KL_STATE_RESUMING, + ATH6KL_STATE_DEEPSLEEP, + ATH6KL_STATE_CUTPOWER, + ATH6KL_STATE_WOW, + ATH6KL_STATE_RECOVERY, +}; + +/* Fw error recovery */ +#define ATH6KL_HB_RESP_MISS_THRES 5 + +enum ath6kl_fw_err { + ATH6KL_FW_ASSERT, + ATH6KL_FW_HB_RESP_FAILURE, + ATH6KL_FW_EP_FULL, +}; + +struct ath6kl { + struct device *dev; + struct wiphy *wiphy; + + enum ath6kl_state state; + unsigned int testmode; + + struct ath6kl_bmi bmi; + const struct ath6kl_hif_ops *hif_ops; + const struct ath6kl_htc_ops *htc_ops; + struct wmi *wmi; + int tx_pending[ENDPOINT_MAX]; + int total_tx_data_pend; + struct htc_target *htc_target; + enum ath6kl_hif_type hif_type; + void *hif_priv; + struct list_head vif_list; + /* Lock to avoid race in vif_list entries among add/del/traverse */ + spinlock_t list_lock; + u8 num_vif; + unsigned int vif_max; + u8 max_norm_iface; + u8 avail_idx_map; + + /* + * Protects at least amsdu_rx_buffer_queue, ath6kl_alloc_cookie() + * calls, tx_pending and total_tx_data_pend. + */ + spinlock_t lock; + + struct semaphore sem; + u8 lrssi_roam_threshold; + struct ath6kl_version version; + u32 target_type; + u8 tx_pwr; + struct ath6kl_node_mapping node_map[MAX_NODE_NUM]; + u8 ibss_ps_enable; + bool ibss_if_active; + u8 node_num; + u8 next_ep_id; + struct ath6kl_cookie *cookie_list; + u32 cookie_count; + enum htc_endpoint_id ac2ep_map[WMM_NUM_AC]; + bool ac_stream_active[WMM_NUM_AC]; + u8 ac_stream_pri_map[WMM_NUM_AC]; + u8 hiac_stream_active_pri; + u8 ep2ac_map[ENDPOINT_MAX]; + enum htc_endpoint_id ctrl_ep; + struct ath6kl_htc_credit_info credit_state_info; + u32 connect_ctrl_flags; + u32 user_key_ctrl; + u8 usr_bss_filter; + struct ath6kl_sta sta_list[AP_MAX_NUM_STA]; + u8 sta_list_index; + struct ath6kl_req_key ap_mode_bkey; + struct sk_buff_head mcastpsq; + u32 want_ch_switch; + u16 last_ch; + + /* + * FIXME: protects access to mcastpsq but is actually useless as + * all skbe_queue_*() functions provide serialisation themselves + */ + spinlock_t mcastpsq_lock; + + u8 intra_bss; + struct wmi_ap_mode_stat ap_stats; + u8 ap_country_code[3]; + struct list_head amsdu_rx_buffer_queue; + u8 rx_meta_ver; + enum wlan_low_pwr_state wlan_pwr_state; + u8 mac_addr[ETH_ALEN]; +#define AR_MCAST_FILTER_MAC_ADDR_SIZE 4 + struct { + void *rx_report; + size_t rx_report_len; + } tm; + + struct ath6kl_hw { + u32 id; + const char *name; + u32 dataset_patch_addr; + u32 app_load_addr; + u32 app_start_override_addr; + u32 board_ext_data_addr; + u32 reserved_ram_size; + u32 board_addr; + u32 refclk_hz; + u32 uarttx_pin; + u32 testscript_addr; + enum wmi_phy_cap cap; + + u32 flags; + + struct ath6kl_hw_fw { + const char *dir; + const char *otp; + const char *fw; + const char *tcmd; + const char *patch; + const char *utf; + const char *testscript; + } fw; + + const char *fw_board; + const char *fw_default_board; + } hw; + + u16 conf_flags; + u16 suspend_mode; + u16 wow_suspend_mode; + wait_queue_head_t event_wq; + struct ath6kl_mbox_info mbox_info; + + struct ath6kl_cookie cookie_mem[MAX_COOKIE_NUM]; + unsigned long flag; + + u8 *fw_board; + size_t fw_board_len; + + u8 *fw_otp; + size_t fw_otp_len; + + u8 *fw; + size_t fw_len; + + u8 *fw_patch; + size_t fw_patch_len; + + u8 *fw_testscript; + size_t fw_testscript_len; + + unsigned int fw_api; + unsigned long fw_capabilities[ATH6KL_CAPABILITY_LEN]; + + struct workqueue_struct *ath6kl_wq; + + struct dentry *debugfs_phy; + + bool p2p; + + bool wiphy_registered; + + struct ath6kl_fw_recovery { + struct work_struct recovery_work; + unsigned long err_reason; + unsigned long hb_poll; + struct timer_list hb_timer; + u32 seq_num; + bool hb_pending; + u8 hb_misscnt; + bool enable; + } fw_recovery; + +#ifdef CPTCFG_ATH6KL_DEBUG + struct { + struct sk_buff_head fwlog_queue; + struct completion fwlog_completion; + bool fwlog_open; + + u32 fwlog_mask; + + unsigned int dbgfs_diag_reg; + u32 diag_reg_addr_wr; + u32 diag_reg_val_wr; + + struct { + unsigned int invalid_rate; + } war_stats; + + u8 *roam_tbl; + unsigned int roam_tbl_len; + + u8 keepalive; + u8 disc_timeout; + } debug; +#endif /* CPTCFG_ATH6KL_DEBUG */ +}; + +static inline struct ath6kl *ath6kl_priv(struct net_device *dev) +{ + return ((struct ath6kl_vif *) netdev_priv(dev))->ar; +} + +static inline u32 ath6kl_get_hi_item_addr(struct ath6kl *ar, + u32 item_offset) +{ + u32 addr = 0; + + if (ar->target_type == TARGET_TYPE_AR6003) + addr = ATH6KL_AR6003_HI_START_ADDR + item_offset; + else if (ar->target_type == TARGET_TYPE_AR6004) + addr = ATH6KL_AR6004_HI_START_ADDR + item_offset; + + return addr; +} + +int ath6kl_configure_target(struct ath6kl *ar); +void ath6kl_detect_error(unsigned long ptr); +void disconnect_timer_handler(unsigned long ptr); +void init_netdev(struct net_device *dev); +void ath6kl_cookie_init(struct ath6kl *ar); +void ath6kl_cookie_cleanup(struct ath6kl *ar); +void ath6kl_rx(struct htc_target *target, struct htc_packet *packet); +void ath6kl_tx_complete(struct htc_target *context, + struct list_head *packet_queue); +enum htc_send_full_action ath6kl_tx_queue_full(struct htc_target *target, + struct htc_packet *packet); +void ath6kl_stop_txrx(struct ath6kl *ar); +void ath6kl_cleanup_amsdu_rxbufs(struct ath6kl *ar); +int ath6kl_diag_write32(struct ath6kl *ar, u32 address, __le32 value); +int ath6kl_diag_write(struct ath6kl *ar, u32 address, void *data, u32 length); +int ath6kl_diag_read32(struct ath6kl *ar, u32 address, u32 *value); +int ath6kl_diag_read(struct ath6kl *ar, u32 address, void *data, u32 length); +int ath6kl_read_fwlogs(struct ath6kl *ar); +void ath6kl_init_profile_info(struct ath6kl_vif *vif); +void ath6kl_tx_data_cleanup(struct ath6kl *ar); + +struct ath6kl_cookie *ath6kl_alloc_cookie(struct ath6kl *ar); +void ath6kl_free_cookie(struct ath6kl *ar, struct ath6kl_cookie *cookie); +int ath6kl_data_tx(struct sk_buff *skb, struct net_device *dev); + +struct aggr_info *aggr_init(struct ath6kl_vif *vif); +void aggr_conn_init(struct ath6kl_vif *vif, struct aggr_info *aggr_info, + struct aggr_info_conn *aggr_conn); +void ath6kl_rx_refill(struct htc_target *target, + enum htc_endpoint_id endpoint); +void ath6kl_refill_amsdu_rxbufs(struct ath6kl *ar, int count); +struct htc_packet *ath6kl_alloc_amsdu_rxbuf(struct htc_target *target, + enum htc_endpoint_id endpoint, + int len); +void aggr_module_destroy(struct aggr_info *aggr_info); +void aggr_reset_state(struct aggr_info_conn *aggr_conn); + +struct ath6kl_sta *ath6kl_find_sta(struct ath6kl_vif *vif, u8 *node_addr); +struct ath6kl_sta *ath6kl_find_sta_by_aid(struct ath6kl *ar, u8 aid); + +void ath6kl_ready_event(void *devt, u8 *datap, u32 sw_ver, u32 abi_ver, + enum wmi_phy_cap cap); +int ath6kl_control_tx(void *devt, struct sk_buff *skb, + enum htc_endpoint_id eid); +void ath6kl_connect_event(struct ath6kl_vif *vif, u16 channel, + u8 *bssid, u16 listen_int, + u16 beacon_int, enum network_type net_type, + u8 beacon_ie_len, u8 assoc_req_len, + u8 assoc_resp_len, u8 *assoc_info); +void ath6kl_connect_ap_mode_bss(struct ath6kl_vif *vif, u16 channel); +void ath6kl_connect_ap_mode_sta(struct ath6kl_vif *vif, u16 aid, u8 *mac_addr, + u8 keymgmt, u8 ucipher, u8 auth, + u8 assoc_req_len, u8 *assoc_info, u8 apsd_info); +void ath6kl_disconnect_event(struct ath6kl_vif *vif, u8 reason, + u8 *bssid, u8 assoc_resp_len, + u8 *assoc_info, u16 prot_reason_status); +void ath6kl_tkip_micerr_event(struct ath6kl_vif *vif, u8 keyid, bool ismcast); +void ath6kl_txpwr_rx_evt(void *devt, u8 tx_pwr); +void ath6kl_scan_complete_evt(struct ath6kl_vif *vif, int status); +void ath6kl_tgt_stats_event(struct ath6kl_vif *vif, u8 *ptr, u32 len); +void ath6kl_indicate_tx_activity(void *devt, u8 traffic_class, bool active); +enum htc_endpoint_id ath6kl_ac2_endpoint_id(void *devt, u8 ac); + +void ath6kl_pspoll_event(struct ath6kl_vif *vif, u8 aid); + +void ath6kl_dtimexpiry_event(struct ath6kl_vif *vif); +void ath6kl_disconnect(struct ath6kl_vif *vif); +void aggr_recv_delba_req_evt(struct ath6kl_vif *vif, u8 tid); +void aggr_recv_addba_req_evt(struct ath6kl_vif *vif, u8 tid, u16 seq_no, + u8 win_sz); +void ath6kl_wakeup_event(void *dev); + +void ath6kl_init_control_info(struct ath6kl_vif *vif); +struct ath6kl_vif *ath6kl_vif_first(struct ath6kl *ar); +void ath6kl_cfg80211_vif_stop(struct ath6kl_vif *vif, bool wmi_ready); +int ath6kl_init_hw_start(struct ath6kl *ar); +int ath6kl_init_hw_stop(struct ath6kl *ar); +int ath6kl_init_fetch_firmwares(struct ath6kl *ar); +int ath6kl_init_hw_params(struct ath6kl *ar); + +void ath6kl_check_wow_status(struct ath6kl *ar); + +void ath6kl_core_tx_complete(struct ath6kl *ar, struct sk_buff *skb); +void ath6kl_core_rx_complete(struct ath6kl *ar, struct sk_buff *skb, u8 pipe); + +struct ath6kl *ath6kl_core_create(struct device *dev); +int ath6kl_core_init(struct ath6kl *ar, enum ath6kl_htc_type htc_type); +void ath6kl_core_cleanup(struct ath6kl *ar); +void ath6kl_core_destroy(struct ath6kl *ar); + +/* Fw error recovery */ +void ath6kl_init_hw_restart(struct ath6kl *ar); +void ath6kl_recovery_err_notify(struct ath6kl *ar, enum ath6kl_fw_err reason); +void ath6kl_recovery_hb_event(struct ath6kl *ar, u32 cookie); +void ath6kl_recovery_init(struct ath6kl *ar); +void ath6kl_recovery_cleanup(struct ath6kl *ar); +void ath6kl_recovery_suspend(struct ath6kl *ar); +void ath6kl_recovery_resume(struct ath6kl *ar); +#endif /* CORE_H */ diff --git a/drivers/net/wireless/ath/ath6kl/debug.c b/drivers/net/wireless/ath/ath6kl/debug.c new file mode 100644 index 0000000..bad321e --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/debug.c @@ -0,0 +1,1885 @@ +/* + * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "core.h" + +#include +#include +#include +#include + +#include "debug.h" +#include "target.h" + +struct ath6kl_fwlog_slot { + __le32 timestamp; + __le32 length; + + /* max ATH6KL_FWLOG_PAYLOAD_SIZE bytes */ + u8 payload[0]; +}; + +#define ATH6KL_FWLOG_MAX_ENTRIES 20 + +#define ATH6KL_FWLOG_VALID_MASK 0x1ffff + +int ath6kl_printk(const char *level, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + int rtn; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + rtn = printk("%sath6kl: %pV", level, &vaf); + + va_end(args); + + return rtn; +} +EXPORT_SYMBOL(ath6kl_printk); + +int ath6kl_info(const char *fmt, ...) +{ + struct va_format vaf = { + .fmt = fmt, + }; + va_list args; + int ret; + + va_start(args, fmt); + vaf.va = &args; + ret = ath6kl_printk(KERN_INFO, "%pV", &vaf); + trace_ath6kl_log_info(&vaf); + va_end(args); + + return ret; +} +EXPORT_SYMBOL(ath6kl_info); + +int ath6kl_err(const char *fmt, ...) +{ + struct va_format vaf = { + .fmt = fmt, + }; + va_list args; + int ret; + + va_start(args, fmt); + vaf.va = &args; + ret = ath6kl_printk(KERN_ERR, "%pV", &vaf); + trace_ath6kl_log_err(&vaf); + va_end(args); + + return ret; +} +EXPORT_SYMBOL(ath6kl_err); + +int ath6kl_warn(const char *fmt, ...) +{ + struct va_format vaf = { + .fmt = fmt, + }; + va_list args; + int ret; + + va_start(args, fmt); + vaf.va = &args; + ret = ath6kl_printk(KERN_WARNING, "%pV", &vaf); + trace_ath6kl_log_warn(&vaf); + va_end(args); + + return ret; +} +EXPORT_SYMBOL(ath6kl_warn); + +#ifdef CPTCFG_ATH6KL_DEBUG + +void ath6kl_dbg(enum ATH6K_DEBUG_MASK mask, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + if (debug_mask & mask) + ath6kl_printk(KERN_DEBUG, "%pV", &vaf); + + trace_ath6kl_log_dbg(mask, &vaf); + + va_end(args); +} +EXPORT_SYMBOL(ath6kl_dbg); + +void ath6kl_dbg_dump(enum ATH6K_DEBUG_MASK mask, + const char *msg, const char *prefix, + const void *buf, size_t len) +{ + if (debug_mask & mask) { + if (msg) + ath6kl_dbg(mask, "%s\n", msg); + + print_hex_dump_bytes(prefix, DUMP_PREFIX_OFFSET, buf, len); + } + + /* tracing code doesn't like null strings :/ */ + trace_ath6kl_log_dbg_dump(msg ? msg : "", prefix ? prefix : "", + buf, len); +} +EXPORT_SYMBOL(ath6kl_dbg_dump); + +#define REG_OUTPUT_LEN_PER_LINE 25 +#define REGTYPE_STR_LEN 100 + +struct ath6kl_diag_reg_info { + u32 reg_start; + u32 reg_end; + const char *reg_info; +}; + +static const struct ath6kl_diag_reg_info diag_reg[] = { + { 0x20000, 0x200fc, "General DMA and Rx registers" }, + { 0x28000, 0x28900, "MAC PCU register & keycache" }, + { 0x20800, 0x20a40, "QCU" }, + { 0x21000, 0x212f0, "DCU" }, + { 0x4000, 0x42e4, "RTC" }, + { 0x540000, 0x540000 + (256 * 1024), "RAM" }, + { 0x29800, 0x2B210, "Base Band" }, + { 0x1C000, 0x1C748, "Analog" }, +}; + +void ath6kl_dump_registers(struct ath6kl_device *dev, + struct ath6kl_irq_proc_registers *irq_proc_reg, + struct ath6kl_irq_enable_reg *irq_enable_reg) +{ + + ath6kl_dbg(ATH6KL_DBG_IRQ, ("<------- Register Table -------->\n")); + + if (irq_proc_reg != NULL) { + ath6kl_dbg(ATH6KL_DBG_IRQ, + "Host Int status: 0x%x\n", + irq_proc_reg->host_int_status); + ath6kl_dbg(ATH6KL_DBG_IRQ, + "CPU Int status: 0x%x\n", + irq_proc_reg->cpu_int_status); + ath6kl_dbg(ATH6KL_DBG_IRQ, + "Error Int status: 0x%x\n", + irq_proc_reg->error_int_status); + ath6kl_dbg(ATH6KL_DBG_IRQ, + "Counter Int status: 0x%x\n", + irq_proc_reg->counter_int_status); + ath6kl_dbg(ATH6KL_DBG_IRQ, + "Mbox Frame: 0x%x\n", + irq_proc_reg->mbox_frame); + ath6kl_dbg(ATH6KL_DBG_IRQ, + "Rx Lookahead Valid: 0x%x\n", + irq_proc_reg->rx_lkahd_valid); + ath6kl_dbg(ATH6KL_DBG_IRQ, + "Rx Lookahead 0: 0x%x\n", + irq_proc_reg->rx_lkahd[0]); + ath6kl_dbg(ATH6KL_DBG_IRQ, + "Rx Lookahead 1: 0x%x\n", + irq_proc_reg->rx_lkahd[1]); + + if (dev->ar->mbox_info.gmbox_addr != 0) { + /* + * If the target supports GMBOX hardware, dump some + * additional state. + */ + ath6kl_dbg(ATH6KL_DBG_IRQ, + "GMBOX Host Int status 2: 0x%x\n", + irq_proc_reg->host_int_status2); + ath6kl_dbg(ATH6KL_DBG_IRQ, + "GMBOX RX Avail: 0x%x\n", + irq_proc_reg->gmbox_rx_avail); + ath6kl_dbg(ATH6KL_DBG_IRQ, + "GMBOX lookahead alias 0: 0x%x\n", + irq_proc_reg->rx_gmbox_lkahd_alias[0]); + ath6kl_dbg(ATH6KL_DBG_IRQ, + "GMBOX lookahead alias 1: 0x%x\n", + irq_proc_reg->rx_gmbox_lkahd_alias[1]); + } + + } + + if (irq_enable_reg != NULL) { + ath6kl_dbg(ATH6KL_DBG_IRQ, + "Int status Enable: 0x%x\n", + irq_enable_reg->int_status_en); + ath6kl_dbg(ATH6KL_DBG_IRQ, "Counter Int status Enable: 0x%x\n", + irq_enable_reg->cntr_int_status_en); + } + ath6kl_dbg(ATH6KL_DBG_IRQ, "<------------------------------->\n"); +} + +static void dump_cred_dist(struct htc_endpoint_credit_dist *ep_dist) +{ + ath6kl_dbg(ATH6KL_DBG_CREDIT, + "--- endpoint: %d svc_id: 0x%X ---\n", + ep_dist->endpoint, ep_dist->svc_id); + ath6kl_dbg(ATH6KL_DBG_CREDIT, " dist_flags : 0x%X\n", + ep_dist->dist_flags); + ath6kl_dbg(ATH6KL_DBG_CREDIT, " cred_norm : %d\n", + ep_dist->cred_norm); + ath6kl_dbg(ATH6KL_DBG_CREDIT, " cred_min : %d\n", + ep_dist->cred_min); + ath6kl_dbg(ATH6KL_DBG_CREDIT, " credits : %d\n", + ep_dist->credits); + ath6kl_dbg(ATH6KL_DBG_CREDIT, " cred_assngd : %d\n", + ep_dist->cred_assngd); + ath6kl_dbg(ATH6KL_DBG_CREDIT, " seek_cred : %d\n", + ep_dist->seek_cred); + ath6kl_dbg(ATH6KL_DBG_CREDIT, " cred_sz : %d\n", + ep_dist->cred_sz); + ath6kl_dbg(ATH6KL_DBG_CREDIT, " cred_per_msg : %d\n", + ep_dist->cred_per_msg); + ath6kl_dbg(ATH6KL_DBG_CREDIT, " cred_to_dist : %d\n", + ep_dist->cred_to_dist); + ath6kl_dbg(ATH6KL_DBG_CREDIT, " txq_depth : %d\n", + get_queue_depth(&ep_dist->htc_ep->txq)); + ath6kl_dbg(ATH6KL_DBG_CREDIT, + "----------------------------------\n"); +} + +/* FIXME: move to htc.c */ +void dump_cred_dist_stats(struct htc_target *target) +{ + struct htc_endpoint_credit_dist *ep_list; + + list_for_each_entry(ep_list, &target->cred_dist_list, list) + dump_cred_dist(ep_list); + + ath6kl_dbg(ATH6KL_DBG_CREDIT, + "credit distribution total %d free %d\n", + target->credit_info->total_avail_credits, + target->credit_info->cur_free_credits); +} + +void ath6kl_debug_war(struct ath6kl *ar, enum ath6kl_war war) +{ + switch (war) { + case ATH6KL_WAR_INVALID_RATE: + ar->debug.war_stats.invalid_rate++; + break; + } +} + +static ssize_t read_file_war_stats(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + char *buf; + unsigned int len = 0, buf_len = 1500; + ssize_t ret_cnt; + + buf = kzalloc(buf_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + len += scnprintf(buf + len, buf_len - len, "\n"); + len += scnprintf(buf + len, buf_len - len, "%25s\n", + "Workaround stats"); + len += scnprintf(buf + len, buf_len - len, "%25s\n\n", + "================="); + len += scnprintf(buf + len, buf_len - len, "%20s %10u\n", + "Invalid rates", ar->debug.war_stats.invalid_rate); + + if (WARN_ON(len > buf_len)) + len = buf_len; + + ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + kfree(buf); + return ret_cnt; +} + +static const struct file_operations fops_war_stats = { + .read = read_file_war_stats, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +void ath6kl_debug_fwlog_event(struct ath6kl *ar, const void *buf, size_t len) +{ + struct ath6kl_fwlog_slot *slot; + struct sk_buff *skb; + size_t slot_len; + + if (WARN_ON(len > ATH6KL_FWLOG_PAYLOAD_SIZE)) + return; + + slot_len = sizeof(*slot) + ATH6KL_FWLOG_PAYLOAD_SIZE; + + skb = alloc_skb(slot_len, GFP_KERNEL); + if (!skb) + return; + + slot = (struct ath6kl_fwlog_slot *) skb_put(skb, slot_len); + slot->timestamp = cpu_to_le32(jiffies); + slot->length = cpu_to_le32(len); + memcpy(slot->payload, buf, len); + + /* Need to pad each record to fixed length ATH6KL_FWLOG_PAYLOAD_SIZE */ + memset(slot->payload + len, 0, ATH6KL_FWLOG_PAYLOAD_SIZE - len); + + spin_lock(&ar->debug.fwlog_queue.lock); + + __skb_queue_tail(&ar->debug.fwlog_queue, skb); + complete(&ar->debug.fwlog_completion); + + /* drop oldest entries */ + while (skb_queue_len(&ar->debug.fwlog_queue) > + ATH6KL_FWLOG_MAX_ENTRIES) { + skb = __skb_dequeue(&ar->debug.fwlog_queue); + kfree_skb(skb); + } + + spin_unlock(&ar->debug.fwlog_queue.lock); + + return; +} + +static int ath6kl_fwlog_open(struct inode *inode, struct file *file) +{ + struct ath6kl *ar = inode->i_private; + + if (ar->debug.fwlog_open) + return -EBUSY; + + ar->debug.fwlog_open = true; + + file->private_data = inode->i_private; + return 0; +} + +static int ath6kl_fwlog_release(struct inode *inode, struct file *file) +{ + struct ath6kl *ar = inode->i_private; + + ar->debug.fwlog_open = false; + + return 0; +} + +static ssize_t ath6kl_fwlog_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + struct sk_buff *skb; + ssize_t ret_cnt; + size_t len = 0; + char *buf; + + buf = vmalloc(count); + if (!buf) + return -ENOMEM; + + /* read undelivered logs from firmware */ + ath6kl_read_fwlogs(ar); + + spin_lock(&ar->debug.fwlog_queue.lock); + + while ((skb = __skb_dequeue(&ar->debug.fwlog_queue))) { + if (skb->len > count - len) { + /* not enough space, put skb back and leave */ + __skb_queue_head(&ar->debug.fwlog_queue, skb); + break; + } + + + memcpy(buf + len, skb->data, skb->len); + len += skb->len; + + kfree_skb(skb); + } + + spin_unlock(&ar->debug.fwlog_queue.lock); + + /* FIXME: what to do if len == 0? */ + + ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + vfree(buf); + + return ret_cnt; +} + +static const struct file_operations fops_fwlog = { + .open = ath6kl_fwlog_open, + .release = ath6kl_fwlog_release, + .read = ath6kl_fwlog_read, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath6kl_fwlog_block_read(struct file *file, + char __user *user_buf, + size_t count, + loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + struct sk_buff *skb; + ssize_t ret_cnt; + size_t len = 0, not_copied; + char *buf; + int ret; + + buf = vmalloc(count); + if (!buf) + return -ENOMEM; + + spin_lock(&ar->debug.fwlog_queue.lock); + + if (skb_queue_len(&ar->debug.fwlog_queue) == 0) { + /* we must init under queue lock */ + init_completion(&ar->debug.fwlog_completion); + + spin_unlock(&ar->debug.fwlog_queue.lock); + + ret = wait_for_completion_interruptible( + &ar->debug.fwlog_completion); + if (ret == -ERESTARTSYS) { + vfree(buf); + return ret; + } + + spin_lock(&ar->debug.fwlog_queue.lock); + } + + while ((skb = __skb_dequeue(&ar->debug.fwlog_queue))) { + if (skb->len > count - len) { + /* not enough space, put skb back and leave */ + __skb_queue_head(&ar->debug.fwlog_queue, skb); + break; + } + + + memcpy(buf + len, skb->data, skb->len); + len += skb->len; + + kfree_skb(skb); + } + + spin_unlock(&ar->debug.fwlog_queue.lock); + + /* FIXME: what to do if len == 0? */ + + not_copied = copy_to_user(user_buf, buf, len); + if (not_copied != 0) { + ret_cnt = -EFAULT; + goto out; + } + + *ppos = *ppos + len; + + ret_cnt = len; + +out: + vfree(buf); + + return ret_cnt; +} + +static const struct file_operations fops_fwlog_block = { + .open = ath6kl_fwlog_open, + .release = ath6kl_fwlog_release, + .read = ath6kl_fwlog_block_read, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath6kl_fwlog_mask_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + char buf[16]; + int len; + + len = snprintf(buf, sizeof(buf), "0x%x\n", ar->debug.fwlog_mask); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t ath6kl_fwlog_mask_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + int ret; + + ret = kstrtou32_from_user(user_buf, count, 0, &ar->debug.fwlog_mask); + if (ret) + return ret; + + ret = ath6kl_wmi_config_debug_module_cmd(ar->wmi, + ATH6KL_FWLOG_VALID_MASK, + ar->debug.fwlog_mask); + if (ret) + return ret; + + return count; +} + +static const struct file_operations fops_fwlog_mask = { + .open = simple_open, + .read = ath6kl_fwlog_mask_read, + .write = ath6kl_fwlog_mask_write, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_tgt_stats(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + struct ath6kl_vif *vif; + struct target_stats *tgt_stats; + char *buf; + unsigned int len = 0, buf_len = 1500; + int i; + long left; + ssize_t ret_cnt; + + vif = ath6kl_vif_first(ar); + if (!vif) + return -EIO; + + tgt_stats = &vif->target_stats; + + buf = kzalloc(buf_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (down_interruptible(&ar->sem)) { + kfree(buf); + return -EBUSY; + } + + set_bit(STATS_UPDATE_PEND, &vif->flags); + + if (ath6kl_wmi_get_stats_cmd(ar->wmi, 0)) { + up(&ar->sem); + kfree(buf); + return -EIO; + } + + left = wait_event_interruptible_timeout(ar->event_wq, + !test_bit(STATS_UPDATE_PEND, + &vif->flags), WMI_TIMEOUT); + + up(&ar->sem); + + if (left <= 0) { + kfree(buf); + return -ETIMEDOUT; + } + + len += scnprintf(buf + len, buf_len - len, "\n"); + len += scnprintf(buf + len, buf_len - len, "%25s\n", + "Target Tx stats"); + len += scnprintf(buf + len, buf_len - len, "%25s\n\n", + "================="); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Ucast packets", tgt_stats->tx_ucast_pkt); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Bcast packets", tgt_stats->tx_bcast_pkt); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Ucast byte", tgt_stats->tx_ucast_byte); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Bcast byte", tgt_stats->tx_bcast_byte); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Rts success cnt", tgt_stats->tx_rts_success_cnt); + for (i = 0; i < 4; i++) + len += scnprintf(buf + len, buf_len - len, + "%18s %d %10llu\n", "PER on ac", + i, tgt_stats->tx_pkt_per_ac[i]); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Error", tgt_stats->tx_err); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Fail count", tgt_stats->tx_fail_cnt); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Retry count", tgt_stats->tx_retry_cnt); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Multi retry cnt", tgt_stats->tx_mult_retry_cnt); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Rts fail cnt", tgt_stats->tx_rts_fail_cnt); + len += scnprintf(buf + len, buf_len - len, "%25s %10llu\n\n", + "TKIP counter measure used", + tgt_stats->tkip_cnter_measures_invoked); + + len += scnprintf(buf + len, buf_len - len, "%25s\n", + "Target Rx stats"); + len += scnprintf(buf + len, buf_len - len, "%25s\n", + "================="); + + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Ucast packets", tgt_stats->rx_ucast_pkt); + len += scnprintf(buf + len, buf_len - len, "%20s %10d\n", + "Ucast Rate", tgt_stats->rx_ucast_rate); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Bcast packets", tgt_stats->rx_bcast_pkt); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Ucast byte", tgt_stats->rx_ucast_byte); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Bcast byte", tgt_stats->rx_bcast_byte); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Fragmented pkt", tgt_stats->rx_frgment_pkt); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Error", tgt_stats->rx_err); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "CRC Err", tgt_stats->rx_crc_err); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Key chache miss", tgt_stats->rx_key_cache_miss); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Decrypt Err", tgt_stats->rx_decrypt_err); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Duplicate frame", tgt_stats->rx_dupl_frame); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Tkip Mic failure", tgt_stats->tkip_local_mic_fail); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "TKIP format err", tgt_stats->tkip_fmt_err); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "CCMP format Err", tgt_stats->ccmp_fmt_err); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n\n", + "CCMP Replay Err", tgt_stats->ccmp_replays); + + len += scnprintf(buf + len, buf_len - len, "%25s\n", + "Misc Target stats"); + len += scnprintf(buf + len, buf_len - len, "%25s\n", + "================="); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Beacon Miss count", tgt_stats->cs_bmiss_cnt); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Num Connects", tgt_stats->cs_connect_cnt); + len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", + "Num disconnects", tgt_stats->cs_discon_cnt); + len += scnprintf(buf + len, buf_len - len, "%20s %10d\n", + "Beacon avg rssi", tgt_stats->cs_ave_beacon_rssi); + len += scnprintf(buf + len, buf_len - len, "%20s %10d\n", + "ARP pkt received", tgt_stats->arp_received); + len += scnprintf(buf + len, buf_len - len, "%20s %10d\n", + "ARP pkt matched", tgt_stats->arp_matched); + len += scnprintf(buf + len, buf_len - len, "%20s %10d\n", + "ARP pkt replied", tgt_stats->arp_replied); + + if (len > buf_len) + len = buf_len; + + ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + kfree(buf); + return ret_cnt; +} + +static const struct file_operations fops_tgt_stats = { + .read = read_file_tgt_stats, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +#define print_credit_info(fmt_str, ep_list_field) \ + (len += scnprintf(buf + len, buf_len - len, fmt_str, \ + ep_list->ep_list_field)) +#define CREDIT_INFO_DISPLAY_STRING_LEN 200 +#define CREDIT_INFO_LEN 128 + +static ssize_t read_file_credit_dist_stats(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + struct htc_target *target = ar->htc_target; + struct htc_endpoint_credit_dist *ep_list; + char *buf; + unsigned int buf_len, len = 0; + ssize_t ret_cnt; + + buf_len = CREDIT_INFO_DISPLAY_STRING_LEN + + get_queue_depth(&target->cred_dist_list) * CREDIT_INFO_LEN; + buf = kzalloc(buf_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + len += scnprintf(buf + len, buf_len - len, "%25s%5d\n", + "Total Avail Credits: ", + target->credit_info->total_avail_credits); + len += scnprintf(buf + len, buf_len - len, "%25s%5d\n", + "Free credits :", + target->credit_info->cur_free_credits); + + len += scnprintf(buf + len, buf_len - len, + " Epid Flags Cred_norm Cred_min Credits Cred_assngd" + " Seek_cred Cred_sz Cred_per_msg Cred_to_dist" + " qdepth\n"); + + list_for_each_entry(ep_list, &target->cred_dist_list, list) { + print_credit_info(" %2d", endpoint); + print_credit_info("%10x", dist_flags); + print_credit_info("%8d", cred_norm); + print_credit_info("%9d", cred_min); + print_credit_info("%9d", credits); + print_credit_info("%10d", cred_assngd); + print_credit_info("%13d", seek_cred); + print_credit_info("%12d", cred_sz); + print_credit_info("%9d", cred_per_msg); + print_credit_info("%14d", cred_to_dist); + len += scnprintf(buf + len, buf_len - len, "%12d\n", + get_queue_depth(&ep_list->htc_ep->txq)); + } + + if (len > buf_len) + len = buf_len; + + ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + return ret_cnt; +} + +static const struct file_operations fops_credit_dist_stats = { + .read = read_file_credit_dist_stats, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static unsigned int print_endpoint_stat(struct htc_target *target, char *buf, + unsigned int buf_len, unsigned int len, + int offset, const char *name) +{ + int i; + struct htc_endpoint_stats *ep_st; + u32 *counter; + + len += scnprintf(buf + len, buf_len - len, "%s:", name); + for (i = 0; i < ENDPOINT_MAX; i++) { + ep_st = &target->endpoint[i].ep_st; + counter = ((u32 *) ep_st) + (offset / 4); + len += scnprintf(buf + len, buf_len - len, " %u", *counter); + } + len += scnprintf(buf + len, buf_len - len, "\n"); + + return len; +} + +static ssize_t ath6kl_endpoint_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + struct htc_target *target = ar->htc_target; + char *buf; + unsigned int buf_len, len = 0; + ssize_t ret_cnt; + + buf_len = sizeof(struct htc_endpoint_stats) / sizeof(u32) * + (25 + ENDPOINT_MAX * 11); + buf = kmalloc(buf_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + +#define EPSTAT(name) \ + do { \ + len = print_endpoint_stat(target, buf, buf_len, len, \ + offsetof(struct htc_endpoint_stats, \ + name), \ + #name); \ + } while (0) + + EPSTAT(cred_low_indicate); + EPSTAT(tx_issued); + EPSTAT(tx_pkt_bundled); + EPSTAT(tx_bundles); + EPSTAT(tx_dropped); + EPSTAT(tx_cred_rpt); + EPSTAT(cred_rpt_from_rx); + EPSTAT(cred_rpt_from_other); + EPSTAT(cred_rpt_ep0); + EPSTAT(cred_from_rx); + EPSTAT(cred_from_other); + EPSTAT(cred_from_ep0); + EPSTAT(cred_cosumd); + EPSTAT(cred_retnd); + EPSTAT(rx_pkts); + EPSTAT(rx_lkahds); + EPSTAT(rx_bundl); + EPSTAT(rx_bundle_lkahd); + EPSTAT(rx_bundle_from_hdr); + EPSTAT(rx_alloc_thresh_hit); + EPSTAT(rxalloc_thresh_byte); +#undef EPSTAT + + if (len > buf_len) + len = buf_len; + + ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + return ret_cnt; +} + +static ssize_t ath6kl_endpoint_stats_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + struct htc_target *target = ar->htc_target; + int ret, i; + u32 val; + struct htc_endpoint_stats *ep_st; + + ret = kstrtou32_from_user(user_buf, count, 0, &val); + if (ret) + return ret; + if (val == 0) { + for (i = 0; i < ENDPOINT_MAX; i++) { + ep_st = &target->endpoint[i].ep_st; + memset(ep_st, 0, sizeof(*ep_st)); + } + } + + return count; +} + +static const struct file_operations fops_endpoint_stats = { + .open = simple_open, + .read = ath6kl_endpoint_stats_read, + .write = ath6kl_endpoint_stats_write, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static unsigned long ath6kl_get_num_reg(void) +{ + int i; + unsigned long n_reg = 0; + + for (i = 0; i < ARRAY_SIZE(diag_reg); i++) + n_reg = n_reg + + (diag_reg[i].reg_end - diag_reg[i].reg_start) / 4 + 1; + + return n_reg; +} + +static bool ath6kl_dbg_is_diag_reg_valid(u32 reg_addr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(diag_reg); i++) { + if (reg_addr >= diag_reg[i].reg_start && + reg_addr <= diag_reg[i].reg_end) + return true; + } + + return false; +} + +static ssize_t ath6kl_regread_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + u8 buf[50]; + unsigned int len = 0; + + if (ar->debug.dbgfs_diag_reg) + len += scnprintf(buf + len, sizeof(buf) - len, "0x%x\n", + ar->debug.dbgfs_diag_reg); + else + len += scnprintf(buf + len, sizeof(buf) - len, + "All diag registers\n"); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t ath6kl_regread_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + unsigned long reg_addr; + + if (kstrtoul_from_user(user_buf, count, 0, ®_addr)) + return -EINVAL; + + if ((reg_addr % 4) != 0) + return -EINVAL; + + if (reg_addr && !ath6kl_dbg_is_diag_reg_valid(reg_addr)) + return -EINVAL; + + ar->debug.dbgfs_diag_reg = reg_addr; + + return count; +} + +static const struct file_operations fops_diag_reg_read = { + .read = ath6kl_regread_read, + .write = ath6kl_regread_write, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static int ath6kl_regdump_open(struct inode *inode, struct file *file) +{ + struct ath6kl *ar = inode->i_private; + u8 *buf; + unsigned long int reg_len; + unsigned int len = 0, n_reg; + u32 addr; + __le32 reg_val; + int i, status; + + /* Dump all the registers if no register is specified */ + if (!ar->debug.dbgfs_diag_reg) + n_reg = ath6kl_get_num_reg(); + else + n_reg = 1; + + reg_len = n_reg * REG_OUTPUT_LEN_PER_LINE; + if (n_reg > 1) + reg_len += REGTYPE_STR_LEN; + + buf = vmalloc(reg_len); + if (!buf) + return -ENOMEM; + + if (n_reg == 1) { + addr = ar->debug.dbgfs_diag_reg; + + status = ath6kl_diag_read32(ar, + TARG_VTOP(ar->target_type, addr), + (u32 *)®_val); + if (status) + goto fail_reg_read; + + len += scnprintf(buf + len, reg_len - len, + "0x%06x 0x%08x\n", addr, le32_to_cpu(reg_val)); + goto done; + } + + for (i = 0; i < ARRAY_SIZE(diag_reg); i++) { + len += scnprintf(buf + len, reg_len - len, + "%s\n", diag_reg[i].reg_info); + for (addr = diag_reg[i].reg_start; + addr <= diag_reg[i].reg_end; addr += 4) { + status = ath6kl_diag_read32(ar, + TARG_VTOP(ar->target_type, addr), + (u32 *)®_val); + if (status) + goto fail_reg_read; + + len += scnprintf(buf + len, reg_len - len, + "0x%06x 0x%08x\n", + addr, le32_to_cpu(reg_val)); + } + } + +done: + file->private_data = buf; + return 0; + +fail_reg_read: + ath6kl_warn("Unable to read memory:%u\n", addr); + vfree(buf); + return -EIO; +} + +static ssize_t ath6kl_regdump_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + u8 *buf = file->private_data; + return simple_read_from_buffer(user_buf, count, ppos, buf, strlen(buf)); +} + +static int ath6kl_regdump_release(struct inode *inode, struct file *file) +{ + vfree(file->private_data); + return 0; +} + +static const struct file_operations fops_reg_dump = { + .open = ath6kl_regdump_open, + .read = ath6kl_regdump_read, + .release = ath6kl_regdump_release, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath6kl_lrssi_roam_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + unsigned long lrssi_roam_threshold; + + if (kstrtoul_from_user(user_buf, count, 0, &lrssi_roam_threshold)) + return -EINVAL; + + ar->lrssi_roam_threshold = lrssi_roam_threshold; + + ath6kl_wmi_set_roam_lrssi_cmd(ar->wmi, ar->lrssi_roam_threshold); + + return count; +} + +static ssize_t ath6kl_lrssi_roam_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + char buf[32]; + unsigned int len; + + len = snprintf(buf, sizeof(buf), "%u\n", ar->lrssi_roam_threshold); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_lrssi_roam_threshold = { + .read = ath6kl_lrssi_roam_read, + .write = ath6kl_lrssi_roam_write, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath6kl_regwrite_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + u8 buf[32]; + unsigned int len = 0; + + len = scnprintf(buf, sizeof(buf), "Addr: 0x%x Val: 0x%x\n", + ar->debug.diag_reg_addr_wr, ar->debug.diag_reg_val_wr); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t ath6kl_regwrite_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + char buf[32]; + char *sptr, *token; + unsigned int len = 0; + u32 reg_addr, reg_val; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + sptr = buf; + + token = strsep(&sptr, "="); + if (!token) + return -EINVAL; + + if (kstrtou32(token, 0, ®_addr)) + return -EINVAL; + + if (!ath6kl_dbg_is_diag_reg_valid(reg_addr)) + return -EINVAL; + + if (kstrtou32(sptr, 0, ®_val)) + return -EINVAL; + + ar->debug.diag_reg_addr_wr = reg_addr; + ar->debug.diag_reg_val_wr = reg_val; + + if (ath6kl_diag_write32(ar, ar->debug.diag_reg_addr_wr, + cpu_to_le32(ar->debug.diag_reg_val_wr))) + return -EIO; + + return count; +} + +static const struct file_operations fops_diag_reg_write = { + .read = ath6kl_regwrite_read, + .write = ath6kl_regwrite_write, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +int ath6kl_debug_roam_tbl_event(struct ath6kl *ar, const void *buf, + size_t len) +{ + const struct wmi_target_roam_tbl *tbl; + u16 num_entries; + + if (len < sizeof(*tbl)) + return -EINVAL; + + tbl = (const struct wmi_target_roam_tbl *) buf; + num_entries = le16_to_cpu(tbl->num_entries); + if (sizeof(*tbl) + num_entries * sizeof(struct wmi_bss_roam_info) > + len) + return -EINVAL; + + if (ar->debug.roam_tbl == NULL || + ar->debug.roam_tbl_len < (unsigned int) len) { + kfree(ar->debug.roam_tbl); + ar->debug.roam_tbl = kmalloc(len, GFP_ATOMIC); + if (ar->debug.roam_tbl == NULL) + return -ENOMEM; + } + + memcpy(ar->debug.roam_tbl, buf, len); + ar->debug.roam_tbl_len = len; + + if (test_bit(ROAM_TBL_PEND, &ar->flag)) { + clear_bit(ROAM_TBL_PEND, &ar->flag); + wake_up(&ar->event_wq); + } + + return 0; +} + +static ssize_t ath6kl_roam_table_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + int ret; + long left; + struct wmi_target_roam_tbl *tbl; + u16 num_entries, i; + char *buf; + unsigned int len, buf_len; + ssize_t ret_cnt; + + if (down_interruptible(&ar->sem)) + return -EBUSY; + + set_bit(ROAM_TBL_PEND, &ar->flag); + + ret = ath6kl_wmi_get_roam_tbl_cmd(ar->wmi); + if (ret) { + up(&ar->sem); + return ret; + } + + left = wait_event_interruptible_timeout( + ar->event_wq, !test_bit(ROAM_TBL_PEND, &ar->flag), WMI_TIMEOUT); + up(&ar->sem); + + if (left <= 0) + return -ETIMEDOUT; + + if (ar->debug.roam_tbl == NULL) + return -ENOMEM; + + tbl = (struct wmi_target_roam_tbl *) ar->debug.roam_tbl; + num_entries = le16_to_cpu(tbl->num_entries); + + buf_len = 100 + num_entries * 100; + buf = kzalloc(buf_len, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + len = 0; + len += scnprintf(buf + len, buf_len - len, + "roam_mode=%u\n\n" + "# roam_util bssid rssi rssidt last_rssi util bias\n", + le16_to_cpu(tbl->roam_mode)); + + for (i = 0; i < num_entries; i++) { + struct wmi_bss_roam_info *info = &tbl->info[i]; + len += scnprintf(buf + len, buf_len - len, + "%d %pM %d %d %d %d %d\n", + a_sle32_to_cpu(info->roam_util), info->bssid, + info->rssi, info->rssidt, info->last_rssi, + info->util, info->bias); + } + + if (len > buf_len) + len = buf_len; + + ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + kfree(buf); + return ret_cnt; +} + +static const struct file_operations fops_roam_table = { + .read = ath6kl_roam_table_read, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath6kl_force_roam_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + int ret; + char buf[20]; + size_t len; + u8 bssid[ETH_ALEN]; + int i; + int addr[ETH_ALEN]; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + buf[len] = '\0'; + + if (sscanf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", + &addr[0], &addr[1], &addr[2], &addr[3], &addr[4], &addr[5]) + != ETH_ALEN) + return -EINVAL; + for (i = 0; i < ETH_ALEN; i++) + bssid[i] = addr[i]; + + ret = ath6kl_wmi_force_roam_cmd(ar->wmi, bssid); + if (ret) + return ret; + + return count; +} + +static const struct file_operations fops_force_roam = { + .write = ath6kl_force_roam_write, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath6kl_roam_mode_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + int ret; + char buf[20]; + size_t len; + enum wmi_roam_mode mode; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + buf[len] = '\0'; + if (len > 0 && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + if (strcasecmp(buf, "default") == 0) + mode = WMI_DEFAULT_ROAM_MODE; + else if (strcasecmp(buf, "bssbias") == 0) + mode = WMI_HOST_BIAS_ROAM_MODE; + else if (strcasecmp(buf, "lock") == 0) + mode = WMI_LOCK_BSS_MODE; + else + return -EINVAL; + + ret = ath6kl_wmi_set_roam_mode_cmd(ar->wmi, mode); + if (ret) + return ret; + + return count; +} + +static const struct file_operations fops_roam_mode = { + .write = ath6kl_roam_mode_write, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +void ath6kl_debug_set_keepalive(struct ath6kl *ar, u8 keepalive) +{ + ar->debug.keepalive = keepalive; +} + +static ssize_t ath6kl_keepalive_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + char buf[16]; + int len; + + len = snprintf(buf, sizeof(buf), "%u\n", ar->debug.keepalive); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t ath6kl_keepalive_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + int ret; + u8 val; + + ret = kstrtou8_from_user(user_buf, count, 0, &val); + if (ret) + return ret; + + ret = ath6kl_wmi_set_keepalive_cmd(ar->wmi, 0, val); + if (ret) + return ret; + + return count; +} + +static const struct file_operations fops_keepalive = { + .open = simple_open, + .read = ath6kl_keepalive_read, + .write = ath6kl_keepalive_write, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +void ath6kl_debug_set_disconnect_timeout(struct ath6kl *ar, u8 timeout) +{ + ar->debug.disc_timeout = timeout; +} + +static ssize_t ath6kl_disconnect_timeout_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + char buf[16]; + int len; + + len = snprintf(buf, sizeof(buf), "%u\n", ar->debug.disc_timeout); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t ath6kl_disconnect_timeout_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + int ret; + u8 val; + + ret = kstrtou8_from_user(user_buf, count, 0, &val); + if (ret) + return ret; + + ret = ath6kl_wmi_disctimeout_cmd(ar->wmi, 0, val); + if (ret) + return ret; + + return count; +} + +static const struct file_operations fops_disconnect_timeout = { + .open = simple_open, + .read = ath6kl_disconnect_timeout_read, + .write = ath6kl_disconnect_timeout_write, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath6kl_create_qos_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + + struct ath6kl *ar = file->private_data; + struct ath6kl_vif *vif; + char buf[200]; + ssize_t len; + char *sptr, *token; + struct wmi_create_pstream_cmd pstream; + u32 val32; + u16 val16; + + vif = ath6kl_vif_first(ar); + if (!vif) + return -EIO; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + buf[len] = '\0'; + sptr = buf; + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou8(token, 0, &pstream.user_pri)) + return -EINVAL; + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou8(token, 0, &pstream.traffic_direc)) + return -EINVAL; + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou8(token, 0, &pstream.traffic_class)) + return -EINVAL; + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou8(token, 0, &pstream.traffic_type)) + return -EINVAL; + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou8(token, 0, &pstream.voice_psc_cap)) + return -EINVAL; + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou32(token, 0, &val32)) + return -EINVAL; + pstream.min_service_int = cpu_to_le32(val32); + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou32(token, 0, &val32)) + return -EINVAL; + pstream.max_service_int = cpu_to_le32(val32); + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou32(token, 0, &val32)) + return -EINVAL; + pstream.inactivity_int = cpu_to_le32(val32); + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou32(token, 0, &val32)) + return -EINVAL; + pstream.suspension_int = cpu_to_le32(val32); + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou32(token, 0, &val32)) + return -EINVAL; + pstream.service_start_time = cpu_to_le32(val32); + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou8(token, 0, &pstream.tsid)) + return -EINVAL; + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou16(token, 0, &val16)) + return -EINVAL; + pstream.nominal_msdu = cpu_to_le16(val16); + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou16(token, 0, &val16)) + return -EINVAL; + pstream.max_msdu = cpu_to_le16(val16); + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou32(token, 0, &val32)) + return -EINVAL; + pstream.min_data_rate = cpu_to_le32(val32); + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou32(token, 0, &val32)) + return -EINVAL; + pstream.mean_data_rate = cpu_to_le32(val32); + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou32(token, 0, &val32)) + return -EINVAL; + pstream.peak_data_rate = cpu_to_le32(val32); + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou32(token, 0, &val32)) + return -EINVAL; + pstream.max_burst_size = cpu_to_le32(val32); + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou32(token, 0, &val32)) + return -EINVAL; + pstream.delay_bound = cpu_to_le32(val32); + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou32(token, 0, &val32)) + return -EINVAL; + pstream.min_phy_rate = cpu_to_le32(val32); + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou32(token, 0, &val32)) + return -EINVAL; + pstream.sba = cpu_to_le32(val32); + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou32(token, 0, &val32)) + return -EINVAL; + pstream.medium_time = cpu_to_le32(val32); + + pstream.nominal_phy = le32_to_cpu(pstream.min_phy_rate) / 1000000; + + ath6kl_wmi_create_pstream_cmd(ar->wmi, vif->fw_vif_idx, &pstream); + + return count; +} + +static const struct file_operations fops_create_qos = { + .write = ath6kl_create_qos_write, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath6kl_delete_qos_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + + struct ath6kl *ar = file->private_data; + struct ath6kl_vif *vif; + char buf[100]; + ssize_t len; + char *sptr, *token; + u8 traffic_class; + u8 tsid; + + vif = ath6kl_vif_first(ar); + if (!vif) + return -EIO; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + buf[len] = '\0'; + sptr = buf; + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou8(token, 0, &traffic_class)) + return -EINVAL; + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou8(token, 0, &tsid)) + return -EINVAL; + + ath6kl_wmi_delete_pstream_cmd(ar->wmi, vif->fw_vif_idx, + traffic_class, tsid); + + return count; +} + +static const struct file_operations fops_delete_qos = { + .write = ath6kl_delete_qos_write, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath6kl_bgscan_int_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + struct ath6kl_vif *vif; + u16 bgscan_int; + char buf[32]; + ssize_t len; + + vif = ath6kl_vif_first(ar); + if (!vif) + return -EIO; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (kstrtou16(buf, 0, &bgscan_int)) + return -EINVAL; + + if (bgscan_int == 0) + bgscan_int = 0xffff; + + vif->bg_scan_period = bgscan_int; + + ath6kl_wmi_scanparams_cmd(ar->wmi, 0, 0, 0, bgscan_int, 0, 0, 0, 3, + 0, 0, 0); + + return count; +} + +static const struct file_operations fops_bgscan_int = { + .write = ath6kl_bgscan_int_write, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath6kl_listen_int_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + struct ath6kl_vif *vif; + u16 listen_interval; + char buf[32]; + ssize_t len; + + vif = ath6kl_vif_first(ar); + if (!vif) + return -EIO; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (kstrtou16(buf, 0, &listen_interval)) + return -EINVAL; + + if ((listen_interval < 15) || (listen_interval > 3000)) + return -EINVAL; + + vif->listen_intvl_t = listen_interval; + ath6kl_wmi_listeninterval_cmd(ar->wmi, vif->fw_vif_idx, + vif->listen_intvl_t, 0); + + return count; +} + +static ssize_t ath6kl_listen_int_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + struct ath6kl_vif *vif; + char buf[32]; + int len; + + vif = ath6kl_vif_first(ar); + if (!vif) + return -EIO; + + len = scnprintf(buf, sizeof(buf), "%u\n", vif->listen_intvl_t); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_listen_int = { + .read = ath6kl_listen_int_read, + .write = ath6kl_listen_int_write, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t ath6kl_power_params_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath6kl *ar = file->private_data; + u8 buf[100]; + unsigned int len = 0; + char *sptr, *token; + u16 idle_period, ps_poll_num, dtim, + tx_wakeup, num_tx; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + buf[len] = '\0'; + sptr = buf; + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou16(token, 0, &idle_period)) + return -EINVAL; + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou16(token, 0, &ps_poll_num)) + return -EINVAL; + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou16(token, 0, &dtim)) + return -EINVAL; + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou16(token, 0, &tx_wakeup)) + return -EINVAL; + + token = strsep(&sptr, " "); + if (!token) + return -EINVAL; + if (kstrtou16(token, 0, &num_tx)) + return -EINVAL; + + ath6kl_wmi_pmparams_cmd(ar->wmi, 0, idle_period, ps_poll_num, + dtim, tx_wakeup, num_tx, 0); + + return count; +} + +static const struct file_operations fops_power_params = { + .write = ath6kl_power_params_write, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +void ath6kl_debug_init(struct ath6kl *ar) +{ + skb_queue_head_init(&ar->debug.fwlog_queue); + init_completion(&ar->debug.fwlog_completion); + + /* + * Actually we are lying here but don't know how to read the mask + * value from the firmware. + */ + ar->debug.fwlog_mask = 0; +} + +/* + * Initialisation needs to happen in two stages as fwlog events can come + * before cfg80211 is initialised, and debugfs depends on cfg80211 + * initialisation. + */ +int ath6kl_debug_init_fs(struct ath6kl *ar) +{ + ar->debugfs_phy = debugfs_create_dir("ath6kl", + ar->wiphy->debugfsdir); + if (!ar->debugfs_phy) + return -ENOMEM; + + debugfs_create_file("tgt_stats", S_IRUSR, ar->debugfs_phy, ar, + &fops_tgt_stats); + + if (ar->hif_type == ATH6KL_HIF_TYPE_SDIO) + debugfs_create_file("credit_dist_stats", S_IRUSR, + ar->debugfs_phy, ar, + &fops_credit_dist_stats); + + debugfs_create_file("endpoint_stats", S_IRUSR | S_IWUSR, + ar->debugfs_phy, ar, &fops_endpoint_stats); + + debugfs_create_file("fwlog", S_IRUSR, ar->debugfs_phy, ar, + &fops_fwlog); + + debugfs_create_file("fwlog_block", S_IRUSR, ar->debugfs_phy, ar, + &fops_fwlog_block); + + debugfs_create_file("fwlog_mask", S_IRUSR | S_IWUSR, ar->debugfs_phy, + ar, &fops_fwlog_mask); + + debugfs_create_file("reg_addr", S_IRUSR | S_IWUSR, ar->debugfs_phy, ar, + &fops_diag_reg_read); + + debugfs_create_file("reg_dump", S_IRUSR, ar->debugfs_phy, ar, + &fops_reg_dump); + + debugfs_create_file("lrssi_roam_threshold", S_IRUSR | S_IWUSR, + ar->debugfs_phy, ar, &fops_lrssi_roam_threshold); + + debugfs_create_file("reg_write", S_IRUSR | S_IWUSR, + ar->debugfs_phy, ar, &fops_diag_reg_write); + + debugfs_create_file("war_stats", S_IRUSR, ar->debugfs_phy, ar, + &fops_war_stats); + + debugfs_create_file("roam_table", S_IRUSR, ar->debugfs_phy, ar, + &fops_roam_table); + + debugfs_create_file("force_roam", S_IWUSR, ar->debugfs_phy, ar, + &fops_force_roam); + + debugfs_create_file("roam_mode", S_IWUSR, ar->debugfs_phy, ar, + &fops_roam_mode); + + debugfs_create_file("keepalive", S_IRUSR | S_IWUSR, ar->debugfs_phy, ar, + &fops_keepalive); + + debugfs_create_file("disconnect_timeout", S_IRUSR | S_IWUSR, + ar->debugfs_phy, ar, &fops_disconnect_timeout); + + debugfs_create_file("create_qos", S_IWUSR, ar->debugfs_phy, ar, + &fops_create_qos); + + debugfs_create_file("delete_qos", S_IWUSR, ar->debugfs_phy, ar, + &fops_delete_qos); + + debugfs_create_file("bgscan_interval", S_IWUSR, + ar->debugfs_phy, ar, &fops_bgscan_int); + + debugfs_create_file("listen_interval", S_IRUSR | S_IWUSR, + ar->debugfs_phy, ar, &fops_listen_int); + + debugfs_create_file("power_params", S_IWUSR, ar->debugfs_phy, ar, + &fops_power_params); + + return 0; +} + +void ath6kl_debug_cleanup(struct ath6kl *ar) +{ + skb_queue_purge(&ar->debug.fwlog_queue); + complete(&ar->debug.fwlog_completion); + kfree(ar->debug.roam_tbl); +} + +#endif diff --git a/drivers/net/wireless/ath/ath6kl/debug.h b/drivers/net/wireless/ath/ath6kl/debug.h new file mode 100644 index 0000000..92d0614 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/debug.h @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DEBUG_H +#define DEBUG_H + +#include "hif.h" +#include "trace.h" + +enum ATH6K_DEBUG_MASK { + ATH6KL_DBG_CREDIT = BIT(0), + /* hole */ + ATH6KL_DBG_WLAN_TX = BIT(2), /* wlan tx */ + ATH6KL_DBG_WLAN_RX = BIT(3), /* wlan rx */ + ATH6KL_DBG_BMI = BIT(4), /* bmi tracing */ + ATH6KL_DBG_HTC = BIT(5), + ATH6KL_DBG_HIF = BIT(6), + ATH6KL_DBG_IRQ = BIT(7), /* interrupt processing */ + /* hole */ + /* hole */ + ATH6KL_DBG_WMI = BIT(10), /* wmi tracing */ + ATH6KL_DBG_TRC = BIT(11), /* generic func tracing */ + ATH6KL_DBG_SCATTER = BIT(12), /* hif scatter tracing */ + ATH6KL_DBG_WLAN_CFG = BIT(13), /* cfg80211 i/f file tracing */ + ATH6KL_DBG_RAW_BYTES = BIT(14), /* dump tx/rx frames */ + ATH6KL_DBG_AGGR = BIT(15), /* aggregation */ + ATH6KL_DBG_SDIO = BIT(16), + ATH6KL_DBG_SDIO_DUMP = BIT(17), + ATH6KL_DBG_BOOT = BIT(18), /* driver init and fw boot */ + ATH6KL_DBG_WMI_DUMP = BIT(19), + ATH6KL_DBG_SUSPEND = BIT(20), + ATH6KL_DBG_USB = BIT(21), + ATH6KL_DBG_USB_BULK = BIT(22), + ATH6KL_DBG_RECOVERY = BIT(23), + ATH6KL_DBG_ANY = 0xffffffff /* enable all logs */ +}; + +extern unsigned int debug_mask; +extern __printf(2, 3) +int ath6kl_printk(const char *level, const char *fmt, ...); +extern __printf(1, 2) int ath6kl_info(const char *fmt, ...); +extern __printf(1, 2) int ath6kl_err(const char *fmt, ...); +extern __printf(1, 2) int ath6kl_warn(const char *fmt, ...); + +enum ath6kl_war { + ATH6KL_WAR_INVALID_RATE, +}; + +#ifdef CPTCFG_ATH6KL_DEBUG + +void ath6kl_dbg(enum ATH6K_DEBUG_MASK mask, const char *fmt, ...); +void ath6kl_dbg_dump(enum ATH6K_DEBUG_MASK mask, + const char *msg, const char *prefix, + const void *buf, size_t len); + +void ath6kl_dump_registers(struct ath6kl_device *dev, + struct ath6kl_irq_proc_registers *irq_proc_reg, + struct ath6kl_irq_enable_reg *irq_en_reg); +void dump_cred_dist_stats(struct htc_target *target); +void ath6kl_debug_fwlog_event(struct ath6kl *ar, const void *buf, size_t len); +void ath6kl_debug_war(struct ath6kl *ar, enum ath6kl_war war); +int ath6kl_debug_roam_tbl_event(struct ath6kl *ar, const void *buf, + size_t len); +void ath6kl_debug_set_keepalive(struct ath6kl *ar, u8 keepalive); +void ath6kl_debug_set_disconnect_timeout(struct ath6kl *ar, u8 timeout); +void ath6kl_debug_init(struct ath6kl *ar); +int ath6kl_debug_init_fs(struct ath6kl *ar); +void ath6kl_debug_cleanup(struct ath6kl *ar); + +#else +static inline int ath6kl_dbg(enum ATH6K_DEBUG_MASK dbg_mask, + const char *fmt, ...) +{ + return 0; +} + +static inline void ath6kl_dbg_dump(enum ATH6K_DEBUG_MASK mask, + const char *msg, const char *prefix, + const void *buf, size_t len) +{ +} + +static inline void ath6kl_dump_registers(struct ath6kl_device *dev, + struct ath6kl_irq_proc_registers *irq_proc_reg, + struct ath6kl_irq_enable_reg *irq_en_reg) +{ + +} +static inline void dump_cred_dist_stats(struct htc_target *target) +{ +} + +static inline void ath6kl_debug_fwlog_event(struct ath6kl *ar, + const void *buf, size_t len) +{ +} + +static inline void ath6kl_debug_war(struct ath6kl *ar, enum ath6kl_war war) +{ +} + +static inline int ath6kl_debug_roam_tbl_event(struct ath6kl *ar, + const void *buf, size_t len) +{ + return 0; +} + +static inline void ath6kl_debug_set_keepalive(struct ath6kl *ar, u8 keepalive) +{ +} + +static inline void ath6kl_debug_set_disconnect_timeout(struct ath6kl *ar, + u8 timeout) +{ +} + +static inline void ath6kl_debug_init(struct ath6kl *ar) +{ +} + +static inline int ath6kl_debug_init_fs(struct ath6kl *ar) +{ + return 0; +} + +static inline void ath6kl_debug_cleanup(struct ath6kl *ar) +{ +} + +#endif +#endif diff --git a/drivers/net/wireless/ath/ath6kl/hif-ops.h b/drivers/net/wireless/ath/ath6kl/hif-ops.h new file mode 100644 index 0000000..8c9e72d --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/hif-ops.h @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef HIF_OPS_H +#define HIF_OPS_H + +#include "hif.h" +#include "debug.h" + +static inline int hif_read_write_sync(struct ath6kl *ar, u32 addr, u8 *buf, + u32 len, u32 request) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, + "hif %s sync addr 0x%x buf 0x%p len %d request 0x%x\n", + (request & HIF_WRITE) ? "write" : "read", + addr, buf, len, request); + + return ar->hif_ops->read_write_sync(ar, addr, buf, len, request); +} + +static inline int hif_write_async(struct ath6kl *ar, u32 address, u8 *buffer, + u32 length, u32 request, + struct htc_packet *packet) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, + "hif write async addr 0x%x buf 0x%p len %d request 0x%x\n", + address, buffer, length, request); + + return ar->hif_ops->write_async(ar, address, buffer, length, + request, packet); +} +static inline void ath6kl_hif_irq_enable(struct ath6kl *ar) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, "hif irq enable\n"); + + return ar->hif_ops->irq_enable(ar); +} + +static inline void ath6kl_hif_irq_disable(struct ath6kl *ar) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, "hif irq disable\n"); + + return ar->hif_ops->irq_disable(ar); +} + +static inline struct hif_scatter_req *hif_scatter_req_get(struct ath6kl *ar) +{ + return ar->hif_ops->scatter_req_get(ar); +} + +static inline void hif_scatter_req_add(struct ath6kl *ar, + struct hif_scatter_req *s_req) +{ + return ar->hif_ops->scatter_req_add(ar, s_req); +} + +static inline int ath6kl_hif_enable_scatter(struct ath6kl *ar) +{ + return ar->hif_ops->enable_scatter(ar); +} + +static inline int ath6kl_hif_scat_req_rw(struct ath6kl *ar, + struct hif_scatter_req *scat_req) +{ + return ar->hif_ops->scat_req_rw(ar, scat_req); +} + +static inline void ath6kl_hif_cleanup_scatter(struct ath6kl *ar) +{ + return ar->hif_ops->cleanup_scatter(ar); +} + +static inline int ath6kl_hif_suspend(struct ath6kl *ar, + struct cfg80211_wowlan *wow) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, "hif suspend\n"); + + return ar->hif_ops->suspend(ar, wow); +} + +/* + * Read from the ATH6KL through its diagnostic window. No cooperation from + * the Target is required for this. + */ +static inline int ath6kl_hif_diag_read32(struct ath6kl *ar, u32 address, + u32 *value) +{ + return ar->hif_ops->diag_read32(ar, address, value); +} + +/* + * Write to the ATH6KL through its diagnostic window. No cooperation from + * the Target is required for this. + */ +static inline int ath6kl_hif_diag_write32(struct ath6kl *ar, u32 address, + __le32 value) +{ + return ar->hif_ops->diag_write32(ar, address, value); +} + +static inline int ath6kl_hif_bmi_read(struct ath6kl *ar, u8 *buf, u32 len) +{ + return ar->hif_ops->bmi_read(ar, buf, len); +} + +static inline int ath6kl_hif_bmi_write(struct ath6kl *ar, u8 *buf, u32 len) +{ + return ar->hif_ops->bmi_write(ar, buf, len); +} + +static inline int ath6kl_hif_resume(struct ath6kl *ar) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, "hif resume\n"); + + return ar->hif_ops->resume(ar); +} + +static inline int ath6kl_hif_power_on(struct ath6kl *ar) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, "hif power on\n"); + + return ar->hif_ops->power_on(ar); +} + +static inline int ath6kl_hif_power_off(struct ath6kl *ar) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, "hif power off\n"); + + return ar->hif_ops->power_off(ar); +} + +static inline void ath6kl_hif_stop(struct ath6kl *ar) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, "hif stop\n"); + + ar->hif_ops->stop(ar); +} + +static inline int ath6kl_hif_pipe_send(struct ath6kl *ar, + u8 pipe, struct sk_buff *hdr_buf, + struct sk_buff *buf) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, "hif pipe send\n"); + + return ar->hif_ops->pipe_send(ar, pipe, hdr_buf, buf); +} + +static inline void ath6kl_hif_pipe_get_default(struct ath6kl *ar, + u8 *ul_pipe, u8 *dl_pipe) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, "hif pipe get default\n"); + + ar->hif_ops->pipe_get_default(ar, ul_pipe, dl_pipe); +} + +static inline int ath6kl_hif_pipe_map_service(struct ath6kl *ar, + u16 service_id, u8 *ul_pipe, + u8 *dl_pipe) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, "hif pipe get default\n"); + + return ar->hif_ops->pipe_map_service(ar, service_id, ul_pipe, dl_pipe); +} + +static inline u16 ath6kl_hif_pipe_get_free_queue_number(struct ath6kl *ar, + u8 pipe) +{ + ath6kl_dbg(ATH6KL_DBG_HIF, "hif pipe get free queue number\n"); + + return ar->hif_ops->pipe_get_free_queue_number(ar, pipe); +} + +#endif diff --git a/drivers/net/wireless/ath/ath6kl/hif.c b/drivers/net/wireless/ath/ath6kl/hif.c new file mode 100644 index 0000000..fea7709 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/hif.c @@ -0,0 +1,705 @@ +/* + * Copyright (c) 2007-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include "hif.h" + +#include + +#include "core.h" +#include "target.h" +#include "hif-ops.h" +#include "debug.h" +#include "trace.h" + +#define MAILBOX_FOR_BLOCK_SIZE 1 + +#define ATH6KL_TIME_QUANTUM 10 /* in ms */ + +static int ath6kl_hif_cp_scat_dma_buf(struct hif_scatter_req *req, + bool from_dma) +{ + u8 *buf; + int i; + + buf = req->virt_dma_buf; + + for (i = 0; i < req->scat_entries; i++) { + + if (from_dma) + memcpy(req->scat_list[i].buf, buf, + req->scat_list[i].len); + else + memcpy(buf, req->scat_list[i].buf, + req->scat_list[i].len); + + buf += req->scat_list[i].len; + } + + return 0; +} + +int ath6kl_hif_rw_comp_handler(void *context, int status) +{ + struct htc_packet *packet = context; + + ath6kl_dbg(ATH6KL_DBG_HIF, "hif rw completion pkt 0x%p status %d\n", + packet, status); + + packet->status = status; + packet->completion(packet->context, packet); + + return 0; +} +EXPORT_SYMBOL(ath6kl_hif_rw_comp_handler); + +#define REG_DUMP_COUNT_AR6003 60 +#define REGISTER_DUMP_LEN_MAX 60 + +static void ath6kl_hif_dump_fw_crash(struct ath6kl *ar) +{ + __le32 regdump_val[REGISTER_DUMP_LEN_MAX]; + u32 i, address, regdump_addr = 0; + int ret; + + if (ar->target_type != TARGET_TYPE_AR6003) + return; + + /* the reg dump pointer is copied to the host interest area */ + address = ath6kl_get_hi_item_addr(ar, HI_ITEM(hi_failure_state)); + address = TARG_VTOP(ar->target_type, address); + + /* read RAM location through diagnostic window */ + ret = ath6kl_diag_read32(ar, address, ®dump_addr); + + if (ret || !regdump_addr) { + ath6kl_warn("failed to get ptr to register dump area: %d\n", + ret); + return; + } + + ath6kl_dbg(ATH6KL_DBG_IRQ, "register dump data address 0x%x\n", + regdump_addr); + regdump_addr = TARG_VTOP(ar->target_type, regdump_addr); + + /* fetch register dump data */ + ret = ath6kl_diag_read(ar, regdump_addr, (u8 *)®dump_val[0], + REG_DUMP_COUNT_AR6003 * (sizeof(u32))); + if (ret) { + ath6kl_warn("failed to get register dump: %d\n", ret); + return; + } + + ath6kl_info("crash dump:\n"); + ath6kl_info("hw 0x%x fw %s\n", ar->wiphy->hw_version, + ar->wiphy->fw_version); + + BUILD_BUG_ON(REG_DUMP_COUNT_AR6003 % 4); + + for (i = 0; i < REG_DUMP_COUNT_AR6003; i += 4) { + ath6kl_info("%d: 0x%8.8x 0x%8.8x 0x%8.8x 0x%8.8x\n", + i, + le32_to_cpu(regdump_val[i]), + le32_to_cpu(regdump_val[i + 1]), + le32_to_cpu(regdump_val[i + 2]), + le32_to_cpu(regdump_val[i + 3])); + } + +} + +static int ath6kl_hif_proc_dbg_intr(struct ath6kl_device *dev) +{ + u32 dummy; + int ret; + + ath6kl_warn("firmware crashed\n"); + + /* + * read counter to clear the interrupt, the debug error interrupt is + * counter 0. + */ + ret = hif_read_write_sync(dev->ar, COUNT_DEC_ADDRESS, + (u8 *)&dummy, 4, HIF_RD_SYNC_BYTE_INC); + if (ret) + ath6kl_warn("Failed to clear debug interrupt: %d\n", ret); + + ath6kl_hif_dump_fw_crash(dev->ar); + ath6kl_read_fwlogs(dev->ar); + ath6kl_recovery_err_notify(dev->ar, ATH6KL_FW_ASSERT); + + return ret; +} + +/* mailbox recv message polling */ +int ath6kl_hif_poll_mboxmsg_rx(struct ath6kl_device *dev, u32 *lk_ahd, + int timeout) +{ + struct ath6kl_irq_proc_registers *rg; + int status = 0, i; + u8 htc_mbox = 1 << HTC_MAILBOX; + + for (i = timeout / ATH6KL_TIME_QUANTUM; i > 0; i--) { + /* this is the standard HIF way, load the reg table */ + status = hif_read_write_sync(dev->ar, HOST_INT_STATUS_ADDRESS, + (u8 *) &dev->irq_proc_reg, + sizeof(dev->irq_proc_reg), + HIF_RD_SYNC_BYTE_INC); + + if (status) { + ath6kl_err("failed to read reg table\n"); + return status; + } + + /* check for MBOX data and valid lookahead */ + if (dev->irq_proc_reg.host_int_status & htc_mbox) { + if (dev->irq_proc_reg.rx_lkahd_valid & + htc_mbox) { + /* + * Mailbox has a message and the look ahead + * is valid. + */ + rg = &dev->irq_proc_reg; + *lk_ahd = + le32_to_cpu(rg->rx_lkahd[HTC_MAILBOX]); + break; + } + } + + /* delay a little */ + mdelay(ATH6KL_TIME_QUANTUM); + ath6kl_dbg(ATH6KL_DBG_HIF, "hif retry mbox poll try %d\n", i); + } + + if (i == 0) { + ath6kl_err("timeout waiting for recv message\n"); + status = -ETIME; + /* check if the target asserted */ + if (dev->irq_proc_reg.counter_int_status & + ATH6KL_TARGET_DEBUG_INTR_MASK) + /* + * Target failure handler will be called in case of + * an assert. + */ + ath6kl_hif_proc_dbg_intr(dev); + } + + return status; +} + +/* + * Disable packet reception (used in case the host runs out of buffers) + * using the interrupt enable registers through the host I/F + */ +int ath6kl_hif_rx_control(struct ath6kl_device *dev, bool enable_rx) +{ + struct ath6kl_irq_enable_reg regs; + int status = 0; + + ath6kl_dbg(ATH6KL_DBG_HIF, "hif rx %s\n", + enable_rx ? "enable" : "disable"); + + /* take the lock to protect interrupt enable shadows */ + spin_lock_bh(&dev->lock); + + if (enable_rx) + dev->irq_en_reg.int_status_en |= + SM(INT_STATUS_ENABLE_MBOX_DATA, 0x01); + else + dev->irq_en_reg.int_status_en &= + ~SM(INT_STATUS_ENABLE_MBOX_DATA, 0x01); + + memcpy(®s, &dev->irq_en_reg, sizeof(regs)); + + spin_unlock_bh(&dev->lock); + + status = hif_read_write_sync(dev->ar, INT_STATUS_ENABLE_ADDRESS, + ®s.int_status_en, + sizeof(struct ath6kl_irq_enable_reg), + HIF_WR_SYNC_BYTE_INC); + + return status; +} + +int ath6kl_hif_submit_scat_req(struct ath6kl_device *dev, + struct hif_scatter_req *scat_req, bool read) +{ + int status = 0; + + if (read) { + scat_req->req = HIF_RD_SYNC_BLOCK_FIX; + scat_req->addr = dev->ar->mbox_info.htc_addr; + } else { + scat_req->req = HIF_WR_ASYNC_BLOCK_INC; + + scat_req->addr = + (scat_req->len > HIF_MBOX_WIDTH) ? + dev->ar->mbox_info.htc_ext_addr : + dev->ar->mbox_info.htc_addr; + } + + ath6kl_dbg(ATH6KL_DBG_HIF, + "hif submit scatter request entries %d len %d mbox 0x%x %s %s\n", + scat_req->scat_entries, scat_req->len, + scat_req->addr, !read ? "async" : "sync", + (read) ? "rd" : "wr"); + + if (!read && scat_req->virt_scat) { + status = ath6kl_hif_cp_scat_dma_buf(scat_req, false); + if (status) { + scat_req->status = status; + scat_req->complete(dev->ar->htc_target, scat_req); + return 0; + } + } + + status = ath6kl_hif_scat_req_rw(dev->ar, scat_req); + + if (read) { + /* in sync mode, we can touch the scatter request */ + scat_req->status = status; + if (!status && scat_req->virt_scat) + scat_req->status = + ath6kl_hif_cp_scat_dma_buf(scat_req, true); + } + + return status; +} + +static int ath6kl_hif_proc_counter_intr(struct ath6kl_device *dev) +{ + u8 counter_int_status; + + ath6kl_dbg(ATH6KL_DBG_IRQ, "counter interrupt\n"); + + counter_int_status = dev->irq_proc_reg.counter_int_status & + dev->irq_en_reg.cntr_int_status_en; + + ath6kl_dbg(ATH6KL_DBG_IRQ, + "valid interrupt source(s) in COUNTER_INT_STATUS: 0x%x\n", + counter_int_status); + + /* + * NOTE: other modules like GMBOX may use the counter interrupt for + * credit flow control on other counters, we only need to check for + * the debug assertion counter interrupt. + */ + if (counter_int_status & ATH6KL_TARGET_DEBUG_INTR_MASK) + return ath6kl_hif_proc_dbg_intr(dev); + + return 0; +} + +static int ath6kl_hif_proc_err_intr(struct ath6kl_device *dev) +{ + int status; + u8 error_int_status; + u8 reg_buf[4]; + + ath6kl_dbg(ATH6KL_DBG_IRQ, "error interrupt\n"); + + error_int_status = dev->irq_proc_reg.error_int_status & 0x0F; + if (!error_int_status) { + WARN_ON(1); + return -EIO; + } + + ath6kl_dbg(ATH6KL_DBG_IRQ, + "valid interrupt source(s) in ERROR_INT_STATUS: 0x%x\n", + error_int_status); + + if (MS(ERROR_INT_STATUS_WAKEUP, error_int_status)) + ath6kl_dbg(ATH6KL_DBG_IRQ, "error : wakeup\n"); + + if (MS(ERROR_INT_STATUS_RX_UNDERFLOW, error_int_status)) + ath6kl_err("rx underflow\n"); + + if (MS(ERROR_INT_STATUS_TX_OVERFLOW, error_int_status)) + ath6kl_err("tx overflow\n"); + + /* Clear the interrupt */ + dev->irq_proc_reg.error_int_status &= ~error_int_status; + + /* set W1C value to clear the interrupt, this hits the register first */ + reg_buf[0] = error_int_status; + reg_buf[1] = 0; + reg_buf[2] = 0; + reg_buf[3] = 0; + + status = hif_read_write_sync(dev->ar, ERROR_INT_STATUS_ADDRESS, + reg_buf, 4, HIF_WR_SYNC_BYTE_FIX); + + WARN_ON(status); + + return status; +} + +static int ath6kl_hif_proc_cpu_intr(struct ath6kl_device *dev) +{ + int status; + u8 cpu_int_status; + u8 reg_buf[4]; + + ath6kl_dbg(ATH6KL_DBG_IRQ, "cpu interrupt\n"); + + cpu_int_status = dev->irq_proc_reg.cpu_int_status & + dev->irq_en_reg.cpu_int_status_en; + if (!cpu_int_status) { + WARN_ON(1); + return -EIO; + } + + ath6kl_dbg(ATH6KL_DBG_IRQ, + "valid interrupt source(s) in CPU_INT_STATUS: 0x%x\n", + cpu_int_status); + + /* Clear the interrupt */ + dev->irq_proc_reg.cpu_int_status &= ~cpu_int_status; + + /* + * Set up the register transfer buffer to hit the register 4 times , + * this is done to make the access 4-byte aligned to mitigate issues + * with host bus interconnects that restrict bus transfer lengths to + * be a multiple of 4-bytes. + */ + + /* set W1C value to clear the interrupt, this hits the register first */ + reg_buf[0] = cpu_int_status; + /* the remaining are set to zero which have no-effect */ + reg_buf[1] = 0; + reg_buf[2] = 0; + reg_buf[3] = 0; + + status = hif_read_write_sync(dev->ar, CPU_INT_STATUS_ADDRESS, + reg_buf, 4, HIF_WR_SYNC_BYTE_FIX); + + WARN_ON(status); + + return status; +} + +/* process pending interrupts synchronously */ +static int proc_pending_irqs(struct ath6kl_device *dev, bool *done) +{ + struct ath6kl_irq_proc_registers *rg; + int status = 0; + u8 host_int_status = 0; + u32 lk_ahd = 0; + u8 htc_mbox = 1 << HTC_MAILBOX; + + ath6kl_dbg(ATH6KL_DBG_IRQ, "proc_pending_irqs: (dev: 0x%p)\n", dev); + + /* + * NOTE: HIF implementation guarantees that the context of this + * call allows us to perform SYNCHRONOUS I/O, that is we can block, + * sleep or call any API that can block or switch thread/task + * contexts. This is a fully schedulable context. + */ + + /* + * Process pending intr only when int_status_en is clear, it may + * result in unnecessary bus transaction otherwise. Target may be + * unresponsive at the time. + */ + if (dev->irq_en_reg.int_status_en) { + /* + * Read the first 28 bytes of the HTC register table. This + * will yield us the value of different int status + * registers and the lookahead registers. + * + * length = sizeof(int_status) + sizeof(cpu_int_status) + * + sizeof(error_int_status) + + * sizeof(counter_int_status) + + * sizeof(mbox_frame) + sizeof(rx_lkahd_valid) + * + sizeof(hole) + sizeof(rx_lkahd) + + * sizeof(int_status_en) + + * sizeof(cpu_int_status_en) + + * sizeof(err_int_status_en) + + * sizeof(cntr_int_status_en); + */ + status = hif_read_write_sync(dev->ar, HOST_INT_STATUS_ADDRESS, + (u8 *) &dev->irq_proc_reg, + sizeof(dev->irq_proc_reg), + HIF_RD_SYNC_BYTE_INC); + if (status) + goto out; + + ath6kl_dump_registers(dev, &dev->irq_proc_reg, + &dev->irq_en_reg); + trace_ath6kl_sdio_irq(&dev->irq_en_reg, + sizeof(dev->irq_en_reg)); + + /* Update only those registers that are enabled */ + host_int_status = dev->irq_proc_reg.host_int_status & + dev->irq_en_reg.int_status_en; + + /* Look at mbox status */ + if (host_int_status & htc_mbox) { + /* + * Mask out pending mbox value, we use "lookAhead as + * the real flag for mbox processing. + */ + host_int_status &= ~htc_mbox; + if (dev->irq_proc_reg.rx_lkahd_valid & + htc_mbox) { + rg = &dev->irq_proc_reg; + lk_ahd = le32_to_cpu(rg->rx_lkahd[HTC_MAILBOX]); + if (!lk_ahd) + ath6kl_err("lookAhead is zero!\n"); + } + } + } + + if (!host_int_status && !lk_ahd) { + *done = true; + goto out; + } + + if (lk_ahd) { + int fetched = 0; + + ath6kl_dbg(ATH6KL_DBG_IRQ, + "pending mailbox msg, lk_ahd: 0x%X\n", lk_ahd); + /* + * Mailbox Interrupt, the HTC layer may issue async + * requests to empty the mailbox. When emptying the recv + * mailbox we use the async handler above called from the + * completion routine of the callers read request. This can + * improve performance by reducing context switching when + * we rapidly pull packets. + */ + status = ath6kl_htc_rxmsg_pending_handler(dev->htc_cnxt, + lk_ahd, &fetched); + if (status) + goto out; + + if (!fetched) + /* + * HTC could not pull any messages out due to lack + * of resources. + */ + dev->htc_cnxt->chk_irq_status_cnt = 0; + } + + /* now handle the rest of them */ + ath6kl_dbg(ATH6KL_DBG_IRQ, + "valid interrupt source(s) for other interrupts: 0x%x\n", + host_int_status); + + if (MS(HOST_INT_STATUS_CPU, host_int_status)) { + /* CPU Interrupt */ + status = ath6kl_hif_proc_cpu_intr(dev); + if (status) + goto out; + } + + if (MS(HOST_INT_STATUS_ERROR, host_int_status)) { + /* Error Interrupt */ + status = ath6kl_hif_proc_err_intr(dev); + if (status) + goto out; + } + + if (MS(HOST_INT_STATUS_COUNTER, host_int_status)) + /* Counter Interrupt */ + status = ath6kl_hif_proc_counter_intr(dev); + +out: + /* + * An optimization to bypass reading the IRQ status registers + * unecessarily which can re-wake the target, if upper layers + * determine that we are in a low-throughput mode, we can rely on + * taking another interrupt rather than re-checking the status + * registers which can re-wake the target. + * + * NOTE : for host interfaces that makes use of detecting pending + * mbox messages at hif can not use this optimization due to + * possible side effects, SPI requires the host to drain all + * messages from the mailbox before exiting the ISR routine. + */ + + ath6kl_dbg(ATH6KL_DBG_IRQ, + "bypassing irq status re-check, forcing done\n"); + + if (!dev->htc_cnxt->chk_irq_status_cnt) + *done = true; + + ath6kl_dbg(ATH6KL_DBG_IRQ, + "proc_pending_irqs: (done:%d, status=%d\n", *done, status); + + return status; +} + +/* interrupt handler, kicks off all interrupt processing */ +int ath6kl_hif_intr_bh_handler(struct ath6kl *ar) +{ + struct ath6kl_device *dev = ar->htc_target->dev; + unsigned long timeout; + int status = 0; + bool done = false; + + /* + * Reset counter used to flag a re-scan of IRQ status registers on + * the target. + */ + dev->htc_cnxt->chk_irq_status_cnt = 0; + + /* + * IRQ processing is synchronous, interrupt status registers can be + * re-read. + */ + timeout = jiffies + msecs_to_jiffies(ATH6KL_HIF_COMMUNICATION_TIMEOUT); + while (time_before(jiffies, timeout) && !done) { + status = proc_pending_irqs(dev, &done); + if (status) + break; + } + + return status; +} +EXPORT_SYMBOL(ath6kl_hif_intr_bh_handler); + +static int ath6kl_hif_enable_intrs(struct ath6kl_device *dev) +{ + struct ath6kl_irq_enable_reg regs; + int status; + + spin_lock_bh(&dev->lock); + + /* Enable all but ATH6KL CPU interrupts */ + dev->irq_en_reg.int_status_en = + SM(INT_STATUS_ENABLE_ERROR, 0x01) | + SM(INT_STATUS_ENABLE_CPU, 0x01) | + SM(INT_STATUS_ENABLE_COUNTER, 0x01); + + /* + * NOTE: There are some cases where HIF can do detection of + * pending mbox messages which is disabled now. + */ + dev->irq_en_reg.int_status_en |= SM(INT_STATUS_ENABLE_MBOX_DATA, 0x01); + + /* Set up the CPU Interrupt status Register */ + dev->irq_en_reg.cpu_int_status_en = 0; + + /* Set up the Error Interrupt status Register */ + dev->irq_en_reg.err_int_status_en = + SM(ERROR_STATUS_ENABLE_RX_UNDERFLOW, 0x01) | + SM(ERROR_STATUS_ENABLE_TX_OVERFLOW, 0x1); + + /* + * Enable Counter interrupt status register to get fatal errors for + * debugging. + */ + dev->irq_en_reg.cntr_int_status_en = SM(COUNTER_INT_STATUS_ENABLE_BIT, + ATH6KL_TARGET_DEBUG_INTR_MASK); + memcpy(®s, &dev->irq_en_reg, sizeof(regs)); + + spin_unlock_bh(&dev->lock); + + status = hif_read_write_sync(dev->ar, INT_STATUS_ENABLE_ADDRESS, + ®s.int_status_en, sizeof(regs), + HIF_WR_SYNC_BYTE_INC); + + if (status) + ath6kl_err("failed to update interrupt ctl reg err: %d\n", + status); + + return status; +} + +int ath6kl_hif_disable_intrs(struct ath6kl_device *dev) +{ + struct ath6kl_irq_enable_reg regs; + + spin_lock_bh(&dev->lock); + /* Disable all interrupts */ + dev->irq_en_reg.int_status_en = 0; + dev->irq_en_reg.cpu_int_status_en = 0; + dev->irq_en_reg.err_int_status_en = 0; + dev->irq_en_reg.cntr_int_status_en = 0; + memcpy(®s, &dev->irq_en_reg, sizeof(regs)); + spin_unlock_bh(&dev->lock); + + return hif_read_write_sync(dev->ar, INT_STATUS_ENABLE_ADDRESS, + ®s.int_status_en, sizeof(regs), + HIF_WR_SYNC_BYTE_INC); +} + +/* enable device interrupts */ +int ath6kl_hif_unmask_intrs(struct ath6kl_device *dev) +{ + int status = 0; + + /* + * Make sure interrupt are disabled before unmasking at the HIF + * layer. The rationale here is that between device insertion + * (where we clear the interrupts the first time) and when HTC + * is finally ready to handle interrupts, other software can perform + * target "soft" resets. The ATH6KL interrupt enables reset back to an + * "enabled" state when this happens. + */ + ath6kl_hif_disable_intrs(dev); + + /* unmask the host controller interrupts */ + ath6kl_hif_irq_enable(dev->ar); + status = ath6kl_hif_enable_intrs(dev); + + return status; +} + +/* disable all device interrupts */ +int ath6kl_hif_mask_intrs(struct ath6kl_device *dev) +{ + /* + * Mask the interrupt at the HIF layer to avoid any stray interrupt + * taken while we zero out our shadow registers in + * ath6kl_hif_disable_intrs(). + */ + ath6kl_hif_irq_disable(dev->ar); + + return ath6kl_hif_disable_intrs(dev); +} + +int ath6kl_hif_setup(struct ath6kl_device *dev) +{ + int status = 0; + + spin_lock_init(&dev->lock); + + /* + * NOTE: we actually get the block size of a mailbox other than 0, + * for SDIO the block size on mailbox 0 is artificially set to 1. + * So we use the block size that is set for the other 3 mailboxes. + */ + dev->htc_cnxt->block_sz = dev->ar->mbox_info.block_size; + + /* must be a power of 2 */ + if ((dev->htc_cnxt->block_sz & (dev->htc_cnxt->block_sz - 1)) != 0) { + WARN_ON(1); + status = -EINVAL; + goto fail_setup; + } + + /* assemble mask, used for padding to a block */ + dev->htc_cnxt->block_mask = dev->htc_cnxt->block_sz - 1; + + ath6kl_dbg(ATH6KL_DBG_HIF, "hif block size %d mbox addr 0x%x\n", + dev->htc_cnxt->block_sz, dev->ar->mbox_info.htc_addr); + + status = ath6kl_hif_disable_intrs(dev); + +fail_setup: + return status; + +} diff --git a/drivers/net/wireless/ath/ath6kl/hif.h b/drivers/net/wireless/ath/ath6kl/hif.h new file mode 100644 index 0000000..61f6b21 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/hif.h @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef HIF_H +#define HIF_H + +#include "common.h" +#include "core.h" + +#include + +#define BUS_REQUEST_MAX_NUM 64 +#define HIF_MBOX_BLOCK_SIZE 128 +#define HIF_MBOX0_BLOCK_SIZE 1 + +#define HIF_DMA_BUFFER_SIZE (32 * 1024) +#define CMD53_FIXED_ADDRESS 1 +#define CMD53_INCR_ADDRESS 2 + +#define MAX_SCATTER_REQUESTS 4 +#define MAX_SCATTER_ENTRIES_PER_REQ 16 +#define MAX_SCATTER_REQ_TRANSFER_SIZE (32 * 1024) + +#define MANUFACTURER_ID_AR6003_BASE 0x300 +#define MANUFACTURER_ID_AR6004_BASE 0x400 + /* SDIO manufacturer ID and Codes */ +#define MANUFACTURER_ID_ATH6KL_BASE_MASK 0xFF00 +#define MANUFACTURER_CODE 0x271 /* Atheros */ + +/* Mailbox address in SDIO address space */ +#define HIF_MBOX_BASE_ADDR 0x800 +#define HIF_MBOX_WIDTH 0x800 + +#define HIF_MBOX_END_ADDR (HTC_MAILBOX_NUM_MAX * HIF_MBOX_WIDTH - 1) + +/* version 1 of the chip has only a 12K extended mbox range */ +#define HIF_MBOX0_EXT_BASE_ADDR 0x4000 +#define HIF_MBOX0_EXT_WIDTH (12*1024) + +/* GMBOX addresses */ +#define HIF_GMBOX_BASE_ADDR 0x7000 +#define HIF_GMBOX_WIDTH 0x4000 + +/* interrupt mode register */ +#define CCCR_SDIO_IRQ_MODE_REG 0xF0 + +/* mode to enable special 4-bit interrupt assertion without clock */ +#define SDIO_IRQ_MODE_ASYNC_4BIT_IRQ (1 << 0) + +/* HTC runs over mailbox 0 */ +#define HTC_MAILBOX 0 + +#define ATH6KL_TARGET_DEBUG_INTR_MASK 0x01 + +/* FIXME: are these duplicates with MAX_SCATTER_ values in hif.h? */ +#define ATH6KL_SCATTER_ENTRIES_PER_REQ 16 +#define ATH6KL_MAX_TRANSFER_SIZE_PER_SCATTER (16 * 1024) +#define ATH6KL_SCATTER_REQS 4 + +#define ATH6KL_HIF_COMMUNICATION_TIMEOUT 1000 + +struct bus_request { + struct list_head list; + + /* request data */ + u32 address; + + u8 *buffer; + u32 length; + u32 request; + struct htc_packet *packet; + int status; + + /* this is a scatter request */ + struct hif_scatter_req *scat_req; +}; + +/* direction of transfer (read/write) */ +#define HIF_READ 0x00000001 +#define HIF_WRITE 0x00000002 +#define HIF_DIR_MASK (HIF_READ | HIF_WRITE) + +/* + * emode - This indicates the whether the command is to be executed in a + * blocking or non-blocking fashion (HIF_SYNCHRONOUS/ + * HIF_ASYNCHRONOUS). The read/write data paths in HTC have been + * implemented using the asynchronous mode allowing the the bus + * driver to indicate the completion of operation through the + * registered callback routine. The requirement primarily comes + * from the contexts these operations get called from (a driver's + * transmit context or the ISR context in case of receive). + * Support for both of these modes is essential. + */ +#define HIF_SYNCHRONOUS 0x00000010 +#define HIF_ASYNCHRONOUS 0x00000020 +#define HIF_EMODE_MASK (HIF_SYNCHRONOUS | HIF_ASYNCHRONOUS) + +/* + * dmode - An interface may support different kinds of commands based on + * the tradeoff between the amount of data it can carry and the + * setup time. Byte and Block modes are supported (HIF_BYTE_BASIS/ + * HIF_BLOCK_BASIS). In case of latter, the data is rounded off + * to the nearest block size by padding. The size of the block is + * configurable at compile time using the HIF_BLOCK_SIZE and is + * negotiated with the target during initialization after the + * ATH6KL interrupts are enabled. + */ +#define HIF_BYTE_BASIS 0x00000040 +#define HIF_BLOCK_BASIS 0x00000080 +#define HIF_DMODE_MASK (HIF_BYTE_BASIS | HIF_BLOCK_BASIS) + +/* + * amode - This indicates if the address has to be incremented on ATH6KL + * after every read/write operation (HIF?FIXED_ADDRESS/ + * HIF_INCREMENTAL_ADDRESS). + */ +#define HIF_FIXED_ADDRESS 0x00000100 +#define HIF_INCREMENTAL_ADDRESS 0x00000200 +#define HIF_AMODE_MASK (HIF_FIXED_ADDRESS | HIF_INCREMENTAL_ADDRESS) + +#define HIF_WR_ASYNC_BYTE_INC \ + (HIF_WRITE | HIF_ASYNCHRONOUS | \ + HIF_BYTE_BASIS | HIF_INCREMENTAL_ADDRESS) + +#define HIF_WR_ASYNC_BLOCK_INC \ + (HIF_WRITE | HIF_ASYNCHRONOUS | \ + HIF_BLOCK_BASIS | HIF_INCREMENTAL_ADDRESS) + +#define HIF_WR_SYNC_BYTE_FIX \ + (HIF_WRITE | HIF_SYNCHRONOUS | \ + HIF_BYTE_BASIS | HIF_FIXED_ADDRESS) + +#define HIF_WR_SYNC_BYTE_INC \ + (HIF_WRITE | HIF_SYNCHRONOUS | \ + HIF_BYTE_BASIS | HIF_INCREMENTAL_ADDRESS) + +#define HIF_WR_SYNC_BLOCK_INC \ + (HIF_WRITE | HIF_SYNCHRONOUS | \ + HIF_BLOCK_BASIS | HIF_INCREMENTAL_ADDRESS) + +#define HIF_RD_SYNC_BYTE_INC \ + (HIF_READ | HIF_SYNCHRONOUS | \ + HIF_BYTE_BASIS | HIF_INCREMENTAL_ADDRESS) + +#define HIF_RD_SYNC_BYTE_FIX \ + (HIF_READ | HIF_SYNCHRONOUS | \ + HIF_BYTE_BASIS | HIF_FIXED_ADDRESS) + +#define HIF_RD_ASYNC_BLOCK_FIX \ + (HIF_READ | HIF_ASYNCHRONOUS | \ + HIF_BLOCK_BASIS | HIF_FIXED_ADDRESS) + +#define HIF_RD_SYNC_BLOCK_FIX \ + (HIF_READ | HIF_SYNCHRONOUS | \ + HIF_BLOCK_BASIS | HIF_FIXED_ADDRESS) + +struct hif_scatter_item { + u8 *buf; + int len; + struct htc_packet *packet; +}; + +struct hif_scatter_req { + struct list_head list; + /* address for the read/write operation */ + u32 addr; + + /* request flags */ + u32 req; + + /* total length of entire transfer */ + u32 len; + + bool virt_scat; + + void (*complete) (struct htc_target *, struct hif_scatter_req *); + int status; + int scat_entries; + + struct bus_request *busrequest; + struct scatterlist *sgentries; + + /* bounce buffer for upper layers to copy to/from */ + u8 *virt_dma_buf; + + struct hif_scatter_item scat_list[1]; + + u32 scat_q_depth; +}; + +struct ath6kl_irq_proc_registers { + u8 host_int_status; + u8 cpu_int_status; + u8 error_int_status; + u8 counter_int_status; + u8 mbox_frame; + u8 rx_lkahd_valid; + u8 host_int_status2; + u8 gmbox_rx_avail; + __le32 rx_lkahd[2]; + __le32 rx_gmbox_lkahd_alias[2]; +} __packed; + +struct ath6kl_irq_enable_reg { + u8 int_status_en; + u8 cpu_int_status_en; + u8 err_int_status_en; + u8 cntr_int_status_en; +} __packed; + +struct ath6kl_device { + /* protects irq_proc_reg and irq_en_reg below */ + spinlock_t lock; + struct ath6kl_irq_proc_registers irq_proc_reg; + struct ath6kl_irq_enable_reg irq_en_reg; + struct htc_target *htc_cnxt; + struct ath6kl *ar; +}; + +struct ath6kl_hif_ops { + int (*read_write_sync)(struct ath6kl *ar, u32 addr, u8 *buf, + u32 len, u32 request); + int (*write_async)(struct ath6kl *ar, u32 address, u8 *buffer, + u32 length, u32 request, struct htc_packet *packet); + + void (*irq_enable)(struct ath6kl *ar); + void (*irq_disable)(struct ath6kl *ar); + + struct hif_scatter_req *(*scatter_req_get)(struct ath6kl *ar); + void (*scatter_req_add)(struct ath6kl *ar, + struct hif_scatter_req *s_req); + int (*enable_scatter)(struct ath6kl *ar); + int (*scat_req_rw) (struct ath6kl *ar, + struct hif_scatter_req *scat_req); + void (*cleanup_scatter)(struct ath6kl *ar); + int (*suspend)(struct ath6kl *ar, struct cfg80211_wowlan *wow); + int (*resume)(struct ath6kl *ar); + int (*diag_read32)(struct ath6kl *ar, u32 address, u32 *value); + int (*diag_write32)(struct ath6kl *ar, u32 address, __le32 value); + int (*bmi_read)(struct ath6kl *ar, u8 *buf, u32 len); + int (*bmi_write)(struct ath6kl *ar, u8 *buf, u32 len); + int (*power_on)(struct ath6kl *ar); + int (*power_off)(struct ath6kl *ar); + void (*stop)(struct ath6kl *ar); + int (*pipe_send)(struct ath6kl *ar, u8 pipe, struct sk_buff *hdr_buf, + struct sk_buff *buf); + void (*pipe_get_default)(struct ath6kl *ar, u8 *pipe_ul, u8 *pipe_dl); + int (*pipe_map_service)(struct ath6kl *ar, u16 service_id, u8 *pipe_ul, + u8 *pipe_dl); + u16 (*pipe_get_free_queue_number)(struct ath6kl *ar, u8 pipe); +}; + +int ath6kl_hif_setup(struct ath6kl_device *dev); +int ath6kl_hif_unmask_intrs(struct ath6kl_device *dev); +int ath6kl_hif_mask_intrs(struct ath6kl_device *dev); +int ath6kl_hif_poll_mboxmsg_rx(struct ath6kl_device *dev, + u32 *lk_ahd, int timeout); +int ath6kl_hif_rx_control(struct ath6kl_device *dev, bool enable_rx); +int ath6kl_hif_disable_intrs(struct ath6kl_device *dev); + +int ath6kl_hif_rw_comp_handler(void *context, int status); +int ath6kl_hif_intr_bh_handler(struct ath6kl *ar); + +/* Scatter Function and Definitions */ +int ath6kl_hif_submit_scat_req(struct ath6kl_device *dev, + struct hif_scatter_req *scat_req, bool read); + +#endif diff --git a/drivers/net/wireless/ath/ath6kl/htc-ops.h b/drivers/net/wireless/ath/ath6kl/htc-ops.h new file mode 100644 index 0000000..2d4eed5 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/htc-ops.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2004-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef HTC_OPS_H +#define HTC_OPS_H + +#include "htc.h" +#include "debug.h" + +static inline void *ath6kl_htc_create(struct ath6kl *ar) +{ + return ar->htc_ops->create(ar); +} + +static inline int ath6kl_htc_wait_target(struct htc_target *target) +{ + return target->dev->ar->htc_ops->wait_target(target); +} + +static inline int ath6kl_htc_start(struct htc_target *target) +{ + return target->dev->ar->htc_ops->start(target); +} + +static inline int ath6kl_htc_conn_service(struct htc_target *target, + struct htc_service_connect_req *req, + struct htc_service_connect_resp *resp) +{ + return target->dev->ar->htc_ops->conn_service(target, req, resp); +} + +static inline int ath6kl_htc_tx(struct htc_target *target, + struct htc_packet *packet) +{ + return target->dev->ar->htc_ops->tx(target, packet); +} + +static inline void ath6kl_htc_stop(struct htc_target *target) +{ + return target->dev->ar->htc_ops->stop(target); +} + +static inline void ath6kl_htc_cleanup(struct htc_target *target) +{ + return target->dev->ar->htc_ops->cleanup(target); +} + +static inline void ath6kl_htc_flush_txep(struct htc_target *target, + enum htc_endpoint_id endpoint, + u16 tag) +{ + return target->dev->ar->htc_ops->flush_txep(target, endpoint, tag); +} + +static inline void ath6kl_htc_flush_rx_buf(struct htc_target *target) +{ + return target->dev->ar->htc_ops->flush_rx_buf(target); +} + +static inline void ath6kl_htc_activity_changed(struct htc_target *target, + enum htc_endpoint_id endpoint, + bool active) +{ + return target->dev->ar->htc_ops->activity_changed(target, endpoint, + active); +} + +static inline int ath6kl_htc_get_rxbuf_num(struct htc_target *target, + enum htc_endpoint_id endpoint) +{ + return target->dev->ar->htc_ops->get_rxbuf_num(target, endpoint); +} + +static inline int ath6kl_htc_add_rxbuf_multiple(struct htc_target *target, + struct list_head *pktq) +{ + return target->dev->ar->htc_ops->add_rxbuf_multiple(target, pktq); +} + +static inline int ath6kl_htc_credit_setup(struct htc_target *target, + struct ath6kl_htc_credit_info *info) +{ + return target->dev->ar->htc_ops->credit_setup(target, info); +} + +static inline void ath6kl_htc_tx_complete(struct ath6kl *ar, + struct sk_buff *skb) +{ + ar->htc_ops->tx_complete(ar, skb); +} + + +static inline void ath6kl_htc_rx_complete(struct ath6kl *ar, + struct sk_buff *skb, u8 pipe) +{ + ar->htc_ops->rx_complete(ar, skb, pipe); +} + + +#endif diff --git a/drivers/net/wireless/ath/ath6kl/htc.h b/drivers/net/wireless/ath/ath6kl/htc.h new file mode 100644 index 0000000..a2c8ff8 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/htc.h @@ -0,0 +1,677 @@ +/* + * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef HTC_H +#define HTC_H + +#include "common.h" + +/* frame header flags */ + +/* send direction */ +#define HTC_FLAGS_NEED_CREDIT_UPDATE (1 << 0) +#define HTC_FLAGS_SEND_BUNDLE (1 << 1) +#define HTC_FLAGS_TX_FIXUP_NETBUF (1 << 2) + +/* receive direction */ +#define HTC_FLG_RX_UNUSED (1 << 0) +#define HTC_FLG_RX_TRAILER (1 << 1) +/* Bundle count maske and shift */ +#define HTC_FLG_RX_BNDL_CNT (0xF0) +#define HTC_FLG_RX_BNDL_CNT_S 4 + +#define HTC_HDR_LENGTH (sizeof(struct htc_frame_hdr)) +#define HTC_MAX_PAYLOAD_LENGTH (4096 - sizeof(struct htc_frame_hdr)) + +/* HTC control message IDs */ + +#define HTC_MSG_READY_ID 1 +#define HTC_MSG_CONN_SVC_ID 2 +#define HTC_MSG_CONN_SVC_RESP_ID 3 +#define HTC_MSG_SETUP_COMPLETE_ID 4 +#define HTC_MSG_SETUP_COMPLETE_EX_ID 5 + +#define HTC_MAX_CTRL_MSG_LEN 256 + +#define HTC_VERSION_2P0 0x00 +#define HTC_VERSION_2P1 0x01 + +#define HTC_SERVICE_META_DATA_MAX_LENGTH 128 + +#define HTC_CONN_FLGS_THRESH_LVL_QUAT 0x0 +#define HTC_CONN_FLGS_THRESH_LVL_HALF 0x1 +#define HTC_CONN_FLGS_THRESH_LVL_THREE_QUAT 0x2 +#define HTC_CONN_FLGS_REDUCE_CRED_DRIB 0x4 +#define HTC_CONN_FLGS_THRESH_MASK 0x3 +/* disable credit flow control on a specific service */ +#define HTC_CONN_FLGS_DISABLE_CRED_FLOW_CTRL (1 << 3) +#define HTC_CONN_FLGS_SET_RECV_ALLOC_SHIFT 8 +#define HTC_CONN_FLGS_SET_RECV_ALLOC_MASK 0xFF00 + +/* connect response status codes */ +#define HTC_SERVICE_SUCCESS 0 +#define HTC_SERVICE_NOT_FOUND 1 +#define HTC_SERVICE_FAILED 2 + +/* no resources (i.e. no more endpoints) */ +#define HTC_SERVICE_NO_RESOURCES 3 + +/* specific service is not allowing any more endpoints */ +#define HTC_SERVICE_NO_MORE_EP 4 + +/* report record IDs */ +#define HTC_RECORD_NULL 0 +#define HTC_RECORD_CREDITS 1 +#define HTC_RECORD_LOOKAHEAD 2 +#define HTC_RECORD_LOOKAHEAD_BUNDLE 3 + +#define HTC_SETUP_COMP_FLG_RX_BNDL_EN (1 << 0) +#define HTC_SETUP_COMP_FLG_DISABLE_TX_CREDIT_FLOW (1 << 1) + +#define MAKE_SERVICE_ID(group, index) \ + (int)(((int)group << 8) | (int)(index)) + +/* NOTE: service ID of 0x0000 is reserved and should never be used */ +#define HTC_CTRL_RSVD_SVC MAKE_SERVICE_ID(RSVD_SERVICE_GROUP, 1) +#define WMI_CONTROL_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 0) +#define WMI_DATA_BE_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 1) +#define WMI_DATA_BK_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 2) +#define WMI_DATA_VI_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 3) +#define WMI_DATA_VO_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 4) +#define WMI_MAX_SERVICES 5 + +#define WMM_NUM_AC 4 + +/* reserved and used to flush ALL packets */ +#define HTC_TX_PACKET_TAG_ALL 0 +#define HTC_SERVICE_TX_PACKET_TAG 1 +#define HTC_TX_PACKET_TAG_USER_DEFINED (HTC_SERVICE_TX_PACKET_TAG + 9) + +/* more packets on this endpoint are being fetched */ +#define HTC_RX_FLAGS_INDICATE_MORE_PKTS (1 << 0) + +/* TODO.. for BMI */ +#define ENDPOINT1 0 +/* TODO -remove me, but we have to fix BMI first */ +#define HTC_MAILBOX_NUM_MAX 4 + +/* enable send bundle padding for this endpoint */ +#define HTC_FLGS_TX_BNDL_PAD_EN (1 << 0) +#define HTC_EP_ACTIVE ((u32) (1u << 31)) + +/* HTC operational parameters */ +#define HTC_TARGET_RESPONSE_TIMEOUT 2000 /* in ms */ +#define HTC_TARGET_RESPONSE_POLL_WAIT 10 +#define HTC_TARGET_RESPONSE_POLL_COUNT 200 +#define HTC_TARGET_DEBUG_INTR_MASK 0x01 +#define HTC_TARGET_CREDIT_INTR_MASK 0xF0 + +#define HTC_HOST_MAX_MSG_PER_BUNDLE 8 +#define HTC_MIN_HTC_MSGS_TO_BUNDLE 2 + +/* packet flags */ + +#define HTC_RX_PKT_IGNORE_LOOKAHEAD (1 << 0) +#define HTC_RX_PKT_REFRESH_HDR (1 << 1) +#define HTC_RX_PKT_PART_OF_BUNDLE (1 << 2) +#define HTC_RX_PKT_NO_RECYCLE (1 << 3) + +#define NUM_CONTROL_BUFFERS 8 +#define NUM_CONTROL_TX_BUFFERS 2 +#define NUM_CONTROL_RX_BUFFERS (NUM_CONTROL_BUFFERS - NUM_CONTROL_TX_BUFFERS) + +#define HTC_RECV_WAIT_BUFFERS (1 << 0) +#define HTC_OP_STATE_STOPPING (1 << 0) +#define HTC_OP_STATE_SETUP_COMPLETE (1 << 1) + +/* + * The frame header length and message formats defined herein were selected + * to accommodate optimal alignment for target processing. This reduces + * code size and improves performance. Any changes to the header length may + * alter the alignment and cause exceptions on the target. When adding to + * the messagestructures insure that fields are properly aligned. + */ + +/* HTC frame header + * + * NOTE: do not remove or re-arrange the fields, these are minimally + * required to take advantage of 4-byte lookaheads in some hardware + * implementations. + */ +struct htc_frame_hdr { + u8 eid; + u8 flags; + + /* length of data (including trailer) that follows the header */ + __le16 payld_len; + + /* end of 4-byte lookahead */ + + u8 ctrl[2]; +} __packed; + +/* HTC ready message */ +struct htc_ready_msg { + __le16 msg_id; + __le16 cred_cnt; + __le16 cred_sz; + u8 max_ep; + u8 pad; +} __packed; + +/* extended HTC ready message */ +struct htc_ready_ext_msg { + struct htc_ready_msg ver2_0_info; + u8 htc_ver; + u8 msg_per_htc_bndl; +} __packed; + +/* connect service */ +struct htc_conn_service_msg { + __le16 msg_id; + __le16 svc_id; + __le16 conn_flags; + u8 svc_meta_len; + u8 pad; +} __packed; + +/* connect response */ +struct htc_conn_service_resp { + __le16 msg_id; + __le16 svc_id; + u8 status; + u8 eid; + __le16 max_msg_sz; + u8 svc_meta_len; + u8 pad; +} __packed; + +struct htc_setup_comp_msg { + __le16 msg_id; +} __packed; + +/* extended setup completion message */ +struct htc_setup_comp_ext_msg { + __le16 msg_id; + __le32 flags; + u8 msg_per_rxbndl; + u8 Rsvd[3]; +} __packed; + +struct htc_record_hdr { + u8 rec_id; + u8 len; +} __packed; + +struct htc_credit_report { + u8 eid; + u8 credits; +} __packed; + +/* + * NOTE: The lk_ahd array is guarded by a pre_valid + * and Post Valid guard bytes. The pre_valid bytes must + * equal the inverse of the post_valid byte. + */ +struct htc_lookahead_report { + u8 pre_valid; + u8 lk_ahd[4]; + u8 post_valid; +} __packed; + +struct htc_bundle_lkahd_rpt { + u8 lk_ahd[4]; +} __packed; + +/* Current service IDs */ + +enum htc_service_grp_ids { + RSVD_SERVICE_GROUP = 0, + WMI_SERVICE_GROUP = 1, + + HTC_TEST_GROUP = 254, + HTC_SERVICE_GROUP_LAST = 255 +}; + +/* ------ endpoint IDS ------ */ + +enum htc_endpoint_id { + ENDPOINT_UNUSED = -1, + ENDPOINT_0 = 0, + ENDPOINT_1 = 1, + ENDPOINT_2 = 2, + ENDPOINT_3, + ENDPOINT_4, + ENDPOINT_5, + ENDPOINT_6, + ENDPOINT_7, + ENDPOINT_8, + ENDPOINT_MAX, +}; + +struct htc_tx_packet_info { + u16 tag; + int cred_used; + u8 flags; + int seqno; +}; + +struct htc_rx_packet_info { + u32 exp_hdr; + u32 rx_flags; + u32 indicat_flags; +}; + +struct htc_target; + +/* wrapper around endpoint-specific packets */ +struct htc_packet { + struct list_head list; + + /* caller's per packet specific context */ + void *pkt_cntxt; + + /* + * the true buffer start , the caller can store the real + * buffer start here. In receive callbacks, the HTC layer + * sets buf to the start of the payload past the header. + * This field allows the caller to reset buf when it recycles + * receive packets back to HTC. + */ + u8 *buf_start; + + /* + * Pointer to the start of the buffer. In the transmit + * direction this points to the start of the payload. In the + * receive direction, however, the buffer when queued up + * points to the start of the HTC header but when returned + * to the caller points to the start of the payload + */ + u8 *buf; + u32 buf_len; + + /* actual length of payload */ + u32 act_len; + + /* endpoint that this packet was sent/recv'd from */ + enum htc_endpoint_id endpoint; + + /* completion status */ + + int status; + union { + struct htc_tx_packet_info tx; + struct htc_rx_packet_info rx; + } info; + + void (*completion) (struct htc_target *, struct htc_packet *); + struct htc_target *context; + + /* + * optimization for network-oriented data, the HTC packet + * can pass the network buffer corresponding to the HTC packet + * lower layers may optimized the transfer knowing this is + * a network buffer + */ + struct sk_buff *skb; +}; + +enum htc_send_full_action { + HTC_SEND_FULL_KEEP = 0, + HTC_SEND_FULL_DROP = 1, +}; + +struct htc_ep_callbacks { + void (*tx_complete) (struct htc_target *, struct htc_packet *); + void (*rx) (struct htc_target *, struct htc_packet *); + void (*rx_refill) (struct htc_target *, enum htc_endpoint_id endpoint); + enum htc_send_full_action (*tx_full) (struct htc_target *, + struct htc_packet *); + struct htc_packet *(*rx_allocthresh) (struct htc_target *, + enum htc_endpoint_id, int); + void (*tx_comp_multi) (struct htc_target *, struct list_head *); + int rx_alloc_thresh; + int rx_refill_thresh; +}; + +/* service connection information */ +struct htc_service_connect_req { + u16 svc_id; + u16 conn_flags; + struct htc_ep_callbacks ep_cb; + int max_txq_depth; + u32 flags; + unsigned int max_rxmsg_sz; +}; + +/* service connection response information */ +struct htc_service_connect_resp { + u8 buf_len; + u8 act_len; + enum htc_endpoint_id endpoint; + unsigned int len_max; + u8 resp_code; +}; + +/* endpoint distributionstructure */ +struct htc_endpoint_credit_dist { + struct list_head list; + + /* Service ID (set by HTC) */ + u16 svc_id; + + /* endpoint for this distributionstruct (set by HTC) */ + enum htc_endpoint_id endpoint; + + u32 dist_flags; + + /* + * credits for normal operation, anything above this + * indicates the endpoint is over-subscribed. + */ + int cred_norm; + + /* floor for credit distribution */ + int cred_min; + + int cred_assngd; + + /* current credits available */ + int credits; + + /* + * pending credits to distribute on this endpoint, this + * is set by HTC when credit reports arrive. The credit + * distribution functions sets this to zero when it distributes + * the credits. + */ + int cred_to_dist; + + /* + * the number of credits that the current pending TX packet needs + * to transmit. This is set by HTC when endpoint needs credits in + * order to transmit. + */ + int seek_cred; + + /* size in bytes of each credit */ + int cred_sz; + + /* credits required for a maximum sized messages */ + int cred_per_msg; + + /* reserved for HTC use */ + struct htc_endpoint *htc_ep; + + /* + * current depth of TX queue , i.e. messages waiting for credits + * This field is valid only when HTC_CREDIT_DIST_ACTIVITY_CHANGE + * or HTC_CREDIT_DIST_SEND_COMPLETE is indicated on an endpoint + * that has non-zero credits to recover. + */ + int txq_depth; +}; + +/* + * credit distibution code that is passed into the distrbution function, + * there are mandatory and optional codes that must be handled + */ +enum htc_credit_dist_reason { + HTC_CREDIT_DIST_SEND_COMPLETE = 0, + HTC_CREDIT_DIST_ACTIVITY_CHANGE = 1, + HTC_CREDIT_DIST_SEEK_CREDITS, +}; + +struct ath6kl_htc_credit_info { + int total_avail_credits; + int cur_free_credits; + + /* list of lowest priority endpoints */ + struct list_head lowestpri_ep_dist; +}; + +/* endpoint statistics */ +struct htc_endpoint_stats { + /* + * number of times the host set the credit-low flag in a send + * message on this endpoint + */ + u32 cred_low_indicate; + + u32 tx_issued; + u32 tx_pkt_bundled; + u32 tx_bundles; + u32 tx_dropped; + + /* running count of total credit reports received for this endpoint */ + u32 tx_cred_rpt; + + /* credit reports received from this endpoint's RX packets */ + u32 cred_rpt_from_rx; + + /* credit reports received from RX packets of other endpoints */ + u32 cred_rpt_from_other; + + /* credit reports received from endpoint 0 RX packets */ + u32 cred_rpt_ep0; + + /* count of credits received via Rx packets on this endpoint */ + u32 cred_from_rx; + + /* count of credits received via another endpoint */ + u32 cred_from_other; + + /* count of credits received via another endpoint */ + u32 cred_from_ep0; + + /* count of consummed credits */ + u32 cred_cosumd; + + /* count of credits returned */ + u32 cred_retnd; + + u32 rx_pkts; + + /* count of lookahead records found in Rx msg */ + u32 rx_lkahds; + + /* count of recv packets received in a bundle */ + u32 rx_bundl; + + /* count of number of bundled lookaheads */ + u32 rx_bundle_lkahd; + + /* count of the number of bundle indications from the HTC header */ + u32 rx_bundle_from_hdr; + + /* the number of times the recv allocation threshold was hit */ + u32 rx_alloc_thresh_hit; + + /* total number of bytes */ + u32 rxalloc_thresh_byte; +}; + +struct htc_endpoint { + enum htc_endpoint_id eid; + u16 svc_id; + struct list_head txq; + struct list_head rx_bufq; + struct htc_endpoint_credit_dist cred_dist; + struct htc_ep_callbacks ep_cb; + int max_txq_depth; + int len_max; + int tx_proc_cnt; + int rx_proc_cnt; + struct htc_target *target; + u8 seqno; + u32 conn_flags; + struct htc_endpoint_stats ep_st; + u16 tx_drop_packet_threshold; + + struct { + u8 pipeid_ul; + u8 pipeid_dl; + struct list_head tx_lookup_queue; + bool tx_credit_flow_enabled; + } pipe; +}; + +struct htc_control_buffer { + struct htc_packet packet; + u8 *buf; +}; + +struct htc_pipe_txcredit_alloc { + u16 service_id; + u8 credit_alloc; +}; + +enum htc_send_queue_result { + HTC_SEND_QUEUE_OK = 0, /* packet was queued */ + HTC_SEND_QUEUE_DROP = 1, /* this packet should be dropped */ +}; + +struct ath6kl_htc_ops { + void* (*create)(struct ath6kl *ar); + int (*wait_target)(struct htc_target *target); + int (*start)(struct htc_target *target); + int (*conn_service)(struct htc_target *target, + struct htc_service_connect_req *req, + struct htc_service_connect_resp *resp); + int (*tx)(struct htc_target *target, struct htc_packet *packet); + void (*stop)(struct htc_target *target); + void (*cleanup)(struct htc_target *target); + void (*flush_txep)(struct htc_target *target, + enum htc_endpoint_id endpoint, u16 tag); + void (*flush_rx_buf)(struct htc_target *target); + void (*activity_changed)(struct htc_target *target, + enum htc_endpoint_id endpoint, + bool active); + int (*get_rxbuf_num)(struct htc_target *target, + enum htc_endpoint_id endpoint); + int (*add_rxbuf_multiple)(struct htc_target *target, + struct list_head *pktq); + int (*credit_setup)(struct htc_target *target, + struct ath6kl_htc_credit_info *cred_info); + int (*tx_complete)(struct ath6kl *ar, struct sk_buff *skb); + int (*rx_complete)(struct ath6kl *ar, struct sk_buff *skb, u8 pipe); +}; + +struct ath6kl_device; + +/* our HTC target state */ +struct htc_target { + struct htc_endpoint endpoint[ENDPOINT_MAX]; + + /* contains struct htc_endpoint_credit_dist */ + struct list_head cred_dist_list; + + struct list_head free_ctrl_txbuf; + struct list_head free_ctrl_rxbuf; + struct ath6kl_htc_credit_info *credit_info; + int tgt_creds; + unsigned int tgt_cred_sz; + + /* protects free_ctrl_txbuf and free_ctrl_rxbuf */ + spinlock_t htc_lock; + + /* FIXME: does this protext rx_bufq and endpoint structures or what? */ + spinlock_t rx_lock; + + /* protects endpoint->txq */ + spinlock_t tx_lock; + + struct ath6kl_device *dev; + u32 htc_flags; + u32 rx_st_flags; + enum htc_endpoint_id ep_waiting; + u8 htc_tgt_ver; + + /* max messages per bundle for HTC */ + int msg_per_bndl_max; + + u32 tx_bndl_mask; + int rx_bndl_enable; + int max_rx_bndl_sz; + int max_tx_bndl_sz; + + u32 block_sz; + u32 block_mask; + + int max_scat_entries; + int max_xfer_szper_scatreq; + + int chk_irq_status_cnt; + + /* counts the number of Tx without bundling continously per AC */ + u32 ac_tx_count[WMM_NUM_AC]; + + struct { + struct htc_packet *htc_packet_pool; + u8 ctrl_response_buf[HTC_MAX_CTRL_MSG_LEN]; + int ctrl_response_len; + bool ctrl_response_valid; + struct htc_pipe_txcredit_alloc txcredit_alloc[ENDPOINT_MAX]; + } pipe; +}; + +int ath6kl_htc_rxmsg_pending_handler(struct htc_target *target, + u32 msg_look_ahead, int *n_pkts); + +static inline void set_htc_pkt_info(struct htc_packet *packet, void *context, + u8 *buf, unsigned int len, + enum htc_endpoint_id eid, u16 tag) +{ + packet->pkt_cntxt = context; + packet->buf = buf; + packet->act_len = len; + packet->endpoint = eid; + packet->info.tx.tag = tag; +} + +static inline void htc_rxpkt_reset(struct htc_packet *packet) +{ + packet->buf = packet->buf_start; + packet->act_len = 0; +} + +static inline void set_htc_rxpkt_info(struct htc_packet *packet, void *context, + u8 *buf, unsigned long len, + enum htc_endpoint_id eid) +{ + packet->pkt_cntxt = context; + packet->buf = buf; + packet->buf_start = buf; + packet->buf_len = len; + packet->endpoint = eid; +} + +static inline int get_queue_depth(struct list_head *queue) +{ + struct list_head *tmp_list; + int depth = 0; + + list_for_each(tmp_list, queue) + depth++; + + return depth; +} + +void ath6kl_htc_pipe_attach(struct ath6kl *ar); +void ath6kl_htc_mbox_attach(struct ath6kl *ar); + +#endif diff --git a/drivers/net/wireless/ath/ath6kl/htc_mbox.c b/drivers/net/wireless/ath/ath6kl/htc_mbox.c new file mode 100644 index 0000000..65e5b71 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/htc_mbox.c @@ -0,0 +1,2946 @@ +/* + * Copyright (c) 2007-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "core.h" +#include "hif.h" +#include "debug.h" +#include "hif-ops.h" +#include "trace.h" + +#include + +#define CALC_TXRX_PADDED_LEN(dev, len) (__ALIGN_MASK((len), (dev)->block_mask)) + +static void ath6kl_htc_mbox_cleanup(struct htc_target *target); +static void ath6kl_htc_mbox_stop(struct htc_target *target); +static int ath6kl_htc_mbox_add_rxbuf_multiple(struct htc_target *target, + struct list_head *pkt_queue); +static void ath6kl_htc_set_credit_dist(struct htc_target *target, + struct ath6kl_htc_credit_info *cred_info, + u16 svc_pri_order[], int len); + +/* threshold to re-enable Tx bundling for an AC*/ +#define TX_RESUME_BUNDLE_THRESHOLD 1500 + +/* Functions for Tx credit handling */ +static void ath6kl_credit_deposit(struct ath6kl_htc_credit_info *cred_info, + struct htc_endpoint_credit_dist *ep_dist, + int credits) +{ + ath6kl_dbg(ATH6KL_DBG_CREDIT, "credit deposit ep %d credits %d\n", + ep_dist->endpoint, credits); + + ep_dist->credits += credits; + ep_dist->cred_assngd += credits; + cred_info->cur_free_credits -= credits; +} + +static void ath6kl_credit_init(struct ath6kl_htc_credit_info *cred_info, + struct list_head *ep_list, + int tot_credits) +{ + struct htc_endpoint_credit_dist *cur_ep_dist; + int count; + + ath6kl_dbg(ATH6KL_DBG_CREDIT, "credit init total %d\n", tot_credits); + + cred_info->cur_free_credits = tot_credits; + cred_info->total_avail_credits = tot_credits; + + list_for_each_entry(cur_ep_dist, ep_list, list) { + if (cur_ep_dist->endpoint == ENDPOINT_0) + continue; + + cur_ep_dist->cred_min = cur_ep_dist->cred_per_msg; + + if (tot_credits > 4) { + if ((cur_ep_dist->svc_id == WMI_DATA_BK_SVC) || + (cur_ep_dist->svc_id == WMI_DATA_BE_SVC)) { + ath6kl_credit_deposit(cred_info, + cur_ep_dist, + cur_ep_dist->cred_min); + cur_ep_dist->dist_flags |= HTC_EP_ACTIVE; + } + } + + if (cur_ep_dist->svc_id == WMI_CONTROL_SVC) { + ath6kl_credit_deposit(cred_info, cur_ep_dist, + cur_ep_dist->cred_min); + /* + * Control service is always marked active, it + * never goes inactive EVER. + */ + cur_ep_dist->dist_flags |= HTC_EP_ACTIVE; + } + + /* + * Streams have to be created (explicit | implicit) for all + * kinds of traffic. BE endpoints are also inactive in the + * beginning. When BE traffic starts it creates implicit + * streams that redistributes credits. + * + * Note: all other endpoints have minimums set but are + * initially given NO credits. credits will be distributed + * as traffic activity demands + */ + } + + /* + * For ath6kl_credit_seek function, + * it use list_for_each_entry_reverse to walk around the whole ep list. + * Therefore assign this lowestpri_ep_dist after walk around the ep_list + */ + cred_info->lowestpri_ep_dist = cur_ep_dist->list; + + WARN_ON(cred_info->cur_free_credits <= 0); + + list_for_each_entry(cur_ep_dist, ep_list, list) { + if (cur_ep_dist->endpoint == ENDPOINT_0) + continue; + + if (cur_ep_dist->svc_id == WMI_CONTROL_SVC) + cur_ep_dist->cred_norm = cur_ep_dist->cred_per_msg; + else { + /* + * For the remaining data endpoints, we assume that + * each cred_per_msg are the same. We use a simple + * calculation here, we take the remaining credits + * and determine how many max messages this can + * cover and then set each endpoint's normal value + * equal to 3/4 this amount. + */ + count = (cred_info->cur_free_credits / + cur_ep_dist->cred_per_msg) + * cur_ep_dist->cred_per_msg; + count = (count * 3) >> 2; + count = max(count, cur_ep_dist->cred_per_msg); + cur_ep_dist->cred_norm = count; + + } + + ath6kl_dbg(ATH6KL_DBG_CREDIT, + "credit ep %d svc_id %d credits %d per_msg %d norm %d min %d\n", + cur_ep_dist->endpoint, + cur_ep_dist->svc_id, + cur_ep_dist->credits, + cur_ep_dist->cred_per_msg, + cur_ep_dist->cred_norm, + cur_ep_dist->cred_min); + } +} + +/* initialize and setup credit distribution */ +static int ath6kl_htc_mbox_credit_setup(struct htc_target *htc_target, + struct ath6kl_htc_credit_info *cred_info) +{ + u16 servicepriority[5]; + + memset(cred_info, 0, sizeof(struct ath6kl_htc_credit_info)); + + servicepriority[0] = WMI_CONTROL_SVC; /* highest */ + servicepriority[1] = WMI_DATA_VO_SVC; + servicepriority[2] = WMI_DATA_VI_SVC; + servicepriority[3] = WMI_DATA_BE_SVC; + servicepriority[4] = WMI_DATA_BK_SVC; /* lowest */ + + /* set priority list */ + ath6kl_htc_set_credit_dist(htc_target, cred_info, servicepriority, 5); + + return 0; +} + +/* reduce an ep's credits back to a set limit */ +static void ath6kl_credit_reduce(struct ath6kl_htc_credit_info *cred_info, + struct htc_endpoint_credit_dist *ep_dist, + int limit) +{ + int credits; + + ath6kl_dbg(ATH6KL_DBG_CREDIT, "credit reduce ep %d limit %d\n", + ep_dist->endpoint, limit); + + ep_dist->cred_assngd = limit; + + if (ep_dist->credits <= limit) + return; + + credits = ep_dist->credits - limit; + ep_dist->credits -= credits; + cred_info->cur_free_credits += credits; +} + +static void ath6kl_credit_update(struct ath6kl_htc_credit_info *cred_info, + struct list_head *epdist_list) +{ + struct htc_endpoint_credit_dist *cur_list; + + list_for_each_entry(cur_list, epdist_list, list) { + if (cur_list->endpoint == ENDPOINT_0) + continue; + + if (cur_list->cred_to_dist > 0) { + cur_list->credits += cur_list->cred_to_dist; + cur_list->cred_to_dist = 0; + + if (cur_list->credits > cur_list->cred_assngd) + ath6kl_credit_reduce(cred_info, + cur_list, + cur_list->cred_assngd); + + if (cur_list->credits > cur_list->cred_norm) + ath6kl_credit_reduce(cred_info, cur_list, + cur_list->cred_norm); + + if (!(cur_list->dist_flags & HTC_EP_ACTIVE)) { + if (cur_list->txq_depth == 0) + ath6kl_credit_reduce(cred_info, + cur_list, 0); + } + } + } +} + +/* + * HTC has an endpoint that needs credits, ep_dist is the endpoint in + * question. + */ +static void ath6kl_credit_seek(struct ath6kl_htc_credit_info *cred_info, + struct htc_endpoint_credit_dist *ep_dist) +{ + struct htc_endpoint_credit_dist *curdist_list; + int credits = 0; + int need; + + if (ep_dist->svc_id == WMI_CONTROL_SVC) + goto out; + + if ((ep_dist->svc_id == WMI_DATA_VI_SVC) || + (ep_dist->svc_id == WMI_DATA_VO_SVC)) + if ((ep_dist->cred_assngd >= ep_dist->cred_norm)) + goto out; + + /* + * For all other services, we follow a simple algorithm of: + * + * 1. checking the free pool for credits + * 2. checking lower priority endpoints for credits to take + */ + + credits = min(cred_info->cur_free_credits, ep_dist->seek_cred); + + if (credits >= ep_dist->seek_cred) + goto out; + + /* + * We don't have enough in the free pool, try taking away from + * lower priority services The rule for taking away credits: + * + * 1. Only take from lower priority endpoints + * 2. Only take what is allocated above the minimum (never + * starve an endpoint completely) + * 3. Only take what you need. + */ + + list_for_each_entry_reverse(curdist_list, + &cred_info->lowestpri_ep_dist, + list) { + if (curdist_list == ep_dist) + break; + + need = ep_dist->seek_cred - cred_info->cur_free_credits; + + if ((curdist_list->cred_assngd - need) >= + curdist_list->cred_min) { + /* + * The current one has been allocated more than + * it's minimum and it has enough credits assigned + * above it's minimum to fulfill our need try to + * take away just enough to fulfill our need. + */ + ath6kl_credit_reduce(cred_info, curdist_list, + curdist_list->cred_assngd - need); + + if (cred_info->cur_free_credits >= + ep_dist->seek_cred) + break; + } + + if (curdist_list->endpoint == ENDPOINT_0) + break; + } + + credits = min(cred_info->cur_free_credits, ep_dist->seek_cred); + +out: + /* did we find some credits? */ + if (credits) + ath6kl_credit_deposit(cred_info, ep_dist, credits); + + ep_dist->seek_cred = 0; +} + +/* redistribute credits based on activity change */ +static void ath6kl_credit_redistribute(struct ath6kl_htc_credit_info *info, + struct list_head *ep_dist_list) +{ + struct htc_endpoint_credit_dist *curdist_list; + + list_for_each_entry(curdist_list, ep_dist_list, list) { + if (curdist_list->endpoint == ENDPOINT_0) + continue; + + if ((curdist_list->svc_id == WMI_DATA_BK_SVC) || + (curdist_list->svc_id == WMI_DATA_BE_SVC)) + curdist_list->dist_flags |= HTC_EP_ACTIVE; + + if ((curdist_list->svc_id != WMI_CONTROL_SVC) && + !(curdist_list->dist_flags & HTC_EP_ACTIVE)) { + if (curdist_list->txq_depth == 0) + ath6kl_credit_reduce(info, curdist_list, 0); + else + ath6kl_credit_reduce(info, + curdist_list, + curdist_list->cred_min); + } + } +} + +/* + * + * This function is invoked whenever endpoints require credit + * distributions. A lock is held while this function is invoked, this + * function shall NOT block. The ep_dist_list is a list of distribution + * structures in prioritized order as defined by the call to the + * htc_set_credit_dist() api. + */ +static void ath6kl_credit_distribute(struct ath6kl_htc_credit_info *cred_info, + struct list_head *ep_dist_list, + enum htc_credit_dist_reason reason) +{ + switch (reason) { + case HTC_CREDIT_DIST_SEND_COMPLETE: + ath6kl_credit_update(cred_info, ep_dist_list); + break; + case HTC_CREDIT_DIST_ACTIVITY_CHANGE: + ath6kl_credit_redistribute(cred_info, ep_dist_list); + break; + default: + break; + } + + WARN_ON(cred_info->cur_free_credits > cred_info->total_avail_credits); + WARN_ON(cred_info->cur_free_credits < 0); +} + +static void ath6kl_htc_tx_buf_align(u8 **buf, unsigned long len) +{ + u8 *align_addr; + + if (!IS_ALIGNED((unsigned long) *buf, 4)) { + align_addr = PTR_ALIGN(*buf - 4, 4); + memmove(align_addr, *buf, len); + *buf = align_addr; + } +} + +static void ath6kl_htc_tx_prep_pkt(struct htc_packet *packet, u8 flags, + int ctrl0, int ctrl1) +{ + struct htc_frame_hdr *hdr; + + packet->buf -= HTC_HDR_LENGTH; + hdr = (struct htc_frame_hdr *)packet->buf; + + /* Endianess? */ + put_unaligned((u16)packet->act_len, &hdr->payld_len); + hdr->flags = flags; + hdr->eid = packet->endpoint; + hdr->ctrl[0] = ctrl0; + hdr->ctrl[1] = ctrl1; +} + +static void htc_reclaim_txctrl_buf(struct htc_target *target, + struct htc_packet *pkt) +{ + spin_lock_bh(&target->htc_lock); + list_add_tail(&pkt->list, &target->free_ctrl_txbuf); + spin_unlock_bh(&target->htc_lock); +} + +static struct htc_packet *htc_get_control_buf(struct htc_target *target, + bool tx) +{ + struct htc_packet *packet = NULL; + struct list_head *buf_list; + + buf_list = tx ? &target->free_ctrl_txbuf : &target->free_ctrl_rxbuf; + + spin_lock_bh(&target->htc_lock); + + if (list_empty(buf_list)) { + spin_unlock_bh(&target->htc_lock); + return NULL; + } + + packet = list_first_entry(buf_list, struct htc_packet, list); + list_del(&packet->list); + spin_unlock_bh(&target->htc_lock); + + if (tx) + packet->buf = packet->buf_start + HTC_HDR_LENGTH; + + return packet; +} + +static void htc_tx_comp_update(struct htc_target *target, + struct htc_endpoint *endpoint, + struct htc_packet *packet) +{ + packet->completion = NULL; + packet->buf += HTC_HDR_LENGTH; + + if (!packet->status) + return; + + ath6kl_err("req failed (status:%d, ep:%d, len:%d creds:%d)\n", + packet->status, packet->endpoint, packet->act_len, + packet->info.tx.cred_used); + + /* on failure to submit, reclaim credits for this packet */ + spin_lock_bh(&target->tx_lock); + endpoint->cred_dist.cred_to_dist += + packet->info.tx.cred_used; + endpoint->cred_dist.txq_depth = get_queue_depth(&endpoint->txq); + + ath6kl_dbg(ATH6KL_DBG_HTC, "htc tx ctxt 0x%p dist 0x%p\n", + target->credit_info, &target->cred_dist_list); + + ath6kl_credit_distribute(target->credit_info, + &target->cred_dist_list, + HTC_CREDIT_DIST_SEND_COMPLETE); + + spin_unlock_bh(&target->tx_lock); +} + +static void htc_tx_complete(struct htc_endpoint *endpoint, + struct list_head *txq) +{ + if (list_empty(txq)) + return; + + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc tx complete ep %d pkts %d\n", + endpoint->eid, get_queue_depth(txq)); + + ath6kl_tx_complete(endpoint->target, txq); +} + +static void htc_tx_comp_handler(struct htc_target *target, + struct htc_packet *packet) +{ + struct htc_endpoint *endpoint = &target->endpoint[packet->endpoint]; + struct list_head container; + + ath6kl_dbg(ATH6KL_DBG_HTC, "htc tx complete seqno %d\n", + packet->info.tx.seqno); + + htc_tx_comp_update(target, endpoint, packet); + INIT_LIST_HEAD(&container); + list_add_tail(&packet->list, &container); + /* do completion */ + htc_tx_complete(endpoint, &container); +} + +static void htc_async_tx_scat_complete(struct htc_target *target, + struct hif_scatter_req *scat_req) +{ + struct htc_endpoint *endpoint; + struct htc_packet *packet; + struct list_head tx_compq; + int i; + + INIT_LIST_HEAD(&tx_compq); + + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc tx scat complete len %d entries %d\n", + scat_req->len, scat_req->scat_entries); + + if (scat_req->status) + ath6kl_err("send scatter req failed: %d\n", scat_req->status); + + packet = scat_req->scat_list[0].packet; + endpoint = &target->endpoint[packet->endpoint]; + + /* walk through the scatter list and process */ + for (i = 0; i < scat_req->scat_entries; i++) { + packet = scat_req->scat_list[i].packet; + if (!packet) { + WARN_ON(1); + return; + } + + packet->status = scat_req->status; + htc_tx_comp_update(target, endpoint, packet); + list_add_tail(&packet->list, &tx_compq); + } + + /* free scatter request */ + hif_scatter_req_add(target->dev->ar, scat_req); + + /* complete all packets */ + htc_tx_complete(endpoint, &tx_compq); +} + +static int ath6kl_htc_tx_issue(struct htc_target *target, + struct htc_packet *packet) +{ + int status; + bool sync = false; + u32 padded_len, send_len; + + if (!packet->completion) + sync = true; + + send_len = packet->act_len + HTC_HDR_LENGTH; + + padded_len = CALC_TXRX_PADDED_LEN(target, send_len); + + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc tx issue len %d seqno %d padded_len %d mbox 0x%X %s\n", + send_len, packet->info.tx.seqno, padded_len, + target->dev->ar->mbox_info.htc_addr, + sync ? "sync" : "async"); + + if (sync) { + status = hif_read_write_sync(target->dev->ar, + target->dev->ar->mbox_info.htc_addr, + packet->buf, padded_len, + HIF_WR_SYNC_BLOCK_INC); + + packet->status = status; + packet->buf += HTC_HDR_LENGTH; + } else + status = hif_write_async(target->dev->ar, + target->dev->ar->mbox_info.htc_addr, + packet->buf, padded_len, + HIF_WR_ASYNC_BLOCK_INC, packet); + + trace_ath6kl_htc_tx(status, packet->endpoint, packet->buf, send_len); + + return status; +} + +static int htc_check_credits(struct htc_target *target, + struct htc_endpoint *ep, u8 *flags, + enum htc_endpoint_id eid, unsigned int len, + int *req_cred) +{ + + *req_cred = (len > target->tgt_cred_sz) ? + DIV_ROUND_UP(len, target->tgt_cred_sz) : 1; + + ath6kl_dbg(ATH6KL_DBG_CREDIT, "credit check need %d got %d\n", + *req_cred, ep->cred_dist.credits); + + if (ep->cred_dist.credits < *req_cred) { + if (eid == ENDPOINT_0) + return -EINVAL; + + /* Seek more credits */ + ep->cred_dist.seek_cred = *req_cred - ep->cred_dist.credits; + + ath6kl_credit_seek(target->credit_info, &ep->cred_dist); + + ep->cred_dist.seek_cred = 0; + + if (ep->cred_dist.credits < *req_cred) { + ath6kl_dbg(ATH6KL_DBG_CREDIT, + "credit not found for ep %d\n", + eid); + return -EINVAL; + } + } + + ep->cred_dist.credits -= *req_cred; + ep->ep_st.cred_cosumd += *req_cred; + + /* When we are getting low on credits, ask for more */ + if (ep->cred_dist.credits < ep->cred_dist.cred_per_msg) { + ep->cred_dist.seek_cred = + ep->cred_dist.cred_per_msg - ep->cred_dist.credits; + + ath6kl_credit_seek(target->credit_info, &ep->cred_dist); + + /* see if we were successful in getting more */ + if (ep->cred_dist.credits < ep->cred_dist.cred_per_msg) { + /* tell the target we need credits ASAP! */ + *flags |= HTC_FLAGS_NEED_CREDIT_UPDATE; + ep->ep_st.cred_low_indicate += 1; + ath6kl_dbg(ATH6KL_DBG_CREDIT, + "credit we need credits asap\n"); + } + } + + return 0; +} + +static void ath6kl_htc_tx_pkts_get(struct htc_target *target, + struct htc_endpoint *endpoint, + struct list_head *queue) +{ + int req_cred; + u8 flags; + struct htc_packet *packet; + unsigned int len; + + while (true) { + + flags = 0; + + if (list_empty(&endpoint->txq)) + break; + packet = list_first_entry(&endpoint->txq, struct htc_packet, + list); + + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc tx got packet 0x%p queue depth %d\n", + packet, get_queue_depth(&endpoint->txq)); + + len = CALC_TXRX_PADDED_LEN(target, + packet->act_len + HTC_HDR_LENGTH); + + if (htc_check_credits(target, endpoint, &flags, + packet->endpoint, len, &req_cred)) + break; + + /* now we can fully move onto caller's queue */ + packet = list_first_entry(&endpoint->txq, struct htc_packet, + list); + list_move_tail(&packet->list, queue); + + /* save the number of credits this packet consumed */ + packet->info.tx.cred_used = req_cred; + + /* all TX packets are handled asynchronously */ + packet->completion = htc_tx_comp_handler; + packet->context = target; + endpoint->ep_st.tx_issued += 1; + + /* save send flags */ + packet->info.tx.flags = flags; + packet->info.tx.seqno = endpoint->seqno; + endpoint->seqno++; + } +} + +/* See if the padded tx length falls on a credit boundary */ +static int htc_get_credit_padding(unsigned int cred_sz, int *len, + struct htc_endpoint *ep) +{ + int rem_cred, cred_pad; + + rem_cred = *len % cred_sz; + + /* No padding needed */ + if (!rem_cred) + return 0; + + if (!(ep->conn_flags & HTC_FLGS_TX_BNDL_PAD_EN)) + return -1; + + /* + * The transfer consumes a "partial" credit, this + * packet cannot be bundled unless we add + * additional "dummy" padding (max 255 bytes) to + * consume the entire credit. + */ + cred_pad = *len < cred_sz ? (cred_sz - *len) : rem_cred; + + if ((cred_pad > 0) && (cred_pad <= 255)) + *len += cred_pad; + else + /* The amount of padding is too large, send as non-bundled */ + return -1; + + return cred_pad; +} + +static int ath6kl_htc_tx_setup_scat_list(struct htc_target *target, + struct htc_endpoint *endpoint, + struct hif_scatter_req *scat_req, + int n_scat, + struct list_head *queue) +{ + struct htc_packet *packet; + int i, len, rem_scat, cred_pad; + int status = 0; + u8 flags; + + rem_scat = target->max_tx_bndl_sz; + + for (i = 0; i < n_scat; i++) { + scat_req->scat_list[i].packet = NULL; + + if (list_empty(queue)) + break; + + packet = list_first_entry(queue, struct htc_packet, list); + len = CALC_TXRX_PADDED_LEN(target, + packet->act_len + HTC_HDR_LENGTH); + + cred_pad = htc_get_credit_padding(target->tgt_cred_sz, + &len, endpoint); + if (cred_pad < 0 || rem_scat < len) { + status = -ENOSPC; + break; + } + + rem_scat -= len; + /* now remove it from the queue */ + list_del(&packet->list); + + scat_req->scat_list[i].packet = packet; + /* prepare packet and flag message as part of a send bundle */ + flags = packet->info.tx.flags | HTC_FLAGS_SEND_BUNDLE; + ath6kl_htc_tx_prep_pkt(packet, flags, + cred_pad, packet->info.tx.seqno); + /* Make sure the buffer is 4-byte aligned */ + ath6kl_htc_tx_buf_align(&packet->buf, + packet->act_len + HTC_HDR_LENGTH); + scat_req->scat_list[i].buf = packet->buf; + scat_req->scat_list[i].len = len; + + scat_req->len += len; + scat_req->scat_entries++; + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc tx adding (%d) pkt 0x%p seqno %d len %d remaining %d\n", + i, packet, packet->info.tx.seqno, len, rem_scat); + } + + /* Roll back scatter setup in case of any failure */ + if (scat_req->scat_entries < HTC_MIN_HTC_MSGS_TO_BUNDLE) { + for (i = scat_req->scat_entries - 1; i >= 0; i--) { + packet = scat_req->scat_list[i].packet; + if (packet) { + packet->buf += HTC_HDR_LENGTH; + list_add(&packet->list, queue); + } + } + return -EAGAIN; + } + + return status; +} + +/* + * Drain a queue and send as bundles this function may return without fully + * draining the queue when + * + * 1. scatter resources are exhausted + * 2. a message that will consume a partial credit will stop the + * bundling process early + * 3. we drop below the minimum number of messages for a bundle + */ +static void ath6kl_htc_tx_bundle(struct htc_endpoint *endpoint, + struct list_head *queue, + int *sent_bundle, int *n_bundle_pkts) +{ + struct htc_target *target = endpoint->target; + struct hif_scatter_req *scat_req = NULL; + int n_scat, n_sent_bundle = 0, tot_pkts_bundle = 0, i; + struct htc_packet *packet; + int status; + u32 txb_mask; + u8 ac = WMM_NUM_AC; + + if ((HTC_CTRL_RSVD_SVC != endpoint->svc_id) && + (WMI_CONTROL_SVC != endpoint->svc_id)) + ac = target->dev->ar->ep2ac_map[endpoint->eid]; + + while (true) { + status = 0; + n_scat = get_queue_depth(queue); + n_scat = min(n_scat, target->msg_per_bndl_max); + + if (n_scat < HTC_MIN_HTC_MSGS_TO_BUNDLE) + /* not enough to bundle */ + break; + + scat_req = hif_scatter_req_get(target->dev->ar); + + if (!scat_req) { + /* no scatter resources */ + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc tx no more scatter resources\n"); + break; + } + + if ((ac < WMM_NUM_AC) && (ac != WMM_AC_BK)) { + if (WMM_AC_BE == ac) + /* + * BE, BK have priorities and bit + * positions reversed + */ + txb_mask = (1 << WMM_AC_BK); + else + /* + * any AC with priority lower than + * itself + */ + txb_mask = ((1 << ac) - 1); + + /* + * when the scatter request resources drop below a + * certain threshold, disable Tx bundling for all + * AC's with priority lower than the current requesting + * AC. Otherwise re-enable Tx bundling for them + */ + if (scat_req->scat_q_depth < ATH6KL_SCATTER_REQS) + target->tx_bndl_mask &= ~txb_mask; + else + target->tx_bndl_mask |= txb_mask; + } + + ath6kl_dbg(ATH6KL_DBG_HTC, "htc tx pkts to scatter: %d\n", + n_scat); + + scat_req->len = 0; + scat_req->scat_entries = 0; + + status = ath6kl_htc_tx_setup_scat_list(target, endpoint, + scat_req, n_scat, + queue); + if (status == -EAGAIN) { + hif_scatter_req_add(target->dev->ar, scat_req); + break; + } + + /* send path is always asynchronous */ + scat_req->complete = htc_async_tx_scat_complete; + n_sent_bundle++; + tot_pkts_bundle += scat_req->scat_entries; + + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc tx scatter bytes %d entries %d\n", + scat_req->len, scat_req->scat_entries); + + for (i = 0; i < scat_req->scat_entries; i++) { + packet = scat_req->scat_list[i].packet; + trace_ath6kl_htc_tx(packet->status, packet->endpoint, + packet->buf, packet->act_len); + } + + ath6kl_hif_submit_scat_req(target->dev, scat_req, false); + + if (status) + break; + } + + *sent_bundle = n_sent_bundle; + *n_bundle_pkts = tot_pkts_bundle; + ath6kl_dbg(ATH6KL_DBG_HTC, "htc tx bundle sent %d pkts\n", + n_sent_bundle); + + return; +} + +static void ath6kl_htc_tx_from_queue(struct htc_target *target, + struct htc_endpoint *endpoint) +{ + struct list_head txq; + struct htc_packet *packet; + int bundle_sent; + int n_pkts_bundle; + u8 ac = WMM_NUM_AC; + int status; + + spin_lock_bh(&target->tx_lock); + + endpoint->tx_proc_cnt++; + if (endpoint->tx_proc_cnt > 1) { + endpoint->tx_proc_cnt--; + spin_unlock_bh(&target->tx_lock); + ath6kl_dbg(ATH6KL_DBG_HTC, "htc tx busy\n"); + return; + } + + /* + * drain the endpoint TX queue for transmission as long + * as we have enough credits. + */ + INIT_LIST_HEAD(&txq); + + if ((HTC_CTRL_RSVD_SVC != endpoint->svc_id) && + (WMI_CONTROL_SVC != endpoint->svc_id)) + ac = target->dev->ar->ep2ac_map[endpoint->eid]; + + while (true) { + + if (list_empty(&endpoint->txq)) + break; + + ath6kl_htc_tx_pkts_get(target, endpoint, &txq); + + if (list_empty(&txq)) + break; + + spin_unlock_bh(&target->tx_lock); + + bundle_sent = 0; + n_pkts_bundle = 0; + + while (true) { + /* try to send a bundle on each pass */ + if ((target->tx_bndl_mask) && + (get_queue_depth(&txq) >= + HTC_MIN_HTC_MSGS_TO_BUNDLE)) { + int temp1 = 0, temp2 = 0; + + /* check if bundling is enabled for an AC */ + if (target->tx_bndl_mask & (1 << ac)) { + ath6kl_htc_tx_bundle(endpoint, &txq, + &temp1, &temp2); + bundle_sent += temp1; + n_pkts_bundle += temp2; + } + } + + if (list_empty(&txq)) + break; + + packet = list_first_entry(&txq, struct htc_packet, + list); + list_del(&packet->list); + + ath6kl_htc_tx_prep_pkt(packet, packet->info.tx.flags, + 0, packet->info.tx.seqno); + status = ath6kl_htc_tx_issue(target, packet); + + if (status) { + packet->status = status; + packet->completion(packet->context, packet); + } + } + + spin_lock_bh(&target->tx_lock); + + endpoint->ep_st.tx_bundles += bundle_sent; + endpoint->ep_st.tx_pkt_bundled += n_pkts_bundle; + + /* + * if an AC has bundling disabled and no tx bundling + * has occured continously for a certain number of TX, + * enable tx bundling for this AC + */ + if (!bundle_sent) { + if (!(target->tx_bndl_mask & (1 << ac)) && + (ac < WMM_NUM_AC)) { + if (++target->ac_tx_count[ac] >= + TX_RESUME_BUNDLE_THRESHOLD) { + target->ac_tx_count[ac] = 0; + target->tx_bndl_mask |= (1 << ac); + } + } + } else { + /* tx bundling will reset the counter */ + if (ac < WMM_NUM_AC) + target->ac_tx_count[ac] = 0; + } + } + + endpoint->tx_proc_cnt = 0; + spin_unlock_bh(&target->tx_lock); +} + +static bool ath6kl_htc_tx_try(struct htc_target *target, + struct htc_endpoint *endpoint, + struct htc_packet *tx_pkt) +{ + struct htc_ep_callbacks ep_cb; + int txq_depth; + bool overflow = false; + + ep_cb = endpoint->ep_cb; + + spin_lock_bh(&target->tx_lock); + txq_depth = get_queue_depth(&endpoint->txq); + spin_unlock_bh(&target->tx_lock); + + if (txq_depth >= endpoint->max_txq_depth) + overflow = true; + + if (overflow) + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc tx overflow ep %d depth %d max %d\n", + endpoint->eid, txq_depth, + endpoint->max_txq_depth); + + if (overflow && ep_cb.tx_full) { + if (ep_cb.tx_full(endpoint->target, tx_pkt) == + HTC_SEND_FULL_DROP) { + endpoint->ep_st.tx_dropped += 1; + return false; + } + } + + spin_lock_bh(&target->tx_lock); + list_add_tail(&tx_pkt->list, &endpoint->txq); + spin_unlock_bh(&target->tx_lock); + + ath6kl_htc_tx_from_queue(target, endpoint); + + return true; +} + +static void htc_chk_ep_txq(struct htc_target *target) +{ + struct htc_endpoint *endpoint; + struct htc_endpoint_credit_dist *cred_dist; + + /* + * Run through the credit distribution list to see if there are + * packets queued. NOTE: no locks need to be taken since the + * distribution list is not dynamic (cannot be re-ordered) and we + * are not modifying any state. + */ + list_for_each_entry(cred_dist, &target->cred_dist_list, list) { + endpoint = cred_dist->htc_ep; + + spin_lock_bh(&target->tx_lock); + if (!list_empty(&endpoint->txq)) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc creds ep %d credits %d pkts %d\n", + cred_dist->endpoint, + endpoint->cred_dist.credits, + get_queue_depth(&endpoint->txq)); + spin_unlock_bh(&target->tx_lock); + /* + * Try to start the stalled queue, this list is + * ordered by priority. If there are credits + * available the highest priority queue will get a + * chance to reclaim credits from lower priority + * ones. + */ + ath6kl_htc_tx_from_queue(target, endpoint); + spin_lock_bh(&target->tx_lock); + } + spin_unlock_bh(&target->tx_lock); + } +} + +static int htc_setup_tx_complete(struct htc_target *target) +{ + struct htc_packet *send_pkt = NULL; + int status; + + send_pkt = htc_get_control_buf(target, true); + + if (!send_pkt) + return -ENOMEM; + + if (target->htc_tgt_ver >= HTC_VERSION_2P1) { + struct htc_setup_comp_ext_msg *setup_comp_ext; + u32 flags = 0; + + setup_comp_ext = + (struct htc_setup_comp_ext_msg *)send_pkt->buf; + memset(setup_comp_ext, 0, sizeof(*setup_comp_ext)); + setup_comp_ext->msg_id = + cpu_to_le16(HTC_MSG_SETUP_COMPLETE_EX_ID); + + if (target->msg_per_bndl_max > 0) { + /* Indicate HTC bundling to the target */ + flags |= HTC_SETUP_COMP_FLG_RX_BNDL_EN; + setup_comp_ext->msg_per_rxbndl = + target->msg_per_bndl_max; + } + + memcpy(&setup_comp_ext->flags, &flags, + sizeof(setup_comp_ext->flags)); + set_htc_pkt_info(send_pkt, NULL, (u8 *) setup_comp_ext, + sizeof(struct htc_setup_comp_ext_msg), + ENDPOINT_0, HTC_SERVICE_TX_PACKET_TAG); + + } else { + struct htc_setup_comp_msg *setup_comp; + setup_comp = (struct htc_setup_comp_msg *)send_pkt->buf; + memset(setup_comp, 0, sizeof(struct htc_setup_comp_msg)); + setup_comp->msg_id = cpu_to_le16(HTC_MSG_SETUP_COMPLETE_ID); + set_htc_pkt_info(send_pkt, NULL, (u8 *) setup_comp, + sizeof(struct htc_setup_comp_msg), + ENDPOINT_0, HTC_SERVICE_TX_PACKET_TAG); + } + + /* we want synchronous operation */ + send_pkt->completion = NULL; + ath6kl_htc_tx_prep_pkt(send_pkt, 0, 0, 0); + status = ath6kl_htc_tx_issue(target, send_pkt); + + if (send_pkt != NULL) + htc_reclaim_txctrl_buf(target, send_pkt); + + return status; +} + +static void ath6kl_htc_set_credit_dist(struct htc_target *target, + struct ath6kl_htc_credit_info *credit_info, + u16 srvc_pri_order[], int list_len) +{ + struct htc_endpoint *endpoint; + int i, ep; + + target->credit_info = credit_info; + + list_add_tail(&target->endpoint[ENDPOINT_0].cred_dist.list, + &target->cred_dist_list); + + for (i = 0; i < list_len; i++) { + for (ep = ENDPOINT_1; ep < ENDPOINT_MAX; ep++) { + endpoint = &target->endpoint[ep]; + if (endpoint->svc_id == srvc_pri_order[i]) { + list_add_tail(&endpoint->cred_dist.list, + &target->cred_dist_list); + break; + } + } + if (ep >= ENDPOINT_MAX) { + WARN_ON(1); + return; + } + } +} + +static int ath6kl_htc_mbox_tx(struct htc_target *target, + struct htc_packet *packet) +{ + struct htc_endpoint *endpoint; + struct list_head queue; + + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc tx ep id %d buf 0x%p len %d\n", + packet->endpoint, packet->buf, packet->act_len); + + if (packet->endpoint >= ENDPOINT_MAX) { + WARN_ON(1); + return -EINVAL; + } + + endpoint = &target->endpoint[packet->endpoint]; + + if (!ath6kl_htc_tx_try(target, endpoint, packet)) { + packet->status = (target->htc_flags & HTC_OP_STATE_STOPPING) ? + -ECANCELED : -ENOSPC; + INIT_LIST_HEAD(&queue); + list_add(&packet->list, &queue); + htc_tx_complete(endpoint, &queue); + } + + return 0; +} + +/* flush endpoint TX queue */ +static void ath6kl_htc_mbox_flush_txep(struct htc_target *target, + enum htc_endpoint_id eid, u16 tag) +{ + struct htc_packet *packet, *tmp_pkt; + struct list_head discard_q, container; + struct htc_endpoint *endpoint = &target->endpoint[eid]; + + if (!endpoint->svc_id) { + WARN_ON(1); + return; + } + + /* initialize the discard queue */ + INIT_LIST_HEAD(&discard_q); + + spin_lock_bh(&target->tx_lock); + + list_for_each_entry_safe(packet, tmp_pkt, &endpoint->txq, list) { + if ((tag == HTC_TX_PACKET_TAG_ALL) || + (tag == packet->info.tx.tag)) + list_move_tail(&packet->list, &discard_q); + } + + spin_unlock_bh(&target->tx_lock); + + list_for_each_entry_safe(packet, tmp_pkt, &discard_q, list) { + packet->status = -ECANCELED; + list_del(&packet->list); + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc tx flushing pkt 0x%p len %d ep %d tag 0x%x\n", + packet, packet->act_len, + packet->endpoint, packet->info.tx.tag); + + INIT_LIST_HEAD(&container); + list_add_tail(&packet->list, &container); + htc_tx_complete(endpoint, &container); + } + +} + +static void ath6kl_htc_flush_txep_all(struct htc_target *target) +{ + struct htc_endpoint *endpoint; + int i; + + dump_cred_dist_stats(target); + + for (i = ENDPOINT_0; i < ENDPOINT_MAX; i++) { + endpoint = &target->endpoint[i]; + if (endpoint->svc_id == 0) + /* not in use.. */ + continue; + ath6kl_htc_mbox_flush_txep(target, i, HTC_TX_PACKET_TAG_ALL); + } +} + +static void ath6kl_htc_mbox_activity_changed(struct htc_target *target, + enum htc_endpoint_id eid, + bool active) +{ + struct htc_endpoint *endpoint = &target->endpoint[eid]; + bool dist = false; + + if (endpoint->svc_id == 0) { + WARN_ON(1); + return; + } + + spin_lock_bh(&target->tx_lock); + + if (active) { + if (!(endpoint->cred_dist.dist_flags & HTC_EP_ACTIVE)) { + endpoint->cred_dist.dist_flags |= HTC_EP_ACTIVE; + dist = true; + } + } else { + if (endpoint->cred_dist.dist_flags & HTC_EP_ACTIVE) { + endpoint->cred_dist.dist_flags &= ~HTC_EP_ACTIVE; + dist = true; + } + } + + if (dist) { + endpoint->cred_dist.txq_depth = + get_queue_depth(&endpoint->txq); + + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc tx activity ctxt 0x%p dist 0x%p\n", + target->credit_info, &target->cred_dist_list); + + ath6kl_credit_distribute(target->credit_info, + &target->cred_dist_list, + HTC_CREDIT_DIST_ACTIVITY_CHANGE); + } + + spin_unlock_bh(&target->tx_lock); + + if (dist && !active) + htc_chk_ep_txq(target); +} + +/* HTC Rx */ + +static inline void ath6kl_htc_rx_update_stats(struct htc_endpoint *endpoint, + int n_look_ahds) +{ + endpoint->ep_st.rx_pkts++; + if (n_look_ahds == 1) + endpoint->ep_st.rx_lkahds++; + else if (n_look_ahds > 1) + endpoint->ep_st.rx_bundle_lkahd++; +} + +static inline bool htc_valid_rx_frame_len(struct htc_target *target, + enum htc_endpoint_id eid, int len) +{ + return (eid == target->dev->ar->ctrl_ep) ? + len <= ATH6KL_BUFFER_SIZE : len <= ATH6KL_AMSDU_BUFFER_SIZE; +} + +static int htc_add_rxbuf(struct htc_target *target, struct htc_packet *packet) +{ + struct list_head queue; + + INIT_LIST_HEAD(&queue); + list_add_tail(&packet->list, &queue); + return ath6kl_htc_mbox_add_rxbuf_multiple(target, &queue); +} + +static void htc_reclaim_rxbuf(struct htc_target *target, + struct htc_packet *packet, + struct htc_endpoint *ep) +{ + if (packet->info.rx.rx_flags & HTC_RX_PKT_NO_RECYCLE) { + htc_rxpkt_reset(packet); + packet->status = -ECANCELED; + ep->ep_cb.rx(ep->target, packet); + } else { + htc_rxpkt_reset(packet); + htc_add_rxbuf((void *)(target), packet); + } +} + +static void reclaim_rx_ctrl_buf(struct htc_target *target, + struct htc_packet *packet) +{ + spin_lock_bh(&target->htc_lock); + list_add_tail(&packet->list, &target->free_ctrl_rxbuf); + spin_unlock_bh(&target->htc_lock); +} + +static int ath6kl_htc_rx_packet(struct htc_target *target, + struct htc_packet *packet, + u32 rx_len) +{ + struct ath6kl_device *dev = target->dev; + u32 padded_len; + int status; + + padded_len = CALC_TXRX_PADDED_LEN(target, rx_len); + + if (padded_len > packet->buf_len) { + ath6kl_err("not enough receive space for packet - padlen %d recvlen %d bufferlen %d\n", + padded_len, rx_len, packet->buf_len); + return -ENOMEM; + } + + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc rx 0x%p hdr 0x%x len %d mbox 0x%x\n", + packet, packet->info.rx.exp_hdr, + padded_len, dev->ar->mbox_info.htc_addr); + + status = hif_read_write_sync(dev->ar, + dev->ar->mbox_info.htc_addr, + packet->buf, padded_len, + HIF_RD_SYNC_BLOCK_FIX); + + packet->status = status; + + return status; +} + +/* + * optimization for recv packets, we can indicate a + * "hint" that there are more single-packets to fetch + * on this endpoint. + */ +static void ath6kl_htc_rx_set_indicate(u32 lk_ahd, + struct htc_endpoint *endpoint, + struct htc_packet *packet) +{ + struct htc_frame_hdr *htc_hdr = (struct htc_frame_hdr *)&lk_ahd; + + if (htc_hdr->eid == packet->endpoint) { + if (!list_empty(&endpoint->rx_bufq)) + packet->info.rx.indicat_flags |= + HTC_RX_FLAGS_INDICATE_MORE_PKTS; + } +} + +static void ath6kl_htc_rx_chk_water_mark(struct htc_endpoint *endpoint) +{ + struct htc_ep_callbacks ep_cb = endpoint->ep_cb; + + if (ep_cb.rx_refill_thresh > 0) { + spin_lock_bh(&endpoint->target->rx_lock); + if (get_queue_depth(&endpoint->rx_bufq) + < ep_cb.rx_refill_thresh) { + spin_unlock_bh(&endpoint->target->rx_lock); + ep_cb.rx_refill(endpoint->target, endpoint->eid); + return; + } + spin_unlock_bh(&endpoint->target->rx_lock); + } +} + +/* This function is called with rx_lock held */ +static int ath6kl_htc_rx_setup(struct htc_target *target, + struct htc_endpoint *ep, + u32 *lk_ahds, struct list_head *queue, int n_msg) +{ + struct htc_packet *packet; + /* FIXME: type of lk_ahds can't be right */ + struct htc_frame_hdr *htc_hdr = (struct htc_frame_hdr *)lk_ahds; + struct htc_ep_callbacks ep_cb; + int status = 0, j, full_len; + bool no_recycle; + + full_len = CALC_TXRX_PADDED_LEN(target, + le16_to_cpu(htc_hdr->payld_len) + + sizeof(*htc_hdr)); + + if (!htc_valid_rx_frame_len(target, ep->eid, full_len)) { + ath6kl_warn("Rx buffer requested with invalid length htc_hdr:eid %d, flags 0x%x, len %d\n", + htc_hdr->eid, htc_hdr->flags, + le16_to_cpu(htc_hdr->payld_len)); + return -EINVAL; + } + + ep_cb = ep->ep_cb; + for (j = 0; j < n_msg; j++) { + + /* + * Reset flag, any packets allocated using the + * rx_alloc() API cannot be recycled on + * cleanup,they must be explicitly returned. + */ + no_recycle = false; + + if (ep_cb.rx_allocthresh && + (full_len > ep_cb.rx_alloc_thresh)) { + ep->ep_st.rx_alloc_thresh_hit += 1; + ep->ep_st.rxalloc_thresh_byte += + le16_to_cpu(htc_hdr->payld_len); + + spin_unlock_bh(&target->rx_lock); + no_recycle = true; + + packet = ep_cb.rx_allocthresh(ep->target, ep->eid, + full_len); + spin_lock_bh(&target->rx_lock); + } else { + /* refill handler is being used */ + if (list_empty(&ep->rx_bufq)) { + if (ep_cb.rx_refill) { + spin_unlock_bh(&target->rx_lock); + ep_cb.rx_refill(ep->target, ep->eid); + spin_lock_bh(&target->rx_lock); + } + } + + if (list_empty(&ep->rx_bufq)) + packet = NULL; + else { + packet = list_first_entry(&ep->rx_bufq, + struct htc_packet, list); + list_del(&packet->list); + } + } + + if (!packet) { + target->rx_st_flags |= HTC_RECV_WAIT_BUFFERS; + target->ep_waiting = ep->eid; + return -ENOSPC; + } + + /* clear flags */ + packet->info.rx.rx_flags = 0; + packet->info.rx.indicat_flags = 0; + packet->status = 0; + + if (no_recycle) + /* + * flag that these packets cannot be + * recycled, they have to be returned to + * the user + */ + packet->info.rx.rx_flags |= HTC_RX_PKT_NO_RECYCLE; + + /* Caller needs to free this upon any failure */ + list_add_tail(&packet->list, queue); + + if (target->htc_flags & HTC_OP_STATE_STOPPING) { + status = -ECANCELED; + break; + } + + if (j) { + packet->info.rx.rx_flags |= HTC_RX_PKT_REFRESH_HDR; + packet->info.rx.exp_hdr = 0xFFFFFFFF; + } else + /* set expected look ahead */ + packet->info.rx.exp_hdr = *lk_ahds; + + packet->act_len = le16_to_cpu(htc_hdr->payld_len) + + HTC_HDR_LENGTH; + } + + return status; +} + +static int ath6kl_htc_rx_alloc(struct htc_target *target, + u32 lk_ahds[], int msg, + struct htc_endpoint *endpoint, + struct list_head *queue) +{ + int status = 0; + struct htc_packet *packet, *tmp_pkt; + struct htc_frame_hdr *htc_hdr; + int i, n_msg; + + spin_lock_bh(&target->rx_lock); + + for (i = 0; i < msg; i++) { + + htc_hdr = (struct htc_frame_hdr *)&lk_ahds[i]; + + if (htc_hdr->eid >= ENDPOINT_MAX) { + ath6kl_err("invalid ep in look-ahead: %d\n", + htc_hdr->eid); + status = -ENOMEM; + break; + } + + if (htc_hdr->eid != endpoint->eid) { + ath6kl_err("invalid ep in look-ahead: %d should be : %d (index:%d)\n", + htc_hdr->eid, endpoint->eid, i); + status = -ENOMEM; + break; + } + + if (le16_to_cpu(htc_hdr->payld_len) > HTC_MAX_PAYLOAD_LENGTH) { + ath6kl_err("payload len %d exceeds max htc : %d !\n", + htc_hdr->payld_len, + (u32) HTC_MAX_PAYLOAD_LENGTH); + status = -ENOMEM; + break; + } + + if (endpoint->svc_id == 0) { + ath6kl_err("ep %d is not connected !\n", htc_hdr->eid); + status = -ENOMEM; + break; + } + + if (htc_hdr->flags & HTC_FLG_RX_BNDL_CNT) { + /* + * HTC header indicates that every packet to follow + * has the same padded length so that it can be + * optimally fetched as a full bundle. + */ + n_msg = (htc_hdr->flags & HTC_FLG_RX_BNDL_CNT) >> + HTC_FLG_RX_BNDL_CNT_S; + + /* the count doesn't include the starter frame */ + n_msg++; + if (n_msg > target->msg_per_bndl_max) { + status = -ENOMEM; + break; + } + + endpoint->ep_st.rx_bundle_from_hdr += 1; + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc rx bundle pkts %d\n", + n_msg); + } else + /* HTC header only indicates 1 message to fetch */ + n_msg = 1; + + /* Setup packet buffers for each message */ + status = ath6kl_htc_rx_setup(target, endpoint, &lk_ahds[i], + queue, n_msg); + + /* + * This is due to unavailabilty of buffers to rx entire data. + * Return no error so that free buffers from queue can be used + * to receive partial data. + */ + if (status == -ENOSPC) { + spin_unlock_bh(&target->rx_lock); + return 0; + } + + if (status) + break; + } + + spin_unlock_bh(&target->rx_lock); + + if (status) { + list_for_each_entry_safe(packet, tmp_pkt, queue, list) { + list_del(&packet->list); + htc_reclaim_rxbuf(target, packet, + &target->endpoint[packet->endpoint]); + } + } + + return status; +} + +static void htc_ctrl_rx(struct htc_target *context, struct htc_packet *packets) +{ + if (packets->endpoint != ENDPOINT_0) { + WARN_ON(1); + return; + } + + if (packets->status == -ECANCELED) { + reclaim_rx_ctrl_buf(context, packets); + return; + } + + if (packets->act_len > 0) { + ath6kl_err("htc_ctrl_rx, got message with len:%zu\n", + packets->act_len + HTC_HDR_LENGTH); + + ath6kl_dbg_dump(ATH6KL_DBG_HTC, + "htc rx unexpected endpoint 0 message", "", + packets->buf - HTC_HDR_LENGTH, + packets->act_len + HTC_HDR_LENGTH); + } + + htc_reclaim_rxbuf(context, packets, &context->endpoint[0]); +} + +static void htc_proc_cred_rpt(struct htc_target *target, + struct htc_credit_report *rpt, + int n_entries, + enum htc_endpoint_id from_ep) +{ + struct htc_endpoint *endpoint; + int tot_credits = 0, i; + bool dist = false; + + spin_lock_bh(&target->tx_lock); + + for (i = 0; i < n_entries; i++, rpt++) { + if (rpt->eid >= ENDPOINT_MAX) { + WARN_ON(1); + spin_unlock_bh(&target->tx_lock); + return; + } + + endpoint = &target->endpoint[rpt->eid]; + + ath6kl_dbg(ATH6KL_DBG_CREDIT, + "credit report ep %d credits %d\n", + rpt->eid, rpt->credits); + + endpoint->ep_st.tx_cred_rpt += 1; + endpoint->ep_st.cred_retnd += rpt->credits; + + if (from_ep == rpt->eid) { + /* + * This credit report arrived on the same endpoint + * indicating it arrived in an RX packet. + */ + endpoint->ep_st.cred_from_rx += rpt->credits; + endpoint->ep_st.cred_rpt_from_rx += 1; + } else if (from_ep == ENDPOINT_0) { + /* credit arrived on endpoint 0 as a NULL message */ + endpoint->ep_st.cred_from_ep0 += rpt->credits; + endpoint->ep_st.cred_rpt_ep0 += 1; + } else { + endpoint->ep_st.cred_from_other += rpt->credits; + endpoint->ep_st.cred_rpt_from_other += 1; + } + + if (rpt->eid == ENDPOINT_0) + /* always give endpoint 0 credits back */ + endpoint->cred_dist.credits += rpt->credits; + else { + endpoint->cred_dist.cred_to_dist += rpt->credits; + dist = true; + } + + /* + * Refresh tx depth for distribution function that will + * recover these credits NOTE: this is only valid when + * there are credits to recover! + */ + endpoint->cred_dist.txq_depth = + get_queue_depth(&endpoint->txq); + + tot_credits += rpt->credits; + } + + if (dist) { + /* + * This was a credit return based on a completed send + * operations note, this is done with the lock held + */ + ath6kl_credit_distribute(target->credit_info, + &target->cred_dist_list, + HTC_CREDIT_DIST_SEND_COMPLETE); + } + + spin_unlock_bh(&target->tx_lock); + + if (tot_credits) + htc_chk_ep_txq(target); +} + +static int htc_parse_trailer(struct htc_target *target, + struct htc_record_hdr *record, + u8 *record_buf, u32 *next_lk_ahds, + enum htc_endpoint_id endpoint, + int *n_lk_ahds) +{ + struct htc_bundle_lkahd_rpt *bundle_lkahd_rpt; + struct htc_lookahead_report *lk_ahd; + int len; + + switch (record->rec_id) { + case HTC_RECORD_CREDITS: + len = record->len / sizeof(struct htc_credit_report); + if (!len) { + WARN_ON(1); + return -EINVAL; + } + + htc_proc_cred_rpt(target, + (struct htc_credit_report *) record_buf, + len, endpoint); + break; + case HTC_RECORD_LOOKAHEAD: + len = record->len / sizeof(*lk_ahd); + if (!len) { + WARN_ON(1); + return -EINVAL; + } + + lk_ahd = (struct htc_lookahead_report *) record_buf; + if ((lk_ahd->pre_valid == ((~lk_ahd->post_valid) & 0xFF)) && + next_lk_ahds) { + + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc rx lk_ahd found pre_valid 0x%x post_valid 0x%x\n", + lk_ahd->pre_valid, lk_ahd->post_valid); + + /* look ahead bytes are valid, copy them over */ + memcpy((u8 *)&next_lk_ahds[0], lk_ahd->lk_ahd, 4); + + ath6kl_dbg_dump(ATH6KL_DBG_HTC, + "htc rx next look ahead", + "", next_lk_ahds, 4); + + *n_lk_ahds = 1; + } + break; + case HTC_RECORD_LOOKAHEAD_BUNDLE: + len = record->len / sizeof(*bundle_lkahd_rpt); + if (!len || (len > HTC_HOST_MAX_MSG_PER_BUNDLE)) { + WARN_ON(1); + return -EINVAL; + } + + if (next_lk_ahds) { + int i; + + bundle_lkahd_rpt = + (struct htc_bundle_lkahd_rpt *) record_buf; + + ath6kl_dbg_dump(ATH6KL_DBG_HTC, "htc rx bundle lk_ahd", + "", record_buf, record->len); + + for (i = 0; i < len; i++) { + memcpy((u8 *)&next_lk_ahds[i], + bundle_lkahd_rpt->lk_ahd, 4); + bundle_lkahd_rpt++; + } + + *n_lk_ahds = i; + } + break; + default: + ath6kl_err("unhandled record: id:%d len:%d\n", + record->rec_id, record->len); + break; + } + + return 0; + +} + +static int htc_proc_trailer(struct htc_target *target, + u8 *buf, int len, u32 *next_lk_ahds, + int *n_lk_ahds, enum htc_endpoint_id endpoint) +{ + struct htc_record_hdr *record; + int orig_len; + int status; + u8 *record_buf; + u8 *orig_buf; + + ath6kl_dbg(ATH6KL_DBG_HTC, "htc rx trailer len %d\n", len); + ath6kl_dbg_dump(ATH6KL_DBG_HTC, NULL, "", buf, len); + + orig_buf = buf; + orig_len = len; + status = 0; + + while (len > 0) { + + if (len < sizeof(struct htc_record_hdr)) { + status = -ENOMEM; + break; + } + /* these are byte aligned structs */ + record = (struct htc_record_hdr *) buf; + len -= sizeof(struct htc_record_hdr); + buf += sizeof(struct htc_record_hdr); + + if (record->len > len) { + ath6kl_err("invalid record len: %d (id:%d) buf has: %d bytes left\n", + record->len, record->rec_id, len); + status = -ENOMEM; + break; + } + record_buf = buf; + + status = htc_parse_trailer(target, record, record_buf, + next_lk_ahds, endpoint, n_lk_ahds); + + if (status) + break; + + /* advance buffer past this record for next time around */ + buf += record->len; + len -= record->len; + } + + if (status) + ath6kl_dbg_dump(ATH6KL_DBG_HTC, "htc rx bad trailer", + "", orig_buf, orig_len); + + return status; +} + +static int ath6kl_htc_rx_process_hdr(struct htc_target *target, + struct htc_packet *packet, + u32 *next_lkahds, int *n_lkahds) +{ + int status = 0; + u16 payload_len; + u32 lk_ahd; + struct htc_frame_hdr *htc_hdr = (struct htc_frame_hdr *)packet->buf; + + if (n_lkahds != NULL) + *n_lkahds = 0; + + /* + * NOTE: we cannot assume the alignment of buf, so we use the safe + * macros to retrieve 16 bit fields. + */ + payload_len = le16_to_cpu(get_unaligned(&htc_hdr->payld_len)); + + memcpy((u8 *)&lk_ahd, packet->buf, sizeof(lk_ahd)); + + if (packet->info.rx.rx_flags & HTC_RX_PKT_REFRESH_HDR) { + /* + * Refresh the expected header and the actual length as it + * was unknown when this packet was grabbed as part of the + * bundle. + */ + packet->info.rx.exp_hdr = lk_ahd; + packet->act_len = payload_len + HTC_HDR_LENGTH; + + /* validate the actual header that was refreshed */ + if (packet->act_len > packet->buf_len) { + ath6kl_err("refreshed hdr payload len (%d) in bundled recv is invalid (hdr: 0x%X)\n", + payload_len, lk_ahd); + /* + * Limit this to max buffer just to print out some + * of the buffer. + */ + packet->act_len = min(packet->act_len, packet->buf_len); + status = -ENOMEM; + goto fail_rx; + } + + if (packet->endpoint != htc_hdr->eid) { + ath6kl_err("refreshed hdr ep (%d) does not match expected ep (%d)\n", + htc_hdr->eid, packet->endpoint); + status = -ENOMEM; + goto fail_rx; + } + } + + if (lk_ahd != packet->info.rx.exp_hdr) { + ath6kl_err("%s(): lk_ahd mismatch! (pPkt:0x%p flags:0x%X)\n", + __func__, packet, packet->info.rx.rx_flags); + ath6kl_dbg_dump(ATH6KL_DBG_HTC, "htc rx expected lk_ahd", + "", &packet->info.rx.exp_hdr, 4); + ath6kl_dbg_dump(ATH6KL_DBG_HTC, "htc rx current header", + "", (u8 *)&lk_ahd, sizeof(lk_ahd)); + status = -ENOMEM; + goto fail_rx; + } + + if (htc_hdr->flags & HTC_FLG_RX_TRAILER) { + if (htc_hdr->ctrl[0] < sizeof(struct htc_record_hdr) || + htc_hdr->ctrl[0] > payload_len) { + ath6kl_err("%s(): invalid hdr (payload len should be :%d, CB[0] is:%d)\n", + __func__, payload_len, htc_hdr->ctrl[0]); + status = -ENOMEM; + goto fail_rx; + } + + if (packet->info.rx.rx_flags & HTC_RX_PKT_IGNORE_LOOKAHEAD) { + next_lkahds = NULL; + n_lkahds = NULL; + } + + status = htc_proc_trailer(target, packet->buf + HTC_HDR_LENGTH + + payload_len - htc_hdr->ctrl[0], + htc_hdr->ctrl[0], next_lkahds, + n_lkahds, packet->endpoint); + + if (status) + goto fail_rx; + + packet->act_len -= htc_hdr->ctrl[0]; + } + + packet->buf += HTC_HDR_LENGTH; + packet->act_len -= HTC_HDR_LENGTH; + +fail_rx: + if (status) + ath6kl_dbg_dump(ATH6KL_DBG_HTC, "htc rx bad packet", + "", packet->buf, packet->act_len); + + return status; +} + +static void ath6kl_htc_rx_complete(struct htc_endpoint *endpoint, + struct htc_packet *packet) +{ + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc rx complete ep %d packet 0x%p\n", + endpoint->eid, packet); + + endpoint->ep_cb.rx(endpoint->target, packet); +} + +static int ath6kl_htc_rx_bundle(struct htc_target *target, + struct list_head *rxq, + struct list_head *sync_compq, + int *n_pkt_fetched, bool part_bundle) +{ + struct hif_scatter_req *scat_req; + struct htc_packet *packet; + int rem_space = target->max_rx_bndl_sz; + int n_scat_pkt, status = 0, i, len; + + n_scat_pkt = get_queue_depth(rxq); + n_scat_pkt = min(n_scat_pkt, target->msg_per_bndl_max); + + if ((get_queue_depth(rxq) - n_scat_pkt) > 0) { + /* + * We were forced to split this bundle receive operation + * all packets in this partial bundle must have their + * lookaheads ignored. + */ + part_bundle = true; + + /* + * This would only happen if the target ignored our max + * bundle limit. + */ + ath6kl_warn("%s(): partial bundle detected num:%d , %d\n", + __func__, get_queue_depth(rxq), n_scat_pkt); + } + + len = 0; + + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc rx bundle depth %d pkts %d\n", + get_queue_depth(rxq), n_scat_pkt); + + scat_req = hif_scatter_req_get(target->dev->ar); + + if (scat_req == NULL) + goto fail_rx_pkt; + + for (i = 0; i < n_scat_pkt; i++) { + int pad_len; + + packet = list_first_entry(rxq, struct htc_packet, list); + list_del(&packet->list); + + pad_len = CALC_TXRX_PADDED_LEN(target, + packet->act_len); + + if ((rem_space - pad_len) < 0) { + list_add(&packet->list, rxq); + break; + } + + rem_space -= pad_len; + + if (part_bundle || (i < (n_scat_pkt - 1))) + /* + * Packet 0..n-1 cannot be checked for look-aheads + * since we are fetching a bundle the last packet + * however can have it's lookahead used + */ + packet->info.rx.rx_flags |= + HTC_RX_PKT_IGNORE_LOOKAHEAD; + + /* NOTE: 1 HTC packet per scatter entry */ + scat_req->scat_list[i].buf = packet->buf; + scat_req->scat_list[i].len = pad_len; + + packet->info.rx.rx_flags |= HTC_RX_PKT_PART_OF_BUNDLE; + + list_add_tail(&packet->list, sync_compq); + + WARN_ON(!scat_req->scat_list[i].len); + len += scat_req->scat_list[i].len; + } + + scat_req->len = len; + scat_req->scat_entries = i; + + status = ath6kl_hif_submit_scat_req(target->dev, scat_req, true); + + if (!status) + *n_pkt_fetched = i; + + /* free scatter request */ + hif_scatter_req_add(target->dev->ar, scat_req); + +fail_rx_pkt: + + return status; +} + +static int ath6kl_htc_rx_process_packets(struct htc_target *target, + struct list_head *comp_pktq, + u32 lk_ahds[], + int *n_lk_ahd) +{ + struct htc_packet *packet, *tmp_pkt; + struct htc_endpoint *ep; + int status = 0; + + list_for_each_entry_safe(packet, tmp_pkt, comp_pktq, list) { + ep = &target->endpoint[packet->endpoint]; + + trace_ath6kl_htc_rx(packet->status, packet->endpoint, + packet->buf, packet->act_len); + + /* process header for each of the recv packet */ + status = ath6kl_htc_rx_process_hdr(target, packet, lk_ahds, + n_lk_ahd); + if (status) + return status; + + list_del(&packet->list); + + if (list_empty(comp_pktq)) { + /* + * Last packet's more packet flag is set + * based on the lookahead. + */ + if (*n_lk_ahd > 0) + ath6kl_htc_rx_set_indicate(lk_ahds[0], + ep, packet); + } else + /* + * Packets in a bundle automatically have + * this flag set. + */ + packet->info.rx.indicat_flags |= + HTC_RX_FLAGS_INDICATE_MORE_PKTS; + + ath6kl_htc_rx_update_stats(ep, *n_lk_ahd); + + if (packet->info.rx.rx_flags & HTC_RX_PKT_PART_OF_BUNDLE) + ep->ep_st.rx_bundl += 1; + + ath6kl_htc_rx_complete(ep, packet); + } + + return status; +} + +static int ath6kl_htc_rx_fetch(struct htc_target *target, + struct list_head *rx_pktq, + struct list_head *comp_pktq) +{ + int fetched_pkts; + bool part_bundle = false; + int status = 0; + struct list_head tmp_rxq; + struct htc_packet *packet, *tmp_pkt; + + /* now go fetch the list of HTC packets */ + while (!list_empty(rx_pktq)) { + fetched_pkts = 0; + + INIT_LIST_HEAD(&tmp_rxq); + + if (target->rx_bndl_enable && (get_queue_depth(rx_pktq) > 1)) { + /* + * There are enough packets to attempt a + * bundle transfer and recv bundling is + * allowed. + */ + status = ath6kl_htc_rx_bundle(target, rx_pktq, + &tmp_rxq, + &fetched_pkts, + part_bundle); + if (status) + goto fail_rx; + + if (!list_empty(rx_pktq)) + part_bundle = true; + + list_splice_tail_init(&tmp_rxq, comp_pktq); + } + + if (!fetched_pkts) { + + packet = list_first_entry(rx_pktq, struct htc_packet, + list); + + /* fully synchronous */ + packet->completion = NULL; + + if (!list_is_singular(rx_pktq)) + /* + * look_aheads in all packet + * except the last one in the + * bundle must be ignored + */ + packet->info.rx.rx_flags |= + HTC_RX_PKT_IGNORE_LOOKAHEAD; + + /* go fetch the packet */ + status = ath6kl_htc_rx_packet(target, packet, + packet->act_len); + + list_move_tail(&packet->list, &tmp_rxq); + + if (status) + goto fail_rx; + + list_splice_tail_init(&tmp_rxq, comp_pktq); + } + } + + return 0; + +fail_rx: + + /* + * Cleanup any packets we allocated but didn't use to + * actually fetch any packets. + */ + + list_for_each_entry_safe(packet, tmp_pkt, rx_pktq, list) { + list_del(&packet->list); + htc_reclaim_rxbuf(target, packet, + &target->endpoint[packet->endpoint]); + } + + list_for_each_entry_safe(packet, tmp_pkt, &tmp_rxq, list) { + list_del(&packet->list); + htc_reclaim_rxbuf(target, packet, + &target->endpoint[packet->endpoint]); + } + + return status; +} + +int ath6kl_htc_rxmsg_pending_handler(struct htc_target *target, + u32 msg_look_ahead, int *num_pkts) +{ + struct htc_packet *packets, *tmp_pkt; + struct htc_endpoint *endpoint; + struct list_head rx_pktq, comp_pktq; + int status = 0; + u32 look_aheads[HTC_HOST_MAX_MSG_PER_BUNDLE]; + int num_look_ahead = 1; + enum htc_endpoint_id id; + int n_fetched = 0; + + INIT_LIST_HEAD(&comp_pktq); + *num_pkts = 0; + + /* + * On first entry copy the look_aheads into our temp array for + * processing + */ + look_aheads[0] = msg_look_ahead; + + while (true) { + + /* + * First lookahead sets the expected endpoint IDs for all + * packets in a bundle. + */ + id = ((struct htc_frame_hdr *)&look_aheads[0])->eid; + endpoint = &target->endpoint[id]; + + if (id >= ENDPOINT_MAX) { + ath6kl_err("MsgPend, invalid endpoint in look-ahead: %d\n", + id); + status = -ENOMEM; + break; + } + + INIT_LIST_HEAD(&rx_pktq); + INIT_LIST_HEAD(&comp_pktq); + + /* + * Try to allocate as many HTC RX packets indicated by the + * look_aheads. + */ + status = ath6kl_htc_rx_alloc(target, look_aheads, + num_look_ahead, endpoint, + &rx_pktq); + if (status) + break; + + if (get_queue_depth(&rx_pktq) >= 2) + /* + * A recv bundle was detected, force IRQ status + * re-check again + */ + target->chk_irq_status_cnt = 1; + + n_fetched += get_queue_depth(&rx_pktq); + + num_look_ahead = 0; + + status = ath6kl_htc_rx_fetch(target, &rx_pktq, &comp_pktq); + + if (!status) + ath6kl_htc_rx_chk_water_mark(endpoint); + + /* Process fetched packets */ + status = ath6kl_htc_rx_process_packets(target, &comp_pktq, + look_aheads, + &num_look_ahead); + + if (!num_look_ahead || status) + break; + + /* + * For SYNCH processing, if we get here, we are running + * through the loop again due to a detected lookahead. Set + * flag that we should re-check IRQ status registers again + * before leaving IRQ processing, this can net better + * performance in high throughput situations. + */ + target->chk_irq_status_cnt = 1; + } + + if (status) { + ath6kl_err("failed to get pending recv messages: %d\n", + status); + + /* cleanup any packets in sync completion queue */ + list_for_each_entry_safe(packets, tmp_pkt, &comp_pktq, list) { + list_del(&packets->list); + htc_reclaim_rxbuf(target, packets, + &target->endpoint[packets->endpoint]); + } + + if (target->htc_flags & HTC_OP_STATE_STOPPING) { + ath6kl_warn("host is going to stop blocking receiver for htc_stop\n"); + ath6kl_hif_rx_control(target->dev, false); + } + } + + /* + * Before leaving, check to see if host ran out of buffers and + * needs to stop the receiver. + */ + if (target->rx_st_flags & HTC_RECV_WAIT_BUFFERS) { + ath6kl_warn("host has no rx buffers blocking receiver to prevent overrun\n"); + ath6kl_hif_rx_control(target->dev, false); + } + *num_pkts = n_fetched; + + return status; +} + +/* + * Synchronously wait for a control message from the target, + * This function is used at initialization time ONLY. At init messages + * on ENDPOINT 0 are expected. + */ +static struct htc_packet *htc_wait_for_ctrl_msg(struct htc_target *target) +{ + struct htc_packet *packet = NULL; + struct htc_frame_hdr *htc_hdr; + u32 look_ahead; + + if (ath6kl_hif_poll_mboxmsg_rx(target->dev, &look_ahead, + HTC_TARGET_RESPONSE_TIMEOUT)) + return NULL; + + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc rx wait ctrl look_ahead 0x%X\n", look_ahead); + + htc_hdr = (struct htc_frame_hdr *)&look_ahead; + + if (htc_hdr->eid != ENDPOINT_0) + return NULL; + + packet = htc_get_control_buf(target, false); + + if (!packet) + return NULL; + + packet->info.rx.rx_flags = 0; + packet->info.rx.exp_hdr = look_ahead; + packet->act_len = le16_to_cpu(htc_hdr->payld_len) + HTC_HDR_LENGTH; + + if (packet->act_len > packet->buf_len) + goto fail_ctrl_rx; + + /* we want synchronous operation */ + packet->completion = NULL; + + /* get the message from the device, this will block */ + if (ath6kl_htc_rx_packet(target, packet, packet->act_len)) + goto fail_ctrl_rx; + + trace_ath6kl_htc_rx(packet->status, packet->endpoint, + packet->buf, packet->act_len); + + /* process receive header */ + packet->status = ath6kl_htc_rx_process_hdr(target, packet, NULL, NULL); + + if (packet->status) { + ath6kl_err("htc_wait_for_ctrl_msg, ath6kl_htc_rx_process_hdr failed (status = %d)\n", + packet->status); + goto fail_ctrl_rx; + } + + return packet; + +fail_ctrl_rx: + if (packet != NULL) { + htc_rxpkt_reset(packet); + reclaim_rx_ctrl_buf(target, packet); + } + + return NULL; +} + +static int ath6kl_htc_mbox_add_rxbuf_multiple(struct htc_target *target, + struct list_head *pkt_queue) +{ + struct htc_endpoint *endpoint; + struct htc_packet *first_pkt; + bool rx_unblock = false; + int status = 0, depth; + + if (list_empty(pkt_queue)) + return -ENOMEM; + + first_pkt = list_first_entry(pkt_queue, struct htc_packet, list); + + if (first_pkt->endpoint >= ENDPOINT_MAX) + return status; + + depth = get_queue_depth(pkt_queue); + + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc rx add multiple ep id %d cnt %d len %d\n", + first_pkt->endpoint, depth, first_pkt->buf_len); + + endpoint = &target->endpoint[first_pkt->endpoint]; + + if (target->htc_flags & HTC_OP_STATE_STOPPING) { + struct htc_packet *packet, *tmp_pkt; + + /* walk through queue and mark each one canceled */ + list_for_each_entry_safe(packet, tmp_pkt, pkt_queue, list) { + packet->status = -ECANCELED; + list_del(&packet->list); + ath6kl_htc_rx_complete(endpoint, packet); + } + + return status; + } + + spin_lock_bh(&target->rx_lock); + + list_splice_tail_init(pkt_queue, &endpoint->rx_bufq); + + /* check if we are blocked waiting for a new buffer */ + if (target->rx_st_flags & HTC_RECV_WAIT_BUFFERS) { + if (target->ep_waiting == first_pkt->endpoint) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc rx blocked on ep %d, unblocking\n", + target->ep_waiting); + target->rx_st_flags &= ~HTC_RECV_WAIT_BUFFERS; + target->ep_waiting = ENDPOINT_MAX; + rx_unblock = true; + } + } + + spin_unlock_bh(&target->rx_lock); + + if (rx_unblock && !(target->htc_flags & HTC_OP_STATE_STOPPING)) + /* TODO : implement a buffer threshold count? */ + ath6kl_hif_rx_control(target->dev, true); + + return status; +} + +static void ath6kl_htc_mbox_flush_rx_buf(struct htc_target *target) +{ + struct htc_endpoint *endpoint; + struct htc_packet *packet, *tmp_pkt; + int i; + + for (i = ENDPOINT_0; i < ENDPOINT_MAX; i++) { + endpoint = &target->endpoint[i]; + if (!endpoint->svc_id) + /* not in use.. */ + continue; + + spin_lock_bh(&target->rx_lock); + list_for_each_entry_safe(packet, tmp_pkt, + &endpoint->rx_bufq, list) { + list_del(&packet->list); + spin_unlock_bh(&target->rx_lock); + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc rx flush pkt 0x%p len %d ep %d\n", + packet, packet->buf_len, + packet->endpoint); + /* + * packets in rx_bufq of endpoint 0 have originally + * been queued from target->free_ctrl_rxbuf where + * packet and packet->buf_start are allocated + * separately using kmalloc(). For other endpoint + * rx_bufq, it is allocated as skb where packet is + * skb->head. Take care of this difference while freeing + * the memory. + */ + if (packet->endpoint == ENDPOINT_0) { + kfree(packet->buf_start); + kfree(packet); + } else { + dev_kfree_skb(packet->pkt_cntxt); + } + spin_lock_bh(&target->rx_lock); + } + spin_unlock_bh(&target->rx_lock); + } +} + +static int ath6kl_htc_mbox_conn_service(struct htc_target *target, + struct htc_service_connect_req *conn_req, + struct htc_service_connect_resp *conn_resp) +{ + struct htc_packet *rx_pkt = NULL; + struct htc_packet *tx_pkt = NULL; + struct htc_conn_service_resp *resp_msg; + struct htc_conn_service_msg *conn_msg; + struct htc_endpoint *endpoint; + enum htc_endpoint_id assigned_ep = ENDPOINT_MAX; + unsigned int max_msg_sz = 0; + int status = 0; + u16 msg_id; + + ath6kl_dbg(ATH6KL_DBG_HTC, + "htc connect service target 0x%p service id 0x%x\n", + target, conn_req->svc_id); + + if (conn_req->svc_id == HTC_CTRL_RSVD_SVC) { + /* special case for pseudo control service */ + assigned_ep = ENDPOINT_0; + max_msg_sz = HTC_MAX_CTRL_MSG_LEN; + } else { + /* allocate a packet to send to the target */ + tx_pkt = htc_get_control_buf(target, true); + + if (!tx_pkt) + return -ENOMEM; + + conn_msg = (struct htc_conn_service_msg *)tx_pkt->buf; + memset(conn_msg, 0, sizeof(*conn_msg)); + conn_msg->msg_id = cpu_to_le16(HTC_MSG_CONN_SVC_ID); + conn_msg->svc_id = cpu_to_le16(conn_req->svc_id); + conn_msg->conn_flags = cpu_to_le16(conn_req->conn_flags); + + set_htc_pkt_info(tx_pkt, NULL, (u8 *) conn_msg, + sizeof(*conn_msg) + conn_msg->svc_meta_len, + ENDPOINT_0, HTC_SERVICE_TX_PACKET_TAG); + + /* we want synchronous operation */ + tx_pkt->completion = NULL; + ath6kl_htc_tx_prep_pkt(tx_pkt, 0, 0, 0); + status = ath6kl_htc_tx_issue(target, tx_pkt); + + if (status) + goto fail_tx; + + /* wait for response */ + rx_pkt = htc_wait_for_ctrl_msg(target); + + if (!rx_pkt) { + status = -ENOMEM; + goto fail_tx; + } + + resp_msg = (struct htc_conn_service_resp *)rx_pkt->buf; + msg_id = le16_to_cpu(resp_msg->msg_id); + + if ((msg_id != HTC_MSG_CONN_SVC_RESP_ID) || + (rx_pkt->act_len < sizeof(*resp_msg))) { + status = -ENOMEM; + goto fail_tx; + } + + conn_resp->resp_code = resp_msg->status; + /* check response status */ + if (resp_msg->status != HTC_SERVICE_SUCCESS) { + ath6kl_err("target failed service 0x%X connect request (status:%d)\n", + resp_msg->svc_id, resp_msg->status); + status = -ENOMEM; + goto fail_tx; + } + + assigned_ep = (enum htc_endpoint_id)resp_msg->eid; + max_msg_sz = le16_to_cpu(resp_msg->max_msg_sz); + } + + if (WARN_ON_ONCE(assigned_ep == ENDPOINT_UNUSED || + assigned_ep >= ENDPOINT_MAX || !max_msg_sz)) { + status = -ENOMEM; + goto fail_tx; + } + + endpoint = &target->endpoint[assigned_ep]; + endpoint->eid = assigned_ep; + if (endpoint->svc_id) { + status = -ENOMEM; + goto fail_tx; + } + + /* return assigned endpoint to caller */ + conn_resp->endpoint = assigned_ep; + conn_resp->len_max = max_msg_sz; + + /* setup the endpoint */ + + /* this marks the endpoint in use */ + endpoint->svc_id = conn_req->svc_id; + + endpoint->max_txq_depth = conn_req->max_txq_depth; + endpoint->len_max = max_msg_sz; + endpoint->ep_cb = conn_req->ep_cb; + endpoint->cred_dist.svc_id = conn_req->svc_id; + endpoint->cred_dist.htc_ep = endpoint; + endpoint->cred_dist.endpoint = assigned_ep; + endpoint->cred_dist.cred_sz = target->tgt_cred_sz; + + switch (endpoint->svc_id) { + case WMI_DATA_BK_SVC: + endpoint->tx_drop_packet_threshold = MAX_DEF_COOKIE_NUM / 3; + break; + default: + endpoint->tx_drop_packet_threshold = MAX_HI_COOKIE_NUM; + break; + } + + if (conn_req->max_rxmsg_sz) { + /* + * Override cred_per_msg calculation, this optimizes + * the credit-low indications since the host will actually + * issue smaller messages in the Send path. + */ + if (conn_req->max_rxmsg_sz > max_msg_sz) { + status = -ENOMEM; + goto fail_tx; + } + endpoint->cred_dist.cred_per_msg = + conn_req->max_rxmsg_sz / target->tgt_cred_sz; + } else + endpoint->cred_dist.cred_per_msg = + max_msg_sz / target->tgt_cred_sz; + + if (!endpoint->cred_dist.cred_per_msg) + endpoint->cred_dist.cred_per_msg = 1; + + /* save local connection flags */ + endpoint->conn_flags = conn_req->flags; + +fail_tx: + if (tx_pkt) + htc_reclaim_txctrl_buf(target, tx_pkt); + + if (rx_pkt) { + htc_rxpkt_reset(rx_pkt); + reclaim_rx_ctrl_buf(target, rx_pkt); + } + + return status; +} + +static void reset_ep_state(struct htc_target *target) +{ + struct htc_endpoint *endpoint; + int i; + + for (i = ENDPOINT_0; i < ENDPOINT_MAX; i++) { + endpoint = &target->endpoint[i]; + memset(&endpoint->cred_dist, 0, sizeof(endpoint->cred_dist)); + endpoint->svc_id = 0; + endpoint->len_max = 0; + endpoint->max_txq_depth = 0; + memset(&endpoint->ep_st, 0, + sizeof(endpoint->ep_st)); + INIT_LIST_HEAD(&endpoint->rx_bufq); + INIT_LIST_HEAD(&endpoint->txq); + endpoint->target = target; + } + + /* reset distribution list */ + /* FIXME: free existing entries */ + INIT_LIST_HEAD(&target->cred_dist_list); +} + +static int ath6kl_htc_mbox_get_rxbuf_num(struct htc_target *target, + enum htc_endpoint_id endpoint) +{ + int num; + + spin_lock_bh(&target->rx_lock); + num = get_queue_depth(&(target->endpoint[endpoint].rx_bufq)); + spin_unlock_bh(&target->rx_lock); + return num; +} + +static void htc_setup_msg_bndl(struct htc_target *target) +{ + /* limit what HTC can handle */ + target->msg_per_bndl_max = min(HTC_HOST_MAX_MSG_PER_BUNDLE, + target->msg_per_bndl_max); + + if (ath6kl_hif_enable_scatter(target->dev->ar)) { + target->msg_per_bndl_max = 0; + return; + } + + /* limit bundle what the device layer can handle */ + target->msg_per_bndl_max = min(target->max_scat_entries, + target->msg_per_bndl_max); + + ath6kl_dbg(ATH6KL_DBG_BOOT, + "htc bundling allowed msg_per_bndl_max %d\n", + target->msg_per_bndl_max); + + /* Max rx bundle size is limited by the max tx bundle size */ + target->max_rx_bndl_sz = target->max_xfer_szper_scatreq; + /* Max tx bundle size if limited by the extended mbox address range */ + target->max_tx_bndl_sz = min(HIF_MBOX0_EXT_WIDTH, + target->max_xfer_szper_scatreq); + + ath6kl_dbg(ATH6KL_DBG_BOOT, "htc max_rx_bndl_sz %d max_tx_bndl_sz %d\n", + target->max_rx_bndl_sz, target->max_tx_bndl_sz); + + if (target->max_tx_bndl_sz) + /* tx_bndl_mask is enabled per AC, each has 1 bit */ + target->tx_bndl_mask = (1 << WMM_NUM_AC) - 1; + + if (target->max_rx_bndl_sz) + target->rx_bndl_enable = true; + + if ((target->tgt_cred_sz % target->block_sz) != 0) { + ath6kl_warn("credit size: %d is not block aligned! Disabling send bundling\n", + target->tgt_cred_sz); + + /* + * Disallow send bundling since the credit size is + * not aligned to a block size the I/O block + * padding will spill into the next credit buffer + * which is fatal. + */ + target->tx_bndl_mask = 0; + } +} + +static int ath6kl_htc_mbox_wait_target(struct htc_target *target) +{ + struct htc_packet *packet = NULL; + struct htc_ready_ext_msg *rdy_msg; + struct htc_service_connect_req connect; + struct htc_service_connect_resp resp; + int status; + + /* we should be getting 1 control message that the target is ready */ + packet = htc_wait_for_ctrl_msg(target); + + if (!packet) + return -ENOMEM; + + /* we controlled the buffer creation so it's properly aligned */ + rdy_msg = (struct htc_ready_ext_msg *)packet->buf; + + if ((le16_to_cpu(rdy_msg->ver2_0_info.msg_id) != HTC_MSG_READY_ID) || + (packet->act_len < sizeof(struct htc_ready_msg))) { + status = -ENOMEM; + goto fail_wait_target; + } + + if (!rdy_msg->ver2_0_info.cred_cnt || !rdy_msg->ver2_0_info.cred_sz) { + status = -ENOMEM; + goto fail_wait_target; + } + + target->tgt_creds = le16_to_cpu(rdy_msg->ver2_0_info.cred_cnt); + target->tgt_cred_sz = le16_to_cpu(rdy_msg->ver2_0_info.cred_sz); + + ath6kl_dbg(ATH6KL_DBG_BOOT, + "htc target ready credits %d size %d\n", + target->tgt_creds, target->tgt_cred_sz); + + /* check if this is an extended ready message */ + if (packet->act_len >= sizeof(struct htc_ready_ext_msg)) { + /* this is an extended message */ + target->htc_tgt_ver = rdy_msg->htc_ver; + target->msg_per_bndl_max = rdy_msg->msg_per_htc_bndl; + } else { + /* legacy */ + target->htc_tgt_ver = HTC_VERSION_2P0; + target->msg_per_bndl_max = 0; + } + + ath6kl_dbg(ATH6KL_DBG_BOOT, "htc using protocol %s (%d)\n", + (target->htc_tgt_ver == HTC_VERSION_2P0) ? "2.0" : ">= 2.1", + target->htc_tgt_ver); + + if (target->msg_per_bndl_max > 0) + htc_setup_msg_bndl(target); + + /* setup our pseudo HTC control endpoint connection */ + memset(&connect, 0, sizeof(connect)); + memset(&resp, 0, sizeof(resp)); + connect.ep_cb.rx = htc_ctrl_rx; + connect.ep_cb.rx_refill = NULL; + connect.ep_cb.tx_full = NULL; + connect.max_txq_depth = NUM_CONTROL_BUFFERS; + connect.svc_id = HTC_CTRL_RSVD_SVC; + + /* connect fake service */ + status = ath6kl_htc_mbox_conn_service((void *)target, &connect, &resp); + + if (status) + /* + * FIXME: this call doesn't make sense, the caller should + * call ath6kl_htc_mbox_cleanup() when it wants remove htc + */ + ath6kl_hif_cleanup_scatter(target->dev->ar); + +fail_wait_target: + if (packet) { + htc_rxpkt_reset(packet); + reclaim_rx_ctrl_buf(target, packet); + } + + return status; +} + +/* + * Start HTC, enable interrupts and let the target know + * host has finished setup. + */ +static int ath6kl_htc_mbox_start(struct htc_target *target) +{ + struct htc_packet *packet; + int status; + + memset(&target->dev->irq_proc_reg, 0, + sizeof(target->dev->irq_proc_reg)); + + /* Disable interrupts at the chip level */ + ath6kl_hif_disable_intrs(target->dev); + + target->htc_flags = 0; + target->rx_st_flags = 0; + + /* Push control receive buffers into htc control endpoint */ + while ((packet = htc_get_control_buf(target, false)) != NULL) { + status = htc_add_rxbuf(target, packet); + if (status) + return status; + } + + /* NOTE: the first entry in the distribution list is ENDPOINT_0 */ + ath6kl_credit_init(target->credit_info, &target->cred_dist_list, + target->tgt_creds); + + dump_cred_dist_stats(target); + + /* Indicate to the target of the setup completion */ + status = htc_setup_tx_complete(target); + + if (status) + return status; + + /* unmask interrupts */ + status = ath6kl_hif_unmask_intrs(target->dev); + + if (status) + ath6kl_htc_mbox_stop(target); + + return status; +} + +static int ath6kl_htc_reset(struct htc_target *target) +{ + u32 block_size, ctrl_bufsz; + struct htc_packet *packet; + int i; + + reset_ep_state(target); + + block_size = target->dev->ar->mbox_info.block_size; + + ctrl_bufsz = (block_size > HTC_MAX_CTRL_MSG_LEN) ? + (block_size + HTC_HDR_LENGTH) : + (HTC_MAX_CTRL_MSG_LEN + HTC_HDR_LENGTH); + + for (i = 0; i < NUM_CONTROL_BUFFERS; i++) { + packet = kzalloc(sizeof(*packet), GFP_KERNEL); + if (!packet) + return -ENOMEM; + + packet->buf_start = kzalloc(ctrl_bufsz, GFP_KERNEL); + if (!packet->buf_start) { + kfree(packet); + return -ENOMEM; + } + + packet->buf_len = ctrl_bufsz; + if (i < NUM_CONTROL_RX_BUFFERS) { + packet->act_len = 0; + packet->buf = packet->buf_start; + packet->endpoint = ENDPOINT_0; + list_add_tail(&packet->list, &target->free_ctrl_rxbuf); + } else + list_add_tail(&packet->list, &target->free_ctrl_txbuf); + } + + return 0; +} + +/* htc_stop: stop interrupt reception, and flush all queued buffers */ +static void ath6kl_htc_mbox_stop(struct htc_target *target) +{ + spin_lock_bh(&target->htc_lock); + target->htc_flags |= HTC_OP_STATE_STOPPING; + spin_unlock_bh(&target->htc_lock); + + /* + * Masking interrupts is a synchronous operation, when this + * function returns all pending HIF I/O has completed, we can + * safely flush the queues. + */ + ath6kl_hif_mask_intrs(target->dev); + + ath6kl_htc_flush_txep_all(target); + + ath6kl_htc_mbox_flush_rx_buf(target); + + ath6kl_htc_reset(target); +} + +static void *ath6kl_htc_mbox_create(struct ath6kl *ar) +{ + struct htc_target *target = NULL; + int status = 0; + + target = kzalloc(sizeof(*target), GFP_KERNEL); + if (!target) { + ath6kl_err("unable to allocate memory\n"); + return NULL; + } + + target->dev = kzalloc(sizeof(*target->dev), GFP_KERNEL); + if (!target->dev) { + ath6kl_err("unable to allocate memory\n"); + status = -ENOMEM; + goto err_htc_cleanup; + } + + spin_lock_init(&target->htc_lock); + spin_lock_init(&target->rx_lock); + spin_lock_init(&target->tx_lock); + + INIT_LIST_HEAD(&target->free_ctrl_txbuf); + INIT_LIST_HEAD(&target->free_ctrl_rxbuf); + INIT_LIST_HEAD(&target->cred_dist_list); + + target->dev->ar = ar; + target->dev->htc_cnxt = target; + target->ep_waiting = ENDPOINT_MAX; + + status = ath6kl_hif_setup(target->dev); + if (status) + goto err_htc_cleanup; + + status = ath6kl_htc_reset(target); + if (status) + goto err_htc_cleanup; + + return target; + +err_htc_cleanup: + ath6kl_htc_mbox_cleanup(target); + + return NULL; +} + +/* cleanup the HTC instance */ +static void ath6kl_htc_mbox_cleanup(struct htc_target *target) +{ + struct htc_packet *packet, *tmp_packet; + + ath6kl_hif_cleanup_scatter(target->dev->ar); + + list_for_each_entry_safe(packet, tmp_packet, + &target->free_ctrl_txbuf, list) { + list_del(&packet->list); + kfree(packet->buf_start); + kfree(packet); + } + + list_for_each_entry_safe(packet, tmp_packet, + &target->free_ctrl_rxbuf, list) { + list_del(&packet->list); + kfree(packet->buf_start); + kfree(packet); + } + + kfree(target->dev); + kfree(target); +} + +static const struct ath6kl_htc_ops ath6kl_htc_mbox_ops = { + .create = ath6kl_htc_mbox_create, + .wait_target = ath6kl_htc_mbox_wait_target, + .start = ath6kl_htc_mbox_start, + .conn_service = ath6kl_htc_mbox_conn_service, + .tx = ath6kl_htc_mbox_tx, + .stop = ath6kl_htc_mbox_stop, + .cleanup = ath6kl_htc_mbox_cleanup, + .flush_txep = ath6kl_htc_mbox_flush_txep, + .flush_rx_buf = ath6kl_htc_mbox_flush_rx_buf, + .activity_changed = ath6kl_htc_mbox_activity_changed, + .get_rxbuf_num = ath6kl_htc_mbox_get_rxbuf_num, + .add_rxbuf_multiple = ath6kl_htc_mbox_add_rxbuf_multiple, + .credit_setup = ath6kl_htc_mbox_credit_setup, +}; + +void ath6kl_htc_mbox_attach(struct ath6kl *ar) +{ + ar->htc_ops = &ath6kl_htc_mbox_ops; +} diff --git a/drivers/net/wireless/ath/ath6kl/htc_pipe.c b/drivers/net/wireless/ath/ath6kl/htc_pipe.c new file mode 100644 index 0000000..67aa924 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/htc_pipe.c @@ -0,0 +1,1719 @@ +/* + * Copyright (c) 2007-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "core.h" +#include "debug.h" +#include "hif-ops.h" + +#define HTC_PACKET_CONTAINER_ALLOCATION 32 +#define HTC_CONTROL_BUFFER_SIZE (HTC_MAX_CTRL_MSG_LEN + HTC_HDR_LENGTH) + +static int ath6kl_htc_pipe_tx(struct htc_target *handle, + struct htc_packet *packet); +static void ath6kl_htc_pipe_cleanup(struct htc_target *handle); + +/* htc pipe tx path */ +static inline void restore_tx_packet(struct htc_packet *packet) +{ + if (packet->info.tx.flags & HTC_FLAGS_TX_FIXUP_NETBUF) { + skb_pull(packet->skb, sizeof(struct htc_frame_hdr)); + packet->info.tx.flags &= ~HTC_FLAGS_TX_FIXUP_NETBUF; + } +} + +static void do_send_completion(struct htc_endpoint *ep, + struct list_head *queue_to_indicate) +{ + struct htc_packet *packet; + + if (list_empty(queue_to_indicate)) { + /* nothing to indicate */ + return; + } + + if (ep->ep_cb.tx_comp_multi != NULL) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: calling ep %d, send complete multiple callback (%d pkts)\n", + __func__, ep->eid, + get_queue_depth(queue_to_indicate)); + /* + * a multiple send complete handler is being used, + * pass the queue to the handler + */ + ep->ep_cb.tx_comp_multi(ep->target, queue_to_indicate); + /* + * all packets are now owned by the callback, + * reset queue to be safe + */ + INIT_LIST_HEAD(queue_to_indicate); + } else { + /* using legacy EpTxComplete */ + do { + packet = list_first_entry(queue_to_indicate, + struct htc_packet, list); + + list_del(&packet->list); + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: calling ep %d send complete callback on packet 0x%p\n", + __func__, ep->eid, packet); + ep->ep_cb.tx_complete(ep->target, packet); + } while (!list_empty(queue_to_indicate)); + } +} + +static void send_packet_completion(struct htc_target *target, + struct htc_packet *packet) +{ + struct htc_endpoint *ep = &target->endpoint[packet->endpoint]; + struct list_head container; + + restore_tx_packet(packet); + INIT_LIST_HEAD(&container); + list_add_tail(&packet->list, &container); + + /* do completion */ + do_send_completion(ep, &container); +} + +static void get_htc_packet_credit_based(struct htc_target *target, + struct htc_endpoint *ep, + struct list_head *queue) +{ + int credits_required; + int remainder; + u8 send_flags; + struct htc_packet *packet; + unsigned int transfer_len; + + /* NOTE : the TX lock is held when this function is called */ + + /* loop until we can grab as many packets out of the queue as we can */ + while (true) { + send_flags = 0; + if (list_empty(&ep->txq)) + break; + + /* get packet at head, but don't remove it */ + packet = list_first_entry(&ep->txq, struct htc_packet, list); + + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: got head packet:0x%p , queue depth: %d\n", + __func__, packet, get_queue_depth(&ep->txq)); + + transfer_len = packet->act_len + HTC_HDR_LENGTH; + + if (transfer_len <= target->tgt_cred_sz) { + credits_required = 1; + } else { + /* figure out how many credits this message requires */ + credits_required = transfer_len / target->tgt_cred_sz; + remainder = transfer_len % target->tgt_cred_sz; + + if (remainder) + credits_required++; + } + + ath6kl_dbg(ATH6KL_DBG_HTC, "%s: creds required:%d got:%d\n", + __func__, credits_required, ep->cred_dist.credits); + + if (ep->eid == ENDPOINT_0) { + /* + * endpoint 0 is special, it always has a credit and + * does not require credit based flow control + */ + credits_required = 0; + + } else { + + if (ep->cred_dist.credits < credits_required) + break; + + ep->cred_dist.credits -= credits_required; + ep->ep_st.cred_cosumd += credits_required; + + /* check if we need credits back from the target */ + if (ep->cred_dist.credits < + ep->cred_dist.cred_per_msg) { + /* tell the target we need credits ASAP! */ + send_flags |= HTC_FLAGS_NEED_CREDIT_UPDATE; + ep->ep_st.cred_low_indicate += 1; + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: host needs credits\n", + __func__); + } + } + + /* now we can fully dequeue */ + packet = list_first_entry(&ep->txq, struct htc_packet, list); + + list_del(&packet->list); + /* save the number of credits this packet consumed */ + packet->info.tx.cred_used = credits_required; + /* save send flags */ + packet->info.tx.flags = send_flags; + packet->info.tx.seqno = ep->seqno; + ep->seqno++; + /* queue this packet into the caller's queue */ + list_add_tail(&packet->list, queue); + } + +} + +static void get_htc_packet(struct htc_target *target, + struct htc_endpoint *ep, + struct list_head *queue, int resources) +{ + struct htc_packet *packet; + + /* NOTE : the TX lock is held when this function is called */ + + /* loop until we can grab as many packets out of the queue as we can */ + while (resources) { + if (list_empty(&ep->txq)) + break; + + packet = list_first_entry(&ep->txq, struct htc_packet, list); + list_del(&packet->list); + + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: got packet:0x%p , new queue depth: %d\n", + __func__, packet, get_queue_depth(&ep->txq)); + packet->info.tx.seqno = ep->seqno; + packet->info.tx.flags = 0; + packet->info.tx.cred_used = 0; + ep->seqno++; + + /* queue this packet into the caller's queue */ + list_add_tail(&packet->list, queue); + resources--; + } +} + +static int htc_issue_packets(struct htc_target *target, + struct htc_endpoint *ep, + struct list_head *pkt_queue) +{ + int status = 0; + u16 payload_len; + struct sk_buff *skb; + struct htc_frame_hdr *htc_hdr; + struct htc_packet *packet; + + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: queue: 0x%p, pkts %d\n", __func__, + pkt_queue, get_queue_depth(pkt_queue)); + + while (!list_empty(pkt_queue)) { + packet = list_first_entry(pkt_queue, struct htc_packet, list); + list_del(&packet->list); + + skb = packet->skb; + if (!skb) { + WARN_ON_ONCE(1); + status = -EINVAL; + break; + } + + payload_len = packet->act_len; + + /* setup HTC frame header */ + htc_hdr = (struct htc_frame_hdr *) skb_push(skb, + sizeof(*htc_hdr)); + if (!htc_hdr) { + WARN_ON_ONCE(1); + status = -EINVAL; + break; + } + + packet->info.tx.flags |= HTC_FLAGS_TX_FIXUP_NETBUF; + + /* Endianess? */ + put_unaligned((u16) payload_len, &htc_hdr->payld_len); + htc_hdr->flags = packet->info.tx.flags; + htc_hdr->eid = (u8) packet->endpoint; + htc_hdr->ctrl[0] = 0; + htc_hdr->ctrl[1] = (u8) packet->info.tx.seqno; + + spin_lock_bh(&target->tx_lock); + + /* store in look up queue to match completions */ + list_add_tail(&packet->list, &ep->pipe.tx_lookup_queue); + ep->ep_st.tx_issued += 1; + spin_unlock_bh(&target->tx_lock); + + status = ath6kl_hif_pipe_send(target->dev->ar, + ep->pipe.pipeid_ul, NULL, skb); + + if (status != 0) { + if (status != -ENOMEM) { + /* TODO: if more than 1 endpoint maps to the + * same PipeID, it is possible to run out of + * resources in the HIF layer. + * Don't emit the error + */ + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: failed status:%d\n", + __func__, status); + } + spin_lock_bh(&target->tx_lock); + list_del(&packet->list); + + /* reclaim credits */ + ep->cred_dist.credits += packet->info.tx.cred_used; + spin_unlock_bh(&target->tx_lock); + + /* put it back into the callers queue */ + list_add(&packet->list, pkt_queue); + break; + } + + } + + if (status != 0) { + while (!list_empty(pkt_queue)) { + if (status != -ENOMEM) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: failed pkt:0x%p status:%d\n", + __func__, packet, status); + } + + packet = list_first_entry(pkt_queue, + struct htc_packet, list); + list_del(&packet->list); + packet->status = status; + send_packet_completion(target, packet); + } + } + + return status; +} + +static enum htc_send_queue_result htc_try_send(struct htc_target *target, + struct htc_endpoint *ep, + struct list_head *txq) +{ + struct list_head send_queue; /* temp queue to hold packets */ + struct htc_packet *packet, *tmp_pkt; + struct ath6kl *ar = target->dev->ar; + enum htc_send_full_action action; + int tx_resources, overflow, txqueue_depth, i, good_pkts; + u8 pipeid; + + ath6kl_dbg(ATH6KL_DBG_HTC, "%s: (queue:0x%p depth:%d)\n", + __func__, txq, + (txq == NULL) ? 0 : get_queue_depth(txq)); + + /* init the local send queue */ + INIT_LIST_HEAD(&send_queue); + + /* + * txq equals to NULL means + * caller didn't provide a queue, just wants us to + * check queues and send + */ + if (txq != NULL) { + if (list_empty(txq)) { + /* empty queue */ + return HTC_SEND_QUEUE_DROP; + } + + spin_lock_bh(&target->tx_lock); + txqueue_depth = get_queue_depth(&ep->txq); + spin_unlock_bh(&target->tx_lock); + + if (txqueue_depth >= ep->max_txq_depth) { + /* we've already overflowed */ + overflow = get_queue_depth(txq); + } else { + /* get how much we will overflow by */ + overflow = txqueue_depth; + overflow += get_queue_depth(txq); + /* get how much we will overflow the TX queue by */ + overflow -= ep->max_txq_depth; + } + + /* if overflow is negative or zero, we are okay */ + if (overflow > 0) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: Endpoint %d, TX queue will overflow :%d, Tx Depth:%d, Max:%d\n", + __func__, ep->eid, overflow, txqueue_depth, + ep->max_txq_depth); + } + if ((overflow <= 0) || + (ep->ep_cb.tx_full == NULL)) { + /* + * all packets will fit or caller did not provide send + * full indication handler -- just move all of them + * to the local send_queue object + */ + list_splice_tail_init(txq, &send_queue); + } else { + good_pkts = get_queue_depth(txq) - overflow; + if (good_pkts < 0) { + WARN_ON_ONCE(1); + return HTC_SEND_QUEUE_DROP; + } + + /* we have overflowed, and a callback is provided */ + /* dequeue all non-overflow packets to the sendqueue */ + for (i = 0; i < good_pkts; i++) { + /* pop off caller's queue */ + packet = list_first_entry(txq, + struct htc_packet, + list); + /* move to local queue */ + list_move_tail(&packet->list, &send_queue); + } + + /* + * the caller's queue has all the packets that won't fit + * walk through the caller's queue and indicate each to + * the send full handler + */ + list_for_each_entry_safe(packet, tmp_pkt, + txq, list) { + + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: Indicat overflowed TX pkts: %p\n", + __func__, packet); + action = ep->ep_cb.tx_full(ep->target, packet); + if (action == HTC_SEND_FULL_DROP) { + /* callback wants the packet dropped */ + ep->ep_st.tx_dropped += 1; + + /* leave this one in the caller's queue + * for cleanup */ + } else { + /* callback wants to keep this packet, + * move from caller's queue to the send + * queue */ + list_move_tail(&packet->list, + &send_queue); + } + + } + + if (list_empty(&send_queue)) { + /* no packets made it in, caller will cleanup */ + return HTC_SEND_QUEUE_DROP; + } + } + } + + if (!ep->pipe.tx_credit_flow_enabled) { + tx_resources = + ath6kl_hif_pipe_get_free_queue_number(ar, + ep->pipe.pipeid_ul); + } else { + tx_resources = 0; + } + + spin_lock_bh(&target->tx_lock); + if (!list_empty(&send_queue)) { + /* transfer packets to tail */ + list_splice_tail_init(&send_queue, &ep->txq); + if (!list_empty(&send_queue)) { + WARN_ON_ONCE(1); + spin_unlock_bh(&target->tx_lock); + return HTC_SEND_QUEUE_DROP; + } + INIT_LIST_HEAD(&send_queue); + } + + /* increment tx processing count on entry */ + ep->tx_proc_cnt++; + + if (ep->tx_proc_cnt > 1) { + /* + * Another thread or task is draining the TX queues on this + * endpoint that thread will reset the tx processing count + * when the queue is drained. + */ + ep->tx_proc_cnt--; + spin_unlock_bh(&target->tx_lock); + return HTC_SEND_QUEUE_OK; + } + + /***** beyond this point only 1 thread may enter ******/ + + /* + * Now drain the endpoint TX queue for transmission as long as we have + * enough transmit resources. + */ + while (true) { + + if (get_queue_depth(&ep->txq) == 0) + break; + + if (ep->pipe.tx_credit_flow_enabled) { + /* + * Credit based mechanism provides flow control + * based on target transmit resource availability, + * we assume that the HIF layer will always have + * bus resources greater than target transmit + * resources. + */ + get_htc_packet_credit_based(target, ep, &send_queue); + } else { + /* + * Get all packets for this endpoint that we can + * for this pass. + */ + get_htc_packet(target, ep, &send_queue, tx_resources); + } + + if (get_queue_depth(&send_queue) == 0) { + /* + * Didn't get packets due to out of resources or TX + * queue was drained. + */ + break; + } + + spin_unlock_bh(&target->tx_lock); + + /* send what we can */ + htc_issue_packets(target, ep, &send_queue); + + if (!ep->pipe.tx_credit_flow_enabled) { + pipeid = ep->pipe.pipeid_ul; + tx_resources = + ath6kl_hif_pipe_get_free_queue_number(ar, pipeid); + } + + spin_lock_bh(&target->tx_lock); + + } + /* done with this endpoint, we can clear the count */ + ep->tx_proc_cnt = 0; + spin_unlock_bh(&target->tx_lock); + + return HTC_SEND_QUEUE_OK; +} + +/* htc control packet manipulation */ +static void destroy_htc_txctrl_packet(struct htc_packet *packet) +{ + struct sk_buff *skb; + skb = packet->skb; + dev_kfree_skb(skb); + kfree(packet); +} + +static struct htc_packet *build_htc_txctrl_packet(void) +{ + struct htc_packet *packet = NULL; + struct sk_buff *skb; + + packet = kzalloc(sizeof(struct htc_packet), GFP_KERNEL); + if (packet == NULL) + return NULL; + + skb = __dev_alloc_skb(HTC_CONTROL_BUFFER_SIZE, GFP_KERNEL); + + if (skb == NULL) { + kfree(packet); + return NULL; + } + packet->skb = skb; + + return packet; +} + +static void htc_free_txctrl_packet(struct htc_target *target, + struct htc_packet *packet) +{ + destroy_htc_txctrl_packet(packet); +} + +static struct htc_packet *htc_alloc_txctrl_packet(struct htc_target *target) +{ + return build_htc_txctrl_packet(); +} + +static void htc_txctrl_complete(struct htc_target *target, + struct htc_packet *packet) +{ + htc_free_txctrl_packet(target, packet); +} + +#define MAX_MESSAGE_SIZE 1536 + +static int htc_setup_target_buffer_assignments(struct htc_target *target) +{ + int status, credits, credit_per_maxmsg, i; + struct htc_pipe_txcredit_alloc *entry; + unsigned int hif_usbaudioclass = 0; + + credit_per_maxmsg = MAX_MESSAGE_SIZE / target->tgt_cred_sz; + if (MAX_MESSAGE_SIZE % target->tgt_cred_sz) + credit_per_maxmsg++; + + /* TODO, this should be configured by the caller! */ + + credits = target->tgt_creds; + entry = &target->pipe.txcredit_alloc[0]; + + status = -ENOMEM; + + /* FIXME: hif_usbaudioclass is always zero */ + if (hif_usbaudioclass) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: For USB Audio Class- Total:%d\n", + __func__, credits); + entry++; + entry++; + /* Setup VO Service To have Max Credits */ + entry->service_id = WMI_DATA_VO_SVC; + entry->credit_alloc = (credits - 6); + if (entry->credit_alloc == 0) + entry->credit_alloc++; + + credits -= (int) entry->credit_alloc; + if (credits <= 0) + return status; + + entry++; + entry->service_id = WMI_CONTROL_SVC; + entry->credit_alloc = credit_per_maxmsg; + credits -= (int) entry->credit_alloc; + if (credits <= 0) + return status; + + /* leftovers go to best effort */ + entry++; + entry++; + entry->service_id = WMI_DATA_BE_SVC; + entry->credit_alloc = (u8) credits; + status = 0; + } else { + entry++; + entry->service_id = WMI_DATA_VI_SVC; + entry->credit_alloc = credits / 4; + if (entry->credit_alloc == 0) + entry->credit_alloc++; + + credits -= (int) entry->credit_alloc; + if (credits <= 0) + return status; + + entry++; + entry->service_id = WMI_DATA_VO_SVC; + entry->credit_alloc = credits / 4; + if (entry->credit_alloc == 0) + entry->credit_alloc++; + + credits -= (int) entry->credit_alloc; + if (credits <= 0) + return status; + + entry++; + entry->service_id = WMI_CONTROL_SVC; + entry->credit_alloc = credit_per_maxmsg; + credits -= (int) entry->credit_alloc; + if (credits <= 0) + return status; + + entry++; + entry->service_id = WMI_DATA_BK_SVC; + entry->credit_alloc = credit_per_maxmsg; + credits -= (int) entry->credit_alloc; + if (credits <= 0) + return status; + + /* leftovers go to best effort */ + entry++; + entry->service_id = WMI_DATA_BE_SVC; + entry->credit_alloc = (u8) credits; + status = 0; + } + + if (status == 0) { + for (i = 0; i < ENDPOINT_MAX; i++) { + if (target->pipe.txcredit_alloc[i].service_id != 0) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "HTC Service Index : %d TX : 0x%2.2X : alloc:%d\n", + i, + target->pipe.txcredit_alloc[i]. + service_id, + target->pipe.txcredit_alloc[i]. + credit_alloc); + } + } + } + return status; +} + +/* process credit reports and call distribution function */ +static void htc_process_credit_report(struct htc_target *target, + struct htc_credit_report *rpt, + int num_entries, + enum htc_endpoint_id from_ep) +{ + int total_credits = 0, i; + struct htc_endpoint *ep; + + /* lock out TX while we update credits */ + spin_lock_bh(&target->tx_lock); + + for (i = 0; i < num_entries; i++, rpt++) { + if (rpt->eid >= ENDPOINT_MAX) { + WARN_ON_ONCE(1); + spin_unlock_bh(&target->tx_lock); + return; + } + + ep = &target->endpoint[rpt->eid]; + ep->cred_dist.credits += rpt->credits; + + if (ep->cred_dist.credits && get_queue_depth(&ep->txq)) { + spin_unlock_bh(&target->tx_lock); + htc_try_send(target, ep, NULL); + spin_lock_bh(&target->tx_lock); + } + + total_credits += rpt->credits; + } + ath6kl_dbg(ATH6KL_DBG_HTC, + "Report indicated %d credits to distribute\n", + total_credits); + + spin_unlock_bh(&target->tx_lock); +} + +/* flush endpoint TX queue */ +static void htc_flush_tx_endpoint(struct htc_target *target, + struct htc_endpoint *ep, u16 tag) +{ + struct htc_packet *packet; + + spin_lock_bh(&target->tx_lock); + while (get_queue_depth(&ep->txq)) { + packet = list_first_entry(&ep->txq, struct htc_packet, list); + list_del(&packet->list); + packet->status = 0; + send_packet_completion(target, packet); + } + spin_unlock_bh(&target->tx_lock); +} + +/* + * In the adapted HIF layer, struct sk_buff * are passed between HIF and HTC, + * since upper layers expects struct htc_packet containers we use the completed + * skb and lookup it's corresponding HTC packet buffer from a lookup list. + * This is extra overhead that can be fixed by re-aligning HIF interfaces with + * HTC. + */ +static struct htc_packet *htc_lookup_tx_packet(struct htc_target *target, + struct htc_endpoint *ep, + struct sk_buff *skb) +{ + struct htc_packet *packet, *tmp_pkt, *found_packet = NULL; + + spin_lock_bh(&target->tx_lock); + + /* + * interate from the front of tx lookup queue + * this lookup should be fast since lower layers completes in-order and + * so the completed packet should be at the head of the list generally + */ + list_for_each_entry_safe(packet, tmp_pkt, &ep->pipe.tx_lookup_queue, + list) { + /* check for removal */ + if (skb == packet->skb) { + /* found it */ + list_del(&packet->list); + found_packet = packet; + break; + } + } + + spin_unlock_bh(&target->tx_lock); + + return found_packet; +} + +static int ath6kl_htc_pipe_tx_complete(struct ath6kl *ar, struct sk_buff *skb) +{ + struct htc_target *target = ar->htc_target; + struct htc_frame_hdr *htc_hdr; + struct htc_endpoint *ep; + struct htc_packet *packet; + u8 ep_id, *netdata; + u32 netlen; + + netdata = skb->data; + netlen = skb->len; + + htc_hdr = (struct htc_frame_hdr *) netdata; + + ep_id = htc_hdr->eid; + ep = &target->endpoint[ep_id]; + + packet = htc_lookup_tx_packet(target, ep, skb); + if (packet == NULL) { + /* may have already been flushed and freed */ + ath6kl_err("HTC TX lookup failed!\n"); + } else { + /* will be giving this buffer back to upper layers */ + packet->status = 0; + send_packet_completion(target, packet); + } + skb = NULL; + + if (!ep->pipe.tx_credit_flow_enabled) { + /* + * note: when using TX credit flow, the re-checking of queues + * happens when credits flow back from the target. in the + * non-TX credit case, we recheck after the packet completes + */ + htc_try_send(target, ep, NULL); + } + + return 0; +} + +static int htc_send_packets_multiple(struct htc_target *target, + struct list_head *pkt_queue) +{ + struct htc_endpoint *ep; + struct htc_packet *packet, *tmp_pkt; + + if (list_empty(pkt_queue)) + return -EINVAL; + + /* get first packet to find out which ep the packets will go into */ + packet = list_first_entry(pkt_queue, struct htc_packet, list); + + if (packet->endpoint >= ENDPOINT_MAX) { + WARN_ON_ONCE(1); + return -EINVAL; + } + ep = &target->endpoint[packet->endpoint]; + + htc_try_send(target, ep, pkt_queue); + + /* do completion on any packets that couldn't get in */ + if (!list_empty(pkt_queue)) { + list_for_each_entry_safe(packet, tmp_pkt, pkt_queue, list) { + packet->status = -ENOMEM; + } + + do_send_completion(ep, pkt_queue); + } + + return 0; +} + +/* htc pipe rx path */ +static struct htc_packet *alloc_htc_packet_container(struct htc_target *target) +{ + struct htc_packet *packet; + spin_lock_bh(&target->rx_lock); + + if (target->pipe.htc_packet_pool == NULL) { + spin_unlock_bh(&target->rx_lock); + return NULL; + } + + packet = target->pipe.htc_packet_pool; + target->pipe.htc_packet_pool = (struct htc_packet *) packet->list.next; + + spin_unlock_bh(&target->rx_lock); + + packet->list.next = NULL; + return packet; +} + +static void free_htc_packet_container(struct htc_target *target, + struct htc_packet *packet) +{ + struct list_head *lh; + + spin_lock_bh(&target->rx_lock); + + if (target->pipe.htc_packet_pool == NULL) { + target->pipe.htc_packet_pool = packet; + packet->list.next = NULL; + } else { + lh = (struct list_head *) target->pipe.htc_packet_pool; + packet->list.next = lh; + target->pipe.htc_packet_pool = packet; + } + + spin_unlock_bh(&target->rx_lock); +} + +static int htc_process_trailer(struct htc_target *target, u8 *buffer, + int len, enum htc_endpoint_id from_ep) +{ + struct htc_credit_report *report; + struct htc_record_hdr *record; + u8 *record_buf, *orig_buf; + int orig_len, status; + + orig_buf = buffer; + orig_len = len; + status = 0; + + while (len > 0) { + if (len < sizeof(struct htc_record_hdr)) { + status = -EINVAL; + break; + } + + /* these are byte aligned structs */ + record = (struct htc_record_hdr *) buffer; + len -= sizeof(struct htc_record_hdr); + buffer += sizeof(struct htc_record_hdr); + + if (record->len > len) { + /* no room left in buffer for record */ + ath6kl_dbg(ATH6KL_DBG_HTC, + "invalid length: %d (id:%d) buffer has: %d bytes left\n", + record->len, record->rec_id, len); + status = -EINVAL; + break; + } + + /* start of record follows the header */ + record_buf = buffer; + + switch (record->rec_id) { + case HTC_RECORD_CREDITS: + if (record->len < sizeof(struct htc_credit_report)) { + WARN_ON_ONCE(1); + return -EINVAL; + } + + report = (struct htc_credit_report *) record_buf; + htc_process_credit_report(target, report, + record->len / sizeof(*report), + from_ep); + break; + default: + ath6kl_dbg(ATH6KL_DBG_HTC, + "unhandled record: id:%d length:%d\n", + record->rec_id, record->len); + break; + } + + if (status != 0) + break; + + /* advance buffer past this record for next time around */ + buffer += record->len; + len -= record->len; + } + + return status; +} + +static void do_recv_completion(struct htc_endpoint *ep, + struct list_head *queue_to_indicate) +{ + struct htc_packet *packet; + + if (list_empty(queue_to_indicate)) { + /* nothing to indicate */ + return; + } + + /* using legacy EpRecv */ + while (!list_empty(queue_to_indicate)) { + packet = list_first_entry(queue_to_indicate, + struct htc_packet, list); + list_del(&packet->list); + ep->ep_cb.rx(ep->target, packet); + } + + return; +} + +static void recv_packet_completion(struct htc_target *target, + struct htc_endpoint *ep, + struct htc_packet *packet) +{ + struct list_head container; + INIT_LIST_HEAD(&container); + list_add_tail(&packet->list, &container); + + /* do completion */ + do_recv_completion(ep, &container); +} + +static int ath6kl_htc_pipe_rx_complete(struct ath6kl *ar, struct sk_buff *skb, + u8 pipeid) +{ + struct htc_target *target = ar->htc_target; + u8 *netdata, *trailer, hdr_info; + struct htc_frame_hdr *htc_hdr; + u32 netlen, trailerlen = 0; + struct htc_packet *packet; + struct htc_endpoint *ep; + u16 payload_len; + int status = 0; + + /* + * ar->htc_target can be NULL due to a race condition that can occur + * during driver initialization(we do 'ath6kl_hif_power_on' before + * initializing 'ar->htc_target' via 'ath6kl_htc_create'). + * 'ath6kl_hif_power_on' assigns 'ath6kl_recv_complete' as + * usb_complete_t/callback function for 'usb_fill_bulk_urb'. + * Thus the possibility of ar->htc_target being NULL + * via ath6kl_recv_complete -> ath6kl_usb_io_comp_work. + */ + if (WARN_ON_ONCE(!target)) { + ath6kl_err("Target not yet initialized\n"); + status = -EINVAL; + goto free_skb; + } + + + netdata = skb->data; + netlen = skb->len; + + htc_hdr = (struct htc_frame_hdr *) netdata; + + if (htc_hdr->eid >= ENDPOINT_MAX) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "HTC Rx: invalid EndpointID=%d\n", + htc_hdr->eid); + status = -EINVAL; + goto free_skb; + } + ep = &target->endpoint[htc_hdr->eid]; + + payload_len = le16_to_cpu(get_unaligned(&htc_hdr->payld_len)); + + if (netlen < (payload_len + HTC_HDR_LENGTH)) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "HTC Rx: insufficient length, got:%d expected =%u\n", + netlen, payload_len + HTC_HDR_LENGTH); + status = -EINVAL; + goto free_skb; + } + + /* get flags to check for trailer */ + hdr_info = htc_hdr->flags; + if (hdr_info & HTC_FLG_RX_TRAILER) { + /* extract the trailer length */ + hdr_info = htc_hdr->ctrl[0]; + if ((hdr_info < sizeof(struct htc_record_hdr)) || + (hdr_info > payload_len)) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "invalid header: payloadlen should be %d, CB[0]: %d\n", + payload_len, hdr_info); + status = -EINVAL; + goto free_skb; + } + + trailerlen = hdr_info; + /* process trailer after hdr/apps payload */ + trailer = (u8 *) htc_hdr + HTC_HDR_LENGTH + + payload_len - hdr_info; + status = htc_process_trailer(target, trailer, hdr_info, + htc_hdr->eid); + if (status != 0) + goto free_skb; + } + + if (((int) payload_len - (int) trailerlen) <= 0) { + /* zero length packet with trailer, just drop these */ + goto free_skb; + } + + if (htc_hdr->eid == ENDPOINT_0) { + /* handle HTC control message */ + if (target->htc_flags & HTC_OP_STATE_SETUP_COMPLETE) { + /* + * fatal: target should not send unsolicited + * messageson the endpoint 0 + */ + ath6kl_dbg(ATH6KL_DBG_HTC, + "HTC ignores Rx Ctrl after setup complete\n"); + status = -EINVAL; + goto free_skb; + } + + /* remove HTC header */ + skb_pull(skb, HTC_HDR_LENGTH); + + netdata = skb->data; + netlen = skb->len; + + spin_lock_bh(&target->rx_lock); + + target->pipe.ctrl_response_valid = true; + target->pipe.ctrl_response_len = min_t(int, netlen, + HTC_MAX_CTRL_MSG_LEN); + memcpy(target->pipe.ctrl_response_buf, netdata, + target->pipe.ctrl_response_len); + + spin_unlock_bh(&target->rx_lock); + + dev_kfree_skb(skb); + skb = NULL; + + goto free_skb; + } + + /* + * TODO: the message based HIF architecture allocates net bufs + * for recv packets since it bridges that HIF to upper layers, + * which expects HTC packets, we form the packets here + */ + packet = alloc_htc_packet_container(target); + if (packet == NULL) { + status = -ENOMEM; + goto free_skb; + } + + packet->status = 0; + packet->endpoint = htc_hdr->eid; + packet->pkt_cntxt = skb; + + /* TODO: for backwards compatibility */ + packet->buf = skb_push(skb, 0) + HTC_HDR_LENGTH; + packet->act_len = netlen - HTC_HDR_LENGTH - trailerlen; + + /* + * TODO: this is a hack because the driver layer will set the + * actual len of the skb again which will just double the len + */ + skb_trim(skb, 0); + + recv_packet_completion(target, ep, packet); + + /* recover the packet container */ + free_htc_packet_container(target, packet); + skb = NULL; + +free_skb: + dev_kfree_skb(skb); + + return status; + +} + +static void htc_flush_rx_queue(struct htc_target *target, + struct htc_endpoint *ep) +{ + struct list_head container; + struct htc_packet *packet; + + spin_lock_bh(&target->rx_lock); + + while (1) { + if (list_empty(&ep->rx_bufq)) + break; + + packet = list_first_entry(&ep->rx_bufq, + struct htc_packet, list); + list_del(&packet->list); + + spin_unlock_bh(&target->rx_lock); + packet->status = -ECANCELED; + packet->act_len = 0; + + ath6kl_dbg(ATH6KL_DBG_HTC, + "Flushing RX packet:0x%p, length:%d, ep:%d\n", + packet, packet->buf_len, + packet->endpoint); + + INIT_LIST_HEAD(&container); + list_add_tail(&packet->list, &container); + + /* give the packet back */ + do_recv_completion(ep, &container); + spin_lock_bh(&target->rx_lock); + } + + spin_unlock_bh(&target->rx_lock); +} + +/* polling routine to wait for a control packet to be received */ +static int htc_wait_recv_ctrl_message(struct htc_target *target) +{ + int count = HTC_TARGET_RESPONSE_POLL_COUNT; + + while (count > 0) { + spin_lock_bh(&target->rx_lock); + + if (target->pipe.ctrl_response_valid) { + target->pipe.ctrl_response_valid = false; + spin_unlock_bh(&target->rx_lock); + break; + } + + spin_unlock_bh(&target->rx_lock); + + count--; + + msleep_interruptible(HTC_TARGET_RESPONSE_POLL_WAIT); + } + + if (count <= 0) { + ath6kl_warn("htc pipe control receive timeout!\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static void htc_rxctrl_complete(struct htc_target *context, + struct htc_packet *packet) +{ + /* TODO, can't really receive HTC control messages yet.... */ + ath6kl_dbg(ATH6KL_DBG_HTC, "%s: invalid call function\n", __func__); +} + +/* htc pipe initialization */ +static void reset_endpoint_states(struct htc_target *target) +{ + struct htc_endpoint *ep; + int i; + + for (i = ENDPOINT_0; i < ENDPOINT_MAX; i++) { + ep = &target->endpoint[i]; + ep->svc_id = 0; + ep->len_max = 0; + ep->max_txq_depth = 0; + ep->eid = i; + INIT_LIST_HEAD(&ep->txq); + INIT_LIST_HEAD(&ep->pipe.tx_lookup_queue); + INIT_LIST_HEAD(&ep->rx_bufq); + ep->target = target; + ep->pipe.tx_credit_flow_enabled = true; + } +} + +/* start HTC, this is called after all services are connected */ +static int htc_config_target_hif_pipe(struct htc_target *target) +{ + return 0; +} + +/* htc service functions */ +static u8 htc_get_credit_alloc(struct htc_target *target, u16 service_id) +{ + u8 allocation = 0; + int i; + + for (i = 0; i < ENDPOINT_MAX; i++) { + if (target->pipe.txcredit_alloc[i].service_id == service_id) + allocation = + target->pipe.txcredit_alloc[i].credit_alloc; + } + + if (allocation == 0) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "HTC Service TX : 0x%2.2X : allocation is zero!\n", + service_id); + } + + return allocation; +} + +static int ath6kl_htc_pipe_conn_service(struct htc_target *target, + struct htc_service_connect_req *conn_req, + struct htc_service_connect_resp *conn_resp) +{ + struct ath6kl *ar = target->dev->ar; + struct htc_packet *packet = NULL; + struct htc_conn_service_resp *resp_msg; + struct htc_conn_service_msg *conn_msg; + enum htc_endpoint_id assigned_epid = ENDPOINT_MAX; + bool disable_credit_flowctrl = false; + unsigned int max_msg_size = 0; + struct htc_endpoint *ep; + int length, status = 0; + struct sk_buff *skb; + u8 tx_alloc; + u16 flags; + + if (conn_req->svc_id == 0) { + WARN_ON_ONCE(1); + status = -EINVAL; + goto free_packet; + } + + if (conn_req->svc_id == HTC_CTRL_RSVD_SVC) { + /* special case for pseudo control service */ + assigned_epid = ENDPOINT_0; + max_msg_size = HTC_MAX_CTRL_MSG_LEN; + tx_alloc = 0; + + } else { + + tx_alloc = htc_get_credit_alloc(target, conn_req->svc_id); + if (tx_alloc == 0) { + status = -ENOMEM; + goto free_packet; + } + + /* allocate a packet to send to the target */ + packet = htc_alloc_txctrl_packet(target); + + if (packet == NULL) { + WARN_ON_ONCE(1); + status = -ENOMEM; + goto free_packet; + } + + skb = packet->skb; + length = sizeof(struct htc_conn_service_msg); + + /* assemble connect service message */ + conn_msg = (struct htc_conn_service_msg *) skb_put(skb, + length); + if (conn_msg == NULL) { + WARN_ON_ONCE(1); + status = -EINVAL; + goto free_packet; + } + + memset(conn_msg, 0, + sizeof(struct htc_conn_service_msg)); + conn_msg->msg_id = cpu_to_le16(HTC_MSG_CONN_SVC_ID); + conn_msg->svc_id = cpu_to_le16(conn_req->svc_id); + conn_msg->conn_flags = cpu_to_le16(conn_req->conn_flags & + ~HTC_CONN_FLGS_SET_RECV_ALLOC_MASK); + + /* tell target desired recv alloc for this ep */ + flags = tx_alloc << HTC_CONN_FLGS_SET_RECV_ALLOC_SHIFT; + conn_msg->conn_flags |= cpu_to_le16(flags); + + if (conn_req->conn_flags & + HTC_CONN_FLGS_DISABLE_CRED_FLOW_CTRL) { + disable_credit_flowctrl = true; + } + + set_htc_pkt_info(packet, NULL, (u8 *) conn_msg, + length, + ENDPOINT_0, HTC_SERVICE_TX_PACKET_TAG); + + status = ath6kl_htc_pipe_tx(target, packet); + + /* we don't own it anymore */ + packet = NULL; + if (status != 0) + goto free_packet; + + /* wait for response */ + status = htc_wait_recv_ctrl_message(target); + if (status != 0) + goto free_packet; + + /* we controlled the buffer creation so it has to be + * properly aligned + */ + resp_msg = (struct htc_conn_service_resp *) + target->pipe.ctrl_response_buf; + + if (resp_msg->msg_id != cpu_to_le16(HTC_MSG_CONN_SVC_RESP_ID) || + (target->pipe.ctrl_response_len < sizeof(*resp_msg))) { + /* this message is not valid */ + WARN_ON_ONCE(1); + status = -EINVAL; + goto free_packet; + } + + ath6kl_dbg(ATH6KL_DBG_TRC, + "%s: service 0x%X conn resp: status: %d ep: %d\n", + __func__, resp_msg->svc_id, resp_msg->status, + resp_msg->eid); + + conn_resp->resp_code = resp_msg->status; + /* check response status */ + if (resp_msg->status != HTC_SERVICE_SUCCESS) { + ath6kl_dbg(ATH6KL_DBG_HTC, + "Target failed service 0x%X connect request (status:%d)\n", + resp_msg->svc_id, resp_msg->status); + status = -EINVAL; + goto free_packet; + } + + assigned_epid = (enum htc_endpoint_id) resp_msg->eid; + max_msg_size = le16_to_cpu(resp_msg->max_msg_sz); + } + + /* the rest are parameter checks so set the error status */ + status = -EINVAL; + + if (assigned_epid >= ENDPOINT_MAX) { + WARN_ON_ONCE(1); + goto free_packet; + } + + if (max_msg_size == 0) { + WARN_ON_ONCE(1); + goto free_packet; + } + + ep = &target->endpoint[assigned_epid]; + ep->eid = assigned_epid; + if (ep->svc_id != 0) { + /* endpoint already in use! */ + WARN_ON_ONCE(1); + goto free_packet; + } + + /* return assigned endpoint to caller */ + conn_resp->endpoint = assigned_epid; + conn_resp->len_max = max_msg_size; + + /* setup the endpoint */ + ep->svc_id = conn_req->svc_id; /* this marks ep in use */ + ep->max_txq_depth = conn_req->max_txq_depth; + ep->len_max = max_msg_size; + ep->cred_dist.credits = tx_alloc; + ep->cred_dist.cred_sz = target->tgt_cred_sz; + ep->cred_dist.cred_per_msg = max_msg_size / target->tgt_cred_sz; + if (max_msg_size % target->tgt_cred_sz) + ep->cred_dist.cred_per_msg++; + + /* copy all the callbacks */ + ep->ep_cb = conn_req->ep_cb; + + /* initialize tx_drop_packet_threshold */ + ep->tx_drop_packet_threshold = MAX_HI_COOKIE_NUM; + + status = ath6kl_hif_pipe_map_service(ar, ep->svc_id, + &ep->pipe.pipeid_ul, + &ep->pipe.pipeid_dl); + if (status != 0) + goto free_packet; + + ath6kl_dbg(ATH6KL_DBG_HTC, + "SVC Ready: 0x%4.4X: ULpipe:%d DLpipe:%d id:%d\n", + ep->svc_id, ep->pipe.pipeid_ul, + ep->pipe.pipeid_dl, ep->eid); + + if (disable_credit_flowctrl && ep->pipe.tx_credit_flow_enabled) { + ep->pipe.tx_credit_flow_enabled = false; + ath6kl_dbg(ATH6KL_DBG_HTC, + "SVC: 0x%4.4X ep:%d TX flow control off\n", + ep->svc_id, assigned_epid); + } + +free_packet: + if (packet != NULL) + htc_free_txctrl_packet(target, packet); + return status; +} + +/* htc export functions */ +static void *ath6kl_htc_pipe_create(struct ath6kl *ar) +{ + int status = 0; + struct htc_endpoint *ep = NULL; + struct htc_target *target = NULL; + struct htc_packet *packet; + int i; + + target = kzalloc(sizeof(struct htc_target), GFP_KERNEL); + if (target == NULL) { + ath6kl_err("htc create unable to allocate memory\n"); + status = -ENOMEM; + goto fail_htc_create; + } + + spin_lock_init(&target->htc_lock); + spin_lock_init(&target->rx_lock); + spin_lock_init(&target->tx_lock); + + reset_endpoint_states(target); + + for (i = 0; i < HTC_PACKET_CONTAINER_ALLOCATION; i++) { + packet = kzalloc(sizeof(struct htc_packet), GFP_KERNEL); + + if (packet != NULL) + free_htc_packet_container(target, packet); + } + + target->dev = kzalloc(sizeof(*target->dev), GFP_KERNEL); + if (!target->dev) { + ath6kl_err("unable to allocate memory\n"); + status = -ENOMEM; + goto fail_htc_create; + } + target->dev->ar = ar; + target->dev->htc_cnxt = target; + + /* Get HIF default pipe for HTC message exchange */ + ep = &target->endpoint[ENDPOINT_0]; + + ath6kl_hif_pipe_get_default(ar, &ep->pipe.pipeid_ul, + &ep->pipe.pipeid_dl); + + return target; + +fail_htc_create: + if (status != 0) { + if (target != NULL) + ath6kl_htc_pipe_cleanup(target); + + target = NULL; + } + return target; +} + +/* cleanup the HTC instance */ +static void ath6kl_htc_pipe_cleanup(struct htc_target *target) +{ + struct htc_packet *packet; + + while (true) { + packet = alloc_htc_packet_container(target); + if (packet == NULL) + break; + kfree(packet); + } + + kfree(target->dev); + + /* kfree our instance */ + kfree(target); +} + +static int ath6kl_htc_pipe_start(struct htc_target *target) +{ + struct sk_buff *skb; + struct htc_setup_comp_ext_msg *setup; + struct htc_packet *packet; + + htc_config_target_hif_pipe(target); + + /* allocate a buffer to send */ + packet = htc_alloc_txctrl_packet(target); + if (packet == NULL) { + WARN_ON_ONCE(1); + return -ENOMEM; + } + + skb = packet->skb; + + /* assemble setup complete message */ + setup = (struct htc_setup_comp_ext_msg *) skb_put(skb, + sizeof(*setup)); + memset(setup, 0, sizeof(struct htc_setup_comp_ext_msg)); + setup->msg_id = cpu_to_le16(HTC_MSG_SETUP_COMPLETE_EX_ID); + + ath6kl_dbg(ATH6KL_DBG_HTC, "HTC using TX credit flow control\n"); + + set_htc_pkt_info(packet, NULL, (u8 *) setup, + sizeof(struct htc_setup_comp_ext_msg), + ENDPOINT_0, HTC_SERVICE_TX_PACKET_TAG); + + target->htc_flags |= HTC_OP_STATE_SETUP_COMPLETE; + + return ath6kl_htc_pipe_tx(target, packet); +} + +static void ath6kl_htc_pipe_stop(struct htc_target *target) +{ + int i; + struct htc_endpoint *ep; + + /* cleanup endpoints */ + for (i = 0; i < ENDPOINT_MAX; i++) { + ep = &target->endpoint[i]; + htc_flush_rx_queue(target, ep); + htc_flush_tx_endpoint(target, ep, HTC_TX_PACKET_TAG_ALL); + } + + reset_endpoint_states(target); + target->htc_flags &= ~HTC_OP_STATE_SETUP_COMPLETE; +} + +static int ath6kl_htc_pipe_get_rxbuf_num(struct htc_target *target, + enum htc_endpoint_id endpoint) +{ + int num; + + spin_lock_bh(&target->rx_lock); + num = get_queue_depth(&(target->endpoint[endpoint].rx_bufq)); + spin_unlock_bh(&target->rx_lock); + + return num; +} + +static int ath6kl_htc_pipe_tx(struct htc_target *target, + struct htc_packet *packet) +{ + struct list_head queue; + + ath6kl_dbg(ATH6KL_DBG_HTC, + "%s: endPointId: %d, buffer: 0x%p, length: %d\n", + __func__, packet->endpoint, packet->buf, + packet->act_len); + + INIT_LIST_HEAD(&queue); + list_add_tail(&packet->list, &queue); + + return htc_send_packets_multiple(target, &queue); +} + +static int ath6kl_htc_pipe_wait_target(struct htc_target *target) +{ + struct htc_ready_ext_msg *ready_msg; + struct htc_service_connect_req connect; + struct htc_service_connect_resp resp; + int status = 0; + + status = htc_wait_recv_ctrl_message(target); + + if (status != 0) + return status; + + if (target->pipe.ctrl_response_len < sizeof(*ready_msg)) { + ath6kl_warn("invalid htc pipe ready msg len: %d\n", + target->pipe.ctrl_response_len); + return -ECOMM; + } + + ready_msg = (struct htc_ready_ext_msg *) target->pipe.ctrl_response_buf; + + if (ready_msg->ver2_0_info.msg_id != cpu_to_le16(HTC_MSG_READY_ID)) { + ath6kl_warn("invalid htc pipe ready msg: 0x%x\n", + ready_msg->ver2_0_info.msg_id); + return -ECOMM; + } + + ath6kl_dbg(ATH6KL_DBG_HTC, + "Target Ready! : transmit resources : %d size:%d\n", + ready_msg->ver2_0_info.cred_cnt, + ready_msg->ver2_0_info.cred_sz); + + target->tgt_creds = le16_to_cpu(ready_msg->ver2_0_info.cred_cnt); + target->tgt_cred_sz = le16_to_cpu(ready_msg->ver2_0_info.cred_sz); + + if ((target->tgt_creds == 0) || (target->tgt_cred_sz == 0)) + return -ECOMM; + + htc_setup_target_buffer_assignments(target); + + /* setup our pseudo HTC control endpoint connection */ + memset(&connect, 0, sizeof(connect)); + memset(&resp, 0, sizeof(resp)); + connect.ep_cb.tx_complete = htc_txctrl_complete; + connect.ep_cb.rx = htc_rxctrl_complete; + connect.max_txq_depth = NUM_CONTROL_TX_BUFFERS; + connect.svc_id = HTC_CTRL_RSVD_SVC; + + /* connect fake service */ + status = ath6kl_htc_pipe_conn_service(target, &connect, &resp); + + return status; +} + +static void ath6kl_htc_pipe_flush_txep(struct htc_target *target, + enum htc_endpoint_id endpoint, u16 tag) +{ + struct htc_endpoint *ep = &target->endpoint[endpoint]; + + if (ep->svc_id == 0) { + WARN_ON_ONCE(1); + /* not in use.. */ + return; + } + + htc_flush_tx_endpoint(target, ep, tag); +} + +static int ath6kl_htc_pipe_add_rxbuf_multiple(struct htc_target *target, + struct list_head *pkt_queue) +{ + struct htc_packet *packet, *tmp_pkt, *first; + struct htc_endpoint *ep; + int status = 0; + + if (list_empty(pkt_queue)) + return -EINVAL; + + first = list_first_entry(pkt_queue, struct htc_packet, list); + + if (first->endpoint >= ENDPOINT_MAX) { + WARN_ON_ONCE(1); + return -EINVAL; + } + + ath6kl_dbg(ATH6KL_DBG_HTC, "%s: epid: %d, cnt:%d, len: %d\n", + __func__, first->endpoint, get_queue_depth(pkt_queue), + first->buf_len); + + ep = &target->endpoint[first->endpoint]; + + spin_lock_bh(&target->rx_lock); + + /* store receive packets */ + list_splice_tail_init(pkt_queue, &ep->rx_bufq); + + spin_unlock_bh(&target->rx_lock); + + if (status != 0) { + /* walk through queue and mark each one canceled */ + list_for_each_entry_safe(packet, tmp_pkt, pkt_queue, list) { + packet->status = -ECANCELED; + } + + do_recv_completion(ep, pkt_queue); + } + + return status; +} + +static void ath6kl_htc_pipe_activity_changed(struct htc_target *target, + enum htc_endpoint_id ep, + bool active) +{ + /* TODO */ +} + +static void ath6kl_htc_pipe_flush_rx_buf(struct htc_target *target) +{ + /* TODO */ +} + +static int ath6kl_htc_pipe_credit_setup(struct htc_target *target, + struct ath6kl_htc_credit_info *info) +{ + return 0; +} + +static const struct ath6kl_htc_ops ath6kl_htc_pipe_ops = { + .create = ath6kl_htc_pipe_create, + .wait_target = ath6kl_htc_pipe_wait_target, + .start = ath6kl_htc_pipe_start, + .conn_service = ath6kl_htc_pipe_conn_service, + .tx = ath6kl_htc_pipe_tx, + .stop = ath6kl_htc_pipe_stop, + .cleanup = ath6kl_htc_pipe_cleanup, + .flush_txep = ath6kl_htc_pipe_flush_txep, + .flush_rx_buf = ath6kl_htc_pipe_flush_rx_buf, + .activity_changed = ath6kl_htc_pipe_activity_changed, + .get_rxbuf_num = ath6kl_htc_pipe_get_rxbuf_num, + .add_rxbuf_multiple = ath6kl_htc_pipe_add_rxbuf_multiple, + .credit_setup = ath6kl_htc_pipe_credit_setup, + .tx_complete = ath6kl_htc_pipe_tx_complete, + .rx_complete = ath6kl_htc_pipe_rx_complete, +}; + +void ath6kl_htc_pipe_attach(struct ath6kl *ar) +{ + ar->htc_ops = &ath6kl_htc_pipe_ops; +} diff --git a/drivers/net/wireless/ath/ath6kl/init.c b/drivers/net/wireless/ath/ath6kl/init.c new file mode 100644 index 0000000..40ffee6 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/init.c @@ -0,0 +1,1864 @@ + +/* + * Copyright (c) 2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "cfg80211.h" +#include "target.h" +#include "debug.h" +#include "hif-ops.h" +#include "htc-ops.h" + +static const struct ath6kl_hw hw_list[] = { + { + .id = AR6003_HW_2_0_VERSION, + .name = "ar6003 hw 2.0", + .dataset_patch_addr = 0x57e884, + .app_load_addr = 0x543180, + .board_ext_data_addr = 0x57e500, + .reserved_ram_size = 6912, + .refclk_hz = 26000000, + .uarttx_pin = 8, + .flags = ATH6KL_HW_SDIO_CRC_ERROR_WAR, + + /* hw2.0 needs override address hardcoded */ + .app_start_override_addr = 0x944C00, + + .fw = { + .dir = AR6003_HW_2_0_FW_DIR, + .otp = AR6003_HW_2_0_OTP_FILE, + .fw = AR6003_HW_2_0_FIRMWARE_FILE, + .tcmd = AR6003_HW_2_0_TCMD_FIRMWARE_FILE, + .patch = AR6003_HW_2_0_PATCH_FILE, + }, + + .fw_board = AR6003_HW_2_0_BOARD_DATA_FILE, + .fw_default_board = AR6003_HW_2_0_DEFAULT_BOARD_DATA_FILE, + }, + { + .id = AR6003_HW_2_1_1_VERSION, + .name = "ar6003 hw 2.1.1", + .dataset_patch_addr = 0x57ff74, + .app_load_addr = 0x1234, + .board_ext_data_addr = 0x542330, + .reserved_ram_size = 512, + .refclk_hz = 26000000, + .uarttx_pin = 8, + .testscript_addr = 0x57ef74, + .flags = ATH6KL_HW_SDIO_CRC_ERROR_WAR, + + .fw = { + .dir = AR6003_HW_2_1_1_FW_DIR, + .otp = AR6003_HW_2_1_1_OTP_FILE, + .fw = AR6003_HW_2_1_1_FIRMWARE_FILE, + .tcmd = AR6003_HW_2_1_1_TCMD_FIRMWARE_FILE, + .patch = AR6003_HW_2_1_1_PATCH_FILE, + .utf = AR6003_HW_2_1_1_UTF_FIRMWARE_FILE, + .testscript = AR6003_HW_2_1_1_TESTSCRIPT_FILE, + }, + + .fw_board = AR6003_HW_2_1_1_BOARD_DATA_FILE, + .fw_default_board = AR6003_HW_2_1_1_DEFAULT_BOARD_DATA_FILE, + }, + { + .id = AR6004_HW_1_0_VERSION, + .name = "ar6004 hw 1.0", + .dataset_patch_addr = 0x57e884, + .app_load_addr = 0x1234, + .board_ext_data_addr = 0x437000, + .reserved_ram_size = 19456, + .board_addr = 0x433900, + .refclk_hz = 26000000, + .uarttx_pin = 11, + .flags = ATH6KL_HW_64BIT_RATES | + ATH6KL_HW_AP_INACTIVITY_MINS, + + .fw = { + .dir = AR6004_HW_1_0_FW_DIR, + .fw = AR6004_HW_1_0_FIRMWARE_FILE, + }, + + .fw_board = AR6004_HW_1_0_BOARD_DATA_FILE, + .fw_default_board = AR6004_HW_1_0_DEFAULT_BOARD_DATA_FILE, + }, + { + .id = AR6004_HW_1_1_VERSION, + .name = "ar6004 hw 1.1", + .dataset_patch_addr = 0x57e884, + .app_load_addr = 0x1234, + .board_ext_data_addr = 0x437000, + .reserved_ram_size = 11264, + .board_addr = 0x43d400, + .refclk_hz = 40000000, + .uarttx_pin = 11, + .flags = ATH6KL_HW_64BIT_RATES | + ATH6KL_HW_AP_INACTIVITY_MINS, + .fw = { + .dir = AR6004_HW_1_1_FW_DIR, + .fw = AR6004_HW_1_1_FIRMWARE_FILE, + }, + + .fw_board = AR6004_HW_1_1_BOARD_DATA_FILE, + .fw_default_board = AR6004_HW_1_1_DEFAULT_BOARD_DATA_FILE, + }, + { + .id = AR6004_HW_1_2_VERSION, + .name = "ar6004 hw 1.2", + .dataset_patch_addr = 0x436ecc, + .app_load_addr = 0x1234, + .board_ext_data_addr = 0x437000, + .reserved_ram_size = 9216, + .board_addr = 0x435c00, + .refclk_hz = 40000000, + .uarttx_pin = 11, + .flags = ATH6KL_HW_64BIT_RATES | + ATH6KL_HW_AP_INACTIVITY_MINS, + + .fw = { + .dir = AR6004_HW_1_2_FW_DIR, + .fw = AR6004_HW_1_2_FIRMWARE_FILE, + }, + .fw_board = AR6004_HW_1_2_BOARD_DATA_FILE, + .fw_default_board = AR6004_HW_1_2_DEFAULT_BOARD_DATA_FILE, + }, + { + .id = AR6004_HW_1_3_VERSION, + .name = "ar6004 hw 1.3", + .dataset_patch_addr = 0x437860, + .app_load_addr = 0x1234, + .board_ext_data_addr = 0x437000, + .reserved_ram_size = 7168, + .board_addr = 0x436400, + .refclk_hz = 40000000, + .uarttx_pin = 11, + .flags = ATH6KL_HW_64BIT_RATES | + ATH6KL_HW_AP_INACTIVITY_MINS | + ATH6KL_HW_MAP_LP_ENDPOINT, + + .fw = { + .dir = AR6004_HW_1_3_FW_DIR, + .fw = AR6004_HW_1_3_FIRMWARE_FILE, + }, + + .fw_board = AR6004_HW_1_3_BOARD_DATA_FILE, + .fw_default_board = AR6004_HW_1_3_DEFAULT_BOARD_DATA_FILE, + }, +}; + +/* + * Include definitions here that can be used to tune the WLAN module + * behavior. Different customers can tune the behavior as per their needs, + * here. + */ + +/* + * This configuration item enable/disable keepalive support. + * Keepalive support: In the absence of any data traffic to AP, null + * frames will be sent to the AP at periodic interval, to keep the association + * active. This configuration item defines the periodic interval. + * Use value of zero to disable keepalive support + * Default: 60 seconds + */ +#define WLAN_CONFIG_KEEP_ALIVE_INTERVAL 60 + +/* + * This configuration item sets the value of disconnect timeout + * Firmware delays sending the disconnec event to the host for this + * timeout after is gets disconnected from the current AP. + * If the firmware successly roams within the disconnect timeout + * it sends a new connect event + */ +#define WLAN_CONFIG_DISCONNECT_TIMEOUT 10 + + +#define ATH6KL_DATA_OFFSET 64 +struct sk_buff *ath6kl_buf_alloc(int size) +{ + struct sk_buff *skb; + u16 reserved; + + /* Add chacheline space at front and back of buffer */ + reserved = roundup((2 * L1_CACHE_BYTES) + ATH6KL_DATA_OFFSET + + sizeof(struct htc_packet) + ATH6KL_HTC_ALIGN_BYTES, 4); + skb = dev_alloc_skb(size + reserved); + + if (skb) + skb_reserve(skb, reserved - L1_CACHE_BYTES); + return skb; +} + +void ath6kl_init_profile_info(struct ath6kl_vif *vif) +{ + vif->ssid_len = 0; + memset(vif->ssid, 0, sizeof(vif->ssid)); + + vif->dot11_auth_mode = OPEN_AUTH; + vif->auth_mode = NONE_AUTH; + vif->prwise_crypto = NONE_CRYPT; + vif->prwise_crypto_len = 0; + vif->grp_crypto = NONE_CRYPT; + vif->grp_crypto_len = 0; + memset(vif->wep_key_list, 0, sizeof(vif->wep_key_list)); + memset(vif->req_bssid, 0, sizeof(vif->req_bssid)); + memset(vif->bssid, 0, sizeof(vif->bssid)); + vif->bss_ch = 0; +} + +static int ath6kl_set_host_app_area(struct ath6kl *ar) +{ + u32 address, data; + struct host_app_area host_app_area; + + /* Fetch the address of the host_app_area_s + * instance in the host interest area */ + address = ath6kl_get_hi_item_addr(ar, HI_ITEM(hi_app_host_interest)); + address = TARG_VTOP(ar->target_type, address); + + if (ath6kl_diag_read32(ar, address, &data)) + return -EIO; + + address = TARG_VTOP(ar->target_type, data); + host_app_area.wmi_protocol_ver = cpu_to_le32(WMI_PROTOCOL_VERSION); + if (ath6kl_diag_write(ar, address, (u8 *) &host_app_area, + sizeof(struct host_app_area))) + return -EIO; + + return 0; +} + +static inline void set_ac2_ep_map(struct ath6kl *ar, + u8 ac, + enum htc_endpoint_id ep) +{ + ar->ac2ep_map[ac] = ep; + ar->ep2ac_map[ep] = ac; +} + +/* connect to a service */ +static int ath6kl_connectservice(struct ath6kl *ar, + struct htc_service_connect_req *con_req, + char *desc) +{ + int status; + struct htc_service_connect_resp response; + + memset(&response, 0, sizeof(response)); + + status = ath6kl_htc_conn_service(ar->htc_target, con_req, &response); + if (status) { + ath6kl_err("failed to connect to %s service status:%d\n", + desc, status); + return status; + } + + switch (con_req->svc_id) { + case WMI_CONTROL_SVC: + if (test_bit(WMI_ENABLED, &ar->flag)) + ath6kl_wmi_set_control_ep(ar->wmi, response.endpoint); + ar->ctrl_ep = response.endpoint; + break; + case WMI_DATA_BE_SVC: + set_ac2_ep_map(ar, WMM_AC_BE, response.endpoint); + break; + case WMI_DATA_BK_SVC: + set_ac2_ep_map(ar, WMM_AC_BK, response.endpoint); + break; + case WMI_DATA_VI_SVC: + set_ac2_ep_map(ar, WMM_AC_VI, response.endpoint); + break; + case WMI_DATA_VO_SVC: + set_ac2_ep_map(ar, WMM_AC_VO, response.endpoint); + break; + default: + ath6kl_err("service id is not mapped %d\n", con_req->svc_id); + return -EINVAL; + } + + return 0; +} + +static int ath6kl_init_service_ep(struct ath6kl *ar) +{ + struct htc_service_connect_req connect; + + memset(&connect, 0, sizeof(connect)); + + /* these fields are the same for all service endpoints */ + connect.ep_cb.tx_comp_multi = ath6kl_tx_complete; + connect.ep_cb.rx = ath6kl_rx; + connect.ep_cb.rx_refill = ath6kl_rx_refill; + connect.ep_cb.tx_full = ath6kl_tx_queue_full; + + /* + * Set the max queue depth so that our ath6kl_tx_queue_full handler + * gets called. + */ + connect.max_txq_depth = MAX_DEFAULT_SEND_QUEUE_DEPTH; + connect.ep_cb.rx_refill_thresh = ATH6KL_MAX_RX_BUFFERS / 4; + if (!connect.ep_cb.rx_refill_thresh) + connect.ep_cb.rx_refill_thresh++; + + /* connect to control service */ + connect.svc_id = WMI_CONTROL_SVC; + if (ath6kl_connectservice(ar, &connect, "WMI CONTROL")) + return -EIO; + + connect.flags |= HTC_FLGS_TX_BNDL_PAD_EN; + + /* + * Limit the HTC message size on the send path, although e can + * receive A-MSDU frames of 4K, we will only send ethernet-sized + * (802.3) frames on the send path. + */ + connect.max_rxmsg_sz = WMI_MAX_TX_DATA_FRAME_LENGTH; + + /* + * To reduce the amount of committed memory for larger A_MSDU + * frames, use the recv-alloc threshold mechanism for larger + * packets. + */ + connect.ep_cb.rx_alloc_thresh = ATH6KL_BUFFER_SIZE; + connect.ep_cb.rx_allocthresh = ath6kl_alloc_amsdu_rxbuf; + + /* + * For the remaining data services set the connection flag to + * reduce dribbling, if configured to do so. + */ + connect.conn_flags |= HTC_CONN_FLGS_REDUCE_CRED_DRIB; + connect.conn_flags &= ~HTC_CONN_FLGS_THRESH_MASK; + connect.conn_flags |= HTC_CONN_FLGS_THRESH_LVL_HALF; + + connect.svc_id = WMI_DATA_BE_SVC; + + if (ath6kl_connectservice(ar, &connect, "WMI DATA BE")) + return -EIO; + + /* connect to back-ground map this to WMI LOW_PRI */ + connect.svc_id = WMI_DATA_BK_SVC; + if (ath6kl_connectservice(ar, &connect, "WMI DATA BK")) + return -EIO; + + /* connect to Video service, map this to HI PRI */ + connect.svc_id = WMI_DATA_VI_SVC; + if (ath6kl_connectservice(ar, &connect, "WMI DATA VI")) + return -EIO; + + /* + * Connect to VO service, this is currently not mapped to a WMI + * priority stream due to historical reasons. WMI originally + * defined 3 priorities over 3 mailboxes We can change this when + * WMI is reworked so that priorities are not dependent on + * mailboxes. + */ + connect.svc_id = WMI_DATA_VO_SVC; + if (ath6kl_connectservice(ar, &connect, "WMI DATA VO")) + return -EIO; + + return 0; +} + +void ath6kl_init_control_info(struct ath6kl_vif *vif) +{ + ath6kl_init_profile_info(vif); + vif->def_txkey_index = 0; + memset(vif->wep_key_list, 0, sizeof(vif->wep_key_list)); + vif->ch_hint = 0; +} + +/* + * Set HTC/Mbox operational parameters, this can only be called when the + * target is in the BMI phase. + */ +static int ath6kl_set_htc_params(struct ath6kl *ar, u32 mbox_isr_yield_val, + u8 htc_ctrl_buf) +{ + int status; + u32 blk_size; + + blk_size = ar->mbox_info.block_size; + + if (htc_ctrl_buf) + blk_size |= ((u32)htc_ctrl_buf) << 16; + + /* set the host interest area for the block size */ + status = ath6kl_bmi_write_hi32(ar, hi_mbox_io_block_sz, blk_size); + if (status) { + ath6kl_err("bmi_write_memory for IO block size failed\n"); + goto out; + } + + ath6kl_dbg(ATH6KL_DBG_TRC, "block size set: %d (target addr:0x%X)\n", + blk_size, + ath6kl_get_hi_item_addr(ar, HI_ITEM(hi_mbox_io_block_sz))); + + if (mbox_isr_yield_val) { + /* set the host interest area for the mbox ISR yield limit */ + status = ath6kl_bmi_write_hi32(ar, hi_mbox_isr_yield_limit, + mbox_isr_yield_val); + if (status) { + ath6kl_err("bmi_write_memory for yield limit failed\n"); + goto out; + } + } + +out: + return status; +} + +static int ath6kl_target_config_wlan_params(struct ath6kl *ar, int idx) +{ + int ret; + + /* + * Configure the device for rx dot11 header rules. "0,0" are the + * default values. Required if checksum offload is needed. Set + * RxMetaVersion to 2. + */ + ret = ath6kl_wmi_set_rx_frame_format_cmd(ar->wmi, idx, + ar->rx_meta_ver, 0, 0); + if (ret) { + ath6kl_err("unable to set the rx frame format: %d\n", ret); + return ret; + } + + if (ar->conf_flags & ATH6KL_CONF_IGNORE_PS_FAIL_EVT_IN_SCAN) { + ret = ath6kl_wmi_pmparams_cmd(ar->wmi, idx, 0, 1, 0, 0, 1, + IGNORE_PS_FAIL_DURING_SCAN); + if (ret) { + ath6kl_err("unable to set power save fail event policy: %d\n", + ret); + return ret; + } + } + + if (!(ar->conf_flags & ATH6KL_CONF_IGNORE_ERP_BARKER)) { + ret = ath6kl_wmi_set_lpreamble_cmd(ar->wmi, idx, 0, + WMI_FOLLOW_BARKER_IN_ERP); + if (ret) { + ath6kl_err("unable to set barker preamble policy: %d\n", + ret); + return ret; + } + } + + ret = ath6kl_wmi_set_keepalive_cmd(ar->wmi, idx, + WLAN_CONFIG_KEEP_ALIVE_INTERVAL); + if (ret) { + ath6kl_err("unable to set keep alive interval: %d\n", ret); + return ret; + } + + ret = ath6kl_wmi_disctimeout_cmd(ar->wmi, idx, + WLAN_CONFIG_DISCONNECT_TIMEOUT); + if (ret) { + ath6kl_err("unable to set disconnect timeout: %d\n", ret); + return ret; + } + + if (!(ar->conf_flags & ATH6KL_CONF_ENABLE_TX_BURST)) { + ret = ath6kl_wmi_set_wmm_txop(ar->wmi, idx, WMI_TXOP_DISABLED); + if (ret) { + ath6kl_err("unable to set txop bursting: %d\n", ret); + return ret; + } + } + + if (ar->p2p && (ar->vif_max == 1 || idx)) { + ret = ath6kl_wmi_info_req_cmd(ar->wmi, idx, + P2P_FLAG_CAPABILITIES_REQ | + P2P_FLAG_MACADDR_REQ | + P2P_FLAG_HMODEL_REQ); + if (ret) { + ath6kl_dbg(ATH6KL_DBG_TRC, + "failed to request P2P capabilities (%d) - assuming P2P not supported\n", + ret); + ar->p2p = false; + } + } + + if (ar->p2p && (ar->vif_max == 1 || idx)) { + /* Enable Probe Request reporting for P2P */ + ret = ath6kl_wmi_probe_report_req_cmd(ar->wmi, idx, true); + if (ret) { + ath6kl_dbg(ATH6KL_DBG_TRC, + "failed to enable Probe Request reporting (%d)\n", + ret); + } + } + + return ret; +} + +int ath6kl_configure_target(struct ath6kl *ar) +{ + u32 param, ram_reserved_size; + u8 fw_iftype, fw_mode = 0, fw_submode = 0; + int i, status; + + param = !!(ar->conf_flags & ATH6KL_CONF_UART_DEBUG); + if (ath6kl_bmi_write_hi32(ar, hi_serial_enable, param)) { + ath6kl_err("bmi_write_memory for uart debug failed\n"); + return -EIO; + } + + /* + * Note: Even though the firmware interface type is + * chosen as BSS_STA for all three interfaces, can + * be configured to IBSS/AP as long as the fw submode + * remains normal mode (0 - AP, STA and IBSS). But + * due to an target assert in firmware only one interface is + * configured for now. + */ + fw_iftype = HI_OPTION_FW_MODE_BSS_STA; + + for (i = 0; i < ar->vif_max; i++) + fw_mode |= fw_iftype << (i * HI_OPTION_FW_MODE_BITS); + + /* + * Submodes when fw does not support dynamic interface + * switching: + * vif[0] - AP/STA/IBSS + * vif[1] - "P2P dev"/"P2P GO"/"P2P Client" + * vif[2] - "P2P dev"/"P2P GO"/"P2P Client" + * Otherwise, All the interface are initialized to p2p dev. + */ + + if (test_bit(ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX, + ar->fw_capabilities)) { + for (i = 0; i < ar->vif_max; i++) + fw_submode |= HI_OPTION_FW_SUBMODE_P2PDEV << + (i * HI_OPTION_FW_SUBMODE_BITS); + } else { + for (i = 0; i < ar->max_norm_iface; i++) + fw_submode |= HI_OPTION_FW_SUBMODE_NONE << + (i * HI_OPTION_FW_SUBMODE_BITS); + + for (i = ar->max_norm_iface; i < ar->vif_max; i++) + fw_submode |= HI_OPTION_FW_SUBMODE_P2PDEV << + (i * HI_OPTION_FW_SUBMODE_BITS); + + if (ar->p2p && ar->vif_max == 1) + fw_submode = HI_OPTION_FW_SUBMODE_P2PDEV; + } + + if (ath6kl_bmi_write_hi32(ar, hi_app_host_interest, + HTC_PROTOCOL_VERSION) != 0) { + ath6kl_err("bmi_write_memory for htc version failed\n"); + return -EIO; + } + + /* set the firmware mode to STA/IBSS/AP */ + param = 0; + + if (ath6kl_bmi_read_hi32(ar, hi_option_flag, ¶m) != 0) { + ath6kl_err("bmi_read_memory for setting fwmode failed\n"); + return -EIO; + } + + param |= (ar->vif_max << HI_OPTION_NUM_DEV_SHIFT); + param |= fw_mode << HI_OPTION_FW_MODE_SHIFT; + param |= fw_submode << HI_OPTION_FW_SUBMODE_SHIFT; + + param |= (0 << HI_OPTION_MAC_ADDR_METHOD_SHIFT); + param |= (0 << HI_OPTION_FW_BRIDGE_SHIFT); + + if (ath6kl_bmi_write_hi32(ar, hi_option_flag, param) != 0) { + ath6kl_err("bmi_write_memory for setting fwmode failed\n"); + return -EIO; + } + + ath6kl_dbg(ATH6KL_DBG_TRC, "firmware mode set\n"); + + /* + * Hardcode the address use for the extended board data + * Ideally this should be pre-allocate by the OS at boot time + * But since it is a new feature and board data is loaded + * at init time, we have to workaround this from host. + * It is difficult to patch the firmware boot code, + * but possible in theory. + */ + + if (ar->target_type == TARGET_TYPE_AR6003) { + param = ar->hw.board_ext_data_addr; + ram_reserved_size = ar->hw.reserved_ram_size; + + if (ath6kl_bmi_write_hi32(ar, hi_board_ext_data, param) != 0) { + ath6kl_err("bmi_write_memory for hi_board_ext_data failed\n"); + return -EIO; + } + + if (ath6kl_bmi_write_hi32(ar, hi_end_ram_reserve_sz, + ram_reserved_size) != 0) { + ath6kl_err("bmi_write_memory for hi_end_ram_reserve_sz failed\n"); + return -EIO; + } + } + + /* set the block size for the target */ + if (ath6kl_set_htc_params(ar, MBOX_YIELD_LIMIT, 0)) + /* use default number of control buffers */ + return -EIO; + + /* Configure GPIO AR600x UART */ + status = ath6kl_bmi_write_hi32(ar, hi_dbg_uart_txpin, + ar->hw.uarttx_pin); + if (status) + return status; + + /* Configure target refclk_hz */ + status = ath6kl_bmi_write_hi32(ar, hi_refclk_hz, ar->hw.refclk_hz); + if (status) + return status; + + return 0; +} + +/* firmware upload */ +static int ath6kl_get_fw(struct ath6kl *ar, const char *filename, + u8 **fw, size_t *fw_len) +{ + const struct firmware *fw_entry; + int ret; + + ret = request_firmware(&fw_entry, filename, ar->dev); + if (ret) + return ret; + + *fw_len = fw_entry->size; + *fw = kmemdup(fw_entry->data, fw_entry->size, GFP_KERNEL); + + if (*fw == NULL) + ret = -ENOMEM; + + release_firmware(fw_entry); + + return ret; +} + +#ifdef CONFIG_OF +/* + * Check the device tree for a board-id and use it to construct + * the pathname to the firmware file. Used (for now) to find a + * fallback to the "bdata.bin" file--typically a symlink to the + * appropriate board-specific file. + */ +static bool check_device_tree(struct ath6kl *ar) +{ + static const char *board_id_prop = "atheros,board-id"; + struct device_node *node; + char board_filename[64]; + const char *board_id; + int ret; + + for_each_compatible_node(node, NULL, "atheros,ath6kl") { + board_id = of_get_property(node, board_id_prop, NULL); + if (board_id == NULL) { + ath6kl_warn("No \"%s\" property on %s node.\n", + board_id_prop, node->name); + continue; + } + snprintf(board_filename, sizeof(board_filename), + "%s/bdata.%s.bin", ar->hw.fw.dir, board_id); + + ret = ath6kl_get_fw(ar, board_filename, &ar->fw_board, + &ar->fw_board_len); + if (ret) { + ath6kl_err("Failed to get DT board file %s: %d\n", + board_filename, ret); + continue; + } + return true; + } + return false; +} +#else +static bool check_device_tree(struct ath6kl *ar) +{ + return false; +} +#endif /* CONFIG_OF */ + +static int ath6kl_fetch_board_file(struct ath6kl *ar) +{ + const char *filename; + int ret; + + if (ar->fw_board != NULL) + return 0; + + if (WARN_ON(ar->hw.fw_board == NULL)) + return -EINVAL; + + filename = ar->hw.fw_board; + + ret = ath6kl_get_fw(ar, filename, &ar->fw_board, + &ar->fw_board_len); + if (ret == 0) { + /* managed to get proper board file */ + return 0; + } + + if (check_device_tree(ar)) { + /* got board file from device tree */ + return 0; + } + + /* there was no proper board file, try to use default instead */ + ath6kl_warn("Failed to get board file %s (%d), trying to find default board file.\n", + filename, ret); + + filename = ar->hw.fw_default_board; + + ret = ath6kl_get_fw(ar, filename, &ar->fw_board, + &ar->fw_board_len); + if (ret) { + ath6kl_err("Failed to get default board file %s: %d\n", + filename, ret); + return ret; + } + + ath6kl_warn("WARNING! No proper board file was not found, instead using a default board file.\n"); + ath6kl_warn("Most likely your hardware won't work as specified. Install correct board file!\n"); + + return 0; +} + +static int ath6kl_fetch_otp_file(struct ath6kl *ar) +{ + char filename[100]; + int ret; + + if (ar->fw_otp != NULL) + return 0; + + if (ar->hw.fw.otp == NULL) { + ath6kl_dbg(ATH6KL_DBG_BOOT, + "no OTP file configured for this hw\n"); + return 0; + } + + snprintf(filename, sizeof(filename), "%s/%s", + ar->hw.fw.dir, ar->hw.fw.otp); + + ret = ath6kl_get_fw(ar, filename, &ar->fw_otp, + &ar->fw_otp_len); + if (ret) { + ath6kl_err("Failed to get OTP file %s: %d\n", + filename, ret); + return ret; + } + + return 0; +} + +static int ath6kl_fetch_testmode_file(struct ath6kl *ar) +{ + char filename[100]; + int ret; + + if (ar->testmode == 0) + return 0; + + ath6kl_dbg(ATH6KL_DBG_BOOT, "testmode %d\n", ar->testmode); + + if (ar->testmode == 2) { + if (ar->hw.fw.utf == NULL) { + ath6kl_warn("testmode 2 not supported\n"); + return -EOPNOTSUPP; + } + + snprintf(filename, sizeof(filename), "%s/%s", + ar->hw.fw.dir, ar->hw.fw.utf); + } else { + if (ar->hw.fw.tcmd == NULL) { + ath6kl_warn("testmode 1 not supported\n"); + return -EOPNOTSUPP; + } + + snprintf(filename, sizeof(filename), "%s/%s", + ar->hw.fw.dir, ar->hw.fw.tcmd); + } + + set_bit(TESTMODE, &ar->flag); + + ret = ath6kl_get_fw(ar, filename, &ar->fw, &ar->fw_len); + if (ret) { + ath6kl_err("Failed to get testmode %d firmware file %s: %d\n", + ar->testmode, filename, ret); + return ret; + } + + return 0; +} + +static int ath6kl_fetch_fw_file(struct ath6kl *ar) +{ + char filename[100]; + int ret; + + if (ar->fw != NULL) + return 0; + + /* FIXME: remove WARN_ON() as we won't support FW API 1 for long */ + if (WARN_ON(ar->hw.fw.fw == NULL)) + return -EINVAL; + + snprintf(filename, sizeof(filename), "%s/%s", + ar->hw.fw.dir, ar->hw.fw.fw); + + ret = ath6kl_get_fw(ar, filename, &ar->fw, &ar->fw_len); + if (ret) { + ath6kl_err("Failed to get firmware file %s: %d\n", + filename, ret); + return ret; + } + + return 0; +} + +static int ath6kl_fetch_patch_file(struct ath6kl *ar) +{ + char filename[100]; + int ret; + + if (ar->fw_patch != NULL) + return 0; + + if (ar->hw.fw.patch == NULL) + return 0; + + snprintf(filename, sizeof(filename), "%s/%s", + ar->hw.fw.dir, ar->hw.fw.patch); + + ret = ath6kl_get_fw(ar, filename, &ar->fw_patch, + &ar->fw_patch_len); + if (ret) { + ath6kl_err("Failed to get patch file %s: %d\n", + filename, ret); + return ret; + } + + return 0; +} + +static int ath6kl_fetch_testscript_file(struct ath6kl *ar) +{ + char filename[100]; + int ret; + + if (ar->testmode != 2) + return 0; + + if (ar->fw_testscript != NULL) + return 0; + + if (ar->hw.fw.testscript == NULL) + return 0; + + snprintf(filename, sizeof(filename), "%s/%s", + ar->hw.fw.dir, ar->hw.fw.testscript); + + ret = ath6kl_get_fw(ar, filename, &ar->fw_testscript, + &ar->fw_testscript_len); + if (ret) { + ath6kl_err("Failed to get testscript file %s: %d\n", + filename, ret); + return ret; + } + + return 0; +} + +static int ath6kl_fetch_fw_api1(struct ath6kl *ar) +{ + int ret; + + ret = ath6kl_fetch_otp_file(ar); + if (ret) + return ret; + + ret = ath6kl_fetch_fw_file(ar); + if (ret) + return ret; + + ret = ath6kl_fetch_patch_file(ar); + if (ret) + return ret; + + ret = ath6kl_fetch_testscript_file(ar); + if (ret) + return ret; + + return 0; +} + +static int ath6kl_fetch_fw_apin(struct ath6kl *ar, const char *name) +{ + size_t magic_len, len, ie_len; + const struct firmware *fw; + struct ath6kl_fw_ie *hdr; + char filename[100]; + const u8 *data; + int ret, ie_id, i, index, bit; + __le32 *val; + + snprintf(filename, sizeof(filename), "%s/%s", ar->hw.fw.dir, name); + + ret = request_firmware(&fw, filename, ar->dev); + if (ret) + return ret; + + data = fw->data; + len = fw->size; + + /* magic also includes the null byte, check that as well */ + magic_len = strlen(ATH6KL_FIRMWARE_MAGIC) + 1; + + if (len < magic_len) { + ret = -EINVAL; + goto out; + } + + if (memcmp(data, ATH6KL_FIRMWARE_MAGIC, magic_len) != 0) { + ret = -EINVAL; + goto out; + } + + len -= magic_len; + data += magic_len; + + /* loop elements */ + while (len > sizeof(struct ath6kl_fw_ie)) { + /* hdr is unaligned! */ + hdr = (struct ath6kl_fw_ie *) data; + + ie_id = le32_to_cpup(&hdr->id); + ie_len = le32_to_cpup(&hdr->len); + + len -= sizeof(*hdr); + data += sizeof(*hdr); + + if (len < ie_len) { + ret = -EINVAL; + goto out; + } + + switch (ie_id) { + case ATH6KL_FW_IE_FW_VERSION: + strlcpy(ar->wiphy->fw_version, data, + sizeof(ar->wiphy->fw_version)); + + ath6kl_dbg(ATH6KL_DBG_BOOT, + "found fw version %s\n", + ar->wiphy->fw_version); + break; + case ATH6KL_FW_IE_OTP_IMAGE: + ath6kl_dbg(ATH6KL_DBG_BOOT, "found otp image ie (%zd B)\n", + ie_len); + + ar->fw_otp = kmemdup(data, ie_len, GFP_KERNEL); + + if (ar->fw_otp == NULL) { + ret = -ENOMEM; + goto out; + } + + ar->fw_otp_len = ie_len; + break; + case ATH6KL_FW_IE_FW_IMAGE: + ath6kl_dbg(ATH6KL_DBG_BOOT, "found fw image ie (%zd B)\n", + ie_len); + + /* in testmode we already might have a fw file */ + if (ar->fw != NULL) + break; + + ar->fw = vmalloc(ie_len); + + if (ar->fw == NULL) { + ret = -ENOMEM; + goto out; + } + + memcpy(ar->fw, data, ie_len); + ar->fw_len = ie_len; + break; + case ATH6KL_FW_IE_PATCH_IMAGE: + ath6kl_dbg(ATH6KL_DBG_BOOT, "found patch image ie (%zd B)\n", + ie_len); + + ar->fw_patch = kmemdup(data, ie_len, GFP_KERNEL); + + if (ar->fw_patch == NULL) { + ret = -ENOMEM; + goto out; + } + + ar->fw_patch_len = ie_len; + break; + case ATH6KL_FW_IE_RESERVED_RAM_SIZE: + val = (__le32 *) data; + ar->hw.reserved_ram_size = le32_to_cpup(val); + + ath6kl_dbg(ATH6KL_DBG_BOOT, + "found reserved ram size ie 0x%d\n", + ar->hw.reserved_ram_size); + break; + case ATH6KL_FW_IE_CAPABILITIES: + ath6kl_dbg(ATH6KL_DBG_BOOT, + "found firmware capabilities ie (%zd B)\n", + ie_len); + + for (i = 0; i < ATH6KL_FW_CAPABILITY_MAX; i++) { + index = i / 8; + bit = i % 8; + + if (index == ie_len) + break; + + if (data[index] & (1 << bit)) + __set_bit(i, ar->fw_capabilities); + } + + ath6kl_dbg_dump(ATH6KL_DBG_BOOT, "capabilities", "", + ar->fw_capabilities, + sizeof(ar->fw_capabilities)); + break; + case ATH6KL_FW_IE_PATCH_ADDR: + if (ie_len != sizeof(*val)) + break; + + val = (__le32 *) data; + ar->hw.dataset_patch_addr = le32_to_cpup(val); + + ath6kl_dbg(ATH6KL_DBG_BOOT, + "found patch address ie 0x%x\n", + ar->hw.dataset_patch_addr); + break; + case ATH6KL_FW_IE_BOARD_ADDR: + if (ie_len != sizeof(*val)) + break; + + val = (__le32 *) data; + ar->hw.board_addr = le32_to_cpup(val); + + ath6kl_dbg(ATH6KL_DBG_BOOT, + "found board address ie 0x%x\n", + ar->hw.board_addr); + break; + case ATH6KL_FW_IE_VIF_MAX: + if (ie_len != sizeof(*val)) + break; + + val = (__le32 *) data; + ar->vif_max = min_t(unsigned int, le32_to_cpup(val), + ATH6KL_VIF_MAX); + + if (ar->vif_max > 1 && !ar->p2p) + ar->max_norm_iface = 2; + + ath6kl_dbg(ATH6KL_DBG_BOOT, + "found vif max ie %d\n", ar->vif_max); + break; + default: + ath6kl_dbg(ATH6KL_DBG_BOOT, "Unknown fw ie: %u\n", + le32_to_cpup(&hdr->id)); + break; + } + + len -= ie_len; + data += ie_len; + }; + + ret = 0; +out: + release_firmware(fw); + + return ret; +} + +int ath6kl_init_fetch_firmwares(struct ath6kl *ar) +{ + int ret; + + ret = ath6kl_fetch_board_file(ar); + if (ret) + return ret; + + ret = ath6kl_fetch_testmode_file(ar); + if (ret) + return ret; + + ret = ath6kl_fetch_fw_apin(ar, ATH6KL_FW_API4_FILE); + if (ret == 0) { + ar->fw_api = 4; + goto out; + } + + ret = ath6kl_fetch_fw_apin(ar, ATH6KL_FW_API3_FILE); + if (ret == 0) { + ar->fw_api = 3; + goto out; + } + + ret = ath6kl_fetch_fw_apin(ar, ATH6KL_FW_API2_FILE); + if (ret == 0) { + ar->fw_api = 2; + goto out; + } + + ret = ath6kl_fetch_fw_api1(ar); + if (ret) + return ret; + + ar->fw_api = 1; + +out: + ath6kl_dbg(ATH6KL_DBG_BOOT, "using fw api %d\n", ar->fw_api); + + return 0; +} + +static int ath6kl_upload_board_file(struct ath6kl *ar) +{ + u32 board_address, board_ext_address, param; + u32 board_data_size, board_ext_data_size; + int ret; + + if (WARN_ON(ar->fw_board == NULL)) + return -ENOENT; + + /* + * Determine where in Target RAM to write Board Data. + * For AR6004, host determine Target RAM address for + * writing board data. + */ + if (ar->hw.board_addr != 0) { + board_address = ar->hw.board_addr; + ath6kl_bmi_write_hi32(ar, hi_board_data, + board_address); + } else { + ath6kl_bmi_read_hi32(ar, hi_board_data, &board_address); + } + + /* determine where in target ram to write extended board data */ + ath6kl_bmi_read_hi32(ar, hi_board_ext_data, &board_ext_address); + + if (ar->target_type == TARGET_TYPE_AR6003 && + board_ext_address == 0) { + ath6kl_err("Failed to get board file target address.\n"); + return -EINVAL; + } + + switch (ar->target_type) { + case TARGET_TYPE_AR6003: + board_data_size = AR6003_BOARD_DATA_SZ; + board_ext_data_size = AR6003_BOARD_EXT_DATA_SZ; + if (ar->fw_board_len > (board_data_size + board_ext_data_size)) + board_ext_data_size = AR6003_BOARD_EXT_DATA_SZ_V2; + break; + case TARGET_TYPE_AR6004: + board_data_size = AR6004_BOARD_DATA_SZ; + board_ext_data_size = AR6004_BOARD_EXT_DATA_SZ; + break; + default: + WARN_ON(1); + return -EINVAL; + break; + } + + if (board_ext_address && + ar->fw_board_len == (board_data_size + board_ext_data_size)) { + + /* write extended board data */ + ath6kl_dbg(ATH6KL_DBG_BOOT, + "writing extended board data to 0x%x (%d B)\n", + board_ext_address, board_ext_data_size); + + ret = ath6kl_bmi_write(ar, board_ext_address, + ar->fw_board + board_data_size, + board_ext_data_size); + if (ret) { + ath6kl_err("Failed to write extended board data: %d\n", + ret); + return ret; + } + + /* record that extended board data is initialized */ + param = (board_ext_data_size << 16) | 1; + + ath6kl_bmi_write_hi32(ar, hi_board_ext_data_config, param); + } + + if (ar->fw_board_len < board_data_size) { + ath6kl_err("Too small board file: %zu\n", ar->fw_board_len); + ret = -EINVAL; + return ret; + } + + ath6kl_dbg(ATH6KL_DBG_BOOT, "writing board file to 0x%x (%d B)\n", + board_address, board_data_size); + + ret = ath6kl_bmi_write(ar, board_address, ar->fw_board, + board_data_size); + + if (ret) { + ath6kl_err("Board file bmi write failed: %d\n", ret); + return ret; + } + + /* record the fact that Board Data IS initialized */ + ath6kl_bmi_write_hi32(ar, hi_board_data_initialized, 1); + + return ret; +} + +static int ath6kl_upload_otp(struct ath6kl *ar) +{ + u32 address, param; + bool from_hw = false; + int ret; + + if (ar->fw_otp == NULL) + return 0; + + address = ar->hw.app_load_addr; + + ath6kl_dbg(ATH6KL_DBG_BOOT, "writing otp to 0x%x (%zd B)\n", address, + ar->fw_otp_len); + + ret = ath6kl_bmi_fast_download(ar, address, ar->fw_otp, + ar->fw_otp_len); + if (ret) { + ath6kl_err("Failed to upload OTP file: %d\n", ret); + return ret; + } + + /* read firmware start address */ + ret = ath6kl_bmi_read_hi32(ar, hi_app_start, &address); + + if (ret) { + ath6kl_err("Failed to read hi_app_start: %d\n", ret); + return ret; + } + + if (ar->hw.app_start_override_addr == 0) { + ar->hw.app_start_override_addr = address; + from_hw = true; + } + + ath6kl_dbg(ATH6KL_DBG_BOOT, "app_start_override_addr%s 0x%x\n", + from_hw ? " (from hw)" : "", + ar->hw.app_start_override_addr); + + /* execute the OTP code */ + ath6kl_dbg(ATH6KL_DBG_BOOT, "executing OTP at 0x%x\n", + ar->hw.app_start_override_addr); + param = 0; + ath6kl_bmi_execute(ar, ar->hw.app_start_override_addr, ¶m); + + return ret; +} + +static int ath6kl_upload_firmware(struct ath6kl *ar) +{ + u32 address; + int ret; + + if (WARN_ON(ar->fw == NULL)) + return 0; + + address = ar->hw.app_load_addr; + + ath6kl_dbg(ATH6KL_DBG_BOOT, "writing firmware to 0x%x (%zd B)\n", + address, ar->fw_len); + + ret = ath6kl_bmi_fast_download(ar, address, ar->fw, ar->fw_len); + + if (ret) { + ath6kl_err("Failed to write firmware: %d\n", ret); + return ret; + } + + /* + * Set starting address for firmware + * Don't need to setup app_start override addr on AR6004 + */ + if (ar->target_type != TARGET_TYPE_AR6004) { + address = ar->hw.app_start_override_addr; + ath6kl_bmi_set_app_start(ar, address); + } + return ret; +} + +static int ath6kl_upload_patch(struct ath6kl *ar) +{ + u32 address; + int ret; + + if (ar->fw_patch == NULL) + return 0; + + address = ar->hw.dataset_patch_addr; + + ath6kl_dbg(ATH6KL_DBG_BOOT, "writing patch to 0x%x (%zd B)\n", + address, ar->fw_patch_len); + + ret = ath6kl_bmi_write(ar, address, ar->fw_patch, ar->fw_patch_len); + if (ret) { + ath6kl_err("Failed to write patch file: %d\n", ret); + return ret; + } + + ath6kl_bmi_write_hi32(ar, hi_dset_list_head, address); + + return 0; +} + +static int ath6kl_upload_testscript(struct ath6kl *ar) +{ + u32 address; + int ret; + + if (ar->testmode != 2) + return 0; + + if (ar->fw_testscript == NULL) + return 0; + + address = ar->hw.testscript_addr; + + ath6kl_dbg(ATH6KL_DBG_BOOT, "writing testscript to 0x%x (%zd B)\n", + address, ar->fw_testscript_len); + + ret = ath6kl_bmi_write(ar, address, ar->fw_testscript, + ar->fw_testscript_len); + if (ret) { + ath6kl_err("Failed to write testscript file: %d\n", ret); + return ret; + } + + ath6kl_bmi_write_hi32(ar, hi_ota_testscript, address); + ath6kl_bmi_write_hi32(ar, hi_end_ram_reserve_sz, 4096); + ath6kl_bmi_write_hi32(ar, hi_test_apps_related, 1); + + return 0; +} + +static int ath6kl_init_upload(struct ath6kl *ar) +{ + u32 param, options, sleep, address; + int status = 0; + + if (ar->target_type != TARGET_TYPE_AR6003 && + ar->target_type != TARGET_TYPE_AR6004) + return -EINVAL; + + /* temporarily disable system sleep */ + address = MBOX_BASE_ADDRESS + LOCAL_SCRATCH_ADDRESS; + status = ath6kl_bmi_reg_read(ar, address, ¶m); + if (status) + return status; + + options = param; + + param |= ATH6KL_OPTION_SLEEP_DISABLE; + status = ath6kl_bmi_reg_write(ar, address, param); + if (status) + return status; + + address = RTC_BASE_ADDRESS + SYSTEM_SLEEP_ADDRESS; + status = ath6kl_bmi_reg_read(ar, address, ¶m); + if (status) + return status; + + sleep = param; + + param |= SM(SYSTEM_SLEEP_DISABLE, 1); + status = ath6kl_bmi_reg_write(ar, address, param); + if (status) + return status; + + ath6kl_dbg(ATH6KL_DBG_TRC, "old options: %d, old sleep: %d\n", + options, sleep); + + /* program analog PLL register */ + /* no need to control 40/44MHz clock on AR6004 */ + if (ar->target_type != TARGET_TYPE_AR6004) { + status = ath6kl_bmi_reg_write(ar, ATH6KL_ANALOG_PLL_REGISTER, + 0xF9104001); + + if (status) + return status; + + /* Run at 80/88MHz by default */ + param = SM(CPU_CLOCK_STANDARD, 1); + + address = RTC_BASE_ADDRESS + CPU_CLOCK_ADDRESS; + status = ath6kl_bmi_reg_write(ar, address, param); + if (status) + return status; + } + + param = 0; + address = RTC_BASE_ADDRESS + LPO_CAL_ADDRESS; + param = SM(LPO_CAL_ENABLE, 1); + status = ath6kl_bmi_reg_write(ar, address, param); + if (status) + return status; + + /* WAR to avoid SDIO CRC err */ + if (ar->hw.flags & ATH6KL_HW_SDIO_CRC_ERROR_WAR) { + ath6kl_err("temporary war to avoid sdio crc error\n"); + + param = 0x28; + address = GPIO_BASE_ADDRESS + GPIO_PIN9_ADDRESS; + status = ath6kl_bmi_reg_write(ar, address, param); + if (status) + return status; + + param = 0x20; + + address = GPIO_BASE_ADDRESS + GPIO_PIN10_ADDRESS; + status = ath6kl_bmi_reg_write(ar, address, param); + if (status) + return status; + + address = GPIO_BASE_ADDRESS + GPIO_PIN11_ADDRESS; + status = ath6kl_bmi_reg_write(ar, address, param); + if (status) + return status; + + address = GPIO_BASE_ADDRESS + GPIO_PIN12_ADDRESS; + status = ath6kl_bmi_reg_write(ar, address, param); + if (status) + return status; + + address = GPIO_BASE_ADDRESS + GPIO_PIN13_ADDRESS; + status = ath6kl_bmi_reg_write(ar, address, param); + if (status) + return status; + } + + /* write EEPROM data to Target RAM */ + status = ath6kl_upload_board_file(ar); + if (status) + return status; + + /* transfer One time Programmable data */ + status = ath6kl_upload_otp(ar); + if (status) + return status; + + /* Download Target firmware */ + status = ath6kl_upload_firmware(ar); + if (status) + return status; + + status = ath6kl_upload_patch(ar); + if (status) + return status; + + /* Download the test script */ + status = ath6kl_upload_testscript(ar); + if (status) + return status; + + /* Restore system sleep */ + address = RTC_BASE_ADDRESS + SYSTEM_SLEEP_ADDRESS; + status = ath6kl_bmi_reg_write(ar, address, sleep); + if (status) + return status; + + address = MBOX_BASE_ADDRESS + LOCAL_SCRATCH_ADDRESS; + param = options | 0x20; + status = ath6kl_bmi_reg_write(ar, address, param); + if (status) + return status; + + return status; +} + +int ath6kl_init_hw_params(struct ath6kl *ar) +{ + const struct ath6kl_hw *uninitialized_var(hw); + int i; + + for (i = 0; i < ARRAY_SIZE(hw_list); i++) { + hw = &hw_list[i]; + + if (hw->id == ar->version.target_ver) + break; + } + + if (i == ARRAY_SIZE(hw_list)) { + ath6kl_err("Unsupported hardware version: 0x%x\n", + ar->version.target_ver); + return -EINVAL; + } + + ar->hw = *hw; + + ath6kl_dbg(ATH6KL_DBG_BOOT, + "target_ver 0x%x target_type 0x%x dataset_patch 0x%x app_load_addr 0x%x\n", + ar->version.target_ver, ar->target_type, + ar->hw.dataset_patch_addr, ar->hw.app_load_addr); + ath6kl_dbg(ATH6KL_DBG_BOOT, + "app_start_override_addr 0x%x board_ext_data_addr 0x%x reserved_ram_size 0x%x", + ar->hw.app_start_override_addr, ar->hw.board_ext_data_addr, + ar->hw.reserved_ram_size); + ath6kl_dbg(ATH6KL_DBG_BOOT, + "refclk_hz %d uarttx_pin %d", + ar->hw.refclk_hz, ar->hw.uarttx_pin); + + return 0; +} + +static const char *ath6kl_init_get_hif_name(enum ath6kl_hif_type type) +{ + switch (type) { + case ATH6KL_HIF_TYPE_SDIO: + return "sdio"; + case ATH6KL_HIF_TYPE_USB: + return "usb"; + } + + return NULL; +} + + +static const struct fw_capa_str_map { + int id; + const char *name; +} fw_capa_map[] = { + { ATH6KL_FW_CAPABILITY_HOST_P2P, "host-p2p" }, + { ATH6KL_FW_CAPABILITY_SCHED_SCAN, "sched-scan" }, + { ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX, "sta-p2pdev-duplex" }, + { ATH6KL_FW_CAPABILITY_INACTIVITY_TIMEOUT, "inactivity-timeout" }, + { ATH6KL_FW_CAPABILITY_RSN_CAP_OVERRIDE, "rsn-cap-override" }, + { ATH6KL_FW_CAPABILITY_WOW_MULTICAST_FILTER, "wow-mc-filter" }, + { ATH6KL_FW_CAPABILITY_BMISS_ENHANCE, "bmiss-enhance" }, + { ATH6KL_FW_CAPABILITY_SCHED_SCAN_MATCH_LIST, "sscan-match-list" }, + { ATH6KL_FW_CAPABILITY_RSSI_SCAN_THOLD, "rssi-scan-thold" }, + { ATH6KL_FW_CAPABILITY_CUSTOM_MAC_ADDR, "custom-mac-addr" }, + { ATH6KL_FW_CAPABILITY_TX_ERR_NOTIFY, "tx-err-notify" }, + { ATH6KL_FW_CAPABILITY_REGDOMAIN, "regdomain" }, + { ATH6KL_FW_CAPABILITY_SCHED_SCAN_V2, "sched-scan-v2" }, + { ATH6KL_FW_CAPABILITY_HEART_BEAT_POLL, "hb-poll" }, +}; + +static const char *ath6kl_init_get_fw_capa_name(unsigned int id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(fw_capa_map); i++) { + if (fw_capa_map[i].id == id) + return fw_capa_map[i].name; + } + + return ""; +} + +static void ath6kl_init_get_fwcaps(struct ath6kl *ar, char *buf, size_t buf_len) +{ + u8 *data = (u8 *) ar->fw_capabilities; + size_t trunc_len, len = 0; + int i, index, bit; + char *trunc = "..."; + + for (i = 0; i < ATH6KL_FW_CAPABILITY_MAX; i++) { + index = i / 8; + bit = i % 8; + + if (index >= sizeof(ar->fw_capabilities) * 4) + break; + + if (buf_len - len < 4) { + ath6kl_warn("firmware capability buffer too small!\n"); + + /* add "..." to the end of string */ + trunc_len = strlen(trunc) + 1; + strncpy(buf + buf_len - trunc_len, trunc, trunc_len); + + return; + } + + if (data[index] & (1 << bit)) { + len += scnprintf(buf + len, buf_len - len, "%s,", + ath6kl_init_get_fw_capa_name(i)); + } + } + + /* overwrite the last comma */ + if (len > 0) + len--; + + buf[len] = '\0'; +} + +static int ath6kl_init_hw_reset(struct ath6kl *ar) +{ + ath6kl_dbg(ATH6KL_DBG_BOOT, "cold resetting the device"); + + return ath6kl_diag_write32(ar, RESET_CONTROL_ADDRESS, + cpu_to_le32(RESET_CONTROL_COLD_RST)); +} + +static int __ath6kl_init_hw_start(struct ath6kl *ar) +{ + long timeleft; + int ret, i; + char buf[200]; + + ath6kl_dbg(ATH6KL_DBG_BOOT, "hw start\n"); + + ret = ath6kl_hif_power_on(ar); + if (ret) + return ret; + + ret = ath6kl_configure_target(ar); + if (ret) + goto err_power_off; + + ret = ath6kl_init_upload(ar); + if (ret) + goto err_power_off; + + /* Do we need to finish the BMI phase */ + ret = ath6kl_bmi_done(ar); + if (ret) + goto err_power_off; + + /* + * The reason we have to wait for the target here is that the + * driver layer has to init BMI in order to set the host block + * size. + */ + ret = ath6kl_htc_wait_target(ar->htc_target); + + if (ret == -ETIMEDOUT) { + /* + * Most likely USB target is in odd state after reboot and + * needs a reset. A cold reset makes the whole device + * disappear from USB bus and initialisation starts from + * beginning. + */ + ath6kl_warn("htc wait target timed out, resetting device\n"); + ath6kl_init_hw_reset(ar); + goto err_power_off; + } else if (ret) { + ath6kl_err("htc wait target failed: %d\n", ret); + goto err_power_off; + } + + ret = ath6kl_init_service_ep(ar); + if (ret) { + ath6kl_err("Endpoint service initilisation failed: %d\n", ret); + goto err_cleanup_scatter; + } + + /* setup credit distribution */ + ath6kl_htc_credit_setup(ar->htc_target, &ar->credit_state_info); + + /* start HTC */ + ret = ath6kl_htc_start(ar->htc_target); + if (ret) { + /* FIXME: call this */ + ath6kl_cookie_cleanup(ar); + goto err_cleanup_scatter; + } + + /* Wait for Wmi event to be ready */ + timeleft = wait_event_interruptible_timeout(ar->event_wq, + test_bit(WMI_READY, + &ar->flag), + WMI_TIMEOUT); + + ath6kl_dbg(ATH6KL_DBG_BOOT, "firmware booted\n"); + + + if (test_and_clear_bit(FIRST_BOOT, &ar->flag)) { + ath6kl_info("%s %s fw %s api %d%s\n", + ar->hw.name, + ath6kl_init_get_hif_name(ar->hif_type), + ar->wiphy->fw_version, + ar->fw_api, + test_bit(TESTMODE, &ar->flag) ? " testmode" : ""); + ath6kl_init_get_fwcaps(ar, buf, sizeof(buf)); + ath6kl_info("firmware supports: %s\n", buf); + } + + if (ar->version.abi_ver != ATH6KL_ABI_VERSION) { + ath6kl_err("abi version mismatch: host(0x%x), target(0x%x)\n", + ATH6KL_ABI_VERSION, ar->version.abi_ver); + ret = -EIO; + goto err_htc_stop; + } + + if (!timeleft || signal_pending(current)) { + ath6kl_err("wmi is not ready or wait was interrupted\n"); + ret = -EIO; + goto err_htc_stop; + } + + ath6kl_dbg(ATH6KL_DBG_TRC, "%s: wmi is ready\n", __func__); + + /* communicate the wmi protocol verision to the target */ + /* FIXME: return error */ + if ((ath6kl_set_host_app_area(ar)) != 0) + ath6kl_err("unable to set the host app area\n"); + + for (i = 0; i < ar->vif_max; i++) { + ret = ath6kl_target_config_wlan_params(ar, i); + if (ret) + goto err_htc_stop; + } + + return 0; + +err_htc_stop: + ath6kl_htc_stop(ar->htc_target); +err_cleanup_scatter: + ath6kl_hif_cleanup_scatter(ar); +err_power_off: + ath6kl_hif_power_off(ar); + + return ret; +} + +int ath6kl_init_hw_start(struct ath6kl *ar) +{ + int err; + + err = __ath6kl_init_hw_start(ar); + if (err) + return err; + ar->state = ATH6KL_STATE_ON; + return 0; +} + +static int __ath6kl_init_hw_stop(struct ath6kl *ar) +{ + int ret; + + ath6kl_dbg(ATH6KL_DBG_BOOT, "hw stop\n"); + + ath6kl_htc_stop(ar->htc_target); + + ath6kl_hif_stop(ar); + + ath6kl_bmi_reset(ar); + + ret = ath6kl_hif_power_off(ar); + if (ret) + ath6kl_warn("failed to power off hif: %d\n", ret); + + return 0; +} + +int ath6kl_init_hw_stop(struct ath6kl *ar) +{ + int err; + + err = __ath6kl_init_hw_stop(ar); + if (err) + return err; + ar->state = ATH6KL_STATE_OFF; + return 0; +} + +void ath6kl_init_hw_restart(struct ath6kl *ar) +{ + clear_bit(WMI_READY, &ar->flag); + + ath6kl_cfg80211_stop_all(ar); + + if (__ath6kl_init_hw_stop(ar)) { + ath6kl_dbg(ATH6KL_DBG_RECOVERY, "Failed to stop during fw error recovery\n"); + return; + } + + if (__ath6kl_init_hw_start(ar)) { + ath6kl_dbg(ATH6KL_DBG_RECOVERY, "Failed to restart during fw error recovery\n"); + return; + } +} + +void ath6kl_stop_txrx(struct ath6kl *ar) +{ + struct ath6kl_vif *vif, *tmp_vif; + int i; + + set_bit(DESTROY_IN_PROGRESS, &ar->flag); + + if (down_interruptible(&ar->sem)) { + ath6kl_err("down_interruptible failed\n"); + return; + } + + for (i = 0; i < AP_MAX_NUM_STA; i++) + aggr_reset_state(ar->sta_list[i].aggr_conn); + + spin_lock_bh(&ar->list_lock); + list_for_each_entry_safe(vif, tmp_vif, &ar->vif_list, list) { + list_del(&vif->list); + spin_unlock_bh(&ar->list_lock); + ath6kl_cfg80211_vif_stop(vif, test_bit(WMI_READY, &ar->flag)); + rtnl_lock(); + ath6kl_cfg80211_vif_cleanup(vif); + rtnl_unlock(); + spin_lock_bh(&ar->list_lock); + } + spin_unlock_bh(&ar->list_lock); + + clear_bit(WMI_READY, &ar->flag); + + /* + * After wmi_shudown all WMI events will be dropped. We + * need to cleanup the buffers allocated in AP mode and + * give disconnect notification to stack, which usually + * happens in the disconnect_event. Simulate the disconnect + * event by calling the function directly. Sometimes + * disconnect_event will be received when the debug logs + * are collected. + */ + ath6kl_wmi_shutdown(ar->wmi); + + clear_bit(WMI_ENABLED, &ar->flag); + if (ar->htc_target) { + ath6kl_dbg(ATH6KL_DBG_TRC, "%s: shut down htc\n", __func__); + ath6kl_htc_stop(ar->htc_target); + } + + /* + * Try to reset the device if we can. The driver may have been + * configure NOT to reset the target during a debug session. + */ + ath6kl_init_hw_reset(ar); + + up(&ar->sem); +} +EXPORT_SYMBOL(ath6kl_stop_txrx); diff --git a/drivers/net/wireless/ath/ath6kl/main.c b/drivers/net/wireless/ath/ath6kl/main.c new file mode 100644 index 0000000..fb05c07 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/main.c @@ -0,0 +1,1326 @@ +/* + * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "core.h" +#include "hif-ops.h" +#include "cfg80211.h" +#include "target.h" +#include "debug.h" + +struct ath6kl_sta *ath6kl_find_sta(struct ath6kl_vif *vif, u8 *node_addr) +{ + struct ath6kl *ar = vif->ar; + struct ath6kl_sta *conn = NULL; + u8 i, max_conn; + + max_conn = (vif->nw_type == AP_NETWORK) ? AP_MAX_NUM_STA : 0; + + for (i = 0; i < max_conn; i++) { + if (memcmp(node_addr, ar->sta_list[i].mac, ETH_ALEN) == 0) { + conn = &ar->sta_list[i]; + break; + } + } + + return conn; +} + +struct ath6kl_sta *ath6kl_find_sta_by_aid(struct ath6kl *ar, u8 aid) +{ + struct ath6kl_sta *conn = NULL; + u8 ctr; + + for (ctr = 0; ctr < AP_MAX_NUM_STA; ctr++) { + if (ar->sta_list[ctr].aid == aid) { + conn = &ar->sta_list[ctr]; + break; + } + } + return conn; +} + +static void ath6kl_add_new_sta(struct ath6kl_vif *vif, u8 *mac, u16 aid, + u8 *wpaie, size_t ielen, u8 keymgmt, + u8 ucipher, u8 auth, u8 apsd_info) +{ + struct ath6kl *ar = vif->ar; + struct ath6kl_sta *sta; + u8 free_slot; + + free_slot = aid - 1; + + sta = &ar->sta_list[free_slot]; + memcpy(sta->mac, mac, ETH_ALEN); + if (ielen <= ATH6KL_MAX_IE) + memcpy(sta->wpa_ie, wpaie, ielen); + sta->aid = aid; + sta->keymgmt = keymgmt; + sta->ucipher = ucipher; + sta->auth = auth; + sta->apsd_info = apsd_info; + + ar->sta_list_index = ar->sta_list_index | (1 << free_slot); + ar->ap_stats.sta[free_slot].aid = cpu_to_le32(aid); + aggr_conn_init(vif, vif->aggr_cntxt, sta->aggr_conn); +} + +static void ath6kl_sta_cleanup(struct ath6kl *ar, u8 i) +{ + struct ath6kl_sta *sta = &ar->sta_list[i]; + struct ath6kl_mgmt_buff *entry, *tmp; + + /* empty the queued pkts in the PS queue if any */ + spin_lock_bh(&sta->psq_lock); + skb_queue_purge(&sta->psq); + skb_queue_purge(&sta->apsdq); + + if (sta->mgmt_psq_len != 0) { + list_for_each_entry_safe(entry, tmp, &sta->mgmt_psq, list) { + kfree(entry); + } + INIT_LIST_HEAD(&sta->mgmt_psq); + sta->mgmt_psq_len = 0; + } + + spin_unlock_bh(&sta->psq_lock); + + memset(&ar->ap_stats.sta[sta->aid - 1], 0, + sizeof(struct wmi_per_sta_stat)); + memset(sta->mac, 0, ETH_ALEN); + memset(sta->wpa_ie, 0, ATH6KL_MAX_IE); + sta->aid = 0; + sta->sta_flags = 0; + + ar->sta_list_index = ar->sta_list_index & ~(1 << i); + aggr_reset_state(sta->aggr_conn); +} + +static u8 ath6kl_remove_sta(struct ath6kl *ar, u8 *mac, u16 reason) +{ + u8 i, removed = 0; + + if (is_zero_ether_addr(mac)) + return removed; + + if (is_broadcast_ether_addr(mac)) { + ath6kl_dbg(ATH6KL_DBG_TRC, "deleting all station\n"); + + for (i = 0; i < AP_MAX_NUM_STA; i++) { + if (!is_zero_ether_addr(ar->sta_list[i].mac)) { + ath6kl_sta_cleanup(ar, i); + removed = 1; + } + } + } else { + for (i = 0; i < AP_MAX_NUM_STA; i++) { + if (memcmp(ar->sta_list[i].mac, mac, ETH_ALEN) == 0) { + ath6kl_dbg(ATH6KL_DBG_TRC, + "deleting station %pM aid=%d reason=%d\n", + mac, ar->sta_list[i].aid, reason); + ath6kl_sta_cleanup(ar, i); + removed = 1; + break; + } + } + } + + return removed; +} + +enum htc_endpoint_id ath6kl_ac2_endpoint_id(void *devt, u8 ac) +{ + struct ath6kl *ar = devt; + return ar->ac2ep_map[ac]; +} + +struct ath6kl_cookie *ath6kl_alloc_cookie(struct ath6kl *ar) +{ + struct ath6kl_cookie *cookie; + + cookie = ar->cookie_list; + if (cookie != NULL) { + ar->cookie_list = cookie->arc_list_next; + ar->cookie_count--; + } + + return cookie; +} + +void ath6kl_cookie_init(struct ath6kl *ar) +{ + u32 i; + + ar->cookie_list = NULL; + ar->cookie_count = 0; + + memset(ar->cookie_mem, 0, sizeof(ar->cookie_mem)); + + for (i = 0; i < MAX_COOKIE_NUM; i++) + ath6kl_free_cookie(ar, &ar->cookie_mem[i]); +} + +void ath6kl_cookie_cleanup(struct ath6kl *ar) +{ + ar->cookie_list = NULL; + ar->cookie_count = 0; +} + +void ath6kl_free_cookie(struct ath6kl *ar, struct ath6kl_cookie *cookie) +{ + /* Insert first */ + + if (!ar || !cookie) + return; + + cookie->arc_list_next = ar->cookie_list; + ar->cookie_list = cookie; + ar->cookie_count++; +} + +/* + * Read from the hardware through its diagnostic window. No cooperation + * from the firmware is required for this. + */ +int ath6kl_diag_read32(struct ath6kl *ar, u32 address, u32 *value) +{ + int ret; + + ret = ath6kl_hif_diag_read32(ar, address, value); + if (ret) { + ath6kl_warn("failed to read32 through diagnose window: %d\n", + ret); + return ret; + } + + return 0; +} + +/* + * Write to the ATH6KL through its diagnostic window. No cooperation from + * the Target is required for this. + */ +int ath6kl_diag_write32(struct ath6kl *ar, u32 address, __le32 value) +{ + int ret; + + ret = ath6kl_hif_diag_write32(ar, address, value); + + if (ret) { + ath6kl_err("failed to write 0x%x during diagnose window to 0x%d\n", + address, value); + return ret; + } + + return 0; +} + +int ath6kl_diag_read(struct ath6kl *ar, u32 address, void *data, u32 length) +{ + u32 count, *buf = data; + int ret; + + if (WARN_ON(length % 4)) + return -EINVAL; + + for (count = 0; count < length / 4; count++, address += 4) { + ret = ath6kl_diag_read32(ar, address, &buf[count]); + if (ret) + return ret; + } + + return 0; +} + +int ath6kl_diag_write(struct ath6kl *ar, u32 address, void *data, u32 length) +{ + u32 count; + __le32 *buf = data; + int ret; + + if (WARN_ON(length % 4)) + return -EINVAL; + + for (count = 0; count < length / 4; count++, address += 4) { + ret = ath6kl_diag_write32(ar, address, buf[count]); + if (ret) + return ret; + } + + return 0; +} + +int ath6kl_read_fwlogs(struct ath6kl *ar) +{ + struct ath6kl_dbglog_hdr debug_hdr; + struct ath6kl_dbglog_buf debug_buf; + u32 address, length, dropped, firstbuf, debug_hdr_addr; + int ret, loop; + u8 *buf; + + buf = kmalloc(ATH6KL_FWLOG_PAYLOAD_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + address = TARG_VTOP(ar->target_type, + ath6kl_get_hi_item_addr(ar, + HI_ITEM(hi_dbglog_hdr))); + + ret = ath6kl_diag_read32(ar, address, &debug_hdr_addr); + if (ret) + goto out; + + /* Get the contents of the ring buffer */ + if (debug_hdr_addr == 0) { + ath6kl_warn("Invalid address for debug_hdr_addr\n"); + ret = -EINVAL; + goto out; + } + + address = TARG_VTOP(ar->target_type, debug_hdr_addr); + ret = ath6kl_diag_read(ar, address, &debug_hdr, sizeof(debug_hdr)); + if (ret) + goto out; + + address = TARG_VTOP(ar->target_type, + le32_to_cpu(debug_hdr.dbuf_addr)); + firstbuf = address; + dropped = le32_to_cpu(debug_hdr.dropped); + ret = ath6kl_diag_read(ar, address, &debug_buf, sizeof(debug_buf)); + if (ret) + goto out; + + loop = 100; + + do { + address = TARG_VTOP(ar->target_type, + le32_to_cpu(debug_buf.buffer_addr)); + length = le32_to_cpu(debug_buf.length); + + if (length != 0 && (le32_to_cpu(debug_buf.length) <= + le32_to_cpu(debug_buf.bufsize))) { + length = ALIGN(length, 4); + + ret = ath6kl_diag_read(ar, address, + buf, length); + if (ret) + goto out; + + ath6kl_debug_fwlog_event(ar, buf, length); + } + + address = TARG_VTOP(ar->target_type, + le32_to_cpu(debug_buf.next)); + ret = ath6kl_diag_read(ar, address, &debug_buf, + sizeof(debug_buf)); + if (ret) + goto out; + + loop--; + + if (WARN_ON(loop == 0)) { + ret = -ETIMEDOUT; + goto out; + } + } while (address != firstbuf); + +out: + kfree(buf); + + return ret; +} + +static void ath6kl_install_static_wep_keys(struct ath6kl_vif *vif) +{ + u8 index; + u8 keyusage; + + for (index = 0; index <= WMI_MAX_KEY_INDEX; index++) { + if (vif->wep_key_list[index].key_len) { + keyusage = GROUP_USAGE; + if (index == vif->def_txkey_index) + keyusage |= TX_USAGE; + + ath6kl_wmi_addkey_cmd(vif->ar->wmi, vif->fw_vif_idx, + index, + WEP_CRYPT, + keyusage, + vif->wep_key_list[index].key_len, + NULL, 0, + vif->wep_key_list[index].key, + KEY_OP_INIT_VAL, NULL, + NO_SYNC_WMIFLAG); + } + } +} + +void ath6kl_connect_ap_mode_bss(struct ath6kl_vif *vif, u16 channel) +{ + struct ath6kl *ar = vif->ar; + struct ath6kl_req_key *ik; + int res; + u8 key_rsc[ATH6KL_KEY_SEQ_LEN]; + + ik = &ar->ap_mode_bkey; + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "AP mode started on %u MHz\n", channel); + + switch (vif->auth_mode) { + case NONE_AUTH: + if (vif->prwise_crypto == WEP_CRYPT) + ath6kl_install_static_wep_keys(vif); + if (!ik->valid || ik->key_type != WAPI_CRYPT) + break; + /* for WAPI, we need to set the delayed group key, continue: */ + case WPA_PSK_AUTH: + case WPA2_PSK_AUTH: + case (WPA_PSK_AUTH | WPA2_PSK_AUTH): + if (!ik->valid) + break; + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "Delayed addkey for the initial group key for AP mode\n"); + memset(key_rsc, 0, sizeof(key_rsc)); + res = ath6kl_wmi_addkey_cmd( + ar->wmi, vif->fw_vif_idx, ik->key_index, ik->key_type, + GROUP_USAGE, ik->key_len, key_rsc, ATH6KL_KEY_SEQ_LEN, + ik->key, + KEY_OP_INIT_VAL, NULL, SYNC_BOTH_WMIFLAG); + if (res) { + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, + "Delayed addkey failed: %d\n", res); + } + break; + } + + if (ar->last_ch != channel) + /* we actually don't know the phymode, default to HT20 */ + ath6kl_cfg80211_ch_switch_notify(vif, channel, WMI_11G_HT20); + + ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx, NONE_BSS_FILTER, 0); + set_bit(CONNECTED, &vif->flags); + netif_carrier_on(vif->ndev); +} + +void ath6kl_connect_ap_mode_sta(struct ath6kl_vif *vif, u16 aid, u8 *mac_addr, + u8 keymgmt, u8 ucipher, u8 auth, + u8 assoc_req_len, u8 *assoc_info, u8 apsd_info) +{ + u8 *ies = NULL, *wpa_ie = NULL, *pos; + size_t ies_len = 0; + struct station_info sinfo; + + ath6kl_dbg(ATH6KL_DBG_TRC, "new station %pM aid=%d\n", mac_addr, aid); + + if (assoc_req_len > sizeof(struct ieee80211_hdr_3addr)) { + struct ieee80211_mgmt *mgmt = + (struct ieee80211_mgmt *) assoc_info; + if (ieee80211_is_assoc_req(mgmt->frame_control) && + assoc_req_len >= sizeof(struct ieee80211_hdr_3addr) + + sizeof(mgmt->u.assoc_req)) { + ies = mgmt->u.assoc_req.variable; + ies_len = assoc_info + assoc_req_len - ies; + } else if (ieee80211_is_reassoc_req(mgmt->frame_control) && + assoc_req_len >= sizeof(struct ieee80211_hdr_3addr) + + sizeof(mgmt->u.reassoc_req)) { + ies = mgmt->u.reassoc_req.variable; + ies_len = assoc_info + assoc_req_len - ies; + } + } + + pos = ies; + while (pos && pos + 1 < ies + ies_len) { + if (pos + 2 + pos[1] > ies + ies_len) + break; + if (pos[0] == WLAN_EID_RSN) + wpa_ie = pos; /* RSN IE */ + else if (pos[0] == WLAN_EID_VENDOR_SPECIFIC && + pos[1] >= 4 && + pos[2] == 0x00 && pos[3] == 0x50 && pos[4] == 0xf2) { + if (pos[5] == 0x01) + wpa_ie = pos; /* WPA IE */ + else if (pos[5] == 0x04) { + wpa_ie = pos; /* WPS IE */ + break; /* overrides WPA/RSN IE */ + } + } else if (pos[0] == 0x44 && wpa_ie == NULL) { + /* + * Note: WAPI Parameter Set IE re-uses Element ID that + * was officially allocated for BSS AC Access Delay. As + * such, we need to be a bit more careful on when + * parsing the frame. However, BSS AC Access Delay + * element is not supposed to be included in + * (Re)Association Request frames, so this should not + * cause problems. + */ + wpa_ie = pos; /* WAPI IE */ + break; + } + pos += 2 + pos[1]; + } + + ath6kl_add_new_sta(vif, mac_addr, aid, wpa_ie, + wpa_ie ? 2 + wpa_ie[1] : 0, + keymgmt, ucipher, auth, apsd_info); + + /* send event to application */ + memset(&sinfo, 0, sizeof(sinfo)); + + /* TODO: sinfo.generation */ + + sinfo.assoc_req_ies = ies; + sinfo.assoc_req_ies_len = ies_len; + sinfo.filled |= STATION_INFO_ASSOC_REQ_IES; + + cfg80211_new_sta(vif->ndev, mac_addr, &sinfo, GFP_KERNEL); + + netif_wake_queue(vif->ndev); +} + +void disconnect_timer_handler(unsigned long ptr) +{ + struct net_device *dev = (struct net_device *)ptr; + struct ath6kl_vif *vif = netdev_priv(dev); + + ath6kl_init_profile_info(vif); + ath6kl_disconnect(vif); +} + +void ath6kl_disconnect(struct ath6kl_vif *vif) +{ + if (test_bit(CONNECTED, &vif->flags) || + test_bit(CONNECT_PEND, &vif->flags)) { + ath6kl_wmi_disconnect_cmd(vif->ar->wmi, vif->fw_vif_idx); + /* + * Disconnect command is issued, clear the connect pending + * flag. The connected flag will be cleared in + * disconnect event notification. + */ + clear_bit(CONNECT_PEND, &vif->flags); + } +} + +/* WMI Event handlers */ + +void ath6kl_ready_event(void *devt, u8 *datap, u32 sw_ver, u32 abi_ver, + enum wmi_phy_cap cap) +{ + struct ath6kl *ar = devt; + + memcpy(ar->mac_addr, datap, ETH_ALEN); + + ath6kl_dbg(ATH6KL_DBG_BOOT, + "ready event mac addr %pM sw_ver 0x%x abi_ver 0x%x cap 0x%x\n", + ar->mac_addr, sw_ver, abi_ver, cap); + + ar->version.wlan_ver = sw_ver; + ar->version.abi_ver = abi_ver; + ar->hw.cap = cap; + + if (strlen(ar->wiphy->fw_version) == 0) { + snprintf(ar->wiphy->fw_version, + sizeof(ar->wiphy->fw_version), + "%u.%u.%u.%u", + (ar->version.wlan_ver & 0xf0000000) >> 28, + (ar->version.wlan_ver & 0x0f000000) >> 24, + (ar->version.wlan_ver & 0x00ff0000) >> 16, + (ar->version.wlan_ver & 0x0000ffff)); + } + + /* indicate to the waiting thread that the ready event was received */ + set_bit(WMI_READY, &ar->flag); + wake_up(&ar->event_wq); +} + +void ath6kl_scan_complete_evt(struct ath6kl_vif *vif, int status) +{ + struct ath6kl *ar = vif->ar; + bool aborted = false; + + if (status != WMI_SCAN_STATUS_SUCCESS) + aborted = true; + + ath6kl_cfg80211_scan_complete_event(vif, aborted); + + if (!ar->usr_bss_filter) { + clear_bit(CLEAR_BSSFILTER_ON_BEACON, &vif->flags); + ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx, + NONE_BSS_FILTER, 0); + } + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "scan complete: %d\n", status); +} + +static int ath6kl_commit_ch_switch(struct ath6kl_vif *vif, u16 channel) +{ + + struct ath6kl *ar = vif->ar; + + vif->profile.ch = cpu_to_le16(channel); + + switch (vif->nw_type) { + case AP_NETWORK: + /* + * reconfigure any saved RSN IE capabilites in the beacon / + * probe response to stay in sync with the supplicant. + */ + if (vif->rsn_capab && + test_bit(ATH6KL_FW_CAPABILITY_RSN_CAP_OVERRIDE, + ar->fw_capabilities)) + ath6kl_wmi_set_ie_cmd(ar->wmi, vif->fw_vif_idx, + WLAN_EID_RSN, WMI_RSN_IE_CAPB, + (const u8 *) &vif->rsn_capab, + sizeof(vif->rsn_capab)); + + return ath6kl_wmi_ap_profile_commit(ar->wmi, vif->fw_vif_idx, + &vif->profile); + default: + ath6kl_err("won't switch channels nw_type=%d\n", vif->nw_type); + return -ENOTSUPP; + } +} + +static void ath6kl_check_ch_switch(struct ath6kl *ar, u16 channel) +{ + + struct ath6kl_vif *vif; + int res = 0; + + if (!ar->want_ch_switch) + return; + + spin_lock_bh(&ar->list_lock); + list_for_each_entry(vif, &ar->vif_list, list) { + if (ar->want_ch_switch & (1 << vif->fw_vif_idx)) + res = ath6kl_commit_ch_switch(vif, channel); + + /* if channel switch failed, oh well we tried */ + ar->want_ch_switch &= ~(1 << vif->fw_vif_idx); + + if (res) + ath6kl_err("channel switch failed nw_type %d res %d\n", + vif->nw_type, res); + } + spin_unlock_bh(&ar->list_lock); +} + +void ath6kl_connect_event(struct ath6kl_vif *vif, u16 channel, u8 *bssid, + u16 listen_int, u16 beacon_int, + enum network_type net_type, u8 beacon_ie_len, + u8 assoc_req_len, u8 assoc_resp_len, + u8 *assoc_info) +{ + struct ath6kl *ar = vif->ar; + + ath6kl_cfg80211_connect_event(vif, channel, bssid, + listen_int, beacon_int, + net_type, beacon_ie_len, + assoc_req_len, assoc_resp_len, + assoc_info); + + memcpy(vif->bssid, bssid, sizeof(vif->bssid)); + vif->bss_ch = channel; + + if ((vif->nw_type == INFRA_NETWORK)) { + ath6kl_wmi_listeninterval_cmd(ar->wmi, vif->fw_vif_idx, + vif->listen_intvl_t, 0); + ath6kl_check_ch_switch(ar, channel); + } + + netif_wake_queue(vif->ndev); + + /* Update connect & link status atomically */ + spin_lock_bh(&vif->if_lock); + set_bit(CONNECTED, &vif->flags); + clear_bit(CONNECT_PEND, &vif->flags); + netif_carrier_on(vif->ndev); + spin_unlock_bh(&vif->if_lock); + + aggr_reset_state(vif->aggr_cntxt->aggr_conn); + vif->reconnect_flag = 0; + + if ((vif->nw_type == ADHOC_NETWORK) && ar->ibss_ps_enable) { + memset(ar->node_map, 0, sizeof(ar->node_map)); + ar->node_num = 0; + ar->next_ep_id = ENDPOINT_2; + } + + if (!ar->usr_bss_filter) { + set_bit(CLEAR_BSSFILTER_ON_BEACON, &vif->flags); + ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx, + CURRENT_BSS_FILTER, 0); + } +} + +void ath6kl_tkip_micerr_event(struct ath6kl_vif *vif, u8 keyid, bool ismcast) +{ + struct ath6kl_sta *sta; + struct ath6kl *ar = vif->ar; + u8 tsc[6]; + + /* + * For AP case, keyid will have aid of STA which sent pkt with + * MIC error. Use this aid to get MAC & send it to hostapd. + */ + if (vif->nw_type == AP_NETWORK) { + sta = ath6kl_find_sta_by_aid(ar, (keyid >> 2)); + if (!sta) + return; + + ath6kl_dbg(ATH6KL_DBG_TRC, + "ap tkip mic error received from aid=%d\n", keyid); + + memset(tsc, 0, sizeof(tsc)); /* FIX: get correct TSC */ + cfg80211_michael_mic_failure(vif->ndev, sta->mac, + NL80211_KEYTYPE_PAIRWISE, keyid, + tsc, GFP_KERNEL); + } else + ath6kl_cfg80211_tkip_micerr_event(vif, keyid, ismcast); + +} + +static void ath6kl_update_target_stats(struct ath6kl_vif *vif, u8 *ptr, u32 len) +{ + struct wmi_target_stats *tgt_stats = + (struct wmi_target_stats *) ptr; + struct ath6kl *ar = vif->ar; + struct target_stats *stats = &vif->target_stats; + struct tkip_ccmp_stats *ccmp_stats; + u8 ac; + + if (len < sizeof(*tgt_stats)) + return; + + ath6kl_dbg(ATH6KL_DBG_TRC, "updating target stats\n"); + + stats->tx_pkt += le32_to_cpu(tgt_stats->stats.tx.pkt); + stats->tx_byte += le32_to_cpu(tgt_stats->stats.tx.byte); + stats->tx_ucast_pkt += le32_to_cpu(tgt_stats->stats.tx.ucast_pkt); + stats->tx_ucast_byte += le32_to_cpu(tgt_stats->stats.tx.ucast_byte); + stats->tx_mcast_pkt += le32_to_cpu(tgt_stats->stats.tx.mcast_pkt); + stats->tx_mcast_byte += le32_to_cpu(tgt_stats->stats.tx.mcast_byte); + stats->tx_bcast_pkt += le32_to_cpu(tgt_stats->stats.tx.bcast_pkt); + stats->tx_bcast_byte += le32_to_cpu(tgt_stats->stats.tx.bcast_byte); + stats->tx_rts_success_cnt += + le32_to_cpu(tgt_stats->stats.tx.rts_success_cnt); + + for (ac = 0; ac < WMM_NUM_AC; ac++) + stats->tx_pkt_per_ac[ac] += + le32_to_cpu(tgt_stats->stats.tx.pkt_per_ac[ac]); + + stats->tx_err += le32_to_cpu(tgt_stats->stats.tx.err); + stats->tx_fail_cnt += le32_to_cpu(tgt_stats->stats.tx.fail_cnt); + stats->tx_retry_cnt += le32_to_cpu(tgt_stats->stats.tx.retry_cnt); + stats->tx_mult_retry_cnt += + le32_to_cpu(tgt_stats->stats.tx.mult_retry_cnt); + stats->tx_rts_fail_cnt += + le32_to_cpu(tgt_stats->stats.tx.rts_fail_cnt); + stats->tx_ucast_rate = + ath6kl_wmi_get_rate(a_sle32_to_cpu(tgt_stats->stats.tx.ucast_rate)); + + stats->rx_pkt += le32_to_cpu(tgt_stats->stats.rx.pkt); + stats->rx_byte += le32_to_cpu(tgt_stats->stats.rx.byte); + stats->rx_ucast_pkt += le32_to_cpu(tgt_stats->stats.rx.ucast_pkt); + stats->rx_ucast_byte += le32_to_cpu(tgt_stats->stats.rx.ucast_byte); + stats->rx_mcast_pkt += le32_to_cpu(tgt_stats->stats.rx.mcast_pkt); + stats->rx_mcast_byte += le32_to_cpu(tgt_stats->stats.rx.mcast_byte); + stats->rx_bcast_pkt += le32_to_cpu(tgt_stats->stats.rx.bcast_pkt); + stats->rx_bcast_byte += le32_to_cpu(tgt_stats->stats.rx.bcast_byte); + stats->rx_frgment_pkt += le32_to_cpu(tgt_stats->stats.rx.frgment_pkt); + stats->rx_err += le32_to_cpu(tgt_stats->stats.rx.err); + stats->rx_crc_err += le32_to_cpu(tgt_stats->stats.rx.crc_err); + stats->rx_key_cache_miss += + le32_to_cpu(tgt_stats->stats.rx.key_cache_miss); + stats->rx_decrypt_err += le32_to_cpu(tgt_stats->stats.rx.decrypt_err); + stats->rx_dupl_frame += le32_to_cpu(tgt_stats->stats.rx.dupl_frame); + stats->rx_ucast_rate = + ath6kl_wmi_get_rate(a_sle32_to_cpu(tgt_stats->stats.rx.ucast_rate)); + + ccmp_stats = &tgt_stats->stats.tkip_ccmp_stats; + + stats->tkip_local_mic_fail += + le32_to_cpu(ccmp_stats->tkip_local_mic_fail); + stats->tkip_cnter_measures_invoked += + le32_to_cpu(ccmp_stats->tkip_cnter_measures_invoked); + stats->tkip_fmt_err += le32_to_cpu(ccmp_stats->tkip_fmt_err); + + stats->ccmp_fmt_err += le32_to_cpu(ccmp_stats->ccmp_fmt_err); + stats->ccmp_replays += le32_to_cpu(ccmp_stats->ccmp_replays); + + stats->pwr_save_fail_cnt += + le32_to_cpu(tgt_stats->pm_stats.pwr_save_failure_cnt); + stats->noise_floor_calib = + a_sle32_to_cpu(tgt_stats->noise_floor_calib); + + stats->cs_bmiss_cnt += + le32_to_cpu(tgt_stats->cserv_stats.cs_bmiss_cnt); + stats->cs_low_rssi_cnt += + le32_to_cpu(tgt_stats->cserv_stats.cs_low_rssi_cnt); + stats->cs_connect_cnt += + le16_to_cpu(tgt_stats->cserv_stats.cs_connect_cnt); + stats->cs_discon_cnt += + le16_to_cpu(tgt_stats->cserv_stats.cs_discon_cnt); + + stats->cs_ave_beacon_rssi = + a_sle16_to_cpu(tgt_stats->cserv_stats.cs_ave_beacon_rssi); + + stats->cs_last_roam_msec = + tgt_stats->cserv_stats.cs_last_roam_msec; + stats->cs_snr = tgt_stats->cserv_stats.cs_snr; + stats->cs_rssi = a_sle16_to_cpu(tgt_stats->cserv_stats.cs_rssi); + + stats->lq_val = le32_to_cpu(tgt_stats->lq_val); + + stats->wow_pkt_dropped += + le32_to_cpu(tgt_stats->wow_stats.wow_pkt_dropped); + stats->wow_host_pkt_wakeups += + tgt_stats->wow_stats.wow_host_pkt_wakeups; + stats->wow_host_evt_wakeups += + tgt_stats->wow_stats.wow_host_evt_wakeups; + stats->wow_evt_discarded += + le16_to_cpu(tgt_stats->wow_stats.wow_evt_discarded); + + stats->arp_received = le32_to_cpu(tgt_stats->arp_stats.arp_received); + stats->arp_replied = le32_to_cpu(tgt_stats->arp_stats.arp_replied); + stats->arp_matched = le32_to_cpu(tgt_stats->arp_stats.arp_matched); + + if (test_bit(STATS_UPDATE_PEND, &vif->flags)) { + clear_bit(STATS_UPDATE_PEND, &vif->flags); + wake_up(&ar->event_wq); + } +} + +static void ath6kl_add_le32(__le32 *var, __le32 val) +{ + *var = cpu_to_le32(le32_to_cpu(*var) + le32_to_cpu(val)); +} + +void ath6kl_tgt_stats_event(struct ath6kl_vif *vif, u8 *ptr, u32 len) +{ + struct wmi_ap_mode_stat *p = (struct wmi_ap_mode_stat *) ptr; + struct ath6kl *ar = vif->ar; + struct wmi_ap_mode_stat *ap = &ar->ap_stats; + struct wmi_per_sta_stat *st_ap, *st_p; + u8 ac; + + if (vif->nw_type == AP_NETWORK) { + if (len < sizeof(*p)) + return; + + for (ac = 0; ac < AP_MAX_NUM_STA; ac++) { + st_ap = &ap->sta[ac]; + st_p = &p->sta[ac]; + + ath6kl_add_le32(&st_ap->tx_bytes, st_p->tx_bytes); + ath6kl_add_le32(&st_ap->tx_pkts, st_p->tx_pkts); + ath6kl_add_le32(&st_ap->tx_error, st_p->tx_error); + ath6kl_add_le32(&st_ap->tx_discard, st_p->tx_discard); + ath6kl_add_le32(&st_ap->rx_bytes, st_p->rx_bytes); + ath6kl_add_le32(&st_ap->rx_pkts, st_p->rx_pkts); + ath6kl_add_le32(&st_ap->rx_error, st_p->rx_error); + ath6kl_add_le32(&st_ap->rx_discard, st_p->rx_discard); + } + + } else { + ath6kl_update_target_stats(vif, ptr, len); + } +} + +void ath6kl_wakeup_event(void *dev) +{ + struct ath6kl *ar = (struct ath6kl *) dev; + + wake_up(&ar->event_wq); +} + +void ath6kl_txpwr_rx_evt(void *devt, u8 tx_pwr) +{ + struct ath6kl *ar = (struct ath6kl *) devt; + + ar->tx_pwr = tx_pwr; + wake_up(&ar->event_wq); +} + +void ath6kl_pspoll_event(struct ath6kl_vif *vif, u8 aid) +{ + struct ath6kl_sta *conn; + struct sk_buff *skb; + bool psq_empty = false; + struct ath6kl *ar = vif->ar; + struct ath6kl_mgmt_buff *mgmt_buf; + + conn = ath6kl_find_sta_by_aid(ar, aid); + + if (!conn) + return; + /* + * Send out a packet queued on ps queue. When the ps queue + * becomes empty update the PVB for this station. + */ + spin_lock_bh(&conn->psq_lock); + psq_empty = skb_queue_empty(&conn->psq) && (conn->mgmt_psq_len == 0); + spin_unlock_bh(&conn->psq_lock); + + if (psq_empty) + /* TODO: Send out a NULL data frame */ + return; + + spin_lock_bh(&conn->psq_lock); + if (conn->mgmt_psq_len > 0) { + mgmt_buf = list_first_entry(&conn->mgmt_psq, + struct ath6kl_mgmt_buff, list); + list_del(&mgmt_buf->list); + conn->mgmt_psq_len--; + spin_unlock_bh(&conn->psq_lock); + + conn->sta_flags |= STA_PS_POLLED; + ath6kl_wmi_send_mgmt_cmd(ar->wmi, vif->fw_vif_idx, + mgmt_buf->id, mgmt_buf->freq, + mgmt_buf->wait, mgmt_buf->buf, + mgmt_buf->len, mgmt_buf->no_cck); + conn->sta_flags &= ~STA_PS_POLLED; + kfree(mgmt_buf); + } else { + skb = skb_dequeue(&conn->psq); + spin_unlock_bh(&conn->psq_lock); + + conn->sta_flags |= STA_PS_POLLED; + ath6kl_data_tx(skb, vif->ndev); + conn->sta_flags &= ~STA_PS_POLLED; + } + + spin_lock_bh(&conn->psq_lock); + psq_empty = skb_queue_empty(&conn->psq) && (conn->mgmt_psq_len == 0); + spin_unlock_bh(&conn->psq_lock); + + if (psq_empty) + ath6kl_wmi_set_pvb_cmd(ar->wmi, vif->fw_vif_idx, conn->aid, 0); +} + +void ath6kl_dtimexpiry_event(struct ath6kl_vif *vif) +{ + bool mcastq_empty = false; + struct sk_buff *skb; + struct ath6kl *ar = vif->ar; + + /* + * If there are no associated STAs, ignore the DTIM expiry event. + * There can be potential race conditions where the last associated + * STA may disconnect & before the host could clear the 'Indicate + * DTIM' request to the firmware, the firmware would have just + * indicated a DTIM expiry event. The race is between 'clear DTIM + * expiry cmd' going from the host to the firmware & the DTIM + * expiry event happening from the firmware to the host. + */ + if (!ar->sta_list_index) + return; + + spin_lock_bh(&ar->mcastpsq_lock); + mcastq_empty = skb_queue_empty(&ar->mcastpsq); + spin_unlock_bh(&ar->mcastpsq_lock); + + if (mcastq_empty) + return; + + /* set the STA flag to dtim_expired for the frame to go out */ + set_bit(DTIM_EXPIRED, &vif->flags); + + spin_lock_bh(&ar->mcastpsq_lock); + while ((skb = skb_dequeue(&ar->mcastpsq)) != NULL) { + spin_unlock_bh(&ar->mcastpsq_lock); + + ath6kl_data_tx(skb, vif->ndev); + + spin_lock_bh(&ar->mcastpsq_lock); + } + spin_unlock_bh(&ar->mcastpsq_lock); + + clear_bit(DTIM_EXPIRED, &vif->flags); + + /* clear the LSB of the BitMapCtl field of the TIM IE */ + ath6kl_wmi_set_pvb_cmd(ar->wmi, vif->fw_vif_idx, MCAST_AID, 0); +} + +void ath6kl_disconnect_event(struct ath6kl_vif *vif, u8 reason, u8 *bssid, + u8 assoc_resp_len, u8 *assoc_info, + u16 prot_reason_status) +{ + struct ath6kl *ar = vif->ar; + + if (vif->nw_type == AP_NETWORK) { + /* disconnect due to other STA vif switching channels */ + if (reason == BSS_DISCONNECTED && + prot_reason_status == WMI_AP_REASON_STA_ROAM) { + ar->want_ch_switch |= 1 << vif->fw_vif_idx; + /* bail back to this channel if STA vif fails connect */ + ar->last_ch = le16_to_cpu(vif->profile.ch); + } + + if (prot_reason_status == WMI_AP_REASON_MAX_STA) { + /* send max client reached notification to user space */ + cfg80211_conn_failed(vif->ndev, bssid, + NL80211_CONN_FAIL_MAX_CLIENTS, + GFP_KERNEL); + } + + if (prot_reason_status == WMI_AP_REASON_ACL) { + /* send blocked client notification to user space */ + cfg80211_conn_failed(vif->ndev, bssid, + NL80211_CONN_FAIL_BLOCKED_CLIENT, + GFP_KERNEL); + } + + if (!ath6kl_remove_sta(ar, bssid, prot_reason_status)) + return; + + /* if no more associated STAs, empty the mcast PS q */ + if (ar->sta_list_index == 0) { + spin_lock_bh(&ar->mcastpsq_lock); + skb_queue_purge(&ar->mcastpsq); + spin_unlock_bh(&ar->mcastpsq_lock); + + /* clear the LSB of the TIM IE's BitMapCtl field */ + if (test_bit(WMI_READY, &ar->flag)) + ath6kl_wmi_set_pvb_cmd(ar->wmi, vif->fw_vif_idx, + MCAST_AID, 0); + } + + if (!is_broadcast_ether_addr(bssid)) { + /* send event to application */ + cfg80211_del_sta(vif->ndev, bssid, GFP_KERNEL); + } + + if (memcmp(vif->ndev->dev_addr, bssid, ETH_ALEN) == 0) { + memset(vif->wep_key_list, 0, sizeof(vif->wep_key_list)); + clear_bit(CONNECTED, &vif->flags); + } + return; + } + + ath6kl_cfg80211_disconnect_event(vif, reason, bssid, + assoc_resp_len, assoc_info, + prot_reason_status); + + aggr_reset_state(vif->aggr_cntxt->aggr_conn); + + del_timer(&vif->disconnect_timer); + + ath6kl_dbg(ATH6KL_DBG_WLAN_CFG, "disconnect reason is %d\n", reason); + + /* + * If the event is due to disconnect cmd from the host, only they + * the target would stop trying to connect. Under any other + * condition, target would keep trying to connect. + */ + if (reason == DISCONNECT_CMD) { + if (!ar->usr_bss_filter && test_bit(WMI_READY, &ar->flag)) + ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx, + NONE_BSS_FILTER, 0); + } else { + set_bit(CONNECT_PEND, &vif->flags); + if (((reason == ASSOC_FAILED) && + (prot_reason_status == 0x11)) || + ((reason == ASSOC_FAILED) && (prot_reason_status == 0x0) && + (vif->reconnect_flag == 1))) { + set_bit(CONNECTED, &vif->flags); + return; + } + } + + /* restart disconnected concurrent vifs waiting for new channel */ + ath6kl_check_ch_switch(ar, ar->last_ch); + + /* update connect & link status atomically */ + spin_lock_bh(&vif->if_lock); + clear_bit(CONNECTED, &vif->flags); + netif_carrier_off(vif->ndev); + spin_unlock_bh(&vif->if_lock); + + if ((reason != CSERV_DISCONNECT) || (vif->reconnect_flag != 1)) + vif->reconnect_flag = 0; + + if (reason != CSERV_DISCONNECT) + ar->user_key_ctrl = 0; + + netif_stop_queue(vif->ndev); + memset(vif->bssid, 0, sizeof(vif->bssid)); + vif->bss_ch = 0; + + ath6kl_tx_data_cleanup(ar); +} + +struct ath6kl_vif *ath6kl_vif_first(struct ath6kl *ar) +{ + struct ath6kl_vif *vif; + + spin_lock_bh(&ar->list_lock); + if (list_empty(&ar->vif_list)) { + spin_unlock_bh(&ar->list_lock); + return NULL; + } + + vif = list_first_entry(&ar->vif_list, struct ath6kl_vif, list); + + spin_unlock_bh(&ar->list_lock); + + return vif; +} + +static int ath6kl_open(struct net_device *dev) +{ + struct ath6kl_vif *vif = netdev_priv(dev); + + set_bit(WLAN_ENABLED, &vif->flags); + + if (test_bit(CONNECTED, &vif->flags)) { + netif_carrier_on(dev); + netif_wake_queue(dev); + } else + netif_carrier_off(dev); + + return 0; +} + +static int ath6kl_close(struct net_device *dev) +{ + struct ath6kl_vif *vif = netdev_priv(dev); + + netif_stop_queue(dev); + + ath6kl_cfg80211_stop(vif); + + clear_bit(WLAN_ENABLED, &vif->flags); + + return 0; +} + +static struct net_device_stats *ath6kl_get_stats(struct net_device *dev) +{ + struct ath6kl_vif *vif = netdev_priv(dev); + + return &vif->net_stats; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) +static int ath6kl_set_features(struct net_device *dev, + netdev_features_t features) +{ + struct ath6kl_vif *vif = netdev_priv(dev); + struct ath6kl *ar = vif->ar; + int err = 0; + + if ((features & NETIF_F_RXCSUM) && + (ar->rx_meta_ver != WMI_META_VERSION_2)) { + ar->rx_meta_ver = WMI_META_VERSION_2; + err = ath6kl_wmi_set_rx_frame_format_cmd(ar->wmi, + vif->fw_vif_idx, + ar->rx_meta_ver, 0, 0); + if (err) { + dev->features = features & ~NETIF_F_RXCSUM; + return err; + } + } else if (!(features & NETIF_F_RXCSUM) && + (ar->rx_meta_ver == WMI_META_VERSION_2)) { + ar->rx_meta_ver = 0; + err = ath6kl_wmi_set_rx_frame_format_cmd(ar->wmi, + vif->fw_vif_idx, + ar->rx_meta_ver, 0, 0); + if (err) { + dev->features = features | NETIF_F_RXCSUM; + return err; + } + + } + + return err; +} +#endif + +static void ath6kl_set_multicast_list(struct net_device *ndev) +{ + struct ath6kl_vif *vif = netdev_priv(ndev); + bool mc_all_on = false; + int mc_count = netdev_mc_count(ndev); + struct netdev_hw_addr *ha; + bool found; + struct ath6kl_mc_filter *mc_filter, *tmp; + struct list_head mc_filter_new; + int ret; + + if (!test_bit(WMI_READY, &vif->ar->flag) || + !test_bit(WLAN_ENABLED, &vif->flags)) + return; + + /* Enable multicast-all filter. */ + mc_all_on = !!(ndev->flags & IFF_PROMISC) || + !!(ndev->flags & IFF_ALLMULTI) || + !!(mc_count > ATH6K_MAX_MC_FILTERS_PER_LIST); + + if (mc_all_on) + set_bit(NETDEV_MCAST_ALL_ON, &vif->flags); + else + clear_bit(NETDEV_MCAST_ALL_ON, &vif->flags); + + if (test_bit(ATH6KL_FW_CAPABILITY_WOW_MULTICAST_FILTER, + vif->ar->fw_capabilities)) { + mc_all_on = mc_all_on || (vif->ar->state == ATH6KL_STATE_ON); + } + + if (!(ndev->flags & IFF_MULTICAST)) { + mc_all_on = false; + set_bit(NETDEV_MCAST_ALL_OFF, &vif->flags); + } else { + clear_bit(NETDEV_MCAST_ALL_OFF, &vif->flags); + } + + /* Enable/disable "multicast-all" filter*/ + ath6kl_dbg(ATH6KL_DBG_TRC, "%s multicast-all filter\n", + mc_all_on ? "enabling" : "disabling"); + + ret = ath6kl_wmi_mcast_filter_cmd(vif->ar->wmi, vif->fw_vif_idx, + mc_all_on); + if (ret) { + ath6kl_warn("Failed to %s multicast-all receive\n", + mc_all_on ? "enable" : "disable"); + return; + } + + if (test_bit(NETDEV_MCAST_ALL_ON, &vif->flags)) + return; + + /* Keep the driver and firmware mcast list in sync. */ + list_for_each_entry_safe(mc_filter, tmp, &vif->mc_filter, list) { + found = false; + netdev_for_each_mc_addr(ha, ndev) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + if (memcmp(ha->addr, mc_filter->hw_addr, +#else + if (memcmp(ha->dmi_addr, mc_filter->hw_addr, +#endif + ATH6KL_MCAST_FILTER_MAC_ADDR_SIZE) == 0) { + found = true; + break; + } + } + + if (!found) { + /* + * Delete the filter which was previously set + * but not in the new request. + */ + ath6kl_dbg(ATH6KL_DBG_TRC, + "Removing %pM from multicast filter\n", + mc_filter->hw_addr); + ret = ath6kl_wmi_add_del_mcast_filter_cmd(vif->ar->wmi, + vif->fw_vif_idx, mc_filter->hw_addr, + false); + if (ret) { + ath6kl_warn("Failed to remove multicast filter:%pM\n", + mc_filter->hw_addr); + return; + } + + list_del(&mc_filter->list); + kfree(mc_filter); + } + } + + INIT_LIST_HEAD(&mc_filter_new); + + netdev_for_each_mc_addr(ha, ndev) { + found = false; + list_for_each_entry(mc_filter, &vif->mc_filter, list) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + if (memcmp(ha->addr, mc_filter->hw_addr, +#else + if (memcmp(ha->dmi_addr, mc_filter->hw_addr, +#endif + ATH6KL_MCAST_FILTER_MAC_ADDR_SIZE) == 0) { + found = true; + break; + } + } + + if (!found) { + mc_filter = kzalloc(sizeof(struct ath6kl_mc_filter), + GFP_ATOMIC); + if (!mc_filter) { + WARN_ON(1); + goto out; + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + memcpy(mc_filter->hw_addr, ha->addr, +#else + memcpy(mc_filter->hw_addr, ha->dmi_addr, +#endif + ATH6KL_MCAST_FILTER_MAC_ADDR_SIZE); + /* Set the multicast filter */ + ath6kl_dbg(ATH6KL_DBG_TRC, + "Adding %pM to multicast filter list\n", + mc_filter->hw_addr); + ret = ath6kl_wmi_add_del_mcast_filter_cmd(vif->ar->wmi, + vif->fw_vif_idx, mc_filter->hw_addr, + true); + if (ret) { + ath6kl_warn("Failed to add multicast filter :%pM\n", + mc_filter->hw_addr); + kfree(mc_filter); + goto out; + } + + list_add_tail(&mc_filter->list, &mc_filter_new); + } + } + +out: + list_splice_tail(&mc_filter_new, &vif->mc_filter); +} + +static const struct net_device_ops ath6kl_netdev_ops = { + .ndo_open = ath6kl_open, + .ndo_stop = ath6kl_close, + .ndo_start_xmit = ath6kl_data_tx, + .ndo_get_stats = ath6kl_get_stats, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + .ndo_set_features = ath6kl_set_features, +#endif + .ndo_set_rx_mode = ath6kl_set_multicast_list, +}; + +void init_netdev(struct net_device *dev) +{ + netdev_attach_ops(dev, &ath6kl_netdev_ops); + dev->destructor = free_netdev; + dev->watchdog_timeo = ATH6KL_TX_TIMEOUT; + + dev->needed_headroom = ETH_HLEN; + dev->needed_headroom += roundup(sizeof(struct ath6kl_llc_snap_hdr) + + sizeof(struct wmi_data_hdr) + + HTC_HDR_LENGTH + + WMI_MAX_TX_META_SZ + + ATH6KL_HTC_ALIGN_BYTES, 4); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)) + dev->hw_features |= NETIF_F_IP_CSUM | NETIF_F_RXCSUM; +#else + dev->features |= NETIF_F_IP_CSUM; +#endif + + return; +} diff --git a/drivers/net/wireless/ath/ath6kl/recovery.c b/drivers/net/wireless/ath/ath6kl/recovery.c new file mode 100644 index 0000000..3a8d5e9 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/recovery.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "core.h" +#include "cfg80211.h" +#include "debug.h" + +static void ath6kl_recovery_work(struct work_struct *work) +{ + struct ath6kl *ar = container_of(work, struct ath6kl, + fw_recovery.recovery_work); + + ar->state = ATH6KL_STATE_RECOVERY; + + del_timer_sync(&ar->fw_recovery.hb_timer); + + ath6kl_init_hw_restart(ar); + + ar->state = ATH6KL_STATE_ON; + clear_bit(WMI_CTRL_EP_FULL, &ar->flag); + + ar->fw_recovery.err_reason = 0; + + if (ar->fw_recovery.hb_poll) + mod_timer(&ar->fw_recovery.hb_timer, jiffies + + msecs_to_jiffies(ar->fw_recovery.hb_poll)); +} + +void ath6kl_recovery_err_notify(struct ath6kl *ar, enum ath6kl_fw_err reason) +{ + if (!ar->fw_recovery.enable) + return; + + ath6kl_dbg(ATH6KL_DBG_RECOVERY, "Fw error detected, reason:%d\n", + reason); + + set_bit(reason, &ar->fw_recovery.err_reason); + + if (!test_bit(RECOVERY_CLEANUP, &ar->flag) && + ar->state != ATH6KL_STATE_RECOVERY) + queue_work(ar->ath6kl_wq, &ar->fw_recovery.recovery_work); +} + +void ath6kl_recovery_hb_event(struct ath6kl *ar, u32 cookie) +{ + if (cookie == ar->fw_recovery.seq_num) + ar->fw_recovery.hb_pending = false; +} + +static void ath6kl_recovery_hb_timer(unsigned long data) +{ + struct ath6kl *ar = (struct ath6kl *) data; + int err; + + if (test_bit(RECOVERY_CLEANUP, &ar->flag) || + (ar->state == ATH6KL_STATE_RECOVERY)) + return; + + if (ar->fw_recovery.hb_pending) + ar->fw_recovery.hb_misscnt++; + else + ar->fw_recovery.hb_misscnt = 0; + + if (ar->fw_recovery.hb_misscnt > ATH6KL_HB_RESP_MISS_THRES) { + ar->fw_recovery.hb_misscnt = 0; + ar->fw_recovery.seq_num = 0; + ar->fw_recovery.hb_pending = false; + ath6kl_recovery_err_notify(ar, ATH6KL_FW_HB_RESP_FAILURE); + return; + } + + ar->fw_recovery.seq_num++; + ar->fw_recovery.hb_pending = true; + + err = ath6kl_wmi_get_challenge_resp_cmd(ar->wmi, + ar->fw_recovery.seq_num, 0); + if (err) + ath6kl_warn("Failed to send hb challenge request, err:%d\n", + err); + + mod_timer(&ar->fw_recovery.hb_timer, jiffies + + msecs_to_jiffies(ar->fw_recovery.hb_poll)); +} + +void ath6kl_recovery_init(struct ath6kl *ar) +{ + struct ath6kl_fw_recovery *recovery = &ar->fw_recovery; + + clear_bit(RECOVERY_CLEANUP, &ar->flag); + INIT_WORK(&recovery->recovery_work, ath6kl_recovery_work); + recovery->seq_num = 0; + recovery->hb_misscnt = 0; + ar->fw_recovery.hb_pending = false; + ar->fw_recovery.hb_timer.function = ath6kl_recovery_hb_timer; + ar->fw_recovery.hb_timer.data = (unsigned long) ar; + init_timer_deferrable(&ar->fw_recovery.hb_timer); + + if (ar->fw_recovery.hb_poll) + mod_timer(&ar->fw_recovery.hb_timer, jiffies + + msecs_to_jiffies(ar->fw_recovery.hb_poll)); +} + +void ath6kl_recovery_cleanup(struct ath6kl *ar) +{ + if (!ar->fw_recovery.enable) + return; + + set_bit(RECOVERY_CLEANUP, &ar->flag); + + del_timer_sync(&ar->fw_recovery.hb_timer); + cancel_work_sync(&ar->fw_recovery.recovery_work); +} + +void ath6kl_recovery_suspend(struct ath6kl *ar) +{ + if (!ar->fw_recovery.enable) + return; + + ath6kl_recovery_cleanup(ar); + + if (!ar->fw_recovery.err_reason) + return; + + /* Process pending fw error detection */ + ar->fw_recovery.err_reason = 0; + WARN_ON(ar->state != ATH6KL_STATE_ON); + ar->state = ATH6KL_STATE_RECOVERY; + ath6kl_init_hw_restart(ar); + ar->state = ATH6KL_STATE_ON; +} + +void ath6kl_recovery_resume(struct ath6kl *ar) +{ + if (!ar->fw_recovery.enable) + return; + + clear_bit(RECOVERY_CLEANUP, &ar->flag); + + if (!ar->fw_recovery.hb_poll) + return; + + ar->fw_recovery.hb_pending = false; + ar->fw_recovery.seq_num = 0; + ar->fw_recovery.hb_misscnt = 0; + mod_timer(&ar->fw_recovery.hb_timer, + jiffies + msecs_to_jiffies(ar->fw_recovery.hb_poll)); +} diff --git a/drivers/net/wireless/ath/ath6kl/sdio.c b/drivers/net/wireless/ath/ath6kl/sdio.c new file mode 100644 index 0000000..8056fbc --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/sdio.c @@ -0,0 +1,1477 @@ +/* + * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "hif.h" +#include "hif-ops.h" +#include "target.h" +#include "debug.h" +#include "cfg80211.h" +#include "trace.h" + +struct ath6kl_sdio { + struct sdio_func *func; + + /* protects access to bus_req_freeq */ + spinlock_t lock; + + /* free list */ + struct list_head bus_req_freeq; + + /* available bus requests */ + struct bus_request bus_req[BUS_REQUEST_MAX_NUM]; + + struct ath6kl *ar; + + u8 *dma_buffer; + + /* protects access to dma_buffer */ + struct mutex dma_buffer_mutex; + + /* scatter request list head */ + struct list_head scat_req; + + atomic_t irq_handling; + wait_queue_head_t irq_wq; + + /* protects access to scat_req */ + spinlock_t scat_lock; + + bool scatter_enabled; + + bool is_disabled; + const struct sdio_device_id *id; + struct work_struct wr_async_work; + struct list_head wr_asyncq; + + /* protects access to wr_asyncq */ + spinlock_t wr_async_lock; +}; + +#define CMD53_ARG_READ 0 +#define CMD53_ARG_WRITE 1 +#define CMD53_ARG_BLOCK_BASIS 1 +#define CMD53_ARG_FIXED_ADDRESS 0 +#define CMD53_ARG_INCR_ADDRESS 1 + +static inline struct ath6kl_sdio *ath6kl_sdio_priv(struct ath6kl *ar) +{ + return ar->hif_priv; +} + +/* + * Macro to check if DMA buffer is WORD-aligned and DMA-able. + * Most host controllers assume the buffer is DMA'able and will + * bug-check otherwise (i.e. buffers on the stack). virt_addr_valid + * check fails on stack memory. + */ +static inline bool buf_needs_bounce(u8 *buf) +{ + return ((unsigned long) buf & 0x3) || !virt_addr_valid(buf); +} + +static void ath6kl_sdio_set_mbox_info(struct ath6kl *ar) +{ + struct ath6kl_mbox_info *mbox_info = &ar->mbox_info; + + /* EP1 has an extended range */ + mbox_info->htc_addr = HIF_MBOX_BASE_ADDR; + mbox_info->htc_ext_addr = HIF_MBOX0_EXT_BASE_ADDR; + mbox_info->htc_ext_sz = HIF_MBOX0_EXT_WIDTH; + mbox_info->block_size = HIF_MBOX_BLOCK_SIZE; + mbox_info->gmbox_addr = HIF_GMBOX_BASE_ADDR; + mbox_info->gmbox_sz = HIF_GMBOX_WIDTH; +} + +static inline void ath6kl_sdio_set_cmd53_arg(u32 *arg, u8 rw, u8 func, + u8 mode, u8 opcode, u32 addr, + u16 blksz) +{ + *arg = (((rw & 1) << 31) | + ((func & 0x7) << 28) | + ((mode & 1) << 27) | + ((opcode & 1) << 26) | + ((addr & 0x1FFFF) << 9) | + (blksz & 0x1FF)); +} + +static inline void ath6kl_sdio_set_cmd52_arg(u32 *arg, u8 write, u8 raw, + unsigned int address, + unsigned char val) +{ + const u8 func = 0; + + *arg = ((write & 1) << 31) | + ((func & 0x7) << 28) | + ((raw & 1) << 27) | + (1 << 26) | + ((address & 0x1FFFF) << 9) | + (1 << 8) | + (val & 0xFF); +} + +static int ath6kl_sdio_func0_cmd52_wr_byte(struct mmc_card *card, + unsigned int address, + unsigned char byte) +{ + struct mmc_command io_cmd; + + memset(&io_cmd, 0, sizeof(io_cmd)); + ath6kl_sdio_set_cmd52_arg(&io_cmd.arg, 1, 0, address, byte); + io_cmd.opcode = SD_IO_RW_DIRECT; + io_cmd.flags = MMC_RSP_R5 | MMC_CMD_AC; + + return mmc_wait_for_cmd(card->host, &io_cmd, 0); +} + +static int ath6kl_sdio_io(struct sdio_func *func, u32 request, u32 addr, + u8 *buf, u32 len) +{ + int ret = 0; + + sdio_claim_host(func); + + if (request & HIF_WRITE) { + /* FIXME: looks like ugly workaround for something */ + if (addr >= HIF_MBOX_BASE_ADDR && + addr <= HIF_MBOX_END_ADDR) + addr += (HIF_MBOX_WIDTH - len); + + /* FIXME: this also looks like ugly workaround */ + if (addr == HIF_MBOX0_EXT_BASE_ADDR) + addr += HIF_MBOX0_EXT_WIDTH - len; + + if (request & HIF_FIXED_ADDRESS) + ret = sdio_writesb(func, addr, buf, len); + else + ret = sdio_memcpy_toio(func, addr, buf, len); + } else { + if (request & HIF_FIXED_ADDRESS) + ret = sdio_readsb(func, buf, addr, len); + else + ret = sdio_memcpy_fromio(func, buf, addr, len); + } + + sdio_release_host(func); + + ath6kl_dbg(ATH6KL_DBG_SDIO, "%s addr 0x%x%s buf 0x%p len %d\n", + request & HIF_WRITE ? "wr" : "rd", addr, + request & HIF_FIXED_ADDRESS ? " (fixed)" : "", buf, len); + ath6kl_dbg_dump(ATH6KL_DBG_SDIO_DUMP, NULL, "sdio ", buf, len); + + trace_ath6kl_sdio(addr, request, buf, len); + + return ret; +} + +static struct bus_request *ath6kl_sdio_alloc_busreq(struct ath6kl_sdio *ar_sdio) +{ + struct bus_request *bus_req; + + spin_lock_bh(&ar_sdio->lock); + + if (list_empty(&ar_sdio->bus_req_freeq)) { + spin_unlock_bh(&ar_sdio->lock); + return NULL; + } + + bus_req = list_first_entry(&ar_sdio->bus_req_freeq, + struct bus_request, list); + list_del(&bus_req->list); + + spin_unlock_bh(&ar_sdio->lock); + ath6kl_dbg(ATH6KL_DBG_SCATTER, "%s: bus request 0x%p\n", + __func__, bus_req); + + return bus_req; +} + +static void ath6kl_sdio_free_bus_req(struct ath6kl_sdio *ar_sdio, + struct bus_request *bus_req) +{ + ath6kl_dbg(ATH6KL_DBG_SCATTER, "%s: bus request 0x%p\n", + __func__, bus_req); + + spin_lock_bh(&ar_sdio->lock); + list_add_tail(&bus_req->list, &ar_sdio->bus_req_freeq); + spin_unlock_bh(&ar_sdio->lock); +} + +static void ath6kl_sdio_setup_scat_data(struct hif_scatter_req *scat_req, + struct mmc_data *data) +{ + struct scatterlist *sg; + int i; + + data->blksz = HIF_MBOX_BLOCK_SIZE; + data->blocks = scat_req->len / HIF_MBOX_BLOCK_SIZE; + + ath6kl_dbg(ATH6KL_DBG_SCATTER, + "hif-scatter: (%s) addr: 0x%X, (block len: %d, block count: %d) , (tot:%d,sg:%d)\n", + (scat_req->req & HIF_WRITE) ? "WR" : "RD", scat_req->addr, + data->blksz, data->blocks, scat_req->len, + scat_req->scat_entries); + + data->flags = (scat_req->req & HIF_WRITE) ? MMC_DATA_WRITE : + MMC_DATA_READ; + + /* fill SG entries */ + sg = scat_req->sgentries; + sg_init_table(sg, scat_req->scat_entries); + + /* assemble SG list */ + for (i = 0; i < scat_req->scat_entries; i++, sg++) { + ath6kl_dbg(ATH6KL_DBG_SCATTER, "%d: addr:0x%p, len:%d\n", + i, scat_req->scat_list[i].buf, + scat_req->scat_list[i].len); + + sg_set_buf(sg, scat_req->scat_list[i].buf, + scat_req->scat_list[i].len); + } + + /* set scatter-gather table for request */ + data->sg = scat_req->sgentries; + data->sg_len = scat_req->scat_entries; +} + +static int ath6kl_sdio_scat_rw(struct ath6kl_sdio *ar_sdio, + struct bus_request *req) +{ + struct mmc_request mmc_req; + struct mmc_command cmd; + struct mmc_data data; + struct hif_scatter_req *scat_req; + u8 opcode, rw; + int status, len; + + scat_req = req->scat_req; + + if (scat_req->virt_scat) { + len = scat_req->len; + if (scat_req->req & HIF_BLOCK_BASIS) + len = round_down(len, HIF_MBOX_BLOCK_SIZE); + + status = ath6kl_sdio_io(ar_sdio->func, scat_req->req, + scat_req->addr, scat_req->virt_dma_buf, + len); + goto scat_complete; + } + + memset(&mmc_req, 0, sizeof(struct mmc_request)); + memset(&cmd, 0, sizeof(struct mmc_command)); + memset(&data, 0, sizeof(struct mmc_data)); + + ath6kl_sdio_setup_scat_data(scat_req, &data); + + opcode = (scat_req->req & HIF_FIXED_ADDRESS) ? + CMD53_ARG_FIXED_ADDRESS : CMD53_ARG_INCR_ADDRESS; + + rw = (scat_req->req & HIF_WRITE) ? CMD53_ARG_WRITE : CMD53_ARG_READ; + + /* Fixup the address so that the last byte will fall on MBOX EOM */ + if (scat_req->req & HIF_WRITE) { + if (scat_req->addr == HIF_MBOX_BASE_ADDR) + scat_req->addr += HIF_MBOX_WIDTH - scat_req->len; + else + /* Uses extended address range */ + scat_req->addr += HIF_MBOX0_EXT_WIDTH - scat_req->len; + } + + /* set command argument */ + ath6kl_sdio_set_cmd53_arg(&cmd.arg, rw, ar_sdio->func->num, + CMD53_ARG_BLOCK_BASIS, opcode, scat_req->addr, + data.blocks); + + cmd.opcode = SD_IO_RW_EXTENDED; + cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC; + + mmc_req.cmd = &cmd; + mmc_req.data = &data; + + sdio_claim_host(ar_sdio->func); + + mmc_set_data_timeout(&data, ar_sdio->func->card); + + trace_ath6kl_sdio_scat(scat_req->addr, + scat_req->req, + scat_req->len, + scat_req->scat_entries, + scat_req->scat_list); + + /* synchronous call to process request */ + mmc_wait_for_req(ar_sdio->func->card->host, &mmc_req); + + sdio_release_host(ar_sdio->func); + + status = cmd.error ? cmd.error : data.error; + +scat_complete: + scat_req->status = status; + + if (scat_req->status) + ath6kl_err("Scatter write request failed:%d\n", + scat_req->status); + + if (scat_req->req & HIF_ASYNCHRONOUS) + scat_req->complete(ar_sdio->ar->htc_target, scat_req); + + return status; +} + +static int ath6kl_sdio_alloc_prep_scat_req(struct ath6kl_sdio *ar_sdio, + int n_scat_entry, int n_scat_req, + bool virt_scat) +{ + struct hif_scatter_req *s_req; + struct bus_request *bus_req; + int i, scat_req_sz, scat_list_sz, sg_sz, buf_sz; + u8 *virt_buf; + + scat_list_sz = (n_scat_entry - 1) * sizeof(struct hif_scatter_item); + scat_req_sz = sizeof(*s_req) + scat_list_sz; + + if (!virt_scat) + sg_sz = sizeof(struct scatterlist) * n_scat_entry; + else + buf_sz = 2 * L1_CACHE_BYTES + + ATH6KL_MAX_TRANSFER_SIZE_PER_SCATTER; + + for (i = 0; i < n_scat_req; i++) { + /* allocate the scatter request */ + s_req = kzalloc(scat_req_sz, GFP_KERNEL); + if (!s_req) + return -ENOMEM; + + if (virt_scat) { + virt_buf = kzalloc(buf_sz, GFP_KERNEL); + if (!virt_buf) { + kfree(s_req); + return -ENOMEM; + } + + s_req->virt_dma_buf = + (u8 *)L1_CACHE_ALIGN((unsigned long)virt_buf); + } else { + /* allocate sglist */ + s_req->sgentries = kzalloc(sg_sz, GFP_KERNEL); + + if (!s_req->sgentries) { + kfree(s_req); + return -ENOMEM; + } + } + + /* allocate a bus request for this scatter request */ + bus_req = ath6kl_sdio_alloc_busreq(ar_sdio); + if (!bus_req) { + kfree(s_req->sgentries); + kfree(s_req->virt_dma_buf); + kfree(s_req); + return -ENOMEM; + } + + /* assign the scatter request to this bus request */ + bus_req->scat_req = s_req; + s_req->busrequest = bus_req; + + s_req->virt_scat = virt_scat; + + /* add it to the scatter pool */ + hif_scatter_req_add(ar_sdio->ar, s_req); + } + + return 0; +} + +static int ath6kl_sdio_read_write_sync(struct ath6kl *ar, u32 addr, u8 *buf, + u32 len, u32 request) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + u8 *tbuf = NULL; + int ret; + bool bounced = false; + + if (request & HIF_BLOCK_BASIS) + len = round_down(len, HIF_MBOX_BLOCK_SIZE); + + if (buf_needs_bounce(buf)) { + if (!ar_sdio->dma_buffer) + return -ENOMEM; + mutex_lock(&ar_sdio->dma_buffer_mutex); + tbuf = ar_sdio->dma_buffer; + + if (request & HIF_WRITE) + memcpy(tbuf, buf, len); + + bounced = true; + } else + tbuf = buf; + + ret = ath6kl_sdio_io(ar_sdio->func, request, addr, tbuf, len); + if ((request & HIF_READ) && bounced) + memcpy(buf, tbuf, len); + + if (bounced) + mutex_unlock(&ar_sdio->dma_buffer_mutex); + + return ret; +} + +static void __ath6kl_sdio_write_async(struct ath6kl_sdio *ar_sdio, + struct bus_request *req) +{ + if (req->scat_req) + ath6kl_sdio_scat_rw(ar_sdio, req); + else { + void *context; + int status; + + status = ath6kl_sdio_read_write_sync(ar_sdio->ar, req->address, + req->buffer, req->length, + req->request); + context = req->packet; + ath6kl_sdio_free_bus_req(ar_sdio, req); + ath6kl_hif_rw_comp_handler(context, status); + } +} + +static void ath6kl_sdio_write_async_work(struct work_struct *work) +{ + struct ath6kl_sdio *ar_sdio; + struct bus_request *req, *tmp_req; + + ar_sdio = container_of(work, struct ath6kl_sdio, wr_async_work); + + spin_lock_bh(&ar_sdio->wr_async_lock); + list_for_each_entry_safe(req, tmp_req, &ar_sdio->wr_asyncq, list) { + list_del(&req->list); + spin_unlock_bh(&ar_sdio->wr_async_lock); + __ath6kl_sdio_write_async(ar_sdio, req); + spin_lock_bh(&ar_sdio->wr_async_lock); + } + spin_unlock_bh(&ar_sdio->wr_async_lock); +} + +static void ath6kl_sdio_irq_handler(struct sdio_func *func) +{ + int status; + struct ath6kl_sdio *ar_sdio; + + ath6kl_dbg(ATH6KL_DBG_SDIO, "irq\n"); + + ar_sdio = sdio_get_drvdata(func); + atomic_set(&ar_sdio->irq_handling, 1); + /* + * Release the host during interrups so we can pick it back up when + * we process commands. + */ + sdio_release_host(ar_sdio->func); + + status = ath6kl_hif_intr_bh_handler(ar_sdio->ar); + sdio_claim_host(ar_sdio->func); + + atomic_set(&ar_sdio->irq_handling, 0); + wake_up(&ar_sdio->irq_wq); + + WARN_ON(status && status != -ECANCELED); +} + +static int ath6kl_sdio_power_on(struct ath6kl *ar) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + struct sdio_func *func = ar_sdio->func; + int ret = 0; + + if (!ar_sdio->is_disabled) + return 0; + + ath6kl_dbg(ATH6KL_DBG_BOOT, "sdio power on\n"); + + sdio_claim_host(func); + + ret = sdio_enable_func(func); + if (ret) { + ath6kl_err("Unable to enable sdio func: %d)\n", ret); + sdio_release_host(func); + return ret; + } + + sdio_release_host(func); + + /* + * Wait for hardware to initialise. It should take a lot less than + * 10 ms but let's be conservative here. + */ + msleep(10); + + ar_sdio->is_disabled = false; + + return ret; +} + +static int ath6kl_sdio_power_off(struct ath6kl *ar) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + int ret; + + if (ar_sdio->is_disabled) + return 0; + + ath6kl_dbg(ATH6KL_DBG_BOOT, "sdio power off\n"); + + /* Disable the card */ + sdio_claim_host(ar_sdio->func); + ret = sdio_disable_func(ar_sdio->func); + sdio_release_host(ar_sdio->func); + + if (ret) + return ret; + + ar_sdio->is_disabled = true; + + return ret; +} + +static int ath6kl_sdio_write_async(struct ath6kl *ar, u32 address, u8 *buffer, + u32 length, u32 request, + struct htc_packet *packet) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + struct bus_request *bus_req; + + bus_req = ath6kl_sdio_alloc_busreq(ar_sdio); + + if (WARN_ON_ONCE(!bus_req)) + return -ENOMEM; + + bus_req->address = address; + bus_req->buffer = buffer; + bus_req->length = length; + bus_req->request = request; + bus_req->packet = packet; + + spin_lock_bh(&ar_sdio->wr_async_lock); + list_add_tail(&bus_req->list, &ar_sdio->wr_asyncq); + spin_unlock_bh(&ar_sdio->wr_async_lock); + queue_work(ar->ath6kl_wq, &ar_sdio->wr_async_work); + + return 0; +} + +static void ath6kl_sdio_irq_enable(struct ath6kl *ar) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + int ret; + + sdio_claim_host(ar_sdio->func); + + /* Register the isr */ + ret = sdio_claim_irq(ar_sdio->func, ath6kl_sdio_irq_handler); + if (ret) + ath6kl_err("Failed to claim sdio irq: %d\n", ret); + + sdio_release_host(ar_sdio->func); +} + +static bool ath6kl_sdio_is_on_irq(struct ath6kl *ar) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + + return !atomic_read(&ar_sdio->irq_handling); +} + +static void ath6kl_sdio_irq_disable(struct ath6kl *ar) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + int ret; + + sdio_claim_host(ar_sdio->func); + + if (atomic_read(&ar_sdio->irq_handling)) { + sdio_release_host(ar_sdio->func); + + ret = wait_event_interruptible(ar_sdio->irq_wq, + ath6kl_sdio_is_on_irq(ar)); + if (ret) + return; + + sdio_claim_host(ar_sdio->func); + } + + ret = sdio_release_irq(ar_sdio->func); + if (ret) + ath6kl_err("Failed to release sdio irq: %d\n", ret); + + sdio_release_host(ar_sdio->func); +} + +static struct hif_scatter_req *ath6kl_sdio_scatter_req_get(struct ath6kl *ar) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + struct hif_scatter_req *node = NULL; + + spin_lock_bh(&ar_sdio->scat_lock); + + if (!list_empty(&ar_sdio->scat_req)) { + node = list_first_entry(&ar_sdio->scat_req, + struct hif_scatter_req, list); + list_del(&node->list); + + node->scat_q_depth = get_queue_depth(&ar_sdio->scat_req); + } + + spin_unlock_bh(&ar_sdio->scat_lock); + + return node; +} + +static void ath6kl_sdio_scatter_req_add(struct ath6kl *ar, + struct hif_scatter_req *s_req) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + + spin_lock_bh(&ar_sdio->scat_lock); + + list_add_tail(&s_req->list, &ar_sdio->scat_req); + + spin_unlock_bh(&ar_sdio->scat_lock); + +} + +/* scatter gather read write request */ +static int ath6kl_sdio_async_rw_scatter(struct ath6kl *ar, + struct hif_scatter_req *scat_req) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + u32 request = scat_req->req; + int status = 0; + + if (!scat_req->len) + return -EINVAL; + + ath6kl_dbg(ATH6KL_DBG_SCATTER, + "hif-scatter: total len: %d scatter entries: %d\n", + scat_req->len, scat_req->scat_entries); + + if (request & HIF_SYNCHRONOUS) + status = ath6kl_sdio_scat_rw(ar_sdio, scat_req->busrequest); + else { + spin_lock_bh(&ar_sdio->wr_async_lock); + list_add_tail(&scat_req->busrequest->list, &ar_sdio->wr_asyncq); + spin_unlock_bh(&ar_sdio->wr_async_lock); + queue_work(ar->ath6kl_wq, &ar_sdio->wr_async_work); + } + + return status; +} + +/* clean up scatter support */ +static void ath6kl_sdio_cleanup_scatter(struct ath6kl *ar) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + struct hif_scatter_req *s_req, *tmp_req; + + /* empty the free list */ + spin_lock_bh(&ar_sdio->scat_lock); + list_for_each_entry_safe(s_req, tmp_req, &ar_sdio->scat_req, list) { + list_del(&s_req->list); + spin_unlock_bh(&ar_sdio->scat_lock); + + /* + * FIXME: should we also call completion handler with + * ath6kl_hif_rw_comp_handler() with status -ECANCELED so + * that the packet is properly freed? + */ + if (s_req->busrequest) + ath6kl_sdio_free_bus_req(ar_sdio, s_req->busrequest); + kfree(s_req->virt_dma_buf); + kfree(s_req->sgentries); + kfree(s_req); + + spin_lock_bh(&ar_sdio->scat_lock); + } + spin_unlock_bh(&ar_sdio->scat_lock); +} + +/* setup of HIF scatter resources */ +static int ath6kl_sdio_enable_scatter(struct ath6kl *ar) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + struct htc_target *target = ar->htc_target; + int ret = 0; + bool virt_scat = false; + + if (ar_sdio->scatter_enabled) + return 0; + + ar_sdio->scatter_enabled = true; + + /* check if host supports scatter and it meets our requirements */ + if (ar_sdio->func->card->host->max_segs < MAX_SCATTER_ENTRIES_PER_REQ) { + ath6kl_err("host only supports scatter of :%d entries, need: %d\n", + ar_sdio->func->card->host->max_segs, + MAX_SCATTER_ENTRIES_PER_REQ); + virt_scat = true; + } + + if (!virt_scat) { + ret = ath6kl_sdio_alloc_prep_scat_req(ar_sdio, + MAX_SCATTER_ENTRIES_PER_REQ, + MAX_SCATTER_REQUESTS, virt_scat); + + if (!ret) { + ath6kl_dbg(ATH6KL_DBG_BOOT, + "hif-scatter enabled requests %d entries %d\n", + MAX_SCATTER_REQUESTS, + MAX_SCATTER_ENTRIES_PER_REQ); + + target->max_scat_entries = MAX_SCATTER_ENTRIES_PER_REQ; + target->max_xfer_szper_scatreq = + MAX_SCATTER_REQ_TRANSFER_SIZE; + } else { + ath6kl_sdio_cleanup_scatter(ar); + ath6kl_warn("hif scatter resource setup failed, trying virtual scatter method\n"); + } + } + + if (virt_scat || ret) { + ret = ath6kl_sdio_alloc_prep_scat_req(ar_sdio, + ATH6KL_SCATTER_ENTRIES_PER_REQ, + ATH6KL_SCATTER_REQS, virt_scat); + + if (ret) { + ath6kl_err("failed to alloc virtual scatter resources !\n"); + ath6kl_sdio_cleanup_scatter(ar); + return ret; + } + + ath6kl_dbg(ATH6KL_DBG_BOOT, + "virtual scatter enabled requests %d entries %d\n", + ATH6KL_SCATTER_REQS, ATH6KL_SCATTER_ENTRIES_PER_REQ); + + target->max_scat_entries = ATH6KL_SCATTER_ENTRIES_PER_REQ; + target->max_xfer_szper_scatreq = + ATH6KL_MAX_TRANSFER_SIZE_PER_SCATTER; + } + + return 0; +} + +static int ath6kl_sdio_config(struct ath6kl *ar) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + struct sdio_func *func = ar_sdio->func; + int ret; + + sdio_claim_host(func); + + if ((ar_sdio->id->device & MANUFACTURER_ID_ATH6KL_BASE_MASK) >= + MANUFACTURER_ID_AR6003_BASE) { + /* enable 4-bit ASYNC interrupt on AR6003 or later */ + ret = ath6kl_sdio_func0_cmd52_wr_byte(func->card, + CCCR_SDIO_IRQ_MODE_REG, + SDIO_IRQ_MODE_ASYNC_4BIT_IRQ); + if (ret) { + ath6kl_err("Failed to enable 4-bit async irq mode %d\n", + ret); + goto out; + } + + ath6kl_dbg(ATH6KL_DBG_BOOT, "4-bit async irq mode enabled\n"); + } + + /* give us some time to enable, in ms */ + func->enable_timeout = 100; + + ret = sdio_set_block_size(func, HIF_MBOX_BLOCK_SIZE); + if (ret) { + ath6kl_err("Set sdio block size %d failed: %d)\n", + HIF_MBOX_BLOCK_SIZE, ret); + goto out; + } + +out: + sdio_release_host(func); + + return ret; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) +static int ath6kl_set_sdio_pm_caps(struct ath6kl *ar) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + struct sdio_func *func = ar_sdio->func; + mmc_pm_flag_t flags; + int ret; + + flags = sdio_get_host_pm_caps(func); + + ath6kl_dbg(ATH6KL_DBG_SUSPEND, "sdio suspend pm_caps 0x%x\n", flags); + + if (!(flags & MMC_PM_WAKE_SDIO_IRQ) || + !(flags & MMC_PM_KEEP_POWER)) + return -EINVAL; + + ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); + if (ret) { + ath6kl_err("set sdio keep pwr flag failed: %d\n", ret); + return ret; + } + + /* sdio irq wakes up host */ + ret = sdio_set_host_pm_flags(func, MMC_PM_WAKE_SDIO_IRQ); + if (ret) + ath6kl_err("set sdio wake irq flag failed: %d\n", ret); + + return ret; +} + +static int ath6kl_sdio_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + struct sdio_func *func = ar_sdio->func; + mmc_pm_flag_t flags; + bool try_deepsleep = false; + int ret; + + if (ar->suspend_mode == WLAN_POWER_STATE_WOW || + (!ar->suspend_mode && wow)) { + + ret = ath6kl_set_sdio_pm_caps(ar); + if (ret) + goto cut_pwr; + + ret = ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_WOW, wow); + if (ret && ret != -ENOTCONN) + ath6kl_err("wow suspend failed: %d\n", ret); + + if (ret && + (!ar->wow_suspend_mode || + ar->wow_suspend_mode == WLAN_POWER_STATE_DEEP_SLEEP)) + try_deepsleep = true; + else if (ret && + ar->wow_suspend_mode == WLAN_POWER_STATE_CUT_PWR) + goto cut_pwr; + if (!ret) + return 0; + } + + if (ar->suspend_mode == WLAN_POWER_STATE_DEEP_SLEEP || + !ar->suspend_mode || try_deepsleep) { + + flags = sdio_get_host_pm_caps(func); + if (!(flags & MMC_PM_KEEP_POWER)) + goto cut_pwr; + + ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); + if (ret) + goto cut_pwr; + + /* + * Workaround to support Deep Sleep with MSM, set the host pm + * flag as MMC_PM_WAKE_SDIO_IRQ to allow SDCC deiver to disable + * the sdc2_clock and internally allows MSM to enter + * TCXO shutdown properly. + */ + if ((flags & MMC_PM_WAKE_SDIO_IRQ)) { + ret = sdio_set_host_pm_flags(func, + MMC_PM_WAKE_SDIO_IRQ); + if (ret) + goto cut_pwr; + } + + ret = ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_DEEPSLEEP, + NULL); + if (ret) + goto cut_pwr; + + return 0; + } + +cut_pwr: + if (func->card && func->card->host) + func->card->host->pm_flags &= ~MMC_PM_KEEP_POWER; + + return ath6kl_cfg80211_suspend(ar, ATH6KL_CFG_SUSPEND_CUTPOWER, NULL); +} + +static int ath6kl_sdio_resume(struct ath6kl *ar) +{ + switch (ar->state) { + case ATH6KL_STATE_OFF: + case ATH6KL_STATE_CUTPOWER: + ath6kl_dbg(ATH6KL_DBG_SUSPEND, + "sdio resume configuring sdio\n"); + + /* need to set sdio settings after power is cut from sdio */ + ath6kl_sdio_config(ar); + break; + + case ATH6KL_STATE_ON: + break; + + case ATH6KL_STATE_DEEPSLEEP: + break; + + case ATH6KL_STATE_WOW: + break; + + case ATH6KL_STATE_SUSPENDING: + break; + + case ATH6KL_STATE_RESUMING: + break; + + case ATH6KL_STATE_RECOVERY: + break; + } + + ath6kl_cfg80211_resume(ar); + + return 0; +} +#else +static int ath6kl_sdio_suspend(struct ath6kl *ar, struct cfg80211_wowlan *wow) +{ + return 0; +} + +static int ath6kl_sdio_resume(struct ath6kl *ar) +{ + return 0; +} +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) */ + +/* set the window address register (using 4-byte register access ). */ +static int ath6kl_set_addrwin_reg(struct ath6kl *ar, u32 reg_addr, u32 addr) +{ + int status; + u8 addr_val[4]; + s32 i; + + /* + * Write bytes 1,2,3 of the register to set the upper address bytes, + * the LSB is written last to initiate the access cycle + */ + + for (i = 1; i <= 3; i++) { + /* + * Fill the buffer with the address byte value we want to + * hit 4 times. + */ + memset(addr_val, ((u8 *)&addr)[i], 4); + + /* + * Hit each byte of the register address with a 4-byte + * write operation to the same address, this is a harmless + * operation. + */ + status = ath6kl_sdio_read_write_sync(ar, reg_addr + i, addr_val, + 4, HIF_WR_SYNC_BYTE_FIX); + if (status) + break; + } + + if (status) { + ath6kl_err("%s: failed to write initial bytes of 0x%x to window reg: 0x%X\n", + __func__, addr, reg_addr); + return status; + } + + /* + * Write the address register again, this time write the whole + * 4-byte value. The effect here is that the LSB write causes the + * cycle to start, the extra 3 byte write to bytes 1,2,3 has no + * effect since we are writing the same values again + */ + status = ath6kl_sdio_read_write_sync(ar, reg_addr, (u8 *)(&addr), + 4, HIF_WR_SYNC_BYTE_INC); + + if (status) { + ath6kl_err("%s: failed to write 0x%x to window reg: 0x%X\n", + __func__, addr, reg_addr); + return status; + } + + return 0; +} + +static int ath6kl_sdio_diag_read32(struct ath6kl *ar, u32 address, u32 *data) +{ + int status; + + /* set window register to start read cycle */ + status = ath6kl_set_addrwin_reg(ar, WINDOW_READ_ADDR_ADDRESS, + address); + + if (status) + return status; + + /* read the data */ + status = ath6kl_sdio_read_write_sync(ar, WINDOW_DATA_ADDRESS, + (u8 *)data, sizeof(u32), HIF_RD_SYNC_BYTE_INC); + if (status) { + ath6kl_err("%s: failed to read from window data addr\n", + __func__); + return status; + } + + return status; +} + +static int ath6kl_sdio_diag_write32(struct ath6kl *ar, u32 address, + __le32 data) +{ + int status; + u32 val = (__force u32) data; + + /* set write data */ + status = ath6kl_sdio_read_write_sync(ar, WINDOW_DATA_ADDRESS, + (u8 *) &val, sizeof(u32), HIF_WR_SYNC_BYTE_INC); + if (status) { + ath6kl_err("%s: failed to write 0x%x to window data addr\n", + __func__, data); + return status; + } + + /* set window register, which starts the write cycle */ + return ath6kl_set_addrwin_reg(ar, WINDOW_WRITE_ADDR_ADDRESS, + address); +} + +static int ath6kl_sdio_bmi_credits(struct ath6kl *ar) +{ + u32 addr; + unsigned long timeout; + int ret; + + ar->bmi.cmd_credits = 0; + + /* Read the counter register to get the command credits */ + addr = COUNT_DEC_ADDRESS + (HTC_MAILBOX_NUM_MAX + ENDPOINT1) * 4; + + timeout = jiffies + msecs_to_jiffies(BMI_COMMUNICATION_TIMEOUT); + while (time_before(jiffies, timeout) && !ar->bmi.cmd_credits) { + + /* + * Hit the credit counter with a 4-byte access, the first byte + * read will hit the counter and cause a decrement, while the + * remaining 3 bytes has no effect. The rationale behind this + * is to make all HIF accesses 4-byte aligned. + */ + ret = ath6kl_sdio_read_write_sync(ar, addr, + (u8 *)&ar->bmi.cmd_credits, 4, + HIF_RD_SYNC_BYTE_INC); + if (ret) { + ath6kl_err("Unable to decrement the command credit count register: %d\n", + ret); + return ret; + } + + /* The counter is only 8 bits. + * Ignore anything in the upper 3 bytes + */ + ar->bmi.cmd_credits &= 0xFF; + } + + if (!ar->bmi.cmd_credits) { + ath6kl_err("bmi communication timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int ath6kl_bmi_get_rx_lkahd(struct ath6kl *ar) +{ + unsigned long timeout; + u32 rx_word = 0; + int ret = 0; + + timeout = jiffies + msecs_to_jiffies(BMI_COMMUNICATION_TIMEOUT); + while ((time_before(jiffies, timeout)) && !rx_word) { + ret = ath6kl_sdio_read_write_sync(ar, + RX_LOOKAHEAD_VALID_ADDRESS, + (u8 *)&rx_word, sizeof(rx_word), + HIF_RD_SYNC_BYTE_INC); + if (ret) { + ath6kl_err("unable to read RX_LOOKAHEAD_VALID\n"); + return ret; + } + + /* all we really want is one bit */ + rx_word &= (1 << ENDPOINT1); + } + + if (!rx_word) { + ath6kl_err("bmi_recv_buf FIFO empty\n"); + return -EINVAL; + } + + return ret; +} + +static int ath6kl_sdio_bmi_write(struct ath6kl *ar, u8 *buf, u32 len) +{ + int ret; + u32 addr; + + ret = ath6kl_sdio_bmi_credits(ar); + if (ret) + return ret; + + addr = ar->mbox_info.htc_addr; + + ret = ath6kl_sdio_read_write_sync(ar, addr, buf, len, + HIF_WR_SYNC_BYTE_INC); + if (ret) { + ath6kl_err("unable to send the bmi data to the device\n"); + return ret; + } + + return 0; +} + +static int ath6kl_sdio_bmi_read(struct ath6kl *ar, u8 *buf, u32 len) +{ + int ret; + u32 addr; + + /* + * During normal bootup, small reads may be required. + * Rather than issue an HIF Read and then wait as the Target + * adds successive bytes to the FIFO, we wait here until + * we know that response data is available. + * + * This allows us to cleanly timeout on an unexpected + * Target failure rather than risk problems at the HIF level. + * In particular, this avoids SDIO timeouts and possibly garbage + * data on some host controllers. And on an interconnect + * such as Compact Flash (as well as some SDIO masters) which + * does not provide any indication on data timeout, it avoids + * a potential hang or garbage response. + * + * Synchronization is more difficult for reads larger than the + * size of the MBOX FIFO (128B), because the Target is unable + * to push the 129th byte of data until AFTER the Host posts an + * HIF Read and removes some FIFO data. So for large reads the + * Host proceeds to post an HIF Read BEFORE all the data is + * actually available to read. Fortunately, large BMI reads do + * not occur in practice -- they're supported for debug/development. + * + * So Host/Target BMI synchronization is divided into these cases: + * CASE 1: length < 4 + * Should not happen + * + * CASE 2: 4 <= length <= 128 + * Wait for first 4 bytes to be in FIFO + * If CONSERVATIVE_BMI_READ is enabled, also wait for + * a BMI command credit, which indicates that the ENTIRE + * response is available in the the FIFO + * + * CASE 3: length > 128 + * Wait for the first 4 bytes to be in FIFO + * + * For most uses, a small timeout should be sufficient and we will + * usually see a response quickly; but there may be some unusual + * (debug) cases of BMI_EXECUTE where we want an larger timeout. + * For now, we use an unbounded busy loop while waiting for + * BMI_EXECUTE. + * + * If BMI_EXECUTE ever needs to support longer-latency execution, + * especially in production, this code needs to be enhanced to sleep + * and yield. Also note that BMI_COMMUNICATION_TIMEOUT is currently + * a function of Host processor speed. + */ + if (len >= 4) { /* NB: Currently, always true */ + ret = ath6kl_bmi_get_rx_lkahd(ar); + if (ret) + return ret; + } + + addr = ar->mbox_info.htc_addr; + ret = ath6kl_sdio_read_write_sync(ar, addr, buf, len, + HIF_RD_SYNC_BYTE_INC); + if (ret) { + ath6kl_err("Unable to read the bmi data from the device: %d\n", + ret); + return ret; + } + + return 0; +} + +static void ath6kl_sdio_stop(struct ath6kl *ar) +{ + struct ath6kl_sdio *ar_sdio = ath6kl_sdio_priv(ar); + struct bus_request *req, *tmp_req; + void *context; + + /* FIXME: make sure that wq is not queued again */ + + cancel_work_sync(&ar_sdio->wr_async_work); + + spin_lock_bh(&ar_sdio->wr_async_lock); + + list_for_each_entry_safe(req, tmp_req, &ar_sdio->wr_asyncq, list) { + list_del(&req->list); + + if (req->scat_req) { + /* this is a scatter gather request */ + req->scat_req->status = -ECANCELED; + req->scat_req->complete(ar_sdio->ar->htc_target, + req->scat_req); + } else { + context = req->packet; + ath6kl_sdio_free_bus_req(ar_sdio, req); + ath6kl_hif_rw_comp_handler(context, -ECANCELED); + } + } + + spin_unlock_bh(&ar_sdio->wr_async_lock); + + WARN_ON(get_queue_depth(&ar_sdio->scat_req) != 4); +} + +static const struct ath6kl_hif_ops ath6kl_sdio_ops = { + .read_write_sync = ath6kl_sdio_read_write_sync, + .write_async = ath6kl_sdio_write_async, + .irq_enable = ath6kl_sdio_irq_enable, + .irq_disable = ath6kl_sdio_irq_disable, + .scatter_req_get = ath6kl_sdio_scatter_req_get, + .scatter_req_add = ath6kl_sdio_scatter_req_add, + .enable_scatter = ath6kl_sdio_enable_scatter, + .scat_req_rw = ath6kl_sdio_async_rw_scatter, + .cleanup_scatter = ath6kl_sdio_cleanup_scatter, + .suspend = ath6kl_sdio_suspend, + .resume = ath6kl_sdio_resume, + .diag_read32 = ath6kl_sdio_diag_read32, + .diag_write32 = ath6kl_sdio_diag_write32, + .bmi_read = ath6kl_sdio_bmi_read, + .bmi_write = ath6kl_sdio_bmi_write, + .power_on = ath6kl_sdio_power_on, + .power_off = ath6kl_sdio_power_off, + .stop = ath6kl_sdio_stop, +}; + +#if defined(CONFIG_PM_SLEEP) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) + +/* + * Empty handlers so that mmc subsystem doesn't remove us entirely during + * suspend. We instead follow cfg80211 suspend/resume handlers. + */ +static int ath6kl_sdio_pm_suspend(struct device *device) +{ + ath6kl_dbg(ATH6KL_DBG_SUSPEND, "sdio pm suspend\n"); + + return 0; +} + +static int ath6kl_sdio_pm_resume(struct device *device) +{ + ath6kl_dbg(ATH6KL_DBG_SUSPEND, "sdio pm resume\n"); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(ath6kl_sdio_pm_ops, ath6kl_sdio_pm_suspend, + ath6kl_sdio_pm_resume); + +#define ATH6KL_SDIO_PM_OPS (&ath6kl_sdio_pm_ops) + +#else + +#define ATH6KL_SDIO_PM_OPS NULL + +#endif /* CONFIG_PM_SLEEP */ + +static int ath6kl_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + int ret; + struct ath6kl_sdio *ar_sdio; + struct ath6kl *ar; + int count; + + ath6kl_dbg(ATH6KL_DBG_BOOT, + "sdio new func %d vendor 0x%x device 0x%x block 0x%x/0x%x\n", + func->num, func->vendor, func->device, + func->max_blksize, func->cur_blksize); + + ar_sdio = kzalloc(sizeof(struct ath6kl_sdio), GFP_KERNEL); + if (!ar_sdio) + return -ENOMEM; + + ar_sdio->dma_buffer = kzalloc(HIF_DMA_BUFFER_SIZE, GFP_KERNEL); + if (!ar_sdio->dma_buffer) { + ret = -ENOMEM; + goto err_hif; + } + + ar_sdio->func = func; + sdio_set_drvdata(func, ar_sdio); + + ar_sdio->id = id; + ar_sdio->is_disabled = true; + + spin_lock_init(&ar_sdio->lock); + spin_lock_init(&ar_sdio->scat_lock); + spin_lock_init(&ar_sdio->wr_async_lock); + mutex_init(&ar_sdio->dma_buffer_mutex); + + INIT_LIST_HEAD(&ar_sdio->scat_req); + INIT_LIST_HEAD(&ar_sdio->bus_req_freeq); + INIT_LIST_HEAD(&ar_sdio->wr_asyncq); + + INIT_WORK(&ar_sdio->wr_async_work, ath6kl_sdio_write_async_work); + + init_waitqueue_head(&ar_sdio->irq_wq); + + for (count = 0; count < BUS_REQUEST_MAX_NUM; count++) + ath6kl_sdio_free_bus_req(ar_sdio, &ar_sdio->bus_req[count]); + + ar = ath6kl_core_create(&ar_sdio->func->dev); + if (!ar) { + ath6kl_err("Failed to alloc ath6kl core\n"); + ret = -ENOMEM; + goto err_dma; + } + + ar_sdio->ar = ar; + ar->hif_type = ATH6KL_HIF_TYPE_SDIO; + ar->hif_priv = ar_sdio; + ar->hif_ops = &ath6kl_sdio_ops; + ar->bmi.max_data_size = 256; + + ath6kl_sdio_set_mbox_info(ar); + + ret = ath6kl_sdio_config(ar); + if (ret) { + ath6kl_err("Failed to config sdio: %d\n", ret); + goto err_core_alloc; + } + + ret = ath6kl_core_init(ar, ATH6KL_HTC_TYPE_MBOX); + if (ret) { + ath6kl_err("Failed to init ath6kl core\n"); + goto err_core_alloc; + } + + return ret; + +err_core_alloc: + ath6kl_core_destroy(ar_sdio->ar); +err_dma: + kfree(ar_sdio->dma_buffer); +err_hif: + kfree(ar_sdio); + + return ret; +} + +static void ath6kl_sdio_remove(struct sdio_func *func) +{ + struct ath6kl_sdio *ar_sdio; + + ath6kl_dbg(ATH6KL_DBG_BOOT, + "sdio removed func %d vendor 0x%x device 0x%x\n", + func->num, func->vendor, func->device); + + ar_sdio = sdio_get_drvdata(func); + + ath6kl_stop_txrx(ar_sdio->ar); + cancel_work_sync(&ar_sdio->wr_async_work); + + ath6kl_core_cleanup(ar_sdio->ar); + ath6kl_core_destroy(ar_sdio->ar); + + kfree(ar_sdio->dma_buffer); + kfree(ar_sdio); +} + +static const struct sdio_device_id ath6kl_sdio_devices[] = { + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6003_BASE | 0x0))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6003_BASE | 0x1))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6004_BASE | 0x0))}, + {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6004_BASE | 0x1))}, + {}, +}; + +MODULE_DEVICE_TABLE(sdio, ath6kl_sdio_devices); + +static struct sdio_driver ath6kl_sdio_driver = { + .name = "ath6kl_sdio", + .id_table = ath6kl_sdio_devices, + .probe = ath6kl_sdio_probe, + .remove = ath6kl_sdio_remove, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) + .drv.pm = ATH6KL_SDIO_PM_OPS, +#endif +}; + +static int __init ath6kl_sdio_init(void) +{ + int ret; + + ret = sdio_register_driver(&ath6kl_sdio_driver); + if (ret) + ath6kl_err("sdio driver registration failed: %d\n", ret); + + return ret; +} + +static void __exit ath6kl_sdio_exit(void) +{ + sdio_unregister_driver(&ath6kl_sdio_driver); +} + +module_init(ath6kl_sdio_init); +module_exit(ath6kl_sdio_exit); + +MODULE_AUTHOR("Atheros Communications, Inc."); +MODULE_DESCRIPTION("Driver support for Atheros AR600x SDIO devices"); +MODULE_LICENSE("Dual BSD/GPL"); + +MODULE_FIRMWARE(AR6003_HW_2_0_FW_DIR "/" AR6003_HW_2_0_OTP_FILE); +MODULE_FIRMWARE(AR6003_HW_2_0_FW_DIR "/" AR6003_HW_2_0_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6003_HW_2_0_FW_DIR "/" AR6003_HW_2_0_PATCH_FILE); +MODULE_FIRMWARE(AR6003_HW_2_0_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6003_HW_2_0_DEFAULT_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6003_HW_2_1_1_FW_DIR "/" AR6003_HW_2_1_1_OTP_FILE); +MODULE_FIRMWARE(AR6003_HW_2_1_1_FW_DIR "/" AR6003_HW_2_1_1_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6003_HW_2_1_1_FW_DIR "/" AR6003_HW_2_1_1_PATCH_FILE); +MODULE_FIRMWARE(AR6003_HW_2_1_1_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6003_HW_2_1_1_DEFAULT_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_0_FW_DIR "/" AR6004_HW_1_0_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6004_HW_1_0_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_0_DEFAULT_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_1_FW_DIR "/" AR6004_HW_1_1_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6004_HW_1_1_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_1_DEFAULT_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_2_FW_DIR "/" AR6004_HW_1_2_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6004_HW_1_2_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_2_DEFAULT_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_3_FW_DIR "/" AR6004_HW_1_3_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6004_HW_1_3_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_3_DEFAULT_BOARD_DATA_FILE); diff --git a/drivers/net/wireless/ath/ath6kl/target.h b/drivers/net/wireless/ath/ath6kl/target.h new file mode 100644 index 0000000..a580a62 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/target.h @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2004-2010 Atheros Communications Inc. + * Copyright (c) 2011 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef TARGET_H +#define TARGET_H + +#define AR6003_BOARD_DATA_SZ 1024 +#define AR6003_BOARD_EXT_DATA_SZ 768 +#define AR6003_BOARD_EXT_DATA_SZ_V2 1024 + +#define AR6004_BOARD_DATA_SZ 6144 +#define AR6004_BOARD_EXT_DATA_SZ 0 + +#define RESET_CONTROL_ADDRESS 0x00004000 +#define RESET_CONTROL_COLD_RST 0x00000100 +#define RESET_CONTROL_MBOX_RST 0x00000004 + +#define CPU_CLOCK_STANDARD_S 0 +#define CPU_CLOCK_STANDARD 0x00000003 +#define CPU_CLOCK_ADDRESS 0x00000020 + +#define CLOCK_CONTROL_ADDRESS 0x00000028 +#define CLOCK_CONTROL_LF_CLK32_S 2 +#define CLOCK_CONTROL_LF_CLK32 0x00000004 + +#define SYSTEM_SLEEP_ADDRESS 0x000000c4 +#define SYSTEM_SLEEP_DISABLE_S 0 +#define SYSTEM_SLEEP_DISABLE 0x00000001 + +#define LPO_CAL_ADDRESS 0x000000e0 +#define LPO_CAL_ENABLE_S 20 +#define LPO_CAL_ENABLE 0x00100000 + +#define GPIO_PIN9_ADDRESS 0x0000004c +#define GPIO_PIN10_ADDRESS 0x00000050 +#define GPIO_PIN11_ADDRESS 0x00000054 +#define GPIO_PIN12_ADDRESS 0x00000058 +#define GPIO_PIN13_ADDRESS 0x0000005c + +#define HOST_INT_STATUS_ADDRESS 0x00000400 +#define HOST_INT_STATUS_ERROR_S 7 +#define HOST_INT_STATUS_ERROR 0x00000080 + +#define HOST_INT_STATUS_CPU_S 6 +#define HOST_INT_STATUS_CPU 0x00000040 + +#define HOST_INT_STATUS_COUNTER_S 4 +#define HOST_INT_STATUS_COUNTER 0x00000010 + +#define CPU_INT_STATUS_ADDRESS 0x00000401 + +#define ERROR_INT_STATUS_ADDRESS 0x00000402 +#define ERROR_INT_STATUS_WAKEUP_S 2 +#define ERROR_INT_STATUS_WAKEUP 0x00000004 + +#define ERROR_INT_STATUS_RX_UNDERFLOW_S 1 +#define ERROR_INT_STATUS_RX_UNDERFLOW 0x00000002 + +#define ERROR_INT_STATUS_TX_OVERFLOW_S 0 +#define ERROR_INT_STATUS_TX_OVERFLOW 0x00000001 + +#define COUNTER_INT_STATUS_ADDRESS 0x00000403 +#define COUNTER_INT_STATUS_COUNTER_S 0 +#define COUNTER_INT_STATUS_COUNTER 0x000000ff + +#define RX_LOOKAHEAD_VALID_ADDRESS 0x00000405 + +#define INT_STATUS_ENABLE_ADDRESS 0x00000418 +#define INT_STATUS_ENABLE_ERROR_S 7 +#define INT_STATUS_ENABLE_ERROR 0x00000080 + +#define INT_STATUS_ENABLE_CPU_S 6 +#define INT_STATUS_ENABLE_CPU 0x00000040 + +#define INT_STATUS_ENABLE_INT_S 5 +#define INT_STATUS_ENABLE_INT 0x00000020 +#define INT_STATUS_ENABLE_COUNTER_S 4 +#define INT_STATUS_ENABLE_COUNTER 0x00000010 + +#define INT_STATUS_ENABLE_MBOX_DATA_S 0 +#define INT_STATUS_ENABLE_MBOX_DATA 0x0000000f + +#define CPU_INT_STATUS_ENABLE_ADDRESS 0x00000419 +#define CPU_INT_STATUS_ENABLE_BIT_S 0 +#define CPU_INT_STATUS_ENABLE_BIT 0x000000ff + +#define ERROR_STATUS_ENABLE_ADDRESS 0x0000041a +#define ERROR_STATUS_ENABLE_RX_UNDERFLOW_S 1 +#define ERROR_STATUS_ENABLE_RX_UNDERFLOW 0x00000002 + +#define ERROR_STATUS_ENABLE_TX_OVERFLOW_S 0 +#define ERROR_STATUS_ENABLE_TX_OVERFLOW 0x00000001 + +#define COUNTER_INT_STATUS_ENABLE_ADDRESS 0x0000041b +#define COUNTER_INT_STATUS_ENABLE_BIT_S 0 +#define COUNTER_INT_STATUS_ENABLE_BIT 0x000000ff + +#define COUNT_ADDRESS 0x00000420 + +#define COUNT_DEC_ADDRESS 0x00000440 + +#define WINDOW_DATA_ADDRESS 0x00000474 +#define WINDOW_WRITE_ADDR_ADDRESS 0x00000478 +#define WINDOW_READ_ADDR_ADDRESS 0x0000047c +#define CPU_DBG_SEL_ADDRESS 0x00000483 +#define CPU_DBG_ADDRESS 0x00000484 + +#define LOCAL_SCRATCH_ADDRESS 0x000000c0 +#define ATH6KL_OPTION_SLEEP_DISABLE 0x08 + +#define RTC_BASE_ADDRESS 0x00004000 +#define GPIO_BASE_ADDRESS 0x00014000 +#define MBOX_BASE_ADDRESS 0x00018000 +#define ANALOG_INTF_BASE_ADDRESS 0x0001c000 + +/* real name of the register is unknown */ +#define ATH6KL_ANALOG_PLL_REGISTER (ANALOG_INTF_BASE_ADDRESS + 0x284) + +#define SM(f, v) (((v) << f##_S) & f) +#define MS(f, v) (((v) & f) >> f##_S) + +/* + * xxx_HOST_INTEREST_ADDRESS is the address in Target RAM of the + * host_interest structure. + * + * Host Interest is shared between Host and Target in order to coordinate + * between the two, and is intended to remain constant (with additions only + * at the end). + */ +#define ATH6KL_AR6003_HI_START_ADDR 0x00540600 +#define ATH6KL_AR6004_HI_START_ADDR 0x00400800 + +/* + * These are items that the Host may need to access + * via BMI or via the Diagnostic Window. The position + * of items in this structure must remain constant. + * across firmware revisions! + * + * Types for each item must be fixed size across target and host platforms. + * The structure is used only to calculate offset for each register with + * HI_ITEM() macro, no values are stored to it. + * + * More items may be added at the end. + */ +struct host_interest { + /* + * Pointer to application-defined area, if any. + * Set by Target application during startup. + */ + u32 hi_app_host_interest; /* 0x00 */ + + /* Pointer to register dump area, valid after Target crash. */ + u32 hi_failure_state; /* 0x04 */ + + /* Pointer to debug logging header */ + u32 hi_dbglog_hdr; /* 0x08 */ + + u32 hi_unused1; /* 0x0c */ + + /* + * General-purpose flag bits, similar to ATH6KL_OPTION_* flags. + * Can be used by application rather than by OS. + */ + u32 hi_option_flag; /* 0x10 */ + + /* + * Boolean that determines whether or not to + * display messages on the serial port. + */ + u32 hi_serial_enable; /* 0x14 */ + + /* Start address of DataSet index, if any */ + u32 hi_dset_list_head; /* 0x18 */ + + /* Override Target application start address */ + u32 hi_app_start; /* 0x1c */ + + /* Clock and voltage tuning */ + u32 hi_skip_clock_init; /* 0x20 */ + u32 hi_core_clock_setting; /* 0x24 */ + u32 hi_cpu_clock_setting; /* 0x28 */ + u32 hi_system_sleep_setting; /* 0x2c */ + u32 hi_xtal_control_setting; /* 0x30 */ + u32 hi_pll_ctrl_setting_24ghz; /* 0x34 */ + u32 hi_pll_ctrl_setting_5ghz; /* 0x38 */ + u32 hi_ref_voltage_trim_setting; /* 0x3c */ + u32 hi_clock_info; /* 0x40 */ + + /* + * Flash configuration overrides, used only + * when firmware is not executing from flash. + * (When using flash, modify the global variables + * with equivalent names.) + */ + u32 hi_bank0_addr_value; /* 0x44 */ + u32 hi_bank0_read_value; /* 0x48 */ + u32 hi_bank0_write_value; /* 0x4c */ + u32 hi_bank0_config_value; /* 0x50 */ + + /* Pointer to Board Data */ + u32 hi_board_data; /* 0x54 */ + u32 hi_board_data_initialized; /* 0x58 */ + + u32 hi_dset_ram_index_tbl; /* 0x5c */ + + u32 hi_desired_baud_rate; /* 0x60 */ + u32 hi_dbglog_config; /* 0x64 */ + u32 hi_end_ram_reserve_sz; /* 0x68 */ + u32 hi_mbox_io_block_sz; /* 0x6c */ + + u32 hi_num_bpatch_streams; /* 0x70 -- unused */ + u32 hi_mbox_isr_yield_limit; /* 0x74 */ + + u32 hi_refclk_hz; /* 0x78 */ + u32 hi_ext_clk_detected; /* 0x7c */ + u32 hi_dbg_uart_txpin; /* 0x80 */ + u32 hi_dbg_uart_rxpin; /* 0x84 */ + u32 hi_hci_uart_baud; /* 0x88 */ + u32 hi_hci_uart_pin_assignments; /* 0x8C */ + /* + * NOTE: byte [0] = tx pin, [1] = rx pin, [2] = rts pin, [3] = cts + * pin + */ + u32 hi_hci_uart_baud_scale_val; /* 0x90 */ + u32 hi_hci_uart_baud_step_val; /* 0x94 */ + + u32 hi_allocram_start; /* 0x98 */ + u32 hi_allocram_sz; /* 0x9c */ + u32 hi_hci_bridge_flags; /* 0xa0 */ + u32 hi_hci_uart_support_pins; /* 0xa4 */ + /* + * NOTE: byte [0] = RESET pin (bit 7 is polarity), + * bytes[1]..bytes[3] are for future use + */ + u32 hi_hci_uart_pwr_mgmt_params; /* 0xa8 */ + /* + * 0xa8 - [1]: 0 = UART FC active low, 1 = UART FC active high + * [31:16]: wakeup timeout in ms + */ + + /* Pointer to extended board data */ + u32 hi_board_ext_data; /* 0xac */ + u32 hi_board_ext_data_config; /* 0xb0 */ + + /* + * Bit [0] : valid + * Bit[31:16: size + */ + /* + * hi_reset_flag is used to do some stuff when target reset. + * such as restore app_start after warm reset or + * preserve host Interest area, or preserve ROM data, literals etc. + */ + u32 hi_reset_flag; /* 0xb4 */ + /* indicate hi_reset_flag is valid */ + u32 hi_reset_flag_valid; /* 0xb8 */ + u32 hi_hci_uart_pwr_mgmt_params_ext; /* 0xbc */ + /* + * 0xbc - [31:0]: idle timeout in ms + */ + /* ACS flags */ + u32 hi_acs_flags; /* 0xc0 */ + u32 hi_console_flags; /* 0xc4 */ + u32 hi_nvram_state; /* 0xc8 */ + u32 hi_option_flag2; /* 0xcc */ + + /* If non-zero, override values sent to Host in WMI_READY event. */ + u32 hi_sw_version_override; /* 0xd0 */ + u32 hi_abi_version_override; /* 0xd4 */ + + /* + * Percentage of high priority RX traffic to total expected RX traffic - + * applicable only to ar6004 + */ + u32 hi_hp_rx_traffic_ratio; /* 0xd8 */ + + /* test applications flags */ + u32 hi_test_apps_related ; /* 0xdc */ + /* location of test script */ + u32 hi_ota_testscript; /* 0xe0 */ + /* location of CAL data */ + u32 hi_cal_data; /* 0xe4 */ + /* Number of packet log buffers */ + u32 hi_pktlog_num_buffers; /* 0xe8 */ + +} __packed; + +#define HI_ITEM(item) offsetof(struct host_interest, item) + +#define HI_OPTION_MAC_ADDR_METHOD_SHIFT 3 + +#define HI_OPTION_FW_MODE_IBSS 0x0 +#define HI_OPTION_FW_MODE_BSS_STA 0x1 +#define HI_OPTION_FW_MODE_AP 0x2 + +#define HI_OPTION_FW_SUBMODE_NONE 0x0 +#define HI_OPTION_FW_SUBMODE_P2PDEV 0x1 +#define HI_OPTION_FW_SUBMODE_P2PCLIENT 0x2 +#define HI_OPTION_FW_SUBMODE_P2PGO 0x3 + +#define HI_OPTION_NUM_DEV_SHIFT 0x9 + +#define HI_OPTION_FW_BRIDGE_SHIFT 0x04 + +/* Fw Mode/SubMode Mask +|------------------------------------------------------------------------------| +| SUB | SUB | SUB | SUB | | | | +| MODE[3] | MODE[2] | MODE[1] | MODE[0] | MODE[3] | MODE[2] | MODE[1] | MODE[0| +| (2) | (2) | (2) | (2) | (2) | (2) | (2) | (2) +|------------------------------------------------------------------------------| +*/ +#define HI_OPTION_FW_MODE_BITS 0x2 +#define HI_OPTION_FW_MODE_SHIFT 0xC + +#define HI_OPTION_FW_SUBMODE_BITS 0x2 +#define HI_OPTION_FW_SUBMODE_SHIFT 0x14 + +/* Convert a Target virtual address into a Target physical address */ +#define AR6003_VTOP(vaddr) ((vaddr) & 0x001fffff) +#define AR6004_VTOP(vaddr) (vaddr) + +#define TARG_VTOP(target_type, vaddr) \ + (((target_type) == TARGET_TYPE_AR6003) ? AR6003_VTOP(vaddr) : \ + (((target_type) == TARGET_TYPE_AR6004) ? AR6004_VTOP(vaddr) : 0)) + +#define ATH6KL_FWLOG_PAYLOAD_SIZE 1500 + +struct ath6kl_dbglog_buf { + __le32 next; + __le32 buffer_addr; + __le32 bufsize; + __le32 length; + __le32 count; + __le32 free; +} __packed; + +struct ath6kl_dbglog_hdr { + __le32 dbuf_addr; + __le32 dropped; +} __packed; + +#endif diff --git a/drivers/net/wireless/ath/ath6kl/testmode.c b/drivers/net/wireless/ath/ath6kl/testmode.c new file mode 100644 index 0000000..acc9aa8 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/testmode.c @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "testmode.h" +#include "debug.h" + +#include + +enum ath6kl_tm_attr { + __ATH6KL_TM_ATTR_INVALID = 0, + ATH6KL_TM_ATTR_CMD = 1, + ATH6KL_TM_ATTR_DATA = 2, + + /* keep last */ + __ATH6KL_TM_ATTR_AFTER_LAST, + ATH6KL_TM_ATTR_MAX = __ATH6KL_TM_ATTR_AFTER_LAST - 1, +}; + +enum ath6kl_tm_cmd { + ATH6KL_TM_CMD_TCMD = 0, + ATH6KL_TM_CMD_RX_REPORT = 1, /* not used anymore */ +}; + +#define ATH6KL_TM_DATA_MAX_LEN 5000 + +static const struct nla_policy ath6kl_tm_policy[ATH6KL_TM_ATTR_MAX + 1] = { + [ATH6KL_TM_ATTR_CMD] = { .type = NLA_U32 }, + [ATH6KL_TM_ATTR_DATA] = { .type = NLA_BINARY, + .len = ATH6KL_TM_DATA_MAX_LEN }, +}; + +void ath6kl_tm_rx_event(struct ath6kl *ar, void *buf, size_t buf_len) +{ + struct sk_buff *skb; + + if (!buf || buf_len == 0) + return; + + skb = cfg80211_testmode_alloc_event_skb(ar->wiphy, buf_len, GFP_KERNEL); + if (!skb) { + ath6kl_warn("failed to allocate testmode rx skb!\n"); + return; + } + if (nla_put_u32(skb, ATH6KL_TM_ATTR_CMD, ATH6KL_TM_CMD_TCMD) || + nla_put(skb, ATH6KL_TM_ATTR_DATA, buf_len, buf)) + goto nla_put_failure; + cfg80211_testmode_event(skb, GFP_KERNEL); + return; + +nla_put_failure: + kfree_skb(skb); + ath6kl_warn("nla_put failed on testmode rx skb!\n"); +} + +int ath6kl_tm_cmd(struct wiphy *wiphy, void *data, int len) +{ + struct ath6kl *ar = wiphy_priv(wiphy); + struct nlattr *tb[ATH6KL_TM_ATTR_MAX + 1]; + int err, buf_len; + void *buf; + + err = nla_parse(tb, ATH6KL_TM_ATTR_MAX, data, len, + ath6kl_tm_policy); + if (err) + return err; + + if (!tb[ATH6KL_TM_ATTR_CMD]) + return -EINVAL; + + switch (nla_get_u32(tb[ATH6KL_TM_ATTR_CMD])) { + case ATH6KL_TM_CMD_TCMD: + if (!tb[ATH6KL_TM_ATTR_DATA]) + return -EINVAL; + + buf = nla_data(tb[ATH6KL_TM_ATTR_DATA]); + buf_len = nla_len(tb[ATH6KL_TM_ATTR_DATA]); + + ath6kl_wmi_test_cmd(ar->wmi, buf, buf_len); + + return 0; + + break; + case ATH6KL_TM_CMD_RX_REPORT: + default: + return -EOPNOTSUPP; + } +} diff --git a/drivers/net/wireless/ath/ath6kl/testmode.h b/drivers/net/wireless/ath/ath6kl/testmode.h new file mode 100644 index 0000000..9dd50f6 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/testmode.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "core.h" + +#ifdef CPTCFG_NL80211_TESTMODE + +void ath6kl_tm_rx_event(struct ath6kl *ar, void *buf, size_t buf_len); +int ath6kl_tm_cmd(struct wiphy *wiphy, void *data, int len); + +#else + +static inline void ath6kl_tm_rx_event(struct ath6kl *ar, void *buf, + size_t buf_len) +{ +} + +static inline int ath6kl_tm_cmd(struct wiphy *wiphy, void *data, int len) +{ + return 0; +} + +#endif diff --git a/drivers/net/wireless/ath/ath6kl/trace.c b/drivers/net/wireless/ath/ath6kl/trace.c new file mode 100644 index 0000000..ae433a6 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/trace.c @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "trace.h" +#define CREATE_TRACE_POINTS +#include "trace.h" + +EXPORT_TRACEPOINT_SYMBOL(ath6kl_sdio); +EXPORT_TRACEPOINT_SYMBOL(ath6kl_sdio_scat); diff --git a/drivers/net/wireless/ath/ath6kl/trace.h b/drivers/net/wireless/ath/ath6kl/trace.h new file mode 100644 index 0000000..fedb95a --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/trace.h @@ -0,0 +1,332 @@ +#if !defined(_ATH6KL_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) + +#include +#include +#include +#include "wmi.h" +#include "hif.h" + +#if !defined(_ATH6KL_TRACE_H) +static inline unsigned int ath6kl_get_wmi_id(void *buf, size_t buf_len) +{ + struct wmi_cmd_hdr *hdr = buf; + + if (buf_len < sizeof(*hdr)) + return 0; + + return le16_to_cpu(hdr->cmd_id); +} +#endif /* __ATH6KL_TRACE_H */ + +#define _ATH6KL_TRACE_H + +/* create empty functions when tracing is disabled */ +#if !defined(CPTCFG_ATH6KL_TRACING) +#undef TRACE_EVENT +#define TRACE_EVENT(name, proto, ...) \ +static inline void trace_ ## name(proto) {} +#undef DECLARE_EVENT_CLASS +#define DECLARE_EVENT_CLASS(...) +#undef DEFINE_EVENT +#define DEFINE_EVENT(evt_class, name, proto, ...) \ +static inline void trace_ ## name(proto) {} +#endif /* !CPTCFG_ATH6KL_TRACING || __CHECKER__ */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM ath6kl + +TRACE_EVENT(ath6kl_wmi_cmd, + TP_PROTO(void *buf, size_t buf_len), + + TP_ARGS(buf, buf_len), + + TP_STRUCT__entry( + __field(unsigned int, id) + __field(size_t, buf_len) + __dynamic_array(u8, buf, buf_len) + ), + + TP_fast_assign( + __entry->id = ath6kl_get_wmi_id(buf, buf_len); + __entry->buf_len = buf_len; + memcpy(__get_dynamic_array(buf), buf, buf_len); + ), + + TP_printk( + "id %d len %zd", + __entry->id, __entry->buf_len + ) +); + +TRACE_EVENT(ath6kl_wmi_event, + TP_PROTO(void *buf, size_t buf_len), + + TP_ARGS(buf, buf_len), + + TP_STRUCT__entry( + __field(unsigned int, id) + __field(size_t, buf_len) + __dynamic_array(u8, buf, buf_len) + ), + + TP_fast_assign( + __entry->id = ath6kl_get_wmi_id(buf, buf_len); + __entry->buf_len = buf_len; + memcpy(__get_dynamic_array(buf), buf, buf_len); + ), + + TP_printk( + "id %d len %zd", + __entry->id, __entry->buf_len + ) +); + +TRACE_EVENT(ath6kl_sdio, + TP_PROTO(unsigned int addr, int flags, + void *buf, size_t buf_len), + + TP_ARGS(addr, flags, buf, buf_len), + + TP_STRUCT__entry( + __field(unsigned int, tx) + __field(unsigned int, addr) + __field(int, flags) + __field(size_t, buf_len) + __dynamic_array(u8, buf, buf_len) + ), + + TP_fast_assign( + __entry->addr = addr; + __entry->flags = flags; + __entry->buf_len = buf_len; + memcpy(__get_dynamic_array(buf), buf, buf_len); + + if (flags & HIF_WRITE) + __entry->tx = 1; + else + __entry->tx = 0; + ), + + TP_printk( + "%s addr 0x%x flags 0x%x len %zd\n", + __entry->tx ? "tx" : "rx", + __entry->addr, + __entry->flags, + __entry->buf_len + ) +); + +TRACE_EVENT(ath6kl_sdio_scat, + TP_PROTO(unsigned int addr, int flags, unsigned int total_len, + unsigned int entries, struct hif_scatter_item *list), + + TP_ARGS(addr, flags, total_len, entries, list), + + TP_STRUCT__entry( + __field(unsigned int, tx) + __field(unsigned int, addr) + __field(int, flags) + __field(unsigned int, entries) + __field(size_t, total_len) + __dynamic_array(unsigned int, len_array, entries) + __dynamic_array(u8, data, total_len) + ), + + TP_fast_assign( + unsigned int *len_array; + int i, offset = 0; + size_t len; + + __entry->addr = addr; + __entry->flags = flags; + __entry->entries = entries; + __entry->total_len = total_len; + + if (flags & HIF_WRITE) + __entry->tx = 1; + else + __entry->tx = 0; + + len_array = __get_dynamic_array(len_array); + + for (i = 0; i < entries; i++) { + len = list[i].len; + + memcpy((u8 *) __get_dynamic_array(data) + offset, + list[i].buf, len); + + len_array[i] = len; + offset += len; + } + ), + + TP_printk( + "%s addr 0x%x flags 0x%x entries %d total_len %zd\n", + __entry->tx ? "tx" : "rx", + __entry->addr, + __entry->flags, + __entry->entries, + __entry->total_len + ) +); + +TRACE_EVENT(ath6kl_sdio_irq, + TP_PROTO(void *buf, size_t buf_len), + + TP_ARGS(buf, buf_len), + + TP_STRUCT__entry( + __field(size_t, buf_len) + __dynamic_array(u8, buf, buf_len) + ), + + TP_fast_assign( + __entry->buf_len = buf_len; + memcpy(__get_dynamic_array(buf), buf, buf_len); + ), + + TP_printk( + "irq len %zd\n", __entry->buf_len + ) +); + +TRACE_EVENT(ath6kl_htc_rx, + TP_PROTO(int status, int endpoint, void *buf, + size_t buf_len), + + TP_ARGS(status, endpoint, buf, buf_len), + + TP_STRUCT__entry( + __field(int, status) + __field(int, endpoint) + __field(size_t, buf_len) + __dynamic_array(u8, buf, buf_len) + ), + + TP_fast_assign( + __entry->status = status; + __entry->endpoint = endpoint; + __entry->buf_len = buf_len; + memcpy(__get_dynamic_array(buf), buf, buf_len); + ), + + TP_printk( + "status %d endpoint %d len %zd\n", + __entry->status, + __entry->endpoint, + __entry->buf_len + ) +); + +TRACE_EVENT(ath6kl_htc_tx, + TP_PROTO(int status, int endpoint, void *buf, + size_t buf_len), + + TP_ARGS(status, endpoint, buf, buf_len), + + TP_STRUCT__entry( + __field(int, status) + __field(int, endpoint) + __field(size_t, buf_len) + __dynamic_array(u8, buf, buf_len) + ), + + TP_fast_assign( + __entry->status = status; + __entry->endpoint = endpoint; + __entry->buf_len = buf_len; + memcpy(__get_dynamic_array(buf), buf, buf_len); + ), + + TP_printk( + "status %d endpoint %d len %zd\n", + __entry->status, + __entry->endpoint, + __entry->buf_len + ) +); + +#define ATH6KL_MSG_MAX 200 + +DECLARE_EVENT_CLASS(ath6kl_log_event, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf), + TP_STRUCT__entry( + __dynamic_array(char, msg, ATH6KL_MSG_MAX) + ), + TP_fast_assign( + WARN_ON_ONCE(vsnprintf(__get_dynamic_array(msg), + ATH6KL_MSG_MAX, + vaf->fmt, + *vaf->va) >= ATH6KL_MSG_MAX); + ), + TP_printk("%s", __get_str(msg)) +); + +DEFINE_EVENT(ath6kl_log_event, ath6kl_log_err, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(ath6kl_log_event, ath6kl_log_warn, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(ath6kl_log_event, ath6kl_log_info, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +TRACE_EVENT(ath6kl_log_dbg, + TP_PROTO(unsigned int level, struct va_format *vaf), + TP_ARGS(level, vaf), + TP_STRUCT__entry( + __field(unsigned int, level) + __dynamic_array(char, msg, ATH6KL_MSG_MAX) + ), + TP_fast_assign( + __entry->level = level; + WARN_ON_ONCE(vsnprintf(__get_dynamic_array(msg), + ATH6KL_MSG_MAX, + vaf->fmt, + *vaf->va) >= ATH6KL_MSG_MAX); + ), + TP_printk("%s", __get_str(msg)) +); + +TRACE_EVENT(ath6kl_log_dbg_dump, + TP_PROTO(const char *msg, const char *prefix, + const void *buf, size_t buf_len), + + TP_ARGS(msg, prefix, buf, buf_len), + + TP_STRUCT__entry( + __string(msg, msg) + __string(prefix, prefix) + __field(size_t, buf_len) + __dynamic_array(u8, buf, buf_len) + ), + + TP_fast_assign( + __assign_str(msg, msg); + __assign_str(prefix, prefix); + __entry->buf_len = buf_len; + memcpy(__get_dynamic_array(buf), buf, buf_len); + ), + + TP_printk( + "%s/%s\n", __get_str(prefix), __get_str(msg) + ) +); + +#endif /* _ ATH6KL_TRACE_H || TRACE_HEADER_MULTI_READ*/ + +/* we don't want to use include/trace/events */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +/* This part must be outside protection */ +#include diff --git a/drivers/net/wireless/ath/ath6kl/txrx.c b/drivers/net/wireless/ath/ath6kl/txrx.c new file mode 100644 index 0000000..ebb2404 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/txrx.c @@ -0,0 +1,1872 @@ +/* + * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "core.h" +#include "debug.h" +#include "htc-ops.h" +#include "trace.h" + +/* + * tid - tid_mux0..tid_mux3 + * aid - tid_mux4..tid_mux7 + */ +#define ATH6KL_TID_MASK 0xf +#define ATH6KL_AID_SHIFT 4 + +static inline u8 ath6kl_get_tid(u8 tid_mux) +{ + return tid_mux & ATH6KL_TID_MASK; +} + +static inline u8 ath6kl_get_aid(u8 tid_mux) +{ + return tid_mux >> ATH6KL_AID_SHIFT; +} + +static u8 ath6kl_ibss_map_epid(struct sk_buff *skb, struct net_device *dev, + u32 *map_no) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct ethhdr *eth_hdr; + u32 i, ep_map = -1; + u8 *datap; + + *map_no = 0; + datap = skb->data; + eth_hdr = (struct ethhdr *) (datap + sizeof(struct wmi_data_hdr)); + + if (is_multicast_ether_addr(eth_hdr->h_dest)) + return ENDPOINT_2; + + for (i = 0; i < ar->node_num; i++) { + if (memcmp(eth_hdr->h_dest, ar->node_map[i].mac_addr, + ETH_ALEN) == 0) { + *map_no = i + 1; + ar->node_map[i].tx_pend++; + return ar->node_map[i].ep_id; + } + + if ((ep_map == -1) && !ar->node_map[i].tx_pend) + ep_map = i; + } + + if (ep_map == -1) { + ep_map = ar->node_num; + ar->node_num++; + if (ar->node_num > MAX_NODE_NUM) + return ENDPOINT_UNUSED; + } + + memcpy(ar->node_map[ep_map].mac_addr, eth_hdr->h_dest, ETH_ALEN); + + for (i = ENDPOINT_2; i <= ENDPOINT_5; i++) { + if (!ar->tx_pending[i]) { + ar->node_map[ep_map].ep_id = i; + break; + } + + /* + * No free endpoint is available, start redistribution on + * the inuse endpoints. + */ + if (i == ENDPOINT_5) { + ar->node_map[ep_map].ep_id = ar->next_ep_id; + ar->next_ep_id++; + if (ar->next_ep_id > ENDPOINT_5) + ar->next_ep_id = ENDPOINT_2; + } + } + + *map_no = ep_map + 1; + ar->node_map[ep_map].tx_pend++; + + return ar->node_map[ep_map].ep_id; +} + +static bool ath6kl_process_uapsdq(struct ath6kl_sta *conn, + struct ath6kl_vif *vif, + struct sk_buff *skb, + u32 *flags) +{ + struct ath6kl *ar = vif->ar; + bool is_apsdq_empty = false; + struct ethhdr *datap = (struct ethhdr *) skb->data; + u8 up = 0, traffic_class, *ip_hdr; + u16 ether_type; + struct ath6kl_llc_snap_hdr *llc_hdr; + + if (conn->sta_flags & STA_PS_APSD_TRIGGER) { + /* + * This tx is because of a uAPSD trigger, determine + * more and EOSP bit. Set EOSP if queue is empty + * or sufficient frames are delivered for this trigger. + */ + spin_lock_bh(&conn->psq_lock); + if (!skb_queue_empty(&conn->apsdq)) + *flags |= WMI_DATA_HDR_FLAGS_MORE; + else if (conn->sta_flags & STA_PS_APSD_EOSP) + *flags |= WMI_DATA_HDR_FLAGS_EOSP; + *flags |= WMI_DATA_HDR_FLAGS_UAPSD; + spin_unlock_bh(&conn->psq_lock); + return false; + } else if (!conn->apsd_info) + return false; + + if (test_bit(WMM_ENABLED, &vif->flags)) { + ether_type = be16_to_cpu(datap->h_proto); + if (is_ethertype(ether_type)) { + /* packet is in DIX format */ + ip_hdr = (u8 *)(datap + 1); + } else { + /* packet is in 802.3 format */ + llc_hdr = (struct ath6kl_llc_snap_hdr *) + (datap + 1); + ether_type = be16_to_cpu(llc_hdr->eth_type); + ip_hdr = (u8 *)(llc_hdr + 1); + } + + if (ether_type == IP_ETHERTYPE) + up = ath6kl_wmi_determine_user_priority( + ip_hdr, 0); + } + + traffic_class = ath6kl_wmi_get_traffic_class(up); + + if ((conn->apsd_info & (1 << traffic_class)) == 0) + return false; + + /* Queue the frames if the STA is sleeping */ + spin_lock_bh(&conn->psq_lock); + is_apsdq_empty = skb_queue_empty(&conn->apsdq); + skb_queue_tail(&conn->apsdq, skb); + spin_unlock_bh(&conn->psq_lock); + + /* + * If this is the first pkt getting queued + * for this STA, update the PVB for this STA + */ + if (is_apsdq_empty) { + ath6kl_wmi_set_apsd_bfrd_traf(ar->wmi, + vif->fw_vif_idx, + conn->aid, 1, 0); + } + *flags |= WMI_DATA_HDR_FLAGS_UAPSD; + + return true; +} + +static bool ath6kl_process_psq(struct ath6kl_sta *conn, + struct ath6kl_vif *vif, + struct sk_buff *skb, + u32 *flags) +{ + bool is_psq_empty = false; + struct ath6kl *ar = vif->ar; + + if (conn->sta_flags & STA_PS_POLLED) { + spin_lock_bh(&conn->psq_lock); + if (!skb_queue_empty(&conn->psq)) + *flags |= WMI_DATA_HDR_FLAGS_MORE; + spin_unlock_bh(&conn->psq_lock); + return false; + } + + /* Queue the frames if the STA is sleeping */ + spin_lock_bh(&conn->psq_lock); + is_psq_empty = skb_queue_empty(&conn->psq); + skb_queue_tail(&conn->psq, skb); + spin_unlock_bh(&conn->psq_lock); + + /* + * If this is the first pkt getting queued + * for this STA, update the PVB for this + * STA. + */ + if (is_psq_empty) + ath6kl_wmi_set_pvb_cmd(ar->wmi, + vif->fw_vif_idx, + conn->aid, 1); + return true; +} + +static bool ath6kl_powersave_ap(struct ath6kl_vif *vif, struct sk_buff *skb, + u32 *flags) +{ + struct ethhdr *datap = (struct ethhdr *) skb->data; + struct ath6kl_sta *conn = NULL; + bool ps_queued = false; + struct ath6kl *ar = vif->ar; + + if (is_multicast_ether_addr(datap->h_dest)) { + u8 ctr = 0; + bool q_mcast = false; + + for (ctr = 0; ctr < AP_MAX_NUM_STA; ctr++) { + if (ar->sta_list[ctr].sta_flags & STA_PS_SLEEP) { + q_mcast = true; + break; + } + } + + if (q_mcast) { + /* + * If this transmit is not because of a Dtim Expiry + * q it. + */ + if (!test_bit(DTIM_EXPIRED, &vif->flags)) { + bool is_mcastq_empty = false; + + spin_lock_bh(&ar->mcastpsq_lock); + is_mcastq_empty = + skb_queue_empty(&ar->mcastpsq); + skb_queue_tail(&ar->mcastpsq, skb); + spin_unlock_bh(&ar->mcastpsq_lock); + + /* + * If this is the first Mcast pkt getting + * queued indicate to the target to set the + * BitmapControl LSB of the TIM IE. + */ + if (is_mcastq_empty) + ath6kl_wmi_set_pvb_cmd(ar->wmi, + vif->fw_vif_idx, + MCAST_AID, 1); + + ps_queued = true; + } else { + /* + * This transmit is because of Dtim expiry. + * Determine if MoreData bit has to be set. + */ + spin_lock_bh(&ar->mcastpsq_lock); + if (!skb_queue_empty(&ar->mcastpsq)) + *flags |= WMI_DATA_HDR_FLAGS_MORE; + spin_unlock_bh(&ar->mcastpsq_lock); + } + } + } else { + conn = ath6kl_find_sta(vif, datap->h_dest); + if (!conn) { + dev_kfree_skb(skb); + + /* Inform the caller that the skb is consumed */ + return true; + } + + if (conn->sta_flags & STA_PS_SLEEP) { + ps_queued = ath6kl_process_uapsdq(conn, + vif, skb, flags); + if (!(*flags & WMI_DATA_HDR_FLAGS_UAPSD)) + ps_queued = ath6kl_process_psq(conn, + vif, skb, flags); + } + } + return ps_queued; +} + +/* Tx functions */ + +int ath6kl_control_tx(void *devt, struct sk_buff *skb, + enum htc_endpoint_id eid) +{ + struct ath6kl *ar = devt; + int status = 0; + struct ath6kl_cookie *cookie = NULL; + + trace_ath6kl_wmi_cmd(skb->data, skb->len); + + if (WARN_ON_ONCE(ar->state == ATH6KL_STATE_WOW)) { + dev_kfree_skb(skb); + return -EACCES; + } + + if (WARN_ON_ONCE(eid == ENDPOINT_UNUSED || + eid >= ENDPOINT_MAX)) { + status = -EINVAL; + goto fail_ctrl_tx; + } + + spin_lock_bh(&ar->lock); + + ath6kl_dbg(ATH6KL_DBG_WLAN_TX, + "%s: skb=0x%p, len=0x%x eid =%d\n", __func__, + skb, skb->len, eid); + + if (test_bit(WMI_CTRL_EP_FULL, &ar->flag) && (eid == ar->ctrl_ep)) { + /* + * Control endpoint is full, don't allocate resources, we + * are just going to drop this packet. + */ + cookie = NULL; + ath6kl_err("wmi ctrl ep full, dropping pkt : 0x%p, len:%d\n", + skb, skb->len); + } else + cookie = ath6kl_alloc_cookie(ar); + + if (cookie == NULL) { + spin_unlock_bh(&ar->lock); + status = -ENOMEM; + goto fail_ctrl_tx; + } + + ar->tx_pending[eid]++; + + if (eid != ar->ctrl_ep) + ar->total_tx_data_pend++; + + spin_unlock_bh(&ar->lock); + + cookie->skb = skb; + cookie->map_no = 0; + set_htc_pkt_info(&cookie->htc_pkt, cookie, skb->data, skb->len, + eid, ATH6KL_CONTROL_PKT_TAG); + cookie->htc_pkt.skb = skb; + + /* + * This interface is asynchronous, if there is an error, cleanup + * will happen in the TX completion callback. + */ + ath6kl_htc_tx(ar->htc_target, &cookie->htc_pkt); + + return 0; + +fail_ctrl_tx: + dev_kfree_skb(skb); + return status; +} + +int ath6kl_data_tx(struct sk_buff *skb, struct net_device *dev) +{ + struct ath6kl *ar = ath6kl_priv(dev); + struct ath6kl_cookie *cookie = NULL; + enum htc_endpoint_id eid = ENDPOINT_UNUSED; + struct ath6kl_vif *vif = netdev_priv(dev); + u32 map_no = 0; + u16 htc_tag = ATH6KL_DATA_PKT_TAG; + u8 ac = 99 ; /* initialize to unmapped ac */ + bool chk_adhoc_ps_mapping = false; + int ret; + struct wmi_tx_meta_v2 meta_v2; + void *meta; + u8 csum_start = 0, csum_dest = 0, csum = skb->ip_summed; + u8 meta_ver = 0; + u32 flags = 0; + + ath6kl_dbg(ATH6KL_DBG_WLAN_TX, + "%s: skb=0x%p, data=0x%p, len=0x%x\n", __func__, + skb, skb->data, skb->len); + + /* If target is not associated */ + if (!test_bit(CONNECTED, &vif->flags)) + goto fail_tx; + + if (WARN_ON_ONCE(ar->state != ATH6KL_STATE_ON)) + goto fail_tx; + + if (!test_bit(WMI_READY, &ar->flag)) + goto fail_tx; + + /* AP mode Power saving processing */ + if (vif->nw_type == AP_NETWORK) { + if (ath6kl_powersave_ap(vif, skb, &flags)) + return 0; + } + + if (test_bit(WMI_ENABLED, &ar->flag)) { + if ((dev->features & NETIF_F_IP_CSUM) && + (csum == CHECKSUM_PARTIAL)) { + csum_start = skb->csum_start - + (skb_network_header(skb) - skb->head) + + sizeof(struct ath6kl_llc_snap_hdr); + csum_dest = skb->csum_offset + csum_start; + } + + if (skb_headroom(skb) < dev->needed_headroom) { + struct sk_buff *tmp_skb = skb; + + skb = skb_realloc_headroom(skb, dev->needed_headroom); + kfree_skb(tmp_skb); + if (skb == NULL) { + vif->net_stats.tx_dropped++; + return 0; + } + } + + if (ath6kl_wmi_dix_2_dot3(ar->wmi, skb)) { + ath6kl_err("ath6kl_wmi_dix_2_dot3 failed\n"); + goto fail_tx; + } + + if ((dev->features & NETIF_F_IP_CSUM) && + (csum == CHECKSUM_PARTIAL)) { + meta_v2.csum_start = csum_start; + meta_v2.csum_dest = csum_dest; + + /* instruct target to calculate checksum */ + meta_v2.csum_flags = WMI_META_V2_FLAG_CSUM_OFFLOAD; + meta_ver = WMI_META_VERSION_2; + meta = &meta_v2; + } else { + meta_ver = 0; + meta = NULL; + } + + ret = ath6kl_wmi_data_hdr_add(ar->wmi, skb, + DATA_MSGTYPE, flags, 0, + meta_ver, + meta, vif->fw_vif_idx); + + if (ret) { + ath6kl_warn("failed to add wmi data header:%d\n" + , ret); + goto fail_tx; + } + + if ((vif->nw_type == ADHOC_NETWORK) && + ar->ibss_ps_enable && test_bit(CONNECTED, &vif->flags)) + chk_adhoc_ps_mapping = true; + else { + /* get the stream mapping */ + ret = ath6kl_wmi_implicit_create_pstream(ar->wmi, + vif->fw_vif_idx, skb, + 0, test_bit(WMM_ENABLED, &vif->flags), &ac); + if (ret) + goto fail_tx; + } + } else + goto fail_tx; + + spin_lock_bh(&ar->lock); + + if (chk_adhoc_ps_mapping) + eid = ath6kl_ibss_map_epid(skb, dev, &map_no); + else + eid = ar->ac2ep_map[ac]; + + if (eid == 0 || eid == ENDPOINT_UNUSED) { + ath6kl_err("eid %d is not mapped!\n", eid); + spin_unlock_bh(&ar->lock); + goto fail_tx; + } + + /* allocate resource for this packet */ + cookie = ath6kl_alloc_cookie(ar); + + if (!cookie) { + spin_unlock_bh(&ar->lock); + goto fail_tx; + } + + /* update counts while the lock is held */ + ar->tx_pending[eid]++; + ar->total_tx_data_pend++; + + spin_unlock_bh(&ar->lock); + + if (!IS_ALIGNED((unsigned long) skb->data - HTC_HDR_LENGTH, 4) && + skb_cloned(skb)) { + /* + * We will touch (move the buffer data to align it. Since the + * skb buffer is cloned and not only the header is changed, we + * have to copy it to allow the changes. Since we are copying + * the data here, we may as well align it by reserving suitable + * headroom to avoid the memmove in ath6kl_htc_tx_buf_align(). + */ + struct sk_buff *nskb; + + nskb = skb_copy_expand(skb, HTC_HDR_LENGTH, 0, GFP_ATOMIC); + if (nskb == NULL) + goto fail_tx; + kfree_skb(skb); + skb = nskb; + } + + cookie->skb = skb; + cookie->map_no = map_no; + set_htc_pkt_info(&cookie->htc_pkt, cookie, skb->data, skb->len, + eid, htc_tag); + cookie->htc_pkt.skb = skb; + + ath6kl_dbg_dump(ATH6KL_DBG_RAW_BYTES, __func__, "tx ", + skb->data, skb->len); + + /* + * HTC interface is asynchronous, if this fails, cleanup will + * happen in the ath6kl_tx_complete callback. + */ + ath6kl_htc_tx(ar->htc_target, &cookie->htc_pkt); + + return 0; + +fail_tx: + dev_kfree_skb(skb); + + vif->net_stats.tx_dropped++; + vif->net_stats.tx_aborted_errors++; + + return 0; +} + +/* indicate tx activity or inactivity on a WMI stream */ +void ath6kl_indicate_tx_activity(void *devt, u8 traffic_class, bool active) +{ + struct ath6kl *ar = devt; + enum htc_endpoint_id eid; + int i; + + eid = ar->ac2ep_map[traffic_class]; + + if (!test_bit(WMI_ENABLED, &ar->flag)) + goto notify_htc; + + spin_lock_bh(&ar->lock); + + ar->ac_stream_active[traffic_class] = active; + + if (active) { + /* + * Keep track of the active stream with the highest + * priority. + */ + if (ar->ac_stream_pri_map[traffic_class] > + ar->hiac_stream_active_pri) + /* set the new highest active priority */ + ar->hiac_stream_active_pri = + ar->ac_stream_pri_map[traffic_class]; + + } else { + /* + * We may have to search for the next active stream + * that is the highest priority. + */ + if (ar->hiac_stream_active_pri == + ar->ac_stream_pri_map[traffic_class]) { + /* + * The highest priority stream just went inactive + * reset and search for the "next" highest "active" + * priority stream. + */ + ar->hiac_stream_active_pri = 0; + + for (i = 0; i < WMM_NUM_AC; i++) { + if (ar->ac_stream_active[i] && + (ar->ac_stream_pri_map[i] > + ar->hiac_stream_active_pri)) + /* + * Set the new highest active + * priority. + */ + ar->hiac_stream_active_pri = + ar->ac_stream_pri_map[i]; + } + } + } + + spin_unlock_bh(&ar->lock); + +notify_htc: + /* notify HTC, this may cause credit distribution changes */ + ath6kl_htc_activity_changed(ar->htc_target, eid, active); +} + +enum htc_send_full_action ath6kl_tx_queue_full(struct htc_target *target, + struct htc_packet *packet) +{ + struct ath6kl *ar = target->dev->ar; + struct ath6kl_vif *vif; + enum htc_endpoint_id endpoint = packet->endpoint; + enum htc_send_full_action action = HTC_SEND_FULL_KEEP; + + if (endpoint == ar->ctrl_ep) { + /* + * Under normal WMI if this is getting full, then something + * is running rampant the host should not be exhausting the + * WMI queue with too many commands the only exception to + * this is during testing using endpointping. + */ + set_bit(WMI_CTRL_EP_FULL, &ar->flag); + ath6kl_err("wmi ctrl ep is full\n"); + ath6kl_recovery_err_notify(ar, ATH6KL_FW_EP_FULL); + return action; + } + + if (packet->info.tx.tag == ATH6KL_CONTROL_PKT_TAG) + return action; + + /* + * The last MAX_HI_COOKIE_NUM "batch" of cookies are reserved for + * the highest active stream. + */ + if (ar->ac_stream_pri_map[ar->ep2ac_map[endpoint]] < + ar->hiac_stream_active_pri && + ar->cookie_count <= + target->endpoint[endpoint].tx_drop_packet_threshold) + /* + * Give preference to the highest priority stream by + * dropping the packets which overflowed. + */ + action = HTC_SEND_FULL_DROP; + + /* FIXME: Locking */ + spin_lock_bh(&ar->list_lock); + list_for_each_entry(vif, &ar->vif_list, list) { + if (vif->nw_type == ADHOC_NETWORK || + action != HTC_SEND_FULL_DROP) { + spin_unlock_bh(&ar->list_lock); + + set_bit(NETQ_STOPPED, &vif->flags); + netif_stop_queue(vif->ndev); + + return action; + } + } + spin_unlock_bh(&ar->list_lock); + + return action; +} + +/* TODO this needs to be looked at */ +static void ath6kl_tx_clear_node_map(struct ath6kl_vif *vif, + enum htc_endpoint_id eid, u32 map_no) +{ + struct ath6kl *ar = vif->ar; + u32 i; + + if (vif->nw_type != ADHOC_NETWORK) + return; + + if (!ar->ibss_ps_enable) + return; + + if (eid == ar->ctrl_ep) + return; + + if (map_no == 0) + return; + + map_no--; + ar->node_map[map_no].tx_pend--; + + if (ar->node_map[map_no].tx_pend) + return; + + if (map_no != (ar->node_num - 1)) + return; + + for (i = ar->node_num; i > 0; i--) { + if (ar->node_map[i - 1].tx_pend) + break; + + memset(&ar->node_map[i - 1], 0, + sizeof(struct ath6kl_node_mapping)); + ar->node_num--; + } +} + +void ath6kl_tx_complete(struct htc_target *target, + struct list_head *packet_queue) +{ + struct ath6kl *ar = target->dev->ar; + struct sk_buff_head skb_queue; + struct htc_packet *packet; + struct sk_buff *skb; + struct ath6kl_cookie *ath6kl_cookie; + u32 map_no = 0; + int status; + enum htc_endpoint_id eid; + bool wake_event = false; + bool flushing[ATH6KL_VIF_MAX] = {false}; + u8 if_idx; + struct ath6kl_vif *vif; + + skb_queue_head_init(&skb_queue); + + /* lock the driver as we update internal state */ + spin_lock_bh(&ar->lock); + + /* reap completed packets */ + while (!list_empty(packet_queue)) { + + packet = list_first_entry(packet_queue, struct htc_packet, + list); + list_del(&packet->list); + + if (WARN_ON_ONCE(packet->endpoint == ENDPOINT_UNUSED || + packet->endpoint >= ENDPOINT_MAX)) + continue; + + ath6kl_cookie = (struct ath6kl_cookie *)packet->pkt_cntxt; + if (WARN_ON_ONCE(!ath6kl_cookie)) + continue; + + status = packet->status; + skb = ath6kl_cookie->skb; + eid = packet->endpoint; + map_no = ath6kl_cookie->map_no; + + if (WARN_ON_ONCE(!skb || !skb->data)) { + dev_kfree_skb(skb); + ath6kl_free_cookie(ar, ath6kl_cookie); + continue; + } + + __skb_queue_tail(&skb_queue, skb); + + if (WARN_ON_ONCE(!status && (packet->act_len != skb->len))) { + ath6kl_free_cookie(ar, ath6kl_cookie); + continue; + } + + ar->tx_pending[eid]--; + + if (eid != ar->ctrl_ep) + ar->total_tx_data_pend--; + + if (eid == ar->ctrl_ep) { + if (test_bit(WMI_CTRL_EP_FULL, &ar->flag)) + clear_bit(WMI_CTRL_EP_FULL, &ar->flag); + + if (ar->tx_pending[eid] == 0) + wake_event = true; + } + + if (eid == ar->ctrl_ep) { + if_idx = wmi_cmd_hdr_get_if_idx( + (struct wmi_cmd_hdr *) packet->buf); + } else { + if_idx = wmi_data_hdr_get_if_idx( + (struct wmi_data_hdr *) packet->buf); + } + + vif = ath6kl_get_vif_by_index(ar, if_idx); + if (!vif) { + ath6kl_free_cookie(ar, ath6kl_cookie); + continue; + } + + if (status) { + if (status == -ECANCELED) + /* a packet was flushed */ + flushing[if_idx] = true; + + vif->net_stats.tx_errors++; + + if (status != -ENOSPC && status != -ECANCELED) + ath6kl_warn("tx complete error: %d\n", status); + + ath6kl_dbg(ATH6KL_DBG_WLAN_TX, + "%s: skb=0x%p data=0x%p len=0x%x eid=%d %s\n", + __func__, skb, packet->buf, packet->act_len, + eid, "error!"); + } else { + ath6kl_dbg(ATH6KL_DBG_WLAN_TX, + "%s: skb=0x%p data=0x%p len=0x%x eid=%d %s\n", + __func__, skb, packet->buf, packet->act_len, + eid, "OK"); + + flushing[if_idx] = false; + vif->net_stats.tx_packets++; + vif->net_stats.tx_bytes += skb->len; + } + + ath6kl_tx_clear_node_map(vif, eid, map_no); + + ath6kl_free_cookie(ar, ath6kl_cookie); + + if (test_bit(NETQ_STOPPED, &vif->flags)) + clear_bit(NETQ_STOPPED, &vif->flags); + } + + spin_unlock_bh(&ar->lock); + + __skb_queue_purge(&skb_queue); + + /* FIXME: Locking */ + spin_lock_bh(&ar->list_lock); + list_for_each_entry(vif, &ar->vif_list, list) { + if (test_bit(CONNECTED, &vif->flags) && + !flushing[vif->fw_vif_idx]) { + spin_unlock_bh(&ar->list_lock); + netif_wake_queue(vif->ndev); + spin_lock_bh(&ar->list_lock); + } + } + spin_unlock_bh(&ar->list_lock); + + if (wake_event) + wake_up(&ar->event_wq); + + return; +} + +void ath6kl_tx_data_cleanup(struct ath6kl *ar) +{ + int i; + + /* flush all the data (non-control) streams */ + for (i = 0; i < WMM_NUM_AC; i++) + ath6kl_htc_flush_txep(ar->htc_target, ar->ac2ep_map[i], + ATH6KL_DATA_PKT_TAG); +} + +/* Rx functions */ + +static void ath6kl_deliver_frames_to_nw_stack(struct net_device *dev, + struct sk_buff *skb) +{ + if (!skb) + return; + + skb->dev = dev; + + if (!(skb->dev->flags & IFF_UP)) { + dev_kfree_skb(skb); + return; + } + + skb->protocol = eth_type_trans(skb, skb->dev); + + netif_rx_ni(skb); +} + +static void ath6kl_alloc_netbufs(struct sk_buff_head *q, u16 num) +{ + struct sk_buff *skb; + + while (num) { + skb = ath6kl_buf_alloc(ATH6KL_BUFFER_SIZE); + if (!skb) { + ath6kl_err("netbuf allocation failed\n"); + return; + } + skb_queue_tail(q, skb); + num--; + } +} + +static struct sk_buff *aggr_get_free_skb(struct aggr_info *p_aggr) +{ + struct sk_buff *skb = NULL; + + if (skb_queue_len(&p_aggr->rx_amsdu_freeq) < + (AGGR_NUM_OF_FREE_NETBUFS >> 2)) + ath6kl_alloc_netbufs(&p_aggr->rx_amsdu_freeq, + AGGR_NUM_OF_FREE_NETBUFS); + + skb = skb_dequeue(&p_aggr->rx_amsdu_freeq); + + return skb; +} + +void ath6kl_rx_refill(struct htc_target *target, enum htc_endpoint_id endpoint) +{ + struct ath6kl *ar = target->dev->ar; + struct sk_buff *skb; + int rx_buf; + int n_buf_refill; + struct htc_packet *packet; + struct list_head queue; + + n_buf_refill = ATH6KL_MAX_RX_BUFFERS - + ath6kl_htc_get_rxbuf_num(ar->htc_target, endpoint); + + if (n_buf_refill <= 0) + return; + + INIT_LIST_HEAD(&queue); + + ath6kl_dbg(ATH6KL_DBG_WLAN_RX, + "%s: providing htc with %d buffers at eid=%d\n", + __func__, n_buf_refill, endpoint); + + for (rx_buf = 0; rx_buf < n_buf_refill; rx_buf++) { + skb = ath6kl_buf_alloc(ATH6KL_BUFFER_SIZE); + if (!skb) + break; + + packet = (struct htc_packet *) skb->head; + if (!IS_ALIGNED((unsigned long) skb->data, 4)) { + size_t len = skb_headlen(skb); + skb->data = PTR_ALIGN(skb->data - 4, 4); + skb_set_tail_pointer(skb, len); + } + set_htc_rxpkt_info(packet, skb, skb->data, + ATH6KL_BUFFER_SIZE, endpoint); + packet->skb = skb; + list_add_tail(&packet->list, &queue); + } + + if (!list_empty(&queue)) + ath6kl_htc_add_rxbuf_multiple(ar->htc_target, &queue); +} + +void ath6kl_refill_amsdu_rxbufs(struct ath6kl *ar, int count) +{ + struct htc_packet *packet; + struct sk_buff *skb; + + while (count) { + skb = ath6kl_buf_alloc(ATH6KL_AMSDU_BUFFER_SIZE); + if (!skb) + return; + + packet = (struct htc_packet *) skb->head; + if (!IS_ALIGNED((unsigned long) skb->data, 4)) { + size_t len = skb_headlen(skb); + skb->data = PTR_ALIGN(skb->data - 4, 4); + skb_set_tail_pointer(skb, len); + } + set_htc_rxpkt_info(packet, skb, skb->data, + ATH6KL_AMSDU_BUFFER_SIZE, 0); + packet->skb = skb; + + spin_lock_bh(&ar->lock); + list_add_tail(&packet->list, &ar->amsdu_rx_buffer_queue); + spin_unlock_bh(&ar->lock); + count--; + } +} + +/* + * Callback to allocate a receive buffer for a pending packet. We use a + * pre-allocated list of buffers of maximum AMSDU size (4K). + */ +struct htc_packet *ath6kl_alloc_amsdu_rxbuf(struct htc_target *target, + enum htc_endpoint_id endpoint, + int len) +{ + struct ath6kl *ar = target->dev->ar; + struct htc_packet *packet = NULL; + struct list_head *pkt_pos; + int refill_cnt = 0, depth = 0; + + ath6kl_dbg(ATH6KL_DBG_WLAN_RX, "%s: eid=%d, len:%d\n", + __func__, endpoint, len); + + if ((len <= ATH6KL_BUFFER_SIZE) || + (len > ATH6KL_AMSDU_BUFFER_SIZE)) + return NULL; + + spin_lock_bh(&ar->lock); + + if (list_empty(&ar->amsdu_rx_buffer_queue)) { + spin_unlock_bh(&ar->lock); + refill_cnt = ATH6KL_MAX_AMSDU_RX_BUFFERS; + goto refill_buf; + } + + packet = list_first_entry(&ar->amsdu_rx_buffer_queue, + struct htc_packet, list); + list_del(&packet->list); + list_for_each(pkt_pos, &ar->amsdu_rx_buffer_queue) + depth++; + + refill_cnt = ATH6KL_MAX_AMSDU_RX_BUFFERS - depth; + spin_unlock_bh(&ar->lock); + + /* set actual endpoint ID */ + packet->endpoint = endpoint; + +refill_buf: + if (refill_cnt >= ATH6KL_AMSDU_REFILL_THRESHOLD) + ath6kl_refill_amsdu_rxbufs(ar, refill_cnt); + + return packet; +} + +static void aggr_slice_amsdu(struct aggr_info *p_aggr, + struct rxtid *rxtid, struct sk_buff *skb) +{ + struct sk_buff *new_skb; + struct ethhdr *hdr; + u16 frame_8023_len, payload_8023_len, mac_hdr_len, amsdu_len; + u8 *framep; + + mac_hdr_len = sizeof(struct ethhdr); + framep = skb->data + mac_hdr_len; + amsdu_len = skb->len - mac_hdr_len; + + while (amsdu_len > mac_hdr_len) { + hdr = (struct ethhdr *) framep; + payload_8023_len = ntohs(hdr->h_proto); + + if (payload_8023_len < MIN_MSDU_SUBFRAME_PAYLOAD_LEN || + payload_8023_len > MAX_MSDU_SUBFRAME_PAYLOAD_LEN) { + ath6kl_err("802.3 AMSDU frame bound check failed. len %d\n", + payload_8023_len); + break; + } + + frame_8023_len = payload_8023_len + mac_hdr_len; + new_skb = aggr_get_free_skb(p_aggr); + if (!new_skb) { + ath6kl_err("no buffer available\n"); + break; + } + + memcpy(new_skb->data, framep, frame_8023_len); + skb_put(new_skb, frame_8023_len); + if (ath6kl_wmi_dot3_2_dix(new_skb)) { + ath6kl_err("dot3_2_dix error\n"); + dev_kfree_skb(new_skb); + break; + } + + skb_queue_tail(&rxtid->q, new_skb); + + /* Is this the last subframe within this aggregate ? */ + if ((amsdu_len - frame_8023_len) == 0) + break; + + /* Add the length of A-MSDU subframe padding bytes - + * Round to nearest word. + */ + frame_8023_len = ALIGN(frame_8023_len, 4); + + framep += frame_8023_len; + amsdu_len -= frame_8023_len; + } + + dev_kfree_skb(skb); +} + +static void aggr_deque_frms(struct aggr_info_conn *agg_conn, u8 tid, + u16 seq_no, u8 order) +{ + struct sk_buff *skb; + struct rxtid *rxtid; + struct skb_hold_q *node; + u16 idx, idx_end, seq_end; + struct rxtid_stats *stats; + + rxtid = &agg_conn->rx_tid[tid]; + stats = &agg_conn->stat[tid]; + + spin_lock_bh(&rxtid->lock); + idx = AGGR_WIN_IDX(rxtid->seq_next, rxtid->hold_q_sz); + + /* + * idx_end is typically the last possible frame in the window, + * but changes to 'the' seq_no, when BAR comes. If seq_no + * is non-zero, we will go up to that and stop. + * Note: last seq no in current window will occupy the same + * index position as index that is just previous to start. + * An imp point : if win_sz is 7, for seq_no space of 4095, + * then, there would be holes when sequence wrap around occurs. + * Target should judiciously choose the win_sz, based on + * this condition. For 4095, (TID_WINDOW_SZ = 2 x win_sz + * 2, 4, 8, 16 win_sz works fine). + * We must deque from "idx" to "idx_end", including both. + */ + seq_end = seq_no ? seq_no : rxtid->seq_next; + idx_end = AGGR_WIN_IDX(seq_end, rxtid->hold_q_sz); + + do { + node = &rxtid->hold_q[idx]; + if ((order == 1) && (!node->skb)) + break; + + if (node->skb) { + if (node->is_amsdu) + aggr_slice_amsdu(agg_conn->aggr_info, rxtid, + node->skb); + else + skb_queue_tail(&rxtid->q, node->skb); + node->skb = NULL; + } else + stats->num_hole++; + + rxtid->seq_next = ATH6KL_NEXT_SEQ_NO(rxtid->seq_next); + idx = AGGR_WIN_IDX(rxtid->seq_next, rxtid->hold_q_sz); + } while (idx != idx_end); + + spin_unlock_bh(&rxtid->lock); + + stats->num_delivered += skb_queue_len(&rxtid->q); + + while ((skb = skb_dequeue(&rxtid->q))) + ath6kl_deliver_frames_to_nw_stack(agg_conn->dev, skb); +} + +static bool aggr_process_recv_frm(struct aggr_info_conn *agg_conn, u8 tid, + u16 seq_no, + bool is_amsdu, struct sk_buff *frame) +{ + struct rxtid *rxtid; + struct rxtid_stats *stats; + struct sk_buff *skb; + struct skb_hold_q *node; + u16 idx, st, cur, end; + bool is_queued = false; + u16 extended_end; + + rxtid = &agg_conn->rx_tid[tid]; + stats = &agg_conn->stat[tid]; + + stats->num_into_aggr++; + + if (!rxtid->aggr) { + if (is_amsdu) { + aggr_slice_amsdu(agg_conn->aggr_info, rxtid, frame); + is_queued = true; + stats->num_amsdu++; + while ((skb = skb_dequeue(&rxtid->q))) + ath6kl_deliver_frames_to_nw_stack(agg_conn->dev, + skb); + } + return is_queued; + } + + /* Check the incoming sequence no, if it's in the window */ + st = rxtid->seq_next; + cur = seq_no; + end = (st + rxtid->hold_q_sz-1) & ATH6KL_MAX_SEQ_NO; + + if (((st < end) && (cur < st || cur > end)) || + ((st > end) && (cur > end) && (cur < st))) { + extended_end = (end + rxtid->hold_q_sz - 1) & + ATH6KL_MAX_SEQ_NO; + + if (((end < extended_end) && + (cur < end || cur > extended_end)) || + ((end > extended_end) && (cur > extended_end) && + (cur < end))) { + aggr_deque_frms(agg_conn, tid, 0, 0); + spin_lock_bh(&rxtid->lock); + if (cur >= rxtid->hold_q_sz - 1) + rxtid->seq_next = cur - (rxtid->hold_q_sz - 1); + else + rxtid->seq_next = ATH6KL_MAX_SEQ_NO - + (rxtid->hold_q_sz - 2 - cur); + spin_unlock_bh(&rxtid->lock); + } else { + /* + * Dequeue only those frames that are outside the + * new shifted window. + */ + if (cur >= rxtid->hold_q_sz - 1) + st = cur - (rxtid->hold_q_sz - 1); + else + st = ATH6KL_MAX_SEQ_NO - + (rxtid->hold_q_sz - 2 - cur); + + aggr_deque_frms(agg_conn, tid, st, 0); + } + + stats->num_oow++; + } + + idx = AGGR_WIN_IDX(seq_no, rxtid->hold_q_sz); + + node = &rxtid->hold_q[idx]; + + spin_lock_bh(&rxtid->lock); + + /* + * Is the cur frame duplicate or something beyond our window(hold_q + * -> which is 2x, already)? + * + * 1. Duplicate is easy - drop incoming frame. + * 2. Not falling in current sliding window. + * 2a. is the frame_seq_no preceding current tid_seq_no? + * -> drop the frame. perhaps sender did not get our ACK. + * this is taken care of above. + * 2b. is the frame_seq_no beyond window(st, TID_WINDOW_SZ); + * -> Taken care of it above, by moving window forward. + */ + dev_kfree_skb(node->skb); + stats->num_dups++; + + node->skb = frame; + is_queued = true; + node->is_amsdu = is_amsdu; + node->seq_no = seq_no; + + if (node->is_amsdu) + stats->num_amsdu++; + else + stats->num_mpdu++; + + spin_unlock_bh(&rxtid->lock); + + aggr_deque_frms(agg_conn, tid, 0, 1); + + if (agg_conn->timer_scheduled) + return is_queued; + + spin_lock_bh(&rxtid->lock); + for (idx = 0 ; idx < rxtid->hold_q_sz; idx++) { + if (rxtid->hold_q[idx].skb) { + /* + * There is a frame in the queue and no + * timer so start a timer to ensure that + * the frame doesn't remain stuck + * forever. + */ + agg_conn->timer_scheduled = true; + mod_timer(&agg_conn->timer, + (jiffies + (HZ * AGGR_RX_TIMEOUT) / 1000)); + rxtid->timer_mon = true; + break; + } + } + spin_unlock_bh(&rxtid->lock); + + return is_queued; +} + +static void ath6kl_uapsd_trigger_frame_rx(struct ath6kl_vif *vif, + struct ath6kl_sta *conn) +{ + struct ath6kl *ar = vif->ar; + bool is_apsdq_empty, is_apsdq_empty_at_start; + u32 num_frames_to_deliver, flags; + struct sk_buff *skb = NULL; + + /* + * If the APSD q for this STA is not empty, dequeue and + * send a pkt from the head of the q. Also update the + * More data bit in the WMI_DATA_HDR if there are + * more pkts for this STA in the APSD q. + * If there are no more pkts for this STA, + * update the APSD bitmap for this STA. + */ + + num_frames_to_deliver = (conn->apsd_info >> ATH6KL_APSD_NUM_OF_AC) & + ATH6KL_APSD_FRAME_MASK; + /* + * Number of frames to send in a service period is + * indicated by the station + * in the QOS_INFO of the association request + * If it is zero, send all frames + */ + if (!num_frames_to_deliver) + num_frames_to_deliver = ATH6KL_APSD_ALL_FRAME; + + spin_lock_bh(&conn->psq_lock); + is_apsdq_empty = skb_queue_empty(&conn->apsdq); + spin_unlock_bh(&conn->psq_lock); + is_apsdq_empty_at_start = is_apsdq_empty; + + while ((!is_apsdq_empty) && (num_frames_to_deliver)) { + + spin_lock_bh(&conn->psq_lock); + skb = skb_dequeue(&conn->apsdq); + is_apsdq_empty = skb_queue_empty(&conn->apsdq); + spin_unlock_bh(&conn->psq_lock); + + /* + * Set the STA flag to Trigger delivery, + * so that the frame will go out + */ + conn->sta_flags |= STA_PS_APSD_TRIGGER; + num_frames_to_deliver--; + + /* Last frame in the service period, set EOSP or queue empty */ + if ((is_apsdq_empty) || (!num_frames_to_deliver)) + conn->sta_flags |= STA_PS_APSD_EOSP; + + ath6kl_data_tx(skb, vif->ndev); + conn->sta_flags &= ~(STA_PS_APSD_TRIGGER); + conn->sta_flags &= ~(STA_PS_APSD_EOSP); + } + + if (is_apsdq_empty) { + if (is_apsdq_empty_at_start) + flags = WMI_AP_APSD_NO_DELIVERY_FRAMES; + else + flags = 0; + + ath6kl_wmi_set_apsd_bfrd_traf(ar->wmi, + vif->fw_vif_idx, + conn->aid, 0, flags); + } + + return; +} + +void ath6kl_rx(struct htc_target *target, struct htc_packet *packet) +{ + struct ath6kl *ar = target->dev->ar; + struct sk_buff *skb = packet->pkt_cntxt; + struct wmi_rx_meta_v2 *meta; + struct wmi_data_hdr *dhdr; + int min_hdr_len; + u8 meta_type, dot11_hdr = 0; + u8 pad_before_data_start; + int status = packet->status; + enum htc_endpoint_id ept = packet->endpoint; + bool is_amsdu, prev_ps, ps_state = false; + bool trig_state = false; + struct ath6kl_sta *conn = NULL; + struct sk_buff *skb1 = NULL; + struct ethhdr *datap = NULL; + struct ath6kl_vif *vif; + struct aggr_info_conn *aggr_conn; + u16 seq_no, offset; + u8 tid, if_idx; + + ath6kl_dbg(ATH6KL_DBG_WLAN_RX, + "%s: ar=0x%p eid=%d, skb=0x%p, data=0x%p, len=0x%x status:%d", + __func__, ar, ept, skb, packet->buf, + packet->act_len, status); + + if (status || packet->act_len < HTC_HDR_LENGTH) { + dev_kfree_skb(skb); + return; + } + + skb_put(skb, packet->act_len + HTC_HDR_LENGTH); + skb_pull(skb, HTC_HDR_LENGTH); + + ath6kl_dbg_dump(ATH6KL_DBG_RAW_BYTES, __func__, "rx ", + skb->data, skb->len); + + if (ept == ar->ctrl_ep) { + if (test_bit(WMI_ENABLED, &ar->flag)) { + ath6kl_check_wow_status(ar); + ath6kl_wmi_control_rx(ar->wmi, skb); + return; + } + if_idx = + wmi_cmd_hdr_get_if_idx((struct wmi_cmd_hdr *) skb->data); + } else { + if_idx = + wmi_data_hdr_get_if_idx((struct wmi_data_hdr *) skb->data); + } + + vif = ath6kl_get_vif_by_index(ar, if_idx); + if (!vif) { + dev_kfree_skb(skb); + return; + } + + /* + * Take lock to protect buffer counts and adaptive power throughput + * state. + */ + spin_lock_bh(&vif->if_lock); + + vif->net_stats.rx_packets++; + vif->net_stats.rx_bytes += packet->act_len; + + spin_unlock_bh(&vif->if_lock); + + skb->dev = vif->ndev; + + if (!test_bit(WMI_ENABLED, &ar->flag)) { + if (EPPING_ALIGNMENT_PAD > 0) + skb_pull(skb, EPPING_ALIGNMENT_PAD); + ath6kl_deliver_frames_to_nw_stack(vif->ndev, skb); + return; + } + + ath6kl_check_wow_status(ar); + + min_hdr_len = sizeof(struct ethhdr) + sizeof(struct wmi_data_hdr) + + sizeof(struct ath6kl_llc_snap_hdr); + + dhdr = (struct wmi_data_hdr *) skb->data; + + /* + * In the case of AP mode we may receive NULL data frames + * that do not have LLC hdr. They are 16 bytes in size. + * Allow these frames in the AP mode. + */ + if (vif->nw_type != AP_NETWORK && + ((packet->act_len < min_hdr_len) || + (packet->act_len > WMI_MAX_AMSDU_RX_DATA_FRAME_LENGTH))) { + ath6kl_info("frame len is too short or too long\n"); + vif->net_stats.rx_errors++; + vif->net_stats.rx_length_errors++; + dev_kfree_skb(skb); + return; + } + + /* Get the Power save state of the STA */ + if (vif->nw_type == AP_NETWORK) { + meta_type = wmi_data_hdr_get_meta(dhdr); + + ps_state = !!((dhdr->info >> WMI_DATA_HDR_PS_SHIFT) & + WMI_DATA_HDR_PS_MASK); + + offset = sizeof(struct wmi_data_hdr); + trig_state = !!(le16_to_cpu(dhdr->info3) & WMI_DATA_HDR_TRIG); + + switch (meta_type) { + case 0: + break; + case WMI_META_VERSION_1: + offset += sizeof(struct wmi_rx_meta_v1); + break; + case WMI_META_VERSION_2: + offset += sizeof(struct wmi_rx_meta_v2); + break; + default: + break; + } + + datap = (struct ethhdr *) (skb->data + offset); + conn = ath6kl_find_sta(vif, datap->h_source); + + if (!conn) { + dev_kfree_skb(skb); + return; + } + + /* + * If there is a change in PS state of the STA, + * take appropriate steps: + * + * 1. If Sleep-->Awake, flush the psq for the STA + * Clear the PVB for the STA. + * 2. If Awake-->Sleep, Starting queueing frames + * the STA. + */ + prev_ps = !!(conn->sta_flags & STA_PS_SLEEP); + + if (ps_state) + conn->sta_flags |= STA_PS_SLEEP; + else + conn->sta_flags &= ~STA_PS_SLEEP; + + /* Accept trigger only when the station is in sleep */ + if ((conn->sta_flags & STA_PS_SLEEP) && trig_state) + ath6kl_uapsd_trigger_frame_rx(vif, conn); + + if (prev_ps ^ !!(conn->sta_flags & STA_PS_SLEEP)) { + if (!(conn->sta_flags & STA_PS_SLEEP)) { + struct sk_buff *skbuff = NULL; + bool is_apsdq_empty; + struct ath6kl_mgmt_buff *mgmt; + u8 idx; + + spin_lock_bh(&conn->psq_lock); + while (conn->mgmt_psq_len > 0) { + mgmt = list_first_entry( + &conn->mgmt_psq, + struct ath6kl_mgmt_buff, + list); + list_del(&mgmt->list); + conn->mgmt_psq_len--; + spin_unlock_bh(&conn->psq_lock); + idx = vif->fw_vif_idx; + + ath6kl_wmi_send_mgmt_cmd(ar->wmi, + idx, + mgmt->id, + mgmt->freq, + mgmt->wait, + mgmt->buf, + mgmt->len, + mgmt->no_cck); + + kfree(mgmt); + spin_lock_bh(&conn->psq_lock); + } + conn->mgmt_psq_len = 0; + while ((skbuff = skb_dequeue(&conn->psq))) { + spin_unlock_bh(&conn->psq_lock); + ath6kl_data_tx(skbuff, vif->ndev); + spin_lock_bh(&conn->psq_lock); + } + + is_apsdq_empty = skb_queue_empty(&conn->apsdq); + while ((skbuff = skb_dequeue(&conn->apsdq))) { + spin_unlock_bh(&conn->psq_lock); + ath6kl_data_tx(skbuff, vif->ndev); + spin_lock_bh(&conn->psq_lock); + } + spin_unlock_bh(&conn->psq_lock); + + if (!is_apsdq_empty) + ath6kl_wmi_set_apsd_bfrd_traf( + ar->wmi, + vif->fw_vif_idx, + conn->aid, 0, 0); + + /* Clear the PVB for this STA */ + ath6kl_wmi_set_pvb_cmd(ar->wmi, vif->fw_vif_idx, + conn->aid, 0); + } + } + + /* drop NULL data frames here */ + if ((packet->act_len < min_hdr_len) || + (packet->act_len > + WMI_MAX_AMSDU_RX_DATA_FRAME_LENGTH)) { + dev_kfree_skb(skb); + return; + } + } + + is_amsdu = wmi_data_hdr_is_amsdu(dhdr) ? true : false; + tid = wmi_data_hdr_get_up(dhdr); + seq_no = wmi_data_hdr_get_seqno(dhdr); + meta_type = wmi_data_hdr_get_meta(dhdr); + dot11_hdr = wmi_data_hdr_get_dot11(dhdr); + pad_before_data_start = + (le16_to_cpu(dhdr->info3) >> WMI_DATA_HDR_PAD_BEFORE_DATA_SHIFT) + & WMI_DATA_HDR_PAD_BEFORE_DATA_MASK; + + skb_pull(skb, sizeof(struct wmi_data_hdr)); + + switch (meta_type) { + case WMI_META_VERSION_1: + skb_pull(skb, sizeof(struct wmi_rx_meta_v1)); + break; + case WMI_META_VERSION_2: + meta = (struct wmi_rx_meta_v2 *) skb->data; + if (meta->csum_flags & 0x1) { + skb->ip_summed = CHECKSUM_COMPLETE; + skb->csum = (__force __wsum) meta->csum; + } + skb_pull(skb, sizeof(struct wmi_rx_meta_v2)); + break; + default: + break; + } + + skb_pull(skb, pad_before_data_start); + + if (dot11_hdr) + status = ath6kl_wmi_dot11_hdr_remove(ar->wmi, skb); + else if (!is_amsdu) + status = ath6kl_wmi_dot3_2_dix(skb); + + if (status) { + /* + * Drop frames that could not be processed (lack of + * memory, etc.) + */ + dev_kfree_skb(skb); + return; + } + + if (!(vif->ndev->flags & IFF_UP)) { + dev_kfree_skb(skb); + return; + } + + if (vif->nw_type == AP_NETWORK) { + datap = (struct ethhdr *) skb->data; + if (is_multicast_ether_addr(datap->h_dest)) + /* + * Bcast/Mcast frames should be sent to the + * OS stack as well as on the air. + */ + skb1 = skb_copy(skb, GFP_ATOMIC); + else { + /* + * Search for a connected STA with dstMac + * as the Mac address. If found send the + * frame to it on the air else send the + * frame up the stack. + */ + conn = ath6kl_find_sta(vif, datap->h_dest); + + if (conn && ar->intra_bss) { + skb1 = skb; + skb = NULL; + } else if (conn && !ar->intra_bss) { + dev_kfree_skb(skb); + skb = NULL; + } + } + if (skb1) + ath6kl_data_tx(skb1, vif->ndev); + + if (skb == NULL) { + /* nothing to deliver up the stack */ + return; + } + } + + datap = (struct ethhdr *) skb->data; + + if (is_unicast_ether_addr(datap->h_dest)) { + if (vif->nw_type == AP_NETWORK) { + conn = ath6kl_find_sta(vif, datap->h_source); + if (!conn) + return; + aggr_conn = conn->aggr_conn; + } else + aggr_conn = vif->aggr_cntxt->aggr_conn; + + if (aggr_process_recv_frm(aggr_conn, tid, seq_no, + is_amsdu, skb)) { + /* aggregation code will handle the skb */ + return; + } + } else if (!is_broadcast_ether_addr(datap->h_dest)) + vif->net_stats.multicast++; + + ath6kl_deliver_frames_to_nw_stack(vif->ndev, skb); +} + +static void aggr_timeout(unsigned long arg) +{ + u8 i, j; + struct aggr_info_conn *aggr_conn = (struct aggr_info_conn *) arg; + struct rxtid *rxtid; + struct rxtid_stats *stats; + + for (i = 0; i < NUM_OF_TIDS; i++) { + rxtid = &aggr_conn->rx_tid[i]; + stats = &aggr_conn->stat[i]; + + if (!rxtid->aggr || !rxtid->timer_mon) + continue; + + stats->num_timeouts++; + ath6kl_dbg(ATH6KL_DBG_AGGR, + "aggr timeout (st %d end %d)\n", + rxtid->seq_next, + ((rxtid->seq_next + rxtid->hold_q_sz-1) & + ATH6KL_MAX_SEQ_NO)); + aggr_deque_frms(aggr_conn, i, 0, 0); + } + + aggr_conn->timer_scheduled = false; + + for (i = 0; i < NUM_OF_TIDS; i++) { + rxtid = &aggr_conn->rx_tid[i]; + + if (rxtid->aggr && rxtid->hold_q) { + spin_lock_bh(&rxtid->lock); + for (j = 0; j < rxtid->hold_q_sz; j++) { + if (rxtid->hold_q[j].skb) { + aggr_conn->timer_scheduled = true; + rxtid->timer_mon = true; + break; + } + } + spin_unlock_bh(&rxtid->lock); + + if (j >= rxtid->hold_q_sz) + rxtid->timer_mon = false; + } + } + + if (aggr_conn->timer_scheduled) + mod_timer(&aggr_conn->timer, + jiffies + msecs_to_jiffies(AGGR_RX_TIMEOUT)); +} + +static void aggr_delete_tid_state(struct aggr_info_conn *aggr_conn, u8 tid) +{ + struct rxtid *rxtid; + struct rxtid_stats *stats; + + if (!aggr_conn || tid >= NUM_OF_TIDS) + return; + + rxtid = &aggr_conn->rx_tid[tid]; + stats = &aggr_conn->stat[tid]; + + if (rxtid->aggr) + aggr_deque_frms(aggr_conn, tid, 0, 0); + + rxtid->aggr = false; + rxtid->timer_mon = false; + rxtid->win_sz = 0; + rxtid->seq_next = 0; + rxtid->hold_q_sz = 0; + + kfree(rxtid->hold_q); + rxtid->hold_q = NULL; + + memset(stats, 0, sizeof(struct rxtid_stats)); +} + +void aggr_recv_addba_req_evt(struct ath6kl_vif *vif, u8 tid_mux, u16 seq_no, + u8 win_sz) +{ + struct ath6kl_sta *sta; + struct aggr_info_conn *aggr_conn = NULL; + struct rxtid *rxtid; + struct rxtid_stats *stats; + u16 hold_q_size; + u8 tid, aid; + + if (vif->nw_type == AP_NETWORK) { + aid = ath6kl_get_aid(tid_mux); + sta = ath6kl_find_sta_by_aid(vif->ar, aid); + if (sta) + aggr_conn = sta->aggr_conn; + } else + aggr_conn = vif->aggr_cntxt->aggr_conn; + + if (!aggr_conn) + return; + + tid = ath6kl_get_tid(tid_mux); + if (tid >= NUM_OF_TIDS) + return; + + rxtid = &aggr_conn->rx_tid[tid]; + stats = &aggr_conn->stat[tid]; + + if (win_sz < AGGR_WIN_SZ_MIN || win_sz > AGGR_WIN_SZ_MAX) + ath6kl_dbg(ATH6KL_DBG_WLAN_RX, "%s: win_sz %d, tid %d\n", + __func__, win_sz, tid); + + if (rxtid->aggr) + aggr_delete_tid_state(aggr_conn, tid); + + rxtid->seq_next = seq_no; + hold_q_size = TID_WINDOW_SZ(win_sz) * sizeof(struct skb_hold_q); + rxtid->hold_q = kzalloc(hold_q_size, GFP_KERNEL); + if (!rxtid->hold_q) + return; + + rxtid->win_sz = win_sz; + rxtid->hold_q_sz = TID_WINDOW_SZ(win_sz); + if (!skb_queue_empty(&rxtid->q)) + return; + + rxtid->aggr = true; +} + +void aggr_conn_init(struct ath6kl_vif *vif, struct aggr_info *aggr_info, + struct aggr_info_conn *aggr_conn) +{ + struct rxtid *rxtid; + u8 i; + + aggr_conn->aggr_sz = AGGR_SZ_DEFAULT; + aggr_conn->dev = vif->ndev; + init_timer(&aggr_conn->timer); + aggr_conn->timer.function = aggr_timeout; + aggr_conn->timer.data = (unsigned long) aggr_conn; + aggr_conn->aggr_info = aggr_info; + + aggr_conn->timer_scheduled = false; + + for (i = 0; i < NUM_OF_TIDS; i++) { + rxtid = &aggr_conn->rx_tid[i]; + rxtid->aggr = false; + rxtid->timer_mon = false; + skb_queue_head_init(&rxtid->q); + spin_lock_init(&rxtid->lock); + } + +} + +struct aggr_info *aggr_init(struct ath6kl_vif *vif) +{ + struct aggr_info *p_aggr = NULL; + + p_aggr = kzalloc(sizeof(struct aggr_info), GFP_KERNEL); + if (!p_aggr) { + ath6kl_err("failed to alloc memory for aggr_node\n"); + return NULL; + } + + p_aggr->aggr_conn = kzalloc(sizeof(struct aggr_info_conn), GFP_KERNEL); + if (!p_aggr->aggr_conn) { + ath6kl_err("failed to alloc memory for connection specific aggr info\n"); + kfree(p_aggr); + return NULL; + } + + aggr_conn_init(vif, p_aggr, p_aggr->aggr_conn); + + skb_queue_head_init(&p_aggr->rx_amsdu_freeq); + ath6kl_alloc_netbufs(&p_aggr->rx_amsdu_freeq, AGGR_NUM_OF_FREE_NETBUFS); + + return p_aggr; +} + +void aggr_recv_delba_req_evt(struct ath6kl_vif *vif, u8 tid_mux) +{ + struct ath6kl_sta *sta; + struct rxtid *rxtid; + struct aggr_info_conn *aggr_conn = NULL; + u8 tid, aid; + + if (vif->nw_type == AP_NETWORK) { + aid = ath6kl_get_aid(tid_mux); + sta = ath6kl_find_sta_by_aid(vif->ar, aid); + if (sta) + aggr_conn = sta->aggr_conn; + } else + aggr_conn = vif->aggr_cntxt->aggr_conn; + + if (!aggr_conn) + return; + + tid = ath6kl_get_tid(tid_mux); + if (tid >= NUM_OF_TIDS) + return; + + rxtid = &aggr_conn->rx_tid[tid]; + + if (rxtid->aggr) + aggr_delete_tid_state(aggr_conn, tid); +} + +void aggr_reset_state(struct aggr_info_conn *aggr_conn) +{ + u8 tid; + + if (!aggr_conn) + return; + + if (aggr_conn->timer_scheduled) { + del_timer(&aggr_conn->timer); + aggr_conn->timer_scheduled = false; + } + + for (tid = 0; tid < NUM_OF_TIDS; tid++) + aggr_delete_tid_state(aggr_conn, tid); +} + +/* clean up our amsdu buffer list */ +void ath6kl_cleanup_amsdu_rxbufs(struct ath6kl *ar) +{ + struct htc_packet *packet, *tmp_pkt; + + spin_lock_bh(&ar->lock); + if (list_empty(&ar->amsdu_rx_buffer_queue)) { + spin_unlock_bh(&ar->lock); + return; + } + + list_for_each_entry_safe(packet, tmp_pkt, &ar->amsdu_rx_buffer_queue, + list) { + list_del(&packet->list); + spin_unlock_bh(&ar->lock); + dev_kfree_skb(packet->pkt_cntxt); + spin_lock_bh(&ar->lock); + } + + spin_unlock_bh(&ar->lock); +} + +void aggr_module_destroy(struct aggr_info *aggr_info) +{ + if (!aggr_info) + return; + + aggr_reset_state(aggr_info->aggr_conn); + skb_queue_purge(&aggr_info->rx_amsdu_freeq); + kfree(aggr_info->aggr_conn); + kfree(aggr_info); +} diff --git a/drivers/net/wireless/ath/ath6kl/usb.c b/drivers/net/wireless/ath/ath6kl/usb.c new file mode 100644 index 0000000..6818132 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/usb.c @@ -0,0 +1,1250 @@ +/* + * Copyright (c) 2007-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "debug.h" +#include "core.h" + +/* constants */ +#define TX_URB_COUNT 32 +#define RX_URB_COUNT 32 +#define ATH6KL_USB_RX_BUFFER_SIZE 1700 + +/* tx/rx pipes for usb */ +enum ATH6KL_USB_PIPE_ID { + ATH6KL_USB_PIPE_TX_CTRL = 0, + ATH6KL_USB_PIPE_TX_DATA_LP, + ATH6KL_USB_PIPE_TX_DATA_MP, + ATH6KL_USB_PIPE_TX_DATA_HP, + ATH6KL_USB_PIPE_RX_CTRL, + ATH6KL_USB_PIPE_RX_DATA, + ATH6KL_USB_PIPE_RX_DATA2, + ATH6KL_USB_PIPE_RX_INT, + ATH6KL_USB_PIPE_MAX +}; + +#define ATH6KL_USB_PIPE_INVALID ATH6KL_USB_PIPE_MAX + +struct ath6kl_usb_pipe { + struct list_head urb_list_head; + struct usb_anchor urb_submitted; + u32 urb_alloc; + u32 urb_cnt; + u32 urb_cnt_thresh; + unsigned int usb_pipe_handle; + u32 flags; + u8 ep_address; + u8 logical_pipe_num; + struct ath6kl_usb *ar_usb; + u16 max_packet_size; + struct work_struct io_complete_work; + struct sk_buff_head io_comp_queue; + struct usb_endpoint_descriptor *ep_desc; +}; + +#define ATH6KL_USB_PIPE_FLAG_TX (1 << 0) + +/* usb device object */ +struct ath6kl_usb { + /* protects pipe->urb_list_head and pipe->urb_cnt */ + spinlock_t cs_lock; + + struct usb_device *udev; + struct usb_interface *interface; + struct ath6kl_usb_pipe pipes[ATH6KL_USB_PIPE_MAX]; + u8 *diag_cmd_buffer; + u8 *diag_resp_buffer; + struct ath6kl *ar; +}; + +/* usb urb object */ +struct ath6kl_urb_context { + struct list_head link; + struct ath6kl_usb_pipe *pipe; + struct sk_buff *skb; + struct ath6kl *ar; +}; + +/* USB endpoint definitions */ +#define ATH6KL_USB_EP_ADDR_APP_CTRL_IN 0x81 +#define ATH6KL_USB_EP_ADDR_APP_DATA_IN 0x82 +#define ATH6KL_USB_EP_ADDR_APP_DATA2_IN 0x83 +#define ATH6KL_USB_EP_ADDR_APP_INT_IN 0x84 + +#define ATH6KL_USB_EP_ADDR_APP_CTRL_OUT 0x01 +#define ATH6KL_USB_EP_ADDR_APP_DATA_LP_OUT 0x02 +#define ATH6KL_USB_EP_ADDR_APP_DATA_MP_OUT 0x03 +#define ATH6KL_USB_EP_ADDR_APP_DATA_HP_OUT 0x04 + +/* diagnostic command defnitions */ +#define ATH6KL_USB_CONTROL_REQ_SEND_BMI_CMD 1 +#define ATH6KL_USB_CONTROL_REQ_RECV_BMI_RESP 2 +#define ATH6KL_USB_CONTROL_REQ_DIAG_CMD 3 +#define ATH6KL_USB_CONTROL_REQ_DIAG_RESP 4 + +#define ATH6KL_USB_CTRL_DIAG_CC_READ 0 +#define ATH6KL_USB_CTRL_DIAG_CC_WRITE 1 + +struct ath6kl_usb_ctrl_diag_cmd_write { + __le32 cmd; + __le32 address; + __le32 value; + __le32 _pad[1]; +} __packed; + +struct ath6kl_usb_ctrl_diag_cmd_read { + __le32 cmd; + __le32 address; +} __packed; + +struct ath6kl_usb_ctrl_diag_resp_read { + __le32 value; +} __packed; + +/* function declarations */ +static void ath6kl_usb_recv_complete(struct urb *urb); + +#define ATH6KL_USB_IS_BULK_EP(attr) (((attr) & 3) == 0x02) +#define ATH6KL_USB_IS_INT_EP(attr) (((attr) & 3) == 0x03) +#define ATH6KL_USB_IS_ISOC_EP(attr) (((attr) & 3) == 0x01) +#define ATH6KL_USB_IS_DIR_IN(addr) ((addr) & 0x80) + +/* pipe/urb operations */ +static struct ath6kl_urb_context * +ath6kl_usb_alloc_urb_from_pipe(struct ath6kl_usb_pipe *pipe) +{ + struct ath6kl_urb_context *urb_context = NULL; + unsigned long flags; + + spin_lock_irqsave(&pipe->ar_usb->cs_lock, flags); + if (!list_empty(&pipe->urb_list_head)) { + urb_context = + list_first_entry(&pipe->urb_list_head, + struct ath6kl_urb_context, link); + list_del(&urb_context->link); + pipe->urb_cnt--; + } + spin_unlock_irqrestore(&pipe->ar_usb->cs_lock, flags); + + return urb_context; +} + +static void ath6kl_usb_free_urb_to_pipe(struct ath6kl_usb_pipe *pipe, + struct ath6kl_urb_context *urb_context) +{ + unsigned long flags; + + spin_lock_irqsave(&pipe->ar_usb->cs_lock, flags); + pipe->urb_cnt++; + + list_add(&urb_context->link, &pipe->urb_list_head); + spin_unlock_irqrestore(&pipe->ar_usb->cs_lock, flags); +} + +static void ath6kl_usb_cleanup_recv_urb(struct ath6kl_urb_context *urb_context) +{ + dev_kfree_skb(urb_context->skb); + urb_context->skb = NULL; + + ath6kl_usb_free_urb_to_pipe(urb_context->pipe, urb_context); +} + +static inline struct ath6kl_usb *ath6kl_usb_priv(struct ath6kl *ar) +{ + return ar->hif_priv; +} + +/* pipe resource allocation/cleanup */ +static int ath6kl_usb_alloc_pipe_resources(struct ath6kl_usb_pipe *pipe, + int urb_cnt) +{ + struct ath6kl_urb_context *urb_context; + int status = 0, i; + + INIT_LIST_HEAD(&pipe->urb_list_head); + init_usb_anchor(&pipe->urb_submitted); + + for (i = 0; i < urb_cnt; i++) { + urb_context = kzalloc(sizeof(struct ath6kl_urb_context), + GFP_KERNEL); + if (urb_context == NULL) { + status = -ENOMEM; + goto fail_alloc_pipe_resources; + } + + urb_context->pipe = pipe; + + /* + * we are only allocate the urb contexts here, the actual URB + * is allocated from the kernel as needed to do a transaction + */ + pipe->urb_alloc++; + ath6kl_usb_free_urb_to_pipe(pipe, urb_context); + } + + ath6kl_dbg(ATH6KL_DBG_USB, + "ath6kl usb: alloc resources lpipe:%d hpipe:0x%X urbs:%d\n", + pipe->logical_pipe_num, pipe->usb_pipe_handle, + pipe->urb_alloc); + +fail_alloc_pipe_resources: + return status; +} + +static void ath6kl_usb_free_pipe_resources(struct ath6kl_usb_pipe *pipe) +{ + struct ath6kl_urb_context *urb_context; + + if (pipe->ar_usb == NULL) { + /* nothing allocated for this pipe */ + return; + } + + ath6kl_dbg(ATH6KL_DBG_USB, + "ath6kl usb: free resources lpipe:%d" + "hpipe:0x%X urbs:%d avail:%d\n", + pipe->logical_pipe_num, pipe->usb_pipe_handle, + pipe->urb_alloc, pipe->urb_cnt); + + if (pipe->urb_alloc != pipe->urb_cnt) { + ath6kl_dbg(ATH6KL_DBG_USB, + "ath6kl usb: urb leak! lpipe:%d" + "hpipe:0x%X urbs:%d avail:%d\n", + pipe->logical_pipe_num, pipe->usb_pipe_handle, + pipe->urb_alloc, pipe->urb_cnt); + } + + while (true) { + urb_context = ath6kl_usb_alloc_urb_from_pipe(pipe); + if (urb_context == NULL) + break; + kfree(urb_context); + } + +} + +static void ath6kl_usb_cleanup_pipe_resources(struct ath6kl_usb *ar_usb) +{ + int i; + + for (i = 0; i < ATH6KL_USB_PIPE_MAX; i++) + ath6kl_usb_free_pipe_resources(&ar_usb->pipes[i]); + +} + +static u8 ath6kl_usb_get_logical_pipe_num(struct ath6kl_usb *ar_usb, + u8 ep_address, int *urb_count) +{ + u8 pipe_num = ATH6KL_USB_PIPE_INVALID; + + switch (ep_address) { + case ATH6KL_USB_EP_ADDR_APP_CTRL_IN: + pipe_num = ATH6KL_USB_PIPE_RX_CTRL; + *urb_count = RX_URB_COUNT; + break; + case ATH6KL_USB_EP_ADDR_APP_DATA_IN: + pipe_num = ATH6KL_USB_PIPE_RX_DATA; + *urb_count = RX_URB_COUNT; + break; + case ATH6KL_USB_EP_ADDR_APP_INT_IN: + pipe_num = ATH6KL_USB_PIPE_RX_INT; + *urb_count = RX_URB_COUNT; + break; + case ATH6KL_USB_EP_ADDR_APP_DATA2_IN: + pipe_num = ATH6KL_USB_PIPE_RX_DATA2; + *urb_count = RX_URB_COUNT; + break; + case ATH6KL_USB_EP_ADDR_APP_CTRL_OUT: + pipe_num = ATH6KL_USB_PIPE_TX_CTRL; + *urb_count = TX_URB_COUNT; + break; + case ATH6KL_USB_EP_ADDR_APP_DATA_LP_OUT: + pipe_num = ATH6KL_USB_PIPE_TX_DATA_LP; + *urb_count = TX_URB_COUNT; + break; + case ATH6KL_USB_EP_ADDR_APP_DATA_MP_OUT: + pipe_num = ATH6KL_USB_PIPE_TX_DATA_MP; + *urb_count = TX_URB_COUNT; + break; + case ATH6KL_USB_EP_ADDR_APP_DATA_HP_OUT: + pipe_num = ATH6KL_USB_PIPE_TX_DATA_HP; + *urb_count = TX_URB_COUNT; + break; + default: + /* note: there may be endpoints not currently used */ + break; + } + + return pipe_num; +} + +static int ath6kl_usb_setup_pipe_resources(struct ath6kl_usb *ar_usb) +{ + struct usb_interface *interface = ar_usb->interface; + struct usb_host_interface *iface_desc = interface->cur_altsetting; + struct usb_endpoint_descriptor *endpoint; + struct ath6kl_usb_pipe *pipe; + int i, urbcount, status = 0; + u8 pipe_num; + + ath6kl_dbg(ATH6KL_DBG_USB, "setting up USB Pipes using interface\n"); + + /* walk decriptors and setup pipes */ + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (ATH6KL_USB_IS_BULK_EP(endpoint->bmAttributes)) { + ath6kl_dbg(ATH6KL_DBG_USB, + "%s Bulk Ep:0x%2.2X maxpktsz:%d\n", + ATH6KL_USB_IS_DIR_IN + (endpoint->bEndpointAddress) ? + "RX" : "TX", endpoint->bEndpointAddress, + le16_to_cpu(endpoint->wMaxPacketSize)); + } else if (ATH6KL_USB_IS_INT_EP(endpoint->bmAttributes)) { + ath6kl_dbg(ATH6KL_DBG_USB, + "%s Int Ep:0x%2.2X maxpktsz:%d interval:%d\n", + ATH6KL_USB_IS_DIR_IN + (endpoint->bEndpointAddress) ? + "RX" : "TX", endpoint->bEndpointAddress, + le16_to_cpu(endpoint->wMaxPacketSize), + endpoint->bInterval); + } else if (ATH6KL_USB_IS_ISOC_EP(endpoint->bmAttributes)) { + /* TODO for ISO */ + ath6kl_dbg(ATH6KL_DBG_USB, + "%s ISOC Ep:0x%2.2X maxpktsz:%d interval:%d\n", + ATH6KL_USB_IS_DIR_IN + (endpoint->bEndpointAddress) ? + "RX" : "TX", endpoint->bEndpointAddress, + le16_to_cpu(endpoint->wMaxPacketSize), + endpoint->bInterval); + } + urbcount = 0; + + pipe_num = + ath6kl_usb_get_logical_pipe_num(ar_usb, + endpoint->bEndpointAddress, + &urbcount); + if (pipe_num == ATH6KL_USB_PIPE_INVALID) + continue; + + pipe = &ar_usb->pipes[pipe_num]; + if (pipe->ar_usb != NULL) { + /* hmmm..pipe was already setup */ + continue; + } + + pipe->ar_usb = ar_usb; + pipe->logical_pipe_num = pipe_num; + pipe->ep_address = endpoint->bEndpointAddress; + pipe->max_packet_size = le16_to_cpu(endpoint->wMaxPacketSize); + + if (ATH6KL_USB_IS_BULK_EP(endpoint->bmAttributes)) { + if (ATH6KL_USB_IS_DIR_IN(pipe->ep_address)) { + pipe->usb_pipe_handle = + usb_rcvbulkpipe(ar_usb->udev, + pipe->ep_address); + } else { + pipe->usb_pipe_handle = + usb_sndbulkpipe(ar_usb->udev, + pipe->ep_address); + } + } else if (ATH6KL_USB_IS_INT_EP(endpoint->bmAttributes)) { + if (ATH6KL_USB_IS_DIR_IN(pipe->ep_address)) { + pipe->usb_pipe_handle = + usb_rcvintpipe(ar_usb->udev, + pipe->ep_address); + } else { + pipe->usb_pipe_handle = + usb_sndintpipe(ar_usb->udev, + pipe->ep_address); + } + } else if (ATH6KL_USB_IS_ISOC_EP(endpoint->bmAttributes)) { + /* TODO for ISO */ + if (ATH6KL_USB_IS_DIR_IN(pipe->ep_address)) { + pipe->usb_pipe_handle = + usb_rcvisocpipe(ar_usb->udev, + pipe->ep_address); + } else { + pipe->usb_pipe_handle = + usb_sndisocpipe(ar_usb->udev, + pipe->ep_address); + } + } + + pipe->ep_desc = endpoint; + + if (!ATH6KL_USB_IS_DIR_IN(pipe->ep_address)) + pipe->flags |= ATH6KL_USB_PIPE_FLAG_TX; + + status = ath6kl_usb_alloc_pipe_resources(pipe, urbcount); + if (status != 0) + break; + } + + return status; +} + +/* pipe operations */ +static void ath6kl_usb_post_recv_transfers(struct ath6kl_usb_pipe *recv_pipe, + int buffer_length) +{ + struct ath6kl_urb_context *urb_context; + struct urb *urb; + int usb_status; + + while (true) { + urb_context = ath6kl_usb_alloc_urb_from_pipe(recv_pipe); + if (urb_context == NULL) + break; + + urb_context->skb = dev_alloc_skb(buffer_length); + if (urb_context->skb == NULL) + goto err_cleanup_urb; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (urb == NULL) + goto err_cleanup_urb; + + usb_fill_bulk_urb(urb, + recv_pipe->ar_usb->udev, + recv_pipe->usb_pipe_handle, + urb_context->skb->data, + buffer_length, + ath6kl_usb_recv_complete, urb_context); + + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "ath6kl usb: bulk recv submit:%d, 0x%X (ep:0x%2.2X), %d bytes buf:0x%p\n", + recv_pipe->logical_pipe_num, + recv_pipe->usb_pipe_handle, recv_pipe->ep_address, + buffer_length, urb_context->skb); + + usb_anchor_urb(urb, &recv_pipe->urb_submitted); + usb_status = usb_submit_urb(urb, GFP_ATOMIC); + + if (usb_status) { + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "ath6kl usb : usb bulk recv failed %d\n", + usb_status); + usb_unanchor_urb(urb); + usb_free_urb(urb); + goto err_cleanup_urb; + } + usb_free_urb(urb); + } + return; + +err_cleanup_urb: + ath6kl_usb_cleanup_recv_urb(urb_context); + return; +} + +static void ath6kl_usb_flush_all(struct ath6kl_usb *ar_usb) +{ + int i; + + for (i = 0; i < ATH6KL_USB_PIPE_MAX; i++) { + if (ar_usb->pipes[i].ar_usb != NULL) + usb_kill_anchored_urbs(&ar_usb->pipes[i].urb_submitted); + } + + /* + * Flushing any pending I/O may schedule work this call will block + * until all scheduled work runs to completion. + */ + flush_scheduled_work(); +} + +static void ath6kl_usb_start_recv_pipes(struct ath6kl_usb *ar_usb) +{ + /* + * note: control pipe is no longer used + * ar_usb->pipes[ATH6KL_USB_PIPE_RX_CTRL].urb_cnt_thresh = + * ar_usb->pipes[ATH6KL_USB_PIPE_RX_CTRL].urb_alloc/2; + * ath6kl_usb_post_recv_transfers(&ar_usb-> + * pipes[ATH6KL_USB_PIPE_RX_CTRL], + * ATH6KL_USB_RX_BUFFER_SIZE); + */ + + ar_usb->pipes[ATH6KL_USB_PIPE_RX_DATA].urb_cnt_thresh = + ar_usb->pipes[ATH6KL_USB_PIPE_RX_DATA].urb_alloc / 2; + ath6kl_usb_post_recv_transfers(&ar_usb->pipes[ATH6KL_USB_PIPE_RX_DATA], + ATH6KL_USB_RX_BUFFER_SIZE); +} + +/* hif usb rx/tx completion functions */ +static void ath6kl_usb_recv_complete(struct urb *urb) +{ + struct ath6kl_urb_context *urb_context = urb->context; + struct ath6kl_usb_pipe *pipe = urb_context->pipe; + struct sk_buff *skb = NULL; + int status = 0; + + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "%s: recv pipe: %d, stat:%d, len:%d urb:0x%p\n", __func__, + pipe->logical_pipe_num, urb->status, urb->actual_length, + urb); + + if (urb->status != 0) { + status = -EIO; + switch (urb->status) { + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* + * no need to spew these errors when device + * removed or urb killed due to driver shutdown + */ + status = -ECANCELED; + break; + default: + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "%s recv pipe: %d (ep:0x%2.2X), failed:%d\n", + __func__, pipe->logical_pipe_num, + pipe->ep_address, urb->status); + break; + } + goto cleanup_recv_urb; + } + + if (urb->actual_length == 0) + goto cleanup_recv_urb; + + skb = urb_context->skb; + + /* we are going to pass it up */ + urb_context->skb = NULL; + skb_put(skb, urb->actual_length); + + /* note: queue implements a lock */ + skb_queue_tail(&pipe->io_comp_queue, skb); + schedule_work(&pipe->io_complete_work); + +cleanup_recv_urb: + ath6kl_usb_cleanup_recv_urb(urb_context); + + if (status == 0 && + pipe->urb_cnt >= pipe->urb_cnt_thresh) { + /* our free urbs are piling up, post more transfers */ + ath6kl_usb_post_recv_transfers(pipe, ATH6KL_USB_RX_BUFFER_SIZE); + } +} + +static void ath6kl_usb_usb_transmit_complete(struct urb *urb) +{ + struct ath6kl_urb_context *urb_context = urb->context; + struct ath6kl_usb_pipe *pipe = urb_context->pipe; + struct sk_buff *skb; + + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "%s: pipe: %d, stat:%d, len:%d\n", + __func__, pipe->logical_pipe_num, urb->status, + urb->actual_length); + + if (urb->status != 0) { + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "%s: pipe: %d, failed:%d\n", + __func__, pipe->logical_pipe_num, urb->status); + } + + skb = urb_context->skb; + urb_context->skb = NULL; + ath6kl_usb_free_urb_to_pipe(urb_context->pipe, urb_context); + + /* note: queue implements a lock */ + skb_queue_tail(&pipe->io_comp_queue, skb); + schedule_work(&pipe->io_complete_work); +} + +static void ath6kl_usb_io_comp_work(struct work_struct *work) +{ + struct ath6kl_usb_pipe *pipe = container_of(work, + struct ath6kl_usb_pipe, + io_complete_work); + struct ath6kl_usb *ar_usb; + struct sk_buff *skb; + + ar_usb = pipe->ar_usb; + + while ((skb = skb_dequeue(&pipe->io_comp_queue))) { + if (pipe->flags & ATH6KL_USB_PIPE_FLAG_TX) { + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "ath6kl usb xmit callback buf:0x%p\n", skb); + ath6kl_core_tx_complete(ar_usb->ar, skb); + } else { + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "ath6kl usb recv callback buf:0x%p\n", skb); + ath6kl_core_rx_complete(ar_usb->ar, skb, + pipe->logical_pipe_num); + } + } +} + +#define ATH6KL_USB_MAX_DIAG_CMD (sizeof(struct ath6kl_usb_ctrl_diag_cmd_write)) +#define ATH6KL_USB_MAX_DIAG_RESP (sizeof(struct ath6kl_usb_ctrl_diag_resp_read)) + +static void ath6kl_usb_destroy(struct ath6kl_usb *ar_usb) +{ + ath6kl_usb_flush_all(ar_usb); + + ath6kl_usb_cleanup_pipe_resources(ar_usb); + + usb_set_intfdata(ar_usb->interface, NULL); + + kfree(ar_usb->diag_cmd_buffer); + kfree(ar_usb->diag_resp_buffer); + + kfree(ar_usb); +} + +static struct ath6kl_usb *ath6kl_usb_create(struct usb_interface *interface) +{ + struct usb_device *dev = interface_to_usbdev(interface); + struct ath6kl_usb *ar_usb; + struct ath6kl_usb_pipe *pipe; + int status = 0; + int i; + + ar_usb = kzalloc(sizeof(struct ath6kl_usb), GFP_KERNEL); + if (ar_usb == NULL) + goto fail_ath6kl_usb_create; + + usb_set_intfdata(interface, ar_usb); + spin_lock_init(&(ar_usb->cs_lock)); + ar_usb->udev = dev; + ar_usb->interface = interface; + + for (i = 0; i < ATH6KL_USB_PIPE_MAX; i++) { + pipe = &ar_usb->pipes[i]; + INIT_WORK(&pipe->io_complete_work, + ath6kl_usb_io_comp_work); + skb_queue_head_init(&pipe->io_comp_queue); + } + + ar_usb->diag_cmd_buffer = kzalloc(ATH6KL_USB_MAX_DIAG_CMD, GFP_KERNEL); + if (ar_usb->diag_cmd_buffer == NULL) { + status = -ENOMEM; + goto fail_ath6kl_usb_create; + } + + ar_usb->diag_resp_buffer = kzalloc(ATH6KL_USB_MAX_DIAG_RESP, + GFP_KERNEL); + if (ar_usb->diag_resp_buffer == NULL) { + status = -ENOMEM; + goto fail_ath6kl_usb_create; + } + + status = ath6kl_usb_setup_pipe_resources(ar_usb); + +fail_ath6kl_usb_create: + if (status != 0) { + ath6kl_usb_destroy(ar_usb); + ar_usb = NULL; + } + return ar_usb; +} + +static void ath6kl_usb_device_detached(struct usb_interface *interface) +{ + struct ath6kl_usb *ar_usb; + + ar_usb = usb_get_intfdata(interface); + if (ar_usb == NULL) + return; + + ath6kl_stop_txrx(ar_usb->ar); + + /* Delay to wait for the target to reboot */ + mdelay(20); + ath6kl_core_cleanup(ar_usb->ar); + ath6kl_usb_destroy(ar_usb); +} + +/* exported hif usb APIs for htc pipe */ +static void hif_start(struct ath6kl *ar) +{ + struct ath6kl_usb *device = ath6kl_usb_priv(ar); + int i; + + ath6kl_usb_start_recv_pipes(device); + + /* set the TX resource avail threshold for each TX pipe */ + for (i = ATH6KL_USB_PIPE_TX_CTRL; + i <= ATH6KL_USB_PIPE_TX_DATA_HP; i++) { + device->pipes[i].urb_cnt_thresh = + device->pipes[i].urb_alloc / 2; + } +} + +static int ath6kl_usb_send(struct ath6kl *ar, u8 PipeID, + struct sk_buff *hdr_skb, struct sk_buff *skb) +{ + struct ath6kl_usb *device = ath6kl_usb_priv(ar); + struct ath6kl_usb_pipe *pipe = &device->pipes[PipeID]; + struct ath6kl_urb_context *urb_context; + int usb_status, status = 0; + struct urb *urb; + u8 *data; + u32 len; + + ath6kl_dbg(ATH6KL_DBG_USB_BULK, "+%s pipe : %d, buf:0x%p\n", + __func__, PipeID, skb); + + urb_context = ath6kl_usb_alloc_urb_from_pipe(pipe); + + if (urb_context == NULL) { + /* + * TODO: it is possible to run out of urbs if + * 2 endpoints map to the same pipe ID + */ + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "%s pipe:%d no urbs left. URB Cnt : %d\n", + __func__, PipeID, pipe->urb_cnt); + status = -ENOMEM; + goto fail_hif_send; + } + + urb_context->skb = skb; + + data = skb->data; + len = skb->len; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (urb == NULL) { + status = -ENOMEM; + ath6kl_usb_free_urb_to_pipe(urb_context->pipe, + urb_context); + goto fail_hif_send; + } + + usb_fill_bulk_urb(urb, + device->udev, + pipe->usb_pipe_handle, + data, + len, + ath6kl_usb_usb_transmit_complete, urb_context); + + if ((len % pipe->max_packet_size) == 0) { + /* hit a max packet boundary on this pipe */ + urb->transfer_flags |= URB_ZERO_PACKET; + } + + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "athusb bulk send submit:%d, 0x%X (ep:0x%2.2X), %d bytes\n", + pipe->logical_pipe_num, pipe->usb_pipe_handle, + pipe->ep_address, len); + + usb_anchor_urb(urb, &pipe->urb_submitted); + usb_status = usb_submit_urb(urb, GFP_ATOMIC); + + if (usb_status) { + ath6kl_dbg(ATH6KL_DBG_USB_BULK, + "ath6kl usb : usb bulk transmit failed %d\n", + usb_status); + usb_unanchor_urb(urb); + ath6kl_usb_free_urb_to_pipe(urb_context->pipe, + urb_context); + status = -EINVAL; + } + usb_free_urb(urb); + +fail_hif_send: + return status; +} + +static void hif_stop(struct ath6kl *ar) +{ + struct ath6kl_usb *device = ath6kl_usb_priv(ar); + + ath6kl_usb_flush_all(device); +} + +static void ath6kl_usb_get_default_pipe(struct ath6kl *ar, + u8 *ul_pipe, u8 *dl_pipe) +{ + *ul_pipe = ATH6KL_USB_PIPE_TX_CTRL; + *dl_pipe = ATH6KL_USB_PIPE_RX_CTRL; +} + +static int ath6kl_usb_map_service_pipe(struct ath6kl *ar, u16 svc_id, + u8 *ul_pipe, u8 *dl_pipe) +{ + int status = 0; + + switch (svc_id) { + case HTC_CTRL_RSVD_SVC: + case WMI_CONTROL_SVC: + *ul_pipe = ATH6KL_USB_PIPE_TX_CTRL; + /* due to large control packets, shift to data pipe */ + *dl_pipe = ATH6KL_USB_PIPE_RX_DATA; + break; + case WMI_DATA_BE_SVC: + case WMI_DATA_BK_SVC: + *ul_pipe = ATH6KL_USB_PIPE_TX_DATA_LP; + /* + * Disable rxdata2 directly, it will be enabled + * if FW enable rxdata2 + */ + *dl_pipe = ATH6KL_USB_PIPE_RX_DATA; + break; + case WMI_DATA_VI_SVC: + + if (ar->hw.flags & ATH6KL_HW_MAP_LP_ENDPOINT) + *ul_pipe = ATH6KL_USB_PIPE_TX_DATA_LP; + else + *ul_pipe = ATH6KL_USB_PIPE_TX_DATA_MP; + /* + * Disable rxdata2 directly, it will be enabled + * if FW enable rxdata2 + */ + *dl_pipe = ATH6KL_USB_PIPE_RX_DATA; + break; + case WMI_DATA_VO_SVC: + + if (ar->hw.flags & ATH6KL_HW_MAP_LP_ENDPOINT) + *ul_pipe = ATH6KL_USB_PIPE_TX_DATA_LP; + else + *ul_pipe = ATH6KL_USB_PIPE_TX_DATA_MP; + /* + * Disable rxdata2 directly, it will be enabled + * if FW enable rxdata2 + */ + *dl_pipe = ATH6KL_USB_PIPE_RX_DATA; + break; + default: + status = -EPERM; + break; + } + + return status; +} + +static u16 ath6kl_usb_get_free_queue_number(struct ath6kl *ar, u8 pipe_id) +{ + struct ath6kl_usb *device = ath6kl_usb_priv(ar); + + return device->pipes[pipe_id].urb_cnt; +} + +static void hif_detach_htc(struct ath6kl *ar) +{ + struct ath6kl_usb *device = ath6kl_usb_priv(ar); + + ath6kl_usb_flush_all(device); +} + +static int ath6kl_usb_submit_ctrl_out(struct ath6kl_usb *ar_usb, + u8 req, u16 value, u16 index, void *data, + u32 size) +{ + u8 *buf = NULL; + int ret; + + if (size > 0) { + buf = kmemdup(data, size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + } + + /* note: if successful returns number of bytes transfered */ + ret = usb_control_msg(ar_usb->udev, + usb_sndctrlpipe(ar_usb->udev, 0), + req, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, value, index, buf, + size, 1000); + + if (ret < 0) { + ath6kl_warn("Failed to submit usb control message: %d\n", ret); + kfree(buf); + return ret; + } + + kfree(buf); + + return 0; +} + +static int ath6kl_usb_submit_ctrl_in(struct ath6kl_usb *ar_usb, + u8 req, u16 value, u16 index, void *data, + u32 size) +{ + u8 *buf = NULL; + int ret; + + if (size > 0) { + buf = kmalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + } + + /* note: if successful returns number of bytes transfered */ + ret = usb_control_msg(ar_usb->udev, + usb_rcvctrlpipe(ar_usb->udev, 0), + req, + USB_DIR_IN | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, value, index, buf, + size, 2 * HZ); + + if (ret < 0) { + ath6kl_warn("Failed to read usb control message: %d\n", ret); + kfree(buf); + return ret; + } + + memcpy((u8 *) data, buf, size); + + kfree(buf); + + return 0; +} + +static int ath6kl_usb_ctrl_msg_exchange(struct ath6kl_usb *ar_usb, + u8 req_val, u8 *req_buf, u32 req_len, + u8 resp_val, u8 *resp_buf, u32 *resp_len) +{ + int ret; + + /* send command */ + ret = ath6kl_usb_submit_ctrl_out(ar_usb, req_val, 0, 0, + req_buf, req_len); + + if (ret != 0) + return ret; + + if (resp_buf == NULL) { + /* no expected response */ + return ret; + } + + /* get response */ + ret = ath6kl_usb_submit_ctrl_in(ar_usb, resp_val, 0, 0, + resp_buf, *resp_len); + + return ret; +} + +static int ath6kl_usb_diag_read32(struct ath6kl *ar, u32 address, u32 *data) +{ + struct ath6kl_usb *ar_usb = ar->hif_priv; + struct ath6kl_usb_ctrl_diag_resp_read *resp; + struct ath6kl_usb_ctrl_diag_cmd_read *cmd; + u32 resp_len; + int ret; + + cmd = (struct ath6kl_usb_ctrl_diag_cmd_read *) ar_usb->diag_cmd_buffer; + + memset(cmd, 0, sizeof(*cmd)); + cmd->cmd = ATH6KL_USB_CTRL_DIAG_CC_READ; + cmd->address = cpu_to_le32(address); + resp_len = sizeof(*resp); + + ret = ath6kl_usb_ctrl_msg_exchange(ar_usb, + ATH6KL_USB_CONTROL_REQ_DIAG_CMD, + (u8 *) cmd, + sizeof(struct ath6kl_usb_ctrl_diag_cmd_write), + ATH6KL_USB_CONTROL_REQ_DIAG_RESP, + ar_usb->diag_resp_buffer, &resp_len); + + if (ret) { + ath6kl_warn("diag read32 failed: %d\n", ret); + return ret; + } + + resp = (struct ath6kl_usb_ctrl_diag_resp_read *) + ar_usb->diag_resp_buffer; + + *data = le32_to_cpu(resp->value); + + return ret; +} + +static int ath6kl_usb_diag_write32(struct ath6kl *ar, u32 address, __le32 data) +{ + struct ath6kl_usb *ar_usb = ar->hif_priv; + struct ath6kl_usb_ctrl_diag_cmd_write *cmd; + int ret; + + cmd = (struct ath6kl_usb_ctrl_diag_cmd_write *) ar_usb->diag_cmd_buffer; + + memset(cmd, 0, sizeof(struct ath6kl_usb_ctrl_diag_cmd_write)); + cmd->cmd = cpu_to_le32(ATH6KL_USB_CTRL_DIAG_CC_WRITE); + cmd->address = cpu_to_le32(address); + cmd->value = data; + + ret = ath6kl_usb_ctrl_msg_exchange(ar_usb, + ATH6KL_USB_CONTROL_REQ_DIAG_CMD, + (u8 *) cmd, + sizeof(*cmd), + 0, NULL, NULL); + if (ret) { + ath6kl_warn("diag_write32 failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int ath6kl_usb_bmi_read(struct ath6kl *ar, u8 *buf, u32 len) +{ + struct ath6kl_usb *ar_usb = ar->hif_priv; + int ret; + + /* get response */ + ret = ath6kl_usb_submit_ctrl_in(ar_usb, + ATH6KL_USB_CONTROL_REQ_RECV_BMI_RESP, + 0, 0, buf, len); + if (ret) { + ath6kl_err("Unable to read the bmi data from the device: %d\n", + ret); + return ret; + } + + return 0; +} + +static int ath6kl_usb_bmi_write(struct ath6kl *ar, u8 *buf, u32 len) +{ + struct ath6kl_usb *ar_usb = ar->hif_priv; + int ret; + + /* send command */ + ret = ath6kl_usb_submit_ctrl_out(ar_usb, + ATH6KL_USB_CONTROL_REQ_SEND_BMI_CMD, + 0, 0, buf, len); + if (ret) { + ath6kl_err("unable to send the bmi data to the device: %d\n", + ret); + return ret; + } + + return 0; +} + +static int ath6kl_usb_power_on(struct ath6kl *ar) +{ + hif_start(ar); + return 0; +} + +static int ath6kl_usb_power_off(struct ath6kl *ar) +{ + hif_detach_htc(ar); + return 0; +} + +static void ath6kl_usb_stop(struct ath6kl *ar) +{ + hif_stop(ar); +} + +static void ath6kl_usb_cleanup_scatter(struct ath6kl *ar) +{ + /* + * USB doesn't support it. Just return. + */ + return; +} + +static const struct ath6kl_hif_ops ath6kl_usb_ops = { + .diag_read32 = ath6kl_usb_diag_read32, + .diag_write32 = ath6kl_usb_diag_write32, + .bmi_read = ath6kl_usb_bmi_read, + .bmi_write = ath6kl_usb_bmi_write, + .power_on = ath6kl_usb_power_on, + .power_off = ath6kl_usb_power_off, + .stop = ath6kl_usb_stop, + .pipe_send = ath6kl_usb_send, + .pipe_get_default = ath6kl_usb_get_default_pipe, + .pipe_map_service = ath6kl_usb_map_service_pipe, + .pipe_get_free_queue_number = ath6kl_usb_get_free_queue_number, + .cleanup_scatter = ath6kl_usb_cleanup_scatter, +}; + +/* ath6kl usb driver registered functions */ +static int ath6kl_usb_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(interface); + struct ath6kl *ar; + struct ath6kl_usb *ar_usb = NULL; + int vendor_id, product_id; + int ret = 0; + + usb_get_dev(dev); + + vendor_id = le16_to_cpu(dev->descriptor.idVendor); + product_id = le16_to_cpu(dev->descriptor.idProduct); + + ath6kl_dbg(ATH6KL_DBG_USB, "vendor_id = %04x\n", vendor_id); + ath6kl_dbg(ATH6KL_DBG_USB, "product_id = %04x\n", product_id); + + if (interface->cur_altsetting) + ath6kl_dbg(ATH6KL_DBG_USB, "USB Interface %d\n", + interface->cur_altsetting->desc.bInterfaceNumber); + + + if (dev->speed == USB_SPEED_HIGH) + ath6kl_dbg(ATH6KL_DBG_USB, "USB 2.0 Host\n"); + else + ath6kl_dbg(ATH6KL_DBG_USB, "USB 1.1 Host\n"); + + ar_usb = ath6kl_usb_create(interface); + + if (ar_usb == NULL) { + ret = -ENOMEM; + goto err_usb_put; + } + + ar = ath6kl_core_create(&ar_usb->udev->dev); + if (ar == NULL) { + ath6kl_err("Failed to alloc ath6kl core\n"); + ret = -ENOMEM; + goto err_usb_destroy; + } + + ar->hif_priv = ar_usb; + ar->hif_type = ATH6KL_HIF_TYPE_USB; + ar->hif_ops = &ath6kl_usb_ops; + ar->mbox_info.block_size = 16; + ar->bmi.max_data_size = 252; + + ar_usb->ar = ar; + + ret = ath6kl_core_init(ar, ATH6KL_HTC_TYPE_PIPE); + if (ret) { + ath6kl_err("Failed to init ath6kl core: %d\n", ret); + goto err_core_free; + } + + return ret; + +err_core_free: + ath6kl_core_destroy(ar); +err_usb_destroy: + ath6kl_usb_destroy(ar_usb); +err_usb_put: + usb_put_dev(dev); + + return ret; +} + +static void ath6kl_usb_remove(struct usb_interface *interface) +{ + usb_put_dev(interface_to_usbdev(interface)); + ath6kl_usb_device_detached(interface); +} + +#ifdef CONFIG_PM + +static int ath6kl_usb_suspend(struct usb_interface *interface, + pm_message_t message) +{ + struct ath6kl_usb *device; + device = usb_get_intfdata(interface); + + ath6kl_usb_flush_all(device); + return 0; +} + +static int ath6kl_usb_resume(struct usb_interface *interface) +{ + struct ath6kl_usb *device; + device = usb_get_intfdata(interface); + + ath6kl_usb_post_recv_transfers(&device->pipes[ATH6KL_USB_PIPE_RX_DATA], + ATH6KL_USB_RX_BUFFER_SIZE); + ath6kl_usb_post_recv_transfers(&device->pipes[ATH6KL_USB_PIPE_RX_DATA2], + ATH6KL_USB_RX_BUFFER_SIZE); + + return 0; +} + +static int ath6kl_usb_reset_resume(struct usb_interface *intf) +{ + if (usb_get_intfdata(intf)) + ath6kl_usb_remove(intf); + return 0; +} + +#else + +#define ath6kl_usb_suspend NULL +#define ath6kl_usb_resume NULL +#define ath6kl_usb_reset_resume NULL + +#endif + +/* table of devices that work with this driver */ +static struct usb_device_id ath6kl_usb_ids[] = { + {USB_DEVICE(0x0cf3, 0x9374)}, + { /* Terminating entry */ }, +}; + +MODULE_DEVICE_TABLE(usb, ath6kl_usb_ids); + +static struct usb_driver ath6kl_usb_driver = { + .name = "ath6kl_usb", + .probe = ath6kl_usb_probe, + .suspend = ath6kl_usb_suspend, + .resume = ath6kl_usb_resume, + .reset_resume = ath6kl_usb_reset_resume, + .disconnect = ath6kl_usb_remove, + .id_table = ath6kl_usb_ids, + .supports_autosuspend = true, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +static int ath6kl_usb_init(void) +{ + int ret; + + ret = usb_register(&ath6kl_usb_driver); + if (ret) { + ath6kl_err("usb registration failed: %d\n", ret); + return ret; + } + + return 0; +} + +static void ath6kl_usb_exit(void) +{ + usb_deregister(&ath6kl_usb_driver); +} + +module_init(ath6kl_usb_init); +module_exit(ath6kl_usb_exit); + +MODULE_AUTHOR("Atheros Communications, Inc."); +MODULE_DESCRIPTION("Driver support for Atheros AR600x USB devices"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_FIRMWARE(AR6004_HW_1_0_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6004_HW_1_0_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_0_DEFAULT_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_1_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6004_HW_1_1_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_1_DEFAULT_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_2_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6004_HW_1_2_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_2_DEFAULT_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_3_FW_DIR "/" AR6004_HW_1_3_FIRMWARE_FILE); +MODULE_FIRMWARE(AR6004_HW_1_3_BOARD_DATA_FILE); +MODULE_FIRMWARE(AR6004_HW_1_3_DEFAULT_BOARD_DATA_FILE); diff --git a/drivers/net/wireless/ath/ath6kl/wmi.c b/drivers/net/wireless/ath/ath6kl/wmi.c new file mode 100644 index 0000000..87aefb4 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/wmi.c @@ -0,0 +1,4134 @@ +/* + * Copyright (c) 2004-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include "core.h" +#include "debug.h" +#include "testmode.h" +#include "trace.h" +#include "../regd.h" +#include "../regd_common.h" + +static int ath6kl_wmi_sync_point(struct wmi *wmi, u8 if_idx); + +static const s32 wmi_rate_tbl[][2] = { + /* {W/O SGI, with SGI} */ + {1000, 1000}, + {2000, 2000}, + {5500, 5500}, + {11000, 11000}, + {6000, 6000}, + {9000, 9000}, + {12000, 12000}, + {18000, 18000}, + {24000, 24000}, + {36000, 36000}, + {48000, 48000}, + {54000, 54000}, + {6500, 7200}, + {13000, 14400}, + {19500, 21700}, + {26000, 28900}, + {39000, 43300}, + {52000, 57800}, + {58500, 65000}, + {65000, 72200}, + {13500, 15000}, + {27000, 30000}, + {40500, 45000}, + {54000, 60000}, + {81000, 90000}, + {108000, 120000}, + {121500, 135000}, + {135000, 150000}, + {0, 0} +}; + +/* 802.1d to AC mapping. Refer pg 57 of WMM-test-plan-v1.2 */ +static const u8 up_to_ac[] = { + WMM_AC_BE, + WMM_AC_BK, + WMM_AC_BK, + WMM_AC_BE, + WMM_AC_VI, + WMM_AC_VI, + WMM_AC_VO, + WMM_AC_VO, +}; + +void ath6kl_wmi_set_control_ep(struct wmi *wmi, enum htc_endpoint_id ep_id) +{ + if (WARN_ON(ep_id == ENDPOINT_UNUSED || ep_id >= ENDPOINT_MAX)) + return; + + wmi->ep_id = ep_id; +} + +enum htc_endpoint_id ath6kl_wmi_get_control_ep(struct wmi *wmi) +{ + return wmi->ep_id; +} + +struct ath6kl_vif *ath6kl_get_vif_by_index(struct ath6kl *ar, u8 if_idx) +{ + struct ath6kl_vif *vif, *found = NULL; + + if (WARN_ON(if_idx > (ar->vif_max - 1))) + return NULL; + + /* FIXME: Locking */ + spin_lock_bh(&ar->list_lock); + list_for_each_entry(vif, &ar->vif_list, list) { + if (vif->fw_vif_idx == if_idx) { + found = vif; + break; + } + } + spin_unlock_bh(&ar->list_lock); + + return found; +} + +/* Performs DIX to 802.3 encapsulation for transmit packets. + * Assumes the entire DIX header is contigous and that there is + * enough room in the buffer for a 802.3 mac header and LLC+SNAP headers. + */ +int ath6kl_wmi_dix_2_dot3(struct wmi *wmi, struct sk_buff *skb) +{ + struct ath6kl_llc_snap_hdr *llc_hdr; + struct ethhdr *eth_hdr; + size_t new_len; + __be16 type; + u8 *datap; + u16 size; + + if (WARN_ON(skb == NULL)) + return -EINVAL; + + size = sizeof(struct ath6kl_llc_snap_hdr) + sizeof(struct wmi_data_hdr); + if (skb_headroom(skb) < size) + return -ENOMEM; + + eth_hdr = (struct ethhdr *) skb->data; + type = eth_hdr->h_proto; + + if (!is_ethertype(be16_to_cpu(type))) { + ath6kl_dbg(ATH6KL_DBG_WMI, + "%s: pkt is already in 802.3 format\n", __func__); + return 0; + } + + new_len = skb->len - sizeof(*eth_hdr) + sizeof(*llc_hdr); + + skb_push(skb, sizeof(struct ath6kl_llc_snap_hdr)); + datap = skb->data; + + eth_hdr->h_proto = cpu_to_be16(new_len); + + memcpy(datap, eth_hdr, sizeof(*eth_hdr)); + + llc_hdr = (struct ath6kl_llc_snap_hdr *)(datap + sizeof(*eth_hdr)); + llc_hdr->dsap = 0xAA; + llc_hdr->ssap = 0xAA; + llc_hdr->cntl = 0x03; + llc_hdr->org_code[0] = 0x0; + llc_hdr->org_code[1] = 0x0; + llc_hdr->org_code[2] = 0x0; + llc_hdr->eth_type = type; + + return 0; +} + +static int ath6kl_wmi_meta_add(struct wmi *wmi, struct sk_buff *skb, + u8 *version, void *tx_meta_info) +{ + struct wmi_tx_meta_v1 *v1; + struct wmi_tx_meta_v2 *v2; + + if (WARN_ON(skb == NULL || version == NULL)) + return -EINVAL; + + switch (*version) { + case WMI_META_VERSION_1: + skb_push(skb, WMI_MAX_TX_META_SZ); + v1 = (struct wmi_tx_meta_v1 *) skb->data; + v1->pkt_id = 0; + v1->rate_plcy_id = 0; + *version = WMI_META_VERSION_1; + break; + case WMI_META_VERSION_2: + skb_push(skb, WMI_MAX_TX_META_SZ); + v2 = (struct wmi_tx_meta_v2 *) skb->data; + memcpy(v2, (struct wmi_tx_meta_v2 *) tx_meta_info, + sizeof(struct wmi_tx_meta_v2)); + break; + } + + return 0; +} + +int ath6kl_wmi_data_hdr_add(struct wmi *wmi, struct sk_buff *skb, + u8 msg_type, u32 flags, + enum wmi_data_hdr_data_type data_type, + u8 meta_ver, void *tx_meta_info, u8 if_idx) +{ + struct wmi_data_hdr *data_hdr; + int ret; + + if (WARN_ON(skb == NULL || (if_idx > wmi->parent_dev->vif_max - 1))) + return -EINVAL; + + if (tx_meta_info) { + ret = ath6kl_wmi_meta_add(wmi, skb, &meta_ver, tx_meta_info); + if (ret) + return ret; + } + + skb_push(skb, sizeof(struct wmi_data_hdr)); + + data_hdr = (struct wmi_data_hdr *)skb->data; + memset(data_hdr, 0, sizeof(struct wmi_data_hdr)); + + data_hdr->info = msg_type << WMI_DATA_HDR_MSG_TYPE_SHIFT; + data_hdr->info |= data_type << WMI_DATA_HDR_DATA_TYPE_SHIFT; + + if (flags & WMI_DATA_HDR_FLAGS_MORE) + data_hdr->info |= WMI_DATA_HDR_MORE; + + if (flags & WMI_DATA_HDR_FLAGS_EOSP) + data_hdr->info3 |= cpu_to_le16(WMI_DATA_HDR_EOSP); + + data_hdr->info2 |= cpu_to_le16(meta_ver << WMI_DATA_HDR_META_SHIFT); + data_hdr->info3 |= cpu_to_le16(if_idx & WMI_DATA_HDR_IF_IDX_MASK); + + return 0; +} + +u8 ath6kl_wmi_determine_user_priority(u8 *pkt, u32 layer2_pri) +{ + struct iphdr *ip_hdr = (struct iphdr *) pkt; + u8 ip_pri; + + /* + * Determine IPTOS priority + * + * IP-TOS - 8bits + * : DSCP(6-bits) ECN(2-bits) + * : DSCP - P2 P1 P0 X X X + * where (P2 P1 P0) form 802.1D + */ + ip_pri = ip_hdr->tos >> 5; + ip_pri &= 0x7; + + if ((layer2_pri & 0x7) > ip_pri) + return (u8) layer2_pri & 0x7; + else + return ip_pri; +} + +u8 ath6kl_wmi_get_traffic_class(u8 user_priority) +{ + return up_to_ac[user_priority & 0x7]; +} + +int ath6kl_wmi_implicit_create_pstream(struct wmi *wmi, u8 if_idx, + struct sk_buff *skb, + u32 layer2_priority, bool wmm_enabled, + u8 *ac) +{ + struct wmi_data_hdr *data_hdr; + struct ath6kl_llc_snap_hdr *llc_hdr; + struct wmi_create_pstream_cmd cmd; + u32 meta_size, hdr_size; + u16 ip_type = IP_ETHERTYPE; + u8 stream_exist, usr_pri; + u8 traffic_class = WMM_AC_BE; + u8 *datap; + + if (WARN_ON(skb == NULL)) + return -EINVAL; + + datap = skb->data; + data_hdr = (struct wmi_data_hdr *) datap; + + meta_size = ((le16_to_cpu(data_hdr->info2) >> WMI_DATA_HDR_META_SHIFT) & + WMI_DATA_HDR_META_MASK) ? WMI_MAX_TX_META_SZ : 0; + + if (!wmm_enabled) { + /* If WMM is disabled all traffic goes as BE traffic */ + usr_pri = 0; + } else { + hdr_size = sizeof(struct ethhdr); + + llc_hdr = (struct ath6kl_llc_snap_hdr *)(datap + + sizeof(struct + wmi_data_hdr) + + meta_size + hdr_size); + + if (llc_hdr->eth_type == htons(ip_type)) { + /* + * Extract the endpoint info from the TOS field + * in the IP header. + */ + usr_pri = + ath6kl_wmi_determine_user_priority(((u8 *) llc_hdr) + + sizeof(struct ath6kl_llc_snap_hdr), + layer2_priority); + } else + usr_pri = layer2_priority & 0x7; + + /* + * Queue the EAPOL frames in the same WMM_AC_VO queue + * as that of management frames. + */ + if (skb->protocol == cpu_to_be16(ETH_P_PAE)) + usr_pri = WMI_VOICE_USER_PRIORITY; + } + + /* + * workaround for WMM S5 + * + * FIXME: wmi->traffic_class is always 100 so this test doesn't + * make sense + */ + if ((wmi->traffic_class == WMM_AC_VI) && + ((usr_pri == 5) || (usr_pri == 4))) + usr_pri = 1; + + /* Convert user priority to traffic class */ + traffic_class = up_to_ac[usr_pri & 0x7]; + + wmi_data_hdr_set_up(data_hdr, usr_pri); + + spin_lock_bh(&wmi->lock); + stream_exist = wmi->fat_pipe_exist; + spin_unlock_bh(&wmi->lock); + + if (!(stream_exist & (1 << traffic_class))) { + memset(&cmd, 0, sizeof(cmd)); + cmd.traffic_class = traffic_class; + cmd.user_pri = usr_pri; + cmd.inactivity_int = + cpu_to_le32(WMI_IMPLICIT_PSTREAM_INACTIVITY_INT); + /* Implicit streams are created with TSID 0xFF */ + cmd.tsid = WMI_IMPLICIT_PSTREAM; + ath6kl_wmi_create_pstream_cmd(wmi, if_idx, &cmd); + } + + *ac = traffic_class; + + return 0; +} + +int ath6kl_wmi_dot11_hdr_remove(struct wmi *wmi, struct sk_buff *skb) +{ + struct ieee80211_hdr_3addr *pwh, wh; + struct ath6kl_llc_snap_hdr *llc_hdr; + struct ethhdr eth_hdr; + u32 hdr_size; + u8 *datap; + __le16 sub_type; + + if (WARN_ON(skb == NULL)) + return -EINVAL; + + datap = skb->data; + pwh = (struct ieee80211_hdr_3addr *) datap; + + sub_type = pwh->frame_control & cpu_to_le16(IEEE80211_FCTL_STYPE); + + memcpy((u8 *) &wh, datap, sizeof(struct ieee80211_hdr_3addr)); + + /* Strip off the 802.11 header */ + if (sub_type == cpu_to_le16(IEEE80211_STYPE_QOS_DATA)) { + hdr_size = roundup(sizeof(struct ieee80211_qos_hdr), + sizeof(u32)); + skb_pull(skb, hdr_size); + } else if (sub_type == cpu_to_le16(IEEE80211_STYPE_DATA)) + skb_pull(skb, sizeof(struct ieee80211_hdr_3addr)); + + datap = skb->data; + llc_hdr = (struct ath6kl_llc_snap_hdr *)(datap); + + memset(ð_hdr, 0, sizeof(eth_hdr)); + eth_hdr.h_proto = llc_hdr->eth_type; + + switch ((le16_to_cpu(wh.frame_control)) & + (IEEE80211_FCTL_FROMDS | IEEE80211_FCTL_TODS)) { + case 0: + memcpy(eth_hdr.h_dest, wh.addr1, ETH_ALEN); + memcpy(eth_hdr.h_source, wh.addr2, ETH_ALEN); + break; + case IEEE80211_FCTL_TODS: + memcpy(eth_hdr.h_dest, wh.addr3, ETH_ALEN); + memcpy(eth_hdr.h_source, wh.addr2, ETH_ALEN); + break; + case IEEE80211_FCTL_FROMDS: + memcpy(eth_hdr.h_dest, wh.addr1, ETH_ALEN); + memcpy(eth_hdr.h_source, wh.addr3, ETH_ALEN); + break; + case IEEE80211_FCTL_FROMDS | IEEE80211_FCTL_TODS: + break; + } + + skb_pull(skb, sizeof(struct ath6kl_llc_snap_hdr)); + skb_push(skb, sizeof(eth_hdr)); + + datap = skb->data; + + memcpy(datap, ð_hdr, sizeof(eth_hdr)); + + return 0; +} + +/* + * Performs 802.3 to DIX encapsulation for received packets. + * Assumes the entire 802.3 header is contigous. + */ +int ath6kl_wmi_dot3_2_dix(struct sk_buff *skb) +{ + struct ath6kl_llc_snap_hdr *llc_hdr; + struct ethhdr eth_hdr; + u8 *datap; + + if (WARN_ON(skb == NULL)) + return -EINVAL; + + datap = skb->data; + + memcpy(ð_hdr, datap, sizeof(eth_hdr)); + + llc_hdr = (struct ath6kl_llc_snap_hdr *) (datap + sizeof(eth_hdr)); + eth_hdr.h_proto = llc_hdr->eth_type; + + skb_pull(skb, sizeof(struct ath6kl_llc_snap_hdr)); + datap = skb->data; + + memcpy(datap, ð_hdr, sizeof(eth_hdr)); + + return 0; +} + +static int ath6kl_wmi_tx_complete_event_rx(u8 *datap, int len) +{ + struct tx_complete_msg_v1 *msg_v1; + struct wmi_tx_complete_event *evt; + int index; + u16 size; + + evt = (struct wmi_tx_complete_event *) datap; + + ath6kl_dbg(ATH6KL_DBG_WMI, "comp: %d %d %d\n", + evt->num_msg, evt->msg_len, evt->msg_type); + + for (index = 0; index < evt->num_msg; index++) { + size = sizeof(struct wmi_tx_complete_event) + + (index * sizeof(struct tx_complete_msg_v1)); + msg_v1 = (struct tx_complete_msg_v1 *)(datap + size); + + ath6kl_dbg(ATH6KL_DBG_WMI, "msg: %d %d %d %d\n", + msg_v1->status, msg_v1->pkt_id, + msg_v1->rate_idx, msg_v1->ack_failures); + } + + return 0; +} + +static int ath6kl_wmi_remain_on_chnl_event_rx(struct wmi *wmi, u8 *datap, + int len, struct ath6kl_vif *vif) +{ + struct wmi_remain_on_chnl_event *ev; + u32 freq; + u32 dur; + struct ieee80211_channel *chan; + struct ath6kl *ar = wmi->parent_dev; + u32 id; + + if (len < sizeof(*ev)) + return -EINVAL; + + ev = (struct wmi_remain_on_chnl_event *) datap; + freq = le32_to_cpu(ev->freq); + dur = le32_to_cpu(ev->duration); + ath6kl_dbg(ATH6KL_DBG_WMI, "remain_on_chnl: freq=%u dur=%u\n", + freq, dur); + chan = ieee80211_get_channel(ar->wiphy, freq); + if (!chan) { + ath6kl_dbg(ATH6KL_DBG_WMI, + "remain_on_chnl: Unknown channel (freq=%u)\n", + freq); + return -EINVAL; + } + id = vif->last_roc_id; + cfg80211_ready_on_channel(&vif->wdev, id, chan, + dur, GFP_ATOMIC); + + return 0; +} + +static int ath6kl_wmi_cancel_remain_on_chnl_event_rx(struct wmi *wmi, + u8 *datap, int len, + struct ath6kl_vif *vif) +{ + struct wmi_cancel_remain_on_chnl_event *ev; + u32 freq; + u32 dur; + struct ieee80211_channel *chan; + struct ath6kl *ar = wmi->parent_dev; + u32 id; + + if (len < sizeof(*ev)) + return -EINVAL; + + ev = (struct wmi_cancel_remain_on_chnl_event *) datap; + freq = le32_to_cpu(ev->freq); + dur = le32_to_cpu(ev->duration); + ath6kl_dbg(ATH6KL_DBG_WMI, + "cancel_remain_on_chnl: freq=%u dur=%u status=%u\n", + freq, dur, ev->status); + chan = ieee80211_get_channel(ar->wiphy, freq); + if (!chan) { + ath6kl_dbg(ATH6KL_DBG_WMI, + "cancel_remain_on_chnl: Unknown channel (freq=%u)\n", + freq); + return -EINVAL; + } + if (vif->last_cancel_roc_id && + vif->last_cancel_roc_id + 1 == vif->last_roc_id) + id = vif->last_cancel_roc_id; /* event for cancel command */ + else + id = vif->last_roc_id; /* timeout on uncanceled r-o-c */ + vif->last_cancel_roc_id = 0; + cfg80211_remain_on_channel_expired(&vif->wdev, id, chan, GFP_ATOMIC); + + return 0; +} + +static int ath6kl_wmi_tx_status_event_rx(struct wmi *wmi, u8 *datap, int len, + struct ath6kl_vif *vif) +{ + struct wmi_tx_status_event *ev; + u32 id; + + if (len < sizeof(*ev)) + return -EINVAL; + + ev = (struct wmi_tx_status_event *) datap; + id = le32_to_cpu(ev->id); + ath6kl_dbg(ATH6KL_DBG_WMI, "tx_status: id=%x ack_status=%u\n", + id, ev->ack_status); + if (wmi->last_mgmt_tx_frame) { + cfg80211_mgmt_tx_status(&vif->wdev, id, + wmi->last_mgmt_tx_frame, + wmi->last_mgmt_tx_frame_len, + !!ev->ack_status, GFP_ATOMIC); + kfree(wmi->last_mgmt_tx_frame); + wmi->last_mgmt_tx_frame = NULL; + wmi->last_mgmt_tx_frame_len = 0; + } + + return 0; +} + +static int ath6kl_wmi_rx_probe_req_event_rx(struct wmi *wmi, u8 *datap, int len, + struct ath6kl_vif *vif) +{ + struct wmi_p2p_rx_probe_req_event *ev; + u32 freq; + u16 dlen; + + if (len < sizeof(*ev)) + return -EINVAL; + + ev = (struct wmi_p2p_rx_probe_req_event *) datap; + freq = le32_to_cpu(ev->freq); + dlen = le16_to_cpu(ev->len); + if (datap + len < ev->data + dlen) { + ath6kl_err("invalid wmi_p2p_rx_probe_req_event: len=%d dlen=%u\n", + len, dlen); + return -EINVAL; + } + ath6kl_dbg(ATH6KL_DBG_WMI, + "rx_probe_req: len=%u freq=%u probe_req_report=%d\n", + dlen, freq, vif->probe_req_report); + + if (vif->probe_req_report || vif->nw_type == AP_NETWORK) + cfg80211_rx_mgmt(&vif->wdev, freq, 0, + ev->data, dlen, GFP_ATOMIC); + + return 0; +} + +static int ath6kl_wmi_p2p_capabilities_event_rx(u8 *datap, int len) +{ + struct wmi_p2p_capabilities_event *ev; + u16 dlen; + + if (len < sizeof(*ev)) + return -EINVAL; + + ev = (struct wmi_p2p_capabilities_event *) datap; + dlen = le16_to_cpu(ev->len); + ath6kl_dbg(ATH6KL_DBG_WMI, "p2p_capab: len=%u\n", dlen); + + return 0; +} + +static int ath6kl_wmi_rx_action_event_rx(struct wmi *wmi, u8 *datap, int len, + struct ath6kl_vif *vif) +{ + struct wmi_rx_action_event *ev; + u32 freq; + u16 dlen; + + if (len < sizeof(*ev)) + return -EINVAL; + + ev = (struct wmi_rx_action_event *) datap; + freq = le32_to_cpu(ev->freq); + dlen = le16_to_cpu(ev->len); + if (datap + len < ev->data + dlen) { + ath6kl_err("invalid wmi_rx_action_event: len=%d dlen=%u\n", + len, dlen); + return -EINVAL; + } + ath6kl_dbg(ATH6KL_DBG_WMI, "rx_action: len=%u freq=%u\n", dlen, freq); + cfg80211_rx_mgmt(&vif->wdev, freq, 0, + ev->data, dlen, GFP_ATOMIC); + + return 0; +} + +static int ath6kl_wmi_p2p_info_event_rx(u8 *datap, int len) +{ + struct wmi_p2p_info_event *ev; + u32 flags; + u16 dlen; + + if (len < sizeof(*ev)) + return -EINVAL; + + ev = (struct wmi_p2p_info_event *) datap; + flags = le32_to_cpu(ev->info_req_flags); + dlen = le16_to_cpu(ev->len); + ath6kl_dbg(ATH6KL_DBG_WMI, "p2p_info: flags=%x len=%d\n", flags, dlen); + + if (flags & P2P_FLAG_CAPABILITIES_REQ) { + struct wmi_p2p_capabilities *cap; + if (dlen < sizeof(*cap)) + return -EINVAL; + cap = (struct wmi_p2p_capabilities *) ev->data; + ath6kl_dbg(ATH6KL_DBG_WMI, "p2p_info: GO Power Save = %d\n", + cap->go_power_save); + } + + if (flags & P2P_FLAG_MACADDR_REQ) { + struct wmi_p2p_macaddr *mac; + if (dlen < sizeof(*mac)) + return -EINVAL; + mac = (struct wmi_p2p_macaddr *) ev->data; + ath6kl_dbg(ATH6KL_DBG_WMI, "p2p_info: MAC Address = %pM\n", + mac->mac_addr); + } + + if (flags & P2P_FLAG_HMODEL_REQ) { + struct wmi_p2p_hmodel *mod; + if (dlen < sizeof(*mod)) + return -EINVAL; + mod = (struct wmi_p2p_hmodel *) ev->data; + ath6kl_dbg(ATH6KL_DBG_WMI, "p2p_info: P2P Model = %d (%s)\n", + mod->p2p_model, + mod->p2p_model ? "host" : "firmware"); + } + return 0; +} + +static inline struct sk_buff *ath6kl_wmi_get_new_buf(u32 size) +{ + struct sk_buff *skb; + + skb = ath6kl_buf_alloc(size); + if (!skb) + return NULL; + + skb_put(skb, size); + if (size) + memset(skb->data, 0, size); + + return skb; +} + +/* Send a "simple" wmi command -- one with no arguments */ +static int ath6kl_wmi_simple_cmd(struct wmi *wmi, u8 if_idx, + enum wmi_cmd_id cmd_id) +{ + struct sk_buff *skb; + int ret; + + skb = ath6kl_wmi_get_new_buf(0); + if (!skb) + return -ENOMEM; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, cmd_id, NO_SYNC_WMIFLAG); + + return ret; +} + +static int ath6kl_wmi_ready_event_rx(struct wmi *wmi, u8 *datap, int len) +{ + struct wmi_ready_event_2 *ev = (struct wmi_ready_event_2 *) datap; + + if (len < sizeof(struct wmi_ready_event_2)) + return -EINVAL; + + ath6kl_ready_event(wmi->parent_dev, ev->mac_addr, + le32_to_cpu(ev->sw_version), + le32_to_cpu(ev->abi_version), ev->phy_cap); + + return 0; +} + +/* + * Mechanism to modify the roaming behavior in the firmware. The lower rssi + * at which the station has to roam can be passed with + * WMI_SET_LRSSI_SCAN_PARAMS. Subtract 96 from RSSI to get the signal level + * in dBm. + */ +int ath6kl_wmi_set_roam_lrssi_cmd(struct wmi *wmi, u8 lrssi) +{ + struct sk_buff *skb; + struct roam_ctrl_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct roam_ctrl_cmd *) skb->data; + + cmd->info.params.lrssi_scan_period = cpu_to_le16(DEF_LRSSI_SCAN_PERIOD); + cmd->info.params.lrssi_scan_threshold = a_cpu_to_sle16(lrssi + + DEF_SCAN_FOR_ROAM_INTVL); + cmd->info.params.lrssi_roam_threshold = a_cpu_to_sle16(lrssi); + cmd->info.params.roam_rssi_floor = DEF_LRSSI_ROAM_FLOOR; + cmd->roam_ctrl = WMI_SET_LRSSI_SCAN_PARAMS; + + ath6kl_wmi_cmd_send(wmi, 0, skb, WMI_SET_ROAM_CTRL_CMDID, + NO_SYNC_WMIFLAG); + + return 0; +} + +int ath6kl_wmi_force_roam_cmd(struct wmi *wmi, const u8 *bssid) +{ + struct sk_buff *skb; + struct roam_ctrl_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct roam_ctrl_cmd *) skb->data; + + memcpy(cmd->info.bssid, bssid, ETH_ALEN); + cmd->roam_ctrl = WMI_FORCE_ROAM; + + ath6kl_dbg(ATH6KL_DBG_WMI, "force roam to %pM\n", bssid); + return ath6kl_wmi_cmd_send(wmi, 0, skb, WMI_SET_ROAM_CTRL_CMDID, + NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_ap_set_beacon_intvl_cmd(struct wmi *wmi, u8 if_idx, + u32 beacon_intvl) +{ + struct sk_buff *skb; + struct set_beacon_int_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct set_beacon_int_cmd *) skb->data; + + cmd->beacon_intvl = cpu_to_le32(beacon_intvl); + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, + WMI_SET_BEACON_INT_CMDID, NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_ap_set_dtim_cmd(struct wmi *wmi, u8 if_idx, u32 dtim_period) +{ + struct sk_buff *skb; + struct set_dtim_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct set_dtim_cmd *) skb->data; + + cmd->dtim_period = cpu_to_le32(dtim_period); + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, + WMI_AP_SET_DTIM_CMDID, NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_set_roam_mode_cmd(struct wmi *wmi, enum wmi_roam_mode mode) +{ + struct sk_buff *skb; + struct roam_ctrl_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct roam_ctrl_cmd *) skb->data; + + cmd->info.roam_mode = mode; + cmd->roam_ctrl = WMI_SET_ROAM_MODE; + + ath6kl_dbg(ATH6KL_DBG_WMI, "set roam mode %d\n", mode); + return ath6kl_wmi_cmd_send(wmi, 0, skb, WMI_SET_ROAM_CTRL_CMDID, + NO_SYNC_WMIFLAG); +} + +static int ath6kl_wmi_connect_event_rx(struct wmi *wmi, u8 *datap, int len, + struct ath6kl_vif *vif) +{ + struct wmi_connect_event *ev; + u8 *pie, *peie; + + if (len < sizeof(struct wmi_connect_event)) + return -EINVAL; + + ev = (struct wmi_connect_event *) datap; + + if (vif->nw_type == AP_NETWORK) { + /* AP mode start/STA connected event */ + struct net_device *dev = vif->ndev; + if (memcmp(dev->dev_addr, ev->u.ap_bss.bssid, ETH_ALEN) == 0) { + ath6kl_dbg(ATH6KL_DBG_WMI, + "%s: freq %d bssid %pM (AP started)\n", + __func__, le16_to_cpu(ev->u.ap_bss.ch), + ev->u.ap_bss.bssid); + ath6kl_connect_ap_mode_bss( + vif, le16_to_cpu(ev->u.ap_bss.ch)); + } else { + ath6kl_dbg(ATH6KL_DBG_WMI, + "%s: aid %u mac_addr %pM auth=%u keymgmt=%u cipher=%u apsd_info=%u (STA connected)\n", + __func__, ev->u.ap_sta.aid, + ev->u.ap_sta.mac_addr, + ev->u.ap_sta.auth, + ev->u.ap_sta.keymgmt, + le16_to_cpu(ev->u.ap_sta.cipher), + ev->u.ap_sta.apsd_info); + + ath6kl_connect_ap_mode_sta( + vif, ev->u.ap_sta.aid, ev->u.ap_sta.mac_addr, + ev->u.ap_sta.keymgmt, + le16_to_cpu(ev->u.ap_sta.cipher), + ev->u.ap_sta.auth, ev->assoc_req_len, + ev->assoc_info + ev->beacon_ie_len, + ev->u.ap_sta.apsd_info); + } + return 0; + } + + /* STA/IBSS mode connection event */ + + ath6kl_dbg(ATH6KL_DBG_WMI, + "wmi event connect freq %d bssid %pM listen_intvl %d beacon_intvl %d type %d\n", + le16_to_cpu(ev->u.sta.ch), ev->u.sta.bssid, + le16_to_cpu(ev->u.sta.listen_intvl), + le16_to_cpu(ev->u.sta.beacon_intvl), + le32_to_cpu(ev->u.sta.nw_type)); + + /* Start of assoc rsp IEs */ + pie = ev->assoc_info + ev->beacon_ie_len + + ev->assoc_req_len + (sizeof(u16) * 3); /* capinfo, status, aid */ + + /* End of assoc rsp IEs */ + peie = ev->assoc_info + ev->beacon_ie_len + ev->assoc_req_len + + ev->assoc_resp_len; + + while (pie < peie) { + switch (*pie) { + case WLAN_EID_VENDOR_SPECIFIC: + if (pie[1] > 3 && pie[2] == 0x00 && pie[3] == 0x50 && + pie[4] == 0xf2 && pie[5] == WMM_OUI_TYPE) { + /* WMM OUT (00:50:F2) */ + if (pie[1] > 5 && + pie[6] == WMM_PARAM_OUI_SUBTYPE) + wmi->is_wmm_enabled = true; + } + break; + } + + if (wmi->is_wmm_enabled) + break; + + pie += pie[1] + 2; + } + + ath6kl_connect_event(vif, le16_to_cpu(ev->u.sta.ch), + ev->u.sta.bssid, + le16_to_cpu(ev->u.sta.listen_intvl), + le16_to_cpu(ev->u.sta.beacon_intvl), + le32_to_cpu(ev->u.sta.nw_type), + ev->beacon_ie_len, ev->assoc_req_len, + ev->assoc_resp_len, ev->assoc_info); + + return 0; +} + +static struct country_code_to_enum_rd * +ath6kl_regd_find_country(u16 countryCode) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(allCountries); i++) { + if (allCountries[i].countryCode == countryCode) + return &allCountries[i]; + } + + return NULL; +} + +static struct reg_dmn_pair_mapping * +ath6kl_get_regpair(u16 regdmn) +{ + int i; + + if (regdmn == NO_ENUMRD) + return NULL; + + for (i = 0; i < ARRAY_SIZE(regDomainPairs); i++) { + if (regDomainPairs[i].regDmnEnum == regdmn) + return ®DomainPairs[i]; + } + + return NULL; +} + +static struct country_code_to_enum_rd * +ath6kl_regd_find_country_by_rd(u16 regdmn) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(allCountries); i++) { + if (allCountries[i].regDmnEnum == regdmn) + return &allCountries[i]; + } + + return NULL; +} + +static void ath6kl_wmi_regdomain_event(struct wmi *wmi, u8 *datap, int len) +{ + + struct ath6kl_wmi_regdomain *ev; + struct country_code_to_enum_rd *country = NULL; + struct reg_dmn_pair_mapping *regpair = NULL; + char alpha2[2]; + u32 reg_code; + + ev = (struct ath6kl_wmi_regdomain *) datap; + reg_code = le32_to_cpu(ev->reg_code); + + if ((reg_code >> ATH6KL_COUNTRY_RD_SHIFT) & COUNTRY_ERD_FLAG) + country = ath6kl_regd_find_country((u16) reg_code); + else if (!(((u16) reg_code & WORLD_SKU_MASK) == WORLD_SKU_PREFIX)) { + + regpair = ath6kl_get_regpair((u16) reg_code); + country = ath6kl_regd_find_country_by_rd((u16) reg_code); + if (regpair) + ath6kl_dbg(ATH6KL_DBG_WMI, "Regpair used: 0x%0x\n", + regpair->regDmnEnum); + else + ath6kl_warn("Regpair not found reg_code 0x%0x\n", + reg_code); + } + + if (country && wmi->parent_dev->wiphy_registered) { + alpha2[0] = country->isoName[0]; + alpha2[1] = country->isoName[1]; + + regulatory_hint(wmi->parent_dev->wiphy, alpha2); + + ath6kl_dbg(ATH6KL_DBG_WMI, "Country alpha2 being used: %c%c\n", + alpha2[0], alpha2[1]); + } +} + +static int ath6kl_wmi_disconnect_event_rx(struct wmi *wmi, u8 *datap, int len, + struct ath6kl_vif *vif) +{ + struct wmi_disconnect_event *ev; + wmi->traffic_class = 100; + + if (len < sizeof(struct wmi_disconnect_event)) + return -EINVAL; + + ev = (struct wmi_disconnect_event *) datap; + + ath6kl_dbg(ATH6KL_DBG_WMI, + "wmi event disconnect proto_reason %d bssid %pM wmi_reason %d assoc_resp_len %d\n", + le16_to_cpu(ev->proto_reason_status), ev->bssid, + ev->disconn_reason, ev->assoc_resp_len); + + wmi->is_wmm_enabled = false; + + ath6kl_disconnect_event(vif, ev->disconn_reason, + ev->bssid, ev->assoc_resp_len, ev->assoc_info, + le16_to_cpu(ev->proto_reason_status)); + + return 0; +} + +static int ath6kl_wmi_peer_node_event_rx(struct wmi *wmi, u8 *datap, int len) +{ + struct wmi_peer_node_event *ev; + + if (len < sizeof(struct wmi_peer_node_event)) + return -EINVAL; + + ev = (struct wmi_peer_node_event *) datap; + + if (ev->event_code == PEER_NODE_JOIN_EVENT) + ath6kl_dbg(ATH6KL_DBG_WMI, "joined node with mac addr: %pM\n", + ev->peer_mac_addr); + else if (ev->event_code == PEER_NODE_LEAVE_EVENT) + ath6kl_dbg(ATH6KL_DBG_WMI, "left node with mac addr: %pM\n", + ev->peer_mac_addr); + + return 0; +} + +static int ath6kl_wmi_tkip_micerr_event_rx(struct wmi *wmi, u8 *datap, int len, + struct ath6kl_vif *vif) +{ + struct wmi_tkip_micerr_event *ev; + + if (len < sizeof(struct wmi_tkip_micerr_event)) + return -EINVAL; + + ev = (struct wmi_tkip_micerr_event *) datap; + + ath6kl_tkip_micerr_event(vif, ev->key_id, ev->is_mcast); + + return 0; +} + +void ath6kl_wmi_sscan_timer(unsigned long ptr) +{ + struct ath6kl_vif *vif = (struct ath6kl_vif *) ptr; + + cfg80211_sched_scan_results(vif->ar->wiphy); +} + +static int ath6kl_wmi_bssinfo_event_rx(struct wmi *wmi, u8 *datap, int len, + struct ath6kl_vif *vif) +{ + struct wmi_bss_info_hdr2 *bih; + u8 *buf; + struct ieee80211_channel *channel; + struct ath6kl *ar = wmi->parent_dev; + struct ieee80211_mgmt *mgmt; + struct cfg80211_bss *bss; + + if (len <= sizeof(struct wmi_bss_info_hdr2)) + return -EINVAL; + + bih = (struct wmi_bss_info_hdr2 *) datap; + buf = datap + sizeof(struct wmi_bss_info_hdr2); + len -= sizeof(struct wmi_bss_info_hdr2); + + ath6kl_dbg(ATH6KL_DBG_WMI, + "bss info evt - ch %u, snr %d, rssi %d, bssid \"%pM\" " + "frame_type=%d\n", + bih->ch, bih->snr, bih->snr - 95, bih->bssid, + bih->frame_type); + + if (bih->frame_type != BEACON_FTYPE && + bih->frame_type != PROBERESP_FTYPE) + return 0; /* Only update BSS table for now */ + + if (bih->frame_type == BEACON_FTYPE && + test_bit(CLEAR_BSSFILTER_ON_BEACON, &vif->flags)) { + clear_bit(CLEAR_BSSFILTER_ON_BEACON, &vif->flags); + ath6kl_wmi_bssfilter_cmd(ar->wmi, vif->fw_vif_idx, + NONE_BSS_FILTER, 0); + } + + channel = ieee80211_get_channel(ar->wiphy, le16_to_cpu(bih->ch)); + if (channel == NULL) + return -EINVAL; + + if (len < 8 + 2 + 2) + return -EINVAL; + + if (bih->frame_type == BEACON_FTYPE && + test_bit(CONNECTED, &vif->flags) && + memcmp(bih->bssid, vif->bssid, ETH_ALEN) == 0) { + const u8 *tim; + tim = cfg80211_find_ie(WLAN_EID_TIM, buf + 8 + 2 + 2, + len - 8 - 2 - 2); + if (tim && tim[1] >= 2) { + vif->assoc_bss_dtim_period = tim[3]; + set_bit(DTIM_PERIOD_AVAIL, &vif->flags); + } + } + + /* + * In theory, use of cfg80211_inform_bss() would be more natural here + * since we do not have the full frame. However, at least for now, + * cfg80211 can only distinguish Beacon and Probe Response frames from + * each other when using cfg80211_inform_bss_frame(), so let's build a + * fake IEEE 802.11 header to be able to take benefit of this. + */ + mgmt = kmalloc(24 + len, GFP_ATOMIC); + if (mgmt == NULL) + return -EINVAL; + + if (bih->frame_type == BEACON_FTYPE) { + mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_BEACON); + memset(mgmt->da, 0xff, ETH_ALEN); + } else { + struct net_device *dev = vif->ndev; + + mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_PROBE_RESP); + memcpy(mgmt->da, dev->dev_addr, ETH_ALEN); + } + mgmt->duration = cpu_to_le16(0); + memcpy(mgmt->sa, bih->bssid, ETH_ALEN); + memcpy(mgmt->bssid, bih->bssid, ETH_ALEN); + mgmt->seq_ctrl = cpu_to_le16(0); + + memcpy(&mgmt->u.beacon, buf, len); + + bss = cfg80211_inform_bss_frame(ar->wiphy, channel, mgmt, + 24 + len, (bih->snr - 95) * 100, + GFP_ATOMIC); + kfree(mgmt); + if (bss == NULL) + return -ENOMEM; + cfg80211_put_bss(ar->wiphy, bss); + + /* + * Firmware doesn't return any event when scheduled scan has + * finished, so we need to use a timer to find out when there are + * no more results. + * + * The timer is started from the first bss info received, otherwise + * the timer would not ever fire if the scan interval is short + * enough. + */ + if (test_bit(SCHED_SCANNING, &vif->flags) && + !timer_pending(&vif->sched_scan_timer)) { + mod_timer(&vif->sched_scan_timer, jiffies + + msecs_to_jiffies(ATH6KL_SCHED_SCAN_RESULT_DELAY)); + } + + return 0; +} + +/* Inactivity timeout of a fatpipe(pstream) at the target */ +static int ath6kl_wmi_pstream_timeout_event_rx(struct wmi *wmi, u8 *datap, + int len) +{ + struct wmi_pstream_timeout_event *ev; + + if (len < sizeof(struct wmi_pstream_timeout_event)) + return -EINVAL; + + ev = (struct wmi_pstream_timeout_event *) datap; + + /* + * When the pstream (fat pipe == AC) timesout, it means there were + * no thinStreams within this pstream & it got implicitly created + * due to data flow on this AC. We start the inactivity timer only + * for implicitly created pstream. Just reset the host state. + */ + spin_lock_bh(&wmi->lock); + wmi->stream_exist_for_ac[ev->traffic_class] = 0; + wmi->fat_pipe_exist &= ~(1 << ev->traffic_class); + spin_unlock_bh(&wmi->lock); + + /* Indicate inactivity to driver layer for this fatpipe (pstream) */ + ath6kl_indicate_tx_activity(wmi->parent_dev, ev->traffic_class, false); + + return 0; +} + +static int ath6kl_wmi_bitrate_reply_rx(struct wmi *wmi, u8 *datap, int len) +{ + struct wmi_bit_rate_reply *reply; + s32 rate; + u32 sgi, index; + + if (len < sizeof(struct wmi_bit_rate_reply)) + return -EINVAL; + + reply = (struct wmi_bit_rate_reply *) datap; + + ath6kl_dbg(ATH6KL_DBG_WMI, "rateindex %d\n", reply->rate_index); + + if (reply->rate_index == (s8) RATE_AUTO) { + rate = RATE_AUTO; + } else { + index = reply->rate_index & 0x7f; + if (WARN_ON_ONCE(index > (RATE_MCS_7_40 + 1))) + return -EINVAL; + + sgi = (reply->rate_index & 0x80) ? 1 : 0; + rate = wmi_rate_tbl[index][sgi]; + } + + ath6kl_wakeup_event(wmi->parent_dev); + + return 0; +} + +static int ath6kl_wmi_test_rx(struct wmi *wmi, u8 *datap, int len) +{ + ath6kl_tm_rx_event(wmi->parent_dev, datap, len); + + return 0; +} + +static int ath6kl_wmi_ratemask_reply_rx(struct wmi *wmi, u8 *datap, int len) +{ + if (len < sizeof(struct wmi_fix_rates_reply)) + return -EINVAL; + + ath6kl_wakeup_event(wmi->parent_dev); + + return 0; +} + +static int ath6kl_wmi_ch_list_reply_rx(struct wmi *wmi, u8 *datap, int len) +{ + if (len < sizeof(struct wmi_channel_list_reply)) + return -EINVAL; + + ath6kl_wakeup_event(wmi->parent_dev); + + return 0; +} + +static int ath6kl_wmi_tx_pwr_reply_rx(struct wmi *wmi, u8 *datap, int len) +{ + struct wmi_tx_pwr_reply *reply; + + if (len < sizeof(struct wmi_tx_pwr_reply)) + return -EINVAL; + + reply = (struct wmi_tx_pwr_reply *) datap; + ath6kl_txpwr_rx_evt(wmi->parent_dev, reply->dbM); + + return 0; +} + +static int ath6kl_wmi_keepalive_reply_rx(struct wmi *wmi, u8 *datap, int len) +{ + if (len < sizeof(struct wmi_get_keepalive_cmd)) + return -EINVAL; + + ath6kl_wakeup_event(wmi->parent_dev); + + return 0; +} + +static int ath6kl_wmi_scan_complete_rx(struct wmi *wmi, u8 *datap, int len, + struct ath6kl_vif *vif) +{ + struct wmi_scan_complete_event *ev; + + ev = (struct wmi_scan_complete_event *) datap; + + ath6kl_scan_complete_evt(vif, a_sle32_to_cpu(ev->status)); + wmi->is_probe_ssid = false; + + return 0; +} + +static int ath6kl_wmi_neighbor_report_event_rx(struct wmi *wmi, u8 *datap, + int len, struct ath6kl_vif *vif) +{ + struct wmi_neighbor_report_event *ev; + u8 i; + + if (len < sizeof(*ev)) + return -EINVAL; + ev = (struct wmi_neighbor_report_event *) datap; + if (sizeof(*ev) + ev->num_neighbors * sizeof(struct wmi_neighbor_info) + > len) { + ath6kl_dbg(ATH6KL_DBG_WMI, + "truncated neighbor event (num=%d len=%d)\n", + ev->num_neighbors, len); + return -EINVAL; + } + for (i = 0; i < ev->num_neighbors; i++) { + ath6kl_dbg(ATH6KL_DBG_WMI, "neighbor %d/%d - %pM 0x%x\n", + i + 1, ev->num_neighbors, ev->neighbor[i].bssid, + ev->neighbor[i].bss_flags); + cfg80211_pmksa_candidate_notify(vif->ndev, i, + ev->neighbor[i].bssid, + !!(ev->neighbor[i].bss_flags & + WMI_PREAUTH_CAPABLE_BSS), + GFP_ATOMIC); + } + + return 0; +} + +/* + * Target is reporting a programming error. This is for + * developer aid only. Target only checks a few common violations + * and it is responsibility of host to do all error checking. + * Behavior of target after wmi error event is undefined. + * A reset is recommended. + */ +static int ath6kl_wmi_error_event_rx(struct wmi *wmi, u8 *datap, int len) +{ + const char *type = "unknown error"; + struct wmi_cmd_error_event *ev; + ev = (struct wmi_cmd_error_event *) datap; + + switch (ev->err_code) { + case INVALID_PARAM: + type = "invalid parameter"; + break; + case ILLEGAL_STATE: + type = "invalid state"; + break; + case INTERNAL_ERROR: + type = "internal error"; + break; + } + + ath6kl_dbg(ATH6KL_DBG_WMI, "programming error, cmd=%d %s\n", + ev->cmd_id, type); + + return 0; +} + +static int ath6kl_wmi_stats_event_rx(struct wmi *wmi, u8 *datap, int len, + struct ath6kl_vif *vif) +{ + ath6kl_tgt_stats_event(vif, datap, len); + + return 0; +} + +static u8 ath6kl_wmi_get_upper_threshold(s16 rssi, + struct sq_threshold_params *sq_thresh, + u32 size) +{ + u32 index; + u8 threshold = (u8) sq_thresh->upper_threshold[size - 1]; + + /* The list is already in sorted order. Get the next lower value */ + for (index = 0; index < size; index++) { + if (rssi < sq_thresh->upper_threshold[index]) { + threshold = (u8) sq_thresh->upper_threshold[index]; + break; + } + } + + return threshold; +} + +static u8 ath6kl_wmi_get_lower_threshold(s16 rssi, + struct sq_threshold_params *sq_thresh, + u32 size) +{ + u32 index; + u8 threshold = (u8) sq_thresh->lower_threshold[size - 1]; + + /* The list is already in sorted order. Get the next lower value */ + for (index = 0; index < size; index++) { + if (rssi > sq_thresh->lower_threshold[index]) { + threshold = (u8) sq_thresh->lower_threshold[index]; + break; + } + } + + return threshold; +} + +static int ath6kl_wmi_send_rssi_threshold_params(struct wmi *wmi, + struct wmi_rssi_threshold_params_cmd *rssi_cmd) +{ + struct sk_buff *skb; + struct wmi_rssi_threshold_params_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_rssi_threshold_params_cmd *) skb->data; + memcpy(cmd, rssi_cmd, sizeof(struct wmi_rssi_threshold_params_cmd)); + + return ath6kl_wmi_cmd_send(wmi, 0, skb, WMI_RSSI_THRESHOLD_PARAMS_CMDID, + NO_SYNC_WMIFLAG); +} + +static int ath6kl_wmi_rssi_threshold_event_rx(struct wmi *wmi, u8 *datap, + int len) +{ + struct wmi_rssi_threshold_event *reply; + struct wmi_rssi_threshold_params_cmd cmd; + struct sq_threshold_params *sq_thresh; + enum wmi_rssi_threshold_val new_threshold; + u8 upper_rssi_threshold, lower_rssi_threshold; + s16 rssi; + int ret; + + if (len < sizeof(struct wmi_rssi_threshold_event)) + return -EINVAL; + + reply = (struct wmi_rssi_threshold_event *) datap; + new_threshold = (enum wmi_rssi_threshold_val) reply->range; + rssi = a_sle16_to_cpu(reply->rssi); + + sq_thresh = &wmi->sq_threshld[SIGNAL_QUALITY_METRICS_RSSI]; + + /* + * Identify the threshold breached and communicate that to the app. + * After that install a new set of thresholds based on the signal + * quality reported by the target + */ + if (new_threshold) { + /* Upper threshold breached */ + if (rssi < sq_thresh->upper_threshold[0]) { + ath6kl_dbg(ATH6KL_DBG_WMI, + "spurious upper rssi threshold event: %d\n", + rssi); + } else if ((rssi < sq_thresh->upper_threshold[1]) && + (rssi >= sq_thresh->upper_threshold[0])) { + new_threshold = WMI_RSSI_THRESHOLD1_ABOVE; + } else if ((rssi < sq_thresh->upper_threshold[2]) && + (rssi >= sq_thresh->upper_threshold[1])) { + new_threshold = WMI_RSSI_THRESHOLD2_ABOVE; + } else if ((rssi < sq_thresh->upper_threshold[3]) && + (rssi >= sq_thresh->upper_threshold[2])) { + new_threshold = WMI_RSSI_THRESHOLD3_ABOVE; + } else if ((rssi < sq_thresh->upper_threshold[4]) && + (rssi >= sq_thresh->upper_threshold[3])) { + new_threshold = WMI_RSSI_THRESHOLD4_ABOVE; + } else if ((rssi < sq_thresh->upper_threshold[5]) && + (rssi >= sq_thresh->upper_threshold[4])) { + new_threshold = WMI_RSSI_THRESHOLD5_ABOVE; + } else if (rssi >= sq_thresh->upper_threshold[5]) { + new_threshold = WMI_RSSI_THRESHOLD6_ABOVE; + } + } else { + /* Lower threshold breached */ + if (rssi > sq_thresh->lower_threshold[0]) { + ath6kl_dbg(ATH6KL_DBG_WMI, + "spurious lower rssi threshold event: %d %d\n", + rssi, sq_thresh->lower_threshold[0]); + } else if ((rssi > sq_thresh->lower_threshold[1]) && + (rssi <= sq_thresh->lower_threshold[0])) { + new_threshold = WMI_RSSI_THRESHOLD6_BELOW; + } else if ((rssi > sq_thresh->lower_threshold[2]) && + (rssi <= sq_thresh->lower_threshold[1])) { + new_threshold = WMI_RSSI_THRESHOLD5_BELOW; + } else if ((rssi > sq_thresh->lower_threshold[3]) && + (rssi <= sq_thresh->lower_threshold[2])) { + new_threshold = WMI_RSSI_THRESHOLD4_BELOW; + } else if ((rssi > sq_thresh->lower_threshold[4]) && + (rssi <= sq_thresh->lower_threshold[3])) { + new_threshold = WMI_RSSI_THRESHOLD3_BELOW; + } else if ((rssi > sq_thresh->lower_threshold[5]) && + (rssi <= sq_thresh->lower_threshold[4])) { + new_threshold = WMI_RSSI_THRESHOLD2_BELOW; + } else if (rssi <= sq_thresh->lower_threshold[5]) { + new_threshold = WMI_RSSI_THRESHOLD1_BELOW; + } + } + + /* Calculate and install the next set of thresholds */ + lower_rssi_threshold = ath6kl_wmi_get_lower_threshold(rssi, sq_thresh, + sq_thresh->lower_threshold_valid_count); + upper_rssi_threshold = ath6kl_wmi_get_upper_threshold(rssi, sq_thresh, + sq_thresh->upper_threshold_valid_count); + + /* Issue a wmi command to install the thresholds */ + cmd.thresh_above1_val = a_cpu_to_sle16(upper_rssi_threshold); + cmd.thresh_below1_val = a_cpu_to_sle16(lower_rssi_threshold); + cmd.weight = sq_thresh->weight; + cmd.poll_time = cpu_to_le32(sq_thresh->polling_interval); + + ret = ath6kl_wmi_send_rssi_threshold_params(wmi, &cmd); + if (ret) { + ath6kl_err("unable to configure rssi thresholds\n"); + return -EIO; + } + + return 0; +} + +static int ath6kl_wmi_cac_event_rx(struct wmi *wmi, u8 *datap, int len, + struct ath6kl_vif *vif) +{ + struct wmi_cac_event *reply; + struct ieee80211_tspec_ie *ts; + u16 active_tsids, tsinfo; + u8 tsid, index; + u8 ts_id; + + if (len < sizeof(struct wmi_cac_event)) + return -EINVAL; + + reply = (struct wmi_cac_event *) datap; + + if ((reply->cac_indication == CAC_INDICATION_ADMISSION_RESP) && + (reply->status_code != IEEE80211_TSPEC_STATUS_ADMISS_ACCEPTED)) { + + ts = (struct ieee80211_tspec_ie *) &(reply->tspec_suggestion); + tsinfo = le16_to_cpu(ts->tsinfo); + tsid = (tsinfo >> IEEE80211_WMM_IE_TSPEC_TID_SHIFT) & + IEEE80211_WMM_IE_TSPEC_TID_MASK; + + ath6kl_wmi_delete_pstream_cmd(wmi, vif->fw_vif_idx, + reply->ac, tsid); + } else if (reply->cac_indication == CAC_INDICATION_NO_RESP) { + /* + * Following assumes that there is only one outstanding + * ADDTS request when this event is received + */ + spin_lock_bh(&wmi->lock); + active_tsids = wmi->stream_exist_for_ac[reply->ac]; + spin_unlock_bh(&wmi->lock); + + for (index = 0; index < sizeof(active_tsids) * 8; index++) { + if ((active_tsids >> index) & 1) + break; + } + if (index < (sizeof(active_tsids) * 8)) + ath6kl_wmi_delete_pstream_cmd(wmi, vif->fw_vif_idx, + reply->ac, index); + } + + /* + * Clear active tsids and Add missing handling + * for delete qos stream from AP + */ + else if (reply->cac_indication == CAC_INDICATION_DELETE) { + + ts = (struct ieee80211_tspec_ie *) &(reply->tspec_suggestion); + tsinfo = le16_to_cpu(ts->tsinfo); + ts_id = ((tsinfo >> IEEE80211_WMM_IE_TSPEC_TID_SHIFT) & + IEEE80211_WMM_IE_TSPEC_TID_MASK); + + spin_lock_bh(&wmi->lock); + wmi->stream_exist_for_ac[reply->ac] &= ~(1 << ts_id); + active_tsids = wmi->stream_exist_for_ac[reply->ac]; + spin_unlock_bh(&wmi->lock); + + /* Indicate stream inactivity to driver layer only if all tsids + * within this AC are deleted. + */ + if (!active_tsids) { + ath6kl_indicate_tx_activity(wmi->parent_dev, reply->ac, + false); + wmi->fat_pipe_exist &= ~(1 << reply->ac); + } + } + + return 0; +} + +static int ath6kl_wmi_txe_notify_event_rx(struct wmi *wmi, u8 *datap, int len, + struct ath6kl_vif *vif) +{ + struct wmi_txe_notify_event *ev; + u32 rate, pkts; + + if (len < sizeof(*ev)) + return -EINVAL; + + if (vif->sme_state != SME_CONNECTED) + return -ENOTCONN; + + ev = (struct wmi_txe_notify_event *) datap; + rate = le32_to_cpu(ev->rate); + pkts = le32_to_cpu(ev->pkts); + + ath6kl_dbg(ATH6KL_DBG_WMI, "TXE notify event: peer %pM rate %d% pkts %d intvl %ds\n", + vif->bssid, rate, pkts, vif->txe_intvl); + + cfg80211_cqm_txe_notify(vif->ndev, vif->bssid, pkts, + rate, vif->txe_intvl, GFP_KERNEL); + + return 0; +} + +int ath6kl_wmi_set_txe_notify(struct wmi *wmi, u8 idx, + u32 rate, u32 pkts, u32 intvl) +{ + struct sk_buff *skb; + struct wmi_txe_notify_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_txe_notify_cmd *) skb->data; + cmd->rate = cpu_to_le32(rate); + cmd->pkts = cpu_to_le32(pkts); + cmd->intvl = cpu_to_le32(intvl); + + return ath6kl_wmi_cmd_send(wmi, idx, skb, WMI_SET_TXE_NOTIFY_CMDID, + NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_set_rssi_filter_cmd(struct wmi *wmi, u8 if_idx, s8 rssi) +{ + struct sk_buff *skb; + struct wmi_set_rssi_filter_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_rssi_filter_cmd *) skb->data; + cmd->rssi = rssi; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_RSSI_FILTER_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +static int ath6kl_wmi_send_snr_threshold_params(struct wmi *wmi, + struct wmi_snr_threshold_params_cmd *snr_cmd) +{ + struct sk_buff *skb; + struct wmi_snr_threshold_params_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_snr_threshold_params_cmd *) skb->data; + memcpy(cmd, snr_cmd, sizeof(struct wmi_snr_threshold_params_cmd)); + + return ath6kl_wmi_cmd_send(wmi, 0, skb, WMI_SNR_THRESHOLD_PARAMS_CMDID, + NO_SYNC_WMIFLAG); +} + +static int ath6kl_wmi_snr_threshold_event_rx(struct wmi *wmi, u8 *datap, + int len) +{ + struct wmi_snr_threshold_event *reply; + struct sq_threshold_params *sq_thresh; + struct wmi_snr_threshold_params_cmd cmd; + enum wmi_snr_threshold_val new_threshold; + u8 upper_snr_threshold, lower_snr_threshold; + s16 snr; + int ret; + + if (len < sizeof(struct wmi_snr_threshold_event)) + return -EINVAL; + + reply = (struct wmi_snr_threshold_event *) datap; + + new_threshold = (enum wmi_snr_threshold_val) reply->range; + snr = reply->snr; + + sq_thresh = &wmi->sq_threshld[SIGNAL_QUALITY_METRICS_SNR]; + + /* + * Identify the threshold breached and communicate that to the app. + * After that install a new set of thresholds based on the signal + * quality reported by the target. + */ + if (new_threshold) { + /* Upper threshold breached */ + if (snr < sq_thresh->upper_threshold[0]) { + ath6kl_dbg(ATH6KL_DBG_WMI, + "spurious upper snr threshold event: %d\n", + snr); + } else if ((snr < sq_thresh->upper_threshold[1]) && + (snr >= sq_thresh->upper_threshold[0])) { + new_threshold = WMI_SNR_THRESHOLD1_ABOVE; + } else if ((snr < sq_thresh->upper_threshold[2]) && + (snr >= sq_thresh->upper_threshold[1])) { + new_threshold = WMI_SNR_THRESHOLD2_ABOVE; + } else if ((snr < sq_thresh->upper_threshold[3]) && + (snr >= sq_thresh->upper_threshold[2])) { + new_threshold = WMI_SNR_THRESHOLD3_ABOVE; + } else if (snr >= sq_thresh->upper_threshold[3]) { + new_threshold = WMI_SNR_THRESHOLD4_ABOVE; + } + } else { + /* Lower threshold breached */ + if (snr > sq_thresh->lower_threshold[0]) { + ath6kl_dbg(ATH6KL_DBG_WMI, + "spurious lower snr threshold event: %d\n", + sq_thresh->lower_threshold[0]); + } else if ((snr > sq_thresh->lower_threshold[1]) && + (snr <= sq_thresh->lower_threshold[0])) { + new_threshold = WMI_SNR_THRESHOLD4_BELOW; + } else if ((snr > sq_thresh->lower_threshold[2]) && + (snr <= sq_thresh->lower_threshold[1])) { + new_threshold = WMI_SNR_THRESHOLD3_BELOW; + } else if ((snr > sq_thresh->lower_threshold[3]) && + (snr <= sq_thresh->lower_threshold[2])) { + new_threshold = WMI_SNR_THRESHOLD2_BELOW; + } else if (snr <= sq_thresh->lower_threshold[3]) { + new_threshold = WMI_SNR_THRESHOLD1_BELOW; + } + } + + /* Calculate and install the next set of thresholds */ + lower_snr_threshold = ath6kl_wmi_get_lower_threshold(snr, sq_thresh, + sq_thresh->lower_threshold_valid_count); + upper_snr_threshold = ath6kl_wmi_get_upper_threshold(snr, sq_thresh, + sq_thresh->upper_threshold_valid_count); + + /* Issue a wmi command to install the thresholds */ + cmd.thresh_above1_val = upper_snr_threshold; + cmd.thresh_below1_val = lower_snr_threshold; + cmd.weight = sq_thresh->weight; + cmd.poll_time = cpu_to_le32(sq_thresh->polling_interval); + + ath6kl_dbg(ATH6KL_DBG_WMI, + "snr: %d, threshold: %d, lower: %d, upper: %d\n", + snr, new_threshold, + lower_snr_threshold, upper_snr_threshold); + + ret = ath6kl_wmi_send_snr_threshold_params(wmi, &cmd); + if (ret) { + ath6kl_err("unable to configure snr threshold\n"); + return -EIO; + } + + return 0; +} + +static int ath6kl_wmi_aplist_event_rx(struct wmi *wmi, u8 *datap, int len) +{ + u16 ap_info_entry_size; + struct wmi_aplist_event *ev = (struct wmi_aplist_event *) datap; + struct wmi_ap_info_v1 *ap_info_v1; + u8 index; + + if (len < sizeof(struct wmi_aplist_event) || + ev->ap_list_ver != APLIST_VER1) + return -EINVAL; + + ap_info_entry_size = sizeof(struct wmi_ap_info_v1); + ap_info_v1 = (struct wmi_ap_info_v1 *) ev->ap_list; + + ath6kl_dbg(ATH6KL_DBG_WMI, + "number of APs in aplist event: %d\n", ev->num_ap); + + if (len < (int) (sizeof(struct wmi_aplist_event) + + (ev->num_ap - 1) * ap_info_entry_size)) + return -EINVAL; + + /* AP list version 1 contents */ + for (index = 0; index < ev->num_ap; index++) { + ath6kl_dbg(ATH6KL_DBG_WMI, "AP#%d BSSID %pM Channel %d\n", + index, ap_info_v1->bssid, ap_info_v1->channel); + ap_info_v1++; + } + + return 0; +} + +int ath6kl_wmi_cmd_send(struct wmi *wmi, u8 if_idx, struct sk_buff *skb, + enum wmi_cmd_id cmd_id, enum wmi_sync_flag sync_flag) +{ + struct wmi_cmd_hdr *cmd_hdr; + enum htc_endpoint_id ep_id = wmi->ep_id; + int ret; + u16 info1; + + if (WARN_ON(skb == NULL || + (if_idx > (wmi->parent_dev->vif_max - 1)))) { + dev_kfree_skb(skb); + return -EINVAL; + } + + ath6kl_dbg(ATH6KL_DBG_WMI, "wmi tx id %d len %d flag %d\n", + cmd_id, skb->len, sync_flag); + ath6kl_dbg_dump(ATH6KL_DBG_WMI_DUMP, NULL, "wmi tx ", + skb->data, skb->len); + + if (sync_flag >= END_WMIFLAG) { + dev_kfree_skb(skb); + return -EINVAL; + } + + if ((sync_flag == SYNC_BEFORE_WMIFLAG) || + (sync_flag == SYNC_BOTH_WMIFLAG)) { + /* + * Make sure all data currently queued is transmitted before + * the cmd execution. Establish a new sync point. + */ + ath6kl_wmi_sync_point(wmi, if_idx); + } + + skb_push(skb, sizeof(struct wmi_cmd_hdr)); + + cmd_hdr = (struct wmi_cmd_hdr *) skb->data; + cmd_hdr->cmd_id = cpu_to_le16(cmd_id); + info1 = if_idx & WMI_CMD_HDR_IF_ID_MASK; + cmd_hdr->info1 = cpu_to_le16(info1); + + /* Only for OPT_TX_CMD, use BE endpoint. */ + if (cmd_id == WMI_OPT_TX_FRAME_CMDID) { + ret = ath6kl_wmi_data_hdr_add(wmi, skb, OPT_MSGTYPE, + false, false, 0, NULL, if_idx); + if (ret) { + dev_kfree_skb(skb); + return ret; + } + ep_id = ath6kl_ac2_endpoint_id(wmi->parent_dev, WMM_AC_BE); + } + + ath6kl_control_tx(wmi->parent_dev, skb, ep_id); + + if ((sync_flag == SYNC_AFTER_WMIFLAG) || + (sync_flag == SYNC_BOTH_WMIFLAG)) { + /* + * Make sure all new data queued waits for the command to + * execute. Establish a new sync point. + */ + ath6kl_wmi_sync_point(wmi, if_idx); + } + + return 0; +} + +int ath6kl_wmi_connect_cmd(struct wmi *wmi, u8 if_idx, + enum network_type nw_type, + enum dot11_auth_mode dot11_auth_mode, + enum auth_mode auth_mode, + enum crypto_type pairwise_crypto, + u8 pairwise_crypto_len, + enum crypto_type group_crypto, + u8 group_crypto_len, int ssid_len, u8 *ssid, + u8 *bssid, u16 channel, u32 ctrl_flags, + u8 nw_subtype) +{ + struct sk_buff *skb; + struct wmi_connect_cmd *cc; + int ret; + + ath6kl_dbg(ATH6KL_DBG_WMI, + "wmi connect bssid %pM freq %d flags 0x%x ssid_len %d " + "type %d dot11_auth %d auth %d pairwise %d group %d\n", + bssid, channel, ctrl_flags, ssid_len, nw_type, + dot11_auth_mode, auth_mode, pairwise_crypto, group_crypto); + ath6kl_dbg_dump(ATH6KL_DBG_WMI, NULL, "ssid ", ssid, ssid_len); + + wmi->traffic_class = 100; + + if ((pairwise_crypto == NONE_CRYPT) && (group_crypto != NONE_CRYPT)) + return -EINVAL; + + if ((pairwise_crypto != NONE_CRYPT) && (group_crypto == NONE_CRYPT)) + return -EINVAL; + + skb = ath6kl_wmi_get_new_buf(sizeof(struct wmi_connect_cmd)); + if (!skb) + return -ENOMEM; + + cc = (struct wmi_connect_cmd *) skb->data; + + if (ssid_len) + memcpy(cc->ssid, ssid, ssid_len); + + cc->ssid_len = ssid_len; + cc->nw_type = nw_type; + cc->dot11_auth_mode = dot11_auth_mode; + cc->auth_mode = auth_mode; + cc->prwise_crypto_type = pairwise_crypto; + cc->prwise_crypto_len = pairwise_crypto_len; + cc->grp_crypto_type = group_crypto; + cc->grp_crypto_len = group_crypto_len; + cc->ch = cpu_to_le16(channel); + cc->ctrl_flags = cpu_to_le32(ctrl_flags); + cc->nw_subtype = nw_subtype; + + if (bssid != NULL) + memcpy(cc->bssid, bssid, ETH_ALEN); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_CONNECT_CMDID, + NO_SYNC_WMIFLAG); + + return ret; +} + +int ath6kl_wmi_reconnect_cmd(struct wmi *wmi, u8 if_idx, u8 *bssid, + u16 channel) +{ + struct sk_buff *skb; + struct wmi_reconnect_cmd *cc; + int ret; + + ath6kl_dbg(ATH6KL_DBG_WMI, "wmi reconnect bssid %pM freq %d\n", + bssid, channel); + + wmi->traffic_class = 100; + + skb = ath6kl_wmi_get_new_buf(sizeof(struct wmi_reconnect_cmd)); + if (!skb) + return -ENOMEM; + + cc = (struct wmi_reconnect_cmd *) skb->data; + cc->channel = cpu_to_le16(channel); + + if (bssid != NULL) + memcpy(cc->bssid, bssid, ETH_ALEN); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_RECONNECT_CMDID, + NO_SYNC_WMIFLAG); + + return ret; +} + +int ath6kl_wmi_disconnect_cmd(struct wmi *wmi, u8 if_idx) +{ + int ret; + + ath6kl_dbg(ATH6KL_DBG_WMI, "wmi disconnect\n"); + + wmi->traffic_class = 100; + + /* Disconnect command does not need to do a SYNC before. */ + ret = ath6kl_wmi_simple_cmd(wmi, if_idx, WMI_DISCONNECT_CMDID); + + return ret; +} + +/* ath6kl_wmi_start_scan_cmd is to be deprecated. Use + * ath6kl_wmi_begin_scan_cmd instead. The new function supports P2P + * mgmt operations using station interface. + */ +static int ath6kl_wmi_startscan_cmd(struct wmi *wmi, u8 if_idx, + enum wmi_scan_type scan_type, + u32 force_fgscan, u32 is_legacy, + u32 home_dwell_time, + u32 force_scan_interval, + s8 num_chan, u16 *ch_list) +{ + struct sk_buff *skb; + struct wmi_start_scan_cmd *sc; + s8 size; + int i, ret; + + size = sizeof(struct wmi_start_scan_cmd); + + if ((scan_type != WMI_LONG_SCAN) && (scan_type != WMI_SHORT_SCAN)) + return -EINVAL; + + if (num_chan > WMI_MAX_CHANNELS) + return -EINVAL; + + if (num_chan) + size += sizeof(u16) * (num_chan - 1); + + skb = ath6kl_wmi_get_new_buf(size); + if (!skb) + return -ENOMEM; + + sc = (struct wmi_start_scan_cmd *) skb->data; + sc->scan_type = scan_type; + sc->force_fg_scan = cpu_to_le32(force_fgscan); + sc->is_legacy = cpu_to_le32(is_legacy); + sc->home_dwell_time = cpu_to_le32(home_dwell_time); + sc->force_scan_intvl = cpu_to_le32(force_scan_interval); + sc->num_ch = num_chan; + + for (i = 0; i < num_chan; i++) + sc->ch_list[i] = cpu_to_le16(ch_list[i]); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_START_SCAN_CMDID, + NO_SYNC_WMIFLAG); + + return ret; +} + +/* + * beginscan supports (compared to old startscan) P2P mgmt operations using + * station interface, send additional information like supported rates to + * advertise and xmit rates for probe requests + */ +int ath6kl_wmi_beginscan_cmd(struct wmi *wmi, u8 if_idx, + enum wmi_scan_type scan_type, + u32 force_fgscan, u32 is_legacy, + u32 home_dwell_time, u32 force_scan_interval, + s8 num_chan, u16 *ch_list, u32 no_cck, u32 *rates) +{ + struct ieee80211_supported_band *sband; + struct sk_buff *skb; + struct wmi_begin_scan_cmd *sc; + s8 size, *supp_rates; + int i, band, ret; + struct ath6kl *ar = wmi->parent_dev; + int num_rates; + u32 ratemask; + + if (!test_bit(ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX, + ar->fw_capabilities)) { + return ath6kl_wmi_startscan_cmd(wmi, if_idx, + scan_type, force_fgscan, + is_legacy, home_dwell_time, + force_scan_interval, + num_chan, ch_list); + } + + size = sizeof(struct wmi_begin_scan_cmd); + + if ((scan_type != WMI_LONG_SCAN) && (scan_type != WMI_SHORT_SCAN)) + return -EINVAL; + + if (num_chan > WMI_MAX_CHANNELS) + return -EINVAL; + + if (num_chan) + size += sizeof(u16) * (num_chan - 1); + + skb = ath6kl_wmi_get_new_buf(size); + if (!skb) + return -ENOMEM; + + sc = (struct wmi_begin_scan_cmd *) skb->data; + sc->scan_type = scan_type; + sc->force_fg_scan = cpu_to_le32(force_fgscan); + sc->is_legacy = cpu_to_le32(is_legacy); + sc->home_dwell_time = cpu_to_le32(home_dwell_time); + sc->force_scan_intvl = cpu_to_le32(force_scan_interval); + sc->no_cck = cpu_to_le32(no_cck); + sc->num_ch = num_chan; + + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { + sband = ar->wiphy->bands[band]; + + if (!sband) + continue; + + if (WARN_ON(band >= ATH6KL_NUM_BANDS)) + break; + + ratemask = rates[band]; + supp_rates = sc->supp_rates[band].rates; + num_rates = 0; + + for (i = 0; i < sband->n_bitrates; i++) { + if ((BIT(i) & ratemask) == 0) + continue; /* skip rate */ + supp_rates[num_rates++] = + (u8) (sband->bitrates[i].bitrate / 5); + } + sc->supp_rates[band].nrates = num_rates; + } + + for (i = 0; i < num_chan; i++) + sc->ch_list[i] = cpu_to_le16(ch_list[i]); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_BEGIN_SCAN_CMDID, + NO_SYNC_WMIFLAG); + + return ret; +} + +int ath6kl_wmi_enable_sched_scan_cmd(struct wmi *wmi, u8 if_idx, bool enable) +{ + struct sk_buff *skb; + struct wmi_enable_sched_scan_cmd *sc; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*sc)); + if (!skb) + return -ENOMEM; + + ath6kl_dbg(ATH6KL_DBG_WMI, "%s scheduled scan on vif %d\n", + enable ? "enabling" : "disabling", if_idx); + sc = (struct wmi_enable_sched_scan_cmd *) skb->data; + sc->enable = enable ? 1 : 0; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, + WMI_ENABLE_SCHED_SCAN_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_scanparams_cmd(struct wmi *wmi, u8 if_idx, + u16 fg_start_sec, + u16 fg_end_sec, u16 bg_sec, + u16 minact_chdw_msec, u16 maxact_chdw_msec, + u16 pas_chdw_msec, u8 short_scan_ratio, + u8 scan_ctrl_flag, u32 max_dfsch_act_time, + u16 maxact_scan_per_ssid) +{ + struct sk_buff *skb; + struct wmi_scan_params_cmd *sc; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*sc)); + if (!skb) + return -ENOMEM; + + sc = (struct wmi_scan_params_cmd *) skb->data; + sc->fg_start_period = cpu_to_le16(fg_start_sec); + sc->fg_end_period = cpu_to_le16(fg_end_sec); + sc->bg_period = cpu_to_le16(bg_sec); + sc->minact_chdwell_time = cpu_to_le16(minact_chdw_msec); + sc->maxact_chdwell_time = cpu_to_le16(maxact_chdw_msec); + sc->pas_chdwell_time = cpu_to_le16(pas_chdw_msec); + sc->short_scan_ratio = short_scan_ratio; + sc->scan_ctrl_flags = scan_ctrl_flag; + sc->max_dfsch_act_time = cpu_to_le32(max_dfsch_act_time); + sc->maxact_scan_per_ssid = cpu_to_le16(maxact_scan_per_ssid); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_SCAN_PARAMS_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_bssfilter_cmd(struct wmi *wmi, u8 if_idx, u8 filter, u32 ie_mask) +{ + struct sk_buff *skb; + struct wmi_bss_filter_cmd *cmd; + int ret; + + if (filter >= LAST_BSS_FILTER) + return -EINVAL; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_bss_filter_cmd *) skb->data; + cmd->bss_filter = filter; + cmd->ie_mask = cpu_to_le32(ie_mask); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_BSS_FILTER_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_probedssid_cmd(struct wmi *wmi, u8 if_idx, u8 index, u8 flag, + u8 ssid_len, u8 *ssid) +{ + struct sk_buff *skb; + struct wmi_probed_ssid_cmd *cmd; + int ret; + + if (index >= MAX_PROBED_SSIDS) + return -EINVAL; + + if (ssid_len > sizeof(cmd->ssid)) + return -EINVAL; + + if ((flag & (DISABLE_SSID_FLAG | ANY_SSID_FLAG)) && (ssid_len > 0)) + return -EINVAL; + + if ((flag & SPECIFIC_SSID_FLAG) && !ssid_len) + return -EINVAL; + + if (flag & SPECIFIC_SSID_FLAG) + wmi->is_probe_ssid = true; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_probed_ssid_cmd *) skb->data; + cmd->entry_index = index; + cmd->flag = flag; + cmd->ssid_len = ssid_len; + memcpy(cmd->ssid, ssid, ssid_len); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_PROBED_SSID_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_listeninterval_cmd(struct wmi *wmi, u8 if_idx, + u16 listen_interval, + u16 listen_beacons) +{ + struct sk_buff *skb; + struct wmi_listen_int_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_listen_int_cmd *) skb->data; + cmd->listen_intvl = cpu_to_le16(listen_interval); + cmd->num_beacons = cpu_to_le16(listen_beacons); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_LISTEN_INT_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_bmisstime_cmd(struct wmi *wmi, u8 if_idx, + u16 bmiss_time, u16 num_beacons) +{ + struct sk_buff *skb; + struct wmi_bmiss_time_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_bmiss_time_cmd *) skb->data; + cmd->bmiss_time = cpu_to_le16(bmiss_time); + cmd->num_beacons = cpu_to_le16(num_beacons); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_BMISS_TIME_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_powermode_cmd(struct wmi *wmi, u8 if_idx, u8 pwr_mode) +{ + struct sk_buff *skb; + struct wmi_power_mode_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_power_mode_cmd *) skb->data; + cmd->pwr_mode = pwr_mode; + wmi->pwr_mode = pwr_mode; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_POWER_MODE_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_pmparams_cmd(struct wmi *wmi, u8 if_idx, u16 idle_period, + u16 ps_poll_num, u16 dtim_policy, + u16 tx_wakeup_policy, u16 num_tx_to_wakeup, + u16 ps_fail_event_policy) +{ + struct sk_buff *skb; + struct wmi_power_params_cmd *pm; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*pm)); + if (!skb) + return -ENOMEM; + + pm = (struct wmi_power_params_cmd *)skb->data; + pm->idle_period = cpu_to_le16(idle_period); + pm->pspoll_number = cpu_to_le16(ps_poll_num); + pm->dtim_policy = cpu_to_le16(dtim_policy); + pm->tx_wakeup_policy = cpu_to_le16(tx_wakeup_policy); + pm->num_tx_to_wakeup = cpu_to_le16(num_tx_to_wakeup); + pm->ps_fail_event_policy = cpu_to_le16(ps_fail_event_policy); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_POWER_PARAMS_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_disctimeout_cmd(struct wmi *wmi, u8 if_idx, u8 timeout) +{ + struct sk_buff *skb; + struct wmi_disc_timeout_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_disc_timeout_cmd *) skb->data; + cmd->discon_timeout = timeout; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_DISC_TIMEOUT_CMDID, + NO_SYNC_WMIFLAG); + + if (ret == 0) + ath6kl_debug_set_disconnect_timeout(wmi->parent_dev, timeout); + + return ret; +} + +int ath6kl_wmi_addkey_cmd(struct wmi *wmi, u8 if_idx, u8 key_index, + enum crypto_type key_type, + u8 key_usage, u8 key_len, + u8 *key_rsc, unsigned int key_rsc_len, + u8 *key_material, + u8 key_op_ctrl, u8 *mac_addr, + enum wmi_sync_flag sync_flag) +{ + struct sk_buff *skb; + struct wmi_add_cipher_key_cmd *cmd; + int ret; + + ath6kl_dbg(ATH6KL_DBG_WMI, + "addkey cmd: key_index=%u key_type=%d key_usage=%d key_len=%d key_op_ctrl=%d\n", + key_index, key_type, key_usage, key_len, key_op_ctrl); + + if ((key_index > WMI_MAX_KEY_INDEX) || (key_len > WMI_MAX_KEY_LEN) || + (key_material == NULL) || key_rsc_len > 8) + return -EINVAL; + + if ((WEP_CRYPT != key_type) && (NULL == key_rsc)) + return -EINVAL; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_add_cipher_key_cmd *) skb->data; + cmd->key_index = key_index; + cmd->key_type = key_type; + cmd->key_usage = key_usage; + cmd->key_len = key_len; + memcpy(cmd->key, key_material, key_len); + + if (key_rsc != NULL) + memcpy(cmd->key_rsc, key_rsc, key_rsc_len); + + cmd->key_op_ctrl = key_op_ctrl; + + if (mac_addr) + memcpy(cmd->key_mac_addr, mac_addr, ETH_ALEN); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_ADD_CIPHER_KEY_CMDID, + sync_flag); + + return ret; +} + +int ath6kl_wmi_add_krk_cmd(struct wmi *wmi, u8 if_idx, u8 *krk) +{ + struct sk_buff *skb; + struct wmi_add_krk_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_add_krk_cmd *) skb->data; + memcpy(cmd->krk, krk, WMI_KRK_LEN); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_ADD_KRK_CMDID, + NO_SYNC_WMIFLAG); + + return ret; +} + +int ath6kl_wmi_deletekey_cmd(struct wmi *wmi, u8 if_idx, u8 key_index) +{ + struct sk_buff *skb; + struct wmi_delete_cipher_key_cmd *cmd; + int ret; + + if (key_index > WMI_MAX_KEY_INDEX) + return -EINVAL; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_delete_cipher_key_cmd *) skb->data; + cmd->key_index = key_index; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_DELETE_CIPHER_KEY_CMDID, + NO_SYNC_WMIFLAG); + + return ret; +} + +int ath6kl_wmi_setpmkid_cmd(struct wmi *wmi, u8 if_idx, const u8 *bssid, + const u8 *pmkid, bool set) +{ + struct sk_buff *skb; + struct wmi_setpmkid_cmd *cmd; + int ret; + + if (bssid == NULL) + return -EINVAL; + + if (set && pmkid == NULL) + return -EINVAL; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_setpmkid_cmd *) skb->data; + memcpy(cmd->bssid, bssid, ETH_ALEN); + if (set) { + memcpy(cmd->pmkid, pmkid, sizeof(cmd->pmkid)); + cmd->enable = PMKID_ENABLE; + } else { + memset(cmd->pmkid, 0, sizeof(cmd->pmkid)); + cmd->enable = PMKID_DISABLE; + } + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_PMKID_CMDID, + NO_SYNC_WMIFLAG); + + return ret; +} + +static int ath6kl_wmi_data_sync_send(struct wmi *wmi, struct sk_buff *skb, + enum htc_endpoint_id ep_id, u8 if_idx) +{ + struct wmi_data_hdr *data_hdr; + int ret; + + if (WARN_ON(skb == NULL || ep_id == wmi->ep_id)) { + dev_kfree_skb(skb); + return -EINVAL; + } + + skb_push(skb, sizeof(struct wmi_data_hdr)); + + data_hdr = (struct wmi_data_hdr *) skb->data; + data_hdr->info = SYNC_MSGTYPE << WMI_DATA_HDR_MSG_TYPE_SHIFT; + data_hdr->info3 = cpu_to_le16(if_idx & WMI_DATA_HDR_IF_IDX_MASK); + + ret = ath6kl_control_tx(wmi->parent_dev, skb, ep_id); + + return ret; +} + +static int ath6kl_wmi_sync_point(struct wmi *wmi, u8 if_idx) +{ + struct sk_buff *skb; + struct wmi_sync_cmd *cmd; + struct wmi_data_sync_bufs data_sync_bufs[WMM_NUM_AC]; + enum htc_endpoint_id ep_id; + u8 index, num_pri_streams = 0; + int ret = 0; + + memset(data_sync_bufs, 0, sizeof(data_sync_bufs)); + + spin_lock_bh(&wmi->lock); + + for (index = 0; index < WMM_NUM_AC; index++) { + if (wmi->fat_pipe_exist & (1 << index)) { + num_pri_streams++; + data_sync_bufs[num_pri_streams - 1].traffic_class = + index; + } + } + + spin_unlock_bh(&wmi->lock); + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_sync_cmd *) skb->data; + + /* + * In the SYNC cmd sent on the control Ep, send a bitmap + * of the data eps on which the Data Sync will be sent + */ + cmd->data_sync_map = wmi->fat_pipe_exist; + + for (index = 0; index < num_pri_streams; index++) { + data_sync_bufs[index].skb = ath6kl_buf_alloc(0); + if (data_sync_bufs[index].skb == NULL) { + ret = -ENOMEM; + break; + } + } + + /* + * If buffer allocation for any of the dataSync fails, + * then do not send the Synchronize cmd on the control ep + */ + if (ret) + goto free_cmd_skb; + + /* + * Send sync cmd followed by sync data messages on all + * endpoints being used + */ + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SYNCHRONIZE_CMDID, + NO_SYNC_WMIFLAG); + + if (ret) + goto free_data_skb; + + for (index = 0; index < num_pri_streams; index++) { + + if (WARN_ON(!data_sync_bufs[index].skb)) + goto free_data_skb; + + ep_id = ath6kl_ac2_endpoint_id(wmi->parent_dev, + data_sync_bufs[index]. + traffic_class); + ret = + ath6kl_wmi_data_sync_send(wmi, data_sync_bufs[index].skb, + ep_id, if_idx); + + data_sync_bufs[index].skb = NULL; + + if (ret) + goto free_data_skb; + } + + return 0; + +free_cmd_skb: + /* free up any resources left over (possibly due to an error) */ + dev_kfree_skb(skb); + +free_data_skb: + for (index = 0; index < num_pri_streams; index++) + dev_kfree_skb((struct sk_buff *)data_sync_bufs[index].skb); + + return ret; +} + +int ath6kl_wmi_create_pstream_cmd(struct wmi *wmi, u8 if_idx, + struct wmi_create_pstream_cmd *params) +{ + struct sk_buff *skb; + struct wmi_create_pstream_cmd *cmd; + u8 fatpipe_exist_for_ac = 0; + s32 min_phy = 0; + s32 nominal_phy = 0; + int ret; + + if (!((params->user_pri < 8) && + (params->user_pri <= 0x7) && + (up_to_ac[params->user_pri & 0x7] == params->traffic_class) && + (params->traffic_direc == UPLINK_TRAFFIC || + params->traffic_direc == DNLINK_TRAFFIC || + params->traffic_direc == BIDIR_TRAFFIC) && + (params->traffic_type == TRAFFIC_TYPE_APERIODIC || + params->traffic_type == TRAFFIC_TYPE_PERIODIC) && + (params->voice_psc_cap == DISABLE_FOR_THIS_AC || + params->voice_psc_cap == ENABLE_FOR_THIS_AC || + params->voice_psc_cap == ENABLE_FOR_ALL_AC) && + (params->tsid == WMI_IMPLICIT_PSTREAM || + params->tsid <= WMI_MAX_THINSTREAM))) { + return -EINVAL; + } + + /* + * Check nominal PHY rate is >= minimalPHY, + * so that DUT can allow TSRS IE + */ + + /* Get the physical rate (units of bps) */ + min_phy = ((le32_to_cpu(params->min_phy_rate) / 1000) / 1000); + + /* Check minimal phy < nominal phy rate */ + if (params->nominal_phy >= min_phy) { + /* unit of 500 kbps */ + nominal_phy = (params->nominal_phy * 1000) / 500; + ath6kl_dbg(ATH6KL_DBG_WMI, + "TSRS IE enabled::MinPhy %x->NominalPhy ===> %x\n", + min_phy, nominal_phy); + + params->nominal_phy = nominal_phy; + } else { + params->nominal_phy = 0; + } + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + ath6kl_dbg(ATH6KL_DBG_WMI, + "sending create_pstream_cmd: ac=%d tsid:%d\n", + params->traffic_class, params->tsid); + + cmd = (struct wmi_create_pstream_cmd *) skb->data; + memcpy(cmd, params, sizeof(*cmd)); + + /* This is an implicitly created Fat pipe */ + if ((u32) params->tsid == (u32) WMI_IMPLICIT_PSTREAM) { + spin_lock_bh(&wmi->lock); + fatpipe_exist_for_ac = (wmi->fat_pipe_exist & + (1 << params->traffic_class)); + wmi->fat_pipe_exist |= (1 << params->traffic_class); + spin_unlock_bh(&wmi->lock); + } else { + /* explicitly created thin stream within a fat pipe */ + spin_lock_bh(&wmi->lock); + fatpipe_exist_for_ac = (wmi->fat_pipe_exist & + (1 << params->traffic_class)); + wmi->stream_exist_for_ac[params->traffic_class] |= + (1 << params->tsid); + /* + * If a thinstream becomes active, the fat pipe automatically + * becomes active + */ + wmi->fat_pipe_exist |= (1 << params->traffic_class); + spin_unlock_bh(&wmi->lock); + } + + /* + * Indicate activty change to driver layer only if this is the + * first TSID to get created in this AC explicitly or an implicit + * fat pipe is getting created. + */ + if (!fatpipe_exist_for_ac) + ath6kl_indicate_tx_activity(wmi->parent_dev, + params->traffic_class, true); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_CREATE_PSTREAM_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_delete_pstream_cmd(struct wmi *wmi, u8 if_idx, u8 traffic_class, + u8 tsid) +{ + struct sk_buff *skb; + struct wmi_delete_pstream_cmd *cmd; + u16 active_tsids = 0; + int ret; + + if (traffic_class > 3) { + ath6kl_err("invalid traffic class: %d\n", traffic_class); + return -EINVAL; + } + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_delete_pstream_cmd *) skb->data; + cmd->traffic_class = traffic_class; + cmd->tsid = tsid; + + spin_lock_bh(&wmi->lock); + active_tsids = wmi->stream_exist_for_ac[traffic_class]; + spin_unlock_bh(&wmi->lock); + + if (!(active_tsids & (1 << tsid))) { + dev_kfree_skb(skb); + ath6kl_dbg(ATH6KL_DBG_WMI, + "TSID %d doesn't exist for traffic class: %d\n", + tsid, traffic_class); + return -ENODATA; + } + + ath6kl_dbg(ATH6KL_DBG_WMI, + "sending delete_pstream_cmd: traffic class: %d tsid=%d\n", + traffic_class, tsid); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_DELETE_PSTREAM_CMDID, + SYNC_BEFORE_WMIFLAG); + + spin_lock_bh(&wmi->lock); + wmi->stream_exist_for_ac[traffic_class] &= ~(1 << tsid); + active_tsids = wmi->stream_exist_for_ac[traffic_class]; + spin_unlock_bh(&wmi->lock); + + /* + * Indicate stream inactivity to driver layer only if all tsids + * within this AC are deleted. + */ + if (!active_tsids) { + ath6kl_indicate_tx_activity(wmi->parent_dev, + traffic_class, false); + wmi->fat_pipe_exist &= ~(1 << traffic_class); + } + + return ret; +} + +int ath6kl_wmi_set_ip_cmd(struct wmi *wmi, u8 if_idx, + __be32 ips0, __be32 ips1) +{ + struct sk_buff *skb; + struct wmi_set_ip_cmd *cmd; + int ret; + + /* Multicast address are not valid */ + if (ipv4_is_multicast(ips0) || + ipv4_is_multicast(ips1)) + return -EINVAL; + + skb = ath6kl_wmi_get_new_buf(sizeof(struct wmi_set_ip_cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_ip_cmd *) skb->data; + cmd->ips[0] = ips0; + cmd->ips[1] = ips1; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_IP_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +static void ath6kl_wmi_relinquish_implicit_pstream_credits(struct wmi *wmi) +{ + u16 active_tsids; + u8 stream_exist; + int i; + + /* + * Relinquish credits from all implicitly created pstreams + * since when we go to sleep. If user created explicit + * thinstreams exists with in a fatpipe leave them intact + * for the user to delete. + */ + spin_lock_bh(&wmi->lock); + stream_exist = wmi->fat_pipe_exist; + spin_unlock_bh(&wmi->lock); + + for (i = 0; i < WMM_NUM_AC; i++) { + if (stream_exist & (1 << i)) { + + /* + * FIXME: Is this lock & unlock inside + * for loop correct? may need rework. + */ + spin_lock_bh(&wmi->lock); + active_tsids = wmi->stream_exist_for_ac[i]; + spin_unlock_bh(&wmi->lock); + + /* + * If there are no user created thin streams + * delete the fatpipe + */ + if (!active_tsids) { + stream_exist &= ~(1 << i); + /* + * Indicate inactivity to driver layer for + * this fatpipe (pstream) + */ + ath6kl_indicate_tx_activity(wmi->parent_dev, + i, false); + } + } + } + + /* FIXME: Can we do this assignment without locking ? */ + spin_lock_bh(&wmi->lock); + wmi->fat_pipe_exist = stream_exist; + spin_unlock_bh(&wmi->lock); +} + +static int ath6kl_set_bitrate_mask64(struct wmi *wmi, u8 if_idx, + const struct cfg80211_bitrate_mask *mask) +{ + struct sk_buff *skb; + int ret, mode, band; + u64 mcsrate, ratemask[ATH6KL_NUM_BANDS]; + struct wmi_set_tx_select_rates64_cmd *cmd; + + memset(&ratemask, 0, sizeof(ratemask)); + + /* only check 2.4 and 5 GHz bands, skip the rest */ + for (band = 0; band <= IEEE80211_BAND_5GHZ; band++) { + /* copy legacy rate mask */ + ratemask[band] = mask->control[band].legacy; + if (band == IEEE80211_BAND_5GHZ) + ratemask[band] = + mask->control[band].legacy << 4; + + /* copy mcs rate mask */ + mcsrate = mask->control[band].mcs[1]; + mcsrate <<= 8; + mcsrate |= mask->control[band].mcs[0]; + ratemask[band] |= mcsrate << 12; + ratemask[band] |= mcsrate << 28; + } + + ath6kl_dbg(ATH6KL_DBG_WMI, + "Ratemask 64 bit: 2.4:%llx 5:%llx\n", + ratemask[0], ratemask[1]); + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd) * WMI_RATES_MODE_MAX); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_tx_select_rates64_cmd *) skb->data; + for (mode = 0; mode < WMI_RATES_MODE_MAX; mode++) { + /* A mode operate in 5GHZ band */ + if (mode == WMI_RATES_MODE_11A || + mode == WMI_RATES_MODE_11A_HT20 || + mode == WMI_RATES_MODE_11A_HT40) + band = IEEE80211_BAND_5GHZ; + else + band = IEEE80211_BAND_2GHZ; + cmd->ratemask[mode] = cpu_to_le64(ratemask[band]); + } + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, + WMI_SET_TX_SELECT_RATES_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +static int ath6kl_set_bitrate_mask32(struct wmi *wmi, u8 if_idx, + const struct cfg80211_bitrate_mask *mask) +{ + struct sk_buff *skb; + int ret, mode, band; + u32 mcsrate, ratemask[ATH6KL_NUM_BANDS]; + struct wmi_set_tx_select_rates32_cmd *cmd; + + memset(&ratemask, 0, sizeof(ratemask)); + + /* only check 2.4 and 5 GHz bands, skip the rest */ + for (band = 0; band <= IEEE80211_BAND_5GHZ; band++) { + /* copy legacy rate mask */ + ratemask[band] = mask->control[band].legacy; + if (band == IEEE80211_BAND_5GHZ) + ratemask[band] = + mask->control[band].legacy << 4; + + /* copy mcs rate mask */ + mcsrate = mask->control[band].mcs[0]; + ratemask[band] |= mcsrate << 12; + ratemask[band] |= mcsrate << 20; + } + + ath6kl_dbg(ATH6KL_DBG_WMI, + "Ratemask 32 bit: 2.4:%x 5:%x\n", + ratemask[0], ratemask[1]); + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd) * WMI_RATES_MODE_MAX); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_tx_select_rates32_cmd *) skb->data; + for (mode = 0; mode < WMI_RATES_MODE_MAX; mode++) { + /* A mode operate in 5GHZ band */ + if (mode == WMI_RATES_MODE_11A || + mode == WMI_RATES_MODE_11A_HT20 || + mode == WMI_RATES_MODE_11A_HT40) + band = IEEE80211_BAND_5GHZ; + else + band = IEEE80211_BAND_2GHZ; + cmd->ratemask[mode] = cpu_to_le32(ratemask[band]); + } + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, + WMI_SET_TX_SELECT_RATES_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_set_bitrate_mask(struct wmi *wmi, u8 if_idx, + const struct cfg80211_bitrate_mask *mask) +{ + struct ath6kl *ar = wmi->parent_dev; + + if (ar->hw.flags & ATH6KL_HW_64BIT_RATES) + return ath6kl_set_bitrate_mask64(wmi, if_idx, mask); + else + return ath6kl_set_bitrate_mask32(wmi, if_idx, mask); +} + +int ath6kl_wmi_set_host_sleep_mode_cmd(struct wmi *wmi, u8 if_idx, + enum ath6kl_host_mode host_mode) +{ + struct sk_buff *skb; + struct wmi_set_host_sleep_mode_cmd *cmd; + int ret; + + if ((host_mode != ATH6KL_HOST_MODE_ASLEEP) && + (host_mode != ATH6KL_HOST_MODE_AWAKE)) { + ath6kl_err("invalid host sleep mode: %d\n", host_mode); + return -EINVAL; + } + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_host_sleep_mode_cmd *) skb->data; + + if (host_mode == ATH6KL_HOST_MODE_ASLEEP) { + ath6kl_wmi_relinquish_implicit_pstream_credits(wmi); + cmd->asleep = cpu_to_le32(1); + } else + cmd->awake = cpu_to_le32(1); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, + WMI_SET_HOST_SLEEP_MODE_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +/* This command has zero length payload */ +static int ath6kl_wmi_host_sleep_mode_cmd_prcd_evt_rx(struct wmi *wmi, + struct ath6kl_vif *vif) +{ + struct ath6kl *ar = wmi->parent_dev; + + set_bit(HOST_SLEEP_MODE_CMD_PROCESSED, &vif->flags); + wake_up(&ar->event_wq); + + return 0; +} + +int ath6kl_wmi_set_wow_mode_cmd(struct wmi *wmi, u8 if_idx, + enum ath6kl_wow_mode wow_mode, + u32 filter, u16 host_req_delay) +{ + struct sk_buff *skb; + struct wmi_set_wow_mode_cmd *cmd; + int ret; + + if ((wow_mode != ATH6KL_WOW_MODE_ENABLE) && + wow_mode != ATH6KL_WOW_MODE_DISABLE) { + ath6kl_err("invalid wow mode: %d\n", wow_mode); + return -EINVAL; + } + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_wow_mode_cmd *) skb->data; + cmd->enable_wow = cpu_to_le32(wow_mode); + cmd->filter = cpu_to_le32(filter); + cmd->host_req_delay = cpu_to_le16(host_req_delay); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_WOW_MODE_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_add_wow_pattern_cmd(struct wmi *wmi, u8 if_idx, + u8 list_id, u8 filter_size, + u8 filter_offset, const u8 *filter, + const u8 *mask) +{ + struct sk_buff *skb; + struct wmi_add_wow_pattern_cmd *cmd; + u16 size; + u8 *filter_mask; + int ret; + + /* + * Allocate additional memory in the buffer to hold + * filter and mask value, which is twice of filter_size. + */ + size = sizeof(*cmd) + (2 * filter_size); + + skb = ath6kl_wmi_get_new_buf(size); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_add_wow_pattern_cmd *) skb->data; + cmd->filter_list_id = list_id; + cmd->filter_size = filter_size; + cmd->filter_offset = filter_offset; + + memcpy(cmd->filter, filter, filter_size); + + filter_mask = (u8 *) (cmd->filter + filter_size); + memcpy(filter_mask, mask, filter_size); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_ADD_WOW_PATTERN_CMDID, + NO_SYNC_WMIFLAG); + + return ret; +} + +int ath6kl_wmi_del_wow_pattern_cmd(struct wmi *wmi, u8 if_idx, + u16 list_id, u16 filter_id) +{ + struct sk_buff *skb; + struct wmi_del_wow_pattern_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_del_wow_pattern_cmd *) skb->data; + cmd->filter_list_id = cpu_to_le16(list_id); + cmd->filter_id = cpu_to_le16(filter_id); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_DEL_WOW_PATTERN_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +static int ath6kl_wmi_cmd_send_xtnd(struct wmi *wmi, struct sk_buff *skb, + enum wmix_command_id cmd_id, + enum wmi_sync_flag sync_flag) +{ + struct wmix_cmd_hdr *cmd_hdr; + int ret; + + skb_push(skb, sizeof(struct wmix_cmd_hdr)); + + cmd_hdr = (struct wmix_cmd_hdr *) skb->data; + cmd_hdr->cmd_id = cpu_to_le32(cmd_id); + + ret = ath6kl_wmi_cmd_send(wmi, 0, skb, WMI_EXTENSION_CMDID, sync_flag); + + return ret; +} + +int ath6kl_wmi_get_challenge_resp_cmd(struct wmi *wmi, u32 cookie, u32 source) +{ + struct sk_buff *skb; + struct wmix_hb_challenge_resp_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmix_hb_challenge_resp_cmd *) skb->data; + cmd->cookie = cpu_to_le32(cookie); + cmd->source = cpu_to_le32(source); + + ret = ath6kl_wmi_cmd_send_xtnd(wmi, skb, WMIX_HB_CHALLENGE_RESP_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_config_debug_module_cmd(struct wmi *wmi, u32 valid, u32 config) +{ + struct ath6kl_wmix_dbglog_cfg_module_cmd *cmd; + struct sk_buff *skb; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct ath6kl_wmix_dbglog_cfg_module_cmd *) skb->data; + cmd->valid = cpu_to_le32(valid); + cmd->config = cpu_to_le32(config); + + ret = ath6kl_wmi_cmd_send_xtnd(wmi, skb, WMIX_DBGLOG_CFG_MODULE_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_get_stats_cmd(struct wmi *wmi, u8 if_idx) +{ + return ath6kl_wmi_simple_cmd(wmi, if_idx, WMI_GET_STATISTICS_CMDID); +} + +int ath6kl_wmi_set_tx_pwr_cmd(struct wmi *wmi, u8 if_idx, u8 dbM) +{ + struct sk_buff *skb; + struct wmi_set_tx_pwr_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(struct wmi_set_tx_pwr_cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_tx_pwr_cmd *) skb->data; + cmd->dbM = dbM; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_TX_PWR_CMDID, + NO_SYNC_WMIFLAG); + + return ret; +} + +int ath6kl_wmi_get_tx_pwr_cmd(struct wmi *wmi, u8 if_idx) +{ + return ath6kl_wmi_simple_cmd(wmi, if_idx, WMI_GET_TX_PWR_CMDID); +} + +int ath6kl_wmi_get_roam_tbl_cmd(struct wmi *wmi) +{ + return ath6kl_wmi_simple_cmd(wmi, 0, WMI_GET_ROAM_TBL_CMDID); +} + +int ath6kl_wmi_set_lpreamble_cmd(struct wmi *wmi, u8 if_idx, u8 status, + u8 preamble_policy) +{ + struct sk_buff *skb; + struct wmi_set_lpreamble_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(struct wmi_set_lpreamble_cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_lpreamble_cmd *) skb->data; + cmd->status = status; + cmd->preamble_policy = preamble_policy; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_LPREAMBLE_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_set_rts_cmd(struct wmi *wmi, u16 threshold) +{ + struct sk_buff *skb; + struct wmi_set_rts_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(struct wmi_set_rts_cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_rts_cmd *) skb->data; + cmd->threshold = cpu_to_le16(threshold); + + ret = ath6kl_wmi_cmd_send(wmi, 0, skb, WMI_SET_RTS_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_set_wmm_txop(struct wmi *wmi, u8 if_idx, enum wmi_txop_cfg cfg) +{ + struct sk_buff *skb; + struct wmi_set_wmm_txop_cmd *cmd; + int ret; + + if (!((cfg == WMI_TXOP_DISABLED) || (cfg == WMI_TXOP_ENABLED))) + return -EINVAL; + + skb = ath6kl_wmi_get_new_buf(sizeof(struct wmi_set_wmm_txop_cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_wmm_txop_cmd *) skb->data; + cmd->txop_enable = cfg; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_WMM_TXOP_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_set_keepalive_cmd(struct wmi *wmi, u8 if_idx, + u8 keep_alive_intvl) +{ + struct sk_buff *skb; + struct wmi_set_keepalive_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_keepalive_cmd *) skb->data; + cmd->keep_alive_intvl = keep_alive_intvl; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_KEEPALIVE_CMDID, + NO_SYNC_WMIFLAG); + + if (ret == 0) + ath6kl_debug_set_keepalive(wmi->parent_dev, keep_alive_intvl); + + return ret; +} + +int ath6kl_wmi_set_htcap_cmd(struct wmi *wmi, u8 if_idx, + enum ieee80211_band band, + struct ath6kl_htcap *htcap) +{ + struct sk_buff *skb; + struct wmi_set_htcap_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_htcap_cmd *) skb->data; + + /* + * NOTE: Band in firmware matches enum ieee80211_band, it is unlikely + * this will be changed in firmware. If at all there is any change in + * band value, the host needs to be fixed. + */ + cmd->band = band; + cmd->ht_enable = !!htcap->ht_enable; + cmd->ht20_sgi = !!(htcap->cap_info & IEEE80211_HT_CAP_SGI_20); + cmd->ht40_supported = + !!(htcap->cap_info & IEEE80211_HT_CAP_SUP_WIDTH_20_40); + cmd->ht40_sgi = !!(htcap->cap_info & IEEE80211_HT_CAP_SGI_40); + cmd->intolerant_40mhz = + !!(htcap->cap_info & IEEE80211_HT_CAP_40MHZ_INTOLERANT); + cmd->max_ampdu_len_exp = htcap->ampdu_factor; + + ath6kl_dbg(ATH6KL_DBG_WMI, + "Set htcap: band:%d ht_enable:%d 40mhz:%d sgi_20mhz:%d sgi_40mhz:%d 40mhz_intolerant:%d ampdu_len_exp:%d\n", + cmd->band, cmd->ht_enable, cmd->ht40_supported, + cmd->ht20_sgi, cmd->ht40_sgi, cmd->intolerant_40mhz, + cmd->max_ampdu_len_exp); + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_HT_CAP_CMDID, + NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_test_cmd(struct wmi *wmi, void *buf, size_t len) +{ + struct sk_buff *skb; + int ret; + + skb = ath6kl_wmi_get_new_buf(len); + if (!skb) + return -ENOMEM; + + memcpy(skb->data, buf, len); + + ret = ath6kl_wmi_cmd_send(wmi, 0, skb, WMI_TEST_CMDID, NO_SYNC_WMIFLAG); + + return ret; +} + +int ath6kl_wmi_mcast_filter_cmd(struct wmi *wmi, u8 if_idx, bool mc_all_on) +{ + struct sk_buff *skb; + struct wmi_mcast_filter_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_mcast_filter_cmd *) skb->data; + cmd->mcast_all_enable = mc_all_on; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_MCAST_FILTER_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_add_del_mcast_filter_cmd(struct wmi *wmi, u8 if_idx, + u8 *filter, bool add_filter) +{ + struct sk_buff *skb; + struct wmi_mcast_filter_add_del_cmd *cmd; + int ret; + + if ((filter[0] != 0x33 || filter[1] != 0x33) && + (filter[0] != 0x01 || filter[1] != 0x00 || + filter[2] != 0x5e || filter[3] > 0x7f)) { + ath6kl_warn("invalid multicast filter address\n"); + return -EINVAL; + } + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_mcast_filter_add_del_cmd *) skb->data; + memcpy(cmd->mcast_mac, filter, ATH6KL_MCAST_FILTER_MAC_ADDR_SIZE); + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, + add_filter ? WMI_SET_MCAST_FILTER_CMDID : + WMI_DEL_MCAST_FILTER_CMDID, + NO_SYNC_WMIFLAG); + + return ret; +} + +int ath6kl_wmi_sta_bmiss_enhance_cmd(struct wmi *wmi, u8 if_idx, bool enhance) +{ + struct sk_buff *skb; + struct wmi_sta_bmiss_enhance_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_sta_bmiss_enhance_cmd *) skb->data; + cmd->enable = enhance ? 1 : 0; + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, + WMI_STA_BMISS_ENHANCE_CMDID, + NO_SYNC_WMIFLAG); + return ret; +} + +int ath6kl_wmi_set_regdomain_cmd(struct wmi *wmi, const char *alpha2) +{ + struct sk_buff *skb; + struct wmi_set_regdomain_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_regdomain_cmd *) skb->data; + memcpy(cmd->iso_name, alpha2, 2); + + return ath6kl_wmi_cmd_send(wmi, 0, skb, + WMI_SET_REGDOMAIN_CMDID, + NO_SYNC_WMIFLAG); +} + +s32 ath6kl_wmi_get_rate(s8 rate_index) +{ + u8 sgi = 0; + + if (rate_index == RATE_AUTO) + return 0; + + /* SGI is stored as the MSB of the rate_index */ + if (rate_index & RATE_INDEX_MSB) { + rate_index &= RATE_INDEX_WITHOUT_SGI_MASK; + sgi = 1; + } + + if (WARN_ON(rate_index > RATE_MCS_7_40)) + rate_index = RATE_MCS_7_40; + + return wmi_rate_tbl[(u32) rate_index][sgi]; +} + +static int ath6kl_wmi_get_pmkid_list_event_rx(struct wmi *wmi, u8 *datap, + u32 len) +{ + struct wmi_pmkid_list_reply *reply; + u32 expected_len; + + if (len < sizeof(struct wmi_pmkid_list_reply)) + return -EINVAL; + + reply = (struct wmi_pmkid_list_reply *)datap; + expected_len = sizeof(reply->num_pmkid) + + le32_to_cpu(reply->num_pmkid) * WMI_PMKID_LEN; + + if (len < expected_len) + return -EINVAL; + + return 0; +} + +static int ath6kl_wmi_addba_req_event_rx(struct wmi *wmi, u8 *datap, int len, + struct ath6kl_vif *vif) +{ + struct wmi_addba_req_event *cmd = (struct wmi_addba_req_event *) datap; + + aggr_recv_addba_req_evt(vif, cmd->tid, + le16_to_cpu(cmd->st_seq_no), cmd->win_sz); + + return 0; +} + +static int ath6kl_wmi_delba_req_event_rx(struct wmi *wmi, u8 *datap, int len, + struct ath6kl_vif *vif) +{ + struct wmi_delba_event *cmd = (struct wmi_delba_event *) datap; + + aggr_recv_delba_req_evt(vif, cmd->tid); + + return 0; +} + +/* AP mode functions */ + +int ath6kl_wmi_ap_profile_commit(struct wmi *wmip, u8 if_idx, + struct wmi_connect_cmd *p) +{ + struct sk_buff *skb; + struct wmi_connect_cmd *cm; + int res; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cm)); + if (!skb) + return -ENOMEM; + + cm = (struct wmi_connect_cmd *) skb->data; + memcpy(cm, p, sizeof(*cm)); + + res = ath6kl_wmi_cmd_send(wmip, if_idx, skb, WMI_AP_CONFIG_COMMIT_CMDID, + NO_SYNC_WMIFLAG); + ath6kl_dbg(ATH6KL_DBG_WMI, + "%s: nw_type=%u auth_mode=%u ch=%u ctrl_flags=0x%x-> res=%d\n", + __func__, p->nw_type, p->auth_mode, le16_to_cpu(p->ch), + le32_to_cpu(p->ctrl_flags), res); + return res; +} + +int ath6kl_wmi_ap_set_mlme(struct wmi *wmip, u8 if_idx, u8 cmd, const u8 *mac, + u16 reason) +{ + struct sk_buff *skb; + struct wmi_ap_set_mlme_cmd *cm; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cm)); + if (!skb) + return -ENOMEM; + + cm = (struct wmi_ap_set_mlme_cmd *) skb->data; + memcpy(cm->mac, mac, ETH_ALEN); + cm->reason = cpu_to_le16(reason); + cm->cmd = cmd; + + ath6kl_dbg(ATH6KL_DBG_WMI, "ap_set_mlme: cmd=%d reason=%d\n", cm->cmd, + cm->reason); + + return ath6kl_wmi_cmd_send(wmip, if_idx, skb, WMI_AP_SET_MLME_CMDID, + NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_ap_hidden_ssid(struct wmi *wmi, u8 if_idx, bool enable) +{ + struct sk_buff *skb; + struct wmi_ap_hidden_ssid_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_ap_hidden_ssid_cmd *) skb->data; + cmd->hidden_ssid = enable ? 1 : 0; + + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_AP_HIDDEN_SSID_CMDID, + NO_SYNC_WMIFLAG); +} + +/* This command will be used to enable/disable AP uAPSD feature */ +int ath6kl_wmi_ap_set_apsd(struct wmi *wmi, u8 if_idx, u8 enable) +{ + struct wmi_ap_set_apsd_cmd *cmd; + struct sk_buff *skb; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_ap_set_apsd_cmd *)skb->data; + cmd->enable = enable; + + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_AP_SET_APSD_CMDID, + NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_set_apsd_bfrd_traf(struct wmi *wmi, u8 if_idx, + u16 aid, u16 bitmap, u32 flags) +{ + struct wmi_ap_apsd_buffered_traffic_cmd *cmd; + struct sk_buff *skb; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_ap_apsd_buffered_traffic_cmd *)skb->data; + cmd->aid = cpu_to_le16(aid); + cmd->bitmap = cpu_to_le16(bitmap); + cmd->flags = cpu_to_le32(flags); + + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, + WMI_AP_APSD_BUFFERED_TRAFFIC_CMDID, + NO_SYNC_WMIFLAG); +} + +static int ath6kl_wmi_pspoll_event_rx(struct wmi *wmi, u8 *datap, int len, + struct ath6kl_vif *vif) +{ + struct wmi_pspoll_event *ev; + + if (len < sizeof(struct wmi_pspoll_event)) + return -EINVAL; + + ev = (struct wmi_pspoll_event *) datap; + + ath6kl_pspoll_event(vif, le16_to_cpu(ev->aid)); + + return 0; +} + +static int ath6kl_wmi_dtimexpiry_event_rx(struct wmi *wmi, u8 *datap, int len, + struct ath6kl_vif *vif) +{ + ath6kl_dtimexpiry_event(vif); + + return 0; +} + +int ath6kl_wmi_set_pvb_cmd(struct wmi *wmi, u8 if_idx, u16 aid, + bool flag) +{ + struct sk_buff *skb; + struct wmi_ap_set_pvb_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(struct wmi_ap_set_pvb_cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_ap_set_pvb_cmd *) skb->data; + cmd->aid = cpu_to_le16(aid); + cmd->rsvd = cpu_to_le16(0); + cmd->flag = cpu_to_le32(flag); + + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_AP_SET_PVB_CMDID, + NO_SYNC_WMIFLAG); + + return 0; +} + +int ath6kl_wmi_set_rx_frame_format_cmd(struct wmi *wmi, u8 if_idx, + u8 rx_meta_ver, + bool rx_dot11_hdr, bool defrag_on_host) +{ + struct sk_buff *skb; + struct wmi_rx_frame_format_cmd *cmd; + int ret; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_rx_frame_format_cmd *) skb->data; + cmd->dot11_hdr = rx_dot11_hdr ? 1 : 0; + cmd->defrag_on_host = defrag_on_host ? 1 : 0; + cmd->meta_ver = rx_meta_ver; + + /* Delete the local aggr state, on host */ + ret = ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_RX_FRAME_FORMAT_CMDID, + NO_SYNC_WMIFLAG); + + return ret; +} + +int ath6kl_wmi_set_appie_cmd(struct wmi *wmi, u8 if_idx, u8 mgmt_frm_type, + const u8 *ie, u8 ie_len) +{ + struct sk_buff *skb; + struct wmi_set_appie_cmd *p; + + skb = ath6kl_wmi_get_new_buf(sizeof(*p) + ie_len); + if (!skb) + return -ENOMEM; + + ath6kl_dbg(ATH6KL_DBG_WMI, + "set_appie_cmd: mgmt_frm_type=%u ie_len=%u\n", + mgmt_frm_type, ie_len); + p = (struct wmi_set_appie_cmd *) skb->data; + p->mgmt_frm_type = mgmt_frm_type; + p->ie_len = ie_len; + + if (ie != NULL && ie_len > 0) + memcpy(p->ie_info, ie, ie_len); + + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_APPIE_CMDID, + NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_set_ie_cmd(struct wmi *wmi, u8 if_idx, u8 ie_id, u8 ie_field, + const u8 *ie_info, u8 ie_len) +{ + struct sk_buff *skb; + struct wmi_set_ie_cmd *p; + + skb = ath6kl_wmi_get_new_buf(sizeof(*p) + ie_len); + if (!skb) + return -ENOMEM; + + ath6kl_dbg(ATH6KL_DBG_WMI, "set_ie_cmd: ie_id=%u ie_ie_field=%u ie_len=%u\n", + ie_id, ie_field, ie_len); + p = (struct wmi_set_ie_cmd *) skb->data; + p->ie_id = ie_id; + p->ie_field = ie_field; + p->ie_len = ie_len; + if (ie_info && ie_len > 0) + memcpy(p->ie_info, ie_info, ie_len); + + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SET_IE_CMDID, + NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_disable_11b_rates_cmd(struct wmi *wmi, bool disable) +{ + struct sk_buff *skb; + struct wmi_disable_11b_rates_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + ath6kl_dbg(ATH6KL_DBG_WMI, "disable_11b_rates_cmd: disable=%u\n", + disable); + cmd = (struct wmi_disable_11b_rates_cmd *) skb->data; + cmd->disable = disable ? 1 : 0; + + return ath6kl_wmi_cmd_send(wmi, 0, skb, WMI_DISABLE_11B_RATES_CMDID, + NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_remain_on_chnl_cmd(struct wmi *wmi, u8 if_idx, u32 freq, u32 dur) +{ + struct sk_buff *skb; + struct wmi_remain_on_chnl_cmd *p; + + skb = ath6kl_wmi_get_new_buf(sizeof(*p)); + if (!skb) + return -ENOMEM; + + ath6kl_dbg(ATH6KL_DBG_WMI, "remain_on_chnl_cmd: freq=%u dur=%u\n", + freq, dur); + p = (struct wmi_remain_on_chnl_cmd *) skb->data; + p->freq = cpu_to_le32(freq); + p->duration = cpu_to_le32(dur); + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_REMAIN_ON_CHNL_CMDID, + NO_SYNC_WMIFLAG); +} + +/* ath6kl_wmi_send_action_cmd is to be deprecated. Use + * ath6kl_wmi_send_mgmt_cmd instead. The new function supports P2P + * mgmt operations using station interface. + */ +static int ath6kl_wmi_send_action_cmd(struct wmi *wmi, u8 if_idx, u32 id, + u32 freq, u32 wait, const u8 *data, + u16 data_len) +{ + struct sk_buff *skb; + struct wmi_send_action_cmd *p; + u8 *buf; + + if (wait) + return -EINVAL; /* Offload for wait not supported */ + + buf = kmalloc(data_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + skb = ath6kl_wmi_get_new_buf(sizeof(*p) + data_len); + if (!skb) { + kfree(buf); + return -ENOMEM; + } + + kfree(wmi->last_mgmt_tx_frame); + memcpy(buf, data, data_len); + wmi->last_mgmt_tx_frame = buf; + wmi->last_mgmt_tx_frame_len = data_len; + + ath6kl_dbg(ATH6KL_DBG_WMI, + "send_action_cmd: id=%u freq=%u wait=%u len=%u\n", + id, freq, wait, data_len); + p = (struct wmi_send_action_cmd *) skb->data; + p->id = cpu_to_le32(id); + p->freq = cpu_to_le32(freq); + p->wait = cpu_to_le32(wait); + p->len = cpu_to_le16(data_len); + memcpy(p->data, data, data_len); + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SEND_ACTION_CMDID, + NO_SYNC_WMIFLAG); +} + +static int __ath6kl_wmi_send_mgmt_cmd(struct wmi *wmi, u8 if_idx, u32 id, + u32 freq, u32 wait, const u8 *data, + u16 data_len, u32 no_cck) +{ + struct sk_buff *skb; + struct wmi_send_mgmt_cmd *p; + u8 *buf; + + if (wait) + return -EINVAL; /* Offload for wait not supported */ + + buf = kmalloc(data_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + skb = ath6kl_wmi_get_new_buf(sizeof(*p) + data_len); + if (!skb) { + kfree(buf); + return -ENOMEM; + } + + kfree(wmi->last_mgmt_tx_frame); + memcpy(buf, data, data_len); + wmi->last_mgmt_tx_frame = buf; + wmi->last_mgmt_tx_frame_len = data_len; + + ath6kl_dbg(ATH6KL_DBG_WMI, + "send_action_cmd: id=%u freq=%u wait=%u len=%u\n", + id, freq, wait, data_len); + p = (struct wmi_send_mgmt_cmd *) skb->data; + p->id = cpu_to_le32(id); + p->freq = cpu_to_le32(freq); + p->wait = cpu_to_le32(wait); + p->no_cck = cpu_to_le32(no_cck); + p->len = cpu_to_le16(data_len); + memcpy(p->data, data, data_len); + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_SEND_MGMT_CMDID, + NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_send_mgmt_cmd(struct wmi *wmi, u8 if_idx, u32 id, u32 freq, + u32 wait, const u8 *data, u16 data_len, + u32 no_cck) +{ + int status; + struct ath6kl *ar = wmi->parent_dev; + + if (test_bit(ATH6KL_FW_CAPABILITY_STA_P2PDEV_DUPLEX, + ar->fw_capabilities)) { + /* + * If capable of doing P2P mgmt operations using + * station interface, send additional information like + * supported rates to advertise and xmit rates for + * probe requests + */ + status = __ath6kl_wmi_send_mgmt_cmd(ar->wmi, if_idx, id, freq, + wait, data, data_len, + no_cck); + } else { + status = ath6kl_wmi_send_action_cmd(ar->wmi, if_idx, id, freq, + wait, data, data_len); + } + + return status; +} + +int ath6kl_wmi_send_probe_response_cmd(struct wmi *wmi, u8 if_idx, u32 freq, + const u8 *dst, const u8 *data, + u16 data_len) +{ + struct sk_buff *skb; + struct wmi_p2p_probe_response_cmd *p; + size_t cmd_len = sizeof(*p) + data_len; + + if (data_len == 0) + cmd_len++; /* work around target minimum length requirement */ + + skb = ath6kl_wmi_get_new_buf(cmd_len); + if (!skb) + return -ENOMEM; + + ath6kl_dbg(ATH6KL_DBG_WMI, + "send_probe_response_cmd: freq=%u dst=%pM len=%u\n", + freq, dst, data_len); + p = (struct wmi_p2p_probe_response_cmd *) skb->data; + p->freq = cpu_to_le32(freq); + memcpy(p->destination_addr, dst, ETH_ALEN); + p->len = cpu_to_le16(data_len); + memcpy(p->data, data, data_len); + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, + WMI_SEND_PROBE_RESPONSE_CMDID, + NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_probe_report_req_cmd(struct wmi *wmi, u8 if_idx, bool enable) +{ + struct sk_buff *skb; + struct wmi_probe_req_report_cmd *p; + + skb = ath6kl_wmi_get_new_buf(sizeof(*p)); + if (!skb) + return -ENOMEM; + + ath6kl_dbg(ATH6KL_DBG_WMI, "probe_report_req_cmd: enable=%u\n", + enable); + p = (struct wmi_probe_req_report_cmd *) skb->data; + p->enable = enable ? 1 : 0; + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_PROBE_REQ_REPORT_CMDID, + NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_info_req_cmd(struct wmi *wmi, u8 if_idx, u32 info_req_flags) +{ + struct sk_buff *skb; + struct wmi_get_p2p_info *p; + + skb = ath6kl_wmi_get_new_buf(sizeof(*p)); + if (!skb) + return -ENOMEM; + + ath6kl_dbg(ATH6KL_DBG_WMI, "info_req_cmd: flags=%x\n", + info_req_flags); + p = (struct wmi_get_p2p_info *) skb->data; + p->info_req_flags = cpu_to_le32(info_req_flags); + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_GET_P2P_INFO_CMDID, + NO_SYNC_WMIFLAG); +} + +int ath6kl_wmi_cancel_remain_on_chnl_cmd(struct wmi *wmi, u8 if_idx) +{ + ath6kl_dbg(ATH6KL_DBG_WMI, "cancel_remain_on_chnl_cmd\n"); + return ath6kl_wmi_simple_cmd(wmi, if_idx, + WMI_CANCEL_REMAIN_ON_CHNL_CMDID); +} + +int ath6kl_wmi_set_inact_period(struct wmi *wmi, u8 if_idx, int inact_timeout) +{ + struct sk_buff *skb; + struct wmi_set_inact_period_cmd *cmd; + + skb = ath6kl_wmi_get_new_buf(sizeof(*cmd)); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_set_inact_period_cmd *) skb->data; + cmd->inact_period = cpu_to_le32(inact_timeout); + cmd->num_null_func = 0; + + return ath6kl_wmi_cmd_send(wmi, if_idx, skb, WMI_AP_CONN_INACT_CMDID, + NO_SYNC_WMIFLAG); +} + +static void ath6kl_wmi_hb_challenge_resp_event(struct wmi *wmi, u8 *datap, + int len) +{ + struct wmix_hb_challenge_resp_cmd *cmd; + + if (len < sizeof(struct wmix_hb_challenge_resp_cmd)) + return; + + cmd = (struct wmix_hb_challenge_resp_cmd *) datap; + ath6kl_recovery_hb_event(wmi->parent_dev, + le32_to_cpu(cmd->cookie)); +} + +static int ath6kl_wmi_control_rx_xtnd(struct wmi *wmi, struct sk_buff *skb) +{ + struct wmix_cmd_hdr *cmd; + u32 len; + u16 id; + u8 *datap; + int ret = 0; + + if (skb->len < sizeof(struct wmix_cmd_hdr)) { + ath6kl_err("bad packet 1\n"); + return -EINVAL; + } + + cmd = (struct wmix_cmd_hdr *) skb->data; + id = le32_to_cpu(cmd->cmd_id); + + skb_pull(skb, sizeof(struct wmix_cmd_hdr)); + + datap = skb->data; + len = skb->len; + + switch (id) { + case WMIX_HB_CHALLENGE_RESP_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "wmi event hb challenge resp\n"); + ath6kl_wmi_hb_challenge_resp_event(wmi, datap, len); + break; + case WMIX_DBGLOG_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "wmi event dbglog len %d\n", len); + ath6kl_debug_fwlog_event(wmi->parent_dev, datap, len); + break; + default: + ath6kl_warn("unknown cmd id 0x%x\n", id); + ret = -EINVAL; + break; + } + + return ret; +} + +static int ath6kl_wmi_roam_tbl_event_rx(struct wmi *wmi, u8 *datap, int len) +{ + return ath6kl_debug_roam_tbl_event(wmi->parent_dev, datap, len); +} + +/* Process interface specific wmi events, caller would free the datap */ +static int ath6kl_wmi_proc_events_vif(struct wmi *wmi, u16 if_idx, u16 cmd_id, + u8 *datap, u32 len) +{ + struct ath6kl_vif *vif; + + vif = ath6kl_get_vif_by_index(wmi->parent_dev, if_idx); + if (!vif) { + ath6kl_dbg(ATH6KL_DBG_WMI, + "Wmi event for unavailable vif, vif_index:%d\n", + if_idx); + return -EINVAL; + } + + switch (cmd_id) { + case WMI_CONNECT_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_CONNECT_EVENTID\n"); + return ath6kl_wmi_connect_event_rx(wmi, datap, len, vif); + case WMI_DISCONNECT_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_DISCONNECT_EVENTID\n"); + return ath6kl_wmi_disconnect_event_rx(wmi, datap, len, vif); + case WMI_TKIP_MICERR_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_TKIP_MICERR_EVENTID\n"); + return ath6kl_wmi_tkip_micerr_event_rx(wmi, datap, len, vif); + case WMI_BSSINFO_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_BSSINFO_EVENTID\n"); + return ath6kl_wmi_bssinfo_event_rx(wmi, datap, len, vif); + case WMI_NEIGHBOR_REPORT_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_NEIGHBOR_REPORT_EVENTID\n"); + return ath6kl_wmi_neighbor_report_event_rx(wmi, datap, len, + vif); + case WMI_SCAN_COMPLETE_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_SCAN_COMPLETE_EVENTID\n"); + return ath6kl_wmi_scan_complete_rx(wmi, datap, len, vif); + case WMI_REPORT_STATISTICS_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_REPORT_STATISTICS_EVENTID\n"); + return ath6kl_wmi_stats_event_rx(wmi, datap, len, vif); + case WMI_CAC_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_CAC_EVENTID\n"); + return ath6kl_wmi_cac_event_rx(wmi, datap, len, vif); + case WMI_PSPOLL_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_PSPOLL_EVENTID\n"); + return ath6kl_wmi_pspoll_event_rx(wmi, datap, len, vif); + case WMI_DTIMEXPIRY_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_DTIMEXPIRY_EVENTID\n"); + return ath6kl_wmi_dtimexpiry_event_rx(wmi, datap, len, vif); + case WMI_ADDBA_REQ_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_ADDBA_REQ_EVENTID\n"); + return ath6kl_wmi_addba_req_event_rx(wmi, datap, len, vif); + case WMI_DELBA_REQ_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_DELBA_REQ_EVENTID\n"); + return ath6kl_wmi_delba_req_event_rx(wmi, datap, len, vif); + case WMI_SET_HOST_SLEEP_MODE_CMD_PROCESSED_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, + "WMI_SET_HOST_SLEEP_MODE_CMD_PROCESSED_EVENTID"); + return ath6kl_wmi_host_sleep_mode_cmd_prcd_evt_rx(wmi, vif); + case WMI_REMAIN_ON_CHNL_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_REMAIN_ON_CHNL_EVENTID\n"); + return ath6kl_wmi_remain_on_chnl_event_rx(wmi, datap, len, vif); + case WMI_CANCEL_REMAIN_ON_CHNL_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, + "WMI_CANCEL_REMAIN_ON_CHNL_EVENTID\n"); + return ath6kl_wmi_cancel_remain_on_chnl_event_rx(wmi, datap, + len, vif); + case WMI_TX_STATUS_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_TX_STATUS_EVENTID\n"); + return ath6kl_wmi_tx_status_event_rx(wmi, datap, len, vif); + case WMI_RX_PROBE_REQ_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_RX_PROBE_REQ_EVENTID\n"); + return ath6kl_wmi_rx_probe_req_event_rx(wmi, datap, len, vif); + case WMI_RX_ACTION_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_RX_ACTION_EVENTID\n"); + return ath6kl_wmi_rx_action_event_rx(wmi, datap, len, vif); + case WMI_TXE_NOTIFY_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_TXE_NOTIFY_EVENTID\n"); + return ath6kl_wmi_txe_notify_event_rx(wmi, datap, len, vif); + default: + ath6kl_dbg(ATH6KL_DBG_WMI, "unknown cmd id 0x%x\n", cmd_id); + return -EINVAL; + } + + return 0; +} + +static int ath6kl_wmi_proc_events(struct wmi *wmi, struct sk_buff *skb) +{ + struct wmi_cmd_hdr *cmd; + int ret = 0; + u32 len; + u16 id; + u8 if_idx; + u8 *datap; + + cmd = (struct wmi_cmd_hdr *) skb->data; + id = le16_to_cpu(cmd->cmd_id); + if_idx = le16_to_cpu(cmd->info1) & WMI_CMD_HDR_IF_ID_MASK; + + skb_pull(skb, sizeof(struct wmi_cmd_hdr)); + datap = skb->data; + len = skb->len; + + ath6kl_dbg(ATH6KL_DBG_WMI, "wmi rx id %d len %d\n", id, len); + ath6kl_dbg_dump(ATH6KL_DBG_WMI_DUMP, NULL, "wmi rx ", + datap, len); + + switch (id) { + case WMI_GET_BITRATE_CMDID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_GET_BITRATE_CMDID\n"); + ret = ath6kl_wmi_bitrate_reply_rx(wmi, datap, len); + break; + case WMI_GET_CHANNEL_LIST_CMDID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_GET_CHANNEL_LIST_CMDID\n"); + ret = ath6kl_wmi_ch_list_reply_rx(wmi, datap, len); + break; + case WMI_GET_TX_PWR_CMDID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_GET_TX_PWR_CMDID\n"); + ret = ath6kl_wmi_tx_pwr_reply_rx(wmi, datap, len); + break; + case WMI_READY_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_READY_EVENTID\n"); + ret = ath6kl_wmi_ready_event_rx(wmi, datap, len); + break; + case WMI_PEER_NODE_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_PEER_NODE_EVENTID\n"); + ret = ath6kl_wmi_peer_node_event_rx(wmi, datap, len); + break; + case WMI_REGDOMAIN_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_REGDOMAIN_EVENTID\n"); + ath6kl_wmi_regdomain_event(wmi, datap, len); + break; + case WMI_PSTREAM_TIMEOUT_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_PSTREAM_TIMEOUT_EVENTID\n"); + ret = ath6kl_wmi_pstream_timeout_event_rx(wmi, datap, len); + break; + case WMI_CMDERROR_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_CMDERROR_EVENTID\n"); + ret = ath6kl_wmi_error_event_rx(wmi, datap, len); + break; + case WMI_RSSI_THRESHOLD_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_RSSI_THRESHOLD_EVENTID\n"); + ret = ath6kl_wmi_rssi_threshold_event_rx(wmi, datap, len); + break; + case WMI_ERROR_REPORT_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_ERROR_REPORT_EVENTID\n"); + break; + case WMI_OPT_RX_FRAME_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_OPT_RX_FRAME_EVENTID\n"); + /* this event has been deprecated */ + break; + case WMI_REPORT_ROAM_TBL_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_REPORT_ROAM_TBL_EVENTID\n"); + ret = ath6kl_wmi_roam_tbl_event_rx(wmi, datap, len); + break; + case WMI_EXTENSION_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_EXTENSION_EVENTID\n"); + ret = ath6kl_wmi_control_rx_xtnd(wmi, skb); + break; + case WMI_CHANNEL_CHANGE_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_CHANNEL_CHANGE_EVENTID\n"); + break; + case WMI_REPORT_ROAM_DATA_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_REPORT_ROAM_DATA_EVENTID\n"); + break; + case WMI_TEST_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_TEST_EVENTID\n"); + ret = ath6kl_wmi_test_rx(wmi, datap, len); + break; + case WMI_GET_FIXRATES_CMDID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_GET_FIXRATES_CMDID\n"); + ret = ath6kl_wmi_ratemask_reply_rx(wmi, datap, len); + break; + case WMI_TX_RETRY_ERR_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_TX_RETRY_ERR_EVENTID\n"); + break; + case WMI_SNR_THRESHOLD_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_SNR_THRESHOLD_EVENTID\n"); + ret = ath6kl_wmi_snr_threshold_event_rx(wmi, datap, len); + break; + case WMI_LQ_THRESHOLD_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_LQ_THRESHOLD_EVENTID\n"); + break; + case WMI_APLIST_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_APLIST_EVENTID\n"); + ret = ath6kl_wmi_aplist_event_rx(wmi, datap, len); + break; + case WMI_GET_KEEPALIVE_CMDID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_GET_KEEPALIVE_CMDID\n"); + ret = ath6kl_wmi_keepalive_reply_rx(wmi, datap, len); + break; + case WMI_GET_WOW_LIST_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_GET_WOW_LIST_EVENTID\n"); + break; + case WMI_GET_PMKID_LIST_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_GET_PMKID_LIST_EVENTID\n"); + ret = ath6kl_wmi_get_pmkid_list_event_rx(wmi, datap, len); + break; + case WMI_SET_PARAMS_REPLY_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_SET_PARAMS_REPLY_EVENTID\n"); + break; + case WMI_ADDBA_RESP_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_ADDBA_RESP_EVENTID\n"); + break; + case WMI_REPORT_BTCOEX_CONFIG_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, + "WMI_REPORT_BTCOEX_CONFIG_EVENTID\n"); + break; + case WMI_REPORT_BTCOEX_STATS_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, + "WMI_REPORT_BTCOEX_STATS_EVENTID\n"); + break; + case WMI_TX_COMPLETE_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_TX_COMPLETE_EVENTID\n"); + ret = ath6kl_wmi_tx_complete_event_rx(datap, len); + break; + case WMI_P2P_CAPABILITIES_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_P2P_CAPABILITIES_EVENTID\n"); + ret = ath6kl_wmi_p2p_capabilities_event_rx(datap, len); + break; + case WMI_P2P_INFO_EVENTID: + ath6kl_dbg(ATH6KL_DBG_WMI, "WMI_P2P_INFO_EVENTID\n"); + ret = ath6kl_wmi_p2p_info_event_rx(datap, len); + break; + default: + /* may be the event is interface specific */ + ret = ath6kl_wmi_proc_events_vif(wmi, if_idx, id, datap, len); + break; + } + + dev_kfree_skb(skb); + return ret; +} + +/* Control Path */ +int ath6kl_wmi_control_rx(struct wmi *wmi, struct sk_buff *skb) +{ + if (WARN_ON(skb == NULL)) + return -EINVAL; + + if (skb->len < sizeof(struct wmi_cmd_hdr)) { + ath6kl_err("bad packet 1\n"); + dev_kfree_skb(skb); + return -EINVAL; + } + + trace_ath6kl_wmi_event(skb->data, skb->len); + + return ath6kl_wmi_proc_events(wmi, skb); +} + +void ath6kl_wmi_reset(struct wmi *wmi) +{ + spin_lock_bh(&wmi->lock); + + wmi->fat_pipe_exist = 0; + memset(wmi->stream_exist_for_ac, 0, sizeof(wmi->stream_exist_for_ac)); + + spin_unlock_bh(&wmi->lock); +} + +void *ath6kl_wmi_init(struct ath6kl *dev) +{ + struct wmi *wmi; + + wmi = kzalloc(sizeof(struct wmi), GFP_KERNEL); + if (!wmi) + return NULL; + + spin_lock_init(&wmi->lock); + + wmi->parent_dev = dev; + + wmi->pwr_mode = REC_POWER; + + ath6kl_wmi_reset(wmi); + + return wmi; +} + +void ath6kl_wmi_shutdown(struct wmi *wmi) +{ + if (!wmi) + return; + + kfree(wmi->last_mgmt_tx_frame); + kfree(wmi); +} diff --git a/drivers/net/wireless/ath/ath6kl/wmi.h b/drivers/net/wireless/ath/ath6kl/wmi.h new file mode 100644 index 0000000..b5f2265 --- /dev/null +++ b/drivers/net/wireless/ath/ath6kl/wmi.h @@ -0,0 +1,2732 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file contains the definitions of the WMI protocol specified in the + * Wireless Module Interface (WMI). It includes definitions of all the + * commands and events. Commands are messages from the host to the WM. + * Events and Replies are messages from the WM to the host. + */ + +#ifndef WMI_H +#define WMI_H + +#include + +#include "htc.h" + +#define HTC_PROTOCOL_VERSION 0x0002 +#define WMI_PROTOCOL_VERSION 0x0002 +#define WMI_CONTROL_MSG_MAX_LEN 256 +#define is_ethertype(type_or_len) ((type_or_len) >= 0x0600) + +#define IP_ETHERTYPE 0x0800 + +#define WMI_IMPLICIT_PSTREAM 0xFF +#define WMI_MAX_THINSTREAM 15 + +#define SSID_IE_LEN_INDEX 13 + +/* Host side link management data structures */ +#define SIG_QUALITY_THRESH_LVLS 6 +#define SIG_QUALITY_UPPER_THRESH_LVLS SIG_QUALITY_THRESH_LVLS +#define SIG_QUALITY_LOWER_THRESH_LVLS SIG_QUALITY_THRESH_LVLS + +#define A_BAND_24GHZ 0 +#define A_BAND_5GHZ 1 +#define ATH6KL_NUM_BANDS 2 + +/* in ms */ +#define WMI_IMPLICIT_PSTREAM_INACTIVITY_INT 5000 + +/* + * There are no signed versions of __le16 and __le32, so for a temporary + * solution come up with our own version. The idea is from fs/ntfs/types.h. + * + * Use a_ prefix so that it doesn't conflict if we get proper support to + * linux/types.h. + */ +typedef __s16 __bitwise a_sle16; +typedef __s32 __bitwise a_sle32; + +static inline a_sle32 a_cpu_to_sle32(s32 val) +{ + return (__force a_sle32) cpu_to_le32(val); +} + +static inline s32 a_sle32_to_cpu(a_sle32 val) +{ + return le32_to_cpu((__force __le32) val); +} + +static inline a_sle16 a_cpu_to_sle16(s16 val) +{ + return (__force a_sle16) cpu_to_le16(val); +} + +static inline s16 a_sle16_to_cpu(a_sle16 val) +{ + return le16_to_cpu((__force __le16) val); +} + +struct sq_threshold_params { + s16 upper_threshold[SIG_QUALITY_UPPER_THRESH_LVLS]; + s16 lower_threshold[SIG_QUALITY_LOWER_THRESH_LVLS]; + u32 upper_threshold_valid_count; + u32 lower_threshold_valid_count; + u32 polling_interval; + u8 weight; + u8 last_rssi; + u8 last_rssi_poll_event; +}; + +struct wmi_data_sync_bufs { + u8 traffic_class; + struct sk_buff *skb; +}; + +/* WMM stream classes */ +#define WMM_NUM_AC 4 +#define WMM_AC_BE 0 /* best effort */ +#define WMM_AC_BK 1 /* background */ +#define WMM_AC_VI 2 /* video */ +#define WMM_AC_VO 3 /* voice */ + +#define WMI_VOICE_USER_PRIORITY 0x7 + +struct wmi { + u16 stream_exist_for_ac[WMM_NUM_AC]; + u8 fat_pipe_exist; + struct ath6kl *parent_dev; + u8 pwr_mode; + + /* protects fat_pipe_exist and stream_exist_for_ac */ + spinlock_t lock; + enum htc_endpoint_id ep_id; + struct sq_threshold_params + sq_threshld[SIGNAL_QUALITY_METRICS_NUM_MAX]; + bool is_wmm_enabled; + u8 traffic_class; + bool is_probe_ssid; + + u8 *last_mgmt_tx_frame; + size_t last_mgmt_tx_frame_len; + u8 saved_pwr_mode; +}; + +struct host_app_area { + __le32 wmi_protocol_ver; +} __packed; + +enum wmi_msg_type { + DATA_MSGTYPE = 0x0, + CNTL_MSGTYPE, + SYNC_MSGTYPE, + OPT_MSGTYPE, +}; + +/* + * Macros for operating on WMI_DATA_HDR (info) field + */ + +#define WMI_DATA_HDR_MSG_TYPE_MASK 0x03 +#define WMI_DATA_HDR_MSG_TYPE_SHIFT 0 +#define WMI_DATA_HDR_UP_MASK 0x07 +#define WMI_DATA_HDR_UP_SHIFT 2 + +/* In AP mode, the same bit (b5) is used to indicate Power save state in + * the Rx dir and More data bit state in the tx direction. + */ +#define WMI_DATA_HDR_PS_MASK 0x1 +#define WMI_DATA_HDR_PS_SHIFT 5 + +#define WMI_DATA_HDR_MORE 0x20 + +enum wmi_data_hdr_data_type { + WMI_DATA_HDR_DATA_TYPE_802_3 = 0, + WMI_DATA_HDR_DATA_TYPE_802_11, + + /* used to be used for the PAL */ + WMI_DATA_HDR_DATA_TYPE_ACL, +}; + +/* Bitmap of data header flags */ +enum wmi_data_hdr_flags { + WMI_DATA_HDR_FLAGS_MORE = 0x1, + WMI_DATA_HDR_FLAGS_EOSP = 0x2, + WMI_DATA_HDR_FLAGS_UAPSD = 0x4, +}; + +#define WMI_DATA_HDR_DATA_TYPE_MASK 0x3 +#define WMI_DATA_HDR_DATA_TYPE_SHIFT 6 + +/* Macros for operating on WMI_DATA_HDR (info2) field */ +#define WMI_DATA_HDR_SEQNO_MASK 0xFFF +#define WMI_DATA_HDR_SEQNO_SHIFT 0 + +#define WMI_DATA_HDR_AMSDU_MASK 0x1 +#define WMI_DATA_HDR_AMSDU_SHIFT 12 + +#define WMI_DATA_HDR_META_MASK 0x7 +#define WMI_DATA_HDR_META_SHIFT 13 + +#define WMI_DATA_HDR_PAD_BEFORE_DATA_MASK 0xFF +#define WMI_DATA_HDR_PAD_BEFORE_DATA_SHIFT 0x8 + +/* Macros for operating on WMI_DATA_HDR (info3) field */ +#define WMI_DATA_HDR_IF_IDX_MASK 0xF + +#define WMI_DATA_HDR_TRIG 0x10 +#define WMI_DATA_HDR_EOSP 0x10 + +struct wmi_data_hdr { + s8 rssi; + + /* + * usage of 'info' field(8-bit): + * + * b1:b0 - WMI_MSG_TYPE + * b4:b3:b2 - UP(tid) + * b5 - Used in AP mode. + * More-data in tx dir, PS in rx. + * b7:b6 - Dot3 header(0), + * Dot11 Header(1), + * ACL data(2) + */ + u8 info; + + /* + * usage of 'info2' field(16-bit): + * + * b11:b0 - seq_no + * b12 - A-MSDU? + * b15:b13 - META_DATA_VERSION 0 - 7 + */ + __le16 info2; + + /* + * usage of info3, 16-bit: + * b3:b0 - Interface index + * b4 - uAPSD trigger in rx & EOSP in tx + * b15:b5 - Reserved + */ + __le16 info3; +} __packed; + +static inline u8 wmi_data_hdr_get_up(struct wmi_data_hdr *dhdr) +{ + return (dhdr->info >> WMI_DATA_HDR_UP_SHIFT) & WMI_DATA_HDR_UP_MASK; +} + +static inline void wmi_data_hdr_set_up(struct wmi_data_hdr *dhdr, + u8 usr_pri) +{ + dhdr->info &= ~(WMI_DATA_HDR_UP_MASK << WMI_DATA_HDR_UP_SHIFT); + dhdr->info |= usr_pri << WMI_DATA_HDR_UP_SHIFT; +} + +static inline u8 wmi_data_hdr_get_dot11(struct wmi_data_hdr *dhdr) +{ + u8 data_type; + + data_type = (dhdr->info >> WMI_DATA_HDR_DATA_TYPE_SHIFT) & + WMI_DATA_HDR_DATA_TYPE_MASK; + return (data_type == WMI_DATA_HDR_DATA_TYPE_802_11); +} + +static inline u16 wmi_data_hdr_get_seqno(struct wmi_data_hdr *dhdr) +{ + return (le16_to_cpu(dhdr->info2) >> WMI_DATA_HDR_SEQNO_SHIFT) & + WMI_DATA_HDR_SEQNO_MASK; +} + +static inline u8 wmi_data_hdr_is_amsdu(struct wmi_data_hdr *dhdr) +{ + return (le16_to_cpu(dhdr->info2) >> WMI_DATA_HDR_AMSDU_SHIFT) & + WMI_DATA_HDR_AMSDU_MASK; +} + +static inline u8 wmi_data_hdr_get_meta(struct wmi_data_hdr *dhdr) +{ + return (le16_to_cpu(dhdr->info2) >> WMI_DATA_HDR_META_SHIFT) & + WMI_DATA_HDR_META_MASK; +} + +static inline u8 wmi_data_hdr_get_if_idx(struct wmi_data_hdr *dhdr) +{ + return le16_to_cpu(dhdr->info3) & WMI_DATA_HDR_IF_IDX_MASK; +} + +/* Tx meta version definitions */ +#define WMI_MAX_TX_META_SZ 12 +#define WMI_META_VERSION_1 0x01 +#define WMI_META_VERSION_2 0x02 + +/* Flag to signal to FW to calculate TCP checksum */ +#define WMI_META_V2_FLAG_CSUM_OFFLOAD 0x01 + +struct wmi_tx_meta_v1 { + /* packet ID to identify the tx request */ + u8 pkt_id; + + /* rate policy to be used for the tx of this frame */ + u8 rate_plcy_id; +} __packed; + +struct wmi_tx_meta_v2 { + /* + * Offset from start of the WMI header for csum calculation to + * begin. + */ + u8 csum_start; + + /* offset from start of WMI header where final csum goes */ + u8 csum_dest; + + /* no of bytes over which csum is calculated */ + u8 csum_flags; +} __packed; + +struct wmi_rx_meta_v1 { + u8 status; + + /* rate index mapped to rate at which this packet was received. */ + u8 rix; + + /* rssi of packet */ + u8 rssi; + + /* rf channel during packet reception */ + u8 channel; + + __le16 flags; +} __packed; + +struct wmi_rx_meta_v2 { + __le16 csum; + + /* bit 0 set -partial csum valid bit 1 set -test mode */ + u8 csum_flags; +} __packed; + +#define WMI_CMD_HDR_IF_ID_MASK 0xF + +/* Control Path */ +struct wmi_cmd_hdr { + __le16 cmd_id; + + /* info1 - 16 bits + * b03:b00 - id + * b15:b04 - unused */ + __le16 info1; + + /* for alignment */ + __le16 reserved; +} __packed; + +static inline u8 wmi_cmd_hdr_get_if_idx(struct wmi_cmd_hdr *chdr) +{ + return le16_to_cpu(chdr->info1) & WMI_CMD_HDR_IF_ID_MASK; +} + +/* List of WMI commands */ +enum wmi_cmd_id { + WMI_CONNECT_CMDID = 0x0001, + WMI_RECONNECT_CMDID, + WMI_DISCONNECT_CMDID, + WMI_SYNCHRONIZE_CMDID, + WMI_CREATE_PSTREAM_CMDID, + WMI_DELETE_PSTREAM_CMDID, + /* WMI_START_SCAN_CMDID is to be deprecated. Use + * WMI_BEGIN_SCAN_CMDID instead. The new cmd supports P2P mgmt + * operations using station interface. + */ + WMI_START_SCAN_CMDID, + WMI_SET_SCAN_PARAMS_CMDID, + WMI_SET_BSS_FILTER_CMDID, + WMI_SET_PROBED_SSID_CMDID, /* 10 */ + WMI_SET_LISTEN_INT_CMDID, + WMI_SET_BMISS_TIME_CMDID, + WMI_SET_DISC_TIMEOUT_CMDID, + WMI_GET_CHANNEL_LIST_CMDID, + WMI_SET_BEACON_INT_CMDID, + WMI_GET_STATISTICS_CMDID, + WMI_SET_CHANNEL_PARAMS_CMDID, + WMI_SET_POWER_MODE_CMDID, + WMI_SET_IBSS_PM_CAPS_CMDID, + WMI_SET_POWER_PARAMS_CMDID, /* 20 */ + WMI_SET_POWERSAVE_TIMERS_POLICY_CMDID, + WMI_ADD_CIPHER_KEY_CMDID, + WMI_DELETE_CIPHER_KEY_CMDID, + WMI_ADD_KRK_CMDID, + WMI_DELETE_KRK_CMDID, + WMI_SET_PMKID_CMDID, + WMI_SET_TX_PWR_CMDID, + WMI_GET_TX_PWR_CMDID, + WMI_SET_ASSOC_INFO_CMDID, + WMI_ADD_BAD_AP_CMDID, /* 30 */ + WMI_DELETE_BAD_AP_CMDID, + WMI_SET_TKIP_COUNTERMEASURES_CMDID, + WMI_RSSI_THRESHOLD_PARAMS_CMDID, + WMI_TARGET_ERROR_REPORT_BITMASK_CMDID, + WMI_SET_ACCESS_PARAMS_CMDID, + WMI_SET_RETRY_LIMITS_CMDID, + WMI_SET_OPT_MODE_CMDID, + WMI_OPT_TX_FRAME_CMDID, + WMI_SET_VOICE_PKT_SIZE_CMDID, + WMI_SET_MAX_SP_LEN_CMDID, /* 40 */ + WMI_SET_ROAM_CTRL_CMDID, + WMI_GET_ROAM_TBL_CMDID, + WMI_GET_ROAM_DATA_CMDID, + WMI_ENABLE_RM_CMDID, + WMI_SET_MAX_OFFHOME_DURATION_CMDID, + WMI_EXTENSION_CMDID, /* Non-wireless extensions */ + WMI_SNR_THRESHOLD_PARAMS_CMDID, + WMI_LQ_THRESHOLD_PARAMS_CMDID, + WMI_SET_LPREAMBLE_CMDID, + WMI_SET_RTS_CMDID, /* 50 */ + WMI_CLR_RSSI_SNR_CMDID, + WMI_SET_FIXRATES_CMDID, + WMI_GET_FIXRATES_CMDID, + WMI_SET_AUTH_MODE_CMDID, + WMI_SET_REASSOC_MODE_CMDID, + WMI_SET_WMM_CMDID, + WMI_SET_WMM_TXOP_CMDID, + WMI_TEST_CMDID, + + /* COEX AR6002 only */ + WMI_SET_BT_STATUS_CMDID, + WMI_SET_BT_PARAMS_CMDID, /* 60 */ + + WMI_SET_KEEPALIVE_CMDID, + WMI_GET_KEEPALIVE_CMDID, + WMI_SET_APPIE_CMDID, + WMI_GET_APPIE_CMDID, + WMI_SET_WSC_STATUS_CMDID, + + /* Wake on Wireless */ + WMI_SET_HOST_SLEEP_MODE_CMDID, + WMI_SET_WOW_MODE_CMDID, + WMI_GET_WOW_LIST_CMDID, + WMI_ADD_WOW_PATTERN_CMDID, + WMI_DEL_WOW_PATTERN_CMDID, /* 70 */ + + WMI_SET_FRAMERATES_CMDID, + WMI_SET_AP_PS_CMDID, + WMI_SET_QOS_SUPP_CMDID, + WMI_SET_IE_CMDID, + + /* WMI_THIN_RESERVED_... mark the start and end + * values for WMI_THIN_RESERVED command IDs. These + * command IDs can be found in wmi_thin.h */ + WMI_THIN_RESERVED_START = 0x8000, + WMI_THIN_RESERVED_END = 0x8fff, + + /* Developer commands starts at 0xF000 */ + WMI_SET_BITRATE_CMDID = 0xF000, + WMI_GET_BITRATE_CMDID, + WMI_SET_WHALPARAM_CMDID, + WMI_SET_MAC_ADDRESS_CMDID, + WMI_SET_AKMP_PARAMS_CMDID, + WMI_SET_PMKID_LIST_CMDID, + WMI_GET_PMKID_LIST_CMDID, + WMI_ABORT_SCAN_CMDID, + WMI_SET_TARGET_EVENT_REPORT_CMDID, + + /* Unused */ + WMI_UNUSED1, + WMI_UNUSED2, + + /* AP mode commands */ + WMI_AP_HIDDEN_SSID_CMDID, + WMI_AP_SET_NUM_STA_CMDID, + WMI_AP_ACL_POLICY_CMDID, + WMI_AP_ACL_MAC_LIST_CMDID, + WMI_AP_CONFIG_COMMIT_CMDID, + WMI_AP_SET_MLME_CMDID, + WMI_AP_SET_PVB_CMDID, + WMI_AP_CONN_INACT_CMDID, + WMI_AP_PROT_SCAN_TIME_CMDID, + WMI_AP_SET_COUNTRY_CMDID, + WMI_AP_SET_DTIM_CMDID, + WMI_AP_MODE_STAT_CMDID, + + WMI_SET_IP_CMDID, + WMI_SET_PARAMS_CMDID, + WMI_SET_MCAST_FILTER_CMDID, + WMI_DEL_MCAST_FILTER_CMDID, + + WMI_ALLOW_AGGR_CMDID, + WMI_ADDBA_REQ_CMDID, + WMI_DELBA_REQ_CMDID, + WMI_SET_HT_CAP_CMDID, + WMI_SET_HT_OP_CMDID, + WMI_SET_TX_SELECT_RATES_CMDID, + WMI_SET_TX_SGI_PARAM_CMDID, + WMI_SET_RATE_POLICY_CMDID, + + WMI_HCI_CMD_CMDID, + WMI_RX_FRAME_FORMAT_CMDID, + WMI_SET_THIN_MODE_CMDID, + WMI_SET_BT_WLAN_CONN_PRECEDENCE_CMDID, + + WMI_AP_SET_11BG_RATESET_CMDID, + WMI_SET_PMK_CMDID, + WMI_MCAST_FILTER_CMDID, + + /* COEX CMDID AR6003 */ + WMI_SET_BTCOEX_FE_ANT_CMDID, + WMI_SET_BTCOEX_COLOCATED_BT_DEV_CMDID, + WMI_SET_BTCOEX_SCO_CONFIG_CMDID, + WMI_SET_BTCOEX_A2DP_CONFIG_CMDID, + WMI_SET_BTCOEX_ACLCOEX_CONFIG_CMDID, + WMI_SET_BTCOEX_BTINQUIRY_PAGE_CONFIG_CMDID, + WMI_SET_BTCOEX_DEBUG_CMDID, + WMI_SET_BTCOEX_BT_OPERATING_STATUS_CMDID, + WMI_GET_BTCOEX_STATS_CMDID, + WMI_GET_BTCOEX_CONFIG_CMDID, + + WMI_SET_DFS_ENABLE_CMDID, /* F034 */ + WMI_SET_DFS_MINRSSITHRESH_CMDID, + WMI_SET_DFS_MAXPULSEDUR_CMDID, + WMI_DFS_RADAR_DETECTED_CMDID, + + /* P2P commands */ + WMI_P2P_SET_CONFIG_CMDID, /* F038 */ + WMI_WPS_SET_CONFIG_CMDID, + WMI_SET_REQ_DEV_ATTR_CMDID, + WMI_P2P_FIND_CMDID, + WMI_P2P_STOP_FIND_CMDID, + WMI_P2P_GO_NEG_START_CMDID, + WMI_P2P_LISTEN_CMDID, + + WMI_CONFIG_TX_MAC_RULES_CMDID, /* F040 */ + WMI_SET_PROMISCUOUS_MODE_CMDID, + WMI_RX_FRAME_FILTER_CMDID, + WMI_SET_CHANNEL_CMDID, + + /* WAC commands */ + WMI_ENABLE_WAC_CMDID, + WMI_WAC_SCAN_REPLY_CMDID, + WMI_WAC_CTRL_REQ_CMDID, + WMI_SET_DIV_PARAMS_CMDID, + + WMI_GET_PMK_CMDID, + WMI_SET_PASSPHRASE_CMDID, + WMI_SEND_ASSOC_RES_CMDID, + WMI_SET_ASSOC_REQ_RELAY_CMDID, + + /* ACS command, consists of sub-commands */ + WMI_ACS_CTRL_CMDID, + WMI_SET_EXCESS_TX_RETRY_THRES_CMDID, + WMI_SET_TBD_TIME_CMDID, /*added for wmiconfig command for TBD */ + + /* Pktlog cmds */ + WMI_PKTLOG_ENABLE_CMDID, + WMI_PKTLOG_DISABLE_CMDID, + + /* More P2P Cmds */ + WMI_P2P_GO_NEG_REQ_RSP_CMDID, + WMI_P2P_GRP_INIT_CMDID, + WMI_P2P_GRP_FORMATION_DONE_CMDID, + WMI_P2P_INVITE_CMDID, + WMI_P2P_INVITE_REQ_RSP_CMDID, + WMI_P2P_PROV_DISC_REQ_CMDID, + WMI_P2P_SET_CMDID, + + WMI_GET_RFKILL_MODE_CMDID, + WMI_SET_RFKILL_MODE_CMDID, + WMI_AP_SET_APSD_CMDID, + WMI_AP_APSD_BUFFERED_TRAFFIC_CMDID, + + WMI_P2P_SDPD_TX_CMDID, /* F05C */ + WMI_P2P_STOP_SDPD_CMDID, + WMI_P2P_CANCEL_CMDID, + /* Ultra low power store / recall commands */ + WMI_STORERECALL_CONFIGURE_CMDID, + WMI_STORERECALL_RECALL_CMDID, + WMI_STORERECALL_HOST_READY_CMDID, + WMI_FORCE_TARGET_ASSERT_CMDID, + + WMI_SET_PROBED_SSID_EX_CMDID, + WMI_SET_NETWORK_LIST_OFFLOAD_CMDID, + WMI_SET_ARP_NS_OFFLOAD_CMDID, + WMI_ADD_WOW_EXT_PATTERN_CMDID, + WMI_GTK_OFFLOAD_OP_CMDID, + WMI_REMAIN_ON_CHNL_CMDID, + WMI_CANCEL_REMAIN_ON_CHNL_CMDID, + /* WMI_SEND_ACTION_CMDID is to be deprecated. Use + * WMI_SEND_MGMT_CMDID instead. The new cmd supports P2P mgmt + * operations using station interface. + */ + WMI_SEND_ACTION_CMDID, + WMI_PROBE_REQ_REPORT_CMDID, + WMI_DISABLE_11B_RATES_CMDID, + WMI_SEND_PROBE_RESPONSE_CMDID, + WMI_GET_P2P_INFO_CMDID, + WMI_AP_JOIN_BSS_CMDID, + + WMI_SMPS_ENABLE_CMDID, + WMI_SMPS_CONFIG_CMDID, + WMI_SET_RATECTRL_PARM_CMDID, + /* LPL specific commands*/ + WMI_LPL_FORCE_ENABLE_CMDID, + WMI_LPL_SET_POLICY_CMDID, + WMI_LPL_GET_POLICY_CMDID, + WMI_LPL_GET_HWSTATE_CMDID, + WMI_LPL_SET_PARAMS_CMDID, + WMI_LPL_GET_PARAMS_CMDID, + + WMI_SET_BUNDLE_PARAM_CMDID, + + /*GreenTx specific commands*/ + + WMI_GREENTX_PARAMS_CMDID, + + WMI_RTT_MEASREQ_CMDID, + WMI_RTT_CAPREQ_CMDID, + WMI_RTT_STATUSREQ_CMDID, + + /* WPS Commands */ + WMI_WPS_START_CMDID, + WMI_GET_WPS_STATUS_CMDID, + + /* More P2P commands */ + WMI_SET_NOA_CMDID, + WMI_GET_NOA_CMDID, + WMI_SET_OPPPS_CMDID, + WMI_GET_OPPPS_CMDID, + WMI_ADD_PORT_CMDID, + WMI_DEL_PORT_CMDID, + + /* 802.11w cmd */ + WMI_SET_RSN_CAP_CMDID, + WMI_GET_RSN_CAP_CMDID, + WMI_SET_IGTK_CMDID, + + WMI_RX_FILTER_COALESCE_FILTER_OP_CMDID, + WMI_RX_FILTER_SET_FRAME_TEST_LIST_CMDID, + + WMI_SEND_MGMT_CMDID, + WMI_BEGIN_SCAN_CMDID, + + WMI_SET_BLACK_LIST, + WMI_SET_MCASTRATE, + + WMI_STA_BMISS_ENHANCE_CMDID, + + WMI_SET_REGDOMAIN_CMDID, + + WMI_SET_RSSI_FILTER_CMDID, + + WMI_SET_KEEP_ALIVE_EXT, + + WMI_VOICE_DETECTION_ENABLE_CMDID, + + WMI_SET_TXE_NOTIFY_CMDID, + + WMI_SET_RECOVERY_TEST_PARAMETER_CMDID, /*0xf094*/ + + WMI_ENABLE_SCHED_SCAN_CMDID, +}; + +enum wmi_mgmt_frame_type { + WMI_FRAME_BEACON = 0, + WMI_FRAME_PROBE_REQ, + WMI_FRAME_PROBE_RESP, + WMI_FRAME_ASSOC_REQ, + WMI_FRAME_ASSOC_RESP, + WMI_NUM_MGMT_FRAME +}; + +enum wmi_ie_field_type { + WMI_RSN_IE_CAPB = 0x1, + WMI_IE_FULL = 0xFF, /* indicats full IE */ +}; + +/* WMI_CONNECT_CMDID */ +enum network_type { + INFRA_NETWORK = 0x01, + ADHOC_NETWORK = 0x02, + ADHOC_CREATOR = 0x04, + AP_NETWORK = 0x10, +}; + +enum network_subtype { + SUBTYPE_NONE, + SUBTYPE_BT, + SUBTYPE_P2PDEV, + SUBTYPE_P2PCLIENT, + SUBTYPE_P2PGO, +}; + +enum dot11_auth_mode { + OPEN_AUTH = 0x01, + SHARED_AUTH = 0x02, + + /* different from IEEE_AUTH_MODE definitions */ + LEAP_AUTH = 0x04, +}; + +enum auth_mode { + NONE_AUTH = 0x01, + WPA_AUTH = 0x02, + WPA2_AUTH = 0x04, + WPA_PSK_AUTH = 0x08, + WPA2_PSK_AUTH = 0x10, + WPA_AUTH_CCKM = 0x20, + WPA2_AUTH_CCKM = 0x40, +}; + +#define WMI_MAX_KEY_INDEX 3 + +#define WMI_MAX_KEY_LEN 32 + +/* + * NB: these values are ordered carefully; there are lots of + * of implications in any reordering. In particular beware + * that 4 is not used to avoid conflicting with IEEE80211_F_PRIVACY. + */ +#define ATH6KL_CIPHER_WEP 0 +#define ATH6KL_CIPHER_TKIP 1 +#define ATH6KL_CIPHER_AES_OCB 2 +#define ATH6KL_CIPHER_AES_CCM 3 +#define ATH6KL_CIPHER_CKIP 5 +#define ATH6KL_CIPHER_CCKM_KRK 6 +#define ATH6KL_CIPHER_NONE 7 /* pseudo value */ + +/* + * 802.11 rate set. + */ +#define ATH6KL_RATE_MAXSIZE 15 /* max rates we'll handle */ + +#define ATH_OUI_TYPE 0x01 +#define WPA_OUI_TYPE 0x01 +#define WMM_PARAM_OUI_SUBTYPE 0x01 +#define WMM_OUI_TYPE 0x02 +#define WSC_OUT_TYPE 0x04 + +enum wmi_connect_ctrl_flags_bits { + CONNECT_ASSOC_POLICY_USER = 0x0001, + CONNECT_SEND_REASSOC = 0x0002, + CONNECT_IGNORE_WPAx_GROUP_CIPHER = 0x0004, + CONNECT_PROFILE_MATCH_DONE = 0x0008, + CONNECT_IGNORE_AAC_BEACON = 0x0010, + CONNECT_CSA_FOLLOW_BSS = 0x0020, + CONNECT_DO_WPA_OFFLOAD = 0x0040, + CONNECT_DO_NOT_DEAUTH = 0x0080, + CONNECT_WPS_FLAG = 0x0100, +}; + +struct wmi_connect_cmd { + u8 nw_type; + u8 dot11_auth_mode; + u8 auth_mode; + u8 prwise_crypto_type; + u8 prwise_crypto_len; + u8 grp_crypto_type; + u8 grp_crypto_len; + u8 ssid_len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + __le16 ch; + u8 bssid[ETH_ALEN]; + __le32 ctrl_flags; + u8 nw_subtype; +} __packed; + +/* WMI_RECONNECT_CMDID */ +struct wmi_reconnect_cmd { + /* channel hint */ + __le16 channel; + + /* mandatory if set */ + u8 bssid[ETH_ALEN]; +} __packed; + +/* WMI_ADD_CIPHER_KEY_CMDID */ +enum key_usage { + PAIRWISE_USAGE = 0x00, + GROUP_USAGE = 0x01, + + /* default Tx Key - static WEP only */ + TX_USAGE = 0x02, +}; + +/* + * Bit Flag + * Bit 0 - Initialise TSC - default is Initialize + */ +#define KEY_OP_INIT_TSC 0x01 +#define KEY_OP_INIT_RSC 0x02 + +/* default initialise the TSC & RSC */ +#define KEY_OP_INIT_VAL 0x03 +#define KEY_OP_VALID_MASK 0x03 + +struct wmi_add_cipher_key_cmd { + u8 key_index; + u8 key_type; + + /* enum key_usage */ + u8 key_usage; + + u8 key_len; + + /* key replay sequence counter */ + u8 key_rsc[8]; + + u8 key[WLAN_MAX_KEY_LEN]; + + /* additional key control info */ + u8 key_op_ctrl; + + u8 key_mac_addr[ETH_ALEN]; +} __packed; + +/* WMI_DELETE_CIPHER_KEY_CMDID */ +struct wmi_delete_cipher_key_cmd { + u8 key_index; +} __packed; + +#define WMI_KRK_LEN 16 + +/* WMI_ADD_KRK_CMDID */ +struct wmi_add_krk_cmd { + u8 krk[WMI_KRK_LEN]; +} __packed; + +/* WMI_SETPMKID_CMDID */ + +#define WMI_PMKID_LEN 16 + +enum pmkid_enable_flg { + PMKID_DISABLE = 0, + PMKID_ENABLE = 1, +}; + +struct wmi_setpmkid_cmd { + u8 bssid[ETH_ALEN]; + + /* enum pmkid_enable_flg */ + u8 enable; + + u8 pmkid[WMI_PMKID_LEN]; +} __packed; + +/* WMI_START_SCAN_CMD */ +enum wmi_scan_type { + WMI_LONG_SCAN = 0, + WMI_SHORT_SCAN = 1, +}; + +struct wmi_supp_rates { + u8 nrates; + u8 rates[ATH6KL_RATE_MAXSIZE]; +}; + +struct wmi_begin_scan_cmd { + __le32 force_fg_scan; + + /* for legacy cisco AP compatibility */ + __le32 is_legacy; + + /* max duration in the home channel(msec) */ + __le32 home_dwell_time; + + /* time interval between scans (msec) */ + __le32 force_scan_intvl; + + /* no CCK rates */ + __le32 no_cck; + + /* enum wmi_scan_type */ + u8 scan_type; + + /* Supported rates to advertise in the probe request frames */ + struct wmi_supp_rates supp_rates[ATH6KL_NUM_BANDS]; + + /* how many channels follow */ + u8 num_ch; + + /* channels in Mhz */ + __le16 ch_list[1]; +} __packed; + +/* wmi_start_scan_cmd is to be deprecated. Use + * wmi_begin_scan_cmd instead. The new structure supports P2P mgmt + * operations using station interface. + */ +struct wmi_start_scan_cmd { + __le32 force_fg_scan; + + /* for legacy cisco AP compatibility */ + __le32 is_legacy; + + /* max duration in the home channel(msec) */ + __le32 home_dwell_time; + + /* time interval between scans (msec) */ + __le32 force_scan_intvl; + + /* enum wmi_scan_type */ + u8 scan_type; + + /* how many channels follow */ + u8 num_ch; + + /* channels in Mhz */ + __le16 ch_list[1]; +} __packed; + +/* + * Warning: scan control flag value of 0xFF is used to disable + * all flags in WMI_SCAN_PARAMS_CMD. Do not add any more + * flags here + */ +enum wmi_scan_ctrl_flags_bits { + + /* set if can scan in the connect cmd */ + CONNECT_SCAN_CTRL_FLAGS = 0x01, + + /* set if scan for the SSID it is already connected to */ + SCAN_CONNECTED_CTRL_FLAGS = 0x02, + + /* set if enable active scan */ + ACTIVE_SCAN_CTRL_FLAGS = 0x04, + + /* set if enable roam scan when bmiss and lowrssi */ + ROAM_SCAN_CTRL_FLAGS = 0x08, + + /* set if follows customer BSSINFO reporting rule */ + REPORT_BSSINFO_CTRL_FLAGS = 0x10, + + /* if disabled, target doesn't scan after a disconnect event */ + ENABLE_AUTO_CTRL_FLAGS = 0x20, + + /* + * Scan complete event with canceled status will be generated when + * a scan is prempted before it gets completed. + */ + ENABLE_SCAN_ABORT_EVENT = 0x40 +}; + +struct wmi_scan_params_cmd { + /* sec */ + __le16 fg_start_period; + + /* sec */ + __le16 fg_end_period; + + /* sec */ + __le16 bg_period; + + /* msec */ + __le16 maxact_chdwell_time; + + /* msec */ + __le16 pas_chdwell_time; + + /* how many shorts scan for one long */ + u8 short_scan_ratio; + + u8 scan_ctrl_flags; + + /* msec */ + __le16 minact_chdwell_time; + + /* max active scans per ssid */ + __le16 maxact_scan_per_ssid; + + /* msecs */ + __le32 max_dfsch_act_time; +} __packed; + +/* WMI_ENABLE_SCHED_SCAN_CMDID */ +struct wmi_enable_sched_scan_cmd { + u8 enable; +} __packed; + +/* WMI_SET_BSS_FILTER_CMDID */ +enum wmi_bss_filter { + /* no beacons forwarded */ + NONE_BSS_FILTER = 0x0, + + /* all beacons forwarded */ + ALL_BSS_FILTER, + + /* only beacons matching profile */ + PROFILE_FILTER, + + /* all but beacons matching profile */ + ALL_BUT_PROFILE_FILTER, + + /* only beacons matching current BSS */ + CURRENT_BSS_FILTER, + + /* all but beacons matching BSS */ + ALL_BUT_BSS_FILTER, + + /* beacons matching probed ssid */ + PROBED_SSID_FILTER, + + /* beacons matching matched ssid */ + MATCHED_SSID_FILTER, + + /* marker only */ + LAST_BSS_FILTER, +}; + +struct wmi_bss_filter_cmd { + /* see, enum wmi_bss_filter */ + u8 bss_filter; + + /* for alignment */ + u8 reserved1; + + /* for alignment */ + __le16 reserved2; + + __le32 ie_mask; +} __packed; + +/* WMI_SET_PROBED_SSID_CMDID */ +#define MAX_PROBED_SSIDS 16 + +enum wmi_ssid_flag { + /* disables entry */ + DISABLE_SSID_FLAG = 0, + + /* probes specified ssid */ + SPECIFIC_SSID_FLAG = 0x01, + + /* probes for any ssid */ + ANY_SSID_FLAG = 0x02, + + /* match for ssid */ + MATCH_SSID_FLAG = 0x08, +}; + +struct wmi_probed_ssid_cmd { + /* 0 to MAX_PROBED_SSIDS - 1 */ + u8 entry_index; + + /* see, enum wmi_ssid_flg */ + u8 flag; + + u8 ssid_len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; +} __packed; + +/* + * WMI_SET_LISTEN_INT_CMDID + * The Listen interval is between 15 and 3000 TUs + */ +struct wmi_listen_int_cmd { + __le16 listen_intvl; + __le16 num_beacons; +} __packed; + +/* WMI_SET_BMISS_TIME_CMDID */ +struct wmi_bmiss_time_cmd { + __le16 bmiss_time; + __le16 num_beacons; +}; + +/* WMI_STA_ENHANCE_BMISS_CMDID */ +struct wmi_sta_bmiss_enhance_cmd { + u8 enable; +} __packed; + +struct wmi_set_regdomain_cmd { + u8 length; + u8 iso_name[2]; +} __packed; + +/* WMI_SET_POWER_MODE_CMDID */ +enum wmi_power_mode { + REC_POWER = 0x01, + MAX_PERF_POWER, +}; + +struct wmi_power_mode_cmd { + /* see, enum wmi_power_mode */ + u8 pwr_mode; +} __packed; + +/* + * Policy to determnine whether power save failure event should be sent to + * host during scanning + */ +enum power_save_fail_event_policy { + SEND_POWER_SAVE_FAIL_EVENT_ALWAYS = 1, + IGNORE_PS_FAIL_DURING_SCAN = 2, +}; + +struct wmi_power_params_cmd { + /* msec */ + __le16 idle_period; + + __le16 pspoll_number; + __le16 dtim_policy; + __le16 tx_wakeup_policy; + __le16 num_tx_to_wakeup; + __le16 ps_fail_event_policy; +} __packed; + +/* + * Ratemask for below modes should be passed + * to WMI_SET_TX_SELECT_RATES_CMDID. + * AR6003 has 32 bit mask for each modes. + * First 12 bits for legacy rates, 13 to 20 + * bits for HT 20 rates and 21 to 28 bits for + * HT 40 rates + */ +enum wmi_mode_phy { + WMI_RATES_MODE_11A = 0, + WMI_RATES_MODE_11G, + WMI_RATES_MODE_11B, + WMI_RATES_MODE_11GONLY, + WMI_RATES_MODE_11A_HT20, + WMI_RATES_MODE_11G_HT20, + WMI_RATES_MODE_11A_HT40, + WMI_RATES_MODE_11G_HT40, + WMI_RATES_MODE_MAX +}; + +/* WMI_SET_TX_SELECT_RATES_CMDID */ +struct wmi_set_tx_select_rates32_cmd { + __le32 ratemask[WMI_RATES_MODE_MAX]; +} __packed; + +/* WMI_SET_TX_SELECT_RATES_CMDID */ +struct wmi_set_tx_select_rates64_cmd { + __le64 ratemask[WMI_RATES_MODE_MAX]; +} __packed; + +/* WMI_SET_DISC_TIMEOUT_CMDID */ +struct wmi_disc_timeout_cmd { + /* seconds */ + u8 discon_timeout; +} __packed; + +enum dir_type { + UPLINK_TRAFFIC = 0, + DNLINK_TRAFFIC = 1, + BIDIR_TRAFFIC = 2, +}; + +enum voiceps_cap_type { + DISABLE_FOR_THIS_AC = 0, + ENABLE_FOR_THIS_AC = 1, + ENABLE_FOR_ALL_AC = 2, +}; + +enum traffic_type { + TRAFFIC_TYPE_APERIODIC = 0, + TRAFFIC_TYPE_PERIODIC = 1, +}; + +/* WMI_SYNCHRONIZE_CMDID */ +struct wmi_sync_cmd { + u8 data_sync_map; +} __packed; + +/* WMI_CREATE_PSTREAM_CMDID */ +struct wmi_create_pstream_cmd { + /* msec */ + __le32 min_service_int; + + /* msec */ + __le32 max_service_int; + + /* msec */ + __le32 inactivity_int; + + /* msec */ + __le32 suspension_int; + + __le32 service_start_time; + + /* in bps */ + __le32 min_data_rate; + + /* in bps */ + __le32 mean_data_rate; + + /* in bps */ + __le32 peak_data_rate; + + __le32 max_burst_size; + __le32 delay_bound; + + /* in bps */ + __le32 min_phy_rate; + + __le32 sba; + __le32 medium_time; + + /* in octects */ + __le16 nominal_msdu; + + /* in octects */ + __le16 max_msdu; + + u8 traffic_class; + + /* see, enum dir_type */ + u8 traffic_direc; + + u8 rx_queue_num; + + /* see, enum traffic_type */ + u8 traffic_type; + + /* see, enum voiceps_cap_type */ + u8 voice_psc_cap; + u8 tsid; + + /* 802.1D user priority */ + u8 user_pri; + + /* nominal phy rate */ + u8 nominal_phy; +} __packed; + +/* WMI_DELETE_PSTREAM_CMDID */ +struct wmi_delete_pstream_cmd { + u8 tx_queue_num; + u8 rx_queue_num; + u8 traffic_direc; + u8 traffic_class; + u8 tsid; +} __packed; + +/* WMI_SET_CHANNEL_PARAMS_CMDID */ +enum wmi_phy_mode { + WMI_11A_MODE = 0x1, + WMI_11G_MODE = 0x2, + WMI_11AG_MODE = 0x3, + WMI_11B_MODE = 0x4, + WMI_11GONLY_MODE = 0x5, + WMI_11G_HT20 = 0x6, +}; + +#define WMI_MAX_CHANNELS 32 + +/* + * WMI_RSSI_THRESHOLD_PARAMS_CMDID + * Setting the polltime to 0 would disable polling. Threshold values are + * in the ascending order, and should agree to: + * (lowThreshold_lowerVal < lowThreshold_upperVal < highThreshold_lowerVal + * < highThreshold_upperVal) + */ + +struct wmi_rssi_threshold_params_cmd { + /* polling time as a factor of LI */ + __le32 poll_time; + + /* lowest of upper */ + a_sle16 thresh_above1_val; + + a_sle16 thresh_above2_val; + a_sle16 thresh_above3_val; + a_sle16 thresh_above4_val; + a_sle16 thresh_above5_val; + + /* highest of upper */ + a_sle16 thresh_above6_val; + + /* lowest of bellow */ + a_sle16 thresh_below1_val; + + a_sle16 thresh_below2_val; + a_sle16 thresh_below3_val; + a_sle16 thresh_below4_val; + a_sle16 thresh_below5_val; + + /* highest of bellow */ + a_sle16 thresh_below6_val; + + /* "alpha" */ + u8 weight; + + u8 reserved[3]; +} __packed; + +/* + * WMI_SNR_THRESHOLD_PARAMS_CMDID + * Setting the polltime to 0 would disable polling. + */ + +struct wmi_snr_threshold_params_cmd { + /* polling time as a factor of LI */ + __le32 poll_time; + + /* "alpha" */ + u8 weight; + + /* lowest of uppper */ + u8 thresh_above1_val; + + u8 thresh_above2_val; + u8 thresh_above3_val; + + /* highest of upper */ + u8 thresh_above4_val; + + /* lowest of bellow */ + u8 thresh_below1_val; + + u8 thresh_below2_val; + u8 thresh_below3_val; + + /* highest of bellow */ + u8 thresh_below4_val; + + u8 reserved[3]; +} __packed; + +/* Don't report BSSs with signal (RSSI) below this threshold */ +struct wmi_set_rssi_filter_cmd { + s8 rssi; +} __packed; + +enum wmi_preamble_policy { + WMI_IGNORE_BARKER_IN_ERP = 0, + WMI_FOLLOW_BARKER_IN_ERP, +}; + +struct wmi_set_lpreamble_cmd { + u8 status; + u8 preamble_policy; +} __packed; + +struct wmi_set_rts_cmd { + __le16 threshold; +} __packed; + +/* WMI_SET_TX_PWR_CMDID */ +struct wmi_set_tx_pwr_cmd { + /* in dbM units */ + u8 dbM; +} __packed; + +struct wmi_tx_pwr_reply { + /* in dbM units */ + u8 dbM; +} __packed; + +struct wmi_report_sleep_state_event { + __le32 sleep_state; +}; + +enum wmi_report_sleep_status { + WMI_REPORT_SLEEP_STATUS_IS_DEEP_SLEEP = 0, + WMI_REPORT_SLEEP_STATUS_IS_AWAKE +}; +enum target_event_report_config { + /* default */ + DISCONN_EVT_IN_RECONN = 0, + + NO_DISCONN_EVT_IN_RECONN +}; + +struct wmi_mcast_filter_cmd { + u8 mcast_all_enable; +} __packed; + +#define ATH6KL_MCAST_FILTER_MAC_ADDR_SIZE 6 +struct wmi_mcast_filter_add_del_cmd { + u8 mcast_mac[ATH6KL_MCAST_FILTER_MAC_ADDR_SIZE]; +} __packed; + +struct wmi_set_htcap_cmd { + u8 band; + u8 ht_enable; + u8 ht40_supported; + u8 ht20_sgi; + u8 ht40_sgi; + u8 intolerant_40mhz; + u8 max_ampdu_len_exp; +} __packed; + +/* Command Replies */ + +/* WMI_GET_CHANNEL_LIST_CMDID reply */ +struct wmi_channel_list_reply { + u8 reserved; + + /* number of channels in reply */ + u8 num_ch; + + /* channel in Mhz */ + __le16 ch_list[1]; +} __packed; + +/* List of Events (target to host) */ +enum wmi_event_id { + WMI_READY_EVENTID = 0x1001, + WMI_CONNECT_EVENTID, + WMI_DISCONNECT_EVENTID, + WMI_BSSINFO_EVENTID, + WMI_CMDERROR_EVENTID, + WMI_REGDOMAIN_EVENTID, + WMI_PSTREAM_TIMEOUT_EVENTID, + WMI_NEIGHBOR_REPORT_EVENTID, + WMI_TKIP_MICERR_EVENTID, + WMI_SCAN_COMPLETE_EVENTID, /* 0x100a */ + WMI_REPORT_STATISTICS_EVENTID, + WMI_RSSI_THRESHOLD_EVENTID, + WMI_ERROR_REPORT_EVENTID, + WMI_OPT_RX_FRAME_EVENTID, + WMI_REPORT_ROAM_TBL_EVENTID, + WMI_EXTENSION_EVENTID, + WMI_CAC_EVENTID, + WMI_SNR_THRESHOLD_EVENTID, + WMI_LQ_THRESHOLD_EVENTID, + WMI_TX_RETRY_ERR_EVENTID, /* 0x1014 */ + WMI_REPORT_ROAM_DATA_EVENTID, + WMI_TEST_EVENTID, + WMI_APLIST_EVENTID, + WMI_GET_WOW_LIST_EVENTID, + WMI_GET_PMKID_LIST_EVENTID, + WMI_CHANNEL_CHANGE_EVENTID, + WMI_PEER_NODE_EVENTID, + WMI_PSPOLL_EVENTID, + WMI_DTIMEXPIRY_EVENTID, + WMI_WLAN_VERSION_EVENTID, + WMI_SET_PARAMS_REPLY_EVENTID, + WMI_ADDBA_REQ_EVENTID, /*0x1020 */ + WMI_ADDBA_RESP_EVENTID, + WMI_DELBA_REQ_EVENTID, + WMI_TX_COMPLETE_EVENTID, + WMI_HCI_EVENT_EVENTID, + WMI_ACL_DATA_EVENTID, + WMI_REPORT_SLEEP_STATE_EVENTID, + WMI_REPORT_BTCOEX_STATS_EVENTID, + WMI_REPORT_BTCOEX_CONFIG_EVENTID, + WMI_GET_PMK_EVENTID, + + /* DFS Events */ + WMI_DFS_HOST_ATTACH_EVENTID, + WMI_DFS_HOST_INIT_EVENTID, + WMI_DFS_RESET_DELAYLINES_EVENTID, + WMI_DFS_RESET_RADARQ_EVENTID, + WMI_DFS_RESET_AR_EVENTID, + WMI_DFS_RESET_ARQ_EVENTID, + WMI_DFS_SET_DUR_MULTIPLIER_EVENTID, + WMI_DFS_SET_BANGRADAR_EVENTID, + WMI_DFS_SET_DEBUGLEVEL_EVENTID, + WMI_DFS_PHYERR_EVENTID, + + /* CCX Evants */ + WMI_CCX_RM_STATUS_EVENTID, + + /* P2P Events */ + WMI_P2P_GO_NEG_RESULT_EVENTID, + + WMI_WAC_SCAN_DONE_EVENTID, + WMI_WAC_REPORT_BSS_EVENTID, + WMI_WAC_START_WPS_EVENTID, + WMI_WAC_CTRL_REQ_REPLY_EVENTID, + + WMI_REPORT_WMM_PARAMS_EVENTID, + WMI_WAC_REJECT_WPS_EVENTID, + + /* More P2P Events */ + WMI_P2P_GO_NEG_REQ_EVENTID, + WMI_P2P_INVITE_REQ_EVENTID, + WMI_P2P_INVITE_RCVD_RESULT_EVENTID, + WMI_P2P_INVITE_SENT_RESULT_EVENTID, + WMI_P2P_PROV_DISC_RESP_EVENTID, + WMI_P2P_PROV_DISC_REQ_EVENTID, + + /* RFKILL Events */ + WMI_RFKILL_STATE_CHANGE_EVENTID, + WMI_RFKILL_GET_MODE_CMD_EVENTID, + + WMI_P2P_START_SDPD_EVENTID, + WMI_P2P_SDPD_RX_EVENTID, + + WMI_SET_HOST_SLEEP_MODE_CMD_PROCESSED_EVENTID = 0x1047, + + WMI_THIN_RESERVED_START_EVENTID = 0x8000, + /* Events in this range are reserved for thinmode */ + WMI_THIN_RESERVED_END_EVENTID = 0x8fff, + + WMI_SET_CHANNEL_EVENTID, + WMI_ASSOC_REQ_EVENTID, + + /* Generic ACS event */ + WMI_ACS_EVENTID, + WMI_STORERECALL_STORE_EVENTID, + WMI_WOW_EXT_WAKE_EVENTID, + WMI_GTK_OFFLOAD_STATUS_EVENTID, + WMI_NETWORK_LIST_OFFLOAD_EVENTID, + WMI_REMAIN_ON_CHNL_EVENTID, + WMI_CANCEL_REMAIN_ON_CHNL_EVENTID, + WMI_TX_STATUS_EVENTID, + WMI_RX_PROBE_REQ_EVENTID, + WMI_P2P_CAPABILITIES_EVENTID, + WMI_RX_ACTION_EVENTID, + WMI_P2P_INFO_EVENTID, + + /* WPS Events */ + WMI_WPS_GET_STATUS_EVENTID, + WMI_WPS_PROFILE_EVENTID, + + /* more P2P events */ + WMI_NOA_INFO_EVENTID, + WMI_OPPPS_INFO_EVENTID, + WMI_PORT_STATUS_EVENTID, + + /* 802.11w */ + WMI_GET_RSN_CAP_EVENTID, + + WMI_TXE_NOTIFY_EVENTID, +}; + +struct wmi_ready_event_2 { + __le32 sw_version; + __le32 abi_version; + u8 mac_addr[ETH_ALEN]; + u8 phy_cap; +} __packed; + +/* WMI_PHY_CAPABILITY */ +enum wmi_phy_cap { + WMI_11A_CAP = 0x01, + WMI_11G_CAP = 0x02, + WMI_11AG_CAP = 0x03, + WMI_11AN_CAP = 0x04, + WMI_11GN_CAP = 0x05, + WMI_11AGN_CAP = 0x06, +}; + +/* Connect Event */ +struct wmi_connect_event { + union { + struct { + __le16 ch; + u8 bssid[ETH_ALEN]; + __le16 listen_intvl; + __le16 beacon_intvl; + __le32 nw_type; + } sta; + struct { + u8 phymode; + u8 aid; + u8 mac_addr[ETH_ALEN]; + u8 auth; + u8 keymgmt; + __le16 cipher; + u8 apsd_info; + u8 unused[3]; + } ap_sta; + struct { + __le16 ch; + u8 bssid[ETH_ALEN]; + u8 unused[8]; + } ap_bss; + } u; + u8 beacon_ie_len; + u8 assoc_req_len; + u8 assoc_resp_len; + u8 assoc_info[1]; +} __packed; + +/* Disconnect Event */ +enum wmi_disconnect_reason { + NO_NETWORK_AVAIL = 0x01, + + /* bmiss */ + LOST_LINK = 0x02, + + DISCONNECT_CMD = 0x03, + BSS_DISCONNECTED = 0x04, + AUTH_FAILED = 0x05, + ASSOC_FAILED = 0x06, + NO_RESOURCES_AVAIL = 0x07, + CSERV_DISCONNECT = 0x08, + INVALID_PROFILE = 0x0a, + DOT11H_CHANNEL_SWITCH = 0x0b, + PROFILE_MISMATCH = 0x0c, + CONNECTION_EVICTED = 0x0d, + IBSS_MERGE = 0xe, +}; + +/* AP mode disconnect proto_reasons */ +enum ap_disconnect_reason { + WMI_AP_REASON_STA_LEFT = 101, + WMI_AP_REASON_FROM_HOST = 102, + WMI_AP_REASON_COMM_TIMEOUT = 103, + WMI_AP_REASON_MAX_STA = 104, + WMI_AP_REASON_ACL = 105, + WMI_AP_REASON_STA_ROAM = 106, + WMI_AP_REASON_DFS_CHANNEL = 107, +}; + +#define ATH6KL_COUNTRY_RD_SHIFT 16 + +struct ath6kl_wmi_regdomain { + __le32 reg_code; +}; + +struct wmi_disconnect_event { + /* reason code, see 802.11 spec. */ + __le16 proto_reason_status; + + /* set if known */ + u8 bssid[ETH_ALEN]; + + /* see WMI_DISCONNECT_REASON */ + u8 disconn_reason; + + u8 assoc_resp_len; + u8 assoc_info[1]; +} __packed; + +/* + * BSS Info Event. + * Mechanism used to inform host of the presence and characteristic of + * wireless networks present. Consists of bss info header followed by + * the beacon or probe-response frame body. The 802.11 header is no included. + */ +enum wmi_bi_ftype { + BEACON_FTYPE = 0x1, + PROBERESP_FTYPE, + ACTION_MGMT_FTYPE, + PROBEREQ_FTYPE, +}; + +#define DEF_LRSSI_SCAN_PERIOD 5 +#define DEF_LRSSI_ROAM_THRESHOLD 20 +#define DEF_LRSSI_ROAM_FLOOR 60 +#define DEF_SCAN_FOR_ROAM_INTVL 2 + +enum wmi_roam_ctrl { + WMI_FORCE_ROAM = 1, + WMI_SET_ROAM_MODE, + WMI_SET_HOST_BIAS, + WMI_SET_LRSSI_SCAN_PARAMS, +}; + +enum wmi_roam_mode { + WMI_DEFAULT_ROAM_MODE = 1, /* RSSI based roam */ + WMI_HOST_BIAS_ROAM_MODE = 2, /* Host bias based roam */ + WMI_LOCK_BSS_MODE = 3, /* Lock to the current BSS */ +}; + +struct bss_bias { + u8 bssid[ETH_ALEN]; + s8 bias; +} __packed; + +struct bss_bias_info { + u8 num_bss; + struct bss_bias bss_bias[0]; +} __packed; + +struct low_rssi_scan_params { + __le16 lrssi_scan_period; + a_sle16 lrssi_scan_threshold; + a_sle16 lrssi_roam_threshold; + u8 roam_rssi_floor; + u8 reserved[1]; +} __packed; + +struct roam_ctrl_cmd { + union { + u8 bssid[ETH_ALEN]; /* WMI_FORCE_ROAM */ + u8 roam_mode; /* WMI_SET_ROAM_MODE */ + struct bss_bias_info bss; /* WMI_SET_HOST_BIAS */ + struct low_rssi_scan_params params; /* WMI_SET_LRSSI_SCAN_PARAMS + */ + } __packed info; + u8 roam_ctrl; +} __packed; + +struct set_beacon_int_cmd { + __le32 beacon_intvl; +} __packed; + +struct set_dtim_cmd { + __le32 dtim_period; +} __packed; + +/* BSS INFO HDR version 2.0 */ +struct wmi_bss_info_hdr2 { + __le16 ch; /* frequency in MHz */ + + /* see, enum wmi_bi_ftype */ + u8 frame_type; + + u8 snr; /* note: rssi = snr - 95 dBm */ + u8 bssid[ETH_ALEN]; + __le16 ie_mask; +} __packed; + +/* Command Error Event */ +enum wmi_error_code { + INVALID_PARAM = 0x01, + ILLEGAL_STATE = 0x02, + INTERNAL_ERROR = 0x03, +}; + +struct wmi_cmd_error_event { + __le16 cmd_id; + u8 err_code; +} __packed; + +struct wmi_pstream_timeout_event { + u8 tx_queue_num; + u8 rx_queue_num; + u8 traffic_direc; + u8 traffic_class; +} __packed; + +/* + * The WMI_NEIGHBOR_REPORT Event is generated by the target to inform + * the host of BSS's it has found that matches the current profile. + * It can be used by the host to cache PMKs and/to initiate pre-authentication + * if the BSS supports it. The first bssid is always the current associated + * BSS. + * The bssid and bssFlags information repeats according to the number + * or APs reported. + */ +enum wmi_bss_flags { + WMI_DEFAULT_BSS_FLAGS = 0x00, + WMI_PREAUTH_CAPABLE_BSS = 0x01, + WMI_PMKID_VALID_BSS = 0x02, +}; + +struct wmi_neighbor_info { + u8 bssid[ETH_ALEN]; + u8 bss_flags; /* enum wmi_bss_flags */ +} __packed; + +struct wmi_neighbor_report_event { + u8 num_neighbors; + struct wmi_neighbor_info neighbor[0]; +} __packed; + +/* TKIP MIC Error Event */ +struct wmi_tkip_micerr_event { + u8 key_id; + u8 is_mcast; +} __packed; + +enum wmi_scan_status { + WMI_SCAN_STATUS_SUCCESS = 0, +}; + +/* WMI_SCAN_COMPLETE_EVENTID */ +struct wmi_scan_complete_event { + a_sle32 status; +} __packed; + +#define MAX_OPT_DATA_LEN 1400 + +/* + * Special frame receive Event. + * Mechanism used to inform host of the receiption of the special frames. + * Consists of special frame info header followed by special frame body. + * The 802.11 header is not included. + */ +struct wmi_opt_rx_info_hdr { + __le16 ch; + u8 frame_type; + s8 snr; + u8 src_addr[ETH_ALEN]; + u8 bssid[ETH_ALEN]; +} __packed; + +/* Reporting statistic */ +struct tx_stats { + __le32 pkt; + __le32 byte; + __le32 ucast_pkt; + __le32 ucast_byte; + __le32 mcast_pkt; + __le32 mcast_byte; + __le32 bcast_pkt; + __le32 bcast_byte; + __le32 rts_success_cnt; + __le32 pkt_per_ac[4]; + __le32 err_per_ac[4]; + + __le32 err; + __le32 fail_cnt; + __le32 retry_cnt; + __le32 mult_retry_cnt; + __le32 rts_fail_cnt; + a_sle32 ucast_rate; +} __packed; + +struct rx_stats { + __le32 pkt; + __le32 byte; + __le32 ucast_pkt; + __le32 ucast_byte; + __le32 mcast_pkt; + __le32 mcast_byte; + __le32 bcast_pkt; + __le32 bcast_byte; + __le32 frgment_pkt; + + __le32 err; + __le32 crc_err; + __le32 key_cache_miss; + __le32 decrypt_err; + __le32 dupl_frame; + a_sle32 ucast_rate; +} __packed; + +#define RATE_INDEX_WITHOUT_SGI_MASK 0x7f +#define RATE_INDEX_MSB 0x80 + +struct tkip_ccmp_stats { + __le32 tkip_local_mic_fail; + __le32 tkip_cnter_measures_invoked; + __le32 tkip_replays; + __le32 tkip_fmt_err; + __le32 ccmp_fmt_err; + __le32 ccmp_replays; +} __packed; + +struct pm_stats { + __le32 pwr_save_failure_cnt; + __le16 stop_tx_failure_cnt; + __le16 atim_tx_failure_cnt; + __le16 atim_rx_failure_cnt; + __le16 bcn_rx_failure_cnt; +} __packed; + +struct cserv_stats { + __le32 cs_bmiss_cnt; + __le32 cs_low_rssi_cnt; + __le16 cs_connect_cnt; + __le16 cs_discon_cnt; + a_sle16 cs_ave_beacon_rssi; + __le16 cs_roam_count; + a_sle16 cs_rssi; + u8 cs_snr; + u8 cs_ave_beacon_snr; + u8 cs_last_roam_msec; +} __packed; + +struct wlan_net_stats { + struct tx_stats tx; + struct rx_stats rx; + struct tkip_ccmp_stats tkip_ccmp_stats; +} __packed; + +struct arp_stats { + __le32 arp_received; + __le32 arp_matched; + __le32 arp_replied; +} __packed; + +struct wlan_wow_stats { + __le32 wow_pkt_dropped; + __le16 wow_evt_discarded; + u8 wow_host_pkt_wakeups; + u8 wow_host_evt_wakeups; +} __packed; + +struct wmi_target_stats { + __le32 lq_val; + a_sle32 noise_floor_calib; + struct pm_stats pm_stats; + struct wlan_net_stats stats; + struct wlan_wow_stats wow_stats; + struct arp_stats arp_stats; + struct cserv_stats cserv_stats; +} __packed; + +/* + * WMI_RSSI_THRESHOLD_EVENTID. + * Indicate the RSSI events to host. Events are indicated when we breach a + * thresold value. + */ +enum wmi_rssi_threshold_val { + WMI_RSSI_THRESHOLD1_ABOVE = 0, + WMI_RSSI_THRESHOLD2_ABOVE, + WMI_RSSI_THRESHOLD3_ABOVE, + WMI_RSSI_THRESHOLD4_ABOVE, + WMI_RSSI_THRESHOLD5_ABOVE, + WMI_RSSI_THRESHOLD6_ABOVE, + WMI_RSSI_THRESHOLD1_BELOW, + WMI_RSSI_THRESHOLD2_BELOW, + WMI_RSSI_THRESHOLD3_BELOW, + WMI_RSSI_THRESHOLD4_BELOW, + WMI_RSSI_THRESHOLD5_BELOW, + WMI_RSSI_THRESHOLD6_BELOW +}; + +struct wmi_rssi_threshold_event { + a_sle16 rssi; + u8 range; +} __packed; + +enum wmi_snr_threshold_val { + WMI_SNR_THRESHOLD1_ABOVE = 1, + WMI_SNR_THRESHOLD1_BELOW, + WMI_SNR_THRESHOLD2_ABOVE, + WMI_SNR_THRESHOLD2_BELOW, + WMI_SNR_THRESHOLD3_ABOVE, + WMI_SNR_THRESHOLD3_BELOW, + WMI_SNR_THRESHOLD4_ABOVE, + WMI_SNR_THRESHOLD4_BELOW +}; + +struct wmi_snr_threshold_event { + /* see, enum wmi_snr_threshold_val */ + u8 range; + + u8 snr; +} __packed; + +/* WMI_REPORT_ROAM_TBL_EVENTID */ +#define MAX_ROAM_TBL_CAND 5 + +struct wmi_bss_roam_info { + a_sle32 roam_util; + u8 bssid[ETH_ALEN]; + s8 rssi; + s8 rssidt; + s8 last_rssi; + s8 util; + s8 bias; + + /* for alignment */ + u8 reserved; +} __packed; + +struct wmi_target_roam_tbl { + __le16 roam_mode; + __le16 num_entries; + struct wmi_bss_roam_info info[]; +} __packed; + +/* WMI_CAC_EVENTID */ +enum cac_indication { + CAC_INDICATION_ADMISSION = 0x00, + CAC_INDICATION_ADMISSION_RESP = 0x01, + CAC_INDICATION_DELETE = 0x02, + CAC_INDICATION_NO_RESP = 0x03, +}; + +#define WMM_TSPEC_IE_LEN 63 + +struct wmi_cac_event { + u8 ac; + u8 cac_indication; + u8 status_code; + u8 tspec_suggestion[WMM_TSPEC_IE_LEN]; +} __packed; + +/* WMI_APLIST_EVENTID */ + +enum aplist_ver { + APLIST_VER1 = 1, +}; + +struct wmi_ap_info_v1 { + u8 bssid[ETH_ALEN]; + __le16 channel; +} __packed; + +union wmi_ap_info { + struct wmi_ap_info_v1 ap_info_v1; +} __packed; + +struct wmi_aplist_event { + u8 ap_list_ver; + u8 num_ap; + union wmi_ap_info ap_list[1]; +} __packed; + +/* Developer Commands */ + +/* + * WMI_SET_BITRATE_CMDID + * + * Get bit rate cmd uses same definition as set bit rate cmd + */ +enum wmi_bit_rate { + RATE_AUTO = -1, + RATE_1Mb = 0, + RATE_2Mb = 1, + RATE_5_5Mb = 2, + RATE_11Mb = 3, + RATE_6Mb = 4, + RATE_9Mb = 5, + RATE_12Mb = 6, + RATE_18Mb = 7, + RATE_24Mb = 8, + RATE_36Mb = 9, + RATE_48Mb = 10, + RATE_54Mb = 11, + RATE_MCS_0_20 = 12, + RATE_MCS_1_20 = 13, + RATE_MCS_2_20 = 14, + RATE_MCS_3_20 = 15, + RATE_MCS_4_20 = 16, + RATE_MCS_5_20 = 17, + RATE_MCS_6_20 = 18, + RATE_MCS_7_20 = 19, + RATE_MCS_0_40 = 20, + RATE_MCS_1_40 = 21, + RATE_MCS_2_40 = 22, + RATE_MCS_3_40 = 23, + RATE_MCS_4_40 = 24, + RATE_MCS_5_40 = 25, + RATE_MCS_6_40 = 26, + RATE_MCS_7_40 = 27, +}; + +struct wmi_bit_rate_reply { + /* see, enum wmi_bit_rate */ + s8 rate_index; +} __packed; + +/* + * WMI_SET_FIXRATES_CMDID + * + * Get fix rates cmd uses same definition as set fix rates cmd + */ +struct wmi_fix_rates_reply { + /* see wmi_bit_rate */ + __le32 fix_rate_mask; +} __packed; + +enum roam_data_type { + /* get the roam time data */ + ROAM_DATA_TIME = 1, +}; + +struct wmi_target_roam_time { + __le32 disassoc_time; + __le32 no_txrx_time; + __le32 assoc_time; + __le32 allow_txrx_time; + u8 disassoc_bssid[ETH_ALEN]; + s8 disassoc_bss_rssi; + u8 assoc_bssid[ETH_ALEN]; + s8 assoc_bss_rssi; +} __packed; + +enum wmi_txop_cfg { + WMI_TXOP_DISABLED = 0, + WMI_TXOP_ENABLED +}; + +struct wmi_set_wmm_txop_cmd { + u8 txop_enable; +} __packed; + +struct wmi_set_keepalive_cmd { + u8 keep_alive_intvl; +} __packed; + +struct wmi_get_keepalive_cmd { + __le32 configured; + u8 keep_alive_intvl; +} __packed; + +struct wmi_set_appie_cmd { + u8 mgmt_frm_type; /* enum wmi_mgmt_frame_type */ + u8 ie_len; + u8 ie_info[0]; +} __packed; + +struct wmi_set_ie_cmd { + u8 ie_id; + u8 ie_field; /* enum wmi_ie_field_type */ + u8 ie_len; + u8 reserved; + u8 ie_info[0]; +} __packed; + +/* Notify the WSC registration status to the target */ +#define WSC_REG_ACTIVE 1 +#define WSC_REG_INACTIVE 0 + +#define WOW_MAX_FILTERS_PER_LIST 4 +#define WOW_PATTERN_SIZE 64 + +#define MAC_MAX_FILTERS_PER_LIST 4 + +struct wow_filter { + u8 wow_valid_filter; + u8 wow_filter_id; + u8 wow_filter_size; + u8 wow_filter_offset; + u8 wow_filter_mask[WOW_PATTERN_SIZE]; + u8 wow_filter_pattern[WOW_PATTERN_SIZE]; +} __packed; + +#define MAX_IP_ADDRS 2 + +struct wmi_set_ip_cmd { + /* IP in network byte order */ + __be32 ips[MAX_IP_ADDRS]; +} __packed; + +enum ath6kl_wow_filters { + WOW_FILTER_SSID = BIT(1), + WOW_FILTER_OPTION_MAGIC_PACKET = BIT(2), + WOW_FILTER_OPTION_EAP_REQ = BIT(3), + WOW_FILTER_OPTION_PATTERNS = BIT(4), + WOW_FILTER_OPTION_OFFLOAD_ARP = BIT(5), + WOW_FILTER_OPTION_OFFLOAD_NS = BIT(6), + WOW_FILTER_OPTION_OFFLOAD_GTK = BIT(7), + WOW_FILTER_OPTION_8021X_4WAYHS = BIT(8), + WOW_FILTER_OPTION_NLO_DISCVRY = BIT(9), + WOW_FILTER_OPTION_NWK_DISASSOC = BIT(10), + WOW_FILTER_OPTION_GTK_ERROR = BIT(11), + WOW_FILTER_OPTION_TEST_MODE = BIT(15), +}; + +enum ath6kl_host_mode { + ATH6KL_HOST_MODE_AWAKE, + ATH6KL_HOST_MODE_ASLEEP, +}; + +struct wmi_set_host_sleep_mode_cmd { + __le32 awake; + __le32 asleep; +} __packed; + +enum ath6kl_wow_mode { + ATH6KL_WOW_MODE_DISABLE, + ATH6KL_WOW_MODE_ENABLE, +}; + +struct wmi_set_wow_mode_cmd { + __le32 enable_wow; + __le32 filter; + __le16 host_req_delay; +} __packed; + +struct wmi_add_wow_pattern_cmd { + u8 filter_list_id; + u8 filter_size; + u8 filter_offset; + u8 filter[0]; +} __packed; + +struct wmi_del_wow_pattern_cmd { + __le16 filter_list_id; + __le16 filter_id; +} __packed; + +/* WMI_SET_TXE_NOTIFY_CMDID */ +struct wmi_txe_notify_cmd { + __le32 rate; + __le32 pkts; + __le32 intvl; +} __packed; + +/* WMI_TXE_NOTIFY_EVENTID */ +struct wmi_txe_notify_event { + __le32 rate; + __le32 pkts; +} __packed; + +/* WMI_SET_AKMP_PARAMS_CMD */ + +struct wmi_pmkid { + u8 pmkid[WMI_PMKID_LEN]; +} __packed; + +/* WMI_GET_PMKID_LIST_CMD Reply */ +struct wmi_pmkid_list_reply { + __le32 num_pmkid; + u8 bssid_list[ETH_ALEN][1]; + struct wmi_pmkid pmkid_list[1]; +} __packed; + +/* WMI_ADDBA_REQ_EVENTID */ +struct wmi_addba_req_event { + u8 tid; + u8 win_sz; + __le16 st_seq_no; + + /* f/w response for ADDBA Req; OK (0) or failure (!=0) */ + u8 status; +} __packed; + +/* WMI_ADDBA_RESP_EVENTID */ +struct wmi_addba_resp_event { + u8 tid; + + /* OK (0), failure (!=0) */ + u8 status; + + /* three values: not supported(0), 3839, 8k */ + __le16 amsdu_sz; +} __packed; + +/* WMI_DELBA_EVENTID + * f/w received a DELBA for peer and processed it. + * Host is notified of this + */ +struct wmi_delba_event { + u8 tid; + u8 is_peer_initiator; + __le16 reason_code; +} __packed; + +#define PEER_NODE_JOIN_EVENT 0x00 +#define PEER_NODE_LEAVE_EVENT 0x01 +#define PEER_FIRST_NODE_JOIN_EVENT 0x10 +#define PEER_LAST_NODE_LEAVE_EVENT 0x11 + +struct wmi_peer_node_event { + u8 event_code; + u8 peer_mac_addr[ETH_ALEN]; +} __packed; + +/* Transmit complete event data structure(s) */ + +/* version 1 of tx complete msg */ +struct tx_complete_msg_v1 { +#define TX_COMPLETE_STATUS_SUCCESS 0 +#define TX_COMPLETE_STATUS_RETRIES 1 +#define TX_COMPLETE_STATUS_NOLINK 2 +#define TX_COMPLETE_STATUS_TIMEOUT 3 +#define TX_COMPLETE_STATUS_OTHER 4 + + u8 status; + + /* packet ID to identify parent packet */ + u8 pkt_id; + + /* rate index on successful transmission */ + u8 rate_idx; + + /* number of ACK failures in tx attempt */ + u8 ack_failures; +} __packed; + +struct wmi_tx_complete_event { + /* no of tx comp msgs following this struct */ + u8 num_msg; + + /* length in bytes for each individual msg following this struct */ + u8 msg_len; + + /* version of tx complete msg data following this struct */ + u8 msg_type; + + /* individual messages follow this header */ + u8 reserved; +} __packed; + +/* + * ------- AP Mode definitions -------------- + */ + +/* + * !!! Warning !!! + * -Changing the following values needs compilation of both driver and firmware + */ +#define AP_MAX_NUM_STA 10 + +/* Spl. AID used to set DTIM flag in the beacons */ +#define MCAST_AID 0xFF + +#define DEF_AP_COUNTRY_CODE "US " + +/* Used with WMI_AP_SET_NUM_STA_CMDID */ + +/* + * Used with WMI_AP_SET_MLME_CMDID + */ + +/* MLME Commands */ +#define WMI_AP_MLME_ASSOC 1 /* associate station */ +#define WMI_AP_DISASSOC 2 /* disassociate station */ +#define WMI_AP_DEAUTH 3 /* deauthenticate station */ +#define WMI_AP_MLME_AUTHORIZE 4 /* authorize station */ +#define WMI_AP_MLME_UNAUTHORIZE 5 /* unauthorize station */ + +struct wmi_ap_set_mlme_cmd { + u8 mac[ETH_ALEN]; + __le16 reason; /* 802.11 reason code */ + u8 cmd; /* operation to perform (WMI_AP_*) */ +} __packed; + +struct wmi_ap_set_pvb_cmd { + __le32 flag; + __le16 rsvd; + __le16 aid; +} __packed; + +struct wmi_rx_frame_format_cmd { + /* version of meta data for rx packets <0 = default> (0-7 = valid) */ + u8 meta_ver; + + /* + * 1 == leave .11 header intact, + * 0 == replace .11 header with .3 + */ + u8 dot11_hdr; + + /* + * 1 == defragmentation is performed by host, + * 0 == performed by target + */ + u8 defrag_on_host; + + /* for alignment */ + u8 reserved[1]; +} __packed; + +struct wmi_ap_hidden_ssid_cmd { + u8 hidden_ssid; +} __packed; + +struct wmi_set_inact_period_cmd { + __le32 inact_period; + u8 num_null_func; +} __packed; + +/* AP mode events */ +struct wmi_ap_set_apsd_cmd { + u8 enable; +} __packed; + +enum wmi_ap_apsd_buffered_traffic_flags { + WMI_AP_APSD_NO_DELIVERY_FRAMES = 0x1, +}; + +struct wmi_ap_apsd_buffered_traffic_cmd { + __le16 aid; + __le16 bitmap; + __le32 flags; +} __packed; + +/* WMI_PS_POLL_EVENT */ +struct wmi_pspoll_event { + __le16 aid; +} __packed; + +struct wmi_per_sta_stat { + __le32 tx_bytes; + __le32 tx_pkts; + __le32 tx_error; + __le32 tx_discard; + __le32 rx_bytes; + __le32 rx_pkts; + __le32 rx_error; + __le32 rx_discard; + __le32 aid; +} __packed; + +struct wmi_ap_mode_stat { + __le32 action; + struct wmi_per_sta_stat sta[AP_MAX_NUM_STA + 1]; +} __packed; + +/* End of AP mode definitions */ + +struct wmi_remain_on_chnl_cmd { + __le32 freq; + __le32 duration; +} __packed; + +/* wmi_send_action_cmd is to be deprecated. Use + * wmi_send_mgmt_cmd instead. The new structure supports P2P mgmt + * operations using station interface. + */ +struct wmi_send_action_cmd { + __le32 id; + __le32 freq; + __le32 wait; + __le16 len; + u8 data[0]; +} __packed; + +struct wmi_send_mgmt_cmd { + __le32 id; + __le32 freq; + __le32 wait; + __le32 no_cck; + __le16 len; + u8 data[0]; +} __packed; + +struct wmi_tx_status_event { + __le32 id; + u8 ack_status; +} __packed; + +struct wmi_probe_req_report_cmd { + u8 enable; +} __packed; + +struct wmi_disable_11b_rates_cmd { + u8 disable; +} __packed; + +struct wmi_set_appie_extended_cmd { + u8 role_id; + u8 mgmt_frm_type; + u8 ie_len; + u8 ie_info[0]; +} __packed; + +struct wmi_remain_on_chnl_event { + __le32 freq; + __le32 duration; +} __packed; + +struct wmi_cancel_remain_on_chnl_event { + __le32 freq; + __le32 duration; + u8 status; +} __packed; + +struct wmi_rx_action_event { + __le32 freq; + __le16 len; + u8 data[0]; +} __packed; + +struct wmi_p2p_capabilities_event { + __le16 len; + u8 data[0]; +} __packed; + +struct wmi_p2p_rx_probe_req_event { + __le32 freq; + __le16 len; + u8 data[0]; +} __packed; + +#define P2P_FLAG_CAPABILITIES_REQ (0x00000001) +#define P2P_FLAG_MACADDR_REQ (0x00000002) +#define P2P_FLAG_HMODEL_REQ (0x00000002) + +struct wmi_get_p2p_info { + __le32 info_req_flags; +} __packed; + +struct wmi_p2p_info_event { + __le32 info_req_flags; + __le16 len; + u8 data[0]; +} __packed; + +struct wmi_p2p_capabilities { + u8 go_power_save; +} __packed; + +struct wmi_p2p_macaddr { + u8 mac_addr[ETH_ALEN]; +} __packed; + +struct wmi_p2p_hmodel { + u8 p2p_model; +} __packed; + +struct wmi_p2p_probe_response_cmd { + __le32 freq; + u8 destination_addr[ETH_ALEN]; + __le16 len; + u8 data[0]; +} __packed; + +/* Extended WMI (WMIX) + * + * Extended WMIX commands are encapsulated in a WMI message with + * cmd=WMI_EXTENSION_CMD. + * + * Extended WMI commands are those that are needed during wireless + * operation, but which are not really wireless commands. This allows, + * for instance, platform-specific commands. Extended WMI commands are + * embedded in a WMI command message with WMI_COMMAND_ID=WMI_EXTENSION_CMDID. + * Extended WMI events are similarly embedded in a WMI event message with + * WMI_EVENT_ID=WMI_EXTENSION_EVENTID. + */ +struct wmix_cmd_hdr { + __le32 cmd_id; +} __packed; + +enum wmix_command_id { + WMIX_DSETOPEN_REPLY_CMDID = 0x2001, + WMIX_DSETDATA_REPLY_CMDID, + WMIX_GPIO_OUTPUT_SET_CMDID, + WMIX_GPIO_INPUT_GET_CMDID, + WMIX_GPIO_REGISTER_SET_CMDID, + WMIX_GPIO_REGISTER_GET_CMDID, + WMIX_GPIO_INTR_ACK_CMDID, + WMIX_HB_CHALLENGE_RESP_CMDID, + WMIX_DBGLOG_CFG_MODULE_CMDID, + WMIX_PROF_CFG_CMDID, /* 0x200a */ + WMIX_PROF_ADDR_SET_CMDID, + WMIX_PROF_START_CMDID, + WMIX_PROF_STOP_CMDID, + WMIX_PROF_COUNT_GET_CMDID, +}; + +enum wmix_event_id { + WMIX_DSETOPENREQ_EVENTID = 0x3001, + WMIX_DSETCLOSE_EVENTID, + WMIX_DSETDATAREQ_EVENTID, + WMIX_GPIO_INTR_EVENTID, + WMIX_GPIO_DATA_EVENTID, + WMIX_GPIO_ACK_EVENTID, + WMIX_HB_CHALLENGE_RESP_EVENTID, + WMIX_DBGLOG_EVENTID, + WMIX_PROF_COUNT_EVENTID, +}; + +/* + * ------Error Detection support------- + */ + +/* + * WMIX_HB_CHALLENGE_RESP_CMDID + * Heartbeat Challenge Response command + */ +struct wmix_hb_challenge_resp_cmd { + __le32 cookie; + __le32 source; +} __packed; + +struct ath6kl_wmix_dbglog_cfg_module_cmd { + __le32 valid; + __le32 config; +} __packed; + +/* End of Extended WMI (WMIX) */ + +enum wmi_sync_flag { + NO_SYNC_WMIFLAG = 0, + + /* transmit all queued data before cmd */ + SYNC_BEFORE_WMIFLAG, + + /* any new data waits until cmd execs */ + SYNC_AFTER_WMIFLAG, + + SYNC_BOTH_WMIFLAG, + + /* end marker */ + END_WMIFLAG +}; + +enum htc_endpoint_id ath6kl_wmi_get_control_ep(struct wmi *wmi); +void ath6kl_wmi_set_control_ep(struct wmi *wmi, enum htc_endpoint_id ep_id); +int ath6kl_wmi_dix_2_dot3(struct wmi *wmi, struct sk_buff *skb); +int ath6kl_wmi_data_hdr_add(struct wmi *wmi, struct sk_buff *skb, + u8 msg_type, u32 flags, + enum wmi_data_hdr_data_type data_type, + u8 meta_ver, void *tx_meta_info, u8 if_idx); + +int ath6kl_wmi_dot11_hdr_remove(struct wmi *wmi, struct sk_buff *skb); +int ath6kl_wmi_dot3_2_dix(struct sk_buff *skb); +int ath6kl_wmi_implicit_create_pstream(struct wmi *wmi, u8 if_idx, + struct sk_buff *skb, u32 layer2_priority, + bool wmm_enabled, u8 *ac); + +int ath6kl_wmi_control_rx(struct wmi *wmi, struct sk_buff *skb); + +int ath6kl_wmi_cmd_send(struct wmi *wmi, u8 if_idx, struct sk_buff *skb, + enum wmi_cmd_id cmd_id, enum wmi_sync_flag sync_flag); + +int ath6kl_wmi_connect_cmd(struct wmi *wmi, u8 if_idx, + enum network_type nw_type, + enum dot11_auth_mode dot11_auth_mode, + enum auth_mode auth_mode, + enum crypto_type pairwise_crypto, + u8 pairwise_crypto_len, + enum crypto_type group_crypto, + u8 group_crypto_len, int ssid_len, u8 *ssid, + u8 *bssid, u16 channel, u32 ctrl_flags, + u8 nw_subtype); + +int ath6kl_wmi_reconnect_cmd(struct wmi *wmi, u8 if_idx, u8 *bssid, + u16 channel); +int ath6kl_wmi_disconnect_cmd(struct wmi *wmi, u8 if_idx); + +int ath6kl_wmi_beginscan_cmd(struct wmi *wmi, u8 if_idx, + enum wmi_scan_type scan_type, + u32 force_fgscan, u32 is_legacy, + u32 home_dwell_time, u32 force_scan_interval, + s8 num_chan, u16 *ch_list, u32 no_cck, + u32 *rates); +int ath6kl_wmi_enable_sched_scan_cmd(struct wmi *wmi, u8 if_idx, bool enable); + +int ath6kl_wmi_scanparams_cmd(struct wmi *wmi, u8 if_idx, u16 fg_start_sec, + u16 fg_end_sec, u16 bg_sec, + u16 minact_chdw_msec, u16 maxact_chdw_msec, + u16 pas_chdw_msec, u8 short_scan_ratio, + u8 scan_ctrl_flag, u32 max_dfsch_act_time, + u16 maxact_scan_per_ssid); +int ath6kl_wmi_bssfilter_cmd(struct wmi *wmi, u8 if_idx, u8 filter, + u32 ie_mask); +int ath6kl_wmi_probedssid_cmd(struct wmi *wmi, u8 if_idx, u8 index, u8 flag, + u8 ssid_len, u8 *ssid); +int ath6kl_wmi_listeninterval_cmd(struct wmi *wmi, u8 if_idx, + u16 listen_interval, + u16 listen_beacons); +int ath6kl_wmi_bmisstime_cmd(struct wmi *wmi, u8 if_idx, + u16 bmiss_time, u16 num_beacons); +int ath6kl_wmi_powermode_cmd(struct wmi *wmi, u8 if_idx, u8 pwr_mode); +int ath6kl_wmi_pmparams_cmd(struct wmi *wmi, u8 if_idx, u16 idle_period, + u16 ps_poll_num, u16 dtim_policy, + u16 tx_wakup_policy, u16 num_tx_to_wakeup, + u16 ps_fail_event_policy); +int ath6kl_wmi_create_pstream_cmd(struct wmi *wmi, u8 if_idx, + struct wmi_create_pstream_cmd *pstream); +int ath6kl_wmi_delete_pstream_cmd(struct wmi *wmi, u8 if_idx, u8 traffic_class, + u8 tsid); +int ath6kl_wmi_disctimeout_cmd(struct wmi *wmi, u8 if_idx, u8 timeout); + +int ath6kl_wmi_set_rts_cmd(struct wmi *wmi, u16 threshold); +int ath6kl_wmi_set_lpreamble_cmd(struct wmi *wmi, u8 if_idx, u8 status, + u8 preamble_policy); + +int ath6kl_wmi_get_challenge_resp_cmd(struct wmi *wmi, u32 cookie, u32 source); +int ath6kl_wmi_config_debug_module_cmd(struct wmi *wmi, u32 valid, u32 config); + +int ath6kl_wmi_get_stats_cmd(struct wmi *wmi, u8 if_idx); +int ath6kl_wmi_addkey_cmd(struct wmi *wmi, u8 if_idx, u8 key_index, + enum crypto_type key_type, + u8 key_usage, u8 key_len, + u8 *key_rsc, unsigned int key_rsc_len, + u8 *key_material, + u8 key_op_ctrl, u8 *mac_addr, + enum wmi_sync_flag sync_flag); +int ath6kl_wmi_add_krk_cmd(struct wmi *wmi, u8 if_idx, u8 *krk); +int ath6kl_wmi_deletekey_cmd(struct wmi *wmi, u8 if_idx, u8 key_index); +int ath6kl_wmi_setpmkid_cmd(struct wmi *wmi, u8 if_idx, const u8 *bssid, + const u8 *pmkid, bool set); +int ath6kl_wmi_set_tx_pwr_cmd(struct wmi *wmi, u8 if_idx, u8 dbM); +int ath6kl_wmi_get_tx_pwr_cmd(struct wmi *wmi, u8 if_idx); +int ath6kl_wmi_get_roam_tbl_cmd(struct wmi *wmi); + +int ath6kl_wmi_set_wmm_txop(struct wmi *wmi, u8 if_idx, enum wmi_txop_cfg cfg); +int ath6kl_wmi_set_keepalive_cmd(struct wmi *wmi, u8 if_idx, + u8 keep_alive_intvl); +int ath6kl_wmi_set_htcap_cmd(struct wmi *wmi, u8 if_idx, + enum ieee80211_band band, + struct ath6kl_htcap *htcap); +int ath6kl_wmi_test_cmd(struct wmi *wmi, void *buf, size_t len); + +s32 ath6kl_wmi_get_rate(s8 rate_index); + +int ath6kl_wmi_set_ip_cmd(struct wmi *wmi, u8 if_idx, + __be32 ips0, __be32 ips1); +int ath6kl_wmi_set_host_sleep_mode_cmd(struct wmi *wmi, u8 if_idx, + enum ath6kl_host_mode host_mode); +int ath6kl_wmi_set_bitrate_mask(struct wmi *wmi, u8 if_idx, + const struct cfg80211_bitrate_mask *mask); +int ath6kl_wmi_set_wow_mode_cmd(struct wmi *wmi, u8 if_idx, + enum ath6kl_wow_mode wow_mode, + u32 filter, u16 host_req_delay); +int ath6kl_wmi_add_wow_pattern_cmd(struct wmi *wmi, u8 if_idx, + u8 list_id, u8 filter_size, + u8 filter_offset, const u8 *filter, + const u8 *mask); +int ath6kl_wmi_del_wow_pattern_cmd(struct wmi *wmi, u8 if_idx, + u16 list_id, u16 filter_id); +int ath6kl_wmi_set_rssi_filter_cmd(struct wmi *wmi, u8 if_idx, s8 rssi); +int ath6kl_wmi_set_roam_lrssi_cmd(struct wmi *wmi, u8 lrssi); +int ath6kl_wmi_ap_set_dtim_cmd(struct wmi *wmi, u8 if_idx, u32 dtim_period); +int ath6kl_wmi_ap_set_beacon_intvl_cmd(struct wmi *wmi, u8 if_idx, + u32 beacon_interval); +int ath6kl_wmi_force_roam_cmd(struct wmi *wmi, const u8 *bssid); +int ath6kl_wmi_set_roam_mode_cmd(struct wmi *wmi, enum wmi_roam_mode mode); +int ath6kl_wmi_mcast_filter_cmd(struct wmi *wmi, u8 if_idx, bool mc_all_on); +int ath6kl_wmi_add_del_mcast_filter_cmd(struct wmi *wmi, u8 if_idx, + u8 *filter, bool add_filter); +int ath6kl_wmi_sta_bmiss_enhance_cmd(struct wmi *wmi, u8 if_idx, bool enable); +int ath6kl_wmi_set_txe_notify(struct wmi *wmi, u8 idx, + u32 rate, u32 pkts, u32 intvl); +int ath6kl_wmi_set_regdomain_cmd(struct wmi *wmi, const char *alpha2); + +/* AP mode uAPSD */ +int ath6kl_wmi_ap_set_apsd(struct wmi *wmi, u8 if_idx, u8 enable); + +int ath6kl_wmi_set_apsd_bfrd_traf(struct wmi *wmi, + u8 if_idx, u16 aid, + u16 bitmap, u32 flags); + +u8 ath6kl_wmi_get_traffic_class(u8 user_priority); + +u8 ath6kl_wmi_determine_user_priority(u8 *pkt, u32 layer2_pri); +/* AP mode */ +int ath6kl_wmi_ap_hidden_ssid(struct wmi *wmi, u8 if_idx, bool enable); +int ath6kl_wmi_ap_profile_commit(struct wmi *wmip, u8 if_idx, + struct wmi_connect_cmd *p); + +int ath6kl_wmi_ap_set_mlme(struct wmi *wmip, u8 if_idx, u8 cmd, + const u8 *mac, u16 reason); + +int ath6kl_wmi_set_pvb_cmd(struct wmi *wmi, u8 if_idx, u16 aid, bool flag); + +int ath6kl_wmi_set_rx_frame_format_cmd(struct wmi *wmi, u8 if_idx, + u8 rx_meta_version, + bool rx_dot11_hdr, bool defrag_on_host); + +int ath6kl_wmi_set_appie_cmd(struct wmi *wmi, u8 if_idx, u8 mgmt_frm_type, + const u8 *ie, u8 ie_len); + +int ath6kl_wmi_set_ie_cmd(struct wmi *wmi, u8 if_idx, u8 ie_id, u8 ie_field, + const u8 *ie_info, u8 ie_len); + +/* P2P */ +int ath6kl_wmi_disable_11b_rates_cmd(struct wmi *wmi, bool disable); + +int ath6kl_wmi_remain_on_chnl_cmd(struct wmi *wmi, u8 if_idx, u32 freq, + u32 dur); + +int ath6kl_wmi_send_mgmt_cmd(struct wmi *wmi, u8 if_idx, u32 id, u32 freq, + u32 wait, const u8 *data, u16 data_len, + u32 no_cck); + +int ath6kl_wmi_send_probe_response_cmd(struct wmi *wmi, u8 if_idx, u32 freq, + const u8 *dst, const u8 *data, + u16 data_len); + +int ath6kl_wmi_probe_report_req_cmd(struct wmi *wmi, u8 if_idx, bool enable); + +int ath6kl_wmi_info_req_cmd(struct wmi *wmi, u8 if_idx, u32 info_req_flags); + +int ath6kl_wmi_cancel_remain_on_chnl_cmd(struct wmi *wmi, u8 if_idx); + +int ath6kl_wmi_set_appie_cmd(struct wmi *wmi, u8 if_idx, u8 mgmt_frm_type, + const u8 *ie, u8 ie_len); + +int ath6kl_wmi_set_inact_period(struct wmi *wmi, u8 if_idx, int inact_timeout); + +void ath6kl_wmi_sscan_timer(unsigned long ptr); + +int ath6kl_wmi_get_challenge_resp_cmd(struct wmi *wmi, u32 cookie, u32 source); + +struct ath6kl_vif *ath6kl_get_vif_by_index(struct ath6kl *ar, u8 if_idx); +void *ath6kl_wmi_init(struct ath6kl *devt); +void ath6kl_wmi_shutdown(struct wmi *wmi); +void ath6kl_wmi_reset(struct wmi *wmi); + +#endif /* WMI_H */ diff --git a/drivers/net/wireless/ath/ath9k/Kconfig b/drivers/net/wireless/ath/ath9k/Kconfig new file mode 100644 index 0000000..e6302f4 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/Kconfig @@ -0,0 +1,132 @@ +config ATH9K_HW + tristate + depends on m +config ATH9K_COMMON + tristate + depends on m + select ATH_COMMON +config ATH9K_DFS_DEBUGFS + def_bool y + depends on ATH9K_DEBUGFS && ATH9K_DFS_CERTIFIED + +config ATH9K_BTCOEX_SUPPORT + bool "Atheros bluetooth coexistence support" + depends on (ATH9K || ATH9K_HTC) + default y + ---help--- + Say Y, if you want to use the ath9k/ath9k_htc radios together with + Bluetooth modules in the same system. + +config ATH9K + tristate "Atheros 802.11n wireless cards support" + depends on m + depends on MAC80211 && HAS_DMA + select ATH9K_HW + select MAC80211_LEDS + select BACKPORT_LEDS_CLASS + select BACKPORT_NEW_LEDS + select ATH9K_COMMON + ---help--- + This module adds support for wireless adapters based on + Atheros IEEE 802.11n AR5008, AR9001 and AR9002 family + of chipsets. For a specific list of supported external + cards, laptops that already ship with these cards and + APs that come with these cards refer to to ath9k wiki + products page: + + http://wireless.kernel.org/en/users/Drivers/ath9k/products + + If you choose to build a module, it'll be called ath9k. + +config ATH9K_PCI + bool "Atheros ath9k PCI/PCIe bus support" + default y + depends on ATH9K && PCI + ---help--- + This option enables the PCI bus support in ath9k. + + Say Y, if you have a compatible PCI/PCIe wireless card. + +config ATH9K_AHB + depends on !BACKPORT_KERNEL_2_6_30 + bool "Atheros ath9k AHB bus support" + depends on ATH9K + default n + ---help--- + This option enables the AHB bus support in ath9k. + + Say Y, if you have a SoC with a compatible built-in + wireless MAC. Say N if unsure. + +config ATH9K_DEBUGFS + bool "Atheros ath9k debugging" + depends on ATH9K + select MAC80211_DEBUGFS + depends on RELAY + ---help--- + Say Y, if you need access to ath9k's statistics for + interrupts, rate control, etc. + + Also required for changing debug message flags at run time. + +config ATH9K_DFS_CERTIFIED + bool "Atheros DFS support for certified platforms" + depends on ATH9K && CFG80211_CERTIFICATION_ONUS + default n + ---help--- + This option enables DFS support for initiating radiation on + ath9k. There is no way to dynamically detect if a card was DFS + certified and as such this is left as a build time option. This + option should only be enabled by system integrators that can + guarantee that all the platforms that their kernel will run on + have obtained appropriate regulatory body certification for a + respective Atheros card by using ath9k on the target shipping + platforms. + + This is currently only a placeholder for future DFS support, + as DFS support requires more components that still need to be + developed. At this point enabling this option won't do anything + except increase code size. + +config ATH9K_MAC_DEBUG + bool "Atheros MAC statistics" + depends on ATH9K_DEBUGFS + default y + ---help--- + This option enables collection of statistics for Rx/Tx status + data and some other MAC related statistics + +config ATH9K_LEGACY_RATE_CONTROL + bool "Atheros ath9k rate control" + depends on ATH9K + default n + ---help--- + Say Y, if you want to use the ath9k specific rate control + module instead of minstrel_ht. Be warned that there are various + issues with the ath9k RC and minstrel is a more robust algorithm. + Note that even if this option is selected, "ath9k_rate_control" + has to be passed to mac80211 using the module parameter, + ieee80211_default_rc_algo. + +config ATH9K_HTC + tristate "Atheros HTC based wireless cards support" + depends on m + depends on USB && MAC80211 + select ATH9K_HW + select MAC80211_LEDS + select BACKPORT_LEDS_CLASS + select BACKPORT_NEW_LEDS + select ATH9K_COMMON + ---help--- + Support for Atheros HTC based cards. + Chipsets supported: AR9271 + + For more information: http://wireless.kernel.org/en/users/Drivers/ath9k_htc + + The built module will be ath9k_htc. + +config ATH9K_HTC_DEBUGFS + bool "Atheros ath9k_htc debugging" + depends on ATH9K_HTC && DEBUG_FS + ---help--- + Say Y, if you need access to ath9k_htc's statistics. diff --git a/drivers/net/wireless/ath/ath9k/Makefile b/drivers/net/wireless/ath/ath9k/Makefile new file mode 100644 index 0000000..7e8a111 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/Makefile @@ -0,0 +1,64 @@ +ath9k-y += beacon.o \ + gpio.o \ + init.o \ + main.o \ + recv.o \ + xmit.o \ + link.o \ + antenna.o + +ath9k-$(CPTCFG_ATH9K_BTCOEX_SUPPORT) += mci.o +ath9k-$(CPTCFG_ATH9K_LEGACY_RATE_CONTROL) += rc.o +ath9k-$(CPTCFG_ATH9K_PCI) += pci.o +ath9k-$(CPTCFG_ATH9K_AHB) += ahb.o +ath9k-$(CPTCFG_ATH9K_DEBUGFS) += debug.o +ath9k-$(CPTCFG_ATH9K_DFS_DEBUGFS) += dfs_debug.o +ath9k-$(CPTCFG_ATH9K_DFS_CERTIFIED) += \ + dfs.o \ + dfs_pattern_detector.o \ + dfs_pri_detector.o +ath9k-$(CONFIG_PM_SLEEP) += wow.o + +obj-$(CPTCFG_ATH9K) += ath9k.o + +ath9k_hw-y:= \ + ar9002_hw.o \ + ar9003_hw.o \ + hw.o \ + ar9003_phy.o \ + ar9002_phy.o \ + ar5008_phy.o \ + ar9002_calib.o \ + ar9003_calib.o \ + ar9003_rtt.o \ + calib.o \ + eeprom.o \ + eeprom_def.o \ + eeprom_4k.o \ + eeprom_9287.o \ + ani.o \ + mac.o \ + ar9002_mac.o \ + ar9003_mac.o \ + ar9003_eeprom.o \ + ar9003_paprd.o + +ath9k_hw-$(CPTCFG_ATH9K_BTCOEX_SUPPORT) += btcoex.o \ + ar9003_mci.o +obj-$(CPTCFG_ATH9K_HW) += ath9k_hw.o + +obj-$(CPTCFG_ATH9K_COMMON) += ath9k_common.o +ath9k_common-y:= common.o + +ath9k_htc-y += htc_hst.o \ + hif_usb.o \ + wmi.o \ + htc_drv_txrx.o \ + htc_drv_main.o \ + htc_drv_beacon.o \ + htc_drv_init.o \ + htc_drv_gpio.o + +ath9k_htc-$(CPTCFG_ATH9K_HTC_DEBUGFS) += htc_drv_debug.o + +obj-$(CPTCFG_ATH9K_HTC) += ath9k_htc.o diff --git a/drivers/net/wireless/ath/ath9k/ahb.c b/drivers/net/wireless/ath/ath9k/ahb.c new file mode 100644 index 0000000..d1ff3c2 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ahb.c @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * Copyright (c) 2009 Gabor Juhos + * Copyright (c) 2009 Imre Kaloz + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include "ath9k.h" + +static const struct platform_device_id ath9k_platform_id_table[] = { + { + .name = "ath9k", + .driver_data = AR5416_AR9100_DEVID, + }, + { + .name = "ar933x_wmac", + .driver_data = AR9300_DEVID_AR9330, + }, + { + .name = "ar934x_wmac", + .driver_data = AR9300_DEVID_AR9340, + }, + { + .name = "qca955x_wmac", + .driver_data = AR9300_DEVID_QCA955X, + }, + {}, +}; + +/* return bus cachesize in 4B word units */ +static void ath_ahb_read_cachesize(struct ath_common *common, int *csz) +{ + *csz = L1_CACHE_BYTES >> 2; +} + +static bool ath_ahb_eeprom_read(struct ath_common *common, u32 off, u16 *data) +{ + struct ath_softc *sc = (struct ath_softc *)common->priv; + struct platform_device *pdev = to_platform_device(sc->dev); + struct ath9k_platform_data *pdata; + + pdata = (struct ath9k_platform_data *) pdev->dev.platform_data; + if (off >= (ARRAY_SIZE(pdata->eeprom_data))) { + ath_err(common, + "%s: flash read failed, offset %08x is out of range\n", + __func__, off); + return false; + } + + *data = pdata->eeprom_data[off]; + return true; +} + +static struct ath_bus_ops ath_ahb_bus_ops = { + .ath_bus_type = ATH_AHB, + .read_cachesize = ath_ahb_read_cachesize, + .eeprom_read = ath_ahb_eeprom_read, +}; + +static int ath_ahb_probe(struct platform_device *pdev) +{ + void __iomem *mem; + struct ath_softc *sc; + struct ieee80211_hw *hw; + struct resource *res; + const struct platform_device_id *id = platform_get_device_id(pdev); + int irq; + int ret = 0; + struct ath_hw *ah; + char hw_name[64]; + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "no platform data specified\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "no memory resource found\n"); + return -ENXIO; + } + + mem = devm_ioremap_nocache(&pdev->dev, res->start, resource_size(res)); + if (mem == NULL) { + dev_err(&pdev->dev, "ioremap failed\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + dev_err(&pdev->dev, "no IRQ resource found\n"); + return -ENXIO; + } + + irq = res->start; + + hw = ieee80211_alloc_hw(sizeof(struct ath_softc), &ath9k_ops); + if (hw == NULL) { + dev_err(&pdev->dev, "no memory for ieee80211_hw\n"); + return -ENOMEM; + } + + SET_IEEE80211_DEV(hw, &pdev->dev); + platform_set_drvdata(pdev, hw); + + sc = hw->priv; + sc->hw = hw; + sc->dev = &pdev->dev; + sc->mem = mem; + sc->irq = irq; + + /* Will be cleared in ath9k_start() */ + set_bit(SC_OP_INVALID, &sc->sc_flags); + + ret = request_irq(irq, ath_isr, IRQF_SHARED, "ath9k", sc); + if (ret) { + dev_err(&pdev->dev, "request_irq failed\n"); + goto err_free_hw; + } + + ret = ath9k_init_device(id->driver_data, sc, &ath_ahb_bus_ops); + if (ret) { + dev_err(&pdev->dev, "failed to initialize device\n"); + goto err_irq; + } + + ah = sc->sc_ah; + ath9k_hw_name(ah, hw_name, sizeof(hw_name)); + wiphy_info(hw->wiphy, "%s mem=0x%lx, irq=%d\n", + hw_name, (unsigned long)mem, irq); + + return 0; + + err_irq: + free_irq(irq, sc); + err_free_hw: + ieee80211_free_hw(hw); + platform_set_drvdata(pdev, NULL); + return ret; +} + +static int ath_ahb_remove(struct platform_device *pdev) +{ + struct ieee80211_hw *hw = platform_get_drvdata(pdev); + + if (hw) { + struct ath_softc *sc = hw->priv; + + ath9k_deinit_device(sc); + free_irq(sc->irq, sc); + ieee80211_free_hw(sc->hw); + platform_set_drvdata(pdev, NULL); + } + + return 0; +} + +static struct platform_driver ath_ahb_driver = { + .probe = ath_ahb_probe, + .remove = ath_ahb_remove, + .driver = { + .name = "ath9k", + .owner = THIS_MODULE, + }, + .id_table = ath9k_platform_id_table, +}; + +MODULE_DEVICE_TABLE(platform, ath9k_platform_id_table); + +int ath_ahb_init(void) +{ + return platform_driver_register(&ath_ahb_driver); +} + +void ath_ahb_exit(void) +{ + platform_driver_unregister(&ath_ahb_driver); +} diff --git a/drivers/net/wireless/ath/ath9k/ani.c b/drivers/net/wireless/ath/ath9k/ani.c new file mode 100644 index 0000000..7ecd40f --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ani.c @@ -0,0 +1,532 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include "hw.h" +#include "hw-ops.h" + +struct ani_ofdm_level_entry { + int spur_immunity_level; + int fir_step_level; + int ofdm_weak_signal_on; +}; + +/* values here are relative to the INI */ + +/* + * Legend: + * + * SI: Spur immunity + * FS: FIR Step + * WS: OFDM / CCK Weak Signal detection + * MRC-CCK: Maximal Ratio Combining for CCK + */ + +static const struct ani_ofdm_level_entry ofdm_level_table[] = { + /* SI FS WS */ + { 0, 0, 1 }, /* lvl 0 */ + { 1, 1, 1 }, /* lvl 1 */ + { 2, 2, 1 }, /* lvl 2 */ + { 3, 2, 1 }, /* lvl 3 (default) */ + { 4, 3, 1 }, /* lvl 4 */ + { 5, 4, 1 }, /* lvl 5 */ + { 6, 5, 1 }, /* lvl 6 */ + { 7, 6, 1 }, /* lvl 7 */ + { 7, 6, 0 }, /* lvl 8 */ + { 7, 7, 0 } /* lvl 9 */ +}; +#define ATH9K_ANI_OFDM_NUM_LEVEL \ + ARRAY_SIZE(ofdm_level_table) +#define ATH9K_ANI_OFDM_MAX_LEVEL \ + (ATH9K_ANI_OFDM_NUM_LEVEL-1) +#define ATH9K_ANI_OFDM_DEF_LEVEL \ + 3 /* default level - matches the INI settings */ + +/* + * MRC (Maximal Ratio Combining) has always been used with multi-antenna ofdm. + * With OFDM for single stream you just add up all antenna inputs, you're + * only interested in what you get after FFT. Signal aligment is also not + * required for OFDM because any phase difference adds up in the frequency + * domain. + * + * MRC requires extra work for use with CCK. You need to align the antenna + * signals from the different antenna before you can add the signals together. + * You need aligment of signals as CCK is in time domain, so addition can cancel + * your signal completely if phase is 180 degrees (think of adding sine waves). + * You also need to remove noise before the addition and this is where ANI + * MRC CCK comes into play. One of the antenna inputs may be stronger but + * lower SNR, so just adding after alignment can be dangerous. + * + * Regardless of alignment in time, the antenna signals add constructively after + * FFT and improve your reception. For more information: + * + * http://en.wikipedia.org/wiki/Maximal-ratio_combining + */ + +struct ani_cck_level_entry { + int fir_step_level; + int mrc_cck_on; +}; + +static const struct ani_cck_level_entry cck_level_table[] = { + /* FS MRC-CCK */ + { 0, 1 }, /* lvl 0 */ + { 1, 1 }, /* lvl 1 */ + { 2, 1 }, /* lvl 2 (default) */ + { 3, 1 }, /* lvl 3 */ + { 4, 0 }, /* lvl 4 */ + { 5, 0 }, /* lvl 5 */ + { 6, 0 }, /* lvl 6 */ + { 6, 0 }, /* lvl 7 (only for high rssi) */ + { 7, 0 } /* lvl 8 (only for high rssi) */ +}; + +#define ATH9K_ANI_CCK_NUM_LEVEL \ + ARRAY_SIZE(cck_level_table) +#define ATH9K_ANI_CCK_MAX_LEVEL \ + (ATH9K_ANI_CCK_NUM_LEVEL-1) +#define ATH9K_ANI_CCK_MAX_LEVEL_LOW_RSSI \ + (ATH9K_ANI_CCK_NUM_LEVEL-3) +#define ATH9K_ANI_CCK_DEF_LEVEL \ + 2 /* default level - matches the INI settings */ + +static void ath9k_hw_update_mibstats(struct ath_hw *ah, + struct ath9k_mib_stats *stats) +{ + stats->ackrcv_bad += REG_READ(ah, AR_ACK_FAIL); + stats->rts_bad += REG_READ(ah, AR_RTS_FAIL); + stats->fcs_bad += REG_READ(ah, AR_FCS_FAIL); + stats->rts_good += REG_READ(ah, AR_RTS_OK); + stats->beacons += REG_READ(ah, AR_BEACON_CNT); +} + +static void ath9k_ani_restart(struct ath_hw *ah) +{ + struct ar5416AniState *aniState; + + if (!DO_ANI(ah)) + return; + + aniState = &ah->curchan->ani; + aniState->listenTime = 0; + + ENABLE_REGWRITE_BUFFER(ah); + + REG_WRITE(ah, AR_PHY_ERR_1, 0); + REG_WRITE(ah, AR_PHY_ERR_2, 0); + REG_WRITE(ah, AR_PHY_ERR_MASK_1, AR_PHY_ERR_OFDM_TIMING); + REG_WRITE(ah, AR_PHY_ERR_MASK_2, AR_PHY_ERR_CCK_TIMING); + + REGWRITE_BUFFER_FLUSH(ah); + + ath9k_hw_update_mibstats(ah, &ah->ah_mibStats); + + aniState->ofdmPhyErrCount = 0; + aniState->cckPhyErrCount = 0; +} + +/* Adjust the OFDM Noise Immunity Level */ +static void ath9k_hw_set_ofdm_nil(struct ath_hw *ah, u8 immunityLevel, + bool scan) +{ + struct ar5416AniState *aniState = &ah->curchan->ani; + struct ath_common *common = ath9k_hw_common(ah); + const struct ani_ofdm_level_entry *entry_ofdm; + const struct ani_cck_level_entry *entry_cck; + bool weak_sig; + + ath_dbg(common, ANI, "**** ofdmlevel %d=>%d, rssi=%d[lo=%d hi=%d]\n", + aniState->ofdmNoiseImmunityLevel, + immunityLevel, BEACON_RSSI(ah), + ATH9K_ANI_RSSI_THR_LOW, + ATH9K_ANI_RSSI_THR_HIGH); + + if (!scan) + aniState->ofdmNoiseImmunityLevel = immunityLevel; + + entry_ofdm = &ofdm_level_table[aniState->ofdmNoiseImmunityLevel]; + entry_cck = &cck_level_table[aniState->cckNoiseImmunityLevel]; + + if (aniState->spurImmunityLevel != entry_ofdm->spur_immunity_level) + ath9k_hw_ani_control(ah, + ATH9K_ANI_SPUR_IMMUNITY_LEVEL, + entry_ofdm->spur_immunity_level); + + if (aniState->firstepLevel != entry_ofdm->fir_step_level && + entry_ofdm->fir_step_level >= entry_cck->fir_step_level) + ath9k_hw_ani_control(ah, + ATH9K_ANI_FIRSTEP_LEVEL, + entry_ofdm->fir_step_level); + + weak_sig = entry_ofdm->ofdm_weak_signal_on; + if (ah->opmode == NL80211_IFTYPE_STATION && + BEACON_RSSI(ah) <= ATH9K_ANI_RSSI_THR_HIGH) + weak_sig = true; + + if (aniState->ofdmWeakSigDetect != weak_sig) + ath9k_hw_ani_control(ah, + ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION, + entry_ofdm->ofdm_weak_signal_on); + + if (aniState->ofdmNoiseImmunityLevel >= ATH9K_ANI_OFDM_DEF_LEVEL) { + ah->config.ofdm_trig_high = ATH9K_ANI_OFDM_TRIG_HIGH; + ah->config.ofdm_trig_low = ATH9K_ANI_OFDM_TRIG_LOW_ABOVE_INI; + } else { + ah->config.ofdm_trig_high = ATH9K_ANI_OFDM_TRIG_HIGH_BELOW_INI; + ah->config.ofdm_trig_low = ATH9K_ANI_OFDM_TRIG_LOW; + } +} + +static void ath9k_hw_ani_ofdm_err_trigger(struct ath_hw *ah) +{ + struct ar5416AniState *aniState; + + if (!DO_ANI(ah)) + return; + + aniState = &ah->curchan->ani; + + if (aniState->ofdmNoiseImmunityLevel < ATH9K_ANI_OFDM_MAX_LEVEL) + ath9k_hw_set_ofdm_nil(ah, aniState->ofdmNoiseImmunityLevel + 1, false); +} + +/* + * Set the ANI settings to match an CCK level. + */ +static void ath9k_hw_set_cck_nil(struct ath_hw *ah, u_int8_t immunityLevel, + bool scan) +{ + struct ar5416AniState *aniState = &ah->curchan->ani; + struct ath_common *common = ath9k_hw_common(ah); + const struct ani_ofdm_level_entry *entry_ofdm; + const struct ani_cck_level_entry *entry_cck; + + ath_dbg(common, ANI, "**** ccklevel %d=>%d, rssi=%d[lo=%d hi=%d]\n", + aniState->cckNoiseImmunityLevel, immunityLevel, + BEACON_RSSI(ah), ATH9K_ANI_RSSI_THR_LOW, + ATH9K_ANI_RSSI_THR_HIGH); + + if (ah->opmode == NL80211_IFTYPE_STATION && + BEACON_RSSI(ah) <= ATH9K_ANI_RSSI_THR_LOW && + immunityLevel > ATH9K_ANI_CCK_MAX_LEVEL_LOW_RSSI) + immunityLevel = ATH9K_ANI_CCK_MAX_LEVEL_LOW_RSSI; + + if (!scan) + aniState->cckNoiseImmunityLevel = immunityLevel; + + entry_ofdm = &ofdm_level_table[aniState->ofdmNoiseImmunityLevel]; + entry_cck = &cck_level_table[aniState->cckNoiseImmunityLevel]; + + if (aniState->firstepLevel != entry_cck->fir_step_level && + entry_cck->fir_step_level >= entry_ofdm->fir_step_level) + ath9k_hw_ani_control(ah, + ATH9K_ANI_FIRSTEP_LEVEL, + entry_cck->fir_step_level); + + /* Skip MRC CCK for pre AR9003 families */ + if (!AR_SREV_9300_20_OR_LATER(ah) || AR_SREV_9485(ah) || AR_SREV_9565(ah)) + return; + + if (aniState->mrcCCK != entry_cck->mrc_cck_on) + ath9k_hw_ani_control(ah, + ATH9K_ANI_MRC_CCK, + entry_cck->mrc_cck_on); +} + +static void ath9k_hw_ani_cck_err_trigger(struct ath_hw *ah) +{ + struct ar5416AniState *aniState; + + if (!DO_ANI(ah)) + return; + + aniState = &ah->curchan->ani; + + if (aniState->cckNoiseImmunityLevel < ATH9K_ANI_CCK_MAX_LEVEL) + ath9k_hw_set_cck_nil(ah, aniState->cckNoiseImmunityLevel + 1, + false); +} + +/* + * only lower either OFDM or CCK errors per turn + * we lower the other one next time + */ +static void ath9k_hw_ani_lower_immunity(struct ath_hw *ah) +{ + struct ar5416AniState *aniState; + + aniState = &ah->curchan->ani; + + /* lower OFDM noise immunity */ + if (aniState->ofdmNoiseImmunityLevel > 0 && + (aniState->ofdmsTurn || aniState->cckNoiseImmunityLevel == 0)) { + ath9k_hw_set_ofdm_nil(ah, aniState->ofdmNoiseImmunityLevel - 1, + false); + return; + } + + /* lower CCK noise immunity */ + if (aniState->cckNoiseImmunityLevel > 0) + ath9k_hw_set_cck_nil(ah, aniState->cckNoiseImmunityLevel - 1, + false); +} + +/* + * Restore the ANI parameters in the HAL and reset the statistics. + * This routine should be called for every hardware reset and for + * every channel change. + */ +void ath9k_ani_reset(struct ath_hw *ah, bool is_scanning) +{ + struct ar5416AniState *aniState = &ah->curchan->ani; + struct ath9k_channel *chan = ah->curchan; + struct ath_common *common = ath9k_hw_common(ah); + int ofdm_nil, cck_nil; + + if (!DO_ANI(ah)) + return; + + BUG_ON(aniState == NULL); + ah->stats.ast_ani_reset++; + + /* only allow a subset of functions in AP mode */ + if (ah->opmode == NL80211_IFTYPE_AP) { + if (IS_CHAN_2GHZ(chan)) { + ah->ani_function = (ATH9K_ANI_SPUR_IMMUNITY_LEVEL | + ATH9K_ANI_FIRSTEP_LEVEL); + if (AR_SREV_9300_20_OR_LATER(ah)) + ah->ani_function |= ATH9K_ANI_MRC_CCK; + } else + ah->ani_function = 0; + } + + /* always allow mode (on/off) to be controlled */ + ah->ani_function |= ATH9K_ANI_MODE; + + ofdm_nil = max_t(int, ATH9K_ANI_OFDM_DEF_LEVEL, + aniState->ofdmNoiseImmunityLevel); + cck_nil = max_t(int, ATH9K_ANI_CCK_DEF_LEVEL, + aniState->cckNoiseImmunityLevel); + + if (is_scanning || + (ah->opmode != NL80211_IFTYPE_STATION && + ah->opmode != NL80211_IFTYPE_ADHOC)) { + /* + * If we're scanning or in AP mode, the defaults (ini) + * should be in place. For an AP we assume the historical + * levels for this channel are probably outdated so start + * from defaults instead. + */ + if (aniState->ofdmNoiseImmunityLevel != + ATH9K_ANI_OFDM_DEF_LEVEL || + aniState->cckNoiseImmunityLevel != + ATH9K_ANI_CCK_DEF_LEVEL) { + ath_dbg(common, ANI, + "Restore defaults: opmode %u chan %d Mhz/0x%x is_scanning=%d ofdm:%d cck:%d\n", + ah->opmode, + chan->channel, + chan->channelFlags, + is_scanning, + aniState->ofdmNoiseImmunityLevel, + aniState->cckNoiseImmunityLevel); + + ofdm_nil = ATH9K_ANI_OFDM_DEF_LEVEL; + cck_nil = ATH9K_ANI_CCK_DEF_LEVEL; + } + } else { + /* + * restore historical levels for this channel + */ + ath_dbg(common, ANI, + "Restore history: opmode %u chan %d Mhz/0x%x is_scanning=%d ofdm:%d cck:%d\n", + ah->opmode, + chan->channel, + chan->channelFlags, + is_scanning, + aniState->ofdmNoiseImmunityLevel, + aniState->cckNoiseImmunityLevel); + } + ath9k_hw_set_ofdm_nil(ah, ofdm_nil, is_scanning); + ath9k_hw_set_cck_nil(ah, cck_nil, is_scanning); + + /* + * enable phy counters if hw supports or if not, enable phy + * interrupts (so we can count each one) + */ + ath9k_ani_restart(ah); + + ENABLE_REGWRITE_BUFFER(ah); + + REG_WRITE(ah, AR_PHY_ERR_MASK_1, AR_PHY_ERR_OFDM_TIMING); + REG_WRITE(ah, AR_PHY_ERR_MASK_2, AR_PHY_ERR_CCK_TIMING); + + REGWRITE_BUFFER_FLUSH(ah); +} + +static bool ath9k_hw_ani_read_counters(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ar5416AniState *aniState = &ah->curchan->ani; + u32 phyCnt1, phyCnt2; + int32_t listenTime; + + ath_hw_cycle_counters_update(common); + listenTime = ath_hw_get_listen_time(common); + + if (listenTime <= 0) { + ah->stats.ast_ani_lneg_or_lzero++; + ath9k_ani_restart(ah); + return false; + } + + aniState->listenTime += listenTime; + + ath9k_hw_update_mibstats(ah, &ah->ah_mibStats); + + phyCnt1 = REG_READ(ah, AR_PHY_ERR_1); + phyCnt2 = REG_READ(ah, AR_PHY_ERR_2); + + ah->stats.ast_ani_ofdmerrs += phyCnt1 - aniState->ofdmPhyErrCount; + aniState->ofdmPhyErrCount = phyCnt1; + + ah->stats.ast_ani_cckerrs += phyCnt2 - aniState->cckPhyErrCount; + aniState->cckPhyErrCount = phyCnt2; + + return true; +} + +void ath9k_hw_ani_monitor(struct ath_hw *ah, struct ath9k_channel *chan) +{ + struct ar5416AniState *aniState; + struct ath_common *common = ath9k_hw_common(ah); + u32 ofdmPhyErrRate, cckPhyErrRate; + + if (!DO_ANI(ah)) + return; + + aniState = &ah->curchan->ani; + if (!ath9k_hw_ani_read_counters(ah)) + return; + + ofdmPhyErrRate = aniState->ofdmPhyErrCount * 1000 / + aniState->listenTime; + cckPhyErrRate = aniState->cckPhyErrCount * 1000 / + aniState->listenTime; + + ath_dbg(common, ANI, + "listenTime=%d OFDM:%d errs=%d/s CCK:%d errs=%d/s ofdm_turn=%d\n", + aniState->listenTime, + aniState->ofdmNoiseImmunityLevel, + ofdmPhyErrRate, aniState->cckNoiseImmunityLevel, + cckPhyErrRate, aniState->ofdmsTurn); + + if (aniState->listenTime > ah->aniperiod) { + if (cckPhyErrRate < ah->config.cck_trig_low && + ofdmPhyErrRate < ah->config.ofdm_trig_low) { + ath9k_hw_ani_lower_immunity(ah); + aniState->ofdmsTurn = !aniState->ofdmsTurn; + } else if (ofdmPhyErrRate > ah->config.ofdm_trig_high) { + ath9k_hw_ani_ofdm_err_trigger(ah); + aniState->ofdmsTurn = false; + } else if (cckPhyErrRate > ah->config.cck_trig_high) { + ath9k_hw_ani_cck_err_trigger(ah); + aniState->ofdmsTurn = true; + } + ath9k_ani_restart(ah); + } +} +EXPORT_SYMBOL(ath9k_hw_ani_monitor); + +void ath9k_enable_mib_counters(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + + ath_dbg(common, ANI, "Enable MIB counters\n"); + + ath9k_hw_update_mibstats(ah, &ah->ah_mibStats); + + ENABLE_REGWRITE_BUFFER(ah); + + REG_WRITE(ah, AR_FILT_OFDM, 0); + REG_WRITE(ah, AR_FILT_CCK, 0); + REG_WRITE(ah, AR_MIBC, + ~(AR_MIBC_COW | AR_MIBC_FMC | AR_MIBC_CMC | AR_MIBC_MCS) + & 0x0f); + REG_WRITE(ah, AR_PHY_ERR_MASK_1, AR_PHY_ERR_OFDM_TIMING); + REG_WRITE(ah, AR_PHY_ERR_MASK_2, AR_PHY_ERR_CCK_TIMING); + + REGWRITE_BUFFER_FLUSH(ah); +} + +/* Freeze the MIB counters, get the stats and then clear them */ +void ath9k_hw_disable_mib_counters(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + + ath_dbg(common, ANI, "Disable MIB counters\n"); + + REG_WRITE(ah, AR_MIBC, AR_MIBC_FMC); + ath9k_hw_update_mibstats(ah, &ah->ah_mibStats); + REG_WRITE(ah, AR_MIBC, AR_MIBC_CMC); + REG_WRITE(ah, AR_FILT_OFDM, 0); + REG_WRITE(ah, AR_FILT_CCK, 0); +} +EXPORT_SYMBOL(ath9k_hw_disable_mib_counters); + +void ath9k_hw_ani_init(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + int i; + + ath_dbg(common, ANI, "Initialize ANI\n"); + + ah->config.ofdm_trig_high = ATH9K_ANI_OFDM_TRIG_HIGH; + ah->config.ofdm_trig_low = ATH9K_ANI_OFDM_TRIG_LOW; + + ah->config.cck_trig_high = ATH9K_ANI_CCK_TRIG_HIGH; + ah->config.cck_trig_low = ATH9K_ANI_CCK_TRIG_LOW; + + for (i = 0; i < ARRAY_SIZE(ah->channels); i++) { + struct ath9k_channel *chan = &ah->channels[i]; + struct ar5416AniState *ani = &chan->ani; + + ani->spurImmunityLevel = ATH9K_ANI_SPUR_IMMUNE_LVL; + + ani->firstepLevel = ATH9K_ANI_FIRSTEP_LVL; + + ani->mrcCCK = AR_SREV_9300_20_OR_LATER(ah) ? true : false; + + ani->ofdmsTurn = true; + + ani->ofdmWeakSigDetect = ATH9K_ANI_USE_OFDM_WEAK_SIG; + ani->cckNoiseImmunityLevel = ATH9K_ANI_CCK_DEF_LEVEL; + ani->ofdmNoiseImmunityLevel = ATH9K_ANI_OFDM_DEF_LEVEL; + } + + /* + * since we expect some ongoing maintenance on the tables, let's sanity + * check here default level should not modify INI setting. + */ + ah->aniperiod = ATH9K_ANI_PERIOD; + ah->config.ani_poll_interval = ATH9K_ANI_POLLINTERVAL; + + if (ah->config.enable_ani) + ah->proc_phyerr |= HAL_PROCESS_ANI; + + ath9k_ani_restart(ah); + ath9k_enable_mib_counters(ah); +} diff --git a/drivers/net/wireless/ath/ath9k/ani.h b/drivers/net/wireless/ath/ath9k/ani.h new file mode 100644 index 0000000..dddb136 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ani.h @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef ANI_H +#define ANI_H + +#define HAL_PROCESS_ANI 0x00000001 + +#define DO_ANI(ah) (((ah)->proc_phyerr & HAL_PROCESS_ANI) && ah->curchan) + +#define BEACON_RSSI(ahp) (ahp->stats.avgbrssi) + +/* units are errors per second */ +#define ATH9K_ANI_OFDM_TRIG_HIGH 3500 +#define ATH9K_ANI_OFDM_TRIG_HIGH_BELOW_INI 1000 + +/* units are errors per second */ +#define ATH9K_ANI_OFDM_TRIG_LOW 400 +#define ATH9K_ANI_OFDM_TRIG_LOW_ABOVE_INI 900 + +/* units are errors per second */ +#define ATH9K_ANI_CCK_TRIG_HIGH 600 + +/* units are errors per second */ +#define ATH9K_ANI_CCK_TRIG_LOW 300 + +#define ATH9K_ANI_NOISE_IMMUNE_LVL 4 +#define ATH9K_ANI_USE_OFDM_WEAK_SIG true +#define ATH9K_ANI_CCK_WEAK_SIG_THR false + +#define ATH9K_ANI_SPUR_IMMUNE_LVL 3 + +#define ATH9K_ANI_FIRSTEP_LVL 2 + +#define ATH9K_ANI_RSSI_THR_HIGH 40 +#define ATH9K_ANI_RSSI_THR_LOW 7 + +#define ATH9K_ANI_PERIOD 300 + +/* in ms */ +#define ATH9K_ANI_POLLINTERVAL 1000 + +#define HAL_NOISE_IMMUNE_MAX 4 +#define HAL_SPUR_IMMUNE_MAX 7 +#define HAL_FIRST_STEP_MAX 2 + +#define ATH9K_SIG_FIRSTEP_SETTING_MIN 0 +#define ATH9K_SIG_FIRSTEP_SETTING_MAX 20 +#define ATH9K_SIG_SPUR_IMM_SETTING_MIN 0 +#define ATH9K_SIG_SPUR_IMM_SETTING_MAX 22 + +/* values here are relative to the INI */ + +enum ath9k_ani_cmd { + ATH9K_ANI_PRESENT = 0x1, + ATH9K_ANI_NOISE_IMMUNITY_LEVEL = 0x2, + ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION = 0x4, + ATH9K_ANI_CCK_WEAK_SIGNAL_THR = 0x8, + ATH9K_ANI_FIRSTEP_LEVEL = 0x10, + ATH9K_ANI_SPUR_IMMUNITY_LEVEL = 0x20, + ATH9K_ANI_MODE = 0x40, + ATH9K_ANI_PHYERR_RESET = 0x80, + ATH9K_ANI_MRC_CCK = 0x100, + ATH9K_ANI_ALL = 0xfff +}; + +struct ath9k_mib_stats { + u32 ackrcv_bad; + u32 rts_bad; + u32 rts_good; + u32 fcs_bad; + u32 beacons; +}; + +/* INI default values for ANI registers */ +struct ath9k_ani_default { + u16 m1ThreshLow; + u16 m2ThreshLow; + u16 m1Thresh; + u16 m2Thresh; + u16 m2CountThr; + u16 m2CountThrLow; + u16 m1ThreshLowExt; + u16 m2ThreshLowExt; + u16 m1ThreshExt; + u16 m2ThreshExt; + u16 firstep; + u16 firstepLow; + u16 cycpwrThr1; + u16 cycpwrThr1Ext; +}; + +struct ar5416AniState { + u8 noiseImmunityLevel; + u8 ofdmNoiseImmunityLevel; + u8 cckNoiseImmunityLevel; + bool ofdmsTurn; + u8 mrcCCK; + u8 spurImmunityLevel; + u8 firstepLevel; + u8 ofdmWeakSigDetect; + u32 listenTime; + u32 ofdmPhyErrCount; + u32 cckPhyErrCount; + struct ath9k_ani_default iniDef; +}; + +struct ar5416Stats { + u32 ast_ani_niup; + u32 ast_ani_nidown; + u32 ast_ani_spurup; + u32 ast_ani_spurdown; + u32 ast_ani_ofdmon; + u32 ast_ani_ofdmoff; + u32 ast_ani_cckhigh; + u32 ast_ani_ccklow; + u32 ast_ani_stepup; + u32 ast_ani_stepdown; + u32 ast_ani_ofdmerrs; + u32 ast_ani_cckerrs; + u32 ast_ani_reset; + u32 ast_ani_lneg_or_lzero; + u32 avgbrssi; + struct ath9k_mib_stats ast_mibstats; +}; +#define ah_mibStats stats.ast_mibstats + +void ath9k_enable_mib_counters(struct ath_hw *ah); +void ath9k_hw_disable_mib_counters(struct ath_hw *ah); +void ath9k_hw_ani_init(struct ath_hw *ah); + +#endif /* ANI_H */ diff --git a/drivers/net/wireless/ath/ath9k/antenna.c b/drivers/net/wireless/ath/ath9k/antenna.c new file mode 100644 index 0000000..664844c --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/antenna.c @@ -0,0 +1,797 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ath9k.h" + +static inline bool ath_is_alt_ant_ratio_better(int alt_ratio, int maxdelta, + int mindelta, int main_rssi_avg, + int alt_rssi_avg, int pkt_count) +{ + return (((alt_ratio >= ATH_ANT_DIV_COMB_ALT_ANT_RATIO2) && + (alt_rssi_avg > main_rssi_avg + maxdelta)) || + (alt_rssi_avg > main_rssi_avg + mindelta)) && (pkt_count > 50); +} + +static inline bool ath_ant_div_comb_alt_check(u8 div_group, int alt_ratio, + int curr_main_set, int curr_alt_set, + int alt_rssi_avg, int main_rssi_avg) +{ + bool result = false; + switch (div_group) { + case 0: + if (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO) + result = true; + break; + case 1: + case 2: + if ((((curr_main_set == ATH_ANT_DIV_COMB_LNA2) && + (curr_alt_set == ATH_ANT_DIV_COMB_LNA1) && + (alt_rssi_avg >= (main_rssi_avg - 5))) || + ((curr_main_set == ATH_ANT_DIV_COMB_LNA1) && + (curr_alt_set == ATH_ANT_DIV_COMB_LNA2) && + (alt_rssi_avg >= (main_rssi_avg - 2)))) && + (alt_rssi_avg >= 4)) + result = true; + else + result = false; + break; + } + + return result; +} + +static void ath_lnaconf_alt_good_scan(struct ath_ant_comb *antcomb, + struct ath_hw_antcomb_conf ant_conf, + int main_rssi_avg) +{ + antcomb->quick_scan_cnt = 0; + + if (ant_conf.main_lna_conf == ATH_ANT_DIV_COMB_LNA2) + antcomb->rssi_lna2 = main_rssi_avg; + else if (ant_conf.main_lna_conf == ATH_ANT_DIV_COMB_LNA1) + antcomb->rssi_lna1 = main_rssi_avg; + + switch ((ant_conf.main_lna_conf << 4) | ant_conf.alt_lna_conf) { + case 0x10: /* LNA2 A-B */ + antcomb->main_conf = ATH_ANT_DIV_COMB_LNA1_MINUS_LNA2; + antcomb->first_quick_scan_conf = + ATH_ANT_DIV_COMB_LNA1_PLUS_LNA2; + antcomb->second_quick_scan_conf = ATH_ANT_DIV_COMB_LNA1; + break; + case 0x20: /* LNA1 A-B */ + antcomb->main_conf = ATH_ANT_DIV_COMB_LNA1_MINUS_LNA2; + antcomb->first_quick_scan_conf = + ATH_ANT_DIV_COMB_LNA1_PLUS_LNA2; + antcomb->second_quick_scan_conf = ATH_ANT_DIV_COMB_LNA2; + break; + case 0x21: /* LNA1 LNA2 */ + antcomb->main_conf = ATH_ANT_DIV_COMB_LNA2; + antcomb->first_quick_scan_conf = + ATH_ANT_DIV_COMB_LNA1_MINUS_LNA2; + antcomb->second_quick_scan_conf = + ATH_ANT_DIV_COMB_LNA1_PLUS_LNA2; + break; + case 0x12: /* LNA2 LNA1 */ + antcomb->main_conf = ATH_ANT_DIV_COMB_LNA1; + antcomb->first_quick_scan_conf = + ATH_ANT_DIV_COMB_LNA1_MINUS_LNA2; + antcomb->second_quick_scan_conf = + ATH_ANT_DIV_COMB_LNA1_PLUS_LNA2; + break; + case 0x13: /* LNA2 A+B */ + antcomb->main_conf = ATH_ANT_DIV_COMB_LNA1_PLUS_LNA2; + antcomb->first_quick_scan_conf = + ATH_ANT_DIV_COMB_LNA1_MINUS_LNA2; + antcomb->second_quick_scan_conf = ATH_ANT_DIV_COMB_LNA1; + break; + case 0x23: /* LNA1 A+B */ + antcomb->main_conf = ATH_ANT_DIV_COMB_LNA1_PLUS_LNA2; + antcomb->first_quick_scan_conf = + ATH_ANT_DIV_COMB_LNA1_MINUS_LNA2; + antcomb->second_quick_scan_conf = ATH_ANT_DIV_COMB_LNA2; + break; + default: + break; + } +} + +static void ath_select_ant_div_from_quick_scan(struct ath_ant_comb *antcomb, + struct ath_hw_antcomb_conf *div_ant_conf, + int main_rssi_avg, int alt_rssi_avg, + int alt_ratio) +{ + /* alt_good */ + switch (antcomb->quick_scan_cnt) { + case 0: + /* set alt to main, and alt to first conf */ + div_ant_conf->main_lna_conf = antcomb->main_conf; + div_ant_conf->alt_lna_conf = antcomb->first_quick_scan_conf; + break; + case 1: + /* set alt to main, and alt to first conf */ + div_ant_conf->main_lna_conf = antcomb->main_conf; + div_ant_conf->alt_lna_conf = antcomb->second_quick_scan_conf; + antcomb->rssi_first = main_rssi_avg; + antcomb->rssi_second = alt_rssi_avg; + + if (antcomb->main_conf == ATH_ANT_DIV_COMB_LNA1) { + /* main is LNA1 */ + if (ath_is_alt_ant_ratio_better(alt_ratio, + ATH_ANT_DIV_COMB_LNA1_DELTA_HI, + ATH_ANT_DIV_COMB_LNA1_DELTA_LOW, + main_rssi_avg, alt_rssi_avg, + antcomb->total_pkt_count)) + antcomb->first_ratio = true; + else + antcomb->first_ratio = false; + } else if (antcomb->main_conf == ATH_ANT_DIV_COMB_LNA2) { + if (ath_is_alt_ant_ratio_better(alt_ratio, + ATH_ANT_DIV_COMB_LNA1_DELTA_MID, + ATH_ANT_DIV_COMB_LNA1_DELTA_LOW, + main_rssi_avg, alt_rssi_avg, + antcomb->total_pkt_count)) + antcomb->first_ratio = true; + else + antcomb->first_ratio = false; + } else { + if ((((alt_ratio >= ATH_ANT_DIV_COMB_ALT_ANT_RATIO2) && + (alt_rssi_avg > main_rssi_avg + + ATH_ANT_DIV_COMB_LNA1_DELTA_HI)) || + (alt_rssi_avg > main_rssi_avg)) && + (antcomb->total_pkt_count > 50)) + antcomb->first_ratio = true; + else + antcomb->first_ratio = false; + } + break; + case 2: + antcomb->alt_good = false; + antcomb->scan_not_start = false; + antcomb->scan = false; + antcomb->rssi_first = main_rssi_avg; + antcomb->rssi_third = alt_rssi_avg; + + if (antcomb->second_quick_scan_conf == ATH_ANT_DIV_COMB_LNA1) + antcomb->rssi_lna1 = alt_rssi_avg; + else if (antcomb->second_quick_scan_conf == + ATH_ANT_DIV_COMB_LNA2) + antcomb->rssi_lna2 = alt_rssi_avg; + else if (antcomb->second_quick_scan_conf == + ATH_ANT_DIV_COMB_LNA1_PLUS_LNA2) { + if (antcomb->main_conf == ATH_ANT_DIV_COMB_LNA2) + antcomb->rssi_lna2 = main_rssi_avg; + else if (antcomb->main_conf == ATH_ANT_DIV_COMB_LNA1) + antcomb->rssi_lna1 = main_rssi_avg; + } + + if (antcomb->rssi_lna2 > antcomb->rssi_lna1 + + ATH_ANT_DIV_COMB_LNA1_LNA2_SWITCH_DELTA) + div_ant_conf->main_lna_conf = ATH_ANT_DIV_COMB_LNA2; + else + div_ant_conf->main_lna_conf = ATH_ANT_DIV_COMB_LNA1; + + if (antcomb->main_conf == ATH_ANT_DIV_COMB_LNA1) { + if (ath_is_alt_ant_ratio_better(alt_ratio, + ATH_ANT_DIV_COMB_LNA1_DELTA_HI, + ATH_ANT_DIV_COMB_LNA1_DELTA_LOW, + main_rssi_avg, alt_rssi_avg, + antcomb->total_pkt_count)) + antcomb->second_ratio = true; + else + antcomb->second_ratio = false; + } else if (antcomb->main_conf == ATH_ANT_DIV_COMB_LNA2) { + if (ath_is_alt_ant_ratio_better(alt_ratio, + ATH_ANT_DIV_COMB_LNA1_DELTA_MID, + ATH_ANT_DIV_COMB_LNA1_DELTA_LOW, + main_rssi_avg, alt_rssi_avg, + antcomb->total_pkt_count)) + antcomb->second_ratio = true; + else + antcomb->second_ratio = false; + } else { + if ((((alt_ratio >= ATH_ANT_DIV_COMB_ALT_ANT_RATIO2) && + (alt_rssi_avg > main_rssi_avg + + ATH_ANT_DIV_COMB_LNA1_DELTA_HI)) || + (alt_rssi_avg > main_rssi_avg)) && + (antcomb->total_pkt_count > 50)) + antcomb->second_ratio = true; + else + antcomb->second_ratio = false; + } + + /* set alt to the conf with maximun ratio */ + if (antcomb->first_ratio && antcomb->second_ratio) { + if (antcomb->rssi_second > antcomb->rssi_third) { + /* first alt*/ + if ((antcomb->first_quick_scan_conf == + ATH_ANT_DIV_COMB_LNA1) || + (antcomb->first_quick_scan_conf == + ATH_ANT_DIV_COMB_LNA2)) + /* Set alt LNA1 or LNA2*/ + if (div_ant_conf->main_lna_conf == + ATH_ANT_DIV_COMB_LNA2) + div_ant_conf->alt_lna_conf = + ATH_ANT_DIV_COMB_LNA1; + else + div_ant_conf->alt_lna_conf = + ATH_ANT_DIV_COMB_LNA2; + else + /* Set alt to A+B or A-B */ + div_ant_conf->alt_lna_conf = + antcomb->first_quick_scan_conf; + } else if ((antcomb->second_quick_scan_conf == + ATH_ANT_DIV_COMB_LNA1) || + (antcomb->second_quick_scan_conf == + ATH_ANT_DIV_COMB_LNA2)) { + /* Set alt LNA1 or LNA2 */ + if (div_ant_conf->main_lna_conf == + ATH_ANT_DIV_COMB_LNA2) + div_ant_conf->alt_lna_conf = + ATH_ANT_DIV_COMB_LNA1; + else + div_ant_conf->alt_lna_conf = + ATH_ANT_DIV_COMB_LNA2; + } else { + /* Set alt to A+B or A-B */ + div_ant_conf->alt_lna_conf = + antcomb->second_quick_scan_conf; + } + } else if (antcomb->first_ratio) { + /* first alt */ + if ((antcomb->first_quick_scan_conf == + ATH_ANT_DIV_COMB_LNA1) || + (antcomb->first_quick_scan_conf == + ATH_ANT_DIV_COMB_LNA2)) + /* Set alt LNA1 or LNA2 */ + if (div_ant_conf->main_lna_conf == + ATH_ANT_DIV_COMB_LNA2) + div_ant_conf->alt_lna_conf = + ATH_ANT_DIV_COMB_LNA1; + else + div_ant_conf->alt_lna_conf = + ATH_ANT_DIV_COMB_LNA2; + else + /* Set alt to A+B or A-B */ + div_ant_conf->alt_lna_conf = + antcomb->first_quick_scan_conf; + } else if (antcomb->second_ratio) { + /* second alt */ + if ((antcomb->second_quick_scan_conf == + ATH_ANT_DIV_COMB_LNA1) || + (antcomb->second_quick_scan_conf == + ATH_ANT_DIV_COMB_LNA2)) + /* Set alt LNA1 or LNA2 */ + if (div_ant_conf->main_lna_conf == + ATH_ANT_DIV_COMB_LNA2) + div_ant_conf->alt_lna_conf = + ATH_ANT_DIV_COMB_LNA1; + else + div_ant_conf->alt_lna_conf = + ATH_ANT_DIV_COMB_LNA2; + else + /* Set alt to A+B or A-B */ + div_ant_conf->alt_lna_conf = + antcomb->second_quick_scan_conf; + } else { + /* main is largest */ + if ((antcomb->main_conf == ATH_ANT_DIV_COMB_LNA1) || + (antcomb->main_conf == ATH_ANT_DIV_COMB_LNA2)) + /* Set alt LNA1 or LNA2 */ + if (div_ant_conf->main_lna_conf == + ATH_ANT_DIV_COMB_LNA2) + div_ant_conf->alt_lna_conf = + ATH_ANT_DIV_COMB_LNA1; + else + div_ant_conf->alt_lna_conf = + ATH_ANT_DIV_COMB_LNA2; + else + /* Set alt to A+B or A-B */ + div_ant_conf->alt_lna_conf = antcomb->main_conf; + } + break; + default: + break; + } +} + +static void ath_ant_div_conf_fast_divbias(struct ath_hw_antcomb_conf *ant_conf, + struct ath_ant_comb *antcomb, + int alt_ratio) +{ + ant_conf->main_gaintb = 0; + ant_conf->alt_gaintb = 0; + + if (ant_conf->div_group == 0) { + /* Adjust the fast_div_bias based on main and alt lna conf */ + switch ((ant_conf->main_lna_conf << 4) | + ant_conf->alt_lna_conf) { + case 0x01: /* A-B LNA2 */ + ant_conf->fast_div_bias = 0x3b; + break; + case 0x02: /* A-B LNA1 */ + ant_conf->fast_div_bias = 0x3d; + break; + case 0x03: /* A-B A+B */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x10: /* LNA2 A-B */ + ant_conf->fast_div_bias = 0x7; + break; + case 0x12: /* LNA2 LNA1 */ + ant_conf->fast_div_bias = 0x2; + break; + case 0x13: /* LNA2 A+B */ + ant_conf->fast_div_bias = 0x7; + break; + case 0x20: /* LNA1 A-B */ + ant_conf->fast_div_bias = 0x6; + break; + case 0x21: /* LNA1 LNA2 */ + ant_conf->fast_div_bias = 0x0; + break; + case 0x23: /* LNA1 A+B */ + ant_conf->fast_div_bias = 0x6; + break; + case 0x30: /* A+B A-B */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x31: /* A+B LNA2 */ + ant_conf->fast_div_bias = 0x3b; + break; + case 0x32: /* A+B LNA1 */ + ant_conf->fast_div_bias = 0x3d; + break; + default: + break; + } + } else if (ant_conf->div_group == 1) { + /* Adjust the fast_div_bias based on main and alt_lna_conf */ + switch ((ant_conf->main_lna_conf << 4) | + ant_conf->alt_lna_conf) { + case 0x01: /* A-B LNA2 */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x02: /* A-B LNA1 */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x03: /* A-B A+B */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x10: /* LNA2 A-B */ + if (!(antcomb->scan) && + (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) + ant_conf->fast_div_bias = 0x3f; + else + ant_conf->fast_div_bias = 0x1; + break; + case 0x12: /* LNA2 LNA1 */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x13: /* LNA2 A+B */ + if (!(antcomb->scan) && + (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) + ant_conf->fast_div_bias = 0x3f; + else + ant_conf->fast_div_bias = 0x1; + break; + case 0x20: /* LNA1 A-B */ + if (!(antcomb->scan) && + (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) + ant_conf->fast_div_bias = 0x3f; + else + ant_conf->fast_div_bias = 0x1; + break; + case 0x21: /* LNA1 LNA2 */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x23: /* LNA1 A+B */ + if (!(antcomb->scan) && + (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) + ant_conf->fast_div_bias = 0x3f; + else + ant_conf->fast_div_bias = 0x1; + break; + case 0x30: /* A+B A-B */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x31: /* A+B LNA2 */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x32: /* A+B LNA1 */ + ant_conf->fast_div_bias = 0x1; + break; + default: + break; + } + } else if (ant_conf->div_group == 2) { + /* Adjust the fast_div_bias based on main and alt_lna_conf */ + switch ((ant_conf->main_lna_conf << 4) | + ant_conf->alt_lna_conf) { + case 0x01: /* A-B LNA2 */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x02: /* A-B LNA1 */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x03: /* A-B A+B */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x10: /* LNA2 A-B */ + if (!(antcomb->scan) && + (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) + ant_conf->fast_div_bias = 0x1; + else + ant_conf->fast_div_bias = 0x2; + break; + case 0x12: /* LNA2 LNA1 */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x13: /* LNA2 A+B */ + if (!(antcomb->scan) && + (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) + ant_conf->fast_div_bias = 0x1; + else + ant_conf->fast_div_bias = 0x2; + break; + case 0x20: /* LNA1 A-B */ + if (!(antcomb->scan) && + (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) + ant_conf->fast_div_bias = 0x1; + else + ant_conf->fast_div_bias = 0x2; + break; + case 0x21: /* LNA1 LNA2 */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x23: /* LNA1 A+B */ + if (!(antcomb->scan) && + (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) + ant_conf->fast_div_bias = 0x1; + else + ant_conf->fast_div_bias = 0x2; + break; + case 0x30: /* A+B A-B */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x31: /* A+B LNA2 */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x32: /* A+B LNA1 */ + ant_conf->fast_div_bias = 0x1; + break; + default: + break; + } + } else if (ant_conf->div_group == 3) { + switch ((ant_conf->main_lna_conf << 4) | + ant_conf->alt_lna_conf) { + case 0x01: /* A-B LNA2 */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x02: /* A-B LNA1 */ + ant_conf->fast_div_bias = 0x39; + break; + case 0x03: /* A-B A+B */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x10: /* LNA2 A-B */ + if ((antcomb->scan == 0) && + (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) { + ant_conf->fast_div_bias = 0x3f; + } else { + ant_conf->fast_div_bias = 0x1; + } + break; + case 0x12: /* LNA2 LNA1 */ + ant_conf->fast_div_bias = 0x39; + break; + case 0x13: /* LNA2 A+B */ + if ((antcomb->scan == 0) && + (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) { + ant_conf->fast_div_bias = 0x3f; + } else { + ant_conf->fast_div_bias = 0x1; + } + break; + case 0x20: /* LNA1 A-B */ + if ((antcomb->scan == 0) && + (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) { + ant_conf->fast_div_bias = 0x3f; + } else { + ant_conf->fast_div_bias = 0x4; + } + break; + case 0x21: /* LNA1 LNA2 */ + ant_conf->fast_div_bias = 0x6; + break; + case 0x23: /* LNA1 A+B */ + if ((antcomb->scan == 0) && + (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO)) { + ant_conf->fast_div_bias = 0x3f; + } else { + ant_conf->fast_div_bias = 0x6; + } + break; + case 0x30: /* A+B A-B */ + ant_conf->fast_div_bias = 0x1; + break; + case 0x31: /* A+B LNA2 */ + ant_conf->fast_div_bias = 0x6; + break; + case 0x32: /* A+B LNA1 */ + ant_conf->fast_div_bias = 0x1; + break; + default: + break; + } + } +} + +void ath_ant_comb_scan(struct ath_softc *sc, struct ath_rx_status *rs) +{ + struct ath_hw_antcomb_conf div_ant_conf; + struct ath_ant_comb *antcomb = &sc->ant_comb; + int alt_ratio = 0, alt_rssi_avg = 0, main_rssi_avg = 0, curr_alt_set; + int curr_main_set; + int main_rssi = rs->rs_rssi_ctl0; + int alt_rssi = rs->rs_rssi_ctl1; + int rx_ant_conf, main_ant_conf; + bool short_scan = false; + + rx_ant_conf = (rs->rs_rssi_ctl2 >> ATH_ANT_RX_CURRENT_SHIFT) & + ATH_ANT_RX_MASK; + main_ant_conf = (rs->rs_rssi_ctl2 >> ATH_ANT_RX_MAIN_SHIFT) & + ATH_ANT_RX_MASK; + + /* Record packet only when both main_rssi and alt_rssi is positive */ + if (main_rssi > 0 && alt_rssi > 0) { + antcomb->total_pkt_count++; + antcomb->main_total_rssi += main_rssi; + antcomb->alt_total_rssi += alt_rssi; + if (main_ant_conf == rx_ant_conf) + antcomb->main_recv_cnt++; + else + antcomb->alt_recv_cnt++; + } + + /* Short scan check */ + if (antcomb->scan && antcomb->alt_good) { + if (time_after(jiffies, antcomb->scan_start_time + + msecs_to_jiffies(ATH_ANT_DIV_COMB_SHORT_SCAN_INTR))) + short_scan = true; + else + if (antcomb->total_pkt_count == + ATH_ANT_DIV_COMB_SHORT_SCAN_PKTCOUNT) { + alt_ratio = ((antcomb->alt_recv_cnt * 100) / + antcomb->total_pkt_count); + if (alt_ratio < ATH_ANT_DIV_COMB_ALT_ANT_RATIO) + short_scan = true; + } + } + + if (((antcomb->total_pkt_count < ATH_ANT_DIV_COMB_MAX_PKTCOUNT) || + rs->rs_moreaggr) && !short_scan) + return; + + if (antcomb->total_pkt_count) { + alt_ratio = ((antcomb->alt_recv_cnt * 100) / + antcomb->total_pkt_count); + main_rssi_avg = (antcomb->main_total_rssi / + antcomb->total_pkt_count); + alt_rssi_avg = (antcomb->alt_total_rssi / + antcomb->total_pkt_count); + } + + + ath9k_hw_antdiv_comb_conf_get(sc->sc_ah, &div_ant_conf); + curr_alt_set = div_ant_conf.alt_lna_conf; + curr_main_set = div_ant_conf.main_lna_conf; + + antcomb->count++; + + if (antcomb->count == ATH_ANT_DIV_COMB_MAX_COUNT) { + if (alt_ratio > ATH_ANT_DIV_COMB_ALT_ANT_RATIO) { + ath_lnaconf_alt_good_scan(antcomb, div_ant_conf, + main_rssi_avg); + antcomb->alt_good = true; + } else { + antcomb->alt_good = false; + } + + antcomb->count = 0; + antcomb->scan = true; + antcomb->scan_not_start = true; + } + + if (!antcomb->scan) { + if (ath_ant_div_comb_alt_check(div_ant_conf.div_group, + alt_ratio, curr_main_set, curr_alt_set, + alt_rssi_avg, main_rssi_avg)) { + if (curr_alt_set == ATH_ANT_DIV_COMB_LNA2) { + /* Switch main and alt LNA */ + div_ant_conf.main_lna_conf = + ATH_ANT_DIV_COMB_LNA2; + div_ant_conf.alt_lna_conf = + ATH_ANT_DIV_COMB_LNA1; + } else if (curr_alt_set == ATH_ANT_DIV_COMB_LNA1) { + div_ant_conf.main_lna_conf = + ATH_ANT_DIV_COMB_LNA1; + div_ant_conf.alt_lna_conf = + ATH_ANT_DIV_COMB_LNA2; + } + + goto div_comb_done; + } else if ((curr_alt_set != ATH_ANT_DIV_COMB_LNA1) && + (curr_alt_set != ATH_ANT_DIV_COMB_LNA2)) { + /* Set alt to another LNA */ + if (curr_main_set == ATH_ANT_DIV_COMB_LNA2) + div_ant_conf.alt_lna_conf = + ATH_ANT_DIV_COMB_LNA1; + else if (curr_main_set == ATH_ANT_DIV_COMB_LNA1) + div_ant_conf.alt_lna_conf = + ATH_ANT_DIV_COMB_LNA2; + + goto div_comb_done; + } + + if ((alt_rssi_avg < (main_rssi_avg + + div_ant_conf.lna1_lna2_delta))) + goto div_comb_done; + } + + if (!antcomb->scan_not_start) { + switch (curr_alt_set) { + case ATH_ANT_DIV_COMB_LNA2: + antcomb->rssi_lna2 = alt_rssi_avg; + antcomb->rssi_lna1 = main_rssi_avg; + antcomb->scan = true; + /* set to A+B */ + div_ant_conf.main_lna_conf = + ATH_ANT_DIV_COMB_LNA1; + div_ant_conf.alt_lna_conf = + ATH_ANT_DIV_COMB_LNA1_PLUS_LNA2; + break; + case ATH_ANT_DIV_COMB_LNA1: + antcomb->rssi_lna1 = alt_rssi_avg; + antcomb->rssi_lna2 = main_rssi_avg; + antcomb->scan = true; + /* set to A+B */ + div_ant_conf.main_lna_conf = ATH_ANT_DIV_COMB_LNA2; + div_ant_conf.alt_lna_conf = + ATH_ANT_DIV_COMB_LNA1_PLUS_LNA2; + break; + case ATH_ANT_DIV_COMB_LNA1_PLUS_LNA2: + antcomb->rssi_add = alt_rssi_avg; + antcomb->scan = true; + /* set to A-B */ + div_ant_conf.alt_lna_conf = + ATH_ANT_DIV_COMB_LNA1_MINUS_LNA2; + break; + case ATH_ANT_DIV_COMB_LNA1_MINUS_LNA2: + antcomb->rssi_sub = alt_rssi_avg; + antcomb->scan = false; + if (antcomb->rssi_lna2 > + (antcomb->rssi_lna1 + + ATH_ANT_DIV_COMB_LNA1_LNA2_SWITCH_DELTA)) { + /* use LNA2 as main LNA */ + if ((antcomb->rssi_add > antcomb->rssi_lna1) && + (antcomb->rssi_add > antcomb->rssi_sub)) { + /* set to A+B */ + div_ant_conf.main_lna_conf = + ATH_ANT_DIV_COMB_LNA2; + div_ant_conf.alt_lna_conf = + ATH_ANT_DIV_COMB_LNA1_PLUS_LNA2; + } else if (antcomb->rssi_sub > + antcomb->rssi_lna1) { + /* set to A-B */ + div_ant_conf.main_lna_conf = + ATH_ANT_DIV_COMB_LNA2; + div_ant_conf.alt_lna_conf = + ATH_ANT_DIV_COMB_LNA1_MINUS_LNA2; + } else { + /* set to LNA1 */ + div_ant_conf.main_lna_conf = + ATH_ANT_DIV_COMB_LNA2; + div_ant_conf.alt_lna_conf = + ATH_ANT_DIV_COMB_LNA1; + } + } else { + /* use LNA1 as main LNA */ + if ((antcomb->rssi_add > antcomb->rssi_lna2) && + (antcomb->rssi_add > antcomb->rssi_sub)) { + /* set to A+B */ + div_ant_conf.main_lna_conf = + ATH_ANT_DIV_COMB_LNA1; + div_ant_conf.alt_lna_conf = + ATH_ANT_DIV_COMB_LNA1_PLUS_LNA2; + } else if (antcomb->rssi_sub > + antcomb->rssi_lna1) { + /* set to A-B */ + div_ant_conf.main_lna_conf = + ATH_ANT_DIV_COMB_LNA1; + div_ant_conf.alt_lna_conf = + ATH_ANT_DIV_COMB_LNA1_MINUS_LNA2; + } else { + /* set to LNA2 */ + div_ant_conf.main_lna_conf = + ATH_ANT_DIV_COMB_LNA1; + div_ant_conf.alt_lna_conf = + ATH_ANT_DIV_COMB_LNA2; + } + } + break; + default: + break; + } + } else { + if (!antcomb->alt_good) { + antcomb->scan_not_start = false; + /* Set alt to another LNA */ + if (curr_main_set == ATH_ANT_DIV_COMB_LNA2) { + div_ant_conf.main_lna_conf = + ATH_ANT_DIV_COMB_LNA2; + div_ant_conf.alt_lna_conf = + ATH_ANT_DIV_COMB_LNA1; + } else if (curr_main_set == ATH_ANT_DIV_COMB_LNA1) { + div_ant_conf.main_lna_conf = + ATH_ANT_DIV_COMB_LNA1; + div_ant_conf.alt_lna_conf = + ATH_ANT_DIV_COMB_LNA2; + } + goto div_comb_done; + } + } + + ath_select_ant_div_from_quick_scan(antcomb, &div_ant_conf, + main_rssi_avg, alt_rssi_avg, + alt_ratio); + + antcomb->quick_scan_cnt++; + +div_comb_done: + ath_ant_div_conf_fast_divbias(&div_ant_conf, antcomb, alt_ratio); + ath9k_hw_antdiv_comb_conf_set(sc->sc_ah, &div_ant_conf); + + antcomb->scan_start_time = jiffies; + antcomb->total_pkt_count = 0; + antcomb->main_total_rssi = 0; + antcomb->alt_total_rssi = 0; + antcomb->main_recv_cnt = 0; + antcomb->alt_recv_cnt = 0; +} + +void ath_ant_comb_update(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath_hw_antcomb_conf div_ant_conf; + u8 lna_conf; + + ath9k_hw_antdiv_comb_conf_get(ah, &div_ant_conf); + + if (sc->ant_rx == 1) + lna_conf = ATH_ANT_DIV_COMB_LNA1; + else + lna_conf = ATH_ANT_DIV_COMB_LNA2; + + div_ant_conf.main_lna_conf = lna_conf; + div_ant_conf.alt_lna_conf = lna_conf; + + ath9k_hw_antdiv_comb_conf_set(ah, &div_ant_conf); + + if (common->antenna_diversity) + ath9k_hw_antctrl_shared_chain_lnadiv(ah, true); +} diff --git a/drivers/net/wireless/ath/ath9k/ar5008_initvals.h b/drivers/net/wireless/ath/ath9k/ar5008_initvals.h new file mode 100644 index 0000000..467ccfa --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar5008_initvals.h @@ -0,0 +1,674 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +static const u32 ar5416Modes[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00001030, 0x00000230, 0x00000460, 0x000002c0, 0x00000160}, + {0x00001070, 0x00000168, 0x000002d0, 0x00000318, 0x0000018c}, + {0x000010b0, 0x00000e60, 0x00001cc0, 0x00007c70, 0x00003e38}, + {0x000010f0, 0x0000a000, 0x00014000, 0x00016000, 0x0000b000}, + {0x00008014, 0x03e803e8, 0x07d007d0, 0x10801600, 0x08400b00}, + {0x0000801c, 0x128d93a7, 0x128d93cf, 0x12e013d7, 0x12e013ab}, + {0x00008120, 0x08f04800, 0x08f04800, 0x08f04810, 0x08f04810}, + {0x000081d0, 0x00003210, 0x00003210, 0x0000320a, 0x0000320a}, + {0x00009804, 0x00000300, 0x000003c4, 0x000003c4, 0x00000300}, + {0x00009820, 0x02020200, 0x02020200, 0x02020200, 0x02020200}, + {0x00009824, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x00009828, 0x0a020001, 0x0a020001, 0x0a020001, 0x0a020001}, + {0x00009834, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x00009838, 0x00000007, 0x00000007, 0x00000007, 0x00000007}, + {0x00009844, 0x1372161e, 0x1372161e, 0x137216a0, 0x137216a0}, + {0x00009848, 0x001a6a65, 0x001a6a65, 0x00197a68, 0x00197a68}, + {0x0000a848, 0x001a6a65, 0x001a6a65, 0x00197a68, 0x00197a68}, + {0x0000b848, 0x001a6a65, 0x001a6a65, 0x00197a68, 0x00197a68}, + {0x00009850, 0x6c48b4e0, 0x6d48b4e0, 0x6d48b0de, 0x6c48b0de}, + {0x00009858, 0x7ec82d2e, 0x7ec82d2e, 0x7ec82d2e, 0x7ec82d2e}, + {0x0000985c, 0x31395d5e, 0x3139605e, 0x3139605e, 0x31395d5e}, + {0x00009860, 0x00049d18, 0x00049d18, 0x00049d18, 0x00049d18}, + {0x00009864, 0x0001ce00, 0x0001ce00, 0x0001ce00, 0x0001ce00}, + {0x00009868, 0x409a4190, 0x409a4190, 0x409a4190, 0x409a4190}, + {0x0000986c, 0x050cb081, 0x050cb081, 0x050cb081, 0x050cb081}, + {0x00009914, 0x000007d0, 0x00000fa0, 0x00001130, 0x00000898}, + {0x00009918, 0x000001b8, 0x00000370, 0x00000268, 0x00000134}, + {0x00009924, 0xd0058a0b, 0xd0058a0b, 0xd0058a0b, 0xd0058a0b}, + {0x00009944, 0xffb81020, 0xffb81020, 0xffb81020, 0xffb81020}, + {0x00009960, 0x00000900, 0x00000900, 0x00012d80, 0x00012d80}, + {0x0000a960, 0x00000900, 0x00000900, 0x00012d80, 0x00012d80}, + {0x0000b960, 0x00000900, 0x00000900, 0x00012d80, 0x00012d80}, + {0x00009964, 0x00000000, 0x00000000, 0x00001120, 0x00001120}, + {0x000099bc, 0x001a0a00, 0x001a0a00, 0x001a0a00, 0x001a0a00}, + {0x000099c0, 0x038919be, 0x038919be, 0x038919be, 0x038919be}, + {0x000099c4, 0x06336f77, 0x06336f77, 0x06336f77, 0x06336f77}, + {0x000099c8, 0x6af6532c, 0x6af6532c, 0x6af6532c, 0x6af6532c}, + {0x000099cc, 0x08f186c8, 0x08f186c8, 0x08f186c8, 0x08f186c8}, + {0x000099d0, 0x00046384, 0x00046384, 0x00046384, 0x00046384}, + {0x000099d4, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x000099d8, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a204, 0x00000880, 0x00000880, 0x00000880, 0x00000880}, + {0x0000a208, 0xd6be4788, 0xd6be4788, 0xd03e4788, 0xd03e4788}, + {0x0000a20c, 0x002ec1e0, 0x002ec1e0, 0x002ac120, 0x002ac120}, + {0x0000b20c, 0x002ec1e0, 0x002ec1e0, 0x002ac120, 0x002ac120}, + {0x0000c20c, 0x002ec1e0, 0x002ec1e0, 0x002ac120, 0x002ac120}, + {0x0000a21c, 0x1883800a, 0x1883800a, 0x1883800a, 0x1883800a}, + {0x0000a230, 0x00000000, 0x00000000, 0x00000210, 0x00000108}, + {0x0000a274, 0x0a1a9caa, 0x0a1a9caa, 0x0a1a7caa, 0x0a1a7caa}, + {0x0000a300, 0x18010000, 0x18010000, 0x18010000, 0x18010000}, + {0x0000a304, 0x30032602, 0x30032602, 0x2e032402, 0x2e032402}, + {0x0000a308, 0x48073e06, 0x48073e06, 0x4a0a3c06, 0x4a0a3c06}, + {0x0000a30c, 0x560b4c0a, 0x560b4c0a, 0x621a540b, 0x621a540b}, + {0x0000a310, 0x641a600f, 0x641a600f, 0x764f6c1b, 0x764f6c1b}, + {0x0000a314, 0x7a4f6e1b, 0x7a4f6e1b, 0x845b7a5a, 0x845b7a5a}, + {0x0000a318, 0x8c5b7e5a, 0x8c5b7e5a, 0x950f8ccf, 0x950f8ccf}, + {0x0000a31c, 0x9d0f96cf, 0x9d0f96cf, 0xa5cf9b4f, 0xa5cf9b4f}, + {0x0000a320, 0xb51fa69f, 0xb51fa69f, 0xbddfaf1f, 0xbddfaf1f}, + {0x0000a324, 0xcb3fbd07, 0xcb3fbcbf, 0xd1ffc93f, 0xd1ffc93f}, + {0x0000a328, 0x0000d7bf, 0x0000d7bf, 0x00000000, 0x00000000}, + {0x0000a32c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a330, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a334, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, +}; + +static const u32 ar5416Common[][2] = { + /* Addr allmodes */ + {0x0000000c, 0x00000000}, + {0x00000030, 0x00020015}, + {0x00000034, 0x00000005}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000008}, + {0x00000048, 0x00000008}, + {0x0000004c, 0x00000010}, + {0x00000050, 0x00000000}, + {0x00000054, 0x0000001f}, + {0x00000800, 0x00000000}, + {0x00000804, 0x00000000}, + {0x00000808, 0x00000000}, + {0x0000080c, 0x00000000}, + {0x00000810, 0x00000000}, + {0x00000814, 0x00000000}, + {0x00000818, 0x00000000}, + {0x0000081c, 0x00000000}, + {0x00000820, 0x00000000}, + {0x00000824, 0x00000000}, + {0x00001040, 0x002ffc0f}, + {0x00001044, 0x002ffc0f}, + {0x00001048, 0x002ffc0f}, + {0x0000104c, 0x002ffc0f}, + {0x00001050, 0x002ffc0f}, + {0x00001054, 0x002ffc0f}, + {0x00001058, 0x002ffc0f}, + {0x0000105c, 0x002ffc0f}, + {0x00001060, 0x002ffc0f}, + {0x00001064, 0x002ffc0f}, + {0x00001230, 0x00000000}, + {0x00001270, 0x00000000}, + {0x00001038, 0x00000000}, + {0x00001078, 0x00000000}, + {0x000010b8, 0x00000000}, + {0x000010f8, 0x00000000}, + {0x00001138, 0x00000000}, + {0x00001178, 0x00000000}, + {0x000011b8, 0x00000000}, + {0x000011f8, 0x00000000}, + {0x00001238, 0x00000000}, + {0x00001278, 0x00000000}, + {0x000012b8, 0x00000000}, + {0x000012f8, 0x00000000}, + {0x00001338, 0x00000000}, + {0x00001378, 0x00000000}, + {0x000013b8, 0x00000000}, + {0x000013f8, 0x00000000}, + {0x00001438, 0x00000000}, + {0x00001478, 0x00000000}, + {0x000014b8, 0x00000000}, + {0x000014f8, 0x00000000}, + {0x00001538, 0x00000000}, + {0x00001578, 0x00000000}, + {0x000015b8, 0x00000000}, + {0x000015f8, 0x00000000}, + {0x00001638, 0x00000000}, + {0x00001678, 0x00000000}, + {0x000016b8, 0x00000000}, + {0x000016f8, 0x00000000}, + {0x00001738, 0x00000000}, + {0x00001778, 0x00000000}, + {0x000017b8, 0x00000000}, + {0x000017f8, 0x00000000}, + {0x0000103c, 0x00000000}, + {0x0000107c, 0x00000000}, + {0x000010bc, 0x00000000}, + {0x000010fc, 0x00000000}, + {0x0000113c, 0x00000000}, + {0x0000117c, 0x00000000}, + {0x000011bc, 0x00000000}, + {0x000011fc, 0x00000000}, + {0x0000123c, 0x00000000}, + {0x0000127c, 0x00000000}, + {0x000012bc, 0x00000000}, + {0x000012fc, 0x00000000}, + {0x0000133c, 0x00000000}, + {0x0000137c, 0x00000000}, + {0x000013bc, 0x00000000}, + {0x000013fc, 0x00000000}, + {0x0000143c, 0x00000000}, + {0x0000147c, 0x00000000}, + {0x00004030, 0x00000002}, + {0x0000403c, 0x00000002}, + {0x00007010, 0x00000000}, + {0x00007038, 0x000004c2}, + {0x00008004, 0x00000000}, + {0x00008008, 0x00000000}, + {0x0000800c, 0x00000000}, + {0x00008018, 0x00000700}, + {0x00008020, 0x00000000}, + {0x00008038, 0x00000000}, + {0x0000803c, 0x00000000}, + {0x00008048, 0x40000000}, + {0x00008054, 0x00000000}, + {0x00008058, 0x00000000}, + {0x0000805c, 0x000fc78f}, + {0x00008060, 0x0000000f}, + {0x00008064, 0x00000000}, + {0x000080c0, 0x2a82301a}, + {0x000080c4, 0x05dc01e0}, + {0x000080c8, 0x1f402710}, + {0x000080cc, 0x01f40000}, + {0x000080d0, 0x00001e00}, + {0x000080d4, 0x00000000}, + {0x000080d8, 0x00400000}, + {0x000080e0, 0xffffffff}, + {0x000080e4, 0x0000ffff}, + {0x000080e8, 0x003f3f3f}, + {0x000080ec, 0x00000000}, + {0x000080f0, 0x00000000}, + {0x000080f4, 0x00000000}, + {0x000080f8, 0x00000000}, + {0x000080fc, 0x00020000}, + {0x00008100, 0x00020000}, + {0x00008104, 0x00000001}, + {0x00008108, 0x00000052}, + {0x0000810c, 0x00000000}, + {0x00008110, 0x00000168}, + {0x00008118, 0x000100aa}, + {0x0000811c, 0x00003210}, + {0x00008124, 0x00000000}, + {0x00008128, 0x00000000}, + {0x0000812c, 0x00000000}, + {0x00008130, 0x00000000}, + {0x00008134, 0x00000000}, + {0x00008138, 0x00000000}, + {0x0000813c, 0x00000000}, + {0x00008144, 0xffffffff}, + {0x00008168, 0x00000000}, + {0x0000816c, 0x00000000}, + {0x00008170, 0x32143320}, + {0x00008174, 0xfaa4fa50}, + {0x00008178, 0x00000100}, + {0x0000817c, 0x00000000}, + {0x000081c4, 0x00000000}, + {0x000081ec, 0x00000000}, + {0x000081f0, 0x00000000}, + {0x000081f4, 0x00000000}, + {0x000081f8, 0x00000000}, + {0x000081fc, 0x00000000}, + {0x00008200, 0x00000000}, + {0x00008204, 0x00000000}, + {0x00008208, 0x00000000}, + {0x0000820c, 0x00000000}, + {0x00008210, 0x00000000}, + {0x00008214, 0x00000000}, + {0x00008218, 0x00000000}, + {0x0000821c, 0x00000000}, + {0x00008220, 0x00000000}, + {0x00008224, 0x00000000}, + {0x00008228, 0x00000000}, + {0x0000822c, 0x00000000}, + {0x00008230, 0x00000000}, + {0x00008234, 0x00000000}, + {0x00008238, 0x00000000}, + {0x0000823c, 0x00000000}, + {0x00008240, 0x00100000}, + {0x00008244, 0x0010f400}, + {0x00008248, 0x00000100}, + {0x0000824c, 0x0001e800}, + {0x00008250, 0x00000000}, + {0x00008254, 0x00000000}, + {0x00008258, 0x00000000}, + {0x0000825c, 0x400000ff}, + {0x00008260, 0x00080922}, + {0x00008264, 0x88000010}, + {0x00008270, 0x00000000}, + {0x00008274, 0x40000000}, + {0x00008278, 0x003e4180}, + {0x0000827c, 0x00000000}, + {0x00008284, 0x0000002c}, + {0x00008288, 0x0000002c}, + {0x0000828c, 0x00000000}, + {0x00008294, 0x00000000}, + {0x00008298, 0x00000000}, + {0x00008300, 0x00000000}, + {0x00008304, 0x00000000}, + {0x00008308, 0x00000000}, + {0x0000830c, 0x00000000}, + {0x00008310, 0x00000000}, + {0x00008314, 0x00000000}, + {0x00008318, 0x00000000}, + {0x00008328, 0x00000000}, + {0x0000832c, 0x00000007}, + {0x00008330, 0x00000302}, + {0x00008334, 0x00000e00}, + {0x00008338, 0x00070000}, + {0x0000833c, 0x00000000}, + {0x00008340, 0x000107ff}, + {0x00009808, 0x00000000}, + {0x0000980c, 0xad848e19}, + {0x00009810, 0x7d14e000}, + {0x00009814, 0x9c0a9f6b}, + {0x0000981c, 0x00000000}, + {0x0000982c, 0x0000a000}, + {0x00009830, 0x00000000}, + {0x0000983c, 0x00200400}, + {0x00009840, 0x206a002e}, + {0x0000984c, 0x1284233c}, + {0x00009854, 0x00000859}, + {0x00009900, 0x00000000}, + {0x00009904, 0x00000000}, + {0x00009908, 0x00000000}, + {0x0000990c, 0x00000000}, + {0x0000991c, 0x10000fff}, + {0x00009920, 0x05100000}, + {0x0000a920, 0x05100000}, + {0x0000b920, 0x05100000}, + {0x00009928, 0x00000001}, + {0x0000992c, 0x00000004}, + {0x00009934, 0x1e1f2022}, + {0x00009938, 0x0a0b0c0d}, + {0x0000993c, 0x00000000}, + {0x00009948, 0x9280b212}, + {0x0000994c, 0x00020028}, + {0x00009954, 0x5d50e188}, + {0x00009958, 0x00081fff}, + {0x0000c95c, 0x004b6a8e}, + {0x0000c968, 0x000003ce}, + {0x00009970, 0x190fb515}, + {0x00009974, 0x00000000}, + {0x00009978, 0x00000001}, + {0x0000997c, 0x00000000}, + {0x00009980, 0x00000000}, + {0x00009984, 0x00000000}, + {0x00009988, 0x00000000}, + {0x0000998c, 0x00000000}, + {0x00009990, 0x00000000}, + {0x00009994, 0x00000000}, + {0x00009998, 0x00000000}, + {0x0000999c, 0x00000000}, + {0x000099a0, 0x00000000}, + {0x000099a4, 0x00000001}, + {0x000099a8, 0x001fff00}, + {0x000099ac, 0x00000000}, + {0x000099b0, 0x03051000}, + {0x000099dc, 0x00000000}, + {0x000099e0, 0x00000200}, + {0x000099e4, 0xaaaaaaaa}, + {0x000099e8, 0x3c466478}, + {0x000099ec, 0x000000aa}, + {0x000099fc, 0x00001042}, + {0x00009b00, 0x00000000}, + {0x00009b04, 0x00000001}, + {0x00009b08, 0x00000002}, + {0x00009b0c, 0x00000003}, + {0x00009b10, 0x00000004}, + {0x00009b14, 0x00000005}, + {0x00009b18, 0x00000008}, + {0x00009b1c, 0x00000009}, + {0x00009b20, 0x0000000a}, + {0x00009b24, 0x0000000b}, + {0x00009b28, 0x0000000c}, + {0x00009b2c, 0x0000000d}, + {0x00009b30, 0x00000010}, + {0x00009b34, 0x00000011}, + {0x00009b38, 0x00000012}, + {0x00009b3c, 0x00000013}, + {0x00009b40, 0x00000014}, + {0x00009b44, 0x00000015}, + {0x00009b48, 0x00000018}, + {0x00009b4c, 0x00000019}, + {0x00009b50, 0x0000001a}, + {0x00009b54, 0x0000001b}, + {0x00009b58, 0x0000001c}, + {0x00009b5c, 0x0000001d}, + {0x00009b60, 0x00000020}, + {0x00009b64, 0x00000021}, + {0x00009b68, 0x00000022}, + {0x00009b6c, 0x00000023}, + {0x00009b70, 0x00000024}, + {0x00009b74, 0x00000025}, + {0x00009b78, 0x00000028}, + {0x00009b7c, 0x00000029}, + {0x00009b80, 0x0000002a}, + {0x00009b84, 0x0000002b}, + {0x00009b88, 0x0000002c}, + {0x00009b8c, 0x0000002d}, + {0x00009b90, 0x00000030}, + {0x00009b94, 0x00000031}, + {0x00009b98, 0x00000032}, + {0x00009b9c, 0x00000033}, + {0x00009ba0, 0x00000034}, + {0x00009ba4, 0x00000035}, + {0x00009ba8, 0x00000035}, + {0x00009bac, 0x00000035}, + {0x00009bb0, 0x00000035}, + {0x00009bb4, 0x00000035}, + {0x00009bb8, 0x00000035}, + {0x00009bbc, 0x00000035}, + {0x00009bc0, 0x00000035}, + {0x00009bc4, 0x00000035}, + {0x00009bc8, 0x00000035}, + {0x00009bcc, 0x00000035}, + {0x00009bd0, 0x00000035}, + {0x00009bd4, 0x00000035}, + {0x00009bd8, 0x00000035}, + {0x00009bdc, 0x00000035}, + {0x00009be0, 0x00000035}, + {0x00009be4, 0x00000035}, + {0x00009be8, 0x00000035}, + {0x00009bec, 0x00000035}, + {0x00009bf0, 0x00000035}, + {0x00009bf4, 0x00000035}, + {0x00009bf8, 0x00000010}, + {0x00009bfc, 0x0000001a}, + {0x0000a210, 0x40806333}, + {0x0000a214, 0x00106c10}, + {0x0000a218, 0x009c4060}, + {0x0000a220, 0x018830c6}, + {0x0000a224, 0x00000400}, + {0x0000a228, 0x00000bb5}, + {0x0000a22c, 0x00000011}, + {0x0000a234, 0x20202020}, + {0x0000a238, 0x20202020}, + {0x0000a23c, 0x13c889af}, + {0x0000a240, 0x38490a20}, + {0x0000a244, 0x00007bb6}, + {0x0000a248, 0x0fff3ffc}, + {0x0000a24c, 0x00000001}, + {0x0000a250, 0x0000a000}, + {0x0000a254, 0x00000000}, + {0x0000a258, 0x0cc75380}, + {0x0000a25c, 0x0f0f0f01}, + {0x0000a260, 0xdfa91f01}, + {0x0000a268, 0x00000000}, + {0x0000a26c, 0x0e79e5c6}, + {0x0000b26c, 0x0e79e5c6}, + {0x0000c26c, 0x0e79e5c6}, + {0x0000d270, 0x00820820}, + {0x0000a278, 0x1ce739ce}, + {0x0000a27c, 0x051701ce}, + {0x0000a338, 0x00000000}, + {0x0000a33c, 0x00000000}, + {0x0000a340, 0x00000000}, + {0x0000a344, 0x00000000}, + {0x0000a348, 0x3fffffff}, + {0x0000a34c, 0x3fffffff}, + {0x0000a350, 0x3fffffff}, + {0x0000a354, 0x0003ffff}, + {0x0000a358, 0x79a8aa1f}, + {0x0000d35c, 0x07ffffef}, + {0x0000d360, 0x0fffffe7}, + {0x0000d364, 0x17ffffe5}, + {0x0000d368, 0x1fffffe4}, + {0x0000d36c, 0x37ffffe3}, + {0x0000d370, 0x3fffffe3}, + {0x0000d374, 0x57ffffe3}, + {0x0000d378, 0x5fffffe2}, + {0x0000d37c, 0x7fffffe2}, + {0x0000d380, 0x7f3c7bba}, + {0x0000d384, 0xf3307ff0}, + {0x0000a388, 0x08000000}, + {0x0000a38c, 0x20202020}, + {0x0000a390, 0x20202020}, + {0x0000a394, 0x1ce739ce}, + {0x0000a398, 0x000001ce}, + {0x0000a39c, 0x00000001}, + {0x0000a3a0, 0x00000000}, + {0x0000a3a4, 0x00000000}, + {0x0000a3a8, 0x00000000}, + {0x0000a3ac, 0x00000000}, + {0x0000a3b0, 0x00000000}, + {0x0000a3b4, 0x00000000}, + {0x0000a3b8, 0x00000000}, + {0x0000a3bc, 0x00000000}, + {0x0000a3c0, 0x00000000}, + {0x0000a3c4, 0x00000000}, + {0x0000a3c8, 0x00000246}, + {0x0000a3cc, 0x20202020}, + {0x0000a3d0, 0x20202020}, + {0x0000a3d4, 0x20202020}, + {0x0000a3dc, 0x1ce739ce}, + {0x0000a3e0, 0x000001ce}, +}; + +static const u32 ar5416Bank0[][2] = { + /* Addr allmodes */ + {0x000098b0, 0x1e5795e5}, + {0x000098e0, 0x02008020}, +}; + +static const u32 ar5416BB_RfGain[][3] = { + /* Addr 5G 2G */ + {0x00009a00, 0x00000000, 0x00000000}, + {0x00009a04, 0x00000040, 0x00000040}, + {0x00009a08, 0x00000080, 0x00000080}, + {0x00009a0c, 0x000001a1, 0x00000141}, + {0x00009a10, 0x000001e1, 0x00000181}, + {0x00009a14, 0x00000021, 0x000001c1}, + {0x00009a18, 0x00000061, 0x00000001}, + {0x00009a1c, 0x00000168, 0x00000041}, + {0x00009a20, 0x000001a8, 0x000001a8}, + {0x00009a24, 0x000001e8, 0x000001e8}, + {0x00009a28, 0x00000028, 0x00000028}, + {0x00009a2c, 0x00000068, 0x00000068}, + {0x00009a30, 0x00000189, 0x000000a8}, + {0x00009a34, 0x000001c9, 0x00000169}, + {0x00009a38, 0x00000009, 0x000001a9}, + {0x00009a3c, 0x00000049, 0x000001e9}, + {0x00009a40, 0x00000089, 0x00000029}, + {0x00009a44, 0x00000170, 0x00000069}, + {0x00009a48, 0x000001b0, 0x00000190}, + {0x00009a4c, 0x000001f0, 0x000001d0}, + {0x00009a50, 0x00000030, 0x00000010}, + {0x00009a54, 0x00000070, 0x00000050}, + {0x00009a58, 0x00000191, 0x00000090}, + {0x00009a5c, 0x000001d1, 0x00000151}, + {0x00009a60, 0x00000011, 0x00000191}, + {0x00009a64, 0x00000051, 0x000001d1}, + {0x00009a68, 0x00000091, 0x00000011}, + {0x00009a6c, 0x000001b8, 0x00000051}, + {0x00009a70, 0x000001f8, 0x00000198}, + {0x00009a74, 0x00000038, 0x000001d8}, + {0x00009a78, 0x00000078, 0x00000018}, + {0x00009a7c, 0x00000199, 0x00000058}, + {0x00009a80, 0x000001d9, 0x00000098}, + {0x00009a84, 0x00000019, 0x00000159}, + {0x00009a88, 0x00000059, 0x00000199}, + {0x00009a8c, 0x00000099, 0x000001d9}, + {0x00009a90, 0x000000d9, 0x00000019}, + {0x00009a94, 0x000000f9, 0x00000059}, + {0x00009a98, 0x000000f9, 0x00000099}, + {0x00009a9c, 0x000000f9, 0x000000d9}, + {0x00009aa0, 0x000000f9, 0x000000f9}, + {0x00009aa4, 0x000000f9, 0x000000f9}, + {0x00009aa8, 0x000000f9, 0x000000f9}, + {0x00009aac, 0x000000f9, 0x000000f9}, + {0x00009ab0, 0x000000f9, 0x000000f9}, + {0x00009ab4, 0x000000f9, 0x000000f9}, + {0x00009ab8, 0x000000f9, 0x000000f9}, + {0x00009abc, 0x000000f9, 0x000000f9}, + {0x00009ac0, 0x000000f9, 0x000000f9}, + {0x00009ac4, 0x000000f9, 0x000000f9}, + {0x00009ac8, 0x000000f9, 0x000000f9}, + {0x00009acc, 0x000000f9, 0x000000f9}, + {0x00009ad0, 0x000000f9, 0x000000f9}, + {0x00009ad4, 0x000000f9, 0x000000f9}, + {0x00009ad8, 0x000000f9, 0x000000f9}, + {0x00009adc, 0x000000f9, 0x000000f9}, + {0x00009ae0, 0x000000f9, 0x000000f9}, + {0x00009ae4, 0x000000f9, 0x000000f9}, + {0x00009ae8, 0x000000f9, 0x000000f9}, + {0x00009aec, 0x000000f9, 0x000000f9}, + {0x00009af0, 0x000000f9, 0x000000f9}, + {0x00009af4, 0x000000f9, 0x000000f9}, + {0x00009af8, 0x000000f9, 0x000000f9}, + {0x00009afc, 0x000000f9, 0x000000f9}, +}; + +static const u32 ar5416Bank1[][2] = { + /* Addr allmodes */ + {0x000098b0, 0x02108421}, + {0x000098ec, 0x00000008}, +}; + +static const u32 ar5416Bank2[][2] = { + /* Addr allmodes */ + {0x000098b0, 0x0e73ff17}, + {0x000098e0, 0x00000420}, +}; + +static const u32 ar5416Bank3[][3] = { + /* Addr 5G 2G */ + {0x000098f0, 0x01400018, 0x01c00018}, +}; + +static const u32 ar5416Bank6[][3] = { + /* Addr 5G 2G */ + {0x0000989c, 0x00000000, 0x00000000}, + {0x0000989c, 0x00000000, 0x00000000}, + {0x0000989c, 0x00000000, 0x00000000}, + {0x0000989c, 0x00e00000, 0x00e00000}, + {0x0000989c, 0x005e0000, 0x005e0000}, + {0x0000989c, 0x00120000, 0x00120000}, + {0x0000989c, 0x00620000, 0x00620000}, + {0x0000989c, 0x00020000, 0x00020000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x40ff0000, 0x40ff0000}, + {0x0000989c, 0x005f0000, 0x005f0000}, + {0x0000989c, 0x00870000, 0x00870000}, + {0x0000989c, 0x00f90000, 0x00f90000}, + {0x0000989c, 0x007b0000, 0x007b0000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x00f50000, 0x00f50000}, + {0x0000989c, 0x00dc0000, 0x00dc0000}, + {0x0000989c, 0x00110000, 0x00110000}, + {0x0000989c, 0x006100a8, 0x006100a8}, + {0x0000989c, 0x004210a2, 0x004210a2}, + {0x0000989c, 0x0014008f, 0x0014008f}, + {0x0000989c, 0x00c40003, 0x00c40003}, + {0x0000989c, 0x003000f2, 0x003000f2}, + {0x0000989c, 0x00440016, 0x00440016}, + {0x0000989c, 0x00410040, 0x00410040}, + {0x0000989c, 0x0001805e, 0x0001805e}, + {0x0000989c, 0x0000c0ab, 0x0000c0ab}, + {0x0000989c, 0x000000f1, 0x000000f1}, + {0x0000989c, 0x00002081, 0x00002081}, + {0x0000989c, 0x000000d4, 0x000000d4}, + {0x000098d0, 0x0000000f, 0x0010000f}, +}; + +static const u32 ar5416Bank6TPC[][3] = { + /* Addr 5G 2G */ + {0x0000989c, 0x00000000, 0x00000000}, + {0x0000989c, 0x00000000, 0x00000000}, + {0x0000989c, 0x00000000, 0x00000000}, + {0x0000989c, 0x00e00000, 0x00e00000}, + {0x0000989c, 0x005e0000, 0x005e0000}, + {0x0000989c, 0x00120000, 0x00120000}, + {0x0000989c, 0x00620000, 0x00620000}, + {0x0000989c, 0x00020000, 0x00020000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x40ff0000, 0x40ff0000}, + {0x0000989c, 0x005f0000, 0x005f0000}, + {0x0000989c, 0x00870000, 0x00870000}, + {0x0000989c, 0x00f90000, 0x00f90000}, + {0x0000989c, 0x007b0000, 0x007b0000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x00f50000, 0x00f50000}, + {0x0000989c, 0x00dc0000, 0x00dc0000}, + {0x0000989c, 0x00110000, 0x00110000}, + {0x0000989c, 0x006100a8, 0x006100a8}, + {0x0000989c, 0x00423022, 0x00423022}, + {0x0000989c, 0x201400df, 0x201400df}, + {0x0000989c, 0x00c40002, 0x00c40002}, + {0x0000989c, 0x003000f2, 0x003000f2}, + {0x0000989c, 0x00440016, 0x00440016}, + {0x0000989c, 0x00410040, 0x00410040}, + {0x0000989c, 0x0001805e, 0x0001805e}, + {0x0000989c, 0x0000c0ab, 0x0000c0ab}, + {0x0000989c, 0x000000e1, 0x000000e1}, + {0x0000989c, 0x00007081, 0x00007081}, + {0x0000989c, 0x000000d4, 0x000000d4}, + {0x000098d0, 0x0000000f, 0x0010000f}, +}; + +static const u32 ar5416Bank7[][2] = { + /* Addr allmodes */ + {0x0000989c, 0x00000500}, + {0x0000989c, 0x00000800}, + {0x000098cc, 0x0000000e}, +}; + +static const u32 ar5416Addac[][2] = { + /* Addr allmodes */ + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000003}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x0000000c}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000030}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000060}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000058}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x000098c4, 0x00000000}, +}; + diff --git a/drivers/net/wireless/ath/ath9k/ar5008_phy.c b/drivers/net/wireless/ath/ath9k/ar5008_phy.c new file mode 100644 index 0000000..391da5a --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar5008_phy.c @@ -0,0 +1,1358 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "hw.h" +#include "hw-ops.h" +#include "../regd.h" +#include "ar9002_phy.h" +#include "ar5008_initvals.h" + +/* All code below is for AR5008, AR9001, AR9002 */ + +static const int firstep_table[] = +/* level: 0 1 2 3 4 5 6 7 8 */ + { -4, -2, 0, 2, 4, 6, 8, 10, 12 }; /* lvl 0-8, default 2 */ + +static const int cycpwrThr1_table[] = +/* level: 0 1 2 3 4 5 6 7 8 */ + { -6, -4, -2, 0, 2, 4, 6, 8 }; /* lvl 0-7, default 3 */ + +/* + * register values to turn OFDM weak signal detection OFF + */ +static const int m1ThreshLow_off = 127; +static const int m2ThreshLow_off = 127; +static const int m1Thresh_off = 127; +static const int m2Thresh_off = 127; +static const int m2CountThr_off = 31; +static const int m2CountThrLow_off = 63; +static const int m1ThreshLowExt_off = 127; +static const int m2ThreshLowExt_off = 127; +static const int m1ThreshExt_off = 127; +static const int m2ThreshExt_off = 127; + +static const struct ar5416IniArray bank0 = STATIC_INI_ARRAY(ar5416Bank0); +static const struct ar5416IniArray bank1 = STATIC_INI_ARRAY(ar5416Bank1); +static const struct ar5416IniArray bank2 = STATIC_INI_ARRAY(ar5416Bank2); +static const struct ar5416IniArray bank3 = STATIC_INI_ARRAY(ar5416Bank3); +static const struct ar5416IniArray bank7 = STATIC_INI_ARRAY(ar5416Bank7); + +static void ar5008_write_bank6(struct ath_hw *ah, unsigned int *writecnt) +{ + struct ar5416IniArray *array = &ah->iniBank6; + u32 *data = ah->analogBank6Data; + int r; + + ENABLE_REGWRITE_BUFFER(ah); + + for (r = 0; r < array->ia_rows; r++) { + REG_WRITE(ah, INI_RA(array, r, 0), data[r]); + DO_DELAY(*writecnt); + } + + REGWRITE_BUFFER_FLUSH(ah); +} + +/** + * ar5008_hw_phy_modify_rx_buffer() - perform analog swizzling of parameters + * @rfbuf: + * @reg32: + * @numBits: + * @firstBit: + * @column: + * + * Performs analog "swizzling" of parameters into their location. + * Used on external AR2133/AR5133 radios. + */ +static void ar5008_hw_phy_modify_rx_buffer(u32 *rfBuf, u32 reg32, + u32 numBits, u32 firstBit, + u32 column) +{ + u32 tmp32, mask, arrayEntry, lastBit; + int32_t bitPosition, bitsLeft; + + tmp32 = ath9k_hw_reverse_bits(reg32, numBits); + arrayEntry = (firstBit - 1) / 8; + bitPosition = (firstBit - 1) % 8; + bitsLeft = numBits; + while (bitsLeft > 0) { + lastBit = (bitPosition + bitsLeft > 8) ? + 8 : bitPosition + bitsLeft; + mask = (((1 << lastBit) - 1) ^ ((1 << bitPosition) - 1)) << + (column * 8); + rfBuf[arrayEntry] &= ~mask; + rfBuf[arrayEntry] |= ((tmp32 << bitPosition) << + (column * 8)) & mask; + bitsLeft -= 8 - bitPosition; + tmp32 = tmp32 >> (8 - bitPosition); + bitPosition = 0; + arrayEntry++; + } +} + +/* + * Fix on 2.4 GHz band for orientation sensitivity issue by increasing + * rf_pwd_icsyndiv. + * + * Theoretical Rules: + * if 2 GHz band + * if forceBiasAuto + * if synth_freq < 2412 + * bias = 0 + * else if 2412 <= synth_freq <= 2422 + * bias = 1 + * else // synth_freq > 2422 + * bias = 2 + * else if forceBias > 0 + * bias = forceBias & 7 + * else + * no change, use value from ini file + * else + * no change, invalid band + * + * 1st Mod: + * 2422 also uses value of 2 + * + * + * 2nd Mod: + * Less than 2412 uses value of 0, 2412 and above uses value of 2 + */ +static void ar5008_hw_force_bias(struct ath_hw *ah, u16 synth_freq) +{ + struct ath_common *common = ath9k_hw_common(ah); + u32 tmp_reg; + int reg_writes = 0; + u32 new_bias = 0; + + if (!AR_SREV_5416(ah) || synth_freq >= 3000) + return; + + BUG_ON(AR_SREV_9280_20_OR_LATER(ah)); + + if (synth_freq < 2412) + new_bias = 0; + else if (synth_freq < 2422) + new_bias = 1; + else + new_bias = 2; + + /* pre-reverse this field */ + tmp_reg = ath9k_hw_reverse_bits(new_bias, 3); + + ath_dbg(common, CONFIG, "Force rf_pwd_icsyndiv to %1d on %4d\n", + new_bias, synth_freq); + + /* swizzle rf_pwd_icsyndiv */ + ar5008_hw_phy_modify_rx_buffer(ah->analogBank6Data, tmp_reg, 3, 181, 3); + + /* write Bank 6 with new params */ + ar5008_write_bank6(ah, ®_writes); +} + +/** + * ar5008_hw_set_channel - tune to a channel on the external AR2133/AR5133 radios + * @ah: atheros hardware structure + * @chan: + * + * For the external AR2133/AR5133 radios, takes the MHz channel value and set + * the channel value. Assumes writes enabled to analog bus and bank6 register + * cache in ah->analogBank6Data. + */ +static int ar5008_hw_set_channel(struct ath_hw *ah, struct ath9k_channel *chan) +{ + struct ath_common *common = ath9k_hw_common(ah); + u32 channelSel = 0; + u32 bModeSynth = 0; + u32 aModeRefSel = 0; + u32 reg32 = 0; + u16 freq; + struct chan_centers centers; + + ath9k_hw_get_channel_centers(ah, chan, ¢ers); + freq = centers.synth_center; + + if (freq < 4800) { + u32 txctl; + + if (((freq - 2192) % 5) == 0) { + channelSel = ((freq - 672) * 2 - 3040) / 10; + bModeSynth = 0; + } else if (((freq - 2224) % 5) == 0) { + channelSel = ((freq - 704) * 2 - 3040) / 10; + bModeSynth = 1; + } else { + ath_err(common, "Invalid channel %u MHz\n", freq); + return -EINVAL; + } + + channelSel = (channelSel << 2) & 0xff; + channelSel = ath9k_hw_reverse_bits(channelSel, 8); + + txctl = REG_READ(ah, AR_PHY_CCK_TX_CTRL); + if (freq == 2484) { + + REG_WRITE(ah, AR_PHY_CCK_TX_CTRL, + txctl | AR_PHY_CCK_TX_CTRL_JAPAN); + } else { + REG_WRITE(ah, AR_PHY_CCK_TX_CTRL, + txctl & ~AR_PHY_CCK_TX_CTRL_JAPAN); + } + + } else if ((freq % 20) == 0 && freq >= 5120) { + channelSel = + ath9k_hw_reverse_bits(((freq - 4800) / 20 << 2), 8); + aModeRefSel = ath9k_hw_reverse_bits(1, 2); + } else if ((freq % 10) == 0) { + channelSel = + ath9k_hw_reverse_bits(((freq - 4800) / 10 << 1), 8); + if (AR_SREV_9100(ah) || AR_SREV_9160_10_OR_LATER(ah)) + aModeRefSel = ath9k_hw_reverse_bits(2, 2); + else + aModeRefSel = ath9k_hw_reverse_bits(1, 2); + } else if ((freq % 5) == 0) { + channelSel = ath9k_hw_reverse_bits((freq - 4800) / 5, 8); + aModeRefSel = ath9k_hw_reverse_bits(1, 2); + } else { + ath_err(common, "Invalid channel %u MHz\n", freq); + return -EINVAL; + } + + ar5008_hw_force_bias(ah, freq); + + reg32 = + (channelSel << 8) | (aModeRefSel << 2) | (bModeSynth << 1) | + (1 << 5) | 0x1; + + REG_WRITE(ah, AR_PHY(0x37), reg32); + + ah->curchan = chan; + + return 0; +} + +/** + * ar5008_hw_spur_mitigate - convert baseband spur frequency for external radios + * @ah: atheros hardware structure + * @chan: + * + * For non single-chip solutions. Converts to baseband spur frequency given the + * input channel frequency and compute register settings below. + */ +static void ar5008_hw_spur_mitigate(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + int bb_spur = AR_NO_SPUR; + int bin, cur_bin; + int spur_freq_sd; + int spur_delta_phase; + int denominator; + int upper, lower, cur_vit_mask; + int tmp, new; + int i; + static int pilot_mask_reg[4] = { + AR_PHY_TIMING7, AR_PHY_TIMING8, + AR_PHY_PILOT_MASK_01_30, AR_PHY_PILOT_MASK_31_60 + }; + static int chan_mask_reg[4] = { + AR_PHY_TIMING9, AR_PHY_TIMING10, + AR_PHY_CHANNEL_MASK_01_30, AR_PHY_CHANNEL_MASK_31_60 + }; + static int inc[4] = { 0, 100, 0, 0 }; + + int8_t mask_m[123]; + int8_t mask_p[123]; + int8_t mask_amt; + int tmp_mask; + int cur_bb_spur; + bool is2GHz = IS_CHAN_2GHZ(chan); + + memset(&mask_m, 0, sizeof(int8_t) * 123); + memset(&mask_p, 0, sizeof(int8_t) * 123); + + for (i = 0; i < AR_EEPROM_MODAL_SPURS; i++) { + cur_bb_spur = ah->eep_ops->get_spur_channel(ah, i, is2GHz); + if (AR_NO_SPUR == cur_bb_spur) + break; + cur_bb_spur = cur_bb_spur - (chan->channel * 10); + if ((cur_bb_spur > -95) && (cur_bb_spur < 95)) { + bb_spur = cur_bb_spur; + break; + } + } + + if (AR_NO_SPUR == bb_spur) + return; + + bin = bb_spur * 32; + + tmp = REG_READ(ah, AR_PHY_TIMING_CTRL4(0)); + new = tmp | (AR_PHY_TIMING_CTRL4_ENABLE_SPUR_RSSI | + AR_PHY_TIMING_CTRL4_ENABLE_SPUR_FILTER | + AR_PHY_TIMING_CTRL4_ENABLE_CHAN_MASK | + AR_PHY_TIMING_CTRL4_ENABLE_PILOT_MASK); + + REG_WRITE(ah, AR_PHY_TIMING_CTRL4(0), new); + + new = (AR_PHY_SPUR_REG_MASK_RATE_CNTL | + AR_PHY_SPUR_REG_ENABLE_MASK_PPM | + AR_PHY_SPUR_REG_MASK_RATE_SELECT | + AR_PHY_SPUR_REG_ENABLE_VIT_SPUR_RSSI | + SM(SPUR_RSSI_THRESH, AR_PHY_SPUR_REG_SPUR_RSSI_THRESH)); + REG_WRITE(ah, AR_PHY_SPUR_REG, new); + + spur_delta_phase = ((bb_spur * 524288) / 100) & + AR_PHY_TIMING11_SPUR_DELTA_PHASE; + + denominator = IS_CHAN_2GHZ(chan) ? 440 : 400; + spur_freq_sd = ((bb_spur * 2048) / denominator) & 0x3ff; + + new = (AR_PHY_TIMING11_USE_SPUR_IN_AGC | + SM(spur_freq_sd, AR_PHY_TIMING11_SPUR_FREQ_SD) | + SM(spur_delta_phase, AR_PHY_TIMING11_SPUR_DELTA_PHASE)); + REG_WRITE(ah, AR_PHY_TIMING11, new); + + cur_bin = -6000; + upper = bin + 100; + lower = bin - 100; + + for (i = 0; i < 4; i++) { + int pilot_mask = 0; + int chan_mask = 0; + int bp = 0; + for (bp = 0; bp < 30; bp++) { + if ((cur_bin > lower) && (cur_bin < upper)) { + pilot_mask = pilot_mask | 0x1 << bp; + chan_mask = chan_mask | 0x1 << bp; + } + cur_bin += 100; + } + cur_bin += inc[i]; + REG_WRITE(ah, pilot_mask_reg[i], pilot_mask); + REG_WRITE(ah, chan_mask_reg[i], chan_mask); + } + + cur_vit_mask = 6100; + upper = bin + 120; + lower = bin - 120; + + for (i = 0; i < 123; i++) { + if ((cur_vit_mask > lower) && (cur_vit_mask < upper)) { + + /* workaround for gcc bug #37014 */ + volatile int tmp_v = abs(cur_vit_mask - bin); + + if (tmp_v < 75) + mask_amt = 1; + else + mask_amt = 0; + if (cur_vit_mask < 0) + mask_m[abs(cur_vit_mask / 100)] = mask_amt; + else + mask_p[cur_vit_mask / 100] = mask_amt; + } + cur_vit_mask -= 100; + } + + tmp_mask = (mask_m[46] << 30) | (mask_m[47] << 28) + | (mask_m[48] << 26) | (mask_m[49] << 24) + | (mask_m[50] << 22) | (mask_m[51] << 20) + | (mask_m[52] << 18) | (mask_m[53] << 16) + | (mask_m[54] << 14) | (mask_m[55] << 12) + | (mask_m[56] << 10) | (mask_m[57] << 8) + | (mask_m[58] << 6) | (mask_m[59] << 4) + | (mask_m[60] << 2) | (mask_m[61] << 0); + REG_WRITE(ah, AR_PHY_BIN_MASK_1, tmp_mask); + REG_WRITE(ah, AR_PHY_VIT_MASK2_M_46_61, tmp_mask); + + tmp_mask = (mask_m[31] << 28) + | (mask_m[32] << 26) | (mask_m[33] << 24) + | (mask_m[34] << 22) | (mask_m[35] << 20) + | (mask_m[36] << 18) | (mask_m[37] << 16) + | (mask_m[48] << 14) | (mask_m[39] << 12) + | (mask_m[40] << 10) | (mask_m[41] << 8) + | (mask_m[42] << 6) | (mask_m[43] << 4) + | (mask_m[44] << 2) | (mask_m[45] << 0); + REG_WRITE(ah, AR_PHY_BIN_MASK_2, tmp_mask); + REG_WRITE(ah, AR_PHY_MASK2_M_31_45, tmp_mask); + + tmp_mask = (mask_m[16] << 30) | (mask_m[16] << 28) + | (mask_m[18] << 26) | (mask_m[18] << 24) + | (mask_m[20] << 22) | (mask_m[20] << 20) + | (mask_m[22] << 18) | (mask_m[22] << 16) + | (mask_m[24] << 14) | (mask_m[24] << 12) + | (mask_m[25] << 10) | (mask_m[26] << 8) + | (mask_m[27] << 6) | (mask_m[28] << 4) + | (mask_m[29] << 2) | (mask_m[30] << 0); + REG_WRITE(ah, AR_PHY_BIN_MASK_3, tmp_mask); + REG_WRITE(ah, AR_PHY_MASK2_M_16_30, tmp_mask); + + tmp_mask = (mask_m[0] << 30) | (mask_m[1] << 28) + | (mask_m[2] << 26) | (mask_m[3] << 24) + | (mask_m[4] << 22) | (mask_m[5] << 20) + | (mask_m[6] << 18) | (mask_m[7] << 16) + | (mask_m[8] << 14) | (mask_m[9] << 12) + | (mask_m[10] << 10) | (mask_m[11] << 8) + | (mask_m[12] << 6) | (mask_m[13] << 4) + | (mask_m[14] << 2) | (mask_m[15] << 0); + REG_WRITE(ah, AR_PHY_MASK_CTL, tmp_mask); + REG_WRITE(ah, AR_PHY_MASK2_M_00_15, tmp_mask); + + tmp_mask = (mask_p[15] << 28) + | (mask_p[14] << 26) | (mask_p[13] << 24) + | (mask_p[12] << 22) | (mask_p[11] << 20) + | (mask_p[10] << 18) | (mask_p[9] << 16) + | (mask_p[8] << 14) | (mask_p[7] << 12) + | (mask_p[6] << 10) | (mask_p[5] << 8) + | (mask_p[4] << 6) | (mask_p[3] << 4) + | (mask_p[2] << 2) | (mask_p[1] << 0); + REG_WRITE(ah, AR_PHY_BIN_MASK2_1, tmp_mask); + REG_WRITE(ah, AR_PHY_MASK2_P_15_01, tmp_mask); + + tmp_mask = (mask_p[30] << 28) + | (mask_p[29] << 26) | (mask_p[28] << 24) + | (mask_p[27] << 22) | (mask_p[26] << 20) + | (mask_p[25] << 18) | (mask_p[24] << 16) + | (mask_p[23] << 14) | (mask_p[22] << 12) + | (mask_p[21] << 10) | (mask_p[20] << 8) + | (mask_p[19] << 6) | (mask_p[18] << 4) + | (mask_p[17] << 2) | (mask_p[16] << 0); + REG_WRITE(ah, AR_PHY_BIN_MASK2_2, tmp_mask); + REG_WRITE(ah, AR_PHY_MASK2_P_30_16, tmp_mask); + + tmp_mask = (mask_p[45] << 28) + | (mask_p[44] << 26) | (mask_p[43] << 24) + | (mask_p[42] << 22) | (mask_p[41] << 20) + | (mask_p[40] << 18) | (mask_p[39] << 16) + | (mask_p[38] << 14) | (mask_p[37] << 12) + | (mask_p[36] << 10) | (mask_p[35] << 8) + | (mask_p[34] << 6) | (mask_p[33] << 4) + | (mask_p[32] << 2) | (mask_p[31] << 0); + REG_WRITE(ah, AR_PHY_BIN_MASK2_3, tmp_mask); + REG_WRITE(ah, AR_PHY_MASK2_P_45_31, tmp_mask); + + tmp_mask = (mask_p[61] << 30) | (mask_p[60] << 28) + | (mask_p[59] << 26) | (mask_p[58] << 24) + | (mask_p[57] << 22) | (mask_p[56] << 20) + | (mask_p[55] << 18) | (mask_p[54] << 16) + | (mask_p[53] << 14) | (mask_p[52] << 12) + | (mask_p[51] << 10) | (mask_p[50] << 8) + | (mask_p[49] << 6) | (mask_p[48] << 4) + | (mask_p[47] << 2) | (mask_p[46] << 0); + REG_WRITE(ah, AR_PHY_BIN_MASK2_4, tmp_mask); + REG_WRITE(ah, AR_PHY_MASK2_P_61_45, tmp_mask); +} + +/** + * ar5008_hw_rf_alloc_ext_banks - allocates banks for external radio programming + * @ah: atheros hardware structure + * + * Only required for older devices with external AR2133/AR5133 radios. + */ +static int ar5008_hw_rf_alloc_ext_banks(struct ath_hw *ah) +{ + int size = ah->iniBank6.ia_rows * sizeof(u32); + + if (AR_SREV_9280_20_OR_LATER(ah)) + return 0; + + ah->analogBank6Data = devm_kzalloc(ah->dev, size, GFP_KERNEL); + if (!ah->analogBank6Data) + return -ENOMEM; + + return 0; +} + + +/* * + * ar5008_hw_set_rf_regs - programs rf registers based on EEPROM + * @ah: atheros hardware structure + * @chan: + * @modesIndex: + * + * Used for the external AR2133/AR5133 radios. + * + * Reads the EEPROM header info from the device structure and programs + * all rf registers. This routine requires access to the analog + * rf device. This is not required for single-chip devices. + */ +static bool ar5008_hw_set_rf_regs(struct ath_hw *ah, + struct ath9k_channel *chan, + u16 modesIndex) +{ + u32 eepMinorRev; + u32 ob5GHz = 0, db5GHz = 0; + u32 ob2GHz = 0, db2GHz = 0; + int regWrites = 0; + int i; + + /* + * Software does not need to program bank data + * for single chip devices, that is AR9280 or anything + * after that. + */ + if (AR_SREV_9280_20_OR_LATER(ah)) + return true; + + /* Setup rf parameters */ + eepMinorRev = ah->eep_ops->get_eeprom(ah, EEP_MINOR_REV); + + for (i = 0; i < ah->iniBank6.ia_rows; i++) + ah->analogBank6Data[i] = INI_RA(&ah->iniBank6, i, modesIndex); + + /* Only the 5 or 2 GHz OB/DB need to be set for a mode */ + if (eepMinorRev >= 2) { + if (IS_CHAN_2GHZ(chan)) { + ob2GHz = ah->eep_ops->get_eeprom(ah, EEP_OB_2); + db2GHz = ah->eep_ops->get_eeprom(ah, EEP_DB_2); + ar5008_hw_phy_modify_rx_buffer(ah->analogBank6Data, + ob2GHz, 3, 197, 0); + ar5008_hw_phy_modify_rx_buffer(ah->analogBank6Data, + db2GHz, 3, 194, 0); + } else { + ob5GHz = ah->eep_ops->get_eeprom(ah, EEP_OB_5); + db5GHz = ah->eep_ops->get_eeprom(ah, EEP_DB_5); + ar5008_hw_phy_modify_rx_buffer(ah->analogBank6Data, + ob5GHz, 3, 203, 0); + ar5008_hw_phy_modify_rx_buffer(ah->analogBank6Data, + db5GHz, 3, 200, 0); + } + } + + /* Write Analog registers */ + REG_WRITE_ARRAY(&bank0, 1, regWrites); + REG_WRITE_ARRAY(&bank1, 1, regWrites); + REG_WRITE_ARRAY(&bank2, 1, regWrites); + REG_WRITE_ARRAY(&bank3, modesIndex, regWrites); + ar5008_write_bank6(ah, ®Writes); + REG_WRITE_ARRAY(&bank7, 1, regWrites); + + return true; +} + +static void ar5008_hw_init_bb(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + u32 synthDelay; + + synthDelay = REG_READ(ah, AR_PHY_RX_DELAY) & AR_PHY_RX_DELAY_DELAY; + + REG_WRITE(ah, AR_PHY_ACTIVE, AR_PHY_ACTIVE_EN); + + ath9k_hw_synth_delay(ah, chan, synthDelay); +} + +static void ar5008_hw_init_chain_masks(struct ath_hw *ah) +{ + int rx_chainmask, tx_chainmask; + + rx_chainmask = ah->rxchainmask; + tx_chainmask = ah->txchainmask; + + + switch (rx_chainmask) { + case 0x5: + REG_SET_BIT(ah, AR_PHY_ANALOG_SWAP, + AR_PHY_SWAP_ALT_CHAIN); + case 0x3: + if (ah->hw_version.macVersion == AR_SREV_REVISION_5416_10) { + REG_WRITE(ah, AR_PHY_RX_CHAINMASK, 0x7); + REG_WRITE(ah, AR_PHY_CAL_CHAINMASK, 0x7); + break; + } + case 0x1: + case 0x2: + case 0x7: + ENABLE_REGWRITE_BUFFER(ah); + REG_WRITE(ah, AR_PHY_RX_CHAINMASK, rx_chainmask); + REG_WRITE(ah, AR_PHY_CAL_CHAINMASK, rx_chainmask); + break; + default: + ENABLE_REGWRITE_BUFFER(ah); + break; + } + + REG_WRITE(ah, AR_SELFGEN_MASK, tx_chainmask); + + REGWRITE_BUFFER_FLUSH(ah); + + if (tx_chainmask == 0x5) { + REG_SET_BIT(ah, AR_PHY_ANALOG_SWAP, + AR_PHY_SWAP_ALT_CHAIN); + } + if (AR_SREV_9100(ah)) + REG_WRITE(ah, AR_PHY_ANALOG_SWAP, + REG_READ(ah, AR_PHY_ANALOG_SWAP) | 0x00000001); +} + +static void ar5008_hw_override_ini(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + u32 val; + + /* + * Set the RX_ABORT and RX_DIS and clear if off only after + * RXE is set for MAC. This prevents frames with corrupted + * descriptor status. + */ + REG_SET_BIT(ah, AR_DIAG_SW, (AR_DIAG_RX_DIS | AR_DIAG_RX_ABORT)); + + if (AR_SREV_9280_20_OR_LATER(ah)) { + val = REG_READ(ah, AR_PCU_MISC_MODE2); + + if (!AR_SREV_9271(ah)) + val &= ~AR_PCU_MISC_MODE2_HWWAR1; + + if (AR_SREV_9287_11_OR_LATER(ah)) + val = val & (~AR_PCU_MISC_MODE2_HWWAR2); + + REG_WRITE(ah, AR_PCU_MISC_MODE2, val); + } + + REG_SET_BIT(ah, AR_PHY_CCK_DETECT, + AR_PHY_CCK_DETECT_BB_ENABLE_ANT_FAST_DIV); + + if (AR_SREV_9280_20_OR_LATER(ah)) + return; + /* + * Disable BB clock gating + * Necessary to avoid issues on AR5416 2.0 + */ + REG_WRITE(ah, 0x9800 + (651 << 2), 0x11); + + /* + * Disable RIFS search on some chips to avoid baseband + * hang issues. + */ + if (AR_SREV_9100(ah) || AR_SREV_9160(ah)) { + val = REG_READ(ah, AR_PHY_HEAVY_CLIP_FACTOR_RIFS); + val &= ~AR_PHY_RIFS_INIT_DELAY; + REG_WRITE(ah, AR_PHY_HEAVY_CLIP_FACTOR_RIFS, val); + } +} + +static void ar5008_hw_set_channel_regs(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + u32 phymode; + u32 enableDacFifo = 0; + + if (AR_SREV_9285_12_OR_LATER(ah)) + enableDacFifo = (REG_READ(ah, AR_PHY_TURBO) & + AR_PHY_FC_ENABLE_DAC_FIFO); + + phymode = AR_PHY_FC_HT_EN | AR_PHY_FC_SHORT_GI_40 + | AR_PHY_FC_SINGLE_HT_LTF1 | AR_PHY_FC_WALSH | enableDacFifo; + + if (IS_CHAN_HT40(chan)) { + phymode |= AR_PHY_FC_DYN2040_EN; + + if ((chan->chanmode == CHANNEL_A_HT40PLUS) || + (chan->chanmode == CHANNEL_G_HT40PLUS)) + phymode |= AR_PHY_FC_DYN2040_PRI_CH; + + } + REG_WRITE(ah, AR_PHY_TURBO, phymode); + + ath9k_hw_set11nmac2040(ah); + + ENABLE_REGWRITE_BUFFER(ah); + + REG_WRITE(ah, AR_GTXTO, 25 << AR_GTXTO_TIMEOUT_LIMIT_S); + REG_WRITE(ah, AR_CST, 0xF << AR_CST_TIMEOUT_LIMIT_S); + + REGWRITE_BUFFER_FLUSH(ah); +} + + +static int ar5008_hw_process_ini(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + struct ath_common *common = ath9k_hw_common(ah); + int i, regWrites = 0; + u32 modesIndex, freqIndex; + + switch (chan->chanmode) { + case CHANNEL_A: + case CHANNEL_A_HT20: + modesIndex = 1; + freqIndex = 1; + break; + case CHANNEL_A_HT40PLUS: + case CHANNEL_A_HT40MINUS: + modesIndex = 2; + freqIndex = 1; + break; + case CHANNEL_G: + case CHANNEL_G_HT20: + case CHANNEL_B: + modesIndex = 4; + freqIndex = 2; + break; + case CHANNEL_G_HT40PLUS: + case CHANNEL_G_HT40MINUS: + modesIndex = 3; + freqIndex = 2; + break; + + default: + return -EINVAL; + } + + /* + * Set correct baseband to analog shift setting to + * access analog chips. + */ + REG_WRITE(ah, AR_PHY(0), 0x00000007); + + /* Write ADDAC shifts */ + REG_WRITE(ah, AR_PHY_ADC_SERIAL_CTL, AR_PHY_SEL_EXTERNAL_RADIO); + if (ah->eep_ops->set_addac) + ah->eep_ops->set_addac(ah, chan); + + REG_WRITE_ARRAY(&ah->iniAddac, 1, regWrites); + REG_WRITE(ah, AR_PHY_ADC_SERIAL_CTL, AR_PHY_SEL_INTERNAL_ADDAC); + + ENABLE_REGWRITE_BUFFER(ah); + + for (i = 0; i < ah->iniModes.ia_rows; i++) { + u32 reg = INI_RA(&ah->iniModes, i, 0); + u32 val = INI_RA(&ah->iniModes, i, modesIndex); + + if (reg == AR_AN_TOP2 && ah->need_an_top2_fixup) + val &= ~AR_AN_TOP2_PWDCLKIND; + + REG_WRITE(ah, reg, val); + + if (reg >= 0x7800 && reg < 0x78a0 + && ah->config.analog_shiftreg + && (common->bus_ops->ath_bus_type != ATH_USB)) { + udelay(100); + } + + DO_DELAY(regWrites); + } + + REGWRITE_BUFFER_FLUSH(ah); + + if (AR_SREV_9280(ah) || AR_SREV_9287_11_OR_LATER(ah)) + REG_WRITE_ARRAY(&ah->iniModesRxGain, modesIndex, regWrites); + + if (AR_SREV_9280(ah) || AR_SREV_9285_12_OR_LATER(ah) || + AR_SREV_9287_11_OR_LATER(ah)) + REG_WRITE_ARRAY(&ah->iniModesTxGain, modesIndex, regWrites); + + if (AR_SREV_9271_10(ah)) { + REG_SET_BIT(ah, AR_PHY_SPECTRAL_SCAN, AR_PHY_SPECTRAL_SCAN_ENA); + REG_RMW_FIELD(ah, AR_PHY_RF_CTL3, AR_PHY_TX_END_TO_ADC_ON, 0xa); + } + + ENABLE_REGWRITE_BUFFER(ah); + + /* Write common array parameters */ + for (i = 0; i < ah->iniCommon.ia_rows; i++) { + u32 reg = INI_RA(&ah->iniCommon, i, 0); + u32 val = INI_RA(&ah->iniCommon, i, 1); + + REG_WRITE(ah, reg, val); + + if (reg >= 0x7800 && reg < 0x78a0 + && ah->config.analog_shiftreg + && (common->bus_ops->ath_bus_type != ATH_USB)) { + udelay(100); + } + + DO_DELAY(regWrites); + } + + REGWRITE_BUFFER_FLUSH(ah); + + REG_WRITE_ARRAY(&ah->iniBB_RfGain, freqIndex, regWrites); + + if (IS_CHAN_A_FAST_CLOCK(ah, chan)) + REG_WRITE_ARRAY(&ah->iniModesFastClock, modesIndex, + regWrites); + + ar5008_hw_override_ini(ah, chan); + ar5008_hw_set_channel_regs(ah, chan); + ar5008_hw_init_chain_masks(ah); + ath9k_olc_init(ah); + ath9k_hw_apply_txpower(ah, chan, false); + + /* Write analog registers */ + if (!ath9k_hw_set_rf_regs(ah, chan, freqIndex)) { + ath_err(ath9k_hw_common(ah), "ar5416SetRfRegs failed\n"); + return -EIO; + } + + return 0; +} + +static void ar5008_hw_set_rfmode(struct ath_hw *ah, struct ath9k_channel *chan) +{ + u32 rfMode = 0; + + if (chan == NULL) + return; + + rfMode |= (IS_CHAN_B(chan) || IS_CHAN_G(chan)) + ? AR_PHY_MODE_DYNAMIC : AR_PHY_MODE_OFDM; + + if (!AR_SREV_9280_20_OR_LATER(ah)) + rfMode |= (IS_CHAN_5GHZ(chan)) ? + AR_PHY_MODE_RF5GHZ : AR_PHY_MODE_RF2GHZ; + + if (IS_CHAN_A_FAST_CLOCK(ah, chan)) + rfMode |= (AR_PHY_MODE_DYNAMIC | AR_PHY_MODE_DYN_CCK_DISABLE); + + REG_WRITE(ah, AR_PHY_MODE, rfMode); +} + +static void ar5008_hw_mark_phy_inactive(struct ath_hw *ah) +{ + REG_WRITE(ah, AR_PHY_ACTIVE, AR_PHY_ACTIVE_DIS); +} + +static void ar5008_hw_set_delta_slope(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + u32 coef_scaled, ds_coef_exp, ds_coef_man; + u32 clockMhzScaled = 0x64000000; + struct chan_centers centers; + + if (IS_CHAN_HALF_RATE(chan)) + clockMhzScaled = clockMhzScaled >> 1; + else if (IS_CHAN_QUARTER_RATE(chan)) + clockMhzScaled = clockMhzScaled >> 2; + + ath9k_hw_get_channel_centers(ah, chan, ¢ers); + coef_scaled = clockMhzScaled / centers.synth_center; + + ath9k_hw_get_delta_slope_vals(ah, coef_scaled, &ds_coef_man, + &ds_coef_exp); + + REG_RMW_FIELD(ah, AR_PHY_TIMING3, + AR_PHY_TIMING3_DSC_MAN, ds_coef_man); + REG_RMW_FIELD(ah, AR_PHY_TIMING3, + AR_PHY_TIMING3_DSC_EXP, ds_coef_exp); + + coef_scaled = (9 * coef_scaled) / 10; + + ath9k_hw_get_delta_slope_vals(ah, coef_scaled, &ds_coef_man, + &ds_coef_exp); + + REG_RMW_FIELD(ah, AR_PHY_HALFGI, + AR_PHY_HALFGI_DSC_MAN, ds_coef_man); + REG_RMW_FIELD(ah, AR_PHY_HALFGI, + AR_PHY_HALFGI_DSC_EXP, ds_coef_exp); +} + +static bool ar5008_hw_rfbus_req(struct ath_hw *ah) +{ + REG_WRITE(ah, AR_PHY_RFBUS_REQ, AR_PHY_RFBUS_REQ_EN); + return ath9k_hw_wait(ah, AR_PHY_RFBUS_GRANT, AR_PHY_RFBUS_GRANT_EN, + AR_PHY_RFBUS_GRANT_EN, AH_WAIT_TIMEOUT); +} + +static void ar5008_hw_rfbus_done(struct ath_hw *ah) +{ + u32 synthDelay = REG_READ(ah, AR_PHY_RX_DELAY) & AR_PHY_RX_DELAY_DELAY; + + ath9k_hw_synth_delay(ah, ah->curchan, synthDelay); + + REG_WRITE(ah, AR_PHY_RFBUS_REQ, 0); +} + +static void ar5008_restore_chainmask(struct ath_hw *ah) +{ + int rx_chainmask = ah->rxchainmask; + + if ((rx_chainmask == 0x5) || (rx_chainmask == 0x3)) { + REG_WRITE(ah, AR_PHY_RX_CHAINMASK, rx_chainmask); + REG_WRITE(ah, AR_PHY_CAL_CHAINMASK, rx_chainmask); + } +} + +static u32 ar9160_hw_compute_pll_control(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + u32 pll; + + pll = SM(0x5, AR_RTC_9160_PLL_REFDIV); + + if (chan && IS_CHAN_HALF_RATE(chan)) + pll |= SM(0x1, AR_RTC_9160_PLL_CLKSEL); + else if (chan && IS_CHAN_QUARTER_RATE(chan)) + pll |= SM(0x2, AR_RTC_9160_PLL_CLKSEL); + + if (chan && IS_CHAN_5GHZ(chan)) + pll |= SM(0x50, AR_RTC_9160_PLL_DIV); + else + pll |= SM(0x58, AR_RTC_9160_PLL_DIV); + + return pll; +} + +static u32 ar5008_hw_compute_pll_control(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + u32 pll; + + pll = AR_RTC_PLL_REFDIV_5 | AR_RTC_PLL_DIV2; + + if (chan && IS_CHAN_HALF_RATE(chan)) + pll |= SM(0x1, AR_RTC_PLL_CLKSEL); + else if (chan && IS_CHAN_QUARTER_RATE(chan)) + pll |= SM(0x2, AR_RTC_PLL_CLKSEL); + + if (chan && IS_CHAN_5GHZ(chan)) + pll |= SM(0xa, AR_RTC_PLL_DIV); + else + pll |= SM(0xb, AR_RTC_PLL_DIV); + + return pll; +} + +static bool ar5008_hw_ani_control_new(struct ath_hw *ah, + enum ath9k_ani_cmd cmd, + int param) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_channel *chan = ah->curchan; + struct ar5416AniState *aniState = &chan->ani; + s32 value, value2; + + switch (cmd & ah->ani_function) { + case ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION:{ + /* + * on == 1 means ofdm weak signal detection is ON + * on == 1 is the default, for less noise immunity + * + * on == 0 means ofdm weak signal detection is OFF + * on == 0 means more noise imm + */ + u32 on = param ? 1 : 0; + /* + * make register setting for default + * (weak sig detect ON) come from INI file + */ + int m1ThreshLow = on ? + aniState->iniDef.m1ThreshLow : m1ThreshLow_off; + int m2ThreshLow = on ? + aniState->iniDef.m2ThreshLow : m2ThreshLow_off; + int m1Thresh = on ? + aniState->iniDef.m1Thresh : m1Thresh_off; + int m2Thresh = on ? + aniState->iniDef.m2Thresh : m2Thresh_off; + int m2CountThr = on ? + aniState->iniDef.m2CountThr : m2CountThr_off; + int m2CountThrLow = on ? + aniState->iniDef.m2CountThrLow : m2CountThrLow_off; + int m1ThreshLowExt = on ? + aniState->iniDef.m1ThreshLowExt : m1ThreshLowExt_off; + int m2ThreshLowExt = on ? + aniState->iniDef.m2ThreshLowExt : m2ThreshLowExt_off; + int m1ThreshExt = on ? + aniState->iniDef.m1ThreshExt : m1ThreshExt_off; + int m2ThreshExt = on ? + aniState->iniDef.m2ThreshExt : m2ThreshExt_off; + + REG_RMW_FIELD(ah, AR_PHY_SFCORR_LOW, + AR_PHY_SFCORR_LOW_M1_THRESH_LOW, + m1ThreshLow); + REG_RMW_FIELD(ah, AR_PHY_SFCORR_LOW, + AR_PHY_SFCORR_LOW_M2_THRESH_LOW, + m2ThreshLow); + REG_RMW_FIELD(ah, AR_PHY_SFCORR, + AR_PHY_SFCORR_M1_THRESH, m1Thresh); + REG_RMW_FIELD(ah, AR_PHY_SFCORR, + AR_PHY_SFCORR_M2_THRESH, m2Thresh); + REG_RMW_FIELD(ah, AR_PHY_SFCORR, + AR_PHY_SFCORR_M2COUNT_THR, m2CountThr); + REG_RMW_FIELD(ah, AR_PHY_SFCORR_LOW, + AR_PHY_SFCORR_LOW_M2COUNT_THR_LOW, + m2CountThrLow); + + REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT, + AR_PHY_SFCORR_EXT_M1_THRESH_LOW, m1ThreshLowExt); + REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT, + AR_PHY_SFCORR_EXT_M2_THRESH_LOW, m2ThreshLowExt); + REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT, + AR_PHY_SFCORR_EXT_M1_THRESH, m1ThreshExt); + REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT, + AR_PHY_SFCORR_EXT_M2_THRESH, m2ThreshExt); + + if (on) + REG_SET_BIT(ah, AR_PHY_SFCORR_LOW, + AR_PHY_SFCORR_LOW_USE_SELF_CORR_LOW); + else + REG_CLR_BIT(ah, AR_PHY_SFCORR_LOW, + AR_PHY_SFCORR_LOW_USE_SELF_CORR_LOW); + + if (on != aniState->ofdmWeakSigDetect) { + ath_dbg(common, ANI, + "** ch %d: ofdm weak signal: %s=>%s\n", + chan->channel, + aniState->ofdmWeakSigDetect ? + "on" : "off", + on ? "on" : "off"); + if (on) + ah->stats.ast_ani_ofdmon++; + else + ah->stats.ast_ani_ofdmoff++; + aniState->ofdmWeakSigDetect = on; + } + break; + } + case ATH9K_ANI_FIRSTEP_LEVEL:{ + u32 level = param; + + if (level >= ARRAY_SIZE(firstep_table)) { + ath_dbg(common, ANI, + "ATH9K_ANI_FIRSTEP_LEVEL: level out of range (%u > %zu)\n", + level, ARRAY_SIZE(firstep_table)); + return false; + } + + /* + * make register setting relative to default + * from INI file & cap value + */ + value = firstep_table[level] - + firstep_table[ATH9K_ANI_FIRSTEP_LVL] + + aniState->iniDef.firstep; + if (value < ATH9K_SIG_FIRSTEP_SETTING_MIN) + value = ATH9K_SIG_FIRSTEP_SETTING_MIN; + if (value > ATH9K_SIG_FIRSTEP_SETTING_MAX) + value = ATH9K_SIG_FIRSTEP_SETTING_MAX; + REG_RMW_FIELD(ah, AR_PHY_FIND_SIG, + AR_PHY_FIND_SIG_FIRSTEP, + value); + /* + * we need to set first step low register too + * make register setting relative to default + * from INI file & cap value + */ + value2 = firstep_table[level] - + firstep_table[ATH9K_ANI_FIRSTEP_LVL] + + aniState->iniDef.firstepLow; + if (value2 < ATH9K_SIG_FIRSTEP_SETTING_MIN) + value2 = ATH9K_SIG_FIRSTEP_SETTING_MIN; + if (value2 > ATH9K_SIG_FIRSTEP_SETTING_MAX) + value2 = ATH9K_SIG_FIRSTEP_SETTING_MAX; + + REG_RMW_FIELD(ah, AR_PHY_FIND_SIG_LOW, + AR_PHY_FIND_SIG_FIRSTEP_LOW, value2); + + if (level != aniState->firstepLevel) { + ath_dbg(common, ANI, + "** ch %d: level %d=>%d[def:%d] firstep[level]=%d ini=%d\n", + chan->channel, + aniState->firstepLevel, + level, + ATH9K_ANI_FIRSTEP_LVL, + value, + aniState->iniDef.firstep); + ath_dbg(common, ANI, + "** ch %d: level %d=>%d[def:%d] firstep_low[level]=%d ini=%d\n", + chan->channel, + aniState->firstepLevel, + level, + ATH9K_ANI_FIRSTEP_LVL, + value2, + aniState->iniDef.firstepLow); + if (level > aniState->firstepLevel) + ah->stats.ast_ani_stepup++; + else if (level < aniState->firstepLevel) + ah->stats.ast_ani_stepdown++; + aniState->firstepLevel = level; + } + break; + } + case ATH9K_ANI_SPUR_IMMUNITY_LEVEL:{ + u32 level = param; + + if (level >= ARRAY_SIZE(cycpwrThr1_table)) { + ath_dbg(common, ANI, + "ATH9K_ANI_SPUR_IMMUNITY_LEVEL: level out of range (%u > %zu)\n", + level, ARRAY_SIZE(cycpwrThr1_table)); + return false; + } + /* + * make register setting relative to default + * from INI file & cap value + */ + value = cycpwrThr1_table[level] - + cycpwrThr1_table[ATH9K_ANI_SPUR_IMMUNE_LVL] + + aniState->iniDef.cycpwrThr1; + if (value < ATH9K_SIG_SPUR_IMM_SETTING_MIN) + value = ATH9K_SIG_SPUR_IMM_SETTING_MIN; + if (value > ATH9K_SIG_SPUR_IMM_SETTING_MAX) + value = ATH9K_SIG_SPUR_IMM_SETTING_MAX; + REG_RMW_FIELD(ah, AR_PHY_TIMING5, + AR_PHY_TIMING5_CYCPWR_THR1, + value); + + /* + * set AR_PHY_EXT_CCA for extension channel + * make register setting relative to default + * from INI file & cap value + */ + value2 = cycpwrThr1_table[level] - + cycpwrThr1_table[ATH9K_ANI_SPUR_IMMUNE_LVL] + + aniState->iniDef.cycpwrThr1Ext; + if (value2 < ATH9K_SIG_SPUR_IMM_SETTING_MIN) + value2 = ATH9K_SIG_SPUR_IMM_SETTING_MIN; + if (value2 > ATH9K_SIG_SPUR_IMM_SETTING_MAX) + value2 = ATH9K_SIG_SPUR_IMM_SETTING_MAX; + REG_RMW_FIELD(ah, AR_PHY_EXT_CCA, + AR_PHY_EXT_TIMING5_CYCPWR_THR1, value2); + + if (level != aniState->spurImmunityLevel) { + ath_dbg(common, ANI, + "** ch %d: level %d=>%d[def:%d] cycpwrThr1[level]=%d ini=%d\n", + chan->channel, + aniState->spurImmunityLevel, + level, + ATH9K_ANI_SPUR_IMMUNE_LVL, + value, + aniState->iniDef.cycpwrThr1); + ath_dbg(common, ANI, + "** ch %d: level %d=>%d[def:%d] cycpwrThr1Ext[level]=%d ini=%d\n", + chan->channel, + aniState->spurImmunityLevel, + level, + ATH9K_ANI_SPUR_IMMUNE_LVL, + value2, + aniState->iniDef.cycpwrThr1Ext); + if (level > aniState->spurImmunityLevel) + ah->stats.ast_ani_spurup++; + else if (level < aniState->spurImmunityLevel) + ah->stats.ast_ani_spurdown++; + aniState->spurImmunityLevel = level; + } + break; + } + case ATH9K_ANI_MRC_CCK: + /* + * You should not see this as AR5008, AR9001, AR9002 + * does not have hardware support for MRC CCK. + */ + WARN_ON(1); + break; + case ATH9K_ANI_PRESENT: + break; + default: + ath_dbg(common, ANI, "invalid cmd %u\n", cmd); + return false; + } + + ath_dbg(common, ANI, + "ANI parameters: SI=%d, ofdmWS=%s FS=%d MRCcck=%s listenTime=%d ofdmErrs=%d cckErrs=%d\n", + aniState->spurImmunityLevel, + aniState->ofdmWeakSigDetect ? "on" : "off", + aniState->firstepLevel, + aniState->mrcCCK ? "on" : "off", + aniState->listenTime, + aniState->ofdmPhyErrCount, + aniState->cckPhyErrCount); + return true; +} + +static void ar5008_hw_do_getnf(struct ath_hw *ah, + int16_t nfarray[NUM_NF_READINGS]) +{ + int16_t nf; + + nf = MS(REG_READ(ah, AR_PHY_CCA), AR_PHY_MINCCA_PWR); + nfarray[0] = sign_extend32(nf, 8); + + nf = MS(REG_READ(ah, AR_PHY_CH1_CCA), AR_PHY_CH1_MINCCA_PWR); + nfarray[1] = sign_extend32(nf, 8); + + nf = MS(REG_READ(ah, AR_PHY_CH2_CCA), AR_PHY_CH2_MINCCA_PWR); + nfarray[2] = sign_extend32(nf, 8); + + if (!IS_CHAN_HT40(ah->curchan)) + return; + + nf = MS(REG_READ(ah, AR_PHY_EXT_CCA), AR_PHY_EXT_MINCCA_PWR); + nfarray[3] = sign_extend32(nf, 8); + + nf = MS(REG_READ(ah, AR_PHY_CH1_EXT_CCA), AR_PHY_CH1_EXT_MINCCA_PWR); + nfarray[4] = sign_extend32(nf, 8); + + nf = MS(REG_READ(ah, AR_PHY_CH2_EXT_CCA), AR_PHY_CH2_EXT_MINCCA_PWR); + nfarray[5] = sign_extend32(nf, 8); +} + +/* + * Initialize the ANI register values with default (ini) values. + * This routine is called during a (full) hardware reset after + * all the registers are initialised from the INI. + */ +static void ar5008_hw_ani_cache_ini_regs(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_channel *chan = ah->curchan; + struct ar5416AniState *aniState = &chan->ani; + struct ath9k_ani_default *iniDef; + u32 val; + + iniDef = &aniState->iniDef; + + ath_dbg(common, ANI, "ver %d.%d opmode %u chan %d Mhz/0x%x\n", + ah->hw_version.macVersion, + ah->hw_version.macRev, + ah->opmode, + chan->channel, + chan->channelFlags); + + val = REG_READ(ah, AR_PHY_SFCORR); + iniDef->m1Thresh = MS(val, AR_PHY_SFCORR_M1_THRESH); + iniDef->m2Thresh = MS(val, AR_PHY_SFCORR_M2_THRESH); + iniDef->m2CountThr = MS(val, AR_PHY_SFCORR_M2COUNT_THR); + + val = REG_READ(ah, AR_PHY_SFCORR_LOW); + iniDef->m1ThreshLow = MS(val, AR_PHY_SFCORR_LOW_M1_THRESH_LOW); + iniDef->m2ThreshLow = MS(val, AR_PHY_SFCORR_LOW_M2_THRESH_LOW); + iniDef->m2CountThrLow = MS(val, AR_PHY_SFCORR_LOW_M2COUNT_THR_LOW); + + val = REG_READ(ah, AR_PHY_SFCORR_EXT); + iniDef->m1ThreshExt = MS(val, AR_PHY_SFCORR_EXT_M1_THRESH); + iniDef->m2ThreshExt = MS(val, AR_PHY_SFCORR_EXT_M2_THRESH); + iniDef->m1ThreshLowExt = MS(val, AR_PHY_SFCORR_EXT_M1_THRESH_LOW); + iniDef->m2ThreshLowExt = MS(val, AR_PHY_SFCORR_EXT_M2_THRESH_LOW); + iniDef->firstep = REG_READ_FIELD(ah, + AR_PHY_FIND_SIG, + AR_PHY_FIND_SIG_FIRSTEP); + iniDef->firstepLow = REG_READ_FIELD(ah, + AR_PHY_FIND_SIG_LOW, + AR_PHY_FIND_SIG_FIRSTEP_LOW); + iniDef->cycpwrThr1 = REG_READ_FIELD(ah, + AR_PHY_TIMING5, + AR_PHY_TIMING5_CYCPWR_THR1); + iniDef->cycpwrThr1Ext = REG_READ_FIELD(ah, + AR_PHY_EXT_CCA, + AR_PHY_EXT_TIMING5_CYCPWR_THR1); + + /* these levels just got reset to defaults by the INI */ + aniState->spurImmunityLevel = ATH9K_ANI_SPUR_IMMUNE_LVL; + aniState->firstepLevel = ATH9K_ANI_FIRSTEP_LVL; + aniState->ofdmWeakSigDetect = ATH9K_ANI_USE_OFDM_WEAK_SIG; + aniState->mrcCCK = false; /* not available on pre AR9003 */ +} + +static void ar5008_hw_set_nf_limits(struct ath_hw *ah) +{ + ah->nf_2g.max = AR_PHY_CCA_MAX_GOOD_VAL_5416_2GHZ; + ah->nf_2g.min = AR_PHY_CCA_MIN_GOOD_VAL_5416_2GHZ; + ah->nf_2g.nominal = AR_PHY_CCA_NOM_VAL_5416_2GHZ; + ah->nf_5g.max = AR_PHY_CCA_MAX_GOOD_VAL_5416_5GHZ; + ah->nf_5g.min = AR_PHY_CCA_MIN_GOOD_VAL_5416_5GHZ; + ah->nf_5g.nominal = AR_PHY_CCA_NOM_VAL_5416_5GHZ; +} + +static void ar5008_hw_set_radar_params(struct ath_hw *ah, + struct ath_hw_radar_conf *conf) +{ + u32 radar_0 = 0, radar_1 = 0; + + if (!conf) { + REG_CLR_BIT(ah, AR_PHY_RADAR_0, AR_PHY_RADAR_0_ENA); + return; + } + + radar_0 |= AR_PHY_RADAR_0_ENA | AR_PHY_RADAR_0_FFT_ENA; + radar_0 |= SM(conf->fir_power, AR_PHY_RADAR_0_FIRPWR); + radar_0 |= SM(conf->radar_rssi, AR_PHY_RADAR_0_RRSSI); + radar_0 |= SM(conf->pulse_height, AR_PHY_RADAR_0_HEIGHT); + radar_0 |= SM(conf->pulse_rssi, AR_PHY_RADAR_0_PRSSI); + radar_0 |= SM(conf->pulse_inband, AR_PHY_RADAR_0_INBAND); + + radar_1 |= AR_PHY_RADAR_1_MAX_RRSSI; + radar_1 |= AR_PHY_RADAR_1_BLOCK_CHECK; + radar_1 |= SM(conf->pulse_maxlen, AR_PHY_RADAR_1_MAXLEN); + radar_1 |= SM(conf->pulse_inband_step, AR_PHY_RADAR_1_RELSTEP_THRESH); + radar_1 |= SM(conf->radar_inband, AR_PHY_RADAR_1_RELPWR_THRESH); + + REG_WRITE(ah, AR_PHY_RADAR_0, radar_0); + REG_WRITE(ah, AR_PHY_RADAR_1, radar_1); + if (conf->ext_channel) + REG_SET_BIT(ah, AR_PHY_RADAR_EXT, AR_PHY_RADAR_EXT_ENA); + else + REG_CLR_BIT(ah, AR_PHY_RADAR_EXT, AR_PHY_RADAR_EXT_ENA); +} + +static void ar5008_hw_set_radar_conf(struct ath_hw *ah) +{ + struct ath_hw_radar_conf *conf = &ah->radar_conf; + + conf->fir_power = -33; + conf->radar_rssi = 20; + conf->pulse_height = 10; + conf->pulse_rssi = 24; + conf->pulse_inband = 15; + conf->pulse_maxlen = 255; + conf->pulse_inband_step = 12; + conf->radar_inband = 8; +} + +int ar5008_hw_attach_phy_ops(struct ath_hw *ah) +{ + struct ath_hw_private_ops *priv_ops = ath9k_hw_private_ops(ah); + static const u32 ar5416_cca_regs[6] = { + AR_PHY_CCA, + AR_PHY_CH1_CCA, + AR_PHY_CH2_CCA, + AR_PHY_EXT_CCA, + AR_PHY_CH1_EXT_CCA, + AR_PHY_CH2_EXT_CCA + }; + int ret; + + ret = ar5008_hw_rf_alloc_ext_banks(ah); + if (ret) + return ret; + + priv_ops->rf_set_freq = ar5008_hw_set_channel; + priv_ops->spur_mitigate_freq = ar5008_hw_spur_mitigate; + + priv_ops->set_rf_regs = ar5008_hw_set_rf_regs; + priv_ops->set_channel_regs = ar5008_hw_set_channel_regs; + priv_ops->init_bb = ar5008_hw_init_bb; + priv_ops->process_ini = ar5008_hw_process_ini; + priv_ops->set_rfmode = ar5008_hw_set_rfmode; + priv_ops->mark_phy_inactive = ar5008_hw_mark_phy_inactive; + priv_ops->set_delta_slope = ar5008_hw_set_delta_slope; + priv_ops->rfbus_req = ar5008_hw_rfbus_req; + priv_ops->rfbus_done = ar5008_hw_rfbus_done; + priv_ops->restore_chainmask = ar5008_restore_chainmask; + priv_ops->do_getnf = ar5008_hw_do_getnf; + priv_ops->set_radar_params = ar5008_hw_set_radar_params; + + priv_ops->ani_control = ar5008_hw_ani_control_new; + priv_ops->ani_cache_ini_regs = ar5008_hw_ani_cache_ini_regs; + + if (AR_SREV_9100(ah) || AR_SREV_9160_10_OR_LATER(ah)) + priv_ops->compute_pll_control = ar9160_hw_compute_pll_control; + else + priv_ops->compute_pll_control = ar5008_hw_compute_pll_control; + + ar5008_hw_set_nf_limits(ah); + ar5008_hw_set_radar_conf(ah); + memcpy(ah->nf_regs, ar5416_cca_regs, sizeof(ah->nf_regs)); + return 0; +} diff --git a/drivers/net/wireless/ath/ath9k/ar9001_initvals.h b/drivers/net/wireless/ath/ath9k/ar9001_initvals.h new file mode 100644 index 0000000..59524e1 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9001_initvals.h @@ -0,0 +1,1089 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +static const u32 ar5416Modes_9100[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00001030, 0x00000230, 0x00000460, 0x000002c0, 0x00000160}, + {0x00001070, 0x00000168, 0x000002d0, 0x00000318, 0x0000018c}, + {0x000010b0, 0x00000e60, 0x00001cc0, 0x00007c70, 0x00003e38}, + {0x000010f0, 0x0000a000, 0x00014000, 0x00016000, 0x0000b000}, + {0x00008014, 0x03e803e8, 0x07d007d0, 0x10801600, 0x08400b00}, + {0x0000801c, 0x128d93a7, 0x128d93cf, 0x12e013d7, 0x12e013ab}, + {0x00008120, 0x08f04800, 0x08f04800, 0x08f04810, 0x08f04810}, + {0x000081d0, 0x00003210, 0x00003210, 0x0000320a, 0x0000320a}, + {0x00009804, 0x00000300, 0x000003c4, 0x000003c4, 0x00000300}, + {0x00009820, 0x02020200, 0x02020200, 0x02020200, 0x02020200}, + {0x00009824, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x00009828, 0x0a020001, 0x0a020001, 0x0a020001, 0x0a020001}, + {0x00009834, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x00009838, 0x00000007, 0x00000007, 0x00000007, 0x00000007}, + {0x00009844, 0x0372161e, 0x0372161e, 0x037216a0, 0x037216a0}, + {0x00009848, 0x001a6a65, 0x001a6a65, 0x00197a68, 0x00197a68}, + {0x0000a848, 0x001a6a65, 0x001a6a65, 0x00197a68, 0x00197a68}, + {0x0000b848, 0x001a6a65, 0x001a6a65, 0x00197a68, 0x00197a68}, + {0x00009850, 0x6c48b4e2, 0x6d48b4e2, 0x6d48b0e2, 0x6c48b0e2}, + {0x00009858, 0x7ec82d2e, 0x7ec82d2e, 0x7ec82d2e, 0x7ec82d2e}, + {0x0000985c, 0x31395d5e, 0x3139605e, 0x3139605e, 0x31395d5e}, + {0x00009860, 0x00048d18, 0x00048d18, 0x00048d20, 0x00048d20}, + {0x0000c864, 0x0001ce00, 0x0001ce00, 0x0001ce00, 0x0001ce00}, + {0x00009868, 0x409a40d0, 0x409a40d0, 0x409a40d0, 0x409a40d0}, + {0x0000986c, 0x050cb081, 0x050cb081, 0x050cb081, 0x050cb081}, + {0x00009914, 0x000007d0, 0x00000fa0, 0x00001130, 0x00000898}, + {0x00009918, 0x0000000a, 0x00000014, 0x00000016, 0x0000000b}, + {0x00009924, 0xd00a8a07, 0xd00a8a07, 0xd00a8a0d, 0xd00a8a0d}, + {0x00009940, 0x00750604, 0x00754604, 0xfff81204, 0xfff81204}, + {0x00009944, 0xdfb81020, 0xdfb81020, 0xdfb81020, 0xdfb81020}, + {0x00009954, 0x5f3ca3de, 0x5f3ca3de, 0xe250a51e, 0xe250a51e}, + {0x00009958, 0x2108ecff, 0x2108ecff, 0x3388ffff, 0x3388ffff}, + {0x00009960, 0x0001bfc0, 0x0001bfc0, 0x0001bfc0, 0x0001bfc0}, + {0x0000a960, 0x0001bfc0, 0x0001bfc0, 0x0001bfc0, 0x0001bfc0}, + {0x0000b960, 0x0001bfc0, 0x0001bfc0, 0x0001bfc0, 0x0001bfc0}, + {0x00009964, 0x00001120, 0x00001120, 0x00001120, 0x00001120}, + {0x0000c9bc, 0x001a0600, 0x001a0600, 0x001a1000, 0x001a0c00}, + {0x000099c0, 0x038919be, 0x038919be, 0x038919be, 0x038919be}, + {0x000099c4, 0x06336f77, 0x06336f77, 0x06336f77, 0x06336f77}, + {0x000099c8, 0x6af65329, 0x6af65329, 0x6af65329, 0x6af65329}, + {0x000099cc, 0x08f186c8, 0x08f186c8, 0x08f186c8, 0x08f186c8}, + {0x000099d0, 0x00046384, 0x00046384, 0x00046384, 0x00046384}, + {0x000099d4, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x000099d8, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a204, 0x00000880, 0x00000880, 0x00000880, 0x00000880}, + {0x0000a208, 0xd6be4788, 0xd6be4788, 0xd03e4788, 0xd03e4788}, + {0x0000a20c, 0x002fc160, 0x002fc160, 0x002ac120, 0x002ac120}, + {0x0000b20c, 0x002fc160, 0x002fc160, 0x002ac120, 0x002ac120}, + {0x0000c20c, 0x002fc160, 0x002fc160, 0x002ac120, 0x002ac120}, + {0x0000a21c, 0x1883800a, 0x1883800a, 0x1883800a, 0x1883800a}, + {0x0000a230, 0x00000000, 0x00000000, 0x00000210, 0x00000108}, + {0x0000a274, 0x0a1a9caa, 0x0a1a9caa, 0x0a1a7caa, 0x0a1a7caa}, + {0x0000a300, 0x18010000, 0x18010000, 0x18010000, 0x18010000}, + {0x0000a304, 0x30032602, 0x30032602, 0x2e032402, 0x2e032402}, + {0x0000a308, 0x48073e06, 0x48073e06, 0x4a0a3c06, 0x4a0a3c06}, + {0x0000a30c, 0x560b4c0a, 0x560b4c0a, 0x621a540b, 0x621a540b}, + {0x0000a310, 0x641a600f, 0x641a600f, 0x764f6c1b, 0x764f6c1b}, + {0x0000a314, 0x7a4f6e1b, 0x7a4f6e1b, 0x845b7a5a, 0x845b7a5a}, + {0x0000a318, 0x8c5b7e5a, 0x8c5b7e5a, 0x950f8ccf, 0x950f8ccf}, + {0x0000a31c, 0x9d0f96cf, 0x9d0f96cf, 0xa5cf9b4f, 0xa5cf9b4f}, + {0x0000a320, 0xb51fa69f, 0xb51fa69f, 0xbddfaf1f, 0xbddfaf1f}, + {0x0000a324, 0xcb3fbd07, 0xcb3fbcbf, 0xd1ffc93f, 0xd1ffc93f}, + {0x0000a328, 0x0000d7bf, 0x0000d7bf, 0x00000000, 0x00000000}, + {0x0000a32c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a330, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a334, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, +}; + +static const u32 ar5416Common_9100[][2] = { + /* Addr allmodes */ + {0x0000000c, 0x00000000}, + {0x00000030, 0x00020015}, + {0x00000034, 0x00000005}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000008}, + {0x00000048, 0x00000008}, + {0x0000004c, 0x00000010}, + {0x00000050, 0x00000000}, + {0x00000054, 0x0000001f}, + {0x00000800, 0x00000000}, + {0x00000804, 0x00000000}, + {0x00000808, 0x00000000}, + {0x0000080c, 0x00000000}, + {0x00000810, 0x00000000}, + {0x00000814, 0x00000000}, + {0x00000818, 0x00000000}, + {0x0000081c, 0x00000000}, + {0x00000820, 0x00000000}, + {0x00000824, 0x00000000}, + {0x00001040, 0x002ffc0f}, + {0x00001044, 0x002ffc0f}, + {0x00001048, 0x002ffc0f}, + {0x0000104c, 0x002ffc0f}, + {0x00001050, 0x002ffc0f}, + {0x00001054, 0x002ffc0f}, + {0x00001058, 0x002ffc0f}, + {0x0000105c, 0x002ffc0f}, + {0x00001060, 0x002ffc0f}, + {0x00001064, 0x002ffc0f}, + {0x00001230, 0x00000000}, + {0x00001270, 0x00000000}, + {0x00001038, 0x00000000}, + {0x00001078, 0x00000000}, + {0x000010b8, 0x00000000}, + {0x000010f8, 0x00000000}, + {0x00001138, 0x00000000}, + {0x00001178, 0x00000000}, + {0x000011b8, 0x00000000}, + {0x000011f8, 0x00000000}, + {0x00001238, 0x00000000}, + {0x00001278, 0x00000000}, + {0x000012b8, 0x00000000}, + {0x000012f8, 0x00000000}, + {0x00001338, 0x00000000}, + {0x00001378, 0x00000000}, + {0x000013b8, 0x00000000}, + {0x000013f8, 0x00000000}, + {0x00001438, 0x00000000}, + {0x00001478, 0x00000000}, + {0x000014b8, 0x00000000}, + {0x000014f8, 0x00000000}, + {0x00001538, 0x00000000}, + {0x00001578, 0x00000000}, + {0x000015b8, 0x00000000}, + {0x000015f8, 0x00000000}, + {0x00001638, 0x00000000}, + {0x00001678, 0x00000000}, + {0x000016b8, 0x00000000}, + {0x000016f8, 0x00000000}, + {0x00001738, 0x00000000}, + {0x00001778, 0x00000000}, + {0x000017b8, 0x00000000}, + {0x000017f8, 0x00000000}, + {0x0000103c, 0x00000000}, + {0x0000107c, 0x00000000}, + {0x000010bc, 0x00000000}, + {0x000010fc, 0x00000000}, + {0x0000113c, 0x00000000}, + {0x0000117c, 0x00000000}, + {0x000011bc, 0x00000000}, + {0x000011fc, 0x00000000}, + {0x0000123c, 0x00000000}, + {0x0000127c, 0x00000000}, + {0x000012bc, 0x00000000}, + {0x000012fc, 0x00000000}, + {0x0000133c, 0x00000000}, + {0x0000137c, 0x00000000}, + {0x000013bc, 0x00000000}, + {0x000013fc, 0x00000000}, + {0x0000143c, 0x00000000}, + {0x0000147c, 0x00000000}, + {0x00020010, 0x00000003}, + {0x00020038, 0x000004c2}, + {0x00008004, 0x00000000}, + {0x00008008, 0x00000000}, + {0x0000800c, 0x00000000}, + {0x00008018, 0x00000700}, + {0x00008020, 0x00000000}, + {0x00008038, 0x00000000}, + {0x0000803c, 0x00000000}, + {0x00008048, 0x40000000}, + {0x00008054, 0x00004000}, + {0x00008058, 0x00000000}, + {0x0000805c, 0x000fc78f}, + {0x00008060, 0x0000000f}, + {0x00008064, 0x00000000}, + {0x000080c0, 0x2a82301a}, + {0x000080c4, 0x05dc01e0}, + {0x000080c8, 0x1f402710}, + {0x000080cc, 0x01f40000}, + {0x000080d0, 0x00001e00}, + {0x000080d4, 0x00000000}, + {0x000080d8, 0x00400000}, + {0x000080e0, 0xffffffff}, + {0x000080e4, 0x0000ffff}, + {0x000080e8, 0x003f3f3f}, + {0x000080ec, 0x00000000}, + {0x000080f0, 0x00000000}, + {0x000080f4, 0x00000000}, + {0x000080f8, 0x00000000}, + {0x000080fc, 0x00020000}, + {0x00008100, 0x00020000}, + {0x00008104, 0x00000001}, + {0x00008108, 0x00000052}, + {0x0000810c, 0x00000000}, + {0x00008110, 0x00000168}, + {0x00008118, 0x000100aa}, + {0x0000811c, 0x00003210}, + {0x00008120, 0x08f04800}, + {0x00008124, 0x00000000}, + {0x00008128, 0x00000000}, + {0x0000812c, 0x00000000}, + {0x00008130, 0x00000000}, + {0x00008134, 0x00000000}, + {0x00008138, 0x00000000}, + {0x0000813c, 0x00000000}, + {0x00008144, 0x00000000}, + {0x00008168, 0x00000000}, + {0x0000816c, 0x00000000}, + {0x00008170, 0x32143320}, + {0x00008174, 0xfaa4fa50}, + {0x00008178, 0x00000100}, + {0x0000817c, 0x00000000}, + {0x000081c4, 0x00000000}, + {0x000081d0, 0x00003210}, + {0x000081ec, 0x00000000}, + {0x000081f0, 0x00000000}, + {0x000081f4, 0x00000000}, + {0x000081f8, 0x00000000}, + {0x000081fc, 0x00000000}, + {0x00008200, 0x00000000}, + {0x00008204, 0x00000000}, + {0x00008208, 0x00000000}, + {0x0000820c, 0x00000000}, + {0x00008210, 0x00000000}, + {0x00008214, 0x00000000}, + {0x00008218, 0x00000000}, + {0x0000821c, 0x00000000}, + {0x00008220, 0x00000000}, + {0x00008224, 0x00000000}, + {0x00008228, 0x00000000}, + {0x0000822c, 0x00000000}, + {0x00008230, 0x00000000}, + {0x00008234, 0x00000000}, + {0x00008238, 0x00000000}, + {0x0000823c, 0x00000000}, + {0x00008240, 0x00100000}, + {0x00008244, 0x0010f400}, + {0x00008248, 0x00000100}, + {0x0000824c, 0x0001e800}, + {0x00008250, 0x00000000}, + {0x00008254, 0x00000000}, + {0x00008258, 0x00000000}, + {0x0000825c, 0x400000ff}, + {0x00008260, 0x00080922}, + {0x00008270, 0x00000000}, + {0x00008274, 0x40000000}, + {0x00008278, 0x003e4180}, + {0x0000827c, 0x00000000}, + {0x00008284, 0x0000002c}, + {0x00008288, 0x0000002c}, + {0x0000828c, 0x00000000}, + {0x00008294, 0x00000000}, + {0x00008298, 0x00000000}, + {0x00008300, 0x00000000}, + {0x00008304, 0x00000000}, + {0x00008308, 0x00000000}, + {0x0000830c, 0x00000000}, + {0x00008310, 0x00000000}, + {0x00008314, 0x00000000}, + {0x00008318, 0x00000000}, + {0x00008328, 0x00000000}, + {0x0000832c, 0x00000007}, + {0x00008330, 0x00000302}, + {0x00008334, 0x00000e00}, + {0x00008338, 0x00000000}, + {0x0000833c, 0x00000000}, + {0x00008340, 0x000107ff}, + {0x00009808, 0x00000000}, + {0x0000980c, 0xad848e19}, + {0x00009810, 0x7d14e000}, + {0x00009814, 0x9c0a9f6b}, + {0x0000981c, 0x00000000}, + {0x0000982c, 0x0000a000}, + {0x00009830, 0x00000000}, + {0x0000983c, 0x00200400}, + {0x00009840, 0x206a01ae}, + {0x0000984c, 0x1284233c}, + {0x00009854, 0x00000859}, + {0x00009900, 0x00000000}, + {0x00009904, 0x00000000}, + {0x00009908, 0x00000000}, + {0x0000990c, 0x00000000}, + {0x0000991c, 0x10000fff}, + {0x00009920, 0x05100000}, + {0x0000a920, 0x05100000}, + {0x0000b920, 0x05100000}, + {0x00009928, 0x00000001}, + {0x0000992c, 0x00000004}, + {0x00009934, 0x1e1f2022}, + {0x00009938, 0x0a0b0c0d}, + {0x0000993c, 0x00000000}, + {0x00009948, 0x9280b212}, + {0x0000994c, 0x00020028}, + {0x0000c95c, 0x004b6a8e}, + {0x0000c968, 0x000003ce}, + {0x00009970, 0x190fb515}, + {0x00009974, 0x00000000}, + {0x00009978, 0x00000001}, + {0x0000997c, 0x00000000}, + {0x00009980, 0x00000000}, + {0x00009984, 0x00000000}, + {0x00009988, 0x00000000}, + {0x0000998c, 0x00000000}, + {0x00009990, 0x00000000}, + {0x00009994, 0x00000000}, + {0x00009998, 0x00000000}, + {0x0000999c, 0x00000000}, + {0x000099a0, 0x00000000}, + {0x000099a4, 0x00000001}, + {0x000099a8, 0x201fff00}, + {0x000099ac, 0x006f0000}, + {0x000099b0, 0x03051000}, + {0x000099dc, 0x00000000}, + {0x000099e0, 0x00000200}, + {0x000099e4, 0xaaaaaaaa}, + {0x000099e8, 0x3c466478}, + {0x000099ec, 0x0cc80caa}, + {0x000099fc, 0x00001042}, + {0x00009b00, 0x00000000}, + {0x00009b04, 0x00000001}, + {0x00009b08, 0x00000002}, + {0x00009b0c, 0x00000003}, + {0x00009b10, 0x00000004}, + {0x00009b14, 0x00000005}, + {0x00009b18, 0x00000008}, + {0x00009b1c, 0x00000009}, + {0x00009b20, 0x0000000a}, + {0x00009b24, 0x0000000b}, + {0x00009b28, 0x0000000c}, + {0x00009b2c, 0x0000000d}, + {0x00009b30, 0x00000010}, + {0x00009b34, 0x00000011}, + {0x00009b38, 0x00000012}, + {0x00009b3c, 0x00000013}, + {0x00009b40, 0x00000014}, + {0x00009b44, 0x00000015}, + {0x00009b48, 0x00000018}, + {0x00009b4c, 0x00000019}, + {0x00009b50, 0x0000001a}, + {0x00009b54, 0x0000001b}, + {0x00009b58, 0x0000001c}, + {0x00009b5c, 0x0000001d}, + {0x00009b60, 0x00000020}, + {0x00009b64, 0x00000021}, + {0x00009b68, 0x00000022}, + {0x00009b6c, 0x00000023}, + {0x00009b70, 0x00000024}, + {0x00009b74, 0x00000025}, + {0x00009b78, 0x00000028}, + {0x00009b7c, 0x00000029}, + {0x00009b80, 0x0000002a}, + {0x00009b84, 0x0000002b}, + {0x00009b88, 0x0000002c}, + {0x00009b8c, 0x0000002d}, + {0x00009b90, 0x00000030}, + {0x00009b94, 0x00000031}, + {0x00009b98, 0x00000032}, + {0x00009b9c, 0x00000033}, + {0x00009ba0, 0x00000034}, + {0x00009ba4, 0x00000035}, + {0x00009ba8, 0x00000035}, + {0x00009bac, 0x00000035}, + {0x00009bb0, 0x00000035}, + {0x00009bb4, 0x00000035}, + {0x00009bb8, 0x00000035}, + {0x00009bbc, 0x00000035}, + {0x00009bc0, 0x00000035}, + {0x00009bc4, 0x00000035}, + {0x00009bc8, 0x00000035}, + {0x00009bcc, 0x00000035}, + {0x00009bd0, 0x00000035}, + {0x00009bd4, 0x00000035}, + {0x00009bd8, 0x00000035}, + {0x00009bdc, 0x00000035}, + {0x00009be0, 0x00000035}, + {0x00009be4, 0x00000035}, + {0x00009be8, 0x00000035}, + {0x00009bec, 0x00000035}, + {0x00009bf0, 0x00000035}, + {0x00009bf4, 0x00000035}, + {0x00009bf8, 0x00000010}, + {0x00009bfc, 0x0000001a}, + {0x0000a210, 0x40806333}, + {0x0000a214, 0x00106c10}, + {0x0000a218, 0x009c4060}, + {0x0000a220, 0x018830c6}, + {0x0000a224, 0x00000400}, + {0x0000a228, 0x001a0bb5}, + {0x0000a22c, 0x00000000}, + {0x0000a234, 0x20202020}, + {0x0000a238, 0x20202020}, + {0x0000a23c, 0x13c889af}, + {0x0000a240, 0x38490a20}, + {0x0000a244, 0x00007bb6}, + {0x0000a248, 0x0fff3ffc}, + {0x0000a24c, 0x00000001}, + {0x0000a250, 0x0000e000}, + {0x0000a254, 0x00000000}, + {0x0000a258, 0x0cc75380}, + {0x0000a25c, 0x0f0f0f01}, + {0x0000a260, 0xdfa91f01}, + {0x0000a268, 0x00000001}, + {0x0000a26c, 0x0ebae9c6}, + {0x0000b26c, 0x0ebae9c6}, + {0x0000c26c, 0x0ebae9c6}, + {0x0000d270, 0x00820820}, + {0x0000a278, 0x1ce739ce}, + {0x0000a27c, 0x050701ce}, + {0x0000a338, 0x00000000}, + {0x0000a33c, 0x00000000}, + {0x0000a340, 0x00000000}, + {0x0000a344, 0x00000000}, + {0x0000a348, 0x3fffffff}, + {0x0000a34c, 0x3fffffff}, + {0x0000a350, 0x3fffffff}, + {0x0000a354, 0x0003ffff}, + {0x0000a358, 0x79a8aa33}, + {0x0000d35c, 0x07ffffef}, + {0x0000d360, 0x0fffffe7}, + {0x0000d364, 0x17ffffe5}, + {0x0000d368, 0x1fffffe4}, + {0x0000d36c, 0x37ffffe3}, + {0x0000d370, 0x3fffffe3}, + {0x0000d374, 0x57ffffe3}, + {0x0000d378, 0x5fffffe2}, + {0x0000d37c, 0x7fffffe2}, + {0x0000d380, 0x7f3c7bba}, + {0x0000d384, 0xf3307ff0}, + {0x0000a388, 0x0c000000}, + {0x0000a38c, 0x20202020}, + {0x0000a390, 0x20202020}, + {0x0000a394, 0x1ce739ce}, + {0x0000a398, 0x000001ce}, + {0x0000a39c, 0x00000001}, + {0x0000a3a0, 0x00000000}, + {0x0000a3a4, 0x00000000}, + {0x0000a3a8, 0x00000000}, + {0x0000a3ac, 0x00000000}, + {0x0000a3b0, 0x00000000}, + {0x0000a3b4, 0x00000000}, + {0x0000a3b8, 0x00000000}, + {0x0000a3bc, 0x00000000}, + {0x0000a3c0, 0x00000000}, + {0x0000a3c4, 0x00000000}, + {0x0000a3c8, 0x00000246}, + {0x0000a3cc, 0x20202020}, + {0x0000a3d0, 0x20202020}, + {0x0000a3d4, 0x20202020}, + {0x0000a3dc, 0x1ce739ce}, + {0x0000a3e0, 0x000001ce}, +}; + +static const u32 ar5416Bank6_9100[][3] = { + /* Addr 5G 2G */ + {0x0000989c, 0x00000000, 0x00000000}, + {0x0000989c, 0x00000000, 0x00000000}, + {0x0000989c, 0x00000000, 0x00000000}, + {0x0000989c, 0x00e00000, 0x00e00000}, + {0x0000989c, 0x005e0000, 0x005e0000}, + {0x0000989c, 0x00120000, 0x00120000}, + {0x0000989c, 0x00620000, 0x00620000}, + {0x0000989c, 0x00020000, 0x00020000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x005f0000, 0x005f0000}, + {0x0000989c, 0x00870000, 0x00870000}, + {0x0000989c, 0x00f90000, 0x00f90000}, + {0x0000989c, 0x007b0000, 0x007b0000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x00f50000, 0x00f50000}, + {0x0000989c, 0x00dc0000, 0x00dc0000}, + {0x0000989c, 0x00110000, 0x00110000}, + {0x0000989c, 0x006100a8, 0x006100a8}, + {0x0000989c, 0x004210a2, 0x004210a2}, + {0x0000989c, 0x0014000f, 0x0014000f}, + {0x0000989c, 0x00c40002, 0x00c40002}, + {0x0000989c, 0x003000f2, 0x003000f2}, + {0x0000989c, 0x00440016, 0x00440016}, + {0x0000989c, 0x00410040, 0x00410040}, + {0x0000989c, 0x000180d6, 0x000180d6}, + {0x0000989c, 0x0000c0aa, 0x0000c0aa}, + {0x0000989c, 0x000000b1, 0x000000b1}, + {0x0000989c, 0x00002000, 0x00002000}, + {0x0000989c, 0x000000d4, 0x000000d4}, + {0x000098d0, 0x0000000f, 0x0010000f}, +}; + +static const u32 ar5416Bank6TPC_9100[][3] = { + /* Addr 5G 2G */ + {0x0000989c, 0x00000000, 0x00000000}, + {0x0000989c, 0x00000000, 0x00000000}, + {0x0000989c, 0x00000000, 0x00000000}, + {0x0000989c, 0x00e00000, 0x00e00000}, + {0x0000989c, 0x005e0000, 0x005e0000}, + {0x0000989c, 0x00120000, 0x00120000}, + {0x0000989c, 0x00620000, 0x00620000}, + {0x0000989c, 0x00020000, 0x00020000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x40ff0000, 0x40ff0000}, + {0x0000989c, 0x005f0000, 0x005f0000}, + {0x0000989c, 0x00870000, 0x00870000}, + {0x0000989c, 0x00f90000, 0x00f90000}, + {0x0000989c, 0x007b0000, 0x007b0000}, + {0x0000989c, 0x00ff0000, 0x00ff0000}, + {0x0000989c, 0x00f50000, 0x00f50000}, + {0x0000989c, 0x00dc0000, 0x00dc0000}, + {0x0000989c, 0x00110000, 0x00110000}, + {0x0000989c, 0x006100a8, 0x006100a8}, + {0x0000989c, 0x00423022, 0x00423022}, + {0x0000989c, 0x2014008f, 0x2014008f}, + {0x0000989c, 0x00c40002, 0x00c40002}, + {0x0000989c, 0x003000f2, 0x003000f2}, + {0x0000989c, 0x00440016, 0x00440016}, + {0x0000989c, 0x00410040, 0x00410040}, + {0x0000989c, 0x0001805e, 0x0001805e}, + {0x0000989c, 0x0000c0ab, 0x0000c0ab}, + {0x0000989c, 0x000000e1, 0x000000e1}, + {0x0000989c, 0x00007080, 0x00007080}, + {0x0000989c, 0x000000d4, 0x000000d4}, + {0x000098d0, 0x0000000f, 0x0010000f}, +}; + +static const u32 ar5416Addac_9100[][2] = { + /* Addr allmodes */ + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000010}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x000000c0}, + {0x0000989c, 0x00000015}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x000098cc, 0x00000000}, +}; + +static const u32 ar5416Modes_9160[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00001030, 0x00000230, 0x00000460, 0x000002c0, 0x00000160}, + {0x00001070, 0x00000168, 0x000002d0, 0x00000318, 0x0000018c}, + {0x000010b0, 0x00000e60, 0x00001cc0, 0x00007c70, 0x00003e38}, + {0x000010f0, 0x0000a000, 0x00014000, 0x00016000, 0x0000b000}, + {0x00008014, 0x03e803e8, 0x07d007d0, 0x10801600, 0x08400b00}, + {0x0000801c, 0x128d93a7, 0x128d93cf, 0x12e013d7, 0x12e013ab}, + {0x00008120, 0x08f04800, 0x08f04800, 0x08f04810, 0x08f04810}, + {0x000081d0, 0x00003210, 0x00003210, 0x0000320a, 0x0000320a}, + {0x00009804, 0x00000300, 0x000003c4, 0x000003c4, 0x00000300}, + {0x00009820, 0x02020200, 0x02020200, 0x02020200, 0x02020200}, + {0x00009824, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x00009828, 0x0a020001, 0x0a020001, 0x0a020001, 0x0a020001}, + {0x00009834, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x00009838, 0x00000007, 0x00000007, 0x00000007, 0x00000007}, + {0x00009844, 0x0372161e, 0x0372161e, 0x037216a0, 0x037216a0}, + {0x00009848, 0x001a6a65, 0x001a6a65, 0x00197a68, 0x00197a68}, + {0x0000a848, 0x001a6a65, 0x001a6a65, 0x00197a68, 0x00197a68}, + {0x0000b848, 0x001a6a65, 0x001a6a65, 0x00197a68, 0x00197a68}, + {0x00009850, 0x6c48b4e2, 0x6d48b4e2, 0x6d48b0e2, 0x6c48b0e2}, + {0x00009858, 0x7ec82d2e, 0x7ec82d2e, 0x7ec82d2e, 0x7ec82d2e}, + {0x0000985c, 0x31395d5e, 0x3139605e, 0x3139605e, 0x31395d5e}, + {0x00009860, 0x00048d18, 0x00048d18, 0x00048d20, 0x00048d20}, + {0x00009864, 0x0001ce00, 0x0001ce00, 0x0001ce00, 0x0001ce00}, + {0x00009868, 0x409a40d0, 0x409a40d0, 0x409a40d0, 0x409a40d0}, + {0x0000986c, 0x050cb081, 0x050cb081, 0x050cb081, 0x050cb081}, + {0x00009914, 0x000007d0, 0x00000fa0, 0x00001130, 0x00000898}, + {0x00009918, 0x0000000a, 0x00000014, 0x00000016, 0x0000000b}, + {0x00009924, 0xd00a8a07, 0xd00a8a07, 0xd00a8a0d, 0xd00a8a0d}, + {0x00009944, 0xffb81020, 0xffb81020, 0xffb81020, 0xffb81020}, + {0x00009960, 0x00009b40, 0x00009b40, 0x00009b40, 0x00009b40}, + {0x0000a960, 0x00009b40, 0x00009b40, 0x00009b40, 0x00009b40}, + {0x0000b960, 0x00009b40, 0x00009b40, 0x00009b40, 0x00009b40}, + {0x00009964, 0x00001120, 0x00001120, 0x00001120, 0x00001120}, + {0x0000c968, 0x000003b5, 0x000003b5, 0x000003ce, 0x000003ce}, + {0x000099bc, 0x001a0600, 0x001a0600, 0x001a0c00, 0x001a0c00}, + {0x000099c0, 0x038919be, 0x038919be, 0x038919be, 0x038919be}, + {0x000099c4, 0x06336f77, 0x06336f77, 0x06336f77, 0x06336f77}, + {0x000099c8, 0x6af65329, 0x6af65329, 0x6af65329, 0x6af65329}, + {0x000099cc, 0x08f186c8, 0x08f186c8, 0x08f186c8, 0x08f186c8}, + {0x000099d0, 0x00046384, 0x00046384, 0x00046384, 0x00046384}, + {0x000099d4, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x000099d8, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a204, 0x00000880, 0x00000880, 0x00000880, 0x00000880}, + {0x0000a208, 0xd6be4788, 0xd6be4788, 0xd03e4788, 0xd03e4788}, + {0x0000a20c, 0x002fc160, 0x002fc160, 0x002ac120, 0x002ac120}, + {0x0000b20c, 0x002fc160, 0x002fc160, 0x002ac120, 0x002ac120}, + {0x0000c20c, 0x002fc160, 0x002fc160, 0x002ac120, 0x002ac120}, + {0x0000a21c, 0x1883800a, 0x1883800a, 0x1883800a, 0x1883800a}, + {0x0000a230, 0x00000000, 0x00000000, 0x00000210, 0x00000108}, + {0x0000a274, 0x0a1a9caa, 0x0a1a9caa, 0x0a1a7caa, 0x0a1a7caa}, + {0x0000a300, 0x18010000, 0x18010000, 0x18010000, 0x18010000}, + {0x0000a304, 0x30032602, 0x30032602, 0x2e032402, 0x2e032402}, + {0x0000a308, 0x48073e06, 0x48073e06, 0x4a0a3c06, 0x4a0a3c06}, + {0x0000a30c, 0x560b4c0a, 0x560b4c0a, 0x621a540b, 0x621a540b}, + {0x0000a310, 0x641a600f, 0x641a600f, 0x764f6c1b, 0x764f6c1b}, + {0x0000a314, 0x7a4f6e1b, 0x7a4f6e1b, 0x845b7a5a, 0x845b7a5a}, + {0x0000a318, 0x8c5b7e5a, 0x8c5b7e5a, 0x950f8ccf, 0x950f8ccf}, + {0x0000a31c, 0x9d0f96cf, 0x9d0f96cf, 0xa5cf9b4f, 0xa5cf9b4f}, + {0x0000a320, 0xb51fa69f, 0xb51fa69f, 0xbddfaf1f, 0xbddfaf1f}, + {0x0000a324, 0xcb3fbd07, 0xcb3fbcbf, 0xd1ffc93f, 0xd1ffc93f}, + {0x0000a328, 0x0000d7bf, 0x0000d7bf, 0x00000000, 0x00000000}, + {0x0000a32c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a330, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a334, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, +}; + +static const u32 ar5416Common_9160[][2] = { + /* Addr allmodes */ + {0x0000000c, 0x00000000}, + {0x00000030, 0x00020015}, + {0x00000034, 0x00000005}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000008}, + {0x00000048, 0x00000008}, + {0x0000004c, 0x00000010}, + {0x00000050, 0x00000000}, + {0x00000054, 0x0000001f}, + {0x00000800, 0x00000000}, + {0x00000804, 0x00000000}, + {0x00000808, 0x00000000}, + {0x0000080c, 0x00000000}, + {0x00000810, 0x00000000}, + {0x00000814, 0x00000000}, + {0x00000818, 0x00000000}, + {0x0000081c, 0x00000000}, + {0x00000820, 0x00000000}, + {0x00000824, 0x00000000}, + {0x00001040, 0x002ffc0f}, + {0x00001044, 0x002ffc0f}, + {0x00001048, 0x002ffc0f}, + {0x0000104c, 0x002ffc0f}, + {0x00001050, 0x002ffc0f}, + {0x00001054, 0x002ffc0f}, + {0x00001058, 0x002ffc0f}, + {0x0000105c, 0x002ffc0f}, + {0x00001060, 0x002ffc0f}, + {0x00001064, 0x002ffc0f}, + {0x00001230, 0x00000000}, + {0x00001270, 0x00000000}, + {0x00001038, 0x00000000}, + {0x00001078, 0x00000000}, + {0x000010b8, 0x00000000}, + {0x000010f8, 0x00000000}, + {0x00001138, 0x00000000}, + {0x00001178, 0x00000000}, + {0x000011b8, 0x00000000}, + {0x000011f8, 0x00000000}, + {0x00001238, 0x00000000}, + {0x00001278, 0x00000000}, + {0x000012b8, 0x00000000}, + {0x000012f8, 0x00000000}, + {0x00001338, 0x00000000}, + {0x00001378, 0x00000000}, + {0x000013b8, 0x00000000}, + {0x000013f8, 0x00000000}, + {0x00001438, 0x00000000}, + {0x00001478, 0x00000000}, + {0x000014b8, 0x00000000}, + {0x000014f8, 0x00000000}, + {0x00001538, 0x00000000}, + {0x00001578, 0x00000000}, + {0x000015b8, 0x00000000}, + {0x000015f8, 0x00000000}, + {0x00001638, 0x00000000}, + {0x00001678, 0x00000000}, + {0x000016b8, 0x00000000}, + {0x000016f8, 0x00000000}, + {0x00001738, 0x00000000}, + {0x00001778, 0x00000000}, + {0x000017b8, 0x00000000}, + {0x000017f8, 0x00000000}, + {0x0000103c, 0x00000000}, + {0x0000107c, 0x00000000}, + {0x000010bc, 0x00000000}, + {0x000010fc, 0x00000000}, + {0x0000113c, 0x00000000}, + {0x0000117c, 0x00000000}, + {0x000011bc, 0x00000000}, + {0x000011fc, 0x00000000}, + {0x0000123c, 0x00000000}, + {0x0000127c, 0x00000000}, + {0x000012bc, 0x00000000}, + {0x000012fc, 0x00000000}, + {0x0000133c, 0x00000000}, + {0x0000137c, 0x00000000}, + {0x000013bc, 0x00000000}, + {0x000013fc, 0x00000000}, + {0x0000143c, 0x00000000}, + {0x0000147c, 0x00000000}, + {0x00004030, 0x00000002}, + {0x0000403c, 0x00000002}, + {0x00007010, 0x00000020}, + {0x00007038, 0x000004c2}, + {0x00008004, 0x00000000}, + {0x00008008, 0x00000000}, + {0x0000800c, 0x00000000}, + {0x00008018, 0x00000700}, + {0x00008020, 0x00000000}, + {0x00008038, 0x00000000}, + {0x0000803c, 0x00000000}, + {0x00008048, 0x40000000}, + {0x00008054, 0x00000000}, + {0x00008058, 0x00000000}, + {0x0000805c, 0x000fc78f}, + {0x00008060, 0x0000000f}, + {0x00008064, 0x00000000}, + {0x000080c0, 0x2a82301a}, + {0x000080c4, 0x05dc01e0}, + {0x000080c8, 0x1f402710}, + {0x000080cc, 0x01f40000}, + {0x000080d0, 0x00001e00}, + {0x000080d4, 0x00000000}, + {0x000080d8, 0x00400000}, + {0x000080e0, 0xffffffff}, + {0x000080e4, 0x0000ffff}, + {0x000080e8, 0x003f3f3f}, + {0x000080ec, 0x00000000}, + {0x000080f0, 0x00000000}, + {0x000080f4, 0x00000000}, + {0x000080f8, 0x00000000}, + {0x000080fc, 0x00020000}, + {0x00008100, 0x00020000}, + {0x00008104, 0x00000001}, + {0x00008108, 0x00000052}, + {0x0000810c, 0x00000000}, + {0x00008110, 0x00000168}, + {0x00008118, 0x000100aa}, + {0x0000811c, 0x00003210}, + {0x00008124, 0x00000000}, + {0x00008128, 0x00000000}, + {0x0000812c, 0x00000000}, + {0x00008130, 0x00000000}, + {0x00008134, 0x00000000}, + {0x00008138, 0x00000000}, + {0x0000813c, 0x00000000}, + {0x00008144, 0xffffffff}, + {0x00008168, 0x00000000}, + {0x0000816c, 0x00000000}, + {0x00008170, 0x32143320}, + {0x00008174, 0xfaa4fa50}, + {0x00008178, 0x00000100}, + {0x0000817c, 0x00000000}, + {0x000081c4, 0x00000000}, + {0x000081ec, 0x00000000}, + {0x000081f0, 0x00000000}, + {0x000081f4, 0x00000000}, + {0x000081f8, 0x00000000}, + {0x000081fc, 0x00000000}, + {0x00008200, 0x00000000}, + {0x00008204, 0x00000000}, + {0x00008208, 0x00000000}, + {0x0000820c, 0x00000000}, + {0x00008210, 0x00000000}, + {0x00008214, 0x00000000}, + {0x00008218, 0x00000000}, + {0x0000821c, 0x00000000}, + {0x00008220, 0x00000000}, + {0x00008224, 0x00000000}, + {0x00008228, 0x00000000}, + {0x0000822c, 0x00000000}, + {0x00008230, 0x00000000}, + {0x00008234, 0x00000000}, + {0x00008238, 0x00000000}, + {0x0000823c, 0x00000000}, + {0x00008240, 0x00100000}, + {0x00008244, 0x0010f400}, + {0x00008248, 0x00000100}, + {0x0000824c, 0x0001e800}, + {0x00008250, 0x00000000}, + {0x00008254, 0x00000000}, + {0x00008258, 0x00000000}, + {0x0000825c, 0x400000ff}, + {0x00008260, 0x00080922}, + {0x00008264, 0x88a00010}, + {0x00008270, 0x00000000}, + {0x00008274, 0x40000000}, + {0x00008278, 0x003e4180}, + {0x0000827c, 0x00000000}, + {0x00008284, 0x0000002c}, + {0x00008288, 0x0000002c}, + {0x0000828c, 0x00000000}, + {0x00008294, 0x00000000}, + {0x00008298, 0x00000000}, + {0x00008300, 0x00000000}, + {0x00008304, 0x00000000}, + {0x00008308, 0x00000000}, + {0x0000830c, 0x00000000}, + {0x00008310, 0x00000000}, + {0x00008314, 0x00000000}, + {0x00008318, 0x00000000}, + {0x00008328, 0x00000000}, + {0x0000832c, 0x00000007}, + {0x00008330, 0x00000302}, + {0x00008334, 0x00000e00}, + {0x00008338, 0x00ff0000}, + {0x0000833c, 0x00000000}, + {0x00008340, 0x000107ff}, + {0x00009808, 0x00000000}, + {0x0000980c, 0xad848e19}, + {0x00009810, 0x7d14e000}, + {0x00009814, 0x9c0a9f6b}, + {0x0000981c, 0x00000000}, + {0x0000982c, 0x0000a000}, + {0x00009830, 0x00000000}, + {0x0000983c, 0x00200400}, + {0x00009840, 0x206a01ae}, + {0x0000984c, 0x1284233c}, + {0x00009854, 0x00000859}, + {0x00009900, 0x00000000}, + {0x00009904, 0x00000000}, + {0x00009908, 0x00000000}, + {0x0000990c, 0x00000000}, + {0x0000991c, 0x10000fff}, + {0x00009920, 0x05100000}, + {0x0000a920, 0x05100000}, + {0x0000b920, 0x05100000}, + {0x00009928, 0x00000001}, + {0x0000992c, 0x00000004}, + {0x00009934, 0x1e1f2022}, + {0x00009938, 0x0a0b0c0d}, + {0x0000993c, 0x00000000}, + {0x00009948, 0x9280b212}, + {0x0000994c, 0x00020028}, + {0x00009954, 0x5f3ca3de}, + {0x00009958, 0x2108ecff}, + {0x00009940, 0x00750604}, + {0x0000c95c, 0x004b6a8e}, + {0x00009970, 0x190fb515}, + {0x00009974, 0x00000000}, + {0x00009978, 0x00000001}, + {0x0000997c, 0x00000000}, + {0x00009980, 0x00000000}, + {0x00009984, 0x00000000}, + {0x00009988, 0x00000000}, + {0x0000998c, 0x00000000}, + {0x00009990, 0x00000000}, + {0x00009994, 0x00000000}, + {0x00009998, 0x00000000}, + {0x0000999c, 0x00000000}, + {0x000099a0, 0x00000000}, + {0x000099a4, 0x00000001}, + {0x000099a8, 0x201fff00}, + {0x000099ac, 0x006f0000}, + {0x000099b0, 0x03051000}, + {0x000099dc, 0x00000000}, + {0x000099e0, 0x00000200}, + {0x000099e4, 0xaaaaaaaa}, + {0x000099e8, 0x3c466478}, + {0x000099ec, 0x0cc80caa}, + {0x000099fc, 0x00001042}, + {0x00009b00, 0x00000000}, + {0x00009b04, 0x00000001}, + {0x00009b08, 0x00000002}, + {0x00009b0c, 0x00000003}, + {0x00009b10, 0x00000004}, + {0x00009b14, 0x00000005}, + {0x00009b18, 0x00000008}, + {0x00009b1c, 0x00000009}, + {0x00009b20, 0x0000000a}, + {0x00009b24, 0x0000000b}, + {0x00009b28, 0x0000000c}, + {0x00009b2c, 0x0000000d}, + {0x00009b30, 0x00000010}, + {0x00009b34, 0x00000011}, + {0x00009b38, 0x00000012}, + {0x00009b3c, 0x00000013}, + {0x00009b40, 0x00000014}, + {0x00009b44, 0x00000015}, + {0x00009b48, 0x00000018}, + {0x00009b4c, 0x00000019}, + {0x00009b50, 0x0000001a}, + {0x00009b54, 0x0000001b}, + {0x00009b58, 0x0000001c}, + {0x00009b5c, 0x0000001d}, + {0x00009b60, 0x00000020}, + {0x00009b64, 0x00000021}, + {0x00009b68, 0x00000022}, + {0x00009b6c, 0x00000023}, + {0x00009b70, 0x00000024}, + {0x00009b74, 0x00000025}, + {0x00009b78, 0x00000028}, + {0x00009b7c, 0x00000029}, + {0x00009b80, 0x0000002a}, + {0x00009b84, 0x0000002b}, + {0x00009b88, 0x0000002c}, + {0x00009b8c, 0x0000002d}, + {0x00009b90, 0x00000030}, + {0x00009b94, 0x00000031}, + {0x00009b98, 0x00000032}, + {0x00009b9c, 0x00000033}, + {0x00009ba0, 0x00000034}, + {0x00009ba4, 0x00000035}, + {0x00009ba8, 0x00000035}, + {0x00009bac, 0x00000035}, + {0x00009bb0, 0x00000035}, + {0x00009bb4, 0x00000035}, + {0x00009bb8, 0x00000035}, + {0x00009bbc, 0x00000035}, + {0x00009bc0, 0x00000035}, + {0x00009bc4, 0x00000035}, + {0x00009bc8, 0x00000035}, + {0x00009bcc, 0x00000035}, + {0x00009bd0, 0x00000035}, + {0x00009bd4, 0x00000035}, + {0x00009bd8, 0x00000035}, + {0x00009bdc, 0x00000035}, + {0x00009be0, 0x00000035}, + {0x00009be4, 0x00000035}, + {0x00009be8, 0x00000035}, + {0x00009bec, 0x00000035}, + {0x00009bf0, 0x00000035}, + {0x00009bf4, 0x00000035}, + {0x00009bf8, 0x00000010}, + {0x00009bfc, 0x0000001a}, + {0x0000a210, 0x40806333}, + {0x0000a214, 0x00106c10}, + {0x0000a218, 0x009c4060}, + {0x0000a220, 0x018830c6}, + {0x0000a224, 0x00000400}, + {0x0000a228, 0x001a0bb5}, + {0x0000a22c, 0x00000000}, + {0x0000a234, 0x20202020}, + {0x0000a238, 0x20202020}, + {0x0000a23c, 0x13c889af}, + {0x0000a240, 0x38490a20}, + {0x0000a244, 0x00007bb6}, + {0x0000a248, 0x0fff3ffc}, + {0x0000a24c, 0x00000001}, + {0x0000a250, 0x0000e000}, + {0x0000a254, 0x00000000}, + {0x0000a258, 0x0cc75380}, + {0x0000a25c, 0x0f0f0f01}, + {0x0000a260, 0xdfa91f01}, + {0x0000a268, 0x00000001}, + {0x0000a26c, 0x0e79e5c6}, + {0x0000b26c, 0x0e79e5c6}, + {0x0000c26c, 0x0e79e5c6}, + {0x0000d270, 0x00820820}, + {0x0000a278, 0x1ce739ce}, + {0x0000a27c, 0x050701ce}, + {0x0000a338, 0x00000000}, + {0x0000a33c, 0x00000000}, + {0x0000a340, 0x00000000}, + {0x0000a344, 0x00000000}, + {0x0000a348, 0x3fffffff}, + {0x0000a34c, 0x3fffffff}, + {0x0000a350, 0x3fffffff}, + {0x0000a354, 0x0003ffff}, + {0x0000a358, 0x79bfaa03}, + {0x0000d35c, 0x07ffffef}, + {0x0000d360, 0x0fffffe7}, + {0x0000d364, 0x17ffffe5}, + {0x0000d368, 0x1fffffe4}, + {0x0000d36c, 0x37ffffe3}, + {0x0000d370, 0x3fffffe3}, + {0x0000d374, 0x57ffffe3}, + {0x0000d378, 0x5fffffe2}, + {0x0000d37c, 0x7fffffe2}, + {0x0000d380, 0x7f3c7bba}, + {0x0000d384, 0xf3307ff0}, + {0x0000a388, 0x0c000000}, + {0x0000a38c, 0x20202020}, + {0x0000a390, 0x20202020}, + {0x0000a394, 0x1ce739ce}, + {0x0000a398, 0x000001ce}, + {0x0000a39c, 0x00000001}, + {0x0000a3a0, 0x00000000}, + {0x0000a3a4, 0x00000000}, + {0x0000a3a8, 0x00000000}, + {0x0000a3ac, 0x00000000}, + {0x0000a3b0, 0x00000000}, + {0x0000a3b4, 0x00000000}, + {0x0000a3b8, 0x00000000}, + {0x0000a3bc, 0x00000000}, + {0x0000a3c0, 0x00000000}, + {0x0000a3c4, 0x00000000}, + {0x0000a3c8, 0x00000246}, + {0x0000a3cc, 0x20202020}, + {0x0000a3d0, 0x20202020}, + {0x0000a3d4, 0x20202020}, + {0x0000a3dc, 0x1ce739ce}, + {0x0000a3e0, 0x000001ce}, +}; + +static const u32 ar5416Addac_9160[][2] = { + /* Addr allmodes */ + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x000000c0}, + {0x0000989c, 0x00000018}, + {0x0000989c, 0x00000004}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x000000c0}, + {0x0000989c, 0x00000019}, + {0x0000989c, 0x00000004}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000004}, + {0x0000989c, 0x00000003}, + {0x0000989c, 0x00000008}, + {0x0000989c, 0x00000000}, + {0x000098cc, 0x00000000}, +}; + +static const u32 ar5416Addac_9160_1_1[][2] = { + /* Addr allmodes */ + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x000000c0}, + {0x0000989c, 0x00000018}, + {0x0000989c, 0x00000004}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x000000c0}, + {0x0000989c, 0x00000019}, + {0x0000989c, 0x00000004}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x0000989c, 0x00000000}, + {0x000098cc, 0x00000000}, +}; + diff --git a/drivers/net/wireless/ath/ath9k/ar9002_calib.c b/drivers/net/wireless/ath/ath9k/ar9002_calib.c new file mode 100644 index 0000000..9f58974 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9002_calib.c @@ -0,0 +1,992 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "hw.h" +#include "hw-ops.h" +#include "ar9002_phy.h" + +#define AR9285_CLCAL_REDO_THRESH 1 + +enum ar9002_cal_types { + ADC_GAIN_CAL = BIT(0), + ADC_DC_CAL = BIT(1), + IQ_MISMATCH_CAL = BIT(2), +}; + +static bool ar9002_hw_is_cal_supported(struct ath_hw *ah, + struct ath9k_channel *chan, + enum ar9002_cal_types cal_type) +{ + bool supported = false; + switch (ah->supp_cals & cal_type) { + case IQ_MISMATCH_CAL: + /* Run IQ Mismatch for non-CCK only */ + if (!IS_CHAN_B(chan)) + supported = true; + break; + case ADC_GAIN_CAL: + case ADC_DC_CAL: + /* Run ADC Gain Cal for non-CCK & non 2GHz-HT20 only */ + if (!IS_CHAN_B(chan) && + !((IS_CHAN_2GHZ(chan) || IS_CHAN_A_FAST_CLOCK(ah, chan)) && + IS_CHAN_HT20(chan))) + supported = true; + break; + } + return supported; +} + +static void ar9002_hw_setup_calibration(struct ath_hw *ah, + struct ath9k_cal_list *currCal) +{ + struct ath_common *common = ath9k_hw_common(ah); + + REG_RMW_FIELD(ah, AR_PHY_TIMING_CTRL4(0), + AR_PHY_TIMING_CTRL4_IQCAL_LOG_COUNT_MAX, + currCal->calData->calCountMax); + + switch (currCal->calData->calType) { + case IQ_MISMATCH_CAL: + REG_WRITE(ah, AR_PHY_CALMODE, AR_PHY_CALMODE_IQ); + ath_dbg(common, CALIBRATE, + "starting IQ Mismatch Calibration\n"); + break; + case ADC_GAIN_CAL: + REG_WRITE(ah, AR_PHY_CALMODE, AR_PHY_CALMODE_ADC_GAIN); + ath_dbg(common, CALIBRATE, "starting ADC Gain Calibration\n"); + break; + case ADC_DC_CAL: + REG_WRITE(ah, AR_PHY_CALMODE, AR_PHY_CALMODE_ADC_DC_PER); + ath_dbg(common, CALIBRATE, "starting ADC DC Calibration\n"); + break; + } + + REG_SET_BIT(ah, AR_PHY_TIMING_CTRL4(0), + AR_PHY_TIMING_CTRL4_DO_CAL); +} + +static bool ar9002_hw_per_calibration(struct ath_hw *ah, + struct ath9k_channel *ichan, + u8 rxchainmask, + struct ath9k_cal_list *currCal) +{ + struct ath9k_hw_cal_data *caldata = ah->caldata; + bool iscaldone = false; + + if (currCal->calState == CAL_RUNNING) { + if (!(REG_READ(ah, AR_PHY_TIMING_CTRL4(0)) & + AR_PHY_TIMING_CTRL4_DO_CAL)) { + + currCal->calData->calCollect(ah); + ah->cal_samples++; + + if (ah->cal_samples >= + currCal->calData->calNumSamples) { + int i, numChains = 0; + for (i = 0; i < AR5416_MAX_CHAINS; i++) { + if (rxchainmask & (1 << i)) + numChains++; + } + + currCal->calData->calPostProc(ah, numChains); + caldata->CalValid |= currCal->calData->calType; + currCal->calState = CAL_DONE; + iscaldone = true; + } else { + ar9002_hw_setup_calibration(ah, currCal); + } + } + } else if (!(caldata->CalValid & currCal->calData->calType)) { + ath9k_hw_reset_calibration(ah, currCal); + } + + return iscaldone; +} + +static void ar9002_hw_iqcal_collect(struct ath_hw *ah) +{ + int i; + + for (i = 0; i < AR5416_MAX_CHAINS; i++) { + ah->totalPowerMeasI[i] += + REG_READ(ah, AR_PHY_CAL_MEAS_0(i)); + ah->totalPowerMeasQ[i] += + REG_READ(ah, AR_PHY_CAL_MEAS_1(i)); + ah->totalIqCorrMeas[i] += + (int32_t) REG_READ(ah, AR_PHY_CAL_MEAS_2(i)); + ath_dbg(ath9k_hw_common(ah), CALIBRATE, + "%d: Chn %d pmi=0x%08x;pmq=0x%08x;iqcm=0x%08x;\n", + ah->cal_samples, i, ah->totalPowerMeasI[i], + ah->totalPowerMeasQ[i], + ah->totalIqCorrMeas[i]); + } +} + +static void ar9002_hw_adc_gaincal_collect(struct ath_hw *ah) +{ + int i; + + for (i = 0; i < AR5416_MAX_CHAINS; i++) { + ah->totalAdcIOddPhase[i] += + REG_READ(ah, AR_PHY_CAL_MEAS_0(i)); + ah->totalAdcIEvenPhase[i] += + REG_READ(ah, AR_PHY_CAL_MEAS_1(i)); + ah->totalAdcQOddPhase[i] += + REG_READ(ah, AR_PHY_CAL_MEAS_2(i)); + ah->totalAdcQEvenPhase[i] += + REG_READ(ah, AR_PHY_CAL_MEAS_3(i)); + + ath_dbg(ath9k_hw_common(ah), CALIBRATE, + "%d: Chn %d oddi=0x%08x; eveni=0x%08x; oddq=0x%08x; evenq=0x%08x;\n", + ah->cal_samples, i, + ah->totalAdcIOddPhase[i], + ah->totalAdcIEvenPhase[i], + ah->totalAdcQOddPhase[i], + ah->totalAdcQEvenPhase[i]); + } +} + +static void ar9002_hw_adc_dccal_collect(struct ath_hw *ah) +{ + int i; + + for (i = 0; i < AR5416_MAX_CHAINS; i++) { + ah->totalAdcDcOffsetIOddPhase[i] += + (int32_t) REG_READ(ah, AR_PHY_CAL_MEAS_0(i)); + ah->totalAdcDcOffsetIEvenPhase[i] += + (int32_t) REG_READ(ah, AR_PHY_CAL_MEAS_1(i)); + ah->totalAdcDcOffsetQOddPhase[i] += + (int32_t) REG_READ(ah, AR_PHY_CAL_MEAS_2(i)); + ah->totalAdcDcOffsetQEvenPhase[i] += + (int32_t) REG_READ(ah, AR_PHY_CAL_MEAS_3(i)); + + ath_dbg(ath9k_hw_common(ah), CALIBRATE, + "%d: Chn %d oddi=0x%08x; eveni=0x%08x; oddq=0x%08x; evenq=0x%08x;\n", + ah->cal_samples, i, + ah->totalAdcDcOffsetIOddPhase[i], + ah->totalAdcDcOffsetIEvenPhase[i], + ah->totalAdcDcOffsetQOddPhase[i], + ah->totalAdcDcOffsetQEvenPhase[i]); + } +} + +static void ar9002_hw_iqcalibrate(struct ath_hw *ah, u8 numChains) +{ + struct ath_common *common = ath9k_hw_common(ah); + u32 powerMeasQ, powerMeasI, iqCorrMeas; + u32 qCoffDenom, iCoffDenom; + int32_t qCoff, iCoff; + int iqCorrNeg, i; + + for (i = 0; i < numChains; i++) { + powerMeasI = ah->totalPowerMeasI[i]; + powerMeasQ = ah->totalPowerMeasQ[i]; + iqCorrMeas = ah->totalIqCorrMeas[i]; + + ath_dbg(common, CALIBRATE, + "Starting IQ Cal and Correction for Chain %d\n", + i); + + ath_dbg(common, CALIBRATE, + "Original: Chn %d iq_corr_meas = 0x%08x\n", + i, ah->totalIqCorrMeas[i]); + + iqCorrNeg = 0; + + if (iqCorrMeas > 0x80000000) { + iqCorrMeas = (0xffffffff - iqCorrMeas) + 1; + iqCorrNeg = 1; + } + + ath_dbg(common, CALIBRATE, "Chn %d pwr_meas_i = 0x%08x\n", + i, powerMeasI); + ath_dbg(common, CALIBRATE, "Chn %d pwr_meas_q = 0x%08x\n", + i, powerMeasQ); + ath_dbg(common, CALIBRATE, "iqCorrNeg is 0x%08x\n", iqCorrNeg); + + iCoffDenom = (powerMeasI / 2 + powerMeasQ / 2) / 128; + qCoffDenom = powerMeasQ / 64; + + if ((powerMeasQ != 0) && (iCoffDenom != 0) && + (qCoffDenom != 0)) { + iCoff = iqCorrMeas / iCoffDenom; + qCoff = powerMeasI / qCoffDenom - 64; + ath_dbg(common, CALIBRATE, "Chn %d iCoff = 0x%08x\n", + i, iCoff); + ath_dbg(common, CALIBRATE, "Chn %d qCoff = 0x%08x\n", + i, qCoff); + + iCoff = iCoff & 0x3f; + ath_dbg(common, CALIBRATE, + "New: Chn %d iCoff = 0x%08x\n", i, iCoff); + if (iqCorrNeg == 0x0) + iCoff = 0x40 - iCoff; + + if (qCoff > 15) + qCoff = 15; + else if (qCoff <= -16) + qCoff = -16; + + ath_dbg(common, CALIBRATE, + "Chn %d : iCoff = 0x%x qCoff = 0x%x\n", + i, iCoff, qCoff); + + REG_RMW_FIELD(ah, AR_PHY_TIMING_CTRL4(i), + AR_PHY_TIMING_CTRL4_IQCORR_Q_I_COFF, + iCoff); + REG_RMW_FIELD(ah, AR_PHY_TIMING_CTRL4(i), + AR_PHY_TIMING_CTRL4_IQCORR_Q_Q_COFF, + qCoff); + ath_dbg(common, CALIBRATE, + "IQ Cal and Correction done for Chain %d\n", + i); + } + } + + REG_SET_BIT(ah, AR_PHY_TIMING_CTRL4(0), + AR_PHY_TIMING_CTRL4_IQCORR_ENABLE); +} + +static void ar9002_hw_adc_gaincal_calibrate(struct ath_hw *ah, u8 numChains) +{ + struct ath_common *common = ath9k_hw_common(ah); + u32 iOddMeasOffset, iEvenMeasOffset, qOddMeasOffset, qEvenMeasOffset; + u32 qGainMismatch, iGainMismatch, val, i; + + for (i = 0; i < numChains; i++) { + iOddMeasOffset = ah->totalAdcIOddPhase[i]; + iEvenMeasOffset = ah->totalAdcIEvenPhase[i]; + qOddMeasOffset = ah->totalAdcQOddPhase[i]; + qEvenMeasOffset = ah->totalAdcQEvenPhase[i]; + + ath_dbg(common, CALIBRATE, + "Starting ADC Gain Cal for Chain %d\n", i); + + ath_dbg(common, CALIBRATE, "Chn %d pwr_meas_odd_i = 0x%08x\n", + i, iOddMeasOffset); + ath_dbg(common, CALIBRATE, "Chn %d pwr_meas_even_i = 0x%08x\n", + i, iEvenMeasOffset); + ath_dbg(common, CALIBRATE, "Chn %d pwr_meas_odd_q = 0x%08x\n", + i, qOddMeasOffset); + ath_dbg(common, CALIBRATE, "Chn %d pwr_meas_even_q = 0x%08x\n", + i, qEvenMeasOffset); + + if (iOddMeasOffset != 0 && qEvenMeasOffset != 0) { + iGainMismatch = + ((iEvenMeasOffset * 32) / + iOddMeasOffset) & 0x3f; + qGainMismatch = + ((qOddMeasOffset * 32) / + qEvenMeasOffset) & 0x3f; + + ath_dbg(common, CALIBRATE, + "Chn %d gain_mismatch_i = 0x%08x\n", + i, iGainMismatch); + ath_dbg(common, CALIBRATE, + "Chn %d gain_mismatch_q = 0x%08x\n", + i, qGainMismatch); + + val = REG_READ(ah, AR_PHY_NEW_ADC_DC_GAIN_CORR(i)); + val &= 0xfffff000; + val |= (qGainMismatch) | (iGainMismatch << 6); + REG_WRITE(ah, AR_PHY_NEW_ADC_DC_GAIN_CORR(i), val); + + ath_dbg(common, CALIBRATE, + "ADC Gain Cal done for Chain %d\n", i); + } + } + + REG_WRITE(ah, AR_PHY_NEW_ADC_DC_GAIN_CORR(0), + REG_READ(ah, AR_PHY_NEW_ADC_DC_GAIN_CORR(0)) | + AR_PHY_NEW_ADC_GAIN_CORR_ENABLE); +} + +static void ar9002_hw_adc_dccal_calibrate(struct ath_hw *ah, u8 numChains) +{ + struct ath_common *common = ath9k_hw_common(ah); + u32 iOddMeasOffset, iEvenMeasOffset, val, i; + int32_t qOddMeasOffset, qEvenMeasOffset, qDcMismatch, iDcMismatch; + const struct ath9k_percal_data *calData = + ah->cal_list_curr->calData; + u32 numSamples = + (1 << (calData->calCountMax + 5)) * calData->calNumSamples; + + for (i = 0; i < numChains; i++) { + iOddMeasOffset = ah->totalAdcDcOffsetIOddPhase[i]; + iEvenMeasOffset = ah->totalAdcDcOffsetIEvenPhase[i]; + qOddMeasOffset = ah->totalAdcDcOffsetQOddPhase[i]; + qEvenMeasOffset = ah->totalAdcDcOffsetQEvenPhase[i]; + + ath_dbg(common, CALIBRATE, + "Starting ADC DC Offset Cal for Chain %d\n", i); + + ath_dbg(common, CALIBRATE, "Chn %d pwr_meas_odd_i = %d\n", + i, iOddMeasOffset); + ath_dbg(common, CALIBRATE, "Chn %d pwr_meas_even_i = %d\n", + i, iEvenMeasOffset); + ath_dbg(common, CALIBRATE, "Chn %d pwr_meas_odd_q = %d\n", + i, qOddMeasOffset); + ath_dbg(common, CALIBRATE, "Chn %d pwr_meas_even_q = %d\n", + i, qEvenMeasOffset); + + iDcMismatch = (((iEvenMeasOffset - iOddMeasOffset) * 2) / + numSamples) & 0x1ff; + qDcMismatch = (((qOddMeasOffset - qEvenMeasOffset) * 2) / + numSamples) & 0x1ff; + + ath_dbg(common, CALIBRATE, + "Chn %d dc_offset_mismatch_i = 0x%08x\n", + i, iDcMismatch); + ath_dbg(common, CALIBRATE, + "Chn %d dc_offset_mismatch_q = 0x%08x\n", + i, qDcMismatch); + + val = REG_READ(ah, AR_PHY_NEW_ADC_DC_GAIN_CORR(i)); + val &= 0xc0000fff; + val |= (qDcMismatch << 12) | (iDcMismatch << 21); + REG_WRITE(ah, AR_PHY_NEW_ADC_DC_GAIN_CORR(i), val); + + ath_dbg(common, CALIBRATE, + "ADC DC Offset Cal done for Chain %d\n", i); + } + + REG_WRITE(ah, AR_PHY_NEW_ADC_DC_GAIN_CORR(0), + REG_READ(ah, AR_PHY_NEW_ADC_DC_GAIN_CORR(0)) | + AR_PHY_NEW_ADC_DC_OFFSET_CORR_ENABLE); +} + +static void ar9287_hw_olc_temp_compensation(struct ath_hw *ah) +{ + u32 rddata; + int32_t delta, currPDADC, slope; + + rddata = REG_READ(ah, AR_PHY_TX_PWRCTRL4); + currPDADC = MS(rddata, AR_PHY_TX_PWRCTRL_PD_AVG_OUT); + + if (ah->initPDADC == 0 || currPDADC == 0) { + /* + * Zero value indicates that no frames have been transmitted + * yet, can't do temperature compensation until frames are + * transmitted. + */ + return; + } else { + slope = ah->eep_ops->get_eeprom(ah, EEP_TEMPSENSE_SLOPE); + + if (slope == 0) { /* to avoid divide by zero case */ + delta = 0; + } else { + delta = ((currPDADC - ah->initPDADC)*4) / slope; + } + REG_RMW_FIELD(ah, AR_PHY_CH0_TX_PWRCTRL11, + AR_PHY_TX_PWRCTRL_OLPC_TEMP_COMP, delta); + REG_RMW_FIELD(ah, AR_PHY_CH1_TX_PWRCTRL11, + AR_PHY_TX_PWRCTRL_OLPC_TEMP_COMP, delta); + } +} + +static void ar9280_hw_olc_temp_compensation(struct ath_hw *ah) +{ + u32 rddata, i; + int delta, currPDADC, regval; + + rddata = REG_READ(ah, AR_PHY_TX_PWRCTRL4); + currPDADC = MS(rddata, AR_PHY_TX_PWRCTRL_PD_AVG_OUT); + + if (ah->initPDADC == 0 || currPDADC == 0) + return; + + if (ah->eep_ops->get_eeprom(ah, EEP_DAC_HPWR_5G)) + delta = (currPDADC - ah->initPDADC + 4) / 8; + else + delta = (currPDADC - ah->initPDADC + 5) / 10; + + if (delta != ah->PDADCdelta) { + ah->PDADCdelta = delta; + for (i = 1; i < AR9280_TX_GAIN_TABLE_SIZE; i++) { + regval = ah->originalGain[i] - delta; + if (regval < 0) + regval = 0; + + REG_RMW_FIELD(ah, + AR_PHY_TX_GAIN_TBL1 + i * 4, + AR_PHY_TX_GAIN, regval); + } + } +} + +static void ar9271_hw_pa_cal(struct ath_hw *ah, bool is_reset) +{ + u32 regVal; + unsigned int i; + u32 regList[][2] = { + { 0x786c, 0 }, + { 0x7854, 0 }, + { 0x7820, 0 }, + { 0x7824, 0 }, + { 0x7868, 0 }, + { 0x783c, 0 }, + { 0x7838, 0 } , + { 0x7828, 0 } , + }; + + for (i = 0; i < ARRAY_SIZE(regList); i++) + regList[i][1] = REG_READ(ah, regList[i][0]); + + regVal = REG_READ(ah, 0x7834); + regVal &= (~(0x1)); + REG_WRITE(ah, 0x7834, regVal); + regVal = REG_READ(ah, 0x9808); + regVal |= (0x1 << 27); + REG_WRITE(ah, 0x9808, regVal); + + /* 786c,b23,1, pwddac=1 */ + REG_RMW_FIELD(ah, AR9285_AN_TOP3, AR9285_AN_TOP3_PWDDAC, 1); + /* 7854, b5,1, pdrxtxbb=1 */ + REG_RMW_FIELD(ah, AR9285_AN_RXTXBB1, AR9285_AN_RXTXBB1_PDRXTXBB1, 1); + /* 7854, b7,1, pdv2i=1 */ + REG_RMW_FIELD(ah, AR9285_AN_RXTXBB1, AR9285_AN_RXTXBB1_PDV2I, 1); + /* 7854, b8,1, pddacinterface=1 */ + REG_RMW_FIELD(ah, AR9285_AN_RXTXBB1, AR9285_AN_RXTXBB1_PDDACIF, 1); + /* 7824,b12,0, offcal=0 */ + REG_RMW_FIELD(ah, AR9285_AN_RF2G2, AR9285_AN_RF2G2_OFFCAL, 0); + /* 7838, b1,0, pwddb=0 */ + REG_RMW_FIELD(ah, AR9285_AN_RF2G7, AR9285_AN_RF2G7_PWDDB, 0); + /* 7820,b11,0, enpacal=0 */ + REG_RMW_FIELD(ah, AR9285_AN_RF2G1, AR9285_AN_RF2G1_ENPACAL, 0); + /* 7820,b25,1, pdpadrv1=0 */ + REG_RMW_FIELD(ah, AR9285_AN_RF2G1, AR9285_AN_RF2G1_PDPADRV1, 0); + /* 7820,b24,0, pdpadrv2=0 */ + REG_RMW_FIELD(ah, AR9285_AN_RF2G1, AR9285_AN_RF2G1_PDPADRV2, 0); + /* 7820,b23,0, pdpaout=0 */ + REG_RMW_FIELD(ah, AR9285_AN_RF2G1, AR9285_AN_RF2G1_PDPAOUT, 0); + /* 783c,b14-16,7, padrvgn2tab_0=7 */ + REG_RMW_FIELD(ah, AR9285_AN_RF2G8, AR9285_AN_RF2G8_PADRVGN2TAB0, 7); + /* + * 7838,b29-31,0, padrvgn1tab_0=0 + * does not matter since we turn it off + */ + REG_RMW_FIELD(ah, AR9285_AN_RF2G7, AR9285_AN_RF2G7_PADRVGN2TAB0, 0); + + REG_RMW_FIELD(ah, AR9285_AN_RF2G3, AR9271_AN_RF2G3_CCOMP, 0xfff); + + /* Set: + * localmode=1,bmode=1,bmoderxtx=1,synthon=1, + * txon=1,paon=1,oscon=1,synthon_force=1 + */ + REG_WRITE(ah, AR9285_AN_TOP2, 0xca0358a0); + udelay(30); + REG_RMW_FIELD(ah, AR9285_AN_RF2G6, AR9271_AN_RF2G6_OFFS, 0); + + /* find off_6_1; */ + for (i = 6; i > 0; i--) { + regVal = REG_READ(ah, 0x7834); + regVal |= (1 << (20 + i)); + REG_WRITE(ah, 0x7834, regVal); + udelay(1); + /* regVal = REG_READ(ah, 0x7834); */ + regVal &= (~(0x1 << (20 + i))); + regVal |= (MS(REG_READ(ah, 0x7840), AR9285_AN_RXTXBB1_SPARE9) + << (20 + i)); + REG_WRITE(ah, 0x7834, regVal); + } + + regVal = (regVal >> 20) & 0x7f; + + /* Update PA cal info */ + if ((!is_reset) && (ah->pacal_info.prev_offset == regVal)) { + if (ah->pacal_info.max_skipcount < MAX_PACAL_SKIPCOUNT) + ah->pacal_info.max_skipcount = + 2 * ah->pacal_info.max_skipcount; + ah->pacal_info.skipcount = ah->pacal_info.max_skipcount; + } else { + ah->pacal_info.max_skipcount = 1; + ah->pacal_info.skipcount = 0; + ah->pacal_info.prev_offset = regVal; + } + + ENABLE_REGWRITE_BUFFER(ah); + + regVal = REG_READ(ah, 0x7834); + regVal |= 0x1; + REG_WRITE(ah, 0x7834, regVal); + regVal = REG_READ(ah, 0x9808); + regVal &= (~(0x1 << 27)); + REG_WRITE(ah, 0x9808, regVal); + + for (i = 0; i < ARRAY_SIZE(regList); i++) + REG_WRITE(ah, regList[i][0], regList[i][1]); + + REGWRITE_BUFFER_FLUSH(ah); +} + +static inline void ar9285_hw_pa_cal(struct ath_hw *ah, bool is_reset) +{ + struct ath_common *common = ath9k_hw_common(ah); + u32 regVal; + int i, offset, offs_6_1, offs_0; + u32 ccomp_org, reg_field; + u32 regList[][2] = { + { 0x786c, 0 }, + { 0x7854, 0 }, + { 0x7820, 0 }, + { 0x7824, 0 }, + { 0x7868, 0 }, + { 0x783c, 0 }, + { 0x7838, 0 }, + }; + + ath_dbg(common, CALIBRATE, "Running PA Calibration\n"); + + /* PA CAL is not needed for high power solution */ + if (ah->eep_ops->get_eeprom(ah, EEP_TXGAIN_TYPE) == + AR5416_EEP_TXGAIN_HIGH_POWER) + return; + + for (i = 0; i < ARRAY_SIZE(regList); i++) + regList[i][1] = REG_READ(ah, regList[i][0]); + + regVal = REG_READ(ah, 0x7834); + regVal &= (~(0x1)); + REG_WRITE(ah, 0x7834, regVal); + regVal = REG_READ(ah, 0x9808); + regVal |= (0x1 << 27); + REG_WRITE(ah, 0x9808, regVal); + + REG_RMW_FIELD(ah, AR9285_AN_TOP3, AR9285_AN_TOP3_PWDDAC, 1); + REG_RMW_FIELD(ah, AR9285_AN_RXTXBB1, AR9285_AN_RXTXBB1_PDRXTXBB1, 1); + REG_RMW_FIELD(ah, AR9285_AN_RXTXBB1, AR9285_AN_RXTXBB1_PDV2I, 1); + REG_RMW_FIELD(ah, AR9285_AN_RXTXBB1, AR9285_AN_RXTXBB1_PDDACIF, 1); + REG_RMW_FIELD(ah, AR9285_AN_RF2G2, AR9285_AN_RF2G2_OFFCAL, 0); + REG_RMW_FIELD(ah, AR9285_AN_RF2G7, AR9285_AN_RF2G7_PWDDB, 0); + REG_RMW_FIELD(ah, AR9285_AN_RF2G1, AR9285_AN_RF2G1_ENPACAL, 0); + REG_RMW_FIELD(ah, AR9285_AN_RF2G1, AR9285_AN_RF2G1_PDPADRV1, 0); + REG_RMW_FIELD(ah, AR9285_AN_RF2G1, AR9285_AN_RF2G1_PDPADRV2, 0); + REG_RMW_FIELD(ah, AR9285_AN_RF2G1, AR9285_AN_RF2G1_PDPAOUT, 0); + REG_RMW_FIELD(ah, AR9285_AN_RF2G8, AR9285_AN_RF2G8_PADRVGN2TAB0, 7); + REG_RMW_FIELD(ah, AR9285_AN_RF2G7, AR9285_AN_RF2G7_PADRVGN2TAB0, 0); + ccomp_org = MS(REG_READ(ah, AR9285_AN_RF2G6), AR9285_AN_RF2G6_CCOMP); + REG_RMW_FIELD(ah, AR9285_AN_RF2G6, AR9285_AN_RF2G6_CCOMP, 0xf); + + REG_WRITE(ah, AR9285_AN_TOP2, 0xca0358a0); + udelay(30); + REG_RMW_FIELD(ah, AR9285_AN_RF2G6, AR9285_AN_RF2G6_OFFS, 0); + REG_RMW_FIELD(ah, AR9285_AN_RF2G3, AR9285_AN_RF2G3_PDVCCOMP, 0); + + for (i = 6; i > 0; i--) { + regVal = REG_READ(ah, 0x7834); + regVal |= (1 << (19 + i)); + REG_WRITE(ah, 0x7834, regVal); + udelay(1); + regVal = REG_READ(ah, 0x7834); + regVal &= (~(0x1 << (19 + i))); + reg_field = MS(REG_READ(ah, 0x7840), AR9285_AN_RXTXBB1_SPARE9); + regVal |= (reg_field << (19 + i)); + REG_WRITE(ah, 0x7834, regVal); + } + + REG_RMW_FIELD(ah, AR9285_AN_RF2G3, AR9285_AN_RF2G3_PDVCCOMP, 1); + udelay(1); + reg_field = MS(REG_READ(ah, AR9285_AN_RF2G9), AR9285_AN_RXTXBB1_SPARE9); + REG_RMW_FIELD(ah, AR9285_AN_RF2G3, AR9285_AN_RF2G3_PDVCCOMP, reg_field); + offs_6_1 = MS(REG_READ(ah, AR9285_AN_RF2G6), AR9285_AN_RF2G6_OFFS); + offs_0 = MS(REG_READ(ah, AR9285_AN_RF2G3), AR9285_AN_RF2G3_PDVCCOMP); + + offset = (offs_6_1<<1) | offs_0; + offset = offset - 0; + offs_6_1 = offset>>1; + offs_0 = offset & 1; + + if ((!is_reset) && (ah->pacal_info.prev_offset == offset)) { + if (ah->pacal_info.max_skipcount < MAX_PACAL_SKIPCOUNT) + ah->pacal_info.max_skipcount = + 2 * ah->pacal_info.max_skipcount; + ah->pacal_info.skipcount = ah->pacal_info.max_skipcount; + } else { + ah->pacal_info.max_skipcount = 1; + ah->pacal_info.skipcount = 0; + ah->pacal_info.prev_offset = offset; + } + + REG_RMW_FIELD(ah, AR9285_AN_RF2G6, AR9285_AN_RF2G6_OFFS, offs_6_1); + REG_RMW_FIELD(ah, AR9285_AN_RF2G3, AR9285_AN_RF2G3_PDVCCOMP, offs_0); + + regVal = REG_READ(ah, 0x7834); + regVal |= 0x1; + REG_WRITE(ah, 0x7834, regVal); + regVal = REG_READ(ah, 0x9808); + regVal &= (~(0x1 << 27)); + REG_WRITE(ah, 0x9808, regVal); + + for (i = 0; i < ARRAY_SIZE(regList); i++) + REG_WRITE(ah, regList[i][0], regList[i][1]); + + REG_RMW_FIELD(ah, AR9285_AN_RF2G6, AR9285_AN_RF2G6_CCOMP, ccomp_org); +} + +static void ar9002_hw_pa_cal(struct ath_hw *ah, bool is_reset) +{ + if (AR_SREV_9271(ah)) { + if (is_reset || !ah->pacal_info.skipcount) + ar9271_hw_pa_cal(ah, is_reset); + else + ah->pacal_info.skipcount--; + } else if (AR_SREV_9285_12_OR_LATER(ah)) { + if (is_reset || !ah->pacal_info.skipcount) + ar9285_hw_pa_cal(ah, is_reset); + else + ah->pacal_info.skipcount--; + } +} + +static void ar9002_hw_olc_temp_compensation(struct ath_hw *ah) +{ + if (OLC_FOR_AR9287_10_LATER) + ar9287_hw_olc_temp_compensation(ah); + else if (OLC_FOR_AR9280_20_LATER) + ar9280_hw_olc_temp_compensation(ah); +} + +static bool ar9002_hw_calibrate(struct ath_hw *ah, + struct ath9k_channel *chan, + u8 rxchainmask, + bool longcal) +{ + bool iscaldone = true; + struct ath9k_cal_list *currCal = ah->cal_list_curr; + bool nfcal, nfcal_pending = false; + + nfcal = !!(REG_READ(ah, AR_PHY_AGC_CONTROL) & AR_PHY_AGC_CONTROL_NF); + if (ah->caldata) + nfcal_pending = ah->caldata->nfcal_pending; + + if (currCal && !nfcal && + (currCal->calState == CAL_RUNNING || + currCal->calState == CAL_WAITING)) { + iscaldone = ar9002_hw_per_calibration(ah, chan, + rxchainmask, currCal); + if (iscaldone) { + ah->cal_list_curr = currCal = currCal->calNext; + + if (currCal->calState == CAL_WAITING) { + iscaldone = false; + ath9k_hw_reset_calibration(ah, currCal); + } + } + } + + /* Do NF cal only at longer intervals */ + if (longcal || nfcal_pending) { + /* + * Get the value from the previous NF cal and update + * history buffer. + */ + if (ath9k_hw_getnf(ah, chan)) { + /* + * Load the NF from history buffer of the current + * channel. + * NF is slow time-variant, so it is OK to use a + * historical value. + */ + ath9k_hw_loadnf(ah, ah->curchan); + } + + if (longcal) { + ath9k_hw_start_nfcal(ah, false); + /* Do periodic PAOffset Cal */ + ar9002_hw_pa_cal(ah, false); + ar9002_hw_olc_temp_compensation(ah); + } + } + + return iscaldone; +} + +/* Carrier leakage Calibration fix */ +static bool ar9285_hw_cl_cal(struct ath_hw *ah, struct ath9k_channel *chan) +{ + struct ath_common *common = ath9k_hw_common(ah); + + REG_SET_BIT(ah, AR_PHY_CL_CAL_CTL, AR_PHY_CL_CAL_ENABLE); + if (IS_CHAN_HT20(chan)) { + REG_SET_BIT(ah, AR_PHY_CL_CAL_CTL, AR_PHY_PARALLEL_CAL_ENABLE); + REG_SET_BIT(ah, AR_PHY_TURBO, AR_PHY_FC_DYN2040_EN); + REG_CLR_BIT(ah, AR_PHY_AGC_CONTROL, + AR_PHY_AGC_CONTROL_FLTR_CAL); + REG_CLR_BIT(ah, AR_PHY_TPCRG1, AR_PHY_TPCRG1_PD_CAL_ENABLE); + REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_CAL); + if (!ath9k_hw_wait(ah, AR_PHY_AGC_CONTROL, + AR_PHY_AGC_CONTROL_CAL, 0, AH_WAIT_TIMEOUT)) { + ath_dbg(common, CALIBRATE, + "offset calibration failed to complete in %d ms; noisy environment?\n", + AH_WAIT_TIMEOUT / 1000); + return false; + } + REG_CLR_BIT(ah, AR_PHY_TURBO, AR_PHY_FC_DYN2040_EN); + REG_CLR_BIT(ah, AR_PHY_CL_CAL_CTL, AR_PHY_PARALLEL_CAL_ENABLE); + REG_CLR_BIT(ah, AR_PHY_CL_CAL_CTL, AR_PHY_CL_CAL_ENABLE); + } + REG_CLR_BIT(ah, AR_PHY_ADC_CTL, AR_PHY_ADC_CTL_OFF_PWDADC); + REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_FLTR_CAL); + REG_SET_BIT(ah, AR_PHY_TPCRG1, AR_PHY_TPCRG1_PD_CAL_ENABLE); + REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_CAL); + if (!ath9k_hw_wait(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_CAL, + 0, AH_WAIT_TIMEOUT)) { + ath_dbg(common, CALIBRATE, + "offset calibration failed to complete in %d ms; noisy environment?\n", + AH_WAIT_TIMEOUT / 1000); + return false; + } + + REG_SET_BIT(ah, AR_PHY_ADC_CTL, AR_PHY_ADC_CTL_OFF_PWDADC); + REG_CLR_BIT(ah, AR_PHY_CL_CAL_CTL, AR_PHY_CL_CAL_ENABLE); + REG_CLR_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_FLTR_CAL); + + return true; +} + +static bool ar9285_hw_clc(struct ath_hw *ah, struct ath9k_channel *chan) +{ + int i; + u_int32_t txgain_max; + u_int32_t clc_gain, gain_mask = 0, clc_num = 0; + u_int32_t reg_clc_I0, reg_clc_Q0; + u_int32_t i0_num = 0; + u_int32_t q0_num = 0; + u_int32_t total_num = 0; + u_int32_t reg_rf2g5_org; + bool retv = true; + + if (!(ar9285_hw_cl_cal(ah, chan))) + return false; + + txgain_max = MS(REG_READ(ah, AR_PHY_TX_PWRCTRL7), + AR_PHY_TX_PWRCTRL_TX_GAIN_TAB_MAX); + + for (i = 0; i < (txgain_max+1); i++) { + clc_gain = (REG_READ(ah, (AR_PHY_TX_GAIN_TBL1+(i<<2))) & + AR_PHY_TX_GAIN_CLC) >> AR_PHY_TX_GAIN_CLC_S; + if (!(gain_mask & (1 << clc_gain))) { + gain_mask |= (1 << clc_gain); + clc_num++; + } + } + + for (i = 0; i < clc_num; i++) { + reg_clc_I0 = (REG_READ(ah, (AR_PHY_CLC_TBL1 + (i << 2))) + & AR_PHY_CLC_I0) >> AR_PHY_CLC_I0_S; + reg_clc_Q0 = (REG_READ(ah, (AR_PHY_CLC_TBL1 + (i << 2))) + & AR_PHY_CLC_Q0) >> AR_PHY_CLC_Q0_S; + if (reg_clc_I0 == 0) + i0_num++; + + if (reg_clc_Q0 == 0) + q0_num++; + } + total_num = i0_num + q0_num; + if (total_num > AR9285_CLCAL_REDO_THRESH) { + reg_rf2g5_org = REG_READ(ah, AR9285_RF2G5); + if (AR_SREV_9285E_20(ah)) { + REG_WRITE(ah, AR9285_RF2G5, + (reg_rf2g5_org & AR9285_RF2G5_IC50TX) | + AR9285_RF2G5_IC50TX_XE_SET); + } else { + REG_WRITE(ah, AR9285_RF2G5, + (reg_rf2g5_org & AR9285_RF2G5_IC50TX) | + AR9285_RF2G5_IC50TX_SET); + } + retv = ar9285_hw_cl_cal(ah, chan); + REG_WRITE(ah, AR9285_RF2G5, reg_rf2g5_org); + } + return retv; +} + +static bool ar9002_hw_init_cal(struct ath_hw *ah, struct ath9k_channel *chan) +{ + struct ath_common *common = ath9k_hw_common(ah); + + if (AR_SREV_9271(ah)) { + if (!ar9285_hw_cl_cal(ah, chan)) + return false; + } else if (AR_SREV_9285(ah) && AR_SREV_9285_12_OR_LATER(ah)) { + if (!ar9285_hw_clc(ah, chan)) + return false; + } else { + if (AR_SREV_9280_20_OR_LATER(ah)) { + if (!AR_SREV_9287_11_OR_LATER(ah)) + REG_CLR_BIT(ah, AR_PHY_ADC_CTL, + AR_PHY_ADC_CTL_OFF_PWDADC); + REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, + AR_PHY_AGC_CONTROL_FLTR_CAL); + } + + /* Calibrate the AGC */ + REG_WRITE(ah, AR_PHY_AGC_CONTROL, + REG_READ(ah, AR_PHY_AGC_CONTROL) | + AR_PHY_AGC_CONTROL_CAL); + + /* Poll for offset calibration complete */ + if (!ath9k_hw_wait(ah, AR_PHY_AGC_CONTROL, + AR_PHY_AGC_CONTROL_CAL, + 0, AH_WAIT_TIMEOUT)) { + ath_dbg(common, CALIBRATE, + "offset calibration failed to complete in %d ms; noisy environment?\n", + AH_WAIT_TIMEOUT / 1000); + return false; + } + + if (AR_SREV_9280_20_OR_LATER(ah)) { + if (!AR_SREV_9287_11_OR_LATER(ah)) + REG_SET_BIT(ah, AR_PHY_ADC_CTL, + AR_PHY_ADC_CTL_OFF_PWDADC); + REG_CLR_BIT(ah, AR_PHY_AGC_CONTROL, + AR_PHY_AGC_CONTROL_FLTR_CAL); + } + } + + /* Do PA Calibration */ + ar9002_hw_pa_cal(ah, true); + + if (ah->caldata) + ah->caldata->nfcal_pending = true; + + ah->cal_list = ah->cal_list_last = ah->cal_list_curr = NULL; + + /* Enable IQ, ADC Gain and ADC DC offset CALs */ + if (AR_SREV_9100(ah) || AR_SREV_9160_10_OR_LATER(ah)) { + ah->supp_cals = IQ_MISMATCH_CAL; + + if (AR_SREV_9160_10_OR_LATER(ah)) + ah->supp_cals |= ADC_GAIN_CAL | ADC_DC_CAL; + + if (AR_SREV_9287(ah)) + ah->supp_cals &= ~ADC_GAIN_CAL; + + if (ar9002_hw_is_cal_supported(ah, chan, ADC_GAIN_CAL)) { + INIT_CAL(&ah->adcgain_caldata); + INSERT_CAL(ah, &ah->adcgain_caldata); + ath_dbg(common, CALIBRATE, + "enabling ADC Gain Calibration\n"); + } + + if (ar9002_hw_is_cal_supported(ah, chan, ADC_DC_CAL)) { + INIT_CAL(&ah->adcdc_caldata); + INSERT_CAL(ah, &ah->adcdc_caldata); + ath_dbg(common, CALIBRATE, + "enabling ADC DC Calibration\n"); + } + + if (ar9002_hw_is_cal_supported(ah, chan, IQ_MISMATCH_CAL)) { + INIT_CAL(&ah->iq_caldata); + INSERT_CAL(ah, &ah->iq_caldata); + ath_dbg(common, CALIBRATE, "enabling IQ Calibration\n"); + } + + ah->cal_list_curr = ah->cal_list; + + if (ah->cal_list_curr) + ath9k_hw_reset_calibration(ah, ah->cal_list_curr); + } + + if (ah->caldata) + ah->caldata->CalValid = 0; + + return true; +} + +static const struct ath9k_percal_data iq_cal_multi_sample = { + IQ_MISMATCH_CAL, + MAX_CAL_SAMPLES, + PER_MIN_LOG_COUNT, + ar9002_hw_iqcal_collect, + ar9002_hw_iqcalibrate +}; +static const struct ath9k_percal_data iq_cal_single_sample = { + IQ_MISMATCH_CAL, + MIN_CAL_SAMPLES, + PER_MAX_LOG_COUNT, + ar9002_hw_iqcal_collect, + ar9002_hw_iqcalibrate +}; +static const struct ath9k_percal_data adc_gain_cal_multi_sample = { + ADC_GAIN_CAL, + MAX_CAL_SAMPLES, + PER_MIN_LOG_COUNT, + ar9002_hw_adc_gaincal_collect, + ar9002_hw_adc_gaincal_calibrate +}; +static const struct ath9k_percal_data adc_gain_cal_single_sample = { + ADC_GAIN_CAL, + MIN_CAL_SAMPLES, + PER_MAX_LOG_COUNT, + ar9002_hw_adc_gaincal_collect, + ar9002_hw_adc_gaincal_calibrate +}; +static const struct ath9k_percal_data adc_dc_cal_multi_sample = { + ADC_DC_CAL, + MAX_CAL_SAMPLES, + PER_MIN_LOG_COUNT, + ar9002_hw_adc_dccal_collect, + ar9002_hw_adc_dccal_calibrate +}; +static const struct ath9k_percal_data adc_dc_cal_single_sample = { + ADC_DC_CAL, + MIN_CAL_SAMPLES, + PER_MAX_LOG_COUNT, + ar9002_hw_adc_dccal_collect, + ar9002_hw_adc_dccal_calibrate +}; + +static void ar9002_hw_init_cal_settings(struct ath_hw *ah) +{ + if (AR_SREV_9100(ah)) { + ah->iq_caldata.calData = &iq_cal_multi_sample; + ah->supp_cals = IQ_MISMATCH_CAL; + return; + } + + if (AR_SREV_9160_10_OR_LATER(ah)) { + if (AR_SREV_9280_20_OR_LATER(ah)) { + ah->iq_caldata.calData = &iq_cal_single_sample; + ah->adcgain_caldata.calData = + &adc_gain_cal_single_sample; + ah->adcdc_caldata.calData = + &adc_dc_cal_single_sample; + } else { + ah->iq_caldata.calData = &iq_cal_multi_sample; + ah->adcgain_caldata.calData = + &adc_gain_cal_multi_sample; + ah->adcdc_caldata.calData = + &adc_dc_cal_multi_sample; + } + ah->supp_cals = ADC_GAIN_CAL | ADC_DC_CAL | IQ_MISMATCH_CAL; + + if (AR_SREV_9287(ah)) + ah->supp_cals &= ~ADC_GAIN_CAL; + } +} + +void ar9002_hw_attach_calib_ops(struct ath_hw *ah) +{ + struct ath_hw_private_ops *priv_ops = ath9k_hw_private_ops(ah); + struct ath_hw_ops *ops = ath9k_hw_ops(ah); + + priv_ops->init_cal_settings = ar9002_hw_init_cal_settings; + priv_ops->init_cal = ar9002_hw_init_cal; + priv_ops->setup_calibration = ar9002_hw_setup_calibration; + + ops->calibrate = ar9002_hw_calibrate; +} diff --git a/drivers/net/wireless/ath/ath9k/ar9002_hw.c b/drivers/net/wireless/ath/ath9k/ar9002_hw.c new file mode 100644 index 0000000..830daa1 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9002_hw.c @@ -0,0 +1,474 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "hw.h" +#include "ar5008_initvals.h" +#include "ar9001_initvals.h" +#include "ar9002_initvals.h" +#include "ar9002_phy.h" + +/* General hardware code for the A5008/AR9001/AR9002 hadware families */ + +static int ar9002_hw_init_mode_regs(struct ath_hw *ah) +{ + if (AR_SREV_9271(ah)) { + INIT_INI_ARRAY(&ah->iniModes, ar9271Modes_9271); + INIT_INI_ARRAY(&ah->iniCommon, ar9271Common_9271); + INIT_INI_ARRAY(&ah->iniModes_9271_ANI_reg, ar9271Modes_9271_ANI_reg); + return 0; + } + + if (ah->config.pcie_clock_req) + INIT_INI_ARRAY(&ah->iniPcieSerdes, + ar9280PciePhy_clkreq_off_L1_9280); + else + INIT_INI_ARRAY(&ah->iniPcieSerdes, + ar9280PciePhy_clkreq_always_on_L1_9280); +#ifdef CONFIG_PM_SLEEP + INIT_INI_ARRAY(&ah->iniPcieSerdesWow, + ar9280PciePhy_awow); +#endif + + if (AR_SREV_9287_11_OR_LATER(ah)) { + INIT_INI_ARRAY(&ah->iniModes, ar9287Modes_9287_1_1); + INIT_INI_ARRAY(&ah->iniCommon, ar9287Common_9287_1_1); + } else if (AR_SREV_9285_12_OR_LATER(ah)) { + INIT_INI_ARRAY(&ah->iniModes, ar9285Modes_9285_1_2); + INIT_INI_ARRAY(&ah->iniCommon, ar9285Common_9285_1_2); + } else if (AR_SREV_9280_20_OR_LATER(ah)) { + INIT_INI_ARRAY(&ah->iniModes, ar9280Modes_9280_2); + INIT_INI_ARRAY(&ah->iniCommon, ar9280Common_9280_2); + + INIT_INI_ARRAY(&ah->iniModesFastClock, + ar9280Modes_fast_clock_9280_2); + } else if (AR_SREV_9160_10_OR_LATER(ah)) { + INIT_INI_ARRAY(&ah->iniModes, ar5416Modes_9160); + INIT_INI_ARRAY(&ah->iniCommon, ar5416Common_9160); + if (AR_SREV_9160_11(ah)) { + INIT_INI_ARRAY(&ah->iniAddac, + ar5416Addac_9160_1_1); + } else { + INIT_INI_ARRAY(&ah->iniAddac, ar5416Addac_9160); + } + } else if (AR_SREV_9100_OR_LATER(ah)) { + INIT_INI_ARRAY(&ah->iniModes, ar5416Modes_9100); + INIT_INI_ARRAY(&ah->iniCommon, ar5416Common_9100); + INIT_INI_ARRAY(&ah->iniAddac, ar5416Addac_9100); + } else { + INIT_INI_ARRAY(&ah->iniModes, ar5416Modes); + INIT_INI_ARRAY(&ah->iniCommon, ar5416Common); + INIT_INI_ARRAY(&ah->iniAddac, ar5416Addac); + } + + if (!AR_SREV_9280_20_OR_LATER(ah)) { + /* Common for AR5416, AR913x, AR9160 */ + INIT_INI_ARRAY(&ah->iniBB_RfGain, ar5416BB_RfGain); + + /* Common for AR913x, AR9160 */ + if (!AR_SREV_5416(ah)) + INIT_INI_ARRAY(&ah->iniBank6, ar5416Bank6TPC_9100); + else + INIT_INI_ARRAY(&ah->iniBank6, ar5416Bank6TPC); + } + + /* iniAddac needs to be modified for these chips */ + if (AR_SREV_9160(ah) || !AR_SREV_5416_22_OR_LATER(ah)) { + struct ar5416IniArray *addac = &ah->iniAddac; + u32 size = sizeof(u32) * addac->ia_rows * addac->ia_columns; + u32 *data; + + data = devm_kzalloc(ah->dev, size, GFP_KERNEL); + if (!data) + return -ENOMEM; + + memcpy(data, addac->ia_array, size); + addac->ia_array = data; + + if (!AR_SREV_5416_22_OR_LATER(ah)) { + /* override CLKDRV value */ + INI_RA(addac, 31,1) = 0; + } + } + if (AR_SREV_9287_11_OR_LATER(ah)) { + INIT_INI_ARRAY(&ah->iniCckfirNormal, + ar9287Common_normal_cck_fir_coeff_9287_1_1); + INIT_INI_ARRAY(&ah->iniCckfirJapan2484, + ar9287Common_japan_2484_cck_fir_coeff_9287_1_1); + } + return 0; +} + +static void ar9280_20_hw_init_rxgain_ini(struct ath_hw *ah) +{ + u32 rxgain_type; + + if (ah->eep_ops->get_eeprom(ah, EEP_MINOR_REV) >= + AR5416_EEP_MINOR_VER_17) { + rxgain_type = ah->eep_ops->get_eeprom(ah, EEP_RXGAIN_TYPE); + + if (rxgain_type == AR5416_EEP_RXGAIN_13DB_BACKOFF) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9280Modes_backoff_13db_rxgain_9280_2); + else if (rxgain_type == AR5416_EEP_RXGAIN_23DB_BACKOFF) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9280Modes_backoff_23db_rxgain_9280_2); + else + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9280Modes_original_rxgain_9280_2); + } else { + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9280Modes_original_rxgain_9280_2); + } +} + +static void ar9280_20_hw_init_txgain_ini(struct ath_hw *ah, u32 txgain_type) +{ + if (ah->eep_ops->get_eeprom(ah, EEP_MINOR_REV) >= + AR5416_EEP_MINOR_VER_19) { + if (txgain_type == AR5416_EEP_TXGAIN_HIGH_POWER) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9280Modes_high_power_tx_gain_9280_2); + else + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9280Modes_original_tx_gain_9280_2); + } else { + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9280Modes_original_tx_gain_9280_2); + } +} + +static void ar9271_hw_init_txgain_ini(struct ath_hw *ah, u32 txgain_type) +{ + if (txgain_type == AR5416_EEP_TXGAIN_HIGH_POWER) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9271Modes_high_power_tx_gain_9271); + else + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9271Modes_normal_power_tx_gain_9271); +} + +static void ar9002_hw_init_mode_gain_regs(struct ath_hw *ah) +{ + u32 txgain_type = ah->eep_ops->get_eeprom(ah, EEP_TXGAIN_TYPE); + + if (AR_SREV_9287_11_OR_LATER(ah)) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9287Modes_rx_gain_9287_1_1); + else if (AR_SREV_9280_20(ah)) + ar9280_20_hw_init_rxgain_ini(ah); + + if (AR_SREV_9271(ah)) { + ar9271_hw_init_txgain_ini(ah, txgain_type); + } else if (AR_SREV_9287_11_OR_LATER(ah)) { + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9287Modes_tx_gain_9287_1_1); + } else if (AR_SREV_9280_20(ah)) { + ar9280_20_hw_init_txgain_ini(ah, txgain_type); + } else if (AR_SREV_9285_12_OR_LATER(ah)) { + /* txgain table */ + if (txgain_type == AR5416_EEP_TXGAIN_HIGH_POWER) { + if (AR_SREV_9285E_20(ah)) { + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9285Modes_XE2_0_high_power); + } else { + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9285Modes_high_power_tx_gain_9285_1_2); + } + } else { + if (AR_SREV_9285E_20(ah)) { + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9285Modes_XE2_0_normal_power); + } else { + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9285Modes_original_tx_gain_9285_1_2); + } + } + } +} + +/* + * Helper for ASPM support. + * + * Disable PLL when in L0s as well as receiver clock when in L1. + * This power saving option must be enabled through the SerDes. + * + * Programming the SerDes must go through the same 288 bit serial shift + * register as the other analog registers. Hence the 9 writes. + */ +static void ar9002_hw_configpcipowersave(struct ath_hw *ah, + bool power_off) +{ + u8 i; + u32 val; + + /* Nothing to do on restore for 11N */ + if (!power_off /* !restore */) { + if (AR_SREV_9280_20_OR_LATER(ah)) { + /* + * AR9280 2.0 or later chips use SerDes values from the + * initvals.h initialized depending on chipset during + * __ath9k_hw_init() + */ + for (i = 0; i < ah->iniPcieSerdes.ia_rows; i++) { + REG_WRITE(ah, INI_RA(&ah->iniPcieSerdes, i, 0), + INI_RA(&ah->iniPcieSerdes, i, 1)); + } + } else { + ENABLE_REGWRITE_BUFFER(ah); + + REG_WRITE(ah, AR_PCIE_SERDES, 0x9248fc00); + REG_WRITE(ah, AR_PCIE_SERDES, 0x24924924); + + /* RX shut off when elecidle is asserted */ + REG_WRITE(ah, AR_PCIE_SERDES, 0x28000039); + REG_WRITE(ah, AR_PCIE_SERDES, 0x53160824); + REG_WRITE(ah, AR_PCIE_SERDES, 0xe5980579); + + /* + * Ignore ah->ah_config.pcie_clock_req setting for + * pre-AR9280 11n + */ + REG_WRITE(ah, AR_PCIE_SERDES, 0x001defff); + + REG_WRITE(ah, AR_PCIE_SERDES, 0x1aaabe40); + REG_WRITE(ah, AR_PCIE_SERDES, 0xbe105554); + REG_WRITE(ah, AR_PCIE_SERDES, 0x000e3007); + + /* Load the new settings */ + REG_WRITE(ah, AR_PCIE_SERDES2, 0x00000000); + + REGWRITE_BUFFER_FLUSH(ah); + } + + udelay(1000); + } + + if (power_off) { + /* clear bit 19 to disable L1 */ + REG_CLR_BIT(ah, AR_PCIE_PM_CTRL, AR_PCIE_PM_CTRL_ENA); + + val = REG_READ(ah, AR_WA); + + /* + * Set PCIe workaround bits + * In AR9280 and AR9285, bit 14 in WA register (disable L1) + * should only be set when device enters D3 and be + * cleared when device comes back to D0. + */ + if (ah->config.pcie_waen) { + if (ah->config.pcie_waen & AR_WA_D3_L1_DISABLE) + val |= AR_WA_D3_L1_DISABLE; + } else { + if (((AR_SREV_9285(ah) || + AR_SREV_9271(ah) || + AR_SREV_9287(ah)) && + (AR9285_WA_DEFAULT & AR_WA_D3_L1_DISABLE)) || + (AR_SREV_9280(ah) && + (AR9280_WA_DEFAULT & AR_WA_D3_L1_DISABLE))) { + val |= AR_WA_D3_L1_DISABLE; + } + } + + if (AR_SREV_9280(ah) || AR_SREV_9285(ah) || AR_SREV_9287(ah)) { + /* + * Disable bit 6 and 7 before entering D3 to + * prevent system hang. + */ + val &= ~(AR_WA_BIT6 | AR_WA_BIT7); + } + + if (AR_SREV_9280(ah)) + val |= AR_WA_BIT22; + + if (AR_SREV_9285E_20(ah)) + val |= AR_WA_BIT23; + + REG_WRITE(ah, AR_WA, val); + } else { + if (ah->config.pcie_waen) { + val = ah->config.pcie_waen; + if (!power_off) + val &= (~AR_WA_D3_L1_DISABLE); + } else { + if (AR_SREV_9285(ah) || + AR_SREV_9271(ah) || + AR_SREV_9287(ah)) { + val = AR9285_WA_DEFAULT; + if (!power_off) + val &= (~AR_WA_D3_L1_DISABLE); + } + else if (AR_SREV_9280(ah)) { + /* + * For AR9280 chips, bit 22 of 0x4004 + * needs to be set. + */ + val = AR9280_WA_DEFAULT; + if (!power_off) + val &= (~AR_WA_D3_L1_DISABLE); + } else { + val = AR_WA_DEFAULT; + } + } + + /* WAR for ASPM system hang */ + if (AR_SREV_9285(ah) || AR_SREV_9287(ah)) + val |= (AR_WA_BIT6 | AR_WA_BIT7); + + if (AR_SREV_9285E_20(ah)) + val |= AR_WA_BIT23; + + REG_WRITE(ah, AR_WA, val); + + /* set bit 19 to allow forcing of pcie core into L1 state */ + REG_SET_BIT(ah, AR_PCIE_PM_CTRL, AR_PCIE_PM_CTRL_ENA); + } +} + +static int ar9002_hw_get_radiorev(struct ath_hw *ah) +{ + u32 val; + int i; + + ENABLE_REGWRITE_BUFFER(ah); + + REG_WRITE(ah, AR_PHY(0x36), 0x00007058); + for (i = 0; i < 8; i++) + REG_WRITE(ah, AR_PHY(0x20), 0x00010000); + + REGWRITE_BUFFER_FLUSH(ah); + + val = (REG_READ(ah, AR_PHY(256)) >> 24) & 0xff; + val = ((val & 0xf0) >> 4) | ((val & 0x0f) << 4); + + return ath9k_hw_reverse_bits(val, 8); +} + +int ar9002_hw_rf_claim(struct ath_hw *ah) +{ + u32 val; + + REG_WRITE(ah, AR_PHY(0), 0x00000007); + + val = ar9002_hw_get_radiorev(ah); + switch (val & AR_RADIO_SREV_MAJOR) { + case 0: + val = AR_RAD5133_SREV_MAJOR; + break; + case AR_RAD5133_SREV_MAJOR: + case AR_RAD5122_SREV_MAJOR: + case AR_RAD2133_SREV_MAJOR: + case AR_RAD2122_SREV_MAJOR: + break; + default: + ath_err(ath9k_hw_common(ah), + "Radio Chip Rev 0x%02X not supported\n", + val & AR_RADIO_SREV_MAJOR); + return -EOPNOTSUPP; + } + + ah->hw_version.analog5GhzRev = val; + + return 0; +} + +void ar9002_hw_enable_async_fifo(struct ath_hw *ah) +{ + if (AR_SREV_9287_13_OR_LATER(ah)) { + REG_SET_BIT(ah, AR_MAC_PCU_ASYNC_FIFO_REG3, + AR_MAC_PCU_ASYNC_FIFO_REG3_DATAPATH_SEL); + REG_SET_BIT(ah, AR_PHY_MODE, AR_PHY_MODE_ASYNCFIFO); + REG_CLR_BIT(ah, AR_MAC_PCU_ASYNC_FIFO_REG3, + AR_MAC_PCU_ASYNC_FIFO_REG3_SOFT_RESET); + REG_SET_BIT(ah, AR_MAC_PCU_ASYNC_FIFO_REG3, + AR_MAC_PCU_ASYNC_FIFO_REG3_SOFT_RESET); + } +} + +/* Sets up the AR5008/AR9001/AR9002 hardware familiy callbacks */ +int ar9002_hw_attach_ops(struct ath_hw *ah) +{ + struct ath_hw_private_ops *priv_ops = ath9k_hw_private_ops(ah); + struct ath_hw_ops *ops = ath9k_hw_ops(ah); + int ret; + + ret = ar9002_hw_init_mode_regs(ah); + if (ret) + return ret; + + priv_ops->init_mode_gain_regs = ar9002_hw_init_mode_gain_regs; + + ops->config_pci_powersave = ar9002_hw_configpcipowersave; + + ret = ar5008_hw_attach_phy_ops(ah); + if (ret) + return ret; + + if (AR_SREV_9280_20_OR_LATER(ah)) + ar9002_hw_attach_phy_ops(ah); + + ar9002_hw_attach_calib_ops(ah); + ar9002_hw_attach_mac_ops(ah); + return 0; +} + +void ar9002_hw_load_ani_reg(struct ath_hw *ah, struct ath9k_channel *chan) +{ + u32 modesIndex; + int i; + + switch (chan->chanmode) { + case CHANNEL_A: + case CHANNEL_A_HT20: + modesIndex = 1; + break; + case CHANNEL_A_HT40PLUS: + case CHANNEL_A_HT40MINUS: + modesIndex = 2; + break; + case CHANNEL_G: + case CHANNEL_G_HT20: + case CHANNEL_B: + modesIndex = 4; + break; + case CHANNEL_G_HT40PLUS: + case CHANNEL_G_HT40MINUS: + modesIndex = 3; + break; + + default: + return; + } + + ENABLE_REGWRITE_BUFFER(ah); + + for (i = 0; i < ah->iniModes_9271_ANI_reg.ia_rows; i++) { + u32 reg = INI_RA(&ah->iniModes_9271_ANI_reg, i, 0); + u32 val = INI_RA(&ah->iniModes_9271_ANI_reg, i, modesIndex); + u32 val_orig; + + if (reg == AR_PHY_CCK_DETECT) { + val_orig = REG_READ(ah, reg); + val &= AR_PHY_CCK_DETECT_WEAK_SIG_THR_CCK; + val_orig &= ~AR_PHY_CCK_DETECT_WEAK_SIG_THR_CCK; + + REG_WRITE(ah, reg, val|val_orig); + } else + REG_WRITE(ah, reg, val); + } + + REGWRITE_BUFFER_FLUSH(ah); +} diff --git a/drivers/net/wireless/ath/ath9k/ar9002_initvals.h b/drivers/net/wireless/ath/ath9k/ar9002_initvals.h new file mode 100644 index 0000000..beb6162 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9002_initvals.h @@ -0,0 +1,3194 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +static const u32 ar9280Modes_9280_2[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00001030, 0x00000230, 0x00000460, 0x000002c0, 0x00000160}, + {0x00001070, 0x00000168, 0x000002d0, 0x00000318, 0x0000018c}, + {0x000010b0, 0x00000e60, 0x00001cc0, 0x00007c70, 0x00003e38}, + {0x000010f0, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00008014, 0x03e803e8, 0x07d007d0, 0x10801600, 0x08400b00}, + {0x0000801c, 0x128d8027, 0x128d804f, 0x12e00057, 0x12e0002b}, + {0x00008120, 0x08f04800, 0x08f04800, 0x08f04810, 0x08f04810}, + {0x000081d0, 0x00003210, 0x00003210, 0x0000320a, 0x0000320a}, + {0x00008318, 0x00003e80, 0x00007d00, 0x00006880, 0x00003440}, + {0x00009804, 0x00000300, 0x000003c4, 0x000003c4, 0x00000300}, + {0x00009820, 0x02020200, 0x02020200, 0x02020200, 0x02020200}, + {0x00009824, 0x01000e0e, 0x01000e0e, 0x01000e0e, 0x01000e0e}, + {0x00009828, 0x0a020001, 0x0a020001, 0x0a020001, 0x0a020001}, + {0x00009834, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x00009838, 0x00000007, 0x00000007, 0x00000007, 0x00000007}, + {0x00009840, 0x206a022e, 0x206a022e, 0x206a012e, 0x206a012e}, + {0x00009844, 0x0372161e, 0x0372161e, 0x037216a0, 0x037216a0}, + {0x00009850, 0x6c4000e2, 0x6d4000e2, 0x6d4000e2, 0x6c4000e2}, + {0x00009858, 0x7ec88d2e, 0x7ec88d2e, 0x7ec84d2e, 0x7ec84d2e}, + {0x0000985c, 0x31395d5e, 0x3139605e, 0x3139605e, 0x31395d5e}, + {0x00009860, 0x00048d18, 0x00048d18, 0x00048d20, 0x00048d20}, + {0x00009864, 0x0001ce00, 0x0001ce00, 0x0001ce00, 0x0001ce00}, + {0x00009868, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0}, + {0x0000986c, 0x06903081, 0x06903081, 0x06903881, 0x06903881}, + {0x00009914, 0x000007d0, 0x00000fa0, 0x00001130, 0x00000898}, + {0x00009918, 0x0000000a, 0x00000014, 0x00000268, 0x0000000b}, + {0x00009924, 0xd00a8a0b, 0xd00a8a0b, 0xd00a8a0d, 0xd00a8a0d}, + {0x00009944, 0xffbc1010, 0xffbc1010, 0xffbc1010, 0xffbc1010}, + {0x00009960, 0x00000010, 0x00000010, 0x00000010, 0x00000010}, + {0x0000a960, 0x00000010, 0x00000010, 0x00000010, 0x00000010}, + {0x00009964, 0x00000210, 0x00000210, 0x00000210, 0x00000210}, + {0x0000c968, 0x000003b5, 0x000003b5, 0x000003ce, 0x000003ce}, + {0x000099b8, 0x0000001c, 0x0000001c, 0x0000001c, 0x0000001c}, + {0x000099bc, 0x00000a00, 0x00000a00, 0x00000c00, 0x00000c00}, + {0x000099c0, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4}, + {0x0000a204, 0x00000444, 0x00000444, 0x00000444, 0x00000444}, + {0x0000a20c, 0x00000014, 0x00000014, 0x0001f019, 0x0001f019}, + {0x0000b20c, 0x00000014, 0x00000014, 0x0001f019, 0x0001f019}, + {0x0000a21c, 0x1883800a, 0x1883800a, 0x1883800a, 0x1883800a}, + {0x0000a230, 0x00000000, 0x00000000, 0x00000210, 0x00000108}, + {0x0000a23c, 0x13c88000, 0x13c88000, 0x13c88001, 0x13c88000}, + {0x0000a250, 0x001ff000, 0x001ff000, 0x0004a000, 0x0004a000}, + {0x0000a358, 0x7999aa02, 0x7999aa02, 0x7999aa0e, 0x7999aa0e}, + {0x0000a388, 0x0c000000, 0x0c000000, 0x08000000, 0x0c000000}, + {0x0000a3d8, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00007894, 0x5a508000, 0x5a508000, 0x5a508000, 0x5a508000}, +}; + +static const u32 ar9280Common_9280_2[][2] = { + /* Addr allmodes */ + {0x0000000c, 0x00000000}, + {0x00000030, 0x00020015}, + {0x00000034, 0x00000005}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000008}, + {0x00000048, 0x00000008}, + {0x0000004c, 0x00000010}, + {0x00000050, 0x00000000}, + {0x00000054, 0x0000001f}, + {0x00000800, 0x00000000}, + {0x00000804, 0x00000000}, + {0x00000808, 0x00000000}, + {0x0000080c, 0x00000000}, + {0x00000810, 0x00000000}, + {0x00000814, 0x00000000}, + {0x00000818, 0x00000000}, + {0x0000081c, 0x00000000}, + {0x00000820, 0x00000000}, + {0x00000824, 0x00000000}, + {0x00001040, 0x002ffc0f}, + {0x00001044, 0x002ffc0f}, + {0x00001048, 0x002ffc0f}, + {0x0000104c, 0x002ffc0f}, + {0x00001050, 0x002ffc0f}, + {0x00001054, 0x002ffc0f}, + {0x00001058, 0x002ffc0f}, + {0x0000105c, 0x002ffc0f}, + {0x00001060, 0x002ffc0f}, + {0x00001064, 0x002ffc0f}, + {0x00001230, 0x00000000}, + {0x00001270, 0x00000000}, + {0x00001038, 0x00000000}, + {0x00001078, 0x00000000}, + {0x000010b8, 0x00000000}, + {0x000010f8, 0x00000000}, + {0x00001138, 0x00000000}, + {0x00001178, 0x00000000}, + {0x000011b8, 0x00000000}, + {0x000011f8, 0x00000000}, + {0x00001238, 0x00000000}, + {0x00001278, 0x00000000}, + {0x000012b8, 0x00000000}, + {0x000012f8, 0x00000000}, + {0x00001338, 0x00000000}, + {0x00001378, 0x00000000}, + {0x000013b8, 0x00000000}, + {0x000013f8, 0x00000000}, + {0x00001438, 0x00000000}, + {0x00001478, 0x00000000}, + {0x000014b8, 0x00000000}, + {0x000014f8, 0x00000000}, + {0x00001538, 0x00000000}, + {0x00001578, 0x00000000}, + {0x000015b8, 0x00000000}, + {0x000015f8, 0x00000000}, + {0x00001638, 0x00000000}, + {0x00001678, 0x00000000}, + {0x000016b8, 0x00000000}, + {0x000016f8, 0x00000000}, + {0x00001738, 0x00000000}, + {0x00001778, 0x00000000}, + {0x000017b8, 0x00000000}, + {0x000017f8, 0x00000000}, + {0x0000103c, 0x00000000}, + {0x0000107c, 0x00000000}, + {0x000010bc, 0x00000000}, + {0x000010fc, 0x00000000}, + {0x0000113c, 0x00000000}, + {0x0000117c, 0x00000000}, + {0x000011bc, 0x00000000}, + {0x000011fc, 0x00000000}, + {0x0000123c, 0x00000000}, + {0x0000127c, 0x00000000}, + {0x000012bc, 0x00000000}, + {0x000012fc, 0x00000000}, + {0x0000133c, 0x00000000}, + {0x0000137c, 0x00000000}, + {0x000013bc, 0x00000000}, + {0x000013fc, 0x00000000}, + {0x0000143c, 0x00000000}, + {0x0000147c, 0x00000000}, + {0x00004030, 0x00000002}, + {0x0000403c, 0x00000002}, + {0x00004024, 0x0000001f}, + {0x00004060, 0x00000000}, + {0x00004064, 0x00000000}, + {0x00007010, 0x00000033}, + {0x00007034, 0x00000002}, + {0x00007038, 0x000004c2}, + {0x00008004, 0x00000000}, + {0x00008008, 0x00000000}, + {0x0000800c, 0x00000000}, + {0x00008018, 0x00000700}, + {0x00008020, 0x00000000}, + {0x00008038, 0x00000000}, + {0x0000803c, 0x00000000}, + {0x00008048, 0x40000000}, + {0x00008054, 0x00000000}, + {0x00008058, 0x00000000}, + {0x0000805c, 0x000fc78f}, + {0x00008060, 0x0000000f}, + {0x00008064, 0x00000000}, + {0x00008070, 0x00000000}, + {0x000080c0, 0x2a80001a}, + {0x000080c4, 0x05dc01e0}, + {0x000080c8, 0x1f402710}, + {0x000080cc, 0x01f40000}, + {0x000080d0, 0x00001e00}, + {0x000080d4, 0x00000000}, + {0x000080d8, 0x00400000}, + {0x000080e0, 0xffffffff}, + {0x000080e4, 0x0000ffff}, + {0x000080e8, 0x003f3f3f}, + {0x000080ec, 0x00000000}, + {0x000080f0, 0x00000000}, + {0x000080f4, 0x00000000}, + {0x000080f8, 0x00000000}, + {0x000080fc, 0x00020000}, + {0x00008100, 0x00020000}, + {0x00008104, 0x00000001}, + {0x00008108, 0x00000052}, + {0x0000810c, 0x00000000}, + {0x00008110, 0x00000168}, + {0x00008118, 0x000100aa}, + {0x0000811c, 0x00003210}, + {0x00008124, 0x00000000}, + {0x00008128, 0x00000000}, + {0x0000812c, 0x00000000}, + {0x00008130, 0x00000000}, + {0x00008134, 0x00000000}, + {0x00008138, 0x00000000}, + {0x0000813c, 0x00000000}, + {0x00008144, 0xffffffff}, + {0x00008168, 0x00000000}, + {0x0000816c, 0x00000000}, + {0x00008170, 0x32143320}, + {0x00008174, 0xfaa4fa50}, + {0x00008178, 0x00000100}, + {0x0000817c, 0x00000000}, + {0x000081c0, 0x00000000}, + {0x000081ec, 0x00000000}, + {0x000081f0, 0x00000000}, + {0x000081f4, 0x00000000}, + {0x000081f8, 0x00000000}, + {0x000081fc, 0x00000000}, + {0x00008200, 0x00000000}, + {0x00008204, 0x00000000}, + {0x00008208, 0x00000000}, + {0x0000820c, 0x00000000}, + {0x00008210, 0x00000000}, + {0x00008214, 0x00000000}, + {0x00008218, 0x00000000}, + {0x0000821c, 0x00000000}, + {0x00008220, 0x00000000}, + {0x00008224, 0x00000000}, + {0x00008228, 0x00000000}, + {0x0000822c, 0x00000000}, + {0x00008230, 0x00000000}, + {0x00008234, 0x00000000}, + {0x00008238, 0x00000000}, + {0x0000823c, 0x00000000}, + {0x00008240, 0x00100000}, + {0x00008244, 0x0010f400}, + {0x00008248, 0x00000100}, + {0x0000824c, 0x0001e800}, + {0x00008250, 0x00000000}, + {0x00008254, 0x00000000}, + {0x00008258, 0x00000000}, + {0x0000825c, 0x400000ff}, + {0x00008260, 0x00080922}, + {0x00008264, 0x88a00010}, + {0x00008270, 0x00000000}, + {0x00008274, 0x40000000}, + {0x00008278, 0x003e4180}, + {0x0000827c, 0x00000000}, + {0x00008284, 0x0000002c}, + {0x00008288, 0x0000002c}, + {0x0000828c, 0x00000000}, + {0x00008294, 0x00000000}, + {0x00008298, 0x00000000}, + {0x0000829c, 0x00000000}, + {0x00008300, 0x00000040}, + {0x00008314, 0x00000000}, + {0x00008328, 0x00000000}, + {0x0000832c, 0x00000007}, + {0x00008330, 0x00000302}, + {0x00008334, 0x00000e00}, + {0x00008338, 0x00ff0000}, + {0x0000833c, 0x00000000}, + {0x00008340, 0x000107ff}, + {0x00008344, 0x00481043}, + {0x00009808, 0x00000000}, + {0x0000980c, 0xafa68e30}, + {0x00009810, 0xfd14e000}, + {0x00009814, 0x9c0a9f6b}, + {0x0000981c, 0x00000000}, + {0x0000982c, 0x0000a000}, + {0x00009830, 0x00000000}, + {0x0000983c, 0x00200400}, + {0x0000984c, 0x0040233c}, + {0x0000a84c, 0x0040233c}, + {0x00009854, 0x00000044}, + {0x00009900, 0x00000000}, + {0x00009904, 0x00000000}, + {0x00009908, 0x00000000}, + {0x0000990c, 0x00000000}, + {0x00009910, 0x01002310}, + {0x0000991c, 0x10000fff}, + {0x00009920, 0x04900000}, + {0x0000a920, 0x04900000}, + {0x00009928, 0x00000001}, + {0x0000992c, 0x00000004}, + {0x00009934, 0x1e1f2022}, + {0x00009938, 0x0a0b0c0d}, + {0x0000993c, 0x00000000}, + {0x00009948, 0x9280c00a}, + {0x0000994c, 0x00020028}, + {0x00009954, 0x5f3ca3de}, + {0x00009958, 0x2108ecff}, + {0x00009940, 0x14750604}, + {0x0000c95c, 0x004b6a8e}, + {0x00009970, 0x190fb514}, + {0x00009974, 0x00000000}, + {0x00009978, 0x00000001}, + {0x0000997c, 0x00000000}, + {0x00009980, 0x00000000}, + {0x00009984, 0x00000000}, + {0x00009988, 0x00000000}, + {0x0000998c, 0x00000000}, + {0x00009990, 0x00000000}, + {0x00009994, 0x00000000}, + {0x00009998, 0x00000000}, + {0x0000999c, 0x00000000}, + {0x000099a0, 0x00000000}, + {0x000099a4, 0x00000001}, + {0x000099a8, 0x201fff00}, + {0x000099ac, 0x006f0000}, + {0x000099b0, 0x03051000}, + {0x000099b4, 0x00000820}, + {0x000099c4, 0x06336f77}, + {0x000099c8, 0x6af6532f}, + {0x000099cc, 0x08f186c8}, + {0x000099d0, 0x00046384}, + {0x000099d4, 0x00000000}, + {0x000099d8, 0x00000000}, + {0x000099dc, 0x00000000}, + {0x000099e0, 0x00000000}, + {0x000099e4, 0xaaaaaaaa}, + {0x000099e8, 0x3c466478}, + {0x000099ec, 0x0cc80caa}, + {0x000099f0, 0x00000000}, + {0x000099fc, 0x00001042}, + {0x0000a208, 0x803e4788}, + {0x0000a210, 0x4080a333}, + {0x0000a214, 0x40206c10}, + {0x0000a218, 0x009c4060}, + {0x0000a220, 0x01834061}, + {0x0000a224, 0x00000400}, + {0x0000a228, 0x000003b5}, + {0x0000a22c, 0x233f7180}, + {0x0000a234, 0x20202020}, + {0x0000a238, 0x20202020}, + {0x0000a240, 0x38490a20}, + {0x0000a244, 0x00007bb6}, + {0x0000a248, 0x0fff3ffc}, + {0x0000a24c, 0x00000000}, + {0x0000a254, 0x00000000}, + {0x0000a258, 0x0cdbd380}, + {0x0000a25c, 0x0f0f0f01}, + {0x0000a260, 0xdfa91f01}, + {0x0000a268, 0x00000000}, + {0x0000a26c, 0x0e79e5c6}, + {0x0000b26c, 0x0e79e5c6}, + {0x0000d270, 0x00820820}, + {0x0000a278, 0x1ce739ce}, + {0x0000d35c, 0x07ffffef}, + {0x0000d360, 0x0fffffe7}, + {0x0000d364, 0x17ffffe5}, + {0x0000d368, 0x1fffffe4}, + {0x0000d36c, 0x37ffffe3}, + {0x0000d370, 0x3fffffe3}, + {0x0000d374, 0x57ffffe3}, + {0x0000d378, 0x5fffffe2}, + {0x0000d37c, 0x7fffffe2}, + {0x0000d380, 0x7f3c7bba}, + {0x0000d384, 0xf3307ff0}, + {0x0000a38c, 0x20202020}, + {0x0000a390, 0x20202020}, + {0x0000a394, 0x1ce739ce}, + {0x0000a398, 0x000001ce}, + {0x0000a39c, 0x00000001}, + {0x0000a3a0, 0x00000000}, + {0x0000a3a4, 0x00000000}, + {0x0000a3a8, 0x00000000}, + {0x0000a3ac, 0x00000000}, + {0x0000a3b0, 0x00000000}, + {0x0000a3b4, 0x00000000}, + {0x0000a3b8, 0x00000000}, + {0x0000a3bc, 0x00000000}, + {0x0000a3c0, 0x00000000}, + {0x0000a3c4, 0x00000000}, + {0x0000a3c8, 0x00000246}, + {0x0000a3cc, 0x20202020}, + {0x0000a3d0, 0x20202020}, + {0x0000a3d4, 0x20202020}, + {0x0000a3dc, 0x1ce739ce}, + {0x0000a3e0, 0x000001ce}, + {0x0000a3e4, 0x00000000}, + {0x0000a3e8, 0x18c43433}, + {0x00007800, 0x00040000}, + {0x00007804, 0xdb005012}, + {0x00007808, 0x04924914}, + {0x0000780c, 0x21084210}, + {0x00007810, 0x6d801300}, + {0x00007818, 0x07e41000}, + {0x00007824, 0x00040000}, + {0x00007828, 0xdb005012}, + {0x0000782c, 0x04924914}, + {0x00007830, 0x21084210}, + {0x00007834, 0x6d801300}, + {0x0000783c, 0x07e40000}, + {0x00007848, 0x00100000}, + {0x0000784c, 0x773f0567}, + {0x00007850, 0x54214514}, + {0x00007854, 0x12035828}, + {0x00007858, 0x9259269a}, + {0x00007860, 0x52802000}, + {0x00007864, 0x0a8e370e}, + {0x00007868, 0xc0102850}, + {0x0000786c, 0x812d4000}, + {0x00007870, 0x807ec400}, + {0x00007874, 0x001b6db0}, + {0x00007878, 0x00376b63}, + {0x0000787c, 0x06db6db6}, + {0x00007880, 0x006d8000}, + {0x00007884, 0xffeffffe}, + {0x00007888, 0xffeffffe}, + {0x0000788c, 0x00010000}, + {0x00007890, 0x02060aeb}, + {0x00007898, 0x2a850160}, +}; + +static const u32 ar9280Modes_fast_clock_9280_2[][3] = { + /* Addr 5G_HT20 5G_HT40 */ + {0x00001030, 0x00000268, 0x000004d0}, + {0x00001070, 0x0000018c, 0x00000318}, + {0x000010b0, 0x00000fd0, 0x00001fa0}, + {0x00008014, 0x044c044c, 0x08980898}, + {0x0000801c, 0x148ec02b, 0x148ec057}, + {0x00008318, 0x000044c0, 0x00008980}, + {0x00009820, 0x02020200, 0x02020200}, + {0x00009824, 0x01000f0f, 0x01000f0f}, + {0x00009828, 0x0b020001, 0x0b020001}, + {0x00009834, 0x00000f0f, 0x00000f0f}, + {0x00009844, 0x03721821, 0x03721821}, + {0x00009914, 0x00000898, 0x00001130}, + {0x00009918, 0x0000000b, 0x00000016}, +}; + +static const u32 ar9280Modes_backoff_23db_rxgain_9280_2[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009a00, 0x00008184, 0x00008184, 0x00000290, 0x00000290}, + {0x00009a04, 0x00008188, 0x00008188, 0x00000300, 0x00000300}, + {0x00009a08, 0x0000818c, 0x0000818c, 0x00000304, 0x00000304}, + {0x00009a0c, 0x00008190, 0x00008190, 0x00000308, 0x00000308}, + {0x00009a10, 0x00008194, 0x00008194, 0x0000030c, 0x0000030c}, + {0x00009a14, 0x00008200, 0x00008200, 0x00008000, 0x00008000}, + {0x00009a18, 0x00008204, 0x00008204, 0x00008004, 0x00008004}, + {0x00009a1c, 0x00008208, 0x00008208, 0x00008008, 0x00008008}, + {0x00009a20, 0x0000820c, 0x0000820c, 0x0000800c, 0x0000800c}, + {0x00009a24, 0x00008210, 0x00008210, 0x00008080, 0x00008080}, + {0x00009a28, 0x00008214, 0x00008214, 0x00008084, 0x00008084}, + {0x00009a2c, 0x00008280, 0x00008280, 0x00008088, 0x00008088}, + {0x00009a30, 0x00008284, 0x00008284, 0x0000808c, 0x0000808c}, + {0x00009a34, 0x00008288, 0x00008288, 0x00008100, 0x00008100}, + {0x00009a38, 0x0000828c, 0x0000828c, 0x00008104, 0x00008104}, + {0x00009a3c, 0x00008290, 0x00008290, 0x00008108, 0x00008108}, + {0x00009a40, 0x00008300, 0x00008300, 0x0000810c, 0x0000810c}, + {0x00009a44, 0x00008304, 0x00008304, 0x00008110, 0x00008110}, + {0x00009a48, 0x00008308, 0x00008308, 0x00008114, 0x00008114}, + {0x00009a4c, 0x0000830c, 0x0000830c, 0x00008180, 0x00008180}, + {0x00009a50, 0x00008310, 0x00008310, 0x00008184, 0x00008184}, + {0x00009a54, 0x00008314, 0x00008314, 0x00008188, 0x00008188}, + {0x00009a58, 0x00008380, 0x00008380, 0x0000818c, 0x0000818c}, + {0x00009a5c, 0x00008384, 0x00008384, 0x00008190, 0x00008190}, + {0x00009a60, 0x00008388, 0x00008388, 0x00008194, 0x00008194}, + {0x00009a64, 0x0000838c, 0x0000838c, 0x000081a0, 0x000081a0}, + {0x00009a68, 0x00008390, 0x00008390, 0x0000820c, 0x0000820c}, + {0x00009a6c, 0x00008394, 0x00008394, 0x000081a8, 0x000081a8}, + {0x00009a70, 0x0000a380, 0x0000a380, 0x00008284, 0x00008284}, + {0x00009a74, 0x0000a384, 0x0000a384, 0x00008288, 0x00008288}, + {0x00009a78, 0x0000a388, 0x0000a388, 0x00008224, 0x00008224}, + {0x00009a7c, 0x0000a38c, 0x0000a38c, 0x00008290, 0x00008290}, + {0x00009a80, 0x0000a390, 0x0000a390, 0x00008300, 0x00008300}, + {0x00009a84, 0x0000a394, 0x0000a394, 0x00008304, 0x00008304}, + {0x00009a88, 0x0000a780, 0x0000a780, 0x00008308, 0x00008308}, + {0x00009a8c, 0x0000a784, 0x0000a784, 0x0000830c, 0x0000830c}, + {0x00009a90, 0x0000a788, 0x0000a788, 0x00008380, 0x00008380}, + {0x00009a94, 0x0000a78c, 0x0000a78c, 0x00008384, 0x00008384}, + {0x00009a98, 0x0000a790, 0x0000a790, 0x00008700, 0x00008700}, + {0x00009a9c, 0x0000a794, 0x0000a794, 0x00008704, 0x00008704}, + {0x00009aa0, 0x0000ab84, 0x0000ab84, 0x00008708, 0x00008708}, + {0x00009aa4, 0x0000ab88, 0x0000ab88, 0x0000870c, 0x0000870c}, + {0x00009aa8, 0x0000ab8c, 0x0000ab8c, 0x00008780, 0x00008780}, + {0x00009aac, 0x0000ab90, 0x0000ab90, 0x00008784, 0x00008784}, + {0x00009ab0, 0x0000ab94, 0x0000ab94, 0x00008b00, 0x00008b00}, + {0x00009ab4, 0x0000af80, 0x0000af80, 0x00008b04, 0x00008b04}, + {0x00009ab8, 0x0000af84, 0x0000af84, 0x00008b08, 0x00008b08}, + {0x00009abc, 0x0000af88, 0x0000af88, 0x00008b0c, 0x00008b0c}, + {0x00009ac0, 0x0000af8c, 0x0000af8c, 0x00008b10, 0x00008b10}, + {0x00009ac4, 0x0000af90, 0x0000af90, 0x00008b80, 0x00008b80}, + {0x00009ac8, 0x0000af94, 0x0000af94, 0x00008b84, 0x00008b84}, + {0x00009acc, 0x0000b380, 0x0000b380, 0x00008b88, 0x00008b88}, + {0x00009ad0, 0x0000b384, 0x0000b384, 0x00008b8c, 0x00008b8c}, + {0x00009ad4, 0x0000b388, 0x0000b388, 0x00008b90, 0x00008b90}, + {0x00009ad8, 0x0000b38c, 0x0000b38c, 0x00008b94, 0x00008b94}, + {0x00009adc, 0x0000b390, 0x0000b390, 0x00008b98, 0x00008b98}, + {0x00009ae0, 0x0000b394, 0x0000b394, 0x00008ba4, 0x00008ba4}, + {0x00009ae4, 0x0000b398, 0x0000b398, 0x00008ba8, 0x00008ba8}, + {0x00009ae8, 0x0000b780, 0x0000b780, 0x00008bac, 0x00008bac}, + {0x00009aec, 0x0000b784, 0x0000b784, 0x00008bb0, 0x00008bb0}, + {0x00009af0, 0x0000b788, 0x0000b788, 0x00008bb4, 0x00008bb4}, + {0x00009af4, 0x0000b78c, 0x0000b78c, 0x00008ba1, 0x00008ba1}, + {0x00009af8, 0x0000b790, 0x0000b790, 0x00008ba5, 0x00008ba5}, + {0x00009afc, 0x0000b794, 0x0000b794, 0x00008ba9, 0x00008ba9}, + {0x00009b00, 0x0000b798, 0x0000b798, 0x00008bad, 0x00008bad}, + {0x00009b04, 0x0000d784, 0x0000d784, 0x00008bb1, 0x00008bb1}, + {0x00009b08, 0x0000d788, 0x0000d788, 0x00008bb5, 0x00008bb5}, + {0x00009b0c, 0x0000d78c, 0x0000d78c, 0x00008ba2, 0x00008ba2}, + {0x00009b10, 0x0000d790, 0x0000d790, 0x00008ba6, 0x00008ba6}, + {0x00009b14, 0x0000f780, 0x0000f780, 0x00008baa, 0x00008baa}, + {0x00009b18, 0x0000f784, 0x0000f784, 0x00008bae, 0x00008bae}, + {0x00009b1c, 0x0000f788, 0x0000f788, 0x00008bb2, 0x00008bb2}, + {0x00009b20, 0x0000f78c, 0x0000f78c, 0x00008bb6, 0x00008bb6}, + {0x00009b24, 0x0000f790, 0x0000f790, 0x00008ba3, 0x00008ba3}, + {0x00009b28, 0x0000f794, 0x0000f794, 0x00008ba7, 0x00008ba7}, + {0x00009b2c, 0x0000f7a4, 0x0000f7a4, 0x00008bab, 0x00008bab}, + {0x00009b30, 0x0000f7a8, 0x0000f7a8, 0x00008baf, 0x00008baf}, + {0x00009b34, 0x0000f7ac, 0x0000f7ac, 0x00008bb3, 0x00008bb3}, + {0x00009b38, 0x0000f7b0, 0x0000f7b0, 0x00008bb7, 0x00008bb7}, + {0x00009b3c, 0x0000f7b4, 0x0000f7b4, 0x00008bc3, 0x00008bc3}, + {0x00009b40, 0x0000f7a1, 0x0000f7a1, 0x00008bc7, 0x00008bc7}, + {0x00009b44, 0x0000f7a5, 0x0000f7a5, 0x00008bcb, 0x00008bcb}, + {0x00009b48, 0x0000f7a9, 0x0000f7a9, 0x00008bcf, 0x00008bcf}, + {0x00009b4c, 0x0000f7ad, 0x0000f7ad, 0x00008bd3, 0x00008bd3}, + {0x00009b50, 0x0000f7b1, 0x0000f7b1, 0x00008bd7, 0x00008bd7}, + {0x00009b54, 0x0000f7b5, 0x0000f7b5, 0x00008bdb, 0x00008bdb}, + {0x00009b58, 0x0000f7c5, 0x0000f7c5, 0x00008bdb, 0x00008bdb}, + {0x00009b5c, 0x0000f7c9, 0x0000f7c9, 0x00008bdb, 0x00008bdb}, + {0x00009b60, 0x0000f7cd, 0x0000f7cd, 0x00008bdb, 0x00008bdb}, + {0x00009b64, 0x0000f7d1, 0x0000f7d1, 0x00008bdb, 0x00008bdb}, + {0x00009b68, 0x0000f7d5, 0x0000f7d5, 0x00008bdb, 0x00008bdb}, + {0x00009b6c, 0x0000f7c2, 0x0000f7c2, 0x00008bdb, 0x00008bdb}, + {0x00009b70, 0x0000f7c6, 0x0000f7c6, 0x00008bdb, 0x00008bdb}, + {0x00009b74, 0x0000f7ca, 0x0000f7ca, 0x00008bdb, 0x00008bdb}, + {0x00009b78, 0x0000f7ce, 0x0000f7ce, 0x00008bdb, 0x00008bdb}, + {0x00009b7c, 0x0000f7d2, 0x0000f7d2, 0x00008bdb, 0x00008bdb}, + {0x00009b80, 0x0000f7d6, 0x0000f7d6, 0x00008bdb, 0x00008bdb}, + {0x00009b84, 0x0000f7c3, 0x0000f7c3, 0x00008bdb, 0x00008bdb}, + {0x00009b88, 0x0000f7c7, 0x0000f7c7, 0x00008bdb, 0x00008bdb}, + {0x00009b8c, 0x0000f7cb, 0x0000f7cb, 0x00008bdb, 0x00008bdb}, + {0x00009b90, 0x0000f7d3, 0x0000f7d3, 0x00008bdb, 0x00008bdb}, + {0x00009b94, 0x0000f7d7, 0x0000f7d7, 0x00008bdb, 0x00008bdb}, + {0x00009b98, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009b9c, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009ba0, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009ba4, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009ba8, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bac, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bb0, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bb4, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bb8, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bbc, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bc0, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bc4, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bc8, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bcc, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bd0, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bd4, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bd8, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bdc, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009be0, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009be4, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009be8, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bec, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bf0, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bf4, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bf8, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009bfc, 0x0000f7db, 0x0000f7db, 0x00008bdb, 0x00008bdb}, + {0x00009848, 0x00001066, 0x00001066, 0x00001055, 0x00001055}, + {0x0000a848, 0x00001066, 0x00001066, 0x00001055, 0x00001055}, +}; + +static const u32 ar9280Modes_original_rxgain_9280_2[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009a00, 0x00008184, 0x00008184, 0x00008000, 0x00008000}, + {0x00009a04, 0x00008188, 0x00008188, 0x00008000, 0x00008000}, + {0x00009a08, 0x0000818c, 0x0000818c, 0x00008000, 0x00008000}, + {0x00009a0c, 0x00008190, 0x00008190, 0x00008000, 0x00008000}, + {0x00009a10, 0x00008194, 0x00008194, 0x00008000, 0x00008000}, + {0x00009a14, 0x00008200, 0x00008200, 0x00008000, 0x00008000}, + {0x00009a18, 0x00008204, 0x00008204, 0x00008004, 0x00008004}, + {0x00009a1c, 0x00008208, 0x00008208, 0x00008008, 0x00008008}, + {0x00009a20, 0x0000820c, 0x0000820c, 0x0000800c, 0x0000800c}, + {0x00009a24, 0x00008210, 0x00008210, 0x00008080, 0x00008080}, + {0x00009a28, 0x00008214, 0x00008214, 0x00008084, 0x00008084}, + {0x00009a2c, 0x00008280, 0x00008280, 0x00008088, 0x00008088}, + {0x00009a30, 0x00008284, 0x00008284, 0x0000808c, 0x0000808c}, + {0x00009a34, 0x00008288, 0x00008288, 0x00008100, 0x00008100}, + {0x00009a38, 0x0000828c, 0x0000828c, 0x00008104, 0x00008104}, + {0x00009a3c, 0x00008290, 0x00008290, 0x00008108, 0x00008108}, + {0x00009a40, 0x00008300, 0x00008300, 0x0000810c, 0x0000810c}, + {0x00009a44, 0x00008304, 0x00008304, 0x00008110, 0x00008110}, + {0x00009a48, 0x00008308, 0x00008308, 0x00008114, 0x00008114}, + {0x00009a4c, 0x0000830c, 0x0000830c, 0x00008180, 0x00008180}, + {0x00009a50, 0x00008310, 0x00008310, 0x00008184, 0x00008184}, + {0x00009a54, 0x00008314, 0x00008314, 0x00008188, 0x00008188}, + {0x00009a58, 0x00008380, 0x00008380, 0x0000818c, 0x0000818c}, + {0x00009a5c, 0x00008384, 0x00008384, 0x00008190, 0x00008190}, + {0x00009a60, 0x00008388, 0x00008388, 0x00008194, 0x00008194}, + {0x00009a64, 0x0000838c, 0x0000838c, 0x000081a0, 0x000081a0}, + {0x00009a68, 0x00008390, 0x00008390, 0x0000820c, 0x0000820c}, + {0x00009a6c, 0x00008394, 0x00008394, 0x000081a8, 0x000081a8}, + {0x00009a70, 0x0000a380, 0x0000a380, 0x00008284, 0x00008284}, + {0x00009a74, 0x0000a384, 0x0000a384, 0x00008288, 0x00008288}, + {0x00009a78, 0x0000a388, 0x0000a388, 0x00008224, 0x00008224}, + {0x00009a7c, 0x0000a38c, 0x0000a38c, 0x00008290, 0x00008290}, + {0x00009a80, 0x0000a390, 0x0000a390, 0x00008300, 0x00008300}, + {0x00009a84, 0x0000a394, 0x0000a394, 0x00008304, 0x00008304}, + {0x00009a88, 0x0000a780, 0x0000a780, 0x00008308, 0x00008308}, + {0x00009a8c, 0x0000a784, 0x0000a784, 0x0000830c, 0x0000830c}, + {0x00009a90, 0x0000a788, 0x0000a788, 0x00008380, 0x00008380}, + {0x00009a94, 0x0000a78c, 0x0000a78c, 0x00008384, 0x00008384}, + {0x00009a98, 0x0000a790, 0x0000a790, 0x00008700, 0x00008700}, + {0x00009a9c, 0x0000a794, 0x0000a794, 0x00008704, 0x00008704}, + {0x00009aa0, 0x0000ab84, 0x0000ab84, 0x00008708, 0x00008708}, + {0x00009aa4, 0x0000ab88, 0x0000ab88, 0x0000870c, 0x0000870c}, + {0x00009aa8, 0x0000ab8c, 0x0000ab8c, 0x00008780, 0x00008780}, + {0x00009aac, 0x0000ab90, 0x0000ab90, 0x00008784, 0x00008784}, + {0x00009ab0, 0x0000ab94, 0x0000ab94, 0x00008b00, 0x00008b00}, + {0x00009ab4, 0x0000af80, 0x0000af80, 0x00008b04, 0x00008b04}, + {0x00009ab8, 0x0000af84, 0x0000af84, 0x00008b08, 0x00008b08}, + {0x00009abc, 0x0000af88, 0x0000af88, 0x00008b0c, 0x00008b0c}, + {0x00009ac0, 0x0000af8c, 0x0000af8c, 0x00008b80, 0x00008b80}, + {0x00009ac4, 0x0000af90, 0x0000af90, 0x00008b84, 0x00008b84}, + {0x00009ac8, 0x0000af94, 0x0000af94, 0x00008b88, 0x00008b88}, + {0x00009acc, 0x0000b380, 0x0000b380, 0x00008b8c, 0x00008b8c}, + {0x00009ad0, 0x0000b384, 0x0000b384, 0x00008b90, 0x00008b90}, + {0x00009ad4, 0x0000b388, 0x0000b388, 0x00008f80, 0x00008f80}, + {0x00009ad8, 0x0000b38c, 0x0000b38c, 0x00008f84, 0x00008f84}, + {0x00009adc, 0x0000b390, 0x0000b390, 0x00008f88, 0x00008f88}, + {0x00009ae0, 0x0000b394, 0x0000b394, 0x00008f8c, 0x00008f8c}, + {0x00009ae4, 0x0000b398, 0x0000b398, 0x00008f90, 0x00008f90}, + {0x00009ae8, 0x0000b780, 0x0000b780, 0x0000930c, 0x0000930c}, + {0x00009aec, 0x0000b784, 0x0000b784, 0x00009310, 0x00009310}, + {0x00009af0, 0x0000b788, 0x0000b788, 0x00009384, 0x00009384}, + {0x00009af4, 0x0000b78c, 0x0000b78c, 0x00009388, 0x00009388}, + {0x00009af8, 0x0000b790, 0x0000b790, 0x00009324, 0x00009324}, + {0x00009afc, 0x0000b794, 0x0000b794, 0x00009704, 0x00009704}, + {0x00009b00, 0x0000b798, 0x0000b798, 0x000096a4, 0x000096a4}, + {0x00009b04, 0x0000d784, 0x0000d784, 0x000096a8, 0x000096a8}, + {0x00009b08, 0x0000d788, 0x0000d788, 0x00009710, 0x00009710}, + {0x00009b0c, 0x0000d78c, 0x0000d78c, 0x00009714, 0x00009714}, + {0x00009b10, 0x0000d790, 0x0000d790, 0x00009720, 0x00009720}, + {0x00009b14, 0x0000f780, 0x0000f780, 0x00009724, 0x00009724}, + {0x00009b18, 0x0000f784, 0x0000f784, 0x00009728, 0x00009728}, + {0x00009b1c, 0x0000f788, 0x0000f788, 0x0000972c, 0x0000972c}, + {0x00009b20, 0x0000f78c, 0x0000f78c, 0x000097a0, 0x000097a0}, + {0x00009b24, 0x0000f790, 0x0000f790, 0x000097a4, 0x000097a4}, + {0x00009b28, 0x0000f794, 0x0000f794, 0x000097a8, 0x000097a8}, + {0x00009b2c, 0x0000f7a4, 0x0000f7a4, 0x000097b0, 0x000097b0}, + {0x00009b30, 0x0000f7a8, 0x0000f7a8, 0x000097b4, 0x000097b4}, + {0x00009b34, 0x0000f7ac, 0x0000f7ac, 0x000097b8, 0x000097b8}, + {0x00009b38, 0x0000f7b0, 0x0000f7b0, 0x000097a5, 0x000097a5}, + {0x00009b3c, 0x0000f7b4, 0x0000f7b4, 0x000097a9, 0x000097a9}, + {0x00009b40, 0x0000f7a1, 0x0000f7a1, 0x000097ad, 0x000097ad}, + {0x00009b44, 0x0000f7a5, 0x0000f7a5, 0x000097b1, 0x000097b1}, + {0x00009b48, 0x0000f7a9, 0x0000f7a9, 0x000097b5, 0x000097b5}, + {0x00009b4c, 0x0000f7ad, 0x0000f7ad, 0x000097b9, 0x000097b9}, + {0x00009b50, 0x0000f7b1, 0x0000f7b1, 0x000097c5, 0x000097c5}, + {0x00009b54, 0x0000f7b5, 0x0000f7b5, 0x000097c9, 0x000097c9}, + {0x00009b58, 0x0000f7c5, 0x0000f7c5, 0x000097d1, 0x000097d1}, + {0x00009b5c, 0x0000f7c9, 0x0000f7c9, 0x000097d5, 0x000097d5}, + {0x00009b60, 0x0000f7cd, 0x0000f7cd, 0x000097d9, 0x000097d9}, + {0x00009b64, 0x0000f7d1, 0x0000f7d1, 0x000097c6, 0x000097c6}, + {0x00009b68, 0x0000f7d5, 0x0000f7d5, 0x000097ca, 0x000097ca}, + {0x00009b6c, 0x0000f7c2, 0x0000f7c2, 0x000097ce, 0x000097ce}, + {0x00009b70, 0x0000f7c6, 0x0000f7c6, 0x000097d2, 0x000097d2}, + {0x00009b74, 0x0000f7ca, 0x0000f7ca, 0x000097d6, 0x000097d6}, + {0x00009b78, 0x0000f7ce, 0x0000f7ce, 0x000097c3, 0x000097c3}, + {0x00009b7c, 0x0000f7d2, 0x0000f7d2, 0x000097c7, 0x000097c7}, + {0x00009b80, 0x0000f7d6, 0x0000f7d6, 0x000097cb, 0x000097cb}, + {0x00009b84, 0x0000f7c3, 0x0000f7c3, 0x000097cf, 0x000097cf}, + {0x00009b88, 0x0000f7c7, 0x0000f7c7, 0x000097d7, 0x000097d7}, + {0x00009b8c, 0x0000f7cb, 0x0000f7cb, 0x000097db, 0x000097db}, + {0x00009b90, 0x0000f7d3, 0x0000f7d3, 0x000097db, 0x000097db}, + {0x00009b94, 0x0000f7d7, 0x0000f7d7, 0x000097db, 0x000097db}, + {0x00009b98, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009b9c, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009ba0, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009ba4, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009ba8, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bac, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bb0, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bb4, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bb8, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bbc, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bc0, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bc4, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bc8, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bcc, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bd0, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bd4, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bd8, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bdc, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009be0, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009be4, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009be8, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bec, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bf0, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bf4, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bf8, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009bfc, 0x0000f7db, 0x0000f7db, 0x000097db, 0x000097db}, + {0x00009848, 0x00001066, 0x00001066, 0x00001063, 0x00001063}, + {0x0000a848, 0x00001066, 0x00001066, 0x00001063, 0x00001063}, +}; + +static const u32 ar9280Modes_backoff_13db_rxgain_9280_2[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009a00, 0x00008184, 0x00008184, 0x00000290, 0x00000290}, + {0x00009a04, 0x00008188, 0x00008188, 0x00000300, 0x00000300}, + {0x00009a08, 0x0000818c, 0x0000818c, 0x00000304, 0x00000304}, + {0x00009a0c, 0x00008190, 0x00008190, 0x00000308, 0x00000308}, + {0x00009a10, 0x00008194, 0x00008194, 0x0000030c, 0x0000030c}, + {0x00009a14, 0x00008200, 0x00008200, 0x00008000, 0x00008000}, + {0x00009a18, 0x00008204, 0x00008204, 0x00008004, 0x00008004}, + {0x00009a1c, 0x00008208, 0x00008208, 0x00008008, 0x00008008}, + {0x00009a20, 0x0000820c, 0x0000820c, 0x0000800c, 0x0000800c}, + {0x00009a24, 0x00008210, 0x00008210, 0x00008080, 0x00008080}, + {0x00009a28, 0x00008214, 0x00008214, 0x00008084, 0x00008084}, + {0x00009a2c, 0x00008280, 0x00008280, 0x00008088, 0x00008088}, + {0x00009a30, 0x00008284, 0x00008284, 0x0000808c, 0x0000808c}, + {0x00009a34, 0x00008288, 0x00008288, 0x00008100, 0x00008100}, + {0x00009a38, 0x0000828c, 0x0000828c, 0x00008104, 0x00008104}, + {0x00009a3c, 0x00008290, 0x00008290, 0x00008108, 0x00008108}, + {0x00009a40, 0x00008300, 0x00008300, 0x0000810c, 0x0000810c}, + {0x00009a44, 0x00008304, 0x00008304, 0x00008110, 0x00008110}, + {0x00009a48, 0x00008308, 0x00008308, 0x00008114, 0x00008114}, + {0x00009a4c, 0x0000830c, 0x0000830c, 0x00008180, 0x00008180}, + {0x00009a50, 0x00008310, 0x00008310, 0x00008184, 0x00008184}, + {0x00009a54, 0x00008314, 0x00008314, 0x00008188, 0x00008188}, + {0x00009a58, 0x00008380, 0x00008380, 0x0000818c, 0x0000818c}, + {0x00009a5c, 0x00008384, 0x00008384, 0x00008190, 0x00008190}, + {0x00009a60, 0x00008388, 0x00008388, 0x00008194, 0x00008194}, + {0x00009a64, 0x0000838c, 0x0000838c, 0x000081a0, 0x000081a0}, + {0x00009a68, 0x00008390, 0x00008390, 0x0000820c, 0x0000820c}, + {0x00009a6c, 0x00008394, 0x00008394, 0x000081a8, 0x000081a8}, + {0x00009a70, 0x0000a380, 0x0000a380, 0x00008284, 0x00008284}, + {0x00009a74, 0x0000a384, 0x0000a384, 0x00008288, 0x00008288}, + {0x00009a78, 0x0000a388, 0x0000a388, 0x00008224, 0x00008224}, + {0x00009a7c, 0x0000a38c, 0x0000a38c, 0x00008290, 0x00008290}, + {0x00009a80, 0x0000a390, 0x0000a390, 0x00008300, 0x00008300}, + {0x00009a84, 0x0000a394, 0x0000a394, 0x00008304, 0x00008304}, + {0x00009a88, 0x0000a780, 0x0000a780, 0x00008308, 0x00008308}, + {0x00009a8c, 0x0000a784, 0x0000a784, 0x0000830c, 0x0000830c}, + {0x00009a90, 0x0000a788, 0x0000a788, 0x00008380, 0x00008380}, + {0x00009a94, 0x0000a78c, 0x0000a78c, 0x00008384, 0x00008384}, + {0x00009a98, 0x0000a790, 0x0000a790, 0x00008700, 0x00008700}, + {0x00009a9c, 0x0000a794, 0x0000a794, 0x00008704, 0x00008704}, + {0x00009aa0, 0x0000ab84, 0x0000ab84, 0x00008708, 0x00008708}, + {0x00009aa4, 0x0000ab88, 0x0000ab88, 0x0000870c, 0x0000870c}, + {0x00009aa8, 0x0000ab8c, 0x0000ab8c, 0x00008780, 0x00008780}, + {0x00009aac, 0x0000ab90, 0x0000ab90, 0x00008784, 0x00008784}, + {0x00009ab0, 0x0000ab94, 0x0000ab94, 0x00008b00, 0x00008b00}, + {0x00009ab4, 0x0000af80, 0x0000af80, 0x00008b04, 0x00008b04}, + {0x00009ab8, 0x0000af84, 0x0000af84, 0x00008b08, 0x00008b08}, + {0x00009abc, 0x0000af88, 0x0000af88, 0x00008b0c, 0x00008b0c}, + {0x00009ac0, 0x0000af8c, 0x0000af8c, 0x00008b80, 0x00008b80}, + {0x00009ac4, 0x0000af90, 0x0000af90, 0x00008b84, 0x00008b84}, + {0x00009ac8, 0x0000af94, 0x0000af94, 0x00008b88, 0x00008b88}, + {0x00009acc, 0x0000b380, 0x0000b380, 0x00008b8c, 0x00008b8c}, + {0x00009ad0, 0x0000b384, 0x0000b384, 0x00008b90, 0x00008b90}, + {0x00009ad4, 0x0000b388, 0x0000b388, 0x00008f80, 0x00008f80}, + {0x00009ad8, 0x0000b38c, 0x0000b38c, 0x00008f84, 0x00008f84}, + {0x00009adc, 0x0000b390, 0x0000b390, 0x00008f88, 0x00008f88}, + {0x00009ae0, 0x0000b394, 0x0000b394, 0x00008f8c, 0x00008f8c}, + {0x00009ae4, 0x0000b398, 0x0000b398, 0x00008f90, 0x00008f90}, + {0x00009ae8, 0x0000b780, 0x0000b780, 0x00009310, 0x00009310}, + {0x00009aec, 0x0000b784, 0x0000b784, 0x00009314, 0x00009314}, + {0x00009af0, 0x0000b788, 0x0000b788, 0x00009320, 0x00009320}, + {0x00009af4, 0x0000b78c, 0x0000b78c, 0x00009324, 0x00009324}, + {0x00009af8, 0x0000b790, 0x0000b790, 0x00009328, 0x00009328}, + {0x00009afc, 0x0000b794, 0x0000b794, 0x0000932c, 0x0000932c}, + {0x00009b00, 0x0000b798, 0x0000b798, 0x00009330, 0x00009330}, + {0x00009b04, 0x0000d784, 0x0000d784, 0x00009334, 0x00009334}, + {0x00009b08, 0x0000d788, 0x0000d788, 0x00009321, 0x00009321}, + {0x00009b0c, 0x0000d78c, 0x0000d78c, 0x00009325, 0x00009325}, + {0x00009b10, 0x0000d790, 0x0000d790, 0x00009329, 0x00009329}, + {0x00009b14, 0x0000f780, 0x0000f780, 0x0000932d, 0x0000932d}, + {0x00009b18, 0x0000f784, 0x0000f784, 0x00009331, 0x00009331}, + {0x00009b1c, 0x0000f788, 0x0000f788, 0x00009335, 0x00009335}, + {0x00009b20, 0x0000f78c, 0x0000f78c, 0x00009322, 0x00009322}, + {0x00009b24, 0x0000f790, 0x0000f790, 0x00009326, 0x00009326}, + {0x00009b28, 0x0000f794, 0x0000f794, 0x0000932a, 0x0000932a}, + {0x00009b2c, 0x0000f7a4, 0x0000f7a4, 0x0000932e, 0x0000932e}, + {0x00009b30, 0x0000f7a8, 0x0000f7a8, 0x00009332, 0x00009332}, + {0x00009b34, 0x0000f7ac, 0x0000f7ac, 0x00009336, 0x00009336}, + {0x00009b38, 0x0000f7b0, 0x0000f7b0, 0x00009323, 0x00009323}, + {0x00009b3c, 0x0000f7b4, 0x0000f7b4, 0x00009327, 0x00009327}, + {0x00009b40, 0x0000f7a1, 0x0000f7a1, 0x0000932b, 0x0000932b}, + {0x00009b44, 0x0000f7a5, 0x0000f7a5, 0x0000932f, 0x0000932f}, + {0x00009b48, 0x0000f7a9, 0x0000f7a9, 0x00009333, 0x00009333}, + {0x00009b4c, 0x0000f7ad, 0x0000f7ad, 0x00009337, 0x00009337}, + {0x00009b50, 0x0000f7b1, 0x0000f7b1, 0x00009343, 0x00009343}, + {0x00009b54, 0x0000f7b5, 0x0000f7b5, 0x00009347, 0x00009347}, + {0x00009b58, 0x0000f7c5, 0x0000f7c5, 0x0000934b, 0x0000934b}, + {0x00009b5c, 0x0000f7c9, 0x0000f7c9, 0x0000934f, 0x0000934f}, + {0x00009b60, 0x0000f7cd, 0x0000f7cd, 0x00009353, 0x00009353}, + {0x00009b64, 0x0000f7d1, 0x0000f7d1, 0x00009357, 0x00009357}, + {0x00009b68, 0x0000f7d5, 0x0000f7d5, 0x0000935b, 0x0000935b}, + {0x00009b6c, 0x0000f7c2, 0x0000f7c2, 0x0000935b, 0x0000935b}, + {0x00009b70, 0x0000f7c6, 0x0000f7c6, 0x0000935b, 0x0000935b}, + {0x00009b74, 0x0000f7ca, 0x0000f7ca, 0x0000935b, 0x0000935b}, + {0x00009b78, 0x0000f7ce, 0x0000f7ce, 0x0000935b, 0x0000935b}, + {0x00009b7c, 0x0000f7d2, 0x0000f7d2, 0x0000935b, 0x0000935b}, + {0x00009b80, 0x0000f7d6, 0x0000f7d6, 0x0000935b, 0x0000935b}, + {0x00009b84, 0x0000f7c3, 0x0000f7c3, 0x0000935b, 0x0000935b}, + {0x00009b88, 0x0000f7c7, 0x0000f7c7, 0x0000935b, 0x0000935b}, + {0x00009b8c, 0x0000f7cb, 0x0000f7cb, 0x0000935b, 0x0000935b}, + {0x00009b90, 0x0000f7d3, 0x0000f7d3, 0x0000935b, 0x0000935b}, + {0x00009b94, 0x0000f7d7, 0x0000f7d7, 0x0000935b, 0x0000935b}, + {0x00009b98, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009b9c, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009ba0, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009ba4, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009ba8, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bac, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bb0, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bb4, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bb8, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bbc, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bc0, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bc4, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bc8, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bcc, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bd0, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bd4, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bd8, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bdc, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009be0, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009be4, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009be8, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bec, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bf0, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bf4, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bf8, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009bfc, 0x0000f7db, 0x0000f7db, 0x0000935b, 0x0000935b}, + {0x00009848, 0x00001066, 0x00001066, 0x0000105a, 0x0000105a}, + {0x0000a848, 0x00001066, 0x00001066, 0x0000105a, 0x0000105a}, +}; + +static const u32 ar9280Modes_high_power_tx_gain_9280_2[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a274, 0x0a19e652, 0x0a19e652, 0x0a1aa652, 0x0a1aa652}, + {0x0000a27c, 0x050739ce, 0x050739ce, 0x050739ce, 0x050739ce}, + {0x0000a300, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a304, 0x00003002, 0x00003002, 0x00004002, 0x00004002}, + {0x0000a308, 0x00006004, 0x00006004, 0x00007008, 0x00007008}, + {0x0000a30c, 0x0000a006, 0x0000a006, 0x0000c010, 0x0000c010}, + {0x0000a310, 0x0000e012, 0x0000e012, 0x00010012, 0x00010012}, + {0x0000a314, 0x00011014, 0x00011014, 0x00013014, 0x00013014}, + {0x0000a318, 0x0001504a, 0x0001504a, 0x0001820a, 0x0001820a}, + {0x0000a31c, 0x0001904c, 0x0001904c, 0x0001b211, 0x0001b211}, + {0x0000a320, 0x0001c04e, 0x0001c04e, 0x0001e213, 0x0001e213}, + {0x0000a324, 0x00021092, 0x00021092, 0x00022411, 0x00022411}, + {0x0000a328, 0x0002510a, 0x0002510a, 0x00025413, 0x00025413}, + {0x0000a32c, 0x0002910c, 0x0002910c, 0x00029811, 0x00029811}, + {0x0000a330, 0x0002c18b, 0x0002c18b, 0x0002c813, 0x0002c813}, + {0x0000a334, 0x0002f1cc, 0x0002f1cc, 0x00030a14, 0x00030a14}, + {0x0000a338, 0x000321eb, 0x000321eb, 0x00035a50, 0x00035a50}, + {0x0000a33c, 0x000341ec, 0x000341ec, 0x00039c4c, 0x00039c4c}, + {0x0000a340, 0x000341ec, 0x000341ec, 0x0003de8a, 0x0003de8a}, + {0x0000a344, 0x000341ec, 0x000341ec, 0x00042e92, 0x00042e92}, + {0x0000a348, 0x000341ec, 0x000341ec, 0x00046ed2, 0x00046ed2}, + {0x0000a34c, 0x000341ec, 0x000341ec, 0x0004bed5, 0x0004bed5}, + {0x0000a350, 0x000341ec, 0x000341ec, 0x0004ff54, 0x0004ff54}, + {0x0000a354, 0x000341ec, 0x000341ec, 0x00055fd5, 0x00055fd5}, + {0x0000a3ec, 0x00f70081, 0x00f70081, 0x00f70081, 0x00f70081}, + {0x00007814, 0x00198eff, 0x00198eff, 0x00198eff, 0x00198eff}, + {0x00007838, 0x00198eff, 0x00198eff, 0x00198eff, 0x00198eff}, + {0x0000781c, 0x00172000, 0x00172000, 0x00172000, 0x00172000}, + {0x00007840, 0x00172000, 0x00172000, 0x00172000, 0x00172000}, + {0x00007820, 0xf258a480, 0xf258a480, 0xf258a480, 0xf258a480}, + {0x00007844, 0xf258a480, 0xf258a480, 0xf258a480, 0xf258a480}, +}; + +static const u32 ar9280Modes_original_tx_gain_9280_2[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a274, 0x0a19c652, 0x0a19c652, 0x0a1aa652, 0x0a1aa652}, + {0x0000a27c, 0x050701ce, 0x050701ce, 0x050701ce, 0x050701ce}, + {0x0000a300, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a304, 0x00003002, 0x00003002, 0x00003002, 0x00003002}, + {0x0000a308, 0x00006004, 0x00006004, 0x00008009, 0x00008009}, + {0x0000a30c, 0x0000a006, 0x0000a006, 0x0000b00b, 0x0000b00b}, + {0x0000a310, 0x0000e012, 0x0000e012, 0x0000e012, 0x0000e012}, + {0x0000a314, 0x00011014, 0x00011014, 0x00012048, 0x00012048}, + {0x0000a318, 0x0001504a, 0x0001504a, 0x0001604a, 0x0001604a}, + {0x0000a31c, 0x0001904c, 0x0001904c, 0x0001a211, 0x0001a211}, + {0x0000a320, 0x0001c04e, 0x0001c04e, 0x0001e213, 0x0001e213}, + {0x0000a324, 0x00020092, 0x00020092, 0x0002121b, 0x0002121b}, + {0x0000a328, 0x0002410a, 0x0002410a, 0x00024412, 0x00024412}, + {0x0000a32c, 0x0002710c, 0x0002710c, 0x00028414, 0x00028414}, + {0x0000a330, 0x0002b18b, 0x0002b18b, 0x0002b44a, 0x0002b44a}, + {0x0000a334, 0x0002e1cc, 0x0002e1cc, 0x00030649, 0x00030649}, + {0x0000a338, 0x000321ec, 0x000321ec, 0x0003364b, 0x0003364b}, + {0x0000a33c, 0x000321ec, 0x000321ec, 0x00038a49, 0x00038a49}, + {0x0000a340, 0x000321ec, 0x000321ec, 0x0003be48, 0x0003be48}, + {0x0000a344, 0x000321ec, 0x000321ec, 0x0003ee4a, 0x0003ee4a}, + {0x0000a348, 0x000321ec, 0x000321ec, 0x00042e88, 0x00042e88}, + {0x0000a34c, 0x000321ec, 0x000321ec, 0x00046e8a, 0x00046e8a}, + {0x0000a350, 0x000321ec, 0x000321ec, 0x00049ec9, 0x00049ec9}, + {0x0000a354, 0x000321ec, 0x000321ec, 0x0004bf42, 0x0004bf42}, + {0x0000a3ec, 0x00f70081, 0x00f70081, 0x00f70081, 0x00f70081}, + {0x00007814, 0x0019beff, 0x0019beff, 0x0019beff, 0x0019beff}, + {0x00007838, 0x0019beff, 0x0019beff, 0x0019beff, 0x0019beff}, + {0x0000781c, 0x00392000, 0x00392000, 0x00392000, 0x00392000}, + {0x00007840, 0x00392000, 0x00392000, 0x00392000, 0x00392000}, + {0x00007820, 0x92592480, 0x92592480, 0x92592480, 0x92592480}, + {0x00007844, 0x92592480, 0x92592480, 0x92592480, 0x92592480}, +}; + +static const u32 ar9280PciePhy_clkreq_off_L1_9280[][2] = { + /* Addr allmodes */ + {0x00004040, 0x9248fd00}, + {0x00004040, 0x24924924}, + {0x00004040, 0xa8000019}, + {0x00004040, 0x13160820}, + {0x00004040, 0xe5980560}, + {0x00004040, 0xc01dcffc}, + {0x00004040, 0x1aaabe41}, + {0x00004040, 0xbe105554}, + {0x00004040, 0x00043007}, + {0x00004044, 0x00000000}, +}; + +static const u32 ar9280PciePhy_clkreq_always_on_L1_9280[][2] = { + /* Addr allmodes */ + {0x00004040, 0x9248fd00}, + {0x00004040, 0x24924924}, + {0x00004040, 0xa8000019}, + {0x00004040, 0x13160820}, + {0x00004040, 0xe5980560}, + {0x00004040, 0xc01dcffd}, + {0x00004040, 0x1aaabe41}, + {0x00004040, 0xbe105554}, + {0x00004040, 0x00043007}, + {0x00004044, 0x00000000}, +}; + +static const u32 ar9280PciePhy_awow[][2] = { + /* Addr allmodes */ + {0x00004040, 0x9248fd00}, + {0x00004040, 0x24924924}, + {0x00004040, 0xa8000019}, + {0x00004040, 0x13160820}, + {0x00004040, 0xe5980560}, + {0x00004040, 0xc01dcffd}, + {0x00004040, 0x1aaabe41}, + {0x00004040, 0xbe105554}, + {0x00004040, 0x00043007}, + {0x00004044, 0x00000000}, +}; + +static const u32 ar9285Modes_9285_1_2[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00001030, 0x00000230, 0x00000460, 0x000002c0, 0x00000160}, + {0x00001070, 0x00000168, 0x000002d0, 0x00000318, 0x0000018c}, + {0x000010b0, 0x00000e60, 0x00001cc0, 0x00007c70, 0x00003e38}, + {0x000010f0, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00008014, 0x03e803e8, 0x07d007d0, 0x10801600, 0x08400b00}, + {0x0000801c, 0x128d8027, 0x128d804f, 0x12e00057, 0x12e0002b}, + {0x00008318, 0x00003e80, 0x00007d00, 0x00006880, 0x00003440}, + {0x00009804, 0x00000300, 0x000003c4, 0x000003c4, 0x00000300}, + {0x00009820, 0x02020200, 0x02020200, 0x02020200, 0x02020200}, + {0x00009824, 0x01000e0e, 0x01000e0e, 0x01000e0e, 0x01000e0e}, + {0x00009828, 0x0a020001, 0x0a020001, 0x0a020001, 0x0a020001}, + {0x00009834, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x00009838, 0x00000007, 0x00000007, 0x00000007, 0x00000007}, + {0x00009840, 0x206a012e, 0x206a012e, 0x206a012e, 0x206a012e}, + {0x00009844, 0x0372161e, 0x0372161e, 0x03721620, 0x03721620}, + {0x00009848, 0x00001066, 0x00001066, 0x00001053, 0x00001053}, + {0x0000a848, 0x00001066, 0x00001066, 0x00001053, 0x00001053}, + {0x00009850, 0x6d4000e2, 0x6d4000e2, 0x6d4000e2, 0x6d4000e2}, + {0x00009858, 0x7ec84d2e, 0x7ec84d2e, 0x7ec84d2e, 0x7ec84d2e}, + {0x0000985c, 0x3139605e, 0x3139605e, 0x3137605e, 0x3137605e}, + {0x00009860, 0x00058d18, 0x00058d18, 0x00058d20, 0x00058d20}, + {0x00009864, 0x0000fe00, 0x0000fe00, 0x0001ce00, 0x0001ce00}, + {0x00009868, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0}, + {0x0000986c, 0x06903081, 0x06903081, 0x06903881, 0x06903881}, + {0x00009914, 0x000007d0, 0x00000fa0, 0x00001130, 0x00000898}, + {0x00009918, 0x0000000a, 0x00000014, 0x00000016, 0x0000000b}, + {0x00009924, 0xd00a8007, 0xd00a8007, 0xd00a800d, 0xd00a800d}, + {0x00009944, 0xffbc1010, 0xffbc1010, 0xffbc1020, 0xffbc1020}, + {0x00009960, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00009964, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x000099b8, 0x0000421c, 0x0000421c, 0x0000421c, 0x0000421c}, + {0x000099bc, 0x00000600, 0x00000600, 0x00000c00, 0x00000c00}, + {0x000099c0, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4}, + {0x000099c4, 0x06336f77, 0x06336f77, 0x06336f77, 0x06336f77}, + {0x000099c8, 0x6af6532f, 0x6af6532f, 0x6af6532f, 0x6af6532f}, + {0x000099cc, 0x08f186c8, 0x08f186c8, 0x08f186c8, 0x08f186c8}, + {0x000099d0, 0x00046384, 0x00046384, 0x00046384, 0x00046384}, + {0x000099d4, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x000099d8, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00009a00, 0x00000000, 0x00000000, 0x00058084, 0x00058084}, + {0x00009a04, 0x00000000, 0x00000000, 0x00058088, 0x00058088}, + {0x00009a08, 0x00000000, 0x00000000, 0x0005808c, 0x0005808c}, + {0x00009a0c, 0x00000000, 0x00000000, 0x00058100, 0x00058100}, + {0x00009a10, 0x00000000, 0x00000000, 0x00058104, 0x00058104}, + {0x00009a14, 0x00000000, 0x00000000, 0x00058108, 0x00058108}, + {0x00009a18, 0x00000000, 0x00000000, 0x0005810c, 0x0005810c}, + {0x00009a1c, 0x00000000, 0x00000000, 0x00058110, 0x00058110}, + {0x00009a20, 0x00000000, 0x00000000, 0x00058114, 0x00058114}, + {0x00009a24, 0x00000000, 0x00000000, 0x00058180, 0x00058180}, + {0x00009a28, 0x00000000, 0x00000000, 0x00058184, 0x00058184}, + {0x00009a2c, 0x00000000, 0x00000000, 0x00058188, 0x00058188}, + {0x00009a30, 0x00000000, 0x00000000, 0x0005818c, 0x0005818c}, + {0x00009a34, 0x00000000, 0x00000000, 0x00058190, 0x00058190}, + {0x00009a38, 0x00000000, 0x00000000, 0x00058194, 0x00058194}, + {0x00009a3c, 0x00000000, 0x00000000, 0x000581a0, 0x000581a0}, + {0x00009a40, 0x00000000, 0x00000000, 0x0005820c, 0x0005820c}, + {0x00009a44, 0x00000000, 0x00000000, 0x000581a8, 0x000581a8}, + {0x00009a48, 0x00000000, 0x00000000, 0x00058284, 0x00058284}, + {0x00009a4c, 0x00000000, 0x00000000, 0x00058288, 0x00058288}, + {0x00009a50, 0x00000000, 0x00000000, 0x00058224, 0x00058224}, + {0x00009a54, 0x00000000, 0x00000000, 0x00058290, 0x00058290}, + {0x00009a58, 0x00000000, 0x00000000, 0x00058300, 0x00058300}, + {0x00009a5c, 0x00000000, 0x00000000, 0x00058304, 0x00058304}, + {0x00009a60, 0x00000000, 0x00000000, 0x00058308, 0x00058308}, + {0x00009a64, 0x00000000, 0x00000000, 0x0005830c, 0x0005830c}, + {0x00009a68, 0x00000000, 0x00000000, 0x00058380, 0x00058380}, + {0x00009a6c, 0x00000000, 0x00000000, 0x00058384, 0x00058384}, + {0x00009a70, 0x00000000, 0x00000000, 0x00068700, 0x00068700}, + {0x00009a74, 0x00000000, 0x00000000, 0x00068704, 0x00068704}, + {0x00009a78, 0x00000000, 0x00000000, 0x00068708, 0x00068708}, + {0x00009a7c, 0x00000000, 0x00000000, 0x0006870c, 0x0006870c}, + {0x00009a80, 0x00000000, 0x00000000, 0x00068780, 0x00068780}, + {0x00009a84, 0x00000000, 0x00000000, 0x00068784, 0x00068784}, + {0x00009a88, 0x00000000, 0x00000000, 0x00078b00, 0x00078b00}, + {0x00009a8c, 0x00000000, 0x00000000, 0x00078b04, 0x00078b04}, + {0x00009a90, 0x00000000, 0x00000000, 0x00078b08, 0x00078b08}, + {0x00009a94, 0x00000000, 0x00000000, 0x00078b0c, 0x00078b0c}, + {0x00009a98, 0x00000000, 0x00000000, 0x00078b80, 0x00078b80}, + {0x00009a9c, 0x00000000, 0x00000000, 0x00078b84, 0x00078b84}, + {0x00009aa0, 0x00000000, 0x00000000, 0x00078b88, 0x00078b88}, + {0x00009aa4, 0x00000000, 0x00000000, 0x00078b8c, 0x00078b8c}, + {0x00009aa8, 0x00000000, 0x00000000, 0x00078b90, 0x00078b90}, + {0x00009aac, 0x00000000, 0x00000000, 0x000caf80, 0x000caf80}, + {0x00009ab0, 0x00000000, 0x00000000, 0x000caf84, 0x000caf84}, + {0x00009ab4, 0x00000000, 0x00000000, 0x000caf88, 0x000caf88}, + {0x00009ab8, 0x00000000, 0x00000000, 0x000caf8c, 0x000caf8c}, + {0x00009abc, 0x00000000, 0x00000000, 0x000caf90, 0x000caf90}, + {0x00009ac0, 0x00000000, 0x00000000, 0x000db30c, 0x000db30c}, + {0x00009ac4, 0x00000000, 0x00000000, 0x000db310, 0x000db310}, + {0x00009ac8, 0x00000000, 0x00000000, 0x000db384, 0x000db384}, + {0x00009acc, 0x00000000, 0x00000000, 0x000db388, 0x000db388}, + {0x00009ad0, 0x00000000, 0x00000000, 0x000db324, 0x000db324}, + {0x00009ad4, 0x00000000, 0x00000000, 0x000eb704, 0x000eb704}, + {0x00009ad8, 0x00000000, 0x00000000, 0x000eb6a4, 0x000eb6a4}, + {0x00009adc, 0x00000000, 0x00000000, 0x000eb6a8, 0x000eb6a8}, + {0x00009ae0, 0x00000000, 0x00000000, 0x000eb710, 0x000eb710}, + {0x00009ae4, 0x00000000, 0x00000000, 0x000eb714, 0x000eb714}, + {0x00009ae8, 0x00000000, 0x00000000, 0x000eb720, 0x000eb720}, + {0x00009aec, 0x00000000, 0x00000000, 0x000eb724, 0x000eb724}, + {0x00009af0, 0x00000000, 0x00000000, 0x000eb728, 0x000eb728}, + {0x00009af4, 0x00000000, 0x00000000, 0x000eb72c, 0x000eb72c}, + {0x00009af8, 0x00000000, 0x00000000, 0x000eb7a0, 0x000eb7a0}, + {0x00009afc, 0x00000000, 0x00000000, 0x000eb7a4, 0x000eb7a4}, + {0x00009b00, 0x00000000, 0x00000000, 0x000eb7a8, 0x000eb7a8}, + {0x00009b04, 0x00000000, 0x00000000, 0x000eb7b0, 0x000eb7b0}, + {0x00009b08, 0x00000000, 0x00000000, 0x000eb7b4, 0x000eb7b4}, + {0x00009b0c, 0x00000000, 0x00000000, 0x000eb7b8, 0x000eb7b8}, + {0x00009b10, 0x00000000, 0x00000000, 0x000eb7a5, 0x000eb7a5}, + {0x00009b14, 0x00000000, 0x00000000, 0x000eb7a9, 0x000eb7a9}, + {0x00009b18, 0x00000000, 0x00000000, 0x000eb7ad, 0x000eb7ad}, + {0x00009b1c, 0x00000000, 0x00000000, 0x000eb7b1, 0x000eb7b1}, + {0x00009b20, 0x00000000, 0x00000000, 0x000eb7b5, 0x000eb7b5}, + {0x00009b24, 0x00000000, 0x00000000, 0x000eb7b9, 0x000eb7b9}, + {0x00009b28, 0x00000000, 0x00000000, 0x000eb7c5, 0x000eb7c5}, + {0x00009b2c, 0x00000000, 0x00000000, 0x000eb7c9, 0x000eb7c9}, + {0x00009b30, 0x00000000, 0x00000000, 0x000eb7d1, 0x000eb7d1}, + {0x00009b34, 0x00000000, 0x00000000, 0x000eb7d5, 0x000eb7d5}, + {0x00009b38, 0x00000000, 0x00000000, 0x000eb7d9, 0x000eb7d9}, + {0x00009b3c, 0x00000000, 0x00000000, 0x000eb7c6, 0x000eb7c6}, + {0x00009b40, 0x00000000, 0x00000000, 0x000eb7ca, 0x000eb7ca}, + {0x00009b44, 0x00000000, 0x00000000, 0x000eb7ce, 0x000eb7ce}, + {0x00009b48, 0x00000000, 0x00000000, 0x000eb7d2, 0x000eb7d2}, + {0x00009b4c, 0x00000000, 0x00000000, 0x000eb7d6, 0x000eb7d6}, + {0x00009b50, 0x00000000, 0x00000000, 0x000eb7c3, 0x000eb7c3}, + {0x00009b54, 0x00000000, 0x00000000, 0x000eb7c7, 0x000eb7c7}, + {0x00009b58, 0x00000000, 0x00000000, 0x000eb7cb, 0x000eb7cb}, + {0x00009b5c, 0x00000000, 0x00000000, 0x000eb7cf, 0x000eb7cf}, + {0x00009b60, 0x00000000, 0x00000000, 0x000eb7d7, 0x000eb7d7}, + {0x00009b64, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b68, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b6c, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b70, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b74, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b78, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b7c, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b80, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b84, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b88, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b8c, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b90, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b94, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b98, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b9c, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009ba0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009ba4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009ba8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bac, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bb0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bb4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bb8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bbc, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bc0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bc4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bc8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bcc, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bd0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bd4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bd8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bdc, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009be0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009be4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009be8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bec, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bf0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bf4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bf8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bfc, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000aa00, 0x00000000, 0x00000000, 0x00058084, 0x00058084}, + {0x0000aa04, 0x00000000, 0x00000000, 0x00058088, 0x00058088}, + {0x0000aa08, 0x00000000, 0x00000000, 0x0005808c, 0x0005808c}, + {0x0000aa0c, 0x00000000, 0x00000000, 0x00058100, 0x00058100}, + {0x0000aa10, 0x00000000, 0x00000000, 0x00058104, 0x00058104}, + {0x0000aa14, 0x00000000, 0x00000000, 0x00058108, 0x00058108}, + {0x0000aa18, 0x00000000, 0x00000000, 0x0005810c, 0x0005810c}, + {0x0000aa1c, 0x00000000, 0x00000000, 0x00058110, 0x00058110}, + {0x0000aa20, 0x00000000, 0x00000000, 0x00058114, 0x00058114}, + {0x0000aa24, 0x00000000, 0x00000000, 0x00058180, 0x00058180}, + {0x0000aa28, 0x00000000, 0x00000000, 0x00058184, 0x00058184}, + {0x0000aa2c, 0x00000000, 0x00000000, 0x00058188, 0x00058188}, + {0x0000aa30, 0x00000000, 0x00000000, 0x0005818c, 0x0005818c}, + {0x0000aa34, 0x00000000, 0x00000000, 0x00058190, 0x00058190}, + {0x0000aa38, 0x00000000, 0x00000000, 0x00058194, 0x00058194}, + {0x0000aa3c, 0x00000000, 0x00000000, 0x000581a0, 0x000581a0}, + {0x0000aa40, 0x00000000, 0x00000000, 0x0005820c, 0x0005820c}, + {0x0000aa44, 0x00000000, 0x00000000, 0x000581a8, 0x000581a8}, + {0x0000aa48, 0x00000000, 0x00000000, 0x00058284, 0x00058284}, + {0x0000aa4c, 0x00000000, 0x00000000, 0x00058288, 0x00058288}, + {0x0000aa50, 0x00000000, 0x00000000, 0x00058224, 0x00058224}, + {0x0000aa54, 0x00000000, 0x00000000, 0x00058290, 0x00058290}, + {0x0000aa58, 0x00000000, 0x00000000, 0x00058300, 0x00058300}, + {0x0000aa5c, 0x00000000, 0x00000000, 0x00058304, 0x00058304}, + {0x0000aa60, 0x00000000, 0x00000000, 0x00058308, 0x00058308}, + {0x0000aa64, 0x00000000, 0x00000000, 0x0005830c, 0x0005830c}, + {0x0000aa68, 0x00000000, 0x00000000, 0x00058380, 0x00058380}, + {0x0000aa6c, 0x00000000, 0x00000000, 0x00058384, 0x00058384}, + {0x0000aa70, 0x00000000, 0x00000000, 0x00068700, 0x00068700}, + {0x0000aa74, 0x00000000, 0x00000000, 0x00068704, 0x00068704}, + {0x0000aa78, 0x00000000, 0x00000000, 0x00068708, 0x00068708}, + {0x0000aa7c, 0x00000000, 0x00000000, 0x0006870c, 0x0006870c}, + {0x0000aa80, 0x00000000, 0x00000000, 0x00068780, 0x00068780}, + {0x0000aa84, 0x00000000, 0x00000000, 0x00068784, 0x00068784}, + {0x0000aa88, 0x00000000, 0x00000000, 0x00078b00, 0x00078b00}, + {0x0000aa8c, 0x00000000, 0x00000000, 0x00078b04, 0x00078b04}, + {0x0000aa90, 0x00000000, 0x00000000, 0x00078b08, 0x00078b08}, + {0x0000aa94, 0x00000000, 0x00000000, 0x00078b0c, 0x00078b0c}, + {0x0000aa98, 0x00000000, 0x00000000, 0x00078b80, 0x00078b80}, + {0x0000aa9c, 0x00000000, 0x00000000, 0x00078b84, 0x00078b84}, + {0x0000aaa0, 0x00000000, 0x00000000, 0x00078b88, 0x00078b88}, + {0x0000aaa4, 0x00000000, 0x00000000, 0x00078b8c, 0x00078b8c}, + {0x0000aaa8, 0x00000000, 0x00000000, 0x00078b90, 0x00078b90}, + {0x0000aaac, 0x00000000, 0x00000000, 0x000caf80, 0x000caf80}, + {0x0000aab0, 0x00000000, 0x00000000, 0x000caf84, 0x000caf84}, + {0x0000aab4, 0x00000000, 0x00000000, 0x000caf88, 0x000caf88}, + {0x0000aab8, 0x00000000, 0x00000000, 0x000caf8c, 0x000caf8c}, + {0x0000aabc, 0x00000000, 0x00000000, 0x000caf90, 0x000caf90}, + {0x0000aac0, 0x00000000, 0x00000000, 0x000db30c, 0x000db30c}, + {0x0000aac4, 0x00000000, 0x00000000, 0x000db310, 0x000db310}, + {0x0000aac8, 0x00000000, 0x00000000, 0x000db384, 0x000db384}, + {0x0000aacc, 0x00000000, 0x00000000, 0x000db388, 0x000db388}, + {0x0000aad0, 0x00000000, 0x00000000, 0x000db324, 0x000db324}, + {0x0000aad4, 0x00000000, 0x00000000, 0x000eb704, 0x000eb704}, + {0x0000aad8, 0x00000000, 0x00000000, 0x000eb6a4, 0x000eb6a4}, + {0x0000aadc, 0x00000000, 0x00000000, 0x000eb6a8, 0x000eb6a8}, + {0x0000aae0, 0x00000000, 0x00000000, 0x000eb710, 0x000eb710}, + {0x0000aae4, 0x00000000, 0x00000000, 0x000eb714, 0x000eb714}, + {0x0000aae8, 0x00000000, 0x00000000, 0x000eb720, 0x000eb720}, + {0x0000aaec, 0x00000000, 0x00000000, 0x000eb724, 0x000eb724}, + {0x0000aaf0, 0x00000000, 0x00000000, 0x000eb728, 0x000eb728}, + {0x0000aaf4, 0x00000000, 0x00000000, 0x000eb72c, 0x000eb72c}, + {0x0000aaf8, 0x00000000, 0x00000000, 0x000eb7a0, 0x000eb7a0}, + {0x0000aafc, 0x00000000, 0x00000000, 0x000eb7a4, 0x000eb7a4}, + {0x0000ab00, 0x00000000, 0x00000000, 0x000eb7a8, 0x000eb7a8}, + {0x0000ab04, 0x00000000, 0x00000000, 0x000eb7b0, 0x000eb7b0}, + {0x0000ab08, 0x00000000, 0x00000000, 0x000eb7b4, 0x000eb7b4}, + {0x0000ab0c, 0x00000000, 0x00000000, 0x000eb7b8, 0x000eb7b8}, + {0x0000ab10, 0x00000000, 0x00000000, 0x000eb7a5, 0x000eb7a5}, + {0x0000ab14, 0x00000000, 0x00000000, 0x000eb7a9, 0x000eb7a9}, + {0x0000ab18, 0x00000000, 0x00000000, 0x000eb7ad, 0x000eb7ad}, + {0x0000ab1c, 0x00000000, 0x00000000, 0x000eb7b1, 0x000eb7b1}, + {0x0000ab20, 0x00000000, 0x00000000, 0x000eb7b5, 0x000eb7b5}, + {0x0000ab24, 0x00000000, 0x00000000, 0x000eb7b9, 0x000eb7b9}, + {0x0000ab28, 0x00000000, 0x00000000, 0x000eb7c5, 0x000eb7c5}, + {0x0000ab2c, 0x00000000, 0x00000000, 0x000eb7c9, 0x000eb7c9}, + {0x0000ab30, 0x00000000, 0x00000000, 0x000eb7d1, 0x000eb7d1}, + {0x0000ab34, 0x00000000, 0x00000000, 0x000eb7d5, 0x000eb7d5}, + {0x0000ab38, 0x00000000, 0x00000000, 0x000eb7d9, 0x000eb7d9}, + {0x0000ab3c, 0x00000000, 0x00000000, 0x000eb7c6, 0x000eb7c6}, + {0x0000ab40, 0x00000000, 0x00000000, 0x000eb7ca, 0x000eb7ca}, + {0x0000ab44, 0x00000000, 0x00000000, 0x000eb7ce, 0x000eb7ce}, + {0x0000ab48, 0x00000000, 0x00000000, 0x000eb7d2, 0x000eb7d2}, + {0x0000ab4c, 0x00000000, 0x00000000, 0x000eb7d6, 0x000eb7d6}, + {0x0000ab50, 0x00000000, 0x00000000, 0x000eb7c3, 0x000eb7c3}, + {0x0000ab54, 0x00000000, 0x00000000, 0x000eb7c7, 0x000eb7c7}, + {0x0000ab58, 0x00000000, 0x00000000, 0x000eb7cb, 0x000eb7cb}, + {0x0000ab5c, 0x00000000, 0x00000000, 0x000eb7cf, 0x000eb7cf}, + {0x0000ab60, 0x00000000, 0x00000000, 0x000eb7d7, 0x000eb7d7}, + {0x0000ab64, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab68, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab6c, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab70, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab74, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab78, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab7c, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab80, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab84, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab88, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab8c, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab90, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab94, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab98, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab9c, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000aba0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000aba4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000aba8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abac, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abb0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abb4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abb8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abbc, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abc0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abc4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abc8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abcc, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abd0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abd4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abd8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abdc, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abe0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abe4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abe8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abec, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abf0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abf4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abf8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abfc, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000a204, 0x00000004, 0x00000004, 0x00000004, 0x00000004}, + {0x0000a20c, 0x00000014, 0x00000014, 0x0001f000, 0x0001f000}, + {0x0000b20c, 0x00000014, 0x00000014, 0x0001f000, 0x0001f000}, + {0x0000a21c, 0x1883800a, 0x1883800a, 0x1883800a, 0x1883800a}, + {0x0000a230, 0x00000000, 0x00000000, 0x00000210, 0x00000108}, + {0x0000a250, 0x0004f000, 0x0004f000, 0x0004a000, 0x0004a000}, + {0x0000a358, 0x7999aa02, 0x7999aa02, 0x7999aa0e, 0x7999aa0e}, +}; + +static const u32 ar9285Common_9285_1_2[][2] = { + /* Addr allmodes */ + {0x0000000c, 0x00000000}, + {0x00000030, 0x00020045}, + {0x00000034, 0x00000005}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000008}, + {0x00000048, 0x00000008}, + {0x0000004c, 0x00000010}, + {0x00000050, 0x00000000}, + {0x00000054, 0x0000001f}, + {0x00000800, 0x00000000}, + {0x00000804, 0x00000000}, + {0x00000808, 0x00000000}, + {0x0000080c, 0x00000000}, + {0x00000810, 0x00000000}, + {0x00000814, 0x00000000}, + {0x00000818, 0x00000000}, + {0x0000081c, 0x00000000}, + {0x00000820, 0x00000000}, + {0x00000824, 0x00000000}, + {0x00001040, 0x002ffc0f}, + {0x00001044, 0x002ffc0f}, + {0x00001048, 0x002ffc0f}, + {0x0000104c, 0x002ffc0f}, + {0x00001050, 0x002ffc0f}, + {0x00001054, 0x002ffc0f}, + {0x00001058, 0x002ffc0f}, + {0x0000105c, 0x002ffc0f}, + {0x00001060, 0x002ffc0f}, + {0x00001064, 0x002ffc0f}, + {0x00001230, 0x00000000}, + {0x00001270, 0x00000000}, + {0x00001038, 0x00000000}, + {0x00001078, 0x00000000}, + {0x000010b8, 0x00000000}, + {0x000010f8, 0x00000000}, + {0x00001138, 0x00000000}, + {0x00001178, 0x00000000}, + {0x000011b8, 0x00000000}, + {0x000011f8, 0x00000000}, + {0x00001238, 0x00000000}, + {0x00001278, 0x00000000}, + {0x000012b8, 0x00000000}, + {0x000012f8, 0x00000000}, + {0x00001338, 0x00000000}, + {0x00001378, 0x00000000}, + {0x000013b8, 0x00000000}, + {0x000013f8, 0x00000000}, + {0x00001438, 0x00000000}, + {0x00001478, 0x00000000}, + {0x000014b8, 0x00000000}, + {0x000014f8, 0x00000000}, + {0x00001538, 0x00000000}, + {0x00001578, 0x00000000}, + {0x000015b8, 0x00000000}, + {0x000015f8, 0x00000000}, + {0x00001638, 0x00000000}, + {0x00001678, 0x00000000}, + {0x000016b8, 0x00000000}, + {0x000016f8, 0x00000000}, + {0x00001738, 0x00000000}, + {0x00001778, 0x00000000}, + {0x000017b8, 0x00000000}, + {0x000017f8, 0x00000000}, + {0x0000103c, 0x00000000}, + {0x0000107c, 0x00000000}, + {0x000010bc, 0x00000000}, + {0x000010fc, 0x00000000}, + {0x0000113c, 0x00000000}, + {0x0000117c, 0x00000000}, + {0x000011bc, 0x00000000}, + {0x000011fc, 0x00000000}, + {0x0000123c, 0x00000000}, + {0x0000127c, 0x00000000}, + {0x000012bc, 0x00000000}, + {0x000012fc, 0x00000000}, + {0x0000133c, 0x00000000}, + {0x0000137c, 0x00000000}, + {0x000013bc, 0x00000000}, + {0x000013fc, 0x00000000}, + {0x0000143c, 0x00000000}, + {0x0000147c, 0x00000000}, + {0x00004030, 0x00000002}, + {0x0000403c, 0x00000002}, + {0x00004024, 0x0000001f}, + {0x00004060, 0x00000000}, + {0x00004064, 0x00000000}, + {0x00007010, 0x00000031}, + {0x00007034, 0x00000002}, + {0x00007038, 0x000004c2}, + {0x00008004, 0x00000000}, + {0x00008008, 0x00000000}, + {0x0000800c, 0x00000000}, + {0x00008018, 0x00000700}, + {0x00008020, 0x00000000}, + {0x00008038, 0x00000000}, + {0x0000803c, 0x00000000}, + {0x00008048, 0x00000000}, + {0x00008054, 0x00000000}, + {0x00008058, 0x00000000}, + {0x0000805c, 0x000fc78f}, + {0x00008060, 0x0000000f}, + {0x00008064, 0x00000000}, + {0x00008070, 0x00000000}, + {0x000080c0, 0x2a80001a}, + {0x000080c4, 0x05dc01e0}, + {0x000080c8, 0x1f402710}, + {0x000080cc, 0x01f40000}, + {0x000080d0, 0x00001e00}, + {0x000080d4, 0x00000000}, + {0x000080d8, 0x00400000}, + {0x000080e0, 0xffffffff}, + {0x000080e4, 0x0000ffff}, + {0x000080e8, 0x003f3f3f}, + {0x000080ec, 0x00000000}, + {0x000080f0, 0x00000000}, + {0x000080f4, 0x00000000}, + {0x000080f8, 0x00000000}, + {0x000080fc, 0x00020000}, + {0x00008100, 0x00020000}, + {0x00008104, 0x00000001}, + {0x00008108, 0x00000052}, + {0x0000810c, 0x00000000}, + {0x00008110, 0x00000168}, + {0x00008118, 0x000100aa}, + {0x0000811c, 0x00003210}, + {0x00008120, 0x08f04810}, + {0x00008124, 0x00000000}, + {0x00008128, 0x00000000}, + {0x0000812c, 0x00000000}, + {0x00008130, 0x00000000}, + {0x00008134, 0x00000000}, + {0x00008138, 0x00000000}, + {0x0000813c, 0x00000000}, + {0x00008144, 0xffffffff}, + {0x00008168, 0x00000000}, + {0x0000816c, 0x00000000}, + {0x00008170, 0x32143320}, + {0x00008174, 0xfaa4fa50}, + {0x00008178, 0x00000100}, + {0x0000817c, 0x00000000}, + {0x000081c0, 0x00000000}, + {0x000081d0, 0x0000320a}, + {0x000081ec, 0x00000000}, + {0x000081f0, 0x00000000}, + {0x000081f4, 0x00000000}, + {0x000081f8, 0x00000000}, + {0x000081fc, 0x00000000}, + {0x00008200, 0x00000000}, + {0x00008204, 0x00000000}, + {0x00008208, 0x00000000}, + {0x0000820c, 0x00000000}, + {0x00008210, 0x00000000}, + {0x00008214, 0x00000000}, + {0x00008218, 0x00000000}, + {0x0000821c, 0x00000000}, + {0x00008220, 0x00000000}, + {0x00008224, 0x00000000}, + {0x00008228, 0x00000000}, + {0x0000822c, 0x00000000}, + {0x00008230, 0x00000000}, + {0x00008234, 0x00000000}, + {0x00008238, 0x00000000}, + {0x0000823c, 0x00000000}, + {0x00008240, 0x00100000}, + {0x00008244, 0x0010f400}, + {0x00008248, 0x00000100}, + {0x0000824c, 0x0001e800}, + {0x00008250, 0x00000000}, + {0x00008254, 0x00000000}, + {0x00008258, 0x00000000}, + {0x0000825c, 0x400000ff}, + {0x00008260, 0x00080922}, + {0x00008264, 0x88a00010}, + {0x00008270, 0x00000000}, + {0x00008274, 0x40000000}, + {0x00008278, 0x003e4180}, + {0x0000827c, 0x00000000}, + {0x00008284, 0x0000002c}, + {0x00008288, 0x0000002c}, + {0x0000828c, 0x00000000}, + {0x00008294, 0x00000000}, + {0x00008298, 0x00000000}, + {0x0000829c, 0x00000000}, + {0x00008300, 0x00000040}, + {0x00008314, 0x00000000}, + {0x00008328, 0x00000000}, + {0x0000832c, 0x00000001}, + {0x00008330, 0x00000302}, + {0x00008334, 0x00000e00}, + {0x00008338, 0x00ff0000}, + {0x0000833c, 0x00000000}, + {0x00008340, 0x00010380}, + {0x00008344, 0x00481043}, + {0x00009808, 0x00000000}, + {0x0000980c, 0xafe68e30}, + {0x00009810, 0xfd14e000}, + {0x00009814, 0x9c0a9f6b}, + {0x0000981c, 0x00000000}, + {0x0000982c, 0x0000a000}, + {0x00009830, 0x00000000}, + {0x0000983c, 0x00200400}, + {0x0000984c, 0x0040233c}, + {0x00009854, 0x00000044}, + {0x00009900, 0x00000000}, + {0x00009904, 0x00000000}, + {0x00009908, 0x00000000}, + {0x0000990c, 0x00000000}, + {0x00009910, 0x01002310}, + {0x0000991c, 0x10000fff}, + {0x00009920, 0x04900000}, + {0x00009928, 0x00000001}, + {0x0000992c, 0x00000004}, + {0x00009934, 0x1e1f2022}, + {0x00009938, 0x0a0b0c0d}, + {0x0000993c, 0x00000000}, + {0x00009940, 0x14750604}, + {0x00009948, 0x9280c00a}, + {0x0000994c, 0x00020028}, + {0x00009954, 0x5f3ca3de}, + {0x00009958, 0x2108ecff}, + {0x00009968, 0x000003ce}, + {0x00009970, 0x192bb514}, + {0x00009974, 0x00000000}, + {0x00009978, 0x00000001}, + {0x0000997c, 0x00000000}, + {0x00009980, 0x00000000}, + {0x00009984, 0x00000000}, + {0x00009988, 0x00000000}, + {0x0000998c, 0x00000000}, + {0x00009990, 0x00000000}, + {0x00009994, 0x00000000}, + {0x00009998, 0x00000000}, + {0x0000999c, 0x00000000}, + {0x000099a0, 0x00000000}, + {0x000099a4, 0x00000001}, + {0x000099a8, 0x201fff00}, + {0x000099ac, 0x2def0400}, + {0x000099b0, 0x03051000}, + {0x000099b4, 0x00000820}, + {0x000099dc, 0x00000000}, + {0x000099e0, 0x00000000}, + {0x000099e4, 0xaaaaaaaa}, + {0x000099e8, 0x3c466478}, + {0x000099ec, 0x0cc80caa}, + {0x000099f0, 0x00000000}, + {0x0000a208, 0x803e68c8}, + {0x0000a210, 0x4080a333}, + {0x0000a214, 0x00206c10}, + {0x0000a218, 0x009c4060}, + {0x0000a220, 0x01834061}, + {0x0000a224, 0x00000400}, + {0x0000a228, 0x000003b5}, + {0x0000a22c, 0x00000000}, + {0x0000a234, 0x20202020}, + {0x0000a238, 0x20202020}, + {0x0000a244, 0x00000000}, + {0x0000a248, 0xfffffffc}, + {0x0000a24c, 0x00000000}, + {0x0000a254, 0x00000000}, + {0x0000a258, 0x0ccb5380}, + {0x0000a25c, 0x15151501}, + {0x0000a260, 0xdfa90f01}, + {0x0000a268, 0x00000000}, + {0x0000a26c, 0x0ebae9e6}, + {0x0000d270, 0x0d820820}, + {0x0000d35c, 0x07ffffef}, + {0x0000d360, 0x0fffffe7}, + {0x0000d364, 0x17ffffe5}, + {0x0000d368, 0x1fffffe4}, + {0x0000d36c, 0x37ffffe3}, + {0x0000d370, 0x3fffffe3}, + {0x0000d374, 0x57ffffe3}, + {0x0000d378, 0x5fffffe2}, + {0x0000d37c, 0x7fffffe2}, + {0x0000d380, 0x7f3c7bba}, + {0x0000d384, 0xf3307ff0}, + {0x0000a388, 0x0c000000}, + {0x0000a38c, 0x20202020}, + {0x0000a390, 0x20202020}, + {0x0000a39c, 0x00000001}, + {0x0000a3a0, 0x00000000}, + {0x0000a3a4, 0x00000000}, + {0x0000a3a8, 0x00000000}, + {0x0000a3ac, 0x00000000}, + {0x0000a3b0, 0x00000000}, + {0x0000a3b4, 0x00000000}, + {0x0000a3b8, 0x00000000}, + {0x0000a3bc, 0x00000000}, + {0x0000a3c0, 0x00000000}, + {0x0000a3c4, 0x00000000}, + {0x0000a3cc, 0x20202020}, + {0x0000a3d0, 0x20202020}, + {0x0000a3d4, 0x20202020}, + {0x0000a3e4, 0x00000000}, + {0x0000a3e8, 0x18c43433}, + {0x0000a3ec, 0x00f70081}, + {0x00007800, 0x00140000}, + {0x00007804, 0x0e4548d8}, + {0x00007808, 0x54214514}, + {0x0000780c, 0x02025830}, + {0x00007810, 0x71c0d388}, + {0x0000781c, 0x00000000}, + {0x00007824, 0x00d86fff}, + {0x0000782c, 0x6e36d97b}, + {0x00007834, 0x71400087}, + {0x00007844, 0x000c0db6}, + {0x00007848, 0x6db6246f}, + {0x0000784c, 0x6d9b66db}, + {0x00007850, 0x6d8c6dba}, + {0x00007854, 0x00040000}, + {0x00007858, 0xdb003012}, + {0x0000785c, 0x04924914}, + {0x00007860, 0x21084210}, + {0x00007864, 0xf7d7ffde}, + {0x00007868, 0xc2034080}, + {0x00007870, 0x10142c00}, +}; + +static const u32 ar9285Modes_high_power_tx_gain_9285_1_2[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a300, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a304, 0x00000000, 0x00000000, 0x00006200, 0x00006200}, + {0x0000a308, 0x00000000, 0x00000000, 0x00008201, 0x00008201}, + {0x0000a30c, 0x00000000, 0x00000000, 0x0000b240, 0x0000b240}, + {0x0000a310, 0x00000000, 0x00000000, 0x0000d241, 0x0000d241}, + {0x0000a314, 0x00000000, 0x00000000, 0x0000f600, 0x0000f600}, + {0x0000a318, 0x00000000, 0x00000000, 0x00012800, 0x00012800}, + {0x0000a31c, 0x00000000, 0x00000000, 0x00016802, 0x00016802}, + {0x0000a320, 0x00000000, 0x00000000, 0x0001b805, 0x0001b805}, + {0x0000a324, 0x00000000, 0x00000000, 0x00021a80, 0x00021a80}, + {0x0000a328, 0x00000000, 0x00000000, 0x00028b00, 0x00028b00}, + {0x0000a32c, 0x00000000, 0x00000000, 0x0002ab40, 0x0002ab40}, + {0x0000a330, 0x00000000, 0x00000000, 0x0002cd80, 0x0002cd80}, + {0x0000a334, 0x00000000, 0x00000000, 0x00033d82, 0x00033d82}, + {0x0000a338, 0x0003891e, 0x0003891e, 0x0003891e, 0x0003891e}, + {0x0000a33c, 0x0003a95e, 0x0003a95e, 0x0003a95e, 0x0003a95e}, + {0x0000a340, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a344, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a348, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a34c, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a350, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a354, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x00007814, 0x924934a8, 0x924934a8, 0x924934a8, 0x924934a8}, + {0x00007828, 0x26d2491b, 0x26d2491b, 0x26d2491b, 0x26d2491b}, + {0x00007830, 0xedb6d96e, 0xedb6d96e, 0xedb6d96e, 0xedb6d96e}, + {0x00007838, 0xfac68803, 0xfac68803, 0xfac68803, 0xfac68803}, + {0x0000783c, 0x0001fffe, 0x0001fffe, 0x0001fffe, 0x0001fffe}, + {0x00007840, 0xffeb1a20, 0xffeb1a20, 0xffeb1a20, 0xffeb1a20}, + {0x0000786c, 0x08609ebe, 0x08609ebe, 0x08609ebe, 0x08609ebe}, + {0x00007820, 0x00000c00, 0x00000c00, 0x00000c00, 0x00000c00}, + {0x0000a274, 0x0a22a652, 0x0a22a652, 0x0a216652, 0x0a216652}, + {0x0000a278, 0x0e739ce7, 0x0e739ce7, 0x0e739ce7, 0x0e739ce7}, + {0x0000a27c, 0x050380e7, 0x050380e7, 0x050380e7, 0x050380e7}, + {0x0000a394, 0x0e739ce7, 0x0e739ce7, 0x0e739ce7, 0x0e739ce7}, + {0x0000a398, 0x000000e7, 0x000000e7, 0x000000e7, 0x000000e7}, + {0x0000a3dc, 0x0e739ce7, 0x0e739ce7, 0x0e739ce7, 0x0e739ce7}, + {0x0000a3e0, 0x000000e7, 0x000000e7, 0x000000e7, 0x000000e7}, +}; + +static const u32 ar9285Modes_original_tx_gain_9285_1_2[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a300, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a304, 0x00000000, 0x00000000, 0x00009200, 0x00009200}, + {0x0000a308, 0x00000000, 0x00000000, 0x00010208, 0x00010208}, + {0x0000a30c, 0x00000000, 0x00000000, 0x00019608, 0x00019608}, + {0x0000a310, 0x00000000, 0x00000000, 0x00022618, 0x00022618}, + {0x0000a314, 0x00000000, 0x00000000, 0x0002a6c9, 0x0002a6c9}, + {0x0000a318, 0x00000000, 0x00000000, 0x00031710, 0x00031710}, + {0x0000a31c, 0x00000000, 0x00000000, 0x00035718, 0x00035718}, + {0x0000a320, 0x00000000, 0x00000000, 0x00038758, 0x00038758}, + {0x0000a324, 0x00000000, 0x00000000, 0x0003c75a, 0x0003c75a}, + {0x0000a328, 0x00000000, 0x00000000, 0x0004075c, 0x0004075c}, + {0x0000a32c, 0x00000000, 0x00000000, 0x0004475e, 0x0004475e}, + {0x0000a330, 0x00000000, 0x00000000, 0x0004679f, 0x0004679f}, + {0x0000a334, 0x00000000, 0x00000000, 0x000487df, 0x000487df}, + {0x0000a338, 0x0003891e, 0x0003891e, 0x0003891e, 0x0003891e}, + {0x0000a33c, 0x0003a95e, 0x0003a95e, 0x0003a95e, 0x0003a95e}, + {0x0000a340, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a344, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a348, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a34c, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a350, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a354, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x00007814, 0x924934a8, 0x924934a8, 0x924934a8, 0x924934a8}, + {0x00007828, 0x26d2491b, 0x26d2491b, 0x26d2491b, 0x26d2491b}, + {0x00007830, 0xedb6d96e, 0xedb6d96e, 0xedb6d96e, 0xedb6d96e}, + {0x00007838, 0xfac68801, 0xfac68801, 0xfac68801, 0xfac68801}, + {0x0000783c, 0x0001fffe, 0x0001fffe, 0x0001fffe, 0x0001fffe}, + {0x00007840, 0xffeb1a20, 0xffeb1a20, 0xffeb1a20, 0xffeb1a20}, + {0x0000786c, 0x48609eb4, 0x48609eb4, 0x48609eb4, 0x48609eb4}, + {0x00007820, 0x00000c04, 0x00000c04, 0x00000c04, 0x00000c04}, + {0x0000a274, 0x0a21c652, 0x0a21c652, 0x0a21a652, 0x0a21a652}, + {0x0000a278, 0x39ce739c, 0x39ce739c, 0x39ce739c, 0x39ce739c}, + {0x0000a27c, 0x050e039c, 0x050e039c, 0x050e039c, 0x050e039c}, + {0x0000a394, 0x39ce739c, 0x39ce739c, 0x39ce739c, 0x39ce739c}, + {0x0000a398, 0x0000039c, 0x0000039c, 0x0000039c, 0x0000039c}, + {0x0000a3dc, 0x39ce739c, 0x39ce739c, 0x39ce739c, 0x39ce739c}, + {0x0000a3e0, 0x0000039c, 0x0000039c, 0x0000039c, 0x0000039c}, +}; + +static const u32 ar9285Modes_XE2_0_normal_power[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a300, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a304, 0x00000000, 0x00000000, 0x00009200, 0x00009200}, + {0x0000a308, 0x00000000, 0x00000000, 0x00010208, 0x00010208}, + {0x0000a30c, 0x00000000, 0x00000000, 0x00019608, 0x00019608}, + {0x0000a310, 0x00000000, 0x00000000, 0x00022618, 0x00022618}, + {0x0000a314, 0x00000000, 0x00000000, 0x0002a6c9, 0x0002a6c9}, + {0x0000a318, 0x00000000, 0x00000000, 0x00031710, 0x00031710}, + {0x0000a31c, 0x00000000, 0x00000000, 0x00035718, 0x00035718}, + {0x0000a320, 0x00000000, 0x00000000, 0x00038758, 0x00038758}, + {0x0000a324, 0x00000000, 0x00000000, 0x0003c75a, 0x0003c75a}, + {0x0000a328, 0x00000000, 0x00000000, 0x0004075c, 0x0004075c}, + {0x0000a32c, 0x00000000, 0x00000000, 0x0004475e, 0x0004475e}, + {0x0000a330, 0x00000000, 0x00000000, 0x0004679f, 0x0004679f}, + {0x0000a334, 0x00000000, 0x00000000, 0x000487df, 0x000487df}, + {0x0000a338, 0x0003891e, 0x0003891e, 0x0003891e, 0x0003891e}, + {0x0000a33c, 0x0003a95e, 0x0003a95e, 0x0003a95e, 0x0003a95e}, + {0x0000a340, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a344, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a348, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a34c, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a350, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a354, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x00007814, 0x92497ca8, 0x92497ca8, 0x92497ca8, 0x92497ca8}, + {0x00007828, 0x4ad2491b, 0x4ad2491b, 0x2ad2491b, 0x4ad2491b}, + {0x00007830, 0xedb6da6e, 0xedb6da6e, 0xedb6da6e, 0xedb6da6e}, + {0x00007838, 0xdac71441, 0xdac71441, 0xdac71441, 0xdac71441}, + {0x0000783c, 0x2481f6fe, 0x2481f6fe, 0x2481f6fe, 0x2481f6fe}, + {0x00007840, 0xba5f638c, 0xba5f638c, 0xba5f638c, 0xba5f638c}, + {0x0000786c, 0x48609eb4, 0x48609eb4, 0x48609eb4, 0x48609eb4}, + {0x00007820, 0x00000c04, 0x00000c04, 0x00000c04, 0x00000c04}, + {0x0000a274, 0x0a21c652, 0x0a21c652, 0x0a21a652, 0x0a21a652}, + {0x0000a278, 0x39ce739c, 0x39ce739c, 0x39ce739c, 0x39ce739c}, + {0x0000a27c, 0x050e039c, 0x050e039c, 0x050e039c, 0x050e039c}, + {0x0000a394, 0x39ce739c, 0x39ce739c, 0x39ce739c, 0x39ce739c}, + {0x0000a398, 0x0000039c, 0x0000039c, 0x0000039c, 0x0000039c}, + {0x0000a3dc, 0x39ce739c, 0x39ce739c, 0x39ce739c, 0x39ce739c}, + {0x0000a3e0, 0x0000039c, 0x0000039c, 0x0000039c, 0x0000039c}, +}; + +static const u32 ar9285Modes_XE2_0_high_power[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a300, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a304, 0x00000000, 0x00000000, 0x00006200, 0x00006200}, + {0x0000a308, 0x00000000, 0x00000000, 0x00008201, 0x00008201}, + {0x0000a30c, 0x00000000, 0x00000000, 0x0000b240, 0x0000b240}, + {0x0000a310, 0x00000000, 0x00000000, 0x0000d241, 0x0000d241}, + {0x0000a314, 0x00000000, 0x00000000, 0x0000f600, 0x0000f600}, + {0x0000a318, 0x00000000, 0x00000000, 0x00012800, 0x00012800}, + {0x0000a31c, 0x00000000, 0x00000000, 0x00016802, 0x00016802}, + {0x0000a320, 0x00000000, 0x00000000, 0x0001b805, 0x0001b805}, + {0x0000a324, 0x00000000, 0x00000000, 0x00021a80, 0x00021a80}, + {0x0000a328, 0x00000000, 0x00000000, 0x00028b00, 0x00028b00}, + {0x0000a32c, 0x00000000, 0x00000000, 0x0002ab40, 0x0002ab40}, + {0x0000a330, 0x00000000, 0x00000000, 0x0002cd80, 0x0002cd80}, + {0x0000a334, 0x00000000, 0x00000000, 0x00033d82, 0x00033d82}, + {0x0000a338, 0x0003891e, 0x0003891e, 0x0003891e, 0x0003891e}, + {0x0000a33c, 0x0003a95e, 0x0003a95e, 0x0003a95e, 0x0003a95e}, + {0x0000a340, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a344, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a348, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a34c, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a350, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a354, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x00007814, 0x92497ca8, 0x92497ca8, 0x92497ca8, 0x92497ca8}, + {0x00007828, 0x4ad2491b, 0x4ad2491b, 0x2ad2491b, 0x4ad2491b}, + {0x00007830, 0xedb6da6e, 0xedb6da6e, 0xedb6da6e, 0xedb6da6e}, + {0x00007838, 0xdac71443, 0xdac71443, 0xdac71443, 0xdac71443}, + {0x0000783c, 0x2481f6fe, 0x2481f6fe, 0x2481f6fe, 0x2481f6fe}, + {0x00007840, 0xba5f638c, 0xba5f638c, 0xba5f638c, 0xba5f638c}, + {0x0000786c, 0x08609ebe, 0x08609ebe, 0x08609ebe, 0x08609ebe}, + {0x00007820, 0x00000c00, 0x00000c00, 0x00000c00, 0x00000c00}, + {0x0000a274, 0x0a22a652, 0x0a22a652, 0x0a216652, 0x0a216652}, + {0x0000a278, 0x0e739ce7, 0x0e739ce7, 0x0e739ce7, 0x0e739ce7}, + {0x0000a27c, 0x050380e7, 0x050380e7, 0x050380e7, 0x050380e7}, + {0x0000a394, 0x0e739ce7, 0x0e739ce7, 0x0e739ce7, 0x0e739ce7}, + {0x0000a398, 0x000000e7, 0x000000e7, 0x000000e7, 0x000000e7}, + {0x0000a3dc, 0x0e739ce7, 0x0e739ce7, 0x0e739ce7, 0x0e739ce7}, + {0x0000a3e0, 0x000000e7, 0x000000e7, 0x000000e7, 0x000000e7}, +}; + +static const u32 ar9287Modes_9287_1_1[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00001030, 0x00000000, 0x00000000, 0x000002c0, 0x00000160}, + {0x00001070, 0x00000000, 0x00000000, 0x00000318, 0x0000018c}, + {0x000010b0, 0x00000000, 0x00000000, 0x00007c70, 0x00003e38}, + {0x000010f0, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00008014, 0x00000000, 0x00000000, 0x10801600, 0x08400b00}, + {0x0000801c, 0x00000000, 0x00000000, 0x12e00057, 0x12e0002b}, + {0x00008120, 0x08f04800, 0x08f04800, 0x08f04810, 0x08f04810}, + {0x000081d0, 0x00003200, 0x00003200, 0x0000320a, 0x0000320a}, + {0x00008318, 0x00000000, 0x00000000, 0x00006880, 0x00003440}, + {0x00009804, 0x00000000, 0x00000000, 0x000003c4, 0x00000300}, + {0x00009820, 0x00000000, 0x00000000, 0x02020200, 0x02020200}, + {0x00009824, 0x00000000, 0x00000000, 0x01000e0e, 0x01000e0e}, + {0x00009828, 0x00000000, 0x00000000, 0x3a020001, 0x3a020001}, + {0x00009834, 0x00000000, 0x00000000, 0x00000e0e, 0x00000e0e}, + {0x00009838, 0x00000003, 0x00000003, 0x00000007, 0x00000007}, + {0x00009840, 0x206a002e, 0x206a002e, 0x206a012e, 0x206a012e}, + {0x00009844, 0x03720000, 0x03720000, 0x037216a0, 0x037216a0}, + {0x00009850, 0x60000000, 0x60000000, 0x6d4000e2, 0x6c4000e2}, + {0x00009858, 0x7c000d00, 0x7c000d00, 0x7ec84d2e, 0x7ec84d2e}, + {0x0000985c, 0x3100005e, 0x3100005e, 0x3139605e, 0x31395d5e}, + {0x00009860, 0x00058d00, 0x00058d00, 0x00058d20, 0x00058d20}, + {0x00009864, 0x00000e00, 0x00000e00, 0x0001ce00, 0x0001ce00}, + {0x00009868, 0x000040c0, 0x000040c0, 0x5ac640d0, 0x5ac640d0}, + {0x0000986c, 0x00000080, 0x00000080, 0x06903881, 0x06903881}, + {0x00009914, 0x00000000, 0x00000000, 0x00001130, 0x00000898}, + {0x00009918, 0x00000000, 0x00000000, 0x00000016, 0x0000000b}, + {0x00009924, 0xd00a8a01, 0xd00a8a01, 0xd00a8a0d, 0xd00a8a0d}, + {0x00009944, 0xefbc0000, 0xefbc0000, 0xefbc1010, 0xefbc1010}, + {0x00009960, 0x00000000, 0x00000000, 0x00000010, 0x00000010}, + {0x0000a960, 0x00000000, 0x00000000, 0x00000010, 0x00000010}, + {0x00009964, 0x00000000, 0x00000000, 0x00000210, 0x00000210}, + {0x0000c968, 0x00000200, 0x00000200, 0x000003ce, 0x000003ce}, + {0x000099b8, 0x00000000, 0x00000000, 0x0000001c, 0x0000001c}, + {0x000099bc, 0x00000000, 0x00000000, 0x00000c00, 0x00000c00}, + {0x000099c0, 0x00000000, 0x00000000, 0x05eea6d4, 0x05eea6d4}, + {0x0000a204, 0x00000440, 0x00000440, 0x00000444, 0x00000444}, + {0x0000a20c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b20c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a21c, 0x1803800a, 0x1803800a, 0x1883800a, 0x1883800a}, + {0x0000a230, 0x00000000, 0x00000000, 0x00000210, 0x00000108}, + {0x0000a250, 0x00000000, 0x00000000, 0x0004a000, 0x0004a000}, + {0x0000a358, 0x7999aa02, 0x7999aa02, 0x7999aa0e, 0x7999aa0e}, + {0x0000a3d8, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, +}; + +static const u32 ar9287Common_9287_1_1[][2] = { + /* Addr allmodes */ + {0x0000000c, 0x00000000}, + {0x00000030, 0x00020015}, + {0x00000034, 0x00000005}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000008}, + {0x00000048, 0x00000008}, + {0x0000004c, 0x00000010}, + {0x00000050, 0x00000000}, + {0x00000054, 0x0000001f}, + {0x00000800, 0x00000000}, + {0x00000804, 0x00000000}, + {0x00000808, 0x00000000}, + {0x0000080c, 0x00000000}, + {0x00000810, 0x00000000}, + {0x00000814, 0x00000000}, + {0x00000818, 0x00000000}, + {0x0000081c, 0x00000000}, + {0x00000820, 0x00000000}, + {0x00000824, 0x00000000}, + {0x00001040, 0x002ffc0f}, + {0x00001044, 0x002ffc0f}, + {0x00001048, 0x002ffc0f}, + {0x0000104c, 0x002ffc0f}, + {0x00001050, 0x002ffc0f}, + {0x00001054, 0x002ffc0f}, + {0x00001058, 0x002ffc0f}, + {0x0000105c, 0x002ffc0f}, + {0x00001060, 0x002ffc0f}, + {0x00001064, 0x002ffc0f}, + {0x00001230, 0x00000000}, + {0x00001270, 0x00000000}, + {0x00001038, 0x00000000}, + {0x00001078, 0x00000000}, + {0x000010b8, 0x00000000}, + {0x000010f8, 0x00000000}, + {0x00001138, 0x00000000}, + {0x00001178, 0x00000000}, + {0x000011b8, 0x00000000}, + {0x000011f8, 0x00000000}, + {0x00001238, 0x00000000}, + {0x00001278, 0x00000000}, + {0x000012b8, 0x00000000}, + {0x000012f8, 0x00000000}, + {0x00001338, 0x00000000}, + {0x00001378, 0x00000000}, + {0x000013b8, 0x00000000}, + {0x000013f8, 0x00000000}, + {0x00001438, 0x00000000}, + {0x00001478, 0x00000000}, + {0x000014b8, 0x00000000}, + {0x000014f8, 0x00000000}, + {0x00001538, 0x00000000}, + {0x00001578, 0x00000000}, + {0x000015b8, 0x00000000}, + {0x000015f8, 0x00000000}, + {0x00001638, 0x00000000}, + {0x00001678, 0x00000000}, + {0x000016b8, 0x00000000}, + {0x000016f8, 0x00000000}, + {0x00001738, 0x00000000}, + {0x00001778, 0x00000000}, + {0x000017b8, 0x00000000}, + {0x000017f8, 0x00000000}, + {0x0000103c, 0x00000000}, + {0x0000107c, 0x00000000}, + {0x000010bc, 0x00000000}, + {0x000010fc, 0x00000000}, + {0x0000113c, 0x00000000}, + {0x0000117c, 0x00000000}, + {0x000011bc, 0x00000000}, + {0x000011fc, 0x00000000}, + {0x0000123c, 0x00000000}, + {0x0000127c, 0x00000000}, + {0x000012bc, 0x00000000}, + {0x000012fc, 0x00000000}, + {0x0000133c, 0x00000000}, + {0x0000137c, 0x00000000}, + {0x000013bc, 0x00000000}, + {0x000013fc, 0x00000000}, + {0x0000143c, 0x00000000}, + {0x0000147c, 0x00000000}, + {0x00004030, 0x00000002}, + {0x0000403c, 0x00000002}, + {0x00004024, 0x0000001f}, + {0x00004060, 0x00000000}, + {0x00004064, 0x00000000}, + {0x00007010, 0x00000033}, + {0x00007020, 0x00000000}, + {0x00007034, 0x00000002}, + {0x00007038, 0x000004c2}, + {0x00008004, 0x00000000}, + {0x00008008, 0x00000000}, + {0x0000800c, 0x00000000}, + {0x00008018, 0x00000700}, + {0x00008020, 0x00000000}, + {0x00008038, 0x00000000}, + {0x0000803c, 0x00000000}, + {0x00008048, 0x40000000}, + {0x00008054, 0x00000000}, + {0x00008058, 0x00000000}, + {0x0000805c, 0x000fc78f}, + {0x00008060, 0x0000000f}, + {0x00008064, 0x00000000}, + {0x00008070, 0x00000000}, + {0x000080c0, 0x2a80001a}, + {0x000080c4, 0x05dc01e0}, + {0x000080c8, 0x1f402710}, + {0x000080cc, 0x01f40000}, + {0x000080d0, 0x00001e00}, + {0x000080d4, 0x00000000}, + {0x000080d8, 0x00400000}, + {0x000080e0, 0xffffffff}, + {0x000080e4, 0x0000ffff}, + {0x000080e8, 0x003f3f3f}, + {0x000080ec, 0x00000000}, + {0x000080f0, 0x00000000}, + {0x000080f4, 0x00000000}, + {0x000080f8, 0x00000000}, + {0x000080fc, 0x00020000}, + {0x00008100, 0x00020000}, + {0x00008104, 0x00000001}, + {0x00008108, 0x00000052}, + {0x0000810c, 0x00000000}, + {0x00008110, 0x00000168}, + {0x00008118, 0x000100aa}, + {0x0000811c, 0x00003210}, + {0x00008124, 0x00000000}, + {0x00008128, 0x00000000}, + {0x0000812c, 0x00000000}, + {0x00008130, 0x00000000}, + {0x00008134, 0x00000000}, + {0x00008138, 0x00000000}, + {0x0000813c, 0x00000000}, + {0x00008144, 0xffffffff}, + {0x00008168, 0x00000000}, + {0x0000816c, 0x00000000}, + {0x00008170, 0x18487320}, + {0x00008174, 0xfaa4fa50}, + {0x00008178, 0x00000100}, + {0x0000817c, 0x00000000}, + {0x000081c0, 0x00000000}, + {0x000081c4, 0x00000000}, + {0x000081d4, 0x00000000}, + {0x000081ec, 0x00000000}, + {0x000081f0, 0x00000000}, + {0x000081f4, 0x00000000}, + {0x000081f8, 0x00000000}, + {0x000081fc, 0x00000000}, + {0x00008200, 0x00000000}, + {0x00008204, 0x00000000}, + {0x00008208, 0x00000000}, + {0x0000820c, 0x00000000}, + {0x00008210, 0x00000000}, + {0x00008214, 0x00000000}, + {0x00008218, 0x00000000}, + {0x0000821c, 0x00000000}, + {0x00008220, 0x00000000}, + {0x00008224, 0x00000000}, + {0x00008228, 0x00000000}, + {0x0000822c, 0x00000000}, + {0x00008230, 0x00000000}, + {0x00008234, 0x00000000}, + {0x00008238, 0x00000000}, + {0x0000823c, 0x00000000}, + {0x00008240, 0x00100000}, + {0x00008244, 0x0010f400}, + {0x00008248, 0x00000100}, + {0x0000824c, 0x0001e800}, + {0x00008250, 0x00000000}, + {0x00008254, 0x00000000}, + {0x00008258, 0x00000000}, + {0x0000825c, 0x400000ff}, + {0x00008260, 0x00080922}, + {0x00008264, 0x88a00010}, + {0x00008270, 0x00000000}, + {0x00008274, 0x40000000}, + {0x00008278, 0x003e4180}, + {0x0000827c, 0x00000000}, + {0x00008284, 0x0000002c}, + {0x00008288, 0x0000002c}, + {0x0000828c, 0x000000ff}, + {0x00008294, 0x00000000}, + {0x00008298, 0x00000000}, + {0x0000829c, 0x00000000}, + {0x00008300, 0x00000040}, + {0x00008314, 0x00000000}, + {0x00008328, 0x00000000}, + {0x0000832c, 0x00000007}, + {0x00008330, 0x00000302}, + {0x00008334, 0x00000e00}, + {0x00008338, 0x00ff0000}, + {0x0000833c, 0x00000000}, + {0x00008340, 0x000107ff}, + {0x00008344, 0x01c81043}, + {0x00008360, 0xffffffff}, + {0x00008364, 0xffffffff}, + {0x00008368, 0x00000000}, + {0x00008370, 0x00000000}, + {0x00008374, 0x000000ff}, + {0x00008378, 0x00000000}, + {0x0000837c, 0x00000000}, + {0x00008380, 0xffffffff}, + {0x00008384, 0xffffffff}, + {0x00008390, 0x0fffffff}, + {0x00008394, 0x0fffffff}, + {0x00008398, 0x00000000}, + {0x0000839c, 0x00000000}, + {0x000083a0, 0x00000000}, + {0x00009808, 0x00000000}, + {0x0000980c, 0xafe68e30}, + {0x00009810, 0xfd14e000}, + {0x00009814, 0x9c0a9f6b}, + {0x0000981c, 0x00000000}, + {0x0000982c, 0x0000a000}, + {0x00009830, 0x00000000}, + {0x0000983c, 0x00200400}, + {0x0000984c, 0x0040233c}, + {0x0000a84c, 0x0040233c}, + {0x00009854, 0x00000044}, + {0x00009900, 0x00000000}, + {0x00009904, 0x00000000}, + {0x00009908, 0x00000000}, + {0x0000990c, 0x00000000}, + {0x00009910, 0x10002310}, + {0x0000991c, 0x10000fff}, + {0x00009920, 0x04900000}, + {0x0000a920, 0x04900000}, + {0x00009928, 0x00000001}, + {0x0000992c, 0x00000004}, + {0x00009930, 0x00000000}, + {0x0000a930, 0x00000000}, + {0x00009934, 0x1e1f2022}, + {0x00009938, 0x0a0b0c0d}, + {0x0000993c, 0x00000000}, + {0x00009948, 0x9280c00a}, + {0x0000994c, 0x00020028}, + {0x00009954, 0x5f3ca3de}, + {0x00009958, 0x0108ecff}, + {0x00009940, 0x14750604}, + {0x0000c95c, 0x004b6a8e}, + {0x00009970, 0x990bb514}, + {0x00009974, 0x00000000}, + {0x00009978, 0x00000001}, + {0x0000997c, 0x00000000}, + {0x000099a0, 0x00000000}, + {0x000099a4, 0x00000001}, + {0x000099a8, 0x201fff00}, + {0x000099ac, 0x0c6f0000}, + {0x000099b0, 0x03051000}, + {0x000099b4, 0x00000820}, + {0x000099c4, 0x06336f77}, + {0x000099c8, 0x6af6532f}, + {0x000099cc, 0x08f186c8}, + {0x000099d0, 0x00046384}, + {0x000099dc, 0x00000000}, + {0x000099e0, 0x00000000}, + {0x000099e4, 0xaaaaaaaa}, + {0x000099e8, 0x3c466478}, + {0x000099ec, 0x0cc80caa}, + {0x000099f0, 0x00000000}, + {0x000099fc, 0x00001042}, + {0x0000a208, 0x803e4788}, + {0x0000a210, 0x4080a333}, + {0x0000a214, 0x40206c10}, + {0x0000a218, 0x009c4060}, + {0x0000a220, 0x01834061}, + {0x0000a224, 0x00000400}, + {0x0000a228, 0x000003b5}, + {0x0000a22c, 0x233f7180}, + {0x0000a234, 0x20202020}, + {0x0000a238, 0x20202020}, + {0x0000a23c, 0x13c889af}, + {0x0000a240, 0x38490a20}, + {0x0000a244, 0x00000000}, + {0x0000a248, 0xfffffffc}, + {0x0000a24c, 0x00000000}, + {0x0000a254, 0x00000000}, + {0x0000a258, 0x0cdbd380}, + {0x0000a25c, 0x0f0f0f01}, + {0x0000a260, 0xdfa91f01}, + {0x0000a264, 0x00418a11}, + {0x0000b264, 0x00418a11}, + {0x0000a268, 0x00000000}, + {0x0000a26c, 0x0e79e5c6}, + {0x0000b26c, 0x0e79e5c6}, + {0x0000d270, 0x00820820}, + {0x0000a278, 0x1ce739ce}, + {0x0000a27c, 0x050701ce}, + {0x0000d35c, 0x07ffffef}, + {0x0000d360, 0x0fffffe7}, + {0x0000d364, 0x17ffffe5}, + {0x0000d368, 0x1fffffe4}, + {0x0000d36c, 0x37ffffe3}, + {0x0000d370, 0x3fffffe3}, + {0x0000d374, 0x57ffffe3}, + {0x0000d378, 0x5fffffe2}, + {0x0000d37c, 0x7fffffe2}, + {0x0000d380, 0x7f3c7bba}, + {0x0000d384, 0xf3307ff0}, + {0x0000a388, 0x0c000000}, + {0x0000a38c, 0x20202020}, + {0x0000a390, 0x20202020}, + {0x0000a394, 0x1ce739ce}, + {0x0000a398, 0x000001ce}, + {0x0000b398, 0x000001ce}, + {0x0000a39c, 0x00000001}, + {0x0000a3c8, 0x00000246}, + {0x0000a3cc, 0x20202020}, + {0x0000a3d0, 0x20202020}, + {0x0000a3d4, 0x20202020}, + {0x0000a3dc, 0x1ce739ce}, + {0x0000a3e0, 0x000001ce}, + {0x0000a3e4, 0x00000000}, + {0x0000a3e8, 0x18c43433}, + {0x0000a3ec, 0x00f70081}, + {0x0000a3f0, 0x01036a1e}, + {0x0000a3f4, 0x00000000}, + {0x0000b3f4, 0x00000000}, + {0x0000a7d8, 0x000003f1}, + {0x00007800, 0x00000800}, + {0x00007804, 0x6c35ffd2}, + {0x00007808, 0x6db6c000}, + {0x0000780c, 0x6db6cb30}, + {0x00007810, 0x6db6cb6c}, + {0x00007814, 0x0501e200}, + {0x00007818, 0x0094128d}, + {0x0000781c, 0x976ee392}, + {0x00007820, 0xf75ff6fc}, + {0x00007824, 0x00040000}, + {0x00007828, 0xdb003012}, + {0x0000782c, 0x04924914}, + {0x00007830, 0x21084210}, + {0x00007834, 0x00140000}, + {0x00007838, 0x0e4548d8}, + {0x0000783c, 0x54214514}, + {0x00007840, 0x02025830}, + {0x00007844, 0x71c0d388}, + {0x00007848, 0x934934a8}, + {0x00007850, 0x00000000}, + {0x00007854, 0x00000800}, + {0x00007858, 0x6c35ffd2}, + {0x0000785c, 0x6db6c000}, + {0x00007860, 0x6db6cb30}, + {0x00007864, 0x6db6cb6c}, + {0x00007868, 0x0501e200}, + {0x0000786c, 0x0094128d}, + {0x00007870, 0x976ee392}, + {0x00007874, 0xf75ff6fc}, + {0x00007878, 0x00040000}, + {0x0000787c, 0xdb003012}, + {0x00007880, 0x04924914}, + {0x00007884, 0x21084210}, + {0x00007888, 0x001b6db0}, + {0x0000788c, 0x00376b63}, + {0x00007890, 0x06db6db6}, + {0x00007894, 0x006d8000}, + {0x00007898, 0x48100000}, + {0x0000789c, 0x00000000}, + {0x000078a0, 0x08000000}, + {0x000078a4, 0x0007ffd8}, + {0x000078a8, 0x0007ffd8}, + {0x000078ac, 0x001c0020}, + {0x000078b0, 0x00060aeb}, + {0x000078b4, 0x40008080}, + {0x000078b8, 0x2a850160}, +}; + +static const u32 ar9287Common_normal_cck_fir_coeff_9287_1_1[][2] = { + /* Addr allmodes */ + {0x0000a1f4, 0x00fffeff}, + {0x0000a1f8, 0x00f5f9ff}, + {0x0000a1fc, 0xb79f6427}, +}; + +static const u32 ar9287Common_japan_2484_cck_fir_coeff_9287_1_1[][2] = { + /* Addr allmodes */ + {0x0000a1f4, 0x00000000}, + {0x0000a1f8, 0xefff0301}, + {0x0000a1fc, 0xca9228ee}, +}; + +static const u32 ar9287Modes_tx_gain_9287_1_1[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a300, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a304, 0x00000000, 0x00000000, 0x00004002, 0x00004002}, + {0x0000a308, 0x00000000, 0x00000000, 0x00008004, 0x00008004}, + {0x0000a30c, 0x00000000, 0x00000000, 0x0000c00a, 0x0000c00a}, + {0x0000a310, 0x00000000, 0x00000000, 0x0001000c, 0x0001000c}, + {0x0000a314, 0x00000000, 0x00000000, 0x0001420b, 0x0001420b}, + {0x0000a318, 0x00000000, 0x00000000, 0x0001824a, 0x0001824a}, + {0x0000a31c, 0x00000000, 0x00000000, 0x0001c44a, 0x0001c44a}, + {0x0000a320, 0x00000000, 0x00000000, 0x0002064a, 0x0002064a}, + {0x0000a324, 0x00000000, 0x00000000, 0x0002484a, 0x0002484a}, + {0x0000a328, 0x00000000, 0x00000000, 0x00028a4a, 0x00028a4a}, + {0x0000a32c, 0x00000000, 0x00000000, 0x0002cc4a, 0x0002cc4a}, + {0x0000a330, 0x00000000, 0x00000000, 0x00030e4a, 0x00030e4a}, + {0x0000a334, 0x00000000, 0x00000000, 0x00034e8a, 0x00034e8a}, + {0x0000a338, 0x00000000, 0x00000000, 0x00038e8c, 0x00038e8c}, + {0x0000a33c, 0x00000000, 0x00000000, 0x0003cecc, 0x0003cecc}, + {0x0000a340, 0x00000000, 0x00000000, 0x00040ed4, 0x00040ed4}, + {0x0000a344, 0x00000000, 0x00000000, 0x00044edc, 0x00044edc}, + {0x0000a348, 0x00000000, 0x00000000, 0x00048ede, 0x00048ede}, + {0x0000a34c, 0x00000000, 0x00000000, 0x0004cf1e, 0x0004cf1e}, + {0x0000a350, 0x00000000, 0x00000000, 0x00050f5e, 0x00050f5e}, + {0x0000a354, 0x00000000, 0x00000000, 0x00054f9e, 0x00054f9e}, + {0x0000a780, 0x00000000, 0x00000000, 0x00000062, 0x00000062}, + {0x0000a784, 0x00000000, 0x00000000, 0x00004064, 0x00004064}, + {0x0000a788, 0x00000000, 0x00000000, 0x000080a4, 0x000080a4}, + {0x0000a78c, 0x00000000, 0x00000000, 0x0000c0aa, 0x0000c0aa}, + {0x0000a790, 0x00000000, 0x00000000, 0x000100ac, 0x000100ac}, + {0x0000a794, 0x00000000, 0x00000000, 0x000140b4, 0x000140b4}, + {0x0000a798, 0x00000000, 0x00000000, 0x000180f4, 0x000180f4}, + {0x0000a79c, 0x00000000, 0x00000000, 0x0001c134, 0x0001c134}, + {0x0000a7a0, 0x00000000, 0x00000000, 0x00020174, 0x00020174}, + {0x0000a7a4, 0x00000000, 0x00000000, 0x0002417c, 0x0002417c}, + {0x0000a7a8, 0x00000000, 0x00000000, 0x0002817e, 0x0002817e}, + {0x0000a7ac, 0x00000000, 0x00000000, 0x0002c1be, 0x0002c1be}, + {0x0000a7b0, 0x00000000, 0x00000000, 0x000301fe, 0x000301fe}, + {0x0000a7b4, 0x00000000, 0x00000000, 0x000301fe, 0x000301fe}, + {0x0000a7b8, 0x00000000, 0x00000000, 0x000301fe, 0x000301fe}, + {0x0000a7bc, 0x00000000, 0x00000000, 0x000301fe, 0x000301fe}, + {0x0000a7c0, 0x00000000, 0x00000000, 0x000301fe, 0x000301fe}, + {0x0000a7c4, 0x00000000, 0x00000000, 0x000301fe, 0x000301fe}, + {0x0000a7c8, 0x00000000, 0x00000000, 0x000301fe, 0x000301fe}, + {0x0000a7cc, 0x00000000, 0x00000000, 0x000301fe, 0x000301fe}, + {0x0000a7d0, 0x00000000, 0x00000000, 0x000301fe, 0x000301fe}, + {0x0000a7d4, 0x00000000, 0x00000000, 0x000301fe, 0x000301fe}, + {0x0000a274, 0x0a180000, 0x0a180000, 0x0a1aa000, 0x0a1aa000}, +}; + +static const u32 ar9287Modes_rx_gain_9287_1_1[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009a00, 0x00000000, 0x00000000, 0x0000a120, 0x0000a120}, + {0x00009a04, 0x00000000, 0x00000000, 0x0000a124, 0x0000a124}, + {0x00009a08, 0x00000000, 0x00000000, 0x0000a128, 0x0000a128}, + {0x00009a0c, 0x00000000, 0x00000000, 0x0000a12c, 0x0000a12c}, + {0x00009a10, 0x00000000, 0x00000000, 0x0000a130, 0x0000a130}, + {0x00009a14, 0x00000000, 0x00000000, 0x0000a194, 0x0000a194}, + {0x00009a18, 0x00000000, 0x00000000, 0x0000a198, 0x0000a198}, + {0x00009a1c, 0x00000000, 0x00000000, 0x0000a20c, 0x0000a20c}, + {0x00009a20, 0x00000000, 0x00000000, 0x0000a210, 0x0000a210}, + {0x00009a24, 0x00000000, 0x00000000, 0x0000a284, 0x0000a284}, + {0x00009a28, 0x00000000, 0x00000000, 0x0000a288, 0x0000a288}, + {0x00009a2c, 0x00000000, 0x00000000, 0x0000a28c, 0x0000a28c}, + {0x00009a30, 0x00000000, 0x00000000, 0x0000a290, 0x0000a290}, + {0x00009a34, 0x00000000, 0x00000000, 0x0000a294, 0x0000a294}, + {0x00009a38, 0x00000000, 0x00000000, 0x0000a2a0, 0x0000a2a0}, + {0x00009a3c, 0x00000000, 0x00000000, 0x0000a2a4, 0x0000a2a4}, + {0x00009a40, 0x00000000, 0x00000000, 0x0000a2a8, 0x0000a2a8}, + {0x00009a44, 0x00000000, 0x00000000, 0x0000a2ac, 0x0000a2ac}, + {0x00009a48, 0x00000000, 0x00000000, 0x0000a2b0, 0x0000a2b0}, + {0x00009a4c, 0x00000000, 0x00000000, 0x0000a2b4, 0x0000a2b4}, + {0x00009a50, 0x00000000, 0x00000000, 0x0000a2b8, 0x0000a2b8}, + {0x00009a54, 0x00000000, 0x00000000, 0x0000a2c4, 0x0000a2c4}, + {0x00009a58, 0x00000000, 0x00000000, 0x0000a708, 0x0000a708}, + {0x00009a5c, 0x00000000, 0x00000000, 0x0000a70c, 0x0000a70c}, + {0x00009a60, 0x00000000, 0x00000000, 0x0000a710, 0x0000a710}, + {0x00009a64, 0x00000000, 0x00000000, 0x0000ab04, 0x0000ab04}, + {0x00009a68, 0x00000000, 0x00000000, 0x0000ab08, 0x0000ab08}, + {0x00009a6c, 0x00000000, 0x00000000, 0x0000ab0c, 0x0000ab0c}, + {0x00009a70, 0x00000000, 0x00000000, 0x0000ab10, 0x0000ab10}, + {0x00009a74, 0x00000000, 0x00000000, 0x0000ab14, 0x0000ab14}, + {0x00009a78, 0x00000000, 0x00000000, 0x0000ab18, 0x0000ab18}, + {0x00009a7c, 0x00000000, 0x00000000, 0x0000ab8c, 0x0000ab8c}, + {0x00009a80, 0x00000000, 0x00000000, 0x0000ab90, 0x0000ab90}, + {0x00009a84, 0x00000000, 0x00000000, 0x0000ab94, 0x0000ab94}, + {0x00009a88, 0x00000000, 0x00000000, 0x0000ab98, 0x0000ab98}, + {0x00009a8c, 0x00000000, 0x00000000, 0x0000aba4, 0x0000aba4}, + {0x00009a90, 0x00000000, 0x00000000, 0x0000aba8, 0x0000aba8}, + {0x00009a94, 0x00000000, 0x00000000, 0x0000cb04, 0x0000cb04}, + {0x00009a98, 0x00000000, 0x00000000, 0x0000cb08, 0x0000cb08}, + {0x00009a9c, 0x00000000, 0x00000000, 0x0000cb0c, 0x0000cb0c}, + {0x00009aa0, 0x00000000, 0x00000000, 0x0000cb10, 0x0000cb10}, + {0x00009aa4, 0x00000000, 0x00000000, 0x0000cb14, 0x0000cb14}, + {0x00009aa8, 0x00000000, 0x00000000, 0x0000cb18, 0x0000cb18}, + {0x00009aac, 0x00000000, 0x00000000, 0x0000cb8c, 0x0000cb8c}, + {0x00009ab0, 0x00000000, 0x00000000, 0x0000cb90, 0x0000cb90}, + {0x00009ab4, 0x00000000, 0x00000000, 0x0000cf18, 0x0000cf18}, + {0x00009ab8, 0x00000000, 0x00000000, 0x0000cf24, 0x0000cf24}, + {0x00009abc, 0x00000000, 0x00000000, 0x0000cf28, 0x0000cf28}, + {0x00009ac0, 0x00000000, 0x00000000, 0x0000d314, 0x0000d314}, + {0x00009ac4, 0x00000000, 0x00000000, 0x0000d318, 0x0000d318}, + {0x00009ac8, 0x00000000, 0x00000000, 0x0000d38c, 0x0000d38c}, + {0x00009acc, 0x00000000, 0x00000000, 0x0000d390, 0x0000d390}, + {0x00009ad0, 0x00000000, 0x00000000, 0x0000d394, 0x0000d394}, + {0x00009ad4, 0x00000000, 0x00000000, 0x0000d398, 0x0000d398}, + {0x00009ad8, 0x00000000, 0x00000000, 0x0000d3a4, 0x0000d3a4}, + {0x00009adc, 0x00000000, 0x00000000, 0x0000d3a8, 0x0000d3a8}, + {0x00009ae0, 0x00000000, 0x00000000, 0x0000d3ac, 0x0000d3ac}, + {0x00009ae4, 0x00000000, 0x00000000, 0x0000d3b0, 0x0000d3b0}, + {0x00009ae8, 0x00000000, 0x00000000, 0x0000f380, 0x0000f380}, + {0x00009aec, 0x00000000, 0x00000000, 0x0000f384, 0x0000f384}, + {0x00009af0, 0x00000000, 0x00000000, 0x0000f388, 0x0000f388}, + {0x00009af4, 0x00000000, 0x00000000, 0x0000f710, 0x0000f710}, + {0x00009af8, 0x00000000, 0x00000000, 0x0000f714, 0x0000f714}, + {0x00009afc, 0x00000000, 0x00000000, 0x0000f718, 0x0000f718}, + {0x00009b00, 0x00000000, 0x00000000, 0x0000fb10, 0x0000fb10}, + {0x00009b04, 0x00000000, 0x00000000, 0x0000fb14, 0x0000fb14}, + {0x00009b08, 0x00000000, 0x00000000, 0x0000fb18, 0x0000fb18}, + {0x00009b0c, 0x00000000, 0x00000000, 0x0000fb8c, 0x0000fb8c}, + {0x00009b10, 0x00000000, 0x00000000, 0x0000fb90, 0x0000fb90}, + {0x00009b14, 0x00000000, 0x00000000, 0x0000fb94, 0x0000fb94}, + {0x00009b18, 0x00000000, 0x00000000, 0x0000ff8c, 0x0000ff8c}, + {0x00009b1c, 0x00000000, 0x00000000, 0x0000ff90, 0x0000ff90}, + {0x00009b20, 0x00000000, 0x00000000, 0x0000ff94, 0x0000ff94}, + {0x00009b24, 0x00000000, 0x00000000, 0x0000ffa0, 0x0000ffa0}, + {0x00009b28, 0x00000000, 0x00000000, 0x0000ffa4, 0x0000ffa4}, + {0x00009b2c, 0x00000000, 0x00000000, 0x0000ffa8, 0x0000ffa8}, + {0x00009b30, 0x00000000, 0x00000000, 0x0000ffac, 0x0000ffac}, + {0x00009b34, 0x00000000, 0x00000000, 0x0000ffb0, 0x0000ffb0}, + {0x00009b38, 0x00000000, 0x00000000, 0x0000ffb4, 0x0000ffb4}, + {0x00009b3c, 0x00000000, 0x00000000, 0x0000ffa1, 0x0000ffa1}, + {0x00009b40, 0x00000000, 0x00000000, 0x0000ffa5, 0x0000ffa5}, + {0x00009b44, 0x00000000, 0x00000000, 0x0000ffa9, 0x0000ffa9}, + {0x00009b48, 0x00000000, 0x00000000, 0x0000ffad, 0x0000ffad}, + {0x00009b4c, 0x00000000, 0x00000000, 0x0000ffb1, 0x0000ffb1}, + {0x00009b50, 0x00000000, 0x00000000, 0x0000ffb5, 0x0000ffb5}, + {0x00009b54, 0x00000000, 0x00000000, 0x0000ffb9, 0x0000ffb9}, + {0x00009b58, 0x00000000, 0x00000000, 0x0000ffc5, 0x0000ffc5}, + {0x00009b5c, 0x00000000, 0x00000000, 0x0000ffc9, 0x0000ffc9}, + {0x00009b60, 0x00000000, 0x00000000, 0x0000ffcd, 0x0000ffcd}, + {0x00009b64, 0x00000000, 0x00000000, 0x0000ffd1, 0x0000ffd1}, + {0x00009b68, 0x00000000, 0x00000000, 0x0000ffd5, 0x0000ffd5}, + {0x00009b6c, 0x00000000, 0x00000000, 0x0000ffc2, 0x0000ffc2}, + {0x00009b70, 0x00000000, 0x00000000, 0x0000ffc6, 0x0000ffc6}, + {0x00009b74, 0x00000000, 0x00000000, 0x0000ffca, 0x0000ffca}, + {0x00009b78, 0x00000000, 0x00000000, 0x0000ffce, 0x0000ffce}, + {0x00009b7c, 0x00000000, 0x00000000, 0x0000ffd2, 0x0000ffd2}, + {0x00009b80, 0x00000000, 0x00000000, 0x0000ffd6, 0x0000ffd6}, + {0x00009b84, 0x00000000, 0x00000000, 0x0000ffda, 0x0000ffda}, + {0x00009b88, 0x00000000, 0x00000000, 0x0000ffc7, 0x0000ffc7}, + {0x00009b8c, 0x00000000, 0x00000000, 0x0000ffcb, 0x0000ffcb}, + {0x00009b90, 0x00000000, 0x00000000, 0x0000ffcf, 0x0000ffcf}, + {0x00009b94, 0x00000000, 0x00000000, 0x0000ffd3, 0x0000ffd3}, + {0x00009b98, 0x00000000, 0x00000000, 0x0000ffd7, 0x0000ffd7}, + {0x00009b9c, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009ba0, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009ba4, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009ba8, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bac, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bb0, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bb4, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bb8, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bbc, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bc0, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bc4, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bc8, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bcc, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bd0, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bd4, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bd8, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bdc, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009be0, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009be4, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009be8, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bec, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bf0, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bf4, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bf8, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009bfc, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000aa00, 0x00000000, 0x00000000, 0x0000a120, 0x0000a120}, + {0x0000aa04, 0x00000000, 0x00000000, 0x0000a124, 0x0000a124}, + {0x0000aa08, 0x00000000, 0x00000000, 0x0000a128, 0x0000a128}, + {0x0000aa0c, 0x00000000, 0x00000000, 0x0000a12c, 0x0000a12c}, + {0x0000aa10, 0x00000000, 0x00000000, 0x0000a130, 0x0000a130}, + {0x0000aa14, 0x00000000, 0x00000000, 0x0000a194, 0x0000a194}, + {0x0000aa18, 0x00000000, 0x00000000, 0x0000a198, 0x0000a198}, + {0x0000aa1c, 0x00000000, 0x00000000, 0x0000a20c, 0x0000a20c}, + {0x0000aa20, 0x00000000, 0x00000000, 0x0000a210, 0x0000a210}, + {0x0000aa24, 0x00000000, 0x00000000, 0x0000a284, 0x0000a284}, + {0x0000aa28, 0x00000000, 0x00000000, 0x0000a288, 0x0000a288}, + {0x0000aa2c, 0x00000000, 0x00000000, 0x0000a28c, 0x0000a28c}, + {0x0000aa30, 0x00000000, 0x00000000, 0x0000a290, 0x0000a290}, + {0x0000aa34, 0x00000000, 0x00000000, 0x0000a294, 0x0000a294}, + {0x0000aa38, 0x00000000, 0x00000000, 0x0000a2a0, 0x0000a2a0}, + {0x0000aa3c, 0x00000000, 0x00000000, 0x0000a2a4, 0x0000a2a4}, + {0x0000aa40, 0x00000000, 0x00000000, 0x0000a2a8, 0x0000a2a8}, + {0x0000aa44, 0x00000000, 0x00000000, 0x0000a2ac, 0x0000a2ac}, + {0x0000aa48, 0x00000000, 0x00000000, 0x0000a2b0, 0x0000a2b0}, + {0x0000aa4c, 0x00000000, 0x00000000, 0x0000a2b4, 0x0000a2b4}, + {0x0000aa50, 0x00000000, 0x00000000, 0x0000a2b8, 0x0000a2b8}, + {0x0000aa54, 0x00000000, 0x00000000, 0x0000a2c4, 0x0000a2c4}, + {0x0000aa58, 0x00000000, 0x00000000, 0x0000a708, 0x0000a708}, + {0x0000aa5c, 0x00000000, 0x00000000, 0x0000a70c, 0x0000a70c}, + {0x0000aa60, 0x00000000, 0x00000000, 0x0000a710, 0x0000a710}, + {0x0000aa64, 0x00000000, 0x00000000, 0x0000ab04, 0x0000ab04}, + {0x0000aa68, 0x00000000, 0x00000000, 0x0000ab08, 0x0000ab08}, + {0x0000aa6c, 0x00000000, 0x00000000, 0x0000ab0c, 0x0000ab0c}, + {0x0000aa70, 0x00000000, 0x00000000, 0x0000ab10, 0x0000ab10}, + {0x0000aa74, 0x00000000, 0x00000000, 0x0000ab14, 0x0000ab14}, + {0x0000aa78, 0x00000000, 0x00000000, 0x0000ab18, 0x0000ab18}, + {0x0000aa7c, 0x00000000, 0x00000000, 0x0000ab8c, 0x0000ab8c}, + {0x0000aa80, 0x00000000, 0x00000000, 0x0000ab90, 0x0000ab90}, + {0x0000aa84, 0x00000000, 0x00000000, 0x0000ab94, 0x0000ab94}, + {0x0000aa88, 0x00000000, 0x00000000, 0x0000ab98, 0x0000ab98}, + {0x0000aa8c, 0x00000000, 0x00000000, 0x0000aba4, 0x0000aba4}, + {0x0000aa90, 0x00000000, 0x00000000, 0x0000aba8, 0x0000aba8}, + {0x0000aa94, 0x00000000, 0x00000000, 0x0000cb04, 0x0000cb04}, + {0x0000aa98, 0x00000000, 0x00000000, 0x0000cb08, 0x0000cb08}, + {0x0000aa9c, 0x00000000, 0x00000000, 0x0000cb0c, 0x0000cb0c}, + {0x0000aaa0, 0x00000000, 0x00000000, 0x0000cb10, 0x0000cb10}, + {0x0000aaa4, 0x00000000, 0x00000000, 0x0000cb14, 0x0000cb14}, + {0x0000aaa8, 0x00000000, 0x00000000, 0x0000cb18, 0x0000cb18}, + {0x0000aaac, 0x00000000, 0x00000000, 0x0000cb8c, 0x0000cb8c}, + {0x0000aab0, 0x00000000, 0x00000000, 0x0000cb90, 0x0000cb90}, + {0x0000aab4, 0x00000000, 0x00000000, 0x0000cf18, 0x0000cf18}, + {0x0000aab8, 0x00000000, 0x00000000, 0x0000cf24, 0x0000cf24}, + {0x0000aabc, 0x00000000, 0x00000000, 0x0000cf28, 0x0000cf28}, + {0x0000aac0, 0x00000000, 0x00000000, 0x0000d314, 0x0000d314}, + {0x0000aac4, 0x00000000, 0x00000000, 0x0000d318, 0x0000d318}, + {0x0000aac8, 0x00000000, 0x00000000, 0x0000d38c, 0x0000d38c}, + {0x0000aacc, 0x00000000, 0x00000000, 0x0000d390, 0x0000d390}, + {0x0000aad0, 0x00000000, 0x00000000, 0x0000d394, 0x0000d394}, + {0x0000aad4, 0x00000000, 0x00000000, 0x0000d398, 0x0000d398}, + {0x0000aad8, 0x00000000, 0x00000000, 0x0000d3a4, 0x0000d3a4}, + {0x0000aadc, 0x00000000, 0x00000000, 0x0000d3a8, 0x0000d3a8}, + {0x0000aae0, 0x00000000, 0x00000000, 0x0000d3ac, 0x0000d3ac}, + {0x0000aae4, 0x00000000, 0x00000000, 0x0000d3b0, 0x0000d3b0}, + {0x0000aae8, 0x00000000, 0x00000000, 0x0000f380, 0x0000f380}, + {0x0000aaec, 0x00000000, 0x00000000, 0x0000f384, 0x0000f384}, + {0x0000aaf0, 0x00000000, 0x00000000, 0x0000f388, 0x0000f388}, + {0x0000aaf4, 0x00000000, 0x00000000, 0x0000f710, 0x0000f710}, + {0x0000aaf8, 0x00000000, 0x00000000, 0x0000f714, 0x0000f714}, + {0x0000aafc, 0x00000000, 0x00000000, 0x0000f718, 0x0000f718}, + {0x0000ab00, 0x00000000, 0x00000000, 0x0000fb10, 0x0000fb10}, + {0x0000ab04, 0x00000000, 0x00000000, 0x0000fb14, 0x0000fb14}, + {0x0000ab08, 0x00000000, 0x00000000, 0x0000fb18, 0x0000fb18}, + {0x0000ab0c, 0x00000000, 0x00000000, 0x0000fb8c, 0x0000fb8c}, + {0x0000ab10, 0x00000000, 0x00000000, 0x0000fb90, 0x0000fb90}, + {0x0000ab14, 0x00000000, 0x00000000, 0x0000fb94, 0x0000fb94}, + {0x0000ab18, 0x00000000, 0x00000000, 0x0000ff8c, 0x0000ff8c}, + {0x0000ab1c, 0x00000000, 0x00000000, 0x0000ff90, 0x0000ff90}, + {0x0000ab20, 0x00000000, 0x00000000, 0x0000ff94, 0x0000ff94}, + {0x0000ab24, 0x00000000, 0x00000000, 0x0000ffa0, 0x0000ffa0}, + {0x0000ab28, 0x00000000, 0x00000000, 0x0000ffa4, 0x0000ffa4}, + {0x0000ab2c, 0x00000000, 0x00000000, 0x0000ffa8, 0x0000ffa8}, + {0x0000ab30, 0x00000000, 0x00000000, 0x0000ffac, 0x0000ffac}, + {0x0000ab34, 0x00000000, 0x00000000, 0x0000ffb0, 0x0000ffb0}, + {0x0000ab38, 0x00000000, 0x00000000, 0x0000ffb4, 0x0000ffb4}, + {0x0000ab3c, 0x00000000, 0x00000000, 0x0000ffa1, 0x0000ffa1}, + {0x0000ab40, 0x00000000, 0x00000000, 0x0000ffa5, 0x0000ffa5}, + {0x0000ab44, 0x00000000, 0x00000000, 0x0000ffa9, 0x0000ffa9}, + {0x0000ab48, 0x00000000, 0x00000000, 0x0000ffad, 0x0000ffad}, + {0x0000ab4c, 0x00000000, 0x00000000, 0x0000ffb1, 0x0000ffb1}, + {0x0000ab50, 0x00000000, 0x00000000, 0x0000ffb5, 0x0000ffb5}, + {0x0000ab54, 0x00000000, 0x00000000, 0x0000ffb9, 0x0000ffb9}, + {0x0000ab58, 0x00000000, 0x00000000, 0x0000ffc5, 0x0000ffc5}, + {0x0000ab5c, 0x00000000, 0x00000000, 0x0000ffc9, 0x0000ffc9}, + {0x0000ab60, 0x00000000, 0x00000000, 0x0000ffcd, 0x0000ffcd}, + {0x0000ab64, 0x00000000, 0x00000000, 0x0000ffd1, 0x0000ffd1}, + {0x0000ab68, 0x00000000, 0x00000000, 0x0000ffd5, 0x0000ffd5}, + {0x0000ab6c, 0x00000000, 0x00000000, 0x0000ffc2, 0x0000ffc2}, + {0x0000ab70, 0x00000000, 0x00000000, 0x0000ffc6, 0x0000ffc6}, + {0x0000ab74, 0x00000000, 0x00000000, 0x0000ffca, 0x0000ffca}, + {0x0000ab78, 0x00000000, 0x00000000, 0x0000ffce, 0x0000ffce}, + {0x0000ab7c, 0x00000000, 0x00000000, 0x0000ffd2, 0x0000ffd2}, + {0x0000ab80, 0x00000000, 0x00000000, 0x0000ffd6, 0x0000ffd6}, + {0x0000ab84, 0x00000000, 0x00000000, 0x0000ffda, 0x0000ffda}, + {0x0000ab88, 0x00000000, 0x00000000, 0x0000ffc7, 0x0000ffc7}, + {0x0000ab8c, 0x00000000, 0x00000000, 0x0000ffcb, 0x0000ffcb}, + {0x0000ab90, 0x00000000, 0x00000000, 0x0000ffcf, 0x0000ffcf}, + {0x0000ab94, 0x00000000, 0x00000000, 0x0000ffd3, 0x0000ffd3}, + {0x0000ab98, 0x00000000, 0x00000000, 0x0000ffd7, 0x0000ffd7}, + {0x0000ab9c, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000aba0, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000aba4, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000aba8, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abac, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abb0, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abb4, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abb8, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abbc, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abc0, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abc4, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abc8, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abcc, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abd0, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abd4, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abd8, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abdc, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abe0, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abe4, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abe8, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abec, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abf0, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abf4, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abf8, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x0000abfc, 0x00000000, 0x00000000, 0x0000ffdb, 0x0000ffdb}, + {0x00009848, 0x00000000, 0x00000000, 0x00001067, 0x00001067}, + {0x0000a848, 0x00000000, 0x00000000, 0x00001067, 0x00001067}, +}; + +static const u32 ar9271Modes_9271[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00001030, 0x00000230, 0x00000460, 0x000002c0, 0x00000160}, + {0x00001070, 0x00000168, 0x000002d0, 0x00000318, 0x0000018c}, + {0x000010b0, 0x00000e60, 0x00001cc0, 0x00007c70, 0x00003e38}, + {0x000010f0, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00008014, 0x03e803e8, 0x07d007d0, 0x10801600, 0x08400b00}, + {0x0000801c, 0x128d8027, 0x128d804f, 0x12e00057, 0x12e0002b}, + {0x00008318, 0x00003e80, 0x00007d00, 0x00006880, 0x00003440}, + {0x00009804, 0x00000300, 0x000003c4, 0x000003c4, 0x00000300}, + {0x00009820, 0x02020200, 0x02020200, 0x02020200, 0x02020200}, + {0x00009824, 0x01000e0e, 0x01000e0e, 0x01000e0e, 0x01000e0e}, + {0x00009828, 0x3a020001, 0x3a020001, 0x3a020001, 0x3a020001}, + {0x00009834, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x00009838, 0x00000007, 0x00000007, 0x00000007, 0x00000007}, + {0x00009840, 0x206a012e, 0x206a012e, 0x206a012e, 0x206a012e}, + {0x00009844, 0x0372161e, 0x0372161e, 0x03721620, 0x03721620}, + {0x00009848, 0x00001066, 0x00001066, 0x00001053, 0x00001053}, + {0x0000a848, 0x00001066, 0x00001066, 0x00001053, 0x00001053}, + {0x00009850, 0x6d4000e2, 0x6d4000e2, 0x6d4000e2, 0x6d4000e2}, + {0x00009858, 0x7ec84d2e, 0x7ec84d2e, 0x7ec84d2e, 0x7ec84d2e}, + {0x0000985c, 0x3139605e, 0x3139605e, 0x3137605e, 0x3137605e}, + {0x00009860, 0x00058d18, 0x00058d18, 0x00058d18, 0x00058d18}, + {0x00009864, 0x0000fe00, 0x0000fe00, 0x0001ce00, 0x0001ce00}, + {0x00009868, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0}, + {0x0000986c, 0x06903081, 0x06903081, 0x06903881, 0x06903881}, + {0x00009910, 0x30002310, 0x30002310, 0x30002310, 0x30002310}, + {0x00009914, 0x000007d0, 0x00000fa0, 0x00001130, 0x00000898}, + {0x00009918, 0x0000000a, 0x00000014, 0x00000016, 0x0000000b}, + {0x00009924, 0xd00a8007, 0xd00a8007, 0xd00a800d, 0xd00a800d}, + {0x00009944, 0xffbc1010, 0xffbc1010, 0xffbc1020, 0xffbc1020}, + {0x00009960, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00009964, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x000099b8, 0x0000421c, 0x0000421c, 0x0000421c, 0x0000421c}, + {0x000099bc, 0x00000600, 0x00000600, 0x00000c00, 0x00000c00}, + {0x000099c0, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4}, + {0x000099c4, 0x06336f77, 0x06336f77, 0x06336f77, 0x06336f77}, + {0x000099c8, 0x6af6532f, 0x6af6532f, 0x6af6532f, 0x6af6532f}, + {0x000099cc, 0x08f186c8, 0x08f186c8, 0x08f186c8, 0x08f186c8}, + {0x000099d0, 0x00046384, 0x00046384, 0x00046384, 0x00046384}, + {0x000099d4, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x000099d8, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00009a00, 0x00000000, 0x00000000, 0x00058084, 0x00058084}, + {0x00009a04, 0x00000000, 0x00000000, 0x00058088, 0x00058088}, + {0x00009a08, 0x00000000, 0x00000000, 0x0005808c, 0x0005808c}, + {0x00009a0c, 0x00000000, 0x00000000, 0x00058100, 0x00058100}, + {0x00009a10, 0x00000000, 0x00000000, 0x00058104, 0x00058104}, + {0x00009a14, 0x00000000, 0x00000000, 0x00058108, 0x00058108}, + {0x00009a18, 0x00000000, 0x00000000, 0x0005810c, 0x0005810c}, + {0x00009a1c, 0x00000000, 0x00000000, 0x00058110, 0x00058110}, + {0x00009a20, 0x00000000, 0x00000000, 0x00058114, 0x00058114}, + {0x00009a24, 0x00000000, 0x00000000, 0x00058180, 0x00058180}, + {0x00009a28, 0x00000000, 0x00000000, 0x00058184, 0x00058184}, + {0x00009a2c, 0x00000000, 0x00000000, 0x00058188, 0x00058188}, + {0x00009a30, 0x00000000, 0x00000000, 0x0005818c, 0x0005818c}, + {0x00009a34, 0x00000000, 0x00000000, 0x00058190, 0x00058190}, + {0x00009a38, 0x00000000, 0x00000000, 0x00058194, 0x00058194}, + {0x00009a3c, 0x00000000, 0x00000000, 0x000581a0, 0x000581a0}, + {0x00009a40, 0x00000000, 0x00000000, 0x0005820c, 0x0005820c}, + {0x00009a44, 0x00000000, 0x00000000, 0x000581a8, 0x000581a8}, + {0x00009a48, 0x00000000, 0x00000000, 0x00058284, 0x00058284}, + {0x00009a4c, 0x00000000, 0x00000000, 0x00058288, 0x00058288}, + {0x00009a50, 0x00000000, 0x00000000, 0x00058224, 0x00058224}, + {0x00009a54, 0x00000000, 0x00000000, 0x00058290, 0x00058290}, + {0x00009a58, 0x00000000, 0x00000000, 0x00058300, 0x00058300}, + {0x00009a5c, 0x00000000, 0x00000000, 0x00058304, 0x00058304}, + {0x00009a60, 0x00000000, 0x00000000, 0x00058308, 0x00058308}, + {0x00009a64, 0x00000000, 0x00000000, 0x0005830c, 0x0005830c}, + {0x00009a68, 0x00000000, 0x00000000, 0x00058380, 0x00058380}, + {0x00009a6c, 0x00000000, 0x00000000, 0x00058384, 0x00058384}, + {0x00009a70, 0x00000000, 0x00000000, 0x00068700, 0x00068700}, + {0x00009a74, 0x00000000, 0x00000000, 0x00068704, 0x00068704}, + {0x00009a78, 0x00000000, 0x00000000, 0x00068708, 0x00068708}, + {0x00009a7c, 0x00000000, 0x00000000, 0x0006870c, 0x0006870c}, + {0x00009a80, 0x00000000, 0x00000000, 0x00068780, 0x00068780}, + {0x00009a84, 0x00000000, 0x00000000, 0x00068784, 0x00068784}, + {0x00009a88, 0x00000000, 0x00000000, 0x00078b00, 0x00078b00}, + {0x00009a8c, 0x00000000, 0x00000000, 0x00078b04, 0x00078b04}, + {0x00009a90, 0x00000000, 0x00000000, 0x00078b08, 0x00078b08}, + {0x00009a94, 0x00000000, 0x00000000, 0x00078b0c, 0x00078b0c}, + {0x00009a98, 0x00000000, 0x00000000, 0x00078b80, 0x00078b80}, + {0x00009a9c, 0x00000000, 0x00000000, 0x00078b84, 0x00078b84}, + {0x00009aa0, 0x00000000, 0x00000000, 0x00078b88, 0x00078b88}, + {0x00009aa4, 0x00000000, 0x00000000, 0x00078b8c, 0x00078b8c}, + {0x00009aa8, 0x00000000, 0x00000000, 0x00078b90, 0x00078b90}, + {0x00009aac, 0x00000000, 0x00000000, 0x000caf80, 0x000caf80}, + {0x00009ab0, 0x00000000, 0x00000000, 0x000caf84, 0x000caf84}, + {0x00009ab4, 0x00000000, 0x00000000, 0x000caf88, 0x000caf88}, + {0x00009ab8, 0x00000000, 0x00000000, 0x000caf8c, 0x000caf8c}, + {0x00009abc, 0x00000000, 0x00000000, 0x000caf90, 0x000caf90}, + {0x00009ac0, 0x00000000, 0x00000000, 0x000db30c, 0x000db30c}, + {0x00009ac4, 0x00000000, 0x00000000, 0x000db310, 0x000db310}, + {0x00009ac8, 0x00000000, 0x00000000, 0x000db384, 0x000db384}, + {0x00009acc, 0x00000000, 0x00000000, 0x000db388, 0x000db388}, + {0x00009ad0, 0x00000000, 0x00000000, 0x000db324, 0x000db324}, + {0x00009ad4, 0x00000000, 0x00000000, 0x000eb704, 0x000eb704}, + {0x00009ad8, 0x00000000, 0x00000000, 0x000eb6a4, 0x000eb6a4}, + {0x00009adc, 0x00000000, 0x00000000, 0x000eb6a8, 0x000eb6a8}, + {0x00009ae0, 0x00000000, 0x00000000, 0x000eb710, 0x000eb710}, + {0x00009ae4, 0x00000000, 0x00000000, 0x000eb714, 0x000eb714}, + {0x00009ae8, 0x00000000, 0x00000000, 0x000eb720, 0x000eb720}, + {0x00009aec, 0x00000000, 0x00000000, 0x000eb724, 0x000eb724}, + {0x00009af0, 0x00000000, 0x00000000, 0x000eb728, 0x000eb728}, + {0x00009af4, 0x00000000, 0x00000000, 0x000eb72c, 0x000eb72c}, + {0x00009af8, 0x00000000, 0x00000000, 0x000eb7a0, 0x000eb7a0}, + {0x00009afc, 0x00000000, 0x00000000, 0x000eb7a4, 0x000eb7a4}, + {0x00009b00, 0x00000000, 0x00000000, 0x000eb7a8, 0x000eb7a8}, + {0x00009b04, 0x00000000, 0x00000000, 0x000eb7b0, 0x000eb7b0}, + {0x00009b08, 0x00000000, 0x00000000, 0x000eb7b4, 0x000eb7b4}, + {0x00009b0c, 0x00000000, 0x00000000, 0x000eb7b8, 0x000eb7b8}, + {0x00009b10, 0x00000000, 0x00000000, 0x000eb7a5, 0x000eb7a5}, + {0x00009b14, 0x00000000, 0x00000000, 0x000eb7a9, 0x000eb7a9}, + {0x00009b18, 0x00000000, 0x00000000, 0x000eb7ad, 0x000eb7ad}, + {0x00009b1c, 0x00000000, 0x00000000, 0x000eb7b1, 0x000eb7b1}, + {0x00009b20, 0x00000000, 0x00000000, 0x000eb7b5, 0x000eb7b5}, + {0x00009b24, 0x00000000, 0x00000000, 0x000eb7b9, 0x000eb7b9}, + {0x00009b28, 0x00000000, 0x00000000, 0x000eb7c5, 0x000eb7c5}, + {0x00009b2c, 0x00000000, 0x00000000, 0x000eb7c9, 0x000eb7c9}, + {0x00009b30, 0x00000000, 0x00000000, 0x000eb7d1, 0x000eb7d1}, + {0x00009b34, 0x00000000, 0x00000000, 0x000eb7d5, 0x000eb7d5}, + {0x00009b38, 0x00000000, 0x00000000, 0x000eb7d9, 0x000eb7d9}, + {0x00009b3c, 0x00000000, 0x00000000, 0x000eb7c6, 0x000eb7c6}, + {0x00009b40, 0x00000000, 0x00000000, 0x000eb7ca, 0x000eb7ca}, + {0x00009b44, 0x00000000, 0x00000000, 0x000eb7ce, 0x000eb7ce}, + {0x00009b48, 0x00000000, 0x00000000, 0x000eb7d2, 0x000eb7d2}, + {0x00009b4c, 0x00000000, 0x00000000, 0x000eb7d6, 0x000eb7d6}, + {0x00009b50, 0x00000000, 0x00000000, 0x000eb7c3, 0x000eb7c3}, + {0x00009b54, 0x00000000, 0x00000000, 0x000eb7c7, 0x000eb7c7}, + {0x00009b58, 0x00000000, 0x00000000, 0x000eb7cb, 0x000eb7cb}, + {0x00009b5c, 0x00000000, 0x00000000, 0x000eb7cf, 0x000eb7cf}, + {0x00009b60, 0x00000000, 0x00000000, 0x000eb7d7, 0x000eb7d7}, + {0x00009b64, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b68, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b6c, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b70, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b74, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b78, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b7c, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b80, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b84, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b88, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b8c, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b90, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b94, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b98, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009b9c, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009ba0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009ba4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009ba8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bac, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bb0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bb4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bb8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bbc, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bc0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bc4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bc8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bcc, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bd0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bd4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bd8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bdc, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009be0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009be4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009be8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bec, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bf0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bf4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bf8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x00009bfc, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000aa00, 0x00000000, 0x00000000, 0x00058084, 0x00058084}, + {0x0000aa04, 0x00000000, 0x00000000, 0x00058088, 0x00058088}, + {0x0000aa08, 0x00000000, 0x00000000, 0x0005808c, 0x0005808c}, + {0x0000aa0c, 0x00000000, 0x00000000, 0x00058100, 0x00058100}, + {0x0000aa10, 0x00000000, 0x00000000, 0x00058104, 0x00058104}, + {0x0000aa14, 0x00000000, 0x00000000, 0x00058108, 0x00058108}, + {0x0000aa18, 0x00000000, 0x00000000, 0x0005810c, 0x0005810c}, + {0x0000aa1c, 0x00000000, 0x00000000, 0x00058110, 0x00058110}, + {0x0000aa20, 0x00000000, 0x00000000, 0x00058114, 0x00058114}, + {0x0000aa24, 0x00000000, 0x00000000, 0x00058180, 0x00058180}, + {0x0000aa28, 0x00000000, 0x00000000, 0x00058184, 0x00058184}, + {0x0000aa2c, 0x00000000, 0x00000000, 0x00058188, 0x00058188}, + {0x0000aa30, 0x00000000, 0x00000000, 0x0005818c, 0x0005818c}, + {0x0000aa34, 0x00000000, 0x00000000, 0x00058190, 0x00058190}, + {0x0000aa38, 0x00000000, 0x00000000, 0x00058194, 0x00058194}, + {0x0000aa3c, 0x00000000, 0x00000000, 0x000581a0, 0x000581a0}, + {0x0000aa40, 0x00000000, 0x00000000, 0x0005820c, 0x0005820c}, + {0x0000aa44, 0x00000000, 0x00000000, 0x000581a8, 0x000581a8}, + {0x0000aa48, 0x00000000, 0x00000000, 0x00058284, 0x00058284}, + {0x0000aa4c, 0x00000000, 0x00000000, 0x00058288, 0x00058288}, + {0x0000aa50, 0x00000000, 0x00000000, 0x00058224, 0x00058224}, + {0x0000aa54, 0x00000000, 0x00000000, 0x00058290, 0x00058290}, + {0x0000aa58, 0x00000000, 0x00000000, 0x00058300, 0x00058300}, + {0x0000aa5c, 0x00000000, 0x00000000, 0x00058304, 0x00058304}, + {0x0000aa60, 0x00000000, 0x00000000, 0x00058308, 0x00058308}, + {0x0000aa64, 0x00000000, 0x00000000, 0x0005830c, 0x0005830c}, + {0x0000aa68, 0x00000000, 0x00000000, 0x00058380, 0x00058380}, + {0x0000aa6c, 0x00000000, 0x00000000, 0x00058384, 0x00058384}, + {0x0000aa70, 0x00000000, 0x00000000, 0x00068700, 0x00068700}, + {0x0000aa74, 0x00000000, 0x00000000, 0x00068704, 0x00068704}, + {0x0000aa78, 0x00000000, 0x00000000, 0x00068708, 0x00068708}, + {0x0000aa7c, 0x00000000, 0x00000000, 0x0006870c, 0x0006870c}, + {0x0000aa80, 0x00000000, 0x00000000, 0x00068780, 0x00068780}, + {0x0000aa84, 0x00000000, 0x00000000, 0x00068784, 0x00068784}, + {0x0000aa88, 0x00000000, 0x00000000, 0x00078b00, 0x00078b00}, + {0x0000aa8c, 0x00000000, 0x00000000, 0x00078b04, 0x00078b04}, + {0x0000aa90, 0x00000000, 0x00000000, 0x00078b08, 0x00078b08}, + {0x0000aa94, 0x00000000, 0x00000000, 0x00078b0c, 0x00078b0c}, + {0x0000aa98, 0x00000000, 0x00000000, 0x00078b80, 0x00078b80}, + {0x0000aa9c, 0x00000000, 0x00000000, 0x00078b84, 0x00078b84}, + {0x0000aaa0, 0x00000000, 0x00000000, 0x00078b88, 0x00078b88}, + {0x0000aaa4, 0x00000000, 0x00000000, 0x00078b8c, 0x00078b8c}, + {0x0000aaa8, 0x00000000, 0x00000000, 0x00078b90, 0x00078b90}, + {0x0000aaac, 0x00000000, 0x00000000, 0x000caf80, 0x000caf80}, + {0x0000aab0, 0x00000000, 0x00000000, 0x000caf84, 0x000caf84}, + {0x0000aab4, 0x00000000, 0x00000000, 0x000caf88, 0x000caf88}, + {0x0000aab8, 0x00000000, 0x00000000, 0x000caf8c, 0x000caf8c}, + {0x0000aabc, 0x00000000, 0x00000000, 0x000caf90, 0x000caf90}, + {0x0000aac0, 0x00000000, 0x00000000, 0x000db30c, 0x000db30c}, + {0x0000aac4, 0x00000000, 0x00000000, 0x000db310, 0x000db310}, + {0x0000aac8, 0x00000000, 0x00000000, 0x000db384, 0x000db384}, + {0x0000aacc, 0x00000000, 0x00000000, 0x000db388, 0x000db388}, + {0x0000aad0, 0x00000000, 0x00000000, 0x000db324, 0x000db324}, + {0x0000aad4, 0x00000000, 0x00000000, 0x000eb704, 0x000eb704}, + {0x0000aad8, 0x00000000, 0x00000000, 0x000eb6a4, 0x000eb6a4}, + {0x0000aadc, 0x00000000, 0x00000000, 0x000eb6a8, 0x000eb6a8}, + {0x0000aae0, 0x00000000, 0x00000000, 0x000eb710, 0x000eb710}, + {0x0000aae4, 0x00000000, 0x00000000, 0x000eb714, 0x000eb714}, + {0x0000aae8, 0x00000000, 0x00000000, 0x000eb720, 0x000eb720}, + {0x0000aaec, 0x00000000, 0x00000000, 0x000eb724, 0x000eb724}, + {0x0000aaf0, 0x00000000, 0x00000000, 0x000eb728, 0x000eb728}, + {0x0000aaf4, 0x00000000, 0x00000000, 0x000eb72c, 0x000eb72c}, + {0x0000aaf8, 0x00000000, 0x00000000, 0x000eb7a0, 0x000eb7a0}, + {0x0000aafc, 0x00000000, 0x00000000, 0x000eb7a4, 0x000eb7a4}, + {0x0000ab00, 0x00000000, 0x00000000, 0x000eb7a8, 0x000eb7a8}, + {0x0000ab04, 0x00000000, 0x00000000, 0x000eb7b0, 0x000eb7b0}, + {0x0000ab08, 0x00000000, 0x00000000, 0x000eb7b4, 0x000eb7b4}, + {0x0000ab0c, 0x00000000, 0x00000000, 0x000eb7b8, 0x000eb7b8}, + {0x0000ab10, 0x00000000, 0x00000000, 0x000eb7a5, 0x000eb7a5}, + {0x0000ab14, 0x00000000, 0x00000000, 0x000eb7a9, 0x000eb7a9}, + {0x0000ab18, 0x00000000, 0x00000000, 0x000eb7ad, 0x000eb7ad}, + {0x0000ab1c, 0x00000000, 0x00000000, 0x000eb7b1, 0x000eb7b1}, + {0x0000ab20, 0x00000000, 0x00000000, 0x000eb7b5, 0x000eb7b5}, + {0x0000ab24, 0x00000000, 0x00000000, 0x000eb7b9, 0x000eb7b9}, + {0x0000ab28, 0x00000000, 0x00000000, 0x000eb7c5, 0x000eb7c5}, + {0x0000ab2c, 0x00000000, 0x00000000, 0x000eb7c9, 0x000eb7c9}, + {0x0000ab30, 0x00000000, 0x00000000, 0x000eb7d1, 0x000eb7d1}, + {0x0000ab34, 0x00000000, 0x00000000, 0x000eb7d5, 0x000eb7d5}, + {0x0000ab38, 0x00000000, 0x00000000, 0x000eb7d9, 0x000eb7d9}, + {0x0000ab3c, 0x00000000, 0x00000000, 0x000eb7c6, 0x000eb7c6}, + {0x0000ab40, 0x00000000, 0x00000000, 0x000eb7ca, 0x000eb7ca}, + {0x0000ab44, 0x00000000, 0x00000000, 0x000eb7ce, 0x000eb7ce}, + {0x0000ab48, 0x00000000, 0x00000000, 0x000eb7d2, 0x000eb7d2}, + {0x0000ab4c, 0x00000000, 0x00000000, 0x000eb7d6, 0x000eb7d6}, + {0x0000ab50, 0x00000000, 0x00000000, 0x000eb7c3, 0x000eb7c3}, + {0x0000ab54, 0x00000000, 0x00000000, 0x000eb7c7, 0x000eb7c7}, + {0x0000ab58, 0x00000000, 0x00000000, 0x000eb7cb, 0x000eb7cb}, + {0x0000ab5c, 0x00000000, 0x00000000, 0x000eb7cf, 0x000eb7cf}, + {0x0000ab60, 0x00000000, 0x00000000, 0x000eb7d7, 0x000eb7d7}, + {0x0000ab64, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab68, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab6c, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab70, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab74, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab78, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab7c, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab80, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab84, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab88, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab8c, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab90, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab94, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab98, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000ab9c, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000aba0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000aba4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000aba8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abac, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abb0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abb4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abb8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abbc, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abc0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abc4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abc8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abcc, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abd0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abd4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abd8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abdc, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abe0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abe4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abe8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abec, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abf0, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abf4, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abf8, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000abfc, 0x00000000, 0x00000000, 0x000eb7db, 0x000eb7db}, + {0x0000a204, 0x00000004, 0x00000004, 0x00000004, 0x00000004}, + {0x0000a20c, 0x00000014, 0x00000014, 0x0001f000, 0x0001f000}, + {0x0000b20c, 0x00000014, 0x00000014, 0x0001f000, 0x0001f000}, + {0x0000a21c, 0x1883800a, 0x1883800a, 0x1883800a, 0x1883800a}, + {0x0000a230, 0x00000000, 0x00000000, 0x00000210, 0x00000108}, + {0x0000a250, 0x0004f000, 0x0004f000, 0x0004a000, 0x0004a000}, + {0x0000a358, 0x7999aa02, 0x7999aa02, 0x7999aa0e, 0x7999aa0e}, +}; + +static const u32 ar9271Common_9271[][2] = { + /* Addr allmodes */ + {0x0000000c, 0x00000000}, + {0x00000030, 0x00020045}, + {0x00000034, 0x00000005}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000008}, + {0x00000048, 0x00000008}, + {0x0000004c, 0x00000010}, + {0x00000050, 0x00000000}, + {0x00000054, 0x0000001f}, + {0x00000800, 0x00000000}, + {0x00000804, 0x00000000}, + {0x00000808, 0x00000000}, + {0x0000080c, 0x00000000}, + {0x00000810, 0x00000000}, + {0x00000814, 0x00000000}, + {0x00000818, 0x00000000}, + {0x0000081c, 0x00000000}, + {0x00000820, 0x00000000}, + {0x00000824, 0x00000000}, + {0x00001040, 0x002ffc0f}, + {0x00001044, 0x002ffc0f}, + {0x00001048, 0x002ffc0f}, + {0x0000104c, 0x002ffc0f}, + {0x00001050, 0x002ffc0f}, + {0x00001054, 0x002ffc0f}, + {0x00001058, 0x002ffc0f}, + {0x0000105c, 0x002ffc0f}, + {0x00001060, 0x002ffc0f}, + {0x00001064, 0x002ffc0f}, + {0x00001230, 0x00000000}, + {0x00001270, 0x00000000}, + {0x00001038, 0x00000000}, + {0x00001078, 0x00000000}, + {0x000010b8, 0x00000000}, + {0x000010f8, 0x00000000}, + {0x00001138, 0x00000000}, + {0x00001178, 0x00000000}, + {0x000011b8, 0x00000000}, + {0x000011f8, 0x00000000}, + {0x00001238, 0x00000000}, + {0x00001278, 0x00000000}, + {0x000012b8, 0x00000000}, + {0x000012f8, 0x00000000}, + {0x00001338, 0x00000000}, + {0x00001378, 0x00000000}, + {0x000013b8, 0x00000000}, + {0x000013f8, 0x00000000}, + {0x00001438, 0x00000000}, + {0x00001478, 0x00000000}, + {0x000014b8, 0x00000000}, + {0x000014f8, 0x00000000}, + {0x00001538, 0x00000000}, + {0x00001578, 0x00000000}, + {0x000015b8, 0x00000000}, + {0x000015f8, 0x00000000}, + {0x00001638, 0x00000000}, + {0x00001678, 0x00000000}, + {0x000016b8, 0x00000000}, + {0x000016f8, 0x00000000}, + {0x00001738, 0x00000000}, + {0x00001778, 0x00000000}, + {0x000017b8, 0x00000000}, + {0x000017f8, 0x00000000}, + {0x0000103c, 0x00000000}, + {0x0000107c, 0x00000000}, + {0x000010bc, 0x00000000}, + {0x000010fc, 0x00000000}, + {0x0000113c, 0x00000000}, + {0x0000117c, 0x00000000}, + {0x000011bc, 0x00000000}, + {0x000011fc, 0x00000000}, + {0x0000123c, 0x00000000}, + {0x0000127c, 0x00000000}, + {0x000012bc, 0x00000000}, + {0x000012fc, 0x00000000}, + {0x0000133c, 0x00000000}, + {0x0000137c, 0x00000000}, + {0x000013bc, 0x00000000}, + {0x000013fc, 0x00000000}, + {0x0000143c, 0x00000000}, + {0x0000147c, 0x00000000}, + {0x00004030, 0x00000002}, + {0x0000403c, 0x00000002}, + {0x00004024, 0x0000001f}, + {0x00004060, 0x00000000}, + {0x00004064, 0x00000000}, + {0x00008004, 0x00000000}, + {0x00008008, 0x00000000}, + {0x0000800c, 0x00000000}, + {0x00008018, 0x00000700}, + {0x00008020, 0x00000000}, + {0x00008038, 0x00000000}, + {0x0000803c, 0x00000000}, + {0x00008048, 0x00000000}, + {0x00008054, 0x00000000}, + {0x00008058, 0x00000000}, + {0x0000805c, 0x000fc78f}, + {0x00008060, 0x0000000f}, + {0x00008064, 0x00000000}, + {0x00008070, 0x00000000}, + {0x000080b0, 0x00000000}, + {0x000080b4, 0x00000000}, + {0x000080b8, 0x00000000}, + {0x000080bc, 0x00000000}, + {0x000080c0, 0x2a80001a}, + {0x000080c4, 0x05dc01e0}, + {0x000080c8, 0x1f402710}, + {0x000080cc, 0x01f40000}, + {0x000080d0, 0x00001e00}, + {0x000080d4, 0x00000000}, + {0x000080d8, 0x00400000}, + {0x000080e0, 0xffffffff}, + {0x000080e4, 0x0000ffff}, + {0x000080e8, 0x003f3f3f}, + {0x000080ec, 0x00000000}, + {0x000080f0, 0x00000000}, + {0x000080f4, 0x00000000}, + {0x000080f8, 0x00000000}, + {0x000080fc, 0x00020000}, + {0x00008100, 0x00020000}, + {0x00008104, 0x00000001}, + {0x00008108, 0x00000052}, + {0x0000810c, 0x00000000}, + {0x00008110, 0x00000168}, + {0x00008118, 0x000100aa}, + {0x0000811c, 0x00003210}, + {0x00008120, 0x08f04810}, + {0x00008124, 0x00000000}, + {0x00008128, 0x00000000}, + {0x0000812c, 0x00000000}, + {0x00008130, 0x00000000}, + {0x00008134, 0x00000000}, + {0x00008138, 0x00000000}, + {0x0000813c, 0x00000000}, + {0x00008144, 0xffffffff}, + {0x00008168, 0x00000000}, + {0x0000816c, 0x00000000}, + {0x00008170, 0x32143320}, + {0x00008174, 0xfaa4fa50}, + {0x00008178, 0x00000100}, + {0x0000817c, 0x00000000}, + {0x000081c0, 0x00000000}, + {0x000081d0, 0x0000320a}, + {0x000081ec, 0x00000000}, + {0x000081f0, 0x00000000}, + {0x000081f4, 0x00000000}, + {0x000081f8, 0x00000000}, + {0x000081fc, 0x00000000}, + {0x00008200, 0x00000000}, + {0x00008204, 0x00000000}, + {0x00008208, 0x00000000}, + {0x0000820c, 0x00000000}, + {0x00008210, 0x00000000}, + {0x00008214, 0x00000000}, + {0x00008218, 0x00000000}, + {0x0000821c, 0x00000000}, + {0x00008220, 0x00000000}, + {0x00008224, 0x00000000}, + {0x00008228, 0x00000000}, + {0x0000822c, 0x00000000}, + {0x00008230, 0x00000000}, + {0x00008234, 0x00000000}, + {0x00008238, 0x00000000}, + {0x0000823c, 0x00000000}, + {0x00008240, 0x00100000}, + {0x00008244, 0x0010f400}, + {0x00008248, 0x00000100}, + {0x0000824c, 0x0001e800}, + {0x00008250, 0x00000000}, + {0x00008254, 0x00000000}, + {0x00008258, 0x00000000}, + {0x0000825c, 0x400000ff}, + {0x00008260, 0x00080922}, + {0x00008264, 0x88a00010}, + {0x00008270, 0x00000000}, + {0x00008274, 0x40000000}, + {0x00008278, 0x003e4180}, + {0x0000827c, 0x00000000}, + {0x00008284, 0x0000002c}, + {0x00008288, 0x0000002c}, + {0x0000828c, 0x00000000}, + {0x00008294, 0x00000000}, + {0x00008298, 0x00000000}, + {0x0000829c, 0x00000000}, + {0x00008300, 0x00000040}, + {0x00008314, 0x00000000}, + {0x00008328, 0x00000000}, + {0x0000832c, 0x00000001}, + {0x00008330, 0x00000302}, + {0x00008334, 0x00000e00}, + {0x00008338, 0x00ff0000}, + {0x0000833c, 0x00000000}, + {0x00008340, 0x00010380}, + {0x00008344, 0x00581043}, + {0x00007010, 0x00000030}, + {0x00007034, 0x00000002}, + {0x00007038, 0x000004c2}, + {0x00007800, 0x00140000}, + {0x00007804, 0x0e4548d8}, + {0x00007808, 0x54214514}, + {0x0000780c, 0x02025820}, + {0x00007810, 0x71c0d388}, + {0x00007814, 0x924934a8}, + {0x0000781c, 0x00000000}, + {0x00007828, 0x66964300}, + {0x0000782c, 0x8db6d961}, + {0x00007830, 0x8db6d96c}, + {0x00007834, 0x6140008b}, + {0x0000783c, 0x72ee0a72}, + {0x00007840, 0xbbfffffc}, + {0x00007844, 0x000c0db6}, + {0x00007848, 0x6db6246f}, + {0x0000784c, 0x6d9b66db}, + {0x00007850, 0x6d8c6dba}, + {0x00007854, 0x00040000}, + {0x00007858, 0xdb003012}, + {0x0000785c, 0x04924914}, + {0x00007860, 0x21084210}, + {0x00007864, 0xf7d7ffde}, + {0x00007868, 0xc2034080}, + {0x00007870, 0x10142c00}, + {0x00009808, 0x00000000}, + {0x0000980c, 0xafe68e30}, + {0x00009810, 0xfd14e000}, + {0x00009814, 0x9c0a9f6b}, + {0x0000981c, 0x00000000}, + {0x0000982c, 0x0000a000}, + {0x00009830, 0x00000000}, + {0x0000983c, 0x00200400}, + {0x0000984c, 0x0040233c}, + {0x00009854, 0x00000044}, + {0x00009900, 0x00000000}, + {0x00009904, 0x00000000}, + {0x00009908, 0x00000000}, + {0x0000990c, 0x00000000}, + {0x0000991c, 0x10000fff}, + {0x00009920, 0x04900000}, + {0x00009928, 0x00000001}, + {0x0000992c, 0x00000004}, + {0x00009934, 0x1e1f2022}, + {0x00009938, 0x0a0b0c0d}, + {0x0000993c, 0x00000000}, + {0x00009940, 0x14750604}, + {0x00009948, 0x9280c00a}, + {0x0000994c, 0x00020028}, + {0x00009954, 0x5f3ca3de}, + {0x00009958, 0x0108ecff}, + {0x00009968, 0x000003ce}, + {0x00009970, 0x192bb514}, + {0x00009974, 0x00000000}, + {0x00009978, 0x00000001}, + {0x0000997c, 0x00000000}, + {0x00009980, 0x00000000}, + {0x00009984, 0x00000000}, + {0x00009988, 0x00000000}, + {0x0000998c, 0x00000000}, + {0x00009990, 0x00000000}, + {0x00009994, 0x00000000}, + {0x00009998, 0x00000000}, + {0x0000999c, 0x00000000}, + {0x000099a0, 0x00000000}, + {0x000099a4, 0x00000001}, + {0x000099a8, 0x201fff00}, + {0x000099ac, 0x2def0400}, + {0x000099b0, 0x03051000}, + {0x000099b4, 0x00000820}, + {0x000099dc, 0x00000000}, + {0x000099e0, 0x00000000}, + {0x000099e4, 0xaaaaaaaa}, + {0x000099e8, 0x3c466478}, + {0x000099ec, 0x0cc80caa}, + {0x000099f0, 0x00000000}, + {0x0000a208, 0x803e68c8}, + {0x0000a210, 0x4080a333}, + {0x0000a214, 0x00206c10}, + {0x0000a218, 0x009c4060}, + {0x0000a220, 0x01834061}, + {0x0000a224, 0x00000400}, + {0x0000a228, 0x000003b5}, + {0x0000a22c, 0x00000000}, + {0x0000a234, 0x20202020}, + {0x0000a238, 0x20202020}, + {0x0000a244, 0x00000000}, + {0x0000a248, 0xfffffffc}, + {0x0000a24c, 0x00000000}, + {0x0000a254, 0x00000000}, + {0x0000a258, 0x0ccb5380}, + {0x0000a25c, 0x15151501}, + {0x0000a260, 0xdfa90f01}, + {0x0000a268, 0x00000000}, + {0x0000a26c, 0x0ebae9e6}, + {0x0000a388, 0x0c000000}, + {0x0000a38c, 0x20202020}, + {0x0000a390, 0x20202020}, + {0x0000a39c, 0x00000001}, + {0x0000a3a0, 0x00000000}, + {0x0000a3a4, 0x00000000}, + {0x0000a3a8, 0x00000000}, + {0x0000a3ac, 0x00000000}, + {0x0000a3b0, 0x00000000}, + {0x0000a3b4, 0x00000000}, + {0x0000a3b8, 0x00000000}, + {0x0000a3bc, 0x00000000}, + {0x0000a3c0, 0x00000000}, + {0x0000a3c4, 0x00000000}, + {0x0000a3cc, 0x20202020}, + {0x0000a3d0, 0x20202020}, + {0x0000a3d4, 0x20202020}, + {0x0000a3e4, 0x00000000}, + {0x0000a3e8, 0x18c43433}, + {0x0000a3ec, 0x00f70081}, + {0x0000a3f0, 0x01036a2f}, + {0x0000a3f4, 0x00000000}, + {0x0000d270, 0x0d820820}, + {0x0000d35c, 0x07ffffef}, + {0x0000d360, 0x0fffffe7}, + {0x0000d364, 0x17ffffe5}, + {0x0000d368, 0x1fffffe4}, + {0x0000d36c, 0x37ffffe3}, + {0x0000d370, 0x3fffffe3}, + {0x0000d374, 0x57ffffe3}, + {0x0000d378, 0x5fffffe2}, + {0x0000d37c, 0x7fffffe2}, + {0x0000d380, 0x7f3c7bba}, + {0x0000d384, 0xf3307ff0}, +}; + +static const u32 ar9271Modes_9271_ANI_reg[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009850, 0x6d4000e2, 0x6d4000e2, 0x6d4000e2, 0x6d4000e2}, + {0x0000985c, 0x3139605e, 0x3139605e, 0x3137605e, 0x3137605e}, + {0x00009858, 0x7ec84d2e, 0x7ec84d2e, 0x7ec84d2e, 0x7ec84d2e}, + {0x0000986c, 0x06903881, 0x06903881, 0x06903881, 0x06903881}, + {0x00009868, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0}, + {0x0000a208, 0x803e68c8, 0x803e68c8, 0x803e68c8, 0x803e68c8}, + {0x00009924, 0xd00a8007, 0xd00a8007, 0xd00a800d, 0xd00a800d}, + {0x000099c0, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4}, +}; + +static const u32 ar9271Modes_normal_power_tx_gain_9271[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a300, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a304, 0x00000000, 0x00000000, 0x00009200, 0x00009200}, + {0x0000a308, 0x00000000, 0x00000000, 0x00010208, 0x00010208}, + {0x0000a30c, 0x00000000, 0x00000000, 0x00019608, 0x00019608}, + {0x0000a310, 0x00000000, 0x00000000, 0x0001e610, 0x0001e610}, + {0x0000a314, 0x00000000, 0x00000000, 0x00024650, 0x00024650}, + {0x0000a318, 0x00000000, 0x00000000, 0x0002d6d0, 0x0002d6d0}, + {0x0000a31c, 0x00000000, 0x00000000, 0x000316d2, 0x000316d2}, + {0x0000a320, 0x00000000, 0x00000000, 0x00039758, 0x00039758}, + {0x0000a324, 0x00000000, 0x00000000, 0x0003b759, 0x0003b759}, + {0x0000a328, 0x00000000, 0x00000000, 0x0003d75a, 0x0003d75a}, + {0x0000a32c, 0x00000000, 0x00000000, 0x0004175c, 0x0004175c}, + {0x0000a330, 0x00000000, 0x00000000, 0x0004575e, 0x0004575e}, + {0x0000a334, 0x000368de, 0x000368de, 0x0004979f, 0x0004979f}, + {0x0000a338, 0x0003891e, 0x0003891e, 0x0004d7df, 0x0004d7df}, + {0x0000a33c, 0x0003a95e, 0x0003a95e, 0x0003a95e, 0x0003a95e}, + {0x0000a340, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a344, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a348, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a34c, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a350, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a354, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x00007838, 0x00000029, 0x00000029, 0x00000029, 0x00000029}, + {0x00007824, 0x00d8abff, 0x00d8abff, 0x00d8abff, 0x00d8abff}, + {0x0000786c, 0x48609eb4, 0x48609eb4, 0x48609eb4, 0x48609eb4}, + {0x00007820, 0x00000c04, 0x00000c04, 0x00000c04, 0x00000c04}, + {0x0000a274, 0x0a21c652, 0x0a21c652, 0x0a21c652, 0x0a21c652}, + {0x0000a278, 0x3bdef7bd, 0x3bdef7bd, 0x3bdef7bd, 0x3bdef7bd}, + {0x0000a27c, 0x050e83bd, 0x050e83bd, 0x050e83bd, 0x050e83bd}, + {0x0000a394, 0x3bdef7bd, 0x3bdef7bd, 0x3bdef7bd, 0x3bdef7bd}, + {0x0000a398, 0x000003bd, 0x000003bd, 0x000003bd, 0x000003bd}, + {0x0000a3dc, 0x3bdef7bd, 0x3bdef7bd, 0x3bdef7bd, 0x3bdef7bd}, + {0x0000a3e0, 0x000003bd, 0x000003bd, 0x000003bd, 0x000003bd}, +}; + +static const u32 ar9271Modes_high_power_tx_gain_9271[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a300, 0x00000000, 0x00000000, 0x00010000, 0x00010000}, + {0x0000a304, 0x00000000, 0x00000000, 0x00016200, 0x00016200}, + {0x0000a308, 0x00000000, 0x00000000, 0x00018201, 0x00018201}, + {0x0000a30c, 0x00000000, 0x00000000, 0x0001b240, 0x0001b240}, + {0x0000a310, 0x00000000, 0x00000000, 0x0001d241, 0x0001d241}, + {0x0000a314, 0x00000000, 0x00000000, 0x0001f600, 0x0001f600}, + {0x0000a318, 0x00000000, 0x00000000, 0x00022800, 0x00022800}, + {0x0000a31c, 0x00000000, 0x00000000, 0x00026802, 0x00026802}, + {0x0000a320, 0x00000000, 0x00000000, 0x0002b805, 0x0002b805}, + {0x0000a324, 0x00000000, 0x00000000, 0x0002ea41, 0x0002ea41}, + {0x0000a328, 0x00000000, 0x00000000, 0x00038b00, 0x00038b00}, + {0x0000a32c, 0x00000000, 0x00000000, 0x0003ab40, 0x0003ab40}, + {0x0000a330, 0x00000000, 0x00000000, 0x0003cd80, 0x0003cd80}, + {0x0000a334, 0x000368de, 0x000368de, 0x000368de, 0x000368de}, + {0x0000a338, 0x0003891e, 0x0003891e, 0x0003891e, 0x0003891e}, + {0x0000a33c, 0x0003a95e, 0x0003a95e, 0x0003a95e, 0x0003a95e}, + {0x0000a340, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a344, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a348, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a34c, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a350, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x0000a354, 0x0003e9df, 0x0003e9df, 0x0003e9df, 0x0003e9df}, + {0x00007838, 0x0000002b, 0x0000002b, 0x0000002b, 0x0000002b}, + {0x00007824, 0x00d8a7ff, 0x00d8a7ff, 0x00d8a7ff, 0x00d8a7ff}, + {0x0000786c, 0x08609eb6, 0x08609eb6, 0x08609eba, 0x08609eba}, + {0x00007820, 0x00000c00, 0x00000c00, 0x00000c00, 0x00000c00}, + {0x0000a274, 0x0a22a652, 0x0a22a652, 0x0a214652, 0x0a214652}, + {0x0000a278, 0x0e739ce7, 0x0e739ce7, 0x0e739ce7, 0x0e739ce7}, + {0x0000a27c, 0x05018063, 0x05038063, 0x05018063, 0x05018063}, + {0x0000a394, 0x06318c63, 0x06318c63, 0x06318c63, 0x06318c63}, + {0x0000a398, 0x00000063, 0x00000063, 0x00000063, 0x00000063}, + {0x0000a3dc, 0x06318c63, 0x06318c63, 0x06318c63, 0x06318c63}, + {0x0000a3e0, 0x00000063, 0x00000063, 0x00000063, 0x00000063}, +}; + diff --git a/drivers/net/wireless/ath/ath9k/ar9002_mac.c b/drivers/net/wireless/ath/ath9k/ar9002_mac.c new file mode 100644 index 0000000..8d78253 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9002_mac.c @@ -0,0 +1,369 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "hw.h" +#include + +#define AR_BufLen 0x00000fff + +static void ar9002_hw_rx_enable(struct ath_hw *ah) +{ + REG_WRITE(ah, AR_CR, AR_CR_RXE); +} + +static void ar9002_hw_set_desc_link(void *ds, u32 ds_link) +{ + ((struct ath_desc*) ds)->ds_link = ds_link; +} + +static bool ar9002_hw_get_isr(struct ath_hw *ah, enum ath9k_int *masked) +{ + u32 isr = 0; + u32 mask2 = 0; + struct ath9k_hw_capabilities *pCap = &ah->caps; + u32 sync_cause = 0; + bool fatal_int = false; + struct ath_common *common = ath9k_hw_common(ah); + + if (!AR_SREV_9100(ah)) { + if (REG_READ(ah, AR_INTR_ASYNC_CAUSE) & AR_INTR_MAC_IRQ) { + if ((REG_READ(ah, AR_RTC_STATUS) & AR_RTC_STATUS_M) + == AR_RTC_STATUS_ON) { + isr = REG_READ(ah, AR_ISR); + } + } + + sync_cause = REG_READ(ah, AR_INTR_SYNC_CAUSE) & + AR_INTR_SYNC_DEFAULT; + + *masked = 0; + + if (!isr && !sync_cause) + return false; + } else { + *masked = 0; + isr = REG_READ(ah, AR_ISR); + } + + if (isr) { + if (isr & AR_ISR_BCNMISC) { + u32 isr2; + isr2 = REG_READ(ah, AR_ISR_S2); + if (isr2 & AR_ISR_S2_TIM) + mask2 |= ATH9K_INT_TIM; + if (isr2 & AR_ISR_S2_DTIM) + mask2 |= ATH9K_INT_DTIM; + if (isr2 & AR_ISR_S2_DTIMSYNC) + mask2 |= ATH9K_INT_DTIMSYNC; + if (isr2 & (AR_ISR_S2_CABEND)) + mask2 |= ATH9K_INT_CABEND; + if (isr2 & AR_ISR_S2_GTT) + mask2 |= ATH9K_INT_GTT; + if (isr2 & AR_ISR_S2_CST) + mask2 |= ATH9K_INT_CST; + if (isr2 & AR_ISR_S2_TSFOOR) + mask2 |= ATH9K_INT_TSFOOR; + } + + isr = REG_READ(ah, AR_ISR_RAC); + if (isr == 0xffffffff) { + *masked = 0; + return false; + } + + *masked = isr & ATH9K_INT_COMMON; + + if (isr & (AR_ISR_RXMINTR | AR_ISR_RXINTM | + AR_ISR_RXOK | AR_ISR_RXERR)) + *masked |= ATH9K_INT_RX; + + if (isr & + (AR_ISR_TXOK | AR_ISR_TXDESC | AR_ISR_TXERR | + AR_ISR_TXEOL)) { + u32 s0_s, s1_s; + + *masked |= ATH9K_INT_TX; + + s0_s = REG_READ(ah, AR_ISR_S0_S); + ah->intr_txqs |= MS(s0_s, AR_ISR_S0_QCU_TXOK); + ah->intr_txqs |= MS(s0_s, AR_ISR_S0_QCU_TXDESC); + + s1_s = REG_READ(ah, AR_ISR_S1_S); + ah->intr_txqs |= MS(s1_s, AR_ISR_S1_QCU_TXERR); + ah->intr_txqs |= MS(s1_s, AR_ISR_S1_QCU_TXEOL); + } + + if (isr & AR_ISR_RXORN) { + ath_dbg(common, INTERRUPT, + "receive FIFO overrun interrupt\n"); + } + + *masked |= mask2; + } + + if (AR_SREV_9100(ah)) + return true; + + if (isr & AR_ISR_GENTMR) { + u32 s5_s; + + s5_s = REG_READ(ah, AR_ISR_S5_S); + ah->intr_gen_timer_trigger = + MS(s5_s, AR_ISR_S5_GENTIMER_TRIG); + + ah->intr_gen_timer_thresh = + MS(s5_s, AR_ISR_S5_GENTIMER_THRESH); + + if (ah->intr_gen_timer_trigger) + *masked |= ATH9K_INT_GENTIMER; + + if ((s5_s & AR_ISR_S5_TIM_TIMER) && + !(pCap->hw_caps & ATH9K_HW_CAP_AUTOSLEEP)) + *masked |= ATH9K_INT_TIM_TIMER; + } + + if (sync_cause) { + ath9k_debug_sync_cause(common, sync_cause); + fatal_int = + (sync_cause & + (AR_INTR_SYNC_HOST1_FATAL | AR_INTR_SYNC_HOST1_PERR)) + ? true : false; + + if (fatal_int) { + if (sync_cause & AR_INTR_SYNC_HOST1_FATAL) { + ath_dbg(common, ANY, + "received PCI FATAL interrupt\n"); + } + if (sync_cause & AR_INTR_SYNC_HOST1_PERR) { + ath_dbg(common, ANY, + "received PCI PERR interrupt\n"); + } + *masked |= ATH9K_INT_FATAL; + } + if (sync_cause & AR_INTR_SYNC_RADM_CPL_TIMEOUT) { + ath_dbg(common, INTERRUPT, + "AR_INTR_SYNC_RADM_CPL_TIMEOUT\n"); + REG_WRITE(ah, AR_RC, AR_RC_HOSTIF); + REG_WRITE(ah, AR_RC, 0); + *masked |= ATH9K_INT_FATAL; + } + if (sync_cause & AR_INTR_SYNC_LOCAL_TIMEOUT) { + ath_dbg(common, INTERRUPT, + "AR_INTR_SYNC_LOCAL_TIMEOUT\n"); + } + + REG_WRITE(ah, AR_INTR_SYNC_CAUSE_CLR, sync_cause); + (void) REG_READ(ah, AR_INTR_SYNC_CAUSE_CLR); + } + + return true; +} + +static void +ar9002_set_txdesc(struct ath_hw *ah, void *ds, struct ath_tx_info *i) +{ + struct ar5416_desc *ads = AR5416DESC(ds); + u32 ctl1, ctl6; + + ads->ds_txstatus0 = ads->ds_txstatus1 = 0; + ads->ds_txstatus2 = ads->ds_txstatus3 = 0; + ads->ds_txstatus4 = ads->ds_txstatus5 = 0; + ads->ds_txstatus6 = ads->ds_txstatus7 = 0; + ads->ds_txstatus8 = ads->ds_txstatus9 = 0; + + ACCESS_ONCE(ads->ds_link) = i->link; + ACCESS_ONCE(ads->ds_data) = i->buf_addr[0]; + + ctl1 = i->buf_len[0] | (i->is_last ? 0 : AR_TxMore); + ctl6 = SM(i->keytype, AR_EncrType); + + if (AR_SREV_9285(ah)) { + ads->ds_ctl8 = 0; + ads->ds_ctl9 = 0; + ads->ds_ctl10 = 0; + ads->ds_ctl11 = 0; + } + + if ((i->is_first || i->is_last) && + i->aggr != AGGR_BUF_MIDDLE && i->aggr != AGGR_BUF_LAST) { + ACCESS_ONCE(ads->ds_ctl2) = set11nTries(i->rates, 0) + | set11nTries(i->rates, 1) + | set11nTries(i->rates, 2) + | set11nTries(i->rates, 3) + | (i->dur_update ? AR_DurUpdateEna : 0) + | SM(0, AR_BurstDur); + + ACCESS_ONCE(ads->ds_ctl3) = set11nRate(i->rates, 0) + | set11nRate(i->rates, 1) + | set11nRate(i->rates, 2) + | set11nRate(i->rates, 3); + } else { + ACCESS_ONCE(ads->ds_ctl2) = 0; + ACCESS_ONCE(ads->ds_ctl3) = 0; + } + + if (!i->is_first) { + ACCESS_ONCE(ads->ds_ctl0) = 0; + ACCESS_ONCE(ads->ds_ctl1) = ctl1; + ACCESS_ONCE(ads->ds_ctl6) = ctl6; + return; + } + + ctl1 |= (i->keyix != ATH9K_TXKEYIX_INVALID ? SM(i->keyix, AR_DestIdx) : 0) + | SM(i->type, AR_FrameType) + | (i->flags & ATH9K_TXDESC_NOACK ? AR_NoAck : 0) + | (i->flags & ATH9K_TXDESC_EXT_ONLY ? AR_ExtOnly : 0) + | (i->flags & ATH9K_TXDESC_EXT_AND_CTL ? AR_ExtAndCtl : 0); + + switch (i->aggr) { + case AGGR_BUF_FIRST: + ctl6 |= SM(i->aggr_len, AR_AggrLen); + /* fall through */ + case AGGR_BUF_MIDDLE: + ctl1 |= AR_IsAggr | AR_MoreAggr; + ctl6 |= SM(i->ndelim, AR_PadDelim); + break; + case AGGR_BUF_LAST: + ctl1 |= AR_IsAggr; + break; + case AGGR_BUF_NONE: + break; + } + + ACCESS_ONCE(ads->ds_ctl0) = (i->pkt_len & AR_FrameLen) + | (i->flags & ATH9K_TXDESC_VMF ? AR_VirtMoreFrag : 0) + | SM(i->txpower, AR_XmitPower) + | (i->flags & ATH9K_TXDESC_VEOL ? AR_VEOL : 0) + | (i->flags & ATH9K_TXDESC_INTREQ ? AR_TxIntrReq : 0) + | (i->keyix != ATH9K_TXKEYIX_INVALID ? AR_DestIdxValid : 0) + | (i->flags & ATH9K_TXDESC_CLRDMASK ? AR_ClrDestMask : 0) + | (i->flags & ATH9K_TXDESC_RTSENA ? AR_RTSEnable : + (i->flags & ATH9K_TXDESC_CTSENA ? AR_CTSEnable : 0)); + + ACCESS_ONCE(ads->ds_ctl1) = ctl1; + ACCESS_ONCE(ads->ds_ctl6) = ctl6; + + if (i->aggr == AGGR_BUF_MIDDLE || i->aggr == AGGR_BUF_LAST) + return; + + ACCESS_ONCE(ads->ds_ctl4) = set11nPktDurRTSCTS(i->rates, 0) + | set11nPktDurRTSCTS(i->rates, 1); + + ACCESS_ONCE(ads->ds_ctl5) = set11nPktDurRTSCTS(i->rates, 2) + | set11nPktDurRTSCTS(i->rates, 3); + + ACCESS_ONCE(ads->ds_ctl7) = set11nRateFlags(i->rates, 0) + | set11nRateFlags(i->rates, 1) + | set11nRateFlags(i->rates, 2) + | set11nRateFlags(i->rates, 3) + | SM(i->rtscts_rate, AR_RTSCTSRate); +} + +static int ar9002_hw_proc_txdesc(struct ath_hw *ah, void *ds, + struct ath_tx_status *ts) +{ + struct ar5416_desc *ads = AR5416DESC(ds); + u32 status; + + status = ACCESS_ONCE(ads->ds_txstatus9); + if ((status & AR_TxDone) == 0) + return -EINPROGRESS; + + ts->ts_tstamp = ads->AR_SendTimestamp; + ts->ts_status = 0; + ts->ts_flags = 0; + + if (status & AR_TxOpExceeded) + ts->ts_status |= ATH9K_TXERR_XTXOP; + ts->tid = MS(status, AR_TxTid); + ts->ts_rateindex = MS(status, AR_FinalTxIdx); + ts->ts_seqnum = MS(status, AR_SeqNum); + + status = ACCESS_ONCE(ads->ds_txstatus0); + ts->ts_rssi_ctl0 = MS(status, AR_TxRSSIAnt00); + ts->ts_rssi_ctl1 = MS(status, AR_TxRSSIAnt01); + ts->ts_rssi_ctl2 = MS(status, AR_TxRSSIAnt02); + if (status & AR_TxBaStatus) { + ts->ts_flags |= ATH9K_TX_BA; + ts->ba_low = ads->AR_BaBitmapLow; + ts->ba_high = ads->AR_BaBitmapHigh; + } + + status = ACCESS_ONCE(ads->ds_txstatus1); + if (status & AR_FrmXmitOK) + ts->ts_status |= ATH9K_TX_ACKED; + else { + if (status & AR_ExcessiveRetries) + ts->ts_status |= ATH9K_TXERR_XRETRY; + if (status & AR_Filtered) + ts->ts_status |= ATH9K_TXERR_FILT; + if (status & AR_FIFOUnderrun) { + ts->ts_status |= ATH9K_TXERR_FIFO; + ath9k_hw_updatetxtriglevel(ah, true); + } + } + if (status & AR_TxTimerExpired) + ts->ts_status |= ATH9K_TXERR_TIMER_EXPIRED; + if (status & AR_DescCfgErr) + ts->ts_flags |= ATH9K_TX_DESC_CFG_ERR; + if (status & AR_TxDataUnderrun) { + ts->ts_flags |= ATH9K_TX_DATA_UNDERRUN; + ath9k_hw_updatetxtriglevel(ah, true); + } + if (status & AR_TxDelimUnderrun) { + ts->ts_flags |= ATH9K_TX_DELIM_UNDERRUN; + ath9k_hw_updatetxtriglevel(ah, true); + } + ts->ts_shortretry = MS(status, AR_RTSFailCnt); + ts->ts_longretry = MS(status, AR_DataFailCnt); + ts->ts_virtcol = MS(status, AR_VirtRetryCnt); + + status = ACCESS_ONCE(ads->ds_txstatus5); + ts->ts_rssi = MS(status, AR_TxRSSICombined); + ts->ts_rssi_ext0 = MS(status, AR_TxRSSIAnt10); + ts->ts_rssi_ext1 = MS(status, AR_TxRSSIAnt11); + ts->ts_rssi_ext2 = MS(status, AR_TxRSSIAnt12); + + ts->evm0 = ads->AR_TxEVM0; + ts->evm1 = ads->AR_TxEVM1; + ts->evm2 = ads->AR_TxEVM2; + + return 0; +} + +void ath9k_hw_setuprxdesc(struct ath_hw *ah, struct ath_desc *ds, + u32 size, u32 flags) +{ + struct ar5416_desc *ads = AR5416DESC(ds); + + ads->ds_ctl1 = size & AR_BufLen; + if (flags & ATH9K_RXDESC_INTREQ) + ads->ds_ctl1 |= AR_RxIntrReq; + + memset(&ads->u.rx, 0, sizeof(ads->u.rx)); +} +EXPORT_SYMBOL(ath9k_hw_setuprxdesc); + +void ar9002_hw_attach_mac_ops(struct ath_hw *ah) +{ + struct ath_hw_ops *ops = ath9k_hw_ops(ah); + + ops->rx_enable = ar9002_hw_rx_enable; + ops->set_desc_link = ar9002_hw_set_desc_link; + ops->get_isr = ar9002_hw_get_isr; + ops->set_txdesc = ar9002_set_txdesc; + ops->proc_txdesc = ar9002_hw_proc_txdesc; +} diff --git a/drivers/net/wireless/ath/ath9k/ar9002_phy.c b/drivers/net/wireless/ath/ath9k/ar9002_phy.c new file mode 100644 index 0000000..f400351 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9002_phy.c @@ -0,0 +1,638 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * DOC: Programming Atheros 802.11n analog front end radios + * + * AR5416 MAC based PCI devices and AR518 MAC based PCI-Express + * devices have either an external AR2133 analog front end radio for single + * band 2.4 GHz communication or an AR5133 analog front end radio for dual + * band 2.4 GHz / 5 GHz communication. + * + * All devices after the AR5416 and AR5418 family starting with the AR9280 + * have their analog front radios, MAC/BB and host PCIe/USB interface embedded + * into a single-chip and require less programming. + * + * The following single-chips exist with a respective embedded radio: + * + * AR9280 - 11n dual-band 2x2 MIMO for PCIe + * AR9281 - 11n single-band 1x2 MIMO for PCIe + * AR9285 - 11n single-band 1x1 for PCIe + * AR9287 - 11n single-band 2x2 MIMO for PCIe + * + * AR9220 - 11n dual-band 2x2 MIMO for PCI + * AR9223 - 11n single-band 2x2 MIMO for PCI + * + * AR9287 - 11n single-band 1x1 MIMO for USB + */ + +#include "hw.h" +#include "ar9002_phy.h" + +/** + * ar9002_hw_set_channel - set channel on single-chip device + * @ah: atheros hardware structure + * @chan: + * + * This is the function to change channel on single-chip devices, that is + * all devices after ar9280. + * + * This function takes the channel value in MHz and sets + * hardware channel value. Assumes writes have been enabled to analog bus. + * + * Actual Expression, + * + * For 2GHz channel, + * Channel Frequency = (3/4) * freq_ref * (chansel[8:0] + chanfrac[16:0]/2^17) + * (freq_ref = 40MHz) + * + * For 5GHz channel, + * Channel Frequency = (3/2) * freq_ref * (chansel[8:0] + chanfrac[16:0]/2^10) + * (freq_ref = 40MHz/(24>>amodeRefSel)) + */ +static int ar9002_hw_set_channel(struct ath_hw *ah, struct ath9k_channel *chan) +{ + u16 bMode, fracMode, aModeRefSel = 0; + u32 freq, ndiv, channelSel = 0, channelFrac = 0, reg32 = 0; + struct chan_centers centers; + u32 refDivA = 24; + + ath9k_hw_get_channel_centers(ah, chan, ¢ers); + freq = centers.synth_center; + + reg32 = REG_READ(ah, AR_PHY_SYNTH_CONTROL); + reg32 &= 0xc0000000; + + if (freq < 4800) { /* 2 GHz, fractional mode */ + u32 txctl; + int regWrites = 0; + + bMode = 1; + fracMode = 1; + aModeRefSel = 0; + channelSel = CHANSEL_2G(freq); + + if (AR_SREV_9287_11_OR_LATER(ah)) { + if (freq == 2484) { + /* Enable channel spreading for channel 14 */ + REG_WRITE_ARRAY(&ah->iniCckfirJapan2484, + 1, regWrites); + } else { + REG_WRITE_ARRAY(&ah->iniCckfirNormal, + 1, regWrites); + } + } else { + txctl = REG_READ(ah, AR_PHY_CCK_TX_CTRL); + if (freq == 2484) { + /* Enable channel spreading for channel 14 */ + REG_WRITE(ah, AR_PHY_CCK_TX_CTRL, + txctl | AR_PHY_CCK_TX_CTRL_JAPAN); + } else { + REG_WRITE(ah, AR_PHY_CCK_TX_CTRL, + txctl & ~AR_PHY_CCK_TX_CTRL_JAPAN); + } + } + } else { + bMode = 0; + fracMode = 0; + + switch (ah->eep_ops->get_eeprom(ah, EEP_FRAC_N_5G)) { + case 0: + if (IS_CHAN_HALF_RATE(chan) || IS_CHAN_QUARTER_RATE(chan)) + aModeRefSel = 0; + else if ((freq % 20) == 0) + aModeRefSel = 3; + else if ((freq % 10) == 0) + aModeRefSel = 2; + if (aModeRefSel) + break; + case 1: + default: + aModeRefSel = 0; + /* + * Enable 2G (fractional) mode for channels + * which are 5MHz spaced. + */ + fracMode = 1; + refDivA = 1; + channelSel = CHANSEL_5G(freq); + + /* RefDivA setting */ + ath9k_hw_analog_shift_rmw(ah, AR_AN_SYNTH9, + AR_AN_SYNTH9_REFDIVA, + AR_AN_SYNTH9_REFDIVA_S, refDivA); + + } + + if (!fracMode) { + ndiv = (freq * (refDivA >> aModeRefSel)) / 60; + channelSel = ndiv & 0x1ff; + channelFrac = (ndiv & 0xfffffe00) * 2; + channelSel = (channelSel << 17) | channelFrac; + } + } + + reg32 = reg32 | + (bMode << 29) | + (fracMode << 28) | (aModeRefSel << 26) | (channelSel); + + REG_WRITE(ah, AR_PHY_SYNTH_CONTROL, reg32); + + ah->curchan = chan; + + return 0; +} + +/** + * ar9002_hw_spur_mitigate - convert baseband spur frequency + * @ah: atheros hardware structure + * @chan: + * + * For single-chip solutions. Converts to baseband spur frequency given the + * input channel frequency and compute register settings below. + */ +static void ar9002_hw_spur_mitigate(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + int bb_spur = AR_NO_SPUR; + int freq; + int bin, cur_bin; + int bb_spur_off, spur_subchannel_sd; + int spur_freq_sd; + int spur_delta_phase; + int denominator; + int upper, lower, cur_vit_mask; + int tmp, newVal; + int i; + static const int pilot_mask_reg[4] = { + AR_PHY_TIMING7, AR_PHY_TIMING8, + AR_PHY_PILOT_MASK_01_30, AR_PHY_PILOT_MASK_31_60 + }; + static const int chan_mask_reg[4] = { + AR_PHY_TIMING9, AR_PHY_TIMING10, + AR_PHY_CHANNEL_MASK_01_30, AR_PHY_CHANNEL_MASK_31_60 + }; + static const int inc[4] = { 0, 100, 0, 0 }; + struct chan_centers centers; + + int8_t mask_m[123]; + int8_t mask_p[123]; + int8_t mask_amt; + int tmp_mask; + int cur_bb_spur; + bool is2GHz = IS_CHAN_2GHZ(chan); + + memset(&mask_m, 0, sizeof(int8_t) * 123); + memset(&mask_p, 0, sizeof(int8_t) * 123); + + ath9k_hw_get_channel_centers(ah, chan, ¢ers); + freq = centers.synth_center; + + ah->config.spurmode = SPUR_ENABLE_EEPROM; + for (i = 0; i < AR_EEPROM_MODAL_SPURS; i++) { + cur_bb_spur = ah->eep_ops->get_spur_channel(ah, i, is2GHz); + + if (AR_NO_SPUR == cur_bb_spur) + break; + + if (is2GHz) + cur_bb_spur = (cur_bb_spur / 10) + AR_BASE_FREQ_2GHZ; + else + cur_bb_spur = (cur_bb_spur / 10) + AR_BASE_FREQ_5GHZ; + + cur_bb_spur = cur_bb_spur - freq; + + if (IS_CHAN_HT40(chan)) { + if ((cur_bb_spur > -AR_SPUR_FEEQ_BOUND_HT40) && + (cur_bb_spur < AR_SPUR_FEEQ_BOUND_HT40)) { + bb_spur = cur_bb_spur; + break; + } + } else if ((cur_bb_spur > -AR_SPUR_FEEQ_BOUND_HT20) && + (cur_bb_spur < AR_SPUR_FEEQ_BOUND_HT20)) { + bb_spur = cur_bb_spur; + break; + } + } + + if (AR_NO_SPUR == bb_spur) { + REG_CLR_BIT(ah, AR_PHY_FORCE_CLKEN_CCK, + AR_PHY_FORCE_CLKEN_CCK_MRC_MUX); + return; + } else { + REG_CLR_BIT(ah, AR_PHY_FORCE_CLKEN_CCK, + AR_PHY_FORCE_CLKEN_CCK_MRC_MUX); + } + + bin = bb_spur * 320; + + tmp = REG_READ(ah, AR_PHY_TIMING_CTRL4(0)); + + ENABLE_REGWRITE_BUFFER(ah); + + newVal = tmp | (AR_PHY_TIMING_CTRL4_ENABLE_SPUR_RSSI | + AR_PHY_TIMING_CTRL4_ENABLE_SPUR_FILTER | + AR_PHY_TIMING_CTRL4_ENABLE_CHAN_MASK | + AR_PHY_TIMING_CTRL4_ENABLE_PILOT_MASK); + REG_WRITE(ah, AR_PHY_TIMING_CTRL4(0), newVal); + + newVal = (AR_PHY_SPUR_REG_MASK_RATE_CNTL | + AR_PHY_SPUR_REG_ENABLE_MASK_PPM | + AR_PHY_SPUR_REG_MASK_RATE_SELECT | + AR_PHY_SPUR_REG_ENABLE_VIT_SPUR_RSSI | + SM(SPUR_RSSI_THRESH, AR_PHY_SPUR_REG_SPUR_RSSI_THRESH)); + REG_WRITE(ah, AR_PHY_SPUR_REG, newVal); + + if (IS_CHAN_HT40(chan)) { + if (bb_spur < 0) { + spur_subchannel_sd = 1; + bb_spur_off = bb_spur + 10; + } else { + spur_subchannel_sd = 0; + bb_spur_off = bb_spur - 10; + } + } else { + spur_subchannel_sd = 0; + bb_spur_off = bb_spur; + } + + if (IS_CHAN_HT40(chan)) + spur_delta_phase = + ((bb_spur * 262144) / + 10) & AR_PHY_TIMING11_SPUR_DELTA_PHASE; + else + spur_delta_phase = + ((bb_spur * 524288) / + 10) & AR_PHY_TIMING11_SPUR_DELTA_PHASE; + + denominator = IS_CHAN_2GHZ(chan) ? 44 : 40; + spur_freq_sd = ((bb_spur_off * 2048) / denominator) & 0x3ff; + + newVal = (AR_PHY_TIMING11_USE_SPUR_IN_AGC | + SM(spur_freq_sd, AR_PHY_TIMING11_SPUR_FREQ_SD) | + SM(spur_delta_phase, AR_PHY_TIMING11_SPUR_DELTA_PHASE)); + REG_WRITE(ah, AR_PHY_TIMING11, newVal); + + newVal = spur_subchannel_sd << AR_PHY_SFCORR_SPUR_SUBCHNL_SD_S; + REG_WRITE(ah, AR_PHY_SFCORR_EXT, newVal); + + cur_bin = -6000; + upper = bin + 100; + lower = bin - 100; + + for (i = 0; i < 4; i++) { + int pilot_mask = 0; + int chan_mask = 0; + int bp = 0; + for (bp = 0; bp < 30; bp++) { + if ((cur_bin > lower) && (cur_bin < upper)) { + pilot_mask = pilot_mask | 0x1 << bp; + chan_mask = chan_mask | 0x1 << bp; + } + cur_bin += 100; + } + cur_bin += inc[i]; + REG_WRITE(ah, pilot_mask_reg[i], pilot_mask); + REG_WRITE(ah, chan_mask_reg[i], chan_mask); + } + + cur_vit_mask = 6100; + upper = bin + 120; + lower = bin - 120; + + for (i = 0; i < 123; i++) { + if ((cur_vit_mask > lower) && (cur_vit_mask < upper)) { + + /* workaround for gcc bug #37014 */ + volatile int tmp_v = abs(cur_vit_mask - bin); + + if (tmp_v < 75) + mask_amt = 1; + else + mask_amt = 0; + if (cur_vit_mask < 0) + mask_m[abs(cur_vit_mask / 100)] = mask_amt; + else + mask_p[cur_vit_mask / 100] = mask_amt; + } + cur_vit_mask -= 100; + } + + tmp_mask = (mask_m[46] << 30) | (mask_m[47] << 28) + | (mask_m[48] << 26) | (mask_m[49] << 24) + | (mask_m[50] << 22) | (mask_m[51] << 20) + | (mask_m[52] << 18) | (mask_m[53] << 16) + | (mask_m[54] << 14) | (mask_m[55] << 12) + | (mask_m[56] << 10) | (mask_m[57] << 8) + | (mask_m[58] << 6) | (mask_m[59] << 4) + | (mask_m[60] << 2) | (mask_m[61] << 0); + REG_WRITE(ah, AR_PHY_BIN_MASK_1, tmp_mask); + REG_WRITE(ah, AR_PHY_VIT_MASK2_M_46_61, tmp_mask); + + tmp_mask = (mask_m[31] << 28) + | (mask_m[32] << 26) | (mask_m[33] << 24) + | (mask_m[34] << 22) | (mask_m[35] << 20) + | (mask_m[36] << 18) | (mask_m[37] << 16) + | (mask_m[48] << 14) | (mask_m[39] << 12) + | (mask_m[40] << 10) | (mask_m[41] << 8) + | (mask_m[42] << 6) | (mask_m[43] << 4) + | (mask_m[44] << 2) | (mask_m[45] << 0); + REG_WRITE(ah, AR_PHY_BIN_MASK_2, tmp_mask); + REG_WRITE(ah, AR_PHY_MASK2_M_31_45, tmp_mask); + + tmp_mask = (mask_m[16] << 30) | (mask_m[16] << 28) + | (mask_m[18] << 26) | (mask_m[18] << 24) + | (mask_m[20] << 22) | (mask_m[20] << 20) + | (mask_m[22] << 18) | (mask_m[22] << 16) + | (mask_m[24] << 14) | (mask_m[24] << 12) + | (mask_m[25] << 10) | (mask_m[26] << 8) + | (mask_m[27] << 6) | (mask_m[28] << 4) + | (mask_m[29] << 2) | (mask_m[30] << 0); + REG_WRITE(ah, AR_PHY_BIN_MASK_3, tmp_mask); + REG_WRITE(ah, AR_PHY_MASK2_M_16_30, tmp_mask); + + tmp_mask = (mask_m[0] << 30) | (mask_m[1] << 28) + | (mask_m[2] << 26) | (mask_m[3] << 24) + | (mask_m[4] << 22) | (mask_m[5] << 20) + | (mask_m[6] << 18) | (mask_m[7] << 16) + | (mask_m[8] << 14) | (mask_m[9] << 12) + | (mask_m[10] << 10) | (mask_m[11] << 8) + | (mask_m[12] << 6) | (mask_m[13] << 4) + | (mask_m[14] << 2) | (mask_m[15] << 0); + REG_WRITE(ah, AR_PHY_MASK_CTL, tmp_mask); + REG_WRITE(ah, AR_PHY_MASK2_M_00_15, tmp_mask); + + tmp_mask = (mask_p[15] << 28) + | (mask_p[14] << 26) | (mask_p[13] << 24) + | (mask_p[12] << 22) | (mask_p[11] << 20) + | (mask_p[10] << 18) | (mask_p[9] << 16) + | (mask_p[8] << 14) | (mask_p[7] << 12) + | (mask_p[6] << 10) | (mask_p[5] << 8) + | (mask_p[4] << 6) | (mask_p[3] << 4) + | (mask_p[2] << 2) | (mask_p[1] << 0); + REG_WRITE(ah, AR_PHY_BIN_MASK2_1, tmp_mask); + REG_WRITE(ah, AR_PHY_MASK2_P_15_01, tmp_mask); + + tmp_mask = (mask_p[30] << 28) + | (mask_p[29] << 26) | (mask_p[28] << 24) + | (mask_p[27] << 22) | (mask_p[26] << 20) + | (mask_p[25] << 18) | (mask_p[24] << 16) + | (mask_p[23] << 14) | (mask_p[22] << 12) + | (mask_p[21] << 10) | (mask_p[20] << 8) + | (mask_p[19] << 6) | (mask_p[18] << 4) + | (mask_p[17] << 2) | (mask_p[16] << 0); + REG_WRITE(ah, AR_PHY_BIN_MASK2_2, tmp_mask); + REG_WRITE(ah, AR_PHY_MASK2_P_30_16, tmp_mask); + + tmp_mask = (mask_p[45] << 28) + | (mask_p[44] << 26) | (mask_p[43] << 24) + | (mask_p[42] << 22) | (mask_p[41] << 20) + | (mask_p[40] << 18) | (mask_p[39] << 16) + | (mask_p[38] << 14) | (mask_p[37] << 12) + | (mask_p[36] << 10) | (mask_p[35] << 8) + | (mask_p[34] << 6) | (mask_p[33] << 4) + | (mask_p[32] << 2) | (mask_p[31] << 0); + REG_WRITE(ah, AR_PHY_BIN_MASK2_3, tmp_mask); + REG_WRITE(ah, AR_PHY_MASK2_P_45_31, tmp_mask); + + tmp_mask = (mask_p[61] << 30) | (mask_p[60] << 28) + | (mask_p[59] << 26) | (mask_p[58] << 24) + | (mask_p[57] << 22) | (mask_p[56] << 20) + | (mask_p[55] << 18) | (mask_p[54] << 16) + | (mask_p[53] << 14) | (mask_p[52] << 12) + | (mask_p[51] << 10) | (mask_p[50] << 8) + | (mask_p[49] << 6) | (mask_p[48] << 4) + | (mask_p[47] << 2) | (mask_p[46] << 0); + REG_WRITE(ah, AR_PHY_BIN_MASK2_4, tmp_mask); + REG_WRITE(ah, AR_PHY_MASK2_P_61_45, tmp_mask); + + REGWRITE_BUFFER_FLUSH(ah); +} + +static void ar9002_olc_init(struct ath_hw *ah) +{ + u32 i; + + if (!OLC_FOR_AR9280_20_LATER) + return; + + if (OLC_FOR_AR9287_10_LATER) { + REG_SET_BIT(ah, AR_PHY_TX_PWRCTRL9, + AR_PHY_TX_PWRCTRL9_RES_DC_REMOVAL); + ath9k_hw_analog_shift_rmw(ah, AR9287_AN_TXPC0, + AR9287_AN_TXPC0_TXPCMODE, + AR9287_AN_TXPC0_TXPCMODE_S, + AR9287_AN_TXPC0_TXPCMODE_TEMPSENSE); + udelay(100); + } else { + for (i = 0; i < AR9280_TX_GAIN_TABLE_SIZE; i++) + ah->originalGain[i] = + MS(REG_READ(ah, AR_PHY_TX_GAIN_TBL1 + i * 4), + AR_PHY_TX_GAIN); + ah->PDADCdelta = 0; + } +} + +static u32 ar9002_hw_compute_pll_control(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + int ref_div = 5; + int pll_div = 0x2c; + u32 pll; + + if (chan && IS_CHAN_5GHZ(chan) && !IS_CHAN_A_FAST_CLOCK(ah, chan)) { + if (AR_SREV_9280_20(ah)) { + ref_div = 10; + pll_div = 0x50; + } else { + pll_div = 0x28; + } + } + + pll = SM(ref_div, AR_RTC_9160_PLL_REFDIV); + pll |= SM(pll_div, AR_RTC_9160_PLL_DIV); + + if (chan && IS_CHAN_HALF_RATE(chan)) + pll |= SM(0x1, AR_RTC_9160_PLL_CLKSEL); + else if (chan && IS_CHAN_QUARTER_RATE(chan)) + pll |= SM(0x2, AR_RTC_9160_PLL_CLKSEL); + + return pll; +} + +static void ar9002_hw_do_getnf(struct ath_hw *ah, + int16_t nfarray[NUM_NF_READINGS]) +{ + int16_t nf; + + nf = MS(REG_READ(ah, AR_PHY_CCA), AR9280_PHY_MINCCA_PWR); + nfarray[0] = sign_extend32(nf, 8); + + nf = MS(REG_READ(ah, AR_PHY_EXT_CCA), AR9280_PHY_EXT_MINCCA_PWR); + if (IS_CHAN_HT40(ah->curchan)) + nfarray[3] = sign_extend32(nf, 8); + + if (AR_SREV_9285(ah) || AR_SREV_9271(ah)) + return; + + nf = MS(REG_READ(ah, AR_PHY_CH1_CCA), AR9280_PHY_CH1_MINCCA_PWR); + nfarray[1] = sign_extend32(nf, 8); + + nf = MS(REG_READ(ah, AR_PHY_CH1_EXT_CCA), AR9280_PHY_CH1_EXT_MINCCA_PWR); + if (IS_CHAN_HT40(ah->curchan)) + nfarray[4] = sign_extend32(nf, 8); +} + +static void ar9002_hw_set_nf_limits(struct ath_hw *ah) +{ + if (AR_SREV_9285(ah)) { + ah->nf_2g.max = AR_PHY_CCA_MAX_GOOD_VAL_9285_2GHZ; + ah->nf_2g.min = AR_PHY_CCA_MIN_GOOD_VAL_9285_2GHZ; + ah->nf_2g.nominal = AR_PHY_CCA_NOM_VAL_9285_2GHZ; + } else if (AR_SREV_9287(ah)) { + ah->nf_2g.max = AR_PHY_CCA_MAX_GOOD_VAL_9287_2GHZ; + ah->nf_2g.min = AR_PHY_CCA_MIN_GOOD_VAL_9287_2GHZ; + ah->nf_2g.nominal = AR_PHY_CCA_NOM_VAL_9287_2GHZ; + } else if (AR_SREV_9271(ah)) { + ah->nf_2g.max = AR_PHY_CCA_MAX_GOOD_VAL_9271_2GHZ; + ah->nf_2g.min = AR_PHY_CCA_MIN_GOOD_VAL_9271_2GHZ; + ah->nf_2g.nominal = AR_PHY_CCA_NOM_VAL_9271_2GHZ; + } else { + ah->nf_2g.max = AR_PHY_CCA_MAX_GOOD_VAL_9280_2GHZ; + ah->nf_2g.min = AR_PHY_CCA_MIN_GOOD_VAL_9280_2GHZ; + ah->nf_2g.nominal = AR_PHY_CCA_NOM_VAL_9280_2GHZ; + ah->nf_5g.max = AR_PHY_CCA_MAX_GOOD_VAL_9280_5GHZ; + ah->nf_5g.min = AR_PHY_CCA_MIN_GOOD_VAL_9280_5GHZ; + ah->nf_5g.nominal = AR_PHY_CCA_NOM_VAL_9280_5GHZ; + } +} + +static void ar9002_hw_antdiv_comb_conf_get(struct ath_hw *ah, + struct ath_hw_antcomb_conf *antconf) +{ + u32 regval; + + regval = REG_READ(ah, AR_PHY_MULTICHAIN_GAIN_CTL); + antconf->main_lna_conf = (regval & AR_PHY_9285_ANT_DIV_MAIN_LNACONF) >> + AR_PHY_9285_ANT_DIV_MAIN_LNACONF_S; + antconf->alt_lna_conf = (regval & AR_PHY_9285_ANT_DIV_ALT_LNACONF) >> + AR_PHY_9285_ANT_DIV_ALT_LNACONF_S; + antconf->fast_div_bias = (regval & AR_PHY_9285_FAST_DIV_BIAS) >> + AR_PHY_9285_FAST_DIV_BIAS_S; + antconf->lna1_lna2_delta = -3; + antconf->div_group = 0; +} + +static void ar9002_hw_antdiv_comb_conf_set(struct ath_hw *ah, + struct ath_hw_antcomb_conf *antconf) +{ + u32 regval; + + regval = REG_READ(ah, AR_PHY_MULTICHAIN_GAIN_CTL); + regval &= ~(AR_PHY_9285_ANT_DIV_MAIN_LNACONF | + AR_PHY_9285_ANT_DIV_ALT_LNACONF | + AR_PHY_9285_FAST_DIV_BIAS); + regval |= ((antconf->main_lna_conf << AR_PHY_9285_ANT_DIV_MAIN_LNACONF_S) + & AR_PHY_9285_ANT_DIV_MAIN_LNACONF); + regval |= ((antconf->alt_lna_conf << AR_PHY_9285_ANT_DIV_ALT_LNACONF_S) + & AR_PHY_9285_ANT_DIV_ALT_LNACONF); + regval |= ((antconf->fast_div_bias << AR_PHY_9285_FAST_DIV_BIAS_S) + & AR_PHY_9285_FAST_DIV_BIAS); + + REG_WRITE(ah, AR_PHY_MULTICHAIN_GAIN_CTL, regval); +} + +static void ar9002_hw_spectral_scan_config(struct ath_hw *ah, + struct ath_spec_scan *param) +{ + u8 count; + + if (!param->enabled) { + REG_CLR_BIT(ah, AR_PHY_SPECTRAL_SCAN, + AR_PHY_SPECTRAL_SCAN_ENABLE); + return; + } + REG_SET_BIT(ah, AR_PHY_RADAR_0, AR_PHY_RADAR_0_FFT_ENA); + REG_SET_BIT(ah, AR_PHY_SPECTRAL_SCAN, AR_PHY_SPECTRAL_SCAN_ENABLE); + + if (param->short_repeat) + REG_SET_BIT(ah, AR_PHY_SPECTRAL_SCAN, + AR_PHY_SPECTRAL_SCAN_SHORT_REPEAT); + else + REG_CLR_BIT(ah, AR_PHY_SPECTRAL_SCAN, + AR_PHY_SPECTRAL_SCAN_SHORT_REPEAT); + + /* on AR92xx, the highest bit of count will make the the chip send + * spectral samples endlessly. Check if this really was intended, + * and fix otherwise. + */ + count = param->count; + if (param->endless) + count = 0x80; + else if (count & 0x80) + count = 0x7f; + + REG_RMW_FIELD(ah, AR_PHY_SPECTRAL_SCAN, + AR_PHY_SPECTRAL_SCAN_COUNT, count); + REG_RMW_FIELD(ah, AR_PHY_SPECTRAL_SCAN, + AR_PHY_SPECTRAL_SCAN_PERIOD, param->period); + REG_RMW_FIELD(ah, AR_PHY_SPECTRAL_SCAN, + AR_PHY_SPECTRAL_SCAN_FFT_PERIOD, param->fft_period); + + return; +} + +static void ar9002_hw_spectral_scan_trigger(struct ath_hw *ah) +{ + REG_SET_BIT(ah, AR_PHY_SPECTRAL_SCAN, AR_PHY_SPECTRAL_SCAN_ENABLE); + /* Activate spectral scan */ + REG_SET_BIT(ah, AR_PHY_SPECTRAL_SCAN, + AR_PHY_SPECTRAL_SCAN_ACTIVE); +} + +static void ar9002_hw_spectral_scan_wait(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + + /* Poll for spectral scan complete */ + if (!ath9k_hw_wait(ah, AR_PHY_SPECTRAL_SCAN, + AR_PHY_SPECTRAL_SCAN_ACTIVE, + 0, AH_WAIT_TIMEOUT)) { + ath_err(common, "spectral scan wait failed\n"); + return; + } +} + +void ar9002_hw_attach_phy_ops(struct ath_hw *ah) +{ + struct ath_hw_private_ops *priv_ops = ath9k_hw_private_ops(ah); + struct ath_hw_ops *ops = ath9k_hw_ops(ah); + + priv_ops->set_rf_regs = NULL; + priv_ops->rf_set_freq = ar9002_hw_set_channel; + priv_ops->spur_mitigate_freq = ar9002_hw_spur_mitigate; + priv_ops->olc_init = ar9002_olc_init; + priv_ops->compute_pll_control = ar9002_hw_compute_pll_control; + priv_ops->do_getnf = ar9002_hw_do_getnf; + + ops->antdiv_comb_conf_get = ar9002_hw_antdiv_comb_conf_get; + ops->antdiv_comb_conf_set = ar9002_hw_antdiv_comb_conf_set; + ops->spectral_scan_config = ar9002_hw_spectral_scan_config; + ops->spectral_scan_trigger = ar9002_hw_spectral_scan_trigger; + ops->spectral_scan_wait = ar9002_hw_spectral_scan_wait; + + ar9002_hw_set_nf_limits(ah); +} diff --git a/drivers/net/wireless/ath/ath9k/ar9002_phy.h b/drivers/net/wireless/ath/ath9k/ar9002_phy.h new file mode 100644 index 0000000..f9eb2c3 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9002_phy.h @@ -0,0 +1,615 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef AR9002_PHY_H +#define AR9002_PHY_H + +#define AR_PHY_TEST 0x9800 +#define PHY_AGC_CLR 0x10000000 +#define RFSILENT_BB 0x00002000 + +#define AR_PHY_TURBO 0x9804 +#define AR_PHY_FC_TURBO_MODE 0x00000001 +#define AR_PHY_FC_TURBO_SHORT 0x00000002 +#define AR_PHY_FC_DYN2040_EN 0x00000004 +#define AR_PHY_FC_DYN2040_PRI_ONLY 0x00000008 +#define AR_PHY_FC_DYN2040_PRI_CH 0x00000010 +/* For 25 MHz channel spacing -- not used but supported by hw */ +#define AR_PHY_FC_DYN2040_EXT_CH 0x00000020 +#define AR_PHY_FC_HT_EN 0x00000040 +#define AR_PHY_FC_SHORT_GI_40 0x00000080 +#define AR_PHY_FC_WALSH 0x00000100 +#define AR_PHY_FC_SINGLE_HT_LTF1 0x00000200 +#define AR_PHY_FC_ENABLE_DAC_FIFO 0x00000800 + +#define AR_PHY_TEST2 0x9808 + +#define AR_PHY_TIMING2 0x9810 +#define AR_PHY_TIMING3 0x9814 +#define AR_PHY_TIMING3_DSC_MAN 0xFFFE0000 +#define AR_PHY_TIMING3_DSC_MAN_S 17 +#define AR_PHY_TIMING3_DSC_EXP 0x0001E000 +#define AR_PHY_TIMING3_DSC_EXP_S 13 + +#define AR_PHY_CHIP_ID_REV_0 0x80 +#define AR_PHY_CHIP_ID_REV_1 0x81 +#define AR_PHY_CHIP_ID_9160_REV_0 0xb0 + +#define AR_PHY_ACTIVE 0x981C +#define AR_PHY_ACTIVE_EN 0x00000001 +#define AR_PHY_ACTIVE_DIS 0x00000000 + +#define AR_PHY_RF_CTL2 0x9824 +#define AR_PHY_TX_END_DATA_START 0x000000FF +#define AR_PHY_TX_END_DATA_START_S 0 +#define AR_PHY_TX_END_PA_ON 0x0000FF00 +#define AR_PHY_TX_END_PA_ON_S 8 + +#define AR_PHY_RF_CTL3 0x9828 +#define AR_PHY_TX_END_TO_A2_RX_ON 0x00FF0000 +#define AR_PHY_TX_END_TO_A2_RX_ON_S 16 +#define AR_PHY_TX_END_TO_ADC_ON 0xFF000000 +#define AR_PHY_TX_END_TO_ADC_ON_S 24 + +#define AR_PHY_ADC_CTL 0x982C +#define AR_PHY_ADC_CTL_OFF_INBUFGAIN 0x00000003 +#define AR_PHY_ADC_CTL_OFF_INBUFGAIN_S 0 +#define AR_PHY_ADC_CTL_OFF_PWDDAC 0x00002000 +#define AR_PHY_ADC_CTL_OFF_PWDBANDGAP 0x00004000 +#define AR_PHY_ADC_CTL_OFF_PWDADC 0x00008000 +#define AR_PHY_ADC_CTL_ON_INBUFGAIN 0x00030000 +#define AR_PHY_ADC_CTL_ON_INBUFGAIN_S 16 + +#define AR_PHY_ADC_SERIAL_CTL 0x9830 +#define AR_PHY_SEL_INTERNAL_ADDAC 0x00000000 +#define AR_PHY_SEL_EXTERNAL_RADIO 0x00000001 + +#define AR_PHY_RF_CTL4 0x9834 +#define AR_PHY_RF_CTL4_TX_END_XPAB_OFF 0xFF000000 +#define AR_PHY_RF_CTL4_TX_END_XPAB_OFF_S 24 +#define AR_PHY_RF_CTL4_TX_END_XPAA_OFF 0x00FF0000 +#define AR_PHY_RF_CTL4_TX_END_XPAA_OFF_S 16 +#define AR_PHY_RF_CTL4_FRAME_XPAB_ON 0x0000FF00 +#define AR_PHY_RF_CTL4_FRAME_XPAB_ON_S 8 +#define AR_PHY_RF_CTL4_FRAME_XPAA_ON 0x000000FF +#define AR_PHY_RF_CTL4_FRAME_XPAA_ON_S 0 + +#define AR_PHY_TSTDAC_CONST 0x983c + +#define AR_PHY_SETTLING 0x9844 +#define AR_PHY_SETTLING_SWITCH 0x00003F80 +#define AR_PHY_SETTLING_SWITCH_S 7 + +#define AR_PHY_RXGAIN 0x9848 +#define AR_PHY_RXGAIN_TXRX_ATTEN 0x0003F000 +#define AR_PHY_RXGAIN_TXRX_ATTEN_S 12 +#define AR_PHY_RXGAIN_TXRX_RF_MAX 0x007C0000 +#define AR_PHY_RXGAIN_TXRX_RF_MAX_S 18 +#define AR9280_PHY_RXGAIN_TXRX_ATTEN 0x00003F80 +#define AR9280_PHY_RXGAIN_TXRX_ATTEN_S 7 +#define AR9280_PHY_RXGAIN_TXRX_MARGIN 0x001FC000 +#define AR9280_PHY_RXGAIN_TXRX_MARGIN_S 14 + +#define AR_PHY_DESIRED_SZ 0x9850 +#define AR_PHY_DESIRED_SZ_ADC 0x000000FF +#define AR_PHY_DESIRED_SZ_ADC_S 0 +#define AR_PHY_DESIRED_SZ_PGA 0x0000FF00 +#define AR_PHY_DESIRED_SZ_PGA_S 8 +#define AR_PHY_DESIRED_SZ_TOT_DES 0x0FF00000 +#define AR_PHY_DESIRED_SZ_TOT_DES_S 20 + +#define AR_PHY_FIND_SIG 0x9858 +#define AR_PHY_FIND_SIG_FIRSTEP 0x0003F000 +#define AR_PHY_FIND_SIG_FIRSTEP_S 12 +#define AR_PHY_FIND_SIG_FIRPWR 0x03FC0000 +#define AR_PHY_FIND_SIG_FIRPWR_S 18 + +#define AR_PHY_FIND_SIG_LOW 0x9840 +#define AR_PHY_FIND_SIG_FIRSTEP_LOW 0x00000FC0L +#define AR_PHY_FIND_SIG_FIRSTEP_LOW_S 6 + +#define AR_PHY_AGC_CTL1 0x985C +#define AR_PHY_AGC_CTL1_COARSE_LOW 0x00007F80 +#define AR_PHY_AGC_CTL1_COARSE_LOW_S 7 +#define AR_PHY_AGC_CTL1_COARSE_HIGH 0x003F8000 +#define AR_PHY_AGC_CTL1_COARSE_HIGH_S 15 + +#define AR_PHY_CCA 0x9864 +#define AR_PHY_MINCCA_PWR 0x0FF80000 +#define AR_PHY_MINCCA_PWR_S 19 +#define AR_PHY_CCA_THRESH62 0x0007F000 +#define AR_PHY_CCA_THRESH62_S 12 +#define AR9280_PHY_MINCCA_PWR 0x1FF00000 +#define AR9280_PHY_MINCCA_PWR_S 20 +#define AR9280_PHY_CCA_THRESH62 0x000FF000 +#define AR9280_PHY_CCA_THRESH62_S 12 + +#define AR_PHY_SFCORR_LOW 0x986C +#define AR_PHY_SFCORR_LOW_USE_SELF_CORR_LOW 0x00000001 +#define AR_PHY_SFCORR_LOW_M2COUNT_THR_LOW 0x00003F00 +#define AR_PHY_SFCORR_LOW_M2COUNT_THR_LOW_S 8 +#define AR_PHY_SFCORR_LOW_M1_THRESH_LOW 0x001FC000 +#define AR_PHY_SFCORR_LOW_M1_THRESH_LOW_S 14 +#define AR_PHY_SFCORR_LOW_M2_THRESH_LOW 0x0FE00000 +#define AR_PHY_SFCORR_LOW_M2_THRESH_LOW_S 21 + +#define AR_PHY_SFCORR 0x9868 +#define AR_PHY_SFCORR_M2COUNT_THR 0x0000001F +#define AR_PHY_SFCORR_M2COUNT_THR_S 0 +#define AR_PHY_SFCORR_M1_THRESH 0x00FE0000 +#define AR_PHY_SFCORR_M1_THRESH_S 17 +#define AR_PHY_SFCORR_M2_THRESH 0x7F000000 +#define AR_PHY_SFCORR_M2_THRESH_S 24 + +#define AR_PHY_SLEEP_CTR_CONTROL 0x9870 +#define AR_PHY_SLEEP_CTR_LIMIT 0x9874 +#define AR_PHY_SYNTH_CONTROL 0x9874 +#define AR_PHY_SLEEP_SCAL 0x9878 + +#define AR_PHY_PLL_CTL 0x987c +#define AR_PHY_PLL_CTL_40 0xaa +#define AR_PHY_PLL_CTL_40_5413 0x04 +#define AR_PHY_PLL_CTL_44 0xab +#define AR_PHY_PLL_CTL_44_2133 0xeb +#define AR_PHY_PLL_CTL_40_2133 0xea + +#define AR_PHY_SPECTRAL_SCAN 0x9910 /* AR9280 spectral scan configuration register */ +#define AR_PHY_SPECTRAL_SCAN_ENABLE 0x1 +#define AR_PHY_SPECTRAL_SCAN_ENA 0x00000001 /* Enable spectral scan, reg 68, bit 0 */ +#define AR_PHY_SPECTRAL_SCAN_ENA_S 0 /* Enable spectral scan, reg 68, bit 0 */ +#define AR_PHY_SPECTRAL_SCAN_ACTIVE 0x00000002 /* Activate spectral scan reg 68, bit 1*/ +#define AR_PHY_SPECTRAL_SCAN_ACTIVE_S 1 /* Activate spectral scan reg 68, bit 1*/ +#define AR_PHY_SPECTRAL_SCAN_FFT_PERIOD 0x000000F0 /* Interval for FFT reports, reg 68, bits 4-7*/ +#define AR_PHY_SPECTRAL_SCAN_FFT_PERIOD_S 4 +#define AR_PHY_SPECTRAL_SCAN_PERIOD 0x0000FF00 /* Interval for FFT reports, reg 68, bits 8-15*/ +#define AR_PHY_SPECTRAL_SCAN_PERIOD_S 8 +#define AR_PHY_SPECTRAL_SCAN_COUNT 0x00FF0000 /* Number of reports, reg 68, bits 16-23*/ +#define AR_PHY_SPECTRAL_SCAN_COUNT_S 16 +#define AR_PHY_SPECTRAL_SCAN_SHORT_REPEAT 0x01000000 /* Short repeat, reg 68, bit 24*/ +#define AR_PHY_SPECTRAL_SCAN_SHORT_REPEAT_S 24 /* Short repeat, reg 68, bit 24*/ + +#define AR_PHY_RX_DELAY 0x9914 +#define AR_PHY_SEARCH_START_DELAY 0x9918 +#define AR_PHY_RX_DELAY_DELAY 0x00003FFF + +#define AR_PHY_TIMING_CTRL4(_i) (0x9920 + ((_i) << 12)) +#define AR_PHY_TIMING_CTRL4_IQCORR_Q_Q_COFF 0x01F +#define AR_PHY_TIMING_CTRL4_IQCORR_Q_Q_COFF_S 0 +#define AR_PHY_TIMING_CTRL4_IQCORR_Q_I_COFF 0x7E0 +#define AR_PHY_TIMING_CTRL4_IQCORR_Q_I_COFF_S 5 +#define AR_PHY_TIMING_CTRL4_IQCORR_ENABLE 0x800 +#define AR_PHY_TIMING_CTRL4_IQCAL_LOG_COUNT_MAX 0xF000 +#define AR_PHY_TIMING_CTRL4_IQCAL_LOG_COUNT_MAX_S 12 +#define AR_PHY_TIMING_CTRL4_DO_CAL 0x10000 + +#define AR_PHY_TIMING_CTRL4_ENABLE_SPUR_RSSI 0x80000000 +#define AR_PHY_TIMING_CTRL4_ENABLE_SPUR_FILTER 0x40000000 +#define AR_PHY_TIMING_CTRL4_ENABLE_CHAN_MASK 0x20000000 +#define AR_PHY_TIMING_CTRL4_ENABLE_PILOT_MASK 0x10000000 + +#define AR_PHY_TIMING5 0x9924 +#define AR_PHY_TIMING5_CYCPWR_THR1 0x000000FE +#define AR_PHY_TIMING5_CYCPWR_THR1_S 1 + +#define AR_PHY_POWER_TX_RATE1 0x9934 +#define AR_PHY_POWER_TX_RATE2 0x9938 +#define AR_PHY_POWER_TX_RATE_MAX 0x993c +#define AR_PHY_POWER_TX_RATE_MAX_TPC_ENABLE 0x00000040 + +#define AR_PHY_FRAME_CTL 0x9944 +#define AR_PHY_FRAME_CTL_TX_CLIP 0x00000038 +#define AR_PHY_FRAME_CTL_TX_CLIP_S 3 + +#define AR_PHY_TXPWRADJ 0x994C +#define AR_PHY_TXPWRADJ_CCK_GAIN_DELTA 0x00000FC0 +#define AR_PHY_TXPWRADJ_CCK_GAIN_DELTA_S 6 +#define AR_PHY_TXPWRADJ_CCK_PCDAC_INDEX 0x00FC0000 +#define AR_PHY_TXPWRADJ_CCK_PCDAC_INDEX_S 18 + +#define AR_PHY_RADAR_EXT 0x9940 +#define AR_PHY_RADAR_EXT_ENA 0x00004000 + +#define AR_PHY_RADAR_0 0x9954 +#define AR_PHY_RADAR_0_ENA 0x00000001 +#define AR_PHY_RADAR_0_FFT_ENA 0x80000000 +#define AR_PHY_RADAR_0_INBAND 0x0000003e +#define AR_PHY_RADAR_0_INBAND_S 1 +#define AR_PHY_RADAR_0_PRSSI 0x00000FC0 +#define AR_PHY_RADAR_0_PRSSI_S 6 +#define AR_PHY_RADAR_0_HEIGHT 0x0003F000 +#define AR_PHY_RADAR_0_HEIGHT_S 12 +#define AR_PHY_RADAR_0_RRSSI 0x00FC0000 +#define AR_PHY_RADAR_0_RRSSI_S 18 +#define AR_PHY_RADAR_0_FIRPWR 0x7F000000 +#define AR_PHY_RADAR_0_FIRPWR_S 24 + +#define AR_PHY_RADAR_1 0x9958 +#define AR_PHY_RADAR_1_RELPWR_ENA 0x00800000 +#define AR_PHY_RADAR_1_USE_FIR128 0x00400000 +#define AR_PHY_RADAR_1_RELPWR_THRESH 0x003F0000 +#define AR_PHY_RADAR_1_RELPWR_THRESH_S 16 +#define AR_PHY_RADAR_1_BLOCK_CHECK 0x00008000 +#define AR_PHY_RADAR_1_MAX_RRSSI 0x00004000 +#define AR_PHY_RADAR_1_RELSTEP_CHECK 0x00002000 +#define AR_PHY_RADAR_1_RELSTEP_THRESH 0x00001F00 +#define AR_PHY_RADAR_1_RELSTEP_THRESH_S 8 +#define AR_PHY_RADAR_1_MAXLEN 0x000000FF +#define AR_PHY_RADAR_1_MAXLEN_S 0 + +#define AR_PHY_SWITCH_CHAIN_0 0x9960 +#define AR_PHY_SWITCH_COM 0x9964 + +#define AR_PHY_SIGMA_DELTA 0x996C +#define AR_PHY_SIGMA_DELTA_ADC_SEL 0x00000003 +#define AR_PHY_SIGMA_DELTA_ADC_SEL_S 0 +#define AR_PHY_SIGMA_DELTA_FILT2 0x000000F8 +#define AR_PHY_SIGMA_DELTA_FILT2_S 3 +#define AR_PHY_SIGMA_DELTA_FILT1 0x00001F00 +#define AR_PHY_SIGMA_DELTA_FILT1_S 8 +#define AR_PHY_SIGMA_DELTA_ADC_CLIP 0x01FFE000 +#define AR_PHY_SIGMA_DELTA_ADC_CLIP_S 13 + +#define AR_PHY_RESTART 0x9970 +#define AR_PHY_RESTART_DIV_GC 0x001C0000 +#define AR_PHY_RESTART_DIV_GC_S 18 + +#define AR_PHY_RFBUS_REQ 0x997C +#define AR_PHY_RFBUS_REQ_EN 0x00000001 + +#define AR_PHY_TIMING7 0x9980 +#define AR_PHY_TIMING8 0x9984 +#define AR_PHY_TIMING8_PILOT_MASK_2 0x000FFFFF +#define AR_PHY_TIMING8_PILOT_MASK_2_S 0 + +#define AR_PHY_BIN_MASK2_1 0x9988 +#define AR_PHY_BIN_MASK2_2 0x998c +#define AR_PHY_BIN_MASK2_3 0x9990 +#define AR_PHY_BIN_MASK2_4 0x9994 + +#define AR_PHY_BIN_MASK_1 0x9900 +#define AR_PHY_BIN_MASK_2 0x9904 +#define AR_PHY_BIN_MASK_3 0x9908 + +#define AR_PHY_MASK_CTL 0x990c + +#define AR_PHY_BIN_MASK2_4_MASK_4 0x00003FFF +#define AR_PHY_BIN_MASK2_4_MASK_4_S 0 + +#define AR_PHY_TIMING9 0x9998 +#define AR_PHY_TIMING10 0x999c +#define AR_PHY_TIMING10_PILOT_MASK_2 0x000FFFFF +#define AR_PHY_TIMING10_PILOT_MASK_2_S 0 + +#define AR_PHY_TIMING11 0x99a0 +#define AR_PHY_TIMING11_SPUR_DELTA_PHASE 0x000FFFFF +#define AR_PHY_TIMING11_SPUR_DELTA_PHASE_S 0 +#define AR_PHY_TIMING11_USE_SPUR_IN_AGC 0x40000000 +#define AR_PHY_TIMING11_USE_SPUR_IN_SELFCOR 0x80000000 + +#define AR_PHY_RX_CHAINMASK 0x99a4 +#define AR_PHY_NEW_ADC_DC_GAIN_CORR(_i) (0x99b4 + ((_i) << 12)) +#define AR_PHY_NEW_ADC_GAIN_CORR_ENABLE 0x40000000 +#define AR_PHY_NEW_ADC_DC_OFFSET_CORR_ENABLE 0x80000000 + +#define AR_PHY_MULTICHAIN_GAIN_CTL 0x99ac +#define AR_PHY_9285_FAST_DIV_BIAS 0x00007E00 +#define AR_PHY_9285_FAST_DIV_BIAS_S 9 +#define AR_PHY_9285_ANT_DIV_CTL_ALL 0x7f000000 +#define AR_PHY_9285_ANT_DIV_CTL 0x01000000 +#define AR_PHY_9285_ANT_DIV_CTL_S 24 +#define AR_PHY_9285_ANT_DIV_ALT_LNACONF 0x06000000 +#define AR_PHY_9285_ANT_DIV_ALT_LNACONF_S 25 +#define AR_PHY_9285_ANT_DIV_MAIN_LNACONF 0x18000000 +#define AR_PHY_9285_ANT_DIV_MAIN_LNACONF_S 27 +#define AR_PHY_9285_ANT_DIV_ALT_GAINTB 0x20000000 +#define AR_PHY_9285_ANT_DIV_ALT_GAINTB_S 29 +#define AR_PHY_9285_ANT_DIV_MAIN_GAINTB 0x40000000 +#define AR_PHY_9285_ANT_DIV_MAIN_GAINTB_S 30 +#define AR_PHY_9285_ANT_DIV_LNA1 2 +#define AR_PHY_9285_ANT_DIV_LNA2 1 +#define AR_PHY_9285_ANT_DIV_LNA1_PLUS_LNA2 3 +#define AR_PHY_9285_ANT_DIV_LNA1_MINUS_LNA2 0 +#define AR_PHY_9285_ANT_DIV_GAINTB_0 0 +#define AR_PHY_9285_ANT_DIV_GAINTB_1 1 + +#define AR_PHY_EXT_CCA0 0x99b8 +#define AR_PHY_EXT_CCA0_THRESH62 0x000000FF +#define AR_PHY_EXT_CCA0_THRESH62_S 0 + +#define AR_PHY_EXT_CCA 0x99bc +#define AR_PHY_EXT_CCA_CYCPWR_THR1 0x0000FE00 +#define AR_PHY_EXT_CCA_CYCPWR_THR1_S 9 +#define AR_PHY_EXT_CCA_THRESH62 0x007F0000 +#define AR_PHY_EXT_CCA_THRESH62_S 16 +#define AR_PHY_EXT_TIMING5_CYCPWR_THR1 0x0000FE00L +#define AR_PHY_EXT_TIMING5_CYCPWR_THR1_S 9 + +#define AR_PHY_EXT_MINCCA_PWR 0xFF800000 +#define AR_PHY_EXT_MINCCA_PWR_S 23 +#define AR9280_PHY_EXT_MINCCA_PWR 0x01FF0000 +#define AR9280_PHY_EXT_MINCCA_PWR_S 16 + +#define AR_PHY_SFCORR_EXT 0x99c0 +#define AR_PHY_SFCORR_EXT_M1_THRESH 0x0000007F +#define AR_PHY_SFCORR_EXT_M1_THRESH_S 0 +#define AR_PHY_SFCORR_EXT_M2_THRESH 0x00003F80 +#define AR_PHY_SFCORR_EXT_M2_THRESH_S 7 +#define AR_PHY_SFCORR_EXT_M1_THRESH_LOW 0x001FC000 +#define AR_PHY_SFCORR_EXT_M1_THRESH_LOW_S 14 +#define AR_PHY_SFCORR_EXT_M2_THRESH_LOW 0x0FE00000 +#define AR_PHY_SFCORR_EXT_M2_THRESH_LOW_S 21 +#define AR_PHY_SFCORR_SPUR_SUBCHNL_SD_S 28 + +#define AR_PHY_HALFGI 0x99D0 +#define AR_PHY_HALFGI_DSC_MAN 0x0007FFF0 +#define AR_PHY_HALFGI_DSC_MAN_S 4 +#define AR_PHY_HALFGI_DSC_EXP 0x0000000F +#define AR_PHY_HALFGI_DSC_EXP_S 0 + +#define AR_PHY_CHAN_INFO_MEMORY 0x99DC +#define AR_PHY_CHAN_INFO_MEMORY_CAPTURE_MASK 0x0001 + +#define AR_PHY_HEAVY_CLIP_ENABLE 0x99E0 + +#define AR_PHY_HEAVY_CLIP_FACTOR_RIFS 0x99EC +#define AR_PHY_RIFS_INIT_DELAY 0x03ff0000 + +#define AR_PHY_M_SLEEP 0x99f0 +#define AR_PHY_REFCLKDLY 0x99f4 +#define AR_PHY_REFCLKPD 0x99f8 + +#define AR_PHY_CALMODE 0x99f0 + +#define AR_PHY_CALMODE_IQ 0x00000000 +#define AR_PHY_CALMODE_ADC_GAIN 0x00000001 +#define AR_PHY_CALMODE_ADC_DC_PER 0x00000002 +#define AR_PHY_CALMODE_ADC_DC_INIT 0x00000003 + +#define AR_PHY_CAL_MEAS_0(_i) (0x9c10 + ((_i) << 12)) +#define AR_PHY_CAL_MEAS_1(_i) (0x9c14 + ((_i) << 12)) +#define AR_PHY_CAL_MEAS_2(_i) (0x9c18 + ((_i) << 12)) +#define AR_PHY_CAL_MEAS_3(_i) (0x9c1c + ((_i) << 12)) + +#define AR_PHY_CURRENT_RSSI 0x9c1c +#define AR9280_PHY_CURRENT_RSSI 0x9c3c + +#define AR_PHY_RFBUS_GRANT 0x9C20 +#define AR_PHY_RFBUS_GRANT_EN 0x00000001 + +#define AR_PHY_CHAN_INFO_GAIN_DIFF 0x9CF4 +#define AR_PHY_CHAN_INFO_GAIN_DIFF_UPPER_LIMIT 320 + +#define AR_PHY_CHAN_INFO_GAIN 0x9CFC + +#define AR_PHY_MODE 0xA200 +#define AR_PHY_MODE_ASYNCFIFO 0x80 +#define AR_PHY_MODE_AR2133 0x08 +#define AR_PHY_MODE_AR5111 0x00 +#define AR_PHY_MODE_AR5112 0x08 +#define AR_PHY_MODE_DYNAMIC 0x04 +#define AR_PHY_MODE_RF2GHZ 0x02 +#define AR_PHY_MODE_RF5GHZ 0x00 +#define AR_PHY_MODE_CCK 0x01 +#define AR_PHY_MODE_OFDM 0x00 +#define AR_PHY_MODE_DYN_CCK_DISABLE 0x100 + +#define AR_PHY_CCK_TX_CTRL 0xA204 +#define AR_PHY_CCK_TX_CTRL_JAPAN 0x00000010 +#define AR_PHY_CCK_TX_CTRL_TX_DAC_SCALE_CCK 0x0000000C +#define AR_PHY_CCK_TX_CTRL_TX_DAC_SCALE_CCK_S 2 + +#define AR_PHY_CCK_DETECT 0xA208 +#define AR_PHY_CCK_DETECT_WEAK_SIG_THR_CCK 0x0000003F +#define AR_PHY_CCK_DETECT_WEAK_SIG_THR_CCK_S 0 +/* [12:6] settling time for antenna switch */ +#define AR_PHY_CCK_DETECT_ANT_SWITCH_TIME 0x00001FC0 +#define AR_PHY_CCK_DETECT_ANT_SWITCH_TIME_S 6 +#define AR_PHY_CCK_DETECT_BB_ENABLE_ANT_FAST_DIV 0x2000 +#define AR_PHY_CCK_DETECT_BB_ENABLE_ANT_FAST_DIV_S 13 + +#define AR_PHY_GAIN_2GHZ 0xA20C +#define AR_PHY_GAIN_2GHZ_RXTX_MARGIN 0x00FC0000 +#define AR_PHY_GAIN_2GHZ_RXTX_MARGIN_S 18 +#define AR_PHY_GAIN_2GHZ_BSW_MARGIN 0x00003C00 +#define AR_PHY_GAIN_2GHZ_BSW_MARGIN_S 10 +#define AR_PHY_GAIN_2GHZ_BSW_ATTEN 0x0000001F +#define AR_PHY_GAIN_2GHZ_BSW_ATTEN_S 0 + +#define AR_PHY_GAIN_2GHZ_XATTEN2_MARGIN 0x003E0000 +#define AR_PHY_GAIN_2GHZ_XATTEN2_MARGIN_S 17 +#define AR_PHY_GAIN_2GHZ_XATTEN1_MARGIN 0x0001F000 +#define AR_PHY_GAIN_2GHZ_XATTEN1_MARGIN_S 12 +#define AR_PHY_GAIN_2GHZ_XATTEN2_DB 0x00000FC0 +#define AR_PHY_GAIN_2GHZ_XATTEN2_DB_S 6 +#define AR_PHY_GAIN_2GHZ_XATTEN1_DB 0x0000003F +#define AR_PHY_GAIN_2GHZ_XATTEN1_DB_S 0 + +#define AR_PHY_CCK_RXCTRL4 0xA21C +#define AR_PHY_CCK_RXCTRL4_FREQ_EST_SHORT 0x01F80000 +#define AR_PHY_CCK_RXCTRL4_FREQ_EST_SHORT_S 19 + +#define AR_PHY_DAG_CTRLCCK 0xA228 +#define AR_PHY_DAG_CTRLCCK_EN_RSSI_THR 0x00000200 +#define AR_PHY_DAG_CTRLCCK_RSSI_THR 0x0001FC00 +#define AR_PHY_DAG_CTRLCCK_RSSI_THR_S 10 + +#define AR_PHY_FORCE_CLKEN_CCK 0xA22C +#define AR_PHY_FORCE_CLKEN_CCK_MRC_MUX 0x00000040 + +#define AR_PHY_POWER_TX_RATE3 0xA234 +#define AR_PHY_POWER_TX_RATE4 0xA238 + +#define AR_PHY_SCRM_SEQ_XR 0xA23C +#define AR_PHY_HEADER_DETECT_XR 0xA240 +#define AR_PHY_CHIRP_DETECTED_XR 0xA244 +#define AR_PHY_BLUETOOTH 0xA254 + +#define AR_PHY_TPCRG1 0xA258 +#define AR_PHY_TPCRG1_NUM_PD_GAIN 0x0000c000 +#define AR_PHY_TPCRG1_NUM_PD_GAIN_S 14 + +#define AR_PHY_TPCRG1_PD_GAIN_1 0x00030000 +#define AR_PHY_TPCRG1_PD_GAIN_1_S 16 +#define AR_PHY_TPCRG1_PD_GAIN_2 0x000C0000 +#define AR_PHY_TPCRG1_PD_GAIN_2_S 18 +#define AR_PHY_TPCRG1_PD_GAIN_3 0x00300000 +#define AR_PHY_TPCRG1_PD_GAIN_3_S 20 + +#define AR_PHY_TPCRG1_PD_CAL_ENABLE 0x00400000 +#define AR_PHY_TPCRG1_PD_CAL_ENABLE_S 22 + +#define AR_PHY_TX_PWRCTRL4 0xa264 +#define AR_PHY_TX_PWRCTRL_PD_AVG_VALID 0x00000001 +#define AR_PHY_TX_PWRCTRL_PD_AVG_VALID_S 0 +#define AR_PHY_TX_PWRCTRL_PD_AVG_OUT 0x000001FE +#define AR_PHY_TX_PWRCTRL_PD_AVG_OUT_S 1 + +#define AR_PHY_TX_PWRCTRL6_0 0xa270 +#define AR_PHY_TX_PWRCTRL6_1 0xb270 +#define AR_PHY_TX_PWRCTRL_ERR_EST_MODE 0x03000000 +#define AR_PHY_TX_PWRCTRL_ERR_EST_MODE_S 24 + +#define AR_PHY_TX_PWRCTRL7 0xa274 +#define AR_PHY_TX_PWRCTRL_INIT_TX_GAIN 0x01F80000 +#define AR_PHY_TX_PWRCTRL_INIT_TX_GAIN_S 19 + +#define AR_PHY_TX_PWRCTRL8 0xa278 + +#define AR_PHY_TX_PWRCTRL9 0xa27C + +#define AR_PHY_TX_PWRCTRL10 0xa394 +#define AR_PHY_TX_DESIRED_SCALE_CCK 0x00007C00 +#define AR_PHY_TX_DESIRED_SCALE_CCK_S 10 +#define AR_PHY_TX_PWRCTRL9_RES_DC_REMOVAL 0x80000000 +#define AR_PHY_TX_PWRCTRL9_RES_DC_REMOVAL_S 31 + +#define AR_PHY_TX_GAIN_TBL1 0xa300 +#define AR_PHY_TX_GAIN 0x0007F000 +#define AR_PHY_TX_GAIN_S 12 + +#define AR_PHY_CH0_TX_PWRCTRL11 0xa398 +#define AR_PHY_CH1_TX_PWRCTRL11 0xb398 +#define AR_PHY_CH0_TX_PWRCTRL12 0xa3dc +#define AR_PHY_CH0_TX_PWRCTRL13 0xa3e0 +#define AR_PHY_TX_PWRCTRL_OLPC_TEMP_COMP 0x0000FC00 +#define AR_PHY_TX_PWRCTRL_OLPC_TEMP_COMP_S 10 + +#define AR_PHY_VIT_MASK2_M_46_61 0xa3a0 +#define AR_PHY_MASK2_M_31_45 0xa3a4 +#define AR_PHY_MASK2_M_16_30 0xa3a8 +#define AR_PHY_MASK2_M_00_15 0xa3ac +#define AR_PHY_MASK2_P_15_01 0xa3b8 +#define AR_PHY_MASK2_P_30_16 0xa3bc +#define AR_PHY_MASK2_P_45_31 0xa3c0 +#define AR_PHY_MASK2_P_61_45 0xa3c4 +#define AR_PHY_SPUR_REG 0x994c + +#define AR_PHY_SPUR_REG_MASK_RATE_CNTL (0xFF << 18) +#define AR_PHY_SPUR_REG_MASK_RATE_CNTL_S 18 + +#define AR_PHY_SPUR_REG_ENABLE_MASK_PPM 0x20000 +#define AR_PHY_SPUR_REG_MASK_RATE_SELECT (0xFF << 9) +#define AR_PHY_SPUR_REG_MASK_RATE_SELECT_S 9 +#define AR_PHY_SPUR_REG_ENABLE_VIT_SPUR_RSSI 0x100 +#define AR_PHY_SPUR_REG_SPUR_RSSI_THRESH 0x7F +#define AR_PHY_SPUR_REG_SPUR_RSSI_THRESH_S 0 + +#define AR_PHY_PILOT_MASK_01_30 0xa3b0 +#define AR_PHY_PILOT_MASK_31_60 0xa3b4 + +#define AR_PHY_CHANNEL_MASK_01_30 0x99d4 +#define AR_PHY_CHANNEL_MASK_31_60 0x99d8 + +#define AR_PHY_ANALOG_SWAP 0xa268 +#define AR_PHY_SWAP_ALT_CHAIN 0x00000040 + +#define AR_PHY_TPCRG5 0xA26C +#define AR_PHY_TPCRG5_PD_GAIN_OVERLAP 0x0000000F +#define AR_PHY_TPCRG5_PD_GAIN_OVERLAP_S 0 +#define AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_1 0x000003F0 +#define AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_1_S 4 +#define AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_2 0x0000FC00 +#define AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_2_S 10 +#define AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_3 0x003F0000 +#define AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_3_S 16 +#define AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_4 0x0FC00000 +#define AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_4_S 22 + +/* Carrier leak calibration control, do it after AGC calibration */ +#define AR_PHY_CL_CAL_CTL 0xA358 +#define AR_PHY_CL_CAL_ENABLE 0x00000002 +#define AR_PHY_PARALLEL_CAL_ENABLE 0x00000001 + +#define AR_PHY_POWER_TX_RATE5 0xA38C +#define AR_PHY_POWER_TX_RATE6 0xA390 + +#define AR_PHY_CAL_CHAINMASK 0xA39C + +#define AR_PHY_POWER_TX_SUB 0xA3C8 +#define AR_PHY_POWER_TX_RATE7 0xA3CC +#define AR_PHY_POWER_TX_RATE8 0xA3D0 +#define AR_PHY_POWER_TX_RATE9 0xA3D4 + +#define AR_PHY_XPA_CFG 0xA3D8 +#define AR_PHY_FORCE_XPA_CFG 0x000000001 +#define AR_PHY_FORCE_XPA_CFG_S 0 + +#define AR_PHY_CH1_CCA 0xa864 +#define AR_PHY_CH1_MINCCA_PWR 0x0FF80000 +#define AR_PHY_CH1_MINCCA_PWR_S 19 +#define AR9280_PHY_CH1_MINCCA_PWR 0x1FF00000 +#define AR9280_PHY_CH1_MINCCA_PWR_S 20 + +#define AR_PHY_CH2_CCA 0xb864 +#define AR_PHY_CH2_MINCCA_PWR 0x0FF80000 +#define AR_PHY_CH2_MINCCA_PWR_S 19 + +#define AR_PHY_CH1_EXT_CCA 0xa9bc +#define AR_PHY_CH1_EXT_MINCCA_PWR 0xFF800000 +#define AR_PHY_CH1_EXT_MINCCA_PWR_S 23 +#define AR9280_PHY_CH1_EXT_MINCCA_PWR 0x01FF0000 +#define AR9280_PHY_CH1_EXT_MINCCA_PWR_S 16 + +#define AR_PHY_CH2_EXT_CCA 0xb9bc +#define AR_PHY_CH2_EXT_MINCCA_PWR 0xFF800000 +#define AR_PHY_CH2_EXT_MINCCA_PWR_S 23 + +#define AR_PHY_CCA_NOM_VAL_5416_2GHZ -90 +#define AR_PHY_CCA_NOM_VAL_5416_5GHZ -100 +#define AR_PHY_CCA_MIN_GOOD_VAL_5416_2GHZ -100 +#define AR_PHY_CCA_MIN_GOOD_VAL_5416_5GHZ -110 +#define AR_PHY_CCA_MAX_GOOD_VAL_5416_2GHZ -80 +#define AR_PHY_CCA_MAX_GOOD_VAL_5416_5GHZ -90 + +#define AR_PHY_CCA_NOM_VAL_9280_2GHZ -112 +#define AR_PHY_CCA_NOM_VAL_9280_5GHZ -112 +#define AR_PHY_CCA_MIN_GOOD_VAL_9280_2GHZ -127 +#define AR_PHY_CCA_MIN_GOOD_VAL_9280_5GHZ -122 +#define AR_PHY_CCA_MAX_GOOD_VAL_9280_2GHZ -97 +#define AR_PHY_CCA_MAX_GOOD_VAL_9280_5GHZ -102 + +#define AR_PHY_CCA_NOM_VAL_9285_2GHZ -118 +#define AR_PHY_CCA_MIN_GOOD_VAL_9285_2GHZ -127 +#define AR_PHY_CCA_MAX_GOOD_VAL_9285_2GHZ -108 + +#define AR_PHY_CCA_NOM_VAL_9271_2GHZ -118 +#define AR_PHY_CCA_MIN_GOOD_VAL_9271_2GHZ -127 +#define AR_PHY_CCA_MAX_GOOD_VAL_9271_2GHZ -116 + +#define AR_PHY_CCA_NOM_VAL_9287_2GHZ -120 +#define AR_PHY_CCA_MIN_GOOD_VAL_9287_2GHZ -127 +#define AR_PHY_CCA_MAX_GOOD_VAL_9287_2GHZ -110 + +#endif diff --git a/drivers/net/wireless/ath/ath9k/ar9003_2p2_initvals.h b/drivers/net/wireless/ath/ath9k/ar9003_2p2_initvals.h new file mode 100644 index 0000000..7546b9a --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9003_2p2_initvals.h @@ -0,0 +1,1741 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef INITVALS_9003_2P2_H +#define INITVALS_9003_2P2_H + +/* AR9003 2.2 */ + +static const u32 ar9300_2p2_radio_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0001609c, 0x0dd08f29, 0x0dd08f29, 0x0b283f31, 0x0b283f31}, + {0x000160ac, 0xa4653c00, 0xa4653c00, 0x24652800, 0x24652800}, + {0x000160b0, 0x03284f3e, 0x03284f3e, 0x05d08f20, 0x05d08f20}, + {0x0001610c, 0xc8000000, 0xc0000000, 0xc0000000, 0xc0000000}, + {0x00016140, 0x10804008, 0x10804008, 0x50804008, 0x50804008}, + {0x0001650c, 0xc8000000, 0xc0000000, 0xc0000000, 0xc0000000}, + {0x00016540, 0x10804008, 0x10804008, 0x50804008, 0x50804008}, + {0x0001690c, 0xc8000000, 0xc0000000, 0xc0000000, 0xc0000000}, + {0x00016940, 0x10804008, 0x10804008, 0x50804008, 0x50804008}, +}; + +static const u32 ar9300Modes_lowest_ob_db_tx_gain_table_2p2[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0x00033800, 0x00033800, 0x03aaa352, 0x03aaa352}, + {0x0000a2e0, 0x0003c000, 0x0003c000, 0x03ccc584, 0x03ccc584}, + {0x0000a2e4, 0x03fc0000, 0x03fc0000, 0x03f0f800, 0x03f0f800}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a504, 0x06000003, 0x06000003, 0x04000002, 0x04000002}, + {0x0000a508, 0x0a000020, 0x0a000020, 0x08000004, 0x08000004}, + {0x0000a50c, 0x10000023, 0x10000023, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x16000220, 0x16000220, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x1c000223, 0x1c000223, 0x12000400, 0x12000400}, + {0x0000a518, 0x21002220, 0x21002220, 0x16000402, 0x16000402}, + {0x0000a51c, 0x27002223, 0x27002223, 0x19000404, 0x19000404}, + {0x0000a520, 0x2b022220, 0x2b022220, 0x1c000603, 0x1c000603}, + {0x0000a524, 0x2f022222, 0x2f022222, 0x21000a02, 0x21000a02}, + {0x0000a528, 0x34022225, 0x34022225, 0x25000a04, 0x25000a04}, + {0x0000a52c, 0x3a02222a, 0x3a02222a, 0x28000a20, 0x28000a20}, + {0x0000a530, 0x3e02222c, 0x3e02222c, 0x2c000e20, 0x2c000e20}, + {0x0000a534, 0x4202242a, 0x4202242a, 0x30000e22, 0x30000e22}, + {0x0000a538, 0x4702244a, 0x4702244a, 0x34000e24, 0x34000e24}, + {0x0000a53c, 0x4b02244c, 0x4b02244c, 0x38001640, 0x38001640}, + {0x0000a540, 0x4e02246c, 0x4e02246c, 0x3c001660, 0x3c001660}, + {0x0000a544, 0x52022470, 0x52022470, 0x3f001861, 0x3f001861}, + {0x0000a548, 0x55022490, 0x55022490, 0x43001a81, 0x43001a81}, + {0x0000a54c, 0x59022492, 0x59022492, 0x47001a83, 0x47001a83}, + {0x0000a550, 0x5d022692, 0x5d022692, 0x4a001c84, 0x4a001c84}, + {0x0000a554, 0x61022892, 0x61022892, 0x4e001ce3, 0x4e001ce3}, + {0x0000a558, 0x65024890, 0x65024890, 0x52001ce5, 0x52001ce5}, + {0x0000a55c, 0x69024892, 0x69024892, 0x56001ce9, 0x56001ce9}, + {0x0000a560, 0x6e024c92, 0x6e024c92, 0x5a001ceb, 0x5a001ceb}, + {0x0000a564, 0x74026e92, 0x74026e92, 0x5d001eec, 0x5d001eec}, + {0x0000a568, 0x74026e92, 0x74026e92, 0x5d001eec, 0x5d001eec}, + {0x0000a56c, 0x74026e92, 0x74026e92, 0x5d001eec, 0x5d001eec}, + {0x0000a570, 0x74026e92, 0x74026e92, 0x5d001eec, 0x5d001eec}, + {0x0000a574, 0x74026e92, 0x74026e92, 0x5d001eec, 0x5d001eec}, + {0x0000a578, 0x74026e92, 0x74026e92, 0x5d001eec, 0x5d001eec}, + {0x0000a57c, 0x74026e92, 0x74026e92, 0x5d001eec, 0x5d001eec}, + {0x0000a580, 0x00800000, 0x00800000, 0x00800000, 0x00800000}, + {0x0000a584, 0x06800003, 0x06800003, 0x04800002, 0x04800002}, + {0x0000a588, 0x0a800020, 0x0a800020, 0x08800004, 0x08800004}, + {0x0000a58c, 0x10800023, 0x10800023, 0x0b800200, 0x0b800200}, + {0x0000a590, 0x16800220, 0x16800220, 0x0f800202, 0x0f800202}, + {0x0000a594, 0x1c800223, 0x1c800223, 0x12800400, 0x12800400}, + {0x0000a598, 0x21802220, 0x21802220, 0x16800402, 0x16800402}, + {0x0000a59c, 0x27802223, 0x27802223, 0x19800404, 0x19800404}, + {0x0000a5a0, 0x2b822220, 0x2b822220, 0x1c800603, 0x1c800603}, + {0x0000a5a4, 0x2f822222, 0x2f822222, 0x21800a02, 0x21800a02}, + {0x0000a5a8, 0x34822225, 0x34822225, 0x25800a04, 0x25800a04}, + {0x0000a5ac, 0x3a82222a, 0x3a82222a, 0x28800a20, 0x28800a20}, + {0x0000a5b0, 0x3e82222c, 0x3e82222c, 0x2c800e20, 0x2c800e20}, + {0x0000a5b4, 0x4282242a, 0x4282242a, 0x30800e22, 0x30800e22}, + {0x0000a5b8, 0x4782244a, 0x4782244a, 0x34800e24, 0x34800e24}, + {0x0000a5bc, 0x4b82244c, 0x4b82244c, 0x38801640, 0x38801640}, + {0x0000a5c0, 0x4e82246c, 0x4e82246c, 0x3c801660, 0x3c801660}, + {0x0000a5c4, 0x52822470, 0x52822470, 0x3f801861, 0x3f801861}, + {0x0000a5c8, 0x55822490, 0x55822490, 0x43801a81, 0x43801a81}, + {0x0000a5cc, 0x59822492, 0x59822492, 0x47801a83, 0x47801a83}, + {0x0000a5d0, 0x5d822692, 0x5d822692, 0x4a801c84, 0x4a801c84}, + {0x0000a5d4, 0x61822892, 0x61822892, 0x4e801ce3, 0x4e801ce3}, + {0x0000a5d8, 0x65824890, 0x65824890, 0x52801ce5, 0x52801ce5}, + {0x0000a5dc, 0x69824892, 0x69824892, 0x56801ce9, 0x56801ce9}, + {0x0000a5e0, 0x6e824c92, 0x6e824c92, 0x5a801ceb, 0x5a801ceb}, + {0x0000a5e4, 0x74826e92, 0x74826e92, 0x5d801eec, 0x5d801eec}, + {0x0000a5e8, 0x74826e92, 0x74826e92, 0x5d801eec, 0x5d801eec}, + {0x0000a5ec, 0x74826e92, 0x74826e92, 0x5d801eec, 0x5d801eec}, + {0x0000a5f0, 0x74826e92, 0x74826e92, 0x5d801eec, 0x5d801eec}, + {0x0000a5f4, 0x74826e92, 0x74826e92, 0x5d801eec, 0x5d801eec}, + {0x0000a5f8, 0x74826e92, 0x74826e92, 0x5d801eec, 0x5d801eec}, + {0x0000a5fc, 0x74826e92, 0x74826e92, 0x5d801eec, 0x5d801eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x02004000, 0x02004000, 0x01404000, 0x01404000}, + {0x0000a618, 0x02004801, 0x02004801, 0x01404501, 0x01404501}, + {0x0000a61c, 0x02808a02, 0x02808a02, 0x02008501, 0x02008501}, + {0x0000a620, 0x0380ce03, 0x0380ce03, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x04411104, 0x04411104, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x04411104, 0x04411104, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x04411104, 0x04411104, 0x04015005, 0x04015005}, + {0x0000a630, 0x04411104, 0x04411104, 0x04015005, 0x04015005}, + {0x0000a634, 0x04411104, 0x04411104, 0x04015005, 0x04015005}, + {0x0000a638, 0x04411104, 0x04411104, 0x04015005, 0x04015005}, + {0x0000a63c, 0x04411104, 0x04411104, 0x04015005, 0x04015005}, + {0x0000b2dc, 0x00033800, 0x00033800, 0x03aaa352, 0x03aaa352}, + {0x0000b2e0, 0x0003c000, 0x0003c000, 0x03ccc584, 0x03ccc584}, + {0x0000b2e4, 0x03fc0000, 0x03fc0000, 0x03f0f800, 0x03f0f800}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000c2dc, 0x00033800, 0x00033800, 0x03aaa352, 0x03aaa352}, + {0x0000c2e0, 0x0003c000, 0x0003c000, 0x03ccc584, 0x03ccc584}, + {0x0000c2e4, 0x03fc0000, 0x03fc0000, 0x03f0f800, 0x03f0f800}, + {0x0000c2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x00016044, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016048, 0x62480001, 0x62480001, 0x62480001, 0x62480001}, + {0x00016068, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016444, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016448, 0x62480001, 0x62480001, 0x62480001, 0x62480001}, + {0x00016468, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016844, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016848, 0x62480001, 0x62480001, 0x62480001, 0x62480001}, + {0x00016868, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, +}; + +static const u32 ar9300Modes_fast_clock_2p2[][3] = { + /* Addr 5G_HT20 5G_HT40 */ + {0x00001030, 0x00000268, 0x000004d0}, + {0x00001070, 0x0000018c, 0x00000318}, + {0x000010b0, 0x00000fd0, 0x00001fa0}, + {0x00008014, 0x044c044c, 0x08980898}, + {0x0000801c, 0x148ec02b, 0x148ec057}, + {0x00008318, 0x000044c0, 0x00008980}, + {0x00009e00, 0x0372131c, 0x0372131c}, + {0x0000a230, 0x0000000b, 0x00000016}, + {0x0000a254, 0x00000898, 0x00001130}, +}; + +static const u32 ar9300_2p2_radio_core[][2] = { + /* Addr allmodes */ + {0x00016000, 0x36db6db6}, + {0x00016004, 0x6db6db40}, + {0x00016008, 0x73f00000}, + {0x0001600c, 0x00000000}, + {0x00016040, 0x7f80fff8}, + {0x0001604c, 0x76d005b5}, + {0x00016050, 0x556cf031}, + {0x00016054, 0x13449440}, + {0x00016058, 0x0c51c92c}, + {0x0001605c, 0x3db7fffc}, + {0x00016060, 0xfffffffc}, + {0x00016064, 0x000f0278}, + {0x0001606c, 0x6db60000}, + {0x00016080, 0x00000000}, + {0x00016084, 0x0e48048c}, + {0x00016088, 0x54214514}, + {0x0001608c, 0x119f481e}, + {0x00016090, 0x24926490}, + {0x00016098, 0xd2888888}, + {0x000160a0, 0x0a108ffe}, + {0x000160a4, 0x812fc370}, + {0x000160a8, 0x423c8000}, + {0x000160b4, 0x92480080}, + {0x000160c0, 0x00adb6d0}, + {0x000160c4, 0x6db6db60}, + {0x000160c8, 0x6db6db6c}, + {0x000160cc, 0x01e6c000}, + {0x00016100, 0x3fffbe01}, + {0x00016104, 0xfff80000}, + {0x00016108, 0x00080010}, + {0x00016144, 0x02084080}, + {0x00016148, 0x00000000}, + {0x00016280, 0x058a0001}, + {0x00016284, 0x3d840208}, + {0x00016288, 0x05a20408}, + {0x0001628c, 0x00038c07}, + {0x00016290, 0x00000004}, + {0x00016294, 0x458a214f}, + {0x00016380, 0x00000000}, + {0x00016384, 0x00000000}, + {0x00016388, 0x00800700}, + {0x0001638c, 0x00800700}, + {0x00016390, 0x00800700}, + {0x00016394, 0x00000000}, + {0x00016398, 0x00000000}, + {0x0001639c, 0x00000000}, + {0x000163a0, 0x00000001}, + {0x000163a4, 0x00000001}, + {0x000163a8, 0x00000000}, + {0x000163ac, 0x00000000}, + {0x000163b0, 0x00000000}, + {0x000163b4, 0x00000000}, + {0x000163b8, 0x00000000}, + {0x000163bc, 0x00000000}, + {0x000163c0, 0x000000a0}, + {0x000163c4, 0x000c0000}, + {0x000163c8, 0x14021402}, + {0x000163cc, 0x00001402}, + {0x000163d0, 0x00000000}, + {0x000163d4, 0x00000000}, + {0x00016400, 0x36db6db6}, + {0x00016404, 0x6db6db40}, + {0x00016408, 0x73f00000}, + {0x0001640c, 0x00000000}, + {0x00016440, 0x7f80fff8}, + {0x0001644c, 0x76d005b5}, + {0x00016450, 0x556cf031}, + {0x00016454, 0x13449440}, + {0x00016458, 0x0c51c92c}, + {0x0001645c, 0x3db7fffc}, + {0x00016460, 0xfffffffc}, + {0x00016464, 0x000f0278}, + {0x0001646c, 0x6db60000}, + {0x00016500, 0x3fffbe01}, + {0x00016504, 0xfff80000}, + {0x00016508, 0x00080010}, + {0x00016544, 0x02084080}, + {0x00016548, 0x00000000}, + {0x00016780, 0x00000000}, + {0x00016784, 0x00000000}, + {0x00016788, 0x00800700}, + {0x0001678c, 0x00800700}, + {0x00016790, 0x00800700}, + {0x00016794, 0x00000000}, + {0x00016798, 0x00000000}, + {0x0001679c, 0x00000000}, + {0x000167a0, 0x00000001}, + {0x000167a4, 0x00000001}, + {0x000167a8, 0x00000000}, + {0x000167ac, 0x00000000}, + {0x000167b0, 0x00000000}, + {0x000167b4, 0x00000000}, + {0x000167b8, 0x00000000}, + {0x000167bc, 0x00000000}, + {0x000167c0, 0x000000a0}, + {0x000167c4, 0x000c0000}, + {0x000167c8, 0x14021402}, + {0x000167cc, 0x00001402}, + {0x000167d0, 0x00000000}, + {0x000167d4, 0x00000000}, + {0x00016800, 0x36db6db6}, + {0x00016804, 0x6db6db40}, + {0x00016808, 0x73f00000}, + {0x0001680c, 0x00000000}, + {0x00016840, 0x7f80fff8}, + {0x0001684c, 0x76d005b5}, + {0x00016850, 0x556cf031}, + {0x00016854, 0x13449440}, + {0x00016858, 0x0c51c92c}, + {0x0001685c, 0x3db7fffc}, + {0x00016860, 0xfffffffc}, + {0x00016864, 0x000f0278}, + {0x0001686c, 0x6db60000}, + {0x00016900, 0x3fffbe01}, + {0x00016904, 0xfff80000}, + {0x00016908, 0x00080010}, + {0x00016944, 0x02084080}, + {0x00016948, 0x00000000}, + {0x00016b80, 0x00000000}, + {0x00016b84, 0x00000000}, + {0x00016b88, 0x00800700}, + {0x00016b8c, 0x00800700}, + {0x00016b90, 0x00800700}, + {0x00016b94, 0x00000000}, + {0x00016b98, 0x00000000}, + {0x00016b9c, 0x00000000}, + {0x00016ba0, 0x00000001}, + {0x00016ba4, 0x00000001}, + {0x00016ba8, 0x00000000}, + {0x00016bac, 0x00000000}, + {0x00016bb0, 0x00000000}, + {0x00016bb4, 0x00000000}, + {0x00016bb8, 0x00000000}, + {0x00016bbc, 0x00000000}, + {0x00016bc0, 0x000000a0}, + {0x00016bc4, 0x000c0000}, + {0x00016bc8, 0x14021402}, + {0x00016bcc, 0x00001402}, + {0x00016bd0, 0x00000000}, + {0x00016bd4, 0x00000000}, +}; + +static const u32 ar9300_2p2_mac_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00001030, 0x00000230, 0x00000460, 0x000002c0, 0x00000160}, + {0x00001070, 0x00000168, 0x000002d0, 0x00000318, 0x0000018c}, + {0x000010b0, 0x00000e60, 0x00001cc0, 0x00007c70, 0x00003e38}, + {0x00008014, 0x03e803e8, 0x07d007d0, 0x10801600, 0x08400b00}, + {0x0000801c, 0x128d8027, 0x128d804f, 0x12e00057, 0x12e0002b}, + {0x00008120, 0x08f04800, 0x08f04800, 0x08f04810, 0x08f04810}, + {0x000081d0, 0x00003210, 0x00003210, 0x0000320a, 0x0000320a}, + {0x00008318, 0x00003e80, 0x00007d00, 0x00006880, 0x00003440}, +}; + +static const u32 ar9300_2p2_soc_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00007010, 0x00000023, 0x00000023, 0x00000023, 0x00000023}, +}; + +static const u32 ar9300_2p2_baseband_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009810, 0xd00a8005, 0xd00a8005, 0xd00a8011, 0xd00a8011}, + {0x00009820, 0x206a022e, 0x206a022e, 0x206a012e, 0x206a012e}, + {0x00009824, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0}, + {0x00009828, 0x06903081, 0x06903081, 0x06903881, 0x06903881}, + {0x0000982c, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4}, + {0x00009830, 0x0000059c, 0x0000059c, 0x0000119c, 0x0000119c}, + {0x00009c00, 0x000000c4, 0x000000c4, 0x000000c4, 0x000000c4}, + {0x00009e00, 0x0372111a, 0x0372111a, 0x037216a0, 0x037216a0}, + {0x00009e04, 0x001c2020, 0x001c2020, 0x001c2020, 0x001c2020}, + {0x00009e0c, 0x6c4000e2, 0x6d4000e2, 0x6d4000e2, 0x6c4000e2}, + {0x00009e10, 0x7ec88d2e, 0x7ec88d2e, 0x7ec84d2e, 0x7ec84d2e}, + {0x00009e14, 0x37b95d5e, 0x37b9605e, 0x3379605e, 0x33795d5e}, + {0x00009e18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00009e1c, 0x0001cf9c, 0x0001cf9c, 0x00021f9c, 0x00021f9c}, + {0x00009e20, 0x000003b5, 0x000003b5, 0x000003ce, 0x000003ce}, + {0x00009e2c, 0x0000001c, 0x0000001c, 0x00000021, 0x00000021}, + {0x00009e3c, 0xcf946220, 0xcf946220, 0xcf946222, 0xcf946222}, + {0x00009e44, 0x02321e27, 0x02321e27, 0x02291e27, 0x02291e27}, + {0x00009e48, 0x5030201a, 0x5030201a, 0x50302012, 0x50302012}, + {0x00009fc8, 0x0003f000, 0x0003f000, 0x0001a000, 0x0001a000}, + {0x0000a204, 0x000036c0, 0x000036c4, 0x000036c4, 0x000036c0}, + {0x0000a208, 0x00000104, 0x00000104, 0x00000004, 0x00000004}, + {0x0000a22c, 0x01026a2f, 0x01026a2f, 0x01026a2f, 0x01026a2f}, + {0x0000a230, 0x0000000a, 0x00000014, 0x00000016, 0x0000000b}, + {0x0000a234, 0x00000fff, 0x10000fff, 0x10000fff, 0x00000fff}, + {0x0000a238, 0xffb81018, 0xffb81018, 0xffb81018, 0xffb81018}, + {0x0000a250, 0x00000000, 0x00000000, 0x00000210, 0x00000108}, + {0x0000a254, 0x000007d0, 0x00000fa0, 0x00001130, 0x00000898}, + {0x0000a258, 0x02020002, 0x02020002, 0x02020002, 0x02020002}, + {0x0000a25c, 0x01000e0e, 0x01000e0e, 0x01000e0e, 0x01000e0e}, + {0x0000a260, 0x0a021501, 0x0a021501, 0x3a021501, 0x3a021501}, + {0x0000a264, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x0000a280, 0x00000007, 0x00000007, 0x0000000b, 0x0000000b}, + {0x0000a284, 0x00000000, 0x00000000, 0x00000150, 0x00000150}, + {0x0000a288, 0x00000110, 0x00000110, 0x00000110, 0x00000110}, + {0x0000a28c, 0x00022222, 0x00022222, 0x00022222, 0x00022222}, + {0x0000a2c4, 0x00158d18, 0x00158d18, 0x00158d18, 0x00158d18}, + {0x0000a2d0, 0x00041981, 0x00041981, 0x00041981, 0x00041982}, + {0x0000a2d8, 0x7999a83b, 0x7999a83b, 0x7999a83b, 0x7999a83b}, + {0x0000a358, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a830, 0x0000019c, 0x0000019c, 0x0000019c, 0x0000019c}, + {0x0000ae04, 0x001c0000, 0x001c0000, 0x001c0000, 0x001c0000}, + {0x0000ae18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000ae1c, 0x0000019c, 0x0000019c, 0x0000019c, 0x0000019c}, + {0x0000ae20, 0x000001b5, 0x000001b5, 0x000001ce, 0x000001ce}, + {0x0000b284, 0x00000000, 0x00000000, 0x00000150, 0x00000150}, + {0x0000b830, 0x0000019c, 0x0000019c, 0x0000019c, 0x0000019c}, + {0x0000be04, 0x001c0000, 0x001c0000, 0x001c0000, 0x001c0000}, + {0x0000be18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000be1c, 0x0000019c, 0x0000019c, 0x0000019c, 0x0000019c}, + {0x0000be20, 0x000001b5, 0x000001b5, 0x000001ce, 0x000001ce}, + {0x0000c284, 0x00000000, 0x00000000, 0x00000150, 0x00000150}, +}; + +static const u32 ar9300_2p2_baseband_core[][2] = { + /* Addr allmodes */ + {0x00009800, 0xafe68e30}, + {0x00009804, 0xfd14e000}, + {0x00009808, 0x9c0a9f6b}, + {0x0000980c, 0x04900000}, + {0x00009814, 0x9280c00a}, + {0x00009818, 0x00000000}, + {0x0000981c, 0x00020028}, + {0x00009834, 0x6400a290}, + {0x00009838, 0x0108ecff}, + {0x0000983c, 0x0d000600}, + {0x00009880, 0x201fff00}, + {0x00009884, 0x00001042}, + {0x000098a4, 0x00200400}, + {0x000098b0, 0x32840bbe}, + {0x000098d0, 0x004b6a8e}, + {0x000098d4, 0x00000820}, + {0x000098dc, 0x00000000}, + {0x000098f0, 0x00000000}, + {0x000098f4, 0x00000000}, + {0x00009c04, 0xff55ff55}, + {0x00009c08, 0x0320ff55}, + {0x00009c0c, 0x00000000}, + {0x00009c10, 0x00000000}, + {0x00009c14, 0x00046384}, + {0x00009c18, 0x05b6b440}, + {0x00009c1c, 0x00b6b440}, + {0x00009d00, 0xc080a333}, + {0x00009d04, 0x40206c10}, + {0x00009d08, 0x009c4060}, + {0x00009d0c, 0x9883800a}, + {0x00009d10, 0x01834061}, + {0x00009d14, 0x00c0040b}, + {0x00009d18, 0x00000000}, + {0x00009e08, 0x0038230c}, + {0x00009e24, 0x990bb515}, + {0x00009e28, 0x0c6f0000}, + {0x00009e30, 0x06336f77}, + {0x00009e34, 0x6af6532f}, + {0x00009e38, 0x0cc80c00}, + {0x00009e40, 0x0d261820}, + {0x00009e4c, 0x00001004}, + {0x00009e50, 0x00ff03f1}, + {0x00009e54, 0x00000000}, + {0x00009fc0, 0x803e4788}, + {0x00009fc4, 0x0001efb5}, + {0x00009fcc, 0x40000014}, + {0x00009fd0, 0x01193b93}, + {0x0000a20c, 0x00000000}, + {0x0000a220, 0x00000000}, + {0x0000a224, 0x00000000}, + {0x0000a228, 0x10002310}, + {0x0000a23c, 0x00000000}, + {0x0000a244, 0x0c000000}, + {0x0000a2a0, 0x00000001}, + {0x0000a2c0, 0x00000001}, + {0x0000a2c8, 0x00000000}, + {0x0000a2cc, 0x18c43433}, + {0x0000a2d4, 0x00000000}, + {0x0000a2ec, 0x00000000}, + {0x0000a2f0, 0x00000000}, + {0x0000a2f4, 0x00000000}, + {0x0000a2f8, 0x00000000}, + {0x0000a344, 0x00000000}, + {0x0000a34c, 0x00000000}, + {0x0000a350, 0x0000a000}, + {0x0000a364, 0x00000000}, + {0x0000a370, 0x00000000}, + {0x0000a390, 0x00000001}, + {0x0000a394, 0x00000444}, + {0x0000a398, 0x001f0e0f}, + {0x0000a39c, 0x0075393f}, + {0x0000a3a0, 0xb79f6427}, + {0x0000a3a4, 0x00000000}, + {0x0000a3a8, 0xaaaaaaaa}, + {0x0000a3ac, 0x3c466478}, + {0x0000a3c0, 0x20202020}, + {0x0000a3c4, 0x22222220}, + {0x0000a3c8, 0x20200020}, + {0x0000a3cc, 0x20202020}, + {0x0000a3d0, 0x20202020}, + {0x0000a3d4, 0x20202020}, + {0x0000a3d8, 0x20202020}, + {0x0000a3dc, 0x20202020}, + {0x0000a3e0, 0x20202020}, + {0x0000a3e4, 0x20202020}, + {0x0000a3e8, 0x20202020}, + {0x0000a3ec, 0x20202020}, + {0x0000a3f0, 0x00000000}, + {0x0000a3f4, 0x00000246}, + {0x0000a3f8, 0x0c9bd380}, + {0x0000a3fc, 0x000f0f01}, + {0x0000a400, 0x8fa91f01}, + {0x0000a404, 0x00000000}, + {0x0000a408, 0x0e79e5c6}, + {0x0000a40c, 0x00820820}, + {0x0000a414, 0x1ce739ce}, + {0x0000a418, 0x2d001dce}, + {0x0000a41c, 0x1ce739ce}, + {0x0000a420, 0x000001ce}, + {0x0000a424, 0x1ce739ce}, + {0x0000a428, 0x000001ce}, + {0x0000a42c, 0x1ce739ce}, + {0x0000a430, 0x1ce739ce}, + {0x0000a434, 0x00000000}, + {0x0000a438, 0x00001801}, + {0x0000a43c, 0x00100000}, + {0x0000a440, 0x00000000}, + {0x0000a444, 0x00000000}, + {0x0000a448, 0x05000080}, + {0x0000a44c, 0x00000001}, + {0x0000a450, 0x00010000}, + {0x0000a458, 0x00000000}, + {0x0000a640, 0x00000000}, + {0x0000a644, 0x3fad9d74}, + {0x0000a648, 0x0048060a}, + {0x0000a64c, 0x00003c37}, + {0x0000a670, 0x03020100}, + {0x0000a674, 0x09080504}, + {0x0000a678, 0x0d0c0b0a}, + {0x0000a67c, 0x13121110}, + {0x0000a680, 0x31301514}, + {0x0000a684, 0x35343332}, + {0x0000a688, 0x00000036}, + {0x0000a690, 0x00000838}, + {0x0000a7c0, 0x00000000}, + {0x0000a7c4, 0xfffffffc}, + {0x0000a7c8, 0x00000000}, + {0x0000a7cc, 0x00000000}, + {0x0000a7d0, 0x00000000}, + {0x0000a7d4, 0x00000004}, + {0x0000a7dc, 0x00000001}, + {0x0000a8d0, 0x004b6a8e}, + {0x0000a8d4, 0x00000820}, + {0x0000a8dc, 0x00000000}, + {0x0000a8f0, 0x00000000}, + {0x0000a8f4, 0x00000000}, + {0x0000b2d0, 0x00000080}, + {0x0000b2d4, 0x00000000}, + {0x0000b2ec, 0x00000000}, + {0x0000b2f0, 0x00000000}, + {0x0000b2f4, 0x00000000}, + {0x0000b2f8, 0x00000000}, + {0x0000b408, 0x0e79e5c0}, + {0x0000b40c, 0x00820820}, + {0x0000b420, 0x00000000}, + {0x0000b8d0, 0x004b6a8e}, + {0x0000b8d4, 0x00000820}, + {0x0000b8dc, 0x00000000}, + {0x0000b8f0, 0x00000000}, + {0x0000b8f4, 0x00000000}, + {0x0000c2d0, 0x00000080}, + {0x0000c2d4, 0x00000000}, + {0x0000c2ec, 0x00000000}, + {0x0000c2f0, 0x00000000}, + {0x0000c2f4, 0x00000000}, + {0x0000c2f8, 0x00000000}, + {0x0000c408, 0x0e79e5c0}, + {0x0000c40c, 0x00820820}, + {0x0000c420, 0x00000000}, +}; + +static const u32 ar9300Modes_high_power_tx_gain_table_2p2[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000a2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000a2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a410, 0x000050d8, 0x000050d8, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00002220, 0x00002220, 0x00000000, 0x00000000}, + {0x0000a504, 0x04002222, 0x04002222, 0x04000002, 0x04000002}, + {0x0000a508, 0x09002421, 0x09002421, 0x08000004, 0x08000004}, + {0x0000a50c, 0x0d002621, 0x0d002621, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x13004620, 0x13004620, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x19004a20, 0x19004a20, 0x11000400, 0x11000400}, + {0x0000a518, 0x1d004e20, 0x1d004e20, 0x15000402, 0x15000402}, + {0x0000a51c, 0x21005420, 0x21005420, 0x19000404, 0x19000404}, + {0x0000a520, 0x26005e20, 0x26005e20, 0x1b000603, 0x1b000603}, + {0x0000a524, 0x2b005e40, 0x2b005e40, 0x1f000a02, 0x1f000a02}, + {0x0000a528, 0x2f005e42, 0x2f005e42, 0x23000a04, 0x23000a04}, + {0x0000a52c, 0x33005e44, 0x33005e44, 0x26000a20, 0x26000a20}, + {0x0000a530, 0x38005e65, 0x38005e65, 0x2a000e20, 0x2a000e20}, + {0x0000a534, 0x3c005e69, 0x3c005e69, 0x2e000e22, 0x2e000e22}, + {0x0000a538, 0x40005e6b, 0x40005e6b, 0x31000e24, 0x31000e24}, + {0x0000a53c, 0x44005e6d, 0x44005e6d, 0x34001640, 0x34001640}, + {0x0000a540, 0x49005e72, 0x49005e72, 0x38001660, 0x38001660}, + {0x0000a544, 0x4e005eb2, 0x4e005eb2, 0x3b001861, 0x3b001861}, + {0x0000a548, 0x53005f12, 0x53005f12, 0x3e001a81, 0x3e001a81}, + {0x0000a54c, 0x59025eb2, 0x59025eb2, 0x42001a83, 0x42001a83}, + {0x0000a550, 0x5e025f12, 0x5e025f12, 0x44001c84, 0x44001c84}, + {0x0000a554, 0x61027f12, 0x61027f12, 0x48001ce3, 0x48001ce3}, + {0x0000a558, 0x6702bf12, 0x6702bf12, 0x4c001ce5, 0x4c001ce5}, + {0x0000a55c, 0x6b02bf14, 0x6b02bf14, 0x50001ce9, 0x50001ce9}, + {0x0000a560, 0x6f02bf16, 0x6f02bf16, 0x54001ceb, 0x54001ceb}, + {0x0000a564, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a568, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a56c, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a570, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a574, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a578, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a57c, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a580, 0x00802220, 0x00802220, 0x00800000, 0x00800000}, + {0x0000a584, 0x04802222, 0x04802222, 0x04800002, 0x04800002}, + {0x0000a588, 0x09802421, 0x09802421, 0x08800004, 0x08800004}, + {0x0000a58c, 0x0d802621, 0x0d802621, 0x0b800200, 0x0b800200}, + {0x0000a590, 0x13804620, 0x13804620, 0x0f800202, 0x0f800202}, + {0x0000a594, 0x19804a20, 0x19804a20, 0x11800400, 0x11800400}, + {0x0000a598, 0x1d804e20, 0x1d804e20, 0x15800402, 0x15800402}, + {0x0000a59c, 0x21805420, 0x21805420, 0x19800404, 0x19800404}, + {0x0000a5a0, 0x26805e20, 0x26805e20, 0x1b800603, 0x1b800603}, + {0x0000a5a4, 0x2b805e40, 0x2b805e40, 0x1f800a02, 0x1f800a02}, + {0x0000a5a8, 0x2f805e42, 0x2f805e42, 0x23800a04, 0x23800a04}, + {0x0000a5ac, 0x33805e44, 0x33805e44, 0x26800a20, 0x26800a20}, + {0x0000a5b0, 0x38805e65, 0x38805e65, 0x2a800e20, 0x2a800e20}, + {0x0000a5b4, 0x3c805e69, 0x3c805e69, 0x2e800e22, 0x2e800e22}, + {0x0000a5b8, 0x40805e6b, 0x40805e6b, 0x31800e24, 0x31800e24}, + {0x0000a5bc, 0x44805e6d, 0x44805e6d, 0x34801640, 0x34801640}, + {0x0000a5c0, 0x49805e72, 0x49805e72, 0x38801660, 0x38801660}, + {0x0000a5c4, 0x4e805eb2, 0x4e805eb2, 0x3b801861, 0x3b801861}, + {0x0000a5c8, 0x53805f12, 0x53805f12, 0x3e801a81, 0x3e801a81}, + {0x0000a5cc, 0x59825eb2, 0x59825eb2, 0x42801a83, 0x42801a83}, + {0x0000a5d0, 0x5e825f12, 0x5e825f12, 0x44801c84, 0x44801c84}, + {0x0000a5d4, 0x61827f12, 0x61827f12, 0x48801ce3, 0x48801ce3}, + {0x0000a5d8, 0x6782bf12, 0x6782bf12, 0x4c801ce5, 0x4c801ce5}, + {0x0000a5dc, 0x6b82bf14, 0x6b82bf14, 0x50801ce9, 0x50801ce9}, + {0x0000a5e0, 0x6f82bf16, 0x6f82bf16, 0x54801ceb, 0x54801ceb}, + {0x0000a5e4, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5e8, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5ec, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5f0, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5f4, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5f8, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5fc, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00804000, 0x00804000, 0x00000000, 0x00000000}, + {0x0000a614, 0x00804201, 0x00804201, 0x01404000, 0x01404000}, + {0x0000a618, 0x0280c802, 0x0280c802, 0x01404501, 0x01404501}, + {0x0000a61c, 0x0280ca03, 0x0280ca03, 0x02008501, 0x02008501}, + {0x0000a620, 0x04c15104, 0x04c15104, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x04c15305, 0x04c15305, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x04c15305, 0x04c15305, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a630, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a634, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a638, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a63c, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000b2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000b2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000b2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000c2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000c2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000c2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000c2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x00016044, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016048, 0x66480001, 0x66480001, 0x66480001, 0x66480001}, + {0x00016068, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016444, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016448, 0x66480001, 0x66480001, 0x66480001, 0x66480001}, + {0x00016468, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016844, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016848, 0x66480001, 0x66480001, 0x66480001, 0x66480001}, + {0x00016868, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, +}; + +static const u32 ar9300Modes_high_ob_db_tx_gain_table_2p2[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0x01feee00, 0x01feee00, 0x03aaa352, 0x03aaa352}, + {0x0000a2e0, 0x0000f000, 0x0000f000, 0x03ccc584, 0x03ccc584}, + {0x0000a2e4, 0x01ff0000, 0x01ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a410, 0x000050d8, 0x000050d8, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00002220, 0x00002220, 0x00000000, 0x00000000}, + {0x0000a504, 0x04002222, 0x04002222, 0x04000002, 0x04000002}, + {0x0000a508, 0x09002421, 0x09002421, 0x08000004, 0x08000004}, + {0x0000a50c, 0x0d002621, 0x0d002621, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x13004620, 0x13004620, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x19004a20, 0x19004a20, 0x11000400, 0x11000400}, + {0x0000a518, 0x1d004e20, 0x1d004e20, 0x15000402, 0x15000402}, + {0x0000a51c, 0x21005420, 0x21005420, 0x19000404, 0x19000404}, + {0x0000a520, 0x26005e20, 0x26005e20, 0x1b000603, 0x1b000603}, + {0x0000a524, 0x2b005e40, 0x2b005e40, 0x1f000a02, 0x1f000a02}, + {0x0000a528, 0x2f005e42, 0x2f005e42, 0x23000a04, 0x23000a04}, + {0x0000a52c, 0x33005e44, 0x33005e44, 0x26000a20, 0x26000a20}, + {0x0000a530, 0x38005e65, 0x38005e65, 0x2a000e20, 0x2a000e20}, + {0x0000a534, 0x3c005e69, 0x3c005e69, 0x2e000e22, 0x2e000e22}, + {0x0000a538, 0x40005e6b, 0x40005e6b, 0x31000e24, 0x31000e24}, + {0x0000a53c, 0x44005e6d, 0x44005e6d, 0x34001640, 0x34001640}, + {0x0000a540, 0x49005e72, 0x49005e72, 0x38001660, 0x38001660}, + {0x0000a544, 0x4e005eb2, 0x4e005eb2, 0x3b001861, 0x3b001861}, + {0x0000a548, 0x53005f12, 0x53005f12, 0x3e001a81, 0x3e001a81}, + {0x0000a54c, 0x59025eb2, 0x59025eb2, 0x42001a83, 0x42001a83}, + {0x0000a550, 0x5e025f12, 0x5e025f12, 0x44001c84, 0x44001c84}, + {0x0000a554, 0x61027f12, 0x61027f12, 0x48001ce3, 0x48001ce3}, + {0x0000a558, 0x6702bf12, 0x6702bf12, 0x4c001ce5, 0x4c001ce5}, + {0x0000a55c, 0x6b02bf14, 0x6b02bf14, 0x50001ce9, 0x50001ce9}, + {0x0000a560, 0x6f02bf16, 0x6f02bf16, 0x54001ceb, 0x54001ceb}, + {0x0000a564, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a568, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a56c, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a570, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a574, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a578, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a57c, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a580, 0x00802220, 0x00802220, 0x00800000, 0x00800000}, + {0x0000a584, 0x04802222, 0x04802222, 0x04800002, 0x04800002}, + {0x0000a588, 0x09802421, 0x09802421, 0x08800004, 0x08800004}, + {0x0000a58c, 0x0d802621, 0x0d802621, 0x0b800200, 0x0b800200}, + {0x0000a590, 0x13804620, 0x13804620, 0x0f800202, 0x0f800202}, + {0x0000a594, 0x19804a20, 0x19804a20, 0x11800400, 0x11800400}, + {0x0000a598, 0x1d804e20, 0x1d804e20, 0x15800402, 0x15800402}, + {0x0000a59c, 0x21805420, 0x21805420, 0x19800404, 0x19800404}, + {0x0000a5a0, 0x26805e20, 0x26805e20, 0x1b800603, 0x1b800603}, + {0x0000a5a4, 0x2b805e40, 0x2b805e40, 0x1f800a02, 0x1f800a02}, + {0x0000a5a8, 0x2f805e42, 0x2f805e42, 0x23800a04, 0x23800a04}, + {0x0000a5ac, 0x33805e44, 0x33805e44, 0x26800a20, 0x26800a20}, + {0x0000a5b0, 0x38805e65, 0x38805e65, 0x2a800e20, 0x2a800e20}, + {0x0000a5b4, 0x3c805e69, 0x3c805e69, 0x2e800e22, 0x2e800e22}, + {0x0000a5b8, 0x40805e6b, 0x40805e6b, 0x31800e24, 0x31800e24}, + {0x0000a5bc, 0x44805e6d, 0x44805e6d, 0x34801640, 0x34801640}, + {0x0000a5c0, 0x49805e72, 0x49805e72, 0x38801660, 0x38801660}, + {0x0000a5c4, 0x4e805eb2, 0x4e805eb2, 0x3b801861, 0x3b801861}, + {0x0000a5c8, 0x53805f12, 0x53805f12, 0x3e801a81, 0x3e801a81}, + {0x0000a5cc, 0x59825eb2, 0x59825eb2, 0x42801a83, 0x42801a83}, + {0x0000a5d0, 0x5e825f12, 0x5e825f12, 0x44801c84, 0x44801c84}, + {0x0000a5d4, 0x61827f12, 0x61827f12, 0x48801ce3, 0x48801ce3}, + {0x0000a5d8, 0x6782bf12, 0x6782bf12, 0x4c801ce5, 0x4c801ce5}, + {0x0000a5dc, 0x6b82bf14, 0x6b82bf14, 0x50801ce9, 0x50801ce9}, + {0x0000a5e0, 0x6f82bf16, 0x6f82bf16, 0x54801ceb, 0x54801ceb}, + {0x0000a5e4, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5e8, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5ec, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5f0, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5f4, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5f8, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5fc, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00804000, 0x00804000, 0x00000000, 0x00000000}, + {0x0000a614, 0x00804201, 0x00804201, 0x01404000, 0x01404000}, + {0x0000a618, 0x0280c802, 0x0280c802, 0x01404501, 0x01404501}, + {0x0000a61c, 0x0280ca03, 0x0280ca03, 0x02008501, 0x02008501}, + {0x0000a620, 0x04c15104, 0x04c15104, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x04c15305, 0x04c15305, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x04c15305, 0x04c15305, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a630, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a634, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a638, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a63c, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000b2dc, 0x01feee00, 0x01feee00, 0x03aaa352, 0x03aaa352}, + {0x0000b2e0, 0x0000f000, 0x0000f000, 0x03ccc584, 0x03ccc584}, + {0x0000b2e4, 0x01ff0000, 0x01ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000c2dc, 0x01feee00, 0x01feee00, 0x03aaa352, 0x03aaa352}, + {0x0000c2e0, 0x0000f000, 0x0000f000, 0x03ccc584, 0x03ccc584}, + {0x0000c2e4, 0x01ff0000, 0x01ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000c2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x00016044, 0x056db2e4, 0x056db2e4, 0x056db2e4, 0x056db2e4}, + {0x00016048, 0x8e480001, 0x8e480001, 0x8e480001, 0x8e480001}, + {0x00016068, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016444, 0x056db2e4, 0x056db2e4, 0x056db2e4, 0x056db2e4}, + {0x00016448, 0x8e480001, 0x8e480001, 0x8e480001, 0x8e480001}, + {0x00016468, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016844, 0x056db2e4, 0x056db2e4, 0x056db2e4, 0x056db2e4}, + {0x00016848, 0x8e480001, 0x8e480001, 0x8e480001, 0x8e480001}, + {0x00016868, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, +}; + +static const u32 ar9300Modes_mixed_ob_db_tx_gain_table_2p2[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0x00033800, 0x00033800, 0x03aaa352, 0x03aaa352}, + {0x0000a2e0, 0x0003c000, 0x0003c000, 0x03ccc584, 0x03ccc584}, + {0x0000a2e4, 0x03fc0000, 0x03fc0000, 0x03f0f800, 0x03f0f800}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a504, 0x06000003, 0x06000003, 0x04000002, 0x04000002}, + {0x0000a508, 0x0a000020, 0x0a000020, 0x08000004, 0x08000004}, + {0x0000a50c, 0x10000023, 0x10000023, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x16000220, 0x16000220, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x1c000223, 0x1c000223, 0x11000400, 0x11000400}, + {0x0000a518, 0x21002220, 0x21002220, 0x15000402, 0x15000402}, + {0x0000a51c, 0x27002223, 0x27002223, 0x19000404, 0x19000404}, + {0x0000a520, 0x2b022220, 0x2b022220, 0x1b000603, 0x1b000603}, + {0x0000a524, 0x2f022222, 0x2f022222, 0x1f000a02, 0x1f000a02}, + {0x0000a528, 0x34022225, 0x34022225, 0x23000a04, 0x23000a04}, + {0x0000a52c, 0x3a02222a, 0x3a02222a, 0x26000a20, 0x26000a20}, + {0x0000a530, 0x3e02222c, 0x3e02222c, 0x2a000e20, 0x2a000e20}, + {0x0000a534, 0x4202242a, 0x4202242a, 0x2e000e22, 0x2e000e22}, + {0x0000a538, 0x4702244a, 0x4702244a, 0x31000e24, 0x31000e24}, + {0x0000a53c, 0x4b02244c, 0x4b02244c, 0x34001640, 0x34001640}, + {0x0000a540, 0x4e02246c, 0x4e02246c, 0x38001660, 0x38001660}, + {0x0000a544, 0x52022470, 0x52022470, 0x3b001861, 0x3b001861}, + {0x0000a548, 0x55022490, 0x55022490, 0x3e001a81, 0x3e001a81}, + {0x0000a54c, 0x59022492, 0x59022492, 0x42001a83, 0x42001a83}, + {0x0000a550, 0x5d022692, 0x5d022692, 0x44001c84, 0x44001c84}, + {0x0000a554, 0x61022892, 0x61022892, 0x48001ce3, 0x48001ce3}, + {0x0000a558, 0x65024890, 0x65024890, 0x4c001ce5, 0x4c001ce5}, + {0x0000a55c, 0x69024892, 0x69024892, 0x50001ce9, 0x50001ce9}, + {0x0000a560, 0x6e024c92, 0x6e024c92, 0x54001ceb, 0x54001ceb}, + {0x0000a564, 0x74026e92, 0x74026e92, 0x56001eec, 0x56001eec}, + {0x0000a568, 0x74026e92, 0x74026e92, 0x56001eec, 0x56001eec}, + {0x0000a56c, 0x74026e92, 0x74026e92, 0x56001eec, 0x56001eec}, + {0x0000a570, 0x74026e92, 0x74026e92, 0x56001eec, 0x56001eec}, + {0x0000a574, 0x74026e92, 0x74026e92, 0x56001eec, 0x56001eec}, + {0x0000a578, 0x74026e92, 0x74026e92, 0x56001eec, 0x56001eec}, + {0x0000a57c, 0x74026e92, 0x74026e92, 0x56001eec, 0x56001eec}, + {0x0000a580, 0x00800000, 0x00800000, 0x00800000, 0x00800000}, + {0x0000a584, 0x06800003, 0x06800003, 0x04800002, 0x04800002}, + {0x0000a588, 0x0a800020, 0x0a800020, 0x08800004, 0x08800004}, + {0x0000a58c, 0x10800023, 0x10800023, 0x0b800200, 0x0b800200}, + {0x0000a590, 0x16800220, 0x16800220, 0x0f800202, 0x0f800202}, + {0x0000a594, 0x1c800223, 0x1c800223, 0x11800400, 0x11800400}, + {0x0000a598, 0x21802220, 0x21802220, 0x15800402, 0x15800402}, + {0x0000a59c, 0x27802223, 0x27802223, 0x19800404, 0x19800404}, + {0x0000a5a0, 0x2b822220, 0x2b822220, 0x1b800603, 0x1b800603}, + {0x0000a5a4, 0x2f822222, 0x2f822222, 0x1f800a02, 0x1f800a02}, + {0x0000a5a8, 0x34822225, 0x34822225, 0x23800a04, 0x23800a04}, + {0x0000a5ac, 0x3a82222a, 0x3a82222a, 0x26800a20, 0x26800a20}, + {0x0000a5b0, 0x3e82222c, 0x3e82222c, 0x2a800e20, 0x2a800e20}, + {0x0000a5b4, 0x4282242a, 0x4282242a, 0x2e800e22, 0x2e800e22}, + {0x0000a5b8, 0x4782244a, 0x4782244a, 0x31800e24, 0x31800e24}, + {0x0000a5bc, 0x4b82244c, 0x4b82244c, 0x34801640, 0x34801640}, + {0x0000a5c0, 0x4e82246c, 0x4e82246c, 0x38801660, 0x38801660}, + {0x0000a5c4, 0x52822470, 0x52822470, 0x3b801861, 0x3b801861}, + {0x0000a5c8, 0x55822490, 0x55822490, 0x3e801a81, 0x3e801a81}, + {0x0000a5cc, 0x59822492, 0x59822492, 0x42801a83, 0x42801a83}, + {0x0000a5d0, 0x5d822692, 0x5d822692, 0x44801c84, 0x44801c84}, + {0x0000a5d4, 0x61822892, 0x61822892, 0x48801ce3, 0x48801ce3}, + {0x0000a5d8, 0x65824890, 0x65824890, 0x4c801ce5, 0x4c801ce5}, + {0x0000a5dc, 0x69824892, 0x69824892, 0x50801ce9, 0x50801ce9}, + {0x0000a5e0, 0x6e824c92, 0x6e824c92, 0x54801ceb, 0x54801ceb}, + {0x0000a5e4, 0x74826e92, 0x74826e92, 0x56801eec, 0x56801eec}, + {0x0000a5e8, 0x74826e92, 0x74826e92, 0x56801eec, 0x56801eec}, + {0x0000a5ec, 0x74826e92, 0x74826e92, 0x56801eec, 0x56801eec}, + {0x0000a5f0, 0x74826e92, 0x74826e92, 0x56801eec, 0x56801eec}, + {0x0000a5f4, 0x74826e92, 0x74826e92, 0x56801eec, 0x56801eec}, + {0x0000a5f8, 0x74826e92, 0x74826e92, 0x56801eec, 0x56801eec}, + {0x0000a5fc, 0x74826e92, 0x74826e92, 0x56801eec, 0x56801eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x02004000, 0x02004000, 0x01404000, 0x01404000}, + {0x0000a618, 0x02004801, 0x02004801, 0x01404501, 0x01404501}, + {0x0000a61c, 0x02808a02, 0x02808a02, 0x02008501, 0x02008501}, + {0x0000a620, 0x0380ce03, 0x0380ce03, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x04411104, 0x04411104, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x04411104, 0x04411104, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x04411104, 0x04411104, 0x04015005, 0x04015005}, + {0x0000a630, 0x04411104, 0x04411104, 0x04015005, 0x04015005}, + {0x0000a634, 0x04411104, 0x04411104, 0x04015005, 0x04015005}, + {0x0000a638, 0x04411104, 0x04411104, 0x04015005, 0x04015005}, + {0x0000a63c, 0x04411104, 0x04411104, 0x04015005, 0x04015005}, + {0x0000b2dc, 0x00033800, 0x00033800, 0x03aaa352, 0x03aaa352}, + {0x0000b2e0, 0x0003c000, 0x0003c000, 0x03ccc584, 0x03ccc584}, + {0x0000b2e4, 0x03fc0000, 0x03fc0000, 0x03f0f800, 0x03f0f800}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000c2dc, 0x00033800, 0x00033800, 0x03aaa352, 0x03aaa352}, + {0x0000c2e0, 0x0003c000, 0x0003c000, 0x03ccc584, 0x03ccc584}, + {0x0000c2e4, 0x03fc0000, 0x03fc0000, 0x03f0f800, 0x03f0f800}, + {0x0000c2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x00016044, 0x012492d4, 0x012492d4, 0x056db2e4, 0x056db2e4}, + {0x00016048, 0x66480001, 0x66480001, 0x8e480001, 0x8e480001}, + {0x00016068, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016444, 0x012492d4, 0x012492d4, 0x056db2e4, 0x056db2e4}, + {0x00016448, 0x66480001, 0x66480001, 0x8e480001, 0x8e480001}, + {0x00016468, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016844, 0x012492d4, 0x012492d4, 0x056db2e4, 0x056db2e4}, + {0x00016848, 0x66480001, 0x66480001, 0x8e480001, 0x8e480001}, + {0x00016868, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, +}; + +static const u32 ar9300Modes_type5_tx_gain_table_2p2[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0x000cfff0, 0x000cfff0, 0x03aaa352, 0x03aaa352}, + {0x0000a2e0, 0x000f0000, 0x000f0000, 0x03ccc584, 0x03ccc584}, + {0x0000a2e4, 0x03f00000, 0x03f00000, 0x03f0f800, 0x03f0f800}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a504, 0x06000003, 0x06000003, 0x04000002, 0x04000002}, + {0x0000a508, 0x0a000020, 0x0a000020, 0x08000004, 0x08000004}, + {0x0000a50c, 0x10000023, 0x10000023, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x15000028, 0x15000028, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x1b00002b, 0x1b00002b, 0x12000400, 0x12000400}, + {0x0000a518, 0x1f020028, 0x1f020028, 0x16000402, 0x16000402}, + {0x0000a51c, 0x2502002b, 0x2502002b, 0x19000404, 0x19000404}, + {0x0000a520, 0x2a04002a, 0x2a04002a, 0x1c000603, 0x1c000603}, + {0x0000a524, 0x2e06002a, 0x2e06002a, 0x21000a02, 0x21000a02}, + {0x0000a528, 0x3302202d, 0x3302202d, 0x25000a04, 0x25000a04}, + {0x0000a52c, 0x3804202c, 0x3804202c, 0x28000a20, 0x28000a20}, + {0x0000a530, 0x3c06202c, 0x3c06202c, 0x2c000e20, 0x2c000e20}, + {0x0000a534, 0x4108202d, 0x4108202d, 0x30000e22, 0x30000e22}, + {0x0000a538, 0x4506402d, 0x4506402d, 0x34000e24, 0x34000e24}, + {0x0000a53c, 0x4906222d, 0x4906222d, 0x38001640, 0x38001640}, + {0x0000a540, 0x4d062231, 0x4d062231, 0x3c001660, 0x3c001660}, + {0x0000a544, 0x50082231, 0x50082231, 0x3f001861, 0x3f001861}, + {0x0000a548, 0x5608422e, 0x5608422e, 0x43001a81, 0x43001a81}, + {0x0000a54c, 0x5e08442e, 0x5e08442e, 0x47001a83, 0x47001a83}, + {0x0000a550, 0x620a4431, 0x620a4431, 0x4a001c84, 0x4a001c84}, + {0x0000a554, 0x640a4432, 0x640a4432, 0x4e001ce3, 0x4e001ce3}, + {0x0000a558, 0x680a4434, 0x680a4434, 0x52001ce5, 0x52001ce5}, + {0x0000a55c, 0x6c0a6434, 0x6c0a6434, 0x56001ce9, 0x56001ce9}, + {0x0000a560, 0x6f0a6633, 0x6f0a6633, 0x5a001ceb, 0x5a001ceb}, + {0x0000a564, 0x730c6634, 0x730c6634, 0x5d001eec, 0x5d001eec}, + {0x0000a568, 0x730c6634, 0x730c6634, 0x5d001eec, 0x5d001eec}, + {0x0000a56c, 0x730c6634, 0x730c6634, 0x5d001eec, 0x5d001eec}, + {0x0000a570, 0x730c6634, 0x730c6634, 0x5d001eec, 0x5d001eec}, + {0x0000a574, 0x730c6634, 0x730c6634, 0x5d001eec, 0x5d001eec}, + {0x0000a578, 0x730c6634, 0x730c6634, 0x5d001eec, 0x5d001eec}, + {0x0000a57c, 0x730c6634, 0x730c6634, 0x5d001eec, 0x5d001eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x01804601, 0x01804601, 0x00000000, 0x00000000}, + {0x0000a60c, 0x01804601, 0x01804601, 0x00000000, 0x00000000}, + {0x0000a610, 0x01804601, 0x01804601, 0x00000000, 0x00000000}, + {0x0000a614, 0x01804601, 0x01804601, 0x01404000, 0x01404000}, + {0x0000a618, 0x01804601, 0x01804601, 0x01404501, 0x01404501}, + {0x0000a61c, 0x01804601, 0x01804601, 0x02008501, 0x02008501}, + {0x0000a620, 0x03408d02, 0x03408d02, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x0300cc03, 0x0300cc03, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x03410d04, 0x03410d04, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x03410d04, 0x03410d04, 0x04015005, 0x04015005}, + {0x0000a630, 0x03410d04, 0x03410d04, 0x04015005, 0x04015005}, + {0x0000a634, 0x03410d04, 0x03410d04, 0x04015005, 0x04015005}, + {0x0000a638, 0x03410d04, 0x03410d04, 0x04015005, 0x04015005}, + {0x0000a63c, 0x03410d04, 0x03410d04, 0x04015005, 0x04015005}, + {0x0000b2dc, 0x000cfff0, 0x000cfff0, 0x03aaa352, 0x03aaa352}, + {0x0000b2e0, 0x000f0000, 0x000f0000, 0x03ccc584, 0x03ccc584}, + {0x0000b2e4, 0x03f00000, 0x03f00000, 0x03f0f800, 0x03f0f800}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000c2dc, 0x000cfff0, 0x000cfff0, 0x03aaa352, 0x03aaa352}, + {0x0000c2e0, 0x000f0000, 0x000f0000, 0x03ccc584, 0x03ccc584}, + {0x0000c2e4, 0x03f00000, 0x03f00000, 0x03f0f800, 0x03f0f800}, + {0x0000c2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x00016044, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016048, 0x65240001, 0x65240001, 0x66480001, 0x66480001}, + {0x00016068, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016444, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016448, 0x65240001, 0x65240001, 0x66480001, 0x66480001}, + {0x00016468, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016844, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016848, 0x65240001, 0x65240001, 0x66480001, 0x66480001}, + {0x00016868, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, +}; + +static const u32 ar9300Common_rx_gain_table_2p2[][2] = { + /* Addr allmodes */ + {0x0000a000, 0x00010000}, + {0x0000a004, 0x00030002}, + {0x0000a008, 0x00050004}, + {0x0000a00c, 0x00810080}, + {0x0000a010, 0x00830082}, + {0x0000a014, 0x01810180}, + {0x0000a018, 0x01830182}, + {0x0000a01c, 0x01850184}, + {0x0000a020, 0x01890188}, + {0x0000a024, 0x018b018a}, + {0x0000a028, 0x018d018c}, + {0x0000a02c, 0x01910190}, + {0x0000a030, 0x01930192}, + {0x0000a034, 0x01950194}, + {0x0000a038, 0x038a0196}, + {0x0000a03c, 0x038c038b}, + {0x0000a040, 0x0390038d}, + {0x0000a044, 0x03920391}, + {0x0000a048, 0x03940393}, + {0x0000a04c, 0x03960395}, + {0x0000a050, 0x00000000}, + {0x0000a054, 0x00000000}, + {0x0000a058, 0x00000000}, + {0x0000a05c, 0x00000000}, + {0x0000a060, 0x00000000}, + {0x0000a064, 0x00000000}, + {0x0000a068, 0x00000000}, + {0x0000a06c, 0x00000000}, + {0x0000a070, 0x00000000}, + {0x0000a074, 0x00000000}, + {0x0000a078, 0x00000000}, + {0x0000a07c, 0x00000000}, + {0x0000a080, 0x22222229}, + {0x0000a084, 0x1d1d1d1d}, + {0x0000a088, 0x1d1d1d1d}, + {0x0000a08c, 0x1d1d1d1d}, + {0x0000a090, 0x171d1d1d}, + {0x0000a094, 0x11111717}, + {0x0000a098, 0x00030311}, + {0x0000a09c, 0x00000000}, + {0x0000a0a0, 0x00000000}, + {0x0000a0a4, 0x00000000}, + {0x0000a0a8, 0x00000000}, + {0x0000a0ac, 0x00000000}, + {0x0000a0b0, 0x00000000}, + {0x0000a0b4, 0x00000000}, + {0x0000a0b8, 0x00000000}, + {0x0000a0bc, 0x00000000}, + {0x0000a0c0, 0x001f0000}, + {0x0000a0c4, 0x01000101}, + {0x0000a0c8, 0x011e011f}, + {0x0000a0cc, 0x011c011d}, + {0x0000a0d0, 0x02030204}, + {0x0000a0d4, 0x02010202}, + {0x0000a0d8, 0x021f0200}, + {0x0000a0dc, 0x0302021e}, + {0x0000a0e0, 0x03000301}, + {0x0000a0e4, 0x031e031f}, + {0x0000a0e8, 0x0402031d}, + {0x0000a0ec, 0x04000401}, + {0x0000a0f0, 0x041e041f}, + {0x0000a0f4, 0x0502041d}, + {0x0000a0f8, 0x05000501}, + {0x0000a0fc, 0x051e051f}, + {0x0000a100, 0x06010602}, + {0x0000a104, 0x061f0600}, + {0x0000a108, 0x061d061e}, + {0x0000a10c, 0x07020703}, + {0x0000a110, 0x07000701}, + {0x0000a114, 0x00000000}, + {0x0000a118, 0x00000000}, + {0x0000a11c, 0x00000000}, + {0x0000a120, 0x00000000}, + {0x0000a124, 0x00000000}, + {0x0000a128, 0x00000000}, + {0x0000a12c, 0x00000000}, + {0x0000a130, 0x00000000}, + {0x0000a134, 0x00000000}, + {0x0000a138, 0x00000000}, + {0x0000a13c, 0x00000000}, + {0x0000a140, 0x001f0000}, + {0x0000a144, 0x01000101}, + {0x0000a148, 0x011e011f}, + {0x0000a14c, 0x011c011d}, + {0x0000a150, 0x02030204}, + {0x0000a154, 0x02010202}, + {0x0000a158, 0x021f0200}, + {0x0000a15c, 0x0302021e}, + {0x0000a160, 0x03000301}, + {0x0000a164, 0x031e031f}, + {0x0000a168, 0x0402031d}, + {0x0000a16c, 0x04000401}, + {0x0000a170, 0x041e041f}, + {0x0000a174, 0x0502041d}, + {0x0000a178, 0x05000501}, + {0x0000a17c, 0x051e051f}, + {0x0000a180, 0x06010602}, + {0x0000a184, 0x061f0600}, + {0x0000a188, 0x061d061e}, + {0x0000a18c, 0x07020703}, + {0x0000a190, 0x07000701}, + {0x0000a194, 0x00000000}, + {0x0000a198, 0x00000000}, + {0x0000a19c, 0x00000000}, + {0x0000a1a0, 0x00000000}, + {0x0000a1a4, 0x00000000}, + {0x0000a1a8, 0x00000000}, + {0x0000a1ac, 0x00000000}, + {0x0000a1b0, 0x00000000}, + {0x0000a1b4, 0x00000000}, + {0x0000a1b8, 0x00000000}, + {0x0000a1bc, 0x00000000}, + {0x0000a1c0, 0x00000000}, + {0x0000a1c4, 0x00000000}, + {0x0000a1c8, 0x00000000}, + {0x0000a1cc, 0x00000000}, + {0x0000a1d0, 0x00000000}, + {0x0000a1d4, 0x00000000}, + {0x0000a1d8, 0x00000000}, + {0x0000a1dc, 0x00000000}, + {0x0000a1e0, 0x00000000}, + {0x0000a1e4, 0x00000000}, + {0x0000a1e8, 0x00000000}, + {0x0000a1ec, 0x00000000}, + {0x0000a1f0, 0x00000396}, + {0x0000a1f4, 0x00000396}, + {0x0000a1f8, 0x00000396}, + {0x0000a1fc, 0x00000196}, + {0x0000b000, 0x00010000}, + {0x0000b004, 0x00030002}, + {0x0000b008, 0x00050004}, + {0x0000b00c, 0x00810080}, + {0x0000b010, 0x00830082}, + {0x0000b014, 0x01810180}, + {0x0000b018, 0x01830182}, + {0x0000b01c, 0x01850184}, + {0x0000b020, 0x02810280}, + {0x0000b024, 0x02830282}, + {0x0000b028, 0x02850284}, + {0x0000b02c, 0x02890288}, + {0x0000b030, 0x028b028a}, + {0x0000b034, 0x0388028c}, + {0x0000b038, 0x038a0389}, + {0x0000b03c, 0x038c038b}, + {0x0000b040, 0x0390038d}, + {0x0000b044, 0x03920391}, + {0x0000b048, 0x03940393}, + {0x0000b04c, 0x03960395}, + {0x0000b050, 0x00000000}, + {0x0000b054, 0x00000000}, + {0x0000b058, 0x00000000}, + {0x0000b05c, 0x00000000}, + {0x0000b060, 0x00000000}, + {0x0000b064, 0x00000000}, + {0x0000b068, 0x00000000}, + {0x0000b06c, 0x00000000}, + {0x0000b070, 0x00000000}, + {0x0000b074, 0x00000000}, + {0x0000b078, 0x00000000}, + {0x0000b07c, 0x00000000}, + {0x0000b080, 0x2a2d2f32}, + {0x0000b084, 0x21232328}, + {0x0000b088, 0x19191c1e}, + {0x0000b08c, 0x12141417}, + {0x0000b090, 0x07070e0e}, + {0x0000b094, 0x03030305}, + {0x0000b098, 0x00000003}, + {0x0000b09c, 0x00000000}, + {0x0000b0a0, 0x00000000}, + {0x0000b0a4, 0x00000000}, + {0x0000b0a8, 0x00000000}, + {0x0000b0ac, 0x00000000}, + {0x0000b0b0, 0x00000000}, + {0x0000b0b4, 0x00000000}, + {0x0000b0b8, 0x00000000}, + {0x0000b0bc, 0x00000000}, + {0x0000b0c0, 0x003f0020}, + {0x0000b0c4, 0x00400041}, + {0x0000b0c8, 0x0140005f}, + {0x0000b0cc, 0x0160015f}, + {0x0000b0d0, 0x017e017f}, + {0x0000b0d4, 0x02410242}, + {0x0000b0d8, 0x025f0240}, + {0x0000b0dc, 0x027f0260}, + {0x0000b0e0, 0x0341027e}, + {0x0000b0e4, 0x035f0340}, + {0x0000b0e8, 0x037f0360}, + {0x0000b0ec, 0x04400441}, + {0x0000b0f0, 0x0460045f}, + {0x0000b0f4, 0x0541047f}, + {0x0000b0f8, 0x055f0540}, + {0x0000b0fc, 0x057f0560}, + {0x0000b100, 0x06400641}, + {0x0000b104, 0x0660065f}, + {0x0000b108, 0x067e067f}, + {0x0000b10c, 0x07410742}, + {0x0000b110, 0x075f0740}, + {0x0000b114, 0x077f0760}, + {0x0000b118, 0x07800781}, + {0x0000b11c, 0x07a0079f}, + {0x0000b120, 0x07c107bf}, + {0x0000b124, 0x000007c0}, + {0x0000b128, 0x00000000}, + {0x0000b12c, 0x00000000}, + {0x0000b130, 0x00000000}, + {0x0000b134, 0x00000000}, + {0x0000b138, 0x00000000}, + {0x0000b13c, 0x00000000}, + {0x0000b140, 0x003f0020}, + {0x0000b144, 0x00400041}, + {0x0000b148, 0x0140005f}, + {0x0000b14c, 0x0160015f}, + {0x0000b150, 0x017e017f}, + {0x0000b154, 0x02410242}, + {0x0000b158, 0x025f0240}, + {0x0000b15c, 0x027f0260}, + {0x0000b160, 0x0341027e}, + {0x0000b164, 0x035f0340}, + {0x0000b168, 0x037f0360}, + {0x0000b16c, 0x04400441}, + {0x0000b170, 0x0460045f}, + {0x0000b174, 0x0541047f}, + {0x0000b178, 0x055f0540}, + {0x0000b17c, 0x057f0560}, + {0x0000b180, 0x06400641}, + {0x0000b184, 0x0660065f}, + {0x0000b188, 0x067e067f}, + {0x0000b18c, 0x07410742}, + {0x0000b190, 0x075f0740}, + {0x0000b194, 0x077f0760}, + {0x0000b198, 0x07800781}, + {0x0000b19c, 0x07a0079f}, + {0x0000b1a0, 0x07c107bf}, + {0x0000b1a4, 0x000007c0}, + {0x0000b1a8, 0x00000000}, + {0x0000b1ac, 0x00000000}, + {0x0000b1b0, 0x00000000}, + {0x0000b1b4, 0x00000000}, + {0x0000b1b8, 0x00000000}, + {0x0000b1bc, 0x00000000}, + {0x0000b1c0, 0x00000000}, + {0x0000b1c4, 0x00000000}, + {0x0000b1c8, 0x00000000}, + {0x0000b1cc, 0x00000000}, + {0x0000b1d0, 0x00000000}, + {0x0000b1d4, 0x00000000}, + {0x0000b1d8, 0x00000000}, + {0x0000b1dc, 0x00000000}, + {0x0000b1e0, 0x00000000}, + {0x0000b1e4, 0x00000000}, + {0x0000b1e8, 0x00000000}, + {0x0000b1ec, 0x00000000}, + {0x0000b1f0, 0x00000396}, + {0x0000b1f4, 0x00000396}, + {0x0000b1f8, 0x00000396}, + {0x0000b1fc, 0x00000196}, +}; + +static const u32 ar9300Modes_low_ob_db_tx_gain_table_2p2[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0x00033800, 0x00033800, 0x03aaa352, 0x03aaa352}, + {0x0000a2e0, 0x0003c000, 0x0003c000, 0x03ccc584, 0x03ccc584}, + {0x0000a2e4, 0x03fc0000, 0x03fc0000, 0x03f0f800, 0x03f0f800}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a504, 0x06000003, 0x06000003, 0x04000002, 0x04000002}, + {0x0000a508, 0x0a000020, 0x0a000020, 0x08000004, 0x08000004}, + {0x0000a50c, 0x10000023, 0x10000023, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x16000220, 0x16000220, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x1c000223, 0x1c000223, 0x12000400, 0x12000400}, + {0x0000a518, 0x21002220, 0x21002220, 0x16000402, 0x16000402}, + {0x0000a51c, 0x27002223, 0x27002223, 0x19000404, 0x19000404}, + {0x0000a520, 0x2b022220, 0x2b022220, 0x1c000603, 0x1c000603}, + {0x0000a524, 0x2f022222, 0x2f022222, 0x21000a02, 0x21000a02}, + {0x0000a528, 0x34022225, 0x34022225, 0x25000a04, 0x25000a04}, + {0x0000a52c, 0x3a02222a, 0x3a02222a, 0x28000a20, 0x28000a20}, + {0x0000a530, 0x3e02222c, 0x3e02222c, 0x2c000e20, 0x2c000e20}, + {0x0000a534, 0x4202242a, 0x4202242a, 0x30000e22, 0x30000e22}, + {0x0000a538, 0x4702244a, 0x4702244a, 0x34000e24, 0x34000e24}, + {0x0000a53c, 0x4b02244c, 0x4b02244c, 0x38001640, 0x38001640}, + {0x0000a540, 0x4e02246c, 0x4e02246c, 0x3c001660, 0x3c001660}, + {0x0000a544, 0x52022470, 0x52022470, 0x3f001861, 0x3f001861}, + {0x0000a548, 0x55022490, 0x55022490, 0x43001a81, 0x43001a81}, + {0x0000a54c, 0x59022492, 0x59022492, 0x47001a83, 0x47001a83}, + {0x0000a550, 0x5d022692, 0x5d022692, 0x4a001c84, 0x4a001c84}, + {0x0000a554, 0x61022892, 0x61022892, 0x4e001ce3, 0x4e001ce3}, + {0x0000a558, 0x65024890, 0x65024890, 0x52001ce5, 0x52001ce5}, + {0x0000a55c, 0x69024892, 0x69024892, 0x56001ce9, 0x56001ce9}, + {0x0000a560, 0x6e024c92, 0x6e024c92, 0x5a001ceb, 0x5a001ceb}, + {0x0000a564, 0x74026e92, 0x74026e92, 0x5d001eec, 0x5d001eec}, + {0x0000a568, 0x74026e92, 0x74026e92, 0x5d001eec, 0x5d001eec}, + {0x0000a56c, 0x74026e92, 0x74026e92, 0x5d001eec, 0x5d001eec}, + {0x0000a570, 0x74026e92, 0x74026e92, 0x5d001eec, 0x5d001eec}, + {0x0000a574, 0x74026e92, 0x74026e92, 0x5d001eec, 0x5d001eec}, + {0x0000a578, 0x74026e92, 0x74026e92, 0x5d001eec, 0x5d001eec}, + {0x0000a57c, 0x74026e92, 0x74026e92, 0x5d001eec, 0x5d001eec}, + {0x0000a580, 0x00800000, 0x00800000, 0x00800000, 0x00800000}, + {0x0000a584, 0x06800003, 0x06800003, 0x04800002, 0x04800002}, + {0x0000a588, 0x0a800020, 0x0a800020, 0x08800004, 0x08800004}, + {0x0000a58c, 0x10800023, 0x10800023, 0x0b800200, 0x0b800200}, + {0x0000a590, 0x16800220, 0x16800220, 0x0f800202, 0x0f800202}, + {0x0000a594, 0x1c800223, 0x1c800223, 0x12800400, 0x12800400}, + {0x0000a598, 0x21802220, 0x21802220, 0x16800402, 0x16800402}, + {0x0000a59c, 0x27802223, 0x27802223, 0x19800404, 0x19800404}, + {0x0000a5a0, 0x2b822220, 0x2b822220, 0x1c800603, 0x1c800603}, + {0x0000a5a4, 0x2f822222, 0x2f822222, 0x21800a02, 0x21800a02}, + {0x0000a5a8, 0x34822225, 0x34822225, 0x25800a04, 0x25800a04}, + {0x0000a5ac, 0x3a82222a, 0x3a82222a, 0x28800a20, 0x28800a20}, + {0x0000a5b0, 0x3e82222c, 0x3e82222c, 0x2c800e20, 0x2c800e20}, + {0x0000a5b4, 0x4282242a, 0x4282242a, 0x30800e22, 0x30800e22}, + {0x0000a5b8, 0x4782244a, 0x4782244a, 0x34800e24, 0x34800e24}, + {0x0000a5bc, 0x4b82244c, 0x4b82244c, 0x38801640, 0x38801640}, + {0x0000a5c0, 0x4e82246c, 0x4e82246c, 0x3c801660, 0x3c801660}, + {0x0000a5c4, 0x52822470, 0x52822470, 0x3f801861, 0x3f801861}, + {0x0000a5c8, 0x55822490, 0x55822490, 0x43801a81, 0x43801a81}, + {0x0000a5cc, 0x59822492, 0x59822492, 0x47801a83, 0x47801a83}, + {0x0000a5d0, 0x5d822692, 0x5d822692, 0x4a801c84, 0x4a801c84}, + {0x0000a5d4, 0x61822892, 0x61822892, 0x4e801ce3, 0x4e801ce3}, + {0x0000a5d8, 0x65824890, 0x65824890, 0x52801ce5, 0x52801ce5}, + {0x0000a5dc, 0x69824892, 0x69824892, 0x56801ce9, 0x56801ce9}, + {0x0000a5e0, 0x6e824c92, 0x6e824c92, 0x5a801ceb, 0x5a801ceb}, + {0x0000a5e4, 0x74826e92, 0x74826e92, 0x5d801eec, 0x5d801eec}, + {0x0000a5e8, 0x74826e92, 0x74826e92, 0x5d801eec, 0x5d801eec}, + {0x0000a5ec, 0x74826e92, 0x74826e92, 0x5d801eec, 0x5d801eec}, + {0x0000a5f0, 0x74826e92, 0x74826e92, 0x5d801eec, 0x5d801eec}, + {0x0000a5f4, 0x74826e92, 0x74826e92, 0x5d801eec, 0x5d801eec}, + {0x0000a5f8, 0x74826e92, 0x74826e92, 0x5d801eec, 0x5d801eec}, + {0x0000a5fc, 0x74826e92, 0x74826e92, 0x5d801eec, 0x5d801eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x02004000, 0x02004000, 0x01404000, 0x01404000}, + {0x0000a618, 0x02004801, 0x02004801, 0x01404501, 0x01404501}, + {0x0000a61c, 0x02808a02, 0x02808a02, 0x02008501, 0x02008501}, + {0x0000a620, 0x0380ce03, 0x0380ce03, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x04411104, 0x04411104, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x04411104, 0x04411104, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x04411104, 0x04411104, 0x04015005, 0x04015005}, + {0x0000a630, 0x04411104, 0x04411104, 0x04015005, 0x04015005}, + {0x0000a634, 0x04411104, 0x04411104, 0x04015005, 0x04015005}, + {0x0000a638, 0x04411104, 0x04411104, 0x04015005, 0x04015005}, + {0x0000a63c, 0x04411104, 0x04411104, 0x04015005, 0x04015005}, + {0x0000b2dc, 0x00033800, 0x00033800, 0x03aaa352, 0x03aaa352}, + {0x0000b2e0, 0x0003c000, 0x0003c000, 0x03ccc584, 0x03ccc584}, + {0x0000b2e4, 0x03fc0000, 0x03fc0000, 0x03f0f800, 0x03f0f800}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000c2dc, 0x00033800, 0x00033800, 0x03aaa352, 0x03aaa352}, + {0x0000c2e0, 0x0003c000, 0x0003c000, 0x03ccc584, 0x03ccc584}, + {0x0000c2e4, 0x03fc0000, 0x03fc0000, 0x03f0f800, 0x03f0f800}, + {0x0000c2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x00016044, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016048, 0x66480001, 0x66480001, 0x66480001, 0x66480001}, + {0x00016068, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016444, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016448, 0x66480001, 0x66480001, 0x66480001, 0x66480001}, + {0x00016468, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016844, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016848, 0x66480001, 0x66480001, 0x66480001, 0x66480001}, + {0x00016868, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, +}; + +static const u32 ar9300_2p2_mac_core[][2] = { + /* Addr allmodes */ + {0x00000008, 0x00000000}, + {0x00000030, 0x00020085}, + {0x00000034, 0x00000005}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000000}, + {0x00000048, 0x00000008}, + {0x0000004c, 0x00000010}, + {0x00000050, 0x00000000}, + {0x00001040, 0x002ffc0f}, + {0x00001044, 0x002ffc0f}, + {0x00001048, 0x002ffc0f}, + {0x0000104c, 0x002ffc0f}, + {0x00001050, 0x002ffc0f}, + {0x00001054, 0x002ffc0f}, + {0x00001058, 0x002ffc0f}, + {0x0000105c, 0x002ffc0f}, + {0x00001060, 0x002ffc0f}, + {0x00001064, 0x002ffc0f}, + {0x000010f0, 0x00000100}, + {0x00001270, 0x00000000}, + {0x000012b0, 0x00000000}, + {0x000012f0, 0x00000000}, + {0x0000143c, 0x00000000}, + {0x0000147c, 0x00000000}, + {0x00008000, 0x00000000}, + {0x00008004, 0x00000000}, + {0x00008008, 0x00000000}, + {0x0000800c, 0x00000000}, + {0x00008018, 0x00000000}, + {0x00008020, 0x00000000}, + {0x00008038, 0x00000000}, + {0x0000803c, 0x00000000}, + {0x00008040, 0x00000000}, + {0x00008044, 0x00000000}, + {0x00008048, 0x00000000}, + {0x0000804c, 0xffffffff}, + {0x00008054, 0x00000000}, + {0x00008058, 0x00000000}, + {0x0000805c, 0x000fc78f}, + {0x00008060, 0x0000000f}, + {0x00008064, 0x00000000}, + {0x00008070, 0x00000310}, + {0x00008074, 0x00000020}, + {0x00008078, 0x00000000}, + {0x0000809c, 0x0000000f}, + {0x000080a0, 0x00000000}, + {0x000080a4, 0x02ff0000}, + {0x000080a8, 0x0e070605}, + {0x000080ac, 0x0000000d}, + {0x000080b0, 0x00000000}, + {0x000080b4, 0x00000000}, + {0x000080b8, 0x00000000}, + {0x000080bc, 0x00000000}, + {0x000080c0, 0x2a800000}, + {0x000080c4, 0x06900168}, + {0x000080c8, 0x13881c20}, + {0x000080cc, 0x01f40000}, + {0x000080d0, 0x00252500}, + {0x000080d4, 0x00a00000}, + {0x000080d8, 0x00400000}, + {0x000080dc, 0x00000000}, + {0x000080e0, 0xffffffff}, + {0x000080e4, 0x0000ffff}, + {0x000080e8, 0x3f3f3f3f}, + {0x000080ec, 0x00000000}, + {0x000080f0, 0x00000000}, + {0x000080f4, 0x00000000}, + {0x000080fc, 0x00020000}, + {0x00008100, 0x00000000}, + {0x00008108, 0x00000052}, + {0x0000810c, 0x00000000}, + {0x00008110, 0x00000000}, + {0x00008114, 0x000007ff}, + {0x00008118, 0x000000aa}, + {0x0000811c, 0x00003210}, + {0x00008124, 0x00000000}, + {0x00008128, 0x00000000}, + {0x0000812c, 0x00000000}, + {0x00008130, 0x00000000}, + {0x00008134, 0x00000000}, + {0x00008138, 0x00000000}, + {0x0000813c, 0x0000ffff}, + {0x00008144, 0xffffffff}, + {0x00008168, 0x00000000}, + {0x0000816c, 0x00000000}, + {0x000081c0, 0x00000000}, + {0x000081c4, 0x33332210}, + {0x000081ec, 0x00000000}, + {0x000081f0, 0x00000000}, + {0x000081f4, 0x00000000}, + {0x000081f8, 0x00000000}, + {0x000081fc, 0x00000000}, + {0x00008240, 0x00100000}, + {0x00008244, 0x0010f424}, + {0x00008248, 0x00000800}, + {0x0000824c, 0x0001e848}, + {0x00008250, 0x00000000}, + {0x00008254, 0x00000000}, + {0x00008258, 0x00000000}, + {0x0000825c, 0x40000000}, + {0x00008260, 0x00080922}, + {0x00008264, 0x9d400010}, + {0x00008268, 0xffffffff}, + {0x0000826c, 0x0000ffff}, + {0x00008270, 0x00000000}, + {0x00008274, 0x40000000}, + {0x00008278, 0x003e4180}, + {0x0000827c, 0x00000004}, + {0x00008284, 0x0000002c}, + {0x00008288, 0x0000002c}, + {0x0000828c, 0x000000ff}, + {0x00008294, 0x00000000}, + {0x00008298, 0x00000000}, + {0x0000829c, 0x00000000}, + {0x00008300, 0x00000140}, + {0x00008314, 0x00000000}, + {0x0000831c, 0x0000010d}, + {0x00008328, 0x00000000}, + {0x0000832c, 0x00000007}, + {0x00008330, 0x00000302}, + {0x00008334, 0x00000700}, + {0x00008338, 0x00ff0000}, + {0x0000833c, 0x02400000}, + {0x00008340, 0x000107ff}, + {0x00008344, 0xaa48105b}, + {0x00008348, 0x008f0000}, + {0x0000835c, 0x00000000}, + {0x00008360, 0xffffffff}, + {0x00008364, 0xffffffff}, + {0x00008368, 0x00000000}, + {0x00008370, 0x00000000}, + {0x00008374, 0x000000ff}, + {0x00008378, 0x00000000}, + {0x0000837c, 0x00000000}, + {0x00008380, 0xffffffff}, + {0x00008384, 0xffffffff}, + {0x00008390, 0xffffffff}, + {0x00008394, 0xffffffff}, + {0x00008398, 0x00000000}, + {0x0000839c, 0x00000000}, + {0x000083a0, 0x00000000}, + {0x000083a4, 0x0000fa14}, + {0x000083a8, 0x000f0c00}, + {0x000083ac, 0x33332210}, + {0x000083b0, 0x33332210}, + {0x000083b4, 0x33332210}, + {0x000083b8, 0x33332210}, + {0x000083bc, 0x00000000}, + {0x000083c0, 0x00000000}, + {0x000083c4, 0x00000000}, + {0x000083c8, 0x00000000}, + {0x000083cc, 0x00000200}, + {0x000083d0, 0x000301ff}, +}; + +static const u32 ar9300Common_wo_xlna_rx_gain_table_2p2[][2] = { + /* Addr allmodes */ + {0x0000a000, 0x00010000}, + {0x0000a004, 0x00030002}, + {0x0000a008, 0x00050004}, + {0x0000a00c, 0x00810080}, + {0x0000a010, 0x00830082}, + {0x0000a014, 0x01810180}, + {0x0000a018, 0x01830182}, + {0x0000a01c, 0x01850184}, + {0x0000a020, 0x01890188}, + {0x0000a024, 0x018b018a}, + {0x0000a028, 0x018d018c}, + {0x0000a02c, 0x03820190}, + {0x0000a030, 0x03840383}, + {0x0000a034, 0x03880385}, + {0x0000a038, 0x038a0389}, + {0x0000a03c, 0x038c038b}, + {0x0000a040, 0x0390038d}, + {0x0000a044, 0x03920391}, + {0x0000a048, 0x03940393}, + {0x0000a04c, 0x03960395}, + {0x0000a050, 0x00000000}, + {0x0000a054, 0x00000000}, + {0x0000a058, 0x00000000}, + {0x0000a05c, 0x00000000}, + {0x0000a060, 0x00000000}, + {0x0000a064, 0x00000000}, + {0x0000a068, 0x00000000}, + {0x0000a06c, 0x00000000}, + {0x0000a070, 0x00000000}, + {0x0000a074, 0x00000000}, + {0x0000a078, 0x00000000}, + {0x0000a07c, 0x00000000}, + {0x0000a080, 0x29292929}, + {0x0000a084, 0x29292929}, + {0x0000a088, 0x29292929}, + {0x0000a08c, 0x29292929}, + {0x0000a090, 0x22292929}, + {0x0000a094, 0x1d1d2222}, + {0x0000a098, 0x0c111117}, + {0x0000a09c, 0x00030303}, + {0x0000a0a0, 0x00000000}, + {0x0000a0a4, 0x00000000}, + {0x0000a0a8, 0x00000000}, + {0x0000a0ac, 0x00000000}, + {0x0000a0b0, 0x00000000}, + {0x0000a0b4, 0x00000000}, + {0x0000a0b8, 0x00000000}, + {0x0000a0bc, 0x00000000}, + {0x0000a0c0, 0x001f0000}, + {0x0000a0c4, 0x01000101}, + {0x0000a0c8, 0x011e011f}, + {0x0000a0cc, 0x011c011d}, + {0x0000a0d0, 0x02030204}, + {0x0000a0d4, 0x02010202}, + {0x0000a0d8, 0x021f0200}, + {0x0000a0dc, 0x0302021e}, + {0x0000a0e0, 0x03000301}, + {0x0000a0e4, 0x031e031f}, + {0x0000a0e8, 0x0402031d}, + {0x0000a0ec, 0x04000401}, + {0x0000a0f0, 0x041e041f}, + {0x0000a0f4, 0x0502041d}, + {0x0000a0f8, 0x05000501}, + {0x0000a0fc, 0x051e051f}, + {0x0000a100, 0x06010602}, + {0x0000a104, 0x061f0600}, + {0x0000a108, 0x061d061e}, + {0x0000a10c, 0x07020703}, + {0x0000a110, 0x07000701}, + {0x0000a114, 0x00000000}, + {0x0000a118, 0x00000000}, + {0x0000a11c, 0x00000000}, + {0x0000a120, 0x00000000}, + {0x0000a124, 0x00000000}, + {0x0000a128, 0x00000000}, + {0x0000a12c, 0x00000000}, + {0x0000a130, 0x00000000}, + {0x0000a134, 0x00000000}, + {0x0000a138, 0x00000000}, + {0x0000a13c, 0x00000000}, + {0x0000a140, 0x001f0000}, + {0x0000a144, 0x01000101}, + {0x0000a148, 0x011e011f}, + {0x0000a14c, 0x011c011d}, + {0x0000a150, 0x02030204}, + {0x0000a154, 0x02010202}, + {0x0000a158, 0x021f0200}, + {0x0000a15c, 0x0302021e}, + {0x0000a160, 0x03000301}, + {0x0000a164, 0x031e031f}, + {0x0000a168, 0x0402031d}, + {0x0000a16c, 0x04000401}, + {0x0000a170, 0x041e041f}, + {0x0000a174, 0x0502041d}, + {0x0000a178, 0x05000501}, + {0x0000a17c, 0x051e051f}, + {0x0000a180, 0x06010602}, + {0x0000a184, 0x061f0600}, + {0x0000a188, 0x061d061e}, + {0x0000a18c, 0x07020703}, + {0x0000a190, 0x07000701}, + {0x0000a194, 0x00000000}, + {0x0000a198, 0x00000000}, + {0x0000a19c, 0x00000000}, + {0x0000a1a0, 0x00000000}, + {0x0000a1a4, 0x00000000}, + {0x0000a1a8, 0x00000000}, + {0x0000a1ac, 0x00000000}, + {0x0000a1b0, 0x00000000}, + {0x0000a1b4, 0x00000000}, + {0x0000a1b8, 0x00000000}, + {0x0000a1bc, 0x00000000}, + {0x0000a1c0, 0x00000000}, + {0x0000a1c4, 0x00000000}, + {0x0000a1c8, 0x00000000}, + {0x0000a1cc, 0x00000000}, + {0x0000a1d0, 0x00000000}, + {0x0000a1d4, 0x00000000}, + {0x0000a1d8, 0x00000000}, + {0x0000a1dc, 0x00000000}, + {0x0000a1e0, 0x00000000}, + {0x0000a1e4, 0x00000000}, + {0x0000a1e8, 0x00000000}, + {0x0000a1ec, 0x00000000}, + {0x0000a1f0, 0x00000396}, + {0x0000a1f4, 0x00000396}, + {0x0000a1f8, 0x00000396}, + {0x0000a1fc, 0x00000196}, + {0x0000b000, 0x00010000}, + {0x0000b004, 0x00030002}, + {0x0000b008, 0x00050004}, + {0x0000b00c, 0x00810080}, + {0x0000b010, 0x00830082}, + {0x0000b014, 0x01810180}, + {0x0000b018, 0x01830182}, + {0x0000b01c, 0x01850184}, + {0x0000b020, 0x02810280}, + {0x0000b024, 0x02830282}, + {0x0000b028, 0x02850284}, + {0x0000b02c, 0x02890288}, + {0x0000b030, 0x028b028a}, + {0x0000b034, 0x0388028c}, + {0x0000b038, 0x038a0389}, + {0x0000b03c, 0x038c038b}, + {0x0000b040, 0x0390038d}, + {0x0000b044, 0x03920391}, + {0x0000b048, 0x03940393}, + {0x0000b04c, 0x03960395}, + {0x0000b050, 0x00000000}, + {0x0000b054, 0x00000000}, + {0x0000b058, 0x00000000}, + {0x0000b05c, 0x00000000}, + {0x0000b060, 0x00000000}, + {0x0000b064, 0x00000000}, + {0x0000b068, 0x00000000}, + {0x0000b06c, 0x00000000}, + {0x0000b070, 0x00000000}, + {0x0000b074, 0x00000000}, + {0x0000b078, 0x00000000}, + {0x0000b07c, 0x00000000}, + {0x0000b080, 0x32323232}, + {0x0000b084, 0x2f2f3232}, + {0x0000b088, 0x23282a2d}, + {0x0000b08c, 0x1c1e2123}, + {0x0000b090, 0x14171919}, + {0x0000b094, 0x0e0e1214}, + {0x0000b098, 0x03050707}, + {0x0000b09c, 0x00030303}, + {0x0000b0a0, 0x00000000}, + {0x0000b0a4, 0x00000000}, + {0x0000b0a8, 0x00000000}, + {0x0000b0ac, 0x00000000}, + {0x0000b0b0, 0x00000000}, + {0x0000b0b4, 0x00000000}, + {0x0000b0b8, 0x00000000}, + {0x0000b0bc, 0x00000000}, + {0x0000b0c0, 0x003f0020}, + {0x0000b0c4, 0x00400041}, + {0x0000b0c8, 0x0140005f}, + {0x0000b0cc, 0x0160015f}, + {0x0000b0d0, 0x017e017f}, + {0x0000b0d4, 0x02410242}, + {0x0000b0d8, 0x025f0240}, + {0x0000b0dc, 0x027f0260}, + {0x0000b0e0, 0x0341027e}, + {0x0000b0e4, 0x035f0340}, + {0x0000b0e8, 0x037f0360}, + {0x0000b0ec, 0x04400441}, + {0x0000b0f0, 0x0460045f}, + {0x0000b0f4, 0x0541047f}, + {0x0000b0f8, 0x055f0540}, + {0x0000b0fc, 0x057f0560}, + {0x0000b100, 0x06400641}, + {0x0000b104, 0x0660065f}, + {0x0000b108, 0x067e067f}, + {0x0000b10c, 0x07410742}, + {0x0000b110, 0x075f0740}, + {0x0000b114, 0x077f0760}, + {0x0000b118, 0x07800781}, + {0x0000b11c, 0x07a0079f}, + {0x0000b120, 0x07c107bf}, + {0x0000b124, 0x000007c0}, + {0x0000b128, 0x00000000}, + {0x0000b12c, 0x00000000}, + {0x0000b130, 0x00000000}, + {0x0000b134, 0x00000000}, + {0x0000b138, 0x00000000}, + {0x0000b13c, 0x00000000}, + {0x0000b140, 0x003f0020}, + {0x0000b144, 0x00400041}, + {0x0000b148, 0x0140005f}, + {0x0000b14c, 0x0160015f}, + {0x0000b150, 0x017e017f}, + {0x0000b154, 0x02410242}, + {0x0000b158, 0x025f0240}, + {0x0000b15c, 0x027f0260}, + {0x0000b160, 0x0341027e}, + {0x0000b164, 0x035f0340}, + {0x0000b168, 0x037f0360}, + {0x0000b16c, 0x04400441}, + {0x0000b170, 0x0460045f}, + {0x0000b174, 0x0541047f}, + {0x0000b178, 0x055f0540}, + {0x0000b17c, 0x057f0560}, + {0x0000b180, 0x06400641}, + {0x0000b184, 0x0660065f}, + {0x0000b188, 0x067e067f}, + {0x0000b18c, 0x07410742}, + {0x0000b190, 0x075f0740}, + {0x0000b194, 0x077f0760}, + {0x0000b198, 0x07800781}, + {0x0000b19c, 0x07a0079f}, + {0x0000b1a0, 0x07c107bf}, + {0x0000b1a4, 0x000007c0}, + {0x0000b1a8, 0x00000000}, + {0x0000b1ac, 0x00000000}, + {0x0000b1b0, 0x00000000}, + {0x0000b1b4, 0x00000000}, + {0x0000b1b8, 0x00000000}, + {0x0000b1bc, 0x00000000}, + {0x0000b1c0, 0x00000000}, + {0x0000b1c4, 0x00000000}, + {0x0000b1c8, 0x00000000}, + {0x0000b1cc, 0x00000000}, + {0x0000b1d0, 0x00000000}, + {0x0000b1d4, 0x00000000}, + {0x0000b1d8, 0x00000000}, + {0x0000b1dc, 0x00000000}, + {0x0000b1e0, 0x00000000}, + {0x0000b1e4, 0x00000000}, + {0x0000b1e8, 0x00000000}, + {0x0000b1ec, 0x00000000}, + {0x0000b1f0, 0x00000396}, + {0x0000b1f4, 0x00000396}, + {0x0000b1f8, 0x00000396}, + {0x0000b1fc, 0x00000196}, +}; + +static const u32 ar9300_2p2_soc_preamble[][2] = { + /* Addr allmodes */ + {0x000040a4, 0x00a0c1c9}, + {0x00007008, 0x00000000}, + {0x00007020, 0x00000000}, + {0x00007034, 0x00000002}, + {0x00007038, 0x000004c2}, + {0x00007048, 0x00000008}, +}; + +static const u32 ar9300PciePhy_pll_on_clkreq_disable_L1_2p2[][2] = { + /* Addr allmodes */ + {0x00004040, 0x0821265e}, + {0x00004040, 0x0008003b}, + {0x00004044, 0x00000000}, +}; + +static const u32 ar9300PciePhy_clkreq_enable_L1_2p2[][2] = { + /* Addr allmodes */ + {0x00004040, 0x08253e5e}, + {0x00004040, 0x0008003b}, + {0x00004044, 0x00000000}, +}; + +static const u32 ar9300PciePhy_clkreq_disable_L1_2p2[][2] = { + /* Addr allmodes */ + {0x00004040, 0x08213e5e}, + {0x00004040, 0x0008003b}, + {0x00004044, 0x00000000}, +}; + +#endif /* INITVALS_9003_2P2_H */ diff --git a/drivers/net/wireless/ath/ath9k/ar9003_calib.c b/drivers/net/wireless/ath/ath9k/ar9003_calib.c new file mode 100644 index 0000000..6988e1d --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9003_calib.c @@ -0,0 +1,1187 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "hw.h" +#include "hw-ops.h" +#include "ar9003_phy.h" +#include "ar9003_rtt.h" +#include "ar9003_mci.h" + +#define MAX_MEASUREMENT MAX_IQCAL_MEASUREMENT +#define MAX_MAG_DELTA 11 +#define MAX_PHS_DELTA 10 + +struct coeff { + int mag_coeff[AR9300_MAX_CHAINS][MAX_MEASUREMENT]; + int phs_coeff[AR9300_MAX_CHAINS][MAX_MEASUREMENT]; + int iqc_coeff[2]; +}; + +enum ar9003_cal_types { + IQ_MISMATCH_CAL = BIT(0), +}; + +static void ar9003_hw_setup_calibration(struct ath_hw *ah, + struct ath9k_cal_list *currCal) +{ + struct ath_common *common = ath9k_hw_common(ah); + + /* Select calibration to run */ + switch (currCal->calData->calType) { + case IQ_MISMATCH_CAL: + /* + * Start calibration with + * 2^(INIT_IQCAL_LOG_COUNT_MAX+1) samples + */ + REG_RMW_FIELD(ah, AR_PHY_TIMING4, + AR_PHY_TIMING4_IQCAL_LOG_COUNT_MAX, + currCal->calData->calCountMax); + REG_WRITE(ah, AR_PHY_CALMODE, AR_PHY_CALMODE_IQ); + + ath_dbg(common, CALIBRATE, + "starting IQ Mismatch Calibration\n"); + + /* Kick-off cal */ + REG_SET_BIT(ah, AR_PHY_TIMING4, AR_PHY_TIMING4_DO_CAL); + break; + default: + ath_err(common, "Invalid calibration type\n"); + break; + } +} + +/* + * Generic calibration routine. + * Recalibrate the lower PHY chips to account for temperature/environment + * changes. + */ +static bool ar9003_hw_per_calibration(struct ath_hw *ah, + struct ath9k_channel *ichan, + u8 rxchainmask, + struct ath9k_cal_list *currCal) +{ + struct ath9k_hw_cal_data *caldata = ah->caldata; + /* Cal is assumed not done until explicitly set below */ + bool iscaldone = false; + + /* Calibration in progress. */ + if (currCal->calState == CAL_RUNNING) { + /* Check to see if it has finished. */ + if (!(REG_READ(ah, AR_PHY_TIMING4) & AR_PHY_TIMING4_DO_CAL)) { + /* + * Accumulate cal measures for active chains + */ + currCal->calData->calCollect(ah); + ah->cal_samples++; + + if (ah->cal_samples >= + currCal->calData->calNumSamples) { + unsigned int i, numChains = 0; + for (i = 0; i < AR9300_MAX_CHAINS; i++) { + if (rxchainmask & (1 << i)) + numChains++; + } + + /* + * Process accumulated data + */ + currCal->calData->calPostProc(ah, numChains); + + /* Calibration has finished. */ + caldata->CalValid |= currCal->calData->calType; + currCal->calState = CAL_DONE; + iscaldone = true; + } else { + /* + * Set-up collection of another sub-sample until we + * get desired number + */ + ar9003_hw_setup_calibration(ah, currCal); + } + } + } else if (!(caldata->CalValid & currCal->calData->calType)) { + /* If current cal is marked invalid in channel, kick it off */ + ath9k_hw_reset_calibration(ah, currCal); + } + + return iscaldone; +} + +static bool ar9003_hw_calibrate(struct ath_hw *ah, + struct ath9k_channel *chan, + u8 rxchainmask, + bool longcal) +{ + bool iscaldone = true; + struct ath9k_cal_list *currCal = ah->cal_list_curr; + + /* + * For given calibration: + * 1. Call generic cal routine + * 2. When this cal is done (isCalDone) if we have more cals waiting + * (eg after reset), mask this to upper layers by not propagating + * isCalDone if it is set to TRUE. + * Instead, change isCalDone to FALSE and setup the waiting cal(s) + * to be run. + */ + if (currCal && + (currCal->calState == CAL_RUNNING || + currCal->calState == CAL_WAITING)) { + iscaldone = ar9003_hw_per_calibration(ah, chan, + rxchainmask, currCal); + if (iscaldone) { + ah->cal_list_curr = currCal = currCal->calNext; + + if (currCal->calState == CAL_WAITING) { + iscaldone = false; + ath9k_hw_reset_calibration(ah, currCal); + } + } + } + + /* + * Do NF cal only at longer intervals. Get the value from + * the previous NF cal and update history buffer. + */ + if (longcal && ath9k_hw_getnf(ah, chan)) { + /* + * Load the NF from history buffer of the current channel. + * NF is slow time-variant, so it is OK to use a historical + * value. + */ + ath9k_hw_loadnf(ah, ah->curchan); + + /* start NF calibration, without updating BB NF register */ + ath9k_hw_start_nfcal(ah, false); + } + + return iscaldone; +} + +static void ar9003_hw_iqcal_collect(struct ath_hw *ah) +{ + int i; + + /* Accumulate IQ cal measures for active chains */ + for (i = 0; i < AR5416_MAX_CHAINS; i++) { + if (ah->txchainmask & BIT(i)) { + ah->totalPowerMeasI[i] += + REG_READ(ah, AR_PHY_CAL_MEAS_0(i)); + ah->totalPowerMeasQ[i] += + REG_READ(ah, AR_PHY_CAL_MEAS_1(i)); + ah->totalIqCorrMeas[i] += + (int32_t) REG_READ(ah, AR_PHY_CAL_MEAS_2(i)); + ath_dbg(ath9k_hw_common(ah), CALIBRATE, + "%d: Chn %d pmi=0x%08x;pmq=0x%08x;iqcm=0x%08x;\n", + ah->cal_samples, i, ah->totalPowerMeasI[i], + ah->totalPowerMeasQ[i], + ah->totalIqCorrMeas[i]); + } + } +} + +static void ar9003_hw_iqcalibrate(struct ath_hw *ah, u8 numChains) +{ + struct ath_common *common = ath9k_hw_common(ah); + u32 powerMeasQ, powerMeasI, iqCorrMeas; + u32 qCoffDenom, iCoffDenom; + int32_t qCoff, iCoff; + int iqCorrNeg, i; + static const u_int32_t offset_array[3] = { + AR_PHY_RX_IQCAL_CORR_B0, + AR_PHY_RX_IQCAL_CORR_B1, + AR_PHY_RX_IQCAL_CORR_B2, + }; + + for (i = 0; i < numChains; i++) { + powerMeasI = ah->totalPowerMeasI[i]; + powerMeasQ = ah->totalPowerMeasQ[i]; + iqCorrMeas = ah->totalIqCorrMeas[i]; + + ath_dbg(common, CALIBRATE, + "Starting IQ Cal and Correction for Chain %d\n", i); + + ath_dbg(common, CALIBRATE, + "Original: Chn %d iq_corr_meas = 0x%08x\n", + i, ah->totalIqCorrMeas[i]); + + iqCorrNeg = 0; + + if (iqCorrMeas > 0x80000000) { + iqCorrMeas = (0xffffffff - iqCorrMeas) + 1; + iqCorrNeg = 1; + } + + ath_dbg(common, CALIBRATE, "Chn %d pwr_meas_i = 0x%08x\n", + i, powerMeasI); + ath_dbg(common, CALIBRATE, "Chn %d pwr_meas_q = 0x%08x\n", + i, powerMeasQ); + ath_dbg(common, CALIBRATE, "iqCorrNeg is 0x%08x\n", iqCorrNeg); + + iCoffDenom = (powerMeasI / 2 + powerMeasQ / 2) / 256; + qCoffDenom = powerMeasQ / 64; + + if ((iCoffDenom != 0) && (qCoffDenom != 0)) { + iCoff = iqCorrMeas / iCoffDenom; + qCoff = powerMeasI / qCoffDenom - 64; + ath_dbg(common, CALIBRATE, "Chn %d iCoff = 0x%08x\n", + i, iCoff); + ath_dbg(common, CALIBRATE, "Chn %d qCoff = 0x%08x\n", + i, qCoff); + + /* Force bounds on iCoff */ + if (iCoff >= 63) + iCoff = 63; + else if (iCoff <= -63) + iCoff = -63; + + /* Negate iCoff if iqCorrNeg == 0 */ + if (iqCorrNeg == 0x0) + iCoff = -iCoff; + + /* Force bounds on qCoff */ + if (qCoff >= 63) + qCoff = 63; + else if (qCoff <= -63) + qCoff = -63; + + iCoff = iCoff & 0x7f; + qCoff = qCoff & 0x7f; + + ath_dbg(common, CALIBRATE, + "Chn %d : iCoff = 0x%x qCoff = 0x%x\n", + i, iCoff, qCoff); + ath_dbg(common, CALIBRATE, + "Register offset (0x%04x) before update = 0x%x\n", + offset_array[i], + REG_READ(ah, offset_array[i])); + + if (AR_SREV_9565(ah) && + (iCoff == 63 || qCoff == 63 || + iCoff == -63 || qCoff == -63)) + return; + + REG_RMW_FIELD(ah, offset_array[i], + AR_PHY_RX_IQCAL_CORR_IQCORR_Q_I_COFF, + iCoff); + REG_RMW_FIELD(ah, offset_array[i], + AR_PHY_RX_IQCAL_CORR_IQCORR_Q_Q_COFF, + qCoff); + ath_dbg(common, CALIBRATE, + "Register offset (0x%04x) QI COFF (bitfields 0x%08x) after update = 0x%x\n", + offset_array[i], + AR_PHY_RX_IQCAL_CORR_IQCORR_Q_I_COFF, + REG_READ(ah, offset_array[i])); + ath_dbg(common, CALIBRATE, + "Register offset (0x%04x) QQ COFF (bitfields 0x%08x) after update = 0x%x\n", + offset_array[i], + AR_PHY_RX_IQCAL_CORR_IQCORR_Q_Q_COFF, + REG_READ(ah, offset_array[i])); + + ath_dbg(common, CALIBRATE, + "IQ Cal and Correction done for Chain %d\n", i); + } + } + + REG_SET_BIT(ah, AR_PHY_RX_IQCAL_CORR_B0, + AR_PHY_RX_IQCAL_CORR_IQCORR_ENABLE); + ath_dbg(common, CALIBRATE, + "IQ Cal and Correction (offset 0x%04x) enabled (bit position 0x%08x). New Value 0x%08x\n", + (unsigned) (AR_PHY_RX_IQCAL_CORR_B0), + AR_PHY_RX_IQCAL_CORR_IQCORR_ENABLE, + REG_READ(ah, AR_PHY_RX_IQCAL_CORR_B0)); +} + +static const struct ath9k_percal_data iq_cal_single_sample = { + IQ_MISMATCH_CAL, + MIN_CAL_SAMPLES, + PER_MAX_LOG_COUNT, + ar9003_hw_iqcal_collect, + ar9003_hw_iqcalibrate +}; + +static void ar9003_hw_init_cal_settings(struct ath_hw *ah) +{ + ah->iq_caldata.calData = &iq_cal_single_sample; + + if (AR_SREV_9300_20_OR_LATER(ah)) { + ah->enabled_cals |= TX_IQ_CAL; + if (AR_SREV_9485_OR_LATER(ah) && !AR_SREV_9340(ah)) + ah->enabled_cals |= TX_IQ_ON_AGC_CAL; + } + + ah->supp_cals = IQ_MISMATCH_CAL; +} + +/* + * solve 4x4 linear equation used in loopback iq cal. + */ +static bool ar9003_hw_solve_iq_cal(struct ath_hw *ah, + s32 sin_2phi_1, + s32 cos_2phi_1, + s32 sin_2phi_2, + s32 cos_2phi_2, + s32 mag_a0_d0, + s32 phs_a0_d0, + s32 mag_a1_d0, + s32 phs_a1_d0, + s32 solved_eq[]) +{ + s32 f1 = cos_2phi_1 - cos_2phi_2, + f3 = sin_2phi_1 - sin_2phi_2, + f2; + s32 mag_tx, phs_tx, mag_rx, phs_rx; + const s32 result_shift = 1 << 15; + struct ath_common *common = ath9k_hw_common(ah); + + f2 = (f1 * f1 + f3 * f3) / result_shift; + + if (!f2) { + ath_dbg(common, CALIBRATE, "Divide by 0\n"); + return false; + } + + /* mag mismatch, tx */ + mag_tx = f1 * (mag_a0_d0 - mag_a1_d0) + f3 * (phs_a0_d0 - phs_a1_d0); + /* phs mismatch, tx */ + phs_tx = f3 * (-mag_a0_d0 + mag_a1_d0) + f1 * (phs_a0_d0 - phs_a1_d0); + + mag_tx = (mag_tx / f2); + phs_tx = (phs_tx / f2); + + /* mag mismatch, rx */ + mag_rx = mag_a0_d0 - (cos_2phi_1 * mag_tx + sin_2phi_1 * phs_tx) / + result_shift; + /* phs mismatch, rx */ + phs_rx = phs_a0_d0 + (sin_2phi_1 * mag_tx - cos_2phi_1 * phs_tx) / + result_shift; + + solved_eq[0] = mag_tx; + solved_eq[1] = phs_tx; + solved_eq[2] = mag_rx; + solved_eq[3] = phs_rx; + + return true; +} + +static s32 ar9003_hw_find_mag_approx(struct ath_hw *ah, s32 in_re, s32 in_im) +{ + s32 abs_i = abs(in_re), + abs_q = abs(in_im), + max_abs, min_abs; + + if (abs_i > abs_q) { + max_abs = abs_i; + min_abs = abs_q; + } else { + max_abs = abs_q; + min_abs = abs_i; + } + + return max_abs - (max_abs / 32) + (min_abs / 8) + (min_abs / 4); +} + +#define DELPT 32 + +static bool ar9003_hw_calc_iq_corr(struct ath_hw *ah, + s32 chain_idx, + const s32 iq_res[], + s32 iqc_coeff[]) +{ + s32 i2_m_q2_a0_d0, i2_p_q2_a0_d0, iq_corr_a0_d0, + i2_m_q2_a0_d1, i2_p_q2_a0_d1, iq_corr_a0_d1, + i2_m_q2_a1_d0, i2_p_q2_a1_d0, iq_corr_a1_d0, + i2_m_q2_a1_d1, i2_p_q2_a1_d1, iq_corr_a1_d1; + s32 mag_a0_d0, mag_a1_d0, mag_a0_d1, mag_a1_d1, + phs_a0_d0, phs_a1_d0, phs_a0_d1, phs_a1_d1, + sin_2phi_1, cos_2phi_1, + sin_2phi_2, cos_2phi_2; + s32 mag_tx, phs_tx, mag_rx, phs_rx; + s32 solved_eq[4], mag_corr_tx, phs_corr_tx, mag_corr_rx, phs_corr_rx, + q_q_coff, q_i_coff; + const s32 res_scale = 1 << 15; + const s32 delpt_shift = 1 << 8; + s32 mag1, mag2; + struct ath_common *common = ath9k_hw_common(ah); + + i2_m_q2_a0_d0 = iq_res[0] & 0xfff; + i2_p_q2_a0_d0 = (iq_res[0] >> 12) & 0xfff; + iq_corr_a0_d0 = ((iq_res[0] >> 24) & 0xff) + ((iq_res[1] & 0xf) << 8); + + if (i2_m_q2_a0_d0 > 0x800) + i2_m_q2_a0_d0 = -((0xfff - i2_m_q2_a0_d0) + 1); + + if (i2_p_q2_a0_d0 > 0x800) + i2_p_q2_a0_d0 = -((0xfff - i2_p_q2_a0_d0) + 1); + + if (iq_corr_a0_d0 > 0x800) + iq_corr_a0_d0 = -((0xfff - iq_corr_a0_d0) + 1); + + i2_m_q2_a0_d1 = (iq_res[1] >> 4) & 0xfff; + i2_p_q2_a0_d1 = (iq_res[2] & 0xfff); + iq_corr_a0_d1 = (iq_res[2] >> 12) & 0xfff; + + if (i2_m_q2_a0_d1 > 0x800) + i2_m_q2_a0_d1 = -((0xfff - i2_m_q2_a0_d1) + 1); + + if (i2_p_q2_a0_d1 > 0x800) + i2_p_q2_a0_d1 = -((0xfff - i2_p_q2_a0_d1) + 1); + + if (iq_corr_a0_d1 > 0x800) + iq_corr_a0_d1 = -((0xfff - iq_corr_a0_d1) + 1); + + i2_m_q2_a1_d0 = ((iq_res[2] >> 24) & 0xff) + ((iq_res[3] & 0xf) << 8); + i2_p_q2_a1_d0 = (iq_res[3] >> 4) & 0xfff; + iq_corr_a1_d0 = iq_res[4] & 0xfff; + + if (i2_m_q2_a1_d0 > 0x800) + i2_m_q2_a1_d0 = -((0xfff - i2_m_q2_a1_d0) + 1); + + if (i2_p_q2_a1_d0 > 0x800) + i2_p_q2_a1_d0 = -((0xfff - i2_p_q2_a1_d0) + 1); + + if (iq_corr_a1_d0 > 0x800) + iq_corr_a1_d0 = -((0xfff - iq_corr_a1_d0) + 1); + + i2_m_q2_a1_d1 = (iq_res[4] >> 12) & 0xfff; + i2_p_q2_a1_d1 = ((iq_res[4] >> 24) & 0xff) + ((iq_res[5] & 0xf) << 8); + iq_corr_a1_d1 = (iq_res[5] >> 4) & 0xfff; + + if (i2_m_q2_a1_d1 > 0x800) + i2_m_q2_a1_d1 = -((0xfff - i2_m_q2_a1_d1) + 1); + + if (i2_p_q2_a1_d1 > 0x800) + i2_p_q2_a1_d1 = -((0xfff - i2_p_q2_a1_d1) + 1); + + if (iq_corr_a1_d1 > 0x800) + iq_corr_a1_d1 = -((0xfff - iq_corr_a1_d1) + 1); + + if ((i2_p_q2_a0_d0 == 0) || (i2_p_q2_a0_d1 == 0) || + (i2_p_q2_a1_d0 == 0) || (i2_p_q2_a1_d1 == 0)) { + ath_dbg(common, CALIBRATE, + "Divide by 0:\n" + "a0_d0=%d\n" + "a0_d1=%d\n" + "a2_d0=%d\n" + "a1_d1=%d\n", + i2_p_q2_a0_d0, i2_p_q2_a0_d1, + i2_p_q2_a1_d0, i2_p_q2_a1_d1); + return false; + } + + mag_a0_d0 = (i2_m_q2_a0_d0 * res_scale) / i2_p_q2_a0_d0; + phs_a0_d0 = (iq_corr_a0_d0 * res_scale) / i2_p_q2_a0_d0; + + mag_a0_d1 = (i2_m_q2_a0_d1 * res_scale) / i2_p_q2_a0_d1; + phs_a0_d1 = (iq_corr_a0_d1 * res_scale) / i2_p_q2_a0_d1; + + mag_a1_d0 = (i2_m_q2_a1_d0 * res_scale) / i2_p_q2_a1_d0; + phs_a1_d0 = (iq_corr_a1_d0 * res_scale) / i2_p_q2_a1_d0; + + mag_a1_d1 = (i2_m_q2_a1_d1 * res_scale) / i2_p_q2_a1_d1; + phs_a1_d1 = (iq_corr_a1_d1 * res_scale) / i2_p_q2_a1_d1; + + /* w/o analog phase shift */ + sin_2phi_1 = (((mag_a0_d0 - mag_a0_d1) * delpt_shift) / DELPT); + /* w/o analog phase shift */ + cos_2phi_1 = (((phs_a0_d1 - phs_a0_d0) * delpt_shift) / DELPT); + /* w/ analog phase shift */ + sin_2phi_2 = (((mag_a1_d0 - mag_a1_d1) * delpt_shift) / DELPT); + /* w/ analog phase shift */ + cos_2phi_2 = (((phs_a1_d1 - phs_a1_d0) * delpt_shift) / DELPT); + + /* + * force sin^2 + cos^2 = 1; + * find magnitude by approximation + */ + mag1 = ar9003_hw_find_mag_approx(ah, cos_2phi_1, sin_2phi_1); + mag2 = ar9003_hw_find_mag_approx(ah, cos_2phi_2, sin_2phi_2); + + if ((mag1 == 0) || (mag2 == 0)) { + ath_dbg(common, CALIBRATE, "Divide by 0: mag1=%d, mag2=%d\n", + mag1, mag2); + return false; + } + + /* normalization sin and cos by mag */ + sin_2phi_1 = (sin_2phi_1 * res_scale / mag1); + cos_2phi_1 = (cos_2phi_1 * res_scale / mag1); + sin_2phi_2 = (sin_2phi_2 * res_scale / mag2); + cos_2phi_2 = (cos_2phi_2 * res_scale / mag2); + + /* calculate IQ mismatch */ + if (!ar9003_hw_solve_iq_cal(ah, + sin_2phi_1, cos_2phi_1, + sin_2phi_2, cos_2phi_2, + mag_a0_d0, phs_a0_d0, + mag_a1_d0, + phs_a1_d0, solved_eq)) { + ath_dbg(common, CALIBRATE, + "Call to ar9003_hw_solve_iq_cal() failed\n"); + return false; + } + + mag_tx = solved_eq[0]; + phs_tx = solved_eq[1]; + mag_rx = solved_eq[2]; + phs_rx = solved_eq[3]; + + ath_dbg(common, CALIBRATE, + "chain %d: mag mismatch=%d phase mismatch=%d\n", + chain_idx, mag_tx/res_scale, phs_tx/res_scale); + + if (res_scale == mag_tx) { + ath_dbg(common, CALIBRATE, + "Divide by 0: mag_tx=%d, res_scale=%d\n", + mag_tx, res_scale); + return false; + } + + /* calculate and quantize Tx IQ correction factor */ + mag_corr_tx = (mag_tx * res_scale) / (res_scale - mag_tx); + phs_corr_tx = -phs_tx; + + q_q_coff = (mag_corr_tx * 128 / res_scale); + q_i_coff = (phs_corr_tx * 256 / res_scale); + + ath_dbg(common, CALIBRATE, "tx chain %d: mag corr=%d phase corr=%d\n", + chain_idx, q_q_coff, q_i_coff); + + if (q_i_coff < -63) + q_i_coff = -63; + if (q_i_coff > 63) + q_i_coff = 63; + if (q_q_coff < -63) + q_q_coff = -63; + if (q_q_coff > 63) + q_q_coff = 63; + + iqc_coeff[0] = (q_q_coff * 128) + q_i_coff; + + ath_dbg(common, CALIBRATE, "tx chain %d: iq corr coeff=%x\n", + chain_idx, iqc_coeff[0]); + + if (-mag_rx == res_scale) { + ath_dbg(common, CALIBRATE, + "Divide by 0: mag_rx=%d, res_scale=%d\n", + mag_rx, res_scale); + return false; + } + + /* calculate and quantize Rx IQ correction factors */ + mag_corr_rx = (-mag_rx * res_scale) / (res_scale + mag_rx); + phs_corr_rx = -phs_rx; + + q_q_coff = (mag_corr_rx * 128 / res_scale); + q_i_coff = (phs_corr_rx * 256 / res_scale); + + ath_dbg(common, CALIBRATE, "rx chain %d: mag corr=%d phase corr=%d\n", + chain_idx, q_q_coff, q_i_coff); + + if (q_i_coff < -63) + q_i_coff = -63; + if (q_i_coff > 63) + q_i_coff = 63; + if (q_q_coff < -63) + q_q_coff = -63; + if (q_q_coff > 63) + q_q_coff = 63; + + iqc_coeff[1] = (q_q_coff * 128) + q_i_coff; + + ath_dbg(common, CALIBRATE, "rx chain %d: iq corr coeff=%x\n", + chain_idx, iqc_coeff[1]); + + return true; +} + +static void ar9003_hw_detect_outlier(int *mp_coeff, int nmeasurement, + int max_delta) +{ + int mp_max = -64, max_idx = 0; + int mp_min = 63, min_idx = 0; + int mp_avg = 0, i, outlier_idx = 0, mp_count = 0; + + /* find min/max mismatch across all calibrated gains */ + for (i = 0; i < nmeasurement; i++) { + if (mp_coeff[i] > mp_max) { + mp_max = mp_coeff[i]; + max_idx = i; + } else if (mp_coeff[i] < mp_min) { + mp_min = mp_coeff[i]; + min_idx = i; + } + } + + /* find average (exclude max abs value) */ + for (i = 0; i < nmeasurement; i++) { + if ((abs(mp_coeff[i]) < abs(mp_max)) || + (abs(mp_coeff[i]) < abs(mp_min))) { + mp_avg += mp_coeff[i]; + mp_count++; + } + } + + /* + * finding mean magnitude/phase if possible, otherwise + * just use the last value as the mean + */ + if (mp_count) + mp_avg /= mp_count; + else + mp_avg = mp_coeff[nmeasurement - 1]; + + /* detect outlier */ + if (abs(mp_max - mp_min) > max_delta) { + if (abs(mp_max - mp_avg) > abs(mp_min - mp_avg)) + outlier_idx = max_idx; + else + outlier_idx = min_idx; + + mp_coeff[outlier_idx] = mp_avg; + } +} + +static void ar9003_hw_tx_iqcal_load_avg_2_passes(struct ath_hw *ah, + struct coeff *coeff, + bool is_reusable) +{ + int i, im, nmeasurement; + u32 tx_corr_coeff[MAX_MEASUREMENT][AR9300_MAX_CHAINS]; + struct ath9k_hw_cal_data *caldata = ah->caldata; + + memset(tx_corr_coeff, 0, sizeof(tx_corr_coeff)); + for (i = 0; i < MAX_MEASUREMENT / 2; i++) { + tx_corr_coeff[i * 2][0] = tx_corr_coeff[(i * 2) + 1][0] = + AR_PHY_TX_IQCAL_CORR_COEFF_B0(i); + if (!AR_SREV_9485(ah)) { + tx_corr_coeff[i * 2][1] = + tx_corr_coeff[(i * 2) + 1][1] = + AR_PHY_TX_IQCAL_CORR_COEFF_B1(i); + + tx_corr_coeff[i * 2][2] = + tx_corr_coeff[(i * 2) + 1][2] = + AR_PHY_TX_IQCAL_CORR_COEFF_B2(i); + } + } + + /* Load the average of 2 passes */ + for (i = 0; i < AR9300_MAX_CHAINS; i++) { + if (!(ah->txchainmask & (1 << i))) + continue; + nmeasurement = REG_READ_FIELD(ah, + AR_PHY_TX_IQCAL_STATUS_B0, + AR_PHY_CALIBRATED_GAINS_0); + + if (nmeasurement > MAX_MEASUREMENT) + nmeasurement = MAX_MEASUREMENT; + + /* detect outlier only if nmeasurement > 1 */ + if (nmeasurement > 1) { + /* Detect magnitude outlier */ + ar9003_hw_detect_outlier(coeff->mag_coeff[i], + nmeasurement, MAX_MAG_DELTA); + + /* Detect phase outlier */ + ar9003_hw_detect_outlier(coeff->phs_coeff[i], + nmeasurement, MAX_PHS_DELTA); + } + + for (im = 0; im < nmeasurement; im++) { + + coeff->iqc_coeff[0] = (coeff->mag_coeff[i][im] & 0x7f) | + ((coeff->phs_coeff[i][im] & 0x7f) << 7); + + if ((im % 2) == 0) + REG_RMW_FIELD(ah, tx_corr_coeff[im][i], + AR_PHY_TX_IQCAL_CORR_COEFF_00_COEFF_TABLE, + coeff->iqc_coeff[0]); + else + REG_RMW_FIELD(ah, tx_corr_coeff[im][i], + AR_PHY_TX_IQCAL_CORR_COEFF_01_COEFF_TABLE, + coeff->iqc_coeff[0]); + + if (caldata) + caldata->tx_corr_coeff[im][i] = + coeff->iqc_coeff[0]; + } + if (caldata) + caldata->num_measures[i] = nmeasurement; + } + + REG_RMW_FIELD(ah, AR_PHY_TX_IQCAL_CONTROL_3, + AR_PHY_TX_IQCAL_CONTROL_3_IQCORR_EN, 0x1); + REG_RMW_FIELD(ah, AR_PHY_RX_IQCAL_CORR_B0, + AR_PHY_RX_IQCAL_CORR_B0_LOOPBACK_IQCORR_EN, 0x1); + + if (caldata) + caldata->done_txiqcal_once = is_reusable; + + return; +} + +static bool ar9003_hw_tx_iq_cal_run(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + u8 tx_gain_forced; + + tx_gain_forced = REG_READ_FIELD(ah, AR_PHY_TX_FORCED_GAIN, + AR_PHY_TXGAIN_FORCE); + if (tx_gain_forced) + REG_RMW_FIELD(ah, AR_PHY_TX_FORCED_GAIN, + AR_PHY_TXGAIN_FORCE, 0); + + REG_RMW_FIELD(ah, AR_PHY_TX_IQCAL_START, + AR_PHY_TX_IQCAL_START_DO_CAL, 1); + + if (!ath9k_hw_wait(ah, AR_PHY_TX_IQCAL_START, + AR_PHY_TX_IQCAL_START_DO_CAL, 0, + AH_WAIT_TIMEOUT)) { + ath_dbg(common, CALIBRATE, "Tx IQ Cal is not completed\n"); + return false; + } + return true; +} + +static void ar9003_hw_tx_iq_cal_post_proc(struct ath_hw *ah, bool is_reusable) +{ + struct ath_common *common = ath9k_hw_common(ah); + const u32 txiqcal_status[AR9300_MAX_CHAINS] = { + AR_PHY_TX_IQCAL_STATUS_B0, + AR_PHY_TX_IQCAL_STATUS_B1, + AR_PHY_TX_IQCAL_STATUS_B2, + }; + const u_int32_t chan_info_tab[] = { + AR_PHY_CHAN_INFO_TAB_0, + AR_PHY_CHAN_INFO_TAB_1, + AR_PHY_CHAN_INFO_TAB_2, + }; + struct coeff coeff; + s32 iq_res[6]; + int i, im, j; + int nmeasurement; + + for (i = 0; i < AR9300_MAX_CHAINS; i++) { + if (!(ah->txchainmask & (1 << i))) + continue; + + nmeasurement = REG_READ_FIELD(ah, + AR_PHY_TX_IQCAL_STATUS_B0, + AR_PHY_CALIBRATED_GAINS_0); + if (nmeasurement > MAX_MEASUREMENT) + nmeasurement = MAX_MEASUREMENT; + + for (im = 0; im < nmeasurement; im++) { + ath_dbg(common, CALIBRATE, + "Doing Tx IQ Cal for chain %d\n", i); + + if (REG_READ(ah, txiqcal_status[i]) & + AR_PHY_TX_IQCAL_STATUS_FAILED) { + ath_dbg(common, CALIBRATE, + "Tx IQ Cal failed for chain %d\n", i); + goto tx_iqcal_fail; + } + + for (j = 0; j < 3; j++) { + u32 idx = 2 * j, offset = 4 * (3 * im + j); + + REG_RMW_FIELD(ah, + AR_PHY_CHAN_INFO_MEMORY, + AR_PHY_CHAN_INFO_TAB_S2_READ, + 0); + + /* 32 bits */ + iq_res[idx] = REG_READ(ah, + chan_info_tab[i] + + offset); + + REG_RMW_FIELD(ah, + AR_PHY_CHAN_INFO_MEMORY, + AR_PHY_CHAN_INFO_TAB_S2_READ, + 1); + + /* 16 bits */ + iq_res[idx + 1] = 0xffff & REG_READ(ah, + chan_info_tab[i] + offset); + + ath_dbg(common, CALIBRATE, + "IQ_RES[%d]=0x%x IQ_RES[%d]=0x%x\n", + idx, iq_res[idx], idx + 1, + iq_res[idx + 1]); + } + + if (!ar9003_hw_calc_iq_corr(ah, i, iq_res, + coeff.iqc_coeff)) { + ath_dbg(common, CALIBRATE, + "Failed in calculation of IQ correction\n"); + goto tx_iqcal_fail; + } + + coeff.mag_coeff[i][im] = coeff.iqc_coeff[0] & 0x7f; + coeff.phs_coeff[i][im] = + (coeff.iqc_coeff[0] >> 7) & 0x7f; + + if (coeff.mag_coeff[i][im] > 63) + coeff.mag_coeff[i][im] -= 128; + if (coeff.phs_coeff[i][im] > 63) + coeff.phs_coeff[i][im] -= 128; + } + } + ar9003_hw_tx_iqcal_load_avg_2_passes(ah, &coeff, is_reusable); + + return; + +tx_iqcal_fail: + ath_dbg(common, CALIBRATE, "Tx IQ Cal failed\n"); + return; +} + +static void ar9003_hw_tx_iq_cal_reload(struct ath_hw *ah) +{ + struct ath9k_hw_cal_data *caldata = ah->caldata; + u32 tx_corr_coeff[MAX_MEASUREMENT][AR9300_MAX_CHAINS]; + int i, im; + + memset(tx_corr_coeff, 0, sizeof(tx_corr_coeff)); + for (i = 0; i < MAX_MEASUREMENT / 2; i++) { + tx_corr_coeff[i * 2][0] = tx_corr_coeff[(i * 2) + 1][0] = + AR_PHY_TX_IQCAL_CORR_COEFF_B0(i); + if (!AR_SREV_9485(ah)) { + tx_corr_coeff[i * 2][1] = + tx_corr_coeff[(i * 2) + 1][1] = + AR_PHY_TX_IQCAL_CORR_COEFF_B1(i); + + tx_corr_coeff[i * 2][2] = + tx_corr_coeff[(i * 2) + 1][2] = + AR_PHY_TX_IQCAL_CORR_COEFF_B2(i); + } + } + + for (i = 0; i < AR9300_MAX_CHAINS; i++) { + if (!(ah->txchainmask & (1 << i))) + continue; + + for (im = 0; im < caldata->num_measures[i]; im++) { + if ((im % 2) == 0) + REG_RMW_FIELD(ah, tx_corr_coeff[im][i], + AR_PHY_TX_IQCAL_CORR_COEFF_00_COEFF_TABLE, + caldata->tx_corr_coeff[im][i]); + else + REG_RMW_FIELD(ah, tx_corr_coeff[im][i], + AR_PHY_TX_IQCAL_CORR_COEFF_01_COEFF_TABLE, + caldata->tx_corr_coeff[im][i]); + } + } + + REG_RMW_FIELD(ah, AR_PHY_TX_IQCAL_CONTROL_3, + AR_PHY_TX_IQCAL_CONTROL_3_IQCORR_EN, 0x1); + REG_RMW_FIELD(ah, AR_PHY_RX_IQCAL_CORR_B0, + AR_PHY_RX_IQCAL_CORR_B0_LOOPBACK_IQCORR_EN, 0x1); +} + +static void ar9003_hw_manual_peak_cal(struct ath_hw *ah, u8 chain, bool is_2g) +{ + int offset[8], total = 0, test; + int agc_out, i; + + REG_RMW_FIELD(ah, AR_PHY_65NM_RXRF_GAINSTAGES(chain), + AR_PHY_65NM_RXRF_GAINSTAGES_RX_OVERRIDE, 0x1); + REG_RMW_FIELD(ah, AR_PHY_65NM_RXRF_GAINSTAGES(chain), + AR_PHY_65NM_RXRF_GAINSTAGES_LNAON_CALDC, 0x0); + if (is_2g) + REG_RMW_FIELD(ah, AR_PHY_65NM_RXRF_GAINSTAGES(chain), + AR_PHY_65NM_RXRF_GAINSTAGES_LNA2G_GAIN_OVR, 0x0); + else + REG_RMW_FIELD(ah, AR_PHY_65NM_RXRF_GAINSTAGES(chain), + AR_PHY_65NM_RXRF_GAINSTAGES_LNA5G_GAIN_OVR, 0x0); + + REG_RMW_FIELD(ah, AR_PHY_65NM_RXTX2(chain), + AR_PHY_65NM_RXTX2_RXON_OVR, 0x1); + REG_RMW_FIELD(ah, AR_PHY_65NM_RXTX2(chain), + AR_PHY_65NM_RXTX2_RXON, 0x0); + + REG_RMW_FIELD(ah, AR_PHY_65NM_RXRF_AGC(chain), + AR_PHY_65NM_RXRF_AGC_AGC_OVERRIDE, 0x1); + REG_RMW_FIELD(ah, AR_PHY_65NM_RXRF_AGC(chain), + AR_PHY_65NM_RXRF_AGC_AGC_ON_OVR, 0x1); + REG_RMW_FIELD(ah, AR_PHY_65NM_RXRF_AGC(chain), + AR_PHY_65NM_RXRF_AGC_AGC_CAL_OVR, 0x1); + if (is_2g) + REG_RMW_FIELD(ah, AR_PHY_65NM_RXRF_AGC(chain), + AR_PHY_65NM_RXRF_AGC_AGC2G_DBDAC_OVR, 0x0); + else + REG_RMW_FIELD(ah, AR_PHY_65NM_RXRF_AGC(chain), + AR_PHY_65NM_RXRF_AGC_AGC5G_DBDAC_OVR, 0x0); + + for (i = 6; i > 0; i--) { + offset[i] = BIT(i - 1); + test = total + offset[i]; + + if (is_2g) + REG_RMW_FIELD(ah, AR_PHY_65NM_RXRF_AGC(chain), + AR_PHY_65NM_RXRF_AGC_AGC2G_CALDAC_OVR, + test); + else + REG_RMW_FIELD(ah, AR_PHY_65NM_RXRF_AGC(chain), + AR_PHY_65NM_RXRF_AGC_AGC5G_CALDAC_OVR, + test); + udelay(100); + agc_out = REG_READ_FIELD(ah, AR_PHY_65NM_RXRF_AGC(chain), + AR_PHY_65NM_RXRF_AGC_AGC_OUT); + offset[i] = (agc_out) ? 0 : 1; + total += (offset[i] << (i - 1)); + } + + if (is_2g) + REG_RMW_FIELD(ah, AR_PHY_65NM_RXRF_AGC(chain), + AR_PHY_65NM_RXRF_AGC_AGC2G_CALDAC_OVR, total); + else + REG_RMW_FIELD(ah, AR_PHY_65NM_RXRF_AGC(chain), + AR_PHY_65NM_RXRF_AGC_AGC5G_CALDAC_OVR, total); + + REG_RMW_FIELD(ah, AR_PHY_65NM_RXRF_GAINSTAGES(chain), + AR_PHY_65NM_RXRF_GAINSTAGES_RX_OVERRIDE, 0); + REG_RMW_FIELD(ah, AR_PHY_65NM_RXTX2(chain), + AR_PHY_65NM_RXTX2_RXON_OVR, 0); + REG_RMW_FIELD(ah, AR_PHY_65NM_RXRF_AGC(chain), + AR_PHY_65NM_RXRF_AGC_AGC_CAL_OVR, 0); +} + +static void ar9003_hw_do_manual_peak_cal(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + int i; + + if (!AR_SREV_9462(ah) && !AR_SREV_9565(ah) && !AR_SREV_9485(ah)) + return; + + for (i = 0; i < AR9300_MAX_CHAINS; i++) { + if (!(ah->rxchainmask & (1 << i))) + continue; + ar9003_hw_manual_peak_cal(ah, i, IS_CHAN_2GHZ(chan)); + } +} + +static void ar9003_hw_cl_cal_post_proc(struct ath_hw *ah, bool is_reusable) +{ + u32 cl_idx[AR9300_MAX_CHAINS] = { AR_PHY_CL_TAB_0, + AR_PHY_CL_TAB_1, + AR_PHY_CL_TAB_2 }; + struct ath9k_hw_cal_data *caldata = ah->caldata; + bool txclcal_done = false; + int i, j; + + if (!caldata || !(ah->enabled_cals & TX_CL_CAL)) + return; + + txclcal_done = !!(REG_READ(ah, AR_PHY_AGC_CONTROL) & + AR_PHY_AGC_CONTROL_CLC_SUCCESS); + + if (caldata->done_txclcal_once) { + for (i = 0; i < AR9300_MAX_CHAINS; i++) { + if (!(ah->txchainmask & (1 << i))) + continue; + for (j = 0; j < MAX_CL_TAB_ENTRY; j++) + REG_WRITE(ah, CL_TAB_ENTRY(cl_idx[i]), + caldata->tx_clcal[i][j]); + } + } else if (is_reusable && txclcal_done) { + for (i = 0; i < AR9300_MAX_CHAINS; i++) { + if (!(ah->txchainmask & (1 << i))) + continue; + for (j = 0; j < MAX_CL_TAB_ENTRY; j++) + caldata->tx_clcal[i][j] = + REG_READ(ah, CL_TAB_ENTRY(cl_idx[i])); + } + caldata->done_txclcal_once = true; + } +} + +static bool ar9003_hw_init_cal(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_hw_cal_data *caldata = ah->caldata; + bool txiqcal_done = false; + bool is_reusable = true, status = true; + bool run_rtt_cal = false, run_agc_cal, sep_iq_cal = false; + bool rtt = !!(ah->caps.hw_caps & ATH9K_HW_CAP_RTT); + u32 agc_ctrl = 0, agc_supp_cals = AR_PHY_AGC_CONTROL_OFFSET_CAL | + AR_PHY_AGC_CONTROL_FLTR_CAL | + AR_PHY_AGC_CONTROL_PKDET_CAL; + + /* Use chip chainmask only for calibration */ + ar9003_hw_set_chain_masks(ah, ah->caps.rx_chainmask, ah->caps.tx_chainmask); + + if (rtt) { + if (!ar9003_hw_rtt_restore(ah, chan)) + run_rtt_cal = true; + + if (run_rtt_cal) + ath_dbg(common, CALIBRATE, "RTT calibration to be done\n"); + } + + run_agc_cal = run_rtt_cal; + + if (run_rtt_cal) { + ar9003_hw_rtt_enable(ah); + ar9003_hw_rtt_set_mask(ah, 0x00); + ar9003_hw_rtt_clear_hist(ah); + } + + if (rtt && !run_rtt_cal) { + agc_ctrl = REG_READ(ah, AR_PHY_AGC_CONTROL); + agc_supp_cals &= agc_ctrl; + agc_ctrl &= ~(AR_PHY_AGC_CONTROL_OFFSET_CAL | + AR_PHY_AGC_CONTROL_FLTR_CAL | + AR_PHY_AGC_CONTROL_PKDET_CAL); + REG_WRITE(ah, AR_PHY_AGC_CONTROL, agc_ctrl); + } + + if (ah->enabled_cals & TX_CL_CAL) { + if (caldata && caldata->done_txclcal_once) + REG_CLR_BIT(ah, AR_PHY_CL_CAL_CTL, + AR_PHY_CL_CAL_ENABLE); + else { + REG_SET_BIT(ah, AR_PHY_CL_CAL_CTL, + AR_PHY_CL_CAL_ENABLE); + run_agc_cal = true; + } + } + + if ((IS_CHAN_HALF_RATE(chan) || IS_CHAN_QUARTER_RATE(chan)) || + !(ah->enabled_cals & TX_IQ_CAL)) + goto skip_tx_iqcal; + + /* Do Tx IQ Calibration */ + REG_RMW_FIELD(ah, AR_PHY_TX_IQCAL_CONTROL_1, + AR_PHY_TX_IQCAL_CONTROL_1_IQCORR_I_Q_COFF_DELPT, + DELPT); + + /* + * For AR9485 or later chips, TxIQ cal runs as part of + * AGC calibration + */ + if (ah->enabled_cals & TX_IQ_ON_AGC_CAL) { + if (caldata && !caldata->done_txiqcal_once) + REG_SET_BIT(ah, AR_PHY_TX_IQCAL_CONTROL_0, + AR_PHY_TX_IQCAL_CONTROL_0_ENABLE_TXIQ_CAL); + else + REG_CLR_BIT(ah, AR_PHY_TX_IQCAL_CONTROL_0, + AR_PHY_TX_IQCAL_CONTROL_0_ENABLE_TXIQ_CAL); + txiqcal_done = run_agc_cal = true; + } else if (caldata && !caldata->done_txiqcal_once) { + run_agc_cal = true; + sep_iq_cal = true; + } + +skip_tx_iqcal: + if (ath9k_hw_mci_is_enabled(ah) && IS_CHAN_2GHZ(chan) && run_agc_cal) + ar9003_mci_init_cal_req(ah, &is_reusable); + + if (sep_iq_cal) { + txiqcal_done = ar9003_hw_tx_iq_cal_run(ah); + REG_WRITE(ah, AR_PHY_ACTIVE, AR_PHY_ACTIVE_DIS); + udelay(5); + REG_WRITE(ah, AR_PHY_ACTIVE, AR_PHY_ACTIVE_EN); + } + + if (run_agc_cal || !(ah->ah_flags & AH_FASTCC)) { + /* Calibrate the AGC */ + REG_WRITE(ah, AR_PHY_AGC_CONTROL, + REG_READ(ah, AR_PHY_AGC_CONTROL) | + AR_PHY_AGC_CONTROL_CAL); + + /* Poll for offset calibration complete */ + status = ath9k_hw_wait(ah, AR_PHY_AGC_CONTROL, + AR_PHY_AGC_CONTROL_CAL, + 0, AH_WAIT_TIMEOUT); + + ar9003_hw_do_manual_peak_cal(ah, chan); + } + + if (ath9k_hw_mci_is_enabled(ah) && IS_CHAN_2GHZ(chan) && run_agc_cal) + ar9003_mci_init_cal_done(ah); + + if (rtt && !run_rtt_cal) { + agc_ctrl |= agc_supp_cals; + REG_WRITE(ah, AR_PHY_AGC_CONTROL, agc_ctrl); + } + + if (!status) { + if (run_rtt_cal) + ar9003_hw_rtt_disable(ah); + + ath_dbg(common, CALIBRATE, + "offset calibration failed to complete in %d ms; noisy environment?\n", + AH_WAIT_TIMEOUT / 1000); + return false; + } + + if (txiqcal_done) + ar9003_hw_tx_iq_cal_post_proc(ah, is_reusable); + else if (caldata && caldata->done_txiqcal_once) + ar9003_hw_tx_iq_cal_reload(ah); + + ar9003_hw_cl_cal_post_proc(ah, is_reusable); + + if (run_rtt_cal && caldata) { + if (is_reusable) { + if (!ath9k_hw_rfbus_req(ah)) + ath_err(ath9k_hw_common(ah), + "Could not stop baseband\n"); + else + ar9003_hw_rtt_fill_hist(ah); + + ath9k_hw_rfbus_done(ah); + } + + ar9003_hw_rtt_disable(ah); + } + + /* Revert chainmask to runtime parameters */ + ar9003_hw_set_chain_masks(ah, ah->rxchainmask, ah->txchainmask); + + /* Initialize list pointers */ + ah->cal_list = ah->cal_list_last = ah->cal_list_curr = NULL; + + INIT_CAL(&ah->iq_caldata); + INSERT_CAL(ah, &ah->iq_caldata); + ath_dbg(common, CALIBRATE, "enabling IQ Calibration\n"); + + /* Initialize current pointer to first element in list */ + ah->cal_list_curr = ah->cal_list; + + if (ah->cal_list_curr) + ath9k_hw_reset_calibration(ah, ah->cal_list_curr); + + if (caldata) + caldata->CalValid = 0; + + return true; +} + +void ar9003_hw_attach_calib_ops(struct ath_hw *ah) +{ + struct ath_hw_private_ops *priv_ops = ath9k_hw_private_ops(ah); + struct ath_hw_ops *ops = ath9k_hw_ops(ah); + + priv_ops->init_cal_settings = ar9003_hw_init_cal_settings; + priv_ops->init_cal = ar9003_hw_init_cal; + priv_ops->setup_calibration = ar9003_hw_setup_calibration; + + ops->calibrate = ar9003_hw_calibrate; +} diff --git a/drivers/net/wireless/ath/ath9k/ar9003_eeprom.c b/drivers/net/wireless/ath/ath9k/ar9003_eeprom.c new file mode 100644 index 0000000..0a72480 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9003_eeprom.c @@ -0,0 +1,5346 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "hw.h" +#include "ar9003_phy.h" +#include "ar9003_eeprom.h" +#include "ar9003_mci.h" + +#define COMP_HDR_LEN 4 +#define COMP_CKSUM_LEN 2 + +#define LE16(x) __constant_cpu_to_le16(x) +#define LE32(x) __constant_cpu_to_le32(x) + +/* Local defines to distinguish between extension and control CTL's */ +#define EXT_ADDITIVE (0x8000) +#define CTL_11A_EXT (CTL_11A | EXT_ADDITIVE) +#define CTL_11G_EXT (CTL_11G | EXT_ADDITIVE) +#define CTL_11B_EXT (CTL_11B | EXT_ADDITIVE) + +#define SUB_NUM_CTL_MODES_AT_5G_40 2 /* excluding HT40, EXT-OFDM */ +#define SUB_NUM_CTL_MODES_AT_2G_40 3 /* excluding HT40, EXT-OFDM, EXT-CCK */ + +#define CTL(_tpower, _flag) ((_tpower) | ((_flag) << 6)) + +#define EEPROM_DATA_LEN_9485 1088 + +static int ar9003_hw_power_interpolate(int32_t x, + int32_t *px, int32_t *py, u_int16_t np); + +static const struct ar9300_eeprom ar9300_default = { + .eepromVersion = 2, + .templateVersion = 2, + .macAddr = {0, 2, 3, 4, 5, 6}, + .custData = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + .baseEepHeader = { + .regDmn = { LE16(0), LE16(0x1f) }, + .txrxMask = 0x77, /* 4 bits tx and 4 bits rx */ + .opCapFlags = { + .opFlags = AR5416_OPFLAGS_11G | AR5416_OPFLAGS_11A, + .eepMisc = 0, + }, + .rfSilent = 0, + .blueToothOptions = 0, + .deviceCap = 0, + .deviceType = 5, /* takes lower byte in eeprom location */ + .pwrTableOffset = AR9300_PWR_TABLE_OFFSET, + .params_for_tuning_caps = {0, 0}, + .featureEnable = 0x0c, + /* + * bit0 - enable tx temp comp - disabled + * bit1 - enable tx volt comp - disabled + * bit2 - enable fastClock - enabled + * bit3 - enable doubling - enabled + * bit4 - enable internal regulator - disabled + * bit5 - enable pa predistortion - disabled + */ + .miscConfiguration = 0, /* bit0 - turn down drivestrength */ + .eepromWriteEnableGpio = 3, + .wlanDisableGpio = 0, + .wlanLedGpio = 8, + .rxBandSelectGpio = 0xff, + .txrxgain = 0, + .swreg = 0, + }, + .modalHeader2G = { + /* ar9300_modal_eep_header 2g */ + /* 4 idle,t1,t2,b(4 bits per setting) */ + .antCtrlCommon = LE32(0x110), + /* 4 ra1l1, ra2l1, ra1l2, ra2l2, ra12 */ + .antCtrlCommon2 = LE32(0x22222), + + /* + * antCtrlChain[AR9300_MAX_CHAINS]; 6 idle, t, r, + * rx1, rx12, b (2 bits each) + */ + .antCtrlChain = { LE16(0x150), LE16(0x150), LE16(0x150) }, + + /* + * xatten1DB[AR9300_MAX_CHAINS]; 3 xatten1_db + * for ar9280 (0xa20c/b20c 5:0) + */ + .xatten1DB = {0, 0, 0}, + + /* + * xatten1Margin[AR9300_MAX_CHAINS]; 3 xatten1_margin + * for ar9280 (0xa20c/b20c 16:12 + */ + .xatten1Margin = {0, 0, 0}, + .tempSlope = 36, + .voltSlope = 0, + + /* + * spurChans[OSPREY_EEPROM_MODAL_SPURS]; spur + * channels in usual fbin coding format + */ + .spurChans = {0, 0, 0, 0, 0}, + + /* + * noiseFloorThreshCh[AR9300_MAX_CHAINS]; 3 Check + * if the register is per chain + */ + .noiseFloorThreshCh = {-1, 0, 0}, + .reserved = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + .quick_drop = 0, + .xpaBiasLvl = 0, + .txFrameToDataStart = 0x0e, + .txFrameToPaOn = 0x0e, + .txClip = 3, /* 4 bits tx_clip, 4 bits dac_scale_cck */ + .antennaGain = 0, + .switchSettling = 0x2c, + .adcDesiredSize = -30, + .txEndToXpaOff = 0, + .txEndToRxOn = 0x2, + .txFrameToXpaOn = 0xe, + .thresh62 = 28, + .papdRateMaskHt20 = LE32(0x0cf0e0e0), + .papdRateMaskHt40 = LE32(0x6cf0e0e0), + .xlna_bias_strength = 0, + .futureModal = { + 0, 0, 0, 0, 0, 0, 0, + }, + }, + .base_ext1 = { + .ant_div_control = 0, + .future = {0, 0, 0}, + .tempslopextension = {0, 0, 0, 0, 0, 0, 0, 0} + }, + .calFreqPier2G = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1), + }, + /* ar9300_cal_data_per_freq_op_loop 2g */ + .calPierData2G = { + { {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0} }, + { {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0} }, + { {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0} }, + }, + .calTarget_freqbin_Cck = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2484, 1), + }, + .calTarget_freqbin_2G = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1) + }, + .calTarget_freqbin_2GHT20 = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1) + }, + .calTarget_freqbin_2GHT40 = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1) + }, + .calTargetPowerCck = { + /* 1L-5L,5S,11L,11S */ + { {36, 36, 36, 36} }, + { {36, 36, 36, 36} }, + }, + .calTargetPower2G = { + /* 6-24,36,48,54 */ + { {32, 32, 28, 24} }, + { {32, 32, 28, 24} }, + { {32, 32, 28, 24} }, + }, + .calTargetPower2GHT20 = { + { {32, 32, 32, 32, 28, 20, 32, 32, 28, 20, 32, 32, 28, 20} }, + { {32, 32, 32, 32, 28, 20, 32, 32, 28, 20, 32, 32, 28, 20} }, + { {32, 32, 32, 32, 28, 20, 32, 32, 28, 20, 32, 32, 28, 20} }, + }, + .calTargetPower2GHT40 = { + { {32, 32, 32, 32, 28, 20, 32, 32, 28, 20, 32, 32, 28, 20} }, + { {32, 32, 32, 32, 28, 20, 32, 32, 28, 20, 32, 32, 28, 20} }, + { {32, 32, 32, 32, 28, 20, 32, 32, 28, 20, 32, 32, 28, 20} }, + }, + .ctlIndex_2G = { + 0x11, 0x12, 0x15, 0x17, 0x41, 0x42, + 0x45, 0x47, 0x31, 0x32, 0x35, 0x37, + }, + .ctl_freqbin_2G = { + { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2417, 1), + FREQ2FBIN(2457, 1), + FREQ2FBIN(2462, 1) + }, + { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2417, 1), + FREQ2FBIN(2462, 1), + 0xFF, + }, + + { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2417, 1), + FREQ2FBIN(2462, 1), + 0xFF, + }, + { + FREQ2FBIN(2422, 1), + FREQ2FBIN(2427, 1), + FREQ2FBIN(2447, 1), + FREQ2FBIN(2452, 1) + }, + + { + /* Data[4].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[4].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[4].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + /* Data[4].ctlEdges[3].bChannel */ FREQ2FBIN(2484, 1), + }, + + { + /* Data[5].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[5].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[5].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + 0, + }, + + { + /* Data[6].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[6].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + FREQ2FBIN(2472, 1), + 0, + }, + + { + /* Data[7].ctlEdges[0].bChannel */ FREQ2FBIN(2422, 1), + /* Data[7].ctlEdges[1].bChannel */ FREQ2FBIN(2427, 1), + /* Data[7].ctlEdges[2].bChannel */ FREQ2FBIN(2447, 1), + /* Data[7].ctlEdges[3].bChannel */ FREQ2FBIN(2462, 1), + }, + + { + /* Data[8].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[8].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[8].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + }, + + { + /* Data[9].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[9].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[9].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + 0 + }, + + { + /* Data[10].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[10].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[10].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + 0 + }, + + { + /* Data[11].ctlEdges[0].bChannel */ FREQ2FBIN(2422, 1), + /* Data[11].ctlEdges[1].bChannel */ FREQ2FBIN(2427, 1), + /* Data[11].ctlEdges[2].bChannel */ FREQ2FBIN(2447, 1), + /* Data[11].ctlEdges[3].bChannel */ FREQ2FBIN(2462, 1), + } + }, + .ctlPowerData_2G = { + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 1) } }, + + { { CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + + { { CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 1) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 1) } }, + }, + .modalHeader5G = { + /* 4 idle,t1,t2,b (4 bits per setting) */ + .antCtrlCommon = LE32(0x110), + /* 4 ra1l1, ra2l1, ra1l2,ra2l2,ra12 */ + .antCtrlCommon2 = LE32(0x22222), + /* antCtrlChain 6 idle, t,r,rx1,rx12,b (2 bits each) */ + .antCtrlChain = { + LE16(0x000), LE16(0x000), LE16(0x000), + }, + /* xatten1DB 3 xatten1_db for AR9280 (0xa20c/b20c 5:0) */ + .xatten1DB = {0, 0, 0}, + + /* + * xatten1Margin[AR9300_MAX_CHAINS]; 3 xatten1_margin + * for merlin (0xa20c/b20c 16:12 + */ + .xatten1Margin = {0, 0, 0}, + .tempSlope = 68, + .voltSlope = 0, + /* spurChans spur channels in usual fbin coding format */ + .spurChans = {0, 0, 0, 0, 0}, + /* noiseFloorThreshCh Check if the register is per chain */ + .noiseFloorThreshCh = {-1, 0, 0}, + .reserved = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + .quick_drop = 0, + .xpaBiasLvl = 0, + .txFrameToDataStart = 0x0e, + .txFrameToPaOn = 0x0e, + .txClip = 3, /* 4 bits tx_clip, 4 bits dac_scale_cck */ + .antennaGain = 0, + .switchSettling = 0x2d, + .adcDesiredSize = -30, + .txEndToXpaOff = 0, + .txEndToRxOn = 0x2, + .txFrameToXpaOn = 0xe, + .thresh62 = 28, + .papdRateMaskHt20 = LE32(0x0c80c080), + .papdRateMaskHt40 = LE32(0x0080c080), + .xlna_bias_strength = 0, + .futureModal = { + 0, 0, 0, 0, 0, 0, 0, + }, + }, + .base_ext2 = { + .tempSlopeLow = 0, + .tempSlopeHigh = 0, + .xatten1DBLow = {0, 0, 0}, + .xatten1MarginLow = {0, 0, 0}, + .xatten1DBHigh = {0, 0, 0}, + .xatten1MarginHigh = {0, 0, 0} + }, + .calFreqPier5G = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5220, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5600, 0), + FREQ2FBIN(5725, 0), + FREQ2FBIN(5825, 0) + }, + .calPierData5G = { + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }, + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }, + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }, + + }, + .calTarget_freqbin_5G = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5220, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5600, 0), + FREQ2FBIN(5725, 0), + FREQ2FBIN(5825, 0) + }, + .calTarget_freqbin_5GHT20 = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5240, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5700, 0), + FREQ2FBIN(5745, 0), + FREQ2FBIN(5725, 0), + FREQ2FBIN(5825, 0) + }, + .calTarget_freqbin_5GHT40 = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5240, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5700, 0), + FREQ2FBIN(5745, 0), + FREQ2FBIN(5725, 0), + FREQ2FBIN(5825, 0) + }, + .calTargetPower5G = { + /* 6-24,36,48,54 */ + { {20, 20, 20, 10} }, + { {20, 20, 20, 10} }, + { {20, 20, 20, 10} }, + { {20, 20, 20, 10} }, + { {20, 20, 20, 10} }, + { {20, 20, 20, 10} }, + { {20, 20, 20, 10} }, + { {20, 20, 20, 10} }, + }, + .calTargetPower5GHT20 = { + /* + * 0_8_16,1-3_9-11_17-19, + * 4,5,6,7,12,13,14,15,20,21,22,23 + */ + { {20, 20, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 0, 0} }, + { {20, 20, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 0, 0} }, + { {20, 20, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 0, 0} }, + { {20, 20, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 0, 0} }, + { {20, 20, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 0, 0} }, + { {20, 20, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 0, 0} }, + { {20, 20, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 0, 0} }, + { {20, 20, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 0, 0} }, + }, + .calTargetPower5GHT40 = { + /* + * 0_8_16,1-3_9-11_17-19, + * 4,5,6,7,12,13,14,15,20,21,22,23 + */ + { {20, 20, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 0, 0} }, + { {20, 20, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 0, 0} }, + { {20, 20, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 0, 0} }, + { {20, 20, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 0, 0} }, + { {20, 20, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 0, 0} }, + { {20, 20, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 0, 0} }, + { {20, 20, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 0, 0} }, + { {20, 20, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 0, 0} }, + }, + .ctlIndex_5G = { + 0x10, 0x16, 0x18, 0x40, 0x46, + 0x48, 0x30, 0x36, 0x38 + }, + .ctl_freqbin_5G = { + { + /* Data[0].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[0].ctlEdges[1].bChannel */ FREQ2FBIN(5260, 0), + /* Data[0].ctlEdges[2].bChannel */ FREQ2FBIN(5280, 0), + /* Data[0].ctlEdges[3].bChannel */ FREQ2FBIN(5500, 0), + /* Data[0].ctlEdges[4].bChannel */ FREQ2FBIN(5600, 0), + /* Data[0].ctlEdges[5].bChannel */ FREQ2FBIN(5700, 0), + /* Data[0].ctlEdges[6].bChannel */ FREQ2FBIN(5745, 0), + /* Data[0].ctlEdges[7].bChannel */ FREQ2FBIN(5825, 0) + }, + { + /* Data[1].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[1].ctlEdges[1].bChannel */ FREQ2FBIN(5260, 0), + /* Data[1].ctlEdges[2].bChannel */ FREQ2FBIN(5280, 0), + /* Data[1].ctlEdges[3].bChannel */ FREQ2FBIN(5500, 0), + /* Data[1].ctlEdges[4].bChannel */ FREQ2FBIN(5520, 0), + /* Data[1].ctlEdges[5].bChannel */ FREQ2FBIN(5700, 0), + /* Data[1].ctlEdges[6].bChannel */ FREQ2FBIN(5745, 0), + /* Data[1].ctlEdges[7].bChannel */ FREQ2FBIN(5825, 0) + }, + + { + /* Data[2].ctlEdges[0].bChannel */ FREQ2FBIN(5190, 0), + /* Data[2].ctlEdges[1].bChannel */ FREQ2FBIN(5230, 0), + /* Data[2].ctlEdges[2].bChannel */ FREQ2FBIN(5270, 0), + /* Data[2].ctlEdges[3].bChannel */ FREQ2FBIN(5310, 0), + /* Data[2].ctlEdges[4].bChannel */ FREQ2FBIN(5510, 0), + /* Data[2].ctlEdges[5].bChannel */ FREQ2FBIN(5550, 0), + /* Data[2].ctlEdges[6].bChannel */ FREQ2FBIN(5670, 0), + /* Data[2].ctlEdges[7].bChannel */ FREQ2FBIN(5755, 0) + }, + + { + /* Data[3].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[3].ctlEdges[1].bChannel */ FREQ2FBIN(5200, 0), + /* Data[3].ctlEdges[2].bChannel */ FREQ2FBIN(5260, 0), + /* Data[3].ctlEdges[3].bChannel */ FREQ2FBIN(5320, 0), + /* Data[3].ctlEdges[4].bChannel */ FREQ2FBIN(5500, 0), + /* Data[3].ctlEdges[5].bChannel */ FREQ2FBIN(5700, 0), + /* Data[3].ctlEdges[6].bChannel */ 0xFF, + /* Data[3].ctlEdges[7].bChannel */ 0xFF, + }, + + { + /* Data[4].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[4].ctlEdges[1].bChannel */ FREQ2FBIN(5260, 0), + /* Data[4].ctlEdges[2].bChannel */ FREQ2FBIN(5500, 0), + /* Data[4].ctlEdges[3].bChannel */ FREQ2FBIN(5700, 0), + /* Data[4].ctlEdges[4].bChannel */ 0xFF, + /* Data[4].ctlEdges[5].bChannel */ 0xFF, + /* Data[4].ctlEdges[6].bChannel */ 0xFF, + /* Data[4].ctlEdges[7].bChannel */ 0xFF, + }, + + { + /* Data[5].ctlEdges[0].bChannel */ FREQ2FBIN(5190, 0), + /* Data[5].ctlEdges[1].bChannel */ FREQ2FBIN(5270, 0), + /* Data[5].ctlEdges[2].bChannel */ FREQ2FBIN(5310, 0), + /* Data[5].ctlEdges[3].bChannel */ FREQ2FBIN(5510, 0), + /* Data[5].ctlEdges[4].bChannel */ FREQ2FBIN(5590, 0), + /* Data[5].ctlEdges[5].bChannel */ FREQ2FBIN(5670, 0), + /* Data[5].ctlEdges[6].bChannel */ 0xFF, + /* Data[5].ctlEdges[7].bChannel */ 0xFF + }, + + { + /* Data[6].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[6].ctlEdges[1].bChannel */ FREQ2FBIN(5200, 0), + /* Data[6].ctlEdges[2].bChannel */ FREQ2FBIN(5220, 0), + /* Data[6].ctlEdges[3].bChannel */ FREQ2FBIN(5260, 0), + /* Data[6].ctlEdges[4].bChannel */ FREQ2FBIN(5500, 0), + /* Data[6].ctlEdges[5].bChannel */ FREQ2FBIN(5600, 0), + /* Data[6].ctlEdges[6].bChannel */ FREQ2FBIN(5700, 0), + /* Data[6].ctlEdges[7].bChannel */ FREQ2FBIN(5745, 0) + }, + + { + /* Data[7].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[7].ctlEdges[1].bChannel */ FREQ2FBIN(5260, 0), + /* Data[7].ctlEdges[2].bChannel */ FREQ2FBIN(5320, 0), + /* Data[7].ctlEdges[3].bChannel */ FREQ2FBIN(5500, 0), + /* Data[7].ctlEdges[4].bChannel */ FREQ2FBIN(5560, 0), + /* Data[7].ctlEdges[5].bChannel */ FREQ2FBIN(5700, 0), + /* Data[7].ctlEdges[6].bChannel */ FREQ2FBIN(5745, 0), + /* Data[7].ctlEdges[7].bChannel */ FREQ2FBIN(5825, 0) + }, + + { + /* Data[8].ctlEdges[0].bChannel */ FREQ2FBIN(5190, 0), + /* Data[8].ctlEdges[1].bChannel */ FREQ2FBIN(5230, 0), + /* Data[8].ctlEdges[2].bChannel */ FREQ2FBIN(5270, 0), + /* Data[8].ctlEdges[3].bChannel */ FREQ2FBIN(5510, 0), + /* Data[8].ctlEdges[4].bChannel */ FREQ2FBIN(5550, 0), + /* Data[8].ctlEdges[5].bChannel */ FREQ2FBIN(5670, 0), + /* Data[8].ctlEdges[6].bChannel */ FREQ2FBIN(5755, 0), + /* Data[8].ctlEdges[7].bChannel */ FREQ2FBIN(5795, 0) + } + }, + .ctlPowerData_5G = { + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + } + }, + { + { + CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + } + }, + { + { + CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 0), + CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + CTL(60, 0), CTL(60, 0), CTL(60, 0), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 0), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 0), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 0), CTL(60, 1), + } + }, + } +}; + +static const struct ar9300_eeprom ar9300_x113 = { + .eepromVersion = 2, + .templateVersion = 6, + .macAddr = {0x00, 0x03, 0x7f, 0x0, 0x0, 0x0}, + .custData = {"x113-023-f0000"}, + .baseEepHeader = { + .regDmn = { LE16(0), LE16(0x1f) }, + .txrxMask = 0x77, /* 4 bits tx and 4 bits rx */ + .opCapFlags = { + .opFlags = AR5416_OPFLAGS_11A, + .eepMisc = 0, + }, + .rfSilent = 0, + .blueToothOptions = 0, + .deviceCap = 0, + .deviceType = 5, /* takes lower byte in eeprom location */ + .pwrTableOffset = AR9300_PWR_TABLE_OFFSET, + .params_for_tuning_caps = {0, 0}, + .featureEnable = 0x0d, + /* + * bit0 - enable tx temp comp - disabled + * bit1 - enable tx volt comp - disabled + * bit2 - enable fastClock - enabled + * bit3 - enable doubling - enabled + * bit4 - enable internal regulator - disabled + * bit5 - enable pa predistortion - disabled + */ + .miscConfiguration = 0, /* bit0 - turn down drivestrength */ + .eepromWriteEnableGpio = 6, + .wlanDisableGpio = 0, + .wlanLedGpio = 8, + .rxBandSelectGpio = 0xff, + .txrxgain = 0x21, + .swreg = 0, + }, + .modalHeader2G = { + /* ar9300_modal_eep_header 2g */ + /* 4 idle,t1,t2,b(4 bits per setting) */ + .antCtrlCommon = LE32(0x110), + /* 4 ra1l1, ra2l1, ra1l2, ra2l2, ra12 */ + .antCtrlCommon2 = LE32(0x44444), + + /* + * antCtrlChain[AR9300_MAX_CHAINS]; 6 idle, t, r, + * rx1, rx12, b (2 bits each) + */ + .antCtrlChain = { LE16(0x150), LE16(0x150), LE16(0x150) }, + + /* + * xatten1DB[AR9300_MAX_CHAINS]; 3 xatten1_db + * for ar9280 (0xa20c/b20c 5:0) + */ + .xatten1DB = {0, 0, 0}, + + /* + * xatten1Margin[AR9300_MAX_CHAINS]; 3 xatten1_margin + * for ar9280 (0xa20c/b20c 16:12 + */ + .xatten1Margin = {0, 0, 0}, + .tempSlope = 25, + .voltSlope = 0, + + /* + * spurChans[OSPREY_EEPROM_MODAL_SPURS]; spur + * channels in usual fbin coding format + */ + .spurChans = {FREQ2FBIN(2464, 1), 0, 0, 0, 0}, + + /* + * noiseFloorThreshCh[AR9300_MAX_CHAINS]; 3 Check + * if the register is per chain + */ + .noiseFloorThreshCh = {-1, 0, 0}, + .reserved = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + .quick_drop = 0, + .xpaBiasLvl = 0, + .txFrameToDataStart = 0x0e, + .txFrameToPaOn = 0x0e, + .txClip = 3, /* 4 bits tx_clip, 4 bits dac_scale_cck */ + .antennaGain = 0, + .switchSettling = 0x2c, + .adcDesiredSize = -30, + .txEndToXpaOff = 0, + .txEndToRxOn = 0x2, + .txFrameToXpaOn = 0xe, + .thresh62 = 28, + .papdRateMaskHt20 = LE32(0x0c80c080), + .papdRateMaskHt40 = LE32(0x0080c080), + .xlna_bias_strength = 0, + .futureModal = { + 0, 0, 0, 0, 0, 0, 0, + }, + }, + .base_ext1 = { + .ant_div_control = 0, + .future = {0, 0, 0}, + .tempslopextension = {0, 0, 0, 0, 0, 0, 0, 0} + }, + .calFreqPier2G = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1), + }, + /* ar9300_cal_data_per_freq_op_loop 2g */ + .calPierData2G = { + { {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0} }, + { {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0} }, + { {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0} }, + }, + .calTarget_freqbin_Cck = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2472, 1), + }, + .calTarget_freqbin_2G = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1) + }, + .calTarget_freqbin_2GHT20 = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1) + }, + .calTarget_freqbin_2GHT40 = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1) + }, + .calTargetPowerCck = { + /* 1L-5L,5S,11L,11S */ + { {34, 34, 34, 34} }, + { {34, 34, 34, 34} }, + }, + .calTargetPower2G = { + /* 6-24,36,48,54 */ + { {34, 34, 32, 32} }, + { {34, 34, 32, 32} }, + { {34, 34, 32, 32} }, + }, + .calTargetPower2GHT20 = { + { {32, 32, 32, 32, 32, 28, 32, 32, 30, 28, 0, 0, 0, 0} }, + { {32, 32, 32, 32, 32, 28, 32, 32, 30, 28, 0, 0, 0, 0} }, + { {32, 32, 32, 32, 32, 28, 32, 32, 30, 28, 0, 0, 0, 0} }, + }, + .calTargetPower2GHT40 = { + { {30, 30, 30, 30, 30, 28, 30, 30, 28, 26, 0, 0, 0, 0} }, + { {30, 30, 30, 30, 30, 28, 30, 30, 28, 26, 0, 0, 0, 0} }, + { {30, 30, 30, 30, 30, 28, 30, 30, 28, 26, 0, 0, 0, 0} }, + }, + .ctlIndex_2G = { + 0x11, 0x12, 0x15, 0x17, 0x41, 0x42, + 0x45, 0x47, 0x31, 0x32, 0x35, 0x37, + }, + .ctl_freqbin_2G = { + { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2417, 1), + FREQ2FBIN(2457, 1), + FREQ2FBIN(2462, 1) + }, + { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2417, 1), + FREQ2FBIN(2462, 1), + 0xFF, + }, + + { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2417, 1), + FREQ2FBIN(2462, 1), + 0xFF, + }, + { + FREQ2FBIN(2422, 1), + FREQ2FBIN(2427, 1), + FREQ2FBIN(2447, 1), + FREQ2FBIN(2452, 1) + }, + + { + /* Data[4].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[4].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[4].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + /* Data[4].ctlEdges[3].bChannel */ FREQ2FBIN(2484, 1), + }, + + { + /* Data[5].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[5].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[5].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + 0, + }, + + { + /* Data[6].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[6].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + FREQ2FBIN(2472, 1), + 0, + }, + + { + /* Data[7].ctlEdges[0].bChannel */ FREQ2FBIN(2422, 1), + /* Data[7].ctlEdges[1].bChannel */ FREQ2FBIN(2427, 1), + /* Data[7].ctlEdges[2].bChannel */ FREQ2FBIN(2447, 1), + /* Data[7].ctlEdges[3].bChannel */ FREQ2FBIN(2462, 1), + }, + + { + /* Data[8].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[8].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[8].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + }, + + { + /* Data[9].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[9].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[9].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + 0 + }, + + { + /* Data[10].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[10].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[10].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + 0 + }, + + { + /* Data[11].ctlEdges[0].bChannel */ FREQ2FBIN(2422, 1), + /* Data[11].ctlEdges[1].bChannel */ FREQ2FBIN(2427, 1), + /* Data[11].ctlEdges[2].bChannel */ FREQ2FBIN(2447, 1), + /* Data[11].ctlEdges[3].bChannel */ FREQ2FBIN(2462, 1), + } + }, + .ctlPowerData_2G = { + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 1) } }, + + { { CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + + { { CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 1) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 1) } }, + }, + .modalHeader5G = { + /* 4 idle,t1,t2,b (4 bits per setting) */ + .antCtrlCommon = LE32(0x220), + /* 4 ra1l1, ra2l1, ra1l2,ra2l2,ra12 */ + .antCtrlCommon2 = LE32(0x11111), + /* antCtrlChain 6 idle, t,r,rx1,rx12,b (2 bits each) */ + .antCtrlChain = { + LE16(0x150), LE16(0x150), LE16(0x150), + }, + /* xatten1DB 3 xatten1_db for AR9280 (0xa20c/b20c 5:0) */ + .xatten1DB = {0, 0, 0}, + + /* + * xatten1Margin[AR9300_MAX_CHAINS]; 3 xatten1_margin + * for merlin (0xa20c/b20c 16:12 + */ + .xatten1Margin = {0, 0, 0}, + .tempSlope = 68, + .voltSlope = 0, + /* spurChans spur channels in usual fbin coding format */ + .spurChans = {FREQ2FBIN(5500, 0), 0, 0, 0, 0}, + /* noiseFloorThreshCh Check if the register is per chain */ + .noiseFloorThreshCh = {-1, 0, 0}, + .reserved = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + .quick_drop = 0, + .xpaBiasLvl = 0xf, + .txFrameToDataStart = 0x0e, + .txFrameToPaOn = 0x0e, + .txClip = 3, /* 4 bits tx_clip, 4 bits dac_scale_cck */ + .antennaGain = 0, + .switchSettling = 0x2d, + .adcDesiredSize = -30, + .txEndToXpaOff = 0, + .txEndToRxOn = 0x2, + .txFrameToXpaOn = 0xe, + .thresh62 = 28, + .papdRateMaskHt20 = LE32(0x0cf0e0e0), + .papdRateMaskHt40 = LE32(0x6cf0e0e0), + .xlna_bias_strength = 0, + .futureModal = { + 0, 0, 0, 0, 0, 0, 0, + }, + }, + .base_ext2 = { + .tempSlopeLow = 72, + .tempSlopeHigh = 105, + .xatten1DBLow = {0, 0, 0}, + .xatten1MarginLow = {0, 0, 0}, + .xatten1DBHigh = {0, 0, 0}, + .xatten1MarginHigh = {0, 0, 0} + }, + .calFreqPier5G = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5240, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5600, 0), + FREQ2FBIN(5745, 0), + FREQ2FBIN(5785, 0) + }, + .calPierData5G = { + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }, + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }, + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }, + + }, + .calTarget_freqbin_5G = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5220, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5600, 0), + FREQ2FBIN(5745, 0), + FREQ2FBIN(5785, 0) + }, + .calTarget_freqbin_5GHT20 = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5240, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5700, 0), + FREQ2FBIN(5745, 0), + FREQ2FBIN(5825, 0) + }, + .calTarget_freqbin_5GHT40 = { + FREQ2FBIN(5190, 0), + FREQ2FBIN(5230, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5410, 0), + FREQ2FBIN(5510, 0), + FREQ2FBIN(5670, 0), + FREQ2FBIN(5755, 0), + FREQ2FBIN(5825, 0) + }, + .calTargetPower5G = { + /* 6-24,36,48,54 */ + { {42, 40, 40, 34} }, + { {42, 40, 40, 34} }, + { {42, 40, 40, 34} }, + { {42, 40, 40, 34} }, + { {42, 40, 40, 34} }, + { {42, 40, 40, 34} }, + { {42, 40, 40, 34} }, + { {42, 40, 40, 34} }, + }, + .calTargetPower5GHT20 = { + /* + * 0_8_16,1-3_9-11_17-19, + * 4,5,6,7,12,13,14,15,20,21,22,23 + */ + { {40, 40, 40, 40, 32, 28, 40, 40, 32, 28, 40, 40, 32, 20} }, + { {40, 40, 40, 40, 32, 28, 40, 40, 32, 28, 40, 40, 32, 20} }, + { {40, 40, 40, 40, 32, 28, 40, 40, 32, 28, 40, 40, 32, 20} }, + { {40, 40, 40, 40, 32, 28, 40, 40, 32, 28, 40, 40, 32, 20} }, + { {40, 40, 40, 40, 32, 28, 40, 40, 32, 28, 40, 40, 32, 20} }, + { {40, 40, 40, 40, 32, 28, 40, 40, 32, 28, 40, 40, 32, 20} }, + { {38, 38, 38, 38, 32, 28, 38, 38, 32, 28, 38, 38, 32, 26} }, + { {36, 36, 36, 36, 32, 28, 36, 36, 32, 28, 36, 36, 32, 26} }, + }, + .calTargetPower5GHT40 = { + /* + * 0_8_16,1-3_9-11_17-19, + * 4,5,6,7,12,13,14,15,20,21,22,23 + */ + { {40, 40, 40, 38, 30, 26, 40, 40, 30, 26, 40, 40, 30, 24} }, + { {40, 40, 40, 38, 30, 26, 40, 40, 30, 26, 40, 40, 30, 24} }, + { {40, 40, 40, 38, 30, 26, 40, 40, 30, 26, 40, 40, 30, 24} }, + { {40, 40, 40, 38, 30, 26, 40, 40, 30, 26, 40, 40, 30, 24} }, + { {40, 40, 40, 38, 30, 26, 40, 40, 30, 26, 40, 40, 30, 24} }, + { {40, 40, 40, 38, 30, 26, 40, 40, 30, 26, 40, 40, 30, 24} }, + { {36, 36, 36, 36, 30, 26, 36, 36, 30, 26, 36, 36, 30, 24} }, + { {34, 34, 34, 34, 30, 26, 34, 34, 30, 26, 34, 34, 30, 24} }, + }, + .ctlIndex_5G = { + 0x10, 0x16, 0x18, 0x40, 0x46, + 0x48, 0x30, 0x36, 0x38 + }, + .ctl_freqbin_5G = { + { + /* Data[0].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[0].ctlEdges[1].bChannel */ FREQ2FBIN(5260, 0), + /* Data[0].ctlEdges[2].bChannel */ FREQ2FBIN(5280, 0), + /* Data[0].ctlEdges[3].bChannel */ FREQ2FBIN(5500, 0), + /* Data[0].ctlEdges[4].bChannel */ FREQ2FBIN(5600, 0), + /* Data[0].ctlEdges[5].bChannel */ FREQ2FBIN(5700, 0), + /* Data[0].ctlEdges[6].bChannel */ FREQ2FBIN(5745, 0), + /* Data[0].ctlEdges[7].bChannel */ FREQ2FBIN(5825, 0) + }, + { + /* Data[1].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[1].ctlEdges[1].bChannel */ FREQ2FBIN(5260, 0), + /* Data[1].ctlEdges[2].bChannel */ FREQ2FBIN(5280, 0), + /* Data[1].ctlEdges[3].bChannel */ FREQ2FBIN(5500, 0), + /* Data[1].ctlEdges[4].bChannel */ FREQ2FBIN(5520, 0), + /* Data[1].ctlEdges[5].bChannel */ FREQ2FBIN(5700, 0), + /* Data[1].ctlEdges[6].bChannel */ FREQ2FBIN(5745, 0), + /* Data[1].ctlEdges[7].bChannel */ FREQ2FBIN(5825, 0) + }, + + { + /* Data[2].ctlEdges[0].bChannel */ FREQ2FBIN(5190, 0), + /* Data[2].ctlEdges[1].bChannel */ FREQ2FBIN(5230, 0), + /* Data[2].ctlEdges[2].bChannel */ FREQ2FBIN(5270, 0), + /* Data[2].ctlEdges[3].bChannel */ FREQ2FBIN(5310, 0), + /* Data[2].ctlEdges[4].bChannel */ FREQ2FBIN(5510, 0), + /* Data[2].ctlEdges[5].bChannel */ FREQ2FBIN(5550, 0), + /* Data[2].ctlEdges[6].bChannel */ FREQ2FBIN(5670, 0), + /* Data[2].ctlEdges[7].bChannel */ FREQ2FBIN(5755, 0) + }, + + { + /* Data[3].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[3].ctlEdges[1].bChannel */ FREQ2FBIN(5200, 0), + /* Data[3].ctlEdges[2].bChannel */ FREQ2FBIN(5260, 0), + /* Data[3].ctlEdges[3].bChannel */ FREQ2FBIN(5320, 0), + /* Data[3].ctlEdges[4].bChannel */ FREQ2FBIN(5500, 0), + /* Data[3].ctlEdges[5].bChannel */ FREQ2FBIN(5700, 0), + /* Data[3].ctlEdges[6].bChannel */ 0xFF, + /* Data[3].ctlEdges[7].bChannel */ 0xFF, + }, + + { + /* Data[4].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[4].ctlEdges[1].bChannel */ FREQ2FBIN(5260, 0), + /* Data[4].ctlEdges[2].bChannel */ FREQ2FBIN(5500, 0), + /* Data[4].ctlEdges[3].bChannel */ FREQ2FBIN(5700, 0), + /* Data[4].ctlEdges[4].bChannel */ 0xFF, + /* Data[4].ctlEdges[5].bChannel */ 0xFF, + /* Data[4].ctlEdges[6].bChannel */ 0xFF, + /* Data[4].ctlEdges[7].bChannel */ 0xFF, + }, + + { + /* Data[5].ctlEdges[0].bChannel */ FREQ2FBIN(5190, 0), + /* Data[5].ctlEdges[1].bChannel */ FREQ2FBIN(5270, 0), + /* Data[5].ctlEdges[2].bChannel */ FREQ2FBIN(5310, 0), + /* Data[5].ctlEdges[3].bChannel */ FREQ2FBIN(5510, 0), + /* Data[5].ctlEdges[4].bChannel */ FREQ2FBIN(5590, 0), + /* Data[5].ctlEdges[5].bChannel */ FREQ2FBIN(5670, 0), + /* Data[5].ctlEdges[6].bChannel */ 0xFF, + /* Data[5].ctlEdges[7].bChannel */ 0xFF + }, + + { + /* Data[6].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[6].ctlEdges[1].bChannel */ FREQ2FBIN(5200, 0), + /* Data[6].ctlEdges[2].bChannel */ FREQ2FBIN(5220, 0), + /* Data[6].ctlEdges[3].bChannel */ FREQ2FBIN(5260, 0), + /* Data[6].ctlEdges[4].bChannel */ FREQ2FBIN(5500, 0), + /* Data[6].ctlEdges[5].bChannel */ FREQ2FBIN(5600, 0), + /* Data[6].ctlEdges[6].bChannel */ FREQ2FBIN(5700, 0), + /* Data[6].ctlEdges[7].bChannel */ FREQ2FBIN(5745, 0) + }, + + { + /* Data[7].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[7].ctlEdges[1].bChannel */ FREQ2FBIN(5260, 0), + /* Data[7].ctlEdges[2].bChannel */ FREQ2FBIN(5320, 0), + /* Data[7].ctlEdges[3].bChannel */ FREQ2FBIN(5500, 0), + /* Data[7].ctlEdges[4].bChannel */ FREQ2FBIN(5560, 0), + /* Data[7].ctlEdges[5].bChannel */ FREQ2FBIN(5700, 0), + /* Data[7].ctlEdges[6].bChannel */ FREQ2FBIN(5745, 0), + /* Data[7].ctlEdges[7].bChannel */ FREQ2FBIN(5825, 0) + }, + + { + /* Data[8].ctlEdges[0].bChannel */ FREQ2FBIN(5190, 0), + /* Data[8].ctlEdges[1].bChannel */ FREQ2FBIN(5230, 0), + /* Data[8].ctlEdges[2].bChannel */ FREQ2FBIN(5270, 0), + /* Data[8].ctlEdges[3].bChannel */ FREQ2FBIN(5510, 0), + /* Data[8].ctlEdges[4].bChannel */ FREQ2FBIN(5550, 0), + /* Data[8].ctlEdges[5].bChannel */ FREQ2FBIN(5670, 0), + /* Data[8].ctlEdges[6].bChannel */ FREQ2FBIN(5755, 0), + /* Data[8].ctlEdges[7].bChannel */ FREQ2FBIN(5795, 0) + } + }, + .ctlPowerData_5G = { + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + } + }, + { + { + CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + } + }, + { + { + CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 0), + CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + CTL(60, 0), CTL(60, 0), CTL(60, 0), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 0), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 0), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 0), CTL(60, 1), + } + }, + } +}; + + +static const struct ar9300_eeprom ar9300_h112 = { + .eepromVersion = 2, + .templateVersion = 3, + .macAddr = {0x00, 0x03, 0x7f, 0x0, 0x0, 0x0}, + .custData = {"h112-241-f0000"}, + .baseEepHeader = { + .regDmn = { LE16(0), LE16(0x1f) }, + .txrxMask = 0x77, /* 4 bits tx and 4 bits rx */ + .opCapFlags = { + .opFlags = AR5416_OPFLAGS_11G | AR5416_OPFLAGS_11A, + .eepMisc = 0, + }, + .rfSilent = 0, + .blueToothOptions = 0, + .deviceCap = 0, + .deviceType = 5, /* takes lower byte in eeprom location */ + .pwrTableOffset = AR9300_PWR_TABLE_OFFSET, + .params_for_tuning_caps = {0, 0}, + .featureEnable = 0x0d, + /* + * bit0 - enable tx temp comp - disabled + * bit1 - enable tx volt comp - disabled + * bit2 - enable fastClock - enabled + * bit3 - enable doubling - enabled + * bit4 - enable internal regulator - disabled + * bit5 - enable pa predistortion - disabled + */ + .miscConfiguration = 0, /* bit0 - turn down drivestrength */ + .eepromWriteEnableGpio = 6, + .wlanDisableGpio = 0, + .wlanLedGpio = 8, + .rxBandSelectGpio = 0xff, + .txrxgain = 0x10, + .swreg = 0, + }, + .modalHeader2G = { + /* ar9300_modal_eep_header 2g */ + /* 4 idle,t1,t2,b(4 bits per setting) */ + .antCtrlCommon = LE32(0x110), + /* 4 ra1l1, ra2l1, ra1l2, ra2l2, ra12 */ + .antCtrlCommon2 = LE32(0x44444), + + /* + * antCtrlChain[AR9300_MAX_CHAINS]; 6 idle, t, r, + * rx1, rx12, b (2 bits each) + */ + .antCtrlChain = { LE16(0x150), LE16(0x150), LE16(0x150) }, + + /* + * xatten1DB[AR9300_MAX_CHAINS]; 3 xatten1_db + * for ar9280 (0xa20c/b20c 5:0) + */ + .xatten1DB = {0, 0, 0}, + + /* + * xatten1Margin[AR9300_MAX_CHAINS]; 3 xatten1_margin + * for ar9280 (0xa20c/b20c 16:12 + */ + .xatten1Margin = {0, 0, 0}, + .tempSlope = 25, + .voltSlope = 0, + + /* + * spurChans[OSPREY_EEPROM_MODAL_SPURS]; spur + * channels in usual fbin coding format + */ + .spurChans = {FREQ2FBIN(2464, 1), 0, 0, 0, 0}, + + /* + * noiseFloorThreshCh[AR9300_MAX_CHAINS]; 3 Check + * if the register is per chain + */ + .noiseFloorThreshCh = {-1, 0, 0}, + .reserved = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + .quick_drop = 0, + .xpaBiasLvl = 0, + .txFrameToDataStart = 0x0e, + .txFrameToPaOn = 0x0e, + .txClip = 3, /* 4 bits tx_clip, 4 bits dac_scale_cck */ + .antennaGain = 0, + .switchSettling = 0x2c, + .adcDesiredSize = -30, + .txEndToXpaOff = 0, + .txEndToRxOn = 0x2, + .txFrameToXpaOn = 0xe, + .thresh62 = 28, + .papdRateMaskHt20 = LE32(0x0c80c080), + .papdRateMaskHt40 = LE32(0x0080c080), + .xlna_bias_strength = 0, + .futureModal = { + 0, 0, 0, 0, 0, 0, 0, + }, + }, + .base_ext1 = { + .ant_div_control = 0, + .future = {0, 0, 0}, + .tempslopextension = {0, 0, 0, 0, 0, 0, 0, 0} + }, + .calFreqPier2G = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2462, 1), + }, + /* ar9300_cal_data_per_freq_op_loop 2g */ + .calPierData2G = { + { {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0} }, + { {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0} }, + { {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0} }, + }, + .calTarget_freqbin_Cck = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2472, 1), + }, + .calTarget_freqbin_2G = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1) + }, + .calTarget_freqbin_2GHT20 = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1) + }, + .calTarget_freqbin_2GHT40 = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1) + }, + .calTargetPowerCck = { + /* 1L-5L,5S,11L,11S */ + { {34, 34, 34, 34} }, + { {34, 34, 34, 34} }, + }, + .calTargetPower2G = { + /* 6-24,36,48,54 */ + { {34, 34, 32, 32} }, + { {34, 34, 32, 32} }, + { {34, 34, 32, 32} }, + }, + .calTargetPower2GHT20 = { + { {32, 32, 32, 32, 32, 30, 32, 32, 30, 28, 28, 28, 28, 24} }, + { {32, 32, 32, 32, 32, 30, 32, 32, 30, 28, 28, 28, 28, 24} }, + { {32, 32, 32, 32, 32, 30, 32, 32, 30, 28, 28, 28, 28, 24} }, + }, + .calTargetPower2GHT40 = { + { {30, 30, 30, 30, 30, 28, 30, 30, 28, 26, 26, 26, 26, 22} }, + { {30, 30, 30, 30, 30, 28, 30, 30, 28, 26, 26, 26, 26, 22} }, + { {30, 30, 30, 30, 30, 28, 30, 30, 28, 26, 26, 26, 26, 22} }, + }, + .ctlIndex_2G = { + 0x11, 0x12, 0x15, 0x17, 0x41, 0x42, + 0x45, 0x47, 0x31, 0x32, 0x35, 0x37, + }, + .ctl_freqbin_2G = { + { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2417, 1), + FREQ2FBIN(2457, 1), + FREQ2FBIN(2462, 1) + }, + { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2417, 1), + FREQ2FBIN(2462, 1), + 0xFF, + }, + + { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2417, 1), + FREQ2FBIN(2462, 1), + 0xFF, + }, + { + FREQ2FBIN(2422, 1), + FREQ2FBIN(2427, 1), + FREQ2FBIN(2447, 1), + FREQ2FBIN(2452, 1) + }, + + { + /* Data[4].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[4].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[4].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + /* Data[4].ctlEdges[3].bChannel */ FREQ2FBIN(2484, 1), + }, + + { + /* Data[5].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[5].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[5].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + 0, + }, + + { + /* Data[6].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[6].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + FREQ2FBIN(2472, 1), + 0, + }, + + { + /* Data[7].ctlEdges[0].bChannel */ FREQ2FBIN(2422, 1), + /* Data[7].ctlEdges[1].bChannel */ FREQ2FBIN(2427, 1), + /* Data[7].ctlEdges[2].bChannel */ FREQ2FBIN(2447, 1), + /* Data[7].ctlEdges[3].bChannel */ FREQ2FBIN(2462, 1), + }, + + { + /* Data[8].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[8].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[8].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + }, + + { + /* Data[9].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[9].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[9].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + 0 + }, + + { + /* Data[10].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[10].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[10].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + 0 + }, + + { + /* Data[11].ctlEdges[0].bChannel */ FREQ2FBIN(2422, 1), + /* Data[11].ctlEdges[1].bChannel */ FREQ2FBIN(2427, 1), + /* Data[11].ctlEdges[2].bChannel */ FREQ2FBIN(2447, 1), + /* Data[11].ctlEdges[3].bChannel */ FREQ2FBIN(2462, 1), + } + }, + .ctlPowerData_2G = { + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 1) } }, + + { { CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + + { { CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 1) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 1) } }, + }, + .modalHeader5G = { + /* 4 idle,t1,t2,b (4 bits per setting) */ + .antCtrlCommon = LE32(0x220), + /* 4 ra1l1, ra2l1, ra1l2,ra2l2,ra12 */ + .antCtrlCommon2 = LE32(0x44444), + /* antCtrlChain 6 idle, t,r,rx1,rx12,b (2 bits each) */ + .antCtrlChain = { + LE16(0x150), LE16(0x150), LE16(0x150), + }, + /* xatten1DB 3 xatten1_db for AR9280 (0xa20c/b20c 5:0) */ + .xatten1DB = {0, 0, 0}, + + /* + * xatten1Margin[AR9300_MAX_CHAINS]; 3 xatten1_margin + * for merlin (0xa20c/b20c 16:12 + */ + .xatten1Margin = {0, 0, 0}, + .tempSlope = 45, + .voltSlope = 0, + /* spurChans spur channels in usual fbin coding format */ + .spurChans = {0, 0, 0, 0, 0}, + /* noiseFloorThreshCh Check if the register is per chain */ + .noiseFloorThreshCh = {-1, 0, 0}, + .reserved = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + .quick_drop = 0, + .xpaBiasLvl = 0, + .txFrameToDataStart = 0x0e, + .txFrameToPaOn = 0x0e, + .txClip = 3, /* 4 bits tx_clip, 4 bits dac_scale_cck */ + .antennaGain = 0, + .switchSettling = 0x2d, + .adcDesiredSize = -30, + .txEndToXpaOff = 0, + .txEndToRxOn = 0x2, + .txFrameToXpaOn = 0xe, + .thresh62 = 28, + .papdRateMaskHt20 = LE32(0x0cf0e0e0), + .papdRateMaskHt40 = LE32(0x6cf0e0e0), + .xlna_bias_strength = 0, + .futureModal = { + 0, 0, 0, 0, 0, 0, 0, + }, + }, + .base_ext2 = { + .tempSlopeLow = 40, + .tempSlopeHigh = 50, + .xatten1DBLow = {0, 0, 0}, + .xatten1MarginLow = {0, 0, 0}, + .xatten1DBHigh = {0, 0, 0}, + .xatten1MarginHigh = {0, 0, 0} + }, + .calFreqPier5G = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5220, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5600, 0), + FREQ2FBIN(5700, 0), + FREQ2FBIN(5785, 0) + }, + .calPierData5G = { + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }, + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }, + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }, + + }, + .calTarget_freqbin_5G = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5240, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5600, 0), + FREQ2FBIN(5700, 0), + FREQ2FBIN(5825, 0) + }, + .calTarget_freqbin_5GHT20 = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5240, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5700, 0), + FREQ2FBIN(5745, 0), + FREQ2FBIN(5825, 0) + }, + .calTarget_freqbin_5GHT40 = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5240, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5700, 0), + FREQ2FBIN(5745, 0), + FREQ2FBIN(5825, 0) + }, + .calTargetPower5G = { + /* 6-24,36,48,54 */ + { {30, 30, 28, 24} }, + { {30, 30, 28, 24} }, + { {30, 30, 28, 24} }, + { {30, 30, 28, 24} }, + { {30, 30, 28, 24} }, + { {30, 30, 28, 24} }, + { {30, 30, 28, 24} }, + { {30, 30, 28, 24} }, + }, + .calTargetPower5GHT20 = { + /* + * 0_8_16,1-3_9-11_17-19, + * 4,5,6,7,12,13,14,15,20,21,22,23 + */ + { {30, 30, 30, 28, 24, 20, 30, 28, 24, 20, 20, 20, 20, 16} }, + { {30, 30, 30, 28, 24, 20, 30, 28, 24, 20, 20, 20, 20, 16} }, + { {30, 30, 30, 26, 22, 18, 30, 26, 22, 18, 18, 18, 18, 16} }, + { {30, 30, 30, 26, 22, 18, 30, 26, 22, 18, 18, 18, 18, 16} }, + { {30, 30, 30, 24, 20, 16, 30, 24, 20, 16, 16, 16, 16, 14} }, + { {30, 30, 30, 24, 20, 16, 30, 24, 20, 16, 16, 16, 16, 14} }, + { {30, 30, 30, 22, 18, 14, 30, 22, 18, 14, 14, 14, 14, 12} }, + { {30, 30, 30, 22, 18, 14, 30, 22, 18, 14, 14, 14, 14, 12} }, + }, + .calTargetPower5GHT40 = { + /* + * 0_8_16,1-3_9-11_17-19, + * 4,5,6,7,12,13,14,15,20,21,22,23 + */ + { {28, 28, 28, 26, 22, 18, 28, 26, 22, 18, 18, 18, 18, 14} }, + { {28, 28, 28, 26, 22, 18, 28, 26, 22, 18, 18, 18, 18, 14} }, + { {28, 28, 28, 24, 20, 16, 28, 24, 20, 16, 16, 16, 16, 12} }, + { {28, 28, 28, 24, 20, 16, 28, 24, 20, 16, 16, 16, 16, 12} }, + { {28, 28, 28, 22, 18, 14, 28, 22, 18, 14, 14, 14, 14, 10} }, + { {28, 28, 28, 22, 18, 14, 28, 22, 18, 14, 14, 14, 14, 10} }, + { {28, 28, 28, 20, 16, 12, 28, 20, 16, 12, 12, 12, 12, 8} }, + { {28, 28, 28, 20, 16, 12, 28, 20, 16, 12, 12, 12, 12, 8} }, + }, + .ctlIndex_5G = { + 0x10, 0x16, 0x18, 0x40, 0x46, + 0x48, 0x30, 0x36, 0x38 + }, + .ctl_freqbin_5G = { + { + /* Data[0].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[0].ctlEdges[1].bChannel */ FREQ2FBIN(5260, 0), + /* Data[0].ctlEdges[2].bChannel */ FREQ2FBIN(5280, 0), + /* Data[0].ctlEdges[3].bChannel */ FREQ2FBIN(5500, 0), + /* Data[0].ctlEdges[4].bChannel */ FREQ2FBIN(5600, 0), + /* Data[0].ctlEdges[5].bChannel */ FREQ2FBIN(5700, 0), + /* Data[0].ctlEdges[6].bChannel */ FREQ2FBIN(5745, 0), + /* Data[0].ctlEdges[7].bChannel */ FREQ2FBIN(5825, 0) + }, + { + /* Data[1].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[1].ctlEdges[1].bChannel */ FREQ2FBIN(5260, 0), + /* Data[1].ctlEdges[2].bChannel */ FREQ2FBIN(5280, 0), + /* Data[1].ctlEdges[3].bChannel */ FREQ2FBIN(5500, 0), + /* Data[1].ctlEdges[4].bChannel */ FREQ2FBIN(5520, 0), + /* Data[1].ctlEdges[5].bChannel */ FREQ2FBIN(5700, 0), + /* Data[1].ctlEdges[6].bChannel */ FREQ2FBIN(5745, 0), + /* Data[1].ctlEdges[7].bChannel */ FREQ2FBIN(5825, 0) + }, + + { + /* Data[2].ctlEdges[0].bChannel */ FREQ2FBIN(5190, 0), + /* Data[2].ctlEdges[1].bChannel */ FREQ2FBIN(5230, 0), + /* Data[2].ctlEdges[2].bChannel */ FREQ2FBIN(5270, 0), + /* Data[2].ctlEdges[3].bChannel */ FREQ2FBIN(5310, 0), + /* Data[2].ctlEdges[4].bChannel */ FREQ2FBIN(5510, 0), + /* Data[2].ctlEdges[5].bChannel */ FREQ2FBIN(5550, 0), + /* Data[2].ctlEdges[6].bChannel */ FREQ2FBIN(5670, 0), + /* Data[2].ctlEdges[7].bChannel */ FREQ2FBIN(5755, 0) + }, + + { + /* Data[3].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[3].ctlEdges[1].bChannel */ FREQ2FBIN(5200, 0), + /* Data[3].ctlEdges[2].bChannel */ FREQ2FBIN(5260, 0), + /* Data[3].ctlEdges[3].bChannel */ FREQ2FBIN(5320, 0), + /* Data[3].ctlEdges[4].bChannel */ FREQ2FBIN(5500, 0), + /* Data[3].ctlEdges[5].bChannel */ FREQ2FBIN(5700, 0), + /* Data[3].ctlEdges[6].bChannel */ 0xFF, + /* Data[3].ctlEdges[7].bChannel */ 0xFF, + }, + + { + /* Data[4].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[4].ctlEdges[1].bChannel */ FREQ2FBIN(5260, 0), + /* Data[4].ctlEdges[2].bChannel */ FREQ2FBIN(5500, 0), + /* Data[4].ctlEdges[3].bChannel */ FREQ2FBIN(5700, 0), + /* Data[4].ctlEdges[4].bChannel */ 0xFF, + /* Data[4].ctlEdges[5].bChannel */ 0xFF, + /* Data[4].ctlEdges[6].bChannel */ 0xFF, + /* Data[4].ctlEdges[7].bChannel */ 0xFF, + }, + + { + /* Data[5].ctlEdges[0].bChannel */ FREQ2FBIN(5190, 0), + /* Data[5].ctlEdges[1].bChannel */ FREQ2FBIN(5270, 0), + /* Data[5].ctlEdges[2].bChannel */ FREQ2FBIN(5310, 0), + /* Data[5].ctlEdges[3].bChannel */ FREQ2FBIN(5510, 0), + /* Data[5].ctlEdges[4].bChannel */ FREQ2FBIN(5590, 0), + /* Data[5].ctlEdges[5].bChannel */ FREQ2FBIN(5670, 0), + /* Data[5].ctlEdges[6].bChannel */ 0xFF, + /* Data[5].ctlEdges[7].bChannel */ 0xFF + }, + + { + /* Data[6].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[6].ctlEdges[1].bChannel */ FREQ2FBIN(5200, 0), + /* Data[6].ctlEdges[2].bChannel */ FREQ2FBIN(5220, 0), + /* Data[6].ctlEdges[3].bChannel */ FREQ2FBIN(5260, 0), + /* Data[6].ctlEdges[4].bChannel */ FREQ2FBIN(5500, 0), + /* Data[6].ctlEdges[5].bChannel */ FREQ2FBIN(5600, 0), + /* Data[6].ctlEdges[6].bChannel */ FREQ2FBIN(5700, 0), + /* Data[6].ctlEdges[7].bChannel */ FREQ2FBIN(5745, 0) + }, + + { + /* Data[7].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[7].ctlEdges[1].bChannel */ FREQ2FBIN(5260, 0), + /* Data[7].ctlEdges[2].bChannel */ FREQ2FBIN(5320, 0), + /* Data[7].ctlEdges[3].bChannel */ FREQ2FBIN(5500, 0), + /* Data[7].ctlEdges[4].bChannel */ FREQ2FBIN(5560, 0), + /* Data[7].ctlEdges[5].bChannel */ FREQ2FBIN(5700, 0), + /* Data[7].ctlEdges[6].bChannel */ FREQ2FBIN(5745, 0), + /* Data[7].ctlEdges[7].bChannel */ FREQ2FBIN(5825, 0) + }, + + { + /* Data[8].ctlEdges[0].bChannel */ FREQ2FBIN(5190, 0), + /* Data[8].ctlEdges[1].bChannel */ FREQ2FBIN(5230, 0), + /* Data[8].ctlEdges[2].bChannel */ FREQ2FBIN(5270, 0), + /* Data[8].ctlEdges[3].bChannel */ FREQ2FBIN(5510, 0), + /* Data[8].ctlEdges[4].bChannel */ FREQ2FBIN(5550, 0), + /* Data[8].ctlEdges[5].bChannel */ FREQ2FBIN(5670, 0), + /* Data[8].ctlEdges[6].bChannel */ FREQ2FBIN(5755, 0), + /* Data[8].ctlEdges[7].bChannel */ FREQ2FBIN(5795, 0) + } + }, + .ctlPowerData_5G = { + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + } + }, + { + { + CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + } + }, + { + { + CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 0), + CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + CTL(60, 0), CTL(60, 0), CTL(60, 0), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 0), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 0), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 0), CTL(60, 1), + } + }, + } +}; + + +static const struct ar9300_eeprom ar9300_x112 = { + .eepromVersion = 2, + .templateVersion = 5, + .macAddr = {0x00, 0x03, 0x7f, 0x0, 0x0, 0x0}, + .custData = {"x112-041-f0000"}, + .baseEepHeader = { + .regDmn = { LE16(0), LE16(0x1f) }, + .txrxMask = 0x77, /* 4 bits tx and 4 bits rx */ + .opCapFlags = { + .opFlags = AR5416_OPFLAGS_11G | AR5416_OPFLAGS_11A, + .eepMisc = 0, + }, + .rfSilent = 0, + .blueToothOptions = 0, + .deviceCap = 0, + .deviceType = 5, /* takes lower byte in eeprom location */ + .pwrTableOffset = AR9300_PWR_TABLE_OFFSET, + .params_for_tuning_caps = {0, 0}, + .featureEnable = 0x0d, + /* + * bit0 - enable tx temp comp - disabled + * bit1 - enable tx volt comp - disabled + * bit2 - enable fastclock - enabled + * bit3 - enable doubling - enabled + * bit4 - enable internal regulator - disabled + * bit5 - enable pa predistortion - disabled + */ + .miscConfiguration = 0, /* bit0 - turn down drivestrength */ + .eepromWriteEnableGpio = 6, + .wlanDisableGpio = 0, + .wlanLedGpio = 8, + .rxBandSelectGpio = 0xff, + .txrxgain = 0x0, + .swreg = 0, + }, + .modalHeader2G = { + /* ar9300_modal_eep_header 2g */ + /* 4 idle,t1,t2,b(4 bits per setting) */ + .antCtrlCommon = LE32(0x110), + /* 4 ra1l1, ra2l1, ra1l2, ra2l2, ra12 */ + .antCtrlCommon2 = LE32(0x22222), + + /* + * antCtrlChain[ar9300_max_chains]; 6 idle, t, r, + * rx1, rx12, b (2 bits each) + */ + .antCtrlChain = { LE16(0x10), LE16(0x10), LE16(0x10) }, + + /* + * xatten1DB[AR9300_max_chains]; 3 xatten1_db + * for ar9280 (0xa20c/b20c 5:0) + */ + .xatten1DB = {0x1b, 0x1b, 0x1b}, + + /* + * xatten1Margin[ar9300_max_chains]; 3 xatten1_margin + * for ar9280 (0xa20c/b20c 16:12 + */ + .xatten1Margin = {0x15, 0x15, 0x15}, + .tempSlope = 50, + .voltSlope = 0, + + /* + * spurChans[OSPrey_eeprom_modal_sPURS]; spur + * channels in usual fbin coding format + */ + .spurChans = {FREQ2FBIN(2464, 1), 0, 0, 0, 0}, + + /* + * noiseFloorThreshch[ar9300_max_cHAINS]; 3 Check + * if the register is per chain + */ + .noiseFloorThreshCh = {-1, 0, 0}, + .reserved = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + .quick_drop = 0, + .xpaBiasLvl = 0, + .txFrameToDataStart = 0x0e, + .txFrameToPaOn = 0x0e, + .txClip = 3, /* 4 bits tx_clip, 4 bits dac_scale_cck */ + .antennaGain = 0, + .switchSettling = 0x2c, + .adcDesiredSize = -30, + .txEndToXpaOff = 0, + .txEndToRxOn = 0x2, + .txFrameToXpaOn = 0xe, + .thresh62 = 28, + .papdRateMaskHt20 = LE32(0x0c80c080), + .papdRateMaskHt40 = LE32(0x0080c080), + .xlna_bias_strength = 0, + .futureModal = { + 0, 0, 0, 0, 0, 0, 0, + }, + }, + .base_ext1 = { + .ant_div_control = 0, + .future = {0, 0, 0}, + .tempslopextension = {0, 0, 0, 0, 0, 0, 0, 0} + }, + .calFreqPier2G = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1), + }, + /* ar9300_cal_data_per_freq_op_loop 2g */ + .calPierData2G = { + { {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0} }, + { {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0} }, + { {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0} }, + }, + .calTarget_freqbin_Cck = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2472, 1), + }, + .calTarget_freqbin_2G = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1) + }, + .calTarget_freqbin_2GHT20 = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1) + }, + .calTarget_freqbin_2GHT40 = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1) + }, + .calTargetPowerCck = { + /* 1L-5L,5S,11L,11s */ + { {38, 38, 38, 38} }, + { {38, 38, 38, 38} }, + }, + .calTargetPower2G = { + /* 6-24,36,48,54 */ + { {38, 38, 36, 34} }, + { {38, 38, 36, 34} }, + { {38, 38, 34, 32} }, + }, + .calTargetPower2GHT20 = { + { {36, 36, 36, 36, 36, 34, 34, 32, 30, 28, 28, 28, 28, 26} }, + { {36, 36, 36, 36, 36, 34, 36, 34, 32, 30, 30, 30, 28, 26} }, + { {36, 36, 36, 36, 36, 34, 34, 32, 30, 28, 28, 28, 28, 26} }, + }, + .calTargetPower2GHT40 = { + { {36, 36, 36, 36, 34, 32, 32, 30, 28, 26, 26, 26, 26, 24} }, + { {36, 36, 36, 36, 34, 32, 34, 32, 30, 28, 28, 28, 28, 24} }, + { {36, 36, 36, 36, 34, 32, 32, 30, 28, 26, 26, 26, 26, 24} }, + }, + .ctlIndex_2G = { + 0x11, 0x12, 0x15, 0x17, 0x41, 0x42, + 0x45, 0x47, 0x31, 0x32, 0x35, 0x37, + }, + .ctl_freqbin_2G = { + { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2417, 1), + FREQ2FBIN(2457, 1), + FREQ2FBIN(2462, 1) + }, + { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2417, 1), + FREQ2FBIN(2462, 1), + 0xFF, + }, + + { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2417, 1), + FREQ2FBIN(2462, 1), + 0xFF, + }, + { + FREQ2FBIN(2422, 1), + FREQ2FBIN(2427, 1), + FREQ2FBIN(2447, 1), + FREQ2FBIN(2452, 1) + }, + + { + /* Data[4].ctledges[0].bchannel */ FREQ2FBIN(2412, 1), + /* Data[4].ctledges[1].bchannel */ FREQ2FBIN(2417, 1), + /* Data[4].ctledges[2].bchannel */ FREQ2FBIN(2472, 1), + /* Data[4].ctledges[3].bchannel */ FREQ2FBIN(2484, 1), + }, + + { + /* Data[5].ctledges[0].bchannel */ FREQ2FBIN(2412, 1), + /* Data[5].ctledges[1].bchannel */ FREQ2FBIN(2417, 1), + /* Data[5].ctledges[2].bchannel */ FREQ2FBIN(2472, 1), + 0, + }, + + { + /* Data[6].ctledges[0].bchannel */ FREQ2FBIN(2412, 1), + /* Data[6].ctledges[1].bchannel */ FREQ2FBIN(2417, 1), + FREQ2FBIN(2472, 1), + 0, + }, + + { + /* Data[7].ctledges[0].bchannel */ FREQ2FBIN(2422, 1), + /* Data[7].ctledges[1].bchannel */ FREQ2FBIN(2427, 1), + /* Data[7].ctledges[2].bchannel */ FREQ2FBIN(2447, 1), + /* Data[7].ctledges[3].bchannel */ FREQ2FBIN(2462, 1), + }, + + { + /* Data[8].ctledges[0].bchannel */ FREQ2FBIN(2412, 1), + /* Data[8].ctledges[1].bchannel */ FREQ2FBIN(2417, 1), + /* Data[8].ctledges[2].bchannel */ FREQ2FBIN(2472, 1), + }, + + { + /* Data[9].ctledges[0].bchannel */ FREQ2FBIN(2412, 1), + /* Data[9].ctledges[1].bchannel */ FREQ2FBIN(2417, 1), + /* Data[9].ctledges[2].bchannel */ FREQ2FBIN(2472, 1), + 0 + }, + + { + /* Data[10].ctledges[0].bchannel */ FREQ2FBIN(2412, 1), + /* Data[10].ctledges[1].bchannel */ FREQ2FBIN(2417, 1), + /* Data[10].ctledges[2].bchannel */ FREQ2FBIN(2472, 1), + 0 + }, + + { + /* Data[11].ctledges[0].bchannel */ FREQ2FBIN(2422, 1), + /* Data[11].ctledges[1].bchannel */ FREQ2FBIN(2427, 1), + /* Data[11].ctledges[2].bchannel */ FREQ2FBIN(2447, 1), + /* Data[11].ctledges[3].bchannel */ FREQ2FBIN(2462, 1), + } + }, + .ctlPowerData_2G = { + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 1) } }, + + { { CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + + { { CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 1) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 1) } }, + }, + .modalHeader5G = { + /* 4 idle,t1,t2,b (4 bits per setting) */ + .antCtrlCommon = LE32(0x110), + /* 4 ra1l1, ra2l1, ra1l2,ra2l2,ra12 */ + .antCtrlCommon2 = LE32(0x22222), + /* antCtrlChain 6 idle, t,r,rx1,rx12,b (2 bits each) */ + .antCtrlChain = { + LE16(0x0), LE16(0x0), LE16(0x0), + }, + /* xatten1DB 3 xatten1_db for ar9280 (0xa20c/b20c 5:0) */ + .xatten1DB = {0x13, 0x19, 0x17}, + + /* + * xatten1Margin[ar9300_max_chains]; 3 xatten1_margin + * for merlin (0xa20c/b20c 16:12 + */ + .xatten1Margin = {0x19, 0x19, 0x19}, + .tempSlope = 70, + .voltSlope = 15, + /* spurChans spur channels in usual fbin coding format */ + .spurChans = {0, 0, 0, 0, 0}, + /* noiseFloorThreshch check if the register is per chain */ + .noiseFloorThreshCh = {-1, 0, 0}, + .reserved = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + .quick_drop = 0, + .xpaBiasLvl = 0, + .txFrameToDataStart = 0x0e, + .txFrameToPaOn = 0x0e, + .txClip = 3, /* 4 bits tx_clip, 4 bits dac_scale_cck */ + .antennaGain = 0, + .switchSettling = 0x2d, + .adcDesiredSize = -30, + .txEndToXpaOff = 0, + .txEndToRxOn = 0x2, + .txFrameToXpaOn = 0xe, + .thresh62 = 28, + .papdRateMaskHt20 = LE32(0x0cf0e0e0), + .papdRateMaskHt40 = LE32(0x6cf0e0e0), + .xlna_bias_strength = 0, + .futureModal = { + 0, 0, 0, 0, 0, 0, 0, + }, + }, + .base_ext2 = { + .tempSlopeLow = 72, + .tempSlopeHigh = 105, + .xatten1DBLow = {0x10, 0x14, 0x10}, + .xatten1MarginLow = {0x19, 0x19 , 0x19}, + .xatten1DBHigh = {0x1d, 0x20, 0x24}, + .xatten1MarginHigh = {0x10, 0x10, 0x10} + }, + .calFreqPier5G = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5220, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5600, 0), + FREQ2FBIN(5700, 0), + FREQ2FBIN(5785, 0) + }, + .calPierData5G = { + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }, + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }, + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }, + + }, + .calTarget_freqbin_5G = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5220, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5600, 0), + FREQ2FBIN(5725, 0), + FREQ2FBIN(5825, 0) + }, + .calTarget_freqbin_5GHT20 = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5220, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5600, 0), + FREQ2FBIN(5725, 0), + FREQ2FBIN(5825, 0) + }, + .calTarget_freqbin_5GHT40 = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5220, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5600, 0), + FREQ2FBIN(5725, 0), + FREQ2FBIN(5825, 0) + }, + .calTargetPower5G = { + /* 6-24,36,48,54 */ + { {32, 32, 28, 26} }, + { {32, 32, 28, 26} }, + { {32, 32, 28, 26} }, + { {32, 32, 26, 24} }, + { {32, 32, 26, 24} }, + { {32, 32, 24, 22} }, + { {30, 30, 24, 22} }, + { {30, 30, 24, 22} }, + }, + .calTargetPower5GHT20 = { + /* + * 0_8_16,1-3_9-11_17-19, + * 4,5,6,7,12,13,14,15,20,21,22,23 + */ + { {32, 32, 32, 32, 28, 26, 32, 28, 26, 24, 24, 24, 22, 22} }, + { {32, 32, 32, 32, 28, 26, 32, 28, 26, 24, 24, 24, 22, 22} }, + { {32, 32, 32, 32, 28, 26, 32, 28, 26, 24, 24, 24, 22, 22} }, + { {32, 32, 32, 32, 28, 26, 32, 26, 24, 22, 22, 22, 20, 20} }, + { {32, 32, 32, 32, 28, 26, 32, 26, 24, 22, 20, 18, 16, 16} }, + { {32, 32, 32, 32, 28, 26, 32, 24, 20, 16, 18, 16, 14, 14} }, + { {30, 30, 30, 30, 28, 26, 30, 24, 20, 16, 18, 16, 14, 14} }, + { {30, 30, 30, 30, 28, 26, 30, 24, 20, 16, 18, 16, 14, 14} }, + }, + .calTargetPower5GHT40 = { + /* + * 0_8_16,1-3_9-11_17-19, + * 4,5,6,7,12,13,14,15,20,21,22,23 + */ + { {32, 32, 32, 30, 28, 26, 30, 28, 26, 24, 24, 24, 22, 22} }, + { {32, 32, 32, 30, 28, 26, 30, 28, 26, 24, 24, 24, 22, 22} }, + { {32, 32, 32, 30, 28, 26, 30, 28, 26, 24, 24, 24, 22, 22} }, + { {32, 32, 32, 30, 28, 26, 30, 26, 24, 22, 22, 22, 20, 20} }, + { {32, 32, 32, 30, 28, 26, 30, 26, 24, 22, 20, 18, 16, 16} }, + { {32, 32, 32, 30, 28, 26, 30, 22, 20, 16, 18, 16, 14, 14} }, + { {30, 30, 30, 30, 28, 26, 30, 22, 20, 16, 18, 16, 14, 14} }, + { {30, 30, 30, 30, 28, 26, 30, 22, 20, 16, 18, 16, 14, 14} }, + }, + .ctlIndex_5G = { + 0x10, 0x16, 0x18, 0x40, 0x46, + 0x48, 0x30, 0x36, 0x38 + }, + .ctl_freqbin_5G = { + { + /* Data[0].ctledges[0].bchannel */ FREQ2FBIN(5180, 0), + /* Data[0].ctledges[1].bchannel */ FREQ2FBIN(5260, 0), + /* Data[0].ctledges[2].bchannel */ FREQ2FBIN(5280, 0), + /* Data[0].ctledges[3].bchannel */ FREQ2FBIN(5500, 0), + /* Data[0].ctledges[4].bchannel */ FREQ2FBIN(5600, 0), + /* Data[0].ctledges[5].bchannel */ FREQ2FBIN(5700, 0), + /* Data[0].ctledges[6].bchannel */ FREQ2FBIN(5745, 0), + /* Data[0].ctledges[7].bchannel */ FREQ2FBIN(5825, 0) + }, + { + /* Data[1].ctledges[0].bchannel */ FREQ2FBIN(5180, 0), + /* Data[1].ctledges[1].bchannel */ FREQ2FBIN(5260, 0), + /* Data[1].ctledges[2].bchannel */ FREQ2FBIN(5280, 0), + /* Data[1].ctledges[3].bchannel */ FREQ2FBIN(5500, 0), + /* Data[1].ctledges[4].bchannel */ FREQ2FBIN(5520, 0), + /* Data[1].ctledges[5].bchannel */ FREQ2FBIN(5700, 0), + /* Data[1].ctledges[6].bchannel */ FREQ2FBIN(5745, 0), + /* Data[1].ctledges[7].bchannel */ FREQ2FBIN(5825, 0) + }, + + { + /* Data[2].ctledges[0].bchannel */ FREQ2FBIN(5190, 0), + /* Data[2].ctledges[1].bchannel */ FREQ2FBIN(5230, 0), + /* Data[2].ctledges[2].bchannel */ FREQ2FBIN(5270, 0), + /* Data[2].ctledges[3].bchannel */ FREQ2FBIN(5310, 0), + /* Data[2].ctledges[4].bchannel */ FREQ2FBIN(5510, 0), + /* Data[2].ctledges[5].bchannel */ FREQ2FBIN(5550, 0), + /* Data[2].ctledges[6].bchannel */ FREQ2FBIN(5670, 0), + /* Data[2].ctledges[7].bchannel */ FREQ2FBIN(5755, 0) + }, + + { + /* Data[3].ctledges[0].bchannel */ FREQ2FBIN(5180, 0), + /* Data[3].ctledges[1].bchannel */ FREQ2FBIN(5200, 0), + /* Data[3].ctledges[2].bchannel */ FREQ2FBIN(5260, 0), + /* Data[3].ctledges[3].bchannel */ FREQ2FBIN(5320, 0), + /* Data[3].ctledges[4].bchannel */ FREQ2FBIN(5500, 0), + /* Data[3].ctledges[5].bchannel */ FREQ2FBIN(5700, 0), + /* Data[3].ctledges[6].bchannel */ 0xFF, + /* Data[3].ctledges[7].bchannel */ 0xFF, + }, + + { + /* Data[4].ctledges[0].bchannel */ FREQ2FBIN(5180, 0), + /* Data[4].ctledges[1].bchannel */ FREQ2FBIN(5260, 0), + /* Data[4].ctledges[2].bchannel */ FREQ2FBIN(5500, 0), + /* Data[4].ctledges[3].bchannel */ FREQ2FBIN(5700, 0), + /* Data[4].ctledges[4].bchannel */ 0xFF, + /* Data[4].ctledges[5].bchannel */ 0xFF, + /* Data[4].ctledges[6].bchannel */ 0xFF, + /* Data[4].ctledges[7].bchannel */ 0xFF, + }, + + { + /* Data[5].ctledges[0].bchannel */ FREQ2FBIN(5190, 0), + /* Data[5].ctledges[1].bchannel */ FREQ2FBIN(5270, 0), + /* Data[5].ctledges[2].bchannel */ FREQ2FBIN(5310, 0), + /* Data[5].ctledges[3].bchannel */ FREQ2FBIN(5510, 0), + /* Data[5].ctledges[4].bchannel */ FREQ2FBIN(5590, 0), + /* Data[5].ctledges[5].bchannel */ FREQ2FBIN(5670, 0), + /* Data[5].ctledges[6].bchannel */ 0xFF, + /* Data[5].ctledges[7].bchannel */ 0xFF + }, + + { + /* Data[6].ctledges[0].bchannel */ FREQ2FBIN(5180, 0), + /* Data[6].ctledges[1].bchannel */ FREQ2FBIN(5200, 0), + /* Data[6].ctledges[2].bchannel */ FREQ2FBIN(5220, 0), + /* Data[6].ctledges[3].bchannel */ FREQ2FBIN(5260, 0), + /* Data[6].ctledges[4].bchannel */ FREQ2FBIN(5500, 0), + /* Data[6].ctledges[5].bchannel */ FREQ2FBIN(5600, 0), + /* Data[6].ctledges[6].bchannel */ FREQ2FBIN(5700, 0), + /* Data[6].ctledges[7].bchannel */ FREQ2FBIN(5745, 0) + }, + + { + /* Data[7].ctledges[0].bchannel */ FREQ2FBIN(5180, 0), + /* Data[7].ctledges[1].bchannel */ FREQ2FBIN(5260, 0), + /* Data[7].ctledges[2].bchannel */ FREQ2FBIN(5320, 0), + /* Data[7].ctledges[3].bchannel */ FREQ2FBIN(5500, 0), + /* Data[7].ctledges[4].bchannel */ FREQ2FBIN(5560, 0), + /* Data[7].ctledges[5].bchannel */ FREQ2FBIN(5700, 0), + /* Data[7].ctledges[6].bchannel */ FREQ2FBIN(5745, 0), + /* Data[7].ctledges[7].bchannel */ FREQ2FBIN(5825, 0) + }, + + { + /* Data[8].ctledges[0].bchannel */ FREQ2FBIN(5190, 0), + /* Data[8].ctledges[1].bchannel */ FREQ2FBIN(5230, 0), + /* Data[8].ctledges[2].bchannel */ FREQ2FBIN(5270, 0), + /* Data[8].ctledges[3].bchannel */ FREQ2FBIN(5510, 0), + /* Data[8].ctledges[4].bchannel */ FREQ2FBIN(5550, 0), + /* Data[8].ctledges[5].bchannel */ FREQ2FBIN(5670, 0), + /* Data[8].ctledges[6].bchannel */ FREQ2FBIN(5755, 0), + /* Data[8].ctledges[7].bchannel */ FREQ2FBIN(5795, 0) + } + }, + .ctlPowerData_5G = { + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + } + }, + { + { + CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + } + }, + { + { + CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 0), + CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + CTL(60, 0), CTL(60, 0), CTL(60, 0), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 0), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 0), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 0), CTL(60, 1), + } + }, + } +}; + +static const struct ar9300_eeprom ar9300_h116 = { + .eepromVersion = 2, + .templateVersion = 4, + .macAddr = {0x00, 0x03, 0x7f, 0x0, 0x0, 0x0}, + .custData = {"h116-041-f0000"}, + .baseEepHeader = { + .regDmn = { LE16(0), LE16(0x1f) }, + .txrxMask = 0x33, /* 4 bits tx and 4 bits rx */ + .opCapFlags = { + .opFlags = AR5416_OPFLAGS_11G | AR5416_OPFLAGS_11A, + .eepMisc = 0, + }, + .rfSilent = 0, + .blueToothOptions = 0, + .deviceCap = 0, + .deviceType = 5, /* takes lower byte in eeprom location */ + .pwrTableOffset = AR9300_PWR_TABLE_OFFSET, + .params_for_tuning_caps = {0, 0}, + .featureEnable = 0x0d, + /* + * bit0 - enable tx temp comp - disabled + * bit1 - enable tx volt comp - disabled + * bit2 - enable fastClock - enabled + * bit3 - enable doubling - enabled + * bit4 - enable internal regulator - disabled + * bit5 - enable pa predistortion - disabled + */ + .miscConfiguration = 0, /* bit0 - turn down drivestrength */ + .eepromWriteEnableGpio = 6, + .wlanDisableGpio = 0, + .wlanLedGpio = 8, + .rxBandSelectGpio = 0xff, + .txrxgain = 0x10, + .swreg = 0, + }, + .modalHeader2G = { + /* ar9300_modal_eep_header 2g */ + /* 4 idle,t1,t2,b(4 bits per setting) */ + .antCtrlCommon = LE32(0x110), + /* 4 ra1l1, ra2l1, ra1l2, ra2l2, ra12 */ + .antCtrlCommon2 = LE32(0x44444), + + /* + * antCtrlChain[AR9300_MAX_CHAINS]; 6 idle, t, r, + * rx1, rx12, b (2 bits each) + */ + .antCtrlChain = { LE16(0x10), LE16(0x10), LE16(0x10) }, + + /* + * xatten1DB[AR9300_MAX_CHAINS]; 3 xatten1_db + * for ar9280 (0xa20c/b20c 5:0) + */ + .xatten1DB = {0x1f, 0x1f, 0x1f}, + + /* + * xatten1Margin[AR9300_MAX_CHAINS]; 3 xatten1_margin + * for ar9280 (0xa20c/b20c 16:12 + */ + .xatten1Margin = {0x12, 0x12, 0x12}, + .tempSlope = 25, + .voltSlope = 0, + + /* + * spurChans[OSPREY_EEPROM_MODAL_SPURS]; spur + * channels in usual fbin coding format + */ + .spurChans = {FREQ2FBIN(2464, 1), 0, 0, 0, 0}, + + /* + * noiseFloorThreshCh[AR9300_MAX_CHAINS]; 3 Check + * if the register is per chain + */ + .noiseFloorThreshCh = {-1, 0, 0}, + .reserved = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + .quick_drop = 0, + .xpaBiasLvl = 0, + .txFrameToDataStart = 0x0e, + .txFrameToPaOn = 0x0e, + .txClip = 3, /* 4 bits tx_clip, 4 bits dac_scale_cck */ + .antennaGain = 0, + .switchSettling = 0x2c, + .adcDesiredSize = -30, + .txEndToXpaOff = 0, + .txEndToRxOn = 0x2, + .txFrameToXpaOn = 0xe, + .thresh62 = 28, + .papdRateMaskHt20 = LE32(0x0c80C080), + .papdRateMaskHt40 = LE32(0x0080C080), + .xlna_bias_strength = 0, + .futureModal = { + 0, 0, 0, 0, 0, 0, 0, + }, + }, + .base_ext1 = { + .ant_div_control = 0, + .future = {0, 0, 0}, + .tempslopextension = {0, 0, 0, 0, 0, 0, 0, 0} + }, + .calFreqPier2G = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2462, 1), + }, + /* ar9300_cal_data_per_freq_op_loop 2g */ + .calPierData2G = { + { {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0} }, + { {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0} }, + { {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0} }, + }, + .calTarget_freqbin_Cck = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2472, 1), + }, + .calTarget_freqbin_2G = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1) + }, + .calTarget_freqbin_2GHT20 = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1) + }, + .calTarget_freqbin_2GHT40 = { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2437, 1), + FREQ2FBIN(2472, 1) + }, + .calTargetPowerCck = { + /* 1L-5L,5S,11L,11S */ + { {34, 34, 34, 34} }, + { {34, 34, 34, 34} }, + }, + .calTargetPower2G = { + /* 6-24,36,48,54 */ + { {34, 34, 32, 32} }, + { {34, 34, 32, 32} }, + { {34, 34, 32, 32} }, + }, + .calTargetPower2GHT20 = { + { {32, 32, 32, 32, 32, 30, 32, 32, 30, 28, 0, 0, 0, 0} }, + { {32, 32, 32, 32, 32, 30, 32, 32, 30, 28, 0, 0, 0, 0} }, + { {32, 32, 32, 32, 32, 30, 32, 32, 30, 28, 0, 0, 0, 0} }, + }, + .calTargetPower2GHT40 = { + { {30, 30, 30, 30, 30, 28, 30, 30, 28, 26, 0, 0, 0, 0} }, + { {30, 30, 30, 30, 30, 28, 30, 30, 28, 26, 0, 0, 0, 0} }, + { {30, 30, 30, 30, 30, 28, 30, 30, 28, 26, 0, 0, 0, 0} }, + }, + .ctlIndex_2G = { + 0x11, 0x12, 0x15, 0x17, 0x41, 0x42, + 0x45, 0x47, 0x31, 0x32, 0x35, 0x37, + }, + .ctl_freqbin_2G = { + { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2417, 1), + FREQ2FBIN(2457, 1), + FREQ2FBIN(2462, 1) + }, + { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2417, 1), + FREQ2FBIN(2462, 1), + 0xFF, + }, + + { + FREQ2FBIN(2412, 1), + FREQ2FBIN(2417, 1), + FREQ2FBIN(2462, 1), + 0xFF, + }, + { + FREQ2FBIN(2422, 1), + FREQ2FBIN(2427, 1), + FREQ2FBIN(2447, 1), + FREQ2FBIN(2452, 1) + }, + + { + /* Data[4].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[4].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[4].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + /* Data[4].ctlEdges[3].bChannel */ FREQ2FBIN(2484, 1), + }, + + { + /* Data[5].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[5].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[5].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + 0, + }, + + { + /* Data[6].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[6].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + FREQ2FBIN(2472, 1), + 0, + }, + + { + /* Data[7].ctlEdges[0].bChannel */ FREQ2FBIN(2422, 1), + /* Data[7].ctlEdges[1].bChannel */ FREQ2FBIN(2427, 1), + /* Data[7].ctlEdges[2].bChannel */ FREQ2FBIN(2447, 1), + /* Data[7].ctlEdges[3].bChannel */ FREQ2FBIN(2462, 1), + }, + + { + /* Data[8].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[8].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[8].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + }, + + { + /* Data[9].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[9].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[9].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + 0 + }, + + { + /* Data[10].ctlEdges[0].bChannel */ FREQ2FBIN(2412, 1), + /* Data[10].ctlEdges[1].bChannel */ FREQ2FBIN(2417, 1), + /* Data[10].ctlEdges[2].bChannel */ FREQ2FBIN(2472, 1), + 0 + }, + + { + /* Data[11].ctlEdges[0].bChannel */ FREQ2FBIN(2422, 1), + /* Data[11].ctlEdges[1].bChannel */ FREQ2FBIN(2427, 1), + /* Data[11].ctlEdges[2].bChannel */ FREQ2FBIN(2447, 1), + /* Data[11].ctlEdges[3].bChannel */ FREQ2FBIN(2462, 1), + } + }, + .ctlPowerData_2G = { + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 1) } }, + + { { CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + + { { CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + + { { CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 0) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 1) } }, + { { CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 1) } }, + }, + .modalHeader5G = { + /* 4 idle,t1,t2,b (4 bits per setting) */ + .antCtrlCommon = LE32(0x220), + /* 4 ra1l1, ra2l1, ra1l2,ra2l2,ra12 */ + .antCtrlCommon2 = LE32(0x44444), + /* antCtrlChain 6 idle, t,r,rx1,rx12,b (2 bits each) */ + .antCtrlChain = { + LE16(0x150), LE16(0x150), LE16(0x150), + }, + /* xatten1DB 3 xatten1_db for AR9280 (0xa20c/b20c 5:0) */ + .xatten1DB = {0x19, 0x19, 0x19}, + + /* + * xatten1Margin[AR9300_MAX_CHAINS]; 3 xatten1_margin + * for merlin (0xa20c/b20c 16:12 + */ + .xatten1Margin = {0x14, 0x14, 0x14}, + .tempSlope = 70, + .voltSlope = 0, + /* spurChans spur channels in usual fbin coding format */ + .spurChans = {0, 0, 0, 0, 0}, + /* noiseFloorThreshCh Check if the register is per chain */ + .noiseFloorThreshCh = {-1, 0, 0}, + .reserved = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + .quick_drop = 0, + .xpaBiasLvl = 0, + .txFrameToDataStart = 0x0e, + .txFrameToPaOn = 0x0e, + .txClip = 3, /* 4 bits tx_clip, 4 bits dac_scale_cck */ + .antennaGain = 0, + .switchSettling = 0x2d, + .adcDesiredSize = -30, + .txEndToXpaOff = 0, + .txEndToRxOn = 0x2, + .txFrameToXpaOn = 0xe, + .thresh62 = 28, + .papdRateMaskHt20 = LE32(0x0cf0e0e0), + .papdRateMaskHt40 = LE32(0x6cf0e0e0), + .xlna_bias_strength = 0, + .futureModal = { + 0, 0, 0, 0, 0, 0, 0, + }, + }, + .base_ext2 = { + .tempSlopeLow = 35, + .tempSlopeHigh = 50, + .xatten1DBLow = {0, 0, 0}, + .xatten1MarginLow = {0, 0, 0}, + .xatten1DBHigh = {0, 0, 0}, + .xatten1MarginHigh = {0, 0, 0} + }, + .calFreqPier5G = { + FREQ2FBIN(5160, 0), + FREQ2FBIN(5220, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5600, 0), + FREQ2FBIN(5700, 0), + FREQ2FBIN(5785, 0) + }, + .calPierData5G = { + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }, + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }, + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }, + + }, + .calTarget_freqbin_5G = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5240, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5600, 0), + FREQ2FBIN(5700, 0), + FREQ2FBIN(5825, 0) + }, + .calTarget_freqbin_5GHT20 = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5240, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5700, 0), + FREQ2FBIN(5745, 0), + FREQ2FBIN(5825, 0) + }, + .calTarget_freqbin_5GHT40 = { + FREQ2FBIN(5180, 0), + FREQ2FBIN(5240, 0), + FREQ2FBIN(5320, 0), + FREQ2FBIN(5400, 0), + FREQ2FBIN(5500, 0), + FREQ2FBIN(5700, 0), + FREQ2FBIN(5745, 0), + FREQ2FBIN(5825, 0) + }, + .calTargetPower5G = { + /* 6-24,36,48,54 */ + { {30, 30, 28, 24} }, + { {30, 30, 28, 24} }, + { {30, 30, 28, 24} }, + { {30, 30, 28, 24} }, + { {30, 30, 28, 24} }, + { {30, 30, 28, 24} }, + { {30, 30, 28, 24} }, + { {30, 30, 28, 24} }, + }, + .calTargetPower5GHT20 = { + /* + * 0_8_16,1-3_9-11_17-19, + * 4,5,6,7,12,13,14,15,20,21,22,23 + */ + { {30, 30, 30, 28, 24, 20, 30, 28, 24, 20, 0, 0, 0, 0} }, + { {30, 30, 30, 28, 24, 20, 30, 28, 24, 20, 0, 0, 0, 0} }, + { {30, 30, 30, 26, 22, 18, 30, 26, 22, 18, 0, 0, 0, 0} }, + { {30, 30, 30, 26, 22, 18, 30, 26, 22, 18, 0, 0, 0, 0} }, + { {30, 30, 30, 24, 20, 16, 30, 24, 20, 16, 0, 0, 0, 0} }, + { {30, 30, 30, 24, 20, 16, 30, 24, 20, 16, 0, 0, 0, 0} }, + { {30, 30, 30, 22, 18, 14, 30, 22, 18, 14, 0, 0, 0, 0} }, + { {30, 30, 30, 22, 18, 14, 30, 22, 18, 14, 0, 0, 0, 0} }, + }, + .calTargetPower5GHT40 = { + /* + * 0_8_16,1-3_9-11_17-19, + * 4,5,6,7,12,13,14,15,20,21,22,23 + */ + { {28, 28, 28, 26, 22, 18, 28, 26, 22, 18, 0, 0, 0, 0} }, + { {28, 28, 28, 26, 22, 18, 28, 26, 22, 18, 0, 0, 0, 0} }, + { {28, 28, 28, 24, 20, 16, 28, 24, 20, 16, 0, 0, 0, 0} }, + { {28, 28, 28, 24, 20, 16, 28, 24, 20, 16, 0, 0, 0, 0} }, + { {28, 28, 28, 22, 18, 14, 28, 22, 18, 14, 0, 0, 0, 0} }, + { {28, 28, 28, 22, 18, 14, 28, 22, 18, 14, 0, 0, 0, 0} }, + { {28, 28, 28, 20, 16, 12, 28, 20, 16, 12, 0, 0, 0, 0} }, + { {28, 28, 28, 20, 16, 12, 28, 20, 16, 12, 0, 0, 0, 0} }, + }, + .ctlIndex_5G = { + 0x10, 0x16, 0x18, 0x40, 0x46, + 0x48, 0x30, 0x36, 0x38 + }, + .ctl_freqbin_5G = { + { + /* Data[0].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[0].ctlEdges[1].bChannel */ FREQ2FBIN(5260, 0), + /* Data[0].ctlEdges[2].bChannel */ FREQ2FBIN(5280, 0), + /* Data[0].ctlEdges[3].bChannel */ FREQ2FBIN(5500, 0), + /* Data[0].ctlEdges[4].bChannel */ FREQ2FBIN(5600, 0), + /* Data[0].ctlEdges[5].bChannel */ FREQ2FBIN(5700, 0), + /* Data[0].ctlEdges[6].bChannel */ FREQ2FBIN(5745, 0), + /* Data[0].ctlEdges[7].bChannel */ FREQ2FBIN(5825, 0) + }, + { + /* Data[1].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[1].ctlEdges[1].bChannel */ FREQ2FBIN(5260, 0), + /* Data[1].ctlEdges[2].bChannel */ FREQ2FBIN(5280, 0), + /* Data[1].ctlEdges[3].bChannel */ FREQ2FBIN(5500, 0), + /* Data[1].ctlEdges[4].bChannel */ FREQ2FBIN(5520, 0), + /* Data[1].ctlEdges[5].bChannel */ FREQ2FBIN(5700, 0), + /* Data[1].ctlEdges[6].bChannel */ FREQ2FBIN(5745, 0), + /* Data[1].ctlEdges[7].bChannel */ FREQ2FBIN(5825, 0) + }, + + { + /* Data[2].ctlEdges[0].bChannel */ FREQ2FBIN(5190, 0), + /* Data[2].ctlEdges[1].bChannel */ FREQ2FBIN(5230, 0), + /* Data[2].ctlEdges[2].bChannel */ FREQ2FBIN(5270, 0), + /* Data[2].ctlEdges[3].bChannel */ FREQ2FBIN(5310, 0), + /* Data[2].ctlEdges[4].bChannel */ FREQ2FBIN(5510, 0), + /* Data[2].ctlEdges[5].bChannel */ FREQ2FBIN(5550, 0), + /* Data[2].ctlEdges[6].bChannel */ FREQ2FBIN(5670, 0), + /* Data[2].ctlEdges[7].bChannel */ FREQ2FBIN(5755, 0) + }, + + { + /* Data[3].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[3].ctlEdges[1].bChannel */ FREQ2FBIN(5200, 0), + /* Data[3].ctlEdges[2].bChannel */ FREQ2FBIN(5260, 0), + /* Data[3].ctlEdges[3].bChannel */ FREQ2FBIN(5320, 0), + /* Data[3].ctlEdges[4].bChannel */ FREQ2FBIN(5500, 0), + /* Data[3].ctlEdges[5].bChannel */ FREQ2FBIN(5700, 0), + /* Data[3].ctlEdges[6].bChannel */ 0xFF, + /* Data[3].ctlEdges[7].bChannel */ 0xFF, + }, + + { + /* Data[4].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[4].ctlEdges[1].bChannel */ FREQ2FBIN(5260, 0), + /* Data[4].ctlEdges[2].bChannel */ FREQ2FBIN(5500, 0), + /* Data[4].ctlEdges[3].bChannel */ FREQ2FBIN(5700, 0), + /* Data[4].ctlEdges[4].bChannel */ 0xFF, + /* Data[4].ctlEdges[5].bChannel */ 0xFF, + /* Data[4].ctlEdges[6].bChannel */ 0xFF, + /* Data[4].ctlEdges[7].bChannel */ 0xFF, + }, + + { + /* Data[5].ctlEdges[0].bChannel */ FREQ2FBIN(5190, 0), + /* Data[5].ctlEdges[1].bChannel */ FREQ2FBIN(5270, 0), + /* Data[5].ctlEdges[2].bChannel */ FREQ2FBIN(5310, 0), + /* Data[5].ctlEdges[3].bChannel */ FREQ2FBIN(5510, 0), + /* Data[5].ctlEdges[4].bChannel */ FREQ2FBIN(5590, 0), + /* Data[5].ctlEdges[5].bChannel */ FREQ2FBIN(5670, 0), + /* Data[5].ctlEdges[6].bChannel */ 0xFF, + /* Data[5].ctlEdges[7].bChannel */ 0xFF + }, + + { + /* Data[6].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[6].ctlEdges[1].bChannel */ FREQ2FBIN(5200, 0), + /* Data[6].ctlEdges[2].bChannel */ FREQ2FBIN(5220, 0), + /* Data[6].ctlEdges[3].bChannel */ FREQ2FBIN(5260, 0), + /* Data[6].ctlEdges[4].bChannel */ FREQ2FBIN(5500, 0), + /* Data[6].ctlEdges[5].bChannel */ FREQ2FBIN(5600, 0), + /* Data[6].ctlEdges[6].bChannel */ FREQ2FBIN(5700, 0), + /* Data[6].ctlEdges[7].bChannel */ FREQ2FBIN(5745, 0) + }, + + { + /* Data[7].ctlEdges[0].bChannel */ FREQ2FBIN(5180, 0), + /* Data[7].ctlEdges[1].bChannel */ FREQ2FBIN(5260, 0), + /* Data[7].ctlEdges[2].bChannel */ FREQ2FBIN(5320, 0), + /* Data[7].ctlEdges[3].bChannel */ FREQ2FBIN(5500, 0), + /* Data[7].ctlEdges[4].bChannel */ FREQ2FBIN(5560, 0), + /* Data[7].ctlEdges[5].bChannel */ FREQ2FBIN(5700, 0), + /* Data[7].ctlEdges[6].bChannel */ FREQ2FBIN(5745, 0), + /* Data[7].ctlEdges[7].bChannel */ FREQ2FBIN(5825, 0) + }, + + { + /* Data[8].ctlEdges[0].bChannel */ FREQ2FBIN(5190, 0), + /* Data[8].ctlEdges[1].bChannel */ FREQ2FBIN(5230, 0), + /* Data[8].ctlEdges[2].bChannel */ FREQ2FBIN(5270, 0), + /* Data[8].ctlEdges[3].bChannel */ FREQ2FBIN(5510, 0), + /* Data[8].ctlEdges[4].bChannel */ FREQ2FBIN(5550, 0), + /* Data[8].ctlEdges[5].bChannel */ FREQ2FBIN(5670, 0), + /* Data[8].ctlEdges[6].bChannel */ FREQ2FBIN(5755, 0), + /* Data[8].ctlEdges[7].bChannel */ FREQ2FBIN(5795, 0) + } + }, + .ctlPowerData_5G = { + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + } + }, + { + { + CTL(60, 0), CTL(60, 1), CTL(60, 0), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + } + }, + { + { + CTL(60, 0), CTL(60, 1), CTL(60, 1), CTL(60, 0), + CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + CTL(60, 0), CTL(60, 0), CTL(60, 0), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 0), CTL(60, 0), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 1), + } + }, + { + { + CTL(60, 1), CTL(60, 1), CTL(60, 0), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 1), CTL(60, 0), + } + }, + { + { + CTL(60, 1), CTL(60, 0), CTL(60, 1), CTL(60, 1), + CTL(60, 1), CTL(60, 1), CTL(60, 0), CTL(60, 1), + } + }, + } +}; + + +static const struct ar9300_eeprom *ar9300_eep_templates[] = { + &ar9300_default, + &ar9300_x112, + &ar9300_h116, + &ar9300_h112, + &ar9300_x113, +}; + +static const struct ar9300_eeprom *ar9003_eeprom_struct_find_by_id(int id) +{ +#define N_LOOP (sizeof(ar9300_eep_templates) / sizeof(ar9300_eep_templates[0])) + int it; + + for (it = 0; it < N_LOOP; it++) + if (ar9300_eep_templates[it]->templateVersion == id) + return ar9300_eep_templates[it]; + return NULL; +#undef N_LOOP +} + +static int ath9k_hw_ar9300_check_eeprom(struct ath_hw *ah) +{ + return 0; +} + +static int interpolate(int x, int xa, int xb, int ya, int yb) +{ + int bf, factor, plus; + + bf = 2 * (yb - ya) * (x - xa) / (xb - xa); + factor = bf / 2; + plus = bf % 2; + return ya + factor + plus; +} + +static u32 ath9k_hw_ar9300_get_eeprom(struct ath_hw *ah, + enum eeprom_param param) +{ + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + struct ar9300_base_eep_hdr *pBase = &eep->baseEepHeader; + + switch (param) { + case EEP_MAC_LSW: + return get_unaligned_be16(eep->macAddr); + case EEP_MAC_MID: + return get_unaligned_be16(eep->macAddr + 2); + case EEP_MAC_MSW: + return get_unaligned_be16(eep->macAddr + 4); + case EEP_REG_0: + return le16_to_cpu(pBase->regDmn[0]); + case EEP_OP_CAP: + return pBase->deviceCap; + case EEP_OP_MODE: + return pBase->opCapFlags.opFlags; + case EEP_RF_SILENT: + return pBase->rfSilent; + case EEP_TX_MASK: + return (pBase->txrxMask >> 4) & 0xf; + case EEP_RX_MASK: + return pBase->txrxMask & 0xf; + case EEP_PAPRD: + return !!(pBase->featureEnable & BIT(5)); + case EEP_CHAIN_MASK_REDUCE: + return (pBase->miscConfiguration >> 0x3) & 0x1; + case EEP_ANT_DIV_CTL1: + return eep->base_ext1.ant_div_control; + case EEP_ANTENNA_GAIN_5G: + return eep->modalHeader5G.antennaGain; + case EEP_ANTENNA_GAIN_2G: + return eep->modalHeader2G.antennaGain; + default: + return 0; + } +} + +static bool ar9300_eeprom_read_byte(struct ath_hw *ah, int address, + u8 *buffer) +{ + u16 val; + + if (unlikely(!ath9k_hw_nvram_read(ah, address / 2, &val))) + return false; + + *buffer = (val >> (8 * (address % 2))) & 0xff; + return true; +} + +static bool ar9300_eeprom_read_word(struct ath_hw *ah, int address, + u8 *buffer) +{ + u16 val; + + if (unlikely(!ath9k_hw_nvram_read(ah, address / 2, &val))) + return false; + + buffer[0] = val >> 8; + buffer[1] = val & 0xff; + + return true; +} + +static bool ar9300_read_eeprom(struct ath_hw *ah, int address, u8 *buffer, + int count) +{ + struct ath_common *common = ath9k_hw_common(ah); + int i; + + if ((address < 0) || ((address + count) / 2 > AR9300_EEPROM_SIZE - 1)) { + ath_dbg(common, EEPROM, "eeprom address not in range\n"); + return false; + } + + /* + * Since we're reading the bytes in reverse order from a little-endian + * word stream, an even address means we only use the lower half of + * the 16-bit word at that address + */ + if (address % 2 == 0) { + if (!ar9300_eeprom_read_byte(ah, address--, buffer++)) + goto error; + + count--; + } + + for (i = 0; i < count / 2; i++) { + if (!ar9300_eeprom_read_word(ah, address, buffer)) + goto error; + + address -= 2; + buffer += 2; + } + + if (count % 2) + if (!ar9300_eeprom_read_byte(ah, address, buffer)) + goto error; + + return true; + +error: + ath_dbg(common, EEPROM, "unable to read eeprom region at offset %d\n", + address); + return false; +} + +static bool ar9300_otp_read_word(struct ath_hw *ah, int addr, u32 *data) +{ + REG_READ(ah, AR9300_OTP_BASE + (4 * addr)); + + if (!ath9k_hw_wait(ah, AR9300_OTP_STATUS, AR9300_OTP_STATUS_TYPE, + AR9300_OTP_STATUS_VALID, 1000)) + return false; + + *data = REG_READ(ah, AR9300_OTP_READ_DATA); + return true; +} + +static bool ar9300_read_otp(struct ath_hw *ah, int address, u8 *buffer, + int count) +{ + u32 data; + int i; + + for (i = 0; i < count; i++) { + int offset = 8 * ((address - i) % 4); + if (!ar9300_otp_read_word(ah, (address - i) / 4, &data)) + return false; + + buffer[i] = (data >> offset) & 0xff; + } + + return true; +} + + +static void ar9300_comp_hdr_unpack(u8 *best, int *code, int *reference, + int *length, int *major, int *minor) +{ + unsigned long value[4]; + + value[0] = best[0]; + value[1] = best[1]; + value[2] = best[2]; + value[3] = best[3]; + *code = ((value[0] >> 5) & 0x0007); + *reference = (value[0] & 0x001f) | ((value[1] >> 2) & 0x0020); + *length = ((value[1] << 4) & 0x07f0) | ((value[2] >> 4) & 0x000f); + *major = (value[2] & 0x000f); + *minor = (value[3] & 0x00ff); +} + +static u16 ar9300_comp_cksum(u8 *data, int dsize) +{ + int it, checksum = 0; + + for (it = 0; it < dsize; it++) { + checksum += data[it]; + checksum &= 0xffff; + } + + return checksum; +} + +static bool ar9300_uncompress_block(struct ath_hw *ah, + u8 *mptr, + int mdataSize, + u8 *block, + int size) +{ + int it; + int spot; + int offset; + int length; + struct ath_common *common = ath9k_hw_common(ah); + + spot = 0; + + for (it = 0; it < size; it += (length+2)) { + offset = block[it]; + offset &= 0xff; + spot += offset; + length = block[it+1]; + length &= 0xff; + + if (length > 0 && spot >= 0 && spot+length <= mdataSize) { + ath_dbg(common, EEPROM, + "Restore at %d: spot=%d offset=%d length=%d\n", + it, spot, offset, length); + memcpy(&mptr[spot], &block[it+2], length); + spot += length; + } else if (length > 0) { + ath_dbg(common, EEPROM, + "Bad restore at %d: spot=%d offset=%d length=%d\n", + it, spot, offset, length); + return false; + } + } + return true; +} + +static int ar9300_compress_decision(struct ath_hw *ah, + int it, + int code, + int reference, + u8 *mptr, + u8 *word, int length, int mdata_size) +{ + struct ath_common *common = ath9k_hw_common(ah); + const struct ar9300_eeprom *eep = NULL; + + switch (code) { + case _CompressNone: + if (length != mdata_size) { + ath_dbg(common, EEPROM, + "EEPROM structure size mismatch memory=%d eeprom=%d\n", + mdata_size, length); + return -1; + } + memcpy(mptr, word + COMP_HDR_LEN, length); + ath_dbg(common, EEPROM, + "restored eeprom %d: uncompressed, length %d\n", + it, length); + break; + case _CompressBlock: + if (reference == 0) { + } else { + eep = ar9003_eeprom_struct_find_by_id(reference); + if (eep == NULL) { + ath_dbg(common, EEPROM, + "can't find reference eeprom struct %d\n", + reference); + return -1; + } + memcpy(mptr, eep, mdata_size); + } + ath_dbg(common, EEPROM, + "restore eeprom %d: block, reference %d, length %d\n", + it, reference, length); + ar9300_uncompress_block(ah, mptr, mdata_size, + (word + COMP_HDR_LEN), length); + break; + default: + ath_dbg(common, EEPROM, "unknown compression code %d\n", code); + return -1; + } + return 0; +} + +typedef bool (*eeprom_read_op)(struct ath_hw *ah, int address, u8 *buffer, + int count); + +static bool ar9300_check_header(void *data) +{ + u32 *word = data; + return !(*word == 0 || *word == ~0); +} + +static bool ar9300_check_eeprom_header(struct ath_hw *ah, eeprom_read_op read, + int base_addr) +{ + u8 header[4]; + + if (!read(ah, base_addr, header, 4)) + return false; + + return ar9300_check_header(header); +} + +static int ar9300_eeprom_restore_flash(struct ath_hw *ah, u8 *mptr, + int mdata_size) +{ + u16 *data = (u16 *) mptr; + int i; + + for (i = 0; i < mdata_size / 2; i++, data++) + ath9k_hw_nvram_read(ah, i, data); + + return 0; +} +/* + * Read the configuration data from the eeprom. + * The data can be put in any specified memory buffer. + * + * Returns -1 on error. + * Returns address of next memory location on success. + */ +static int ar9300_eeprom_restore_internal(struct ath_hw *ah, + u8 *mptr, int mdata_size) +{ +#define MDEFAULT 15 +#define MSTATE 100 + int cptr; + u8 *word; + int code; + int reference, length, major, minor; + int osize; + int it; + u16 checksum, mchecksum; + struct ath_common *common = ath9k_hw_common(ah); + struct ar9300_eeprom *eep; + eeprom_read_op read; + + if (ath9k_hw_use_flash(ah)) { + u8 txrx; + + ar9300_eeprom_restore_flash(ah, mptr, mdata_size); + + /* check if eeprom contains valid data */ + eep = (struct ar9300_eeprom *) mptr; + txrx = eep->baseEepHeader.txrxMask; + if (txrx != 0 && txrx != 0xff) + return 0; + } + + word = kzalloc(2048, GFP_KERNEL); + if (!word) + return -ENOMEM; + + memcpy(mptr, &ar9300_default, mdata_size); + + read = ar9300_read_eeprom; + if (AR_SREV_9485(ah)) + cptr = AR9300_BASE_ADDR_4K; + else if (AR_SREV_9330(ah)) + cptr = AR9300_BASE_ADDR_512; + else + cptr = AR9300_BASE_ADDR; + ath_dbg(common, EEPROM, "Trying EEPROM access at Address 0x%04x\n", + cptr); + if (ar9300_check_eeprom_header(ah, read, cptr)) + goto found; + + cptr = AR9300_BASE_ADDR_512; + ath_dbg(common, EEPROM, "Trying EEPROM access at Address 0x%04x\n", + cptr); + if (ar9300_check_eeprom_header(ah, read, cptr)) + goto found; + + read = ar9300_read_otp; + cptr = AR9300_BASE_ADDR; + ath_dbg(common, EEPROM, "Trying OTP access at Address 0x%04x\n", cptr); + if (ar9300_check_eeprom_header(ah, read, cptr)) + goto found; + + cptr = AR9300_BASE_ADDR_512; + ath_dbg(common, EEPROM, "Trying OTP access at Address 0x%04x\n", cptr); + if (ar9300_check_eeprom_header(ah, read, cptr)) + goto found; + + goto fail; + +found: + ath_dbg(common, EEPROM, "Found valid EEPROM data\n"); + + for (it = 0; it < MSTATE; it++) { + if (!read(ah, cptr, word, COMP_HDR_LEN)) + goto fail; + + if (!ar9300_check_header(word)) + break; + + ar9300_comp_hdr_unpack(word, &code, &reference, + &length, &major, &minor); + ath_dbg(common, EEPROM, + "Found block at %x: code=%d ref=%d length=%d major=%d minor=%d\n", + cptr, code, reference, length, major, minor); + if ((!AR_SREV_9485(ah) && length >= 1024) || + (AR_SREV_9485(ah) && length > EEPROM_DATA_LEN_9485)) { + ath_dbg(common, EEPROM, "Skipping bad header\n"); + cptr -= COMP_HDR_LEN; + continue; + } + + osize = length; + read(ah, cptr, word, COMP_HDR_LEN + osize + COMP_CKSUM_LEN); + checksum = ar9300_comp_cksum(&word[COMP_HDR_LEN], length); + mchecksum = get_unaligned_le16(&word[COMP_HDR_LEN + osize]); + ath_dbg(common, EEPROM, "checksum %x %x\n", + checksum, mchecksum); + if (checksum == mchecksum) { + ar9300_compress_decision(ah, it, code, reference, mptr, + word, length, mdata_size); + } else { + ath_dbg(common, EEPROM, + "skipping block with bad checksum\n"); + } + cptr -= (COMP_HDR_LEN + osize + COMP_CKSUM_LEN); + } + + kfree(word); + return cptr; + +fail: + kfree(word); + return -1; +} + +/* + * Restore the configuration structure by reading the eeprom. + * This function destroys any existing in-memory structure + * content. + */ +static bool ath9k_hw_ar9300_fill_eeprom(struct ath_hw *ah) +{ + u8 *mptr = (u8 *) &ah->eeprom.ar9300_eep; + + if (ar9300_eeprom_restore_internal(ah, mptr, + sizeof(struct ar9300_eeprom)) < 0) + return false; + + return true; +} + +#if defined(CPTCFG_ATH9K_DEBUGFS) || defined(CPTCFG_ATH9K_HTC_DEBUGFS) +static u32 ar9003_dump_modal_eeprom(char *buf, u32 len, u32 size, + struct ar9300_modal_eep_header *modal_hdr) +{ + PR_EEP("Chain0 Ant. Control", le16_to_cpu(modal_hdr->antCtrlChain[0])); + PR_EEP("Chain1 Ant. Control", le16_to_cpu(modal_hdr->antCtrlChain[1])); + PR_EEP("Chain2 Ant. Control", le16_to_cpu(modal_hdr->antCtrlChain[2])); + PR_EEP("Ant. Common Control", le32_to_cpu(modal_hdr->antCtrlCommon)); + PR_EEP("Ant. Common Control2", le32_to_cpu(modal_hdr->antCtrlCommon2)); + PR_EEP("Ant. Gain", modal_hdr->antennaGain); + PR_EEP("Switch Settle", modal_hdr->switchSettling); + PR_EEP("Chain0 xatten1DB", modal_hdr->xatten1DB[0]); + PR_EEP("Chain1 xatten1DB", modal_hdr->xatten1DB[1]); + PR_EEP("Chain2 xatten1DB", modal_hdr->xatten1DB[2]); + PR_EEP("Chain0 xatten1Margin", modal_hdr->xatten1Margin[0]); + PR_EEP("Chain1 xatten1Margin", modal_hdr->xatten1Margin[1]); + PR_EEP("Chain2 xatten1Margin", modal_hdr->xatten1Margin[2]); + PR_EEP("Temp Slope", modal_hdr->tempSlope); + PR_EEP("Volt Slope", modal_hdr->voltSlope); + PR_EEP("spur Channels0", modal_hdr->spurChans[0]); + PR_EEP("spur Channels1", modal_hdr->spurChans[1]); + PR_EEP("spur Channels2", modal_hdr->spurChans[2]); + PR_EEP("spur Channels3", modal_hdr->spurChans[3]); + PR_EEP("spur Channels4", modal_hdr->spurChans[4]); + PR_EEP("Chain0 NF Threshold", modal_hdr->noiseFloorThreshCh[0]); + PR_EEP("Chain1 NF Threshold", modal_hdr->noiseFloorThreshCh[1]); + PR_EEP("Chain2 NF Threshold", modal_hdr->noiseFloorThreshCh[2]); + PR_EEP("Quick Drop", modal_hdr->quick_drop); + PR_EEP("txEndToXpaOff", modal_hdr->txEndToXpaOff); + PR_EEP("xPA Bias Level", modal_hdr->xpaBiasLvl); + PR_EEP("txFrameToDataStart", modal_hdr->txFrameToDataStart); + PR_EEP("txFrameToPaOn", modal_hdr->txFrameToPaOn); + PR_EEP("txFrameToXpaOn", modal_hdr->txFrameToXpaOn); + PR_EEP("txClip", modal_hdr->txClip); + PR_EEP("ADC Desired size", modal_hdr->adcDesiredSize); + + return len; +} + +static u32 ath9k_hw_ar9003_dump_eeprom(struct ath_hw *ah, bool dump_base_hdr, + u8 *buf, u32 len, u32 size) +{ + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + struct ar9300_base_eep_hdr *pBase; + + if (!dump_base_hdr) { + len += snprintf(buf + len, size - len, + "%20s :\n", "2GHz modal Header"); + len = ar9003_dump_modal_eeprom(buf, len, size, + &eep->modalHeader2G); + len += snprintf(buf + len, size - len, + "%20s :\n", "5GHz modal Header"); + len = ar9003_dump_modal_eeprom(buf, len, size, + &eep->modalHeader5G); + goto out; + } + + pBase = &eep->baseEepHeader; + + PR_EEP("EEPROM Version", ah->eeprom.ar9300_eep.eepromVersion); + PR_EEP("RegDomain1", le16_to_cpu(pBase->regDmn[0])); + PR_EEP("RegDomain2", le16_to_cpu(pBase->regDmn[1])); + PR_EEP("TX Mask", (pBase->txrxMask >> 4)); + PR_EEP("RX Mask", (pBase->txrxMask & 0x0f)); + PR_EEP("Allow 5GHz", !!(pBase->opCapFlags.opFlags & + AR5416_OPFLAGS_11A)); + PR_EEP("Allow 2GHz", !!(pBase->opCapFlags.opFlags & + AR5416_OPFLAGS_11G)); + PR_EEP("Disable 2GHz HT20", !!(pBase->opCapFlags.opFlags & + AR5416_OPFLAGS_N_2G_HT20)); + PR_EEP("Disable 2GHz HT40", !!(pBase->opCapFlags.opFlags & + AR5416_OPFLAGS_N_2G_HT40)); + PR_EEP("Disable 5Ghz HT20", !!(pBase->opCapFlags.opFlags & + AR5416_OPFLAGS_N_5G_HT20)); + PR_EEP("Disable 5Ghz HT40", !!(pBase->opCapFlags.opFlags & + AR5416_OPFLAGS_N_5G_HT40)); + PR_EEP("Big Endian", !!(pBase->opCapFlags.eepMisc & 0x01)); + PR_EEP("RF Silent", pBase->rfSilent); + PR_EEP("BT option", pBase->blueToothOptions); + PR_EEP("Device Cap", pBase->deviceCap); + PR_EEP("Device Type", pBase->deviceType); + PR_EEP("Power Table Offset", pBase->pwrTableOffset); + PR_EEP("Tuning Caps1", pBase->params_for_tuning_caps[0]); + PR_EEP("Tuning Caps2", pBase->params_for_tuning_caps[1]); + PR_EEP("Enable Tx Temp Comp", !!(pBase->featureEnable & BIT(0))); + PR_EEP("Enable Tx Volt Comp", !!(pBase->featureEnable & BIT(1))); + PR_EEP("Enable fast clock", !!(pBase->featureEnable & BIT(2))); + PR_EEP("Enable doubling", !!(pBase->featureEnable & BIT(3))); + PR_EEP("Internal regulator", !!(pBase->featureEnable & BIT(4))); + PR_EEP("Enable Paprd", !!(pBase->featureEnable & BIT(5))); + PR_EEP("Driver Strength", !!(pBase->miscConfiguration & BIT(0))); + PR_EEP("Quick Drop", !!(pBase->miscConfiguration & BIT(1))); + PR_EEP("Chain mask Reduce", (pBase->miscConfiguration >> 0x3) & 0x1); + PR_EEP("Write enable Gpio", pBase->eepromWriteEnableGpio); + PR_EEP("WLAN Disable Gpio", pBase->wlanDisableGpio); + PR_EEP("WLAN LED Gpio", pBase->wlanLedGpio); + PR_EEP("Rx Band Select Gpio", pBase->rxBandSelectGpio); + PR_EEP("Tx Gain", pBase->txrxgain >> 4); + PR_EEP("Rx Gain", pBase->txrxgain & 0xf); + PR_EEP("SW Reg", le32_to_cpu(pBase->swreg)); + + len += snprintf(buf + len, size - len, "%20s : %pM\n", "MacAddress", + ah->eeprom.ar9300_eep.macAddr); +out: + if (len > size) + len = size; + + return len; +} +#else +static u32 ath9k_hw_ar9003_dump_eeprom(struct ath_hw *ah, bool dump_base_hdr, + u8 *buf, u32 len, u32 size) +{ + return 0; +} +#endif + +/* XXX: review hardware docs */ +static int ath9k_hw_ar9300_get_eeprom_ver(struct ath_hw *ah) +{ + return ah->eeprom.ar9300_eep.eepromVersion; +} + +/* XXX: could be read from the eepromVersion, not sure yet */ +static int ath9k_hw_ar9300_get_eeprom_rev(struct ath_hw *ah) +{ + return 0; +} + +static struct ar9300_modal_eep_header *ar9003_modal_header(struct ath_hw *ah, + bool is2ghz) +{ + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + + if (is2ghz) + return &eep->modalHeader2G; + else + return &eep->modalHeader5G; +} + +static void ar9003_hw_xpa_bias_level_apply(struct ath_hw *ah, bool is2ghz) +{ + int bias = ar9003_modal_header(ah, is2ghz)->xpaBiasLvl; + + if (AR_SREV_9485(ah) || AR_SREV_9330(ah) || AR_SREV_9340(ah)) + REG_RMW_FIELD(ah, AR_CH0_TOP2, AR_CH0_TOP2_XPABIASLVL, bias); + else if (AR_SREV_9462(ah) || AR_SREV_9550(ah) || AR_SREV_9565(ah)) + REG_RMW_FIELD(ah, AR_CH0_TOP, AR_CH0_TOP_XPABIASLVL, bias); + else { + REG_RMW_FIELD(ah, AR_CH0_TOP, AR_CH0_TOP_XPABIASLVL, bias); + REG_RMW_FIELD(ah, AR_CH0_THERM, + AR_CH0_THERM_XPABIASLVL_MSB, + bias >> 2); + REG_RMW_FIELD(ah, AR_CH0_THERM, + AR_CH0_THERM_XPASHORT2GND, 1); + } +} + +static u16 ar9003_switch_com_spdt_get(struct ath_hw *ah, bool is2ghz) +{ + return le16_to_cpu(ar9003_modal_header(ah, is2ghz)->switchcomspdt); +} + + +static u32 ar9003_hw_ant_ctrl_common_get(struct ath_hw *ah, bool is2ghz) +{ + return le32_to_cpu(ar9003_modal_header(ah, is2ghz)->antCtrlCommon); +} + +static u32 ar9003_hw_ant_ctrl_common_2_get(struct ath_hw *ah, bool is2ghz) +{ + return le32_to_cpu(ar9003_modal_header(ah, is2ghz)->antCtrlCommon2); +} + +static u16 ar9003_hw_ant_ctrl_chain_get(struct ath_hw *ah, int chain, + bool is2ghz) +{ + __le16 val = ar9003_modal_header(ah, is2ghz)->antCtrlChain[chain]; + return le16_to_cpu(val); +} + +static void ar9003_hw_ant_ctrl_apply(struct ath_hw *ah, bool is2ghz) +{ + struct ath9k_hw_capabilities *pCap = &ah->caps; + int chain; + u32 regval, value; + static const u32 switch_chain_reg[AR9300_MAX_CHAINS] = { + AR_PHY_SWITCH_CHAIN_0, + AR_PHY_SWITCH_CHAIN_1, + AR_PHY_SWITCH_CHAIN_2, + }; + + if (AR_SREV_9485(ah) && (ar9003_hw_get_rx_gain_idx(ah) == 0)) + ath9k_hw_cfg_output(ah, AR9300_EXT_LNA_CTL_GPIO_AR9485, + AR_GPIO_OUTPUT_MUX_AS_PCIE_ATTENTION_LED); + + value = ar9003_hw_ant_ctrl_common_get(ah, is2ghz); + + if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) { + REG_RMW_FIELD(ah, AR_PHY_SWITCH_COM, + AR_SWITCH_TABLE_COM_AR9462_ALL, value); + } else if (AR_SREV_9550(ah)) { + REG_RMW_FIELD(ah, AR_PHY_SWITCH_COM, + AR_SWITCH_TABLE_COM_AR9550_ALL, value); + } else + REG_RMW_FIELD(ah, AR_PHY_SWITCH_COM, + AR_SWITCH_TABLE_COM_ALL, value); + + + /* + * AR9462 defines new switch table for BT/WLAN, + * here's new field name in XXX.ref for both 2G and 5G. + * Register: [GLB_CONTROL] GLB_CONTROL (@0x20044) + * 15:12 R/W SWITCH_TABLE_COM_SPDT_WLAN_RX + * SWITCH_TABLE_COM_SPDT_WLAN_RX + * + * 11:8 R/W SWITCH_TABLE_COM_SPDT_WLAN_TX + * SWITCH_TABLE_COM_SPDT_WLAN_TX + * + * 7:4 R/W SWITCH_TABLE_COM_SPDT_WLAN_IDLE + * SWITCH_TABLE_COM_SPDT_WLAN_IDLE + */ + if (AR_SREV_9462_20(ah) || AR_SREV_9565(ah)) { + value = ar9003_switch_com_spdt_get(ah, is2ghz); + REG_RMW_FIELD(ah, AR_PHY_GLB_CONTROL, + AR_SWITCH_TABLE_COM_SPDT_ALL, value); + REG_SET_BIT(ah, AR_PHY_GLB_CONTROL, AR_BTCOEX_CTRL_SPDT_ENABLE); + } + + value = ar9003_hw_ant_ctrl_common_2_get(ah, is2ghz); + REG_RMW_FIELD(ah, AR_PHY_SWITCH_COM_2, AR_SWITCH_TABLE_COM2_ALL, value); + + if ((AR_SREV_9462(ah)) && (ah->rxchainmask == 0x2)) { + value = ar9003_hw_ant_ctrl_chain_get(ah, 1, is2ghz); + REG_RMW_FIELD(ah, switch_chain_reg[0], + AR_SWITCH_TABLE_ALL, value); + } + + for (chain = 0; chain < AR9300_MAX_CHAINS; chain++) { + if ((ah->rxchainmask & BIT(chain)) || + (ah->txchainmask & BIT(chain))) { + value = ar9003_hw_ant_ctrl_chain_get(ah, chain, + is2ghz); + REG_RMW_FIELD(ah, switch_chain_reg[chain], + AR_SWITCH_TABLE_ALL, value); + } + } + + if (AR_SREV_9330(ah) || AR_SREV_9485(ah) || AR_SREV_9565(ah)) { + value = ath9k_hw_ar9300_get_eeprom(ah, EEP_ANT_DIV_CTL1); + /* + * main_lnaconf, alt_lnaconf, main_tb, alt_tb + * are the fields present + */ + regval = REG_READ(ah, AR_PHY_MC_GAIN_CTRL); + regval &= (~AR_ANT_DIV_CTRL_ALL); + regval |= (value & 0x3f) << AR_ANT_DIV_CTRL_ALL_S; + /* enable_lnadiv */ + regval &= (~AR_PHY_ANT_DIV_LNADIV); + regval |= ((value >> 6) & 0x1) << AR_PHY_ANT_DIV_LNADIV_S; + + if (AR_SREV_9565(ah)) { + if (ah->shared_chain_lnadiv) { + regval |= (1 << AR_PHY_ANT_SW_RX_PROT_S); + } else { + regval &= ~(1 << AR_PHY_ANT_DIV_LNADIV_S); + regval &= ~(1 << AR_PHY_ANT_SW_RX_PROT_S); + } + } + + REG_WRITE(ah, AR_PHY_MC_GAIN_CTRL, regval); + + /*enable fast_div */ + regval = REG_READ(ah, AR_PHY_CCK_DETECT); + regval &= (~AR_FAST_DIV_ENABLE); + regval |= ((value >> 7) & 0x1) << AR_FAST_DIV_ENABLE_S; + REG_WRITE(ah, AR_PHY_CCK_DETECT, regval); + + if (pCap->hw_caps & ATH9K_HW_CAP_ANT_DIV_COMB) { + regval = REG_READ(ah, AR_PHY_MC_GAIN_CTRL); + /* + * clear bits 25-30 main_lnaconf, alt_lnaconf, + * main_tb, alt_tb + */ + regval &= (~(AR_PHY_ANT_DIV_MAIN_LNACONF | + AR_PHY_ANT_DIV_ALT_LNACONF | + AR_PHY_ANT_DIV_ALT_GAINTB | + AR_PHY_ANT_DIV_MAIN_GAINTB)); + /* by default use LNA1 for the main antenna */ + regval |= (AR_PHY_ANT_DIV_LNA1 << + AR_PHY_ANT_DIV_MAIN_LNACONF_S); + regval |= (AR_PHY_ANT_DIV_LNA2 << + AR_PHY_ANT_DIV_ALT_LNACONF_S); + REG_WRITE(ah, AR_PHY_MC_GAIN_CTRL, regval); + } + } +} + +static void ar9003_hw_drive_strength_apply(struct ath_hw *ah) +{ + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + struct ar9300_base_eep_hdr *pBase = &eep->baseEepHeader; + int drive_strength; + unsigned long reg; + + drive_strength = pBase->miscConfiguration & BIT(0); + if (!drive_strength) + return; + + reg = REG_READ(ah, AR_PHY_65NM_CH0_BIAS1); + reg &= ~0x00ffffc0; + reg |= 0x5 << 21; + reg |= 0x5 << 18; + reg |= 0x5 << 15; + reg |= 0x5 << 12; + reg |= 0x5 << 9; + reg |= 0x5 << 6; + REG_WRITE(ah, AR_PHY_65NM_CH0_BIAS1, reg); + + reg = REG_READ(ah, AR_PHY_65NM_CH0_BIAS2); + reg &= ~0xffffffe0; + reg |= 0x5 << 29; + reg |= 0x5 << 26; + reg |= 0x5 << 23; + reg |= 0x5 << 20; + reg |= 0x5 << 17; + reg |= 0x5 << 14; + reg |= 0x5 << 11; + reg |= 0x5 << 8; + reg |= 0x5 << 5; + REG_WRITE(ah, AR_PHY_65NM_CH0_BIAS2, reg); + + reg = REG_READ(ah, AR_PHY_65NM_CH0_BIAS4); + reg &= ~0xff800000; + reg |= 0x5 << 29; + reg |= 0x5 << 26; + reg |= 0x5 << 23; + REG_WRITE(ah, AR_PHY_65NM_CH0_BIAS4, reg); +} + +static u16 ar9003_hw_atten_chain_get(struct ath_hw *ah, int chain, + struct ath9k_channel *chan) +{ + int f[3], t[3]; + u16 value; + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + + if (chain >= 0 && chain < 3) { + if (IS_CHAN_2GHZ(chan)) + return eep->modalHeader2G.xatten1DB[chain]; + else if (eep->base_ext2.xatten1DBLow[chain] != 0) { + t[0] = eep->base_ext2.xatten1DBLow[chain]; + f[0] = 5180; + t[1] = eep->modalHeader5G.xatten1DB[chain]; + f[1] = 5500; + t[2] = eep->base_ext2.xatten1DBHigh[chain]; + f[2] = 5785; + value = ar9003_hw_power_interpolate((s32) chan->channel, + f, t, 3); + return value; + } else + return eep->modalHeader5G.xatten1DB[chain]; + } + + return 0; +} + + +static u16 ar9003_hw_atten_chain_get_margin(struct ath_hw *ah, int chain, + struct ath9k_channel *chan) +{ + int f[3], t[3]; + u16 value; + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + + if (chain >= 0 && chain < 3) { + if (IS_CHAN_2GHZ(chan)) + return eep->modalHeader2G.xatten1Margin[chain]; + else if (eep->base_ext2.xatten1MarginLow[chain] != 0) { + t[0] = eep->base_ext2.xatten1MarginLow[chain]; + f[0] = 5180; + t[1] = eep->modalHeader5G.xatten1Margin[chain]; + f[1] = 5500; + t[2] = eep->base_ext2.xatten1MarginHigh[chain]; + f[2] = 5785; + value = ar9003_hw_power_interpolate((s32) chan->channel, + f, t, 3); + return value; + } else + return eep->modalHeader5G.xatten1Margin[chain]; + } + + return 0; +} + +static void ar9003_hw_atten_apply(struct ath_hw *ah, struct ath9k_channel *chan) +{ + int i; + u16 value; + unsigned long ext_atten_reg[3] = {AR_PHY_EXT_ATTEN_CTL_0, + AR_PHY_EXT_ATTEN_CTL_1, + AR_PHY_EXT_ATTEN_CTL_2, + }; + + if ((AR_SREV_9462(ah)) && (ah->rxchainmask == 0x2)) { + value = ar9003_hw_atten_chain_get(ah, 1, chan); + REG_RMW_FIELD(ah, ext_atten_reg[0], + AR_PHY_EXT_ATTEN_CTL_XATTEN1_DB, value); + + value = ar9003_hw_atten_chain_get_margin(ah, 1, chan); + REG_RMW_FIELD(ah, ext_atten_reg[0], + AR_PHY_EXT_ATTEN_CTL_XATTEN1_MARGIN, + value); + } + + /* Test value. if 0 then attenuation is unused. Don't load anything. */ + for (i = 0; i < 3; i++) { + if (ah->txchainmask & BIT(i)) { + value = ar9003_hw_atten_chain_get(ah, i, chan); + REG_RMW_FIELD(ah, ext_atten_reg[i], + AR_PHY_EXT_ATTEN_CTL_XATTEN1_DB, value); + + value = ar9003_hw_atten_chain_get_margin(ah, i, chan); + REG_RMW_FIELD(ah, ext_atten_reg[i], + AR_PHY_EXT_ATTEN_CTL_XATTEN1_MARGIN, + value); + } + } +} + +static bool is_pmu_set(struct ath_hw *ah, u32 pmu_reg, int pmu_set) +{ + int timeout = 100; + + while (pmu_set != REG_READ(ah, pmu_reg)) { + if (timeout-- == 0) + return false; + REG_WRITE(ah, pmu_reg, pmu_set); + udelay(10); + } + + return true; +} + +void ar9003_hw_internal_regulator_apply(struct ath_hw *ah) +{ + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + struct ar9300_base_eep_hdr *pBase = &eep->baseEepHeader; + u32 reg_val; + + if (pBase->featureEnable & BIT(4)) { + if (AR_SREV_9330(ah) || AR_SREV_9485(ah)) { + int reg_pmu_set; + + reg_pmu_set = REG_READ(ah, AR_PHY_PMU2) & ~AR_PHY_PMU2_PGM; + REG_WRITE(ah, AR_PHY_PMU2, reg_pmu_set); + if (!is_pmu_set(ah, AR_PHY_PMU2, reg_pmu_set)) + return; + + if (AR_SREV_9330(ah)) { + if (ah->is_clk_25mhz) { + reg_pmu_set = (3 << 1) | (8 << 4) | + (3 << 8) | (1 << 14) | + (6 << 17) | (1 << 20) | + (3 << 24); + } else { + reg_pmu_set = (4 << 1) | (7 << 4) | + (3 << 8) | (1 << 14) | + (6 << 17) | (1 << 20) | + (3 << 24); + } + } else { + reg_pmu_set = (5 << 1) | (7 << 4) | + (2 << 8) | (2 << 14) | + (6 << 17) | (1 << 20) | + (3 << 24) | (1 << 28); + } + + REG_WRITE(ah, AR_PHY_PMU1, reg_pmu_set); + if (!is_pmu_set(ah, AR_PHY_PMU1, reg_pmu_set)) + return; + + reg_pmu_set = (REG_READ(ah, AR_PHY_PMU2) & ~0xFFC00000) + | (4 << 26); + REG_WRITE(ah, AR_PHY_PMU2, reg_pmu_set); + if (!is_pmu_set(ah, AR_PHY_PMU2, reg_pmu_set)) + return; + + reg_pmu_set = (REG_READ(ah, AR_PHY_PMU2) & ~0x00200000) + | (1 << 21); + REG_WRITE(ah, AR_PHY_PMU2, reg_pmu_set); + if (!is_pmu_set(ah, AR_PHY_PMU2, reg_pmu_set)) + return; + } else if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) { + reg_val = le32_to_cpu(pBase->swreg); + REG_WRITE(ah, AR_PHY_PMU1, reg_val); + } else { + /* Internal regulator is ON. Write swreg register. */ + reg_val = le32_to_cpu(pBase->swreg); + REG_WRITE(ah, AR_RTC_REG_CONTROL1, + REG_READ(ah, AR_RTC_REG_CONTROL1) & + (~AR_RTC_REG_CONTROL1_SWREG_PROGRAM)); + REG_WRITE(ah, AR_RTC_REG_CONTROL0, reg_val); + /* Set REG_CONTROL1.SWREG_PROGRAM */ + REG_WRITE(ah, AR_RTC_REG_CONTROL1, + REG_READ(ah, + AR_RTC_REG_CONTROL1) | + AR_RTC_REG_CONTROL1_SWREG_PROGRAM); + } + } else { + if (AR_SREV_9330(ah) || AR_SREV_9485(ah)) { + REG_RMW_FIELD(ah, AR_PHY_PMU2, AR_PHY_PMU2_PGM, 0); + while (REG_READ_FIELD(ah, AR_PHY_PMU2, + AR_PHY_PMU2_PGM)) + udelay(10); + + REG_RMW_FIELD(ah, AR_PHY_PMU1, AR_PHY_PMU1_PWD, 0x1); + while (!REG_READ_FIELD(ah, AR_PHY_PMU1, + AR_PHY_PMU1_PWD)) + udelay(10); + REG_RMW_FIELD(ah, AR_PHY_PMU2, AR_PHY_PMU2_PGM, 0x1); + while (!REG_READ_FIELD(ah, AR_PHY_PMU2, + AR_PHY_PMU2_PGM)) + udelay(10); + } else if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) + REG_RMW_FIELD(ah, AR_PHY_PMU1, AR_PHY_PMU1_PWD, 0x1); + else { + reg_val = REG_READ(ah, AR_RTC_SLEEP_CLK) | + AR_RTC_FORCE_SWREG_PRD; + REG_WRITE(ah, AR_RTC_SLEEP_CLK, reg_val); + } + } + +} + +static void ar9003_hw_apply_tuning_caps(struct ath_hw *ah) +{ + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + u8 tuning_caps_param = eep->baseEepHeader.params_for_tuning_caps[0]; + + if (AR_SREV_9485(ah) || AR_SREV_9330(ah) || AR_SREV_9340(ah)) + return; + + if (eep->baseEepHeader.featureEnable & 0x40) { + tuning_caps_param &= 0x7f; + REG_RMW_FIELD(ah, AR_CH0_XTAL, AR_CH0_XTAL_CAPINDAC, + tuning_caps_param); + REG_RMW_FIELD(ah, AR_CH0_XTAL, AR_CH0_XTAL_CAPOUTDAC, + tuning_caps_param); + } +} + +static void ar9003_hw_quick_drop_apply(struct ath_hw *ah, u16 freq) +{ + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + struct ar9300_base_eep_hdr *pBase = &eep->baseEepHeader; + int quick_drop; + s32 t[3], f[3] = {5180, 5500, 5785}; + + if (!(pBase->miscConfiguration & BIT(1))) + return; + + if (freq < 4000) + quick_drop = eep->modalHeader2G.quick_drop; + else { + t[0] = eep->base_ext1.quick_drop_low; + t[1] = eep->modalHeader5G.quick_drop; + t[2] = eep->base_ext1.quick_drop_high; + quick_drop = ar9003_hw_power_interpolate(freq, f, t, 3); + } + REG_RMW_FIELD(ah, AR_PHY_AGC, AR_PHY_AGC_QUICK_DROP, quick_drop); +} + +static void ar9003_hw_txend_to_xpa_off_apply(struct ath_hw *ah, bool is2ghz) +{ + u32 value; + + value = ar9003_modal_header(ah, is2ghz)->txEndToXpaOff; + + REG_RMW_FIELD(ah, AR_PHY_XPA_TIMING_CTL, + AR_PHY_XPA_TIMING_CTL_TX_END_XPAB_OFF, value); + REG_RMW_FIELD(ah, AR_PHY_XPA_TIMING_CTL, + AR_PHY_XPA_TIMING_CTL_TX_END_XPAA_OFF, value); +} + +static void ar9003_hw_xpa_timing_control_apply(struct ath_hw *ah, bool is2ghz) +{ + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + u8 xpa_ctl; + + if (!(eep->baseEepHeader.featureEnable & 0x80)) + return; + + if (!AR_SREV_9300(ah) && !AR_SREV_9340(ah) && !AR_SREV_9580(ah)) + return; + + xpa_ctl = ar9003_modal_header(ah, is2ghz)->txFrameToXpaOn; + if (is2ghz) + REG_RMW_FIELD(ah, AR_PHY_XPA_TIMING_CTL, + AR_PHY_XPA_TIMING_CTL_FRAME_XPAB_ON, xpa_ctl); + else + REG_RMW_FIELD(ah, AR_PHY_XPA_TIMING_CTL, + AR_PHY_XPA_TIMING_CTL_FRAME_XPAA_ON, xpa_ctl); +} + +static void ar9003_hw_xlna_bias_strength_apply(struct ath_hw *ah, bool is2ghz) +{ + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + u8 bias; + + if (!(eep->baseEepHeader.featureEnable & 0x40)) + return; + + if (!AR_SREV_9300(ah)) + return; + + bias = ar9003_modal_header(ah, is2ghz)->xlna_bias_strength; + REG_RMW_FIELD(ah, AR_PHY_65NM_CH0_RXTX4, AR_PHY_65NM_RXTX4_XLNA_BIAS, + bias & 0x3); + bias >>= 2; + REG_RMW_FIELD(ah, AR_PHY_65NM_CH1_RXTX4, AR_PHY_65NM_RXTX4_XLNA_BIAS, + bias & 0x3); + bias >>= 2; + REG_RMW_FIELD(ah, AR_PHY_65NM_CH2_RXTX4, AR_PHY_65NM_RXTX4_XLNA_BIAS, + bias & 0x3); +} + +static int ar9003_hw_get_thermometer(struct ath_hw *ah) +{ + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + struct ar9300_base_eep_hdr *pBase = &eep->baseEepHeader; + int thermometer = (pBase->miscConfiguration >> 1) & 0x3; + + return --thermometer; +} + +static void ar9003_hw_thermometer_apply(struct ath_hw *ah) +{ + int thermometer = ar9003_hw_get_thermometer(ah); + u8 therm_on = (thermometer < 0) ? 0 : 1; + + REG_RMW_FIELD(ah, AR_PHY_65NM_CH0_RXTX4, + AR_PHY_65NM_CH0_RXTX4_THERM_ON_OVR, therm_on); + if (ah->caps.tx_chainmask & BIT(1)) + REG_RMW_FIELD(ah, AR_PHY_65NM_CH1_RXTX4, + AR_PHY_65NM_CH0_RXTX4_THERM_ON_OVR, therm_on); + if (ah->caps.tx_chainmask & BIT(2)) + REG_RMW_FIELD(ah, AR_PHY_65NM_CH2_RXTX4, + AR_PHY_65NM_CH0_RXTX4_THERM_ON_OVR, therm_on); + + therm_on = (thermometer < 0) ? 0 : (thermometer == 0); + REG_RMW_FIELD(ah, AR_PHY_65NM_CH0_RXTX4, + AR_PHY_65NM_CH0_RXTX4_THERM_ON, therm_on); + if (ah->caps.tx_chainmask & BIT(1)) { + therm_on = (thermometer < 0) ? 0 : (thermometer == 1); + REG_RMW_FIELD(ah, AR_PHY_65NM_CH1_RXTX4, + AR_PHY_65NM_CH0_RXTX4_THERM_ON, therm_on); + } + if (ah->caps.tx_chainmask & BIT(2)) { + therm_on = (thermometer < 0) ? 0 : (thermometer == 2); + REG_RMW_FIELD(ah, AR_PHY_65NM_CH2_RXTX4, + AR_PHY_65NM_CH0_RXTX4_THERM_ON, therm_on); + } +} + +static void ar9003_hw_thermo_cal_apply(struct ath_hw *ah) +{ + u32 data, ko, kg; + + if (!AR_SREV_9462_20(ah)) + return; + ar9300_otp_read_word(ah, 1, &data); + ko = data & 0xff; + kg = (data >> 8) & 0xff; + if (ko || kg) { + REG_RMW_FIELD(ah, AR_PHY_BB_THERM_ADC_3, + AR_PHY_BB_THERM_ADC_3_THERM_ADC_OFFSET, ko); + REG_RMW_FIELD(ah, AR_PHY_BB_THERM_ADC_3, + AR_PHY_BB_THERM_ADC_3_THERM_ADC_SCALE_GAIN, + kg + 256); + } +} + +static void ath9k_hw_ar9300_set_board_values(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + bool is2ghz = IS_CHAN_2GHZ(chan); + ar9003_hw_xpa_timing_control_apply(ah, is2ghz); + ar9003_hw_xpa_bias_level_apply(ah, is2ghz); + ar9003_hw_ant_ctrl_apply(ah, is2ghz); + ar9003_hw_drive_strength_apply(ah); + ar9003_hw_xlna_bias_strength_apply(ah, is2ghz); + ar9003_hw_atten_apply(ah, chan); + ar9003_hw_quick_drop_apply(ah, chan->channel); + if (!AR_SREV_9330(ah) && !AR_SREV_9340(ah) && !AR_SREV_9550(ah)) + ar9003_hw_internal_regulator_apply(ah); + ar9003_hw_apply_tuning_caps(ah); + ar9003_hw_txend_to_xpa_off_apply(ah, is2ghz); + ar9003_hw_thermometer_apply(ah); + ar9003_hw_thermo_cal_apply(ah); +} + +static void ath9k_hw_ar9300_set_addac(struct ath_hw *ah, + struct ath9k_channel *chan) +{ +} + +/* + * Returns the interpolated y value corresponding to the specified x value + * from the np ordered pairs of data (px,py). + * The pairs do not have to be in any order. + * If the specified x value is less than any of the px, + * the returned y value is equal to the py for the lowest px. + * If the specified x value is greater than any of the px, + * the returned y value is equal to the py for the highest px. + */ +static int ar9003_hw_power_interpolate(int32_t x, + int32_t *px, int32_t *py, u_int16_t np) +{ + int ip = 0; + int lx = 0, ly = 0, lhave = 0; + int hx = 0, hy = 0, hhave = 0; + int dx = 0; + int y = 0; + + lhave = 0; + hhave = 0; + + /* identify best lower and higher x calibration measurement */ + for (ip = 0; ip < np; ip++) { + dx = x - px[ip]; + + /* this measurement is higher than our desired x */ + if (dx <= 0) { + if (!hhave || dx > (x - hx)) { + /* new best higher x measurement */ + hx = px[ip]; + hy = py[ip]; + hhave = 1; + } + } + /* this measurement is lower than our desired x */ + if (dx >= 0) { + if (!lhave || dx < (x - lx)) { + /* new best lower x measurement */ + lx = px[ip]; + ly = py[ip]; + lhave = 1; + } + } + } + + /* the low x is good */ + if (lhave) { + /* so is the high x */ + if (hhave) { + /* they're the same, so just pick one */ + if (hx == lx) + y = ly; + else /* interpolate */ + y = interpolate(x, lx, hx, ly, hy); + } else /* only low is good, use it */ + y = ly; + } else if (hhave) /* only high is good, use it */ + y = hy; + else /* nothing is good,this should never happen unless np=0, ???? */ + y = -(1 << 30); + return y; +} + +static u8 ar9003_hw_eeprom_get_tgt_pwr(struct ath_hw *ah, + u16 rateIndex, u16 freq, bool is2GHz) +{ + u16 numPiers, i; + s32 targetPowerArray[AR9300_NUM_5G_20_TARGET_POWERS]; + s32 freqArray[AR9300_NUM_5G_20_TARGET_POWERS]; + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + struct cal_tgt_pow_legacy *pEepromTargetPwr; + u8 *pFreqBin; + + if (is2GHz) { + numPiers = AR9300_NUM_2G_20_TARGET_POWERS; + pEepromTargetPwr = eep->calTargetPower2G; + pFreqBin = eep->calTarget_freqbin_2G; + } else { + numPiers = AR9300_NUM_5G_20_TARGET_POWERS; + pEepromTargetPwr = eep->calTargetPower5G; + pFreqBin = eep->calTarget_freqbin_5G; + } + + /* + * create array of channels and targetpower from + * targetpower piers stored on eeprom + */ + for (i = 0; i < numPiers; i++) { + freqArray[i] = ath9k_hw_fbin2freq(pFreqBin[i], is2GHz); + targetPowerArray[i] = pEepromTargetPwr[i].tPow2x[rateIndex]; + } + + /* interpolate to get target power for given frequency */ + return (u8) ar9003_hw_power_interpolate((s32) freq, + freqArray, + targetPowerArray, numPiers); +} + +static u8 ar9003_hw_eeprom_get_ht20_tgt_pwr(struct ath_hw *ah, + u16 rateIndex, + u16 freq, bool is2GHz) +{ + u16 numPiers, i; + s32 targetPowerArray[AR9300_NUM_5G_20_TARGET_POWERS]; + s32 freqArray[AR9300_NUM_5G_20_TARGET_POWERS]; + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + struct cal_tgt_pow_ht *pEepromTargetPwr; + u8 *pFreqBin; + + if (is2GHz) { + numPiers = AR9300_NUM_2G_20_TARGET_POWERS; + pEepromTargetPwr = eep->calTargetPower2GHT20; + pFreqBin = eep->calTarget_freqbin_2GHT20; + } else { + numPiers = AR9300_NUM_5G_20_TARGET_POWERS; + pEepromTargetPwr = eep->calTargetPower5GHT20; + pFreqBin = eep->calTarget_freqbin_5GHT20; + } + + /* + * create array of channels and targetpower + * from targetpower piers stored on eeprom + */ + for (i = 0; i < numPiers; i++) { + freqArray[i] = ath9k_hw_fbin2freq(pFreqBin[i], is2GHz); + targetPowerArray[i] = pEepromTargetPwr[i].tPow2x[rateIndex]; + } + + /* interpolate to get target power for given frequency */ + return (u8) ar9003_hw_power_interpolate((s32) freq, + freqArray, + targetPowerArray, numPiers); +} + +static u8 ar9003_hw_eeprom_get_ht40_tgt_pwr(struct ath_hw *ah, + u16 rateIndex, + u16 freq, bool is2GHz) +{ + u16 numPiers, i; + s32 targetPowerArray[AR9300_NUM_5G_40_TARGET_POWERS]; + s32 freqArray[AR9300_NUM_5G_40_TARGET_POWERS]; + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + struct cal_tgt_pow_ht *pEepromTargetPwr; + u8 *pFreqBin; + + if (is2GHz) { + numPiers = AR9300_NUM_2G_40_TARGET_POWERS; + pEepromTargetPwr = eep->calTargetPower2GHT40; + pFreqBin = eep->calTarget_freqbin_2GHT40; + } else { + numPiers = AR9300_NUM_5G_40_TARGET_POWERS; + pEepromTargetPwr = eep->calTargetPower5GHT40; + pFreqBin = eep->calTarget_freqbin_5GHT40; + } + + /* + * create array of channels and targetpower from + * targetpower piers stored on eeprom + */ + for (i = 0; i < numPiers; i++) { + freqArray[i] = ath9k_hw_fbin2freq(pFreqBin[i], is2GHz); + targetPowerArray[i] = pEepromTargetPwr[i].tPow2x[rateIndex]; + } + + /* interpolate to get target power for given frequency */ + return (u8) ar9003_hw_power_interpolate((s32) freq, + freqArray, + targetPowerArray, numPiers); +} + +static u8 ar9003_hw_eeprom_get_cck_tgt_pwr(struct ath_hw *ah, + u16 rateIndex, u16 freq) +{ + u16 numPiers = AR9300_NUM_2G_CCK_TARGET_POWERS, i; + s32 targetPowerArray[AR9300_NUM_2G_CCK_TARGET_POWERS]; + s32 freqArray[AR9300_NUM_2G_CCK_TARGET_POWERS]; + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + struct cal_tgt_pow_legacy *pEepromTargetPwr = eep->calTargetPowerCck; + u8 *pFreqBin = eep->calTarget_freqbin_Cck; + + /* + * create array of channels and targetpower from + * targetpower piers stored on eeprom + */ + for (i = 0; i < numPiers; i++) { + freqArray[i] = ath9k_hw_fbin2freq(pFreqBin[i], 1); + targetPowerArray[i] = pEepromTargetPwr[i].tPow2x[rateIndex]; + } + + /* interpolate to get target power for given frequency */ + return (u8) ar9003_hw_power_interpolate((s32) freq, + freqArray, + targetPowerArray, numPiers); +} + +/* Set tx power registers to array of values passed in */ +static int ar9003_hw_tx_power_regwrite(struct ath_hw *ah, u8 * pPwrArray) +{ +#define POW_SM(_r, _s) (((_r) & 0x3f) << (_s)) + /* make sure forced gain is not set */ + REG_WRITE(ah, AR_PHY_TX_FORCED_GAIN, 0); + + /* Write the OFDM power per rate set */ + + /* 6 (LSB), 9, 12, 18 (MSB) */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE(0), + POW_SM(pPwrArray[ALL_TARGET_LEGACY_6_24], 24) | + POW_SM(pPwrArray[ALL_TARGET_LEGACY_6_24], 16) | + POW_SM(pPwrArray[ALL_TARGET_LEGACY_6_24], 8) | + POW_SM(pPwrArray[ALL_TARGET_LEGACY_6_24], 0)); + + /* 24 (LSB), 36, 48, 54 (MSB) */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE(1), + POW_SM(pPwrArray[ALL_TARGET_LEGACY_54], 24) | + POW_SM(pPwrArray[ALL_TARGET_LEGACY_48], 16) | + POW_SM(pPwrArray[ALL_TARGET_LEGACY_36], 8) | + POW_SM(pPwrArray[ALL_TARGET_LEGACY_6_24], 0)); + + /* Write the CCK power per rate set */ + + /* 1L (LSB), reserved, 2L, 2S (MSB) */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE(2), + POW_SM(pPwrArray[ALL_TARGET_LEGACY_1L_5L], 24) | + POW_SM(pPwrArray[ALL_TARGET_LEGACY_1L_5L], 16) | + /* POW_SM(txPowerTimes2, 8) | this is reserved for AR9003 */ + POW_SM(pPwrArray[ALL_TARGET_LEGACY_1L_5L], 0)); + + /* 5.5L (LSB), 5.5S, 11L, 11S (MSB) */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE(3), + POW_SM(pPwrArray[ALL_TARGET_LEGACY_11S], 24) | + POW_SM(pPwrArray[ALL_TARGET_LEGACY_11L], 16) | + POW_SM(pPwrArray[ALL_TARGET_LEGACY_5S], 8) | + POW_SM(pPwrArray[ALL_TARGET_LEGACY_1L_5L], 0) + ); + + /* Write the power for duplicated frames - HT40 */ + + /* dup40_cck (LSB), dup40_ofdm, ext20_cck, ext20_ofdm (MSB) */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE(8), + POW_SM(pPwrArray[ALL_TARGET_LEGACY_6_24], 24) | + POW_SM(pPwrArray[ALL_TARGET_LEGACY_1L_5L], 16) | + POW_SM(pPwrArray[ALL_TARGET_LEGACY_6_24], 8) | + POW_SM(pPwrArray[ALL_TARGET_LEGACY_1L_5L], 0) + ); + + /* Write the HT20 power per rate set */ + + /* 0/8/16 (LSB), 1-3/9-11/17-19, 4, 5 (MSB) */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE(4), + POW_SM(pPwrArray[ALL_TARGET_HT20_5], 24) | + POW_SM(pPwrArray[ALL_TARGET_HT20_4], 16) | + POW_SM(pPwrArray[ALL_TARGET_HT20_1_3_9_11_17_19], 8) | + POW_SM(pPwrArray[ALL_TARGET_HT20_0_8_16], 0) + ); + + /* 6 (LSB), 7, 12, 13 (MSB) */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE(5), + POW_SM(pPwrArray[ALL_TARGET_HT20_13], 24) | + POW_SM(pPwrArray[ALL_TARGET_HT20_12], 16) | + POW_SM(pPwrArray[ALL_TARGET_HT20_7], 8) | + POW_SM(pPwrArray[ALL_TARGET_HT20_6], 0) + ); + + /* 14 (LSB), 15, 20, 21 */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE(9), + POW_SM(pPwrArray[ALL_TARGET_HT20_21], 24) | + POW_SM(pPwrArray[ALL_TARGET_HT20_20], 16) | + POW_SM(pPwrArray[ALL_TARGET_HT20_15], 8) | + POW_SM(pPwrArray[ALL_TARGET_HT20_14], 0) + ); + + /* Mixed HT20 and HT40 rates */ + + /* HT20 22 (LSB), HT20 23, HT40 22, HT40 23 (MSB) */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE(10), + POW_SM(pPwrArray[ALL_TARGET_HT40_23], 24) | + POW_SM(pPwrArray[ALL_TARGET_HT40_22], 16) | + POW_SM(pPwrArray[ALL_TARGET_HT20_23], 8) | + POW_SM(pPwrArray[ALL_TARGET_HT20_22], 0) + ); + + /* + * Write the HT40 power per rate set + * correct PAR difference between HT40 and HT20/LEGACY + * 0/8/16 (LSB), 1-3/9-11/17-19, 4, 5 (MSB) + */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE(6), + POW_SM(pPwrArray[ALL_TARGET_HT40_5], 24) | + POW_SM(pPwrArray[ALL_TARGET_HT40_4], 16) | + POW_SM(pPwrArray[ALL_TARGET_HT40_1_3_9_11_17_19], 8) | + POW_SM(pPwrArray[ALL_TARGET_HT40_0_8_16], 0) + ); + + /* 6 (LSB), 7, 12, 13 (MSB) */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE(7), + POW_SM(pPwrArray[ALL_TARGET_HT40_13], 24) | + POW_SM(pPwrArray[ALL_TARGET_HT40_12], 16) | + POW_SM(pPwrArray[ALL_TARGET_HT40_7], 8) | + POW_SM(pPwrArray[ALL_TARGET_HT40_6], 0) + ); + + /* 14 (LSB), 15, 20, 21 */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE(11), + POW_SM(pPwrArray[ALL_TARGET_HT40_21], 24) | + POW_SM(pPwrArray[ALL_TARGET_HT40_20], 16) | + POW_SM(pPwrArray[ALL_TARGET_HT40_15], 8) | + POW_SM(pPwrArray[ALL_TARGET_HT40_14], 0) + ); + + return 0; +#undef POW_SM +} + +static void ar9003_hw_get_legacy_target_powers(struct ath_hw *ah, u16 freq, + u8 *targetPowerValT2, + bool is2GHz) +{ + targetPowerValT2[ALL_TARGET_LEGACY_6_24] = + ar9003_hw_eeprom_get_tgt_pwr(ah, LEGACY_TARGET_RATE_6_24, freq, + is2GHz); + targetPowerValT2[ALL_TARGET_LEGACY_36] = + ar9003_hw_eeprom_get_tgt_pwr(ah, LEGACY_TARGET_RATE_36, freq, + is2GHz); + targetPowerValT2[ALL_TARGET_LEGACY_48] = + ar9003_hw_eeprom_get_tgt_pwr(ah, LEGACY_TARGET_RATE_48, freq, + is2GHz); + targetPowerValT2[ALL_TARGET_LEGACY_54] = + ar9003_hw_eeprom_get_tgt_pwr(ah, LEGACY_TARGET_RATE_54, freq, + is2GHz); +} + +static void ar9003_hw_get_cck_target_powers(struct ath_hw *ah, u16 freq, + u8 *targetPowerValT2) +{ + targetPowerValT2[ALL_TARGET_LEGACY_1L_5L] = + ar9003_hw_eeprom_get_cck_tgt_pwr(ah, LEGACY_TARGET_RATE_1L_5L, + freq); + targetPowerValT2[ALL_TARGET_LEGACY_5S] = + ar9003_hw_eeprom_get_cck_tgt_pwr(ah, LEGACY_TARGET_RATE_5S, freq); + targetPowerValT2[ALL_TARGET_LEGACY_11L] = + ar9003_hw_eeprom_get_cck_tgt_pwr(ah, LEGACY_TARGET_RATE_11L, freq); + targetPowerValT2[ALL_TARGET_LEGACY_11S] = + ar9003_hw_eeprom_get_cck_tgt_pwr(ah, LEGACY_TARGET_RATE_11S, freq); +} + +static void ar9003_hw_get_ht20_target_powers(struct ath_hw *ah, u16 freq, + u8 *targetPowerValT2, bool is2GHz) +{ + targetPowerValT2[ALL_TARGET_HT20_0_8_16] = + ar9003_hw_eeprom_get_ht20_tgt_pwr(ah, HT_TARGET_RATE_0_8_16, freq, + is2GHz); + targetPowerValT2[ALL_TARGET_HT20_1_3_9_11_17_19] = + ar9003_hw_eeprom_get_ht20_tgt_pwr(ah, HT_TARGET_RATE_1_3_9_11_17_19, + freq, is2GHz); + targetPowerValT2[ALL_TARGET_HT20_4] = + ar9003_hw_eeprom_get_ht20_tgt_pwr(ah, HT_TARGET_RATE_4, freq, + is2GHz); + targetPowerValT2[ALL_TARGET_HT20_5] = + ar9003_hw_eeprom_get_ht20_tgt_pwr(ah, HT_TARGET_RATE_5, freq, + is2GHz); + targetPowerValT2[ALL_TARGET_HT20_6] = + ar9003_hw_eeprom_get_ht20_tgt_pwr(ah, HT_TARGET_RATE_6, freq, + is2GHz); + targetPowerValT2[ALL_TARGET_HT20_7] = + ar9003_hw_eeprom_get_ht20_tgt_pwr(ah, HT_TARGET_RATE_7, freq, + is2GHz); + targetPowerValT2[ALL_TARGET_HT20_12] = + ar9003_hw_eeprom_get_ht20_tgt_pwr(ah, HT_TARGET_RATE_12, freq, + is2GHz); + targetPowerValT2[ALL_TARGET_HT20_13] = + ar9003_hw_eeprom_get_ht20_tgt_pwr(ah, HT_TARGET_RATE_13, freq, + is2GHz); + targetPowerValT2[ALL_TARGET_HT20_14] = + ar9003_hw_eeprom_get_ht20_tgt_pwr(ah, HT_TARGET_RATE_14, freq, + is2GHz); + targetPowerValT2[ALL_TARGET_HT20_15] = + ar9003_hw_eeprom_get_ht20_tgt_pwr(ah, HT_TARGET_RATE_15, freq, + is2GHz); + targetPowerValT2[ALL_TARGET_HT20_20] = + ar9003_hw_eeprom_get_ht20_tgt_pwr(ah, HT_TARGET_RATE_20, freq, + is2GHz); + targetPowerValT2[ALL_TARGET_HT20_21] = + ar9003_hw_eeprom_get_ht20_tgt_pwr(ah, HT_TARGET_RATE_21, freq, + is2GHz); + targetPowerValT2[ALL_TARGET_HT20_22] = + ar9003_hw_eeprom_get_ht20_tgt_pwr(ah, HT_TARGET_RATE_22, freq, + is2GHz); + targetPowerValT2[ALL_TARGET_HT20_23] = + ar9003_hw_eeprom_get_ht20_tgt_pwr(ah, HT_TARGET_RATE_23, freq, + is2GHz); +} + +static void ar9003_hw_get_ht40_target_powers(struct ath_hw *ah, + u16 freq, + u8 *targetPowerValT2, + bool is2GHz) +{ + /* XXX: hard code for now, need to get from eeprom struct */ + u8 ht40PowerIncForPdadc = 0; + + targetPowerValT2[ALL_TARGET_HT40_0_8_16] = + ar9003_hw_eeprom_get_ht40_tgt_pwr(ah, HT_TARGET_RATE_0_8_16, freq, + is2GHz) + ht40PowerIncForPdadc; + targetPowerValT2[ALL_TARGET_HT40_1_3_9_11_17_19] = + ar9003_hw_eeprom_get_ht40_tgt_pwr(ah, HT_TARGET_RATE_1_3_9_11_17_19, + freq, + is2GHz) + ht40PowerIncForPdadc; + targetPowerValT2[ALL_TARGET_HT40_4] = + ar9003_hw_eeprom_get_ht40_tgt_pwr(ah, HT_TARGET_RATE_4, freq, + is2GHz) + ht40PowerIncForPdadc; + targetPowerValT2[ALL_TARGET_HT40_5] = + ar9003_hw_eeprom_get_ht40_tgt_pwr(ah, HT_TARGET_RATE_5, freq, + is2GHz) + ht40PowerIncForPdadc; + targetPowerValT2[ALL_TARGET_HT40_6] = + ar9003_hw_eeprom_get_ht40_tgt_pwr(ah, HT_TARGET_RATE_6, freq, + is2GHz) + ht40PowerIncForPdadc; + targetPowerValT2[ALL_TARGET_HT40_7] = + ar9003_hw_eeprom_get_ht40_tgt_pwr(ah, HT_TARGET_RATE_7, freq, + is2GHz) + ht40PowerIncForPdadc; + targetPowerValT2[ALL_TARGET_HT40_12] = + ar9003_hw_eeprom_get_ht40_tgt_pwr(ah, HT_TARGET_RATE_12, freq, + is2GHz) + ht40PowerIncForPdadc; + targetPowerValT2[ALL_TARGET_HT40_13] = + ar9003_hw_eeprom_get_ht40_tgt_pwr(ah, HT_TARGET_RATE_13, freq, + is2GHz) + ht40PowerIncForPdadc; + targetPowerValT2[ALL_TARGET_HT40_14] = + ar9003_hw_eeprom_get_ht40_tgt_pwr(ah, HT_TARGET_RATE_14, freq, + is2GHz) + ht40PowerIncForPdadc; + targetPowerValT2[ALL_TARGET_HT40_15] = + ar9003_hw_eeprom_get_ht40_tgt_pwr(ah, HT_TARGET_RATE_15, freq, + is2GHz) + ht40PowerIncForPdadc; + targetPowerValT2[ALL_TARGET_HT40_20] = + ar9003_hw_eeprom_get_ht40_tgt_pwr(ah, HT_TARGET_RATE_20, freq, + is2GHz) + ht40PowerIncForPdadc; + targetPowerValT2[ALL_TARGET_HT40_21] = + ar9003_hw_eeprom_get_ht40_tgt_pwr(ah, HT_TARGET_RATE_21, freq, + is2GHz) + ht40PowerIncForPdadc; + targetPowerValT2[ALL_TARGET_HT40_22] = + ar9003_hw_eeprom_get_ht40_tgt_pwr(ah, HT_TARGET_RATE_22, freq, + is2GHz) + ht40PowerIncForPdadc; + targetPowerValT2[ALL_TARGET_HT40_23] = + ar9003_hw_eeprom_get_ht40_tgt_pwr(ah, HT_TARGET_RATE_23, freq, + is2GHz) + ht40PowerIncForPdadc; +} + +static void ar9003_hw_get_target_power_eeprom(struct ath_hw *ah, + struct ath9k_channel *chan, + u8 *targetPowerValT2) +{ + bool is2GHz = IS_CHAN_2GHZ(chan); + unsigned int i = 0; + struct ath_common *common = ath9k_hw_common(ah); + u16 freq = chan->channel; + + if (is2GHz) + ar9003_hw_get_cck_target_powers(ah, freq, targetPowerValT2); + + ar9003_hw_get_legacy_target_powers(ah, freq, targetPowerValT2, is2GHz); + ar9003_hw_get_ht20_target_powers(ah, freq, targetPowerValT2, is2GHz); + + if (IS_CHAN_HT40(chan)) + ar9003_hw_get_ht40_target_powers(ah, freq, targetPowerValT2, + is2GHz); + + for (i = 0; i < ar9300RateSize; i++) { + ath_dbg(common, EEPROM, "TPC[%02d] 0x%08x\n", + i, targetPowerValT2[i]); + } +} + +static int ar9003_hw_cal_pier_get(struct ath_hw *ah, + int mode, + int ipier, + int ichain, + int *pfrequency, + int *pcorrection, + int *ptemperature, int *pvoltage) +{ + u8 *pCalPier; + struct ar9300_cal_data_per_freq_op_loop *pCalPierStruct; + int is2GHz; + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + struct ath_common *common = ath9k_hw_common(ah); + + if (ichain >= AR9300_MAX_CHAINS) { + ath_dbg(common, EEPROM, + "Invalid chain index, must be less than %d\n", + AR9300_MAX_CHAINS); + return -1; + } + + if (mode) { /* 5GHz */ + if (ipier >= AR9300_NUM_5G_CAL_PIERS) { + ath_dbg(common, EEPROM, + "Invalid 5GHz cal pier index, must be less than %d\n", + AR9300_NUM_5G_CAL_PIERS); + return -1; + } + pCalPier = &(eep->calFreqPier5G[ipier]); + pCalPierStruct = &(eep->calPierData5G[ichain][ipier]); + is2GHz = 0; + } else { + if (ipier >= AR9300_NUM_2G_CAL_PIERS) { + ath_dbg(common, EEPROM, + "Invalid 2GHz cal pier index, must be less than %d\n", + AR9300_NUM_2G_CAL_PIERS); + return -1; + } + + pCalPier = &(eep->calFreqPier2G[ipier]); + pCalPierStruct = &(eep->calPierData2G[ichain][ipier]); + is2GHz = 1; + } + + *pfrequency = ath9k_hw_fbin2freq(*pCalPier, is2GHz); + *pcorrection = pCalPierStruct->refPower; + *ptemperature = pCalPierStruct->tempMeas; + *pvoltage = pCalPierStruct->voltMeas; + + return 0; +} + +static void ar9003_hw_power_control_override(struct ath_hw *ah, + int frequency, + int *correction, + int *voltage, int *temperature) +{ + int temp_slope = 0, temp_slope1 = 0, temp_slope2 = 0; + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + int f[8], t[8], t1[3], t2[3], i; + + REG_RMW(ah, AR_PHY_TPC_11_B0, + (correction[0] << AR_PHY_TPC_OLPC_GAIN_DELTA_S), + AR_PHY_TPC_OLPC_GAIN_DELTA); + if (ah->caps.tx_chainmask & BIT(1)) + REG_RMW(ah, AR_PHY_TPC_11_B1, + (correction[1] << AR_PHY_TPC_OLPC_GAIN_DELTA_S), + AR_PHY_TPC_OLPC_GAIN_DELTA); + if (ah->caps.tx_chainmask & BIT(2)) + REG_RMW(ah, AR_PHY_TPC_11_B2, + (correction[2] << AR_PHY_TPC_OLPC_GAIN_DELTA_S), + AR_PHY_TPC_OLPC_GAIN_DELTA); + + /* enable open loop power control on chip */ + REG_RMW(ah, AR_PHY_TPC_6_B0, + (3 << AR_PHY_TPC_6_ERROR_EST_MODE_S), + AR_PHY_TPC_6_ERROR_EST_MODE); + if (ah->caps.tx_chainmask & BIT(1)) + REG_RMW(ah, AR_PHY_TPC_6_B1, + (3 << AR_PHY_TPC_6_ERROR_EST_MODE_S), + AR_PHY_TPC_6_ERROR_EST_MODE); + if (ah->caps.tx_chainmask & BIT(2)) + REG_RMW(ah, AR_PHY_TPC_6_B2, + (3 << AR_PHY_TPC_6_ERROR_EST_MODE_S), + AR_PHY_TPC_6_ERROR_EST_MODE); + + /* + * enable temperature compensation + * Need to use register names + */ + if (frequency < 4000) { + temp_slope = eep->modalHeader2G.tempSlope; + } else { + if (AR_SREV_9550(ah)) { + t[0] = eep->base_ext1.tempslopextension[2]; + t1[0] = eep->base_ext1.tempslopextension[3]; + t2[0] = eep->base_ext1.tempslopextension[4]; + f[0] = 5180; + + t[1] = eep->modalHeader5G.tempSlope; + t1[1] = eep->base_ext1.tempslopextension[0]; + t2[1] = eep->base_ext1.tempslopextension[1]; + f[1] = 5500; + + t[2] = eep->base_ext1.tempslopextension[5]; + t1[2] = eep->base_ext1.tempslopextension[6]; + t2[2] = eep->base_ext1.tempslopextension[7]; + f[2] = 5785; + + temp_slope = ar9003_hw_power_interpolate(frequency, + f, t, 3); + temp_slope1 = ar9003_hw_power_interpolate(frequency, + f, t1, 3); + temp_slope2 = ar9003_hw_power_interpolate(frequency, + f, t2, 3); + + goto tempslope; + } + + if ((eep->baseEepHeader.miscConfiguration & 0x20) != 0) { + for (i = 0; i < 8; i++) { + t[i] = eep->base_ext1.tempslopextension[i]; + f[i] = FBIN2FREQ(eep->calFreqPier5G[i], 0); + } + temp_slope = ar9003_hw_power_interpolate((s32) frequency, + f, t, 8); + } else if (eep->base_ext2.tempSlopeLow != 0) { + t[0] = eep->base_ext2.tempSlopeLow; + f[0] = 5180; + t[1] = eep->modalHeader5G.tempSlope; + f[1] = 5500; + t[2] = eep->base_ext2.tempSlopeHigh; + f[2] = 5785; + temp_slope = ar9003_hw_power_interpolate((s32) frequency, + f, t, 3); + } else { + temp_slope = eep->modalHeader5G.tempSlope; + } + } + +tempslope: + if (AR_SREV_9550(ah)) { + /* + * AR955x has tempSlope register for each chain. + * Check whether temp_compensation feature is enabled or not. + */ + if (eep->baseEepHeader.featureEnable & 0x1) { + if (frequency < 4000) { + REG_RMW_FIELD(ah, AR_PHY_TPC_19, + AR_PHY_TPC_19_ALPHA_THERM, + eep->base_ext2.tempSlopeLow); + REG_RMW_FIELD(ah, AR_PHY_TPC_19_B1, + AR_PHY_TPC_19_ALPHA_THERM, + temp_slope); + REG_RMW_FIELD(ah, AR_PHY_TPC_19_B2, + AR_PHY_TPC_19_ALPHA_THERM, + eep->base_ext2.tempSlopeHigh); + } else { + REG_RMW_FIELD(ah, AR_PHY_TPC_19, + AR_PHY_TPC_19_ALPHA_THERM, + temp_slope); + REG_RMW_FIELD(ah, AR_PHY_TPC_19_B1, + AR_PHY_TPC_19_ALPHA_THERM, + temp_slope1); + REG_RMW_FIELD(ah, AR_PHY_TPC_19_B2, + AR_PHY_TPC_19_ALPHA_THERM, + temp_slope2); + } + } else { + /* + * If temp compensation is not enabled, + * set all registers to 0. + */ + REG_RMW_FIELD(ah, AR_PHY_TPC_19, + AR_PHY_TPC_19_ALPHA_THERM, 0); + REG_RMW_FIELD(ah, AR_PHY_TPC_19_B1, + AR_PHY_TPC_19_ALPHA_THERM, 0); + REG_RMW_FIELD(ah, AR_PHY_TPC_19_B2, + AR_PHY_TPC_19_ALPHA_THERM, 0); + } + } else { + REG_RMW_FIELD(ah, AR_PHY_TPC_19, + AR_PHY_TPC_19_ALPHA_THERM, temp_slope); + } + + if (AR_SREV_9462_20(ah)) + REG_RMW_FIELD(ah, AR_PHY_TPC_19_B1, + AR_PHY_TPC_19_B1_ALPHA_THERM, temp_slope); + + + REG_RMW_FIELD(ah, AR_PHY_TPC_18, AR_PHY_TPC_18_THERM_CAL_VALUE, + temperature[0]); +} + +/* Apply the recorded correction values. */ +static int ar9003_hw_calibration_apply(struct ath_hw *ah, int frequency) +{ + int ichain, ipier, npier; + int mode; + int lfrequency[AR9300_MAX_CHAINS], + lcorrection[AR9300_MAX_CHAINS], + ltemperature[AR9300_MAX_CHAINS], lvoltage[AR9300_MAX_CHAINS]; + int hfrequency[AR9300_MAX_CHAINS], + hcorrection[AR9300_MAX_CHAINS], + htemperature[AR9300_MAX_CHAINS], hvoltage[AR9300_MAX_CHAINS]; + int fdiff; + int correction[AR9300_MAX_CHAINS], + voltage[AR9300_MAX_CHAINS], temperature[AR9300_MAX_CHAINS]; + int pfrequency, pcorrection, ptemperature, pvoltage; + struct ath_common *common = ath9k_hw_common(ah); + + mode = (frequency >= 4000); + if (mode) + npier = AR9300_NUM_5G_CAL_PIERS; + else + npier = AR9300_NUM_2G_CAL_PIERS; + + for (ichain = 0; ichain < AR9300_MAX_CHAINS; ichain++) { + lfrequency[ichain] = 0; + hfrequency[ichain] = 100000; + } + /* identify best lower and higher frequency calibration measurement */ + for (ichain = 0; ichain < AR9300_MAX_CHAINS; ichain++) { + for (ipier = 0; ipier < npier; ipier++) { + if (!ar9003_hw_cal_pier_get(ah, mode, ipier, ichain, + &pfrequency, &pcorrection, + &ptemperature, &pvoltage)) { + fdiff = frequency - pfrequency; + + /* + * this measurement is higher than + * our desired frequency + */ + if (fdiff <= 0) { + if (hfrequency[ichain] <= 0 || + hfrequency[ichain] >= 100000 || + fdiff > + (frequency - hfrequency[ichain])) { + /* + * new best higher + * frequency measurement + */ + hfrequency[ichain] = pfrequency; + hcorrection[ichain] = + pcorrection; + htemperature[ichain] = + ptemperature; + hvoltage[ichain] = pvoltage; + } + } + if (fdiff >= 0) { + if (lfrequency[ichain] <= 0 + || fdiff < + (frequency - lfrequency[ichain])) { + /* + * new best lower + * frequency measurement + */ + lfrequency[ichain] = pfrequency; + lcorrection[ichain] = + pcorrection; + ltemperature[ichain] = + ptemperature; + lvoltage[ichain] = pvoltage; + } + } + } + } + } + + /* interpolate */ + for (ichain = 0; ichain < AR9300_MAX_CHAINS; ichain++) { + ath_dbg(common, EEPROM, "ch=%d f=%d low=%d %d h=%d %d\n", + ichain, frequency, lfrequency[ichain], + lcorrection[ichain], hfrequency[ichain], + hcorrection[ichain]); + /* they're the same, so just pick one */ + if (hfrequency[ichain] == lfrequency[ichain]) { + correction[ichain] = lcorrection[ichain]; + voltage[ichain] = lvoltage[ichain]; + temperature[ichain] = ltemperature[ichain]; + } + /* the low frequency is good */ + else if (frequency - lfrequency[ichain] < 1000) { + /* so is the high frequency, interpolate */ + if (hfrequency[ichain] - frequency < 1000) { + + correction[ichain] = interpolate(frequency, + lfrequency[ichain], + hfrequency[ichain], + lcorrection[ichain], + hcorrection[ichain]); + + temperature[ichain] = interpolate(frequency, + lfrequency[ichain], + hfrequency[ichain], + ltemperature[ichain], + htemperature[ichain]); + + voltage[ichain] = interpolate(frequency, + lfrequency[ichain], + hfrequency[ichain], + lvoltage[ichain], + hvoltage[ichain]); + } + /* only low is good, use it */ + else { + correction[ichain] = lcorrection[ichain]; + temperature[ichain] = ltemperature[ichain]; + voltage[ichain] = lvoltage[ichain]; + } + } + /* only high is good, use it */ + else if (hfrequency[ichain] - frequency < 1000) { + correction[ichain] = hcorrection[ichain]; + temperature[ichain] = htemperature[ichain]; + voltage[ichain] = hvoltage[ichain]; + } else { /* nothing is good, presume 0???? */ + correction[ichain] = 0; + temperature[ichain] = 0; + voltage[ichain] = 0; + } + } + + ar9003_hw_power_control_override(ah, frequency, correction, voltage, + temperature); + + ath_dbg(common, EEPROM, + "for frequency=%d, calibration correction = %d %d %d\n", + frequency, correction[0], correction[1], correction[2]); + + return 0; +} + +static u16 ar9003_hw_get_direct_edge_power(struct ar9300_eeprom *eep, + int idx, + int edge, + bool is2GHz) +{ + struct cal_ctl_data_2g *ctl_2g = eep->ctlPowerData_2G; + struct cal_ctl_data_5g *ctl_5g = eep->ctlPowerData_5G; + + if (is2GHz) + return CTL_EDGE_TPOWER(ctl_2g[idx].ctlEdges[edge]); + else + return CTL_EDGE_TPOWER(ctl_5g[idx].ctlEdges[edge]); +} + +static u16 ar9003_hw_get_indirect_edge_power(struct ar9300_eeprom *eep, + int idx, + unsigned int edge, + u16 freq, + bool is2GHz) +{ + struct cal_ctl_data_2g *ctl_2g = eep->ctlPowerData_2G; + struct cal_ctl_data_5g *ctl_5g = eep->ctlPowerData_5G; + + u8 *ctl_freqbin = is2GHz ? + &eep->ctl_freqbin_2G[idx][0] : + &eep->ctl_freqbin_5G[idx][0]; + + if (is2GHz) { + if (ath9k_hw_fbin2freq(ctl_freqbin[edge - 1], 1) < freq && + CTL_EDGE_FLAGS(ctl_2g[idx].ctlEdges[edge - 1])) + return CTL_EDGE_TPOWER(ctl_2g[idx].ctlEdges[edge - 1]); + } else { + if (ath9k_hw_fbin2freq(ctl_freqbin[edge - 1], 0) < freq && + CTL_EDGE_FLAGS(ctl_5g[idx].ctlEdges[edge - 1])) + return CTL_EDGE_TPOWER(ctl_5g[idx].ctlEdges[edge - 1]); + } + + return MAX_RATE_POWER; +} + +/* + * Find the maximum conformance test limit for the given channel and CTL info + */ +static u16 ar9003_hw_get_max_edge_power(struct ar9300_eeprom *eep, + u16 freq, int idx, bool is2GHz) +{ + u16 twiceMaxEdgePower = MAX_RATE_POWER; + u8 *ctl_freqbin = is2GHz ? + &eep->ctl_freqbin_2G[idx][0] : + &eep->ctl_freqbin_5G[idx][0]; + u16 num_edges = is2GHz ? + AR9300_NUM_BAND_EDGES_2G : AR9300_NUM_BAND_EDGES_5G; + unsigned int edge; + + /* Get the edge power */ + for (edge = 0; + (edge < num_edges) && (ctl_freqbin[edge] != AR5416_BCHAN_UNUSED); + edge++) { + /* + * If there's an exact channel match or an inband flag set + * on the lower channel use the given rdEdgePower + */ + if (freq == ath9k_hw_fbin2freq(ctl_freqbin[edge], is2GHz)) { + twiceMaxEdgePower = + ar9003_hw_get_direct_edge_power(eep, idx, + edge, is2GHz); + break; + } else if ((edge > 0) && + (freq < ath9k_hw_fbin2freq(ctl_freqbin[edge], + is2GHz))) { + twiceMaxEdgePower = + ar9003_hw_get_indirect_edge_power(eep, idx, + edge, freq, + is2GHz); + /* + * Leave loop - no more affecting edges possible in + * this monotonic increasing list + */ + break; + } + } + return twiceMaxEdgePower; +} + +static void ar9003_hw_set_power_per_rate_table(struct ath_hw *ah, + struct ath9k_channel *chan, + u8 *pPwrArray, u16 cfgCtl, + u8 antenna_reduction, + u16 powerLimit) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ar9300_eeprom *pEepData = &ah->eeprom.ar9300_eep; + u16 twiceMaxEdgePower; + int i; + u16 scaledPower = 0, minCtlPower; + static const u16 ctlModesFor11a[] = { + CTL_11A, CTL_5GHT20, CTL_11A_EXT, CTL_5GHT40 + }; + static const u16 ctlModesFor11g[] = { + CTL_11B, CTL_11G, CTL_2GHT20, CTL_11B_EXT, + CTL_11G_EXT, CTL_2GHT40 + }; + u16 numCtlModes; + const u16 *pCtlMode; + u16 ctlMode, freq; + struct chan_centers centers; + u8 *ctlIndex; + u8 ctlNum; + u16 twiceMinEdgePower; + bool is2ghz = IS_CHAN_2GHZ(chan); + + ath9k_hw_get_channel_centers(ah, chan, ¢ers); + scaledPower = ath9k_hw_get_scaled_power(ah, powerLimit, + antenna_reduction); + + if (is2ghz) { + /* Setup for CTL modes */ + /* CTL_11B, CTL_11G, CTL_2GHT20 */ + numCtlModes = + ARRAY_SIZE(ctlModesFor11g) - + SUB_NUM_CTL_MODES_AT_2G_40; + pCtlMode = ctlModesFor11g; + if (IS_CHAN_HT40(chan)) + /* All 2G CTL's */ + numCtlModes = ARRAY_SIZE(ctlModesFor11g); + } else { + /* Setup for CTL modes */ + /* CTL_11A, CTL_5GHT20 */ + numCtlModes = ARRAY_SIZE(ctlModesFor11a) - + SUB_NUM_CTL_MODES_AT_5G_40; + pCtlMode = ctlModesFor11a; + if (IS_CHAN_HT40(chan)) + /* All 5G CTL's */ + numCtlModes = ARRAY_SIZE(ctlModesFor11a); + } + + /* + * For MIMO, need to apply regulatory caps individually across + * dynamically running modes: CCK, OFDM, HT20, HT40 + * + * The outer loop walks through each possible applicable runtime mode. + * The inner loop walks through each ctlIndex entry in EEPROM. + * The ctl value is encoded as [7:4] == test group, [3:0] == test mode. + */ + for (ctlMode = 0; ctlMode < numCtlModes; ctlMode++) { + bool isHt40CtlMode = (pCtlMode[ctlMode] == CTL_5GHT40) || + (pCtlMode[ctlMode] == CTL_2GHT40); + if (isHt40CtlMode) + freq = centers.synth_center; + else if (pCtlMode[ctlMode] & EXT_ADDITIVE) + freq = centers.ext_center; + else + freq = centers.ctl_center; + + ath_dbg(common, REGULATORY, + "LOOP-Mode ctlMode %d < %d, isHt40CtlMode %d, EXT_ADDITIVE %d\n", + ctlMode, numCtlModes, isHt40CtlMode, + (pCtlMode[ctlMode] & EXT_ADDITIVE)); + + /* walk through each CTL index stored in EEPROM */ + if (is2ghz) { + ctlIndex = pEepData->ctlIndex_2G; + ctlNum = AR9300_NUM_CTLS_2G; + } else { + ctlIndex = pEepData->ctlIndex_5G; + ctlNum = AR9300_NUM_CTLS_5G; + } + + twiceMaxEdgePower = MAX_RATE_POWER; + for (i = 0; (i < ctlNum) && ctlIndex[i]; i++) { + ath_dbg(common, REGULATORY, + "LOOP-Ctlidx %d: cfgCtl 0x%2.2x pCtlMode 0x%2.2x ctlIndex 0x%2.2x chan %d\n", + i, cfgCtl, pCtlMode[ctlMode], ctlIndex[i], + chan->channel); + + /* + * compare test group from regulatory + * channel list with test mode from pCtlMode + * list + */ + if ((((cfgCtl & ~CTL_MODE_M) | + (pCtlMode[ctlMode] & CTL_MODE_M)) == + ctlIndex[i]) || + (((cfgCtl & ~CTL_MODE_M) | + (pCtlMode[ctlMode] & CTL_MODE_M)) == + ((ctlIndex[i] & CTL_MODE_M) | + SD_NO_CTL))) { + twiceMinEdgePower = + ar9003_hw_get_max_edge_power(pEepData, + freq, i, + is2ghz); + + if ((cfgCtl & ~CTL_MODE_M) == SD_NO_CTL) + /* + * Find the minimum of all CTL + * edge powers that apply to + * this channel + */ + twiceMaxEdgePower = + min(twiceMaxEdgePower, + twiceMinEdgePower); + else { + /* specific */ + twiceMaxEdgePower = twiceMinEdgePower; + break; + } + } + } + + minCtlPower = (u8)min(twiceMaxEdgePower, scaledPower); + + ath_dbg(common, REGULATORY, + "SEL-Min ctlMode %d pCtlMode %d 2xMaxEdge %d sP %d minCtlPwr %d\n", + ctlMode, pCtlMode[ctlMode], twiceMaxEdgePower, + scaledPower, minCtlPower); + + /* Apply ctl mode to correct target power set */ + switch (pCtlMode[ctlMode]) { + case CTL_11B: + for (i = ALL_TARGET_LEGACY_1L_5L; + i <= ALL_TARGET_LEGACY_11S; i++) + pPwrArray[i] = (u8)min((u16)pPwrArray[i], + minCtlPower); + break; + case CTL_11A: + case CTL_11G: + for (i = ALL_TARGET_LEGACY_6_24; + i <= ALL_TARGET_LEGACY_54; i++) + pPwrArray[i] = (u8)min((u16)pPwrArray[i], + minCtlPower); + break; + case CTL_5GHT20: + case CTL_2GHT20: + for (i = ALL_TARGET_HT20_0_8_16; + i <= ALL_TARGET_HT20_23; i++) { + pPwrArray[i] = (u8)min((u16)pPwrArray[i], + minCtlPower); + if (ath9k_hw_mci_is_enabled(ah)) + pPwrArray[i] = + (u8)min((u16)pPwrArray[i], + ar9003_mci_get_max_txpower(ah, + pCtlMode[ctlMode])); + } + break; + case CTL_5GHT40: + case CTL_2GHT40: + for (i = ALL_TARGET_HT40_0_8_16; + i <= ALL_TARGET_HT40_23; i++) { + pPwrArray[i] = (u8)min((u16)pPwrArray[i], + minCtlPower); + if (ath9k_hw_mci_is_enabled(ah)) + pPwrArray[i] = + (u8)min((u16)pPwrArray[i], + ar9003_mci_get_max_txpower(ah, + pCtlMode[ctlMode])); + } + break; + default: + break; + } + } /* end ctl mode checking */ +} + +static inline u8 mcsidx_to_tgtpwridx(unsigned int mcs_idx, u8 base_pwridx) +{ + u8 mod_idx = mcs_idx % 8; + + if (mod_idx <= 3) + return mod_idx ? (base_pwridx + 1) : base_pwridx; + else + return base_pwridx + 4 * (mcs_idx / 8) + mod_idx - 2; +} + +static void ar9003_paprd_set_txpower(struct ath_hw *ah, + struct ath9k_channel *chan, + u8 *targetPowerValT2) +{ + int i; + + if (!ar9003_is_paprd_enabled(ah)) + return; + + if (IS_CHAN_HT40(chan)) + i = ALL_TARGET_HT40_7; + else + i = ALL_TARGET_HT20_7; + + if (IS_CHAN_2GHZ(chan)) { + if (!AR_SREV_9330(ah) && !AR_SREV_9340(ah) && + !AR_SREV_9462(ah) && !AR_SREV_9565(ah)) { + if (IS_CHAN_HT40(chan)) + i = ALL_TARGET_HT40_0_8_16; + else + i = ALL_TARGET_HT20_0_8_16; + } + } + + ah->paprd_target_power = targetPowerValT2[i]; +} + +static void ath9k_hw_ar9300_set_txpower(struct ath_hw *ah, + struct ath9k_channel *chan, u16 cfgCtl, + u8 twiceAntennaReduction, + u8 powerLimit, bool test) +{ + struct ath_regulatory *regulatory = ath9k_hw_regulatory(ah); + struct ath_common *common = ath9k_hw_common(ah); + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + struct ar9300_modal_eep_header *modal_hdr; + u8 targetPowerValT2[ar9300RateSize]; + u8 target_power_val_t2_eep[ar9300RateSize]; + unsigned int i = 0, paprd_scale_factor = 0; + u8 pwr_idx, min_pwridx = 0; + + memset(targetPowerValT2, 0 , sizeof(targetPowerValT2)); + + /* + * Get target powers from EEPROM - our baseline for TX Power + */ + ar9003_hw_get_target_power_eeprom(ah, chan, targetPowerValT2); + + if (ar9003_is_paprd_enabled(ah)) { + if (IS_CHAN_2GHZ(chan)) + modal_hdr = &eep->modalHeader2G; + else + modal_hdr = &eep->modalHeader5G; + + ah->paprd_ratemask = + le32_to_cpu(modal_hdr->papdRateMaskHt20) & + AR9300_PAPRD_RATE_MASK; + + ah->paprd_ratemask_ht40 = + le32_to_cpu(modal_hdr->papdRateMaskHt40) & + AR9300_PAPRD_RATE_MASK; + + paprd_scale_factor = ar9003_get_paprd_scale_factor(ah, chan); + min_pwridx = IS_CHAN_HT40(chan) ? ALL_TARGET_HT40_0_8_16 : + ALL_TARGET_HT20_0_8_16; + + if (!ah->paprd_table_write_done) { + memcpy(target_power_val_t2_eep, targetPowerValT2, + sizeof(targetPowerValT2)); + for (i = 0; i < 24; i++) { + pwr_idx = mcsidx_to_tgtpwridx(i, min_pwridx); + if (ah->paprd_ratemask & (1 << i)) { + if (targetPowerValT2[pwr_idx] && + targetPowerValT2[pwr_idx] == + target_power_val_t2_eep[pwr_idx]) + targetPowerValT2[pwr_idx] -= + paprd_scale_factor; + } + } + } + memcpy(target_power_val_t2_eep, targetPowerValT2, + sizeof(targetPowerValT2)); + } + + ar9003_hw_set_power_per_rate_table(ah, chan, + targetPowerValT2, cfgCtl, + twiceAntennaReduction, + powerLimit); + + if (ar9003_is_paprd_enabled(ah)) { + for (i = 0; i < ar9300RateSize; i++) { + if ((ah->paprd_ratemask & (1 << i)) && + (abs(targetPowerValT2[i] - + target_power_val_t2_eep[i]) > + paprd_scale_factor)) { + ah->paprd_ratemask &= ~(1 << i); + ath_dbg(common, EEPROM, + "paprd disabled for mcs %d\n", i); + } + } + } + + regulatory->max_power_level = 0; + for (i = 0; i < ar9300RateSize; i++) { + if (targetPowerValT2[i] > regulatory->max_power_level) + regulatory->max_power_level = targetPowerValT2[i]; + } + + ath9k_hw_update_regulatory_maxpower(ah); + + if (test) + return; + + for (i = 0; i < ar9300RateSize; i++) { + ath_dbg(common, EEPROM, "TPC[%02d] 0x%08x\n", + i, targetPowerValT2[i]); + } + + /* Write target power array to registers */ + ar9003_hw_tx_power_regwrite(ah, targetPowerValT2); + ar9003_hw_calibration_apply(ah, chan->channel); + ar9003_paprd_set_txpower(ah, chan, targetPowerValT2); +} + +static u16 ath9k_hw_ar9300_get_spur_channel(struct ath_hw *ah, + u16 i, bool is2GHz) +{ + return AR_NO_SPUR; +} + +s32 ar9003_hw_get_tx_gain_idx(struct ath_hw *ah) +{ + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + + return (eep->baseEepHeader.txrxgain >> 4) & 0xf; /* bits 7:4 */ +} + +s32 ar9003_hw_get_rx_gain_idx(struct ath_hw *ah) +{ + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + + return (eep->baseEepHeader.txrxgain) & 0xf; /* bits 3:0 */ +} + +u8 *ar9003_get_spur_chan_ptr(struct ath_hw *ah, bool is2ghz) +{ + return ar9003_modal_header(ah, is2ghz)->spurChans; +} + +unsigned int ar9003_get_paprd_scale_factor(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + + if (IS_CHAN_2GHZ(chan)) + return MS(le32_to_cpu(eep->modalHeader2G.papdRateMaskHt20), + AR9300_PAPRD_SCALE_1); + else { + if (chan->channel >= 5700) + return MS(le32_to_cpu(eep->modalHeader5G.papdRateMaskHt20), + AR9300_PAPRD_SCALE_1); + else if (chan->channel >= 5400) + return MS(le32_to_cpu(eep->modalHeader5G.papdRateMaskHt40), + AR9300_PAPRD_SCALE_2); + else + return MS(le32_to_cpu(eep->modalHeader5G.papdRateMaskHt40), + AR9300_PAPRD_SCALE_1); + } +} + +const struct eeprom_ops eep_ar9300_ops = { + .check_eeprom = ath9k_hw_ar9300_check_eeprom, + .get_eeprom = ath9k_hw_ar9300_get_eeprom, + .fill_eeprom = ath9k_hw_ar9300_fill_eeprom, + .dump_eeprom = ath9k_hw_ar9003_dump_eeprom, + .get_eeprom_ver = ath9k_hw_ar9300_get_eeprom_ver, + .get_eeprom_rev = ath9k_hw_ar9300_get_eeprom_rev, + .set_board_values = ath9k_hw_ar9300_set_board_values, + .set_addac = ath9k_hw_ar9300_set_addac, + .set_txpower = ath9k_hw_ar9300_set_txpower, + .get_spur_channel = ath9k_hw_ar9300_get_spur_channel +}; diff --git a/drivers/net/wireless/ath/ath9k/ar9003_eeprom.h b/drivers/net/wireless/ath/ath9k/ar9003_eeprom.h new file mode 100644 index 0000000..874f657 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9003_eeprom.h @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef AR9003_EEPROM_H +#define AR9003_EEPROM_H + +#include + +#define AR9300_EEP_VER 0xD000 +#define AR9300_EEP_VER_MINOR_MASK 0xFFF +#define AR9300_EEP_MINOR_VER_1 0x1 +#define AR9300_EEP_MINOR_VER AR9300_EEP_MINOR_VER_1 + +/* 16-bit offset location start of calibration struct */ +#define AR9300_EEP_START_LOC 256 +#define AR9300_NUM_5G_CAL_PIERS 8 +#define AR9300_NUM_2G_CAL_PIERS 3 +#define AR9300_NUM_5G_20_TARGET_POWERS 8 +#define AR9300_NUM_5G_40_TARGET_POWERS 8 +#define AR9300_NUM_2G_CCK_TARGET_POWERS 2 +#define AR9300_NUM_2G_20_TARGET_POWERS 3 +#define AR9300_NUM_2G_40_TARGET_POWERS 3 +/* #define AR9300_NUM_CTLS 21 */ +#define AR9300_NUM_CTLS_5G 9 +#define AR9300_NUM_CTLS_2G 12 +#define AR9300_NUM_BAND_EDGES_5G 8 +#define AR9300_NUM_BAND_EDGES_2G 4 +#define AR9300_EEPMISC_BIG_ENDIAN 0x01 +#define AR9300_EEPMISC_WOW 0x02 +#define AR9300_CUSTOMER_DATA_SIZE 20 + +#define AR9300_MAX_CHAINS 3 +#define AR9300_ANT_16S 25 +#define AR9300_FUTURE_MODAL_SZ 6 + +#define AR9300_PAPRD_RATE_MASK 0x01ffffff +#define AR9300_PAPRD_SCALE_1 0x0e000000 +#define AR9300_PAPRD_SCALE_1_S 25 +#define AR9300_PAPRD_SCALE_2 0x70000000 +#define AR9300_PAPRD_SCALE_2_S 28 + +/* Delta from which to start power to pdadc table */ +/* This offset is used in both open loop and closed loop power control + * schemes. In open loop power control, it is not really needed, but for + * the "sake of consistency" it was kept. For certain AP designs, this + * value is overwritten by the value in the flag "pwrTableOffset" just + * before writing the pdadc vs pwr into the chip registers. + */ +#define AR9300_PWR_TABLE_OFFSET 0 + +/* byte addressable */ +#define AR9300_EEPROM_SIZE (16*1024) + +#define AR9300_BASE_ADDR_4K 0xfff +#define AR9300_BASE_ADDR 0x3ff +#define AR9300_BASE_ADDR_512 0x1ff + +#define AR9300_OTP_BASE \ + ((AR_SREV_9340(ah) || AR_SREV_9550(ah)) ? 0x30000 : 0x14000) +#define AR9300_OTP_STATUS \ + ((AR_SREV_9340(ah) || AR_SREV_9550(ah)) ? 0x30018 : 0x15f18) +#define AR9300_OTP_STATUS_TYPE 0x7 +#define AR9300_OTP_STATUS_VALID 0x4 +#define AR9300_OTP_STATUS_ACCESS_BUSY 0x2 +#define AR9300_OTP_STATUS_SM_BUSY 0x1 +#define AR9300_OTP_READ_DATA \ + ((AR_SREV_9340(ah) || AR_SREV_9550(ah)) ? 0x3001c : 0x15f1c) + +enum targetPowerHTRates { + HT_TARGET_RATE_0_8_16, + HT_TARGET_RATE_1_3_9_11_17_19, + HT_TARGET_RATE_4, + HT_TARGET_RATE_5, + HT_TARGET_RATE_6, + HT_TARGET_RATE_7, + HT_TARGET_RATE_12, + HT_TARGET_RATE_13, + HT_TARGET_RATE_14, + HT_TARGET_RATE_15, + HT_TARGET_RATE_20, + HT_TARGET_RATE_21, + HT_TARGET_RATE_22, + HT_TARGET_RATE_23 +}; + +enum targetPowerLegacyRates { + LEGACY_TARGET_RATE_6_24, + LEGACY_TARGET_RATE_36, + LEGACY_TARGET_RATE_48, + LEGACY_TARGET_RATE_54 +}; + +enum targetPowerCckRates { + LEGACY_TARGET_RATE_1L_5L, + LEGACY_TARGET_RATE_5S, + LEGACY_TARGET_RATE_11L, + LEGACY_TARGET_RATE_11S +}; + +enum ar9300_Rates { + ALL_TARGET_LEGACY_6_24, + ALL_TARGET_LEGACY_36, + ALL_TARGET_LEGACY_48, + ALL_TARGET_LEGACY_54, + ALL_TARGET_LEGACY_1L_5L, + ALL_TARGET_LEGACY_5S, + ALL_TARGET_LEGACY_11L, + ALL_TARGET_LEGACY_11S, + ALL_TARGET_HT20_0_8_16, + ALL_TARGET_HT20_1_3_9_11_17_19, + ALL_TARGET_HT20_4, + ALL_TARGET_HT20_5, + ALL_TARGET_HT20_6, + ALL_TARGET_HT20_7, + ALL_TARGET_HT20_12, + ALL_TARGET_HT20_13, + ALL_TARGET_HT20_14, + ALL_TARGET_HT20_15, + ALL_TARGET_HT20_20, + ALL_TARGET_HT20_21, + ALL_TARGET_HT20_22, + ALL_TARGET_HT20_23, + ALL_TARGET_HT40_0_8_16, + ALL_TARGET_HT40_1_3_9_11_17_19, + ALL_TARGET_HT40_4, + ALL_TARGET_HT40_5, + ALL_TARGET_HT40_6, + ALL_TARGET_HT40_7, + ALL_TARGET_HT40_12, + ALL_TARGET_HT40_13, + ALL_TARGET_HT40_14, + ALL_TARGET_HT40_15, + ALL_TARGET_HT40_20, + ALL_TARGET_HT40_21, + ALL_TARGET_HT40_22, + ALL_TARGET_HT40_23, + ar9300RateSize, +}; + + +struct eepFlags { + u8 opFlags; + u8 eepMisc; +} __packed; + +enum CompressAlgorithm { + _CompressNone = 0, + _CompressLzma, + _CompressPairs, + _CompressBlock, + _Compress4, + _Compress5, + _Compress6, + _Compress7, +}; + +struct ar9300_base_eep_hdr { + __le16 regDmn[2]; + /* 4 bits tx and 4 bits rx */ + u8 txrxMask; + struct eepFlags opCapFlags; + u8 rfSilent; + u8 blueToothOptions; + u8 deviceCap; + /* takes lower byte in eeprom location */ + u8 deviceType; + /* offset in dB to be added to beginning + * of pdadc table in calibration + */ + int8_t pwrTableOffset; + u8 params_for_tuning_caps[2]; + /* + * bit0 - enable tx temp comp + * bit1 - enable tx volt comp + * bit2 - enable fastClock - default to 1 + * bit3 - enable doubling - default to 1 + * bit4 - enable internal regulator - default to 1 + */ + u8 featureEnable; + /* misc flags: bit0 - turn down drivestrength */ + u8 miscConfiguration; + u8 eepromWriteEnableGpio; + u8 wlanDisableGpio; + u8 wlanLedGpio; + u8 rxBandSelectGpio; + u8 txrxgain; + /* SW controlled internal regulator fields */ + __le32 swreg; +} __packed; + +struct ar9300_modal_eep_header { + /* 4 idle, t1, t2, b (4 bits per setting) */ + __le32 antCtrlCommon; + /* 4 ra1l1, ra2l1, ra1l2, ra2l2, ra12 */ + __le32 antCtrlCommon2; + /* 6 idle, t, r, rx1, rx12, b (2 bits each) */ + __le16 antCtrlChain[AR9300_MAX_CHAINS]; + /* 3 xatten1_db for AR9280 (0xa20c/b20c 5:0) */ + u8 xatten1DB[AR9300_MAX_CHAINS]; + /* 3 xatten1_margin for merlin (0xa20c/b20c 16:12 */ + u8 xatten1Margin[AR9300_MAX_CHAINS]; + int8_t tempSlope; + int8_t voltSlope; + /* spur channels in usual fbin coding format */ + u8 spurChans[AR_EEPROM_MODAL_SPURS]; + /* 3 Check if the register is per chain */ + int8_t noiseFloorThreshCh[AR9300_MAX_CHAINS]; + u8 reserved[11]; + int8_t quick_drop; + u8 xpaBiasLvl; + u8 txFrameToDataStart; + u8 txFrameToPaOn; + u8 txClip; + int8_t antennaGain; + u8 switchSettling; + int8_t adcDesiredSize; + u8 txEndToXpaOff; + u8 txEndToRxOn; + u8 txFrameToXpaOn; + u8 thresh62; + __le32 papdRateMaskHt20; + __le32 papdRateMaskHt40; + __le16 switchcomspdt; + u8 xlna_bias_strength; + u8 futureModal[7]; +} __packed; + +struct ar9300_cal_data_per_freq_op_loop { + int8_t refPower; + /* pdadc voltage at power measurement */ + u8 voltMeas; + /* pcdac used for power measurement */ + u8 tempMeas; + /* range is -60 to -127 create a mapping equation 1db resolution */ + int8_t rxNoisefloorCal; + /*range is same as noisefloor */ + int8_t rxNoisefloorPower; + /* temp measured when noisefloor cal was performed */ + u8 rxTempMeas; +} __packed; + +struct cal_tgt_pow_legacy { + u8 tPow2x[4]; +} __packed; + +struct cal_tgt_pow_ht { + u8 tPow2x[14]; +} __packed; + +struct cal_ctl_data_2g { + u8 ctlEdges[AR9300_NUM_BAND_EDGES_2G]; +} __packed; + +struct cal_ctl_data_5g { + u8 ctlEdges[AR9300_NUM_BAND_EDGES_5G]; +} __packed; + +struct ar9300_BaseExtension_1 { + u8 ant_div_control; + u8 future[3]; + u8 tempslopextension[8]; + int8_t quick_drop_low; + int8_t quick_drop_high; +} __packed; + +struct ar9300_BaseExtension_2 { + int8_t tempSlopeLow; + int8_t tempSlopeHigh; + u8 xatten1DBLow[AR9300_MAX_CHAINS]; + u8 xatten1MarginLow[AR9300_MAX_CHAINS]; + u8 xatten1DBHigh[AR9300_MAX_CHAINS]; + u8 xatten1MarginHigh[AR9300_MAX_CHAINS]; +} __packed; + +struct ar9300_eeprom { + u8 eepromVersion; + u8 templateVersion; + u8 macAddr[6]; + u8 custData[AR9300_CUSTOMER_DATA_SIZE]; + + struct ar9300_base_eep_hdr baseEepHeader; + + struct ar9300_modal_eep_header modalHeader2G; + struct ar9300_BaseExtension_1 base_ext1; + u8 calFreqPier2G[AR9300_NUM_2G_CAL_PIERS]; + struct ar9300_cal_data_per_freq_op_loop + calPierData2G[AR9300_MAX_CHAINS][AR9300_NUM_2G_CAL_PIERS]; + u8 calTarget_freqbin_Cck[AR9300_NUM_2G_CCK_TARGET_POWERS]; + u8 calTarget_freqbin_2G[AR9300_NUM_2G_20_TARGET_POWERS]; + u8 calTarget_freqbin_2GHT20[AR9300_NUM_2G_20_TARGET_POWERS]; + u8 calTarget_freqbin_2GHT40[AR9300_NUM_2G_40_TARGET_POWERS]; + struct cal_tgt_pow_legacy + calTargetPowerCck[AR9300_NUM_2G_CCK_TARGET_POWERS]; + struct cal_tgt_pow_legacy + calTargetPower2G[AR9300_NUM_2G_20_TARGET_POWERS]; + struct cal_tgt_pow_ht + calTargetPower2GHT20[AR9300_NUM_2G_20_TARGET_POWERS]; + struct cal_tgt_pow_ht + calTargetPower2GHT40[AR9300_NUM_2G_40_TARGET_POWERS]; + u8 ctlIndex_2G[AR9300_NUM_CTLS_2G]; + u8 ctl_freqbin_2G[AR9300_NUM_CTLS_2G][AR9300_NUM_BAND_EDGES_2G]; + struct cal_ctl_data_2g ctlPowerData_2G[AR9300_NUM_CTLS_2G]; + struct ar9300_modal_eep_header modalHeader5G; + struct ar9300_BaseExtension_2 base_ext2; + u8 calFreqPier5G[AR9300_NUM_5G_CAL_PIERS]; + struct ar9300_cal_data_per_freq_op_loop + calPierData5G[AR9300_MAX_CHAINS][AR9300_NUM_5G_CAL_PIERS]; + u8 calTarget_freqbin_5G[AR9300_NUM_5G_20_TARGET_POWERS]; + u8 calTarget_freqbin_5GHT20[AR9300_NUM_5G_20_TARGET_POWERS]; + u8 calTarget_freqbin_5GHT40[AR9300_NUM_5G_40_TARGET_POWERS]; + struct cal_tgt_pow_legacy + calTargetPower5G[AR9300_NUM_5G_20_TARGET_POWERS]; + struct cal_tgt_pow_ht + calTargetPower5GHT20[AR9300_NUM_5G_20_TARGET_POWERS]; + struct cal_tgt_pow_ht + calTargetPower5GHT40[AR9300_NUM_5G_40_TARGET_POWERS]; + u8 ctlIndex_5G[AR9300_NUM_CTLS_5G]; + u8 ctl_freqbin_5G[AR9300_NUM_CTLS_5G][AR9300_NUM_BAND_EDGES_5G]; + struct cal_ctl_data_5g ctlPowerData_5G[AR9300_NUM_CTLS_5G]; +} __packed; + +s32 ar9003_hw_get_tx_gain_idx(struct ath_hw *ah); +s32 ar9003_hw_get_rx_gain_idx(struct ath_hw *ah); + +u8 *ar9003_get_spur_chan_ptr(struct ath_hw *ah, bool is_2ghz); + +unsigned int ar9003_get_paprd_scale_factor(struct ath_hw *ah, + struct ath9k_channel *chan); + +void ar9003_hw_internal_regulator_apply(struct ath_hw *ah); + +#endif diff --git a/drivers/net/wireless/ath/ath9k/ar9003_hw.c b/drivers/net/wireless/ath/ath9k/ar9003_hw.c new file mode 100644 index 0000000..a3523c9 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9003_hw.c @@ -0,0 +1,715 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "hw.h" +#include "ar9003_mac.h" +#include "ar9003_2p2_initvals.h" +#include "ar9485_initvals.h" +#include "ar9340_initvals.h" +#include "ar9330_1p1_initvals.h" +#include "ar9330_1p2_initvals.h" +#include "ar955x_1p0_initvals.h" +#include "ar9580_1p0_initvals.h" +#include "ar9462_2p0_initvals.h" +#include "ar9565_1p0_initvals.h" + +/* General hardware code for the AR9003 hadware family */ + +/* + * The AR9003 family uses a new INI format (pre, core, post + * arrays per subsystem). This provides support for the + * AR9003 2.2 chipsets. + */ +static void ar9003_hw_init_mode_regs(struct ath_hw *ah) +{ + if (AR_SREV_9330_11(ah)) { + /* mac */ + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_CORE], + ar9331_1p1_mac_core); + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_POST], + ar9331_1p1_mac_postamble); + + /* bb */ + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_CORE], + ar9331_1p1_baseband_core); + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_POST], + ar9331_1p1_baseband_postamble); + + /* radio */ + INIT_INI_ARRAY(&ah->iniRadio[ATH_INI_CORE], + ar9331_1p1_radio_core); + + /* soc */ + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_PRE], + ar9331_1p1_soc_preamble); + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_POST], + ar9331_1p1_soc_postamble); + + /* rx/tx gain */ + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9331_common_rx_gain_1p1); + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9331_modes_lowest_ob_db_tx_gain_1p1); + + /* Japan 2484 Mhz CCK */ + INIT_INI_ARRAY(&ah->iniCckfirJapan2484, + ar9331_1p1_baseband_core_txfir_coeff_japan_2484); + + /* additional clock settings */ + if (ah->is_clk_25mhz) + INIT_INI_ARRAY(&ah->iniAdditional, + ar9331_1p1_xtal_25M); + else + INIT_INI_ARRAY(&ah->iniAdditional, + ar9331_1p1_xtal_40M); + } else if (AR_SREV_9330_12(ah)) { + /* mac */ + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_CORE], + ar9331_1p2_mac_core); + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_POST], + ar9331_1p2_mac_postamble); + + /* bb */ + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_CORE], + ar9331_1p2_baseband_core); + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_POST], + ar9331_1p2_baseband_postamble); + + /* radio */ + INIT_INI_ARRAY(&ah->iniRadio[ATH_INI_CORE], + ar9331_1p2_radio_core); + + /* soc */ + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_PRE], + ar9331_1p2_soc_preamble); + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_POST], + ar9331_1p2_soc_postamble); + + /* rx/tx gain */ + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9331_common_rx_gain_1p2); + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9331_modes_lowest_ob_db_tx_gain_1p2); + + /* Japan 2484 Mhz CCK */ + INIT_INI_ARRAY(&ah->iniCckfirJapan2484, + ar9331_1p2_baseband_core_txfir_coeff_japan_2484); + + /* additional clock settings */ + if (ah->is_clk_25mhz) + INIT_INI_ARRAY(&ah->iniAdditional, + ar9331_1p2_xtal_25M); + else + INIT_INI_ARRAY(&ah->iniAdditional, + ar9331_1p2_xtal_40M); + } else if (AR_SREV_9340(ah)) { + /* mac */ + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_CORE], + ar9340_1p0_mac_core); + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_POST], + ar9340_1p0_mac_postamble); + + /* bb */ + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_CORE], + ar9340_1p0_baseband_core); + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_POST], + ar9340_1p0_baseband_postamble); + + /* radio */ + INIT_INI_ARRAY(&ah->iniRadio[ATH_INI_CORE], + ar9340_1p0_radio_core); + INIT_INI_ARRAY(&ah->iniRadio[ATH_INI_POST], + ar9340_1p0_radio_postamble); + + /* soc */ + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_PRE], + ar9340_1p0_soc_preamble); + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_POST], + ar9340_1p0_soc_postamble); + + /* rx/tx gain */ + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9340Common_wo_xlna_rx_gain_table_1p0); + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9340Modes_high_ob_db_tx_gain_table_1p0); + + INIT_INI_ARRAY(&ah->iniModesFastClock, + ar9340Modes_fast_clock_1p0); + + if (!ah->is_clk_25mhz) + INIT_INI_ARRAY(&ah->iniAdditional, + ar9340_1p0_radio_core_40M); + } else if (AR_SREV_9485_11(ah)) { + /* mac */ + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_CORE], + ar9485_1_1_mac_core); + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_POST], + ar9485_1_1_mac_postamble); + + /* bb */ + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_PRE], ar9485_1_1); + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_CORE], + ar9485_1_1_baseband_core); + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_POST], + ar9485_1_1_baseband_postamble); + + /* radio */ + INIT_INI_ARRAY(&ah->iniRadio[ATH_INI_CORE], + ar9485_1_1_radio_core); + INIT_INI_ARRAY(&ah->iniRadio[ATH_INI_POST], + ar9485_1_1_radio_postamble); + + /* soc */ + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_PRE], + ar9485_1_1_soc_preamble); + + /* rx/tx gain */ + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9485Common_wo_xlna_rx_gain_1_1); + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9485_modes_lowest_ob_db_tx_gain_1_1); + + /* Japan 2484 Mhz CCK */ + INIT_INI_ARRAY(&ah->iniCckfirJapan2484, + ar9485_1_1_baseband_core_txfir_coeff_japan_2484); + + /* Load PCIE SERDES settings from INI */ + + /* Awake Setting */ + + INIT_INI_ARRAY(&ah->iniPcieSerdes, + ar9485_1_1_pcie_phy_clkreq_disable_L1); + + /* Sleep Setting */ + + INIT_INI_ARRAY(&ah->iniPcieSerdesLowPower, + ar9485_1_1_pcie_phy_clkreq_disable_L1); + } else if (AR_SREV_9462_20(ah)) { + + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_CORE], ar9462_2p0_mac_core); + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_POST], + ar9462_2p0_mac_postamble); + + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_CORE], + ar9462_2p0_baseband_core); + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_POST], + ar9462_2p0_baseband_postamble); + + INIT_INI_ARRAY(&ah->iniRadio[ATH_INI_CORE], + ar9462_2p0_radio_core); + INIT_INI_ARRAY(&ah->iniRadio[ATH_INI_POST], + ar9462_2p0_radio_postamble); + INIT_INI_ARRAY(&ah->ini_radio_post_sys2ant, + ar9462_2p0_radio_postamble_sys2ant); + + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_PRE], + ar9462_2p0_soc_preamble); + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_POST], + ar9462_2p0_soc_postamble); + + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9462_common_rx_gain_table_2p0); + + /* Awake -> Sleep Setting */ + INIT_INI_ARRAY(&ah->iniPcieSerdes, + ar9462_pciephy_clkreq_disable_L1_2p0); + /* Sleep -> Awake Setting */ + INIT_INI_ARRAY(&ah->iniPcieSerdesLowPower, + ar9462_pciephy_clkreq_disable_L1_2p0); + + /* Fast clock modal settings */ + INIT_INI_ARRAY(&ah->iniModesFastClock, + ar9462_modes_fast_clock_2p0); + + INIT_INI_ARRAY(&ah->iniCckfirJapan2484, + ar9462_2p0_baseband_core_txfir_coeff_japan_2484); + } else if (AR_SREV_9550(ah)) { + /* mac */ + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_CORE], + ar955x_1p0_mac_core); + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_POST], + ar955x_1p0_mac_postamble); + + /* bb */ + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_CORE], + ar955x_1p0_baseband_core); + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_POST], + ar955x_1p0_baseband_postamble); + + /* radio */ + INIT_INI_ARRAY(&ah->iniRadio[ATH_INI_CORE], + ar955x_1p0_radio_core); + INIT_INI_ARRAY(&ah->iniRadio[ATH_INI_POST], + ar955x_1p0_radio_postamble); + + /* soc */ + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_PRE], + ar955x_1p0_soc_preamble); + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_POST], + ar955x_1p0_soc_postamble); + + /* rx/tx gain */ + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar955x_1p0_common_wo_xlna_rx_gain_table); + INIT_INI_ARRAY(&ah->ini_modes_rx_gain_bounds, + ar955x_1p0_common_wo_xlna_rx_gain_bounds); + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar955x_1p0_modes_xpa_tx_gain_table); + + /* Fast clock modal settings */ + INIT_INI_ARRAY(&ah->iniModesFastClock, + ar955x_1p0_modes_fast_clock); + } else if (AR_SREV_9580(ah)) { + /* mac */ + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_CORE], + ar9580_1p0_mac_core); + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_POST], + ar9580_1p0_mac_postamble); + + /* bb */ + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_CORE], + ar9580_1p0_baseband_core); + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_POST], + ar9580_1p0_baseband_postamble); + + /* radio */ + INIT_INI_ARRAY(&ah->iniRadio[ATH_INI_CORE], + ar9580_1p0_radio_core); + INIT_INI_ARRAY(&ah->iniRadio[ATH_INI_POST], + ar9580_1p0_radio_postamble); + + /* soc */ + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_PRE], + ar9580_1p0_soc_preamble); + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_POST], + ar9580_1p0_soc_postamble); + + /* rx/tx gain */ + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9580_1p0_rx_gain_table); + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9580_1p0_low_ob_db_tx_gain_table); + + INIT_INI_ARRAY(&ah->iniModesFastClock, + ar9580_1p0_modes_fast_clock); + } else if (AR_SREV_9565(ah)) { + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_CORE], + ar9565_1p0_mac_core); + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_POST], + ar9565_1p0_mac_postamble); + + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_CORE], + ar9565_1p0_baseband_core); + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_POST], + ar9565_1p0_baseband_postamble); + + INIT_INI_ARRAY(&ah->iniRadio[ATH_INI_CORE], + ar9565_1p0_radio_core); + INIT_INI_ARRAY(&ah->iniRadio[ATH_INI_POST], + ar9565_1p0_radio_postamble); + + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_PRE], + ar9565_1p0_soc_preamble); + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_POST], + ar9565_1p0_soc_postamble); + + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9565_1p0_Common_rx_gain_table); + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9565_1p0_Modes_lowest_ob_db_tx_gain_table); + + INIT_INI_ARRAY(&ah->iniPcieSerdes, + ar9565_1p0_pciephy_clkreq_disable_L1); + INIT_INI_ARRAY(&ah->iniPcieSerdesLowPower, + ar9565_1p0_pciephy_clkreq_disable_L1); + + INIT_INI_ARRAY(&ah->iniModesFastClock, + ar9565_1p0_modes_fast_clock); + } else { + /* mac */ + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_CORE], + ar9300_2p2_mac_core); + INIT_INI_ARRAY(&ah->iniMac[ATH_INI_POST], + ar9300_2p2_mac_postamble); + + /* bb */ + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_CORE], + ar9300_2p2_baseband_core); + INIT_INI_ARRAY(&ah->iniBB[ATH_INI_POST], + ar9300_2p2_baseband_postamble); + + /* radio */ + INIT_INI_ARRAY(&ah->iniRadio[ATH_INI_CORE], + ar9300_2p2_radio_core); + INIT_INI_ARRAY(&ah->iniRadio[ATH_INI_POST], + ar9300_2p2_radio_postamble); + + /* soc */ + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_PRE], + ar9300_2p2_soc_preamble); + INIT_INI_ARRAY(&ah->iniSOC[ATH_INI_POST], + ar9300_2p2_soc_postamble); + + /* rx/tx gain */ + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9300Common_rx_gain_table_2p2); + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9300Modes_lowest_ob_db_tx_gain_table_2p2); + + /* Load PCIE SERDES settings from INI */ + + /* Awake Setting */ + + INIT_INI_ARRAY(&ah->iniPcieSerdes, + ar9300PciePhy_pll_on_clkreq_disable_L1_2p2); + + /* Sleep Setting */ + + INIT_INI_ARRAY(&ah->iniPcieSerdesLowPower, + ar9300PciePhy_pll_on_clkreq_disable_L1_2p2); + + /* Fast clock modal settings */ + INIT_INI_ARRAY(&ah->iniModesFastClock, + ar9300Modes_fast_clock_2p2); + } +} + +static void ar9003_tx_gain_table_mode0(struct ath_hw *ah) +{ + if (AR_SREV_9330_12(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9331_modes_lowest_ob_db_tx_gain_1p2); + else if (AR_SREV_9330_11(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9331_modes_lowest_ob_db_tx_gain_1p1); + else if (AR_SREV_9340(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9340Modes_lowest_ob_db_tx_gain_table_1p0); + else if (AR_SREV_9485_11(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9485_modes_lowest_ob_db_tx_gain_1_1); + else if (AR_SREV_9550(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar955x_1p0_modes_xpa_tx_gain_table); + else if (AR_SREV_9580(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9580_1p0_lowest_ob_db_tx_gain_table); + else if (AR_SREV_9462_20(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9462_modes_low_ob_db_tx_gain_table_2p0); + else if (AR_SREV_9565(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9565_1p0_modes_low_ob_db_tx_gain_table); + else + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9300Modes_lowest_ob_db_tx_gain_table_2p2); +} + +static void ar9003_tx_gain_table_mode1(struct ath_hw *ah) +{ + if (AR_SREV_9330_12(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9331_modes_high_ob_db_tx_gain_1p2); + else if (AR_SREV_9330_11(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9331_modes_high_ob_db_tx_gain_1p1); + else if (AR_SREV_9340(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9340Modes_high_ob_db_tx_gain_table_1p0); + else if (AR_SREV_9485_11(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9485Modes_high_ob_db_tx_gain_1_1); + else if (AR_SREV_9580(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9580_1p0_high_ob_db_tx_gain_table); + else if (AR_SREV_9550(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar955x_1p0_modes_no_xpa_tx_gain_table); + else if (AR_SREV_9462_20(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9462_modes_high_ob_db_tx_gain_table_2p0); + else if (AR_SREV_9565(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9565_1p0_modes_high_ob_db_tx_gain_table); + else + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9300Modes_high_ob_db_tx_gain_table_2p2); +} + +static void ar9003_tx_gain_table_mode2(struct ath_hw *ah) +{ + if (AR_SREV_9330_12(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9331_modes_low_ob_db_tx_gain_1p2); + else if (AR_SREV_9330_11(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9331_modes_low_ob_db_tx_gain_1p1); + else if (AR_SREV_9340(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9340Modes_low_ob_db_tx_gain_table_1p0); + else if (AR_SREV_9485_11(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9485Modes_low_ob_db_tx_gain_1_1); + else if (AR_SREV_9580(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9580_1p0_low_ob_db_tx_gain_table); + else if (AR_SREV_9565(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9565_1p0_modes_low_ob_db_tx_gain_table); + else + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9300Modes_low_ob_db_tx_gain_table_2p2); +} + +static void ar9003_tx_gain_table_mode3(struct ath_hw *ah) +{ + if (AR_SREV_9330_12(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9331_modes_high_power_tx_gain_1p2); + else if (AR_SREV_9330_11(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9331_modes_high_power_tx_gain_1p1); + else if (AR_SREV_9340(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9340Modes_high_power_tx_gain_table_1p0); + else if (AR_SREV_9485_11(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9485Modes_high_power_tx_gain_1_1); + else if (AR_SREV_9580(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9580_1p0_high_power_tx_gain_table); + else if (AR_SREV_9565(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9565_1p0_modes_high_power_tx_gain_table); + else + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9300Modes_high_power_tx_gain_table_2p2); +} + +static void ar9003_tx_gain_table_mode4(struct ath_hw *ah) +{ + if (AR_SREV_9340(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9340Modes_mixed_ob_db_tx_gain_table_1p0); + else if (AR_SREV_9580(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9580_1p0_mixed_ob_db_tx_gain_table); + else + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9300Modes_mixed_ob_db_tx_gain_table_2p2); +} + +static void ar9003_tx_gain_table_mode5(struct ath_hw *ah) +{ + if (AR_SREV_9485_11(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9485Modes_green_ob_db_tx_gain_1_1); + else if (AR_SREV_9340(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9340Modes_ub124_tx_gain_table_1p0); + else if (AR_SREV_9580(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9580_1p0_type5_tx_gain_table); + else if (AR_SREV_9300_22(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9300Modes_type5_tx_gain_table_2p2); +} + +static void ar9003_tx_gain_table_mode6(struct ath_hw *ah) +{ + if (AR_SREV_9340(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9340Modes_low_ob_db_and_spur_tx_gain_table_1p0); + else if (AR_SREV_9485_11(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9485Modes_green_spur_ob_db_tx_gain_1_1); + else if (AR_SREV_9580(ah)) + INIT_INI_ARRAY(&ah->iniModesTxGain, + ar9580_1p0_type6_tx_gain_table); +} + +typedef void (*ath_txgain_tab)(struct ath_hw *ah); + +static void ar9003_tx_gain_table_apply(struct ath_hw *ah) +{ + static const ath_txgain_tab modes[] = { + ar9003_tx_gain_table_mode0, + ar9003_tx_gain_table_mode1, + ar9003_tx_gain_table_mode2, + ar9003_tx_gain_table_mode3, + ar9003_tx_gain_table_mode4, + ar9003_tx_gain_table_mode5, + ar9003_tx_gain_table_mode6, + }; + int idx = ar9003_hw_get_tx_gain_idx(ah); + + if (idx >= ARRAY_SIZE(modes)) + idx = 0; + + modes[idx](ah); +} + +static void ar9003_rx_gain_table_mode0(struct ath_hw *ah) +{ + if (AR_SREV_9330_12(ah)) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9331_common_rx_gain_1p2); + else if (AR_SREV_9330_11(ah)) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9331_common_rx_gain_1p1); + else if (AR_SREV_9340(ah)) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9340Common_rx_gain_table_1p0); + else if (AR_SREV_9485_11(ah)) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9485_common_rx_gain_1_1); + else if (AR_SREV_9550(ah)) { + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar955x_1p0_common_rx_gain_table); + INIT_INI_ARRAY(&ah->ini_modes_rx_gain_bounds, + ar955x_1p0_common_rx_gain_bounds); + } else if (AR_SREV_9580(ah)) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9580_1p0_rx_gain_table); + else if (AR_SREV_9462_20(ah)) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9462_common_rx_gain_table_2p0); + else + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9300Common_rx_gain_table_2p2); +} + +static void ar9003_rx_gain_table_mode1(struct ath_hw *ah) +{ + if (AR_SREV_9330_12(ah)) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9331_common_wo_xlna_rx_gain_1p2); + else if (AR_SREV_9330_11(ah)) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9331_common_wo_xlna_rx_gain_1p1); + else if (AR_SREV_9340(ah)) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9340Common_wo_xlna_rx_gain_table_1p0); + else if (AR_SREV_9485_11(ah)) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9485Common_wo_xlna_rx_gain_1_1); + else if (AR_SREV_9462_20(ah)) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9462_common_wo_xlna_rx_gain_table_2p0); + else if (AR_SREV_9550(ah)) { + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar955x_1p0_common_wo_xlna_rx_gain_table); + INIT_INI_ARRAY(&ah->ini_modes_rx_gain_bounds, + ar955x_1p0_common_wo_xlna_rx_gain_bounds); + } else if (AR_SREV_9580(ah)) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9580_1p0_wo_xlna_rx_gain_table); + else if (AR_SREV_9565(ah)) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9565_1p0_common_wo_xlna_rx_gain_table); + else + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9300Common_wo_xlna_rx_gain_table_2p2); +} + +static void ar9003_rx_gain_table_mode2(struct ath_hw *ah) +{ + if (AR_SREV_9462_20(ah)) + INIT_INI_ARRAY(&ah->iniModesRxGain, + ar9462_common_mixed_rx_gain_table_2p0); +} + +static void ar9003_rx_gain_table_apply(struct ath_hw *ah) +{ + switch (ar9003_hw_get_rx_gain_idx(ah)) { + case 0: + default: + ar9003_rx_gain_table_mode0(ah); + break; + case 1: + ar9003_rx_gain_table_mode1(ah); + break; + case 2: + ar9003_rx_gain_table_mode2(ah); + break; + } +} + +/* set gain table pointers according to values read from the eeprom */ +static void ar9003_hw_init_mode_gain_regs(struct ath_hw *ah) +{ + ar9003_tx_gain_table_apply(ah); + ar9003_rx_gain_table_apply(ah); +} + +/* + * Helper for ASPM support. + * + * Disable PLL when in L0s as well as receiver clock when in L1. + * This power saving option must be enabled through the SerDes. + * + * Programming the SerDes must go through the same 288 bit serial shift + * register as the other analog registers. Hence the 9 writes. + */ +static void ar9003_hw_configpcipowersave(struct ath_hw *ah, + bool power_off) +{ + /* Nothing to do on restore for 11N */ + if (!power_off /* !restore */) { + /* set bit 19 to allow forcing of pcie core into L1 state */ + REG_SET_BIT(ah, AR_PCIE_PM_CTRL, AR_PCIE_PM_CTRL_ENA); + + /* Several PCIe massages to ensure proper behaviour */ + if (ah->config.pcie_waen) + REG_WRITE(ah, AR_WA, ah->config.pcie_waen); + else + REG_WRITE(ah, AR_WA, ah->WARegVal); + } + + /* + * Configire PCIE after Ini init. SERDES values now come from ini file + * This enables PCIe low power mode. + */ + if (ah->config.pcieSerDesWrite) { + unsigned int i; + struct ar5416IniArray *array; + + array = power_off ? &ah->iniPcieSerdes : + &ah->iniPcieSerdesLowPower; + + for (i = 0; i < array->ia_rows; i++) { + REG_WRITE(ah, + INI_RA(array, i, 0), + INI_RA(array, i, 1)); + } + } +} + +/* Sets up the AR9003 hardware familiy callbacks */ +void ar9003_hw_attach_ops(struct ath_hw *ah) +{ + struct ath_hw_private_ops *priv_ops = ath9k_hw_private_ops(ah); + struct ath_hw_ops *ops = ath9k_hw_ops(ah); + + ar9003_hw_init_mode_regs(ah); + priv_ops->init_mode_gain_regs = ar9003_hw_init_mode_gain_regs; + + ops->config_pci_powersave = ar9003_hw_configpcipowersave; + + ar9003_hw_attach_phy_ops(ah); + ar9003_hw_attach_calib_ops(ah); + ar9003_hw_attach_mac_ops(ah); +} diff --git a/drivers/net/wireless/ath/ath9k/ar9003_mac.c b/drivers/net/wireless/ath/ath9k/ar9003_mac.c new file mode 100644 index 0000000..301bf72 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9003_mac.c @@ -0,0 +1,589 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include "hw.h" +#include "ar9003_mac.h" +#include "ar9003_mci.h" + +static void ar9003_hw_rx_enable(struct ath_hw *hw) +{ + REG_WRITE(hw, AR_CR, 0); +} + +static void +ar9003_set_txdesc(struct ath_hw *ah, void *ds, struct ath_tx_info *i) +{ + struct ar9003_txc *ads = ds; + int checksum = 0; + u32 val, ctl12, ctl17; + u8 desc_len; + + desc_len = ((AR_SREV_9462(ah) || AR_SREV_9565(ah)) ? 0x18 : 0x17); + + val = (ATHEROS_VENDOR_ID << AR_DescId_S) | + (1 << AR_TxRxDesc_S) | + (1 << AR_CtrlStat_S) | + (i->qcu << AR_TxQcuNum_S) | desc_len; + + checksum += val; + ACCESS_ONCE(ads->info) = val; + + checksum += i->link; + ACCESS_ONCE(ads->link) = i->link; + + checksum += i->buf_addr[0]; + ACCESS_ONCE(ads->data0) = i->buf_addr[0]; + checksum += i->buf_addr[1]; + ACCESS_ONCE(ads->data1) = i->buf_addr[1]; + checksum += i->buf_addr[2]; + ACCESS_ONCE(ads->data2) = i->buf_addr[2]; + checksum += i->buf_addr[3]; + ACCESS_ONCE(ads->data3) = i->buf_addr[3]; + + checksum += (val = (i->buf_len[0] << AR_BufLen_S) & AR_BufLen); + ACCESS_ONCE(ads->ctl3) = val; + checksum += (val = (i->buf_len[1] << AR_BufLen_S) & AR_BufLen); + ACCESS_ONCE(ads->ctl5) = val; + checksum += (val = (i->buf_len[2] << AR_BufLen_S) & AR_BufLen); + ACCESS_ONCE(ads->ctl7) = val; + checksum += (val = (i->buf_len[3] << AR_BufLen_S) & AR_BufLen); + ACCESS_ONCE(ads->ctl9) = val; + + checksum = (u16) (((checksum & 0xffff) + (checksum >> 16)) & 0xffff); + ACCESS_ONCE(ads->ctl10) = checksum; + + if (i->is_first || i->is_last) { + ACCESS_ONCE(ads->ctl13) = set11nTries(i->rates, 0) + | set11nTries(i->rates, 1) + | set11nTries(i->rates, 2) + | set11nTries(i->rates, 3) + | (i->dur_update ? AR_DurUpdateEna : 0) + | SM(0, AR_BurstDur); + + ACCESS_ONCE(ads->ctl14) = set11nRate(i->rates, 0) + | set11nRate(i->rates, 1) + | set11nRate(i->rates, 2) + | set11nRate(i->rates, 3); + } else { + ACCESS_ONCE(ads->ctl13) = 0; + ACCESS_ONCE(ads->ctl14) = 0; + } + + ads->ctl20 = 0; + ads->ctl21 = 0; + ads->ctl22 = 0; + ads->ctl23 = 0; + + ctl17 = SM(i->keytype, AR_EncrType); + if (!i->is_first) { + ACCESS_ONCE(ads->ctl11) = 0; + ACCESS_ONCE(ads->ctl12) = i->is_last ? 0 : AR_TxMore; + ACCESS_ONCE(ads->ctl15) = 0; + ACCESS_ONCE(ads->ctl16) = 0; + ACCESS_ONCE(ads->ctl17) = ctl17; + ACCESS_ONCE(ads->ctl18) = 0; + ACCESS_ONCE(ads->ctl19) = 0; + return; + } + + ACCESS_ONCE(ads->ctl11) = (i->pkt_len & AR_FrameLen) + | (i->flags & ATH9K_TXDESC_VMF ? AR_VirtMoreFrag : 0) + | SM(i->txpower, AR_XmitPower) + | (i->flags & ATH9K_TXDESC_VEOL ? AR_VEOL : 0) + | (i->keyix != ATH9K_TXKEYIX_INVALID ? AR_DestIdxValid : 0) + | (i->flags & ATH9K_TXDESC_LOWRXCHAIN ? AR_LowRxChain : 0) + | (i->flags & ATH9K_TXDESC_CLRDMASK ? AR_ClrDestMask : 0) + | (i->flags & ATH9K_TXDESC_RTSENA ? AR_RTSEnable : + (i->flags & ATH9K_TXDESC_CTSENA ? AR_CTSEnable : 0)); + + ctl12 = (i->keyix != ATH9K_TXKEYIX_INVALID ? + SM(i->keyix, AR_DestIdx) : 0) + | SM(i->type, AR_FrameType) + | (i->flags & ATH9K_TXDESC_NOACK ? AR_NoAck : 0) + | (i->flags & ATH9K_TXDESC_EXT_ONLY ? AR_ExtOnly : 0) + | (i->flags & ATH9K_TXDESC_EXT_AND_CTL ? AR_ExtAndCtl : 0); + + ctl17 |= (i->flags & ATH9K_TXDESC_LDPC ? AR_LDPC : 0); + switch (i->aggr) { + case AGGR_BUF_FIRST: + ctl17 |= SM(i->aggr_len, AR_AggrLen); + /* fall through */ + case AGGR_BUF_MIDDLE: + ctl12 |= AR_IsAggr | AR_MoreAggr; + ctl17 |= SM(i->ndelim, AR_PadDelim); + break; + case AGGR_BUF_LAST: + ctl12 |= AR_IsAggr; + break; + case AGGR_BUF_NONE: + break; + } + + val = (i->flags & ATH9K_TXDESC_PAPRD) >> ATH9K_TXDESC_PAPRD_S; + ctl12 |= SM(val, AR_PAPRDChainMask); + + ACCESS_ONCE(ads->ctl12) = ctl12; + ACCESS_ONCE(ads->ctl17) = ctl17; + + ACCESS_ONCE(ads->ctl15) = set11nPktDurRTSCTS(i->rates, 0) + | set11nPktDurRTSCTS(i->rates, 1); + + ACCESS_ONCE(ads->ctl16) = set11nPktDurRTSCTS(i->rates, 2) + | set11nPktDurRTSCTS(i->rates, 3); + + ACCESS_ONCE(ads->ctl18) = set11nRateFlags(i->rates, 0) + | set11nRateFlags(i->rates, 1) + | set11nRateFlags(i->rates, 2) + | set11nRateFlags(i->rates, 3) + | SM(i->rtscts_rate, AR_RTSCTSRate); + + ACCESS_ONCE(ads->ctl19) = AR_Not_Sounding; +} + +static u16 ar9003_calc_ptr_chksum(struct ar9003_txc *ads) +{ + int checksum; + + checksum = ads->info + ads->link + + ads->data0 + ads->ctl3 + + ads->data1 + ads->ctl5 + + ads->data2 + ads->ctl7 + + ads->data3 + ads->ctl9; + + return ((checksum & 0xffff) + (checksum >> 16)) & AR_TxPtrChkSum; +} + +static void ar9003_hw_set_desc_link(void *ds, u32 ds_link) +{ + struct ar9003_txc *ads = ds; + + ads->link = ds_link; + ads->ctl10 &= ~AR_TxPtrChkSum; + ads->ctl10 |= ar9003_calc_ptr_chksum(ads); +} + +static bool ar9003_hw_get_isr(struct ath_hw *ah, enum ath9k_int *masked) +{ + u32 isr = 0; + u32 mask2 = 0; + struct ath9k_hw_capabilities *pCap = &ah->caps; + struct ath_common *common = ath9k_hw_common(ah); + u32 sync_cause = 0, async_cause, async_mask = AR_INTR_MAC_IRQ; + bool fatal_int; + + if (ath9k_hw_mci_is_enabled(ah)) + async_mask |= AR_INTR_ASYNC_MASK_MCI; + + async_cause = REG_READ(ah, AR_INTR_ASYNC_CAUSE); + + if (async_cause & async_mask) { + if ((REG_READ(ah, AR_RTC_STATUS) & AR_RTC_STATUS_M) + == AR_RTC_STATUS_ON) + isr = REG_READ(ah, AR_ISR); + } + + + sync_cause = REG_READ(ah, AR_INTR_SYNC_CAUSE) & AR_INTR_SYNC_DEFAULT; + + *masked = 0; + + if (!isr && !sync_cause && !async_cause) + return false; + + if (isr) { + if (isr & AR_ISR_BCNMISC) { + u32 isr2; + isr2 = REG_READ(ah, AR_ISR_S2); + + mask2 |= ((isr2 & AR_ISR_S2_TIM) >> + MAP_ISR_S2_TIM); + mask2 |= ((isr2 & AR_ISR_S2_DTIM) >> + MAP_ISR_S2_DTIM); + mask2 |= ((isr2 & AR_ISR_S2_DTIMSYNC) >> + MAP_ISR_S2_DTIMSYNC); + mask2 |= ((isr2 & AR_ISR_S2_CABEND) >> + MAP_ISR_S2_CABEND); + mask2 |= ((isr2 & AR_ISR_S2_GTT) << + MAP_ISR_S2_GTT); + mask2 |= ((isr2 & AR_ISR_S2_CST) << + MAP_ISR_S2_CST); + mask2 |= ((isr2 & AR_ISR_S2_TSFOOR) >> + MAP_ISR_S2_TSFOOR); + mask2 |= ((isr2 & AR_ISR_S2_BB_WATCHDOG) >> + MAP_ISR_S2_BB_WATCHDOG); + + if (!(pCap->hw_caps & ATH9K_HW_CAP_RAC_SUPPORTED)) { + REG_WRITE(ah, AR_ISR_S2, isr2); + isr &= ~AR_ISR_BCNMISC; + } + } + + if ((pCap->hw_caps & ATH9K_HW_CAP_RAC_SUPPORTED)) + isr = REG_READ(ah, AR_ISR_RAC); + + if (isr == 0xffffffff) { + *masked = 0; + return false; + } + + *masked = isr & ATH9K_INT_COMMON; + + if (ah->config.rx_intr_mitigation) + if (isr & (AR_ISR_RXMINTR | AR_ISR_RXINTM)) + *masked |= ATH9K_INT_RXLP; + + if (ah->config.tx_intr_mitigation) + if (isr & (AR_ISR_TXMINTR | AR_ISR_TXINTM)) + *masked |= ATH9K_INT_TX; + + if (isr & (AR_ISR_LP_RXOK | AR_ISR_RXERR)) + *masked |= ATH9K_INT_RXLP; + + if (isr & AR_ISR_HP_RXOK) + *masked |= ATH9K_INT_RXHP; + + if (isr & (AR_ISR_TXOK | AR_ISR_TXERR | AR_ISR_TXEOL)) { + *masked |= ATH9K_INT_TX; + + if (!(pCap->hw_caps & ATH9K_HW_CAP_RAC_SUPPORTED)) { + u32 s0, s1; + s0 = REG_READ(ah, AR_ISR_S0); + REG_WRITE(ah, AR_ISR_S0, s0); + s1 = REG_READ(ah, AR_ISR_S1); + REG_WRITE(ah, AR_ISR_S1, s1); + + isr &= ~(AR_ISR_TXOK | AR_ISR_TXERR | + AR_ISR_TXEOL); + } + } + + if (isr & AR_ISR_GENTMR) { + u32 s5; + + if (pCap->hw_caps & ATH9K_HW_CAP_RAC_SUPPORTED) + s5 = REG_READ(ah, AR_ISR_S5_S); + else + s5 = REG_READ(ah, AR_ISR_S5); + + ah->intr_gen_timer_trigger = + MS(s5, AR_ISR_S5_GENTIMER_TRIG); + + ah->intr_gen_timer_thresh = + MS(s5, AR_ISR_S5_GENTIMER_THRESH); + + if (ah->intr_gen_timer_trigger) + *masked |= ATH9K_INT_GENTIMER; + + if (!(pCap->hw_caps & ATH9K_HW_CAP_RAC_SUPPORTED)) { + REG_WRITE(ah, AR_ISR_S5, s5); + isr &= ~AR_ISR_GENTMR; + } + + } + + *masked |= mask2; + + if (!(pCap->hw_caps & ATH9K_HW_CAP_RAC_SUPPORTED)) { + REG_WRITE(ah, AR_ISR, isr); + + (void) REG_READ(ah, AR_ISR); + } + + if (*masked & ATH9K_INT_BB_WATCHDOG) + ar9003_hw_bb_watchdog_read(ah); + } + + if (async_cause & AR_INTR_ASYNC_MASK_MCI) + ar9003_mci_get_isr(ah, masked); + + if (sync_cause) { + ath9k_debug_sync_cause(common, sync_cause); + fatal_int = + (sync_cause & + (AR_INTR_SYNC_HOST1_FATAL | AR_INTR_SYNC_HOST1_PERR)) + ? true : false; + + if (fatal_int) { + if (sync_cause & AR_INTR_SYNC_HOST1_FATAL) { + ath_dbg(common, ANY, + "received PCI FATAL interrupt\n"); + } + if (sync_cause & AR_INTR_SYNC_HOST1_PERR) { + ath_dbg(common, ANY, + "received PCI PERR interrupt\n"); + } + *masked |= ATH9K_INT_FATAL; + } + + if (sync_cause & AR_INTR_SYNC_RADM_CPL_TIMEOUT) { + REG_WRITE(ah, AR_RC, AR_RC_HOSTIF); + REG_WRITE(ah, AR_RC, 0); + *masked |= ATH9K_INT_FATAL; + } + + if (sync_cause & AR_INTR_SYNC_LOCAL_TIMEOUT) + ath_dbg(common, INTERRUPT, + "AR_INTR_SYNC_LOCAL_TIMEOUT\n"); + + REG_WRITE(ah, AR_INTR_SYNC_CAUSE_CLR, sync_cause); + (void) REG_READ(ah, AR_INTR_SYNC_CAUSE_CLR); + + } + return true; +} + +static int ar9003_hw_proc_txdesc(struct ath_hw *ah, void *ds, + struct ath_tx_status *ts) +{ + struct ar9003_txs *ads; + u32 status; + + ads = &ah->ts_ring[ah->ts_tail]; + + status = ACCESS_ONCE(ads->status8); + if ((status & AR_TxDone) == 0) + return -EINPROGRESS; + + ah->ts_tail = (ah->ts_tail + 1) % ah->ts_size; + + if ((MS(ads->ds_info, AR_DescId) != ATHEROS_VENDOR_ID) || + (MS(ads->ds_info, AR_TxRxDesc) != 1)) { + ath_dbg(ath9k_hw_common(ah), XMIT, + "Tx Descriptor error %x\n", ads->ds_info); + memset(ads, 0, sizeof(*ads)); + return -EIO; + } + + ts->ts_rateindex = MS(status, AR_FinalTxIdx); + ts->ts_seqnum = MS(status, AR_SeqNum); + ts->tid = MS(status, AR_TxTid); + + ts->qid = MS(ads->ds_info, AR_TxQcuNum); + ts->desc_id = MS(ads->status1, AR_TxDescId); + ts->ts_tstamp = ads->status4; + ts->ts_status = 0; + ts->ts_flags = 0; + + if (status & AR_TxOpExceeded) + ts->ts_status |= ATH9K_TXERR_XTXOP; + status = ACCESS_ONCE(ads->status2); + ts->ts_rssi_ctl0 = MS(status, AR_TxRSSIAnt00); + ts->ts_rssi_ctl1 = MS(status, AR_TxRSSIAnt01); + ts->ts_rssi_ctl2 = MS(status, AR_TxRSSIAnt02); + if (status & AR_TxBaStatus) { + ts->ts_flags |= ATH9K_TX_BA; + ts->ba_low = ads->status5; + ts->ba_high = ads->status6; + } + + status = ACCESS_ONCE(ads->status3); + if (status & AR_ExcessiveRetries) + ts->ts_status |= ATH9K_TXERR_XRETRY; + if (status & AR_Filtered) + ts->ts_status |= ATH9K_TXERR_FILT; + if (status & AR_FIFOUnderrun) { + ts->ts_status |= ATH9K_TXERR_FIFO; + ath9k_hw_updatetxtriglevel(ah, true); + } + if (status & AR_TxTimerExpired) + ts->ts_status |= ATH9K_TXERR_TIMER_EXPIRED; + if (status & AR_DescCfgErr) + ts->ts_flags |= ATH9K_TX_DESC_CFG_ERR; + if (status & AR_TxDataUnderrun) { + ts->ts_flags |= ATH9K_TX_DATA_UNDERRUN; + ath9k_hw_updatetxtriglevel(ah, true); + } + if (status & AR_TxDelimUnderrun) { + ts->ts_flags |= ATH9K_TX_DELIM_UNDERRUN; + ath9k_hw_updatetxtriglevel(ah, true); + } + ts->ts_shortretry = MS(status, AR_RTSFailCnt); + ts->ts_longretry = MS(status, AR_DataFailCnt); + ts->ts_virtcol = MS(status, AR_VirtRetryCnt); + + status = ACCESS_ONCE(ads->status7); + ts->ts_rssi = MS(status, AR_TxRSSICombined); + ts->ts_rssi_ext0 = MS(status, AR_TxRSSIAnt10); + ts->ts_rssi_ext1 = MS(status, AR_TxRSSIAnt11); + ts->ts_rssi_ext2 = MS(status, AR_TxRSSIAnt12); + + memset(ads, 0, sizeof(*ads)); + + return 0; +} + +void ar9003_hw_attach_mac_ops(struct ath_hw *hw) +{ + struct ath_hw_ops *ops = ath9k_hw_ops(hw); + + ops->rx_enable = ar9003_hw_rx_enable; + ops->set_desc_link = ar9003_hw_set_desc_link; + ops->get_isr = ar9003_hw_get_isr; + ops->set_txdesc = ar9003_set_txdesc; + ops->proc_txdesc = ar9003_hw_proc_txdesc; +} + +void ath9k_hw_set_rx_bufsize(struct ath_hw *ah, u16 buf_size) +{ + REG_WRITE(ah, AR_DATABUF_SIZE, buf_size & AR_DATABUF_SIZE_MASK); +} +EXPORT_SYMBOL(ath9k_hw_set_rx_bufsize); + +void ath9k_hw_addrxbuf_edma(struct ath_hw *ah, u32 rxdp, + enum ath9k_rx_qtype qtype) +{ + if (qtype == ATH9K_RX_QUEUE_HP) + REG_WRITE(ah, AR_HP_RXDP, rxdp); + else + REG_WRITE(ah, AR_LP_RXDP, rxdp); +} +EXPORT_SYMBOL(ath9k_hw_addrxbuf_edma); + +int ath9k_hw_process_rxdesc_edma(struct ath_hw *ah, struct ath_rx_status *rxs, + void *buf_addr) +{ + struct ar9003_rxs *rxsp = (struct ar9003_rxs *) buf_addr; + unsigned int phyerr; + + if ((rxsp->status11 & AR_RxDone) == 0) + return -EINPROGRESS; + + if (MS(rxsp->ds_info, AR_DescId) != 0x168c) + return -EINVAL; + + if ((rxsp->ds_info & (AR_TxRxDesc | AR_CtrlStat)) != 0) + return -EINPROGRESS; + + rxs->rs_status = 0; + rxs->rs_flags = 0; + + rxs->rs_datalen = rxsp->status2 & AR_DataLen; + rxs->rs_tstamp = rxsp->status3; + + /* XXX: Keycache */ + rxs->rs_rssi = MS(rxsp->status5, AR_RxRSSICombined); + rxs->rs_rssi_ctl0 = MS(rxsp->status1, AR_RxRSSIAnt00); + rxs->rs_rssi_ctl1 = MS(rxsp->status1, AR_RxRSSIAnt01); + rxs->rs_rssi_ctl2 = MS(rxsp->status1, AR_RxRSSIAnt02); + rxs->rs_rssi_ext0 = MS(rxsp->status5, AR_RxRSSIAnt10); + rxs->rs_rssi_ext1 = MS(rxsp->status5, AR_RxRSSIAnt11); + rxs->rs_rssi_ext2 = MS(rxsp->status5, AR_RxRSSIAnt12); + + if (rxsp->status11 & AR_RxKeyIdxValid) + rxs->rs_keyix = MS(rxsp->status11, AR_KeyIdx); + else + rxs->rs_keyix = ATH9K_RXKEYIX_INVALID; + + rxs->rs_rate = MS(rxsp->status1, AR_RxRate); + rxs->rs_more = (rxsp->status2 & AR_RxMore) ? 1 : 0; + + rxs->rs_isaggr = (rxsp->status11 & AR_RxAggr) ? 1 : 0; + rxs->rs_moreaggr = (rxsp->status11 & AR_RxMoreAggr) ? 1 : 0; + rxs->rs_antenna = (MS(rxsp->status4, AR_RxAntenna) & 0x7); + rxs->rs_flags = (rxsp->status4 & AR_GI) ? ATH9K_RX_GI : 0; + rxs->rs_flags |= (rxsp->status4 & AR_2040) ? ATH9K_RX_2040 : 0; + + rxs->evm0 = rxsp->status6; + rxs->evm1 = rxsp->status7; + rxs->evm2 = rxsp->status8; + rxs->evm3 = rxsp->status9; + rxs->evm4 = (rxsp->status10 & 0xffff); + + if (rxsp->status11 & AR_PreDelimCRCErr) + rxs->rs_flags |= ATH9K_RX_DELIM_CRC_PRE; + + if (rxsp->status11 & AR_PostDelimCRCErr) + rxs->rs_flags |= ATH9K_RX_DELIM_CRC_POST; + + if (rxsp->status11 & AR_DecryptBusyErr) + rxs->rs_flags |= ATH9K_RX_DECRYPT_BUSY; + + if ((rxsp->status11 & AR_RxFrameOK) == 0) { + /* + * AR_CRCErr will bet set to true if we're on the last + * subframe and the AR_PostDelimCRCErr is caught. + * In a way this also gives us a guarantee that when + * (!(AR_CRCErr) && (AR_PostDelimCRCErr)) we cannot + * possibly be reviewing the last subframe. AR_CRCErr + * is the CRC of the actual data. + */ + if (rxsp->status11 & AR_CRCErr) + rxs->rs_status |= ATH9K_RXERR_CRC; + else if (rxsp->status11 & AR_DecryptCRCErr) + rxs->rs_status |= ATH9K_RXERR_DECRYPT; + else if (rxsp->status11 & AR_MichaelErr) + rxs->rs_status |= ATH9K_RXERR_MIC; + if (rxsp->status11 & AR_PHYErr) { + phyerr = MS(rxsp->status11, AR_PHYErrCode); + /* + * If we reach a point here where AR_PostDelimCRCErr is + * true it implies we're *not* on the last subframe. In + * in that case that we know already that the CRC of + * the frame was OK, and MAC would send an ACK for that + * subframe, even if we did get a phy error of type + * ATH9K_PHYERR_OFDM_RESTART. This is only applicable + * to frame that are prior to the last subframe. + * The AR_PostDelimCRCErr is the CRC for the MPDU + * delimiter, which contains the 4 reserved bits, + * the MPDU length (12 bits), and follows the MPDU + * delimiter for an A-MPDU subframe (0x4E = 'N' ASCII). + */ + if ((phyerr == ATH9K_PHYERR_OFDM_RESTART) && + (rxsp->status11 & AR_PostDelimCRCErr)) { + rxs->rs_phyerr = 0; + } else { + rxs->rs_status |= ATH9K_RXERR_PHY; + rxs->rs_phyerr = phyerr; + } + } + } + + if (rxsp->status11 & AR_KeyMiss) + rxs->rs_status |= ATH9K_RXERR_KEYMISS; + + return 0; +} +EXPORT_SYMBOL(ath9k_hw_process_rxdesc_edma); + +void ath9k_hw_reset_txstatus_ring(struct ath_hw *ah) +{ + ah->ts_tail = 0; + + memset((void *) ah->ts_ring, 0, + ah->ts_size * sizeof(struct ar9003_txs)); + + ath_dbg(ath9k_hw_common(ah), XMIT, + "TS Start 0x%x End 0x%x Virt %p, Size %d\n", + ah->ts_paddr_start, ah->ts_paddr_end, + ah->ts_ring, ah->ts_size); + + REG_WRITE(ah, AR_Q_STATUS_RING_START, ah->ts_paddr_start); + REG_WRITE(ah, AR_Q_STATUS_RING_END, ah->ts_paddr_end); +} + +void ath9k_hw_setup_statusring(struct ath_hw *ah, void *ts_start, + u32 ts_paddr_start, + u16 size) +{ + + ah->ts_paddr_start = ts_paddr_start; + ah->ts_paddr_end = ts_paddr_start + (size * sizeof(struct ar9003_txs)); + ah->ts_size = size; + ah->ts_ring = (struct ar9003_txs *) ts_start; + + ath9k_hw_reset_txstatus_ring(ah); +} +EXPORT_SYMBOL(ath9k_hw_setup_statusring); diff --git a/drivers/net/wireless/ath/ath9k/ar9003_mac.h b/drivers/net/wireless/ath/ath9k/ar9003_mac.h new file mode 100644 index 0000000..cbf60b0 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9003_mac.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef AR9003_MAC_H +#define AR9003_MAC_H + +#define AR_DescId 0xffff0000 +#define AR_DescId_S 16 +#define AR_CtrlStat 0x00004000 +#define AR_CtrlStat_S 14 +#define AR_TxRxDesc 0x00008000 +#define AR_TxRxDesc_S 15 +#define AR_TxQcuNum 0x00000f00 +#define AR_TxQcuNum_S 8 + +#define AR_BufLen 0x0fff0000 +#define AR_BufLen_S 16 + +#define AR_TxDescId 0xffff0000 +#define AR_TxDescId_S 16 +#define AR_TxPtrChkSum 0x0000ffff + +#define AR_LowRxChain 0x00004000 + +#define AR_Not_Sounding 0x20000000 + +/* ctl 12 */ +#define AR_PAPRDChainMask 0x00000e00 +#define AR_PAPRDChainMask_S 9 + +#define MAP_ISR_S2_CST 6 +#define MAP_ISR_S2_GTT 6 +#define MAP_ISR_S2_TIM 3 +#define MAP_ISR_S2_CABEND 0 +#define MAP_ISR_S2_DTIMSYNC 7 +#define MAP_ISR_S2_DTIM 7 +#define MAP_ISR_S2_TSFOOR 4 +#define MAP_ISR_S2_BB_WATCHDOG 6 + +#define AR9003TXC_CONST(_ds) ((const struct ar9003_txc *) _ds) + +struct ar9003_rxs { + u32 ds_info; + u32 status1; + u32 status2; + u32 status3; + u32 status4; + u32 status5; + u32 status6; + u32 status7; + u32 status8; + u32 status9; + u32 status10; + u32 status11; +} __packed __aligned(4); + +/* Transmit Control Descriptor */ +struct ar9003_txc { + u32 info; /* descriptor information */ + u32 link; /* link pointer */ + u32 data0; /* data pointer to 1st buffer */ + u32 ctl3; /* DMA control 3 */ + u32 data1; /* data pointer to 2nd buffer */ + u32 ctl5; /* DMA control 5 */ + u32 data2; /* data pointer to 3rd buffer */ + u32 ctl7; /* DMA control 7 */ + u32 data3; /* data pointer to 4th buffer */ + u32 ctl9; /* DMA control 9 */ + u32 ctl10; /* DMA control 10 */ + u32 ctl11; /* DMA control 11 */ + u32 ctl12; /* DMA control 12 */ + u32 ctl13; /* DMA control 13 */ + u32 ctl14; /* DMA control 14 */ + u32 ctl15; /* DMA control 15 */ + u32 ctl16; /* DMA control 16 */ + u32 ctl17; /* DMA control 17 */ + u32 ctl18; /* DMA control 18 */ + u32 ctl19; /* DMA control 19 */ + u32 ctl20; /* DMA control 20 */ + u32 ctl21; /* DMA control 21 */ + u32 ctl22; /* DMA control 22 */ + u32 ctl23; /* DMA control 23 */ + u32 pad[8]; /* pad to cache line (128 bytes/32 dwords) */ +} __packed __aligned(4); + +struct ar9003_txs { + u32 ds_info; + u32 status1; + u32 status2; + u32 status3; + u32 status4; + u32 status5; + u32 status6; + u32 status7; + u32 status8; +} __packed __aligned(4); + +void ar9003_hw_attach_mac_ops(struct ath_hw *hw); +void ath9k_hw_set_rx_bufsize(struct ath_hw *ah, u16 buf_size); +void ath9k_hw_addrxbuf_edma(struct ath_hw *ah, u32 rxdp, + enum ath9k_rx_qtype qtype); + +int ath9k_hw_process_rxdesc_edma(struct ath_hw *ah, + struct ath_rx_status *rxs, + void *buf_addr); +void ath9k_hw_reset_txstatus_ring(struct ath_hw *ah); +void ath9k_hw_setup_statusring(struct ath_hw *ah, void *ts_start, + u32 ts_paddr_start, + u16 size); +#endif diff --git a/drivers/net/wireless/ath/ath9k/ar9003_mci.c b/drivers/net/wireless/ath/ath9k/ar9003_mci.c new file mode 100644 index 0000000..8dd0692 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9003_mci.c @@ -0,0 +1,1485 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "hw.h" +#include "hw-ops.h" +#include "ar9003_phy.h" +#include "ar9003_mci.h" + +static void ar9003_mci_reset_req_wakeup(struct ath_hw *ah) +{ + REG_RMW_FIELD(ah, AR_MCI_COMMAND2, + AR_MCI_COMMAND2_RESET_REQ_WAKEUP, 1); + udelay(1); + REG_RMW_FIELD(ah, AR_MCI_COMMAND2, + AR_MCI_COMMAND2_RESET_REQ_WAKEUP, 0); +} + +static int ar9003_mci_wait_for_interrupt(struct ath_hw *ah, u32 address, + u32 bit_position, int time_out) +{ + struct ath_common *common = ath9k_hw_common(ah); + + while (time_out) { + if (!(REG_READ(ah, address) & bit_position)) { + udelay(10); + time_out -= 10; + + if (time_out < 0) + break; + else + continue; + } + REG_WRITE(ah, address, bit_position); + + if (address != AR_MCI_INTERRUPT_RX_MSG_RAW) + break; + + if (bit_position & AR_MCI_INTERRUPT_RX_MSG_REQ_WAKE) + ar9003_mci_reset_req_wakeup(ah); + + if (bit_position & (AR_MCI_INTERRUPT_RX_MSG_SYS_SLEEPING | + AR_MCI_INTERRUPT_RX_MSG_SYS_WAKING)) + REG_WRITE(ah, AR_MCI_INTERRUPT_RAW, + AR_MCI_INTERRUPT_REMOTE_SLEEP_UPDATE); + + REG_WRITE(ah, AR_MCI_INTERRUPT_RAW, AR_MCI_INTERRUPT_RX_MSG); + break; + } + + if (time_out <= 0) { + ath_dbg(common, MCI, + "MCI Wait for Reg 0x%08x = 0x%08x timeout\n", + address, bit_position); + ath_dbg(common, MCI, + "MCI INT_RAW = 0x%08x, RX_MSG_RAW = 0x%08x\n", + REG_READ(ah, AR_MCI_INTERRUPT_RAW), + REG_READ(ah, AR_MCI_INTERRUPT_RX_MSG_RAW)); + time_out = 0; + } + + return time_out; +} + +static void ar9003_mci_remote_reset(struct ath_hw *ah, bool wait_done) +{ + u32 payload[4] = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffff00}; + + ar9003_mci_send_message(ah, MCI_REMOTE_RESET, 0, payload, 16, + wait_done, false); + udelay(5); +} + +static void ar9003_mci_send_lna_transfer(struct ath_hw *ah, bool wait_done) +{ + u32 payload = 0x00000000; + + ar9003_mci_send_message(ah, MCI_LNA_TRANS, 0, &payload, 1, + wait_done, false); +} + +static void ar9003_mci_send_req_wake(struct ath_hw *ah, bool wait_done) +{ + ar9003_mci_send_message(ah, MCI_REQ_WAKE, MCI_FLAG_DISABLE_TIMESTAMP, + NULL, 0, wait_done, false); + udelay(5); +} + +static void ar9003_mci_send_sys_waking(struct ath_hw *ah, bool wait_done) +{ + ar9003_mci_send_message(ah, MCI_SYS_WAKING, MCI_FLAG_DISABLE_TIMESTAMP, + NULL, 0, wait_done, false); +} + +static void ar9003_mci_send_lna_take(struct ath_hw *ah, bool wait_done) +{ + u32 payload = 0x70000000; + + ar9003_mci_send_message(ah, MCI_LNA_TAKE, 0, &payload, 1, + wait_done, false); +} + +static void ar9003_mci_send_sys_sleeping(struct ath_hw *ah, bool wait_done) +{ + ar9003_mci_send_message(ah, MCI_SYS_SLEEPING, + MCI_FLAG_DISABLE_TIMESTAMP, + NULL, 0, wait_done, false); +} + +static void ar9003_mci_send_coex_version_query(struct ath_hw *ah, + bool wait_done) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u32 payload[4] = {0, 0, 0, 0}; + + if (mci->bt_version_known || + (mci->bt_state == MCI_BT_SLEEP)) + return; + + MCI_GPM_SET_TYPE_OPCODE(payload, MCI_GPM_COEX_AGENT, + MCI_GPM_COEX_VERSION_QUERY); + ar9003_mci_send_message(ah, MCI_GPM, 0, payload, 16, wait_done, true); +} + +static void ar9003_mci_send_coex_version_response(struct ath_hw *ah, + bool wait_done) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u32 payload[4] = {0, 0, 0, 0}; + + MCI_GPM_SET_TYPE_OPCODE(payload, MCI_GPM_COEX_AGENT, + MCI_GPM_COEX_VERSION_RESPONSE); + *(((u8 *)payload) + MCI_GPM_COEX_B_MAJOR_VERSION) = + mci->wlan_ver_major; + *(((u8 *)payload) + MCI_GPM_COEX_B_MINOR_VERSION) = + mci->wlan_ver_minor; + ar9003_mci_send_message(ah, MCI_GPM, 0, payload, 16, wait_done, true); +} + +static void ar9003_mci_send_coex_wlan_channels(struct ath_hw *ah, + bool wait_done) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u32 *payload = &mci->wlan_channels[0]; + + if (!mci->wlan_channels_update || + (mci->bt_state == MCI_BT_SLEEP)) + return; + + MCI_GPM_SET_TYPE_OPCODE(payload, MCI_GPM_COEX_AGENT, + MCI_GPM_COEX_WLAN_CHANNELS); + ar9003_mci_send_message(ah, MCI_GPM, 0, payload, 16, wait_done, true); + MCI_GPM_SET_TYPE_OPCODE(payload, 0xff, 0xff); +} + +static void ar9003_mci_send_coex_bt_status_query(struct ath_hw *ah, + bool wait_done, u8 query_type) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u32 payload[4] = {0, 0, 0, 0}; + bool query_btinfo; + + if (mci->bt_state == MCI_BT_SLEEP) + return; + + query_btinfo = !!(query_type & (MCI_GPM_COEX_QUERY_BT_ALL_INFO | + MCI_GPM_COEX_QUERY_BT_TOPOLOGY)); + MCI_GPM_SET_TYPE_OPCODE(payload, MCI_GPM_COEX_AGENT, + MCI_GPM_COEX_STATUS_QUERY); + + *(((u8 *)payload) + MCI_GPM_COEX_B_BT_BITMAP) = query_type; + + /* + * If bt_status_query message is not sent successfully, + * then need_flush_btinfo should be set again. + */ + if (!ar9003_mci_send_message(ah, MCI_GPM, 0, payload, 16, + wait_done, true)) { + if (query_btinfo) + mci->need_flush_btinfo = true; + } + + if (query_btinfo) + mci->query_bt = false; +} + +static void ar9003_mci_send_coex_halt_bt_gpm(struct ath_hw *ah, bool halt, + bool wait_done) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u32 payload[4] = {0, 0, 0, 0}; + + MCI_GPM_SET_TYPE_OPCODE(payload, MCI_GPM_COEX_AGENT, + MCI_GPM_COEX_HALT_BT_GPM); + + if (halt) { + mci->query_bt = true; + /* Send next unhalt no matter halt sent or not */ + mci->unhalt_bt_gpm = true; + mci->need_flush_btinfo = true; + *(((u8 *)payload) + MCI_GPM_COEX_B_HALT_STATE) = + MCI_GPM_COEX_BT_GPM_HALT; + } else + *(((u8 *)payload) + MCI_GPM_COEX_B_HALT_STATE) = + MCI_GPM_COEX_BT_GPM_UNHALT; + + ar9003_mci_send_message(ah, MCI_GPM, 0, payload, 16, wait_done, true); +} + +static void ar9003_mci_prep_interface(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u32 saved_mci_int_en; + u32 mci_timeout = 150; + + mci->bt_state = MCI_BT_SLEEP; + saved_mci_int_en = REG_READ(ah, AR_MCI_INTERRUPT_EN); + + REG_WRITE(ah, AR_MCI_INTERRUPT_EN, 0); + REG_WRITE(ah, AR_MCI_INTERRUPT_RX_MSG_RAW, + REG_READ(ah, AR_MCI_INTERRUPT_RX_MSG_RAW)); + REG_WRITE(ah, AR_MCI_INTERRUPT_RAW, + REG_READ(ah, AR_MCI_INTERRUPT_RAW)); + + ar9003_mci_remote_reset(ah, true); + ar9003_mci_send_req_wake(ah, true); + + if (!ar9003_mci_wait_for_interrupt(ah, AR_MCI_INTERRUPT_RX_MSG_RAW, + AR_MCI_INTERRUPT_RX_MSG_SYS_WAKING, 500)) + goto clear_redunt; + + mci->bt_state = MCI_BT_AWAKE; + + /* + * we don't need to send more remote_reset at this moment. + * If BT receive first remote_reset, then BT HW will + * be cleaned up and will be able to receive req_wake + * and BT HW will respond sys_waking. + * In this case, WLAN will receive BT's HW sys_waking. + * Otherwise, if BT SW missed initial remote_reset, + * that remote_reset will still clean up BT MCI RX, + * and the req_wake will wake BT up, + * and BT SW will respond this req_wake with a remote_reset and + * sys_waking. In this case, WLAN will receive BT's SW + * sys_waking. In either case, BT's RX is cleaned up. So we + * don't need to reply BT's remote_reset now, if any. + * Similarly, if in any case, WLAN can receive BT's sys_waking, + * that means WLAN's RX is also fine. + */ + ar9003_mci_send_sys_waking(ah, true); + udelay(10); + + /* + * Set BT priority interrupt value to be 0xff to + * avoid having too many BT PRIORITY interrupts. + */ + REG_WRITE(ah, AR_MCI_BT_PRI0, 0xFFFFFFFF); + REG_WRITE(ah, AR_MCI_BT_PRI1, 0xFFFFFFFF); + REG_WRITE(ah, AR_MCI_BT_PRI2, 0xFFFFFFFF); + REG_WRITE(ah, AR_MCI_BT_PRI3, 0xFFFFFFFF); + REG_WRITE(ah, AR_MCI_BT_PRI, 0X000000FF); + + /* + * A contention reset will be received after send out + * sys_waking. Also BT priority interrupt bits will be set. + * Clear those bits before the next step. + */ + + REG_WRITE(ah, AR_MCI_INTERRUPT_RX_MSG_RAW, + AR_MCI_INTERRUPT_RX_MSG_CONT_RST); + REG_WRITE(ah, AR_MCI_INTERRUPT_RAW, AR_MCI_INTERRUPT_BT_PRI); + + if (mci->is_2g) { + ar9003_mci_send_lna_transfer(ah, true); + udelay(5); + } + + if ((mci->is_2g && !mci->update_2g5g)) { + if (ar9003_mci_wait_for_interrupt(ah, + AR_MCI_INTERRUPT_RX_MSG_RAW, + AR_MCI_INTERRUPT_RX_MSG_LNA_INFO, + mci_timeout)) + ath_dbg(common, MCI, + "MCI WLAN has control over the LNA & BT obeys it\n"); + else + ath_dbg(common, MCI, + "MCI BT didn't respond to LNA_TRANS\n"); + } + +clear_redunt: + /* Clear the extra redundant SYS_WAKING from BT */ + if ((mci->bt_state == MCI_BT_AWAKE) && + (REG_READ_FIELD(ah, AR_MCI_INTERRUPT_RX_MSG_RAW, + AR_MCI_INTERRUPT_RX_MSG_SYS_WAKING)) && + (REG_READ_FIELD(ah, AR_MCI_INTERRUPT_RX_MSG_RAW, + AR_MCI_INTERRUPT_RX_MSG_SYS_SLEEPING) == 0)) { + REG_WRITE(ah, AR_MCI_INTERRUPT_RX_MSG_RAW, + AR_MCI_INTERRUPT_RX_MSG_SYS_WAKING); + REG_WRITE(ah, AR_MCI_INTERRUPT_RAW, + AR_MCI_INTERRUPT_REMOTE_SLEEP_UPDATE); + } + + REG_WRITE(ah, AR_MCI_INTERRUPT_EN, saved_mci_int_en); +} + +void ar9003_mci_set_full_sleep(struct ath_hw *ah) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + + if (ar9003_mci_state(ah, MCI_STATE_ENABLE) && + (mci->bt_state != MCI_BT_SLEEP) && + !mci->halted_bt_gpm) { + ar9003_mci_send_coex_halt_bt_gpm(ah, true, true); + } + + mci->ready = false; +} + +static void ar9003_mci_disable_interrupt(struct ath_hw *ah) +{ + REG_WRITE(ah, AR_MCI_INTERRUPT_EN, 0); + REG_WRITE(ah, AR_MCI_INTERRUPT_RX_MSG_EN, 0); +} + +static void ar9003_mci_enable_interrupt(struct ath_hw *ah) +{ + REG_WRITE(ah, AR_MCI_INTERRUPT_EN, AR_MCI_INTERRUPT_DEFAULT); + REG_WRITE(ah, AR_MCI_INTERRUPT_RX_MSG_EN, + AR_MCI_INTERRUPT_RX_MSG_DEFAULT); +} + +static bool ar9003_mci_check_int(struct ath_hw *ah, u32 ints) +{ + u32 intr; + + intr = REG_READ(ah, AR_MCI_INTERRUPT_RX_MSG_RAW); + return ((intr & ints) == ints); +} + +void ar9003_mci_get_interrupt(struct ath_hw *ah, u32 *raw_intr, + u32 *rx_msg_intr) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + + *raw_intr = mci->raw_intr; + *rx_msg_intr = mci->rx_msg_intr; + + /* Clean int bits after the values are read. */ + mci->raw_intr = 0; + mci->rx_msg_intr = 0; +} +EXPORT_SYMBOL(ar9003_mci_get_interrupt); + +void ar9003_mci_get_isr(struct ath_hw *ah, enum ath9k_int *masked) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u32 raw_intr, rx_msg_intr; + + rx_msg_intr = REG_READ(ah, AR_MCI_INTERRUPT_RX_MSG_RAW); + raw_intr = REG_READ(ah, AR_MCI_INTERRUPT_RAW); + + if ((raw_intr == 0xdeadbeef) || (rx_msg_intr == 0xdeadbeef)) { + ath_dbg(common, MCI, + "MCI gets 0xdeadbeef during int processing\n"); + } else { + mci->rx_msg_intr |= rx_msg_intr; + mci->raw_intr |= raw_intr; + *masked |= ATH9K_INT_MCI; + + if (rx_msg_intr & AR_MCI_INTERRUPT_RX_MSG_CONT_INFO) + mci->cont_status = REG_READ(ah, AR_MCI_CONT_STATUS); + + REG_WRITE(ah, AR_MCI_INTERRUPT_RX_MSG_RAW, rx_msg_intr); + REG_WRITE(ah, AR_MCI_INTERRUPT_RAW, raw_intr); + } +} + +static void ar9003_mci_2g5g_changed(struct ath_hw *ah, bool is_2g) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + + if (!mci->update_2g5g && + (mci->is_2g != is_2g)) + mci->update_2g5g = true; + + mci->is_2g = is_2g; +} + +static bool ar9003_mci_is_gpm_valid(struct ath_hw *ah, u32 msg_index) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u32 *payload; + u32 recv_type, offset; + + if (msg_index == MCI_GPM_INVALID) + return false; + + offset = msg_index << 4; + + payload = (u32 *)(mci->gpm_buf + offset); + recv_type = MCI_GPM_TYPE(payload); + + if (recv_type == MCI_GPM_RSVD_PATTERN) + return false; + + return true; +} + +static void ar9003_mci_observation_set_up(struct ath_hw *ah) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + + if (mci->config & ATH_MCI_CONFIG_MCI_OBS_MCI) { + ath9k_hw_cfg_output(ah, 3, AR_GPIO_OUTPUT_MUX_AS_MCI_WLAN_DATA); + ath9k_hw_cfg_output(ah, 2, AR_GPIO_OUTPUT_MUX_AS_MCI_WLAN_CLK); + ath9k_hw_cfg_output(ah, 1, AR_GPIO_OUTPUT_MUX_AS_MCI_BT_DATA); + ath9k_hw_cfg_output(ah, 0, AR_GPIO_OUTPUT_MUX_AS_MCI_BT_CLK); + } else if (mci->config & ATH_MCI_CONFIG_MCI_OBS_TXRX) { + ath9k_hw_cfg_output(ah, 3, AR_GPIO_OUTPUT_MUX_AS_WL_IN_TX); + ath9k_hw_cfg_output(ah, 2, AR_GPIO_OUTPUT_MUX_AS_WL_IN_RX); + ath9k_hw_cfg_output(ah, 1, AR_GPIO_OUTPUT_MUX_AS_BT_IN_TX); + ath9k_hw_cfg_output(ah, 0, AR_GPIO_OUTPUT_MUX_AS_BT_IN_RX); + ath9k_hw_cfg_output(ah, 5, AR_GPIO_OUTPUT_MUX_AS_OUTPUT); + } else if (mci->config & ATH_MCI_CONFIG_MCI_OBS_BT) { + ath9k_hw_cfg_output(ah, 3, AR_GPIO_OUTPUT_MUX_AS_BT_IN_TX); + ath9k_hw_cfg_output(ah, 2, AR_GPIO_OUTPUT_MUX_AS_BT_IN_RX); + ath9k_hw_cfg_output(ah, 1, AR_GPIO_OUTPUT_MUX_AS_MCI_BT_DATA); + ath9k_hw_cfg_output(ah, 0, AR_GPIO_OUTPUT_MUX_AS_MCI_BT_CLK); + } else + return; + + REG_SET_BIT(ah, AR_GPIO_INPUT_EN_VAL, AR_GPIO_JTAG_DISABLE); + + REG_RMW_FIELD(ah, AR_PHY_GLB_CONTROL, AR_GLB_DS_JTAG_DISABLE, 1); + REG_RMW_FIELD(ah, AR_PHY_GLB_CONTROL, AR_GLB_WLAN_UART_INTF_EN, 0); + REG_SET_BIT(ah, AR_GLB_GPIO_CONTROL, ATH_MCI_CONFIG_MCI_OBS_GPIO); + + REG_RMW_FIELD(ah, AR_BTCOEX_CTRL2, AR_BTCOEX_CTRL2_GPIO_OBS_SEL, 0); + REG_RMW_FIELD(ah, AR_BTCOEX_CTRL2, AR_BTCOEX_CTRL2_MAC_BB_OBS_SEL, 1); + REG_WRITE(ah, AR_OBS, 0x4b); + REG_RMW_FIELD(ah, AR_DIAG_SW, AR_DIAG_OBS_PT_SEL1, 0x03); + REG_RMW_FIELD(ah, AR_DIAG_SW, AR_DIAG_OBS_PT_SEL2, 0x01); + REG_RMW_FIELD(ah, AR_MACMISC, AR_MACMISC_MISC_OBS_BUS_LSB, 0x02); + REG_RMW_FIELD(ah, AR_MACMISC, AR_MACMISC_MISC_OBS_BUS_MSB, 0x03); + REG_RMW_FIELD(ah, AR_PHY_TEST_CTL_STATUS, + AR_PHY_TEST_CTL_DEBUGPORT_SEL, 0x07); +} + +static bool ar9003_mci_send_coex_bt_flags(struct ath_hw *ah, bool wait_done, + u8 opcode, u32 bt_flags) +{ + u32 pld[4] = {0, 0, 0, 0}; + + MCI_GPM_SET_TYPE_OPCODE(pld, MCI_GPM_COEX_AGENT, + MCI_GPM_COEX_BT_UPDATE_FLAGS); + + *(((u8 *)pld) + MCI_GPM_COEX_B_BT_FLAGS_OP) = opcode; + *(((u8 *)pld) + MCI_GPM_COEX_W_BT_FLAGS + 0) = bt_flags & 0xFF; + *(((u8 *)pld) + MCI_GPM_COEX_W_BT_FLAGS + 1) = (bt_flags >> 8) & 0xFF; + *(((u8 *)pld) + MCI_GPM_COEX_W_BT_FLAGS + 2) = (bt_flags >> 16) & 0xFF; + *(((u8 *)pld) + MCI_GPM_COEX_W_BT_FLAGS + 3) = (bt_flags >> 24) & 0xFF; + + return ar9003_mci_send_message(ah, MCI_GPM, 0, pld, 16, + wait_done, true); +} + +static void ar9003_mci_sync_bt_state(struct ath_hw *ah) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u32 cur_bt_state; + + cur_bt_state = ar9003_mci_state(ah, MCI_STATE_REMOTE_SLEEP); + + if (mci->bt_state != cur_bt_state) + mci->bt_state = cur_bt_state; + + if (mci->bt_state != MCI_BT_SLEEP) { + + ar9003_mci_send_coex_version_query(ah, true); + ar9003_mci_send_coex_wlan_channels(ah, true); + + if (mci->unhalt_bt_gpm == true) + ar9003_mci_send_coex_halt_bt_gpm(ah, false, true); + } +} + +void ar9003_mci_check_bt(struct ath_hw *ah) +{ + struct ath9k_hw_mci *mci_hw = &ah->btcoex_hw.mci; + + if (!mci_hw->ready) + return; + + /* + * check BT state again to make + * sure it's not changed. + */ + ar9003_mci_sync_bt_state(ah); + ar9003_mci_2g5g_switch(ah, true); + + if ((mci_hw->bt_state == MCI_BT_AWAKE) && + (mci_hw->query_bt == true)) { + mci_hw->need_flush_btinfo = true; + } +} + +static void ar9003_mci_process_gpm_extra(struct ath_hw *ah, u8 gpm_type, + u8 gpm_opcode, u32 *p_gpm) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u8 *p_data = (u8 *) p_gpm; + + if (gpm_type != MCI_GPM_COEX_AGENT) + return; + + switch (gpm_opcode) { + case MCI_GPM_COEX_VERSION_QUERY: + ath_dbg(common, MCI, "MCI Recv GPM COEX Version Query\n"); + ar9003_mci_send_coex_version_response(ah, true); + break; + case MCI_GPM_COEX_VERSION_RESPONSE: + ath_dbg(common, MCI, "MCI Recv GPM COEX Version Response\n"); + mci->bt_ver_major = + *(p_data + MCI_GPM_COEX_B_MAJOR_VERSION); + mci->bt_ver_minor = + *(p_data + MCI_GPM_COEX_B_MINOR_VERSION); + mci->bt_version_known = true; + ath_dbg(common, MCI, "MCI BT Coex version: %d.%d\n", + mci->bt_ver_major, mci->bt_ver_minor); + break; + case MCI_GPM_COEX_STATUS_QUERY: + ath_dbg(common, MCI, + "MCI Recv GPM COEX Status Query = 0x%02X\n", + *(p_data + MCI_GPM_COEX_B_WLAN_BITMAP)); + mci->wlan_channels_update = true; + ar9003_mci_send_coex_wlan_channels(ah, true); + break; + case MCI_GPM_COEX_BT_PROFILE_INFO: + mci->query_bt = true; + ath_dbg(common, MCI, "MCI Recv GPM COEX BT_Profile_Info\n"); + break; + case MCI_GPM_COEX_BT_STATUS_UPDATE: + mci->query_bt = true; + ath_dbg(common, MCI, + "MCI Recv GPM COEX BT_Status_Update SEQ=%d (drop&query)\n", + *(p_gpm + 3)); + break; + default: + break; + } +} + +static u32 ar9003_mci_wait_for_gpm(struct ath_hw *ah, u8 gpm_type, + u8 gpm_opcode, int time_out) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u32 *p_gpm = NULL, mismatch = 0, more_data; + u32 offset; + u8 recv_type = 0, recv_opcode = 0; + bool b_is_bt_cal_done = (gpm_type == MCI_GPM_BT_CAL_DONE); + + more_data = time_out ? MCI_GPM_NOMORE : MCI_GPM_MORE; + + while (time_out > 0) { + if (p_gpm) { + MCI_GPM_RECYCLE(p_gpm); + p_gpm = NULL; + } + + if (more_data != MCI_GPM_MORE) + time_out = ar9003_mci_wait_for_interrupt(ah, + AR_MCI_INTERRUPT_RX_MSG_RAW, + AR_MCI_INTERRUPT_RX_MSG_GPM, + time_out); + + if (!time_out) + break; + + offset = ar9003_mci_get_next_gpm_offset(ah, false, &more_data); + + if (offset == MCI_GPM_INVALID) + continue; + + p_gpm = (u32 *) (mci->gpm_buf + offset); + recv_type = MCI_GPM_TYPE(p_gpm); + recv_opcode = MCI_GPM_OPCODE(p_gpm); + + if (MCI_GPM_IS_CAL_TYPE(recv_type)) { + if (recv_type == gpm_type) { + if ((gpm_type == MCI_GPM_BT_CAL_DONE) && + !b_is_bt_cal_done) { + gpm_type = MCI_GPM_BT_CAL_GRANT; + continue; + } + break; + } + } else if ((recv_type == gpm_type) && + (recv_opcode == gpm_opcode)) + break; + + /* + * check if it's cal_grant + * + * When we're waiting for cal_grant in reset routine, + * it's possible that BT sends out cal_request at the + * same time. Since BT's calibration doesn't happen + * that often, we'll let BT completes calibration then + * we continue to wait for cal_grant from BT. + * Orginal: Wait BT_CAL_GRANT. + * New: Receive BT_CAL_REQ -> send WLAN_CAL_GRANT->wait + * BT_CAL_DONE -> Wait BT_CAL_GRANT. + */ + + if ((gpm_type == MCI_GPM_BT_CAL_GRANT) && + (recv_type == MCI_GPM_BT_CAL_REQ)) { + + u32 payload[4] = {0, 0, 0, 0}; + + gpm_type = MCI_GPM_BT_CAL_DONE; + MCI_GPM_SET_CAL_TYPE(payload, + MCI_GPM_WLAN_CAL_GRANT); + ar9003_mci_send_message(ah, MCI_GPM, 0, payload, 16, + false, false); + continue; + } else { + ath_dbg(common, MCI, "MCI GPM subtype not match 0x%x\n", + *(p_gpm + 1)); + mismatch++; + ar9003_mci_process_gpm_extra(ah, recv_type, + recv_opcode, p_gpm); + } + } + + if (p_gpm) { + MCI_GPM_RECYCLE(p_gpm); + p_gpm = NULL; + } + + if (time_out <= 0) + time_out = 0; + + while (more_data == MCI_GPM_MORE) { + offset = ar9003_mci_get_next_gpm_offset(ah, false, &more_data); + if (offset == MCI_GPM_INVALID) + break; + + p_gpm = (u32 *) (mci->gpm_buf + offset); + recv_type = MCI_GPM_TYPE(p_gpm); + recv_opcode = MCI_GPM_OPCODE(p_gpm); + + if (!MCI_GPM_IS_CAL_TYPE(recv_type)) + ar9003_mci_process_gpm_extra(ah, recv_type, + recv_opcode, p_gpm); + + MCI_GPM_RECYCLE(p_gpm); + } + + return time_out; +} + +bool ar9003_mci_start_reset(struct ath_hw *ah, struct ath9k_channel *chan) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_hw_mci *mci_hw = &ah->btcoex_hw.mci; + u32 payload[4] = {0, 0, 0, 0}; + + ar9003_mci_2g5g_changed(ah, IS_CHAN_2GHZ(chan)); + + if (mci_hw->bt_state != MCI_BT_CAL_START) + return false; + + mci_hw->bt_state = MCI_BT_CAL; + + /* + * MCI FIX: disable mci interrupt here. This is to avoid + * SW_MSG_DONE or RX_MSG bits to trigger MCI_INT and + * lead to mci_intr reentry. + */ + ar9003_mci_disable_interrupt(ah); + + MCI_GPM_SET_CAL_TYPE(payload, MCI_GPM_WLAN_CAL_GRANT); + ar9003_mci_send_message(ah, MCI_GPM, 0, payload, + 16, true, false); + + /* Wait BT calibration to be completed for 25ms */ + + if (ar9003_mci_wait_for_gpm(ah, MCI_GPM_BT_CAL_DONE, + 0, 25000)) + ath_dbg(common, MCI, "MCI BT_CAL_DONE received\n"); + else + ath_dbg(common, MCI, + "MCI BT_CAL_DONE not received\n"); + + mci_hw->bt_state = MCI_BT_AWAKE; + /* MCI FIX: enable mci interrupt here */ + ar9003_mci_enable_interrupt(ah); + + return true; +} + +int ar9003_mci_end_reset(struct ath_hw *ah, struct ath9k_channel *chan, + struct ath9k_hw_cal_data *caldata) +{ + struct ath9k_hw_mci *mci_hw = &ah->btcoex_hw.mci; + + if (!mci_hw->ready) + return 0; + + if (!IS_CHAN_2GHZ(chan) || (mci_hw->bt_state != MCI_BT_SLEEP)) + goto exit; + + if (!ar9003_mci_check_int(ah, AR_MCI_INTERRUPT_RX_MSG_REMOTE_RESET) && + !ar9003_mci_check_int(ah, AR_MCI_INTERRUPT_RX_MSG_REQ_WAKE)) + goto exit; + + /* + * BT is sleeping. Check if BT wakes up during + * WLAN calibration. If BT wakes up during + * WLAN calibration, need to go through all + * message exchanges again and recal. + */ + REG_WRITE(ah, AR_MCI_INTERRUPT_RX_MSG_RAW, + (AR_MCI_INTERRUPT_RX_MSG_REMOTE_RESET | + AR_MCI_INTERRUPT_RX_MSG_REQ_WAKE)); + + ar9003_mci_remote_reset(ah, true); + ar9003_mci_send_sys_waking(ah, true); + udelay(1); + + if (IS_CHAN_2GHZ(chan)) + ar9003_mci_send_lna_transfer(ah, true); + + mci_hw->bt_state = MCI_BT_AWAKE; + + REG_CLR_BIT(ah, AR_PHY_TIMING4, + 1 << AR_PHY_TIMING_CONTROL4_DO_GAIN_DC_IQ_CAL_SHIFT); + + if (caldata) { + caldata->done_txiqcal_once = false; + caldata->done_txclcal_once = false; + caldata->rtt_done = false; + } + + if (!ath9k_hw_init_cal(ah, chan)) + return -EIO; + + REG_SET_BIT(ah, AR_PHY_TIMING4, + 1 << AR_PHY_TIMING_CONTROL4_DO_GAIN_DC_IQ_CAL_SHIFT); + +exit: + ar9003_mci_enable_interrupt(ah); + return 0; +} + +static void ar9003_mci_mute_bt(struct ath_hw *ah) +{ + /* disable all MCI messages */ + REG_WRITE(ah, AR_MCI_MSG_ATTRIBUTES_TABLE, 0xffff0000); + REG_SET_BIT(ah, AR_MCI_TX_CTRL, AR_MCI_TX_CTRL_DISABLE_LNA_UPDATE); + + /* wait pending HW messages to flush out */ + udelay(10); + + /* + * Send LNA_TAKE and SYS_SLEEPING when + * 1. reset not after resuming from full sleep + * 2. before reset MCI RX, to quiet BT and avoid MCI RX misalignment + */ + ar9003_mci_send_lna_take(ah, true); + + udelay(5); + + ar9003_mci_send_sys_sleeping(ah, true); +} + +static void ar9003_mci_osla_setup(struct ath_hw *ah, bool enable) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u32 thresh; + + if (!enable) { + REG_CLR_BIT(ah, AR_BTCOEX_CTRL, + AR_BTCOEX_CTRL_ONE_STEP_LOOK_AHEAD_EN); + return; + } + REG_RMW_FIELD(ah, AR_MCI_SCHD_TABLE_2, AR_MCI_SCHD_TABLE_2_HW_BASED, 1); + REG_RMW_FIELD(ah, AR_MCI_SCHD_TABLE_2, + AR_MCI_SCHD_TABLE_2_MEM_BASED, 1); + + if (AR_SREV_9565(ah)) + REG_RMW_FIELD(ah, AR_MCI_MISC, AR_MCI_MISC_HW_FIX_EN, 1); + + if (!(mci->config & ATH_MCI_CONFIG_DISABLE_AGGR_THRESH)) { + thresh = MS(mci->config, ATH_MCI_CONFIG_AGGR_THRESH); + REG_RMW_FIELD(ah, AR_BTCOEX_CTRL, + AR_BTCOEX_CTRL_AGGR_THRESH, thresh); + REG_RMW_FIELD(ah, AR_BTCOEX_CTRL, + AR_BTCOEX_CTRL_TIME_TO_NEXT_BT_THRESH_EN, 1); + } else + REG_RMW_FIELD(ah, AR_BTCOEX_CTRL, + AR_BTCOEX_CTRL_TIME_TO_NEXT_BT_THRESH_EN, 0); + + REG_RMW_FIELD(ah, AR_BTCOEX_CTRL, + AR_BTCOEX_CTRL_ONE_STEP_LOOK_AHEAD_EN, 1); +} + +int ar9003_mci_reset(struct ath_hw *ah, bool en_int, bool is_2g, + bool is_full_sleep) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u32 regval, i; + + ath_dbg(common, MCI, "MCI Reset (full_sleep = %d, is_2g = %d)\n", + is_full_sleep, is_2g); + + if (!mci->gpm_addr && !mci->sched_addr) { + ath_err(common, "MCI GPM and schedule buffers are not allocated\n"); + return -ENOMEM; + } + + if (REG_READ(ah, AR_BTCOEX_CTRL) == 0xdeadbeef) { + ath_err(common, "BTCOEX control register is dead\n"); + return -EINVAL; + } + + /* Program MCI DMA related registers */ + REG_WRITE(ah, AR_MCI_GPM_0, mci->gpm_addr); + REG_WRITE(ah, AR_MCI_GPM_1, mci->gpm_len); + REG_WRITE(ah, AR_MCI_SCHD_TABLE_0, mci->sched_addr); + + /* + * To avoid MCI state machine be affected by incoming remote MCI msgs, + * MCI mode will be enabled later, right before reset the MCI TX and RX. + */ + + regval = SM(1, AR_BTCOEX_CTRL_AR9462_MODE) | + SM(1, AR_BTCOEX_CTRL_WBTIMER_EN) | + SM(1, AR_BTCOEX_CTRL_PA_SHARED) | + SM(1, AR_BTCOEX_CTRL_LNA_SHARED) | + SM(0, AR_BTCOEX_CTRL_1_CHAIN_ACK) | + SM(0, AR_BTCOEX_CTRL_1_CHAIN_BCN) | + SM(0, AR_BTCOEX_CTRL_ONE_STEP_LOOK_AHEAD_EN); + if (AR_SREV_9565(ah)) { + regval |= SM(1, AR_BTCOEX_CTRL_NUM_ANTENNAS) | + SM(1, AR_BTCOEX_CTRL_RX_CHAIN_MASK); + REG_RMW_FIELD(ah, AR_BTCOEX_CTRL2, + AR_BTCOEX_CTRL2_TX_CHAIN_MASK, 0x1); + } else { + regval |= SM(2, AR_BTCOEX_CTRL_NUM_ANTENNAS) | + SM(3, AR_BTCOEX_CTRL_RX_CHAIN_MASK); + } + + REG_WRITE(ah, AR_BTCOEX_CTRL, regval); + + if (is_2g && !(mci->config & ATH_MCI_CONFIG_DISABLE_OSLA)) + ar9003_mci_osla_setup(ah, true); + else + ar9003_mci_osla_setup(ah, false); + + REG_SET_BIT(ah, AR_PHY_GLB_CONTROL, + AR_BTCOEX_CTRL_SPDT_ENABLE); + REG_RMW_FIELD(ah, AR_BTCOEX_CTRL3, + AR_BTCOEX_CTRL3_CONT_INFO_TIMEOUT, 20); + + REG_RMW_FIELD(ah, AR_BTCOEX_CTRL2, AR_BTCOEX_CTRL2_RX_DEWEIGHT, 0); + REG_RMW_FIELD(ah, AR_PCU_MISC, AR_PCU_BT_ANT_PREVENT_RX, 0); + + /* Set the time out to 3.125ms (5 BT slots) */ + REG_RMW_FIELD(ah, AR_BTCOEX_WL_LNA, AR_BTCOEX_WL_LNA_TIMEOUT, 0x3D090); + + /* concurrent tx priority */ + if (mci->config & ATH_MCI_CONFIG_CONCUR_TX) { + REG_RMW_FIELD(ah, AR_BTCOEX_CTRL2, + AR_BTCOEX_CTRL2_DESC_BASED_TXPWR_ENABLE, 0); + REG_RMW_FIELD(ah, AR_BTCOEX_CTRL2, + AR_BTCOEX_CTRL2_TXPWR_THRESH, 0x7f); + REG_RMW_FIELD(ah, AR_BTCOEX_CTRL, + AR_BTCOEX_CTRL_REDUCE_TXPWR, 0); + for (i = 0; i < 8; i++) + REG_WRITE(ah, AR_BTCOEX_MAX_TXPWR(i), 0x7f7f7f7f); + } + + regval = MS(mci->config, ATH_MCI_CONFIG_CLK_DIV); + REG_RMW_FIELD(ah, AR_MCI_TX_CTRL, AR_MCI_TX_CTRL_CLK_DIV, regval); + REG_SET_BIT(ah, AR_BTCOEX_CTRL, AR_BTCOEX_CTRL_MCI_MODE_EN); + + /* Resetting the Rx and Tx paths of MCI */ + regval = REG_READ(ah, AR_MCI_COMMAND2); + regval |= SM(1, AR_MCI_COMMAND2_RESET_TX); + REG_WRITE(ah, AR_MCI_COMMAND2, regval); + + udelay(1); + + regval &= ~SM(1, AR_MCI_COMMAND2_RESET_TX); + REG_WRITE(ah, AR_MCI_COMMAND2, regval); + + if (is_full_sleep) { + ar9003_mci_mute_bt(ah); + udelay(100); + } + + /* Check pending GPM msg before MCI Reset Rx */ + ar9003_mci_check_gpm_offset(ah); + + regval |= SM(1, AR_MCI_COMMAND2_RESET_RX); + REG_WRITE(ah, AR_MCI_COMMAND2, regval); + udelay(1); + regval &= ~SM(1, AR_MCI_COMMAND2_RESET_RX); + REG_WRITE(ah, AR_MCI_COMMAND2, regval); + + ar9003_mci_get_next_gpm_offset(ah, true, NULL); + + REG_WRITE(ah, AR_MCI_MSG_ATTRIBUTES_TABLE, + (SM(0xe801, AR_MCI_MSG_ATTRIBUTES_TABLE_INVALID_HDR) | + SM(0x0000, AR_MCI_MSG_ATTRIBUTES_TABLE_CHECKSUM))); + + REG_CLR_BIT(ah, AR_MCI_TX_CTRL, + AR_MCI_TX_CTRL_DISABLE_LNA_UPDATE); + + ar9003_mci_observation_set_up(ah); + + mci->ready = true; + ar9003_mci_prep_interface(ah); + + if (AR_SREV_9565(ah)) + REG_RMW_FIELD(ah, AR_MCI_DBG_CNT_CTRL, + AR_MCI_DBG_CNT_CTRL_ENABLE, 0); + if (en_int) + ar9003_mci_enable_interrupt(ah); + + return 0; +} + +void ar9003_mci_stop_bt(struct ath_hw *ah, bool save_fullsleep) +{ + struct ath9k_hw_mci *mci_hw = &ah->btcoex_hw.mci; + + ar9003_mci_disable_interrupt(ah); + + if (mci_hw->ready && !save_fullsleep) { + ar9003_mci_mute_bt(ah); + udelay(20); + REG_WRITE(ah, AR_BTCOEX_CTRL, 0); + } + + mci_hw->bt_state = MCI_BT_SLEEP; + mci_hw->ready = false; +} + +static void ar9003_mci_send_2g5g_status(struct ath_hw *ah, bool wait_done) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u32 new_flags, to_set, to_clear; + + if (!mci->update_2g5g || (mci->bt_state == MCI_BT_SLEEP)) + return; + + if (mci->is_2g) { + new_flags = MCI_2G_FLAGS; + to_clear = MCI_2G_FLAGS_CLEAR_MASK; + to_set = MCI_2G_FLAGS_SET_MASK; + } else { + new_flags = MCI_5G_FLAGS; + to_clear = MCI_5G_FLAGS_CLEAR_MASK; + to_set = MCI_5G_FLAGS_SET_MASK; + } + + if (to_clear) + ar9003_mci_send_coex_bt_flags(ah, wait_done, + MCI_GPM_COEX_BT_FLAGS_CLEAR, + to_clear); + if (to_set) + ar9003_mci_send_coex_bt_flags(ah, wait_done, + MCI_GPM_COEX_BT_FLAGS_SET, + to_set); +} + +static void ar9003_mci_queue_unsent_gpm(struct ath_hw *ah, u8 header, + u32 *payload, bool queue) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u8 type, opcode; + + /* check if the message is to be queued */ + if (header != MCI_GPM) + return; + + type = MCI_GPM_TYPE(payload); + opcode = MCI_GPM_OPCODE(payload); + + if (type != MCI_GPM_COEX_AGENT) + return; + + switch (opcode) { + case MCI_GPM_COEX_BT_UPDATE_FLAGS: + if (*(((u8 *)payload) + MCI_GPM_COEX_B_BT_FLAGS_OP) == + MCI_GPM_COEX_BT_FLAGS_READ) + break; + + mci->update_2g5g = queue; + + break; + case MCI_GPM_COEX_WLAN_CHANNELS: + mci->wlan_channels_update = queue; + break; + case MCI_GPM_COEX_HALT_BT_GPM: + if (*(((u8 *)payload) + MCI_GPM_COEX_B_HALT_STATE) == + MCI_GPM_COEX_BT_GPM_UNHALT) { + mci->unhalt_bt_gpm = queue; + + if (!queue) + mci->halted_bt_gpm = false; + } + + if (*(((u8 *)payload) + MCI_GPM_COEX_B_HALT_STATE) == + MCI_GPM_COEX_BT_GPM_HALT) { + + mci->halted_bt_gpm = !queue; + } + + break; + default: + break; + } +} + +void ar9003_mci_2g5g_switch(struct ath_hw *ah, bool force) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + + if (!mci->update_2g5g && !force) + return; + + if (mci->is_2g) { + ar9003_mci_send_2g5g_status(ah, true); + ar9003_mci_send_lna_transfer(ah, true); + udelay(5); + + REG_CLR_BIT(ah, AR_MCI_TX_CTRL, + AR_MCI_TX_CTRL_DISABLE_LNA_UPDATE); + REG_CLR_BIT(ah, AR_PHY_GLB_CONTROL, + AR_BTCOEX_CTRL_BT_OWN_SPDT_CTRL); + + if (!(mci->config & ATH_MCI_CONFIG_DISABLE_OSLA)) + ar9003_mci_osla_setup(ah, true); + + if (AR_SREV_9462(ah)) + REG_WRITE(ah, AR_SELFGEN_MASK, 0x02); + } else { + ar9003_mci_send_lna_take(ah, true); + udelay(5); + + REG_SET_BIT(ah, AR_MCI_TX_CTRL, + AR_MCI_TX_CTRL_DISABLE_LNA_UPDATE); + REG_SET_BIT(ah, AR_PHY_GLB_CONTROL, + AR_BTCOEX_CTRL_BT_OWN_SPDT_CTRL); + + ar9003_mci_osla_setup(ah, false); + ar9003_mci_send_2g5g_status(ah, true); + } +} + +bool ar9003_mci_send_message(struct ath_hw *ah, u8 header, u32 flag, + u32 *payload, u8 len, bool wait_done, + bool check_bt) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + bool msg_sent = false; + u32 regval; + u32 saved_mci_int_en; + int i; + + saved_mci_int_en = REG_READ(ah, AR_MCI_INTERRUPT_EN); + regval = REG_READ(ah, AR_BTCOEX_CTRL); + + if ((regval == 0xdeadbeef) || !(regval & AR_BTCOEX_CTRL_MCI_MODE_EN)) { + ath_dbg(common, MCI, + "MCI Not sending 0x%x. MCI is not enabled. full_sleep = %d\n", + header, (ah->power_mode == ATH9K_PM_FULL_SLEEP) ? 1 : 0); + ar9003_mci_queue_unsent_gpm(ah, header, payload, true); + return false; + } else if (check_bt && (mci->bt_state == MCI_BT_SLEEP)) { + ath_dbg(common, MCI, + "MCI Don't send message 0x%x. BT is in sleep state\n", + header); + ar9003_mci_queue_unsent_gpm(ah, header, payload, true); + return false; + } + + if (wait_done) + REG_WRITE(ah, AR_MCI_INTERRUPT_EN, 0); + + /* Need to clear SW_MSG_DONE raw bit before wait */ + + REG_WRITE(ah, AR_MCI_INTERRUPT_RAW, + (AR_MCI_INTERRUPT_SW_MSG_DONE | + AR_MCI_INTERRUPT_MSG_FAIL_MASK)); + + if (payload) { + for (i = 0; (i * 4) < len; i++) + REG_WRITE(ah, (AR_MCI_TX_PAYLOAD0 + i * 4), + *(payload + i)); + } + + REG_WRITE(ah, AR_MCI_COMMAND0, + (SM((flag & MCI_FLAG_DISABLE_TIMESTAMP), + AR_MCI_COMMAND0_DISABLE_TIMESTAMP) | + SM(len, AR_MCI_COMMAND0_LEN) | + SM(header, AR_MCI_COMMAND0_HEADER))); + + if (wait_done && + !(ar9003_mci_wait_for_interrupt(ah, AR_MCI_INTERRUPT_RAW, + AR_MCI_INTERRUPT_SW_MSG_DONE, 500))) + ar9003_mci_queue_unsent_gpm(ah, header, payload, true); + else { + ar9003_mci_queue_unsent_gpm(ah, header, payload, false); + msg_sent = true; + } + + if (wait_done) + REG_WRITE(ah, AR_MCI_INTERRUPT_EN, saved_mci_int_en); + + return msg_sent; +} +EXPORT_SYMBOL(ar9003_mci_send_message); + +void ar9003_mci_init_cal_req(struct ath_hw *ah, bool *is_reusable) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_hw_mci *mci_hw = &ah->btcoex_hw.mci; + u32 pld[4] = {0, 0, 0, 0}; + + if ((mci_hw->bt_state != MCI_BT_AWAKE) || + (mci_hw->config & ATH_MCI_CONFIG_DISABLE_MCI_CAL)) + return; + + MCI_GPM_SET_CAL_TYPE(pld, MCI_GPM_WLAN_CAL_REQ); + pld[MCI_GPM_WLAN_CAL_W_SEQUENCE] = mci_hw->wlan_cal_seq++; + + ar9003_mci_send_message(ah, MCI_GPM, 0, pld, 16, true, false); + + if (ar9003_mci_wait_for_gpm(ah, MCI_GPM_BT_CAL_GRANT, 0, 50000)) { + ath_dbg(common, MCI, "MCI BT_CAL_GRANT received\n"); + } else { + *is_reusable = false; + ath_dbg(common, MCI, "MCI BT_CAL_GRANT not received\n"); + } +} + +void ar9003_mci_init_cal_done(struct ath_hw *ah) +{ + struct ath9k_hw_mci *mci_hw = &ah->btcoex_hw.mci; + u32 pld[4] = {0, 0, 0, 0}; + + if ((mci_hw->bt_state != MCI_BT_AWAKE) || + (mci_hw->config & ATH_MCI_CONFIG_DISABLE_MCI_CAL)) + return; + + MCI_GPM_SET_CAL_TYPE(pld, MCI_GPM_WLAN_CAL_DONE); + pld[MCI_GPM_WLAN_CAL_W_SEQUENCE] = mci_hw->wlan_cal_done++; + ar9003_mci_send_message(ah, MCI_GPM, 0, pld, 16, true, false); +} + +int ar9003_mci_setup(struct ath_hw *ah, u32 gpm_addr, void *gpm_buf, + u16 len, u32 sched_addr) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + + mci->gpm_addr = gpm_addr; + mci->gpm_buf = gpm_buf; + mci->gpm_len = len; + mci->sched_addr = sched_addr; + + return ar9003_mci_reset(ah, true, true, true); +} +EXPORT_SYMBOL(ar9003_mci_setup); + +void ar9003_mci_cleanup(struct ath_hw *ah) +{ + /* Turn off MCI and Jupiter mode. */ + REG_WRITE(ah, AR_BTCOEX_CTRL, 0x00); + ar9003_mci_disable_interrupt(ah); +} +EXPORT_SYMBOL(ar9003_mci_cleanup); + +u32 ar9003_mci_state(struct ath_hw *ah, u32 state_type) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u32 value = 0, tsf; + u8 query_type; + + switch (state_type) { + case MCI_STATE_ENABLE: + if (mci->ready) { + value = REG_READ(ah, AR_BTCOEX_CTRL); + + if ((value == 0xdeadbeef) || (value == 0xffffffff)) + value = 0; + } + value &= AR_BTCOEX_CTRL_MCI_MODE_EN; + break; + case MCI_STATE_LAST_SCHD_MSG_OFFSET: + value = MS(REG_READ(ah, AR_MCI_RX_STATUS), + AR_MCI_RX_LAST_SCHD_MSG_INDEX); + /* Make it in bytes */ + value <<= 4; + break; + case MCI_STATE_REMOTE_SLEEP: + value = MS(REG_READ(ah, AR_MCI_RX_STATUS), + AR_MCI_RX_REMOTE_SLEEP) ? + MCI_BT_SLEEP : MCI_BT_AWAKE; + break; + case MCI_STATE_SET_BT_AWAKE: + mci->bt_state = MCI_BT_AWAKE; + ar9003_mci_send_coex_version_query(ah, true); + ar9003_mci_send_coex_wlan_channels(ah, true); + + if (mci->unhalt_bt_gpm) + ar9003_mci_send_coex_halt_bt_gpm(ah, false, true); + + ar9003_mci_2g5g_switch(ah, false); + break; + case MCI_STATE_RESET_REQ_WAKE: + ar9003_mci_reset_req_wakeup(ah); + mci->update_2g5g = true; + + if (mci->config & ATH_MCI_CONFIG_MCI_OBS_MASK) { + /* Check if we still have control of the GPIOs */ + if ((REG_READ(ah, AR_GLB_GPIO_CONTROL) & + ATH_MCI_CONFIG_MCI_OBS_GPIO) != + ATH_MCI_CONFIG_MCI_OBS_GPIO) { + ar9003_mci_observation_set_up(ah); + } + } + break; + case MCI_STATE_SEND_WLAN_COEX_VERSION: + ar9003_mci_send_coex_version_response(ah, true); + break; + case MCI_STATE_SEND_VERSION_QUERY: + ar9003_mci_send_coex_version_query(ah, true); + break; + case MCI_STATE_SEND_STATUS_QUERY: + query_type = MCI_GPM_COEX_QUERY_BT_TOPOLOGY; + ar9003_mci_send_coex_bt_status_query(ah, true, query_type); + break; + case MCI_STATE_RECOVER_RX: + tsf = ath9k_hw_gettsf32(ah); + if ((tsf - mci->last_recovery) <= MCI_RECOVERY_DUR_TSF) { + ath_dbg(ath9k_hw_common(ah), MCI, + "(MCI) ignore Rx recovery\n"); + break; + } + ath_dbg(ath9k_hw_common(ah), MCI, "(MCI) RECOVER RX\n"); + mci->last_recovery = tsf; + ar9003_mci_prep_interface(ah); + mci->query_bt = true; + mci->need_flush_btinfo = true; + ar9003_mci_send_coex_wlan_channels(ah, true); + ar9003_mci_2g5g_switch(ah, false); + break; + case MCI_STATE_NEED_FTP_STOMP: + value = !(mci->config & ATH_MCI_CONFIG_DISABLE_FTP_STOMP); + break; + case MCI_STATE_NEED_FLUSH_BT_INFO: + value = (!mci->unhalt_bt_gpm && mci->need_flush_btinfo) ? 1 : 0; + mci->need_flush_btinfo = false; + break; + default: + break; + } + + return value; +} +EXPORT_SYMBOL(ar9003_mci_state); + +void ar9003_mci_bt_gain_ctrl(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + + ath_dbg(common, MCI, "Give LNA and SPDT control to BT\n"); + + ar9003_mci_send_lna_take(ah, true); + udelay(50); + + REG_SET_BIT(ah, AR_PHY_GLB_CONTROL, AR_BTCOEX_CTRL_BT_OWN_SPDT_CTRL); + mci->is_2g = false; + mci->update_2g5g = true; + ar9003_mci_send_2g5g_status(ah, true); + + /* Force another 2g5g update at next scanning */ + mci->update_2g5g = true; +} + +void ar9003_mci_set_power_awake(struct ath_hw *ah) +{ + u32 btcoex_ctrl2, diag_sw; + int i; + u8 lna_ctrl, bt_sleep; + + for (i = 0; i < AH_WAIT_TIMEOUT; i++) { + btcoex_ctrl2 = REG_READ(ah, AR_BTCOEX_CTRL2); + if (btcoex_ctrl2 != 0xdeadbeef) + break; + udelay(AH_TIME_QUANTUM); + } + REG_WRITE(ah, AR_BTCOEX_CTRL2, (btcoex_ctrl2 | BIT(23))); + + for (i = 0; i < AH_WAIT_TIMEOUT; i++) { + diag_sw = REG_READ(ah, AR_DIAG_SW); + if (diag_sw != 0xdeadbeef) + break; + udelay(AH_TIME_QUANTUM); + } + REG_WRITE(ah, AR_DIAG_SW, (diag_sw | BIT(27) | BIT(19) | BIT(18))); + lna_ctrl = REG_READ(ah, AR_OBS_BUS_CTRL) & 0x3; + bt_sleep = MS(REG_READ(ah, AR_MCI_RX_STATUS), AR_MCI_RX_REMOTE_SLEEP); + + REG_WRITE(ah, AR_BTCOEX_CTRL2, btcoex_ctrl2); + REG_WRITE(ah, AR_DIAG_SW, diag_sw); + + if (bt_sleep && (lna_ctrl == 2)) { + REG_SET_BIT(ah, AR_BTCOEX_RC, 0x1); + REG_CLR_BIT(ah, AR_BTCOEX_RC, 0x1); + udelay(50); + } +} + +void ar9003_mci_check_gpm_offset(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u32 offset; + + /* + * This should only be called before "MAC Warm Reset" or "MCI Reset Rx". + */ + offset = MS(REG_READ(ah, AR_MCI_GPM_1), AR_MCI_GPM_WRITE_PTR); + if (mci->gpm_idx == offset) + return; + ath_dbg(common, MCI, "GPM cached write pointer mismatch %d %d\n", + mci->gpm_idx, offset); + mci->query_bt = true; + mci->need_flush_btinfo = true; + mci->gpm_idx = 0; +} + +u32 ar9003_mci_get_next_gpm_offset(struct ath_hw *ah, bool first, u32 *more) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + u32 offset, more_gpm = 0, gpm_ptr; + + if (first) { + gpm_ptr = MS(REG_READ(ah, AR_MCI_GPM_1), AR_MCI_GPM_WRITE_PTR); + + if (gpm_ptr >= mci->gpm_len) + gpm_ptr = 0; + + mci->gpm_idx = gpm_ptr; + return gpm_ptr; + } + + /* + * This could be useful to avoid new GPM message interrupt which + * may lead to spurious interrupt after power sleep, or multiple + * entry of ath_mci_intr(). + * Adding empty GPM check by returning HAL_MCI_GPM_INVALID can + * alleviate this effect, but clearing GPM RX interrupt bit is + * safe, because whether this is called from hw or driver code + * there must be an interrupt bit set/triggered initially + */ + REG_WRITE(ah, AR_MCI_INTERRUPT_RX_MSG_RAW, + AR_MCI_INTERRUPT_RX_MSG_GPM); + + gpm_ptr = MS(REG_READ(ah, AR_MCI_GPM_1), AR_MCI_GPM_WRITE_PTR); + offset = gpm_ptr; + + if (!offset) + offset = mci->gpm_len - 1; + else if (offset >= mci->gpm_len) { + if (offset != 0xFFFF) + offset = 0; + } else { + offset--; + } + + if ((offset == 0xFFFF) || (gpm_ptr == mci->gpm_idx)) { + offset = MCI_GPM_INVALID; + more_gpm = MCI_GPM_NOMORE; + goto out; + } + for (;;) { + u32 temp_index; + + /* skip reserved GPM if any */ + + if (offset != mci->gpm_idx) + more_gpm = MCI_GPM_MORE; + else + more_gpm = MCI_GPM_NOMORE; + + temp_index = mci->gpm_idx; + + if (temp_index >= mci->gpm_len) + temp_index = 0; + + mci->gpm_idx++; + + if (mci->gpm_idx >= mci->gpm_len) + mci->gpm_idx = 0; + + if (ar9003_mci_is_gpm_valid(ah, temp_index)) { + offset = temp_index; + break; + } + + if (more_gpm == MCI_GPM_NOMORE) { + offset = MCI_GPM_INVALID; + break; + } + } + + if (offset != MCI_GPM_INVALID) + offset <<= 4; +out: + if (more) + *more = more_gpm; + + return offset; +} +EXPORT_SYMBOL(ar9003_mci_get_next_gpm_offset); + +void ar9003_mci_set_bt_version(struct ath_hw *ah, u8 major, u8 minor) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + + mci->bt_ver_major = major; + mci->bt_ver_minor = minor; + mci->bt_version_known = true; + ath_dbg(ath9k_hw_common(ah), MCI, "MCI BT version set: %d.%d\n", + mci->bt_ver_major, mci->bt_ver_minor); +} +EXPORT_SYMBOL(ar9003_mci_set_bt_version); + +void ar9003_mci_send_wlan_channels(struct ath_hw *ah) +{ + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + + mci->wlan_channels_update = true; + ar9003_mci_send_coex_wlan_channels(ah, true); +} +EXPORT_SYMBOL(ar9003_mci_send_wlan_channels); + +u16 ar9003_mci_get_max_txpower(struct ath_hw *ah, u8 ctlmode) +{ + if (!ah->btcoex_hw.mci.concur_tx) + goto out; + + if (ctlmode == CTL_2GHT20) + return ATH_BTCOEX_HT20_MAX_TXPOWER; + else if (ctlmode == CTL_2GHT40) + return ATH_BTCOEX_HT40_MAX_TXPOWER; + +out: + return -1; +} diff --git a/drivers/net/wireless/ath/ath9k/ar9003_mci.h b/drivers/net/wireless/ath/ath9k/ar9003_mci.h new file mode 100644 index 0000000..2d28278 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9003_mci.h @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef AR9003_MCI_H +#define AR9003_MCI_H + +#define MCI_FLAG_DISABLE_TIMESTAMP 0x00000001 /* Disable time stamp */ +#define MCI_RECOVERY_DUR_TSF (100 * 1000) /* 100 ms */ + +/* Default remote BT device MCI COEX version */ +#define MCI_GPM_COEX_MAJOR_VERSION_DEFAULT 3 +#define MCI_GPM_COEX_MINOR_VERSION_DEFAULT 0 + +/* Local WLAN MCI COEX version */ +#define MCI_GPM_COEX_MAJOR_VERSION_WLAN 3 +#define MCI_GPM_COEX_MINOR_VERSION_WLAN 0 + +enum mci_gpm_coex_query_type { + MCI_GPM_COEX_QUERY_BT_ALL_INFO = BIT(0), + MCI_GPM_COEX_QUERY_BT_TOPOLOGY = BIT(1), + MCI_GPM_COEX_QUERY_BT_DEBUG = BIT(2), +}; + +enum mci_gpm_coex_halt_bt_gpm { + MCI_GPM_COEX_BT_GPM_UNHALT, + MCI_GPM_COEX_BT_GPM_HALT +}; + +enum mci_gpm_coex_bt_update_flags_op { + MCI_GPM_COEX_BT_FLAGS_READ, + MCI_GPM_COEX_BT_FLAGS_SET, + MCI_GPM_COEX_BT_FLAGS_CLEAR +}; + +#define MCI_NUM_BT_CHANNELS 79 + +#define MCI_BT_MCI_FLAGS_UPDATE_CORR 0x00000002 +#define MCI_BT_MCI_FLAGS_UPDATE_HDR 0x00000004 +#define MCI_BT_MCI_FLAGS_UPDATE_PLD 0x00000008 +#define MCI_BT_MCI_FLAGS_LNA_CTRL 0x00000010 +#define MCI_BT_MCI_FLAGS_DEBUG 0x00000020 +#define MCI_BT_MCI_FLAGS_SCHED_MSG 0x00000040 +#define MCI_BT_MCI_FLAGS_CONT_MSG 0x00000080 +#define MCI_BT_MCI_FLAGS_COEX_GPM 0x00000100 +#define MCI_BT_MCI_FLAGS_CPU_INT_MSG 0x00000200 +#define MCI_BT_MCI_FLAGS_MCI_MODE 0x00000400 +#define MCI_BT_MCI_FLAGS_AR9462_MODE 0x00001000 +#define MCI_BT_MCI_FLAGS_OTHER 0x00010000 + +#define MCI_DEFAULT_BT_MCI_FLAGS 0x00011dde + +#define MCI_TOGGLE_BT_MCI_FLAGS (MCI_BT_MCI_FLAGS_UPDATE_CORR | \ + MCI_BT_MCI_FLAGS_UPDATE_HDR | \ + MCI_BT_MCI_FLAGS_UPDATE_PLD | \ + MCI_BT_MCI_FLAGS_MCI_MODE) + +#define MCI_2G_FLAGS_CLEAR_MASK 0x00000000 +#define MCI_2G_FLAGS_SET_MASK MCI_TOGGLE_BT_MCI_FLAGS +#define MCI_2G_FLAGS MCI_DEFAULT_BT_MCI_FLAGS + +#define MCI_5G_FLAGS_CLEAR_MASK MCI_TOGGLE_BT_MCI_FLAGS +#define MCI_5G_FLAGS_SET_MASK 0x00000000 +#define MCI_5G_FLAGS (MCI_DEFAULT_BT_MCI_FLAGS & \ + ~MCI_TOGGLE_BT_MCI_FLAGS) + +/* + * Default value for AR9462 is 0x00002201 + */ +#define ATH_MCI_CONFIG_CONCUR_TX 0x00000003 +#define ATH_MCI_CONFIG_MCI_OBS_MCI 0x00000004 +#define ATH_MCI_CONFIG_MCI_OBS_TXRX 0x00000008 +#define ATH_MCI_CONFIG_MCI_OBS_BT 0x00000010 +#define ATH_MCI_CONFIG_DISABLE_MCI_CAL 0x00000020 +#define ATH_MCI_CONFIG_DISABLE_OSLA 0x00000040 +#define ATH_MCI_CONFIG_DISABLE_FTP_STOMP 0x00000080 +#define ATH_MCI_CONFIG_AGGR_THRESH 0x00000700 +#define ATH_MCI_CONFIG_AGGR_THRESH_S 8 +#define ATH_MCI_CONFIG_DISABLE_AGGR_THRESH 0x00000800 +#define ATH_MCI_CONFIG_CLK_DIV 0x00003000 +#define ATH_MCI_CONFIG_CLK_DIV_S 12 +#define ATH_MCI_CONFIG_DISABLE_TUNING 0x00004000 +#define ATH_MCI_CONFIG_MCI_WEIGHT_DBG 0x40000000 +#define ATH_MCI_CONFIG_DISABLE_MCI 0x80000000 + +#define ATH_MCI_CONFIG_MCI_OBS_MASK (ATH_MCI_CONFIG_MCI_OBS_MCI | \ + ATH_MCI_CONFIG_MCI_OBS_TXRX | \ + ATH_MCI_CONFIG_MCI_OBS_BT) +#define ATH_MCI_CONFIG_MCI_OBS_GPIO 0x0000002F + +enum mci_message_header { /* length of payload */ + MCI_LNA_CTRL = 0x10, /* len = 0 */ + MCI_CONT_NACK = 0x20, /* len = 0 */ + MCI_CONT_INFO = 0x30, /* len = 4 */ + MCI_CONT_RST = 0x40, /* len = 0 */ + MCI_SCHD_INFO = 0x50, /* len = 16 */ + MCI_CPU_INT = 0x60, /* len = 4 */ + MCI_SYS_WAKING = 0x70, /* len = 0 */ + MCI_GPM = 0x80, /* len = 16 */ + MCI_LNA_INFO = 0x90, /* len = 1 */ + MCI_LNA_STATE = 0x94, + MCI_LNA_TAKE = 0x98, + MCI_LNA_TRANS = 0x9c, + MCI_SYS_SLEEPING = 0xa0, /* len = 0 */ + MCI_REQ_WAKE = 0xc0, /* len = 0 */ + MCI_DEBUG_16 = 0xfe, /* len = 2 */ + MCI_REMOTE_RESET = 0xff /* len = 16 */ +}; + +enum ath_mci_gpm_coex_profile_type { + MCI_GPM_COEX_PROFILE_UNKNOWN, + MCI_GPM_COEX_PROFILE_RFCOMM, + MCI_GPM_COEX_PROFILE_A2DP, + MCI_GPM_COEX_PROFILE_HID, + MCI_GPM_COEX_PROFILE_BNEP, + MCI_GPM_COEX_PROFILE_VOICE, + MCI_GPM_COEX_PROFILE_A2DPVO, + MCI_GPM_COEX_PROFILE_MAX +}; + +/* MCI GPM/Coex opcode/type definitions */ +enum { + MCI_GPM_COEX_W_GPM_PAYLOAD = 1, + MCI_GPM_COEX_B_GPM_TYPE = 4, + MCI_GPM_COEX_B_GPM_OPCODE = 5, + /* MCI_GPM_WLAN_CAL_REQ, MCI_GPM_WLAN_CAL_DONE */ + MCI_GPM_WLAN_CAL_W_SEQUENCE = 2, + + /* MCI_GPM_COEX_VERSION_QUERY */ + /* MCI_GPM_COEX_VERSION_RESPONSE */ + MCI_GPM_COEX_B_MAJOR_VERSION = 6, + MCI_GPM_COEX_B_MINOR_VERSION = 7, + /* MCI_GPM_COEX_STATUS_QUERY */ + MCI_GPM_COEX_B_BT_BITMAP = 6, + MCI_GPM_COEX_B_WLAN_BITMAP = 7, + /* MCI_GPM_COEX_HALT_BT_GPM */ + MCI_GPM_COEX_B_HALT_STATE = 6, + /* MCI_GPM_COEX_WLAN_CHANNELS */ + MCI_GPM_COEX_B_CHANNEL_MAP = 6, + /* MCI_GPM_COEX_BT_PROFILE_INFO */ + MCI_GPM_COEX_B_PROFILE_TYPE = 6, + MCI_GPM_COEX_B_PROFILE_LINKID = 7, + MCI_GPM_COEX_B_PROFILE_STATE = 8, + MCI_GPM_COEX_B_PROFILE_ROLE = 9, + MCI_GPM_COEX_B_PROFILE_RATE = 10, + MCI_GPM_COEX_B_PROFILE_VOTYPE = 11, + MCI_GPM_COEX_H_PROFILE_T = 12, + MCI_GPM_COEX_B_PROFILE_W = 14, + MCI_GPM_COEX_B_PROFILE_A = 15, + /* MCI_GPM_COEX_BT_STATUS_UPDATE */ + MCI_GPM_COEX_B_STATUS_TYPE = 6, + MCI_GPM_COEX_B_STATUS_LINKID = 7, + MCI_GPM_COEX_B_STATUS_STATE = 8, + /* MCI_GPM_COEX_BT_UPDATE_FLAGS */ + MCI_GPM_COEX_W_BT_FLAGS = 6, + MCI_GPM_COEX_B_BT_FLAGS_OP = 10 +}; + +enum mci_gpm_subtype { + MCI_GPM_BT_CAL_REQ = 0, + MCI_GPM_BT_CAL_GRANT = 1, + MCI_GPM_BT_CAL_DONE = 2, + MCI_GPM_WLAN_CAL_REQ = 3, + MCI_GPM_WLAN_CAL_GRANT = 4, + MCI_GPM_WLAN_CAL_DONE = 5, + MCI_GPM_COEX_AGENT = 0x0c, + MCI_GPM_RSVD_PATTERN = 0xfe, + MCI_GPM_RSVD_PATTERN32 = 0xfefefefe, + MCI_GPM_BT_DEBUG = 0xff +}; + +enum mci_bt_state { + MCI_BT_SLEEP, + MCI_BT_AWAKE, + MCI_BT_CAL_START, + MCI_BT_CAL +}; + +/* Type of state query */ +enum mci_state_type { + MCI_STATE_ENABLE, + MCI_STATE_SET_BT_AWAKE, + MCI_STATE_LAST_SCHD_MSG_OFFSET, + MCI_STATE_REMOTE_SLEEP, + MCI_STATE_RESET_REQ_WAKE, + MCI_STATE_SEND_WLAN_COEX_VERSION, + MCI_STATE_SEND_VERSION_QUERY, + MCI_STATE_SEND_STATUS_QUERY, + MCI_STATE_RECOVER_RX, + MCI_STATE_NEED_FTP_STOMP, + MCI_STATE_DEBUG, + MCI_STATE_NEED_FLUSH_BT_INFO, + MCI_STATE_MAX +}; + +enum mci_gpm_coex_opcode { + MCI_GPM_COEX_VERSION_QUERY, + MCI_GPM_COEX_VERSION_RESPONSE, + MCI_GPM_COEX_STATUS_QUERY, + MCI_GPM_COEX_HALT_BT_GPM, + MCI_GPM_COEX_WLAN_CHANNELS, + MCI_GPM_COEX_BT_PROFILE_INFO, + MCI_GPM_COEX_BT_STATUS_UPDATE, + MCI_GPM_COEX_BT_UPDATE_FLAGS, + MCI_GPM_COEX_NOOP, +}; + +#define MCI_GPM_NOMORE 0 +#define MCI_GPM_MORE 1 +#define MCI_GPM_INVALID 0xffffffff + +#define MCI_GPM_RECYCLE(_p_gpm) do { \ + *(((u32 *)_p_gpm) + MCI_GPM_COEX_W_GPM_PAYLOAD) = \ + MCI_GPM_RSVD_PATTERN32; \ +} while (0) + +#define MCI_GPM_TYPE(_p_gpm) \ + (*(((u8 *)(_p_gpm)) + MCI_GPM_COEX_B_GPM_TYPE) & 0xff) + +#define MCI_GPM_OPCODE(_p_gpm) \ + (*(((u8 *)(_p_gpm)) + MCI_GPM_COEX_B_GPM_OPCODE) & 0xff) + +#define MCI_GPM_SET_CAL_TYPE(_p_gpm, _cal_type) do { \ + *(((u8 *)(_p_gpm)) + MCI_GPM_COEX_B_GPM_TYPE) = (_cal_type) & 0xff;\ +} while (0) + +#define MCI_GPM_SET_TYPE_OPCODE(_p_gpm, _type, _opcode) do { \ + *(((u8 *)(_p_gpm)) + MCI_GPM_COEX_B_GPM_TYPE) = (_type) & 0xff; \ + *(((u8 *)(_p_gpm)) + MCI_GPM_COEX_B_GPM_OPCODE) = (_opcode) & 0xff;\ +} while (0) + +#define MCI_GPM_IS_CAL_TYPE(_type) ((_type) <= MCI_GPM_WLAN_CAL_DONE) + +/* + * Functions that are available to the MCI driver core. + */ +bool ar9003_mci_send_message(struct ath_hw *ah, u8 header, u32 flag, + u32 *payload, u8 len, bool wait_done, + bool check_bt); +u32 ar9003_mci_state(struct ath_hw *ah, u32 state_type); +int ar9003_mci_setup(struct ath_hw *ah, u32 gpm_addr, void *gpm_buf, + u16 len, u32 sched_addr); +void ar9003_mci_cleanup(struct ath_hw *ah); +void ar9003_mci_get_interrupt(struct ath_hw *ah, u32 *raw_intr, + u32 *rx_msg_intr); +u32 ar9003_mci_get_next_gpm_offset(struct ath_hw *ah, bool first, u32 *more); +void ar9003_mci_set_bt_version(struct ath_hw *ah, u8 major, u8 minor); +void ar9003_mci_send_wlan_channels(struct ath_hw *ah); +/* + * These functions are used by ath9k_hw. + */ + +#ifdef CPTCFG_ATH9K_BTCOEX_SUPPORT + +void ar9003_mci_stop_bt(struct ath_hw *ah, bool save_fullsleep); +void ar9003_mci_init_cal_req(struct ath_hw *ah, bool *is_reusable); +void ar9003_mci_init_cal_done(struct ath_hw *ah); +void ar9003_mci_set_full_sleep(struct ath_hw *ah); +void ar9003_mci_2g5g_switch(struct ath_hw *ah, bool force); +void ar9003_mci_check_bt(struct ath_hw *ah); +bool ar9003_mci_start_reset(struct ath_hw *ah, struct ath9k_channel *chan); +int ar9003_mci_end_reset(struct ath_hw *ah, struct ath9k_channel *chan, + struct ath9k_hw_cal_data *caldata); +int ar9003_mci_reset(struct ath_hw *ah, bool en_int, bool is_2g, + bool is_full_sleep); +void ar9003_mci_get_isr(struct ath_hw *ah, enum ath9k_int *masked); +void ar9003_mci_bt_gain_ctrl(struct ath_hw *ah); +void ar9003_mci_set_power_awake(struct ath_hw *ah); +void ar9003_mci_check_gpm_offset(struct ath_hw *ah); +u16 ar9003_mci_get_max_txpower(struct ath_hw *ah, u8 ctlmode); + +#else + +static inline void ar9003_mci_stop_bt(struct ath_hw *ah, bool save_fullsleep) +{ +} +static inline void ar9003_mci_init_cal_req(struct ath_hw *ah, bool *is_reusable) +{ +} +static inline void ar9003_mci_init_cal_done(struct ath_hw *ah) +{ +} +static inline void ar9003_mci_set_full_sleep(struct ath_hw *ah) +{ +} +static inline void ar9003_mci_2g5g_switch(struct ath_hw *ah, bool wait_done) +{ +} +static inline void ar9003_mci_check_bt(struct ath_hw *ah) +{ +} +static inline bool ar9003_mci_start_reset(struct ath_hw *ah, struct ath9k_channel *chan) +{ + return false; +} +static inline int ar9003_mci_end_reset(struct ath_hw *ah, struct ath9k_channel *chan, + struct ath9k_hw_cal_data *caldata) +{ + return 0; +} +static inline void ar9003_mci_reset(struct ath_hw *ah, bool en_int, bool is_2g, + bool is_full_sleep) +{ +} +static inline void ar9003_mci_get_isr(struct ath_hw *ah, enum ath9k_int *masked) +{ +} +static inline void ar9003_mci_bt_gain_ctrl(struct ath_hw *ah) +{ +} +static inline void ar9003_mci_set_power_awake(struct ath_hw *ah) +{ +} +static inline void ar9003_mci_check_gpm_offset(struct ath_hw *ah) +{ +} +static inline u16 ar9003_mci_get_max_txpower(struct ath_hw *ah, u8 ctlmode) +{ + return -1; +} +#endif /* CPTCFG_ATH9K_BTCOEX_SUPPORT */ + +#endif diff --git a/drivers/net/wireless/ath/ath9k/ar9003_paprd.c b/drivers/net/wireless/ath/ath9k/ar9003_paprd.c new file mode 100644 index 0000000..09c1f9d --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9003_paprd.c @@ -0,0 +1,1010 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "hw.h" +#include "ar9003_phy.h" + +void ar9003_paprd_enable(struct ath_hw *ah, bool val) +{ + struct ath9k_channel *chan = ah->curchan; + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + + /* + * 3 bits for modalHeader5G.papdRateMaskHt20 + * is used for sub-band disabling of PAPRD. + * 5G band is divided into 3 sub-bands -- upper, + * middle, lower. + * if bit 30 of modalHeader5G.papdRateMaskHt20 is set + * -- disable PAPRD for upper band 5GHz + * if bit 29 of modalHeader5G.papdRateMaskHt20 is set + * -- disable PAPRD for middle band 5GHz + * if bit 28 of modalHeader5G.papdRateMaskHt20 is set + * -- disable PAPRD for lower band 5GHz + */ + + if (IS_CHAN_5GHZ(chan)) { + if (chan->channel >= UPPER_5G_SUB_BAND_START) { + if (le32_to_cpu(eep->modalHeader5G.papdRateMaskHt20) + & BIT(30)) + val = false; + } else if (chan->channel >= MID_5G_SUB_BAND_START) { + if (le32_to_cpu(eep->modalHeader5G.papdRateMaskHt20) + & BIT(29)) + val = false; + } else { + if (le32_to_cpu(eep->modalHeader5G.papdRateMaskHt20) + & BIT(28)) + val = false; + } + } + + if (val) { + ah->paprd_table_write_done = true; + ath9k_hw_apply_txpower(ah, chan, false); + } + + REG_RMW_FIELD(ah, AR_PHY_PAPRD_CTRL0_B0, + AR_PHY_PAPRD_CTRL0_PAPRD_ENABLE, !!val); + if (ah->caps.tx_chainmask & BIT(1)) + REG_RMW_FIELD(ah, AR_PHY_PAPRD_CTRL0_B1, + AR_PHY_PAPRD_CTRL0_PAPRD_ENABLE, !!val); + if (ah->caps.tx_chainmask & BIT(2)) + REG_RMW_FIELD(ah, AR_PHY_PAPRD_CTRL0_B2, + AR_PHY_PAPRD_CTRL0_PAPRD_ENABLE, !!val); +} +EXPORT_SYMBOL(ar9003_paprd_enable); + +static int ar9003_get_training_power_2g(struct ath_hw *ah) +{ + struct ath9k_channel *chan = ah->curchan; + unsigned int power, scale, delta; + + scale = ar9003_get_paprd_scale_factor(ah, chan); + + if (AR_SREV_9330(ah) || AR_SREV_9340(ah) || + AR_SREV_9462(ah) || AR_SREV_9565(ah)) { + power = ah->paprd_target_power + 2; + } else if (AR_SREV_9485(ah)) { + power = 25; + } else { + power = REG_READ_FIELD(ah, AR_PHY_POWERTX_RATE5, + AR_PHY_POWERTX_RATE5_POWERTXHT20_0); + + delta = abs((int) ah->paprd_target_power - (int) power); + if (delta > scale) + return -1; + + if (delta < 4) + power -= 4 - delta; + } + + return power; +} + +static int ar9003_get_training_power_5g(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_channel *chan = ah->curchan; + unsigned int power, scale, delta; + + scale = ar9003_get_paprd_scale_factor(ah, chan); + + if (IS_CHAN_HT40(chan)) + power = REG_READ_FIELD(ah, AR_PHY_POWERTX_RATE8, + AR_PHY_POWERTX_RATE8_POWERTXHT40_5); + else + power = REG_READ_FIELD(ah, AR_PHY_POWERTX_RATE6, + AR_PHY_POWERTX_RATE6_POWERTXHT20_5); + + power += scale; + delta = abs((int) ah->paprd_target_power - (int) power); + if (delta > scale) + return -1; + + switch (get_streams(ah->txchainmask)) { + case 1: + delta = 6; + break; + case 2: + delta = 4; + break; + case 3: + delta = 2; + break; + default: + delta = 0; + ath_dbg(common, CALIBRATE, "Invalid tx-chainmask: %u\n", + ah->txchainmask); + } + + power += delta; + return power; +} + +static int ar9003_paprd_setup_single_table(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + static const u32 ctrl0[3] = { + AR_PHY_PAPRD_CTRL0_B0, + AR_PHY_PAPRD_CTRL0_B1, + AR_PHY_PAPRD_CTRL0_B2 + }; + static const u32 ctrl1[3] = { + AR_PHY_PAPRD_CTRL1_B0, + AR_PHY_PAPRD_CTRL1_B1, + AR_PHY_PAPRD_CTRL1_B2 + }; + int training_power; + int i, val; + u32 am2pm_mask = ah->paprd_ratemask; + + if (IS_CHAN_2GHZ(ah->curchan)) + training_power = ar9003_get_training_power_2g(ah); + else + training_power = ar9003_get_training_power_5g(ah); + + ath_dbg(common, CALIBRATE, "Training power: %d, Target power: %d\n", + training_power, ah->paprd_target_power); + + if (training_power < 0) { + ath_dbg(common, CALIBRATE, + "PAPRD target power delta out of range\n"); + return -ERANGE; + } + ah->paprd_training_power = training_power; + + if (AR_SREV_9330(ah)) + am2pm_mask = 0; + + REG_RMW_FIELD(ah, AR_PHY_PAPRD_AM2AM, AR_PHY_PAPRD_AM2AM_MASK, + ah->paprd_ratemask); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_AM2PM, AR_PHY_PAPRD_AM2PM_MASK, + am2pm_mask); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_HT40, AR_PHY_PAPRD_HT40_MASK, + ah->paprd_ratemask_ht40); + + ath_dbg(common, CALIBRATE, "PAPRD HT20 mask: 0x%x, HT40 mask: 0x%x\n", + ah->paprd_ratemask, ah->paprd_ratemask_ht40); + + for (i = 0; i < ah->caps.max_txchains; i++) { + REG_RMW_FIELD(ah, ctrl0[i], + AR_PHY_PAPRD_CTRL0_USE_SINGLE_TABLE_MASK, 1); + REG_RMW_FIELD(ah, ctrl1[i], + AR_PHY_PAPRD_CTRL1_ADAPTIVE_AM2PM_ENABLE, 1); + REG_RMW_FIELD(ah, ctrl1[i], + AR_PHY_PAPRD_CTRL1_ADAPTIVE_AM2AM_ENABLE, 1); + REG_RMW_FIELD(ah, ctrl1[i], + AR_PHY_PAPRD_CTRL1_ADAPTIVE_SCALING_ENA, 0); + REG_RMW_FIELD(ah, ctrl1[i], + AR_PHY_PAPRD_CTRL1_PA_GAIN_SCALE_FACT_MASK, 181); + REG_RMW_FIELD(ah, ctrl1[i], + AR_PHY_PAPRD_CTRL1_PAPRD_MAG_SCALE_FACT, 361); + REG_RMW_FIELD(ah, ctrl1[i], + AR_PHY_PAPRD_CTRL1_ADAPTIVE_SCALING_ENA, 0); + REG_RMW_FIELD(ah, ctrl0[i], + AR_PHY_PAPRD_CTRL0_PAPRD_MAG_THRSH, 3); + } + + ar9003_paprd_enable(ah, false); + + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL1, + AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_LB_SKIP, 0x30); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL1, + AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_LB_ENABLE, 1); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL1, + AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_TX_GAIN_FORCE, 1); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL1, + AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_RX_BB_GAIN_FORCE, 0); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL1, + AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_IQCORR_ENABLE, 0); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL1, + AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_AGC2_SETTLING, 28); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL1, + AR_PHY_PAPRD_TRAINER_CNTL1_CF_CF_PAPRD_TRAIN_ENABLE, 1); + + if (AR_SREV_9485(ah)) { + val = 148; + } else { + if (IS_CHAN_2GHZ(ah->curchan)) { + if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) + val = 145; + else + val = 147; + } else { + val = 137; + } + } + + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL2, + AR_PHY_PAPRD_TRAINER_CNTL2_CF_PAPRD_INIT_RX_BB_GAIN, val); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL3, + AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_FINE_CORR_LEN, 4); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL3, + AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_COARSE_CORR_LEN, 4); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL3, + AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_NUM_CORR_STAGES, 7); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL3, + AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_MIN_LOOPBACK_DEL, 1); + + if (AR_SREV_9485(ah) || + AR_SREV_9462(ah) || + AR_SREV_9565(ah) || + AR_SREV_9550(ah) || + AR_SREV_9330(ah) || + AR_SREV_9340(ah)) + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL3, + AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_QUICK_DROP, -3); + else + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL3, + AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_QUICK_DROP, -6); + + val = -10; + + if (IS_CHAN_2GHZ(ah->curchan) && !AR_SREV_9462(ah) && !AR_SREV_9565(ah)) + val = -15; + + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL3, + AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_ADC_DESIRED_SIZE, + val); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL3, + AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_BBTXMIX_DISABLE, 1); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL4, + AR_PHY_PAPRD_TRAINER_CNTL4_CF_PAPRD_SAFETY_DELTA, 0); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL4, + AR_PHY_PAPRD_TRAINER_CNTL4_CF_PAPRD_MIN_CORR, 400); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL4, + AR_PHY_PAPRD_TRAINER_CNTL4_CF_PAPRD_NUM_TRAIN_SAMPLES, + 100); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_PRE_POST_SCALE_0_B0, + AR_PHY_PAPRD_PRE_POST_SCALING, 261376); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_PRE_POST_SCALE_1_B0, + AR_PHY_PAPRD_PRE_POST_SCALING, 248079); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_PRE_POST_SCALE_2_B0, + AR_PHY_PAPRD_PRE_POST_SCALING, 233759); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_PRE_POST_SCALE_3_B0, + AR_PHY_PAPRD_PRE_POST_SCALING, 220464); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_PRE_POST_SCALE_4_B0, + AR_PHY_PAPRD_PRE_POST_SCALING, 208194); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_PRE_POST_SCALE_5_B0, + AR_PHY_PAPRD_PRE_POST_SCALING, 196949); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_PRE_POST_SCALE_6_B0, + AR_PHY_PAPRD_PRE_POST_SCALING, 185706); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_PRE_POST_SCALE_7_B0, + AR_PHY_PAPRD_PRE_POST_SCALING, 175487); + return 0; +} + +static void ar9003_paprd_get_gain_table(struct ath_hw *ah) +{ + u32 *entry = ah->paprd_gain_table_entries; + u8 *index = ah->paprd_gain_table_index; + u32 reg = AR_PHY_TXGAIN_TABLE; + int i; + + for (i = 0; i < PAPRD_GAIN_TABLE_ENTRIES; i++) { + entry[i] = REG_READ(ah, reg); + index[i] = (entry[i] >> 24) & 0xff; + reg += 4; + } +} + +static unsigned int ar9003_get_desired_gain(struct ath_hw *ah, int chain, + int target_power) +{ + int olpc_gain_delta = 0, cl_gain_mod; + int alpha_therm, alpha_volt; + int therm_cal_value, volt_cal_value; + int therm_value, volt_value; + int thermal_gain_corr, voltage_gain_corr; + int desired_scale, desired_gain = 0; + u32 reg_olpc = 0, reg_cl_gain = 0; + + REG_CLR_BIT(ah, AR_PHY_PAPRD_TRAINER_STAT1, + AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_TRAIN_DONE); + desired_scale = REG_READ_FIELD(ah, AR_PHY_TPC_12, + AR_PHY_TPC_12_DESIRED_SCALE_HT40_5); + alpha_therm = REG_READ_FIELD(ah, AR_PHY_TPC_19, + AR_PHY_TPC_19_ALPHA_THERM); + alpha_volt = REG_READ_FIELD(ah, AR_PHY_TPC_19, + AR_PHY_TPC_19_ALPHA_VOLT); + therm_cal_value = REG_READ_FIELD(ah, AR_PHY_TPC_18, + AR_PHY_TPC_18_THERM_CAL_VALUE); + volt_cal_value = REG_READ_FIELD(ah, AR_PHY_TPC_18, + AR_PHY_TPC_18_VOLT_CAL_VALUE); + therm_value = REG_READ_FIELD(ah, AR_PHY_BB_THERM_ADC_4, + AR_PHY_BB_THERM_ADC_4_LATEST_THERM_VALUE); + volt_value = REG_READ_FIELD(ah, AR_PHY_BB_THERM_ADC_4, + AR_PHY_BB_THERM_ADC_4_LATEST_VOLT_VALUE); + + switch (chain) { + case 0: + reg_olpc = AR_PHY_TPC_11_B0; + reg_cl_gain = AR_PHY_CL_TAB_0; + break; + case 1: + reg_olpc = AR_PHY_TPC_11_B1; + reg_cl_gain = AR_PHY_CL_TAB_1; + break; + case 2: + reg_olpc = AR_PHY_TPC_11_B2; + reg_cl_gain = AR_PHY_CL_TAB_2; + break; + default: + ath_dbg(ath9k_hw_common(ah), CALIBRATE, + "Invalid chainmask: %d\n", chain); + break; + } + + olpc_gain_delta = REG_READ_FIELD(ah, reg_olpc, + AR_PHY_TPC_11_OLPC_GAIN_DELTA); + cl_gain_mod = REG_READ_FIELD(ah, reg_cl_gain, + AR_PHY_CL_TAB_CL_GAIN_MOD); + + if (olpc_gain_delta >= 128) + olpc_gain_delta = olpc_gain_delta - 256; + + thermal_gain_corr = (alpha_therm * (therm_value - therm_cal_value) + + (256 / 2)) / 256; + voltage_gain_corr = (alpha_volt * (volt_value - volt_cal_value) + + (128 / 2)) / 128; + desired_gain = target_power - olpc_gain_delta - thermal_gain_corr - + voltage_gain_corr + desired_scale + cl_gain_mod; + + return desired_gain; +} + +static void ar9003_tx_force_gain(struct ath_hw *ah, unsigned int gain_index) +{ + int selected_gain_entry, txbb1dbgain, txbb6dbgain, txmxrgain; + int padrvgnA, padrvgnB, padrvgnC, padrvgnD; + u32 *gain_table_entries = ah->paprd_gain_table_entries; + + selected_gain_entry = gain_table_entries[gain_index]; + txbb1dbgain = selected_gain_entry & 0x7; + txbb6dbgain = (selected_gain_entry >> 3) & 0x3; + txmxrgain = (selected_gain_entry >> 5) & 0xf; + padrvgnA = (selected_gain_entry >> 9) & 0xf; + padrvgnB = (selected_gain_entry >> 13) & 0xf; + padrvgnC = (selected_gain_entry >> 17) & 0xf; + padrvgnD = (selected_gain_entry >> 21) & 0x3; + + REG_RMW_FIELD(ah, AR_PHY_TX_FORCED_GAIN, + AR_PHY_TX_FORCED_GAIN_FORCED_TXBB1DBGAIN, txbb1dbgain); + REG_RMW_FIELD(ah, AR_PHY_TX_FORCED_GAIN, + AR_PHY_TX_FORCED_GAIN_FORCED_TXBB6DBGAIN, txbb6dbgain); + REG_RMW_FIELD(ah, AR_PHY_TX_FORCED_GAIN, + AR_PHY_TX_FORCED_GAIN_FORCED_TXMXRGAIN, txmxrgain); + REG_RMW_FIELD(ah, AR_PHY_TX_FORCED_GAIN, + AR_PHY_TX_FORCED_GAIN_FORCED_PADRVGNA, padrvgnA); + REG_RMW_FIELD(ah, AR_PHY_TX_FORCED_GAIN, + AR_PHY_TX_FORCED_GAIN_FORCED_PADRVGNB, padrvgnB); + REG_RMW_FIELD(ah, AR_PHY_TX_FORCED_GAIN, + AR_PHY_TX_FORCED_GAIN_FORCED_PADRVGNC, padrvgnC); + REG_RMW_FIELD(ah, AR_PHY_TX_FORCED_GAIN, + AR_PHY_TX_FORCED_GAIN_FORCED_PADRVGND, padrvgnD); + REG_RMW_FIELD(ah, AR_PHY_TX_FORCED_GAIN, + AR_PHY_TX_FORCED_GAIN_FORCED_ENABLE_PAL, 0); + REG_RMW_FIELD(ah, AR_PHY_TX_FORCED_GAIN, + AR_PHY_TX_FORCED_GAIN_FORCE_TX_GAIN, 0); + REG_RMW_FIELD(ah, AR_PHY_TPC_1, AR_PHY_TPC_1_FORCED_DAC_GAIN, 0); + REG_RMW_FIELD(ah, AR_PHY_TPC_1, AR_PHY_TPC_1_FORCE_DAC_GAIN, 0); +} + +static inline int find_expn(int num) +{ + return fls(num) - 1; +} + +static inline int find_proper_scale(int expn, int N) +{ + return (expn > N) ? expn - 10 : 0; +} + +#define NUM_BIN 23 + +static bool create_pa_curve(u32 *data_L, u32 *data_U, u32 *pa_table, u16 *gain) +{ + unsigned int thresh_accum_cnt; + int x_est[NUM_BIN + 1], Y[NUM_BIN + 1], theta[NUM_BIN + 1]; + int PA_in[NUM_BIN + 1]; + int B1_tmp[NUM_BIN + 1], B2_tmp[NUM_BIN + 1]; + unsigned int B1_abs_max, B2_abs_max; + int max_index, scale_factor; + int y_est[NUM_BIN + 1]; + int x_est_fxp1_nonlin, x_tilde[NUM_BIN + 1]; + unsigned int x_tilde_abs; + int G_fxp, Y_intercept, order_x_by_y, M, I, L, sum_y_sqr, sum_y_quad; + int Q_x, Q_B1, Q_B2, beta_raw, alpha_raw, scale_B; + int Q_scale_B, Q_beta, Q_alpha, alpha, beta, order_1, order_2; + int order1_5x, order2_3x, order1_5x_rem, order2_3x_rem; + int y5, y3, tmp; + int theta_low_bin = 0; + int i; + + /* disregard any bin that contains <= 16 samples */ + thresh_accum_cnt = 16; + scale_factor = 5; + max_index = 0; + memset(theta, 0, sizeof(theta)); + memset(x_est, 0, sizeof(x_est)); + memset(Y, 0, sizeof(Y)); + memset(y_est, 0, sizeof(y_est)); + memset(x_tilde, 0, sizeof(x_tilde)); + + for (i = 0; i < NUM_BIN; i++) { + s32 accum_cnt, accum_tx, accum_rx, accum_ang; + + /* number of samples */ + accum_cnt = data_L[i] & 0xffff; + + if (accum_cnt <= thresh_accum_cnt) + continue; + + /* sum(tx amplitude) */ + accum_tx = ((data_L[i] >> 16) & 0xffff) | + ((data_U[i] & 0x7ff) << 16); + + /* sum(rx amplitude distance to lower bin edge) */ + accum_rx = ((data_U[i] >> 11) & 0x1f) | + ((data_L[i + 23] & 0xffff) << 5); + + /* sum(angles) */ + accum_ang = ((data_L[i + 23] >> 16) & 0xffff) | + ((data_U[i + 23] & 0x7ff) << 16); + + accum_tx <<= scale_factor; + accum_rx <<= scale_factor; + x_est[i + 1] = (((accum_tx + accum_cnt) / accum_cnt) + 32) >> + scale_factor; + + Y[i + 1] = ((((accum_rx + accum_cnt) / accum_cnt) + 32) >> + scale_factor) + + (1 << scale_factor) * max_index + 16; + + if (accum_ang >= (1 << 26)) + accum_ang -= 1 << 27; + + theta[i + 1] = ((accum_ang * (1 << scale_factor)) + accum_cnt) / + accum_cnt; + + max_index++; + } + + /* + * Find average theta of first 5 bin and all of those to same value. + * Curve is linear at that range. + */ + for (i = 1; i < 6; i++) + theta_low_bin += theta[i]; + + theta_low_bin = theta_low_bin / 5; + for (i = 1; i < 6; i++) + theta[i] = theta_low_bin; + + /* Set values at origin */ + theta[0] = theta_low_bin; + for (i = 0; i <= max_index; i++) + theta[i] -= theta_low_bin; + + x_est[0] = 0; + Y[0] = 0; + scale_factor = 8; + + /* low signal gain */ + if (x_est[6] == x_est[3]) + return false; + + G_fxp = + (((Y[6] - Y[3]) * 1 << scale_factor) + + (x_est[6] - x_est[3])) / (x_est[6] - x_est[3]); + + /* prevent division by zero */ + if (G_fxp == 0) + return false; + + Y_intercept = + (G_fxp * (x_est[0] - x_est[3]) + + (1 << scale_factor)) / (1 << scale_factor) + Y[3]; + + for (i = 0; i <= max_index; i++) + y_est[i] = Y[i] - Y_intercept; + + for (i = 0; i <= 3; i++) { + y_est[i] = i * 32; + x_est[i] = ((y_est[i] * 1 << scale_factor) + G_fxp) / G_fxp; + } + + if (y_est[max_index] == 0) + return false; + + x_est_fxp1_nonlin = + x_est[max_index] - ((1 << scale_factor) * y_est[max_index] + + G_fxp) / G_fxp; + + order_x_by_y = + (x_est_fxp1_nonlin + y_est[max_index]) / y_est[max_index]; + + if (order_x_by_y == 0) + M = 10; + else if (order_x_by_y == 1) + M = 9; + else + M = 8; + + I = (max_index > 15) ? 7 : max_index >> 1; + L = max_index - I; + scale_factor = 8; + sum_y_sqr = 0; + sum_y_quad = 0; + x_tilde_abs = 0; + + for (i = 0; i <= L; i++) { + unsigned int y_sqr; + unsigned int y_quad; + unsigned int tmp_abs; + + /* prevent division by zero */ + if (y_est[i + I] == 0) + return false; + + x_est_fxp1_nonlin = + x_est[i + I] - ((1 << scale_factor) * y_est[i + I] + + G_fxp) / G_fxp; + + x_tilde[i] = + (x_est_fxp1_nonlin * (1 << M) + y_est[i + I]) / y_est[i + + I]; + x_tilde[i] = + (x_tilde[i] * (1 << M) + y_est[i + I]) / y_est[i + I]; + x_tilde[i] = + (x_tilde[i] * (1 << M) + y_est[i + I]) / y_est[i + I]; + y_sqr = + (y_est[i + I] * y_est[i + I] + + (scale_factor * scale_factor)) / (scale_factor * + scale_factor); + tmp_abs = abs(x_tilde[i]); + if (tmp_abs > x_tilde_abs) + x_tilde_abs = tmp_abs; + + y_quad = y_sqr * y_sqr; + sum_y_sqr = sum_y_sqr + y_sqr; + sum_y_quad = sum_y_quad + y_quad; + B1_tmp[i] = y_sqr * (L + 1); + B2_tmp[i] = y_sqr; + } + + B1_abs_max = 0; + B2_abs_max = 0; + for (i = 0; i <= L; i++) { + int abs_val; + + B1_tmp[i] -= sum_y_sqr; + B2_tmp[i] = sum_y_quad - sum_y_sqr * B2_tmp[i]; + + abs_val = abs(B1_tmp[i]); + if (abs_val > B1_abs_max) + B1_abs_max = abs_val; + + abs_val = abs(B2_tmp[i]); + if (abs_val > B2_abs_max) + B2_abs_max = abs_val; + } + + Q_x = find_proper_scale(find_expn(x_tilde_abs), 10); + Q_B1 = find_proper_scale(find_expn(B1_abs_max), 10); + Q_B2 = find_proper_scale(find_expn(B2_abs_max), 10); + + beta_raw = 0; + alpha_raw = 0; + for (i = 0; i <= L; i++) { + x_tilde[i] = x_tilde[i] / (1 << Q_x); + B1_tmp[i] = B1_tmp[i] / (1 << Q_B1); + B2_tmp[i] = B2_tmp[i] / (1 << Q_B2); + beta_raw = beta_raw + B1_tmp[i] * x_tilde[i]; + alpha_raw = alpha_raw + B2_tmp[i] * x_tilde[i]; + } + + scale_B = + ((sum_y_quad / scale_factor) * (L + 1) - + (sum_y_sqr / scale_factor) * sum_y_sqr) * scale_factor; + + Q_scale_B = find_proper_scale(find_expn(abs(scale_B)), 10); + scale_B = scale_B / (1 << Q_scale_B); + if (scale_B == 0) + return false; + Q_beta = find_proper_scale(find_expn(abs(beta_raw)), 10); + Q_alpha = find_proper_scale(find_expn(abs(alpha_raw)), 10); + beta_raw = beta_raw / (1 << Q_beta); + alpha_raw = alpha_raw / (1 << Q_alpha); + alpha = (alpha_raw << 10) / scale_B; + beta = (beta_raw << 10) / scale_B; + order_1 = 3 * M - Q_x - Q_B1 - Q_beta + 10 + Q_scale_B; + order_2 = 3 * M - Q_x - Q_B2 - Q_alpha + 10 + Q_scale_B; + order1_5x = order_1 / 5; + order2_3x = order_2 / 3; + order1_5x_rem = order_1 - 5 * order1_5x; + order2_3x_rem = order_2 - 3 * order2_3x; + + for (i = 0; i < PAPRD_TABLE_SZ; i++) { + tmp = i * 32; + y5 = ((beta * tmp) >> 6) >> order1_5x; + y5 = (y5 * tmp) >> order1_5x; + y5 = (y5 * tmp) >> order1_5x; + y5 = (y5 * tmp) >> order1_5x; + y5 = (y5 * tmp) >> order1_5x; + y5 = y5 >> order1_5x_rem; + y3 = (alpha * tmp) >> order2_3x; + y3 = (y3 * tmp) >> order2_3x; + y3 = (y3 * tmp) >> order2_3x; + y3 = y3 >> order2_3x_rem; + PA_in[i] = y5 + y3 + (256 * tmp) / G_fxp; + + if (i >= 2) { + tmp = PA_in[i] - PA_in[i - 1]; + if (tmp < 0) + PA_in[i] = + PA_in[i - 1] + (PA_in[i - 1] - + PA_in[i - 2]); + } + + PA_in[i] = (PA_in[i] < 1400) ? PA_in[i] : 1400; + } + + beta_raw = 0; + alpha_raw = 0; + + for (i = 0; i <= L; i++) { + int theta_tilde = + ((theta[i + I] << M) + y_est[i + I]) / y_est[i + I]; + theta_tilde = + ((theta_tilde << M) + y_est[i + I]) / y_est[i + I]; + theta_tilde = + ((theta_tilde << M) + y_est[i + I]) / y_est[i + I]; + beta_raw = beta_raw + B1_tmp[i] * theta_tilde; + alpha_raw = alpha_raw + B2_tmp[i] * theta_tilde; + } + + Q_beta = find_proper_scale(find_expn(abs(beta_raw)), 10); + Q_alpha = find_proper_scale(find_expn(abs(alpha_raw)), 10); + beta_raw = beta_raw / (1 << Q_beta); + alpha_raw = alpha_raw / (1 << Q_alpha); + + alpha = (alpha_raw << 10) / scale_B; + beta = (beta_raw << 10) / scale_B; + order_1 = 3 * M - Q_x - Q_B1 - Q_beta + 10 + Q_scale_B + 5; + order_2 = 3 * M - Q_x - Q_B2 - Q_alpha + 10 + Q_scale_B + 5; + order1_5x = order_1 / 5; + order2_3x = order_2 / 3; + order1_5x_rem = order_1 - 5 * order1_5x; + order2_3x_rem = order_2 - 3 * order2_3x; + + for (i = 0; i < PAPRD_TABLE_SZ; i++) { + int PA_angle; + + /* pa_table[4] is calculated from PA_angle for i=5 */ + if (i == 4) + continue; + + tmp = i * 32; + if (beta > 0) + y5 = (((beta * tmp - 64) >> 6) - + (1 << order1_5x)) / (1 << order1_5x); + else + y5 = ((((beta * tmp - 64) >> 6) + + (1 << order1_5x)) / (1 << order1_5x)); + + y5 = (y5 * tmp) / (1 << order1_5x); + y5 = (y5 * tmp) / (1 << order1_5x); + y5 = (y5 * tmp) / (1 << order1_5x); + y5 = (y5 * tmp) / (1 << order1_5x); + y5 = y5 / (1 << order1_5x_rem); + + if (beta > 0) + y3 = (alpha * tmp - + (1 << order2_3x)) / (1 << order2_3x); + else + y3 = (alpha * tmp + + (1 << order2_3x)) / (1 << order2_3x); + y3 = (y3 * tmp) / (1 << order2_3x); + y3 = (y3 * tmp) / (1 << order2_3x); + y3 = y3 / (1 << order2_3x_rem); + + if (i < 4) { + PA_angle = 0; + } else { + PA_angle = y5 + y3; + if (PA_angle < -150) + PA_angle = -150; + else if (PA_angle > 150) + PA_angle = 150; + } + + pa_table[i] = ((PA_in[i] & 0x7ff) << 11) + (PA_angle & 0x7ff); + if (i == 5) { + PA_angle = (PA_angle + 2) >> 1; + pa_table[i - 1] = ((PA_in[i - 1] & 0x7ff) << 11) + + (PA_angle & 0x7ff); + } + } + + *gain = G_fxp; + return true; +} + +void ar9003_paprd_populate_single_table(struct ath_hw *ah, + struct ath9k_hw_cal_data *caldata, + int chain) +{ + u32 *paprd_table_val = caldata->pa_table[chain]; + u32 small_signal_gain = caldata->small_signal_gain[chain]; + u32 training_power = ah->paprd_training_power; + u32 reg = 0; + int i; + + if (chain == 0) + reg = AR_PHY_PAPRD_MEM_TAB_B0; + else if (chain == 1) + reg = AR_PHY_PAPRD_MEM_TAB_B1; + else if (chain == 2) + reg = AR_PHY_PAPRD_MEM_TAB_B2; + + for (i = 0; i < PAPRD_TABLE_SZ; i++) { + REG_WRITE(ah, reg, paprd_table_val[i]); + reg = reg + 4; + } + + if (chain == 0) + reg = AR_PHY_PA_GAIN123_B0; + else if (chain == 1) + reg = AR_PHY_PA_GAIN123_B1; + else + reg = AR_PHY_PA_GAIN123_B2; + + REG_RMW_FIELD(ah, reg, AR_PHY_PA_GAIN123_PA_GAIN1, small_signal_gain); + + REG_RMW_FIELD(ah, AR_PHY_PAPRD_CTRL1_B0, + AR_PHY_PAPRD_CTRL1_PAPRD_POWER_AT_AM2AM_CAL, + training_power); + + if (ah->caps.tx_chainmask & BIT(1)) + REG_RMW_FIELD(ah, AR_PHY_PAPRD_CTRL1_B1, + AR_PHY_PAPRD_CTRL1_PAPRD_POWER_AT_AM2AM_CAL, + training_power); + + if (ah->caps.tx_chainmask & BIT(2)) + /* val AR_PHY_PAPRD_CTRL1_PAPRD_POWER_AT_AM2AM_CAL correct? */ + REG_RMW_FIELD(ah, AR_PHY_PAPRD_CTRL1_B2, + AR_PHY_PAPRD_CTRL1_PAPRD_POWER_AT_AM2AM_CAL, + training_power); +} +EXPORT_SYMBOL(ar9003_paprd_populate_single_table); + +void ar9003_paprd_setup_gain_table(struct ath_hw *ah, int chain) +{ + unsigned int i, desired_gain, gain_index; + unsigned int train_power = ah->paprd_training_power; + + desired_gain = ar9003_get_desired_gain(ah, chain, train_power); + + gain_index = 0; + for (i = 0; i < PAPRD_GAIN_TABLE_ENTRIES; i++) { + if (ah->paprd_gain_table_index[i] >= desired_gain) + break; + gain_index++; + } + + ar9003_tx_force_gain(ah, gain_index); + + REG_CLR_BIT(ah, AR_PHY_PAPRD_TRAINER_STAT1, + AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_TRAIN_DONE); +} +EXPORT_SYMBOL(ar9003_paprd_setup_gain_table); + +static bool ar9003_paprd_retrain_pa_in(struct ath_hw *ah, + struct ath9k_hw_cal_data *caldata, + int chain) +{ + u32 *pa_in = caldata->pa_table[chain]; + int capdiv_offset, quick_drop_offset; + int capdiv2g, quick_drop; + int count = 0; + int i; + + if (!AR_SREV_9485(ah) && !AR_SREV_9330(ah)) + return false; + + capdiv2g = REG_READ_FIELD(ah, AR_PHY_65NM_CH0_TXRF3, + AR_PHY_65NM_CH0_TXRF3_CAPDIV2G); + + quick_drop = REG_READ_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL3, + AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_QUICK_DROP); + + if (quick_drop) + quick_drop -= 0x40; + + for (i = 0; i < NUM_BIN + 1; i++) { + if (pa_in[i] == 1400) + count++; + } + + if (AR_SREV_9485(ah)) { + if (pa_in[23] < 800) { + capdiv_offset = (int)((1000 - pa_in[23] + 75) / 150); + capdiv2g += capdiv_offset; + if (capdiv2g > 7) { + capdiv2g = 7; + if (pa_in[23] < 600) { + quick_drop++; + if (quick_drop > 0) + quick_drop = 0; + } + } + } else if (pa_in[23] == 1400) { + quick_drop_offset = min_t(int, count / 3, 2); + quick_drop += quick_drop_offset; + capdiv2g += quick_drop_offset / 2; + + if (capdiv2g > 7) + capdiv2g = 7; + + if (quick_drop > 0) { + quick_drop = 0; + capdiv2g -= quick_drop_offset; + if (capdiv2g < 0) + capdiv2g = 0; + } + } else { + return false; + } + } else if (AR_SREV_9330(ah)) { + if (pa_in[23] < 1000) { + capdiv_offset = (1000 - pa_in[23]) / 100; + capdiv2g += capdiv_offset; + if (capdiv_offset > 3) { + capdiv_offset = 1; + quick_drop--; + } + + capdiv2g += capdiv_offset; + if (capdiv2g > 6) + capdiv2g = 6; + if (quick_drop < -4) + quick_drop = -4; + } else if (pa_in[23] == 1400) { + if (count > 3) { + quick_drop++; + capdiv2g -= count / 4; + if (quick_drop > -2) + quick_drop = -2; + } else { + capdiv2g--; + } + + if (capdiv2g < 0) + capdiv2g = 0; + } else { + return false; + } + } + + REG_RMW_FIELD(ah, AR_PHY_65NM_CH0_TXRF3, + AR_PHY_65NM_CH0_TXRF3_CAPDIV2G, capdiv2g); + REG_RMW_FIELD(ah, AR_PHY_PAPRD_TRAINER_CNTL3, + AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_QUICK_DROP, + quick_drop); + + return true; +} + +int ar9003_paprd_create_curve(struct ath_hw *ah, + struct ath9k_hw_cal_data *caldata, int chain) +{ + u16 *small_signal_gain = &caldata->small_signal_gain[chain]; + u32 *pa_table = caldata->pa_table[chain]; + u32 *data_L, *data_U; + int i, status = 0; + u32 *buf; + u32 reg; + + memset(caldata->pa_table[chain], 0, sizeof(caldata->pa_table[chain])); + + buf = kmalloc(2 * 48 * sizeof(u32), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + data_L = &buf[0]; + data_U = &buf[48]; + + REG_CLR_BIT(ah, AR_PHY_CHAN_INFO_MEMORY, + AR_PHY_CHAN_INFO_MEMORY_CHANINFOMEM_S2_READ); + + reg = AR_PHY_CHAN_INFO_TAB_0; + for (i = 0; i < 48; i++) + data_L[i] = REG_READ(ah, reg + (i << 2)); + + REG_SET_BIT(ah, AR_PHY_CHAN_INFO_MEMORY, + AR_PHY_CHAN_INFO_MEMORY_CHANINFOMEM_S2_READ); + + for (i = 0; i < 48; i++) + data_U[i] = REG_READ(ah, reg + (i << 2)); + + if (!create_pa_curve(data_L, data_U, pa_table, small_signal_gain)) + status = -2; + + if (ar9003_paprd_retrain_pa_in(ah, caldata, chain)) + status = -EINPROGRESS; + + REG_CLR_BIT(ah, AR_PHY_PAPRD_TRAINER_STAT1, + AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_TRAIN_DONE); + + kfree(buf); + + return status; +} +EXPORT_SYMBOL(ar9003_paprd_create_curve); + +int ar9003_paprd_init_table(struct ath_hw *ah) +{ + int ret; + + ret = ar9003_paprd_setup_single_table(ah); + if (ret < 0) + return ret; + + ar9003_paprd_get_gain_table(ah); + return 0; +} +EXPORT_SYMBOL(ar9003_paprd_init_table); + +bool ar9003_paprd_is_done(struct ath_hw *ah) +{ + int paprd_done, agc2_pwr; + + paprd_done = REG_READ_FIELD(ah, AR_PHY_PAPRD_TRAINER_STAT1, + AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_TRAIN_DONE); + + if (AR_SREV_9485(ah)) + goto exit; + + if (paprd_done == 0x1) { + agc2_pwr = REG_READ_FIELD(ah, AR_PHY_PAPRD_TRAINER_STAT1, + AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_AGC2_PWR); + + ath_dbg(ath9k_hw_common(ah), CALIBRATE, + "AGC2_PWR = 0x%x training done = 0x%x\n", + agc2_pwr, paprd_done); + /* + * agc2_pwr range should not be less than 'IDEAL_AGC2_PWR_CHANGE' + * when the training is completely done, otherwise retraining is + * done to make sure the value is in ideal range + */ + if (agc2_pwr <= PAPRD_IDEAL_AGC2_PWR_RANGE) + paprd_done = 0; + } +exit: + return !!paprd_done; +} +EXPORT_SYMBOL(ar9003_paprd_is_done); + +bool ar9003_is_paprd_enabled(struct ath_hw *ah) +{ + if ((ah->caps.hw_caps & ATH9K_HW_CAP_PAPRD) && ah->config.enable_paprd) + return true; + + return false; +} +EXPORT_SYMBOL(ar9003_is_paprd_enabled); diff --git a/drivers/net/wireless/ath/ath9k/ar9003_phy.c b/drivers/net/wireless/ath/ath9k/ar9003_phy.c new file mode 100644 index 0000000..3457ca5 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9003_phy.c @@ -0,0 +1,1685 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "hw.h" +#include "ar9003_phy.h" + +static const int firstep_table[] = +/* level: 0 1 2 3 4 5 6 7 8 */ + { -4, -2, 0, 2, 4, 6, 8, 10, 12 }; /* lvl 0-8, default 2 */ + +static const int cycpwrThr1_table[] = +/* level: 0 1 2 3 4 5 6 7 8 */ + { -6, -4, -2, 0, 2, 4, 6, 8 }; /* lvl 0-7, default 3 */ + +/* + * register values to turn OFDM weak signal detection OFF + */ +static const int m1ThreshLow_off = 127; +static const int m2ThreshLow_off = 127; +static const int m1Thresh_off = 127; +static const int m2Thresh_off = 127; +static const int m2CountThr_off = 31; +static const int m2CountThrLow_off = 63; +static const int m1ThreshLowExt_off = 127; +static const int m2ThreshLowExt_off = 127; +static const int m1ThreshExt_off = 127; +static const int m2ThreshExt_off = 127; + +/** + * ar9003_hw_set_channel - set channel on single-chip device + * @ah: atheros hardware structure + * @chan: + * + * This is the function to change channel on single-chip devices, that is + * for AR9300 family of chipsets. + * + * This function takes the channel value in MHz and sets + * hardware channel value. Assumes writes have been enabled to analog bus. + * + * Actual Expression, + * + * For 2GHz channel, + * Channel Frequency = (3/4) * freq_ref * (chansel[8:0] + chanfrac[16:0]/2^17) + * (freq_ref = 40MHz) + * + * For 5GHz channel, + * Channel Frequency = (3/2) * freq_ref * (chansel[8:0] + chanfrac[16:0]/2^10) + * (freq_ref = 40MHz/(24>>amodeRefSel)) + * + * For 5GHz channels which are 5MHz spaced, + * Channel Frequency = (3/2) * freq_ref * (chansel[8:0] + chanfrac[16:0]/2^17) + * (freq_ref = 40MHz) + */ +static int ar9003_hw_set_channel(struct ath_hw *ah, struct ath9k_channel *chan) +{ + u16 bMode, fracMode = 0, aModeRefSel = 0; + u32 freq, chan_frac, div, channelSel = 0, reg32 = 0; + struct chan_centers centers; + int loadSynthChannel; + + ath9k_hw_get_channel_centers(ah, chan, ¢ers); + freq = centers.synth_center; + + if (freq < 4800) { /* 2 GHz, fractional mode */ + if (AR_SREV_9330(ah)) { + if (ah->is_clk_25mhz) + div = 75; + else + div = 120; + + channelSel = (freq * 4) / div; + chan_frac = (((freq * 4) % div) * 0x20000) / div; + channelSel = (channelSel << 17) | chan_frac; + } else if (AR_SREV_9485(ah) || AR_SREV_9565(ah)) { + /* + * freq_ref = 40 / (refdiva >> amoderefsel); + * where refdiva=1 and amoderefsel=0 + * ndiv = ((chan_mhz * 4) / 3) / freq_ref; + * chansel = int(ndiv), chanfrac = (ndiv - chansel) * 0x20000 + */ + channelSel = (freq * 4) / 120; + chan_frac = (((freq * 4) % 120) * 0x20000) / 120; + channelSel = (channelSel << 17) | chan_frac; + } else if (AR_SREV_9340(ah)) { + if (ah->is_clk_25mhz) { + channelSel = (freq * 2) / 75; + chan_frac = (((freq * 2) % 75) * 0x20000) / 75; + channelSel = (channelSel << 17) | chan_frac; + } else { + channelSel = CHANSEL_2G(freq) >> 1; + } + } else if (AR_SREV_9550(ah)) { + if (ah->is_clk_25mhz) + div = 75; + else + div = 120; + + channelSel = (freq * 4) / div; + chan_frac = (((freq * 4) % div) * 0x20000) / div; + channelSel = (channelSel << 17) | chan_frac; + } else { + channelSel = CHANSEL_2G(freq); + } + /* Set to 2G mode */ + bMode = 1; + } else { + if ((AR_SREV_9340(ah) || AR_SREV_9550(ah)) && + ah->is_clk_25mhz) { + channelSel = freq / 75; + chan_frac = ((freq % 75) * 0x20000) / 75; + channelSel = (channelSel << 17) | chan_frac; + } else { + channelSel = CHANSEL_5G(freq); + /* Doubler is ON, so, divide channelSel by 2. */ + channelSel >>= 1; + } + /* Set to 5G mode */ + bMode = 0; + } + + /* Enable fractional mode for all channels */ + fracMode = 1; + aModeRefSel = 0; + loadSynthChannel = 0; + + reg32 = (bMode << 29); + REG_WRITE(ah, AR_PHY_SYNTH_CONTROL, reg32); + + /* Enable Long shift Select for Synthesizer */ + REG_RMW_FIELD(ah, AR_PHY_65NM_CH0_SYNTH4, + AR_PHY_SYNTH4_LONG_SHIFT_SELECT, 1); + + /* Program Synth. setting */ + reg32 = (channelSel << 2) | (fracMode << 30) | + (aModeRefSel << 28) | (loadSynthChannel << 31); + REG_WRITE(ah, AR_PHY_65NM_CH0_SYNTH7, reg32); + + /* Toggle Load Synth channel bit */ + loadSynthChannel = 1; + reg32 = (channelSel << 2) | (fracMode << 30) | + (aModeRefSel << 28) | (loadSynthChannel << 31); + REG_WRITE(ah, AR_PHY_65NM_CH0_SYNTH7, reg32); + + ah->curchan = chan; + + return 0; +} + +/** + * ar9003_hw_spur_mitigate_mrc_cck - convert baseband spur frequency + * @ah: atheros hardware structure + * @chan: + * + * For single-chip solutions. Converts to baseband spur frequency given the + * input channel frequency and compute register settings below. + * + * Spur mitigation for MRC CCK + */ +static void ar9003_hw_spur_mitigate_mrc_cck(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + static const u32 spur_freq[4] = { 2420, 2440, 2464, 2480 }; + int cur_bb_spur, negative = 0, cck_spur_freq; + int i; + int range, max_spur_cnts, synth_freq; + u8 *spur_fbin_ptr = ar9003_get_spur_chan_ptr(ah, IS_CHAN_2GHZ(chan)); + + /* + * Need to verify range +/- 10 MHz in control channel, otherwise spur + * is out-of-band and can be ignored. + */ + + if (AR_SREV_9485(ah) || AR_SREV_9340(ah) || AR_SREV_9330(ah) || + AR_SREV_9550(ah)) { + if (spur_fbin_ptr[0] == 0) /* No spur */ + return; + max_spur_cnts = 5; + if (IS_CHAN_HT40(chan)) { + range = 19; + if (REG_READ_FIELD(ah, AR_PHY_GEN_CTRL, + AR_PHY_GC_DYN2040_PRI_CH) == 0) + synth_freq = chan->channel + 10; + else + synth_freq = chan->channel - 10; + } else { + range = 10; + synth_freq = chan->channel; + } + } else { + range = AR_SREV_9462(ah) ? 5 : 10; + max_spur_cnts = 4; + synth_freq = chan->channel; + } + + for (i = 0; i < max_spur_cnts; i++) { + if (AR_SREV_9462(ah) && (i == 0 || i == 3)) + continue; + + negative = 0; + if (AR_SREV_9485(ah) || AR_SREV_9340(ah) || AR_SREV_9330(ah) || + AR_SREV_9550(ah)) + cur_bb_spur = ath9k_hw_fbin2freq(spur_fbin_ptr[i], + IS_CHAN_2GHZ(chan)); + else + cur_bb_spur = spur_freq[i]; + + cur_bb_spur -= synth_freq; + if (cur_bb_spur < 0) { + negative = 1; + cur_bb_spur = -cur_bb_spur; + } + if (cur_bb_spur < range) { + cck_spur_freq = (int)((cur_bb_spur << 19) / 11); + + if (negative == 1) + cck_spur_freq = -cck_spur_freq; + + cck_spur_freq = cck_spur_freq & 0xfffff; + + REG_RMW_FIELD(ah, AR_PHY_AGC_CONTROL, + AR_PHY_AGC_CONTROL_YCOK_MAX, 0x7); + REG_RMW_FIELD(ah, AR_PHY_CCK_SPUR_MIT, + AR_PHY_CCK_SPUR_MIT_SPUR_RSSI_THR, 0x7f); + REG_RMW_FIELD(ah, AR_PHY_CCK_SPUR_MIT, + AR_PHY_CCK_SPUR_MIT_SPUR_FILTER_TYPE, + 0x2); + REG_RMW_FIELD(ah, AR_PHY_CCK_SPUR_MIT, + AR_PHY_CCK_SPUR_MIT_USE_CCK_SPUR_MIT, + 0x1); + REG_RMW_FIELD(ah, AR_PHY_CCK_SPUR_MIT, + AR_PHY_CCK_SPUR_MIT_CCK_SPUR_FREQ, + cck_spur_freq); + + return; + } + } + + REG_RMW_FIELD(ah, AR_PHY_AGC_CONTROL, + AR_PHY_AGC_CONTROL_YCOK_MAX, 0x5); + REG_RMW_FIELD(ah, AR_PHY_CCK_SPUR_MIT, + AR_PHY_CCK_SPUR_MIT_USE_CCK_SPUR_MIT, 0x0); + REG_RMW_FIELD(ah, AR_PHY_CCK_SPUR_MIT, + AR_PHY_CCK_SPUR_MIT_CCK_SPUR_FREQ, 0x0); +} + +/* Clean all spur register fields */ +static void ar9003_hw_spur_ofdm_clear(struct ath_hw *ah) +{ + REG_RMW_FIELD(ah, AR_PHY_TIMING4, + AR_PHY_TIMING4_ENABLE_SPUR_FILTER, 0); + REG_RMW_FIELD(ah, AR_PHY_TIMING11, + AR_PHY_TIMING11_SPUR_FREQ_SD, 0); + REG_RMW_FIELD(ah, AR_PHY_TIMING11, + AR_PHY_TIMING11_SPUR_DELTA_PHASE, 0); + REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT, + AR_PHY_SFCORR_EXT_SPUR_SUBCHANNEL_SD, 0); + REG_RMW_FIELD(ah, AR_PHY_TIMING11, + AR_PHY_TIMING11_USE_SPUR_FILTER_IN_AGC, 0); + REG_RMW_FIELD(ah, AR_PHY_TIMING11, + AR_PHY_TIMING11_USE_SPUR_FILTER_IN_SELFCOR, 0); + REG_RMW_FIELD(ah, AR_PHY_TIMING4, + AR_PHY_TIMING4_ENABLE_SPUR_RSSI, 0); + REG_RMW_FIELD(ah, AR_PHY_SPUR_REG, + AR_PHY_SPUR_REG_EN_VIT_SPUR_RSSI, 0); + REG_RMW_FIELD(ah, AR_PHY_SPUR_REG, + AR_PHY_SPUR_REG_ENABLE_NF_RSSI_SPUR_MIT, 0); + + REG_RMW_FIELD(ah, AR_PHY_SPUR_REG, + AR_PHY_SPUR_REG_ENABLE_MASK_PPM, 0); + REG_RMW_FIELD(ah, AR_PHY_TIMING4, + AR_PHY_TIMING4_ENABLE_PILOT_MASK, 0); + REG_RMW_FIELD(ah, AR_PHY_TIMING4, + AR_PHY_TIMING4_ENABLE_CHAN_MASK, 0); + REG_RMW_FIELD(ah, AR_PHY_PILOT_SPUR_MASK, + AR_PHY_PILOT_SPUR_MASK_CF_PILOT_MASK_IDX_A, 0); + REG_RMW_FIELD(ah, AR_PHY_SPUR_MASK_A, + AR_PHY_SPUR_MASK_A_CF_PUNC_MASK_IDX_A, 0); + REG_RMW_FIELD(ah, AR_PHY_CHAN_SPUR_MASK, + AR_PHY_CHAN_SPUR_MASK_CF_CHAN_MASK_IDX_A, 0); + REG_RMW_FIELD(ah, AR_PHY_PILOT_SPUR_MASK, + AR_PHY_PILOT_SPUR_MASK_CF_PILOT_MASK_A, 0); + REG_RMW_FIELD(ah, AR_PHY_CHAN_SPUR_MASK, + AR_PHY_CHAN_SPUR_MASK_CF_CHAN_MASK_A, 0); + REG_RMW_FIELD(ah, AR_PHY_SPUR_MASK_A, + AR_PHY_SPUR_MASK_A_CF_PUNC_MASK_A, 0); + REG_RMW_FIELD(ah, AR_PHY_SPUR_REG, + AR_PHY_SPUR_REG_MASK_RATE_CNTL, 0); +} + +static void ar9003_hw_spur_ofdm(struct ath_hw *ah, + int freq_offset, + int spur_freq_sd, + int spur_delta_phase, + int spur_subchannel_sd, + int range, + int synth_freq) +{ + int mask_index = 0; + + /* OFDM Spur mitigation */ + REG_RMW_FIELD(ah, AR_PHY_TIMING4, + AR_PHY_TIMING4_ENABLE_SPUR_FILTER, 0x1); + REG_RMW_FIELD(ah, AR_PHY_TIMING11, + AR_PHY_TIMING11_SPUR_FREQ_SD, spur_freq_sd); + REG_RMW_FIELD(ah, AR_PHY_TIMING11, + AR_PHY_TIMING11_SPUR_DELTA_PHASE, spur_delta_phase); + REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT, + AR_PHY_SFCORR_EXT_SPUR_SUBCHANNEL_SD, spur_subchannel_sd); + REG_RMW_FIELD(ah, AR_PHY_TIMING11, + AR_PHY_TIMING11_USE_SPUR_FILTER_IN_AGC, 0x1); + + if (!(AR_SREV_9565(ah) && range == 10 && synth_freq == 2437)) + REG_RMW_FIELD(ah, AR_PHY_TIMING11, + AR_PHY_TIMING11_USE_SPUR_FILTER_IN_SELFCOR, 0x1); + + REG_RMW_FIELD(ah, AR_PHY_TIMING4, + AR_PHY_TIMING4_ENABLE_SPUR_RSSI, 0x1); + REG_RMW_FIELD(ah, AR_PHY_SPUR_REG, + AR_PHY_SPUR_REG_SPUR_RSSI_THRESH, 34); + REG_RMW_FIELD(ah, AR_PHY_SPUR_REG, + AR_PHY_SPUR_REG_EN_VIT_SPUR_RSSI, 1); + + if (!AR_SREV_9340(ah) && + REG_READ_FIELD(ah, AR_PHY_MODE, + AR_PHY_MODE_DYNAMIC) == 0x1) + REG_RMW_FIELD(ah, AR_PHY_SPUR_REG, + AR_PHY_SPUR_REG_ENABLE_NF_RSSI_SPUR_MIT, 1); + + mask_index = (freq_offset << 4) / 5; + if (mask_index < 0) + mask_index = mask_index - 1; + + mask_index = mask_index & 0x7f; + + REG_RMW_FIELD(ah, AR_PHY_SPUR_REG, + AR_PHY_SPUR_REG_ENABLE_MASK_PPM, 0x1); + REG_RMW_FIELD(ah, AR_PHY_TIMING4, + AR_PHY_TIMING4_ENABLE_PILOT_MASK, 0x1); + REG_RMW_FIELD(ah, AR_PHY_TIMING4, + AR_PHY_TIMING4_ENABLE_CHAN_MASK, 0x1); + REG_RMW_FIELD(ah, AR_PHY_PILOT_SPUR_MASK, + AR_PHY_PILOT_SPUR_MASK_CF_PILOT_MASK_IDX_A, mask_index); + REG_RMW_FIELD(ah, AR_PHY_SPUR_MASK_A, + AR_PHY_SPUR_MASK_A_CF_PUNC_MASK_IDX_A, mask_index); + REG_RMW_FIELD(ah, AR_PHY_CHAN_SPUR_MASK, + AR_PHY_CHAN_SPUR_MASK_CF_CHAN_MASK_IDX_A, mask_index); + REG_RMW_FIELD(ah, AR_PHY_PILOT_SPUR_MASK, + AR_PHY_PILOT_SPUR_MASK_CF_PILOT_MASK_A, 0xc); + REG_RMW_FIELD(ah, AR_PHY_CHAN_SPUR_MASK, + AR_PHY_CHAN_SPUR_MASK_CF_CHAN_MASK_A, 0xc); + REG_RMW_FIELD(ah, AR_PHY_SPUR_MASK_A, + AR_PHY_SPUR_MASK_A_CF_PUNC_MASK_A, 0xa0); + REG_RMW_FIELD(ah, AR_PHY_SPUR_REG, + AR_PHY_SPUR_REG_MASK_RATE_CNTL, 0xff); +} + +static void ar9003_hw_spur_ofdm_9565(struct ath_hw *ah, + int freq_offset) +{ + int mask_index = 0; + + mask_index = (freq_offset << 4) / 5; + if (mask_index < 0) + mask_index = mask_index - 1; + + mask_index = mask_index & 0x7f; + + REG_RMW_FIELD(ah, AR_PHY_PILOT_SPUR_MASK, + AR_PHY_PILOT_SPUR_MASK_CF_PILOT_MASK_IDX_B, + mask_index); + + /* A == B */ + REG_RMW_FIELD(ah, AR_PHY_SPUR_MASK_B, + AR_PHY_SPUR_MASK_A_CF_PUNC_MASK_IDX_A, + mask_index); + + REG_RMW_FIELD(ah, AR_PHY_CHAN_SPUR_MASK, + AR_PHY_CHAN_SPUR_MASK_CF_CHAN_MASK_IDX_B, + mask_index); + REG_RMW_FIELD(ah, AR_PHY_PILOT_SPUR_MASK, + AR_PHY_PILOT_SPUR_MASK_CF_PILOT_MASK_B, 0xe); + REG_RMW_FIELD(ah, AR_PHY_CHAN_SPUR_MASK, + AR_PHY_CHAN_SPUR_MASK_CF_CHAN_MASK_B, 0xe); + + /* A == B */ + REG_RMW_FIELD(ah, AR_PHY_SPUR_MASK_B, + AR_PHY_SPUR_MASK_A_CF_PUNC_MASK_A, 0xa0); +} + +static void ar9003_hw_spur_ofdm_work(struct ath_hw *ah, + struct ath9k_channel *chan, + int freq_offset, + int range, + int synth_freq) +{ + int spur_freq_sd = 0; + int spur_subchannel_sd = 0; + int spur_delta_phase = 0; + + if (IS_CHAN_HT40(chan)) { + if (freq_offset < 0) { + if (REG_READ_FIELD(ah, AR_PHY_GEN_CTRL, + AR_PHY_GC_DYN2040_PRI_CH) == 0x0) + spur_subchannel_sd = 1; + else + spur_subchannel_sd = 0; + + spur_freq_sd = ((freq_offset + 10) << 9) / 11; + + } else { + if (REG_READ_FIELD(ah, AR_PHY_GEN_CTRL, + AR_PHY_GC_DYN2040_PRI_CH) == 0x0) + spur_subchannel_sd = 0; + else + spur_subchannel_sd = 1; + + spur_freq_sd = ((freq_offset - 10) << 9) / 11; + + } + + spur_delta_phase = (freq_offset << 17) / 5; + + } else { + spur_subchannel_sd = 0; + spur_freq_sd = (freq_offset << 9) /11; + spur_delta_phase = (freq_offset << 18) / 5; + } + + spur_freq_sd = spur_freq_sd & 0x3ff; + spur_delta_phase = spur_delta_phase & 0xfffff; + + ar9003_hw_spur_ofdm(ah, + freq_offset, + spur_freq_sd, + spur_delta_phase, + spur_subchannel_sd, + range, synth_freq); +} + +/* Spur mitigation for OFDM */ +static void ar9003_hw_spur_mitigate_ofdm(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + int synth_freq; + int range = 10; + int freq_offset = 0; + int mode; + u8* spurChansPtr; + unsigned int i; + struct ar9300_eeprom *eep = &ah->eeprom.ar9300_eep; + + if (IS_CHAN_5GHZ(chan)) { + spurChansPtr = &(eep->modalHeader5G.spurChans[0]); + mode = 0; + } + else { + spurChansPtr = &(eep->modalHeader2G.spurChans[0]); + mode = 1; + } + + if (spurChansPtr[0] == 0) + return; /* No spur in the mode */ + + if (IS_CHAN_HT40(chan)) { + range = 19; + if (REG_READ_FIELD(ah, AR_PHY_GEN_CTRL, + AR_PHY_GC_DYN2040_PRI_CH) == 0x0) + synth_freq = chan->channel - 10; + else + synth_freq = chan->channel + 10; + } else { + range = 10; + synth_freq = chan->channel; + } + + ar9003_hw_spur_ofdm_clear(ah); + + for (i = 0; i < AR_EEPROM_MODAL_SPURS && spurChansPtr[i]; i++) { + freq_offset = ath9k_hw_fbin2freq(spurChansPtr[i], mode); + freq_offset -= synth_freq; + if (abs(freq_offset) < range) { + ar9003_hw_spur_ofdm_work(ah, chan, freq_offset, + range, synth_freq); + + if (AR_SREV_9565(ah) && (i < 4)) { + freq_offset = ath9k_hw_fbin2freq(spurChansPtr[i + 1], + mode); + freq_offset -= synth_freq; + if (abs(freq_offset) < range) + ar9003_hw_spur_ofdm_9565(ah, freq_offset); + } + + break; + } + } +} + +static void ar9003_hw_spur_mitigate(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + if (!AR_SREV_9565(ah)) + ar9003_hw_spur_mitigate_mrc_cck(ah, chan); + ar9003_hw_spur_mitigate_ofdm(ah, chan); +} + +static u32 ar9003_hw_compute_pll_control(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + u32 pll; + + pll = SM(0x5, AR_RTC_9300_PLL_REFDIV); + + if (chan && IS_CHAN_HALF_RATE(chan)) + pll |= SM(0x1, AR_RTC_9300_PLL_CLKSEL); + else if (chan && IS_CHAN_QUARTER_RATE(chan)) + pll |= SM(0x2, AR_RTC_9300_PLL_CLKSEL); + + pll |= SM(0x2c, AR_RTC_9300_PLL_DIV); + + return pll; +} + +static void ar9003_hw_set_channel_regs(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + u32 phymode; + u32 enableDacFifo = 0; + + enableDacFifo = + (REG_READ(ah, AR_PHY_GEN_CTRL) & AR_PHY_GC_ENABLE_DAC_FIFO); + + /* Enable 11n HT, 20 MHz */ + phymode = AR_PHY_GC_HT_EN | AR_PHY_GC_SINGLE_HT_LTF1 | + AR_PHY_GC_SHORT_GI_40 | enableDacFifo; + + /* Configure baseband for dynamic 20/40 operation */ + if (IS_CHAN_HT40(chan)) { + phymode |= AR_PHY_GC_DYN2040_EN; + /* Configure control (primary) channel at +-10MHz */ + if ((chan->chanmode == CHANNEL_A_HT40PLUS) || + (chan->chanmode == CHANNEL_G_HT40PLUS)) + phymode |= AR_PHY_GC_DYN2040_PRI_CH; + + } + + /* make sure we preserve INI settings */ + phymode |= REG_READ(ah, AR_PHY_GEN_CTRL); + /* turn off Green Field detection for STA for now */ + phymode &= ~AR_PHY_GC_GF_DETECT_EN; + + REG_WRITE(ah, AR_PHY_GEN_CTRL, phymode); + + /* Configure MAC for 20/40 operation */ + ath9k_hw_set11nmac2040(ah); + + /* global transmit timeout (25 TUs default)*/ + REG_WRITE(ah, AR_GTXTO, 25 << AR_GTXTO_TIMEOUT_LIMIT_S); + /* carrier sense timeout */ + REG_WRITE(ah, AR_CST, 0xF << AR_CST_TIMEOUT_LIMIT_S); +} + +static void ar9003_hw_init_bb(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + u32 synthDelay; + + /* + * Wait for the frequency synth to settle (synth goes on + * via AR_PHY_ACTIVE_EN). Read the phy active delay register. + * Value is in 100ns increments. + */ + synthDelay = REG_READ(ah, AR_PHY_RX_DELAY) & AR_PHY_RX_DELAY_DELAY; + + /* Activate the PHY (includes baseband activate + synthesizer on) */ + REG_WRITE(ah, AR_PHY_ACTIVE, AR_PHY_ACTIVE_EN); + ath9k_hw_synth_delay(ah, chan, synthDelay); +} + +void ar9003_hw_set_chain_masks(struct ath_hw *ah, u8 rx, u8 tx) +{ + if (ah->caps.tx_chainmask == 5 || ah->caps.rx_chainmask == 5) + REG_SET_BIT(ah, AR_PHY_ANALOG_SWAP, + AR_PHY_SWAP_ALT_CHAIN); + + REG_WRITE(ah, AR_PHY_RX_CHAINMASK, rx); + REG_WRITE(ah, AR_PHY_CAL_CHAINMASK, rx); + + if ((ah->caps.hw_caps & ATH9K_HW_CAP_APM) && (tx == 0x7)) + tx = 3; + + REG_WRITE(ah, AR_SELFGEN_MASK, tx); +} + +/* + * Override INI values with chip specific configuration. + */ +static void ar9003_hw_override_ini(struct ath_hw *ah) +{ + u32 val; + + /* + * Set the RX_ABORT and RX_DIS and clear it only after + * RXE is set for MAC. This prevents frames with + * corrupted descriptor status. + */ + REG_SET_BIT(ah, AR_DIAG_SW, (AR_DIAG_RX_DIS | AR_DIAG_RX_ABORT)); + + /* + * For AR9280 and above, there is a new feature that allows + * Multicast search based on both MAC Address and Key ID. By default, + * this feature is enabled. But since the driver is not using this + * feature, we switch it off; otherwise multicast search based on + * MAC addr only will fail. + */ + val = REG_READ(ah, AR_PCU_MISC_MODE2) & (~AR_ADHOC_MCAST_KEYID_ENABLE); + REG_WRITE(ah, AR_PCU_MISC_MODE2, + val | AR_AGG_WEP_ENABLE_FIX | AR_AGG_WEP_ENABLE); + + REG_SET_BIT(ah, AR_PHY_CCK_DETECT, + AR_PHY_CCK_DETECT_BB_ENABLE_ANT_FAST_DIV); +} + +static void ar9003_hw_prog_ini(struct ath_hw *ah, + struct ar5416IniArray *iniArr, + int column) +{ + unsigned int i, regWrites = 0; + + /* New INI format: Array may be undefined (pre, core, post arrays) */ + if (!iniArr->ia_array) + return; + + /* + * New INI format: Pre, core, and post arrays for a given subsystem + * may be modal (> 2 columns) or non-modal (2 columns). Determine if + * the array is non-modal and force the column to 1. + */ + if (column >= iniArr->ia_columns) + column = 1; + + for (i = 0; i < iniArr->ia_rows; i++) { + u32 reg = INI_RA(iniArr, i, 0); + u32 val = INI_RA(iniArr, i, column); + + REG_WRITE(ah, reg, val); + + DO_DELAY(regWrites); + } +} + +static int ar9550_hw_get_modes_txgain_index(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + int ret; + + switch (chan->chanmode) { + case CHANNEL_A: + case CHANNEL_A_HT20: + if (chan->channel <= 5350) + ret = 1; + else if ((chan->channel > 5350) && (chan->channel <= 5600)) + ret = 3; + else + ret = 5; + break; + + case CHANNEL_A_HT40PLUS: + case CHANNEL_A_HT40MINUS: + if (chan->channel <= 5350) + ret = 2; + else if ((chan->channel > 5350) && (chan->channel <= 5600)) + ret = 4; + else + ret = 6; + break; + + case CHANNEL_G: + case CHANNEL_G_HT20: + case CHANNEL_B: + ret = 8; + break; + + case CHANNEL_G_HT40PLUS: + case CHANNEL_G_HT40MINUS: + ret = 7; + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static int ar9003_hw_process_ini(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + unsigned int regWrites = 0, i; + u32 modesIndex; + + switch (chan->chanmode) { + case CHANNEL_A: + case CHANNEL_A_HT20: + modesIndex = 1; + break; + case CHANNEL_A_HT40PLUS: + case CHANNEL_A_HT40MINUS: + modesIndex = 2; + break; + case CHANNEL_G: + case CHANNEL_G_HT20: + case CHANNEL_B: + modesIndex = 4; + break; + case CHANNEL_G_HT40PLUS: + case CHANNEL_G_HT40MINUS: + modesIndex = 3; + break; + + default: + return -EINVAL; + } + + for (i = 0; i < ATH_INI_NUM_SPLIT; i++) { + ar9003_hw_prog_ini(ah, &ah->iniSOC[i], modesIndex); + ar9003_hw_prog_ini(ah, &ah->iniMac[i], modesIndex); + ar9003_hw_prog_ini(ah, &ah->iniBB[i], modesIndex); + ar9003_hw_prog_ini(ah, &ah->iniRadio[i], modesIndex); + if (i == ATH_INI_POST && AR_SREV_9462_20(ah)) + ar9003_hw_prog_ini(ah, + &ah->ini_radio_post_sys2ant, + modesIndex); + } + + REG_WRITE_ARRAY(&ah->iniModesRxGain, 1, regWrites); + if (AR_SREV_9550(ah)) + REG_WRITE_ARRAY(&ah->ini_modes_rx_gain_bounds, modesIndex, + regWrites); + + if (AR_SREV_9550(ah)) { + int modes_txgain_index; + + modes_txgain_index = ar9550_hw_get_modes_txgain_index(ah, chan); + if (modes_txgain_index < 0) + return -EINVAL; + + REG_WRITE_ARRAY(&ah->iniModesTxGain, modes_txgain_index, + regWrites); + } else { + REG_WRITE_ARRAY(&ah->iniModesTxGain, modesIndex, regWrites); + } + + /* + * For 5GHz channels requiring Fast Clock, apply + * different modal values. + */ + if (IS_CHAN_A_FAST_CLOCK(ah, chan)) + REG_WRITE_ARRAY(&ah->iniModesFastClock, + modesIndex, regWrites); + + REG_WRITE_ARRAY(&ah->iniAdditional, 1, regWrites); + + if (chan->channel == 2484) + ar9003_hw_prog_ini(ah, &ah->iniCckfirJapan2484, 1); + + if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) + REG_WRITE(ah, AR_GLB_SWREG_DISCONT_MODE, + AR_GLB_SWREG_DISCONT_EN_BT_WLAN); + + ah->modes_index = modesIndex; + ar9003_hw_override_ini(ah); + ar9003_hw_set_channel_regs(ah, chan); + ar9003_hw_set_chain_masks(ah, ah->rxchainmask, ah->txchainmask); + ath9k_hw_apply_txpower(ah, chan, false); + + if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) { + if (REG_READ_FIELD(ah, AR_PHY_TX_IQCAL_CONTROL_0, + AR_PHY_TX_IQCAL_CONTROL_0_ENABLE_TXIQ_CAL)) + ah->enabled_cals |= TX_IQ_CAL; + else + ah->enabled_cals &= ~TX_IQ_CAL; + + if (REG_READ(ah, AR_PHY_CL_CAL_CTL) & AR_PHY_CL_CAL_ENABLE) + ah->enabled_cals |= TX_CL_CAL; + else + ah->enabled_cals &= ~TX_CL_CAL; + } + + return 0; +} + +static void ar9003_hw_set_rfmode(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + u32 rfMode = 0; + + if (chan == NULL) + return; + + rfMode |= (IS_CHAN_B(chan) || IS_CHAN_G(chan)) + ? AR_PHY_MODE_DYNAMIC : AR_PHY_MODE_OFDM; + + if (IS_CHAN_A_FAST_CLOCK(ah, chan)) + rfMode |= (AR_PHY_MODE_DYNAMIC | AR_PHY_MODE_DYN_CCK_DISABLE); + if (IS_CHAN_QUARTER_RATE(chan)) + rfMode |= AR_PHY_MODE_QUARTER; + if (IS_CHAN_HALF_RATE(chan)) + rfMode |= AR_PHY_MODE_HALF; + + if (rfMode & (AR_PHY_MODE_QUARTER | AR_PHY_MODE_HALF)) + REG_RMW_FIELD(ah, AR_PHY_FRAME_CTL, + AR_PHY_FRAME_CTL_CF_OVERLAP_WINDOW, 3); + + REG_WRITE(ah, AR_PHY_MODE, rfMode); +} + +static void ar9003_hw_mark_phy_inactive(struct ath_hw *ah) +{ + REG_WRITE(ah, AR_PHY_ACTIVE, AR_PHY_ACTIVE_DIS); +} + +static void ar9003_hw_set_delta_slope(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + u32 coef_scaled, ds_coef_exp, ds_coef_man; + u32 clockMhzScaled = 0x64000000; + struct chan_centers centers; + + /* + * half and quarter rate can divide the scaled clock by 2 or 4 + * scale for selected channel bandwidth + */ + if (IS_CHAN_HALF_RATE(chan)) + clockMhzScaled = clockMhzScaled >> 1; + else if (IS_CHAN_QUARTER_RATE(chan)) + clockMhzScaled = clockMhzScaled >> 2; + + /* + * ALGO -> coef = 1e8/fcarrier*fclock/40; + * scaled coef to provide precision for this floating calculation + */ + ath9k_hw_get_channel_centers(ah, chan, ¢ers); + coef_scaled = clockMhzScaled / centers.synth_center; + + ath9k_hw_get_delta_slope_vals(ah, coef_scaled, &ds_coef_man, + &ds_coef_exp); + + REG_RMW_FIELD(ah, AR_PHY_TIMING3, + AR_PHY_TIMING3_DSC_MAN, ds_coef_man); + REG_RMW_FIELD(ah, AR_PHY_TIMING3, + AR_PHY_TIMING3_DSC_EXP, ds_coef_exp); + + /* + * For Short GI, + * scaled coeff is 9/10 that of normal coeff + */ + coef_scaled = (9 * coef_scaled) / 10; + + ath9k_hw_get_delta_slope_vals(ah, coef_scaled, &ds_coef_man, + &ds_coef_exp); + + /* for short gi */ + REG_RMW_FIELD(ah, AR_PHY_SGI_DELTA, + AR_PHY_SGI_DSC_MAN, ds_coef_man); + REG_RMW_FIELD(ah, AR_PHY_SGI_DELTA, + AR_PHY_SGI_DSC_EXP, ds_coef_exp); +} + +static bool ar9003_hw_rfbus_req(struct ath_hw *ah) +{ + REG_WRITE(ah, AR_PHY_RFBUS_REQ, AR_PHY_RFBUS_REQ_EN); + return ath9k_hw_wait(ah, AR_PHY_RFBUS_GRANT, AR_PHY_RFBUS_GRANT_EN, + AR_PHY_RFBUS_GRANT_EN, AH_WAIT_TIMEOUT); +} + +/* + * Wait for the frequency synth to settle (synth goes on via PHY_ACTIVE_EN). + * Read the phy active delay register. Value is in 100ns increments. + */ +static void ar9003_hw_rfbus_done(struct ath_hw *ah) +{ + u32 synthDelay = REG_READ(ah, AR_PHY_RX_DELAY) & AR_PHY_RX_DELAY_DELAY; + + ath9k_hw_synth_delay(ah, ah->curchan, synthDelay); + + REG_WRITE(ah, AR_PHY_RFBUS_REQ, 0); +} + +static bool ar9003_hw_ani_control(struct ath_hw *ah, + enum ath9k_ani_cmd cmd, int param) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_channel *chan = ah->curchan; + struct ar5416AniState *aniState = &chan->ani; + s32 value, value2; + + switch (cmd & ah->ani_function) { + case ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION:{ + /* + * on == 1 means ofdm weak signal detection is ON + * on == 1 is the default, for less noise immunity + * + * on == 0 means ofdm weak signal detection is OFF + * on == 0 means more noise imm + */ + u32 on = param ? 1 : 0; + + if (on) + REG_SET_BIT(ah, AR_PHY_SFCORR_LOW, + AR_PHY_SFCORR_LOW_USE_SELF_CORR_LOW); + else + REG_CLR_BIT(ah, AR_PHY_SFCORR_LOW, + AR_PHY_SFCORR_LOW_USE_SELF_CORR_LOW); + + if (on != aniState->ofdmWeakSigDetect) { + ath_dbg(common, ANI, + "** ch %d: ofdm weak signal: %s=>%s\n", + chan->channel, + aniState->ofdmWeakSigDetect ? + "on" : "off", + on ? "on" : "off"); + if (on) + ah->stats.ast_ani_ofdmon++; + else + ah->stats.ast_ani_ofdmoff++; + aniState->ofdmWeakSigDetect = on; + } + break; + } + case ATH9K_ANI_FIRSTEP_LEVEL:{ + u32 level = param; + + if (level >= ARRAY_SIZE(firstep_table)) { + ath_dbg(common, ANI, + "ATH9K_ANI_FIRSTEP_LEVEL: level out of range (%u > %zu)\n", + level, ARRAY_SIZE(firstep_table)); + return false; + } + + /* + * make register setting relative to default + * from INI file & cap value + */ + value = firstep_table[level] - + firstep_table[ATH9K_ANI_FIRSTEP_LVL] + + aniState->iniDef.firstep; + if (value < ATH9K_SIG_FIRSTEP_SETTING_MIN) + value = ATH9K_SIG_FIRSTEP_SETTING_MIN; + if (value > ATH9K_SIG_FIRSTEP_SETTING_MAX) + value = ATH9K_SIG_FIRSTEP_SETTING_MAX; + REG_RMW_FIELD(ah, AR_PHY_FIND_SIG, + AR_PHY_FIND_SIG_FIRSTEP, + value); + /* + * we need to set first step low register too + * make register setting relative to default + * from INI file & cap value + */ + value2 = firstep_table[level] - + firstep_table[ATH9K_ANI_FIRSTEP_LVL] + + aniState->iniDef.firstepLow; + if (value2 < ATH9K_SIG_FIRSTEP_SETTING_MIN) + value2 = ATH9K_SIG_FIRSTEP_SETTING_MIN; + if (value2 > ATH9K_SIG_FIRSTEP_SETTING_MAX) + value2 = ATH9K_SIG_FIRSTEP_SETTING_MAX; + + REG_RMW_FIELD(ah, AR_PHY_FIND_SIG_LOW, + AR_PHY_FIND_SIG_LOW_FIRSTEP_LOW, value2); + + if (level != aniState->firstepLevel) { + ath_dbg(common, ANI, + "** ch %d: level %d=>%d[def:%d] firstep[level]=%d ini=%d\n", + chan->channel, + aniState->firstepLevel, + level, + ATH9K_ANI_FIRSTEP_LVL, + value, + aniState->iniDef.firstep); + ath_dbg(common, ANI, + "** ch %d: level %d=>%d[def:%d] firstep_low[level]=%d ini=%d\n", + chan->channel, + aniState->firstepLevel, + level, + ATH9K_ANI_FIRSTEP_LVL, + value2, + aniState->iniDef.firstepLow); + if (level > aniState->firstepLevel) + ah->stats.ast_ani_stepup++; + else if (level < aniState->firstepLevel) + ah->stats.ast_ani_stepdown++; + aniState->firstepLevel = level; + } + break; + } + case ATH9K_ANI_SPUR_IMMUNITY_LEVEL:{ + u32 level = param; + + if (level >= ARRAY_SIZE(cycpwrThr1_table)) { + ath_dbg(common, ANI, + "ATH9K_ANI_SPUR_IMMUNITY_LEVEL: level out of range (%u > %zu)\n", + level, ARRAY_SIZE(cycpwrThr1_table)); + return false; + } + /* + * make register setting relative to default + * from INI file & cap value + */ + value = cycpwrThr1_table[level] - + cycpwrThr1_table[ATH9K_ANI_SPUR_IMMUNE_LVL] + + aniState->iniDef.cycpwrThr1; + if (value < ATH9K_SIG_SPUR_IMM_SETTING_MIN) + value = ATH9K_SIG_SPUR_IMM_SETTING_MIN; + if (value > ATH9K_SIG_SPUR_IMM_SETTING_MAX) + value = ATH9K_SIG_SPUR_IMM_SETTING_MAX; + REG_RMW_FIELD(ah, AR_PHY_TIMING5, + AR_PHY_TIMING5_CYCPWR_THR1, + value); + + /* + * set AR_PHY_EXT_CCA for extension channel + * make register setting relative to default + * from INI file & cap value + */ + value2 = cycpwrThr1_table[level] - + cycpwrThr1_table[ATH9K_ANI_SPUR_IMMUNE_LVL] + + aniState->iniDef.cycpwrThr1Ext; + if (value2 < ATH9K_SIG_SPUR_IMM_SETTING_MIN) + value2 = ATH9K_SIG_SPUR_IMM_SETTING_MIN; + if (value2 > ATH9K_SIG_SPUR_IMM_SETTING_MAX) + value2 = ATH9K_SIG_SPUR_IMM_SETTING_MAX; + REG_RMW_FIELD(ah, AR_PHY_EXT_CCA, + AR_PHY_EXT_CYCPWR_THR1, value2); + + if (level != aniState->spurImmunityLevel) { + ath_dbg(common, ANI, + "** ch %d: level %d=>%d[def:%d] cycpwrThr1[level]=%d ini=%d\n", + chan->channel, + aniState->spurImmunityLevel, + level, + ATH9K_ANI_SPUR_IMMUNE_LVL, + value, + aniState->iniDef.cycpwrThr1); + ath_dbg(common, ANI, + "** ch %d: level %d=>%d[def:%d] cycpwrThr1Ext[level]=%d ini=%d\n", + chan->channel, + aniState->spurImmunityLevel, + level, + ATH9K_ANI_SPUR_IMMUNE_LVL, + value2, + aniState->iniDef.cycpwrThr1Ext); + if (level > aniState->spurImmunityLevel) + ah->stats.ast_ani_spurup++; + else if (level < aniState->spurImmunityLevel) + ah->stats.ast_ani_spurdown++; + aniState->spurImmunityLevel = level; + } + break; + } + case ATH9K_ANI_MRC_CCK:{ + /* + * is_on == 1 means MRC CCK ON (default, less noise imm) + * is_on == 0 means MRC CCK is OFF (more noise imm) + */ + bool is_on = param ? 1 : 0; + + if (ah->caps.rx_chainmask == 1) + break; + + REG_RMW_FIELD(ah, AR_PHY_MRC_CCK_CTRL, + AR_PHY_MRC_CCK_ENABLE, is_on); + REG_RMW_FIELD(ah, AR_PHY_MRC_CCK_CTRL, + AR_PHY_MRC_CCK_MUX_REG, is_on); + if (is_on != aniState->mrcCCK) { + ath_dbg(common, ANI, "** ch %d: MRC CCK: %s=>%s\n", + chan->channel, + aniState->mrcCCK ? "on" : "off", + is_on ? "on" : "off"); + if (is_on) + ah->stats.ast_ani_ccklow++; + else + ah->stats.ast_ani_cckhigh++; + aniState->mrcCCK = is_on; + } + break; + } + case ATH9K_ANI_PRESENT: + break; + default: + ath_dbg(common, ANI, "invalid cmd %u\n", cmd); + return false; + } + + ath_dbg(common, ANI, + "ANI parameters: SI=%d, ofdmWS=%s FS=%d MRCcck=%s listenTime=%d ofdmErrs=%d cckErrs=%d\n", + aniState->spurImmunityLevel, + aniState->ofdmWeakSigDetect ? "on" : "off", + aniState->firstepLevel, + aniState->mrcCCK ? "on" : "off", + aniState->listenTime, + aniState->ofdmPhyErrCount, + aniState->cckPhyErrCount); + return true; +} + +static void ar9003_hw_do_getnf(struct ath_hw *ah, + int16_t nfarray[NUM_NF_READINGS]) +{ +#define AR_PHY_CH_MINCCA_PWR 0x1FF00000 +#define AR_PHY_CH_MINCCA_PWR_S 20 +#define AR_PHY_CH_EXT_MINCCA_PWR 0x01FF0000 +#define AR_PHY_CH_EXT_MINCCA_PWR_S 16 + + int16_t nf; + int i; + + for (i = 0; i < AR9300_MAX_CHAINS; i++) { + if (ah->rxchainmask & BIT(i)) { + nf = MS(REG_READ(ah, ah->nf_regs[i]), + AR_PHY_CH_MINCCA_PWR); + nfarray[i] = sign_extend32(nf, 8); + + if (IS_CHAN_HT40(ah->curchan)) { + u8 ext_idx = AR9300_MAX_CHAINS + i; + + nf = MS(REG_READ(ah, ah->nf_regs[ext_idx]), + AR_PHY_CH_EXT_MINCCA_PWR); + nfarray[ext_idx] = sign_extend32(nf, 8); + } + } + } +} + +static void ar9003_hw_set_nf_limits(struct ath_hw *ah) +{ + ah->nf_2g.max = AR_PHY_CCA_MAX_GOOD_VAL_9300_2GHZ; + ah->nf_2g.min = AR_PHY_CCA_MIN_GOOD_VAL_9300_2GHZ; + ah->nf_2g.nominal = AR_PHY_CCA_NOM_VAL_9300_2GHZ; + ah->nf_5g.max = AR_PHY_CCA_MAX_GOOD_VAL_9300_5GHZ; + ah->nf_5g.min = AR_PHY_CCA_MIN_GOOD_VAL_9300_5GHZ; + ah->nf_5g.nominal = AR_PHY_CCA_NOM_VAL_9300_5GHZ; + + if (AR_SREV_9330(ah)) + ah->nf_2g.nominal = AR_PHY_CCA_NOM_VAL_9330_2GHZ; + + if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) { + ah->nf_2g.min = AR_PHY_CCA_MIN_GOOD_VAL_9462_2GHZ; + ah->nf_2g.nominal = AR_PHY_CCA_NOM_VAL_9462_2GHZ; + ah->nf_5g.min = AR_PHY_CCA_MIN_GOOD_VAL_9462_5GHZ; + ah->nf_5g.nominal = AR_PHY_CCA_NOM_VAL_9462_5GHZ; + } +} + +/* + * Initialize the ANI register values with default (ini) values. + * This routine is called during a (full) hardware reset after + * all the registers are initialised from the INI. + */ +static void ar9003_hw_ani_cache_ini_regs(struct ath_hw *ah) +{ + struct ar5416AniState *aniState; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_channel *chan = ah->curchan; + struct ath9k_ani_default *iniDef; + u32 val; + + aniState = &ah->curchan->ani; + iniDef = &aniState->iniDef; + + ath_dbg(common, ANI, "ver %d.%d opmode %u chan %d Mhz/0x%x\n", + ah->hw_version.macVersion, + ah->hw_version.macRev, + ah->opmode, + chan->channel, + chan->channelFlags); + + val = REG_READ(ah, AR_PHY_SFCORR); + iniDef->m1Thresh = MS(val, AR_PHY_SFCORR_M1_THRESH); + iniDef->m2Thresh = MS(val, AR_PHY_SFCORR_M2_THRESH); + iniDef->m2CountThr = MS(val, AR_PHY_SFCORR_M2COUNT_THR); + + val = REG_READ(ah, AR_PHY_SFCORR_LOW); + iniDef->m1ThreshLow = MS(val, AR_PHY_SFCORR_LOW_M1_THRESH_LOW); + iniDef->m2ThreshLow = MS(val, AR_PHY_SFCORR_LOW_M2_THRESH_LOW); + iniDef->m2CountThrLow = MS(val, AR_PHY_SFCORR_LOW_M2COUNT_THR_LOW); + + val = REG_READ(ah, AR_PHY_SFCORR_EXT); + iniDef->m1ThreshExt = MS(val, AR_PHY_SFCORR_EXT_M1_THRESH); + iniDef->m2ThreshExt = MS(val, AR_PHY_SFCORR_EXT_M2_THRESH); + iniDef->m1ThreshLowExt = MS(val, AR_PHY_SFCORR_EXT_M1_THRESH_LOW); + iniDef->m2ThreshLowExt = MS(val, AR_PHY_SFCORR_EXT_M2_THRESH_LOW); + iniDef->firstep = REG_READ_FIELD(ah, + AR_PHY_FIND_SIG, + AR_PHY_FIND_SIG_FIRSTEP); + iniDef->firstepLow = REG_READ_FIELD(ah, + AR_PHY_FIND_SIG_LOW, + AR_PHY_FIND_SIG_LOW_FIRSTEP_LOW); + iniDef->cycpwrThr1 = REG_READ_FIELD(ah, + AR_PHY_TIMING5, + AR_PHY_TIMING5_CYCPWR_THR1); + iniDef->cycpwrThr1Ext = REG_READ_FIELD(ah, + AR_PHY_EXT_CCA, + AR_PHY_EXT_CYCPWR_THR1); + + /* these levels just got reset to defaults by the INI */ + aniState->spurImmunityLevel = ATH9K_ANI_SPUR_IMMUNE_LVL; + aniState->firstepLevel = ATH9K_ANI_FIRSTEP_LVL; + aniState->ofdmWeakSigDetect = ATH9K_ANI_USE_OFDM_WEAK_SIG; + aniState->mrcCCK = true; +} + +static void ar9003_hw_set_radar_params(struct ath_hw *ah, + struct ath_hw_radar_conf *conf) +{ + u32 radar_0 = 0, radar_1 = 0; + + if (!conf) { + REG_CLR_BIT(ah, AR_PHY_RADAR_0, AR_PHY_RADAR_0_ENA); + return; + } + + radar_0 |= AR_PHY_RADAR_0_ENA | AR_PHY_RADAR_0_FFT_ENA; + radar_0 |= SM(conf->fir_power, AR_PHY_RADAR_0_FIRPWR); + radar_0 |= SM(conf->radar_rssi, AR_PHY_RADAR_0_RRSSI); + radar_0 |= SM(conf->pulse_height, AR_PHY_RADAR_0_HEIGHT); + radar_0 |= SM(conf->pulse_rssi, AR_PHY_RADAR_0_PRSSI); + radar_0 |= SM(conf->pulse_inband, AR_PHY_RADAR_0_INBAND); + + radar_1 |= AR_PHY_RADAR_1_MAX_RRSSI; + radar_1 |= AR_PHY_RADAR_1_BLOCK_CHECK; + radar_1 |= SM(conf->pulse_maxlen, AR_PHY_RADAR_1_MAXLEN); + radar_1 |= SM(conf->pulse_inband_step, AR_PHY_RADAR_1_RELSTEP_THRESH); + radar_1 |= SM(conf->radar_inband, AR_PHY_RADAR_1_RELPWR_THRESH); + + REG_WRITE(ah, AR_PHY_RADAR_0, radar_0); + REG_WRITE(ah, AR_PHY_RADAR_1, radar_1); + if (conf->ext_channel) + REG_SET_BIT(ah, AR_PHY_RADAR_EXT, AR_PHY_RADAR_EXT_ENA); + else + REG_CLR_BIT(ah, AR_PHY_RADAR_EXT, AR_PHY_RADAR_EXT_ENA); +} + +static void ar9003_hw_set_radar_conf(struct ath_hw *ah) +{ + struct ath_hw_radar_conf *conf = &ah->radar_conf; + + conf->fir_power = -28; + conf->radar_rssi = 0; + conf->pulse_height = 10; + conf->pulse_rssi = 24; + conf->pulse_inband = 8; + conf->pulse_maxlen = 255; + conf->pulse_inband_step = 12; + conf->radar_inband = 8; +} + +static void ar9003_hw_antdiv_comb_conf_get(struct ath_hw *ah, + struct ath_hw_antcomb_conf *antconf) +{ + u32 regval; + + regval = REG_READ(ah, AR_PHY_MC_GAIN_CTRL); + antconf->main_lna_conf = (regval & AR_PHY_ANT_DIV_MAIN_LNACONF) >> + AR_PHY_ANT_DIV_MAIN_LNACONF_S; + antconf->alt_lna_conf = (regval & AR_PHY_ANT_DIV_ALT_LNACONF) >> + AR_PHY_ANT_DIV_ALT_LNACONF_S; + antconf->fast_div_bias = (regval & AR_PHY_ANT_FAST_DIV_BIAS) >> + AR_PHY_ANT_FAST_DIV_BIAS_S; + + if (AR_SREV_9330_11(ah)) { + antconf->lna1_lna2_delta = -9; + antconf->div_group = 1; + } else if (AR_SREV_9485(ah)) { + antconf->lna1_lna2_delta = -9; + antconf->div_group = 2; + } else if (AR_SREV_9565(ah)) { + antconf->lna1_lna2_delta = -3; + antconf->div_group = 3; + } else { + antconf->lna1_lna2_delta = -3; + antconf->div_group = 0; + } +} + +static void ar9003_hw_antdiv_comb_conf_set(struct ath_hw *ah, + struct ath_hw_antcomb_conf *antconf) +{ + u32 regval; + + regval = REG_READ(ah, AR_PHY_MC_GAIN_CTRL); + regval &= ~(AR_PHY_ANT_DIV_MAIN_LNACONF | + AR_PHY_ANT_DIV_ALT_LNACONF | + AR_PHY_ANT_FAST_DIV_BIAS | + AR_PHY_ANT_DIV_MAIN_GAINTB | + AR_PHY_ANT_DIV_ALT_GAINTB); + regval |= ((antconf->main_lna_conf << AR_PHY_ANT_DIV_MAIN_LNACONF_S) + & AR_PHY_ANT_DIV_MAIN_LNACONF); + regval |= ((antconf->alt_lna_conf << AR_PHY_ANT_DIV_ALT_LNACONF_S) + & AR_PHY_ANT_DIV_ALT_LNACONF); + regval |= ((antconf->fast_div_bias << AR_PHY_ANT_FAST_DIV_BIAS_S) + & AR_PHY_ANT_FAST_DIV_BIAS); + regval |= ((antconf->main_gaintb << AR_PHY_ANT_DIV_MAIN_GAINTB_S) + & AR_PHY_ANT_DIV_MAIN_GAINTB); + regval |= ((antconf->alt_gaintb << AR_PHY_ANT_DIV_ALT_GAINTB_S) + & AR_PHY_ANT_DIV_ALT_GAINTB); + + REG_WRITE(ah, AR_PHY_MC_GAIN_CTRL, regval); +} + +static void ar9003_hw_antctrl_shared_chain_lnadiv(struct ath_hw *ah, + bool enable) +{ + u8 ant_div_ctl1; + u32 regval; + + if (!AR_SREV_9565(ah)) + return; + + ah->shared_chain_lnadiv = enable; + ant_div_ctl1 = ah->eep_ops->get_eeprom(ah, EEP_ANT_DIV_CTL1); + + regval = REG_READ(ah, AR_PHY_MC_GAIN_CTRL); + regval &= (~AR_ANT_DIV_CTRL_ALL); + regval |= (ant_div_ctl1 & 0x3f) << AR_ANT_DIV_CTRL_ALL_S; + regval &= ~AR_PHY_ANT_DIV_LNADIV; + regval |= ((ant_div_ctl1 >> 6) & 0x1) << AR_PHY_ANT_DIV_LNADIV_S; + + if (enable) + regval |= AR_ANT_DIV_ENABLE; + + REG_WRITE(ah, AR_PHY_MC_GAIN_CTRL, regval); + + regval = REG_READ(ah, AR_PHY_CCK_DETECT); + regval &= ~AR_FAST_DIV_ENABLE; + regval |= ((ant_div_ctl1 >> 7) & 0x1) << AR_FAST_DIV_ENABLE_S; + + if (enable) + regval |= AR_FAST_DIV_ENABLE; + + REG_WRITE(ah, AR_PHY_CCK_DETECT, regval); + + if (enable) { + REG_SET_BIT(ah, AR_PHY_MC_GAIN_CTRL, + (1 << AR_PHY_ANT_SW_RX_PROT_S)); + if (ah->curchan && IS_CHAN_2GHZ(ah->curchan)) + REG_SET_BIT(ah, AR_PHY_RESTART, + AR_PHY_RESTART_ENABLE_DIV_M2FLAG); + REG_SET_BIT(ah, AR_BTCOEX_WL_LNADIV, + AR_BTCOEX_WL_LNADIV_FORCE_ON); + } else { + REG_CLR_BIT(ah, AR_PHY_MC_GAIN_CTRL, AR_ANT_DIV_ENABLE); + REG_CLR_BIT(ah, AR_PHY_MC_GAIN_CTRL, + (1 << AR_PHY_ANT_SW_RX_PROT_S)); + REG_CLR_BIT(ah, AR_PHY_CCK_DETECT, AR_FAST_DIV_ENABLE); + REG_CLR_BIT(ah, AR_BTCOEX_WL_LNADIV, + AR_BTCOEX_WL_LNADIV_FORCE_ON); + + regval = REG_READ(ah, AR_PHY_MC_GAIN_CTRL); + regval &= ~(AR_PHY_ANT_DIV_MAIN_LNACONF | + AR_PHY_ANT_DIV_ALT_LNACONF | + AR_PHY_ANT_DIV_MAIN_GAINTB | + AR_PHY_ANT_DIV_ALT_GAINTB); + regval |= (AR_PHY_ANT_DIV_LNA1 << AR_PHY_ANT_DIV_MAIN_LNACONF_S); + regval |= (AR_PHY_ANT_DIV_LNA2 << AR_PHY_ANT_DIV_ALT_LNACONF_S); + REG_WRITE(ah, AR_PHY_MC_GAIN_CTRL, regval); + } +} + +static int ar9003_hw_fast_chan_change(struct ath_hw *ah, + struct ath9k_channel *chan, + u8 *ini_reloaded) +{ + unsigned int regWrites = 0; + u32 modesIndex; + + switch (chan->chanmode) { + case CHANNEL_A: + case CHANNEL_A_HT20: + modesIndex = 1; + break; + case CHANNEL_A_HT40PLUS: + case CHANNEL_A_HT40MINUS: + modesIndex = 2; + break; + case CHANNEL_G: + case CHANNEL_G_HT20: + case CHANNEL_B: + modesIndex = 4; + break; + case CHANNEL_G_HT40PLUS: + case CHANNEL_G_HT40MINUS: + modesIndex = 3; + break; + + default: + return -EINVAL; + } + + if (modesIndex == ah->modes_index) { + *ini_reloaded = false; + goto set_rfmode; + } + + ar9003_hw_prog_ini(ah, &ah->iniSOC[ATH_INI_POST], modesIndex); + ar9003_hw_prog_ini(ah, &ah->iniMac[ATH_INI_POST], modesIndex); + ar9003_hw_prog_ini(ah, &ah->iniBB[ATH_INI_POST], modesIndex); + ar9003_hw_prog_ini(ah, &ah->iniRadio[ATH_INI_POST], modesIndex); + + if (AR_SREV_9462_20(ah)) + ar9003_hw_prog_ini(ah, &ah->ini_radio_post_sys2ant, + modesIndex); + + REG_WRITE_ARRAY(&ah->iniModesTxGain, modesIndex, regWrites); + + /* + * For 5GHz channels requiring Fast Clock, apply + * different modal values. + */ + if (IS_CHAN_A_FAST_CLOCK(ah, chan)) + REG_WRITE_ARRAY(&ah->iniModesFastClock, modesIndex, regWrites); + + if (AR_SREV_9565(ah)) + REG_WRITE_ARRAY(&ah->iniModesFastClock, 1, regWrites); + + REG_WRITE_ARRAY(&ah->iniAdditional, 1, regWrites); + + ah->modes_index = modesIndex; + *ini_reloaded = true; + +set_rfmode: + ar9003_hw_set_rfmode(ah, chan); + return 0; +} + +static void ar9003_hw_spectral_scan_config(struct ath_hw *ah, + struct ath_spec_scan *param) +{ + u8 count; + + if (!param->enabled) { + REG_CLR_BIT(ah, AR_PHY_SPECTRAL_SCAN, + AR_PHY_SPECTRAL_SCAN_ENABLE); + return; + } + + REG_SET_BIT(ah, AR_PHY_RADAR_0, AR_PHY_RADAR_0_FFT_ENA); + REG_SET_BIT(ah, AR_PHY_SPECTRAL_SCAN, AR_PHY_SPECTRAL_SCAN_ENABLE); + + /* on AR93xx and newer, count = 0 will make the the chip send + * spectral samples endlessly. Check if this really was intended, + * and fix otherwise. + */ + count = param->count; + if (param->endless) + count = 0; + else if (param->count == 0) + count = 1; + + if (param->short_repeat) + REG_SET_BIT(ah, AR_PHY_SPECTRAL_SCAN, + AR_PHY_SPECTRAL_SCAN_SHORT_REPEAT); + else + REG_CLR_BIT(ah, AR_PHY_SPECTRAL_SCAN, + AR_PHY_SPECTRAL_SCAN_SHORT_REPEAT); + + REG_RMW_FIELD(ah, AR_PHY_SPECTRAL_SCAN, + AR_PHY_SPECTRAL_SCAN_COUNT, count); + REG_RMW_FIELD(ah, AR_PHY_SPECTRAL_SCAN, + AR_PHY_SPECTRAL_SCAN_PERIOD, param->period); + REG_RMW_FIELD(ah, AR_PHY_SPECTRAL_SCAN, + AR_PHY_SPECTRAL_SCAN_FFT_PERIOD, param->fft_period); + + return; +} + +static void ar9003_hw_spectral_scan_trigger(struct ath_hw *ah) +{ + /* Activate spectral scan */ + REG_SET_BIT(ah, AR_PHY_SPECTRAL_SCAN, + AR_PHY_SPECTRAL_SCAN_ACTIVE); +} + +static void ar9003_hw_spectral_scan_wait(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + + /* Poll for spectral scan complete */ + if (!ath9k_hw_wait(ah, AR_PHY_SPECTRAL_SCAN, + AR_PHY_SPECTRAL_SCAN_ACTIVE, + 0, AH_WAIT_TIMEOUT)) { + ath_err(common, "spectral scan wait failed\n"); + return; + } +} + +void ar9003_hw_attach_phy_ops(struct ath_hw *ah) +{ + struct ath_hw_private_ops *priv_ops = ath9k_hw_private_ops(ah); + struct ath_hw_ops *ops = ath9k_hw_ops(ah); + static const u32 ar9300_cca_regs[6] = { + AR_PHY_CCA_0, + AR_PHY_CCA_1, + AR_PHY_CCA_2, + AR_PHY_EXT_CCA, + AR_PHY_EXT_CCA_1, + AR_PHY_EXT_CCA_2, + }; + + priv_ops->rf_set_freq = ar9003_hw_set_channel; + priv_ops->spur_mitigate_freq = ar9003_hw_spur_mitigate; + priv_ops->compute_pll_control = ar9003_hw_compute_pll_control; + priv_ops->set_channel_regs = ar9003_hw_set_channel_regs; + priv_ops->init_bb = ar9003_hw_init_bb; + priv_ops->process_ini = ar9003_hw_process_ini; + priv_ops->set_rfmode = ar9003_hw_set_rfmode; + priv_ops->mark_phy_inactive = ar9003_hw_mark_phy_inactive; + priv_ops->set_delta_slope = ar9003_hw_set_delta_slope; + priv_ops->rfbus_req = ar9003_hw_rfbus_req; + priv_ops->rfbus_done = ar9003_hw_rfbus_done; + priv_ops->ani_control = ar9003_hw_ani_control; + priv_ops->do_getnf = ar9003_hw_do_getnf; + priv_ops->ani_cache_ini_regs = ar9003_hw_ani_cache_ini_regs; + priv_ops->set_radar_params = ar9003_hw_set_radar_params; + priv_ops->fast_chan_change = ar9003_hw_fast_chan_change; + + ops->antdiv_comb_conf_get = ar9003_hw_antdiv_comb_conf_get; + ops->antdiv_comb_conf_set = ar9003_hw_antdiv_comb_conf_set; + ops->antctrl_shared_chain_lnadiv = ar9003_hw_antctrl_shared_chain_lnadiv; + ops->spectral_scan_config = ar9003_hw_spectral_scan_config; + ops->spectral_scan_trigger = ar9003_hw_spectral_scan_trigger; + ops->spectral_scan_wait = ar9003_hw_spectral_scan_wait; + + ar9003_hw_set_nf_limits(ah); + ar9003_hw_set_radar_conf(ah); + memcpy(ah->nf_regs, ar9300_cca_regs, sizeof(ah->nf_regs)); +} + +void ar9003_hw_bb_watchdog_config(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + u32 idle_tmo_ms = ah->bb_watchdog_timeout_ms; + u32 val, idle_count; + + if (!idle_tmo_ms) { + /* disable IRQ, disable chip-reset for BB panic */ + REG_WRITE(ah, AR_PHY_WATCHDOG_CTL_2, + REG_READ(ah, AR_PHY_WATCHDOG_CTL_2) & + ~(AR_PHY_WATCHDOG_RST_ENABLE | + AR_PHY_WATCHDOG_IRQ_ENABLE)); + + /* disable watchdog in non-IDLE mode, disable in IDLE mode */ + REG_WRITE(ah, AR_PHY_WATCHDOG_CTL_1, + REG_READ(ah, AR_PHY_WATCHDOG_CTL_1) & + ~(AR_PHY_WATCHDOG_NON_IDLE_ENABLE | + AR_PHY_WATCHDOG_IDLE_ENABLE)); + + ath_dbg(common, RESET, "Disabled BB Watchdog\n"); + return; + } + + /* enable IRQ, disable chip-reset for BB watchdog */ + val = REG_READ(ah, AR_PHY_WATCHDOG_CTL_2) & AR_PHY_WATCHDOG_CNTL2_MASK; + REG_WRITE(ah, AR_PHY_WATCHDOG_CTL_2, + (val | AR_PHY_WATCHDOG_IRQ_ENABLE) & + ~AR_PHY_WATCHDOG_RST_ENABLE); + + /* bound limit to 10 secs */ + if (idle_tmo_ms > 10000) + idle_tmo_ms = 10000; + + /* + * The time unit for watchdog event is 2^15 44/88MHz cycles. + * + * For HT20 we have a time unit of 2^15/44 MHz = .74 ms per tick + * For HT40 we have a time unit of 2^15/88 MHz = .37 ms per tick + * + * Given we use fast clock now in 5 GHz, these time units should + * be common for both 2 GHz and 5 GHz. + */ + idle_count = (100 * idle_tmo_ms) / 74; + if (ah->curchan && IS_CHAN_HT40(ah->curchan)) + idle_count = (100 * idle_tmo_ms) / 37; + + /* + * enable watchdog in non-IDLE mode, disable in IDLE mode, + * set idle time-out. + */ + REG_WRITE(ah, AR_PHY_WATCHDOG_CTL_1, + AR_PHY_WATCHDOG_NON_IDLE_ENABLE | + AR_PHY_WATCHDOG_IDLE_MASK | + (AR_PHY_WATCHDOG_NON_IDLE_MASK & (idle_count << 2))); + + ath_dbg(common, RESET, "Enabled BB Watchdog timeout (%u ms)\n", + idle_tmo_ms); +} + +void ar9003_hw_bb_watchdog_read(struct ath_hw *ah) +{ + /* + * we want to avoid printing in ISR context so we save the + * watchdog status to be printed later in bottom half context. + */ + ah->bb_watchdog_last_status = REG_READ(ah, AR_PHY_WATCHDOG_STATUS); + + /* + * the watchdog timer should reset on status read but to be sure + * sure we write 0 to the watchdog status bit. + */ + REG_WRITE(ah, AR_PHY_WATCHDOG_STATUS, + ah->bb_watchdog_last_status & ~AR_PHY_WATCHDOG_STATUS_CLR); +} + +void ar9003_hw_bb_watchdog_dbg_info(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + u32 status; + + if (likely(!(common->debug_mask & ATH_DBG_RESET))) + return; + + status = ah->bb_watchdog_last_status; + ath_dbg(common, RESET, + "\n==== BB update: BB status=0x%08x ====\n", status); + ath_dbg(common, RESET, + "** BB state: wd=%u det=%u rdar=%u rOFDM=%d rCCK=%u tOFDM=%u tCCK=%u agc=%u src=%u **\n", + MS(status, AR_PHY_WATCHDOG_INFO), + MS(status, AR_PHY_WATCHDOG_DET_HANG), + MS(status, AR_PHY_WATCHDOG_RADAR_SM), + MS(status, AR_PHY_WATCHDOG_RX_OFDM_SM), + MS(status, AR_PHY_WATCHDOG_RX_CCK_SM), + MS(status, AR_PHY_WATCHDOG_TX_OFDM_SM), + MS(status, AR_PHY_WATCHDOG_TX_CCK_SM), + MS(status, AR_PHY_WATCHDOG_AGC_SM), + MS(status, AR_PHY_WATCHDOG_SRCH_SM)); + + ath_dbg(common, RESET, "** BB WD cntl: cntl1=0x%08x cntl2=0x%08x **\n", + REG_READ(ah, AR_PHY_WATCHDOG_CTL_1), + REG_READ(ah, AR_PHY_WATCHDOG_CTL_2)); + ath_dbg(common, RESET, "** BB mode: BB_gen_controls=0x%08x **\n", + REG_READ(ah, AR_PHY_GEN_CTRL)); + +#define PCT(_field) (common->cc_survey._field * 100 / common->cc_survey.cycles) + if (common->cc_survey.cycles) + ath_dbg(common, RESET, + "** BB busy times: rx_clear=%d%%, rx_frame=%d%%, tx_frame=%d%% **\n", + PCT(rx_busy), PCT(rx_frame), PCT(tx_frame)); + + ath_dbg(common, RESET, "==== BB update: done ====\n\n"); +} +EXPORT_SYMBOL(ar9003_hw_bb_watchdog_dbg_info); + +void ar9003_hw_disable_phy_restart(struct ath_hw *ah) +{ + u32 val; + + /* While receiving unsupported rate frame rx state machine + * gets into a state 0xb and if phy_restart happens in that + * state, BB would go hang. If RXSM is in 0xb state after + * first bb panic, ensure to disable the phy_restart. + */ + if (!((MS(ah->bb_watchdog_last_status, + AR_PHY_WATCHDOG_RX_OFDM_SM) == 0xb) || + ah->bb_hang_rx_ofdm)) + return; + + ah->bb_hang_rx_ofdm = true; + val = REG_READ(ah, AR_PHY_RESTART); + val &= ~AR_PHY_RESTART_ENA; + + REG_WRITE(ah, AR_PHY_RESTART, val); +} +EXPORT_SYMBOL(ar9003_hw_disable_phy_restart); diff --git a/drivers/net/wireless/ath/ath9k/ar9003_phy.h b/drivers/net/wireless/ath/ath9k/ar9003_phy.h new file mode 100644 index 0000000..5013c73 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9003_phy.h @@ -0,0 +1,1322 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef AR9003_PHY_H +#define AR9003_PHY_H + +/* + * Channel Register Map + */ +#define AR_CHAN_BASE 0x9800 + +#define AR_PHY_TIMING1 (AR_CHAN_BASE + 0x0) +#define AR_PHY_TIMING2 (AR_CHAN_BASE + 0x4) +#define AR_PHY_TIMING3 (AR_CHAN_BASE + 0x8) +#define AR_PHY_TIMING4 (AR_CHAN_BASE + 0xc) +#define AR_PHY_TIMING5 (AR_CHAN_BASE + 0x10) +#define AR_PHY_TIMING6 (AR_CHAN_BASE + 0x14) +#define AR_PHY_TIMING11 (AR_CHAN_BASE + 0x18) +#define AR_PHY_SPUR_REG (AR_CHAN_BASE + 0x1c) +#define AR_PHY_RX_IQCAL_CORR_B0 (AR_CHAN_BASE + 0xdc) +#define AR_PHY_TX_IQCAL_CONTROL_3 (AR_CHAN_BASE + 0xb0) +#define AR_PHY_TIMING_CONTROL4_DO_GAIN_DC_IQ_CAL_SHIFT 16 + +#define AR_PHY_TIMING11_SPUR_FREQ_SD 0x3FF00000 +#define AR_PHY_TIMING11_SPUR_FREQ_SD_S 20 + +#define AR_PHY_TIMING11_SPUR_DELTA_PHASE 0x000FFFFF +#define AR_PHY_TIMING11_SPUR_DELTA_PHASE_S 0 + +#define AR_PHY_TIMING11_USE_SPUR_FILTER_IN_AGC 0x40000000 +#define AR_PHY_TIMING11_USE_SPUR_FILTER_IN_AGC_S 30 + +#define AR_PHY_TIMING11_USE_SPUR_FILTER_IN_SELFCOR 0x80000000 +#define AR_PHY_TIMING11_USE_SPUR_FILTER_IN_SELFCOR_S 31 + +#define AR_PHY_SPUR_REG_ENABLE_NF_RSSI_SPUR_MIT 0x4000000 +#define AR_PHY_SPUR_REG_ENABLE_NF_RSSI_SPUR_MIT_S 26 + +#define AR_PHY_SPUR_REG_ENABLE_MASK_PPM 0x20000 /* bins move with freq offset */ +#define AR_PHY_SPUR_REG_ENABLE_MASK_PPM_S 17 +#define AR_PHY_SPUR_REG_SPUR_RSSI_THRESH 0x000000FF +#define AR_PHY_SPUR_REG_SPUR_RSSI_THRESH_S 0 +#define AR_PHY_SPUR_REG_EN_VIT_SPUR_RSSI 0x00000100 +#define AR_PHY_SPUR_REG_EN_VIT_SPUR_RSSI_S 8 +#define AR_PHY_SPUR_REG_MASK_RATE_CNTL 0x03FC0000 +#define AR_PHY_SPUR_REG_MASK_RATE_CNTL_S 18 + +#define AR_PHY_RX_IQCAL_CORR_B0_LOOPBACK_IQCORR_EN 0x20000000 +#define AR_PHY_RX_IQCAL_CORR_B0_LOOPBACK_IQCORR_EN_S 29 + +#define AR_PHY_TX_IQCAL_CONTROL_3_IQCORR_EN 0x80000000 +#define AR_PHY_TX_IQCAL_CONTROL_3_IQCORR_EN_S 31 + +#define AR_PHY_FIND_SIG_LOW (AR_CHAN_BASE + 0x20) + +#define AR_PHY_SFCORR (AR_CHAN_BASE + 0x24) +#define AR_PHY_SFCORR_LOW (AR_CHAN_BASE + 0x28) +#define AR_PHY_SFCORR_EXT (AR_CHAN_BASE + 0x2c) + +#define AR_PHY_EXT_CCA (AR_CHAN_BASE + 0x30) +#define AR_PHY_RADAR_0 (AR_CHAN_BASE + 0x34) +#define AR_PHY_RADAR_1 (AR_CHAN_BASE + 0x38) +#define AR_PHY_RADAR_EXT (AR_CHAN_BASE + 0x3c) +#define AR_PHY_MULTICHAIN_CTRL (AR_CHAN_BASE + 0x80) +#define AR_PHY_PERCHAIN_CSD (AR_CHAN_BASE + 0x84) + +#define AR_PHY_TX_PHASE_RAMP_0 (AR_CHAN_BASE + 0xd0) +#define AR_PHY_ADC_GAIN_DC_CORR_0 (AR_CHAN_BASE + 0xd4) +#define AR_PHY_IQ_ADC_MEAS_0_B0 (AR_CHAN_BASE + 0xc0) +#define AR_PHY_IQ_ADC_MEAS_1_B0 (AR_CHAN_BASE + 0xc4) +#define AR_PHY_IQ_ADC_MEAS_2_B0 (AR_CHAN_BASE + 0xc8) +#define AR_PHY_IQ_ADC_MEAS_3_B0 (AR_CHAN_BASE + 0xcc) + +/* The following registers changed position from AR9300 1.0 to AR9300 2.0 */ +#define AR_PHY_TX_PHASE_RAMP_0_9300_10 (AR_CHAN_BASE + 0xd0 - 0x10) +#define AR_PHY_ADC_GAIN_DC_CORR_0_9300_10 (AR_CHAN_BASE + 0xd4 - 0x10) +#define AR_PHY_IQ_ADC_MEAS_0_B0_9300_10 (AR_CHAN_BASE + 0xc0 + 0x8) +#define AR_PHY_IQ_ADC_MEAS_1_B0_9300_10 (AR_CHAN_BASE + 0xc4 + 0x8) +#define AR_PHY_IQ_ADC_MEAS_2_B0_9300_10 (AR_CHAN_BASE + 0xc8 + 0x8) +#define AR_PHY_IQ_ADC_MEAS_3_B0_9300_10 (AR_CHAN_BASE + 0xcc + 0x8) + +#define AR_PHY_TX_CRC (AR_CHAN_BASE + 0xa0) +#define AR_PHY_TST_DAC_CONST (AR_CHAN_BASE + 0xa4) +#define AR_PHY_SPUR_REPORT_0 (AR_CHAN_BASE + 0xa8) +#define AR_PHY_CHAN_INFO_TAB_0 (AR_CHAN_BASE + 0x300) + +/* + * Channel Field Definitions + */ +#define AR_PHY_TIMING2_USE_FORCE_PPM 0x00001000 +#define AR_PHY_TIMING2_FORCE_PPM_VAL 0x00000fff +#define AR_PHY_TIMING3_DSC_MAN 0xFFFE0000 +#define AR_PHY_TIMING3_DSC_MAN_S 17 +#define AR_PHY_TIMING3_DSC_EXP 0x0001E000 +#define AR_PHY_TIMING3_DSC_EXP_S 13 +#define AR_PHY_TIMING4_IQCAL_LOG_COUNT_MAX 0xF000 +#define AR_PHY_TIMING4_IQCAL_LOG_COUNT_MAX_S 12 +#define AR_PHY_TIMING4_DO_CAL 0x10000 + +#define AR_PHY_TIMING4_ENABLE_PILOT_MASK 0x10000000 +#define AR_PHY_TIMING4_ENABLE_PILOT_MASK_S 28 +#define AR_PHY_TIMING4_ENABLE_CHAN_MASK 0x20000000 +#define AR_PHY_TIMING4_ENABLE_CHAN_MASK_S 29 + +#define AR_PHY_TIMING4_ENABLE_SPUR_FILTER 0x40000000 +#define AR_PHY_TIMING4_ENABLE_SPUR_FILTER_S 30 +#define AR_PHY_TIMING4_ENABLE_SPUR_RSSI 0x80000000 +#define AR_PHY_TIMING4_ENABLE_SPUR_RSSI_S 31 + +#define AR_PHY_NEW_ADC_GAIN_CORR_ENABLE 0x40000000 +#define AR_PHY_NEW_ADC_DC_OFFSET_CORR_ENABLE 0x80000000 +#define AR_PHY_SFCORR_LOW_USE_SELF_CORR_LOW 0x00000001 +#define AR_PHY_SFCORR_LOW_M2COUNT_THR_LOW 0x00003F00 +#define AR_PHY_SFCORR_LOW_M2COUNT_THR_LOW_S 8 +#define AR_PHY_SFCORR_LOW_M1_THRESH_LOW 0x001FC000 +#define AR_PHY_SFCORR_LOW_M1_THRESH_LOW_S 14 +#define AR_PHY_SFCORR_LOW_M2_THRESH_LOW 0x0FE00000 +#define AR_PHY_SFCORR_LOW_M2_THRESH_LOW_S 21 +#define AR_PHY_SFCORR_M2COUNT_THR 0x0000001F +#define AR_PHY_SFCORR_M2COUNT_THR_S 0 +#define AR_PHY_SFCORR_M1_THRESH 0x00FE0000 +#define AR_PHY_SFCORR_M1_THRESH_S 17 +#define AR_PHY_SFCORR_M2_THRESH 0x7F000000 +#define AR_PHY_SFCORR_M2_THRESH_S 24 +#define AR_PHY_SFCORR_EXT_M1_THRESH 0x0000007F +#define AR_PHY_SFCORR_EXT_M1_THRESH_S 0 +#define AR_PHY_SFCORR_EXT_M2_THRESH 0x00003F80 +#define AR_PHY_SFCORR_EXT_M2_THRESH_S 7 +#define AR_PHY_SFCORR_EXT_M1_THRESH_LOW 0x001FC000 +#define AR_PHY_SFCORR_EXT_M1_THRESH_LOW_S 14 +#define AR_PHY_SFCORR_EXT_M2_THRESH_LOW 0x0FE00000 +#define AR_PHY_SFCORR_EXT_M2_THRESH_LOW_S 21 +#define AR_PHY_SFCORR_EXT_SPUR_SUBCHANNEL_SD 0x10000000 +#define AR_PHY_SFCORR_EXT_SPUR_SUBCHANNEL_SD_S 28 +#define AR_PHY_SFCORR_SPUR_SUBCHNL_SD_S 28 +#define AR_PHY_EXT_CCA_THRESH62 0x007F0000 +#define AR_PHY_EXT_CCA_THRESH62_S 16 +#define AR_PHY_EXT_MINCCA_PWR 0x01FF0000 +#define AR_PHY_EXT_MINCCA_PWR_S 16 +#define AR_PHY_EXT_CYCPWR_THR1 0x0000FE00L +#define AR_PHY_EXT_CYCPWR_THR1_S 9 +#define AR_PHY_TIMING5_CYCPWR_THR1 0x000000FE +#define AR_PHY_TIMING5_CYCPWR_THR1_S 1 +#define AR_PHY_TIMING5_CYCPWR_THR1_ENABLE 0x00000001 +#define AR_PHY_TIMING5_CYCPWR_THR1_ENABLE_S 0 +#define AR_PHY_TIMING5_CYCPWR_THR1A 0x007F0000 +#define AR_PHY_TIMING5_CYCPWR_THR1A_S 16 +#define AR_PHY_TIMING5_RSSI_THR1A (0x7F << 16) +#define AR_PHY_TIMING5_RSSI_THR1A_S 16 +#define AR_PHY_TIMING5_RSSI_THR1A_ENA (0x1 << 15) +#define AR_PHY_RADAR_0_ENA 0x00000001 +#define AR_PHY_RADAR_0_FFT_ENA 0x80000000 +#define AR_PHY_RADAR_0_INBAND 0x0000003e +#define AR_PHY_RADAR_0_INBAND_S 1 +#define AR_PHY_RADAR_0_PRSSI 0x00000FC0 +#define AR_PHY_RADAR_0_PRSSI_S 6 +#define AR_PHY_RADAR_0_HEIGHT 0x0003F000 +#define AR_PHY_RADAR_0_HEIGHT_S 12 +#define AR_PHY_RADAR_0_RRSSI 0x00FC0000 +#define AR_PHY_RADAR_0_RRSSI_S 18 +#define AR_PHY_RADAR_0_FIRPWR 0x7F000000 +#define AR_PHY_RADAR_0_FIRPWR_S 24 +#define AR_PHY_RADAR_1_RELPWR_ENA 0x00800000 +#define AR_PHY_RADAR_1_USE_FIR128 0x00400000 +#define AR_PHY_RADAR_1_RELPWR_THRESH 0x003F0000 +#define AR_PHY_RADAR_1_RELPWR_THRESH_S 16 +#define AR_PHY_RADAR_1_BLOCK_CHECK 0x00008000 +#define AR_PHY_RADAR_1_MAX_RRSSI 0x00004000 +#define AR_PHY_RADAR_1_RELSTEP_CHECK 0x00002000 +#define AR_PHY_RADAR_1_RELSTEP_THRESH 0x00001F00 +#define AR_PHY_RADAR_1_RELSTEP_THRESH_S 8 +#define AR_PHY_RADAR_1_MAXLEN 0x000000FF +#define AR_PHY_RADAR_1_MAXLEN_S 0 +#define AR_PHY_RADAR_EXT_ENA 0x00004000 +#define AR_PHY_RADAR_DC_PWR_THRESH 0x007f8000 +#define AR_PHY_RADAR_DC_PWR_THRESH_S 15 +#define AR_PHY_RADAR_LB_DC_CAP 0x7f800000 +#define AR_PHY_RADAR_LB_DC_CAP_S 23 +#define AR_PHY_FIND_SIG_LOW_FIRSTEP_LOW (0x3f << 6) +#define AR_PHY_FIND_SIG_LOW_FIRSTEP_LOW_S 6 +#define AR_PHY_FIND_SIG_LOW_FIRPWR (0x7f << 12) +#define AR_PHY_FIND_SIG_LOW_FIRPWR_S 12 +#define AR_PHY_FIND_SIG_LOW_FIRPWR_SIGN_BIT 19 +#define AR_PHY_FIND_SIG_LOW_RELSTEP 0x1f +#define AR_PHY_FIND_SIG_LOW_RELSTEP_S 0 +#define AR_PHY_FIND_SIG_LOW_RELSTEP_SIGN_BIT 5 +#define AR_PHY_CHAN_INFO_TAB_S2_READ 0x00000008 +#define AR_PHY_CHAN_INFO_TAB_S2_READ_S 3 +#define AR_PHY_RX_IQCAL_CORR_IQCORR_Q_Q_COFF 0x0000007F +#define AR_PHY_RX_IQCAL_CORR_IQCORR_Q_Q_COFF_S 0 +#define AR_PHY_RX_IQCAL_CORR_IQCORR_Q_I_COFF 0x00003F80 +#define AR_PHY_RX_IQCAL_CORR_IQCORR_Q_I_COFF_S 7 +#define AR_PHY_RX_IQCAL_CORR_IQCORR_ENABLE 0x00004000 +#define AR_PHY_RX_IQCAL_CORR_LOOPBACK_IQCORR_Q_Q_COFF 0x003f8000 +#define AR_PHY_RX_IQCAL_CORR_LOOPBACK_IQCORR_Q_Q_COFF_S 15 +#define AR_PHY_RX_IQCAL_CORR_LOOPBACK_IQCORR_Q_I_COFF 0x1fc00000 +#define AR_PHY_RX_IQCAL_CORR_LOOPBACK_IQCORR_Q_I_COFF_S 22 + +/* + * MRC Register Map + */ +#define AR_MRC_BASE 0x9c00 + +#define AR_PHY_TIMING_3A (AR_MRC_BASE + 0x0) +#define AR_PHY_LDPC_CNTL1 (AR_MRC_BASE + 0x4) +#define AR_PHY_LDPC_CNTL2 (AR_MRC_BASE + 0x8) +#define AR_PHY_PILOT_SPUR_MASK (AR_MRC_BASE + 0xc) +#define AR_PHY_CHAN_SPUR_MASK (AR_MRC_BASE + 0x10) +#define AR_PHY_SGI_DELTA (AR_MRC_BASE + 0x14) +#define AR_PHY_ML_CNTL_1 (AR_MRC_BASE + 0x18) +#define AR_PHY_ML_CNTL_2 (AR_MRC_BASE + 0x1c) +#define AR_PHY_TST_ADC (AR_MRC_BASE + 0x20) + +#define AR_PHY_PILOT_SPUR_MASK_CF_PILOT_MASK_IDX_A 0x00000FE0 +#define AR_PHY_PILOT_SPUR_MASK_CF_PILOT_MASK_IDX_A_S 5 +#define AR_PHY_PILOT_SPUR_MASK_CF_PILOT_MASK_A 0x1F +#define AR_PHY_PILOT_SPUR_MASK_CF_PILOT_MASK_A_S 0 +#define AR_PHY_PILOT_SPUR_MASK_CF_PILOT_MASK_IDX_B 0x00FE0000 +#define AR_PHY_PILOT_SPUR_MASK_CF_PILOT_MASK_IDX_B_S 17 +#define AR_PHY_PILOT_SPUR_MASK_CF_PILOT_MASK_B 0x0001F000 +#define AR_PHY_PILOT_SPUR_MASK_CF_PILOT_MASK_B_S 12 + +#define AR_PHY_CHAN_SPUR_MASK_CF_CHAN_MASK_IDX_A 0x00000FE0 +#define AR_PHY_CHAN_SPUR_MASK_CF_CHAN_MASK_IDX_A_S 5 +#define AR_PHY_CHAN_SPUR_MASK_CF_CHAN_MASK_A 0x1F +#define AR_PHY_CHAN_SPUR_MASK_CF_CHAN_MASK_A_S 0 +#define AR_PHY_CHAN_SPUR_MASK_CF_CHAN_MASK_IDX_B 0x00FE0000 +#define AR_PHY_CHAN_SPUR_MASK_CF_CHAN_MASK_IDX_B_S 17 +#define AR_PHY_CHAN_SPUR_MASK_CF_CHAN_MASK_B 0x0001F000 +#define AR_PHY_CHAN_SPUR_MASK_CF_CHAN_MASK_B_S 12 + + +/* + * MRC Feild Definitions + */ +#define AR_PHY_SGI_DSC_MAN 0x0007FFF0 +#define AR_PHY_SGI_DSC_MAN_S 4 +#define AR_PHY_SGI_DSC_EXP 0x0000000F +#define AR_PHY_SGI_DSC_EXP_S 0 +/* + * BBB Register Map + */ +#define AR_BBB_BASE 0x9d00 + +/* + * AGC Register Map + */ +#define AR_AGC_BASE 0x9e00 + +#define AR_PHY_SETTLING (AR_AGC_BASE + 0x0) +#define AR_PHY_FORCEMAX_GAINS_0 (AR_AGC_BASE + 0x4) +#define AR_PHY_GAINS_MINOFF0 (AR_AGC_BASE + 0x8) +#define AR_PHY_DESIRED_SZ (AR_AGC_BASE + 0xc) +#define AR_PHY_FIND_SIG (AR_AGC_BASE + 0x10) +#define AR_PHY_AGC (AR_AGC_BASE + 0x14) +#define AR_PHY_EXT_ATTEN_CTL_0 (AR_AGC_BASE + 0x18) +#define AR_PHY_CCA_0 (AR_AGC_BASE + 0x1c) +#define AR_PHY_EXT_CCA0 (AR_AGC_BASE + 0x20) +#define AR_PHY_RESTART (AR_AGC_BASE + 0x24) + +/* + * Antenna Diversity settings + */ +#define AR_PHY_MC_GAIN_CTRL (AR_AGC_BASE + 0x28) +#define AR_ANT_DIV_CTRL_ALL 0x7e000000 +#define AR_ANT_DIV_CTRL_ALL_S 25 +#define AR_ANT_DIV_ENABLE 0x1000000 +#define AR_ANT_DIV_ENABLE_S 24 + + +#define AR_PHY_ANT_FAST_DIV_BIAS 0x00007e00 +#define AR_PHY_ANT_FAST_DIV_BIAS_S 9 +#define AR_PHY_ANT_SW_RX_PROT 0x00800000 +#define AR_PHY_ANT_SW_RX_PROT_S 23 +#define AR_PHY_ANT_DIV_LNADIV 0x01000000 +#define AR_PHY_ANT_DIV_LNADIV_S 24 +#define AR_PHY_ANT_DIV_ALT_LNACONF 0x06000000 +#define AR_PHY_ANT_DIV_ALT_LNACONF_S 25 +#define AR_PHY_ANT_DIV_MAIN_LNACONF 0x18000000 +#define AR_PHY_ANT_DIV_MAIN_LNACONF_S 27 +#define AR_PHY_ANT_DIV_ALT_GAINTB 0x20000000 +#define AR_PHY_ANT_DIV_ALT_GAINTB_S 29 +#define AR_PHY_ANT_DIV_MAIN_GAINTB 0x40000000 +#define AR_PHY_ANT_DIV_MAIN_GAINTB_S 30 + +#define AR_PHY_ANT_DIV_LNA1_MINUS_LNA2 0x0 +#define AR_PHY_ANT_DIV_LNA2 0x1 +#define AR_PHY_ANT_DIV_LNA1 0x2 +#define AR_PHY_ANT_DIV_LNA1_PLUS_LNA2 0x3 + +#define AR_PHY_EXTCHN_PWRTHR1 (AR_AGC_BASE + 0x2c) +#define AR_PHY_EXT_CHN_WIN (AR_AGC_BASE + 0x30) +#define AR_PHY_20_40_DET_THR (AR_AGC_BASE + 0x34) +#define AR_PHY_RIFS_SRCH (AR_AGC_BASE + 0x38) +#define AR_PHY_PEAK_DET_CTRL_1 (AR_AGC_BASE + 0x3c) +#define AR_PHY_PEAK_DET_CTRL_2 (AR_AGC_BASE + 0x40) +#define AR_PHY_RX_GAIN_BOUNDS_1 (AR_AGC_BASE + 0x44) +#define AR_PHY_RX_GAIN_BOUNDS_2 (AR_AGC_BASE + 0x48) +#define AR_PHY_RSSI_0 (AR_AGC_BASE + 0x180) +#define AR_PHY_SPUR_CCK_REP0 (AR_AGC_BASE + 0x184) + +#define AR_PHY_CCK_DETECT (AR_AGC_BASE + 0x1c0) +#define AR_FAST_DIV_ENABLE 0x2000 +#define AR_FAST_DIV_ENABLE_S 13 + +#define AR_PHY_DAG_CTRLCCK (AR_AGC_BASE + 0x1c4) +#define AR_PHY_IQCORR_CTRL_CCK (AR_AGC_BASE + 0x1c8) + +#define AR_PHY_CCK_SPUR_MIT (AR_AGC_BASE + 0x1cc) +#define AR_PHY_CCK_SPUR_MIT_SPUR_RSSI_THR 0x000001fe +#define AR_PHY_CCK_SPUR_MIT_SPUR_RSSI_THR_S 1 +#define AR_PHY_CCK_SPUR_MIT_SPUR_FILTER_TYPE 0x60000000 +#define AR_PHY_CCK_SPUR_MIT_SPUR_FILTER_TYPE_S 29 +#define AR_PHY_CCK_SPUR_MIT_USE_CCK_SPUR_MIT 0x00000001 +#define AR_PHY_CCK_SPUR_MIT_USE_CCK_SPUR_MIT_S 0 +#define AR_PHY_CCK_SPUR_MIT_CCK_SPUR_FREQ 0x1ffffe00 +#define AR_PHY_CCK_SPUR_MIT_CCK_SPUR_FREQ_S 9 + +#define AR_PHY_MRC_CCK_CTRL (AR_AGC_BASE + 0x1d0) +#define AR_PHY_MRC_CCK_ENABLE 0x00000001 +#define AR_PHY_MRC_CCK_ENABLE_S 0 +#define AR_PHY_MRC_CCK_MUX_REG 0x00000002 +#define AR_PHY_MRC_CCK_MUX_REG_S 1 + +#define AR_PHY_RX_OCGAIN (AR_AGC_BASE + 0x200) + +#define AR_PHY_CCA_NOM_VAL_9300_2GHZ -110 +#define AR_PHY_CCA_NOM_VAL_9300_5GHZ -115 +#define AR_PHY_CCA_MIN_GOOD_VAL_9300_2GHZ -125 +#define AR_PHY_CCA_MIN_GOOD_VAL_9300_5GHZ -125 +#define AR_PHY_CCA_MAX_GOOD_VAL_9300_2GHZ -95 +#define AR_PHY_CCA_MAX_GOOD_VAL_9300_5GHZ -100 + +#define AR_PHY_CCA_NOM_VAL_9462_2GHZ -127 +#define AR_PHY_CCA_MIN_GOOD_VAL_9462_2GHZ -127 +#define AR_PHY_CCA_NOM_VAL_9462_5GHZ -127 +#define AR_PHY_CCA_MIN_GOOD_VAL_9462_5GHZ -127 + +#define AR_PHY_CCA_NOM_VAL_9330_2GHZ -118 + +#define AR9300_EXT_LNA_CTL_GPIO_AR9485 9 + +/* + * AGC Field Definitions + */ +#define AR_PHY_EXT_ATTEN_CTL_RXTX_MARGIN 0x00FC0000 +#define AR_PHY_EXT_ATTEN_CTL_RXTX_MARGIN_S 18 +#define AR_PHY_EXT_ATTEN_CTL_BSW_MARGIN 0x00003C00 +#define AR_PHY_EXT_ATTEN_CTL_BSW_MARGIN_S 10 +#define AR_PHY_EXT_ATTEN_CTL_BSW_ATTEN 0x0000001F +#define AR_PHY_EXT_ATTEN_CTL_BSW_ATTEN_S 0 +#define AR_PHY_EXT_ATTEN_CTL_XATTEN2_MARGIN 0x003E0000 +#define AR_PHY_EXT_ATTEN_CTL_XATTEN2_MARGIN_S 17 +#define AR_PHY_EXT_ATTEN_CTL_XATTEN1_MARGIN 0x0001F000 +#define AR_PHY_EXT_ATTEN_CTL_XATTEN1_MARGIN_S 12 +#define AR_PHY_EXT_ATTEN_CTL_XATTEN2_DB 0x00000FC0 +#define AR_PHY_EXT_ATTEN_CTL_XATTEN2_DB_S 6 +#define AR_PHY_EXT_ATTEN_CTL_XATTEN1_DB 0x0000003F +#define AR_PHY_EXT_ATTEN_CTL_XATTEN1_DB_S 0 +#define AR_PHY_RXGAIN_TXRX_ATTEN 0x0003F000 +#define AR_PHY_RXGAIN_TXRX_ATTEN_S 12 +#define AR_PHY_RXGAIN_TXRX_RF_MAX 0x007C0000 +#define AR_PHY_RXGAIN_TXRX_RF_MAX_S 18 +#define AR9280_PHY_RXGAIN_TXRX_ATTEN 0x00003F80 +#define AR9280_PHY_RXGAIN_TXRX_ATTEN_S 7 +#define AR9280_PHY_RXGAIN_TXRX_MARGIN 0x001FC000 +#define AR9280_PHY_RXGAIN_TXRX_MARGIN_S 14 +#define AR_PHY_SETTLING_SWITCH 0x00003F80 +#define AR_PHY_SETTLING_SWITCH_S 7 +#define AR_PHY_DESIRED_SZ_ADC 0x000000FF +#define AR_PHY_DESIRED_SZ_ADC_S 0 +#define AR_PHY_DESIRED_SZ_PGA 0x0000FF00 +#define AR_PHY_DESIRED_SZ_PGA_S 8 +#define AR_PHY_DESIRED_SZ_TOT_DES 0x0FF00000 +#define AR_PHY_DESIRED_SZ_TOT_DES_S 20 +#define AR_PHY_MINCCA_PWR 0x1FF00000 +#define AR_PHY_MINCCA_PWR_S 20 +#define AR_PHY_CCA_THRESH62 0x0007F000 +#define AR_PHY_CCA_THRESH62_S 12 +#define AR9280_PHY_MINCCA_PWR 0x1FF00000 +#define AR9280_PHY_MINCCA_PWR_S 20 +#define AR9280_PHY_CCA_THRESH62 0x000FF000 +#define AR9280_PHY_CCA_THRESH62_S 12 +#define AR_PHY_EXT_CCA0_THRESH62 0x000000FF +#define AR_PHY_EXT_CCA0_THRESH62_S 0 +#define AR_PHY_CCK_DETECT_WEAK_SIG_THR_CCK 0x0000003F +#define AR_PHY_CCK_DETECT_WEAK_SIG_THR_CCK_S 0 +#define AR_PHY_CCK_DETECT_ANT_SWITCH_TIME 0x00001FC0 +#define AR_PHY_CCK_DETECT_ANT_SWITCH_TIME_S 6 +#define AR_PHY_CCK_DETECT_BB_ENABLE_ANT_FAST_DIV 0x2000 + +#define AR_PHY_DAG_CTRLCCK_EN_RSSI_THR 0x00000200 +#define AR_PHY_DAG_CTRLCCK_EN_RSSI_THR_S 9 +#define AR_PHY_DAG_CTRLCCK_RSSI_THR 0x0001FC00 +#define AR_PHY_DAG_CTRLCCK_RSSI_THR_S 10 + +#define AR_PHY_RIFS_INIT_DELAY 0x3ff0000 +#define AR_PHY_AGC_QUICK_DROP 0x03c00000 +#define AR_PHY_AGC_QUICK_DROP_S 22 +#define AR_PHY_AGC_COARSE_LOW 0x00007F80 +#define AR_PHY_AGC_COARSE_LOW_S 7 +#define AR_PHY_AGC_COARSE_HIGH 0x003F8000 +#define AR_PHY_AGC_COARSE_HIGH_S 15 +#define AR_PHY_AGC_COARSE_PWR_CONST 0x0000007F +#define AR_PHY_AGC_COARSE_PWR_CONST_S 0 +#define AR_PHY_FIND_SIG_FIRSTEP 0x0003F000 +#define AR_PHY_FIND_SIG_FIRSTEP_S 12 +#define AR_PHY_FIND_SIG_FIRPWR 0x03FC0000 +#define AR_PHY_FIND_SIG_FIRPWR_S 18 +#define AR_PHY_FIND_SIG_FIRPWR_SIGN_BIT 25 +#define AR_PHY_FIND_SIG_RELPWR (0x1f << 6) +#define AR_PHY_FIND_SIG_RELPWR_S 6 +#define AR_PHY_FIND_SIG_RELPWR_SIGN_BIT 11 +#define AR_PHY_FIND_SIG_RELSTEP 0x1f +#define AR_PHY_FIND_SIG_RELSTEP_S 0 +#define AR_PHY_FIND_SIG_RELSTEP_SIGN_BIT 5 +#define AR_PHY_RESTART_ENABLE_DIV_M2FLAG 0x00200000 +#define AR_PHY_RESTART_ENABLE_DIV_M2FLAG_S 21 +#define AR_PHY_RESTART_DIV_GC 0x001C0000 +#define AR_PHY_RESTART_DIV_GC_S 18 +#define AR_PHY_RESTART_ENA 0x01 +#define AR_PHY_DC_RESTART_DIS 0x40000000 + +#define AR_PHY_TPC_OLPC_GAIN_DELTA_PAL_ON 0xFF000000 +#define AR_PHY_TPC_OLPC_GAIN_DELTA_PAL_ON_S 24 +#define AR_PHY_TPC_OLPC_GAIN_DELTA 0x00FF0000 +#define AR_PHY_TPC_OLPC_GAIN_DELTA_S 16 + +#define AR_PHY_TPC_6_ERROR_EST_MODE 0x03000000 +#define AR_PHY_TPC_6_ERROR_EST_MODE_S 24 + +/* + * SM Register Map + */ +#define AR_SM_BASE 0xa200 + +#define AR_PHY_D2_CHIP_ID (AR_SM_BASE + 0x0) +#define AR_PHY_GEN_CTRL (AR_SM_BASE + 0x4) +#define AR_PHY_MODE (AR_SM_BASE + 0x8) +#define AR_PHY_ACTIVE (AR_SM_BASE + 0xc) +#define AR_PHY_SPUR_MASK_A (AR_SM_BASE + 0x20) +#define AR_PHY_SPUR_MASK_B (AR_SM_BASE + 0x24) +#define AR_PHY_SPECTRAL_SCAN (AR_SM_BASE + 0x28) +#define AR_PHY_RADAR_BW_FILTER (AR_SM_BASE + 0x2c) +#define AR_PHY_SEARCH_START_DELAY (AR_SM_BASE + 0x30) +#define AR_PHY_MAX_RX_LEN (AR_SM_BASE + 0x34) +#define AR_PHY_FRAME_CTL (AR_SM_BASE + 0x38) +#define AR_PHY_RFBUS_REQ (AR_SM_BASE + 0x3c) +#define AR_PHY_RFBUS_GRANT (AR_SM_BASE + 0x40) +#define AR_PHY_RIFS (AR_SM_BASE + 0x44) +#define AR_PHY_RX_CLR_DELAY (AR_SM_BASE + 0x50) +#define AR_PHY_RX_DELAY (AR_SM_BASE + 0x54) + +#define AR_PHY_XPA_TIMING_CTL (AR_SM_BASE + 0x64) +#define AR_PHY_MISC_PA_CTL (AR_SM_BASE + 0x80) +#define AR_PHY_SWITCH_CHAIN_0 (AR_SM_BASE + 0x84) +#define AR_PHY_SWITCH_COM (AR_SM_BASE + 0x88) +#define AR_PHY_SWITCH_COM_2 (AR_SM_BASE + 0x8c) +#define AR_PHY_RX_CHAINMASK (AR_SM_BASE + 0xa0) +#define AR_PHY_CAL_CHAINMASK (AR_SM_BASE + 0xc0) +#define AR_PHY_CALMODE (AR_SM_BASE + 0xc8) +#define AR_PHY_FCAL_1 (AR_SM_BASE + 0xcc) +#define AR_PHY_FCAL_2_0 (AR_SM_BASE + 0xd0) +#define AR_PHY_DFT_TONE_CTL_0 (AR_SM_BASE + 0xd4) +#define AR_PHY_CL_CAL_CTL (AR_SM_BASE + 0xd8) +#define AR_PHY_CL_TAB_0 (AR_SM_BASE + 0x100) +#define AR_PHY_SYNTH_CONTROL (AR_SM_BASE + 0x140) +#define AR_PHY_ADDAC_CLK_SEL (AR_SM_BASE + 0x144) +#define AR_PHY_PLL_CTL (AR_SM_BASE + 0x148) +#define AR_PHY_ANALOG_SWAP (AR_SM_BASE + 0x14c) +#define AR_PHY_ADDAC_PARA_CTL (AR_SM_BASE + 0x150) +#define AR_PHY_XPA_CFG (AR_SM_BASE + 0x158) + +#define AR_PHY_FRAME_CTL_CF_OVERLAP_WINDOW 3 +#define AR_PHY_FRAME_CTL_CF_OVERLAP_WINDOW_S 0 + +#define AR_PHY_SPUR_MASK_A_CF_PUNC_MASK_IDX_A 0x0001FC00 +#define AR_PHY_SPUR_MASK_A_CF_PUNC_MASK_IDX_A_S 10 +#define AR_PHY_SPUR_MASK_A_CF_PUNC_MASK_A 0x3FF +#define AR_PHY_SPUR_MASK_A_CF_PUNC_MASK_A_S 0 + +#define AR_PHY_TEST (AR_SM_BASE + 0x160) + +#define AR_PHY_TEST_BBB_OBS_SEL 0x780000 +#define AR_PHY_TEST_BBB_OBS_SEL_S 19 + +#define AR_PHY_TEST_RX_OBS_SEL_BIT5_S 23 +#define AR_PHY_TEST_RX_OBS_SEL_BIT5 (1 << AR_PHY_TEST_RX_OBS_SEL_BIT5_S) + +#define AR_PHY_TEST_CHAIN_SEL 0xC0000000 +#define AR_PHY_TEST_CHAIN_SEL_S 30 + +#define AR_PHY_TEST_CTL_STATUS (AR_SM_BASE + 0x164) +#define AR_PHY_TEST_CTL_TSTDAC_EN 0x1 +#define AR_PHY_TEST_CTL_TSTDAC_EN_S 0 +#define AR_PHY_TEST_CTL_TX_OBS_SEL 0x1C +#define AR_PHY_TEST_CTL_TX_OBS_SEL_S 2 +#define AR_PHY_TEST_CTL_TX_OBS_MUX_SEL 0x60 +#define AR_PHY_TEST_CTL_TX_OBS_MUX_SEL_S 5 +#define AR_PHY_TEST_CTL_TSTADC_EN 0x100 +#define AR_PHY_TEST_CTL_TSTADC_EN_S 8 +#define AR_PHY_TEST_CTL_RX_OBS_SEL 0x3C00 +#define AR_PHY_TEST_CTL_RX_OBS_SEL_S 10 +#define AR_PHY_TEST_CTL_DEBUGPORT_SEL 0xe0000000 +#define AR_PHY_TEST_CTL_DEBUGPORT_SEL_S 29 + + +#define AR_PHY_TSTDAC (AR_SM_BASE + 0x168) + +#define AR_PHY_CHAN_STATUS (AR_SM_BASE + 0x16c) + +#define AR_PHY_CHAN_INFO_MEMORY (AR_SM_BASE + 0x170) +#define AR_PHY_CHAN_INFO_MEMORY_CHANINFOMEM_S2_READ 0x00000008 +#define AR_PHY_CHAN_INFO_MEMORY_CHANINFOMEM_S2_READ_S 3 + +#define AR_PHY_CHNINFO_NOISEPWR (AR_SM_BASE + 0x174) +#define AR_PHY_CHNINFO_GAINDIFF (AR_SM_BASE + 0x178) +#define AR_PHY_CHNINFO_FINETIM (AR_SM_BASE + 0x17c) +#define AR_PHY_CHAN_INFO_GAIN_0 (AR_SM_BASE + 0x180) +#define AR_PHY_SCRAMBLER_SEED (AR_SM_BASE + 0x190) +#define AR_PHY_CCK_TX_CTRL (AR_SM_BASE + 0x194) + +#define AR_PHY_HEAVYCLIP_CTL (AR_SM_BASE + 0x1a4) +#define AR_PHY_HEAVYCLIP_20 (AR_SM_BASE + 0x1a8) +#define AR_PHY_HEAVYCLIP_40 (AR_SM_BASE + 0x1ac) +#define AR_PHY_ILLEGAL_TXRATE (AR_SM_BASE + 0x1b0) + +#define AR_PHY_POWER_TX_RATE(_d) (AR_SM_BASE + 0x1c0 + ((_d) << 2)) + +#define AR_PHY_PWRTX_MAX (AR_SM_BASE + 0x1f0) +#define AR_PHY_POWER_TX_SUB (AR_SM_BASE + 0x1f4) + +#define AR_PHY_TPC_1 (AR_SM_BASE + 0x1f8) +#define AR_PHY_TPC_1_FORCED_DAC_GAIN 0x0000003e +#define AR_PHY_TPC_1_FORCED_DAC_GAIN_S 1 +#define AR_PHY_TPC_1_FORCE_DAC_GAIN 0x00000001 +#define AR_PHY_TPC_1_FORCE_DAC_GAIN_S 0 + +#define AR_PHY_TPC_4_B0 (AR_SM_BASE + 0x204) +#define AR_PHY_TPC_5_B0 (AR_SM_BASE + 0x208) +#define AR_PHY_TPC_6_B0 (AR_SM_BASE + 0x20c) + +#define AR_PHY_TPC_11_B0 (AR_SM_BASE + 0x220) +#define AR_PHY_TPC_11_B1 (AR_SM1_BASE + 0x220) +#define AR_PHY_TPC_11_B2 (AR_SM2_BASE + 0x220) +#define AR_PHY_TPC_11_OLPC_GAIN_DELTA 0x00ff0000 +#define AR_PHY_TPC_11_OLPC_GAIN_DELTA_S 16 + +#define AR_PHY_TPC_12 (AR_SM_BASE + 0x224) +#define AR_PHY_TPC_12_DESIRED_SCALE_HT40_5 0x3e000000 +#define AR_PHY_TPC_12_DESIRED_SCALE_HT40_5_S 25 + +#define AR_PHY_TPC_18 (AR_SM_BASE + 0x23c) +#define AR_PHY_TPC_18_THERM_CAL_VALUE 0x000000ff +#define AR_PHY_TPC_18_THERM_CAL_VALUE_S 0 +#define AR_PHY_TPC_18_VOLT_CAL_VALUE 0x0000ff00 +#define AR_PHY_TPC_18_VOLT_CAL_VALUE_S 8 + +#define AR_PHY_TPC_19 (AR_SM_BASE + 0x240) +#define AR_PHY_TPC_19_ALPHA_VOLT 0x001f0000 +#define AR_PHY_TPC_19_ALPHA_VOLT_S 16 +#define AR_PHY_TPC_19_ALPHA_THERM 0xff +#define AR_PHY_TPC_19_ALPHA_THERM_S 0 + +#define AR_PHY_TX_FORCED_GAIN (AR_SM_BASE + 0x258) +#define AR_PHY_TX_FORCED_GAIN_FORCE_TX_GAIN 0x00000001 +#define AR_PHY_TX_FORCED_GAIN_FORCE_TX_GAIN_S 0 +#define AR_PHY_TX_FORCED_GAIN_FORCED_TXBB1DBGAIN 0x0000000e +#define AR_PHY_TX_FORCED_GAIN_FORCED_TXBB1DBGAIN_S 1 +#define AR_PHY_TX_FORCED_GAIN_FORCED_TXBB6DBGAIN 0x00000030 +#define AR_PHY_TX_FORCED_GAIN_FORCED_TXBB6DBGAIN_S 4 +#define AR_PHY_TX_FORCED_GAIN_FORCED_TXMXRGAIN 0x000003c0 +#define AR_PHY_TX_FORCED_GAIN_FORCED_TXMXRGAIN_S 6 +#define AR_PHY_TX_FORCED_GAIN_FORCED_PADRVGNA 0x00003c00 +#define AR_PHY_TX_FORCED_GAIN_FORCED_PADRVGNA_S 10 +#define AR_PHY_TX_FORCED_GAIN_FORCED_PADRVGNB 0x0003c000 +#define AR_PHY_TX_FORCED_GAIN_FORCED_PADRVGNB_S 14 +#define AR_PHY_TX_FORCED_GAIN_FORCED_PADRVGNC 0x003c0000 +#define AR_PHY_TX_FORCED_GAIN_FORCED_PADRVGNC_S 18 +#define AR_PHY_TX_FORCED_GAIN_FORCED_PADRVGND 0x00c00000 +#define AR_PHY_TX_FORCED_GAIN_FORCED_PADRVGND_S 22 +#define AR_PHY_TX_FORCED_GAIN_FORCED_ENABLE_PAL 0x01000000 +#define AR_PHY_TX_FORCED_GAIN_FORCED_ENABLE_PAL_S 24 + + +#define AR_PHY_PDADC_TAB_0 (AR_SM_BASE + 0x280) + +#define AR_PHY_TXGAIN_TABLE (AR_SM_BASE + 0x300) + +#define AR_PHY_TX_IQCAL_CONTROL_0 (AR_SM_BASE + (AR_SREV_9485(ah) ? \ + 0x3c4 : 0x444)) +#define AR_PHY_TX_IQCAL_CONTROL_1 (AR_SM_BASE + (AR_SREV_9485(ah) ? \ + 0x3c8 : 0x448)) +#define AR_PHY_TX_IQCAL_START (AR_SM_BASE + (AR_SREV_9485(ah) ? \ + 0x3c4 : 0x440)) +#define AR_PHY_TX_IQCAL_STATUS_B0 (AR_SM_BASE + (AR_SREV_9485(ah) ? \ + 0x3f0 : 0x48c)) +#define AR_PHY_TX_IQCAL_CORR_COEFF_B0(_i) (AR_SM_BASE + \ + (AR_SREV_9485(ah) ? \ + 0x3d0 : 0x450) + ((_i) << 2)) +#define AR_PHY_RTT_CTRL (AR_SM_BASE + 0x380) + +#define AR_PHY_WATCHDOG_STATUS (AR_SM_BASE + 0x5c0) +#define AR_PHY_WATCHDOG_CTL_1 (AR_SM_BASE + 0x5c4) +#define AR_PHY_WATCHDOG_CTL_2 (AR_SM_BASE + 0x5c8) +#define AR_PHY_WATCHDOG_CTL (AR_SM_BASE + 0x5cc) +#define AR_PHY_ONLY_WARMRESET (AR_SM_BASE + 0x5d0) +#define AR_PHY_ONLY_CTL (AR_SM_BASE + 0x5d4) +#define AR_PHY_ECO_CTRL (AR_SM_BASE + 0x5dc) + +#define AR_PHY_BB_THERM_ADC_1 (AR_SM_BASE + 0x248) +#define AR_PHY_BB_THERM_ADC_1_INIT_THERM 0x000000ff +#define AR_PHY_BB_THERM_ADC_1_INIT_THERM_S 0 + +#define AR_PHY_BB_THERM_ADC_3 (AR_SM_BASE + 0x250) +#define AR_PHY_BB_THERM_ADC_3_THERM_ADC_SCALE_GAIN 0x0001ff00 +#define AR_PHY_BB_THERM_ADC_3_THERM_ADC_SCALE_GAIN_S 8 +#define AR_PHY_BB_THERM_ADC_3_THERM_ADC_OFFSET 0x000000ff +#define AR_PHY_BB_THERM_ADC_3_THERM_ADC_OFFSET_S 0 + +#define AR_PHY_BB_THERM_ADC_4 (AR_SM_BASE + 0x254) +#define AR_PHY_BB_THERM_ADC_4_LATEST_THERM_VALUE 0x000000ff +#define AR_PHY_BB_THERM_ADC_4_LATEST_THERM_VALUE_S 0 +#define AR_PHY_BB_THERM_ADC_4_LATEST_VOLT_VALUE 0x0000ff00 +#define AR_PHY_BB_THERM_ADC_4_LATEST_VOLT_VALUE_S 8 + +/* AIC Registers */ +#define AR_PHY_AIC_CTRL_0_B0 (AR_SM_BASE + 0x4b0) +#define AR_PHY_AIC_CTRL_1_B0 (AR_SM_BASE + 0x4b4) +#define AR_PHY_AIC_CTRL_2_B0 (AR_SM_BASE + 0x4b8) +#define AR_PHY_AIC_CTRL_3_B0 (AR_SM_BASE + 0x4bc) +#define AR_PHY_AIC_STAT_0_B0 (AR_SM_BASE + 0x4c4)) +#define AR_PHY_AIC_STAT_1_B0 (AR_SM_BASE + 0x4c8)) +#define AR_PHY_AIC_CTRL_4_B0 (AR_SM_BASE + 0x4c0) +#define AR_PHY_AIC_STAT_2_B0 (AR_SM_BASE + 0x4cc) + +#define AR_PHY_65NM_CH0_TXRF3 0x16048 +#define AR_PHY_65NM_CH0_TXRF3_CAPDIV2G 0x0000001e +#define AR_PHY_65NM_CH0_TXRF3_CAPDIV2G_S 1 + +#define AR_PHY_65NM_CH0_SYNTH4 0x1608c +#define AR_PHY_SYNTH4_LONG_SHIFT_SELECT ((AR_SREV_9462(ah) || AR_SREV_9565(ah)) ? 0x00000001 : 0x00000002) +#define AR_PHY_SYNTH4_LONG_SHIFT_SELECT_S ((AR_SREV_9462(ah) || AR_SREV_9565(ah)) ? 0 : 1) +#define AR_PHY_65NM_CH0_SYNTH7 0x16098 +#define AR_PHY_65NM_CH0_BIAS1 0x160c0 +#define AR_PHY_65NM_CH0_BIAS2 0x160c4 +#define AR_PHY_65NM_CH0_BIAS4 0x160cc +#define AR_PHY_65NM_CH0_RXTX4 0x1610c +#define AR_PHY_65NM_CH1_RXTX4 0x1650c +#define AR_PHY_65NM_CH2_RXTX4 0x1690c + +#define AR_CH0_TOP (AR_SREV_9300(ah) ? 0x16288 : \ + (((AR_SREV_9462(ah) || AR_SREV_9565(ah)) ? 0x1628c : 0x16280))) +#define AR_CH0_TOP_XPABIASLVL (AR_SREV_9550(ah) ? 0x3c0 : 0x300) +#define AR_CH0_TOP_XPABIASLVL_S (AR_SREV_9550(ah) ? 6 : 8) + +#define AR_CH0_THERM (AR_SREV_9300(ah) ? 0x16290 : \ + ((AR_SREV_9485(ah) ? 0x1628c : 0x16294))) +#define AR_CH0_THERM_XPABIASLVL_MSB 0x3 +#define AR_CH0_THERM_XPABIASLVL_MSB_S 0 +#define AR_CH0_THERM_XPASHORT2GND 0x4 +#define AR_CH0_THERM_XPASHORT2GND_S 2 + +#define AR_SWITCH_TABLE_COM_ALL (0xffff) +#define AR_SWITCH_TABLE_COM_ALL_S (0) +#define AR_SWITCH_TABLE_COM_AR9462_ALL (0xffffff) +#define AR_SWITCH_TABLE_COM_AR9462_ALL_S (0) +#define AR_SWITCH_TABLE_COM_AR9550_ALL (0xffffff) +#define AR_SWITCH_TABLE_COM_AR9550_ALL_S (0) +#define AR_SWITCH_TABLE_COM_SPDT (0x00f00000) +#define AR_SWITCH_TABLE_COM_SPDT_ALL (0x0000fff0) +#define AR_SWITCH_TABLE_COM_SPDT_ALL_S (4) + +#define AR_SWITCH_TABLE_COM2_ALL (0xffffff) +#define AR_SWITCH_TABLE_COM2_ALL_S (0) + +#define AR_SWITCH_TABLE_ALL (0xfff) +#define AR_SWITCH_TABLE_ALL_S (0) + +#define AR_PHY_65NM_CH0_THERM (AR_SREV_9300(ah) ? 0x16290 :\ + ((AR_SREV_9462(ah) || AR_SREV_9565(ah)) ? 0x16294 : 0x1628c)) + +#define AR_PHY_65NM_CH0_THERM_LOCAL 0x80000000 +#define AR_PHY_65NM_CH0_THERM_LOCAL_S 31 +#define AR_PHY_65NM_CH0_THERM_START 0x20000000 +#define AR_PHY_65NM_CH0_THERM_START_S 29 +#define AR_PHY_65NM_CH0_THERM_SAR_ADC_OUT 0x0000ff00 +#define AR_PHY_65NM_CH0_THERM_SAR_ADC_OUT_S 8 + +#define AR_CH0_TOP2 (AR_SREV_9300(ah) ? 0x1628c : \ + (AR_SREV_9462(ah) ? 0x16290 : 0x16284)) +#define AR_CH0_TOP2_XPABIASLVL 0xf000 +#define AR_CH0_TOP2_XPABIASLVL_S 12 + +#define AR_CH0_XTAL (AR_SREV_9300(ah) ? 0x16294 : \ + ((AR_SREV_9462(ah) || AR_SREV_9565(ah)) ? 0x16298 : 0x16290)) +#define AR_CH0_XTAL_CAPINDAC 0x7f000000 +#define AR_CH0_XTAL_CAPINDAC_S 24 +#define AR_CH0_XTAL_CAPOUTDAC 0x00fe0000 +#define AR_CH0_XTAL_CAPOUTDAC_S 17 + +#define AR_PHY_PMU1 ((AR_SREV_9462(ah) || AR_SREV_9565(ah)) ? 0x16340 : 0x16c40) +#define AR_PHY_PMU1_PWD 0x1 +#define AR_PHY_PMU1_PWD_S 0 + +#define AR_PHY_PMU2 ((AR_SREV_9462(ah) || AR_SREV_9565(ah)) ? 0x16344 : 0x16c44) +#define AR_PHY_PMU2_PGM 0x00200000 +#define AR_PHY_PMU2_PGM_S 21 + +#define AR_PHY_RX1DB_BIQUAD_LONG_SHIFT 0x00380000 +#define AR_PHY_RX1DB_BIQUAD_LONG_SHIFT_S 19 +#define AR_PHY_RX6DB_BIQUAD_LONG_SHIFT 0x00c00000 +#define AR_PHY_RX6DB_BIQUAD_LONG_SHIFT_S 22 +#define AR_PHY_LNAGAIN_LONG_SHIFT 0xe0000000 +#define AR_PHY_LNAGAIN_LONG_SHIFT_S 29 +#define AR_PHY_MXRGAIN_LONG_SHIFT 0x03000000 +#define AR_PHY_MXRGAIN_LONG_SHIFT_S 24 +#define AR_PHY_VGAGAIN_LONG_SHIFT 0x1c000000 +#define AR_PHY_VGAGAIN_LONG_SHIFT_S 26 +#define AR_PHY_SCFIR_GAIN_LONG_SHIFT 0x00000001 +#define AR_PHY_SCFIR_GAIN_LONG_SHIFT_S 0 +#define AR_PHY_MANRXGAIN_LONG_SHIFT 0x00000002 +#define AR_PHY_MANRXGAIN_LONG_SHIFT_S 1 + +/* + * SM Field Definitions + */ +#define AR_PHY_CL_CAL_ENABLE 0x00000002 +#define AR_PHY_PARALLEL_CAL_ENABLE 0x00000001 +#define AR_PHY_TPCRG1_PD_CAL_ENABLE 0x00400000 +#define AR_PHY_TPCRG1_PD_CAL_ENABLE_S 22 + +#define AR_PHY_ADDAC_PARACTL_OFF_PWDADC 0x00008000 + +#define AR_PHY_FCAL20_CAP_STATUS_0 0x01f00000 +#define AR_PHY_FCAL20_CAP_STATUS_0_S 20 + +#define AR_PHY_RFBUS_REQ_EN 0x00000001 /* request for RF bus */ +#define AR_PHY_RFBUS_GRANT_EN 0x00000001 /* RF bus granted */ +#define AR_PHY_GC_TURBO_MODE 0x00000001 /* set turbo mode bits */ +#define AR_PHY_GC_TURBO_SHORT 0x00000002 /* set short symbols to turbo mode setting */ +#define AR_PHY_GC_DYN2040_EN 0x00000004 /* enable dyn 20/40 mode */ +#define AR_PHY_GC_DYN2040_PRI_ONLY 0x00000008 /* dyn 20/40 - primary only */ +#define AR_PHY_GC_DYN2040_PRI_CH 0x00000010 /* dyn 20/40 - primary ch offset (0=+10MHz, 1=-10MHz)*/ +#define AR_PHY_GC_DYN2040_PRI_CH_S 4 +#define AR_PHY_GC_DYN2040_EXT_CH 0x00000020 /* dyn 20/40 - ext ch spacing (0=20MHz/ 1=25MHz) */ +#define AR_PHY_GC_HT_EN 0x00000040 /* ht enable */ +#define AR_PHY_GC_SHORT_GI_40 0x00000080 /* allow short GI for HT 40 */ +#define AR_PHY_GC_WALSH 0x00000100 /* walsh spatial spreading for 2 chains,2 streams TX */ +#define AR_PHY_GC_SINGLE_HT_LTF1 0x00000200 /* single length (4us) 1st HT long training symbol */ +#define AR_PHY_GC_GF_DETECT_EN 0x00000400 /* enable Green Field detection. Only affects rx, not tx */ +#define AR_PHY_GC_ENABLE_DAC_FIFO 0x00000800 /* fifo between bb and dac */ +#define AR_PHY_RX_DELAY_DELAY 0x00003FFF /* delay from wakeup to rx ena */ + +#define AR_PHY_CALMODE_IQ 0x00000000 +#define AR_PHY_CALMODE_ADC_GAIN 0x00000001 +#define AR_PHY_CALMODE_ADC_DC_PER 0x00000002 +#define AR_PHY_CALMODE_ADC_DC_INIT 0x00000003 +#define AR_PHY_SWAP_ALT_CHAIN 0x00000040 +#define AR_PHY_MODE_OFDM 0x00000000 +#define AR_PHY_MODE_CCK 0x00000001 +#define AR_PHY_MODE_DYNAMIC 0x00000004 +#define AR_PHY_MODE_DYNAMIC_S 2 +#define AR_PHY_MODE_HALF 0x00000020 +#define AR_PHY_MODE_QUARTER 0x00000040 +#define AR_PHY_MAC_CLK_MODE 0x00000080 +#define AR_PHY_MODE_DYN_CCK_DISABLE 0x00000100 +#define AR_PHY_MODE_SVD_HALF 0x00000200 +#define AR_PHY_ACTIVE_EN 0x00000001 +#define AR_PHY_ACTIVE_DIS 0x00000000 +#define AR_PHY_FORCE_XPA_CFG 0x000000001 +#define AR_PHY_FORCE_XPA_CFG_S 0 +#define AR_PHY_XPA_TIMING_CTL_TX_END_XPAB_OFF 0xFF000000 +#define AR_PHY_XPA_TIMING_CTL_TX_END_XPAB_OFF_S 24 +#define AR_PHY_XPA_TIMING_CTL_TX_END_XPAA_OFF 0x00FF0000 +#define AR_PHY_XPA_TIMING_CTL_TX_END_XPAA_OFF_S 16 +#define AR_PHY_XPA_TIMING_CTL_FRAME_XPAB_ON 0x0000FF00 +#define AR_PHY_XPA_TIMING_CTL_FRAME_XPAB_ON_S 8 +#define AR_PHY_XPA_TIMING_CTL_FRAME_XPAA_ON 0x000000FF +#define AR_PHY_XPA_TIMING_CTL_FRAME_XPAA_ON_S 0 +#define AR_PHY_TX_END_TO_A2_RX_ON 0x00FF0000 +#define AR_PHY_TX_END_TO_A2_RX_ON_S 16 +#define AR_PHY_TX_END_DATA_START 0x000000FF +#define AR_PHY_TX_END_DATA_START_S 0 +#define AR_PHY_TX_END_PA_ON 0x0000FF00 +#define AR_PHY_TX_END_PA_ON_S 8 +#define AR_PHY_TPCRG5_PD_GAIN_OVERLAP 0x0000000F +#define AR_PHY_TPCRG5_PD_GAIN_OVERLAP_S 0 +#define AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_1 0x000003F0 +#define AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_1_S 4 +#define AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_2 0x0000FC00 +#define AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_2_S 10 +#define AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_3 0x003F0000 +#define AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_3_S 16 +#define AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_4 0x0FC00000 +#define AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_4_S 22 +#define AR_PHY_TPCRG1_NUM_PD_GAIN 0x0000c000 +#define AR_PHY_TPCRG1_NUM_PD_GAIN_S 14 +#define AR_PHY_TPCRG1_PD_GAIN_1 0x00030000 +#define AR_PHY_TPCRG1_PD_GAIN_1_S 16 +#define AR_PHY_TPCRG1_PD_GAIN_2 0x000C0000 +#define AR_PHY_TPCRG1_PD_GAIN_2_S 18 +#define AR_PHY_TPCRG1_PD_GAIN_3 0x00300000 +#define AR_PHY_TPCRG1_PD_GAIN_3_S 20 +#define AR_PHY_TPCGR1_FORCED_DAC_GAIN 0x0000003e +#define AR_PHY_TPCGR1_FORCED_DAC_GAIN_S 1 +#define AR_PHY_TPCGR1_FORCE_DAC_GAIN 0x00000001 +#define AR_PHY_TXGAIN_FORCE 0x00000001 +#define AR_PHY_TXGAIN_FORCE_S 0 +#define AR_PHY_TXGAIN_FORCED_PADVGNRA 0x00003c00 +#define AR_PHY_TXGAIN_FORCED_PADVGNRA_S 10 +#define AR_PHY_TXGAIN_FORCED_PADVGNRB 0x0003c000 +#define AR_PHY_TXGAIN_FORCED_PADVGNRB_S 14 +#define AR_PHY_TXGAIN_FORCED_PADVGNRD 0x00c00000 +#define AR_PHY_TXGAIN_FORCED_PADVGNRD_S 22 +#define AR_PHY_TXGAIN_FORCED_TXMXRGAIN 0x000003c0 +#define AR_PHY_TXGAIN_FORCED_TXMXRGAIN_S 6 +#define AR_PHY_TXGAIN_FORCED_TXBB1DBGAIN 0x0000000e +#define AR_PHY_TXGAIN_FORCED_TXBB1DBGAIN_S 1 + +#define AR_PHY_POWER_TX_RATE1 0x9934 +#define AR_PHY_POWER_TX_RATE2 0x9938 +#define AR_PHY_POWER_TX_RATE_MAX 0x993c +#define AR_PHY_POWER_TX_RATE_MAX_TPC_ENABLE 0x00000040 +#define PHY_AGC_CLR 0x10000000 +#define RFSILENT_BB 0x00002000 +#define AR_PHY_CHAN_INFO_GAIN_DIFF_PPM_MASK 0xFFF +#define AR_PHY_CHAN_INFO_GAIN_DIFF_PPM_SIGNED_BIT 0x800 +#define AR_PHY_CHAN_INFO_GAIN_DIFF_UPPER_LIMIT 320 +#define AR_PHY_CHAN_INFO_MEMORY_CAPTURE_MASK 0x0001 +#define AR_PHY_RX_DELAY_DELAY 0x00003FFF +#define AR_PHY_CCK_TX_CTRL_JAPAN 0x00000010 + +#define AR_PHY_SPECTRAL_SCAN_ENABLE 0x00000001 +#define AR_PHY_SPECTRAL_SCAN_ENABLE_S 0 +#define AR_PHY_SPECTRAL_SCAN_ACTIVE 0x00000002 +#define AR_PHY_SPECTRAL_SCAN_ACTIVE_S 1 +#define AR_PHY_SPECTRAL_SCAN_FFT_PERIOD 0x000000F0 +#define AR_PHY_SPECTRAL_SCAN_FFT_PERIOD_S 4 +#define AR_PHY_SPECTRAL_SCAN_PERIOD 0x0000FF00 +#define AR_PHY_SPECTRAL_SCAN_PERIOD_S 8 +#define AR_PHY_SPECTRAL_SCAN_COUNT 0x0FFF0000 +#define AR_PHY_SPECTRAL_SCAN_COUNT_S 16 +#define AR_PHY_SPECTRAL_SCAN_SHORT_REPEAT 0x10000000 +#define AR_PHY_SPECTRAL_SCAN_SHORT_REPEAT_S 28 +#define AR_PHY_SPECTRAL_SCAN_PRIORITY 0x20000000 +#define AR_PHY_SPECTRAL_SCAN_PRIORITY_S 29 +#define AR_PHY_SPECTRAL_SCAN_USE_ERR5 0x40000000 +#define AR_PHY_SPECTRAL_SCAN_USE_ERR5_S 30 +#define AR_PHY_SPECTRAL_SCAN_COMPRESSED_RPT 0x80000000 +#define AR_PHY_SPECTRAL_SCAN_COMPRESSED_RPT_S 31 + +#define AR_PHY_CHANNEL_STATUS_RX_CLEAR 0x00000004 +#define AR_PHY_RTT_CTRL_ENA_RADIO_RETENTION 0x00000001 +#define AR_PHY_RTT_CTRL_ENA_RADIO_RETENTION_S 0 +#define AR_PHY_RTT_CTRL_RESTORE_MASK 0x0000007E +#define AR_PHY_RTT_CTRL_RESTORE_MASK_S 1 +#define AR_PHY_RTT_CTRL_FORCE_RADIO_RESTORE 0x00000080 +#define AR_PHY_RTT_CTRL_FORCE_RADIO_RESTORE_S 7 +#define AR_PHY_RTT_SW_RTT_TABLE_ACCESS 0x00000001 +#define AR_PHY_RTT_SW_RTT_TABLE_ACCESS_S 0 +#define AR_PHY_RTT_SW_RTT_TABLE_WRITE 0x00000002 +#define AR_PHY_RTT_SW_RTT_TABLE_WRITE_S 1 +#define AR_PHY_RTT_SW_RTT_TABLE_ADDR 0x0000001C +#define AR_PHY_RTT_SW_RTT_TABLE_ADDR_S 2 +#define AR_PHY_RTT_SW_RTT_TABLE_DATA 0xFFFFFFF0 +#define AR_PHY_RTT_SW_RTT_TABLE_DATA_S 4 +#define AR_PHY_TX_IQCAL_CONTROL_0_ENABLE_TXIQ_CAL 0x80000000 +#define AR_PHY_TX_IQCAL_CONTROL_0_ENABLE_TXIQ_CAL_S 31 +#define AR_PHY_TX_IQCAL_CONTROL_1_IQCORR_I_Q_COFF_DELPT 0x01fc0000 +#define AR_PHY_TX_IQCAL_CONTROL_1_IQCORR_I_Q_COFF_DELPT_S 18 +#define AR_PHY_TX_IQCAL_START_DO_CAL 0x00000001 +#define AR_PHY_TX_IQCAL_START_DO_CAL_S 0 + +#define AR_PHY_TX_IQCAL_STATUS_FAILED 0x00000001 +#define AR_PHY_CALIBRATED_GAINS_0 0x3e +#define AR_PHY_CALIBRATED_GAINS_0_S 1 + +#define AR_PHY_TX_IQCAL_CORR_COEFF_00_COEFF_TABLE 0x00003fff +#define AR_PHY_TX_IQCAL_CORR_COEFF_00_COEFF_TABLE_S 0 +#define AR_PHY_TX_IQCAL_CORR_COEFF_01_COEFF_TABLE 0x0fffc000 +#define AR_PHY_TX_IQCAL_CORR_COEFF_01_COEFF_TABLE_S 14 + +#define AR_PHY_65NM_CH0_RXTX4_THERM_ON 0x10000000 +#define AR_PHY_65NM_CH0_RXTX4_THERM_ON_S 28 +#define AR_PHY_65NM_CH0_RXTX4_THERM_ON_OVR 0x20000000 +#define AR_PHY_65NM_CH0_RXTX4_THERM_ON_OVR_S 29 + +#define AR_PHY_65NM_RXTX4_XLNA_BIAS 0xC0000000 +#define AR_PHY_65NM_RXTX4_XLNA_BIAS_S 30 + +/* + * Channel 1 Register Map + */ +#define AR_CHAN1_BASE 0xa800 + +#define AR_PHY_EXT_CCA_1 (AR_CHAN1_BASE + 0x30) +#define AR_PHY_TX_PHASE_RAMP_1 (AR_CHAN1_BASE + 0xd0) +#define AR_PHY_ADC_GAIN_DC_CORR_1 (AR_CHAN1_BASE + 0xd4) + +#define AR_PHY_SPUR_REPORT_1 (AR_CHAN1_BASE + 0xa8) +#define AR_PHY_CHAN_INFO_TAB_1 (AR_CHAN1_BASE + 0x300) +#define AR_PHY_RX_IQCAL_CORR_B1 (AR_CHAN1_BASE + 0xdc) + +/* + * Channel 1 Field Definitions + */ +#define AR_PHY_CH1_EXT_MINCCA_PWR 0x01FF0000 +#define AR_PHY_CH1_EXT_MINCCA_PWR_S 16 + +/* + * AGC 1 Register Map + */ +#define AR_AGC1_BASE 0xae00 + +#define AR_PHY_FORCEMAX_GAINS_1 (AR_AGC1_BASE + 0x4) +#define AR_PHY_EXT_ATTEN_CTL_1 (AR_AGC1_BASE + 0x18) +#define AR_PHY_CCA_1 (AR_AGC1_BASE + 0x1c) +#define AR_PHY_CCA_CTRL_1 (AR_AGC1_BASE + 0x20) +#define AR_PHY_RSSI_1 (AR_AGC1_BASE + 0x180) +#define AR_PHY_SPUR_CCK_REP_1 (AR_AGC1_BASE + 0x184) +#define AR_PHY_RX_OCGAIN_2 (AR_AGC1_BASE + 0x200) + +/* + * AGC 1 Field Definitions + */ +#define AR_PHY_CH1_MINCCA_PWR 0x1FF00000 +#define AR_PHY_CH1_MINCCA_PWR_S 20 + +/* + * SM 1 Register Map + */ +#define AR_SM1_BASE 0xb200 + +#define AR_PHY_SWITCH_CHAIN_1 (AR_SM1_BASE + 0x84) +#define AR_PHY_FCAL_2_1 (AR_SM1_BASE + 0xd0) +#define AR_PHY_DFT_TONE_CTL_1 (AR_SM1_BASE + 0xd4) +#define AR_PHY_CL_TAB_1 (AR_SM1_BASE + 0x100) +#define AR_PHY_CHAN_INFO_GAIN_1 (AR_SM1_BASE + 0x180) +#define AR_PHY_TPC_4_B1 (AR_SM1_BASE + 0x204) +#define AR_PHY_TPC_5_B1 (AR_SM1_BASE + 0x208) +#define AR_PHY_TPC_6_B1 (AR_SM1_BASE + 0x20c) +#define AR_PHY_TPC_11_B1 (AR_SM1_BASE + 0x220) +#define AR_PHY_PDADC_TAB_1 (AR_SM1_BASE + (AR_SREV_AR9462(ah) ? \ + 0x280 : 0x240)) +#define AR_PHY_TPC_19_B1 (AR_SM1_BASE + 0x240) +#define AR_PHY_TPC_19_B1_ALPHA_THERM 0xff +#define AR_PHY_TPC_19_B1_ALPHA_THERM_S 0 +#define AR_PHY_TX_IQCAL_STATUS_B1 (AR_SM1_BASE + 0x48c) +#define AR_PHY_TX_IQCAL_CORR_COEFF_B1(_i) (AR_SM1_BASE + 0x450 + ((_i) << 2)) + +/* SM 1 AIC Registers */ + +#define AR_PHY_AIC_CTRL_0_B1 (AR_SM1_BASE + 0x4b0) +#define AR_PHY_AIC_CTRL_1_B1 (AR_SM1_BASE + 0x4b4) +#define AR_PHY_AIC_CTRL_2_B1 (AR_SM1_BASE + 0x4b8) +#define AR_PHY_AIC_STAT_0_B1 (AR_SM1_BASE + (AR_SREV_9462_10(ah) ? \ + 0x4c0 : 0x4c4)) +#define AR_PHY_AIC_STAT_1_B1 (AR_SM1_BASE + (AR_SREV_9462_10(ah) ? \ + 0x4c4 : 0x4c8)) +#define AR_PHY_AIC_CTRL_4_B1 (AR_SM1_BASE + 0x4c0) +#define AR_PHY_AIC_STAT_2_B1 (AR_SM1_BASE + 0x4cc) + +#define AR_PHY_AIC_SRAM_ADDR_B1 (AR_SM1_BASE + 0x5f0) +#define AR_PHY_AIC_SRAM_DATA_B1 (AR_SM1_BASE + 0x5f4) + +#define AR_PHY_RTT_TABLE_SW_INTF_B(i) (0x384 + ((i) ? \ + AR_SM1_BASE : AR_SM_BASE)) +#define AR_PHY_RTT_TABLE_SW_INTF_1_B(i) (0x388 + ((i) ? \ + AR_SM1_BASE : AR_SM_BASE)) +/* + * Channel 2 Register Map + */ +#define AR_CHAN2_BASE 0xb800 + +#define AR_PHY_EXT_CCA_2 (AR_CHAN2_BASE + 0x30) +#define AR_PHY_TX_PHASE_RAMP_2 (AR_CHAN2_BASE + 0xd0) +#define AR_PHY_ADC_GAIN_DC_CORR_2 (AR_CHAN2_BASE + 0xd4) + +#define AR_PHY_SPUR_REPORT_2 (AR_CHAN2_BASE + 0xa8) +#define AR_PHY_CHAN_INFO_TAB_2 (AR_CHAN2_BASE + 0x300) +#define AR_PHY_RX_IQCAL_CORR_B2 (AR_CHAN2_BASE + 0xdc) + +/* + * Channel 2 Field Definitions + */ +#define AR_PHY_CH2_EXT_MINCCA_PWR 0x01FF0000 +#define AR_PHY_CH2_EXT_MINCCA_PWR_S 16 +/* + * AGC 2 Register Map + */ +#define AR_AGC2_BASE 0xbe00 + +#define AR_PHY_FORCEMAX_GAINS_2 (AR_AGC2_BASE + 0x4) +#define AR_PHY_EXT_ATTEN_CTL_2 (AR_AGC2_BASE + 0x18) +#define AR_PHY_CCA_2 (AR_AGC2_BASE + 0x1c) +#define AR_PHY_CCA_CTRL_2 (AR_AGC2_BASE + 0x20) +#define AR_PHY_RSSI_2 (AR_AGC2_BASE + 0x180) + +/* + * AGC 2 Field Definitions + */ +#define AR_PHY_CH2_MINCCA_PWR 0x1FF00000 +#define AR_PHY_CH2_MINCCA_PWR_S 20 + +/* + * SM 2 Register Map + */ +#define AR_SM2_BASE 0xc200 + +#define AR_PHY_SWITCH_CHAIN_2 (AR_SM2_BASE + 0x84) +#define AR_PHY_FCAL_2_2 (AR_SM2_BASE + 0xd0) +#define AR_PHY_DFT_TONE_CTL_2 (AR_SM2_BASE + 0xd4) +#define AR_PHY_CL_TAB_2 (AR_SM2_BASE + 0x100) +#define AR_PHY_CHAN_INFO_GAIN_2 (AR_SM2_BASE + 0x180) +#define AR_PHY_TPC_4_B2 (AR_SM2_BASE + 0x204) +#define AR_PHY_TPC_5_B2 (AR_SM2_BASE + 0x208) +#define AR_PHY_TPC_6_B2 (AR_SM2_BASE + 0x20c) +#define AR_PHY_TPC_11_B2 (AR_SM2_BASE + 0x220) +#define AR_PHY_TPC_19_B2 (AR_SM2_BASE + 0x240) +#define AR_PHY_TX_IQCAL_STATUS_B2 (AR_SM2_BASE + 0x48c) +#define AR_PHY_TX_IQCAL_CORR_COEFF_B2(_i) (AR_SM2_BASE + 0x450 + ((_i) << 2)) + +#define AR_PHY_TX_IQCAL_STATUS_B2_FAILED 0x00000001 + +/* + * AGC 3 Register Map + */ +#define AR_AGC3_BASE 0xce00 + +#define AR_PHY_RSSI_3 (AR_AGC3_BASE + 0x180) + +/* GLB Registers */ +#define AR_GLB_BASE 0x20000 +#define AR_GLB_GPIO_CONTROL (AR_GLB_BASE) +#define AR_PHY_GLB_CONTROL (AR_GLB_BASE + 0x44) +#define AR_GLB_SCRATCH(_ah) (AR_GLB_BASE + \ + (AR_SREV_9462_20(_ah) ? 0x4c : 0x50)) +#define AR_GLB_STATUS (AR_GLB_BASE + 0x48) + +/* + * Misc helper defines + */ +#define AR_PHY_CHAIN_OFFSET (AR_CHAN1_BASE - AR_CHAN_BASE) + +#define AR_PHY_NEW_ADC_DC_GAIN_CORR(_i) (AR_PHY_ADC_GAIN_DC_CORR_0 + (AR_PHY_CHAIN_OFFSET * (_i))) +#define AR_PHY_NEW_ADC_DC_GAIN_CORR_9300_10(_i) (AR_PHY_ADC_GAIN_DC_CORR_0_9300_10 + (AR_PHY_CHAIN_OFFSET * (_i))) +#define AR_PHY_SWITCH_CHAIN(_i) (AR_PHY_SWITCH_CHAIN_0 + (AR_PHY_CHAIN_OFFSET * (_i))) +#define AR_PHY_EXT_ATTEN_CTL(_i) (AR_PHY_EXT_ATTEN_CTL_0 + (AR_PHY_CHAIN_OFFSET * (_i))) + +#define AR_PHY_RXGAIN(_i) (AR_PHY_FORCEMAX_GAINS_0 + (AR_PHY_CHAIN_OFFSET * (_i))) +#define AR_PHY_TPCRG5(_i) (AR_PHY_TPC_5_B0 + (AR_PHY_CHAIN_OFFSET * (_i))) +#define AR_PHY_PDADC_TAB(_i) (AR_PHY_PDADC_TAB_0 + (AR_PHY_CHAIN_OFFSET * (_i))) + +#define AR_PHY_CAL_MEAS_0(_i) (AR_PHY_IQ_ADC_MEAS_0_B0 + (AR_PHY_CHAIN_OFFSET * (_i))) +#define AR_PHY_CAL_MEAS_1(_i) (AR_PHY_IQ_ADC_MEAS_1_B0 + (AR_PHY_CHAIN_OFFSET * (_i))) +#define AR_PHY_CAL_MEAS_2(_i) (AR_PHY_IQ_ADC_MEAS_2_B0 + (AR_PHY_CHAIN_OFFSET * (_i))) +#define AR_PHY_CAL_MEAS_3(_i) (AR_PHY_IQ_ADC_MEAS_3_B0 + (AR_PHY_CHAIN_OFFSET * (_i))) +#define AR_PHY_CAL_MEAS_0_9300_10(_i) (AR_PHY_IQ_ADC_MEAS_0_B0_9300_10 + (AR_PHY_CHAIN_OFFSET * (_i))) +#define AR_PHY_CAL_MEAS_1_9300_10(_i) (AR_PHY_IQ_ADC_MEAS_1_B0_9300_10 + (AR_PHY_CHAIN_OFFSET * (_i))) +#define AR_PHY_CAL_MEAS_2_9300_10(_i) (AR_PHY_IQ_ADC_MEAS_2_B0_9300_10 + (AR_PHY_CHAIN_OFFSET * (_i))) +#define AR_PHY_CAL_MEAS_3_9300_10(_i) (AR_PHY_IQ_ADC_MEAS_3_B0_9300_10 + (AR_PHY_CHAIN_OFFSET * (_i))) + +#define AR_PHY_WATCHDOG_NON_IDLE_ENABLE 0x00000001 +#define AR_PHY_WATCHDOG_IDLE_ENABLE 0x00000002 +#define AR_PHY_WATCHDOG_IDLE_MASK 0xFFFF0000 +#define AR_PHY_WATCHDOG_NON_IDLE_MASK 0x0000FFFC + +#define AR_PHY_WATCHDOG_RST_ENABLE 0x00000002 +#define AR_PHY_WATCHDOG_IRQ_ENABLE 0x00000004 +#define AR_PHY_WATCHDOG_CNTL2_MASK 0xFFFFFFF9 + +#define AR_PHY_WATCHDOG_INFO 0x00000007 +#define AR_PHY_WATCHDOG_INFO_S 0 +#define AR_PHY_WATCHDOG_DET_HANG 0x00000008 +#define AR_PHY_WATCHDOG_DET_HANG_S 3 +#define AR_PHY_WATCHDOG_RADAR_SM 0x000000F0 +#define AR_PHY_WATCHDOG_RADAR_SM_S 4 +#define AR_PHY_WATCHDOG_RX_OFDM_SM 0x00000F00 +#define AR_PHY_WATCHDOG_RX_OFDM_SM_S 8 +#define AR_PHY_WATCHDOG_RX_CCK_SM 0x0000F000 +#define AR_PHY_WATCHDOG_RX_CCK_SM_S 12 +#define AR_PHY_WATCHDOG_TX_OFDM_SM 0x000F0000 +#define AR_PHY_WATCHDOG_TX_OFDM_SM_S 16 +#define AR_PHY_WATCHDOG_TX_CCK_SM 0x00F00000 +#define AR_PHY_WATCHDOG_TX_CCK_SM_S 20 +#define AR_PHY_WATCHDOG_AGC_SM 0x0F000000 +#define AR_PHY_WATCHDOG_AGC_SM_S 24 +#define AR_PHY_WATCHDOG_SRCH_SM 0xF0000000 +#define AR_PHY_WATCHDOG_SRCH_SM_S 28 + +#define AR_PHY_WATCHDOG_STATUS_CLR 0x00000008 + +/* + * PAPRD registers + */ +#define AR_PHY_XPA_TIMING_CTL (AR_SM_BASE + 0x64) + +#define AR_PHY_PAPRD_AM2AM (AR_CHAN_BASE + 0xe4) +#define AR_PHY_PAPRD_AM2AM_MASK 0x01ffffff +#define AR_PHY_PAPRD_AM2AM_MASK_S 0 + +#define AR_PHY_PAPRD_AM2PM (AR_CHAN_BASE + 0xe8) +#define AR_PHY_PAPRD_AM2PM_MASK 0x01ffffff +#define AR_PHY_PAPRD_AM2PM_MASK_S 0 + +#define AR_PHY_PAPRD_HT40 (AR_CHAN_BASE + 0xec) +#define AR_PHY_PAPRD_HT40_MASK 0x01ffffff +#define AR_PHY_PAPRD_HT40_MASK_S 0 + +#define AR_PHY_PAPRD_CTRL0_B0 (AR_CHAN_BASE + 0xf0) +#define AR_PHY_PAPRD_CTRL0_B1 (AR_CHAN1_BASE + 0xf0) +#define AR_PHY_PAPRD_CTRL0_B2 (AR_CHAN2_BASE + 0xf0) +#define AR_PHY_PAPRD_CTRL0_PAPRD_ENABLE 0x00000001 +#define AR_PHY_PAPRD_CTRL0_PAPRD_ENABLE_S 0 +#define AR_PHY_PAPRD_CTRL0_USE_SINGLE_TABLE_MASK 0x00000002 +#define AR_PHY_PAPRD_CTRL0_USE_SINGLE_TABLE_MASK_S 1 +#define AR_PHY_PAPRD_CTRL0_PAPRD_MAG_THRSH 0xf8000000 +#define AR_PHY_PAPRD_CTRL0_PAPRD_MAG_THRSH_S 27 + +#define AR_PHY_PAPRD_CTRL1_B0 (AR_CHAN_BASE + 0xf4) +#define AR_PHY_PAPRD_CTRL1_B1 (AR_CHAN1_BASE + 0xf4) +#define AR_PHY_PAPRD_CTRL1_B2 (AR_CHAN2_BASE + 0xf4) +#define AR_PHY_PAPRD_CTRL1_ADAPTIVE_SCALING_ENA 0x00000001 +#define AR_PHY_PAPRD_CTRL1_ADAPTIVE_SCALING_ENA_S 0 +#define AR_PHY_PAPRD_CTRL1_ADAPTIVE_AM2AM_ENABLE 0x00000002 +#define AR_PHY_PAPRD_CTRL1_ADAPTIVE_AM2AM_ENABLE_S 1 +#define AR_PHY_PAPRD_CTRL1_ADAPTIVE_AM2PM_ENABLE 0x00000004 +#define AR_PHY_PAPRD_CTRL1_ADAPTIVE_AM2PM_ENABLE_S 2 +#define AR_PHY_PAPRD_CTRL1_PAPRD_POWER_AT_AM2AM_CAL 0x000001f8 +#define AR_PHY_PAPRD_CTRL1_PAPRD_POWER_AT_AM2AM_CAL_S 3 +#define AR_PHY_PAPRD_CTRL1_PA_GAIN_SCALE_FACT_MASK 0x0001fe00 +#define AR_PHY_PAPRD_CTRL1_PA_GAIN_SCALE_FACT_MASK_S 9 +#define AR_PHY_PAPRD_CTRL1_PAPRD_MAG_SCALE_FACT 0x0ffe0000 +#define AR_PHY_PAPRD_CTRL1_PAPRD_MAG_SCALE_FACT_S 17 + +#define AR_PHY_PAPRD_TRAINER_CNTL1 (AR_SM_BASE + (AR_SREV_9485(ah) ? 0x580 : 0x490)) + +#define AR_PHY_PAPRD_TRAINER_CNTL1_CF_CF_PAPRD_TRAIN_ENABLE 0x00000001 +#define AR_PHY_PAPRD_TRAINER_CNTL1_CF_CF_PAPRD_TRAIN_ENABLE_S 0 +#define AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_AGC2_SETTLING 0x0000007e +#define AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_AGC2_SETTLING_S 1 +#define AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_IQCORR_ENABLE 0x00000100 +#define AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_IQCORR_ENABLE_S 8 +#define AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_RX_BB_GAIN_FORCE 0x00000200 +#define AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_RX_BB_GAIN_FORCE_S 9 +#define AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_TX_GAIN_FORCE 0x00000400 +#define AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_TX_GAIN_FORCE_S 10 +#define AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_LB_ENABLE 0x00000800 +#define AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_LB_ENABLE_S 11 +#define AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_LB_SKIP 0x0003f000 +#define AR_PHY_PAPRD_TRAINER_CNTL1_CF_PAPRD_LB_SKIP_S 12 + +#define AR_PHY_PAPRD_TRAINER_CNTL2 (AR_SM_BASE + (AR_SREV_9485(ah) ? 0x584 : 0x494)) + +#define AR_PHY_PAPRD_TRAINER_CNTL2_CF_PAPRD_INIT_RX_BB_GAIN 0xFFFFFFFF +#define AR_PHY_PAPRD_TRAINER_CNTL2_CF_PAPRD_INIT_RX_BB_GAIN_S 0 + +#define AR_PHY_PAPRD_TRAINER_CNTL3 (AR_SM_BASE + (AR_SREV_9485(ah) ? 0x588 : 0x498)) + +#define AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_ADC_DESIRED_SIZE 0x0000003f +#define AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_ADC_DESIRED_SIZE_S 0 +#define AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_QUICK_DROP 0x00000fc0 +#define AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_QUICK_DROP_S 6 +#define AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_MIN_LOOPBACK_DEL 0x0001f000 +#define AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_MIN_LOOPBACK_DEL_S 12 +#define AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_NUM_CORR_STAGES 0x000e0000 +#define AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_NUM_CORR_STAGES_S 17 +#define AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_COARSE_CORR_LEN 0x00f00000 +#define AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_COARSE_CORR_LEN_S 20 +#define AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_FINE_CORR_LEN 0x0f000000 +#define AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_FINE_CORR_LEN_S 24 +#define AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_BBTXMIX_DISABLE 0x20000000 +#define AR_PHY_PAPRD_TRAINER_CNTL3_CF_PAPRD_BBTXMIX_DISABLE_S 29 + +#define AR_PHY_PAPRD_TRAINER_CNTL4 (AR_SM_BASE + (AR_SREV_9485(ah) ? 0x58c : 0x49c)) + +#define AR_PHY_PAPRD_TRAINER_CNTL4_CF_PAPRD_NUM_TRAIN_SAMPLES 0x03ff0000 +#define AR_PHY_PAPRD_TRAINER_CNTL4_CF_PAPRD_NUM_TRAIN_SAMPLES_S 16 +#define AR_PHY_PAPRD_TRAINER_CNTL4_CF_PAPRD_SAFETY_DELTA 0x0000f000 +#define AR_PHY_PAPRD_TRAINER_CNTL4_CF_PAPRD_SAFETY_DELTA_S 12 +#define AR_PHY_PAPRD_TRAINER_CNTL4_CF_PAPRD_MIN_CORR 0x00000fff +#define AR_PHY_PAPRD_TRAINER_CNTL4_CF_PAPRD_MIN_CORR_S 0 + +#define AR_PHY_PAPRD_PRE_POST_SCALE_0_B0 (AR_CHAN_BASE + 0x100) +#define AR_PHY_PAPRD_PRE_POST_SCALE_1_B0 (AR_CHAN_BASE + 0x104) +#define AR_PHY_PAPRD_PRE_POST_SCALE_2_B0 (AR_CHAN_BASE + 0x108) +#define AR_PHY_PAPRD_PRE_POST_SCALE_3_B0 (AR_CHAN_BASE + 0x10c) +#define AR_PHY_PAPRD_PRE_POST_SCALE_4_B0 (AR_CHAN_BASE + 0x110) +#define AR_PHY_PAPRD_PRE_POST_SCALE_5_B0 (AR_CHAN_BASE + 0x114) +#define AR_PHY_PAPRD_PRE_POST_SCALE_6_B0 (AR_CHAN_BASE + 0x118) +#define AR_PHY_PAPRD_PRE_POST_SCALE_7_B0 (AR_CHAN_BASE + 0x11c) +#define AR_PHY_PAPRD_PRE_POST_SCALING 0x3FFFF +#define AR_PHY_PAPRD_PRE_POST_SCALING_S 0 + +#define AR_PHY_PAPRD_TRAINER_STAT1 (AR_SM_BASE + (AR_SREV_9485(ah) ? 0x590 : 0x4a0)) + +#define AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_TRAIN_DONE 0x00000001 +#define AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_TRAIN_DONE_S 0 +#define AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_TRAIN_INCOMPLETE 0x00000002 +#define AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_TRAIN_INCOMPLETE_S 1 +#define AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_CORR_ERR 0x00000004 +#define AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_CORR_ERR_S 2 +#define AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_TRAIN_ACTIVE 0x00000008 +#define AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_TRAIN_ACTIVE_S 3 +#define AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_RX_GAIN_IDX 0x000001f0 +#define AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_RX_GAIN_IDX_S 4 +#define AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_AGC2_PWR 0x0001fe00 +#define AR_PHY_PAPRD_TRAINER_STAT1_PAPRD_AGC2_PWR_S 9 + +#define AR_PHY_PAPRD_TRAINER_STAT2 (AR_SM_BASE + (AR_SREV_9485(ah) ? 0x594 : 0x4a4)) + +#define AR_PHY_PAPRD_TRAINER_STAT2_PAPRD_FINE_VAL 0x0000ffff +#define AR_PHY_PAPRD_TRAINER_STAT2_PAPRD_FINE_VAL_S 0 +#define AR_PHY_PAPRD_TRAINER_STAT2_PAPRD_COARSE_IDX 0x001f0000 +#define AR_PHY_PAPRD_TRAINER_STAT2_PAPRD_COARSE_IDX_S 16 +#define AR_PHY_PAPRD_TRAINER_STAT2_PAPRD_FINE_IDX 0x00600000 +#define AR_PHY_PAPRD_TRAINER_STAT2_PAPRD_FINE_IDX_S 21 + +#define AR_PHY_PAPRD_TRAINER_STAT3 (AR_SM_BASE + (AR_SREV_9485(ah) ? 0x598 : 0x4a8)) + +#define AR_PHY_PAPRD_TRAINER_STAT3_PAPRD_TRAIN_SAMPLES_CNT 0x000fffff +#define AR_PHY_PAPRD_TRAINER_STAT3_PAPRD_TRAIN_SAMPLES_CNT_S 0 + +#define AR_PHY_PAPRD_MEM_TAB_B0 (AR_CHAN_BASE + 0x120) +#define AR_PHY_PAPRD_MEM_TAB_B1 (AR_CHAN1_BASE + 0x120) +#define AR_PHY_PAPRD_MEM_TAB_B2 (AR_CHAN2_BASE + 0x120) + +#define AR_PHY_PA_GAIN123_B0 (AR_CHAN_BASE + 0xf8) +#define AR_PHY_PA_GAIN123_B1 (AR_CHAN1_BASE + 0xf8) +#define AR_PHY_PA_GAIN123_B2 (AR_CHAN2_BASE + 0xf8) +#define AR_PHY_PA_GAIN123_PA_GAIN1 0x3FF +#define AR_PHY_PA_GAIN123_PA_GAIN1_S 0 + +#define AR_PHY_POWERTX_RATE5 (AR_SM_BASE + 0x1d0) +#define AR_PHY_POWERTX_RATE5_POWERTXHT20_0 0x3F +#define AR_PHY_POWERTX_RATE5_POWERTXHT20_0_S 0 + +#define AR_PHY_POWERTX_RATE6 (AR_SM_BASE + 0x1d4) +#define AR_PHY_POWERTX_RATE6_POWERTXHT20_5 0x3F00 +#define AR_PHY_POWERTX_RATE6_POWERTXHT20_5_S 8 + +#define AR_PHY_POWERTX_RATE8 (AR_SM_BASE + 0x1dc) +#define AR_PHY_POWERTX_RATE8_POWERTXHT40_5 0x3F00 +#define AR_PHY_POWERTX_RATE8_POWERTXHT40_5_S 8 + +#define AR_PHY_CL_TAB_CL_GAIN_MOD 0x1f +#define AR_PHY_CL_TAB_CL_GAIN_MOD_S 0 + +#define AR_BTCOEX_WL_LNADIV 0x1a64 +#define AR_BTCOEX_WL_LNADIV_PREDICTED_PERIOD 0x00003FFF +#define AR_BTCOEX_WL_LNADIV_PREDICTED_PERIOD_S 0 +#define AR_BTCOEX_WL_LNADIV_DPDT_IGNORE_PRIORITY 0x00004000 +#define AR_BTCOEX_WL_LNADIV_DPDT_IGNORE_PRIORITY_S 14 +#define AR_BTCOEX_WL_LNADIV_FORCE_ON 0x00008000 +#define AR_BTCOEX_WL_LNADIV_FORCE_ON_S 15 +#define AR_BTCOEX_WL_LNADIV_MODE_OPTION 0x00030000 +#define AR_BTCOEX_WL_LNADIV_MODE_OPTION_S 16 +#define AR_BTCOEX_WL_LNADIV_MODE 0x007c0000 +#define AR_BTCOEX_WL_LNADIV_MODE_S 18 +#define AR_BTCOEX_WL_LNADIV_ALLOWED_TX_ANTDIV_WL_TX_REQ 0x00800000 +#define AR_BTCOEX_WL_LNADIV_ALLOWED_TX_ANTDIV_WL_TX_REQ_S 23 +#define AR_BTCOEX_WL_LNADIV_DISABLE_TX_ANTDIV_ENABLE 0x01000000 +#define AR_BTCOEX_WL_LNADIV_DISABLE_TX_ANTDIV_ENABLE_S 24 +#define AR_BTCOEX_WL_LNADIV_CONTINUOUS_BT_ACTIVE_PROTECT 0x02000000 +#define AR_BTCOEX_WL_LNADIV_CONTINUOUS_BT_ACTIVE_PROTECT_S 25 +#define AR_BTCOEX_WL_LNADIV_BT_INACTIVE_THRESHOLD 0xFC000000 +#define AR_BTCOEX_WL_LNADIV_BT_INACTIVE_THRESHOLD_S 26 + +/* Manual Peak detector calibration */ +#define AR_PHY_65NM_BASE 0x16000 +#define AR_PHY_65NM_RXRF_GAINSTAGES(i) (AR_PHY_65NM_BASE + \ + (i * 0x400) + 0x8) +#define AR_PHY_65NM_RXRF_GAINSTAGES_RX_OVERRIDE 0x80000000 +#define AR_PHY_65NM_RXRF_GAINSTAGES_RX_OVERRIDE_S 31 +#define AR_PHY_65NM_RXRF_GAINSTAGES_LNAON_CALDC 0x00000002 +#define AR_PHY_65NM_RXRF_GAINSTAGES_LNAON_CALDC_S 1 +#define AR_PHY_65NM_RXRF_GAINSTAGES_LNA2G_GAIN_OVR 0x70000000 +#define AR_PHY_65NM_RXRF_GAINSTAGES_LNA2G_GAIN_OVR_S 28 +#define AR_PHY_65NM_RXRF_GAINSTAGES_LNA5G_GAIN_OVR 0x03800000 +#define AR_PHY_65NM_RXRF_GAINSTAGES_LNA5G_GAIN_OVR_S 23 + +#define AR_PHY_65NM_RXTX2(i) (AR_PHY_65NM_BASE + \ + (i * 0x400) + 0x104) +#define AR_PHY_65NM_RXTX2_RXON_OVR 0x00001000 +#define AR_PHY_65NM_RXTX2_RXON_OVR_S 12 +#define AR_PHY_65NM_RXTX2_RXON 0x00000800 +#define AR_PHY_65NM_RXTX2_RXON_S 11 + +#define AR_PHY_65NM_RXRF_AGC(i) (AR_PHY_65NM_BASE + \ + (i * 0x400) + 0xc) +#define AR_PHY_65NM_RXRF_AGC_AGC_OVERRIDE 0x80000000 +#define AR_PHY_65NM_RXRF_AGC_AGC_OVERRIDE_S 31 +#define AR_PHY_65NM_RXRF_AGC_AGC_ON_OVR 0x40000000 +#define AR_PHY_65NM_RXRF_AGC_AGC_ON_OVR_S 30 +#define AR_PHY_65NM_RXRF_AGC_AGC_CAL_OVR 0x20000000 +#define AR_PHY_65NM_RXRF_AGC_AGC_CAL_OVR_S 29 +#define AR_PHY_65NM_RXRF_AGC_AGC2G_DBDAC_OVR 0x1E000000 +#define AR_PHY_65NM_RXRF_AGC_AGC2G_DBDAC_OVR_S 25 +#define AR_PHY_65NM_RXRF_AGC_AGC5G_DBDAC_OVR 0x00078000 +#define AR_PHY_65NM_RXRF_AGC_AGC5G_DBDAC_OVR_S 15 +#define AR_PHY_65NM_RXRF_AGC_AGC2G_CALDAC_OVR 0x01F80000 +#define AR_PHY_65NM_RXRF_AGC_AGC2G_CALDAC_OVR_S 19 +#define AR_PHY_65NM_RXRF_AGC_AGC5G_CALDAC_OVR 0x00007e00 +#define AR_PHY_65NM_RXRF_AGC_AGC5G_CALDAC_OVR_S 9 +#define AR_PHY_65NM_RXRF_AGC_AGC_OUT 0x00000004 +#define AR_PHY_65NM_RXRF_AGC_AGC_OUT_S 2 + +#endif /* AR9003_PHY_H */ diff --git a/drivers/net/wireless/ath/ath9k/ar9003_rtt.c b/drivers/net/wireless/ath/ath9k/ar9003_rtt.c new file mode 100644 index 0000000..74de353 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9003_rtt.c @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "hw.h" +#include "hw-ops.h" +#include "ar9003_phy.h" +#include "ar9003_rtt.h" + +#define RTT_RESTORE_TIMEOUT 1000 +#define RTT_ACCESS_TIMEOUT 100 +#define RTT_BAD_VALUE 0x0bad0bad + +/* + * RTT (Radio Retention Table) hardware implementation information + * + * There is an internal table (i.e. the rtt) for each chain (or bank). + * Each table contains 6 entries and each entry is corresponding to + * a specific calibration parameter as depicted below. + * 0~2 - DC offset DAC calibration: loop, low, high (offsetI/Q_...) + * 3 - Filter cal (filterfc) + * 4 - RX gain settings + * 5 - Peak detector offset calibration (agc_caldac) + */ + +void ar9003_hw_rtt_enable(struct ath_hw *ah) +{ + REG_WRITE(ah, AR_PHY_RTT_CTRL, 1); +} + +void ar9003_hw_rtt_disable(struct ath_hw *ah) +{ + REG_WRITE(ah, AR_PHY_RTT_CTRL, 0); +} + +void ar9003_hw_rtt_set_mask(struct ath_hw *ah, u32 rtt_mask) +{ + REG_RMW_FIELD(ah, AR_PHY_RTT_CTRL, + AR_PHY_RTT_CTRL_RESTORE_MASK, rtt_mask); +} + +bool ar9003_hw_rtt_force_restore(struct ath_hw *ah) +{ + if (!ath9k_hw_wait(ah, AR_PHY_RTT_CTRL, + AR_PHY_RTT_CTRL_FORCE_RADIO_RESTORE, + 0, RTT_RESTORE_TIMEOUT)) + return false; + + REG_RMW_FIELD(ah, AR_PHY_RTT_CTRL, + AR_PHY_RTT_CTRL_FORCE_RADIO_RESTORE, 1); + + if (!ath9k_hw_wait(ah, AR_PHY_RTT_CTRL, + AR_PHY_RTT_CTRL_FORCE_RADIO_RESTORE, + 0, RTT_RESTORE_TIMEOUT)) + return false; + + return true; +} + +static void ar9003_hw_rtt_load_hist_entry(struct ath_hw *ah, u8 chain, + u32 index, u32 data28) +{ + u32 val; + + val = SM(data28, AR_PHY_RTT_SW_RTT_TABLE_DATA); + REG_WRITE(ah, AR_PHY_RTT_TABLE_SW_INTF_1_B(chain), val); + + val = SM(0, AR_PHY_RTT_SW_RTT_TABLE_ACCESS) | + SM(1, AR_PHY_RTT_SW_RTT_TABLE_WRITE) | + SM(index, AR_PHY_RTT_SW_RTT_TABLE_ADDR); + REG_WRITE(ah, AR_PHY_RTT_TABLE_SW_INTF_B(chain), val); + udelay(1); + + val |= SM(1, AR_PHY_RTT_SW_RTT_TABLE_ACCESS); + REG_WRITE(ah, AR_PHY_RTT_TABLE_SW_INTF_B(chain), val); + udelay(1); + + if (!ath9k_hw_wait(ah, AR_PHY_RTT_TABLE_SW_INTF_B(chain), + AR_PHY_RTT_SW_RTT_TABLE_ACCESS, 0, + RTT_ACCESS_TIMEOUT)) + return; + + val &= ~SM(1, AR_PHY_RTT_SW_RTT_TABLE_WRITE); + REG_WRITE(ah, AR_PHY_RTT_TABLE_SW_INTF_B(chain), val); + udelay(1); + + ath9k_hw_wait(ah, AR_PHY_RTT_TABLE_SW_INTF_B(chain), + AR_PHY_RTT_SW_RTT_TABLE_ACCESS, 0, + RTT_ACCESS_TIMEOUT); +} + +void ar9003_hw_rtt_load_hist(struct ath_hw *ah) +{ + int chain, i; + + for (chain = 0; chain < AR9300_MAX_CHAINS; chain++) { + if (!(ah->rxchainmask & (1 << chain))) + continue; + for (i = 0; i < MAX_RTT_TABLE_ENTRY; i++) { + ar9003_hw_rtt_load_hist_entry(ah, chain, i, + ah->caldata->rtt_table[chain][i]); + ath_dbg(ath9k_hw_common(ah), CALIBRATE, + "Load RTT value at idx %d, chain %d: 0x%x\n", + i, chain, ah->caldata->rtt_table[chain][i]); + } + } +} + +static int ar9003_hw_rtt_fill_hist_entry(struct ath_hw *ah, u8 chain, u32 index) +{ + u32 val; + + val = SM(0, AR_PHY_RTT_SW_RTT_TABLE_ACCESS) | + SM(0, AR_PHY_RTT_SW_RTT_TABLE_WRITE) | + SM(index, AR_PHY_RTT_SW_RTT_TABLE_ADDR); + + REG_WRITE(ah, AR_PHY_RTT_TABLE_SW_INTF_B(chain), val); + udelay(1); + + val |= SM(1, AR_PHY_RTT_SW_RTT_TABLE_ACCESS); + REG_WRITE(ah, AR_PHY_RTT_TABLE_SW_INTF_B(chain), val); + udelay(1); + + if (!ath9k_hw_wait(ah, AR_PHY_RTT_TABLE_SW_INTF_B(chain), + AR_PHY_RTT_SW_RTT_TABLE_ACCESS, 0, + RTT_ACCESS_TIMEOUT)) + return RTT_BAD_VALUE; + + val = MS(REG_READ(ah, AR_PHY_RTT_TABLE_SW_INTF_1_B(chain)), + AR_PHY_RTT_SW_RTT_TABLE_DATA); + + + return val; +} + +void ar9003_hw_rtt_fill_hist(struct ath_hw *ah) +{ + int chain, i; + + for (chain = 0; chain < AR9300_MAX_CHAINS; chain++) { + if (!(ah->rxchainmask & (1 << chain))) + continue; + for (i = 0; i < MAX_RTT_TABLE_ENTRY; i++) { + ah->caldata->rtt_table[chain][i] = + ar9003_hw_rtt_fill_hist_entry(ah, chain, i); + ath_dbg(ath9k_hw_common(ah), CALIBRATE, + "RTT value at idx %d, chain %d is: 0x%x\n", + i, chain, ah->caldata->rtt_table[chain][i]); + } + } + + ah->caldata->rtt_done = true; +} + +void ar9003_hw_rtt_clear_hist(struct ath_hw *ah) +{ + int chain, i; + + for (chain = 0; chain < AR9300_MAX_CHAINS; chain++) { + if (!(ah->rxchainmask & (1 << chain))) + continue; + for (i = 0; i < MAX_RTT_TABLE_ENTRY; i++) + ar9003_hw_rtt_load_hist_entry(ah, chain, i, 0); + } + + if (ah->caldata) + ah->caldata->rtt_done = false; +} + +bool ar9003_hw_rtt_restore(struct ath_hw *ah, struct ath9k_channel *chan) +{ + bool restore; + + if (!ah->caldata) + return false; + + if (!ah->caldata->rtt_done) + return false; + + ar9003_hw_rtt_enable(ah); + ar9003_hw_rtt_set_mask(ah, 0x10); + + if (!ath9k_hw_rfbus_req(ah)) { + ath_err(ath9k_hw_common(ah), "Could not stop baseband\n"); + restore = false; + goto fail; + } + + ar9003_hw_rtt_load_hist(ah); + restore = ar9003_hw_rtt_force_restore(ah); + +fail: + ath9k_hw_rfbus_done(ah); + ar9003_hw_rtt_disable(ah); + return restore; +} diff --git a/drivers/net/wireless/ath/ath9k/ar9003_rtt.h b/drivers/net/wireless/ath/ath9k/ar9003_rtt.h new file mode 100644 index 0000000..a43b30d --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9003_rtt.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef AR9003_RTT_H +#define AR9003_RTT_H + +void ar9003_hw_rtt_enable(struct ath_hw *ah); +void ar9003_hw_rtt_disable(struct ath_hw *ah); +void ar9003_hw_rtt_set_mask(struct ath_hw *ah, u32 rtt_mask); +bool ar9003_hw_rtt_force_restore(struct ath_hw *ah); +void ar9003_hw_rtt_load_hist(struct ath_hw *ah); +void ar9003_hw_rtt_fill_hist(struct ath_hw *ah); +void ar9003_hw_rtt_clear_hist(struct ath_hw *ah); +bool ar9003_hw_rtt_restore(struct ath_hw *ah, struct ath9k_channel *chan); + +#endif diff --git a/drivers/net/wireless/ath/ath9k/ar9330_1p1_initvals.h b/drivers/net/wireless/ath/ath9k/ar9330_1p1_initvals.h new file mode 100644 index 0000000..6e1756b --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9330_1p1_initvals.h @@ -0,0 +1,1100 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef INITVALS_9330_1P1_H +#define INITVALS_9330_1P1_H + +static const u32 ar9331_1p1_baseband_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009810, 0xd00a8005, 0xd00a8005, 0xd00a8005, 0xd00a8005}, + {0x00009820, 0x206a002e, 0x206a002e, 0x206a002e, 0x206a002e}, + {0x00009824, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0}, + {0x00009828, 0x06903081, 0x06903081, 0x06903881, 0x06903881}, + {0x0000982c, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4}, + {0x00009830, 0x0000059c, 0x0000059c, 0x0000059c, 0x0000059c}, + {0x00009c00, 0x00000044, 0x00000044, 0x00000044, 0x00000044}, + {0x00009e00, 0x0372161e, 0x0372161e, 0x037216a4, 0x037216a4}, + {0x00009e04, 0x00202020, 0x00202020, 0x00202020, 0x00202020}, + {0x00009e0c, 0x6c4000e2, 0x6d4000e2, 0x6d4000e2, 0x6c4000e2}, + {0x00009e10, 0x7ec80d2e, 0x7ec80d2e, 0x7ec80d2e, 0x7ec80d2e}, + {0x00009e14, 0x31365d5e, 0x3136605e, 0x3136605e, 0x31365d5e}, + {0x00009e18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00009e1c, 0x0001cf9c, 0x0001cf9c, 0x00021f9c, 0x00021f9c}, + {0x00009e20, 0x000003b5, 0x000003b5, 0x000003ce, 0x000003ce}, + {0x00009e2c, 0x0000001c, 0x0000001c, 0x00003221, 0x00003221}, + {0x00009e3c, 0xcf946222, 0xcf946222, 0xcf946222, 0xcf946222}, + {0x00009e44, 0x02321e27, 0x02321e27, 0x02282324, 0x02282324}, + {0x00009e48, 0x5030201a, 0x5030201a, 0x50302010, 0x50302010}, + {0x00009fc8, 0x0003f000, 0x0003f000, 0x0001a000, 0x0001a000}, + {0x0000a204, 0x00003fc0, 0x00003fc4, 0x00003fc4, 0x00003fc0}, + {0x0000a208, 0x00000104, 0x00000104, 0x00000004, 0x00000004}, + {0x0000a230, 0x0000000a, 0x00000014, 0x00000016, 0x0000000b}, + {0x0000a234, 0x00000fff, 0x00000fff, 0x10000fff, 0x00000fff}, + {0x0000a238, 0xffb81018, 0xffb81018, 0xffb81018, 0xffb81018}, + {0x0000a250, 0x00000000, 0x00000000, 0x00000210, 0x00000108}, + {0x0000a254, 0x000007d0, 0x00000fa0, 0x00001130, 0x00000898}, + {0x0000a258, 0x02020002, 0x02020002, 0x02020002, 0x02020002}, + {0x0000a25c, 0x01000e0e, 0x01000e0e, 0x01000e0e, 0x01000e0e}, + {0x0000a260, 0x3a021501, 0x3a021501, 0x3a021501, 0x3a021501}, + {0x0000a264, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x0000a280, 0x00000007, 0x00000007, 0x0000000b, 0x0000000b}, + {0x0000a284, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a288, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a28c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a2c4, 0x00158d18, 0x00158d18, 0x00158d18, 0x00158d18}, + {0x0000a2d0, 0x00071982, 0x00071982, 0x00071982, 0x00071982}, + {0x0000a2d8, 0xf999a83a, 0xf999a83a, 0xf999a83a, 0xf999a83a}, + {0x0000a358, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000ae04, 0x00802020, 0x00802020, 0x00802020, 0x00802020}, + {0x0000ae18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, +}; + +static const u32 ar9331_modes_lowest_ob_db_tx_gain_1p1[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2d8, 0x7999a83a, 0x7999a83a, 0x7999a83a, 0x7999a83a}, + {0x0000a2dc, 0xffff2a52, 0xffff2a52, 0xffff2a52, 0xffff2a52}, + {0x0000a2e0, 0xffffcc84, 0xffffcc84, 0xffffcc84, 0xffffcc84}, + {0x0000a2e4, 0xfffff000, 0xfffff000, 0xfffff000, 0xfffff000}, + {0x0000a2e8, 0xfffe0000, 0xfffe0000, 0xfffe0000, 0xfffe0000}, + {0x0000a410, 0x000050d7, 0x000050d7, 0x000050d0, 0x000050d0}, + {0x0000a500, 0x00022200, 0x00022200, 0x00000000, 0x00000000}, + {0x0000a504, 0x05062002, 0x05062002, 0x04000002, 0x04000002}, + {0x0000a508, 0x0c002e00, 0x0c002e00, 0x08000004, 0x08000004}, + {0x0000a50c, 0x11062202, 0x11062202, 0x0d000200, 0x0d000200}, + {0x0000a510, 0x17022e00, 0x17022e00, 0x11000202, 0x11000202}, + {0x0000a514, 0x1d000ec2, 0x1d000ec2, 0x15000400, 0x15000400}, + {0x0000a518, 0x25020ec0, 0x25020ec0, 0x19000402, 0x19000402}, + {0x0000a51c, 0x2b020ec3, 0x2b020ec3, 0x1d000404, 0x1d000404}, + {0x0000a520, 0x2f001f04, 0x2f001f04, 0x23000a00, 0x23000a00}, + {0x0000a524, 0x35001fc4, 0x35001fc4, 0x27000a02, 0x27000a02}, + {0x0000a528, 0x3c022f04, 0x3c022f04, 0x2b000a04, 0x2b000a04}, + {0x0000a52c, 0x41023e85, 0x41023e85, 0x2d000a20, 0x2d000a20}, + {0x0000a530, 0x48023ec6, 0x48023ec6, 0x31000a22, 0x31000a22}, + {0x0000a534, 0x4d023f01, 0x4d023f01, 0x35000a24, 0x35000a24}, + {0x0000a538, 0x53023f4b, 0x53023f4b, 0x38000a43, 0x38000a43}, + {0x0000a53c, 0x5a027f09, 0x5a027f09, 0x3b000e42, 0x3b000e42}, + {0x0000a540, 0x5f027fc9, 0x5f027fc9, 0x3f000e44, 0x3f000e44}, + {0x0000a544, 0x6502feca, 0x6502feca, 0x42000e64, 0x42000e64}, + {0x0000a548, 0x6b02ff4a, 0x6b02ff4a, 0x46000e66, 0x46000e66}, + {0x0000a54c, 0x7203feca, 0x7203feca, 0x4a000ea6, 0x4a000ea6}, + {0x0000a550, 0x7703ff0b, 0x7703ff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a554, 0x7d06ffcb, 0x7d06ffcb, 0x4a000ea6, 0x4a000ea6}, + {0x0000a558, 0x8407ff0b, 0x8407ff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a55c, 0x8907ffcb, 0x8907ffcb, 0x4a000ea6, 0x4a000ea6}, + {0x0000a560, 0x900fff0b, 0x900fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a564, 0x960fffcb, 0x960fffcb, 0x4a000ea6, 0x4a000ea6}, + {0x0000a568, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a56c, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a570, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a574, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a578, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a57c, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a580, 0x00022200, 0x00022200, 0x00000000, 0x00000000}, + {0x0000a584, 0x05062002, 0x05062002, 0x04000002, 0x04000002}, + {0x0000a588, 0x0c002e00, 0x0c002e00, 0x08000004, 0x08000004}, + {0x0000a58c, 0x11062202, 0x11062202, 0x0b000200, 0x0b000200}, + {0x0000a590, 0x17022e00, 0x17022e00, 0x0f000202, 0x0f000202}, + {0x0000a594, 0x1d000ec2, 0x1d000ec2, 0x11000400, 0x11000400}, + {0x0000a598, 0x25020ec0, 0x25020ec0, 0x15000402, 0x15000402}, + {0x0000a59c, 0x2b020ec3, 0x2b020ec3, 0x19000404, 0x19000404}, + {0x0000a5a0, 0x2f001f04, 0x2f001f04, 0x1b000603, 0x1b000603}, + {0x0000a5a4, 0x35001fc4, 0x35001fc4, 0x1f000a02, 0x1f000a02}, + {0x0000a5a8, 0x3c022f04, 0x3c022f04, 0x23000a04, 0x23000a04}, + {0x0000a5ac, 0x41023e85, 0x41023e85, 0x26000a20, 0x26000a20}, + {0x0000a5b0, 0x48023ec6, 0x48023ec6, 0x2a000e20, 0x2a000e20}, + {0x0000a5b4, 0x4d023f01, 0x4d023f01, 0x2e000e22, 0x2e000e22}, + {0x0000a5b8, 0x53023f4b, 0x53023f4b, 0x31000e24, 0x31000e24}, + {0x0000a5bc, 0x5a027f09, 0x5a027f09, 0x34001640, 0x34001640}, + {0x0000a5c0, 0x5f027fc9, 0x5f027fc9, 0x38001660, 0x38001660}, + {0x0000a5c4, 0x6502feca, 0x6502feca, 0x3b001861, 0x3b001861}, + {0x0000a5c8, 0x6b02ff4a, 0x6b02ff4a, 0x3e001a81, 0x3e001a81}, + {0x0000a5cc, 0x7203feca, 0x7203feca, 0x42001a83, 0x42001a83}, + {0x0000a5d0, 0x7703ff0b, 0x7703ff0b, 0x44001c84, 0x44001c84}, + {0x0000a5d4, 0x7d06ffcb, 0x7d06ffcb, 0x48001ce3, 0x48001ce3}, + {0x0000a5d8, 0x8407ff0b, 0x8407ff0b, 0x4c001ce5, 0x4c001ce5}, + {0x0000a5dc, 0x8907ffcb, 0x8907ffcb, 0x50001ce9, 0x50001ce9}, + {0x0000a5e0, 0x900fff0b, 0x900fff0b, 0x54001ceb, 0x54001ceb}, + {0x0000a5e4, 0x960fffcb, 0x960fffcb, 0x56001eec, 0x56001eec}, + {0x0000a5e8, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5ec, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5f0, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5f4, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5f8, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5fc, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x01404000, 0x01404000, 0x01404000, 0x01404000}, + {0x0000a618, 0x01404501, 0x01404501, 0x01404501, 0x01404501}, + {0x0000a61c, 0x02008802, 0x02008802, 0x02008802, 0x02008802}, + {0x0000a620, 0x0280c802, 0x0280c802, 0x0280c802, 0x0280c802}, + {0x0000a624, 0x03010a03, 0x03010a03, 0x03010a03, 0x03010a03}, + {0x0000a628, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x0000a62c, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x0000a630, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x0000a634, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x0000a638, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x0000a63c, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x00016044, 0x034922db, 0x034922db, 0x034922db, 0x034922db}, + {0x00016284, 0x14d3f000, 0x14d3f000, 0x14d3f000, 0x14d3f000}, +}; + +static const u32 ar9331_modes_high_ob_db_tx_gain_1p1[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2d8, 0x7999a83a, 0x7999a83a, 0x7999a83a, 0x7999a83a}, + {0x0000a2dc, 0xffaa9a52, 0xffaa9a52, 0xffaa9a52, 0xffaa9a52}, + {0x0000a2e0, 0xffb31c84, 0xffb31c84, 0xffb31c84, 0xffb31c84}, + {0x0000a2e4, 0xff43e000, 0xff43e000, 0xff43e000, 0xff43e000}, + {0x0000a2e8, 0xfffc0000, 0xfffc0000, 0xfffc0000, 0xfffc0000}, + {0x0000a410, 0x000050d7, 0x000050d7, 0x000050d7, 0x000050d7}, + {0x0000a500, 0x00022200, 0x00022200, 0x00000000, 0x00000000}, + {0x0000a504, 0x05062002, 0x05062002, 0x04000002, 0x04000002}, + {0x0000a508, 0x0c002e00, 0x0c002e00, 0x08000004, 0x08000004}, + {0x0000a50c, 0x11062202, 0x11062202, 0x0d000200, 0x0d000200}, + {0x0000a510, 0x17022e00, 0x17022e00, 0x11000202, 0x11000202}, + {0x0000a514, 0x1d000ec2, 0x1d000ec2, 0x15000400, 0x15000400}, + {0x0000a518, 0x25020ec0, 0x25020ec0, 0x19000402, 0x19000402}, + {0x0000a51c, 0x2b020ec3, 0x2b020ec3, 0x1d000404, 0x1d000404}, + {0x0000a520, 0x2f001f04, 0x2f001f04, 0x23000a00, 0x23000a00}, + {0x0000a524, 0x35001fc4, 0x35001fc4, 0x27000a02, 0x27000a02}, + {0x0000a528, 0x3c022f04, 0x3c022f04, 0x2b000a04, 0x2b000a04}, + {0x0000a52c, 0x41023e85, 0x41023e85, 0x3d001620, 0x3d001620}, + {0x0000a530, 0x48023ec6, 0x48023ec6, 0x3f001621, 0x3f001621}, + {0x0000a534, 0x4d023f01, 0x4d023f01, 0x42001640, 0x42001640}, + {0x0000a538, 0x53023f4b, 0x53023f4b, 0x44001641, 0x44001641}, + {0x0000a53c, 0x5a027f09, 0x5a027f09, 0x46001642, 0x46001642}, + {0x0000a540, 0x5f027fc9, 0x5f027fc9, 0x49001644, 0x49001644}, + {0x0000a544, 0x6502feca, 0x6502feca, 0x4c001a81, 0x4c001a81}, + {0x0000a548, 0x6b02ff4a, 0x6b02ff4a, 0x4f001a83, 0x4f001a83}, + {0x0000a54c, 0x7203feca, 0x7203feca, 0x52001c84, 0x52001c84}, + {0x0000a550, 0x7703ff0b, 0x7703ff0b, 0x55001ce3, 0x55001ce3}, + {0x0000a554, 0x7d06ffcb, 0x7d06ffcb, 0x59001ce5, 0x59001ce5}, + {0x0000a558, 0x8407ff0b, 0x8407ff0b, 0x5d001ce9, 0x5d001ce9}, + {0x0000a55c, 0x8907ffcb, 0x8907ffcb, 0x64001eec, 0x64001eec}, + {0x0000a560, 0x900fff0b, 0x900fff0b, 0x64001eec, 0x64001eec}, + {0x0000a564, 0x960fffcb, 0x960fffcb, 0x64001eec, 0x64001eec}, + {0x0000a568, 0x9c1fff0b, 0x9c1fff0b, 0x64001eec, 0x64001eec}, + {0x0000a56c, 0x9c1fff0b, 0x9c1fff0b, 0x64001eec, 0x64001eec}, + {0x0000a570, 0x9c1fff0b, 0x9c1fff0b, 0x64001eec, 0x64001eec}, + {0x0000a574, 0x9c1fff0b, 0x9c1fff0b, 0x64001eec, 0x64001eec}, + {0x0000a578, 0x9c1fff0b, 0x9c1fff0b, 0x64001eec, 0x64001eec}, + {0x0000a57c, 0x9c1fff0b, 0x9c1fff0b, 0x64001eec, 0x64001eec}, + {0x0000a580, 0x00022200, 0x00022200, 0x00000000, 0x00000000}, + {0x0000a584, 0x05062002, 0x05062002, 0x04000002, 0x04000002}, + {0x0000a588, 0x0c002e00, 0x0c002e00, 0x08000004, 0x08000004}, + {0x0000a58c, 0x11062202, 0x11062202, 0x0b000200, 0x0b000200}, + {0x0000a590, 0x17022e00, 0x17022e00, 0x0f000202, 0x0f000202}, + {0x0000a594, 0x1d000ec2, 0x1d000ec2, 0x11000400, 0x11000400}, + {0x0000a598, 0x25020ec0, 0x25020ec0, 0x15000402, 0x15000402}, + {0x0000a59c, 0x2b020ec3, 0x2b020ec3, 0x19000404, 0x19000404}, + {0x0000a5a0, 0x2f001f04, 0x2f001f04, 0x1b000603, 0x1b000603}, + {0x0000a5a4, 0x35001fc4, 0x35001fc4, 0x1f000a02, 0x1f000a02}, + {0x0000a5a8, 0x3c022f04, 0x3c022f04, 0x23000a04, 0x23000a04}, + {0x0000a5ac, 0x41023e85, 0x41023e85, 0x26000a20, 0x26000a20}, + {0x0000a5b0, 0x48023ec6, 0x48023ec6, 0x2a000e20, 0x2a000e20}, + {0x0000a5b4, 0x4d023f01, 0x4d023f01, 0x2e000e22, 0x2e000e22}, + {0x0000a5b8, 0x53023f4b, 0x53023f4b, 0x31000e24, 0x31000e24}, + {0x0000a5bc, 0x5a027f09, 0x5a027f09, 0x34001640, 0x34001640}, + {0x0000a5c0, 0x5f027fc9, 0x5f027fc9, 0x38001660, 0x38001660}, + {0x0000a5c4, 0x6502feca, 0x6502feca, 0x3b001861, 0x3b001861}, + {0x0000a5c8, 0x6b02ff4a, 0x6b02ff4a, 0x3e001a81, 0x3e001a81}, + {0x0000a5cc, 0x7203feca, 0x7203feca, 0x42001a83, 0x42001a83}, + {0x0000a5d0, 0x7703ff0b, 0x7703ff0b, 0x44001c84, 0x44001c84}, + {0x0000a5d4, 0x7d06ffcb, 0x7d06ffcb, 0x48001ce3, 0x48001ce3}, + {0x0000a5d8, 0x8407ff0b, 0x8407ff0b, 0x4c001ce5, 0x4c001ce5}, + {0x0000a5dc, 0x8907ffcb, 0x8907ffcb, 0x50001ce9, 0x50001ce9}, + {0x0000a5e0, 0x900fff0b, 0x900fff0b, 0x54001ceb, 0x54001ceb}, + {0x0000a5e4, 0x960fffcb, 0x960fffcb, 0x56001eec, 0x56001eec}, + {0x0000a5e8, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5ec, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5f0, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5f4, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5f8, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5fc, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x01404000, 0x01404000, 0x01404000, 0x01404000}, + {0x0000a618, 0x02008501, 0x02008501, 0x02008501, 0x02008501}, + {0x0000a61c, 0x02008802, 0x02008802, 0x02008802, 0x02008802}, + {0x0000a620, 0x0280c802, 0x0280c802, 0x0280c802, 0x0280c802}, + {0x0000a624, 0x0280ca03, 0x0280ca03, 0x0280ca03, 0x0280ca03}, + {0x0000a628, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x0000a62c, 0x04015005, 0x04015005, 0x04015005, 0x04015005}, + {0x0000a630, 0x04015005, 0x04015005, 0x04015005, 0x04015005}, + {0x0000a634, 0x04015005, 0x04015005, 0x04015005, 0x04015005}, + {0x0000a638, 0x04015005, 0x04015005, 0x04015005, 0x04015005}, + {0x0000a63c, 0x04015005, 0x04015005, 0x04015005, 0x04015005}, +}; + +static const u32 ar9331_modes_low_ob_db_tx_gain_1p1[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2d8, 0x7999a83a, 0x7999a83a, 0x7999a83a, 0x7999a83a}, + {0x0000a2dc, 0xffff2a52, 0xffff2a52, 0xffff2a52, 0xffff2a52}, + {0x0000a2e0, 0xffffcc84, 0xffffcc84, 0xffffcc84, 0xffffcc84}, + {0x0000a2e4, 0xfffff000, 0xfffff000, 0xfffff000, 0xfffff000}, + {0x0000a2e8, 0xfffe0000, 0xfffe0000, 0xfffe0000, 0xfffe0000}, + {0x0000a410, 0x000050d7, 0x000050d7, 0x000050d0, 0x000050d0}, + {0x0000a500, 0x00022200, 0x00022200, 0x00000000, 0x00000000}, + {0x0000a504, 0x05062002, 0x05062002, 0x04000002, 0x04000002}, + {0x0000a508, 0x0c002e00, 0x0c002e00, 0x08000004, 0x08000004}, + {0x0000a50c, 0x11062202, 0x11062202, 0x0d000200, 0x0d000200}, + {0x0000a510, 0x17022e00, 0x17022e00, 0x11000202, 0x11000202}, + {0x0000a514, 0x1d000ec2, 0x1d000ec2, 0x15000400, 0x15000400}, + {0x0000a518, 0x25020ec0, 0x25020ec0, 0x19000402, 0x19000402}, + {0x0000a51c, 0x2b020ec3, 0x2b020ec3, 0x1d000404, 0x1d000404}, + {0x0000a520, 0x2f001f04, 0x2f001f04, 0x23000a00, 0x23000a00}, + {0x0000a524, 0x35001fc4, 0x35001fc4, 0x27000a02, 0x27000a02}, + {0x0000a528, 0x3c022f04, 0x3c022f04, 0x2b000a04, 0x2b000a04}, + {0x0000a52c, 0x41023e85, 0x41023e85, 0x2d000a20, 0x2d000a20}, + {0x0000a530, 0x48023ec6, 0x48023ec6, 0x31000a22, 0x31000a22}, + {0x0000a534, 0x4d023f01, 0x4d023f01, 0x35000a24, 0x35000a24}, + {0x0000a538, 0x53023f4b, 0x53023f4b, 0x38000a43, 0x38000a43}, + {0x0000a53c, 0x5a027f09, 0x5a027f09, 0x3b000e42, 0x3b000e42}, + {0x0000a540, 0x5f027fc9, 0x5f027fc9, 0x3f000e44, 0x3f000e44}, + {0x0000a544, 0x6502feca, 0x6502feca, 0x42000e64, 0x42000e64}, + {0x0000a548, 0x6b02ff4a, 0x6b02ff4a, 0x46000e66, 0x46000e66}, + {0x0000a54c, 0x7203feca, 0x7203feca, 0x4a000ea6, 0x4a000ea6}, + {0x0000a550, 0x7703ff0b, 0x7703ff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a554, 0x7d06ffcb, 0x7d06ffcb, 0x4a000ea6, 0x4a000ea6}, + {0x0000a558, 0x8407ff0b, 0x8407ff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a55c, 0x8907ffcb, 0x8907ffcb, 0x4a000ea6, 0x4a000ea6}, + {0x0000a560, 0x900fff0b, 0x900fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a564, 0x960fffcb, 0x960fffcb, 0x4a000ea6, 0x4a000ea6}, + {0x0000a568, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a56c, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a570, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a574, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a578, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a57c, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a580, 0x00022200, 0x00022200, 0x00000000, 0x00000000}, + {0x0000a584, 0x05062002, 0x05062002, 0x04000002, 0x04000002}, + {0x0000a588, 0x0c002e00, 0x0c002e00, 0x08000004, 0x08000004}, + {0x0000a58c, 0x11062202, 0x11062202, 0x0b000200, 0x0b000200}, + {0x0000a590, 0x17022e00, 0x17022e00, 0x0f000202, 0x0f000202}, + {0x0000a594, 0x1d000ec2, 0x1d000ec2, 0x11000400, 0x11000400}, + {0x0000a598, 0x25020ec0, 0x25020ec0, 0x15000402, 0x15000402}, + {0x0000a59c, 0x2b020ec3, 0x2b020ec3, 0x19000404, 0x19000404}, + {0x0000a5a0, 0x2f001f04, 0x2f001f04, 0x1b000603, 0x1b000603}, + {0x0000a5a4, 0x35001fc4, 0x35001fc4, 0x1f000a02, 0x1f000a02}, + {0x0000a5a8, 0x3c022f04, 0x3c022f04, 0x23000a04, 0x23000a04}, + {0x0000a5ac, 0x41023e85, 0x41023e85, 0x26000a20, 0x26000a20}, + {0x0000a5b0, 0x48023ec6, 0x48023ec6, 0x2a000e20, 0x2a000e20}, + {0x0000a5b4, 0x4d023f01, 0x4d023f01, 0x2e000e22, 0x2e000e22}, + {0x0000a5b8, 0x53023f4b, 0x53023f4b, 0x31000e24, 0x31000e24}, + {0x0000a5bc, 0x5a027f09, 0x5a027f09, 0x34001640, 0x34001640}, + {0x0000a5c0, 0x5f027fc9, 0x5f027fc9, 0x38001660, 0x38001660}, + {0x0000a5c4, 0x6502feca, 0x6502feca, 0x3b001861, 0x3b001861}, + {0x0000a5c8, 0x6b02ff4a, 0x6b02ff4a, 0x3e001a81, 0x3e001a81}, + {0x0000a5cc, 0x7203feca, 0x7203feca, 0x42001a83, 0x42001a83}, + {0x0000a5d0, 0x7703ff0b, 0x7703ff0b, 0x44001c84, 0x44001c84}, + {0x0000a5d4, 0x7d06ffcb, 0x7d06ffcb, 0x48001ce3, 0x48001ce3}, + {0x0000a5d8, 0x8407ff0b, 0x8407ff0b, 0x4c001ce5, 0x4c001ce5}, + {0x0000a5dc, 0x8907ffcb, 0x8907ffcb, 0x50001ce9, 0x50001ce9}, + {0x0000a5e0, 0x900fff0b, 0x900fff0b, 0x54001ceb, 0x54001ceb}, + {0x0000a5e4, 0x960fffcb, 0x960fffcb, 0x56001eec, 0x56001eec}, + {0x0000a5e8, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5ec, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5f0, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5f4, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5f8, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5fc, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x01404000, 0x01404000, 0x01404000, 0x01404000}, + {0x0000a618, 0x01404501, 0x01404501, 0x01404501, 0x01404501}, + {0x0000a61c, 0x02008802, 0x02008802, 0x02008802, 0x02008802}, + {0x0000a620, 0x0280c802, 0x0280c802, 0x0280c802, 0x0280c802}, + {0x0000a624, 0x03010a03, 0x03010a03, 0x03010a03, 0x03010a03}, + {0x0000a628, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x0000a62c, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x0000a630, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x0000a634, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x0000a638, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x0000a63c, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x00016044, 0x034922db, 0x034922db, 0x034922db, 0x034922db}, + {0x00016284, 0x14d3f000, 0x14d3f000, 0x14d3f000, 0x14d3f000}, +}; + +#define ar9331_1p1_baseband_core_txfir_coeff_japan_2484 ar9462_2p0_baseband_core_txfir_coeff_japan_2484 + +static const u32 ar9331_1p1_xtal_25M[][2] = { + /* Addr allmodes */ + {0x00007038, 0x000002f8}, + {0x00008244, 0x0010f3d7}, + {0x0000824c, 0x0001e7ae}, + {0x0001609c, 0x0f508f29}, +}; + +static const u32 ar9331_1p1_radio_core[][2] = { + /* Addr allmodes */ + {0x00016000, 0x36db6db6}, + {0x00016004, 0x6db6db40}, + {0x00016008, 0x73800000}, + {0x0001600c, 0x00000000}, + {0x00016040, 0x7f80fff8}, + {0x00016044, 0x03db62db}, + {0x00016048, 0x6c924268}, + {0x0001604c, 0x000f0278}, + {0x00016050, 0x4db6db8c}, + {0x00016054, 0x6db60000}, + {0x00016080, 0x00080000}, + {0x00016084, 0x0e48048c}, + {0x00016088, 0x14214514}, + {0x0001608c, 0x119f081c}, + {0x00016090, 0x24926490}, + {0x00016098, 0xd411eb84}, + {0x000160a0, 0xc2108ffe}, + {0x000160a4, 0x812fc370}, + {0x000160a8, 0x423c8000}, + {0x000160ac, 0x24651800}, + {0x000160b0, 0x03284f3e}, + {0x000160b4, 0x92480040}, + {0x000160c0, 0x006db6db}, + {0x000160c4, 0x0186db60}, + {0x000160c8, 0x6db4db6c}, + {0x000160cc, 0x6de6c300}, + {0x000160d0, 0x14500820}, + {0x00016100, 0x04cb0001}, + {0x00016104, 0xfff80015}, + {0x00016108, 0x00080010}, + {0x0001610c, 0x00170000}, + {0x00016140, 0x10800000}, + {0x00016144, 0x01884080}, + {0x00016148, 0x000080c0}, + {0x00016280, 0x01000015}, + {0x00016284, 0x14d20000}, + {0x00016288, 0x00318000}, + {0x0001628c, 0x50000000}, + {0x00016290, 0x4b96210f}, + {0x00016380, 0x00000000}, + {0x00016384, 0x00000000}, + {0x00016388, 0x00800700}, + {0x0001638c, 0x00800700}, + {0x00016390, 0x00800700}, + {0x00016394, 0x00000000}, + {0x00016398, 0x00000000}, + {0x0001639c, 0x00000000}, + {0x000163a0, 0x00000001}, + {0x000163a4, 0x00000001}, + {0x000163a8, 0x00000000}, + {0x000163ac, 0x00000000}, + {0x000163b0, 0x00000000}, + {0x000163b4, 0x00000000}, + {0x000163b8, 0x00000000}, + {0x000163bc, 0x00000000}, + {0x000163c0, 0x000000a0}, + {0x000163c4, 0x000c0000}, + {0x000163c8, 0x14021402}, + {0x000163cc, 0x00001402}, + {0x000163d0, 0x00000000}, + {0x000163d4, 0x00000000}, +}; + +static const u32 ar9331_1p1_soc_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00007010, 0x00000022, 0x00000022, 0x00000022, 0x00000022}, +}; + +static const u32 ar9331_common_wo_xlna_rx_gain_1p1[][2] = { + /* Addr allmodes */ + {0x0000a000, 0x00060005}, + {0x0000a004, 0x00810080}, + {0x0000a008, 0x00830082}, + {0x0000a00c, 0x00850084}, + {0x0000a010, 0x01820181}, + {0x0000a014, 0x01840183}, + {0x0000a018, 0x01880185}, + {0x0000a01c, 0x018a0189}, + {0x0000a020, 0x02850284}, + {0x0000a024, 0x02890288}, + {0x0000a028, 0x028b028a}, + {0x0000a02c, 0x03850384}, + {0x0000a030, 0x03890388}, + {0x0000a034, 0x038b038a}, + {0x0000a038, 0x038d038c}, + {0x0000a03c, 0x03910390}, + {0x0000a040, 0x03930392}, + {0x0000a044, 0x03950394}, + {0x0000a048, 0x00000396}, + {0x0000a04c, 0x00000000}, + {0x0000a050, 0x00000000}, + {0x0000a054, 0x00000000}, + {0x0000a058, 0x00000000}, + {0x0000a05c, 0x00000000}, + {0x0000a060, 0x00000000}, + {0x0000a064, 0x00000000}, + {0x0000a068, 0x00000000}, + {0x0000a06c, 0x00000000}, + {0x0000a070, 0x00000000}, + {0x0000a074, 0x00000000}, + {0x0000a078, 0x00000000}, + {0x0000a07c, 0x00000000}, + {0x0000a080, 0x28282828}, + {0x0000a084, 0x28282828}, + {0x0000a088, 0x28282828}, + {0x0000a08c, 0x28282828}, + {0x0000a090, 0x28282828}, + {0x0000a094, 0x24242428}, + {0x0000a098, 0x171e1e1e}, + {0x0000a09c, 0x02020b0b}, + {0x0000a0a0, 0x02020202}, + {0x0000a0a4, 0x00000000}, + {0x0000a0a8, 0x00000000}, + {0x0000a0ac, 0x00000000}, + {0x0000a0b0, 0x00000000}, + {0x0000a0b4, 0x00000000}, + {0x0000a0b8, 0x00000000}, + {0x0000a0bc, 0x00000000}, + {0x0000a0c0, 0x22072208}, + {0x0000a0c4, 0x22052206}, + {0x0000a0c8, 0x22032204}, + {0x0000a0cc, 0x22012202}, + {0x0000a0d0, 0x221f2200}, + {0x0000a0d4, 0x221d221e}, + {0x0000a0d8, 0x33023303}, + {0x0000a0dc, 0x33003301}, + {0x0000a0e0, 0x331e331f}, + {0x0000a0e4, 0x4402331d}, + {0x0000a0e8, 0x44004401}, + {0x0000a0ec, 0x441e441f}, + {0x0000a0f0, 0x55025503}, + {0x0000a0f4, 0x55005501}, + {0x0000a0f8, 0x551e551f}, + {0x0000a0fc, 0x6602551d}, + {0x0000a100, 0x66006601}, + {0x0000a104, 0x661e661f}, + {0x0000a108, 0x7703661d}, + {0x0000a10c, 0x77017702}, + {0x0000a110, 0x00007700}, + {0x0000a114, 0x00000000}, + {0x0000a118, 0x00000000}, + {0x0000a11c, 0x00000000}, + {0x0000a120, 0x00000000}, + {0x0000a124, 0x00000000}, + {0x0000a128, 0x00000000}, + {0x0000a12c, 0x00000000}, + {0x0000a130, 0x00000000}, + {0x0000a134, 0x00000000}, + {0x0000a138, 0x00000000}, + {0x0000a13c, 0x00000000}, + {0x0000a140, 0x001f0000}, + {0x0000a144, 0x111f1100}, + {0x0000a148, 0x111d111e}, + {0x0000a14c, 0x111b111c}, + {0x0000a150, 0x22032204}, + {0x0000a154, 0x22012202}, + {0x0000a158, 0x221f2200}, + {0x0000a15c, 0x221d221e}, + {0x0000a160, 0x33013302}, + {0x0000a164, 0x331f3300}, + {0x0000a168, 0x4402331e}, + {0x0000a16c, 0x44004401}, + {0x0000a170, 0x441e441f}, + {0x0000a174, 0x55015502}, + {0x0000a178, 0x551f5500}, + {0x0000a17c, 0x6602551e}, + {0x0000a180, 0x66006601}, + {0x0000a184, 0x661e661f}, + {0x0000a188, 0x7703661d}, + {0x0000a18c, 0x77017702}, + {0x0000a190, 0x00007700}, + {0x0000a194, 0x00000000}, + {0x0000a198, 0x00000000}, + {0x0000a19c, 0x00000000}, + {0x0000a1a0, 0x00000000}, + {0x0000a1a4, 0x00000000}, + {0x0000a1a8, 0x00000000}, + {0x0000a1ac, 0x00000000}, + {0x0000a1b0, 0x00000000}, + {0x0000a1b4, 0x00000000}, + {0x0000a1b8, 0x00000000}, + {0x0000a1bc, 0x00000000}, + {0x0000a1c0, 0x00000000}, + {0x0000a1c4, 0x00000000}, + {0x0000a1c8, 0x00000000}, + {0x0000a1cc, 0x00000000}, + {0x0000a1d0, 0x00000000}, + {0x0000a1d4, 0x00000000}, + {0x0000a1d8, 0x00000000}, + {0x0000a1dc, 0x00000000}, + {0x0000a1e0, 0x00000000}, + {0x0000a1e4, 0x00000000}, + {0x0000a1e8, 0x00000000}, + {0x0000a1ec, 0x00000000}, + {0x0000a1f0, 0x00000396}, + {0x0000a1f4, 0x00000396}, + {0x0000a1f8, 0x00000396}, + {0x0000a1fc, 0x00000296}, +}; + +static const u32 ar9331_1p1_baseband_core[][2] = { + /* Addr allmodes */ + {0x00009800, 0xafe68e30}, + {0x00009804, 0xfd14e000}, + {0x00009808, 0x9c0a8f6b}, + {0x0000980c, 0x04800000}, + {0x00009814, 0x9280c00a}, + {0x00009818, 0x00000000}, + {0x0000981c, 0x00020028}, + {0x00009834, 0x5f3ca3de}, + {0x00009838, 0x0108ecff}, + {0x0000983c, 0x14750600}, + {0x00009880, 0x201fff00}, + {0x00009884, 0x00001042}, + {0x000098a4, 0x00200400}, + {0x000098b0, 0x32840bbe}, + {0x000098d0, 0x004b6a8e}, + {0x000098d4, 0x00000820}, + {0x000098dc, 0x00000000}, + {0x000098f0, 0x00000000}, + {0x000098f4, 0x00000000}, + {0x00009c04, 0x00000000}, + {0x00009c08, 0x03200000}, + {0x00009c0c, 0x00000000}, + {0x00009c10, 0x00000000}, + {0x00009c14, 0x00046384}, + {0x00009c18, 0x05b6b440}, + {0x00009c1c, 0x00b6b440}, + {0x00009d00, 0xc080a333}, + {0x00009d04, 0x40206c10}, + {0x00009d08, 0x009c4060}, + {0x00009d0c, 0x1883800a}, + {0x00009d10, 0x01834061}, + {0x00009d14, 0x00c00400}, + {0x00009d18, 0x00000000}, + {0x00009e08, 0x0038233c}, + {0x00009e24, 0x9927b515}, + {0x00009e28, 0x12ef0200}, + {0x00009e30, 0x06336f77}, + {0x00009e34, 0x6af6532f}, + {0x00009e38, 0x0cc80c00}, + {0x00009e40, 0x0d261820}, + {0x00009e4c, 0x00001004}, + {0x00009e50, 0x00ff03f1}, + {0x00009fc0, 0x803e4788}, + {0x00009fc4, 0x0001efb5}, + {0x00009fcc, 0x40000014}, + {0x0000a20c, 0x00000000}, + {0x0000a220, 0x00000000}, + {0x0000a224, 0x00000000}, + {0x0000a228, 0x10002310}, + {0x0000a23c, 0x00000000}, + {0x0000a244, 0x0c000000}, + {0x0000a2a0, 0x00000001}, + {0x0000a2c0, 0x00000001}, + {0x0000a2c8, 0x00000000}, + {0x0000a2cc, 0x18c43433}, + {0x0000a2d4, 0x00000000}, + {0x0000a2dc, 0x00000000}, + {0x0000a2e0, 0x00000000}, + {0x0000a2e4, 0x00000000}, + {0x0000a2e8, 0x00000000}, + {0x0000a2ec, 0x00000000}, + {0x0000a2f0, 0x00000000}, + {0x0000a2f4, 0x00000000}, + {0x0000a2f8, 0x00000000}, + {0x0000a344, 0x00000000}, + {0x0000a34c, 0x00000000}, + {0x0000a350, 0x0000a000}, + {0x0000a364, 0x00000000}, + {0x0000a370, 0x00000000}, + {0x0000a390, 0x00000001}, + {0x0000a394, 0x00000444}, + {0x0000a398, 0x001f0e0f}, + {0x0000a39c, 0x0075393f}, + {0x0000a3a0, 0xb79f6427}, + {0x0000a3a4, 0x00000000}, + {0x0000a3a8, 0xaaaaaaaa}, + {0x0000a3ac, 0x3c466478}, + {0x0000a3c0, 0x20202020}, + {0x0000a3c4, 0x22222220}, + {0x0000a3c8, 0x20200020}, + {0x0000a3cc, 0x20202020}, + {0x0000a3d0, 0x20202020}, + {0x0000a3d4, 0x20202020}, + {0x0000a3d8, 0x20202020}, + {0x0000a3dc, 0x20202020}, + {0x0000a3e0, 0x20202020}, + {0x0000a3e4, 0x20202020}, + {0x0000a3e8, 0x20202020}, + {0x0000a3ec, 0x20202020}, + {0x0000a3f0, 0x00000000}, + {0x0000a3f4, 0x00000006}, + {0x0000a3f8, 0x0cdbd380}, + {0x0000a3fc, 0x000f0f01}, + {0x0000a400, 0x8fa91f01}, + {0x0000a404, 0x00000000}, + {0x0000a408, 0x0e79e5c6}, + {0x0000a40c, 0x00820820}, + {0x0000a414, 0x1ce739ce}, + {0x0000a418, 0x2d001dce}, + {0x0000a41c, 0x1ce739ce}, + {0x0000a420, 0x000001ce}, + {0x0000a424, 0x1ce739ce}, + {0x0000a428, 0x000001ce}, + {0x0000a42c, 0x1ce739ce}, + {0x0000a430, 0x1ce739ce}, + {0x0000a434, 0x00000000}, + {0x0000a438, 0x00001801}, + {0x0000a43c, 0x00000000}, + {0x0000a440, 0x00000000}, + {0x0000a444, 0x00000000}, + {0x0000a448, 0x04000000}, + {0x0000a44c, 0x00000001}, + {0x0000a450, 0x00010000}, + {0x0000a458, 0x00000000}, + {0x0000a640, 0x00000000}, + {0x0000a644, 0x3fad9d74}, + {0x0000a648, 0x0048060a}, + {0x0000a64c, 0x00003c37}, + {0x0000a670, 0x03020100}, + {0x0000a674, 0x09080504}, + {0x0000a678, 0x0d0c0b0a}, + {0x0000a67c, 0x13121110}, + {0x0000a680, 0x31301514}, + {0x0000a684, 0x35343332}, + {0x0000a688, 0x00000036}, + {0x0000a690, 0x00000838}, + {0x0000a7c0, 0x00000000}, + {0x0000a7c4, 0xfffffffc}, + {0x0000a7c8, 0x00000000}, + {0x0000a7cc, 0x00000000}, + {0x0000a7d0, 0x00000000}, + {0x0000a7d4, 0x00000004}, + {0x0000a7dc, 0x00000001}, +}; + +static const u32 ar9331_modes_high_power_tx_gain_1p1[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2d8, 0x7999a83a, 0x7999a83a, 0x7999a83a, 0x7999a83a}, + {0x0000a2dc, 0xffff2a52, 0xffff2a52, 0xffff2a52, 0xffff2a52}, + {0x0000a2e0, 0xffffcc84, 0xffffcc84, 0xffffcc84, 0xffffcc84}, + {0x0000a2e4, 0xfffff000, 0xfffff000, 0xfffff000, 0xfffff000}, + {0x0000a2e8, 0xfffe0000, 0xfffe0000, 0xfffe0000, 0xfffe0000}, + {0x0000a410, 0x000050d7, 0x000050d7, 0x000050d0, 0x000050d0}, + {0x0000a500, 0x00022200, 0x00022200, 0x00000000, 0x00000000}, + {0x0000a504, 0x05062002, 0x05062002, 0x04000002, 0x04000002}, + {0x0000a508, 0x0c002e00, 0x0c002e00, 0x08000004, 0x08000004}, + {0x0000a50c, 0x11062202, 0x11062202, 0x0d000200, 0x0d000200}, + {0x0000a510, 0x17022e00, 0x17022e00, 0x11000202, 0x11000202}, + {0x0000a514, 0x1d000ec2, 0x1d000ec2, 0x15000400, 0x15000400}, + {0x0000a518, 0x25020ec0, 0x25020ec0, 0x19000402, 0x19000402}, + {0x0000a51c, 0x2b020ec3, 0x2b020ec3, 0x1d000404, 0x1d000404}, + {0x0000a520, 0x2f001f04, 0x2f001f04, 0x23000a00, 0x23000a00}, + {0x0000a524, 0x35001fc4, 0x35001fc4, 0x27000a02, 0x27000a02}, + {0x0000a528, 0x3c022f04, 0x3c022f04, 0x2b000a04, 0x2b000a04}, + {0x0000a52c, 0x41023e85, 0x41023e85, 0x2d000a20, 0x2d000a20}, + {0x0000a530, 0x48023ec6, 0x48023ec6, 0x31000a22, 0x31000a22}, + {0x0000a534, 0x4d023f01, 0x4d023f01, 0x35000a24, 0x35000a24}, + {0x0000a538, 0x53023f4b, 0x53023f4b, 0x38000a43, 0x38000a43}, + {0x0000a53c, 0x5a027f09, 0x5a027f09, 0x3b000e42, 0x3b000e42}, + {0x0000a540, 0x5f027fc9, 0x5f027fc9, 0x3f000e44, 0x3f000e44}, + {0x0000a544, 0x6502feca, 0x6502feca, 0x42000e64, 0x42000e64}, + {0x0000a548, 0x6b02ff4a, 0x6b02ff4a, 0x46000e66, 0x46000e66}, + {0x0000a54c, 0x7203feca, 0x7203feca, 0x4a000ea6, 0x4a000ea6}, + {0x0000a550, 0x7703ff0b, 0x7703ff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a554, 0x7d06ffcb, 0x7d06ffcb, 0x4a000ea6, 0x4a000ea6}, + {0x0000a558, 0x8407ff0b, 0x8407ff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a55c, 0x8907ffcb, 0x8907ffcb, 0x4a000ea6, 0x4a000ea6}, + {0x0000a560, 0x900fff0b, 0x900fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a564, 0x960fffcb, 0x960fffcb, 0x4a000ea6, 0x4a000ea6}, + {0x0000a568, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a56c, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a570, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a574, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a578, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a57c, 0x9c1fff0b, 0x9c1fff0b, 0x4a000ea6, 0x4a000ea6}, + {0x0000a580, 0x00022200, 0x00022200, 0x00000000, 0x00000000}, + {0x0000a584, 0x05062002, 0x05062002, 0x04000002, 0x04000002}, + {0x0000a588, 0x0c002e00, 0x0c002e00, 0x08000004, 0x08000004}, + {0x0000a58c, 0x11062202, 0x11062202, 0x0b000200, 0x0b000200}, + {0x0000a590, 0x17022e00, 0x17022e00, 0x0f000202, 0x0f000202}, + {0x0000a594, 0x1d000ec2, 0x1d000ec2, 0x11000400, 0x11000400}, + {0x0000a598, 0x25020ec0, 0x25020ec0, 0x15000402, 0x15000402}, + {0x0000a59c, 0x2b020ec3, 0x2b020ec3, 0x19000404, 0x19000404}, + {0x0000a5a0, 0x2f001f04, 0x2f001f04, 0x1b000603, 0x1b000603}, + {0x0000a5a4, 0x35001fc4, 0x35001fc4, 0x1f000a02, 0x1f000a02}, + {0x0000a5a8, 0x3c022f04, 0x3c022f04, 0x23000a04, 0x23000a04}, + {0x0000a5ac, 0x41023e85, 0x41023e85, 0x26000a20, 0x26000a20}, + {0x0000a5b0, 0x48023ec6, 0x48023ec6, 0x2a000e20, 0x2a000e20}, + {0x0000a5b4, 0x4d023f01, 0x4d023f01, 0x2e000e22, 0x2e000e22}, + {0x0000a5b8, 0x53023f4b, 0x53023f4b, 0x31000e24, 0x31000e24}, + {0x0000a5bc, 0x5a027f09, 0x5a027f09, 0x34001640, 0x34001640}, + {0x0000a5c0, 0x5f027fc9, 0x5f027fc9, 0x38001660, 0x38001660}, + {0x0000a5c4, 0x6502feca, 0x6502feca, 0x3b001861, 0x3b001861}, + {0x0000a5c8, 0x6b02ff4a, 0x6b02ff4a, 0x3e001a81, 0x3e001a81}, + {0x0000a5cc, 0x7203feca, 0x7203feca, 0x42001a83, 0x42001a83}, + {0x0000a5d0, 0x7703ff0b, 0x7703ff0b, 0x44001c84, 0x44001c84}, + {0x0000a5d4, 0x7d06ffcb, 0x7d06ffcb, 0x48001ce3, 0x48001ce3}, + {0x0000a5d8, 0x8407ff0b, 0x8407ff0b, 0x4c001ce5, 0x4c001ce5}, + {0x0000a5dc, 0x8907ffcb, 0x8907ffcb, 0x50001ce9, 0x50001ce9}, + {0x0000a5e0, 0x900fff0b, 0x900fff0b, 0x54001ceb, 0x54001ceb}, + {0x0000a5e4, 0x960fffcb, 0x960fffcb, 0x56001eec, 0x56001eec}, + {0x0000a5e8, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5ec, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5f0, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5f4, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5f8, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5fc, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x01404000, 0x01404000, 0x01404000, 0x01404000}, + {0x0000a618, 0x01404501, 0x01404501, 0x01404501, 0x01404501}, + {0x0000a61c, 0x02008802, 0x02008802, 0x02008802, 0x02008802}, + {0x0000a620, 0x0280c802, 0x0280c802, 0x0280c802, 0x0280c802}, + {0x0000a624, 0x03010a03, 0x03010a03, 0x03010a03, 0x03010a03}, + {0x0000a628, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x0000a62c, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x0000a630, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x0000a634, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x0000a638, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x0000a63c, 0x03010c04, 0x03010c04, 0x03010c04, 0x03010c04}, + {0x00016044, 0x034922db, 0x034922db, 0x034922db, 0x034922db}, + {0x00016284, 0x14d3f000, 0x14d3f000, 0x14d3f000, 0x14d3f000}, +}; + +#define ar9331_1p1_mac_postamble ar9300_2p2_mac_postamble + +static const u32 ar9331_1p1_soc_preamble[][2] = { + /* Addr allmodes */ + {0x00007020, 0x00000000}, + {0x00007034, 0x00000002}, + {0x00007038, 0x000002f8}, +}; + +static const u32 ar9331_1p1_xtal_40M[][2] = { + /* Addr allmodes */ + {0x00007038, 0x000004c2}, + {0x00008244, 0x0010f400}, + {0x0000824c, 0x0001e800}, + {0x0001609c, 0x0b283f31}, +}; + +static const u32 ar9331_1p1_mac_core[][2] = { + /* Addr allmodes */ + {0x00000008, 0x00000000}, + {0x00000030, 0x00020085}, + {0x00000034, 0x00000005}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000000}, + {0x00000048, 0x00000008}, + {0x0000004c, 0x00000010}, + {0x00000050, 0x00000000}, + {0x00001040, 0x002ffc0f}, + {0x00001044, 0x002ffc0f}, + {0x00001048, 0x002ffc0f}, + {0x0000104c, 0x002ffc0f}, + {0x00001050, 0x002ffc0f}, + {0x00001054, 0x002ffc0f}, + {0x00001058, 0x002ffc0f}, + {0x0000105c, 0x002ffc0f}, + {0x00001060, 0x002ffc0f}, + {0x00001064, 0x002ffc0f}, + {0x000010f0, 0x00000100}, + {0x00001270, 0x00000000}, + {0x000012b0, 0x00000000}, + {0x000012f0, 0x00000000}, + {0x0000143c, 0x00000000}, + {0x0000147c, 0x00000000}, + {0x00008000, 0x00000000}, + {0x00008004, 0x00000000}, + {0x00008008, 0x00000000}, + {0x0000800c, 0x00000000}, + {0x00008018, 0x00000000}, + {0x00008020, 0x00000000}, + {0x00008038, 0x00000000}, + {0x0000803c, 0x00000000}, + {0x00008040, 0x00000000}, + {0x00008044, 0x00000000}, + {0x00008048, 0x00000000}, + {0x0000804c, 0xffffffff}, + {0x00008054, 0x00000000}, + {0x00008058, 0x00000000}, + {0x0000805c, 0x000fc78f}, + {0x00008060, 0x0000000f}, + {0x00008064, 0x00000000}, + {0x00008070, 0x00000310}, + {0x00008074, 0x00000020}, + {0x00008078, 0x00000000}, + {0x0000809c, 0x0000000f}, + {0x000080a0, 0x00000000}, + {0x000080a4, 0x02ff0000}, + {0x000080a8, 0x0e070605}, + {0x000080ac, 0x0000000d}, + {0x000080b0, 0x00000000}, + {0x000080b4, 0x00000000}, + {0x000080b8, 0x00000000}, + {0x000080bc, 0x00000000}, + {0x000080c0, 0x2a800000}, + {0x000080c4, 0x06900168}, + {0x000080c8, 0x13881c20}, + {0x000080cc, 0x01f40000}, + {0x000080d0, 0x00252500}, + {0x000080d4, 0x00a00000}, + {0x000080d8, 0x00400000}, + {0x000080dc, 0x00000000}, + {0x000080e0, 0xffffffff}, + {0x000080e4, 0x0000ffff}, + {0x000080e8, 0x3f3f3f3f}, + {0x000080ec, 0x00000000}, + {0x000080f0, 0x00000000}, + {0x000080f4, 0x00000000}, + {0x000080fc, 0x00020000}, + {0x00008100, 0x00000000}, + {0x00008108, 0x00000052}, + {0x0000810c, 0x00000000}, + {0x00008110, 0x00000000}, + {0x00008114, 0x000007ff}, + {0x00008118, 0x000000aa}, + {0x0000811c, 0x00003210}, + {0x00008124, 0x00000000}, + {0x00008128, 0x00000000}, + {0x0000812c, 0x00000000}, + {0x00008130, 0x00000000}, + {0x00008134, 0x00000000}, + {0x00008138, 0x00000000}, + {0x0000813c, 0x0000ffff}, + {0x00008144, 0xffffffff}, + {0x00008168, 0x00000000}, + {0x0000816c, 0x00000000}, + {0x00008170, 0x18486200}, + {0x00008174, 0x33332210}, + {0x00008178, 0x00000000}, + {0x0000817c, 0x00020000}, + {0x000081c0, 0x00000000}, + {0x000081c4, 0x33332210}, + {0x000081c8, 0x00000000}, + {0x000081cc, 0x00000000}, + {0x000081d4, 0x00000000}, + {0x000081ec, 0x00000000}, + {0x000081f0, 0x00000000}, + {0x000081f4, 0x00000000}, + {0x000081f8, 0x00000000}, + {0x000081fc, 0x00000000}, + {0x00008240, 0x00100000}, + {0x00008248, 0x00000800}, + {0x00008250, 0x00000000}, + {0x00008254, 0x00000000}, + {0x00008258, 0x00000000}, + {0x0000825c, 0x40000000}, + {0x00008260, 0x00080922}, + {0x00008264, 0x9d400010}, + {0x00008268, 0xffffffff}, + {0x0000826c, 0x0000ffff}, + {0x00008270, 0x00000000}, + {0x00008274, 0x40000000}, + {0x00008278, 0x003e4180}, + {0x0000827c, 0x00000004}, + {0x00008284, 0x0000002c}, + {0x00008288, 0x0000002c}, + {0x0000828c, 0x000000ff}, + {0x00008294, 0x00000000}, + {0x00008298, 0x00000000}, + {0x0000829c, 0x00000000}, + {0x00008300, 0x00000140}, + {0x00008314, 0x00000000}, + {0x0000831c, 0x0000010d}, + {0x00008328, 0x00000000}, + {0x0000832c, 0x00000007}, + {0x00008330, 0x00000302}, + {0x00008334, 0x00000700}, + {0x00008338, 0x00ff0000}, + {0x0000833c, 0x02400000}, + {0x00008340, 0x000107ff}, + {0x00008344, 0xaa48105b}, + {0x00008348, 0x008f0000}, + {0x0000835c, 0x00000000}, + {0x00008360, 0xffffffff}, + {0x00008364, 0xffffffff}, + {0x00008368, 0x00000000}, + {0x00008370, 0x00000000}, + {0x00008374, 0x000000ff}, + {0x00008378, 0x00000000}, + {0x0000837c, 0x00000000}, + {0x00008380, 0xffffffff}, + {0x00008384, 0xffffffff}, + {0x00008390, 0xffffffff}, + {0x00008394, 0xffffffff}, + {0x00008398, 0x00000000}, + {0x0000839c, 0x00000000}, + {0x000083a0, 0x00000000}, + {0x000083a4, 0x0000fa14}, + {0x000083a8, 0x000f0c00}, + {0x000083ac, 0x33332210}, + {0x000083b0, 0x33332210}, + {0x000083b4, 0x33332210}, + {0x000083b8, 0x33332210}, + {0x000083bc, 0x00000000}, + {0x000083c0, 0x00000000}, + {0x000083c4, 0x00000000}, + {0x000083c8, 0x00000000}, + {0x000083cc, 0x00000200}, + {0x000083d0, 0x000301ff}, +}; + +static const u32 ar9331_common_rx_gain_1p1[][2] = { + /* Addr allmodes */ + {0x00009e18, 0x05000000}, + {0x0000a000, 0x00060005}, + {0x0000a004, 0x00810080}, + {0x0000a008, 0x00830082}, + {0x0000a00c, 0x00850084}, + {0x0000a010, 0x01820181}, + {0x0000a014, 0x01840183}, + {0x0000a018, 0x01880185}, + {0x0000a01c, 0x018a0189}, + {0x0000a020, 0x02850284}, + {0x0000a024, 0x02890288}, + {0x0000a028, 0x028b028a}, + {0x0000a02c, 0x03850384}, + {0x0000a030, 0x03890388}, + {0x0000a034, 0x038b038a}, + {0x0000a038, 0x038d038c}, + {0x0000a03c, 0x03910390}, + {0x0000a040, 0x03930392}, + {0x0000a044, 0x03950394}, + {0x0000a048, 0x00000396}, + {0x0000a04c, 0x00000000}, + {0x0000a050, 0x00000000}, + {0x0000a054, 0x00000000}, + {0x0000a058, 0x00000000}, + {0x0000a05c, 0x00000000}, + {0x0000a060, 0x00000000}, + {0x0000a064, 0x00000000}, + {0x0000a068, 0x00000000}, + {0x0000a06c, 0x00000000}, + {0x0000a070, 0x00000000}, + {0x0000a074, 0x00000000}, + {0x0000a078, 0x00000000}, + {0x0000a07c, 0x00000000}, + {0x0000a080, 0x28282828}, + {0x0000a084, 0x28282828}, + {0x0000a088, 0x28282828}, + {0x0000a08c, 0x28282828}, + {0x0000a090, 0x28282828}, + {0x0000a094, 0x24242428}, + {0x0000a098, 0x171e1e1e}, + {0x0000a09c, 0x02020b0b}, + {0x0000a0a0, 0x02020202}, + {0x0000a0a4, 0x00000000}, + {0x0000a0a8, 0x00000000}, + {0x0000a0ac, 0x00000000}, + {0x0000a0b0, 0x00000000}, + {0x0000a0b4, 0x00000000}, + {0x0000a0b8, 0x00000000}, + {0x0000a0bc, 0x00000000}, + {0x0000a0c0, 0x22072208}, + {0x0000a0c4, 0x22052206}, + {0x0000a0c8, 0x22032204}, + {0x0000a0cc, 0x22012202}, + {0x0000a0d0, 0x221f2200}, + {0x0000a0d4, 0x221d221e}, + {0x0000a0d8, 0x33023303}, + {0x0000a0dc, 0x33003301}, + {0x0000a0e0, 0x331e331f}, + {0x0000a0e4, 0x4402331d}, + {0x0000a0e8, 0x44004401}, + {0x0000a0ec, 0x441e441f}, + {0x0000a0f0, 0x55025503}, + {0x0000a0f4, 0x55005501}, + {0x0000a0f8, 0x551e551f}, + {0x0000a0fc, 0x6602551d}, + {0x0000a100, 0x66006601}, + {0x0000a104, 0x661e661f}, + {0x0000a108, 0x7703661d}, + {0x0000a10c, 0x77017702}, + {0x0000a110, 0x00007700}, + {0x0000a114, 0x00000000}, + {0x0000a118, 0x00000000}, + {0x0000a11c, 0x00000000}, + {0x0000a120, 0x00000000}, + {0x0000a124, 0x00000000}, + {0x0000a128, 0x00000000}, + {0x0000a12c, 0x00000000}, + {0x0000a130, 0x00000000}, + {0x0000a134, 0x00000000}, + {0x0000a138, 0x00000000}, + {0x0000a13c, 0x00000000}, + {0x0000a140, 0x001f0000}, + {0x0000a144, 0x111f1100}, + {0x0000a148, 0x111d111e}, + {0x0000a14c, 0x111b111c}, + {0x0000a150, 0x22032204}, + {0x0000a154, 0x22012202}, + {0x0000a158, 0x221f2200}, + {0x0000a15c, 0x221d221e}, + {0x0000a160, 0x33013302}, + {0x0000a164, 0x331f3300}, + {0x0000a168, 0x4402331e}, + {0x0000a16c, 0x44004401}, + {0x0000a170, 0x441e441f}, + {0x0000a174, 0x55015502}, + {0x0000a178, 0x551f5500}, + {0x0000a17c, 0x6602551e}, + {0x0000a180, 0x66006601}, + {0x0000a184, 0x661e661f}, + {0x0000a188, 0x7703661d}, + {0x0000a18c, 0x77017702}, + {0x0000a190, 0x00007700}, + {0x0000a194, 0x00000000}, + {0x0000a198, 0x00000000}, + {0x0000a19c, 0x00000000}, + {0x0000a1a0, 0x00000000}, + {0x0000a1a4, 0x00000000}, + {0x0000a1a8, 0x00000000}, + {0x0000a1ac, 0x00000000}, + {0x0000a1b0, 0x00000000}, + {0x0000a1b4, 0x00000000}, + {0x0000a1b8, 0x00000000}, + {0x0000a1bc, 0x00000000}, + {0x0000a1c0, 0x00000000}, + {0x0000a1c4, 0x00000000}, + {0x0000a1c8, 0x00000000}, + {0x0000a1cc, 0x00000000}, + {0x0000a1d0, 0x00000000}, + {0x0000a1d4, 0x00000000}, + {0x0000a1d8, 0x00000000}, + {0x0000a1dc, 0x00000000}, + {0x0000a1e0, 0x00000000}, + {0x0000a1e4, 0x00000000}, + {0x0000a1e8, 0x00000000}, + {0x0000a1ec, 0x00000000}, + {0x0000a1f0, 0x00000396}, + {0x0000a1f4, 0x00000396}, + {0x0000a1f8, 0x00000396}, + {0x0000a1fc, 0x00000296}, +}; + +static const u32 ar9331_common_tx_gain_offset1_1[][1] = { + {0x00000000}, + {0x00000003}, + {0x00000000}, + {0x00000000}, +}; + +#endif /* INITVALS_9330_1P1_H */ diff --git a/drivers/net/wireless/ath/ath9k/ar9330_1p2_initvals.h b/drivers/net/wireless/ath/ath9k/ar9330_1p2_initvals.h new file mode 100644 index 0000000..57ed8a1 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9330_1p2_initvals.h @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef INITVALS_9330_1P2_H +#define INITVALS_9330_1P2_H + +static const u32 ar9331_modes_high_ob_db_tx_gain_1p2[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a410, 0x000050d7, 0x000050d7, 0x000050d7, 0x000050d7}, + {0x0000a500, 0x00022200, 0x00022200, 0x00000000, 0x00000000}, + {0x0000a504, 0x05062002, 0x05062002, 0x04000002, 0x04000002}, + {0x0000a508, 0x0c002e00, 0x0c002e00, 0x08000004, 0x08000004}, + {0x0000a50c, 0x11062202, 0x11062202, 0x0d000200, 0x0d000200}, + {0x0000a510, 0x17022e00, 0x17022e00, 0x11000202, 0x11000202}, + {0x0000a514, 0x1d000ec2, 0x1d000ec2, 0x15000400, 0x15000400}, + {0x0000a518, 0x25020ec0, 0x25020ec0, 0x19000402, 0x19000402}, + {0x0000a51c, 0x2b020ec3, 0x2b020ec3, 0x1d000404, 0x1d000404}, + {0x0000a520, 0x2f001f04, 0x2f001f04, 0x23000a00, 0x23000a00}, + {0x0000a524, 0x35001fc4, 0x35001fc4, 0x27000a02, 0x27000a02}, + {0x0000a528, 0x3c022f04, 0x3c022f04, 0x2b000a04, 0x2b000a04}, + {0x0000a52c, 0x41023e85, 0x41023e85, 0x3f001620, 0x3f001620}, + {0x0000a530, 0x48023ec6, 0x48023ec6, 0x41001621, 0x41001621}, + {0x0000a534, 0x4d023f01, 0x4d023f01, 0x44001640, 0x44001640}, + {0x0000a538, 0x53023f4b, 0x53023f4b, 0x46001641, 0x46001641}, + {0x0000a53c, 0x5a027f09, 0x5a027f09, 0x48001642, 0x48001642}, + {0x0000a540, 0x5f027fc9, 0x5f027fc9, 0x4b001644, 0x4b001644}, + {0x0000a544, 0x6502feca, 0x6502feca, 0x4e001a81, 0x4e001a81}, + {0x0000a548, 0x6b02ff4a, 0x6b02ff4a, 0x51001a83, 0x51001a83}, + {0x0000a54c, 0x7203feca, 0x7203feca, 0x54001c84, 0x54001c84}, + {0x0000a550, 0x7703ff0b, 0x7703ff0b, 0x57001ce3, 0x57001ce3}, + {0x0000a554, 0x7d06ffcb, 0x7d06ffcb, 0x5b001ce5, 0x5b001ce5}, + {0x0000a558, 0x8407ff0b, 0x8407ff0b, 0x5f001ce9, 0x5f001ce9}, + {0x0000a55c, 0x8907ffcb, 0x8907ffcb, 0x66001eec, 0x66001eec}, + {0x0000a560, 0x900fff0b, 0x900fff0b, 0x66001eec, 0x66001eec}, + {0x0000a564, 0x960fffcb, 0x960fffcb, 0x66001eec, 0x66001eec}, + {0x0000a568, 0x9c1fff0b, 0x9c1fff0b, 0x66001eec, 0x66001eec}, + {0x0000a56c, 0x9c1fff0b, 0x9c1fff0b, 0x66001eec, 0x66001eec}, + {0x0000a570, 0x9c1fff0b, 0x9c1fff0b, 0x66001eec, 0x66001eec}, + {0x0000a574, 0x9c1fff0b, 0x9c1fff0b, 0x66001eec, 0x66001eec}, + {0x0000a578, 0x9c1fff0b, 0x9c1fff0b, 0x66001eec, 0x66001eec}, + {0x0000a57c, 0x9c1fff0b, 0x9c1fff0b, 0x66001eec, 0x66001eec}, + {0x0000a580, 0x00022200, 0x00022200, 0x00000000, 0x00000000}, + {0x0000a584, 0x05062002, 0x05062002, 0x04000002, 0x04000002}, + {0x0000a588, 0x0c002e00, 0x0c002e00, 0x08000004, 0x08000004}, + {0x0000a58c, 0x11062202, 0x11062202, 0x0b000200, 0x0b000200}, + {0x0000a590, 0x17022e00, 0x17022e00, 0x0f000202, 0x0f000202}, + {0x0000a594, 0x1d000ec2, 0x1d000ec2, 0x11000400, 0x11000400}, + {0x0000a598, 0x25020ec0, 0x25020ec0, 0x15000402, 0x15000402}, + {0x0000a59c, 0x2b020ec3, 0x2b020ec3, 0x19000404, 0x19000404}, + {0x0000a5a0, 0x2f001f04, 0x2f001f04, 0x1b000603, 0x1b000603}, + {0x0000a5a4, 0x35001fc4, 0x35001fc4, 0x1f000a02, 0x1f000a02}, + {0x0000a5a8, 0x3c022f04, 0x3c022f04, 0x23000a04, 0x23000a04}, + {0x0000a5ac, 0x41023e85, 0x41023e85, 0x26000a20, 0x26000a20}, + {0x0000a5b0, 0x48023ec6, 0x48023ec6, 0x2a000e20, 0x2a000e20}, + {0x0000a5b4, 0x4d023f01, 0x4d023f01, 0x2e000e22, 0x2e000e22}, + {0x0000a5b8, 0x53023f4b, 0x53023f4b, 0x31000e24, 0x31000e24}, + {0x0000a5bc, 0x5a027f09, 0x5a027f09, 0x34001640, 0x34001640}, + {0x0000a5c0, 0x5f027fc9, 0x5f027fc9, 0x38001660, 0x38001660}, + {0x0000a5c4, 0x6502feca, 0x6502feca, 0x3b001861, 0x3b001861}, + {0x0000a5c8, 0x6b02ff4a, 0x6b02ff4a, 0x3e001a81, 0x3e001a81}, + {0x0000a5cc, 0x7203feca, 0x7203feca, 0x42001a83, 0x42001a83}, + {0x0000a5d0, 0x7703ff0b, 0x7703ff0b, 0x44001c84, 0x44001c84}, + {0x0000a5d4, 0x7d06ffcb, 0x7d06ffcb, 0x48001ce3, 0x48001ce3}, + {0x0000a5d8, 0x8407ff0b, 0x8407ff0b, 0x4c001ce5, 0x4c001ce5}, + {0x0000a5dc, 0x8907ffcb, 0x8907ffcb, 0x50001ce9, 0x50001ce9}, + {0x0000a5e0, 0x900fff0b, 0x900fff0b, 0x54001ceb, 0x54001ceb}, + {0x0000a5e4, 0x960fffcb, 0x960fffcb, 0x56001eec, 0x56001eec}, + {0x0000a5e8, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5ec, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5f0, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5f4, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5f8, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a5fc, 0x9c1fff0b, 0x9c1fff0b, 0x56001eec, 0x56001eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x01404000, 0x01404000, 0x01404000, 0x01404000}, + {0x0000a618, 0x02008501, 0x02008501, 0x02008501, 0x02008501}, + {0x0000a61c, 0x02008802, 0x02008802, 0x02008802, 0x02008802}, + {0x0000a620, 0x0300c802, 0x0300c802, 0x0300c802, 0x0300c802}, + {0x0000a624, 0x0300cc03, 0x0300cc03, 0x0300cc03, 0x0300cc03}, + {0x0000a628, 0x04011004, 0x04011004, 0x04011004, 0x04011004}, + {0x0000a62c, 0x04011004, 0x04011004, 0x04011004, 0x04011004}, + {0x0000a630, 0x04011004, 0x04011004, 0x04011004, 0x04011004}, + {0x0000a634, 0x04011004, 0x04011004, 0x04011004, 0x04011004}, + {0x0000a638, 0x04011004, 0x04011004, 0x04011004, 0x04011004}, + {0x0000a63c, 0x04011004, 0x04011004, 0x04011004, 0x04011004}, +}; + +#define ar9331_modes_high_power_tx_gain_1p2 ar9331_modes_high_ob_db_tx_gain_1p2 + +#define ar9331_modes_low_ob_db_tx_gain_1p2 ar9331_modes_high_power_tx_gain_1p2 + +#define ar9331_modes_lowest_ob_db_tx_gain_1p2 ar9331_modes_low_ob_db_tx_gain_1p2 + +static const u32 ar9331_1p2_baseband_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009810, 0xd00a8005, 0xd00a8005, 0xd00a8005, 0xd00a8005}, + {0x00009820, 0x206a002e, 0x206a002e, 0x206a002e, 0x206a002e}, + {0x00009824, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0}, + {0x00009828, 0x06903081, 0x06903081, 0x06903881, 0x06903881}, + {0x0000982c, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4}, + {0x00009830, 0x0000059c, 0x0000059c, 0x0000059c, 0x0000059c}, + {0x00009c00, 0x00000044, 0x00000044, 0x00000044, 0x00000044}, + {0x00009e00, 0x0372161e, 0x0372161e, 0x037216a4, 0x037216a4}, + {0x00009e04, 0x00182020, 0x00182020, 0x00182020, 0x00182020}, + {0x00009e0c, 0x6c4000e2, 0x6d4000e2, 0x6d4000e2, 0x6c4000e2}, + {0x00009e10, 0x7ec80d2e, 0x7ec80d2e, 0x7ec80d2e, 0x7ec80d2e}, + {0x00009e14, 0x31395d5e, 0x3139605e, 0x3139605e, 0x31395d5e}, + {0x00009e18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00009e1c, 0x0001cf9c, 0x0001cf9c, 0x00021f9c, 0x00021f9c}, + {0x00009e20, 0x000003b5, 0x000003b5, 0x000003ce, 0x000003ce}, + {0x00009e2c, 0x0000001c, 0x0000001c, 0x00003221, 0x00003221}, + {0x00009e3c, 0xcf946222, 0xcf946222, 0xcf946222, 0xcf946222}, + {0x00009e44, 0x02321e27, 0x02321e27, 0x02282324, 0x02282324}, + {0x00009e48, 0x5030201a, 0x5030201a, 0x50302010, 0x50302010}, + {0x00009fc8, 0x0003f000, 0x0003f000, 0x0001a000, 0x0001a000}, + {0x0000a204, 0x00003fc0, 0x00003fc4, 0x00003fc4, 0x00003fc0}, + {0x0000a208, 0x00000104, 0x00000104, 0x00000004, 0x00000004}, + {0x0000a230, 0x0000000a, 0x00000014, 0x00000016, 0x0000000b}, + {0x0000a234, 0x00000fff, 0x00000fff, 0x10000fff, 0x00000fff}, + {0x0000a238, 0xffb81018, 0xffb81018, 0xffb81018, 0xffb81018}, + {0x0000a250, 0x00000000, 0x00000000, 0x00000210, 0x00000108}, + {0x0000a254, 0x000007d0, 0x00000fa0, 0x00001130, 0x00000898}, + {0x0000a258, 0x02020002, 0x02020002, 0x02020002, 0x02020002}, + {0x0000a25c, 0x01000e0e, 0x01000e0e, 0x01000e0e, 0x01000e0e}, + {0x0000a260, 0x3a021501, 0x3a021501, 0x3a021501, 0x3a021501}, + {0x0000a264, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x0000a280, 0x00000007, 0x00000007, 0x0000000b, 0x0000000b}, + {0x0000a284, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a288, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a28c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a2c4, 0x00158d18, 0x00158d18, 0x00158d18, 0x00158d18}, + {0x0000a2d0, 0x00071981, 0x00071981, 0x00071981, 0x00071981}, + {0x0000a2d8, 0xf999a83a, 0xf999a83a, 0xf999a83a, 0xf999a83a}, + {0x0000a358, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000ae04, 0x00802020, 0x00802020, 0x00802020, 0x00802020}, + {0x0000ae18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, +}; + +static const u32 ar9331_1p2_radio_core[][2] = { + /* Addr allmodes */ + {0x00016000, 0x36db6db6}, + {0x00016004, 0x6db6db40}, + {0x00016008, 0x73800000}, + {0x0001600c, 0x00000000}, + {0x00016040, 0x7f80fff8}, + {0x00016044, 0x03d6d2db}, + {0x00016048, 0x6c924268}, + {0x0001604c, 0x000f0278}, + {0x00016050, 0x4db6db8c}, + {0x00016054, 0x6db60000}, + {0x00016080, 0x00080000}, + {0x00016084, 0x0e48048c}, + {0x00016088, 0x14214514}, + {0x0001608c, 0x119f081c}, + {0x00016090, 0x24926490}, + {0x00016098, 0xd411eb84}, + {0x000160a0, 0xc2108ffe}, + {0x000160a4, 0x812fc370}, + {0x000160a8, 0x423c8000}, + {0x000160ac, 0x24651800}, + {0x000160b0, 0x03284f3e}, + {0x000160b4, 0x92480040}, + {0x000160c0, 0x006db6db}, + {0x000160c4, 0x0186db60}, + {0x000160c8, 0x6db6db6c}, + {0x000160cc, 0x6de6c300}, + {0x000160d0, 0x14500820}, + {0x00016100, 0x04cb0001}, + {0x00016104, 0xfff80015}, + {0x00016108, 0x00080010}, + {0x0001610c, 0x00170000}, + {0x00016140, 0x10804000}, + {0x00016144, 0x01884080}, + {0x00016148, 0x000080c0}, + {0x00016280, 0x01000015}, + {0x00016284, 0x14d20000}, + {0x00016288, 0x00318000}, + {0x0001628c, 0x50000000}, + {0x00016290, 0x4b96210f}, + {0x00016380, 0x00000000}, + {0x00016384, 0x00000000}, + {0x00016388, 0x00800700}, + {0x0001638c, 0x00800700}, + {0x00016390, 0x00800700}, + {0x00016394, 0x00000000}, + {0x00016398, 0x00000000}, + {0x0001639c, 0x00000000}, + {0x000163a0, 0x00000001}, + {0x000163a4, 0x00000001}, + {0x000163a8, 0x00000000}, + {0x000163ac, 0x00000000}, + {0x000163b0, 0x00000000}, + {0x000163b4, 0x00000000}, + {0x000163b8, 0x00000000}, + {0x000163bc, 0x00000000}, + {0x000163c0, 0x000000a0}, + {0x000163c4, 0x000c0000}, + {0x000163c8, 0x14021402}, + {0x000163cc, 0x00001402}, + {0x000163d0, 0x00000000}, + {0x000163d4, 0x00000000}, +}; + +#define ar9331_1p2_baseband_core_txfir_coeff_japan_2484 ar9331_1p1_baseband_core_txfir_coeff_japan_2484 + +#define ar9331_1p2_xtal_25M ar9331_1p1_xtal_25M + +#define ar9331_1p2_xtal_40M ar9331_1p1_xtal_40M + +#define ar9331_1p2_baseband_core ar9331_1p1_baseband_core + +#define ar9331_1p2_soc_postamble ar9331_1p1_soc_postamble + +#define ar9331_1p2_mac_postamble ar9331_1p1_mac_postamble + +#define ar9331_1p2_soc_preamble ar9331_1p1_soc_preamble + +#define ar9331_1p2_mac_core ar9331_1p1_mac_core + +#define ar9331_common_wo_xlna_rx_gain_1p2 ar9331_common_wo_xlna_rx_gain_1p1 + +#define ar9331_common_rx_gain_1p2 ar9485_common_rx_gain_1_1 + +#endif /* INITVALS_9330_1P2_H */ diff --git a/drivers/net/wireless/ath/ath9k/ar9340_initvals.h b/drivers/net/wireless/ath/ath9k/ar9340_initvals.h new file mode 100644 index 0000000..25db921 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9340_initvals.h @@ -0,0 +1,1450 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef INITVALS_9340_H +#define INITVALS_9340_H + +static const u32 ar9340_1p0_radio_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x000160ac, 0xa4646800, 0xa4646800, 0xa4646800, 0xa4646800}, + {0x0001610c, 0x08000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00016140, 0x10804000, 0x10804000, 0x50804000, 0x50804000}, + {0x0001650c, 0x08000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00016540, 0x10804000, 0x10804000, 0x50804000, 0x50804000}, +}; + +static const u32 ar9340Modes_lowest_ob_db_tx_gain_table_1p0[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a504, 0x06000003, 0x06000003, 0x04000002, 0x04000002}, + {0x0000a508, 0x0a000020, 0x0a000020, 0x08000004, 0x08000004}, + {0x0000a50c, 0x10000023, 0x10000023, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x16000220, 0x16000220, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x1c000223, 0x1c000223, 0x12000400, 0x12000400}, + {0x0000a518, 0x21020220, 0x21020220, 0x16000402, 0x16000402}, + {0x0000a51c, 0x27020223, 0x27020223, 0x19000404, 0x19000404}, + {0x0000a520, 0x2b022220, 0x2b022220, 0x1c000603, 0x1c000603}, + {0x0000a524, 0x2f022222, 0x2f022222, 0x21000a02, 0x21000a02}, + {0x0000a528, 0x34022225, 0x34022225, 0x25000a04, 0x25000a04}, + {0x0000a52c, 0x3a02222a, 0x3a02222a, 0x28000a20, 0x28000a20}, + {0x0000a530, 0x3e02222c, 0x3e02222c, 0x2c000e20, 0x2c000e20}, + {0x0000a534, 0x4202242a, 0x4202242a, 0x30000e22, 0x30000e22}, + {0x0000a538, 0x4702244a, 0x4702244a, 0x34000e24, 0x34000e24}, + {0x0000a53c, 0x4b02244c, 0x4b02244c, 0x38001640, 0x38001640}, + {0x0000a540, 0x4e02246c, 0x4e02246c, 0x3c001660, 0x3c001660}, + {0x0000a544, 0x5302266c, 0x5302266c, 0x3f001861, 0x3f001861}, + {0x0000a548, 0x5702286c, 0x5702286c, 0x43001a81, 0x43001a81}, + {0x0000a54c, 0x5c04286b, 0x5c04286b, 0x47001a83, 0x47001a83}, + {0x0000a550, 0x61042a6c, 0x61042a6c, 0x4a001c84, 0x4a001c84}, + {0x0000a554, 0x66062a6c, 0x66062a6c, 0x4e001ce3, 0x4e001ce3}, + {0x0000a558, 0x6b062e6c, 0x6b062e6c, 0x52001ce5, 0x52001ce5}, + {0x0000a55c, 0x7006308c, 0x7006308c, 0x56001ce9, 0x56001ce9}, + {0x0000a560, 0x730a308a, 0x730a308a, 0x5a001ceb, 0x5a001ceb}, + {0x0000a564, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a568, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a56c, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a570, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a574, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a578, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a57c, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a580, 0x00800000, 0x00800000, 0x00800000, 0x00800000}, + {0x0000a584, 0x06800003, 0x06800003, 0x04800002, 0x04800002}, + {0x0000a588, 0x0a800020, 0x0a800020, 0x08800004, 0x08800004}, + {0x0000a58c, 0x10800023, 0x10800023, 0x0b800200, 0x0b800200}, + {0x0000a590, 0x16800220, 0x16800220, 0x0f800202, 0x0f800202}, + {0x0000a594, 0x1c800223, 0x1c800223, 0x12800400, 0x12800400}, + {0x0000a598, 0x21820220, 0x21820220, 0x16800402, 0x16800402}, + {0x0000a59c, 0x27820223, 0x27820223, 0x19800404, 0x19800404}, + {0x0000a5a0, 0x2b822220, 0x2b822220, 0x1c800603, 0x1c800603}, + {0x0000a5a4, 0x2f822222, 0x2f822222, 0x21800a02, 0x21800a02}, + {0x0000a5a8, 0x34822225, 0x34822225, 0x25800a04, 0x25800a04}, + {0x0000a5ac, 0x3a82222a, 0x3a82222a, 0x28800a20, 0x28800a20}, + {0x0000a5b0, 0x3e82222c, 0x3e82222c, 0x2c800e20, 0x2c800e20}, + {0x0000a5b4, 0x4282242a, 0x4282242a, 0x30800e22, 0x30800e22}, + {0x0000a5b8, 0x4782244a, 0x4782244a, 0x34800e24, 0x34800e24}, + {0x0000a5bc, 0x4b82244c, 0x4b82244c, 0x38801640, 0x38801640}, + {0x0000a5c0, 0x4e82246c, 0x4e82246c, 0x3c801660, 0x3c801660}, + {0x0000a5c4, 0x5382266c, 0x5382266c, 0x3f801861, 0x3f801861}, + {0x0000a5c8, 0x5782286c, 0x5782286c, 0x43801a81, 0x43801a81}, + {0x0000a5cc, 0x5c84286b, 0x5c84286b, 0x47801a83, 0x47801a83}, + {0x0000a5d0, 0x61842a6c, 0x61842a6c, 0x4a801c84, 0x4a801c84}, + {0x0000a5d4, 0x66862a6c, 0x66862a6c, 0x4e801ce3, 0x4e801ce3}, + {0x0000a5d8, 0x6b862e6c, 0x6b862e6c, 0x52801ce5, 0x52801ce5}, + {0x0000a5dc, 0x7086308c, 0x7086308c, 0x56801ce9, 0x56801ce9}, + {0x0000a5e0, 0x738a308a, 0x738a308a, 0x5a801ceb, 0x5a801ceb}, + {0x0000a5e4, 0x778a308c, 0x778a308c, 0x5d801eec, 0x5d801eec}, + {0x0000a5e8, 0x778a308c, 0x778a308c, 0x5d801eec, 0x5d801eec}, + {0x0000a5ec, 0x778a308c, 0x778a308c, 0x5d801eec, 0x5d801eec}, + {0x0000a5f0, 0x778a308c, 0x778a308c, 0x5d801eec, 0x5d801eec}, + {0x0000a5f4, 0x778a308c, 0x778a308c, 0x5d801eec, 0x5d801eec}, + {0x0000a5f8, 0x778a308c, 0x778a308c, 0x5d801eec, 0x5d801eec}, + {0x0000a5fc, 0x778a308c, 0x778a308c, 0x5d801eec, 0x5d801eec}, + {0x00016044, 0x056db2db, 0x056db2db, 0x056db2db, 0x056db2db}, + {0x00016048, 0x24925266, 0x24925266, 0x24925266, 0x24925266}, + {0x00016444, 0x056db2db, 0x056db2db, 0x056db2db, 0x056db2db}, + {0x00016448, 0x24925266, 0x24925266, 0x24925266, 0x24925266}, +}; + +#define ar9340Modes_fast_clock_1p0 ar9300Modes_fast_clock_2p2 + +static const u32 ar9340_1p0_radio_core[][2] = { + /* Addr allmodes */ + {0x00016000, 0x36db6db6}, + {0x00016004, 0x6db6db40}, + {0x00016008, 0x73f00000}, + {0x0001600c, 0x00000000}, + {0x00016040, 0x7f80fff8}, + {0x00016044, 0x03b6d2db}, + {0x00016048, 0x24925266}, + {0x0001604c, 0x000f0278}, + {0x00016050, 0x6db6db6c}, + {0x00016054, 0x6db60000}, + {0x00016080, 0x00080000}, + {0x00016084, 0x0e48048c}, + {0x00016088, 0x14214514}, + {0x0001608c, 0x119f081c}, + {0x00016090, 0x24926490}, + {0x00016094, 0x00000000}, + {0x00016098, 0xd411eb84}, + {0x0001609c, 0x03e47f32}, + {0x000160a0, 0xc2108ffe}, + {0x000160a4, 0x812fc370}, + {0x000160a8, 0x423c8000}, + {0x000160ac, 0xa4646800}, + {0x000160b0, 0x00fe7f46}, + {0x000160b4, 0x92480000}, + {0x000160c0, 0x006db6db}, + {0x000160c4, 0x6db6db60}, + {0x000160c8, 0x6db6db6c}, + {0x000160cc, 0x6de6db6c}, + {0x000160d0, 0xb6da4924}, + {0x00016100, 0x04cb0001}, + {0x00016104, 0xfff80000}, + {0x00016108, 0x00080010}, + {0x00016140, 0x50804008}, + {0x00016144, 0x01884080}, + {0x00016148, 0x000080c0}, + {0x00016280, 0x01000015}, + {0x00016284, 0x15530000}, + {0x00016288, 0x00318000}, + {0x0001628c, 0x50000000}, + {0x00016380, 0x00000000}, + {0x00016384, 0x00000000}, + {0x00016388, 0x00800700}, + {0x0001638c, 0x00800700}, + {0x00016390, 0x00800700}, + {0x00016394, 0x00000000}, + {0x00016398, 0x00000000}, + {0x0001639c, 0x00000000}, + {0x000163a0, 0x00000001}, + {0x000163a4, 0x00000001}, + {0x000163a8, 0x00000000}, + {0x000163ac, 0x00000000}, + {0x000163b0, 0x00000000}, + {0x000163b4, 0x00000000}, + {0x000163b8, 0x00000000}, + {0x000163bc, 0x00000000}, + {0x000163c0, 0x000000a0}, + {0x000163c4, 0x000c0000}, + {0x000163c8, 0x14021402}, + {0x000163cc, 0x00001402}, + {0x000163d0, 0x00000000}, + {0x000163d4, 0x00000000}, + {0x00016400, 0x36db6db6}, + {0x00016404, 0x6db6db40}, + {0x00016408, 0x73f00000}, + {0x0001640c, 0x00000000}, + {0x00016440, 0x7f80fff8}, + {0x00016444, 0x03b6d2db}, + {0x00016448, 0x24927266}, + {0x0001644c, 0x000f0278}, + {0x00016450, 0x6db6db6c}, + {0x00016454, 0x6db60000}, + {0x00016500, 0x04cb0001}, + {0x00016504, 0xfff80000}, + {0x00016508, 0x00080010}, + {0x0001650c, 0x00000000}, + {0x00016540, 0x50804008}, + {0x00016544, 0x01884080}, + {0x00016548, 0x000080c0}, + {0x00016780, 0x00000000}, + {0x00016784, 0x00000000}, + {0x00016788, 0x00800700}, + {0x0001678c, 0x00800700}, + {0x00016790, 0x00800700}, + {0x00016794, 0x00000000}, + {0x00016798, 0x00000000}, + {0x0001679c, 0x00000000}, + {0x000167a0, 0x00000001}, + {0x000167a4, 0x00000001}, + {0x000167a8, 0x00000000}, + {0x000167ac, 0x00000000}, + {0x000167b0, 0x00000000}, + {0x000167b4, 0x00000000}, + {0x000167b8, 0x00000000}, + {0x000167bc, 0x00000000}, + {0x000167c0, 0x000000a0}, + {0x000167c4, 0x000c0000}, + {0x000167c8, 0x14021402}, + {0x000167cc, 0x00001402}, + {0x000167d0, 0x00000000}, + {0x000167d4, 0x00000000}, +}; + +static const u32 ar9340_1p0_radio_core_40M[][2] = { + /* Addr allmodes */ + {0x0001609c, 0x02566f3a}, + {0x000160ac, 0xa4647c00}, + {0x000160b0, 0x01885f5a}, + {0x00008244, 0x0010f400}, + {0x0000824c, 0x0001e800}, +}; + +#define ar9340_1p0_mac_postamble ar9300_2p2_mac_postamble + +#define ar9340_1p0_soc_postamble ar9300_2p2_soc_postamble + +static const u32 ar9340_1p0_baseband_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009810, 0xd00a8005, 0xd00a8005, 0xd00a8011, 0xd00a8011}, + {0x00009820, 0x206a022e, 0x206a022e, 0x206a022e, 0x206a022e}, + {0x00009824, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0}, + {0x00009828, 0x06903081, 0x06903081, 0x06903881, 0x06903881}, + {0x0000982c, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4}, + {0x00009830, 0x0000059c, 0x0000059c, 0x0000119c, 0x0000119c}, + {0x00009c00, 0x000000c4, 0x000000c4, 0x000000c4, 0x000000c4}, + {0x00009e00, 0x0372111a, 0x0372111a, 0x037216a0, 0x037216a0}, + {0x00009e04, 0x001c2020, 0x001c2020, 0x001c2020, 0x001c2020}, + {0x00009e0c, 0x6c4000e2, 0x6d4000e2, 0x6d4000e2, 0x6c4000e2}, + {0x00009e10, 0x7ec88d2e, 0x7ec88d2e, 0x7ec88d2e, 0x7ec88d2e}, + {0x00009e14, 0x37b95d5e, 0x37b9605e, 0x3379605e, 0x33795d5e}, + {0x00009e18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00009e1c, 0x0001cf9c, 0x0001cf9c, 0x00021f9c, 0x00021f9c}, + {0x00009e20, 0x000003b5, 0x000003b5, 0x000003ce, 0x000003ce}, + {0x00009e2c, 0x0000001c, 0x0000001c, 0x00000021, 0x00000021}, + {0x00009e3c, 0xcf946220, 0xcf946220, 0xcf946222, 0xcf946222}, + {0x00009e44, 0x02321e27, 0x02321e27, 0x02291e27, 0x02291e27}, + {0x00009e48, 0x5030201a, 0x5030201a, 0x50302012, 0x50302012}, + {0x00009fc8, 0x0003f000, 0x0003f000, 0x0001a000, 0x0001a000}, + {0x0000a204, 0x00003ec0, 0x00003ec4, 0x00003ec4, 0x00003ec0}, + {0x0000a208, 0x00000104, 0x00000104, 0x00000004, 0x00000004}, + {0x0000a22c, 0x07e26a2f, 0x07e26a2f, 0x01026a2f, 0x01026a2f}, + {0x0000a230, 0x0000000a, 0x00000014, 0x00000016, 0x0000000b}, + {0x0000a234, 0x00000fff, 0x10000fff, 0x10000fff, 0x00000fff}, + {0x0000a238, 0xffb81018, 0xffb81018, 0xffb81018, 0xffb81018}, + {0x0000a250, 0x00000000, 0x00000000, 0x00000210, 0x00000108}, + {0x0000a254, 0x000007d0, 0x00000fa0, 0x00001130, 0x00000898}, + {0x0000a258, 0x02020002, 0x02020002, 0x02020002, 0x02020002}, + {0x0000a25c, 0x01000e0e, 0x01000e0e, 0x01000e0e, 0x01000e0e}, + {0x0000a260, 0x0a021501, 0x0a021501, 0x3a021501, 0x3a021501}, + {0x0000a264, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x0000a280, 0x00000007, 0x00000007, 0x0000000b, 0x0000000b}, + {0x0000a284, 0x00000000, 0x00000000, 0x00000150, 0x00000150}, + {0x0000a288, 0x00000220, 0x00000220, 0x00000110, 0x00000110}, + {0x0000a28c, 0x00011111, 0x00011111, 0x00022222, 0x00022222}, + {0x0000a2c4, 0x00158d18, 0x00158d18, 0x00158d18, 0x00158d18}, + {0x0000a2d0, 0x00041983, 0x00041983, 0x00041982, 0x00041982}, + {0x0000a2d8, 0x7999a83a, 0x7999a83a, 0x7999a83a, 0x7999a83a}, + {0x0000a358, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a830, 0x0000019c, 0x0000019c, 0x0000019c, 0x0000019c}, + {0x0000ae04, 0x001c0000, 0x001c0000, 0x001c0000, 0x001c0000}, + {0x0000ae18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000ae1c, 0x0000019c, 0x0000019c, 0x0000019c, 0x0000019c}, + {0x0000ae20, 0x000001b5, 0x000001b5, 0x000001ce, 0x000001ce}, + {0x0000b284, 0x00000000, 0x00000000, 0x00000150, 0x00000150}, +}; + +static const u32 ar9340_1p0_baseband_core[][2] = { + /* Addr allmodes */ + {0x00009800, 0xafe68e30}, + {0x00009804, 0xfd14e000}, + {0x00009808, 0x9c0a9f6b}, + {0x0000980c, 0x04900000}, + {0x00009814, 0x3280c00a}, + {0x00009818, 0x00000000}, + {0x0000981c, 0x00020028}, + {0x00009834, 0x6400a190}, + {0x00009838, 0x0108ecff}, + {0x0000983c, 0x14000600}, + {0x00009880, 0x201fff00}, + {0x00009884, 0x00001042}, + {0x000098a4, 0x00200400}, + {0x000098b0, 0x32840bbe}, + {0x000098d0, 0x004b6a8e}, + {0x000098d4, 0x00000820}, + {0x000098dc, 0x00000000}, + {0x000098f0, 0x00000000}, + {0x000098f4, 0x00000000}, + {0x00009c04, 0xff55ff55}, + {0x00009c08, 0x0320ff55}, + {0x00009c0c, 0x00000000}, + {0x00009c10, 0x00000000}, + {0x00009c14, 0x00046384}, + {0x00009c18, 0x05b6b440}, + {0x00009c1c, 0x00b6b440}, + {0x00009d00, 0xc080a333}, + {0x00009d04, 0x40206c10}, + {0x00009d08, 0x009c4060}, + {0x00009d0c, 0x9883800a}, + {0x00009d10, 0x01834061}, + {0x00009d14, 0x00c0040b}, + {0x00009d18, 0x00000000}, + {0x00009e08, 0x0038230c}, + {0x00009e24, 0x990bb515}, + {0x00009e28, 0x0c6f0000}, + {0x00009e30, 0x06336f77}, + {0x00009e34, 0x6af6532f}, + {0x00009e38, 0x0cc80c00}, + {0x00009e40, 0x0d261820}, + {0x00009e4c, 0x00001004}, + {0x00009e50, 0x00ff03f1}, + {0x00009e54, 0x00000000}, + {0x00009fc0, 0x803e4788}, + {0x00009fc4, 0x0001efb5}, + {0x00009fcc, 0x40000014}, + {0x00009fd0, 0x01193b93}, + {0x0000a20c, 0x00000000}, + {0x0000a220, 0x00000000}, + {0x0000a224, 0x00000000}, + {0x0000a228, 0x10002310}, + {0x0000a23c, 0x00000000}, + {0x0000a244, 0x0c000000}, + {0x0000a2a0, 0x00000001}, + {0x0000a2c0, 0x00000001}, + {0x0000a2c8, 0x00000000}, + {0x0000a2cc, 0x18c43433}, + {0x0000a2d4, 0x00000000}, + {0x0000a2ec, 0x00000000}, + {0x0000a2f0, 0x00000000}, + {0x0000a2f4, 0x00000000}, + {0x0000a2f8, 0x00000000}, + {0x0000a344, 0x00000000}, + {0x0000a34c, 0x00000000}, + {0x0000a350, 0x0000a000}, + {0x0000a364, 0x00000000}, + {0x0000a370, 0x00000000}, + {0x0000a390, 0x00000001}, + {0x0000a394, 0x00000444}, + {0x0000a398, 0x001f0e0f}, + {0x0000a39c, 0x0075393f}, + {0x0000a3a0, 0xb79f6427}, + {0x0000a3a4, 0x00000000}, + {0x0000a3a8, 0xaaaaaaaa}, + {0x0000a3ac, 0x3c466478}, + {0x0000a3c0, 0x20202020}, + {0x0000a3c4, 0x22222220}, + {0x0000a3c8, 0x20200020}, + {0x0000a3cc, 0x20202020}, + {0x0000a3d0, 0x20202020}, + {0x0000a3d4, 0x20202020}, + {0x0000a3d8, 0x20202020}, + {0x0000a3dc, 0x20202020}, + {0x0000a3e0, 0x20202020}, + {0x0000a3e4, 0x20202020}, + {0x0000a3e8, 0x20202020}, + {0x0000a3ec, 0x20202020}, + {0x0000a3f0, 0x00000000}, + {0x0000a3f4, 0x00000000}, + {0x0000a3f8, 0x0cdbd380}, + {0x0000a3fc, 0x000f0f01}, + {0x0000a400, 0x8fa91f01}, + {0x0000a404, 0x00000000}, + {0x0000a408, 0x0e79e5c6}, + {0x0000a40c, 0x00820820}, + {0x0000a414, 0x1ce739ce}, + {0x0000a418, 0x2d001dce}, + {0x0000a41c, 0x1ce739ce}, + {0x0000a420, 0x000001ce}, + {0x0000a424, 0x1ce739ce}, + {0x0000a428, 0x000001ce}, + {0x0000a42c, 0x1ce739ce}, + {0x0000a430, 0x1ce739ce}, + {0x0000a434, 0x00000000}, + {0x0000a438, 0x00001801}, + {0x0000a43c, 0x00100000}, + {0x0000a440, 0x00000000}, + {0x0000a444, 0x00000000}, + {0x0000a448, 0x05000080}, + {0x0000a44c, 0x00000001}, + {0x0000a450, 0x00010000}, + {0x0000a458, 0x00000000}, + {0x0000a640, 0x00000000}, + {0x0000a644, 0x3fad9d74}, + {0x0000a648, 0x0048060a}, + {0x0000a64c, 0x00003c37}, + {0x0000a670, 0x03020100}, + {0x0000a674, 0x09080504}, + {0x0000a678, 0x0d0c0b0a}, + {0x0000a67c, 0x13121110}, + {0x0000a680, 0x31301514}, + {0x0000a684, 0x35343332}, + {0x0000a688, 0x00000036}, + {0x0000a690, 0x00000838}, + {0x0000a7c0, 0x00000000}, + {0x0000a7c4, 0xfffffffc}, + {0x0000a7c8, 0x00000000}, + {0x0000a7cc, 0x00000000}, + {0x0000a7d0, 0x00000000}, + {0x0000a7d4, 0x00000004}, + {0x0000a7dc, 0x00000000}, + {0x0000a8d0, 0x004b6a8e}, + {0x0000a8d4, 0x00000820}, + {0x0000a8dc, 0x00000000}, + {0x0000a8f0, 0x00000000}, + {0x0000a8f4, 0x00000000}, + {0x0000b2d0, 0x00000080}, + {0x0000b2d4, 0x00000000}, + {0x0000b2ec, 0x00000000}, + {0x0000b2f0, 0x00000000}, + {0x0000b2f4, 0x00000000}, + {0x0000b2f8, 0x00000000}, + {0x0000b408, 0x0e79e5c0}, + {0x0000b40c, 0x00820820}, + {0x0000b420, 0x00000000}, +}; + +static const u32 ar9340Modes_high_power_tx_gain_table_1p0[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000a2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000a2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x01404000, 0x01404000, 0x01404000, 0x01404000}, + {0x0000a618, 0x01404501, 0x01404501, 0x01404501, 0x01404501}, + {0x0000a61c, 0x02008802, 0x02008802, 0x02008501, 0x02008501}, + {0x0000a620, 0x0300cc03, 0x0300cc03, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x0300cc03, 0x0300cc03, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x0300cc03, 0x0300cc03, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x03810c03, 0x03810c03, 0x04015005, 0x04015005}, + {0x0000a630, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a634, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a638, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a63c, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000b2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000b2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000b2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a410, 0x000050d8, 0x000050d8, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00002220, 0x00002220, 0x00000000, 0x00000000}, + {0x0000a504, 0x04002222, 0x04002222, 0x02000001, 0x02000001}, + {0x0000a508, 0x09002421, 0x09002421, 0x05000003, 0x05000003}, + {0x0000a50c, 0x0d002621, 0x0d002621, 0x0a000005, 0x0a000005}, + {0x0000a510, 0x13004620, 0x13004620, 0x0e000201, 0x0e000201}, + {0x0000a514, 0x19004a20, 0x19004a20, 0x11000203, 0x11000203}, + {0x0000a518, 0x1d004e20, 0x1d004e20, 0x14000401, 0x14000401}, + {0x0000a51c, 0x21005420, 0x21005420, 0x18000403, 0x18000403}, + {0x0000a520, 0x26005e20, 0x26005e20, 0x1b000602, 0x1b000602}, + {0x0000a524, 0x2b005e40, 0x2b005e40, 0x1f000802, 0x1f000802}, + {0x0000a528, 0x2f005e42, 0x2f005e42, 0x21000620, 0x21000620}, + {0x0000a52c, 0x33005e44, 0x33005e44, 0x25000820, 0x25000820}, + {0x0000a530, 0x38005e65, 0x38005e65, 0x29000822, 0x29000822}, + {0x0000a534, 0x3c005e69, 0x3c005e69, 0x2d000824, 0x2d000824}, + {0x0000a538, 0x40005e6b, 0x40005e6b, 0x30000828, 0x30000828}, + {0x0000a53c, 0x44005e6d, 0x44005e6d, 0x3400082a, 0x3400082a}, + {0x0000a540, 0x49005e72, 0x49005e72, 0x38000849, 0x38000849}, + {0x0000a544, 0x4e005eb2, 0x4e005eb2, 0x3b000a2c, 0x3b000a2c}, + {0x0000a548, 0x53005f12, 0x53005f12, 0x3e000e2b, 0x3e000e2b}, + {0x0000a54c, 0x59025eb5, 0x59025eb5, 0x42000e2d, 0x42000e2d}, + {0x0000a550, 0x5e025f12, 0x5e025f12, 0x4500124a, 0x4500124a}, + {0x0000a554, 0x61027f12, 0x61027f12, 0x4900124c, 0x4900124c}, + {0x0000a558, 0x6702bf12, 0x6702bf12, 0x4c00126c, 0x4c00126c}, + {0x0000a55c, 0x6b02bf14, 0x6b02bf14, 0x4f00128c, 0x4f00128c}, + {0x0000a560, 0x6f02bf16, 0x6f02bf16, 0x52001290, 0x52001290}, + {0x0000a564, 0x6f02bf16, 0x6f02bf16, 0x56001292, 0x56001292}, + {0x0000a568, 0x6f02bf16, 0x6f02bf16, 0x56001292, 0x56001292}, + {0x0000a56c, 0x6f02bf16, 0x6f02bf16, 0x56001292, 0x56001292}, + {0x0000a570, 0x6f02bf16, 0x6f02bf16, 0x56001292, 0x56001292}, + {0x0000a574, 0x6f02bf16, 0x6f02bf16, 0x56001292, 0x56001292}, + {0x0000a578, 0x6f02bf16, 0x6f02bf16, 0x56001292, 0x56001292}, + {0x0000a57c, 0x6f02bf16, 0x6f02bf16, 0x56001292, 0x56001292}, + {0x0000a580, 0x00802220, 0x00802220, 0x00800000, 0x00800000}, + {0x0000a584, 0x04802222, 0x04802222, 0x02800001, 0x02800001}, + {0x0000a588, 0x09802421, 0x09802421, 0x05800003, 0x05800003}, + {0x0000a58c, 0x0d802621, 0x0d802621, 0x0a800005, 0x0a800005}, + {0x0000a590, 0x13804620, 0x13804620, 0x0e800201, 0x0e800201}, + {0x0000a594, 0x19804a20, 0x19804a20, 0x11800203, 0x11800203}, + {0x0000a598, 0x1d804e20, 0x1d804e20, 0x14800401, 0x14800401}, + {0x0000a59c, 0x21805420, 0x21805420, 0x18800403, 0x18800403}, + {0x0000a5a0, 0x26805e20, 0x26805e20, 0x1b800602, 0x1b800602}, + {0x0000a5a4, 0x2b805e40, 0x2b805e40, 0x1f800802, 0x1f800802}, + {0x0000a5a8, 0x2f805e42, 0x2f805e42, 0x21800620, 0x21800620}, + {0x0000a5ac, 0x33805e44, 0x33805e44, 0x25800820, 0x25800820}, + {0x0000a5b0, 0x38805e65, 0x38805e65, 0x29800822, 0x29800822}, + {0x0000a5b4, 0x3c805e69, 0x3c805e69, 0x2d800824, 0x2d800824}, + {0x0000a5b8, 0x40805e6b, 0x40805e6b, 0x30800828, 0x30800828}, + {0x0000a5bc, 0x44805e6d, 0x44805e6d, 0x3480082a, 0x3480082a}, + {0x0000a5c0, 0x49805e72, 0x49805e72, 0x38800849, 0x38800849}, + {0x0000a5c4, 0x4e805eb2, 0x4e805eb2, 0x3b800a2c, 0x3b800a2c}, + {0x0000a5c8, 0x53805f12, 0x53805f12, 0x3e800e2b, 0x3e800e2b}, + {0x0000a5cc, 0x59825eb2, 0x59825eb2, 0x42800e2d, 0x42800e2d}, + {0x0000a5d0, 0x5e825f12, 0x5e825f12, 0x4580124a, 0x4580124a}, + {0x0000a5d4, 0x61827f12, 0x61827f12, 0x4980124c, 0x4980124c}, + {0x0000a5d8, 0x6782bf12, 0x6782bf12, 0x4c80126c, 0x4c80126c}, + {0x0000a5dc, 0x6b82bf14, 0x6b82bf14, 0x4f80128c, 0x4f80128c}, + {0x0000a5e0, 0x6f82bf16, 0x6f82bf16, 0x52801290, 0x52801290}, + {0x0000a5e4, 0x6f82bf16, 0x6f82bf16, 0x56801292, 0x56801292}, + {0x0000a5e8, 0x6f82bf16, 0x6f82bf16, 0x56801292, 0x56801292}, + {0x0000a5ec, 0x6f82bf16, 0x6f82bf16, 0x56801292, 0x56801292}, + {0x0000a5f0, 0x6f82bf16, 0x6f82bf16, 0x56801292, 0x56801292}, + {0x0000a5f4, 0x6f82bf16, 0x6f82bf16, 0x56801292, 0x56801292}, + {0x0000a5f8, 0x6f82bf16, 0x6f82bf16, 0x56801292, 0x56801292}, + {0x0000a5fc, 0x6f82bf16, 0x6f82bf16, 0x56801292, 0x56801292}, + {0x00016044, 0x056db2db, 0x056db2db, 0x022492db, 0x022492db}, + {0x00016048, 0x24925266, 0x24925266, 0x24925266, 0x24925266}, + {0x00016444, 0x056db2db, 0x056db2db, 0x022492db, 0x022492db}, + {0x00016448, 0x24925266, 0x24925266, 0x24925266, 0x24925266}, +}; + +static const u32 ar9340Modes_high_ob_db_tx_gain_table_1p0[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0x01feee00, 0x01feee00, 0x03aaa352, 0x03aaa352}, + {0x0000a2e0, 0x0000f000, 0x0000f000, 0x03ccc584, 0x03ccc584}, + {0x0000a2e4, 0x01ff0000, 0x01ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a410, 0x000050d8, 0x000050d8, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00002220, 0x00002220, 0x00000000, 0x00000000}, + {0x0000a504, 0x04002222, 0x04002222, 0x04000002, 0x04000002}, + {0x0000a508, 0x09002421, 0x09002421, 0x08000004, 0x08000004}, + {0x0000a50c, 0x0d002621, 0x0d002621, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x13004620, 0x13004620, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x19004a20, 0x19004a20, 0x11000400, 0x11000400}, + {0x0000a518, 0x1d004e20, 0x1d004e20, 0x15000402, 0x15000402}, + {0x0000a51c, 0x21005420, 0x21005420, 0x19000404, 0x19000404}, + {0x0000a520, 0x26005e20, 0x26005e20, 0x1b000603, 0x1b000603}, + {0x0000a524, 0x2b005e40, 0x2b005e40, 0x1f000a02, 0x1f000a02}, + {0x0000a528, 0x2f005e42, 0x2f005e42, 0x23000a04, 0x23000a04}, + {0x0000a52c, 0x33005e44, 0x33005e44, 0x26000a20, 0x26000a20}, + {0x0000a530, 0x38005e65, 0x38005e65, 0x2a000e20, 0x2a000e20}, + {0x0000a534, 0x3c005e69, 0x3c005e69, 0x2e000e22, 0x2e000e22}, + {0x0000a538, 0x40005e6b, 0x40005e6b, 0x31000e24, 0x31000e24}, + {0x0000a53c, 0x44005e6d, 0x44005e6d, 0x34001640, 0x34001640}, + {0x0000a540, 0x49005e72, 0x49005e72, 0x38001660, 0x38001660}, + {0x0000a544, 0x4e005eb2, 0x4e005eb2, 0x3b001861, 0x3b001861}, + {0x0000a548, 0x53005f12, 0x53005f12, 0x3e001a81, 0x3e001a81}, + {0x0000a54c, 0x59025eb2, 0x59025eb2, 0x42001a83, 0x42001a83}, + {0x0000a550, 0x5e025f12, 0x5e025f12, 0x44001c84, 0x44001c84}, + {0x0000a554, 0x61027f12, 0x61027f12, 0x48001ce3, 0x48001ce3}, + {0x0000a558, 0x6702bf12, 0x6702bf12, 0x4c001ce5, 0x4c001ce5}, + {0x0000a55c, 0x6b02bf14, 0x6b02bf14, 0x50001ce9, 0x50001ce9}, + {0x0000a560, 0x6f02bf16, 0x6f02bf16, 0x54001ceb, 0x54001ceb}, + {0x0000a564, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a568, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a56c, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a570, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a574, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a578, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a57c, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a580, 0x00802220, 0x00802220, 0x00800000, 0x00800000}, + {0x0000a584, 0x04802222, 0x04802222, 0x04800002, 0x04800002}, + {0x0000a588, 0x09802421, 0x09802421, 0x08800004, 0x08800004}, + {0x0000a58c, 0x0d802621, 0x0d802621, 0x0b800200, 0x0b800200}, + {0x0000a590, 0x13804620, 0x13804620, 0x0f800202, 0x0f800202}, + {0x0000a594, 0x19804a20, 0x19804a20, 0x11800400, 0x11800400}, + {0x0000a598, 0x1d804e20, 0x1d804e20, 0x15800402, 0x15800402}, + {0x0000a59c, 0x21805420, 0x21805420, 0x19800404, 0x19800404}, + {0x0000a5a0, 0x26805e20, 0x26805e20, 0x1b800603, 0x1b800603}, + {0x0000a5a4, 0x2b805e40, 0x2b805e40, 0x1f800a02, 0x1f800a02}, + {0x0000a5a8, 0x2f805e42, 0x2f805e42, 0x23800a04, 0x23800a04}, + {0x0000a5ac, 0x33805e44, 0x33805e44, 0x26800a20, 0x26800a20}, + {0x0000a5b0, 0x38805e65, 0x38805e65, 0x2a800e20, 0x2a800e20}, + {0x0000a5b4, 0x3c805e69, 0x3c805e69, 0x2e800e22, 0x2e800e22}, + {0x0000a5b8, 0x40805e6b, 0x40805e6b, 0x31800e24, 0x31800e24}, + {0x0000a5bc, 0x44805e6d, 0x44805e6d, 0x34801640, 0x34801640}, + {0x0000a5c0, 0x49805e72, 0x49805e72, 0x38801660, 0x38801660}, + {0x0000a5c4, 0x4e805eb2, 0x4e805eb2, 0x3b801861, 0x3b801861}, + {0x0000a5c8, 0x53805f12, 0x53805f12, 0x3e801a81, 0x3e801a81}, + {0x0000a5cc, 0x59825eb2, 0x59825eb2, 0x42801a83, 0x42801a83}, + {0x0000a5d0, 0x5e825f12, 0x5e825f12, 0x44801c84, 0x44801c84}, + {0x0000a5d4, 0x61827f12, 0x61827f12, 0x48801ce3, 0x48801ce3}, + {0x0000a5d8, 0x6782bf12, 0x6782bf12, 0x4c801ce5, 0x4c801ce5}, + {0x0000a5dc, 0x6b82bf14, 0x6b82bf14, 0x50801ce9, 0x50801ce9}, + {0x0000a5e0, 0x6f82bf16, 0x6f82bf16, 0x54801ceb, 0x54801ceb}, + {0x0000a5e4, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5e8, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5ec, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5f0, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5f4, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5f8, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5fc, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00804000, 0x00804000, 0x00000000, 0x00000000}, + {0x0000a614, 0x00804201, 0x00804201, 0x01404000, 0x01404000}, + {0x0000a618, 0x0280c802, 0x0280c802, 0x01404501, 0x01404501}, + {0x0000a61c, 0x0280ca03, 0x0280ca03, 0x02008501, 0x02008501}, + {0x0000a620, 0x04c15104, 0x04c15104, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x04c15305, 0x04c15305, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x04c15305, 0x04c15305, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a630, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a634, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a638, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a63c, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000b2dc, 0x01feee00, 0x01feee00, 0x03aaa352, 0x03aaa352}, + {0x0000b2e0, 0x0000f000, 0x0000f000, 0x03ccc584, 0x03ccc584}, + {0x0000b2e4, 0x01ff0000, 0x01ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x00016044, 0x03b6d2e4, 0x03b6d2e4, 0x03b6d2e4, 0x03b6d2e4}, + {0x00016048, 0x8e481666, 0x8e481666, 0x8e481266, 0x8e481266}, + {0x00016280, 0x01000015, 0x01000015, 0x01001015, 0x01001015}, + {0x00016444, 0x03b6d2e4, 0x03b6d2e4, 0x03b6d2e4, 0x03b6d2e4}, + {0x00016448, 0x8e481666, 0x8e481666, 0x8e481266, 0x8e481266}, +}; + +static const u32 ar9340Modes_ub124_tx_gain_table_1p0[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009810, 0xd00a8005, 0xd00a8005, 0xd00a8005, 0xd00a8005}, + {0x00009820, 0x206a022e, 0x206a022e, 0x206a00ae, 0x206a00ae}, + {0x00009830, 0x0000059c, 0x0000059c, 0x0000059c, 0x0000059c}, + {0x00009e10, 0x7ec88d2e, 0x7ec88d2e, 0x7ec82d2e, 0x7ec82d2e}, + {0x0000a2dc, 0xfef5d402, 0xfef5d402, 0xfdab5b52, 0xfdab5b52}, + {0x0000a2e0, 0xfe896600, 0xfe896600, 0xfd339c84, 0xfd339c84}, + {0x0000a2e4, 0xff01f800, 0xff01f800, 0xfec3e000, 0xfec3e000}, + {0x0000a2e8, 0xfffe0000, 0xfffe0000, 0xfffc0000, 0xfffc0000}, + {0x0000a410, 0x000050d8, 0x000050d8, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00002220, 0x00002220, 0x00000000, 0x00000000}, + {0x0000a504, 0x04002222, 0x04002222, 0x04000002, 0x04000002}, + {0x0000a508, 0x09002421, 0x09002421, 0x08000004, 0x08000004}, + {0x0000a50c, 0x0d002621, 0x0d002621, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x13004620, 0x13004620, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x19004a20, 0x19004a20, 0x11000400, 0x11000400}, + {0x0000a518, 0x1d004e20, 0x1d004e20, 0x15000402, 0x15000402}, + {0x0000a51c, 0x21005420, 0x21005420, 0x19000404, 0x19000404}, + {0x0000a520, 0x26005e20, 0x26005e20, 0x1b000603, 0x1b000603}, + {0x0000a524, 0x2b005e40, 0x2b005e40, 0x1f000a02, 0x1f000a02}, + {0x0000a528, 0x2f005e42, 0x2f005e42, 0x23000a04, 0x23000a04}, + {0x0000a52c, 0x33005e44, 0x33005e44, 0x26000a20, 0x26000a20}, + {0x0000a530, 0x38005e65, 0x38005e65, 0x2a000e20, 0x2a000e20}, + {0x0000a534, 0x3c005e69, 0x3c005e69, 0x2e000e22, 0x2e000e22}, + {0x0000a538, 0x40005e6b, 0x40005e6b, 0x31000e24, 0x31000e24}, + {0x0000a53c, 0x44005e6d, 0x44005e6d, 0x34001640, 0x34001640}, + {0x0000a540, 0x49005e72, 0x49005e72, 0x38001660, 0x38001660}, + {0x0000a544, 0x4e005eb2, 0x4e005eb2, 0x3b001861, 0x3b001861}, + {0x0000a548, 0x53005f12, 0x53005f12, 0x3e001a81, 0x3e001a81}, + {0x0000a54c, 0x59025eb5, 0x59025eb5, 0x42001a83, 0x42001a83}, + {0x0000a550, 0x5e025f12, 0x5e025f12, 0x44001c84, 0x44001c84}, + {0x0000a554, 0x61027f12, 0x61027f12, 0x48001ce3, 0x48001ce3}, + {0x0000a558, 0x6702bf12, 0x6702bf12, 0x4c001ce5, 0x4c001ce5}, + {0x0000a55c, 0x6b02bf14, 0x6b02bf14, 0x50001ce9, 0x50001ce9}, + {0x0000a560, 0x6f02bf16, 0x6f02bf16, 0x54001ceb, 0x54001ceb}, + {0x0000a564, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a568, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a56c, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a570, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a574, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a578, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a57c, 0x6f02bf16, 0x6f02bf16, 0x56001eec, 0x56001eec}, + {0x0000a580, 0x00802220, 0x00802220, 0x00800000, 0x00800000}, + {0x0000a584, 0x04802222, 0x04802222, 0x04800002, 0x04800002}, + {0x0000a588, 0x09802421, 0x09802421, 0x08800004, 0x08800004}, + {0x0000a58c, 0x0d802621, 0x0d802621, 0x0b800200, 0x0b800200}, + {0x0000a590, 0x13804620, 0x13804620, 0x0f800202, 0x0f800202}, + {0x0000a594, 0x19804a20, 0x19804a20, 0x11800400, 0x11800400}, + {0x0000a598, 0x1d804e20, 0x1d804e20, 0x15800402, 0x15800402}, + {0x0000a59c, 0x21805420, 0x21805420, 0x19800404, 0x19800404}, + {0x0000a5a0, 0x26805e20, 0x26805e20, 0x1b800603, 0x1b800603}, + {0x0000a5a4, 0x2b805e40, 0x2b805e40, 0x1f800a02, 0x1f800a02}, + {0x0000a5a8, 0x2f805e42, 0x2f805e42, 0x23800a04, 0x23800a04}, + {0x0000a5ac, 0x33805e44, 0x33805e44, 0x26800a20, 0x26800a20}, + {0x0000a5b0, 0x38805e65, 0x38805e65, 0x2a800e20, 0x2a800e20}, + {0x0000a5b4, 0x3c805e69, 0x3c805e69, 0x2e800e22, 0x2e800e22}, + {0x0000a5b8, 0x40805e6b, 0x40805e6b, 0x31800e24, 0x31800e24}, + {0x0000a5bc, 0x44805e6d, 0x44805e6d, 0x34801640, 0x34801640}, + {0x0000a5c0, 0x49805e72, 0x49805e72, 0x38801660, 0x38801660}, + {0x0000a5c4, 0x4e805eb2, 0x4e805eb2, 0x3b801861, 0x3b801861}, + {0x0000a5c8, 0x53805f12, 0x53805f12, 0x3e801a81, 0x3e801a81}, + {0x0000a5cc, 0x59825eb2, 0x59825eb2, 0x42801a83, 0x42801a83}, + {0x0000a5d0, 0x5e825f12, 0x5e825f12, 0x44801c84, 0x44801c84}, + {0x0000a5d4, 0x61827f12, 0x61827f12, 0x48801ce3, 0x48801ce3}, + {0x0000a5d8, 0x6782bf12, 0x6782bf12, 0x4c801ce5, 0x4c801ce5}, + {0x0000a5dc, 0x6b82bf14, 0x6b82bf14, 0x50801ce9, 0x50801ce9}, + {0x0000a5e0, 0x6f82bf16, 0x6f82bf16, 0x54801ceb, 0x54801ceb}, + {0x0000a5e4, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5e8, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5ec, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5f0, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5f4, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5f8, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x0000a5fc, 0x6f82bf16, 0x6f82bf16, 0x56801eec, 0x56801eec}, + {0x00016044, 0x03b6d2e4, 0x03b6d2e4, 0x03b6d2e4, 0x03b6d2e4}, + {0x00016048, 0x8e480086, 0x8e480086, 0x8e480086, 0x8e480086}, + {0x00016444, 0x03b6d2e4, 0x03b6d2e4, 0x03b6d2e4, 0x03b6d2e4}, + {0x00016448, 0x8e480086, 0x8e480086, 0x8e480086, 0x8e480086}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00804000, 0x00804000, 0x00000000, 0x00000000}, + {0x0000a614, 0x00804201, 0x00804201, 0x01404000, 0x01404000}, + {0x0000a618, 0x0280c802, 0x0280c802, 0x01404501, 0x01404501}, + {0x0000a61c, 0x0280ca03, 0x0280ca03, 0x02008501, 0x02008501}, + {0x0000a620, 0x04c15104, 0x04c15104, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x04c15305, 0x04c15305, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x04c15305, 0x04c15305, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a630, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a634, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a638, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a63c, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000b2dc, 0xfef5d402, 0xfef5d402, 0xfdab5b52, 0xfdab5b52}, + {0x0000b2e0, 0xfe896600, 0xfe896600, 0xfd339c84, 0xfd339c84}, + {0x0000b2e4, 0xff01f800, 0xff01f800, 0xfec3e000, 0xfec3e000}, + {0x0000b2e8, 0xfffe0000, 0xfffe0000, 0xfffc0000, 0xfffc0000}, +}; + +static const u32 ar9340Common_rx_gain_table_1p0[][2] = { + /* Addr allmodes */ + {0x0000a000, 0x00010000}, + {0x0000a004, 0x00030002}, + {0x0000a008, 0x00050004}, + {0x0000a00c, 0x00810080}, + {0x0000a010, 0x00830082}, + {0x0000a014, 0x01810180}, + {0x0000a018, 0x01830182}, + {0x0000a01c, 0x01850184}, + {0x0000a020, 0x01890188}, + {0x0000a024, 0x018b018a}, + {0x0000a028, 0x018d018c}, + {0x0000a02c, 0x01910190}, + {0x0000a030, 0x01930192}, + {0x0000a034, 0x01950194}, + {0x0000a038, 0x038a0196}, + {0x0000a03c, 0x038c038b}, + {0x0000a040, 0x0390038d}, + {0x0000a044, 0x03920391}, + {0x0000a048, 0x03940393}, + {0x0000a04c, 0x03960395}, + {0x0000a050, 0x00000000}, + {0x0000a054, 0x00000000}, + {0x0000a058, 0x00000000}, + {0x0000a05c, 0x00000000}, + {0x0000a060, 0x00000000}, + {0x0000a064, 0x00000000}, + {0x0000a068, 0x00000000}, + {0x0000a06c, 0x00000000}, + {0x0000a070, 0x00000000}, + {0x0000a074, 0x00000000}, + {0x0000a078, 0x00000000}, + {0x0000a07c, 0x00000000}, + {0x0000a080, 0x22222229}, + {0x0000a084, 0x1d1d1d1d}, + {0x0000a088, 0x1d1d1d1d}, + {0x0000a08c, 0x1d1d1d1d}, + {0x0000a090, 0x171d1d1d}, + {0x0000a094, 0x11111717}, + {0x0000a098, 0x00030311}, + {0x0000a09c, 0x00000000}, + {0x0000a0a0, 0x00000000}, + {0x0000a0a4, 0x00000000}, + {0x0000a0a8, 0x00000000}, + {0x0000a0ac, 0x00000000}, + {0x0000a0b0, 0x00000000}, + {0x0000a0b4, 0x00000000}, + {0x0000a0b8, 0x00000000}, + {0x0000a0bc, 0x00000000}, + {0x0000a0c0, 0x001f0000}, + {0x0000a0c4, 0x01000101}, + {0x0000a0c8, 0x011e011f}, + {0x0000a0cc, 0x011c011d}, + {0x0000a0d0, 0x02030204}, + {0x0000a0d4, 0x02010202}, + {0x0000a0d8, 0x021f0200}, + {0x0000a0dc, 0x0302021e}, + {0x0000a0e0, 0x03000301}, + {0x0000a0e4, 0x031e031f}, + {0x0000a0e8, 0x0402031d}, + {0x0000a0ec, 0x04000401}, + {0x0000a0f0, 0x041e041f}, + {0x0000a0f4, 0x0502041d}, + {0x0000a0f8, 0x05000501}, + {0x0000a0fc, 0x051e051f}, + {0x0000a100, 0x06010602}, + {0x0000a104, 0x061f0600}, + {0x0000a108, 0x061d061e}, + {0x0000a10c, 0x07020703}, + {0x0000a110, 0x07000701}, + {0x0000a114, 0x00000000}, + {0x0000a118, 0x00000000}, + {0x0000a11c, 0x00000000}, + {0x0000a120, 0x00000000}, + {0x0000a124, 0x00000000}, + {0x0000a128, 0x00000000}, + {0x0000a12c, 0x00000000}, + {0x0000a130, 0x00000000}, + {0x0000a134, 0x00000000}, + {0x0000a138, 0x00000000}, + {0x0000a13c, 0x00000000}, + {0x0000a140, 0x001f0000}, + {0x0000a144, 0x01000101}, + {0x0000a148, 0x011e011f}, + {0x0000a14c, 0x011c011d}, + {0x0000a150, 0x02030204}, + {0x0000a154, 0x02010202}, + {0x0000a158, 0x021f0200}, + {0x0000a15c, 0x0302021e}, + {0x0000a160, 0x03000301}, + {0x0000a164, 0x031e031f}, + {0x0000a168, 0x0402031d}, + {0x0000a16c, 0x04000401}, + {0x0000a170, 0x041e041f}, + {0x0000a174, 0x0502041d}, + {0x0000a178, 0x05000501}, + {0x0000a17c, 0x051e051f}, + {0x0000a180, 0x06010602}, + {0x0000a184, 0x061f0600}, + {0x0000a188, 0x061d061e}, + {0x0000a18c, 0x07020703}, + {0x0000a190, 0x07000701}, + {0x0000a194, 0x00000000}, + {0x0000a198, 0x00000000}, + {0x0000a19c, 0x00000000}, + {0x0000a1a0, 0x00000000}, + {0x0000a1a4, 0x00000000}, + {0x0000a1a8, 0x00000000}, + {0x0000a1ac, 0x00000000}, + {0x0000a1b0, 0x00000000}, + {0x0000a1b4, 0x00000000}, + {0x0000a1b8, 0x00000000}, + {0x0000a1bc, 0x00000000}, + {0x0000a1c0, 0x00000000}, + {0x0000a1c4, 0x00000000}, + {0x0000a1c8, 0x00000000}, + {0x0000a1cc, 0x00000000}, + {0x0000a1d0, 0x00000000}, + {0x0000a1d4, 0x00000000}, + {0x0000a1d8, 0x00000000}, + {0x0000a1dc, 0x00000000}, + {0x0000a1e0, 0x00000000}, + {0x0000a1e4, 0x00000000}, + {0x0000a1e8, 0x00000000}, + {0x0000a1ec, 0x00000000}, + {0x0000a1f0, 0x00000396}, + {0x0000a1f4, 0x00000396}, + {0x0000a1f8, 0x00000396}, + {0x0000a1fc, 0x00000196}, + {0x0000b000, 0x00010000}, + {0x0000b004, 0x00030002}, + {0x0000b008, 0x00050004}, + {0x0000b00c, 0x00810080}, + {0x0000b010, 0x00830082}, + {0x0000b014, 0x01810180}, + {0x0000b018, 0x01830182}, + {0x0000b01c, 0x01850184}, + {0x0000b020, 0x02810280}, + {0x0000b024, 0x02830282}, + {0x0000b028, 0x02850284}, + {0x0000b02c, 0x02890288}, + {0x0000b030, 0x028b028a}, + {0x0000b034, 0x0388028c}, + {0x0000b038, 0x038a0389}, + {0x0000b03c, 0x038c038b}, + {0x0000b040, 0x0390038d}, + {0x0000b044, 0x03920391}, + {0x0000b048, 0x03940393}, + {0x0000b04c, 0x03960395}, + {0x0000b050, 0x00000000}, + {0x0000b054, 0x00000000}, + {0x0000b058, 0x00000000}, + {0x0000b05c, 0x00000000}, + {0x0000b060, 0x00000000}, + {0x0000b064, 0x00000000}, + {0x0000b068, 0x00000000}, + {0x0000b06c, 0x00000000}, + {0x0000b070, 0x00000000}, + {0x0000b074, 0x00000000}, + {0x0000b078, 0x00000000}, + {0x0000b07c, 0x00000000}, + {0x0000b080, 0x23232323}, + {0x0000b084, 0x21232323}, + {0x0000b088, 0x19191c1e}, + {0x0000b08c, 0x12141417}, + {0x0000b090, 0x07070e0e}, + {0x0000b094, 0x03030305}, + {0x0000b098, 0x00000003}, + {0x0000b09c, 0x00000000}, + {0x0000b0a0, 0x00000000}, + {0x0000b0a4, 0x00000000}, + {0x0000b0a8, 0x00000000}, + {0x0000b0ac, 0x00000000}, + {0x0000b0b0, 0x00000000}, + {0x0000b0b4, 0x00000000}, + {0x0000b0b8, 0x00000000}, + {0x0000b0bc, 0x00000000}, + {0x0000b0c0, 0x003f0020}, + {0x0000b0c4, 0x00400041}, + {0x0000b0c8, 0x0140005f}, + {0x0000b0cc, 0x0160015f}, + {0x0000b0d0, 0x017e017f}, + {0x0000b0d4, 0x02410242}, + {0x0000b0d8, 0x025f0240}, + {0x0000b0dc, 0x027f0260}, + {0x0000b0e0, 0x0341027e}, + {0x0000b0e4, 0x035f0340}, + {0x0000b0e8, 0x037f0360}, + {0x0000b0ec, 0x04400441}, + {0x0000b0f0, 0x0460045f}, + {0x0000b0f4, 0x0541047f}, + {0x0000b0f8, 0x055f0540}, + {0x0000b0fc, 0x057f0560}, + {0x0000b100, 0x06400641}, + {0x0000b104, 0x0660065f}, + {0x0000b108, 0x067e067f}, + {0x0000b10c, 0x07410742}, + {0x0000b110, 0x075f0740}, + {0x0000b114, 0x077f0760}, + {0x0000b118, 0x07800781}, + {0x0000b11c, 0x07a0079f}, + {0x0000b120, 0x07c107bf}, + {0x0000b124, 0x000007c0}, + {0x0000b128, 0x00000000}, + {0x0000b12c, 0x00000000}, + {0x0000b130, 0x00000000}, + {0x0000b134, 0x00000000}, + {0x0000b138, 0x00000000}, + {0x0000b13c, 0x00000000}, + {0x0000b140, 0x003f0020}, + {0x0000b144, 0x00400041}, + {0x0000b148, 0x0140005f}, + {0x0000b14c, 0x0160015f}, + {0x0000b150, 0x017e017f}, + {0x0000b154, 0x02410242}, + {0x0000b158, 0x025f0240}, + {0x0000b15c, 0x027f0260}, + {0x0000b160, 0x0341027e}, + {0x0000b164, 0x035f0340}, + {0x0000b168, 0x037f0360}, + {0x0000b16c, 0x04400441}, + {0x0000b170, 0x0460045f}, + {0x0000b174, 0x0541047f}, + {0x0000b178, 0x055f0540}, + {0x0000b17c, 0x057f0560}, + {0x0000b180, 0x06400641}, + {0x0000b184, 0x0660065f}, + {0x0000b188, 0x067e067f}, + {0x0000b18c, 0x07410742}, + {0x0000b190, 0x075f0740}, + {0x0000b194, 0x077f0760}, + {0x0000b198, 0x07800781}, + {0x0000b19c, 0x07a0079f}, + {0x0000b1a0, 0x07c107bf}, + {0x0000b1a4, 0x000007c0}, + {0x0000b1a8, 0x00000000}, + {0x0000b1ac, 0x00000000}, + {0x0000b1b0, 0x00000000}, + {0x0000b1b4, 0x00000000}, + {0x0000b1b8, 0x00000000}, + {0x0000b1bc, 0x00000000}, + {0x0000b1c0, 0x00000000}, + {0x0000b1c4, 0x00000000}, + {0x0000b1c8, 0x00000000}, + {0x0000b1cc, 0x00000000}, + {0x0000b1d0, 0x00000000}, + {0x0000b1d4, 0x00000000}, + {0x0000b1d8, 0x00000000}, + {0x0000b1dc, 0x00000000}, + {0x0000b1e0, 0x00000000}, + {0x0000b1e4, 0x00000000}, + {0x0000b1e8, 0x00000000}, + {0x0000b1ec, 0x00000000}, + {0x0000b1f0, 0x00000396}, + {0x0000b1f4, 0x00000396}, + {0x0000b1f8, 0x00000396}, + {0x0000b1fc, 0x00000196}, +}; + +static const u32 ar9340Modes_low_ob_db_tx_gain_table_1p0[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000a2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000a2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a504, 0x06000003, 0x06000003, 0x04000002, 0x04000002}, + {0x0000a508, 0x0a000020, 0x0a000020, 0x08000004, 0x08000004}, + {0x0000a50c, 0x10000023, 0x10000023, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x16000220, 0x16000220, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x1c000223, 0x1c000223, 0x12000400, 0x12000400}, + {0x0000a518, 0x21002220, 0x21002220, 0x16000402, 0x16000402}, + {0x0000a51c, 0x27002223, 0x27002223, 0x19000404, 0x19000404}, + {0x0000a520, 0x2b022220, 0x2b022220, 0x1c000603, 0x1c000603}, + {0x0000a524, 0x2f022222, 0x2f022222, 0x21000a02, 0x21000a02}, + {0x0000a528, 0x34022225, 0x34022225, 0x25000a04, 0x25000a04}, + {0x0000a52c, 0x3a02222a, 0x3a02222a, 0x28000a20, 0x28000a20}, + {0x0000a530, 0x3e02222c, 0x3e02222c, 0x2c000e20, 0x2c000e20}, + {0x0000a534, 0x4202242a, 0x4202242a, 0x30000e22, 0x30000e22}, + {0x0000a538, 0x4702244a, 0x4702244a, 0x34000e24, 0x34000e24}, + {0x0000a53c, 0x4b02244c, 0x4b02244c, 0x38001640, 0x38001640}, + {0x0000a540, 0x4e02246c, 0x4e02246c, 0x3c001660, 0x3c001660}, + {0x0000a544, 0x5302266c, 0x5302266c, 0x3f001861, 0x3f001861}, + {0x0000a548, 0x5702286c, 0x5702286c, 0x43001a81, 0x43001a81}, + {0x0000a54c, 0x5c02486b, 0x5c02486b, 0x47001a83, 0x47001a83}, + {0x0000a550, 0x61024a6c, 0x61024a6c, 0x4a001c84, 0x4a001c84}, + {0x0000a554, 0x66026a6c, 0x66026a6c, 0x4e001ce3, 0x4e001ce3}, + {0x0000a558, 0x6b026e6c, 0x6b026e6c, 0x52001ce5, 0x52001ce5}, + {0x0000a55c, 0x7002708c, 0x7002708c, 0x56001ce9, 0x56001ce9}, + {0x0000a560, 0x7302b08a, 0x7302b08a, 0x5a001ceb, 0x5a001ceb}, + {0x0000a564, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a568, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a56c, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a570, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a574, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a578, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a57c, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a580, 0x00800000, 0x00800000, 0x00800000, 0x00800000}, + {0x0000a584, 0x06800003, 0x06800003, 0x04800002, 0x04800002}, + {0x0000a588, 0x0a800020, 0x0a800020, 0x08800004, 0x08800004}, + {0x0000a58c, 0x10800023, 0x10800023, 0x0b800200, 0x0b800200}, + {0x0000a590, 0x16800220, 0x16800220, 0x0f800202, 0x0f800202}, + {0x0000a594, 0x1c800223, 0x1c800223, 0x12800400, 0x12800400}, + {0x0000a598, 0x21820220, 0x21820220, 0x16800402, 0x16800402}, + {0x0000a59c, 0x27820223, 0x27820223, 0x19800404, 0x19800404}, + {0x0000a5a0, 0x2b822220, 0x2b822220, 0x1c800603, 0x1c800603}, + {0x0000a5a4, 0x2f822222, 0x2f822222, 0x21800a02, 0x21800a02}, + {0x0000a5a8, 0x34822225, 0x34822225, 0x25800a04, 0x25800a04}, + {0x0000a5ac, 0x3a82222a, 0x3a82222a, 0x28800a20, 0x28800a20}, + {0x0000a5b0, 0x3e82222c, 0x3e82222c, 0x2c800e20, 0x2c800e20}, + {0x0000a5b4, 0x4282242a, 0x4282242a, 0x30800e22, 0x30800e22}, + {0x0000a5b8, 0x4782244a, 0x4782244a, 0x34800e24, 0x34800e24}, + {0x0000a5bc, 0x4b82244c, 0x4b82244c, 0x38801640, 0x38801640}, + {0x0000a5c0, 0x4e82246c, 0x4e82246c, 0x3c801660, 0x3c801660}, + {0x0000a5c4, 0x5382266c, 0x5382266c, 0x3f801861, 0x3f801861}, + {0x0000a5c8, 0x5782286c, 0x5782286c, 0x43801a81, 0x43801a81}, + {0x0000a5cc, 0x5c84286b, 0x5c84286b, 0x47801a83, 0x47801a83}, + {0x0000a5d0, 0x61842a6c, 0x61842a6c, 0x4a801c84, 0x4a801c84}, + {0x0000a5d4, 0x66862a6c, 0x66862a6c, 0x4e801ce3, 0x4e801ce3}, + {0x0000a5d8, 0x6b862e6c, 0x6b862e6c, 0x52801ce5, 0x52801ce5}, + {0x0000a5dc, 0x7086308c, 0x7086308c, 0x56801ce9, 0x56801ce9}, + {0x0000a5e0, 0x738a308a, 0x738a308a, 0x5a801ceb, 0x5a801ceb}, + {0x0000a5e4, 0x778a308c, 0x778a308c, 0x5d801eec, 0x5d801eec}, + {0x0000a5e8, 0x778a308c, 0x778a308c, 0x5d801eec, 0x5d801eec}, + {0x0000a5ec, 0x778a308c, 0x778a308c, 0x5d801eec, 0x5d801eec}, + {0x0000a5f0, 0x778a308c, 0x778a308c, 0x5d801eec, 0x5d801eec}, + {0x0000a5f4, 0x778a308c, 0x778a308c, 0x5d801eec, 0x5d801eec}, + {0x0000a5f8, 0x778a308c, 0x778a308c, 0x5d801eec, 0x5d801eec}, + {0x0000a5fc, 0x778a308c, 0x778a308c, 0x5d801eec, 0x5d801eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x01404000, 0x01404000, 0x01404000, 0x01404000}, + {0x0000a618, 0x01404501, 0x01404501, 0x01404501, 0x01404501}, + {0x0000a61c, 0x02008802, 0x02008802, 0x02008501, 0x02008501}, + {0x0000a620, 0x0300cc03, 0x0300cc03, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x0300cc03, 0x0300cc03, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x0300cc03, 0x0300cc03, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x03810c03, 0x03810c03, 0x04015005, 0x04015005}, + {0x0000a630, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a634, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a638, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a63c, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000b2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000b2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000b2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x00016044, 0x056db2db, 0x056db2db, 0x056db2db, 0x056db2db}, + {0x00016048, 0x24925666, 0x24925666, 0x24925266, 0x24925266}, + {0x00016280, 0x01000015, 0x01000015, 0x01001015, 0x01001015}, + {0x00016288, 0xf0318000, 0xf0318000, 0xf0318000, 0xf0318000}, + {0x00016444, 0x056db2db, 0x056db2db, 0x056db2db, 0x056db2db}, + {0x00016448, 0x24925666, 0x24925666, 0x24925266, 0x24925266}, +}; + +static const u32 ar9340Modes_mixed_ob_db_tx_gain_table_1p0[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000a2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000a2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a504, 0x06000003, 0x06000003, 0x04000002, 0x04000002}, + {0x0000a508, 0x0a000020, 0x0a000020, 0x08000004, 0x08000004}, + {0x0000a50c, 0x10000023, 0x10000023, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x16000220, 0x16000220, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x1c000223, 0x1c000223, 0x11000400, 0x11000400}, + {0x0000a518, 0x21002220, 0x21002220, 0x15000402, 0x15000402}, + {0x0000a51c, 0x27002223, 0x27002223, 0x19000404, 0x19000404}, + {0x0000a520, 0x2b022220, 0x2b022220, 0x1b000603, 0x1b000603}, + {0x0000a524, 0x2f022222, 0x2f022222, 0x1f000a02, 0x1f000a02}, + {0x0000a528, 0x34022225, 0x34022225, 0x23000a04, 0x23000a04}, + {0x0000a52c, 0x3a02222a, 0x3a02222a, 0x26000a20, 0x26000a20}, + {0x0000a530, 0x3e02222c, 0x3e02222c, 0x2a000e20, 0x2a000e20}, + {0x0000a534, 0x4202242a, 0x4202242a, 0x2e000e22, 0x2e000e22}, + {0x0000a538, 0x4702244a, 0x4702244a, 0x31000e24, 0x31000e24}, + {0x0000a53c, 0x4b02244c, 0x4b02244c, 0x34001640, 0x34001640}, + {0x0000a540, 0x4e02246c, 0x4e02246c, 0x38001660, 0x38001660}, + {0x0000a544, 0x5302266c, 0x5302266c, 0x3b001861, 0x3b001861}, + {0x0000a548, 0x5702286c, 0x5702286c, 0x3e001a81, 0x3e001a81}, + {0x0000a54c, 0x5c02486b, 0x5c02486b, 0x42001a83, 0x42001a83}, + {0x0000a550, 0x61024a6c, 0x61024a6c, 0x44001c84, 0x44001c84}, + {0x0000a554, 0x66026a6c, 0x66026a6c, 0x48001ce3, 0x48001ce3}, + {0x0000a558, 0x6b026e6c, 0x6b026e6c, 0x4c001ce5, 0x4c001ce5}, + {0x0000a55c, 0x7002708c, 0x7002708c, 0x50001ce9, 0x50001ce9}, + {0x0000a560, 0x7302b08a, 0x7302b08a, 0x54001ceb, 0x54001ceb}, + {0x0000a564, 0x7702b08c, 0x7702b08c, 0x56001eec, 0x56001eec}, + {0x0000a568, 0x7702b08c, 0x7702b08c, 0x56001eec, 0x56001eec}, + {0x0000a56c, 0x7702b08c, 0x7702b08c, 0x56001eec, 0x56001eec}, + {0x0000a570, 0x7702b08c, 0x7702b08c, 0x56001eec, 0x56001eec}, + {0x0000a574, 0x7702b08c, 0x7702b08c, 0x56001eec, 0x56001eec}, + {0x0000a578, 0x7702b08c, 0x7702b08c, 0x56001eec, 0x56001eec}, + {0x0000a57c, 0x7702b08c, 0x7702b08c, 0x56001eec, 0x56001eec}, + {0x0000a580, 0x00800000, 0x00800000, 0x00800000, 0x00800000}, + {0x0000a584, 0x06800003, 0x06800003, 0x04800002, 0x04800002}, + {0x0000a588, 0x0a800020, 0x0a800020, 0x08800004, 0x08800004}, + {0x0000a58c, 0x10800023, 0x10800023, 0x0b800200, 0x0b800200}, + {0x0000a590, 0x16800220, 0x16800220, 0x0f800202, 0x0f800202}, + {0x0000a594, 0x1c800223, 0x1c800223, 0x11800400, 0x11800400}, + {0x0000a598, 0x21820220, 0x21820220, 0x15800402, 0x15800402}, + {0x0000a59c, 0x27820223, 0x27820223, 0x19800404, 0x19800404}, + {0x0000a5a0, 0x2b822220, 0x2b822220, 0x1b800603, 0x1b800603}, + {0x0000a5a4, 0x2f822222, 0x2f822222, 0x1f800a02, 0x1f800a02}, + {0x0000a5a8, 0x34822225, 0x34822225, 0x23800a04, 0x23800a04}, + {0x0000a5ac, 0x3a82222a, 0x3a82222a, 0x26800a20, 0x26800a20}, + {0x0000a5b0, 0x3e82222c, 0x3e82222c, 0x2a800e20, 0x2a800e20}, + {0x0000a5b4, 0x4282242a, 0x4282242a, 0x2e800e22, 0x2e800e22}, + {0x0000a5b8, 0x4782244a, 0x4782244a, 0x31800e24, 0x31800e24}, + {0x0000a5bc, 0x4b82244c, 0x4b82244c, 0x34801640, 0x34801640}, + {0x0000a5c0, 0x4e82246c, 0x4e82246c, 0x38801660, 0x38801660}, + {0x0000a5c4, 0x5382266c, 0x5382266c, 0x3b801861, 0x3b801861}, + {0x0000a5c8, 0x5782286c, 0x5782286c, 0x3e801a81, 0x3e801a81}, + {0x0000a5cc, 0x5c84286b, 0x5c84286b, 0x42801a83, 0x42801a83}, + {0x0000a5d0, 0x61842a6c, 0x61842a6c, 0x44801c84, 0x44801c84}, + {0x0000a5d4, 0x66862a6c, 0x66862a6c, 0x48801ce3, 0x48801ce3}, + {0x0000a5d8, 0x6b862e6c, 0x6b862e6c, 0x4c801ce5, 0x4c801ce5}, + {0x0000a5dc, 0x7086308c, 0x7086308c, 0x50801ce9, 0x50801ce9}, + {0x0000a5e0, 0x738a308a, 0x738a308a, 0x54801ceb, 0x54801ceb}, + {0x0000a5e4, 0x778a308c, 0x778a308c, 0x56801eec, 0x56801eec}, + {0x0000a5e8, 0x778a308c, 0x778a308c, 0x56801eec, 0x56801eec}, + {0x0000a5ec, 0x778a308c, 0x778a308c, 0x56801eec, 0x56801eec}, + {0x0000a5f0, 0x778a308c, 0x778a308c, 0x56801eec, 0x56801eec}, + {0x0000a5f4, 0x778a308c, 0x778a308c, 0x56801eec, 0x56801eec}, + {0x0000a5f8, 0x778a308c, 0x778a308c, 0x56801eec, 0x56801eec}, + {0x0000a5fc, 0x778a308c, 0x778a308c, 0x56801eec, 0x56801eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x01404000, 0x01404000, 0x01404000, 0x01404000}, + {0x0000a618, 0x01404501, 0x01404501, 0x01404501, 0x01404501}, + {0x0000a61c, 0x02008802, 0x02008802, 0x02008501, 0x02008501}, + {0x0000a620, 0x0300cc03, 0x0300cc03, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x0300cc03, 0x0300cc03, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x0300cc03, 0x0300cc03, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x03810c03, 0x03810c03, 0x04015005, 0x04015005}, + {0x0000a630, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a634, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a638, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a63c, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000b2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000b2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000b2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x00016044, 0x056db2db, 0x056db2db, 0x03b6d2e4, 0x03b6d2e4}, + {0x00016048, 0x24925666, 0x24925666, 0x8e481266, 0x8e481266}, + {0x00016280, 0x01000015, 0x01000015, 0x01001015, 0x01001015}, + {0x00016288, 0x30318000, 0x30318000, 0x00318000, 0x00318000}, + {0x00016444, 0x056db2db, 0x056db2db, 0x03b6d2e4, 0x03b6d2e4}, + {0x00016448, 0x24925666, 0x24925666, 0x8e481266, 0x8e481266}, +}; + +static const u32 ar9340Modes_low_ob_db_and_spur_tx_gain_table_1p0[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0x0380c7fc, 0x0380c7fc, 0x03eaac5a, 0x03eaac5a}, + {0x0000a2e0, 0x0000f800, 0x0000f800, 0x03f330ac, 0x03f330ac}, + {0x0000a2e4, 0x03ff0000, 0x03ff0000, 0x03fc3f00, 0x03fc3f00}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ffc000, 0x03ffc000}, + {0x0000a394, 0x00000444, 0x00000444, 0x00000404, 0x00000404}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a504, 0x06000003, 0x06000003, 0x02000001, 0x02000001}, + {0x0000a508, 0x0a000020, 0x0a000020, 0x05000003, 0x05000003}, + {0x0000a50c, 0x10000023, 0x10000023, 0x0a000005, 0x0a000005}, + {0x0000a510, 0x16000220, 0x16000220, 0x0e000201, 0x0e000201}, + {0x0000a514, 0x1c000223, 0x1c000223, 0x11000203, 0x11000203}, + {0x0000a518, 0x21002220, 0x21002220, 0x14000401, 0x14000401}, + {0x0000a51c, 0x27002223, 0x27002223, 0x18000403, 0x18000403}, + {0x0000a520, 0x2b022220, 0x2b022220, 0x1b000602, 0x1b000602}, + {0x0000a524, 0x2f022222, 0x2f022222, 0x1f000802, 0x1f000802}, + {0x0000a528, 0x34022225, 0x34022225, 0x21000620, 0x21000620}, + {0x0000a52c, 0x3a02222a, 0x3a02222a, 0x25000820, 0x25000820}, + {0x0000a530, 0x3e02222c, 0x3e02222c, 0x29000822, 0x29000822}, + {0x0000a534, 0x4202242a, 0x4202242a, 0x2d000824, 0x2d000824}, + {0x0000a538, 0x4702244a, 0x4702244a, 0x30000828, 0x30000828}, + {0x0000a53c, 0x4b02244c, 0x4b02244c, 0x3400082a, 0x3400082a}, + {0x0000a540, 0x4e02246c, 0x4e02246c, 0x38000849, 0x38000849}, + {0x0000a544, 0x5302266c, 0x5302266c, 0x3b000a2c, 0x3b000a2c}, + {0x0000a548, 0x5702286c, 0x5702286c, 0x3e000e2b, 0x3e000e2b}, + {0x0000a54c, 0x5c02486b, 0x5c02486b, 0x42000e2d, 0x42000e2d}, + {0x0000a550, 0x61024a6c, 0x61024a6c, 0x4500124a, 0x4500124a}, + {0x0000a554, 0x66026a6c, 0x66026a6c, 0x4900124c, 0x4900124c}, + {0x0000a558, 0x6b026e6c, 0x6b026e6c, 0x4c00126c, 0x4c00126c}, + {0x0000a55c, 0x7002708c, 0x7002708c, 0x4f00128c, 0x4f00128c}, + {0x0000a560, 0x7302b08a, 0x7302b08a, 0x52001290, 0x52001290}, + {0x0000a564, 0x7702b08c, 0x7702b08c, 0x56001292, 0x56001292}, + {0x0000a568, 0x7702b08c, 0x7702b08c, 0x56001292, 0x56001292}, + {0x0000a56c, 0x7702b08c, 0x7702b08c, 0x56001292, 0x56001292}, + {0x0000a570, 0x7702b08c, 0x7702b08c, 0x56001292, 0x56001292}, + {0x0000a574, 0x7702b08c, 0x7702b08c, 0x56001292, 0x56001292}, + {0x0000a578, 0x7702b08c, 0x7702b08c, 0x56001292, 0x56001292}, + {0x0000a57c, 0x7702b08c, 0x7702b08c, 0x56001292, 0x56001292}, + {0x0000a580, 0x00800000, 0x00800000, 0x00800000, 0x00800000}, + {0x0000a584, 0x06800003, 0x06800003, 0x02800001, 0x02800001}, + {0x0000a588, 0x0a800020, 0x0a800020, 0x05800003, 0x05800003}, + {0x0000a58c, 0x10800023, 0x10800023, 0x0a800005, 0x0a800005}, + {0x0000a590, 0x16800220, 0x16800220, 0x0e800201, 0x0e800201}, + {0x0000a594, 0x1c800223, 0x1c800223, 0x11800203, 0x11800203}, + {0x0000a598, 0x21820220, 0x21820220, 0x14800401, 0x14800401}, + {0x0000a59c, 0x27820223, 0x27820223, 0x18800403, 0x18800403}, + {0x0000a5a0, 0x2b822220, 0x2b822220, 0x1b800602, 0x1b800602}, + {0x0000a5a4, 0x2f822222, 0x2f822222, 0x1f800802, 0x1f800802}, + {0x0000a5a8, 0x34822225, 0x34822225, 0x21800620, 0x21800620}, + {0x0000a5ac, 0x3a82222a, 0x3a82222a, 0x25800820, 0x25800820}, + {0x0000a5b0, 0x3e82222c, 0x3e82222c, 0x29800822, 0x29800822}, + {0x0000a5b4, 0x4282242a, 0x4282242a, 0x2d800824, 0x2d800824}, + {0x0000a5b8, 0x4782244a, 0x4782244a, 0x30800828, 0x30800828}, + {0x0000a5bc, 0x4b82244c, 0x4b82244c, 0x3480082a, 0x3480082a}, + {0x0000a5c0, 0x4e82246c, 0x4e82246c, 0x38800849, 0x38800849}, + {0x0000a5c4, 0x5382266c, 0x5382266c, 0x3b800a2c, 0x3b800a2c}, + {0x0000a5c8, 0x5782286c, 0x5782286c, 0x3e800e2b, 0x3e800e2b}, + {0x0000a5cc, 0x5c84286b, 0x5c84286b, 0x42800e2d, 0x42800e2d}, + {0x0000a5d0, 0x61842a6c, 0x61842a6c, 0x4580124a, 0x4580124a}, + {0x0000a5d4, 0x66862a6c, 0x66862a6c, 0x4980124c, 0x4980124c}, + {0x0000a5d8, 0x6b862e6c, 0x6b862e6c, 0x4c80126c, 0x4c80126c}, + {0x0000a5dc, 0x7086308c, 0x7086308c, 0x4f80128c, 0x4f80128c}, + {0x0000a5e0, 0x738a308a, 0x738a308a, 0x52801290, 0x52801290}, + {0x0000a5e4, 0x778a308c, 0x778a308c, 0x56801292, 0x56801292}, + {0x0000a5e8, 0x778a308c, 0x778a308c, 0x56801292, 0x56801292}, + {0x0000a5ec, 0x778a308c, 0x778a308c, 0x56801292, 0x56801292}, + {0x0000a5f0, 0x778a308c, 0x778a308c, 0x56801292, 0x56801292}, + {0x0000a5f4, 0x778a308c, 0x778a308c, 0x56801292, 0x56801292}, + {0x0000a5f8, 0x778a308c, 0x778a308c, 0x56801292, 0x56801292}, + {0x0000a5fc, 0x778a308c, 0x778a308c, 0x56801292, 0x56801292}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x01404000, 0x01404000, 0x01404501, 0x01404501}, + {0x0000a618, 0x01404501, 0x01404501, 0x01404501, 0x01404501}, + {0x0000a61c, 0x02008802, 0x02008802, 0x01404501, 0x01404501}, + {0x0000a620, 0x0300cc03, 0x0300cc03, 0x03c0cf02, 0x03c0cf02}, + {0x0000a624, 0x0300cc03, 0x0300cc03, 0x03c0cf03, 0x03c0cf03}, + {0x0000a628, 0x0300cc03, 0x0300cc03, 0x04011004, 0x04011004}, + {0x0000a62c, 0x03810c03, 0x03810c03, 0x05419405, 0x05419405}, + {0x0000a630, 0x03810e04, 0x03810e04, 0x05419506, 0x05419506}, + {0x0000a634, 0x03810e04, 0x03810e04, 0x05419506, 0x05419506}, + {0x0000a638, 0x03810e04, 0x03810e04, 0x05419506, 0x05419506}, + {0x0000a63c, 0x03810e04, 0x03810e04, 0x05419506, 0x05419506}, + {0x0000b2dc, 0x0380c7fc, 0x0380c7fc, 0x03eaac5a, 0x03eaac5a}, + {0x0000b2e0, 0x0000f800, 0x0000f800, 0x03f330ac, 0x03f330ac}, + {0x0000b2e4, 0x03ff0000, 0x03ff0000, 0x03fc3f00, 0x03fc3f00}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ffc000, 0x03ffc000}, + {0x00016044, 0x022492db, 0x022492db, 0x022492db, 0x022492db}, + {0x00016048, 0x24925666, 0x24925666, 0x24925266, 0x24925266}, + {0x00016280, 0x01000015, 0x01000015, 0x01001015, 0x01001015}, + {0x00016288, 0xf0318000, 0xf0318000, 0xf0318000, 0xf0318000}, + {0x00016444, 0x022492db, 0x022492db, 0x022492db, 0x022492db}, + {0x00016448, 0x24925666, 0x24925666, 0x24925266, 0x24925266}, +}; + +static const u32 ar9340_1p0_mac_core[][2] = { + /* Addr allmodes */ + {0x00000008, 0x00000000}, + {0x00000030, 0x00020085}, + {0x00000034, 0x00000005}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000000}, + {0x00000048, 0x00000008}, + {0x0000004c, 0x00000010}, + {0x00000050, 0x00000000}, + {0x00001040, 0x002ffc0f}, + {0x00001044, 0x002ffc0f}, + {0x00001048, 0x002ffc0f}, + {0x0000104c, 0x002ffc0f}, + {0x00001050, 0x002ffc0f}, + {0x00001054, 0x002ffc0f}, + {0x00001058, 0x002ffc0f}, + {0x0000105c, 0x002ffc0f}, + {0x00001060, 0x002ffc0f}, + {0x00001064, 0x002ffc0f}, + {0x000010f0, 0x00000100}, + {0x00001270, 0x00000000}, + {0x000012b0, 0x00000000}, + {0x000012f0, 0x00000000}, + {0x0000143c, 0x00000000}, + {0x0000147c, 0x00000000}, + {0x00008000, 0x00000000}, + {0x00008004, 0x00000000}, + {0x00008008, 0x00000000}, + {0x0000800c, 0x00000000}, + {0x00008010, 0x00080800}, + {0x00008018, 0x00000000}, + {0x00008020, 0x00000000}, + {0x00008038, 0x00000000}, + {0x0000803c, 0x00000000}, + {0x00008040, 0x00000000}, + {0x00008044, 0x00000000}, + {0x00008048, 0x00000000}, + {0x0000804c, 0xffffffff}, + {0x00008054, 0x00000000}, + {0x00008058, 0x00000000}, + {0x0000805c, 0x000fc78f}, + {0x00008060, 0x0000000f}, + {0x00008064, 0x00000000}, + {0x00008070, 0x00000310}, + {0x00008074, 0x00000020}, + {0x00008078, 0x00000000}, + {0x0000809c, 0x0000000f}, + {0x000080a0, 0x00000000}, + {0x000080a4, 0x02ff0000}, + {0x000080a8, 0x0e070605}, + {0x000080ac, 0x0000000d}, + {0x000080b0, 0x00000000}, + {0x000080b4, 0x00000000}, + {0x000080b8, 0x00000000}, + {0x000080bc, 0x00000000}, + {0x000080c0, 0x2a800000}, + {0x000080c4, 0x06900168}, + {0x000080c8, 0x13881c22}, + {0x000080cc, 0x01f40000}, + {0x000080d0, 0x00252500}, + {0x000080d4, 0x00a00000}, + {0x000080d8, 0x00400000}, + {0x000080dc, 0x00000000}, + {0x000080e0, 0xffffffff}, + {0x000080e4, 0x0000ffff}, + {0x000080e8, 0x3f3f3f3f}, + {0x000080ec, 0x00000000}, + {0x000080f0, 0x00000000}, + {0x000080f4, 0x00000000}, + {0x000080fc, 0x00020000}, + {0x00008100, 0x00000000}, + {0x00008108, 0x00000052}, + {0x0000810c, 0x00000000}, + {0x00008110, 0x00000000}, + {0x00008114, 0x000007ff}, + {0x00008118, 0x000000aa}, + {0x0000811c, 0x00003210}, + {0x00008124, 0x00000000}, + {0x00008128, 0x00000000}, + {0x0000812c, 0x00000000}, + {0x00008130, 0x00000000}, + {0x00008134, 0x00000000}, + {0x00008138, 0x00000000}, + {0x0000813c, 0x0000ffff}, + {0x00008144, 0xffffffff}, + {0x00008168, 0x00000000}, + {0x0000816c, 0x00000000}, + {0x00008170, 0x18486200}, + {0x00008174, 0x33332210}, + {0x00008178, 0x00000000}, + {0x0000817c, 0x00020000}, + {0x000081c0, 0x00000000}, + {0x000081c4, 0x33332210}, + {0x000081c8, 0x00000000}, + {0x000081cc, 0x00000000}, + {0x000081d4, 0x00000000}, + {0x000081ec, 0x00000000}, + {0x000081f0, 0x00000000}, + {0x000081f4, 0x00000000}, + {0x000081f8, 0x00000000}, + {0x000081fc, 0x00000000}, + {0x00008240, 0x00100000}, + {0x00008244, 0x0010f3d7}, + {0x00008248, 0x00000800}, + {0x0000824c, 0x0001e7ae}, + {0x00008250, 0x00000000}, + {0x00008254, 0x00000000}, + {0x00008258, 0x00000000}, + {0x0000825c, 0x40000000}, + {0x00008260, 0x00080922}, + {0x00008264, 0x9d400010}, + {0x00008268, 0xffffffff}, + {0x0000826c, 0x0000ffff}, + {0x00008270, 0x00000000}, + {0x00008274, 0x40000000}, + {0x00008278, 0x003e4180}, + {0x0000827c, 0x00000004}, + {0x00008284, 0x0000002c}, + {0x00008288, 0x0000002c}, + {0x0000828c, 0x000000ff}, + {0x00008294, 0x00000000}, + {0x00008298, 0x00000000}, + {0x0000829c, 0x00000000}, + {0x00008300, 0x00000140}, + {0x00008314, 0x00000000}, + {0x0000831c, 0x0000010d}, + {0x00008328, 0x00000000}, + {0x0000832c, 0x00000007}, + {0x00008330, 0x00000302}, + {0x00008334, 0x00000700}, + {0x00008338, 0x00ff0000}, + {0x0000833c, 0x02400000}, + {0x00008340, 0x000107ff}, + {0x00008344, 0xaa48105b}, + {0x00008348, 0x008f0000}, + {0x0000835c, 0x00000000}, + {0x00008360, 0xffffffff}, + {0x00008364, 0xffffffff}, + {0x00008368, 0x00000000}, + {0x00008370, 0x00000000}, + {0x00008374, 0x000000ff}, + {0x00008378, 0x00000000}, + {0x0000837c, 0x00000000}, + {0x00008380, 0xffffffff}, + {0x00008384, 0xffffffff}, + {0x00008390, 0xffffffff}, + {0x00008394, 0xffffffff}, + {0x00008398, 0x00000000}, + {0x0000839c, 0x00000000}, + {0x000083a0, 0x00000000}, + {0x000083a4, 0x0000fa14}, + {0x000083a8, 0x000f0c00}, + {0x000083ac, 0x33332210}, + {0x000083b0, 0x33332210}, + {0x000083b4, 0x33332210}, + {0x000083b8, 0x33332210}, + {0x000083bc, 0x00000000}, + {0x000083c0, 0x00000000}, + {0x000083c4, 0x00000000}, + {0x000083c8, 0x00000000}, + {0x000083cc, 0x00000200}, + {0x000083d0, 0x000101ff}, +}; + +#define ar9340Common_wo_xlna_rx_gain_table_1p0 ar9300Common_wo_xlna_rx_gain_table_2p2 + +static const u32 ar9340_1p0_soc_preamble[][2] = { + /* Addr allmodes */ + {0x00007008, 0x00000000}, + {0x00007020, 0x00000000}, + {0x00007034, 0x00000002}, + {0x00007038, 0x000004c2}, +}; + +#endif /* INITVALS_9340_H */ diff --git a/drivers/net/wireless/ath/ath9k/ar9462_2p0_initvals.h b/drivers/net/wireless/ath/ath9k/ar9462_2p0_initvals.h new file mode 100644 index 0000000..999ab08 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9462_2p0_initvals.h @@ -0,0 +1,1452 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef INITVALS_9462_2P0_H +#define INITVALS_9462_2P0_H + +/* AR9462 2.0 */ + +static const u32 ar9462_modes_fast_clock_2p0[][3] = { + /* Addr 5G_HT20 5G_HT40 */ + {0x00001030, 0x00000268, 0x000004d0}, + {0x00001070, 0x0000018c, 0x00000318}, + {0x000010b0, 0x00000fd0, 0x00001fa0}, + {0x00008014, 0x044c044c, 0x08980898}, + {0x0000801c, 0x148ec02b, 0x148ec057}, + {0x00008318, 0x000044c0, 0x00008980}, + {0x00009e00, 0x0372131c, 0x0372131c}, + {0x0000a230, 0x0000400b, 0x00004016}, + {0x0000a254, 0x00000898, 0x00001130}, +}; + +static const u32 ar9462_pciephy_clkreq_enable_L1_2p0[][2] = { + /* Addr allmodes */ + {0x00018c00, 0x18253ede}, + {0x00018c04, 0x000801d8}, + {0x00018c08, 0x0003780c}, +}; + +static const u32 ar9462_2p0_baseband_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009810, 0xd00a8005, 0xd00a8005, 0xd00a8011, 0xd00a800d}, + {0x00009820, 0x206a022e, 0x206a022e, 0x206a012e, 0x206a01ae}, + {0x00009824, 0x63c640de, 0x5ac640d0, 0x5ac640d0, 0x63c640da}, + {0x00009828, 0x0796be89, 0x0696b081, 0x0696b881, 0x09143e81}, + {0x0000982c, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4}, + {0x00009830, 0x0000059c, 0x0000059c, 0x0000119c, 0x0000119c}, + {0x00009c00, 0x000000c4, 0x000000c4, 0x000000c4, 0x000000c4}, + {0x00009e00, 0x0372111a, 0x0372111a, 0x037216a0, 0x037216a2}, + {0x00009e04, 0x001c2020, 0x001c2020, 0x001c2020, 0x001c2020}, + {0x00009e0c, 0x6c4000e2, 0x6d4000e2, 0x6d4000e2, 0x6c4000d8}, + {0x00009e10, 0x92c88d2e, 0x7ec88d2e, 0x7ec84d2e, 0x7ec86d2e}, + {0x00009e14, 0x37b95d5e, 0x37b9605e, 0x3236605e, 0x32365a5e}, + {0x00009e18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00009e1c, 0x0001cf9c, 0x0001cf9c, 0x00021f9c, 0x00021f9c}, + {0x00009e20, 0x000003b5, 0x000003b5, 0x000003ce, 0x000003ce}, + {0x00009e2c, 0x0000001c, 0x0000001c, 0x00000021, 0x00000021}, + {0x00009e3c, 0xcf946220, 0xcf946220, 0xcfd5c782, 0xcfd5c282}, + {0x00009e44, 0x62321e27, 0x62321e27, 0xfe291e27, 0xfe291e27}, + {0x00009e48, 0x5030201a, 0x5030201a, 0x50302012, 0x50302012}, + {0x00009fc8, 0x0003f000, 0x0003f000, 0x0001a000, 0x0001a000}, + {0x0000a204, 0x01318fc0, 0x01318fc4, 0x01318fc4, 0x01318fc0}, + {0x0000a208, 0x00000104, 0x00000104, 0x00000004, 0x00000004}, + {0x0000a22c, 0x01026a2f, 0x01026a27, 0x01026a2f, 0x01026a2f}, + {0x0000a230, 0x0000400a, 0x00004014, 0x00004016, 0x0000400b}, + {0x0000a234, 0x00000fff, 0x10000fff, 0x10000fff, 0x00000fff}, + {0x0000a238, 0xffb81018, 0xffb81018, 0xffb81018, 0xffb81018}, + {0x0000a250, 0x00000000, 0x00000000, 0x00000210, 0x00000108}, + {0x0000a254, 0x000007d0, 0x00000fa0, 0x00001130, 0x00000898}, + {0x0000a258, 0x02020002, 0x02020002, 0x02020002, 0x02020002}, + {0x0000a25c, 0x01000e0e, 0x01000e0e, 0x01000e0e, 0x01000e0e}, + {0x0000a260, 0x0a021501, 0x0a021501, 0x3a021501, 0x3a021501}, + {0x0000a264, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x0000a280, 0x00000007, 0x00000007, 0x0000000b, 0x0000000b}, + {0x0000a284, 0x00000000, 0x00000000, 0x00000150, 0x00000150}, + {0x0000a288, 0x00000110, 0x00000110, 0x00000110, 0x00000110}, + {0x0000a28c, 0x00022222, 0x00022222, 0x00022222, 0x00022222}, + {0x0000a2c4, 0x00058d18, 0x00058d18, 0x00058d18, 0x00058d18}, + {0x0000a2d0, 0x00041981, 0x00041981, 0x00041981, 0x00041982}, + {0x0000a2d8, 0x7999a83b, 0x7999a83b, 0x7999a83b, 0x7999a83b}, + {0x0000a358, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a3a4, 0x00000050, 0x00000050, 0x00000000, 0x00000000}, + {0x0000a3a8, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa}, + {0x0000a3ac, 0xaaaaaa00, 0xaa30aa30, 0xaaaaaa00, 0xaaaaaa00}, + {0x0000a41c, 0x1ce739ce, 0x1ce739ce, 0x1ce739ce, 0x1ce739ce}, + {0x0000a420, 0x000001ce, 0x000001ce, 0x000001ce, 0x000001ce}, + {0x0000a424, 0x1ce739ce, 0x1ce739ce, 0x1ce739ce, 0x1ce739ce}, + {0x0000a428, 0x000001ce, 0x000001ce, 0x000001ce, 0x000001ce}, + {0x0000a42c, 0x1ce739ce, 0x1ce739ce, 0x1ce739ce, 0x1ce739ce}, + {0x0000a430, 0x1ce739ce, 0x1ce739ce, 0x1ce739ce, 0x1ce739ce}, + {0x0000a830, 0x0000019c, 0x0000019c, 0x0000019c, 0x0000019c}, + {0x0000ae04, 0x001c0000, 0x001c0000, 0x001c0000, 0x00100000}, + {0x0000ae18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000ae1c, 0x0000019c, 0x0000019c, 0x0000019c, 0x0000019c}, + {0x0000ae20, 0x000001b5, 0x000001b5, 0x000001ce, 0x000001ce}, + {0x0000b284, 0x00000000, 0x00000000, 0x00000550, 0x00000550}, +}; + +static const u32 ar9462_common_rx_gain_table_2p0[][2] = { + /* Addr allmodes */ + {0x0000a000, 0x00010000}, + {0x0000a004, 0x00030002}, + {0x0000a008, 0x00050004}, + {0x0000a00c, 0x00810080}, + {0x0000a010, 0x00830082}, + {0x0000a014, 0x01810180}, + {0x0000a018, 0x01830182}, + {0x0000a01c, 0x01850184}, + {0x0000a020, 0x01890188}, + {0x0000a024, 0x018b018a}, + {0x0000a028, 0x018d018c}, + {0x0000a02c, 0x01910190}, + {0x0000a030, 0x01930192}, + {0x0000a034, 0x01950194}, + {0x0000a038, 0x038a0196}, + {0x0000a03c, 0x038c038b}, + {0x0000a040, 0x0390038d}, + {0x0000a044, 0x03920391}, + {0x0000a048, 0x03940393}, + {0x0000a04c, 0x03960395}, + {0x0000a050, 0x00000000}, + {0x0000a054, 0x00000000}, + {0x0000a058, 0x00000000}, + {0x0000a05c, 0x00000000}, + {0x0000a060, 0x00000000}, + {0x0000a064, 0x00000000}, + {0x0000a068, 0x00000000}, + {0x0000a06c, 0x00000000}, + {0x0000a070, 0x00000000}, + {0x0000a074, 0x00000000}, + {0x0000a078, 0x00000000}, + {0x0000a07c, 0x00000000}, + {0x0000a080, 0x22222229}, + {0x0000a084, 0x1d1d1d1d}, + {0x0000a088, 0x1d1d1d1d}, + {0x0000a08c, 0x1d1d1d1d}, + {0x0000a090, 0x171d1d1d}, + {0x0000a094, 0x11111717}, + {0x0000a098, 0x00030311}, + {0x0000a09c, 0x00000000}, + {0x0000a0a0, 0x00000000}, + {0x0000a0a4, 0x00000000}, + {0x0000a0a8, 0x00000000}, + {0x0000a0ac, 0x00000000}, + {0x0000a0b0, 0x00000000}, + {0x0000a0b4, 0x00000000}, + {0x0000a0b8, 0x00000000}, + {0x0000a0bc, 0x00000000}, + {0x0000a0c0, 0x001f0000}, + {0x0000a0c4, 0x01000101}, + {0x0000a0c8, 0x011e011f}, + {0x0000a0cc, 0x011c011d}, + {0x0000a0d0, 0x02030204}, + {0x0000a0d4, 0x02010202}, + {0x0000a0d8, 0x021f0200}, + {0x0000a0dc, 0x0302021e}, + {0x0000a0e0, 0x03000301}, + {0x0000a0e4, 0x031e031f}, + {0x0000a0e8, 0x0402031d}, + {0x0000a0ec, 0x04000401}, + {0x0000a0f0, 0x041e041f}, + {0x0000a0f4, 0x0502041d}, + {0x0000a0f8, 0x05000501}, + {0x0000a0fc, 0x051e051f}, + {0x0000a100, 0x06010602}, + {0x0000a104, 0x061f0600}, + {0x0000a108, 0x061d061e}, + {0x0000a10c, 0x07020703}, + {0x0000a110, 0x07000701}, + {0x0000a114, 0x00000000}, + {0x0000a118, 0x00000000}, + {0x0000a11c, 0x00000000}, + {0x0000a120, 0x00000000}, + {0x0000a124, 0x00000000}, + {0x0000a128, 0x00000000}, + {0x0000a12c, 0x00000000}, + {0x0000a130, 0x00000000}, + {0x0000a134, 0x00000000}, + {0x0000a138, 0x00000000}, + {0x0000a13c, 0x00000000}, + {0x0000a140, 0x001f0000}, + {0x0000a144, 0x01000101}, + {0x0000a148, 0x011e011f}, + {0x0000a14c, 0x011c011d}, + {0x0000a150, 0x02030204}, + {0x0000a154, 0x02010202}, + {0x0000a158, 0x021f0200}, + {0x0000a15c, 0x0302021e}, + {0x0000a160, 0x03000301}, + {0x0000a164, 0x031e031f}, + {0x0000a168, 0x0402031d}, + {0x0000a16c, 0x04000401}, + {0x0000a170, 0x041e041f}, + {0x0000a174, 0x0502041d}, + {0x0000a178, 0x05000501}, + {0x0000a17c, 0x051e051f}, + {0x0000a180, 0x06010602}, + {0x0000a184, 0x061f0600}, + {0x0000a188, 0x061d061e}, + {0x0000a18c, 0x07020703}, + {0x0000a190, 0x07000701}, + {0x0000a194, 0x00000000}, + {0x0000a198, 0x00000000}, + {0x0000a19c, 0x00000000}, + {0x0000a1a0, 0x00000000}, + {0x0000a1a4, 0x00000000}, + {0x0000a1a8, 0x00000000}, + {0x0000a1ac, 0x00000000}, + {0x0000a1b0, 0x00000000}, + {0x0000a1b4, 0x00000000}, + {0x0000a1b8, 0x00000000}, + {0x0000a1bc, 0x00000000}, + {0x0000a1c0, 0x00000000}, + {0x0000a1c4, 0x00000000}, + {0x0000a1c8, 0x00000000}, + {0x0000a1cc, 0x00000000}, + {0x0000a1d0, 0x00000000}, + {0x0000a1d4, 0x00000000}, + {0x0000a1d8, 0x00000000}, + {0x0000a1dc, 0x00000000}, + {0x0000a1e0, 0x00000000}, + {0x0000a1e4, 0x00000000}, + {0x0000a1e8, 0x00000000}, + {0x0000a1ec, 0x00000000}, + {0x0000a1f0, 0x00000396}, + {0x0000a1f4, 0x00000396}, + {0x0000a1f8, 0x00000396}, + {0x0000a1fc, 0x00000196}, + {0x0000b000, 0x00010000}, + {0x0000b004, 0x00030002}, + {0x0000b008, 0x00050004}, + {0x0000b00c, 0x00810080}, + {0x0000b010, 0x00830082}, + {0x0000b014, 0x01810180}, + {0x0000b018, 0x01830182}, + {0x0000b01c, 0x01850184}, + {0x0000b020, 0x02810280}, + {0x0000b024, 0x02830282}, + {0x0000b028, 0x02850284}, + {0x0000b02c, 0x02890288}, + {0x0000b030, 0x028b028a}, + {0x0000b034, 0x0388028c}, + {0x0000b038, 0x038a0389}, + {0x0000b03c, 0x038c038b}, + {0x0000b040, 0x0390038d}, + {0x0000b044, 0x03920391}, + {0x0000b048, 0x03940393}, + {0x0000b04c, 0x03960395}, + {0x0000b050, 0x00000000}, + {0x0000b054, 0x00000000}, + {0x0000b058, 0x00000000}, + {0x0000b05c, 0x00000000}, + {0x0000b060, 0x00000000}, + {0x0000b064, 0x00000000}, + {0x0000b068, 0x00000000}, + {0x0000b06c, 0x00000000}, + {0x0000b070, 0x00000000}, + {0x0000b074, 0x00000000}, + {0x0000b078, 0x00000000}, + {0x0000b07c, 0x00000000}, + {0x0000b080, 0x2a2d2f32}, + {0x0000b084, 0x21232328}, + {0x0000b088, 0x19191c1e}, + {0x0000b08c, 0x12141417}, + {0x0000b090, 0x07070e0e}, + {0x0000b094, 0x03030305}, + {0x0000b098, 0x00000003}, + {0x0000b09c, 0x00000000}, + {0x0000b0a0, 0x00000000}, + {0x0000b0a4, 0x00000000}, + {0x0000b0a8, 0x00000000}, + {0x0000b0ac, 0x00000000}, + {0x0000b0b0, 0x00000000}, + {0x0000b0b4, 0x00000000}, + {0x0000b0b8, 0x00000000}, + {0x0000b0bc, 0x00000000}, + {0x0000b0c0, 0x003f0020}, + {0x0000b0c4, 0x00400041}, + {0x0000b0c8, 0x0140005f}, + {0x0000b0cc, 0x0160015f}, + {0x0000b0d0, 0x017e017f}, + {0x0000b0d4, 0x02410242}, + {0x0000b0d8, 0x025f0240}, + {0x0000b0dc, 0x027f0260}, + {0x0000b0e0, 0x0341027e}, + {0x0000b0e4, 0x035f0340}, + {0x0000b0e8, 0x037f0360}, + {0x0000b0ec, 0x04400441}, + {0x0000b0f0, 0x0460045f}, + {0x0000b0f4, 0x0541047f}, + {0x0000b0f8, 0x055f0540}, + {0x0000b0fc, 0x057f0560}, + {0x0000b100, 0x06400641}, + {0x0000b104, 0x0660065f}, + {0x0000b108, 0x067e067f}, + {0x0000b10c, 0x07410742}, + {0x0000b110, 0x075f0740}, + {0x0000b114, 0x077f0760}, + {0x0000b118, 0x07800781}, + {0x0000b11c, 0x07a0079f}, + {0x0000b120, 0x07c107bf}, + {0x0000b124, 0x000007c0}, + {0x0000b128, 0x00000000}, + {0x0000b12c, 0x00000000}, + {0x0000b130, 0x00000000}, + {0x0000b134, 0x00000000}, + {0x0000b138, 0x00000000}, + {0x0000b13c, 0x00000000}, + {0x0000b140, 0x003f0020}, + {0x0000b144, 0x00400041}, + {0x0000b148, 0x0140005f}, + {0x0000b14c, 0x0160015f}, + {0x0000b150, 0x017e017f}, + {0x0000b154, 0x02410242}, + {0x0000b158, 0x025f0240}, + {0x0000b15c, 0x027f0260}, + {0x0000b160, 0x0341027e}, + {0x0000b164, 0x035f0340}, + {0x0000b168, 0x037f0360}, + {0x0000b16c, 0x04400441}, + {0x0000b170, 0x0460045f}, + {0x0000b174, 0x0541047f}, + {0x0000b178, 0x055f0540}, + {0x0000b17c, 0x057f0560}, + {0x0000b180, 0x06400641}, + {0x0000b184, 0x0660065f}, + {0x0000b188, 0x067e067f}, + {0x0000b18c, 0x07410742}, + {0x0000b190, 0x075f0740}, + {0x0000b194, 0x077f0760}, + {0x0000b198, 0x07800781}, + {0x0000b19c, 0x07a0079f}, + {0x0000b1a0, 0x07c107bf}, + {0x0000b1a4, 0x000007c0}, + {0x0000b1a8, 0x00000000}, + {0x0000b1ac, 0x00000000}, + {0x0000b1b0, 0x00000000}, + {0x0000b1b4, 0x00000000}, + {0x0000b1b8, 0x00000000}, + {0x0000b1bc, 0x00000000}, + {0x0000b1c0, 0x00000000}, + {0x0000b1c4, 0x00000000}, + {0x0000b1c8, 0x00000000}, + {0x0000b1cc, 0x00000000}, + {0x0000b1d0, 0x00000000}, + {0x0000b1d4, 0x00000000}, + {0x0000b1d8, 0x00000000}, + {0x0000b1dc, 0x00000000}, + {0x0000b1e0, 0x00000000}, + {0x0000b1e4, 0x00000000}, + {0x0000b1e8, 0x00000000}, + {0x0000b1ec, 0x00000000}, + {0x0000b1f0, 0x00000396}, + {0x0000b1f4, 0x00000396}, + {0x0000b1f8, 0x00000396}, + {0x0000b1fc, 0x00000196}, +}; + +static const u32 ar9462_pciephy_clkreq_disable_L1_2p0[][2] = { + /* Addr allmodes */ + {0x00018c00, 0x18213ede}, + {0x00018c04, 0x000801d8}, + {0x00018c08, 0x0003780c}, +}; + +static const u32 ar9462_pciephy_pll_on_clkreq_disable_L1_2p0[][2] = { + /* Addr allmodes */ + {0x00018c00, 0x18212ede}, + {0x00018c04, 0x000801d8}, + {0x00018c08, 0x0003780c}, +}; + +static const u32 ar9462_2p0_radio_postamble_sys2ant[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x000160ac, 0xa4646c08, 0xa4646c08, 0x24645808, 0x24645808}, + {0x00016140, 0x10804008, 0x10804008, 0x50804008, 0x50804008}, + {0x00016540, 0x10804008, 0x10804008, 0x50804008, 0x50804008}, +}; + +static const u32 ar9462_common_wo_xlna_rx_gain_table_2p0[][2] = { + /* Addr allmodes */ + {0x0000a000, 0x00010000}, + {0x0000a004, 0x00030002}, + {0x0000a008, 0x00050004}, + {0x0000a00c, 0x00810080}, + {0x0000a010, 0x00830082}, + {0x0000a014, 0x01810180}, + {0x0000a018, 0x01830182}, + {0x0000a01c, 0x01850184}, + {0x0000a020, 0x01890188}, + {0x0000a024, 0x018b018a}, + {0x0000a028, 0x018d018c}, + {0x0000a02c, 0x03820190}, + {0x0000a030, 0x03840383}, + {0x0000a034, 0x03880385}, + {0x0000a038, 0x038a0389}, + {0x0000a03c, 0x038c038b}, + {0x0000a040, 0x0390038d}, + {0x0000a044, 0x03920391}, + {0x0000a048, 0x03940393}, + {0x0000a04c, 0x03960395}, + {0x0000a050, 0x00000000}, + {0x0000a054, 0x00000000}, + {0x0000a058, 0x00000000}, + {0x0000a05c, 0x00000000}, + {0x0000a060, 0x00000000}, + {0x0000a064, 0x00000000}, + {0x0000a068, 0x00000000}, + {0x0000a06c, 0x00000000}, + {0x0000a070, 0x00000000}, + {0x0000a074, 0x00000000}, + {0x0000a078, 0x00000000}, + {0x0000a07c, 0x00000000}, + {0x0000a080, 0x29292929}, + {0x0000a084, 0x29292929}, + {0x0000a088, 0x29292929}, + {0x0000a08c, 0x29292929}, + {0x0000a090, 0x22292929}, + {0x0000a094, 0x1d1d2222}, + {0x0000a098, 0x0c111117}, + {0x0000a09c, 0x00030303}, + {0x0000a0a0, 0x00000000}, + {0x0000a0a4, 0x00000000}, + {0x0000a0a8, 0x00000000}, + {0x0000a0ac, 0x00000000}, + {0x0000a0b0, 0x00000000}, + {0x0000a0b4, 0x00000000}, + {0x0000a0b8, 0x00000000}, + {0x0000a0bc, 0x00000000}, + {0x0000a0c0, 0x001f0000}, + {0x0000a0c4, 0x01000101}, + {0x0000a0c8, 0x011e011f}, + {0x0000a0cc, 0x011c011d}, + {0x0000a0d0, 0x02030204}, + {0x0000a0d4, 0x02010202}, + {0x0000a0d8, 0x021f0200}, + {0x0000a0dc, 0x0302021e}, + {0x0000a0e0, 0x03000301}, + {0x0000a0e4, 0x031e031f}, + {0x0000a0e8, 0x0402031d}, + {0x0000a0ec, 0x04000401}, + {0x0000a0f0, 0x041e041f}, + {0x0000a0f4, 0x0502041d}, + {0x0000a0f8, 0x05000501}, + {0x0000a0fc, 0x051e051f}, + {0x0000a100, 0x06010602}, + {0x0000a104, 0x061f0600}, + {0x0000a108, 0x061d061e}, + {0x0000a10c, 0x07020703}, + {0x0000a110, 0x07000701}, + {0x0000a114, 0x00000000}, + {0x0000a118, 0x00000000}, + {0x0000a11c, 0x00000000}, + {0x0000a120, 0x00000000}, + {0x0000a124, 0x00000000}, + {0x0000a128, 0x00000000}, + {0x0000a12c, 0x00000000}, + {0x0000a130, 0x00000000}, + {0x0000a134, 0x00000000}, + {0x0000a138, 0x00000000}, + {0x0000a13c, 0x00000000}, + {0x0000a140, 0x001f0000}, + {0x0000a144, 0x01000101}, + {0x0000a148, 0x011e011f}, + {0x0000a14c, 0x011c011d}, + {0x0000a150, 0x02030204}, + {0x0000a154, 0x02010202}, + {0x0000a158, 0x021f0200}, + {0x0000a15c, 0x0302021e}, + {0x0000a160, 0x03000301}, + {0x0000a164, 0x031e031f}, + {0x0000a168, 0x0402031d}, + {0x0000a16c, 0x04000401}, + {0x0000a170, 0x041e041f}, + {0x0000a174, 0x0502041d}, + {0x0000a178, 0x05000501}, + {0x0000a17c, 0x051e051f}, + {0x0000a180, 0x06010602}, + {0x0000a184, 0x061f0600}, + {0x0000a188, 0x061d061e}, + {0x0000a18c, 0x07020703}, + {0x0000a190, 0x07000701}, + {0x0000a194, 0x00000000}, + {0x0000a198, 0x00000000}, + {0x0000a19c, 0x00000000}, + {0x0000a1a0, 0x00000000}, + {0x0000a1a4, 0x00000000}, + {0x0000a1a8, 0x00000000}, + {0x0000a1ac, 0x00000000}, + {0x0000a1b0, 0x00000000}, + {0x0000a1b4, 0x00000000}, + {0x0000a1b8, 0x00000000}, + {0x0000a1bc, 0x00000000}, + {0x0000a1c0, 0x00000000}, + {0x0000a1c4, 0x00000000}, + {0x0000a1c8, 0x00000000}, + {0x0000a1cc, 0x00000000}, + {0x0000a1d0, 0x00000000}, + {0x0000a1d4, 0x00000000}, + {0x0000a1d8, 0x00000000}, + {0x0000a1dc, 0x00000000}, + {0x0000a1e0, 0x00000000}, + {0x0000a1e4, 0x00000000}, + {0x0000a1e8, 0x00000000}, + {0x0000a1ec, 0x00000000}, + {0x0000a1f0, 0x00000396}, + {0x0000a1f4, 0x00000396}, + {0x0000a1f8, 0x00000396}, + {0x0000a1fc, 0x00000196}, + {0x0000b000, 0x00010000}, + {0x0000b004, 0x00030002}, + {0x0000b008, 0x00050004}, + {0x0000b00c, 0x00810080}, + {0x0000b010, 0x00830082}, + {0x0000b014, 0x01810180}, + {0x0000b018, 0x01830182}, + {0x0000b01c, 0x01850184}, + {0x0000b020, 0x02810280}, + {0x0000b024, 0x02830282}, + {0x0000b028, 0x02850284}, + {0x0000b02c, 0x02890288}, + {0x0000b030, 0x028b028a}, + {0x0000b034, 0x0388028c}, + {0x0000b038, 0x038a0389}, + {0x0000b03c, 0x038c038b}, + {0x0000b040, 0x0390038d}, + {0x0000b044, 0x03920391}, + {0x0000b048, 0x03940393}, + {0x0000b04c, 0x03960395}, + {0x0000b050, 0x00000000}, + {0x0000b054, 0x00000000}, + {0x0000b058, 0x00000000}, + {0x0000b05c, 0x00000000}, + {0x0000b060, 0x00000000}, + {0x0000b064, 0x00000000}, + {0x0000b068, 0x00000000}, + {0x0000b06c, 0x00000000}, + {0x0000b070, 0x00000000}, + {0x0000b074, 0x00000000}, + {0x0000b078, 0x00000000}, + {0x0000b07c, 0x00000000}, + {0x0000b080, 0x32323232}, + {0x0000b084, 0x2f2f3232}, + {0x0000b088, 0x23282a2d}, + {0x0000b08c, 0x1c1e2123}, + {0x0000b090, 0x14171919}, + {0x0000b094, 0x0e0e1214}, + {0x0000b098, 0x03050707}, + {0x0000b09c, 0x00030303}, + {0x0000b0a0, 0x00000000}, + {0x0000b0a4, 0x00000000}, + {0x0000b0a8, 0x00000000}, + {0x0000b0ac, 0x00000000}, + {0x0000b0b0, 0x00000000}, + {0x0000b0b4, 0x00000000}, + {0x0000b0b8, 0x00000000}, + {0x0000b0bc, 0x00000000}, + {0x0000b0c0, 0x003f0020}, + {0x0000b0c4, 0x00400041}, + {0x0000b0c8, 0x0140005f}, + {0x0000b0cc, 0x0160015f}, + {0x0000b0d0, 0x017e017f}, + {0x0000b0d4, 0x02410242}, + {0x0000b0d8, 0x025f0240}, + {0x0000b0dc, 0x027f0260}, + {0x0000b0e0, 0x0341027e}, + {0x0000b0e4, 0x035f0340}, + {0x0000b0e8, 0x037f0360}, + {0x0000b0ec, 0x04400441}, + {0x0000b0f0, 0x0460045f}, + {0x0000b0f4, 0x0541047f}, + {0x0000b0f8, 0x055f0540}, + {0x0000b0fc, 0x057f0560}, + {0x0000b100, 0x06400641}, + {0x0000b104, 0x0660065f}, + {0x0000b108, 0x067e067f}, + {0x0000b10c, 0x07410742}, + {0x0000b110, 0x075f0740}, + {0x0000b114, 0x077f0760}, + {0x0000b118, 0x07800781}, + {0x0000b11c, 0x07a0079f}, + {0x0000b120, 0x07c107bf}, + {0x0000b124, 0x000007c0}, + {0x0000b128, 0x00000000}, + {0x0000b12c, 0x00000000}, + {0x0000b130, 0x00000000}, + {0x0000b134, 0x00000000}, + {0x0000b138, 0x00000000}, + {0x0000b13c, 0x00000000}, + {0x0000b140, 0x003f0020}, + {0x0000b144, 0x00400041}, + {0x0000b148, 0x0140005f}, + {0x0000b14c, 0x0160015f}, + {0x0000b150, 0x017e017f}, + {0x0000b154, 0x02410242}, + {0x0000b158, 0x025f0240}, + {0x0000b15c, 0x027f0260}, + {0x0000b160, 0x0341027e}, + {0x0000b164, 0x035f0340}, + {0x0000b168, 0x037f0360}, + {0x0000b16c, 0x04400441}, + {0x0000b170, 0x0460045f}, + {0x0000b174, 0x0541047f}, + {0x0000b178, 0x055f0540}, + {0x0000b17c, 0x057f0560}, + {0x0000b180, 0x06400641}, + {0x0000b184, 0x0660065f}, + {0x0000b188, 0x067e067f}, + {0x0000b18c, 0x07410742}, + {0x0000b190, 0x075f0740}, + {0x0000b194, 0x077f0760}, + {0x0000b198, 0x07800781}, + {0x0000b19c, 0x07a0079f}, + {0x0000b1a0, 0x07c107bf}, + {0x0000b1a4, 0x000007c0}, + {0x0000b1a8, 0x00000000}, + {0x0000b1ac, 0x00000000}, + {0x0000b1b0, 0x00000000}, + {0x0000b1b4, 0x00000000}, + {0x0000b1b8, 0x00000000}, + {0x0000b1bc, 0x00000000}, + {0x0000b1c0, 0x00000000}, + {0x0000b1c4, 0x00000000}, + {0x0000b1c8, 0x00000000}, + {0x0000b1cc, 0x00000000}, + {0x0000b1d0, 0x00000000}, + {0x0000b1d4, 0x00000000}, + {0x0000b1d8, 0x00000000}, + {0x0000b1dc, 0x00000000}, + {0x0000b1e0, 0x00000000}, + {0x0000b1e4, 0x00000000}, + {0x0000b1e8, 0x00000000}, + {0x0000b1ec, 0x00000000}, + {0x0000b1f0, 0x00000396}, + {0x0000b1f4, 0x00000396}, + {0x0000b1f8, 0x00000396}, + {0x0000b1fc, 0x00000196}, +}; + +static const u32 ar9462_2p0_baseband_core_txfir_coeff_japan_2484[][2] = { + /* Addr allmodes */ + {0x0000a398, 0x00000000}, + {0x0000a39c, 0x6f7f0301}, + {0x0000a3a0, 0xca9228ee}, +}; + +static const u32 ar9462_modes_low_ob_db_tx_gain_table_2p0[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x000098bc, 0x00000002, 0x00000002, 0x00000002, 0x00000002}, + {0x0000a2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000a2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000a2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d9, 0x000050d9}, + {0x0000a458, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a504, 0x06000003, 0x06000003, 0x04000002, 0x04000002}, + {0x0000a508, 0x0a000020, 0x0a000020, 0x08000004, 0x08000004}, + {0x0000a50c, 0x10000023, 0x10000023, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x16000220, 0x16000220, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x1c000223, 0x1c000223, 0x12000400, 0x12000400}, + {0x0000a518, 0x21020220, 0x21020220, 0x16000402, 0x16000402}, + {0x0000a51c, 0x27020223, 0x27020223, 0x19000404, 0x19000404}, + {0x0000a520, 0x2b022220, 0x2b022220, 0x1c000603, 0x1c000603}, + {0x0000a524, 0x2f022222, 0x2f022222, 0x21000a02, 0x21000a02}, + {0x0000a528, 0x34022225, 0x34022225, 0x25000a04, 0x25000a04}, + {0x0000a52c, 0x3a02222a, 0x3a02222a, 0x28000a20, 0x28000a20}, + {0x0000a530, 0x3e02222c, 0x3e02222c, 0x2c000e20, 0x2c000e20}, + {0x0000a534, 0x4202242a, 0x4202242a, 0x30000e22, 0x30000e22}, + {0x0000a538, 0x4702244a, 0x4702244a, 0x34000e24, 0x34000e24}, + {0x0000a53c, 0x4b02244c, 0x4b02244c, 0x38001640, 0x38001640}, + {0x0000a540, 0x4e02246c, 0x4e02246c, 0x3c001660, 0x3c001660}, + {0x0000a544, 0x5302266c, 0x5302266c, 0x3f001861, 0x3f001861}, + {0x0000a548, 0x5702286c, 0x5702286c, 0x43001a81, 0x43001a81}, + {0x0000a54c, 0x5c04286b, 0x5c04286b, 0x47001a83, 0x47001a83}, + {0x0000a550, 0x61042a6c, 0x61042a6c, 0x4a001c84, 0x4a001c84}, + {0x0000a554, 0x66062a6c, 0x66062a6c, 0x4e001ce3, 0x4e001ce3}, + {0x0000a558, 0x6b062e6c, 0x6b062e6c, 0x52001ce5, 0x52001ce5}, + {0x0000a55c, 0x7006308c, 0x7006308c, 0x56001ce9, 0x56001ce9}, + {0x0000a560, 0x730a308a, 0x730a308a, 0x5a001ceb, 0x5a001ceb}, + {0x0000a564, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a568, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a56c, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a570, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a574, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a578, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a57c, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x01404000, 0x01404000, 0x01404000, 0x01404000}, + {0x0000a618, 0x01404501, 0x01404501, 0x01404501, 0x01404501}, + {0x0000a61c, 0x02008802, 0x02008802, 0x02008501, 0x02008501}, + {0x0000a620, 0x0300cc03, 0x0300cc03, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x0300cc03, 0x0300cc03, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x0300cc03, 0x0300cc03, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x03810c03, 0x03810c03, 0x04015005, 0x04015005}, + {0x0000a630, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a634, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a638, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a63c, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000b2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000b2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000b2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x00016044, 0x012482d4, 0x012482d4, 0x012482d4, 0x012482d4}, + {0x00016048, 0x64992060, 0x64992060, 0x64992060, 0x64992060}, + {0x00016054, 0x6db60000, 0x6db60000, 0x6db60000, 0x6db60000}, + {0x00016444, 0x012482d4, 0x012482d4, 0x012482d4, 0x012482d4}, + {0x00016448, 0x64992000, 0x64992000, 0x64992000, 0x64992000}, + {0x00016454, 0x6db60000, 0x6db60000, 0x6db60000, 0x6db60000}, +}; + +static const u32 ar9462_2p0_soc_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00007010, 0x00000033, 0x00000033, 0x00000033, 0x00000033}, +}; + +static const u32 ar9462_2p0_baseband_core[][2] = { + /* Addr allmodes */ + {0x00009800, 0xafe68e30}, + {0x00009804, 0xfd14e000}, + {0x00009808, 0x9c0a9f6b}, + {0x0000980c, 0x04900000}, + {0x00009814, 0x9280c00a}, + {0x00009818, 0x00000000}, + {0x0000981c, 0x00020028}, + {0x00009834, 0x6400a290}, + {0x00009838, 0x0108ecff}, + {0x0000983c, 0x0d000600}, + {0x00009880, 0x201fff00}, + {0x00009884, 0x00001042}, + {0x000098a4, 0x00200400}, + {0x000098b0, 0x32440bbe}, + {0x000098d0, 0x004b6a8e}, + {0x000098d4, 0x00000820}, + {0x000098dc, 0x00000000}, + {0x000098e4, 0x01ffffff}, + {0x000098e8, 0x01ffffff}, + {0x000098ec, 0x01ffffff}, + {0x000098f0, 0x00000000}, + {0x000098f4, 0x00000000}, + {0x00009bf0, 0x80000000}, + {0x00009c04, 0xff55ff55}, + {0x00009c08, 0x0320ff55}, + {0x00009c0c, 0x00000000}, + {0x00009c10, 0x00000000}, + {0x00009c14, 0x00046384}, + {0x00009c18, 0x05b6b440}, + {0x00009c1c, 0x00b6b440}, + {0x00009d00, 0xc080a333}, + {0x00009d04, 0x40206c10}, + {0x00009d08, 0x009c4060}, + {0x00009d0c, 0x9883800a}, + {0x00009d10, 0x01834061}, + {0x00009d14, 0x00c0040b}, + {0x00009d18, 0x00000000}, + {0x00009e08, 0x0038230c}, + {0x00009e24, 0x990bb515}, + {0x00009e28, 0x0c6f0000}, + {0x00009e30, 0x06336f77}, + {0x00009e34, 0x6af6532f}, + {0x00009e38, 0x0cc80c00}, + {0x00009e40, 0x15262820}, + {0x00009e4c, 0x00001004}, + {0x00009e50, 0x00ff03f1}, + {0x00009e54, 0xe4c555c2}, + {0x00009e58, 0xfd857722}, + {0x00009e5c, 0xe9198724}, + {0x00009fc0, 0x803e4788}, + {0x00009fc4, 0x0001efb5}, + {0x00009fcc, 0x40000014}, + {0x00009fd0, 0x0a193b93}, + {0x0000a20c, 0x00000000}, + {0x0000a220, 0x00000000}, + {0x0000a224, 0x00000000}, + {0x0000a228, 0x10002310}, + {0x0000a23c, 0x00000000}, + {0x0000a244, 0x0c000000}, + {0x0000a2a0, 0x00000001}, + {0x0000a2c0, 0x00000001}, + {0x0000a2c8, 0x00000000}, + {0x0000a2cc, 0x18c43433}, + {0x0000a2d4, 0x00000000}, + {0x0000a2ec, 0x00000000}, + {0x0000a2f0, 0x00000000}, + {0x0000a2f4, 0x00000000}, + {0x0000a2f8, 0x00000000}, + {0x0000a344, 0x00000000}, + {0x0000a34c, 0x00000000}, + {0x0000a350, 0x0000a000}, + {0x0000a364, 0x00000000}, + {0x0000a370, 0x00000000}, + {0x0000a390, 0x00000001}, + {0x0000a394, 0x00000444}, + {0x0000a398, 0x001f0e0f}, + {0x0000a39c, 0x0075393f}, + {0x0000a3a0, 0xb79f6427}, + {0x0000a3c0, 0x20202020}, + {0x0000a3c4, 0x22222220}, + {0x0000a3c8, 0x20200020}, + {0x0000a3cc, 0x20202020}, + {0x0000a3d0, 0x20202020}, + {0x0000a3d4, 0x20202020}, + {0x0000a3d8, 0x20202020}, + {0x0000a3dc, 0x20202020}, + {0x0000a3e0, 0x20202020}, + {0x0000a3e4, 0x20202020}, + {0x0000a3e8, 0x20202020}, + {0x0000a3ec, 0x20202020}, + {0x0000a3f0, 0x00000000}, + {0x0000a3f4, 0x00000006}, + {0x0000a3f8, 0x0c9bd380}, + {0x0000a3fc, 0x000f0f01}, + {0x0000a400, 0x8fa91f01}, + {0x0000a404, 0x00000000}, + {0x0000a408, 0x0e79e5c6}, + {0x0000a40c, 0x00820820}, + {0x0000a414, 0x1ce739ce}, + {0x0000a418, 0x2d001dce}, + {0x0000a434, 0x00000000}, + {0x0000a438, 0x00001801}, + {0x0000a43c, 0x00100000}, + {0x0000a444, 0x00000000}, + {0x0000a448, 0x05000080}, + {0x0000a44c, 0x00000001}, + {0x0000a450, 0x00010000}, + {0x0000a454, 0x07000000}, + {0x0000a644, 0xbfad9d74}, + {0x0000a648, 0x0048060a}, + {0x0000a64c, 0x00002037}, + {0x0000a670, 0x03020100}, + {0x0000a674, 0x09080504}, + {0x0000a678, 0x0d0c0b0a}, + {0x0000a67c, 0x13121110}, + {0x0000a680, 0x31301514}, + {0x0000a684, 0x35343332}, + {0x0000a688, 0x00000036}, + {0x0000a690, 0x00000838}, + {0x0000a6b0, 0x0000000a}, + {0x0000a6b4, 0x00512c01}, + {0x0000a7c0, 0x00000000}, + {0x0000a7c4, 0xfffffffc}, + {0x0000a7c8, 0x00000000}, + {0x0000a7cc, 0x00000000}, + {0x0000a7d0, 0x00000000}, + {0x0000a7d4, 0x00000004}, + {0x0000a7dc, 0x00000000}, + {0x0000a7f0, 0x80000000}, + {0x0000a8d0, 0x004b6a8e}, + {0x0000a8d4, 0x00000820}, + {0x0000a8dc, 0x00000000}, + {0x0000a8f0, 0x00000000}, + {0x0000a8f4, 0x00000000}, + {0x0000abf0, 0x80000000}, + {0x0000b2d0, 0x00000080}, + {0x0000b2d4, 0x00000000}, + {0x0000b2ec, 0x00000000}, + {0x0000b2f0, 0x00000000}, + {0x0000b2f4, 0x00000000}, + {0x0000b2f8, 0x00000000}, + {0x0000b408, 0x0e79e5c0}, + {0x0000b40c, 0x00820820}, + {0x0000b420, 0x00000000}, + {0x0000b6b0, 0x0000000a}, + {0x0000b6b4, 0x00000001}, +}; + +static const u32 ar9462_2p0_radio_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0001609c, 0x0b8ee524, 0x0b8ee524, 0x0b8ee524, 0x0b8ee524}, + {0x000160b0, 0x01d67f70, 0x01d67f70, 0x01d67f70, 0x01d67f70}, + {0x0001610c, 0x48000000, 0x40000000, 0x40000000, 0x40000000}, + {0x0001650c, 0x48000000, 0x40000000, 0x40000000, 0x40000000}, +}; + +static const u32 ar9462_modes_high_ob_db_tx_gain_table_2p0[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x000098bc, 0x00000002, 0x00000002, 0x00000002, 0x00000002}, + {0x0000a2dc, 0x01feee00, 0x01feee00, 0x03aaa352, 0x03aaa352}, + {0x0000a2e0, 0x0000f000, 0x0000f000, 0x03ccc584, 0x03ccc584}, + {0x0000a2e4, 0x01ff0000, 0x01ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a410, 0x000050da, 0x000050da, 0x000050de, 0x000050de}, + {0x0000a458, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a500, 0x00002220, 0x00002220, 0x00000000, 0x00000000}, + {0x0000a504, 0x06002223, 0x06002223, 0x04000002, 0x04000002}, + {0x0000a508, 0x0a022220, 0x0a022220, 0x08000004, 0x08000004}, + {0x0000a50c, 0x0f022223, 0x0f022223, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x14022620, 0x14022620, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x18022622, 0x18022622, 0x11000400, 0x11000400}, + {0x0000a518, 0x1b022822, 0x1b022822, 0x15000402, 0x15000402}, + {0x0000a51c, 0x20022842, 0x20022842, 0x19000404, 0x19000404}, + {0x0000a520, 0x22022c41, 0x22022c41, 0x1b000603, 0x1b000603}, + {0x0000a524, 0x28023042, 0x28023042, 0x1f000a02, 0x1f000a02}, + {0x0000a528, 0x2c023044, 0x2c023044, 0x23000a04, 0x23000a04}, + {0x0000a52c, 0x2f023644, 0x2f023644, 0x26000a20, 0x26000a20}, + {0x0000a530, 0x34025643, 0x34025643, 0x2a000e20, 0x2a000e20}, + {0x0000a534, 0x38025a44, 0x38025a44, 0x2e000e22, 0x2e000e22}, + {0x0000a538, 0x3b025e45, 0x3b025e45, 0x31000e24, 0x31000e24}, + {0x0000a53c, 0x41025e4a, 0x41025e4a, 0x34001640, 0x34001640}, + {0x0000a540, 0x48025e6c, 0x48025e6c, 0x38001660, 0x38001660}, + {0x0000a544, 0x4e025e8e, 0x4e025e8e, 0x3b001861, 0x3b001861}, + {0x0000a548, 0x55025eb3, 0x55025eb3, 0x3e001a81, 0x3e001a81}, + {0x0000a54c, 0x58025ef3, 0x58025ef3, 0x42001a83, 0x42001a83}, + {0x0000a550, 0x5d025ef6, 0x5d025ef6, 0x44001a84, 0x44001a84}, + {0x0000a554, 0x62025f56, 0x62025f56, 0x48001ce3, 0x48001ce3}, + {0x0000a558, 0x66027f56, 0x66027f56, 0x4c001ce5, 0x4c001ce5}, + {0x0000a55c, 0x6a029f56, 0x6a029f56, 0x50001ce9, 0x50001ce9}, + {0x0000a560, 0x70049f56, 0x70049f56, 0x54001ceb, 0x54001ceb}, + {0x0000a564, 0x751ffff6, 0x751ffff6, 0x56001eec, 0x56001eec}, + {0x0000a568, 0x751ffff6, 0x751ffff6, 0x58001ef0, 0x58001ef0}, + {0x0000a56c, 0x751ffff6, 0x751ffff6, 0x5a001ef4, 0x5a001ef4}, + {0x0000a570, 0x751ffff6, 0x751ffff6, 0x5c001ff6, 0x5c001ff6}, + {0x0000a574, 0x751ffff6, 0x751ffff6, 0x5c001ff6, 0x5c001ff6}, + {0x0000a578, 0x751ffff6, 0x751ffff6, 0x5c001ff6, 0x5c001ff6}, + {0x0000a57c, 0x751ffff6, 0x751ffff6, 0x5c001ff6, 0x5c001ff6}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00804000, 0x00804000, 0x00000000, 0x00000000}, + {0x0000a614, 0x00804201, 0x00804201, 0x01404000, 0x01404000}, + {0x0000a618, 0x0280c802, 0x0280c802, 0x01404501, 0x01404501}, + {0x0000a61c, 0x0280ca03, 0x0280ca03, 0x02008501, 0x02008501}, + {0x0000a620, 0x04c15104, 0x04c15104, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x04c15305, 0x04c15305, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x04c15305, 0x04c15305, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a630, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a634, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a638, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000a63c, 0x04c15305, 0x04c15305, 0x04015005, 0x04015005}, + {0x0000b2dc, 0x01feee00, 0x01feee00, 0x03aaa352, 0x03aaa352}, + {0x0000b2e0, 0x0000f000, 0x0000f000, 0x03ccc584, 0x03ccc584}, + {0x0000b2e4, 0x01ff0000, 0x01ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x00016044, 0x056d82e4, 0x056d82e4, 0x056d82e4, 0x056d82e4}, + {0x00016048, 0x8db49060, 0x8db49060, 0x8db49060, 0x8db49060}, + {0x00016054, 0x6db60000, 0x6db60000, 0x6db60000, 0x6db60000}, + {0x00016444, 0x056d82e4, 0x056d82e4, 0x056d82e4, 0x056d82e4}, + {0x00016448, 0x8db49000, 0x8db49000, 0x8db49000, 0x8db49000}, + {0x00016454, 0x6db60000, 0x6db60000, 0x6db60000, 0x6db60000}, +}; + +static const u32 ar9462_2p0_radio_core[][2] = { + /* Addr allmodes */ + {0x00016000, 0x36db6db6}, + {0x00016004, 0x6db6db40}, + {0x00016008, 0x73f00000}, + {0x0001600c, 0x00000000}, + {0x00016010, 0x6d820001}, + {0x00016040, 0x7f80fff8}, + {0x0001604c, 0x2699e04f}, + {0x00016050, 0x6db6db6c}, + {0x00016058, 0x6c200000}, + {0x00016080, 0x000c0000}, + {0x00016084, 0x9a68048c}, + {0x00016088, 0x54214514}, + {0x0001608c, 0x1203040b}, + {0x00016090, 0x24926490}, + {0x00016098, 0xd2888888}, + {0x000160a0, 0x0a108ffe}, + {0x000160a4, 0x812fc491}, + {0x000160a8, 0x423c8000}, + {0x000160b4, 0x92000000}, + {0x000160b8, 0x0285dddc}, + {0x000160bc, 0x02908888}, + {0x000160c0, 0x00adb6d0}, + {0x000160c4, 0x6db6db60}, + {0x000160c8, 0x6db6db6c}, + {0x000160cc, 0x0de6c1b0}, + {0x00016100, 0x3fffbe04}, + {0x00016104, 0xfff80000}, + {0x00016108, 0x00200400}, + {0x00016110, 0x00000000}, + {0x00016144, 0x02084080}, + {0x00016148, 0x000080c0}, + {0x00016280, 0x050a0001}, + {0x00016284, 0x3d841418}, + {0x00016288, 0x00000000}, + {0x0001628c, 0xe3000000}, + {0x00016290, 0xa1005080}, + {0x00016294, 0x00000020}, + {0x00016298, 0x54a82900}, + {0x00016340, 0x121e4276}, + {0x00016344, 0x00300000}, + {0x00016400, 0x36db6db6}, + {0x00016404, 0x6db6db40}, + {0x00016408, 0x73f00000}, + {0x0001640c, 0x00000000}, + {0x00016410, 0x6c800001}, + {0x00016440, 0x7f80fff8}, + {0x0001644c, 0x4699e04f}, + {0x00016450, 0x6db6db6c}, + {0x00016500, 0x3fffbe04}, + {0x00016504, 0xfff80000}, + {0x00016508, 0x00200400}, + {0x00016510, 0x00000000}, + {0x00016544, 0x02084080}, + {0x00016548, 0x000080c0}, +}; + +static const u32 ar9462_2p0_soc_preamble[][2] = { + /* Addr allmodes */ + {0x000040a4, 0x00a0c1c9}, + {0x00007020, 0x00000000}, + {0x00007034, 0x00000002}, + {0x00007038, 0x000004c2}, +}; + +static const u32 ar9462_2p0_mac_core[][2] = { + /* Addr allmodes */ + {0x00000008, 0x00000000}, + {0x00000030, 0x000e0085}, + {0x00000034, 0x00000005}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000000}, + {0x00000048, 0x00000008}, + {0x0000004c, 0x00000010}, + {0x00000050, 0x00000000}, + {0x00001040, 0x002ffc0f}, + {0x00001044, 0x002ffc0f}, + {0x00001048, 0x002ffc0f}, + {0x0000104c, 0x002ffc0f}, + {0x00001050, 0x002ffc0f}, + {0x00001054, 0x002ffc0f}, + {0x00001058, 0x002ffc0f}, + {0x0000105c, 0x002ffc0f}, + {0x00001060, 0x002ffc0f}, + {0x00001064, 0x002ffc0f}, + {0x000010f0, 0x00000100}, + {0x00001270, 0x00000000}, + {0x000012b0, 0x00000000}, + {0x000012f0, 0x00000000}, + {0x0000143c, 0x00000000}, + {0x0000147c, 0x00000000}, + {0x00001810, 0x0f000003}, + {0x00008000, 0x00000000}, + {0x00008004, 0x00000000}, + {0x00008008, 0x00000000}, + {0x0000800c, 0x00000000}, + {0x00008018, 0x00000000}, + {0x00008020, 0x00000000}, + {0x00008038, 0x00000000}, + {0x0000803c, 0x00080000}, + {0x00008040, 0x00000000}, + {0x00008044, 0x00000000}, + {0x00008048, 0x00000000}, + {0x0000804c, 0xffffffff}, + {0x00008054, 0x00000000}, + {0x00008058, 0x00000000}, + {0x0000805c, 0x000fc78f}, + {0x00008060, 0x0000000f}, + {0x00008064, 0x00000000}, + {0x00008070, 0x00000310}, + {0x00008074, 0x00000020}, + {0x00008078, 0x00000000}, + {0x0000809c, 0x0000000f}, + {0x000080a0, 0x00000000}, + {0x000080a4, 0x02ff0000}, + {0x000080a8, 0x0e070605}, + {0x000080ac, 0x0000000d}, + {0x000080b0, 0x00000000}, + {0x000080b4, 0x00000000}, + {0x000080b8, 0x00000000}, + {0x000080bc, 0x00000000}, + {0x000080c0, 0x2a800000}, + {0x000080c4, 0x06900168}, + {0x000080c8, 0x13881c20}, + {0x000080cc, 0x01f40000}, + {0x000080d0, 0x00252500}, + {0x000080d4, 0x00b00005}, + {0x000080d8, 0x00400002}, + {0x000080dc, 0x00000000}, + {0x000080e0, 0xffffffff}, + {0x000080e4, 0x0000ffff}, + {0x000080e8, 0x3f3f3f3f}, + {0x000080ec, 0x00000000}, + {0x000080f0, 0x00000000}, + {0x000080f4, 0x00000000}, + {0x000080fc, 0x00020000}, + {0x00008100, 0x00000000}, + {0x00008108, 0x00000052}, + {0x0000810c, 0x00000000}, + {0x00008110, 0x00000000}, + {0x00008114, 0x000007ff}, + {0x00008118, 0x000000aa}, + {0x0000811c, 0x00003210}, + {0x00008124, 0x00000000}, + {0x00008128, 0x00000000}, + {0x0000812c, 0x00000000}, + {0x00008130, 0x00000000}, + {0x00008134, 0x00000000}, + {0x00008138, 0x00000000}, + {0x0000813c, 0x0000ffff}, + {0x00008144, 0xffffffff}, + {0x00008168, 0x00000000}, + {0x0000816c, 0x00000000}, + {0x00008170, 0x18486e00}, + {0x00008174, 0x33332210}, + {0x00008178, 0x00000000}, + {0x0000817c, 0x00020000}, + {0x000081c4, 0x33332210}, + {0x000081c8, 0x00000000}, + {0x000081cc, 0x00000000}, + {0x000081d4, 0x00000000}, + {0x000081ec, 0x00000000}, + {0x000081f0, 0x00000000}, + {0x000081f4, 0x00000000}, + {0x000081f8, 0x00000000}, + {0x000081fc, 0x00000000}, + {0x00008240, 0x00100000}, + {0x00008244, 0x0010f400}, + {0x00008248, 0x00000800}, + {0x0000824c, 0x0001e800}, + {0x00008250, 0x00000000}, + {0x00008254, 0x00000000}, + {0x00008258, 0x00000000}, + {0x0000825c, 0x40000000}, + {0x00008260, 0x00080922}, + {0x00008264, 0x99c00010}, + {0x00008268, 0xffffffff}, + {0x0000826c, 0x0000ffff}, + {0x00008270, 0x00000000}, + {0x00008274, 0x40000000}, + {0x00008278, 0x003e4180}, + {0x0000827c, 0x00000004}, + {0x00008284, 0x0000002c}, + {0x00008288, 0x0000002c}, + {0x0000828c, 0x000000ff}, + {0x00008294, 0x00000000}, + {0x00008298, 0x00000000}, + {0x0000829c, 0x00000000}, + {0x00008300, 0x00000140}, + {0x00008314, 0x00000000}, + {0x0000831c, 0x0000010d}, + {0x00008328, 0x00000000}, + {0x0000832c, 0x0000001f}, + {0x00008330, 0x00000302}, + {0x00008334, 0x00000700}, + {0x00008338, 0xffff0000}, + {0x0000833c, 0x02400000}, + {0x00008340, 0x000107ff}, + {0x00008344, 0xaa48105b}, + {0x00008348, 0x008f0000}, + {0x0000835c, 0x00000000}, + {0x00008360, 0xffffffff}, + {0x00008364, 0xffffffff}, + {0x00008368, 0x00000000}, + {0x00008370, 0x00000000}, + {0x00008374, 0x000000ff}, + {0x00008378, 0x00000000}, + {0x0000837c, 0x00000000}, + {0x00008380, 0xffffffff}, + {0x00008384, 0xffffffff}, + {0x00008390, 0xffffffff}, + {0x00008394, 0xffffffff}, + {0x00008398, 0x00000000}, + {0x0000839c, 0x00000000}, + {0x000083a4, 0x0000fa14}, + {0x000083a8, 0x000f0c00}, + {0x000083ac, 0x33332210}, + {0x000083b0, 0x33332210}, + {0x000083b4, 0x33332210}, + {0x000083b8, 0x33332210}, + {0x000083bc, 0x00000000}, + {0x000083c0, 0x00000000}, + {0x000083c4, 0x00000000}, + {0x000083c8, 0x00000000}, + {0x000083cc, 0x00000200}, + {0x000083d0, 0x000301ff}, +}; + +static const u32 ar9462_2p0_mac_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00001030, 0x00000230, 0x00000460, 0x000002c0, 0x00000160}, + {0x00001070, 0x00000168, 0x000002d0, 0x00000318, 0x0000018c}, + {0x000010b0, 0x00000e60, 0x00001cc0, 0x00007c70, 0x00003e38}, + {0x00008014, 0x03e803e8, 0x07d007d0, 0x10801600, 0x08400b00}, + {0x0000801c, 0x128d8027, 0x128d804f, 0x12e00057, 0x12e0002b}, + {0x00008120, 0x08f04800, 0x08f04800, 0x08f04810, 0x08f04810}, + {0x000081d0, 0x00003210, 0x00003210, 0x0000320a, 0x0000320a}, + {0x00008318, 0x00003e80, 0x00007d00, 0x00006880, 0x00003440}, +}; + +static const u32 ar9462_common_mixed_rx_gain_table_2p0[][2] = { + /* Addr allmodes */ + {0x0000a000, 0x00010000}, + {0x0000a004, 0x00030002}, + {0x0000a008, 0x00050004}, + {0x0000a00c, 0x00810080}, + {0x0000a010, 0x00830082}, + {0x0000a014, 0x01810180}, + {0x0000a018, 0x01830182}, + {0x0000a01c, 0x01850184}, + {0x0000a020, 0x01890188}, + {0x0000a024, 0x018b018a}, + {0x0000a028, 0x018d018c}, + {0x0000a02c, 0x03820190}, + {0x0000a030, 0x03840383}, + {0x0000a034, 0x03880385}, + {0x0000a038, 0x038a0389}, + {0x0000a03c, 0x038c038b}, + {0x0000a040, 0x0390038d}, + {0x0000a044, 0x03920391}, + {0x0000a048, 0x03940393}, + {0x0000a04c, 0x03960395}, + {0x0000a050, 0x00000000}, + {0x0000a054, 0x00000000}, + {0x0000a058, 0x00000000}, + {0x0000a05c, 0x00000000}, + {0x0000a060, 0x00000000}, + {0x0000a064, 0x00000000}, + {0x0000a068, 0x00000000}, + {0x0000a06c, 0x00000000}, + {0x0000a070, 0x00000000}, + {0x0000a074, 0x00000000}, + {0x0000a078, 0x00000000}, + {0x0000a07c, 0x00000000}, + {0x0000a080, 0x29292929}, + {0x0000a084, 0x29292929}, + {0x0000a088, 0x29292929}, + {0x0000a08c, 0x29292929}, + {0x0000a090, 0x22292929}, + {0x0000a094, 0x1d1d2222}, + {0x0000a098, 0x0c111117}, + {0x0000a09c, 0x00030303}, + {0x0000a0a0, 0x00000000}, + {0x0000a0a4, 0x00000000}, + {0x0000a0a8, 0x00000000}, + {0x0000a0ac, 0x00000000}, + {0x0000a0b0, 0x00000000}, + {0x0000a0b4, 0x00000000}, + {0x0000a0b8, 0x00000000}, + {0x0000a0bc, 0x00000000}, + {0x0000a0c0, 0x001f0000}, + {0x0000a0c4, 0x01000101}, + {0x0000a0c8, 0x011e011f}, + {0x0000a0cc, 0x011c011d}, + {0x0000a0d0, 0x02030204}, + {0x0000a0d4, 0x02010202}, + {0x0000a0d8, 0x021f0200}, + {0x0000a0dc, 0x0302021e}, + {0x0000a0e0, 0x03000301}, + {0x0000a0e4, 0x031e031f}, + {0x0000a0e8, 0x0402031d}, + {0x0000a0ec, 0x04000401}, + {0x0000a0f0, 0x041e041f}, + {0x0000a0f4, 0x0502041d}, + {0x0000a0f8, 0x05000501}, + {0x0000a0fc, 0x051e051f}, + {0x0000a100, 0x06010602}, + {0x0000a104, 0x061f0600}, + {0x0000a108, 0x061d061e}, + {0x0000a10c, 0x07020703}, + {0x0000a110, 0x07000701}, + {0x0000a114, 0x00000000}, + {0x0000a118, 0x00000000}, + {0x0000a11c, 0x00000000}, + {0x0000a120, 0x00000000}, + {0x0000a124, 0x00000000}, + {0x0000a128, 0x00000000}, + {0x0000a12c, 0x00000000}, + {0x0000a130, 0x00000000}, + {0x0000a134, 0x00000000}, + {0x0000a138, 0x00000000}, + {0x0000a13c, 0x00000000}, + {0x0000a140, 0x001f0000}, + {0x0000a144, 0x01000101}, + {0x0000a148, 0x011e011f}, + {0x0000a14c, 0x011c011d}, + {0x0000a150, 0x02030204}, + {0x0000a154, 0x02010202}, + {0x0000a158, 0x021f0200}, + {0x0000a15c, 0x0302021e}, + {0x0000a160, 0x03000301}, + {0x0000a164, 0x031e031f}, + {0x0000a168, 0x0402031d}, + {0x0000a16c, 0x04000401}, + {0x0000a170, 0x041e041f}, + {0x0000a174, 0x0502041d}, + {0x0000a178, 0x05000501}, + {0x0000a17c, 0x051e051f}, + {0x0000a180, 0x06010602}, + {0x0000a184, 0x061f0600}, + {0x0000a188, 0x061d061e}, + {0x0000a18c, 0x07020703}, + {0x0000a190, 0x07000701}, + {0x0000a194, 0x00000000}, + {0x0000a198, 0x00000000}, + {0x0000a19c, 0x00000000}, + {0x0000a1a0, 0x00000000}, + {0x0000a1a4, 0x00000000}, + {0x0000a1a8, 0x00000000}, + {0x0000a1ac, 0x00000000}, + {0x0000a1b0, 0x00000000}, + {0x0000a1b4, 0x00000000}, + {0x0000a1b8, 0x00000000}, + {0x0000a1bc, 0x00000000}, + {0x0000a1c0, 0x00000000}, + {0x0000a1c4, 0x00000000}, + {0x0000a1c8, 0x00000000}, + {0x0000a1cc, 0x00000000}, + {0x0000a1d0, 0x00000000}, + {0x0000a1d4, 0x00000000}, + {0x0000a1d8, 0x00000000}, + {0x0000a1dc, 0x00000000}, + {0x0000a1e0, 0x00000000}, + {0x0000a1e4, 0x00000000}, + {0x0000a1e8, 0x00000000}, + {0x0000a1ec, 0x00000000}, + {0x0000a1f0, 0x00000396}, + {0x0000a1f4, 0x00000396}, + {0x0000a1f8, 0x00000396}, + {0x0000a1fc, 0x00000196}, + {0x0000b000, 0x00010000}, + {0x0000b004, 0x00030002}, + {0x0000b008, 0x00050004}, + {0x0000b00c, 0x00810080}, + {0x0000b010, 0x00830082}, + {0x0000b014, 0x01810180}, + {0x0000b018, 0x01830182}, + {0x0000b01c, 0x01850184}, + {0x0000b020, 0x02810280}, + {0x0000b024, 0x02830282}, + {0x0000b028, 0x02850284}, + {0x0000b02c, 0x02890288}, + {0x0000b030, 0x028b028a}, + {0x0000b034, 0x0388028c}, + {0x0000b038, 0x038a0389}, + {0x0000b03c, 0x038c038b}, + {0x0000b040, 0x0390038d}, + {0x0000b044, 0x03920391}, + {0x0000b048, 0x03940393}, + {0x0000b04c, 0x03960395}, + {0x0000b050, 0x00000000}, + {0x0000b054, 0x00000000}, + {0x0000b058, 0x00000000}, + {0x0000b05c, 0x00000000}, + {0x0000b060, 0x00000000}, + {0x0000b064, 0x00000000}, + {0x0000b068, 0x00000000}, + {0x0000b06c, 0x00000000}, + {0x0000b070, 0x00000000}, + {0x0000b074, 0x00000000}, + {0x0000b078, 0x00000000}, + {0x0000b07c, 0x00000000}, + {0x0000b080, 0x2a2d2f32}, + {0x0000b084, 0x21232328}, + {0x0000b088, 0x19191c1e}, + {0x0000b08c, 0x12141417}, + {0x0000b090, 0x07070e0e}, + {0x0000b094, 0x03030305}, + {0x0000b098, 0x00000003}, + {0x0000b09c, 0x00000000}, + {0x0000b0a0, 0x00000000}, + {0x0000b0a4, 0x00000000}, + {0x0000b0a8, 0x00000000}, + {0x0000b0ac, 0x00000000}, + {0x0000b0b0, 0x00000000}, + {0x0000b0b4, 0x00000000}, + {0x0000b0b8, 0x00000000}, + {0x0000b0bc, 0x00000000}, + {0x0000b0c0, 0x003f0020}, + {0x0000b0c4, 0x00400041}, + {0x0000b0c8, 0x0140005f}, + {0x0000b0cc, 0x0160015f}, + {0x0000b0d0, 0x017e017f}, + {0x0000b0d4, 0x02410242}, + {0x0000b0d8, 0x025f0240}, + {0x0000b0dc, 0x027f0260}, + {0x0000b0e0, 0x0341027e}, + {0x0000b0e4, 0x035f0340}, + {0x0000b0e8, 0x037f0360}, + {0x0000b0ec, 0x04400441}, + {0x0000b0f0, 0x0460045f}, + {0x0000b0f4, 0x0541047f}, + {0x0000b0f8, 0x055f0540}, + {0x0000b0fc, 0x057f0560}, + {0x0000b100, 0x06400641}, + {0x0000b104, 0x0660065f}, + {0x0000b108, 0x067e067f}, + {0x0000b10c, 0x07410742}, + {0x0000b110, 0x075f0740}, + {0x0000b114, 0x077f0760}, + {0x0000b118, 0x07800781}, + {0x0000b11c, 0x07a0079f}, + {0x0000b120, 0x07c107bf}, + {0x0000b124, 0x000007c0}, + {0x0000b128, 0x00000000}, + {0x0000b12c, 0x00000000}, + {0x0000b130, 0x00000000}, + {0x0000b134, 0x00000000}, + {0x0000b138, 0x00000000}, + {0x0000b13c, 0x00000000}, + {0x0000b140, 0x003f0020}, + {0x0000b144, 0x00400041}, + {0x0000b148, 0x0140005f}, + {0x0000b14c, 0x0160015f}, + {0x0000b150, 0x017e017f}, + {0x0000b154, 0x02410242}, + {0x0000b158, 0x025f0240}, + {0x0000b15c, 0x027f0260}, + {0x0000b160, 0x0341027e}, + {0x0000b164, 0x035f0340}, + {0x0000b168, 0x037f0360}, + {0x0000b16c, 0x04400441}, + {0x0000b170, 0x0460045f}, + {0x0000b174, 0x0541047f}, + {0x0000b178, 0x055f0540}, + {0x0000b17c, 0x057f0560}, + {0x0000b180, 0x06400641}, + {0x0000b184, 0x0660065f}, + {0x0000b188, 0x067e067f}, + {0x0000b18c, 0x07410742}, + {0x0000b190, 0x075f0740}, + {0x0000b194, 0x077f0760}, + {0x0000b198, 0x07800781}, + {0x0000b19c, 0x07a0079f}, + {0x0000b1a0, 0x07c107bf}, + {0x0000b1a4, 0x000007c0}, + {0x0000b1a8, 0x00000000}, + {0x0000b1ac, 0x00000000}, + {0x0000b1b0, 0x00000000}, + {0x0000b1b4, 0x00000000}, + {0x0000b1b8, 0x00000000}, + {0x0000b1bc, 0x00000000}, + {0x0000b1c0, 0x00000000}, + {0x0000b1c4, 0x00000000}, + {0x0000b1c8, 0x00000000}, + {0x0000b1cc, 0x00000000}, + {0x0000b1d0, 0x00000000}, + {0x0000b1d4, 0x00000000}, + {0x0000b1d8, 0x00000000}, + {0x0000b1dc, 0x00000000}, + {0x0000b1e0, 0x00000000}, + {0x0000b1e4, 0x00000000}, + {0x0000b1e8, 0x00000000}, + {0x0000b1ec, 0x00000000}, + {0x0000b1f0, 0x00000396}, + {0x0000b1f4, 0x00000396}, + {0x0000b1f8, 0x00000396}, + {0x0000b1fc, 0x00000196}, +}; + +#endif /* INITVALS_9462_2P0_H */ diff --git a/drivers/net/wireless/ath/ath9k/ar9485_initvals.h b/drivers/net/wireless/ath/ath9k/ar9485_initvals.h new file mode 100644 index 0000000..88ff1d7 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9485_initvals.h @@ -0,0 +1,1211 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef INITVALS_9485_H +#define INITVALS_9485_H + +/* AR9485 1.1 */ + +#define ar9485_1_1_mac_postamble ar9300_2p2_mac_postamble + +static const u32 ar9485_1_1_pcie_phy_pll_on_clkreq_disable_L1[][2] = { + /* Addr allmodes */ + {0x00018c00, 0x18012e5e}, + {0x00018c04, 0x000801d8}, + {0x00018c08, 0x0000080c}, +}; + +static const u32 ar9485Common_wo_xlna_rx_gain_1_1[][2] = { + /* Addr allmodes */ + {0x00009e00, 0x037216a0}, + {0x00009e04, 0x00182020}, + {0x00009e18, 0x00000000}, + {0x00009e2c, 0x00004121}, + {0x00009e44, 0x02282324}, + {0x0000a000, 0x00060005}, + {0x0000a004, 0x00810080}, + {0x0000a008, 0x00830082}, + {0x0000a00c, 0x00850084}, + {0x0000a010, 0x01820181}, + {0x0000a014, 0x01840183}, + {0x0000a018, 0x01880185}, + {0x0000a01c, 0x018a0189}, + {0x0000a020, 0x02850284}, + {0x0000a024, 0x02890288}, + {0x0000a028, 0x028b028a}, + {0x0000a02c, 0x03850384}, + {0x0000a030, 0x03890388}, + {0x0000a034, 0x038b038a}, + {0x0000a038, 0x038d038c}, + {0x0000a03c, 0x03910390}, + {0x0000a040, 0x03930392}, + {0x0000a044, 0x03950394}, + {0x0000a048, 0x00000396}, + {0x0000a04c, 0x00000000}, + {0x0000a050, 0x00000000}, + {0x0000a054, 0x00000000}, + {0x0000a058, 0x00000000}, + {0x0000a05c, 0x00000000}, + {0x0000a060, 0x00000000}, + {0x0000a064, 0x00000000}, + {0x0000a068, 0x00000000}, + {0x0000a06c, 0x00000000}, + {0x0000a070, 0x00000000}, + {0x0000a074, 0x00000000}, + {0x0000a078, 0x00000000}, + {0x0000a07c, 0x00000000}, + {0x0000a080, 0x28282828}, + {0x0000a084, 0x28282828}, + {0x0000a088, 0x28282828}, + {0x0000a08c, 0x28282828}, + {0x0000a090, 0x28282828}, + {0x0000a094, 0x24242428}, + {0x0000a098, 0x171e1e1e}, + {0x0000a09c, 0x02020b0b}, + {0x0000a0a0, 0x02020202}, + {0x0000a0a4, 0x00000000}, + {0x0000a0a8, 0x00000000}, + {0x0000a0ac, 0x00000000}, + {0x0000a0b0, 0x00000000}, + {0x0000a0b4, 0x00000000}, + {0x0000a0b8, 0x00000000}, + {0x0000a0bc, 0x00000000}, + {0x0000a0c0, 0x22072208}, + {0x0000a0c4, 0x22052206}, + {0x0000a0c8, 0x22032204}, + {0x0000a0cc, 0x22012202}, + {0x0000a0d0, 0x221f2200}, + {0x0000a0d4, 0x221d221e}, + {0x0000a0d8, 0x33023303}, + {0x0000a0dc, 0x33003301}, + {0x0000a0e0, 0x331e331f}, + {0x0000a0e4, 0x4402331d}, + {0x0000a0e8, 0x44004401}, + {0x0000a0ec, 0x441e441f}, + {0x0000a0f0, 0x55025503}, + {0x0000a0f4, 0x55005501}, + {0x0000a0f8, 0x551e551f}, + {0x0000a0fc, 0x6602551d}, + {0x0000a100, 0x66006601}, + {0x0000a104, 0x661e661f}, + {0x0000a108, 0x7703661d}, + {0x0000a10c, 0x77017702}, + {0x0000a110, 0x00007700}, + {0x0000a114, 0x00000000}, + {0x0000a118, 0x00000000}, + {0x0000a11c, 0x00000000}, + {0x0000a120, 0x00000000}, + {0x0000a124, 0x00000000}, + {0x0000a128, 0x00000000}, + {0x0000a12c, 0x00000000}, + {0x0000a130, 0x00000000}, + {0x0000a134, 0x00000000}, + {0x0000a138, 0x00000000}, + {0x0000a13c, 0x00000000}, + {0x0000a140, 0x001f0000}, + {0x0000a144, 0x111f1100}, + {0x0000a148, 0x111d111e}, + {0x0000a14c, 0x111b111c}, + {0x0000a150, 0x22032204}, + {0x0000a154, 0x22012202}, + {0x0000a158, 0x221f2200}, + {0x0000a15c, 0x221d221e}, + {0x0000a160, 0x33013302}, + {0x0000a164, 0x331f3300}, + {0x0000a168, 0x4402331e}, + {0x0000a16c, 0x44004401}, + {0x0000a170, 0x441e441f}, + {0x0000a174, 0x55015502}, + {0x0000a178, 0x551f5500}, + {0x0000a17c, 0x6602551e}, + {0x0000a180, 0x66006601}, + {0x0000a184, 0x661e661f}, + {0x0000a188, 0x7703661d}, + {0x0000a18c, 0x77017702}, + {0x0000a190, 0x00007700}, + {0x0000a194, 0x00000000}, + {0x0000a198, 0x00000000}, + {0x0000a19c, 0x00000000}, + {0x0000a1a0, 0x00000000}, + {0x0000a1a4, 0x00000000}, + {0x0000a1a8, 0x00000000}, + {0x0000a1ac, 0x00000000}, + {0x0000a1b0, 0x00000000}, + {0x0000a1b4, 0x00000000}, + {0x0000a1b8, 0x00000000}, + {0x0000a1bc, 0x00000000}, + {0x0000a1c0, 0x00000000}, + {0x0000a1c4, 0x00000000}, + {0x0000a1c8, 0x00000000}, + {0x0000a1cc, 0x00000000}, + {0x0000a1d0, 0x00000000}, + {0x0000a1d4, 0x00000000}, + {0x0000a1d8, 0x00000000}, + {0x0000a1dc, 0x00000000}, + {0x0000a1e0, 0x00000000}, + {0x0000a1e4, 0x00000000}, + {0x0000a1e8, 0x00000000}, + {0x0000a1ec, 0x00000000}, + {0x0000a1f0, 0x00000396}, + {0x0000a1f4, 0x00000396}, + {0x0000a1f8, 0x00000396}, + {0x0000a1fc, 0x00000296}, +}; + +static const u32 ar9485Modes_high_power_tx_gain_1_1[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x000098bc, 0x00000002, 0x00000002, 0x00000002, 0x00000002}, + {0x0000a2d8, 0xf999a83a, 0xf999a83a, 0x7999a83a, 0x7999a83a}, + {0x0000a2dc, 0x00000000, 0x00000000, 0xfe2d3552, 0xfe2d3552}, + {0x0000a2e0, 0x00000000, 0x00000000, 0xfe2d3552, 0xfe2d3552}, + {0x0000a2e4, 0x00000000, 0x00000000, 0xfe2d3552, 0xfe2d3552}, + {0x0000a2e8, 0x00000000, 0x00000000, 0xfe2d3552, 0xfe2d3552}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d8, 0x000050d8}, + {0x0000a458, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a500, 0x00022200, 0x00022200, 0x00000000, 0x00000000}, + {0x0000a504, 0x05062002, 0x05062002, 0x04000002, 0x04000002}, + {0x0000a508, 0x0c002e00, 0x0c002e00, 0x08000004, 0x08000004}, + {0x0000a50c, 0x11062202, 0x11062202, 0x0d000200, 0x0d000200}, + {0x0000a510, 0x17022e00, 0x17022e00, 0x11000202, 0x11000202}, + {0x0000a514, 0x1d000ec2, 0x1d000ec2, 0x15000400, 0x15000400}, + {0x0000a518, 0x25020ec0, 0x25020ec0, 0x19000402, 0x19000402}, + {0x0000a51c, 0x2b020ec3, 0x2b020ec3, 0x1d000404, 0x1d000404}, + {0x0000a520, 0x2f001f04, 0x2f001f04, 0x21000603, 0x21000603}, + {0x0000a524, 0x35001fc4, 0x35001fc4, 0x25000605, 0x25000605}, + {0x0000a528, 0x3c022f04, 0x3c022f04, 0x2a000a03, 0x2a000a03}, + {0x0000a52c, 0x41023e85, 0x41023e85, 0x2c000a04, 0x2c000a04}, + {0x0000a530, 0x48023ec6, 0x48023ec6, 0x34000e20, 0x34000e20}, + {0x0000a534, 0x4d023f01, 0x4d023f01, 0x35000e21, 0x35000e21}, + {0x0000a538, 0x53023f4b, 0x53023f4b, 0x43000e62, 0x43000e62}, + {0x0000a53c, 0x5a027f09, 0x5a027f09, 0x45000e63, 0x45000e63}, + {0x0000a540, 0x5f027fc9, 0x5f027fc9, 0x49000e65, 0x49000e65}, + {0x0000a544, 0x6502feca, 0x6502feca, 0x4b000e66, 0x4b000e66}, + {0x0000a548, 0x6b02ff4a, 0x6b02ff4a, 0x4d001645, 0x4d001645}, + {0x0000a54c, 0x7203feca, 0x7203feca, 0x51001865, 0x51001865}, + {0x0000a550, 0x7703ff0b, 0x7703ff0b, 0x55001a86, 0x55001a86}, + {0x0000a554, 0x7d06ffcb, 0x7d06ffcb, 0x57001ce9, 0x57001ce9}, + {0x0000a558, 0x8407ff0b, 0x8407ff0b, 0x5a001ceb, 0x5a001ceb}, + {0x0000a55c, 0x8907ffcb, 0x8907ffcb, 0x5e001eeb, 0x5e001eeb}, + {0x0000a560, 0x900fff0b, 0x900fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a564, 0x960fffcb, 0x960fffcb, 0x5e001eeb, 0x5e001eeb}, + {0x0000a568, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a56c, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a570, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a574, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a578, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a57c, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a580, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a584, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a588, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a58c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a590, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a594, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a598, 0x00000000, 0x00000000, 0x01404501, 0x01404501}, + {0x0000a59c, 0x00000000, 0x00000000, 0x02808a02, 0x02808a02}, + {0x0000a5a0, 0x00000000, 0x00000000, 0x02808a02, 0x02808a02}, + {0x0000a5a4, 0x00000000, 0x00000000, 0x02808803, 0x02808803}, + {0x0000a5a8, 0x00000000, 0x00000000, 0x04c14b04, 0x04c14b04}, + {0x0000a5ac, 0x00000000, 0x00000000, 0x04c15305, 0x04c15305}, + {0x0000a5b0, 0x00000000, 0x00000000, 0x04c15305, 0x04c15305}, + {0x0000a5b4, 0x00000000, 0x00000000, 0x04c15305, 0x04c15305}, + {0x0000a5b8, 0x00000000, 0x00000000, 0x04c15305, 0x04c15305}, + {0x0000a5bc, 0x00000000, 0x00000000, 0x04c15305, 0x04c15305}, + {0x0000b500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b504, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b508, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b50c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b510, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b514, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b518, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b51c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b520, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b524, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b528, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b52c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b530, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b534, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b538, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b53c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b540, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b544, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b548, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b54c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b550, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b554, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b558, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b55c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b560, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b564, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b568, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b56c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b570, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b574, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b578, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b57c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00016044, 0x05d6b2db, 0x05d6b2db, 0x05d6b2db, 0x05d6b2db}, + {0x00016048, 0x6c924260, 0x6c924260, 0x6c924260, 0x6c924260}, +}; + +static const u32 ar9485Modes_green_ob_db_tx_gain_1_1[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x000098bc, 0x00000003, 0x00000003, 0x00000003, 0x00000003}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d8, 0x000050d8}, + {0x0000a458, 0x80000000, 0x80000000, 0x80000000, 0x80000000}, + {0x0000a500, 0x00022200, 0x00022200, 0x00000006, 0x00000006}, + {0x0000a504, 0x05062002, 0x05062002, 0x03000201, 0x03000201}, + {0x0000a508, 0x0c002e00, 0x0c002e00, 0x06000203, 0x06000203}, + {0x0000a50c, 0x11062202, 0x11062202, 0x0a000401, 0x0a000401}, + {0x0000a510, 0x17022e00, 0x17022e00, 0x0e000403, 0x0e000403}, + {0x0000a514, 0x1d000ec2, 0x1d000ec2, 0x12000405, 0x12000405}, + {0x0000a518, 0x25020ec0, 0x25020ec0, 0x15000604, 0x15000604}, + {0x0000a51c, 0x2b020ec3, 0x2b020ec3, 0x18000605, 0x18000605}, + {0x0000a520, 0x2f001f04, 0x2f001f04, 0x1c000a04, 0x1c000a04}, + {0x0000a524, 0x35001fc4, 0x35001fc4, 0x21000a06, 0x21000a06}, + {0x0000a528, 0x3c022f04, 0x3c022f04, 0x29000a24, 0x29000a24}, + {0x0000a52c, 0x41023e85, 0x41023e85, 0x2f000e21, 0x2f000e21}, + {0x0000a530, 0x48023ec6, 0x48023ec6, 0x31000e20, 0x31000e20}, + {0x0000a534, 0x4d023f01, 0x4d023f01, 0x33000e20, 0x33000e20}, + {0x0000a538, 0x53023f4b, 0x53023f4b, 0x43000e62, 0x43000e62}, + {0x0000a53c, 0x5a027f09, 0x5a027f09, 0x45000e63, 0x45000e63}, + {0x0000a540, 0x5f027fc9, 0x5f027fc9, 0x49000e65, 0x49000e65}, + {0x0000a544, 0x6502feca, 0x6502feca, 0x4b000e66, 0x4b000e66}, + {0x0000a548, 0x6b02ff4a, 0x6b02ff4a, 0x4d001645, 0x4d001645}, + {0x0000a54c, 0x7203feca, 0x7203feca, 0x51001865, 0x51001865}, + {0x0000a550, 0x7703ff0b, 0x7703ff0b, 0x55001a86, 0x55001a86}, + {0x0000a554, 0x7d06ffcb, 0x7d06ffcb, 0x57001ce9, 0x57001ce9}, + {0x0000a558, 0x8407ff0b, 0x8407ff0b, 0x5a001ceb, 0x5a001ceb}, + {0x0000a55c, 0x8907ffcb, 0x8907ffcb, 0x5e001eeb, 0x5e001eeb}, + {0x0000a560, 0x900fff0b, 0x900fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a564, 0x960fffcb, 0x960fffcb, 0x5e001eeb, 0x5e001eeb}, + {0x0000a568, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a56c, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a570, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a574, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a578, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a57c, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000b500, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b504, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b508, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b50c, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b510, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b514, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b518, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b51c, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b520, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b524, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b528, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b52c, 0x0000002a, 0x0000002a, 0x0000002a, 0x0000002a}, + {0x0000b530, 0x0000003a, 0x0000003a, 0x0000003a, 0x0000003a}, + {0x0000b534, 0x0000004a, 0x0000004a, 0x0000004a, 0x0000004a}, + {0x0000b538, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b53c, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b540, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b544, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b548, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b54c, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b550, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b554, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b558, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b55c, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b560, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b564, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b568, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b56c, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b570, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b574, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b578, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b57c, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x00016044, 0x05d6b2db, 0x05d6b2db, 0x05d6b2db, 0x05d6b2db}, + {0x00016048, 0x6c924260, 0x6c924260, 0x6c924260, 0x6c924260}, +}; + +static const u32 ar9485Modes_high_ob_db_tx_gain_1_1[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x000098bc, 0x00000002, 0x00000002, 0x00000002, 0x00000002}, + {0x0000a2d8, 0xf999a83a, 0xf999a83a, 0x7999a83a, 0x7999a83a}, + {0x0000a2dc, 0x00000000, 0x00000000, 0xfe2d3552, 0xfe2d3552}, + {0x0000a2e0, 0x00000000, 0x00000000, 0xffc63a84, 0xffc63a84}, + {0x0000a2e4, 0x00000000, 0x00000000, 0xfe0fc000, 0xfe0fc000}, + {0x0000a2e8, 0x00000000, 0x00000000, 0xfff00000, 0xfff00000}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d8, 0x000050d8}, + {0x0000a458, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a500, 0x00022200, 0x00022200, 0x00000000, 0x00000000}, + {0x0000a504, 0x05062002, 0x05062002, 0x04000002, 0x04000002}, + {0x0000a508, 0x0c002e00, 0x0c002e00, 0x08000004, 0x08000004}, + {0x0000a50c, 0x11062202, 0x11062202, 0x0d000200, 0x0d000200}, + {0x0000a510, 0x17022e00, 0x17022e00, 0x11000202, 0x11000202}, + {0x0000a514, 0x1d000ec2, 0x1d000ec2, 0x15000400, 0x15000400}, + {0x0000a518, 0x25020ec0, 0x25020ec0, 0x19000402, 0x19000402}, + {0x0000a51c, 0x2b020ec3, 0x2b020ec3, 0x1d000404, 0x1d000404}, + {0x0000a520, 0x2f001f04, 0x2f001f04, 0x21000603, 0x21000603}, + {0x0000a524, 0x35001fc4, 0x35001fc4, 0x25000605, 0x25000605}, + {0x0000a528, 0x3c022f04, 0x3c022f04, 0x2a000a03, 0x2a000a03}, + {0x0000a52c, 0x41023e85, 0x41023e85, 0x2c000a04, 0x2c000a04}, + {0x0000a530, 0x48023ec6, 0x48023ec6, 0x34000e20, 0x34000e20}, + {0x0000a534, 0x4d023f01, 0x4d023f01, 0x35000e21, 0x35000e21}, + {0x0000a538, 0x53023f4b, 0x53023f4b, 0x43000e62, 0x43000e62}, + {0x0000a53c, 0x5a027f09, 0x5a027f09, 0x45000e63, 0x45000e63}, + {0x0000a540, 0x5f027fc9, 0x5f027fc9, 0x49000e65, 0x49000e65}, + {0x0000a544, 0x6502feca, 0x6502feca, 0x4b000e66, 0x4b000e66}, + {0x0000a548, 0x6b02ff4a, 0x6b02ff4a, 0x4d001645, 0x4d001645}, + {0x0000a54c, 0x7203feca, 0x7203feca, 0x51001865, 0x51001865}, + {0x0000a550, 0x7703ff0b, 0x7703ff0b, 0x55001a86, 0x55001a86}, + {0x0000a554, 0x7d06ffcb, 0x7d06ffcb, 0x57001ce9, 0x57001ce9}, + {0x0000a558, 0x8407ff0b, 0x8407ff0b, 0x5a001ceb, 0x5a001ceb}, + {0x0000a55c, 0x8907ffcb, 0x8907ffcb, 0x5e001eeb, 0x5e001eeb}, + {0x0000a560, 0x900fff0b, 0x900fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a564, 0x960fffcb, 0x960fffcb, 0x5e001eeb, 0x5e001eeb}, + {0x0000a568, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a56c, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a570, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a574, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a578, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a57c, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a580, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a584, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a588, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a58c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a590, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a594, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a598, 0x00000000, 0x00000000, 0x01404501, 0x01404501}, + {0x0000a59c, 0x00000000, 0x00000000, 0x02808a02, 0x02808a02}, + {0x0000a5a0, 0x00000000, 0x00000000, 0x02808a02, 0x02808a02}, + {0x0000a5a4, 0x00000000, 0x00000000, 0x02808803, 0x02808803}, + {0x0000a5a8, 0x00000000, 0x00000000, 0x04c14b04, 0x04c14b04}, + {0x0000a5ac, 0x00000000, 0x00000000, 0x04c15305, 0x04c15305}, + {0x0000a5b0, 0x00000000, 0x00000000, 0x04c15305, 0x04c15305}, + {0x0000a5b4, 0x00000000, 0x00000000, 0x04c15305, 0x04c15305}, + {0x0000a5b8, 0x00000000, 0x00000000, 0x04c15305, 0x04c15305}, + {0x0000a5bc, 0x00000000, 0x00000000, 0x04c15305, 0x04c15305}, + {0x0000b500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b504, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b508, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b50c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b510, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b514, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b518, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b51c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b520, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b524, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b528, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b52c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b530, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b534, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b538, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b53c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b540, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b544, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b548, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b54c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b550, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b554, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b558, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b55c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b560, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b564, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b568, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b56c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b570, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b574, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b578, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b57c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00016044, 0x05d6b2db, 0x05d6b2db, 0x05d6b2db, 0x05d6b2db}, + {0x00016048, 0x6c924260, 0x6c924260, 0x6c924260, 0x6c924260}, +}; + +static const u32 ar9485Modes_low_ob_db_tx_gain_1_1[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x000098bc, 0x00000002, 0x00000002, 0x00000002, 0x00000002}, + {0x0000a2d8, 0xf999a83a, 0xf999a83a, 0x7999a83a, 0x7999a83a}, + {0x0000a2dc, 0x00000000, 0x00000000, 0xfe2d3552, 0xfe2d3552}, + {0x0000a2e0, 0x00000000, 0x00000000, 0xfe2d3552, 0xfe2d3552}, + {0x0000a2e4, 0x00000000, 0x00000000, 0xfe2d3552, 0xfe2d3552}, + {0x0000a2e8, 0x00000000, 0x00000000, 0xfe2d3552, 0xfe2d3552}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d8, 0x000050d8}, + {0x0000a458, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a500, 0x00022200, 0x00022200, 0x00000000, 0x00000000}, + {0x0000a504, 0x05062002, 0x05062002, 0x04000002, 0x04000002}, + {0x0000a508, 0x0c002e00, 0x0c002e00, 0x08000004, 0x08000004}, + {0x0000a50c, 0x11062202, 0x11062202, 0x0d000200, 0x0d000200}, + {0x0000a510, 0x17022e00, 0x17022e00, 0x11000202, 0x11000202}, + {0x0000a514, 0x1d000ec2, 0x1d000ec2, 0x15000400, 0x15000400}, + {0x0000a518, 0x25020ec0, 0x25020ec0, 0x19000402, 0x19000402}, + {0x0000a51c, 0x2b020ec3, 0x2b020ec3, 0x1d000404, 0x1d000404}, + {0x0000a520, 0x2f001f04, 0x2f001f04, 0x21000603, 0x21000603}, + {0x0000a524, 0x35001fc4, 0x35001fc4, 0x25000605, 0x25000605}, + {0x0000a528, 0x3c022f04, 0x3c022f04, 0x2a000a03, 0x2a000a03}, + {0x0000a52c, 0x41023e85, 0x41023e85, 0x2c000a04, 0x2c000a04}, + {0x0000a530, 0x48023ec6, 0x48023ec6, 0x34000e20, 0x34000e20}, + {0x0000a534, 0x4d023f01, 0x4d023f01, 0x35000e21, 0x35000e21}, + {0x0000a538, 0x53023f4b, 0x53023f4b, 0x43000e62, 0x43000e62}, + {0x0000a53c, 0x5a027f09, 0x5a027f09, 0x45000e63, 0x45000e63}, + {0x0000a540, 0x5f027fc9, 0x5f027fc9, 0x49000e65, 0x49000e65}, + {0x0000a544, 0x6502feca, 0x6502feca, 0x4b000e66, 0x4b000e66}, + {0x0000a548, 0x6b02ff4a, 0x6b02ff4a, 0x4d001645, 0x4d001645}, + {0x0000a54c, 0x7203feca, 0x7203feca, 0x51001865, 0x51001865}, + {0x0000a550, 0x7703ff0b, 0x7703ff0b, 0x55001a86, 0x55001a86}, + {0x0000a554, 0x7d06ffcb, 0x7d06ffcb, 0x57001ce9, 0x57001ce9}, + {0x0000a558, 0x8407ff0b, 0x8407ff0b, 0x5a001ceb, 0x5a001ceb}, + {0x0000a55c, 0x8907ffcb, 0x8907ffcb, 0x5e001eeb, 0x5e001eeb}, + {0x0000a560, 0x900fff0b, 0x900fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a564, 0x960fffcb, 0x960fffcb, 0x5e001eeb, 0x5e001eeb}, + {0x0000a568, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a56c, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a570, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a574, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a578, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a57c, 0x9c1fff0b, 0x9c1fff0b, 0x5e001eeb, 0x5e001eeb}, + {0x0000a580, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a584, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a588, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a58c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a590, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a594, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a598, 0x00000000, 0x00000000, 0x01404501, 0x01404501}, + {0x0000a59c, 0x00000000, 0x00000000, 0x02808a02, 0x02808a02}, + {0x0000a5a0, 0x00000000, 0x00000000, 0x02808a02, 0x02808a02}, + {0x0000a5a4, 0x00000000, 0x00000000, 0x02808803, 0x02808803}, + {0x0000a5a8, 0x00000000, 0x00000000, 0x04c14b04, 0x04c14b04}, + {0x0000a5ac, 0x00000000, 0x00000000, 0x04c15305, 0x04c15305}, + {0x0000a5b0, 0x00000000, 0x00000000, 0x04c15305, 0x04c15305}, + {0x0000a5b4, 0x00000000, 0x00000000, 0x04c15305, 0x04c15305}, + {0x0000a5b8, 0x00000000, 0x00000000, 0x04c15305, 0x04c15305}, + {0x0000a5bc, 0x00000000, 0x00000000, 0x04c15305, 0x04c15305}, + {0x0000b500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b504, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b508, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b50c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b510, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b514, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b518, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b51c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b520, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b524, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b528, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b52c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b530, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b534, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b538, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b53c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b540, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b544, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b548, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b54c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b550, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b554, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b558, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b55c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b560, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b564, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b568, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b56c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b570, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b574, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b578, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000b57c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00016044, 0x05d6b2db, 0x05d6b2db, 0x05d6b2db, 0x05d6b2db}, + {0x00016048, 0x6c924260, 0x6c924260, 0x6c924260, 0x6c924260}, +}; + +#define ar9485_modes_lowest_ob_db_tx_gain_1_1 ar9485Modes_low_ob_db_tx_gain_1_1 + +static const u32 ar9485Modes_green_spur_ob_db_tx_gain_1_1[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x000098bc, 0x00000003, 0x00000003, 0x00000003, 0x00000003}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d8, 0x000050d8}, + {0x0000a458, 0x80000000, 0x80000000, 0x80000000, 0x80000000}, + {0x0000a500, 0x00022200, 0x00022200, 0x00000006, 0x00000006}, + {0x0000a504, 0x05062002, 0x05062002, 0x03000201, 0x03000201}, + {0x0000a508, 0x0c002e00, 0x0c002e00, 0x07000203, 0x07000203}, + {0x0000a50c, 0x11062202, 0x11062202, 0x0a000401, 0x0a000401}, + {0x0000a510, 0x17022e00, 0x17022e00, 0x0e000403, 0x0e000403}, + {0x0000a514, 0x1d000ec2, 0x1d000ec2, 0x12000405, 0x12000405}, + {0x0000a518, 0x25020ec0, 0x25020ec0, 0x14000406, 0x14000406}, + {0x0000a51c, 0x2b020ec3, 0x2b020ec3, 0x1800040a, 0x1800040a}, + {0x0000a520, 0x2f001f04, 0x2f001f04, 0x1c000460, 0x1c000460}, + {0x0000a524, 0x35001fc4, 0x35001fc4, 0x22000463, 0x22000463}, + {0x0000a528, 0x3c022f04, 0x3c022f04, 0x26000465, 0x26000465}, + {0x0000a52c, 0x41023e85, 0x41023e85, 0x2e0006e0, 0x2e0006e0}, + {0x0000a530, 0x48023ec6, 0x48023ec6, 0x310006e0, 0x310006e0}, + {0x0000a534, 0x4d023f01, 0x4d023f01, 0x330006e0, 0x330006e0}, + {0x0000a538, 0x53023f4b, 0x53023f4b, 0x3e0008e3, 0x3e0008e3}, + {0x0000a53c, 0x5a027f09, 0x5a027f09, 0x410008e5, 0x410008e5}, + {0x0000a540, 0x5f027fc9, 0x5f027fc9, 0x430008e6, 0x430008e6}, + {0x0000a544, 0x6502feca, 0x6502feca, 0x4a0008ec, 0x4a0008ec}, + {0x0000a548, 0x6b02ff4a, 0x6b02ff4a, 0x4e0008f1, 0x4e0008f1}, + {0x0000a54c, 0x7203feca, 0x7203feca, 0x520008f3, 0x520008f3}, + {0x0000a550, 0x7703ff0b, 0x7703ff0b, 0x54000eed, 0x54000eed}, + {0x0000a554, 0x7d06ffcb, 0x7d06ffcb, 0x58000ef1, 0x58000ef1}, + {0x0000a558, 0x8407ff0b, 0x8407ff0b, 0x5c000ef3, 0x5c000ef3}, + {0x0000a55c, 0x8907ffcb, 0x8907ffcb, 0x60000ef5, 0x60000ef5}, + {0x0000a560, 0x900fff0b, 0x900fff0b, 0x62000ef6, 0x62000ef6}, + {0x0000a564, 0x960fffcb, 0x960fffcb, 0x62000ef6, 0x62000ef6}, + {0x0000a568, 0x9c1fff0b, 0x9c1fff0b, 0x62000ef6, 0x62000ef6}, + {0x0000a56c, 0x9c1fff0b, 0x9c1fff0b, 0x62000ef6, 0x62000ef6}, + {0x0000a570, 0x9c1fff0b, 0x9c1fff0b, 0x62000ef6, 0x62000ef6}, + {0x0000a574, 0x9c1fff0b, 0x9c1fff0b, 0x62000ef6, 0x62000ef6}, + {0x0000a578, 0x9c1fff0b, 0x9c1fff0b, 0x62000ef6, 0x62000ef6}, + {0x0000a57c, 0x9c1fff0b, 0x9c1fff0b, 0x62000ef6, 0x62000ef6}, + {0x0000b500, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b504, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b508, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b50c, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b510, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b514, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b518, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b51c, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b520, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b524, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b528, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a}, + {0x0000b52c, 0x0000002a, 0x0000002a, 0x0000002a, 0x0000002a}, + {0x0000b530, 0x0000003a, 0x0000003a, 0x0000003a, 0x0000003a}, + {0x0000b534, 0x0000004a, 0x0000004a, 0x0000004a, 0x0000004a}, + {0x0000b538, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b53c, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b540, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b544, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b548, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b54c, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b550, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b554, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b558, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b55c, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b560, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b564, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b568, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b56c, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b570, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b574, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b578, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x0000b57c, 0x0000005b, 0x0000005b, 0x0000005b, 0x0000005b}, + {0x00016044, 0x05d6b2db, 0x05d6b2db, 0x05d6b2db, 0x05d6b2db}, + {0x00016048, 0x6c924260, 0x6c924260, 0x6c924260, 0x6c924260}, +}; + +static const u32 ar9485_1_1[][2] = { + /* Addr allmodes */ + {0x0000a580, 0x00000000}, + {0x0000a584, 0x00000000}, + {0x0000a588, 0x00000000}, + {0x0000a58c, 0x01804000}, + {0x0000a590, 0x02808a02}, + {0x0000a594, 0x0340ca02}, + {0x0000a598, 0x0340cd03}, + {0x0000a59c, 0x0340cd03}, + {0x0000a5a0, 0x06415304}, + {0x0000a5a4, 0x04c11905}, + {0x0000a5a8, 0x06415905}, + {0x0000a5ac, 0x06415905}, + {0x0000a5b0, 0x06415905}, + {0x0000a5b4, 0x06415905}, + {0x0000a5b8, 0x06415905}, + {0x0000a5bc, 0x06415905}, +}; + +static const u32 ar9485_1_1_radio_core[][2] = { + /* Addr allmodes */ + {0x00016000, 0x36db6db6}, + {0x00016004, 0x6db6db40}, + {0x00016008, 0x73800000}, + {0x0001600c, 0x00000000}, + {0x00016040, 0x7f80fff8}, + {0x0001604c, 0x000f0278}, + {0x00016050, 0x4db6db8c}, + {0x00016054, 0x6db60000}, + {0x00016080, 0x00080000}, + {0x00016084, 0x0e48048c}, + {0x00016088, 0x14214514}, + {0x0001608c, 0x119f081e}, + {0x00016090, 0x24926490}, + {0x00016098, 0xd28b3330}, + {0x000160a0, 0xc2108ffe}, + {0x000160a4, 0x812fc370}, + {0x000160a8, 0x423c8000}, + {0x000160b4, 0x92480040}, + {0x000160c0, 0x006db6db}, + {0x000160c4, 0x0186db60}, + {0x000160c8, 0x6db6db6c}, + {0x000160cc, 0x6de6fbe0}, + {0x000160d0, 0xf7dfcf3c}, + {0x00016100, 0x04cb0001}, + {0x00016104, 0xfff80015}, + {0x00016108, 0x00080010}, + {0x00016144, 0x01884080}, + {0x00016148, 0x00008040}, + {0x00016240, 0x08400000}, + {0x00016244, 0x1bf90f00}, + {0x00016248, 0x00000000}, + {0x0001624c, 0x00000000}, + {0x00016280, 0x01000015}, + {0x00016284, 0x00d30000}, + {0x00016288, 0x00318000}, + {0x0001628c, 0x50000000}, + {0x00016290, 0x4b96210f}, + {0x00016380, 0x00000000}, + {0x00016384, 0x00000000}, + {0x00016388, 0x00800700}, + {0x0001638c, 0x00800700}, + {0x00016390, 0x00800700}, + {0x00016394, 0x00000000}, + {0x00016398, 0x00000000}, + {0x0001639c, 0x00000000}, + {0x000163a0, 0x00000001}, + {0x000163a4, 0x00000001}, + {0x000163a8, 0x00000000}, + {0x000163ac, 0x00000000}, + {0x000163b0, 0x00000000}, + {0x000163b4, 0x00000000}, + {0x000163b8, 0x00000000}, + {0x000163bc, 0x00000000}, + {0x000163c0, 0x000000a0}, + {0x000163c4, 0x000c0000}, + {0x000163c8, 0x14021402}, + {0x000163cc, 0x00001402}, + {0x000163d0, 0x00000000}, + {0x000163d4, 0x00000000}, + {0x00016c40, 0x13188278}, + {0x00016c44, 0x12000000}, +}; + +static const u32 ar9485_1_1_baseband_core[][2] = { + /* Addr allmodes */ + {0x00009800, 0xafe68e30}, + {0x00009804, 0xfd14e000}, + {0x00009808, 0x9c0a8f6b}, + {0x0000980c, 0x04800000}, + {0x00009814, 0x9280c00a}, + {0x00009818, 0x00000000}, + {0x0000981c, 0x00020028}, + {0x00009834, 0x5f3ca3de}, + {0x00009838, 0x0108ecff}, + {0x0000983c, 0x14750600}, + {0x00009880, 0x201fff00}, + {0x00009884, 0x00001042}, + {0x000098a4, 0x00200400}, + {0x000098b0, 0x32840bbe}, + {0x000098d0, 0x004b6a8e}, + {0x000098d4, 0x00000820}, + {0x000098dc, 0x00000000}, + {0x000098f0, 0x00000000}, + {0x000098f4, 0x00000000}, + {0x00009c04, 0x00000000}, + {0x00009c08, 0x03200000}, + {0x00009c0c, 0x00000000}, + {0x00009c10, 0x00000000}, + {0x00009c14, 0x00046384}, + {0x00009c18, 0x05b6b440}, + {0x00009c1c, 0x00b6b440}, + {0x00009d00, 0xc080a333}, + {0x00009d04, 0x40206c10}, + {0x00009d08, 0x009c4060}, + {0x00009d0c, 0x1883800a}, + {0x00009d10, 0x01834061}, + {0x00009d14, 0x00c00400}, + {0x00009d18, 0x00000000}, + {0x00009d1c, 0x00000000}, + {0x00009e08, 0x0038233c}, + {0x00009e24, 0x992bb515}, + {0x00009e28, 0x12ef0200}, + {0x00009e30, 0x06336f77}, + {0x00009e34, 0x6af6532f}, + {0x00009e38, 0x0cc80c00}, + {0x00009e40, 0x0d261820}, + {0x00009e4c, 0x00001004}, + {0x00009e50, 0x00ff03f1}, + {0x00009fc0, 0x80be4788}, + {0x00009fc4, 0x0001efb5}, + {0x00009fcc, 0x40000014}, + {0x0000a20c, 0x00000000}, + {0x0000a210, 0x00000000}, + {0x0000a220, 0x00000000}, + {0x0000a224, 0x00000000}, + {0x0000a228, 0x10002310}, + {0x0000a23c, 0x00000000}, + {0x0000a244, 0x0c000000}, + {0x0000a2a0, 0x00000001}, + {0x0000a2c0, 0x00000001}, + {0x0000a2c8, 0x00000000}, + {0x0000a2cc, 0x18c43433}, + {0x0000a2d4, 0x00000000}, + {0x0000a2dc, 0x00000000}, + {0x0000a2e0, 0x00000000}, + {0x0000a2e4, 0x00000000}, + {0x0000a2e8, 0x00000000}, + {0x0000a2ec, 0x00000000}, + {0x0000a2f0, 0x00000000}, + {0x0000a2f4, 0x00000000}, + {0x0000a2f8, 0x00000000}, + {0x0000a344, 0x00000000}, + {0x0000a34c, 0x00000000}, + {0x0000a350, 0x0000a000}, + {0x0000a364, 0x00000000}, + {0x0000a370, 0x00000000}, + {0x0000a390, 0x00000001}, + {0x0000a394, 0x00000444}, + {0x0000a398, 0x001f0e0f}, + {0x0000a39c, 0x0075393f}, + {0x0000a3a0, 0xb79f6427}, + {0x0000a3a4, 0x000000ff}, + {0x0000a3a8, 0x3b3b3b3b}, + {0x0000a3ac, 0x2f2f2f2f}, + {0x0000a3c0, 0x20202020}, + {0x0000a3c4, 0x22222220}, + {0x0000a3c8, 0x20200020}, + {0x0000a3cc, 0x20202020}, + {0x0000a3d0, 0x20202020}, + {0x0000a3d4, 0x20202020}, + {0x0000a3d8, 0x20202020}, + {0x0000a3dc, 0x20202020}, + {0x0000a3e0, 0x20202020}, + {0x0000a3e4, 0x20202020}, + {0x0000a3e8, 0x20202020}, + {0x0000a3ec, 0x20202020}, + {0x0000a3f0, 0x00000000}, + {0x0000a3f4, 0x00000006}, + {0x0000a3f8, 0x0cdbd380}, + {0x0000a3fc, 0x000f0f01}, + {0x0000a400, 0x8fa91f01}, + {0x0000a404, 0x00000000}, + {0x0000a408, 0x0e79e5c6}, + {0x0000a40c, 0x00820820}, + {0x0000a414, 0x1ce739cf}, + {0x0000a418, 0x2d0021ce}, + {0x0000a41c, 0x1ce739ce}, + {0x0000a420, 0x000001ce}, + {0x0000a424, 0x1ce739ce}, + {0x0000a428, 0x000001ce}, + {0x0000a42c, 0x1ce739ce}, + {0x0000a430, 0x1ce739ce}, + {0x0000a434, 0x00000000}, + {0x0000a438, 0x00001801}, + {0x0000a43c, 0x00000000}, + {0x0000a440, 0x00000000}, + {0x0000a444, 0x00000000}, + {0x0000a448, 0x04000000}, + {0x0000a44c, 0x00000001}, + {0x0000a450, 0x00010000}, + {0x0000a5c4, 0xbfad9d74}, + {0x0000a5c8, 0x00480605}, + {0x0000a5cc, 0x00002e37}, + {0x0000a760, 0x03020100}, + {0x0000a764, 0x09080504}, + {0x0000a768, 0x0d0c0b0a}, + {0x0000a76c, 0x13121110}, + {0x0000a770, 0x31301514}, + {0x0000a774, 0x35343332}, + {0x0000a778, 0x00000036}, + {0x0000a780, 0x00000838}, + {0x0000a7c0, 0x00000000}, + {0x0000a7c4, 0xfffffffc}, + {0x0000a7c8, 0x00000000}, + {0x0000a7cc, 0x00000000}, + {0x0000a7d0, 0x00000000}, + {0x0000a7d4, 0x00000004}, + {0x0000a7dc, 0x00000000}, +}; + +static const u32 ar9485_common_rx_gain_1_1[][2] = { + /* Addr allmodes */ + {0x00009e00, 0x03721b20}, + {0x00009e04, 0x00082020}, + {0x00009e18, 0x0300501e}, + {0x00009e2c, 0x00002e21}, + {0x00009e44, 0x02182324}, + {0x0000a000, 0x00060005}, + {0x0000a004, 0x00810080}, + {0x0000a008, 0x00830082}, + {0x0000a00c, 0x00850084}, + {0x0000a010, 0x01820181}, + {0x0000a014, 0x01840183}, + {0x0000a018, 0x01880185}, + {0x0000a01c, 0x018a0189}, + {0x0000a020, 0x02850284}, + {0x0000a024, 0x02890288}, + {0x0000a028, 0x028b028a}, + {0x0000a02c, 0x03850384}, + {0x0000a030, 0x03890388}, + {0x0000a034, 0x038b038a}, + {0x0000a038, 0x038d038c}, + {0x0000a03c, 0x03910390}, + {0x0000a040, 0x03930392}, + {0x0000a044, 0x03950394}, + {0x0000a048, 0x00000396}, + {0x0000a04c, 0x00000000}, + {0x0000a050, 0x00000000}, + {0x0000a054, 0x00000000}, + {0x0000a058, 0x00000000}, + {0x0000a05c, 0x00000000}, + {0x0000a060, 0x00000000}, + {0x0000a064, 0x00000000}, + {0x0000a068, 0x00000000}, + {0x0000a06c, 0x00000000}, + {0x0000a070, 0x00000000}, + {0x0000a074, 0x00000000}, + {0x0000a078, 0x00000000}, + {0x0000a07c, 0x00000000}, + {0x0000a080, 0x18181818}, + {0x0000a084, 0x18181818}, + {0x0000a088, 0x18181818}, + {0x0000a08c, 0x18181818}, + {0x0000a090, 0x18181818}, + {0x0000a094, 0x18181818}, + {0x0000a098, 0x17181818}, + {0x0000a09c, 0x02020b0b}, + {0x0000a0a0, 0x02020202}, + {0x0000a0a4, 0x00000000}, + {0x0000a0a8, 0x00000000}, + {0x0000a0ac, 0x00000000}, + {0x0000a0b0, 0x00000000}, + {0x0000a0b4, 0x00000000}, + {0x0000a0b8, 0x00000000}, + {0x0000a0bc, 0x00000000}, + {0x0000a0c0, 0x22072208}, + {0x0000a0c4, 0x22052206}, + {0x0000a0c8, 0x22032204}, + {0x0000a0cc, 0x22012202}, + {0x0000a0d0, 0x221f2200}, + {0x0000a0d4, 0x221d221e}, + {0x0000a0d8, 0x33023303}, + {0x0000a0dc, 0x33003301}, + {0x0000a0e0, 0x331e331f}, + {0x0000a0e4, 0x4402331d}, + {0x0000a0e8, 0x44004401}, + {0x0000a0ec, 0x441e441f}, + {0x0000a0f0, 0x55025503}, + {0x0000a0f4, 0x55005501}, + {0x0000a0f8, 0x551e551f}, + {0x0000a0fc, 0x6602551d}, + {0x0000a100, 0x66006601}, + {0x0000a104, 0x661e661f}, + {0x0000a108, 0x7703661d}, + {0x0000a10c, 0x77017702}, + {0x0000a110, 0x00007700}, + {0x0000a114, 0x00000000}, + {0x0000a118, 0x00000000}, + {0x0000a11c, 0x00000000}, + {0x0000a120, 0x00000000}, + {0x0000a124, 0x00000000}, + {0x0000a128, 0x00000000}, + {0x0000a12c, 0x00000000}, + {0x0000a130, 0x00000000}, + {0x0000a134, 0x00000000}, + {0x0000a138, 0x00000000}, + {0x0000a13c, 0x00000000}, + {0x0000a140, 0x001f0000}, + {0x0000a144, 0x111f1100}, + {0x0000a148, 0x111d111e}, + {0x0000a14c, 0x111b111c}, + {0x0000a150, 0x22032204}, + {0x0000a154, 0x22012202}, + {0x0000a158, 0x221f2200}, + {0x0000a15c, 0x221d221e}, + {0x0000a160, 0x33013302}, + {0x0000a164, 0x331f3300}, + {0x0000a168, 0x4402331e}, + {0x0000a16c, 0x44004401}, + {0x0000a170, 0x441e441f}, + {0x0000a174, 0x55015502}, + {0x0000a178, 0x551f5500}, + {0x0000a17c, 0x6602551e}, + {0x0000a180, 0x66006601}, + {0x0000a184, 0x661e661f}, + {0x0000a188, 0x7703661d}, + {0x0000a18c, 0x77017702}, + {0x0000a190, 0x00007700}, + {0x0000a194, 0x00000000}, + {0x0000a198, 0x00000000}, + {0x0000a19c, 0x00000000}, + {0x0000a1a0, 0x00000000}, + {0x0000a1a4, 0x00000000}, + {0x0000a1a8, 0x00000000}, + {0x0000a1ac, 0x00000000}, + {0x0000a1b0, 0x00000000}, + {0x0000a1b4, 0x00000000}, + {0x0000a1b8, 0x00000000}, + {0x0000a1bc, 0x00000000}, + {0x0000a1c0, 0x00000000}, + {0x0000a1c4, 0x00000000}, + {0x0000a1c8, 0x00000000}, + {0x0000a1cc, 0x00000000}, + {0x0000a1d0, 0x00000000}, + {0x0000a1d4, 0x00000000}, + {0x0000a1d8, 0x00000000}, + {0x0000a1dc, 0x00000000}, + {0x0000a1e0, 0x00000000}, + {0x0000a1e4, 0x00000000}, + {0x0000a1e8, 0x00000000}, + {0x0000a1ec, 0x00000000}, + {0x0000a1f0, 0x00000396}, + {0x0000a1f4, 0x00000396}, + {0x0000a1f8, 0x00000396}, + {0x0000a1fc, 0x00000296}, +}; + +static const u32 ar9485_1_1_pcie_phy_pll_on_clkreq_enable_L1[][2] = { + /* Addr allmodes */ + {0x00018c00, 0x18052e5e}, + {0x00018c04, 0x000801d8}, + {0x00018c08, 0x0000080c}, +}; + +static const u32 ar9485_1_1_pcie_phy_clkreq_enable_L1[][2] = { + /* Addr allmodes */ + {0x00018c00, 0x18053e5e}, + {0x00018c04, 0x000801d8}, + {0x00018c08, 0x0000080c}, +}; + +static const u32 ar9485_1_1_soc_preamble[][2] = { + /* Addr allmodes */ + {0x00004014, 0xba280400}, + {0x00004090, 0x00aa10aa}, + {0x000040a4, 0x00a0c9c9}, + {0x00007010, 0x00000022}, + {0x00007020, 0x00000000}, + {0x00007034, 0x00000002}, + {0x00007038, 0x000004c2}, + {0x00007048, 0x00000002}, +}; + +static const u32 ar9485_fast_clock_1_1_baseband_postamble[][3] = { + /* Addr 5G_HT20 5G_HT40 */ + {0x00009e00, 0x03721821, 0x03721821}, + {0x0000a230, 0x0000400b, 0x00004016}, + {0x0000a254, 0x00000898, 0x00001130}, +}; + +static const u32 ar9485_1_1_baseband_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009810, 0xd00a8005, 0xd00a8005, 0xd00a8005, 0xd00a8005}, + {0x00009820, 0x206a002e, 0x206a002e, 0x206a002e, 0x206a002e}, + {0x00009824, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0}, + {0x00009828, 0x06903081, 0x06903081, 0x06903881, 0x06903881}, + {0x0000982c, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4}, + {0x00009830, 0x0000059c, 0x0000059c, 0x0000059c, 0x0000059c}, + {0x00009c00, 0x00000044, 0x00000044, 0x00000044, 0x00000044}, + {0x00009e0c, 0x6c4000e2, 0x6d4000e2, 0x6d4000e2, 0x6c4000e2}, + {0x00009e10, 0x7ec88d2e, 0x7ec88d2e, 0x7ec80d2e, 0x7ec80d2e}, + {0x00009e14, 0x31395d53, 0x31396053, 0x312e6053, 0x312e5d53}, + {0x00009e1c, 0x0001cf9c, 0x0001cf9c, 0x00021f9c, 0x00021f9c}, + {0x00009e20, 0x000003b5, 0x000003b5, 0x000003ce, 0x000003ce}, + {0x00009e3c, 0xcf946220, 0xcf946220, 0xcf946222, 0xcf946222}, + {0x00009e48, 0x5030201a, 0x5030201a, 0x50302010, 0x50302010}, + {0x00009fc8, 0x0003f000, 0x0003f000, 0x0001a000, 0x0001a000}, + {0x0000a204, 0x01303fc0, 0x01303fc4, 0x01303fc4, 0x01303fc0}, + {0x0000a208, 0x00000104, 0x00000104, 0x00000004, 0x00000004}, + {0x0000a230, 0x0000400a, 0x00004014, 0x00004016, 0x0000400b}, + {0x0000a234, 0x10000fff, 0x10000fff, 0x10000fff, 0x10000fff}, + {0x0000a238, 0xffb81018, 0xffb81018, 0xffb81018, 0xffb81018}, + {0x0000a250, 0x00000000, 0x00000000, 0x00000210, 0x00000108}, + {0x0000a254, 0x000007d0, 0x00000fa0, 0x00001130, 0x00000898}, + {0x0000a258, 0x02020002, 0x02020002, 0x02020002, 0x02020002}, + {0x0000a25c, 0x01000e0e, 0x01000e0e, 0x01000e0e, 0x01000e0e}, + {0x0000a260, 0x3a021501, 0x3a021501, 0x3a021501, 0x3a021501}, + {0x0000a264, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x0000a280, 0x00000007, 0x00000007, 0x0000000b, 0x0000000b}, + {0x0000a284, 0x00000000, 0x00000000, 0x000002a0, 0x000002a0}, + {0x0000a288, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a28c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a2c4, 0x00158d18, 0x00158d18, 0x00058d18, 0x00058d18}, + {0x0000a2d0, 0x00071981, 0x00071981, 0x00071982, 0x00071982}, + {0x0000a2d8, 0xf999a83a, 0xf999a83a, 0xf999a83a, 0xf999a83a}, + {0x0000a358, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000be04, 0x00802020, 0x00802020, 0x00802020, 0x00802020}, + {0x0000be18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, +}; + +static const u32 ar9485_1_1_pcie_phy_clkreq_disable_L1[][2] = { + /* Addr allmodes */ + {0x00018c00, 0x18013e5e}, + {0x00018c04, 0x000801d8}, + {0x00018c08, 0x0000080c}, +}; + +static const u32 ar9485_1_1_radio_postamble[][2] = { + /* Addr allmodes */ + {0x0001609c, 0x0b283f31}, + {0x000160ac, 0x24611800}, + {0x000160b0, 0x03284f3e}, + {0x0001610c, 0x00170000}, + {0x00016140, 0x50804008}, +}; + +static const u32 ar9485_1_1_mac_core[][2] = { + /* Addr allmodes */ + {0x00000008, 0x00000000}, + {0x00000030, 0x00020085}, + {0x00000034, 0x00000005}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000000}, + {0x00000048, 0x00000008}, + {0x0000004c, 0x00000010}, + {0x00000050, 0x00000000}, + {0x00001040, 0x002ffc0f}, + {0x00001044, 0x002ffc0f}, + {0x00001048, 0x002ffc0f}, + {0x0000104c, 0x002ffc0f}, + {0x00001050, 0x002ffc0f}, + {0x00001054, 0x002ffc0f}, + {0x00001058, 0x002ffc0f}, + {0x0000105c, 0x002ffc0f}, + {0x00001060, 0x002ffc0f}, + {0x00001064, 0x002ffc0f}, + {0x000010f0, 0x00000100}, + {0x00001270, 0x00000000}, + {0x000012b0, 0x00000000}, + {0x000012f0, 0x00000000}, + {0x0000143c, 0x00000000}, + {0x0000147c, 0x00000000}, + {0x00008000, 0x00000000}, + {0x00008004, 0x00000000}, + {0x00008008, 0x00000000}, + {0x0000800c, 0x00000000}, + {0x00008018, 0x00000000}, + {0x00008020, 0x00000000}, + {0x00008038, 0x00000000}, + {0x0000803c, 0x00000000}, + {0x00008040, 0x00000000}, + {0x00008044, 0x00000000}, + {0x00008048, 0x00000000}, + {0x0000804c, 0xffffffff}, + {0x00008054, 0x00000000}, + {0x00008058, 0x00000000}, + {0x0000805c, 0x000fc78f}, + {0x00008060, 0x0000000f}, + {0x00008064, 0x00000000}, + {0x00008070, 0x00000310}, + {0x00008074, 0x00000020}, + {0x00008078, 0x00000000}, + {0x0000809c, 0x0000000f}, + {0x000080a0, 0x00000000}, + {0x000080a4, 0x02ff0000}, + {0x000080a8, 0x0e070605}, + {0x000080ac, 0x0000000d}, + {0x000080b0, 0x00000000}, + {0x000080b4, 0x00000000}, + {0x000080b8, 0x00000000}, + {0x000080bc, 0x00000000}, + {0x000080c0, 0x2a800000}, + {0x000080c4, 0x06900168}, + {0x000080c8, 0x13881c22}, + {0x000080cc, 0x01f40000}, + {0x000080d0, 0x00252500}, + {0x000080d4, 0x00a00000}, + {0x000080d8, 0x00400000}, + {0x000080dc, 0x00000000}, + {0x000080e0, 0xffffffff}, + {0x000080e4, 0x0000ffff}, + {0x000080e8, 0x3f3f3f3f}, + {0x000080ec, 0x00000000}, + {0x000080f0, 0x00000000}, + {0x000080f4, 0x00000000}, + {0x000080fc, 0x00020000}, + {0x00008100, 0x00000000}, + {0x00008108, 0x00000052}, + {0x0000810c, 0x00000000}, + {0x00008110, 0x00000000}, + {0x00008114, 0x000007ff}, + {0x00008118, 0x000000aa}, + {0x0000811c, 0x00003210}, + {0x00008124, 0x00000000}, + {0x00008128, 0x00000000}, + {0x0000812c, 0x00000000}, + {0x00008130, 0x00000000}, + {0x00008134, 0x00000000}, + {0x00008138, 0x00000000}, + {0x0000813c, 0x0000ffff}, + {0x00008144, 0xffffffff}, + {0x00008168, 0x00000000}, + {0x0000816c, 0x00000000}, + {0x00008170, 0x18486200}, + {0x00008174, 0x33332210}, + {0x00008178, 0x00000000}, + {0x0000817c, 0x00020000}, + {0x000081c0, 0x00000000}, + {0x000081c4, 0x33332210}, + {0x000081d4, 0x00000000}, + {0x000081ec, 0x00000000}, + {0x000081f0, 0x00000000}, + {0x000081f4, 0x00000000}, + {0x000081f8, 0x00000000}, + {0x000081fc, 0x00000000}, + {0x00008240, 0x00100000}, + {0x00008244, 0x0010f400}, + {0x00008248, 0x00000800}, + {0x0000824c, 0x0001e800}, + {0x00008250, 0x00000000}, + {0x00008254, 0x00000000}, + {0x00008258, 0x00000000}, + {0x0000825c, 0x40000000}, + {0x00008260, 0x00080922}, + {0x00008264, 0x9ca00010}, + {0x00008268, 0xffffffff}, + {0x0000826c, 0x0000ffff}, + {0x00008270, 0x00000000}, + {0x00008274, 0x40000000}, + {0x00008278, 0x003e4180}, + {0x0000827c, 0x00000004}, + {0x00008284, 0x0000002c}, + {0x00008288, 0x0000002c}, + {0x0000828c, 0x000000ff}, + {0x00008294, 0x00000000}, + {0x00008298, 0x00000000}, + {0x0000829c, 0x00000000}, + {0x00008300, 0x00000140}, + {0x00008314, 0x00000000}, + {0x0000831c, 0x0000010d}, + {0x00008328, 0x00000000}, + {0x0000832c, 0x00000007}, + {0x00008330, 0x00000302}, + {0x00008334, 0x00000700}, + {0x00008338, 0x00ff0000}, + {0x0000833c, 0x02400000}, + {0x00008340, 0x000107ff}, + {0x00008344, 0xa248105b}, + {0x00008348, 0x008f0000}, + {0x0000835c, 0x00000000}, + {0x00008360, 0xffffffff}, + {0x00008364, 0xffffffff}, + {0x00008368, 0x00000000}, + {0x00008370, 0x00000000}, + {0x00008374, 0x000000ff}, + {0x00008378, 0x00000000}, + {0x0000837c, 0x00000000}, + {0x00008380, 0xffffffff}, + {0x00008384, 0xffffffff}, + {0x00008390, 0xffffffff}, + {0x00008394, 0xffffffff}, + {0x00008398, 0x00000000}, + {0x0000839c, 0x00000000}, + {0x000083a0, 0x00000000}, + {0x000083a4, 0x0000fa14}, + {0x000083a8, 0x000f0c00}, + {0x000083ac, 0x33332210}, + {0x000083b0, 0x33332210}, + {0x000083b4, 0x33332210}, + {0x000083b8, 0x33332210}, + {0x000083bc, 0x00000000}, + {0x000083c0, 0x00000000}, + {0x000083c4, 0x00000000}, + {0x000083c8, 0x00000000}, + {0x000083cc, 0x00000200}, + {0x000083d0, 0x000301ff}, +}; + +#define ar9485_1_1_baseband_core_txfir_coeff_japan_2484 ar9462_2p0_baseband_core_txfir_coeff_japan_2484 + +#endif /* INITVALS_9485_H */ diff --git a/drivers/net/wireless/ath/ath9k/ar955x_1p0_initvals.h b/drivers/net/wireless/ath/ath9k/ar955x_1p0_initvals.h new file mode 100644 index 0000000..ccc5b6c --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar955x_1p0_initvals.h @@ -0,0 +1,1284 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef INITVALS_955X_1P0_H +#define INITVALS_955X_1P0_H + +/* AR955X 1.0 */ + +static const u32 ar955x_1p0_radio_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00016098, 0xd2dd5554, 0xd2dd5554, 0xd28b3330, 0xd28b3330}, + {0x0001609c, 0x0a566f3a, 0x0a566f3a, 0x0a566f3a, 0x0a566f3a}, + {0x000160ac, 0xa4647c00, 0xa4647c00, 0x24647c00, 0x24647c00}, + {0x000160b0, 0x01885f52, 0x01885f52, 0x01885f52, 0x01885f52}, + {0x00016104, 0xb7a00000, 0xb7a00000, 0xb7a00001, 0xb7a00001}, + {0x0001610c, 0xc0000000, 0xc0000000, 0xc0000000, 0xc0000000}, + {0x00016140, 0x10804008, 0x10804008, 0x10804008, 0x10804008}, + {0x00016504, 0xb7a00000, 0xb7a00000, 0xb7a00001, 0xb7a00001}, + {0x0001650c, 0xc0000000, 0xc0000000, 0xc0000000, 0xc0000000}, + {0x00016540, 0x10804008, 0x10804008, 0x10804008, 0x10804008}, + {0x00016904, 0xb7a00000, 0xb7a00000, 0xb7a00001, 0xb7a00001}, + {0x0001690c, 0xc0000000, 0xc0000000, 0xc0000000, 0xc0000000}, + {0x00016940, 0x10804008, 0x10804008, 0x10804008, 0x10804008}, +}; + +static const u32 ar955x_1p0_baseband_core_txfir_coeff_japan_2484[][2] = { + /* Addr allmodes */ + {0x0000a398, 0x00000000}, + {0x0000a39c, 0x6f7f0301}, + {0x0000a3a0, 0xca9228ee}, +}; + +static const u32 ar955x_1p0_baseband_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009810, 0xd00a8005, 0xd00a8005, 0xd00a8011, 0xd00a8011}, + {0x00009820, 0x206a022e, 0x206a022e, 0x206a012e, 0x206a012e}, + {0x00009824, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0}, + {0x00009828, 0x06903081, 0x06903081, 0x06903881, 0x06903881}, + {0x0000982c, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4}, + {0x00009830, 0x0000059c, 0x0000059c, 0x0000119c, 0x0000119c}, + {0x00009c00, 0x000000c4, 0x000000c4, 0x000000c4, 0x000000c4}, + {0x00009e00, 0x0372111a, 0x0372111a, 0x037216a0, 0x037216a0}, + {0x00009e04, 0x001c2020, 0x001c2020, 0x001c2020, 0x001c2020}, + {0x00009e0c, 0x6c4000e2, 0x6d4000e2, 0x6d4000e2, 0x6c4000e2}, + {0x00009e10, 0x7ec88d2e, 0x7ec88d2e, 0x7ec84d2e, 0x7ec84d2e}, + {0x00009e14, 0x37b95d5e, 0x37b9605e, 0x3379605e, 0x33795d5e}, + {0x00009e18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00009e1c, 0x0001cf9c, 0x0001cf9c, 0x00021f9c, 0x00021f9c}, + {0x00009e20, 0x000003b5, 0x000003b5, 0x000003ce, 0x000003ce}, + {0x00009e2c, 0x0000001c, 0x0000001c, 0x00000021, 0x00000021}, + {0x00009e3c, 0xcfa10820, 0xcfa10820, 0xcfa10822, 0xcfa10822}, + {0x00009e44, 0xfe321e27, 0xfe321e27, 0xfe291e27, 0xfe291e27}, + {0x00009e48, 0x5030201a, 0x5030201a, 0x50302012, 0x50302012}, + {0x00009fc8, 0x0003f000, 0x0003f000, 0x0001a000, 0x0001a000}, + {0x0000a204, 0x005c0ec0, 0x005c0ec4, 0x005c0ec4, 0x005c0ec0}, + {0x0000a208, 0x00000104, 0x00000104, 0x00000004, 0x00000004}, + {0x0000a22c, 0x07e26a2f, 0x07e26a2f, 0x01026a2f, 0x01026a2f}, + {0x0000a230, 0x0000400a, 0x00004014, 0x00004016, 0x0000400b}, + {0x0000a234, 0x00000fff, 0x10000fff, 0x10000fff, 0x00000fff}, + {0x0000a238, 0xffb01018, 0xffb01018, 0xffb01018, 0xffb01018}, + {0x0000a250, 0x00000000, 0x00000000, 0x00000210, 0x00000108}, + {0x0000a254, 0x000007d0, 0x00000fa0, 0x00001130, 0x00000898}, + {0x0000a258, 0x02020002, 0x02020002, 0x02020002, 0x02020002}, + {0x0000a25c, 0x01000e0e, 0x01000e0e, 0x01010e0e, 0x01010e0e}, + {0x0000a260, 0x0a021501, 0x0a021501, 0x3a021501, 0x3a021501}, + {0x0000a264, 0x00000e0e, 0x00000e0e, 0x01000e0e, 0x01000e0e}, + {0x0000a280, 0x00000007, 0x00000007, 0x0000000b, 0x0000000b}, + {0x0000a284, 0x00000000, 0x00000000, 0x00000010, 0x00000010}, + {0x0000a288, 0x00000110, 0x00000110, 0x00000110, 0x00000110}, + {0x0000a28c, 0x00022222, 0x00022222, 0x00022222, 0x00022222}, + {0x0000a2c4, 0x00158d18, 0x00158d18, 0x00158d18, 0x00158d18}, + {0x0000a2cc, 0x18c50033, 0x18c43433, 0x18c41033, 0x18c44c33}, + {0x0000a2d0, 0x00041982, 0x00041982, 0x00041982, 0x00041982}, + {0x0000a2d8, 0x7999a83b, 0x7999a83b, 0x7999a83b, 0x7999a83b}, + {0x0000a358, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a830, 0x0000019c, 0x0000019c, 0x0000019c, 0x0000019c}, + {0x0000ae04, 0x001c0000, 0x001c0000, 0x001c0000, 0x001c0000}, + {0x0000ae18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000ae1c, 0x0000019c, 0x0000019c, 0x0000019c, 0x0000019c}, + {0x0000ae20, 0x000001b5, 0x000001b5, 0x000001ce, 0x000001ce}, + {0x0000b284, 0x00000000, 0x00000000, 0x00000010, 0x00000010}, + {0x0000b830, 0x0000019c, 0x0000019c, 0x0000019c, 0x0000019c}, + {0x0000be04, 0x001c0000, 0x001c0000, 0x001c0000, 0x001c0000}, + {0x0000be18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000be1c, 0x0000019c, 0x0000019c, 0x0000019c, 0x0000019c}, + {0x0000be20, 0x000001b5, 0x000001b5, 0x000001ce, 0x000001ce}, + {0x0000c284, 0x00000000, 0x00000000, 0x00000010, 0x00000010}, +}; + +static const u32 ar955x_1p0_radio_core[][2] = { + /* Addr allmodes */ + {0x00016000, 0x36db6db6}, + {0x00016004, 0x6db6db40}, + {0x00016008, 0x73f00000}, + {0x0001600c, 0x00000000}, + {0x00016040, 0x7f80fff8}, + {0x0001604c, 0x76d005b5}, + {0x00016050, 0x557cf031}, + {0x00016054, 0x13449440}, + {0x00016058, 0x0c51c92c}, + {0x0001605c, 0x3db7fffc}, + {0x00016060, 0xfffffffc}, + {0x00016064, 0x000f0278}, + {0x00016068, 0x6db6db6c}, + {0x0001606c, 0x6db60000}, + {0x00016080, 0x00080000}, + {0x00016084, 0x0e48048c}, + {0x00016088, 0x14214514}, + {0x0001608c, 0x119f101e}, + {0x00016090, 0x24926490}, + {0x00016094, 0x00000000}, + {0x000160a0, 0x0a108ffe}, + {0x000160a4, 0x812fc370}, + {0x000160a8, 0x423c8100}, + {0x000160b4, 0x92480080}, + {0x000160c0, 0x006db6d0}, + {0x000160c4, 0x6db6db60}, + {0x000160c8, 0x6db6db6c}, + {0x000160cc, 0x01e6c000}, + {0x00016100, 0x11999601}, + {0x00016108, 0x00080010}, + {0x00016144, 0x02084080}, + {0x00016148, 0x00008040}, + {0x00016280, 0x01800804}, + {0x00016284, 0x00038dc5}, + {0x00016288, 0x00000000}, + {0x0001628c, 0x00000040}, + {0x00016380, 0x00000000}, + {0x00016384, 0x00000000}, + {0x00016388, 0x00400705}, + {0x0001638c, 0x00800700}, + {0x00016390, 0x00800700}, + {0x00016394, 0x00000000}, + {0x00016398, 0x00000000}, + {0x0001639c, 0x00000000}, + {0x000163a0, 0x00000001}, + {0x000163a4, 0x00000001}, + {0x000163a8, 0x00000000}, + {0x000163ac, 0x00000000}, + {0x000163b0, 0x00000000}, + {0x000163b4, 0x00000000}, + {0x000163b8, 0x00000000}, + {0x000163bc, 0x00000000}, + {0x000163c0, 0x000000a0}, + {0x000163c4, 0x000c0000}, + {0x000163c8, 0x14021402}, + {0x000163cc, 0x00001402}, + {0x000163d0, 0x00000000}, + {0x000163d4, 0x00000000}, + {0x00016400, 0x36db6db6}, + {0x00016404, 0x6db6db40}, + {0x00016408, 0x73f00000}, + {0x0001640c, 0x00000000}, + {0x00016440, 0x7f80fff8}, + {0x0001644c, 0x76d005b5}, + {0x00016450, 0x557cf031}, + {0x00016454, 0x13449440}, + {0x00016458, 0x0c51c92c}, + {0x0001645c, 0x3db7fffc}, + {0x00016460, 0xfffffffc}, + {0x00016464, 0x000f0278}, + {0x00016468, 0x6db6db6c}, + {0x0001646c, 0x6db60000}, + {0x00016500, 0x11999601}, + {0x00016508, 0x00080010}, + {0x00016544, 0x02084080}, + {0x00016548, 0x00008040}, + {0x00016780, 0x00000000}, + {0x00016784, 0x00000000}, + {0x00016788, 0x00400705}, + {0x0001678c, 0x00800700}, + {0x00016790, 0x00800700}, + {0x00016794, 0x00000000}, + {0x00016798, 0x00000000}, + {0x0001679c, 0x00000000}, + {0x000167a0, 0x00000001}, + {0x000167a4, 0x00000001}, + {0x000167a8, 0x00000000}, + {0x000167ac, 0x00000000}, + {0x000167b0, 0x00000000}, + {0x000167b4, 0x00000000}, + {0x000167b8, 0x00000000}, + {0x000167bc, 0x00000000}, + {0x000167c0, 0x000000a0}, + {0x000167c4, 0x000c0000}, + {0x000167c8, 0x14021402}, + {0x000167cc, 0x00001402}, + {0x000167d0, 0x00000000}, + {0x000167d4, 0x00000000}, + {0x00016800, 0x36db6db6}, + {0x00016804, 0x6db6db40}, + {0x00016808, 0x73f00000}, + {0x0001680c, 0x00000000}, + {0x00016840, 0x7f80fff8}, + {0x0001684c, 0x76d005b5}, + {0x00016850, 0x557cf031}, + {0x00016854, 0x13449440}, + {0x00016858, 0x0c51c92c}, + {0x0001685c, 0x3db7fffc}, + {0x00016860, 0xfffffffc}, + {0x00016864, 0x000f0278}, + {0x00016868, 0x6db6db6c}, + {0x0001686c, 0x6db60000}, + {0x00016900, 0x11999601}, + {0x00016908, 0x00080010}, + {0x00016944, 0x02084080}, + {0x00016948, 0x00008040}, + {0x00016b80, 0x00000000}, + {0x00016b84, 0x00000000}, + {0x00016b88, 0x00400705}, + {0x00016b8c, 0x00800700}, + {0x00016b90, 0x00800700}, + {0x00016b94, 0x00000000}, + {0x00016b98, 0x00000000}, + {0x00016b9c, 0x00000000}, + {0x00016ba0, 0x00000001}, + {0x00016ba4, 0x00000001}, + {0x00016ba8, 0x00000000}, + {0x00016bac, 0x00000000}, + {0x00016bb0, 0x00000000}, + {0x00016bb4, 0x00000000}, + {0x00016bb8, 0x00000000}, + {0x00016bbc, 0x00000000}, + {0x00016bc0, 0x000000a0}, + {0x00016bc4, 0x000c0000}, + {0x00016bc8, 0x14021402}, + {0x00016bcc, 0x00001402}, + {0x00016bd0, 0x00000000}, + {0x00016bd4, 0x00000000}, +}; + +static const u32 ar955x_1p0_modes_xpa_tx_gain_table[][9] = { + /* Addr 5G_HT20_L 5G_HT40_L 5G_HT20_M 5G_HT40_M 5G_HT20_H 5G_HT40_H 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0xffff6aaa, 0xffff6aaa, 0xffff6aaa, 0xffff6aaa, 0xffff6aaa, 0xffff6aaa, 0xfffd5aaa, 0xfffd5aaa}, + {0x0000a2e0, 0xfffdcccc, 0xfffdcccc, 0xfffdcccc, 0xfffdcccc, 0xfffdcccc, 0xfffdcccc, 0xfffe9ccc, 0xfffe9ccc}, + {0x0000a2e4, 0xffe3b0f0, 0xffe3b0f0, 0xffe3b0f0, 0xffe3b0f0, 0xffe3b0f0, 0xffe3b0f0, 0xffffe0f0, 0xffffe0f0}, + {0x0000a2e8, 0xffffff00, 0xffffff00, 0xffffff00, 0xffffff00, 0xffffff00, 0xffffff00, 0xfffcff00, 0xfffcff00}, + {0x0000a410, 0x000050de, 0x000050de, 0x000050de, 0x000050de, 0x000050de, 0x000050de, 0x000050da, 0x000050da}, + {0x0000a500, 0x00000003, 0x00000003, 0x00000003, 0x00000003, 0x00000003, 0x00000003, 0x00000000, 0x00000000}, + {0x0000a504, 0x04000005, 0x04000005, 0x04000005, 0x04000005, 0x04000005, 0x04000005, 0x04000002, 0x04000002}, + {0x0000a508, 0x08000009, 0x08000009, 0x08000009, 0x08000009, 0x08000009, 0x08000009, 0x08000004, 0x08000004}, + {0x0000a50c, 0x0c00000b, 0x0c00000b, 0x0c00000b, 0x0c00000b, 0x0c00000b, 0x0c00000b, 0x0c000006, 0x0c000006}, + {0x0000a510, 0x1000000d, 0x1000000d, 0x1000000d, 0x1000000d, 0x1000000d, 0x1000000d, 0x0f00000a, 0x0f00000a}, + {0x0000a514, 0x14000011, 0x14000011, 0x14000011, 0x14000011, 0x14000011, 0x14000011, 0x1300000c, 0x1300000c}, + {0x0000a518, 0x1700002b, 0x1700002b, 0x1700002b, 0x1700002b, 0x1600002b, 0x1600002b, 0x1700000e, 0x1700000e}, + {0x0000a51c, 0x1b00002d, 0x1b00002d, 0x1b00002d, 0x1b00002d, 0x1a00002d, 0x1a00002d, 0x1b000064, 0x1b000064}, + {0x0000a520, 0x20000031, 0x20000031, 0x1f000031, 0x1f000031, 0x1e000031, 0x1e000031, 0x1f000242, 0x1f000242}, + {0x0000a524, 0x24000051, 0x24000051, 0x23000051, 0x23000051, 0x23000051, 0x23000051, 0x23000229, 0x23000229}, + {0x0000a528, 0x27000071, 0x27000071, 0x27000071, 0x27000071, 0x26000071, 0x26000071, 0x270002a2, 0x270002a2}, + {0x0000a52c, 0x2b000092, 0x2b000092, 0x2b000092, 0x2b000092, 0x2b000092, 0x2b000092, 0x2c001203, 0x2c001203}, + {0x0000a530, 0x3000028c, 0x3000028c, 0x2f00028c, 0x2f00028c, 0x2e00028c, 0x2e00028c, 0x30001803, 0x30001803}, + {0x0000a534, 0x34000290, 0x34000290, 0x33000290, 0x33000290, 0x32000290, 0x32000290, 0x33000881, 0x33000881}, + {0x0000a538, 0x37000292, 0x37000292, 0x36000292, 0x36000292, 0x35000292, 0x35000292, 0x38001809, 0x38001809}, + {0x0000a53c, 0x3b02028d, 0x3b02028d, 0x3a02028d, 0x3a02028d, 0x3902028d, 0x3902028d, 0x3a000814, 0x3a000814}, + {0x0000a540, 0x3f020291, 0x3f020291, 0x3e020291, 0x3e020291, 0x3d020291, 0x3d020291, 0x3f001a0c, 0x3f001a0c}, + {0x0000a544, 0x44020490, 0x44020490, 0x43020490, 0x43020490, 0x42020490, 0x42020490, 0x43001a0e, 0x43001a0e}, + {0x0000a548, 0x48020492, 0x48020492, 0x47020492, 0x47020492, 0x46020492, 0x46020492, 0x46001812, 0x46001812}, + {0x0000a54c, 0x4c020692, 0x4c020692, 0x4b020692, 0x4b020692, 0x4a020692, 0x4a020692, 0x49001884, 0x49001884}, + {0x0000a550, 0x50020892, 0x50020892, 0x4f020892, 0x4f020892, 0x4e020892, 0x4e020892, 0x4d001e84, 0x4d001e84}, + {0x0000a554, 0x53040891, 0x53040891, 0x53040891, 0x53040891, 0x52040891, 0x52040891, 0x50001e69, 0x50001e69}, + {0x0000a558, 0x58040893, 0x58040893, 0x57040893, 0x57040893, 0x56040893, 0x56040893, 0x550006f4, 0x550006f4}, + {0x0000a55c, 0x5c0408b4, 0x5c0408b4, 0x5a0408b4, 0x5a0408b4, 0x5a0408b4, 0x5a0408b4, 0x59000ad3, 0x59000ad3}, + {0x0000a560, 0x610408b6, 0x610408b6, 0x5e0408b6, 0x5e0408b6, 0x5e0408b6, 0x5e0408b6, 0x5e000ad5, 0x5e000ad5}, + {0x0000a564, 0x670408f6, 0x670408f6, 0x620408f6, 0x620408f6, 0x620408f6, 0x620408f6, 0x61001ced, 0x61001ced}, + {0x0000a568, 0x6a040cf6, 0x6a040cf6, 0x66040cf6, 0x66040cf6, 0x66040cf6, 0x66040cf6, 0x660018d4, 0x660018d4}, + {0x0000a56c, 0x6d040d76, 0x6d040d76, 0x6a040d76, 0x6a040d76, 0x6a040d76, 0x6a040d76, 0x660018d4, 0x660018d4}, + {0x0000a570, 0x70060db6, 0x70060db6, 0x6e060db6, 0x6e060db6, 0x6e060db6, 0x6e060db6, 0x660018d4, 0x660018d4}, + {0x0000a574, 0x730a0df6, 0x730a0df6, 0x720a0df6, 0x720a0df6, 0x720a0df6, 0x720a0df6, 0x660018d4, 0x660018d4}, + {0x0000a578, 0x770a13f6, 0x770a13f6, 0x760a13f6, 0x760a13f6, 0x760a13f6, 0x760a13f6, 0x660018d4, 0x660018d4}, + {0x0000a57c, 0x770a13f6, 0x770a13f6, 0x760a13f6, 0x760a13f6, 0x760a13f6, 0x760a13f6, 0x660018d4, 0x660018d4}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x02c04b01, 0x02c04b01, 0x02c04b01, 0x02c04b01, 0x02c04b01, 0x02c04b01, 0x03804000, 0x03804000}, + {0x0000a610, 0x04008b01, 0x04008b01, 0x04008b01, 0x04008b01, 0x03c08b01, 0x03c08b01, 0x0300ca02, 0x0300ca02}, + {0x0000a614, 0x05811403, 0x05811403, 0x05411303, 0x05411303, 0x05411303, 0x05411303, 0x00000e04, 0x00000e04}, + {0x0000a618, 0x05811604, 0x05811604, 0x05411504, 0x05411504, 0x05411504, 0x05411504, 0x03014000, 0x03014000}, + {0x0000a61c, 0x05811604, 0x05811604, 0x05411504, 0x05411504, 0x05411504, 0x05411504, 0x00000000, 0x00000000}, + {0x0000a620, 0x05811604, 0x05811604, 0x05411504, 0x05411504, 0x05411504, 0x05411504, 0x00000000, 0x00000000}, + {0x0000a624, 0x05811604, 0x05811604, 0x05411504, 0x05411504, 0x05411504, 0x05411504, 0x03014000, 0x03014000}, + {0x0000a628, 0x05811604, 0x05811604, 0x05411504, 0x05411504, 0x05411504, 0x05411504, 0x03804c05, 0x03804c05}, + {0x0000a62c, 0x06815604, 0x06815604, 0x06415504, 0x06415504, 0x06015504, 0x06015504, 0x0701de06, 0x0701de06}, + {0x0000a630, 0x07819a05, 0x07819a05, 0x07419905, 0x07419905, 0x07019805, 0x07019805, 0x07819c07, 0x07819c07}, + {0x0000a634, 0x07819e06, 0x07819e06, 0x07419d06, 0x07419d06, 0x07019c06, 0x07019c06, 0x0701dc07, 0x0701dc07}, + {0x0000a638, 0x07819e06, 0x07819e06, 0x07419d06, 0x07419d06, 0x07019c06, 0x07019c06, 0x0701dc07, 0x0701dc07}, + {0x0000a63c, 0x07819e06, 0x07819e06, 0x07419d06, 0x07419d06, 0x07019c06, 0x07019c06, 0x0701dc07, 0x0701dc07}, + {0x0000b2dc, 0xffff6aaa, 0xffff6aaa, 0xffff6aaa, 0xffff6aaa, 0xffff6aaa, 0xffff6aaa, 0xfffd5aaa, 0xfffd5aaa}, + {0x0000b2e0, 0xfffdcccc, 0xfffdcccc, 0xfffdcccc, 0xfffdcccc, 0xfffdcccc, 0xfffdcccc, 0xfffe9ccc, 0xfffe9ccc}, + {0x0000b2e4, 0xffe3b0f0, 0xffe3b0f0, 0xffe3b0f0, 0xffe3b0f0, 0xffe3b0f0, 0xffe3b0f0, 0xffffe0f0, 0xffffe0f0}, + {0x0000b2e8, 0xffffff00, 0xffffff00, 0xffffff00, 0xffffff00, 0xffffff00, 0xffffff00, 0xfffcff00, 0xfffcff00}, + {0x0000c2dc, 0xffff6aaa, 0xffff6aaa, 0xffff6aaa, 0xffff6aaa, 0xffff6aaa, 0xffff6aaa, 0xfffd5aaa, 0xfffd5aaa}, + {0x0000c2e0, 0xfffdcccc, 0xfffdcccc, 0xfffdcccc, 0xfffdcccc, 0xfffdcccc, 0xfffdcccc, 0xfffe9ccc, 0xfffe9ccc}, + {0x0000c2e4, 0xffe3b0f0, 0xffe3b0f0, 0xffe3b0f0, 0xffe3b0f0, 0xffe3b0f0, 0xffe3b0f0, 0xffffe0f0, 0xffffe0f0}, + {0x0000c2e8, 0xffffff00, 0xffffff00, 0xffffff00, 0xffffff00, 0xffffff00, 0xffffff00, 0xfffcff00, 0xfffcff00}, + {0x00016044, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x010002d4, 0x010002d4}, + {0x00016048, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401}, + {0x00016280, 0x01801e84, 0x01801e84, 0x01801e84, 0x01801e84, 0x01801e84, 0x01801e84, 0x01808e84, 0x01808e84}, + {0x00016444, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x010002d4, 0x010002d4}, + {0x00016448, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401}, + {0x00016844, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x010002d4, 0x010002d4}, + {0x00016848, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401}, +}; + +static const u32 ar955x_1p0_mac_core[][2] = { + /* Addr allmodes */ + {0x00000008, 0x00000000}, + {0x00000030, 0x00020085}, + {0x00000034, 0x00000005}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000000}, + {0x00000048, 0x00000008}, + {0x0000004c, 0x00000010}, + {0x00000050, 0x00000000}, + {0x00001040, 0x002ffc0f}, + {0x00001044, 0x002ffc0f}, + {0x00001048, 0x002ffc0f}, + {0x0000104c, 0x002ffc0f}, + {0x00001050, 0x002ffc0f}, + {0x00001054, 0x002ffc0f}, + {0x00001058, 0x002ffc0f}, + {0x0000105c, 0x002ffc0f}, + {0x00001060, 0x002ffc0f}, + {0x00001064, 0x002ffc0f}, + {0x000010f0, 0x00000100}, + {0x00001270, 0x00000000}, + {0x000012b0, 0x00000000}, + {0x000012f0, 0x00000000}, + {0x0000143c, 0x00000000}, + {0x0000147c, 0x00000000}, + {0x00008000, 0x00000000}, + {0x00008004, 0x00000000}, + {0x00008008, 0x00000000}, + {0x0000800c, 0x00000000}, + {0x00008018, 0x00000000}, + {0x00008020, 0x00000000}, + {0x00008038, 0x00000000}, + {0x0000803c, 0x00000000}, + {0x00008040, 0x00000000}, + {0x00008044, 0x00000000}, + {0x00008048, 0x00000000}, + {0x0000804c, 0xffffffff}, + {0x00008054, 0x00000000}, + {0x00008058, 0x00000000}, + {0x0000805c, 0x000fc78f}, + {0x00008060, 0x0000000f}, + {0x00008064, 0x00000000}, + {0x00008070, 0x00000310}, + {0x00008074, 0x00000020}, + {0x00008078, 0x00000000}, + {0x0000809c, 0x0000000f}, + {0x000080a0, 0x00000000}, + {0x000080a4, 0x02ff0000}, + {0x000080a8, 0x0e070605}, + {0x000080ac, 0x0000000d}, + {0x000080b0, 0x00000000}, + {0x000080b4, 0x00000000}, + {0x000080b8, 0x00000000}, + {0x000080bc, 0x00000000}, + {0x000080c0, 0x2a800000}, + {0x000080c4, 0x06900168}, + {0x000080c8, 0x13881c22}, + {0x000080cc, 0x01f40000}, + {0x000080d0, 0x00252500}, + {0x000080d4, 0x00a00000}, + {0x000080d8, 0x00400000}, + {0x000080dc, 0x00000000}, + {0x000080e0, 0xffffffff}, + {0x000080e4, 0x0000ffff}, + {0x000080e8, 0x3f3f3f3f}, + {0x000080ec, 0x00000000}, + {0x000080f0, 0x00000000}, + {0x000080f4, 0x00000000}, + {0x000080fc, 0x00020000}, + {0x00008100, 0x00000000}, + {0x00008108, 0x00000052}, + {0x0000810c, 0x00000000}, + {0x00008110, 0x00000000}, + {0x00008114, 0x000007ff}, + {0x00008118, 0x000000aa}, + {0x0000811c, 0x00003210}, + {0x00008124, 0x00000000}, + {0x00008128, 0x00000000}, + {0x0000812c, 0x00000000}, + {0x00008130, 0x00000000}, + {0x00008134, 0x00000000}, + {0x00008138, 0x00000000}, + {0x0000813c, 0x0000ffff}, + {0x00008140, 0x000000fe}, + {0x00008144, 0xffffffff}, + {0x00008168, 0x00000000}, + {0x0000816c, 0x00000000}, + {0x000081c0, 0x00000000}, + {0x000081c4, 0x33332210}, + {0x000081ec, 0x00000000}, + {0x000081f0, 0x00000000}, + {0x000081f4, 0x00000000}, + {0x000081f8, 0x00000000}, + {0x000081fc, 0x00000000}, + {0x00008240, 0x00100000}, + {0x00008244, 0x0010f400}, + {0x00008248, 0x00000800}, + {0x0000824c, 0x0001e800}, + {0x00008250, 0x00000000}, + {0x00008254, 0x00000000}, + {0x00008258, 0x00000000}, + {0x0000825c, 0x40000000}, + {0x00008260, 0x00080922}, + {0x00008264, 0x9d400010}, + {0x00008268, 0xffffffff}, + {0x0000826c, 0x0000ffff}, + {0x00008270, 0x00000000}, + {0x00008274, 0x40000000}, + {0x00008278, 0x003e4180}, + {0x0000827c, 0x00000004}, + {0x00008284, 0x0000002c}, + {0x00008288, 0x0000002c}, + {0x0000828c, 0x000000ff}, + {0x00008294, 0x00000000}, + {0x00008298, 0x00000000}, + {0x0000829c, 0x00000000}, + {0x00008300, 0x00001d40}, + {0x00008314, 0x00000000}, + {0x0000831c, 0x0000010d}, + {0x00008328, 0x00000000}, + {0x0000832c, 0x0000001f}, + {0x00008330, 0x00000302}, + {0x00008334, 0x00000700}, + {0x00008338, 0xffff0000}, + {0x0000833c, 0x02400000}, + {0x00008340, 0x000107ff}, + {0x00008344, 0xaa48107b}, + {0x00008348, 0x008f0000}, + {0x0000835c, 0x00000000}, + {0x00008360, 0xffffffff}, + {0x00008364, 0xffffffff}, + {0x00008368, 0x00000000}, + {0x00008370, 0x00000000}, + {0x00008374, 0x000000ff}, + {0x00008378, 0x00000000}, + {0x0000837c, 0x00000000}, + {0x00008380, 0xffffffff}, + {0x00008384, 0xffffffff}, + {0x00008390, 0xffffffff}, + {0x00008394, 0xffffffff}, + {0x00008398, 0x00000000}, + {0x0000839c, 0x00000000}, + {0x000083a0, 0x00000000}, + {0x000083a4, 0x0000fa14}, + {0x000083a8, 0x000f0c00}, + {0x000083ac, 0x33332210}, + {0x000083b0, 0x33332210}, + {0x000083b4, 0x33332210}, + {0x000083b8, 0x33332210}, + {0x000083bc, 0x00000000}, + {0x000083c0, 0x00000000}, + {0x000083c4, 0x00000000}, + {0x000083c8, 0x00000000}, + {0x000083cc, 0x00000200}, + {0x000083d0, 0x8c7901ff}, +}; + +static const u32 ar955x_1p0_common_rx_gain_table[][2] = { + /* Addr allmodes */ + {0x0000a000, 0x00010000}, + {0x0000a004, 0x00030002}, + {0x0000a008, 0x00050004}, + {0x0000a00c, 0x00810080}, + {0x0000a010, 0x00830082}, + {0x0000a014, 0x01810180}, + {0x0000a018, 0x01830182}, + {0x0000a01c, 0x01850184}, + {0x0000a020, 0x01890188}, + {0x0000a024, 0x018b018a}, + {0x0000a028, 0x018d018c}, + {0x0000a02c, 0x01910190}, + {0x0000a030, 0x01930192}, + {0x0000a034, 0x01950194}, + {0x0000a038, 0x038a0196}, + {0x0000a03c, 0x038c038b}, + {0x0000a040, 0x0390038d}, + {0x0000a044, 0x03920391}, + {0x0000a048, 0x03940393}, + {0x0000a04c, 0x03960395}, + {0x0000a050, 0x00000000}, + {0x0000a054, 0x00000000}, + {0x0000a058, 0x00000000}, + {0x0000a05c, 0x00000000}, + {0x0000a060, 0x00000000}, + {0x0000a064, 0x00000000}, + {0x0000a068, 0x00000000}, + {0x0000a06c, 0x00000000}, + {0x0000a070, 0x00000000}, + {0x0000a074, 0x00000000}, + {0x0000a078, 0x00000000}, + {0x0000a07c, 0x00000000}, + {0x0000a080, 0x22222229}, + {0x0000a084, 0x1d1d1d1d}, + {0x0000a088, 0x1d1d1d1d}, + {0x0000a08c, 0x1d1d1d1d}, + {0x0000a090, 0x171d1d1d}, + {0x0000a094, 0x11111717}, + {0x0000a098, 0x00030311}, + {0x0000a09c, 0x00000000}, + {0x0000a0a0, 0x00000000}, + {0x0000a0a4, 0x00000000}, + {0x0000a0a8, 0x00000000}, + {0x0000a0ac, 0x00000000}, + {0x0000a0b0, 0x00000000}, + {0x0000a0b4, 0x00000000}, + {0x0000a0b8, 0x00000000}, + {0x0000a0bc, 0x00000000}, + {0x0000a0c0, 0x001f0000}, + {0x0000a0c4, 0x01000101}, + {0x0000a0c8, 0x011e011f}, + {0x0000a0cc, 0x011c011d}, + {0x0000a0d0, 0x02030204}, + {0x0000a0d4, 0x02010202}, + {0x0000a0d8, 0x021f0200}, + {0x0000a0dc, 0x0302021e}, + {0x0000a0e0, 0x03000301}, + {0x0000a0e4, 0x031e031f}, + {0x0000a0e8, 0x0402031d}, + {0x0000a0ec, 0x04000401}, + {0x0000a0f0, 0x041e041f}, + {0x0000a0f4, 0x0502041d}, + {0x0000a0f8, 0x05000501}, + {0x0000a0fc, 0x051e051f}, + {0x0000a100, 0x06010602}, + {0x0000a104, 0x061f0600}, + {0x0000a108, 0x061d061e}, + {0x0000a10c, 0x07020703}, + {0x0000a110, 0x07000701}, + {0x0000a114, 0x00000000}, + {0x0000a118, 0x00000000}, + {0x0000a11c, 0x00000000}, + {0x0000a120, 0x00000000}, + {0x0000a124, 0x00000000}, + {0x0000a128, 0x00000000}, + {0x0000a12c, 0x00000000}, + {0x0000a130, 0x00000000}, + {0x0000a134, 0x00000000}, + {0x0000a138, 0x00000000}, + {0x0000a13c, 0x00000000}, + {0x0000a140, 0x001f0000}, + {0x0000a144, 0x01000101}, + {0x0000a148, 0x011e011f}, + {0x0000a14c, 0x011c011d}, + {0x0000a150, 0x02030204}, + {0x0000a154, 0x02010202}, + {0x0000a158, 0x021f0200}, + {0x0000a15c, 0x0302021e}, + {0x0000a160, 0x03000301}, + {0x0000a164, 0x031e031f}, + {0x0000a168, 0x0402031d}, + {0x0000a16c, 0x04000401}, + {0x0000a170, 0x041e041f}, + {0x0000a174, 0x0502041d}, + {0x0000a178, 0x05000501}, + {0x0000a17c, 0x051e051f}, + {0x0000a180, 0x06010602}, + {0x0000a184, 0x061f0600}, + {0x0000a188, 0x061d061e}, + {0x0000a18c, 0x07020703}, + {0x0000a190, 0x07000701}, + {0x0000a194, 0x00000000}, + {0x0000a198, 0x00000000}, + {0x0000a19c, 0x00000000}, + {0x0000a1a0, 0x00000000}, + {0x0000a1a4, 0x00000000}, + {0x0000a1a8, 0x00000000}, + {0x0000a1ac, 0x00000000}, + {0x0000a1b0, 0x00000000}, + {0x0000a1b4, 0x00000000}, + {0x0000a1b8, 0x00000000}, + {0x0000a1bc, 0x00000000}, + {0x0000a1c0, 0x00000000}, + {0x0000a1c4, 0x00000000}, + {0x0000a1c8, 0x00000000}, + {0x0000a1cc, 0x00000000}, + {0x0000a1d0, 0x00000000}, + {0x0000a1d4, 0x00000000}, + {0x0000a1d8, 0x00000000}, + {0x0000a1dc, 0x00000000}, + {0x0000a1e0, 0x00000000}, + {0x0000a1e4, 0x00000000}, + {0x0000a1e8, 0x00000000}, + {0x0000a1ec, 0x00000000}, + {0x0000a1f0, 0x00000396}, + {0x0000a1f4, 0x00000396}, + {0x0000a1f8, 0x00000396}, + {0x0000a1fc, 0x00000196}, + {0x0000b000, 0x00010000}, + {0x0000b004, 0x00030002}, + {0x0000b008, 0x00050004}, + {0x0000b00c, 0x00810080}, + {0x0000b010, 0x00830082}, + {0x0000b014, 0x01810180}, + {0x0000b018, 0x01830182}, + {0x0000b01c, 0x01850184}, + {0x0000b020, 0x02810280}, + {0x0000b024, 0x02830282}, + {0x0000b028, 0x02850284}, + {0x0000b02c, 0x02890288}, + {0x0000b030, 0x028b028a}, + {0x0000b034, 0x0388028c}, + {0x0000b038, 0x038a0389}, + {0x0000b03c, 0x038c038b}, + {0x0000b040, 0x0390038d}, + {0x0000b044, 0x03920391}, + {0x0000b048, 0x03940393}, + {0x0000b04c, 0x03960395}, + {0x0000b050, 0x00000000}, + {0x0000b054, 0x00000000}, + {0x0000b058, 0x00000000}, + {0x0000b05c, 0x00000000}, + {0x0000b060, 0x00000000}, + {0x0000b064, 0x00000000}, + {0x0000b068, 0x00000000}, + {0x0000b06c, 0x00000000}, + {0x0000b070, 0x00000000}, + {0x0000b074, 0x00000000}, + {0x0000b078, 0x00000000}, + {0x0000b07c, 0x00000000}, + {0x0000b080, 0x23232323}, + {0x0000b084, 0x21232323}, + {0x0000b088, 0x19191c1e}, + {0x0000b08c, 0x12141417}, + {0x0000b090, 0x07070e0e}, + {0x0000b094, 0x03030305}, + {0x0000b098, 0x00000003}, + {0x0000b09c, 0x00000000}, + {0x0000b0a0, 0x00000000}, + {0x0000b0a4, 0x00000000}, + {0x0000b0a8, 0x00000000}, + {0x0000b0ac, 0x00000000}, + {0x0000b0b0, 0x00000000}, + {0x0000b0b4, 0x00000000}, + {0x0000b0b8, 0x00000000}, + {0x0000b0bc, 0x00000000}, + {0x0000b0c0, 0x003f0020}, + {0x0000b0c4, 0x00400041}, + {0x0000b0c8, 0x0140005f}, + {0x0000b0cc, 0x0160015f}, + {0x0000b0d0, 0x017e017f}, + {0x0000b0d4, 0x02410242}, + {0x0000b0d8, 0x025f0240}, + {0x0000b0dc, 0x027f0260}, + {0x0000b0e0, 0x0341027e}, + {0x0000b0e4, 0x035f0340}, + {0x0000b0e8, 0x037f0360}, + {0x0000b0ec, 0x04400441}, + {0x0000b0f0, 0x0460045f}, + {0x0000b0f4, 0x0541047f}, + {0x0000b0f8, 0x055f0540}, + {0x0000b0fc, 0x057f0560}, + {0x0000b100, 0x06400641}, + {0x0000b104, 0x0660065f}, + {0x0000b108, 0x067e067f}, + {0x0000b10c, 0x07410742}, + {0x0000b110, 0x075f0740}, + {0x0000b114, 0x077f0760}, + {0x0000b118, 0x07800781}, + {0x0000b11c, 0x07a0079f}, + {0x0000b120, 0x07c107bf}, + {0x0000b124, 0x000007c0}, + {0x0000b128, 0x00000000}, + {0x0000b12c, 0x00000000}, + {0x0000b130, 0x00000000}, + {0x0000b134, 0x00000000}, + {0x0000b138, 0x00000000}, + {0x0000b13c, 0x00000000}, + {0x0000b140, 0x003f0020}, + {0x0000b144, 0x00400041}, + {0x0000b148, 0x0140005f}, + {0x0000b14c, 0x0160015f}, + {0x0000b150, 0x017e017f}, + {0x0000b154, 0x02410242}, + {0x0000b158, 0x025f0240}, + {0x0000b15c, 0x027f0260}, + {0x0000b160, 0x0341027e}, + {0x0000b164, 0x035f0340}, + {0x0000b168, 0x037f0360}, + {0x0000b16c, 0x04400441}, + {0x0000b170, 0x0460045f}, + {0x0000b174, 0x0541047f}, + {0x0000b178, 0x055f0540}, + {0x0000b17c, 0x057f0560}, + {0x0000b180, 0x06400641}, + {0x0000b184, 0x0660065f}, + {0x0000b188, 0x067e067f}, + {0x0000b18c, 0x07410742}, + {0x0000b190, 0x075f0740}, + {0x0000b194, 0x077f0760}, + {0x0000b198, 0x07800781}, + {0x0000b19c, 0x07a0079f}, + {0x0000b1a0, 0x07c107bf}, + {0x0000b1a4, 0x000007c0}, + {0x0000b1a8, 0x00000000}, + {0x0000b1ac, 0x00000000}, + {0x0000b1b0, 0x00000000}, + {0x0000b1b4, 0x00000000}, + {0x0000b1b8, 0x00000000}, + {0x0000b1bc, 0x00000000}, + {0x0000b1c0, 0x00000000}, + {0x0000b1c4, 0x00000000}, + {0x0000b1c8, 0x00000000}, + {0x0000b1cc, 0x00000000}, + {0x0000b1d0, 0x00000000}, + {0x0000b1d4, 0x00000000}, + {0x0000b1d8, 0x00000000}, + {0x0000b1dc, 0x00000000}, + {0x0000b1e0, 0x00000000}, + {0x0000b1e4, 0x00000000}, + {0x0000b1e8, 0x00000000}, + {0x0000b1ec, 0x00000000}, + {0x0000b1f0, 0x00000396}, + {0x0000b1f4, 0x00000396}, + {0x0000b1f8, 0x00000396}, + {0x0000b1fc, 0x00000196}, +}; + +static const u32 ar955x_1p0_baseband_core[][2] = { + /* Addr allmodes */ + {0x00009800, 0xafe68e30}, + {0x00009804, 0xfd14e000}, + {0x00009808, 0x9c0a9f6b}, + {0x0000980c, 0x04900000}, + {0x00009814, 0x0280c00a}, + {0x00009818, 0x00000000}, + {0x0000981c, 0x00020028}, + {0x00009834, 0x6400a190}, + {0x00009838, 0x0108ecff}, + {0x0000983c, 0x14000600}, + {0x00009880, 0x201fff00}, + {0x00009884, 0x00001042}, + {0x000098a4, 0x00200400}, + {0x000098b0, 0x32840bbe}, + {0x000098bc, 0x00000002}, + {0x000098d0, 0x004b6a8e}, + {0x000098d4, 0x00000820}, + {0x000098dc, 0x00000000}, + {0x000098f0, 0x00000000}, + {0x000098f4, 0x00000000}, + {0x00009c04, 0xff55ff55}, + {0x00009c08, 0x0320ff55}, + {0x00009c0c, 0x00000000}, + {0x00009c10, 0x00000000}, + {0x00009c14, 0x00046384}, + {0x00009c18, 0x05b6b440}, + {0x00009c1c, 0x00b6b440}, + {0x00009d00, 0xc080a333}, + {0x00009d04, 0x40206c10}, + {0x00009d08, 0x009c4060}, + {0x00009d0c, 0x9883800a}, + {0x00009d10, 0x01834061}, + {0x00009d14, 0x00c0040b}, + {0x00009d18, 0x00000000}, + {0x00009e08, 0x0038230c}, + {0x00009e24, 0x990bb515}, + {0x00009e28, 0x0c6f0000}, + {0x00009e30, 0x06336f77}, + {0x00009e34, 0x6af6532f}, + {0x00009e38, 0x0cc80c00}, + {0x00009e40, 0x0d261820}, + {0x00009e4c, 0x00001004}, + {0x00009e50, 0x00ff03f1}, + {0x00009fc0, 0x813e4788}, + {0x00009fc4, 0x0001efb5}, + {0x00009fcc, 0x40000014}, + {0x00009fd0, 0x01193b93}, + {0x0000a20c, 0x00000000}, + {0x0000a220, 0x00000000}, + {0x0000a224, 0x00000000}, + {0x0000a228, 0x10002310}, + {0x0000a23c, 0x00000000}, + {0x0000a244, 0x0c000000}, + {0x0000a248, 0x00000140}, + {0x0000a2a0, 0x00000007}, + {0x0000a2c0, 0x00000007}, + {0x0000a2c8, 0x00000000}, + {0x0000a2d4, 0x00000000}, + {0x0000a2ec, 0x00000000}, + {0x0000a2f0, 0x00000000}, + {0x0000a2f4, 0x00000000}, + {0x0000a2f8, 0x00000000}, + {0x0000a344, 0x00000000}, + {0x0000a34c, 0x00000000}, + {0x0000a350, 0x0000a000}, + {0x0000a364, 0x00000000}, + {0x0000a370, 0x00000000}, + {0x0000a390, 0x00000001}, + {0x0000a394, 0x00000444}, + {0x0000a398, 0x1f020503}, + {0x0000a39c, 0x29180c03}, + {0x0000a3a0, 0x9a8b6844}, + {0x0000a3a4, 0x00000000}, + {0x0000a3a8, 0xaaaaaaaa}, + {0x0000a3ac, 0x3c466478}, + {0x0000a3c0, 0x20202020}, + {0x0000a3c4, 0x22222220}, + {0x0000a3c8, 0x20200020}, + {0x0000a3cc, 0x20202020}, + {0x0000a3d0, 0x20202020}, + {0x0000a3d4, 0x20202020}, + {0x0000a3d8, 0x20202020}, + {0x0000a3dc, 0x20202020}, + {0x0000a3e0, 0x20202020}, + {0x0000a3e4, 0x20202020}, + {0x0000a3e8, 0x20202020}, + {0x0000a3ec, 0x20202020}, + {0x0000a3f0, 0x00000000}, + {0x0000a3f4, 0x00000000}, + {0x0000a3f8, 0x0c9bd380}, + {0x0000a3fc, 0x000f0f01}, + {0x0000a400, 0x8fa91f01}, + {0x0000a404, 0x00000000}, + {0x0000a408, 0x0e79e5c6}, + {0x0000a40c, 0x00820820}, + {0x0000a414, 0x1ce739ce}, + {0x0000a418, 0x2d001dce}, + {0x0000a41c, 0x1ce739ce}, + {0x0000a420, 0x000001ce}, + {0x0000a424, 0x1ce739ce}, + {0x0000a428, 0x000001ce}, + {0x0000a42c, 0x1ce739ce}, + {0x0000a430, 0x1ce739ce}, + {0x0000a434, 0x00000000}, + {0x0000a438, 0x00001801}, + {0x0000a43c, 0x00100000}, + {0x0000a444, 0x00000000}, + {0x0000a448, 0x05000080}, + {0x0000a44c, 0x00000001}, + {0x0000a450, 0x00010000}, + {0x0000a458, 0x00000000}, + {0x0000a644, 0xbfad9d74}, + {0x0000a648, 0x0048060a}, + {0x0000a64c, 0x00003c37}, + {0x0000a670, 0x03020100}, + {0x0000a674, 0x09080504}, + {0x0000a678, 0x0d0c0b0a}, + {0x0000a67c, 0x13121110}, + {0x0000a680, 0x31301514}, + {0x0000a684, 0x35343332}, + {0x0000a688, 0x00000036}, + {0x0000a690, 0x00000838}, + {0x0000a7cc, 0x00000000}, + {0x0000a7d0, 0x00000000}, + {0x0000a7d4, 0x00000004}, + {0x0000a7dc, 0x00000000}, + {0x0000a8d0, 0x004b6a8e}, + {0x0000a8d4, 0x00000820}, + {0x0000a8dc, 0x00000000}, + {0x0000a8f0, 0x00000000}, + {0x0000a8f4, 0x00000000}, + {0x0000b2d0, 0x00000080}, + {0x0000b2d4, 0x00000000}, + {0x0000b2ec, 0x00000000}, + {0x0000b2f0, 0x00000000}, + {0x0000b2f4, 0x00000000}, + {0x0000b2f8, 0x00000000}, + {0x0000b408, 0x0e79e5c0}, + {0x0000b40c, 0x00820820}, + {0x0000b420, 0x00000000}, + {0x0000b8d0, 0x004b6a8e}, + {0x0000b8d4, 0x00000820}, + {0x0000b8dc, 0x00000000}, + {0x0000b8f0, 0x00000000}, + {0x0000b8f4, 0x00000000}, + {0x0000c2d0, 0x00000080}, + {0x0000c2d4, 0x00000000}, + {0x0000c2ec, 0x00000000}, + {0x0000c2f0, 0x00000000}, + {0x0000c2f4, 0x00000000}, + {0x0000c2f8, 0x00000000}, + {0x0000c408, 0x0e79e5c0}, + {0x0000c40c, 0x00820820}, + {0x0000c420, 0x00000000}, +}; + +static const u32 ar955x_1p0_common_wo_xlna_rx_gain_table[][2] = { + /* Addr allmodes */ + {0x0000a000, 0x00010000}, + {0x0000a004, 0x00030002}, + {0x0000a008, 0x00050004}, + {0x0000a00c, 0x00810080}, + {0x0000a010, 0x00830082}, + {0x0000a014, 0x01810180}, + {0x0000a018, 0x01830182}, + {0x0000a01c, 0x01850184}, + {0x0000a020, 0x01890188}, + {0x0000a024, 0x018b018a}, + {0x0000a028, 0x018d018c}, + {0x0000a02c, 0x03820190}, + {0x0000a030, 0x03840383}, + {0x0000a034, 0x03880385}, + {0x0000a038, 0x038a0389}, + {0x0000a03c, 0x038c038b}, + {0x0000a040, 0x0390038d}, + {0x0000a044, 0x03920391}, + {0x0000a048, 0x03940393}, + {0x0000a04c, 0x03960395}, + {0x0000a050, 0x00000000}, + {0x0000a054, 0x00000000}, + {0x0000a058, 0x00000000}, + {0x0000a05c, 0x00000000}, + {0x0000a060, 0x00000000}, + {0x0000a064, 0x00000000}, + {0x0000a068, 0x00000000}, + {0x0000a06c, 0x00000000}, + {0x0000a070, 0x00000000}, + {0x0000a074, 0x00000000}, + {0x0000a078, 0x00000000}, + {0x0000a07c, 0x00000000}, + {0x0000a080, 0x29292929}, + {0x0000a084, 0x29292929}, + {0x0000a088, 0x29292929}, + {0x0000a08c, 0x29292929}, + {0x0000a090, 0x22292929}, + {0x0000a094, 0x1d1d2222}, + {0x0000a098, 0x0c111117}, + {0x0000a09c, 0x00030303}, + {0x0000a0a0, 0x00000000}, + {0x0000a0a4, 0x00000000}, + {0x0000a0a8, 0x00000000}, + {0x0000a0ac, 0x00000000}, + {0x0000a0b0, 0x00000000}, + {0x0000a0b4, 0x00000000}, + {0x0000a0b8, 0x00000000}, + {0x0000a0bc, 0x00000000}, + {0x0000a0c0, 0x001f0000}, + {0x0000a0c4, 0x01000101}, + {0x0000a0c8, 0x011e011f}, + {0x0000a0cc, 0x011c011d}, + {0x0000a0d0, 0x02030204}, + {0x0000a0d4, 0x02010202}, + {0x0000a0d8, 0x021f0200}, + {0x0000a0dc, 0x0302021e}, + {0x0000a0e0, 0x03000301}, + {0x0000a0e4, 0x031e031f}, + {0x0000a0e8, 0x0402031d}, + {0x0000a0ec, 0x04000401}, + {0x0000a0f0, 0x041e041f}, + {0x0000a0f4, 0x0502041d}, + {0x0000a0f8, 0x05000501}, + {0x0000a0fc, 0x051e051f}, + {0x0000a100, 0x06010602}, + {0x0000a104, 0x061f0600}, + {0x0000a108, 0x061d061e}, + {0x0000a10c, 0x07020703}, + {0x0000a110, 0x07000701}, + {0x0000a114, 0x00000000}, + {0x0000a118, 0x00000000}, + {0x0000a11c, 0x00000000}, + {0x0000a120, 0x00000000}, + {0x0000a124, 0x00000000}, + {0x0000a128, 0x00000000}, + {0x0000a12c, 0x00000000}, + {0x0000a130, 0x00000000}, + {0x0000a134, 0x00000000}, + {0x0000a138, 0x00000000}, + {0x0000a13c, 0x00000000}, + {0x0000a140, 0x001f0000}, + {0x0000a144, 0x01000101}, + {0x0000a148, 0x011e011f}, + {0x0000a14c, 0x011c011d}, + {0x0000a150, 0x02030204}, + {0x0000a154, 0x02010202}, + {0x0000a158, 0x021f0200}, + {0x0000a15c, 0x0302021e}, + {0x0000a160, 0x03000301}, + {0x0000a164, 0x031e031f}, + {0x0000a168, 0x0402031d}, + {0x0000a16c, 0x04000401}, + {0x0000a170, 0x041e041f}, + {0x0000a174, 0x0502041d}, + {0x0000a178, 0x05000501}, + {0x0000a17c, 0x051e051f}, + {0x0000a180, 0x06010602}, + {0x0000a184, 0x061f0600}, + {0x0000a188, 0x061d061e}, + {0x0000a18c, 0x07020703}, + {0x0000a190, 0x07000701}, + {0x0000a194, 0x00000000}, + {0x0000a198, 0x00000000}, + {0x0000a19c, 0x00000000}, + {0x0000a1a0, 0x00000000}, + {0x0000a1a4, 0x00000000}, + {0x0000a1a8, 0x00000000}, + {0x0000a1ac, 0x00000000}, + {0x0000a1b0, 0x00000000}, + {0x0000a1b4, 0x00000000}, + {0x0000a1b8, 0x00000000}, + {0x0000a1bc, 0x00000000}, + {0x0000a1c0, 0x00000000}, + {0x0000a1c4, 0x00000000}, + {0x0000a1c8, 0x00000000}, + {0x0000a1cc, 0x00000000}, + {0x0000a1d0, 0x00000000}, + {0x0000a1d4, 0x00000000}, + {0x0000a1d8, 0x00000000}, + {0x0000a1dc, 0x00000000}, + {0x0000a1e0, 0x00000000}, + {0x0000a1e4, 0x00000000}, + {0x0000a1e8, 0x00000000}, + {0x0000a1ec, 0x00000000}, + {0x0000a1f0, 0x00000396}, + {0x0000a1f4, 0x00000396}, + {0x0000a1f8, 0x00000396}, + {0x0000a1fc, 0x00000196}, + {0x0000b000, 0x00010000}, + {0x0000b004, 0x00030002}, + {0x0000b008, 0x00050004}, + {0x0000b00c, 0x00810080}, + {0x0000b010, 0x00830082}, + {0x0000b014, 0x01810180}, + {0x0000b018, 0x01830182}, + {0x0000b01c, 0x01850184}, + {0x0000b020, 0x02810280}, + {0x0000b024, 0x02830282}, + {0x0000b028, 0x02850284}, + {0x0000b02c, 0x02890288}, + {0x0000b030, 0x028b028a}, + {0x0000b034, 0x0388028c}, + {0x0000b038, 0x038a0389}, + {0x0000b03c, 0x038c038b}, + {0x0000b040, 0x0390038d}, + {0x0000b044, 0x03920391}, + {0x0000b048, 0x03940393}, + {0x0000b04c, 0x03960395}, + {0x0000b050, 0x00000000}, + {0x0000b054, 0x00000000}, + {0x0000b058, 0x00000000}, + {0x0000b05c, 0x00000000}, + {0x0000b060, 0x00000000}, + {0x0000b064, 0x00000000}, + {0x0000b068, 0x00000000}, + {0x0000b06c, 0x00000000}, + {0x0000b070, 0x00000000}, + {0x0000b074, 0x00000000}, + {0x0000b078, 0x00000000}, + {0x0000b07c, 0x00000000}, + {0x0000b080, 0x32323232}, + {0x0000b084, 0x2f2f3232}, + {0x0000b088, 0x23282a2d}, + {0x0000b08c, 0x1c1e2123}, + {0x0000b090, 0x14171919}, + {0x0000b094, 0x0e0e1214}, + {0x0000b098, 0x03050707}, + {0x0000b09c, 0x00030303}, + {0x0000b0a0, 0x00000000}, + {0x0000b0a4, 0x00000000}, + {0x0000b0a8, 0x00000000}, + {0x0000b0ac, 0x00000000}, + {0x0000b0b0, 0x00000000}, + {0x0000b0b4, 0x00000000}, + {0x0000b0b8, 0x00000000}, + {0x0000b0bc, 0x00000000}, + {0x0000b0c0, 0x003f0020}, + {0x0000b0c4, 0x00400041}, + {0x0000b0c8, 0x0140005f}, + {0x0000b0cc, 0x0160015f}, + {0x0000b0d0, 0x017e017f}, + {0x0000b0d4, 0x02410242}, + {0x0000b0d8, 0x025f0240}, + {0x0000b0dc, 0x027f0260}, + {0x0000b0e0, 0x0341027e}, + {0x0000b0e4, 0x035f0340}, + {0x0000b0e8, 0x037f0360}, + {0x0000b0ec, 0x04400441}, + {0x0000b0f0, 0x0460045f}, + {0x0000b0f4, 0x0541047f}, + {0x0000b0f8, 0x055f0540}, + {0x0000b0fc, 0x057f0560}, + {0x0000b100, 0x06400641}, + {0x0000b104, 0x0660065f}, + {0x0000b108, 0x067e067f}, + {0x0000b10c, 0x07410742}, + {0x0000b110, 0x075f0740}, + {0x0000b114, 0x077f0760}, + {0x0000b118, 0x07800781}, + {0x0000b11c, 0x07a0079f}, + {0x0000b120, 0x07c107bf}, + {0x0000b124, 0x000007c0}, + {0x0000b128, 0x00000000}, + {0x0000b12c, 0x00000000}, + {0x0000b130, 0x00000000}, + {0x0000b134, 0x00000000}, + {0x0000b138, 0x00000000}, + {0x0000b13c, 0x00000000}, + {0x0000b140, 0x003f0020}, + {0x0000b144, 0x00400041}, + {0x0000b148, 0x0140005f}, + {0x0000b14c, 0x0160015f}, + {0x0000b150, 0x017e017f}, + {0x0000b154, 0x02410242}, + {0x0000b158, 0x025f0240}, + {0x0000b15c, 0x027f0260}, + {0x0000b160, 0x0341027e}, + {0x0000b164, 0x035f0340}, + {0x0000b168, 0x037f0360}, + {0x0000b16c, 0x04400441}, + {0x0000b170, 0x0460045f}, + {0x0000b174, 0x0541047f}, + {0x0000b178, 0x055f0540}, + {0x0000b17c, 0x057f0560}, + {0x0000b180, 0x06400641}, + {0x0000b184, 0x0660065f}, + {0x0000b188, 0x067e067f}, + {0x0000b18c, 0x07410742}, + {0x0000b190, 0x075f0740}, + {0x0000b194, 0x077f0760}, + {0x0000b198, 0x07800781}, + {0x0000b19c, 0x07a0079f}, + {0x0000b1a0, 0x07c107bf}, + {0x0000b1a4, 0x000007c0}, + {0x0000b1a8, 0x00000000}, + {0x0000b1ac, 0x00000000}, + {0x0000b1b0, 0x00000000}, + {0x0000b1b4, 0x00000000}, + {0x0000b1b8, 0x00000000}, + {0x0000b1bc, 0x00000000}, + {0x0000b1c0, 0x00000000}, + {0x0000b1c4, 0x00000000}, + {0x0000b1c8, 0x00000000}, + {0x0000b1cc, 0x00000000}, + {0x0000b1d0, 0x00000000}, + {0x0000b1d4, 0x00000000}, + {0x0000b1d8, 0x00000000}, + {0x0000b1dc, 0x00000000}, + {0x0000b1e0, 0x00000000}, + {0x0000b1e4, 0x00000000}, + {0x0000b1e8, 0x00000000}, + {0x0000b1ec, 0x00000000}, + {0x0000b1f0, 0x00000396}, + {0x0000b1f4, 0x00000396}, + {0x0000b1f8, 0x00000396}, + {0x0000b1fc, 0x00000196}, +}; + +static const u32 ar955x_1p0_soc_preamble[][2] = { + /* Addr allmodes */ + {0x00007000, 0x00000000}, + {0x00007004, 0x00000000}, + {0x00007008, 0x00000000}, + {0x0000700c, 0x00000000}, + {0x0000701c, 0x00000000}, + {0x00007020, 0x00000000}, + {0x00007024, 0x00000000}, + {0x00007028, 0x00000000}, + {0x0000702c, 0x00000000}, + {0x00007030, 0x00000000}, + {0x00007034, 0x00000002}, + {0x00007038, 0x000004c2}, + {0x00007048, 0x00000000}, +}; + +static const u32 ar955x_1p0_common_wo_xlna_rx_gain_bounds[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009e44, 0xfe321e27, 0xfe321e27, 0xfe291e27, 0xfe291e27}, + {0x00009e48, 0x5030201a, 0x5030201a, 0x50302012, 0x50302012}, +}; + +static const u32 ar955x_1p0_mac_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00001030, 0x00000230, 0x00000460, 0x000002c0, 0x00000160}, + {0x00001070, 0x00000168, 0x000002d0, 0x00000318, 0x0000018c}, + {0x000010b0, 0x00000e60, 0x00001cc0, 0x00007c70, 0x00003e38}, + {0x00008014, 0x03e803e8, 0x07d007d0, 0x10801600, 0x08400b00}, + {0x0000801c, 0x128d8027, 0x128d804f, 0x12e00057, 0x12e0002b}, + {0x00008120, 0x08f04800, 0x08f04800, 0x08f04810, 0x08f04810}, + {0x000081d0, 0x00003210, 0x00003210, 0x0000320a, 0x0000320a}, + {0x00008318, 0x00003e80, 0x00007d00, 0x00006880, 0x00003440}, +}; + +static const u32 ar955x_1p0_common_rx_gain_bounds[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009e44, 0xfe321e27, 0xfe321e27, 0xfe291e27, 0xfe291e27}, + {0x00009e48, 0x5030201a, 0x5030201a, 0x50302018, 0x50302018}, +}; + +static const u32 ar955x_1p0_modes_no_xpa_tx_gain_table[][9] = { + /* Addr 5G_HT20_L 5G_HT40_L 5G_HT20_M 5G_HT40_M 5G_HT20_H 5G_HT40_H 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0x01feee00, 0x01feee00, 0x01feee00, 0x01feee00, 0x01feee00, 0x01feee00, 0xfffe5aaa, 0xfffe5aaa}, + {0x0000a2e0, 0x0000f000, 0x0000f000, 0x0000f000, 0x0000f000, 0x0000f000, 0x0000f000, 0xfffe9ccc, 0xfffe9ccc}, + {0x0000a2e4, 0x01ff0000, 0x01ff0000, 0x01ff0000, 0x01ff0000, 0x01ff0000, 0x01ff0000, 0xffffe0f0, 0xffffe0f0}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffef00, 0xffffef00}, + {0x0000a410, 0x000050d8, 0x000050d8, 0x000050d8, 0x000050d8, 0x000050d8, 0x000050d8, 0x000050d7, 0x000050d7}, + {0x0000a500, 0x00002220, 0x00002220, 0x00002220, 0x00002220, 0x00002220, 0x00002220, 0x00000000, 0x00000000}, + {0x0000a504, 0x04002222, 0x04002222, 0x04002222, 0x04002222, 0x04002222, 0x04002222, 0x04000002, 0x04000002}, + {0x0000a508, 0x09002421, 0x09002421, 0x09002421, 0x09002421, 0x09002421, 0x09002421, 0x08000004, 0x08000004}, + {0x0000a50c, 0x0d002621, 0x0d002621, 0x0d002621, 0x0d002621, 0x0d002621, 0x0d002621, 0x0b000006, 0x0b000006}, + {0x0000a510, 0x13004620, 0x13004620, 0x13004620, 0x13004620, 0x13004620, 0x13004620, 0x0f00000a, 0x0f00000a}, + {0x0000a514, 0x19004a20, 0x19004a20, 0x19004a20, 0x19004a20, 0x19004a20, 0x19004a20, 0x1300000c, 0x1300000c}, + {0x0000a518, 0x1d004e20, 0x1d004e20, 0x1d004e20, 0x1d004e20, 0x1d004e20, 0x1d004e20, 0x1700000e, 0x1700000e}, + {0x0000a51c, 0x21005420, 0x21005420, 0x21005420, 0x21005420, 0x21005420, 0x21005420, 0x1b000012, 0x1b000012}, + {0x0000a520, 0x26005e20, 0x26005e20, 0x26005e20, 0x26005e20, 0x26005e20, 0x26005e20, 0x1f00004a, 0x1f00004a}, + {0x0000a524, 0x2b005e40, 0x2b005e40, 0x2b005e40, 0x2b005e40, 0x2b005e40, 0x2b005e40, 0x23000244, 0x23000244}, + {0x0000a528, 0x2f005e42, 0x2f005e42, 0x2f005e42, 0x2f005e42, 0x2f005e42, 0x2f005e42, 0x2700022b, 0x2700022b}, + {0x0000a52c, 0x33005e44, 0x33005e44, 0x33005e44, 0x33005e44, 0x33005e44, 0x33005e44, 0x2b000625, 0x2b000625}, + {0x0000a530, 0x38005e65, 0x38005e65, 0x38005e65, 0x38005e65, 0x38005e65, 0x38005e65, 0x2f001006, 0x2f001006}, + {0x0000a534, 0x3c005e69, 0x3c005e69, 0x3c005e69, 0x3c005e69, 0x3c005e69, 0x3c005e69, 0x330008a0, 0x330008a0}, + {0x0000a538, 0x40005e6b, 0x40005e6b, 0x40005e6b, 0x40005e6b, 0x40005e6b, 0x40005e6b, 0x37000a2a, 0x37000a2a}, + {0x0000a53c, 0x44005e6d, 0x44005e6d, 0x44005e6d, 0x44005e6d, 0x44005e6d, 0x44005e6d, 0x3b001c23, 0x3b001c23}, + {0x0000a540, 0x49005e72, 0x49005e72, 0x49005e72, 0x49005e72, 0x49005e72, 0x49005e72, 0x3f0014a0, 0x3f0014a0}, + {0x0000a544, 0x4e005eb2, 0x4e005eb2, 0x4e005eb2, 0x4e005eb2, 0x4e005eb2, 0x4e005eb2, 0x43001882, 0x43001882}, + {0x0000a548, 0x53005f12, 0x53005f12, 0x53005f12, 0x53005f12, 0x53005f12, 0x53005f12, 0x47001ca2, 0x47001ca2}, + {0x0000a54c, 0x59025eb2, 0x59025eb2, 0x59025eb2, 0x59025eb2, 0x59025eb2, 0x59025eb2, 0x4b001ec3, 0x4b001ec3}, + {0x0000a550, 0x5e025f12, 0x5e025f12, 0x5e025f12, 0x5e025f12, 0x5e025f12, 0x5e025f12, 0x4f00148c, 0x4f00148c}, + {0x0000a554, 0x61027f12, 0x61027f12, 0x61027f12, 0x61027f12, 0x61027f12, 0x61027f12, 0x53001c6e, 0x53001c6e}, + {0x0000a558, 0x6702bf12, 0x6702bf12, 0x6702bf12, 0x6702bf12, 0x6702bf12, 0x6702bf12, 0x57001c92, 0x57001c92}, + {0x0000a55c, 0x6b02bf14, 0x6b02bf14, 0x6b02bf14, 0x6b02bf14, 0x6b02bf14, 0x6b02bf14, 0x5c001af6, 0x5c001af6}, + {0x0000a560, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x5c001af6, 0x5c001af6}, + {0x0000a564, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x5c001af6, 0x5c001af6}, + {0x0000a568, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x5c001af6, 0x5c001af6}, + {0x0000a56c, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x5c001af6, 0x5c001af6}, + {0x0000a570, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x5c001af6, 0x5c001af6}, + {0x0000a574, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x5c001af6, 0x5c001af6}, + {0x0000a578, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x5c001af6, 0x5c001af6}, + {0x0000a57c, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x6f02bf16, 0x5c001af6, 0x5c001af6}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00804000, 0x00804000, 0x00804000, 0x00804000, 0x00804000, 0x00804000, 0x04005001, 0x04005001}, + {0x0000a614, 0x00804201, 0x00804201, 0x00804201, 0x00804201, 0x00804201, 0x00804201, 0x03808e02, 0x03808e02}, + {0x0000a618, 0x0280c802, 0x0280c802, 0x0280c802, 0x0280c802, 0x0280c802, 0x0280c802, 0x0300c000, 0x0300c000}, + {0x0000a61c, 0x0280ca03, 0x0280ca03, 0x0280ca03, 0x0280ca03, 0x0280ca03, 0x0280ca03, 0x03808e02, 0x03808e02}, + {0x0000a620, 0x04c15104, 0x04c15104, 0x04c15104, 0x04c15104, 0x04c15104, 0x04c15104, 0x03410c03, 0x03410c03}, + {0x0000a624, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x04014c03, 0x04014c03}, + {0x0000a628, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x05818d04, 0x05818d04}, + {0x0000a62c, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x0801cd04, 0x0801cd04}, + {0x0000a630, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x0801e007, 0x0801e007}, + {0x0000a634, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x0801e007, 0x0801e007}, + {0x0000a638, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x0801e007, 0x0801e007}, + {0x0000a63c, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x04c15305, 0x0801e007, 0x0801e007}, + {0x0000b2dc, 0x01feee00, 0x01feee00, 0x01feee00, 0x01feee00, 0x01feee00, 0x01feee00, 0xfffe5aaa, 0xfffe5aaa}, + {0x0000b2e0, 0x0000f000, 0x0000f000, 0x0000f000, 0x0000f000, 0x0000f000, 0x0000f000, 0xfffe9ccc, 0xfffe9ccc}, + {0x0000b2e4, 0x01ff0000, 0x01ff0000, 0x01ff0000, 0x01ff0000, 0x01ff0000, 0x01ff0000, 0xffffe0f0, 0xffffe0f0}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffef00, 0xffffef00}, + {0x0000c2dc, 0x01feee00, 0x01feee00, 0x01feee00, 0x01feee00, 0x01feee00, 0x01feee00, 0xfffe5aaa, 0xfffe5aaa}, + {0x0000c2e0, 0x0000f000, 0x0000f000, 0x0000f000, 0x0000f000, 0x0000f000, 0x0000f000, 0xfffe9ccc, 0xfffe9ccc}, + {0x0000c2e4, 0x01ff0000, 0x01ff0000, 0x01ff0000, 0x01ff0000, 0x01ff0000, 0x01ff0000, 0xffffe0f0, 0xffffe0f0}, + {0x0000c2e8, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffef00, 0xffffef00}, + {0x00016044, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x054922d4, 0x054922d4}, + {0x00016048, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401}, + {0x00016444, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x054922d4, 0x054922d4}, + {0x00016448, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401}, + {0x00016844, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x056db2d4, 0x054922d4, 0x054922d4}, + {0x00016848, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401, 0x66482401}, +}; + +static const u32 ar955x_1p0_soc_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00007010, 0x00000023, 0x00000023, 0x00000023, 0x00000023}, +}; + +static const u32 ar955x_1p0_modes_fast_clock[][3] = { + /* Addr 5G_HT20 5G_HT40 */ + {0x00001030, 0x00000268, 0x000004d0}, + {0x00001070, 0x0000018c, 0x00000318}, + {0x000010b0, 0x00000fd0, 0x00001fa0}, + {0x00008014, 0x044c044c, 0x08980898}, + {0x0000801c, 0x148ec02b, 0x148ec057}, + {0x00008318, 0x000044c0, 0x00008980}, + {0x00009e00, 0x0372131c, 0x0372131c}, + {0x0000a230, 0x0000400b, 0x00004016}, + {0x0000a254, 0x00000898, 0x00001130}, +}; + +#endif /* INITVALS_955X_1P0_H */ diff --git a/drivers/net/wireless/ath/ath9k/ar9565_1p0_initvals.h b/drivers/net/wireless/ath/ath9k/ar9565_1p0_initvals.h new file mode 100644 index 0000000..e85a8b0 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9565_1p0_initvals.h @@ -0,0 +1,1233 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef INITVALS_9565_1P0_H +#define INITVALS_9565_1P0_H + +/* AR9565 1.0 */ + +static const u32 ar9565_1p0_mac_core[][2] = { + /* Addr allmodes */ + {0x00000008, 0x00000000}, + {0x00000030, 0x000a0085}, + {0x00000034, 0x00000005}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000000}, + {0x00000048, 0x00000008}, + {0x0000004c, 0x00000010}, + {0x00000050, 0x00000000}, + {0x00001040, 0x002ffc0f}, + {0x00001044, 0x002ffc0f}, + {0x00001048, 0x002ffc0f}, + {0x0000104c, 0x002ffc0f}, + {0x00001050, 0x002ffc0f}, + {0x00001054, 0x002ffc0f}, + {0x00001058, 0x002ffc0f}, + {0x0000105c, 0x002ffc0f}, + {0x00001060, 0x002ffc0f}, + {0x00001064, 0x002ffc0f}, + {0x000010f0, 0x00000100}, + {0x00001270, 0x00000000}, + {0x000012b0, 0x00000000}, + {0x000012f0, 0x00000000}, + {0x0000143c, 0x00000000}, + {0x0000147c, 0x00000000}, + {0x00001810, 0x0f000003}, + {0x00008000, 0x00000000}, + {0x00008004, 0x00000000}, + {0x00008008, 0x00000000}, + {0x0000800c, 0x00000000}, + {0x00008018, 0x00000000}, + {0x00008020, 0x00000000}, + {0x00008038, 0x00000000}, + {0x0000803c, 0x00000000}, + {0x00008040, 0x00000000}, + {0x00008044, 0x00000000}, + {0x00008048, 0x00000000}, + {0x00008054, 0x00000000}, + {0x00008058, 0x00000000}, + {0x0000805c, 0x000fc78f}, + {0x00008060, 0x0000000f}, + {0x00008064, 0x00000000}, + {0x00008070, 0x00000310}, + {0x00008074, 0x00000020}, + {0x00008078, 0x00000000}, + {0x0000809c, 0x0000000f}, + {0x000080a0, 0x00000000}, + {0x000080a4, 0x02ff0000}, + {0x000080a8, 0x0e070605}, + {0x000080ac, 0x0000000d}, + {0x000080b0, 0x00000000}, + {0x000080b4, 0x00000000}, + {0x000080b8, 0x00000000}, + {0x000080bc, 0x00000000}, + {0x000080c0, 0x2a800000}, + {0x000080c4, 0x06900168}, + {0x000080c8, 0x13881c20}, + {0x000080cc, 0x01f40000}, + {0x000080d0, 0x00252500}, + {0x000080d4, 0x00b00005}, + {0x000080d8, 0x00400002}, + {0x000080dc, 0x00000000}, + {0x000080e0, 0xffffffff}, + {0x000080e4, 0x0000ffff}, + {0x000080e8, 0x3f3f3f3f}, + {0x000080ec, 0x00000000}, + {0x000080f0, 0x00000000}, + {0x000080f4, 0x00000000}, + {0x000080fc, 0x00020000}, + {0x00008100, 0x00000000}, + {0x00008108, 0x00000052}, + {0x0000810c, 0x00000000}, + {0x00008110, 0x00000000}, + {0x00008114, 0x000007ff}, + {0x00008118, 0x000000aa}, + {0x0000811c, 0x00003210}, + {0x00008124, 0x00000000}, + {0x00008128, 0x00000000}, + {0x0000812c, 0x00000000}, + {0x00008130, 0x00000000}, + {0x00008134, 0x00000000}, + {0x00008138, 0x00000000}, + {0x0000813c, 0x0000ffff}, + {0x00008144, 0xffffffff}, + {0x00008168, 0x00000000}, + {0x0000816c, 0x00000000}, + {0x00008170, 0x18486200}, + {0x00008174, 0x33332210}, + {0x00008178, 0x00000000}, + {0x0000817c, 0x00020000}, + {0x000081c4, 0x33332210}, + {0x000081c8, 0x00000000}, + {0x000081cc, 0x00000000}, + {0x000081d4, 0x00000000}, + {0x000081ec, 0x00000000}, + {0x000081f0, 0x00000000}, + {0x000081f4, 0x00000000}, + {0x000081f8, 0x00000000}, + {0x000081fc, 0x00000000}, + {0x00008240, 0x00100000}, + {0x00008244, 0x0010f424}, + {0x00008248, 0x00000800}, + {0x0000824c, 0x0001e848}, + {0x00008250, 0x00000000}, + {0x00008254, 0x00000000}, + {0x00008258, 0x00000000}, + {0x0000825c, 0x40000000}, + {0x00008260, 0x00080922}, + {0x00008264, 0x9d400010}, + {0x00008268, 0xffffffff}, + {0x0000826c, 0x0000ffff}, + {0x00008270, 0x00000000}, + {0x00008274, 0x40000000}, + {0x00008278, 0x003e4180}, + {0x0000827c, 0x00000004}, + {0x00008284, 0x0000002c}, + {0x00008288, 0x0000002c}, + {0x0000828c, 0x000000ff}, + {0x00008294, 0x00000000}, + {0x00008298, 0x00000000}, + {0x0000829c, 0x00000000}, + {0x00008300, 0x00000140}, + {0x00008314, 0x00000000}, + {0x0000831c, 0x0000010d}, + {0x00008328, 0x00000000}, + {0x0000832c, 0x0000001f}, + {0x00008330, 0x00000302}, + {0x00008334, 0x00000700}, + {0x00008338, 0xffff0000}, + {0x0000833c, 0x02400000}, + {0x00008340, 0x000107ff}, + {0x00008344, 0xaa48105b}, + {0x00008348, 0x008f0000}, + {0x0000835c, 0x00000000}, + {0x00008360, 0xffffffff}, + {0x00008364, 0xffffffff}, + {0x00008368, 0x00000000}, + {0x00008370, 0x00000000}, + {0x00008374, 0x000000ff}, + {0x00008378, 0x00000000}, + {0x0000837c, 0x00000000}, + {0x00008380, 0xffffffff}, + {0x00008384, 0xffffffff}, + {0x00008390, 0xffffffff}, + {0x00008394, 0xffffffff}, + {0x00008398, 0x00000000}, + {0x0000839c, 0x00000000}, + {0x000083a4, 0x0000fa14}, + {0x000083a8, 0x000f0c00}, + {0x000083ac, 0x33332210}, + {0x000083b0, 0x33332210}, + {0x000083b4, 0x33332210}, + {0x000083b8, 0x33332210}, + {0x000083bc, 0x00000000}, + {0x000083c0, 0x00000000}, + {0x000083c4, 0x00000000}, + {0x000083c8, 0x00000000}, + {0x000083cc, 0x00000200}, + {0x000083d0, 0x800301ff}, +}; + +static const u32 ar9565_1p0_mac_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00001030, 0x00000230, 0x00000460, 0x000002c0, 0x00000160}, + {0x00001070, 0x00000168, 0x000002d0, 0x00000318, 0x0000018c}, + {0x000010b0, 0x00000e60, 0x00001cc0, 0x00007c70, 0x00003e38}, + {0x00008014, 0x03e803e8, 0x07d007d0, 0x10801600, 0x08400b00}, + {0x0000801c, 0x128d8027, 0x128d804f, 0x12e00057, 0x12e0002b}, + {0x00008120, 0x08f04800, 0x08f04800, 0x08f04810, 0x08f04810}, + {0x000081d0, 0x00003210, 0x00003210, 0x0000320a, 0x0000320a}, + {0x00008318, 0x00003e80, 0x00007d00, 0x00006880, 0x00003440}, +}; + +static const u32 ar9565_1p0_baseband_core[][2] = { + /* Addr allmodes */ + {0x00009800, 0xafe68e30}, + {0x00009804, 0xfd14e000}, + {0x00009808, 0x9c0a8f6b}, + {0x0000980c, 0x04800000}, + {0x00009814, 0x9280c00a}, + {0x00009818, 0x00000000}, + {0x0000981c, 0x00020028}, + {0x00009834, 0x6400a290}, + {0x00009838, 0x0108ecff}, + {0x0000983c, 0x0d000600}, + {0x00009880, 0x201fff00}, + {0x00009884, 0x00001042}, + {0x000098a4, 0x00200400}, + {0x000098b0, 0x32840bbe}, + {0x000098d0, 0x004b6a8e}, + {0x000098d4, 0x00000820}, + {0x000098dc, 0x00000000}, + {0x000098e4, 0x01ffffff}, + {0x000098e8, 0x01ffffff}, + {0x000098ec, 0x01ffffff}, + {0x000098f0, 0x00000000}, + {0x000098f4, 0x00000000}, + {0x00009bf0, 0x80000000}, + {0x00009c04, 0xff55ff55}, + {0x00009c08, 0x0320ff55}, + {0x00009c0c, 0x00000000}, + {0x00009c10, 0x00000000}, + {0x00009c14, 0x00046384}, + {0x00009c18, 0x05b6b440}, + {0x00009c1c, 0x00b6b440}, + {0x00009d00, 0xc080a333}, + {0x00009d04, 0x40206c10}, + {0x00009d08, 0x009c4060}, + {0x00009d0c, 0x1883800a}, + {0x00009d10, 0x01834061}, + {0x00009d14, 0x00c00400}, + {0x00009d18, 0x00000000}, + {0x00009e08, 0x0038230c}, + {0x00009e24, 0x9907b515}, + {0x00009e28, 0x126f0600}, + {0x00009e30, 0x06336f77}, + {0x00009e34, 0x6af6532f}, + {0x00009e38, 0x0cc80c00}, + {0x00009e40, 0x0d261820}, + {0x00009e4c, 0x00001004}, + {0x00009e50, 0x00ff03f1}, + {0x00009e54, 0xe4c355c7}, + {0x00009e5c, 0xe9198724}, + {0x00009fc0, 0x823e4fc8}, + {0x00009fc4, 0x0001efb5}, + {0x00009fcc, 0x40000014}, + {0x0000a20c, 0x00000000}, + {0x0000a220, 0x00000000}, + {0x0000a224, 0x00000000}, + {0x0000a228, 0x10002310}, + {0x0000a23c, 0x00000000}, + {0x0000a244, 0x0c000000}, + {0x0000a2a0, 0x00000001}, + {0x0000a2c0, 0x00000001}, + {0x0000a2c8, 0x00000000}, + {0x0000a2cc, 0x18c43433}, + {0x0000a2d4, 0x00000000}, + {0x0000a2ec, 0x00000000}, + {0x0000a2f0, 0x00000000}, + {0x0000a2f4, 0x00000000}, + {0x0000a2f8, 0x00000000}, + {0x0000a344, 0x00000000}, + {0x0000a34c, 0x00000000}, + {0x0000a350, 0x0000a000}, + {0x0000a364, 0x00000000}, + {0x0000a370, 0x00000000}, + {0x0000a390, 0x00000001}, + {0x0000a394, 0x00000444}, + {0x0000a398, 0x001f0e0f}, + {0x0000a39c, 0x0075393f}, + {0x0000a3a0, 0xb79f6427}, + {0x0000a3a4, 0x00000000}, + {0x0000a3a8, 0xaaaaaaaa}, + {0x0000a3ac, 0x3c466478}, + {0x0000a3c0, 0x20202020}, + {0x0000a3c4, 0x22222220}, + {0x0000a3c8, 0x20200020}, + {0x0000a3cc, 0x20202020}, + {0x0000a3d0, 0x20202020}, + {0x0000a3d4, 0x20202020}, + {0x0000a3d8, 0x20202020}, + {0x0000a3dc, 0x20202020}, + {0x0000a3e0, 0x20202020}, + {0x0000a3e4, 0x20202020}, + {0x0000a3e8, 0x20202020}, + {0x0000a3ec, 0x20202020}, + {0x0000a3f0, 0x00000000}, + {0x0000a3f4, 0x00000006}, + {0x0000a3f8, 0x0c9bd380}, + {0x0000a3fc, 0x000f0f01}, + {0x0000a400, 0x8fa91f01}, + {0x0000a404, 0x00000000}, + {0x0000a408, 0x0e79e5c6}, + {0x0000a40c, 0x00820820}, + {0x0000a414, 0x1ce739ce}, + {0x0000a418, 0x2d001dce}, + {0x0000a41c, 0x1ce739ce}, + {0x0000a420, 0x000001ce}, + {0x0000a424, 0x1ce739ce}, + {0x0000a428, 0x000001ce}, + {0x0000a42c, 0x1ce739ce}, + {0x0000a430, 0x1ce739ce}, + {0x0000a434, 0x00000000}, + {0x0000a438, 0x00001801}, + {0x0000a43c, 0x00000000}, + {0x0000a440, 0x00000000}, + {0x0000a444, 0x00000000}, + {0x0000a448, 0x05000096}, + {0x0000a44c, 0x00000001}, + {0x0000a450, 0x00010000}, + {0x0000a454, 0x03000000}, + {0x0000a458, 0x00000000}, + {0x0000a644, 0xbfad9d74}, + {0x0000a648, 0x0048060a}, + {0x0000a64c, 0x00003c37}, + {0x0000a670, 0x03020100}, + {0x0000a674, 0x09080504}, + {0x0000a678, 0x0d0c0b0a}, + {0x0000a67c, 0x13121110}, + {0x0000a680, 0x31301514}, + {0x0000a684, 0x35343332}, + {0x0000a688, 0x00000036}, + {0x0000a690, 0x00000838}, + {0x0000a6b4, 0x00512c01}, + {0x0000a7c0, 0x00000000}, + {0x0000a7c4, 0xfffffffc}, + {0x0000a7c8, 0x00000000}, + {0x0000a7cc, 0x00000000}, + {0x0000a7d0, 0x00000000}, + {0x0000a7d4, 0x00000004}, + {0x0000a7dc, 0x00000001}, + {0x0000a7f0, 0x80000000}, +}; + +static const u32 ar9565_1p0_baseband_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009810, 0xd00a8005, 0xd00a8005, 0xd00a8005, 0xd00a8009}, + {0x00009820, 0x206a022e, 0x206a022e, 0x206a012e, 0x206a01ae}, + {0x00009824, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0, 0x63c640da}, + {0x00009828, 0x06903081, 0x06903081, 0x06903881, 0x09143c81}, + {0x0000982c, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4}, + {0x00009830, 0x0000059c, 0x0000059c, 0x0000059c, 0x0000059c}, + {0x00009c00, 0x000000c4, 0x000000c4, 0x000000c4, 0x000000c4}, + {0x00009e00, 0x0372111a, 0x0372111a, 0x037216a0, 0x037216a0}, + {0x00009e04, 0x00802020, 0x00802020, 0x00142020, 0x00142020}, + {0x00009e0c, 0x6c4000e2, 0x6d4000e2, 0x6d4000e2, 0x6c4000e2}, + {0x00009e10, 0x7ec88d2e, 0x7ec88d2e, 0x7ec84d2e, 0x7ec84d2e}, + {0x00009e14, 0x37b95d5e, 0x37b9605e, 0x3379605e, 0x33795d5e}, + {0x00009e18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00009e1c, 0x0001cf9c, 0x0001cf9c, 0x00021f9c, 0x00021f9c}, + {0x00009e20, 0x000003b5, 0x000003b5, 0x000003ce, 0x000003ce}, + {0x00009e2c, 0x0000001c, 0x0000001c, 0x00000021, 0x00000021}, + {0x00009e3c, 0xcf946222, 0xcf946222, 0xcf946222, 0xcf946222}, + {0x00009e44, 0xfe321e27, 0xfe321e27, 0xfe291e27, 0xfe291e27}, + {0x00009e48, 0x5030201a, 0x5030201a, 0x50302012, 0x50302012}, + {0x00009fc8, 0x0003f000, 0x0003f000, 0x0001a000, 0x0001a000}, + {0x0000a204, 0x07318fc0, 0x07318fc4, 0x07318fc4, 0x07318fc0}, + {0x0000a208, 0x00000104, 0x00000104, 0x00000004, 0x00000004}, + {0x0000a22c, 0x01026a2f, 0x01026a27, 0x01026a2f, 0x01026a2f}, + {0x0000a230, 0x0000400a, 0x00004014, 0x00004016, 0x0000400b}, + {0x0000a234, 0x00000fff, 0x10000fff, 0x10000fff, 0x00000fff}, + {0x0000a238, 0xffb81018, 0xffb81018, 0xffb81018, 0xffb81018}, + {0x0000a250, 0x00000000, 0x00000000, 0x00000210, 0x00000108}, + {0x0000a254, 0x000007d0, 0x00000fa0, 0x00001130, 0x00000898}, + {0x0000a258, 0x02020002, 0x02020002, 0x02020002, 0x02020002}, + {0x0000a25c, 0x01000e0e, 0x01000e0e, 0x01000e0e, 0x01000e0e}, + {0x0000a260, 0x0a021501, 0x0a021501, 0x3a021501, 0x3a021501}, + {0x0000a264, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x0000a280, 0x00000007, 0x00000007, 0x0000000b, 0x0000000b}, + {0x0000a284, 0x00000000, 0x00000000, 0x00000150, 0x00000150}, + {0x0000a288, 0x00100510, 0x00100510, 0x00100510, 0x00100510}, + {0x0000a28c, 0x00021551, 0x00021551, 0x00021551, 0x00021551}, + {0x0000a2c4, 0x00058d18, 0x00058d18, 0x00058d18, 0x00058d18}, + {0x0000a2d0, 0x00071982, 0x00071982, 0x00071982, 0x00071982}, + {0x0000a2d8, 0x7999a83b, 0x7999a83b, 0x7999a83b, 0x7999a83b}, + {0x0000a358, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000ae04, 0x00800000, 0x00800000, 0x00800000, 0x00800000}, + {0x0000ae18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, +}; + +static const u32 ar9565_1p0_radio_core[][2] = { + /* Addr allmodes */ + {0x00016000, 0x36db6db6}, + {0x00016004, 0x6db6db40}, + {0x00016008, 0x73f00000}, + {0x0001600c, 0x00000000}, + {0x00016010, 0x6d823601}, + {0x00016040, 0x7f80fff8}, + {0x0001604c, 0x1c99e04f}, + {0x00016050, 0x6db6db6c}, + {0x00016058, 0x6c200000}, + {0x00016080, 0x000c0000}, + {0x00016084, 0x9a68048c}, + {0x00016088, 0x54214514}, + {0x0001608c, 0x1203040b}, + {0x00016090, 0x24926490}, + {0x00016098, 0xd28b3330}, + {0x000160a0, 0x0a108ffe}, + {0x000160a4, 0x812fc491}, + {0x000160a8, 0x423c8000}, + {0x000160b4, 0x92000000}, + {0x000160b8, 0x0285dddc}, + {0x000160bc, 0x02908888}, + {0x000160c0, 0x006db6d0}, + {0x000160c4, 0x6dd6db60}, + {0x000160c8, 0x6db6db6c}, + {0x000160cc, 0x6de6c1b0}, + {0x00016100, 0x3fffbe04}, + {0x00016104, 0xfff80000}, + {0x00016108, 0x00200400}, + {0x00016110, 0x00000000}, + {0x00016144, 0x02084080}, + {0x00016148, 0x000080c0}, + {0x00016280, 0x050a0001}, + {0x00016284, 0x3d841440}, + {0x00016288, 0x00000000}, + {0x0001628c, 0xe3000000}, + {0x00016290, 0xa1004080}, + {0x00016294, 0x40000028}, + {0x00016298, 0x55aa2900}, + {0x00016340, 0x131c827a}, + {0x00016344, 0x00300000}, +}; + +static const u32 ar9565_1p0_radio_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0001609c, 0x0b8ee524, 0x0b8ee524, 0x0b8ee524, 0x0b8ee524}, + {0x000160ac, 0xa4646c08, 0xa4646c08, 0xa4646c08, 0xa4646c08}, + {0x000160b0, 0x01d67f70, 0x01d67f70, 0x01d67f70, 0x01d67f70}, + {0x0001610c, 0x40000000, 0x40000000, 0x40000000, 0x40000000}, + {0x00016140, 0x10804008, 0x10804008, 0x50804008, 0x50804008}, +}; + +static const u32 ar9565_1p0_soc_preamble[][2] = { + /* Addr allmodes */ + {0x00004078, 0x00000002}, + {0x000040a4, 0x00a0c9c9}, + {0x00007020, 0x00000000}, + {0x00007034, 0x00000002}, + {0x00007038, 0x000004c2}, +}; + +static const u32 ar9565_1p0_soc_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00007010, 0x00002233, 0x00002233, 0x00002233, 0x00002233}, +}; + +static const u32 ar9565_1p0_Common_rx_gain_table[][2] = { + /* Addr allmodes */ + {0x00004050, 0x00300300}, + {0x0000406c, 0x00100000}, + {0x0000a000, 0x00010000}, + {0x0000a004, 0x00030002}, + {0x0000a008, 0x00050004}, + {0x0000a00c, 0x00810080}, + {0x0000a010, 0x00830082}, + {0x0000a014, 0x01810180}, + {0x0000a018, 0x01830182}, + {0x0000a01c, 0x01850184}, + {0x0000a020, 0x01890188}, + {0x0000a024, 0x018b018a}, + {0x0000a028, 0x018d018c}, + {0x0000a02c, 0x01910190}, + {0x0000a030, 0x01930192}, + {0x0000a034, 0x01950194}, + {0x0000a038, 0x038a0196}, + {0x0000a03c, 0x038c038b}, + {0x0000a040, 0x0390038d}, + {0x0000a044, 0x03920391}, + {0x0000a048, 0x03940393}, + {0x0000a04c, 0x03960395}, + {0x0000a050, 0x00000000}, + {0x0000a054, 0x00000000}, + {0x0000a058, 0x00000000}, + {0x0000a05c, 0x00000000}, + {0x0000a060, 0x00000000}, + {0x0000a064, 0x00000000}, + {0x0000a068, 0x00000000}, + {0x0000a06c, 0x00000000}, + {0x0000a070, 0x00000000}, + {0x0000a074, 0x00000000}, + {0x0000a078, 0x00000000}, + {0x0000a07c, 0x00000000}, + {0x0000a080, 0x22222229}, + {0x0000a084, 0x1d1d1d1d}, + {0x0000a088, 0x1d1d1d1d}, + {0x0000a08c, 0x1d1d1d1d}, + {0x0000a090, 0x171d1d1d}, + {0x0000a094, 0x11111717}, + {0x0000a098, 0x00030311}, + {0x0000a09c, 0x00000000}, + {0x0000a0a0, 0x00000000}, + {0x0000a0a4, 0x00000000}, + {0x0000a0a8, 0x00000000}, + {0x0000a0ac, 0x00000000}, + {0x0000a0b0, 0x00000000}, + {0x0000a0b4, 0x00000000}, + {0x0000a0b8, 0x00000000}, + {0x0000a0bc, 0x00000000}, + {0x0000a0c0, 0x00bf00a0}, + {0x0000a0c4, 0x11a011a1}, + {0x0000a0c8, 0x11be11bf}, + {0x0000a0cc, 0x11bc11bd}, + {0x0000a0d0, 0x22632264}, + {0x0000a0d4, 0x22612262}, + {0x0000a0d8, 0x227f2260}, + {0x0000a0dc, 0x4322227e}, + {0x0000a0e0, 0x43204321}, + {0x0000a0e4, 0x433e433f}, + {0x0000a0e8, 0x4462433d}, + {0x0000a0ec, 0x44604461}, + {0x0000a0f0, 0x447e447f}, + {0x0000a0f4, 0x5582447d}, + {0x0000a0f8, 0x55805581}, + {0x0000a0fc, 0x559e559f}, + {0x0000a100, 0x66816682}, + {0x0000a104, 0x669f6680}, + {0x0000a108, 0x669d669e}, + {0x0000a10c, 0x77627763}, + {0x0000a110, 0x77607761}, + {0x0000a114, 0x00000000}, + {0x0000a118, 0x00000000}, + {0x0000a11c, 0x00000000}, + {0x0000a120, 0x00000000}, + {0x0000a124, 0x00000000}, + {0x0000a128, 0x00000000}, + {0x0000a12c, 0x00000000}, + {0x0000a130, 0x00000000}, + {0x0000a134, 0x00000000}, + {0x0000a138, 0x00000000}, + {0x0000a13c, 0x00000000}, + {0x0000a140, 0x00bf00a0}, + {0x0000a144, 0x11a011a1}, + {0x0000a148, 0x11be11bf}, + {0x0000a14c, 0x11bc11bd}, + {0x0000a150, 0x22632264}, + {0x0000a154, 0x22612262}, + {0x0000a158, 0x227f2260}, + {0x0000a15c, 0x4322227e}, + {0x0000a160, 0x43204321}, + {0x0000a164, 0x433e433f}, + {0x0000a168, 0x4462433d}, + {0x0000a16c, 0x44604461}, + {0x0000a170, 0x447e447f}, + {0x0000a174, 0x5582447d}, + {0x0000a178, 0x55805581}, + {0x0000a17c, 0x559e559f}, + {0x0000a180, 0x66816682}, + {0x0000a184, 0x669f6680}, + {0x0000a188, 0x669d669e}, + {0x0000a18c, 0x77e677e7}, + {0x0000a190, 0x77e477e5}, + {0x0000a194, 0x00000000}, + {0x0000a198, 0x00000000}, + {0x0000a19c, 0x00000000}, + {0x0000a1a0, 0x00000000}, + {0x0000a1a4, 0x00000000}, + {0x0000a1a8, 0x00000000}, + {0x0000a1ac, 0x00000000}, + {0x0000a1b0, 0x00000000}, + {0x0000a1b4, 0x00000000}, + {0x0000a1b8, 0x00000000}, + {0x0000a1bc, 0x00000000}, + {0x0000a1c0, 0x00000000}, + {0x0000a1c4, 0x00000000}, + {0x0000a1c8, 0x00000000}, + {0x0000a1cc, 0x00000000}, + {0x0000a1d0, 0x00000000}, + {0x0000a1d4, 0x00000000}, + {0x0000a1d8, 0x00000000}, + {0x0000a1dc, 0x00000000}, + {0x0000a1e0, 0x00000000}, + {0x0000a1e4, 0x00000000}, + {0x0000a1e8, 0x00000000}, + {0x0000a1ec, 0x00000000}, + {0x0000a1f0, 0x00000396}, + {0x0000a1f4, 0x00000396}, + {0x0000a1f8, 0x00000396}, + {0x0000a1fc, 0x00000196}, + {0x0000b000, 0x00010000}, + {0x0000b004, 0x00030002}, + {0x0000b008, 0x00050004}, + {0x0000b00c, 0x00810080}, + {0x0000b010, 0x00830082}, + {0x0000b014, 0x01810180}, + {0x0000b018, 0x01830182}, + {0x0000b01c, 0x01850184}, + {0x0000b020, 0x02810280}, + {0x0000b024, 0x02830282}, + {0x0000b028, 0x02850284}, + {0x0000b02c, 0x02890288}, + {0x0000b030, 0x028b028a}, + {0x0000b034, 0x0388028c}, + {0x0000b038, 0x038a0389}, + {0x0000b03c, 0x038c038b}, + {0x0000b040, 0x0390038d}, + {0x0000b044, 0x03920391}, + {0x0000b048, 0x03940393}, + {0x0000b04c, 0x03960395}, + {0x0000b050, 0x00000000}, + {0x0000b054, 0x00000000}, + {0x0000b058, 0x00000000}, + {0x0000b05c, 0x00000000}, + {0x0000b060, 0x00000000}, + {0x0000b064, 0x00000000}, + {0x0000b068, 0x00000000}, + {0x0000b06c, 0x00000000}, + {0x0000b070, 0x00000000}, + {0x0000b074, 0x00000000}, + {0x0000b078, 0x00000000}, + {0x0000b07c, 0x00000000}, + {0x0000b080, 0x32323232}, + {0x0000b084, 0x2f2f3232}, + {0x0000b088, 0x23282a2d}, + {0x0000b08c, 0x1c1e2123}, + {0x0000b090, 0x14171919}, + {0x0000b094, 0x0e0e1214}, + {0x0000b098, 0x03050707}, + {0x0000b09c, 0x00030303}, + {0x0000b0a0, 0x00000000}, + {0x0000b0a4, 0x00000000}, + {0x0000b0a8, 0x00000000}, + {0x0000b0ac, 0x00000000}, + {0x0000b0b0, 0x00000000}, + {0x0000b0b4, 0x00000000}, + {0x0000b0b8, 0x00000000}, + {0x0000b0bc, 0x00000000}, + {0x0000b0c0, 0x003f0020}, + {0x0000b0c4, 0x00400041}, + {0x0000b0c8, 0x0140005f}, + {0x0000b0cc, 0x0160015f}, + {0x0000b0d0, 0x017e017f}, + {0x0000b0d4, 0x02410242}, + {0x0000b0d8, 0x025f0240}, + {0x0000b0dc, 0x027f0260}, + {0x0000b0e0, 0x0341027e}, + {0x0000b0e4, 0x035f0340}, + {0x0000b0e8, 0x037f0360}, + {0x0000b0ec, 0x04400441}, + {0x0000b0f0, 0x0460045f}, + {0x0000b0f4, 0x0541047f}, + {0x0000b0f8, 0x055f0540}, + {0x0000b0fc, 0x057f0560}, + {0x0000b100, 0x06400641}, + {0x0000b104, 0x0660065f}, + {0x0000b108, 0x067e067f}, + {0x0000b10c, 0x07410742}, + {0x0000b110, 0x075f0740}, + {0x0000b114, 0x077f0760}, + {0x0000b118, 0x07800781}, + {0x0000b11c, 0x07a0079f}, + {0x0000b120, 0x07c107bf}, + {0x0000b124, 0x000007c0}, + {0x0000b128, 0x00000000}, + {0x0000b12c, 0x00000000}, + {0x0000b130, 0x00000000}, + {0x0000b134, 0x00000000}, + {0x0000b138, 0x00000000}, + {0x0000b13c, 0x00000000}, + {0x0000b140, 0x003f0020}, + {0x0000b144, 0x00400041}, + {0x0000b148, 0x0140005f}, + {0x0000b14c, 0x0160015f}, + {0x0000b150, 0x017e017f}, + {0x0000b154, 0x02410242}, + {0x0000b158, 0x025f0240}, + {0x0000b15c, 0x027f0260}, + {0x0000b160, 0x0341027e}, + {0x0000b164, 0x035f0340}, + {0x0000b168, 0x037f0360}, + {0x0000b16c, 0x04400441}, + {0x0000b170, 0x0460045f}, + {0x0000b174, 0x0541047f}, + {0x0000b178, 0x055f0540}, + {0x0000b17c, 0x057f0560}, + {0x0000b180, 0x06400641}, + {0x0000b184, 0x0660065f}, + {0x0000b188, 0x067e067f}, + {0x0000b18c, 0x07410742}, + {0x0000b190, 0x075f0740}, + {0x0000b194, 0x077f0760}, + {0x0000b198, 0x07800781}, + {0x0000b19c, 0x07a0079f}, + {0x0000b1a0, 0x07c107bf}, + {0x0000b1a4, 0x000007c0}, + {0x0000b1a8, 0x00000000}, + {0x0000b1ac, 0x00000000}, + {0x0000b1b0, 0x00000000}, + {0x0000b1b4, 0x00000000}, + {0x0000b1b8, 0x00000000}, + {0x0000b1bc, 0x00000000}, + {0x0000b1c0, 0x00000000}, + {0x0000b1c4, 0x00000000}, + {0x0000b1c8, 0x00000000}, + {0x0000b1cc, 0x00000000}, + {0x0000b1d0, 0x00000000}, + {0x0000b1d4, 0x00000000}, + {0x0000b1d8, 0x00000000}, + {0x0000b1dc, 0x00000000}, + {0x0000b1e0, 0x00000000}, + {0x0000b1e4, 0x00000000}, + {0x0000b1e8, 0x00000000}, + {0x0000b1ec, 0x00000000}, + {0x0000b1f0, 0x00000396}, + {0x0000b1f4, 0x00000396}, + {0x0000b1f8, 0x00000396}, + {0x0000b1fc, 0x00000196}, +}; + +static const u32 ar9565_1p0_Modes_lowest_ob_db_tx_gain_table[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0xfc0a9380, 0xfc0a9380, 0xfdab5b52, 0xfdab5b52}, + {0x0000a2e0, 0xffecec00, 0xffecec00, 0xfd339c84, 0xfd339c84}, + {0x0000a2e4, 0xfc0f0000, 0xfc0f0000, 0xfec3e000, 0xfec3e000}, + {0x0000a2e8, 0xfc100000, 0xfc100000, 0xfffc0000, 0xfffc0000}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a504, 0x06000003, 0x06000003, 0x04000002, 0x04000002}, + {0x0000a508, 0x0a000020, 0x0a000020, 0x08000004, 0x08000004}, + {0x0000a50c, 0x10000023, 0x10000023, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x16000220, 0x16000220, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x1c000223, 0x1c000223, 0x12000400, 0x12000400}, + {0x0000a518, 0x21020220, 0x21020220, 0x16000402, 0x16000402}, + {0x0000a51c, 0x27020223, 0x27020223, 0x19000404, 0x19000404}, + {0x0000a520, 0x2b022220, 0x2b022220, 0x1c000603, 0x1c000603}, + {0x0000a524, 0x2f022222, 0x2f022222, 0x21000a02, 0x21000a02}, + {0x0000a528, 0x34022225, 0x34022225, 0x25000a04, 0x25000a04}, + {0x0000a52c, 0x3a02222a, 0x3a02222a, 0x28000a20, 0x28000a20}, + {0x0000a530, 0x3e02222c, 0x3e02222c, 0x2c000e20, 0x2c000e20}, + {0x0000a534, 0x4202242a, 0x4202242a, 0x30000e22, 0x30000e22}, + {0x0000a538, 0x4702244a, 0x4702244a, 0x34000e24, 0x34000e24}, + {0x0000a53c, 0x4b02244c, 0x4b02244c, 0x38001640, 0x38001640}, + {0x0000a540, 0x4e02246c, 0x4e02246c, 0x3c001660, 0x3c001660}, + {0x0000a544, 0x5302266c, 0x5302266c, 0x3f001861, 0x3f001861}, + {0x0000a548, 0x5702286c, 0x5702286c, 0x43001a81, 0x43001a81}, + {0x0000a54c, 0x5c04286b, 0x5c04286b, 0x47001a83, 0x47001a83}, + {0x0000a550, 0x61042a6c, 0x61042a6c, 0x4a001c84, 0x4a001c84}, + {0x0000a554, 0x66062a6c, 0x66062a6c, 0x4e001ce3, 0x4e001ce3}, + {0x0000a558, 0x6b062e6c, 0x6b062e6c, 0x52001ce5, 0x52001ce5}, + {0x0000a55c, 0x7006308c, 0x7006308c, 0x56001ce9, 0x56001ce9}, + {0x0000a560, 0x730a308a, 0x730a308a, 0x5a001ceb, 0x5a001ceb}, + {0x0000a564, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a568, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a56c, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a570, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a574, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a578, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a57c, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a618, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a61c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a620, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a624, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a628, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a62c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a630, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a634, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a638, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a63c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00016044, 0x012482d4, 0x012482d4, 0x012482d4, 0x012482d4}, + {0x00016048, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00016054, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, +}; + +static const u32 ar9565_1p0_pciephy_clkreq_disable_L1[][2] = { + /* Addr allmodes */ + {0x00018c00, 0x18212ede}, + {0x00018c04, 0x000801d8}, + {0x00018c08, 0x0003780c}, +}; + +static const u32 ar9565_1p0_modes_fast_clock[][3] = { + /* Addr 5G_HT20 5G_HT40 */ + {0x00001030, 0x00000268, 0x000004d0}, + {0x00001070, 0x0000018c, 0x00000318}, + {0x000010b0, 0x00000fd0, 0x00001fa0}, + {0x00008014, 0x044c044c, 0x08980898}, + {0x0000801c, 0x148ec02b, 0x148ec057}, + {0x00008318, 0x000044c0, 0x00008980}, + {0x00009e00, 0x03721821, 0x03721821}, + {0x0000a230, 0x0000400b, 0x00004016}, + {0x0000a254, 0x00000898, 0x00001130}, +}; + +static const u32 ar9565_1p0_common_wo_xlna_rx_gain_table[][2] = { + /* Addr allmodes */ + {0x0000a000, 0x00010000}, + {0x0000a004, 0x00030002}, + {0x0000a008, 0x00050004}, + {0x0000a00c, 0x00810080}, + {0x0000a010, 0x00830082}, + {0x0000a014, 0x01810180}, + {0x0000a018, 0x01830182}, + {0x0000a01c, 0x01850184}, + {0x0000a020, 0x01890188}, + {0x0000a024, 0x018b018a}, + {0x0000a028, 0x018d018c}, + {0x0000a02c, 0x03820190}, + {0x0000a030, 0x03840383}, + {0x0000a034, 0x03880385}, + {0x0000a038, 0x038a0389}, + {0x0000a03c, 0x038c038b}, + {0x0000a040, 0x0390038d}, + {0x0000a044, 0x03920391}, + {0x0000a048, 0x03940393}, + {0x0000a04c, 0x03960395}, + {0x0000a050, 0x00000000}, + {0x0000a054, 0x00000000}, + {0x0000a058, 0x00000000}, + {0x0000a05c, 0x00000000}, + {0x0000a060, 0x00000000}, + {0x0000a064, 0x00000000}, + {0x0000a068, 0x00000000}, + {0x0000a06c, 0x00000000}, + {0x0000a070, 0x00000000}, + {0x0000a074, 0x00000000}, + {0x0000a078, 0x00000000}, + {0x0000a07c, 0x00000000}, + {0x0000a080, 0x29292929}, + {0x0000a084, 0x29292929}, + {0x0000a088, 0x29292929}, + {0x0000a08c, 0x29292929}, + {0x0000a090, 0x22292929}, + {0x0000a094, 0x1d1d2222}, + {0x0000a098, 0x0c111117}, + {0x0000a09c, 0x00030303}, + {0x0000a0a0, 0x00000000}, + {0x0000a0a4, 0x00000000}, + {0x0000a0a8, 0x00000000}, + {0x0000a0ac, 0x00000000}, + {0x0000a0b0, 0x00000000}, + {0x0000a0b4, 0x00000000}, + {0x0000a0b8, 0x00000000}, + {0x0000a0bc, 0x00000000}, + {0x0000a0c0, 0x00bf00a0}, + {0x0000a0c4, 0x11a011a1}, + {0x0000a0c8, 0x11be11bf}, + {0x0000a0cc, 0x11bc11bd}, + {0x0000a0d0, 0x22632264}, + {0x0000a0d4, 0x22612262}, + {0x0000a0d8, 0x227f2260}, + {0x0000a0dc, 0x4322227e}, + {0x0000a0e0, 0x43204321}, + {0x0000a0e4, 0x433e433f}, + {0x0000a0e8, 0x4462433d}, + {0x0000a0ec, 0x44604461}, + {0x0000a0f0, 0x447e447f}, + {0x0000a0f4, 0x5582447d}, + {0x0000a0f8, 0x55805581}, + {0x0000a0fc, 0x559e559f}, + {0x0000a100, 0x66816682}, + {0x0000a104, 0x669f6680}, + {0x0000a108, 0x669d669e}, + {0x0000a10c, 0x77627763}, + {0x0000a110, 0x77607761}, + {0x0000a114, 0x00000000}, + {0x0000a118, 0x00000000}, + {0x0000a11c, 0x00000000}, + {0x0000a120, 0x00000000}, + {0x0000a124, 0x00000000}, + {0x0000a128, 0x00000000}, + {0x0000a12c, 0x00000000}, + {0x0000a130, 0x00000000}, + {0x0000a134, 0x00000000}, + {0x0000a138, 0x00000000}, + {0x0000a13c, 0x00000000}, + {0x0000a140, 0x00bf00a0}, + {0x0000a144, 0x11a011a1}, + {0x0000a148, 0x11be11bf}, + {0x0000a14c, 0x11bc11bd}, + {0x0000a150, 0x22632264}, + {0x0000a154, 0x22612262}, + {0x0000a158, 0x227f2260}, + {0x0000a15c, 0x4322227e}, + {0x0000a160, 0x43204321}, + {0x0000a164, 0x433e433f}, + {0x0000a168, 0x4462433d}, + {0x0000a16c, 0x44604461}, + {0x0000a170, 0x447e447f}, + {0x0000a174, 0x5582447d}, + {0x0000a178, 0x55805581}, + {0x0000a17c, 0x559e559f}, + {0x0000a180, 0x66816682}, + {0x0000a184, 0x669f6680}, + {0x0000a188, 0x669d669e}, + {0x0000a18c, 0x77e677e7}, + {0x0000a190, 0x77e477e5}, + {0x0000a194, 0x00000000}, + {0x0000a198, 0x00000000}, + {0x0000a19c, 0x00000000}, + {0x0000a1a0, 0x00000000}, + {0x0000a1a4, 0x00000000}, + {0x0000a1a8, 0x00000000}, + {0x0000a1ac, 0x00000000}, + {0x0000a1b0, 0x00000000}, + {0x0000a1b4, 0x00000000}, + {0x0000a1b8, 0x00000000}, + {0x0000a1bc, 0x00000000}, + {0x0000a1c0, 0x00000000}, + {0x0000a1c4, 0x00000000}, + {0x0000a1c8, 0x00000000}, + {0x0000a1cc, 0x00000000}, + {0x0000a1d0, 0x00000000}, + {0x0000a1d4, 0x00000000}, + {0x0000a1d8, 0x00000000}, + {0x0000a1dc, 0x00000000}, + {0x0000a1e0, 0x00000000}, + {0x0000a1e4, 0x00000000}, + {0x0000a1e8, 0x00000000}, + {0x0000a1ec, 0x00000000}, + {0x0000a1f0, 0x00000396}, + {0x0000a1f4, 0x00000396}, + {0x0000a1f8, 0x00000396}, + {0x0000a1fc, 0x00000196}, + {0x0000b000, 0x00010000}, + {0x0000b004, 0x00030002}, + {0x0000b008, 0x00050004}, + {0x0000b00c, 0x00810080}, + {0x0000b010, 0x00830082}, + {0x0000b014, 0x01810180}, + {0x0000b018, 0x01830182}, + {0x0000b01c, 0x01850184}, + {0x0000b020, 0x02810280}, + {0x0000b024, 0x02830282}, + {0x0000b028, 0x02850284}, + {0x0000b02c, 0x02890288}, + {0x0000b030, 0x028b028a}, + {0x0000b034, 0x0388028c}, + {0x0000b038, 0x038a0389}, + {0x0000b03c, 0x038c038b}, + {0x0000b040, 0x0390038d}, + {0x0000b044, 0x03920391}, + {0x0000b048, 0x03940393}, + {0x0000b04c, 0x03960395}, + {0x0000b050, 0x00000000}, + {0x0000b054, 0x00000000}, + {0x0000b058, 0x00000000}, + {0x0000b05c, 0x00000000}, + {0x0000b060, 0x00000000}, + {0x0000b064, 0x00000000}, + {0x0000b068, 0x00000000}, + {0x0000b06c, 0x00000000}, + {0x0000b070, 0x00000000}, + {0x0000b074, 0x00000000}, + {0x0000b078, 0x00000000}, + {0x0000b07c, 0x00000000}, + {0x0000b080, 0x32323232}, + {0x0000b084, 0x2f2f3232}, + {0x0000b088, 0x23282a2d}, + {0x0000b08c, 0x1c1e2123}, + {0x0000b090, 0x14171919}, + {0x0000b094, 0x0e0e1214}, + {0x0000b098, 0x03050707}, + {0x0000b09c, 0x00030303}, + {0x0000b0a0, 0x00000000}, + {0x0000b0a4, 0x00000000}, + {0x0000b0a8, 0x00000000}, + {0x0000b0ac, 0x00000000}, + {0x0000b0b0, 0x00000000}, + {0x0000b0b4, 0x00000000}, + {0x0000b0b8, 0x00000000}, + {0x0000b0bc, 0x00000000}, + {0x0000b0c0, 0x003f0020}, + {0x0000b0c4, 0x00400041}, + {0x0000b0c8, 0x0140005f}, + {0x0000b0cc, 0x0160015f}, + {0x0000b0d0, 0x017e017f}, + {0x0000b0d4, 0x02410242}, + {0x0000b0d8, 0x025f0240}, + {0x0000b0dc, 0x027f0260}, + {0x0000b0e0, 0x0341027e}, + {0x0000b0e4, 0x035f0340}, + {0x0000b0e8, 0x037f0360}, + {0x0000b0ec, 0x04400441}, + {0x0000b0f0, 0x0460045f}, + {0x0000b0f4, 0x0541047f}, + {0x0000b0f8, 0x055f0540}, + {0x0000b0fc, 0x057f0560}, + {0x0000b100, 0x06400641}, + {0x0000b104, 0x0660065f}, + {0x0000b108, 0x067e067f}, + {0x0000b10c, 0x07410742}, + {0x0000b110, 0x075f0740}, + {0x0000b114, 0x077f0760}, + {0x0000b118, 0x07800781}, + {0x0000b11c, 0x07a0079f}, + {0x0000b120, 0x07c107bf}, + {0x0000b124, 0x000007c0}, + {0x0000b128, 0x00000000}, + {0x0000b12c, 0x00000000}, + {0x0000b130, 0x00000000}, + {0x0000b134, 0x00000000}, + {0x0000b138, 0x00000000}, + {0x0000b13c, 0x00000000}, + {0x0000b140, 0x003f0020}, + {0x0000b144, 0x00400041}, + {0x0000b148, 0x0140005f}, + {0x0000b14c, 0x0160015f}, + {0x0000b150, 0x017e017f}, + {0x0000b154, 0x02410242}, + {0x0000b158, 0x025f0240}, + {0x0000b15c, 0x027f0260}, + {0x0000b160, 0x0341027e}, + {0x0000b164, 0x035f0340}, + {0x0000b168, 0x037f0360}, + {0x0000b16c, 0x04400441}, + {0x0000b170, 0x0460045f}, + {0x0000b174, 0x0541047f}, + {0x0000b178, 0x055f0540}, + {0x0000b17c, 0x057f0560}, + {0x0000b180, 0x06400641}, + {0x0000b184, 0x0660065f}, + {0x0000b188, 0x067e067f}, + {0x0000b18c, 0x07410742}, + {0x0000b190, 0x075f0740}, + {0x0000b194, 0x077f0760}, + {0x0000b198, 0x07800781}, + {0x0000b19c, 0x07a0079f}, + {0x0000b1a0, 0x07c107bf}, + {0x0000b1a4, 0x000007c0}, + {0x0000b1a8, 0x00000000}, + {0x0000b1ac, 0x00000000}, + {0x0000b1b0, 0x00000000}, + {0x0000b1b4, 0x00000000}, + {0x0000b1b8, 0x00000000}, + {0x0000b1bc, 0x00000000}, + {0x0000b1c0, 0x00000000}, + {0x0000b1c4, 0x00000000}, + {0x0000b1c8, 0x00000000}, + {0x0000b1cc, 0x00000000}, + {0x0000b1d0, 0x00000000}, + {0x0000b1d4, 0x00000000}, + {0x0000b1d8, 0x00000000}, + {0x0000b1dc, 0x00000000}, + {0x0000b1e0, 0x00000000}, + {0x0000b1e4, 0x00000000}, + {0x0000b1e8, 0x00000000}, + {0x0000b1ec, 0x00000000}, + {0x0000b1f0, 0x00000396}, + {0x0000b1f4, 0x00000396}, + {0x0000b1f8, 0x00000396}, + {0x0000b1fc, 0x00000196}, +}; + +static const u32 ar9565_1p0_modes_low_ob_db_tx_gain_table[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0xfc0a9380, 0xfc0a9380, 0xfdab5b52, 0xfdab5b52}, + {0x0000a2e0, 0xffecec00, 0xffecec00, 0xfd339c84, 0xfd339c84}, + {0x0000a2e4, 0xfc0f0000, 0xfc0f0000, 0xfec3e000, 0xfec3e000}, + {0x0000a2e8, 0xfc100000, 0xfc100000, 0xfffc0000, 0xfffc0000}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a504, 0x06000003, 0x06000003, 0x04000002, 0x04000002}, + {0x0000a508, 0x0a000020, 0x0a000020, 0x08000004, 0x08000004}, + {0x0000a50c, 0x10000023, 0x10000023, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x16000220, 0x16000220, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x1c000223, 0x1c000223, 0x12000400, 0x12000400}, + {0x0000a518, 0x21020220, 0x21020220, 0x16000402, 0x16000402}, + {0x0000a51c, 0x27020223, 0x27020223, 0x19000404, 0x19000404}, + {0x0000a520, 0x2b022220, 0x2b022220, 0x1c000603, 0x1c000603}, + {0x0000a524, 0x2f022222, 0x2f022222, 0x21000a02, 0x21000a02}, + {0x0000a528, 0x34022225, 0x34022225, 0x25000a04, 0x25000a04}, + {0x0000a52c, 0x3a02222a, 0x3a02222a, 0x28000a20, 0x28000a20}, + {0x0000a530, 0x3e02222c, 0x3e02222c, 0x2c000e20, 0x2c000e20}, + {0x0000a534, 0x4202242a, 0x4202242a, 0x30000e22, 0x30000e22}, + {0x0000a538, 0x4702244a, 0x4702244a, 0x34000e24, 0x34000e24}, + {0x0000a53c, 0x4b02244c, 0x4b02244c, 0x38001640, 0x38001640}, + {0x0000a540, 0x4e02246c, 0x4e02246c, 0x3c001660, 0x3c001660}, + {0x0000a544, 0x5302266c, 0x5302266c, 0x3f001861, 0x3f001861}, + {0x0000a548, 0x5702286c, 0x5702286c, 0x43001a81, 0x43001a81}, + {0x0000a54c, 0x5c04286b, 0x5c04286b, 0x47001a83, 0x47001a83}, + {0x0000a550, 0x61042a6c, 0x61042a6c, 0x4a001c84, 0x4a001c84}, + {0x0000a554, 0x66062a6c, 0x66062a6c, 0x4e001ce3, 0x4e001ce3}, + {0x0000a558, 0x6b062e6c, 0x6b062e6c, 0x52001ce5, 0x52001ce5}, + {0x0000a55c, 0x7006308c, 0x7006308c, 0x56001ce9, 0x56001ce9}, + {0x0000a560, 0x730a308a, 0x730a308a, 0x5a001ceb, 0x5a001ceb}, + {0x0000a564, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a568, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a56c, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a570, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a574, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a578, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a57c, 0x770a308c, 0x770a308c, 0x5d001eec, 0x5d001eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a618, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a61c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a620, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a624, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a628, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a62c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a630, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a634, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a638, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a63c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00016044, 0x012482d4, 0x012482d4, 0x012482d4, 0x012482d4}, + {0x00016048, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00016054, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, +}; + +static const u32 ar9565_1p0_modes_high_ob_db_tx_gain_table[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0xfc0a9380, 0xfc0a9380, 0xfdab5b52, 0xfdab5b52}, + {0x0000a2e0, 0xffecec00, 0xffecec00, 0xfd339c84, 0xfd339c84}, + {0x0000a2e4, 0xfc0f0000, 0xfc0f0000, 0xfec3e000, 0xfec3e000}, + {0x0000a2e8, 0xfc100000, 0xfc100000, 0xfffc0000, 0xfffc0000}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050df, 0x000050df}, + {0x0000a500, 0x00002220, 0x00002220, 0x00000000, 0x00000000}, + {0x0000a504, 0x06002223, 0x06002223, 0x04000002, 0x04000002}, + {0x0000a508, 0x0b022220, 0x0b022220, 0x08000004, 0x08000004}, + {0x0000a50c, 0x10022223, 0x10022223, 0x0c000200, 0x0c000200}, + {0x0000a510, 0x15022620, 0x15022620, 0x10000202, 0x10000202}, + {0x0000a514, 0x19022622, 0x19022622, 0x13000400, 0x13000400}, + {0x0000a518, 0x1c022822, 0x1c022822, 0x17000402, 0x17000402}, + {0x0000a51c, 0x21022842, 0x21022842, 0x1b000404, 0x1b000404}, + {0x0000a520, 0x24022c41, 0x24022c41, 0x1e000603, 0x1e000603}, + {0x0000a524, 0x29023042, 0x29023042, 0x23000a02, 0x23000a02}, + {0x0000a528, 0x2d023044, 0x2d023044, 0x27000a04, 0x27000a04}, + {0x0000a52c, 0x31023644, 0x31023644, 0x2a000a20, 0x2a000a20}, + {0x0000a530, 0x36025643, 0x36025643, 0x2e000e20, 0x2e000e20}, + {0x0000a534, 0x3a025a44, 0x3a025a44, 0x32000e22, 0x32000e22}, + {0x0000a538, 0x3d025e45, 0x3d025e45, 0x36000e24, 0x36000e24}, + {0x0000a53c, 0x43025e4a, 0x43025e4a, 0x3a001640, 0x3a001640}, + {0x0000a540, 0x4a025e6c, 0x4a025e6c, 0x3e001660, 0x3e001660}, + {0x0000a544, 0x50025e8e, 0x50025e8e, 0x41001861, 0x41001861}, + {0x0000a548, 0x56025eb2, 0x56025eb2, 0x45001a81, 0x45001a81}, + {0x0000a54c, 0x5c025eb5, 0x5c025eb5, 0x49001a83, 0x49001a83}, + {0x0000a550, 0x62025ef6, 0x62025ef6, 0x4c001c84, 0x4c001c84}, + {0x0000a554, 0x65025f56, 0x65025f56, 0x4f001ce3, 0x4f001ce3}, + {0x0000a558, 0x69027f56, 0x69027f56, 0x53001ce5, 0x53001ce5}, + {0x0000a55c, 0x6d029f56, 0x6d029f56, 0x57001ce9, 0x57001ce9}, + {0x0000a560, 0x73049f56, 0x73049f56, 0x5b001ceb, 0x5b001ceb}, + {0x0000a564, 0x7804ff56, 0x7804ff56, 0x60001cf0, 0x60001cf0}, + {0x0000a568, 0x7804ff56, 0x7804ff56, 0x61001cf1, 0x61001cf1}, + {0x0000a56c, 0x7804ff56, 0x7804ff56, 0x62001cf2, 0x62001cf2}, + {0x0000a570, 0x7804ff56, 0x7804ff56, 0x63001cf3, 0x63001cf3}, + {0x0000a574, 0x7804ff56, 0x7804ff56, 0x64001cf4, 0x64001cf4}, + {0x0000a578, 0x7804ff56, 0x7804ff56, 0x66001ff6, 0x66001ff6}, + {0x0000a57c, 0x7804ff56, 0x7804ff56, 0x66001ff6, 0x66001ff6}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00804000, 0x00804000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00804201, 0x00804201, 0x00000000, 0x00000000}, + {0x0000a614, 0x00804201, 0x00804201, 0x01404000, 0x01404000}, + {0x0000a618, 0x00804201, 0x00804201, 0x01404501, 0x01404501}, + {0x0000a61c, 0x02008201, 0x02008201, 0x02008501, 0x02008501}, + {0x0000a620, 0x02c10a03, 0x02c10a03, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x04815205, 0x04815205, 0x02c10b04, 0x02c10b04}, + {0x0000a628, 0x0581d406, 0x0581d406, 0x03814b04, 0x03814b04}, + {0x0000a62c, 0x0581d607, 0x0581d607, 0x05018e05, 0x05018e05}, + {0x0000a630, 0x0581d607, 0x0581d607, 0x05019406, 0x05019406}, + {0x0000a634, 0x0581d607, 0x0581d607, 0x05019406, 0x05019406}, + {0x0000a638, 0x0581d607, 0x0581d607, 0x05019406, 0x05019406}, + {0x0000a63c, 0x0581d607, 0x0581d607, 0x05019406, 0x05019406}, + {0x00016044, 0x056d82e4, 0x056d82e4, 0x056d82e4, 0x056d82e4}, + {0x00016048, 0x8db49060, 0x8db49060, 0x8db49060, 0x8db49060}, + {0x00016054, 0x6db60000, 0x6db60000, 0x6db60000, 0x6db60000}, +}; + +static const u32 ar9565_1p0_modes_high_power_tx_gain_table[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0xfc0a9380, 0xfc0a9380, 0xfdab5b52, 0xfdab5b52}, + {0x0000a2e0, 0xffecec00, 0xffecec00, 0xfd339c84, 0xfd339c84}, + {0x0000a2e4, 0xfc0f0000, 0xfc0f0000, 0xfec3e000, 0xfec3e000}, + {0x0000a2e8, 0xfc100000, 0xfc100000, 0xfffc0000, 0xfffc0000}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050df, 0x000050df}, + {0x0000a500, 0x00002220, 0x00002220, 0x00000000, 0x00000000}, + {0x0000a504, 0x06002223, 0x06002223, 0x04000002, 0x04000002}, + {0x0000a508, 0x0a022220, 0x0a022220, 0x08000004, 0x08000004}, + {0x0000a50c, 0x0f022223, 0x0f022223, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x14022620, 0x14022620, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x18022622, 0x18022622, 0x11000400, 0x11000400}, + {0x0000a518, 0x1b022822, 0x1b022822, 0x15000402, 0x15000402}, + {0x0000a51c, 0x20022842, 0x20022842, 0x19000404, 0x19000404}, + {0x0000a520, 0x22022c41, 0x22022c41, 0x1b000603, 0x1b000603}, + {0x0000a524, 0x28023042, 0x28023042, 0x1f000a02, 0x1f000a02}, + {0x0000a528, 0x2c023044, 0x2c023044, 0x23000a04, 0x23000a04}, + {0x0000a52c, 0x2f023644, 0x2f023644, 0x26000a20, 0x26000a20}, + {0x0000a530, 0x34025643, 0x34025643, 0x2a000e20, 0x2a000e20}, + {0x0000a534, 0x38025a44, 0x38025a44, 0x2e000e22, 0x2e000e22}, + {0x0000a538, 0x3b025e45, 0x3b025e45, 0x31000e24, 0x31000e24}, + {0x0000a53c, 0x41025e4a, 0x41025e4a, 0x34001640, 0x34001640}, + {0x0000a540, 0x48025e6c, 0x48025e6c, 0x38001660, 0x38001660}, + {0x0000a544, 0x4e025e8e, 0x4e025e8e, 0x3b001861, 0x3b001861}, + {0x0000a548, 0x53025eb2, 0x53025eb2, 0x3e001a81, 0x3e001a81}, + {0x0000a54c, 0x59025eb5, 0x59025eb5, 0x42001a83, 0x42001a83}, + {0x0000a550, 0x5f025ef6, 0x5f025ef6, 0x44001c84, 0x44001c84}, + {0x0000a554, 0x62025f56, 0x62025f56, 0x48001ce3, 0x48001ce3}, + {0x0000a558, 0x66027f56, 0x66027f56, 0x4c001ce5, 0x4c001ce5}, + {0x0000a55c, 0x6a029f56, 0x6a029f56, 0x50001ce9, 0x50001ce9}, + {0x0000a560, 0x70049f56, 0x70049f56, 0x54001ceb, 0x54001ceb}, + {0x0000a564, 0x7504ff56, 0x7504ff56, 0x59001cf0, 0x59001cf0}, + {0x0000a568, 0x7504ff56, 0x7504ff56, 0x5a001cf1, 0x5a001cf1}, + {0x0000a56c, 0x7504ff56, 0x7504ff56, 0x5b001cf2, 0x5b001cf2}, + {0x0000a570, 0x7504ff56, 0x7504ff56, 0x5c001cf3, 0x5c001cf3}, + {0x0000a574, 0x7504ff56, 0x7504ff56, 0x5d001cf4, 0x5d001cf4}, + {0x0000a578, 0x7504ff56, 0x7504ff56, 0x5f001ff6, 0x5f001ff6}, + {0x0000a57c, 0x7504ff56, 0x7504ff56, 0x5f001ff6, 0x5f001ff6}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a618, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a61c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a620, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a624, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a628, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a62c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a630, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a634, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a638, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a63c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00016044, 0x056d82e6, 0x056d82e6, 0x056d82e6, 0x056d82e6}, + {0x00016048, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00016054, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, +}; + +#endif /* INITVALS_9565_1P0_H */ diff --git a/drivers/net/wireless/ath/ath9k/ar9580_1p0_initvals.h b/drivers/net/wireless/ath/ath9k/ar9580_1p0_initvals.h new file mode 100644 index 0000000..bdee2ed --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ar9580_1p0_initvals.h @@ -0,0 +1,997 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * Copyright (c) 2011-2012 Qualcomm Atheros Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef INITVALS_9580_1P0_H +#define INITVALS_9580_1P0_H + +/* AR9580 1.0 */ + +#define ar9580_1p0_modes_fast_clock ar9300Modes_fast_clock_2p2 + +static const u32 ar9580_1p0_radio_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0001609c, 0x0dd08f29, 0x0dd08f29, 0x0b283f31, 0x0b283f31}, + {0x000160ac, 0xa4653c00, 0xa4653c00, 0x24652800, 0x24652800}, + {0x000160b0, 0x03284f3e, 0x03284f3e, 0x05d08f20, 0x05d08f20}, + {0x0001610c, 0x08000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00016140, 0x10804008, 0x10804008, 0x50804008, 0x50804008}, + {0x0001650c, 0x08000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00016540, 0x10804008, 0x10804008, 0x50804008, 0x50804008}, + {0x0001690c, 0x08000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00016940, 0x10804008, 0x10804008, 0x50804008, 0x50804008}, +}; + +static const u32 ar9580_1p0_baseband_core[][2] = { + /* Addr allmodes */ + {0x00009800, 0xafe68e30}, + {0x00009804, 0xfd14e000}, + {0x00009808, 0x9c0a9f6b}, + {0x0000980c, 0x04900000}, + {0x00009814, 0x3280c00a}, + {0x00009818, 0x00000000}, + {0x0000981c, 0x00020028}, + {0x00009834, 0x6400a290}, + {0x00009838, 0x0108ecff}, + {0x0000983c, 0x0d000600}, + {0x00009880, 0x201fff00}, + {0x00009884, 0x00001042}, + {0x000098a4, 0x00200400}, + {0x000098b0, 0x32840bbe}, + {0x000098d0, 0x004b6a8e}, + {0x000098d4, 0x00000820}, + {0x000098dc, 0x00000000}, + {0x000098f0, 0x00000000}, + {0x000098f4, 0x00000000}, + {0x00009c04, 0xff55ff55}, + {0x00009c08, 0x0320ff55}, + {0x00009c0c, 0x00000000}, + {0x00009c10, 0x00000000}, + {0x00009c14, 0x00046384}, + {0x00009c18, 0x05b6b440}, + {0x00009c1c, 0x00b6b440}, + {0x00009d00, 0xc080a333}, + {0x00009d04, 0x40206c10}, + {0x00009d08, 0x009c4060}, + {0x00009d0c, 0x9883800a}, + {0x00009d10, 0x01834061}, + {0x00009d14, 0x00c0040b}, + {0x00009d18, 0x00000000}, + {0x00009e08, 0x0038230c}, + {0x00009e24, 0x990bb515}, + {0x00009e28, 0x0c6f0000}, + {0x00009e30, 0x06336f77}, + {0x00009e34, 0x6af6532f}, + {0x00009e38, 0x0cc80c00}, + {0x00009e40, 0x0d261820}, + {0x00009e4c, 0x00001004}, + {0x00009e50, 0x00ff03f1}, + {0x00009e54, 0x00000000}, + {0x00009fc0, 0x803e4788}, + {0x00009fc4, 0x0001efb5}, + {0x00009fcc, 0x40000014}, + {0x00009fd0, 0x01193b93}, + {0x0000a20c, 0x00000000}, + {0x0000a220, 0x00000000}, + {0x0000a224, 0x00000000}, + {0x0000a228, 0x10002310}, + {0x0000a23c, 0x00000000}, + {0x0000a244, 0x0c000000}, + {0x0000a2a0, 0x00000001}, + {0x0000a2c0, 0x00000001}, + {0x0000a2c8, 0x00000000}, + {0x0000a2cc, 0x18c43433}, + {0x0000a2d4, 0x00000000}, + {0x0000a2ec, 0x00000000}, + {0x0000a2f0, 0x00000000}, + {0x0000a2f4, 0x00000000}, + {0x0000a2f8, 0x00000000}, + {0x0000a344, 0x00000000}, + {0x0000a34c, 0x00000000}, + {0x0000a350, 0x0000a000}, + {0x0000a364, 0x00000000}, + {0x0000a370, 0x00000000}, + {0x0000a390, 0x00000001}, + {0x0000a394, 0x00000444}, + {0x0000a398, 0x001f0e0f}, + {0x0000a39c, 0x0075393f}, + {0x0000a3a0, 0xb79f6427}, + {0x0000a3a4, 0x00000000}, + {0x0000a3a8, 0xaaaaaaaa}, + {0x0000a3ac, 0x3c466478}, + {0x0000a3c0, 0x20202020}, + {0x0000a3c4, 0x22222220}, + {0x0000a3c8, 0x20200020}, + {0x0000a3cc, 0x20202020}, + {0x0000a3d0, 0x20202020}, + {0x0000a3d4, 0x20202020}, + {0x0000a3d8, 0x20202020}, + {0x0000a3dc, 0x20202020}, + {0x0000a3e0, 0x20202020}, + {0x0000a3e4, 0x20202020}, + {0x0000a3e8, 0x20202020}, + {0x0000a3ec, 0x20202020}, + {0x0000a3f0, 0x00000000}, + {0x0000a3f4, 0x00000000}, + {0x0000a3f8, 0x0c9bd380}, + {0x0000a3fc, 0x000f0f01}, + {0x0000a400, 0x8fa91f01}, + {0x0000a404, 0x00000000}, + {0x0000a408, 0x0e79e5c6}, + {0x0000a40c, 0x00820820}, + {0x0000a414, 0x1ce739ce}, + {0x0000a418, 0x2d001dce}, + {0x0000a41c, 0x1ce739ce}, + {0x0000a420, 0x000001ce}, + {0x0000a424, 0x1ce739ce}, + {0x0000a428, 0x000001ce}, + {0x0000a42c, 0x1ce739ce}, + {0x0000a430, 0x1ce739ce}, + {0x0000a434, 0x00000000}, + {0x0000a438, 0x00001801}, + {0x0000a43c, 0x00100000}, + {0x0000a440, 0x00000000}, + {0x0000a444, 0x00000000}, + {0x0000a448, 0x05000080}, + {0x0000a44c, 0x00000001}, + {0x0000a450, 0x00010000}, + {0x0000a458, 0x00000000}, + {0x0000a640, 0x00000000}, + {0x0000a644, 0x3fad9d74}, + {0x0000a648, 0x0048060a}, + {0x0000a64c, 0x00003c37}, + {0x0000a670, 0x03020100}, + {0x0000a674, 0x09080504}, + {0x0000a678, 0x0d0c0b0a}, + {0x0000a67c, 0x13121110}, + {0x0000a680, 0x31301514}, + {0x0000a684, 0x35343332}, + {0x0000a688, 0x00000036}, + {0x0000a690, 0x00000838}, + {0x0000a7c0, 0x00000000}, + {0x0000a7c4, 0xfffffffc}, + {0x0000a7c8, 0x00000000}, + {0x0000a7cc, 0x00000000}, + {0x0000a7d0, 0x00000000}, + {0x0000a7d4, 0x00000004}, + {0x0000a7dc, 0x00000000}, + {0x0000a8d0, 0x004b6a8e}, + {0x0000a8d4, 0x00000820}, + {0x0000a8dc, 0x00000000}, + {0x0000a8f0, 0x00000000}, + {0x0000a8f4, 0x00000000}, + {0x0000b2d0, 0x00000080}, + {0x0000b2d4, 0x00000000}, + {0x0000b2ec, 0x00000000}, + {0x0000b2f0, 0x00000000}, + {0x0000b2f4, 0x00000000}, + {0x0000b2f8, 0x00000000}, + {0x0000b408, 0x0e79e5c0}, + {0x0000b40c, 0x00820820}, + {0x0000b420, 0x00000000}, + {0x0000b8d0, 0x004b6a8e}, + {0x0000b8d4, 0x00000820}, + {0x0000b8dc, 0x00000000}, + {0x0000b8f0, 0x00000000}, + {0x0000b8f4, 0x00000000}, + {0x0000c2d0, 0x00000080}, + {0x0000c2d4, 0x00000000}, + {0x0000c2ec, 0x00000000}, + {0x0000c2f0, 0x00000000}, + {0x0000c2f4, 0x00000000}, + {0x0000c2f8, 0x00000000}, + {0x0000c408, 0x0e79e5c0}, + {0x0000c40c, 0x00820820}, + {0x0000c420, 0x00000000}, +}; + +#define ar9580_1p0_mac_postamble ar9300_2p2_mac_postamble + +static const u32 ar9580_1p0_low_ob_db_tx_gain_table[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000a2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000a2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a504, 0x06000003, 0x06000003, 0x04000002, 0x04000002}, + {0x0000a508, 0x0a000020, 0x0a000020, 0x08000004, 0x08000004}, + {0x0000a50c, 0x10000023, 0x10000023, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x16000220, 0x16000220, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x1c000223, 0x1c000223, 0x12000400, 0x12000400}, + {0x0000a518, 0x21002220, 0x21002220, 0x16000402, 0x16000402}, + {0x0000a51c, 0x27002223, 0x27002223, 0x19000404, 0x19000404}, + {0x0000a520, 0x2b022220, 0x2b022220, 0x1c000603, 0x1c000603}, + {0x0000a524, 0x2f022222, 0x2f022222, 0x21000a02, 0x21000a02}, + {0x0000a528, 0x34022225, 0x34022225, 0x25000a04, 0x25000a04}, + {0x0000a52c, 0x3a02222a, 0x3a02222a, 0x28000a20, 0x28000a20}, + {0x0000a530, 0x3e02222c, 0x3e02222c, 0x2c000e20, 0x2c000e20}, + {0x0000a534, 0x4202242a, 0x4202242a, 0x30000e22, 0x30000e22}, + {0x0000a538, 0x4702244a, 0x4702244a, 0x34000e24, 0x34000e24}, + {0x0000a53c, 0x4b02244c, 0x4b02244c, 0x38001640, 0x38001640}, + {0x0000a540, 0x4e02246c, 0x4e02246c, 0x3c001660, 0x3c001660}, + {0x0000a544, 0x5302266c, 0x5302266c, 0x3f001861, 0x3f001861}, + {0x0000a548, 0x5702286c, 0x5702286c, 0x43001a81, 0x43001a81}, + {0x0000a54c, 0x5c02486b, 0x5c02486b, 0x47001a83, 0x47001a83}, + {0x0000a550, 0x61024a6c, 0x61024a6c, 0x4a001c84, 0x4a001c84}, + {0x0000a554, 0x66026a6c, 0x66026a6c, 0x4e001ce3, 0x4e001ce3}, + {0x0000a558, 0x6b026e6c, 0x6b026e6c, 0x52001ce5, 0x52001ce5}, + {0x0000a55c, 0x7002708c, 0x7002708c, 0x56001ce9, 0x56001ce9}, + {0x0000a560, 0x7302b08a, 0x7302b08a, 0x5a001ceb, 0x5a001ceb}, + {0x0000a564, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a568, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a56c, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a570, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a574, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a578, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a57c, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a580, 0x00800000, 0x00800000, 0x00800000, 0x00800000}, + {0x0000a584, 0x06800003, 0x06800003, 0x04800002, 0x04800002}, + {0x0000a588, 0x0a800020, 0x0a800020, 0x08800004, 0x08800004}, + {0x0000a58c, 0x10800023, 0x10800023, 0x0b800200, 0x0b800200}, + {0x0000a590, 0x16800220, 0x16800220, 0x0f800202, 0x0f800202}, + {0x0000a594, 0x1c800223, 0x1c800223, 0x12800400, 0x12800400}, + {0x0000a598, 0x21802220, 0x21802220, 0x16800402, 0x16800402}, + {0x0000a59c, 0x27802223, 0x27802223, 0x19800404, 0x19800404}, + {0x0000a5a0, 0x2b822220, 0x2b822220, 0x1c800603, 0x1c800603}, + {0x0000a5a4, 0x2f822222, 0x2f822222, 0x21800a02, 0x21800a02}, + {0x0000a5a8, 0x34822225, 0x34822225, 0x25800a04, 0x25800a04}, + {0x0000a5ac, 0x3a82222a, 0x3a82222a, 0x28800a20, 0x28800a20}, + {0x0000a5b0, 0x3e82222c, 0x3e82222c, 0x2c800e20, 0x2c800e20}, + {0x0000a5b4, 0x4282242a, 0x4282242a, 0x30800e22, 0x30800e22}, + {0x0000a5b8, 0x4782244a, 0x4782244a, 0x34800e24, 0x34800e24}, + {0x0000a5bc, 0x4b82244c, 0x4b82244c, 0x38801640, 0x38801640}, + {0x0000a5c0, 0x4e82246c, 0x4e82246c, 0x3c801660, 0x3c801660}, + {0x0000a5c4, 0x5382266c, 0x5382266c, 0x3f801861, 0x3f801861}, + {0x0000a5c8, 0x5782286c, 0x5782286c, 0x43801a81, 0x43801a81}, + {0x0000a5cc, 0x5c82486b, 0x5c82486b, 0x47801a83, 0x47801a83}, + {0x0000a5d0, 0x61824a6c, 0x61824a6c, 0x4a801c84, 0x4a801c84}, + {0x0000a5d4, 0x66826a6c, 0x66826a6c, 0x4e801ce3, 0x4e801ce3}, + {0x0000a5d8, 0x6b826e6c, 0x6b826e6c, 0x52801ce5, 0x52801ce5}, + {0x0000a5dc, 0x7082708c, 0x7082708c, 0x56801ce9, 0x56801ce9}, + {0x0000a5e0, 0x7382b08a, 0x7382b08a, 0x5a801ceb, 0x5a801ceb}, + {0x0000a5e4, 0x7782b08c, 0x7782b08c, 0x5d801eec, 0x5d801eec}, + {0x0000a5e8, 0x7782b08c, 0x7782b08c, 0x5d801eec, 0x5d801eec}, + {0x0000a5ec, 0x7782b08c, 0x7782b08c, 0x5d801eec, 0x5d801eec}, + {0x0000a5f0, 0x7782b08c, 0x7782b08c, 0x5d801eec, 0x5d801eec}, + {0x0000a5f4, 0x7782b08c, 0x7782b08c, 0x5d801eec, 0x5d801eec}, + {0x0000a5f8, 0x7782b08c, 0x7782b08c, 0x5d801eec, 0x5d801eec}, + {0x0000a5fc, 0x7782b08c, 0x7782b08c, 0x5d801eec, 0x5d801eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x01404000, 0x01404000, 0x01404000, 0x01404000}, + {0x0000a618, 0x01404501, 0x01404501, 0x01404501, 0x01404501}, + {0x0000a61c, 0x02008802, 0x02008802, 0x02008501, 0x02008501}, + {0x0000a620, 0x0300cc03, 0x0300cc03, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x0300cc03, 0x0300cc03, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x0300cc03, 0x0300cc03, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x03810c03, 0x03810c03, 0x04015005, 0x04015005}, + {0x0000a630, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a634, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a638, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a63c, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000b2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000b2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000b2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000c2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000c2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000c2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000c2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x00016044, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016048, 0x66480001, 0x66480001, 0x66480001, 0x66480001}, + {0x00016068, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016444, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016448, 0x66480001, 0x66480001, 0x66480001, 0x66480001}, + {0x00016468, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016844, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016848, 0x66480001, 0x66480001, 0x66480001, 0x66480001}, + {0x00016868, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, +}; + +#define ar9580_1p0_high_power_tx_gain_table ar9580_1p0_low_ob_db_tx_gain_table + +static const u32 ar9580_1p0_lowest_ob_db_tx_gain_table[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000a2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000a2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a504, 0x06000003, 0x06000003, 0x04000002, 0x04000002}, + {0x0000a508, 0x0a000020, 0x0a000020, 0x08000004, 0x08000004}, + {0x0000a50c, 0x10000023, 0x10000023, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x16000220, 0x16000220, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x1c000223, 0x1c000223, 0x12000400, 0x12000400}, + {0x0000a518, 0x21002220, 0x21002220, 0x16000402, 0x16000402}, + {0x0000a51c, 0x27002223, 0x27002223, 0x19000404, 0x19000404}, + {0x0000a520, 0x2b022220, 0x2b022220, 0x1c000603, 0x1c000603}, + {0x0000a524, 0x2f022222, 0x2f022222, 0x21000a02, 0x21000a02}, + {0x0000a528, 0x34022225, 0x34022225, 0x25000a04, 0x25000a04}, + {0x0000a52c, 0x3a02222a, 0x3a02222a, 0x28000a20, 0x28000a20}, + {0x0000a530, 0x3e02222c, 0x3e02222c, 0x2c000e20, 0x2c000e20}, + {0x0000a534, 0x4202242a, 0x4202242a, 0x30000e22, 0x30000e22}, + {0x0000a538, 0x4702244a, 0x4702244a, 0x34000e24, 0x34000e24}, + {0x0000a53c, 0x4b02244c, 0x4b02244c, 0x38001640, 0x38001640}, + {0x0000a540, 0x4e02246c, 0x4e02246c, 0x3c001660, 0x3c001660}, + {0x0000a544, 0x5302266c, 0x5302266c, 0x3f001861, 0x3f001861}, + {0x0000a548, 0x5702286c, 0x5702286c, 0x43001a81, 0x43001a81}, + {0x0000a54c, 0x5c02486b, 0x5c02486b, 0x47001a83, 0x47001a83}, + {0x0000a550, 0x61024a6c, 0x61024a6c, 0x4a001c84, 0x4a001c84}, + {0x0000a554, 0x66026a6c, 0x66026a6c, 0x4e001ce3, 0x4e001ce3}, + {0x0000a558, 0x6b026e6c, 0x6b026e6c, 0x52001ce5, 0x52001ce5}, + {0x0000a55c, 0x7002708c, 0x7002708c, 0x56001ce9, 0x56001ce9}, + {0x0000a560, 0x7302b08a, 0x7302b08a, 0x5a001ceb, 0x5a001ceb}, + {0x0000a564, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a568, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a56c, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a570, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a574, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a578, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a57c, 0x7702b08c, 0x7702b08c, 0x5d001eec, 0x5d001eec}, + {0x0000a580, 0x00800000, 0x00800000, 0x00800000, 0x00800000}, + {0x0000a584, 0x06800003, 0x06800003, 0x04800002, 0x04800002}, + {0x0000a588, 0x0a800020, 0x0a800020, 0x08800004, 0x08800004}, + {0x0000a58c, 0x10800023, 0x10800023, 0x0b800200, 0x0b800200}, + {0x0000a590, 0x16800220, 0x16800220, 0x0f800202, 0x0f800202}, + {0x0000a594, 0x1c800223, 0x1c800223, 0x12800400, 0x12800400}, + {0x0000a598, 0x21802220, 0x21802220, 0x16800402, 0x16800402}, + {0x0000a59c, 0x27802223, 0x27802223, 0x19800404, 0x19800404}, + {0x0000a5a0, 0x2b822220, 0x2b822220, 0x1c800603, 0x1c800603}, + {0x0000a5a4, 0x2f822222, 0x2f822222, 0x21800a02, 0x21800a02}, + {0x0000a5a8, 0x34822225, 0x34822225, 0x25800a04, 0x25800a04}, + {0x0000a5ac, 0x3a82222a, 0x3a82222a, 0x28800a20, 0x28800a20}, + {0x0000a5b0, 0x3e82222c, 0x3e82222c, 0x2c800e20, 0x2c800e20}, + {0x0000a5b4, 0x4282242a, 0x4282242a, 0x30800e22, 0x30800e22}, + {0x0000a5b8, 0x4782244a, 0x4782244a, 0x34800e24, 0x34800e24}, + {0x0000a5bc, 0x4b82244c, 0x4b82244c, 0x38801640, 0x38801640}, + {0x0000a5c0, 0x4e82246c, 0x4e82246c, 0x3c801660, 0x3c801660}, + {0x0000a5c4, 0x5382266c, 0x5382266c, 0x3f801861, 0x3f801861}, + {0x0000a5c8, 0x5782286c, 0x5782286c, 0x43801a81, 0x43801a81}, + {0x0000a5cc, 0x5c82486b, 0x5c82486b, 0x47801a83, 0x47801a83}, + {0x0000a5d0, 0x61824a6c, 0x61824a6c, 0x4a801c84, 0x4a801c84}, + {0x0000a5d4, 0x66826a6c, 0x66826a6c, 0x4e801ce3, 0x4e801ce3}, + {0x0000a5d8, 0x6b826e6c, 0x6b826e6c, 0x52801ce5, 0x52801ce5}, + {0x0000a5dc, 0x7082708c, 0x7082708c, 0x56801ce9, 0x56801ce9}, + {0x0000a5e0, 0x7382b08a, 0x7382b08a, 0x5a801ceb, 0x5a801ceb}, + {0x0000a5e4, 0x7782b08c, 0x7782b08c, 0x5d801eec, 0x5d801eec}, + {0x0000a5e8, 0x7782b08c, 0x7782b08c, 0x5d801eec, 0x5d801eec}, + {0x0000a5ec, 0x7782b08c, 0x7782b08c, 0x5d801eec, 0x5d801eec}, + {0x0000a5f0, 0x7782b08c, 0x7782b08c, 0x5d801eec, 0x5d801eec}, + {0x0000a5f4, 0x7782b08c, 0x7782b08c, 0x5d801eec, 0x5d801eec}, + {0x0000a5f8, 0x7782b08c, 0x7782b08c, 0x5d801eec, 0x5d801eec}, + {0x0000a5fc, 0x7782b08c, 0x7782b08c, 0x5d801eec, 0x5d801eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x01404000, 0x01404000, 0x01404000, 0x01404000}, + {0x0000a618, 0x01404501, 0x01404501, 0x01404501, 0x01404501}, + {0x0000a61c, 0x02008802, 0x02008802, 0x02008501, 0x02008501}, + {0x0000a620, 0x0300cc03, 0x0300cc03, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x0300cc03, 0x0300cc03, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x0300cc03, 0x0300cc03, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x03810c03, 0x03810c03, 0x04015005, 0x04015005}, + {0x0000a630, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a634, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a638, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a63c, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000b2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000b2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000b2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000c2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000c2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000c2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000c2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x00016044, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016048, 0x62480001, 0x62480001, 0x62480001, 0x62480001}, + {0x00016068, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016444, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016448, 0x62480001, 0x62480001, 0x62480001, 0x62480001}, + {0x00016468, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016844, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016848, 0x62480001, 0x62480001, 0x62480001, 0x62480001}, + {0x00016868, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, +}; + +#define ar9580_1p0_baseband_core_txfir_coeff_japan_2484 ar9462_2p0_baseband_core_txfir_coeff_japan_2484 + +static const u32 ar9580_1p0_mac_core[][2] = { + /* Addr allmodes */ + {0x00000008, 0x00000000}, + {0x00000030, 0x00020085}, + {0x00000034, 0x00000005}, + {0x00000040, 0x00000000}, + {0x00000044, 0x00000000}, + {0x00000048, 0x00000008}, + {0x0000004c, 0x00000010}, + {0x00000050, 0x00000000}, + {0x00001040, 0x002ffc0f}, + {0x00001044, 0x002ffc0f}, + {0x00001048, 0x002ffc0f}, + {0x0000104c, 0x002ffc0f}, + {0x00001050, 0x002ffc0f}, + {0x00001054, 0x002ffc0f}, + {0x00001058, 0x002ffc0f}, + {0x0000105c, 0x002ffc0f}, + {0x00001060, 0x002ffc0f}, + {0x00001064, 0x002ffc0f}, + {0x000010f0, 0x00000100}, + {0x00001270, 0x00000000}, + {0x000012b0, 0x00000000}, + {0x000012f0, 0x00000000}, + {0x0000143c, 0x00000000}, + {0x0000147c, 0x00000000}, + {0x00008000, 0x00000000}, + {0x00008004, 0x00000000}, + {0x00008008, 0x00000000}, + {0x0000800c, 0x00000000}, + {0x00008018, 0x00000000}, + {0x00008020, 0x00000000}, + {0x00008038, 0x00000000}, + {0x0000803c, 0x00000000}, + {0x00008040, 0x00000000}, + {0x00008044, 0x00000000}, + {0x00008048, 0x00000000}, + {0x0000804c, 0xffffffff}, + {0x00008054, 0x00000000}, + {0x00008058, 0x00000000}, + {0x0000805c, 0x000fc78f}, + {0x00008060, 0x0000000f}, + {0x00008064, 0x00000000}, + {0x00008070, 0x00000310}, + {0x00008074, 0x00000020}, + {0x00008078, 0x00000000}, + {0x0000809c, 0x0000000f}, + {0x000080a0, 0x00000000}, + {0x000080a4, 0x02ff0000}, + {0x000080a8, 0x0e070605}, + {0x000080ac, 0x0000000d}, + {0x000080b0, 0x00000000}, + {0x000080b4, 0x00000000}, + {0x000080b8, 0x00000000}, + {0x000080bc, 0x00000000}, + {0x000080c0, 0x2a800000}, + {0x000080c4, 0x06900168}, + {0x000080c8, 0x13881c22}, + {0x000080cc, 0x01f40000}, + {0x000080d0, 0x00252500}, + {0x000080d4, 0x00a00000}, + {0x000080d8, 0x00400000}, + {0x000080dc, 0x00000000}, + {0x000080e0, 0xffffffff}, + {0x000080e4, 0x0000ffff}, + {0x000080e8, 0x3f3f3f3f}, + {0x000080ec, 0x00000000}, + {0x000080f0, 0x00000000}, + {0x000080f4, 0x00000000}, + {0x000080fc, 0x00020000}, + {0x00008100, 0x00000000}, + {0x00008108, 0x00000052}, + {0x0000810c, 0x00000000}, + {0x00008110, 0x00000000}, + {0x00008114, 0x000007ff}, + {0x00008118, 0x000000aa}, + {0x0000811c, 0x00003210}, + {0x00008124, 0x00000000}, + {0x00008128, 0x00000000}, + {0x0000812c, 0x00000000}, + {0x00008130, 0x00000000}, + {0x00008134, 0x00000000}, + {0x00008138, 0x00000000}, + {0x0000813c, 0x0000ffff}, + {0x00008144, 0xffffffff}, + {0x00008168, 0x00000000}, + {0x0000816c, 0x00000000}, + {0x000081c0, 0x00000000}, + {0x000081c4, 0x33332210}, + {0x000081ec, 0x00000000}, + {0x000081f0, 0x00000000}, + {0x000081f4, 0x00000000}, + {0x000081f8, 0x00000000}, + {0x000081fc, 0x00000000}, + {0x00008240, 0x00100000}, + {0x00008244, 0x0010f400}, + {0x00008248, 0x00000800}, + {0x0000824c, 0x0001e800}, + {0x00008250, 0x00000000}, + {0x00008254, 0x00000000}, + {0x00008258, 0x00000000}, + {0x0000825c, 0x40000000}, + {0x00008260, 0x00080922}, + {0x00008264, 0x9d400010}, + {0x00008268, 0xffffffff}, + {0x0000826c, 0x0000ffff}, + {0x00008270, 0x00000000}, + {0x00008274, 0x40000000}, + {0x00008278, 0x003e4180}, + {0x0000827c, 0x00000004}, + {0x00008284, 0x0000002c}, + {0x00008288, 0x0000002c}, + {0x0000828c, 0x000000ff}, + {0x00008294, 0x00000000}, + {0x00008298, 0x00000000}, + {0x0000829c, 0x00000000}, + {0x00008300, 0x00000140}, + {0x00008314, 0x00000000}, + {0x0000831c, 0x0000010d}, + {0x00008328, 0x00000000}, + {0x0000832c, 0x00000007}, + {0x00008330, 0x00000302}, + {0x00008334, 0x00000700}, + {0x00008338, 0x00ff0000}, + {0x0000833c, 0x02400000}, + {0x00008340, 0x000107ff}, + {0x00008344, 0xaa48105b}, + {0x00008348, 0x008f0000}, + {0x0000835c, 0x00000000}, + {0x00008360, 0xffffffff}, + {0x00008364, 0xffffffff}, + {0x00008368, 0x00000000}, + {0x00008370, 0x00000000}, + {0x00008374, 0x000000ff}, + {0x00008378, 0x00000000}, + {0x0000837c, 0x00000000}, + {0x00008380, 0xffffffff}, + {0x00008384, 0xffffffff}, + {0x00008390, 0xffffffff}, + {0x00008394, 0xffffffff}, + {0x00008398, 0x00000000}, + {0x0000839c, 0x00000000}, + {0x000083a0, 0x00000000}, + {0x000083a4, 0x0000fa14}, + {0x000083a8, 0x000f0c00}, + {0x000083ac, 0x33332210}, + {0x000083b0, 0x33332210}, + {0x000083b4, 0x33332210}, + {0x000083b8, 0x33332210}, + {0x000083bc, 0x00000000}, + {0x000083c0, 0x00000000}, + {0x000083c4, 0x00000000}, + {0x000083c8, 0x00000000}, + {0x000083cc, 0x00000200}, + {0x000083d0, 0x000301ff}, +}; + +static const u32 ar9580_1p0_mixed_ob_db_tx_gain_table[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000a2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000a2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a504, 0x06000003, 0x06000003, 0x04000002, 0x04000002}, + {0x0000a508, 0x0a000020, 0x0a000020, 0x08000004, 0x08000004}, + {0x0000a50c, 0x10000023, 0x10000023, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x16000220, 0x16000220, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x1c000223, 0x1c000223, 0x11000400, 0x11000400}, + {0x0000a518, 0x21002220, 0x21002220, 0x15000402, 0x15000402}, + {0x0000a51c, 0x27002223, 0x27002223, 0x19000404, 0x19000404}, + {0x0000a520, 0x2b022220, 0x2b022220, 0x1b000603, 0x1b000603}, + {0x0000a524, 0x2f022222, 0x2f022222, 0x1f000a02, 0x1f000a02}, + {0x0000a528, 0x34022225, 0x34022225, 0x23000a04, 0x23000a04}, + {0x0000a52c, 0x3a02222a, 0x3a02222a, 0x26000a20, 0x26000a20}, + {0x0000a530, 0x3e02222c, 0x3e02222c, 0x2a000e20, 0x2a000e20}, + {0x0000a534, 0x4202242a, 0x4202242a, 0x2e000e22, 0x2e000e22}, + {0x0000a538, 0x4702244a, 0x4702244a, 0x31000e24, 0x31000e24}, + {0x0000a53c, 0x4b02244c, 0x4b02244c, 0x34001640, 0x34001640}, + {0x0000a540, 0x4e02246c, 0x4e02246c, 0x38001660, 0x38001660}, + {0x0000a544, 0x5302266c, 0x5302266c, 0x3b001861, 0x3b001861}, + {0x0000a548, 0x5702286c, 0x5702286c, 0x3e001a81, 0x3e001a81}, + {0x0000a54c, 0x5c02486b, 0x5c02486b, 0x42001a83, 0x42001a83}, + {0x0000a550, 0x61024a6c, 0x61024a6c, 0x44001c84, 0x44001c84}, + {0x0000a554, 0x66026a6c, 0x66026a6c, 0x48001ce3, 0x48001ce3}, + {0x0000a558, 0x6b026e6c, 0x6b026e6c, 0x4c001ce5, 0x4c001ce5}, + {0x0000a55c, 0x7002708c, 0x7002708c, 0x50001ce9, 0x50001ce9}, + {0x0000a560, 0x7302b08a, 0x7302b08a, 0x54001ceb, 0x54001ceb}, + {0x0000a564, 0x7702b08c, 0x7702b08c, 0x56001eec, 0x56001eec}, + {0x0000a568, 0x7702b08c, 0x7702b08c, 0x56001eec, 0x56001eec}, + {0x0000a56c, 0x7702b08c, 0x7702b08c, 0x56001eec, 0x56001eec}, + {0x0000a570, 0x7702b08c, 0x7702b08c, 0x56001eec, 0x56001eec}, + {0x0000a574, 0x7702b08c, 0x7702b08c, 0x56001eec, 0x56001eec}, + {0x0000a578, 0x7702b08c, 0x7702b08c, 0x56001eec, 0x56001eec}, + {0x0000a57c, 0x7702b08c, 0x7702b08c, 0x56001eec, 0x56001eec}, + {0x0000a580, 0x00800000, 0x00800000, 0x00800000, 0x00800000}, + {0x0000a584, 0x06800003, 0x06800003, 0x04800002, 0x04800002}, + {0x0000a588, 0x0a800020, 0x0a800020, 0x08800004, 0x08800004}, + {0x0000a58c, 0x10800023, 0x10800023, 0x0b800200, 0x0b800200}, + {0x0000a590, 0x16800220, 0x16800220, 0x0f800202, 0x0f800202}, + {0x0000a594, 0x1c800223, 0x1c800223, 0x11800400, 0x11800400}, + {0x0000a598, 0x21802220, 0x21802220, 0x15800402, 0x15800402}, + {0x0000a59c, 0x27802223, 0x27802223, 0x19800404, 0x19800404}, + {0x0000a5a0, 0x2b822220, 0x2b822220, 0x1b800603, 0x1b800603}, + {0x0000a5a4, 0x2f822222, 0x2f822222, 0x1f800a02, 0x1f800a02}, + {0x0000a5a8, 0x34822225, 0x34822225, 0x23800a04, 0x23800a04}, + {0x0000a5ac, 0x3a82222a, 0x3a82222a, 0x26800a20, 0x26800a20}, + {0x0000a5b0, 0x3e82222c, 0x3e82222c, 0x2a800e20, 0x2a800e20}, + {0x0000a5b4, 0x4282242a, 0x4282242a, 0x2e800e22, 0x2e800e22}, + {0x0000a5b8, 0x4782244a, 0x4782244a, 0x31800e24, 0x31800e24}, + {0x0000a5bc, 0x4b82244c, 0x4b82244c, 0x34801640, 0x34801640}, + {0x0000a5c0, 0x4e82246c, 0x4e82246c, 0x38801660, 0x38801660}, + {0x0000a5c4, 0x5382266c, 0x5382266c, 0x3b801861, 0x3b801861}, + {0x0000a5c8, 0x5782286c, 0x5782286c, 0x3e801a81, 0x3e801a81}, + {0x0000a5cc, 0x5c82486b, 0x5c82486b, 0x42801a83, 0x42801a83}, + {0x0000a5d0, 0x61824a6c, 0x61824a6c, 0x44801c84, 0x44801c84}, + {0x0000a5d4, 0x66826a6c, 0x66826a6c, 0x48801ce3, 0x48801ce3}, + {0x0000a5d8, 0x6b826e6c, 0x6b826e6c, 0x4c801ce5, 0x4c801ce5}, + {0x0000a5dc, 0x7082708c, 0x7082708c, 0x50801ce9, 0x50801ce9}, + {0x0000a5e0, 0x7382b08a, 0x7382b08a, 0x54801ceb, 0x54801ceb}, + {0x0000a5e4, 0x7782b08c, 0x7782b08c, 0x56801eec, 0x56801eec}, + {0x0000a5e8, 0x7782b08c, 0x7782b08c, 0x56801eec, 0x56801eec}, + {0x0000a5ec, 0x7782b08c, 0x7782b08c, 0x56801eec, 0x56801eec}, + {0x0000a5f0, 0x7782b08c, 0x7782b08c, 0x56801eec, 0x56801eec}, + {0x0000a5f4, 0x7782b08c, 0x7782b08c, 0x56801eec, 0x56801eec}, + {0x0000a5f8, 0x7782b08c, 0x7782b08c, 0x56801eec, 0x56801eec}, + {0x0000a5fc, 0x7782b08c, 0x7782b08c, 0x56801eec, 0x56801eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a60c, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a610, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a614, 0x01404000, 0x01404000, 0x01404000, 0x01404000}, + {0x0000a618, 0x01404501, 0x01404501, 0x01404501, 0x01404501}, + {0x0000a61c, 0x02008802, 0x02008802, 0x02008501, 0x02008501}, + {0x0000a620, 0x0300cc03, 0x0300cc03, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x0300cc03, 0x0300cc03, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x0300cc03, 0x0300cc03, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x03810c03, 0x03810c03, 0x04015005, 0x04015005}, + {0x0000a630, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a634, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a638, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000a63c, 0x03810e04, 0x03810e04, 0x04015005, 0x04015005}, + {0x0000b2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000b2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000b2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000c2dc, 0x0380c7fc, 0x0380c7fc, 0x03aaa352, 0x03aaa352}, + {0x0000c2e0, 0x0000f800, 0x0000f800, 0x03ccc584, 0x03ccc584}, + {0x0000c2e4, 0x03ff0000, 0x03ff0000, 0x03f0f800, 0x03f0f800}, + {0x0000c2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x00016044, 0x012492d4, 0x012492d4, 0x056db2e4, 0x056db2e4}, + {0x00016048, 0x66480001, 0x66480001, 0x8e480001, 0x8e480001}, + {0x00016068, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016444, 0x012492d4, 0x012492d4, 0x056db2e4, 0x056db2e4}, + {0x00016448, 0x66480001, 0x66480001, 0x8e480001, 0x8e480001}, + {0x00016468, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016844, 0x012492d4, 0x012492d4, 0x056db2e4, 0x056db2e4}, + {0x00016848, 0x66480001, 0x66480001, 0x8e480001, 0x8e480001}, + {0x00016868, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, +}; + +#define ar9580_1p0_wo_xlna_rx_gain_table ar9300Common_wo_xlna_rx_gain_table_2p2 + +#define ar9580_1p0_soc_postamble ar9300_2p2_soc_postamble + +#define ar9580_1p0_high_ob_db_tx_gain_table ar9300Modes_high_ob_db_tx_gain_table_2p2 + +#define ar9580_1p0_type5_tx_gain_table ar9300Modes_type5_tx_gain_table_2p2 + +static const u32 ar9580_1p0_type6_tx_gain_table[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x0000a2dc, 0x000cfff0, 0x000cfff0, 0x03aaa352, 0x03aaa352}, + {0x0000a2e0, 0x000f0000, 0x000f0000, 0x03ccc584, 0x03ccc584}, + {0x0000a2e4, 0x03f00000, 0x03f00000, 0x03f0f800, 0x03f0f800}, + {0x0000a2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000a410, 0x000050d9, 0x000050d9, 0x000050d9, 0x000050d9}, + {0x0000a500, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a504, 0x06000003, 0x06000003, 0x04000002, 0x04000002}, + {0x0000a508, 0x0a000020, 0x0a000020, 0x08000004, 0x08000004}, + {0x0000a50c, 0x10000023, 0x10000023, 0x0b000200, 0x0b000200}, + {0x0000a510, 0x15000028, 0x15000028, 0x0f000202, 0x0f000202}, + {0x0000a514, 0x1b00002b, 0x1b00002b, 0x12000400, 0x12000400}, + {0x0000a518, 0x1f020028, 0x1f020028, 0x16000402, 0x16000402}, + {0x0000a51c, 0x2502002b, 0x2502002b, 0x19000404, 0x19000404}, + {0x0000a520, 0x2a04002a, 0x2a04002a, 0x1c000603, 0x1c000603}, + {0x0000a524, 0x2e06002a, 0x2e06002a, 0x21000a02, 0x21000a02}, + {0x0000a528, 0x3302202d, 0x3302202d, 0x25000a04, 0x25000a04}, + {0x0000a52c, 0x3804202c, 0x3804202c, 0x28000a20, 0x28000a20}, + {0x0000a530, 0x3c06202c, 0x3c06202c, 0x2c000e20, 0x2c000e20}, + {0x0000a534, 0x4108202d, 0x4108202d, 0x30000e22, 0x30000e22}, + {0x0000a538, 0x4506402d, 0x4506402d, 0x34000e24, 0x34000e24}, + {0x0000a53c, 0x4906222d, 0x4906222d, 0x38001640, 0x38001640}, + {0x0000a540, 0x4d062231, 0x4d062231, 0x3c001660, 0x3c001660}, + {0x0000a544, 0x50082231, 0x50082231, 0x3f001861, 0x3f001861}, + {0x0000a548, 0x5608422e, 0x5608422e, 0x43001a81, 0x43001a81}, + {0x0000a54c, 0x5e08442e, 0x5e08442e, 0x47001a83, 0x47001a83}, + {0x0000a550, 0x620a4431, 0x620a4431, 0x4a001c84, 0x4a001c84}, + {0x0000a554, 0x640a4432, 0x640a4432, 0x4e001ce3, 0x4e001ce3}, + {0x0000a558, 0x680a4434, 0x680a4434, 0x52001ce5, 0x52001ce5}, + {0x0000a55c, 0x6c0a6434, 0x6c0a6434, 0x56001ce9, 0x56001ce9}, + {0x0000a560, 0x6f0a6633, 0x6f0a6633, 0x5a001ceb, 0x5a001ceb}, + {0x0000a564, 0x730c6634, 0x730c6634, 0x5d001eec, 0x5d001eec}, + {0x0000a568, 0x730c6634, 0x730c6634, 0x5d001eec, 0x5d001eec}, + {0x0000a56c, 0x730c6634, 0x730c6634, 0x5d001eec, 0x5d001eec}, + {0x0000a570, 0x730c6634, 0x730c6634, 0x5d001eec, 0x5d001eec}, + {0x0000a574, 0x730c6634, 0x730c6634, 0x5d001eec, 0x5d001eec}, + {0x0000a578, 0x730c6634, 0x730c6634, 0x5d001eec, 0x5d001eec}, + {0x0000a57c, 0x730c6634, 0x730c6634, 0x5d001eec, 0x5d001eec}, + {0x0000a600, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a604, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a608, 0x01804601, 0x01804601, 0x00000000, 0x00000000}, + {0x0000a60c, 0x01804601, 0x01804601, 0x00000000, 0x00000000}, + {0x0000a610, 0x01804601, 0x01804601, 0x00000000, 0x00000000}, + {0x0000a614, 0x01804601, 0x01804601, 0x01404000, 0x01404000}, + {0x0000a618, 0x01804601, 0x01804601, 0x01404501, 0x01404501}, + {0x0000a61c, 0x01804601, 0x01804601, 0x02008501, 0x02008501}, + {0x0000a620, 0x03408d02, 0x03408d02, 0x0280ca03, 0x0280ca03}, + {0x0000a624, 0x0300cc03, 0x0300cc03, 0x03010c04, 0x03010c04}, + {0x0000a628, 0x03410d04, 0x03410d04, 0x04014c04, 0x04014c04}, + {0x0000a62c, 0x03410d04, 0x03410d04, 0x04015005, 0x04015005}, + {0x0000a630, 0x03410d04, 0x03410d04, 0x04015005, 0x04015005}, + {0x0000a634, 0x03410d04, 0x03410d04, 0x04015005, 0x04015005}, + {0x0000a638, 0x03410d04, 0x03410d04, 0x04015005, 0x04015005}, + {0x0000a63c, 0x03410d04, 0x03410d04, 0x04015005, 0x04015005}, + {0x0000b2dc, 0x000cfff0, 0x000cfff0, 0x03aaa352, 0x03aaa352}, + {0x0000b2e0, 0x000f0000, 0x000f0000, 0x03ccc584, 0x03ccc584}, + {0x0000b2e4, 0x03f00000, 0x03f00000, 0x03f0f800, 0x03f0f800}, + {0x0000b2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x0000c2dc, 0x000cfff0, 0x000cfff0, 0x03aaa352, 0x03aaa352}, + {0x0000c2e0, 0x000f0000, 0x000f0000, 0x03ccc584, 0x03ccc584}, + {0x0000c2e4, 0x03f00000, 0x03f00000, 0x03f0f800, 0x03f0f800}, + {0x0000c2e8, 0x00000000, 0x00000000, 0x03ff0000, 0x03ff0000}, + {0x00016044, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016048, 0x61200001, 0x61200001, 0x66480001, 0x66480001}, + {0x00016068, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016444, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016448, 0x61200001, 0x61200001, 0x66480001, 0x66480001}, + {0x00016468, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, + {0x00016844, 0x012492d4, 0x012492d4, 0x012492d4, 0x012492d4}, + {0x00016848, 0x61200001, 0x61200001, 0x66480001, 0x66480001}, + {0x00016868, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c, 0x6db6db6c}, +}; + +static const u32 ar9580_1p0_soc_preamble[][2] = { + /* Addr allmodes */ + {0x000040a4, 0x00a0c1c9}, + {0x00007008, 0x00000000}, + {0x00007020, 0x00000000}, + {0x00007034, 0x00000002}, + {0x00007038, 0x000004c2}, + {0x00007048, 0x00000008}, +}; + +#define ar9580_1p0_rx_gain_table ar9462_common_rx_gain_table_2p0 + +static const u32 ar9580_1p0_radio_core[][2] = { + /* Addr allmodes */ + {0x00016000, 0x36db6db6}, + {0x00016004, 0x6db6db40}, + {0x00016008, 0x73f00000}, + {0x0001600c, 0x00000000}, + {0x00016040, 0x7f80fff8}, + {0x0001604c, 0x76d005b5}, + {0x00016050, 0x556cf031}, + {0x00016054, 0x13449440}, + {0x00016058, 0x0c51c92c}, + {0x0001605c, 0x3db7fffc}, + {0x00016060, 0xfffffffc}, + {0x00016064, 0x000f0278}, + {0x0001606c, 0x6db60000}, + {0x00016080, 0x00000000}, + {0x00016084, 0x0e48048c}, + {0x00016088, 0x54214514}, + {0x0001608c, 0x119f481e}, + {0x00016090, 0x24926490}, + {0x00016098, 0xd2888888}, + {0x000160a0, 0x0a108ffe}, + {0x000160a4, 0x812fc370}, + {0x000160a8, 0x423c8000}, + {0x000160b4, 0x92480080}, + {0x000160c0, 0x00adb6d0}, + {0x000160c4, 0x6db6db60}, + {0x000160c8, 0x6db6db6c}, + {0x000160cc, 0x01e6c000}, + {0x00016100, 0x3fffbe01}, + {0x00016104, 0xfff80000}, + {0x00016108, 0x00080010}, + {0x00016144, 0x02084080}, + {0x00016148, 0x00000000}, + {0x00016280, 0x058a0001}, + {0x00016284, 0x3d840208}, + {0x00016288, 0x05a20408}, + {0x0001628c, 0x00038c07}, + {0x00016290, 0x00000004}, + {0x00016294, 0x458aa14f}, + {0x00016380, 0x00000000}, + {0x00016384, 0x00000000}, + {0x00016388, 0x00800700}, + {0x0001638c, 0x00800700}, + {0x00016390, 0x00800700}, + {0x00016394, 0x00000000}, + {0x00016398, 0x00000000}, + {0x0001639c, 0x00000000}, + {0x000163a0, 0x00000001}, + {0x000163a4, 0x00000001}, + {0x000163a8, 0x00000000}, + {0x000163ac, 0x00000000}, + {0x000163b0, 0x00000000}, + {0x000163b4, 0x00000000}, + {0x000163b8, 0x00000000}, + {0x000163bc, 0x00000000}, + {0x000163c0, 0x000000a0}, + {0x000163c4, 0x000c0000}, + {0x000163c8, 0x14021402}, + {0x000163cc, 0x00001402}, + {0x000163d0, 0x00000000}, + {0x000163d4, 0x00000000}, + {0x00016400, 0x36db6db6}, + {0x00016404, 0x6db6db40}, + {0x00016408, 0x73f00000}, + {0x0001640c, 0x00000000}, + {0x00016440, 0x7f80fff8}, + {0x0001644c, 0x76d005b5}, + {0x00016450, 0x556cf031}, + {0x00016454, 0x13449440}, + {0x00016458, 0x0c51c92c}, + {0x0001645c, 0x3db7fffc}, + {0x00016460, 0xfffffffc}, + {0x00016464, 0x000f0278}, + {0x0001646c, 0x6db60000}, + {0x00016500, 0x3fffbe01}, + {0x00016504, 0xfff80000}, + {0x00016508, 0x00080010}, + {0x00016544, 0x02084080}, + {0x00016548, 0x00000000}, + {0x00016780, 0x00000000}, + {0x00016784, 0x00000000}, + {0x00016788, 0x00800700}, + {0x0001678c, 0x00800700}, + {0x00016790, 0x00800700}, + {0x00016794, 0x00000000}, + {0x00016798, 0x00000000}, + {0x0001679c, 0x00000000}, + {0x000167a0, 0x00000001}, + {0x000167a4, 0x00000001}, + {0x000167a8, 0x00000000}, + {0x000167ac, 0x00000000}, + {0x000167b0, 0x00000000}, + {0x000167b4, 0x00000000}, + {0x000167b8, 0x00000000}, + {0x000167bc, 0x00000000}, + {0x000167c0, 0x000000a0}, + {0x000167c4, 0x000c0000}, + {0x000167c8, 0x14021402}, + {0x000167cc, 0x00001402}, + {0x000167d0, 0x00000000}, + {0x000167d4, 0x00000000}, + {0x00016800, 0x36db6db6}, + {0x00016804, 0x6db6db40}, + {0x00016808, 0x73f00000}, + {0x0001680c, 0x00000000}, + {0x00016840, 0x7f80fff8}, + {0x0001684c, 0x76d005b5}, + {0x00016850, 0x556cf031}, + {0x00016854, 0x13449440}, + {0x00016858, 0x0c51c92c}, + {0x0001685c, 0x3db7fffc}, + {0x00016860, 0xfffffffc}, + {0x00016864, 0x000f0278}, + {0x0001686c, 0x6db60000}, + {0x00016900, 0x3fffbe01}, + {0x00016904, 0xfff80000}, + {0x00016908, 0x00080010}, + {0x00016944, 0x02084080}, + {0x00016948, 0x00000000}, + {0x00016b80, 0x00000000}, + {0x00016b84, 0x00000000}, + {0x00016b88, 0x00800700}, + {0x00016b8c, 0x00800700}, + {0x00016b90, 0x00800700}, + {0x00016b94, 0x00000000}, + {0x00016b98, 0x00000000}, + {0x00016b9c, 0x00000000}, + {0x00016ba0, 0x00000001}, + {0x00016ba4, 0x00000001}, + {0x00016ba8, 0x00000000}, + {0x00016bac, 0x00000000}, + {0x00016bb0, 0x00000000}, + {0x00016bb4, 0x00000000}, + {0x00016bb8, 0x00000000}, + {0x00016bbc, 0x00000000}, + {0x00016bc0, 0x000000a0}, + {0x00016bc4, 0x000c0000}, + {0x00016bc8, 0x14021402}, + {0x00016bcc, 0x00001402}, + {0x00016bd0, 0x00000000}, + {0x00016bd4, 0x00000000}, +}; + +static const u32 ar9580_1p0_baseband_postamble[][5] = { + /* Addr 5G_HT20 5G_HT40 2G_HT40 2G_HT20 */ + {0x00009810, 0xd00a8005, 0xd00a8005, 0xd00a8011, 0xd00a8011}, + {0x00009820, 0x206a022e, 0x206a022e, 0x206a012e, 0x206a012e}, + {0x00009824, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0, 0x5ac640d0}, + {0x00009828, 0x06903081, 0x06903081, 0x06903881, 0x06903881}, + {0x0000982c, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4, 0x05eea6d4}, + {0x00009830, 0x0000059c, 0x0000059c, 0x0000119c, 0x0000119c}, + {0x00009c00, 0x000000c4, 0x000000c4, 0x000000c4, 0x000000c4}, + {0x00009e00, 0x0372111a, 0x0372111a, 0x037216a0, 0x037216a0}, + {0x00009e04, 0x001c2020, 0x001c2020, 0x001c2020, 0x001c2020}, + {0x00009e0c, 0x6c4000e2, 0x6d4000e2, 0x6d4000e2, 0x6c4000e2}, + {0x00009e10, 0x7ec88d2e, 0x7ec88d2e, 0x7ec84d2e, 0x7ec84d2e}, + {0x00009e14, 0x37b95d5e, 0x37b9605e, 0x3379605e, 0x33795d5e}, + {0x00009e18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x00009e1c, 0x0001cf9c, 0x0001cf9c, 0x00021f9c, 0x00021f9c}, + {0x00009e20, 0x000003b5, 0x000003b5, 0x000003ce, 0x000003ce}, + {0x00009e2c, 0x0000001c, 0x0000001c, 0x00000021, 0x00000021}, + {0x00009e3c, 0xcf946220, 0xcf946220, 0xcf946222, 0xcf946222}, + {0x00009e44, 0x02321e27, 0x02321e27, 0x02291e27, 0x02291e27}, + {0x00009e48, 0x5030201a, 0x5030201a, 0x50302012, 0x50302012}, + {0x00009fc8, 0x0003f000, 0x0003f000, 0x0001a000, 0x0001a000}, + {0x0000a204, 0x000036c0, 0x000036c4, 0x000036c4, 0x000036c0}, + {0x0000a208, 0x00000104, 0x00000104, 0x00000004, 0x00000004}, + {0x0000a22c, 0x07e26a2f, 0x07e26a2f, 0x01026a2f, 0x01026a2f}, + {0x0000a230, 0x0000000a, 0x00000014, 0x00000016, 0x0000000b}, + {0x0000a234, 0x00000fff, 0x10000fff, 0x10000fff, 0x00000fff}, + {0x0000a238, 0xffb01018, 0xffb01018, 0xffb01018, 0xffb01018}, + {0x0000a250, 0x00000000, 0x00000000, 0x00000210, 0x00000108}, + {0x0000a254, 0x000007d0, 0x00000fa0, 0x00001130, 0x00000898}, + {0x0000a258, 0x02020002, 0x02020002, 0x02020002, 0x02020002}, + {0x0000a25c, 0x01000e0e, 0x01000e0e, 0x01000e0e, 0x01000e0e}, + {0x0000a260, 0x0a021501, 0x0a021501, 0x3a021501, 0x3a021501}, + {0x0000a264, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e}, + {0x0000a280, 0x00000007, 0x00000007, 0x0000000b, 0x0000000b}, + {0x0000a284, 0x00000000, 0x00000000, 0x00000150, 0x00000150}, + {0x0000a288, 0x00000110, 0x00000110, 0x00000110, 0x00000110}, + {0x0000a28c, 0x00022222, 0x00022222, 0x00022222, 0x00022222}, + {0x0000a2c4, 0x00158d18, 0x00158d18, 0x00158d18, 0x00158d18}, + {0x0000a2d0, 0x00041981, 0x00041981, 0x00041981, 0x00041982}, + {0x0000a2d8, 0x7999a83b, 0x7999a83b, 0x7999a83b, 0x7999a83b}, + {0x0000a358, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000a830, 0x0000019c, 0x0000019c, 0x0000019c, 0x0000019c}, + {0x0000ae04, 0x001c0000, 0x001c0000, 0x001c0000, 0x001c0000}, + {0x0000ae18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000ae1c, 0x0000019c, 0x0000019c, 0x0000019c, 0x0000019c}, + {0x0000ae20, 0x000001b5, 0x000001b5, 0x000001ce, 0x000001ce}, + {0x0000b284, 0x00000000, 0x00000000, 0x00000150, 0x00000150}, + {0x0000b830, 0x0000019c, 0x0000019c, 0x0000019c, 0x0000019c}, + {0x0000be04, 0x001c0000, 0x001c0000, 0x001c0000, 0x001c0000}, + {0x0000be18, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, + {0x0000be1c, 0x0000019c, 0x0000019c, 0x0000019c, 0x0000019c}, + {0x0000be20, 0x000001b5, 0x000001b5, 0x000001ce, 0x000001ce}, + {0x0000c284, 0x00000000, 0x00000000, 0x00000150, 0x00000150}, +}; + +static const u32 ar9580_1p0_pcie_phy_clkreq_enable_L1[][2] = { + /* Addr allmodes */ + {0x00004040, 0x0835365e}, + {0x00004040, 0x0008003b}, + {0x00004044, 0x00000000}, +}; + +static const u32 ar9580_1p0_pcie_phy_clkreq_disable_L1[][2] = { + /* Addr allmodes */ + {0x00004040, 0x0831365e}, + {0x00004040, 0x0008003b}, + {0x00004044, 0x00000000}, +}; + +static const u32 ar9580_1p0_pcie_phy_pll_on_clkreq[][2] = { + /* Addr allmodes */ + {0x00004040, 0x0831265e}, + {0x00004040, 0x0008003b}, + {0x00004044, 0x00000000}, +}; + +#endif /* INITVALS_9580_1P0_H */ diff --git a/drivers/net/wireless/ath/ath9k/ath9k.h b/drivers/net/wireless/ath/ath9k/ath9k.h new file mode 100644 index 0000000..7131fbc --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/ath9k.h @@ -0,0 +1,945 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef ATH9K_H +#define ATH9K_H + +#include +#include +#include +#include +#include + +#include "debug.h" +#include "common.h" +#include "mci.h" +#include "dfs.h" + +/* + * Header for the ath9k.ko driver core *only* -- hw code nor any other driver + * should rely on this file or its contents. + */ + +struct ath_node; + +/* Macro to expand scalars to 64-bit objects */ + +#define ito64(x) (sizeof(x) == 1) ? \ + (((unsigned long long int)(x)) & (0xff)) : \ + (sizeof(x) == 2) ? \ + (((unsigned long long int)(x)) & 0xffff) : \ + ((sizeof(x) == 4) ? \ + (((unsigned long long int)(x)) & 0xffffffff) : \ + (unsigned long long int)(x)) + +/* increment with wrap-around */ +#define INCR(_l, _sz) do { \ + (_l)++; \ + (_l) &= ((_sz) - 1); \ + } while (0) + +/* decrement with wrap-around */ +#define DECR(_l, _sz) do { \ + (_l)--; \ + (_l) &= ((_sz) - 1); \ + } while (0) + +#define TSF_TO_TU(_h,_l) \ + ((((u32)(_h)) << 22) | (((u32)(_l)) >> 10)) + +#define ATH_TXQ_SETUP(sc, i) ((sc)->tx.txqsetup & (1<bf_stale = false; \ + (_bf)->bf_lastbf = NULL; \ + (_bf)->bf_next = NULL; \ + memset(&((_bf)->bf_state), 0, \ + sizeof(struct ath_buf_state)); \ + } while (0) + +/** + * enum buffer_type - Buffer type flags + * + * @BUF_AMPDU: This buffer is an ampdu, as part of an aggregate (during TX) + * @BUF_AGGR: Indicates whether the buffer can be aggregated + * (used in aggregation scheduling) + */ +enum buffer_type { + BUF_AMPDU = BIT(0), + BUF_AGGR = BIT(1), +}; + +#define bf_isampdu(bf) (bf->bf_state.bf_type & BUF_AMPDU) +#define bf_isaggr(bf) (bf->bf_state.bf_type & BUF_AGGR) + +#define ATH_TXSTATUS_RING_SIZE 512 + +#define DS2PHYS(_dd, _ds) \ + ((_dd)->dd_desc_paddr + ((caddr_t)(_ds) - (caddr_t)(_dd)->dd_desc)) +#define ATH_DESC_4KB_BOUND_CHECK(_daddr) ((((_daddr) & 0xFFF) > 0xF7F) ? 1 : 0) +#define ATH_DESC_4KB_BOUND_NUM_SKIPPED(_len) ((_len) / 4096) + +struct ath_descdma { + void *dd_desc; + dma_addr_t dd_desc_paddr; + u32 dd_desc_len; +}; + +int ath_descdma_setup(struct ath_softc *sc, struct ath_descdma *dd, + struct list_head *head, const char *name, + int nbuf, int ndesc, bool is_tx); + +/***********/ +/* RX / TX */ +/***********/ + +#define ATH_RXBUF 512 +#define ATH_TXBUF 512 +#define ATH_TXBUF_RESERVE 5 +#define ATH_MAX_QDEPTH (ATH_TXBUF / 4 - ATH_TXBUF_RESERVE) +#define ATH_TXMAXTRY 13 + +#define TID_TO_WME_AC(_tid) \ + ((((_tid) == 0) || ((_tid) == 3)) ? IEEE80211_AC_BE : \ + (((_tid) == 1) || ((_tid) == 2)) ? IEEE80211_AC_BK : \ + (((_tid) == 4) || ((_tid) == 5)) ? IEEE80211_AC_VI : \ + IEEE80211_AC_VO) + +#define ATH_AGGR_DELIM_SZ 4 +#define ATH_AGGR_MINPLEN 256 /* in bytes, minimum packet length */ +/* number of delimiters for encryption padding */ +#define ATH_AGGR_ENCRYPTDELIM 10 +/* minimum h/w qdepth to be sustained to maximize aggregation */ +#define ATH_AGGR_MIN_QDEPTH 2 +#define ATH_AMPDU_SUBFRAME_DEFAULT 32 + +#define IEEE80211_SEQ_SEQ_SHIFT 4 +#define IEEE80211_SEQ_MAX 4096 +#define IEEE80211_WEP_IVLEN 3 +#define IEEE80211_WEP_KIDLEN 1 +#define IEEE80211_WEP_CRCLEN 4 +#define IEEE80211_MAX_MPDU_LEN (3840 + FCS_LEN + \ + (IEEE80211_WEP_IVLEN + \ + IEEE80211_WEP_KIDLEN + \ + IEEE80211_WEP_CRCLEN)) + +/* return whether a bit at index _n in bitmap _bm is set + * _sz is the size of the bitmap */ +#define ATH_BA_ISSET(_bm, _n) (((_n) < (WME_BA_BMP_SIZE)) && \ + ((_bm)[(_n) >> 5] & (1 << ((_n) & 31)))) + +/* return block-ack bitmap index given sequence and starting sequence */ +#define ATH_BA_INDEX(_st, _seq) (((_seq) - (_st)) & (IEEE80211_SEQ_MAX - 1)) + +/* return the seqno for _start + _offset */ +#define ATH_BA_INDEX2SEQ(_seq, _offset) (((_seq) + (_offset)) & (IEEE80211_SEQ_MAX - 1)) + +/* returns delimiter padding required given the packet length */ +#define ATH_AGGR_GET_NDELIM(_len) \ + (((_len) >= ATH_AGGR_MINPLEN) ? 0 : \ + DIV_ROUND_UP(ATH_AGGR_MINPLEN - (_len), ATH_AGGR_DELIM_SZ)) + +#define BAW_WITHIN(_start, _bawsz, _seqno) \ + ((((_seqno) - (_start)) & 4095) < (_bawsz)) + +#define ATH_AN_2_TID(_an, _tidno) (&(_an)->tid[(_tidno)]) + +#define IS_CCK_RATE(rate) ((rate >= 0x18) && (rate <= 0x1e)) + +#define ATH_TX_COMPLETE_POLL_INT 1000 + +enum ATH_AGGR_STATUS { + ATH_AGGR_DONE, + ATH_AGGR_BAW_CLOSED, + ATH_AGGR_LIMITED, +}; + +#define ATH_TXFIFO_DEPTH 8 +struct ath_txq { + int mac80211_qnum; /* mac80211 queue number, -1 means not mac80211 Q */ + u32 axq_qnum; /* ath9k hardware queue number */ + void *axq_link; + struct list_head axq_q; + spinlock_t axq_lock; + u32 axq_depth; + u32 axq_ampdu_depth; + bool stopped; + bool axq_tx_inprogress; + struct list_head axq_acq; + struct list_head txq_fifo[ATH_TXFIFO_DEPTH]; + u8 txq_headidx; + u8 txq_tailidx; + int pending_frames; + struct sk_buff_head complete_q; +}; + +struct ath_atx_ac { + struct ath_txq *txq; + int sched; + struct list_head list; + struct list_head tid_q; + bool clear_ps_filter; +}; + +struct ath_frame_info { + struct ath_buf *bf; + int framelen; + enum ath9k_key_type keytype; + u8 keyix; + u8 retries; + u8 rtscts_rate; +}; + +struct ath_buf_state { + u8 bf_type; + u8 bfs_paprd; + u8 ndelim; + u16 seqno; + unsigned long bfs_paprd_timestamp; +}; + +struct ath_buf { + struct list_head list; + struct ath_buf *bf_lastbf; /* last buf of this unit (a frame or + an aggregate) */ + struct ath_buf *bf_next; /* next subframe in the aggregate */ + struct sk_buff *bf_mpdu; /* enclosing frame structure */ + void *bf_desc; /* virtual addr of desc */ + dma_addr_t bf_daddr; /* physical addr of desc */ + dma_addr_t bf_buf_addr; /* physical addr of data buffer, for DMA */ + bool bf_stale; + struct ieee80211_tx_rate rates[4]; + struct ath_buf_state bf_state; +}; + +struct ath_atx_tid { + struct list_head list; + struct sk_buff_head buf_q; + struct ath_node *an; + struct ath_atx_ac *ac; + unsigned long tx_buf[BITS_TO_LONGS(ATH_TID_MAX_BUFS)]; + int bar_index; + u16 seq_start; + u16 seq_next; + u16 baw_size; + int tidno; + int baw_head; /* first un-acked tx buffer */ + int baw_tail; /* next unused tx buffer slot */ + bool sched; + bool paused; + bool active; +}; + +struct ath_node { + struct ath_softc *sc; + struct ieee80211_sta *sta; /* station struct we're part of */ + struct ieee80211_vif *vif; /* interface with which we're associated */ + struct ath_atx_tid tid[IEEE80211_NUM_TIDS]; + struct ath_atx_ac ac[IEEE80211_NUM_ACS]; + int ps_key; + + u16 maxampdu; + u8 mpdudensity; + + bool sleeping; + +#if defined(CPTCFG_MAC80211_DEBUGFS) && defined(CPTCFG_ATH9K_DEBUGFS) + struct dentry *node_stat; +#endif +}; + +struct ath_tx_control { + struct ath_txq *txq; + struct ath_node *an; + u8 paprd; + struct ieee80211_sta *sta; +}; + +#define ATH_TX_ERROR 0x01 + +/** + * @txq_map: Index is mac80211 queue number. This is + * not necessarily the same as the hardware queue number + * (axq_qnum). + */ +struct ath_tx { + u16 seq_no; + u32 txqsetup; + spinlock_t txbuflock; + struct list_head txbuf; + struct ath_txq txq[ATH9K_NUM_TX_QUEUES]; + struct ath_descdma txdma; + struct ath_txq *txq_map[IEEE80211_NUM_ACS]; + u32 txq_max_pending[IEEE80211_NUM_ACS]; + u16 max_aggr_framelen[IEEE80211_NUM_ACS][4][32]; +}; + +struct ath_rx_edma { + struct sk_buff_head rx_fifo; + u32 rx_fifo_hwsize; +}; + +struct ath_rx { + u8 defant; + u8 rxotherant; + bool discard_next; + u32 *rxlink; + u32 num_pkts; + unsigned int rxfilter; + struct list_head rxbuf; + struct ath_descdma rxdma; + struct ath_rx_edma rx_edma[ATH9K_RX_QUEUE_MAX]; + + struct ath_buf *buf_hold; + struct sk_buff *frag; + + u32 ampdu_ref; +}; + +int ath_startrecv(struct ath_softc *sc); +bool ath_stoprecv(struct ath_softc *sc); +u32 ath_calcrxfilter(struct ath_softc *sc); +int ath_rx_init(struct ath_softc *sc, int nbufs); +void ath_rx_cleanup(struct ath_softc *sc); +int ath_rx_tasklet(struct ath_softc *sc, int flush, bool hp); +struct ath_txq *ath_txq_setup(struct ath_softc *sc, int qtype, int subtype); +void ath_txq_lock(struct ath_softc *sc, struct ath_txq *txq); +void ath_txq_unlock(struct ath_softc *sc, struct ath_txq *txq); +void ath_txq_unlock_complete(struct ath_softc *sc, struct ath_txq *txq); +void ath_tx_cleanupq(struct ath_softc *sc, struct ath_txq *txq); +bool ath_drain_all_txq(struct ath_softc *sc); +void ath_draintxq(struct ath_softc *sc, struct ath_txq *txq); +void ath_tx_node_init(struct ath_softc *sc, struct ath_node *an); +void ath_tx_node_cleanup(struct ath_softc *sc, struct ath_node *an); +void ath_txq_schedule(struct ath_softc *sc, struct ath_txq *txq); +int ath_tx_init(struct ath_softc *sc, int nbufs); +int ath_txq_update(struct ath_softc *sc, int qnum, + struct ath9k_tx_queue_info *q); +void ath_update_max_aggr_framelen(struct ath_softc *sc, int queue, int txop); +int ath_tx_start(struct ieee80211_hw *hw, struct sk_buff *skb, + struct ath_tx_control *txctl); +void ath_tx_tasklet(struct ath_softc *sc); +void ath_tx_edma_tasklet(struct ath_softc *sc); +int ath_tx_aggr_start(struct ath_softc *sc, struct ieee80211_sta *sta, + u16 tid, u16 *ssn); +void ath_tx_aggr_stop(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid); +void ath_tx_aggr_resume(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid); + +void ath_tx_aggr_wakeup(struct ath_softc *sc, struct ath_node *an); +void ath_tx_aggr_sleep(struct ieee80211_sta *sta, struct ath_softc *sc, + struct ath_node *an); + +/********/ +/* VIFs */ +/********/ + +struct ath_vif { + int av_bslot; + bool primary_sta_vif; + __le64 tsf_adjust; /* TSF adjustment for staggered beacons */ + struct ath_buf *av_bcbuf; +}; + +/*******************/ +/* Beacon Handling */ +/*******************/ + +/* + * Regardless of the number of beacons we stagger, (i.e. regardless of the + * number of BSSIDs) if a given beacon does not go out even after waiting this + * number of beacon intervals, the game's up. + */ +#define BSTUCK_THRESH 9 +#define ATH_BCBUF 8 +#define ATH_DEFAULT_BINTVAL 100 /* TU */ +#define ATH_DEFAULT_BMISS_LIMIT 10 +#define IEEE80211_MS_TO_TU(x) (((x) * 1000) / 1024) + +struct ath_beacon_config { + int beacon_interval; + u16 listen_interval; + u16 dtim_period; + u16 bmiss_timeout; + u8 dtim_count; + bool enable_beacon; + bool ibss_creator; +}; + +struct ath_beacon { + enum { + OK, /* no change needed */ + UPDATE, /* update pending */ + COMMIT /* beacon sent, commit change */ + } updateslot; /* slot time update fsm */ + + u32 beaconq; + u32 bmisscnt; + u32 bc_tstamp; + struct ieee80211_vif *bslot[ATH_BCBUF]; + int slottime; + int slotupdate; + struct ath9k_tx_queue_info beacon_qi; + struct ath_descdma bdma; + struct ath_txq *cabq; + struct list_head bbuf; + + bool tx_processed; + bool tx_last; +}; + +void ath9k_beacon_tasklet(unsigned long data); +bool ath9k_allow_beacon_config(struct ath_softc *sc, struct ieee80211_vif *vif); +void ath9k_beacon_config(struct ath_softc *sc, struct ieee80211_vif *vif, + u32 changed); +void ath9k_beacon_assign_slot(struct ath_softc *sc, struct ieee80211_vif *vif); +void ath9k_beacon_remove_slot(struct ath_softc *sc, struct ieee80211_vif *vif); +void ath9k_set_tsfadjust(struct ath_softc *sc, struct ieee80211_vif *vif); +void ath9k_set_beacon(struct ath_softc *sc); + +/*******************/ +/* Link Monitoring */ +/*******************/ + +#define ATH_STA_SHORT_CALINTERVAL 1000 /* 1 second */ +#define ATH_AP_SHORT_CALINTERVAL 100 /* 100 ms */ +#define ATH_ANI_POLLINTERVAL_OLD 100 /* 100 ms */ +#define ATH_ANI_POLLINTERVAL_NEW 1000 /* 1000 ms */ +#define ATH_LONG_CALINTERVAL_INT 1000 /* 1000 ms */ +#define ATH_LONG_CALINTERVAL 30000 /* 30 seconds */ +#define ATH_RESTART_CALINTERVAL 1200000 /* 20 minutes */ +#define ATH_ANI_MAX_SKIP_COUNT 10 + +#define ATH_PAPRD_TIMEOUT 100 /* msecs */ +#define ATH_PLL_WORK_INTERVAL 100 + +void ath_tx_complete_poll_work(struct work_struct *work); +void ath_reset_work(struct work_struct *work); +void ath_hw_check(struct work_struct *work); +void ath_hw_pll_work(struct work_struct *work); +void ath_rx_poll(unsigned long data); +void ath_start_rx_poll(struct ath_softc *sc, u8 nbeacon); +void ath_paprd_calibrate(struct work_struct *work); +void ath_ani_calibrate(unsigned long data); +void ath_start_ani(struct ath_softc *sc); +void ath_stop_ani(struct ath_softc *sc); +void ath_check_ani(struct ath_softc *sc); +int ath_update_survey_stats(struct ath_softc *sc); +void ath_update_survey_nf(struct ath_softc *sc, int channel); +void ath9k_queue_reset(struct ath_softc *sc, enum ath_reset_type type); + +/**********/ +/* BTCOEX */ +/**********/ + +#define ATH_DUMP_BTCOEX(_s, _val) \ + do { \ + len += snprintf(buf + len, size - len, \ + "%20s : %10d\n", _s, (_val)); \ + } while (0) + +enum bt_op_flags { + BT_OP_PRIORITY_DETECTED, + BT_OP_SCAN, +}; + +struct ath_btcoex { + bool hw_timer_enabled; + spinlock_t btcoex_lock; + struct timer_list period_timer; /* Timer for BT period */ + u32 bt_priority_cnt; + unsigned long bt_priority_time; + unsigned long op_flags; + int bt_stomp_type; /* Types of BT stomping */ + u32 btcoex_no_stomp; /* in usec */ + u32 btcoex_period; /* in msec */ + u32 btscan_no_stomp; /* in usec */ + u32 duty_cycle; + u32 bt_wait_time; + int rssi_count; + struct ath_gen_timer *no_stomp_timer; /* Timer for no BT stomping */ + struct ath_mci_profile mci; + u8 stomp_audio; +}; + +#ifdef CPTCFG_ATH9K_BTCOEX_SUPPORT +int ath9k_init_btcoex(struct ath_softc *sc); +void ath9k_deinit_btcoex(struct ath_softc *sc); +void ath9k_start_btcoex(struct ath_softc *sc); +void ath9k_stop_btcoex(struct ath_softc *sc); +void ath9k_btcoex_timer_resume(struct ath_softc *sc); +void ath9k_btcoex_timer_pause(struct ath_softc *sc); +void ath9k_btcoex_handle_interrupt(struct ath_softc *sc, u32 status); +u16 ath9k_btcoex_aggr_limit(struct ath_softc *sc, u32 max_4ms_framelen); +void ath9k_btcoex_stop_gen_timer(struct ath_softc *sc); +int ath9k_dump_btcoex(struct ath_softc *sc, u8 *buf, u32 size); +#else +static inline int ath9k_init_btcoex(struct ath_softc *sc) +{ + return 0; +} +static inline void ath9k_deinit_btcoex(struct ath_softc *sc) +{ +} +static inline void ath9k_start_btcoex(struct ath_softc *sc) +{ +} +static inline void ath9k_stop_btcoex(struct ath_softc *sc) +{ +} +static inline void ath9k_btcoex_handle_interrupt(struct ath_softc *sc, + u32 status) +{ +} +static inline u16 ath9k_btcoex_aggr_limit(struct ath_softc *sc, + u32 max_4ms_framelen) +{ + return 0; +} +static inline void ath9k_btcoex_stop_gen_timer(struct ath_softc *sc) +{ +} +static inline int ath9k_dump_btcoex(struct ath_softc *sc, u8 *buf, u32 size) +{ + return 0; +} +#endif /* CPTCFG_ATH9K_BTCOEX_SUPPORT */ + +struct ath9k_wow_pattern { + u8 pattern_bytes[MAX_PATTERN_SIZE]; + u8 mask_bytes[MAX_PATTERN_SIZE]; + u32 pattern_len; +}; + +/********************/ +/* LED Control */ +/********************/ + +#define ATH_LED_PIN_DEF 1 +#define ATH_LED_PIN_9287 8 +#define ATH_LED_PIN_9300 10 +#define ATH_LED_PIN_9485 6 +#define ATH_LED_PIN_9462 4 + +#ifdef CPTCFG_MAC80211_LEDS +void ath_init_leds(struct ath_softc *sc); +void ath_deinit_leds(struct ath_softc *sc); +void ath_fill_led_pin(struct ath_softc *sc); +#else +static inline void ath_init_leds(struct ath_softc *sc) +{ +} + +static inline void ath_deinit_leds(struct ath_softc *sc) +{ +} +static inline void ath_fill_led_pin(struct ath_softc *sc) +{ +} +#endif + +/*******************************/ +/* Antenna diversity/combining */ +/*******************************/ + +#define ATH_ANT_RX_CURRENT_SHIFT 4 +#define ATH_ANT_RX_MAIN_SHIFT 2 +#define ATH_ANT_RX_MASK 0x3 + +#define ATH_ANT_DIV_COMB_SHORT_SCAN_INTR 50 +#define ATH_ANT_DIV_COMB_SHORT_SCAN_PKTCOUNT 0x100 +#define ATH_ANT_DIV_COMB_MAX_PKTCOUNT 0x200 +#define ATH_ANT_DIV_COMB_INIT_COUNT 95 +#define ATH_ANT_DIV_COMB_MAX_COUNT 100 +#define ATH_ANT_DIV_COMB_ALT_ANT_RATIO 30 +#define ATH_ANT_DIV_COMB_ALT_ANT_RATIO2 20 + +#define ATH_ANT_DIV_COMB_LNA1_LNA2_SWITCH_DELTA -1 +#define ATH_ANT_DIV_COMB_LNA1_DELTA_HI -4 +#define ATH_ANT_DIV_COMB_LNA1_DELTA_MID -2 +#define ATH_ANT_DIV_COMB_LNA1_DELTA_LOW 2 + +enum ath9k_ant_div_comb_lna_conf { + ATH_ANT_DIV_COMB_LNA1_MINUS_LNA2, + ATH_ANT_DIV_COMB_LNA2, + ATH_ANT_DIV_COMB_LNA1, + ATH_ANT_DIV_COMB_LNA1_PLUS_LNA2, +}; + +struct ath_ant_comb { + u16 count; + u16 total_pkt_count; + bool scan; + bool scan_not_start; + int main_total_rssi; + int alt_total_rssi; + int alt_recv_cnt; + int main_recv_cnt; + int rssi_lna1; + int rssi_lna2; + int rssi_add; + int rssi_sub; + int rssi_first; + int rssi_second; + int rssi_third; + bool alt_good; + int quick_scan_cnt; + int main_conf; + enum ath9k_ant_div_comb_lna_conf first_quick_scan_conf; + enum ath9k_ant_div_comb_lna_conf second_quick_scan_conf; + bool first_ratio; + bool second_ratio; + unsigned long scan_start_time; +}; + +void ath_ant_comb_scan(struct ath_softc *sc, struct ath_rx_status *rs); +void ath_ant_comb_update(struct ath_softc *sc); + +/********************/ +/* Main driver core */ +/********************/ + +/* + * Default cache line size, in bytes. + * Used when PCI device not fully initialized by bootrom/BIOS +*/ +#define DEFAULT_CACHELINE 32 +#define ATH_REGCLASSIDS_MAX 10 +#define ATH_CABQ_READY_TIME 80 /* % of beacon interval */ +#define ATH_MAX_SW_RETRIES 30 +#define ATH_CHAN_MAX 255 + +#define ATH_TXPOWER_MAX 100 /* .5 dBm units */ +#define ATH_RATE_DUMMY_MARKER 0 + +enum sc_op_flags { + SC_OP_INVALID, + SC_OP_BEACONS, + SC_OP_ANI_RUN, + SC_OP_PRIM_STA_VIF, + SC_OP_HW_RESET, +}; + +/* Powersave flags */ +#define PS_WAIT_FOR_BEACON BIT(0) +#define PS_WAIT_FOR_CAB BIT(1) +#define PS_WAIT_FOR_PSPOLL_DATA BIT(2) +#define PS_WAIT_FOR_TX_ACK BIT(3) +#define PS_BEACON_SYNC BIT(4) +#define PS_WAIT_FOR_ANI BIT(5) + +struct ath_rate_table; + +struct ath9k_vif_iter_data { + u8 hw_macaddr[ETH_ALEN]; /* address of the first vif */ + u8 mask[ETH_ALEN]; /* bssid mask */ + bool has_hw_macaddr; + + int naps; /* number of AP vifs */ + int nmeshes; /* number of mesh vifs */ + int nstations; /* number of station vifs */ + int nwds; /* number of WDS vifs */ + int nadhocs; /* number of adhoc vifs */ +}; + +/* enum spectral_mode: + * + * @SPECTRAL_DISABLED: spectral mode is disabled + * @SPECTRAL_BACKGROUND: hardware sends samples when it is not busy with + * something else. + * @SPECTRAL_MANUAL: spectral scan is enabled, triggering for samples + * is performed manually. + * @SPECTRAL_CHANSCAN: Like manual, but also triggered when changing channels + * during a channel scan. + */ +enum spectral_mode { + SPECTRAL_DISABLED = 0, + SPECTRAL_BACKGROUND, + SPECTRAL_MANUAL, + SPECTRAL_CHANSCAN, +}; + +struct ath_softc { + struct ieee80211_hw *hw; + struct device *dev; + + struct survey_info *cur_survey; + struct survey_info survey[ATH9K_NUM_CHANNELS]; + + struct tasklet_struct intr_tq; + struct tasklet_struct bcon_tasklet; + struct ath_hw *sc_ah; + void __iomem *mem; + int irq; + spinlock_t sc_serial_rw; + spinlock_t sc_pm_lock; + spinlock_t sc_pcu_lock; + struct mutex mutex; + struct work_struct paprd_work; + struct work_struct hw_check_work; + struct work_struct hw_reset_work; + struct completion paprd_complete; + + unsigned int hw_busy_count; + unsigned long sc_flags; + + u32 intrstatus; + u16 ps_flags; /* PS_* */ + u16 curtxpow; + bool ps_enabled; + bool ps_idle; + short nbcnvifs; + short nvifs; + unsigned long ps_usecount; + + struct ath_config config; + struct ath_rx rx; + struct ath_tx tx; + struct ath_beacon beacon; + struct ieee80211_supported_band sbands[IEEE80211_NUM_BANDS]; + +#ifdef CPTCFG_MAC80211_LEDS + bool led_registered; + char led_name[32]; + struct led_classdev led_cdev; +#endif + + struct ath9k_hw_cal_data caldata; + int last_rssi; + +#ifdef CPTCFG_ATH9K_DEBUGFS + struct ath9k_debug debug; +#endif + struct ath_beacon_config cur_beacon_conf; + struct delayed_work tx_complete_work; + struct delayed_work hw_pll_work; + struct timer_list rx_poll_timer; + +#ifdef CPTCFG_ATH9K_BTCOEX_SUPPORT + struct ath_btcoex btcoex; + struct ath_mci_coex mci_coex; + struct work_struct mci_work; +#endif + + struct ath_descdma txsdma; + + struct ath_ant_comb ant_comb; + u8 ant_tx, ant_rx; + struct dfs_pattern_detector *dfs_detector; + u32 wow_enabled; + /* relay(fs) channel for spectral scan */ + struct rchan *rfs_chan_spec_scan; + enum spectral_mode spectral_mode; + struct ath_spec_scan spec_config; + int scanning; + +#ifdef CONFIG_PM_SLEEP + atomic_t wow_got_bmiss_intr; + atomic_t wow_sleep_proc_intr; /* in the middle of WoW sleep ? */ + u32 wow_intr_before_sleep; +#endif +}; + +#define SPECTRAL_SCAN_BITMASK 0x10 +/* Radar info packet format, used for DFS and spectral formats. */ +struct ath_radar_info { + u8 pulse_length_pri; + u8 pulse_length_ext; + u8 pulse_bw_info; +} __packed; + +/* The HT20 spectral data has 4 bytes of additional information at it's end. + * + * [7:0]: all bins {max_magnitude[1:0], bitmap_weight[5:0]} + * [7:0]: all bins max_magnitude[9:2] + * [7:0]: all bins {max_index[5:0], max_magnitude[11:10]} + * [3:0]: max_exp (shift amount to size max bin to 8-bit unsigned) + */ +struct ath_ht20_mag_info { + u8 all_bins[3]; + u8 max_exp; +} __packed; + +#define SPECTRAL_HT20_NUM_BINS 56 + +/* WARNING: don't actually use this struct! MAC may vary the amount of + * data by -1/+2. This struct is for reference only. + */ +struct ath_ht20_fft_packet { + u8 data[SPECTRAL_HT20_NUM_BINS]; + struct ath_ht20_mag_info mag_info; + struct ath_radar_info radar_info; +} __packed; + +#define SPECTRAL_HT20_TOTAL_DATA_LEN (sizeof(struct ath_ht20_fft_packet)) + +/* Dynamic 20/40 mode: + * + * [7:0]: lower bins {max_magnitude[1:0], bitmap_weight[5:0]} + * [7:0]: lower bins max_magnitude[9:2] + * [7:0]: lower bins {max_index[5:0], max_magnitude[11:10]} + * [7:0]: upper bins {max_magnitude[1:0], bitmap_weight[5:0]} + * [7:0]: upper bins max_magnitude[9:2] + * [7:0]: upper bins {max_index[5:0], max_magnitude[11:10]} + * [3:0]: max_exp (shift amount to size max bin to 8-bit unsigned) + */ +struct ath_ht20_40_mag_info { + u8 lower_bins[3]; + u8 upper_bins[3]; + u8 max_exp; +} __packed; + +#define SPECTRAL_HT20_40_NUM_BINS 128 + +/* WARNING: don't actually use this struct! MAC may vary the amount of + * data. This struct is for reference only. + */ +struct ath_ht20_40_fft_packet { + u8 data[SPECTRAL_HT20_40_NUM_BINS]; + struct ath_ht20_40_mag_info mag_info; + struct ath_radar_info radar_info; +} __packed; + + +#define SPECTRAL_HT20_40_TOTAL_DATA_LEN (sizeof(struct ath_ht20_40_fft_packet)) + +/* grabs the max magnitude from the all/upper/lower bins */ +static inline u16 spectral_max_magnitude(u8 *bins) +{ + return (bins[0] & 0xc0) >> 6 | + (bins[1] & 0xff) << 2 | + (bins[2] & 0x03) << 10; +} + +/* return the max magnitude from the all/upper/lower bins */ +static inline u8 spectral_max_index(u8 *bins) +{ + s8 m = (bins[2] & 0xfc) >> 2; + + /* TODO: this still doesn't always report the right values ... */ + if (m > 32) + m |= 0xe0; + else + m &= ~0xe0; + + return m + 29; +} + +/* return the bitmap weight from the all/upper/lower bins */ +static inline u8 spectral_bitmap_weight(u8 *bins) +{ + return bins[0] & 0x3f; +} + +/* FFT sample format given to userspace via debugfs. + * + * Please keep the type/length at the front position and change + * other fields after adding another sample type + * + * TODO: this might need rework when switching to nl80211-based + * interface. + */ +enum ath_fft_sample_type { + ATH_FFT_SAMPLE_HT20 = 1, +}; + +struct fft_sample_tlv { + u8 type; /* see ath_fft_sample */ + __be16 length; + /* type dependent data follows */ +} __packed; + +struct fft_sample_ht20 { + struct fft_sample_tlv tlv; + + u8 max_exp; + + __be16 freq; + s8 rssi; + s8 noise; + + __be16 max_magnitude; + u8 max_index; + u8 bitmap_weight; + + __be64 tsf; + + u8 data[SPECTRAL_HT20_NUM_BINS]; +} __packed; + +void ath9k_tasklet(unsigned long data); +int ath_cabq_update(struct ath_softc *); + +static inline void ath_read_cachesize(struct ath_common *common, int *csz) +{ + common->bus_ops->read_cachesize(common, csz); +} + +extern struct ieee80211_ops ath9k_ops; +extern int ath9k_modparam_nohwcrypt; +extern int led_blink; +extern bool is_ath9k_unloaded; + +u8 ath9k_parse_mpdudensity(u8 mpdudensity); +irqreturn_t ath_isr(int irq, void *dev); +int ath9k_init_device(u16 devid, struct ath_softc *sc, + const struct ath_bus_ops *bus_ops); +void ath9k_deinit_device(struct ath_softc *sc); +void ath9k_set_hw_capab(struct ath_softc *sc, struct ieee80211_hw *hw); +void ath9k_reload_chainmask_settings(struct ath_softc *sc); + +bool ath9k_uses_beacons(int type); +void ath9k_spectral_scan_trigger(struct ieee80211_hw *hw); +int ath9k_spectral_scan_config(struct ieee80211_hw *hw, + enum spectral_mode spectral_mode); + + +#ifdef CPTCFG_ATH9K_PCI +int ath_pci_init(void); +void ath_pci_exit(void); +#else +static inline int ath_pci_init(void) { return 0; }; +static inline void ath_pci_exit(void) {}; +#endif + +#ifdef CPTCFG_ATH9K_AHB +int ath_ahb_init(void); +void ath_ahb_exit(void); +#else +static inline int ath_ahb_init(void) { return 0; }; +static inline void ath_ahb_exit(void) {}; +#endif + +void ath9k_ps_wakeup(struct ath_softc *sc); +void ath9k_ps_restore(struct ath_softc *sc); + +u8 ath_txchainmask_reduction(struct ath_softc *sc, u8 chainmask, u32 rate); + +void ath_start_rfkill_poll(struct ath_softc *sc); +extern void ath9k_rfkill_poll_state(struct ieee80211_hw *hw); +void ath9k_calculate_iter_data(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ath9k_vif_iter_data *iter_data); + +#endif /* ATH9K_H */ diff --git a/drivers/net/wireless/ath/ath9k/beacon.c b/drivers/net/wireless/ath/ath9k/beacon.c new file mode 100644 index 0000000..2ff570f --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/beacon.c @@ -0,0 +1,781 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "ath9k.h" + +#define FUDGE 2 + +static void ath9k_reset_beacon_status(struct ath_softc *sc) +{ + sc->beacon.tx_processed = false; + sc->beacon.tx_last = false; +} + +/* + * This function will modify certain transmit queue properties depending on + * the operating mode of the station (AP or AdHoc). Parameters are AIFS + * settings and channel width min/max +*/ +static void ath9k_beaconq_config(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_tx_queue_info qi, qi_be; + struct ath_txq *txq; + + ath9k_hw_get_txq_props(ah, sc->beacon.beaconq, &qi); + + if (sc->sc_ah->opmode == NL80211_IFTYPE_AP) { + /* Always burst out beacon and CAB traffic. */ + qi.tqi_aifs = 1; + qi.tqi_cwmin = 0; + qi.tqi_cwmax = 0; + } else { + /* Adhoc mode; important thing is to use 2x cwmin. */ + txq = sc->tx.txq_map[IEEE80211_AC_BE]; + ath9k_hw_get_txq_props(ah, txq->axq_qnum, &qi_be); + qi.tqi_aifs = qi_be.tqi_aifs; + if (ah->slottime == ATH9K_SLOT_TIME_20) + qi.tqi_cwmin = 2*qi_be.tqi_cwmin; + else + qi.tqi_cwmin = 4*qi_be.tqi_cwmin; + qi.tqi_cwmax = qi_be.tqi_cwmax; + } + + if (!ath9k_hw_set_txq_props(ah, sc->beacon.beaconq, &qi)) { + ath_err(common, "Unable to update h/w beacon queue parameters\n"); + } else { + ath9k_hw_resettxqueue(ah, sc->beacon.beaconq); + } +} + +/* + * Associates the beacon frame buffer with a transmit descriptor. Will set + * up rate codes, and channel flags. Beacons are always sent out at the + * lowest rate, and are not retried. +*/ +static void ath9k_beacon_setup(struct ath_softc *sc, struct ieee80211_vif *vif, + struct ath_buf *bf, int rateidx) +{ + struct sk_buff *skb = bf->bf_mpdu; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath_tx_info info; + struct ieee80211_supported_band *sband; + u8 chainmask = ah->txchainmask; + u8 rate = 0; + + sband = &sc->sbands[common->hw->conf.chandef.chan->band]; + rate = sband->bitrates[rateidx].hw_value; + if (vif->bss_conf.use_short_preamble) + rate |= sband->bitrates[rateidx].hw_value_short; + + memset(&info, 0, sizeof(info)); + info.pkt_len = skb->len + FCS_LEN; + info.type = ATH9K_PKT_TYPE_BEACON; + info.txpower = MAX_RATE_POWER; + info.keyix = ATH9K_TXKEYIX_INVALID; + info.keytype = ATH9K_KEY_TYPE_CLEAR; + info.flags = ATH9K_TXDESC_NOACK | ATH9K_TXDESC_CLRDMASK; + + info.buf_addr[0] = bf->bf_buf_addr; + info.buf_len[0] = roundup(skb->len, 4); + + info.is_first = true; + info.is_last = true; + + info.qcu = sc->beacon.beaconq; + + info.rates[0].Tries = 1; + info.rates[0].Rate = rate; + info.rates[0].ChSel = ath_txchainmask_reduction(sc, chainmask, rate); + + ath9k_hw_set_txdesc(ah, bf->bf_desc, &info); +} + +static void ath9k_tx_cabq(struct ieee80211_hw *hw, struct sk_buff *skb) +{ + struct ath_softc *sc = hw->priv; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_tx_control txctl; + + memset(&txctl, 0, sizeof(struct ath_tx_control)); + txctl.txq = sc->beacon.cabq; + + ath_dbg(common, XMIT, "transmitting CABQ packet, skb: %p\n", skb); + + if (ath_tx_start(hw, skb, &txctl) != 0) { + ath_dbg(common, XMIT, "CABQ TX failed\n"); + ieee80211_free_txskb(hw, skb); + } +} + +static struct ath_buf *ath9k_beacon_generate(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct ath_softc *sc = hw->priv; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_buf *bf; + struct ath_vif *avp = (void *)vif->drv_priv; + struct sk_buff *skb; + struct ath_txq *cabq = sc->beacon.cabq; + struct ieee80211_tx_info *info; + struct ieee80211_mgmt *mgmt_hdr; + int cabq_depth; + + if (avp->av_bcbuf == NULL) + return NULL; + + bf = avp->av_bcbuf; + skb = bf->bf_mpdu; + if (skb) { + dma_unmap_single(sc->dev, bf->bf_buf_addr, + skb->len, DMA_TO_DEVICE); + dev_kfree_skb_any(skb); + bf->bf_buf_addr = 0; + bf->bf_mpdu = NULL; + } + + skb = ieee80211_beacon_get(hw, vif); + if (skb == NULL) + return NULL; + + bf->bf_mpdu = skb; + + mgmt_hdr = (struct ieee80211_mgmt *)skb->data; + mgmt_hdr->u.beacon.timestamp = avp->tsf_adjust; + + info = IEEE80211_SKB_CB(skb); + if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) { + /* + * TODO: make sure the seq# gets assigned properly (vs. other + * TX frames) + */ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + sc->tx.seq_no += 0x10; + hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); + hdr->seq_ctrl |= cpu_to_le16(sc->tx.seq_no); + } + + bf->bf_buf_addr = dma_map_single(sc->dev, skb->data, + skb->len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(sc->dev, bf->bf_buf_addr))) { + dev_kfree_skb_any(skb); + bf->bf_mpdu = NULL; + bf->bf_buf_addr = 0; + ath_err(common, "dma_mapping_error on beaconing\n"); + return NULL; + } + + skb = ieee80211_get_buffered_bc(hw, vif); + + /* + * if the CABQ traffic from previous DTIM is pending and the current + * beacon is also a DTIM. + * 1) if there is only one vif let the cab traffic continue. + * 2) if there are more than one vif and we are using staggered + * beacons, then drain the cabq by dropping all the frames in + * the cabq so that the current vifs cab traffic can be scheduled. + */ + spin_lock_bh(&cabq->axq_lock); + cabq_depth = cabq->axq_depth; + spin_unlock_bh(&cabq->axq_lock); + + if (skb && cabq_depth) { + if (sc->nvifs > 1) { + ath_dbg(common, BEACON, + "Flushing previous cabq traffic\n"); + ath_draintxq(sc, cabq); + } + } + + ath9k_beacon_setup(sc, vif, bf, info->control.rates[0].idx); + + while (skb) { + ath9k_tx_cabq(hw, skb); + skb = ieee80211_get_buffered_bc(hw, vif); + } + + return bf; +} + +void ath9k_beacon_assign_slot(struct ath_softc *sc, struct ieee80211_vif *vif) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_vif *avp = (void *)vif->drv_priv; + int slot; + + avp->av_bcbuf = list_first_entry(&sc->beacon.bbuf, struct ath_buf, list); + list_del(&avp->av_bcbuf->list); + + for (slot = 0; slot < ATH_BCBUF; slot++) { + if (sc->beacon.bslot[slot] == NULL) { + avp->av_bslot = slot; + break; + } + } + + sc->beacon.bslot[avp->av_bslot] = vif; + sc->nbcnvifs++; + + ath_dbg(common, CONFIG, "Added interface at beacon slot: %d\n", + avp->av_bslot); +} + +void ath9k_beacon_remove_slot(struct ath_softc *sc, struct ieee80211_vif *vif) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_vif *avp = (void *)vif->drv_priv; + struct ath_buf *bf = avp->av_bcbuf; + + ath_dbg(common, CONFIG, "Removing interface at beacon slot: %d\n", + avp->av_bslot); + + tasklet_disable(&sc->bcon_tasklet); + + if (bf && bf->bf_mpdu) { + struct sk_buff *skb = bf->bf_mpdu; + dma_unmap_single(sc->dev, bf->bf_buf_addr, + skb->len, DMA_TO_DEVICE); + dev_kfree_skb_any(skb); + bf->bf_mpdu = NULL; + bf->bf_buf_addr = 0; + } + + avp->av_bcbuf = NULL; + sc->beacon.bslot[avp->av_bslot] = NULL; + sc->nbcnvifs--; + list_add_tail(&bf->list, &sc->beacon.bbuf); + + tasklet_enable(&sc->bcon_tasklet); +} + +static int ath9k_beacon_choose_slot(struct ath_softc *sc) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_beacon_config *cur_conf = &sc->cur_beacon_conf; + u16 intval; + u32 tsftu; + u64 tsf; + int slot; + + if (sc->sc_ah->opmode != NL80211_IFTYPE_AP) { + ath_dbg(common, BEACON, "slot 0, tsf: %llu\n", + ath9k_hw_gettsf64(sc->sc_ah)); + return 0; + } + + intval = cur_conf->beacon_interval ? : ATH_DEFAULT_BINTVAL; + tsf = ath9k_hw_gettsf64(sc->sc_ah); + tsf += TU_TO_USEC(sc->sc_ah->config.sw_beacon_response_time); + tsftu = TSF_TO_TU((tsf * ATH_BCBUF) >>32, tsf * ATH_BCBUF); + slot = (tsftu % (intval * ATH_BCBUF)) / intval; + + ath_dbg(common, BEACON, "slot: %d tsf: %llu tsftu: %u\n", + slot, tsf, tsftu / ATH_BCBUF); + + return slot; +} + +void ath9k_set_tsfadjust(struct ath_softc *sc, struct ieee80211_vif *vif) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_beacon_config *cur_conf = &sc->cur_beacon_conf; + struct ath_vif *avp = (void *)vif->drv_priv; + u64 tsfadjust; + + if (avp->av_bslot == 0) + return; + + tsfadjust = cur_conf->beacon_interval * avp->av_bslot / ATH_BCBUF; + avp->tsf_adjust = cpu_to_le64(TU_TO_USEC(tsfadjust)); + + ath_dbg(common, CONFIG, "tsfadjust is: %llu for bslot: %d\n", + (unsigned long long)tsfadjust, avp->av_bslot); +} + +void ath9k_beacon_tasklet(unsigned long data) +{ + struct ath_softc *sc = (struct ath_softc *)data; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath_buf *bf = NULL; + struct ieee80211_vif *vif; + bool edma = !!(ah->caps.hw_caps & ATH9K_HW_CAP_EDMA); + int slot; + + if (test_bit(SC_OP_HW_RESET, &sc->sc_flags)) { + ath_dbg(common, RESET, + "reset work is pending, skip beaconing now\n"); + return; + } + + /* + * Check if the previous beacon has gone out. If + * not don't try to post another, skip this period + * and wait for the next. Missed beacons indicate + * a problem and should not occur. If we miss too + * many consecutive beacons reset the device. + */ + if (ath9k_hw_numtxpending(ah, sc->beacon.beaconq) != 0) { + sc->beacon.bmisscnt++; + + if (!ath9k_hw_check_alive(ah)) + ieee80211_queue_work(sc->hw, &sc->hw_check_work); + + if (sc->beacon.bmisscnt < BSTUCK_THRESH * sc->nbcnvifs) { + ath_dbg(common, BSTUCK, + "missed %u consecutive beacons\n", + sc->beacon.bmisscnt); + ath9k_hw_stop_dma_queue(ah, sc->beacon.beaconq); + if (sc->beacon.bmisscnt > 3) + ath9k_hw_bstuck_nfcal(ah); + } else if (sc->beacon.bmisscnt >= BSTUCK_THRESH) { + ath_dbg(common, BSTUCK, "beacon is officially stuck\n"); + sc->beacon.bmisscnt = 0; + ath9k_queue_reset(sc, RESET_TYPE_BEACON_STUCK); + } + + return; + } + + slot = ath9k_beacon_choose_slot(sc); + vif = sc->beacon.bslot[slot]; + + if (!vif || !vif->bss_conf.enable_beacon) + return; + + bf = ath9k_beacon_generate(sc->hw, vif); + + if (sc->beacon.bmisscnt != 0) { + ath_dbg(common, BSTUCK, "resume beacon xmit after %u misses\n", + sc->beacon.bmisscnt); + sc->beacon.bmisscnt = 0; + } + + /* + * Handle slot time change when a non-ERP station joins/leaves + * an 11g network. The 802.11 layer notifies us via callback, + * we mark updateslot, then wait one beacon before effecting + * the change. This gives associated stations at least one + * beacon interval to note the state change. + * + * NB: The slot time change state machine is clocked according + * to whether we are bursting or staggering beacons. We + * recognize the request to update and record the current + * slot then don't transition until that slot is reached + * again. If we miss a beacon for that slot then we'll be + * slow to transition but we'll be sure at least one beacon + * interval has passed. When bursting slot is always left + * set to ATH_BCBUF so this check is a noop. + */ + if (sc->beacon.updateslot == UPDATE) { + sc->beacon.updateslot = COMMIT; + sc->beacon.slotupdate = slot; + } else if (sc->beacon.updateslot == COMMIT && + sc->beacon.slotupdate == slot) { + ah->slottime = sc->beacon.slottime; + ath9k_hw_init_global_settings(ah); + sc->beacon.updateslot = OK; + } + + if (bf) { + ath9k_reset_beacon_status(sc); + + ath_dbg(common, BEACON, + "Transmitting beacon for slot: %d\n", slot); + + /* NB: cabq traffic should already be queued and primed */ + ath9k_hw_puttxbuf(ah, sc->beacon.beaconq, bf->bf_daddr); + + if (!edma) + ath9k_hw_txstart(ah, sc->beacon.beaconq); + } +} + +/* + * Both nexttbtt and intval have to be in usecs. + */ +static void ath9k_beacon_init(struct ath_softc *sc, u32 nexttbtt, + u32 intval, bool reset_tsf) +{ + struct ath_hw *ah = sc->sc_ah; + + ath9k_hw_disable_interrupts(ah); + if (reset_tsf) + ath9k_hw_reset_tsf(ah); + ath9k_beaconq_config(sc); + ath9k_hw_beaconinit(ah, nexttbtt, intval); + sc->beacon.bmisscnt = 0; + ath9k_hw_set_interrupts(ah); + ath9k_hw_enable_interrupts(ah); +} + +/* + * For multi-bss ap support beacons are either staggered evenly over N slots or + * burst together. For the former arrange for the SWBA to be delivered for each + * slot. Slots that are not occupied will generate nothing. + */ +static void ath9k_beacon_config_ap(struct ath_softc *sc, + struct ath_beacon_config *conf) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + u32 nexttbtt, intval; + + /* NB: the beacon interval is kept internally in TU's */ + intval = TU_TO_USEC(conf->beacon_interval); + intval /= ATH_BCBUF; + nexttbtt = intval; + + if (conf->enable_beacon) + ah->imask |= ATH9K_INT_SWBA; + else + ah->imask &= ~ATH9K_INT_SWBA; + + ath_dbg(common, BEACON, + "AP (%s) nexttbtt: %u intval: %u conf_intval: %u\n", + (conf->enable_beacon) ? "Enable" : "Disable", + nexttbtt, intval, conf->beacon_interval); + + ath9k_beacon_init(sc, nexttbtt, intval, true); +} + +/* + * This sets up the beacon timers according to the timestamp of the last + * received beacon and the current TSF, configures PCF and DTIM + * handling, programs the sleep registers so the hardware will wakeup in + * time to receive beacons, and configures the beacon miss handling so + * we'll receive a BMISS interrupt when we stop seeing beacons from the AP + * we've associated with. + */ +static void ath9k_beacon_config_sta(struct ath_softc *sc, + struct ath_beacon_config *conf) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_beacon_state bs; + int dtimperiod, dtimcount, sleepduration; + int cfpperiod, cfpcount; + u32 nexttbtt = 0, intval, tsftu; + u64 tsf; + int num_beacons, offset, dtim_dec_count, cfp_dec_count; + + /* No need to configure beacon if we are not associated */ + if (!test_bit(SC_OP_PRIM_STA_VIF, &sc->sc_flags)) { + ath_dbg(common, BEACON, + "STA is not yet associated..skipping beacon config\n"); + return; + } + + memset(&bs, 0, sizeof(bs)); + intval = conf->beacon_interval; + + /* + * Setup dtim and cfp parameters according to + * last beacon we received (which may be none). + */ + dtimperiod = conf->dtim_period; + dtimcount = conf->dtim_count; + if (dtimcount >= dtimperiod) /* NB: sanity check */ + dtimcount = 0; + cfpperiod = 1; /* NB: no PCF support yet */ + cfpcount = 0; + + sleepduration = conf->listen_interval * intval; + + /* + * Pull nexttbtt forward to reflect the current + * TSF and calculate dtim+cfp state for the result. + */ + tsf = ath9k_hw_gettsf64(ah); + tsftu = TSF_TO_TU(tsf>>32, tsf) + FUDGE; + + num_beacons = tsftu / intval + 1; + offset = tsftu % intval; + nexttbtt = tsftu - offset; + if (offset) + nexttbtt += intval; + + /* DTIM Beacon every dtimperiod Beacon */ + dtim_dec_count = num_beacons % dtimperiod; + /* CFP every cfpperiod DTIM Beacon */ + cfp_dec_count = (num_beacons / dtimperiod) % cfpperiod; + if (dtim_dec_count) + cfp_dec_count++; + + dtimcount -= dtim_dec_count; + if (dtimcount < 0) + dtimcount += dtimperiod; + + cfpcount -= cfp_dec_count; + if (cfpcount < 0) + cfpcount += cfpperiod; + + bs.bs_intval = intval; + bs.bs_nexttbtt = nexttbtt; + bs.bs_dtimperiod = dtimperiod*intval; + bs.bs_nextdtim = bs.bs_nexttbtt + dtimcount*intval; + bs.bs_cfpperiod = cfpperiod*bs.bs_dtimperiod; + bs.bs_cfpnext = bs.bs_nextdtim + cfpcount*bs.bs_dtimperiod; + bs.bs_cfpmaxduration = 0; + + /* + * Calculate the number of consecutive beacons to miss* before taking + * a BMISS interrupt. The configuration is specified in TU so we only + * need calculate based on the beacon interval. Note that we clamp the + * result to at most 15 beacons. + */ + if (sleepduration > intval) { + bs.bs_bmissthreshold = conf->listen_interval * + ATH_DEFAULT_BMISS_LIMIT / 2; + } else { + bs.bs_bmissthreshold = DIV_ROUND_UP(conf->bmiss_timeout, intval); + if (bs.bs_bmissthreshold > 15) + bs.bs_bmissthreshold = 15; + else if (bs.bs_bmissthreshold <= 0) + bs.bs_bmissthreshold = 1; + } + + /* + * Calculate sleep duration. The configuration is given in ms. + * We ensure a multiple of the beacon period is used. Also, if the sleep + * duration is greater than the DTIM period then it makes senses + * to make it a multiple of that. + * + * XXX fixed at 100ms + */ + + bs.bs_sleepduration = roundup(IEEE80211_MS_TO_TU(100), sleepduration); + if (bs.bs_sleepduration > bs.bs_dtimperiod) + bs.bs_sleepduration = bs.bs_dtimperiod; + + /* TSF out of range threshold fixed at 1 second */ + bs.bs_tsfoor_threshold = ATH9K_TSFOOR_THRESHOLD; + + ath_dbg(common, BEACON, "tsf: %llu tsftu: %u\n", tsf, tsftu); + ath_dbg(common, BEACON, + "bmiss: %u sleep: %u cfp-period: %u maxdur: %u next: %u\n", + bs.bs_bmissthreshold, bs.bs_sleepduration, + bs.bs_cfpperiod, bs.bs_cfpmaxduration, bs.bs_cfpnext); + + /* Set the computed STA beacon timers */ + + ath9k_hw_disable_interrupts(ah); + ath9k_hw_set_sta_beacon_timers(ah, &bs); + ah->imask |= ATH9K_INT_BMISS; + + ath9k_hw_set_interrupts(ah); + ath9k_hw_enable_interrupts(ah); +} + +static void ath9k_beacon_config_adhoc(struct ath_softc *sc, + struct ath_beacon_config *conf) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + u32 intval, nexttbtt; + + ath9k_reset_beacon_status(sc); + + intval = TU_TO_USEC(conf->beacon_interval); + + if (conf->ibss_creator) { + nexttbtt = intval; + } else { + u32 tbtt, offset, tsftu; + u64 tsf; + + /* + * Pull nexttbtt forward to reflect the current + * sync'd TSF. + */ + tsf = ath9k_hw_gettsf64(ah); + tsftu = TSF_TO_TU(tsf >> 32, tsf) + FUDGE; + offset = tsftu % conf->beacon_interval; + tbtt = tsftu - offset; + if (offset) + tbtt += conf->beacon_interval; + + nexttbtt = TU_TO_USEC(tbtt); + } + + if (conf->enable_beacon) + ah->imask |= ATH9K_INT_SWBA; + else + ah->imask &= ~ATH9K_INT_SWBA; + + ath_dbg(common, BEACON, + "IBSS (%s) nexttbtt: %u intval: %u conf_intval: %u\n", + (conf->enable_beacon) ? "Enable" : "Disable", + nexttbtt, intval, conf->beacon_interval); + + ath9k_beacon_init(sc, nexttbtt, intval, conf->ibss_creator); + + /* + * Set the global 'beacon has been configured' flag for the + * joiner case in IBSS mode. + */ + if (!conf->ibss_creator && conf->enable_beacon) + set_bit(SC_OP_BEACONS, &sc->sc_flags); +} + +bool ath9k_allow_beacon_config(struct ath_softc *sc, struct ieee80211_vif *vif) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_vif *avp = (void *)vif->drv_priv; + + if (sc->sc_ah->opmode == NL80211_IFTYPE_AP) { + if ((vif->type != NL80211_IFTYPE_AP) || + (sc->nbcnvifs > 1)) { + ath_dbg(common, CONFIG, + "An AP interface is already present !\n"); + return false; + } + } + + if (sc->sc_ah->opmode == NL80211_IFTYPE_STATION) { + if ((vif->type == NL80211_IFTYPE_STATION) && + test_bit(SC_OP_BEACONS, &sc->sc_flags) && + !avp->primary_sta_vif) { + ath_dbg(common, CONFIG, + "Beacon already configured for a station interface\n"); + return false; + } + } + + return true; +} + +static void ath9k_cache_beacon_config(struct ath_softc *sc, + struct ieee80211_bss_conf *bss_conf) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_beacon_config *cur_conf = &sc->cur_beacon_conf; + + ath_dbg(common, BEACON, + "Caching beacon data for BSS: %pM\n", bss_conf->bssid); + + cur_conf->beacon_interval = bss_conf->beacon_int; + cur_conf->dtim_period = bss_conf->dtim_period; + cur_conf->listen_interval = 1; + cur_conf->dtim_count = 1; + cur_conf->ibss_creator = bss_conf->ibss_creator; + cur_conf->bmiss_timeout = + ATH_DEFAULT_BMISS_LIMIT * cur_conf->beacon_interval; + + /* + * It looks like mac80211 may end up using beacon interval of zero in + * some cases (at least for mesh point). Avoid getting into an + * infinite loop by using a bit safer value instead. To be safe, + * do sanity check on beacon interval for all operating modes. + */ + if (cur_conf->beacon_interval == 0) + cur_conf->beacon_interval = 100; + + /* + * We don't parse dtim period from mac80211 during the driver + * initialization as it breaks association with hidden-ssid + * AP and it causes latency in roaming + */ + if (cur_conf->dtim_period == 0) + cur_conf->dtim_period = 1; + +} + +void ath9k_beacon_config(struct ath_softc *sc, struct ieee80211_vif *vif, + u32 changed) +{ + struct ieee80211_bss_conf *bss_conf = &vif->bss_conf; + struct ath_beacon_config *cur_conf = &sc->cur_beacon_conf; + unsigned long flags; + bool skip_beacon = false; + + if (sc->sc_ah->opmode == NL80211_IFTYPE_STATION) { + ath9k_cache_beacon_config(sc, bss_conf); + ath9k_set_beacon(sc); + set_bit(SC_OP_BEACONS, &sc->sc_flags); + return; + + } + + /* + * Take care of multiple interfaces when + * enabling/disabling SWBA. + */ + if (changed & BSS_CHANGED_BEACON_ENABLED) { + if (!bss_conf->enable_beacon && + (sc->nbcnvifs <= 1)) { + cur_conf->enable_beacon = false; + } else if (bss_conf->enable_beacon) { + cur_conf->enable_beacon = true; + ath9k_cache_beacon_config(sc, bss_conf); + } + } + + /* + * Configure the HW beacon registers only when we have a valid + * beacon interval. + */ + if (cur_conf->beacon_interval) { + /* + * If we are joining an existing IBSS network, start beaconing + * only after a TSF-sync has taken place. Ensure that this + * happens by setting the appropriate flags. + */ + if ((changed & BSS_CHANGED_IBSS) && !bss_conf->ibss_creator && + bss_conf->enable_beacon) { + spin_lock_irqsave(&sc->sc_pm_lock, flags); + sc->ps_flags |= PS_BEACON_SYNC | PS_WAIT_FOR_BEACON; + spin_unlock_irqrestore(&sc->sc_pm_lock, flags); + skip_beacon = true; + } else { + ath9k_set_beacon(sc); + } + + /* + * Do not set the SC_OP_BEACONS flag for IBSS joiner mode + * here, it is done in ath9k_beacon_config_adhoc(). + */ + if (cur_conf->enable_beacon && !skip_beacon) + set_bit(SC_OP_BEACONS, &sc->sc_flags); + else + clear_bit(SC_OP_BEACONS, &sc->sc_flags); + } +} + +void ath9k_set_beacon(struct ath_softc *sc) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_beacon_config *cur_conf = &sc->cur_beacon_conf; + + switch (sc->sc_ah->opmode) { + case NL80211_IFTYPE_AP: + ath9k_beacon_config_ap(sc, cur_conf); + break; + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_MESH_POINT: + ath9k_beacon_config_adhoc(sc, cur_conf); + break; + case NL80211_IFTYPE_STATION: + ath9k_beacon_config_sta(sc, cur_conf); + break; + default: + ath_dbg(common, CONFIG, "Unsupported beaconing mode\n"); + return; + } +} diff --git a/drivers/net/wireless/ath/ath9k/btcoex.c b/drivers/net/wireless/ath/ath9k/btcoex.c new file mode 100644 index 0000000..9963b0b --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/btcoex.c @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2009-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "hw.h" + +enum ath_bt_mode { + ATH_BT_COEX_MODE_LEGACY, /* legacy rx_clear mode */ + ATH_BT_COEX_MODE_UNSLOTTED, /* untimed/unslotted mode */ + ATH_BT_COEX_MODE_SLOTTED, /* slotted mode */ + ATH_BT_COEX_MODE_DISABLED, /* coexistence disabled */ +}; + +struct ath_btcoex_config { + u8 bt_time_extend; + bool bt_txstate_extend; + bool bt_txframe_extend; + enum ath_bt_mode bt_mode; /* coexistence mode */ + bool bt_quiet_collision; + bool bt_rxclear_polarity; /* invert rx_clear as WLAN_ACTIVE*/ + u8 bt_priority_time; + u8 bt_first_slot_time; + bool bt_hold_rx_clear; +}; + +static const u32 ar9003_wlan_weights[ATH_BTCOEX_STOMP_MAX] + [AR9300_NUM_WLAN_WEIGHTS] = { + { 0xfffffff0, 0xfffffff0, 0xfffffff0, 0xfffffff0 }, /* STOMP_ALL */ + { 0x88888880, 0x88888880, 0x88888880, 0x88888880 }, /* STOMP_LOW */ + { 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, /* STOMP_NONE */ +}; + +static const u32 mci_wlan_weights[ATH_BTCOEX_STOMP_MAX] + [AR9300_NUM_WLAN_WEIGHTS] = { + { 0x01017d01, 0x41414101, 0x41414101, 0x41414141 }, /* STOMP_ALL */ + { 0x01017d01, 0x3b3b3b01, 0x3b3b3b01, 0x3b3b3b3b }, /* STOMP_LOW */ + { 0x01017d01, 0x01010101, 0x01010101, 0x01010101 }, /* STOMP_NONE */ + { 0x01017d01, 0x013b0101, 0x3b3b0101, 0x3b3b013b }, /* STOMP_LOW_FTP */ + { 0xffffff01, 0xffffffff, 0xffffff01, 0xffffffff }, /* STOMP_AUDIO */ +}; + +void ath9k_hw_init_btcoex_hw(struct ath_hw *ah, int qnum) +{ + struct ath_btcoex_hw *btcoex_hw = &ah->btcoex_hw; + const struct ath_btcoex_config ath_bt_config = { + .bt_time_extend = 0, + .bt_txstate_extend = true, + .bt_txframe_extend = true, + .bt_mode = ATH_BT_COEX_MODE_SLOTTED, + .bt_quiet_collision = true, + .bt_rxclear_polarity = true, + .bt_priority_time = 2, + .bt_first_slot_time = 5, + .bt_hold_rx_clear = true, + }; + u32 i, idx; + bool rxclear_polarity = ath_bt_config.bt_rxclear_polarity; + + if (AR_SREV_9300_20_OR_LATER(ah)) + rxclear_polarity = !ath_bt_config.bt_rxclear_polarity; + + btcoex_hw->bt_coex_mode = + (btcoex_hw->bt_coex_mode & AR_BT_QCU_THRESH) | + SM(ath_bt_config.bt_time_extend, AR_BT_TIME_EXTEND) | + SM(ath_bt_config.bt_txstate_extend, AR_BT_TXSTATE_EXTEND) | + SM(ath_bt_config.bt_txframe_extend, AR_BT_TX_FRAME_EXTEND) | + SM(ath_bt_config.bt_mode, AR_BT_MODE) | + SM(ath_bt_config.bt_quiet_collision, AR_BT_QUIET) | + SM(rxclear_polarity, AR_BT_RX_CLEAR_POLARITY) | + SM(ath_bt_config.bt_priority_time, AR_BT_PRIORITY_TIME) | + SM(ath_bt_config.bt_first_slot_time, AR_BT_FIRST_SLOT_TIME) | + SM(qnum, AR_BT_QCU_THRESH); + + btcoex_hw->bt_coex_mode2 = + SM(ath_bt_config.bt_hold_rx_clear, AR_BT_HOLD_RX_CLEAR) | + SM(ATH_BTCOEX_BMISS_THRESH, AR_BT_BCN_MISS_THRESH) | + AR_BT_DISABLE_BT_ANT; + + for (i = 0; i < 32; i++) { + idx = (debruijn32 << i) >> 27; + ah->hw_gen_timers.gen_timer_index[idx] = i; + } +} +EXPORT_SYMBOL(ath9k_hw_init_btcoex_hw); + +void ath9k_hw_btcoex_init_scheme(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath_btcoex_hw *btcoex_hw = &ah->btcoex_hw; + + /* + * Check if BTCOEX is globally disabled. + */ + if (!common->btcoex_enabled) { + btcoex_hw->scheme = ATH_BTCOEX_CFG_NONE; + return; + } + + if (AR_SREV_9300_20_OR_LATER(ah)) { + btcoex_hw->scheme = ATH_BTCOEX_CFG_3WIRE; + btcoex_hw->btactive_gpio = ATH_BTACTIVE_GPIO_9300; + btcoex_hw->wlanactive_gpio = ATH_WLANACTIVE_GPIO_9300; + btcoex_hw->btpriority_gpio = ATH_BTPRIORITY_GPIO_9300; + } else if (AR_SREV_9280_20_OR_LATER(ah)) { + btcoex_hw->btactive_gpio = ATH_BTACTIVE_GPIO_9280; + btcoex_hw->wlanactive_gpio = ATH_WLANACTIVE_GPIO_9280; + + if (AR_SREV_9285(ah)) { + btcoex_hw->scheme = ATH_BTCOEX_CFG_3WIRE; + btcoex_hw->btpriority_gpio = ATH_BTPRIORITY_GPIO_9285; + } else { + btcoex_hw->scheme = ATH_BTCOEX_CFG_2WIRE; + } + } +} +EXPORT_SYMBOL(ath9k_hw_btcoex_init_scheme); + +void ath9k_hw_btcoex_init_2wire(struct ath_hw *ah) +{ + struct ath_btcoex_hw *btcoex_hw = &ah->btcoex_hw; + + /* connect bt_active to baseband */ + REG_CLR_BIT(ah, AR_GPIO_INPUT_EN_VAL, + (AR_GPIO_INPUT_EN_VAL_BT_PRIORITY_DEF | + AR_GPIO_INPUT_EN_VAL_BT_FREQUENCY_DEF)); + + REG_SET_BIT(ah, AR_GPIO_INPUT_EN_VAL, + AR_GPIO_INPUT_EN_VAL_BT_ACTIVE_BB); + + /* Set input mux for bt_active to gpio pin */ + REG_RMW_FIELD(ah, AR_GPIO_INPUT_MUX1, + AR_GPIO_INPUT_MUX1_BT_ACTIVE, + btcoex_hw->btactive_gpio); + + /* Configure the desired gpio port for input */ + ath9k_hw_cfg_gpio_input(ah, btcoex_hw->btactive_gpio); +} +EXPORT_SYMBOL(ath9k_hw_btcoex_init_2wire); + +void ath9k_hw_btcoex_init_3wire(struct ath_hw *ah) +{ + struct ath_btcoex_hw *btcoex_hw = &ah->btcoex_hw; + + /* btcoex 3-wire */ + REG_SET_BIT(ah, AR_GPIO_INPUT_EN_VAL, + (AR_GPIO_INPUT_EN_VAL_BT_PRIORITY_BB | + AR_GPIO_INPUT_EN_VAL_BT_ACTIVE_BB)); + + /* Set input mux for bt_prority_async and + * bt_active_async to GPIO pins */ + REG_RMW_FIELD(ah, AR_GPIO_INPUT_MUX1, + AR_GPIO_INPUT_MUX1_BT_ACTIVE, + btcoex_hw->btactive_gpio); + + REG_RMW_FIELD(ah, AR_GPIO_INPUT_MUX1, + AR_GPIO_INPUT_MUX1_BT_PRIORITY, + btcoex_hw->btpriority_gpio); + + /* Configure the desired GPIO ports for input */ + + ath9k_hw_cfg_gpio_input(ah, btcoex_hw->btactive_gpio); + ath9k_hw_cfg_gpio_input(ah, btcoex_hw->btpriority_gpio); +} +EXPORT_SYMBOL(ath9k_hw_btcoex_init_3wire); + +void ath9k_hw_btcoex_init_mci(struct ath_hw *ah) +{ + ah->btcoex_hw.mci.ready = false; + ah->btcoex_hw.mci.bt_state = 0; + ah->btcoex_hw.mci.bt_ver_major = 3; + ah->btcoex_hw.mci.bt_ver_minor = 0; + ah->btcoex_hw.mci.bt_version_known = false; + ah->btcoex_hw.mci.update_2g5g = true; + ah->btcoex_hw.mci.is_2g = true; + ah->btcoex_hw.mci.wlan_channels_update = false; + ah->btcoex_hw.mci.wlan_channels[0] = 0x00000000; + ah->btcoex_hw.mci.wlan_channels[1] = 0xffffffff; + ah->btcoex_hw.mci.wlan_channels[2] = 0xffffffff; + ah->btcoex_hw.mci.wlan_channels[3] = 0x7fffffff; + ah->btcoex_hw.mci.query_bt = true; + ah->btcoex_hw.mci.unhalt_bt_gpm = true; + ah->btcoex_hw.mci.halted_bt_gpm = false; + ah->btcoex_hw.mci.need_flush_btinfo = false; + ah->btcoex_hw.mci.wlan_cal_seq = 0; + ah->btcoex_hw.mci.wlan_cal_done = 0; + ah->btcoex_hw.mci.config = (AR_SREV_9462(ah)) ? 0x2201 : 0xa4c1; +} +EXPORT_SYMBOL(ath9k_hw_btcoex_init_mci); + +static void ath9k_hw_btcoex_enable_2wire(struct ath_hw *ah) +{ + struct ath_btcoex_hw *btcoex_hw = &ah->btcoex_hw; + + /* Configure the desired GPIO port for TX_FRAME output */ + ath9k_hw_cfg_output(ah, btcoex_hw->wlanactive_gpio, + AR_GPIO_OUTPUT_MUX_AS_TX_FRAME); +} + +/* + * For AR9002, bt_weight/wlan_weight are used. + * For AR9003 and above, stomp_type is used. + */ +void ath9k_hw_btcoex_set_weight(struct ath_hw *ah, + u32 bt_weight, + u32 wlan_weight, + enum ath_stomp_type stomp_type) +{ + struct ath_btcoex_hw *btcoex_hw = &ah->btcoex_hw; + struct ath9k_hw_mci *mci_hw = &ah->btcoex_hw.mci; + u8 txprio_shift[] = { 24, 16, 16, 0 }; /* tx priority weight */ + bool concur_tx = (mci_hw->concur_tx && btcoex_hw->tx_prio[stomp_type]); + const u32 *weight = ar9003_wlan_weights[stomp_type]; + int i; + + if (!AR_SREV_9300_20_OR_LATER(ah)) { + btcoex_hw->bt_coex_weights = + SM(bt_weight, AR_BTCOEX_BT_WGHT) | + SM(wlan_weight, AR_BTCOEX_WL_WGHT); + return; + } + + if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) { + enum ath_stomp_type stype = + ((stomp_type == ATH_BTCOEX_STOMP_LOW) && + btcoex_hw->mci.stomp_ftp) ? + ATH_BTCOEX_STOMP_LOW_FTP : stomp_type; + weight = mci_wlan_weights[stype]; + } + + for (i = 0; i < AR9300_NUM_WLAN_WEIGHTS; i++) { + btcoex_hw->bt_weight[i] = AR9300_BT_WGHT; + btcoex_hw->wlan_weight[i] = weight[i]; + if (concur_tx && i) { + btcoex_hw->wlan_weight[i] &= + ~(0xff << txprio_shift[i-1]); + btcoex_hw->wlan_weight[i] |= + (btcoex_hw->tx_prio[stomp_type] << + txprio_shift[i-1]); + } + } + /* Last WLAN weight has to be adjusted wrt tx priority */ + if (concur_tx) { + btcoex_hw->wlan_weight[i-1] &= ~(0xff << txprio_shift[i-1]); + btcoex_hw->wlan_weight[i-1] |= (btcoex_hw->tx_prio[stomp_type] + << txprio_shift[i-1]); + } + +} +EXPORT_SYMBOL(ath9k_hw_btcoex_set_weight); + + +static void ath9k_hw_btcoex_enable_3wire(struct ath_hw *ah) +{ + struct ath_btcoex_hw *btcoex = &ah->btcoex_hw; + u32 val; + int i; + + /* + * Program coex mode and weight registers to + * enable coex 3-wire + */ + REG_WRITE(ah, AR_BT_COEX_MODE, btcoex->bt_coex_mode); + REG_WRITE(ah, AR_BT_COEX_MODE2, btcoex->bt_coex_mode2); + + + if (AR_SREV_9300_20_OR_LATER(ah)) { + REG_WRITE(ah, AR_BT_COEX_WL_WEIGHTS0, btcoex->wlan_weight[0]); + REG_WRITE(ah, AR_BT_COEX_WL_WEIGHTS1, btcoex->wlan_weight[1]); + for (i = 0; i < AR9300_NUM_BT_WEIGHTS; i++) + REG_WRITE(ah, AR_BT_COEX_BT_WEIGHTS(i), + btcoex->bt_weight[i]); + } else + REG_WRITE(ah, AR_BT_COEX_WEIGHT, btcoex->bt_coex_weights); + + + + if (AR_SREV_9271(ah)) { + val = REG_READ(ah, 0x50040); + val &= 0xFFFFFEFF; + REG_WRITE(ah, 0x50040, val); + } + + REG_RMW_FIELD(ah, AR_QUIET1, AR_QUIET1_QUIET_ACK_CTS_ENABLE, 1); + REG_RMW_FIELD(ah, AR_PCU_MISC, AR_PCU_BT_ANT_PREVENT_RX, 0); + + ath9k_hw_cfg_output(ah, btcoex->wlanactive_gpio, + AR_GPIO_OUTPUT_MUX_AS_RX_CLEAR_EXTERNAL); +} + +static void ath9k_hw_btcoex_enable_mci(struct ath_hw *ah) +{ + struct ath_btcoex_hw *btcoex = &ah->btcoex_hw; + int i; + + for (i = 0; i < AR9300_NUM_BT_WEIGHTS; i++) + REG_WRITE(ah, AR_MCI_COEX_WL_WEIGHTS(i), + btcoex->wlan_weight[i]); + + REG_RMW_FIELD(ah, AR_QUIET1, AR_QUIET1_QUIET_ACK_CTS_ENABLE, 1); + btcoex->enabled = true; +} + +void ath9k_hw_btcoex_enable(struct ath_hw *ah) +{ + struct ath_btcoex_hw *btcoex_hw = &ah->btcoex_hw; + + switch (ath9k_hw_get_btcoex_scheme(ah)) { + case ATH_BTCOEX_CFG_NONE: + return; + case ATH_BTCOEX_CFG_2WIRE: + ath9k_hw_btcoex_enable_2wire(ah); + break; + case ATH_BTCOEX_CFG_3WIRE: + if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) { + ath9k_hw_btcoex_enable_mci(ah); + return; + } + ath9k_hw_btcoex_enable_3wire(ah); + break; + } + + REG_RMW(ah, AR_GPIO_PDPU, + (0x2 << (btcoex_hw->btactive_gpio * 2)), + (0x3 << (btcoex_hw->btactive_gpio * 2))); + + ah->btcoex_hw.enabled = true; +} +EXPORT_SYMBOL(ath9k_hw_btcoex_enable); + +void ath9k_hw_btcoex_disable(struct ath_hw *ah) +{ + struct ath_btcoex_hw *btcoex_hw = &ah->btcoex_hw; + int i; + + btcoex_hw->enabled = false; + if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) { + ath9k_hw_btcoex_bt_stomp(ah, ATH_BTCOEX_STOMP_NONE); + for (i = 0; i < AR9300_NUM_BT_WEIGHTS; i++) + REG_WRITE(ah, AR_MCI_COEX_WL_WEIGHTS(i), + btcoex_hw->wlan_weight[i]); + return; + } + ath9k_hw_set_gpio(ah, btcoex_hw->wlanactive_gpio, 0); + + ath9k_hw_cfg_output(ah, btcoex_hw->wlanactive_gpio, + AR_GPIO_OUTPUT_MUX_AS_OUTPUT); + + if (btcoex_hw->scheme == ATH_BTCOEX_CFG_3WIRE) { + REG_WRITE(ah, AR_BT_COEX_MODE, AR_BT_QUIET | AR_BT_MODE); + REG_WRITE(ah, AR_BT_COEX_MODE2, 0); + + if (AR_SREV_9300_20_OR_LATER(ah)) { + REG_WRITE(ah, AR_BT_COEX_WL_WEIGHTS0, 0); + REG_WRITE(ah, AR_BT_COEX_WL_WEIGHTS1, 0); + for (i = 0; i < AR9300_NUM_BT_WEIGHTS; i++) + REG_WRITE(ah, AR_BT_COEX_BT_WEIGHTS(i), 0); + } else + REG_WRITE(ah, AR_BT_COEX_WEIGHT, 0); + + } +} +EXPORT_SYMBOL(ath9k_hw_btcoex_disable); + +/* + * Configures appropriate weight based on stomp type. + */ +void ath9k_hw_btcoex_bt_stomp(struct ath_hw *ah, + enum ath_stomp_type stomp_type) +{ + if (AR_SREV_9300_20_OR_LATER(ah)) { + ath9k_hw_btcoex_set_weight(ah, 0, 0, stomp_type); + return; + } + + switch (stomp_type) { + case ATH_BTCOEX_STOMP_ALL: + ath9k_hw_btcoex_set_weight(ah, AR_BT_COEX_WGHT, + AR_STOMP_ALL_WLAN_WGHT, 0); + break; + case ATH_BTCOEX_STOMP_LOW: + ath9k_hw_btcoex_set_weight(ah, AR_BT_COEX_WGHT, + AR_STOMP_LOW_WLAN_WGHT, 0); + break; + case ATH_BTCOEX_STOMP_NONE: + ath9k_hw_btcoex_set_weight(ah, AR_BT_COEX_WGHT, + AR_STOMP_NONE_WLAN_WGHT, 0); + break; + default: + ath_dbg(ath9k_hw_common(ah), BTCOEX, "Invalid Stomptype\n"); + break; + } +} +EXPORT_SYMBOL(ath9k_hw_btcoex_bt_stomp); + +void ath9k_hw_btcoex_set_concur_txprio(struct ath_hw *ah, u8 *stomp_txprio) +{ + struct ath_btcoex_hw *btcoex = &ah->btcoex_hw; + int i; + + for (i = 0; i < ATH_BTCOEX_STOMP_MAX; i++) + btcoex->tx_prio[i] = stomp_txprio[i]; +} +EXPORT_SYMBOL(ath9k_hw_btcoex_set_concur_txprio); diff --git a/drivers/net/wireless/ath/ath9k/btcoex.h b/drivers/net/wireless/ath/ath9k/btcoex.h new file mode 100644 index 0000000..6de26ea --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/btcoex.h @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2009-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef BTCOEX_H +#define BTCOEX_H + +#include "hw.h" + +#define ATH_WLANACTIVE_GPIO_9280 5 +#define ATH_BTACTIVE_GPIO_9280 6 +#define ATH_BTPRIORITY_GPIO_9285 7 + +#define ATH_WLANACTIVE_GPIO_9300 5 +#define ATH_BTACTIVE_GPIO_9300 4 +#define ATH_BTPRIORITY_GPIO_9300 8 + +#define ATH_BTCOEX_DEF_BT_PERIOD 45 +#define ATH_BTCOEX_DEF_DUTY_CYCLE 55 +#define ATH_BTCOEX_BTSCAN_DUTY_CYCLE 90 +#define ATH_BTCOEX_BMISS_THRESH 50 + +#define ATH_BT_PRIORITY_TIME_THRESHOLD 1000 /* ms */ +#define ATH_BT_CNT_THRESHOLD 3 +#define ATH_BT_CNT_SCAN_THRESHOLD 15 + +#define ATH_BTCOEX_RX_WAIT_TIME 100 +#define ATH_BTCOEX_STOMP_FTP_THRESH 5 + +#define ATH_BTCOEX_HT20_MAX_TXPOWER 0x14 +#define ATH_BTCOEX_HT40_MAX_TXPOWER 0x10 + +#define AR9300_NUM_BT_WEIGHTS 4 +#define AR9300_NUM_WLAN_WEIGHTS 4 +/* Defines the BT AR_BT_COEX_WGHT used */ +enum ath_stomp_type { + ATH_BTCOEX_STOMP_ALL, + ATH_BTCOEX_STOMP_LOW, + ATH_BTCOEX_STOMP_NONE, + ATH_BTCOEX_STOMP_LOW_FTP, + ATH_BTCOEX_STOMP_AUDIO, + ATH_BTCOEX_STOMP_MAX +}; + +enum ath_btcoex_scheme { + ATH_BTCOEX_CFG_NONE, + ATH_BTCOEX_CFG_2WIRE, + ATH_BTCOEX_CFG_3WIRE, +}; + +struct ath9k_hw_mci { + u32 raw_intr; + u32 rx_msg_intr; + u32 cont_status; + u32 gpm_addr; + u32 gpm_len; + u32 gpm_idx; + u32 sched_addr; + u32 wlan_channels[4]; + u32 wlan_cal_seq; + u32 wlan_cal_done; + u32 config; + u8 *gpm_buf; + bool ready; + bool update_2g5g; + bool is_2g; + bool query_bt; + bool unhalt_bt_gpm; /* need send UNHALT */ + bool halted_bt_gpm; /* HALT sent */ + bool need_flush_btinfo; + bool bt_version_known; + bool wlan_channels_update; + u8 wlan_ver_major; + u8 wlan_ver_minor; + u8 bt_ver_major; + u8 bt_ver_minor; + u8 bt_state; + u8 stomp_ftp; + bool concur_tx; + u32 last_recovery; +}; + +struct ath_btcoex_hw { + enum ath_btcoex_scheme scheme; + struct ath9k_hw_mci mci; + bool enabled; + u8 wlanactive_gpio; + u8 btactive_gpio; + u8 btpriority_gpio; + u32 bt_coex_mode; /* Register setting for AR_BT_COEX_MODE */ + u32 bt_coex_weights; /* Register setting for AR_BT_COEX_WEIGHT */ + u32 bt_coex_mode2; /* Register setting for AR_BT_COEX_MODE2 */ + u32 bt_weight[AR9300_NUM_BT_WEIGHTS]; + u32 wlan_weight[AR9300_NUM_WLAN_WEIGHTS]; + u8 tx_prio[ATH_BTCOEX_STOMP_MAX]; +}; + +void ath9k_hw_btcoex_init_scheme(struct ath_hw *ah); +void ath9k_hw_btcoex_init_2wire(struct ath_hw *ah); +void ath9k_hw_btcoex_init_3wire(struct ath_hw *ah); +void ath9k_hw_btcoex_init_mci(struct ath_hw *ah); +void ath9k_hw_init_btcoex_hw(struct ath_hw *ah, int qnum); +void ath9k_hw_btcoex_set_weight(struct ath_hw *ah, + u32 bt_weight, + u32 wlan_weight, + enum ath_stomp_type stomp_type); +void ath9k_hw_btcoex_disable(struct ath_hw *ah); +void ath9k_hw_btcoex_bt_stomp(struct ath_hw *ah, + enum ath_stomp_type stomp_type); +void ath9k_hw_btcoex_set_concur_txprio(struct ath_hw *ah, u8 *stomp_txprio); + +#endif diff --git a/drivers/net/wireless/ath/ath9k/calib.c b/drivers/net/wireless/ath/ath9k/calib.c new file mode 100644 index 0000000..5e8219a --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/calib.c @@ -0,0 +1,448 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "hw.h" +#include "hw-ops.h" +#include + +/* Common calibration code */ + + +static int16_t ath9k_hw_get_nf_hist_mid(int16_t *nfCalBuffer) +{ + int16_t nfval; + int16_t sort[ATH9K_NF_CAL_HIST_MAX]; + int i, j; + + for (i = 0; i < ATH9K_NF_CAL_HIST_MAX; i++) + sort[i] = nfCalBuffer[i]; + + for (i = 0; i < ATH9K_NF_CAL_HIST_MAX - 1; i++) { + for (j = 1; j < ATH9K_NF_CAL_HIST_MAX - i; j++) { + if (sort[j] > sort[j - 1]) { + nfval = sort[j]; + sort[j] = sort[j - 1]; + sort[j - 1] = nfval; + } + } + } + nfval = sort[(ATH9K_NF_CAL_HIST_MAX - 1) >> 1]; + + return nfval; +} + +static struct ath_nf_limits *ath9k_hw_get_nf_limits(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + struct ath_nf_limits *limit; + + if (!chan || IS_CHAN_2GHZ(chan)) + limit = &ah->nf_2g; + else + limit = &ah->nf_5g; + + return limit; +} + +static s16 ath9k_hw_get_default_nf(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + return ath9k_hw_get_nf_limits(ah, chan)->nominal; +} + +s16 ath9k_hw_getchan_noise(struct ath_hw *ah, struct ath9k_channel *chan) +{ + s8 noise = ATH_DEFAULT_NOISE_FLOOR; + + if (chan && chan->noisefloor) { + s8 delta = chan->noisefloor - + ATH9K_NF_CAL_NOISE_THRESH - + ath9k_hw_get_default_nf(ah, chan); + if (delta > 0) + noise += delta; + } + return noise; +} +EXPORT_SYMBOL(ath9k_hw_getchan_noise); + +static void ath9k_hw_update_nfcal_hist_buffer(struct ath_hw *ah, + struct ath9k_hw_cal_data *cal, + int16_t *nfarray) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath_nf_limits *limit; + struct ath9k_nfcal_hist *h; + bool high_nf_mid = false; + u8 chainmask = (ah->rxchainmask << 3) | ah->rxchainmask; + int i; + + h = cal->nfCalHist; + limit = ath9k_hw_get_nf_limits(ah, ah->curchan); + + for (i = 0; i < NUM_NF_READINGS; i++) { + if (!(chainmask & (1 << i)) || + ((i >= AR5416_MAX_CHAINS) && !IS_CHAN_HT40(ah->curchan))) + continue; + + h[i].nfCalBuffer[h[i].currIndex] = nfarray[i]; + + if (++h[i].currIndex >= ATH9K_NF_CAL_HIST_MAX) + h[i].currIndex = 0; + + if (h[i].invalidNFcount > 0) { + h[i].invalidNFcount--; + h[i].privNF = nfarray[i]; + } else { + h[i].privNF = + ath9k_hw_get_nf_hist_mid(h[i].nfCalBuffer); + } + + if (!h[i].privNF) + continue; + + if (h[i].privNF > limit->max) { + high_nf_mid = true; + + ath_dbg(common, CALIBRATE, + "NFmid[%d] (%d) > MAX (%d), %s\n", + i, h[i].privNF, limit->max, + (cal->nfcal_interference ? + "not corrected (due to interference)" : + "correcting to MAX")); + + /* + * Normally we limit the average noise floor by the + * hardware specific maximum here. However if we have + * encountered stuck beacons because of interference, + * we bypass this limit here in order to better deal + * with our environment. + */ + if (!cal->nfcal_interference) + h[i].privNF = limit->max; + } + } + + /* + * If the noise floor seems normal for all chains, assume that + * there is no significant interference in the environment anymore. + * Re-enable the enforcement of the NF maximum again. + */ + if (!high_nf_mid) + cal->nfcal_interference = false; +} + +static bool ath9k_hw_get_nf_thresh(struct ath_hw *ah, + enum ieee80211_band band, + int16_t *nft) +{ + switch (band) { + case IEEE80211_BAND_5GHZ: + *nft = (int8_t)ah->eep_ops->get_eeprom(ah, EEP_NFTHRESH_5); + break; + case IEEE80211_BAND_2GHZ: + *nft = (int8_t)ah->eep_ops->get_eeprom(ah, EEP_NFTHRESH_2); + break; + default: + BUG_ON(1); + return false; + } + + return true; +} + +void ath9k_hw_reset_calibration(struct ath_hw *ah, + struct ath9k_cal_list *currCal) +{ + int i; + + ath9k_hw_setup_calibration(ah, currCal); + + currCal->calState = CAL_RUNNING; + + for (i = 0; i < AR5416_MAX_CHAINS; i++) { + ah->meas0.sign[i] = 0; + ah->meas1.sign[i] = 0; + ah->meas2.sign[i] = 0; + ah->meas3.sign[i] = 0; + } + + ah->cal_samples = 0; +} + +/* This is done for the currently configured channel */ +bool ath9k_hw_reset_calvalid(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ieee80211_conf *conf = &common->hw->conf; + struct ath9k_cal_list *currCal = ah->cal_list_curr; + + if (!ah->caldata) + return true; + + if (!AR_SREV_9100(ah) && !AR_SREV_9160_10_OR_LATER(ah)) + return true; + + if (currCal == NULL) + return true; + + if (currCal->calState != CAL_DONE) { + ath_dbg(common, CALIBRATE, "Calibration state incorrect, %d\n", + currCal->calState); + return true; + } + + if (!(ah->supp_cals & currCal->calData->calType)) + return true; + + ath_dbg(common, CALIBRATE, "Resetting Cal %d state for channel %u\n", + currCal->calData->calType, conf->chandef.chan->center_freq); + + ah->caldata->CalValid &= ~currCal->calData->calType; + currCal->calState = CAL_WAITING; + + return false; +} +EXPORT_SYMBOL(ath9k_hw_reset_calvalid); + +void ath9k_hw_start_nfcal(struct ath_hw *ah, bool update) +{ + if (ah->caldata) + ah->caldata->nfcal_pending = true; + + REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, + AR_PHY_AGC_CONTROL_ENABLE_NF); + + if (update) + REG_CLR_BIT(ah, AR_PHY_AGC_CONTROL, + AR_PHY_AGC_CONTROL_NO_UPDATE_NF); + else + REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, + AR_PHY_AGC_CONTROL_NO_UPDATE_NF); + + REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_NF); +} + +void ath9k_hw_loadnf(struct ath_hw *ah, struct ath9k_channel *chan) +{ + struct ath9k_nfcal_hist *h = NULL; + unsigned i, j; + int32_t val; + u8 chainmask = (ah->rxchainmask << 3) | ah->rxchainmask; + struct ath_common *common = ath9k_hw_common(ah); + struct ieee80211_conf *conf = &common->hw->conf; + s16 default_nf = ath9k_hw_get_default_nf(ah, chan); + + if (ah->caldata) + h = ah->caldata->nfCalHist; + + for (i = 0; i < NUM_NF_READINGS; i++) { + if (chainmask & (1 << i)) { + s16 nfval; + + if ((i >= AR5416_MAX_CHAINS) && !conf_is_ht40(conf)) + continue; + + if (h) + nfval = h[i].privNF; + else + nfval = default_nf; + + val = REG_READ(ah, ah->nf_regs[i]); + val &= 0xFFFFFE00; + val |= (((u32) nfval << 1) & 0x1ff); + REG_WRITE(ah, ah->nf_regs[i], val); + } + } + + /* + * Load software filtered NF value into baseband internal minCCApwr + * variable. + */ + REG_CLR_BIT(ah, AR_PHY_AGC_CONTROL, + AR_PHY_AGC_CONTROL_ENABLE_NF); + REG_CLR_BIT(ah, AR_PHY_AGC_CONTROL, + AR_PHY_AGC_CONTROL_NO_UPDATE_NF); + REG_SET_BIT(ah, AR_PHY_AGC_CONTROL, AR_PHY_AGC_CONTROL_NF); + + /* + * Wait for load to complete, should be fast, a few 10s of us. + * The max delay was changed from an original 250us to 10000us + * since 250us often results in NF load timeout and causes deaf + * condition during stress testing 12/12/2009 + */ + for (j = 0; j < 10000; j++) { + if ((REG_READ(ah, AR_PHY_AGC_CONTROL) & + AR_PHY_AGC_CONTROL_NF) == 0) + break; + udelay(10); + } + + /* + * We timed out waiting for the noisefloor to load, probably due to an + * in-progress rx. Simply return here and allow the load plenty of time + * to complete before the next calibration interval. We need to avoid + * trying to load -50 (which happens below) while the previous load is + * still in progress as this can cause rx deafness. Instead by returning + * here, the baseband nf cal will just be capped by our present + * noisefloor until the next calibration timer. + */ + if (j == 10000) { + ath_dbg(common, ANY, + "Timeout while waiting for nf to load: AR_PHY_AGC_CONTROL=0x%x\n", + REG_READ(ah, AR_PHY_AGC_CONTROL)); + return; + } + + /* + * Restore maxCCAPower register parameter again so that we're not capped + * by the median we just loaded. This will be initial (and max) value + * of next noise floor calibration the baseband does. + */ + ENABLE_REGWRITE_BUFFER(ah); + for (i = 0; i < NUM_NF_READINGS; i++) { + if (chainmask & (1 << i)) { + if ((i >= AR5416_MAX_CHAINS) && !conf_is_ht40(conf)) + continue; + + val = REG_READ(ah, ah->nf_regs[i]); + val &= 0xFFFFFE00; + val |= (((u32) (-50) << 1) & 0x1ff); + REG_WRITE(ah, ah->nf_regs[i], val); + } + } + REGWRITE_BUFFER_FLUSH(ah); +} + + +static void ath9k_hw_nf_sanitize(struct ath_hw *ah, s16 *nf) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath_nf_limits *limit; + int i; + + if (IS_CHAN_2GHZ(ah->curchan)) + limit = &ah->nf_2g; + else + limit = &ah->nf_5g; + + for (i = 0; i < NUM_NF_READINGS; i++) { + if (!nf[i]) + continue; + + ath_dbg(common, CALIBRATE, + "NF calibrated [%s] [chain %d] is %d\n", + (i >= 3 ? "ext" : "ctl"), i % 3, nf[i]); + + if (nf[i] > limit->max) { + ath_dbg(common, CALIBRATE, + "NF[%d] (%d) > MAX (%d), correcting to MAX\n", + i, nf[i], limit->max); + nf[i] = limit->max; + } else if (nf[i] < limit->min) { + ath_dbg(common, CALIBRATE, + "NF[%d] (%d) < MIN (%d), correcting to NOM\n", + i, nf[i], limit->min); + nf[i] = limit->nominal; + } + } +} + +bool ath9k_hw_getnf(struct ath_hw *ah, struct ath9k_channel *chan) +{ + struct ath_common *common = ath9k_hw_common(ah); + int16_t nf, nfThresh; + int16_t nfarray[NUM_NF_READINGS] = { 0 }; + struct ath9k_nfcal_hist *h; + struct ieee80211_channel *c = chan->chan; + struct ath9k_hw_cal_data *caldata = ah->caldata; + + if (REG_READ(ah, AR_PHY_AGC_CONTROL) & AR_PHY_AGC_CONTROL_NF) { + ath_dbg(common, CALIBRATE, + "NF did not complete in calibration window\n"); + return false; + } + + ath9k_hw_do_getnf(ah, nfarray); + ath9k_hw_nf_sanitize(ah, nfarray); + nf = nfarray[0]; + if (ath9k_hw_get_nf_thresh(ah, c->band, &nfThresh) + && nf > nfThresh) { + ath_dbg(common, CALIBRATE, + "noise floor failed detected; detected %d, threshold %d\n", + nf, nfThresh); + } + + if (!caldata) { + chan->noisefloor = nf; + return false; + } + + h = caldata->nfCalHist; + caldata->nfcal_pending = false; + ath9k_hw_update_nfcal_hist_buffer(ah, caldata, nfarray); + chan->noisefloor = h[0].privNF; + ah->noise = ath9k_hw_getchan_noise(ah, chan); + return true; +} +EXPORT_SYMBOL(ath9k_hw_getnf); + +void ath9k_init_nfcal_hist_buffer(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + struct ath9k_nfcal_hist *h; + s16 default_nf; + int i, j; + + ah->caldata->channel = chan->channel; + ah->caldata->channelFlags = chan->channelFlags; + ah->caldata->chanmode = chan->chanmode; + h = ah->caldata->nfCalHist; + default_nf = ath9k_hw_get_default_nf(ah, chan); + for (i = 0; i < NUM_NF_READINGS; i++) { + h[i].currIndex = 0; + h[i].privNF = default_nf; + h[i].invalidNFcount = AR_PHY_CCA_FILTERWINDOW_LENGTH; + for (j = 0; j < ATH9K_NF_CAL_HIST_MAX; j++) { + h[i].nfCalBuffer[j] = default_nf; + } + } +} + + +void ath9k_hw_bstuck_nfcal(struct ath_hw *ah) +{ + struct ath9k_hw_cal_data *caldata = ah->caldata; + + if (unlikely(!caldata)) + return; + + /* + * If beacons are stuck, the most likely cause is interference. + * Triggering a noise floor calibration at this point helps the + * hardware adapt to a noisy environment much faster. + * To ensure that we recover from stuck beacons quickly, let + * the baseband update the internal NF value itself, similar to + * what is being done after a full reset. + */ + if (!caldata->nfcal_pending) + ath9k_hw_start_nfcal(ah, true); + else if (!(REG_READ(ah, AR_PHY_AGC_CONTROL) & AR_PHY_AGC_CONTROL_NF)) + ath9k_hw_getnf(ah, ah->curchan); + + caldata->nfcal_interference = true; +} +EXPORT_SYMBOL(ath9k_hw_bstuck_nfcal); + diff --git a/drivers/net/wireless/ath/ath9k/calib.h b/drivers/net/wireless/ath/ath9k/calib.h new file mode 100644 index 0000000..3d70b8c --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/calib.h @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef CALIB_H +#define CALIB_H + +#include "hw.h" + +#define AR_PHY_CCA_FILTERWINDOW_LENGTH 5 + +/* Internal noise floor can vary by about 6db depending on the frequency */ +#define ATH9K_NF_CAL_NOISE_THRESH 6 + +#define NUM_NF_READINGS 6 +#define ATH9K_NF_CAL_HIST_MAX 5 + +struct ar5416IniArray { + u32 *ia_array; + u32 ia_rows; + u32 ia_columns; +}; + +#define STATIC_INI_ARRAY(array) { \ + .ia_array = (u32 *)(array), \ + .ia_rows = ARRAY_SIZE(array), \ + .ia_columns = ARRAY_SIZE(array[0]), \ + } + +#define INIT_INI_ARRAY(iniarray, array) do { \ + (iniarray)->ia_array = (u32 *)(array); \ + (iniarray)->ia_rows = ARRAY_SIZE(array); \ + (iniarray)->ia_columns = ARRAY_SIZE(array[0]); \ + } while (0) + +#define INI_RA(iniarray, row, column) \ + (((iniarray)->ia_array)[(row) * ((iniarray)->ia_columns) + (column)]) + +#define INIT_CAL(_perCal) do { \ + (_perCal)->calState = CAL_WAITING; \ + (_perCal)->calNext = NULL; \ + } while (0) + +#define INSERT_CAL(_ahp, _perCal) \ + do { \ + if ((_ahp)->cal_list_last == NULL) { \ + (_ahp)->cal_list = \ + (_ahp)->cal_list_last = (_perCal); \ + ((_ahp)->cal_list_last)->calNext = (_perCal); \ + } else { \ + ((_ahp)->cal_list_last)->calNext = (_perCal); \ + (_ahp)->cal_list_last = (_perCal); \ + (_perCal)->calNext = (_ahp)->cal_list; \ + } \ + } while (0) + +enum ath9k_cal_state { + CAL_INACTIVE, + CAL_WAITING, + CAL_RUNNING, + CAL_DONE +}; + +#define MIN_CAL_SAMPLES 1 +#define MAX_CAL_SAMPLES 64 +#define INIT_LOG_COUNT 5 +#define PER_MIN_LOG_COUNT 2 +#define PER_MAX_LOG_COUNT 10 + +struct ath9k_percal_data { + u32 calType; + u32 calNumSamples; + u32 calCountMax; + void (*calCollect) (struct ath_hw *); + void (*calPostProc) (struct ath_hw *, u8); +}; + +struct ath9k_cal_list { + const struct ath9k_percal_data *calData; + enum ath9k_cal_state calState; + struct ath9k_cal_list *calNext; +}; + +struct ath9k_nfcal_hist { + int16_t nfCalBuffer[ATH9K_NF_CAL_HIST_MAX]; + u8 currIndex; + int16_t privNF; + u8 invalidNFcount; +}; + +#define MAX_PACAL_SKIPCOUNT 8 +struct ath9k_pacal_info{ + int32_t prev_offset; /* Previous value of PA offset value */ + int8_t max_skipcount; /* Max No. of times PACAL can be skipped */ + int8_t skipcount; /* No. of times the PACAL to be skipped */ +}; + +bool ath9k_hw_reset_calvalid(struct ath_hw *ah); +void ath9k_hw_start_nfcal(struct ath_hw *ah, bool update); +void ath9k_hw_loadnf(struct ath_hw *ah, struct ath9k_channel *chan); +bool ath9k_hw_getnf(struct ath_hw *ah, struct ath9k_channel *chan); +void ath9k_init_nfcal_hist_buffer(struct ath_hw *ah, + struct ath9k_channel *chan); +void ath9k_hw_bstuck_nfcal(struct ath_hw *ah); +void ath9k_hw_reset_calibration(struct ath_hw *ah, + struct ath9k_cal_list *currCal); +s16 ath9k_hw_getchan_noise(struct ath_hw *ah, struct ath9k_channel *chan); + + +#endif /* CALIB_H */ diff --git a/drivers/net/wireless/ath/ath9k/common.c b/drivers/net/wireless/ath/ath9k/common.c new file mode 100644 index 0000000..344fdde --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/common.c @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2009-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Module for common driver code between ath9k and ath9k_htc + */ + +#include +#include + +#include "common.h" + +MODULE_AUTHOR("Atheros Communications"); +MODULE_DESCRIPTION("Shared library for Atheros wireless 802.11n LAN cards."); +MODULE_LICENSE("Dual BSD/GPL"); + +int ath9k_cmn_get_hw_crypto_keytype(struct sk_buff *skb) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + + if (tx_info->control.hw_key) { + switch (tx_info->control.hw_key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + return ATH9K_KEY_TYPE_WEP; + case WLAN_CIPHER_SUITE_TKIP: + return ATH9K_KEY_TYPE_TKIP; + case WLAN_CIPHER_SUITE_CCMP: + return ATH9K_KEY_TYPE_AES; + default: + break; + } + } + + return ATH9K_KEY_TYPE_CLEAR; +} +EXPORT_SYMBOL(ath9k_cmn_get_hw_crypto_keytype); + +static u32 ath9k_get_extchanmode(struct ieee80211_channel *chan, + enum nl80211_channel_type channel_type) +{ + u32 chanmode = 0; + + switch (chan->band) { + case IEEE80211_BAND_2GHZ: + switch (channel_type) { + case NL80211_CHAN_NO_HT: + case NL80211_CHAN_HT20: + chanmode = CHANNEL_G_HT20; + break; + case NL80211_CHAN_HT40PLUS: + chanmode = CHANNEL_G_HT40PLUS; + break; + case NL80211_CHAN_HT40MINUS: + chanmode = CHANNEL_G_HT40MINUS; + break; + } + break; + case IEEE80211_BAND_5GHZ: + switch (channel_type) { + case NL80211_CHAN_NO_HT: + case NL80211_CHAN_HT20: + chanmode = CHANNEL_A_HT20; + break; + case NL80211_CHAN_HT40PLUS: + chanmode = CHANNEL_A_HT40PLUS; + break; + case NL80211_CHAN_HT40MINUS: + chanmode = CHANNEL_A_HT40MINUS; + break; + } + break; + default: + break; + } + + return chanmode; +} + +/* + * Update internal channel flags. + */ +void ath9k_cmn_update_ichannel(struct ath9k_channel *ichan, + struct ieee80211_channel *chan, + enum nl80211_channel_type channel_type) +{ + ichan->channel = chan->center_freq; + ichan->chan = chan; + + if (chan->band == IEEE80211_BAND_2GHZ) { + ichan->chanmode = CHANNEL_G; + ichan->channelFlags = CHANNEL_2GHZ | CHANNEL_OFDM; + } else { + ichan->chanmode = CHANNEL_A; + ichan->channelFlags = CHANNEL_5GHZ | CHANNEL_OFDM; + } + + if (channel_type != NL80211_CHAN_NO_HT) + ichan->chanmode = ath9k_get_extchanmode(chan, channel_type); +} +EXPORT_SYMBOL(ath9k_cmn_update_ichannel); + +/* + * Get the internal channel reference. + */ +struct ath9k_channel *ath9k_cmn_get_curchannel(struct ieee80211_hw *hw, + struct ath_hw *ah) +{ + struct ieee80211_channel *curchan = hw->conf.chandef.chan; + struct ath9k_channel *channel; + u8 chan_idx; + + chan_idx = curchan->hw_value; + channel = &ah->channels[chan_idx]; + ath9k_cmn_update_ichannel(channel, curchan, + cfg80211_get_chandef_type(&hw->conf.chandef)); + + return channel; +} +EXPORT_SYMBOL(ath9k_cmn_get_curchannel); + +int ath9k_cmn_count_streams(unsigned int chainmask, int max) +{ + int streams = 0; + + do { + if (++streams == max) + break; + } while ((chainmask = chainmask & (chainmask - 1))); + + return streams; +} +EXPORT_SYMBOL(ath9k_cmn_count_streams); + +void ath9k_cmn_update_txpow(struct ath_hw *ah, u16 cur_txpow, + u16 new_txpow, u16 *txpower) +{ + struct ath_regulatory *reg = ath9k_hw_regulatory(ah); + + if (reg->power_limit != new_txpow) { + ath9k_hw_set_txpowerlimit(ah, new_txpow, false); + /* read back in case value is clamped */ + *txpower = reg->max_power_level; + } +} +EXPORT_SYMBOL(ath9k_cmn_update_txpow); + +void ath9k_cmn_init_crypto(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + int i = 0; + + /* Get the hardware key cache size. */ + common->keymax = AR_KEYTABLE_SIZE; + + /* + * Check whether the separate key cache entries + * are required to handle both tx+rx MIC keys. + * With split mic keys the number of stations is limited + * to 27 otherwise 59. + */ + if (ah->misc_mode & AR_PCU_MIC_NEW_LOC_ENA) + common->crypt_caps |= ATH_CRYPT_CAP_MIC_COMBINED; + + /* + * Reset the key cache since some parts do not + * reset the contents on initial power up. + */ + for (i = 0; i < common->keymax; i++) + ath_hw_keyreset(common, (u16) i); +} +EXPORT_SYMBOL(ath9k_cmn_init_crypto); + +static int __init ath9k_cmn_init(void) +{ + return 0; +} +module_init(ath9k_cmn_init); + +static void __exit ath9k_cmn_exit(void) +{ + return; +} +module_exit(ath9k_cmn_exit); diff --git a/drivers/net/wireless/ath/ath9k/common.h b/drivers/net/wireless/ath/ath9k/common.h new file mode 100644 index 0000000..207d069 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/common.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2009-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "../ath.h" + +#include "hw.h" +#include "hw-ops.h" + +/* Common header for Atheros 802.11n base driver cores */ + +#define WME_BA_BMP_SIZE 64 +#define WME_MAX_BA WME_BA_BMP_SIZE +#define ATH_TID_MAX_BUFS (2 * WME_MAX_BA) + +#define ATH_RSSI_DUMMY_MARKER 127 +#define ATH_RSSI_LPF_LEN 10 +#define RSSI_LPF_THRESHOLD -20 +#define ATH_RSSI_EP_MULTIPLIER (1<<7) +#define ATH_EP_MUL(x, mul) ((x) * (mul)) +#define ATH_RSSI_IN(x) (ATH_EP_MUL((x), ATH_RSSI_EP_MULTIPLIER)) +#define ATH_LPF_RSSI(x, y, len) \ + ((x != ATH_RSSI_DUMMY_MARKER) ? (((x) * ((len) - 1) + (y)) / (len)) : (y)) +#define ATH_RSSI_LPF(x, y) do { \ + if ((y) >= RSSI_LPF_THRESHOLD) \ + x = ATH_LPF_RSSI((x), ATH_RSSI_IN((y)), ATH_RSSI_LPF_LEN); \ +} while (0) +#define ATH_EP_RND(x, mul) \ + (((x) + ((mul)/2)) / (mul)) + +int ath9k_cmn_get_hw_crypto_keytype(struct sk_buff *skb); +void ath9k_cmn_update_ichannel(struct ath9k_channel *ichan, + struct ieee80211_channel *chan, + enum nl80211_channel_type channel_type); +struct ath9k_channel *ath9k_cmn_get_curchannel(struct ieee80211_hw *hw, + struct ath_hw *ah); +int ath9k_cmn_count_streams(unsigned int chainmask, int max); +void ath9k_cmn_btcoex_bt_stomp(struct ath_common *common, + enum ath_stomp_type stomp_type); +void ath9k_cmn_update_txpow(struct ath_hw *ah, u16 cur_txpow, + u16 new_txpow, u16 *txpower); +void ath9k_cmn_init_crypto(struct ath_hw *ah); diff --git a/drivers/net/wireless/ath/ath9k/debug.c b/drivers/net/wireless/ath/ath9k/debug.c new file mode 100644 index 0000000..b7a8110 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/debug.c @@ -0,0 +1,2118 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "ath9k.h" + +#define REG_WRITE_D(_ah, _reg, _val) \ + ath9k_hw_common(_ah)->ops->write((_ah), (_val), (_reg)) +#define REG_READ_D(_ah, _reg) \ + ath9k_hw_common(_ah)->ops->read((_ah), (_reg)) + + +static ssize_t ath9k_debugfs_read_buf(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + u8 *buf = file->private_data; + return simple_read_from_buffer(user_buf, count, ppos, buf, strlen(buf)); +} + +static int ath9k_debugfs_release_buf(struct inode *inode, struct file *file) +{ + vfree(file->private_data); + return 0; +} + +#ifdef CPTCFG_ATH_DEBUG + +static ssize_t read_file_debug(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + char buf[32]; + unsigned int len; + + len = sprintf(buf, "0x%08x\n", common->debug_mask); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_debug(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + unsigned long mask; + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (strict_strtoul(buf, 0, &mask)) + return -EINVAL; + + common->debug_mask = mask; + return count; +} + +static const struct file_operations fops_debug = { + .read = read_file_debug, + .write = write_file_debug, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +#endif + +#define DMA_BUF_LEN 1024 + +static ssize_t read_file_tx_chainmask(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_hw *ah = sc->sc_ah; + char buf[32]; + unsigned int len; + + len = sprintf(buf, "0x%08x\n", ah->txchainmask); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_tx_chainmask(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_hw *ah = sc->sc_ah; + unsigned long mask; + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (strict_strtoul(buf, 0, &mask)) + return -EINVAL; + + ah->txchainmask = mask; + ah->caps.tx_chainmask = mask; + return count; +} + +static const struct file_operations fops_tx_chainmask = { + .read = read_file_tx_chainmask, + .write = write_file_tx_chainmask, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + + +static ssize_t read_file_rx_chainmask(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_hw *ah = sc->sc_ah; + char buf[32]; + unsigned int len; + + len = sprintf(buf, "0x%08x\n", ah->rxchainmask); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_rx_chainmask(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_hw *ah = sc->sc_ah; + unsigned long mask; + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (strict_strtoul(buf, 0, &mask)) + return -EINVAL; + + ah->rxchainmask = mask; + ah->caps.rx_chainmask = mask; + return count; +} + +static const struct file_operations fops_rx_chainmask = { + .read = read_file_rx_chainmask, + .write = write_file_rx_chainmask, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_disable_ani(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + char buf[32]; + unsigned int len; + + len = sprintf(buf, "%d\n", common->disable_ani); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_disable_ani(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + unsigned long disable_ani; + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (strict_strtoul(buf, 0, &disable_ani)) + return -EINVAL; + + common->disable_ani = !!disable_ani; + + if (disable_ani) { + clear_bit(SC_OP_ANI_RUN, &sc->sc_flags); + ath_stop_ani(sc); + } else { + ath_check_ani(sc); + } + + return count; +} + +static const struct file_operations fops_disable_ani = { + .read = read_file_disable_ani, + .write = write_file_disable_ani, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_ant_diversity(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + char buf[32]; + unsigned int len; + + len = sprintf(buf, "%d\n", common->antenna_diversity); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_ant_diversity(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + unsigned long antenna_diversity; + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + if (!AR_SREV_9565(sc->sc_ah)) + goto exit; + + buf[len] = '\0'; + if (strict_strtoul(buf, 0, &antenna_diversity)) + return -EINVAL; + + common->antenna_diversity = !!antenna_diversity; + ath9k_ps_wakeup(sc); + ath_ant_comb_update(sc); + ath_dbg(common, CONFIG, "Antenna diversity: %d\n", + common->antenna_diversity); + ath9k_ps_restore(sc); +exit: + return count; +} + +static const struct file_operations fops_ant_diversity = { + .read = read_file_ant_diversity, + .write = write_file_ant_diversity, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_dma(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_hw *ah = sc->sc_ah; + char *buf; + int retval; + unsigned int len = 0; + u32 val[ATH9K_NUM_DMA_DEBUG_REGS]; + int i, qcuOffset = 0, dcuOffset = 0; + u32 *qcuBase = &val[0], *dcuBase = &val[4]; + + buf = kmalloc(DMA_BUF_LEN, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ath9k_ps_wakeup(sc); + + REG_WRITE_D(ah, AR_MACMISC, + ((AR_MACMISC_DMA_OBS_LINE_8 << AR_MACMISC_DMA_OBS_S) | + (AR_MACMISC_MISC_OBS_BUS_1 << + AR_MACMISC_MISC_OBS_BUS_MSB_S))); + + len += snprintf(buf + len, DMA_BUF_LEN - len, + "Raw DMA Debug values:\n"); + + for (i = 0; i < ATH9K_NUM_DMA_DEBUG_REGS; i++) { + if (i % 4 == 0) + len += snprintf(buf + len, DMA_BUF_LEN - len, "\n"); + + val[i] = REG_READ_D(ah, AR_DMADBG_0 + (i * sizeof(u32))); + len += snprintf(buf + len, DMA_BUF_LEN - len, "%d: %08x ", + i, val[i]); + } + + len += snprintf(buf + len, DMA_BUF_LEN - len, "\n\n"); + len += snprintf(buf + len, DMA_BUF_LEN - len, + "Num QCU: chain_st fsp_ok fsp_st DCU: chain_st\n"); + + for (i = 0; i < ATH9K_NUM_QUEUES; i++, qcuOffset += 4, dcuOffset += 5) { + if (i == 8) { + qcuOffset = 0; + qcuBase++; + } + + if (i == 6) { + dcuOffset = 0; + dcuBase++; + } + + len += snprintf(buf + len, DMA_BUF_LEN - len, + "%2d %2x %1x %2x %2x\n", + i, (*qcuBase & (0x7 << qcuOffset)) >> qcuOffset, + (*qcuBase & (0x8 << qcuOffset)) >> (qcuOffset + 3), + val[2] & (0x7 << (i * 3)) >> (i * 3), + (*dcuBase & (0x1f << dcuOffset)) >> dcuOffset); + } + + len += snprintf(buf + len, DMA_BUF_LEN - len, "\n"); + + len += snprintf(buf + len, DMA_BUF_LEN - len, + "qcu_stitch state: %2x qcu_fetch state: %2x\n", + (val[3] & 0x003c0000) >> 18, (val[3] & 0x03c00000) >> 22); + len += snprintf(buf + len, DMA_BUF_LEN - len, + "qcu_complete state: %2x dcu_complete state: %2x\n", + (val[3] & 0x1c000000) >> 26, (val[6] & 0x3)); + len += snprintf(buf + len, DMA_BUF_LEN - len, + "dcu_arb state: %2x dcu_fp state: %2x\n", + (val[5] & 0x06000000) >> 25, (val[5] & 0x38000000) >> 27); + len += snprintf(buf + len, DMA_BUF_LEN - len, + "chan_idle_dur: %3d chan_idle_dur_valid: %1d\n", + (val[6] & 0x000003fc) >> 2, (val[6] & 0x00000400) >> 10); + len += snprintf(buf + len, DMA_BUF_LEN - len, + "txfifo_valid_0: %1d txfifo_valid_1: %1d\n", + (val[6] & 0x00000800) >> 11, (val[6] & 0x00001000) >> 12); + len += snprintf(buf + len, DMA_BUF_LEN - len, + "txfifo_dcu_num_0: %2d txfifo_dcu_num_1: %2d\n", + (val[6] & 0x0001e000) >> 13, (val[6] & 0x001e0000) >> 17); + + len += snprintf(buf + len, DMA_BUF_LEN - len, "pcu observe: 0x%x\n", + REG_READ_D(ah, AR_OBS_BUS_1)); + len += snprintf(buf + len, DMA_BUF_LEN - len, + "AR_CR: 0x%x\n", REG_READ_D(ah, AR_CR)); + + ath9k_ps_restore(sc); + + if (len > DMA_BUF_LEN) + len = DMA_BUF_LEN; + + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + return retval; +} + +static const struct file_operations fops_dma = { + .read = read_file_dma, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + + +void ath_debug_stat_interrupt(struct ath_softc *sc, enum ath9k_int status) +{ + if (status) + sc->debug.stats.istats.total++; + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) { + if (status & ATH9K_INT_RXLP) + sc->debug.stats.istats.rxlp++; + if (status & ATH9K_INT_RXHP) + sc->debug.stats.istats.rxhp++; + if (status & ATH9K_INT_BB_WATCHDOG) + sc->debug.stats.istats.bb_watchdog++; + } else { + if (status & ATH9K_INT_RX) + sc->debug.stats.istats.rxok++; + } + if (status & ATH9K_INT_RXEOL) + sc->debug.stats.istats.rxeol++; + if (status & ATH9K_INT_RXORN) + sc->debug.stats.istats.rxorn++; + if (status & ATH9K_INT_TX) + sc->debug.stats.istats.txok++; + if (status & ATH9K_INT_TXURN) + sc->debug.stats.istats.txurn++; + if (status & ATH9K_INT_RXPHY) + sc->debug.stats.istats.rxphyerr++; + if (status & ATH9K_INT_RXKCM) + sc->debug.stats.istats.rx_keycache_miss++; + if (status & ATH9K_INT_SWBA) + sc->debug.stats.istats.swba++; + if (status & ATH9K_INT_BMISS) + sc->debug.stats.istats.bmiss++; + if (status & ATH9K_INT_BNR) + sc->debug.stats.istats.bnr++; + if (status & ATH9K_INT_CST) + sc->debug.stats.istats.cst++; + if (status & ATH9K_INT_GTT) + sc->debug.stats.istats.gtt++; + if (status & ATH9K_INT_TIM) + sc->debug.stats.istats.tim++; + if (status & ATH9K_INT_CABEND) + sc->debug.stats.istats.cabend++; + if (status & ATH9K_INT_DTIMSYNC) + sc->debug.stats.istats.dtimsync++; + if (status & ATH9K_INT_DTIM) + sc->debug.stats.istats.dtim++; + if (status & ATH9K_INT_TSFOOR) + sc->debug.stats.istats.tsfoor++; + if (status & ATH9K_INT_MCI) + sc->debug.stats.istats.mci++; + if (status & ATH9K_INT_GENTIMER) + sc->debug.stats.istats.gen_timer++; +} + +static ssize_t read_file_interrupt(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + unsigned int len = 0; + int rv; + int mxlen = 4000; + char *buf = kmalloc(mxlen, GFP_KERNEL); + if (!buf) + return -ENOMEM; + +#define PR_IS(a, s) \ + do { \ + len += snprintf(buf + len, mxlen - len, \ + "%21s: %10u\n", a, \ + sc->debug.stats.istats.s); \ + } while (0) + + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) { + PR_IS("RXLP", rxlp); + PR_IS("RXHP", rxhp); + PR_IS("WATHDOG", bb_watchdog); + } else { + PR_IS("RX", rxok); + } + PR_IS("RXEOL", rxeol); + PR_IS("RXORN", rxorn); + PR_IS("TX", txok); + PR_IS("TXURN", txurn); + PR_IS("MIB", mib); + PR_IS("RXPHY", rxphyerr); + PR_IS("RXKCM", rx_keycache_miss); + PR_IS("SWBA", swba); + PR_IS("BMISS", bmiss); + PR_IS("BNR", bnr); + PR_IS("CST", cst); + PR_IS("GTT", gtt); + PR_IS("TIM", tim); + PR_IS("CABEND", cabend); + PR_IS("DTIMSYNC", dtimsync); + PR_IS("DTIM", dtim); + PR_IS("TSFOOR", tsfoor); + PR_IS("MCI", mci); + PR_IS("GENTIMER", gen_timer); + PR_IS("TOTAL", total); + + len += snprintf(buf + len, mxlen - len, + "SYNC_CAUSE stats:\n"); + + PR_IS("Sync-All", sync_cause_all); + PR_IS("RTC-IRQ", sync_rtc_irq); + PR_IS("MAC-IRQ", sync_mac_irq); + PR_IS("EEPROM-Illegal-Access", eeprom_illegal_access); + PR_IS("APB-Timeout", apb_timeout); + PR_IS("PCI-Mode-Conflict", pci_mode_conflict); + PR_IS("HOST1-Fatal", host1_fatal); + PR_IS("HOST1-Perr", host1_perr); + PR_IS("TRCV-FIFO-Perr", trcv_fifo_perr); + PR_IS("RADM-CPL-EP", radm_cpl_ep); + PR_IS("RADM-CPL-DLLP-Abort", radm_cpl_dllp_abort); + PR_IS("RADM-CPL-TLP-Abort", radm_cpl_tlp_abort); + PR_IS("RADM-CPL-ECRC-Err", radm_cpl_ecrc_err); + PR_IS("RADM-CPL-Timeout", radm_cpl_timeout); + PR_IS("Local-Bus-Timeout", local_timeout); + PR_IS("PM-Access", pm_access); + PR_IS("MAC-Awake", mac_awake); + PR_IS("MAC-Asleep", mac_asleep); + PR_IS("MAC-Sleep-Access", mac_sleep_access); + + if (len > mxlen) + len = mxlen; + + rv = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + return rv; +} + +static const struct file_operations fops_interrupt = { + .read = read_file_interrupt, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_xmit(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + char *buf; + unsigned int len = 0, size = 2048; + ssize_t retval = 0; + + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + len += sprintf(buf, "%30s %10s%10s%10s\n\n", + "BE", "BK", "VI", "VO"); + + PR("MPDUs Queued: ", queued); + PR("MPDUs Completed: ", completed); + PR("MPDUs XRetried: ", xretries); + PR("Aggregates: ", a_aggr); + PR("AMPDUs Queued HW:", a_queued_hw); + PR("AMPDUs Queued SW:", a_queued_sw); + PR("AMPDUs Completed:", a_completed); + PR("AMPDUs Retried: ", a_retries); + PR("AMPDUs XRetried: ", a_xretries); + PR("TXERR Filtered: ", txerr_filtered); + PR("FIFO Underrun: ", fifo_underrun); + PR("TXOP Exceeded: ", xtxop); + PR("TXTIMER Expiry: ", timer_exp); + PR("DESC CFG Error: ", desc_cfg_err); + PR("DATA Underrun: ", data_underrun); + PR("DELIM Underrun: ", delim_underrun); + PR("TX-Pkts-All: ", tx_pkts_all); + PR("TX-Bytes-All: ", tx_bytes_all); + PR("HW-put-tx-buf: ", puttxbuf); + PR("HW-tx-start: ", txstart); + PR("HW-tx-proc-desc: ", txprocdesc); + PR("TX-Failed: ", txfailed); + + if (len > size) + len = size; + + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return retval; +} + +static ssize_t read_file_queues(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_txq *txq; + char *buf; + unsigned int len = 0, size = 1024; + ssize_t retval = 0; + int i; + char *qname[4] = {"VO", "VI", "BE", "BK"}; + + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + for (i = 0; i < IEEE80211_NUM_ACS; i++) { + txq = sc->tx.txq_map[i]; + len += snprintf(buf + len, size - len, "(%s): ", qname[i]); + + ath_txq_lock(sc, txq); + + len += snprintf(buf + len, size - len, "%s: %d ", + "qnum", txq->axq_qnum); + len += snprintf(buf + len, size - len, "%s: %2d ", + "qdepth", txq->axq_depth); + len += snprintf(buf + len, size - len, "%s: %2d ", + "ampdu-depth", txq->axq_ampdu_depth); + len += snprintf(buf + len, size - len, "%s: %3d ", + "pending", txq->pending_frames); + len += snprintf(buf + len, size - len, "%s: %d\n", + "stopped", txq->stopped); + + ath_txq_unlock(sc, txq); + } + + if (len > size) + len = size; + + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return retval; +} + +static ssize_t read_file_misc(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ieee80211_hw *hw = sc->hw; + struct ath9k_vif_iter_data iter_data; + char buf[512]; + unsigned int len = 0; + ssize_t retval = 0; + unsigned int reg; + u32 rxfilter; + + len += snprintf(buf + len, sizeof(buf) - len, + "BSSID: %pM\n", common->curbssid); + len += snprintf(buf + len, sizeof(buf) - len, + "BSSID-MASK: %pM\n", common->bssidmask); + len += snprintf(buf + len, sizeof(buf) - len, + "OPMODE: %s\n", ath_opmode_to_string(sc->sc_ah->opmode)); + + ath9k_ps_wakeup(sc); + rxfilter = ath9k_hw_getrxfilter(sc->sc_ah); + ath9k_ps_restore(sc); + + len += snprintf(buf + len, sizeof(buf) - len, + "RXFILTER: 0x%x", rxfilter); + + if (rxfilter & ATH9K_RX_FILTER_UCAST) + len += snprintf(buf + len, sizeof(buf) - len, " UCAST"); + if (rxfilter & ATH9K_RX_FILTER_MCAST) + len += snprintf(buf + len, sizeof(buf) - len, " MCAST"); + if (rxfilter & ATH9K_RX_FILTER_BCAST) + len += snprintf(buf + len, sizeof(buf) - len, " BCAST"); + if (rxfilter & ATH9K_RX_FILTER_CONTROL) + len += snprintf(buf + len, sizeof(buf) - len, " CONTROL"); + if (rxfilter & ATH9K_RX_FILTER_BEACON) + len += snprintf(buf + len, sizeof(buf) - len, " BEACON"); + if (rxfilter & ATH9K_RX_FILTER_PROM) + len += snprintf(buf + len, sizeof(buf) - len, " PROM"); + if (rxfilter & ATH9K_RX_FILTER_PROBEREQ) + len += snprintf(buf + len, sizeof(buf) - len, " PROBEREQ"); + if (rxfilter & ATH9K_RX_FILTER_PHYERR) + len += snprintf(buf + len, sizeof(buf) - len, " PHYERR"); + if (rxfilter & ATH9K_RX_FILTER_MYBEACON) + len += snprintf(buf + len, sizeof(buf) - len, " MYBEACON"); + if (rxfilter & ATH9K_RX_FILTER_COMP_BAR) + len += snprintf(buf + len, sizeof(buf) - len, " COMP_BAR"); + if (rxfilter & ATH9K_RX_FILTER_PSPOLL) + len += snprintf(buf + len, sizeof(buf) - len, " PSPOLL"); + if (rxfilter & ATH9K_RX_FILTER_PHYRADAR) + len += snprintf(buf + len, sizeof(buf) - len, " PHYRADAR"); + if (rxfilter & ATH9K_RX_FILTER_MCAST_BCAST_ALL) + len += snprintf(buf + len, sizeof(buf) - len, " MCAST_BCAST_ALL"); + if (rxfilter & ATH9K_RX_FILTER_CONTROL_WRAPPER) + len += snprintf(buf + len, sizeof(buf) - len, " CONTROL_WRAPPER"); + + len += snprintf(buf + len, sizeof(buf) - len, "\n"); + + reg = sc->sc_ah->imask; + + len += snprintf(buf + len, sizeof(buf) - len, "INTERRUPT-MASK: 0x%x", reg); + + if (reg & ATH9K_INT_SWBA) + len += snprintf(buf + len, sizeof(buf) - len, " SWBA"); + if (reg & ATH9K_INT_BMISS) + len += snprintf(buf + len, sizeof(buf) - len, " BMISS"); + if (reg & ATH9K_INT_CST) + len += snprintf(buf + len, sizeof(buf) - len, " CST"); + if (reg & ATH9K_INT_RX) + len += snprintf(buf + len, sizeof(buf) - len, " RX"); + if (reg & ATH9K_INT_RXHP) + len += snprintf(buf + len, sizeof(buf) - len, " RXHP"); + if (reg & ATH9K_INT_RXLP) + len += snprintf(buf + len, sizeof(buf) - len, " RXLP"); + if (reg & ATH9K_INT_BB_WATCHDOG) + len += snprintf(buf + len, sizeof(buf) - len, " BB_WATCHDOG"); + + len += snprintf(buf + len, sizeof(buf) - len, "\n"); + + ath9k_calculate_iter_data(hw, NULL, &iter_data); + + len += snprintf(buf + len, sizeof(buf) - len, + "VIF-COUNTS: AP: %i STA: %i MESH: %i WDS: %i" + " ADHOC: %i TOTAL: %hi BEACON-VIF: %hi\n", + iter_data.naps, iter_data.nstations, iter_data.nmeshes, + iter_data.nwds, iter_data.nadhocs, + sc->nvifs, sc->nbcnvifs); + + if (len > sizeof(buf)) + len = sizeof(buf); + + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + return retval; +} + +static ssize_t read_file_reset(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + char buf[512]; + unsigned int len = 0; + + len += snprintf(buf + len, sizeof(buf) - len, + "%17s: %2d\n", "Baseband Hang", + sc->debug.stats.reset[RESET_TYPE_BB_HANG]); + len += snprintf(buf + len, sizeof(buf) - len, + "%17s: %2d\n", "Baseband Watchdog", + sc->debug.stats.reset[RESET_TYPE_BB_WATCHDOG]); + len += snprintf(buf + len, sizeof(buf) - len, + "%17s: %2d\n", "Fatal HW Error", + sc->debug.stats.reset[RESET_TYPE_FATAL_INT]); + len += snprintf(buf + len, sizeof(buf) - len, + "%17s: %2d\n", "TX HW error", + sc->debug.stats.reset[RESET_TYPE_TX_ERROR]); + len += snprintf(buf + len, sizeof(buf) - len, + "%17s: %2d\n", "TX Path Hang", + sc->debug.stats.reset[RESET_TYPE_TX_HANG]); + len += snprintf(buf + len, sizeof(buf) - len, + "%17s: %2d\n", "PLL RX Hang", + sc->debug.stats.reset[RESET_TYPE_PLL_HANG]); + len += snprintf(buf + len, sizeof(buf) - len, + "%17s: %2d\n", "MCI Reset", + sc->debug.stats.reset[RESET_TYPE_MCI]); + + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +void ath_debug_stat_tx(struct ath_softc *sc, struct ath_buf *bf, + struct ath_tx_status *ts, struct ath_txq *txq, + unsigned int flags) +{ +#define TX_SAMP_DBG(c) (sc->debug.bb_mac_samp[sc->debug.sampidx].ts\ + [sc->debug.tsidx].c) + int qnum = txq->axq_qnum; + + TX_STAT_INC(qnum, tx_pkts_all); + sc->debug.stats.txstats[qnum].tx_bytes_all += bf->bf_mpdu->len; + + if (bf_isampdu(bf)) { + if (flags & ATH_TX_ERROR) + TX_STAT_INC(qnum, a_xretries); + else + TX_STAT_INC(qnum, a_completed); + } else { + if (ts->ts_status & ATH9K_TXERR_XRETRY) + TX_STAT_INC(qnum, xretries); + else + TX_STAT_INC(qnum, completed); + } + + if (ts->ts_status & ATH9K_TXERR_FILT) + TX_STAT_INC(qnum, txerr_filtered); + if (ts->ts_status & ATH9K_TXERR_FIFO) + TX_STAT_INC(qnum, fifo_underrun); + if (ts->ts_status & ATH9K_TXERR_XTXOP) + TX_STAT_INC(qnum, xtxop); + if (ts->ts_status & ATH9K_TXERR_TIMER_EXPIRED) + TX_STAT_INC(qnum, timer_exp); + if (ts->ts_flags & ATH9K_TX_DESC_CFG_ERR) + TX_STAT_INC(qnum, desc_cfg_err); + if (ts->ts_flags & ATH9K_TX_DATA_UNDERRUN) + TX_STAT_INC(qnum, data_underrun); + if (ts->ts_flags & ATH9K_TX_DELIM_UNDERRUN) + TX_STAT_INC(qnum, delim_underrun); + +#ifdef CPTCFG_ATH9K_MAC_DEBUG + spin_lock(&sc->debug.samp_lock); + TX_SAMP_DBG(jiffies) = jiffies; + TX_SAMP_DBG(rssi_ctl0) = ts->ts_rssi_ctl0; + TX_SAMP_DBG(rssi_ctl1) = ts->ts_rssi_ctl1; + TX_SAMP_DBG(rssi_ctl2) = ts->ts_rssi_ctl2; + TX_SAMP_DBG(rssi_ext0) = ts->ts_rssi_ext0; + TX_SAMP_DBG(rssi_ext1) = ts->ts_rssi_ext1; + TX_SAMP_DBG(rssi_ext2) = ts->ts_rssi_ext2; + TX_SAMP_DBG(rateindex) = ts->ts_rateindex; + TX_SAMP_DBG(isok) = !!(ts->ts_status & ATH9K_TXERR_MASK); + TX_SAMP_DBG(rts_fail_cnt) = ts->ts_shortretry; + TX_SAMP_DBG(data_fail_cnt) = ts->ts_longretry; + TX_SAMP_DBG(rssi) = ts->ts_rssi; + TX_SAMP_DBG(tid) = ts->tid; + TX_SAMP_DBG(qid) = ts->qid; + + if (ts->ts_flags & ATH9K_TX_BA) { + TX_SAMP_DBG(ba_low) = ts->ba_low; + TX_SAMP_DBG(ba_high) = ts->ba_high; + } else { + TX_SAMP_DBG(ba_low) = 0; + TX_SAMP_DBG(ba_high) = 0; + } + + sc->debug.tsidx = (sc->debug.tsidx + 1) % ATH_DBG_MAX_SAMPLES; + spin_unlock(&sc->debug.samp_lock); +#endif + +#undef TX_SAMP_DBG +} + +static const struct file_operations fops_xmit = { + .read = read_file_xmit, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static const struct file_operations fops_queues = { + .read = read_file_queues, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static const struct file_operations fops_misc = { + .read = read_file_misc, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static const struct file_operations fops_reset = { + .read = read_file_reset, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_recv(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ +#define PHY_ERR(s, p) \ + len += snprintf(buf + len, size - len, "%22s : %10u\n", s, \ + sc->debug.stats.rxstats.phy_err_stats[p]); + +#define RXS_ERR(s, e) \ + do { \ + len += snprintf(buf + len, size - len, \ + "%22s : %10u\n", s, \ + sc->debug.stats.rxstats.e); \ + } while (0) + + struct ath_softc *sc = file->private_data; + char *buf; + unsigned int len = 0, size = 1600; + ssize_t retval = 0; + + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + RXS_ERR("CRC ERR", crc_err); + RXS_ERR("DECRYPT CRC ERR", decrypt_crc_err); + RXS_ERR("PHY ERR", phy_err); + RXS_ERR("MIC ERR", mic_err); + RXS_ERR("PRE-DELIM CRC ERR", pre_delim_crc_err); + RXS_ERR("POST-DELIM CRC ERR", post_delim_crc_err); + RXS_ERR("DECRYPT BUSY ERR", decrypt_busy_err); + RXS_ERR("RX-LENGTH-ERR", rx_len_err); + RXS_ERR("RX-OOM-ERR", rx_oom_err); + RXS_ERR("RX-RATE-ERR", rx_rate_err); + RXS_ERR("RX-TOO-MANY-FRAGS", rx_too_many_frags_err); + + PHY_ERR("UNDERRUN ERR", ATH9K_PHYERR_UNDERRUN); + PHY_ERR("TIMING ERR", ATH9K_PHYERR_TIMING); + PHY_ERR("PARITY ERR", ATH9K_PHYERR_PARITY); + PHY_ERR("RATE ERR", ATH9K_PHYERR_RATE); + PHY_ERR("LENGTH ERR", ATH9K_PHYERR_LENGTH); + PHY_ERR("RADAR ERR", ATH9K_PHYERR_RADAR); + PHY_ERR("SERVICE ERR", ATH9K_PHYERR_SERVICE); + PHY_ERR("TOR ERR", ATH9K_PHYERR_TOR); + PHY_ERR("OFDM-TIMING ERR", ATH9K_PHYERR_OFDM_TIMING); + PHY_ERR("OFDM-SIGNAL-PARITY ERR", ATH9K_PHYERR_OFDM_SIGNAL_PARITY); + PHY_ERR("OFDM-RATE ERR", ATH9K_PHYERR_OFDM_RATE_ILLEGAL); + PHY_ERR("OFDM-LENGTH ERR", ATH9K_PHYERR_OFDM_LENGTH_ILLEGAL); + PHY_ERR("OFDM-POWER-DROP ERR", ATH9K_PHYERR_OFDM_POWER_DROP); + PHY_ERR("OFDM-SERVICE ERR", ATH9K_PHYERR_OFDM_SERVICE); + PHY_ERR("OFDM-RESTART ERR", ATH9K_PHYERR_OFDM_RESTART); + PHY_ERR("FALSE-RADAR-EXT ERR", ATH9K_PHYERR_FALSE_RADAR_EXT); + PHY_ERR("CCK-TIMING ERR", ATH9K_PHYERR_CCK_TIMING); + PHY_ERR("CCK-HEADER-CRC ERR", ATH9K_PHYERR_CCK_HEADER_CRC); + PHY_ERR("CCK-RATE ERR", ATH9K_PHYERR_CCK_RATE_ILLEGAL); + PHY_ERR("CCK-SERVICE ERR", ATH9K_PHYERR_CCK_SERVICE); + PHY_ERR("CCK-RESTART ERR", ATH9K_PHYERR_CCK_RESTART); + PHY_ERR("CCK-LENGTH ERR", ATH9K_PHYERR_CCK_LENGTH_ILLEGAL); + PHY_ERR("CCK-POWER-DROP ERR", ATH9K_PHYERR_CCK_POWER_DROP); + PHY_ERR("HT-CRC ERR", ATH9K_PHYERR_HT_CRC_ERROR); + PHY_ERR("HT-LENGTH ERR", ATH9K_PHYERR_HT_LENGTH_ILLEGAL); + PHY_ERR("HT-RATE ERR", ATH9K_PHYERR_HT_RATE_ILLEGAL); + + RXS_ERR("RX-Pkts-All", rx_pkts_all); + RXS_ERR("RX-Bytes-All", rx_bytes_all); + RXS_ERR("RX-Beacons", rx_beacons); + RXS_ERR("RX-Frags", rx_frags); + RXS_ERR("RX-Spectral", rx_spectral); + + if (len > size) + len = size; + + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return retval; + +#undef RXS_ERR +#undef PHY_ERR +} + +void ath_debug_stat_rx(struct ath_softc *sc, struct ath_rx_status *rs) +{ +#define RX_PHY_ERR_INC(c) sc->debug.stats.rxstats.phy_err_stats[c]++ +#define RX_SAMP_DBG(c) (sc->debug.bb_mac_samp[sc->debug.sampidx].rs\ + [sc->debug.rsidx].c) + + RX_STAT_INC(rx_pkts_all); + sc->debug.stats.rxstats.rx_bytes_all += rs->rs_datalen; + + if (rs->rs_status & ATH9K_RXERR_CRC) + RX_STAT_INC(crc_err); + if (rs->rs_status & ATH9K_RXERR_DECRYPT) + RX_STAT_INC(decrypt_crc_err); + if (rs->rs_status & ATH9K_RXERR_MIC) + RX_STAT_INC(mic_err); + if (rs->rs_status & ATH9K_RX_DELIM_CRC_PRE) + RX_STAT_INC(pre_delim_crc_err); + if (rs->rs_status & ATH9K_RX_DELIM_CRC_POST) + RX_STAT_INC(post_delim_crc_err); + if (rs->rs_status & ATH9K_RX_DECRYPT_BUSY) + RX_STAT_INC(decrypt_busy_err); + + if (rs->rs_status & ATH9K_RXERR_PHY) { + RX_STAT_INC(phy_err); + if (rs->rs_phyerr < ATH9K_PHYERR_MAX) + RX_PHY_ERR_INC(rs->rs_phyerr); + } + +#ifdef CPTCFG_ATH9K_MAC_DEBUG + spin_lock(&sc->debug.samp_lock); + RX_SAMP_DBG(jiffies) = jiffies; + RX_SAMP_DBG(rssi_ctl0) = rs->rs_rssi_ctl0; + RX_SAMP_DBG(rssi_ctl1) = rs->rs_rssi_ctl1; + RX_SAMP_DBG(rssi_ctl2) = rs->rs_rssi_ctl2; + RX_SAMP_DBG(rssi_ext0) = rs->rs_rssi_ext0; + RX_SAMP_DBG(rssi_ext1) = rs->rs_rssi_ext1; + RX_SAMP_DBG(rssi_ext2) = rs->rs_rssi_ext2; + RX_SAMP_DBG(antenna) = rs->rs_antenna; + RX_SAMP_DBG(rssi) = rs->rs_rssi; + RX_SAMP_DBG(rate) = rs->rs_rate; + RX_SAMP_DBG(is_mybeacon) = rs->is_mybeacon; + + sc->debug.rsidx = (sc->debug.rsidx + 1) % ATH_DBG_MAX_SAMPLES; + spin_unlock(&sc->debug.samp_lock); + +#endif + +#undef RX_PHY_ERR_INC +#undef RX_SAMP_DBG +} + +static const struct file_operations fops_recv = { + .read = read_file_recv, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_spec_scan_ctl(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + char *mode = ""; + unsigned int len; + + switch (sc->spectral_mode) { + case SPECTRAL_DISABLED: + mode = "disable"; + break; + case SPECTRAL_BACKGROUND: + mode = "background"; + break; + case SPECTRAL_CHANSCAN: + mode = "chanscan"; + break; + case SPECTRAL_MANUAL: + mode = "manual"; + break; + } + len = strlen(mode); + return simple_read_from_buffer(user_buf, count, ppos, mode, len); +} + +static ssize_t write_file_spec_scan_ctl(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + + if (strncmp("trigger", buf, 7) == 0) { + ath9k_spectral_scan_trigger(sc->hw); + } else if (strncmp("background", buf, 9) == 0) { + ath9k_spectral_scan_config(sc->hw, SPECTRAL_BACKGROUND); + ath_dbg(common, CONFIG, "spectral scan: background mode enabled\n"); + } else if (strncmp("chanscan", buf, 8) == 0) { + ath9k_spectral_scan_config(sc->hw, SPECTRAL_CHANSCAN); + ath_dbg(common, CONFIG, "spectral scan: channel scan mode enabled\n"); + } else if (strncmp("manual", buf, 6) == 0) { + ath9k_spectral_scan_config(sc->hw, SPECTRAL_MANUAL); + ath_dbg(common, CONFIG, "spectral scan: manual mode enabled\n"); + } else if (strncmp("disable", buf, 7) == 0) { + ath9k_spectral_scan_config(sc->hw, SPECTRAL_DISABLED); + ath_dbg(common, CONFIG, "spectral scan: disabled\n"); + } else { + return -EINVAL; + } + + return count; +} + +static const struct file_operations fops_spec_scan_ctl = { + .read = read_file_spec_scan_ctl, + .write = write_file_spec_scan_ctl, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_spectral_short_repeat(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + char buf[32]; + unsigned int len; + + len = sprintf(buf, "%d\n", sc->spec_config.short_repeat); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_spectral_short_repeat(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + unsigned long val; + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val < 0 || val > 1) + return -EINVAL; + + sc->spec_config.short_repeat = val; + return count; +} + +static const struct file_operations fops_spectral_short_repeat = { + .read = read_file_spectral_short_repeat, + .write = write_file_spectral_short_repeat, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_spectral_count(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + char buf[32]; + unsigned int len; + + len = sprintf(buf, "%d\n", sc->spec_config.count); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_spectral_count(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + unsigned long val; + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val < 0 || val > 255) + return -EINVAL; + + sc->spec_config.count = val; + return count; +} + +static const struct file_operations fops_spectral_count = { + .read = read_file_spectral_count, + .write = write_file_spectral_count, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_spectral_period(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + char buf[32]; + unsigned int len; + + len = sprintf(buf, "%d\n", sc->spec_config.period); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_spectral_period(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + unsigned long val; + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val < 0 || val > 255) + return -EINVAL; + + sc->spec_config.period = val; + return count; +} + +static const struct file_operations fops_spectral_period = { + .read = read_file_spectral_period, + .write = write_file_spectral_period, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_spectral_fft_period(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + char buf[32]; + unsigned int len; + + len = sprintf(buf, "%d\n", sc->spec_config.fft_period); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_spectral_fft_period(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + unsigned long val; + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val < 0 || val > 15) + return -EINVAL; + + sc->spec_config.fft_period = val; + return count; +} + +static const struct file_operations fops_spectral_fft_period = { + .read = read_file_spectral_fft_period, + .write = write_file_spectral_fft_period, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static struct dentry *create_buf_file_handler(const char *filename, + struct dentry *parent, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,3,0)) + umode_t mode, +#else + int mode, +#endif + struct rchan_buf *buf, + int *is_global) +{ + struct dentry *buf_file; + + buf_file = debugfs_create_file(filename, mode, parent, buf, + &relay_file_operations); + *is_global = 1; + return buf_file; +} + +static int remove_buf_file_handler(struct dentry *dentry) +{ + debugfs_remove(dentry); + + return 0; +} + +void ath_debug_send_fft_sample(struct ath_softc *sc, + struct fft_sample_tlv *fft_sample_tlv) +{ + int length; + if (!sc->rfs_chan_spec_scan) + return; + + length = __be16_to_cpu(fft_sample_tlv->length) + + sizeof(*fft_sample_tlv); + relay_write(sc->rfs_chan_spec_scan, fft_sample_tlv, length); +} + +static struct rchan_callbacks rfs_spec_scan_cb = { + .create_buf_file = create_buf_file_handler, + .remove_buf_file = remove_buf_file_handler, +}; + + +static ssize_t read_file_regidx(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + char buf[32]; + unsigned int len; + + len = sprintf(buf, "0x%08x\n", sc->debug.regidx); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_regidx(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + unsigned long regidx; + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (strict_strtoul(buf, 0, ®idx)) + return -EINVAL; + + sc->debug.regidx = regidx; + return count; +} + +static const struct file_operations fops_regidx = { + .read = read_file_regidx, + .write = write_file_regidx, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_regval(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_hw *ah = sc->sc_ah; + char buf[32]; + unsigned int len; + u32 regval; + + ath9k_ps_wakeup(sc); + regval = REG_READ_D(ah, sc->debug.regidx); + ath9k_ps_restore(sc); + len = sprintf(buf, "0x%08x\n", regval); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_regval(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_hw *ah = sc->sc_ah; + unsigned long regval; + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (strict_strtoul(buf, 0, ®val)) + return -EINVAL; + + ath9k_ps_wakeup(sc); + REG_WRITE_D(ah, sc->debug.regidx, regval); + ath9k_ps_restore(sc); + return count; +} + +static const struct file_operations fops_regval = { + .read = read_file_regval, + .write = write_file_regval, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +#define REGDUMP_LINE_SIZE 20 + +static int open_file_regdump(struct inode *inode, struct file *file) +{ + struct ath_softc *sc = inode->i_private; + unsigned int len = 0; + u8 *buf; + int i; + unsigned long num_regs, regdump_len, max_reg_offset; + + max_reg_offset = AR_SREV_9300_20_OR_LATER(sc->sc_ah) ? 0x16bd4 : 0xb500; + num_regs = max_reg_offset / 4 + 1; + regdump_len = num_regs * REGDUMP_LINE_SIZE + 1; + buf = vmalloc(regdump_len); + if (!buf) + return -ENOMEM; + + ath9k_ps_wakeup(sc); + for (i = 0; i < num_regs; i++) + len += scnprintf(buf + len, regdump_len - len, + "0x%06x 0x%08x\n", i << 2, REG_READ(sc->sc_ah, i << 2)); + ath9k_ps_restore(sc); + + file->private_data = buf; + + return 0; +} + +static const struct file_operations fops_regdump = { + .open = open_file_regdump, + .read = ath9k_debugfs_read_buf, + .release = ath9k_debugfs_release_buf, + .owner = THIS_MODULE, + .llseek = default_llseek,/* read accesses f_pos */ +}; + +static ssize_t read_file_dump_nfcal(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_hw *ah = sc->sc_ah; + struct ath9k_nfcal_hist *h = sc->caldata.nfCalHist; + struct ath_common *common = ath9k_hw_common(ah); + struct ieee80211_conf *conf = &common->hw->conf; + u32 len = 0, size = 1500; + u32 i, j; + ssize_t retval = 0; + char *buf; + u8 chainmask = (ah->rxchainmask << 3) | ah->rxchainmask; + u8 nread; + + buf = kzalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + len += snprintf(buf + len, size - len, + "Channel Noise Floor : %d\n", ah->noise); + len += snprintf(buf + len, size - len, + "Chain | privNF | # Readings | NF Readings\n"); + for (i = 0; i < NUM_NF_READINGS; i++) { + if (!(chainmask & (1 << i)) || + ((i >= AR5416_MAX_CHAINS) && !conf_is_ht40(conf))) + continue; + + nread = AR_PHY_CCA_FILTERWINDOW_LENGTH - h[i].invalidNFcount; + len += snprintf(buf + len, size - len, " %d\t %d\t %d\t\t", + i, h[i].privNF, nread); + for (j = 0; j < nread; j++) + len += snprintf(buf + len, size - len, + " %d", h[i].nfCalBuffer[j]); + len += snprintf(buf + len, size - len, "\n"); + } + + if (len > size) + len = size; + + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return retval; +} + +static const struct file_operations fops_dump_nfcal = { + .read = read_file_dump_nfcal, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_base_eeprom(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_hw *ah = sc->sc_ah; + u32 len = 0, size = 1500; + ssize_t retval = 0; + char *buf; + + buf = kzalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + len = ah->eep_ops->dump_eeprom(ah, true, buf, len, size); + + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return retval; +} + +static const struct file_operations fops_base_eeprom = { + .read = read_file_base_eeprom, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_modal_eeprom(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath_hw *ah = sc->sc_ah; + u32 len = 0, size = 6000; + char *buf; + size_t retval; + + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + len = ah->eep_ops->dump_eeprom(ah, false, buf, len, size); + + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return retval; +} + +static const struct file_operations fops_modal_eeprom = { + .read = read_file_modal_eeprom, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +#ifdef CPTCFG_ATH9K_MAC_DEBUG + +void ath9k_debug_samp_bb_mac(struct ath_softc *sc) +{ +#define ATH_SAMP_DBG(c) (sc->debug.bb_mac_samp[sc->debug.sampidx].c) + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + unsigned long flags; + int i; + + ath9k_ps_wakeup(sc); + + spin_lock_bh(&sc->debug.samp_lock); + + spin_lock_irqsave(&common->cc_lock, flags); + ath_hw_cycle_counters_update(common); + + ATH_SAMP_DBG(cc.cycles) = common->cc_ani.cycles; + ATH_SAMP_DBG(cc.rx_busy) = common->cc_ani.rx_busy; + ATH_SAMP_DBG(cc.rx_frame) = common->cc_ani.rx_frame; + ATH_SAMP_DBG(cc.tx_frame) = common->cc_ani.tx_frame; + spin_unlock_irqrestore(&common->cc_lock, flags); + + ATH_SAMP_DBG(noise) = ah->noise; + + REG_WRITE_D(ah, AR_MACMISC, + ((AR_MACMISC_DMA_OBS_LINE_8 << AR_MACMISC_DMA_OBS_S) | + (AR_MACMISC_MISC_OBS_BUS_1 << + AR_MACMISC_MISC_OBS_BUS_MSB_S))); + + for (i = 0; i < ATH9K_NUM_DMA_DEBUG_REGS; i++) + ATH_SAMP_DBG(dma_dbg_reg_vals[i]) = REG_READ_D(ah, + AR_DMADBG_0 + (i * sizeof(u32))); + + ATH_SAMP_DBG(pcu_obs) = REG_READ_D(ah, AR_OBS_BUS_1); + ATH_SAMP_DBG(pcu_cr) = REG_READ_D(ah, AR_CR); + + memcpy(ATH_SAMP_DBG(nfCalHist), sc->caldata.nfCalHist, + sizeof(ATH_SAMP_DBG(nfCalHist))); + + sc->debug.sampidx = (sc->debug.sampidx + 1) % ATH_DBG_MAX_SAMPLES; + spin_unlock_bh(&sc->debug.samp_lock); + ath9k_ps_restore(sc); + +#undef ATH_SAMP_DBG +} + +static int open_file_bb_mac_samps(struct inode *inode, struct file *file) +{ +#define ATH_SAMP_DBG(c) bb_mac_samp[sampidx].c + struct ath_softc *sc = inode->i_private; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ieee80211_conf *conf = &common->hw->conf; + struct ath_dbg_bb_mac_samp *bb_mac_samp; + struct ath9k_nfcal_hist *h; + int i, j, qcuOffset = 0, dcuOffset = 0; + u32 *qcuBase, *dcuBase, size = 30000, len = 0; + u32 sampidx = 0; + u8 *buf; + u8 chainmask = (ah->rxchainmask << 3) | ah->rxchainmask; + u8 nread; + + if (test_bit(SC_OP_INVALID, &sc->sc_flags)) + return -EAGAIN; + + buf = vmalloc(size); + if (!buf) + return -ENOMEM; + bb_mac_samp = vmalloc(sizeof(*bb_mac_samp) * ATH_DBG_MAX_SAMPLES); + if (!bb_mac_samp) { + vfree(buf); + return -ENOMEM; + } + /* Account the current state too */ + ath9k_debug_samp_bb_mac(sc); + + spin_lock_bh(&sc->debug.samp_lock); + memcpy(bb_mac_samp, sc->debug.bb_mac_samp, + sizeof(*bb_mac_samp) * ATH_DBG_MAX_SAMPLES); + len += snprintf(buf + len, size - len, + "Current Sample Index: %d\n", sc->debug.sampidx); + spin_unlock_bh(&sc->debug.samp_lock); + + len += snprintf(buf + len, size - len, + "Raw DMA Debug Dump:\n"); + len += snprintf(buf + len, size - len, "Sample |\t"); + for (i = 0; i < ATH9K_NUM_DMA_DEBUG_REGS; i++) + len += snprintf(buf + len, size - len, " DMA Reg%d |\t", i); + len += snprintf(buf + len, size - len, "\n"); + + for (sampidx = 0; sampidx < ATH_DBG_MAX_SAMPLES; sampidx++) { + len += snprintf(buf + len, size - len, "%d\t", sampidx); + + for (i = 0; i < ATH9K_NUM_DMA_DEBUG_REGS; i++) + len += snprintf(buf + len, size - len, " %08x\t", + ATH_SAMP_DBG(dma_dbg_reg_vals[i])); + len += snprintf(buf + len, size - len, "\n"); + } + len += snprintf(buf + len, size - len, "\n"); + + len += snprintf(buf + len, size - len, + "Sample Num QCU: chain_st fsp_ok fsp_st DCU: chain_st\n"); + for (sampidx = 0; sampidx < ATH_DBG_MAX_SAMPLES; sampidx++) { + qcuBase = &ATH_SAMP_DBG(dma_dbg_reg_vals[0]); + dcuBase = &ATH_SAMP_DBG(dma_dbg_reg_vals[4]); + + for (i = 0; i < ATH9K_NUM_QUEUES; i++, + qcuOffset += 4, dcuOffset += 5) { + if (i == 8) { + qcuOffset = 0; + qcuBase++; + } + + if (i == 6) { + dcuOffset = 0; + dcuBase++; + } + if (!sc->debug.stats.txstats[i].queued) + continue; + + len += snprintf(buf + len, size - len, + "%4d %7d %2x %1x %2x %2x\n", + sampidx, i, + (*qcuBase & (0x7 << qcuOffset)) >> qcuOffset, + (*qcuBase & (0x8 << qcuOffset)) >> + (qcuOffset + 3), + ATH_SAMP_DBG(dma_dbg_reg_vals[2]) & + (0x7 << (i * 3)) >> (i * 3), + (*dcuBase & (0x1f << dcuOffset)) >> dcuOffset); + } + len += snprintf(buf + len, size - len, "\n"); + } + len += snprintf(buf + len, size - len, + "samp qcu_sh qcu_fh qcu_comp dcu_comp dcu_arb dcu_fp " + "ch_idle_dur ch_idle_dur_val txfifo_val0 txfifo_val1 " + "txfifo_dcu0 txfifo_dcu1 pcu_obs AR_CR\n"); + + for (sampidx = 0; sampidx < ATH_DBG_MAX_SAMPLES; sampidx++) { + qcuBase = &ATH_SAMP_DBG(dma_dbg_reg_vals[0]); + dcuBase = &ATH_SAMP_DBG(dma_dbg_reg_vals[4]); + + len += snprintf(buf + len, size - len, "%4d %5x %5x ", sampidx, + (ATH_SAMP_DBG(dma_dbg_reg_vals[3]) & 0x003c0000) >> 18, + (ATH_SAMP_DBG(dma_dbg_reg_vals[3]) & 0x03c00000) >> 22); + len += snprintf(buf + len, size - len, "%7x %8x ", + (ATH_SAMP_DBG(dma_dbg_reg_vals[3]) & 0x1c000000) >> 26, + (ATH_SAMP_DBG(dma_dbg_reg_vals[6]) & 0x3)); + len += snprintf(buf + len, size - len, "%7x %7x ", + (ATH_SAMP_DBG(dma_dbg_reg_vals[5]) & 0x06000000) >> 25, + (ATH_SAMP_DBG(dma_dbg_reg_vals[5]) & 0x38000000) >> 27); + len += snprintf(buf + len, size - len, "%7d %12d ", + (ATH_SAMP_DBG(dma_dbg_reg_vals[6]) & 0x000003fc) >> 2, + (ATH_SAMP_DBG(dma_dbg_reg_vals[6]) & 0x00000400) >> 10); + len += snprintf(buf + len, size - len, "%12d %12d ", + (ATH_SAMP_DBG(dma_dbg_reg_vals[6]) & 0x00000800) >> 11, + (ATH_SAMP_DBG(dma_dbg_reg_vals[6]) & 0x00001000) >> 12); + len += snprintf(buf + len, size - len, "%12d %12d ", + (ATH_SAMP_DBG(dma_dbg_reg_vals[6]) & 0x0001e000) >> 13, + (ATH_SAMP_DBG(dma_dbg_reg_vals[6]) & 0x001e0000) >> 17); + len += snprintf(buf + len, size - len, "0x%07x 0x%07x\n", + ATH_SAMP_DBG(pcu_obs), ATH_SAMP_DBG(pcu_cr)); + } + + len += snprintf(buf + len, size - len, + "Sample ChNoise Chain privNF #Reading Readings\n"); + for (sampidx = 0; sampidx < ATH_DBG_MAX_SAMPLES; sampidx++) { + h = ATH_SAMP_DBG(nfCalHist); + if (!ATH_SAMP_DBG(noise)) + continue; + + for (i = 0; i < NUM_NF_READINGS; i++) { + if (!(chainmask & (1 << i)) || + ((i >= AR5416_MAX_CHAINS) && !conf_is_ht40(conf))) + continue; + + nread = AR_PHY_CCA_FILTERWINDOW_LENGTH - + h[i].invalidNFcount; + len += snprintf(buf + len, size - len, + "%4d %5d %4d\t %d\t %d\t", + sampidx, ATH_SAMP_DBG(noise), + i, h[i].privNF, nread); + for (j = 0; j < nread; j++) + len += snprintf(buf + len, size - len, + " %d", h[i].nfCalBuffer[j]); + len += snprintf(buf + len, size - len, "\n"); + } + } + len += snprintf(buf + len, size - len, "\nCycle counters:\n" + "Sample Total Rxbusy Rxframes Txframes\n"); + for (sampidx = 0; sampidx < ATH_DBG_MAX_SAMPLES; sampidx++) { + if (!ATH_SAMP_DBG(cc.cycles)) + continue; + len += snprintf(buf + len, size - len, + "%4d %08x %08x %08x %08x\n", + sampidx, ATH_SAMP_DBG(cc.cycles), + ATH_SAMP_DBG(cc.rx_busy), + ATH_SAMP_DBG(cc.rx_frame), + ATH_SAMP_DBG(cc.tx_frame)); + } + + len += snprintf(buf + len, size - len, "Tx status Dump :\n"); + len += snprintf(buf + len, size - len, + "Sample rssi:- ctl0 ctl1 ctl2 ext0 ext1 ext2 comb " + "isok rts_fail data_fail rate tid qid " + "ba_low ba_high tx_before(ms)\n"); + for (sampidx = 0; sampidx < ATH_DBG_MAX_SAMPLES; sampidx++) { + for (i = 0; i < ATH_DBG_MAX_SAMPLES; i++) { + if (!ATH_SAMP_DBG(ts[i].jiffies)) + continue; + len += snprintf(buf + len, size - len, "%-14d" + "%-4d %-4d %-4d %-4d %-4d %-4d %-4d %-4d %-8d " + "%-9d %-4d %-3d %-3d %08x %08x %-11d\n", + sampidx, + ATH_SAMP_DBG(ts[i].rssi_ctl0), + ATH_SAMP_DBG(ts[i].rssi_ctl1), + ATH_SAMP_DBG(ts[i].rssi_ctl2), + ATH_SAMP_DBG(ts[i].rssi_ext0), + ATH_SAMP_DBG(ts[i].rssi_ext1), + ATH_SAMP_DBG(ts[i].rssi_ext2), + ATH_SAMP_DBG(ts[i].rssi), + ATH_SAMP_DBG(ts[i].isok), + ATH_SAMP_DBG(ts[i].rts_fail_cnt), + ATH_SAMP_DBG(ts[i].data_fail_cnt), + ATH_SAMP_DBG(ts[i].rateindex), + ATH_SAMP_DBG(ts[i].tid), + ATH_SAMP_DBG(ts[i].qid), + ATH_SAMP_DBG(ts[i].ba_low), + ATH_SAMP_DBG(ts[i].ba_high), + jiffies_to_msecs(jiffies - + ATH_SAMP_DBG(ts[i].jiffies))); + } + } + + len += snprintf(buf + len, size - len, "Rx status Dump :\n"); + len += snprintf(buf + len, size - len, "Sample rssi:- ctl0 ctl1 ctl2 " + "ext0 ext1 ext2 comb beacon ant rate rx_before(ms)\n"); + for (sampidx = 0; sampidx < ATH_DBG_MAX_SAMPLES; sampidx++) { + for (i = 0; i < ATH_DBG_MAX_SAMPLES; i++) { + if (!ATH_SAMP_DBG(rs[i].jiffies)) + continue; + len += snprintf(buf + len, size - len, "%-14d" + "%-4d %-4d %-4d %-4d %-4d %-4d %-4d %-9s %-2d %02x %-13d\n", + sampidx, + ATH_SAMP_DBG(rs[i].rssi_ctl0), + ATH_SAMP_DBG(rs[i].rssi_ctl1), + ATH_SAMP_DBG(rs[i].rssi_ctl2), + ATH_SAMP_DBG(rs[i].rssi_ext0), + ATH_SAMP_DBG(rs[i].rssi_ext1), + ATH_SAMP_DBG(rs[i].rssi_ext2), + ATH_SAMP_DBG(rs[i].rssi), + ATH_SAMP_DBG(rs[i].is_mybeacon) ? + "True" : "False", + ATH_SAMP_DBG(rs[i].antenna), + ATH_SAMP_DBG(rs[i].rate), + jiffies_to_msecs(jiffies - + ATH_SAMP_DBG(rs[i].jiffies))); + } + } + + vfree(bb_mac_samp); + file->private_data = buf; + + return 0; +#undef ATH_SAMP_DBG +} + +static const struct file_operations fops_samps = { + .open = open_file_bb_mac_samps, + .read = ath9k_debugfs_read_buf, + .release = ath9k_debugfs_release_buf, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +#endif + +#ifdef CPTCFG_ATH9K_BTCOEX_SUPPORT +static ssize_t read_file_btcoex(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + u32 len = 0, size = 1500; + char *buf; + size_t retval; + + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (!sc->sc_ah->common.btcoex_enabled) { + len = snprintf(buf, size, "%s\n", + "BTCOEX is disabled"); + goto exit; + } + + len = ath9k_dump_btcoex(sc, buf, size); +exit: + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return retval; +} + +static const struct file_operations fops_btcoex = { + .read = read_file_btcoex, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; +#endif + +static ssize_t read_file_node_stat(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_node *an = file->private_data; + struct ath_softc *sc = an->sc; + struct ath_atx_tid *tid; + struct ath_atx_ac *ac; + struct ath_txq *txq; + u32 len = 0, size = 4096; + char *buf; + size_t retval; + int tidno, acno; + + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (!an->sta->ht_cap.ht_supported) { + len = snprintf(buf, size, "%s\n", + "HT not supported"); + goto exit; + } + + len = snprintf(buf, size, "Max-AMPDU: %d\n", + an->maxampdu); + len += snprintf(buf + len, size - len, "MPDU Density: %d\n\n", + an->mpdudensity); + + len += snprintf(buf + len, size - len, + "%2s%7s\n", "AC", "SCHED"); + + for (acno = 0, ac = &an->ac[acno]; + acno < IEEE80211_NUM_ACS; acno++, ac++) { + txq = ac->txq; + ath_txq_lock(sc, txq); + len += snprintf(buf + len, size - len, + "%2d%7d\n", + acno, ac->sched); + ath_txq_unlock(sc, txq); + } + + len += snprintf(buf + len, size - len, + "\n%3s%11s%10s%10s%10s%10s%9s%6s%8s\n", + "TID", "SEQ_START", "SEQ_NEXT", "BAW_SIZE", + "BAW_HEAD", "BAW_TAIL", "BAR_IDX", "SCHED", "PAUSED"); + + for (tidno = 0, tid = &an->tid[tidno]; + tidno < IEEE80211_NUM_TIDS; tidno++, tid++) { + txq = tid->ac->txq; + ath_txq_lock(sc, txq); + len += snprintf(buf + len, size - len, + "%3d%11d%10d%10d%10d%10d%9d%6d%8d\n", + tid->tidno, tid->seq_start, tid->seq_next, + tid->baw_size, tid->baw_head, tid->baw_tail, + tid->bar_index, tid->sched, tid->paused); + ath_txq_unlock(sc, txq); + } +exit: + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return retval; +} + +static const struct file_operations fops_node_stat = { + .read = read_file_node_stat, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +void ath9k_sta_add_debugfs(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct dentry *dir) +{ + struct ath_node *an = (struct ath_node *)sta->drv_priv; + an->node_stat = debugfs_create_file("node_stat", S_IRUGO, + dir, an, &fops_node_stat); +} + +void ath9k_sta_remove_debugfs(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct dentry *dir) +{ + struct ath_node *an = (struct ath_node *)sta->drv_priv; + debugfs_remove(an->node_stat); +} + +/* Ethtool support for get-stats */ + +#define AMKSTR(nm) #nm "_BE", #nm "_BK", #nm "_VI", #nm "_VO" +static const char ath9k_gstrings_stats[][ETH_GSTRING_LEN] = { + "tx_pkts_nic", + "tx_bytes_nic", + "rx_pkts_nic", + "rx_bytes_nic", + AMKSTR(d_tx_pkts), + AMKSTR(d_tx_bytes), + AMKSTR(d_tx_mpdus_queued), + AMKSTR(d_tx_mpdus_completed), + AMKSTR(d_tx_mpdu_xretries), + AMKSTR(d_tx_aggregates), + AMKSTR(d_tx_ampdus_queued_hw), + AMKSTR(d_tx_ampdus_queued_sw), + AMKSTR(d_tx_ampdus_completed), + AMKSTR(d_tx_ampdu_retries), + AMKSTR(d_tx_ampdu_xretries), + AMKSTR(d_tx_fifo_underrun), + AMKSTR(d_tx_op_exceeded), + AMKSTR(d_tx_timer_expiry), + AMKSTR(d_tx_desc_cfg_err), + AMKSTR(d_tx_data_underrun), + AMKSTR(d_tx_delim_underrun), + "d_rx_crc_err", + "d_rx_decrypt_crc_err", + "d_rx_phy_err", + "d_rx_mic_err", + "d_rx_pre_delim_crc_err", + "d_rx_post_delim_crc_err", + "d_rx_decrypt_busy_err", + + "d_rx_phyerr_radar", + "d_rx_phyerr_ofdm_timing", + "d_rx_phyerr_cck_timing", + +}; +#define ATH9K_SSTATS_LEN ARRAY_SIZE(ath9k_gstrings_stats) + +void ath9k_get_et_strings(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u32 sset, u8 *data) +{ + if (sset == ETH_SS_STATS) + memcpy(data, *ath9k_gstrings_stats, + sizeof(ath9k_gstrings_stats)); +} + +int ath9k_get_et_sset_count(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, int sset) +{ + if (sset == ETH_SS_STATS) + return ATH9K_SSTATS_LEN; + return 0; +} + +#define AWDATA(elem) \ + do { \ + data[i++] = sc->debug.stats.txstats[PR_QNUM(IEEE80211_AC_BE)].elem; \ + data[i++] = sc->debug.stats.txstats[PR_QNUM(IEEE80211_AC_BK)].elem; \ + data[i++] = sc->debug.stats.txstats[PR_QNUM(IEEE80211_AC_VI)].elem; \ + data[i++] = sc->debug.stats.txstats[PR_QNUM(IEEE80211_AC_VO)].elem; \ + } while (0) + +#define AWDATA_RX(elem) \ + do { \ + data[i++] = sc->debug.stats.rxstats.elem; \ + } while (0) + +void ath9k_get_et_stats(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ethtool_stats *stats, u64 *data) +{ + struct ath_softc *sc = hw->priv; + int i = 0; + + data[i++] = (sc->debug.stats.txstats[PR_QNUM(IEEE80211_AC_BE)].tx_pkts_all + + sc->debug.stats.txstats[PR_QNUM(IEEE80211_AC_BK)].tx_pkts_all + + sc->debug.stats.txstats[PR_QNUM(IEEE80211_AC_VI)].tx_pkts_all + + sc->debug.stats.txstats[PR_QNUM(IEEE80211_AC_VO)].tx_pkts_all); + data[i++] = (sc->debug.stats.txstats[PR_QNUM(IEEE80211_AC_BE)].tx_bytes_all + + sc->debug.stats.txstats[PR_QNUM(IEEE80211_AC_BK)].tx_bytes_all + + sc->debug.stats.txstats[PR_QNUM(IEEE80211_AC_VI)].tx_bytes_all + + sc->debug.stats.txstats[PR_QNUM(IEEE80211_AC_VO)].tx_bytes_all); + AWDATA_RX(rx_pkts_all); + AWDATA_RX(rx_bytes_all); + + AWDATA(tx_pkts_all); + AWDATA(tx_bytes_all); + AWDATA(queued); + AWDATA(completed); + AWDATA(xretries); + AWDATA(a_aggr); + AWDATA(a_queued_hw); + AWDATA(a_queued_sw); + AWDATA(a_completed); + AWDATA(a_retries); + AWDATA(a_xretries); + AWDATA(fifo_underrun); + AWDATA(xtxop); + AWDATA(timer_exp); + AWDATA(desc_cfg_err); + AWDATA(data_underrun); + AWDATA(delim_underrun); + + AWDATA_RX(crc_err); + AWDATA_RX(decrypt_crc_err); + AWDATA_RX(phy_err); + AWDATA_RX(mic_err); + AWDATA_RX(pre_delim_crc_err); + AWDATA_RX(post_delim_crc_err); + AWDATA_RX(decrypt_busy_err); + + AWDATA_RX(phy_err_stats[ATH9K_PHYERR_RADAR]); + AWDATA_RX(phy_err_stats[ATH9K_PHYERR_OFDM_TIMING]); + AWDATA_RX(phy_err_stats[ATH9K_PHYERR_CCK_TIMING]); + + WARN_ON(i != ATH9K_SSTATS_LEN); +} + +void ath9k_deinit_debug(struct ath_softc *sc) +{ + if (config_enabled(CPTCFG_ATH9K_DEBUGFS) && sc->rfs_chan_spec_scan) { + relay_close(sc->rfs_chan_spec_scan); + sc->rfs_chan_spec_scan = NULL; + } +} + +int ath9k_init_debug(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath_softc *sc = (struct ath_softc *) common->priv; + + sc->debug.debugfs_phy = debugfs_create_dir("ath9k", + sc->hw->wiphy->debugfsdir); + if (!sc->debug.debugfs_phy) + return -ENOMEM; + +#ifdef CPTCFG_ATH_DEBUG + debugfs_create_file("debug", S_IRUSR | S_IWUSR, sc->debug.debugfs_phy, + sc, &fops_debug); +#endif + + ath9k_dfs_init_debug(sc); + + debugfs_create_file("dma", S_IRUSR, sc->debug.debugfs_phy, sc, + &fops_dma); + debugfs_create_file("interrupt", S_IRUSR, sc->debug.debugfs_phy, sc, + &fops_interrupt); + debugfs_create_file("xmit", S_IRUSR, sc->debug.debugfs_phy, sc, + &fops_xmit); + debugfs_create_file("queues", S_IRUSR, sc->debug.debugfs_phy, sc, + &fops_queues); + debugfs_create_u32("qlen_bk", S_IRUSR | S_IWUSR, sc->debug.debugfs_phy, + &sc->tx.txq_max_pending[IEEE80211_AC_BK]); + debugfs_create_u32("qlen_be", S_IRUSR | S_IWUSR, sc->debug.debugfs_phy, + &sc->tx.txq_max_pending[IEEE80211_AC_BE]); + debugfs_create_u32("qlen_vi", S_IRUSR | S_IWUSR, sc->debug.debugfs_phy, + &sc->tx.txq_max_pending[IEEE80211_AC_VI]); + debugfs_create_u32("qlen_vo", S_IRUSR | S_IWUSR, sc->debug.debugfs_phy, + &sc->tx.txq_max_pending[IEEE80211_AC_VO]); + debugfs_create_file("misc", S_IRUSR, sc->debug.debugfs_phy, sc, + &fops_misc); + debugfs_create_file("reset", S_IRUSR, sc->debug.debugfs_phy, sc, + &fops_reset); + debugfs_create_file("recv", S_IRUSR, sc->debug.debugfs_phy, sc, + &fops_recv); + debugfs_create_file("rx_chainmask", S_IRUSR | S_IWUSR, + sc->debug.debugfs_phy, sc, &fops_rx_chainmask); + debugfs_create_file("tx_chainmask", S_IRUSR | S_IWUSR, + sc->debug.debugfs_phy, sc, &fops_tx_chainmask); + debugfs_create_file("disable_ani", S_IRUSR | S_IWUSR, + sc->debug.debugfs_phy, sc, &fops_disable_ani); + debugfs_create_bool("paprd", S_IRUSR | S_IWUSR, sc->debug.debugfs_phy, + &sc->sc_ah->config.enable_paprd); + debugfs_create_file("regidx", S_IRUSR | S_IWUSR, sc->debug.debugfs_phy, + sc, &fops_regidx); + debugfs_create_file("regval", S_IRUSR | S_IWUSR, sc->debug.debugfs_phy, + sc, &fops_regval); + debugfs_create_bool("ignore_extcca", S_IRUSR | S_IWUSR, + sc->debug.debugfs_phy, + &ah->config.cwm_ignore_extcca); + debugfs_create_file("regdump", S_IRUSR, sc->debug.debugfs_phy, sc, + &fops_regdump); + debugfs_create_file("dump_nfcal", S_IRUSR, sc->debug.debugfs_phy, sc, + &fops_dump_nfcal); + debugfs_create_file("base_eeprom", S_IRUSR, sc->debug.debugfs_phy, sc, + &fops_base_eeprom); + debugfs_create_file("modal_eeprom", S_IRUSR, sc->debug.debugfs_phy, sc, + &fops_modal_eeprom); + sc->rfs_chan_spec_scan = relay_open("spectral_scan", + sc->debug.debugfs_phy, + 1024, 256, &rfs_spec_scan_cb, + NULL); + debugfs_create_file("spectral_scan_ctl", S_IRUSR | S_IWUSR, + sc->debug.debugfs_phy, sc, + &fops_spec_scan_ctl); + debugfs_create_file("spectral_short_repeat", S_IRUSR | S_IWUSR, + sc->debug.debugfs_phy, sc, + &fops_spectral_short_repeat); + debugfs_create_file("spectral_count", S_IRUSR | S_IWUSR, + sc->debug.debugfs_phy, sc, &fops_spectral_count); + debugfs_create_file("spectral_period", S_IRUSR | S_IWUSR, + sc->debug.debugfs_phy, sc, &fops_spectral_period); + debugfs_create_file("spectral_fft_period", S_IRUSR | S_IWUSR, + sc->debug.debugfs_phy, sc, + &fops_spectral_fft_period); + +#ifdef CPTCFG_ATH9K_MAC_DEBUG + debugfs_create_file("samples", S_IRUSR, sc->debug.debugfs_phy, sc, + &fops_samps); +#endif + debugfs_create_u32("gpio_mask", S_IRUSR | S_IWUSR, + sc->debug.debugfs_phy, &sc->sc_ah->gpio_mask); + debugfs_create_u32("gpio_val", S_IRUSR | S_IWUSR, + sc->debug.debugfs_phy, &sc->sc_ah->gpio_val); + debugfs_create_file("diversity", S_IRUSR | S_IWUSR, + sc->debug.debugfs_phy, sc, &fops_ant_diversity); +#ifdef CPTCFG_ATH9K_BTCOEX_SUPPORT + debugfs_create_file("btcoex", S_IRUSR, sc->debug.debugfs_phy, sc, + &fops_btcoex); +#endif + return 0; +} diff --git a/drivers/net/wireless/ath/ath9k/debug.h b/drivers/net/wireless/ath/ath9k/debug.h new file mode 100644 index 0000000..d071cd6 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/debug.h @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DEBUG_H +#define DEBUG_H + +#include "hw.h" +#include "rc.h" +#include "dfs_debug.h" + +struct ath_txq; +struct ath_buf; +struct fft_sample_tlv; + +#ifdef CPTCFG_ATH9K_DEBUGFS +#define TX_STAT_INC(q, c) sc->debug.stats.txstats[q].c++ +#define RESET_STAT_INC(sc, type) sc->debug.stats.reset[type]++ +#else +#define TX_STAT_INC(q, c) do { } while (0) +#define RESET_STAT_INC(sc, type) do { } while (0) +#endif + +enum ath_reset_type { + RESET_TYPE_BB_HANG, + RESET_TYPE_BB_WATCHDOG, + RESET_TYPE_FATAL_INT, + RESET_TYPE_TX_ERROR, + RESET_TYPE_TX_HANG, + RESET_TYPE_PLL_HANG, + RESET_TYPE_MAC_HANG, + RESET_TYPE_BEACON_STUCK, + RESET_TYPE_MCI, + __RESET_TYPE_MAX +}; + +#ifdef CPTCFG_ATH9K_DEBUGFS + +/** + * struct ath_interrupt_stats - Contains statistics about interrupts + * @total: Total no. of interrupts generated so far + * @rxok: RX with no errors + * @rxlp: RX with low priority RX + * @rxhp: RX with high priority, uapsd only + * @rxeol: RX with no more RXDESC available + * @rxorn: RX FIFO overrun + * @txok: TX completed at the requested rate + * @txurn: TX FIFO underrun + * @mib: MIB regs reaching its threshold + * @rxphyerr: RX with phy errors + * @rx_keycache_miss: RX with key cache misses + * @swba: Software Beacon Alert + * @bmiss: Beacon Miss + * @bnr: Beacon Not Ready + * @cst: Carrier Sense TImeout + * @gtt: Global TX Timeout + * @tim: RX beacon TIM occurrence + * @cabend: RX End of CAB traffic + * @dtimsync: DTIM sync lossage + * @dtim: RX Beacon with DTIM + * @bb_watchdog: Baseband watchdog + * @tsfoor: TSF out of range, indicates that the corrected TSF received + * from a beacon differs from the PCU's internal TSF by more than a + * (programmable) threshold + * @local_timeout: Internal bus timeout. + * @mci: MCI interrupt, specific to MCI based BTCOEX chipsets + * @gen_timer: Generic hardware timer interrupt + */ +struct ath_interrupt_stats { + u32 total; + u32 rxok; + u32 rxlp; + u32 rxhp; + u32 rxeol; + u32 rxorn; + u32 txok; + u32 txeol; + u32 txurn; + u32 mib; + u32 rxphyerr; + u32 rx_keycache_miss; + u32 swba; + u32 bmiss; + u32 bnr; + u32 cst; + u32 gtt; + u32 tim; + u32 cabend; + u32 dtimsync; + u32 dtim; + u32 bb_watchdog; + u32 tsfoor; + u32 mci; + u32 gen_timer; + + /* Sync-cause stats */ + u32 sync_cause_all; + u32 sync_rtc_irq; + u32 sync_mac_irq; + u32 eeprom_illegal_access; + u32 apb_timeout; + u32 pci_mode_conflict; + u32 host1_fatal; + u32 host1_perr; + u32 trcv_fifo_perr; + u32 radm_cpl_ep; + u32 radm_cpl_dllp_abort; + u32 radm_cpl_tlp_abort; + u32 radm_cpl_ecrc_err; + u32 radm_cpl_timeout; + u32 local_timeout; + u32 pm_access; + u32 mac_awake; + u32 mac_asleep; + u32 mac_sleep_access; +}; + + +/** + * struct ath_tx_stats - Statistics about TX + * @tx_pkts_all: No. of total frames transmitted, including ones that + may have had errors. + * @tx_bytes_all: No. of total bytes transmitted, including ones that + may have had errors. + * @queued: Total MPDUs (non-aggr) queued + * @completed: Total MPDUs (non-aggr) completed + * @a_aggr: Total no. of aggregates queued + * @a_queued_hw: Total AMPDUs queued to hardware + * @a_queued_sw: Total AMPDUs queued to software queues + * @a_completed: Total AMPDUs completed + * @a_retries: No. of AMPDUs retried (SW) + * @a_xretries: No. of AMPDUs dropped due to xretries + * @txerr_filtered: No. of frames with TXERR_FILT flag set. + * @fifo_underrun: FIFO underrun occurrences + Valid only for: + - non-aggregate condition. + - first packet of aggregate. + * @xtxop: No. of frames filtered because of TXOP limit + * @timer_exp: Transmit timer expiry + * @desc_cfg_err: Descriptor configuration errors + * @data_urn: TX data underrun errors + * @delim_urn: TX delimiter underrun errors + * @puttxbuf: Number of times hardware was given txbuf to write. + * @txstart: Number of times hardware was told to start tx. + * @txprocdesc: Number of times tx descriptor was processed + * @txfailed: Out-of-memory or other errors in xmit path. + */ +struct ath_tx_stats { + u32 tx_pkts_all; + u32 tx_bytes_all; + u32 queued; + u32 completed; + u32 xretries; + u32 a_aggr; + u32 a_queued_hw; + u32 a_queued_sw; + u32 a_completed; + u32 a_retries; + u32 a_xretries; + u32 txerr_filtered; + u32 fifo_underrun; + u32 xtxop; + u32 timer_exp; + u32 desc_cfg_err; + u32 data_underrun; + u32 delim_underrun; + u32 puttxbuf; + u32 txstart; + u32 txprocdesc; + u32 txfailed; +}; + +/* + * Various utility macros to print TX/Queue counters. + */ +#define PR_QNUM(_n) sc->tx.txq_map[_n]->axq_qnum +#define TXSTATS sc->debug.stats.txstats +#define PR(str, elem) \ + do { \ + len += snprintf(buf + len, size - len, \ + "%s%13u%11u%10u%10u\n", str, \ + TXSTATS[PR_QNUM(IEEE80211_AC_BE)].elem, \ + TXSTATS[PR_QNUM(IEEE80211_AC_BK)].elem, \ + TXSTATS[PR_QNUM(IEEE80211_AC_VI)].elem, \ + TXSTATS[PR_QNUM(IEEE80211_AC_VO)].elem); \ + } while(0) + +#define RX_STAT_INC(c) (sc->debug.stats.rxstats.c++) + +/** + * struct ath_rx_stats - RX Statistics + * @rx_pkts_all: No. of total frames received, including ones that + may have had errors. + * @rx_bytes_all: No. of total bytes received, including ones that + may have had errors. + * @crc_err: No. of frames with incorrect CRC value + * @decrypt_crc_err: No. of frames whose CRC check failed after + decryption process completed + * @phy_err: No. of frames whose reception failed because the PHY + encountered an error + * @mic_err: No. of frames with incorrect TKIP MIC verification failure + * @pre_delim_crc_err: Pre-Frame delimiter CRC error detections + * @post_delim_crc_err: Post-Frame delimiter CRC error detections + * @decrypt_busy_err: Decryption interruptions counter + * @phy_err_stats: Individual PHY error statistics + * @rx_len_err: No. of frames discarded due to bad length. + * @rx_oom_err: No. of frames dropped due to OOM issues. + * @rx_rate_err: No. of frames dropped due to rate errors. + * @rx_too_many_frags_err: Frames dropped due to too-many-frags received. + * @rx_beacons: No. of beacons received. + * @rx_frags: No. of rx-fragements received. + * @rx_spectral: No of spectral packets received. + */ +struct ath_rx_stats { + u32 rx_pkts_all; + u32 rx_bytes_all; + u32 crc_err; + u32 decrypt_crc_err; + u32 phy_err; + u32 mic_err; + u32 pre_delim_crc_err; + u32 post_delim_crc_err; + u32 decrypt_busy_err; + u32 phy_err_stats[ATH9K_PHYERR_MAX]; + u32 rx_len_err; + u32 rx_oom_err; + u32 rx_rate_err; + u32 rx_too_many_frags_err; + u32 rx_beacons; + u32 rx_frags; + u32 rx_spectral; +}; + +struct ath_stats { + struct ath_interrupt_stats istats; + struct ath_tx_stats txstats[ATH9K_NUM_TX_QUEUES]; + struct ath_rx_stats rxstats; + struct ath_dfs_stats dfs_stats; + u32 reset[__RESET_TYPE_MAX]; +}; + +#define ATH_DBG_MAX_SAMPLES 10 +struct ath_dbg_bb_mac_samp { + u32 dma_dbg_reg_vals[ATH9K_NUM_DMA_DEBUG_REGS]; + u32 pcu_obs, pcu_cr, noise; + struct { + u64 jiffies; + int8_t rssi_ctl0; + int8_t rssi_ctl1; + int8_t rssi_ctl2; + int8_t rssi_ext0; + int8_t rssi_ext1; + int8_t rssi_ext2; + int8_t rssi; + bool isok; + u8 rts_fail_cnt; + u8 data_fail_cnt; + u8 rateindex; + u8 qid; + u8 tid; + u32 ba_low; + u32 ba_high; + } ts[ATH_DBG_MAX_SAMPLES]; + struct { + u64 jiffies; + int8_t rssi_ctl0; + int8_t rssi_ctl1; + int8_t rssi_ctl2; + int8_t rssi_ext0; + int8_t rssi_ext1; + int8_t rssi_ext2; + int8_t rssi; + bool is_mybeacon; + u8 antenna; + u8 rate; + } rs[ATH_DBG_MAX_SAMPLES]; + struct ath_cycle_counters cc; + struct ath9k_nfcal_hist nfCalHist[NUM_NF_READINGS]; +}; + +struct ath9k_debug { + struct dentry *debugfs_phy; + u32 regidx; + struct ath_stats stats; +#ifdef CPTCFG_ATH9K_MAC_DEBUG + spinlock_t samp_lock; + struct ath_dbg_bb_mac_samp bb_mac_samp[ATH_DBG_MAX_SAMPLES]; + u8 sampidx; + u8 tsidx; + u8 rsidx; +#endif +}; + +int ath9k_init_debug(struct ath_hw *ah); +void ath9k_deinit_debug(struct ath_softc *sc); + +void ath_debug_stat_interrupt(struct ath_softc *sc, enum ath9k_int status); +void ath_debug_stat_tx(struct ath_softc *sc, struct ath_buf *bf, + struct ath_tx_status *ts, struct ath_txq *txq, + unsigned int flags); +void ath_debug_stat_rx(struct ath_softc *sc, struct ath_rx_status *rs); +int ath9k_get_et_sset_count(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, int sset); +void ath9k_get_et_stats(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ethtool_stats *stats, u64 *data); +void ath9k_get_et_strings(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u32 sset, u8 *data); +void ath9k_sta_add_debugfs(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct dentry *dir); +void ath9k_sta_remove_debugfs(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct dentry *dir); + +void ath_debug_send_fft_sample(struct ath_softc *sc, + struct fft_sample_tlv *fft_sample); + +#else + +#define RX_STAT_INC(c) /* NOP */ + +static inline int ath9k_init_debug(struct ath_hw *ah) +{ + return 0; +} + +static inline void ath9k_deinit_debug(struct ath_softc *sc) +{ +} + +static inline void ath_debug_stat_interrupt(struct ath_softc *sc, + enum ath9k_int status) +{ +} + +static inline void ath_debug_stat_tx(struct ath_softc *sc, + struct ath_buf *bf, + struct ath_tx_status *ts, + struct ath_txq *txq, + unsigned int flags) +{ +} + +static inline void ath_debug_stat_rx(struct ath_softc *sc, + struct ath_rx_status *rs) +{ +} + +#endif /* CPTCFG_ATH9K_DEBUGFS */ + +#ifdef CPTCFG_ATH9K_MAC_DEBUG + +void ath9k_debug_samp_bb_mac(struct ath_softc *sc); + +#else + +static inline void ath9k_debug_samp_bb_mac(struct ath_softc *sc) +{ +} + +#endif + + +#endif /* DEBUG_H */ diff --git a/drivers/net/wireless/ath/ath9k/dfs.c b/drivers/net/wireless/ath/ath9k/dfs.c new file mode 100644 index 0000000..7187d36 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/dfs.c @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * Copyright (c) 2011 Neratec Solutions AG + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "hw.h" +#include "hw-ops.h" +#include "ath9k.h" +#include "dfs.h" +#include "dfs_debug.h" + +/* internal struct to pass radar data */ +struct ath_radar_data { + u8 pulse_bw_info; + u8 rssi; + u8 ext_rssi; + u8 pulse_length_ext; + u8 pulse_length_pri; +}; + +/* convert pulse duration to usecs, considering clock mode */ +static u32 dur_to_usecs(struct ath_hw *ah, u32 dur) +{ + const u32 AR93X_NSECS_PER_DUR = 800; + const u32 AR93X_NSECS_PER_DUR_FAST = (8000 / 11); + u32 nsecs; + + if (IS_CHAN_A_FAST_CLOCK(ah, ah->curchan)) + nsecs = dur * AR93X_NSECS_PER_DUR_FAST; + else + nsecs = dur * AR93X_NSECS_PER_DUR; + + return (nsecs + 500) / 1000; +} + +#define PRI_CH_RADAR_FOUND 0x01 +#define EXT_CH_RADAR_FOUND 0x02 +static bool +ath9k_postprocess_radar_event(struct ath_softc *sc, + struct ath_radar_data *ard, + struct pulse_event *pe) +{ + u8 rssi; + u16 dur; + + /* + * Only the last 2 bits of the BW info are relevant, they indicate + * which channel the radar was detected in. + */ + ard->pulse_bw_info &= 0x03; + + switch (ard->pulse_bw_info) { + case PRI_CH_RADAR_FOUND: + /* radar in ctrl channel */ + dur = ard->pulse_length_pri; + DFS_STAT_INC(sc, pri_phy_errors); + /* + * cannot use ctrl channel RSSI + * if extension channel is stronger + */ + rssi = (ard->ext_rssi >= (ard->rssi + 3)) ? 0 : ard->rssi; + break; + case EXT_CH_RADAR_FOUND: + /* radar in extension channel */ + dur = ard->pulse_length_ext; + DFS_STAT_INC(sc, ext_phy_errors); + /* + * cannot use extension channel RSSI + * if control channel is stronger + */ + rssi = (ard->rssi >= (ard->ext_rssi + 12)) ? 0 : ard->ext_rssi; + break; + case (PRI_CH_RADAR_FOUND | EXT_CH_RADAR_FOUND): + /* + * Conducted testing, when pulse is on DC, both pri and ext + * durations are reported to be same + * + * Radiated testing, when pulse is on DC, different pri and + * ext durations are reported, so take the larger of the two + */ + if (ard->pulse_length_ext >= ard->pulse_length_pri) + dur = ard->pulse_length_ext; + else + dur = ard->pulse_length_pri; + DFS_STAT_INC(sc, dc_phy_errors); + + /* when both are present use stronger one */ + rssi = (ard->rssi < ard->ext_rssi) ? ard->ext_rssi : ard->rssi; + break; + default: + /* + * Bogus bandwidth info was received in descriptor, + * so ignore this PHY error + */ + DFS_STAT_INC(sc, bwinfo_discards); + return false; + } + + if (rssi == 0) { + DFS_STAT_INC(sc, rssi_discards); + return false; + } + + /* + * TODO: check chirping pulses + * checks for chirping are dependent on the DFS regulatory domain + * used, which is yet TBD + */ + + /* convert duration to usecs */ + pe->width = dur_to_usecs(sc->sc_ah, dur); + pe->rssi = rssi; + + DFS_STAT_INC(sc, pulses_detected); + return true; +} +#undef PRI_CH_RADAR_FOUND +#undef EXT_CH_RADAR_FOUND + +/* + * DFS: check PHY-error for radar pulse and feed the detector + */ +void ath9k_dfs_process_phyerr(struct ath_softc *sc, void *data, + struct ath_rx_status *rs, u64 mactime) +{ + struct ath_radar_data ard; + u16 datalen; + char *vdata_end; + struct pulse_event pe; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + + DFS_STAT_INC(sc, pulses_total); + if ((rs->rs_phyerr != ATH9K_PHYERR_RADAR) && + (rs->rs_phyerr != ATH9K_PHYERR_FALSE_RADAR_EXT)) { + ath_dbg(common, DFS, + "Error: rs_phyer=0x%x not a radar error\n", + rs->rs_phyerr); + DFS_STAT_INC(sc, pulses_no_dfs); + return; + } + + datalen = rs->rs_datalen; + if (datalen == 0) { + DFS_STAT_INC(sc, datalen_discards); + return; + } + + ard.rssi = rs->rs_rssi_ctl0; + ard.ext_rssi = rs->rs_rssi_ext0; + + /* + * hardware stores this as 8 bit signed value. + * we will cap it at 0 if it is a negative number + */ + if (ard.rssi & 0x80) + ard.rssi = 0; + if (ard.ext_rssi & 0x80) + ard.ext_rssi = 0; + + vdata_end = (char *)data + datalen; + ard.pulse_bw_info = vdata_end[-1]; + ard.pulse_length_ext = vdata_end[-2]; + ard.pulse_length_pri = vdata_end[-3]; + pe.freq = ah->curchan->channel; + pe.ts = mactime; + if (ath9k_postprocess_radar_event(sc, &ard, &pe)) { + struct dfs_pattern_detector *pd = sc->dfs_detector; + static u64 last_ts; + ath_dbg(common, DFS, + "ath9k_dfs_process_phyerr: channel=%d, ts=%llu, " + "width=%d, rssi=%d, delta_ts=%llu\n", + pe.freq, pe.ts, pe.width, pe.rssi, pe.ts-last_ts); + last_ts = pe.ts; + DFS_STAT_INC(sc, pulses_processed); + if (pd != NULL && pd->add_pulse(pd, &pe)) { + DFS_STAT_INC(sc, radar_detected); + ieee80211_radar_detected(sc->hw); + } + } +} diff --git a/drivers/net/wireless/ath/ath9k/dfs.h b/drivers/net/wireless/ath/ath9k/dfs.h new file mode 100644 index 0000000..315b2bb --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/dfs.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * Copyright (c) 2011 Neratec Solutions AG + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef ATH9K_DFS_H +#define ATH9K_DFS_H +#include "dfs_pattern_detector.h" + +#if defined(CPTCFG_ATH9K_DFS_CERTIFIED) +/** + * ath9k_dfs_process_phyerr - process radar PHY error + * @sc: ath_softc + * @data: RX payload data + * @rs: RX status after processing descriptor + * @mactime: receive time + * + * This function is called whenever the HW DFS module detects a radar + * pulse and reports it as a PHY error. + * + * The radar information provided as raw payload data is validated and + * filtered for false pulses. Events passing all tests are forwarded to + * the DFS detector for pattern detection. + */ +void ath9k_dfs_process_phyerr(struct ath_softc *sc, void *data, + struct ath_rx_status *rs, u64 mactime); +#else +static inline void +ath9k_dfs_process_phyerr(struct ath_softc *sc, void *data, + struct ath_rx_status *rs, u64 mactime) { } +#endif + +#endif /* ATH9K_DFS_H */ diff --git a/drivers/net/wireless/ath/ath9k/dfs_debug.c b/drivers/net/wireless/ath/ath9k/dfs_debug.c new file mode 100644 index 0000000..b7611b7 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/dfs_debug.c @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * Copyright (c) 2011 Neratec Solutions AG + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "ath9k.h" +#include "dfs_debug.h" + + +struct ath_dfs_pool_stats global_dfs_pool_stats = { 0 }; + +#define ATH9K_DFS_STAT(s, p) \ + len += snprintf(buf + len, size - len, "%28s : %10u\n", s, \ + sc->debug.stats.dfs_stats.p); +#define ATH9K_DFS_POOL_STAT(s, p) \ + len += snprintf(buf + len, size - len, "%28s : %10u\n", s, \ + global_dfs_pool_stats.p); + +static ssize_t read_file_dfs(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + struct ath9k_hw_version *hw_ver = &sc->sc_ah->hw_version; + char *buf; + unsigned int len = 0, size = 8000; + ssize_t retval = 0; + + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + len += snprintf(buf + len, size - len, "DFS support for " + "macVersion = 0x%x, macRev = 0x%x: %s\n", + hw_ver->macVersion, hw_ver->macRev, + (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_DFS) ? + "enabled" : "disabled"); + len += snprintf(buf + len, size - len, "Pulse detector statistics:\n"); + ATH9K_DFS_STAT("pulse events reported ", pulses_total); + ATH9K_DFS_STAT("invalid pulse events ", pulses_no_dfs); + ATH9K_DFS_STAT("DFS pulses detected ", pulses_detected); + ATH9K_DFS_STAT("Datalen discards ", datalen_discards); + ATH9K_DFS_STAT("RSSI discards ", rssi_discards); + ATH9K_DFS_STAT("BW info discards ", bwinfo_discards); + ATH9K_DFS_STAT("Primary channel pulses ", pri_phy_errors); + ATH9K_DFS_STAT("Secondary channel pulses", ext_phy_errors); + ATH9K_DFS_STAT("Dual channel pulses ", dc_phy_errors); + len += snprintf(buf + len, size - len, "Radar detector statistics " + "(current DFS region: %d)\n", sc->dfs_detector->region); + ATH9K_DFS_STAT("Pulse events processed ", pulses_processed); + ATH9K_DFS_STAT("Radars detected ", radar_detected); + len += snprintf(buf + len, size - len, "Global Pool statistics:\n"); + ATH9K_DFS_POOL_STAT("Pool references ", pool_reference); + ATH9K_DFS_POOL_STAT("Pulses allocated ", pulse_allocated); + ATH9K_DFS_POOL_STAT("Pulses alloc error ", pulse_alloc_error); + ATH9K_DFS_POOL_STAT("Pulses in use ", pulse_used); + ATH9K_DFS_POOL_STAT("Seqs. allocated ", pseq_allocated); + ATH9K_DFS_POOL_STAT("Seqs. alloc error ", pseq_alloc_error); + ATH9K_DFS_POOL_STAT("Seqs. in use ", pseq_used); + + if (len > size) + len = size; + + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return retval; +} + +/* magic number to prevent accidental reset of DFS statistics */ +#define DFS_STATS_RESET_MAGIC 0x80000000 +static ssize_t write_file_dfs(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + unsigned long val; + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if (val == DFS_STATS_RESET_MAGIC) + memset(&sc->debug.stats.dfs_stats, 0, + sizeof(sc->debug.stats.dfs_stats)); + return count; +} + +static ssize_t write_file_simulate_radar(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_softc *sc = file->private_data; + + ieee80211_radar_detected(sc->hw); + + return count; +} + +static const struct file_operations fops_simulate_radar = { + .write = write_file_simulate_radar, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static const struct file_operations fops_dfs_stats = { + .read = read_file_dfs, + .write = write_file_dfs, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +void ath9k_dfs_init_debug(struct ath_softc *sc) +{ + debugfs_create_file("dfs_stats", S_IRUSR, + sc->debug.debugfs_phy, sc, &fops_dfs_stats); + debugfs_create_file("dfs_simulate_radar", S_IWUSR, + sc->debug.debugfs_phy, sc, &fops_simulate_radar); +} diff --git a/drivers/net/wireless/ath/ath9k/dfs_debug.h b/drivers/net/wireless/ath/ath9k/dfs_debug.h new file mode 100644 index 0000000..b311a6a --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/dfs_debug.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * Copyright (c) 2011 Neratec Solutions AG + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +#ifndef ATH9K_DFS_DEBUG_H +#define ATH9K_DFS_DEBUG_H + +#include "hw.h" + +/** + * struct ath_dfs_stats - DFS Statistics per wiphy + * @pulses_total: pulses reported by HW + * @pulses_no_dfs: pulses wrongly reported as DFS + * @pulses_detected: pulses detected so far + * @datalen_discards: pulses discarded due to invalid datalen + * @rssi_discards: pulses discarded due to invalid RSSI + * @bwinfo_discards: pulses discarded due to invalid BW info + * @pri_phy_errors: pulses reported for primary channel + * @ext_phy_errors: pulses reported for extension channel + * @dc_phy_errors: pulses reported for primary + extension channel + * @pulses_processed: pulses forwarded to detector + * @radar_detected: radars detected + */ +struct ath_dfs_stats { + /* pulse stats */ + u32 pulses_total; + u32 pulses_no_dfs; + u32 pulses_detected; + u32 datalen_discards; + u32 rssi_discards; + u32 bwinfo_discards; + u32 pri_phy_errors; + u32 ext_phy_errors; + u32 dc_phy_errors; + /* pattern detection stats */ + u32 pulses_processed; + u32 radar_detected; +}; + +/** + * struct ath_dfs_pool_stats - DFS Statistics for global pools + */ +struct ath_dfs_pool_stats { + u32 pool_reference; + u32 pulse_allocated; + u32 pulse_alloc_error; + u32 pulse_used; + u32 pseq_allocated; + u32 pseq_alloc_error; + u32 pseq_used; +}; +#if defined(CPTCFG_ATH9K_DFS_DEBUGFS) + +#define DFS_STAT_INC(sc, c) (sc->debug.stats.dfs_stats.c++) +void ath9k_dfs_init_debug(struct ath_softc *sc); + +#define DFS_POOL_STAT_INC(c) (global_dfs_pool_stats.c++) +#define DFS_POOL_STAT_DEC(c) (global_dfs_pool_stats.c--) +extern struct ath_dfs_pool_stats global_dfs_pool_stats; + +#else + +#define DFS_STAT_INC(sc, c) do { } while (0) +static inline void ath9k_dfs_init_debug(struct ath_softc *sc) { } + +#define DFS_POOL_STAT_INC(c) do { } while (0) +#define DFS_POOL_STAT_DEC(c) do { } while (0) +#endif /* CPTCFG_ATH9K_DFS_DEBUGFS */ + +#endif /* ATH9K_DFS_DEBUG_H */ diff --git a/drivers/net/wireless/ath/ath9k/dfs_pattern_detector.c b/drivers/net/wireless/ath/ath9k/dfs_pattern_detector.c new file mode 100644 index 0000000..491305c --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/dfs_pattern_detector.c @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2012 Neratec Solutions AG + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "dfs_pattern_detector.h" +#include "dfs_pri_detector.h" +#include "ath9k.h" + +/* + * tolerated deviation of radar time stamp in usecs on both sides + * TODO: this might need to be HW-dependent + */ +#define PRI_TOLERANCE 16 + +/** + * struct radar_types - contains array of patterns defined for one DFS domain + * @domain: DFS regulatory domain + * @num_radar_types: number of radar types to follow + * @radar_types: radar types array + */ +struct radar_types { + enum nl80211_dfs_regions region; + u32 num_radar_types; + const struct radar_detector_specs *radar_types; +}; + +/* percentage on ppb threshold to trigger detection */ +#define MIN_PPB_THRESH 50 +#define PPB_THRESH(PPB) ((PPB * MIN_PPB_THRESH + 50) / 100) +#define PRF2PRI(PRF) ((1000000 + PRF / 2) / PRF) +/* percentage of pulse width tolerance */ +#define WIDTH_TOLERANCE 5 +#define WIDTH_LOWER(X) ((X*(100-WIDTH_TOLERANCE)+50)/100) +#define WIDTH_UPPER(X) ((X*(100+WIDTH_TOLERANCE)+50)/100) + +#define ETSI_PATTERN(ID, WMIN, WMAX, PMIN, PMAX, PRF, PPB) \ +{ \ + ID, WIDTH_LOWER(WMIN), WIDTH_UPPER(WMAX), \ + (PRF2PRI(PMAX) - PRI_TOLERANCE), \ + (PRF2PRI(PMIN) * PRF + PRI_TOLERANCE), PRF, PPB * PRF, \ + PPB_THRESH(PPB), PRI_TOLERANCE, \ +} + +/* radar types as defined by ETSI EN-301-893 v1.5.1 */ +static const struct radar_detector_specs etsi_radar_ref_types_v15[] = { + ETSI_PATTERN(0, 0, 1, 700, 700, 1, 18), + ETSI_PATTERN(1, 0, 5, 200, 1000, 1, 10), + ETSI_PATTERN(2, 0, 15, 200, 1600, 1, 15), + ETSI_PATTERN(3, 0, 15, 2300, 4000, 1, 25), + ETSI_PATTERN(4, 20, 30, 2000, 4000, 1, 20), + ETSI_PATTERN(5, 0, 2, 300, 400, 3, 10), + ETSI_PATTERN(6, 0, 2, 400, 1200, 3, 15), +}; + +static const struct radar_types etsi_radar_types_v15 = { + .region = NL80211_DFS_ETSI, + .num_radar_types = ARRAY_SIZE(etsi_radar_ref_types_v15), + .radar_types = etsi_radar_ref_types_v15, +}; + +/* for now, we support ETSI radar types, FCC and JP are TODO */ +static const struct radar_types *dfs_domains[] = { + &etsi_radar_types_v15, +}; + +/** + * get_dfs_domain_radar_types() - get radar types for a given DFS domain + * @param domain DFS domain + * @return radar_types ptr on success, NULL if DFS domain is not supported + */ +static const struct radar_types * +get_dfs_domain_radar_types(enum nl80211_dfs_regions region) +{ + u32 i; + for (i = 0; i < ARRAY_SIZE(dfs_domains); i++) { + if (dfs_domains[i]->region == region) + return dfs_domains[i]; + } + return NULL; +} + +/** + * struct channel_detector - detector elements for a DFS channel + * @head: list_head + * @freq: frequency for this channel detector in MHz + * @detectors: array of dynamically created detector elements for this freq + * + * Channel detectors are required to provide multi-channel DFS detection, e.g. + * to support off-channel scanning. A pattern detector has a list of channels + * radar pulses have been reported for in the past. + */ +struct channel_detector { + struct list_head head; + u16 freq; + struct pri_detector **detectors; +}; + +/* channel_detector_reset() - reset detector lines for a given channel */ +static void channel_detector_reset(struct dfs_pattern_detector *dpd, + struct channel_detector *cd) +{ + u32 i; + if (cd == NULL) + return; + for (i = 0; i < dpd->num_radar_types; i++) + cd->detectors[i]->reset(cd->detectors[i], dpd->last_pulse_ts); +} + +/* channel_detector_exit() - destructor */ +static void channel_detector_exit(struct dfs_pattern_detector *dpd, + struct channel_detector *cd) +{ + u32 i; + if (cd == NULL) + return; + list_del(&cd->head); + for (i = 0; i < dpd->num_radar_types; i++) { + struct pri_detector *de = cd->detectors[i]; + if (de != NULL) + de->exit(de); + } + kfree(cd->detectors); + kfree(cd); +} + +static struct channel_detector * +channel_detector_create(struct dfs_pattern_detector *dpd, u16 freq) +{ + u32 sz, i; + struct channel_detector *cd; + struct ath_common *common = ath9k_hw_common(dpd->ah); + + cd = kmalloc(sizeof(*cd), GFP_ATOMIC); + if (cd == NULL) + goto fail; + + INIT_LIST_HEAD(&cd->head); + cd->freq = freq; + sz = sizeof(cd->detectors) * dpd->num_radar_types; + cd->detectors = kzalloc(sz, GFP_ATOMIC); + if (cd->detectors == NULL) + goto fail; + + for (i = 0; i < dpd->num_radar_types; i++) { + const struct radar_detector_specs *rs = &dpd->radar_spec[i]; + struct pri_detector *de = pri_detector_init(rs); + if (de == NULL) + goto fail; + cd->detectors[i] = de; + } + list_add(&cd->head, &dpd->channel_detectors); + return cd; + +fail: + ath_dbg(common, DFS, + "failed to allocate channel_detector for freq=%d\n", freq); + channel_detector_exit(dpd, cd); + return NULL; +} + +/** + * channel_detector_get() - get channel detector for given frequency + * @param dpd instance pointer + * @param freq frequency in MHz + * @return pointer to channel detector on success, NULL otherwise + * + * Return existing channel detector for the given frequency or return a + * newly create one. + */ +static struct channel_detector * +channel_detector_get(struct dfs_pattern_detector *dpd, u16 freq) +{ + struct channel_detector *cd; + list_for_each_entry(cd, &dpd->channel_detectors, head) { + if (cd->freq == freq) + return cd; + } + return channel_detector_create(dpd, freq); +} + +/* + * DFS Pattern Detector + */ + +/* dpd_reset(): reset all channel detectors */ +static void dpd_reset(struct dfs_pattern_detector *dpd) +{ + struct channel_detector *cd; + if (!list_empty(&dpd->channel_detectors)) + list_for_each_entry(cd, &dpd->channel_detectors, head) + channel_detector_reset(dpd, cd); + +} +static void dpd_exit(struct dfs_pattern_detector *dpd) +{ + struct channel_detector *cd, *cd0; + if (!list_empty(&dpd->channel_detectors)) + list_for_each_entry_safe(cd, cd0, &dpd->channel_detectors, head) + channel_detector_exit(dpd, cd); + kfree(dpd); +} + +static bool +dpd_add_pulse(struct dfs_pattern_detector *dpd, struct pulse_event *event) +{ + u32 i; + struct channel_detector *cd; + + /* + * pulses received for a non-supported or un-initialized + * domain are treated as detected radars for fail-safety + */ + if (dpd->region == NL80211_DFS_UNSET) + return true; + + cd = channel_detector_get(dpd, event->freq); + if (cd == NULL) + return false; + + dpd->last_pulse_ts = event->ts; + /* reset detector on time stamp wraparound, caused by TSF reset */ + if (event->ts < dpd->last_pulse_ts) + dpd_reset(dpd); + + /* do type individual pattern matching */ + for (i = 0; i < dpd->num_radar_types; i++) { + struct pri_detector *pd = cd->detectors[i]; + struct pri_sequence *ps = pd->add_pulse(pd, event); + if (ps != NULL) { + ath_dbg(ath9k_hw_common(dpd->ah), DFS, + "DFS: radar found on freq=%d: id=%d, pri=%d, " + "count=%d, count_false=%d\n", + event->freq, pd->rs->type_id, + ps->pri, ps->count, ps->count_falses); + channel_detector_reset(dpd, cd); + return true; + } + } + return false; +} + +static bool dpd_set_domain(struct dfs_pattern_detector *dpd, + enum nl80211_dfs_regions region) +{ + const struct radar_types *rt; + struct channel_detector *cd, *cd0; + + if (dpd->region == region) + return true; + + dpd->region = NL80211_DFS_UNSET; + + rt = get_dfs_domain_radar_types(region); + if (rt == NULL) + return false; + + /* delete all channel detectors for previous DFS domain */ + if (!list_empty(&dpd->channel_detectors)) + list_for_each_entry_safe(cd, cd0, &dpd->channel_detectors, head) + channel_detector_exit(dpd, cd); + dpd->radar_spec = rt->radar_types; + dpd->num_radar_types = rt->num_radar_types; + + dpd->region = region; + return true; +} + +static struct dfs_pattern_detector default_dpd = { + .exit = dpd_exit, + .set_dfs_domain = dpd_set_domain, + .add_pulse = dpd_add_pulse, + .region = NL80211_DFS_UNSET, +}; + +struct dfs_pattern_detector * +dfs_pattern_detector_init(struct ath_hw *ah, enum nl80211_dfs_regions region) +{ + struct dfs_pattern_detector *dpd; + struct ath_common *common = ath9k_hw_common(ah); + + dpd = kmalloc(sizeof(*dpd), GFP_KERNEL); + if (dpd == NULL) + return NULL; + + *dpd = default_dpd; + INIT_LIST_HEAD(&dpd->channel_detectors); + + dpd->ah = ah; + if (dpd->set_dfs_domain(dpd, region)) + return dpd; + + ath_dbg(common, DFS,"Could not set DFS domain to %d", region); + kfree(dpd); + return NULL; +} +EXPORT_SYMBOL(dfs_pattern_detector_init); diff --git a/drivers/net/wireless/ath/ath9k/dfs_pattern_detector.h b/drivers/net/wireless/ath/ath9k/dfs_pattern_detector.h new file mode 100644 index 0000000..c8fa2e0 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/dfs_pattern_detector.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2012 Neratec Solutions AG + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DFS_PATTERN_DETECTOR_H +#define DFS_PATTERN_DETECTOR_H + +#include +#include +#include + +/** + * struct pulse_event - describing pulses reported by PHY + * @ts: pulse time stamp in us + * @freq: channel frequency in MHz + * @width: pulse duration in us + * @rssi: rssi of radar event + */ +struct pulse_event { + u64 ts; + u16 freq; + u8 width; + u8 rssi; +}; + +/** + * struct radar_detector_specs - detector specs for a radar pattern type + * @type_id: pattern type, as defined by regulatory + * @width_min: minimum radar pulse width in [us] + * @width_max: maximum radar pulse width in [us] + * @pri_min: minimum pulse repetition interval in [us] (including tolerance) + * @pri_max: minimum pri in [us] (including tolerance) + * @num_pri: maximum number of different pri for this type + * @ppb: pulses per bursts for this type + * @ppb_thresh: number of pulses required to trigger detection + * @max_pri_tolerance: pulse time stamp tolerance on both sides [us] + */ +struct radar_detector_specs { + u8 type_id; + u8 width_min; + u8 width_max; + u16 pri_min; + u16 pri_max; + u8 num_pri; + u8 ppb; + u8 ppb_thresh; + u8 max_pri_tolerance; +}; + +/** + * struct dfs_pattern_detector - DFS pattern detector + * @exit(): destructor + * @set_dfs_domain(): set DFS domain, resets detector lines upon domain changes + * @add_pulse(): add radar pulse to detector, returns true on detection + * @region: active DFS region, NL80211_DFS_UNSET until set + * @num_radar_types: number of different radar types + * @last_pulse_ts: time stamp of last valid pulse in usecs + * @radar_detector_specs: array of radar detection specs + * @channel_detectors: list connecting channel_detector elements + */ +struct dfs_pattern_detector { + void (*exit)(struct dfs_pattern_detector *dpd); + bool (*set_dfs_domain)(struct dfs_pattern_detector *dpd, + enum nl80211_dfs_regions region); + bool (*add_pulse)(struct dfs_pattern_detector *dpd, + struct pulse_event *pe); + + enum nl80211_dfs_regions region; + u8 num_radar_types; + u64 last_pulse_ts; + /* needed for ath_dbg() */ + struct ath_hw *ah; + + const struct radar_detector_specs *radar_spec; + struct list_head channel_detectors; +}; + +/** + * dfs_pattern_detector_init() - constructor for pattern detector class + * @param region: DFS domain to be used, can be NL80211_DFS_UNSET at creation + * @return instance pointer on success, NULL otherwise + */ +#if defined(CPTCFG_ATH9K_DFS_CERTIFIED) +extern struct dfs_pattern_detector * +dfs_pattern_detector_init(struct ath_hw *ah, enum nl80211_dfs_regions region); +#else +static inline struct dfs_pattern_detector * +dfs_pattern_detector_init(struct ath_hw *ah, enum nl80211_dfs_regions region) +{ + return NULL; +} +#endif /* CPTCFG_ATH9K_DFS_CERTIFIED */ + +#endif /* DFS_PATTERN_DETECTOR_H */ diff --git a/drivers/net/wireless/ath/ath9k/dfs_pri_detector.c b/drivers/net/wireless/ath/ath9k/dfs_pri_detector.c new file mode 100644 index 0000000..5ba4b6f --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/dfs_pri_detector.c @@ -0,0 +1,425 @@ +/* + * Copyright (c) 2012 Neratec Solutions AG + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "ath9k.h" +#include "dfs_pattern_detector.h" +#include "dfs_pri_detector.h" +#include "dfs_debug.h" + +/** + * struct pulse_elem - elements in pulse queue + * @ts: time stamp in usecs + */ +struct pulse_elem { + struct list_head head; + u64 ts; +}; + +/** + * pde_get_multiple() - get number of multiples considering a given tolerance + * @return factor if abs(val - factor*fraction) <= tolerance, 0 otherwise + */ +static u32 pde_get_multiple(u32 val, u32 fraction, u32 tolerance) +{ + u32 remainder; + u32 factor; + u32 delta; + + if (fraction == 0) + return 0; + + delta = (val < fraction) ? (fraction - val) : (val - fraction); + + if (delta <= tolerance) + /* val and fraction are within tolerance */ + return 1; + + factor = val / fraction; + remainder = val % fraction; + if (remainder > tolerance) { + /* no exact match */ + if ((fraction - remainder) <= tolerance) + /* remainder is within tolerance */ + factor++; + else + factor = 0; + } + return factor; +} + +/** + * DOC: Singleton Pulse and Sequence Pools + * + * Instances of pri_sequence and pulse_elem are kept in singleton pools to + * reduce the number of dynamic allocations. They are shared between all + * instances and grow up to the peak number of simultaneously used objects. + * + * Memory is freed after all references to the pools are released. + */ +static u32 singleton_pool_references; +static LIST_HEAD(pulse_pool); +static LIST_HEAD(pseq_pool); +static DEFINE_SPINLOCK(pool_lock); + +static void pool_register_ref(void) +{ + spin_lock_bh(&pool_lock); + singleton_pool_references++; + DFS_POOL_STAT_INC(pool_reference); + spin_unlock_bh(&pool_lock); +} + +static void pool_deregister_ref(void) +{ + spin_lock_bh(&pool_lock); + singleton_pool_references--; + DFS_POOL_STAT_DEC(pool_reference); + if (singleton_pool_references == 0) { + /* free singleton pools with no references left */ + struct pri_sequence *ps, *ps0; + struct pulse_elem *p, *p0; + + list_for_each_entry_safe(p, p0, &pulse_pool, head) { + list_del(&p->head); + DFS_POOL_STAT_DEC(pulse_allocated); + kfree(p); + } + list_for_each_entry_safe(ps, ps0, &pseq_pool, head) { + list_del(&ps->head); + DFS_POOL_STAT_DEC(pseq_allocated); + kfree(ps); + } + } + spin_unlock_bh(&pool_lock); +} + +static void pool_put_pulse_elem(struct pulse_elem *pe) +{ + spin_lock_bh(&pool_lock); + list_add(&pe->head, &pulse_pool); + DFS_POOL_STAT_DEC(pulse_used); + spin_unlock_bh(&pool_lock); +} + +static void pool_put_pseq_elem(struct pri_sequence *pse) +{ + spin_lock_bh(&pool_lock); + list_add(&pse->head, &pseq_pool); + DFS_POOL_STAT_DEC(pseq_used); + spin_unlock_bh(&pool_lock); +} + +static struct pri_sequence *pool_get_pseq_elem(void) +{ + struct pri_sequence *pse = NULL; + spin_lock_bh(&pool_lock); + if (!list_empty(&pseq_pool)) { + pse = list_first_entry(&pseq_pool, struct pri_sequence, head); + list_del(&pse->head); + DFS_POOL_STAT_INC(pseq_used); + } + spin_unlock_bh(&pool_lock); + return pse; +} + +static struct pulse_elem *pool_get_pulse_elem(void) +{ + struct pulse_elem *pe = NULL; + spin_lock_bh(&pool_lock); + if (!list_empty(&pulse_pool)) { + pe = list_first_entry(&pulse_pool, struct pulse_elem, head); + list_del(&pe->head); + DFS_POOL_STAT_INC(pulse_used); + } + spin_unlock_bh(&pool_lock); + return pe; +} + +static struct pulse_elem *pulse_queue_get_tail(struct pri_detector *pde) +{ + struct list_head *l = &pde->pulses; + if (list_empty(l)) + return NULL; + return list_entry(l->prev, struct pulse_elem, head); +} + +static bool pulse_queue_dequeue(struct pri_detector *pde) +{ + struct pulse_elem *p = pulse_queue_get_tail(pde); + if (p != NULL) { + list_del_init(&p->head); + pde->count--; + /* give it back to pool */ + pool_put_pulse_elem(p); + } + return (pde->count > 0); +} + +/* remove pulses older than window */ +static void pulse_queue_check_window(struct pri_detector *pde) +{ + u64 min_valid_ts; + struct pulse_elem *p; + + /* there is no delta time with less than 2 pulses */ + if (pde->count < 2) + return; + + if (pde->last_ts <= pde->window_size) + return; + + min_valid_ts = pde->last_ts - pde->window_size; + while ((p = pulse_queue_get_tail(pde)) != NULL) { + if (p->ts >= min_valid_ts) + return; + pulse_queue_dequeue(pde); + } +} + +static bool pulse_queue_enqueue(struct pri_detector *pde, u64 ts) +{ + struct pulse_elem *p = pool_get_pulse_elem(); + if (p == NULL) { + p = kmalloc(sizeof(*p), GFP_ATOMIC); + if (p == NULL) { + DFS_POOL_STAT_INC(pulse_alloc_error); + return false; + } + DFS_POOL_STAT_INC(pulse_allocated); + DFS_POOL_STAT_INC(pulse_used); + } + INIT_LIST_HEAD(&p->head); + p->ts = ts; + list_add(&p->head, &pde->pulses); + pde->count++; + pde->last_ts = ts; + pulse_queue_check_window(pde); + if (pde->count >= pde->max_count) + pulse_queue_dequeue(pde); + return true; +} + +static bool pseq_handler_create_sequences(struct pri_detector *pde, + u64 ts, u32 min_count) +{ + struct pulse_elem *p; + list_for_each_entry(p, &pde->pulses, head) { + struct pri_sequence ps, *new_ps; + struct pulse_elem *p2; + u32 tmp_false_count; + u64 min_valid_ts; + u32 delta_ts = ts - p->ts; + + if (delta_ts < pde->rs->pri_min) + /* ignore too small pri */ + continue; + + if (delta_ts > pde->rs->pri_max) + /* stop on too large pri (sorted list) */ + break; + + /* build a new sequence with new potential pri */ + ps.count = 2; + ps.count_falses = 0; + ps.first_ts = p->ts; + ps.last_ts = ts; + ps.pri = ts - p->ts; + ps.dur = ps.pri * (pde->rs->ppb - 1) + + 2 * pde->rs->max_pri_tolerance; + + p2 = p; + tmp_false_count = 0; + min_valid_ts = ts - ps.dur; + /* check which past pulses are candidates for new sequence */ + list_for_each_entry_continue(p2, &pde->pulses, head) { + u32 factor; + if (p2->ts < min_valid_ts) + /* stop on crossing window border */ + break; + /* check if pulse match (multi)PRI */ + factor = pde_get_multiple(ps.last_ts - p2->ts, ps.pri, + pde->rs->max_pri_tolerance); + if (factor > 0) { + ps.count++; + ps.first_ts = p2->ts; + /* + * on match, add the intermediate falses + * and reset counter + */ + ps.count_falses += tmp_false_count; + tmp_false_count = 0; + } else { + /* this is a potential false one */ + tmp_false_count++; + } + } + if (ps.count < min_count) + /* did not reach minimum count, drop sequence */ + continue; + + /* this is a valid one, add it */ + ps.deadline_ts = ps.first_ts + ps.dur; + new_ps = pool_get_pseq_elem(); + if (new_ps == NULL) { + new_ps = kmalloc(sizeof(*new_ps), GFP_ATOMIC); + if (new_ps == NULL) { + DFS_POOL_STAT_INC(pseq_alloc_error); + return false; + } + DFS_POOL_STAT_INC(pseq_allocated); + DFS_POOL_STAT_INC(pseq_used); + } + memcpy(new_ps, &ps, sizeof(ps)); + INIT_LIST_HEAD(&new_ps->head); + list_add(&new_ps->head, &pde->sequences); + } + return true; +} + +/* check new ts and add to all matching existing sequences */ +static u32 +pseq_handler_add_to_existing_seqs(struct pri_detector *pde, u64 ts) +{ + u32 max_count = 0; + struct pri_sequence *ps, *ps2; + list_for_each_entry_safe(ps, ps2, &pde->sequences, head) { + u32 delta_ts; + u32 factor; + + /* first ensure that sequence is within window */ + if (ts > ps->deadline_ts) { + list_del_init(&ps->head); + pool_put_pseq_elem(ps); + continue; + } + + delta_ts = ts - ps->last_ts; + factor = pde_get_multiple(delta_ts, ps->pri, + pde->rs->max_pri_tolerance); + if (factor > 0) { + ps->last_ts = ts; + ps->count++; + + if (max_count < ps->count) + max_count = ps->count; + } else { + ps->count_falses++; + } + } + return max_count; +} + +static struct pri_sequence * +pseq_handler_check_detection(struct pri_detector *pde) +{ + struct pri_sequence *ps; + + if (list_empty(&pde->sequences)) + return NULL; + + list_for_each_entry(ps, &pde->sequences, head) { + /* + * we assume to have enough matching confidence if we + * 1) have enough pulses + * 2) have more matching than false pulses + */ + if ((ps->count >= pde->rs->ppb_thresh) && + (ps->count * pde->rs->num_pri >= ps->count_falses)) + return ps; + } + return NULL; +} + + +/* free pulse queue and sequences list and give objects back to pools */ +static void pri_detector_reset(struct pri_detector *pde, u64 ts) +{ + struct pri_sequence *ps, *ps0; + struct pulse_elem *p, *p0; + list_for_each_entry_safe(ps, ps0, &pde->sequences, head) { + list_del_init(&ps->head); + pool_put_pseq_elem(ps); + } + list_for_each_entry_safe(p, p0, &pde->pulses, head) { + list_del_init(&p->head); + pool_put_pulse_elem(p); + } + pde->count = 0; + pde->last_ts = ts; +} + +static void pri_detector_exit(struct pri_detector *de) +{ + pri_detector_reset(de, 0); + pool_deregister_ref(); + kfree(de); +} + +static struct pri_sequence *pri_detector_add_pulse(struct pri_detector *de, + struct pulse_event *event) +{ + u32 max_updated_seq; + struct pri_sequence *ps; + u64 ts = event->ts; + const struct radar_detector_specs *rs = de->rs; + + /* ignore pulses not within width range */ + if ((rs->width_min > event->width) || (rs->width_max < event->width)) + return NULL; + + if ((ts - de->last_ts) < rs->max_pri_tolerance) + /* if delta to last pulse is too short, don't use this pulse */ + return NULL; + de->last_ts = ts; + + max_updated_seq = pseq_handler_add_to_existing_seqs(de, ts); + + if (!pseq_handler_create_sequences(de, ts, max_updated_seq)) { + pri_detector_reset(de, ts); + return false; + } + + ps = pseq_handler_check_detection(de); + + if (ps == NULL) + pulse_queue_enqueue(de, ts); + + return ps; +} + +struct pri_detector *pri_detector_init(const struct radar_detector_specs *rs) +{ + struct pri_detector *de; + + de = kzalloc(sizeof(*de), GFP_ATOMIC); + if (de == NULL) + return NULL; + de->exit = pri_detector_exit; + de->add_pulse = pri_detector_add_pulse; + de->reset = pri_detector_reset; + + INIT_LIST_HEAD(&de->sequences); + INIT_LIST_HEAD(&de->pulses); + de->window_size = rs->pri_max * rs->ppb * rs->num_pri; + de->max_count = rs->ppb * 2; + de->rs = rs; + + pool_register_ref(); + return de; +} diff --git a/drivers/net/wireless/ath/ath9k/dfs_pri_detector.h b/drivers/net/wireless/ath/ath9k/dfs_pri_detector.h new file mode 100644 index 0000000..723962d --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/dfs_pri_detector.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2012 Neratec Solutions AG + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DFS_PRI_DETECTOR_H +#define DFS_PRI_DETECTOR_H + +#include + +/** + * struct pri_sequence - sequence of pulses matching one PRI + * @head: list_head + * @pri: pulse repetition interval (PRI) in usecs + * @dur: duration of sequence in usecs + * @count: number of pulses in this sequence + * @count_falses: number of not matching pulses in this sequence + * @first_ts: time stamp of first pulse in usecs + * @last_ts: time stamp of last pulse in usecs + * @deadline_ts: deadline when this sequence becomes invalid (first_ts + dur) + */ +struct pri_sequence { + struct list_head head; + u32 pri; + u32 dur; + u32 count; + u32 count_falses; + u64 first_ts; + u64 last_ts; + u64 deadline_ts; +}; + +/** + * struct pri_detector - PRI detector element for a dedicated radar type + * @exit(): destructor + * @add_pulse(): add pulse event, returns pri_sequence if pattern was detected + * @reset(): clear states and reset to given time stamp + * @rs: detector specs for this detector element + * @last_ts: last pulse time stamp considered for this element in usecs + * @sequences: list_head holding potential pulse sequences + * @pulses: list connecting pulse_elem objects + * @count: number of pulses in queue + * @max_count: maximum number of pulses to be queued + * @window_size: window size back from newest pulse time stamp in usecs + */ +struct pri_detector { + void (*exit) (struct pri_detector *de); + struct pri_sequence * + (*add_pulse)(struct pri_detector *de, struct pulse_event *e); + void (*reset) (struct pri_detector *de, u64 ts); + +/* private: internal use only */ + const struct radar_detector_specs *rs; + u64 last_ts; + struct list_head sequences; + struct list_head pulses; + u32 count; + u32 max_count; + u32 window_size; +}; + +struct pri_detector *pri_detector_init(const struct radar_detector_specs *rs); + +#endif /* DFS_PRI_DETECTOR_H */ diff --git a/drivers/net/wireless/ath/ath9k/eeprom.c b/drivers/net/wireless/ath/ath9k/eeprom.c new file mode 100644 index 0000000..971d770 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/eeprom.c @@ -0,0 +1,586 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "hw.h" + +void ath9k_hw_analog_shift_regwrite(struct ath_hw *ah, u32 reg, u32 val) +{ + REG_WRITE(ah, reg, val); + + if (ah->config.analog_shiftreg) + udelay(100); +} + +void ath9k_hw_analog_shift_rmw(struct ath_hw *ah, u32 reg, u32 mask, + u32 shift, u32 val) +{ + u32 regVal; + + regVal = REG_READ(ah, reg) & ~mask; + regVal |= (val << shift) & mask; + + REG_WRITE(ah, reg, regVal); + + if (ah->config.analog_shiftreg) + udelay(100); +} + +int16_t ath9k_hw_interpolate(u16 target, u16 srcLeft, u16 srcRight, + int16_t targetLeft, int16_t targetRight) +{ + int16_t rv; + + if (srcRight == srcLeft) { + rv = targetLeft; + } else { + rv = (int16_t) (((target - srcLeft) * targetRight + + (srcRight - target) * targetLeft) / + (srcRight - srcLeft)); + } + return rv; +} + +bool ath9k_hw_get_lower_upper_index(u8 target, u8 *pList, u16 listSize, + u16 *indexL, u16 *indexR) +{ + u16 i; + + if (target <= pList[0]) { + *indexL = *indexR = 0; + return true; + } + if (target >= pList[listSize - 1]) { + *indexL = *indexR = (u16) (listSize - 1); + return true; + } + + for (i = 0; i < listSize - 1; i++) { + if (pList[i] == target) { + *indexL = *indexR = i; + return true; + } + if (target < pList[i + 1]) { + *indexL = i; + *indexR = (u16) (i + 1); + return false; + } + } + return false; +} + +void ath9k_hw_usb_gen_fill_eeprom(struct ath_hw *ah, u16 *eep_data, + int eep_start_loc, int size) +{ + int i = 0, j, addr; + u32 addrdata[8]; + u32 data[8]; + + for (addr = 0; addr < size; addr++) { + addrdata[i] = AR5416_EEPROM_OFFSET + + ((addr + eep_start_loc) << AR5416_EEPROM_S); + i++; + if (i == 8) { + REG_READ_MULTI(ah, addrdata, data, i); + + for (j = 0; j < i; j++) { + *eep_data = data[j]; + eep_data++; + } + i = 0; + } + } + + if (i != 0) { + REG_READ_MULTI(ah, addrdata, data, i); + + for (j = 0; j < i; j++) { + *eep_data = data[j]; + eep_data++; + } + } +} + +static bool ath9k_hw_nvram_read_blob(struct ath_hw *ah, u32 off, + u16 *data) +{ + u16 *blob_data; + + if (off * sizeof(u16) > ah->eeprom_blob->size) + return false; + + blob_data = (u16 *)ah->eeprom_blob->data; + *data = blob_data[off]; + return true; +} + +bool ath9k_hw_nvram_read(struct ath_hw *ah, u32 off, u16 *data) +{ + struct ath_common *common = ath9k_hw_common(ah); + bool ret; + + if (ah->eeprom_blob) + ret = ath9k_hw_nvram_read_blob(ah, off, data); + else + ret = common->bus_ops->eeprom_read(common, off, data); + + if (!ret) + ath_dbg(common, EEPROM, + "unable to read eeprom region at offset %u\n", off); + + return ret; +} + +void ath9k_hw_fill_vpd_table(u8 pwrMin, u8 pwrMax, u8 *pPwrList, + u8 *pVpdList, u16 numIntercepts, + u8 *pRetVpdList) +{ + u16 i, k; + u8 currPwr = pwrMin; + u16 idxL = 0, idxR = 0; + + for (i = 0; i <= (pwrMax - pwrMin) / 2; i++) { + ath9k_hw_get_lower_upper_index(currPwr, pPwrList, + numIntercepts, &(idxL), + &(idxR)); + if (idxR < 1) + idxR = 1; + if (idxL == numIntercepts - 1) + idxL = (u16) (numIntercepts - 2); + if (pPwrList[idxL] == pPwrList[idxR]) + k = pVpdList[idxL]; + else + k = (u16)(((currPwr - pPwrList[idxL]) * pVpdList[idxR] + + (pPwrList[idxR] - currPwr) * pVpdList[idxL]) / + (pPwrList[idxR] - pPwrList[idxL])); + pRetVpdList[i] = (u8) k; + currPwr += 2; + } +} + +void ath9k_hw_get_legacy_target_powers(struct ath_hw *ah, + struct ath9k_channel *chan, + struct cal_target_power_leg *powInfo, + u16 numChannels, + struct cal_target_power_leg *pNewPower, + u16 numRates, bool isExtTarget) +{ + struct chan_centers centers; + u16 clo, chi; + int i; + int matchIndex = -1, lowIndex = -1; + u16 freq; + + ath9k_hw_get_channel_centers(ah, chan, ¢ers); + freq = (isExtTarget) ? centers.ext_center : centers.ctl_center; + + if (freq <= ath9k_hw_fbin2freq(powInfo[0].bChannel, + IS_CHAN_2GHZ(chan))) { + matchIndex = 0; + } else { + for (i = 0; (i < numChannels) && + (powInfo[i].bChannel != AR5416_BCHAN_UNUSED); i++) { + if (freq == ath9k_hw_fbin2freq(powInfo[i].bChannel, + IS_CHAN_2GHZ(chan))) { + matchIndex = i; + break; + } else if (freq < ath9k_hw_fbin2freq(powInfo[i].bChannel, + IS_CHAN_2GHZ(chan)) && i > 0 && + freq > ath9k_hw_fbin2freq(powInfo[i - 1].bChannel, + IS_CHAN_2GHZ(chan))) { + lowIndex = i - 1; + break; + } + } + if ((matchIndex == -1) && (lowIndex == -1)) + matchIndex = i - 1; + } + + if (matchIndex != -1) { + *pNewPower = powInfo[matchIndex]; + } else { + clo = ath9k_hw_fbin2freq(powInfo[lowIndex].bChannel, + IS_CHAN_2GHZ(chan)); + chi = ath9k_hw_fbin2freq(powInfo[lowIndex + 1].bChannel, + IS_CHAN_2GHZ(chan)); + + for (i = 0; i < numRates; i++) { + pNewPower->tPow2x[i] = + (u8)ath9k_hw_interpolate(freq, clo, chi, + powInfo[lowIndex].tPow2x[i], + powInfo[lowIndex + 1].tPow2x[i]); + } + } +} + +void ath9k_hw_get_target_powers(struct ath_hw *ah, + struct ath9k_channel *chan, + struct cal_target_power_ht *powInfo, + u16 numChannels, + struct cal_target_power_ht *pNewPower, + u16 numRates, bool isHt40Target) +{ + struct chan_centers centers; + u16 clo, chi; + int i; + int matchIndex = -1, lowIndex = -1; + u16 freq; + + ath9k_hw_get_channel_centers(ah, chan, ¢ers); + freq = isHt40Target ? centers.synth_center : centers.ctl_center; + + if (freq <= ath9k_hw_fbin2freq(powInfo[0].bChannel, IS_CHAN_2GHZ(chan))) { + matchIndex = 0; + } else { + for (i = 0; (i < numChannels) && + (powInfo[i].bChannel != AR5416_BCHAN_UNUSED); i++) { + if (freq == ath9k_hw_fbin2freq(powInfo[i].bChannel, + IS_CHAN_2GHZ(chan))) { + matchIndex = i; + break; + } else + if (freq < ath9k_hw_fbin2freq(powInfo[i].bChannel, + IS_CHAN_2GHZ(chan)) && i > 0 && + freq > ath9k_hw_fbin2freq(powInfo[i - 1].bChannel, + IS_CHAN_2GHZ(chan))) { + lowIndex = i - 1; + break; + } + } + if ((matchIndex == -1) && (lowIndex == -1)) + matchIndex = i - 1; + } + + if (matchIndex != -1) { + *pNewPower = powInfo[matchIndex]; + } else { + clo = ath9k_hw_fbin2freq(powInfo[lowIndex].bChannel, + IS_CHAN_2GHZ(chan)); + chi = ath9k_hw_fbin2freq(powInfo[lowIndex + 1].bChannel, + IS_CHAN_2GHZ(chan)); + + for (i = 0; i < numRates; i++) { + pNewPower->tPow2x[i] = (u8)ath9k_hw_interpolate(freq, + clo, chi, + powInfo[lowIndex].tPow2x[i], + powInfo[lowIndex + 1].tPow2x[i]); + } + } +} + +u16 ath9k_hw_get_max_edge_power(u16 freq, struct cal_ctl_edges *pRdEdgesPower, + bool is2GHz, int num_band_edges) +{ + u16 twiceMaxEdgePower = MAX_RATE_POWER; + int i; + + for (i = 0; (i < num_band_edges) && + (pRdEdgesPower[i].bChannel != AR5416_BCHAN_UNUSED); i++) { + if (freq == ath9k_hw_fbin2freq(pRdEdgesPower[i].bChannel, is2GHz)) { + twiceMaxEdgePower = CTL_EDGE_TPOWER(pRdEdgesPower[i].ctl); + break; + } else if ((i > 0) && + (freq < ath9k_hw_fbin2freq(pRdEdgesPower[i].bChannel, + is2GHz))) { + if (ath9k_hw_fbin2freq(pRdEdgesPower[i - 1].bChannel, + is2GHz) < freq && + CTL_EDGE_FLAGS(pRdEdgesPower[i - 1].ctl)) { + twiceMaxEdgePower = + CTL_EDGE_TPOWER(pRdEdgesPower[i - 1].ctl); + } + break; + } + } + + return twiceMaxEdgePower; +} + +u16 ath9k_hw_get_scaled_power(struct ath_hw *ah, u16 power_limit, + u8 antenna_reduction) +{ + u16 reduction = antenna_reduction; + + /* + * Reduce scaled Power by number of chains active + * to get the per chain tx power level. + */ + switch (ar5416_get_ntxchains(ah->txchainmask)) { + case 1: + break; + case 2: + reduction += POWER_CORRECTION_FOR_TWO_CHAIN; + break; + case 3: + reduction += POWER_CORRECTION_FOR_THREE_CHAIN; + break; + } + + if (power_limit > reduction) + power_limit -= reduction; + else + power_limit = 0; + + return power_limit; +} + +void ath9k_hw_update_regulatory_maxpower(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath_regulatory *regulatory = ath9k_hw_regulatory(ah); + + switch (ar5416_get_ntxchains(ah->txchainmask)) { + case 1: + break; + case 2: + regulatory->max_power_level += POWER_CORRECTION_FOR_TWO_CHAIN; + break; + case 3: + regulatory->max_power_level += POWER_CORRECTION_FOR_THREE_CHAIN; + break; + default: + ath_dbg(common, EEPROM, "Invalid chainmask configuration\n"); + break; + } +} + +void ath9k_hw_get_gain_boundaries_pdadcs(struct ath_hw *ah, + struct ath9k_channel *chan, + void *pRawDataSet, + u8 *bChans, u16 availPiers, + u16 tPdGainOverlap, + u16 *pPdGainBoundaries, u8 *pPDADCValues, + u16 numXpdGains) +{ + int i, j, k; + int16_t ss; + u16 idxL = 0, idxR = 0, numPiers; + static u8 vpdTableL[AR5416_NUM_PD_GAINS] + [AR5416_MAX_PWR_RANGE_IN_HALF_DB]; + static u8 vpdTableR[AR5416_NUM_PD_GAINS] + [AR5416_MAX_PWR_RANGE_IN_HALF_DB]; + static u8 vpdTableI[AR5416_NUM_PD_GAINS] + [AR5416_MAX_PWR_RANGE_IN_HALF_DB]; + + u8 *pVpdL, *pVpdR, *pPwrL, *pPwrR; + u8 minPwrT4[AR5416_NUM_PD_GAINS]; + u8 maxPwrT4[AR5416_NUM_PD_GAINS]; + int16_t vpdStep; + int16_t tmpVal; + u16 sizeCurrVpdTable, maxIndex, tgtIndex; + bool match; + int16_t minDelta = 0; + struct chan_centers centers; + int pdgain_boundary_default; + struct cal_data_per_freq *data_def = pRawDataSet; + struct cal_data_per_freq_4k *data_4k = pRawDataSet; + struct cal_data_per_freq_ar9287 *data_9287 = pRawDataSet; + bool eeprom_4k = AR_SREV_9285(ah) || AR_SREV_9271(ah); + int intercepts; + + if (AR_SREV_9287(ah)) + intercepts = AR9287_PD_GAIN_ICEPTS; + else + intercepts = AR5416_PD_GAIN_ICEPTS; + + memset(&minPwrT4, 0, AR5416_NUM_PD_GAINS); + ath9k_hw_get_channel_centers(ah, chan, ¢ers); + + for (numPiers = 0; numPiers < availPiers; numPiers++) { + if (bChans[numPiers] == AR5416_BCHAN_UNUSED) + break; + } + + match = ath9k_hw_get_lower_upper_index((u8)FREQ2FBIN(centers.synth_center, + IS_CHAN_2GHZ(chan)), + bChans, numPiers, &idxL, &idxR); + + if (match) { + if (AR_SREV_9287(ah)) { + /* FIXME: array overrun? */ + for (i = 0; i < numXpdGains; i++) { + minPwrT4[i] = data_9287[idxL].pwrPdg[i][0]; + maxPwrT4[i] = data_9287[idxL].pwrPdg[i][4]; + ath9k_hw_fill_vpd_table(minPwrT4[i], maxPwrT4[i], + data_9287[idxL].pwrPdg[i], + data_9287[idxL].vpdPdg[i], + intercepts, + vpdTableI[i]); + } + } else if (eeprom_4k) { + for (i = 0; i < numXpdGains; i++) { + minPwrT4[i] = data_4k[idxL].pwrPdg[i][0]; + maxPwrT4[i] = data_4k[idxL].pwrPdg[i][4]; + ath9k_hw_fill_vpd_table(minPwrT4[i], maxPwrT4[i], + data_4k[idxL].pwrPdg[i], + data_4k[idxL].vpdPdg[i], + intercepts, + vpdTableI[i]); + } + } else { + for (i = 0; i < numXpdGains; i++) { + minPwrT4[i] = data_def[idxL].pwrPdg[i][0]; + maxPwrT4[i] = data_def[idxL].pwrPdg[i][4]; + ath9k_hw_fill_vpd_table(minPwrT4[i], maxPwrT4[i], + data_def[idxL].pwrPdg[i], + data_def[idxL].vpdPdg[i], + intercepts, + vpdTableI[i]); + } + } + } else { + for (i = 0; i < numXpdGains; i++) { + if (AR_SREV_9287(ah)) { + pVpdL = data_9287[idxL].vpdPdg[i]; + pPwrL = data_9287[idxL].pwrPdg[i]; + pVpdR = data_9287[idxR].vpdPdg[i]; + pPwrR = data_9287[idxR].pwrPdg[i]; + } else if (eeprom_4k) { + pVpdL = data_4k[idxL].vpdPdg[i]; + pPwrL = data_4k[idxL].pwrPdg[i]; + pVpdR = data_4k[idxR].vpdPdg[i]; + pPwrR = data_4k[idxR].pwrPdg[i]; + } else { + pVpdL = data_def[idxL].vpdPdg[i]; + pPwrL = data_def[idxL].pwrPdg[i]; + pVpdR = data_def[idxR].vpdPdg[i]; + pPwrR = data_def[idxR].pwrPdg[i]; + } + + minPwrT4[i] = max(pPwrL[0], pPwrR[0]); + + maxPwrT4[i] = + min(pPwrL[intercepts - 1], + pPwrR[intercepts - 1]); + + + ath9k_hw_fill_vpd_table(minPwrT4[i], maxPwrT4[i], + pPwrL, pVpdL, + intercepts, + vpdTableL[i]); + ath9k_hw_fill_vpd_table(minPwrT4[i], maxPwrT4[i], + pPwrR, pVpdR, + intercepts, + vpdTableR[i]); + + for (j = 0; j <= (maxPwrT4[i] - minPwrT4[i]) / 2; j++) { + vpdTableI[i][j] = + (u8)(ath9k_hw_interpolate((u16) + FREQ2FBIN(centers. + synth_center, + IS_CHAN_2GHZ + (chan)), + bChans[idxL], bChans[idxR], + vpdTableL[i][j], vpdTableR[i][j])); + } + } + } + + k = 0; + + for (i = 0; i < numXpdGains; i++) { + if (i == (numXpdGains - 1)) + pPdGainBoundaries[i] = + (u16)(maxPwrT4[i] / 2); + else + pPdGainBoundaries[i] = + (u16)((maxPwrT4[i] + minPwrT4[i + 1]) / 4); + + pPdGainBoundaries[i] = + min((u16)MAX_RATE_POWER, pPdGainBoundaries[i]); + + minDelta = 0; + + if (i == 0) { + if (AR_SREV_9280_20_OR_LATER(ah)) + ss = (int16_t)(0 - (minPwrT4[i] / 2)); + else + ss = 0; + } else { + ss = (int16_t)((pPdGainBoundaries[i - 1] - + (minPwrT4[i] / 2)) - + tPdGainOverlap + 1 + minDelta); + } + vpdStep = (int16_t)(vpdTableI[i][1] - vpdTableI[i][0]); + vpdStep = (int16_t)((vpdStep < 1) ? 1 : vpdStep); + + while ((ss < 0) && (k < (AR5416_NUM_PDADC_VALUES - 1))) { + tmpVal = (int16_t)(vpdTableI[i][0] + ss * vpdStep); + pPDADCValues[k++] = (u8)((tmpVal < 0) ? 0 : tmpVal); + ss++; + } + + sizeCurrVpdTable = (u8) ((maxPwrT4[i] - minPwrT4[i]) / 2 + 1); + tgtIndex = (u8)(pPdGainBoundaries[i] + tPdGainOverlap - + (minPwrT4[i] / 2)); + maxIndex = (tgtIndex < sizeCurrVpdTable) ? + tgtIndex : sizeCurrVpdTable; + + while ((ss < maxIndex) && (k < (AR5416_NUM_PDADC_VALUES - 1))) { + pPDADCValues[k++] = vpdTableI[i][ss++]; + } + + vpdStep = (int16_t)(vpdTableI[i][sizeCurrVpdTable - 1] - + vpdTableI[i][sizeCurrVpdTable - 2]); + vpdStep = (int16_t)((vpdStep < 1) ? 1 : vpdStep); + + if (tgtIndex >= maxIndex) { + while ((ss <= tgtIndex) && + (k < (AR5416_NUM_PDADC_VALUES - 1))) { + tmpVal = (int16_t)((vpdTableI[i][sizeCurrVpdTable - 1] + + (ss - maxIndex + 1) * vpdStep)); + pPDADCValues[k++] = (u8)((tmpVal > 255) ? + 255 : tmpVal); + ss++; + } + } + } + + if (eeprom_4k) + pdgain_boundary_default = 58; + else + pdgain_boundary_default = pPdGainBoundaries[i - 1]; + + while (i < AR5416_PD_GAINS_IN_MASK) { + pPdGainBoundaries[i] = pdgain_boundary_default; + i++; + } + + while (k < AR5416_NUM_PDADC_VALUES) { + pPDADCValues[k] = pPDADCValues[k - 1]; + k++; + } +} + +int ath9k_hw_eeprom_init(struct ath_hw *ah) +{ + int status; + + if (AR_SREV_9300_20_OR_LATER(ah)) + ah->eep_ops = &eep_ar9300_ops; + else if (AR_SREV_9287(ah)) { + ah->eep_ops = &eep_ar9287_ops; + } else if (AR_SREV_9285(ah) || AR_SREV_9271(ah)) { + ah->eep_ops = &eep_4k_ops; + } else { + ah->eep_ops = &eep_def_ops; + } + + if (!ah->eep_ops->fill_eeprom(ah)) + return -EIO; + + status = ah->eep_ops->check_eeprom(ah); + + return status; +} diff --git a/drivers/net/wireless/ath/ath9k/eeprom.h b/drivers/net/wireless/ath/ath9k/eeprom.h new file mode 100644 index 0000000..40d4f62 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/eeprom.h @@ -0,0 +1,717 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef EEPROM_H +#define EEPROM_H + +#define AR_EEPROM_MODAL_SPURS 5 + +#include "../ath.h" +#include +#include "ar9003_eeprom.h" + +#ifdef __BIG_ENDIAN +#define AR5416_EEPROM_MAGIC 0x5aa5 +#else +#define AR5416_EEPROM_MAGIC 0xa55a +#endif + +#define CTRY_DEBUG 0x1ff +#define CTRY_DEFAULT 0 + +#define AR_EEPROM_EEPCAP_COMPRESS_DIS 0x0001 +#define AR_EEPROM_EEPCAP_AES_DIS 0x0002 +#define AR_EEPROM_EEPCAP_FASTFRAME_DIS 0x0004 +#define AR_EEPROM_EEPCAP_BURST_DIS 0x0008 +#define AR_EEPROM_EEPCAP_MAXQCU 0x01F0 +#define AR_EEPROM_EEPCAP_MAXQCU_S 4 +#define AR_EEPROM_EEPCAP_HEAVY_CLIP_EN 0x0200 +#define AR_EEPROM_EEPCAP_KC_ENTRIES 0xF000 +#define AR_EEPROM_EEPCAP_KC_ENTRIES_S 12 + +#define AR_EEPROM_EEREGCAP_EN_FCC_MIDBAND 0x0040 +#define AR_EEPROM_EEREGCAP_EN_KK_U1_EVEN 0x0080 +#define AR_EEPROM_EEREGCAP_EN_KK_U2 0x0100 +#define AR_EEPROM_EEREGCAP_EN_KK_MIDBAND 0x0200 +#define AR_EEPROM_EEREGCAP_EN_KK_U1_ODD 0x0400 +#define AR_EEPROM_EEREGCAP_EN_KK_NEW_11A 0x0800 + +#define AR_EEPROM_EEREGCAP_EN_KK_U1_ODD_PRE4_0 0x4000 +#define AR_EEPROM_EEREGCAP_EN_KK_NEW_11A_PRE4_0 0x8000 + +#define AR5416_EEPROM_MAGIC_OFFSET 0x0 +#define AR5416_EEPROM_S 2 +#define AR5416_EEPROM_OFFSET 0x2000 +#define AR5416_EEPROM_MAX 0xae0 + +#define AR5416_EEPROM_START_ADDR \ + (AR_SREV_9100(ah)) ? 0x1fff1000 : 0x503f1200 + +#define SD_NO_CTL 0xE0 +#define NO_CTL 0xff +#define CTL_MODE_M 0xf +#define CTL_11A 0 +#define CTL_11B 1 +#define CTL_11G 2 +#define CTL_2GHT20 5 +#define CTL_5GHT20 6 +#define CTL_2GHT40 7 +#define CTL_5GHT40 8 + +#define EXT_ADDITIVE (0x8000) +#define CTL_11A_EXT (CTL_11A | EXT_ADDITIVE) +#define CTL_11G_EXT (CTL_11G | EXT_ADDITIVE) +#define CTL_11B_EXT (CTL_11B | EXT_ADDITIVE) + +#define SUB_NUM_CTL_MODES_AT_5G_40 2 +#define SUB_NUM_CTL_MODES_AT_2G_40 3 + +#define POWER_CORRECTION_FOR_TWO_CHAIN 6 /* 10*log10(2)*2 */ +#define POWER_CORRECTION_FOR_THREE_CHAIN 10 /* 10*log10(3)*2 */ + +/* + * For AR9285 and later chipsets, the following bits are not being programmed + * in EEPROM and so need to be enabled always. + * + * Bit 0: en_fcc_mid + * Bit 1: en_jap_mid + * Bit 2: en_fcc_dfs_ht40 + * Bit 3: en_jap_ht40 + * Bit 4: en_jap_dfs_ht40 + */ +#define AR9285_RDEXT_DEFAULT 0x1F + +#define ATH9K_POW_SM(_r, _s) (((_r) & 0x3f) << (_s)) +#define FREQ2FBIN(x, y) ((y) ? ((x) - 2300) : (((x) - 4800) / 5)) +#define FBIN2FREQ(x, y) ((y) ? (2300 + x) : (4800 + 5 * x)) +#define ath9k_hw_use_flash(_ah) (!(_ah->ah_flags & AH_USE_EEPROM)) + +#define AR5416_VER_MASK (eep->baseEepHeader.version & AR5416_EEP_VER_MINOR_MASK) +#define OLC_FOR_AR9280_20_LATER (AR_SREV_9280_20_OR_LATER(ah) && \ + ah->eep_ops->get_eeprom(ah, EEP_OL_PWRCTRL)) +#define OLC_FOR_AR9287_10_LATER (AR_SREV_9287_11_OR_LATER(ah) && \ + ah->eep_ops->get_eeprom(ah, EEP_OL_PWRCTRL)) + +#define EEP_RFSILENT_ENABLED 0x0001 +#define EEP_RFSILENT_ENABLED_S 0 +#define EEP_RFSILENT_POLARITY 0x0002 +#define EEP_RFSILENT_POLARITY_S 1 +#define EEP_RFSILENT_GPIO_SEL ((AR_SREV_9462(ah) || AR_SREV_9565(ah)) ? 0x00fc : 0x001c) +#define EEP_RFSILENT_GPIO_SEL_S 2 + +#define AR5416_OPFLAGS_11A 0x01 +#define AR5416_OPFLAGS_11G 0x02 +#define AR5416_OPFLAGS_N_5G_HT40 0x04 +#define AR5416_OPFLAGS_N_2G_HT40 0x08 +#define AR5416_OPFLAGS_N_5G_HT20 0x10 +#define AR5416_OPFLAGS_N_2G_HT20 0x20 + +#define AR5416_EEP_NO_BACK_VER 0x1 +#define AR5416_EEP_VER 0xE +#define AR5416_EEP_VER_MINOR_MASK 0x0FFF +#define AR5416_EEP_MINOR_VER_2 0x2 +#define AR5416_EEP_MINOR_VER_3 0x3 +#define AR5416_EEP_MINOR_VER_7 0x7 +#define AR5416_EEP_MINOR_VER_9 0x9 +#define AR5416_EEP_MINOR_VER_16 0x10 +#define AR5416_EEP_MINOR_VER_17 0x11 +#define AR5416_EEP_MINOR_VER_19 0x13 +#define AR5416_EEP_MINOR_VER_20 0x14 +#define AR5416_EEP_MINOR_VER_21 0x15 +#define AR5416_EEP_MINOR_VER_22 0x16 + +#define AR5416_NUM_5G_CAL_PIERS 8 +#define AR5416_NUM_2G_CAL_PIERS 4 +#define AR5416_NUM_5G_20_TARGET_POWERS 8 +#define AR5416_NUM_5G_40_TARGET_POWERS 8 +#define AR5416_NUM_2G_CCK_TARGET_POWERS 3 +#define AR5416_NUM_2G_20_TARGET_POWERS 4 +#define AR5416_NUM_2G_40_TARGET_POWERS 4 +#define AR5416_NUM_CTLS 24 +#define AR5416_NUM_BAND_EDGES 8 +#define AR5416_NUM_PD_GAINS 4 +#define AR5416_PD_GAINS_IN_MASK 4 +#define AR5416_PD_GAIN_ICEPTS 5 +#define AR5416_NUM_PDADC_VALUES 128 +#define AR5416_BCHAN_UNUSED 0xFF +#define AR5416_MAX_PWR_RANGE_IN_HALF_DB 64 +#define AR5416_MAX_CHAINS 3 +#define AR9300_MAX_CHAINS 3 +#define AR5416_PWR_TABLE_OFFSET_DB -5 + +/* Rx gain type values */ +#define AR5416_EEP_RXGAIN_23DB_BACKOFF 0 +#define AR5416_EEP_RXGAIN_13DB_BACKOFF 1 +#define AR5416_EEP_RXGAIN_ORIG 2 + +/* Tx gain type values */ +#define AR5416_EEP_TXGAIN_ORIGINAL 0 +#define AR5416_EEP_TXGAIN_HIGH_POWER 1 + +#define AR5416_EEP4K_START_LOC 64 +#define AR5416_EEP4K_NUM_2G_CAL_PIERS 3 +#define AR5416_EEP4K_NUM_2G_CCK_TARGET_POWERS 3 +#define AR5416_EEP4K_NUM_2G_20_TARGET_POWERS 3 +#define AR5416_EEP4K_NUM_2G_40_TARGET_POWERS 3 +#define AR5416_EEP4K_NUM_CTLS 12 +#define AR5416_EEP4K_NUM_BAND_EDGES 4 +#define AR5416_EEP4K_NUM_PD_GAINS 2 +#define AR5416_EEP4K_MAX_CHAINS 1 + +#define AR9280_TX_GAIN_TABLE_SIZE 22 + +#define AR9287_EEP_VER 0xE +#define AR9287_EEP_VER_MINOR_MASK 0xFFF +#define AR9287_EEP_MINOR_VER_1 0x1 +#define AR9287_EEP_MINOR_VER_2 0x2 +#define AR9287_EEP_MINOR_VER_3 0x3 +#define AR9287_EEP_MINOR_VER AR9287_EEP_MINOR_VER_3 +#define AR9287_EEP_MINOR_VER_b AR9287_EEP_MINOR_VER +#define AR9287_EEP_NO_BACK_VER AR9287_EEP_MINOR_VER_1 + +#define AR9287_EEP_START_LOC 128 +#define AR9287_HTC_EEP_START_LOC 256 +#define AR9287_NUM_2G_CAL_PIERS 3 +#define AR9287_NUM_2G_CCK_TARGET_POWERS 3 +#define AR9287_NUM_2G_20_TARGET_POWERS 3 +#define AR9287_NUM_2G_40_TARGET_POWERS 3 +#define AR9287_NUM_CTLS 12 +#define AR9287_NUM_BAND_EDGES 4 +#define AR9287_PD_GAIN_ICEPTS 1 +#define AR9287_EEPMISC_BIG_ENDIAN 0x01 +#define AR9287_EEPMISC_WOW 0x02 +#define AR9287_MAX_CHAINS 2 +#define AR9287_ANT_16S 32 + +#define AR9287_DATA_SZ 32 + +#define AR9287_PWR_TABLE_OFFSET_DB -5 + +#define AR9287_CHECKSUM_LOCATION (AR9287_EEP_START_LOC + 1) + +#define CTL_EDGE_TPOWER(_ctl) ((_ctl) & 0x3f) +#define CTL_EDGE_FLAGS(_ctl) (((_ctl) >> 6) & 0x03) + +#define LNA_CTL_BUF_MODE BIT(0) +#define LNA_CTL_ISEL_LO BIT(1) +#define LNA_CTL_ISEL_HI BIT(2) +#define LNA_CTL_BUF_IN BIT(3) +#define LNA_CTL_FEM_BAND BIT(4) +#define LNA_CTL_LOCAL_BIAS BIT(5) +#define LNA_CTL_FORCE_XPA BIT(6) +#define LNA_CTL_USE_ANT1 BIT(7) + +enum eeprom_param { + EEP_NFTHRESH_5, + EEP_NFTHRESH_2, + EEP_MAC_MSW, + EEP_MAC_MID, + EEP_MAC_LSW, + EEP_REG_0, + EEP_OP_CAP, + EEP_OP_MODE, + EEP_RF_SILENT, + EEP_OB_5, + EEP_DB_5, + EEP_OB_2, + EEP_DB_2, + EEP_MINOR_REV, + EEP_TX_MASK, + EEP_RX_MASK, + EEP_FSTCLK_5G, + EEP_RXGAIN_TYPE, + EEP_OL_PWRCTRL, + EEP_TXGAIN_TYPE, + EEP_RC_CHAIN_MASK, + EEP_DAC_HPWR_5G, + EEP_FRAC_N_5G, + EEP_DEV_TYPE, + EEP_TEMPSENSE_SLOPE, + EEP_TEMPSENSE_SLOPE_PAL_ON, + EEP_PWR_TABLE_OFFSET, + EEP_PAPRD, + EEP_MODAL_VER, + EEP_ANT_DIV_CTL1, + EEP_CHAIN_MASK_REDUCE, + EEP_ANTENNA_GAIN_2G, + EEP_ANTENNA_GAIN_5G, +}; + +enum ar5416_rates { + rate6mb, rate9mb, rate12mb, rate18mb, + rate24mb, rate36mb, rate48mb, rate54mb, + rate1l, rate2l, rate2s, rate5_5l, + rate5_5s, rate11l, rate11s, rateXr, + rateHt20_0, rateHt20_1, rateHt20_2, rateHt20_3, + rateHt20_4, rateHt20_5, rateHt20_6, rateHt20_7, + rateHt40_0, rateHt40_1, rateHt40_2, rateHt40_3, + rateHt40_4, rateHt40_5, rateHt40_6, rateHt40_7, + rateDupCck, rateDupOfdm, rateExtCck, rateExtOfdm, + Ar5416RateSize +}; + +enum ath9k_hal_freq_band { + ATH9K_HAL_FREQ_BAND_5GHZ = 0, + ATH9K_HAL_FREQ_BAND_2GHZ = 1 +}; + +struct base_eep_header { + u16 length; + u16 checksum; + u16 version; + u8 opCapFlags; + u8 eepMisc; + u16 regDmn[2]; + u8 macAddr[6]; + u8 rxMask; + u8 txMask; + u16 rfSilent; + u16 blueToothOptions; + u16 deviceCap; + u32 binBuildNumber; + u8 deviceType; + u8 pwdclkind; + u8 fastClk5g; + u8 divChain; + u8 rxGainType; + u8 dacHiPwrMode_5G; + u8 openLoopPwrCntl; + u8 dacLpMode; + u8 txGainType; + u8 rcChainMask; + u8 desiredScaleCCK; + u8 pwr_table_offset; + u8 frac_n_5g; + u8 futureBase_3[21]; +} __packed; + +struct base_eep_header_4k { + u16 length; + u16 checksum; + u16 version; + u8 opCapFlags; + u8 eepMisc; + u16 regDmn[2]; + u8 macAddr[6]; + u8 rxMask; + u8 txMask; + u16 rfSilent; + u16 blueToothOptions; + u16 deviceCap; + u32 binBuildNumber; + u8 deviceType; + u8 txGainType; +} __packed; + + +struct spur_chan { + u16 spurChan; + u8 spurRangeLow; + u8 spurRangeHigh; +} __packed; + +struct modal_eep_header { + u32 antCtrlChain[AR5416_MAX_CHAINS]; + u32 antCtrlCommon; + u8 antennaGainCh[AR5416_MAX_CHAINS]; + u8 switchSettling; + u8 txRxAttenCh[AR5416_MAX_CHAINS]; + u8 rxTxMarginCh[AR5416_MAX_CHAINS]; + u8 adcDesiredSize; + u8 pgaDesiredSize; + u8 xlnaGainCh[AR5416_MAX_CHAINS]; + u8 txEndToXpaOff; + u8 txEndToRxOn; + u8 txFrameToXpaOn; + u8 thresh62; + u8 noiseFloorThreshCh[AR5416_MAX_CHAINS]; + u8 xpdGain; + u8 xpd; + u8 iqCalICh[AR5416_MAX_CHAINS]; + u8 iqCalQCh[AR5416_MAX_CHAINS]; + u8 pdGainOverlap; + u8 ob; + u8 db; + u8 xpaBiasLvl; + u8 pwrDecreaseFor2Chain; + u8 pwrDecreaseFor3Chain; + u8 txFrameToDataStart; + u8 txFrameToPaOn; + u8 ht40PowerIncForPdadc; + u8 bswAtten[AR5416_MAX_CHAINS]; + u8 bswMargin[AR5416_MAX_CHAINS]; + u8 swSettleHt40; + u8 xatten2Db[AR5416_MAX_CHAINS]; + u8 xatten2Margin[AR5416_MAX_CHAINS]; + u8 ob_ch1; + u8 db_ch1; + u8 lna_ctl; + u8 miscBits; + u16 xpaBiasLvlFreq[3]; + u8 futureModal[6]; + + struct spur_chan spurChans[AR_EEPROM_MODAL_SPURS]; +} __packed; + +struct calDataPerFreqOpLoop { + u8 pwrPdg[2][5]; + u8 vpdPdg[2][5]; + u8 pcdac[2][5]; + u8 empty[2][5]; +} __packed; + +struct modal_eep_4k_header { + u32 antCtrlChain[AR5416_EEP4K_MAX_CHAINS]; + u32 antCtrlCommon; + u8 antennaGainCh[AR5416_EEP4K_MAX_CHAINS]; + u8 switchSettling; + u8 txRxAttenCh[AR5416_EEP4K_MAX_CHAINS]; + u8 rxTxMarginCh[AR5416_EEP4K_MAX_CHAINS]; + u8 adcDesiredSize; + u8 pgaDesiredSize; + u8 xlnaGainCh[AR5416_EEP4K_MAX_CHAINS]; + u8 txEndToXpaOff; + u8 txEndToRxOn; + u8 txFrameToXpaOn; + u8 thresh62; + u8 noiseFloorThreshCh[AR5416_EEP4K_MAX_CHAINS]; + u8 xpdGain; + u8 xpd; + u8 iqCalICh[AR5416_EEP4K_MAX_CHAINS]; + u8 iqCalQCh[AR5416_EEP4K_MAX_CHAINS]; + u8 pdGainOverlap; +#ifdef __BIG_ENDIAN_BITFIELD + u8 ob_1:4, ob_0:4; + u8 db1_1:4, db1_0:4; +#else + u8 ob_0:4, ob_1:4; + u8 db1_0:4, db1_1:4; +#endif + u8 xpaBiasLvl; + u8 txFrameToDataStart; + u8 txFrameToPaOn; + u8 ht40PowerIncForPdadc; + u8 bswAtten[AR5416_EEP4K_MAX_CHAINS]; + u8 bswMargin[AR5416_EEP4K_MAX_CHAINS]; + u8 swSettleHt40; + u8 xatten2Db[AR5416_EEP4K_MAX_CHAINS]; + u8 xatten2Margin[AR5416_EEP4K_MAX_CHAINS]; +#ifdef __BIG_ENDIAN_BITFIELD + u8 db2_1:4, db2_0:4; +#else + u8 db2_0:4, db2_1:4; +#endif + u8 version; +#ifdef __BIG_ENDIAN_BITFIELD + u8 ob_3:4, ob_2:4; + u8 antdiv_ctl1:4, ob_4:4; + u8 db1_3:4, db1_2:4; + u8 antdiv_ctl2:4, db1_4:4; + u8 db2_2:4, db2_3:4; + u8 reserved:4, db2_4:4; +#else + u8 ob_2:4, ob_3:4; + u8 ob_4:4, antdiv_ctl1:4; + u8 db1_2:4, db1_3:4; + u8 db1_4:4, antdiv_ctl2:4; + u8 db2_2:4, db2_3:4; + u8 db2_4:4, reserved:4; +#endif + u8 tx_diversity; + u8 flc_pwr_thresh; + u8 bb_scale_smrt_antenna; +#define EEP_4K_BB_DESIRED_SCALE_MASK 0x1f + u8 futureModal[1]; + struct spur_chan spurChans[AR_EEPROM_MODAL_SPURS]; +} __packed; + +struct base_eep_ar9287_header { + u16 length; + u16 checksum; + u16 version; + u8 opCapFlags; + u8 eepMisc; + u16 regDmn[2]; + u8 macAddr[6]; + u8 rxMask; + u8 txMask; + u16 rfSilent; + u16 blueToothOptions; + u16 deviceCap; + u32 binBuildNumber; + u8 deviceType; + u8 openLoopPwrCntl; + int8_t pwrTableOffset; + int8_t tempSensSlope; + int8_t tempSensSlopePalOn; + u8 futureBase[29]; +} __packed; + +struct modal_eep_ar9287_header { + u32 antCtrlChain[AR9287_MAX_CHAINS]; + u32 antCtrlCommon; + int8_t antennaGainCh[AR9287_MAX_CHAINS]; + u8 switchSettling; + u8 txRxAttenCh[AR9287_MAX_CHAINS]; + u8 rxTxMarginCh[AR9287_MAX_CHAINS]; + int8_t adcDesiredSize; + u8 txEndToXpaOff; + u8 txEndToRxOn; + u8 txFrameToXpaOn; + u8 thresh62; + int8_t noiseFloorThreshCh[AR9287_MAX_CHAINS]; + u8 xpdGain; + u8 xpd; + int8_t iqCalICh[AR9287_MAX_CHAINS]; + int8_t iqCalQCh[AR9287_MAX_CHAINS]; + u8 pdGainOverlap; + u8 xpaBiasLvl; + u8 txFrameToDataStart; + u8 txFrameToPaOn; + u8 ht40PowerIncForPdadc; + u8 bswAtten[AR9287_MAX_CHAINS]; + u8 bswMargin[AR9287_MAX_CHAINS]; + u8 swSettleHt40; + u8 version; + u8 db1; + u8 db2; + u8 ob_cck; + u8 ob_psk; + u8 ob_qam; + u8 ob_pal_off; + u8 futureModal[30]; + struct spur_chan spurChans[AR_EEPROM_MODAL_SPURS]; +} __packed; + +struct cal_data_per_freq { + u8 pwrPdg[AR5416_NUM_PD_GAINS][AR5416_PD_GAIN_ICEPTS]; + u8 vpdPdg[AR5416_NUM_PD_GAINS][AR5416_PD_GAIN_ICEPTS]; +} __packed; + +struct cal_data_per_freq_4k { + u8 pwrPdg[AR5416_EEP4K_NUM_PD_GAINS][AR5416_PD_GAIN_ICEPTS]; + u8 vpdPdg[AR5416_EEP4K_NUM_PD_GAINS][AR5416_PD_GAIN_ICEPTS]; +} __packed; + +struct cal_target_power_leg { + u8 bChannel; + u8 tPow2x[4]; +} __packed; + +struct cal_target_power_ht { + u8 bChannel; + u8 tPow2x[8]; +} __packed; + +struct cal_ctl_edges { + u8 bChannel; + u8 ctl; +} __packed; + +struct cal_data_op_loop_ar9287 { + u8 pwrPdg[2][5]; + u8 vpdPdg[2][5]; + u8 pcdac[2][5]; + u8 empty[2][5]; +} __packed; + +struct cal_data_per_freq_ar9287 { + u8 pwrPdg[AR5416_NUM_PD_GAINS][AR9287_PD_GAIN_ICEPTS]; + u8 vpdPdg[AR5416_NUM_PD_GAINS][AR9287_PD_GAIN_ICEPTS]; +} __packed; + +union cal_data_per_freq_ar9287_u { + struct cal_data_op_loop_ar9287 calDataOpen; + struct cal_data_per_freq_ar9287 calDataClose; +} __packed; + +struct cal_ctl_data_ar9287 { + struct cal_ctl_edges + ctlEdges[AR9287_MAX_CHAINS][AR9287_NUM_BAND_EDGES]; +} __packed; + +struct cal_ctl_data { + struct cal_ctl_edges + ctlEdges[AR5416_MAX_CHAINS][AR5416_NUM_BAND_EDGES]; +} __packed; + +struct cal_ctl_data_4k { + struct cal_ctl_edges + ctlEdges[AR5416_EEP4K_MAX_CHAINS][AR5416_EEP4K_NUM_BAND_EDGES]; +} __packed; + +struct ar5416_eeprom_def { + struct base_eep_header baseEepHeader; + u8 custData[64]; + struct modal_eep_header modalHeader[2]; + u8 calFreqPier5G[AR5416_NUM_5G_CAL_PIERS]; + u8 calFreqPier2G[AR5416_NUM_2G_CAL_PIERS]; + struct cal_data_per_freq + calPierData5G[AR5416_MAX_CHAINS][AR5416_NUM_5G_CAL_PIERS]; + struct cal_data_per_freq + calPierData2G[AR5416_MAX_CHAINS][AR5416_NUM_2G_CAL_PIERS]; + struct cal_target_power_leg + calTargetPower5G[AR5416_NUM_5G_20_TARGET_POWERS]; + struct cal_target_power_ht + calTargetPower5GHT20[AR5416_NUM_5G_20_TARGET_POWERS]; + struct cal_target_power_ht + calTargetPower5GHT40[AR5416_NUM_5G_40_TARGET_POWERS]; + struct cal_target_power_leg + calTargetPowerCck[AR5416_NUM_2G_CCK_TARGET_POWERS]; + struct cal_target_power_leg + calTargetPower2G[AR5416_NUM_2G_20_TARGET_POWERS]; + struct cal_target_power_ht + calTargetPower2GHT20[AR5416_NUM_2G_20_TARGET_POWERS]; + struct cal_target_power_ht + calTargetPower2GHT40[AR5416_NUM_2G_40_TARGET_POWERS]; + u8 ctlIndex[AR5416_NUM_CTLS]; + struct cal_ctl_data ctlData[AR5416_NUM_CTLS]; + u8 padding; +} __packed; + +struct ar5416_eeprom_4k { + struct base_eep_header_4k baseEepHeader; + u8 custData[20]; + struct modal_eep_4k_header modalHeader; + u8 calFreqPier2G[AR5416_EEP4K_NUM_2G_CAL_PIERS]; + struct cal_data_per_freq_4k + calPierData2G[AR5416_EEP4K_MAX_CHAINS][AR5416_EEP4K_NUM_2G_CAL_PIERS]; + struct cal_target_power_leg + calTargetPowerCck[AR5416_EEP4K_NUM_2G_CCK_TARGET_POWERS]; + struct cal_target_power_leg + calTargetPower2G[AR5416_EEP4K_NUM_2G_20_TARGET_POWERS]; + struct cal_target_power_ht + calTargetPower2GHT20[AR5416_EEP4K_NUM_2G_20_TARGET_POWERS]; + struct cal_target_power_ht + calTargetPower2GHT40[AR5416_EEP4K_NUM_2G_40_TARGET_POWERS]; + u8 ctlIndex[AR5416_EEP4K_NUM_CTLS]; + struct cal_ctl_data_4k ctlData[AR5416_EEP4K_NUM_CTLS]; + u8 padding; +} __packed; + +struct ar9287_eeprom { + struct base_eep_ar9287_header baseEepHeader; + u8 custData[AR9287_DATA_SZ]; + struct modal_eep_ar9287_header modalHeader; + u8 calFreqPier2G[AR9287_NUM_2G_CAL_PIERS]; + union cal_data_per_freq_ar9287_u + calPierData2G[AR9287_MAX_CHAINS][AR9287_NUM_2G_CAL_PIERS]; + struct cal_target_power_leg + calTargetPowerCck[AR9287_NUM_2G_CCK_TARGET_POWERS]; + struct cal_target_power_leg + calTargetPower2G[AR9287_NUM_2G_20_TARGET_POWERS]; + struct cal_target_power_ht + calTargetPower2GHT20[AR9287_NUM_2G_20_TARGET_POWERS]; + struct cal_target_power_ht + calTargetPower2GHT40[AR9287_NUM_2G_40_TARGET_POWERS]; + u8 ctlIndex[AR9287_NUM_CTLS]; + struct cal_ctl_data_ar9287 ctlData[AR9287_NUM_CTLS]; + u8 padding; +} __packed; + +enum reg_ext_bitmap { + REG_EXT_FCC_MIDBAND = 0, + REG_EXT_JAPAN_MIDBAND = 1, + REG_EXT_FCC_DFS_HT40 = 2, + REG_EXT_JAPAN_NONDFS_HT40 = 3, + REG_EXT_JAPAN_DFS_HT40 = 4 +}; + +struct ath9k_country_entry { + u16 countryCode; + u16 regDmnEnum; + u16 regDmn5G; + u16 regDmn2G; + u8 isMultidomain; + u8 iso[3]; +}; + +struct eeprom_ops { + int (*check_eeprom)(struct ath_hw *hw); + u32 (*get_eeprom)(struct ath_hw *hw, enum eeprom_param param); + bool (*fill_eeprom)(struct ath_hw *hw); + u32 (*dump_eeprom)(struct ath_hw *hw, bool dump_base_hdr, u8 *buf, + u32 len, u32 size); + int (*get_eeprom_ver)(struct ath_hw *hw); + int (*get_eeprom_rev)(struct ath_hw *hw); + void (*set_board_values)(struct ath_hw *hw, struct ath9k_channel *chan); + void (*set_addac)(struct ath_hw *hw, struct ath9k_channel *chan); + void (*set_txpower)(struct ath_hw *hw, struct ath9k_channel *chan, + u16 cfgCtl, u8 twiceAntennaReduction, + u8 powerLimit, bool test); + u16 (*get_spur_channel)(struct ath_hw *ah, u16 i, bool is2GHz); +}; + +void ath9k_hw_analog_shift_regwrite(struct ath_hw *ah, u32 reg, u32 val); +void ath9k_hw_analog_shift_rmw(struct ath_hw *ah, u32 reg, u32 mask, + u32 shift, u32 val); +int16_t ath9k_hw_interpolate(u16 target, u16 srcLeft, u16 srcRight, + int16_t targetLeft, + int16_t targetRight); +bool ath9k_hw_get_lower_upper_index(u8 target, u8 *pList, u16 listSize, + u16 *indexL, u16 *indexR); +bool ath9k_hw_nvram_read(struct ath_hw *ah, u32 off, u16 *data); +void ath9k_hw_usb_gen_fill_eeprom(struct ath_hw *ah, u16 *eep_data, + int eep_start_loc, int size); +void ath9k_hw_fill_vpd_table(u8 pwrMin, u8 pwrMax, u8 *pPwrList, + u8 *pVpdList, u16 numIntercepts, + u8 *pRetVpdList); +void ath9k_hw_get_legacy_target_powers(struct ath_hw *ah, + struct ath9k_channel *chan, + struct cal_target_power_leg *powInfo, + u16 numChannels, + struct cal_target_power_leg *pNewPower, + u16 numRates, bool isExtTarget); +void ath9k_hw_get_target_powers(struct ath_hw *ah, + struct ath9k_channel *chan, + struct cal_target_power_ht *powInfo, + u16 numChannels, + struct cal_target_power_ht *pNewPower, + u16 numRates, bool isHt40Target); +u16 ath9k_hw_get_max_edge_power(u16 freq, struct cal_ctl_edges *pRdEdgesPower, + bool is2GHz, int num_band_edges); +u16 ath9k_hw_get_scaled_power(struct ath_hw *ah, u16 power_limit, + u8 antenna_reduction); +void ath9k_hw_update_regulatory_maxpower(struct ath_hw *ah); +int ath9k_hw_eeprom_init(struct ath_hw *ah); + +void ath9k_hw_get_gain_boundaries_pdadcs(struct ath_hw *ah, + struct ath9k_channel *chan, + void *pRawDataSet, + u8 *bChans, u16 availPiers, + u16 tPdGainOverlap, + u16 *pPdGainBoundaries, u8 *pPDADCValues, + u16 numXpdGains); + +static inline u16 ath9k_hw_fbin2freq(u8 fbin, bool is2GHz) +{ + if (fbin == AR5416_BCHAN_UNUSED) + return fbin; + + return (u16) ((is2GHz) ? (2300 + fbin) : (4800 + 5 * fbin)); +} + +#define ar5416_get_ntxchains(_txchainmask) \ + (((_txchainmask >> 2) & 1) + \ + ((_txchainmask >> 1) & 1) + (_txchainmask & 1)) + +extern const struct eeprom_ops eep_def_ops; +extern const struct eeprom_ops eep_4k_ops; +extern const struct eeprom_ops eep_ar9287_ops; +extern const struct eeprom_ops eep_ar9287_ops; +extern const struct eeprom_ops eep_ar9300_ops; + +#endif /* EEPROM_H */ diff --git a/drivers/net/wireless/ath/ath9k/eeprom_4k.c b/drivers/net/wireless/ath/ath9k/eeprom_4k.c new file mode 100644 index 0000000..a7a5544 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/eeprom_4k.c @@ -0,0 +1,1106 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "hw.h" +#include "ar9002_phy.h" + +static int ath9k_hw_4k_get_eeprom_ver(struct ath_hw *ah) +{ + return ((ah->eeprom.map4k.baseEepHeader.version >> 12) & 0xF); +} + +static int ath9k_hw_4k_get_eeprom_rev(struct ath_hw *ah) +{ + return ((ah->eeprom.map4k.baseEepHeader.version) & 0xFFF); +} + +#define SIZE_EEPROM_4K (sizeof(struct ar5416_eeprom_4k) / sizeof(u16)) + +static bool __ath9k_hw_4k_fill_eeprom(struct ath_hw *ah) +{ + u16 *eep_data = (u16 *)&ah->eeprom.map4k; + int addr, eep_start_loc = 64; + + for (addr = 0; addr < SIZE_EEPROM_4K; addr++) { + if (!ath9k_hw_nvram_read(ah, addr + eep_start_loc, eep_data)) + return false; + eep_data++; + } + + return true; +} + +static bool __ath9k_hw_usb_4k_fill_eeprom(struct ath_hw *ah) +{ + u16 *eep_data = (u16 *)&ah->eeprom.map4k; + + ath9k_hw_usb_gen_fill_eeprom(ah, eep_data, 64, SIZE_EEPROM_4K); + + return true; +} + +static bool ath9k_hw_4k_fill_eeprom(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + + if (!ath9k_hw_use_flash(ah)) { + ath_dbg(common, EEPROM, "Reading from EEPROM, not flash\n"); + } + + if (common->bus_ops->ath_bus_type == ATH_USB) + return __ath9k_hw_usb_4k_fill_eeprom(ah); + else + return __ath9k_hw_4k_fill_eeprom(ah); +} + +#if defined(CPTCFG_ATH9K_DEBUGFS) || defined(CPTCFG_ATH9K_HTC_DEBUGFS) +static u32 ath9k_dump_4k_modal_eeprom(char *buf, u32 len, u32 size, + struct modal_eep_4k_header *modal_hdr) +{ + PR_EEP("Chain0 Ant. Control", modal_hdr->antCtrlChain[0]); + PR_EEP("Ant. Common Control", modal_hdr->antCtrlCommon); + PR_EEP("Chain0 Ant. Gain", modal_hdr->antennaGainCh[0]); + PR_EEP("Switch Settle", modal_hdr->switchSettling); + PR_EEP("Chain0 TxRxAtten", modal_hdr->txRxAttenCh[0]); + PR_EEP("Chain0 RxTxMargin", modal_hdr->rxTxMarginCh[0]); + PR_EEP("ADC Desired size", modal_hdr->adcDesiredSize); + PR_EEP("PGA Desired size", modal_hdr->pgaDesiredSize); + PR_EEP("Chain0 xlna Gain", modal_hdr->xlnaGainCh[0]); + PR_EEP("txEndToXpaOff", modal_hdr->txEndToXpaOff); + PR_EEP("txEndToRxOn", modal_hdr->txEndToRxOn); + PR_EEP("txFrameToXpaOn", modal_hdr->txFrameToXpaOn); + PR_EEP("CCA Threshold)", modal_hdr->thresh62); + PR_EEP("Chain0 NF Threshold", modal_hdr->noiseFloorThreshCh[0]); + PR_EEP("xpdGain", modal_hdr->xpdGain); + PR_EEP("External PD", modal_hdr->xpd); + PR_EEP("Chain0 I Coefficient", modal_hdr->iqCalICh[0]); + PR_EEP("Chain0 Q Coefficient", modal_hdr->iqCalQCh[0]); + PR_EEP("pdGainOverlap", modal_hdr->pdGainOverlap); + PR_EEP("O/D Bias Version", modal_hdr->version); + PR_EEP("CCK OutputBias", modal_hdr->ob_0); + PR_EEP("BPSK OutputBias", modal_hdr->ob_1); + PR_EEP("QPSK OutputBias", modal_hdr->ob_2); + PR_EEP("16QAM OutputBias", modal_hdr->ob_3); + PR_EEP("64QAM OutputBias", modal_hdr->ob_4); + PR_EEP("CCK Driver1_Bias", modal_hdr->db1_0); + PR_EEP("BPSK Driver1_Bias", modal_hdr->db1_1); + PR_EEP("QPSK Driver1_Bias", modal_hdr->db1_2); + PR_EEP("16QAM Driver1_Bias", modal_hdr->db1_3); + PR_EEP("64QAM Driver1_Bias", modal_hdr->db1_4); + PR_EEP("CCK Driver2_Bias", modal_hdr->db2_0); + PR_EEP("BPSK Driver2_Bias", modal_hdr->db2_1); + PR_EEP("QPSK Driver2_Bias", modal_hdr->db2_2); + PR_EEP("16QAM Driver2_Bias", modal_hdr->db2_3); + PR_EEP("64QAM Driver2_Bias", modal_hdr->db2_4); + PR_EEP("xPA Bias Level", modal_hdr->xpaBiasLvl); + PR_EEP("txFrameToDataStart", modal_hdr->txFrameToDataStart); + PR_EEP("txFrameToPaOn", modal_hdr->txFrameToPaOn); + PR_EEP("HT40 Power Inc.", modal_hdr->ht40PowerIncForPdadc); + PR_EEP("Chain0 bswAtten", modal_hdr->bswAtten[0]); + PR_EEP("Chain0 bswMargin", modal_hdr->bswMargin[0]); + PR_EEP("HT40 Switch Settle", modal_hdr->swSettleHt40); + PR_EEP("Chain0 xatten2Db", modal_hdr->xatten2Db[0]); + PR_EEP("Chain0 xatten2Margin", modal_hdr->xatten2Margin[0]); + PR_EEP("Ant. Diversity ctl1", modal_hdr->antdiv_ctl1); + PR_EEP("Ant. Diversity ctl2", modal_hdr->antdiv_ctl2); + PR_EEP("TX Diversity", modal_hdr->tx_diversity); + + return len; +} + +static u32 ath9k_hw_4k_dump_eeprom(struct ath_hw *ah, bool dump_base_hdr, + u8 *buf, u32 len, u32 size) +{ + struct ar5416_eeprom_4k *eep = &ah->eeprom.map4k; + struct base_eep_header_4k *pBase = &eep->baseEepHeader; + + if (!dump_base_hdr) { + len += snprintf(buf + len, size - len, + "%20s :\n", "2GHz modal Header"); + len = ath9k_dump_4k_modal_eeprom(buf, len, size, + &eep->modalHeader); + goto out; + } + + PR_EEP("Major Version", pBase->version >> 12); + PR_EEP("Minor Version", pBase->version & 0xFFF); + PR_EEP("Checksum", pBase->checksum); + PR_EEP("Length", pBase->length); + PR_EEP("RegDomain1", pBase->regDmn[0]); + PR_EEP("RegDomain2", pBase->regDmn[1]); + PR_EEP("TX Mask", pBase->txMask); + PR_EEP("RX Mask", pBase->rxMask); + PR_EEP("Allow 5GHz", !!(pBase->opCapFlags & AR5416_OPFLAGS_11A)); + PR_EEP("Allow 2GHz", !!(pBase->opCapFlags & AR5416_OPFLAGS_11G)); + PR_EEP("Disable 2GHz HT20", !!(pBase->opCapFlags & + AR5416_OPFLAGS_N_2G_HT20)); + PR_EEP("Disable 2GHz HT40", !!(pBase->opCapFlags & + AR5416_OPFLAGS_N_2G_HT40)); + PR_EEP("Disable 5Ghz HT20", !!(pBase->opCapFlags & + AR5416_OPFLAGS_N_5G_HT20)); + PR_EEP("Disable 5Ghz HT40", !!(pBase->opCapFlags & + AR5416_OPFLAGS_N_5G_HT40)); + PR_EEP("Big Endian", !!(pBase->eepMisc & 0x01)); + PR_EEP("Cal Bin Major Ver", (pBase->binBuildNumber >> 24) & 0xFF); + PR_EEP("Cal Bin Minor Ver", (pBase->binBuildNumber >> 16) & 0xFF); + PR_EEP("Cal Bin Build", (pBase->binBuildNumber >> 8) & 0xFF); + PR_EEP("TX Gain type", pBase->txGainType); + + len += snprintf(buf + len, size - len, "%20s : %pM\n", "MacAddress", + pBase->macAddr); + +out: + if (len > size) + len = size; + + return len; +} +#else +static u32 ath9k_hw_4k_dump_eeprom(struct ath_hw *ah, bool dump_base_hdr, + u8 *buf, u32 len, u32 size) +{ + return 0; +} +#endif + + +#undef SIZE_EEPROM_4K + +static int ath9k_hw_4k_check_eeprom(struct ath_hw *ah) +{ +#define EEPROM_4K_SIZE (sizeof(struct ar5416_eeprom_4k) / sizeof(u16)) + struct ath_common *common = ath9k_hw_common(ah); + struct ar5416_eeprom_4k *eep = &ah->eeprom.map4k; + u16 *eepdata, temp, magic, magic2; + u32 sum = 0, el; + bool need_swap = false; + int i, addr; + + + if (!ath9k_hw_use_flash(ah)) { + if (!ath9k_hw_nvram_read(ah, AR5416_EEPROM_MAGIC_OFFSET, + &magic)) { + ath_err(common, "Reading Magic # failed\n"); + return false; + } + + ath_dbg(common, EEPROM, "Read Magic = 0x%04X\n", magic); + + if (magic != AR5416_EEPROM_MAGIC) { + magic2 = swab16(magic); + + if (magic2 == AR5416_EEPROM_MAGIC) { + need_swap = true; + eepdata = (u16 *) (&ah->eeprom); + + for (addr = 0; addr < EEPROM_4K_SIZE; addr++) { + temp = swab16(*eepdata); + *eepdata = temp; + eepdata++; + } + } else { + ath_err(common, + "Invalid EEPROM Magic. Endianness mismatch.\n"); + return -EINVAL; + } + } + } + + ath_dbg(common, EEPROM, "need_swap = %s\n", + need_swap ? "True" : "False"); + + if (need_swap) + el = swab16(ah->eeprom.map4k.baseEepHeader.length); + else + el = ah->eeprom.map4k.baseEepHeader.length; + + if (el > sizeof(struct ar5416_eeprom_4k)) + el = sizeof(struct ar5416_eeprom_4k) / sizeof(u16); + else + el = el / sizeof(u16); + + eepdata = (u16 *)(&ah->eeprom); + + for (i = 0; i < el; i++) + sum ^= *eepdata++; + + if (need_swap) { + u32 integer; + u16 word; + + ath_dbg(common, EEPROM, + "EEPROM Endianness is not native.. Changing\n"); + + word = swab16(eep->baseEepHeader.length); + eep->baseEepHeader.length = word; + + word = swab16(eep->baseEepHeader.checksum); + eep->baseEepHeader.checksum = word; + + word = swab16(eep->baseEepHeader.version); + eep->baseEepHeader.version = word; + + word = swab16(eep->baseEepHeader.regDmn[0]); + eep->baseEepHeader.regDmn[0] = word; + + word = swab16(eep->baseEepHeader.regDmn[1]); + eep->baseEepHeader.regDmn[1] = word; + + word = swab16(eep->baseEepHeader.rfSilent); + eep->baseEepHeader.rfSilent = word; + + word = swab16(eep->baseEepHeader.blueToothOptions); + eep->baseEepHeader.blueToothOptions = word; + + word = swab16(eep->baseEepHeader.deviceCap); + eep->baseEepHeader.deviceCap = word; + + integer = swab32(eep->modalHeader.antCtrlCommon); + eep->modalHeader.antCtrlCommon = integer; + + for (i = 0; i < AR5416_EEP4K_MAX_CHAINS; i++) { + integer = swab32(eep->modalHeader.antCtrlChain[i]); + eep->modalHeader.antCtrlChain[i] = integer; + } + + for (i = 0; i < AR_EEPROM_MODAL_SPURS; i++) { + word = swab16(eep->modalHeader.spurChans[i].spurChan); + eep->modalHeader.spurChans[i].spurChan = word; + } + } + + if (sum != 0xffff || ah->eep_ops->get_eeprom_ver(ah) != AR5416_EEP_VER || + ah->eep_ops->get_eeprom_rev(ah) < AR5416_EEP_NO_BACK_VER) { + ath_err(common, "Bad EEPROM checksum 0x%x or revision 0x%04x\n", + sum, ah->eep_ops->get_eeprom_ver(ah)); + return -EINVAL; + } + + return 0; +#undef EEPROM_4K_SIZE +} + +static u32 ath9k_hw_4k_get_eeprom(struct ath_hw *ah, + enum eeprom_param param) +{ + struct ar5416_eeprom_4k *eep = &ah->eeprom.map4k; + struct modal_eep_4k_header *pModal = &eep->modalHeader; + struct base_eep_header_4k *pBase = &eep->baseEepHeader; + u16 ver_minor; + + ver_minor = pBase->version & AR5416_EEP_VER_MINOR_MASK; + + switch (param) { + case EEP_NFTHRESH_2: + return pModal->noiseFloorThreshCh[0]; + case EEP_MAC_LSW: + return get_unaligned_be16(pBase->macAddr); + case EEP_MAC_MID: + return get_unaligned_be16(pBase->macAddr + 2); + case EEP_MAC_MSW: + return get_unaligned_be16(pBase->macAddr + 4); + case EEP_REG_0: + return pBase->regDmn[0]; + case EEP_OP_CAP: + return pBase->deviceCap; + case EEP_OP_MODE: + return pBase->opCapFlags; + case EEP_RF_SILENT: + return pBase->rfSilent; + case EEP_OB_2: + return pModal->ob_0; + case EEP_DB_2: + return pModal->db1_1; + case EEP_MINOR_REV: + return ver_minor; + case EEP_TX_MASK: + return pBase->txMask; + case EEP_RX_MASK: + return pBase->rxMask; + case EEP_FRAC_N_5G: + return 0; + case EEP_PWR_TABLE_OFFSET: + return AR5416_PWR_TABLE_OFFSET_DB; + case EEP_MODAL_VER: + return pModal->version; + case EEP_ANT_DIV_CTL1: + return pModal->antdiv_ctl1; + case EEP_TXGAIN_TYPE: + return pBase->txGainType; + case EEP_ANTENNA_GAIN_2G: + return pModal->antennaGainCh[0]; + default: + return 0; + } +} + +static void ath9k_hw_set_4k_power_cal_table(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ar5416_eeprom_4k *pEepData = &ah->eeprom.map4k; + struct cal_data_per_freq_4k *pRawDataset; + u8 *pCalBChans = NULL; + u16 pdGainOverlap_t2; + static u8 pdadcValues[AR5416_NUM_PDADC_VALUES]; + u16 gainBoundaries[AR5416_PD_GAINS_IN_MASK]; + u16 numPiers, i, j; + u16 numXpdGain, xpdMask; + u16 xpdGainValues[AR5416_EEP4K_NUM_PD_GAINS] = { 0, 0 }; + u32 reg32, regOffset, regChainOffset; + + xpdMask = pEepData->modalHeader.xpdGain; + + if ((pEepData->baseEepHeader.version & AR5416_EEP_VER_MINOR_MASK) >= + AR5416_EEP_MINOR_VER_2) { + pdGainOverlap_t2 = + pEepData->modalHeader.pdGainOverlap; + } else { + pdGainOverlap_t2 = (u16)(MS(REG_READ(ah, AR_PHY_TPCRG5), + AR_PHY_TPCRG5_PD_GAIN_OVERLAP)); + } + + pCalBChans = pEepData->calFreqPier2G; + numPiers = AR5416_EEP4K_NUM_2G_CAL_PIERS; + + numXpdGain = 0; + + for (i = 1; i <= AR5416_PD_GAINS_IN_MASK; i++) { + if ((xpdMask >> (AR5416_PD_GAINS_IN_MASK - i)) & 1) { + if (numXpdGain >= AR5416_EEP4K_NUM_PD_GAINS) + break; + xpdGainValues[numXpdGain] = + (u16)(AR5416_PD_GAINS_IN_MASK - i); + numXpdGain++; + } + } + + REG_RMW_FIELD(ah, AR_PHY_TPCRG1, AR_PHY_TPCRG1_NUM_PD_GAIN, + (numXpdGain - 1) & 0x3); + REG_RMW_FIELD(ah, AR_PHY_TPCRG1, AR_PHY_TPCRG1_PD_GAIN_1, + xpdGainValues[0]); + REG_RMW_FIELD(ah, AR_PHY_TPCRG1, AR_PHY_TPCRG1_PD_GAIN_2, + xpdGainValues[1]); + REG_RMW_FIELD(ah, AR_PHY_TPCRG1, AR_PHY_TPCRG1_PD_GAIN_3, 0); + + for (i = 0; i < AR5416_EEP4K_MAX_CHAINS; i++) { + regChainOffset = i * 0x1000; + + if (pEepData->baseEepHeader.txMask & (1 << i)) { + pRawDataset = pEepData->calPierData2G[i]; + + ath9k_hw_get_gain_boundaries_pdadcs(ah, chan, + pRawDataset, pCalBChans, + numPiers, pdGainOverlap_t2, + gainBoundaries, + pdadcValues, numXpdGain); + + ENABLE_REGWRITE_BUFFER(ah); + + REG_WRITE(ah, AR_PHY_TPCRG5 + regChainOffset, + SM(pdGainOverlap_t2, + AR_PHY_TPCRG5_PD_GAIN_OVERLAP) + | SM(gainBoundaries[0], + AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_1) + | SM(gainBoundaries[1], + AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_2) + | SM(gainBoundaries[2], + AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_3) + | SM(gainBoundaries[3], + AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_4)); + + regOffset = AR_PHY_BASE + (672 << 2) + regChainOffset; + for (j = 0; j < 32; j++) { + reg32 = get_unaligned_le32(&pdadcValues[4 * j]); + REG_WRITE(ah, regOffset, reg32); + + ath_dbg(common, EEPROM, + "PDADC (%d,%4x): %4.4x %8.8x\n", + i, regChainOffset, regOffset, + reg32); + ath_dbg(common, EEPROM, + "PDADC: Chain %d | " + "PDADC %3d Value %3d | " + "PDADC %3d Value %3d | " + "PDADC %3d Value %3d | " + "PDADC %3d Value %3d |\n", + i, 4 * j, pdadcValues[4 * j], + 4 * j + 1, pdadcValues[4 * j + 1], + 4 * j + 2, pdadcValues[4 * j + 2], + 4 * j + 3, pdadcValues[4 * j + 3]); + + regOffset += 4; + } + + REGWRITE_BUFFER_FLUSH(ah); + } + } +} + +static void ath9k_hw_set_4k_power_per_rate_table(struct ath_hw *ah, + struct ath9k_channel *chan, + int16_t *ratesArray, + u16 cfgCtl, + u16 antenna_reduction, + u16 powerLimit) +{ +#define CMP_TEST_GRP \ + (((cfgCtl & ~CTL_MODE_M)| (pCtlMode[ctlMode] & CTL_MODE_M)) == \ + pEepData->ctlIndex[i]) \ + || (((cfgCtl & ~CTL_MODE_M) | (pCtlMode[ctlMode] & CTL_MODE_M)) == \ + ((pEepData->ctlIndex[i] & CTL_MODE_M) | SD_NO_CTL)) + + int i; + u16 twiceMinEdgePower; + u16 twiceMaxEdgePower; + u16 scaledPower = 0, minCtlPower; + u16 numCtlModes; + const u16 *pCtlMode; + u16 ctlMode, freq; + struct chan_centers centers; + struct cal_ctl_data_4k *rep; + struct ar5416_eeprom_4k *pEepData = &ah->eeprom.map4k; + struct cal_target_power_leg targetPowerOfdm, targetPowerCck = { + 0, { 0, 0, 0, 0} + }; + struct cal_target_power_leg targetPowerOfdmExt = { + 0, { 0, 0, 0, 0} }, targetPowerCckExt = { + 0, { 0, 0, 0, 0 } + }; + struct cal_target_power_ht targetPowerHt20, targetPowerHt40 = { + 0, {0, 0, 0, 0} + }; + static const u16 ctlModesFor11g[] = { + CTL_11B, CTL_11G, CTL_2GHT20, + CTL_11B_EXT, CTL_11G_EXT, CTL_2GHT40 + }; + + ath9k_hw_get_channel_centers(ah, chan, ¢ers); + + scaledPower = powerLimit - antenna_reduction; + numCtlModes = ARRAY_SIZE(ctlModesFor11g) - SUB_NUM_CTL_MODES_AT_2G_40; + pCtlMode = ctlModesFor11g; + + ath9k_hw_get_legacy_target_powers(ah, chan, + pEepData->calTargetPowerCck, + AR5416_NUM_2G_CCK_TARGET_POWERS, + &targetPowerCck, 4, false); + ath9k_hw_get_legacy_target_powers(ah, chan, + pEepData->calTargetPower2G, + AR5416_NUM_2G_20_TARGET_POWERS, + &targetPowerOfdm, 4, false); + ath9k_hw_get_target_powers(ah, chan, + pEepData->calTargetPower2GHT20, + AR5416_NUM_2G_20_TARGET_POWERS, + &targetPowerHt20, 8, false); + + if (IS_CHAN_HT40(chan)) { + numCtlModes = ARRAY_SIZE(ctlModesFor11g); + ath9k_hw_get_target_powers(ah, chan, + pEepData->calTargetPower2GHT40, + AR5416_NUM_2G_40_TARGET_POWERS, + &targetPowerHt40, 8, true); + ath9k_hw_get_legacy_target_powers(ah, chan, + pEepData->calTargetPowerCck, + AR5416_NUM_2G_CCK_TARGET_POWERS, + &targetPowerCckExt, 4, true); + ath9k_hw_get_legacy_target_powers(ah, chan, + pEepData->calTargetPower2G, + AR5416_NUM_2G_20_TARGET_POWERS, + &targetPowerOfdmExt, 4, true); + } + + for (ctlMode = 0; ctlMode < numCtlModes; ctlMode++) { + bool isHt40CtlMode = (pCtlMode[ctlMode] == CTL_5GHT40) || + (pCtlMode[ctlMode] == CTL_2GHT40); + + if (isHt40CtlMode) + freq = centers.synth_center; + else if (pCtlMode[ctlMode] & EXT_ADDITIVE) + freq = centers.ext_center; + else + freq = centers.ctl_center; + + twiceMaxEdgePower = MAX_RATE_POWER; + + for (i = 0; (i < AR5416_EEP4K_NUM_CTLS) && + pEepData->ctlIndex[i]; i++) { + + if (CMP_TEST_GRP) { + rep = &(pEepData->ctlData[i]); + + twiceMinEdgePower = ath9k_hw_get_max_edge_power( + freq, + rep->ctlEdges[ + ar5416_get_ntxchains(ah->txchainmask) - 1], + IS_CHAN_2GHZ(chan), + AR5416_EEP4K_NUM_BAND_EDGES); + + if ((cfgCtl & ~CTL_MODE_M) == SD_NO_CTL) { + twiceMaxEdgePower = + min(twiceMaxEdgePower, + twiceMinEdgePower); + } else { + twiceMaxEdgePower = twiceMinEdgePower; + break; + } + } + } + + minCtlPower = (u8)min(twiceMaxEdgePower, scaledPower); + + switch (pCtlMode[ctlMode]) { + case CTL_11B: + for (i = 0; i < ARRAY_SIZE(targetPowerCck.tPow2x); i++) { + targetPowerCck.tPow2x[i] = + min((u16)targetPowerCck.tPow2x[i], + minCtlPower); + } + break; + case CTL_11G: + for (i = 0; i < ARRAY_SIZE(targetPowerOfdm.tPow2x); i++) { + targetPowerOfdm.tPow2x[i] = + min((u16)targetPowerOfdm.tPow2x[i], + minCtlPower); + } + break; + case CTL_2GHT20: + for (i = 0; i < ARRAY_SIZE(targetPowerHt20.tPow2x); i++) { + targetPowerHt20.tPow2x[i] = + min((u16)targetPowerHt20.tPow2x[i], + minCtlPower); + } + break; + case CTL_11B_EXT: + targetPowerCckExt.tPow2x[0] = + min((u16)targetPowerCckExt.tPow2x[0], + minCtlPower); + break; + case CTL_11G_EXT: + targetPowerOfdmExt.tPow2x[0] = + min((u16)targetPowerOfdmExt.tPow2x[0], + minCtlPower); + break; + case CTL_2GHT40: + for (i = 0; i < ARRAY_SIZE(targetPowerHt40.tPow2x); i++) { + targetPowerHt40.tPow2x[i] = + min((u16)targetPowerHt40.tPow2x[i], + minCtlPower); + } + break; + default: + break; + } + } + + ratesArray[rate6mb] = + ratesArray[rate9mb] = + ratesArray[rate12mb] = + ratesArray[rate18mb] = + ratesArray[rate24mb] = + targetPowerOfdm.tPow2x[0]; + + ratesArray[rate36mb] = targetPowerOfdm.tPow2x[1]; + ratesArray[rate48mb] = targetPowerOfdm.tPow2x[2]; + ratesArray[rate54mb] = targetPowerOfdm.tPow2x[3]; + ratesArray[rateXr] = targetPowerOfdm.tPow2x[0]; + + for (i = 0; i < ARRAY_SIZE(targetPowerHt20.tPow2x); i++) + ratesArray[rateHt20_0 + i] = targetPowerHt20.tPow2x[i]; + + ratesArray[rate1l] = targetPowerCck.tPow2x[0]; + ratesArray[rate2s] = ratesArray[rate2l] = targetPowerCck.tPow2x[1]; + ratesArray[rate5_5s] = ratesArray[rate5_5l] = targetPowerCck.tPow2x[2]; + ratesArray[rate11s] = ratesArray[rate11l] = targetPowerCck.tPow2x[3]; + + if (IS_CHAN_HT40(chan)) { + for (i = 0; i < ARRAY_SIZE(targetPowerHt40.tPow2x); i++) { + ratesArray[rateHt40_0 + i] = + targetPowerHt40.tPow2x[i]; + } + ratesArray[rateDupOfdm] = targetPowerHt40.tPow2x[0]; + ratesArray[rateDupCck] = targetPowerHt40.tPow2x[0]; + ratesArray[rateExtOfdm] = targetPowerOfdmExt.tPow2x[0]; + ratesArray[rateExtCck] = targetPowerCckExt.tPow2x[0]; + } + +#undef CMP_TEST_GRP +} + +static void ath9k_hw_4k_set_txpower(struct ath_hw *ah, + struct ath9k_channel *chan, + u16 cfgCtl, + u8 twiceAntennaReduction, + u8 powerLimit, bool test) +{ + struct ath_regulatory *regulatory = ath9k_hw_regulatory(ah); + struct ar5416_eeprom_4k *pEepData = &ah->eeprom.map4k; + struct modal_eep_4k_header *pModal = &pEepData->modalHeader; + int16_t ratesArray[Ar5416RateSize]; + u8 ht40PowerIncForPdadc = 2; + int i; + + memset(ratesArray, 0, sizeof(ratesArray)); + + if ((pEepData->baseEepHeader.version & AR5416_EEP_VER_MINOR_MASK) >= + AR5416_EEP_MINOR_VER_2) { + ht40PowerIncForPdadc = pModal->ht40PowerIncForPdadc; + } + + ath9k_hw_set_4k_power_per_rate_table(ah, chan, + &ratesArray[0], cfgCtl, + twiceAntennaReduction, + powerLimit); + + ath9k_hw_set_4k_power_cal_table(ah, chan); + + regulatory->max_power_level = 0; + for (i = 0; i < ARRAY_SIZE(ratesArray); i++) { + if (ratesArray[i] > MAX_RATE_POWER) + ratesArray[i] = MAX_RATE_POWER; + + if (ratesArray[i] > regulatory->max_power_level) + regulatory->max_power_level = ratesArray[i]; + } + + if (test) + return; + + for (i = 0; i < Ar5416RateSize; i++) + ratesArray[i] -= AR5416_PWR_TABLE_OFFSET_DB * 2; + + ENABLE_REGWRITE_BUFFER(ah); + + /* OFDM power per rate */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE1, + ATH9K_POW_SM(ratesArray[rate18mb], 24) + | ATH9K_POW_SM(ratesArray[rate12mb], 16) + | ATH9K_POW_SM(ratesArray[rate9mb], 8) + | ATH9K_POW_SM(ratesArray[rate6mb], 0)); + REG_WRITE(ah, AR_PHY_POWER_TX_RATE2, + ATH9K_POW_SM(ratesArray[rate54mb], 24) + | ATH9K_POW_SM(ratesArray[rate48mb], 16) + | ATH9K_POW_SM(ratesArray[rate36mb], 8) + | ATH9K_POW_SM(ratesArray[rate24mb], 0)); + + /* CCK power per rate */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE3, + ATH9K_POW_SM(ratesArray[rate2s], 24) + | ATH9K_POW_SM(ratesArray[rate2l], 16) + | ATH9K_POW_SM(ratesArray[rateXr], 8) + | ATH9K_POW_SM(ratesArray[rate1l], 0)); + REG_WRITE(ah, AR_PHY_POWER_TX_RATE4, + ATH9K_POW_SM(ratesArray[rate11s], 24) + | ATH9K_POW_SM(ratesArray[rate11l], 16) + | ATH9K_POW_SM(ratesArray[rate5_5s], 8) + | ATH9K_POW_SM(ratesArray[rate5_5l], 0)); + + /* HT20 power per rate */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE5, + ATH9K_POW_SM(ratesArray[rateHt20_3], 24) + | ATH9K_POW_SM(ratesArray[rateHt20_2], 16) + | ATH9K_POW_SM(ratesArray[rateHt20_1], 8) + | ATH9K_POW_SM(ratesArray[rateHt20_0], 0)); + REG_WRITE(ah, AR_PHY_POWER_TX_RATE6, + ATH9K_POW_SM(ratesArray[rateHt20_7], 24) + | ATH9K_POW_SM(ratesArray[rateHt20_6], 16) + | ATH9K_POW_SM(ratesArray[rateHt20_5], 8) + | ATH9K_POW_SM(ratesArray[rateHt20_4], 0)); + + /* HT40 power per rate */ + if (IS_CHAN_HT40(chan)) { + REG_WRITE(ah, AR_PHY_POWER_TX_RATE7, + ATH9K_POW_SM(ratesArray[rateHt40_3] + + ht40PowerIncForPdadc, 24) + | ATH9K_POW_SM(ratesArray[rateHt40_2] + + ht40PowerIncForPdadc, 16) + | ATH9K_POW_SM(ratesArray[rateHt40_1] + + ht40PowerIncForPdadc, 8) + | ATH9K_POW_SM(ratesArray[rateHt40_0] + + ht40PowerIncForPdadc, 0)); + REG_WRITE(ah, AR_PHY_POWER_TX_RATE8, + ATH9K_POW_SM(ratesArray[rateHt40_7] + + ht40PowerIncForPdadc, 24) + | ATH9K_POW_SM(ratesArray[rateHt40_6] + + ht40PowerIncForPdadc, 16) + | ATH9K_POW_SM(ratesArray[rateHt40_5] + + ht40PowerIncForPdadc, 8) + | ATH9K_POW_SM(ratesArray[rateHt40_4] + + ht40PowerIncForPdadc, 0)); + REG_WRITE(ah, AR_PHY_POWER_TX_RATE9, + ATH9K_POW_SM(ratesArray[rateExtOfdm], 24) + | ATH9K_POW_SM(ratesArray[rateExtCck], 16) + | ATH9K_POW_SM(ratesArray[rateDupOfdm], 8) + | ATH9K_POW_SM(ratesArray[rateDupCck], 0)); + } + + REGWRITE_BUFFER_FLUSH(ah); +} + +static void ath9k_hw_4k_set_gain(struct ath_hw *ah, + struct modal_eep_4k_header *pModal, + struct ar5416_eeprom_4k *eep, + u8 txRxAttenLocal) +{ + REG_WRITE(ah, AR_PHY_SWITCH_CHAIN_0, + pModal->antCtrlChain[0]); + + REG_WRITE(ah, AR_PHY_TIMING_CTRL4(0), + (REG_READ(ah, AR_PHY_TIMING_CTRL4(0)) & + ~(AR_PHY_TIMING_CTRL4_IQCORR_Q_Q_COFF | + AR_PHY_TIMING_CTRL4_IQCORR_Q_I_COFF)) | + SM(pModal->iqCalICh[0], AR_PHY_TIMING_CTRL4_IQCORR_Q_I_COFF) | + SM(pModal->iqCalQCh[0], AR_PHY_TIMING_CTRL4_IQCORR_Q_Q_COFF)); + + if ((eep->baseEepHeader.version & AR5416_EEP_VER_MINOR_MASK) >= + AR5416_EEP_MINOR_VER_3) { + txRxAttenLocal = pModal->txRxAttenCh[0]; + + REG_RMW_FIELD(ah, AR_PHY_GAIN_2GHZ, + AR_PHY_GAIN_2GHZ_XATTEN1_MARGIN, pModal->bswMargin[0]); + REG_RMW_FIELD(ah, AR_PHY_GAIN_2GHZ, + AR_PHY_GAIN_2GHZ_XATTEN1_DB, pModal->bswAtten[0]); + REG_RMW_FIELD(ah, AR_PHY_GAIN_2GHZ, + AR_PHY_GAIN_2GHZ_XATTEN2_MARGIN, + pModal->xatten2Margin[0]); + REG_RMW_FIELD(ah, AR_PHY_GAIN_2GHZ, + AR_PHY_GAIN_2GHZ_XATTEN2_DB, pModal->xatten2Db[0]); + + /* Set the block 1 value to block 0 value */ + REG_RMW_FIELD(ah, AR_PHY_GAIN_2GHZ + 0x1000, + AR_PHY_GAIN_2GHZ_XATTEN1_MARGIN, + pModal->bswMargin[0]); + REG_RMW_FIELD(ah, AR_PHY_GAIN_2GHZ + 0x1000, + AR_PHY_GAIN_2GHZ_XATTEN1_DB, pModal->bswAtten[0]); + REG_RMW_FIELD(ah, AR_PHY_GAIN_2GHZ + 0x1000, + AR_PHY_GAIN_2GHZ_XATTEN2_MARGIN, + pModal->xatten2Margin[0]); + REG_RMW_FIELD(ah, AR_PHY_GAIN_2GHZ + 0x1000, + AR_PHY_GAIN_2GHZ_XATTEN2_DB, + pModal->xatten2Db[0]); + } + + REG_RMW_FIELD(ah, AR_PHY_RXGAIN, + AR9280_PHY_RXGAIN_TXRX_ATTEN, txRxAttenLocal); + REG_RMW_FIELD(ah, AR_PHY_RXGAIN, + AR9280_PHY_RXGAIN_TXRX_MARGIN, pModal->rxTxMarginCh[0]); + + REG_RMW_FIELD(ah, AR_PHY_RXGAIN + 0x1000, + AR9280_PHY_RXGAIN_TXRX_ATTEN, txRxAttenLocal); + REG_RMW_FIELD(ah, AR_PHY_RXGAIN + 0x1000, + AR9280_PHY_RXGAIN_TXRX_MARGIN, pModal->rxTxMarginCh[0]); +} + +/* + * Read EEPROM header info and program the device for correct operation + * given the channel value. + */ +static void ath9k_hw_4k_set_board_values(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + struct modal_eep_4k_header *pModal; + struct ar5416_eeprom_4k *eep = &ah->eeprom.map4k; + struct base_eep_header_4k *pBase = &eep->baseEepHeader; + u8 txRxAttenLocal; + u8 ob[5], db1[5], db2[5]; + u8 ant_div_control1, ant_div_control2; + u8 bb_desired_scale; + u32 regVal; + + pModal = &eep->modalHeader; + txRxAttenLocal = 23; + + REG_WRITE(ah, AR_PHY_SWITCH_COM, pModal->antCtrlCommon); + + /* Single chain for 4K EEPROM*/ + ath9k_hw_4k_set_gain(ah, pModal, eep, txRxAttenLocal); + + /* Initialize Ant Diversity settings from EEPROM */ + if (pModal->version >= 3) { + ant_div_control1 = pModal->antdiv_ctl1; + ant_div_control2 = pModal->antdiv_ctl2; + + regVal = REG_READ(ah, AR_PHY_MULTICHAIN_GAIN_CTL); + regVal &= (~(AR_PHY_9285_ANT_DIV_CTL_ALL)); + + regVal |= SM(ant_div_control1, + AR_PHY_9285_ANT_DIV_CTL); + regVal |= SM(ant_div_control2, + AR_PHY_9285_ANT_DIV_ALT_LNACONF); + regVal |= SM((ant_div_control2 >> 2), + AR_PHY_9285_ANT_DIV_MAIN_LNACONF); + regVal |= SM((ant_div_control1 >> 1), + AR_PHY_9285_ANT_DIV_ALT_GAINTB); + regVal |= SM((ant_div_control1 >> 2), + AR_PHY_9285_ANT_DIV_MAIN_GAINTB); + + + REG_WRITE(ah, AR_PHY_MULTICHAIN_GAIN_CTL, regVal); + regVal = REG_READ(ah, AR_PHY_MULTICHAIN_GAIN_CTL); + regVal = REG_READ(ah, AR_PHY_CCK_DETECT); + regVal &= (~AR_PHY_CCK_DETECT_BB_ENABLE_ANT_FAST_DIV); + regVal |= SM((ant_div_control1 >> 3), + AR_PHY_CCK_DETECT_BB_ENABLE_ANT_FAST_DIV); + + REG_WRITE(ah, AR_PHY_CCK_DETECT, regVal); + regVal = REG_READ(ah, AR_PHY_CCK_DETECT); + } + + if (pModal->version >= 2) { + ob[0] = pModal->ob_0; + ob[1] = pModal->ob_1; + ob[2] = pModal->ob_2; + ob[3] = pModal->ob_3; + ob[4] = pModal->ob_4; + + db1[0] = pModal->db1_0; + db1[1] = pModal->db1_1; + db1[2] = pModal->db1_2; + db1[3] = pModal->db1_3; + db1[4] = pModal->db1_4; + + db2[0] = pModal->db2_0; + db2[1] = pModal->db2_1; + db2[2] = pModal->db2_2; + db2[3] = pModal->db2_3; + db2[4] = pModal->db2_4; + } else if (pModal->version == 1) { + ob[0] = pModal->ob_0; + ob[1] = ob[2] = ob[3] = ob[4] = pModal->ob_1; + db1[0] = pModal->db1_0; + db1[1] = db1[2] = db1[3] = db1[4] = pModal->db1_1; + db2[0] = pModal->db2_0; + db2[1] = db2[2] = db2[3] = db2[4] = pModal->db2_1; + } else { + int i; + + for (i = 0; i < 5; i++) { + ob[i] = pModal->ob_0; + db1[i] = pModal->db1_0; + db2[i] = pModal->db1_0; + } + } + + if (AR_SREV_9271(ah)) { + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G3, + AR9271_AN_RF2G3_OB_cck, + AR9271_AN_RF2G3_OB_cck_S, + ob[0]); + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G3, + AR9271_AN_RF2G3_OB_psk, + AR9271_AN_RF2G3_OB_psk_S, + ob[1]); + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G3, + AR9271_AN_RF2G3_OB_qam, + AR9271_AN_RF2G3_OB_qam_S, + ob[2]); + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G3, + AR9271_AN_RF2G3_DB_1, + AR9271_AN_RF2G3_DB_1_S, + db1[0]); + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G4, + AR9271_AN_RF2G4_DB_2, + AR9271_AN_RF2G4_DB_2_S, + db2[0]); + } else { + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G3, + AR9285_AN_RF2G3_OB_0, + AR9285_AN_RF2G3_OB_0_S, + ob[0]); + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G3, + AR9285_AN_RF2G3_OB_1, + AR9285_AN_RF2G3_OB_1_S, + ob[1]); + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G3, + AR9285_AN_RF2G3_OB_2, + AR9285_AN_RF2G3_OB_2_S, + ob[2]); + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G3, + AR9285_AN_RF2G3_OB_3, + AR9285_AN_RF2G3_OB_3_S, + ob[3]); + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G3, + AR9285_AN_RF2G3_OB_4, + AR9285_AN_RF2G3_OB_4_S, + ob[4]); + + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G3, + AR9285_AN_RF2G3_DB1_0, + AR9285_AN_RF2G3_DB1_0_S, + db1[0]); + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G3, + AR9285_AN_RF2G3_DB1_1, + AR9285_AN_RF2G3_DB1_1_S, + db1[1]); + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G3, + AR9285_AN_RF2G3_DB1_2, + AR9285_AN_RF2G3_DB1_2_S, + db1[2]); + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G4, + AR9285_AN_RF2G4_DB1_3, + AR9285_AN_RF2G4_DB1_3_S, + db1[3]); + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G4, + AR9285_AN_RF2G4_DB1_4, + AR9285_AN_RF2G4_DB1_4_S, db1[4]); + + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G4, + AR9285_AN_RF2G4_DB2_0, + AR9285_AN_RF2G4_DB2_0_S, + db2[0]); + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G4, + AR9285_AN_RF2G4_DB2_1, + AR9285_AN_RF2G4_DB2_1_S, + db2[1]); + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G4, + AR9285_AN_RF2G4_DB2_2, + AR9285_AN_RF2G4_DB2_2_S, + db2[2]); + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G4, + AR9285_AN_RF2G4_DB2_3, + AR9285_AN_RF2G4_DB2_3_S, + db2[3]); + ath9k_hw_analog_shift_rmw(ah, + AR9285_AN_RF2G4, + AR9285_AN_RF2G4_DB2_4, + AR9285_AN_RF2G4_DB2_4_S, + db2[4]); + } + + + REG_RMW_FIELD(ah, AR_PHY_SETTLING, AR_PHY_SETTLING_SWITCH, + pModal->switchSettling); + REG_RMW_FIELD(ah, AR_PHY_DESIRED_SZ, AR_PHY_DESIRED_SZ_ADC, + pModal->adcDesiredSize); + + REG_WRITE(ah, AR_PHY_RF_CTL4, + SM(pModal->txEndToXpaOff, AR_PHY_RF_CTL4_TX_END_XPAA_OFF) | + SM(pModal->txEndToXpaOff, AR_PHY_RF_CTL4_TX_END_XPAB_OFF) | + SM(pModal->txFrameToXpaOn, AR_PHY_RF_CTL4_FRAME_XPAA_ON) | + SM(pModal->txFrameToXpaOn, AR_PHY_RF_CTL4_FRAME_XPAB_ON)); + + REG_RMW_FIELD(ah, AR_PHY_RF_CTL3, AR_PHY_TX_END_TO_A2_RX_ON, + pModal->txEndToRxOn); + + if (AR_SREV_9271_10(ah)) + REG_RMW_FIELD(ah, AR_PHY_RF_CTL3, AR_PHY_TX_END_TO_A2_RX_ON, + pModal->txEndToRxOn); + REG_RMW_FIELD(ah, AR_PHY_CCA, AR9280_PHY_CCA_THRESH62, + pModal->thresh62); + REG_RMW_FIELD(ah, AR_PHY_EXT_CCA0, AR_PHY_EXT_CCA0_THRESH62, + pModal->thresh62); + + if ((eep->baseEepHeader.version & AR5416_EEP_VER_MINOR_MASK) >= + AR5416_EEP_MINOR_VER_2) { + REG_RMW_FIELD(ah, AR_PHY_RF_CTL2, AR_PHY_TX_END_DATA_START, + pModal->txFrameToDataStart); + REG_RMW_FIELD(ah, AR_PHY_RF_CTL2, AR_PHY_TX_END_PA_ON, + pModal->txFrameToPaOn); + } + + if ((eep->baseEepHeader.version & AR5416_EEP_VER_MINOR_MASK) >= + AR5416_EEP_MINOR_VER_3) { + if (IS_CHAN_HT40(chan)) + REG_RMW_FIELD(ah, AR_PHY_SETTLING, + AR_PHY_SETTLING_SWITCH, + pModal->swSettleHt40); + } + + bb_desired_scale = (pModal->bb_scale_smrt_antenna & + EEP_4K_BB_DESIRED_SCALE_MASK); + if ((pBase->txGainType == 0) && (bb_desired_scale != 0)) { + u32 pwrctrl, mask, clr; + + mask = BIT(0)|BIT(5)|BIT(10)|BIT(15)|BIT(20)|BIT(25); + pwrctrl = mask * bb_desired_scale; + clr = mask * 0x1f; + REG_RMW(ah, AR_PHY_TX_PWRCTRL8, pwrctrl, clr); + REG_RMW(ah, AR_PHY_TX_PWRCTRL10, pwrctrl, clr); + REG_RMW(ah, AR_PHY_CH0_TX_PWRCTRL12, pwrctrl, clr); + + mask = BIT(0)|BIT(5)|BIT(15); + pwrctrl = mask * bb_desired_scale; + clr = mask * 0x1f; + REG_RMW(ah, AR_PHY_TX_PWRCTRL9, pwrctrl, clr); + + mask = BIT(0)|BIT(5); + pwrctrl = mask * bb_desired_scale; + clr = mask * 0x1f; + REG_RMW(ah, AR_PHY_CH0_TX_PWRCTRL11, pwrctrl, clr); + REG_RMW(ah, AR_PHY_CH0_TX_PWRCTRL13, pwrctrl, clr); + } +} + +static u16 ath9k_hw_4k_get_spur_channel(struct ath_hw *ah, u16 i, bool is2GHz) +{ +#define EEP_MAP4K_SPURCHAN \ + (ah->eeprom.map4k.modalHeader.spurChans[i].spurChan) + struct ath_common *common = ath9k_hw_common(ah); + + u16 spur_val = AR_NO_SPUR; + + ath_dbg(common, ANI, "Getting spur idx:%d is2Ghz:%d val:%x\n", + i, is2GHz, ah->config.spurchans[i][is2GHz]); + + switch (ah->config.spurmode) { + case SPUR_DISABLE: + break; + case SPUR_ENABLE_IOCTL: + spur_val = ah->config.spurchans[i][is2GHz]; + ath_dbg(common, ANI, "Getting spur val from new loc. %d\n", + spur_val); + break; + case SPUR_ENABLE_EEPROM: + spur_val = EEP_MAP4K_SPURCHAN; + break; + } + + return spur_val; + +#undef EEP_MAP4K_SPURCHAN +} + +const struct eeprom_ops eep_4k_ops = { + .check_eeprom = ath9k_hw_4k_check_eeprom, + .get_eeprom = ath9k_hw_4k_get_eeprom, + .fill_eeprom = ath9k_hw_4k_fill_eeprom, + .dump_eeprom = ath9k_hw_4k_dump_eeprom, + .get_eeprom_ver = ath9k_hw_4k_get_eeprom_ver, + .get_eeprom_rev = ath9k_hw_4k_get_eeprom_rev, + .set_board_values = ath9k_hw_4k_set_board_values, + .set_txpower = ath9k_hw_4k_set_txpower, + .get_spur_channel = ath9k_hw_4k_get_spur_channel +}; diff --git a/drivers/net/wireless/ath/ath9k/eeprom_9287.c b/drivers/net/wireless/ath/ath9k/eeprom_9287.c new file mode 100644 index 0000000..e318ec7 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/eeprom_9287.c @@ -0,0 +1,1044 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "hw.h" +#include "ar9002_phy.h" + +#define SIZE_EEPROM_AR9287 (sizeof(struct ar9287_eeprom) / sizeof(u16)) + +static int ath9k_hw_ar9287_get_eeprom_ver(struct ath_hw *ah) +{ + return (ah->eeprom.map9287.baseEepHeader.version >> 12) & 0xF; +} + +static int ath9k_hw_ar9287_get_eeprom_rev(struct ath_hw *ah) +{ + return (ah->eeprom.map9287.baseEepHeader.version) & 0xFFF; +} + +static bool __ath9k_hw_ar9287_fill_eeprom(struct ath_hw *ah) +{ + struct ar9287_eeprom *eep = &ah->eeprom.map9287; + u16 *eep_data; + int addr, eep_start_loc = AR9287_EEP_START_LOC; + eep_data = (u16 *)eep; + + for (addr = 0; addr < SIZE_EEPROM_AR9287; addr++) { + if (!ath9k_hw_nvram_read(ah, addr + eep_start_loc, eep_data)) + return false; + eep_data++; + } + + return true; +} + +static bool __ath9k_hw_usb_ar9287_fill_eeprom(struct ath_hw *ah) +{ + u16 *eep_data = (u16 *)&ah->eeprom.map9287; + + ath9k_hw_usb_gen_fill_eeprom(ah, eep_data, + AR9287_HTC_EEP_START_LOC, + SIZE_EEPROM_AR9287); + return true; +} + +static bool ath9k_hw_ar9287_fill_eeprom(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + + if (!ath9k_hw_use_flash(ah)) { + ath_dbg(common, EEPROM, "Reading from EEPROM, not flash\n"); + } + + if (common->bus_ops->ath_bus_type == ATH_USB) + return __ath9k_hw_usb_ar9287_fill_eeprom(ah); + else + return __ath9k_hw_ar9287_fill_eeprom(ah); +} + +#if defined(CPTCFG_ATH9K_DEBUGFS) || defined(CPTCFG_ATH9K_HTC_DEBUGFS) +static u32 ar9287_dump_modal_eeprom(char *buf, u32 len, u32 size, + struct modal_eep_ar9287_header *modal_hdr) +{ + PR_EEP("Chain0 Ant. Control", modal_hdr->antCtrlChain[0]); + PR_EEP("Chain1 Ant. Control", modal_hdr->antCtrlChain[1]); + PR_EEP("Ant. Common Control", modal_hdr->antCtrlCommon); + PR_EEP("Chain0 Ant. Gain", modal_hdr->antennaGainCh[0]); + PR_EEP("Chain1 Ant. Gain", modal_hdr->antennaGainCh[1]); + PR_EEP("Switch Settle", modal_hdr->switchSettling); + PR_EEP("Chain0 TxRxAtten", modal_hdr->txRxAttenCh[0]); + PR_EEP("Chain1 TxRxAtten", modal_hdr->txRxAttenCh[1]); + PR_EEP("Chain0 RxTxMargin", modal_hdr->rxTxMarginCh[0]); + PR_EEP("Chain1 RxTxMargin", modal_hdr->rxTxMarginCh[1]); + PR_EEP("ADC Desired size", modal_hdr->adcDesiredSize); + PR_EEP("txEndToXpaOff", modal_hdr->txEndToXpaOff); + PR_EEP("txEndToRxOn", modal_hdr->txEndToRxOn); + PR_EEP("txFrameToXpaOn", modal_hdr->txFrameToXpaOn); + PR_EEP("CCA Threshold)", modal_hdr->thresh62); + PR_EEP("Chain0 NF Threshold", modal_hdr->noiseFloorThreshCh[0]); + PR_EEP("Chain1 NF Threshold", modal_hdr->noiseFloorThreshCh[1]); + PR_EEP("xpdGain", modal_hdr->xpdGain); + PR_EEP("External PD", modal_hdr->xpd); + PR_EEP("Chain0 I Coefficient", modal_hdr->iqCalICh[0]); + PR_EEP("Chain1 I Coefficient", modal_hdr->iqCalICh[1]); + PR_EEP("Chain0 Q Coefficient", modal_hdr->iqCalQCh[0]); + PR_EEP("Chain1 Q Coefficient", modal_hdr->iqCalQCh[1]); + PR_EEP("pdGainOverlap", modal_hdr->pdGainOverlap); + PR_EEP("xPA Bias Level", modal_hdr->xpaBiasLvl); + PR_EEP("txFrameToDataStart", modal_hdr->txFrameToDataStart); + PR_EEP("txFrameToPaOn", modal_hdr->txFrameToPaOn); + PR_EEP("HT40 Power Inc.", modal_hdr->ht40PowerIncForPdadc); + PR_EEP("Chain0 bswAtten", modal_hdr->bswAtten[0]); + PR_EEP("Chain1 bswAtten", modal_hdr->bswAtten[1]); + PR_EEP("Chain0 bswMargin", modal_hdr->bswMargin[0]); + PR_EEP("Chain1 bswMargin", modal_hdr->bswMargin[1]); + PR_EEP("HT40 Switch Settle", modal_hdr->swSettleHt40); + PR_EEP("AR92x7 Version", modal_hdr->version); + PR_EEP("DriverBias1", modal_hdr->db1); + PR_EEP("DriverBias2", modal_hdr->db1); + PR_EEP("CCK OutputBias", modal_hdr->ob_cck); + PR_EEP("PSK OutputBias", modal_hdr->ob_psk); + PR_EEP("QAM OutputBias", modal_hdr->ob_qam); + PR_EEP("PAL_OFF OutputBias", modal_hdr->ob_pal_off); + + return len; +} + +static u32 ath9k_hw_ar9287_dump_eeprom(struct ath_hw *ah, bool dump_base_hdr, + u8 *buf, u32 len, u32 size) +{ + struct ar9287_eeprom *eep = &ah->eeprom.map9287; + struct base_eep_ar9287_header *pBase = &eep->baseEepHeader; + + if (!dump_base_hdr) { + len += snprintf(buf + len, size - len, + "%20s :\n", "2GHz modal Header"); + len = ar9287_dump_modal_eeprom(buf, len, size, + &eep->modalHeader); + goto out; + } + + PR_EEP("Major Version", pBase->version >> 12); + PR_EEP("Minor Version", pBase->version & 0xFFF); + PR_EEP("Checksum", pBase->checksum); + PR_EEP("Length", pBase->length); + PR_EEP("RegDomain1", pBase->regDmn[0]); + PR_EEP("RegDomain2", pBase->regDmn[1]); + PR_EEP("TX Mask", pBase->txMask); + PR_EEP("RX Mask", pBase->rxMask); + PR_EEP("Allow 5GHz", !!(pBase->opCapFlags & AR5416_OPFLAGS_11A)); + PR_EEP("Allow 2GHz", !!(pBase->opCapFlags & AR5416_OPFLAGS_11G)); + PR_EEP("Disable 2GHz HT20", !!(pBase->opCapFlags & + AR5416_OPFLAGS_N_2G_HT20)); + PR_EEP("Disable 2GHz HT40", !!(pBase->opCapFlags & + AR5416_OPFLAGS_N_2G_HT40)); + PR_EEP("Disable 5Ghz HT20", !!(pBase->opCapFlags & + AR5416_OPFLAGS_N_5G_HT20)); + PR_EEP("Disable 5Ghz HT40", !!(pBase->opCapFlags & + AR5416_OPFLAGS_N_5G_HT40)); + PR_EEP("Big Endian", !!(pBase->eepMisc & 0x01)); + PR_EEP("Cal Bin Major Ver", (pBase->binBuildNumber >> 24) & 0xFF); + PR_EEP("Cal Bin Minor Ver", (pBase->binBuildNumber >> 16) & 0xFF); + PR_EEP("Cal Bin Build", (pBase->binBuildNumber >> 8) & 0xFF); + PR_EEP("Power Table Offset", pBase->pwrTableOffset); + PR_EEP("OpenLoop Power Ctrl", pBase->openLoopPwrCntl); + + len += snprintf(buf + len, size - len, "%20s : %pM\n", "MacAddress", + pBase->macAddr); + +out: + if (len > size) + len = size; + + return len; +} +#else +static u32 ath9k_hw_ar9287_dump_eeprom(struct ath_hw *ah, bool dump_base_hdr, + u8 *buf, u32 len, u32 size) +{ + return 0; +} +#endif + + +static int ath9k_hw_ar9287_check_eeprom(struct ath_hw *ah) +{ + u32 sum = 0, el, integer; + u16 temp, word, magic, magic2, *eepdata; + int i, addr; + bool need_swap = false; + struct ar9287_eeprom *eep = &ah->eeprom.map9287; + struct ath_common *common = ath9k_hw_common(ah); + + if (!ath9k_hw_use_flash(ah)) { + if (!ath9k_hw_nvram_read(ah, AR5416_EEPROM_MAGIC_OFFSET, + &magic)) { + ath_err(common, "Reading Magic # failed\n"); + return false; + } + + ath_dbg(common, EEPROM, "Read Magic = 0x%04X\n", magic); + + if (magic != AR5416_EEPROM_MAGIC) { + magic2 = swab16(magic); + + if (magic2 == AR5416_EEPROM_MAGIC) { + need_swap = true; + eepdata = (u16 *)(&ah->eeprom); + + for (addr = 0; addr < SIZE_EEPROM_AR9287; addr++) { + temp = swab16(*eepdata); + *eepdata = temp; + eepdata++; + } + } else { + ath_err(common, + "Invalid EEPROM Magic. Endianness mismatch.\n"); + return -EINVAL; + } + } + } + + ath_dbg(common, EEPROM, "need_swap = %s\n", + need_swap ? "True" : "False"); + + if (need_swap) + el = swab16(ah->eeprom.map9287.baseEepHeader.length); + else + el = ah->eeprom.map9287.baseEepHeader.length; + + if (el > sizeof(struct ar9287_eeprom)) + el = sizeof(struct ar9287_eeprom) / sizeof(u16); + else + el = el / sizeof(u16); + + eepdata = (u16 *)(&ah->eeprom); + + for (i = 0; i < el; i++) + sum ^= *eepdata++; + + if (need_swap) { + word = swab16(eep->baseEepHeader.length); + eep->baseEepHeader.length = word; + + word = swab16(eep->baseEepHeader.checksum); + eep->baseEepHeader.checksum = word; + + word = swab16(eep->baseEepHeader.version); + eep->baseEepHeader.version = word; + + word = swab16(eep->baseEepHeader.regDmn[0]); + eep->baseEepHeader.regDmn[0] = word; + + word = swab16(eep->baseEepHeader.regDmn[1]); + eep->baseEepHeader.regDmn[1] = word; + + word = swab16(eep->baseEepHeader.rfSilent); + eep->baseEepHeader.rfSilent = word; + + word = swab16(eep->baseEepHeader.blueToothOptions); + eep->baseEepHeader.blueToothOptions = word; + + word = swab16(eep->baseEepHeader.deviceCap); + eep->baseEepHeader.deviceCap = word; + + integer = swab32(eep->modalHeader.antCtrlCommon); + eep->modalHeader.antCtrlCommon = integer; + + for (i = 0; i < AR9287_MAX_CHAINS; i++) { + integer = swab32(eep->modalHeader.antCtrlChain[i]); + eep->modalHeader.antCtrlChain[i] = integer; + } + + for (i = 0; i < AR_EEPROM_MODAL_SPURS; i++) { + word = swab16(eep->modalHeader.spurChans[i].spurChan); + eep->modalHeader.spurChans[i].spurChan = word; + } + } + + if (sum != 0xffff || ah->eep_ops->get_eeprom_ver(ah) != AR9287_EEP_VER + || ah->eep_ops->get_eeprom_rev(ah) < AR5416_EEP_NO_BACK_VER) { + ath_err(common, "Bad EEPROM checksum 0x%x or revision 0x%04x\n", + sum, ah->eep_ops->get_eeprom_ver(ah)); + return -EINVAL; + } + + return 0; +} + +static u32 ath9k_hw_ar9287_get_eeprom(struct ath_hw *ah, + enum eeprom_param param) +{ + struct ar9287_eeprom *eep = &ah->eeprom.map9287; + struct modal_eep_ar9287_header *pModal = &eep->modalHeader; + struct base_eep_ar9287_header *pBase = &eep->baseEepHeader; + u16 ver_minor; + + ver_minor = pBase->version & AR9287_EEP_VER_MINOR_MASK; + + switch (param) { + case EEP_NFTHRESH_2: + return pModal->noiseFloorThreshCh[0]; + case EEP_MAC_LSW: + return get_unaligned_be16(pBase->macAddr); + case EEP_MAC_MID: + return get_unaligned_be16(pBase->macAddr + 2); + case EEP_MAC_MSW: + return get_unaligned_be16(pBase->macAddr + 4); + case EEP_REG_0: + return pBase->regDmn[0]; + case EEP_OP_CAP: + return pBase->deviceCap; + case EEP_OP_MODE: + return pBase->opCapFlags; + case EEP_RF_SILENT: + return pBase->rfSilent; + case EEP_MINOR_REV: + return ver_minor; + case EEP_TX_MASK: + return pBase->txMask; + case EEP_RX_MASK: + return pBase->rxMask; + case EEP_DEV_TYPE: + return pBase->deviceType; + case EEP_OL_PWRCTRL: + return pBase->openLoopPwrCntl; + case EEP_TEMPSENSE_SLOPE: + if (ver_minor >= AR9287_EEP_MINOR_VER_2) + return pBase->tempSensSlope; + else + return 0; + case EEP_TEMPSENSE_SLOPE_PAL_ON: + if (ver_minor >= AR9287_EEP_MINOR_VER_3) + return pBase->tempSensSlopePalOn; + else + return 0; + case EEP_ANTENNA_GAIN_2G: + return max_t(u8, pModal->antennaGainCh[0], + pModal->antennaGainCh[1]); + default: + return 0; + } +} + +static void ar9287_eeprom_get_tx_gain_index(struct ath_hw *ah, + struct ath9k_channel *chan, + struct cal_data_op_loop_ar9287 *pRawDatasetOpLoop, + u8 *pCalChans, u16 availPiers, int8_t *pPwr) +{ + u16 idxL = 0, idxR = 0, numPiers; + bool match; + struct chan_centers centers; + + ath9k_hw_get_channel_centers(ah, chan, ¢ers); + + for (numPiers = 0; numPiers < availPiers; numPiers++) { + if (pCalChans[numPiers] == AR5416_BCHAN_UNUSED) + break; + } + + match = ath9k_hw_get_lower_upper_index( + (u8)FREQ2FBIN(centers.synth_center, IS_CHAN_2GHZ(chan)), + pCalChans, numPiers, &idxL, &idxR); + + if (match) { + *pPwr = (int8_t) pRawDatasetOpLoop[idxL].pwrPdg[0][0]; + } else { + *pPwr = ((int8_t) pRawDatasetOpLoop[idxL].pwrPdg[0][0] + + (int8_t) pRawDatasetOpLoop[idxR].pwrPdg[0][0])/2; + } + +} + +static void ar9287_eeprom_olpc_set_pdadcs(struct ath_hw *ah, + int32_t txPower, u16 chain) +{ + u32 tmpVal; + u32 a; + + /* Enable OLPC for chain 0 */ + + tmpVal = REG_READ(ah, 0xa270); + tmpVal = tmpVal & 0xFCFFFFFF; + tmpVal = tmpVal | (0x3 << 24); + REG_WRITE(ah, 0xa270, tmpVal); + + /* Enable OLPC for chain 1 */ + + tmpVal = REG_READ(ah, 0xb270); + tmpVal = tmpVal & 0xFCFFFFFF; + tmpVal = tmpVal | (0x3 << 24); + REG_WRITE(ah, 0xb270, tmpVal); + + /* Write the OLPC ref power for chain 0 */ + + if (chain == 0) { + tmpVal = REG_READ(ah, 0xa398); + tmpVal = tmpVal & 0xff00ffff; + a = (txPower)&0xff; + tmpVal = tmpVal | (a << 16); + REG_WRITE(ah, 0xa398, tmpVal); + } + + /* Write the OLPC ref power for chain 1 */ + + if (chain == 1) { + tmpVal = REG_READ(ah, 0xb398); + tmpVal = tmpVal & 0xff00ffff; + a = (txPower)&0xff; + tmpVal = tmpVal | (a << 16); + REG_WRITE(ah, 0xb398, tmpVal); + } +} + +static void ath9k_hw_set_ar9287_power_cal_table(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + struct cal_data_per_freq_ar9287 *pRawDataset; + struct cal_data_op_loop_ar9287 *pRawDatasetOpenLoop; + u8 *pCalBChans = NULL; + u16 pdGainOverlap_t2; + u8 pdadcValues[AR5416_NUM_PDADC_VALUES]; + u16 gainBoundaries[AR5416_PD_GAINS_IN_MASK]; + u16 numPiers = 0, i, j; + u16 numXpdGain, xpdMask; + u16 xpdGainValues[AR5416_NUM_PD_GAINS] = {0, 0, 0, 0}; + u32 reg32, regOffset, regChainOffset, regval; + int16_t diff = 0; + struct ar9287_eeprom *pEepData = &ah->eeprom.map9287; + + xpdMask = pEepData->modalHeader.xpdGain; + + if ((pEepData->baseEepHeader.version & AR9287_EEP_VER_MINOR_MASK) >= + AR9287_EEP_MINOR_VER_2) + pdGainOverlap_t2 = pEepData->modalHeader.pdGainOverlap; + else + pdGainOverlap_t2 = (u16)(MS(REG_READ(ah, AR_PHY_TPCRG5), + AR_PHY_TPCRG5_PD_GAIN_OVERLAP)); + + if (IS_CHAN_2GHZ(chan)) { + pCalBChans = pEepData->calFreqPier2G; + numPiers = AR9287_NUM_2G_CAL_PIERS; + if (ath9k_hw_ar9287_get_eeprom(ah, EEP_OL_PWRCTRL)) { + pRawDatasetOpenLoop = + (struct cal_data_op_loop_ar9287 *)pEepData->calPierData2G[0]; + ah->initPDADC = pRawDatasetOpenLoop->vpdPdg[0][0]; + } + } + + numXpdGain = 0; + + /* Calculate the value of xpdgains from the xpdGain Mask */ + for (i = 1; i <= AR5416_PD_GAINS_IN_MASK; i++) { + if ((xpdMask >> (AR5416_PD_GAINS_IN_MASK - i)) & 1) { + if (numXpdGain >= AR5416_NUM_PD_GAINS) + break; + xpdGainValues[numXpdGain] = + (u16)(AR5416_PD_GAINS_IN_MASK-i); + numXpdGain++; + } + } + + REG_RMW_FIELD(ah, AR_PHY_TPCRG1, AR_PHY_TPCRG1_NUM_PD_GAIN, + (numXpdGain - 1) & 0x3); + REG_RMW_FIELD(ah, AR_PHY_TPCRG1, AR_PHY_TPCRG1_PD_GAIN_1, + xpdGainValues[0]); + REG_RMW_FIELD(ah, AR_PHY_TPCRG1, AR_PHY_TPCRG1_PD_GAIN_2, + xpdGainValues[1]); + REG_RMW_FIELD(ah, AR_PHY_TPCRG1, AR_PHY_TPCRG1_PD_GAIN_3, + xpdGainValues[2]); + + for (i = 0; i < AR9287_MAX_CHAINS; i++) { + regChainOffset = i * 0x1000; + + if (pEepData->baseEepHeader.txMask & (1 << i)) { + pRawDatasetOpenLoop = + (struct cal_data_op_loop_ar9287 *)pEepData->calPierData2G[i]; + + if (ath9k_hw_ar9287_get_eeprom(ah, EEP_OL_PWRCTRL)) { + int8_t txPower; + ar9287_eeprom_get_tx_gain_index(ah, chan, + pRawDatasetOpenLoop, + pCalBChans, numPiers, + &txPower); + ar9287_eeprom_olpc_set_pdadcs(ah, txPower, i); + } else { + pRawDataset = + (struct cal_data_per_freq_ar9287 *) + pEepData->calPierData2G[i]; + + ath9k_hw_get_gain_boundaries_pdadcs(ah, chan, + pRawDataset, + pCalBChans, numPiers, + pdGainOverlap_t2, + gainBoundaries, + pdadcValues, + numXpdGain); + } + + ENABLE_REGWRITE_BUFFER(ah); + + if (i == 0) { + if (!ath9k_hw_ar9287_get_eeprom(ah, + EEP_OL_PWRCTRL)) { + + regval = SM(pdGainOverlap_t2, + AR_PHY_TPCRG5_PD_GAIN_OVERLAP) + | SM(gainBoundaries[0], + AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_1) + | SM(gainBoundaries[1], + AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_2) + | SM(gainBoundaries[2], + AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_3) + | SM(gainBoundaries[3], + AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_4); + + REG_WRITE(ah, + AR_PHY_TPCRG5 + regChainOffset, + regval); + } + } + + if ((int32_t)AR9287_PWR_TABLE_OFFSET_DB != + pEepData->baseEepHeader.pwrTableOffset) { + diff = (u16)(pEepData->baseEepHeader.pwrTableOffset - + (int32_t)AR9287_PWR_TABLE_OFFSET_DB); + diff *= 2; + + for (j = 0; j < ((u16)AR5416_NUM_PDADC_VALUES-diff); j++) + pdadcValues[j] = pdadcValues[j+diff]; + + for (j = (u16)(AR5416_NUM_PDADC_VALUES-diff); + j < AR5416_NUM_PDADC_VALUES; j++) + pdadcValues[j] = + pdadcValues[AR5416_NUM_PDADC_VALUES-diff]; + } + + if (!ath9k_hw_ar9287_get_eeprom(ah, EEP_OL_PWRCTRL)) { + regOffset = AR_PHY_BASE + + (672 << 2) + regChainOffset; + + for (j = 0; j < 32; j++) { + reg32 = get_unaligned_le32(&pdadcValues[4 * j]); + + REG_WRITE(ah, regOffset, reg32); + regOffset += 4; + } + } + REGWRITE_BUFFER_FLUSH(ah); + } + } +} + +static void ath9k_hw_set_ar9287_power_per_rate_table(struct ath_hw *ah, + struct ath9k_channel *chan, + int16_t *ratesArray, + u16 cfgCtl, + u16 antenna_reduction, + u16 powerLimit) +{ +#define CMP_CTL \ + (((cfgCtl & ~CTL_MODE_M) | (pCtlMode[ctlMode] & CTL_MODE_M)) == \ + pEepData->ctlIndex[i]) + +#define CMP_NO_CTL \ + (((cfgCtl & ~CTL_MODE_M) | (pCtlMode[ctlMode] & CTL_MODE_M)) == \ + ((pEepData->ctlIndex[i] & CTL_MODE_M) | SD_NO_CTL)) + + u16 twiceMaxEdgePower; + int i; + struct cal_ctl_data_ar9287 *rep; + struct cal_target_power_leg targetPowerOfdm = {0, {0, 0, 0, 0} }, + targetPowerCck = {0, {0, 0, 0, 0} }; + struct cal_target_power_leg targetPowerOfdmExt = {0, {0, 0, 0, 0} }, + targetPowerCckExt = {0, {0, 0, 0, 0} }; + struct cal_target_power_ht targetPowerHt20, + targetPowerHt40 = {0, {0, 0, 0, 0} }; + u16 scaledPower = 0, minCtlPower; + static const u16 ctlModesFor11g[] = { + CTL_11B, CTL_11G, CTL_2GHT20, + CTL_11B_EXT, CTL_11G_EXT, CTL_2GHT40 + }; + u16 numCtlModes = 0; + const u16 *pCtlMode = NULL; + u16 ctlMode, freq; + struct chan_centers centers; + int tx_chainmask; + u16 twiceMinEdgePower; + struct ar9287_eeprom *pEepData = &ah->eeprom.map9287; + tx_chainmask = ah->txchainmask; + + ath9k_hw_get_channel_centers(ah, chan, ¢ers); + scaledPower = ath9k_hw_get_scaled_power(ah, powerLimit, + antenna_reduction); + + /* + * Get TX power from EEPROM. + */ + if (IS_CHAN_2GHZ(chan)) { + /* CTL_11B, CTL_11G, CTL_2GHT20 */ + numCtlModes = + ARRAY_SIZE(ctlModesFor11g) - SUB_NUM_CTL_MODES_AT_2G_40; + + pCtlMode = ctlModesFor11g; + + ath9k_hw_get_legacy_target_powers(ah, chan, + pEepData->calTargetPowerCck, + AR9287_NUM_2G_CCK_TARGET_POWERS, + &targetPowerCck, 4, false); + ath9k_hw_get_legacy_target_powers(ah, chan, + pEepData->calTargetPower2G, + AR9287_NUM_2G_20_TARGET_POWERS, + &targetPowerOfdm, 4, false); + ath9k_hw_get_target_powers(ah, chan, + pEepData->calTargetPower2GHT20, + AR9287_NUM_2G_20_TARGET_POWERS, + &targetPowerHt20, 8, false); + + if (IS_CHAN_HT40(chan)) { + /* All 2G CTLs */ + numCtlModes = ARRAY_SIZE(ctlModesFor11g); + ath9k_hw_get_target_powers(ah, chan, + pEepData->calTargetPower2GHT40, + AR9287_NUM_2G_40_TARGET_POWERS, + &targetPowerHt40, 8, true); + ath9k_hw_get_legacy_target_powers(ah, chan, + pEepData->calTargetPowerCck, + AR9287_NUM_2G_CCK_TARGET_POWERS, + &targetPowerCckExt, 4, true); + ath9k_hw_get_legacy_target_powers(ah, chan, + pEepData->calTargetPower2G, + AR9287_NUM_2G_20_TARGET_POWERS, + &targetPowerOfdmExt, 4, true); + } + } + + for (ctlMode = 0; ctlMode < numCtlModes; ctlMode++) { + bool isHt40CtlMode = + (pCtlMode[ctlMode] == CTL_2GHT40) ? true : false; + + if (isHt40CtlMode) + freq = centers.synth_center; + else if (pCtlMode[ctlMode] & EXT_ADDITIVE) + freq = centers.ext_center; + else + freq = centers.ctl_center; + + twiceMaxEdgePower = MAX_RATE_POWER; + /* Walk through the CTL indices stored in EEPROM */ + for (i = 0; (i < AR9287_NUM_CTLS) && pEepData->ctlIndex[i]; i++) { + struct cal_ctl_edges *pRdEdgesPower; + + /* + * Compare test group from regulatory channel list + * with test mode from pCtlMode list + */ + if (CMP_CTL || CMP_NO_CTL) { + rep = &(pEepData->ctlData[i]); + pRdEdgesPower = + rep->ctlEdges[ar5416_get_ntxchains(tx_chainmask) - 1]; + + twiceMinEdgePower = ath9k_hw_get_max_edge_power(freq, + pRdEdgesPower, + IS_CHAN_2GHZ(chan), + AR5416_NUM_BAND_EDGES); + + if ((cfgCtl & ~CTL_MODE_M) == SD_NO_CTL) { + twiceMaxEdgePower = min(twiceMaxEdgePower, + twiceMinEdgePower); + } else { + twiceMaxEdgePower = twiceMinEdgePower; + break; + } + } + } + + minCtlPower = (u8)min(twiceMaxEdgePower, scaledPower); + + /* Apply ctl mode to correct target power set */ + switch (pCtlMode[ctlMode]) { + case CTL_11B: + for (i = 0; i < ARRAY_SIZE(targetPowerCck.tPow2x); i++) { + targetPowerCck.tPow2x[i] = + (u8)min((u16)targetPowerCck.tPow2x[i], + minCtlPower); + } + break; + case CTL_11A: + case CTL_11G: + for (i = 0; i < ARRAY_SIZE(targetPowerOfdm.tPow2x); i++) { + targetPowerOfdm.tPow2x[i] = + (u8)min((u16)targetPowerOfdm.tPow2x[i], + minCtlPower); + } + break; + case CTL_5GHT20: + case CTL_2GHT20: + for (i = 0; i < ARRAY_SIZE(targetPowerHt20.tPow2x); i++) { + targetPowerHt20.tPow2x[i] = + (u8)min((u16)targetPowerHt20.tPow2x[i], + minCtlPower); + } + break; + case CTL_11B_EXT: + targetPowerCckExt.tPow2x[0] = + (u8)min((u16)targetPowerCckExt.tPow2x[0], + minCtlPower); + break; + case CTL_11A_EXT: + case CTL_11G_EXT: + targetPowerOfdmExt.tPow2x[0] = + (u8)min((u16)targetPowerOfdmExt.tPow2x[0], + minCtlPower); + break; + case CTL_5GHT40: + case CTL_2GHT40: + for (i = 0; i < ARRAY_SIZE(targetPowerHt40.tPow2x); i++) { + targetPowerHt40.tPow2x[i] = + (u8)min((u16)targetPowerHt40.tPow2x[i], + minCtlPower); + } + break; + default: + break; + } + } + + /* Now set the rates array */ + + ratesArray[rate6mb] = + ratesArray[rate9mb] = + ratesArray[rate12mb] = + ratesArray[rate18mb] = + ratesArray[rate24mb] = targetPowerOfdm.tPow2x[0]; + + ratesArray[rate36mb] = targetPowerOfdm.tPow2x[1]; + ratesArray[rate48mb] = targetPowerOfdm.tPow2x[2]; + ratesArray[rate54mb] = targetPowerOfdm.tPow2x[3]; + ratesArray[rateXr] = targetPowerOfdm.tPow2x[0]; + + for (i = 0; i < ARRAY_SIZE(targetPowerHt20.tPow2x); i++) + ratesArray[rateHt20_0 + i] = targetPowerHt20.tPow2x[i]; + + if (IS_CHAN_2GHZ(chan)) { + ratesArray[rate1l] = targetPowerCck.tPow2x[0]; + ratesArray[rate2s] = + ratesArray[rate2l] = targetPowerCck.tPow2x[1]; + ratesArray[rate5_5s] = + ratesArray[rate5_5l] = targetPowerCck.tPow2x[2]; + ratesArray[rate11s] = + ratesArray[rate11l] = targetPowerCck.tPow2x[3]; + } + if (IS_CHAN_HT40(chan)) { + for (i = 0; i < ARRAY_SIZE(targetPowerHt40.tPow2x); i++) + ratesArray[rateHt40_0 + i] = targetPowerHt40.tPow2x[i]; + + ratesArray[rateDupOfdm] = targetPowerHt40.tPow2x[0]; + ratesArray[rateDupCck] = targetPowerHt40.tPow2x[0]; + ratesArray[rateExtOfdm] = targetPowerOfdmExt.tPow2x[0]; + + if (IS_CHAN_2GHZ(chan)) + ratesArray[rateExtCck] = targetPowerCckExt.tPow2x[0]; + } + +#undef CMP_CTL +#undef CMP_NO_CTL +} + +static void ath9k_hw_ar9287_set_txpower(struct ath_hw *ah, + struct ath9k_channel *chan, u16 cfgCtl, + u8 twiceAntennaReduction, + u8 powerLimit, bool test) +{ + struct ath_regulatory *regulatory = ath9k_hw_regulatory(ah); + struct ar9287_eeprom *pEepData = &ah->eeprom.map9287; + struct modal_eep_ar9287_header *pModal = &pEepData->modalHeader; + int16_t ratesArray[Ar5416RateSize]; + u8 ht40PowerIncForPdadc = 2; + int i; + + memset(ratesArray, 0, sizeof(ratesArray)); + + if ((pEepData->baseEepHeader.version & AR9287_EEP_VER_MINOR_MASK) >= + AR9287_EEP_MINOR_VER_2) + ht40PowerIncForPdadc = pModal->ht40PowerIncForPdadc; + + ath9k_hw_set_ar9287_power_per_rate_table(ah, chan, + &ratesArray[0], cfgCtl, + twiceAntennaReduction, + powerLimit); + + ath9k_hw_set_ar9287_power_cal_table(ah, chan); + + regulatory->max_power_level = 0; + for (i = 0; i < ARRAY_SIZE(ratesArray); i++) { + if (ratesArray[i] > MAX_RATE_POWER) + ratesArray[i] = MAX_RATE_POWER; + + if (ratesArray[i] > regulatory->max_power_level) + regulatory->max_power_level = ratesArray[i]; + } + + ath9k_hw_update_regulatory_maxpower(ah); + + if (test) + return; + + for (i = 0; i < Ar5416RateSize; i++) + ratesArray[i] -= AR9287_PWR_TABLE_OFFSET_DB * 2; + + ENABLE_REGWRITE_BUFFER(ah); + + /* OFDM power per rate */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE1, + ATH9K_POW_SM(ratesArray[rate18mb], 24) + | ATH9K_POW_SM(ratesArray[rate12mb], 16) + | ATH9K_POW_SM(ratesArray[rate9mb], 8) + | ATH9K_POW_SM(ratesArray[rate6mb], 0)); + + REG_WRITE(ah, AR_PHY_POWER_TX_RATE2, + ATH9K_POW_SM(ratesArray[rate54mb], 24) + | ATH9K_POW_SM(ratesArray[rate48mb], 16) + | ATH9K_POW_SM(ratesArray[rate36mb], 8) + | ATH9K_POW_SM(ratesArray[rate24mb], 0)); + + /* CCK power per rate */ + if (IS_CHAN_2GHZ(chan)) { + REG_WRITE(ah, AR_PHY_POWER_TX_RATE3, + ATH9K_POW_SM(ratesArray[rate2s], 24) + | ATH9K_POW_SM(ratesArray[rate2l], 16) + | ATH9K_POW_SM(ratesArray[rateXr], 8) + | ATH9K_POW_SM(ratesArray[rate1l], 0)); + REG_WRITE(ah, AR_PHY_POWER_TX_RATE4, + ATH9K_POW_SM(ratesArray[rate11s], 24) + | ATH9K_POW_SM(ratesArray[rate11l], 16) + | ATH9K_POW_SM(ratesArray[rate5_5s], 8) + | ATH9K_POW_SM(ratesArray[rate5_5l], 0)); + } + + /* HT20 power per rate */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE5, + ATH9K_POW_SM(ratesArray[rateHt20_3], 24) + | ATH9K_POW_SM(ratesArray[rateHt20_2], 16) + | ATH9K_POW_SM(ratesArray[rateHt20_1], 8) + | ATH9K_POW_SM(ratesArray[rateHt20_0], 0)); + + REG_WRITE(ah, AR_PHY_POWER_TX_RATE6, + ATH9K_POW_SM(ratesArray[rateHt20_7], 24) + | ATH9K_POW_SM(ratesArray[rateHt20_6], 16) + | ATH9K_POW_SM(ratesArray[rateHt20_5], 8) + | ATH9K_POW_SM(ratesArray[rateHt20_4], 0)); + + /* HT40 power per rate */ + if (IS_CHAN_HT40(chan)) { + if (ath9k_hw_ar9287_get_eeprom(ah, EEP_OL_PWRCTRL)) { + REG_WRITE(ah, AR_PHY_POWER_TX_RATE7, + ATH9K_POW_SM(ratesArray[rateHt40_3], 24) + | ATH9K_POW_SM(ratesArray[rateHt40_2], 16) + | ATH9K_POW_SM(ratesArray[rateHt40_1], 8) + | ATH9K_POW_SM(ratesArray[rateHt40_0], 0)); + + REG_WRITE(ah, AR_PHY_POWER_TX_RATE8, + ATH9K_POW_SM(ratesArray[rateHt40_7], 24) + | ATH9K_POW_SM(ratesArray[rateHt40_6], 16) + | ATH9K_POW_SM(ratesArray[rateHt40_5], 8) + | ATH9K_POW_SM(ratesArray[rateHt40_4], 0)); + } else { + REG_WRITE(ah, AR_PHY_POWER_TX_RATE7, + ATH9K_POW_SM(ratesArray[rateHt40_3] + + ht40PowerIncForPdadc, 24) + | ATH9K_POW_SM(ratesArray[rateHt40_2] + + ht40PowerIncForPdadc, 16) + | ATH9K_POW_SM(ratesArray[rateHt40_1] + + ht40PowerIncForPdadc, 8) + | ATH9K_POW_SM(ratesArray[rateHt40_0] + + ht40PowerIncForPdadc, 0)); + + REG_WRITE(ah, AR_PHY_POWER_TX_RATE8, + ATH9K_POW_SM(ratesArray[rateHt40_7] + + ht40PowerIncForPdadc, 24) + | ATH9K_POW_SM(ratesArray[rateHt40_6] + + ht40PowerIncForPdadc, 16) + | ATH9K_POW_SM(ratesArray[rateHt40_5] + + ht40PowerIncForPdadc, 8) + | ATH9K_POW_SM(ratesArray[rateHt40_4] + + ht40PowerIncForPdadc, 0)); + } + + /* Dup/Ext power per rate */ + REG_WRITE(ah, AR_PHY_POWER_TX_RATE9, + ATH9K_POW_SM(ratesArray[rateExtOfdm], 24) + | ATH9K_POW_SM(ratesArray[rateExtCck], 16) + | ATH9K_POW_SM(ratesArray[rateDupOfdm], 8) + | ATH9K_POW_SM(ratesArray[rateDupCck], 0)); + } + REGWRITE_BUFFER_FLUSH(ah); +} + +static void ath9k_hw_ar9287_set_board_values(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + struct ar9287_eeprom *eep = &ah->eeprom.map9287; + struct modal_eep_ar9287_header *pModal = &eep->modalHeader; + u32 regChainOffset, regval; + u8 txRxAttenLocal; + int i; + + pModal = &eep->modalHeader; + + REG_WRITE(ah, AR_PHY_SWITCH_COM, pModal->antCtrlCommon); + + for (i = 0; i < AR9287_MAX_CHAINS; i++) { + regChainOffset = i * 0x1000; + + REG_WRITE(ah, AR_PHY_SWITCH_CHAIN_0 + regChainOffset, + pModal->antCtrlChain[i]); + + REG_WRITE(ah, AR_PHY_TIMING_CTRL4(0) + regChainOffset, + (REG_READ(ah, AR_PHY_TIMING_CTRL4(0) + regChainOffset) + & ~(AR_PHY_TIMING_CTRL4_IQCORR_Q_Q_COFF | + AR_PHY_TIMING_CTRL4_IQCORR_Q_I_COFF)) | + SM(pModal->iqCalICh[i], + AR_PHY_TIMING_CTRL4_IQCORR_Q_I_COFF) | + SM(pModal->iqCalQCh[i], + AR_PHY_TIMING_CTRL4_IQCORR_Q_Q_COFF)); + + txRxAttenLocal = pModal->txRxAttenCh[i]; + + REG_RMW_FIELD(ah, AR_PHY_GAIN_2GHZ + regChainOffset, + AR_PHY_GAIN_2GHZ_XATTEN1_MARGIN, + pModal->bswMargin[i]); + REG_RMW_FIELD(ah, AR_PHY_GAIN_2GHZ + regChainOffset, + AR_PHY_GAIN_2GHZ_XATTEN1_DB, + pModal->bswAtten[i]); + REG_RMW_FIELD(ah, AR_PHY_RXGAIN + regChainOffset, + AR9280_PHY_RXGAIN_TXRX_ATTEN, + txRxAttenLocal); + REG_RMW_FIELD(ah, AR_PHY_RXGAIN + regChainOffset, + AR9280_PHY_RXGAIN_TXRX_MARGIN, + pModal->rxTxMarginCh[i]); + } + + + if (IS_CHAN_HT40(chan)) + REG_RMW_FIELD(ah, AR_PHY_SETTLING, + AR_PHY_SETTLING_SWITCH, pModal->swSettleHt40); + else + REG_RMW_FIELD(ah, AR_PHY_SETTLING, + AR_PHY_SETTLING_SWITCH, pModal->switchSettling); + + REG_RMW_FIELD(ah, AR_PHY_DESIRED_SZ, + AR_PHY_DESIRED_SZ_ADC, pModal->adcDesiredSize); + + REG_WRITE(ah, AR_PHY_RF_CTL4, + SM(pModal->txEndToXpaOff, AR_PHY_RF_CTL4_TX_END_XPAA_OFF) + | SM(pModal->txEndToXpaOff, AR_PHY_RF_CTL4_TX_END_XPAB_OFF) + | SM(pModal->txFrameToXpaOn, AR_PHY_RF_CTL4_FRAME_XPAA_ON) + | SM(pModal->txFrameToXpaOn, AR_PHY_RF_CTL4_FRAME_XPAB_ON)); + + REG_RMW_FIELD(ah, AR_PHY_RF_CTL3, + AR_PHY_TX_END_TO_A2_RX_ON, pModal->txEndToRxOn); + + REG_RMW_FIELD(ah, AR_PHY_CCA, + AR9280_PHY_CCA_THRESH62, pModal->thresh62); + REG_RMW_FIELD(ah, AR_PHY_EXT_CCA0, + AR_PHY_EXT_CCA0_THRESH62, pModal->thresh62); + + regval = REG_READ(ah, AR9287_AN_RF2G3_CH0); + regval &= ~(AR9287_AN_RF2G3_DB1 | + AR9287_AN_RF2G3_DB2 | + AR9287_AN_RF2G3_OB_CCK | + AR9287_AN_RF2G3_OB_PSK | + AR9287_AN_RF2G3_OB_QAM | + AR9287_AN_RF2G3_OB_PAL_OFF); + regval |= (SM(pModal->db1, AR9287_AN_RF2G3_DB1) | + SM(pModal->db2, AR9287_AN_RF2G3_DB2) | + SM(pModal->ob_cck, AR9287_AN_RF2G3_OB_CCK) | + SM(pModal->ob_psk, AR9287_AN_RF2G3_OB_PSK) | + SM(pModal->ob_qam, AR9287_AN_RF2G3_OB_QAM) | + SM(pModal->ob_pal_off, AR9287_AN_RF2G3_OB_PAL_OFF)); + + ath9k_hw_analog_shift_regwrite(ah, AR9287_AN_RF2G3_CH0, regval); + + regval = REG_READ(ah, AR9287_AN_RF2G3_CH1); + regval &= ~(AR9287_AN_RF2G3_DB1 | + AR9287_AN_RF2G3_DB2 | + AR9287_AN_RF2G3_OB_CCK | + AR9287_AN_RF2G3_OB_PSK | + AR9287_AN_RF2G3_OB_QAM | + AR9287_AN_RF2G3_OB_PAL_OFF); + regval |= (SM(pModal->db1, AR9287_AN_RF2G3_DB1) | + SM(pModal->db2, AR9287_AN_RF2G3_DB2) | + SM(pModal->ob_cck, AR9287_AN_RF2G3_OB_CCK) | + SM(pModal->ob_psk, AR9287_AN_RF2G3_OB_PSK) | + SM(pModal->ob_qam, AR9287_AN_RF2G3_OB_QAM) | + SM(pModal->ob_pal_off, AR9287_AN_RF2G3_OB_PAL_OFF)); + + ath9k_hw_analog_shift_regwrite(ah, AR9287_AN_RF2G3_CH1, regval); + + REG_RMW_FIELD(ah, AR_PHY_RF_CTL2, + AR_PHY_TX_END_DATA_START, pModal->txFrameToDataStart); + REG_RMW_FIELD(ah, AR_PHY_RF_CTL2, + AR_PHY_TX_END_PA_ON, pModal->txFrameToPaOn); + + ath9k_hw_analog_shift_rmw(ah, AR9287_AN_TOP2, + AR9287_AN_TOP2_XPABIAS_LVL, + AR9287_AN_TOP2_XPABIAS_LVL_S, + pModal->xpaBiasLvl); +} + +static u16 ath9k_hw_ar9287_get_spur_channel(struct ath_hw *ah, + u16 i, bool is2GHz) +{ +#define EEP_MAP9287_SPURCHAN \ + (ah->eeprom.map9287.modalHeader.spurChans[i].spurChan) + + struct ath_common *common = ath9k_hw_common(ah); + u16 spur_val = AR_NO_SPUR; + + ath_dbg(common, ANI, "Getting spur idx:%d is2Ghz:%d val:%x\n", + i, is2GHz, ah->config.spurchans[i][is2GHz]); + + switch (ah->config.spurmode) { + case SPUR_DISABLE: + break; + case SPUR_ENABLE_IOCTL: + spur_val = ah->config.spurchans[i][is2GHz]; + ath_dbg(common, ANI, "Getting spur val from new loc. %d\n", + spur_val); + break; + case SPUR_ENABLE_EEPROM: + spur_val = EEP_MAP9287_SPURCHAN; + break; + } + + return spur_val; + +#undef EEP_MAP9287_SPURCHAN +} + +const struct eeprom_ops eep_ar9287_ops = { + .check_eeprom = ath9k_hw_ar9287_check_eeprom, + .get_eeprom = ath9k_hw_ar9287_get_eeprom, + .fill_eeprom = ath9k_hw_ar9287_fill_eeprom, + .dump_eeprom = ath9k_hw_ar9287_dump_eeprom, + .get_eeprom_ver = ath9k_hw_ar9287_get_eeprom_ver, + .get_eeprom_rev = ath9k_hw_ar9287_get_eeprom_rev, + .set_board_values = ath9k_hw_ar9287_set_board_values, + .set_txpower = ath9k_hw_ar9287_set_txpower, + .get_spur_channel = ath9k_hw_ar9287_get_spur_channel +}; diff --git a/drivers/net/wireless/ath/ath9k/eeprom_def.c b/drivers/net/wireless/ath/ath9k/eeprom_def.c new file mode 100644 index 0000000..43d6b0f --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/eeprom_def.c @@ -0,0 +1,1389 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "hw.h" +#include "ar9002_phy.h" + +static void ath9k_get_txgain_index(struct ath_hw *ah, + struct ath9k_channel *chan, + struct calDataPerFreqOpLoop *rawDatasetOpLoop, + u8 *calChans, u16 availPiers, u8 *pwr, u8 *pcdacIdx) +{ + u8 pcdac, i = 0; + u16 idxL = 0, idxR = 0, numPiers; + bool match; + struct chan_centers centers; + + ath9k_hw_get_channel_centers(ah, chan, ¢ers); + + for (numPiers = 0; numPiers < availPiers; numPiers++) + if (calChans[numPiers] == AR5416_BCHAN_UNUSED) + break; + + match = ath9k_hw_get_lower_upper_index( + (u8)FREQ2FBIN(centers.synth_center, IS_CHAN_2GHZ(chan)), + calChans, numPiers, &idxL, &idxR); + if (match) { + pcdac = rawDatasetOpLoop[idxL].pcdac[0][0]; + *pwr = rawDatasetOpLoop[idxL].pwrPdg[0][0]; + } else { + pcdac = rawDatasetOpLoop[idxR].pcdac[0][0]; + *pwr = (rawDatasetOpLoop[idxL].pwrPdg[0][0] + + rawDatasetOpLoop[idxR].pwrPdg[0][0])/2; + } + + while (pcdac > ah->originalGain[i] && + i < (AR9280_TX_GAIN_TABLE_SIZE - 1)) + i++; + + *pcdacIdx = i; +} + +static void ath9k_olc_get_pdadcs(struct ath_hw *ah, + u32 initTxGain, + int txPower, + u8 *pPDADCValues) +{ + u32 i; + u32 offset; + + REG_RMW_FIELD(ah, AR_PHY_TX_PWRCTRL6_0, + AR_PHY_TX_PWRCTRL_ERR_EST_MODE, 3); + REG_RMW_FIELD(ah, AR_PHY_TX_PWRCTRL6_1, + AR_PHY_TX_PWRCTRL_ERR_EST_MODE, 3); + + REG_RMW_FIELD(ah, AR_PHY_TX_PWRCTRL7, + AR_PHY_TX_PWRCTRL_INIT_TX_GAIN, initTxGain); + + offset = txPower; + for (i = 0; i < AR5416_NUM_PDADC_VALUES; i++) + if (i < offset) + pPDADCValues[i] = 0x0; + else + pPDADCValues[i] = 0xFF; +} + +static int ath9k_hw_def_get_eeprom_ver(struct ath_hw *ah) +{ + return ((ah->eeprom.def.baseEepHeader.version >> 12) & 0xF); +} + +static int ath9k_hw_def_get_eeprom_rev(struct ath_hw *ah) +{ + return ((ah->eeprom.def.baseEepHeader.version) & 0xFFF); +} + +#define SIZE_EEPROM_DEF (sizeof(struct ar5416_eeprom_def) / sizeof(u16)) + +static bool __ath9k_hw_def_fill_eeprom(struct ath_hw *ah) +{ + u16 *eep_data = (u16 *)&ah->eeprom.def; + int addr, ar5416_eep_start_loc = 0x100; + + for (addr = 0; addr < SIZE_EEPROM_DEF; addr++) { + if (!ath9k_hw_nvram_read(ah, addr + ar5416_eep_start_loc, + eep_data)) + return false; + eep_data++; + } + return true; +} + +static bool __ath9k_hw_usb_def_fill_eeprom(struct ath_hw *ah) +{ + u16 *eep_data = (u16 *)&ah->eeprom.def; + + ath9k_hw_usb_gen_fill_eeprom(ah, eep_data, + 0x100, SIZE_EEPROM_DEF); + return true; +} + +static bool ath9k_hw_def_fill_eeprom(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + + if (!ath9k_hw_use_flash(ah)) { + ath_dbg(common, EEPROM, "Reading from EEPROM, not flash\n"); + } + + if (common->bus_ops->ath_bus_type == ATH_USB) + return __ath9k_hw_usb_def_fill_eeprom(ah); + else + return __ath9k_hw_def_fill_eeprom(ah); +} + +#undef SIZE_EEPROM_DEF + +#if defined(CPTCFG_ATH9K_DEBUGFS) || defined(CPTCFG_ATH9K_HTC_DEBUGFS) +static u32 ath9k_def_dump_modal_eeprom(char *buf, u32 len, u32 size, + struct modal_eep_header *modal_hdr) +{ + PR_EEP("Chain0 Ant. Control", modal_hdr->antCtrlChain[0]); + PR_EEP("Chain1 Ant. Control", modal_hdr->antCtrlChain[1]); + PR_EEP("Chain2 Ant. Control", modal_hdr->antCtrlChain[2]); + PR_EEP("Ant. Common Control", modal_hdr->antCtrlCommon); + PR_EEP("Chain0 Ant. Gain", modal_hdr->antennaGainCh[0]); + PR_EEP("Chain1 Ant. Gain", modal_hdr->antennaGainCh[1]); + PR_EEP("Chain2 Ant. Gain", modal_hdr->antennaGainCh[2]); + PR_EEP("Switch Settle", modal_hdr->switchSettling); + PR_EEP("Chain0 TxRxAtten", modal_hdr->txRxAttenCh[0]); + PR_EEP("Chain1 TxRxAtten", modal_hdr->txRxAttenCh[1]); + PR_EEP("Chain2 TxRxAtten", modal_hdr->txRxAttenCh[2]); + PR_EEP("Chain0 RxTxMargin", modal_hdr->rxTxMarginCh[0]); + PR_EEP("Chain1 RxTxMargin", modal_hdr->rxTxMarginCh[1]); + PR_EEP("Chain2 RxTxMargin", modal_hdr->rxTxMarginCh[2]); + PR_EEP("ADC Desired size", modal_hdr->adcDesiredSize); + PR_EEP("PGA Desired size", modal_hdr->pgaDesiredSize); + PR_EEP("Chain0 xlna Gain", modal_hdr->xlnaGainCh[0]); + PR_EEP("Chain1 xlna Gain", modal_hdr->xlnaGainCh[1]); + PR_EEP("Chain2 xlna Gain", modal_hdr->xlnaGainCh[2]); + PR_EEP("txEndToXpaOff", modal_hdr->txEndToXpaOff); + PR_EEP("txEndToRxOn", modal_hdr->txEndToRxOn); + PR_EEP("txFrameToXpaOn", modal_hdr->txFrameToXpaOn); + PR_EEP("CCA Threshold)", modal_hdr->thresh62); + PR_EEP("Chain0 NF Threshold", modal_hdr->noiseFloorThreshCh[0]); + PR_EEP("Chain1 NF Threshold", modal_hdr->noiseFloorThreshCh[1]); + PR_EEP("Chain2 NF Threshold", modal_hdr->noiseFloorThreshCh[2]); + PR_EEP("xpdGain", modal_hdr->xpdGain); + PR_EEP("External PD", modal_hdr->xpd); + PR_EEP("Chain0 I Coefficient", modal_hdr->iqCalICh[0]); + PR_EEP("Chain1 I Coefficient", modal_hdr->iqCalICh[1]); + PR_EEP("Chain2 I Coefficient", modal_hdr->iqCalICh[2]); + PR_EEP("Chain0 Q Coefficient", modal_hdr->iqCalQCh[0]); + PR_EEP("Chain1 Q Coefficient", modal_hdr->iqCalQCh[1]); + PR_EEP("Chain2 Q Coefficient", modal_hdr->iqCalQCh[2]); + PR_EEP("pdGainOverlap", modal_hdr->pdGainOverlap); + PR_EEP("Chain0 OutputBias", modal_hdr->ob); + PR_EEP("Chain0 DriverBias", modal_hdr->db); + PR_EEP("xPA Bias Level", modal_hdr->xpaBiasLvl); + PR_EEP("2chain pwr decrease", modal_hdr->pwrDecreaseFor2Chain); + PR_EEP("3chain pwr decrease", modal_hdr->pwrDecreaseFor3Chain); + PR_EEP("txFrameToDataStart", modal_hdr->txFrameToDataStart); + PR_EEP("txFrameToPaOn", modal_hdr->txFrameToPaOn); + PR_EEP("HT40 Power Inc.", modal_hdr->ht40PowerIncForPdadc); + PR_EEP("Chain0 bswAtten", modal_hdr->bswAtten[0]); + PR_EEP("Chain1 bswAtten", modal_hdr->bswAtten[1]); + PR_EEP("Chain2 bswAtten", modal_hdr->bswAtten[2]); + PR_EEP("Chain0 bswMargin", modal_hdr->bswMargin[0]); + PR_EEP("Chain1 bswMargin", modal_hdr->bswMargin[1]); + PR_EEP("Chain2 bswMargin", modal_hdr->bswMargin[2]); + PR_EEP("HT40 Switch Settle", modal_hdr->swSettleHt40); + PR_EEP("Chain0 xatten2Db", modal_hdr->xatten2Db[0]); + PR_EEP("Chain1 xatten2Db", modal_hdr->xatten2Db[1]); + PR_EEP("Chain2 xatten2Db", modal_hdr->xatten2Db[2]); + PR_EEP("Chain0 xatten2Margin", modal_hdr->xatten2Margin[0]); + PR_EEP("Chain1 xatten2Margin", modal_hdr->xatten2Margin[1]); + PR_EEP("Chain2 xatten2Margin", modal_hdr->xatten2Margin[2]); + PR_EEP("Chain1 OutputBias", modal_hdr->ob_ch1); + PR_EEP("Chain1 DriverBias", modal_hdr->db_ch1); + PR_EEP("LNA Control", modal_hdr->lna_ctl); + PR_EEP("XPA Bias Freq0", modal_hdr->xpaBiasLvlFreq[0]); + PR_EEP("XPA Bias Freq1", modal_hdr->xpaBiasLvlFreq[1]); + PR_EEP("XPA Bias Freq2", modal_hdr->xpaBiasLvlFreq[2]); + + return len; +} + +static u32 ath9k_hw_def_dump_eeprom(struct ath_hw *ah, bool dump_base_hdr, + u8 *buf, u32 len, u32 size) +{ + struct ar5416_eeprom_def *eep = &ah->eeprom.def; + struct base_eep_header *pBase = &eep->baseEepHeader; + + if (!dump_base_hdr) { + len += snprintf(buf + len, size - len, + "%20s :\n", "2GHz modal Header"); + len = ath9k_def_dump_modal_eeprom(buf, len, size, + &eep->modalHeader[0]); + len += snprintf(buf + len, size - len, + "%20s :\n", "5GHz modal Header"); + len = ath9k_def_dump_modal_eeprom(buf, len, size, + &eep->modalHeader[1]); + goto out; + } + + PR_EEP("Major Version", pBase->version >> 12); + PR_EEP("Minor Version", pBase->version & 0xFFF); + PR_EEP("Checksum", pBase->checksum); + PR_EEP("Length", pBase->length); + PR_EEP("RegDomain1", pBase->regDmn[0]); + PR_EEP("RegDomain2", pBase->regDmn[1]); + PR_EEP("TX Mask", pBase->txMask); + PR_EEP("RX Mask", pBase->rxMask); + PR_EEP("Allow 5GHz", !!(pBase->opCapFlags & AR5416_OPFLAGS_11A)); + PR_EEP("Allow 2GHz", !!(pBase->opCapFlags & AR5416_OPFLAGS_11G)); + PR_EEP("Disable 2GHz HT20", !!(pBase->opCapFlags & + AR5416_OPFLAGS_N_2G_HT20)); + PR_EEP("Disable 2GHz HT40", !!(pBase->opCapFlags & + AR5416_OPFLAGS_N_2G_HT40)); + PR_EEP("Disable 5Ghz HT20", !!(pBase->opCapFlags & + AR5416_OPFLAGS_N_5G_HT20)); + PR_EEP("Disable 5Ghz HT40", !!(pBase->opCapFlags & + AR5416_OPFLAGS_N_5G_HT40)); + PR_EEP("Big Endian", !!(pBase->eepMisc & 0x01)); + PR_EEP("Cal Bin Major Ver", (pBase->binBuildNumber >> 24) & 0xFF); + PR_EEP("Cal Bin Minor Ver", (pBase->binBuildNumber >> 16) & 0xFF); + PR_EEP("Cal Bin Build", (pBase->binBuildNumber >> 8) & 0xFF); + PR_EEP("OpenLoop Power Ctrl", pBase->openLoopPwrCntl); + + len += snprintf(buf + len, size - len, "%20s : %pM\n", "MacAddress", + pBase->macAddr); + +out: + if (len > size) + len = size; + + return len; +} +#else +static u32 ath9k_hw_def_dump_eeprom(struct ath_hw *ah, bool dump_base_hdr, + u8 *buf, u32 len, u32 size) +{ + return 0; +} +#endif + + +static int ath9k_hw_def_check_eeprom(struct ath_hw *ah) +{ + struct ar5416_eeprom_def *eep = &ah->eeprom.def; + struct ath_common *common = ath9k_hw_common(ah); + u16 *eepdata, temp, magic, magic2; + u32 sum = 0, el; + bool need_swap = false; + int i, addr, size; + + if (!ath9k_hw_nvram_read(ah, AR5416_EEPROM_MAGIC_OFFSET, &magic)) { + ath_err(common, "Reading Magic # failed\n"); + return false; + } + + if (!ath9k_hw_use_flash(ah)) { + ath_dbg(common, EEPROM, "Read Magic = 0x%04X\n", magic); + + if (magic != AR5416_EEPROM_MAGIC) { + magic2 = swab16(magic); + + if (magic2 == AR5416_EEPROM_MAGIC) { + size = sizeof(struct ar5416_eeprom_def); + need_swap = true; + eepdata = (u16 *) (&ah->eeprom); + + for (addr = 0; addr < size / sizeof(u16); addr++) { + temp = swab16(*eepdata); + *eepdata = temp; + eepdata++; + } + } else { + ath_err(common, + "Invalid EEPROM Magic. Endianness mismatch.\n"); + return -EINVAL; + } + } + } + + ath_dbg(common, EEPROM, "need_swap = %s\n", + need_swap ? "True" : "False"); + + if (need_swap) + el = swab16(ah->eeprom.def.baseEepHeader.length); + else + el = ah->eeprom.def.baseEepHeader.length; + + if (el > sizeof(struct ar5416_eeprom_def)) + el = sizeof(struct ar5416_eeprom_def) / sizeof(u16); + else + el = el / sizeof(u16); + + eepdata = (u16 *)(&ah->eeprom); + + for (i = 0; i < el; i++) + sum ^= *eepdata++; + + if (need_swap) { + u32 integer, j; + u16 word; + + ath_dbg(common, EEPROM, + "EEPROM Endianness is not native.. Changing.\n"); + + word = swab16(eep->baseEepHeader.length); + eep->baseEepHeader.length = word; + + word = swab16(eep->baseEepHeader.checksum); + eep->baseEepHeader.checksum = word; + + word = swab16(eep->baseEepHeader.version); + eep->baseEepHeader.version = word; + + word = swab16(eep->baseEepHeader.regDmn[0]); + eep->baseEepHeader.regDmn[0] = word; + + word = swab16(eep->baseEepHeader.regDmn[1]); + eep->baseEepHeader.regDmn[1] = word; + + word = swab16(eep->baseEepHeader.rfSilent); + eep->baseEepHeader.rfSilent = word; + + word = swab16(eep->baseEepHeader.blueToothOptions); + eep->baseEepHeader.blueToothOptions = word; + + word = swab16(eep->baseEepHeader.deviceCap); + eep->baseEepHeader.deviceCap = word; + + for (j = 0; j < ARRAY_SIZE(eep->modalHeader); j++) { + struct modal_eep_header *pModal = + &eep->modalHeader[j]; + integer = swab32(pModal->antCtrlCommon); + pModal->antCtrlCommon = integer; + + for (i = 0; i < AR5416_MAX_CHAINS; i++) { + integer = swab32(pModal->antCtrlChain[i]); + pModal->antCtrlChain[i] = integer; + } + for (i = 0; i < 3; i++) { + word = swab16(pModal->xpaBiasLvlFreq[i]); + pModal->xpaBiasLvlFreq[i] = word; + } + + for (i = 0; i < AR_EEPROM_MODAL_SPURS; i++) { + word = swab16(pModal->spurChans[i].spurChan); + pModal->spurChans[i].spurChan = word; + } + } + } + + if (sum != 0xffff || ah->eep_ops->get_eeprom_ver(ah) != AR5416_EEP_VER || + ah->eep_ops->get_eeprom_rev(ah) < AR5416_EEP_NO_BACK_VER) { + ath_err(common, "Bad EEPROM checksum 0x%x or revision 0x%04x\n", + sum, ah->eep_ops->get_eeprom_ver(ah)); + return -EINVAL; + } + + /* Enable fixup for AR_AN_TOP2 if necessary */ + if ((ah->hw_version.devid == AR9280_DEVID_PCI) && + ((eep->baseEepHeader.version & 0xff) > 0x0a) && + (eep->baseEepHeader.pwdclkind == 0)) + ah->need_an_top2_fixup = true; + + if ((common->bus_ops->ath_bus_type == ATH_USB) && + (AR_SREV_9280(ah))) + eep->modalHeader[0].xpaBiasLvl = 0; + + return 0; +} + +static u32 ath9k_hw_def_get_eeprom(struct ath_hw *ah, + enum eeprom_param param) +{ + struct ar5416_eeprom_def *eep = &ah->eeprom.def; + struct modal_eep_header *pModal = eep->modalHeader; + struct base_eep_header *pBase = &eep->baseEepHeader; + int band = 0; + + switch (param) { + case EEP_NFTHRESH_5: + return pModal[0].noiseFloorThreshCh[0]; + case EEP_NFTHRESH_2: + return pModal[1].noiseFloorThreshCh[0]; + case EEP_MAC_LSW: + return get_unaligned_be16(pBase->macAddr); + case EEP_MAC_MID: + return get_unaligned_be16(pBase->macAddr + 2); + case EEP_MAC_MSW: + return get_unaligned_be16(pBase->macAddr + 4); + case EEP_REG_0: + return pBase->regDmn[0]; + case EEP_OP_CAP: + return pBase->deviceCap; + case EEP_OP_MODE: + return pBase->opCapFlags; + case EEP_RF_SILENT: + return pBase->rfSilent; + case EEP_OB_5: + return pModal[0].ob; + case EEP_DB_5: + return pModal[0].db; + case EEP_OB_2: + return pModal[1].ob; + case EEP_DB_2: + return pModal[1].db; + case EEP_MINOR_REV: + return AR5416_VER_MASK; + case EEP_TX_MASK: + return pBase->txMask; + case EEP_RX_MASK: + return pBase->rxMask; + case EEP_FSTCLK_5G: + return pBase->fastClk5g; + case EEP_RXGAIN_TYPE: + return pBase->rxGainType; + case EEP_TXGAIN_TYPE: + return pBase->txGainType; + case EEP_OL_PWRCTRL: + if (AR5416_VER_MASK >= AR5416_EEP_MINOR_VER_19) + return pBase->openLoopPwrCntl ? true : false; + else + return false; + case EEP_RC_CHAIN_MASK: + if (AR5416_VER_MASK >= AR5416_EEP_MINOR_VER_19) + return pBase->rcChainMask; + else + return 0; + case EEP_DAC_HPWR_5G: + if (AR5416_VER_MASK >= AR5416_EEP_MINOR_VER_20) + return pBase->dacHiPwrMode_5G; + else + return 0; + case EEP_FRAC_N_5G: + if (AR5416_VER_MASK >= AR5416_EEP_MINOR_VER_22) + return pBase->frac_n_5g; + else + return 0; + case EEP_PWR_TABLE_OFFSET: + if (AR5416_VER_MASK >= AR5416_EEP_MINOR_VER_21) + return pBase->pwr_table_offset; + else + return AR5416_PWR_TABLE_OFFSET_DB; + case EEP_ANTENNA_GAIN_2G: + band = 1; + /* fall through */ + case EEP_ANTENNA_GAIN_5G: + return max_t(u8, max_t(u8, + pModal[band].antennaGainCh[0], + pModal[band].antennaGainCh[1]), + pModal[band].antennaGainCh[2]); + default: + return 0; + } +} + +static void ath9k_hw_def_set_gain(struct ath_hw *ah, + struct modal_eep_header *pModal, + struct ar5416_eeprom_def *eep, + u8 txRxAttenLocal, int regChainOffset, int i) +{ + if (AR5416_VER_MASK >= AR5416_EEP_MINOR_VER_3) { + txRxAttenLocal = pModal->txRxAttenCh[i]; + + if (AR_SREV_9280_20_OR_LATER(ah)) { + REG_RMW_FIELD(ah, AR_PHY_GAIN_2GHZ + regChainOffset, + AR_PHY_GAIN_2GHZ_XATTEN1_MARGIN, + pModal->bswMargin[i]); + REG_RMW_FIELD(ah, AR_PHY_GAIN_2GHZ + regChainOffset, + AR_PHY_GAIN_2GHZ_XATTEN1_DB, + pModal->bswAtten[i]); + REG_RMW_FIELD(ah, AR_PHY_GAIN_2GHZ + regChainOffset, + AR_PHY_GAIN_2GHZ_XATTEN2_MARGIN, + pModal->xatten2Margin[i]); + REG_RMW_FIELD(ah, AR_PHY_GAIN_2GHZ + regChainOffset, + AR_PHY_GAIN_2GHZ_XATTEN2_DB, + pModal->xatten2Db[i]); + } else { + REG_WRITE(ah, AR_PHY_GAIN_2GHZ + regChainOffset, + (REG_READ(ah, AR_PHY_GAIN_2GHZ + regChainOffset) & + ~AR_PHY_GAIN_2GHZ_BSW_MARGIN) + | SM(pModal-> bswMargin[i], + AR_PHY_GAIN_2GHZ_BSW_MARGIN)); + REG_WRITE(ah, AR_PHY_GAIN_2GHZ + regChainOffset, + (REG_READ(ah, AR_PHY_GAIN_2GHZ + regChainOffset) & + ~AR_PHY_GAIN_2GHZ_BSW_ATTEN) + | SM(pModal->bswAtten[i], + AR_PHY_GAIN_2GHZ_BSW_ATTEN)); + } + } + + if (AR_SREV_9280_20_OR_LATER(ah)) { + REG_RMW_FIELD(ah, + AR_PHY_RXGAIN + regChainOffset, + AR9280_PHY_RXGAIN_TXRX_ATTEN, txRxAttenLocal); + REG_RMW_FIELD(ah, + AR_PHY_RXGAIN + regChainOffset, + AR9280_PHY_RXGAIN_TXRX_MARGIN, pModal->rxTxMarginCh[i]); + } else { + REG_WRITE(ah, + AR_PHY_RXGAIN + regChainOffset, + (REG_READ(ah, AR_PHY_RXGAIN + regChainOffset) & + ~AR_PHY_RXGAIN_TXRX_ATTEN) + | SM(txRxAttenLocal, AR_PHY_RXGAIN_TXRX_ATTEN)); + REG_WRITE(ah, + AR_PHY_GAIN_2GHZ + regChainOffset, + (REG_READ(ah, AR_PHY_GAIN_2GHZ + regChainOffset) & + ~AR_PHY_GAIN_2GHZ_RXTX_MARGIN) | + SM(pModal->rxTxMarginCh[i], AR_PHY_GAIN_2GHZ_RXTX_MARGIN)); + } +} + +static void ath9k_hw_def_set_board_values(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + struct modal_eep_header *pModal; + struct ar5416_eeprom_def *eep = &ah->eeprom.def; + int i, regChainOffset; + u8 txRxAttenLocal; + + pModal = &(eep->modalHeader[IS_CHAN_2GHZ(chan)]); + txRxAttenLocal = IS_CHAN_2GHZ(chan) ? 23 : 44; + + REG_WRITE(ah, AR_PHY_SWITCH_COM, pModal->antCtrlCommon & 0xffff); + + for (i = 0; i < AR5416_MAX_CHAINS; i++) { + if (AR_SREV_9280(ah)) { + if (i >= 2) + break; + } + + if ((ah->rxchainmask == 5 || ah->txchainmask == 5) && (i != 0)) + regChainOffset = (i == 1) ? 0x2000 : 0x1000; + else + regChainOffset = i * 0x1000; + + REG_WRITE(ah, AR_PHY_SWITCH_CHAIN_0 + regChainOffset, + pModal->antCtrlChain[i]); + + REG_WRITE(ah, AR_PHY_TIMING_CTRL4(0) + regChainOffset, + (REG_READ(ah, AR_PHY_TIMING_CTRL4(0) + regChainOffset) & + ~(AR_PHY_TIMING_CTRL4_IQCORR_Q_Q_COFF | + AR_PHY_TIMING_CTRL4_IQCORR_Q_I_COFF)) | + SM(pModal->iqCalICh[i], + AR_PHY_TIMING_CTRL4_IQCORR_Q_I_COFF) | + SM(pModal->iqCalQCh[i], + AR_PHY_TIMING_CTRL4_IQCORR_Q_Q_COFF)); + + ath9k_hw_def_set_gain(ah, pModal, eep, txRxAttenLocal, + regChainOffset, i); + } + + if (AR_SREV_9280_20_OR_LATER(ah)) { + if (IS_CHAN_2GHZ(chan)) { + ath9k_hw_analog_shift_rmw(ah, AR_AN_RF2G1_CH0, + AR_AN_RF2G1_CH0_OB, + AR_AN_RF2G1_CH0_OB_S, + pModal->ob); + ath9k_hw_analog_shift_rmw(ah, AR_AN_RF2G1_CH0, + AR_AN_RF2G1_CH0_DB, + AR_AN_RF2G1_CH0_DB_S, + pModal->db); + ath9k_hw_analog_shift_rmw(ah, AR_AN_RF2G1_CH1, + AR_AN_RF2G1_CH1_OB, + AR_AN_RF2G1_CH1_OB_S, + pModal->ob_ch1); + ath9k_hw_analog_shift_rmw(ah, AR_AN_RF2G1_CH1, + AR_AN_RF2G1_CH1_DB, + AR_AN_RF2G1_CH1_DB_S, + pModal->db_ch1); + } else { + ath9k_hw_analog_shift_rmw(ah, AR_AN_RF5G1_CH0, + AR_AN_RF5G1_CH0_OB5, + AR_AN_RF5G1_CH0_OB5_S, + pModal->ob); + ath9k_hw_analog_shift_rmw(ah, AR_AN_RF5G1_CH0, + AR_AN_RF5G1_CH0_DB5, + AR_AN_RF5G1_CH0_DB5_S, + pModal->db); + ath9k_hw_analog_shift_rmw(ah, AR_AN_RF5G1_CH1, + AR_AN_RF5G1_CH1_OB5, + AR_AN_RF5G1_CH1_OB5_S, + pModal->ob_ch1); + ath9k_hw_analog_shift_rmw(ah, AR_AN_RF5G1_CH1, + AR_AN_RF5G1_CH1_DB5, + AR_AN_RF5G1_CH1_DB5_S, + pModal->db_ch1); + } + ath9k_hw_analog_shift_rmw(ah, AR_AN_TOP2, + AR_AN_TOP2_XPABIAS_LVL, + AR_AN_TOP2_XPABIAS_LVL_S, + pModal->xpaBiasLvl); + ath9k_hw_analog_shift_rmw(ah, AR_AN_TOP2, + AR_AN_TOP2_LOCALBIAS, + AR_AN_TOP2_LOCALBIAS_S, + !!(pModal->lna_ctl & + LNA_CTL_LOCAL_BIAS)); + REG_RMW_FIELD(ah, AR_PHY_XPA_CFG, AR_PHY_FORCE_XPA_CFG, + !!(pModal->lna_ctl & LNA_CTL_FORCE_XPA)); + } + + REG_RMW_FIELD(ah, AR_PHY_SETTLING, AR_PHY_SETTLING_SWITCH, + pModal->switchSettling); + REG_RMW_FIELD(ah, AR_PHY_DESIRED_SZ, AR_PHY_DESIRED_SZ_ADC, + pModal->adcDesiredSize); + + if (!AR_SREV_9280_20_OR_LATER(ah)) + REG_RMW_FIELD(ah, AR_PHY_DESIRED_SZ, + AR_PHY_DESIRED_SZ_PGA, + pModal->pgaDesiredSize); + + REG_WRITE(ah, AR_PHY_RF_CTL4, + SM(pModal->txEndToXpaOff, AR_PHY_RF_CTL4_TX_END_XPAA_OFF) + | SM(pModal->txEndToXpaOff, + AR_PHY_RF_CTL4_TX_END_XPAB_OFF) + | SM(pModal->txFrameToXpaOn, + AR_PHY_RF_CTL4_FRAME_XPAA_ON) + | SM(pModal->txFrameToXpaOn, + AR_PHY_RF_CTL4_FRAME_XPAB_ON)); + + REG_RMW_FIELD(ah, AR_PHY_RF_CTL3, AR_PHY_TX_END_TO_A2_RX_ON, + pModal->txEndToRxOn); + + if (AR_SREV_9280_20_OR_LATER(ah)) { + REG_RMW_FIELD(ah, AR_PHY_CCA, AR9280_PHY_CCA_THRESH62, + pModal->thresh62); + REG_RMW_FIELD(ah, AR_PHY_EXT_CCA0, + AR_PHY_EXT_CCA0_THRESH62, + pModal->thresh62); + } else { + REG_RMW_FIELD(ah, AR_PHY_CCA, AR_PHY_CCA_THRESH62, + pModal->thresh62); + REG_RMW_FIELD(ah, AR_PHY_EXT_CCA, + AR_PHY_EXT_CCA_THRESH62, + pModal->thresh62); + } + + if (AR5416_VER_MASK >= AR5416_EEP_MINOR_VER_2) { + REG_RMW_FIELD(ah, AR_PHY_RF_CTL2, + AR_PHY_TX_END_DATA_START, + pModal->txFrameToDataStart); + REG_RMW_FIELD(ah, AR_PHY_RF_CTL2, AR_PHY_TX_END_PA_ON, + pModal->txFrameToPaOn); + } + + if (AR5416_VER_MASK >= AR5416_EEP_MINOR_VER_3) { + if (IS_CHAN_HT40(chan)) + REG_RMW_FIELD(ah, AR_PHY_SETTLING, + AR_PHY_SETTLING_SWITCH, + pModal->swSettleHt40); + } + + if (AR_SREV_9280_20_OR_LATER(ah) && + AR5416_VER_MASK >= AR5416_EEP_MINOR_VER_19) + REG_RMW_FIELD(ah, AR_PHY_CCK_TX_CTRL, + AR_PHY_CCK_TX_CTRL_TX_DAC_SCALE_CCK, + pModal->miscBits); + + + if (AR_SREV_9280_20(ah) && AR5416_VER_MASK >= AR5416_EEP_MINOR_VER_20) { + if (IS_CHAN_2GHZ(chan)) + REG_RMW_FIELD(ah, AR_AN_TOP1, AR_AN_TOP1_DACIPMODE, + eep->baseEepHeader.dacLpMode); + else if (eep->baseEepHeader.dacHiPwrMode_5G) + REG_RMW_FIELD(ah, AR_AN_TOP1, AR_AN_TOP1_DACIPMODE, 0); + else + REG_RMW_FIELD(ah, AR_AN_TOP1, AR_AN_TOP1_DACIPMODE, + eep->baseEepHeader.dacLpMode); + + udelay(100); + + REG_RMW_FIELD(ah, AR_PHY_FRAME_CTL, AR_PHY_FRAME_CTL_TX_CLIP, + pModal->miscBits >> 2); + + REG_RMW_FIELD(ah, AR_PHY_TX_PWRCTRL9, + AR_PHY_TX_DESIRED_SCALE_CCK, + eep->baseEepHeader.desiredScaleCCK); + } +} + +static void ath9k_hw_def_set_addac(struct ath_hw *ah, + struct ath9k_channel *chan) +{ +#define XPA_LVL_FREQ(cnt) (pModal->xpaBiasLvlFreq[cnt]) + struct modal_eep_header *pModal; + struct ar5416_eeprom_def *eep = &ah->eeprom.def; + u8 biaslevel; + + if (ah->hw_version.macVersion != AR_SREV_VERSION_9160) + return; + + if (ah->eep_ops->get_eeprom_rev(ah) < AR5416_EEP_MINOR_VER_7) + return; + + pModal = &(eep->modalHeader[IS_CHAN_2GHZ(chan)]); + + if (pModal->xpaBiasLvl != 0xff) { + biaslevel = pModal->xpaBiasLvl; + } else { + u16 resetFreqBin, freqBin, freqCount = 0; + struct chan_centers centers; + + ath9k_hw_get_channel_centers(ah, chan, ¢ers); + + resetFreqBin = FREQ2FBIN(centers.synth_center, + IS_CHAN_2GHZ(chan)); + freqBin = XPA_LVL_FREQ(0) & 0xff; + biaslevel = (u8) (XPA_LVL_FREQ(0) >> 14); + + freqCount++; + + while (freqCount < 3) { + if (XPA_LVL_FREQ(freqCount) == 0x0) + break; + + freqBin = XPA_LVL_FREQ(freqCount) & 0xff; + if (resetFreqBin >= freqBin) + biaslevel = (u8)(XPA_LVL_FREQ(freqCount) >> 14); + else + break; + freqCount++; + } + } + + if (IS_CHAN_2GHZ(chan)) { + INI_RA(&ah->iniAddac, 7, 1) = (INI_RA(&ah->iniAddac, + 7, 1) & (~0x18)) | biaslevel << 3; + } else { + INI_RA(&ah->iniAddac, 6, 1) = (INI_RA(&ah->iniAddac, + 6, 1) & (~0xc0)) | biaslevel << 6; + } +#undef XPA_LVL_FREQ +} + +static int16_t ath9k_change_gain_boundary_setting(struct ath_hw *ah, + u16 *gb, + u16 numXpdGain, + u16 pdGainOverlap_t2, + int8_t pwr_table_offset, + int16_t *diff) + +{ + u16 k; + + /* Prior to writing the boundaries or the pdadc vs. power table + * into the chip registers the default starting point on the pdadc + * vs. power table needs to be checked and the curve boundaries + * adjusted accordingly + */ + if (AR_SREV_9280_20_OR_LATER(ah)) { + u16 gb_limit; + + if (AR5416_PWR_TABLE_OFFSET_DB != pwr_table_offset) { + /* get the difference in dB */ + *diff = (u16)(pwr_table_offset - AR5416_PWR_TABLE_OFFSET_DB); + /* get the number of half dB steps */ + *diff *= 2; + /* change the original gain boundary settings + * by the number of half dB steps + */ + for (k = 0; k < numXpdGain; k++) + gb[k] = (u16)(gb[k] - *diff); + } + /* Because of a hardware limitation, ensure the gain boundary + * is not larger than (63 - overlap) + */ + gb_limit = (u16)(MAX_RATE_POWER - pdGainOverlap_t2); + + for (k = 0; k < numXpdGain; k++) + gb[k] = (u16)min(gb_limit, gb[k]); + } + + return *diff; +} + +static void ath9k_adjust_pdadc_values(struct ath_hw *ah, + int8_t pwr_table_offset, + int16_t diff, + u8 *pdadcValues) +{ +#define NUM_PDADC(diff) (AR5416_NUM_PDADC_VALUES - diff) + u16 k; + + /* If this is a board that has a pwrTableOffset that differs from + * the default AR5416_PWR_TABLE_OFFSET_DB then the start of the + * pdadc vs pwr table needs to be adjusted prior to writing to the + * chip. + */ + if (AR_SREV_9280_20_OR_LATER(ah)) { + if (AR5416_PWR_TABLE_OFFSET_DB != pwr_table_offset) { + /* shift the table to start at the new offset */ + for (k = 0; k < (u16)NUM_PDADC(diff); k++ ) { + pdadcValues[k] = pdadcValues[k + diff]; + } + + /* fill the back of the table */ + for (k = (u16)NUM_PDADC(diff); k < NUM_PDADC(0); k++) { + pdadcValues[k] = pdadcValues[NUM_PDADC(diff)]; + } + } + } +#undef NUM_PDADC +} + +static void ath9k_hw_set_def_power_cal_table(struct ath_hw *ah, + struct ath9k_channel *chan) +{ +#define SM_PD_GAIN(x) SM(0x38, AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_##x) +#define SM_PDGAIN_B(x, y) \ + SM((gainBoundaries[x]), AR_PHY_TPCRG5_PD_GAIN_BOUNDARY_##y) + struct ath_common *common = ath9k_hw_common(ah); + struct ar5416_eeprom_def *pEepData = &ah->eeprom.def; + struct cal_data_per_freq *pRawDataset; + u8 *pCalBChans = NULL; + u16 pdGainOverlap_t2; + static u8 pdadcValues[AR5416_NUM_PDADC_VALUES]; + u16 gainBoundaries[AR5416_PD_GAINS_IN_MASK]; + u16 numPiers, i, j; + int16_t diff = 0; + u16 numXpdGain, xpdMask; + u16 xpdGainValues[AR5416_NUM_PD_GAINS] = { 0, 0, 0, 0 }; + u32 reg32, regOffset, regChainOffset; + int16_t modalIdx; + int8_t pwr_table_offset; + + modalIdx = IS_CHAN_2GHZ(chan) ? 1 : 0; + xpdMask = pEepData->modalHeader[modalIdx].xpdGain; + + pwr_table_offset = ah->eep_ops->get_eeprom(ah, EEP_PWR_TABLE_OFFSET); + + if ((pEepData->baseEepHeader.version & AR5416_EEP_VER_MINOR_MASK) >= + AR5416_EEP_MINOR_VER_2) { + pdGainOverlap_t2 = + pEepData->modalHeader[modalIdx].pdGainOverlap; + } else { + pdGainOverlap_t2 = (u16)(MS(REG_READ(ah, AR_PHY_TPCRG5), + AR_PHY_TPCRG5_PD_GAIN_OVERLAP)); + } + + if (IS_CHAN_2GHZ(chan)) { + pCalBChans = pEepData->calFreqPier2G; + numPiers = AR5416_NUM_2G_CAL_PIERS; + } else { + pCalBChans = pEepData->calFreqPier5G; + numPiers = AR5416_NUM_5G_CAL_PIERS; + } + + if (OLC_FOR_AR9280_20_LATER && IS_CHAN_2GHZ(chan)) { + pRawDataset = pEepData->calPierData2G[0]; + ah->initPDADC = ((struct calDataPerFreqOpLoop *) + pRawDataset)->vpdPdg[0][0]; + } + + numXpdGain = 0; + + for (i = 1; i <= AR5416_PD_GAINS_IN_MASK; i++) { + if ((xpdMask >> (AR5416_PD_GAINS_IN_MASK - i)) & 1) { + if (numXpdGain >= AR5416_NUM_PD_GAINS) + break; + xpdGainValues[numXpdGain] = + (u16)(AR5416_PD_GAINS_IN_MASK - i); + numXpdGain++; + } + } + + REG_RMW_FIELD(ah, AR_PHY_TPCRG1, AR_PHY_TPCRG1_NUM_PD_GAIN, + (numXpdGain - 1) & 0x3); + REG_RMW_FIELD(ah, AR_PHY_TPCRG1, AR_PHY_TPCRG1_PD_GAIN_1, + xpdGainValues[0]); + REG_RMW_FIELD(ah, AR_PHY_TPCRG1, AR_PHY_TPCRG1_PD_GAIN_2, + xpdGainValues[1]); + REG_RMW_FIELD(ah, AR_PHY_TPCRG1, AR_PHY_TPCRG1_PD_GAIN_3, + xpdGainValues[2]); + + for (i = 0; i < AR5416_MAX_CHAINS; i++) { + if ((ah->rxchainmask == 5 || ah->txchainmask == 5) && + (i != 0)) { + regChainOffset = (i == 1) ? 0x2000 : 0x1000; + } else + regChainOffset = i * 0x1000; + + if (pEepData->baseEepHeader.txMask & (1 << i)) { + if (IS_CHAN_2GHZ(chan)) + pRawDataset = pEepData->calPierData2G[i]; + else + pRawDataset = pEepData->calPierData5G[i]; + + + if (OLC_FOR_AR9280_20_LATER) { + u8 pcdacIdx; + u8 txPower; + + ath9k_get_txgain_index(ah, chan, + (struct calDataPerFreqOpLoop *)pRawDataset, + pCalBChans, numPiers, &txPower, &pcdacIdx); + ath9k_olc_get_pdadcs(ah, pcdacIdx, + txPower/2, pdadcValues); + } else { + ath9k_hw_get_gain_boundaries_pdadcs(ah, + chan, pRawDataset, + pCalBChans, numPiers, + pdGainOverlap_t2, + gainBoundaries, + pdadcValues, + numXpdGain); + } + + diff = ath9k_change_gain_boundary_setting(ah, + gainBoundaries, + numXpdGain, + pdGainOverlap_t2, + pwr_table_offset, + &diff); + + ENABLE_REGWRITE_BUFFER(ah); + + if (OLC_FOR_AR9280_20_LATER) { + REG_WRITE(ah, + AR_PHY_TPCRG5 + regChainOffset, + SM(0x6, + AR_PHY_TPCRG5_PD_GAIN_OVERLAP) | + SM_PD_GAIN(1) | SM_PD_GAIN(2) | + SM_PD_GAIN(3) | SM_PD_GAIN(4)); + } else { + REG_WRITE(ah, + AR_PHY_TPCRG5 + regChainOffset, + SM(pdGainOverlap_t2, + AR_PHY_TPCRG5_PD_GAIN_OVERLAP)| + SM_PDGAIN_B(0, 1) | + SM_PDGAIN_B(1, 2) | + SM_PDGAIN_B(2, 3) | + SM_PDGAIN_B(3, 4)); + } + + ath9k_adjust_pdadc_values(ah, pwr_table_offset, + diff, pdadcValues); + + regOffset = AR_PHY_BASE + (672 << 2) + regChainOffset; + for (j = 0; j < 32; j++) { + reg32 = get_unaligned_le32(&pdadcValues[4 * j]); + REG_WRITE(ah, regOffset, reg32); + + ath_dbg(common, EEPROM, + "PDADC (%d,%4x): %4.4x %8.8x\n", + i, regChainOffset, regOffset, + reg32); + ath_dbg(common, EEPROM, + "PDADC: Chain %d | PDADC %3d Value %3d | PDADC %3d Value %3d | PDADC %3d Value %3d | PDADC %3d Value %3d |\n", + i, 4 * j, pdadcValues[4 * j], + 4 * j + 1, pdadcValues[4 * j + 1], + 4 * j + 2, pdadcValues[4 * j + 2], + 4 * j + 3, pdadcValues[4 * j + 3]); + + regOffset += 4; + } + REGWRITE_BUFFER_FLUSH(ah); + } + } + +#undef SM_PD_GAIN +#undef SM_PDGAIN_B +} + +static void ath9k_hw_set_def_power_per_rate_table(struct ath_hw *ah, + struct ath9k_channel *chan, + int16_t *ratesArray, + u16 cfgCtl, + u16 antenna_reduction, + u16 powerLimit) +{ + struct ar5416_eeprom_def *pEepData = &ah->eeprom.def; + u16 twiceMaxEdgePower; + int i; + struct cal_ctl_data *rep; + struct cal_target_power_leg targetPowerOfdm, targetPowerCck = { + 0, { 0, 0, 0, 0} + }; + struct cal_target_power_leg targetPowerOfdmExt = { + 0, { 0, 0, 0, 0} }, targetPowerCckExt = { + 0, { 0, 0, 0, 0 } + }; + struct cal_target_power_ht targetPowerHt20, targetPowerHt40 = { + 0, {0, 0, 0, 0} + }; + u16 scaledPower = 0, minCtlPower; + static const u16 ctlModesFor11a[] = { + CTL_11A, CTL_5GHT20, CTL_11A_EXT, CTL_5GHT40 + }; + static const u16 ctlModesFor11g[] = { + CTL_11B, CTL_11G, CTL_2GHT20, + CTL_11B_EXT, CTL_11G_EXT, CTL_2GHT40 + }; + u16 numCtlModes; + const u16 *pCtlMode; + u16 ctlMode, freq; + struct chan_centers centers; + int tx_chainmask; + u16 twiceMinEdgePower; + + tx_chainmask = ah->txchainmask; + + ath9k_hw_get_channel_centers(ah, chan, ¢ers); + + scaledPower = ath9k_hw_get_scaled_power(ah, powerLimit, + antenna_reduction); + + if (IS_CHAN_2GHZ(chan)) { + numCtlModes = ARRAY_SIZE(ctlModesFor11g) - + SUB_NUM_CTL_MODES_AT_2G_40; + pCtlMode = ctlModesFor11g; + + ath9k_hw_get_legacy_target_powers(ah, chan, + pEepData->calTargetPowerCck, + AR5416_NUM_2G_CCK_TARGET_POWERS, + &targetPowerCck, 4, false); + ath9k_hw_get_legacy_target_powers(ah, chan, + pEepData->calTargetPower2G, + AR5416_NUM_2G_20_TARGET_POWERS, + &targetPowerOfdm, 4, false); + ath9k_hw_get_target_powers(ah, chan, + pEepData->calTargetPower2GHT20, + AR5416_NUM_2G_20_TARGET_POWERS, + &targetPowerHt20, 8, false); + + if (IS_CHAN_HT40(chan)) { + numCtlModes = ARRAY_SIZE(ctlModesFor11g); + ath9k_hw_get_target_powers(ah, chan, + pEepData->calTargetPower2GHT40, + AR5416_NUM_2G_40_TARGET_POWERS, + &targetPowerHt40, 8, true); + ath9k_hw_get_legacy_target_powers(ah, chan, + pEepData->calTargetPowerCck, + AR5416_NUM_2G_CCK_TARGET_POWERS, + &targetPowerCckExt, 4, true); + ath9k_hw_get_legacy_target_powers(ah, chan, + pEepData->calTargetPower2G, + AR5416_NUM_2G_20_TARGET_POWERS, + &targetPowerOfdmExt, 4, true); + } + } else { + numCtlModes = ARRAY_SIZE(ctlModesFor11a) - + SUB_NUM_CTL_MODES_AT_5G_40; + pCtlMode = ctlModesFor11a; + + ath9k_hw_get_legacy_target_powers(ah, chan, + pEepData->calTargetPower5G, + AR5416_NUM_5G_20_TARGET_POWERS, + &targetPowerOfdm, 4, false); + ath9k_hw_get_target_powers(ah, chan, + pEepData->calTargetPower5GHT20, + AR5416_NUM_5G_20_TARGET_POWERS, + &targetPowerHt20, 8, false); + + if (IS_CHAN_HT40(chan)) { + numCtlModes = ARRAY_SIZE(ctlModesFor11a); + ath9k_hw_get_target_powers(ah, chan, + pEepData->calTargetPower5GHT40, + AR5416_NUM_5G_40_TARGET_POWERS, + &targetPowerHt40, 8, true); + ath9k_hw_get_legacy_target_powers(ah, chan, + pEepData->calTargetPower5G, + AR5416_NUM_5G_20_TARGET_POWERS, + &targetPowerOfdmExt, 4, true); + } + } + + for (ctlMode = 0; ctlMode < numCtlModes; ctlMode++) { + bool isHt40CtlMode = (pCtlMode[ctlMode] == CTL_5GHT40) || + (pCtlMode[ctlMode] == CTL_2GHT40); + if (isHt40CtlMode) + freq = centers.synth_center; + else if (pCtlMode[ctlMode] & EXT_ADDITIVE) + freq = centers.ext_center; + else + freq = centers.ctl_center; + + twiceMaxEdgePower = MAX_RATE_POWER; + + for (i = 0; (i < AR5416_NUM_CTLS) && pEepData->ctlIndex[i]; i++) { + if ((((cfgCtl & ~CTL_MODE_M) | + (pCtlMode[ctlMode] & CTL_MODE_M)) == + pEepData->ctlIndex[i]) || + (((cfgCtl & ~CTL_MODE_M) | + (pCtlMode[ctlMode] & CTL_MODE_M)) == + ((pEepData->ctlIndex[i] & CTL_MODE_M) | SD_NO_CTL))) { + rep = &(pEepData->ctlData[i]); + + twiceMinEdgePower = ath9k_hw_get_max_edge_power(freq, + rep->ctlEdges[ar5416_get_ntxchains(tx_chainmask) - 1], + IS_CHAN_2GHZ(chan), AR5416_NUM_BAND_EDGES); + + if ((cfgCtl & ~CTL_MODE_M) == SD_NO_CTL) { + twiceMaxEdgePower = min(twiceMaxEdgePower, + twiceMinEdgePower); + } else { + twiceMaxEdgePower = twiceMinEdgePower; + break; + } + } + } + + minCtlPower = min(twiceMaxEdgePower, scaledPower); + + switch (pCtlMode[ctlMode]) { + case CTL_11B: + for (i = 0; i < ARRAY_SIZE(targetPowerCck.tPow2x); i++) { + targetPowerCck.tPow2x[i] = + min((u16)targetPowerCck.tPow2x[i], + minCtlPower); + } + break; + case CTL_11A: + case CTL_11G: + for (i = 0; i < ARRAY_SIZE(targetPowerOfdm.tPow2x); i++) { + targetPowerOfdm.tPow2x[i] = + min((u16)targetPowerOfdm.tPow2x[i], + minCtlPower); + } + break; + case CTL_5GHT20: + case CTL_2GHT20: + for (i = 0; i < ARRAY_SIZE(targetPowerHt20.tPow2x); i++) { + targetPowerHt20.tPow2x[i] = + min((u16)targetPowerHt20.tPow2x[i], + minCtlPower); + } + break; + case CTL_11B_EXT: + targetPowerCckExt.tPow2x[0] = min((u16) + targetPowerCckExt.tPow2x[0], + minCtlPower); + break; + case CTL_11A_EXT: + case CTL_11G_EXT: + targetPowerOfdmExt.tPow2x[0] = min((u16) + targetPowerOfdmExt.tPow2x[0], + minCtlPower); + break; + case CTL_5GHT40: + case CTL_2GHT40: + for (i = 0; i < ARRAY_SIZE(targetPowerHt40.tPow2x); i++) { + targetPowerHt40.tPow2x[i] = + min((u16)targetPowerHt40.tPow2x[i], + minCtlPower); + } + break; + default: + break; + } + } + + ratesArray[rate6mb] = ratesArray[rate9mb] = ratesArray[rate12mb] = + ratesArray[rate18mb] = ratesArray[rate24mb] = + targetPowerOfdm.tPow2x[0]; + ratesArray[rate36mb] = targetPowerOfdm.tPow2x[1]; + ratesArray[rate48mb] = targetPowerOfdm.tPow2x[2]; + ratesArray[rate54mb] = targetPowerOfdm.tPow2x[3]; + ratesArray[rateXr] = targetPowerOfdm.tPow2x[0]; + + for (i = 0; i < ARRAY_SIZE(targetPowerHt20.tPow2x); i++) + ratesArray[rateHt20_0 + i] = targetPowerHt20.tPow2x[i]; + + if (IS_CHAN_2GHZ(chan)) { + ratesArray[rate1l] = targetPowerCck.tPow2x[0]; + ratesArray[rate2s] = ratesArray[rate2l] = + targetPowerCck.tPow2x[1]; + ratesArray[rate5_5s] = ratesArray[rate5_5l] = + targetPowerCck.tPow2x[2]; + ratesArray[rate11s] = ratesArray[rate11l] = + targetPowerCck.tPow2x[3]; + } + if (IS_CHAN_HT40(chan)) { + for (i = 0; i < ARRAY_SIZE(targetPowerHt40.tPow2x); i++) { + ratesArray[rateHt40_0 + i] = + targetPowerHt40.tPow2x[i]; + } + ratesArray[rateDupOfdm] = targetPowerHt40.tPow2x[0]; + ratesArray[rateDupCck] = targetPowerHt40.tPow2x[0]; + ratesArray[rateExtOfdm] = targetPowerOfdmExt.tPow2x[0]; + if (IS_CHAN_2GHZ(chan)) { + ratesArray[rateExtCck] = + targetPowerCckExt.tPow2x[0]; + } + } +} + +static void ath9k_hw_def_set_txpower(struct ath_hw *ah, + struct ath9k_channel *chan, + u16 cfgCtl, + u8 twiceAntennaReduction, + u8 powerLimit, bool test) +{ +#define RT_AR_DELTA(x) (ratesArray[x] - cck_ofdm_delta) + struct ath_regulatory *regulatory = ath9k_hw_regulatory(ah); + struct ar5416_eeprom_def *pEepData = &ah->eeprom.def; + struct modal_eep_header *pModal = + &(pEepData->modalHeader[IS_CHAN_2GHZ(chan)]); + int16_t ratesArray[Ar5416RateSize]; + u8 ht40PowerIncForPdadc = 2; + int i, cck_ofdm_delta = 0; + + memset(ratesArray, 0, sizeof(ratesArray)); + + if ((pEepData->baseEepHeader.version & AR5416_EEP_VER_MINOR_MASK) >= + AR5416_EEP_MINOR_VER_2) { + ht40PowerIncForPdadc = pModal->ht40PowerIncForPdadc; + } + + ath9k_hw_set_def_power_per_rate_table(ah, chan, + &ratesArray[0], cfgCtl, + twiceAntennaReduction, + powerLimit); + + ath9k_hw_set_def_power_cal_table(ah, chan); + + regulatory->max_power_level = 0; + for (i = 0; i < ARRAY_SIZE(ratesArray); i++) { + if (ratesArray[i] > MAX_RATE_POWER) + ratesArray[i] = MAX_RATE_POWER; + if (ratesArray[i] > regulatory->max_power_level) + regulatory->max_power_level = ratesArray[i]; + } + + ath9k_hw_update_regulatory_maxpower(ah); + + if (test) + return; + + if (AR_SREV_9280_20_OR_LATER(ah)) { + for (i = 0; i < Ar5416RateSize; i++) { + int8_t pwr_table_offset; + + pwr_table_offset = ah->eep_ops->get_eeprom(ah, + EEP_PWR_TABLE_OFFSET); + ratesArray[i] -= pwr_table_offset * 2; + } + } + + ENABLE_REGWRITE_BUFFER(ah); + + REG_WRITE(ah, AR_PHY_POWER_TX_RATE1, + ATH9K_POW_SM(ratesArray[rate18mb], 24) + | ATH9K_POW_SM(ratesArray[rate12mb], 16) + | ATH9K_POW_SM(ratesArray[rate9mb], 8) + | ATH9K_POW_SM(ratesArray[rate6mb], 0)); + REG_WRITE(ah, AR_PHY_POWER_TX_RATE2, + ATH9K_POW_SM(ratesArray[rate54mb], 24) + | ATH9K_POW_SM(ratesArray[rate48mb], 16) + | ATH9K_POW_SM(ratesArray[rate36mb], 8) + | ATH9K_POW_SM(ratesArray[rate24mb], 0)); + + if (IS_CHAN_2GHZ(chan)) { + if (OLC_FOR_AR9280_20_LATER) { + cck_ofdm_delta = 2; + REG_WRITE(ah, AR_PHY_POWER_TX_RATE3, + ATH9K_POW_SM(RT_AR_DELTA(rate2s), 24) + | ATH9K_POW_SM(RT_AR_DELTA(rate2l), 16) + | ATH9K_POW_SM(ratesArray[rateXr], 8) + | ATH9K_POW_SM(RT_AR_DELTA(rate1l), 0)); + REG_WRITE(ah, AR_PHY_POWER_TX_RATE4, + ATH9K_POW_SM(RT_AR_DELTA(rate11s), 24) + | ATH9K_POW_SM(RT_AR_DELTA(rate11l), 16) + | ATH9K_POW_SM(RT_AR_DELTA(rate5_5s), 8) + | ATH9K_POW_SM(RT_AR_DELTA(rate5_5l), 0)); + } else { + REG_WRITE(ah, AR_PHY_POWER_TX_RATE3, + ATH9K_POW_SM(ratesArray[rate2s], 24) + | ATH9K_POW_SM(ratesArray[rate2l], 16) + | ATH9K_POW_SM(ratesArray[rateXr], 8) + | ATH9K_POW_SM(ratesArray[rate1l], 0)); + REG_WRITE(ah, AR_PHY_POWER_TX_RATE4, + ATH9K_POW_SM(ratesArray[rate11s], 24) + | ATH9K_POW_SM(ratesArray[rate11l], 16) + | ATH9K_POW_SM(ratesArray[rate5_5s], 8) + | ATH9K_POW_SM(ratesArray[rate5_5l], 0)); + } + } + + REG_WRITE(ah, AR_PHY_POWER_TX_RATE5, + ATH9K_POW_SM(ratesArray[rateHt20_3], 24) + | ATH9K_POW_SM(ratesArray[rateHt20_2], 16) + | ATH9K_POW_SM(ratesArray[rateHt20_1], 8) + | ATH9K_POW_SM(ratesArray[rateHt20_0], 0)); + REG_WRITE(ah, AR_PHY_POWER_TX_RATE6, + ATH9K_POW_SM(ratesArray[rateHt20_7], 24) + | ATH9K_POW_SM(ratesArray[rateHt20_6], 16) + | ATH9K_POW_SM(ratesArray[rateHt20_5], 8) + | ATH9K_POW_SM(ratesArray[rateHt20_4], 0)); + + if (IS_CHAN_HT40(chan)) { + REG_WRITE(ah, AR_PHY_POWER_TX_RATE7, + ATH9K_POW_SM(ratesArray[rateHt40_3] + + ht40PowerIncForPdadc, 24) + | ATH9K_POW_SM(ratesArray[rateHt40_2] + + ht40PowerIncForPdadc, 16) + | ATH9K_POW_SM(ratesArray[rateHt40_1] + + ht40PowerIncForPdadc, 8) + | ATH9K_POW_SM(ratesArray[rateHt40_0] + + ht40PowerIncForPdadc, 0)); + REG_WRITE(ah, AR_PHY_POWER_TX_RATE8, + ATH9K_POW_SM(ratesArray[rateHt40_7] + + ht40PowerIncForPdadc, 24) + | ATH9K_POW_SM(ratesArray[rateHt40_6] + + ht40PowerIncForPdadc, 16) + | ATH9K_POW_SM(ratesArray[rateHt40_5] + + ht40PowerIncForPdadc, 8) + | ATH9K_POW_SM(ratesArray[rateHt40_4] + + ht40PowerIncForPdadc, 0)); + if (OLC_FOR_AR9280_20_LATER) { + REG_WRITE(ah, AR_PHY_POWER_TX_RATE9, + ATH9K_POW_SM(ratesArray[rateExtOfdm], 24) + | ATH9K_POW_SM(RT_AR_DELTA(rateExtCck), 16) + | ATH9K_POW_SM(ratesArray[rateDupOfdm], 8) + | ATH9K_POW_SM(RT_AR_DELTA(rateDupCck), 0)); + } else { + REG_WRITE(ah, AR_PHY_POWER_TX_RATE9, + ATH9K_POW_SM(ratesArray[rateExtOfdm], 24) + | ATH9K_POW_SM(ratesArray[rateExtCck], 16) + | ATH9K_POW_SM(ratesArray[rateDupOfdm], 8) + | ATH9K_POW_SM(ratesArray[rateDupCck], 0)); + } + } + + REG_WRITE(ah, AR_PHY_POWER_TX_SUB, + ATH9K_POW_SM(pModal->pwrDecreaseFor3Chain, 6) + | ATH9K_POW_SM(pModal->pwrDecreaseFor2Chain, 0)); + + REGWRITE_BUFFER_FLUSH(ah); +} + +static u16 ath9k_hw_def_get_spur_channel(struct ath_hw *ah, u16 i, bool is2GHz) +{ +#define EEP_DEF_SPURCHAN \ + (ah->eeprom.def.modalHeader[is2GHz].spurChans[i].spurChan) + struct ath_common *common = ath9k_hw_common(ah); + + u16 spur_val = AR_NO_SPUR; + + ath_dbg(common, ANI, "Getting spur idx:%d is2Ghz:%d val:%x\n", + i, is2GHz, ah->config.spurchans[i][is2GHz]); + + switch (ah->config.spurmode) { + case SPUR_DISABLE: + break; + case SPUR_ENABLE_IOCTL: + spur_val = ah->config.spurchans[i][is2GHz]; + ath_dbg(common, ANI, "Getting spur val from new loc. %d\n", + spur_val); + break; + case SPUR_ENABLE_EEPROM: + spur_val = EEP_DEF_SPURCHAN; + break; + } + + return spur_val; + +#undef EEP_DEF_SPURCHAN +} + +const struct eeprom_ops eep_def_ops = { + .check_eeprom = ath9k_hw_def_check_eeprom, + .get_eeprom = ath9k_hw_def_get_eeprom, + .fill_eeprom = ath9k_hw_def_fill_eeprom, + .dump_eeprom = ath9k_hw_def_dump_eeprom, + .get_eeprom_ver = ath9k_hw_def_get_eeprom_ver, + .get_eeprom_rev = ath9k_hw_def_get_eeprom_rev, + .set_board_values = ath9k_hw_def_set_board_values, + .set_addac = ath9k_hw_def_set_addac, + .set_txpower = ath9k_hw_def_set_txpower, + .get_spur_channel = ath9k_hw_def_get_spur_channel +}; diff --git a/drivers/net/wireless/ath/ath9k/gpio.c b/drivers/net/wireless/ath/ath9k/gpio.c new file mode 100644 index 0000000..1bf5288 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/gpio.c @@ -0,0 +1,567 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ath9k.h" + +/********************************/ +/* LED functions */ +/********************************/ + +#ifdef CPTCFG_MAC80211_LEDS +static void ath_led_brightness(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct ath_softc *sc = container_of(led_cdev, struct ath_softc, led_cdev); + ath9k_hw_set_gpio(sc->sc_ah, sc->sc_ah->led_pin, (brightness == LED_OFF)); +} + +void ath_deinit_leds(struct ath_softc *sc) +{ + if (!sc->led_registered) + return; + + ath_led_brightness(&sc->led_cdev, LED_OFF); + led_classdev_unregister(&sc->led_cdev); +} + +void ath_init_leds(struct ath_softc *sc) +{ + int ret; + + if (AR_SREV_9100(sc->sc_ah)) + return; + + if (!led_blink) + sc->led_cdev.default_trigger = + ieee80211_get_radio_led_name(sc->hw); + + snprintf(sc->led_name, sizeof(sc->led_name), + "ath9k-%s", wiphy_name(sc->hw->wiphy)); + sc->led_cdev.name = sc->led_name; + sc->led_cdev.brightness_set = ath_led_brightness; + + ret = led_classdev_register(wiphy_dev(sc->hw->wiphy), &sc->led_cdev); + if (ret < 0) + return; + + sc->led_registered = true; +} + +void ath_fill_led_pin(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + + if (AR_SREV_9100(ah) || (ah->led_pin >= 0)) + return; + + if (AR_SREV_9287(ah)) + ah->led_pin = ATH_LED_PIN_9287; + else if (AR_SREV_9485(sc->sc_ah)) + ah->led_pin = ATH_LED_PIN_9485; + else if (AR_SREV_9300(sc->sc_ah)) + ah->led_pin = ATH_LED_PIN_9300; + else if (AR_SREV_9462(sc->sc_ah) || AR_SREV_9565(sc->sc_ah)) + ah->led_pin = ATH_LED_PIN_9462; + else + ah->led_pin = ATH_LED_PIN_DEF; + + /* Configure gpio 1 for output */ + ath9k_hw_cfg_output(ah, ah->led_pin, AR_GPIO_OUTPUT_MUX_AS_OUTPUT); + + /* LED off, active low */ + ath9k_hw_set_gpio(ah, ah->led_pin, 1); +} +#endif + +/*******************/ +/* Rfkill */ +/*******************/ + +static bool ath_is_rfkill_set(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + bool is_blocked; + + ath9k_ps_wakeup(sc); + is_blocked = ath9k_hw_gpio_get(ah, ah->rfkill_gpio) == + ah->rfkill_polarity; + ath9k_ps_restore(sc); + + return is_blocked; +} + +void ath9k_rfkill_poll_state(struct ieee80211_hw *hw) +{ + struct ath_softc *sc = hw->priv; + bool blocked = !!ath_is_rfkill_set(sc); + + wiphy_rfkill_set_hw_state(hw->wiphy, blocked); +} + +void ath_start_rfkill_poll(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + + if (ah->caps.hw_caps & ATH9K_HW_CAP_RFSILENT) + wiphy_rfkill_start_polling(sc->hw->wiphy); +} + +#ifdef CPTCFG_ATH9K_BTCOEX_SUPPORT + +/******************/ +/* BTCOEX */ +/******************/ + +/* + * Detects if there is any priority bt traffic + */ +static void ath_detect_bt_priority(struct ath_softc *sc) +{ + struct ath_btcoex *btcoex = &sc->btcoex; + struct ath_hw *ah = sc->sc_ah; + + if (ath9k_hw_gpio_get(sc->sc_ah, ah->btcoex_hw.btpriority_gpio)) + btcoex->bt_priority_cnt++; + + if (time_after(jiffies, btcoex->bt_priority_time + + msecs_to_jiffies(ATH_BT_PRIORITY_TIME_THRESHOLD))) { + clear_bit(BT_OP_PRIORITY_DETECTED, &btcoex->op_flags); + clear_bit(BT_OP_SCAN, &btcoex->op_flags); + /* Detect if colocated bt started scanning */ + if (btcoex->bt_priority_cnt >= ATH_BT_CNT_SCAN_THRESHOLD) { + ath_dbg(ath9k_hw_common(sc->sc_ah), BTCOEX, + "BT scan detected\n"); + set_bit(BT_OP_PRIORITY_DETECTED, &btcoex->op_flags); + set_bit(BT_OP_SCAN, &btcoex->op_flags); + } else if (btcoex->bt_priority_cnt >= ATH_BT_CNT_THRESHOLD) { + ath_dbg(ath9k_hw_common(sc->sc_ah), BTCOEX, + "BT priority traffic detected\n"); + set_bit(BT_OP_PRIORITY_DETECTED, &btcoex->op_flags); + } + + btcoex->bt_priority_cnt = 0; + btcoex->bt_priority_time = jiffies; + } +} + +static void ath9k_gen_timer_start(struct ath_hw *ah, + struct ath_gen_timer *timer, + u32 trig_timeout, + u32 timer_period) +{ + ath9k_hw_gen_timer_start(ah, timer, trig_timeout, timer_period); + + if ((ah->imask & ATH9K_INT_GENTIMER) == 0) { + ath9k_hw_disable_interrupts(ah); + ah->imask |= ATH9K_INT_GENTIMER; + ath9k_hw_set_interrupts(ah); + ath9k_hw_enable_interrupts(ah); + } +} + +static void ath9k_gen_timer_stop(struct ath_hw *ah, struct ath_gen_timer *timer) +{ + struct ath_gen_timer_table *timer_table = &ah->hw_gen_timers; + + ath9k_hw_gen_timer_stop(ah, timer); + + /* if no timer is enabled, turn off interrupt mask */ + if (timer_table->timer_mask.val == 0) { + ath9k_hw_disable_interrupts(ah); + ah->imask &= ~ATH9K_INT_GENTIMER; + ath9k_hw_set_interrupts(ah); + ath9k_hw_enable_interrupts(ah); + } +} + +static void ath_mci_ftp_adjust(struct ath_softc *sc) +{ + struct ath_btcoex *btcoex = &sc->btcoex; + struct ath_mci_profile *mci = &btcoex->mci; + struct ath_hw *ah = sc->sc_ah; + + if (btcoex->bt_wait_time > ATH_BTCOEX_RX_WAIT_TIME) { + if (ar9003_mci_state(ah, MCI_STATE_NEED_FTP_STOMP) && + (mci->num_pan || mci->num_other_acl)) + ah->btcoex_hw.mci.stomp_ftp = + (sc->rx.num_pkts < ATH_BTCOEX_STOMP_FTP_THRESH); + else + ah->btcoex_hw.mci.stomp_ftp = false; + btcoex->bt_wait_time = 0; + sc->rx.num_pkts = 0; + } +} + +/* + * This is the master bt coex timer which runs for every + * 45ms, bt traffic will be given priority during 55% of this + * period while wlan gets remaining 45% + */ +static void ath_btcoex_period_timer(unsigned long data) +{ + struct ath_softc *sc = (struct ath_softc *) data; + struct ath_hw *ah = sc->sc_ah; + struct ath_btcoex *btcoex = &sc->btcoex; + enum ath_stomp_type stomp_type; + u32 timer_period; + unsigned long flags; + + spin_lock_irqsave(&sc->sc_pm_lock, flags); + if (sc->sc_ah->power_mode == ATH9K_PM_NETWORK_SLEEP) { + btcoex->bt_wait_time += btcoex->btcoex_period; + spin_unlock_irqrestore(&sc->sc_pm_lock, flags); + goto skip_hw_wakeup; + } + spin_unlock_irqrestore(&sc->sc_pm_lock, flags); + + ath9k_mci_update_rssi(sc); + + ath9k_ps_wakeup(sc); + + if (!(ah->caps.hw_caps & ATH9K_HW_CAP_MCI)) + ath_detect_bt_priority(sc); + + if (ah->caps.hw_caps & ATH9K_HW_CAP_MCI) + ath_mci_ftp_adjust(sc); + + spin_lock_bh(&btcoex->btcoex_lock); + + stomp_type = btcoex->bt_stomp_type; + timer_period = btcoex->btcoex_no_stomp; + + if (!(ah->caps.hw_caps & ATH9K_HW_CAP_MCI)) { + if (test_bit(BT_OP_SCAN, &btcoex->op_flags)) { + stomp_type = ATH_BTCOEX_STOMP_ALL; + timer_period = btcoex->btscan_no_stomp; + } + } else if (btcoex->stomp_audio >= 5) { + stomp_type = ATH_BTCOEX_STOMP_AUDIO; + btcoex->stomp_audio = 0; + } + + ath9k_hw_btcoex_bt_stomp(ah, stomp_type); + ath9k_hw_btcoex_enable(ah); + + spin_unlock_bh(&btcoex->btcoex_lock); + + /* + * btcoex_period is in msec while (btocex/btscan_)no_stomp are in usec, + * ensure that we properly convert btcoex_period to usec + * for any comparision with (btcoex/btscan_)no_stomp. + */ + if (btcoex->btcoex_period * 1000 != btcoex->btcoex_no_stomp) { + if (btcoex->hw_timer_enabled) + ath9k_gen_timer_stop(ah, btcoex->no_stomp_timer); + + ath9k_gen_timer_start(ah, btcoex->no_stomp_timer, timer_period, + timer_period * 10); + btcoex->hw_timer_enabled = true; + } + + ath9k_ps_restore(sc); + +skip_hw_wakeup: + mod_timer(&btcoex->period_timer, + jiffies + msecs_to_jiffies(btcoex->btcoex_period)); +} + +/* + * Generic tsf based hw timer which configures weight + * registers to time slice between wlan and bt traffic + */ +static void ath_btcoex_no_stomp_timer(void *arg) +{ + struct ath_softc *sc = (struct ath_softc *)arg; + struct ath_hw *ah = sc->sc_ah; + struct ath_btcoex *btcoex = &sc->btcoex; + struct ath_common *common = ath9k_hw_common(ah); + + ath_dbg(common, BTCOEX, "no stomp timer running\n"); + + ath9k_ps_wakeup(sc); + spin_lock_bh(&btcoex->btcoex_lock); + + if (btcoex->bt_stomp_type == ATH_BTCOEX_STOMP_LOW || + (!(ah->caps.hw_caps & ATH9K_HW_CAP_MCI) && + test_bit(BT_OP_SCAN, &btcoex->op_flags))) + ath9k_hw_btcoex_bt_stomp(ah, ATH_BTCOEX_STOMP_NONE); + else if (btcoex->bt_stomp_type == ATH_BTCOEX_STOMP_ALL) + ath9k_hw_btcoex_bt_stomp(ah, ATH_BTCOEX_STOMP_LOW); + + ath9k_hw_btcoex_enable(ah); + spin_unlock_bh(&btcoex->btcoex_lock); + ath9k_ps_restore(sc); +} + +static int ath_init_btcoex_timer(struct ath_softc *sc) +{ + struct ath_btcoex *btcoex = &sc->btcoex; + + btcoex->btcoex_period = ATH_BTCOEX_DEF_BT_PERIOD; + btcoex->btcoex_no_stomp = (100 - ATH_BTCOEX_DEF_DUTY_CYCLE) * 1000 * + btcoex->btcoex_period / 100; + btcoex->btscan_no_stomp = (100 - ATH_BTCOEX_BTSCAN_DUTY_CYCLE) * 1000 * + btcoex->btcoex_period / 100; + + setup_timer(&btcoex->period_timer, ath_btcoex_period_timer, + (unsigned long) sc); + + spin_lock_init(&btcoex->btcoex_lock); + + btcoex->no_stomp_timer = ath_gen_timer_alloc(sc->sc_ah, + ath_btcoex_no_stomp_timer, + ath_btcoex_no_stomp_timer, + (void *) sc, AR_FIRST_NDP_TIMER); + + if (!btcoex->no_stomp_timer) + return -ENOMEM; + + return 0; +} + +/* + * (Re)start btcoex timers + */ +void ath9k_btcoex_timer_resume(struct ath_softc *sc) +{ + struct ath_btcoex *btcoex = &sc->btcoex; + struct ath_hw *ah = sc->sc_ah; + + ath_dbg(ath9k_hw_common(ah), BTCOEX, "Starting btcoex timers\n"); + + /* make sure duty cycle timer is also stopped when resuming */ + if (btcoex->hw_timer_enabled) { + ath9k_gen_timer_stop(sc->sc_ah, btcoex->no_stomp_timer); + btcoex->hw_timer_enabled = false; + } + + btcoex->bt_priority_cnt = 0; + btcoex->bt_priority_time = jiffies; + clear_bit(BT_OP_PRIORITY_DETECTED, &btcoex->op_flags); + clear_bit(BT_OP_SCAN, &btcoex->op_flags); + + mod_timer(&btcoex->period_timer, jiffies); +} + + +/* + * Pause btcoex timer and bt duty cycle timer + */ +void ath9k_btcoex_timer_pause(struct ath_softc *sc) +{ + struct ath_btcoex *btcoex = &sc->btcoex; + struct ath_hw *ah = sc->sc_ah; + + del_timer_sync(&btcoex->period_timer); + + if (btcoex->hw_timer_enabled) { + ath9k_gen_timer_stop(ah, btcoex->no_stomp_timer); + btcoex->hw_timer_enabled = false; + } +} + +void ath9k_btcoex_stop_gen_timer(struct ath_softc *sc) +{ + struct ath_btcoex *btcoex = &sc->btcoex; + + if (btcoex->hw_timer_enabled) { + ath9k_gen_timer_stop(sc->sc_ah, btcoex->no_stomp_timer); + btcoex->hw_timer_enabled = false; + } +} + +u16 ath9k_btcoex_aggr_limit(struct ath_softc *sc, u32 max_4ms_framelen) +{ + struct ath_btcoex *btcoex = &sc->btcoex; + struct ath_mci_profile *mci = &sc->btcoex.mci; + u16 aggr_limit = 0; + + if ((sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_MCI) && mci->aggr_limit) + aggr_limit = (max_4ms_framelen * mci->aggr_limit) >> 4; + else if (test_bit(BT_OP_PRIORITY_DETECTED, &btcoex->op_flags)) + aggr_limit = min((max_4ms_framelen * 3) / 8, + (u32)ATH_AMPDU_LIMIT_MAX); + + return aggr_limit; +} + +void ath9k_btcoex_handle_interrupt(struct ath_softc *sc, u32 status) +{ + struct ath_hw *ah = sc->sc_ah; + + if (ath9k_hw_get_btcoex_scheme(ah) == ATH_BTCOEX_CFG_3WIRE) + if (status & ATH9K_INT_GENTIMER) + ath_gen_timer_isr(sc->sc_ah); + + if (status & ATH9K_INT_MCI) + ath_mci_intr(sc); +} + +void ath9k_start_btcoex(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + + if ((ath9k_hw_get_btcoex_scheme(ah) != ATH_BTCOEX_CFG_NONE) && + !ah->btcoex_hw.enabled) { + if (!(sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_MCI)) + ath9k_hw_btcoex_set_weight(ah, AR_BT_COEX_WGHT, + AR_STOMP_LOW_WLAN_WGHT, 0); + else + ath9k_hw_btcoex_set_weight(ah, 0, 0, + ATH_BTCOEX_STOMP_NONE); + ath9k_hw_btcoex_enable(ah); + + if (ath9k_hw_get_btcoex_scheme(ah) == ATH_BTCOEX_CFG_3WIRE) + ath9k_btcoex_timer_resume(sc); + } +} + +void ath9k_stop_btcoex(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + + if (ah->btcoex_hw.enabled && + ath9k_hw_get_btcoex_scheme(ah) != ATH_BTCOEX_CFG_NONE) { + if (ath9k_hw_get_btcoex_scheme(ah) == ATH_BTCOEX_CFG_3WIRE) + ath9k_btcoex_timer_pause(sc); + ath9k_hw_btcoex_disable(ah); + if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) + ath_mci_flush_profile(&sc->btcoex.mci); + } +} + +void ath9k_deinit_btcoex(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + + if ((sc->btcoex.no_stomp_timer) && + ath9k_hw_get_btcoex_scheme(sc->sc_ah) == ATH_BTCOEX_CFG_3WIRE) + ath_gen_timer_free(sc->sc_ah, sc->btcoex.no_stomp_timer); + + if (ath9k_hw_mci_is_enabled(ah)) + ath_mci_cleanup(sc); +} + +int ath9k_init_btcoex(struct ath_softc *sc) +{ + struct ath_txq *txq; + struct ath_hw *ah = sc->sc_ah; + int r; + + ath9k_hw_btcoex_init_scheme(ah); + + switch (ath9k_hw_get_btcoex_scheme(sc->sc_ah)) { + case ATH_BTCOEX_CFG_NONE: + break; + case ATH_BTCOEX_CFG_2WIRE: + ath9k_hw_btcoex_init_2wire(sc->sc_ah); + break; + case ATH_BTCOEX_CFG_3WIRE: + ath9k_hw_btcoex_init_3wire(sc->sc_ah); + r = ath_init_btcoex_timer(sc); + if (r) + return -1; + txq = sc->tx.txq_map[IEEE80211_AC_BE]; + ath9k_hw_init_btcoex_hw(sc->sc_ah, txq->axq_qnum); + sc->btcoex.bt_stomp_type = ATH_BTCOEX_STOMP_LOW; + if (ath9k_hw_mci_is_enabled(ah)) { + sc->btcoex.duty_cycle = ATH_BTCOEX_DEF_DUTY_CYCLE; + INIT_LIST_HEAD(&sc->btcoex.mci.info); + + r = ath_mci_setup(sc); + if (r) + return r; + + ath9k_hw_btcoex_init_mci(ah); + } + + break; + default: + WARN_ON(1); + break; + } + + return 0; +} + +static int ath9k_dump_mci_btcoex(struct ath_softc *sc, u8 *buf, u32 size) +{ + struct ath_btcoex *btcoex = &sc->btcoex; + struct ath_mci_profile *mci = &btcoex->mci; + struct ath_hw *ah = sc->sc_ah; + struct ath_btcoex_hw *btcoex_hw = &ah->btcoex_hw; + u32 len = 0; + int i; + + ATH_DUMP_BTCOEX("Total BT profiles", NUM_PROF(mci)); + ATH_DUMP_BTCOEX("MGMT", mci->num_mgmt); + ATH_DUMP_BTCOEX("SCO", mci->num_sco); + ATH_DUMP_BTCOEX("A2DP", mci->num_a2dp); + ATH_DUMP_BTCOEX("HID", mci->num_hid); + ATH_DUMP_BTCOEX("PAN", mci->num_pan); + ATH_DUMP_BTCOEX("ACL", mci->num_other_acl); + ATH_DUMP_BTCOEX("BDR", mci->num_bdr); + ATH_DUMP_BTCOEX("Aggr. Limit", mci->aggr_limit); + ATH_DUMP_BTCOEX("Stomp Type", btcoex->bt_stomp_type); + ATH_DUMP_BTCOEX("BTCoex Period (msec)", btcoex->btcoex_period); + ATH_DUMP_BTCOEX("Duty Cycle", btcoex->duty_cycle); + ATH_DUMP_BTCOEX("BT Wait time", btcoex->bt_wait_time); + ATH_DUMP_BTCOEX("Concurrent Tx", btcoex_hw->mci.concur_tx); + ATH_DUMP_BTCOEX("Concurrent RSSI cnt", btcoex->rssi_count); + + len += snprintf(buf + len, size - len, "BT Weights: "); + for (i = 0; i < AR9300_NUM_BT_WEIGHTS; i++) + len += snprintf(buf + len, size - len, "%08x ", + btcoex_hw->bt_weight[i]); + len += snprintf(buf + len, size - len, "\n"); + len += snprintf(buf + len, size - len, "WLAN Weights: "); + for (i = 0; i < AR9300_NUM_BT_WEIGHTS; i++) + len += snprintf(buf + len, size - len, "%08x ", + btcoex_hw->wlan_weight[i]); + len += snprintf(buf + len, size - len, "\n"); + len += snprintf(buf + len, size - len, "Tx Priorities: "); + for (i = 0; i < ATH_BTCOEX_STOMP_MAX; i++) + len += snprintf(buf + len, size - len, "%08x ", + btcoex_hw->tx_prio[i]); + + len += snprintf(buf + len, size - len, "\n"); + + return len; +} + +static int ath9k_dump_legacy_btcoex(struct ath_softc *sc, u8 *buf, u32 size) +{ + + struct ath_btcoex *btcoex = &sc->btcoex; + u32 len = 0; + + ATH_DUMP_BTCOEX("Stomp Type", btcoex->bt_stomp_type); + ATH_DUMP_BTCOEX("BTCoex Period (msec)", btcoex->btcoex_period); + ATH_DUMP_BTCOEX("Duty Cycle", btcoex->duty_cycle); + ATH_DUMP_BTCOEX("BT Wait time", btcoex->bt_wait_time); + + return len; +} + +int ath9k_dump_btcoex(struct ath_softc *sc, u8 *buf, u32 size) +{ + if (ath9k_hw_mci_is_enabled(sc->sc_ah)) + return ath9k_dump_mci_btcoex(sc, buf, size); + else + return ath9k_dump_legacy_btcoex(sc, buf, size); +} + +#endif /* CPTCFG_ATH9K_BTCOEX_SUPPORT */ diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.c b/drivers/net/wireless/ath/ath9k/hif_usb.c new file mode 100644 index 0000000..592c47e --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/hif_usb.c @@ -0,0 +1,1391 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "htc.h" + +/* identify firmware images */ +#define FIRMWARE_AR7010_1_1 "htc_7010.fw" +#define FIRMWARE_AR9271 "htc_9271.fw" + +MODULE_FIRMWARE(FIRMWARE_AR7010_1_1); +MODULE_FIRMWARE(FIRMWARE_AR9271); + +static struct usb_device_id ath9k_hif_usb_ids[] = { + { USB_DEVICE(0x0cf3, 0x9271) }, /* Atheros */ + { USB_DEVICE(0x0cf3, 0x1006) }, /* Atheros */ + { USB_DEVICE(0x0846, 0x9030) }, /* Netgear N150 */ + { USB_DEVICE(0x07D1, 0x3A10) }, /* Dlink Wireless 150 */ + { USB_DEVICE(0x13D3, 0x3327) }, /* Azurewave */ + { USB_DEVICE(0x13D3, 0x3328) }, /* Azurewave */ + { USB_DEVICE(0x13D3, 0x3346) }, /* IMC Networks */ + { USB_DEVICE(0x13D3, 0x3348) }, /* Azurewave */ + { USB_DEVICE(0x13D3, 0x3349) }, /* Azurewave */ + { USB_DEVICE(0x13D3, 0x3350) }, /* Azurewave */ + { USB_DEVICE(0x04CA, 0x4605) }, /* Liteon */ + { USB_DEVICE(0x040D, 0x3801) }, /* VIA */ + { USB_DEVICE(0x0cf3, 0xb003) }, /* Ubiquiti WifiStation Ext */ + { USB_DEVICE(0x0cf3, 0xb002) }, /* Ubiquiti WifiStation */ + { USB_DEVICE(0x057c, 0x8403) }, /* AVM FRITZ!WLAN 11N v2 USB */ + + { USB_DEVICE(0x0cf3, 0x7015), + .driver_info = AR9287_USB }, /* Atheros */ + { USB_DEVICE(0x1668, 0x1200), + .driver_info = AR9287_USB }, /* Verizon */ + + { USB_DEVICE(0x0cf3, 0x7010), + .driver_info = AR9280_USB }, /* Atheros */ + { USB_DEVICE(0x0846, 0x9018), + .driver_info = AR9280_USB }, /* Netgear WNDA3200 */ + { USB_DEVICE(0x083A, 0xA704), + .driver_info = AR9280_USB }, /* SMC Networks */ + { USB_DEVICE(0x0411, 0x017f), + .driver_info = AR9280_USB }, /* Sony UWA-BR100 */ + { USB_DEVICE(0x04da, 0x3904), + .driver_info = AR9280_USB }, + + { USB_DEVICE(0x0cf3, 0x20ff), + .driver_info = STORAGE_DEVICE }, + + { }, +}; + +MODULE_DEVICE_TABLE(usb, ath9k_hif_usb_ids); + +static int __hif_usb_tx(struct hif_device_usb *hif_dev); + +static void hif_usb_regout_cb(struct urb *urb) +{ + struct cmd_buf *cmd = (struct cmd_buf *)urb->context; + + switch (urb->status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ENODEV: + case -ESHUTDOWN: + goto free; + default: + break; + } + + if (cmd) { + ath9k_htc_txcompletion_cb(cmd->hif_dev->htc_handle, + cmd->skb, true); + kfree(cmd); + } + + return; +free: + kfree_skb(cmd->skb); + kfree(cmd); +} + +static int hif_usb_send_regout(struct hif_device_usb *hif_dev, + struct sk_buff *skb) +{ + struct urb *urb; + struct cmd_buf *cmd; + int ret = 0; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (urb == NULL) + return -ENOMEM; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) { + usb_free_urb(urb); + return -ENOMEM; + } + + cmd->skb = skb; + cmd->hif_dev = hif_dev; + + usb_fill_bulk_urb(urb, hif_dev->udev, + usb_sndbulkpipe(hif_dev->udev, USB_REG_OUT_PIPE), + skb->data, skb->len, + hif_usb_regout_cb, cmd); + + usb_anchor_urb(urb, &hif_dev->regout_submitted); + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret) { + usb_unanchor_urb(urb); + kfree(cmd); + } + usb_free_urb(urb); + + return ret; +} + +static void hif_usb_mgmt_cb(struct urb *urb) +{ + struct cmd_buf *cmd = (struct cmd_buf *)urb->context; + struct hif_device_usb *hif_dev; + bool txok = true; + + if (!cmd || !cmd->skb || !cmd->hif_dev) + return; + + hif_dev = cmd->hif_dev; + + switch (urb->status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ENODEV: + case -ESHUTDOWN: + txok = false; + + /* + * If the URBs are being flushed, no need to complete + * this packet. + */ + spin_lock(&hif_dev->tx.tx_lock); + if (hif_dev->tx.flags & HIF_USB_TX_FLUSH) { + spin_unlock(&hif_dev->tx.tx_lock); + dev_kfree_skb_any(cmd->skb); + kfree(cmd); + return; + } + spin_unlock(&hif_dev->tx.tx_lock); + + break; + default: + txok = false; + break; + } + + skb_pull(cmd->skb, 4); + ath9k_htc_txcompletion_cb(cmd->hif_dev->htc_handle, + cmd->skb, txok); + kfree(cmd); +} + +static int hif_usb_send_mgmt(struct hif_device_usb *hif_dev, + struct sk_buff *skb) +{ + struct urb *urb; + struct cmd_buf *cmd; + int ret = 0; + __le16 *hdr; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (urb == NULL) + return -ENOMEM; + + cmd = kzalloc(sizeof(*cmd), GFP_ATOMIC); + if (cmd == NULL) { + usb_free_urb(urb); + return -ENOMEM; + } + + cmd->skb = skb; + cmd->hif_dev = hif_dev; + + hdr = (__le16 *) skb_push(skb, 4); + *hdr++ = cpu_to_le16(skb->len - 4); + *hdr++ = cpu_to_le16(ATH_USB_TX_STREAM_MODE_TAG); + + usb_fill_bulk_urb(urb, hif_dev->udev, + usb_sndbulkpipe(hif_dev->udev, USB_WLAN_TX_PIPE), + skb->data, skb->len, + hif_usb_mgmt_cb, cmd); + + usb_anchor_urb(urb, &hif_dev->mgmt_submitted); + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) { + usb_unanchor_urb(urb); + kfree(cmd); + } + usb_free_urb(urb); + + return ret; +} + +static inline void ath9k_skb_queue_purge(struct hif_device_usb *hif_dev, + struct sk_buff_head *list) +{ + struct sk_buff *skb; + + while ((skb = __skb_dequeue(list)) != NULL) { + dev_kfree_skb_any(skb); + } +} + +static inline void ath9k_skb_queue_complete(struct hif_device_usb *hif_dev, + struct sk_buff_head *queue, + bool txok) +{ + struct sk_buff *skb; + + while ((skb = __skb_dequeue(queue)) != NULL) { + ath9k_htc_txcompletion_cb(hif_dev->htc_handle, + skb, txok); + if (txok) + TX_STAT_INC(skb_success); + else + TX_STAT_INC(skb_failed); + } +} + +static void hif_usb_tx_cb(struct urb *urb) +{ + struct tx_buf *tx_buf = (struct tx_buf *) urb->context; + struct hif_device_usb *hif_dev; + bool txok = true; + + if (!tx_buf || !tx_buf->hif_dev) + return; + + hif_dev = tx_buf->hif_dev; + + switch (urb->status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ENODEV: + case -ESHUTDOWN: + txok = false; + + /* + * If the URBs are being flushed, no need to add this + * URB to the free list. + */ + spin_lock(&hif_dev->tx.tx_lock); + if (hif_dev->tx.flags & HIF_USB_TX_FLUSH) { + spin_unlock(&hif_dev->tx.tx_lock); + ath9k_skb_queue_purge(hif_dev, &tx_buf->skb_queue); + return; + } + spin_unlock(&hif_dev->tx.tx_lock); + + break; + default: + txok = false; + break; + } + + ath9k_skb_queue_complete(hif_dev, &tx_buf->skb_queue, txok); + + /* Re-initialize the SKB queue */ + tx_buf->len = tx_buf->offset = 0; + __skb_queue_head_init(&tx_buf->skb_queue); + + /* Add this TX buffer to the free list */ + spin_lock(&hif_dev->tx.tx_lock); + list_move_tail(&tx_buf->list, &hif_dev->tx.tx_buf); + hif_dev->tx.tx_buf_cnt++; + if (!(hif_dev->tx.flags & HIF_USB_TX_STOP)) + __hif_usb_tx(hif_dev); /* Check for pending SKBs */ + TX_STAT_INC(buf_completed); + spin_unlock(&hif_dev->tx.tx_lock); +} + +/* TX lock has to be taken */ +static int __hif_usb_tx(struct hif_device_usb *hif_dev) +{ + struct tx_buf *tx_buf = NULL; + struct sk_buff *nskb = NULL; + int ret = 0, i; + u16 tx_skb_cnt = 0; + u8 *buf; + __le16 *hdr; + + if (hif_dev->tx.tx_skb_cnt == 0) + return 0; + + /* Check if a free TX buffer is available */ + if (list_empty(&hif_dev->tx.tx_buf)) + return 0; + + tx_buf = list_first_entry(&hif_dev->tx.tx_buf, struct tx_buf, list); + list_move_tail(&tx_buf->list, &hif_dev->tx.tx_pending); + hif_dev->tx.tx_buf_cnt--; + + tx_skb_cnt = min_t(u16, hif_dev->tx.tx_skb_cnt, MAX_TX_AGGR_NUM); + + for (i = 0; i < tx_skb_cnt; i++) { + nskb = __skb_dequeue(&hif_dev->tx.tx_skb_queue); + + /* Should never be NULL */ + BUG_ON(!nskb); + + hif_dev->tx.tx_skb_cnt--; + + buf = tx_buf->buf; + buf += tx_buf->offset; + hdr = (__le16 *)buf; + *hdr++ = cpu_to_le16(nskb->len); + *hdr++ = cpu_to_le16(ATH_USB_TX_STREAM_MODE_TAG); + buf += 4; + memcpy(buf, nskb->data, nskb->len); + tx_buf->len = nskb->len + 4; + + if (i < (tx_skb_cnt - 1)) + tx_buf->offset += (((tx_buf->len - 1) / 4) + 1) * 4; + + if (i == (tx_skb_cnt - 1)) + tx_buf->len += tx_buf->offset; + + __skb_queue_tail(&tx_buf->skb_queue, nskb); + TX_STAT_INC(skb_queued); + } + + usb_fill_bulk_urb(tx_buf->urb, hif_dev->udev, + usb_sndbulkpipe(hif_dev->udev, USB_WLAN_TX_PIPE), + tx_buf->buf, tx_buf->len, + hif_usb_tx_cb, tx_buf); + + ret = usb_submit_urb(tx_buf->urb, GFP_ATOMIC); + if (ret) { + tx_buf->len = tx_buf->offset = 0; + ath9k_skb_queue_complete(hif_dev, &tx_buf->skb_queue, false); + __skb_queue_head_init(&tx_buf->skb_queue); + list_move_tail(&tx_buf->list, &hif_dev->tx.tx_buf); + hif_dev->tx.tx_buf_cnt++; + } + + if (!ret) + TX_STAT_INC(buf_queued); + + return ret; +} + +static int hif_usb_send_tx(struct hif_device_usb *hif_dev, struct sk_buff *skb) +{ + struct ath9k_htc_tx_ctl *tx_ctl; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&hif_dev->tx.tx_lock, flags); + + if (hif_dev->tx.flags & HIF_USB_TX_STOP) { + spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags); + return -ENODEV; + } + + /* Check if the max queue count has been reached */ + if (hif_dev->tx.tx_skb_cnt > MAX_TX_BUF_NUM) { + spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags); + return -ENOMEM; + } + + spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags); + + tx_ctl = HTC_SKB_CB(skb); + + /* Mgmt/Beacon frames don't use the TX buffer pool */ + if ((tx_ctl->type == ATH9K_HTC_MGMT) || + (tx_ctl->type == ATH9K_HTC_BEACON)) { + ret = hif_usb_send_mgmt(hif_dev, skb); + } + + spin_lock_irqsave(&hif_dev->tx.tx_lock, flags); + + if ((tx_ctl->type == ATH9K_HTC_NORMAL) || + (tx_ctl->type == ATH9K_HTC_AMPDU)) { + __skb_queue_tail(&hif_dev->tx.tx_skb_queue, skb); + hif_dev->tx.tx_skb_cnt++; + } + + /* Check if AMPDUs have to be sent immediately */ + if ((hif_dev->tx.tx_buf_cnt == MAX_TX_URB_NUM) && + (hif_dev->tx.tx_skb_cnt < 2)) { + __hif_usb_tx(hif_dev); + } + + spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags); + + return ret; +} + +static void hif_usb_start(void *hif_handle) +{ + struct hif_device_usb *hif_dev = (struct hif_device_usb *)hif_handle; + unsigned long flags; + + hif_dev->flags |= HIF_USB_START; + + spin_lock_irqsave(&hif_dev->tx.tx_lock, flags); + hif_dev->tx.flags &= ~HIF_USB_TX_STOP; + spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags); +} + +static void hif_usb_stop(void *hif_handle) +{ + struct hif_device_usb *hif_dev = (struct hif_device_usb *)hif_handle; + struct tx_buf *tx_buf = NULL, *tx_buf_tmp = NULL; + unsigned long flags; + + spin_lock_irqsave(&hif_dev->tx.tx_lock, flags); + ath9k_skb_queue_complete(hif_dev, &hif_dev->tx.tx_skb_queue, false); + hif_dev->tx.tx_skb_cnt = 0; + hif_dev->tx.flags |= HIF_USB_TX_STOP; + spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags); + + /* The pending URBs have to be canceled. */ + list_for_each_entry_safe(tx_buf, tx_buf_tmp, + &hif_dev->tx.tx_pending, list) { + usb_kill_urb(tx_buf->urb); + } + + usb_kill_anchored_urbs(&hif_dev->mgmt_submitted); +} + +static int hif_usb_send(void *hif_handle, u8 pipe_id, struct sk_buff *skb) +{ + struct hif_device_usb *hif_dev = (struct hif_device_usb *)hif_handle; + int ret = 0; + + switch (pipe_id) { + case USB_WLAN_TX_PIPE: + ret = hif_usb_send_tx(hif_dev, skb); + break; + case USB_REG_OUT_PIPE: + ret = hif_usb_send_regout(hif_dev, skb); + break; + default: + dev_err(&hif_dev->udev->dev, + "ath9k_htc: Invalid TX pipe: %d\n", pipe_id); + ret = -EINVAL; + break; + } + + return ret; +} + +static inline bool check_index(struct sk_buff *skb, u8 idx) +{ + struct ath9k_htc_tx_ctl *tx_ctl; + + tx_ctl = HTC_SKB_CB(skb); + + if ((tx_ctl->type == ATH9K_HTC_AMPDU) && + (tx_ctl->sta_idx == idx)) + return true; + + return false; +} + +static void hif_usb_sta_drain(void *hif_handle, u8 idx) +{ + struct hif_device_usb *hif_dev = (struct hif_device_usb *)hif_handle; + struct sk_buff *skb, *tmp; + unsigned long flags; + + spin_lock_irqsave(&hif_dev->tx.tx_lock, flags); + + skb_queue_walk_safe(&hif_dev->tx.tx_skb_queue, skb, tmp) { + if (check_index(skb, idx)) { + __skb_unlink(skb, &hif_dev->tx.tx_skb_queue); + ath9k_htc_txcompletion_cb(hif_dev->htc_handle, + skb, false); + hif_dev->tx.tx_skb_cnt--; + TX_STAT_INC(skb_failed); + } + } + + spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags); +} + +static struct ath9k_htc_hif hif_usb = { + .transport = ATH9K_HIF_USB, + .name = "ath9k_hif_usb", + + .control_ul_pipe = USB_REG_OUT_PIPE, + .control_dl_pipe = USB_REG_IN_PIPE, + + .start = hif_usb_start, + .stop = hif_usb_stop, + .sta_drain = hif_usb_sta_drain, + .send = hif_usb_send, +}; + +static void ath9k_hif_usb_rx_stream(struct hif_device_usb *hif_dev, + struct sk_buff *skb) +{ + struct sk_buff *nskb, *skb_pool[MAX_PKT_NUM_IN_TRANSFER]; + int index = 0, i = 0, len = skb->len; + int rx_remain_len, rx_pkt_len; + u16 pool_index = 0; + u8 *ptr; + + spin_lock(&hif_dev->rx_lock); + + rx_remain_len = hif_dev->rx_remain_len; + rx_pkt_len = hif_dev->rx_transfer_len; + + if (rx_remain_len != 0) { + struct sk_buff *remain_skb = hif_dev->remain_skb; + + if (remain_skb) { + ptr = (u8 *) remain_skb->data; + + index = rx_remain_len; + rx_remain_len -= hif_dev->rx_pad_len; + ptr += rx_pkt_len; + + memcpy(ptr, skb->data, rx_remain_len); + + rx_pkt_len += rx_remain_len; + hif_dev->rx_remain_len = 0; + skb_put(remain_skb, rx_pkt_len); + + skb_pool[pool_index++] = remain_skb; + + } else { + index = rx_remain_len; + } + } + + spin_unlock(&hif_dev->rx_lock); + + while (index < len) { + u16 pkt_len; + u16 pkt_tag; + u16 pad_len; + int chk_idx; + + ptr = (u8 *) skb->data; + + pkt_len = get_unaligned_le16(ptr + index); + pkt_tag = get_unaligned_le16(ptr + index + 2); + + if (pkt_tag != ATH_USB_RX_STREAM_MODE_TAG) { + RX_STAT_INC(skb_dropped); + return; + } + + pad_len = 4 - (pkt_len & 0x3); + if (pad_len == 4) + pad_len = 0; + + chk_idx = index; + index = index + 4 + pkt_len + pad_len; + + if (index > MAX_RX_BUF_SIZE) { + spin_lock(&hif_dev->rx_lock); + hif_dev->rx_remain_len = index - MAX_RX_BUF_SIZE; + hif_dev->rx_transfer_len = + MAX_RX_BUF_SIZE - chk_idx - 4; + hif_dev->rx_pad_len = pad_len; + + nskb = __dev_alloc_skb(pkt_len + 32, GFP_ATOMIC); + if (!nskb) { + dev_err(&hif_dev->udev->dev, + "ath9k_htc: RX memory allocation error\n"); + spin_unlock(&hif_dev->rx_lock); + goto err; + } + skb_reserve(nskb, 32); + RX_STAT_INC(skb_allocated); + + memcpy(nskb->data, &(skb->data[chk_idx+4]), + hif_dev->rx_transfer_len); + + /* Record the buffer pointer */ + hif_dev->remain_skb = nskb; + spin_unlock(&hif_dev->rx_lock); + } else { + nskb = __dev_alloc_skb(pkt_len + 32, GFP_ATOMIC); + if (!nskb) { + dev_err(&hif_dev->udev->dev, + "ath9k_htc: RX memory allocation error\n"); + goto err; + } + skb_reserve(nskb, 32); + RX_STAT_INC(skb_allocated); + + memcpy(nskb->data, &(skb->data[chk_idx+4]), pkt_len); + skb_put(nskb, pkt_len); + skb_pool[pool_index++] = nskb; + } + } + +err: + for (i = 0; i < pool_index; i++) { + ath9k_htc_rx_msg(hif_dev->htc_handle, skb_pool[i], + skb_pool[i]->len, USB_WLAN_RX_PIPE); + RX_STAT_INC(skb_completed); + } +} + +static void ath9k_hif_usb_rx_cb(struct urb *urb) +{ + struct sk_buff *skb = (struct sk_buff *) urb->context; + struct hif_device_usb *hif_dev = + usb_get_intfdata(usb_ifnum_to_if(urb->dev, 0)); + int ret; + + if (!skb) + return; + + if (!hif_dev) + goto free; + + switch (urb->status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ENODEV: + case -ESHUTDOWN: + goto free; + default: + goto resubmit; + } + + if (likely(urb->actual_length != 0)) { + skb_put(skb, urb->actual_length); + ath9k_hif_usb_rx_stream(hif_dev, skb); + } + +resubmit: + skb_reset_tail_pointer(skb); + skb_trim(skb, 0); + + usb_anchor_urb(urb, &hif_dev->rx_submitted); + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) { + usb_unanchor_urb(urb); + goto free; + } + + return; +free: + kfree_skb(skb); +} + +static void ath9k_hif_usb_reg_in_cb(struct urb *urb) +{ + struct sk_buff *skb = (struct sk_buff *) urb->context; + struct sk_buff *nskb; + struct hif_device_usb *hif_dev = + usb_get_intfdata(usb_ifnum_to_if(urb->dev, 0)); + int ret; + + if (!skb) + return; + + if (!hif_dev) + goto free; + + switch (urb->status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ENODEV: + case -ESHUTDOWN: + goto free; + default: + skb_reset_tail_pointer(skb); + skb_trim(skb, 0); + + goto resubmit; + } + + if (likely(urb->actual_length != 0)) { + skb_put(skb, urb->actual_length); + + /* Process the command first */ + ath9k_htc_rx_msg(hif_dev->htc_handle, skb, + skb->len, USB_REG_IN_PIPE); + + + nskb = alloc_skb(MAX_REG_IN_BUF_SIZE, GFP_ATOMIC); + if (!nskb) { + dev_err(&hif_dev->udev->dev, + "ath9k_htc: REG_IN memory allocation failure\n"); + urb->context = NULL; + return; + } + + usb_fill_bulk_urb(urb, hif_dev->udev, + usb_rcvbulkpipe(hif_dev->udev, + USB_REG_IN_PIPE), + nskb->data, MAX_REG_IN_BUF_SIZE, + ath9k_hif_usb_reg_in_cb, nskb); + } + +resubmit: + usb_anchor_urb(urb, &hif_dev->reg_in_submitted); + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) { + usb_unanchor_urb(urb); + goto free; + } + + return; +free: + kfree_skb(skb); + urb->context = NULL; +} + +static void ath9k_hif_usb_dealloc_tx_urbs(struct hif_device_usb *hif_dev) +{ + struct tx_buf *tx_buf = NULL, *tx_buf_tmp = NULL; + unsigned long flags; + + list_for_each_entry_safe(tx_buf, tx_buf_tmp, + &hif_dev->tx.tx_buf, list) { + usb_kill_urb(tx_buf->urb); + list_del(&tx_buf->list); + usb_free_urb(tx_buf->urb); + kfree(tx_buf->buf); + kfree(tx_buf); + } + + spin_lock_irqsave(&hif_dev->tx.tx_lock, flags); + hif_dev->tx.flags |= HIF_USB_TX_FLUSH; + spin_unlock_irqrestore(&hif_dev->tx.tx_lock, flags); + + list_for_each_entry_safe(tx_buf, tx_buf_tmp, + &hif_dev->tx.tx_pending, list) { + usb_kill_urb(tx_buf->urb); + list_del(&tx_buf->list); + usb_free_urb(tx_buf->urb); + kfree(tx_buf->buf); + kfree(tx_buf); + } + + usb_kill_anchored_urbs(&hif_dev->mgmt_submitted); +} + +static int ath9k_hif_usb_alloc_tx_urbs(struct hif_device_usb *hif_dev) +{ + struct tx_buf *tx_buf; + int i; + + INIT_LIST_HEAD(&hif_dev->tx.tx_buf); + INIT_LIST_HEAD(&hif_dev->tx.tx_pending); + spin_lock_init(&hif_dev->tx.tx_lock); + __skb_queue_head_init(&hif_dev->tx.tx_skb_queue); + init_usb_anchor(&hif_dev->mgmt_submitted); + + for (i = 0; i < MAX_TX_URB_NUM; i++) { + tx_buf = kzalloc(sizeof(struct tx_buf), GFP_KERNEL); + if (!tx_buf) + goto err; + + tx_buf->buf = kzalloc(MAX_TX_BUF_SIZE, GFP_KERNEL); + if (!tx_buf->buf) + goto err; + + tx_buf->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!tx_buf->urb) + goto err; + + tx_buf->hif_dev = hif_dev; + __skb_queue_head_init(&tx_buf->skb_queue); + + list_add_tail(&tx_buf->list, &hif_dev->tx.tx_buf); + } + + hif_dev->tx.tx_buf_cnt = MAX_TX_URB_NUM; + + return 0; +err: + if (tx_buf) { + kfree(tx_buf->buf); + kfree(tx_buf); + } + ath9k_hif_usb_dealloc_tx_urbs(hif_dev); + return -ENOMEM; +} + +static void ath9k_hif_usb_dealloc_rx_urbs(struct hif_device_usb *hif_dev) +{ + usb_kill_anchored_urbs(&hif_dev->rx_submitted); +} + +static int ath9k_hif_usb_alloc_rx_urbs(struct hif_device_usb *hif_dev) +{ + struct urb *urb = NULL; + struct sk_buff *skb = NULL; + int i, ret; + + init_usb_anchor(&hif_dev->rx_submitted); + spin_lock_init(&hif_dev->rx_lock); + + for (i = 0; i < MAX_RX_URB_NUM; i++) { + + /* Allocate URB */ + urb = usb_alloc_urb(0, GFP_KERNEL); + if (urb == NULL) { + ret = -ENOMEM; + goto err_urb; + } + + /* Allocate buffer */ + skb = alloc_skb(MAX_RX_BUF_SIZE, GFP_KERNEL); + if (!skb) { + ret = -ENOMEM; + goto err_skb; + } + + usb_fill_bulk_urb(urb, hif_dev->udev, + usb_rcvbulkpipe(hif_dev->udev, + USB_WLAN_RX_PIPE), + skb->data, MAX_RX_BUF_SIZE, + ath9k_hif_usb_rx_cb, skb); + + /* Anchor URB */ + usb_anchor_urb(urb, &hif_dev->rx_submitted); + + /* Submit URB */ + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret) { + usb_unanchor_urb(urb); + goto err_submit; + } + + /* + * Drop reference count. + * This ensures that the URB is freed when killing them. + */ + usb_free_urb(urb); + } + + return 0; + +err_submit: + kfree_skb(skb); +err_skb: + usb_free_urb(urb); +err_urb: + ath9k_hif_usb_dealloc_rx_urbs(hif_dev); + return ret; +} + +static void ath9k_hif_usb_dealloc_reg_in_urbs(struct hif_device_usb *hif_dev) +{ + usb_kill_anchored_urbs(&hif_dev->reg_in_submitted); +} + +static int ath9k_hif_usb_alloc_reg_in_urbs(struct hif_device_usb *hif_dev) +{ + struct urb *urb = NULL; + struct sk_buff *skb = NULL; + int i, ret; + + init_usb_anchor(&hif_dev->reg_in_submitted); + + for (i = 0; i < MAX_REG_IN_URB_NUM; i++) { + + /* Allocate URB */ + urb = usb_alloc_urb(0, GFP_KERNEL); + if (urb == NULL) { + ret = -ENOMEM; + goto err_urb; + } + + /* Allocate buffer */ + skb = alloc_skb(MAX_REG_IN_BUF_SIZE, GFP_KERNEL); + if (!skb) { + ret = -ENOMEM; + goto err_skb; + } + + usb_fill_bulk_urb(urb, hif_dev->udev, + usb_rcvbulkpipe(hif_dev->udev, + USB_REG_IN_PIPE), + skb->data, MAX_REG_IN_BUF_SIZE, + ath9k_hif_usb_reg_in_cb, skb); + + /* Anchor URB */ + usb_anchor_urb(urb, &hif_dev->reg_in_submitted); + + /* Submit URB */ + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret) { + usb_unanchor_urb(urb); + goto err_submit; + } + + /* + * Drop reference count. + * This ensures that the URB is freed when killing them. + */ + usb_free_urb(urb); + } + + return 0; + +err_submit: + kfree_skb(skb); +err_skb: + usb_free_urb(urb); +err_urb: + ath9k_hif_usb_dealloc_reg_in_urbs(hif_dev); + return ret; +} + +static int ath9k_hif_usb_alloc_urbs(struct hif_device_usb *hif_dev) +{ + /* Register Write */ + init_usb_anchor(&hif_dev->regout_submitted); + + /* TX */ + if (ath9k_hif_usb_alloc_tx_urbs(hif_dev) < 0) + goto err; + + /* RX */ + if (ath9k_hif_usb_alloc_rx_urbs(hif_dev) < 0) + goto err_rx; + + /* Register Read */ + if (ath9k_hif_usb_alloc_reg_in_urbs(hif_dev) < 0) + goto err_reg; + + return 0; +err_reg: + ath9k_hif_usb_dealloc_rx_urbs(hif_dev); +err_rx: + ath9k_hif_usb_dealloc_tx_urbs(hif_dev); +err: + return -ENOMEM; +} + +static void ath9k_hif_usb_dealloc_urbs(struct hif_device_usb *hif_dev) +{ + usb_kill_anchored_urbs(&hif_dev->regout_submitted); + ath9k_hif_usb_dealloc_reg_in_urbs(hif_dev); + ath9k_hif_usb_dealloc_tx_urbs(hif_dev); + ath9k_hif_usb_dealloc_rx_urbs(hif_dev); +} + +static int ath9k_hif_usb_download_fw(struct hif_device_usb *hif_dev) +{ + int transfer, err; + const void *data = hif_dev->fw_data; + size_t len = hif_dev->fw_size; + u32 addr = AR9271_FIRMWARE; + u8 *buf = kzalloc(4096, GFP_KERNEL); + u32 firm_offset; + + if (!buf) + return -ENOMEM; + + while (len) { + transfer = min_t(size_t, len, 4096); + memcpy(buf, data, transfer); + + err = usb_control_msg(hif_dev->udev, + usb_sndctrlpipe(hif_dev->udev, 0), + FIRMWARE_DOWNLOAD, 0x40 | USB_DIR_OUT, + addr >> 8, 0, buf, transfer, HZ); + if (err < 0) { + kfree(buf); + return err; + } + + len -= transfer; + data += transfer; + addr += transfer; + } + kfree(buf); + + if (IS_AR7010_DEVICE(hif_dev->usb_device_id->driver_info)) + firm_offset = AR7010_FIRMWARE_TEXT; + else + firm_offset = AR9271_FIRMWARE_TEXT; + + /* + * Issue FW download complete command to firmware. + */ + err = usb_control_msg(hif_dev->udev, usb_sndctrlpipe(hif_dev->udev, 0), + FIRMWARE_DOWNLOAD_COMP, + 0x40 | USB_DIR_OUT, + firm_offset >> 8, 0, NULL, 0, HZ); + if (err) + return -EIO; + + dev_info(&hif_dev->udev->dev, "ath9k_htc: Transferred FW: %s, size: %ld\n", + hif_dev->fw_name, (unsigned long) hif_dev->fw_size); + + return 0; +} + +static int ath9k_hif_usb_dev_init(struct hif_device_usb *hif_dev) +{ + struct usb_host_interface *alt = &hif_dev->interface->altsetting[0]; + struct usb_endpoint_descriptor *endp; + int ret, idx; + + ret = ath9k_hif_usb_download_fw(hif_dev); + if (ret) { + dev_err(&hif_dev->udev->dev, + "ath9k_htc: Firmware - %s download failed\n", + hif_dev->fw_name); + return ret; + } + + /* On downloading the firmware to the target, the USB descriptor of EP4 + * is 'patched' to change the type of the endpoint to Bulk. This will + * bring down CPU usage during the scan period. + */ + for (idx = 0; idx < alt->desc.bNumEndpoints; idx++) { + endp = &alt->endpoint[idx].desc; + if ((endp->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + == USB_ENDPOINT_XFER_INT) { + endp->bmAttributes &= ~USB_ENDPOINT_XFERTYPE_MASK; + endp->bmAttributes |= USB_ENDPOINT_XFER_BULK; + endp->bInterval = 0; + } + } + + /* Alloc URBs */ + ret = ath9k_hif_usb_alloc_urbs(hif_dev); + if (ret) { + dev_err(&hif_dev->udev->dev, + "ath9k_htc: Unable to allocate URBs\n"); + return ret; + } + + return 0; +} + +static void ath9k_hif_usb_dev_deinit(struct hif_device_usb *hif_dev) +{ + ath9k_hif_usb_dealloc_urbs(hif_dev); +} + +/* + * If initialization fails or the FW cannot be retrieved, + * detach the device. + */ +static void ath9k_hif_usb_firmware_fail(struct hif_device_usb *hif_dev) +{ + struct device *dev = &hif_dev->udev->dev; + struct device *parent = dev->parent; + + complete(&hif_dev->fw_done); + + if (parent) + device_lock(parent); + + device_release_driver(dev); + + if (parent) + device_unlock(parent); +} + +static void ath9k_hif_usb_firmware_cb(const struct firmware *fw, void *context) +{ + struct hif_device_usb *hif_dev = context; + int ret; + + if (!fw) { + dev_err(&hif_dev->udev->dev, + "ath9k_htc: Failed to get firmware %s\n", + hif_dev->fw_name); + goto err_fw; + } + + hif_dev->htc_handle = ath9k_htc_hw_alloc(hif_dev, &hif_usb, + &hif_dev->udev->dev); + if (hif_dev->htc_handle == NULL) + goto err_dev_alloc; + + hif_dev->fw_data = fw->data; + hif_dev->fw_size = fw->size; + + /* Proceed with initialization */ + + ret = ath9k_hif_usb_dev_init(hif_dev); + if (ret) + goto err_dev_init; + + ret = ath9k_htc_hw_init(hif_dev->htc_handle, + &hif_dev->interface->dev, + hif_dev->usb_device_id->idProduct, + hif_dev->udev->product, + hif_dev->usb_device_id->driver_info); + if (ret) { + ret = -EINVAL; + goto err_htc_hw_init; + } + + release_firmware(fw); + hif_dev->flags |= HIF_USB_READY; + complete(&hif_dev->fw_done); + + return; + +err_htc_hw_init: + ath9k_hif_usb_dev_deinit(hif_dev); +err_dev_init: + ath9k_htc_hw_free(hif_dev->htc_handle); +err_dev_alloc: + release_firmware(fw); +err_fw: + ath9k_hif_usb_firmware_fail(hif_dev); +} + +/* + * An exact copy of the function from zd1211rw. + */ +static int send_eject_command(struct usb_interface *interface) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_host_interface *iface_desc = &interface->altsetting[0]; + struct usb_endpoint_descriptor *endpoint; + unsigned char *cmd; + u8 bulk_out_ep; + int r; + + /* Find bulk out endpoint */ + for (r = 1; r >= 0; r--) { + endpoint = &iface_desc->endpoint[r].desc; + if (usb_endpoint_dir_out(endpoint) && + usb_endpoint_xfer_bulk(endpoint)) { + bulk_out_ep = endpoint->bEndpointAddress; + break; + } + } + if (r == -1) { + dev_err(&udev->dev, + "ath9k_htc: Could not find bulk out endpoint\n"); + return -ENODEV; + } + + cmd = kzalloc(31, GFP_KERNEL); + if (cmd == NULL) + return -ENODEV; + + /* USB bulk command block */ + cmd[0] = 0x55; /* bulk command signature */ + cmd[1] = 0x53; /* bulk command signature */ + cmd[2] = 0x42; /* bulk command signature */ + cmd[3] = 0x43; /* bulk command signature */ + cmd[14] = 6; /* command length */ + + cmd[15] = 0x1b; /* SCSI command: START STOP UNIT */ + cmd[19] = 0x2; /* eject disc */ + + dev_info(&udev->dev, "Ejecting storage device...\n"); + r = usb_bulk_msg(udev, usb_sndbulkpipe(udev, bulk_out_ep), + cmd, 31, NULL, 2000); + kfree(cmd); + if (r) + return r; + + /* At this point, the device disconnects and reconnects with the real + * ID numbers. */ + + usb_set_intfdata(interface, NULL); + return 0; +} + +static int ath9k_hif_usb_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct hif_device_usb *hif_dev; + int ret = 0; + + if (id->driver_info == STORAGE_DEVICE) + return send_eject_command(interface); + + hif_dev = kzalloc(sizeof(struct hif_device_usb), GFP_KERNEL); + if (!hif_dev) { + ret = -ENOMEM; + goto err_alloc; + } + + usb_get_dev(udev); + + hif_dev->udev = udev; + hif_dev->interface = interface; + hif_dev->usb_device_id = id; +#ifdef CONFIG_PM + udev->reset_resume = 1; +#endif + usb_set_intfdata(interface, hif_dev); + + init_completion(&hif_dev->fw_done); + + /* Find out which firmware to load */ + + if (IS_AR7010_DEVICE(id->driver_info)) + hif_dev->fw_name = FIRMWARE_AR7010_1_1; + else + hif_dev->fw_name = FIRMWARE_AR9271; + + ret = request_firmware_nowait(THIS_MODULE, true, hif_dev->fw_name, + &hif_dev->udev->dev, GFP_KERNEL, + hif_dev, ath9k_hif_usb_firmware_cb); + if (ret) { + dev_err(&hif_dev->udev->dev, + "ath9k_htc: Async request for firmware %s failed\n", + hif_dev->fw_name); + goto err_fw_req; + } + + dev_info(&hif_dev->udev->dev, "ath9k_htc: Firmware %s requested\n", + hif_dev->fw_name); + + return 0; + +err_fw_req: + usb_set_intfdata(interface, NULL); + kfree(hif_dev); + usb_put_dev(udev); +err_alloc: + return ret; +} + +static void ath9k_hif_usb_reboot(struct usb_device *udev) +{ + u32 reboot_cmd = 0xffffffff; + void *buf; + int ret; + + buf = kmemdup(&reboot_cmd, 4, GFP_KERNEL); + if (!buf) + return; + + ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, USB_REG_OUT_PIPE), + buf, 4, NULL, HZ); + if (ret) + dev_err(&udev->dev, "ath9k_htc: USB reboot failed\n"); + + kfree(buf); +} + +static void ath9k_hif_usb_disconnect(struct usb_interface *interface) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct hif_device_usb *hif_dev = usb_get_intfdata(interface); + bool unplugged = (udev->state == USB_STATE_NOTATTACHED) ? true : false; + + if (!hif_dev) + return; + + wait_for_completion(&hif_dev->fw_done); + + if (hif_dev->flags & HIF_USB_READY) { + ath9k_htc_hw_deinit(hif_dev->htc_handle, unplugged); + ath9k_htc_hw_free(hif_dev->htc_handle); + ath9k_hif_usb_dev_deinit(hif_dev); + } + + usb_set_intfdata(interface, NULL); + + /* If firmware was loaded we should drop it + * go back to first stage bootloader. */ + if (!unplugged && (hif_dev->flags & HIF_USB_READY)) + ath9k_hif_usb_reboot(udev); + + kfree(hif_dev); + dev_info(&udev->dev, "ath9k_htc: USB layer deinitialized\n"); + usb_put_dev(udev); +} + +#ifdef CONFIG_PM +static int ath9k_hif_usb_suspend(struct usb_interface *interface, + pm_message_t message) +{ + struct hif_device_usb *hif_dev = usb_get_intfdata(interface); + + /* + * The device has to be set to FULLSLEEP mode in case no + * interface is up. + */ + if (!(hif_dev->flags & HIF_USB_START)) + ath9k_htc_suspend(hif_dev->htc_handle); + + ath9k_hif_usb_dealloc_urbs(hif_dev); + + return 0; +} + +static int ath9k_hif_usb_resume(struct usb_interface *interface) +{ + struct hif_device_usb *hif_dev = usb_get_intfdata(interface); + struct htc_target *htc_handle = hif_dev->htc_handle; + int ret; + const struct firmware *fw; + + ret = ath9k_hif_usb_alloc_urbs(hif_dev); + if (ret) + return ret; + + if (hif_dev->flags & HIF_USB_READY) { + /* request cached firmware during suspend/resume cycle */ + ret = request_firmware(&fw, hif_dev->fw_name, + &hif_dev->udev->dev); + if (ret) + goto fail_resume; + + hif_dev->fw_data = fw->data; + hif_dev->fw_size = fw->size; + ret = ath9k_hif_usb_download_fw(hif_dev); + release_firmware(fw); + if (ret) + goto fail_resume; + } else { + ath9k_hif_usb_dealloc_urbs(hif_dev); + return -EIO; + } + + mdelay(100); + + ret = ath9k_htc_resume(htc_handle); + + if (ret) + goto fail_resume; + + return 0; + +fail_resume: + ath9k_hif_usb_dealloc_urbs(hif_dev); + + return ret; +} +#endif + +static struct usb_driver ath9k_hif_usb_driver = { + .name = KBUILD_MODNAME, + .probe = ath9k_hif_usb_probe, + .disconnect = ath9k_hif_usb_disconnect, +#ifdef CONFIG_PM + .suspend = ath9k_hif_usb_suspend, + .resume = ath9k_hif_usb_resume, + .reset_resume = ath9k_hif_usb_resume, +#endif + .id_table = ath9k_hif_usb_ids, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) + .soft_unbind = 1, +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +int ath9k_hif_usb_init(void) +{ + return usb_register(&ath9k_hif_usb_driver); +} + +void ath9k_hif_usb_exit(void) +{ + usb_deregister(&ath9k_hif_usb_driver); +} diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.h b/drivers/net/wireless/ath/ath9k/hif_usb.h new file mode 100644 index 0000000..51496e7 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/hif_usb.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef HTC_USB_H +#define HTC_USB_H + +#define MAJOR_VERSION_REQ 1 +#define MINOR_VERSION_REQ 3 + +#define IS_AR7010_DEVICE(_v) (((_v) == AR9280_USB) || ((_v) == AR9287_USB)) + +#define AR9271_FIRMWARE 0x501000 +#define AR9271_FIRMWARE_TEXT 0x903000 +#define AR7010_FIRMWARE_TEXT 0x906000 + +#define FIRMWARE_DOWNLOAD 0x30 +#define FIRMWARE_DOWNLOAD_COMP 0x31 + +#define ATH_USB_RX_STREAM_MODE_TAG 0x4e00 +#define ATH_USB_TX_STREAM_MODE_TAG 0x697e + +/* FIXME: Verify these numbers (with Windows) */ +#define MAX_TX_URB_NUM 8 +#define MAX_TX_BUF_NUM 256 +#define MAX_TX_BUF_SIZE 32768 +#define MAX_TX_AGGR_NUM 20 + +#define MAX_RX_URB_NUM 8 +#define MAX_RX_BUF_SIZE 16384 +#define MAX_PKT_NUM_IN_TRANSFER 10 + +#define MAX_REG_OUT_URB_NUM 1 +#define MAX_REG_IN_URB_NUM 64 + +#define MAX_REG_IN_BUF_SIZE 64 + +/* USB Endpoint definition */ +#define USB_WLAN_TX_PIPE 1 +#define USB_WLAN_RX_PIPE 2 +#define USB_REG_IN_PIPE 3 +#define USB_REG_OUT_PIPE 4 + +#define HIF_USB_MAX_RXPIPES 2 +#define HIF_USB_MAX_TXPIPES 4 + +struct tx_buf { + u8 *buf; + u16 len; + u16 offset; + struct urb *urb; + struct sk_buff_head skb_queue; + struct hif_device_usb *hif_dev; + struct list_head list; +}; + +#define HIF_USB_TX_STOP BIT(0) +#define HIF_USB_TX_FLUSH BIT(1) + +struct hif_usb_tx { + u8 flags; + u8 tx_buf_cnt; + u16 tx_skb_cnt; + struct sk_buff_head tx_skb_queue; + struct list_head tx_buf; + struct list_head tx_pending; + spinlock_t tx_lock; +}; + +struct cmd_buf { + struct sk_buff *skb; + struct hif_device_usb *hif_dev; +}; + +#define HIF_USB_START BIT(0) +#define HIF_USB_READY BIT(1) + +struct hif_device_usb { + struct usb_device *udev; + struct usb_interface *interface; + const struct usb_device_id *usb_device_id; + const void *fw_data; + size_t fw_size; + struct completion fw_done; + struct htc_target *htc_handle; + struct hif_usb_tx tx; + struct usb_anchor regout_submitted; + struct usb_anchor rx_submitted; + struct usb_anchor reg_in_submitted; + struct usb_anchor mgmt_submitted; + struct sk_buff *remain_skb; + const char *fw_name; + int rx_remain_len; + int rx_pkt_len; + int rx_transfer_len; + int rx_pad_len; + spinlock_t rx_lock; + u8 flags; /* HIF_USB_* */ +}; + +int ath9k_hif_usb_init(void); +void ath9k_hif_usb_exit(void); + +#endif /* HTC_USB_H */ diff --git a/drivers/net/wireless/ath/ath9k/htc.h b/drivers/net/wireless/ath/ath9k/htc.h new file mode 100644 index 0000000..a204187 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/htc.h @@ -0,0 +1,609 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef HTC_H +#define HTC_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "htc_hst.h" +#include "hif_usb.h" +#include "wmi.h" + +#define ATH_STA_SHORT_CALINTERVAL 1000 /* 1 second */ +#define ATH_AP_SHORT_CALINTERVAL 100 /* 100 ms */ +#define ATH_ANI_POLLINTERVAL 100 /* 100 ms */ +#define ATH_LONG_CALINTERVAL 30000 /* 30 seconds */ +#define ATH_RESTART_CALINTERVAL 1200000 /* 20 minutes */ + +#define ATH_DEFAULT_BMISS_LIMIT 10 +#define IEEE80211_MS_TO_TU(x) (((x) * 1000) / 1024) +#define TSF_TO_TU(_h, _l) \ + ((((u32)(_h)) << 22) | (((u32)(_l)) >> 10)) + +extern struct ieee80211_ops ath9k_htc_ops; +extern int htc_modparam_nohwcrypt; + +enum htc_phymode { + HTC_MODE_11NA = 0, + HTC_MODE_11NG = 1 +}; + +enum htc_opmode { + HTC_M_STA = 1, + HTC_M_IBSS = 0, + HTC_M_AHDEMO = 3, + HTC_M_HOSTAP = 6, + HTC_M_MONITOR = 8, + HTC_M_WDS = 2 +}; + +#define ATH9K_HTC_AMPDU 1 +#define ATH9K_HTC_NORMAL 2 +#define ATH9K_HTC_BEACON 3 +#define ATH9K_HTC_MGMT 4 + +#define ATH9K_HTC_TX_CTSONLY 0x1 +#define ATH9K_HTC_TX_RTSCTS 0x2 + +struct tx_frame_hdr { + u8 data_type; + u8 node_idx; + u8 vif_idx; + u8 tidno; + __be32 flags; /* ATH9K_HTC_TX_* */ + u8 key_type; + u8 keyix; + u8 cookie; + u8 pad; +} __packed; + +struct tx_mgmt_hdr { + u8 node_idx; + u8 vif_idx; + u8 tidno; + u8 flags; + u8 key_type; + u8 keyix; + u8 cookie; + u8 pad; +} __packed; + +struct tx_beacon_header { + u8 vif_index; + u8 len_changed; + u16 rev; +} __packed; + +#define MAX_TX_AMPDU_SUBFRAMES_9271 17 +#define MAX_TX_AMPDU_SUBFRAMES_7010 22 + +struct ath9k_htc_cap_target { + __be32 ampdu_limit; + u8 ampdu_subframes; + u8 enable_coex; + u8 tx_chainmask; + u8 pad; +} __packed; + +struct ath9k_htc_target_vif { + u8 index; + u8 opmode; + u8 myaddr[ETH_ALEN]; + u8 ath_cap; + __be16 rtsthreshold; + u8 pad; +} __packed; + +struct ath9k_htc_target_sta { + u8 macaddr[ETH_ALEN]; + u8 bssid[ETH_ALEN]; + u8 sta_index; + u8 vif_index; + u8 is_vif_sta; + __be16 flags; + __be16 htcap; + __be16 maxampdu; + u8 pad; +} __packed; + +struct ath9k_htc_target_aggr { + u8 sta_index; + u8 tidno; + u8 aggr_enable; + u8 padding; +} __packed; + +#define ATH_HTC_RATE_MAX 30 + +#define WLAN_RC_DS_FLAG 0x01 +#define WLAN_RC_40_FLAG 0x02 +#define WLAN_RC_SGI_FLAG 0x04 +#define WLAN_RC_HT_FLAG 0x08 + +struct ath9k_htc_rateset { + u8 rs_nrates; + u8 rs_rates[ATH_HTC_RATE_MAX]; +}; + +struct ath9k_htc_rate { + struct ath9k_htc_rateset legacy_rates; + struct ath9k_htc_rateset ht_rates; +} __packed; + +struct ath9k_htc_target_rate { + u8 sta_index; + u8 isnew; + __be32 capflags; + struct ath9k_htc_rate rates; +}; + +struct ath9k_htc_target_rate_mask { + u8 vif_index; + u8 band; + __be32 mask; + u16 pad; +} __packed; + +struct ath9k_htc_target_int_stats { + __be32 rx; + __be32 rxorn; + __be32 rxeol; + __be32 txurn; + __be32 txto; + __be32 cst; +} __packed; + +struct ath9k_htc_target_tx_stats { + __be32 xretries; + __be32 fifoerr; + __be32 filtered; + __be32 timer_exp; + __be32 shortretries; + __be32 longretries; + __be32 qnull; + __be32 encap_fail; + __be32 nobuf; +} __packed; + +struct ath9k_htc_target_rx_stats { + __be32 nobuf; + __be32 host_send; + __be32 host_done; +} __packed; + +#define ATH9K_HTC_MAX_VIF 2 +#define ATH9K_HTC_MAX_BCN_VIF 2 + +#define INC_VIF(_priv, _type) do { \ + switch (_type) { \ + case NL80211_IFTYPE_STATION: \ + _priv->num_sta_vif++; \ + break; \ + case NL80211_IFTYPE_ADHOC: \ + _priv->num_ibss_vif++; \ + break; \ + case NL80211_IFTYPE_AP: \ + _priv->num_ap_vif++; \ + break; \ + default: \ + break; \ + } \ + } while (0) + +#define DEC_VIF(_priv, _type) do { \ + switch (_type) { \ + case NL80211_IFTYPE_STATION: \ + _priv->num_sta_vif--; \ + break; \ + case NL80211_IFTYPE_ADHOC: \ + _priv->num_ibss_vif--; \ + break; \ + case NL80211_IFTYPE_AP: \ + _priv->num_ap_vif--; \ + break; \ + default: \ + break; \ + } \ + } while (0) + +struct ath9k_htc_vif { + u8 index; + u16 seq_no; + bool beacon_configured; + int bslot; + __le64 tsfadjust; +}; + +struct ath9k_vif_iter_data { + const u8 *hw_macaddr; + u8 mask[ETH_ALEN]; +}; + +#define ATH9K_HTC_MAX_STA 8 +#define ATH9K_HTC_MAX_TID 8 + +enum tid_aggr_state { + AGGR_STOP = 0, + AGGR_PROGRESS, + AGGR_START, + AGGR_OPERATIONAL +}; + +struct ath9k_htc_sta { + u8 index; + enum tid_aggr_state tid_state[ATH9K_HTC_MAX_TID]; +}; + +#define ATH9K_HTC_RXBUF 256 +#define HTC_RX_FRAME_HEADER_SIZE 40 + +struct ath9k_htc_rxbuf { + bool in_process; + struct sk_buff *skb; + struct ath_htc_rx_status rxstatus; + struct list_head list; +}; + +struct ath9k_htc_rx { + int last_rssi; /* FIXME: per-STA */ + struct list_head rxbuf; + spinlock_t rxbuflock; +}; + +#define ATH9K_HTC_TX_CLEANUP_INTERVAL 50 /* ms */ +#define ATH9K_HTC_TX_TIMEOUT_INTERVAL 3000 /* ms */ +#define ATH9K_HTC_TX_RESERVE 10 +#define ATH9K_HTC_TX_TIMEOUT_COUNT 40 +#define ATH9K_HTC_TX_THRESHOLD (MAX_TX_BUF_NUM - ATH9K_HTC_TX_RESERVE) + +#define ATH9K_HTC_OP_TX_QUEUES_STOP BIT(0) +#define ATH9K_HTC_OP_TX_DRAIN BIT(1) + +struct ath9k_htc_tx { + u8 flags; + int queued_cnt; + struct sk_buff_head mgmt_ep_queue; + struct sk_buff_head cab_ep_queue; + struct sk_buff_head data_be_queue; + struct sk_buff_head data_bk_queue; + struct sk_buff_head data_vi_queue; + struct sk_buff_head data_vo_queue; + struct sk_buff_head tx_failed; + DECLARE_BITMAP(tx_slot, MAX_TX_BUF_NUM); + struct timer_list cleanup_timer; + spinlock_t tx_lock; +}; + +struct ath9k_htc_tx_ctl { + u8 type; /* ATH9K_HTC_* */ + u8 epid; + u8 txok; + u8 sta_idx; + unsigned long timestamp; +}; + +static inline struct ath9k_htc_tx_ctl *HTC_SKB_CB(struct sk_buff *skb) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + + BUILD_BUG_ON(sizeof(struct ath9k_htc_tx_ctl) > + IEEE80211_TX_INFO_DRIVER_DATA_SIZE); + return (struct ath9k_htc_tx_ctl *) &tx_info->driver_data; +} + +#ifdef CPTCFG_ATH9K_HTC_DEBUGFS + +#define TX_STAT_INC(c) (hif_dev->htc_handle->drv_priv->debug.tx_stats.c++) +#define RX_STAT_INC(c) (hif_dev->htc_handle->drv_priv->debug.rx_stats.c++) +#define CAB_STAT_INC priv->debug.tx_stats.cab_queued++ + +#define TX_QSTAT_INC(q) (priv->debug.tx_stats.queue_stats[q]++) + +void ath9k_htc_err_stat_rx(struct ath9k_htc_priv *priv, + struct ath_htc_rx_status *rxs); + +struct ath_tx_stats { + u32 buf_queued; + u32 buf_completed; + u32 skb_queued; + u32 skb_success; + u32 skb_failed; + u32 cab_queued; + u32 queue_stats[IEEE80211_NUM_ACS]; +}; + +struct ath_rx_stats { + u32 skb_allocated; + u32 skb_completed; + u32 skb_dropped; + u32 err_crc; + u32 err_decrypt_crc; + u32 err_mic; + u32 err_pre_delim; + u32 err_post_delim; + u32 err_decrypt_busy; + u32 err_phy; + u32 err_phy_stats[ATH9K_PHYERR_MAX]; +}; + +struct ath9k_debug { + struct dentry *debugfs_phy; + struct ath_tx_stats tx_stats; + struct ath_rx_stats rx_stats; +}; + +#else + +#define TX_STAT_INC(c) do { } while (0) +#define RX_STAT_INC(c) do { } while (0) +#define CAB_STAT_INC do { } while (0) + +#define TX_QSTAT_INC(c) do { } while (0) + +static inline void ath9k_htc_err_stat_rx(struct ath9k_htc_priv *priv, + struct ath_htc_rx_status *rxs) +{ +} + +#endif /* CPTCFG_ATH9K_HTC_DEBUGFS */ + +#define ATH_LED_PIN_DEF 1 +#define ATH_LED_PIN_9287 10 +#define ATH_LED_PIN_9271 15 +#define ATH_LED_PIN_7010 12 + +#define BSTUCK_THRESHOLD 10 + +/* + * Adjust these when the max. no of beaconing interfaces is + * increased. + */ +#define DEFAULT_SWBA_RESPONSE 40 /* in TUs */ +#define MIN_SWBA_RESPONSE 10 /* in TUs */ + +struct htc_beacon_config { + struct ieee80211_vif *bslot[ATH9K_HTC_MAX_BCN_VIF]; + u16 beacon_interval; + u16 dtim_period; + u16 bmiss_timeout; + u32 bmiss_cnt; +}; + +struct ath_btcoex { + u32 bt_priority_cnt; + unsigned long bt_priority_time; + int bt_stomp_type; /* Types of BT stomping */ + u32 btcoex_no_stomp; + u32 btcoex_period; + u32 btscan_no_stomp; +}; + +#ifdef CPTCFG_ATH9K_BTCOEX_SUPPORT +void ath9k_htc_init_btcoex(struct ath9k_htc_priv *priv, char *product); +void ath9k_htc_start_btcoex(struct ath9k_htc_priv *priv); +void ath9k_htc_stop_btcoex(struct ath9k_htc_priv *priv); +#else +static inline void ath9k_htc_init_btcoex(struct ath9k_htc_priv *priv, char *product) +{ +} +static inline void ath9k_htc_start_btcoex(struct ath9k_htc_priv *priv) +{ +} +static inline void ath9k_htc_stop_btcoex(struct ath9k_htc_priv *priv) +{ +} +#endif /* CPTCFG_ATH9K_BTCOEX_SUPPORT */ + +#define OP_INVALID BIT(0) +#define OP_SCANNING BIT(1) +#define OP_ENABLE_BEACON BIT(2) +#define OP_BT_PRIORITY_DETECTED BIT(3) +#define OP_BT_SCAN BIT(4) +#define OP_ANI_RUNNING BIT(5) +#define OP_TSF_RESET BIT(6) + +struct ath9k_htc_priv { + struct device *dev; + struct ieee80211_hw *hw; + struct ath_hw *ah; + struct htc_target *htc; + struct wmi *wmi; + + u16 fw_version_major; + u16 fw_version_minor; + + enum htc_endpoint_id wmi_cmd_ep; + enum htc_endpoint_id beacon_ep; + enum htc_endpoint_id cab_ep; + enum htc_endpoint_id uapsd_ep; + enum htc_endpoint_id mgmt_ep; + enum htc_endpoint_id data_be_ep; + enum htc_endpoint_id data_bk_ep; + enum htc_endpoint_id data_vi_ep; + enum htc_endpoint_id data_vo_ep; + + u8 vif_slot; + u8 mon_vif_idx; + u8 sta_slot; + u8 vif_sta_pos[ATH9K_HTC_MAX_VIF]; + u8 num_ibss_vif; + u8 num_sta_vif; + u8 num_sta_assoc_vif; + u8 num_ap_vif; + + u16 curtxpow; + u16 txpowlimit; + u16 nvifs; + u16 nstations; + bool rearm_ani; + bool reconfig_beacon; + unsigned int rxfilter; + unsigned long op_flags; + + struct ath9k_hw_cal_data caldata; + struct ieee80211_supported_band sbands[IEEE80211_NUM_BANDS]; + + spinlock_t beacon_lock; + struct htc_beacon_config cur_beacon_conf; + + struct ath9k_htc_rx rx; + struct ath9k_htc_tx tx; + + struct tasklet_struct swba_tasklet; + struct tasklet_struct rx_tasklet; + struct delayed_work ani_work; + struct tasklet_struct tx_failed_tasklet; + struct work_struct ps_work; + struct work_struct fatal_work; + + struct mutex htc_pm_lock; + unsigned long ps_usecount; + bool ps_enabled; + bool ps_idle; + +#ifdef CPTCFG_MAC80211_LEDS + enum led_brightness brightness; + bool led_registered; + char led_name[32]; + struct led_classdev led_cdev; + struct work_struct led_work; +#endif + + int beaconq; + int cabq; + int hwq_map[IEEE80211_NUM_ACS]; + +#ifdef CPTCFG_ATH9K_BTCOEX_SUPPORT + struct ath_btcoex btcoex; +#endif + + struct delayed_work coex_period_work; + struct delayed_work duty_cycle_work; +#ifdef CPTCFG_ATH9K_HTC_DEBUGFS + struct ath9k_debug debug; +#endif + struct mutex mutex; +}; + +static inline void ath_read_cachesize(struct ath_common *common, int *csz) +{ + common->bus_ops->read_cachesize(common, csz); +} + +void ath9k_htc_reset(struct ath9k_htc_priv *priv); + +void ath9k_htc_assign_bslot(struct ath9k_htc_priv *priv, + struct ieee80211_vif *vif); +void ath9k_htc_remove_bslot(struct ath9k_htc_priv *priv, + struct ieee80211_vif *vif); +void ath9k_htc_set_tsfadjust(struct ath9k_htc_priv *priv, + struct ieee80211_vif *vif); +void ath9k_htc_beaconq_config(struct ath9k_htc_priv *priv); +void ath9k_htc_beacon_config(struct ath9k_htc_priv *priv, + struct ieee80211_vif *vif); +void ath9k_htc_beacon_reconfig(struct ath9k_htc_priv *priv); +void ath9k_htc_swba(struct ath9k_htc_priv *priv, + struct wmi_event_swba *swba); + +void ath9k_htc_rxep(void *priv, struct sk_buff *skb, + enum htc_endpoint_id ep_id); +void ath9k_htc_txep(void *priv, struct sk_buff *skb, enum htc_endpoint_id ep_id, + bool txok); +void ath9k_htc_beaconep(void *drv_priv, struct sk_buff *skb, + enum htc_endpoint_id ep_id, bool txok); + +int ath9k_htc_update_cap_target(struct ath9k_htc_priv *priv, + u8 enable_coex); +void ath9k_htc_ani_work(struct work_struct *work); +void ath9k_htc_start_ani(struct ath9k_htc_priv *priv); +void ath9k_htc_stop_ani(struct ath9k_htc_priv *priv); + +int ath9k_tx_init(struct ath9k_htc_priv *priv); +int ath9k_htc_tx_start(struct ath9k_htc_priv *priv, + struct ieee80211_sta *sta, + struct sk_buff *skb, u8 slot, bool is_cab); +void ath9k_tx_cleanup(struct ath9k_htc_priv *priv); +bool ath9k_htc_txq_setup(struct ath9k_htc_priv *priv, int subtype); +int ath9k_htc_cabq_setup(struct ath9k_htc_priv *priv); +int get_hw_qnum(u16 queue, int *hwq_map); +int ath_htc_txq_update(struct ath9k_htc_priv *priv, int qnum, + struct ath9k_tx_queue_info *qinfo); +void ath9k_htc_check_stop_queues(struct ath9k_htc_priv *priv); +void ath9k_htc_check_wake_queues(struct ath9k_htc_priv *priv); +int ath9k_htc_tx_get_slot(struct ath9k_htc_priv *priv); +void ath9k_htc_tx_clear_slot(struct ath9k_htc_priv *priv, int slot); +void ath9k_htc_tx_drain(struct ath9k_htc_priv *priv); +void ath9k_htc_txstatus(struct ath9k_htc_priv *priv, void *wmi_event); +void ath9k_tx_failed_tasklet(unsigned long data); +void ath9k_htc_tx_cleanup_timer(unsigned long data); + +int ath9k_rx_init(struct ath9k_htc_priv *priv); +void ath9k_rx_cleanup(struct ath9k_htc_priv *priv); +void ath9k_host_rx_init(struct ath9k_htc_priv *priv); +void ath9k_rx_tasklet(unsigned long data); +u32 ath9k_htc_calcrxfilter(struct ath9k_htc_priv *priv); + +void ath9k_htc_ps_wakeup(struct ath9k_htc_priv *priv); +void ath9k_htc_ps_restore(struct ath9k_htc_priv *priv); +void ath9k_ps_work(struct work_struct *work); +bool ath9k_htc_setpower(struct ath9k_htc_priv *priv, + enum ath9k_power_mode mode); + +void ath9k_start_rfkill_poll(struct ath9k_htc_priv *priv); +void ath9k_htc_rfkill_poll_state(struct ieee80211_hw *hw); + +#ifdef CPTCFG_MAC80211_LEDS +void ath9k_init_leds(struct ath9k_htc_priv *priv); +void ath9k_deinit_leds(struct ath9k_htc_priv *priv); +void ath9k_led_work(struct work_struct *work); +#else +static inline void ath9k_init_leds(struct ath9k_htc_priv *priv) +{ +} + +static inline void ath9k_deinit_leds(struct ath9k_htc_priv *priv) +{ +} + +static inline void ath9k_led_work(struct work_struct *work) +{ +} +#endif + +int ath9k_htc_probe_device(struct htc_target *htc_handle, struct device *dev, + u16 devid, char *product, u32 drv_info); +void ath9k_htc_disconnect_device(struct htc_target *htc_handle, bool hotunplug); +#ifdef CONFIG_PM +void ath9k_htc_suspend(struct htc_target *htc_handle); +int ath9k_htc_resume(struct htc_target *htc_handle); +#endif +#ifdef CPTCFG_ATH9K_HTC_DEBUGFS +int ath9k_htc_init_debug(struct ath_hw *ah); +#else +static inline int ath9k_htc_init_debug(struct ath_hw *ah) { return 0; }; +#endif /* CPTCFG_ATH9K_HTC_DEBUGFS */ + +#endif /* HTC_H */ diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_beacon.c b/drivers/net/wireless/ath/ath9k/htc_drv_beacon.c new file mode 100644 index 0000000..f13f458 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/htc_drv_beacon.c @@ -0,0 +1,659 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "htc.h" + +#define FUDGE 2 + +void ath9k_htc_beaconq_config(struct ath9k_htc_priv *priv) +{ + struct ath_hw *ah = priv->ah; + struct ath9k_tx_queue_info qi, qi_be; + + memset(&qi, 0, sizeof(struct ath9k_tx_queue_info)); + memset(&qi_be, 0, sizeof(struct ath9k_tx_queue_info)); + + ath9k_hw_get_txq_props(ah, priv->beaconq, &qi); + + if (priv->ah->opmode == NL80211_IFTYPE_AP) { + qi.tqi_aifs = 1; + qi.tqi_cwmin = 0; + qi.tqi_cwmax = 0; + } else if (priv->ah->opmode == NL80211_IFTYPE_ADHOC) { + int qnum = priv->hwq_map[IEEE80211_AC_BE]; + + ath9k_hw_get_txq_props(ah, qnum, &qi_be); + + qi.tqi_aifs = qi_be.tqi_aifs; + + /* + * For WIFI Beacon Distribution + * Long slot time : 2x cwmin + * Short slot time : 4x cwmin + */ + if (ah->slottime == ATH9K_SLOT_TIME_20) + qi.tqi_cwmin = 2*qi_be.tqi_cwmin; + else + qi.tqi_cwmin = 4*qi_be.tqi_cwmin; + + qi.tqi_cwmax = qi_be.tqi_cwmax; + + } + + if (!ath9k_hw_set_txq_props(ah, priv->beaconq, &qi)) { + ath_err(ath9k_hw_common(ah), + "Unable to update beacon queue %u!\n", priv->beaconq); + } else { + ath9k_hw_resettxqueue(ah, priv->beaconq); + } +} + + +static void ath9k_htc_beacon_config_sta(struct ath9k_htc_priv *priv, + struct htc_beacon_config *bss_conf) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_beacon_state bs; + enum ath9k_int imask = 0; + int dtimperiod, dtimcount, sleepduration; + int cfpperiod, cfpcount, bmiss_timeout; + u32 nexttbtt = 0, intval, tsftu; + __be32 htc_imask = 0; + u64 tsf; + int num_beacons, offset, dtim_dec_count, cfp_dec_count; + int ret __attribute__ ((unused)); + u8 cmd_rsp; + + memset(&bs, 0, sizeof(bs)); + + intval = bss_conf->beacon_interval; + bmiss_timeout = (ATH_DEFAULT_BMISS_LIMIT * bss_conf->beacon_interval); + + /* + * Setup dtim and cfp parameters according to + * last beacon we received (which may be none). + */ + dtimperiod = bss_conf->dtim_period; + if (dtimperiod <= 0) /* NB: 0 if not known */ + dtimperiod = 1; + dtimcount = 1; + if (dtimcount >= dtimperiod) /* NB: sanity check */ + dtimcount = 0; + cfpperiod = 1; /* NB: no PCF support yet */ + cfpcount = 0; + + sleepduration = intval; + if (sleepduration <= 0) + sleepduration = intval; + + /* + * Pull nexttbtt forward to reflect the current + * TSF and calculate dtim+cfp state for the result. + */ + tsf = ath9k_hw_gettsf64(priv->ah); + tsftu = TSF_TO_TU(tsf>>32, tsf) + FUDGE; + + num_beacons = tsftu / intval + 1; + offset = tsftu % intval; + nexttbtt = tsftu - offset; + if (offset) + nexttbtt += intval; + + /* DTIM Beacon every dtimperiod Beacon */ + dtim_dec_count = num_beacons % dtimperiod; + /* CFP every cfpperiod DTIM Beacon */ + cfp_dec_count = (num_beacons / dtimperiod) % cfpperiod; + if (dtim_dec_count) + cfp_dec_count++; + + dtimcount -= dtim_dec_count; + if (dtimcount < 0) + dtimcount += dtimperiod; + + cfpcount -= cfp_dec_count; + if (cfpcount < 0) + cfpcount += cfpperiod; + + bs.bs_intval = intval; + bs.bs_nexttbtt = nexttbtt; + bs.bs_dtimperiod = dtimperiod*intval; + bs.bs_nextdtim = bs.bs_nexttbtt + dtimcount*intval; + bs.bs_cfpperiod = cfpperiod*bs.bs_dtimperiod; + bs.bs_cfpnext = bs.bs_nextdtim + cfpcount*bs.bs_dtimperiod; + bs.bs_cfpmaxduration = 0; + + /* + * Calculate the number of consecutive beacons to miss* before taking + * a BMISS interrupt. The configuration is specified in TU so we only + * need calculate based on the beacon interval. Note that we clamp the + * result to at most 15 beacons. + */ + if (sleepduration > intval) { + bs.bs_bmissthreshold = ATH_DEFAULT_BMISS_LIMIT / 2; + } else { + bs.bs_bmissthreshold = DIV_ROUND_UP(bmiss_timeout, intval); + if (bs.bs_bmissthreshold > 15) + bs.bs_bmissthreshold = 15; + else if (bs.bs_bmissthreshold <= 0) + bs.bs_bmissthreshold = 1; + } + + /* + * Calculate sleep duration. The configuration is given in ms. + * We ensure a multiple of the beacon period is used. Also, if the sleep + * duration is greater than the DTIM period then it makes senses + * to make it a multiple of that. + * + * XXX fixed at 100ms + */ + + bs.bs_sleepduration = roundup(IEEE80211_MS_TO_TU(100), sleepduration); + if (bs.bs_sleepduration > bs.bs_dtimperiod) + bs.bs_sleepduration = bs.bs_dtimperiod; + + /* TSF out of range threshold fixed at 1 second */ + bs.bs_tsfoor_threshold = ATH9K_TSFOOR_THRESHOLD; + + ath_dbg(common, CONFIG, "intval: %u tsf: %llu tsftu: %u\n", + intval, tsf, tsftu); + ath_dbg(common, CONFIG, + "bmiss: %u sleep: %u cfp-period: %u maxdur: %u next: %u\n", + bs.bs_bmissthreshold, bs.bs_sleepduration, + bs.bs_cfpperiod, bs.bs_cfpmaxduration, bs.bs_cfpnext); + + /* Set the computed STA beacon timers */ + + WMI_CMD(WMI_DISABLE_INTR_CMDID); + ath9k_hw_set_sta_beacon_timers(priv->ah, &bs); + imask |= ATH9K_INT_BMISS; + htc_imask = cpu_to_be32(imask); + WMI_CMD_BUF(WMI_ENABLE_INTR_CMDID, &htc_imask); +} + +static void ath9k_htc_beacon_config_ap(struct ath9k_htc_priv *priv, + struct htc_beacon_config *bss_conf) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + enum ath9k_int imask = 0; + u32 nexttbtt, intval, tsftu; + __be32 htc_imask = 0; + int ret __attribute__ ((unused)); + u8 cmd_rsp; + u64 tsf; + + intval = bss_conf->beacon_interval; + intval /= ATH9K_HTC_MAX_BCN_VIF; + nexttbtt = intval; + + /* + * To reduce beacon misses under heavy TX load, + * set the beacon response time to a larger value. + */ + if (intval > DEFAULT_SWBA_RESPONSE) + priv->ah->config.sw_beacon_response_time = DEFAULT_SWBA_RESPONSE; + else + priv->ah->config.sw_beacon_response_time = MIN_SWBA_RESPONSE; + + if (test_bit(OP_TSF_RESET, &priv->op_flags)) { + ath9k_hw_reset_tsf(priv->ah); + clear_bit(OP_TSF_RESET, &priv->op_flags); + } else { + /* + * Pull nexttbtt forward to reflect the current TSF. + */ + tsf = ath9k_hw_gettsf64(priv->ah); + tsftu = TSF_TO_TU(tsf >> 32, tsf) + FUDGE; + do { + nexttbtt += intval; + } while (nexttbtt < tsftu); + } + + if (test_bit(OP_ENABLE_BEACON, &priv->op_flags)) + imask |= ATH9K_INT_SWBA; + + ath_dbg(common, CONFIG, + "AP Beacon config, intval: %d, nexttbtt: %u, resp_time: %d imask: 0x%x\n", + bss_conf->beacon_interval, nexttbtt, + priv->ah->config.sw_beacon_response_time, imask); + + ath9k_htc_beaconq_config(priv); + + WMI_CMD(WMI_DISABLE_INTR_CMDID); + ath9k_hw_beaconinit(priv->ah, TU_TO_USEC(nexttbtt), TU_TO_USEC(intval)); + priv->cur_beacon_conf.bmiss_cnt = 0; + htc_imask = cpu_to_be32(imask); + WMI_CMD_BUF(WMI_ENABLE_INTR_CMDID, &htc_imask); +} + +static void ath9k_htc_beacon_config_adhoc(struct ath9k_htc_priv *priv, + struct htc_beacon_config *bss_conf) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + enum ath9k_int imask = 0; + u32 nexttbtt, intval, tsftu; + __be32 htc_imask = 0; + int ret __attribute__ ((unused)); + u8 cmd_rsp; + u64 tsf; + + intval = bss_conf->beacon_interval; + nexttbtt = intval; + + /* + * Pull nexttbtt forward to reflect the current TSF. + */ + tsf = ath9k_hw_gettsf64(priv->ah); + tsftu = TSF_TO_TU(tsf >> 32, tsf) + FUDGE; + do { + nexttbtt += intval; + } while (nexttbtt < tsftu); + + /* + * Only one IBSS interfce is allowed. + */ + if (intval > DEFAULT_SWBA_RESPONSE) + priv->ah->config.sw_beacon_response_time = DEFAULT_SWBA_RESPONSE; + else + priv->ah->config.sw_beacon_response_time = MIN_SWBA_RESPONSE; + + if (test_bit(OP_ENABLE_BEACON, &priv->op_flags)) + imask |= ATH9K_INT_SWBA; + + ath_dbg(common, CONFIG, + "IBSS Beacon config, intval: %d, nexttbtt: %u, resp_time: %d, imask: 0x%x\n", + bss_conf->beacon_interval, nexttbtt, + priv->ah->config.sw_beacon_response_time, imask); + + WMI_CMD(WMI_DISABLE_INTR_CMDID); + ath9k_hw_beaconinit(priv->ah, TU_TO_USEC(nexttbtt), TU_TO_USEC(intval)); + priv->cur_beacon_conf.bmiss_cnt = 0; + htc_imask = cpu_to_be32(imask); + WMI_CMD_BUF(WMI_ENABLE_INTR_CMDID, &htc_imask); +} + +void ath9k_htc_beaconep(void *drv_priv, struct sk_buff *skb, + enum htc_endpoint_id ep_id, bool txok) +{ + dev_kfree_skb_any(skb); +} + +static void ath9k_htc_send_buffered(struct ath9k_htc_priv *priv, + int slot) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ieee80211_vif *vif; + struct sk_buff *skb; + struct ieee80211_hdr *hdr; + int padpos, padsize, ret, tx_slot; + + spin_lock_bh(&priv->beacon_lock); + + vif = priv->cur_beacon_conf.bslot[slot]; + + skb = ieee80211_get_buffered_bc(priv->hw, vif); + + while(skb) { + hdr = (struct ieee80211_hdr *) skb->data; + + padpos = ieee80211_hdrlen(hdr->frame_control); + padsize = padpos & 3; + if (padsize && skb->len > padpos) { + if (skb_headroom(skb) < padsize) { + dev_kfree_skb_any(skb); + goto next; + } + skb_push(skb, padsize); + memmove(skb->data, skb->data + padsize, padpos); + } + + tx_slot = ath9k_htc_tx_get_slot(priv); + if (tx_slot < 0) { + ath_dbg(common, XMIT, "No free CAB slot\n"); + dev_kfree_skb_any(skb); + goto next; + } + + ret = ath9k_htc_tx_start(priv, NULL, skb, tx_slot, true); + if (ret != 0) { + ath9k_htc_tx_clear_slot(priv, tx_slot); + dev_kfree_skb_any(skb); + + ath_dbg(common, XMIT, "Failed to send CAB frame\n"); + } else { + spin_lock_bh(&priv->tx.tx_lock); + priv->tx.queued_cnt++; + spin_unlock_bh(&priv->tx.tx_lock); + } + next: + skb = ieee80211_get_buffered_bc(priv->hw, vif); + } + + spin_unlock_bh(&priv->beacon_lock); +} + +static void ath9k_htc_send_beacon(struct ath9k_htc_priv *priv, + int slot) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ieee80211_vif *vif; + struct ath9k_htc_vif *avp; + struct tx_beacon_header beacon_hdr; + struct ath9k_htc_tx_ctl *tx_ctl; + struct ieee80211_tx_info *info; + struct ieee80211_mgmt *mgmt; + struct sk_buff *beacon; + u8 *tx_fhdr; + int ret; + + memset(&beacon_hdr, 0, sizeof(struct tx_beacon_header)); + + spin_lock_bh(&priv->beacon_lock); + + vif = priv->cur_beacon_conf.bslot[slot]; + avp = (struct ath9k_htc_vif *)vif->drv_priv; + + if (unlikely(test_bit(OP_SCANNING, &priv->op_flags))) { + spin_unlock_bh(&priv->beacon_lock); + return; + } + + /* Get a new beacon */ + beacon = ieee80211_beacon_get(priv->hw, vif); + if (!beacon) { + spin_unlock_bh(&priv->beacon_lock); + return; + } + + /* + * Update the TSF adjust value here, the HW will + * add this value for every beacon. + */ + mgmt = (struct ieee80211_mgmt *)beacon->data; + mgmt->u.beacon.timestamp = avp->tsfadjust; + + info = IEEE80211_SKB_CB(beacon); + if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) { + struct ieee80211_hdr *hdr = + (struct ieee80211_hdr *) beacon->data; + avp->seq_no += 0x10; + hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); + hdr->seq_ctrl |= cpu_to_le16(avp->seq_no); + } + + tx_ctl = HTC_SKB_CB(beacon); + memset(tx_ctl, 0, sizeof(*tx_ctl)); + + tx_ctl->type = ATH9K_HTC_BEACON; + tx_ctl->epid = priv->beacon_ep; + + beacon_hdr.vif_index = avp->index; + tx_fhdr = skb_push(beacon, sizeof(beacon_hdr)); + memcpy(tx_fhdr, (u8 *) &beacon_hdr, sizeof(beacon_hdr)); + + ret = htc_send(priv->htc, beacon); + if (ret != 0) { + if (ret == -ENOMEM) { + ath_dbg(common, BSTUCK, + "Failed to send beacon, no free TX buffer\n"); + } + dev_kfree_skb_any(beacon); + } + + spin_unlock_bh(&priv->beacon_lock); +} + +static int ath9k_htc_choose_bslot(struct ath9k_htc_priv *priv, + struct wmi_event_swba *swba) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + u64 tsf; + u32 tsftu; + u16 intval; + int slot; + + intval = priv->cur_beacon_conf.beacon_interval; + + tsf = be64_to_cpu(swba->tsf); + tsftu = TSF_TO_TU(tsf >> 32, tsf); + slot = ((tsftu % intval) * ATH9K_HTC_MAX_BCN_VIF) / intval; + slot = ATH9K_HTC_MAX_BCN_VIF - slot - 1; + + ath_dbg(common, BEACON, + "Choose slot: %d, tsf: %llu, tsftu: %u, intval: %u\n", + slot, tsf, tsftu, intval); + + return slot; +} + +void ath9k_htc_swba(struct ath9k_htc_priv *priv, + struct wmi_event_swba *swba) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + int slot; + + if (swba->beacon_pending != 0) { + priv->cur_beacon_conf.bmiss_cnt++; + if (priv->cur_beacon_conf.bmiss_cnt > BSTUCK_THRESHOLD) { + ath_dbg(common, BSTUCK, "Beacon stuck, HW reset\n"); + ieee80211_queue_work(priv->hw, + &priv->fatal_work); + } + return; + } + + if (priv->cur_beacon_conf.bmiss_cnt) { + ath_dbg(common, BSTUCK, + "Resuming beacon xmit after %u misses\n", + priv->cur_beacon_conf.bmiss_cnt); + priv->cur_beacon_conf.bmiss_cnt = 0; + } + + slot = ath9k_htc_choose_bslot(priv, swba); + spin_lock_bh(&priv->beacon_lock); + if (priv->cur_beacon_conf.bslot[slot] == NULL) { + spin_unlock_bh(&priv->beacon_lock); + return; + } + spin_unlock_bh(&priv->beacon_lock); + + ath9k_htc_send_buffered(priv, slot); + ath9k_htc_send_beacon(priv, slot); +} + +void ath9k_htc_assign_bslot(struct ath9k_htc_priv *priv, + struct ieee80211_vif *vif) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_vif *avp = (struct ath9k_htc_vif *)vif->drv_priv; + int i = 0; + + spin_lock_bh(&priv->beacon_lock); + for (i = 0; i < ATH9K_HTC_MAX_BCN_VIF; i++) { + if (priv->cur_beacon_conf.bslot[i] == NULL) { + avp->bslot = i; + break; + } + } + + priv->cur_beacon_conf.bslot[avp->bslot] = vif; + spin_unlock_bh(&priv->beacon_lock); + + ath_dbg(common, CONFIG, "Added interface at beacon slot: %d\n", + avp->bslot); +} + +void ath9k_htc_remove_bslot(struct ath9k_htc_priv *priv, + struct ieee80211_vif *vif) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_vif *avp = (struct ath9k_htc_vif *)vif->drv_priv; + + spin_lock_bh(&priv->beacon_lock); + priv->cur_beacon_conf.bslot[avp->bslot] = NULL; + spin_unlock_bh(&priv->beacon_lock); + + ath_dbg(common, CONFIG, "Removed interface at beacon slot: %d\n", + avp->bslot); +} + +/* + * Calculate the TSF adjustment value for all slots + * other than zero. + */ +void ath9k_htc_set_tsfadjust(struct ath9k_htc_priv *priv, + struct ieee80211_vif *vif) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_vif *avp = (struct ath9k_htc_vif *)vif->drv_priv; + struct htc_beacon_config *cur_conf = &priv->cur_beacon_conf; + u64 tsfadjust; + + if (avp->bslot == 0) + return; + + /* + * The beacon interval cannot be different for multi-AP mode, + * and we reach here only for VIF slots greater than zero, + * so beacon_interval is guaranteed to be set in cur_conf. + */ + tsfadjust = cur_conf->beacon_interval * avp->bslot / ATH9K_HTC_MAX_BCN_VIF; + avp->tsfadjust = cpu_to_le64(TU_TO_USEC(tsfadjust)); + + ath_dbg(common, CONFIG, "tsfadjust is: %llu for bslot: %d\n", + (unsigned long long)tsfadjust, avp->bslot); +} + +static void ath9k_htc_beacon_iter(void *data, u8 *mac, struct ieee80211_vif *vif) +{ + bool *beacon_configured = (bool *)data; + struct ath9k_htc_vif *avp = (struct ath9k_htc_vif *) vif->drv_priv; + + if (vif->type == NL80211_IFTYPE_STATION && + avp->beacon_configured) + *beacon_configured = true; +} + +static bool ath9k_htc_check_beacon_config(struct ath9k_htc_priv *priv, + struct ieee80211_vif *vif) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct htc_beacon_config *cur_conf = &priv->cur_beacon_conf; + struct ieee80211_bss_conf *bss_conf = &vif->bss_conf; + bool beacon_configured; + + /* + * Changing the beacon interval when multiple AP interfaces + * are configured will affect beacon transmission of all + * of them. + */ + if ((priv->ah->opmode == NL80211_IFTYPE_AP) && + (priv->num_ap_vif > 1) && + (vif->type == NL80211_IFTYPE_AP) && + (cur_conf->beacon_interval != bss_conf->beacon_int)) { + ath_dbg(common, CONFIG, + "Changing beacon interval of multiple AP interfaces !\n"); + return false; + } + + /* + * If the HW is operating in AP mode, any new station interfaces that + * are added cannot change the beacon parameters. + */ + if (priv->num_ap_vif && + (vif->type != NL80211_IFTYPE_AP)) { + ath_dbg(common, CONFIG, + "HW in AP mode, cannot set STA beacon parameters\n"); + return false; + } + + /* + * The beacon parameters are configured only for the first + * station interface. + */ + if ((priv->ah->opmode == NL80211_IFTYPE_STATION) && + (priv->num_sta_vif > 1) && + (vif->type == NL80211_IFTYPE_STATION)) { + beacon_configured = false; + ieee80211_iterate_active_interfaces_atomic( + priv->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + ath9k_htc_beacon_iter, &beacon_configured); + + if (beacon_configured) { + ath_dbg(common, CONFIG, + "Beacon already configured for a station interface\n"); + return false; + } + } + + return true; +} + +void ath9k_htc_beacon_config(struct ath9k_htc_priv *priv, + struct ieee80211_vif *vif) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct htc_beacon_config *cur_conf = &priv->cur_beacon_conf; + struct ieee80211_bss_conf *bss_conf = &vif->bss_conf; + struct ath9k_htc_vif *avp = (struct ath9k_htc_vif *) vif->drv_priv; + + if (!ath9k_htc_check_beacon_config(priv, vif)) + return; + + cur_conf->beacon_interval = bss_conf->beacon_int; + if (cur_conf->beacon_interval == 0) + cur_conf->beacon_interval = 100; + + cur_conf->dtim_period = bss_conf->dtim_period; + cur_conf->bmiss_timeout = + ATH_DEFAULT_BMISS_LIMIT * cur_conf->beacon_interval; + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + ath9k_htc_beacon_config_sta(priv, cur_conf); + avp->beacon_configured = true; + break; + case NL80211_IFTYPE_ADHOC: + ath9k_htc_beacon_config_adhoc(priv, cur_conf); + break; + case NL80211_IFTYPE_AP: + ath9k_htc_beacon_config_ap(priv, cur_conf); + break; + default: + ath_dbg(common, CONFIG, "Unsupported beaconing mode\n"); + return; + } +} + +void ath9k_htc_beacon_reconfig(struct ath9k_htc_priv *priv) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct htc_beacon_config *cur_conf = &priv->cur_beacon_conf; + + switch (priv->ah->opmode) { + case NL80211_IFTYPE_STATION: + ath9k_htc_beacon_config_sta(priv, cur_conf); + break; + case NL80211_IFTYPE_ADHOC: + ath9k_htc_beacon_config_adhoc(priv, cur_conf); + break; + case NL80211_IFTYPE_AP: + ath9k_htc_beacon_config_ap(priv, cur_conf); + break; + default: + ath_dbg(common, CONFIG, "Unsupported beaconing mode\n"); + return; + } +} diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_debug.c b/drivers/net/wireless/ath/ath9k/htc_drv_debug.c new file mode 100644 index 0000000..87110de --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/htc_drv_debug.c @@ -0,0 +1,951 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "htc.h" + +static ssize_t read_file_tgt_int_stats(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath9k_htc_priv *priv = file->private_data; + struct ath9k_htc_target_int_stats cmd_rsp; + char buf[512]; + unsigned int len = 0; + int ret = 0; + + memset(&cmd_rsp, 0, sizeof(cmd_rsp)); + + ath9k_htc_ps_wakeup(priv); + + WMI_CMD(WMI_INT_STATS_CMDID); + if (ret) { + ath9k_htc_ps_restore(priv); + return -EINVAL; + } + + ath9k_htc_ps_restore(priv); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "RX", + be32_to_cpu(cmd_rsp.rx)); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "RXORN", + be32_to_cpu(cmd_rsp.rxorn)); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "RXEOL", + be32_to_cpu(cmd_rsp.rxeol)); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "TXURN", + be32_to_cpu(cmd_rsp.txurn)); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "TXTO", + be32_to_cpu(cmd_rsp.txto)); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "CST", + be32_to_cpu(cmd_rsp.cst)); + + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_tgt_int_stats = { + .read = read_file_tgt_int_stats, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_tgt_tx_stats(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath9k_htc_priv *priv = file->private_data; + struct ath9k_htc_target_tx_stats cmd_rsp; + char buf[512]; + unsigned int len = 0; + int ret = 0; + + memset(&cmd_rsp, 0, sizeof(cmd_rsp)); + + ath9k_htc_ps_wakeup(priv); + + WMI_CMD(WMI_TX_STATS_CMDID); + if (ret) { + ath9k_htc_ps_restore(priv); + return -EINVAL; + } + + ath9k_htc_ps_restore(priv); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "Xretries", + be32_to_cpu(cmd_rsp.xretries)); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "FifoErr", + be32_to_cpu(cmd_rsp.fifoerr)); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "Filtered", + be32_to_cpu(cmd_rsp.filtered)); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "TimerExp", + be32_to_cpu(cmd_rsp.timer_exp)); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "ShortRetries", + be32_to_cpu(cmd_rsp.shortretries)); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "LongRetries", + be32_to_cpu(cmd_rsp.longretries)); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "QueueNull", + be32_to_cpu(cmd_rsp.qnull)); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "EncapFail", + be32_to_cpu(cmd_rsp.encap_fail)); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "NoBuf", + be32_to_cpu(cmd_rsp.nobuf)); + + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_tgt_tx_stats = { + .read = read_file_tgt_tx_stats, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_tgt_rx_stats(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath9k_htc_priv *priv = file->private_data; + struct ath9k_htc_target_rx_stats cmd_rsp; + char buf[512]; + unsigned int len = 0; + int ret = 0; + + memset(&cmd_rsp, 0, sizeof(cmd_rsp)); + + ath9k_htc_ps_wakeup(priv); + + WMI_CMD(WMI_RX_STATS_CMDID); + if (ret) { + ath9k_htc_ps_restore(priv); + return -EINVAL; + } + + ath9k_htc_ps_restore(priv); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "NoBuf", + be32_to_cpu(cmd_rsp.nobuf)); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "HostSend", + be32_to_cpu(cmd_rsp.host_send)); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "HostDone", + be32_to_cpu(cmd_rsp.host_done)); + + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_tgt_rx_stats = { + .read = read_file_tgt_rx_stats, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_xmit(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath9k_htc_priv *priv = file->private_data; + char buf[512]; + unsigned int len = 0; + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "Buffers queued", + priv->debug.tx_stats.buf_queued); + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "Buffers completed", + priv->debug.tx_stats.buf_completed); + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "SKBs queued", + priv->debug.tx_stats.skb_queued); + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "SKBs success", + priv->debug.tx_stats.skb_success); + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "SKBs failed", + priv->debug.tx_stats.skb_failed); + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "CAB queued", + priv->debug.tx_stats.cab_queued); + + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "BE queued", + priv->debug.tx_stats.queue_stats[IEEE80211_AC_BE]); + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "BK queued", + priv->debug.tx_stats.queue_stats[IEEE80211_AC_BK]); + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "VI queued", + priv->debug.tx_stats.queue_stats[IEEE80211_AC_VI]); + len += snprintf(buf + len, sizeof(buf) - len, + "%20s : %10u\n", "VO queued", + priv->debug.tx_stats.queue_stats[IEEE80211_AC_VO]); + + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_xmit = { + .read = read_file_xmit, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +void ath9k_htc_err_stat_rx(struct ath9k_htc_priv *priv, + struct ath_htc_rx_status *rxs) +{ +#define RX_PHY_ERR_INC(c) priv->debug.rx_stats.err_phy_stats[c]++ + + if (rxs->rs_status & ATH9K_RXERR_CRC) + priv->debug.rx_stats.err_crc++; + if (rxs->rs_status & ATH9K_RXERR_DECRYPT) + priv->debug.rx_stats.err_decrypt_crc++; + if (rxs->rs_status & ATH9K_RXERR_MIC) + priv->debug.rx_stats.err_mic++; + if (rxs->rs_status & ATH9K_RX_DELIM_CRC_PRE) + priv->debug.rx_stats.err_pre_delim++; + if (rxs->rs_status & ATH9K_RX_DELIM_CRC_POST) + priv->debug.rx_stats.err_post_delim++; + if (rxs->rs_status & ATH9K_RX_DECRYPT_BUSY) + priv->debug.rx_stats.err_decrypt_busy++; + + if (rxs->rs_status & ATH9K_RXERR_PHY) { + priv->debug.rx_stats.err_phy++; + if (rxs->rs_phyerr < ATH9K_PHYERR_MAX) + RX_PHY_ERR_INC(rxs->rs_phyerr); + } + +#undef RX_PHY_ERR_INC +} + +static ssize_t read_file_recv(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ +#define PHY_ERR(s, p) \ + len += snprintf(buf + len, size - len, "%20s : %10u\n", s, \ + priv->debug.rx_stats.err_phy_stats[p]); + + struct ath9k_htc_priv *priv = file->private_data; + char *buf; + unsigned int len = 0, size = 1500; + ssize_t retval = 0; + + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + len += snprintf(buf + len, size - len, + "%20s : %10u\n", "SKBs allocated", + priv->debug.rx_stats.skb_allocated); + len += snprintf(buf + len, size - len, + "%20s : %10u\n", "SKBs completed", + priv->debug.rx_stats.skb_completed); + len += snprintf(buf + len, size - len, + "%20s : %10u\n", "SKBs Dropped", + priv->debug.rx_stats.skb_dropped); + + len += snprintf(buf + len, size - len, + "%20s : %10u\n", "CRC ERR", + priv->debug.rx_stats.err_crc); + len += snprintf(buf + len, size - len, + "%20s : %10u\n", "DECRYPT CRC ERR", + priv->debug.rx_stats.err_decrypt_crc); + len += snprintf(buf + len, size - len, + "%20s : %10u\n", "MIC ERR", + priv->debug.rx_stats.err_mic); + len += snprintf(buf + len, size - len, + "%20s : %10u\n", "PRE-DELIM CRC ERR", + priv->debug.rx_stats.err_pre_delim); + len += snprintf(buf + len, size - len, + "%20s : %10u\n", "POST-DELIM CRC ERR", + priv->debug.rx_stats.err_post_delim); + len += snprintf(buf + len, size - len, + "%20s : %10u\n", "DECRYPT BUSY ERR", + priv->debug.rx_stats.err_decrypt_busy); + len += snprintf(buf + len, size - len, + "%20s : %10u\n", "TOTAL PHY ERR", + priv->debug.rx_stats.err_phy); + + + PHY_ERR("UNDERRUN", ATH9K_PHYERR_UNDERRUN); + PHY_ERR("TIMING", ATH9K_PHYERR_TIMING); + PHY_ERR("PARITY", ATH9K_PHYERR_PARITY); + PHY_ERR("RATE", ATH9K_PHYERR_RATE); + PHY_ERR("LENGTH", ATH9K_PHYERR_LENGTH); + PHY_ERR("RADAR", ATH9K_PHYERR_RADAR); + PHY_ERR("SERVICE", ATH9K_PHYERR_SERVICE); + PHY_ERR("TOR", ATH9K_PHYERR_TOR); + PHY_ERR("OFDM-TIMING", ATH9K_PHYERR_OFDM_TIMING); + PHY_ERR("OFDM-SIGNAL-PARITY", ATH9K_PHYERR_OFDM_SIGNAL_PARITY); + PHY_ERR("OFDM-RATE", ATH9K_PHYERR_OFDM_RATE_ILLEGAL); + PHY_ERR("OFDM-LENGTH", ATH9K_PHYERR_OFDM_LENGTH_ILLEGAL); + PHY_ERR("OFDM-POWER-DROP", ATH9K_PHYERR_OFDM_POWER_DROP); + PHY_ERR("OFDM-SERVICE", ATH9K_PHYERR_OFDM_SERVICE); + PHY_ERR("OFDM-RESTART", ATH9K_PHYERR_OFDM_RESTART); + PHY_ERR("FALSE-RADAR-EXT", ATH9K_PHYERR_FALSE_RADAR_EXT); + PHY_ERR("CCK-TIMING", ATH9K_PHYERR_CCK_TIMING); + PHY_ERR("CCK-HEADER-CRC", ATH9K_PHYERR_CCK_HEADER_CRC); + PHY_ERR("CCK-RATE", ATH9K_PHYERR_CCK_RATE_ILLEGAL); + PHY_ERR("CCK-SERVICE", ATH9K_PHYERR_CCK_SERVICE); + PHY_ERR("CCK-RESTART", ATH9K_PHYERR_CCK_RESTART); + PHY_ERR("CCK-LENGTH", ATH9K_PHYERR_CCK_LENGTH_ILLEGAL); + PHY_ERR("CCK-POWER-DROP", ATH9K_PHYERR_CCK_POWER_DROP); + PHY_ERR("HT-CRC", ATH9K_PHYERR_HT_CRC_ERROR); + PHY_ERR("HT-LENGTH", ATH9K_PHYERR_HT_LENGTH_ILLEGAL); + PHY_ERR("HT-RATE", ATH9K_PHYERR_HT_RATE_ILLEGAL); + + if (len > size) + len = size; + + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return retval; + +#undef PHY_ERR +} + +static const struct file_operations fops_recv = { + .read = read_file_recv, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_slot(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath9k_htc_priv *priv = file->private_data; + char buf[512]; + unsigned int len = 0; + + spin_lock_bh(&priv->tx.tx_lock); + + len += snprintf(buf + len, sizeof(buf) - len, "TX slot bitmap : "); + + len += bitmap_scnprintf(buf + len, sizeof(buf) - len, + priv->tx.tx_slot, MAX_TX_BUF_NUM); + + len += snprintf(buf + len, sizeof(buf) - len, "\n"); + + len += snprintf(buf + len, sizeof(buf) - len, + "Used slots : %d\n", + bitmap_weight(priv->tx.tx_slot, MAX_TX_BUF_NUM)); + + spin_unlock_bh(&priv->tx.tx_lock); + + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_slot = { + .read = read_file_slot, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_queue(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath9k_htc_priv *priv = file->private_data; + char buf[512]; + unsigned int len = 0; + + len += snprintf(buf + len, sizeof(buf) - len, "%20s : %10u\n", + "Mgmt endpoint", skb_queue_len(&priv->tx.mgmt_ep_queue)); + + len += snprintf(buf + len, sizeof(buf) - len, "%20s : %10u\n", + "Cab endpoint", skb_queue_len(&priv->tx.cab_ep_queue)); + + len += snprintf(buf + len, sizeof(buf) - len, "%20s : %10u\n", + "Data BE endpoint", skb_queue_len(&priv->tx.data_be_queue)); + + len += snprintf(buf + len, sizeof(buf) - len, "%20s : %10u\n", + "Data BK endpoint", skb_queue_len(&priv->tx.data_bk_queue)); + + len += snprintf(buf + len, sizeof(buf) - len, "%20s : %10u\n", + "Data VI endpoint", skb_queue_len(&priv->tx.data_vi_queue)); + + len += snprintf(buf + len, sizeof(buf) - len, "%20s : %10u\n", + "Data VO endpoint", skb_queue_len(&priv->tx.data_vo_queue)); + + len += snprintf(buf + len, sizeof(buf) - len, "%20s : %10u\n", + "Failed queue", skb_queue_len(&priv->tx.tx_failed)); + + spin_lock_bh(&priv->tx.tx_lock); + len += snprintf(buf + len, sizeof(buf) - len, "%20s : %10u\n", + "Queued count", priv->tx.queued_cnt); + spin_unlock_bh(&priv->tx.tx_lock); + + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); + +} + +static const struct file_operations fops_queue = { + .read = read_file_queue, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_debug(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath9k_htc_priv *priv = file->private_data; + struct ath_common *common = ath9k_hw_common(priv->ah); + char buf[32]; + unsigned int len; + + len = sprintf(buf, "0x%08x\n", common->debug_mask); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t write_file_debug(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath9k_htc_priv *priv = file->private_data; + struct ath_common *common = ath9k_hw_common(priv->ah); + unsigned long mask; + char buf[32]; + ssize_t len; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + + buf[len] = '\0'; + if (strict_strtoul(buf, 0, &mask)) + return -EINVAL; + + common->debug_mask = mask; + return count; +} + +static const struct file_operations fops_debug = { + .read = read_file_debug, + .write = write_file_debug, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_file_base_eeprom(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath9k_htc_priv *priv = file->private_data; + struct ath_common *common = ath9k_hw_common(priv->ah); + struct base_eep_header *pBase = NULL; + unsigned int len = 0, size = 1500; + ssize_t retval = 0; + char *buf; + + /* + * This can be done since all the 3 EEPROM families have the + * same base header upto a certain point, and we are interested in + * the data only upto that point. + */ + + if (AR_SREV_9271(priv->ah)) + pBase = (struct base_eep_header *) + &priv->ah->eeprom.map4k.baseEepHeader; + else if (priv->ah->hw_version.usbdev == AR9280_USB) + pBase = (struct base_eep_header *) + &priv->ah->eeprom.def.baseEepHeader; + else if (priv->ah->hw_version.usbdev == AR9287_USB) + pBase = (struct base_eep_header *) + &priv->ah->eeprom.map9287.baseEepHeader; + + if (pBase == NULL) { + ath_err(common, "Unknown EEPROM type\n"); + return 0; + } + + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + len += snprintf(buf + len, size - len, + "%20s : %10d\n", "Major Version", + pBase->version >> 12); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", "Minor Version", + pBase->version & 0xFFF); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", "Checksum", + pBase->checksum); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", "Length", + pBase->length); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", "RegDomain1", + pBase->regDmn[0]); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", "RegDomain2", + pBase->regDmn[1]); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", + "TX Mask", pBase->txMask); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", + "RX Mask", pBase->rxMask); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", + "Allow 5GHz", + !!(pBase->opCapFlags & AR5416_OPFLAGS_11A)); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", + "Allow 2GHz", + !!(pBase->opCapFlags & AR5416_OPFLAGS_11G)); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", + "Disable 2GHz HT20", + !!(pBase->opCapFlags & AR5416_OPFLAGS_N_2G_HT20)); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", + "Disable 2GHz HT40", + !!(pBase->opCapFlags & AR5416_OPFLAGS_N_2G_HT40)); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", + "Disable 5Ghz HT20", + !!(pBase->opCapFlags & AR5416_OPFLAGS_N_5G_HT20)); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", + "Disable 5Ghz HT40", + !!(pBase->opCapFlags & AR5416_OPFLAGS_N_5G_HT40)); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", + "Big Endian", + !!(pBase->eepMisc & 0x01)); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", + "Cal Bin Major Ver", + (pBase->binBuildNumber >> 24) & 0xFF); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", + "Cal Bin Minor Ver", + (pBase->binBuildNumber >> 16) & 0xFF); + len += snprintf(buf + len, size - len, + "%20s : %10d\n", + "Cal Bin Build", + (pBase->binBuildNumber >> 8) & 0xFF); + + /* + * UB91 specific data. + */ + if (AR_SREV_9271(priv->ah)) { + struct base_eep_header_4k *pBase4k = + &priv->ah->eeprom.map4k.baseEepHeader; + + len += snprintf(buf + len, size - len, + "%20s : %10d\n", + "TX Gain type", + pBase4k->txGainType); + } + + /* + * UB95 specific data. + */ + if (priv->ah->hw_version.usbdev == AR9287_USB) { + struct base_eep_ar9287_header *pBase9287 = + &priv->ah->eeprom.map9287.baseEepHeader; + + len += snprintf(buf + len, size - len, + "%20s : %10ddB\n", + "Power Table Offset", + pBase9287->pwrTableOffset); + + len += snprintf(buf + len, size - len, + "%20s : %10d\n", + "OpenLoop Power Ctrl", + pBase9287->openLoopPwrCntl); + } + + len += snprintf(buf + len, size - len, "%20s : %pM\n", "MacAddress", + pBase->macAddr); + if (len > size) + len = size; + + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return retval; +} + +static const struct file_operations fops_base_eeprom = { + .read = read_file_base_eeprom, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static ssize_t read_4k_modal_eeprom(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ +#define PR_EEP(_s, _val) \ + do { \ + len += snprintf(buf + len, size - len, "%20s : %10d\n", \ + _s, (_val)); \ + } while (0) + + struct ath9k_htc_priv *priv = file->private_data; + struct modal_eep_4k_header *pModal = &priv->ah->eeprom.map4k.modalHeader; + unsigned int len = 0, size = 2048; + ssize_t retval = 0; + char *buf; + + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + PR_EEP("Chain0 Ant. Control", pModal->antCtrlChain[0]); + PR_EEP("Ant. Common Control", pModal->antCtrlCommon); + PR_EEP("Chain0 Ant. Gain", pModal->antennaGainCh[0]); + PR_EEP("Switch Settle", pModal->switchSettling); + PR_EEP("Chain0 TxRxAtten", pModal->txRxAttenCh[0]); + PR_EEP("Chain0 RxTxMargin", pModal->rxTxMarginCh[0]); + PR_EEP("ADC Desired size", pModal->adcDesiredSize); + PR_EEP("PGA Desired size", pModal->pgaDesiredSize); + PR_EEP("Chain0 xlna Gain", pModal->xlnaGainCh[0]); + PR_EEP("txEndToXpaOff", pModal->txEndToXpaOff); + PR_EEP("txEndToRxOn", pModal->txEndToRxOn); + PR_EEP("txFrameToXpaOn", pModal->txFrameToXpaOn); + PR_EEP("CCA Threshold)", pModal->thresh62); + PR_EEP("Chain0 NF Threshold", pModal->noiseFloorThreshCh[0]); + PR_EEP("xpdGain", pModal->xpdGain); + PR_EEP("External PD", pModal->xpd); + PR_EEP("Chain0 I Coefficient", pModal->iqCalICh[0]); + PR_EEP("Chain0 Q Coefficient", pModal->iqCalQCh[0]); + PR_EEP("pdGainOverlap", pModal->pdGainOverlap); + PR_EEP("O/D Bias Version", pModal->version); + PR_EEP("CCK OutputBias", pModal->ob_0); + PR_EEP("BPSK OutputBias", pModal->ob_1); + PR_EEP("QPSK OutputBias", pModal->ob_2); + PR_EEP("16QAM OutputBias", pModal->ob_3); + PR_EEP("64QAM OutputBias", pModal->ob_4); + PR_EEP("CCK Driver1_Bias", pModal->db1_0); + PR_EEP("BPSK Driver1_Bias", pModal->db1_1); + PR_EEP("QPSK Driver1_Bias", pModal->db1_2); + PR_EEP("16QAM Driver1_Bias", pModal->db1_3); + PR_EEP("64QAM Driver1_Bias", pModal->db1_4); + PR_EEP("CCK Driver2_Bias", pModal->db2_0); + PR_EEP("BPSK Driver2_Bias", pModal->db2_1); + PR_EEP("QPSK Driver2_Bias", pModal->db2_2); + PR_EEP("16QAM Driver2_Bias", pModal->db2_3); + PR_EEP("64QAM Driver2_Bias", pModal->db2_4); + PR_EEP("xPA Bias Level", pModal->xpaBiasLvl); + PR_EEP("txFrameToDataStart", pModal->txFrameToDataStart); + PR_EEP("txFrameToPaOn", pModal->txFrameToPaOn); + PR_EEP("HT40 Power Inc.", pModal->ht40PowerIncForPdadc); + PR_EEP("Chain0 bswAtten", pModal->bswAtten[0]); + PR_EEP("Chain0 bswMargin", pModal->bswMargin[0]); + PR_EEP("HT40 Switch Settle", pModal->swSettleHt40); + PR_EEP("Chain0 xatten2Db", pModal->xatten2Db[0]); + PR_EEP("Chain0 xatten2Margin", pModal->xatten2Margin[0]); + PR_EEP("Ant. Diversity ctl1", pModal->antdiv_ctl1); + PR_EEP("Ant. Diversity ctl2", pModal->antdiv_ctl2); + PR_EEP("TX Diversity", pModal->tx_diversity); + + if (len > size) + len = size; + + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return retval; + +#undef PR_EEP +} + +static ssize_t read_def_modal_eeprom(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ +#define PR_EEP(_s, _val) \ + do { \ + if (pBase->opCapFlags & AR5416_OPFLAGS_11G) { \ + pModal = &priv->ah->eeprom.def.modalHeader[1]; \ + len += snprintf(buf + len, size - len, "%20s : %8d%7s", \ + _s, (_val), "|"); \ + } \ + if (pBase->opCapFlags & AR5416_OPFLAGS_11A) { \ + pModal = &priv->ah->eeprom.def.modalHeader[0]; \ + len += snprintf(buf + len, size - len, "%9d\n", \ + (_val)); \ + } \ + } while (0) + + struct ath9k_htc_priv *priv = file->private_data; + struct base_eep_header *pBase = &priv->ah->eeprom.def.baseEepHeader; + struct modal_eep_header *pModal = NULL; + unsigned int len = 0, size = 3500; + ssize_t retval = 0; + char *buf; + + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + len += snprintf(buf + len, size - len, + "%31s %15s\n", "2G", "5G"); + len += snprintf(buf + len, size - len, + "%32s %16s\n", "====", "====\n"); + + PR_EEP("Chain0 Ant. Control", pModal->antCtrlChain[0]); + PR_EEP("Chain1 Ant. Control", pModal->antCtrlChain[1]); + PR_EEP("Chain2 Ant. Control", pModal->antCtrlChain[2]); + PR_EEP("Ant. Common Control", pModal->antCtrlCommon); + PR_EEP("Chain0 Ant. Gain", pModal->antennaGainCh[0]); + PR_EEP("Chain1 Ant. Gain", pModal->antennaGainCh[1]); + PR_EEP("Chain2 Ant. Gain", pModal->antennaGainCh[2]); + PR_EEP("Switch Settle", pModal->switchSettling); + PR_EEP("Chain0 TxRxAtten", pModal->txRxAttenCh[0]); + PR_EEP("Chain1 TxRxAtten", pModal->txRxAttenCh[1]); + PR_EEP("Chain2 TxRxAtten", pModal->txRxAttenCh[2]); + PR_EEP("Chain0 RxTxMargin", pModal->rxTxMarginCh[0]); + PR_EEP("Chain1 RxTxMargin", pModal->rxTxMarginCh[1]); + PR_EEP("Chain2 RxTxMargin", pModal->rxTxMarginCh[2]); + PR_EEP("ADC Desired size", pModal->adcDesiredSize); + PR_EEP("PGA Desired size", pModal->pgaDesiredSize); + PR_EEP("Chain0 xlna Gain", pModal->xlnaGainCh[0]); + PR_EEP("Chain1 xlna Gain", pModal->xlnaGainCh[1]); + PR_EEP("Chain2 xlna Gain", pModal->xlnaGainCh[2]); + PR_EEP("txEndToXpaOff", pModal->txEndToXpaOff); + PR_EEP("txEndToRxOn", pModal->txEndToRxOn); + PR_EEP("txFrameToXpaOn", pModal->txFrameToXpaOn); + PR_EEP("CCA Threshold)", pModal->thresh62); + PR_EEP("Chain0 NF Threshold", pModal->noiseFloorThreshCh[0]); + PR_EEP("Chain1 NF Threshold", pModal->noiseFloorThreshCh[1]); + PR_EEP("Chain2 NF Threshold", pModal->noiseFloorThreshCh[2]); + PR_EEP("xpdGain", pModal->xpdGain); + PR_EEP("External PD", pModal->xpd); + PR_EEP("Chain0 I Coefficient", pModal->iqCalICh[0]); + PR_EEP("Chain1 I Coefficient", pModal->iqCalICh[1]); + PR_EEP("Chain2 I Coefficient", pModal->iqCalICh[2]); + PR_EEP("Chain0 Q Coefficient", pModal->iqCalQCh[0]); + PR_EEP("Chain1 Q Coefficient", pModal->iqCalQCh[1]); + PR_EEP("Chain2 Q Coefficient", pModal->iqCalQCh[2]); + PR_EEP("pdGainOverlap", pModal->pdGainOverlap); + PR_EEP("Chain0 OutputBias", pModal->ob); + PR_EEP("Chain0 DriverBias", pModal->db); + PR_EEP("xPA Bias Level", pModal->xpaBiasLvl); + PR_EEP("2chain pwr decrease", pModal->pwrDecreaseFor2Chain); + PR_EEP("3chain pwr decrease", pModal->pwrDecreaseFor3Chain); + PR_EEP("txFrameToDataStart", pModal->txFrameToDataStart); + PR_EEP("txFrameToPaOn", pModal->txFrameToPaOn); + PR_EEP("HT40 Power Inc.", pModal->ht40PowerIncForPdadc); + PR_EEP("Chain0 bswAtten", pModal->bswAtten[0]); + PR_EEP("Chain1 bswAtten", pModal->bswAtten[1]); + PR_EEP("Chain2 bswAtten", pModal->bswAtten[2]); + PR_EEP("Chain0 bswMargin", pModal->bswMargin[0]); + PR_EEP("Chain1 bswMargin", pModal->bswMargin[1]); + PR_EEP("Chain2 bswMargin", pModal->bswMargin[2]); + PR_EEP("HT40 Switch Settle", pModal->swSettleHt40); + PR_EEP("Chain0 xatten2Db", pModal->xatten2Db[0]); + PR_EEP("Chain1 xatten2Db", pModal->xatten2Db[1]); + PR_EEP("Chain2 xatten2Db", pModal->xatten2Db[2]); + PR_EEP("Chain0 xatten2Margin", pModal->xatten2Margin[0]); + PR_EEP("Chain1 xatten2Margin", pModal->xatten2Margin[1]); + PR_EEP("Chain2 xatten2Margin", pModal->xatten2Margin[2]); + PR_EEP("Chain1 OutputBias", pModal->ob_ch1); + PR_EEP("Chain1 DriverBias", pModal->db_ch1); + PR_EEP("LNA Control", pModal->lna_ctl); + PR_EEP("XPA Bias Freq0", pModal->xpaBiasLvlFreq[0]); + PR_EEP("XPA Bias Freq1", pModal->xpaBiasLvlFreq[1]); + PR_EEP("XPA Bias Freq2", pModal->xpaBiasLvlFreq[2]); + + if (len > size) + len = size; + + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return retval; + +#undef PR_EEP +} + +static ssize_t read_9287_modal_eeprom(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ +#define PR_EEP(_s, _val) \ + do { \ + len += snprintf(buf + len, size - len, "%20s : %10d\n", \ + _s, (_val)); \ + } while (0) + + struct ath9k_htc_priv *priv = file->private_data; + struct modal_eep_ar9287_header *pModal = &priv->ah->eeprom.map9287.modalHeader; + unsigned int len = 0, size = 3000; + ssize_t retval = 0; + char *buf; + + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + PR_EEP("Chain0 Ant. Control", pModal->antCtrlChain[0]); + PR_EEP("Chain1 Ant. Control", pModal->antCtrlChain[1]); + PR_EEP("Ant. Common Control", pModal->antCtrlCommon); + PR_EEP("Chain0 Ant. Gain", pModal->antennaGainCh[0]); + PR_EEP("Chain1 Ant. Gain", pModal->antennaGainCh[1]); + PR_EEP("Switch Settle", pModal->switchSettling); + PR_EEP("Chain0 TxRxAtten", pModal->txRxAttenCh[0]); + PR_EEP("Chain1 TxRxAtten", pModal->txRxAttenCh[1]); + PR_EEP("Chain0 RxTxMargin", pModal->rxTxMarginCh[0]); + PR_EEP("Chain1 RxTxMargin", pModal->rxTxMarginCh[1]); + PR_EEP("ADC Desired size", pModal->adcDesiredSize); + PR_EEP("txEndToXpaOff", pModal->txEndToXpaOff); + PR_EEP("txEndToRxOn", pModal->txEndToRxOn); + PR_EEP("txFrameToXpaOn", pModal->txFrameToXpaOn); + PR_EEP("CCA Threshold)", pModal->thresh62); + PR_EEP("Chain0 NF Threshold", pModal->noiseFloorThreshCh[0]); + PR_EEP("Chain1 NF Threshold", pModal->noiseFloorThreshCh[1]); + PR_EEP("xpdGain", pModal->xpdGain); + PR_EEP("External PD", pModal->xpd); + PR_EEP("Chain0 I Coefficient", pModal->iqCalICh[0]); + PR_EEP("Chain1 I Coefficient", pModal->iqCalICh[1]); + PR_EEP("Chain0 Q Coefficient", pModal->iqCalQCh[0]); + PR_EEP("Chain1 Q Coefficient", pModal->iqCalQCh[1]); + PR_EEP("pdGainOverlap", pModal->pdGainOverlap); + PR_EEP("xPA Bias Level", pModal->xpaBiasLvl); + PR_EEP("txFrameToDataStart", pModal->txFrameToDataStart); + PR_EEP("txFrameToPaOn", pModal->txFrameToPaOn); + PR_EEP("HT40 Power Inc.", pModal->ht40PowerIncForPdadc); + PR_EEP("Chain0 bswAtten", pModal->bswAtten[0]); + PR_EEP("Chain1 bswAtten", pModal->bswAtten[1]); + PR_EEP("Chain0 bswMargin", pModal->bswMargin[0]); + PR_EEP("Chain1 bswMargin", pModal->bswMargin[1]); + PR_EEP("HT40 Switch Settle", pModal->swSettleHt40); + PR_EEP("AR92x7 Version", pModal->version); + PR_EEP("DriverBias1", pModal->db1); + PR_EEP("DriverBias2", pModal->db1); + PR_EEP("CCK OutputBias", pModal->ob_cck); + PR_EEP("PSK OutputBias", pModal->ob_psk); + PR_EEP("QAM OutputBias", pModal->ob_qam); + PR_EEP("PAL_OFF OutputBias", pModal->ob_pal_off); + + if (len > size) + len = size; + + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + + return retval; + +#undef PR_EEP +} + +static ssize_t read_file_modal_eeprom(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath9k_htc_priv *priv = file->private_data; + + if (AR_SREV_9271(priv->ah)) + return read_4k_modal_eeprom(file, user_buf, count, ppos); + else if (priv->ah->hw_version.usbdev == AR9280_USB) + return read_def_modal_eeprom(file, user_buf, count, ppos); + else if (priv->ah->hw_version.usbdev == AR9287_USB) + return read_9287_modal_eeprom(file, user_buf, count, ppos); + + return 0; +} + +static const struct file_operations fops_modal_eeprom = { + .read = read_file_modal_eeprom, + .open = simple_open, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +int ath9k_htc_init_debug(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) common->priv; + + priv->debug.debugfs_phy = debugfs_create_dir(KBUILD_MODNAME, + priv->hw->wiphy->debugfsdir); + if (!priv->debug.debugfs_phy) + return -ENOMEM; + + debugfs_create_file("tgt_int_stats", S_IRUSR, priv->debug.debugfs_phy, + priv, &fops_tgt_int_stats); + debugfs_create_file("tgt_tx_stats", S_IRUSR, priv->debug.debugfs_phy, + priv, &fops_tgt_tx_stats); + debugfs_create_file("tgt_rx_stats", S_IRUSR, priv->debug.debugfs_phy, + priv, &fops_tgt_rx_stats); + debugfs_create_file("xmit", S_IRUSR, priv->debug.debugfs_phy, + priv, &fops_xmit); + debugfs_create_file("recv", S_IRUSR, priv->debug.debugfs_phy, + priv, &fops_recv); + debugfs_create_file("slot", S_IRUSR, priv->debug.debugfs_phy, + priv, &fops_slot); + debugfs_create_file("queue", S_IRUSR, priv->debug.debugfs_phy, + priv, &fops_queue); + debugfs_create_file("debug", S_IRUSR | S_IWUSR, priv->debug.debugfs_phy, + priv, &fops_debug); + debugfs_create_file("base_eeprom", S_IRUSR, priv->debug.debugfs_phy, + priv, &fops_base_eeprom); + debugfs_create_file("modal_eeprom", S_IRUSR, priv->debug.debugfs_phy, + priv, &fops_modal_eeprom); + + return 0; +} diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_gpio.c b/drivers/net/wireless/ath/ath9k/htc_drv_gpio.c new file mode 100644 index 0000000..6f9df5e --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/htc_drv_gpio.c @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "htc.h" + +/******************/ +/* BTCOEX */ +/******************/ + +#define ATH_HTC_BTCOEX_PRODUCT_ID "wb193" + +#ifdef CPTCFG_ATH9K_BTCOEX_SUPPORT + +/* + * Detects if there is any priority bt traffic + */ +static void ath_detect_bt_priority(struct ath9k_htc_priv *priv) +{ + struct ath_btcoex *btcoex = &priv->btcoex; + struct ath_hw *ah = priv->ah; + + if (ath9k_hw_gpio_get(ah, ah->btcoex_hw.btpriority_gpio)) + btcoex->bt_priority_cnt++; + + if (time_after(jiffies, btcoex->bt_priority_time + + msecs_to_jiffies(ATH_BT_PRIORITY_TIME_THRESHOLD))) { + clear_bit(OP_BT_PRIORITY_DETECTED, &priv->op_flags); + clear_bit(OP_BT_SCAN, &priv->op_flags); + /* Detect if colocated bt started scanning */ + if (btcoex->bt_priority_cnt >= ATH_BT_CNT_SCAN_THRESHOLD) { + ath_dbg(ath9k_hw_common(ah), BTCOEX, + "BT scan detected\n"); + set_bit(OP_BT_PRIORITY_DETECTED, &priv->op_flags); + set_bit(OP_BT_SCAN, &priv->op_flags); + } else if (btcoex->bt_priority_cnt >= ATH_BT_CNT_THRESHOLD) { + ath_dbg(ath9k_hw_common(ah), BTCOEX, + "BT priority traffic detected\n"); + set_bit(OP_BT_PRIORITY_DETECTED, &priv->op_flags); + } + + btcoex->bt_priority_cnt = 0; + btcoex->bt_priority_time = jiffies; + } +} + +/* + * This is the master bt coex work which runs for every + * 45ms, bt traffic will be given priority during 55% of this + * period while wlan gets remaining 45% + */ +static void ath_btcoex_period_work(struct work_struct *work) +{ + struct ath9k_htc_priv *priv = container_of(work, struct ath9k_htc_priv, + coex_period_work.work); + struct ath_btcoex *btcoex = &priv->btcoex; + struct ath_common *common = ath9k_hw_common(priv->ah); + u32 timer_period; + int ret; + + ath_detect_bt_priority(priv); + + ret = ath9k_htc_update_cap_target(priv, + test_bit(OP_BT_PRIORITY_DETECTED, &priv->op_flags)); + if (ret) { + ath_err(common, "Unable to set BTCOEX parameters\n"); + return; + } + + ath9k_hw_btcoex_bt_stomp(priv->ah, test_bit(OP_BT_SCAN, &priv->op_flags) ? + ATH_BTCOEX_STOMP_ALL : btcoex->bt_stomp_type); + + ath9k_hw_btcoex_enable(priv->ah); + timer_period = test_bit(OP_BT_SCAN, &priv->op_flags) ? + btcoex->btscan_no_stomp : btcoex->btcoex_no_stomp; + ieee80211_queue_delayed_work(priv->hw, &priv->duty_cycle_work, + msecs_to_jiffies(timer_period)); + ieee80211_queue_delayed_work(priv->hw, &priv->coex_period_work, + msecs_to_jiffies(btcoex->btcoex_period)); +} + +/* + * Work to time slice between wlan and bt traffic and + * configure weight registers + */ +static void ath_btcoex_duty_cycle_work(struct work_struct *work) +{ + struct ath9k_htc_priv *priv = container_of(work, struct ath9k_htc_priv, + duty_cycle_work.work); + struct ath_hw *ah = priv->ah; + struct ath_btcoex *btcoex = &priv->btcoex; + struct ath_common *common = ath9k_hw_common(ah); + + ath_dbg(common, BTCOEX, "time slice work for bt and wlan\n"); + + if (btcoex->bt_stomp_type == ATH_BTCOEX_STOMP_LOW || + test_bit(OP_BT_SCAN, &priv->op_flags)) + ath9k_hw_btcoex_bt_stomp(ah, ATH_BTCOEX_STOMP_NONE); + else if (btcoex->bt_stomp_type == ATH_BTCOEX_STOMP_ALL) + ath9k_hw_btcoex_bt_stomp(ah, ATH_BTCOEX_STOMP_LOW); + + ath9k_hw_btcoex_enable(priv->ah); +} + +static void ath_htc_init_btcoex_work(struct ath9k_htc_priv *priv) +{ + struct ath_btcoex *btcoex = &priv->btcoex; + + btcoex->btcoex_period = ATH_BTCOEX_DEF_BT_PERIOD; + btcoex->btcoex_no_stomp = (100 - ATH_BTCOEX_DEF_DUTY_CYCLE) * + btcoex->btcoex_period / 100; + btcoex->btscan_no_stomp = (100 - ATH_BTCOEX_BTSCAN_DUTY_CYCLE) * + btcoex->btcoex_period / 100; + INIT_DELAYED_WORK(&priv->coex_period_work, ath_btcoex_period_work); + INIT_DELAYED_WORK(&priv->duty_cycle_work, ath_btcoex_duty_cycle_work); +} + +/* + * (Re)start btcoex work + */ + +static void ath_htc_resume_btcoex_work(struct ath9k_htc_priv *priv) +{ + struct ath_btcoex *btcoex = &priv->btcoex; + struct ath_hw *ah = priv->ah; + + ath_dbg(ath9k_hw_common(ah), BTCOEX, "Starting btcoex work\n"); + + btcoex->bt_priority_cnt = 0; + btcoex->bt_priority_time = jiffies; + clear_bit(OP_BT_PRIORITY_DETECTED, &priv->op_flags); + clear_bit(OP_BT_SCAN, &priv->op_flags); + ieee80211_queue_delayed_work(priv->hw, &priv->coex_period_work, 0); +} + + +/* + * Cancel btcoex and bt duty cycle work. + */ +static void ath_htc_cancel_btcoex_work(struct ath9k_htc_priv *priv) +{ + cancel_delayed_work_sync(&priv->coex_period_work); + cancel_delayed_work_sync(&priv->duty_cycle_work); +} + +void ath9k_htc_start_btcoex(struct ath9k_htc_priv *priv) +{ + struct ath_hw *ah = priv->ah; + + if (ath9k_hw_get_btcoex_scheme(ah) == ATH_BTCOEX_CFG_3WIRE) { + ath9k_hw_btcoex_set_weight(ah, AR_BT_COEX_WGHT, + AR_STOMP_LOW_WLAN_WGHT, 0); + ath9k_hw_btcoex_enable(ah); + ath_htc_resume_btcoex_work(priv); + } +} + +void ath9k_htc_stop_btcoex(struct ath9k_htc_priv *priv) +{ + struct ath_hw *ah = priv->ah; + + if (ah->btcoex_hw.enabled && + ath9k_hw_get_btcoex_scheme(ah) != ATH_BTCOEX_CFG_NONE) { + if (ah->btcoex_hw.scheme == ATH_BTCOEX_CFG_3WIRE) + ath_htc_cancel_btcoex_work(priv); + ath9k_hw_btcoex_disable(ah); + } +} + +void ath9k_htc_init_btcoex(struct ath9k_htc_priv *priv, char *product) +{ + struct ath_hw *ah = priv->ah; + struct ath_common *common = ath9k_hw_common(ah); + int qnum; + + /* + * Check if BTCOEX is globally disabled. + */ + if (!common->btcoex_enabled) { + ah->btcoex_hw.scheme = ATH_BTCOEX_CFG_NONE; + return; + } + + if (product && strncmp(product, ATH_HTC_BTCOEX_PRODUCT_ID, 5) == 0) { + ah->btcoex_hw.scheme = ATH_BTCOEX_CFG_3WIRE; + } + + switch (ath9k_hw_get_btcoex_scheme(priv->ah)) { + case ATH_BTCOEX_CFG_NONE: + break; + case ATH_BTCOEX_CFG_3WIRE: + priv->ah->btcoex_hw.btactive_gpio = 7; + priv->ah->btcoex_hw.btpriority_gpio = 6; + priv->ah->btcoex_hw.wlanactive_gpio = 8; + priv->btcoex.bt_stomp_type = ATH_BTCOEX_STOMP_LOW; + ath9k_hw_btcoex_init_3wire(priv->ah); + ath_htc_init_btcoex_work(priv); + qnum = priv->hwq_map[IEEE80211_AC_BE]; + ath9k_hw_init_btcoex_hw(priv->ah, qnum); + break; + default: + WARN_ON(1); + break; + } +} + +#endif /* CPTCFG_ATH9K_BTCOEX_SUPPORT */ + +/*******/ +/* LED */ +/*******/ + +#ifdef CPTCFG_MAC80211_LEDS +void ath9k_led_work(struct work_struct *work) +{ + struct ath9k_htc_priv *priv = container_of(work, + struct ath9k_htc_priv, + led_work); + + ath9k_hw_set_gpio(priv->ah, priv->ah->led_pin, + (priv->brightness == LED_OFF)); +} + +static void ath9k_led_brightness(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct ath9k_htc_priv *priv = container_of(led_cdev, + struct ath9k_htc_priv, + led_cdev); + + /* Not locked, but it's just a tiny green light..*/ + priv->brightness = brightness; + ieee80211_queue_work(priv->hw, &priv->led_work); +} + +void ath9k_deinit_leds(struct ath9k_htc_priv *priv) +{ + if (!priv->led_registered) + return; + + ath9k_led_brightness(&priv->led_cdev, LED_OFF); + led_classdev_unregister(&priv->led_cdev); + cancel_work_sync(&priv->led_work); +} + +void ath9k_init_leds(struct ath9k_htc_priv *priv) +{ + int ret; + + if (AR_SREV_9287(priv->ah)) + priv->ah->led_pin = ATH_LED_PIN_9287; + else if (AR_SREV_9271(priv->ah)) + priv->ah->led_pin = ATH_LED_PIN_9271; + else if (AR_DEVID_7010(priv->ah)) + priv->ah->led_pin = ATH_LED_PIN_7010; + else + priv->ah->led_pin = ATH_LED_PIN_DEF; + + /* Configure gpio 1 for output */ + ath9k_hw_cfg_output(priv->ah, priv->ah->led_pin, + AR_GPIO_OUTPUT_MUX_AS_OUTPUT); + /* LED off, active low */ + ath9k_hw_set_gpio(priv->ah, priv->ah->led_pin, 1); + + snprintf(priv->led_name, sizeof(priv->led_name), + "ath9k_htc-%s", wiphy_name(priv->hw->wiphy)); + priv->led_cdev.name = priv->led_name; + priv->led_cdev.brightness_set = ath9k_led_brightness; + + ret = led_classdev_register(wiphy_dev(priv->hw->wiphy), &priv->led_cdev); + if (ret < 0) + return; + + INIT_WORK(&priv->led_work, ath9k_led_work); + priv->led_registered = true; + + return; +} +#endif + +/*******************/ +/* Rfkill */ +/*******************/ + +static bool ath_is_rfkill_set(struct ath9k_htc_priv *priv) +{ + bool is_blocked; + + ath9k_htc_ps_wakeup(priv); + is_blocked = ath9k_hw_gpio_get(priv->ah, priv->ah->rfkill_gpio) == + priv->ah->rfkill_polarity; + ath9k_htc_ps_restore(priv); + + return is_blocked; +} + +void ath9k_htc_rfkill_poll_state(struct ieee80211_hw *hw) +{ + struct ath9k_htc_priv *priv = hw->priv; + bool blocked = !!ath_is_rfkill_set(priv); + + wiphy_rfkill_set_hw_state(hw->wiphy, blocked); +} + +void ath9k_start_rfkill_poll(struct ath9k_htc_priv *priv) +{ + if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_RFSILENT) + wiphy_rfkill_start_polling(priv->hw->wiphy); +} diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_init.c b/drivers/net/wireless/ath/ath9k/htc_drv_init.c new file mode 100644 index 0000000..f1d15f6 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/htc_drv_init.c @@ -0,0 +1,1008 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "htc.h" + +MODULE_AUTHOR("Atheros Communications"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("Atheros driver 802.11n HTC based wireless devices"); + +static unsigned int ath9k_debug = ATH_DBG_DEFAULT; +module_param_named(debug, ath9k_debug, uint, 0); +MODULE_PARM_DESC(debug, "Debugging mask"); + +int htc_modparam_nohwcrypt; +module_param_named(nohwcrypt, htc_modparam_nohwcrypt, int, 0444); +MODULE_PARM_DESC(nohwcrypt, "Disable hardware encryption"); + +static int ath9k_htc_btcoex_enable; +module_param_named(btcoex_enable, ath9k_htc_btcoex_enable, int, 0444); +MODULE_PARM_DESC(btcoex_enable, "Enable wifi-BT coexistence"); + +#define CHAN2G(_freq, _idx) { \ + .center_freq = (_freq), \ + .hw_value = (_idx), \ + .max_power = 20, \ +} + +#define CHAN5G(_freq, _idx) { \ + .band = IEEE80211_BAND_5GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_idx), \ + .max_power = 20, \ +} + +static struct ieee80211_channel ath9k_2ghz_channels[] = { + CHAN2G(2412, 0), /* Channel 1 */ + CHAN2G(2417, 1), /* Channel 2 */ + CHAN2G(2422, 2), /* Channel 3 */ + CHAN2G(2427, 3), /* Channel 4 */ + CHAN2G(2432, 4), /* Channel 5 */ + CHAN2G(2437, 5), /* Channel 6 */ + CHAN2G(2442, 6), /* Channel 7 */ + CHAN2G(2447, 7), /* Channel 8 */ + CHAN2G(2452, 8), /* Channel 9 */ + CHAN2G(2457, 9), /* Channel 10 */ + CHAN2G(2462, 10), /* Channel 11 */ + CHAN2G(2467, 11), /* Channel 12 */ + CHAN2G(2472, 12), /* Channel 13 */ + CHAN2G(2484, 13), /* Channel 14 */ +}; + +static struct ieee80211_channel ath9k_5ghz_channels[] = { + /* _We_ call this UNII 1 */ + CHAN5G(5180, 14), /* Channel 36 */ + CHAN5G(5200, 15), /* Channel 40 */ + CHAN5G(5220, 16), /* Channel 44 */ + CHAN5G(5240, 17), /* Channel 48 */ + /* _We_ call this UNII 2 */ + CHAN5G(5260, 18), /* Channel 52 */ + CHAN5G(5280, 19), /* Channel 56 */ + CHAN5G(5300, 20), /* Channel 60 */ + CHAN5G(5320, 21), /* Channel 64 */ + /* _We_ call this "Middle band" */ + CHAN5G(5500, 22), /* Channel 100 */ + CHAN5G(5520, 23), /* Channel 104 */ + CHAN5G(5540, 24), /* Channel 108 */ + CHAN5G(5560, 25), /* Channel 112 */ + CHAN5G(5580, 26), /* Channel 116 */ + CHAN5G(5600, 27), /* Channel 120 */ + CHAN5G(5620, 28), /* Channel 124 */ + CHAN5G(5640, 29), /* Channel 128 */ + CHAN5G(5660, 30), /* Channel 132 */ + CHAN5G(5680, 31), /* Channel 136 */ + CHAN5G(5700, 32), /* Channel 140 */ + /* _We_ call this UNII 3 */ + CHAN5G(5745, 33), /* Channel 149 */ + CHAN5G(5765, 34), /* Channel 153 */ + CHAN5G(5785, 35), /* Channel 157 */ + CHAN5G(5805, 36), /* Channel 161 */ + CHAN5G(5825, 37), /* Channel 165 */ +}; + +/* Atheros hardware rate code addition for short premble */ +#define SHPCHECK(__hw_rate, __flags) \ + ((__flags & IEEE80211_RATE_SHORT_PREAMBLE) ? (__hw_rate | 0x04) : 0) + +#define RATE(_bitrate, _hw_rate, _flags) { \ + .bitrate = (_bitrate), \ + .flags = (_flags), \ + .hw_value = (_hw_rate), \ + .hw_value_short = (SHPCHECK(_hw_rate, _flags)) \ +} + +static struct ieee80211_rate ath9k_legacy_rates[] = { + RATE(10, 0x1b, 0), + RATE(20, 0x1a, IEEE80211_RATE_SHORT_PREAMBLE), /* shortp : 0x1e */ + RATE(55, 0x19, IEEE80211_RATE_SHORT_PREAMBLE), /* shortp: 0x1d */ + RATE(110, 0x18, IEEE80211_RATE_SHORT_PREAMBLE), /* short: 0x1c */ + RATE(60, 0x0b, 0), + RATE(90, 0x0f, 0), + RATE(120, 0x0a, 0), + RATE(180, 0x0e, 0), + RATE(240, 0x09, 0), + RATE(360, 0x0d, 0), + RATE(480, 0x08, 0), + RATE(540, 0x0c, 0), +}; + +#ifdef CPTCFG_MAC80211_LEDS +static const struct ieee80211_tpt_blink ath9k_htc_tpt_blink[] = { + { .throughput = 0 * 1024, .blink_time = 334 }, + { .throughput = 1 * 1024, .blink_time = 260 }, + { .throughput = 5 * 1024, .blink_time = 220 }, + { .throughput = 10 * 1024, .blink_time = 190 }, + { .throughput = 20 * 1024, .blink_time = 170 }, + { .throughput = 50 * 1024, .blink_time = 150 }, + { .throughput = 70 * 1024, .blink_time = 130 }, + { .throughput = 100 * 1024, .blink_time = 110 }, + { .throughput = 200 * 1024, .blink_time = 80 }, + { .throughput = 300 * 1024, .blink_time = 50 }, +}; +#endif + +static int ath9k_htc_wait_for_target(struct ath9k_htc_priv *priv) +{ + int time_left; + + if (atomic_read(&priv->htc->tgt_ready) > 0) { + atomic_dec(&priv->htc->tgt_ready); + return 0; + } + + /* Firmware can take up to 50ms to get ready, to be safe use 1 second */ + time_left = wait_for_completion_timeout(&priv->htc->target_wait, HZ); + if (!time_left) { + dev_err(priv->dev, "ath9k_htc: Target is unresponsive\n"); + return -ETIMEDOUT; + } + + atomic_dec(&priv->htc->tgt_ready); + + return 0; +} + +static void ath9k_deinit_priv(struct ath9k_htc_priv *priv) +{ + ath9k_hw_deinit(priv->ah); + kfree(priv->ah); + priv->ah = NULL; +} + +static void ath9k_deinit_device(struct ath9k_htc_priv *priv) +{ + struct ieee80211_hw *hw = priv->hw; + + wiphy_rfkill_stop_polling(hw->wiphy); + ath9k_deinit_leds(priv); + ieee80211_unregister_hw(hw); + ath9k_rx_cleanup(priv); + ath9k_tx_cleanup(priv); + ath9k_deinit_priv(priv); +} + +static inline int ath9k_htc_connect_svc(struct ath9k_htc_priv *priv, + u16 service_id, + void (*tx) (void *, + struct sk_buff *, + enum htc_endpoint_id, + bool txok), + enum htc_endpoint_id *ep_id) +{ + struct htc_service_connreq req; + + memset(&req, 0, sizeof(struct htc_service_connreq)); + + req.service_id = service_id; + req.ep_callbacks.priv = priv; + req.ep_callbacks.rx = ath9k_htc_rxep; + req.ep_callbacks.tx = tx; + + return htc_connect_service(priv->htc, &req, ep_id); +} + +static int ath9k_init_htc_services(struct ath9k_htc_priv *priv, u16 devid, + u32 drv_info) +{ + int ret; + + /* WMI CMD*/ + ret = ath9k_wmi_connect(priv->htc, priv->wmi, &priv->wmi_cmd_ep); + if (ret) + goto err; + + /* Beacon */ + ret = ath9k_htc_connect_svc(priv, WMI_BEACON_SVC, ath9k_htc_beaconep, + &priv->beacon_ep); + if (ret) + goto err; + + /* CAB */ + ret = ath9k_htc_connect_svc(priv, WMI_CAB_SVC, ath9k_htc_txep, + &priv->cab_ep); + if (ret) + goto err; + + + /* UAPSD */ + ret = ath9k_htc_connect_svc(priv, WMI_UAPSD_SVC, ath9k_htc_txep, + &priv->uapsd_ep); + if (ret) + goto err; + + /* MGMT */ + ret = ath9k_htc_connect_svc(priv, WMI_MGMT_SVC, ath9k_htc_txep, + &priv->mgmt_ep); + if (ret) + goto err; + + /* DATA BE */ + ret = ath9k_htc_connect_svc(priv, WMI_DATA_BE_SVC, ath9k_htc_txep, + &priv->data_be_ep); + if (ret) + goto err; + + /* DATA BK */ + ret = ath9k_htc_connect_svc(priv, WMI_DATA_BK_SVC, ath9k_htc_txep, + &priv->data_bk_ep); + if (ret) + goto err; + + /* DATA VI */ + ret = ath9k_htc_connect_svc(priv, WMI_DATA_VI_SVC, ath9k_htc_txep, + &priv->data_vi_ep); + if (ret) + goto err; + + /* DATA VO */ + ret = ath9k_htc_connect_svc(priv, WMI_DATA_VO_SVC, ath9k_htc_txep, + &priv->data_vo_ep); + if (ret) + goto err; + + /* + * Setup required credits before initializing HTC. + * This is a bit hacky, but, since queuing is done in + * the HIF layer, shouldn't matter much. + */ + + if (IS_AR7010_DEVICE(drv_info)) + priv->htc->credits = 45; + else + priv->htc->credits = 33; + + ret = htc_init(priv->htc); + if (ret) + goto err; + + dev_info(priv->dev, "ath9k_htc: HTC initialized with %d credits\n", + priv->htc->credits); + + return 0; + +err: + dev_err(priv->dev, "ath9k_htc: Unable to initialize HTC services\n"); + return ret; +} + +static void ath9k_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct ath9k_htc_priv *priv = hw->priv; + + ath_reg_notifier_apply(wiphy, request, + ath9k_hw_regulatory(priv->ah)); +} + +static unsigned int ath9k_regread(void *hw_priv, u32 reg_offset) +{ + struct ath_hw *ah = (struct ath_hw *) hw_priv; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) common->priv; + __be32 val, reg = cpu_to_be32(reg_offset); + int r; + + r = ath9k_wmi_cmd(priv->wmi, WMI_REG_READ_CMDID, + (u8 *) ®, sizeof(reg), + (u8 *) &val, sizeof(val), + 100); + if (unlikely(r)) { + ath_dbg(common, WMI, "REGISTER READ FAILED: (0x%04x, %d)\n", + reg_offset, r); + return -EIO; + } + + return be32_to_cpu(val); +} + +static void ath9k_multi_regread(void *hw_priv, u32 *addr, + u32 *val, u16 count) +{ + struct ath_hw *ah = (struct ath_hw *) hw_priv; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) common->priv; + __be32 tmpaddr[8]; + __be32 tmpval[8]; + int i, ret; + + for (i = 0; i < count; i++) { + tmpaddr[i] = cpu_to_be32(addr[i]); + } + + ret = ath9k_wmi_cmd(priv->wmi, WMI_REG_READ_CMDID, + (u8 *)tmpaddr , sizeof(u32) * count, + (u8 *)tmpval, sizeof(u32) * count, + 100); + if (unlikely(ret)) { + ath_dbg(common, WMI, + "Multiple REGISTER READ FAILED (count: %d)\n", count); + } + + for (i = 0; i < count; i++) { + val[i] = be32_to_cpu(tmpval[i]); + } +} + +static void ath9k_regwrite_single(void *hw_priv, u32 val, u32 reg_offset) +{ + struct ath_hw *ah = (struct ath_hw *) hw_priv; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) common->priv; + const __be32 buf[2] = { + cpu_to_be32(reg_offset), + cpu_to_be32(val), + }; + int r; + + r = ath9k_wmi_cmd(priv->wmi, WMI_REG_WRITE_CMDID, + (u8 *) &buf, sizeof(buf), + (u8 *) &val, sizeof(val), + 100); + if (unlikely(r)) { + ath_dbg(common, WMI, "REGISTER WRITE FAILED:(0x%04x, %d)\n", + reg_offset, r); + } +} + +static void ath9k_regwrite_buffer(void *hw_priv, u32 val, u32 reg_offset) +{ + struct ath_hw *ah = (struct ath_hw *) hw_priv; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) common->priv; + u32 rsp_status; + int r; + + mutex_lock(&priv->wmi->multi_write_mutex); + + /* Store the register/value */ + priv->wmi->multi_write[priv->wmi->multi_write_idx].reg = + cpu_to_be32(reg_offset); + priv->wmi->multi_write[priv->wmi->multi_write_idx].val = + cpu_to_be32(val); + + priv->wmi->multi_write_idx++; + + /* If the buffer is full, send it out. */ + if (priv->wmi->multi_write_idx == MAX_CMD_NUMBER) { + r = ath9k_wmi_cmd(priv->wmi, WMI_REG_WRITE_CMDID, + (u8 *) &priv->wmi->multi_write, + sizeof(struct register_write) * priv->wmi->multi_write_idx, + (u8 *) &rsp_status, sizeof(rsp_status), + 100); + if (unlikely(r)) { + ath_dbg(common, WMI, + "REGISTER WRITE FAILED, multi len: %d\n", + priv->wmi->multi_write_idx); + } + priv->wmi->multi_write_idx = 0; + } + + mutex_unlock(&priv->wmi->multi_write_mutex); +} + +static void ath9k_regwrite(void *hw_priv, u32 val, u32 reg_offset) +{ + struct ath_hw *ah = (struct ath_hw *) hw_priv; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) common->priv; + + if (atomic_read(&priv->wmi->mwrite_cnt)) + ath9k_regwrite_buffer(hw_priv, val, reg_offset); + else + ath9k_regwrite_single(hw_priv, val, reg_offset); +} + +static void ath9k_enable_regwrite_buffer(void *hw_priv) +{ + struct ath_hw *ah = (struct ath_hw *) hw_priv; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) common->priv; + + atomic_inc(&priv->wmi->mwrite_cnt); +} + +static void ath9k_regwrite_flush(void *hw_priv) +{ + struct ath_hw *ah = (struct ath_hw *) hw_priv; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) common->priv; + u32 rsp_status; + int r; + + atomic_dec(&priv->wmi->mwrite_cnt); + + mutex_lock(&priv->wmi->multi_write_mutex); + + if (priv->wmi->multi_write_idx) { + r = ath9k_wmi_cmd(priv->wmi, WMI_REG_WRITE_CMDID, + (u8 *) &priv->wmi->multi_write, + sizeof(struct register_write) * priv->wmi->multi_write_idx, + (u8 *) &rsp_status, sizeof(rsp_status), + 100); + if (unlikely(r)) { + ath_dbg(common, WMI, + "REGISTER WRITE FAILED, multi len: %d\n", + priv->wmi->multi_write_idx); + } + priv->wmi->multi_write_idx = 0; + } + + mutex_unlock(&priv->wmi->multi_write_mutex); +} + +static u32 ath9k_reg_rmw(void *hw_priv, u32 reg_offset, u32 set, u32 clr) +{ + u32 val; + + val = ath9k_regread(hw_priv, reg_offset); + val &= ~clr; + val |= set; + ath9k_regwrite(hw_priv, val, reg_offset); + return val; +} + +static void ath_usb_read_cachesize(struct ath_common *common, int *csz) +{ + *csz = L1_CACHE_BYTES >> 2; +} + +static bool ath_usb_eeprom_read(struct ath_common *common, u32 off, u16 *data) +{ + struct ath_hw *ah = (struct ath_hw *) common->ah; + + (void)REG_READ(ah, AR5416_EEPROM_OFFSET + (off << AR5416_EEPROM_S)); + + if (!ath9k_hw_wait(ah, + AR_EEPROM_STATUS_DATA, + AR_EEPROM_STATUS_DATA_BUSY | + AR_EEPROM_STATUS_DATA_PROT_ACCESS, 0, + AH_WAIT_TIMEOUT)) + return false; + + *data = MS(REG_READ(ah, AR_EEPROM_STATUS_DATA), + AR_EEPROM_STATUS_DATA_VAL); + + return true; +} + +static const struct ath_bus_ops ath9k_usb_bus_ops = { + .ath_bus_type = ATH_USB, + .read_cachesize = ath_usb_read_cachesize, + .eeprom_read = ath_usb_eeprom_read, +}; + +static void setup_ht_cap(struct ath9k_htc_priv *priv, + struct ieee80211_sta_ht_cap *ht_info) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + u8 tx_streams, rx_streams; + int i; + + ht_info->ht_supported = true; + ht_info->cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 | + IEEE80211_HT_CAP_SM_PS | + IEEE80211_HT_CAP_SGI_40 | + IEEE80211_HT_CAP_DSSSCCK40; + + if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_SGI_20) + ht_info->cap |= IEEE80211_HT_CAP_SGI_20; + + ht_info->cap |= (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT); + + ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + ht_info->ampdu_density = IEEE80211_HT_MPDU_DENSITY_8; + + memset(&ht_info->mcs, 0, sizeof(ht_info->mcs)); + + /* ath9k_htc supports only 1 or 2 stream devices */ + tx_streams = ath9k_cmn_count_streams(priv->ah->txchainmask, 2); + rx_streams = ath9k_cmn_count_streams(priv->ah->rxchainmask, 2); + + ath_dbg(common, CONFIG, "TX streams %d, RX streams: %d\n", + tx_streams, rx_streams); + + if (tx_streams != rx_streams) { + ht_info->mcs.tx_params |= IEEE80211_HT_MCS_TX_RX_DIFF; + ht_info->mcs.tx_params |= ((tx_streams - 1) << + IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT); + } + + for (i = 0; i < rx_streams; i++) + ht_info->mcs.rx_mask[i] = 0xff; + + ht_info->mcs.tx_params |= IEEE80211_HT_MCS_TX_DEFINED; +} + +static int ath9k_init_queues(struct ath9k_htc_priv *priv) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + int i; + + for (i = 0; i < ARRAY_SIZE(priv->hwq_map); i++) + priv->hwq_map[i] = -1; + + priv->beaconq = ath9k_hw_beaconq_setup(priv->ah); + if (priv->beaconq == -1) { + ath_err(common, "Unable to setup BEACON xmit queue\n"); + goto err; + } + + priv->cabq = ath9k_htc_cabq_setup(priv); + if (priv->cabq == -1) { + ath_err(common, "Unable to setup CAB xmit queue\n"); + goto err; + } + + if (!ath9k_htc_txq_setup(priv, IEEE80211_AC_BE)) { + ath_err(common, "Unable to setup xmit queue for BE traffic\n"); + goto err; + } + + if (!ath9k_htc_txq_setup(priv, IEEE80211_AC_BK)) { + ath_err(common, "Unable to setup xmit queue for BK traffic\n"); + goto err; + } + if (!ath9k_htc_txq_setup(priv, IEEE80211_AC_VI)) { + ath_err(common, "Unable to setup xmit queue for VI traffic\n"); + goto err; + } + if (!ath9k_htc_txq_setup(priv, IEEE80211_AC_VO)) { + ath_err(common, "Unable to setup xmit queue for VO traffic\n"); + goto err; + } + + return 0; + +err: + return -EINVAL; +} + +static void ath9k_init_channels_rates(struct ath9k_htc_priv *priv) +{ + if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_2GHZ) { + priv->sbands[IEEE80211_BAND_2GHZ].channels = + ath9k_2ghz_channels; + priv->sbands[IEEE80211_BAND_2GHZ].band = IEEE80211_BAND_2GHZ; + priv->sbands[IEEE80211_BAND_2GHZ].n_channels = + ARRAY_SIZE(ath9k_2ghz_channels); + priv->sbands[IEEE80211_BAND_2GHZ].bitrates = ath9k_legacy_rates; + priv->sbands[IEEE80211_BAND_2GHZ].n_bitrates = + ARRAY_SIZE(ath9k_legacy_rates); + } + + if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_5GHZ) { + priv->sbands[IEEE80211_BAND_5GHZ].channels = ath9k_5ghz_channels; + priv->sbands[IEEE80211_BAND_5GHZ].band = IEEE80211_BAND_5GHZ; + priv->sbands[IEEE80211_BAND_5GHZ].n_channels = + ARRAY_SIZE(ath9k_5ghz_channels); + priv->sbands[IEEE80211_BAND_5GHZ].bitrates = + ath9k_legacy_rates + 4; + priv->sbands[IEEE80211_BAND_5GHZ].n_bitrates = + ARRAY_SIZE(ath9k_legacy_rates) - 4; + } +} + +static void ath9k_init_misc(struct ath9k_htc_priv *priv) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + + memcpy(common->bssidmask, ath_bcast_mac, ETH_ALEN); + + priv->ah->opmode = NL80211_IFTYPE_STATION; +} + +static int ath9k_init_priv(struct ath9k_htc_priv *priv, + u16 devid, char *product, + u32 drv_info) +{ + struct ath_hw *ah = NULL; + struct ath_common *common; + int i, ret = 0, csz = 0; + + set_bit(OP_INVALID, &priv->op_flags); + + ah = kzalloc(sizeof(struct ath_hw), GFP_KERNEL); + if (!ah) + return -ENOMEM; + + ah->hw_version.devid = devid; + ah->hw_version.usbdev = drv_info; + ah->ah_flags |= AH_USE_EEPROM; + ah->reg_ops.read = ath9k_regread; + ah->reg_ops.multi_read = ath9k_multi_regread; + ah->reg_ops.write = ath9k_regwrite; + ah->reg_ops.enable_write_buffer = ath9k_enable_regwrite_buffer; + ah->reg_ops.write_flush = ath9k_regwrite_flush; + ah->reg_ops.rmw = ath9k_reg_rmw; + priv->ah = ah; + + common = ath9k_hw_common(ah); + common->ops = &ah->reg_ops; + common->bus_ops = &ath9k_usb_bus_ops; + common->ah = ah; + common->hw = priv->hw; + common->priv = priv; + common->debug_mask = ath9k_debug; + common->btcoex_enabled = ath9k_htc_btcoex_enable == 1; + + spin_lock_init(&priv->beacon_lock); + spin_lock_init(&priv->tx.tx_lock); + mutex_init(&priv->mutex); + mutex_init(&priv->htc_pm_lock); + tasklet_init(&priv->rx_tasklet, ath9k_rx_tasklet, + (unsigned long)priv); + tasklet_init(&priv->tx_failed_tasklet, ath9k_tx_failed_tasklet, + (unsigned long)priv); + INIT_DELAYED_WORK(&priv->ani_work, ath9k_htc_ani_work); + INIT_WORK(&priv->ps_work, ath9k_ps_work); + INIT_WORK(&priv->fatal_work, ath9k_fatal_work); + setup_timer(&priv->tx.cleanup_timer, ath9k_htc_tx_cleanup_timer, + (unsigned long)priv); + + /* + * Cache line size is used to size and align various + * structures used to communicate with the hardware. + */ + ath_read_cachesize(common, &csz); + common->cachelsz = csz << 2; /* convert to bytes */ + + ret = ath9k_hw_init(ah); + if (ret) { + ath_err(common, + "Unable to initialize hardware; initialization status: %d\n", + ret); + goto err_hw; + } + + ret = ath9k_init_queues(priv); + if (ret) + goto err_queues; + + for (i = 0; i < ATH9K_HTC_MAX_BCN_VIF; i++) + priv->cur_beacon_conf.bslot[i] = NULL; + + ath9k_cmn_init_crypto(ah); + ath9k_init_channels_rates(priv); + ath9k_init_misc(priv); + ath9k_htc_init_btcoex(priv, product); + + return 0; + +err_queues: + ath9k_hw_deinit(ah); +err_hw: + + kfree(ah); + priv->ah = NULL; + + return ret; +} + +static const struct ieee80211_iface_limit if_limits[] = { + { .max = 2, .types = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_P2P_CLIENT) }, + { .max = 2, .types = BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_GO) }, +}; + +static const struct ieee80211_iface_combination if_comb = { + .limits = if_limits, + .n_limits = ARRAY_SIZE(if_limits), + .max_interfaces = 2, + .num_different_channels = 1, +}; + +static void ath9k_set_hw_capab(struct ath9k_htc_priv *priv, + struct ieee80211_hw *hw) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + + hw->flags = IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_AMPDU_AGGREGATION | + IEEE80211_HW_SPECTRUM_MGMT | + IEEE80211_HW_HAS_RATE_CONTROL | + IEEE80211_HW_RX_INCLUDES_FCS | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_PS_NULLFUNC_STACK | + IEEE80211_HW_REPORTS_TX_ACK_STATUS | + IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING; + + hw->wiphy->interface_modes = + BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_P2P_CLIENT); + + hw->wiphy->iface_combinations = &if_comb; + hw->wiphy->n_iface_combinations = 1; + + hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + + hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN | + WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; + + hw->queues = 4; + hw->channel_change_time = 5000; + hw->max_listen_interval = 1; + + hw->vif_data_size = sizeof(struct ath9k_htc_vif); + hw->sta_data_size = sizeof(struct ath9k_htc_sta); + + /* tx_frame_hdr is larger than tx_mgmt_hdr anyway */ + hw->extra_tx_headroom = sizeof(struct tx_frame_hdr) + + sizeof(struct htc_frame_hdr) + 4; + + if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_2GHZ) + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = + &priv->sbands[IEEE80211_BAND_2GHZ]; + if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_5GHZ) + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = + &priv->sbands[IEEE80211_BAND_5GHZ]; + + if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_HT) { + if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_2GHZ) + setup_ht_cap(priv, + &priv->sbands[IEEE80211_BAND_2GHZ].ht_cap); + if (priv->ah->caps.hw_caps & ATH9K_HW_CAP_5GHZ) + setup_ht_cap(priv, + &priv->sbands[IEEE80211_BAND_5GHZ].ht_cap); + } + + SET_IEEE80211_PERM_ADDR(hw, common->macaddr); +} + +static int ath9k_init_firmware_version(struct ath9k_htc_priv *priv) +{ + struct ieee80211_hw *hw = priv->hw; + struct wmi_fw_version cmd_rsp; + int ret; + + memset(&cmd_rsp, 0, sizeof(cmd_rsp)); + + WMI_CMD(WMI_GET_FW_VERSION); + if (ret) + return -EINVAL; + + priv->fw_version_major = be16_to_cpu(cmd_rsp.major); + priv->fw_version_minor = be16_to_cpu(cmd_rsp.minor); + + snprintf(hw->wiphy->fw_version, sizeof(hw->wiphy->fw_version), "%d.%d", + priv->fw_version_major, + priv->fw_version_minor); + + dev_info(priv->dev, "ath9k_htc: FW Version: %d.%d\n", + priv->fw_version_major, + priv->fw_version_minor); + + /* + * Check if the available FW matches the driver's + * required version. + */ + if (priv->fw_version_major != MAJOR_VERSION_REQ || + priv->fw_version_minor < MINOR_VERSION_REQ) { + dev_err(priv->dev, "ath9k_htc: Please upgrade to FW version %d.%d\n", + MAJOR_VERSION_REQ, MINOR_VERSION_REQ); + return -EINVAL; + } + + return 0; +} + +static int ath9k_init_device(struct ath9k_htc_priv *priv, + u16 devid, char *product, u32 drv_info) +{ + struct ieee80211_hw *hw = priv->hw; + struct ath_common *common; + struct ath_hw *ah; + int error = 0; + struct ath_regulatory *reg; + char hw_name[64]; + + /* Bring up device */ + error = ath9k_init_priv(priv, devid, product, drv_info); + if (error != 0) + goto err_init; + + ah = priv->ah; + common = ath9k_hw_common(ah); + ath9k_set_hw_capab(priv, hw); + + error = ath9k_init_firmware_version(priv); + if (error != 0) + goto err_fw; + + /* Initialize regulatory */ + error = ath_regd_init(&common->regulatory, priv->hw->wiphy, + ath9k_reg_notifier); + if (error) + goto err_regd; + + reg = &common->regulatory; + + /* Setup TX */ + error = ath9k_tx_init(priv); + if (error != 0) + goto err_tx; + + /* Setup RX */ + error = ath9k_rx_init(priv); + if (error != 0) + goto err_rx; + + ath9k_hw_disable(priv->ah); +#ifdef CPTCFG_MAC80211_LEDS + /* must be initialized before ieee80211_register_hw */ + priv->led_cdev.default_trigger = ieee80211_create_tpt_led_trigger(priv->hw, + IEEE80211_TPT_LEDTRIG_FL_RADIO, ath9k_htc_tpt_blink, + ARRAY_SIZE(ath9k_htc_tpt_blink)); +#endif + + /* Register with mac80211 */ + error = ieee80211_register_hw(hw); + if (error) + goto err_register; + + /* Handle world regulatory */ + if (!ath_is_world_regd(reg)) { + error = regulatory_hint(hw->wiphy, reg->alpha2); + if (error) + goto err_world; + } + + error = ath9k_htc_init_debug(priv->ah); + if (error) { + ath_err(common, "Unable to create debugfs files\n"); + goto err_world; + } + + ath_dbg(common, CONFIG, + "WMI:%d, BCN:%d, CAB:%d, UAPSD:%d, MGMT:%d, BE:%d, BK:%d, VI:%d, VO:%d\n", + priv->wmi_cmd_ep, + priv->beacon_ep, + priv->cab_ep, + priv->uapsd_ep, + priv->mgmt_ep, + priv->data_be_ep, + priv->data_bk_ep, + priv->data_vi_ep, + priv->data_vo_ep); + + ath9k_hw_name(priv->ah, hw_name, sizeof(hw_name)); + wiphy_info(hw->wiphy, "%s\n", hw_name); + + ath9k_init_leds(priv); + ath9k_start_rfkill_poll(priv); + + return 0; + +err_world: + ieee80211_unregister_hw(hw); +err_register: + ath9k_rx_cleanup(priv); +err_rx: + ath9k_tx_cleanup(priv); +err_tx: + /* Nothing */ +err_regd: + /* Nothing */ +err_fw: + ath9k_deinit_priv(priv); +err_init: + return error; +} + +int ath9k_htc_probe_device(struct htc_target *htc_handle, struct device *dev, + u16 devid, char *product, u32 drv_info) +{ + struct ieee80211_hw *hw; + struct ath9k_htc_priv *priv; + int ret; + + hw = ieee80211_alloc_hw(sizeof(struct ath9k_htc_priv), &ath9k_htc_ops); + if (!hw) + return -ENOMEM; + + priv = hw->priv; + priv->hw = hw; + priv->htc = htc_handle; + priv->dev = dev; + htc_handle->drv_priv = priv; + SET_IEEE80211_DEV(hw, priv->dev); + + ret = ath9k_htc_wait_for_target(priv); + if (ret) + goto err_free; + + priv->wmi = ath9k_init_wmi(priv); + if (!priv->wmi) { + ret = -EINVAL; + goto err_free; + } + + ret = ath9k_init_htc_services(priv, devid, drv_info); + if (ret) + goto err_init; + + ret = ath9k_init_device(priv, devid, product, drv_info); + if (ret) + goto err_init; + + return 0; + +err_init: + ath9k_deinit_wmi(priv); +err_free: + ieee80211_free_hw(hw); + return ret; +} + +void ath9k_htc_disconnect_device(struct htc_target *htc_handle, bool hotunplug) +{ + if (htc_handle->drv_priv) { + + /* Check if the device has been yanked out. */ + if (hotunplug) + htc_handle->drv_priv->ah->ah_flags |= AH_UNPLUGGED; + + ath9k_deinit_device(htc_handle->drv_priv); + ath9k_deinit_wmi(htc_handle->drv_priv); + ieee80211_free_hw(htc_handle->drv_priv->hw); + } +} + +#ifdef CONFIG_PM + +void ath9k_htc_suspend(struct htc_target *htc_handle) +{ + ath9k_htc_setpower(htc_handle->drv_priv, ATH9K_PM_FULL_SLEEP); +} + +int ath9k_htc_resume(struct htc_target *htc_handle) +{ + struct ath9k_htc_priv *priv = htc_handle->drv_priv; + int ret; + + ret = ath9k_htc_wait_for_target(priv); + if (ret) + return ret; + + ret = ath9k_init_htc_services(priv, priv->ah->hw_version.devid, + priv->ah->hw_version.usbdev); + return ret; +} +#endif + +static int __init ath9k_htc_init(void) +{ + if (ath9k_hif_usb_init() < 0) { + pr_err("No USB devices found, driver not installed\n"); + return -ENODEV; + } + + return 0; +} +module_init(ath9k_htc_init); + +static void __exit ath9k_htc_exit(void) +{ + ath9k_hif_usb_exit(); + pr_info("Driver unloaded\n"); +} +module_exit(ath9k_htc_exit); diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_main.c b/drivers/net/wireless/ath/ath9k/htc_drv_main.c new file mode 100644 index 0000000..9d06a93 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/htc_drv_main.c @@ -0,0 +1,1789 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "htc.h" + +/*************/ +/* Utilities */ +/*************/ + +/* HACK Alert: Use 11NG for 2.4, use 11NA for 5 */ +static enum htc_phymode ath9k_htc_get_curmode(struct ath9k_htc_priv *priv, + struct ath9k_channel *ichan) +{ + enum htc_phymode mode; + + mode = -EINVAL; + + switch (ichan->chanmode) { + case CHANNEL_G: + case CHANNEL_G_HT20: + case CHANNEL_G_HT40PLUS: + case CHANNEL_G_HT40MINUS: + mode = HTC_MODE_11NG; + break; + case CHANNEL_A: + case CHANNEL_A_HT20: + case CHANNEL_A_HT40PLUS: + case CHANNEL_A_HT40MINUS: + mode = HTC_MODE_11NA; + break; + default: + break; + } + + WARN_ON(mode < 0); + + return mode; +} + +bool ath9k_htc_setpower(struct ath9k_htc_priv *priv, + enum ath9k_power_mode mode) +{ + bool ret; + + mutex_lock(&priv->htc_pm_lock); + ret = ath9k_hw_setpower(priv->ah, mode); + mutex_unlock(&priv->htc_pm_lock); + + return ret; +} + +void ath9k_htc_ps_wakeup(struct ath9k_htc_priv *priv) +{ + mutex_lock(&priv->htc_pm_lock); + if (++priv->ps_usecount != 1) + goto unlock; + ath9k_hw_setpower(priv->ah, ATH9K_PM_AWAKE); + +unlock: + mutex_unlock(&priv->htc_pm_lock); +} + +void ath9k_htc_ps_restore(struct ath9k_htc_priv *priv) +{ + bool reset; + + mutex_lock(&priv->htc_pm_lock); + if (--priv->ps_usecount != 0) + goto unlock; + + if (priv->ps_idle) { + ath9k_hw_setrxabort(priv->ah, true); + ath9k_hw_stopdmarecv(priv->ah, &reset); + ath9k_hw_setpower(priv->ah, ATH9K_PM_FULL_SLEEP); + } else if (priv->ps_enabled) { + ath9k_hw_setpower(priv->ah, ATH9K_PM_NETWORK_SLEEP); + } + +unlock: + mutex_unlock(&priv->htc_pm_lock); +} + +void ath9k_ps_work(struct work_struct *work) +{ + struct ath9k_htc_priv *priv = + container_of(work, struct ath9k_htc_priv, + ps_work); + ath9k_htc_setpower(priv, ATH9K_PM_AWAKE); + + /* The chip wakes up after receiving the first beacon + while network sleep is enabled. For the driver to + be in sync with the hw, set the chip to awake and + only then set it to sleep. + */ + ath9k_htc_setpower(priv, ATH9K_PM_NETWORK_SLEEP); +} + +static void ath9k_htc_vif_iter(void *data, u8 *mac, struct ieee80211_vif *vif) +{ + struct ath9k_htc_priv *priv = data; + struct ieee80211_bss_conf *bss_conf = &vif->bss_conf; + + if ((vif->type == NL80211_IFTYPE_AP) && bss_conf->enable_beacon) + priv->reconfig_beacon = true; + + if (bss_conf->assoc) { + priv->rearm_ani = true; + priv->reconfig_beacon = true; + } +} + +static void ath9k_htc_vif_reconfig(struct ath9k_htc_priv *priv) +{ + priv->rearm_ani = false; + priv->reconfig_beacon = false; + + ieee80211_iterate_active_interfaces_atomic( + priv->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + ath9k_htc_vif_iter, priv); + if (priv->rearm_ani) + ath9k_htc_start_ani(priv); + + if (priv->reconfig_beacon) { + ath9k_htc_ps_wakeup(priv); + ath9k_htc_beacon_reconfig(priv); + ath9k_htc_ps_restore(priv); + } +} + +static void ath9k_htc_bssid_iter(void *data, u8 *mac, struct ieee80211_vif *vif) +{ + struct ath9k_vif_iter_data *iter_data = data; + int i; + + for (i = 0; i < ETH_ALEN; i++) + iter_data->mask[i] &= ~(iter_data->hw_macaddr[i] ^ mac[i]); +} + +static void ath9k_htc_set_bssid_mask(struct ath9k_htc_priv *priv, + struct ieee80211_vif *vif) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_vif_iter_data iter_data; + + /* + * Use the hardware MAC address as reference, the hardware uses it + * together with the BSSID mask when matching addresses. + */ + iter_data.hw_macaddr = common->macaddr; + memset(&iter_data.mask, 0xff, ETH_ALEN); + + if (vif) + ath9k_htc_bssid_iter(&iter_data, vif->addr, vif); + + /* Get list of all active MAC addresses */ + ieee80211_iterate_active_interfaces_atomic( + priv->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + ath9k_htc_bssid_iter, &iter_data); + + memcpy(common->bssidmask, iter_data.mask, ETH_ALEN); + ath_hw_setbssidmask(common); +} + +static void ath9k_htc_set_opmode(struct ath9k_htc_priv *priv) +{ + if (priv->num_ibss_vif) + priv->ah->opmode = NL80211_IFTYPE_ADHOC; + else if (priv->num_ap_vif) + priv->ah->opmode = NL80211_IFTYPE_AP; + else + priv->ah->opmode = NL80211_IFTYPE_STATION; + + ath9k_hw_setopmode(priv->ah); +} + +void ath9k_htc_reset(struct ath9k_htc_priv *priv) +{ + struct ath_hw *ah = priv->ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ieee80211_channel *channel = priv->hw->conf.chandef.chan; + struct ath9k_hw_cal_data *caldata = NULL; + enum htc_phymode mode; + __be16 htc_mode; + u8 cmd_rsp; + int ret; + + mutex_lock(&priv->mutex); + ath9k_htc_ps_wakeup(priv); + + ath9k_htc_stop_ani(priv); + ieee80211_stop_queues(priv->hw); + + del_timer_sync(&priv->tx.cleanup_timer); + ath9k_htc_tx_drain(priv); + + WMI_CMD(WMI_DISABLE_INTR_CMDID); + WMI_CMD(WMI_DRAIN_TXQ_ALL_CMDID); + WMI_CMD(WMI_STOP_RECV_CMDID); + + ath9k_wmi_event_drain(priv); + + caldata = &priv->caldata; + ret = ath9k_hw_reset(ah, ah->curchan, caldata, false); + if (ret) { + ath_err(common, + "Unable to reset device (%u Mhz) reset status %d\n", + channel->center_freq, ret); + } + + ath9k_cmn_update_txpow(ah, priv->curtxpow, priv->txpowlimit, + &priv->curtxpow); + + WMI_CMD(WMI_START_RECV_CMDID); + ath9k_host_rx_init(priv); + + mode = ath9k_htc_get_curmode(priv, ah->curchan); + htc_mode = cpu_to_be16(mode); + WMI_CMD_BUF(WMI_SET_MODE_CMDID, &htc_mode); + + WMI_CMD(WMI_ENABLE_INTR_CMDID); + htc_start(priv->htc); + ath9k_htc_vif_reconfig(priv); + ieee80211_wake_queues(priv->hw); + + mod_timer(&priv->tx.cleanup_timer, + jiffies + msecs_to_jiffies(ATH9K_HTC_TX_CLEANUP_INTERVAL)); + + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); +} + +static int ath9k_htc_set_channel(struct ath9k_htc_priv *priv, + struct ieee80211_hw *hw, + struct ath9k_channel *hchan) +{ + struct ath_hw *ah = priv->ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ieee80211_conf *conf = &common->hw->conf; + bool fastcc; + struct ieee80211_channel *channel = hw->conf.chandef.chan; + struct ath9k_hw_cal_data *caldata = NULL; + enum htc_phymode mode; + __be16 htc_mode; + u8 cmd_rsp; + int ret; + + if (test_bit(OP_INVALID, &priv->op_flags)) + return -EIO; + + fastcc = !!(hw->conf.flags & IEEE80211_CONF_OFFCHANNEL); + + ath9k_htc_ps_wakeup(priv); + + del_timer_sync(&priv->tx.cleanup_timer); + ath9k_htc_tx_drain(priv); + + WMI_CMD(WMI_DISABLE_INTR_CMDID); + WMI_CMD(WMI_DRAIN_TXQ_ALL_CMDID); + WMI_CMD(WMI_STOP_RECV_CMDID); + + ath9k_wmi_event_drain(priv); + + ath_dbg(common, CONFIG, + "(%u MHz) -> (%u MHz), HT: %d, HT40: %d fastcc: %d\n", + priv->ah->curchan->channel, + channel->center_freq, conf_is_ht(conf), conf_is_ht40(conf), + fastcc); + + if (!fastcc) + caldata = &priv->caldata; + + ret = ath9k_hw_reset(ah, hchan, caldata, fastcc); + if (ret) { + ath_err(common, + "Unable to reset channel (%u Mhz) reset status %d\n", + channel->center_freq, ret); + goto err; + } + + ath9k_cmn_update_txpow(ah, priv->curtxpow, priv->txpowlimit, + &priv->curtxpow); + + WMI_CMD(WMI_START_RECV_CMDID); + if (ret) + goto err; + + ath9k_host_rx_init(priv); + + mode = ath9k_htc_get_curmode(priv, hchan); + htc_mode = cpu_to_be16(mode); + WMI_CMD_BUF(WMI_SET_MODE_CMDID, &htc_mode); + if (ret) + goto err; + + WMI_CMD(WMI_ENABLE_INTR_CMDID); + if (ret) + goto err; + + htc_start(priv->htc); + + if (!test_bit(OP_SCANNING, &priv->op_flags) && + !(hw->conf.flags & IEEE80211_CONF_OFFCHANNEL)) + ath9k_htc_vif_reconfig(priv); + + mod_timer(&priv->tx.cleanup_timer, + jiffies + msecs_to_jiffies(ATH9K_HTC_TX_CLEANUP_INTERVAL)); + +err: + ath9k_htc_ps_restore(priv); + return ret; +} + +/* + * Monitor mode handling is a tad complicated because the firmware requires + * an interface to be created exclusively, while mac80211 doesn't associate + * an interface with the mode. + * + * So, for now, only one monitor interface can be configured. + */ +static void __ath9k_htc_remove_monitor_interface(struct ath9k_htc_priv *priv) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_target_vif hvif; + int ret = 0; + u8 cmd_rsp; + + memset(&hvif, 0, sizeof(struct ath9k_htc_target_vif)); + memcpy(&hvif.myaddr, common->macaddr, ETH_ALEN); + hvif.index = priv->mon_vif_idx; + WMI_CMD_BUF(WMI_VAP_REMOVE_CMDID, &hvif); + if (ret) { + ath_err(common, "Unable to remove monitor interface at idx: %d\n", + priv->mon_vif_idx); + } + + priv->nvifs--; + priv->vif_slot &= ~(1 << priv->mon_vif_idx); +} + +static int ath9k_htc_add_monitor_interface(struct ath9k_htc_priv *priv) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_target_vif hvif; + struct ath9k_htc_target_sta tsta; + int ret = 0, sta_idx; + u8 cmd_rsp; + + if ((priv->nvifs >= ATH9K_HTC_MAX_VIF) || + (priv->nstations >= ATH9K_HTC_MAX_STA)) { + ret = -ENOBUFS; + goto err_vif; + } + + sta_idx = ffz(priv->sta_slot); + if ((sta_idx < 0) || (sta_idx > ATH9K_HTC_MAX_STA)) { + ret = -ENOBUFS; + goto err_vif; + } + + /* + * Add an interface. + */ + memset(&hvif, 0, sizeof(struct ath9k_htc_target_vif)); + memcpy(&hvif.myaddr, common->macaddr, ETH_ALEN); + + hvif.opmode = HTC_M_MONITOR; + hvif.index = ffz(priv->vif_slot); + + WMI_CMD_BUF(WMI_VAP_CREATE_CMDID, &hvif); + if (ret) + goto err_vif; + + /* + * Assign the monitor interface index as a special case here. + * This is needed when the interface is brought down. + */ + priv->mon_vif_idx = hvif.index; + priv->vif_slot |= (1 << hvif.index); + + /* + * Set the hardware mode to monitor only if there are no + * other interfaces. + */ + if (!priv->nvifs) + priv->ah->opmode = NL80211_IFTYPE_MONITOR; + + priv->nvifs++; + + /* + * Associate a station with the interface for packet injection. + */ + memset(&tsta, 0, sizeof(struct ath9k_htc_target_sta)); + + memcpy(&tsta.macaddr, common->macaddr, ETH_ALEN); + + tsta.is_vif_sta = 1; + tsta.sta_index = sta_idx; + tsta.vif_index = hvif.index; + tsta.maxampdu = cpu_to_be16(0xffff); + + WMI_CMD_BUF(WMI_NODE_CREATE_CMDID, &tsta); + if (ret) { + ath_err(common, "Unable to add station entry for monitor mode\n"); + goto err_sta; + } + + priv->sta_slot |= (1 << sta_idx); + priv->nstations++; + priv->vif_sta_pos[priv->mon_vif_idx] = sta_idx; + priv->ah->is_monitoring = true; + + ath_dbg(common, CONFIG, + "Attached a monitor interface at idx: %d, sta idx: %d\n", + priv->mon_vif_idx, sta_idx); + + return 0; + +err_sta: + /* + * Remove the interface from the target. + */ + __ath9k_htc_remove_monitor_interface(priv); +err_vif: + ath_dbg(common, FATAL, "Unable to attach a monitor interface\n"); + + return ret; +} + +static int ath9k_htc_remove_monitor_interface(struct ath9k_htc_priv *priv) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + int ret = 0; + u8 cmd_rsp, sta_idx; + + __ath9k_htc_remove_monitor_interface(priv); + + sta_idx = priv->vif_sta_pos[priv->mon_vif_idx]; + + WMI_CMD_BUF(WMI_NODE_REMOVE_CMDID, &sta_idx); + if (ret) { + ath_err(common, "Unable to remove station entry for monitor mode\n"); + return ret; + } + + priv->sta_slot &= ~(1 << sta_idx); + priv->nstations--; + priv->ah->is_monitoring = false; + + ath_dbg(common, CONFIG, + "Removed a monitor interface at idx: %d, sta idx: %d\n", + priv->mon_vif_idx, sta_idx); + + return 0; +} + +static int ath9k_htc_add_station(struct ath9k_htc_priv *priv, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_target_sta tsta; + struct ath9k_htc_vif *avp = (struct ath9k_htc_vif *) vif->drv_priv; + struct ath9k_htc_sta *ista; + int ret, sta_idx; + u8 cmd_rsp; + u16 maxampdu; + + if (priv->nstations >= ATH9K_HTC_MAX_STA) + return -ENOBUFS; + + sta_idx = ffz(priv->sta_slot); + if ((sta_idx < 0) || (sta_idx > ATH9K_HTC_MAX_STA)) + return -ENOBUFS; + + memset(&tsta, 0, sizeof(struct ath9k_htc_target_sta)); + + if (sta) { + ista = (struct ath9k_htc_sta *) sta->drv_priv; + memcpy(&tsta.macaddr, sta->addr, ETH_ALEN); + memcpy(&tsta.bssid, common->curbssid, ETH_ALEN); + ista->index = sta_idx; + tsta.is_vif_sta = 0; + maxampdu = 1 << (IEEE80211_HT_MAX_AMPDU_FACTOR + + sta->ht_cap.ampdu_factor); + tsta.maxampdu = cpu_to_be16(maxampdu); + } else { + memcpy(&tsta.macaddr, vif->addr, ETH_ALEN); + tsta.is_vif_sta = 1; + tsta.maxampdu = cpu_to_be16(0xffff); + } + + tsta.sta_index = sta_idx; + tsta.vif_index = avp->index; + + WMI_CMD_BUF(WMI_NODE_CREATE_CMDID, &tsta); + if (ret) { + if (sta) + ath_err(common, + "Unable to add station entry for: %pM\n", + sta->addr); + return ret; + } + + if (sta) { + ath_dbg(common, CONFIG, + "Added a station entry for: %pM (idx: %d)\n", + sta->addr, tsta.sta_index); + } else { + ath_dbg(common, CONFIG, + "Added a station entry for VIF %d (idx: %d)\n", + avp->index, tsta.sta_index); + } + + priv->sta_slot |= (1 << sta_idx); + priv->nstations++; + if (!sta) + priv->vif_sta_pos[avp->index] = sta_idx; + + return 0; +} + +static int ath9k_htc_remove_station(struct ath9k_htc_priv *priv, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_vif *avp = (struct ath9k_htc_vif *) vif->drv_priv; + struct ath9k_htc_sta *ista; + int ret; + u8 cmd_rsp, sta_idx; + + if (sta) { + ista = (struct ath9k_htc_sta *) sta->drv_priv; + sta_idx = ista->index; + } else { + sta_idx = priv->vif_sta_pos[avp->index]; + } + + WMI_CMD_BUF(WMI_NODE_REMOVE_CMDID, &sta_idx); + if (ret) { + if (sta) + ath_err(common, + "Unable to remove station entry for: %pM\n", + sta->addr); + return ret; + } + + if (sta) { + ath_dbg(common, CONFIG, + "Removed a station entry for: %pM (idx: %d)\n", + sta->addr, sta_idx); + } else { + ath_dbg(common, CONFIG, + "Removed a station entry for VIF %d (idx: %d)\n", + avp->index, sta_idx); + } + + priv->sta_slot &= ~(1 << sta_idx); + priv->nstations--; + + return 0; +} + +int ath9k_htc_update_cap_target(struct ath9k_htc_priv *priv, + u8 enable_coex) +{ + struct ath9k_htc_cap_target tcap; + int ret; + u8 cmd_rsp; + + memset(&tcap, 0, sizeof(struct ath9k_htc_cap_target)); + + tcap.ampdu_limit = cpu_to_be32(0xffff); + tcap.ampdu_subframes = 0xff; + tcap.enable_coex = enable_coex; + tcap.tx_chainmask = priv->ah->caps.tx_chainmask; + + WMI_CMD_BUF(WMI_TARGET_IC_UPDATE_CMDID, &tcap); + + return ret; +} + +static void ath9k_htc_setup_rate(struct ath9k_htc_priv *priv, + struct ieee80211_sta *sta, + struct ath9k_htc_target_rate *trate) +{ + struct ath9k_htc_sta *ista = (struct ath9k_htc_sta *) sta->drv_priv; + struct ieee80211_supported_band *sband; + u32 caps = 0; + int i, j; + + sband = priv->hw->wiphy->bands[priv->hw->conf.chandef.chan->band]; + + for (i = 0, j = 0; i < sband->n_bitrates; i++) { + if (sta->supp_rates[sband->band] & BIT(i)) { + trate->rates.legacy_rates.rs_rates[j] + = (sband->bitrates[i].bitrate * 2) / 10; + j++; + } + } + trate->rates.legacy_rates.rs_nrates = j; + + if (sta->ht_cap.ht_supported) { + for (i = 0, j = 0; i < 77; i++) { + if (sta->ht_cap.mcs.rx_mask[i/8] & (1<<(i%8))) + trate->rates.ht_rates.rs_rates[j++] = i; + if (j == ATH_HTC_RATE_MAX) + break; + } + trate->rates.ht_rates.rs_nrates = j; + + caps = WLAN_RC_HT_FLAG; + if (sta->ht_cap.mcs.rx_mask[1]) + caps |= WLAN_RC_DS_FLAG; + if ((sta->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) && + (conf_is_ht40(&priv->hw->conf))) + caps |= WLAN_RC_40_FLAG; + if (conf_is_ht40(&priv->hw->conf) && + (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_40)) + caps |= WLAN_RC_SGI_FLAG; + else if (conf_is_ht20(&priv->hw->conf) && + (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_20)) + caps |= WLAN_RC_SGI_FLAG; + } + + trate->sta_index = ista->index; + trate->isnew = 1; + trate->capflags = cpu_to_be32(caps); +} + +static int ath9k_htc_send_rate_cmd(struct ath9k_htc_priv *priv, + struct ath9k_htc_target_rate *trate) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + int ret; + u8 cmd_rsp; + + WMI_CMD_BUF(WMI_RC_RATE_UPDATE_CMDID, trate); + if (ret) { + ath_err(common, + "Unable to initialize Rate information on target\n"); + } + + return ret; +} + +static void ath9k_htc_init_rate(struct ath9k_htc_priv *priv, + struct ieee80211_sta *sta) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_target_rate trate; + int ret; + + memset(&trate, 0, sizeof(struct ath9k_htc_target_rate)); + ath9k_htc_setup_rate(priv, sta, &trate); + ret = ath9k_htc_send_rate_cmd(priv, &trate); + if (!ret) + ath_dbg(common, CONFIG, + "Updated target sta: %pM, rate caps: 0x%X\n", + sta->addr, be32_to_cpu(trate.capflags)); +} + +static void ath9k_htc_update_rate(struct ath9k_htc_priv *priv, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_target_rate trate; + struct ieee80211_sta *sta; + int ret; + + memset(&trate, 0, sizeof(struct ath9k_htc_target_rate)); + + rcu_read_lock(); + sta = ieee80211_find_sta(vif, bss_conf->bssid); + if (!sta) { + rcu_read_unlock(); + return; + } + ath9k_htc_setup_rate(priv, sta, &trate); + rcu_read_unlock(); + + ret = ath9k_htc_send_rate_cmd(priv, &trate); + if (!ret) + ath_dbg(common, CONFIG, + "Updated target sta: %pM, rate caps: 0x%X\n", + bss_conf->bssid, be32_to_cpu(trate.capflags)); +} + +static int ath9k_htc_tx_aggr_oper(struct ath9k_htc_priv *priv, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + enum ieee80211_ampdu_mlme_action action, + u16 tid) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_target_aggr aggr; + struct ath9k_htc_sta *ista; + int ret = 0; + u8 cmd_rsp; + + if (tid >= ATH9K_HTC_MAX_TID) + return -EINVAL; + + memset(&aggr, 0, sizeof(struct ath9k_htc_target_aggr)); + ista = (struct ath9k_htc_sta *) sta->drv_priv; + + aggr.sta_index = ista->index; + aggr.tidno = tid & 0xf; + aggr.aggr_enable = (action == IEEE80211_AMPDU_TX_START) ? true : false; + + WMI_CMD_BUF(WMI_TX_AGGR_ENABLE_CMDID, &aggr); + if (ret) + ath_dbg(common, CONFIG, + "Unable to %s TX aggregation for (%pM, %d)\n", + (aggr.aggr_enable) ? "start" : "stop", sta->addr, tid); + else + ath_dbg(common, CONFIG, + "%s TX aggregation for (%pM, %d)\n", + (aggr.aggr_enable) ? "Starting" : "Stopping", + sta->addr, tid); + + spin_lock_bh(&priv->tx.tx_lock); + ista->tid_state[tid] = (aggr.aggr_enable && !ret) ? AGGR_START : AGGR_STOP; + spin_unlock_bh(&priv->tx.tx_lock); + + return ret; +} + +/*******/ +/* ANI */ +/*******/ + +void ath9k_htc_start_ani(struct ath9k_htc_priv *priv) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + unsigned long timestamp = jiffies_to_msecs(jiffies); + + common->ani.longcal_timer = timestamp; + common->ani.shortcal_timer = timestamp; + common->ani.checkani_timer = timestamp; + + set_bit(OP_ANI_RUNNING, &priv->op_flags); + + ieee80211_queue_delayed_work(common->hw, &priv->ani_work, + msecs_to_jiffies(ATH_ANI_POLLINTERVAL)); +} + +void ath9k_htc_stop_ani(struct ath9k_htc_priv *priv) +{ + cancel_delayed_work_sync(&priv->ani_work); + clear_bit(OP_ANI_RUNNING, &priv->op_flags); +} + +void ath9k_htc_ani_work(struct work_struct *work) +{ + struct ath9k_htc_priv *priv = + container_of(work, struct ath9k_htc_priv, ani_work.work); + struct ath_hw *ah = priv->ah; + struct ath_common *common = ath9k_hw_common(ah); + bool longcal = false; + bool shortcal = false; + bool aniflag = false; + unsigned int timestamp = jiffies_to_msecs(jiffies); + u32 cal_interval, short_cal_interval; + + short_cal_interval = (ah->opmode == NL80211_IFTYPE_AP) ? + ATH_AP_SHORT_CALINTERVAL : ATH_STA_SHORT_CALINTERVAL; + + /* Only calibrate if awake */ + if (ah->power_mode != ATH9K_PM_AWAKE) + goto set_timer; + + /* Long calibration runs independently of short calibration. */ + if ((timestamp - common->ani.longcal_timer) >= ATH_LONG_CALINTERVAL) { + longcal = true; + ath_dbg(common, ANI, "longcal @%lu\n", jiffies); + common->ani.longcal_timer = timestamp; + } + + /* Short calibration applies only while caldone is false */ + if (!common->ani.caldone) { + if ((timestamp - common->ani.shortcal_timer) >= + short_cal_interval) { + shortcal = true; + ath_dbg(common, ANI, "shortcal @%lu\n", jiffies); + common->ani.shortcal_timer = timestamp; + common->ani.resetcal_timer = timestamp; + } + } else { + if ((timestamp - common->ani.resetcal_timer) >= + ATH_RESTART_CALINTERVAL) { + common->ani.caldone = ath9k_hw_reset_calvalid(ah); + if (common->ani.caldone) + common->ani.resetcal_timer = timestamp; + } + } + + /* Verify whether we must check ANI */ + if (ah->config.enable_ani && + (timestamp - common->ani.checkani_timer) >= ATH_ANI_POLLINTERVAL) { + aniflag = true; + common->ani.checkani_timer = timestamp; + } + + /* Skip all processing if there's nothing to do. */ + if (longcal || shortcal || aniflag) { + + ath9k_htc_ps_wakeup(priv); + + /* Call ANI routine if necessary */ + if (aniflag) + ath9k_hw_ani_monitor(ah, ah->curchan); + + /* Perform calibration if necessary */ + if (longcal || shortcal) + common->ani.caldone = + ath9k_hw_calibrate(ah, ah->curchan, + ah->rxchainmask, longcal); + + ath9k_htc_ps_restore(priv); + } + +set_timer: + /* + * Set timer interval based on previous results. + * The interval must be the shortest necessary to satisfy ANI, + * short calibration and long calibration. + */ + cal_interval = ATH_LONG_CALINTERVAL; + if (ah->config.enable_ani) + cal_interval = min(cal_interval, (u32)ATH_ANI_POLLINTERVAL); + if (!common->ani.caldone) + cal_interval = min(cal_interval, (u32)short_cal_interval); + + ieee80211_queue_delayed_work(common->hw, &priv->ani_work, + msecs_to_jiffies(cal_interval)); +} + +/**********************/ +/* mac80211 Callbacks */ +/**********************/ + +static void ath9k_htc_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr; + struct ath9k_htc_priv *priv = hw->priv; + struct ath_common *common = ath9k_hw_common(priv->ah); + int padpos, padsize, ret, slot; + + hdr = (struct ieee80211_hdr *) skb->data; + + /* Add the padding after the header if this is not already done */ + padpos = ieee80211_hdrlen(hdr->frame_control); + padsize = padpos & 3; + if (padsize && skb->len > padpos) { + if (skb_headroom(skb) < padsize) { + ath_dbg(common, XMIT, "No room for padding\n"); + goto fail_tx; + } + skb_push(skb, padsize); + memmove(skb->data, skb->data + padsize, padpos); + } + + slot = ath9k_htc_tx_get_slot(priv); + if (slot < 0) { + ath_dbg(common, XMIT, "No free TX slot\n"); + goto fail_tx; + } + + ret = ath9k_htc_tx_start(priv, control->sta, skb, slot, false); + if (ret != 0) { + ath_dbg(common, XMIT, "Tx failed\n"); + goto clear_slot; + } + + ath9k_htc_check_stop_queues(priv); + + return; + +clear_slot: + ath9k_htc_tx_clear_slot(priv, slot); +fail_tx: + dev_kfree_skb_any(skb); +} + +static int ath9k_htc_start(struct ieee80211_hw *hw) +{ + struct ath9k_htc_priv *priv = hw->priv; + struct ath_hw *ah = priv->ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ieee80211_channel *curchan = hw->conf.chandef.chan; + struct ath9k_channel *init_channel; + int ret = 0; + enum htc_phymode mode; + __be16 htc_mode; + u8 cmd_rsp; + + mutex_lock(&priv->mutex); + + ath_dbg(common, CONFIG, + "Starting driver with initial channel: %d MHz\n", + curchan->center_freq); + + /* Ensure that HW is awake before flushing RX */ + ath9k_htc_setpower(priv, ATH9K_PM_AWAKE); + WMI_CMD(WMI_FLUSH_RECV_CMDID); + + /* setup initial channel */ + init_channel = ath9k_cmn_get_curchannel(hw, ah); + + ret = ath9k_hw_reset(ah, init_channel, ah->caldata, false); + if (ret) { + ath_err(common, + "Unable to reset hardware; reset status %d (freq %u MHz)\n", + ret, curchan->center_freq); + mutex_unlock(&priv->mutex); + return ret; + } + + ath9k_cmn_update_txpow(ah, priv->curtxpow, priv->txpowlimit, + &priv->curtxpow); + + mode = ath9k_htc_get_curmode(priv, init_channel); + htc_mode = cpu_to_be16(mode); + WMI_CMD_BUF(WMI_SET_MODE_CMDID, &htc_mode); + WMI_CMD(WMI_ATH_INIT_CMDID); + WMI_CMD(WMI_START_RECV_CMDID); + + ath9k_host_rx_init(priv); + + ret = ath9k_htc_update_cap_target(priv, 0); + if (ret) + ath_dbg(common, CONFIG, + "Failed to update capability in target\n"); + + clear_bit(OP_INVALID, &priv->op_flags); + htc_start(priv->htc); + + spin_lock_bh(&priv->tx.tx_lock); + priv->tx.flags &= ~ATH9K_HTC_OP_TX_QUEUES_STOP; + spin_unlock_bh(&priv->tx.tx_lock); + + ieee80211_wake_queues(hw); + + mod_timer(&priv->tx.cleanup_timer, + jiffies + msecs_to_jiffies(ATH9K_HTC_TX_CLEANUP_INTERVAL)); + + ath9k_htc_start_btcoex(priv); + + mutex_unlock(&priv->mutex); + + return ret; +} + +static void ath9k_htc_stop(struct ieee80211_hw *hw) +{ + struct ath9k_htc_priv *priv = hw->priv; + struct ath_hw *ah = priv->ah; + struct ath_common *common = ath9k_hw_common(ah); + int ret __attribute__ ((unused)); + u8 cmd_rsp; + + mutex_lock(&priv->mutex); + + if (test_bit(OP_INVALID, &priv->op_flags)) { + ath_dbg(common, ANY, "Device not present\n"); + mutex_unlock(&priv->mutex); + return; + } + + ath9k_htc_ps_wakeup(priv); + + WMI_CMD(WMI_DISABLE_INTR_CMDID); + WMI_CMD(WMI_DRAIN_TXQ_ALL_CMDID); + WMI_CMD(WMI_STOP_RECV_CMDID); + + tasklet_kill(&priv->rx_tasklet); + + del_timer_sync(&priv->tx.cleanup_timer); + ath9k_htc_tx_drain(priv); + ath9k_wmi_event_drain(priv); + + mutex_unlock(&priv->mutex); + + /* Cancel all the running timers/work .. */ + cancel_work_sync(&priv->fatal_work); + cancel_work_sync(&priv->ps_work); + +#ifdef CPTCFG_MAC80211_LEDS + cancel_work_sync(&priv->led_work); +#endif + ath9k_htc_stop_ani(priv); + + mutex_lock(&priv->mutex); + + ath9k_htc_stop_btcoex(priv); + + /* Remove a monitor interface if it's present. */ + if (priv->ah->is_monitoring) + ath9k_htc_remove_monitor_interface(priv); + + ath9k_hw_phy_disable(ah); + ath9k_hw_disable(ah); + ath9k_htc_ps_restore(priv); + ath9k_htc_setpower(priv, ATH9K_PM_FULL_SLEEP); + + set_bit(OP_INVALID, &priv->op_flags); + + ath_dbg(common, CONFIG, "Driver halt\n"); + mutex_unlock(&priv->mutex); +} + +static int ath9k_htc_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct ath9k_htc_priv *priv = hw->priv; + struct ath9k_htc_vif *avp = (void *)vif->drv_priv; + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_target_vif hvif; + int ret = 0; + u8 cmd_rsp; + + mutex_lock(&priv->mutex); + + ath9k_htc_ps_wakeup(priv); + memset(&hvif, 0, sizeof(struct ath9k_htc_target_vif)); + memcpy(&hvif.myaddr, vif->addr, ETH_ALEN); + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + hvif.opmode = HTC_M_STA; + break; + case NL80211_IFTYPE_ADHOC: + hvif.opmode = HTC_M_IBSS; + break; + case NL80211_IFTYPE_AP: + hvif.opmode = HTC_M_HOSTAP; + break; + default: + ath_err(common, + "Interface type %d not yet supported\n", vif->type); + ret = -EOPNOTSUPP; + goto out; + } + + /* Index starts from zero on the target */ + avp->index = hvif.index = ffz(priv->vif_slot); + hvif.rtsthreshold = cpu_to_be16(2304); + WMI_CMD_BUF(WMI_VAP_CREATE_CMDID, &hvif); + if (ret) + goto out; + + /* + * We need a node in target to tx mgmt frames + * before association. + */ + ret = ath9k_htc_add_station(priv, vif, NULL); + if (ret) { + WMI_CMD_BUF(WMI_VAP_REMOVE_CMDID, &hvif); + goto out; + } + + ath9k_htc_set_bssid_mask(priv, vif); + + priv->vif_slot |= (1 << avp->index); + priv->nvifs++; + + INC_VIF(priv, vif->type); + + if ((vif->type == NL80211_IFTYPE_AP) || + (vif->type == NL80211_IFTYPE_ADHOC)) + ath9k_htc_assign_bslot(priv, vif); + + ath9k_htc_set_opmode(priv); + + if ((priv->ah->opmode == NL80211_IFTYPE_AP) && + !test_bit(OP_ANI_RUNNING, &priv->op_flags)) { + ath9k_hw_set_tsfadjust(priv->ah, true); + ath9k_htc_start_ani(priv); + } + + ath_dbg(common, CONFIG, "Attach a VIF of type: %d at idx: %d\n", + vif->type, avp->index); + +out: + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); + + return ret; +} + +static void ath9k_htc_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct ath9k_htc_priv *priv = hw->priv; + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_vif *avp = (void *)vif->drv_priv; + struct ath9k_htc_target_vif hvif; + int ret = 0; + u8 cmd_rsp; + + mutex_lock(&priv->mutex); + ath9k_htc_ps_wakeup(priv); + + memset(&hvif, 0, sizeof(struct ath9k_htc_target_vif)); + memcpy(&hvif.myaddr, vif->addr, ETH_ALEN); + hvif.index = avp->index; + WMI_CMD_BUF(WMI_VAP_REMOVE_CMDID, &hvif); + if (ret) { + ath_err(common, "Unable to remove interface at idx: %d\n", + avp->index); + } + priv->nvifs--; + priv->vif_slot &= ~(1 << avp->index); + + ath9k_htc_remove_station(priv, vif, NULL); + + DEC_VIF(priv, vif->type); + + if ((vif->type == NL80211_IFTYPE_AP) || + (vif->type == NL80211_IFTYPE_ADHOC)) + ath9k_htc_remove_bslot(priv, vif); + + ath9k_htc_set_opmode(priv); + + ath9k_htc_set_bssid_mask(priv, vif); + + /* + * Stop ANI only if there are no associated station interfaces. + */ + if ((vif->type == NL80211_IFTYPE_AP) && (priv->num_ap_vif == 0)) { + priv->rearm_ani = false; + ieee80211_iterate_active_interfaces_atomic( + priv->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + ath9k_htc_vif_iter, priv); + if (!priv->rearm_ani) + ath9k_htc_stop_ani(priv); + } + + ath_dbg(common, CONFIG, "Detach Interface at idx: %d\n", avp->index); + + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); +} + +static int ath9k_htc_config(struct ieee80211_hw *hw, u32 changed) +{ + struct ath9k_htc_priv *priv = hw->priv; + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ieee80211_conf *conf = &hw->conf; + bool chip_reset = false; + int ret = 0; + + mutex_lock(&priv->mutex); + ath9k_htc_ps_wakeup(priv); + + if (changed & IEEE80211_CONF_CHANGE_IDLE) { + mutex_lock(&priv->htc_pm_lock); + + priv->ps_idle = !!(conf->flags & IEEE80211_CONF_IDLE); + if (!priv->ps_idle) + chip_reset = true; + + mutex_unlock(&priv->htc_pm_lock); + } + + /* + * Monitor interface should be added before + * IEEE80211_CONF_CHANGE_CHANNEL is handled. + */ + if (changed & IEEE80211_CONF_CHANGE_MONITOR) { + if ((conf->flags & IEEE80211_CONF_MONITOR) && + !priv->ah->is_monitoring) + ath9k_htc_add_monitor_interface(priv); + else if (priv->ah->is_monitoring) + ath9k_htc_remove_monitor_interface(priv); + } + + if ((changed & IEEE80211_CONF_CHANGE_CHANNEL) || chip_reset) { + struct ieee80211_channel *curchan = hw->conf.chandef.chan; + enum nl80211_channel_type channel_type = + cfg80211_get_chandef_type(&hw->conf.chandef); + int pos = curchan->hw_value; + + ath_dbg(common, CONFIG, "Set channel: %d MHz\n", + curchan->center_freq); + + ath9k_cmn_update_ichannel(&priv->ah->channels[pos], + hw->conf.chandef.chan, + channel_type); + + if (ath9k_htc_set_channel(priv, hw, &priv->ah->channels[pos]) < 0) { + ath_err(common, "Unable to set channel\n"); + ret = -EINVAL; + goto out; + } + + } + + if (changed & IEEE80211_CONF_CHANGE_PS) { + if (conf->flags & IEEE80211_CONF_PS) { + ath9k_htc_setpower(priv, ATH9K_PM_NETWORK_SLEEP); + priv->ps_enabled = true; + } else { + priv->ps_enabled = false; + cancel_work_sync(&priv->ps_work); + ath9k_htc_setpower(priv, ATH9K_PM_AWAKE); + } + } + + if (changed & IEEE80211_CONF_CHANGE_POWER) { + priv->txpowlimit = 2 * conf->power_level; + ath9k_cmn_update_txpow(priv->ah, priv->curtxpow, + priv->txpowlimit, &priv->curtxpow); + } + +out: + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); + return ret; +} + +#define SUPPORTED_FILTERS \ + (FIF_PROMISC_IN_BSS | \ + FIF_ALLMULTI | \ + FIF_CONTROL | \ + FIF_PSPOLL | \ + FIF_OTHER_BSS | \ + FIF_BCN_PRBRESP_PROMISC | \ + FIF_PROBE_REQ | \ + FIF_FCSFAIL) + +static void ath9k_htc_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + struct ath9k_htc_priv *priv = hw->priv; + u32 rfilt; + + mutex_lock(&priv->mutex); + changed_flags &= SUPPORTED_FILTERS; + *total_flags &= SUPPORTED_FILTERS; + + if (test_bit(OP_INVALID, &priv->op_flags)) { + ath_dbg(ath9k_hw_common(priv->ah), ANY, + "Unable to configure filter on invalid state\n"); + mutex_unlock(&priv->mutex); + return; + } + ath9k_htc_ps_wakeup(priv); + + priv->rxfilter = *total_flags; + rfilt = ath9k_htc_calcrxfilter(priv); + ath9k_hw_setrxfilter(priv->ah, rfilt); + + ath_dbg(ath9k_hw_common(priv->ah), CONFIG, "Set HW RX filter: 0x%x\n", + rfilt); + + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); +} + +static int ath9k_htc_sta_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct ath9k_htc_priv *priv = hw->priv; + int ret; + + mutex_lock(&priv->mutex); + ath9k_htc_ps_wakeup(priv); + ret = ath9k_htc_add_station(priv, vif, sta); + if (!ret) + ath9k_htc_init_rate(priv, sta); + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); + + return ret; +} + +static int ath9k_htc_sta_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct ath9k_htc_priv *priv = hw->priv; + struct ath9k_htc_sta *ista; + int ret; + + mutex_lock(&priv->mutex); + ath9k_htc_ps_wakeup(priv); + ista = (struct ath9k_htc_sta *) sta->drv_priv; + htc_sta_drain(priv->htc, ista->index); + ret = ath9k_htc_remove_station(priv, vif, sta); + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); + + return ret; +} + +static void ath9k_htc_sta_rc_update(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u32 changed) +{ + struct ath9k_htc_priv *priv = hw->priv; + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_target_rate trate; + + mutex_lock(&priv->mutex); + ath9k_htc_ps_wakeup(priv); + + if (changed & IEEE80211_RC_SUPP_RATES_CHANGED) { + memset(&trate, 0, sizeof(struct ath9k_htc_target_rate)); + ath9k_htc_setup_rate(priv, sta, &trate); + if (!ath9k_htc_send_rate_cmd(priv, &trate)) + ath_dbg(common, CONFIG, + "Supported rates for sta: %pM updated, rate caps: 0x%X\n", + sta->addr, be32_to_cpu(trate.capflags)); + else + ath_dbg(common, CONFIG, + "Unable to update supported rates for sta: %pM\n", + sta->addr); + } + + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); +} + +static int ath9k_htc_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct ath9k_htc_priv *priv = hw->priv; + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_tx_queue_info qi; + int ret = 0, qnum; + + if (queue >= IEEE80211_NUM_ACS) + return 0; + + mutex_lock(&priv->mutex); + ath9k_htc_ps_wakeup(priv); + + memset(&qi, 0, sizeof(struct ath9k_tx_queue_info)); + + qi.tqi_aifs = params->aifs; + qi.tqi_cwmin = params->cw_min; + qi.tqi_cwmax = params->cw_max; + qi.tqi_burstTime = params->txop * 32; + + qnum = get_hw_qnum(queue, priv->hwq_map); + + ath_dbg(common, CONFIG, + "Configure tx [queue/hwq] [%d/%d], aifs: %d, cw_min: %d, cw_max: %d, txop: %d\n", + queue, qnum, params->aifs, params->cw_min, + params->cw_max, params->txop); + + ret = ath_htc_txq_update(priv, qnum, &qi); + if (ret) { + ath_err(common, "TXQ Update failed\n"); + goto out; + } + + if ((priv->ah->opmode == NL80211_IFTYPE_ADHOC) && + (qnum == priv->hwq_map[IEEE80211_AC_BE])) + ath9k_htc_beaconq_config(priv); +out: + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); + + return ret; +} + +static int ath9k_htc_set_key(struct ieee80211_hw *hw, + enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct ath9k_htc_priv *priv = hw->priv; + struct ath_common *common = ath9k_hw_common(priv->ah); + int ret = 0; + + if (htc_modparam_nohwcrypt) + return -ENOSPC; + + if ((vif->type == NL80211_IFTYPE_ADHOC || + vif->type == NL80211_IFTYPE_MESH_POINT) && + (key->cipher == WLAN_CIPHER_SUITE_TKIP || + key->cipher == WLAN_CIPHER_SUITE_CCMP) && + !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) { + /* + * For now, disable hw crypto for the RSN IBSS group keys. This + * could be optimized in the future to use a modified key cache + * design to support per-STA RX GTK, but until that gets + * implemented, use of software crypto for group addressed + * frames is a acceptable to allow RSN IBSS to be used. + */ + return -EOPNOTSUPP; + } + + mutex_lock(&priv->mutex); + ath_dbg(common, CONFIG, "Set HW Key\n"); + ath9k_htc_ps_wakeup(priv); + + switch (cmd) { + case SET_KEY: + ret = ath_key_config(common, vif, sta, key); + if (ret >= 0) { + key->hw_key_idx = ret; + /* push IV and Michael MIC generation to stack */ + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + if (key->cipher == WLAN_CIPHER_SUITE_TKIP) + key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC; + if (priv->ah->sw_mgmt_crypto && + key->cipher == WLAN_CIPHER_SUITE_CCMP) + key->flags |= IEEE80211_KEY_FLAG_SW_MGMT_TX; + ret = 0; + } + break; + case DISABLE_KEY: + ath_key_delete(common, key); + break; + default: + ret = -EINVAL; + } + + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); + + return ret; +} + +static void ath9k_htc_set_bssid(struct ath9k_htc_priv *priv) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + + ath9k_hw_write_associd(priv->ah); + ath_dbg(common, CONFIG, "BSSID: %pM aid: 0x%x\n", + common->curbssid, common->curaid); +} + +static void ath9k_htc_bss_iter(void *data, u8 *mac, struct ieee80211_vif *vif) +{ + struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *)data; + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ieee80211_bss_conf *bss_conf = &vif->bss_conf; + + if ((vif->type == NL80211_IFTYPE_STATION) && bss_conf->assoc) { + common->curaid = bss_conf->aid; + memcpy(common->curbssid, bss_conf->bssid, ETH_ALEN); + } +} + +static void ath9k_htc_choose_set_bssid(struct ath9k_htc_priv *priv) +{ + if (priv->num_sta_assoc_vif == 1) { + ieee80211_iterate_active_interfaces_atomic( + priv->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + ath9k_htc_bss_iter, priv); + ath9k_htc_set_bssid(priv); + } +} + +static void ath9k_htc_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changed) +{ + struct ath9k_htc_priv *priv = hw->priv; + struct ath_hw *ah = priv->ah; + struct ath_common *common = ath9k_hw_common(ah); + + mutex_lock(&priv->mutex); + ath9k_htc_ps_wakeup(priv); + + if (changed & BSS_CHANGED_ASSOC) { + ath_dbg(common, CONFIG, "BSS Changed ASSOC %d\n", + bss_conf->assoc); + + bss_conf->assoc ? + priv->num_sta_assoc_vif++ : priv->num_sta_assoc_vif--; + + if (priv->ah->opmode == NL80211_IFTYPE_STATION) { + ath9k_htc_choose_set_bssid(priv); + if (bss_conf->assoc && (priv->num_sta_assoc_vif == 1)) + ath9k_htc_start_ani(priv); + else if (priv->num_sta_assoc_vif == 0) + ath9k_htc_stop_ani(priv); + } + } + + if (changed & BSS_CHANGED_IBSS) { + if (priv->ah->opmode == NL80211_IFTYPE_ADHOC) { + common->curaid = bss_conf->aid; + memcpy(common->curbssid, bss_conf->bssid, ETH_ALEN); + ath9k_htc_set_bssid(priv); + } + } + + if ((changed & BSS_CHANGED_BEACON_ENABLED) && bss_conf->enable_beacon) { + ath_dbg(common, CONFIG, "Beacon enabled for BSS: %pM\n", + bss_conf->bssid); + ath9k_htc_set_tsfadjust(priv, vif); + set_bit(OP_ENABLE_BEACON, &priv->op_flags); + ath9k_htc_beacon_config(priv, vif); + } + + if ((changed & BSS_CHANGED_BEACON_ENABLED) && !bss_conf->enable_beacon) { + /* + * Disable SWBA interrupt only if there are no + * AP/IBSS interfaces. + */ + if ((priv->num_ap_vif <= 1) || priv->num_ibss_vif) { + ath_dbg(common, CONFIG, + "Beacon disabled for BSS: %pM\n", + bss_conf->bssid); + clear_bit(OP_ENABLE_BEACON, &priv->op_flags); + ath9k_htc_beacon_config(priv, vif); + } + } + + if (changed & BSS_CHANGED_BEACON_INT) { + /* + * Reset the HW TSF for the first AP interface. + */ + if ((priv->ah->opmode == NL80211_IFTYPE_AP) && + (priv->nvifs == 1) && + (priv->num_ap_vif == 1) && + (vif->type == NL80211_IFTYPE_AP)) { + set_bit(OP_TSF_RESET, &priv->op_flags); + } + ath_dbg(common, CONFIG, + "Beacon interval changed for BSS: %pM\n", + bss_conf->bssid); + ath9k_htc_beacon_config(priv, vif); + } + + if (changed & BSS_CHANGED_ERP_SLOT) { + if (bss_conf->use_short_slot) + ah->slottime = 9; + else + ah->slottime = 20; + + ath9k_hw_init_global_settings(ah); + } + + if (changed & BSS_CHANGED_HT) + ath9k_htc_update_rate(priv, vif, bss_conf); + + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); +} + +static u64 ath9k_htc_get_tsf(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct ath9k_htc_priv *priv = hw->priv; + u64 tsf; + + mutex_lock(&priv->mutex); + ath9k_htc_ps_wakeup(priv); + tsf = ath9k_hw_gettsf64(priv->ah); + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); + + return tsf; +} + +static void ath9k_htc_set_tsf(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u64 tsf) +{ + struct ath9k_htc_priv *priv = hw->priv; + + mutex_lock(&priv->mutex); + ath9k_htc_ps_wakeup(priv); + ath9k_hw_settsf64(priv->ah, tsf); + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); +} + +static void ath9k_htc_reset_tsf(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct ath9k_htc_priv *priv = hw->priv; + + mutex_lock(&priv->mutex); + ath9k_htc_ps_wakeup(priv); + ath9k_hw_reset_tsf(priv->ah); + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); +} + +static int ath9k_htc_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, + u16 tid, u16 *ssn, u8 buf_size) +{ + struct ath9k_htc_priv *priv = hw->priv; + struct ath9k_htc_sta *ista; + int ret = 0; + + mutex_lock(&priv->mutex); + ath9k_htc_ps_wakeup(priv); + + switch (action) { + case IEEE80211_AMPDU_RX_START: + break; + case IEEE80211_AMPDU_RX_STOP: + break; + case IEEE80211_AMPDU_TX_START: + ret = ath9k_htc_tx_aggr_oper(priv, vif, sta, action, tid); + if (!ret) + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + ath9k_htc_tx_aggr_oper(priv, vif, sta, action, tid); + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + ista = (struct ath9k_htc_sta *) sta->drv_priv; + spin_lock_bh(&priv->tx.tx_lock); + ista->tid_state[tid] = AGGR_OPERATIONAL; + spin_unlock_bh(&priv->tx.tx_lock); + break; + default: + ath_err(ath9k_hw_common(priv->ah), "Unknown AMPDU action\n"); + } + + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); + + return ret; +} + +static void ath9k_htc_sw_scan_start(struct ieee80211_hw *hw) +{ + struct ath9k_htc_priv *priv = hw->priv; + + mutex_lock(&priv->mutex); + spin_lock_bh(&priv->beacon_lock); + set_bit(OP_SCANNING, &priv->op_flags); + spin_unlock_bh(&priv->beacon_lock); + cancel_work_sync(&priv->ps_work); + ath9k_htc_stop_ani(priv); + mutex_unlock(&priv->mutex); +} + +static void ath9k_htc_sw_scan_complete(struct ieee80211_hw *hw) +{ + struct ath9k_htc_priv *priv = hw->priv; + + mutex_lock(&priv->mutex); + spin_lock_bh(&priv->beacon_lock); + clear_bit(OP_SCANNING, &priv->op_flags); + spin_unlock_bh(&priv->beacon_lock); + ath9k_htc_ps_wakeup(priv); + ath9k_htc_vif_reconfig(priv); + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); +} + +static int ath9k_htc_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +{ + return 0; +} + +static void ath9k_htc_set_coverage_class(struct ieee80211_hw *hw, + u8 coverage_class) +{ + struct ath9k_htc_priv *priv = hw->priv; + + mutex_lock(&priv->mutex); + ath9k_htc_ps_wakeup(priv); + priv->ah->coverage_class = coverage_class; + ath9k_hw_init_global_settings(priv->ah); + ath9k_htc_ps_restore(priv); + mutex_unlock(&priv->mutex); +} + +/* + * Currently, this is used only for selecting the minimum rate + * for management frames, rate selection for data frames remain + * unaffected. + */ +static int ath9k_htc_set_bitrate_mask(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const struct cfg80211_bitrate_mask *mask) +{ + struct ath9k_htc_priv *priv = hw->priv; + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_target_rate_mask tmask; + struct ath9k_htc_vif *avp = (void *)vif->drv_priv; + int ret = 0; + u8 cmd_rsp; + + memset(&tmask, 0, sizeof(struct ath9k_htc_target_rate_mask)); + + tmask.vif_index = avp->index; + tmask.band = IEEE80211_BAND_2GHZ; + tmask.mask = cpu_to_be32(mask->control[IEEE80211_BAND_2GHZ].legacy); + + WMI_CMD_BUF(WMI_BITRATE_MASK_CMDID, &tmask); + if (ret) { + ath_err(common, + "Unable to set 2G rate mask for " + "interface at idx: %d\n", avp->index); + goto out; + } + + tmask.band = IEEE80211_BAND_5GHZ; + tmask.mask = cpu_to_be32(mask->control[IEEE80211_BAND_5GHZ].legacy); + + WMI_CMD_BUF(WMI_BITRATE_MASK_CMDID, &tmask); + if (ret) { + ath_err(common, + "Unable to set 5G rate mask for " + "interface at idx: %d\n", avp->index); + goto out; + } + + ath_dbg(common, CONFIG, "Set bitrate masks: 0x%x, 0x%x\n", + mask->control[IEEE80211_BAND_2GHZ].legacy, + mask->control[IEEE80211_BAND_5GHZ].legacy); +out: + return ret; +} + + +static int ath9k_htc_get_stats(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + struct ath9k_htc_priv *priv = hw->priv; + struct ath_hw *ah = priv->ah; + struct ath9k_mib_stats *mib_stats = &ah->ah_mibStats; + + stats->dot11ACKFailureCount = mib_stats->ackrcv_bad; + stats->dot11RTSFailureCount = mib_stats->rts_bad; + stats->dot11FCSErrorCount = mib_stats->fcs_bad; + stats->dot11RTSSuccessCount = mib_stats->rts_good; + + return 0; +} + +struct ieee80211_ops ath9k_htc_ops = { + .tx = ath9k_htc_tx, + .start = ath9k_htc_start, + .stop = ath9k_htc_stop, + .add_interface = ath9k_htc_add_interface, + .remove_interface = ath9k_htc_remove_interface, + .config = ath9k_htc_config, + .configure_filter = ath9k_htc_configure_filter, + .sta_add = ath9k_htc_sta_add, + .sta_remove = ath9k_htc_sta_remove, + .conf_tx = ath9k_htc_conf_tx, + .sta_rc_update = ath9k_htc_sta_rc_update, + .bss_info_changed = ath9k_htc_bss_info_changed, + .set_key = ath9k_htc_set_key, + .get_tsf = ath9k_htc_get_tsf, + .set_tsf = ath9k_htc_set_tsf, + .reset_tsf = ath9k_htc_reset_tsf, + .ampdu_action = ath9k_htc_ampdu_action, + .sw_scan_start = ath9k_htc_sw_scan_start, + .sw_scan_complete = ath9k_htc_sw_scan_complete, + .set_rts_threshold = ath9k_htc_set_rts_threshold, + .rfkill_poll = ath9k_htc_rfkill_poll_state, + .set_coverage_class = ath9k_htc_set_coverage_class, + .set_bitrate_mask = ath9k_htc_set_bitrate_mask, + .get_stats = ath9k_htc_get_stats, +}; diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c b/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c new file mode 100644 index 0000000..417a089 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c @@ -0,0 +1,1230 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "htc.h" + +/******/ +/* TX */ +/******/ + +static const int subtype_txq_to_hwq[] = { + [IEEE80211_AC_BE] = ATH_TXQ_AC_BE, + [IEEE80211_AC_BK] = ATH_TXQ_AC_BK, + [IEEE80211_AC_VI] = ATH_TXQ_AC_VI, + [IEEE80211_AC_VO] = ATH_TXQ_AC_VO, +}; + +#define ATH9K_HTC_INIT_TXQ(subtype) do { \ + qi.tqi_subtype = subtype_txq_to_hwq[subtype]; \ + qi.tqi_aifs = ATH9K_TXQ_USEDEFAULT; \ + qi.tqi_cwmin = ATH9K_TXQ_USEDEFAULT; \ + qi.tqi_cwmax = ATH9K_TXQ_USEDEFAULT; \ + qi.tqi_physCompBuf = 0; \ + qi.tqi_qflags = TXQ_FLAG_TXEOLINT_ENABLE | \ + TXQ_FLAG_TXDESCINT_ENABLE; \ + } while (0) + +int get_hw_qnum(u16 queue, int *hwq_map) +{ + switch (queue) { + case 0: + return hwq_map[IEEE80211_AC_VO]; + case 1: + return hwq_map[IEEE80211_AC_VI]; + case 2: + return hwq_map[IEEE80211_AC_BE]; + case 3: + return hwq_map[IEEE80211_AC_BK]; + default: + return hwq_map[IEEE80211_AC_BE]; + } +} + +void ath9k_htc_check_stop_queues(struct ath9k_htc_priv *priv) +{ + spin_lock_bh(&priv->tx.tx_lock); + priv->tx.queued_cnt++; + if ((priv->tx.queued_cnt >= ATH9K_HTC_TX_THRESHOLD) && + !(priv->tx.flags & ATH9K_HTC_OP_TX_QUEUES_STOP)) { + priv->tx.flags |= ATH9K_HTC_OP_TX_QUEUES_STOP; + ieee80211_stop_queues(priv->hw); + } + spin_unlock_bh(&priv->tx.tx_lock); +} + +void ath9k_htc_check_wake_queues(struct ath9k_htc_priv *priv) +{ + spin_lock_bh(&priv->tx.tx_lock); + if ((priv->tx.queued_cnt < ATH9K_HTC_TX_THRESHOLD) && + (priv->tx.flags & ATH9K_HTC_OP_TX_QUEUES_STOP)) { + priv->tx.flags &= ~ATH9K_HTC_OP_TX_QUEUES_STOP; + ieee80211_wake_queues(priv->hw); + } + spin_unlock_bh(&priv->tx.tx_lock); +} + +int ath9k_htc_tx_get_slot(struct ath9k_htc_priv *priv) +{ + int slot; + + spin_lock_bh(&priv->tx.tx_lock); + slot = find_first_zero_bit(priv->tx.tx_slot, MAX_TX_BUF_NUM); + if (slot >= MAX_TX_BUF_NUM) { + spin_unlock_bh(&priv->tx.tx_lock); + return -ENOBUFS; + } + __set_bit(slot, priv->tx.tx_slot); + spin_unlock_bh(&priv->tx.tx_lock); + + return slot; +} + +void ath9k_htc_tx_clear_slot(struct ath9k_htc_priv *priv, int slot) +{ + spin_lock_bh(&priv->tx.tx_lock); + __clear_bit(slot, priv->tx.tx_slot); + spin_unlock_bh(&priv->tx.tx_lock); +} + +static inline enum htc_endpoint_id get_htc_epid(struct ath9k_htc_priv *priv, + u16 qnum) +{ + enum htc_endpoint_id epid; + + switch (qnum) { + case 0: + TX_QSTAT_INC(IEEE80211_AC_VO); + epid = priv->data_vo_ep; + break; + case 1: + TX_QSTAT_INC(IEEE80211_AC_VI); + epid = priv->data_vi_ep; + break; + case 2: + TX_QSTAT_INC(IEEE80211_AC_BE); + epid = priv->data_be_ep; + break; + case 3: + default: + TX_QSTAT_INC(IEEE80211_AC_BK); + epid = priv->data_bk_ep; + break; + } + + return epid; +} + +static inline struct sk_buff_head* +get_htc_epid_queue(struct ath9k_htc_priv *priv, u8 epid) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct sk_buff_head *epid_queue = NULL; + + if (epid == priv->mgmt_ep) + epid_queue = &priv->tx.mgmt_ep_queue; + else if (epid == priv->cab_ep) + epid_queue = &priv->tx.cab_ep_queue; + else if (epid == priv->data_be_ep) + epid_queue = &priv->tx.data_be_queue; + else if (epid == priv->data_bk_ep) + epid_queue = &priv->tx.data_bk_queue; + else if (epid == priv->data_vi_ep) + epid_queue = &priv->tx.data_vi_queue; + else if (epid == priv->data_vo_ep) + epid_queue = &priv->tx.data_vo_queue; + else + ath_err(common, "Invalid EPID: %d\n", epid); + + return epid_queue; +} + +/* + * Removes the driver header and returns the TX slot number + */ +static inline int strip_drv_header(struct ath9k_htc_priv *priv, + struct sk_buff *skb) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_tx_ctl *tx_ctl; + int slot; + + tx_ctl = HTC_SKB_CB(skb); + + if (tx_ctl->epid == priv->mgmt_ep) { + struct tx_mgmt_hdr *tx_mhdr = + (struct tx_mgmt_hdr *)skb->data; + slot = tx_mhdr->cookie; + skb_pull(skb, sizeof(struct tx_mgmt_hdr)); + } else if ((tx_ctl->epid == priv->data_bk_ep) || + (tx_ctl->epid == priv->data_be_ep) || + (tx_ctl->epid == priv->data_vi_ep) || + (tx_ctl->epid == priv->data_vo_ep) || + (tx_ctl->epid == priv->cab_ep)) { + struct tx_frame_hdr *tx_fhdr = + (struct tx_frame_hdr *)skb->data; + slot = tx_fhdr->cookie; + skb_pull(skb, sizeof(struct tx_frame_hdr)); + } else { + ath_err(common, "Unsupported EPID: %d\n", tx_ctl->epid); + slot = -EINVAL; + } + + return slot; +} + +int ath_htc_txq_update(struct ath9k_htc_priv *priv, int qnum, + struct ath9k_tx_queue_info *qinfo) +{ + struct ath_hw *ah = priv->ah; + int error = 0; + struct ath9k_tx_queue_info qi; + + ath9k_hw_get_txq_props(ah, qnum, &qi); + + qi.tqi_aifs = qinfo->tqi_aifs; + qi.tqi_cwmin = qinfo->tqi_cwmin / 2; /* XXX */ + qi.tqi_cwmax = qinfo->tqi_cwmax; + qi.tqi_burstTime = qinfo->tqi_burstTime; + qi.tqi_readyTime = qinfo->tqi_readyTime; + + if (!ath9k_hw_set_txq_props(ah, qnum, &qi)) { + ath_err(ath9k_hw_common(ah), + "Unable to update hardware queue %u!\n", qnum); + error = -EIO; + } else { + ath9k_hw_resettxqueue(ah, qnum); + } + + return error; +} + +static void ath9k_htc_tx_mgmt(struct ath9k_htc_priv *priv, + struct ath9k_htc_vif *avp, + struct sk_buff *skb, + u8 sta_idx, u8 vif_idx, u8 slot) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_mgmt *mgmt; + struct ieee80211_hdr *hdr; + struct tx_mgmt_hdr mgmt_hdr; + struct ath9k_htc_tx_ctl *tx_ctl; + u8 *tx_fhdr; + + tx_ctl = HTC_SKB_CB(skb); + hdr = (struct ieee80211_hdr *) skb->data; + + memset(tx_ctl, 0, sizeof(*tx_ctl)); + memset(&mgmt_hdr, 0, sizeof(struct tx_mgmt_hdr)); + + /* + * Set the TSF adjust value for probe response + * frame also. + */ + if (avp && unlikely(ieee80211_is_probe_resp(hdr->frame_control))) { + mgmt = (struct ieee80211_mgmt *)skb->data; + mgmt->u.probe_resp.timestamp = avp->tsfadjust; + } + + tx_ctl->type = ATH9K_HTC_MGMT; + + mgmt_hdr.node_idx = sta_idx; + mgmt_hdr.vif_idx = vif_idx; + mgmt_hdr.tidno = 0; + mgmt_hdr.flags = 0; + mgmt_hdr.cookie = slot; + + mgmt_hdr.key_type = ath9k_cmn_get_hw_crypto_keytype(skb); + if (mgmt_hdr.key_type == ATH9K_KEY_TYPE_CLEAR) + mgmt_hdr.keyix = (u8) ATH9K_TXKEYIX_INVALID; + else + mgmt_hdr.keyix = tx_info->control.hw_key->hw_key_idx; + + tx_fhdr = skb_push(skb, sizeof(mgmt_hdr)); + memcpy(tx_fhdr, (u8 *) &mgmt_hdr, sizeof(mgmt_hdr)); + tx_ctl->epid = priv->mgmt_ep; +} + +static void ath9k_htc_tx_data(struct ath9k_htc_priv *priv, + struct ieee80211_vif *vif, + struct sk_buff *skb, + u8 sta_idx, u8 vif_idx, u8 slot, + bool is_cab) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr; + struct ath9k_htc_tx_ctl *tx_ctl; + struct tx_frame_hdr tx_hdr; + u32 flags = 0; + u8 *qc, *tx_fhdr; + u16 qnum; + + tx_ctl = HTC_SKB_CB(skb); + hdr = (struct ieee80211_hdr *) skb->data; + + memset(tx_ctl, 0, sizeof(*tx_ctl)); + memset(&tx_hdr, 0, sizeof(struct tx_frame_hdr)); + + tx_hdr.node_idx = sta_idx; + tx_hdr.vif_idx = vif_idx; + tx_hdr.cookie = slot; + + /* + * This is a bit redundant but it helps to get + * the per-packet index quickly when draining the + * TX queue in the HIF layer. Otherwise we would + * have to parse the packet contents ... + */ + tx_ctl->sta_idx = sta_idx; + + if (tx_info->flags & IEEE80211_TX_CTL_AMPDU) { + tx_ctl->type = ATH9K_HTC_AMPDU; + tx_hdr.data_type = ATH9K_HTC_AMPDU; + } else { + tx_ctl->type = ATH9K_HTC_NORMAL; + tx_hdr.data_type = ATH9K_HTC_NORMAL; + } + + if (ieee80211_is_data_qos(hdr->frame_control)) { + qc = ieee80211_get_qos_ctl(hdr); + tx_hdr.tidno = qc[0] & IEEE80211_QOS_CTL_TID_MASK; + } + + /* Check for RTS protection */ + if (priv->hw->wiphy->rts_threshold != (u32) -1) + if (skb->len > priv->hw->wiphy->rts_threshold) + flags |= ATH9K_HTC_TX_RTSCTS; + + /* CTS-to-self */ + if (!(flags & ATH9K_HTC_TX_RTSCTS) && + (vif && vif->bss_conf.use_cts_prot)) + flags |= ATH9K_HTC_TX_CTSONLY; + + tx_hdr.flags = cpu_to_be32(flags); + tx_hdr.key_type = ath9k_cmn_get_hw_crypto_keytype(skb); + if (tx_hdr.key_type == ATH9K_KEY_TYPE_CLEAR) + tx_hdr.keyix = (u8) ATH9K_TXKEYIX_INVALID; + else + tx_hdr.keyix = tx_info->control.hw_key->hw_key_idx; + + tx_fhdr = skb_push(skb, sizeof(tx_hdr)); + memcpy(tx_fhdr, (u8 *) &tx_hdr, sizeof(tx_hdr)); + + if (is_cab) { + CAB_STAT_INC; + tx_ctl->epid = priv->cab_ep; + return; + } + + qnum = skb_get_queue_mapping(skb); + tx_ctl->epid = get_htc_epid(priv, qnum); +} + +int ath9k_htc_tx_start(struct ath9k_htc_priv *priv, + struct ieee80211_sta *sta, + struct sk_buff *skb, + u8 slot, bool is_cab) +{ + struct ieee80211_hdr *hdr; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_vif *vif = tx_info->control.vif; + struct ath9k_htc_sta *ista; + struct ath9k_htc_vif *avp = NULL; + u8 sta_idx, vif_idx; + + hdr = (struct ieee80211_hdr *) skb->data; + + /* + * Find out on which interface this packet has to be + * sent out. + */ + if (vif) { + avp = (struct ath9k_htc_vif *) vif->drv_priv; + vif_idx = avp->index; + } else { + if (!priv->ah->is_monitoring) { + ath_dbg(ath9k_hw_common(priv->ah), XMIT, + "VIF is null, but no monitor interface !\n"); + return -EINVAL; + } + + vif_idx = priv->mon_vif_idx; + } + + /* + * Find out which station this packet is destined for. + */ + if (sta) { + ista = (struct ath9k_htc_sta *) sta->drv_priv; + sta_idx = ista->index; + } else { + sta_idx = priv->vif_sta_pos[vif_idx]; + } + + if (ieee80211_is_data(hdr->frame_control)) + ath9k_htc_tx_data(priv, vif, skb, + sta_idx, vif_idx, slot, is_cab); + else + ath9k_htc_tx_mgmt(priv, avp, skb, + sta_idx, vif_idx, slot); + + + return htc_send(priv->htc, skb); +} + +static inline bool __ath9k_htc_check_tx_aggr(struct ath9k_htc_priv *priv, + struct ath9k_htc_sta *ista, u8 tid) +{ + bool ret = false; + + spin_lock_bh(&priv->tx.tx_lock); + if ((tid < ATH9K_HTC_MAX_TID) && (ista->tid_state[tid] == AGGR_STOP)) + ret = true; + spin_unlock_bh(&priv->tx.tx_lock); + + return ret; +} + +static void ath9k_htc_check_tx_aggr(struct ath9k_htc_priv *priv, + struct ieee80211_vif *vif, + struct sk_buff *skb) +{ + struct ieee80211_sta *sta; + struct ieee80211_hdr *hdr; + __le16 fc; + + hdr = (struct ieee80211_hdr *) skb->data; + fc = hdr->frame_control; + + rcu_read_lock(); + + sta = ieee80211_find_sta(vif, hdr->addr1); + if (!sta) { + rcu_read_unlock(); + return; + } + + if (sta && conf_is_ht(&priv->hw->conf) && + !(skb->protocol == cpu_to_be16(ETH_P_PAE))) { + if (ieee80211_is_data_qos(fc)) { + u8 *qc, tid; + struct ath9k_htc_sta *ista; + + qc = ieee80211_get_qos_ctl(hdr); + tid = qc[0] & 0xf; + ista = (struct ath9k_htc_sta *)sta->drv_priv; + if (__ath9k_htc_check_tx_aggr(priv, ista, tid)) { + ieee80211_start_tx_ba_session(sta, tid, 0); + spin_lock_bh(&priv->tx.tx_lock); + ista->tid_state[tid] = AGGR_PROGRESS; + spin_unlock_bh(&priv->tx.tx_lock); + } + } + } + + rcu_read_unlock(); +} + +static void ath9k_htc_tx_process(struct ath9k_htc_priv *priv, + struct sk_buff *skb, + struct __wmi_event_txstatus *txs) +{ + struct ieee80211_vif *vif; + struct ath9k_htc_tx_ctl *tx_ctl; + struct ieee80211_tx_info *tx_info; + struct ieee80211_tx_rate *rate; + struct ieee80211_conf *cur_conf = &priv->hw->conf; + bool txok; + int slot; + int hdrlen, padsize; + + slot = strip_drv_header(priv, skb); + if (slot < 0) { + dev_kfree_skb_any(skb); + return; + } + + tx_ctl = HTC_SKB_CB(skb); + txok = tx_ctl->txok; + tx_info = IEEE80211_SKB_CB(skb); + vif = tx_info->control.vif; + rate = &tx_info->status.rates[0]; + + memset(&tx_info->status, 0, sizeof(tx_info->status)); + + /* + * URB submission failed for this frame, it never reached + * the target. + */ + if (!txok || !vif || !txs) + goto send_mac80211; + + if (txs->ts_flags & ATH9K_HTC_TXSTAT_ACK) + tx_info->flags |= IEEE80211_TX_STAT_ACK; + + if (txs->ts_flags & ATH9K_HTC_TXSTAT_FILT) + tx_info->flags |= IEEE80211_TX_STAT_TX_FILTERED; + + if (txs->ts_flags & ATH9K_HTC_TXSTAT_RTC_CTS) + rate->flags |= IEEE80211_TX_RC_USE_RTS_CTS; + + rate->count = 1; + rate->idx = MS(txs->ts_rate, ATH9K_HTC_TXSTAT_RATE); + + if (txs->ts_flags & ATH9K_HTC_TXSTAT_MCS) { + rate->flags |= IEEE80211_TX_RC_MCS; + + if (txs->ts_flags & ATH9K_HTC_TXSTAT_CW40) + rate->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; + if (txs->ts_flags & ATH9K_HTC_TXSTAT_SGI) + rate->flags |= IEEE80211_TX_RC_SHORT_GI; + } else { + if (cur_conf->chandef.chan->band == IEEE80211_BAND_5GHZ) + rate->idx += 4; /* No CCK rates */ + } + + ath9k_htc_check_tx_aggr(priv, vif, skb); + +send_mac80211: + spin_lock_bh(&priv->tx.tx_lock); + if (WARN_ON(--priv->tx.queued_cnt < 0)) + priv->tx.queued_cnt = 0; + spin_unlock_bh(&priv->tx.tx_lock); + + ath9k_htc_tx_clear_slot(priv, slot); + + /* Remove padding before handing frame back to mac80211 */ + hdrlen = ieee80211_get_hdrlen_from_skb(skb); + + padsize = hdrlen & 3; + if (padsize && skb->len > hdrlen + padsize) { + memmove(skb->data + padsize, skb->data, hdrlen); + skb_pull(skb, padsize); + } + + /* Send status to mac80211 */ + ieee80211_tx_status(priv->hw, skb); +} + +static inline void ath9k_htc_tx_drainq(struct ath9k_htc_priv *priv, + struct sk_buff_head *queue) +{ + struct sk_buff *skb; + + while ((skb = skb_dequeue(queue)) != NULL) { + ath9k_htc_tx_process(priv, skb, NULL); + } +} + +void ath9k_htc_tx_drain(struct ath9k_htc_priv *priv) +{ + struct ath9k_htc_tx_event *event, *tmp; + + spin_lock_bh(&priv->tx.tx_lock); + priv->tx.flags |= ATH9K_HTC_OP_TX_DRAIN; + spin_unlock_bh(&priv->tx.tx_lock); + + /* + * Ensure that all pending TX frames are flushed, + * and that the TX completion/failed tasklets is killed. + */ + htc_stop(priv->htc); + tasklet_kill(&priv->wmi->wmi_event_tasklet); + tasklet_kill(&priv->tx_failed_tasklet); + + ath9k_htc_tx_drainq(priv, &priv->tx.mgmt_ep_queue); + ath9k_htc_tx_drainq(priv, &priv->tx.cab_ep_queue); + ath9k_htc_tx_drainq(priv, &priv->tx.data_be_queue); + ath9k_htc_tx_drainq(priv, &priv->tx.data_bk_queue); + ath9k_htc_tx_drainq(priv, &priv->tx.data_vi_queue); + ath9k_htc_tx_drainq(priv, &priv->tx.data_vo_queue); + ath9k_htc_tx_drainq(priv, &priv->tx.tx_failed); + + /* + * The TX cleanup timer has already been killed. + */ + spin_lock_bh(&priv->wmi->event_lock); + list_for_each_entry_safe(event, tmp, &priv->wmi->pending_tx_events, list) { + list_del(&event->list); + kfree(event); + } + spin_unlock_bh(&priv->wmi->event_lock); + + spin_lock_bh(&priv->tx.tx_lock); + priv->tx.flags &= ~ATH9K_HTC_OP_TX_DRAIN; + spin_unlock_bh(&priv->tx.tx_lock); +} + +void ath9k_tx_failed_tasklet(unsigned long data) +{ + struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *)data; + + spin_lock_bh(&priv->tx.tx_lock); + if (priv->tx.flags & ATH9K_HTC_OP_TX_DRAIN) { + spin_unlock_bh(&priv->tx.tx_lock); + return; + } + spin_unlock_bh(&priv->tx.tx_lock); + + ath9k_htc_tx_drainq(priv, &priv->tx.tx_failed); +} + +static inline bool check_cookie(struct ath9k_htc_priv *priv, + struct sk_buff *skb, + u8 cookie, u8 epid) +{ + u8 fcookie = 0; + + if (epid == priv->mgmt_ep) { + struct tx_mgmt_hdr *hdr; + hdr = (struct tx_mgmt_hdr *) skb->data; + fcookie = hdr->cookie; + } else if ((epid == priv->data_bk_ep) || + (epid == priv->data_be_ep) || + (epid == priv->data_vi_ep) || + (epid == priv->data_vo_ep) || + (epid == priv->cab_ep)) { + struct tx_frame_hdr *hdr; + hdr = (struct tx_frame_hdr *) skb->data; + fcookie = hdr->cookie; + } + + if (fcookie == cookie) + return true; + + return false; +} + +static struct sk_buff* ath9k_htc_tx_get_packet(struct ath9k_htc_priv *priv, + struct __wmi_event_txstatus *txs) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct sk_buff_head *epid_queue; + struct sk_buff *skb, *tmp; + unsigned long flags; + u8 epid = MS(txs->ts_rate, ATH9K_HTC_TXSTAT_EPID); + + epid_queue = get_htc_epid_queue(priv, epid); + if (!epid_queue) + return NULL; + + spin_lock_irqsave(&epid_queue->lock, flags); + skb_queue_walk_safe(epid_queue, skb, tmp) { + if (check_cookie(priv, skb, txs->cookie, epid)) { + __skb_unlink(skb, epid_queue); + spin_unlock_irqrestore(&epid_queue->lock, flags); + return skb; + } + } + spin_unlock_irqrestore(&epid_queue->lock, flags); + + ath_dbg(common, XMIT, "No matching packet for cookie: %d, epid: %d\n", + txs->cookie, epid); + + return NULL; +} + +void ath9k_htc_txstatus(struct ath9k_htc_priv *priv, void *wmi_event) +{ + struct wmi_event_txstatus *txs = (struct wmi_event_txstatus *)wmi_event; + struct __wmi_event_txstatus *__txs; + struct sk_buff *skb; + struct ath9k_htc_tx_event *tx_pend; + int i; + + for (i = 0; i < txs->cnt; i++) { + WARN_ON(txs->cnt > HTC_MAX_TX_STATUS); + + __txs = &txs->txstatus[i]; + + skb = ath9k_htc_tx_get_packet(priv, __txs); + if (!skb) { + /* + * Store this event, so that the TX cleanup + * routine can check later for the needed packet. + */ + tx_pend = kzalloc(sizeof(struct ath9k_htc_tx_event), + GFP_ATOMIC); + if (!tx_pend) + continue; + + memcpy(&tx_pend->txs, __txs, + sizeof(struct __wmi_event_txstatus)); + + spin_lock(&priv->wmi->event_lock); + list_add_tail(&tx_pend->list, + &priv->wmi->pending_tx_events); + spin_unlock(&priv->wmi->event_lock); + + continue; + } + + ath9k_htc_tx_process(priv, skb, __txs); + } + + /* Wake TX queues if needed */ + ath9k_htc_check_wake_queues(priv); +} + +void ath9k_htc_txep(void *drv_priv, struct sk_buff *skb, + enum htc_endpoint_id ep_id, bool txok) +{ + struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) drv_priv; + struct ath9k_htc_tx_ctl *tx_ctl; + struct sk_buff_head *epid_queue; + + tx_ctl = HTC_SKB_CB(skb); + tx_ctl->txok = txok; + tx_ctl->timestamp = jiffies; + + if (!txok) { + skb_queue_tail(&priv->tx.tx_failed, skb); + tasklet_schedule(&priv->tx_failed_tasklet); + return; + } + + epid_queue = get_htc_epid_queue(priv, ep_id); + if (!epid_queue) { + dev_kfree_skb_any(skb); + return; + } + + skb_queue_tail(epid_queue, skb); +} + +static inline bool check_packet(struct ath9k_htc_priv *priv, struct sk_buff *skb) +{ + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_tx_ctl *tx_ctl; + + tx_ctl = HTC_SKB_CB(skb); + + if (time_after(jiffies, + tx_ctl->timestamp + + msecs_to_jiffies(ATH9K_HTC_TX_TIMEOUT_INTERVAL))) { + ath_dbg(common, XMIT, "Dropping a packet due to TX timeout\n"); + return true; + } + + return false; +} + +static void ath9k_htc_tx_cleanup_queue(struct ath9k_htc_priv *priv, + struct sk_buff_head *epid_queue) +{ + bool process = false; + unsigned long flags; + struct sk_buff *skb, *tmp; + struct sk_buff_head queue; + + skb_queue_head_init(&queue); + + spin_lock_irqsave(&epid_queue->lock, flags); + skb_queue_walk_safe(epid_queue, skb, tmp) { + if (check_packet(priv, skb)) { + __skb_unlink(skb, epid_queue); + __skb_queue_tail(&queue, skb); + process = true; + } + } + spin_unlock_irqrestore(&epid_queue->lock, flags); + + if (process) { + skb_queue_walk_safe(&queue, skb, tmp) { + __skb_unlink(skb, &queue); + ath9k_htc_tx_process(priv, skb, NULL); + } + } +} + +void ath9k_htc_tx_cleanup_timer(unsigned long data) +{ + struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) data; + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath9k_htc_tx_event *event, *tmp; + struct sk_buff *skb; + + spin_lock(&priv->wmi->event_lock); + list_for_each_entry_safe(event, tmp, &priv->wmi->pending_tx_events, list) { + + skb = ath9k_htc_tx_get_packet(priv, &event->txs); + if (skb) { + ath_dbg(common, XMIT, + "Found packet for cookie: %d, epid: %d\n", + event->txs.cookie, + MS(event->txs.ts_rate, ATH9K_HTC_TXSTAT_EPID)); + + ath9k_htc_tx_process(priv, skb, &event->txs); + list_del(&event->list); + kfree(event); + continue; + } + + if (++event->count >= ATH9K_HTC_TX_TIMEOUT_COUNT) { + list_del(&event->list); + kfree(event); + } + } + spin_unlock(&priv->wmi->event_lock); + + /* + * Check if status-pending packets have to be cleaned up. + */ + ath9k_htc_tx_cleanup_queue(priv, &priv->tx.mgmt_ep_queue); + ath9k_htc_tx_cleanup_queue(priv, &priv->tx.cab_ep_queue); + ath9k_htc_tx_cleanup_queue(priv, &priv->tx.data_be_queue); + ath9k_htc_tx_cleanup_queue(priv, &priv->tx.data_bk_queue); + ath9k_htc_tx_cleanup_queue(priv, &priv->tx.data_vi_queue); + ath9k_htc_tx_cleanup_queue(priv, &priv->tx.data_vo_queue); + + /* Wake TX queues if needed */ + ath9k_htc_check_wake_queues(priv); + + mod_timer(&priv->tx.cleanup_timer, + jiffies + msecs_to_jiffies(ATH9K_HTC_TX_CLEANUP_INTERVAL)); +} + +int ath9k_tx_init(struct ath9k_htc_priv *priv) +{ + skb_queue_head_init(&priv->tx.mgmt_ep_queue); + skb_queue_head_init(&priv->tx.cab_ep_queue); + skb_queue_head_init(&priv->tx.data_be_queue); + skb_queue_head_init(&priv->tx.data_bk_queue); + skb_queue_head_init(&priv->tx.data_vi_queue); + skb_queue_head_init(&priv->tx.data_vo_queue); + skb_queue_head_init(&priv->tx.tx_failed); + return 0; +} + +void ath9k_tx_cleanup(struct ath9k_htc_priv *priv) +{ + +} + +bool ath9k_htc_txq_setup(struct ath9k_htc_priv *priv, int subtype) +{ + struct ath_hw *ah = priv->ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_tx_queue_info qi; + int qnum; + + memset(&qi, 0, sizeof(qi)); + ATH9K_HTC_INIT_TXQ(subtype); + + qnum = ath9k_hw_setuptxqueue(priv->ah, ATH9K_TX_QUEUE_DATA, &qi); + if (qnum == -1) + return false; + + if (qnum >= ARRAY_SIZE(priv->hwq_map)) { + ath_err(common, "qnum %u out of range, max %zu!\n", + qnum, ARRAY_SIZE(priv->hwq_map)); + ath9k_hw_releasetxqueue(ah, qnum); + return false; + } + + priv->hwq_map[subtype] = qnum; + return true; +} + +int ath9k_htc_cabq_setup(struct ath9k_htc_priv *priv) +{ + struct ath9k_tx_queue_info qi; + + memset(&qi, 0, sizeof(qi)); + ATH9K_HTC_INIT_TXQ(0); + + return ath9k_hw_setuptxqueue(priv->ah, ATH9K_TX_QUEUE_CAB, &qi); +} + +/******/ +/* RX */ +/******/ + +/* + * Calculate the RX filter to be set in the HW. + */ +u32 ath9k_htc_calcrxfilter(struct ath9k_htc_priv *priv) +{ +#define RX_FILTER_PRESERVE (ATH9K_RX_FILTER_PHYERR | ATH9K_RX_FILTER_PHYRADAR) + + struct ath_hw *ah = priv->ah; + u32 rfilt; + + rfilt = (ath9k_hw_getrxfilter(ah) & RX_FILTER_PRESERVE) + | ATH9K_RX_FILTER_UCAST | ATH9K_RX_FILTER_BCAST + | ATH9K_RX_FILTER_MCAST; + + if (priv->rxfilter & FIF_PROBE_REQ) + rfilt |= ATH9K_RX_FILTER_PROBEREQ; + + /* + * Set promiscuous mode when FIF_PROMISC_IN_BSS is enabled for station + * mode interface or when in monitor mode. AP mode does not need this + * since it receives all in-BSS frames anyway. + */ + if (((ah->opmode != NL80211_IFTYPE_AP) && + (priv->rxfilter & FIF_PROMISC_IN_BSS)) || + ah->is_monitoring) + rfilt |= ATH9K_RX_FILTER_PROM; + + if (priv->rxfilter & FIF_CONTROL) + rfilt |= ATH9K_RX_FILTER_CONTROL; + + if ((ah->opmode == NL80211_IFTYPE_STATION) && + (priv->nvifs <= 1) && + !(priv->rxfilter & FIF_BCN_PRBRESP_PROMISC)) + rfilt |= ATH9K_RX_FILTER_MYBEACON; + else + rfilt |= ATH9K_RX_FILTER_BEACON; + + if (conf_is_ht(&priv->hw->conf)) { + rfilt |= ATH9K_RX_FILTER_COMP_BAR; + rfilt |= ATH9K_RX_FILTER_UNCOMP_BA_BAR; + } + + if (priv->rxfilter & FIF_PSPOLL) + rfilt |= ATH9K_RX_FILTER_PSPOLL; + + if (priv->nvifs > 1) + rfilt |= ATH9K_RX_FILTER_MCAST_BCAST_ALL; + + return rfilt; + +#undef RX_FILTER_PRESERVE +} + +/* + * Recv initialization for opmode change. + */ +static void ath9k_htc_opmode_init(struct ath9k_htc_priv *priv) +{ + struct ath_hw *ah = priv->ah; + u32 rfilt, mfilt[2]; + + /* configure rx filter */ + rfilt = ath9k_htc_calcrxfilter(priv); + ath9k_hw_setrxfilter(ah, rfilt); + + /* calculate and install multicast filter */ + mfilt[0] = mfilt[1] = ~0; + ath9k_hw_setmcastfilter(ah, mfilt[0], mfilt[1]); +} + +void ath9k_host_rx_init(struct ath9k_htc_priv *priv) +{ + ath9k_hw_rxena(priv->ah); + ath9k_htc_opmode_init(priv); + ath9k_hw_startpcureceive(priv->ah, test_bit(OP_SCANNING, &priv->op_flags)); + priv->rx.last_rssi = ATH_RSSI_DUMMY_MARKER; +} + +static void ath9k_process_rate(struct ieee80211_hw *hw, + struct ieee80211_rx_status *rxs, + u8 rx_rate, u8 rs_flags) +{ + struct ieee80211_supported_band *sband; + enum ieee80211_band band; + unsigned int i = 0; + + if (rx_rate & 0x80) { + /* HT rate */ + rxs->flag |= RX_FLAG_HT; + if (rs_flags & ATH9K_RX_2040) + rxs->flag |= RX_FLAG_40MHZ; + if (rs_flags & ATH9K_RX_GI) + rxs->flag |= RX_FLAG_SHORT_GI; + rxs->rate_idx = rx_rate & 0x7f; + return; + } + + band = hw->conf.chandef.chan->band; + sband = hw->wiphy->bands[band]; + + for (i = 0; i < sband->n_bitrates; i++) { + if (sband->bitrates[i].hw_value == rx_rate) { + rxs->rate_idx = i; + return; + } + if (sband->bitrates[i].hw_value_short == rx_rate) { + rxs->rate_idx = i; + rxs->flag |= RX_FLAG_SHORTPRE; + return; + } + } + +} + +static bool ath9k_rx_prepare(struct ath9k_htc_priv *priv, + struct ath9k_htc_rxbuf *rxbuf, + struct ieee80211_rx_status *rx_status) + +{ + struct ieee80211_hdr *hdr; + struct ieee80211_hw *hw = priv->hw; + struct sk_buff *skb = rxbuf->skb; + struct ath_common *common = ath9k_hw_common(priv->ah); + struct ath_htc_rx_status *rxstatus; + int hdrlen, padsize; + int last_rssi = ATH_RSSI_DUMMY_MARKER; + __le16 fc; + + if (skb->len < HTC_RX_FRAME_HEADER_SIZE) { + ath_err(common, "Corrupted RX frame, dropping (len: %d)\n", + skb->len); + goto rx_next; + } + + rxstatus = (struct ath_htc_rx_status *)skb->data; + + if (be16_to_cpu(rxstatus->rs_datalen) - + (skb->len - HTC_RX_FRAME_HEADER_SIZE) != 0) { + ath_err(common, + "Corrupted RX data len, dropping (dlen: %d, skblen: %d)\n", + rxstatus->rs_datalen, skb->len); + goto rx_next; + } + + ath9k_htc_err_stat_rx(priv, rxstatus); + + /* Get the RX status information */ + memcpy(&rxbuf->rxstatus, rxstatus, HTC_RX_FRAME_HEADER_SIZE); + skb_pull(skb, HTC_RX_FRAME_HEADER_SIZE); + + hdr = (struct ieee80211_hdr *)skb->data; + fc = hdr->frame_control; + hdrlen = ieee80211_get_hdrlen_from_skb(skb); + + padsize = hdrlen & 3; + if (padsize && skb->len >= hdrlen+padsize+FCS_LEN) { + memmove(skb->data + padsize, skb->data, hdrlen); + skb_pull(skb, padsize); + } + + memset(rx_status, 0, sizeof(struct ieee80211_rx_status)); + + if (rxbuf->rxstatus.rs_status != 0) { + if (rxbuf->rxstatus.rs_status & ATH9K_RXERR_CRC) + rx_status->flag |= RX_FLAG_FAILED_FCS_CRC; + if (rxbuf->rxstatus.rs_status & ATH9K_RXERR_PHY) + goto rx_next; + + if (rxbuf->rxstatus.rs_status & ATH9K_RXERR_DECRYPT) { + /* FIXME */ + } else if (rxbuf->rxstatus.rs_status & ATH9K_RXERR_MIC) { + if (ieee80211_is_ctl(fc)) + /* + * Sometimes, we get invalid + * MIC failures on valid control frames. + * Remove these mic errors. + */ + rxbuf->rxstatus.rs_status &= ~ATH9K_RXERR_MIC; + else + rx_status->flag |= RX_FLAG_MMIC_ERROR; + } + + /* + * Reject error frames with the exception of + * decryption and MIC failures. For monitor mode, + * we also ignore the CRC error. + */ + if (priv->ah->opmode == NL80211_IFTYPE_MONITOR) { + if (rxbuf->rxstatus.rs_status & + ~(ATH9K_RXERR_DECRYPT | ATH9K_RXERR_MIC | + ATH9K_RXERR_CRC)) + goto rx_next; + } else { + if (rxbuf->rxstatus.rs_status & + ~(ATH9K_RXERR_DECRYPT | ATH9K_RXERR_MIC)) { + goto rx_next; + } + } + } + + if (!(rxbuf->rxstatus.rs_status & ATH9K_RXERR_DECRYPT)) { + u8 keyix; + keyix = rxbuf->rxstatus.rs_keyix; + if (keyix != ATH9K_RXKEYIX_INVALID) { + rx_status->flag |= RX_FLAG_DECRYPTED; + } else if (ieee80211_has_protected(fc) && + skb->len >= hdrlen + 4) { + keyix = skb->data[hdrlen + 3] >> 6; + if (test_bit(keyix, common->keymap)) + rx_status->flag |= RX_FLAG_DECRYPTED; + } + } + + ath9k_process_rate(hw, rx_status, rxbuf->rxstatus.rs_rate, + rxbuf->rxstatus.rs_flags); + + if (rxbuf->rxstatus.rs_rssi != ATH9K_RSSI_BAD && + !rxbuf->rxstatus.rs_moreaggr) + ATH_RSSI_LPF(priv->rx.last_rssi, + rxbuf->rxstatus.rs_rssi); + + last_rssi = priv->rx.last_rssi; + + if (ieee80211_is_beacon(hdr->frame_control) && + !is_zero_ether_addr(common->curbssid) && + ether_addr_equal(hdr->addr3, common->curbssid)) { + s8 rssi = rxbuf->rxstatus.rs_rssi; + + if (likely(last_rssi != ATH_RSSI_DUMMY_MARKER)) + rssi = ATH_EP_RND(last_rssi, ATH_RSSI_EP_MULTIPLIER); + + if (rssi < 0) + rssi = 0; + + priv->ah->stats.avgbrssi = rssi; + } + + rx_status->mactime = be64_to_cpu(rxbuf->rxstatus.rs_tstamp); + rx_status->band = hw->conf.chandef.chan->band; + rx_status->freq = hw->conf.chandef.chan->center_freq; + rx_status->signal = rxbuf->rxstatus.rs_rssi + ATH_DEFAULT_NOISE_FLOOR; + rx_status->antenna = rxbuf->rxstatus.rs_antenna; + rx_status->flag |= RX_FLAG_MACTIME_END; + + return true; + +rx_next: + return false; +} + +/* + * FIXME: Handle FLUSH later on. + */ +void ath9k_rx_tasklet(unsigned long data) +{ + struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *)data; + struct ath9k_htc_rxbuf *rxbuf = NULL, *tmp_buf = NULL; + struct ieee80211_rx_status rx_status; + struct sk_buff *skb; + unsigned long flags; + struct ieee80211_hdr *hdr; + + do { + spin_lock_irqsave(&priv->rx.rxbuflock, flags); + list_for_each_entry(tmp_buf, &priv->rx.rxbuf, list) { + if (tmp_buf->in_process) { + rxbuf = tmp_buf; + break; + } + } + + if (rxbuf == NULL) { + spin_unlock_irqrestore(&priv->rx.rxbuflock, flags); + break; + } + + if (!rxbuf->skb) + goto requeue; + + if (!ath9k_rx_prepare(priv, rxbuf, &rx_status)) { + dev_kfree_skb_any(rxbuf->skb); + goto requeue; + } + + memcpy(IEEE80211_SKB_RXCB(rxbuf->skb), &rx_status, + sizeof(struct ieee80211_rx_status)); + skb = rxbuf->skb; + hdr = (struct ieee80211_hdr *) skb->data; + + if (ieee80211_is_beacon(hdr->frame_control) && priv->ps_enabled) + ieee80211_queue_work(priv->hw, &priv->ps_work); + + spin_unlock_irqrestore(&priv->rx.rxbuflock, flags); + + ieee80211_rx(priv->hw, skb); + + spin_lock_irqsave(&priv->rx.rxbuflock, flags); +requeue: + rxbuf->in_process = false; + rxbuf->skb = NULL; + list_move_tail(&rxbuf->list, &priv->rx.rxbuf); + rxbuf = NULL; + spin_unlock_irqrestore(&priv->rx.rxbuflock, flags); + } while (1); + +} + +void ath9k_htc_rxep(void *drv_priv, struct sk_buff *skb, + enum htc_endpoint_id ep_id) +{ + struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *)drv_priv; + struct ath_hw *ah = priv->ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_htc_rxbuf *rxbuf = NULL, *tmp_buf = NULL; + + spin_lock(&priv->rx.rxbuflock); + list_for_each_entry(tmp_buf, &priv->rx.rxbuf, list) { + if (!tmp_buf->in_process) { + rxbuf = tmp_buf; + break; + } + } + spin_unlock(&priv->rx.rxbuflock); + + if (rxbuf == NULL) { + ath_dbg(common, ANY, "No free RX buffer\n"); + goto err; + } + + spin_lock(&priv->rx.rxbuflock); + rxbuf->skb = skb; + rxbuf->in_process = true; + spin_unlock(&priv->rx.rxbuflock); + + tasklet_schedule(&priv->rx_tasklet); + return; +err: + dev_kfree_skb_any(skb); +} + +/* FIXME: Locking for cleanup/init */ + +void ath9k_rx_cleanup(struct ath9k_htc_priv *priv) +{ + struct ath9k_htc_rxbuf *rxbuf, *tbuf; + + list_for_each_entry_safe(rxbuf, tbuf, &priv->rx.rxbuf, list) { + list_del(&rxbuf->list); + if (rxbuf->skb) + dev_kfree_skb_any(rxbuf->skb); + kfree(rxbuf); + } +} + +int ath9k_rx_init(struct ath9k_htc_priv *priv) +{ + int i = 0; + + INIT_LIST_HEAD(&priv->rx.rxbuf); + spin_lock_init(&priv->rx.rxbuflock); + + for (i = 0; i < ATH9K_HTC_RXBUF; i++) { + struct ath9k_htc_rxbuf *rxbuf = + kzalloc(sizeof(struct ath9k_htc_rxbuf), GFP_KERNEL); + if (rxbuf == NULL) + goto err; + + list_add_tail(&rxbuf->list, &priv->rx.rxbuf); + } + + return 0; + +err: + ath9k_rx_cleanup(priv); + return -ENOMEM; +} diff --git a/drivers/net/wireless/ath/ath9k/htc_hst.c b/drivers/net/wireless/ath/ath9k/htc_hst.c new file mode 100644 index 0000000..aac4a40 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/htc_hst.c @@ -0,0 +1,479 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "htc.h" + +static int htc_issue_send(struct htc_target *target, struct sk_buff* skb, + u16 len, u8 flags, u8 epid) + +{ + struct htc_frame_hdr *hdr; + struct htc_endpoint *endpoint = &target->endpoint[epid]; + int status; + + hdr = (struct htc_frame_hdr *) + skb_push(skb, sizeof(struct htc_frame_hdr)); + hdr->endpoint_id = epid; + hdr->flags = flags; + hdr->payload_len = cpu_to_be16(len); + + status = target->hif->send(target->hif_dev, endpoint->ul_pipeid, skb); + + return status; +} + +static struct htc_endpoint *get_next_avail_ep(struct htc_endpoint *endpoint) +{ + enum htc_endpoint_id avail_epid; + + for (avail_epid = (ENDPOINT_MAX - 1); avail_epid > ENDPOINT0; avail_epid--) + if (endpoint[avail_epid].service_id == 0) + return &endpoint[avail_epid]; + return NULL; +} + +static u8 service_to_ulpipe(u16 service_id) +{ + switch (service_id) { + case WMI_CONTROL_SVC: + return 4; + case WMI_BEACON_SVC: + case WMI_CAB_SVC: + case WMI_UAPSD_SVC: + case WMI_MGMT_SVC: + case WMI_DATA_VO_SVC: + case WMI_DATA_VI_SVC: + case WMI_DATA_BE_SVC: + case WMI_DATA_BK_SVC: + return 1; + default: + return 0; + } +} + +static u8 service_to_dlpipe(u16 service_id) +{ + switch (service_id) { + case WMI_CONTROL_SVC: + return 3; + case WMI_BEACON_SVC: + case WMI_CAB_SVC: + case WMI_UAPSD_SVC: + case WMI_MGMT_SVC: + case WMI_DATA_VO_SVC: + case WMI_DATA_VI_SVC: + case WMI_DATA_BE_SVC: + case WMI_DATA_BK_SVC: + return 2; + default: + return 0; + } +} + +static void htc_process_target_rdy(struct htc_target *target, + void *buf) +{ + struct htc_endpoint *endpoint; + struct htc_ready_msg *htc_ready_msg = (struct htc_ready_msg *) buf; + + target->credit_size = be16_to_cpu(htc_ready_msg->credit_size); + + endpoint = &target->endpoint[ENDPOINT0]; + endpoint->service_id = HTC_CTRL_RSVD_SVC; + endpoint->max_msglen = HTC_MAX_CONTROL_MESSAGE_LENGTH; + atomic_inc(&target->tgt_ready); + complete(&target->target_wait); +} + +static void htc_process_conn_rsp(struct htc_target *target, + struct htc_frame_hdr *htc_hdr) +{ + struct htc_conn_svc_rspmsg *svc_rspmsg; + struct htc_endpoint *endpoint, *tmp_endpoint = NULL; + u16 service_id; + u16 max_msglen; + enum htc_endpoint_id epid, tepid; + + svc_rspmsg = (struct htc_conn_svc_rspmsg *) + ((void *) htc_hdr + sizeof(struct htc_frame_hdr)); + + if (svc_rspmsg->status == HTC_SERVICE_SUCCESS) { + epid = svc_rspmsg->endpoint_id; + service_id = be16_to_cpu(svc_rspmsg->service_id); + max_msglen = be16_to_cpu(svc_rspmsg->max_msg_len); + endpoint = &target->endpoint[epid]; + + for (tepid = (ENDPOINT_MAX - 1); tepid > ENDPOINT0; tepid--) { + tmp_endpoint = &target->endpoint[tepid]; + if (tmp_endpoint->service_id == service_id) { + tmp_endpoint->service_id = 0; + break; + } + } + + if (tepid == ENDPOINT0) + return; + + endpoint->service_id = service_id; + endpoint->max_txqdepth = tmp_endpoint->max_txqdepth; + endpoint->ep_callbacks = tmp_endpoint->ep_callbacks; + endpoint->ul_pipeid = tmp_endpoint->ul_pipeid; + endpoint->dl_pipeid = tmp_endpoint->dl_pipeid; + endpoint->max_msglen = max_msglen; + target->conn_rsp_epid = epid; + complete(&target->cmd_wait); + } else { + target->conn_rsp_epid = ENDPOINT_UNUSED; + } +} + +static int htc_config_pipe_credits(struct htc_target *target) +{ + struct sk_buff *skb; + struct htc_config_pipe_msg *cp_msg; + int ret, time_left; + + skb = alloc_skb(50 + sizeof(struct htc_frame_hdr), GFP_ATOMIC); + if (!skb) { + dev_err(target->dev, "failed to allocate send buffer\n"); + return -ENOMEM; + } + skb_reserve(skb, sizeof(struct htc_frame_hdr)); + + cp_msg = (struct htc_config_pipe_msg *) + skb_put(skb, sizeof(struct htc_config_pipe_msg)); + + cp_msg->message_id = cpu_to_be16(HTC_MSG_CONFIG_PIPE_ID); + cp_msg->pipe_id = USB_WLAN_TX_PIPE; + cp_msg->credits = target->credits; + + target->htc_flags |= HTC_OP_CONFIG_PIPE_CREDITS; + + ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0); + if (ret) + goto err; + + time_left = wait_for_completion_timeout(&target->cmd_wait, HZ); + if (!time_left) { + dev_err(target->dev, "HTC credit config timeout\n"); + return -ETIMEDOUT; + } + + return 0; +err: + kfree_skb(skb); + return -EINVAL; +} + +static int htc_setup_complete(struct htc_target *target) +{ + struct sk_buff *skb; + struct htc_comp_msg *comp_msg; + int ret = 0, time_left; + + skb = alloc_skb(50 + sizeof(struct htc_frame_hdr), GFP_ATOMIC); + if (!skb) { + dev_err(target->dev, "failed to allocate send buffer\n"); + return -ENOMEM; + } + skb_reserve(skb, sizeof(struct htc_frame_hdr)); + + comp_msg = (struct htc_comp_msg *) + skb_put(skb, sizeof(struct htc_comp_msg)); + comp_msg->msg_id = cpu_to_be16(HTC_MSG_SETUP_COMPLETE_ID); + + target->htc_flags |= HTC_OP_START_WAIT; + + ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0); + if (ret) + goto err; + + time_left = wait_for_completion_timeout(&target->cmd_wait, HZ); + if (!time_left) { + dev_err(target->dev, "HTC start timeout\n"); + return -ETIMEDOUT; + } + + return 0; + +err: + kfree_skb(skb); + return -EINVAL; +} + +/* HTC APIs */ + +int htc_init(struct htc_target *target) +{ + int ret; + + ret = htc_config_pipe_credits(target); + if (ret) + return ret; + + return htc_setup_complete(target); +} + +int htc_connect_service(struct htc_target *target, + struct htc_service_connreq *service_connreq, + enum htc_endpoint_id *conn_rsp_epid) +{ + struct sk_buff *skb; + struct htc_endpoint *endpoint; + struct htc_conn_svc_msg *conn_msg; + int ret, time_left; + + /* Find an available endpoint */ + endpoint = get_next_avail_ep(target->endpoint); + if (!endpoint) { + dev_err(target->dev, "Endpoint is not available for" + "service %d\n", service_connreq->service_id); + return -EINVAL; + } + + endpoint->service_id = service_connreq->service_id; + endpoint->max_txqdepth = service_connreq->max_send_qdepth; + endpoint->ul_pipeid = service_to_ulpipe(service_connreq->service_id); + endpoint->dl_pipeid = service_to_dlpipe(service_connreq->service_id); + endpoint->ep_callbacks = service_connreq->ep_callbacks; + + skb = alloc_skb(sizeof(struct htc_conn_svc_msg) + + sizeof(struct htc_frame_hdr), GFP_ATOMIC); + if (!skb) { + dev_err(target->dev, "Failed to allocate buf to send" + "service connect req\n"); + return -ENOMEM; + } + + skb_reserve(skb, sizeof(struct htc_frame_hdr)); + + conn_msg = (struct htc_conn_svc_msg *) + skb_put(skb, sizeof(struct htc_conn_svc_msg)); + conn_msg->service_id = cpu_to_be16(service_connreq->service_id); + conn_msg->msg_id = cpu_to_be16(HTC_MSG_CONNECT_SERVICE_ID); + conn_msg->con_flags = cpu_to_be16(service_connreq->con_flags); + conn_msg->dl_pipeid = endpoint->dl_pipeid; + conn_msg->ul_pipeid = endpoint->ul_pipeid; + + ret = htc_issue_send(target, skb, skb->len, 0, ENDPOINT0); + if (ret) + goto err; + + time_left = wait_for_completion_timeout(&target->cmd_wait, HZ); + if (!time_left) { + dev_err(target->dev, "Service connection timeout for: %d\n", + service_connreq->service_id); + return -ETIMEDOUT; + } + + *conn_rsp_epid = target->conn_rsp_epid; + return 0; +err: + kfree_skb(skb); + return ret; +} + +int htc_send(struct htc_target *target, struct sk_buff *skb) +{ + struct ath9k_htc_tx_ctl *tx_ctl; + + tx_ctl = HTC_SKB_CB(skb); + return htc_issue_send(target, skb, skb->len, 0, tx_ctl->epid); +} + +int htc_send_epid(struct htc_target *target, struct sk_buff *skb, + enum htc_endpoint_id epid) +{ + return htc_issue_send(target, skb, skb->len, 0, epid); +} + +void htc_stop(struct htc_target *target) +{ + target->hif->stop(target->hif_dev); +} + +void htc_start(struct htc_target *target) +{ + target->hif->start(target->hif_dev); +} + +void htc_sta_drain(struct htc_target *target, u8 idx) +{ + target->hif->sta_drain(target->hif_dev, idx); +} + +void ath9k_htc_txcompletion_cb(struct htc_target *htc_handle, + struct sk_buff *skb, bool txok) +{ + struct htc_endpoint *endpoint; + struct htc_frame_hdr *htc_hdr = NULL; + + if (htc_handle->htc_flags & HTC_OP_CONFIG_PIPE_CREDITS) { + complete(&htc_handle->cmd_wait); + htc_handle->htc_flags &= ~HTC_OP_CONFIG_PIPE_CREDITS; + goto ret; + } + + if (htc_handle->htc_flags & HTC_OP_START_WAIT) { + complete(&htc_handle->cmd_wait); + htc_handle->htc_flags &= ~HTC_OP_START_WAIT; + goto ret; + } + + if (skb) { + htc_hdr = (struct htc_frame_hdr *) skb->data; + endpoint = &htc_handle->endpoint[htc_hdr->endpoint_id]; + skb_pull(skb, sizeof(struct htc_frame_hdr)); + + if (endpoint->ep_callbacks.tx) { + endpoint->ep_callbacks.tx(endpoint->ep_callbacks.priv, + skb, htc_hdr->endpoint_id, + txok); + } else { + kfree_skb(skb); + } + } + + return; +ret: + /* HTC-generated packets are freed here. */ + if (htc_hdr && htc_hdr->endpoint_id != ENDPOINT0) + dev_kfree_skb_any(skb); + else + kfree_skb(skb); +} + +/* + * HTC Messages are handled directly here and the obtained SKB + * is freed. + * + * Service messages (Data, WMI) passed to the corresponding + * endpoint RX handlers, which have to free the SKB. + */ +void ath9k_htc_rx_msg(struct htc_target *htc_handle, + struct sk_buff *skb, u32 len, u8 pipe_id) +{ + struct htc_frame_hdr *htc_hdr; + enum htc_endpoint_id epid; + struct htc_endpoint *endpoint; + __be16 *msg_id; + + if (!htc_handle || !skb) + return; + + htc_hdr = (struct htc_frame_hdr *) skb->data; + epid = htc_hdr->endpoint_id; + + if (epid >= ENDPOINT_MAX) { + if (pipe_id != USB_REG_IN_PIPE) + dev_kfree_skb_any(skb); + else + kfree_skb(skb); + return; + } + + if (epid == ENDPOINT0) { + + /* Handle trailer */ + if (htc_hdr->flags & HTC_FLAGS_RECV_TRAILER) { + if (be32_to_cpu(*(__be32 *) skb->data) == 0x00C60000) + /* Move past the Watchdog pattern */ + htc_hdr = (struct htc_frame_hdr *)(skb->data + 4); + } + + /* Get the message ID */ + msg_id = (__be16 *) ((void *) htc_hdr + + sizeof(struct htc_frame_hdr)); + + /* Now process HTC messages */ + switch (be16_to_cpu(*msg_id)) { + case HTC_MSG_READY_ID: + htc_process_target_rdy(htc_handle, htc_hdr); + break; + case HTC_MSG_CONNECT_SERVICE_RESPONSE_ID: + htc_process_conn_rsp(htc_handle, htc_hdr); + break; + default: + break; + } + + kfree_skb(skb); + + } else { + if (htc_hdr->flags & HTC_FLAGS_RECV_TRAILER) + skb_trim(skb, len - htc_hdr->control[0]); + + skb_pull(skb, sizeof(struct htc_frame_hdr)); + + endpoint = &htc_handle->endpoint[epid]; + if (endpoint->ep_callbacks.rx) + endpoint->ep_callbacks.rx(endpoint->ep_callbacks.priv, + skb, epid); + } +} + +struct htc_target *ath9k_htc_hw_alloc(void *hif_handle, + struct ath9k_htc_hif *hif, + struct device *dev) +{ + struct htc_endpoint *endpoint; + struct htc_target *target; + + target = kzalloc(sizeof(struct htc_target), GFP_KERNEL); + if (!target) + return NULL; + + init_completion(&target->target_wait); + init_completion(&target->cmd_wait); + + target->hif = hif; + target->hif_dev = hif_handle; + target->dev = dev; + + /* Assign control endpoint pipe IDs */ + endpoint = &target->endpoint[ENDPOINT0]; + endpoint->ul_pipeid = hif->control_ul_pipe; + endpoint->dl_pipeid = hif->control_dl_pipe; + + atomic_set(&target->tgt_ready, 0); + + return target; +} + +void ath9k_htc_hw_free(struct htc_target *htc) +{ + kfree(htc); +} + +int ath9k_htc_hw_init(struct htc_target *target, + struct device *dev, u16 devid, + char *product, u32 drv_info) +{ + if (ath9k_htc_probe_device(target, dev, devid, product, drv_info)) { + pr_err("Failed to initialize the device\n"); + return -ENODEV; + } + + return 0; +} + +void ath9k_htc_hw_deinit(struct htc_target *target, bool hot_unplug) +{ + if (target) + ath9k_htc_disconnect_device(target, hot_unplug); +} diff --git a/drivers/net/wireless/ath/ath9k/htc_hst.h b/drivers/net/wireless/ath/ath9k/htc_hst.h new file mode 100644 index 0000000..e1ffbb6 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/htc_hst.h @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef HTC_HST_H +#define HTC_HST_H + +struct ath9k_htc_priv; +struct htc_target; +struct ath9k_htc_tx_ctl; + +enum ath9k_hif_transports { + ATH9K_HIF_USB, +}; + +struct ath9k_htc_hif { + struct list_head list; + const enum ath9k_hif_transports transport; + const char *name; + + u8 control_dl_pipe; + u8 control_ul_pipe; + + void (*start) (void *hif_handle); + void (*stop) (void *hif_handle); + void (*sta_drain) (void *hif_handle, u8 idx); + int (*send) (void *hif_handle, u8 pipe, struct sk_buff *buf); +}; + +enum htc_endpoint_id { + ENDPOINT_UNUSED = -1, + ENDPOINT0 = 0, + ENDPOINT1 = 1, + ENDPOINT2 = 2, + ENDPOINT3 = 3, + ENDPOINT4 = 4, + ENDPOINT5 = 5, + ENDPOINT6 = 6, + ENDPOINT7 = 7, + ENDPOINT8 = 8, + ENDPOINT_MAX = 22 +}; + +/* Htc frame hdr flags */ +#define HTC_FLAGS_RECV_TRAILER (1 << 1) + +struct htc_frame_hdr { + u8 endpoint_id; + u8 flags; + __be16 payload_len; + u8 control[4]; +} __packed; + +struct htc_ready_msg { + __be16 message_id; + __be16 credits; + __be16 credit_size; + u8 max_endpoints; + u8 pad; +} __packed; + +struct htc_config_pipe_msg { + __be16 message_id; + u8 pipe_id; + u8 credits; +} __packed; + +struct htc_ep_callbacks { + void *priv; + void (*tx) (void *, struct sk_buff *, enum htc_endpoint_id, bool txok); + void (*rx) (void *, struct sk_buff *, enum htc_endpoint_id); +}; + +struct htc_endpoint { + u16 service_id; + + struct htc_ep_callbacks ep_callbacks; + u32 max_txqdepth; + int max_msglen; + + u8 ul_pipeid; + u8 dl_pipeid; +}; + +#define HTC_MAX_CONTROL_MESSAGE_LENGTH 255 +#define HTC_CONTROL_BUFFER_SIZE \ + (HTC_MAX_CONTROL_MESSAGE_LENGTH + sizeof(struct htc_frame_hdr)) + +#define HTC_OP_START_WAIT BIT(0) +#define HTC_OP_CONFIG_PIPE_CREDITS BIT(1) + +struct htc_target { + void *hif_dev; + struct ath9k_htc_priv *drv_priv; + struct device *dev; + struct ath9k_htc_hif *hif; + struct htc_endpoint endpoint[ENDPOINT_MAX]; + struct completion target_wait; + struct completion cmd_wait; + struct list_head list; + enum htc_endpoint_id conn_rsp_epid; + u16 credits; + u16 credit_size; + u8 htc_flags; + atomic_t tgt_ready; +}; + +enum htc_msg_id { + HTC_MSG_READY_ID = 1, + HTC_MSG_CONNECT_SERVICE_ID, + HTC_MSG_CONNECT_SERVICE_RESPONSE_ID, + HTC_MSG_SETUP_COMPLETE_ID, + HTC_MSG_CONFIG_PIPE_ID, + HTC_MSG_CONFIG_PIPE_RESPONSE_ID, +}; + +struct htc_service_connreq { + u16 service_id; + u16 con_flags; + u32 max_send_qdepth; + struct htc_ep_callbacks ep_callbacks; +}; + +/* Current service IDs */ + +enum htc_service_group_ids{ + RSVD_SERVICE_GROUP = 0, + WMI_SERVICE_GROUP = 1, + + HTC_SERVICE_GROUP_LAST = 255 +}; + +#define MAKE_SERVICE_ID(group, index) \ + (int)(((int)group << 8) | (int)(index)) + +/* NOTE: service ID of 0x0000 is reserved and should never be used */ +#define HTC_CTRL_RSVD_SVC MAKE_SERVICE_ID(RSVD_SERVICE_GROUP, 1) +#define HTC_LOOPBACK_RSVD_SVC MAKE_SERVICE_ID(RSVD_SERVICE_GROUP, 2) + +#define WMI_CONTROL_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 0) +#define WMI_BEACON_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 1) +#define WMI_CAB_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 2) +#define WMI_UAPSD_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 3) +#define WMI_MGMT_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 4) +#define WMI_DATA_VO_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 5) +#define WMI_DATA_VI_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 6) +#define WMI_DATA_BE_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 7) +#define WMI_DATA_BK_SVC MAKE_SERVICE_ID(WMI_SERVICE_GROUP, 8) + +struct htc_conn_svc_msg { + __be16 msg_id; + __be16 service_id; + __be16 con_flags; + u8 dl_pipeid; + u8 ul_pipeid; + u8 svc_meta_len; + u8 pad; +} __packed; + +/* connect response status codes */ +#define HTC_SERVICE_SUCCESS 0 +#define HTC_SERVICE_NOT_FOUND 1 +#define HTC_SERVICE_FAILED 2 +#define HTC_SERVICE_NO_RESOURCES 3 +#define HTC_SERVICE_NO_MORE_EP 4 + +struct htc_conn_svc_rspmsg { + __be16 msg_id; + __be16 service_id; + u8 status; + u8 endpoint_id; + __be16 max_msg_len; + u8 svc_meta_len; + u8 pad; +} __packed; + +struct htc_comp_msg { + __be16 msg_id; +} __packed; + +int htc_init(struct htc_target *target); +int htc_connect_service(struct htc_target *target, + struct htc_service_connreq *service_connreq, + enum htc_endpoint_id *conn_rsp_eid); +int htc_send(struct htc_target *target, struct sk_buff *skb); +int htc_send_epid(struct htc_target *target, struct sk_buff *skb, + enum htc_endpoint_id epid); +void htc_stop(struct htc_target *target); +void htc_start(struct htc_target *target); +void htc_sta_drain(struct htc_target *target, u8 idx); + +void ath9k_htc_rx_msg(struct htc_target *htc_handle, + struct sk_buff *skb, u32 len, u8 pipe_id); +void ath9k_htc_txcompletion_cb(struct htc_target *htc_handle, + struct sk_buff *skb, bool txok); + +struct htc_target *ath9k_htc_hw_alloc(void *hif_handle, + struct ath9k_htc_hif *hif, + struct device *dev); +void ath9k_htc_hw_free(struct htc_target *htc); +int ath9k_htc_hw_init(struct htc_target *target, + struct device *dev, u16 devid, char *product, + u32 drv_info); +void ath9k_htc_hw_deinit(struct htc_target *target, bool hot_unplug); + +#endif /* HTC_HST_H */ diff --git a/drivers/net/wireless/ath/ath9k/hw-ops.h b/drivers/net/wireless/ath/ath9k/hw-ops.h new file mode 100644 index 0000000..14b7011 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/hw-ops.h @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef ATH9K_HW_OPS_H +#define ATH9K_HW_OPS_H + +#include "hw.h" + +/* Hardware core and driver accessible callbacks */ + +static inline void ath9k_hw_configpcipowersave(struct ath_hw *ah, + bool power_off) +{ + if (!ah->aspm_enabled) + return; + + ath9k_hw_ops(ah)->config_pci_powersave(ah, power_off); +} + +static inline void ath9k_hw_rxena(struct ath_hw *ah) +{ + ath9k_hw_ops(ah)->rx_enable(ah); +} + +static inline void ath9k_hw_set_desc_link(struct ath_hw *ah, void *ds, + u32 link) +{ + ath9k_hw_ops(ah)->set_desc_link(ds, link); +} + +static inline bool ath9k_hw_calibrate(struct ath_hw *ah, + struct ath9k_channel *chan, + u8 rxchainmask, + bool longcal) +{ + return ath9k_hw_ops(ah)->calibrate(ah, chan, rxchainmask, longcal); +} + +static inline bool ath9k_hw_getisr(struct ath_hw *ah, enum ath9k_int *masked) +{ + return ath9k_hw_ops(ah)->get_isr(ah, masked); +} + +static inline void ath9k_hw_set_txdesc(struct ath_hw *ah, void *ds, + struct ath_tx_info *i) +{ + return ath9k_hw_ops(ah)->set_txdesc(ah, ds, i); +} + +static inline int ath9k_hw_txprocdesc(struct ath_hw *ah, void *ds, + struct ath_tx_status *ts) +{ + return ath9k_hw_ops(ah)->proc_txdesc(ah, ds, ts); +} + +static inline void ath9k_hw_antdiv_comb_conf_get(struct ath_hw *ah, + struct ath_hw_antcomb_conf *antconf) +{ + ath9k_hw_ops(ah)->antdiv_comb_conf_get(ah, antconf); +} + +static inline void ath9k_hw_antdiv_comb_conf_set(struct ath_hw *ah, + struct ath_hw_antcomb_conf *antconf) +{ + ath9k_hw_ops(ah)->antdiv_comb_conf_set(ah, antconf); +} + +static inline void ath9k_hw_antctrl_shared_chain_lnadiv(struct ath_hw *ah, + bool enable) +{ + if (ath9k_hw_ops(ah)->antctrl_shared_chain_lnadiv) + ath9k_hw_ops(ah)->antctrl_shared_chain_lnadiv(ah, enable); +} + +/* Private hardware call ops */ + +/* PHY ops */ + +static inline int ath9k_hw_rf_set_freq(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + return ath9k_hw_private_ops(ah)->rf_set_freq(ah, chan); +} + +static inline void ath9k_hw_spur_mitigate_freq(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + ath9k_hw_private_ops(ah)->spur_mitigate_freq(ah, chan); +} + +static inline bool ath9k_hw_set_rf_regs(struct ath_hw *ah, + struct ath9k_channel *chan, + u16 modesIndex) +{ + if (!ath9k_hw_private_ops(ah)->set_rf_regs) + return true; + + return ath9k_hw_private_ops(ah)->set_rf_regs(ah, chan, modesIndex); +} + +static inline void ath9k_hw_init_bb(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + return ath9k_hw_private_ops(ah)->init_bb(ah, chan); +} + +static inline void ath9k_hw_set_channel_regs(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + return ath9k_hw_private_ops(ah)->set_channel_regs(ah, chan); +} + +static inline int ath9k_hw_process_ini(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + return ath9k_hw_private_ops(ah)->process_ini(ah, chan); +} + +static inline void ath9k_olc_init(struct ath_hw *ah) +{ + if (!ath9k_hw_private_ops(ah)->olc_init) + return; + + return ath9k_hw_private_ops(ah)->olc_init(ah); +} + +static inline void ath9k_hw_set_rfmode(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + return ath9k_hw_private_ops(ah)->set_rfmode(ah, chan); +} + +static inline void ath9k_hw_mark_phy_inactive(struct ath_hw *ah) +{ + return ath9k_hw_private_ops(ah)->mark_phy_inactive(ah); +} + +static inline void ath9k_hw_set_delta_slope(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + return ath9k_hw_private_ops(ah)->set_delta_slope(ah, chan); +} + +static inline bool ath9k_hw_rfbus_req(struct ath_hw *ah) +{ + return ath9k_hw_private_ops(ah)->rfbus_req(ah); +} + +static inline void ath9k_hw_rfbus_done(struct ath_hw *ah) +{ + return ath9k_hw_private_ops(ah)->rfbus_done(ah); +} + +static inline void ath9k_hw_restore_chainmask(struct ath_hw *ah) +{ + if (!ath9k_hw_private_ops(ah)->restore_chainmask) + return; + + return ath9k_hw_private_ops(ah)->restore_chainmask(ah); +} + +static inline bool ath9k_hw_ani_control(struct ath_hw *ah, + enum ath9k_ani_cmd cmd, int param) +{ + return ath9k_hw_private_ops(ah)->ani_control(ah, cmd, param); +} + +static inline void ath9k_hw_do_getnf(struct ath_hw *ah, + int16_t nfarray[NUM_NF_READINGS]) +{ + ath9k_hw_private_ops(ah)->do_getnf(ah, nfarray); +} + +static inline bool ath9k_hw_init_cal(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + return ath9k_hw_private_ops(ah)->init_cal(ah, chan); +} + +static inline void ath9k_hw_setup_calibration(struct ath_hw *ah, + struct ath9k_cal_list *currCal) +{ + ath9k_hw_private_ops(ah)->setup_calibration(ah, currCal); +} + +static inline int ath9k_hw_fast_chan_change(struct ath_hw *ah, + struct ath9k_channel *chan, + u8 *ini_reloaded) +{ + return ath9k_hw_private_ops(ah)->fast_chan_change(ah, chan, + ini_reloaded); +} + +static inline void ath9k_hw_set_radar_params(struct ath_hw *ah) +{ + if (!ath9k_hw_private_ops(ah)->set_radar_params) + return; + + ath9k_hw_private_ops(ah)->set_radar_params(ah, &ah->radar_conf); +} + +#endif /* ATH9K_HW_OPS_H */ diff --git a/drivers/net/wireless/ath/ath9k/hw.c b/drivers/net/wireless/ath/ath9k/hw.c new file mode 100644 index 0000000..52a8770 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/hw.c @@ -0,0 +1,3262 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include "hw.h" +#include "hw-ops.h" +#include "rc.h" +#include "ar9003_mac.h" +#include "ar9003_mci.h" +#include "ar9003_phy.h" +#include "debug.h" +#include "ath9k.h" + +static bool ath9k_hw_set_reset_reg(struct ath_hw *ah, u32 type); + +MODULE_AUTHOR("Atheros Communications"); +MODULE_DESCRIPTION("Support for Atheros 802.11n wireless LAN cards."); +MODULE_SUPPORTED_DEVICE("Atheros 802.11n WLAN cards"); +MODULE_LICENSE("Dual BSD/GPL"); + +static int __init ath9k_init(void) +{ + return 0; +} +module_init(ath9k_init); + +static void __exit ath9k_exit(void) +{ + return; +} +module_exit(ath9k_exit); + +/* Private hardware callbacks */ + +static void ath9k_hw_init_cal_settings(struct ath_hw *ah) +{ + ath9k_hw_private_ops(ah)->init_cal_settings(ah); +} + +static u32 ath9k_hw_compute_pll_control(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + return ath9k_hw_private_ops(ah)->compute_pll_control(ah, chan); +} + +static void ath9k_hw_init_mode_gain_regs(struct ath_hw *ah) +{ + if (!ath9k_hw_private_ops(ah)->init_mode_gain_regs) + return; + + ath9k_hw_private_ops(ah)->init_mode_gain_regs(ah); +} + +static void ath9k_hw_ani_cache_ini_regs(struct ath_hw *ah) +{ + /* You will not have this callback if using the old ANI */ + if (!ath9k_hw_private_ops(ah)->ani_cache_ini_regs) + return; + + ath9k_hw_private_ops(ah)->ani_cache_ini_regs(ah); +} + +/********************/ +/* Helper Functions */ +/********************/ + +#ifdef CPTCFG_ATH9K_DEBUGFS + +void ath9k_debug_sync_cause(struct ath_common *common, u32 sync_cause) +{ + struct ath_softc *sc = common->priv; + if (sync_cause) + sc->debug.stats.istats.sync_cause_all++; + if (sync_cause & AR_INTR_SYNC_RTC_IRQ) + sc->debug.stats.istats.sync_rtc_irq++; + if (sync_cause & AR_INTR_SYNC_MAC_IRQ) + sc->debug.stats.istats.sync_mac_irq++; + if (sync_cause & AR_INTR_SYNC_EEPROM_ILLEGAL_ACCESS) + sc->debug.stats.istats.eeprom_illegal_access++; + if (sync_cause & AR_INTR_SYNC_APB_TIMEOUT) + sc->debug.stats.istats.apb_timeout++; + if (sync_cause & AR_INTR_SYNC_PCI_MODE_CONFLICT) + sc->debug.stats.istats.pci_mode_conflict++; + if (sync_cause & AR_INTR_SYNC_HOST1_FATAL) + sc->debug.stats.istats.host1_fatal++; + if (sync_cause & AR_INTR_SYNC_HOST1_PERR) + sc->debug.stats.istats.host1_perr++; + if (sync_cause & AR_INTR_SYNC_TRCV_FIFO_PERR) + sc->debug.stats.istats.trcv_fifo_perr++; + if (sync_cause & AR_INTR_SYNC_RADM_CPL_EP) + sc->debug.stats.istats.radm_cpl_ep++; + if (sync_cause & AR_INTR_SYNC_RADM_CPL_DLLP_ABORT) + sc->debug.stats.istats.radm_cpl_dllp_abort++; + if (sync_cause & AR_INTR_SYNC_RADM_CPL_TLP_ABORT) + sc->debug.stats.istats.radm_cpl_tlp_abort++; + if (sync_cause & AR_INTR_SYNC_RADM_CPL_ECRC_ERR) + sc->debug.stats.istats.radm_cpl_ecrc_err++; + if (sync_cause & AR_INTR_SYNC_RADM_CPL_TIMEOUT) + sc->debug.stats.istats.radm_cpl_timeout++; + if (sync_cause & AR_INTR_SYNC_LOCAL_TIMEOUT) + sc->debug.stats.istats.local_timeout++; + if (sync_cause & AR_INTR_SYNC_PM_ACCESS) + sc->debug.stats.istats.pm_access++; + if (sync_cause & AR_INTR_SYNC_MAC_AWAKE) + sc->debug.stats.istats.mac_awake++; + if (sync_cause & AR_INTR_SYNC_MAC_ASLEEP) + sc->debug.stats.istats.mac_asleep++; + if (sync_cause & AR_INTR_SYNC_MAC_SLEEP_ACCESS) + sc->debug.stats.istats.mac_sleep_access++; +} +#endif + + +static void ath9k_hw_set_clockrate(struct ath_hw *ah) +{ + struct ieee80211_conf *conf = &ath9k_hw_common(ah)->hw->conf; + struct ath_common *common = ath9k_hw_common(ah); + unsigned int clockrate; + + /* AR9287 v1.3+ uses async FIFO and runs the MAC at 117 MHz */ + if (AR_SREV_9287(ah) && AR_SREV_9287_13_OR_LATER(ah)) + clockrate = 117; + else if (!ah->curchan) /* should really check for CCK instead */ + clockrate = ATH9K_CLOCK_RATE_CCK; + else if (conf->chandef.chan->band == IEEE80211_BAND_2GHZ) + clockrate = ATH9K_CLOCK_RATE_2GHZ_OFDM; + else if (ah->caps.hw_caps & ATH9K_HW_CAP_FASTCLOCK) + clockrate = ATH9K_CLOCK_FAST_RATE_5GHZ_OFDM; + else + clockrate = ATH9K_CLOCK_RATE_5GHZ_OFDM; + + if (conf_is_ht40(conf)) + clockrate *= 2; + + if (ah->curchan) { + if (IS_CHAN_HALF_RATE(ah->curchan)) + clockrate /= 2; + if (IS_CHAN_QUARTER_RATE(ah->curchan)) + clockrate /= 4; + } + + common->clockrate = clockrate; +} + +static u32 ath9k_hw_mac_to_clks(struct ath_hw *ah, u32 usecs) +{ + struct ath_common *common = ath9k_hw_common(ah); + + return usecs * common->clockrate; +} + +bool ath9k_hw_wait(struct ath_hw *ah, u32 reg, u32 mask, u32 val, u32 timeout) +{ + int i; + + BUG_ON(timeout < AH_TIME_QUANTUM); + + for (i = 0; i < (timeout / AH_TIME_QUANTUM); i++) { + if ((REG_READ(ah, reg) & mask) == val) + return true; + + udelay(AH_TIME_QUANTUM); + } + + ath_dbg(ath9k_hw_common(ah), ANY, + "timeout (%d us) on reg 0x%x: 0x%08x & 0x%08x != 0x%08x\n", + timeout, reg, REG_READ(ah, reg), mask, val); + + return false; +} +EXPORT_SYMBOL(ath9k_hw_wait); + +void ath9k_hw_synth_delay(struct ath_hw *ah, struct ath9k_channel *chan, + int hw_delay) +{ + if (IS_CHAN_B(chan)) + hw_delay = (4 * hw_delay) / 22; + else + hw_delay /= 10; + + if (IS_CHAN_HALF_RATE(chan)) + hw_delay *= 2; + else if (IS_CHAN_QUARTER_RATE(chan)) + hw_delay *= 4; + + udelay(hw_delay + BASE_ACTIVATE_DELAY); +} + +void ath9k_hw_write_array(struct ath_hw *ah, const struct ar5416IniArray *array, + int column, unsigned int *writecnt) +{ + int r; + + ENABLE_REGWRITE_BUFFER(ah); + for (r = 0; r < array->ia_rows; r++) { + REG_WRITE(ah, INI_RA(array, r, 0), + INI_RA(array, r, column)); + DO_DELAY(*writecnt); + } + REGWRITE_BUFFER_FLUSH(ah); +} + +u32 ath9k_hw_reverse_bits(u32 val, u32 n) +{ + u32 retval; + int i; + + for (i = 0, retval = 0; i < n; i++) { + retval = (retval << 1) | (val & 1); + val >>= 1; + } + return retval; +} + +u16 ath9k_hw_computetxtime(struct ath_hw *ah, + u8 phy, int kbps, + u32 frameLen, u16 rateix, + bool shortPreamble) +{ + u32 bitsPerSymbol, numBits, numSymbols, phyTime, txTime; + + if (kbps == 0) + return 0; + + switch (phy) { + case WLAN_RC_PHY_CCK: + phyTime = CCK_PREAMBLE_BITS + CCK_PLCP_BITS; + if (shortPreamble) + phyTime >>= 1; + numBits = frameLen << 3; + txTime = CCK_SIFS_TIME + phyTime + ((numBits * 1000) / kbps); + break; + case WLAN_RC_PHY_OFDM: + if (ah->curchan && IS_CHAN_QUARTER_RATE(ah->curchan)) { + bitsPerSymbol = (kbps * OFDM_SYMBOL_TIME_QUARTER) / 1000; + numBits = OFDM_PLCP_BITS + (frameLen << 3); + numSymbols = DIV_ROUND_UP(numBits, bitsPerSymbol); + txTime = OFDM_SIFS_TIME_QUARTER + + OFDM_PREAMBLE_TIME_QUARTER + + (numSymbols * OFDM_SYMBOL_TIME_QUARTER); + } else if (ah->curchan && + IS_CHAN_HALF_RATE(ah->curchan)) { + bitsPerSymbol = (kbps * OFDM_SYMBOL_TIME_HALF) / 1000; + numBits = OFDM_PLCP_BITS + (frameLen << 3); + numSymbols = DIV_ROUND_UP(numBits, bitsPerSymbol); + txTime = OFDM_SIFS_TIME_HALF + + OFDM_PREAMBLE_TIME_HALF + + (numSymbols * OFDM_SYMBOL_TIME_HALF); + } else { + bitsPerSymbol = (kbps * OFDM_SYMBOL_TIME) / 1000; + numBits = OFDM_PLCP_BITS + (frameLen << 3); + numSymbols = DIV_ROUND_UP(numBits, bitsPerSymbol); + txTime = OFDM_SIFS_TIME + OFDM_PREAMBLE_TIME + + (numSymbols * OFDM_SYMBOL_TIME); + } + break; + default: + ath_err(ath9k_hw_common(ah), + "Unknown phy %u (rate ix %u)\n", phy, rateix); + txTime = 0; + break; + } + + return txTime; +} +EXPORT_SYMBOL(ath9k_hw_computetxtime); + +void ath9k_hw_get_channel_centers(struct ath_hw *ah, + struct ath9k_channel *chan, + struct chan_centers *centers) +{ + int8_t extoff; + + if (!IS_CHAN_HT40(chan)) { + centers->ctl_center = centers->ext_center = + centers->synth_center = chan->channel; + return; + } + + if ((chan->chanmode == CHANNEL_A_HT40PLUS) || + (chan->chanmode == CHANNEL_G_HT40PLUS)) { + centers->synth_center = + chan->channel + HT40_CHANNEL_CENTER_SHIFT; + extoff = 1; + } else { + centers->synth_center = + chan->channel - HT40_CHANNEL_CENTER_SHIFT; + extoff = -1; + } + + centers->ctl_center = + centers->synth_center - (extoff * HT40_CHANNEL_CENTER_SHIFT); + /* 25 MHz spacing is supported by hw but not on upper layers */ + centers->ext_center = + centers->synth_center + (extoff * HT40_CHANNEL_CENTER_SHIFT); +} + +/******************/ +/* Chip Revisions */ +/******************/ + +static void ath9k_hw_read_revisions(struct ath_hw *ah) +{ + u32 val; + + switch (ah->hw_version.devid) { + case AR5416_AR9100_DEVID: + ah->hw_version.macVersion = AR_SREV_VERSION_9100; + break; + case AR9300_DEVID_AR9330: + ah->hw_version.macVersion = AR_SREV_VERSION_9330; + if (ah->get_mac_revision) { + ah->hw_version.macRev = ah->get_mac_revision(); + } else { + val = REG_READ(ah, AR_SREV); + ah->hw_version.macRev = MS(val, AR_SREV_REVISION2); + } + return; + case AR9300_DEVID_AR9340: + ah->hw_version.macVersion = AR_SREV_VERSION_9340; + val = REG_READ(ah, AR_SREV); + ah->hw_version.macRev = MS(val, AR_SREV_REVISION2); + return; + case AR9300_DEVID_QCA955X: + ah->hw_version.macVersion = AR_SREV_VERSION_9550; + return; + } + + val = REG_READ(ah, AR_SREV) & AR_SREV_ID; + + if (val == 0xFF) { + val = REG_READ(ah, AR_SREV); + ah->hw_version.macVersion = + (val & AR_SREV_VERSION2) >> AR_SREV_TYPE2_S; + ah->hw_version.macRev = MS(val, AR_SREV_REVISION2); + + if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) + ah->is_pciexpress = true; + else + ah->is_pciexpress = (val & + AR_SREV_TYPE2_HOST_MODE) ? 0 : 1; + } else { + if (!AR_SREV_9100(ah)) + ah->hw_version.macVersion = MS(val, AR_SREV_VERSION); + + ah->hw_version.macRev = val & AR_SREV_REVISION; + + if (ah->hw_version.macVersion == AR_SREV_VERSION_5416_PCIE) + ah->is_pciexpress = true; + } +} + +/************************************/ +/* HW Attach, Detach, Init Routines */ +/************************************/ + +static void ath9k_hw_disablepcie(struct ath_hw *ah) +{ + if (!AR_SREV_5416(ah)) + return; + + REG_WRITE(ah, AR_PCIE_SERDES, 0x9248fc00); + REG_WRITE(ah, AR_PCIE_SERDES, 0x24924924); + REG_WRITE(ah, AR_PCIE_SERDES, 0x28000029); + REG_WRITE(ah, AR_PCIE_SERDES, 0x57160824); + REG_WRITE(ah, AR_PCIE_SERDES, 0x25980579); + REG_WRITE(ah, AR_PCIE_SERDES, 0x00000000); + REG_WRITE(ah, AR_PCIE_SERDES, 0x1aaabe40); + REG_WRITE(ah, AR_PCIE_SERDES, 0xbe105554); + REG_WRITE(ah, AR_PCIE_SERDES, 0x000e1007); + + REG_WRITE(ah, AR_PCIE_SERDES2, 0x00000000); +} + +/* This should work for all families including legacy */ +static bool ath9k_hw_chip_test(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + u32 regAddr[2] = { AR_STA_ID0 }; + u32 regHold[2]; + static const u32 patternData[4] = { + 0x55555555, 0xaaaaaaaa, 0x66666666, 0x99999999 + }; + int i, j, loop_max; + + if (!AR_SREV_9300_20_OR_LATER(ah)) { + loop_max = 2; + regAddr[1] = AR_PHY_BASE + (8 << 2); + } else + loop_max = 1; + + for (i = 0; i < loop_max; i++) { + u32 addr = regAddr[i]; + u32 wrData, rdData; + + regHold[i] = REG_READ(ah, addr); + for (j = 0; j < 0x100; j++) { + wrData = (j << 16) | j; + REG_WRITE(ah, addr, wrData); + rdData = REG_READ(ah, addr); + if (rdData != wrData) { + ath_err(common, + "address test failed addr: 0x%08x - wr:0x%08x != rd:0x%08x\n", + addr, wrData, rdData); + return false; + } + } + for (j = 0; j < 4; j++) { + wrData = patternData[j]; + REG_WRITE(ah, addr, wrData); + rdData = REG_READ(ah, addr); + if (wrData != rdData) { + ath_err(common, + "address test failed addr: 0x%08x - wr:0x%08x != rd:0x%08x\n", + addr, wrData, rdData); + return false; + } + } + REG_WRITE(ah, regAddr[i], regHold[i]); + } + udelay(100); + + return true; +} + +static void ath9k_hw_init_config(struct ath_hw *ah) +{ + int i; + + ah->config.dma_beacon_response_time = 1; + ah->config.sw_beacon_response_time = 6; + ah->config.additional_swba_backoff = 0; + ah->config.ack_6mb = 0x0; + ah->config.cwm_ignore_extcca = 0; + ah->config.pcie_clock_req = 0; + ah->config.pcie_waen = 0; + ah->config.analog_shiftreg = 1; + ah->config.enable_ani = true; + + for (i = 0; i < AR_EEPROM_MODAL_SPURS; i++) { + ah->config.spurchans[i][0] = AR_NO_SPUR; + ah->config.spurchans[i][1] = AR_NO_SPUR; + } + + ah->config.rx_intr_mitigation = true; + ah->config.pcieSerDesWrite = true; + + /* + * We need this for PCI devices only (Cardbus, PCI, miniPCI) + * _and_ if on non-uniprocessor systems (Multiprocessor/HT). + * This means we use it for all AR5416 devices, and the few + * minor PCI AR9280 devices out there. + * + * Serialization is required because these devices do not handle + * well the case of two concurrent reads/writes due to the latency + * involved. During one read/write another read/write can be issued + * on another CPU while the previous read/write may still be working + * on our hardware, if we hit this case the hardware poops in a loop. + * We prevent this by serializing reads and writes. + * + * This issue is not present on PCI-Express devices or pre-AR5416 + * devices (legacy, 802.11abg). + */ + if (num_possible_cpus() > 1) + ah->config.serialize_regmode = SER_REG_MODE_AUTO; +} + +static void ath9k_hw_init_defaults(struct ath_hw *ah) +{ + struct ath_regulatory *regulatory = ath9k_hw_regulatory(ah); + + regulatory->country_code = CTRY_DEFAULT; + regulatory->power_limit = MAX_RATE_POWER; + + ah->hw_version.magic = AR5416_MAGIC; + ah->hw_version.subvendorid = 0; + + ah->atim_window = 0; + ah->sta_id1_defaults = + AR_STA_ID1_CRPT_MIC_ENABLE | + AR_STA_ID1_MCAST_KSRCH; + if (AR_SREV_9100(ah)) + ah->sta_id1_defaults |= AR_STA_ID1_AR9100_BA_FIX; + ah->slottime = ATH9K_SLOT_TIME_9; + ah->globaltxtimeout = (u32) -1; + ah->power_mode = ATH9K_PM_UNDEFINED; + ah->htc_reset_init = true; +} + +static int ath9k_hw_init_macaddr(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + u32 sum; + int i; + u16 eeval; + static const u32 EEP_MAC[] = { EEP_MAC_LSW, EEP_MAC_MID, EEP_MAC_MSW }; + + sum = 0; + for (i = 0; i < 3; i++) { + eeval = ah->eep_ops->get_eeprom(ah, EEP_MAC[i]); + sum += eeval; + common->macaddr[2 * i] = eeval >> 8; + common->macaddr[2 * i + 1] = eeval & 0xff; + } + if (sum == 0 || sum == 0xffff * 3) + return -EADDRNOTAVAIL; + + return 0; +} + +static int ath9k_hw_post_init(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + int ecode; + + if (common->bus_ops->ath_bus_type != ATH_USB) { + if (!ath9k_hw_chip_test(ah)) + return -ENODEV; + } + + if (!AR_SREV_9300_20_OR_LATER(ah)) { + ecode = ar9002_hw_rf_claim(ah); + if (ecode != 0) + return ecode; + } + + ecode = ath9k_hw_eeprom_init(ah); + if (ecode != 0) + return ecode; + + ath_dbg(ath9k_hw_common(ah), CONFIG, "Eeprom VER: %d, REV: %d\n", + ah->eep_ops->get_eeprom_ver(ah), + ah->eep_ops->get_eeprom_rev(ah)); + + if (ah->config.enable_ani) + ath9k_hw_ani_init(ah); + + return 0; +} + +static int ath9k_hw_attach_ops(struct ath_hw *ah) +{ + if (!AR_SREV_9300_20_OR_LATER(ah)) + return ar9002_hw_attach_ops(ah); + + ar9003_hw_attach_ops(ah); + return 0; +} + +/* Called for all hardware families */ +static int __ath9k_hw_init(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + int r = 0; + + ath9k_hw_read_revisions(ah); + + /* + * Read back AR_WA into a permanent copy and set bits 14 and 17. + * We need to do this to avoid RMW of this register. We cannot + * read the reg when chip is asleep. + */ + ah->WARegVal = REG_READ(ah, AR_WA); + ah->WARegVal |= (AR_WA_D3_L1_DISABLE | + AR_WA_ASPM_TIMER_BASED_DISABLE); + + if (!ath9k_hw_set_reset_reg(ah, ATH9K_RESET_POWER_ON)) { + ath_err(common, "Couldn't reset chip\n"); + return -EIO; + } + + if (AR_SREV_9462(ah)) + ah->WARegVal &= ~AR_WA_D3_L1_DISABLE; + + if (AR_SREV_9565(ah)) { + ah->WARegVal |= AR_WA_BIT22; + REG_WRITE(ah, AR_WA, ah->WARegVal); + } + + ath9k_hw_init_defaults(ah); + ath9k_hw_init_config(ah); + + r = ath9k_hw_attach_ops(ah); + if (r) + return r; + + if (!ath9k_hw_setpower(ah, ATH9K_PM_AWAKE)) { + ath_err(common, "Couldn't wakeup chip\n"); + return -EIO; + } + + if (NR_CPUS > 1 && ah->config.serialize_regmode == SER_REG_MODE_AUTO) { + if (ah->hw_version.macVersion == AR_SREV_VERSION_5416_PCI || + ((AR_SREV_9160(ah) || AR_SREV_9280(ah) || AR_SREV_9287(ah)) && + !ah->is_pciexpress)) { + ah->config.serialize_regmode = + SER_REG_MODE_ON; + } else { + ah->config.serialize_regmode = + SER_REG_MODE_OFF; + } + } + + ath_dbg(common, RESET, "serialize_regmode is %d\n", + ah->config.serialize_regmode); + + if (AR_SREV_9285(ah) || AR_SREV_9271(ah)) + ah->config.max_txtrig_level = MAX_TX_FIFO_THRESHOLD >> 1; + else + ah->config.max_txtrig_level = MAX_TX_FIFO_THRESHOLD; + + switch (ah->hw_version.macVersion) { + case AR_SREV_VERSION_5416_PCI: + case AR_SREV_VERSION_5416_PCIE: + case AR_SREV_VERSION_9160: + case AR_SREV_VERSION_9100: + case AR_SREV_VERSION_9280: + case AR_SREV_VERSION_9285: + case AR_SREV_VERSION_9287: + case AR_SREV_VERSION_9271: + case AR_SREV_VERSION_9300: + case AR_SREV_VERSION_9330: + case AR_SREV_VERSION_9485: + case AR_SREV_VERSION_9340: + case AR_SREV_VERSION_9462: + case AR_SREV_VERSION_9550: + case AR_SREV_VERSION_9565: + break; + default: + ath_err(common, + "Mac Chip Rev 0x%02x.%x is not supported by this driver\n", + ah->hw_version.macVersion, ah->hw_version.macRev); + return -EOPNOTSUPP; + } + + if (AR_SREV_9271(ah) || AR_SREV_9100(ah) || AR_SREV_9340(ah) || + AR_SREV_9330(ah) || AR_SREV_9550(ah)) + ah->is_pciexpress = false; + + ah->hw_version.phyRev = REG_READ(ah, AR_PHY_CHIP_ID); + ath9k_hw_init_cal_settings(ah); + + ah->ani_function = ATH9K_ANI_ALL; + if (AR_SREV_9280_20_OR_LATER(ah) && !AR_SREV_9300_20_OR_LATER(ah)) + ah->ani_function &= ~ATH9K_ANI_NOISE_IMMUNITY_LEVEL; + if (!AR_SREV_9300_20_OR_LATER(ah)) + ah->ani_function &= ~ATH9K_ANI_MRC_CCK; + + if (!ah->is_pciexpress) + ath9k_hw_disablepcie(ah); + + r = ath9k_hw_post_init(ah); + if (r) + return r; + + ath9k_hw_init_mode_gain_regs(ah); + r = ath9k_hw_fill_cap_info(ah); + if (r) + return r; + + r = ath9k_hw_init_macaddr(ah); + if (r) { + ath_err(common, "Failed to initialize MAC address\n"); + return r; + } + + if (AR_SREV_9285(ah) || AR_SREV_9271(ah)) + ah->tx_trig_level = (AR_FTRIG_256B >> AR_FTRIG_S); + else + ah->tx_trig_level = (AR_FTRIG_512B >> AR_FTRIG_S); + + if (AR_SREV_9330(ah)) + ah->bb_watchdog_timeout_ms = 85; + else + ah->bb_watchdog_timeout_ms = 25; + + common->state = ATH_HW_INITIALIZED; + + return 0; +} + +int ath9k_hw_init(struct ath_hw *ah) +{ + int ret; + struct ath_common *common = ath9k_hw_common(ah); + + /* These are all the AR5008/AR9001/AR9002/AR9003 hardware family of chipsets */ + switch (ah->hw_version.devid) { + case AR5416_DEVID_PCI: + case AR5416_DEVID_PCIE: + case AR5416_AR9100_DEVID: + case AR9160_DEVID_PCI: + case AR9280_DEVID_PCI: + case AR9280_DEVID_PCIE: + case AR9285_DEVID_PCIE: + case AR9287_DEVID_PCI: + case AR9287_DEVID_PCIE: + case AR2427_DEVID_PCIE: + case AR9300_DEVID_PCIE: + case AR9300_DEVID_AR9485_PCIE: + case AR9300_DEVID_AR9330: + case AR9300_DEVID_AR9340: + case AR9300_DEVID_QCA955X: + case AR9300_DEVID_AR9580: + case AR9300_DEVID_AR9462: + case AR9485_DEVID_AR1111: + case AR9300_DEVID_AR9565: + break; + default: + if (common->bus_ops->ath_bus_type == ATH_USB) + break; + ath_err(common, "Hardware device ID 0x%04x not supported\n", + ah->hw_version.devid); + return -EOPNOTSUPP; + } + + ret = __ath9k_hw_init(ah); + if (ret) { + ath_err(common, + "Unable to initialize hardware; initialization status: %d\n", + ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(ath9k_hw_init); + +static void ath9k_hw_init_qos(struct ath_hw *ah) +{ + ENABLE_REGWRITE_BUFFER(ah); + + REG_WRITE(ah, AR_MIC_QOS_CONTROL, 0x100aa); + REG_WRITE(ah, AR_MIC_QOS_SELECT, 0x3210); + + REG_WRITE(ah, AR_QOS_NO_ACK, + SM(2, AR_QOS_NO_ACK_TWO_BIT) | + SM(5, AR_QOS_NO_ACK_BIT_OFF) | + SM(0, AR_QOS_NO_ACK_BYTE_OFF)); + + REG_WRITE(ah, AR_TXOP_X, AR_TXOP_X_VAL); + REG_WRITE(ah, AR_TXOP_0_3, 0xFFFFFFFF); + REG_WRITE(ah, AR_TXOP_4_7, 0xFFFFFFFF); + REG_WRITE(ah, AR_TXOP_8_11, 0xFFFFFFFF); + REG_WRITE(ah, AR_TXOP_12_15, 0xFFFFFFFF); + + REGWRITE_BUFFER_FLUSH(ah); +} + +u32 ar9003_get_pll_sqsum_dvc(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + int i = 0; + + REG_CLR_BIT(ah, PLL3, PLL3_DO_MEAS_MASK); + udelay(100); + REG_SET_BIT(ah, PLL3, PLL3_DO_MEAS_MASK); + + while ((REG_READ(ah, PLL4) & PLL4_MEAS_DONE) == 0) { + + udelay(100); + + if (WARN_ON_ONCE(i >= 100)) { + ath_err(common, "PLL4 meaurement not done\n"); + break; + } + + i++; + } + + return (REG_READ(ah, PLL3) & SQSUM_DVC_MASK) >> 3; +} +EXPORT_SYMBOL(ar9003_get_pll_sqsum_dvc); + +static void ath9k_hw_init_pll(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + u32 pll; + + if (AR_SREV_9485(ah) || AR_SREV_9565(ah)) { + /* program BB PLL ki and kd value, ki=0x4, kd=0x40 */ + REG_RMW_FIELD(ah, AR_CH0_BB_DPLL2, + AR_CH0_BB_DPLL2_PLL_PWD, 0x1); + REG_RMW_FIELD(ah, AR_CH0_BB_DPLL2, + AR_CH0_DPLL2_KD, 0x40); + REG_RMW_FIELD(ah, AR_CH0_BB_DPLL2, + AR_CH0_DPLL2_KI, 0x4); + + REG_RMW_FIELD(ah, AR_CH0_BB_DPLL1, + AR_CH0_BB_DPLL1_REFDIV, 0x5); + REG_RMW_FIELD(ah, AR_CH0_BB_DPLL1, + AR_CH0_BB_DPLL1_NINI, 0x58); + REG_RMW_FIELD(ah, AR_CH0_BB_DPLL1, + AR_CH0_BB_DPLL1_NFRAC, 0x0); + + REG_RMW_FIELD(ah, AR_CH0_BB_DPLL2, + AR_CH0_BB_DPLL2_OUTDIV, 0x1); + REG_RMW_FIELD(ah, AR_CH0_BB_DPLL2, + AR_CH0_BB_DPLL2_LOCAL_PLL, 0x1); + REG_RMW_FIELD(ah, AR_CH0_BB_DPLL2, + AR_CH0_BB_DPLL2_EN_NEGTRIG, 0x1); + + /* program BB PLL phase_shift to 0x6 */ + REG_RMW_FIELD(ah, AR_CH0_BB_DPLL3, + AR_CH0_BB_DPLL3_PHASE_SHIFT, 0x6); + + REG_RMW_FIELD(ah, AR_CH0_BB_DPLL2, + AR_CH0_BB_DPLL2_PLL_PWD, 0x0); + udelay(1000); + } else if (AR_SREV_9330(ah)) { + u32 ddr_dpll2, pll_control2, kd; + + if (ah->is_clk_25mhz) { + ddr_dpll2 = 0x18e82f01; + pll_control2 = 0xe04a3d; + kd = 0x1d; + } else { + ddr_dpll2 = 0x19e82f01; + pll_control2 = 0x886666; + kd = 0x3d; + } + + /* program DDR PLL ki and kd value */ + REG_WRITE(ah, AR_CH0_DDR_DPLL2, ddr_dpll2); + + /* program DDR PLL phase_shift */ + REG_RMW_FIELD(ah, AR_CH0_DDR_DPLL3, + AR_CH0_DPLL3_PHASE_SHIFT, 0x1); + + REG_WRITE(ah, AR_RTC_PLL_CONTROL, 0x1142c); + udelay(1000); + + /* program refdiv, nint, frac to RTC register */ + REG_WRITE(ah, AR_RTC_PLL_CONTROL2, pll_control2); + + /* program BB PLL kd and ki value */ + REG_RMW_FIELD(ah, AR_CH0_BB_DPLL2, AR_CH0_DPLL2_KD, kd); + REG_RMW_FIELD(ah, AR_CH0_BB_DPLL2, AR_CH0_DPLL2_KI, 0x06); + + /* program BB PLL phase_shift */ + REG_RMW_FIELD(ah, AR_CH0_BB_DPLL3, + AR_CH0_BB_DPLL3_PHASE_SHIFT, 0x1); + } else if (AR_SREV_9340(ah) || AR_SREV_9550(ah)) { + u32 regval, pll2_divint, pll2_divfrac, refdiv; + + REG_WRITE(ah, AR_RTC_PLL_CONTROL, 0x1142c); + udelay(1000); + + REG_SET_BIT(ah, AR_PHY_PLL_MODE, 0x1 << 16); + udelay(100); + + if (ah->is_clk_25mhz) { + pll2_divint = 0x54; + pll2_divfrac = 0x1eb85; + refdiv = 3; + } else { + if (AR_SREV_9340(ah)) { + pll2_divint = 88; + pll2_divfrac = 0; + refdiv = 5; + } else { + pll2_divint = 0x11; + pll2_divfrac = 0x26666; + refdiv = 1; + } + } + + regval = REG_READ(ah, AR_PHY_PLL_MODE); + regval |= (0x1 << 16); + REG_WRITE(ah, AR_PHY_PLL_MODE, regval); + udelay(100); + + REG_WRITE(ah, AR_PHY_PLL_CONTROL, (refdiv << 27) | + (pll2_divint << 18) | pll2_divfrac); + udelay(100); + + regval = REG_READ(ah, AR_PHY_PLL_MODE); + if (AR_SREV_9340(ah)) + regval = (regval & 0x80071fff) | (0x1 << 30) | + (0x1 << 13) | (0x4 << 26) | (0x18 << 19); + else + regval = (regval & 0x80071fff) | (0x3 << 30) | + (0x1 << 13) | (0x4 << 26) | (0x60 << 19); + REG_WRITE(ah, AR_PHY_PLL_MODE, regval); + REG_WRITE(ah, AR_PHY_PLL_MODE, + REG_READ(ah, AR_PHY_PLL_MODE) & 0xfffeffff); + udelay(1000); + } + + pll = ath9k_hw_compute_pll_control(ah, chan); + if (AR_SREV_9565(ah)) + pll |= 0x40000; + REG_WRITE(ah, AR_RTC_PLL_CONTROL, pll); + + if (AR_SREV_9485(ah) || AR_SREV_9340(ah) || AR_SREV_9330(ah) || + AR_SREV_9550(ah)) + udelay(1000); + + /* Switch the core clock for ar9271 to 117Mhz */ + if (AR_SREV_9271(ah)) { + udelay(500); + REG_WRITE(ah, 0x50040, 0x304); + } + + udelay(RTC_PLL_SETTLE_DELAY); + + REG_WRITE(ah, AR_RTC_SLEEP_CLK, AR_RTC_FORCE_DERIVED_CLK); + + if (AR_SREV_9340(ah) || AR_SREV_9550(ah)) { + if (ah->is_clk_25mhz) { + REG_WRITE(ah, AR_RTC_DERIVED_CLK, 0x17c << 1); + REG_WRITE(ah, AR_SLP32_MODE, 0x0010f3d7); + REG_WRITE(ah, AR_SLP32_INC, 0x0001e7ae); + } else { + REG_WRITE(ah, AR_RTC_DERIVED_CLK, 0x261 << 1); + REG_WRITE(ah, AR_SLP32_MODE, 0x0010f400); + REG_WRITE(ah, AR_SLP32_INC, 0x0001e800); + } + udelay(100); + } +} + +static void ath9k_hw_init_interrupt_masks(struct ath_hw *ah, + enum nl80211_iftype opmode) +{ + u32 sync_default = AR_INTR_SYNC_DEFAULT; + u32 imr_reg = AR_IMR_TXERR | + AR_IMR_TXURN | + AR_IMR_RXERR | + AR_IMR_RXORN | + AR_IMR_BCNMISC; + + if (AR_SREV_9340(ah) || AR_SREV_9550(ah)) + sync_default &= ~AR_INTR_SYNC_HOST1_FATAL; + + if (AR_SREV_9300_20_OR_LATER(ah)) { + imr_reg |= AR_IMR_RXOK_HP; + if (ah->config.rx_intr_mitigation) + imr_reg |= AR_IMR_RXINTM | AR_IMR_RXMINTR; + else + imr_reg |= AR_IMR_RXOK_LP; + + } else { + if (ah->config.rx_intr_mitigation) + imr_reg |= AR_IMR_RXINTM | AR_IMR_RXMINTR; + else + imr_reg |= AR_IMR_RXOK; + } + + if (ah->config.tx_intr_mitigation) + imr_reg |= AR_IMR_TXINTM | AR_IMR_TXMINTR; + else + imr_reg |= AR_IMR_TXOK; + + ENABLE_REGWRITE_BUFFER(ah); + + REG_WRITE(ah, AR_IMR, imr_reg); + ah->imrs2_reg |= AR_IMR_S2_GTT; + REG_WRITE(ah, AR_IMR_S2, ah->imrs2_reg); + + if (!AR_SREV_9100(ah)) { + REG_WRITE(ah, AR_INTR_SYNC_CAUSE, 0xFFFFFFFF); + REG_WRITE(ah, AR_INTR_SYNC_ENABLE, sync_default); + REG_WRITE(ah, AR_INTR_SYNC_MASK, 0); + } + + REGWRITE_BUFFER_FLUSH(ah); + + if (AR_SREV_9300_20_OR_LATER(ah)) { + REG_WRITE(ah, AR_INTR_PRIO_ASYNC_ENABLE, 0); + REG_WRITE(ah, AR_INTR_PRIO_ASYNC_MASK, 0); + REG_WRITE(ah, AR_INTR_PRIO_SYNC_ENABLE, 0); + REG_WRITE(ah, AR_INTR_PRIO_SYNC_MASK, 0); + } +} + +static void ath9k_hw_set_sifs_time(struct ath_hw *ah, u32 us) +{ + u32 val = ath9k_hw_mac_to_clks(ah, us - 2); + val = min(val, (u32) 0xFFFF); + REG_WRITE(ah, AR_D_GBL_IFS_SIFS, val); +} + +static void ath9k_hw_setslottime(struct ath_hw *ah, u32 us) +{ + u32 val = ath9k_hw_mac_to_clks(ah, us); + val = min(val, (u32) 0xFFFF); + REG_WRITE(ah, AR_D_GBL_IFS_SLOT, val); +} + +static void ath9k_hw_set_ack_timeout(struct ath_hw *ah, u32 us) +{ + u32 val = ath9k_hw_mac_to_clks(ah, us); + val = min(val, (u32) MS(0xFFFFFFFF, AR_TIME_OUT_ACK)); + REG_RMW_FIELD(ah, AR_TIME_OUT, AR_TIME_OUT_ACK, val); +} + +static void ath9k_hw_set_cts_timeout(struct ath_hw *ah, u32 us) +{ + u32 val = ath9k_hw_mac_to_clks(ah, us); + val = min(val, (u32) MS(0xFFFFFFFF, AR_TIME_OUT_CTS)); + REG_RMW_FIELD(ah, AR_TIME_OUT, AR_TIME_OUT_CTS, val); +} + +static bool ath9k_hw_set_global_txtimeout(struct ath_hw *ah, u32 tu) +{ + if (tu > 0xFFFF) { + ath_dbg(ath9k_hw_common(ah), XMIT, "bad global tx timeout %u\n", + tu); + ah->globaltxtimeout = (u32) -1; + return false; + } else { + REG_RMW_FIELD(ah, AR_GTXTO, AR_GTXTO_TIMEOUT_LIMIT, tu); + ah->globaltxtimeout = tu; + return true; + } +} + +void ath9k_hw_init_global_settings(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ieee80211_conf *conf = &common->hw->conf; + const struct ath9k_channel *chan = ah->curchan; + int acktimeout, ctstimeout, ack_offset = 0; + int slottime; + int sifstime; + int rx_lat = 0, tx_lat = 0, eifs = 0; + u32 reg; + + ath_dbg(ath9k_hw_common(ah), RESET, "ah->misc_mode 0x%x\n", + ah->misc_mode); + + if (!chan) + return; + + if (ah->misc_mode != 0) + REG_SET_BIT(ah, AR_PCU_MISC, ah->misc_mode); + + if (IS_CHAN_A_FAST_CLOCK(ah, chan)) + rx_lat = 41; + else + rx_lat = 37; + tx_lat = 54; + + if (IS_CHAN_5GHZ(chan)) + sifstime = 16; + else + sifstime = 10; + + if (IS_CHAN_HALF_RATE(chan)) { + eifs = 175; + rx_lat *= 2; + tx_lat *= 2; + if (IS_CHAN_A_FAST_CLOCK(ah, chan)) + tx_lat += 11; + + sifstime *= 2; + ack_offset = 16; + slottime = 13; + } else if (IS_CHAN_QUARTER_RATE(chan)) { + eifs = 340; + rx_lat = (rx_lat * 4) - 1; + tx_lat *= 4; + if (IS_CHAN_A_FAST_CLOCK(ah, chan)) + tx_lat += 22; + + sifstime *= 4; + ack_offset = 32; + slottime = 21; + } else { + if (AR_SREV_9287(ah) && AR_SREV_9287_13_OR_LATER(ah)) { + eifs = AR_D_GBL_IFS_EIFS_ASYNC_FIFO; + reg = AR_USEC_ASYNC_FIFO; + } else { + eifs = REG_READ(ah, AR_D_GBL_IFS_EIFS)/ + common->clockrate; + reg = REG_READ(ah, AR_USEC); + } + rx_lat = MS(reg, AR_USEC_RX_LAT); + tx_lat = MS(reg, AR_USEC_TX_LAT); + + slottime = ah->slottime; + } + + /* As defined by IEEE 802.11-2007 17.3.8.6 */ + slottime += 3 * ah->coverage_class; + acktimeout = slottime + sifstime + ack_offset; + ctstimeout = acktimeout; + + /* + * Workaround for early ACK timeouts, add an offset to match the + * initval's 64us ack timeout value. Use 48us for the CTS timeout. + * This was initially only meant to work around an issue with delayed + * BA frames in some implementations, but it has been found to fix ACK + * timeout issues in other cases as well. + */ + if (conf->chandef.chan && + conf->chandef.chan->band == IEEE80211_BAND_2GHZ && + !IS_CHAN_HALF_RATE(chan) && !IS_CHAN_QUARTER_RATE(chan)) { + acktimeout += 64 - sifstime - ah->slottime; + ctstimeout += 48 - sifstime - ah->slottime; + } + + + ath9k_hw_set_sifs_time(ah, sifstime); + ath9k_hw_setslottime(ah, slottime); + ath9k_hw_set_ack_timeout(ah, acktimeout); + ath9k_hw_set_cts_timeout(ah, ctstimeout); + if (ah->globaltxtimeout != (u32) -1) + ath9k_hw_set_global_txtimeout(ah, ah->globaltxtimeout); + + REG_WRITE(ah, AR_D_GBL_IFS_EIFS, ath9k_hw_mac_to_clks(ah, eifs)); + REG_RMW(ah, AR_USEC, + (common->clockrate - 1) | + SM(rx_lat, AR_USEC_RX_LAT) | + SM(tx_lat, AR_USEC_TX_LAT), + AR_USEC_TX_LAT | AR_USEC_RX_LAT | AR_USEC_USEC); + +} +EXPORT_SYMBOL(ath9k_hw_init_global_settings); + +void ath9k_hw_deinit(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + + if (common->state < ATH_HW_INITIALIZED) + return; + + ath9k_hw_setpower(ah, ATH9K_PM_FULL_SLEEP); +} +EXPORT_SYMBOL(ath9k_hw_deinit); + +/*******/ +/* INI */ +/*******/ + +u32 ath9k_regd_get_ctl(struct ath_regulatory *reg, struct ath9k_channel *chan) +{ + u32 ctl = ath_regd_get_band_ctl(reg, chan->chan->band); + + if (IS_CHAN_B(chan)) + ctl |= CTL_11B; + else if (IS_CHAN_G(chan)) + ctl |= CTL_11G; + else + ctl |= CTL_11A; + + return ctl; +} + +/****************************************/ +/* Reset and Channel Switching Routines */ +/****************************************/ + +static inline void ath9k_hw_set_dma(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + int txbuf_size; + + ENABLE_REGWRITE_BUFFER(ah); + + /* + * set AHB_MODE not to do cacheline prefetches + */ + if (!AR_SREV_9300_20_OR_LATER(ah)) + REG_SET_BIT(ah, AR_AHB_MODE, AR_AHB_PREFETCH_RD_EN); + + /* + * let mac dma reads be in 128 byte chunks + */ + REG_RMW(ah, AR_TXCFG, AR_TXCFG_DMASZ_128B, AR_TXCFG_DMASZ_MASK); + + REGWRITE_BUFFER_FLUSH(ah); + + /* + * Restore TX Trigger Level to its pre-reset value. + * The initial value depends on whether aggregation is enabled, and is + * adjusted whenever underruns are detected. + */ + if (!AR_SREV_9300_20_OR_LATER(ah)) + REG_RMW_FIELD(ah, AR_TXCFG, AR_FTRIG, ah->tx_trig_level); + + ENABLE_REGWRITE_BUFFER(ah); + + /* + * let mac dma writes be in 128 byte chunks + */ + REG_RMW(ah, AR_RXCFG, AR_RXCFG_DMASZ_128B, AR_RXCFG_DMASZ_MASK); + + /* + * Setup receive FIFO threshold to hold off TX activities + */ + REG_WRITE(ah, AR_RXFIFO_CFG, 0x200); + + if (AR_SREV_9300_20_OR_LATER(ah)) { + REG_RMW_FIELD(ah, AR_RXBP_THRESH, AR_RXBP_THRESH_HP, 0x1); + REG_RMW_FIELD(ah, AR_RXBP_THRESH, AR_RXBP_THRESH_LP, 0x1); + + ath9k_hw_set_rx_bufsize(ah, common->rx_bufsize - + ah->caps.rx_status_len); + } + + /* + * reduce the number of usable entries in PCU TXBUF to avoid + * wrap around issues. + */ + if (AR_SREV_9285(ah)) { + /* For AR9285 the number of Fifos are reduced to half. + * So set the usable tx buf size also to half to + * avoid data/delimiter underruns + */ + txbuf_size = AR_9285_PCU_TXBUF_CTRL_USABLE_SIZE; + } else if (AR_SREV_9340_13_OR_LATER(ah)) { + /* Uses fewer entries for AR934x v1.3+ to prevent rx overruns */ + txbuf_size = AR_9340_PCU_TXBUF_CTRL_USABLE_SIZE; + } else { + txbuf_size = AR_PCU_TXBUF_CTRL_USABLE_SIZE; + } + + if (!AR_SREV_9271(ah)) + REG_WRITE(ah, AR_PCU_TXBUF_CTRL, txbuf_size); + + REGWRITE_BUFFER_FLUSH(ah); + + if (AR_SREV_9300_20_OR_LATER(ah)) + ath9k_hw_reset_txstatus_ring(ah); +} + +static void ath9k_hw_set_operating_mode(struct ath_hw *ah, int opmode) +{ + u32 mask = AR_STA_ID1_STA_AP | AR_STA_ID1_ADHOC; + u32 set = AR_STA_ID1_KSRCH_MODE; + + switch (opmode) { + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_MESH_POINT: + set |= AR_STA_ID1_ADHOC; + REG_SET_BIT(ah, AR_CFG, AR_CFG_AP_ADHOC_INDICATION); + break; + case NL80211_IFTYPE_AP: + set |= AR_STA_ID1_STA_AP; + /* fall through */ + case NL80211_IFTYPE_STATION: + REG_CLR_BIT(ah, AR_CFG, AR_CFG_AP_ADHOC_INDICATION); + break; + default: + if (!ah->is_monitoring) + set = 0; + break; + } + REG_RMW(ah, AR_STA_ID1, set, mask); +} + +void ath9k_hw_get_delta_slope_vals(struct ath_hw *ah, u32 coef_scaled, + u32 *coef_mantissa, u32 *coef_exponent) +{ + u32 coef_exp, coef_man; + + for (coef_exp = 31; coef_exp > 0; coef_exp--) + if ((coef_scaled >> coef_exp) & 0x1) + break; + + coef_exp = 14 - (coef_exp - COEF_SCALE_S); + + coef_man = coef_scaled + (1 << (COEF_SCALE_S - coef_exp - 1)); + + *coef_mantissa = coef_man >> (COEF_SCALE_S - coef_exp); + *coef_exponent = coef_exp - 16; +} + +static bool ath9k_hw_set_reset(struct ath_hw *ah, int type) +{ + u32 rst_flags; + u32 tmpReg; + + if (AR_SREV_9100(ah)) { + REG_RMW_FIELD(ah, AR_RTC_DERIVED_CLK, + AR_RTC_DERIVED_CLK_PERIOD, 1); + (void)REG_READ(ah, AR_RTC_DERIVED_CLK); + } + + ENABLE_REGWRITE_BUFFER(ah); + + if (AR_SREV_9300_20_OR_LATER(ah)) { + REG_WRITE(ah, AR_WA, ah->WARegVal); + udelay(10); + } + + REG_WRITE(ah, AR_RTC_FORCE_WAKE, AR_RTC_FORCE_WAKE_EN | + AR_RTC_FORCE_WAKE_ON_INT); + + if (AR_SREV_9100(ah)) { + rst_flags = AR_RTC_RC_MAC_WARM | AR_RTC_RC_MAC_COLD | + AR_RTC_RC_COLD_RESET | AR_RTC_RC_WARM_RESET; + } else { + tmpReg = REG_READ(ah, AR_INTR_SYNC_CAUSE); + if (AR_SREV_9340(ah)) + tmpReg &= AR9340_INTR_SYNC_LOCAL_TIMEOUT; + else + tmpReg &= AR_INTR_SYNC_LOCAL_TIMEOUT | + AR_INTR_SYNC_RADM_CPL_TIMEOUT; + + if (tmpReg) { + u32 val; + REG_WRITE(ah, AR_INTR_SYNC_ENABLE, 0); + + val = AR_RC_HOSTIF; + if (!AR_SREV_9300_20_OR_LATER(ah)) + val |= AR_RC_AHB; + REG_WRITE(ah, AR_RC, val); + + } else if (!AR_SREV_9300_20_OR_LATER(ah)) + REG_WRITE(ah, AR_RC, AR_RC_AHB); + + rst_flags = AR_RTC_RC_MAC_WARM; + if (type == ATH9K_RESET_COLD) + rst_flags |= AR_RTC_RC_MAC_COLD; + } + + if (AR_SREV_9330(ah)) { + int npend = 0; + int i; + + /* AR9330 WAR: + * call external reset function to reset WMAC if: + * - doing a cold reset + * - we have pending frames in the TX queues + */ + + for (i = 0; i < AR_NUM_QCU; i++) { + npend = ath9k_hw_numtxpending(ah, i); + if (npend) + break; + } + + if (ah->external_reset && + (npend || type == ATH9K_RESET_COLD)) { + int reset_err = 0; + + ath_dbg(ath9k_hw_common(ah), RESET, + "reset MAC via external reset\n"); + + reset_err = ah->external_reset(); + if (reset_err) { + ath_err(ath9k_hw_common(ah), + "External reset failed, err=%d\n", + reset_err); + return false; + } + + REG_WRITE(ah, AR_RTC_RESET, 1); + } + } + + if (ath9k_hw_mci_is_enabled(ah)) + ar9003_mci_check_gpm_offset(ah); + + REG_WRITE(ah, AR_RTC_RC, rst_flags); + + REGWRITE_BUFFER_FLUSH(ah); + + udelay(50); + + REG_WRITE(ah, AR_RTC_RC, 0); + if (!ath9k_hw_wait(ah, AR_RTC_RC, AR_RTC_RC_M, 0, AH_WAIT_TIMEOUT)) { + ath_dbg(ath9k_hw_common(ah), RESET, "RTC stuck in MAC reset\n"); + return false; + } + + if (!AR_SREV_9100(ah)) + REG_WRITE(ah, AR_RC, 0); + + if (AR_SREV_9100(ah)) + udelay(50); + + return true; +} + +static bool ath9k_hw_set_reset_power_on(struct ath_hw *ah) +{ + ENABLE_REGWRITE_BUFFER(ah); + + if (AR_SREV_9300_20_OR_LATER(ah)) { + REG_WRITE(ah, AR_WA, ah->WARegVal); + udelay(10); + } + + REG_WRITE(ah, AR_RTC_FORCE_WAKE, AR_RTC_FORCE_WAKE_EN | + AR_RTC_FORCE_WAKE_ON_INT); + + if (!AR_SREV_9100(ah) && !AR_SREV_9300_20_OR_LATER(ah)) + REG_WRITE(ah, AR_RC, AR_RC_AHB); + + REG_WRITE(ah, AR_RTC_RESET, 0); + + REGWRITE_BUFFER_FLUSH(ah); + + if (!AR_SREV_9300_20_OR_LATER(ah)) + udelay(2); + + if (!AR_SREV_9100(ah) && !AR_SREV_9300_20_OR_LATER(ah)) + REG_WRITE(ah, AR_RC, 0); + + REG_WRITE(ah, AR_RTC_RESET, 1); + + if (!ath9k_hw_wait(ah, + AR_RTC_STATUS, + AR_RTC_STATUS_M, + AR_RTC_STATUS_ON, + AH_WAIT_TIMEOUT)) { + ath_dbg(ath9k_hw_common(ah), RESET, "RTC not waking up\n"); + return false; + } + + return ath9k_hw_set_reset(ah, ATH9K_RESET_WARM); +} + +static bool ath9k_hw_set_reset_reg(struct ath_hw *ah, u32 type) +{ + bool ret = false; + + if (AR_SREV_9300_20_OR_LATER(ah)) { + REG_WRITE(ah, AR_WA, ah->WARegVal); + udelay(10); + } + + REG_WRITE(ah, AR_RTC_FORCE_WAKE, + AR_RTC_FORCE_WAKE_EN | AR_RTC_FORCE_WAKE_ON_INT); + + if (!ah->reset_power_on) + type = ATH9K_RESET_POWER_ON; + + switch (type) { + case ATH9K_RESET_POWER_ON: + ret = ath9k_hw_set_reset_power_on(ah); + if (ret) + ah->reset_power_on = true; + break; + case ATH9K_RESET_WARM: + case ATH9K_RESET_COLD: + ret = ath9k_hw_set_reset(ah, type); + break; + default: + break; + } + + return ret; +} + +static bool ath9k_hw_chip_reset(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + int reset_type = ATH9K_RESET_WARM; + + if (AR_SREV_9280(ah)) { + if (ah->eep_ops->get_eeprom(ah, EEP_OL_PWRCTRL)) + reset_type = ATH9K_RESET_POWER_ON; + else + reset_type = ATH9K_RESET_COLD; + } else if (ah->chip_fullsleep || REG_READ(ah, AR_Q_TXE) || + (REG_READ(ah, AR_CR) & AR_CR_RXE)) + reset_type = ATH9K_RESET_COLD; + + if (!ath9k_hw_set_reset_reg(ah, reset_type)) + return false; + + if (!ath9k_hw_setpower(ah, ATH9K_PM_AWAKE)) + return false; + + ah->chip_fullsleep = false; + + if (AR_SREV_9330(ah)) + ar9003_hw_internal_regulator_apply(ah); + ath9k_hw_init_pll(ah, chan); + ath9k_hw_set_rfmode(ah, chan); + + return true; +} + +static bool ath9k_hw_channel_change(struct ath_hw *ah, + struct ath9k_channel *chan) +{ + struct ath_common *common = ath9k_hw_common(ah); + u32 qnum; + int r; + bool edma = !!(ah->caps.hw_caps & ATH9K_HW_CAP_EDMA); + bool band_switch, mode_diff; + u8 ini_reloaded; + + band_switch = (chan->channelFlags & (CHANNEL_2GHZ | CHANNEL_5GHZ)) != + (ah->curchan->channelFlags & (CHANNEL_2GHZ | + CHANNEL_5GHZ)); + mode_diff = (chan->chanmode != ah->curchan->chanmode); + + for (qnum = 0; qnum < AR_NUM_QCU; qnum++) { + if (ath9k_hw_numtxpending(ah, qnum)) { + ath_dbg(common, QUEUE, + "Transmit frames pending on queue %d\n", qnum); + return false; + } + } + + if (!ath9k_hw_rfbus_req(ah)) { + ath_err(common, "Could not kill baseband RX\n"); + return false; + } + + if (edma && (band_switch || mode_diff)) { + ath9k_hw_mark_phy_inactive(ah); + udelay(5); + + ath9k_hw_init_pll(ah, NULL); + + if (ath9k_hw_fast_chan_change(ah, chan, &ini_reloaded)) { + ath_err(common, "Failed to do fast channel change\n"); + return false; + } + } + + ath9k_hw_set_channel_regs(ah, chan); + + r = ath9k_hw_rf_set_freq(ah, chan); + if (r) { + ath_err(common, "Failed to set channel\n"); + return false; + } + ath9k_hw_set_clockrate(ah); + ath9k_hw_apply_txpower(ah, chan, false); + ath9k_hw_rfbus_done(ah); + + if (IS_CHAN_OFDM(chan) || IS_CHAN_HT(chan)) + ath9k_hw_set_delta_slope(ah, chan); + + ath9k_hw_spur_mitigate_freq(ah, chan); + + if (edma && (band_switch || mode_diff)) { + ah->ah_flags |= AH_FASTCC; + if (band_switch || ini_reloaded) + ah->eep_ops->set_board_values(ah, chan); + + ath9k_hw_init_bb(ah, chan); + + if (band_switch || ini_reloaded) + ath9k_hw_init_cal(ah, chan); + ah->ah_flags &= ~AH_FASTCC; + } + + return true; +} + +static void ath9k_hw_apply_gpio_override(struct ath_hw *ah) +{ + u32 gpio_mask = ah->gpio_mask; + int i; + + for (i = 0; gpio_mask; i++, gpio_mask >>= 1) { + if (!(gpio_mask & 1)) + continue; + + ath9k_hw_cfg_output(ah, i, AR_GPIO_OUTPUT_MUX_AS_OUTPUT); + ath9k_hw_set_gpio(ah, i, !!(ah->gpio_val & BIT(i))); + } +} + +static bool ath9k_hw_check_dcs(u32 dma_dbg, u32 num_dcu_states, + int *hang_state, int *hang_pos) +{ + static u32 dcu_chain_state[] = {5, 6, 9}; /* DCU chain stuck states */ + u32 chain_state, dcs_pos, i; + + for (dcs_pos = 0; dcs_pos < num_dcu_states; dcs_pos++) { + chain_state = (dma_dbg >> (5 * dcs_pos)) & 0x1f; + for (i = 0; i < 3; i++) { + if (chain_state == dcu_chain_state[i]) { + *hang_state = chain_state; + *hang_pos = dcs_pos; + return true; + } + } + } + return false; +} + +#define DCU_COMPLETE_STATE 1 +#define DCU_COMPLETE_STATE_MASK 0x3 +#define NUM_STATUS_READS 50 +static bool ath9k_hw_detect_mac_hang(struct ath_hw *ah) +{ + u32 chain_state, comp_state, dcs_reg = AR_DMADBG_4; + u32 i, hang_pos, hang_state, num_state = 6; + + comp_state = REG_READ(ah, AR_DMADBG_6); + + if ((comp_state & DCU_COMPLETE_STATE_MASK) != DCU_COMPLETE_STATE) { + ath_dbg(ath9k_hw_common(ah), RESET, + "MAC Hang signature not found at DCU complete\n"); + return false; + } + + chain_state = REG_READ(ah, dcs_reg); + if (ath9k_hw_check_dcs(chain_state, num_state, &hang_state, &hang_pos)) + goto hang_check_iter; + + dcs_reg = AR_DMADBG_5; + num_state = 4; + chain_state = REG_READ(ah, dcs_reg); + if (ath9k_hw_check_dcs(chain_state, num_state, &hang_state, &hang_pos)) + goto hang_check_iter; + + ath_dbg(ath9k_hw_common(ah), RESET, + "MAC Hang signature 1 not found\n"); + return false; + +hang_check_iter: + ath_dbg(ath9k_hw_common(ah), RESET, + "DCU registers: chain %08x complete %08x Hang: state %d pos %d\n", + chain_state, comp_state, hang_state, hang_pos); + + for (i = 0; i < NUM_STATUS_READS; i++) { + chain_state = REG_READ(ah, dcs_reg); + chain_state = (chain_state >> (5 * hang_pos)) & 0x1f; + comp_state = REG_READ(ah, AR_DMADBG_6); + + if (((comp_state & DCU_COMPLETE_STATE_MASK) != + DCU_COMPLETE_STATE) || + (chain_state != hang_state)) + return false; + } + + ath_dbg(ath9k_hw_common(ah), RESET, "MAC Hang signature 1 found\n"); + + return true; +} + +bool ath9k_hw_check_alive(struct ath_hw *ah) +{ + int count = 50; + u32 reg; + + if (AR_SREV_9300(ah)) + return !ath9k_hw_detect_mac_hang(ah); + + if (AR_SREV_9285_12_OR_LATER(ah)) + return true; + + do { + reg = REG_READ(ah, AR_OBS_BUS_1); + + if ((reg & 0x7E7FFFEF) == 0x00702400) + continue; + + switch (reg & 0x7E000B00) { + case 0x1E000000: + case 0x52000B00: + case 0x18000B00: + continue; + default: + return true; + } + } while (count-- > 0); + + return false; +} +EXPORT_SYMBOL(ath9k_hw_check_alive); + +static void ath9k_hw_init_mfp(struct ath_hw *ah) +{ + /* Setup MFP options for CCMP */ + if (AR_SREV_9280_20_OR_LATER(ah)) { + /* Mask Retry(b11), PwrMgt(b12), MoreData(b13) to 0 in mgmt + * frames when constructing CCMP AAD. */ + REG_RMW_FIELD(ah, AR_AES_MUTE_MASK1, AR_AES_MUTE_MASK1_FC_MGMT, + 0xc7ff); + ah->sw_mgmt_crypto = false; + } else if (AR_SREV_9160_10_OR_LATER(ah)) { + /* Disable hardware crypto for management frames */ + REG_CLR_BIT(ah, AR_PCU_MISC_MODE2, + AR_PCU_MISC_MODE2_MGMT_CRYPTO_ENABLE); + REG_SET_BIT(ah, AR_PCU_MISC_MODE2, + AR_PCU_MISC_MODE2_NO_CRYPTO_FOR_NON_DATA_PKT); + ah->sw_mgmt_crypto = true; + } else { + ah->sw_mgmt_crypto = true; + } +} + +static void ath9k_hw_reset_opmode(struct ath_hw *ah, + u32 macStaId1, u32 saveDefAntenna) +{ + struct ath_common *common = ath9k_hw_common(ah); + + ENABLE_REGWRITE_BUFFER(ah); + + REG_RMW(ah, AR_STA_ID1, macStaId1 + | AR_STA_ID1_RTS_USE_DEF + | (ah->config.ack_6mb ? AR_STA_ID1_ACKCTS_6MB : 0) + | ah->sta_id1_defaults, + ~AR_STA_ID1_SADH_MASK); + ath_hw_setbssidmask(common); + REG_WRITE(ah, AR_DEF_ANTENNA, saveDefAntenna); + ath9k_hw_write_associd(ah); + REG_WRITE(ah, AR_ISR, ~0); + REG_WRITE(ah, AR_RSSI_THR, INIT_RSSI_THR); + + REGWRITE_BUFFER_FLUSH(ah); + + ath9k_hw_set_operating_mode(ah, ah->opmode); +} + +static void ath9k_hw_init_queues(struct ath_hw *ah) +{ + int i; + + ENABLE_REGWRITE_BUFFER(ah); + + for (i = 0; i < AR_NUM_DCU; i++) + REG_WRITE(ah, AR_DQCUMASK(i), 1 << i); + + REGWRITE_BUFFER_FLUSH(ah); + + ah->intr_txqs = 0; + for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) + ath9k_hw_resettxqueue(ah, i); +} + +/* + * For big endian systems turn on swapping for descriptors + */ +static void ath9k_hw_init_desc(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + + if (AR_SREV_9100(ah)) { + u32 mask; + mask = REG_READ(ah, AR_CFG); + if (mask & (AR_CFG_SWRB | AR_CFG_SWTB | AR_CFG_SWRG)) { + ath_dbg(common, RESET, "CFG Byte Swap Set 0x%x\n", + mask); + } else { + mask = INIT_CONFIG_STATUS | AR_CFG_SWRB | AR_CFG_SWTB; + REG_WRITE(ah, AR_CFG, mask); + ath_dbg(common, RESET, "Setting CFG 0x%x\n", + REG_READ(ah, AR_CFG)); + } + } else { + if (common->bus_ops->ath_bus_type == ATH_USB) { + /* Configure AR9271 target WLAN */ + if (AR_SREV_9271(ah)) + REG_WRITE(ah, AR_CFG, AR_CFG_SWRB | AR_CFG_SWTB); + else + REG_WRITE(ah, AR_CFG, AR_CFG_SWTD | AR_CFG_SWRD); + } +#ifdef __BIG_ENDIAN + else if (AR_SREV_9330(ah) || AR_SREV_9340(ah) || + AR_SREV_9550(ah)) + REG_RMW(ah, AR_CFG, AR_CFG_SWRB | AR_CFG_SWTB, 0); + else + REG_WRITE(ah, AR_CFG, AR_CFG_SWTD | AR_CFG_SWRD); +#endif + } +} + +/* + * Fast channel change: + * (Change synthesizer based on channel freq without resetting chip) + * + * Don't do FCC when + * - Flag is not set + * - Chip is just coming out of full sleep + * - Channel to be set is same as current channel + * - Channel flags are different, (eg.,moving from 2GHz to 5GHz channel) + */ +static int ath9k_hw_do_fastcc(struct ath_hw *ah, struct ath9k_channel *chan) +{ + struct ath_common *common = ath9k_hw_common(ah); + int ret; + + if (AR_SREV_9280(ah) && common->bus_ops->ath_bus_type == ATH_PCI) + goto fail; + + if (ah->chip_fullsleep) + goto fail; + + if (!ah->curchan) + goto fail; + + if (chan->channel == ah->curchan->channel) + goto fail; + + if ((ah->curchan->channelFlags | chan->channelFlags) & + (CHANNEL_HALF | CHANNEL_QUARTER)) + goto fail; + + if ((chan->channelFlags & CHANNEL_ALL) != + (ah->curchan->channelFlags & CHANNEL_ALL)) + goto fail; + + if (!ath9k_hw_check_alive(ah)) + goto fail; + + /* + * For AR9462, make sure that calibration data for + * re-using are present. + */ + if (AR_SREV_9462(ah) && (ah->caldata && + (!ah->caldata->done_txiqcal_once || + !ah->caldata->done_txclcal_once || + !ah->caldata->rtt_done))) + goto fail; + + ath_dbg(common, RESET, "FastChannelChange for %d -> %d\n", + ah->curchan->channel, chan->channel); + + ret = ath9k_hw_channel_change(ah, chan); + if (!ret) + goto fail; + + if (ath9k_hw_mci_is_enabled(ah)) + ar9003_mci_2g5g_switch(ah, false); + + ath9k_hw_loadnf(ah, ah->curchan); + ath9k_hw_start_nfcal(ah, true); + + if (AR_SREV_9271(ah)) + ar9002_hw_load_ani_reg(ah, chan); + + return 0; +fail: + return -EINVAL; +} + +int ath9k_hw_reset(struct ath_hw *ah, struct ath9k_channel *chan, + struct ath9k_hw_cal_data *caldata, bool fastcc) +{ + struct ath_common *common = ath9k_hw_common(ah); + u32 saveLedState; + u32 saveDefAntenna; + u32 macStaId1; + u64 tsf = 0; + int r; + bool start_mci_reset = false; + bool save_fullsleep = ah->chip_fullsleep; + + if (ath9k_hw_mci_is_enabled(ah)) { + start_mci_reset = ar9003_mci_start_reset(ah, chan); + if (start_mci_reset) + return 0; + } + + if (!ath9k_hw_setpower(ah, ATH9K_PM_AWAKE)) + return -EIO; + + if (ah->curchan && !ah->chip_fullsleep) + ath9k_hw_getnf(ah, ah->curchan); + + ah->caldata = caldata; + if (caldata && (chan->channel != caldata->channel || + chan->channelFlags != caldata->channelFlags || + chan->chanmode != caldata->chanmode)) { + /* Operating channel changed, reset channel calibration data */ + memset(caldata, 0, sizeof(*caldata)); + ath9k_init_nfcal_hist_buffer(ah, chan); + } else if (caldata) { + caldata->paprd_packet_sent = false; + } + ah->noise = ath9k_hw_getchan_noise(ah, chan); + + if (fastcc) { + r = ath9k_hw_do_fastcc(ah, chan); + if (!r) + return r; + } + + if (ath9k_hw_mci_is_enabled(ah)) + ar9003_mci_stop_bt(ah, save_fullsleep); + + saveDefAntenna = REG_READ(ah, AR_DEF_ANTENNA); + if (saveDefAntenna == 0) + saveDefAntenna = 1; + + macStaId1 = REG_READ(ah, AR_STA_ID1) & AR_STA_ID1_BASE_RATE_11B; + + /* For chips on which RTC reset is done, save TSF before it gets cleared */ + if (AR_SREV_9100(ah) || + (AR_SREV_9280(ah) && ah->eep_ops->get_eeprom(ah, EEP_OL_PWRCTRL))) + tsf = ath9k_hw_gettsf64(ah); + + saveLedState = REG_READ(ah, AR_CFG_LED) & + (AR_CFG_LED_ASSOC_CTL | AR_CFG_LED_MODE_SEL | + AR_CFG_LED_BLINK_THRESH_SEL | AR_CFG_LED_BLINK_SLOW); + + ath9k_hw_mark_phy_inactive(ah); + + ah->paprd_table_write_done = false; + + /* Only required on the first reset */ + if (AR_SREV_9271(ah) && ah->htc_reset_init) { + REG_WRITE(ah, + AR9271_RESET_POWER_DOWN_CONTROL, + AR9271_RADIO_RF_RST); + udelay(50); + } + + if (!ath9k_hw_chip_reset(ah, chan)) { + ath_err(common, "Chip reset failed\n"); + return -EINVAL; + } + + /* Only required on the first reset */ + if (AR_SREV_9271(ah) && ah->htc_reset_init) { + ah->htc_reset_init = false; + REG_WRITE(ah, + AR9271_RESET_POWER_DOWN_CONTROL, + AR9271_GATE_MAC_CTL); + udelay(50); + } + + /* Restore TSF */ + if (tsf) + ath9k_hw_settsf64(ah, tsf); + + if (AR_SREV_9280_20_OR_LATER(ah)) + REG_SET_BIT(ah, AR_GPIO_INPUT_EN_VAL, AR_GPIO_JTAG_DISABLE); + + if (!AR_SREV_9300_20_OR_LATER(ah)) + ar9002_hw_enable_async_fifo(ah); + + r = ath9k_hw_process_ini(ah, chan); + if (r) + return r; + + if (ath9k_hw_mci_is_enabled(ah)) + ar9003_mci_reset(ah, false, IS_CHAN_2GHZ(chan), save_fullsleep); + + /* + * Some AR91xx SoC devices frequently fail to accept TSF writes + * right after the chip reset. When that happens, write a new + * value after the initvals have been applied, with an offset + * based on measured time difference + */ + if (AR_SREV_9100(ah) && (ath9k_hw_gettsf64(ah) < tsf)) { + tsf += 1500; + ath9k_hw_settsf64(ah, tsf); + } + + ath9k_hw_init_mfp(ah); + + if (IS_CHAN_OFDM(chan) || IS_CHAN_HT(chan)) + ath9k_hw_set_delta_slope(ah, chan); + + ath9k_hw_spur_mitigate_freq(ah, chan); + ah->eep_ops->set_board_values(ah, chan); + + ath9k_hw_reset_opmode(ah, macStaId1, saveDefAntenna); + + r = ath9k_hw_rf_set_freq(ah, chan); + if (r) + return r; + + ath9k_hw_set_clockrate(ah); + + ath9k_hw_init_queues(ah); + ath9k_hw_init_interrupt_masks(ah, ah->opmode); + ath9k_hw_ani_cache_ini_regs(ah); + ath9k_hw_init_qos(ah); + + if (ah->caps.hw_caps & ATH9K_HW_CAP_RFSILENT) + ath9k_hw_cfg_gpio_input(ah, ah->rfkill_gpio); + + ath9k_hw_init_global_settings(ah); + + if (AR_SREV_9287(ah) && AR_SREV_9287_13_OR_LATER(ah)) { + REG_SET_BIT(ah, AR_MAC_PCU_LOGIC_ANALYZER, + AR_MAC_PCU_LOGIC_ANALYZER_DISBUG20768); + REG_RMW_FIELD(ah, AR_AHB_MODE, AR_AHB_CUSTOM_BURST_EN, + AR_AHB_CUSTOM_BURST_ASYNC_FIFO_VAL); + REG_SET_BIT(ah, AR_PCU_MISC_MODE2, + AR_PCU_MISC_MODE2_ENABLE_AGGWEP); + } + + REG_SET_BIT(ah, AR_STA_ID1, AR_STA_ID1_PRESERVE_SEQNUM); + + ath9k_hw_set_dma(ah); + + if (!ath9k_hw_mci_is_enabled(ah)) + REG_WRITE(ah, AR_OBS, 8); + + if (ah->config.rx_intr_mitigation) { + REG_RMW_FIELD(ah, AR_RIMT, AR_RIMT_LAST, 500); + REG_RMW_FIELD(ah, AR_RIMT, AR_RIMT_FIRST, 2000); + } + + if (ah->config.tx_intr_mitigation) { + REG_RMW_FIELD(ah, AR_TIMT, AR_TIMT_LAST, 300); + REG_RMW_FIELD(ah, AR_TIMT, AR_TIMT_FIRST, 750); + } + + ath9k_hw_init_bb(ah, chan); + + if (caldata) { + caldata->done_txiqcal_once = false; + caldata->done_txclcal_once = false; + } + if (!ath9k_hw_init_cal(ah, chan)) + return -EIO; + + if (ath9k_hw_mci_is_enabled(ah) && ar9003_mci_end_reset(ah, chan, caldata)) + return -EIO; + + ENABLE_REGWRITE_BUFFER(ah); + + ath9k_hw_restore_chainmask(ah); + REG_WRITE(ah, AR_CFG_LED, saveLedState | AR_CFG_SCLK_32KHZ); + + REGWRITE_BUFFER_FLUSH(ah); + + ath9k_hw_init_desc(ah); + + if (ath9k_hw_btcoex_is_enabled(ah)) + ath9k_hw_btcoex_enable(ah); + + if (ath9k_hw_mci_is_enabled(ah)) + ar9003_mci_check_bt(ah); + + ath9k_hw_loadnf(ah, chan); + ath9k_hw_start_nfcal(ah, true); + + if (AR_SREV_9300_20_OR_LATER(ah)) { + ar9003_hw_bb_watchdog_config(ah); + ar9003_hw_disable_phy_restart(ah); + } + + ath9k_hw_apply_gpio_override(ah); + + if (AR_SREV_9565(ah) && ah->shared_chain_lnadiv) + REG_SET_BIT(ah, AR_BTCOEX_WL_LNADIV, AR_BTCOEX_WL_LNADIV_FORCE_ON); + + return 0; +} +EXPORT_SYMBOL(ath9k_hw_reset); + +/******************************/ +/* Power Management (Chipset) */ +/******************************/ + +/* + * Notify Power Mgt is disabled in self-generated frames. + * If requested, force chip to sleep. + */ +static void ath9k_set_power_sleep(struct ath_hw *ah) +{ + REG_SET_BIT(ah, AR_STA_ID1, AR_STA_ID1_PWR_SAV); + + if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) { + REG_CLR_BIT(ah, AR_TIMER_MODE, 0xff); + REG_CLR_BIT(ah, AR_NDP2_TIMER_MODE, 0xff); + REG_CLR_BIT(ah, AR_SLP32_INC, 0xfffff); + /* xxx Required for WLAN only case ? */ + REG_WRITE(ah, AR_MCI_INTERRUPT_RX_MSG_EN, 0); + udelay(100); + } + + /* + * Clear the RTC force wake bit to allow the + * mac to go to sleep. + */ + REG_CLR_BIT(ah, AR_RTC_FORCE_WAKE, AR_RTC_FORCE_WAKE_EN); + + if (ath9k_hw_mci_is_enabled(ah)) + udelay(100); + + if (!AR_SREV_9100(ah) && !AR_SREV_9300_20_OR_LATER(ah)) + REG_WRITE(ah, AR_RC, AR_RC_AHB | AR_RC_HOSTIF); + + /* Shutdown chip. Active low */ + if (!AR_SREV_5416(ah) && !AR_SREV_9271(ah)) { + REG_CLR_BIT(ah, AR_RTC_RESET, AR_RTC_RESET_EN); + udelay(2); + } + + /* Clear Bit 14 of AR_WA after putting chip into Full Sleep mode. */ + if (AR_SREV_9300_20_OR_LATER(ah)) + REG_WRITE(ah, AR_WA, ah->WARegVal & ~AR_WA_D3_L1_DISABLE); +} + +/* + * Notify Power Management is enabled in self-generating + * frames. If request, set power mode of chip to + * auto/normal. Duration in units of 128us (1/8 TU). + */ +static void ath9k_set_power_network_sleep(struct ath_hw *ah) +{ + struct ath9k_hw_capabilities *pCap = &ah->caps; + + REG_SET_BIT(ah, AR_STA_ID1, AR_STA_ID1_PWR_SAV); + + if (!(pCap->hw_caps & ATH9K_HW_CAP_AUTOSLEEP)) { + /* Set WakeOnInterrupt bit; clear ForceWake bit */ + REG_WRITE(ah, AR_RTC_FORCE_WAKE, + AR_RTC_FORCE_WAKE_ON_INT); + } else { + + /* When chip goes into network sleep, it could be waken + * up by MCI_INT interrupt caused by BT's HW messages + * (LNA_xxx, CONT_xxx) which chould be in a very fast + * rate (~100us). This will cause chip to leave and + * re-enter network sleep mode frequently, which in + * consequence will have WLAN MCI HW to generate lots of + * SYS_WAKING and SYS_SLEEPING messages which will make + * BT CPU to busy to process. + */ + if (ath9k_hw_mci_is_enabled(ah)) + REG_CLR_BIT(ah, AR_MCI_INTERRUPT_RX_MSG_EN, + AR_MCI_INTERRUPT_RX_HW_MSG_MASK); + /* + * Clear the RTC force wake bit to allow the + * mac to go to sleep. + */ + REG_CLR_BIT(ah, AR_RTC_FORCE_WAKE, AR_RTC_FORCE_WAKE_EN); + + if (ath9k_hw_mci_is_enabled(ah)) + udelay(30); + } + + /* Clear Bit 14 of AR_WA after putting chip into Net Sleep mode. */ + if (AR_SREV_9300_20_OR_LATER(ah)) + REG_WRITE(ah, AR_WA, ah->WARegVal & ~AR_WA_D3_L1_DISABLE); +} + +static bool ath9k_hw_set_power_awake(struct ath_hw *ah) +{ + u32 val; + int i; + + /* Set Bits 14 and 17 of AR_WA before powering on the chip. */ + if (AR_SREV_9300_20_OR_LATER(ah)) { + REG_WRITE(ah, AR_WA, ah->WARegVal); + udelay(10); + } + + if ((REG_READ(ah, AR_RTC_STATUS) & + AR_RTC_STATUS_M) == AR_RTC_STATUS_SHUTDOWN) { + if (!ath9k_hw_set_reset_reg(ah, ATH9K_RESET_POWER_ON)) { + return false; + } + if (!AR_SREV_9300_20_OR_LATER(ah)) + ath9k_hw_init_pll(ah, NULL); + } + if (AR_SREV_9100(ah)) + REG_SET_BIT(ah, AR_RTC_RESET, + AR_RTC_RESET_EN); + + REG_SET_BIT(ah, AR_RTC_FORCE_WAKE, + AR_RTC_FORCE_WAKE_EN); + udelay(50); + + for (i = POWER_UP_TIME / 50; i > 0; i--) { + val = REG_READ(ah, AR_RTC_STATUS) & AR_RTC_STATUS_M; + if (val == AR_RTC_STATUS_ON) + break; + udelay(50); + REG_SET_BIT(ah, AR_RTC_FORCE_WAKE, + AR_RTC_FORCE_WAKE_EN); + } + if (i == 0) { + ath_err(ath9k_hw_common(ah), + "Failed to wakeup in %uus\n", + POWER_UP_TIME / 20); + return false; + } + + if (ath9k_hw_mci_is_enabled(ah)) + ar9003_mci_set_power_awake(ah); + + REG_CLR_BIT(ah, AR_STA_ID1, AR_STA_ID1_PWR_SAV); + + return true; +} + +bool ath9k_hw_setpower(struct ath_hw *ah, enum ath9k_power_mode mode) +{ + struct ath_common *common = ath9k_hw_common(ah); + int status = true; + static const char *modes[] = { + "AWAKE", + "FULL-SLEEP", + "NETWORK SLEEP", + "UNDEFINED" + }; + + if (ah->power_mode == mode) + return status; + + ath_dbg(common, RESET, "%s -> %s\n", + modes[ah->power_mode], modes[mode]); + + switch (mode) { + case ATH9K_PM_AWAKE: + status = ath9k_hw_set_power_awake(ah); + break; + case ATH9K_PM_FULL_SLEEP: + if (ath9k_hw_mci_is_enabled(ah)) + ar9003_mci_set_full_sleep(ah); + + ath9k_set_power_sleep(ah); + ah->chip_fullsleep = true; + break; + case ATH9K_PM_NETWORK_SLEEP: + ath9k_set_power_network_sleep(ah); + break; + default: + ath_err(common, "Unknown power mode %u\n", mode); + return false; + } + ah->power_mode = mode; + + /* + * XXX: If this warning never comes up after a while then + * simply keep the ATH_DBG_WARN_ON_ONCE() but make + * ath9k_hw_setpower() return type void. + */ + + if (!(ah->ah_flags & AH_UNPLUGGED)) + ATH_DBG_WARN_ON_ONCE(!status); + + return status; +} +EXPORT_SYMBOL(ath9k_hw_setpower); + +/*******************/ +/* Beacon Handling */ +/*******************/ + +void ath9k_hw_beaconinit(struct ath_hw *ah, u32 next_beacon, u32 beacon_period) +{ + int flags = 0; + + ENABLE_REGWRITE_BUFFER(ah); + + switch (ah->opmode) { + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_MESH_POINT: + REG_SET_BIT(ah, AR_TXCFG, + AR_TXCFG_ADHOC_BEACON_ATIM_TX_POLICY); + REG_WRITE(ah, AR_NEXT_NDP_TIMER, next_beacon + + TU_TO_USEC(ah->atim_window ? ah->atim_window : 1)); + flags |= AR_NDP_TIMER_EN; + case NL80211_IFTYPE_AP: + REG_WRITE(ah, AR_NEXT_TBTT_TIMER, next_beacon); + REG_WRITE(ah, AR_NEXT_DMA_BEACON_ALERT, next_beacon - + TU_TO_USEC(ah->config.dma_beacon_response_time)); + REG_WRITE(ah, AR_NEXT_SWBA, next_beacon - + TU_TO_USEC(ah->config.sw_beacon_response_time)); + flags |= + AR_TBTT_TIMER_EN | AR_DBA_TIMER_EN | AR_SWBA_TIMER_EN; + break; + default: + ath_dbg(ath9k_hw_common(ah), BEACON, + "%s: unsupported opmode: %d\n", __func__, ah->opmode); + return; + break; + } + + REG_WRITE(ah, AR_BEACON_PERIOD, beacon_period); + REG_WRITE(ah, AR_DMA_BEACON_PERIOD, beacon_period); + REG_WRITE(ah, AR_SWBA_PERIOD, beacon_period); + REG_WRITE(ah, AR_NDP_PERIOD, beacon_period); + + REGWRITE_BUFFER_FLUSH(ah); + + REG_SET_BIT(ah, AR_TIMER_MODE, flags); +} +EXPORT_SYMBOL(ath9k_hw_beaconinit); + +void ath9k_hw_set_sta_beacon_timers(struct ath_hw *ah, + const struct ath9k_beacon_state *bs) +{ + u32 nextTbtt, beaconintval, dtimperiod, beacontimeout; + struct ath9k_hw_capabilities *pCap = &ah->caps; + struct ath_common *common = ath9k_hw_common(ah); + + ENABLE_REGWRITE_BUFFER(ah); + + REG_WRITE(ah, AR_NEXT_TBTT_TIMER, TU_TO_USEC(bs->bs_nexttbtt)); + + REG_WRITE(ah, AR_BEACON_PERIOD, + TU_TO_USEC(bs->bs_intval)); + REG_WRITE(ah, AR_DMA_BEACON_PERIOD, + TU_TO_USEC(bs->bs_intval)); + + REGWRITE_BUFFER_FLUSH(ah); + + REG_RMW_FIELD(ah, AR_RSSI_THR, + AR_RSSI_THR_BM_THR, bs->bs_bmissthreshold); + + beaconintval = bs->bs_intval; + + if (bs->bs_sleepduration > beaconintval) + beaconintval = bs->bs_sleepduration; + + dtimperiod = bs->bs_dtimperiod; + if (bs->bs_sleepduration > dtimperiod) + dtimperiod = bs->bs_sleepduration; + + if (beaconintval == dtimperiod) + nextTbtt = bs->bs_nextdtim; + else + nextTbtt = bs->bs_nexttbtt; + + ath_dbg(common, BEACON, "next DTIM %d\n", bs->bs_nextdtim); + ath_dbg(common, BEACON, "next beacon %d\n", nextTbtt); + ath_dbg(common, BEACON, "beacon period %d\n", beaconintval); + ath_dbg(common, BEACON, "DTIM period %d\n", dtimperiod); + + ENABLE_REGWRITE_BUFFER(ah); + + REG_WRITE(ah, AR_NEXT_DTIM, + TU_TO_USEC(bs->bs_nextdtim - SLEEP_SLOP)); + REG_WRITE(ah, AR_NEXT_TIM, TU_TO_USEC(nextTbtt - SLEEP_SLOP)); + + REG_WRITE(ah, AR_SLEEP1, + SM((CAB_TIMEOUT_VAL << 3), AR_SLEEP1_CAB_TIMEOUT) + | AR_SLEEP1_ASSUME_DTIM); + + if (pCap->hw_caps & ATH9K_HW_CAP_AUTOSLEEP) + beacontimeout = (BEACON_TIMEOUT_VAL << 3); + else + beacontimeout = MIN_BEACON_TIMEOUT_VAL; + + REG_WRITE(ah, AR_SLEEP2, + SM(beacontimeout, AR_SLEEP2_BEACON_TIMEOUT)); + + REG_WRITE(ah, AR_TIM_PERIOD, TU_TO_USEC(beaconintval)); + REG_WRITE(ah, AR_DTIM_PERIOD, TU_TO_USEC(dtimperiod)); + + REGWRITE_BUFFER_FLUSH(ah); + + REG_SET_BIT(ah, AR_TIMER_MODE, + AR_TBTT_TIMER_EN | AR_TIM_TIMER_EN | + AR_DTIM_TIMER_EN); + + /* TSF Out of Range Threshold */ + REG_WRITE(ah, AR_TSFOOR_THRESHOLD, bs->bs_tsfoor_threshold); +} +EXPORT_SYMBOL(ath9k_hw_set_sta_beacon_timers); + +/*******************/ +/* HW Capabilities */ +/*******************/ + +static u8 fixup_chainmask(u8 chip_chainmask, u8 eeprom_chainmask) +{ + eeprom_chainmask &= chip_chainmask; + if (eeprom_chainmask) + return eeprom_chainmask; + else + return chip_chainmask; +} + +/** + * ath9k_hw_dfs_tested - checks if DFS has been tested with used chipset + * @ah: the atheros hardware data structure + * + * We enable DFS support upstream on chipsets which have passed a series + * of tests. The testing requirements are going to be documented. Desired + * test requirements are documented at: + * + * http://wireless.kernel.org/en/users/Drivers/ath9k/dfs + * + * Once a new chipset gets properly tested an individual commit can be used + * to document the testing for DFS for that chipset. + */ +static bool ath9k_hw_dfs_tested(struct ath_hw *ah) +{ + + switch (ah->hw_version.macVersion) { + /* for temporary testing DFS with 9280 */ + case AR_SREV_VERSION_9280: + /* AR9580 will likely be our first target to get testing on */ + case AR_SREV_VERSION_9580: + return true; + default: + return false; + } +} + +int ath9k_hw_fill_cap_info(struct ath_hw *ah) +{ + struct ath9k_hw_capabilities *pCap = &ah->caps; + struct ath_regulatory *regulatory = ath9k_hw_regulatory(ah); + struct ath_common *common = ath9k_hw_common(ah); + unsigned int chip_chainmask; + + u16 eeval; + u8 ant_div_ctl1, tx_chainmask, rx_chainmask; + + eeval = ah->eep_ops->get_eeprom(ah, EEP_REG_0); + regulatory->current_rd = eeval; + + if (ah->opmode != NL80211_IFTYPE_AP && + ah->hw_version.subvendorid == AR_SUBVENDOR_ID_NEW_A) { + if (regulatory->current_rd == 0x64 || + regulatory->current_rd == 0x65) + regulatory->current_rd += 5; + else if (regulatory->current_rd == 0x41) + regulatory->current_rd = 0x43; + ath_dbg(common, REGULATORY, "regdomain mapped to 0x%x\n", + regulatory->current_rd); + } + + eeval = ah->eep_ops->get_eeprom(ah, EEP_OP_MODE); + if ((eeval & (AR5416_OPFLAGS_11G | AR5416_OPFLAGS_11A)) == 0) { + ath_err(common, + "no band has been marked as supported in EEPROM\n"); + return -EINVAL; + } + + if (eeval & AR5416_OPFLAGS_11A) + pCap->hw_caps |= ATH9K_HW_CAP_5GHZ; + + if (eeval & AR5416_OPFLAGS_11G) + pCap->hw_caps |= ATH9K_HW_CAP_2GHZ; + + if (AR_SREV_9485(ah) || + AR_SREV_9285(ah) || + AR_SREV_9330(ah) || + AR_SREV_9565(ah)) + chip_chainmask = 1; + else if (AR_SREV_9462(ah)) + chip_chainmask = 3; + else if (!AR_SREV_9280_20_OR_LATER(ah)) + chip_chainmask = 7; + else if (!AR_SREV_9300_20_OR_LATER(ah) || AR_SREV_9340(ah)) + chip_chainmask = 3; + else + chip_chainmask = 7; + + pCap->tx_chainmask = ah->eep_ops->get_eeprom(ah, EEP_TX_MASK); + /* + * For AR9271 we will temporarilly uses the rx chainmax as read from + * the EEPROM. + */ + if ((ah->hw_version.devid == AR5416_DEVID_PCI) && + !(eeval & AR5416_OPFLAGS_11A) && + !(AR_SREV_9271(ah))) + /* CB71: GPIO 0 is pulled down to indicate 3 rx chains */ + pCap->rx_chainmask = ath9k_hw_gpio_get(ah, 0) ? 0x5 : 0x7; + else if (AR_SREV_9100(ah)) + pCap->rx_chainmask = 0x7; + else + /* Use rx_chainmask from EEPROM. */ + pCap->rx_chainmask = ah->eep_ops->get_eeprom(ah, EEP_RX_MASK); + + pCap->tx_chainmask = fixup_chainmask(chip_chainmask, pCap->tx_chainmask); + pCap->rx_chainmask = fixup_chainmask(chip_chainmask, pCap->rx_chainmask); + ah->txchainmask = pCap->tx_chainmask; + ah->rxchainmask = pCap->rx_chainmask; + + ah->misc_mode |= AR_PCU_MIC_NEW_LOC_ENA; + + /* enable key search for every frame in an aggregate */ + if (AR_SREV_9300_20_OR_LATER(ah)) + ah->misc_mode |= AR_PCU_ALWAYS_PERFORM_KEYSEARCH; + + common->crypt_caps |= ATH_CRYPT_CAP_CIPHER_AESCCM; + + if (ah->hw_version.devid != AR2427_DEVID_PCIE) + pCap->hw_caps |= ATH9K_HW_CAP_HT; + else + pCap->hw_caps &= ~ATH9K_HW_CAP_HT; + + if (AR_SREV_9271(ah)) + pCap->num_gpio_pins = AR9271_NUM_GPIO; + else if (AR_DEVID_7010(ah)) + pCap->num_gpio_pins = AR7010_NUM_GPIO; + else if (AR_SREV_9300_20_OR_LATER(ah)) + pCap->num_gpio_pins = AR9300_NUM_GPIO; + else if (AR_SREV_9287_11_OR_LATER(ah)) + pCap->num_gpio_pins = AR9287_NUM_GPIO; + else if (AR_SREV_9285_12_OR_LATER(ah)) + pCap->num_gpio_pins = AR9285_NUM_GPIO; + else if (AR_SREV_9280_20_OR_LATER(ah)) + pCap->num_gpio_pins = AR928X_NUM_GPIO; + else + pCap->num_gpio_pins = AR_NUM_GPIO; + + if (AR_SREV_9160_10_OR_LATER(ah) || AR_SREV_9100(ah)) + pCap->rts_aggr_limit = ATH_AMPDU_LIMIT_MAX; + else + pCap->rts_aggr_limit = (8 * 1024); + +#if defined(CONFIG_RFKILL) || defined(CONFIG_RFKILL_MODULE) + ah->rfsilent = ah->eep_ops->get_eeprom(ah, EEP_RF_SILENT); + if (ah->rfsilent & EEP_RFSILENT_ENABLED) { + ah->rfkill_gpio = + MS(ah->rfsilent, EEP_RFSILENT_GPIO_SEL); + ah->rfkill_polarity = + MS(ah->rfsilent, EEP_RFSILENT_POLARITY); + + pCap->hw_caps |= ATH9K_HW_CAP_RFSILENT; + } +#endif + if (AR_SREV_9271(ah) || AR_SREV_9300_20_OR_LATER(ah)) + pCap->hw_caps |= ATH9K_HW_CAP_AUTOSLEEP; + else + pCap->hw_caps &= ~ATH9K_HW_CAP_AUTOSLEEP; + + if (AR_SREV_9280(ah) || AR_SREV_9285(ah)) + pCap->hw_caps &= ~ATH9K_HW_CAP_4KB_SPLITTRANS; + else + pCap->hw_caps |= ATH9K_HW_CAP_4KB_SPLITTRANS; + + if (AR_SREV_9300_20_OR_LATER(ah)) { + pCap->hw_caps |= ATH9K_HW_CAP_EDMA | ATH9K_HW_CAP_FASTCLOCK; + if (!AR_SREV_9330(ah) && !AR_SREV_9485(ah) && !AR_SREV_9565(ah)) + pCap->hw_caps |= ATH9K_HW_CAP_LDPC; + + pCap->rx_hp_qdepth = ATH9K_HW_RX_HP_QDEPTH; + pCap->rx_lp_qdepth = ATH9K_HW_RX_LP_QDEPTH; + pCap->rx_status_len = sizeof(struct ar9003_rxs); + pCap->tx_desc_len = sizeof(struct ar9003_txc); + pCap->txs_len = sizeof(struct ar9003_txs); + } else { + pCap->tx_desc_len = sizeof(struct ath_desc); + if (AR_SREV_9280_20(ah)) + pCap->hw_caps |= ATH9K_HW_CAP_FASTCLOCK; + } + + if (AR_SREV_9300_20_OR_LATER(ah)) + pCap->hw_caps |= ATH9K_HW_CAP_RAC_SUPPORTED; + + if (AR_SREV_9300_20_OR_LATER(ah)) + ah->ent_mode = REG_READ(ah, AR_ENT_OTP); + + if (AR_SREV_9287_11_OR_LATER(ah) || AR_SREV_9271(ah)) + pCap->hw_caps |= ATH9K_HW_CAP_SGI_20; + + if (AR_SREV_9285(ah)) + if (ah->eep_ops->get_eeprom(ah, EEP_MODAL_VER) >= 3) { + ant_div_ctl1 = + ah->eep_ops->get_eeprom(ah, EEP_ANT_DIV_CTL1); + if ((ant_div_ctl1 & 0x1) && ((ant_div_ctl1 >> 3) & 0x1)) + pCap->hw_caps |= ATH9K_HW_CAP_ANT_DIV_COMB; + } + if (AR_SREV_9300_20_OR_LATER(ah)) { + if (ah->eep_ops->get_eeprom(ah, EEP_CHAIN_MASK_REDUCE)) + pCap->hw_caps |= ATH9K_HW_CAP_APM; + } + + + if (AR_SREV_9330(ah) || AR_SREV_9485(ah) || AR_SREV_9565(ah)) { + ant_div_ctl1 = ah->eep_ops->get_eeprom(ah, EEP_ANT_DIV_CTL1); + /* + * enable the diversity-combining algorithm only when + * both enable_lna_div and enable_fast_div are set + * Table for Diversity + * ant_div_alt_lnaconf bit 0-1 + * ant_div_main_lnaconf bit 2-3 + * ant_div_alt_gaintb bit 4 + * ant_div_main_gaintb bit 5 + * enable_ant_div_lnadiv bit 6 + * enable_ant_fast_div bit 7 + */ + if ((ant_div_ctl1 >> 0x6) == 0x3) + pCap->hw_caps |= ATH9K_HW_CAP_ANT_DIV_COMB; + } + + if (ath9k_hw_dfs_tested(ah)) + pCap->hw_caps |= ATH9K_HW_CAP_DFS; + + tx_chainmask = pCap->tx_chainmask; + rx_chainmask = pCap->rx_chainmask; + while (tx_chainmask || rx_chainmask) { + if (tx_chainmask & BIT(0)) + pCap->max_txchains++; + if (rx_chainmask & BIT(0)) + pCap->max_rxchains++; + + tx_chainmask >>= 1; + rx_chainmask >>= 1; + } + + if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) { + if (!(ah->ent_mode & AR_ENT_OTP_49GHZ_DISABLE)) + pCap->hw_caps |= ATH9K_HW_CAP_MCI; + + if (AR_SREV_9462_20(ah)) + pCap->hw_caps |= ATH9K_HW_CAP_RTT; + } + + if (AR_SREV_9280_20_OR_LATER(ah)) { + pCap->hw_caps |= ATH9K_HW_WOW_DEVICE_CAPABLE | + ATH9K_HW_WOW_PATTERN_MATCH_EXACT; + + if (AR_SREV_9280(ah)) + pCap->hw_caps |= ATH9K_HW_WOW_PATTERN_MATCH_DWORD; + } + + if (AR_SREV_9300_20_OR_LATER(ah) && + ah->eep_ops->get_eeprom(ah, EEP_PAPRD)) + pCap->hw_caps |= ATH9K_HW_CAP_PAPRD; + + return 0; +} + +/****************************/ +/* GPIO / RFKILL / Antennae */ +/****************************/ + +static void ath9k_hw_gpio_cfg_output_mux(struct ath_hw *ah, + u32 gpio, u32 type) +{ + int addr; + u32 gpio_shift, tmp; + + if (gpio > 11) + addr = AR_GPIO_OUTPUT_MUX3; + else if (gpio > 5) + addr = AR_GPIO_OUTPUT_MUX2; + else + addr = AR_GPIO_OUTPUT_MUX1; + + gpio_shift = (gpio % 6) * 5; + + if (AR_SREV_9280_20_OR_LATER(ah) + || (addr != AR_GPIO_OUTPUT_MUX1)) { + REG_RMW(ah, addr, (type << gpio_shift), + (0x1f << gpio_shift)); + } else { + tmp = REG_READ(ah, addr); + tmp = ((tmp & 0x1F0) << 1) | (tmp & ~0x1F0); + tmp &= ~(0x1f << gpio_shift); + tmp |= (type << gpio_shift); + REG_WRITE(ah, addr, tmp); + } +} + +void ath9k_hw_cfg_gpio_input(struct ath_hw *ah, u32 gpio) +{ + u32 gpio_shift; + + BUG_ON(gpio >= ah->caps.num_gpio_pins); + + if (AR_DEVID_7010(ah)) { + gpio_shift = gpio; + REG_RMW(ah, AR7010_GPIO_OE, + (AR7010_GPIO_OE_AS_INPUT << gpio_shift), + (AR7010_GPIO_OE_MASK << gpio_shift)); + return; + } + + gpio_shift = gpio << 1; + REG_RMW(ah, + AR_GPIO_OE_OUT, + (AR_GPIO_OE_OUT_DRV_NO << gpio_shift), + (AR_GPIO_OE_OUT_DRV << gpio_shift)); +} +EXPORT_SYMBOL(ath9k_hw_cfg_gpio_input); + +u32 ath9k_hw_gpio_get(struct ath_hw *ah, u32 gpio) +{ +#define MS_REG_READ(x, y) \ + (MS(REG_READ(ah, AR_GPIO_IN_OUT), x##_GPIO_IN_VAL) & (AR_GPIO_BIT(y))) + + if (gpio >= ah->caps.num_gpio_pins) + return 0xffffffff; + + if (AR_DEVID_7010(ah)) { + u32 val; + val = REG_READ(ah, AR7010_GPIO_IN); + return (MS(val, AR7010_GPIO_IN_VAL) & AR_GPIO_BIT(gpio)) == 0; + } else if (AR_SREV_9300_20_OR_LATER(ah)) + return (MS(REG_READ(ah, AR_GPIO_IN), AR9300_GPIO_IN_VAL) & + AR_GPIO_BIT(gpio)) != 0; + else if (AR_SREV_9271(ah)) + return MS_REG_READ(AR9271, gpio) != 0; + else if (AR_SREV_9287_11_OR_LATER(ah)) + return MS_REG_READ(AR9287, gpio) != 0; + else if (AR_SREV_9285_12_OR_LATER(ah)) + return MS_REG_READ(AR9285, gpio) != 0; + else if (AR_SREV_9280_20_OR_LATER(ah)) + return MS_REG_READ(AR928X, gpio) != 0; + else + return MS_REG_READ(AR, gpio) != 0; +} +EXPORT_SYMBOL(ath9k_hw_gpio_get); + +void ath9k_hw_cfg_output(struct ath_hw *ah, u32 gpio, + u32 ah_signal_type) +{ + u32 gpio_shift; + + if (AR_DEVID_7010(ah)) { + gpio_shift = gpio; + REG_RMW(ah, AR7010_GPIO_OE, + (AR7010_GPIO_OE_AS_OUTPUT << gpio_shift), + (AR7010_GPIO_OE_MASK << gpio_shift)); + return; + } + + ath9k_hw_gpio_cfg_output_mux(ah, gpio, ah_signal_type); + gpio_shift = 2 * gpio; + REG_RMW(ah, + AR_GPIO_OE_OUT, + (AR_GPIO_OE_OUT_DRV_ALL << gpio_shift), + (AR_GPIO_OE_OUT_DRV << gpio_shift)); +} +EXPORT_SYMBOL(ath9k_hw_cfg_output); + +void ath9k_hw_set_gpio(struct ath_hw *ah, u32 gpio, u32 val) +{ + if (AR_DEVID_7010(ah)) { + val = val ? 0 : 1; + REG_RMW(ah, AR7010_GPIO_OUT, ((val&1) << gpio), + AR_GPIO_BIT(gpio)); + return; + } + + if (AR_SREV_9271(ah)) + val = ~val; + + REG_RMW(ah, AR_GPIO_IN_OUT, ((val & 1) << gpio), + AR_GPIO_BIT(gpio)); +} +EXPORT_SYMBOL(ath9k_hw_set_gpio); + +void ath9k_hw_setantenna(struct ath_hw *ah, u32 antenna) +{ + REG_WRITE(ah, AR_DEF_ANTENNA, (antenna & 0x7)); +} +EXPORT_SYMBOL(ath9k_hw_setantenna); + +/*********************/ +/* General Operation */ +/*********************/ + +u32 ath9k_hw_getrxfilter(struct ath_hw *ah) +{ + u32 bits = REG_READ(ah, AR_RX_FILTER); + u32 phybits = REG_READ(ah, AR_PHY_ERR); + + if (phybits & AR_PHY_ERR_RADAR) + bits |= ATH9K_RX_FILTER_PHYRADAR; + if (phybits & (AR_PHY_ERR_OFDM_TIMING | AR_PHY_ERR_CCK_TIMING)) + bits |= ATH9K_RX_FILTER_PHYERR; + + return bits; +} +EXPORT_SYMBOL(ath9k_hw_getrxfilter); + +void ath9k_hw_setrxfilter(struct ath_hw *ah, u32 bits) +{ + u32 phybits; + + ENABLE_REGWRITE_BUFFER(ah); + + if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) + bits |= ATH9K_RX_FILTER_CONTROL_WRAPPER; + + REG_WRITE(ah, AR_RX_FILTER, bits); + + phybits = 0; + if (bits & ATH9K_RX_FILTER_PHYRADAR) + phybits |= AR_PHY_ERR_RADAR; + if (bits & ATH9K_RX_FILTER_PHYERR) + phybits |= AR_PHY_ERR_OFDM_TIMING | AR_PHY_ERR_CCK_TIMING; + REG_WRITE(ah, AR_PHY_ERR, phybits); + + if (phybits) + REG_SET_BIT(ah, AR_RXCFG, AR_RXCFG_ZLFDMA); + else + REG_CLR_BIT(ah, AR_RXCFG, AR_RXCFG_ZLFDMA); + + REGWRITE_BUFFER_FLUSH(ah); +} +EXPORT_SYMBOL(ath9k_hw_setrxfilter); + +bool ath9k_hw_phy_disable(struct ath_hw *ah) +{ + if (ath9k_hw_mci_is_enabled(ah)) + ar9003_mci_bt_gain_ctrl(ah); + + if (!ath9k_hw_set_reset_reg(ah, ATH9K_RESET_WARM)) + return false; + + ath9k_hw_init_pll(ah, NULL); + ah->htc_reset_init = true; + return true; +} +EXPORT_SYMBOL(ath9k_hw_phy_disable); + +bool ath9k_hw_disable(struct ath_hw *ah) +{ + if (!ath9k_hw_setpower(ah, ATH9K_PM_AWAKE)) + return false; + + if (!ath9k_hw_set_reset_reg(ah, ATH9K_RESET_COLD)) + return false; + + ath9k_hw_init_pll(ah, NULL); + return true; +} +EXPORT_SYMBOL(ath9k_hw_disable); + +static int get_antenna_gain(struct ath_hw *ah, struct ath9k_channel *chan) +{ + enum eeprom_param gain_param; + + if (IS_CHAN_2GHZ(chan)) + gain_param = EEP_ANTENNA_GAIN_2G; + else + gain_param = EEP_ANTENNA_GAIN_5G; + + return ah->eep_ops->get_eeprom(ah, gain_param); +} + +void ath9k_hw_apply_txpower(struct ath_hw *ah, struct ath9k_channel *chan, + bool test) +{ + struct ath_regulatory *reg = ath9k_hw_regulatory(ah); + struct ieee80211_channel *channel; + int chan_pwr, new_pwr, max_gain; + int ant_gain, ant_reduction = 0; + + if (!chan) + return; + + channel = chan->chan; + chan_pwr = min_t(int, channel->max_power * 2, MAX_RATE_POWER); + new_pwr = min_t(int, chan_pwr, reg->power_limit); + max_gain = chan_pwr - new_pwr + channel->max_antenna_gain * 2; + + ant_gain = get_antenna_gain(ah, chan); + if (ant_gain > max_gain) + ant_reduction = ant_gain - max_gain; + + ah->eep_ops->set_txpower(ah, chan, + ath9k_regd_get_ctl(reg, chan), + ant_reduction, new_pwr, test); +} + +void ath9k_hw_set_txpowerlimit(struct ath_hw *ah, u32 limit, bool test) +{ + struct ath_regulatory *reg = ath9k_hw_regulatory(ah); + struct ath9k_channel *chan = ah->curchan; + struct ieee80211_channel *channel = chan->chan; + + reg->power_limit = min_t(u32, limit, MAX_RATE_POWER); + if (test) + channel->max_power = MAX_RATE_POWER / 2; + + ath9k_hw_apply_txpower(ah, chan, test); + + if (test) + channel->max_power = DIV_ROUND_UP(reg->max_power_level, 2); +} +EXPORT_SYMBOL(ath9k_hw_set_txpowerlimit); + +void ath9k_hw_setopmode(struct ath_hw *ah) +{ + ath9k_hw_set_operating_mode(ah, ah->opmode); +} +EXPORT_SYMBOL(ath9k_hw_setopmode); + +void ath9k_hw_setmcastfilter(struct ath_hw *ah, u32 filter0, u32 filter1) +{ + REG_WRITE(ah, AR_MCAST_FIL0, filter0); + REG_WRITE(ah, AR_MCAST_FIL1, filter1); +} +EXPORT_SYMBOL(ath9k_hw_setmcastfilter); + +void ath9k_hw_write_associd(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + + REG_WRITE(ah, AR_BSS_ID0, get_unaligned_le32(common->curbssid)); + REG_WRITE(ah, AR_BSS_ID1, get_unaligned_le16(common->curbssid + 4) | + ((common->curaid & 0x3fff) << AR_BSS_ID1_AID_S)); +} +EXPORT_SYMBOL(ath9k_hw_write_associd); + +#define ATH9K_MAX_TSF_READ 10 + +u64 ath9k_hw_gettsf64(struct ath_hw *ah) +{ + u32 tsf_lower, tsf_upper1, tsf_upper2; + int i; + + tsf_upper1 = REG_READ(ah, AR_TSF_U32); + for (i = 0; i < ATH9K_MAX_TSF_READ; i++) { + tsf_lower = REG_READ(ah, AR_TSF_L32); + tsf_upper2 = REG_READ(ah, AR_TSF_U32); + if (tsf_upper2 == tsf_upper1) + break; + tsf_upper1 = tsf_upper2; + } + + WARN_ON( i == ATH9K_MAX_TSF_READ ); + + return (((u64)tsf_upper1 << 32) | tsf_lower); +} +EXPORT_SYMBOL(ath9k_hw_gettsf64); + +void ath9k_hw_settsf64(struct ath_hw *ah, u64 tsf64) +{ + REG_WRITE(ah, AR_TSF_L32, tsf64 & 0xffffffff); + REG_WRITE(ah, AR_TSF_U32, (tsf64 >> 32) & 0xffffffff); +} +EXPORT_SYMBOL(ath9k_hw_settsf64); + +void ath9k_hw_reset_tsf(struct ath_hw *ah) +{ + if (!ath9k_hw_wait(ah, AR_SLP32_MODE, AR_SLP32_TSF_WRITE_STATUS, 0, + AH_TSF_WRITE_TIMEOUT)) + ath_dbg(ath9k_hw_common(ah), RESET, + "AR_SLP32_TSF_WRITE_STATUS limit exceeded\n"); + + REG_WRITE(ah, AR_RESET_TSF, AR_RESET_TSF_ONCE); +} +EXPORT_SYMBOL(ath9k_hw_reset_tsf); + +void ath9k_hw_set_tsfadjust(struct ath_hw *ah, bool set) +{ + if (set) + ah->misc_mode |= AR_PCU_TX_ADD_TSF; + else + ah->misc_mode &= ~AR_PCU_TX_ADD_TSF; +} +EXPORT_SYMBOL(ath9k_hw_set_tsfadjust); + +void ath9k_hw_set11nmac2040(struct ath_hw *ah) +{ + struct ieee80211_conf *conf = &ath9k_hw_common(ah)->hw->conf; + u32 macmode; + + if (conf_is_ht40(conf) && !ah->config.cwm_ignore_extcca) + macmode = AR_2040_JOINED_RX_CLEAR; + else + macmode = 0; + + REG_WRITE(ah, AR_2040_MODE, macmode); +} + +/* HW Generic timers configuration */ + +static const struct ath_gen_timer_configuration gen_tmr_configuration[] = +{ + {AR_NEXT_NDP_TIMER, AR_NDP_PERIOD, AR_TIMER_MODE, 0x0080}, + {AR_NEXT_NDP_TIMER, AR_NDP_PERIOD, AR_TIMER_MODE, 0x0080}, + {AR_NEXT_NDP_TIMER, AR_NDP_PERIOD, AR_TIMER_MODE, 0x0080}, + {AR_NEXT_NDP_TIMER, AR_NDP_PERIOD, AR_TIMER_MODE, 0x0080}, + {AR_NEXT_NDP_TIMER, AR_NDP_PERIOD, AR_TIMER_MODE, 0x0080}, + {AR_NEXT_NDP_TIMER, AR_NDP_PERIOD, AR_TIMER_MODE, 0x0080}, + {AR_NEXT_NDP_TIMER, AR_NDP_PERIOD, AR_TIMER_MODE, 0x0080}, + {AR_NEXT_NDP_TIMER, AR_NDP_PERIOD, AR_TIMER_MODE, 0x0080}, + {AR_NEXT_NDP2_TIMER, AR_NDP2_PERIOD, AR_NDP2_TIMER_MODE, 0x0001}, + {AR_NEXT_NDP2_TIMER + 1*4, AR_NDP2_PERIOD + 1*4, + AR_NDP2_TIMER_MODE, 0x0002}, + {AR_NEXT_NDP2_TIMER + 2*4, AR_NDP2_PERIOD + 2*4, + AR_NDP2_TIMER_MODE, 0x0004}, + {AR_NEXT_NDP2_TIMER + 3*4, AR_NDP2_PERIOD + 3*4, + AR_NDP2_TIMER_MODE, 0x0008}, + {AR_NEXT_NDP2_TIMER + 4*4, AR_NDP2_PERIOD + 4*4, + AR_NDP2_TIMER_MODE, 0x0010}, + {AR_NEXT_NDP2_TIMER + 5*4, AR_NDP2_PERIOD + 5*4, + AR_NDP2_TIMER_MODE, 0x0020}, + {AR_NEXT_NDP2_TIMER + 6*4, AR_NDP2_PERIOD + 6*4, + AR_NDP2_TIMER_MODE, 0x0040}, + {AR_NEXT_NDP2_TIMER + 7*4, AR_NDP2_PERIOD + 7*4, + AR_NDP2_TIMER_MODE, 0x0080} +}; + +/* HW generic timer primitives */ + +/* compute and clear index of rightmost 1 */ +static u32 rightmost_index(struct ath_gen_timer_table *timer_table, u32 *mask) +{ + u32 b; + + b = *mask; + b &= (0-b); + *mask &= ~b; + b *= debruijn32; + b >>= 27; + + return timer_table->gen_timer_index[b]; +} + +u32 ath9k_hw_gettsf32(struct ath_hw *ah) +{ + return REG_READ(ah, AR_TSF_L32); +} +EXPORT_SYMBOL(ath9k_hw_gettsf32); + +struct ath_gen_timer *ath_gen_timer_alloc(struct ath_hw *ah, + void (*trigger)(void *), + void (*overflow)(void *), + void *arg, + u8 timer_index) +{ + struct ath_gen_timer_table *timer_table = &ah->hw_gen_timers; + struct ath_gen_timer *timer; + + timer = kzalloc(sizeof(struct ath_gen_timer), GFP_KERNEL); + if (timer == NULL) + return NULL; + + /* allocate a hardware generic timer slot */ + timer_table->timers[timer_index] = timer; + timer->index = timer_index; + timer->trigger = trigger; + timer->overflow = overflow; + timer->arg = arg; + + return timer; +} +EXPORT_SYMBOL(ath_gen_timer_alloc); + +void ath9k_hw_gen_timer_start(struct ath_hw *ah, + struct ath_gen_timer *timer, + u32 trig_timeout, + u32 timer_period) +{ + struct ath_gen_timer_table *timer_table = &ah->hw_gen_timers; + u32 tsf, timer_next; + + BUG_ON(!timer_period); + + set_bit(timer->index, &timer_table->timer_mask.timer_bits); + + tsf = ath9k_hw_gettsf32(ah); + + timer_next = tsf + trig_timeout; + + ath_dbg(ath9k_hw_common(ah), HWTIMER, + "current tsf %x period %x timer_next %x\n", + tsf, timer_period, timer_next); + + /* + * Program generic timer registers + */ + REG_WRITE(ah, gen_tmr_configuration[timer->index].next_addr, + timer_next); + REG_WRITE(ah, gen_tmr_configuration[timer->index].period_addr, + timer_period); + REG_SET_BIT(ah, gen_tmr_configuration[timer->index].mode_addr, + gen_tmr_configuration[timer->index].mode_mask); + + if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) { + /* + * Starting from AR9462, each generic timer can select which tsf + * to use. But we still follow the old rule, 0 - 7 use tsf and + * 8 - 15 use tsf2. + */ + if ((timer->index < AR_GEN_TIMER_BANK_1_LEN)) + REG_CLR_BIT(ah, AR_MAC_PCU_GEN_TIMER_TSF_SEL, + (1 << timer->index)); + else + REG_SET_BIT(ah, AR_MAC_PCU_GEN_TIMER_TSF_SEL, + (1 << timer->index)); + } + + /* Enable both trigger and thresh interrupt masks */ + REG_SET_BIT(ah, AR_IMR_S5, + (SM(AR_GENTMR_BIT(timer->index), AR_IMR_S5_GENTIMER_THRESH) | + SM(AR_GENTMR_BIT(timer->index), AR_IMR_S5_GENTIMER_TRIG))); +} +EXPORT_SYMBOL(ath9k_hw_gen_timer_start); + +void ath9k_hw_gen_timer_stop(struct ath_hw *ah, struct ath_gen_timer *timer) +{ + struct ath_gen_timer_table *timer_table = &ah->hw_gen_timers; + + if ((timer->index < AR_FIRST_NDP_TIMER) || + (timer->index >= ATH_MAX_GEN_TIMER)) { + return; + } + + /* Clear generic timer enable bits. */ + REG_CLR_BIT(ah, gen_tmr_configuration[timer->index].mode_addr, + gen_tmr_configuration[timer->index].mode_mask); + + if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) { + /* + * Need to switch back to TSF if it was using TSF2. + */ + if ((timer->index >= AR_GEN_TIMER_BANK_1_LEN)) { + REG_CLR_BIT(ah, AR_MAC_PCU_GEN_TIMER_TSF_SEL, + (1 << timer->index)); + } + } + + /* Disable both trigger and thresh interrupt masks */ + REG_CLR_BIT(ah, AR_IMR_S5, + (SM(AR_GENTMR_BIT(timer->index), AR_IMR_S5_GENTIMER_THRESH) | + SM(AR_GENTMR_BIT(timer->index), AR_IMR_S5_GENTIMER_TRIG))); + + clear_bit(timer->index, &timer_table->timer_mask.timer_bits); +} +EXPORT_SYMBOL(ath9k_hw_gen_timer_stop); + +void ath_gen_timer_free(struct ath_hw *ah, struct ath_gen_timer *timer) +{ + struct ath_gen_timer_table *timer_table = &ah->hw_gen_timers; + + /* free the hardware generic timer slot */ + timer_table->timers[timer->index] = NULL; + kfree(timer); +} +EXPORT_SYMBOL(ath_gen_timer_free); + +/* + * Generic Timer Interrupts handling + */ +void ath_gen_timer_isr(struct ath_hw *ah) +{ + struct ath_gen_timer_table *timer_table = &ah->hw_gen_timers; + struct ath_gen_timer *timer; + struct ath_common *common = ath9k_hw_common(ah); + u32 trigger_mask, thresh_mask, index; + + /* get hardware generic timer interrupt status */ + trigger_mask = ah->intr_gen_timer_trigger; + thresh_mask = ah->intr_gen_timer_thresh; + trigger_mask &= timer_table->timer_mask.val; + thresh_mask &= timer_table->timer_mask.val; + + trigger_mask &= ~thresh_mask; + + while (thresh_mask) { + index = rightmost_index(timer_table, &thresh_mask); + timer = timer_table->timers[index]; + BUG_ON(!timer); + ath_dbg(common, HWTIMER, "TSF overflow for Gen timer %d\n", + index); + timer->overflow(timer->arg); + } + + while (trigger_mask) { + index = rightmost_index(timer_table, &trigger_mask); + timer = timer_table->timers[index]; + BUG_ON(!timer); + ath_dbg(common, HWTIMER, + "Gen timer[%d] trigger\n", index); + timer->trigger(timer->arg); + } +} +EXPORT_SYMBOL(ath_gen_timer_isr); + +/********/ +/* HTC */ +/********/ + +static struct { + u32 version; + const char * name; +} ath_mac_bb_names[] = { + /* Devices with external radios */ + { AR_SREV_VERSION_5416_PCI, "5416" }, + { AR_SREV_VERSION_5416_PCIE, "5418" }, + { AR_SREV_VERSION_9100, "9100" }, + { AR_SREV_VERSION_9160, "9160" }, + /* Single-chip solutions */ + { AR_SREV_VERSION_9280, "9280" }, + { AR_SREV_VERSION_9285, "9285" }, + { AR_SREV_VERSION_9287, "9287" }, + { AR_SREV_VERSION_9271, "9271" }, + { AR_SREV_VERSION_9300, "9300" }, + { AR_SREV_VERSION_9330, "9330" }, + { AR_SREV_VERSION_9340, "9340" }, + { AR_SREV_VERSION_9485, "9485" }, + { AR_SREV_VERSION_9462, "9462" }, + { AR_SREV_VERSION_9550, "9550" }, + { AR_SREV_VERSION_9565, "9565" }, +}; + +/* For devices with external radios */ +static struct { + u16 version; + const char * name; +} ath_rf_names[] = { + { 0, "5133" }, + { AR_RAD5133_SREV_MAJOR, "5133" }, + { AR_RAD5122_SREV_MAJOR, "5122" }, + { AR_RAD2133_SREV_MAJOR, "2133" }, + { AR_RAD2122_SREV_MAJOR, "2122" } +}; + +/* + * Return the MAC/BB name. "????" is returned if the MAC/BB is unknown. + */ +static const char *ath9k_hw_mac_bb_name(u32 mac_bb_version) +{ + int i; + + for (i=0; i= AR9280 are single-chip */ + if (AR_SREV_9280_20_OR_LATER(ah)) { + used = snprintf(hw_name, len, + "Atheros AR%s Rev:%x", + ath9k_hw_mac_bb_name(ah->hw_version.macVersion), + ah->hw_version.macRev); + } + else { + used = snprintf(hw_name, len, + "Atheros AR%s MAC/BB Rev:%x AR%s RF Rev:%x", + ath9k_hw_mac_bb_name(ah->hw_version.macVersion), + ah->hw_version.macRev, + ath9k_hw_rf_name((ah->hw_version.analog5GhzRev & + AR_RADIO_SREV_MAJOR)), + ah->hw_version.phyRev); + } + + hw_name[used] = '\0'; +} +EXPORT_SYMBOL(ath9k_hw_name); diff --git a/drivers/net/wireless/ath/ath9k/hw.h b/drivers/net/wireless/ath/ath9k/hw.h new file mode 100644 index 0000000..661e6cf --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/hw.h @@ -0,0 +1,1175 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef HW_H +#define HW_H + +#include +#include +#include +#include + +#include "mac.h" +#include "ani.h" +#include "eeprom.h" +#include "calib.h" +#include "reg.h" +#include "phy.h" +#include "btcoex.h" + +#include "../regd.h" + +#define ATHEROS_VENDOR_ID 0x168c + +#define AR5416_DEVID_PCI 0x0023 +#define AR5416_DEVID_PCIE 0x0024 +#define AR9160_DEVID_PCI 0x0027 +#define AR9280_DEVID_PCI 0x0029 +#define AR9280_DEVID_PCIE 0x002a +#define AR9285_DEVID_PCIE 0x002b +#define AR2427_DEVID_PCIE 0x002c +#define AR9287_DEVID_PCI 0x002d +#define AR9287_DEVID_PCIE 0x002e +#define AR9300_DEVID_PCIE 0x0030 +#define AR9300_DEVID_AR9340 0x0031 +#define AR9300_DEVID_AR9485_PCIE 0x0032 +#define AR9300_DEVID_AR9580 0x0033 +#define AR9300_DEVID_AR9462 0x0034 +#define AR9300_DEVID_AR9330 0x0035 +#define AR9300_DEVID_QCA955X 0x0038 +#define AR9485_DEVID_AR1111 0x0037 +#define AR9300_DEVID_AR9565 0x0036 + +#define AR5416_AR9100_DEVID 0x000b + +#define AR_SUBVENDOR_ID_NOG 0x0e11 +#define AR_SUBVENDOR_ID_NEW_A 0x7065 +#define AR5416_MAGIC 0x19641014 + +#define AR9280_COEX2WIRE_SUBSYSID 0x309b +#define AT9285_COEX3WIRE_SA_SUBSYSID 0x30aa +#define AT9285_COEX3WIRE_DA_SUBSYSID 0x30ab + +#define ATH_AMPDU_LIMIT_MAX (64 * 1024 - 1) + +#define ATH_DEFAULT_NOISE_FLOOR -95 + +#define ATH9K_RSSI_BAD -128 + +#define ATH9K_NUM_CHANNELS 38 + +/* Register read/write primitives */ +#define REG_WRITE(_ah, _reg, _val) \ + (_ah)->reg_ops.write((_ah), (_val), (_reg)) + +#define REG_READ(_ah, _reg) \ + (_ah)->reg_ops.read((_ah), (_reg)) + +#define REG_READ_MULTI(_ah, _addr, _val, _cnt) \ + (_ah)->reg_ops.multi_read((_ah), (_addr), (_val), (_cnt)) + +#define REG_RMW(_ah, _reg, _set, _clr) \ + (_ah)->reg_ops.rmw((_ah), (_reg), (_set), (_clr)) + +#define ENABLE_REGWRITE_BUFFER(_ah) \ + do { \ + if ((_ah)->reg_ops.enable_write_buffer) \ + (_ah)->reg_ops.enable_write_buffer((_ah)); \ + } while (0) + +#define REGWRITE_BUFFER_FLUSH(_ah) \ + do { \ + if ((_ah)->reg_ops.write_flush) \ + (_ah)->reg_ops.write_flush((_ah)); \ + } while (0) + +#define PR_EEP(_s, _val) \ + do { \ + len += snprintf(buf + len, size - len, "%20s : %10d\n", \ + _s, (_val)); \ + } while (0) + +#define SM(_v, _f) (((_v) << _f##_S) & _f) +#define MS(_v, _f) (((_v) & _f) >> _f##_S) +#define REG_RMW_FIELD(_a, _r, _f, _v) \ + REG_RMW(_a, _r, (((_v) << _f##_S) & _f), (_f)) +#define REG_READ_FIELD(_a, _r, _f) \ + (((REG_READ(_a, _r) & _f) >> _f##_S)) +#define REG_SET_BIT(_a, _r, _f) \ + REG_RMW(_a, _r, (_f), 0) +#define REG_CLR_BIT(_a, _r, _f) \ + REG_RMW(_a, _r, 0, (_f)) + +#define DO_DELAY(x) do { \ + if (((++(x) % 64) == 0) && \ + (ath9k_hw_common(ah)->bus_ops->ath_bus_type \ + != ATH_USB)) \ + udelay(1); \ + } while (0) + +#define REG_WRITE_ARRAY(iniarray, column, regWr) \ + ath9k_hw_write_array(ah, iniarray, column, &(regWr)) + +#define AR_GPIO_OUTPUT_MUX_AS_OUTPUT 0 +#define AR_GPIO_OUTPUT_MUX_AS_PCIE_ATTENTION_LED 1 +#define AR_GPIO_OUTPUT_MUX_AS_PCIE_POWER_LED 2 +#define AR_GPIO_OUTPUT_MUX_AS_TX_FRAME 3 +#define AR_GPIO_OUTPUT_MUX_AS_RX_CLEAR_EXTERNAL 4 +#define AR_GPIO_OUTPUT_MUX_AS_MAC_NETWORK_LED 5 +#define AR_GPIO_OUTPUT_MUX_AS_MAC_POWER_LED 6 +#define AR_GPIO_OUTPUT_MUX_AS_MCI_WLAN_DATA 0x16 +#define AR_GPIO_OUTPUT_MUX_AS_MCI_WLAN_CLK 0x17 +#define AR_GPIO_OUTPUT_MUX_AS_MCI_BT_DATA 0x18 +#define AR_GPIO_OUTPUT_MUX_AS_MCI_BT_CLK 0x19 +#define AR_GPIO_OUTPUT_MUX_AS_WL_IN_TX 0x14 +#define AR_GPIO_OUTPUT_MUX_AS_WL_IN_RX 0x13 +#define AR_GPIO_OUTPUT_MUX_AS_BT_IN_TX 9 +#define AR_GPIO_OUTPUT_MUX_AS_BT_IN_RX 8 +#define AR_GPIO_OUTPUT_MUX_AS_RUCKUS_STROBE 0x1d +#define AR_GPIO_OUTPUT_MUX_AS_RUCKUS_DATA 0x1e + +#define AR_GPIOD_MASK 0x00001FFF +#define AR_GPIO_BIT(_gpio) (1 << (_gpio)) + +#define BASE_ACTIVATE_DELAY 100 +#define RTC_PLL_SETTLE_DELAY (AR_SREV_9340(ah) ? 1000 : 100) +#define COEF_SCALE_S 24 +#define HT40_CHANNEL_CENTER_SHIFT 10 + +#define ATH9K_ANTENNA0_CHAINMASK 0x1 +#define ATH9K_ANTENNA1_CHAINMASK 0x2 + +#define ATH9K_NUM_DMA_DEBUG_REGS 8 +#define ATH9K_NUM_QUEUES 10 + +#define MAX_RATE_POWER 63 +#define AH_WAIT_TIMEOUT 100000 /* (us) */ +#define AH_TSF_WRITE_TIMEOUT 100 /* (us) */ +#define AH_TIME_QUANTUM 10 +#define AR_KEYTABLE_SIZE 128 +#define POWER_UP_TIME 10000 +#define SPUR_RSSI_THRESH 40 +#define UPPER_5G_SUB_BAND_START 5700 +#define MID_5G_SUB_BAND_START 5400 + +#define CAB_TIMEOUT_VAL 10 +#define BEACON_TIMEOUT_VAL 10 +#define MIN_BEACON_TIMEOUT_VAL 1 +#define SLEEP_SLOP 3 + +#define INIT_CONFIG_STATUS 0x00000000 +#define INIT_RSSI_THR 0x00000700 +#define INIT_BCON_CNTRL_REG 0x00000000 + +#define TU_TO_USEC(_tu) ((_tu) << 10) + +#define ATH9K_HW_RX_HP_QDEPTH 16 +#define ATH9K_HW_RX_LP_QDEPTH 128 + +#define PAPRD_GAIN_TABLE_ENTRIES 32 +#define PAPRD_TABLE_SZ 24 +#define PAPRD_IDEAL_AGC2_PWR_RANGE 0xe0 + +/* + * Wake on Wireless + */ + +/* Keep Alive Frame */ +#define KAL_FRAME_LEN 28 +#define KAL_FRAME_TYPE 0x2 /* data frame */ +#define KAL_FRAME_SUB_TYPE 0x4 /* null data frame */ +#define KAL_DURATION_ID 0x3d +#define KAL_NUM_DATA_WORDS 6 +#define KAL_NUM_DESC_WORDS 12 +#define KAL_ANTENNA_MODE 1 +#define KAL_TO_DS 1 +#define KAL_DELAY 4 /*delay of 4ms between 2 KAL frames */ +#define KAL_TIMEOUT 900 + +#define MAX_PATTERN_SIZE 256 +#define MAX_PATTERN_MASK_SIZE 32 +#define MAX_NUM_PATTERN 8 +#define MAX_NUM_USER_PATTERN 6 /* deducting the disassociate and + deauthenticate packets */ + +/* + * WoW trigger mapping to hardware code + */ + +#define AH_WOW_USER_PATTERN_EN BIT(0) +#define AH_WOW_MAGIC_PATTERN_EN BIT(1) +#define AH_WOW_LINK_CHANGE BIT(2) +#define AH_WOW_BEACON_MISS BIT(3) + +enum ath_hw_txq_subtype { + ATH_TXQ_AC_BE = 0, + ATH_TXQ_AC_BK = 1, + ATH_TXQ_AC_VI = 2, + ATH_TXQ_AC_VO = 3, +}; + +enum ath_ini_subsys { + ATH_INI_PRE = 0, + ATH_INI_CORE, + ATH_INI_POST, + ATH_INI_NUM_SPLIT, +}; + +enum ath9k_hw_caps { + ATH9K_HW_CAP_HT = BIT(0), + ATH9K_HW_CAP_RFSILENT = BIT(1), + ATH9K_HW_CAP_AUTOSLEEP = BIT(2), + ATH9K_HW_CAP_4KB_SPLITTRANS = BIT(3), + ATH9K_HW_CAP_EDMA = BIT(4), + ATH9K_HW_CAP_RAC_SUPPORTED = BIT(5), + ATH9K_HW_CAP_LDPC = BIT(6), + ATH9K_HW_CAP_FASTCLOCK = BIT(7), + ATH9K_HW_CAP_SGI_20 = BIT(8), + ATH9K_HW_CAP_ANT_DIV_COMB = BIT(10), + ATH9K_HW_CAP_2GHZ = BIT(11), + ATH9K_HW_CAP_5GHZ = BIT(12), + ATH9K_HW_CAP_APM = BIT(13), + ATH9K_HW_CAP_RTT = BIT(14), + ATH9K_HW_CAP_MCI = BIT(15), + ATH9K_HW_CAP_DFS = BIT(16), + ATH9K_HW_WOW_DEVICE_CAPABLE = BIT(17), + ATH9K_HW_WOW_PATTERN_MATCH_EXACT = BIT(18), + ATH9K_HW_WOW_PATTERN_MATCH_DWORD = BIT(19), + ATH9K_HW_CAP_PAPRD = BIT(20), +}; + +/* + * WoW device capabilities + * @ATH9K_HW_WOW_DEVICE_CAPABLE: device revision is capable of WoW. + * @ATH9K_HW_WOW_PATTERN_MATCH_EXACT: device is capable of matching + * an exact user defined pattern or de-authentication/disassoc pattern. + * @ATH9K_HW_WOW_PATTERN_MATCH_DWORD: device requires the first four + * bytes of the pattern for user defined pattern, de-authentication and + * disassociation patterns for all types of possible frames recieved + * of those types. + */ + +struct ath9k_hw_capabilities { + u32 hw_caps; /* ATH9K_HW_CAP_* from ath9k_hw_caps */ + u16 rts_aggr_limit; + u8 tx_chainmask; + u8 rx_chainmask; + u8 max_txchains; + u8 max_rxchains; + u8 num_gpio_pins; + u8 rx_hp_qdepth; + u8 rx_lp_qdepth; + u8 rx_status_len; + u8 tx_desc_len; + u8 txs_len; +}; + +struct ath9k_ops_config { + int dma_beacon_response_time; + int sw_beacon_response_time; + int additional_swba_backoff; + int ack_6mb; + u32 cwm_ignore_extcca; + bool pcieSerDesWrite; + u8 pcie_clock_req; + u32 pcie_waen; + u8 analog_shiftreg; + u32 ofdm_trig_low; + u32 ofdm_trig_high; + u32 cck_trig_high; + u32 cck_trig_low; + u32 enable_ani; + u32 enable_paprd; + int serialize_regmode; + bool rx_intr_mitigation; + bool tx_intr_mitigation; +#define SPUR_DISABLE 0 +#define SPUR_ENABLE_IOCTL 1 +#define SPUR_ENABLE_EEPROM 2 +#define AR_SPUR_5413_1 1640 +#define AR_SPUR_5413_2 1200 +#define AR_NO_SPUR 0x8000 +#define AR_BASE_FREQ_2GHZ 2300 +#define AR_BASE_FREQ_5GHZ 4900 +#define AR_SPUR_FEEQ_BOUND_HT40 19 +#define AR_SPUR_FEEQ_BOUND_HT20 10 + int spurmode; + u16 spurchans[AR_EEPROM_MODAL_SPURS][2]; + u8 max_txtrig_level; + u16 ani_poll_interval; /* ANI poll interval in ms */ +}; + +enum ath9k_int { + ATH9K_INT_RX = 0x00000001, + ATH9K_INT_RXDESC = 0x00000002, + ATH9K_INT_RXHP = 0x00000001, + ATH9K_INT_RXLP = 0x00000002, + ATH9K_INT_RXNOFRM = 0x00000008, + ATH9K_INT_RXEOL = 0x00000010, + ATH9K_INT_RXORN = 0x00000020, + ATH9K_INT_TX = 0x00000040, + ATH9K_INT_TXDESC = 0x00000080, + ATH9K_INT_TIM_TIMER = 0x00000100, + ATH9K_INT_MCI = 0x00000200, + ATH9K_INT_BB_WATCHDOG = 0x00000400, + ATH9K_INT_TXURN = 0x00000800, + ATH9K_INT_MIB = 0x00001000, + ATH9K_INT_RXPHY = 0x00004000, + ATH9K_INT_RXKCM = 0x00008000, + ATH9K_INT_SWBA = 0x00010000, + ATH9K_INT_BMISS = 0x00040000, + ATH9K_INT_BNR = 0x00100000, + ATH9K_INT_TIM = 0x00200000, + ATH9K_INT_DTIM = 0x00400000, + ATH9K_INT_DTIMSYNC = 0x00800000, + ATH9K_INT_GPIO = 0x01000000, + ATH9K_INT_CABEND = 0x02000000, + ATH9K_INT_TSFOOR = 0x04000000, + ATH9K_INT_GENTIMER = 0x08000000, + ATH9K_INT_CST = 0x10000000, + ATH9K_INT_GTT = 0x20000000, + ATH9K_INT_FATAL = 0x40000000, + ATH9K_INT_GLOBAL = 0x80000000, + ATH9K_INT_BMISC = ATH9K_INT_TIM | + ATH9K_INT_DTIM | + ATH9K_INT_DTIMSYNC | + ATH9K_INT_TSFOOR | + ATH9K_INT_CABEND, + ATH9K_INT_COMMON = ATH9K_INT_RXNOFRM | + ATH9K_INT_RXDESC | + ATH9K_INT_RXEOL | + ATH9K_INT_RXORN | + ATH9K_INT_TXURN | + ATH9K_INT_TXDESC | + ATH9K_INT_MIB | + ATH9K_INT_RXPHY | + ATH9K_INT_RXKCM | + ATH9K_INT_SWBA | + ATH9K_INT_BMISS | + ATH9K_INT_GPIO, + ATH9K_INT_NOCARD = 0xffffffff +}; + +#define CHANNEL_CCK 0x00020 +#define CHANNEL_OFDM 0x00040 +#define CHANNEL_2GHZ 0x00080 +#define CHANNEL_5GHZ 0x00100 +#define CHANNEL_PASSIVE 0x00200 +#define CHANNEL_DYN 0x00400 +#define CHANNEL_HALF 0x04000 +#define CHANNEL_QUARTER 0x08000 +#define CHANNEL_HT20 0x10000 +#define CHANNEL_HT40PLUS 0x20000 +#define CHANNEL_HT40MINUS 0x40000 + +#define CHANNEL_A (CHANNEL_5GHZ|CHANNEL_OFDM) +#define CHANNEL_B (CHANNEL_2GHZ|CHANNEL_CCK) +#define CHANNEL_G (CHANNEL_2GHZ|CHANNEL_OFDM) +#define CHANNEL_G_HT20 (CHANNEL_2GHZ|CHANNEL_HT20) +#define CHANNEL_A_HT20 (CHANNEL_5GHZ|CHANNEL_HT20) +#define CHANNEL_G_HT40PLUS (CHANNEL_2GHZ|CHANNEL_HT40PLUS) +#define CHANNEL_G_HT40MINUS (CHANNEL_2GHZ|CHANNEL_HT40MINUS) +#define CHANNEL_A_HT40PLUS (CHANNEL_5GHZ|CHANNEL_HT40PLUS) +#define CHANNEL_A_HT40MINUS (CHANNEL_5GHZ|CHANNEL_HT40MINUS) +#define CHANNEL_ALL \ + (CHANNEL_OFDM| \ + CHANNEL_CCK| \ + CHANNEL_2GHZ | \ + CHANNEL_5GHZ | \ + CHANNEL_HT20 | \ + CHANNEL_HT40PLUS | \ + CHANNEL_HT40MINUS) + +#define MAX_RTT_TABLE_ENTRY 6 +#define MAX_IQCAL_MEASUREMENT 8 +#define MAX_CL_TAB_ENTRY 16 +#define CL_TAB_ENTRY(reg_base) (reg_base + (4 * j)) + +struct ath9k_hw_cal_data { + u16 channel; + u32 channelFlags; + u32 chanmode; + int32_t CalValid; + int8_t iCoff; + int8_t qCoff; + bool rtt_done; + bool paprd_packet_sent; + bool paprd_done; + bool nfcal_pending; + bool nfcal_interference; + bool done_txiqcal_once; + bool done_txclcal_once; + u16 small_signal_gain[AR9300_MAX_CHAINS]; + u32 pa_table[AR9300_MAX_CHAINS][PAPRD_TABLE_SZ]; + u32 num_measures[AR9300_MAX_CHAINS]; + int tx_corr_coeff[MAX_IQCAL_MEASUREMENT][AR9300_MAX_CHAINS]; + u32 tx_clcal[AR9300_MAX_CHAINS][MAX_CL_TAB_ENTRY]; + u32 rtt_table[AR9300_MAX_CHAINS][MAX_RTT_TABLE_ENTRY]; + struct ath9k_nfcal_hist nfCalHist[NUM_NF_READINGS]; +}; + +struct ath9k_channel { + struct ieee80211_channel *chan; + struct ar5416AniState ani; + u16 channel; + u32 channelFlags; + u32 chanmode; + s16 noisefloor; +}; + +#define IS_CHAN_G(_c) ((((_c)->channelFlags & (CHANNEL_G)) == CHANNEL_G) || \ + (((_c)->channelFlags & CHANNEL_G_HT20) == CHANNEL_G_HT20) || \ + (((_c)->channelFlags & CHANNEL_G_HT40PLUS) == CHANNEL_G_HT40PLUS) || \ + (((_c)->channelFlags & CHANNEL_G_HT40MINUS) == CHANNEL_G_HT40MINUS)) +#define IS_CHAN_OFDM(_c) (((_c)->channelFlags & CHANNEL_OFDM) != 0) +#define IS_CHAN_5GHZ(_c) (((_c)->channelFlags & CHANNEL_5GHZ) != 0) +#define IS_CHAN_2GHZ(_c) (((_c)->channelFlags & CHANNEL_2GHZ) != 0) +#define IS_CHAN_HALF_RATE(_c) (((_c)->channelFlags & CHANNEL_HALF) != 0) +#define IS_CHAN_QUARTER_RATE(_c) (((_c)->channelFlags & CHANNEL_QUARTER) != 0) +#define IS_CHAN_A_FAST_CLOCK(_ah, _c) \ + ((((_c)->channelFlags & CHANNEL_5GHZ) != 0) && \ + ((_ah)->caps.hw_caps & ATH9K_HW_CAP_FASTCLOCK)) + +/* These macros check chanmode and not channelFlags */ +#define IS_CHAN_B(_c) ((_c)->chanmode == CHANNEL_B) +#define IS_CHAN_HT20(_c) (((_c)->chanmode == CHANNEL_A_HT20) || \ + ((_c)->chanmode == CHANNEL_G_HT20)) +#define IS_CHAN_HT40(_c) (((_c)->chanmode == CHANNEL_A_HT40PLUS) || \ + ((_c)->chanmode == CHANNEL_A_HT40MINUS) || \ + ((_c)->chanmode == CHANNEL_G_HT40PLUS) || \ + ((_c)->chanmode == CHANNEL_G_HT40MINUS)) +#define IS_CHAN_HT(_c) (IS_CHAN_HT20((_c)) || IS_CHAN_HT40((_c))) + +enum ath9k_power_mode { + ATH9K_PM_AWAKE = 0, + ATH9K_PM_FULL_SLEEP, + ATH9K_PM_NETWORK_SLEEP, + ATH9K_PM_UNDEFINED +}; + +enum ser_reg_mode { + SER_REG_MODE_OFF = 0, + SER_REG_MODE_ON = 1, + SER_REG_MODE_AUTO = 2, +}; + +enum ath9k_rx_qtype { + ATH9K_RX_QUEUE_HP, + ATH9K_RX_QUEUE_LP, + ATH9K_RX_QUEUE_MAX, +}; + +struct ath9k_beacon_state { + u32 bs_nexttbtt; + u32 bs_nextdtim; + u32 bs_intval; +#define ATH9K_TSFOOR_THRESHOLD 0x00004240 /* 16k us */ + u32 bs_dtimperiod; + u16 bs_cfpperiod; + u16 bs_cfpmaxduration; + u32 bs_cfpnext; + u16 bs_timoffset; + u16 bs_bmissthreshold; + u32 bs_sleepduration; + u32 bs_tsfoor_threshold; +}; + +struct chan_centers { + u16 synth_center; + u16 ctl_center; + u16 ext_center; +}; + +enum { + ATH9K_RESET_POWER_ON, + ATH9K_RESET_WARM, + ATH9K_RESET_COLD, +}; + +struct ath9k_hw_version { + u32 magic; + u16 devid; + u16 subvendorid; + u32 macVersion; + u16 macRev; + u16 phyRev; + u16 analog5GhzRev; + u16 analog2GhzRev; + enum ath_usb_dev usbdev; +}; + +/* Generic TSF timer definitions */ + +#define ATH_MAX_GEN_TIMER 16 + +#define AR_GENTMR_BIT(_index) (1 << (_index)) + +/* + * Using de Bruijin sequence to look up 1's index in a 32 bit number + * debruijn32 = 0000 0111 0111 1100 1011 0101 0011 0001 + */ +#define debruijn32 0x077CB531U + +struct ath_gen_timer_configuration { + u32 next_addr; + u32 period_addr; + u32 mode_addr; + u32 mode_mask; +}; + +struct ath_gen_timer { + void (*trigger)(void *arg); + void (*overflow)(void *arg); + void *arg; + u8 index; +}; + +struct ath_gen_timer_table { + u32 gen_timer_index[32]; + struct ath_gen_timer *timers[ATH_MAX_GEN_TIMER]; + union { + unsigned long timer_bits; + u16 val; + } timer_mask; +}; + +struct ath_hw_antcomb_conf { + u8 main_lna_conf; + u8 alt_lna_conf; + u8 fast_div_bias; + u8 main_gaintb; + u8 alt_gaintb; + int lna1_lna2_delta; + u8 div_group; +}; + +/** + * struct ath_hw_radar_conf - radar detection initialization parameters + * + * @pulse_inband: threshold for checking the ratio of in-band power + * to total power for short radar pulses (half dB steps) + * @pulse_inband_step: threshold for checking an in-band power to total + * power ratio increase for short radar pulses (half dB steps) + * @pulse_height: threshold for detecting the beginning of a short + * radar pulse (dB step) + * @pulse_rssi: threshold for detecting if a short radar pulse is + * gone (dB step) + * @pulse_maxlen: maximum pulse length (0.8 us steps) + * + * @radar_rssi: RSSI threshold for starting long radar detection (dB steps) + * @radar_inband: threshold for checking the ratio of in-band power + * to total power for long radar pulses (half dB steps) + * @fir_power: threshold for detecting the end of a long radar pulse (dB) + * + * @ext_channel: enable extension channel radar detection + */ +struct ath_hw_radar_conf { + unsigned int pulse_inband; + unsigned int pulse_inband_step; + unsigned int pulse_height; + unsigned int pulse_rssi; + unsigned int pulse_maxlen; + + unsigned int radar_rssi; + unsigned int radar_inband; + int fir_power; + + bool ext_channel; +}; + +/** + * struct ath_hw_private_ops - callbacks used internally by hardware code + * + * This structure contains private callbacks designed to only be used internally + * by the hardware core. + * + * @init_cal_settings: setup types of calibrations supported + * @init_cal: starts actual calibration + * + * @init_mode_gain_regs: Initialize TX/RX gain registers + * + * @rf_set_freq: change frequency + * @spur_mitigate_freq: spur mitigation + * @set_rf_regs: + * @compute_pll_control: compute the PLL control value to use for + * AR_RTC_PLL_CONTROL for a given channel + * @setup_calibration: set up calibration + * @iscal_supported: used to query if a type of calibration is supported + * + * @ani_cache_ini_regs: cache the values for ANI from the initial + * register settings through the register initialization. + */ +struct ath_hw_private_ops { + /* Calibration ops */ + void (*init_cal_settings)(struct ath_hw *ah); + bool (*init_cal)(struct ath_hw *ah, struct ath9k_channel *chan); + + void (*init_mode_gain_regs)(struct ath_hw *ah); + void (*setup_calibration)(struct ath_hw *ah, + struct ath9k_cal_list *currCal); + + /* PHY ops */ + int (*rf_set_freq)(struct ath_hw *ah, + struct ath9k_channel *chan); + void (*spur_mitigate_freq)(struct ath_hw *ah, + struct ath9k_channel *chan); + bool (*set_rf_regs)(struct ath_hw *ah, + struct ath9k_channel *chan, + u16 modesIndex); + void (*set_channel_regs)(struct ath_hw *ah, struct ath9k_channel *chan); + void (*init_bb)(struct ath_hw *ah, + struct ath9k_channel *chan); + int (*process_ini)(struct ath_hw *ah, struct ath9k_channel *chan); + void (*olc_init)(struct ath_hw *ah); + void (*set_rfmode)(struct ath_hw *ah, struct ath9k_channel *chan); + void (*mark_phy_inactive)(struct ath_hw *ah); + void (*set_delta_slope)(struct ath_hw *ah, struct ath9k_channel *chan); + bool (*rfbus_req)(struct ath_hw *ah); + void (*rfbus_done)(struct ath_hw *ah); + void (*restore_chainmask)(struct ath_hw *ah); + u32 (*compute_pll_control)(struct ath_hw *ah, + struct ath9k_channel *chan); + bool (*ani_control)(struct ath_hw *ah, enum ath9k_ani_cmd cmd, + int param); + void (*do_getnf)(struct ath_hw *ah, int16_t nfarray[NUM_NF_READINGS]); + void (*set_radar_params)(struct ath_hw *ah, + struct ath_hw_radar_conf *conf); + int (*fast_chan_change)(struct ath_hw *ah, struct ath9k_channel *chan, + u8 *ini_reloaded); + + /* ANI */ + void (*ani_cache_ini_regs)(struct ath_hw *ah); +}; + +/** + * struct ath_spec_scan - parameters for Atheros spectral scan + * + * @enabled: enable/disable spectral scan + * @short_repeat: controls whether the chip is in spectral scan mode + * for 4 usec (enabled) or 204 usec (disabled) + * @count: number of scan results requested. There are special meanings + * in some chip revisions: + * AR92xx: highest bit set (>=128) for endless mode + * (spectral scan won't stopped until explicitly disabled) + * AR9300 and newer: 0 for endless mode + * @endless: true if endless mode is intended. Otherwise, count value is + * corrected to the next possible value. + * @period: time duration between successive spectral scan entry points + * (period*256*Tclk). Tclk = ath_common->clockrate + * @fft_period: PHY passes FFT frames to MAC every (fft_period+1)*4uS + * + * Note: Tclk = 40MHz or 44MHz depending upon operating mode. + * Typically it's 44MHz in 2/5GHz on later chips, but there's + * a "fast clock" check for this in 5GHz. + * + */ +struct ath_spec_scan { + bool enabled; + bool short_repeat; + bool endless; + u8 count; + u8 period; + u8 fft_period; +}; + +/** + * struct ath_hw_ops - callbacks used by hardware code and driver code + * + * This structure contains callbacks designed to to be used internally by + * hardware code and also by the lower level driver. + * + * @config_pci_powersave: + * @calibrate: periodic calibration for NF, ANI, IQ, ADC gain, ADC-DC + * + * @spectral_scan_config: set parameters for spectral scan and enable/disable it + * @spectral_scan_trigger: trigger a spectral scan run + * @spectral_scan_wait: wait for a spectral scan run to finish + */ +struct ath_hw_ops { + void (*config_pci_powersave)(struct ath_hw *ah, + bool power_off); + void (*rx_enable)(struct ath_hw *ah); + void (*set_desc_link)(void *ds, u32 link); + bool (*calibrate)(struct ath_hw *ah, + struct ath9k_channel *chan, + u8 rxchainmask, + bool longcal); + bool (*get_isr)(struct ath_hw *ah, enum ath9k_int *masked); + void (*set_txdesc)(struct ath_hw *ah, void *ds, + struct ath_tx_info *i); + int (*proc_txdesc)(struct ath_hw *ah, void *ds, + struct ath_tx_status *ts); + void (*antdiv_comb_conf_get)(struct ath_hw *ah, + struct ath_hw_antcomb_conf *antconf); + void (*antdiv_comb_conf_set)(struct ath_hw *ah, + struct ath_hw_antcomb_conf *antconf); + void (*antctrl_shared_chain_lnadiv)(struct ath_hw *hw, bool enable); + void (*spectral_scan_config)(struct ath_hw *ah, + struct ath_spec_scan *param); + void (*spectral_scan_trigger)(struct ath_hw *ah); + void (*spectral_scan_wait)(struct ath_hw *ah); +}; + +struct ath_nf_limits { + s16 max; + s16 min; + s16 nominal; +}; + +enum ath_cal_list { + TX_IQ_CAL = BIT(0), + TX_IQ_ON_AGC_CAL = BIT(1), + TX_CL_CAL = BIT(2), +}; + +/* ah_flags */ +#define AH_USE_EEPROM 0x1 +#define AH_UNPLUGGED 0x2 /* The card has been physically removed. */ +#define AH_FASTCC 0x4 + +struct ath_hw { + struct ath_ops reg_ops; + + struct device *dev; + struct ieee80211_hw *hw; + struct ath_common common; + struct ath9k_hw_version hw_version; + struct ath9k_ops_config config; + struct ath9k_hw_capabilities caps; + struct ath9k_channel channels[ATH9K_NUM_CHANNELS]; + struct ath9k_channel *curchan; + + union { + struct ar5416_eeprom_def def; + struct ar5416_eeprom_4k map4k; + struct ar9287_eeprom map9287; + struct ar9300_eeprom ar9300_eep; + } eeprom; + const struct eeprom_ops *eep_ops; + + bool sw_mgmt_crypto; + bool is_pciexpress; + bool aspm_enabled; + bool is_monitoring; + bool need_an_top2_fixup; + bool shared_chain_lnadiv; + u16 tx_trig_level; + + u32 nf_regs[6]; + struct ath_nf_limits nf_2g; + struct ath_nf_limits nf_5g; + u16 rfsilent; + u32 rfkill_gpio; + u32 rfkill_polarity; + u32 ah_flags; + + bool reset_power_on; + bool htc_reset_init; + + enum nl80211_iftype opmode; + enum ath9k_power_mode power_mode; + + s8 noise; + struct ath9k_hw_cal_data *caldata; + struct ath9k_pacal_info pacal_info; + struct ar5416Stats stats; + struct ath9k_tx_queue_info txq[ATH9K_NUM_TX_QUEUES]; + + enum ath9k_int imask; + u32 imrs2_reg; + u32 txok_interrupt_mask; + u32 txerr_interrupt_mask; + u32 txdesc_interrupt_mask; + u32 txeol_interrupt_mask; + u32 txurn_interrupt_mask; + atomic_t intr_ref_cnt; + bool chip_fullsleep; + u32 atim_window; + u32 modes_index; + + /* Calibration */ + u32 supp_cals; + struct ath9k_cal_list iq_caldata; + struct ath9k_cal_list adcgain_caldata; + struct ath9k_cal_list adcdc_caldata; + struct ath9k_cal_list *cal_list; + struct ath9k_cal_list *cal_list_last; + struct ath9k_cal_list *cal_list_curr; +#define totalPowerMeasI meas0.unsign +#define totalPowerMeasQ meas1.unsign +#define totalIqCorrMeas meas2.sign +#define totalAdcIOddPhase meas0.unsign +#define totalAdcIEvenPhase meas1.unsign +#define totalAdcQOddPhase meas2.unsign +#define totalAdcQEvenPhase meas3.unsign +#define totalAdcDcOffsetIOddPhase meas0.sign +#define totalAdcDcOffsetIEvenPhase meas1.sign +#define totalAdcDcOffsetQOddPhase meas2.sign +#define totalAdcDcOffsetQEvenPhase meas3.sign + union { + u32 unsign[AR5416_MAX_CHAINS]; + int32_t sign[AR5416_MAX_CHAINS]; + } meas0; + union { + u32 unsign[AR5416_MAX_CHAINS]; + int32_t sign[AR5416_MAX_CHAINS]; + } meas1; + union { + u32 unsign[AR5416_MAX_CHAINS]; + int32_t sign[AR5416_MAX_CHAINS]; + } meas2; + union { + u32 unsign[AR5416_MAX_CHAINS]; + int32_t sign[AR5416_MAX_CHAINS]; + } meas3; + u16 cal_samples; + u8 enabled_cals; + + u32 sta_id1_defaults; + u32 misc_mode; + + /* Private to hardware code */ + struct ath_hw_private_ops private_ops; + /* Accessed by the lower level driver */ + struct ath_hw_ops ops; + + /* Used to program the radio on non single-chip devices */ + u32 *analogBank6Data; + + int coverage_class; + u32 slottime; + u32 globaltxtimeout; + + /* ANI */ + u32 proc_phyerr; + u32 aniperiod; + enum ath9k_ani_cmd ani_function; + u32 ani_skip_count; + +#ifdef CPTCFG_ATH9K_BTCOEX_SUPPORT + struct ath_btcoex_hw btcoex_hw; +#endif + + u32 intr_txqs; + u8 txchainmask; + u8 rxchainmask; + + struct ath_hw_radar_conf radar_conf; + + u32 originalGain[22]; + int initPDADC; + int PDADCdelta; + int led_pin; + u32 gpio_mask; + u32 gpio_val; + + struct ar5416IniArray iniModes; + struct ar5416IniArray iniCommon; + struct ar5416IniArray iniBB_RfGain; + struct ar5416IniArray iniBank6; + struct ar5416IniArray iniAddac; + struct ar5416IniArray iniPcieSerdes; +#ifdef CONFIG_PM_SLEEP + struct ar5416IniArray iniPcieSerdesWow; +#endif + struct ar5416IniArray iniPcieSerdesLowPower; + struct ar5416IniArray iniModesFastClock; + struct ar5416IniArray iniAdditional; + struct ar5416IniArray iniModesRxGain; + struct ar5416IniArray ini_modes_rx_gain_bounds; + struct ar5416IniArray iniModesTxGain; + struct ar5416IniArray iniCckfirNormal; + struct ar5416IniArray iniCckfirJapan2484; + struct ar5416IniArray iniModes_9271_ANI_reg; + struct ar5416IniArray ini_radio_post_sys2ant; + + struct ar5416IniArray iniMac[ATH_INI_NUM_SPLIT]; + struct ar5416IniArray iniBB[ATH_INI_NUM_SPLIT]; + struct ar5416IniArray iniRadio[ATH_INI_NUM_SPLIT]; + struct ar5416IniArray iniSOC[ATH_INI_NUM_SPLIT]; + + u32 intr_gen_timer_trigger; + u32 intr_gen_timer_thresh; + struct ath_gen_timer_table hw_gen_timers; + + struct ar9003_txs *ts_ring; + u32 ts_paddr_start; + u32 ts_paddr_end; + u16 ts_tail; + u16 ts_size; + + u32 bb_watchdog_last_status; + u32 bb_watchdog_timeout_ms; /* in ms, 0 to disable */ + u8 bb_hang_rx_ofdm; /* true if bb hang due to rx_ofdm */ + + unsigned int paprd_target_power; + unsigned int paprd_training_power; + unsigned int paprd_ratemask; + unsigned int paprd_ratemask_ht40; + bool paprd_table_write_done; + u32 paprd_gain_table_entries[PAPRD_GAIN_TABLE_ENTRIES]; + u8 paprd_gain_table_index[PAPRD_GAIN_TABLE_ENTRIES]; + /* + * Store the permanent value of Reg 0x4004in WARegVal + * so we dont have to R/M/W. We should not be reading + * this register when in sleep states. + */ + u32 WARegVal; + + /* Enterprise mode cap */ + u32 ent_mode; + +#ifdef CONFIG_PM_SLEEP + u32 wow_event_mask; +#endif + bool is_clk_25mhz; + int (*get_mac_revision)(void); + int (*external_reset)(void); + + const struct firmware *eeprom_blob; +}; + +struct ath_bus_ops { + enum ath_bus_type ath_bus_type; + void (*read_cachesize)(struct ath_common *common, int *csz); + bool (*eeprom_read)(struct ath_common *common, u32 off, u16 *data); + void (*bt_coex_prep)(struct ath_common *common); + void (*aspm_init)(struct ath_common *common); +}; + +static inline struct ath_common *ath9k_hw_common(struct ath_hw *ah) +{ + return &ah->common; +} + +static inline struct ath_regulatory *ath9k_hw_regulatory(struct ath_hw *ah) +{ + return &(ath9k_hw_common(ah)->regulatory); +} + +static inline struct ath_hw_private_ops *ath9k_hw_private_ops(struct ath_hw *ah) +{ + return &ah->private_ops; +} + +static inline struct ath_hw_ops *ath9k_hw_ops(struct ath_hw *ah) +{ + return &ah->ops; +} + +static inline u8 get_streams(int mask) +{ + return !!(mask & BIT(0)) + !!(mask & BIT(1)) + !!(mask & BIT(2)); +} + +/* Initialization, Detach, Reset */ +void ath9k_hw_deinit(struct ath_hw *ah); +int ath9k_hw_init(struct ath_hw *ah); +int ath9k_hw_reset(struct ath_hw *ah, struct ath9k_channel *chan, + struct ath9k_hw_cal_data *caldata, bool fastcc); +int ath9k_hw_fill_cap_info(struct ath_hw *ah); +u32 ath9k_regd_get_ctl(struct ath_regulatory *reg, struct ath9k_channel *chan); + +/* GPIO / RFKILL / Antennae */ +void ath9k_hw_cfg_gpio_input(struct ath_hw *ah, u32 gpio); +u32 ath9k_hw_gpio_get(struct ath_hw *ah, u32 gpio); +void ath9k_hw_cfg_output(struct ath_hw *ah, u32 gpio, + u32 ah_signal_type); +void ath9k_hw_set_gpio(struct ath_hw *ah, u32 gpio, u32 val); +void ath9k_hw_setantenna(struct ath_hw *ah, u32 antenna); + +/* General Operation */ +void ath9k_hw_synth_delay(struct ath_hw *ah, struct ath9k_channel *chan, + int hw_delay); +bool ath9k_hw_wait(struct ath_hw *ah, u32 reg, u32 mask, u32 val, u32 timeout); +void ath9k_hw_write_array(struct ath_hw *ah, const struct ar5416IniArray *array, + int column, unsigned int *writecnt); +u32 ath9k_hw_reverse_bits(u32 val, u32 n); +u16 ath9k_hw_computetxtime(struct ath_hw *ah, + u8 phy, int kbps, + u32 frameLen, u16 rateix, bool shortPreamble); +void ath9k_hw_get_channel_centers(struct ath_hw *ah, + struct ath9k_channel *chan, + struct chan_centers *centers); +u32 ath9k_hw_getrxfilter(struct ath_hw *ah); +void ath9k_hw_setrxfilter(struct ath_hw *ah, u32 bits); +bool ath9k_hw_phy_disable(struct ath_hw *ah); +bool ath9k_hw_disable(struct ath_hw *ah); +void ath9k_hw_set_txpowerlimit(struct ath_hw *ah, u32 limit, bool test); +void ath9k_hw_setopmode(struct ath_hw *ah); +void ath9k_hw_setmcastfilter(struct ath_hw *ah, u32 filter0, u32 filter1); +void ath9k_hw_write_associd(struct ath_hw *ah); +u32 ath9k_hw_gettsf32(struct ath_hw *ah); +u64 ath9k_hw_gettsf64(struct ath_hw *ah); +void ath9k_hw_settsf64(struct ath_hw *ah, u64 tsf64); +void ath9k_hw_reset_tsf(struct ath_hw *ah); +void ath9k_hw_set_tsfadjust(struct ath_hw *ah, bool set); +void ath9k_hw_init_global_settings(struct ath_hw *ah); +u32 ar9003_get_pll_sqsum_dvc(struct ath_hw *ah); +void ath9k_hw_set11nmac2040(struct ath_hw *ah); +void ath9k_hw_beaconinit(struct ath_hw *ah, u32 next_beacon, u32 beacon_period); +void ath9k_hw_set_sta_beacon_timers(struct ath_hw *ah, + const struct ath9k_beacon_state *bs); +bool ath9k_hw_check_alive(struct ath_hw *ah); + +bool ath9k_hw_setpower(struct ath_hw *ah, enum ath9k_power_mode mode); + +#ifdef CPTCFG_ATH9K_DEBUGFS +void ath9k_debug_sync_cause(struct ath_common *common, u32 sync_cause); +#else +static inline void ath9k_debug_sync_cause(struct ath_common *common, + u32 sync_cause) {} +#endif + +/* Generic hw timer primitives */ +struct ath_gen_timer *ath_gen_timer_alloc(struct ath_hw *ah, + void (*trigger)(void *), + void (*overflow)(void *), + void *arg, + u8 timer_index); +void ath9k_hw_gen_timer_start(struct ath_hw *ah, + struct ath_gen_timer *timer, + u32 timer_next, + u32 timer_period); +void ath9k_hw_gen_timer_stop(struct ath_hw *ah, struct ath_gen_timer *timer); + +void ath_gen_timer_free(struct ath_hw *ah, struct ath_gen_timer *timer); +void ath_gen_timer_isr(struct ath_hw *hw); + +void ath9k_hw_name(struct ath_hw *ah, char *hw_name, size_t len); + +/* PHY */ +void ath9k_hw_get_delta_slope_vals(struct ath_hw *ah, u32 coef_scaled, + u32 *coef_mantissa, u32 *coef_exponent); +void ath9k_hw_apply_txpower(struct ath_hw *ah, struct ath9k_channel *chan, + bool test); + +/* + * Code Specific to AR5008, AR9001 or AR9002, + * we stuff these here to avoid callbacks for AR9003. + */ +int ar9002_hw_rf_claim(struct ath_hw *ah); +void ar9002_hw_enable_async_fifo(struct ath_hw *ah); + +/* + * Code specific to AR9003, we stuff these here to avoid callbacks + * for older families + */ +void ar9003_hw_bb_watchdog_config(struct ath_hw *ah); +void ar9003_hw_bb_watchdog_read(struct ath_hw *ah); +void ar9003_hw_bb_watchdog_dbg_info(struct ath_hw *ah); +void ar9003_hw_disable_phy_restart(struct ath_hw *ah); +void ar9003_paprd_enable(struct ath_hw *ah, bool val); +void ar9003_paprd_populate_single_table(struct ath_hw *ah, + struct ath9k_hw_cal_data *caldata, + int chain); +int ar9003_paprd_create_curve(struct ath_hw *ah, + struct ath9k_hw_cal_data *caldata, int chain); +void ar9003_paprd_setup_gain_table(struct ath_hw *ah, int chain); +int ar9003_paprd_init_table(struct ath_hw *ah); +bool ar9003_paprd_is_done(struct ath_hw *ah); +bool ar9003_is_paprd_enabled(struct ath_hw *ah); +void ar9003_hw_set_chain_masks(struct ath_hw *ah, u8 rx, u8 tx); + +/* Hardware family op attach helpers */ +int ar5008_hw_attach_phy_ops(struct ath_hw *ah); +void ar9002_hw_attach_phy_ops(struct ath_hw *ah); +void ar9003_hw_attach_phy_ops(struct ath_hw *ah); + +void ar9002_hw_attach_calib_ops(struct ath_hw *ah); +void ar9003_hw_attach_calib_ops(struct ath_hw *ah); + +int ar9002_hw_attach_ops(struct ath_hw *ah); +void ar9003_hw_attach_ops(struct ath_hw *ah); + +void ar9002_hw_load_ani_reg(struct ath_hw *ah, struct ath9k_channel *chan); + +void ath9k_ani_reset(struct ath_hw *ah, bool is_scanning); +void ath9k_hw_ani_monitor(struct ath_hw *ah, struct ath9k_channel *chan); + +#ifdef CPTCFG_ATH9K_BTCOEX_SUPPORT +static inline bool ath9k_hw_btcoex_is_enabled(struct ath_hw *ah) +{ + return ah->btcoex_hw.enabled; +} +static inline bool ath9k_hw_mci_is_enabled(struct ath_hw *ah) +{ + return ah->common.btcoex_enabled && + (ah->caps.hw_caps & ATH9K_HW_CAP_MCI); + +} +void ath9k_hw_btcoex_enable(struct ath_hw *ah); +static inline enum ath_btcoex_scheme +ath9k_hw_get_btcoex_scheme(struct ath_hw *ah) +{ + return ah->btcoex_hw.scheme; +} +#else +static inline bool ath9k_hw_btcoex_is_enabled(struct ath_hw *ah) +{ + return false; +} +static inline bool ath9k_hw_mci_is_enabled(struct ath_hw *ah) +{ + return false; +} +static inline void ath9k_hw_btcoex_enable(struct ath_hw *ah) +{ +} +static inline enum ath_btcoex_scheme +ath9k_hw_get_btcoex_scheme(struct ath_hw *ah) +{ + return ATH_BTCOEX_CFG_NONE; +} +#endif /* CPTCFG_ATH9K_BTCOEX_SUPPORT */ + + +#ifdef CONFIG_PM_SLEEP +const char *ath9k_hw_wow_event_to_string(u32 wow_event); +void ath9k_hw_wow_apply_pattern(struct ath_hw *ah, u8 *user_pattern, + u8 *user_mask, int pattern_count, + int pattern_len); +u32 ath9k_hw_wow_wakeup(struct ath_hw *ah); +void ath9k_hw_wow_enable(struct ath_hw *ah, u32 pattern_enable); +#else +static inline const char *ath9k_hw_wow_event_to_string(u32 wow_event) +{ + return NULL; +} +static inline void ath9k_hw_wow_apply_pattern(struct ath_hw *ah, + u8 *user_pattern, + u8 *user_mask, + int pattern_count, + int pattern_len) +{ +} +static inline u32 ath9k_hw_wow_wakeup(struct ath_hw *ah) +{ + return 0; +} +static inline void ath9k_hw_wow_enable(struct ath_hw *ah, u32 pattern_enable) +{ +} +#endif + + + +#define ATH9K_CLOCK_RATE_CCK 22 +#define ATH9K_CLOCK_RATE_5GHZ_OFDM 40 +#define ATH9K_CLOCK_RATE_2GHZ_OFDM 44 +#define ATH9K_CLOCK_FAST_RATE_5GHZ_OFDM 44 + +#endif diff --git a/drivers/net/wireless/ath/ath9k/init.c b/drivers/net/wireless/ath/ath9k/init.c new file mode 100644 index 0000000..91f44d2 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/init.c @@ -0,0 +1,1011 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include + +#include "ath9k.h" + +struct ath9k_eeprom_ctx { + struct completion complete; + struct ath_hw *ah; +}; + +static char *dev_info = "ath9k"; + +MODULE_AUTHOR("Atheros Communications"); +MODULE_DESCRIPTION("Support for Atheros 802.11n wireless LAN cards."); +MODULE_SUPPORTED_DEVICE("Atheros 802.11n WLAN cards"); +MODULE_LICENSE("Dual BSD/GPL"); + +static unsigned int ath9k_debug = ATH_DBG_DEFAULT; +module_param_named(debug, ath9k_debug, uint, 0); +MODULE_PARM_DESC(debug, "Debugging mask"); + +int ath9k_modparam_nohwcrypt; +module_param_named(nohwcrypt, ath9k_modparam_nohwcrypt, int, 0444); +MODULE_PARM_DESC(nohwcrypt, "Disable hardware encryption"); + +int led_blink; +module_param_named(blink, led_blink, int, 0444); +MODULE_PARM_DESC(blink, "Enable LED blink on activity"); + +static int ath9k_btcoex_enable; +module_param_named(btcoex_enable, ath9k_btcoex_enable, int, 0444); +MODULE_PARM_DESC(btcoex_enable, "Enable wifi-BT coexistence"); + +static int ath9k_enable_diversity; +module_param_named(enable_diversity, ath9k_enable_diversity, int, 0444); +MODULE_PARM_DESC(enable_diversity, "Enable Antenna diversity for AR9565"); + +bool is_ath9k_unloaded; +/* We use the hw_value as an index into our private channel structure */ + +#define CHAN2G(_freq, _idx) { \ + .band = IEEE80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_idx), \ + .max_power = 20, \ +} + +#define CHAN5G(_freq, _idx) { \ + .band = IEEE80211_BAND_5GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_idx), \ + .max_power = 20, \ +} + +/* Some 2 GHz radios are actually tunable on 2312-2732 + * on 5 MHz steps, we support the channels which we know + * we have calibration data for all cards though to make + * this static */ +static const struct ieee80211_channel ath9k_2ghz_chantable[] = { + CHAN2G(2412, 0), /* Channel 1 */ + CHAN2G(2417, 1), /* Channel 2 */ + CHAN2G(2422, 2), /* Channel 3 */ + CHAN2G(2427, 3), /* Channel 4 */ + CHAN2G(2432, 4), /* Channel 5 */ + CHAN2G(2437, 5), /* Channel 6 */ + CHAN2G(2442, 6), /* Channel 7 */ + CHAN2G(2447, 7), /* Channel 8 */ + CHAN2G(2452, 8), /* Channel 9 */ + CHAN2G(2457, 9), /* Channel 10 */ + CHAN2G(2462, 10), /* Channel 11 */ + CHAN2G(2467, 11), /* Channel 12 */ + CHAN2G(2472, 12), /* Channel 13 */ + CHAN2G(2484, 13), /* Channel 14 */ +}; + +/* Some 5 GHz radios are actually tunable on XXXX-YYYY + * on 5 MHz steps, we support the channels which we know + * we have calibration data for all cards though to make + * this static */ +static const struct ieee80211_channel ath9k_5ghz_chantable[] = { + /* _We_ call this UNII 1 */ + CHAN5G(5180, 14), /* Channel 36 */ + CHAN5G(5200, 15), /* Channel 40 */ + CHAN5G(5220, 16), /* Channel 44 */ + CHAN5G(5240, 17), /* Channel 48 */ + /* _We_ call this UNII 2 */ + CHAN5G(5260, 18), /* Channel 52 */ + CHAN5G(5280, 19), /* Channel 56 */ + CHAN5G(5300, 20), /* Channel 60 */ + CHAN5G(5320, 21), /* Channel 64 */ + /* _We_ call this "Middle band" */ + CHAN5G(5500, 22), /* Channel 100 */ + CHAN5G(5520, 23), /* Channel 104 */ + CHAN5G(5540, 24), /* Channel 108 */ + CHAN5G(5560, 25), /* Channel 112 */ + CHAN5G(5580, 26), /* Channel 116 */ + CHAN5G(5600, 27), /* Channel 120 */ + CHAN5G(5620, 28), /* Channel 124 */ + CHAN5G(5640, 29), /* Channel 128 */ + CHAN5G(5660, 30), /* Channel 132 */ + CHAN5G(5680, 31), /* Channel 136 */ + CHAN5G(5700, 32), /* Channel 140 */ + /* _We_ call this UNII 3 */ + CHAN5G(5745, 33), /* Channel 149 */ + CHAN5G(5765, 34), /* Channel 153 */ + CHAN5G(5785, 35), /* Channel 157 */ + CHAN5G(5805, 36), /* Channel 161 */ + CHAN5G(5825, 37), /* Channel 165 */ +}; + +/* Atheros hardware rate code addition for short premble */ +#define SHPCHECK(__hw_rate, __flags) \ + ((__flags & IEEE80211_RATE_SHORT_PREAMBLE) ? (__hw_rate | 0x04 ) : 0) + +#define RATE(_bitrate, _hw_rate, _flags) { \ + .bitrate = (_bitrate), \ + .flags = (_flags), \ + .hw_value = (_hw_rate), \ + .hw_value_short = (SHPCHECK(_hw_rate, _flags)) \ +} + +static struct ieee80211_rate ath9k_legacy_rates[] = { + RATE(10, 0x1b, 0), + RATE(20, 0x1a, IEEE80211_RATE_SHORT_PREAMBLE), + RATE(55, 0x19, IEEE80211_RATE_SHORT_PREAMBLE), + RATE(110, 0x18, IEEE80211_RATE_SHORT_PREAMBLE), + RATE(60, 0x0b, 0), + RATE(90, 0x0f, 0), + RATE(120, 0x0a, 0), + RATE(180, 0x0e, 0), + RATE(240, 0x09, 0), + RATE(360, 0x0d, 0), + RATE(480, 0x08, 0), + RATE(540, 0x0c, 0), +}; + +#ifdef CPTCFG_MAC80211_LEDS +static const struct ieee80211_tpt_blink ath9k_tpt_blink[] = { + { .throughput = 0 * 1024, .blink_time = 334 }, + { .throughput = 1 * 1024, .blink_time = 260 }, + { .throughput = 5 * 1024, .blink_time = 220 }, + { .throughput = 10 * 1024, .blink_time = 190 }, + { .throughput = 20 * 1024, .blink_time = 170 }, + { .throughput = 50 * 1024, .blink_time = 150 }, + { .throughput = 70 * 1024, .blink_time = 130 }, + { .throughput = 100 * 1024, .blink_time = 110 }, + { .throughput = 200 * 1024, .blink_time = 80 }, + { .throughput = 300 * 1024, .blink_time = 50 }, +}; +#endif + +static void ath9k_deinit_softc(struct ath_softc *sc); + +/* + * Read and write, they both share the same lock. We do this to serialize + * reads and writes on Atheros 802.11n PCI devices only. This is required + * as the FIFO on these devices can only accept sanely 2 requests. + */ + +static void ath9k_iowrite32(void *hw_priv, u32 val, u32 reg_offset) +{ + struct ath_hw *ah = (struct ath_hw *) hw_priv; + struct ath_common *common = ath9k_hw_common(ah); + struct ath_softc *sc = (struct ath_softc *) common->priv; + + if (NR_CPUS > 1 && ah->config.serialize_regmode == SER_REG_MODE_ON) { + unsigned long flags; + spin_lock_irqsave(&sc->sc_serial_rw, flags); + iowrite32(val, sc->mem + reg_offset); + spin_unlock_irqrestore(&sc->sc_serial_rw, flags); + } else + iowrite32(val, sc->mem + reg_offset); +} + +static unsigned int ath9k_ioread32(void *hw_priv, u32 reg_offset) +{ + struct ath_hw *ah = (struct ath_hw *) hw_priv; + struct ath_common *common = ath9k_hw_common(ah); + struct ath_softc *sc = (struct ath_softc *) common->priv; + u32 val; + + if (NR_CPUS > 1 && ah->config.serialize_regmode == SER_REG_MODE_ON) { + unsigned long flags; + spin_lock_irqsave(&sc->sc_serial_rw, flags); + val = ioread32(sc->mem + reg_offset); + spin_unlock_irqrestore(&sc->sc_serial_rw, flags); + } else + val = ioread32(sc->mem + reg_offset); + return val; +} + +static unsigned int __ath9k_reg_rmw(struct ath_softc *sc, u32 reg_offset, + u32 set, u32 clr) +{ + u32 val; + + val = ioread32(sc->mem + reg_offset); + val &= ~clr; + val |= set; + iowrite32(val, sc->mem + reg_offset); + + return val; +} + +static unsigned int ath9k_reg_rmw(void *hw_priv, u32 reg_offset, u32 set, u32 clr) +{ + struct ath_hw *ah = (struct ath_hw *) hw_priv; + struct ath_common *common = ath9k_hw_common(ah); + struct ath_softc *sc = (struct ath_softc *) common->priv; + unsigned long uninitialized_var(flags); + u32 val; + + if (NR_CPUS > 1 && ah->config.serialize_regmode == SER_REG_MODE_ON) { + spin_lock_irqsave(&sc->sc_serial_rw, flags); + val = __ath9k_reg_rmw(sc, reg_offset, set, clr); + spin_unlock_irqrestore(&sc->sc_serial_rw, flags); + } else + val = __ath9k_reg_rmw(sc, reg_offset, set, clr); + + return val; +} + +/**************************/ +/* Initialization */ +/**************************/ + +static void setup_ht_cap(struct ath_softc *sc, + struct ieee80211_sta_ht_cap *ht_info) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + u8 tx_streams, rx_streams; + int i, max_streams; + + ht_info->ht_supported = true; + ht_info->cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 | + IEEE80211_HT_CAP_SM_PS | + IEEE80211_HT_CAP_SGI_40 | + IEEE80211_HT_CAP_DSSSCCK40; + + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_LDPC) + ht_info->cap |= IEEE80211_HT_CAP_LDPC_CODING; + + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_SGI_20) + ht_info->cap |= IEEE80211_HT_CAP_SGI_20; + + ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + ht_info->ampdu_density = IEEE80211_HT_MPDU_DENSITY_8; + + if (AR_SREV_9330(ah) || AR_SREV_9485(ah) || AR_SREV_9565(ah)) + max_streams = 1; + else if (AR_SREV_9462(ah)) + max_streams = 2; + else if (AR_SREV_9300_20_OR_LATER(ah)) + max_streams = 3; + else + max_streams = 2; + + if (AR_SREV_9280_20_OR_LATER(ah)) { + if (max_streams >= 2) + ht_info->cap |= IEEE80211_HT_CAP_TX_STBC; + ht_info->cap |= (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT); + } + + /* set up supported mcs set */ + memset(&ht_info->mcs, 0, sizeof(ht_info->mcs)); + tx_streams = ath9k_cmn_count_streams(ah->txchainmask, max_streams); + rx_streams = ath9k_cmn_count_streams(ah->rxchainmask, max_streams); + + ath_dbg(common, CONFIG, "TX streams %d, RX streams: %d\n", + tx_streams, rx_streams); + + if (tx_streams != rx_streams) { + ht_info->mcs.tx_params |= IEEE80211_HT_MCS_TX_RX_DIFF; + ht_info->mcs.tx_params |= ((tx_streams - 1) << + IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT); + } + + for (i = 0; i < rx_streams; i++) + ht_info->mcs.rx_mask[i] = 0xff; + + ht_info->mcs.tx_params |= IEEE80211_HT_MCS_TX_DEFINED; +} + +static void ath9k_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + struct ath_regulatory *reg = ath9k_hw_regulatory(ah); + + ath_reg_notifier_apply(wiphy, request, reg); + + /* Set tx power */ + if (ah->curchan) { + sc->config.txpowlimit = 2 * ah->curchan->chan->max_power; + ath9k_ps_wakeup(sc); + ath9k_hw_set_txpowerlimit(ah, sc->config.txpowlimit, false); + sc->curtxpow = ath9k_hw_regulatory(ah)->power_limit; + /* synchronize DFS detector if regulatory domain changed */ + if (sc->dfs_detector != NULL) + sc->dfs_detector->set_dfs_domain(sc->dfs_detector, + request->dfs_region); + ath9k_ps_restore(sc); + } +} + +/* + * This function will allocate both the DMA descriptor structure, and the + * buffers it contains. These are used to contain the descriptors used + * by the system. +*/ +int ath_descdma_setup(struct ath_softc *sc, struct ath_descdma *dd, + struct list_head *head, const char *name, + int nbuf, int ndesc, bool is_tx) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + u8 *ds; + struct ath_buf *bf; + int i, bsize, desc_len; + + ath_dbg(common, CONFIG, "%s DMA: %u buffers %u desc/buf\n", + name, nbuf, ndesc); + + INIT_LIST_HEAD(head); + + if (is_tx) + desc_len = sc->sc_ah->caps.tx_desc_len; + else + desc_len = sizeof(struct ath_desc); + + /* ath_desc must be a multiple of DWORDs */ + if ((desc_len % 4) != 0) { + ath_err(common, "ath_desc not DWORD aligned\n"); + BUG_ON((desc_len % 4) != 0); + return -ENOMEM; + } + + dd->dd_desc_len = desc_len * nbuf * ndesc; + + /* + * Need additional DMA memory because we can't use + * descriptors that cross the 4K page boundary. Assume + * one skipped descriptor per 4K page. + */ + if (!(sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_4KB_SPLITTRANS)) { + u32 ndesc_skipped = + ATH_DESC_4KB_BOUND_NUM_SKIPPED(dd->dd_desc_len); + u32 dma_len; + + while (ndesc_skipped) { + dma_len = ndesc_skipped * desc_len; + dd->dd_desc_len += dma_len; + + ndesc_skipped = ATH_DESC_4KB_BOUND_NUM_SKIPPED(dma_len); + } + } + + /* allocate descriptors */ + dd->dd_desc = dmam_alloc_coherent(sc->dev, dd->dd_desc_len, + &dd->dd_desc_paddr, GFP_KERNEL); + if (!dd->dd_desc) + return -ENOMEM; + + ds = (u8 *) dd->dd_desc; + ath_dbg(common, CONFIG, "%s DMA map: %p (%u) -> %llx (%u)\n", + name, ds, (u32) dd->dd_desc_len, + ito64(dd->dd_desc_paddr), /*XXX*/(u32) dd->dd_desc_len); + + /* allocate buffers */ + bsize = sizeof(struct ath_buf) * nbuf; + bf = devm_kzalloc(sc->dev, bsize, GFP_KERNEL); + if (!bf) + return -ENOMEM; + + for (i = 0; i < nbuf; i++, bf++, ds += (desc_len * ndesc)) { + bf->bf_desc = ds; + bf->bf_daddr = DS2PHYS(dd, ds); + + if (!(sc->sc_ah->caps.hw_caps & + ATH9K_HW_CAP_4KB_SPLITTRANS)) { + /* + * Skip descriptor addresses which can cause 4KB + * boundary crossing (addr + length) with a 32 dword + * descriptor fetch. + */ + while (ATH_DESC_4KB_BOUND_CHECK(bf->bf_daddr)) { + BUG_ON((caddr_t) bf->bf_desc >= + ((caddr_t) dd->dd_desc + + dd->dd_desc_len)); + + ds += (desc_len * ndesc); + bf->bf_desc = ds; + bf->bf_daddr = DS2PHYS(dd, ds); + } + } + list_add_tail(&bf->list, head); + } + return 0; +} + +static int ath9k_init_queues(struct ath_softc *sc) +{ + int i = 0; + + sc->beacon.beaconq = ath9k_hw_beaconq_setup(sc->sc_ah); + sc->beacon.cabq = ath_txq_setup(sc, ATH9K_TX_QUEUE_CAB, 0); + + sc->config.cabqReadytime = ATH_CABQ_READY_TIME; + ath_cabq_update(sc); + + for (i = 0; i < IEEE80211_NUM_ACS; i++) { + sc->tx.txq_map[i] = ath_txq_setup(sc, ATH9K_TX_QUEUE_DATA, i); + sc->tx.txq_map[i]->mac80211_qnum = i; + sc->tx.txq_max_pending[i] = ATH_MAX_QDEPTH; + } + return 0; +} + +static int ath9k_init_channels_rates(struct ath_softc *sc) +{ + void *channels; + + BUILD_BUG_ON(ARRAY_SIZE(ath9k_2ghz_chantable) + + ARRAY_SIZE(ath9k_5ghz_chantable) != + ATH9K_NUM_CHANNELS); + + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_2GHZ) { + channels = devm_kzalloc(sc->dev, + sizeof(ath9k_2ghz_chantable), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + memcpy(channels, ath9k_2ghz_chantable, + sizeof(ath9k_2ghz_chantable)); + sc->sbands[IEEE80211_BAND_2GHZ].channels = channels; + sc->sbands[IEEE80211_BAND_2GHZ].band = IEEE80211_BAND_2GHZ; + sc->sbands[IEEE80211_BAND_2GHZ].n_channels = + ARRAY_SIZE(ath9k_2ghz_chantable); + sc->sbands[IEEE80211_BAND_2GHZ].bitrates = ath9k_legacy_rates; + sc->sbands[IEEE80211_BAND_2GHZ].n_bitrates = + ARRAY_SIZE(ath9k_legacy_rates); + } + + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_5GHZ) { + channels = devm_kzalloc(sc->dev, + sizeof(ath9k_5ghz_chantable), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + memcpy(channels, ath9k_5ghz_chantable, + sizeof(ath9k_5ghz_chantable)); + sc->sbands[IEEE80211_BAND_5GHZ].channels = channels; + sc->sbands[IEEE80211_BAND_5GHZ].band = IEEE80211_BAND_5GHZ; + sc->sbands[IEEE80211_BAND_5GHZ].n_channels = + ARRAY_SIZE(ath9k_5ghz_chantable); + sc->sbands[IEEE80211_BAND_5GHZ].bitrates = + ath9k_legacy_rates + 4; + sc->sbands[IEEE80211_BAND_5GHZ].n_bitrates = + ARRAY_SIZE(ath9k_legacy_rates) - 4; + } + return 0; +} + +static void ath9k_init_misc(struct ath_softc *sc) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + int i = 0; + + setup_timer(&common->ani.timer, ath_ani_calibrate, (unsigned long)sc); + + sc->last_rssi = ATH_RSSI_DUMMY_MARKER; + sc->config.txpowlimit = ATH_TXPOWER_MAX; + memcpy(common->bssidmask, ath_bcast_mac, ETH_ALEN); + sc->beacon.slottime = ATH9K_SLOT_TIME_9; + + for (i = 0; i < ARRAY_SIZE(sc->beacon.bslot); i++) + sc->beacon.bslot[i] = NULL; + + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_ANT_DIV_COMB) + sc->ant_comb.count = ATH_ANT_DIV_COMB_INIT_COUNT; + + sc->spec_config.enabled = 0; + sc->spec_config.short_repeat = true; + sc->spec_config.count = 8; + sc->spec_config.endless = false; + sc->spec_config.period = 0xFF; + sc->spec_config.fft_period = 0xF; +} + +static void ath9k_eeprom_request_cb(const struct firmware *eeprom_blob, + void *ctx) +{ + struct ath9k_eeprom_ctx *ec = ctx; + + if (eeprom_blob) + ec->ah->eeprom_blob = eeprom_blob; + + complete(&ec->complete); +} + +static int ath9k_eeprom_request(struct ath_softc *sc, const char *name) +{ + struct ath9k_eeprom_ctx ec; + struct ath_hw *ah = ah = sc->sc_ah; + int err; + + /* try to load the EEPROM content asynchronously */ + init_completion(&ec.complete); + ec.ah = sc->sc_ah; + + err = request_firmware_nowait(THIS_MODULE, 1, name, sc->dev, GFP_KERNEL, + &ec, ath9k_eeprom_request_cb); + if (err < 0) { + ath_err(ath9k_hw_common(ah), + "EEPROM request failed\n"); + return err; + } + + wait_for_completion(&ec.complete); + + if (!ah->eeprom_blob) { + ath_err(ath9k_hw_common(ah), + "Unable to load EEPROM file %s\n", name); + return -EINVAL; + } + + return 0; +} + +static void ath9k_eeprom_release(struct ath_softc *sc) +{ + release_firmware(sc->sc_ah->eeprom_blob); +} + +static int ath9k_init_softc(u16 devid, struct ath_softc *sc, + const struct ath_bus_ops *bus_ops) +{ + struct ath9k_platform_data *pdata = sc->dev->platform_data; + struct ath_hw *ah = NULL; + struct ath_common *common; + int ret = 0, i; + int csz = 0; + + ah = devm_kzalloc(sc->dev, sizeof(struct ath_hw), GFP_KERNEL); + if (!ah) + return -ENOMEM; + + ah->dev = sc->dev; + ah->hw = sc->hw; + ah->hw_version.devid = devid; + ah->reg_ops.read = ath9k_ioread32; + ah->reg_ops.write = ath9k_iowrite32; + ah->reg_ops.rmw = ath9k_reg_rmw; + atomic_set(&ah->intr_ref_cnt, -1); + sc->sc_ah = ah; + + sc->dfs_detector = dfs_pattern_detector_init(ah, NL80211_DFS_UNSET); + + if (!pdata) { + ah->ah_flags |= AH_USE_EEPROM; + sc->sc_ah->led_pin = -1; + } else { + sc->sc_ah->gpio_mask = pdata->gpio_mask; + sc->sc_ah->gpio_val = pdata->gpio_val; + sc->sc_ah->led_pin = pdata->led_pin; + ah->is_clk_25mhz = pdata->is_clk_25mhz; + ah->get_mac_revision = pdata->get_mac_revision; + ah->external_reset = pdata->external_reset; + } + + common = ath9k_hw_common(ah); + common->ops = &ah->reg_ops; + common->bus_ops = bus_ops; + common->ah = ah; + common->hw = sc->hw; + common->priv = sc; + common->debug_mask = ath9k_debug; + common->btcoex_enabled = ath9k_btcoex_enable == 1; + common->disable_ani = false; + + /* + * Enable Antenna diversity only when BTCOEX is disabled + * and the user manually requests the feature. + */ + if (!common->btcoex_enabled && ath9k_enable_diversity) + common->antenna_diversity = 1; + + spin_lock_init(&common->cc_lock); + + spin_lock_init(&sc->sc_serial_rw); + spin_lock_init(&sc->sc_pm_lock); + mutex_init(&sc->mutex); +#ifdef CPTCFG_ATH9K_MAC_DEBUG + spin_lock_init(&sc->debug.samp_lock); +#endif + tasklet_init(&sc->intr_tq, ath9k_tasklet, (unsigned long)sc); + tasklet_init(&sc->bcon_tasklet, ath9k_beacon_tasklet, + (unsigned long)sc); + + INIT_WORK(&sc->hw_reset_work, ath_reset_work); + INIT_WORK(&sc->hw_check_work, ath_hw_check); + INIT_WORK(&sc->paprd_work, ath_paprd_calibrate); + INIT_DELAYED_WORK(&sc->hw_pll_work, ath_hw_pll_work); + setup_timer(&sc->rx_poll_timer, ath_rx_poll, (unsigned long)sc); + + /* + * Cache line size is used to size and align various + * structures used to communicate with the hardware. + */ + ath_read_cachesize(common, &csz); + common->cachelsz = csz << 2; /* convert to bytes */ + + if (pdata && pdata->eeprom_name) { + ret = ath9k_eeprom_request(sc, pdata->eeprom_name); + if (ret) + return ret; + } + + /* Initializes the hardware for all supported chipsets */ + ret = ath9k_hw_init(ah); + if (ret) + goto err_hw; + + if (pdata && pdata->macaddr) + memcpy(common->macaddr, pdata->macaddr, ETH_ALEN); + + ret = ath9k_init_queues(sc); + if (ret) + goto err_queues; + + ret = ath9k_init_btcoex(sc); + if (ret) + goto err_btcoex; + + ret = ath9k_init_channels_rates(sc); + if (ret) + goto err_btcoex; + + ath9k_cmn_init_crypto(sc->sc_ah); + ath9k_init_misc(sc); + ath_fill_led_pin(sc); + + if (common->bus_ops->aspm_init) + common->bus_ops->aspm_init(common); + + return 0; + +err_btcoex: + for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) + if (ATH_TXQ_SETUP(sc, i)) + ath_tx_cleanupq(sc, &sc->tx.txq[i]); +err_queues: + ath9k_hw_deinit(ah); +err_hw: + ath9k_eeprom_release(sc); + return ret; +} + +static void ath9k_init_band_txpower(struct ath_softc *sc, int band) +{ + struct ieee80211_supported_band *sband; + struct ieee80211_channel *chan; + struct ath_hw *ah = sc->sc_ah; + int i; + + sband = &sc->sbands[band]; + for (i = 0; i < sband->n_channels; i++) { + chan = &sband->channels[i]; + ah->curchan = &ah->channels[chan->hw_value]; + ath9k_cmn_update_ichannel(ah->curchan, chan, NL80211_CHAN_HT20); + ath9k_hw_set_txpowerlimit(ah, MAX_RATE_POWER, true); + } +} + +static void ath9k_init_txpower_limits(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath9k_channel *curchan = ah->curchan; + + if (ah->caps.hw_caps & ATH9K_HW_CAP_2GHZ) + ath9k_init_band_txpower(sc, IEEE80211_BAND_2GHZ); + if (ah->caps.hw_caps & ATH9K_HW_CAP_5GHZ) + ath9k_init_band_txpower(sc, IEEE80211_BAND_5GHZ); + + ah->curchan = curchan; +} + +void ath9k_reload_chainmask_settings(struct ath_softc *sc) +{ + if (!(sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_HT)) + return; + + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_2GHZ) + setup_ht_cap(sc, &sc->sbands[IEEE80211_BAND_2GHZ].ht_cap); + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_5GHZ) + setup_ht_cap(sc, &sc->sbands[IEEE80211_BAND_5GHZ].ht_cap); +} + +static const struct ieee80211_iface_limit if_limits[] = { + { .max = 2048, .types = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_WDS) }, + { .max = 8, .types = +#ifdef CPTCFG_MAC80211_MESH + BIT(NL80211_IFTYPE_MESH_POINT) | +#endif + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_GO) }, +}; + + +static const struct ieee80211_iface_limit if_dfs_limits[] = { + { .max = 1, .types = BIT(NL80211_IFTYPE_AP) }, +}; + +static const struct ieee80211_iface_combination if_comb[] = { + { + .limits = if_limits, + .n_limits = ARRAY_SIZE(if_limits), + .max_interfaces = 2048, + .num_different_channels = 1, + .beacon_int_infra_match = true, + }, + { + .limits = if_dfs_limits, + .n_limits = ARRAY_SIZE(if_dfs_limits), + .max_interfaces = 1, + .num_different_channels = 1, + .beacon_int_infra_match = true, + .radar_detect_widths = BIT(NL80211_CHAN_NO_HT) | + BIT(NL80211_CHAN_HT20), + } +}; + +void ath9k_set_hw_capab(struct ath_softc *sc, struct ieee80211_hw *hw) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + + hw->flags = IEEE80211_HW_RX_INCLUDES_FCS | + IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING | + IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_PS_NULLFUNC_STACK | + IEEE80211_HW_SPECTRUM_MGMT | + IEEE80211_HW_REPORTS_TX_ACK_STATUS | + IEEE80211_HW_SUPPORTS_RC_TABLE | + IEEE80211_HW_SUPPORTS_HT_CCK_RATES; + + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_HT) + hw->flags |= IEEE80211_HW_AMPDU_AGGREGATION; + + if (AR_SREV_9160_10_OR_LATER(sc->sc_ah) || ath9k_modparam_nohwcrypt) + hw->flags |= IEEE80211_HW_MFP_CAPABLE; + + hw->wiphy->interface_modes = + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_WDS) | + BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_MESH_POINT); + + hw->wiphy->iface_combinations = if_comb; + hw->wiphy->n_iface_combinations = ARRAY_SIZE(if_comb); + + hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + + hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN; + hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS; + hw->wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; + +#ifdef CONFIG_PM_SLEEP + + if ((ah->caps.hw_caps & ATH9K_HW_WOW_DEVICE_CAPABLE) && + device_can_wakeup(sc->dev)) { + + hw->wiphy->wowlan.flags = WIPHY_WOWLAN_MAGIC_PKT | + WIPHY_WOWLAN_DISCONNECT; + hw->wiphy->wowlan.n_patterns = MAX_NUM_USER_PATTERN; + hw->wiphy->wowlan.pattern_min_len = 1; + hw->wiphy->wowlan.pattern_max_len = MAX_PATTERN_SIZE; + + } + + atomic_set(&sc->wow_sleep_proc_intr, -1); + atomic_set(&sc->wow_got_bmiss_intr, -1); + +#endif + + hw->queues = 4; + hw->max_rates = 4; + hw->channel_change_time = 5000; + hw->max_listen_interval = 1; + hw->max_rate_tries = 10; + hw->sta_data_size = sizeof(struct ath_node); + hw->vif_data_size = sizeof(struct ath_vif); + + hw->wiphy->available_antennas_rx = BIT(ah->caps.max_rxchains) - 1; + hw->wiphy->available_antennas_tx = BIT(ah->caps.max_txchains) - 1; + + /* single chain devices with rx diversity */ + if (ah->caps.hw_caps & ATH9K_HW_CAP_ANT_DIV_COMB) + hw->wiphy->available_antennas_rx = BIT(0) | BIT(1); + + sc->ant_rx = hw->wiphy->available_antennas_rx; + sc->ant_tx = hw->wiphy->available_antennas_tx; + + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_2GHZ) + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = + &sc->sbands[IEEE80211_BAND_2GHZ]; + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_5GHZ) + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = + &sc->sbands[IEEE80211_BAND_5GHZ]; + + ath9k_reload_chainmask_settings(sc); + + SET_IEEE80211_PERM_ADDR(hw, common->macaddr); +} + +int ath9k_init_device(u16 devid, struct ath_softc *sc, + const struct ath_bus_ops *bus_ops) +{ + struct ieee80211_hw *hw = sc->hw; + struct ath_common *common; + struct ath_hw *ah; + int error = 0; + struct ath_regulatory *reg; + + /* Bring up device */ + error = ath9k_init_softc(devid, sc, bus_ops); + if (error) + return error; + + ah = sc->sc_ah; + common = ath9k_hw_common(ah); + ath9k_set_hw_capab(sc, hw); + + /* Initialize regulatory */ + error = ath_regd_init(&common->regulatory, sc->hw->wiphy, + ath9k_reg_notifier); + if (error) + goto deinit; + + reg = &common->regulatory; + + /* Setup TX DMA */ + error = ath_tx_init(sc, ATH_TXBUF); + if (error != 0) + goto deinit; + + /* Setup RX DMA */ + error = ath_rx_init(sc, ATH_RXBUF); + if (error != 0) + goto deinit; + + ath9k_init_txpower_limits(sc); + +#ifdef CPTCFG_MAC80211_LEDS + /* must be initialized before ieee80211_register_hw */ + sc->led_cdev.default_trigger = ieee80211_create_tpt_led_trigger(sc->hw, + IEEE80211_TPT_LEDTRIG_FL_RADIO, ath9k_tpt_blink, + ARRAY_SIZE(ath9k_tpt_blink)); +#endif + + /* Register with mac80211 */ + error = ieee80211_register_hw(hw); + if (error) + goto rx_cleanup; + + error = ath9k_init_debug(ah); + if (error) { + ath_err(common, "Unable to create debugfs files\n"); + goto unregister; + } + + /* Handle world regulatory */ + if (!ath_is_world_regd(reg)) { + error = regulatory_hint(hw->wiphy, reg->alpha2); + if (error) + goto debug_cleanup; + } + + ath_init_leds(sc); + ath_start_rfkill_poll(sc); + + return 0; + +debug_cleanup: + ath9k_deinit_debug(sc); +unregister: + ieee80211_unregister_hw(hw); +rx_cleanup: + ath_rx_cleanup(sc); +deinit: + ath9k_deinit_softc(sc); + return error; +} + +/*****************************/ +/* De-Initialization */ +/*****************************/ + +static void ath9k_deinit_softc(struct ath_softc *sc) +{ + int i = 0; + + ath9k_deinit_btcoex(sc); + + for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) + if (ATH_TXQ_SETUP(sc, i)) + ath_tx_cleanupq(sc, &sc->tx.txq[i]); + + ath9k_hw_deinit(sc->sc_ah); + if (sc->dfs_detector != NULL) + sc->dfs_detector->exit(sc->dfs_detector); + + ath9k_eeprom_release(sc); +} + +void ath9k_deinit_device(struct ath_softc *sc) +{ + struct ieee80211_hw *hw = sc->hw; + + ath9k_ps_wakeup(sc); + + wiphy_rfkill_stop_polling(sc->hw->wiphy); + ath_deinit_leds(sc); + + ath9k_ps_restore(sc); + + ath9k_deinit_debug(sc); + ieee80211_unregister_hw(hw); + ath_rx_cleanup(sc); + ath9k_deinit_softc(sc); +} + +/************************/ +/* Module Hooks */ +/************************/ + +static int __init ath9k_init(void) +{ + int error; + + /* Register rate control algorithm */ + error = ath_rate_control_register(); + if (error != 0) { + pr_err("Unable to register rate control algorithm: %d\n", + error); + goto err_out; + } + + error = ath_pci_init(); + if (error < 0) { + pr_err("No PCI devices found, driver not installed\n"); + error = -ENODEV; + goto err_rate_unregister; + } + + error = ath_ahb_init(); + if (error < 0) { + error = -ENODEV; + goto err_pci_exit; + } + + return 0; + + err_pci_exit: + ath_pci_exit(); + + err_rate_unregister: + ath_rate_control_unregister(); + err_out: + return error; +} +module_init(ath9k_init); + +static void __exit ath9k_exit(void) +{ + is_ath9k_unloaded = true; + ath_ahb_exit(); + ath_pci_exit(); + ath_rate_control_unregister(); + pr_info("%s: Driver unloaded\n", dev_info); +} +module_exit(ath9k_exit); diff --git a/drivers/net/wireless/ath/ath9k/link.c b/drivers/net/wireless/ath/ath9k/link.c new file mode 100644 index 0000000..849259b --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/link.c @@ -0,0 +1,571 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ath9k.h" + +/* + * TX polling - checks if the TX engine is stuck somewhere + * and issues a chip reset if so. + */ +void ath_tx_complete_poll_work(struct work_struct *work) +{ + struct ath_softc *sc = container_of(work, struct ath_softc, + tx_complete_work.work); + struct ath_txq *txq; + int i; + bool needreset = false; + + for (i = 0; i < IEEE80211_NUM_ACS; i++) { + txq = sc->tx.txq_map[i]; + + ath_txq_lock(sc, txq); + if (txq->axq_depth) { + if (txq->axq_tx_inprogress) { + needreset = true; + ath_txq_unlock(sc, txq); + break; + } else { + txq->axq_tx_inprogress = true; + } + } + ath_txq_unlock_complete(sc, txq); + } + + if (needreset) { + ath_dbg(ath9k_hw_common(sc->sc_ah), RESET, + "tx hung, resetting the chip\n"); + ath9k_queue_reset(sc, RESET_TYPE_TX_HANG); + return; + } + + ieee80211_queue_delayed_work(sc->hw, &sc->tx_complete_work, + msecs_to_jiffies(ATH_TX_COMPLETE_POLL_INT)); +} + +/* + * Checks if the BB/MAC is hung. + */ +void ath_hw_check(struct work_struct *work) +{ + struct ath_softc *sc = container_of(work, struct ath_softc, hw_check_work); + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + unsigned long flags; + int busy; + u8 is_alive, nbeacon = 1; + enum ath_reset_type type; + + ath9k_ps_wakeup(sc); + is_alive = ath9k_hw_check_alive(sc->sc_ah); + + if (is_alive && !AR_SREV_9300(sc->sc_ah)) + goto out; + else if (!is_alive && AR_SREV_9300(sc->sc_ah)) { + ath_dbg(common, RESET, + "DCU stuck is detected. Schedule chip reset\n"); + type = RESET_TYPE_MAC_HANG; + goto sched_reset; + } + + spin_lock_irqsave(&common->cc_lock, flags); + busy = ath_update_survey_stats(sc); + spin_unlock_irqrestore(&common->cc_lock, flags); + + ath_dbg(common, RESET, "Possible baseband hang, busy=%d (try %d)\n", + busy, sc->hw_busy_count + 1); + if (busy >= 99) { + if (++sc->hw_busy_count >= 3) { + type = RESET_TYPE_BB_HANG; + goto sched_reset; + } + } else if (busy >= 0) { + sc->hw_busy_count = 0; + nbeacon = 3; + } + + ath_start_rx_poll(sc, nbeacon); + goto out; + +sched_reset: + ath9k_queue_reset(sc, type); +out: + ath9k_ps_restore(sc); +} + +/* + * PLL-WAR for AR9485/AR9340 + */ +static bool ath_hw_pll_rx_hang_check(struct ath_softc *sc, u32 pll_sqsum) +{ + static int count; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + + if (pll_sqsum >= 0x40000) { + count++; + if (count == 3) { + ath_dbg(common, RESET, "PLL WAR, resetting the chip\n"); + ath9k_queue_reset(sc, RESET_TYPE_PLL_HANG); + count = 0; + return true; + } + } else { + count = 0; + } + + return false; +} + +void ath_hw_pll_work(struct work_struct *work) +{ + u32 pll_sqsum; + struct ath_softc *sc = container_of(work, struct ath_softc, + hw_pll_work.work); + /* + * ensure that the PLL WAR is executed only + * after the STA is associated (or) if the + * beaconing had started in interfaces that + * uses beacons. + */ + if (!test_bit(SC_OP_BEACONS, &sc->sc_flags)) + return; + + ath9k_ps_wakeup(sc); + pll_sqsum = ar9003_get_pll_sqsum_dvc(sc->sc_ah); + ath9k_ps_restore(sc); + if (ath_hw_pll_rx_hang_check(sc, pll_sqsum)) + return; + + ieee80211_queue_delayed_work(sc->hw, &sc->hw_pll_work, + msecs_to_jiffies(ATH_PLL_WORK_INTERVAL)); +} + +/* + * RX Polling - monitors baseband hangs. + */ +void ath_start_rx_poll(struct ath_softc *sc, u8 nbeacon) +{ + if (!AR_SREV_9300(sc->sc_ah)) + return; + + if (!test_bit(SC_OP_PRIM_STA_VIF, &sc->sc_flags)) + return; + + mod_timer(&sc->rx_poll_timer, jiffies + msecs_to_jiffies + (nbeacon * sc->cur_beacon_conf.beacon_interval)); +} + +void ath_rx_poll(unsigned long data) +{ + struct ath_softc *sc = (struct ath_softc *)data; + + if (!test_bit(SC_OP_INVALID, &sc->sc_flags)) + ieee80211_queue_work(sc->hw, &sc->hw_check_work); +} + +/* + * PA Pre-distortion. + */ +static void ath_paprd_activate(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_hw_cal_data *caldata = ah->caldata; + int chain; + + if (!caldata || !caldata->paprd_done) { + ath_dbg(common, CALIBRATE, "Failed to activate PAPRD\n"); + return; + } + + ar9003_paprd_enable(ah, false); + for (chain = 0; chain < AR9300_MAX_CHAINS; chain++) { + if (!(ah->txchainmask & BIT(chain))) + continue; + + ar9003_paprd_populate_single_table(ah, caldata, chain); + } + + ath_dbg(common, CALIBRATE, "Activating PAPRD\n"); + ar9003_paprd_enable(ah, true); +} + +static bool ath_paprd_send_frame(struct ath_softc *sc, struct sk_buff *skb, int chain) +{ + struct ieee80211_hw *hw = sc->hw; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath_tx_control txctl; + int time_left; + + memset(&txctl, 0, sizeof(txctl)); + txctl.txq = sc->tx.txq_map[IEEE80211_AC_BE]; + + memset(tx_info, 0, sizeof(*tx_info)); + tx_info->band = hw->conf.chandef.chan->band; + tx_info->flags |= IEEE80211_TX_CTL_NO_ACK; + tx_info->control.rates[0].idx = 0; + tx_info->control.rates[0].count = 1; + tx_info->control.rates[0].flags = IEEE80211_TX_RC_MCS; + tx_info->control.rates[1].idx = -1; + + init_completion(&sc->paprd_complete); + txctl.paprd = BIT(chain); + + if (ath_tx_start(hw, skb, &txctl) != 0) { + ath_dbg(common, CALIBRATE, "PAPRD TX failed\n"); + dev_kfree_skb_any(skb); + return false; + } + + time_left = wait_for_completion_timeout(&sc->paprd_complete, + msecs_to_jiffies(ATH_PAPRD_TIMEOUT)); + + if (!time_left) + ath_dbg(common, CALIBRATE, + "Timeout waiting for paprd training on TX chain %d\n", + chain); + + return !!time_left; +} + +void ath_paprd_calibrate(struct work_struct *work) +{ + struct ath_softc *sc = container_of(work, struct ath_softc, paprd_work); + struct ieee80211_hw *hw = sc->hw; + struct ath_hw *ah = sc->sc_ah; + struct ieee80211_hdr *hdr; + struct sk_buff *skb = NULL; + struct ath9k_hw_cal_data *caldata = ah->caldata; + struct ath_common *common = ath9k_hw_common(ah); + int ftype; + int chain_ok = 0; + int chain; + int len = 1800; + int ret; + + if (!caldata || !caldata->paprd_packet_sent || caldata->paprd_done) { + ath_dbg(common, CALIBRATE, "Skipping PAPRD calibration\n"); + return; + } + + ath9k_ps_wakeup(sc); + + if (ar9003_paprd_init_table(ah) < 0) + goto fail_paprd; + + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) + goto fail_paprd; + + skb_put(skb, len); + memset(skb->data, 0, len); + hdr = (struct ieee80211_hdr *)skb->data; + ftype = IEEE80211_FTYPE_DATA | IEEE80211_STYPE_NULLFUNC; + hdr->frame_control = cpu_to_le16(ftype); + hdr->duration_id = cpu_to_le16(10); + memcpy(hdr->addr1, hw->wiphy->perm_addr, ETH_ALEN); + memcpy(hdr->addr2, hw->wiphy->perm_addr, ETH_ALEN); + memcpy(hdr->addr3, hw->wiphy->perm_addr, ETH_ALEN); + + for (chain = 0; chain < AR9300_MAX_CHAINS; chain++) { + if (!(ah->txchainmask & BIT(chain))) + continue; + + chain_ok = 0; + ar9003_paprd_setup_gain_table(ah, chain); + + ath_dbg(common, CALIBRATE, + "Sending PAPRD training frame on chain %d\n", chain); + if (!ath_paprd_send_frame(sc, skb, chain)) + goto fail_paprd; + + if (!ar9003_paprd_is_done(ah)) { + ath_dbg(common, CALIBRATE, + "PAPRD not yet done on chain %d\n", chain); + break; + } + + ret = ar9003_paprd_create_curve(ah, caldata, chain); + if (ret == -EINPROGRESS) { + ath_dbg(common, CALIBRATE, + "PAPRD curve on chain %d needs to be re-trained\n", + chain); + break; + } else if (ret) { + ath_dbg(common, CALIBRATE, + "PAPRD create curve failed on chain %d\n", + chain); + break; + } + + chain_ok = 1; + } + kfree_skb(skb); + + if (chain_ok) { + caldata->paprd_done = true; + ath_paprd_activate(sc); + } + +fail_paprd: + ath9k_ps_restore(sc); +} + +/* + * ANI performs periodic noise floor calibration + * that is used to adjust and optimize the chip performance. This + * takes environmental changes (location, temperature) into account. + * When the task is complete, it reschedules itself depending on the + * appropriate interval that was calculated. + */ +void ath_ani_calibrate(unsigned long data) +{ + struct ath_softc *sc = (struct ath_softc *)data; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + bool longcal = false; + bool shortcal = false; + bool aniflag = false; + unsigned int timestamp = jiffies_to_msecs(jiffies); + u32 cal_interval, short_cal_interval, long_cal_interval; + unsigned long flags; + + if (ah->caldata && ah->caldata->nfcal_interference) + long_cal_interval = ATH_LONG_CALINTERVAL_INT; + else + long_cal_interval = ATH_LONG_CALINTERVAL; + + short_cal_interval = (ah->opmode == NL80211_IFTYPE_AP) ? + ATH_AP_SHORT_CALINTERVAL : ATH_STA_SHORT_CALINTERVAL; + + /* Only calibrate if awake */ + if (sc->sc_ah->power_mode != ATH9K_PM_AWAKE) { + if (++ah->ani_skip_count >= ATH_ANI_MAX_SKIP_COUNT) { + spin_lock_irqsave(&sc->sc_pm_lock, flags); + sc->ps_flags |= PS_WAIT_FOR_ANI; + spin_unlock_irqrestore(&sc->sc_pm_lock, flags); + } + goto set_timer; + } + ah->ani_skip_count = 0; + spin_lock_irqsave(&sc->sc_pm_lock, flags); + sc->ps_flags &= ~PS_WAIT_FOR_ANI; + spin_unlock_irqrestore(&sc->sc_pm_lock, flags); + + ath9k_ps_wakeup(sc); + + /* Long calibration runs independently of short calibration. */ + if ((timestamp - common->ani.longcal_timer) >= long_cal_interval) { + longcal = true; + common->ani.longcal_timer = timestamp; + } + + /* Short calibration applies only while caldone is false */ + if (!common->ani.caldone) { + if ((timestamp - common->ani.shortcal_timer) >= short_cal_interval) { + shortcal = true; + common->ani.shortcal_timer = timestamp; + common->ani.resetcal_timer = timestamp; + } + } else { + if ((timestamp - common->ani.resetcal_timer) >= + ATH_RESTART_CALINTERVAL) { + common->ani.caldone = ath9k_hw_reset_calvalid(ah); + if (common->ani.caldone) + common->ani.resetcal_timer = timestamp; + } + } + + /* Verify whether we must check ANI */ + if (sc->sc_ah->config.enable_ani + && (timestamp - common->ani.checkani_timer) >= + ah->config.ani_poll_interval) { + aniflag = true; + common->ani.checkani_timer = timestamp; + } + + /* Call ANI routine if necessary */ + if (aniflag) { + spin_lock_irqsave(&common->cc_lock, flags); + ath9k_hw_ani_monitor(ah, ah->curchan); + ath_update_survey_stats(sc); + spin_unlock_irqrestore(&common->cc_lock, flags); + } + + /* Perform calibration if necessary */ + if (longcal || shortcal) { + common->ani.caldone = + ath9k_hw_calibrate(ah, ah->curchan, + ah->rxchainmask, longcal); + } + + ath_dbg(common, ANI, + "Calibration @%lu finished: %s %s %s, caldone: %s\n", + jiffies, + longcal ? "long" : "", shortcal ? "short" : "", + aniflag ? "ani" : "", common->ani.caldone ? "true" : "false"); + + ath9k_debug_samp_bb_mac(sc); + ath9k_ps_restore(sc); + +set_timer: + /* + * Set timer interval based on previous results. + * The interval must be the shortest necessary to satisfy ANI, + * short calibration and long calibration. + */ + cal_interval = ATH_LONG_CALINTERVAL; + if (sc->sc_ah->config.enable_ani) + cal_interval = min(cal_interval, + (u32)ah->config.ani_poll_interval); + if (!common->ani.caldone) + cal_interval = min(cal_interval, (u32)short_cal_interval); + + mod_timer(&common->ani.timer, jiffies + msecs_to_jiffies(cal_interval)); + + if (ar9003_is_paprd_enabled(ah) && ah->caldata) { + if (!ah->caldata->paprd_done) { + ieee80211_queue_work(sc->hw, &sc->paprd_work); + } else if (!ah->paprd_table_write_done) { + ath9k_ps_wakeup(sc); + ath_paprd_activate(sc); + ath9k_ps_restore(sc); + } + } +} + +void ath_start_ani(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + unsigned long timestamp = jiffies_to_msecs(jiffies); + + if (common->disable_ani || + !test_bit(SC_OP_ANI_RUN, &sc->sc_flags) || + (sc->hw->conf.flags & IEEE80211_CONF_OFFCHANNEL)) + return; + + common->ani.longcal_timer = timestamp; + common->ani.shortcal_timer = timestamp; + common->ani.checkani_timer = timestamp; + + ath_dbg(common, ANI, "Starting ANI\n"); + mod_timer(&common->ani.timer, + jiffies + msecs_to_jiffies((u32)ah->config.ani_poll_interval)); +} + +void ath_stop_ani(struct ath_softc *sc) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + + ath_dbg(common, ANI, "Stopping ANI\n"); + del_timer_sync(&common->ani.timer); +} + +void ath_check_ani(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_beacon_config *cur_conf = &sc->cur_beacon_conf; + + /* + * Check for the various conditions in which ANI has to + * be stopped. + */ + if (ah->opmode == NL80211_IFTYPE_ADHOC) { + if (!cur_conf->enable_beacon) + goto stop_ani; + } else if (ah->opmode == NL80211_IFTYPE_AP) { + if (!cur_conf->enable_beacon) { + /* + * Disable ANI only when there are no + * associated stations. + */ + if (!test_bit(SC_OP_PRIM_STA_VIF, &sc->sc_flags)) + goto stop_ani; + } + } else if (ah->opmode == NL80211_IFTYPE_STATION) { + if (!test_bit(SC_OP_PRIM_STA_VIF, &sc->sc_flags)) + goto stop_ani; + } + + if (!test_bit(SC_OP_ANI_RUN, &sc->sc_flags)) { + set_bit(SC_OP_ANI_RUN, &sc->sc_flags); + ath_start_ani(sc); + } + + return; + +stop_ani: + clear_bit(SC_OP_ANI_RUN, &sc->sc_flags); + ath_stop_ani(sc); +} + +void ath_update_survey_nf(struct ath_softc *sc, int channel) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath9k_channel *chan = &ah->channels[channel]; + struct survey_info *survey = &sc->survey[channel]; + + if (chan->noisefloor) { + survey->filled |= SURVEY_INFO_NOISE_DBM; + survey->noise = ath9k_hw_getchan_noise(ah, chan); + } +} + +/* + * Updates the survey statistics and returns the busy time since last + * update in %, if the measurement duration was long enough for the + * result to be useful, -1 otherwise. + */ +int ath_update_survey_stats(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + int pos = ah->curchan - &ah->channels[0]; + struct survey_info *survey = &sc->survey[pos]; + struct ath_cycle_counters *cc = &common->cc_survey; + unsigned int div = common->clockrate * 1000; + int ret = 0; + + if (!ah->curchan) + return -1; + + if (ah->power_mode == ATH9K_PM_AWAKE) + ath_hw_cycle_counters_update(common); + + if (cc->cycles > 0) { + survey->filled |= SURVEY_INFO_CHANNEL_TIME | + SURVEY_INFO_CHANNEL_TIME_BUSY | + SURVEY_INFO_CHANNEL_TIME_RX | + SURVEY_INFO_CHANNEL_TIME_TX; + survey->channel_time += cc->cycles / div; + survey->channel_time_busy += cc->rx_busy / div; + survey->channel_time_rx += cc->rx_frame / div; + survey->channel_time_tx += cc->tx_frame / div; + } + + if (cc->cycles < div) + return -1; + + if (cc->cycles > 0) + ret = cc->rx_busy * 100 / cc->cycles; + + memset(cc, 0, sizeof(*cc)); + + ath_update_survey_nf(sc, pos); + + return ret; +} diff --git a/drivers/net/wireless/ath/ath9k/mac.c b/drivers/net/wireless/ath/ath9k/mac.c new file mode 100644 index 0000000..566109a --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/mac.c @@ -0,0 +1,939 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "hw.h" +#include "hw-ops.h" +#include + +static void ath9k_hw_set_txq_interrupts(struct ath_hw *ah, + struct ath9k_tx_queue_info *qi) +{ + ath_dbg(ath9k_hw_common(ah), INTERRUPT, + "tx ok 0x%x err 0x%x desc 0x%x eol 0x%x urn 0x%x\n", + ah->txok_interrupt_mask, ah->txerr_interrupt_mask, + ah->txdesc_interrupt_mask, ah->txeol_interrupt_mask, + ah->txurn_interrupt_mask); + + ENABLE_REGWRITE_BUFFER(ah); + + REG_WRITE(ah, AR_IMR_S0, + SM(ah->txok_interrupt_mask, AR_IMR_S0_QCU_TXOK) + | SM(ah->txdesc_interrupt_mask, AR_IMR_S0_QCU_TXDESC)); + REG_WRITE(ah, AR_IMR_S1, + SM(ah->txerr_interrupt_mask, AR_IMR_S1_QCU_TXERR) + | SM(ah->txeol_interrupt_mask, AR_IMR_S1_QCU_TXEOL)); + + ah->imrs2_reg &= ~AR_IMR_S2_QCU_TXURN; + ah->imrs2_reg |= (ah->txurn_interrupt_mask & AR_IMR_S2_QCU_TXURN); + REG_WRITE(ah, AR_IMR_S2, ah->imrs2_reg); + + REGWRITE_BUFFER_FLUSH(ah); +} + +u32 ath9k_hw_gettxbuf(struct ath_hw *ah, u32 q) +{ + return REG_READ(ah, AR_QTXDP(q)); +} +EXPORT_SYMBOL(ath9k_hw_gettxbuf); + +void ath9k_hw_puttxbuf(struct ath_hw *ah, u32 q, u32 txdp) +{ + REG_WRITE(ah, AR_QTXDP(q), txdp); +} +EXPORT_SYMBOL(ath9k_hw_puttxbuf); + +void ath9k_hw_txstart(struct ath_hw *ah, u32 q) +{ + ath_dbg(ath9k_hw_common(ah), QUEUE, "Enable TXE on queue: %u\n", q); + REG_WRITE(ah, AR_Q_TXE, 1 << q); +} +EXPORT_SYMBOL(ath9k_hw_txstart); + +u32 ath9k_hw_numtxpending(struct ath_hw *ah, u32 q) +{ + u32 npend; + + npend = REG_READ(ah, AR_QSTS(q)) & AR_Q_STS_PEND_FR_CNT; + if (npend == 0) { + + if (REG_READ(ah, AR_Q_TXE) & (1 << q)) + npend = 1; + } + + return npend; +} +EXPORT_SYMBOL(ath9k_hw_numtxpending); + +/** + * ath9k_hw_updatetxtriglevel - adjusts the frame trigger level + * + * @ah: atheros hardware struct + * @bIncTrigLevel: whether or not the frame trigger level should be updated + * + * The frame trigger level specifies the minimum number of bytes, + * in units of 64 bytes, that must be DMA'ed into the PCU TX FIFO + * before the PCU will initiate sending the frame on the air. This can + * mean we initiate transmit before a full frame is on the PCU TX FIFO. + * Resets to 0x1 (meaning 64 bytes or a full frame, whichever occurs + * first) + * + * Caution must be taken to ensure to set the frame trigger level based + * on the DMA request size. For example if the DMA request size is set to + * 128 bytes the trigger level cannot exceed 6 * 64 = 384. This is because + * there need to be enough space in the tx FIFO for the requested transfer + * size. Hence the tx FIFO will stop with 512 - 128 = 384 bytes. If we set + * the threshold to a value beyond 6, then the transmit will hang. + * + * Current dual stream devices have a PCU TX FIFO size of 8 KB. + * Current single stream devices have a PCU TX FIFO size of 4 KB, however, + * there is a hardware issue which forces us to use 2 KB instead so the + * frame trigger level must not exceed 2 KB for these chipsets. + */ +bool ath9k_hw_updatetxtriglevel(struct ath_hw *ah, bool bIncTrigLevel) +{ + u32 txcfg, curLevel, newLevel; + + if (ah->tx_trig_level >= ah->config.max_txtrig_level) + return false; + + ath9k_hw_disable_interrupts(ah); + + txcfg = REG_READ(ah, AR_TXCFG); + curLevel = MS(txcfg, AR_FTRIG); + newLevel = curLevel; + if (bIncTrigLevel) { + if (curLevel < ah->config.max_txtrig_level) + newLevel++; + } else if (curLevel > MIN_TX_FIFO_THRESHOLD) + newLevel--; + if (newLevel != curLevel) + REG_WRITE(ah, AR_TXCFG, + (txcfg & ~AR_FTRIG) | SM(newLevel, AR_FTRIG)); + + ath9k_hw_enable_interrupts(ah); + + ah->tx_trig_level = newLevel; + + return newLevel != curLevel; +} +EXPORT_SYMBOL(ath9k_hw_updatetxtriglevel); + +void ath9k_hw_abort_tx_dma(struct ath_hw *ah) +{ + int maxdelay = 1000; + int i, q; + + if (ah->curchan) { + if (IS_CHAN_HALF_RATE(ah->curchan)) + maxdelay *= 2; + else if (IS_CHAN_QUARTER_RATE(ah->curchan)) + maxdelay *= 4; + } + + REG_WRITE(ah, AR_Q_TXD, AR_Q_TXD_M); + + REG_SET_BIT(ah, AR_PCU_MISC, AR_PCU_FORCE_QUIET_COLL | AR_PCU_CLEAR_VMF); + REG_SET_BIT(ah, AR_DIAG_SW, AR_DIAG_FORCE_CH_IDLE_HIGH); + REG_SET_BIT(ah, AR_D_GBL_IFS_MISC, AR_D_GBL_IFS_MISC_IGNORE_BACKOFF); + + for (q = 0; q < AR_NUM_QCU; q++) { + for (i = 0; i < maxdelay; i++) { + if (i) + udelay(5); + + if (!ath9k_hw_numtxpending(ah, q)) + break; + } + } + + REG_CLR_BIT(ah, AR_PCU_MISC, AR_PCU_FORCE_QUIET_COLL | AR_PCU_CLEAR_VMF); + REG_CLR_BIT(ah, AR_DIAG_SW, AR_DIAG_FORCE_CH_IDLE_HIGH); + REG_CLR_BIT(ah, AR_D_GBL_IFS_MISC, AR_D_GBL_IFS_MISC_IGNORE_BACKOFF); + + REG_WRITE(ah, AR_Q_TXD, 0); +} +EXPORT_SYMBOL(ath9k_hw_abort_tx_dma); + +bool ath9k_hw_stop_dma_queue(struct ath_hw *ah, u32 q) +{ +#define ATH9K_TX_STOP_DMA_TIMEOUT 1000 /* usec */ +#define ATH9K_TIME_QUANTUM 100 /* usec */ + int wait_time = ATH9K_TX_STOP_DMA_TIMEOUT / ATH9K_TIME_QUANTUM; + int wait; + + REG_WRITE(ah, AR_Q_TXD, 1 << q); + + for (wait = wait_time; wait != 0; wait--) { + if (wait != wait_time) + udelay(ATH9K_TIME_QUANTUM); + + if (ath9k_hw_numtxpending(ah, q) == 0) + break; + } + + REG_WRITE(ah, AR_Q_TXD, 0); + + return wait != 0; + +#undef ATH9K_TX_STOP_DMA_TIMEOUT +#undef ATH9K_TIME_QUANTUM +} +EXPORT_SYMBOL(ath9k_hw_stop_dma_queue); + +bool ath9k_hw_set_txq_props(struct ath_hw *ah, int q, + const struct ath9k_tx_queue_info *qinfo) +{ + u32 cw; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_tx_queue_info *qi; + + qi = &ah->txq[q]; + if (qi->tqi_type == ATH9K_TX_QUEUE_INACTIVE) { + ath_dbg(common, QUEUE, + "Set TXQ properties, inactive queue: %u\n", q); + return false; + } + + ath_dbg(common, QUEUE, "Set queue properties for: %u\n", q); + + qi->tqi_ver = qinfo->tqi_ver; + qi->tqi_subtype = qinfo->tqi_subtype; + qi->tqi_qflags = qinfo->tqi_qflags; + qi->tqi_priority = qinfo->tqi_priority; + if (qinfo->tqi_aifs != ATH9K_TXQ_USEDEFAULT) + qi->tqi_aifs = min(qinfo->tqi_aifs, 255U); + else + qi->tqi_aifs = INIT_AIFS; + if (qinfo->tqi_cwmin != ATH9K_TXQ_USEDEFAULT) { + cw = min(qinfo->tqi_cwmin, 1024U); + qi->tqi_cwmin = 1; + while (qi->tqi_cwmin < cw) + qi->tqi_cwmin = (qi->tqi_cwmin << 1) | 1; + } else + qi->tqi_cwmin = qinfo->tqi_cwmin; + if (qinfo->tqi_cwmax != ATH9K_TXQ_USEDEFAULT) { + cw = min(qinfo->tqi_cwmax, 1024U); + qi->tqi_cwmax = 1; + while (qi->tqi_cwmax < cw) + qi->tqi_cwmax = (qi->tqi_cwmax << 1) | 1; + } else + qi->tqi_cwmax = INIT_CWMAX; + + if (qinfo->tqi_shretry != 0) + qi->tqi_shretry = min((u32) qinfo->tqi_shretry, 15U); + else + qi->tqi_shretry = INIT_SH_RETRY; + if (qinfo->tqi_lgretry != 0) + qi->tqi_lgretry = min((u32) qinfo->tqi_lgretry, 15U); + else + qi->tqi_lgretry = INIT_LG_RETRY; + qi->tqi_cbrPeriod = qinfo->tqi_cbrPeriod; + qi->tqi_cbrOverflowLimit = qinfo->tqi_cbrOverflowLimit; + qi->tqi_burstTime = qinfo->tqi_burstTime; + qi->tqi_readyTime = qinfo->tqi_readyTime; + + switch (qinfo->tqi_subtype) { + case ATH9K_WME_UPSD: + if (qi->tqi_type == ATH9K_TX_QUEUE_DATA) + qi->tqi_intFlags = ATH9K_TXQ_USE_LOCKOUT_BKOFF_DIS; + break; + default: + break; + } + + return true; +} +EXPORT_SYMBOL(ath9k_hw_set_txq_props); + +bool ath9k_hw_get_txq_props(struct ath_hw *ah, int q, + struct ath9k_tx_queue_info *qinfo) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_tx_queue_info *qi; + + qi = &ah->txq[q]; + if (qi->tqi_type == ATH9K_TX_QUEUE_INACTIVE) { + ath_dbg(common, QUEUE, + "Get TXQ properties, inactive queue: %u\n", q); + return false; + } + + qinfo->tqi_qflags = qi->tqi_qflags; + qinfo->tqi_ver = qi->tqi_ver; + qinfo->tqi_subtype = qi->tqi_subtype; + qinfo->tqi_qflags = qi->tqi_qflags; + qinfo->tqi_priority = qi->tqi_priority; + qinfo->tqi_aifs = qi->tqi_aifs; + qinfo->tqi_cwmin = qi->tqi_cwmin; + qinfo->tqi_cwmax = qi->tqi_cwmax; + qinfo->tqi_shretry = qi->tqi_shretry; + qinfo->tqi_lgretry = qi->tqi_lgretry; + qinfo->tqi_cbrPeriod = qi->tqi_cbrPeriod; + qinfo->tqi_cbrOverflowLimit = qi->tqi_cbrOverflowLimit; + qinfo->tqi_burstTime = qi->tqi_burstTime; + qinfo->tqi_readyTime = qi->tqi_readyTime; + + return true; +} +EXPORT_SYMBOL(ath9k_hw_get_txq_props); + +int ath9k_hw_setuptxqueue(struct ath_hw *ah, enum ath9k_tx_queue type, + const struct ath9k_tx_queue_info *qinfo) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_tx_queue_info *qi; + int q; + + switch (type) { + case ATH9K_TX_QUEUE_BEACON: + q = ATH9K_NUM_TX_QUEUES - 1; + break; + case ATH9K_TX_QUEUE_CAB: + q = ATH9K_NUM_TX_QUEUES - 2; + break; + case ATH9K_TX_QUEUE_PSPOLL: + q = 1; + break; + case ATH9K_TX_QUEUE_UAPSD: + q = ATH9K_NUM_TX_QUEUES - 3; + break; + case ATH9K_TX_QUEUE_DATA: + for (q = 0; q < ATH9K_NUM_TX_QUEUES; q++) + if (ah->txq[q].tqi_type == + ATH9K_TX_QUEUE_INACTIVE) + break; + if (q == ATH9K_NUM_TX_QUEUES) { + ath_err(common, "No available TX queue\n"); + return -1; + } + break; + default: + ath_err(common, "Invalid TX queue type: %u\n", type); + return -1; + } + + ath_dbg(common, QUEUE, "Setup TX queue: %u\n", q); + + qi = &ah->txq[q]; + if (qi->tqi_type != ATH9K_TX_QUEUE_INACTIVE) { + ath_err(common, "TX queue: %u already active\n", q); + return -1; + } + memset(qi, 0, sizeof(struct ath9k_tx_queue_info)); + qi->tqi_type = type; + qi->tqi_physCompBuf = qinfo->tqi_physCompBuf; + (void) ath9k_hw_set_txq_props(ah, q, qinfo); + + return q; +} +EXPORT_SYMBOL(ath9k_hw_setuptxqueue); + +static void ath9k_hw_clear_queue_interrupts(struct ath_hw *ah, u32 q) +{ + ah->txok_interrupt_mask &= ~(1 << q); + ah->txerr_interrupt_mask &= ~(1 << q); + ah->txdesc_interrupt_mask &= ~(1 << q); + ah->txeol_interrupt_mask &= ~(1 << q); + ah->txurn_interrupt_mask &= ~(1 << q); +} + +bool ath9k_hw_releasetxqueue(struct ath_hw *ah, u32 q) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_tx_queue_info *qi; + + qi = &ah->txq[q]; + if (qi->tqi_type == ATH9K_TX_QUEUE_INACTIVE) { + ath_dbg(common, QUEUE, "Release TXQ, inactive queue: %u\n", q); + return false; + } + + ath_dbg(common, QUEUE, "Release TX queue: %u\n", q); + + qi->tqi_type = ATH9K_TX_QUEUE_INACTIVE; + ath9k_hw_clear_queue_interrupts(ah, q); + ath9k_hw_set_txq_interrupts(ah, qi); + + return true; +} +EXPORT_SYMBOL(ath9k_hw_releasetxqueue); + +bool ath9k_hw_resettxqueue(struct ath_hw *ah, u32 q) +{ + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_channel *chan = ah->curchan; + struct ath9k_tx_queue_info *qi; + u32 cwMin, chanCwMin, value; + + qi = &ah->txq[q]; + if (qi->tqi_type == ATH9K_TX_QUEUE_INACTIVE) { + ath_dbg(common, QUEUE, "Reset TXQ, inactive queue: %u\n", q); + return true; + } + + ath_dbg(common, QUEUE, "Reset TX queue: %u\n", q); + + if (qi->tqi_cwmin == ATH9K_TXQ_USEDEFAULT) { + if (chan && IS_CHAN_B(chan)) + chanCwMin = INIT_CWMIN_11B; + else + chanCwMin = INIT_CWMIN; + + for (cwMin = 1; cwMin < chanCwMin; cwMin = (cwMin << 1) | 1); + } else + cwMin = qi->tqi_cwmin; + + ENABLE_REGWRITE_BUFFER(ah); + + REG_WRITE(ah, AR_DLCL_IFS(q), + SM(cwMin, AR_D_LCL_IFS_CWMIN) | + SM(qi->tqi_cwmax, AR_D_LCL_IFS_CWMAX) | + SM(qi->tqi_aifs, AR_D_LCL_IFS_AIFS)); + + REG_WRITE(ah, AR_DRETRY_LIMIT(q), + SM(INIT_SSH_RETRY, AR_D_RETRY_LIMIT_STA_SH) | + SM(INIT_SLG_RETRY, AR_D_RETRY_LIMIT_STA_LG) | + SM(qi->tqi_shretry, AR_D_RETRY_LIMIT_FR_SH)); + + REG_WRITE(ah, AR_QMISC(q), AR_Q_MISC_DCU_EARLY_TERM_REQ); + + if (AR_SREV_9340(ah) && !AR_SREV_9340_13_OR_LATER(ah)) + REG_WRITE(ah, AR_DMISC(q), + AR_D_MISC_CW_BKOFF_EN | AR_D_MISC_FRAG_WAIT_EN | 0x1); + else + REG_WRITE(ah, AR_DMISC(q), + AR_D_MISC_CW_BKOFF_EN | AR_D_MISC_FRAG_WAIT_EN | 0x2); + + if (qi->tqi_cbrPeriod) { + REG_WRITE(ah, AR_QCBRCFG(q), + SM(qi->tqi_cbrPeriod, AR_Q_CBRCFG_INTERVAL) | + SM(qi->tqi_cbrOverflowLimit, AR_Q_CBRCFG_OVF_THRESH)); + REG_SET_BIT(ah, AR_QMISC(q), AR_Q_MISC_FSP_CBR | + (qi->tqi_cbrOverflowLimit ? + AR_Q_MISC_CBR_EXP_CNTR_LIMIT_EN : 0)); + } + if (qi->tqi_readyTime && (qi->tqi_type != ATH9K_TX_QUEUE_CAB)) { + REG_WRITE(ah, AR_QRDYTIMECFG(q), + SM(qi->tqi_readyTime, AR_Q_RDYTIMECFG_DURATION) | + AR_Q_RDYTIMECFG_EN); + } + + REG_WRITE(ah, AR_DCHNTIME(q), + SM(qi->tqi_burstTime, AR_D_CHNTIME_DUR) | + (qi->tqi_burstTime ? AR_D_CHNTIME_EN : 0)); + + if (qi->tqi_burstTime + && (qi->tqi_qflags & TXQ_FLAG_RDYTIME_EXP_POLICY_ENABLE)) + REG_SET_BIT(ah, AR_QMISC(q), AR_Q_MISC_RDYTIME_EXP_POLICY); + + if (qi->tqi_qflags & TXQ_FLAG_BACKOFF_DISABLE) + REG_SET_BIT(ah, AR_DMISC(q), AR_D_MISC_POST_FR_BKOFF_DIS); + + REGWRITE_BUFFER_FLUSH(ah); + + if (qi->tqi_qflags & TXQ_FLAG_FRAG_BURST_BACKOFF_ENABLE) + REG_SET_BIT(ah, AR_DMISC(q), AR_D_MISC_FRAG_BKOFF_EN); + + switch (qi->tqi_type) { + case ATH9K_TX_QUEUE_BEACON: + ENABLE_REGWRITE_BUFFER(ah); + + REG_SET_BIT(ah, AR_QMISC(q), + AR_Q_MISC_FSP_DBA_GATED + | AR_Q_MISC_BEACON_USE + | AR_Q_MISC_CBR_INCR_DIS1); + + REG_SET_BIT(ah, AR_DMISC(q), + (AR_D_MISC_ARB_LOCKOUT_CNTRL_GLOBAL << + AR_D_MISC_ARB_LOCKOUT_CNTRL_S) + | AR_D_MISC_BEACON_USE + | AR_D_MISC_POST_FR_BKOFF_DIS); + + REGWRITE_BUFFER_FLUSH(ah); + + /* + * cwmin and cwmax should be 0 for beacon queue + * but not for IBSS as we would create an imbalance + * on beaconing fairness for participating nodes. + */ + if (AR_SREV_9300_20_OR_LATER(ah) && + ah->opmode != NL80211_IFTYPE_ADHOC) { + REG_WRITE(ah, AR_DLCL_IFS(q), SM(0, AR_D_LCL_IFS_CWMIN) + | SM(0, AR_D_LCL_IFS_CWMAX) + | SM(qi->tqi_aifs, AR_D_LCL_IFS_AIFS)); + } + break; + case ATH9K_TX_QUEUE_CAB: + ENABLE_REGWRITE_BUFFER(ah); + + REG_SET_BIT(ah, AR_QMISC(q), + AR_Q_MISC_FSP_DBA_GATED + | AR_Q_MISC_CBR_INCR_DIS1 + | AR_Q_MISC_CBR_INCR_DIS0); + value = (qi->tqi_readyTime - + (ah->config.sw_beacon_response_time - + ah->config.dma_beacon_response_time) - + ah->config.additional_swba_backoff) * 1024; + REG_WRITE(ah, AR_QRDYTIMECFG(q), + value | AR_Q_RDYTIMECFG_EN); + REG_SET_BIT(ah, AR_DMISC(q), + (AR_D_MISC_ARB_LOCKOUT_CNTRL_GLOBAL << + AR_D_MISC_ARB_LOCKOUT_CNTRL_S)); + + REGWRITE_BUFFER_FLUSH(ah); + + break; + case ATH9K_TX_QUEUE_PSPOLL: + REG_SET_BIT(ah, AR_QMISC(q), AR_Q_MISC_CBR_INCR_DIS1); + break; + case ATH9K_TX_QUEUE_UAPSD: + REG_SET_BIT(ah, AR_DMISC(q), AR_D_MISC_POST_FR_BKOFF_DIS); + break; + default: + break; + } + + if (qi->tqi_intFlags & ATH9K_TXQ_USE_LOCKOUT_BKOFF_DIS) { + REG_SET_BIT(ah, AR_DMISC(q), + SM(AR_D_MISC_ARB_LOCKOUT_CNTRL_GLOBAL, + AR_D_MISC_ARB_LOCKOUT_CNTRL) | + AR_D_MISC_POST_FR_BKOFF_DIS); + } + + if (AR_SREV_9300_20_OR_LATER(ah)) + REG_WRITE(ah, AR_Q_DESC_CRCCHK, AR_Q_DESC_CRCCHK_EN); + + ath9k_hw_clear_queue_interrupts(ah, q); + if (qi->tqi_qflags & TXQ_FLAG_TXINT_ENABLE) { + ah->txok_interrupt_mask |= 1 << q; + ah->txerr_interrupt_mask |= 1 << q; + } + if (qi->tqi_qflags & TXQ_FLAG_TXDESCINT_ENABLE) + ah->txdesc_interrupt_mask |= 1 << q; + if (qi->tqi_qflags & TXQ_FLAG_TXEOLINT_ENABLE) + ah->txeol_interrupt_mask |= 1 << q; + if (qi->tqi_qflags & TXQ_FLAG_TXURNINT_ENABLE) + ah->txurn_interrupt_mask |= 1 << q; + ath9k_hw_set_txq_interrupts(ah, qi); + + return true; +} +EXPORT_SYMBOL(ath9k_hw_resettxqueue); + +int ath9k_hw_rxprocdesc(struct ath_hw *ah, struct ath_desc *ds, + struct ath_rx_status *rs) +{ + struct ar5416_desc ads; + struct ar5416_desc *adsp = AR5416DESC(ds); + u32 phyerr; + + if ((adsp->ds_rxstatus8 & AR_RxDone) == 0) + return -EINPROGRESS; + + ads.u.rx = adsp->u.rx; + + rs->rs_status = 0; + rs->rs_flags = 0; + + rs->rs_datalen = ads.ds_rxstatus1 & AR_DataLen; + rs->rs_tstamp = ads.AR_RcvTimestamp; + + if (ads.ds_rxstatus8 & AR_PostDelimCRCErr) { + rs->rs_rssi = ATH9K_RSSI_BAD; + rs->rs_rssi_ctl0 = ATH9K_RSSI_BAD; + rs->rs_rssi_ctl1 = ATH9K_RSSI_BAD; + rs->rs_rssi_ctl2 = ATH9K_RSSI_BAD; + rs->rs_rssi_ext0 = ATH9K_RSSI_BAD; + rs->rs_rssi_ext1 = ATH9K_RSSI_BAD; + rs->rs_rssi_ext2 = ATH9K_RSSI_BAD; + } else { + rs->rs_rssi = MS(ads.ds_rxstatus4, AR_RxRSSICombined); + rs->rs_rssi_ctl0 = MS(ads.ds_rxstatus0, + AR_RxRSSIAnt00); + rs->rs_rssi_ctl1 = MS(ads.ds_rxstatus0, + AR_RxRSSIAnt01); + rs->rs_rssi_ctl2 = MS(ads.ds_rxstatus0, + AR_RxRSSIAnt02); + rs->rs_rssi_ext0 = MS(ads.ds_rxstatus4, + AR_RxRSSIAnt10); + rs->rs_rssi_ext1 = MS(ads.ds_rxstatus4, + AR_RxRSSIAnt11); + rs->rs_rssi_ext2 = MS(ads.ds_rxstatus4, + AR_RxRSSIAnt12); + } + if (ads.ds_rxstatus8 & AR_RxKeyIdxValid) + rs->rs_keyix = MS(ads.ds_rxstatus8, AR_KeyIdx); + else + rs->rs_keyix = ATH9K_RXKEYIX_INVALID; + + rs->rs_rate = MS(ads.ds_rxstatus0, AR_RxRate); + rs->rs_more = (ads.ds_rxstatus1 & AR_RxMore) ? 1 : 0; + + rs->rs_isaggr = (ads.ds_rxstatus8 & AR_RxAggr) ? 1 : 0; + rs->rs_moreaggr = + (ads.ds_rxstatus8 & AR_RxMoreAggr) ? 1 : 0; + rs->rs_antenna = MS(ads.ds_rxstatus3, AR_RxAntenna); + rs->rs_flags = + (ads.ds_rxstatus3 & AR_GI) ? ATH9K_RX_GI : 0; + rs->rs_flags |= + (ads.ds_rxstatus3 & AR_2040) ? ATH9K_RX_2040 : 0; + + if (ads.ds_rxstatus8 & AR_PreDelimCRCErr) + rs->rs_flags |= ATH9K_RX_DELIM_CRC_PRE; + if (ads.ds_rxstatus8 & AR_PostDelimCRCErr) + rs->rs_flags |= ATH9K_RX_DELIM_CRC_POST; + if (ads.ds_rxstatus8 & AR_DecryptBusyErr) + rs->rs_flags |= ATH9K_RX_DECRYPT_BUSY; + + if ((ads.ds_rxstatus8 & AR_RxFrameOK) == 0) { + /* + * Treat these errors as mutually exclusive to avoid spurious + * extra error reports from the hardware. If a CRC error is + * reported, then decryption and MIC errors are irrelevant, + * the frame is going to be dropped either way + */ + if (ads.ds_rxstatus8 & AR_PHYErr) { + rs->rs_status |= ATH9K_RXERR_PHY; + phyerr = MS(ads.ds_rxstatus8, AR_PHYErrCode); + rs->rs_phyerr = phyerr; + } else if (ads.ds_rxstatus8 & AR_CRCErr) + rs->rs_status |= ATH9K_RXERR_CRC; + else if (ads.ds_rxstatus8 & AR_DecryptCRCErr) + rs->rs_status |= ATH9K_RXERR_DECRYPT; + else if (ads.ds_rxstatus8 & AR_MichaelErr) + rs->rs_status |= ATH9K_RXERR_MIC; + } else { + if (ads.ds_rxstatus8 & + (AR_CRCErr | AR_PHYErr | AR_DecryptCRCErr | AR_MichaelErr)) + rs->rs_status |= ATH9K_RXERR_CORRUPT_DESC; + + /* Only up to MCS16 supported, everything above is invalid */ + if (rs->rs_rate >= 0x90) + rs->rs_status |= ATH9K_RXERR_CORRUPT_DESC; + } + + if (ads.ds_rxstatus8 & AR_KeyMiss) + rs->rs_status |= ATH9K_RXERR_KEYMISS; + + return 0; +} +EXPORT_SYMBOL(ath9k_hw_rxprocdesc); + +/* + * This can stop or re-enables RX. + * + * If bool is set this will kill any frame which is currently being + * transferred between the MAC and baseband and also prevent any new + * frames from getting started. + */ +bool ath9k_hw_setrxabort(struct ath_hw *ah, bool set) +{ + u32 reg; + + if (set) { + REG_SET_BIT(ah, AR_DIAG_SW, + (AR_DIAG_RX_DIS | AR_DIAG_RX_ABORT)); + + if (!ath9k_hw_wait(ah, AR_OBS_BUS_1, AR_OBS_BUS_1_RX_STATE, + 0, AH_WAIT_TIMEOUT)) { + REG_CLR_BIT(ah, AR_DIAG_SW, + (AR_DIAG_RX_DIS | + AR_DIAG_RX_ABORT)); + + reg = REG_READ(ah, AR_OBS_BUS_1); + ath_err(ath9k_hw_common(ah), + "RX failed to go idle in 10 ms RXSM=0x%x\n", + reg); + + return false; + } + } else { + REG_CLR_BIT(ah, AR_DIAG_SW, + (AR_DIAG_RX_DIS | AR_DIAG_RX_ABORT)); + } + + return true; +} +EXPORT_SYMBOL(ath9k_hw_setrxabort); + +void ath9k_hw_putrxbuf(struct ath_hw *ah, u32 rxdp) +{ + REG_WRITE(ah, AR_RXDP, rxdp); +} +EXPORT_SYMBOL(ath9k_hw_putrxbuf); + +void ath9k_hw_startpcureceive(struct ath_hw *ah, bool is_scanning) +{ + ath9k_enable_mib_counters(ah); + + ath9k_ani_reset(ah, is_scanning); + + REG_CLR_BIT(ah, AR_DIAG_SW, (AR_DIAG_RX_DIS | AR_DIAG_RX_ABORT)); +} +EXPORT_SYMBOL(ath9k_hw_startpcureceive); + +void ath9k_hw_abortpcurecv(struct ath_hw *ah) +{ + REG_SET_BIT(ah, AR_DIAG_SW, AR_DIAG_RX_ABORT | AR_DIAG_RX_DIS); + + ath9k_hw_disable_mib_counters(ah); +} +EXPORT_SYMBOL(ath9k_hw_abortpcurecv); + +bool ath9k_hw_stopdmarecv(struct ath_hw *ah, bool *reset) +{ +#define AH_RX_STOP_DMA_TIMEOUT 10000 /* usec */ + struct ath_common *common = ath9k_hw_common(ah); + u32 mac_status, last_mac_status = 0; + int i; + + /* Enable access to the DMA observation bus */ + REG_WRITE(ah, AR_MACMISC, + ((AR_MACMISC_DMA_OBS_LINE_8 << AR_MACMISC_DMA_OBS_S) | + (AR_MACMISC_MISC_OBS_BUS_1 << + AR_MACMISC_MISC_OBS_BUS_MSB_S))); + + REG_WRITE(ah, AR_CR, AR_CR_RXD); + + /* Wait for rx enable bit to go low */ + for (i = AH_RX_STOP_DMA_TIMEOUT / AH_TIME_QUANTUM; i != 0; i--) { + if ((REG_READ(ah, AR_CR) & AR_CR_RXE) == 0) + break; + + if (!AR_SREV_9300_20_OR_LATER(ah)) { + mac_status = REG_READ(ah, AR_DMADBG_7) & 0x7f0; + if (mac_status == 0x1c0 && mac_status == last_mac_status) { + *reset = true; + break; + } + + last_mac_status = mac_status; + } + + udelay(AH_TIME_QUANTUM); + } + + if (i == 0) { + ath_err(common, + "DMA failed to stop in %d ms AR_CR=0x%08x AR_DIAG_SW=0x%08x DMADBG_7=0x%08x\n", + AH_RX_STOP_DMA_TIMEOUT / 1000, + REG_READ(ah, AR_CR), + REG_READ(ah, AR_DIAG_SW), + REG_READ(ah, AR_DMADBG_7)); + return false; + } else { + return true; + } + +#undef AH_RX_STOP_DMA_TIMEOUT +} +EXPORT_SYMBOL(ath9k_hw_stopdmarecv); + +int ath9k_hw_beaconq_setup(struct ath_hw *ah) +{ + struct ath9k_tx_queue_info qi; + + memset(&qi, 0, sizeof(qi)); + qi.tqi_aifs = 1; + qi.tqi_cwmin = 0; + qi.tqi_cwmax = 0; + + if (ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) + qi.tqi_qflags = TXQ_FLAG_TXINT_ENABLE; + + return ath9k_hw_setuptxqueue(ah, ATH9K_TX_QUEUE_BEACON, &qi); +} +EXPORT_SYMBOL(ath9k_hw_beaconq_setup); + +bool ath9k_hw_intrpend(struct ath_hw *ah) +{ + u32 host_isr; + + if (AR_SREV_9100(ah)) + return true; + + host_isr = REG_READ(ah, AR_INTR_ASYNC_CAUSE); + + if (((host_isr & AR_INTR_MAC_IRQ) || + (host_isr & AR_INTR_ASYNC_MASK_MCI)) && + (host_isr != AR_INTR_SPURIOUS)) + return true; + + host_isr = REG_READ(ah, AR_INTR_SYNC_CAUSE); + if ((host_isr & AR_INTR_SYNC_DEFAULT) + && (host_isr != AR_INTR_SPURIOUS)) + return true; + + return false; +} +EXPORT_SYMBOL(ath9k_hw_intrpend); + +void ath9k_hw_kill_interrupts(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + + ath_dbg(common, INTERRUPT, "disable IER\n"); + REG_WRITE(ah, AR_IER, AR_IER_DISABLE); + (void) REG_READ(ah, AR_IER); + if (!AR_SREV_9100(ah)) { + REG_WRITE(ah, AR_INTR_ASYNC_ENABLE, 0); + (void) REG_READ(ah, AR_INTR_ASYNC_ENABLE); + + REG_WRITE(ah, AR_INTR_SYNC_ENABLE, 0); + (void) REG_READ(ah, AR_INTR_SYNC_ENABLE); + } +} +EXPORT_SYMBOL(ath9k_hw_kill_interrupts); + +void ath9k_hw_disable_interrupts(struct ath_hw *ah) +{ + if (!(ah->imask & ATH9K_INT_GLOBAL)) + atomic_set(&ah->intr_ref_cnt, -1); + else + atomic_dec(&ah->intr_ref_cnt); + + ath9k_hw_kill_interrupts(ah); +} +EXPORT_SYMBOL(ath9k_hw_disable_interrupts); + +void ath9k_hw_enable_interrupts(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + u32 sync_default = AR_INTR_SYNC_DEFAULT; + u32 async_mask; + + if (!(ah->imask & ATH9K_INT_GLOBAL)) + return; + + if (!atomic_inc_and_test(&ah->intr_ref_cnt)) { + ath_dbg(common, INTERRUPT, "Do not enable IER ref count %d\n", + atomic_read(&ah->intr_ref_cnt)); + return; + } + + if (AR_SREV_9340(ah) || AR_SREV_9550(ah)) + sync_default &= ~AR_INTR_SYNC_HOST1_FATAL; + + async_mask = AR_INTR_MAC_IRQ; + + if (ah->imask & ATH9K_INT_MCI) + async_mask |= AR_INTR_ASYNC_MASK_MCI; + + ath_dbg(common, INTERRUPT, "enable IER\n"); + REG_WRITE(ah, AR_IER, AR_IER_ENABLE); + if (!AR_SREV_9100(ah)) { + REG_WRITE(ah, AR_INTR_ASYNC_ENABLE, async_mask); + REG_WRITE(ah, AR_INTR_ASYNC_MASK, async_mask); + + REG_WRITE(ah, AR_INTR_SYNC_ENABLE, sync_default); + REG_WRITE(ah, AR_INTR_SYNC_MASK, sync_default); + } + ath_dbg(common, INTERRUPT, "AR_IMR 0x%x IER 0x%x\n", + REG_READ(ah, AR_IMR), REG_READ(ah, AR_IER)); +} +EXPORT_SYMBOL(ath9k_hw_enable_interrupts); + +void ath9k_hw_set_interrupts(struct ath_hw *ah) +{ + enum ath9k_int ints = ah->imask; + u32 mask, mask2; + struct ath9k_hw_capabilities *pCap = &ah->caps; + struct ath_common *common = ath9k_hw_common(ah); + + if (!(ints & ATH9K_INT_GLOBAL)) + ath9k_hw_disable_interrupts(ah); + + ath_dbg(common, INTERRUPT, "New interrupt mask 0x%x\n", ints); + + mask = ints & ATH9K_INT_COMMON; + mask2 = 0; + + if (ints & ATH9K_INT_TX) { + if (ah->config.tx_intr_mitigation) + mask |= AR_IMR_TXMINTR | AR_IMR_TXINTM; + else { + if (ah->txok_interrupt_mask) + mask |= AR_IMR_TXOK; + if (ah->txdesc_interrupt_mask) + mask |= AR_IMR_TXDESC; + } + if (ah->txerr_interrupt_mask) + mask |= AR_IMR_TXERR; + if (ah->txeol_interrupt_mask) + mask |= AR_IMR_TXEOL; + } + if (ints & ATH9K_INT_RX) { + if (AR_SREV_9300_20_OR_LATER(ah)) { + mask |= AR_IMR_RXERR | AR_IMR_RXOK_HP; + if (ah->config.rx_intr_mitigation) { + mask &= ~AR_IMR_RXOK_LP; + mask |= AR_IMR_RXMINTR | AR_IMR_RXINTM; + } else { + mask |= AR_IMR_RXOK_LP; + } + } else { + if (ah->config.rx_intr_mitigation) + mask |= AR_IMR_RXMINTR | AR_IMR_RXINTM; + else + mask |= AR_IMR_RXOK | AR_IMR_RXDESC; + } + if (!(pCap->hw_caps & ATH9K_HW_CAP_AUTOSLEEP)) + mask |= AR_IMR_GENTMR; + } + + if (ints & ATH9K_INT_GENTIMER) + mask |= AR_IMR_GENTMR; + + if (ints & (ATH9K_INT_BMISC)) { + mask |= AR_IMR_BCNMISC; + if (ints & ATH9K_INT_TIM) + mask2 |= AR_IMR_S2_TIM; + if (ints & ATH9K_INT_DTIM) + mask2 |= AR_IMR_S2_DTIM; + if (ints & ATH9K_INT_DTIMSYNC) + mask2 |= AR_IMR_S2_DTIMSYNC; + if (ints & ATH9K_INT_CABEND) + mask2 |= AR_IMR_S2_CABEND; + if (ints & ATH9K_INT_TSFOOR) + mask2 |= AR_IMR_S2_TSFOOR; + } + + if (ints & (ATH9K_INT_GTT | ATH9K_INT_CST)) { + mask |= AR_IMR_BCNMISC; + if (ints & ATH9K_INT_GTT) + mask2 |= AR_IMR_S2_GTT; + if (ints & ATH9K_INT_CST) + mask2 |= AR_IMR_S2_CST; + } + + ath_dbg(common, INTERRUPT, "new IMR 0x%x\n", mask); + REG_WRITE(ah, AR_IMR, mask); + ah->imrs2_reg &= ~(AR_IMR_S2_TIM | AR_IMR_S2_DTIM | AR_IMR_S2_DTIMSYNC | + AR_IMR_S2_CABEND | AR_IMR_S2_CABTO | + AR_IMR_S2_TSFOOR | AR_IMR_S2_GTT | AR_IMR_S2_CST); + ah->imrs2_reg |= mask2; + REG_WRITE(ah, AR_IMR_S2, ah->imrs2_reg); + + if (!(pCap->hw_caps & ATH9K_HW_CAP_AUTOSLEEP)) { + if (ints & ATH9K_INT_TIM_TIMER) + REG_SET_BIT(ah, AR_IMR_S5, AR_IMR_S5_TIM_TIMER); + else + REG_CLR_BIT(ah, AR_IMR_S5, AR_IMR_S5_TIM_TIMER); + } + + return; +} +EXPORT_SYMBOL(ath9k_hw_set_interrupts); diff --git a/drivers/net/wireless/ath/ath9k/mac.h b/drivers/net/wireless/ath/ath9k/mac.h new file mode 100644 index 0000000..5865f92 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/mac.h @@ -0,0 +1,747 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef MAC_H +#define MAC_H + +#define set11nTries(_series, _index) \ + (SM((_series)[_index].Tries, AR_XmitDataTries##_index)) + +#define set11nRate(_series, _index) \ + (SM((_series)[_index].Rate, AR_XmitRate##_index)) + +#define set11nPktDurRTSCTS(_series, _index) \ + (SM((_series)[_index].PktDuration, AR_PacketDur##_index) | \ + ((_series)[_index].RateFlags & ATH9K_RATESERIES_RTS_CTS ? \ + AR_RTSCTSQual##_index : 0)) + +#define set11nRateFlags(_series, _index) \ + (((_series)[_index].RateFlags & ATH9K_RATESERIES_2040 ? \ + AR_2040_##_index : 0) \ + |((_series)[_index].RateFlags & ATH9K_RATESERIES_HALFGI ? \ + AR_GI##_index : 0) \ + |((_series)[_index].RateFlags & ATH9K_RATESERIES_STBC ? \ + AR_STBC##_index : 0) \ + |SM((_series)[_index].ChSel, AR_ChainSel##_index)) + +#define CCK_SIFS_TIME 10 +#define CCK_PREAMBLE_BITS 144 +#define CCK_PLCP_BITS 48 + +#define OFDM_SIFS_TIME 16 +#define OFDM_PREAMBLE_TIME 20 +#define OFDM_PLCP_BITS 22 +#define OFDM_SYMBOL_TIME 4 + +#define OFDM_SIFS_TIME_HALF 32 +#define OFDM_PREAMBLE_TIME_HALF 40 +#define OFDM_PLCP_BITS_HALF 22 +#define OFDM_SYMBOL_TIME_HALF 8 + +#define OFDM_SIFS_TIME_QUARTER 64 +#define OFDM_PREAMBLE_TIME_QUARTER 80 +#define OFDM_PLCP_BITS_QUARTER 22 +#define OFDM_SYMBOL_TIME_QUARTER 16 + +#define INIT_AIFS 2 +#define INIT_CWMIN 15 +#define INIT_CWMIN_11B 31 +#define INIT_CWMAX 1023 +#define INIT_SH_RETRY 10 +#define INIT_LG_RETRY 10 +#define INIT_SSH_RETRY 32 +#define INIT_SLG_RETRY 32 + +#define ATH9K_SLOT_TIME_6 6 +#define ATH9K_SLOT_TIME_9 9 +#define ATH9K_SLOT_TIME_20 20 + +#define ATH9K_TXERR_XRETRY 0x01 +#define ATH9K_TXERR_FILT 0x02 +#define ATH9K_TXERR_FIFO 0x04 +#define ATH9K_TXERR_XTXOP 0x08 +#define ATH9K_TXERR_TIMER_EXPIRED 0x10 +#define ATH9K_TX_ACKED 0x20 +#define ATH9K_TX_FLUSH 0x40 +#define ATH9K_TXERR_MASK \ + (ATH9K_TXERR_XRETRY | ATH9K_TXERR_FILT | ATH9K_TXERR_FIFO | \ + ATH9K_TXERR_XTXOP | ATH9K_TXERR_TIMER_EXPIRED | ATH9K_TX_FLUSH) + +#define ATH9K_TX_BA 0x01 +#define ATH9K_TX_PWRMGMT 0x02 +#define ATH9K_TX_DESC_CFG_ERR 0x04 +#define ATH9K_TX_DATA_UNDERRUN 0x08 +#define ATH9K_TX_DELIM_UNDERRUN 0x10 +#define ATH9K_TX_SW_FILTERED 0x80 + +/* 64 bytes */ +#define MIN_TX_FIFO_THRESHOLD 0x1 + +/* + * Single stream device AR9285 and AR9271 require 2 KB + * to work around a hardware issue, all other devices + * have can use the max 4 KB limit. + */ +#define MAX_TX_FIFO_THRESHOLD ((4096 / 64) - 1) + +struct ath_tx_status { + u32 ts_tstamp; + u16 ts_seqnum; + u8 ts_status; + u8 ts_rateindex; + int8_t ts_rssi; + u8 ts_shortretry; + u8 ts_longretry; + u8 ts_virtcol; + u8 ts_flags; + int8_t ts_rssi_ctl0; + int8_t ts_rssi_ctl1; + int8_t ts_rssi_ctl2; + int8_t ts_rssi_ext0; + int8_t ts_rssi_ext1; + int8_t ts_rssi_ext2; + u8 qid; + u16 desc_id; + u8 tid; + u32 ba_low; + u32 ba_high; + u32 evm0; + u32 evm1; + u32 evm2; +}; + +struct ath_rx_status { + u32 rs_tstamp; + u16 rs_datalen; + u8 rs_status; + u8 rs_phyerr; + int8_t rs_rssi; + u8 rs_keyix; + u8 rs_rate; + u8 rs_antenna; + u8 rs_more; + int8_t rs_rssi_ctl0; + int8_t rs_rssi_ctl1; + int8_t rs_rssi_ctl2; + int8_t rs_rssi_ext0; + int8_t rs_rssi_ext1; + int8_t rs_rssi_ext2; + u8 rs_isaggr; + u8 rs_moreaggr; + u8 rs_num_delims; + u8 rs_flags; + bool is_mybeacon; + u32 evm0; + u32 evm1; + u32 evm2; + u32 evm3; + u32 evm4; +}; + +struct ath_htc_rx_status { + __be64 rs_tstamp; + __be16 rs_datalen; + u8 rs_status; + u8 rs_phyerr; + int8_t rs_rssi; + int8_t rs_rssi_ctl0; + int8_t rs_rssi_ctl1; + int8_t rs_rssi_ctl2; + int8_t rs_rssi_ext0; + int8_t rs_rssi_ext1; + int8_t rs_rssi_ext2; + u8 rs_keyix; + u8 rs_rate; + u8 rs_antenna; + u8 rs_more; + u8 rs_isaggr; + u8 rs_moreaggr; + u8 rs_num_delims; + u8 rs_flags; + u8 rs_dummy; + __be32 evm0; + __be32 evm1; + __be32 evm2; +}; + +#define ATH9K_RXERR_CRC 0x01 +#define ATH9K_RXERR_PHY 0x02 +#define ATH9K_RXERR_FIFO 0x04 +#define ATH9K_RXERR_DECRYPT 0x08 +#define ATH9K_RXERR_MIC 0x10 +#define ATH9K_RXERR_KEYMISS 0x20 +#define ATH9K_RXERR_CORRUPT_DESC 0x40 + +#define ATH9K_RX_MORE 0x01 +#define ATH9K_RX_MORE_AGGR 0x02 +#define ATH9K_RX_GI 0x04 +#define ATH9K_RX_2040 0x08 +#define ATH9K_RX_DELIM_CRC_PRE 0x10 +#define ATH9K_RX_DELIM_CRC_POST 0x20 +#define ATH9K_RX_DECRYPT_BUSY 0x40 + +#define ATH9K_RXKEYIX_INVALID ((u8)-1) +#define ATH9K_TXKEYIX_INVALID ((u8)-1) + +enum ath9k_phyerr { + ATH9K_PHYERR_UNDERRUN = 0, /* Transmit underrun */ + ATH9K_PHYERR_TIMING = 1, /* Timing error */ + ATH9K_PHYERR_PARITY = 2, /* Illegal parity */ + ATH9K_PHYERR_RATE = 3, /* Illegal rate */ + ATH9K_PHYERR_LENGTH = 4, /* Illegal length */ + ATH9K_PHYERR_RADAR = 5, /* Radar detect */ + ATH9K_PHYERR_SERVICE = 6, /* Illegal service */ + ATH9K_PHYERR_TOR = 7, /* Transmit override receive */ + + ATH9K_PHYERR_OFDM_TIMING = 17, + ATH9K_PHYERR_OFDM_SIGNAL_PARITY = 18, + ATH9K_PHYERR_OFDM_RATE_ILLEGAL = 19, + ATH9K_PHYERR_OFDM_LENGTH_ILLEGAL = 20, + ATH9K_PHYERR_OFDM_POWER_DROP = 21, + ATH9K_PHYERR_OFDM_SERVICE = 22, + ATH9K_PHYERR_OFDM_RESTART = 23, + ATH9K_PHYERR_FALSE_RADAR_EXT = 24, + + ATH9K_PHYERR_CCK_TIMING = 25, + ATH9K_PHYERR_CCK_HEADER_CRC = 26, + ATH9K_PHYERR_CCK_RATE_ILLEGAL = 27, + ATH9K_PHYERR_CCK_SERVICE = 30, + ATH9K_PHYERR_CCK_RESTART = 31, + ATH9K_PHYERR_CCK_LENGTH_ILLEGAL = 32, + ATH9K_PHYERR_CCK_POWER_DROP = 33, + + ATH9K_PHYERR_HT_CRC_ERROR = 34, + ATH9K_PHYERR_HT_LENGTH_ILLEGAL = 35, + ATH9K_PHYERR_HT_RATE_ILLEGAL = 36, + + ATH9K_PHYERR_SPECTRAL = 38, + ATH9K_PHYERR_MAX = 39, +}; + +struct ath_desc { + u32 ds_link; + u32 ds_data; + u32 ds_ctl0; + u32 ds_ctl1; + u32 ds_hw[20]; + void *ds_vdata; +} __packed __aligned(4); + +#define ATH9K_TXDESC_NOACK 0x0002 +#define ATH9K_TXDESC_RTSENA 0x0004 +#define ATH9K_TXDESC_CTSENA 0x0008 +/* ATH9K_TXDESC_INTREQ forces a tx interrupt to be generated for + * the descriptor its marked on. We take a tx interrupt to reap + * descriptors when the h/w hits an EOL condition or + * when the descriptor is specifically marked to generate + * an interrupt with this flag. Descriptors should be + * marked periodically to insure timely replenishing of the + * supply needed for sending frames. Defering interrupts + * reduces system load and potentially allows more concurrent + * work to be done but if done to aggressively can cause + * senders to backup. When the hardware queue is left too + * large rate control information may also be too out of + * date. An Alternative for this is TX interrupt mitigation + * but this needs more testing. */ +#define ATH9K_TXDESC_INTREQ 0x0010 +#define ATH9K_TXDESC_VEOL 0x0020 +#define ATH9K_TXDESC_EXT_ONLY 0x0040 +#define ATH9K_TXDESC_EXT_AND_CTL 0x0080 +#define ATH9K_TXDESC_VMF 0x0100 +#define ATH9K_TXDESC_FRAG_IS_ON 0x0200 +#define ATH9K_TXDESC_LOWRXCHAIN 0x0400 +#define ATH9K_TXDESC_LDPC 0x0800 +#define ATH9K_TXDESC_CLRDMASK 0x1000 + +#define ATH9K_TXDESC_PAPRD 0x70000 +#define ATH9K_TXDESC_PAPRD_S 16 + +#define ATH9K_RXDESC_INTREQ 0x0020 + +struct ar5416_desc { + u32 ds_link; + u32 ds_data; + u32 ds_ctl0; + u32 ds_ctl1; + union { + struct { + u32 ctl2; + u32 ctl3; + u32 ctl4; + u32 ctl5; + u32 ctl6; + u32 ctl7; + u32 ctl8; + u32 ctl9; + u32 ctl10; + u32 ctl11; + u32 status0; + u32 status1; + u32 status2; + u32 status3; + u32 status4; + u32 status5; + u32 status6; + u32 status7; + u32 status8; + u32 status9; + } tx; + struct { + u32 status0; + u32 status1; + u32 status2; + u32 status3; + u32 status4; + u32 status5; + u32 status6; + u32 status7; + u32 status8; + } rx; + } u; +} __packed __aligned(4); + +#define AR5416DESC(_ds) ((struct ar5416_desc *)(_ds)) +#define AR5416DESC_CONST(_ds) ((const struct ar5416_desc *)(_ds)) + +#define ds_ctl2 u.tx.ctl2 +#define ds_ctl3 u.tx.ctl3 +#define ds_ctl4 u.tx.ctl4 +#define ds_ctl5 u.tx.ctl5 +#define ds_ctl6 u.tx.ctl6 +#define ds_ctl7 u.tx.ctl7 +#define ds_ctl8 u.tx.ctl8 +#define ds_ctl9 u.tx.ctl9 +#define ds_ctl10 u.tx.ctl10 +#define ds_ctl11 u.tx.ctl11 + +#define ds_txstatus0 u.tx.status0 +#define ds_txstatus1 u.tx.status1 +#define ds_txstatus2 u.tx.status2 +#define ds_txstatus3 u.tx.status3 +#define ds_txstatus4 u.tx.status4 +#define ds_txstatus5 u.tx.status5 +#define ds_txstatus6 u.tx.status6 +#define ds_txstatus7 u.tx.status7 +#define ds_txstatus8 u.tx.status8 +#define ds_txstatus9 u.tx.status9 + +#define ds_rxstatus0 u.rx.status0 +#define ds_rxstatus1 u.rx.status1 +#define ds_rxstatus2 u.rx.status2 +#define ds_rxstatus3 u.rx.status3 +#define ds_rxstatus4 u.rx.status4 +#define ds_rxstatus5 u.rx.status5 +#define ds_rxstatus6 u.rx.status6 +#define ds_rxstatus7 u.rx.status7 +#define ds_rxstatus8 u.rx.status8 + +#define AR_FrameLen 0x00000fff +#define AR_VirtMoreFrag 0x00001000 +#define AR_TxCtlRsvd00 0x0000e000 +#define AR_XmitPower 0x003f0000 +#define AR_XmitPower_S 16 +#define AR_RTSEnable 0x00400000 +#define AR_VEOL 0x00800000 +#define AR_ClrDestMask 0x01000000 +#define AR_TxCtlRsvd01 0x1e000000 +#define AR_TxIntrReq 0x20000000 +#define AR_DestIdxValid 0x40000000 +#define AR_CTSEnable 0x80000000 + +#define AR_TxMore 0x00001000 +#define AR_DestIdx 0x000fe000 +#define AR_DestIdx_S 13 +#define AR_FrameType 0x00f00000 +#define AR_FrameType_S 20 +#define AR_NoAck 0x01000000 +#define AR_InsertTS 0x02000000 +#define AR_CorruptFCS 0x04000000 +#define AR_ExtOnly 0x08000000 +#define AR_ExtAndCtl 0x10000000 +#define AR_MoreAggr 0x20000000 +#define AR_IsAggr 0x40000000 + +#define AR_BurstDur 0x00007fff +#define AR_BurstDur_S 0 +#define AR_DurUpdateEna 0x00008000 +#define AR_XmitDataTries0 0x000f0000 +#define AR_XmitDataTries0_S 16 +#define AR_XmitDataTries1 0x00f00000 +#define AR_XmitDataTries1_S 20 +#define AR_XmitDataTries2 0x0f000000 +#define AR_XmitDataTries2_S 24 +#define AR_XmitDataTries3 0xf0000000 +#define AR_XmitDataTries3_S 28 + +#define AR_XmitRate0 0x000000ff +#define AR_XmitRate0_S 0 +#define AR_XmitRate1 0x0000ff00 +#define AR_XmitRate1_S 8 +#define AR_XmitRate2 0x00ff0000 +#define AR_XmitRate2_S 16 +#define AR_XmitRate3 0xff000000 +#define AR_XmitRate3_S 24 + +#define AR_PacketDur0 0x00007fff +#define AR_PacketDur0_S 0 +#define AR_RTSCTSQual0 0x00008000 +#define AR_PacketDur1 0x7fff0000 +#define AR_PacketDur1_S 16 +#define AR_RTSCTSQual1 0x80000000 + +#define AR_PacketDur2 0x00007fff +#define AR_PacketDur2_S 0 +#define AR_RTSCTSQual2 0x00008000 +#define AR_PacketDur3 0x7fff0000 +#define AR_PacketDur3_S 16 +#define AR_RTSCTSQual3 0x80000000 + +#define AR_AggrLen 0x0000ffff +#define AR_AggrLen_S 0 +#define AR_TxCtlRsvd60 0x00030000 +#define AR_PadDelim 0x03fc0000 +#define AR_PadDelim_S 18 +#define AR_EncrType 0x0c000000 +#define AR_EncrType_S 26 +#define AR_TxCtlRsvd61 0xf0000000 +#define AR_LDPC 0x80000000 + +#define AR_2040_0 0x00000001 +#define AR_GI0 0x00000002 +#define AR_ChainSel0 0x0000001c +#define AR_ChainSel0_S 2 +#define AR_2040_1 0x00000020 +#define AR_GI1 0x00000040 +#define AR_ChainSel1 0x00000380 +#define AR_ChainSel1_S 7 +#define AR_2040_2 0x00000400 +#define AR_GI2 0x00000800 +#define AR_ChainSel2 0x00007000 +#define AR_ChainSel2_S 12 +#define AR_2040_3 0x00008000 +#define AR_GI3 0x00010000 +#define AR_ChainSel3 0x000e0000 +#define AR_ChainSel3_S 17 +#define AR_RTSCTSRate 0x0ff00000 +#define AR_RTSCTSRate_S 20 +#define AR_STBC0 0x10000000 +#define AR_STBC1 0x20000000 +#define AR_STBC2 0x40000000 +#define AR_STBC3 0x80000000 + +#define AR_TxRSSIAnt00 0x000000ff +#define AR_TxRSSIAnt00_S 0 +#define AR_TxRSSIAnt01 0x0000ff00 +#define AR_TxRSSIAnt01_S 8 +#define AR_TxRSSIAnt02 0x00ff0000 +#define AR_TxRSSIAnt02_S 16 +#define AR_TxStatusRsvd00 0x3f000000 +#define AR_TxBaStatus 0x40000000 +#define AR_TxStatusRsvd01 0x80000000 + +/* + * AR_FrmXmitOK - Frame transmission success flag. If set, the frame was + * transmitted successfully. If clear, no ACK or BA was received to indicate + * successful transmission when we were expecting an ACK or BA. + */ +#define AR_FrmXmitOK 0x00000001 +#define AR_ExcessiveRetries 0x00000002 +#define AR_FIFOUnderrun 0x00000004 +#define AR_Filtered 0x00000008 +#define AR_RTSFailCnt 0x000000f0 +#define AR_RTSFailCnt_S 4 +#define AR_DataFailCnt 0x00000f00 +#define AR_DataFailCnt_S 8 +#define AR_VirtRetryCnt 0x0000f000 +#define AR_VirtRetryCnt_S 12 +#define AR_TxDelimUnderrun 0x00010000 +#define AR_TxDataUnderrun 0x00020000 +#define AR_DescCfgErr 0x00040000 +#define AR_TxTimerExpired 0x00080000 +#define AR_TxStatusRsvd10 0xfff00000 + +#define AR_SendTimestamp ds_txstatus2 +#define AR_BaBitmapLow ds_txstatus3 +#define AR_BaBitmapHigh ds_txstatus4 + +#define AR_TxRSSIAnt10 0x000000ff +#define AR_TxRSSIAnt10_S 0 +#define AR_TxRSSIAnt11 0x0000ff00 +#define AR_TxRSSIAnt11_S 8 +#define AR_TxRSSIAnt12 0x00ff0000 +#define AR_TxRSSIAnt12_S 16 +#define AR_TxRSSICombined 0xff000000 +#define AR_TxRSSICombined_S 24 + +#define AR_TxTid 0xf0000000 +#define AR_TxTid_S 28 + +#define AR_TxEVM0 ds_txstatus5 +#define AR_TxEVM1 ds_txstatus6 +#define AR_TxEVM2 ds_txstatus7 + +#define AR_TxDone 0x00000001 +#define AR_SeqNum 0x00001ffe +#define AR_SeqNum_S 1 +#define AR_TxStatusRsvd80 0x0001e000 +#define AR_TxOpExceeded 0x00020000 +#define AR_TxStatusRsvd81 0x001c0000 +#define AR_FinalTxIdx 0x00600000 +#define AR_FinalTxIdx_S 21 +#define AR_TxStatusRsvd82 0x01800000 +#define AR_PowerMgmt 0x02000000 +#define AR_TxStatusRsvd83 0xfc000000 + +#define AR_RxCTLRsvd00 0xffffffff + +#define AR_RxCtlRsvd00 0x00001000 +#define AR_RxIntrReq 0x00002000 +#define AR_RxCtlRsvd01 0xffffc000 + +#define AR_RxRSSIAnt00 0x000000ff +#define AR_RxRSSIAnt00_S 0 +#define AR_RxRSSIAnt01 0x0000ff00 +#define AR_RxRSSIAnt01_S 8 +#define AR_RxRSSIAnt02 0x00ff0000 +#define AR_RxRSSIAnt02_S 16 +#define AR_RxRate 0xff000000 +#define AR_RxRate_S 24 +#define AR_RxStatusRsvd00 0xff000000 + +#define AR_DataLen 0x00000fff +#define AR_RxMore 0x00001000 +#define AR_NumDelim 0x003fc000 +#define AR_NumDelim_S 14 +#define AR_RxStatusRsvd10 0xff800000 + +#define AR_RcvTimestamp ds_rxstatus2 + +#define AR_GI 0x00000001 +#define AR_2040 0x00000002 +#define AR_Parallel40 0x00000004 +#define AR_Parallel40_S 2 +#define AR_RxStatusRsvd30 0x000000f8 +#define AR_RxAntenna 0xffffff00 +#define AR_RxAntenna_S 8 + +#define AR_RxRSSIAnt10 0x000000ff +#define AR_RxRSSIAnt10_S 0 +#define AR_RxRSSIAnt11 0x0000ff00 +#define AR_RxRSSIAnt11_S 8 +#define AR_RxRSSIAnt12 0x00ff0000 +#define AR_RxRSSIAnt12_S 16 +#define AR_RxRSSICombined 0xff000000 +#define AR_RxRSSICombined_S 24 + +#define AR_RxEVM0 ds_rxstatus4 +#define AR_RxEVM1 ds_rxstatus5 +#define AR_RxEVM2 ds_rxstatus6 + +#define AR_RxDone 0x00000001 +#define AR_RxFrameOK 0x00000002 +#define AR_CRCErr 0x00000004 +#define AR_DecryptCRCErr 0x00000008 +#define AR_PHYErr 0x00000010 +#define AR_MichaelErr 0x00000020 +#define AR_PreDelimCRCErr 0x00000040 +#define AR_RxStatusRsvd70 0x00000080 +#define AR_RxKeyIdxValid 0x00000100 +#define AR_KeyIdx 0x0000fe00 +#define AR_KeyIdx_S 9 +#define AR_PHYErrCode 0x0000ff00 +#define AR_PHYErrCode_S 8 +#define AR_RxMoreAggr 0x00010000 +#define AR_RxAggr 0x00020000 +#define AR_PostDelimCRCErr 0x00040000 +#define AR_RxStatusRsvd71 0x3ff80000 +#define AR_DecryptBusyErr 0x40000000 +#define AR_KeyMiss 0x80000000 + +enum ath9k_tx_queue { + ATH9K_TX_QUEUE_INACTIVE = 0, + ATH9K_TX_QUEUE_DATA, + ATH9K_TX_QUEUE_BEACON, + ATH9K_TX_QUEUE_CAB, + ATH9K_TX_QUEUE_UAPSD, + ATH9K_TX_QUEUE_PSPOLL +}; + +#define ATH9K_NUM_TX_QUEUES 10 + +/* Used as a queue subtype instead of a WMM AC */ +#define ATH9K_WME_UPSD 4 + +enum ath9k_tx_queue_flags { + TXQ_FLAG_TXINT_ENABLE = 0x0001, + TXQ_FLAG_TXDESCINT_ENABLE = 0x0002, + TXQ_FLAG_TXEOLINT_ENABLE = 0x0004, + TXQ_FLAG_TXURNINT_ENABLE = 0x0008, + TXQ_FLAG_BACKOFF_DISABLE = 0x0010, + TXQ_FLAG_COMPRESSION_ENABLE = 0x0020, + TXQ_FLAG_RDYTIME_EXP_POLICY_ENABLE = 0x0040, + TXQ_FLAG_FRAG_BURST_BACKOFF_ENABLE = 0x0080, +}; + +#define ATH9K_TXQ_USEDEFAULT ((u32) -1) +#define ATH9K_TXQ_USE_LOCKOUT_BKOFF_DIS 0x00000001 + +#define ATH9K_DECOMP_MASK_SIZE 128 +#define ATH9K_READY_TIME_LO_BOUND 50 +#define ATH9K_READY_TIME_HI_BOUND 96 + +enum ath9k_pkt_type { + ATH9K_PKT_TYPE_NORMAL = 0, + ATH9K_PKT_TYPE_ATIM, + ATH9K_PKT_TYPE_PSPOLL, + ATH9K_PKT_TYPE_BEACON, + ATH9K_PKT_TYPE_PROBE_RESP, + ATH9K_PKT_TYPE_CHIRP, + ATH9K_PKT_TYPE_GRP_POLL, +}; + +struct ath9k_tx_queue_info { + u32 tqi_ver; + enum ath9k_tx_queue tqi_type; + int tqi_subtype; + enum ath9k_tx_queue_flags tqi_qflags; + u32 tqi_priority; + u32 tqi_aifs; + u32 tqi_cwmin; + u32 tqi_cwmax; + u16 tqi_shretry; + u16 tqi_lgretry; + u32 tqi_cbrPeriod; + u32 tqi_cbrOverflowLimit; + u32 tqi_burstTime; + u32 tqi_readyTime; + u32 tqi_physCompBuf; + u32 tqi_intFlags; +}; + +enum ath9k_rx_filter { + ATH9K_RX_FILTER_UCAST = 0x00000001, + ATH9K_RX_FILTER_MCAST = 0x00000002, + ATH9K_RX_FILTER_BCAST = 0x00000004, + ATH9K_RX_FILTER_CONTROL = 0x00000008, + ATH9K_RX_FILTER_BEACON = 0x00000010, + ATH9K_RX_FILTER_PROM = 0x00000020, + ATH9K_RX_FILTER_PROBEREQ = 0x00000080, + ATH9K_RX_FILTER_PHYERR = 0x00000100, + ATH9K_RX_FILTER_MYBEACON = 0x00000200, + ATH9K_RX_FILTER_COMP_BAR = 0x00000400, + ATH9K_RX_FILTER_COMP_BA = 0x00000800, + ATH9K_RX_FILTER_UNCOMP_BA_BAR = 0x00001000, + ATH9K_RX_FILTER_PSPOLL = 0x00004000, + ATH9K_RX_FILTER_PHYRADAR = 0x00002000, + ATH9K_RX_FILTER_MCAST_BCAST_ALL = 0x00008000, + ATH9K_RX_FILTER_CONTROL_WRAPPER = 0x00080000, + ATH9K_RX_FILTER_4ADDRESS = 0x00100000, +}; + +#define ATH9K_RATESERIES_RTS_CTS 0x0001 +#define ATH9K_RATESERIES_2040 0x0002 +#define ATH9K_RATESERIES_HALFGI 0x0004 +#define ATH9K_RATESERIES_STBC 0x0008 + +struct ath9k_11n_rate_series { + u32 Tries; + u32 Rate; + u32 PktDuration; + u32 ChSel; + u32 RateFlags; +}; + +enum aggr_type { + AGGR_BUF_NONE, + AGGR_BUF_FIRST, + AGGR_BUF_MIDDLE, + AGGR_BUF_LAST, +}; + +enum ath9k_key_type { + ATH9K_KEY_TYPE_CLEAR, + ATH9K_KEY_TYPE_WEP, + ATH9K_KEY_TYPE_AES, + ATH9K_KEY_TYPE_TKIP, +}; + +struct ath_tx_info { + u8 qcu; + + bool is_first; + bool is_last; + + enum aggr_type aggr; + u8 ndelim; + u16 aggr_len; + + dma_addr_t link; + int pkt_len; + u32 flags; + + dma_addr_t buf_addr[4]; + int buf_len[4]; + + struct ath9k_11n_rate_series rates[4]; + u8 rtscts_rate; + bool dur_update; + + enum ath9k_pkt_type type; + enum ath9k_key_type keytype; + u8 keyix; + u8 txpower; +}; + +struct ath_hw; +struct ath9k_channel; +enum ath9k_int; + +u32 ath9k_hw_gettxbuf(struct ath_hw *ah, u32 q); +void ath9k_hw_puttxbuf(struct ath_hw *ah, u32 q, u32 txdp); +void ath9k_hw_txstart(struct ath_hw *ah, u32 q); +u32 ath9k_hw_numtxpending(struct ath_hw *ah, u32 q); +bool ath9k_hw_updatetxtriglevel(struct ath_hw *ah, bool bIncTrigLevel); +bool ath9k_hw_stop_dma_queue(struct ath_hw *ah, u32 q); +void ath9k_hw_abort_tx_dma(struct ath_hw *ah); +bool ath9k_hw_set_txq_props(struct ath_hw *ah, int q, + const struct ath9k_tx_queue_info *qinfo); +bool ath9k_hw_get_txq_props(struct ath_hw *ah, int q, + struct ath9k_tx_queue_info *qinfo); +int ath9k_hw_setuptxqueue(struct ath_hw *ah, enum ath9k_tx_queue type, + const struct ath9k_tx_queue_info *qinfo); +bool ath9k_hw_releasetxqueue(struct ath_hw *ah, u32 q); +bool ath9k_hw_resettxqueue(struct ath_hw *ah, u32 q); +int ath9k_hw_rxprocdesc(struct ath_hw *ah, struct ath_desc *ds, + struct ath_rx_status *rs); +void ath9k_hw_setuprxdesc(struct ath_hw *ah, struct ath_desc *ds, + u32 size, u32 flags); +bool ath9k_hw_setrxabort(struct ath_hw *ah, bool set); +void ath9k_hw_putrxbuf(struct ath_hw *ah, u32 rxdp); +void ath9k_hw_startpcureceive(struct ath_hw *ah, bool is_scanning); +void ath9k_hw_abortpcurecv(struct ath_hw *ah); +bool ath9k_hw_stopdmarecv(struct ath_hw *ah, bool *reset); +int ath9k_hw_beaconq_setup(struct ath_hw *ah); + +/* Interrupt Handling */ +bool ath9k_hw_intrpend(struct ath_hw *ah); +void ath9k_hw_set_interrupts(struct ath_hw *ah); +void ath9k_hw_enable_interrupts(struct ath_hw *ah); +void ath9k_hw_disable_interrupts(struct ath_hw *ah); +void ath9k_hw_kill_interrupts(struct ath_hw *ah); + +void ar9002_hw_attach_mac_ops(struct ath_hw *ah); + +#endif /* MAC_H */ diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c new file mode 100644 index 0000000..0f7aa35 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/main.c @@ -0,0 +1,2394 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include "ath9k.h" +#include "btcoex.h" + +static void ath9k_set_assoc_state(struct ath_softc *sc, + struct ieee80211_vif *vif); + +u8 ath9k_parse_mpdudensity(u8 mpdudensity) +{ + /* + * 802.11n D2.0 defined values for "Minimum MPDU Start Spacing": + * 0 for no restriction + * 1 for 1/4 us + * 2 for 1/2 us + * 3 for 1 us + * 4 for 2 us + * 5 for 4 us + * 6 for 8 us + * 7 for 16 us + */ + switch (mpdudensity) { + case 0: + return 0; + case 1: + case 2: + case 3: + /* Our lower layer calculations limit our precision to + 1 microsecond */ + return 1; + case 4: + return 2; + case 5: + return 4; + case 6: + return 8; + case 7: + return 16; + default: + return 0; + } +} + +static bool ath9k_has_pending_frames(struct ath_softc *sc, struct ath_txq *txq) +{ + bool pending = false; + + spin_lock_bh(&txq->axq_lock); + + if (txq->axq_depth || !list_empty(&txq->axq_acq)) + pending = true; + + spin_unlock_bh(&txq->axq_lock); + return pending; +} + +static bool ath9k_setpower(struct ath_softc *sc, enum ath9k_power_mode mode) +{ + unsigned long flags; + bool ret; + + spin_lock_irqsave(&sc->sc_pm_lock, flags); + ret = ath9k_hw_setpower(sc->sc_ah, mode); + spin_unlock_irqrestore(&sc->sc_pm_lock, flags); + + return ret; +} + +void ath9k_ps_wakeup(struct ath_softc *sc) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + unsigned long flags; + enum ath9k_power_mode power_mode; + + spin_lock_irqsave(&sc->sc_pm_lock, flags); + if (++sc->ps_usecount != 1) + goto unlock; + + power_mode = sc->sc_ah->power_mode; + ath9k_hw_setpower(sc->sc_ah, ATH9K_PM_AWAKE); + + /* + * While the hardware is asleep, the cycle counters contain no + * useful data. Better clear them now so that they don't mess up + * survey data results. + */ + if (power_mode != ATH9K_PM_AWAKE) { + spin_lock(&common->cc_lock); + ath_hw_cycle_counters_update(common); + memset(&common->cc_survey, 0, sizeof(common->cc_survey)); + memset(&common->cc_ani, 0, sizeof(common->cc_ani)); + spin_unlock(&common->cc_lock); + } + + unlock: + spin_unlock_irqrestore(&sc->sc_pm_lock, flags); +} + +void ath9k_ps_restore(struct ath_softc *sc) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + enum ath9k_power_mode mode; + unsigned long flags; + bool reset; + + spin_lock_irqsave(&sc->sc_pm_lock, flags); + if (--sc->ps_usecount != 0) + goto unlock; + + if (sc->ps_idle) { + ath9k_hw_setrxabort(sc->sc_ah, 1); + ath9k_hw_stopdmarecv(sc->sc_ah, &reset); + mode = ATH9K_PM_FULL_SLEEP; + } else if (sc->ps_enabled && + !(sc->ps_flags & (PS_WAIT_FOR_BEACON | + PS_WAIT_FOR_CAB | + PS_WAIT_FOR_PSPOLL_DATA | + PS_WAIT_FOR_TX_ACK | + PS_WAIT_FOR_ANI))) { + mode = ATH9K_PM_NETWORK_SLEEP; + if (ath9k_hw_btcoex_is_enabled(sc->sc_ah)) + ath9k_btcoex_stop_gen_timer(sc); + } else { + goto unlock; + } + + spin_lock(&common->cc_lock); + ath_hw_cycle_counters_update(common); + spin_unlock(&common->cc_lock); + + ath9k_hw_setpower(sc->sc_ah, mode); + + unlock: + spin_unlock_irqrestore(&sc->sc_pm_lock, flags); +} + +static void __ath_cancel_work(struct ath_softc *sc) +{ + cancel_work_sync(&sc->paprd_work); + cancel_work_sync(&sc->hw_check_work); + cancel_delayed_work_sync(&sc->tx_complete_work); + cancel_delayed_work_sync(&sc->hw_pll_work); + +#ifdef CPTCFG_ATH9K_BTCOEX_SUPPORT + if (ath9k_hw_mci_is_enabled(sc->sc_ah)) + cancel_work_sync(&sc->mci_work); +#endif +} + +static void ath_cancel_work(struct ath_softc *sc) +{ + __ath_cancel_work(sc); + cancel_work_sync(&sc->hw_reset_work); +} + +static void ath_restart_work(struct ath_softc *sc) +{ + ieee80211_queue_delayed_work(sc->hw, &sc->tx_complete_work, 0); + + if (AR_SREV_9340(sc->sc_ah) || AR_SREV_9330(sc->sc_ah)) + ieee80211_queue_delayed_work(sc->hw, &sc->hw_pll_work, + msecs_to_jiffies(ATH_PLL_WORK_INTERVAL)); + + ath_start_rx_poll(sc, 3); + ath_start_ani(sc); +} + +static bool ath_prepare_reset(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + bool ret = true; + + ieee80211_stop_queues(sc->hw); + + sc->hw_busy_count = 0; + ath_stop_ani(sc); + del_timer_sync(&sc->rx_poll_timer); + + ath9k_debug_samp_bb_mac(sc); + ath9k_hw_disable_interrupts(ah); + + if (!ath_drain_all_txq(sc)) + ret = false; + + if (!ath_stoprecv(sc)) + ret = false; + + return ret; +} + +static bool ath_complete_reset(struct ath_softc *sc, bool start) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + unsigned long flags; + int i; + + if (ath_startrecv(sc) != 0) { + ath_err(common, "Unable to restart recv logic\n"); + return false; + } + + ath9k_cmn_update_txpow(ah, sc->curtxpow, + sc->config.txpowlimit, &sc->curtxpow); + + clear_bit(SC_OP_HW_RESET, &sc->sc_flags); + ath9k_hw_set_interrupts(ah); + ath9k_hw_enable_interrupts(ah); + + if (!(sc->hw->conf.flags & IEEE80211_CONF_OFFCHANNEL) && start) { + if (!test_bit(SC_OP_BEACONS, &sc->sc_flags)) + goto work; + + if (ah->opmode == NL80211_IFTYPE_STATION && + test_bit(SC_OP_PRIM_STA_VIF, &sc->sc_flags)) { + spin_lock_irqsave(&sc->sc_pm_lock, flags); + sc->ps_flags |= PS_BEACON_SYNC | PS_WAIT_FOR_BEACON; + spin_unlock_irqrestore(&sc->sc_pm_lock, flags); + } else { + ath9k_set_beacon(sc); + } + work: + ath_restart_work(sc); + + for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) { + if (!ATH_TXQ_SETUP(sc, i)) + continue; + + spin_lock_bh(&sc->tx.txq[i].axq_lock); + ath_txq_schedule(sc, &sc->tx.txq[i]); + spin_unlock_bh(&sc->tx.txq[i].axq_lock); + } + } + + if ((ah->caps.hw_caps & ATH9K_HW_CAP_ANT_DIV_COMB) && sc->ant_rx != 3) + ath_ant_comb_update(sc); + + ieee80211_wake_queues(sc->hw); + + return true; +} + +static int ath_reset_internal(struct ath_softc *sc, struct ath9k_channel *hchan) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_hw_cal_data *caldata = NULL; + bool fastcc = true; + int r; + + __ath_cancel_work(sc); + + tasklet_disable(&sc->intr_tq); + spin_lock_bh(&sc->sc_pcu_lock); + + if (!(sc->hw->conf.flags & IEEE80211_CONF_OFFCHANNEL)) { + fastcc = false; + caldata = &sc->caldata; + } + + if (!hchan) { + fastcc = false; + hchan = ah->curchan; + } + + if (!ath_prepare_reset(sc)) + fastcc = false; + + ath_dbg(common, CONFIG, "Reset to %u MHz, HT40: %d fastcc: %d\n", + hchan->channel, IS_CHAN_HT40(hchan), fastcc); + + r = ath9k_hw_reset(ah, hchan, caldata, fastcc); + if (r) { + ath_err(common, + "Unable to reset channel, reset status %d\n", r); + + ath9k_hw_enable_interrupts(ah); + ath9k_queue_reset(sc, RESET_TYPE_BB_HANG); + + goto out; + } + + if (ath9k_hw_mci_is_enabled(sc->sc_ah) && + (sc->hw->conf.flags & IEEE80211_CONF_OFFCHANNEL)) + ath9k_mci_set_txpower(sc, true, false); + + if (!ath_complete_reset(sc, true)) + r = -EIO; + +out: + spin_unlock_bh(&sc->sc_pcu_lock); + tasklet_enable(&sc->intr_tq); + + return r; +} + + +/* + * Set/change channels. If the channel is really being changed, it's done + * by reseting the chip. To accomplish this we must first cleanup any pending + * DMA, then restart stuff. +*/ +static int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw, + struct ath9k_channel *hchan) +{ + int r; + + if (test_bit(SC_OP_INVALID, &sc->sc_flags)) + return -EIO; + + r = ath_reset_internal(sc, hchan); + + return r; +} + +static void ath_node_attach(struct ath_softc *sc, struct ieee80211_sta *sta, + struct ieee80211_vif *vif) +{ + struct ath_node *an; + an = (struct ath_node *)sta->drv_priv; + + an->sc = sc; + an->sta = sta; + an->vif = vif; + + ath_tx_node_init(sc, an); + + if (sta->ht_cap.ht_supported) { + an->maxampdu = 1 << (IEEE80211_HT_MAX_AMPDU_FACTOR + + sta->ht_cap.ampdu_factor); + an->mpdudensity = ath9k_parse_mpdudensity(sta->ht_cap.ampdu_density); + } +} + +static void ath_node_detach(struct ath_softc *sc, struct ieee80211_sta *sta) +{ + struct ath_node *an = (struct ath_node *)sta->drv_priv; + ath_tx_node_cleanup(sc, an); +} + +void ath9k_tasklet(unsigned long data) +{ + struct ath_softc *sc = (struct ath_softc *)data; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + enum ath_reset_type type; + unsigned long flags; + u32 status = sc->intrstatus; + u32 rxmask; + + ath9k_ps_wakeup(sc); + spin_lock(&sc->sc_pcu_lock); + + if ((status & ATH9K_INT_FATAL) || + (status & ATH9K_INT_BB_WATCHDOG)) { + + if (status & ATH9K_INT_FATAL) + type = RESET_TYPE_FATAL_INT; + else + type = RESET_TYPE_BB_WATCHDOG; + + ath9k_queue_reset(sc, type); + goto out; + } + + spin_lock_irqsave(&sc->sc_pm_lock, flags); + if ((status & ATH9K_INT_TSFOOR) && sc->ps_enabled) { + /* + * TSF sync does not look correct; remain awake to sync with + * the next Beacon. + */ + ath_dbg(common, PS, "TSFOOR - Sync with next Beacon\n"); + sc->ps_flags |= PS_WAIT_FOR_BEACON | PS_BEACON_SYNC; + } + spin_unlock_irqrestore(&sc->sc_pm_lock, flags); + + if (ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) + rxmask = (ATH9K_INT_RXHP | ATH9K_INT_RXLP | ATH9K_INT_RXEOL | + ATH9K_INT_RXORN); + else + rxmask = (ATH9K_INT_RX | ATH9K_INT_RXEOL | ATH9K_INT_RXORN); + + if (status & rxmask) { + /* Check for high priority Rx first */ + if ((ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) && + (status & ATH9K_INT_RXHP)) + ath_rx_tasklet(sc, 0, true); + + ath_rx_tasklet(sc, 0, false); + } + + if (status & ATH9K_INT_TX) { + if (ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) + ath_tx_edma_tasklet(sc); + else + ath_tx_tasklet(sc); + } + + ath9k_btcoex_handle_interrupt(sc, status); + +out: + /* re-enable hardware interrupt */ + ath9k_hw_enable_interrupts(ah); + + spin_unlock(&sc->sc_pcu_lock); + ath9k_ps_restore(sc); +} + +irqreturn_t ath_isr(int irq, void *dev) +{ +#define SCHED_INTR ( \ + ATH9K_INT_FATAL | \ + ATH9K_INT_BB_WATCHDOG | \ + ATH9K_INT_RXORN | \ + ATH9K_INT_RXEOL | \ + ATH9K_INT_RX | \ + ATH9K_INT_RXLP | \ + ATH9K_INT_RXHP | \ + ATH9K_INT_TX | \ + ATH9K_INT_BMISS | \ + ATH9K_INT_CST | \ + ATH9K_INT_TSFOOR | \ + ATH9K_INT_GENTIMER | \ + ATH9K_INT_MCI) + + struct ath_softc *sc = dev; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + enum ath9k_int status; + bool sched = false; + + /* + * The hardware is not ready/present, don't + * touch anything. Note this can happen early + * on if the IRQ is shared. + */ + if (test_bit(SC_OP_INVALID, &sc->sc_flags)) + return IRQ_NONE; + + /* shared irq, not for us */ + + if (!ath9k_hw_intrpend(ah)) + return IRQ_NONE; + + if (test_bit(SC_OP_HW_RESET, &sc->sc_flags)) { + ath9k_hw_kill_interrupts(ah); + return IRQ_HANDLED; + } + + /* + * Figure out the reason(s) for the interrupt. Note + * that the hal returns a pseudo-ISR that may include + * bits we haven't explicitly enabled so we mask the + * value to insure we only process bits we requested. + */ + ath9k_hw_getisr(ah, &status); /* NB: clears ISR too */ + status &= ah->imask; /* discard unasked-for bits */ + + /* + * If there are no status bits set, then this interrupt was not + * for me (should have been caught above). + */ + if (!status) + return IRQ_NONE; + + /* Cache the status */ + sc->intrstatus = status; + + if (status & SCHED_INTR) + sched = true; + + /* + * If a FATAL or RXORN interrupt is received, we have to reset the + * chip immediately. + */ + if ((status & ATH9K_INT_FATAL) || ((status & ATH9K_INT_RXORN) && + !(ah->caps.hw_caps & ATH9K_HW_CAP_EDMA))) + goto chip_reset; + + if ((ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) && + (status & ATH9K_INT_BB_WATCHDOG)) { + + spin_lock(&common->cc_lock); + ath_hw_cycle_counters_update(common); + ar9003_hw_bb_watchdog_dbg_info(ah); + spin_unlock(&common->cc_lock); + + goto chip_reset; + } +#ifdef CONFIG_PM_SLEEP + if (status & ATH9K_INT_BMISS) { + if (atomic_read(&sc->wow_sleep_proc_intr) == 0) { + ath_dbg(common, ANY, "during WoW we got a BMISS\n"); + atomic_inc(&sc->wow_got_bmiss_intr); + atomic_dec(&sc->wow_sleep_proc_intr); + } + } +#endif + if (status & ATH9K_INT_SWBA) + tasklet_schedule(&sc->bcon_tasklet); + + if (status & ATH9K_INT_TXURN) + ath9k_hw_updatetxtriglevel(ah, true); + + if (status & ATH9K_INT_RXEOL) { + ah->imask &= ~(ATH9K_INT_RXEOL | ATH9K_INT_RXORN); + ath9k_hw_set_interrupts(ah); + } + + if (!(ah->caps.hw_caps & ATH9K_HW_CAP_AUTOSLEEP)) + if (status & ATH9K_INT_TIM_TIMER) { + if (ATH_DBG_WARN_ON_ONCE(sc->ps_idle)) + goto chip_reset; + /* Clear RxAbort bit so that we can + * receive frames */ + ath9k_setpower(sc, ATH9K_PM_AWAKE); + spin_lock(&sc->sc_pm_lock); + ath9k_hw_setrxabort(sc->sc_ah, 0); + sc->ps_flags |= PS_WAIT_FOR_BEACON; + spin_unlock(&sc->sc_pm_lock); + } + +chip_reset: + + ath_debug_stat_interrupt(sc, status); + + if (sched) { + /* turn off every interrupt */ + ath9k_hw_disable_interrupts(ah); + tasklet_schedule(&sc->intr_tq); + } + + return IRQ_HANDLED; + +#undef SCHED_INTR +} + +static int ath_reset(struct ath_softc *sc) +{ + int r; + + ath9k_ps_wakeup(sc); + r = ath_reset_internal(sc, NULL); + ath9k_ps_restore(sc); + + return r; +} + +void ath9k_queue_reset(struct ath_softc *sc, enum ath_reset_type type) +{ +#ifdef CPTCFG_ATH9K_DEBUGFS + RESET_STAT_INC(sc, type); +#endif + set_bit(SC_OP_HW_RESET, &sc->sc_flags); + ieee80211_queue_work(sc->hw, &sc->hw_reset_work); +} + +void ath_reset_work(struct work_struct *work) +{ + struct ath_softc *sc = container_of(work, struct ath_softc, hw_reset_work); + + ath_reset(sc); +} + +/**********************/ +/* mac80211 callbacks */ +/**********************/ + +static int ath9k_start(struct ieee80211_hw *hw) +{ + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ieee80211_channel *curchan = hw->conf.chandef.chan; + struct ath9k_channel *init_channel; + int r; + + ath_dbg(common, CONFIG, + "Starting driver with initial channel: %d MHz\n", + curchan->center_freq); + + ath9k_ps_wakeup(sc); + mutex_lock(&sc->mutex); + + init_channel = ath9k_cmn_get_curchannel(hw, ah); + + /* Reset SERDES registers */ + ath9k_hw_configpcipowersave(ah, false); + + /* + * The basic interface to setting the hardware in a good + * state is ``reset''. On return the hardware is known to + * be powered up and with interrupts disabled. This must + * be followed by initialization of the appropriate bits + * and then setup of the interrupt mask. + */ + spin_lock_bh(&sc->sc_pcu_lock); + + atomic_set(&ah->intr_ref_cnt, -1); + + r = ath9k_hw_reset(ah, init_channel, ah->caldata, false); + if (r) { + ath_err(common, + "Unable to reset hardware; reset status %d (freq %u MHz)\n", + r, curchan->center_freq); + ah->reset_power_on = false; + } + + /* Setup our intr mask. */ + ah->imask = ATH9K_INT_TX | ATH9K_INT_RXEOL | + ATH9K_INT_RXORN | ATH9K_INT_FATAL | + ATH9K_INT_GLOBAL; + + if (ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) + ah->imask |= ATH9K_INT_RXHP | + ATH9K_INT_RXLP | + ATH9K_INT_BB_WATCHDOG; + else + ah->imask |= ATH9K_INT_RX; + + ah->imask |= ATH9K_INT_GTT; + + if (ah->caps.hw_caps & ATH9K_HW_CAP_HT) + ah->imask |= ATH9K_INT_CST; + + ath_mci_enable(sc); + + clear_bit(SC_OP_INVALID, &sc->sc_flags); + sc->sc_ah->is_monitoring = false; + + if (!ath_complete_reset(sc, false)) + ah->reset_power_on = false; + + if (ah->led_pin >= 0) { + ath9k_hw_cfg_output(ah, ah->led_pin, + AR_GPIO_OUTPUT_MUX_AS_OUTPUT); + ath9k_hw_set_gpio(ah, ah->led_pin, 0); + } + + /* + * Reset key cache to sane defaults (all entries cleared) instead of + * semi-random values after suspend/resume. + */ + ath9k_cmn_init_crypto(sc->sc_ah); + + spin_unlock_bh(&sc->sc_pcu_lock); + + mutex_unlock(&sc->mutex); + + ath9k_ps_restore(sc); + + return 0; +} + +static void ath9k_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct ath_softc *sc = hw->priv; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_tx_control txctl; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; + unsigned long flags; + + if (sc->ps_enabled) { + /* + * mac80211 does not set PM field for normal data frames, so we + * need to update that based on the current PS mode. + */ + if (ieee80211_is_data(hdr->frame_control) && + !ieee80211_is_nullfunc(hdr->frame_control) && + !ieee80211_has_pm(hdr->frame_control)) { + ath_dbg(common, PS, + "Add PM=1 for a TX frame while in PS mode\n"); + hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PM); + } + } + + if (unlikely(sc->sc_ah->power_mode == ATH9K_PM_NETWORK_SLEEP)) { + /* + * We are using PS-Poll and mac80211 can request TX while in + * power save mode. Need to wake up hardware for the TX to be + * completed and if needed, also for RX of buffered frames. + */ + ath9k_ps_wakeup(sc); + spin_lock_irqsave(&sc->sc_pm_lock, flags); + if (!(sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_AUTOSLEEP)) + ath9k_hw_setrxabort(sc->sc_ah, 0); + if (ieee80211_is_pspoll(hdr->frame_control)) { + ath_dbg(common, PS, + "Sending PS-Poll to pick a buffered frame\n"); + sc->ps_flags |= PS_WAIT_FOR_PSPOLL_DATA; + } else { + ath_dbg(common, PS, "Wake up to complete TX\n"); + sc->ps_flags |= PS_WAIT_FOR_TX_ACK; + } + /* + * The actual restore operation will happen only after + * the ps_flags bit is cleared. We are just dropping + * the ps_usecount here. + */ + spin_unlock_irqrestore(&sc->sc_pm_lock, flags); + ath9k_ps_restore(sc); + } + + /* + * Cannot tx while the hardware is in full sleep, it first needs a full + * chip reset to recover from that + */ + if (unlikely(sc->sc_ah->power_mode == ATH9K_PM_FULL_SLEEP)) { + ath_err(common, "TX while HW is in FULL_SLEEP mode\n"); + goto exit; + } + + memset(&txctl, 0, sizeof(struct ath_tx_control)); + txctl.txq = sc->tx.txq_map[skb_get_queue_mapping(skb)]; + txctl.sta = control->sta; + + ath_dbg(common, XMIT, "transmitting packet, skb: %p\n", skb); + + if (ath_tx_start(hw, skb, &txctl) != 0) { + ath_dbg(common, XMIT, "TX failed\n"); + TX_STAT_INC(txctl.txq->axq_qnum, txfailed); + goto exit; + } + + return; +exit: + ieee80211_free_txskb(hw, skb); +} + +static void ath9k_stop(struct ieee80211_hw *hw) +{ + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + bool prev_idle; + + mutex_lock(&sc->mutex); + + ath_cancel_work(sc); + del_timer_sync(&sc->rx_poll_timer); + + if (test_bit(SC_OP_INVALID, &sc->sc_flags)) { + ath_dbg(common, ANY, "Device not present\n"); + mutex_unlock(&sc->mutex); + return; + } + + /* Ensure HW is awake when we try to shut it down. */ + ath9k_ps_wakeup(sc); + + spin_lock_bh(&sc->sc_pcu_lock); + + /* prevent tasklets to enable interrupts once we disable them */ + ah->imask &= ~ATH9K_INT_GLOBAL; + + /* make sure h/w will not generate any interrupt + * before setting the invalid flag. */ + ath9k_hw_disable_interrupts(ah); + + spin_unlock_bh(&sc->sc_pcu_lock); + + /* we can now sync irq and kill any running tasklets, since we already + * disabled interrupts and not holding a spin lock */ + synchronize_irq(sc->irq); + tasklet_kill(&sc->intr_tq); + tasklet_kill(&sc->bcon_tasklet); + + prev_idle = sc->ps_idle; + sc->ps_idle = true; + + spin_lock_bh(&sc->sc_pcu_lock); + + if (ah->led_pin >= 0) { + ath9k_hw_set_gpio(ah, ah->led_pin, 1); + ath9k_hw_cfg_gpio_input(ah, ah->led_pin); + } + + ath_prepare_reset(sc); + + if (sc->rx.frag) { + dev_kfree_skb_any(sc->rx.frag); + sc->rx.frag = NULL; + } + + if (!ah->curchan) + ah->curchan = ath9k_cmn_get_curchannel(hw, ah); + + ath9k_hw_reset(ah, ah->curchan, ah->caldata, false); + ath9k_hw_phy_disable(ah); + + ath9k_hw_configpcipowersave(ah, true); + + spin_unlock_bh(&sc->sc_pcu_lock); + + ath9k_ps_restore(sc); + + set_bit(SC_OP_INVALID, &sc->sc_flags); + sc->ps_idle = prev_idle; + + mutex_unlock(&sc->mutex); + + ath_dbg(common, CONFIG, "Driver halt\n"); +} + +bool ath9k_uses_beacons(int type) +{ + switch (type) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_MESH_POINT: + return true; + default: + return false; + } +} + +static void ath9k_vif_iter(void *data, u8 *mac, struct ieee80211_vif *vif) +{ + struct ath9k_vif_iter_data *iter_data = data; + int i; + + if (iter_data->has_hw_macaddr) { + for (i = 0; i < ETH_ALEN; i++) + iter_data->mask[i] &= + ~(iter_data->hw_macaddr[i] ^ mac[i]); + } else { + memcpy(iter_data->hw_macaddr, mac, ETH_ALEN); + iter_data->has_hw_macaddr = true; + } + + switch (vif->type) { + case NL80211_IFTYPE_AP: + iter_data->naps++; + break; + case NL80211_IFTYPE_STATION: + iter_data->nstations++; + break; + case NL80211_IFTYPE_ADHOC: + iter_data->nadhocs++; + break; + case NL80211_IFTYPE_MESH_POINT: + iter_data->nmeshes++; + break; + case NL80211_IFTYPE_WDS: + iter_data->nwds++; + break; + default: + break; + } +} + +static void ath9k_sta_vif_iter(void *data, u8 *mac, struct ieee80211_vif *vif) +{ + struct ath_softc *sc = data; + struct ath_vif *avp = (void *)vif->drv_priv; + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + if (avp->primary_sta_vif) + ath9k_set_assoc_state(sc, vif); +} + +/* Called with sc->mutex held. */ +void ath9k_calculate_iter_data(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ath9k_vif_iter_data *iter_data) +{ + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + + /* + * Use the hardware MAC address as reference, the hardware uses it + * together with the BSSID mask when matching addresses. + */ + memset(iter_data, 0, sizeof(*iter_data)); + memset(&iter_data->mask, 0xff, ETH_ALEN); + + if (vif) + ath9k_vif_iter(iter_data, vif->addr, vif); + + /* Get list of all active MAC addresses */ + ieee80211_iterate_active_interfaces_atomic( + sc->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + ath9k_vif_iter, iter_data); + + memcpy(common->macaddr, iter_data->hw_macaddr, ETH_ALEN); +} + +/* Called with sc->mutex held. */ +static void ath9k_calculate_summary_state(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_vif_iter_data iter_data; + enum nl80211_iftype old_opmode = ah->opmode; + + ath9k_calculate_iter_data(hw, vif, &iter_data); + + memcpy(common->bssidmask, iter_data.mask, ETH_ALEN); + ath_hw_setbssidmask(common); + + if (iter_data.naps > 0) { + ath9k_hw_set_tsfadjust(ah, true); + ah->opmode = NL80211_IFTYPE_AP; + } else { + ath9k_hw_set_tsfadjust(ah, false); + + if (iter_data.nmeshes) + ah->opmode = NL80211_IFTYPE_MESH_POINT; + else if (iter_data.nwds) + ah->opmode = NL80211_IFTYPE_AP; + else if (iter_data.nadhocs) + ah->opmode = NL80211_IFTYPE_ADHOC; + else + ah->opmode = NL80211_IFTYPE_STATION; + } + + ath9k_hw_setopmode(ah); + + if ((iter_data.nstations + iter_data.nadhocs + iter_data.nmeshes) > 0) + ah->imask |= ATH9K_INT_TSFOOR; + else + ah->imask &= ~ATH9K_INT_TSFOOR; + + ath9k_hw_set_interrupts(ah); + + /* + * If we are changing the opmode to STATION, + * a beacon sync needs to be done. + */ + if (ah->opmode == NL80211_IFTYPE_STATION && + old_opmode == NL80211_IFTYPE_AP && + test_bit(SC_OP_PRIM_STA_VIF, &sc->sc_flags)) { + ieee80211_iterate_active_interfaces_atomic( + sc->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + ath9k_sta_vif_iter, sc); + } +} + +static int ath9k_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + + mutex_lock(&sc->mutex); + + ath_dbg(common, CONFIG, "Attach a VIF of type: %d\n", vif->type); + sc->nvifs++; + + ath9k_ps_wakeup(sc); + ath9k_calculate_summary_state(hw, vif); + ath9k_ps_restore(sc); + + if (ath9k_uses_beacons(vif->type)) + ath9k_beacon_assign_slot(sc, vif); + + mutex_unlock(&sc->mutex); + return 0; +} + +static int ath9k_change_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum nl80211_iftype new_type, + bool p2p) +{ + struct ath_softc *sc = hw->priv; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + + ath_dbg(common, CONFIG, "Change Interface\n"); + mutex_lock(&sc->mutex); + + if (ath9k_uses_beacons(vif->type)) + ath9k_beacon_remove_slot(sc, vif); + + vif->type = new_type; + vif->p2p = p2p; + + ath9k_ps_wakeup(sc); + ath9k_calculate_summary_state(hw, vif); + ath9k_ps_restore(sc); + + if (ath9k_uses_beacons(vif->type)) + ath9k_beacon_assign_slot(sc, vif); + + mutex_unlock(&sc->mutex); + return 0; +} + +static void ath9k_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct ath_softc *sc = hw->priv; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + + ath_dbg(common, CONFIG, "Detach Interface\n"); + + mutex_lock(&sc->mutex); + + sc->nvifs--; + + if (ath9k_uses_beacons(vif->type)) + ath9k_beacon_remove_slot(sc, vif); + + ath9k_ps_wakeup(sc); + ath9k_calculate_summary_state(hw, NULL); + ath9k_ps_restore(sc); + + mutex_unlock(&sc->mutex); +} + +static void ath9k_enable_ps(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + + sc->ps_enabled = true; + if (!(ah->caps.hw_caps & ATH9K_HW_CAP_AUTOSLEEP)) { + if ((ah->imask & ATH9K_INT_TIM_TIMER) == 0) { + ah->imask |= ATH9K_INT_TIM_TIMER; + ath9k_hw_set_interrupts(ah); + } + ath9k_hw_setrxabort(ah, 1); + } + ath_dbg(common, PS, "PowerSave enabled\n"); +} + +static void ath9k_disable_ps(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + + sc->ps_enabled = false; + ath9k_hw_setpower(ah, ATH9K_PM_AWAKE); + if (!(ah->caps.hw_caps & ATH9K_HW_CAP_AUTOSLEEP)) { + ath9k_hw_setrxabort(ah, 0); + sc->ps_flags &= ~(PS_WAIT_FOR_BEACON | + PS_WAIT_FOR_CAB | + PS_WAIT_FOR_PSPOLL_DATA | + PS_WAIT_FOR_TX_ACK); + if (ah->imask & ATH9K_INT_TIM_TIMER) { + ah->imask &= ~ATH9K_INT_TIM_TIMER; + ath9k_hw_set_interrupts(ah); + } + } + ath_dbg(common, PS, "PowerSave disabled\n"); +} + +void ath9k_spectral_scan_trigger(struct ieee80211_hw *hw) +{ + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + u32 rxfilter; + + if (!ath9k_hw_ops(ah)->spectral_scan_trigger) { + ath_err(common, "spectrum analyzer not implemented on this hardware\n"); + return; + } + + ath9k_ps_wakeup(sc); + rxfilter = ath9k_hw_getrxfilter(ah); + ath9k_hw_setrxfilter(ah, rxfilter | + ATH9K_RX_FILTER_PHYRADAR | + ATH9K_RX_FILTER_PHYERR); + + /* TODO: usually this should not be neccesary, but for some reason + * (or in some mode?) the trigger must be called after the + * configuration, otherwise the register will have its values reset + * (on my ar9220 to value 0x01002310) + */ + ath9k_spectral_scan_config(hw, sc->spectral_mode); + ath9k_hw_ops(ah)->spectral_scan_trigger(ah); + ath9k_ps_restore(sc); +} + +int ath9k_spectral_scan_config(struct ieee80211_hw *hw, + enum spectral_mode spectral_mode) +{ + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + + if (!ath9k_hw_ops(ah)->spectral_scan_trigger) { + ath_err(common, "spectrum analyzer not implemented on this hardware\n"); + return -1; + } + + switch (spectral_mode) { + case SPECTRAL_DISABLED: + sc->spec_config.enabled = 0; + break; + case SPECTRAL_BACKGROUND: + /* send endless samples. + * TODO: is this really useful for "background"? + */ + sc->spec_config.endless = 1; + sc->spec_config.enabled = 1; + break; + case SPECTRAL_CHANSCAN: + case SPECTRAL_MANUAL: + sc->spec_config.endless = 0; + sc->spec_config.enabled = 1; + break; + default: + return -1; + } + + ath9k_ps_wakeup(sc); + ath9k_hw_ops(ah)->spectral_scan_config(ah, &sc->spec_config); + ath9k_ps_restore(sc); + + sc->spectral_mode = spectral_mode; + + return 0; +} + +static int ath9k_config(struct ieee80211_hw *hw, u32 changed) +{ + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ieee80211_conf *conf = &hw->conf; + bool reset_channel = false; + + ath9k_ps_wakeup(sc); + mutex_lock(&sc->mutex); + + if (changed & IEEE80211_CONF_CHANGE_IDLE) { + sc->ps_idle = !!(conf->flags & IEEE80211_CONF_IDLE); + if (sc->ps_idle) { + ath_cancel_work(sc); + ath9k_stop_btcoex(sc); + } else { + ath9k_start_btcoex(sc); + /* + * The chip needs a reset to properly wake up from + * full sleep + */ + reset_channel = ah->chip_fullsleep; + } + } + + /* + * We just prepare to enable PS. We have to wait until our AP has + * ACK'd our null data frame to disable RX otherwise we'll ignore + * those ACKs and end up retransmitting the same null data frames. + * IEEE80211_CONF_CHANGE_PS is only passed by mac80211 for STA mode. + */ + if (changed & IEEE80211_CONF_CHANGE_PS) { + unsigned long flags; + spin_lock_irqsave(&sc->sc_pm_lock, flags); + if (conf->flags & IEEE80211_CONF_PS) + ath9k_enable_ps(sc); + else + ath9k_disable_ps(sc); + spin_unlock_irqrestore(&sc->sc_pm_lock, flags); + } + + if (changed & IEEE80211_CONF_CHANGE_MONITOR) { + if (conf->flags & IEEE80211_CONF_MONITOR) { + ath_dbg(common, CONFIG, "Monitor mode is enabled\n"); + sc->sc_ah->is_monitoring = true; + } else { + ath_dbg(common, CONFIG, "Monitor mode is disabled\n"); + sc->sc_ah->is_monitoring = false; + } + } + + if ((changed & IEEE80211_CONF_CHANGE_CHANNEL) || reset_channel) { + struct ieee80211_channel *curchan = hw->conf.chandef.chan; + enum nl80211_channel_type channel_type = + cfg80211_get_chandef_type(&conf->chandef); + int pos = curchan->hw_value; + int old_pos = -1; + unsigned long flags; + + if (ah->curchan) + old_pos = ah->curchan - &ah->channels[0]; + + ath_dbg(common, CONFIG, "Set channel: %d MHz type: %d\n", + curchan->center_freq, channel_type); + + /* update survey stats for the old channel before switching */ + spin_lock_irqsave(&common->cc_lock, flags); + ath_update_survey_stats(sc); + spin_unlock_irqrestore(&common->cc_lock, flags); + + ath9k_cmn_update_ichannel(&sc->sc_ah->channels[pos], + curchan, channel_type); + + /* + * If the operating channel changes, change the survey in-use flags + * along with it. + * Reset the survey data for the new channel, unless we're switching + * back to the operating channel from an off-channel operation. + */ + if (!(hw->conf.flags & IEEE80211_CONF_OFFCHANNEL) && + sc->cur_survey != &sc->survey[pos]) { + + if (sc->cur_survey) + sc->cur_survey->filled &= ~SURVEY_INFO_IN_USE; + + sc->cur_survey = &sc->survey[pos]; + + memset(sc->cur_survey, 0, sizeof(struct survey_info)); + sc->cur_survey->filled |= SURVEY_INFO_IN_USE; + } else if (!(sc->survey[pos].filled & SURVEY_INFO_IN_USE)) { + memset(&sc->survey[pos], 0, sizeof(struct survey_info)); + } + + if (ath_set_channel(sc, hw, &sc->sc_ah->channels[pos]) < 0) { + ath_err(common, "Unable to set channel\n"); + mutex_unlock(&sc->mutex); + ath9k_ps_restore(sc); + return -EINVAL; + } + + /* + * The most recent snapshot of channel->noisefloor for the old + * channel is only available after the hardware reset. Copy it to + * the survey stats now. + */ + if (old_pos >= 0) + ath_update_survey_nf(sc, old_pos); + + /* + * Enable radar pulse detection if on a DFS channel. Spectral + * scanning and radar detection can not be used concurrently. + */ + if (hw->conf.radar_enabled) { + u32 rxfilter; + + /* set HW specific DFS configuration */ + ath9k_hw_set_radar_params(ah); + rxfilter = ath9k_hw_getrxfilter(ah); + rxfilter |= ATH9K_RX_FILTER_PHYRADAR | + ATH9K_RX_FILTER_PHYERR; + ath9k_hw_setrxfilter(ah, rxfilter); + ath_dbg(common, DFS, "DFS enabled at freq %d\n", + curchan->center_freq); + } else { + /* perform spectral scan if requested. */ + if (sc->scanning && + sc->spectral_mode == SPECTRAL_CHANSCAN) + ath9k_spectral_scan_trigger(hw); + } + } + + if (changed & IEEE80211_CONF_CHANGE_POWER) { + ath_dbg(common, CONFIG, "Set power: %d\n", conf->power_level); + sc->config.txpowlimit = 2 * conf->power_level; + ath9k_cmn_update_txpow(ah, sc->curtxpow, + sc->config.txpowlimit, &sc->curtxpow); + } + + mutex_unlock(&sc->mutex); + ath9k_ps_restore(sc); + + return 0; +} + +#define SUPPORTED_FILTERS \ + (FIF_PROMISC_IN_BSS | \ + FIF_ALLMULTI | \ + FIF_CONTROL | \ + FIF_PSPOLL | \ + FIF_OTHER_BSS | \ + FIF_BCN_PRBRESP_PROMISC | \ + FIF_PROBE_REQ | \ + FIF_FCSFAIL) + +/* FIXME: sc->sc_full_reset ? */ +static void ath9k_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + struct ath_softc *sc = hw->priv; + u32 rfilt; + + changed_flags &= SUPPORTED_FILTERS; + *total_flags &= SUPPORTED_FILTERS; + + sc->rx.rxfilter = *total_flags; + ath9k_ps_wakeup(sc); + rfilt = ath_calcrxfilter(sc); + ath9k_hw_setrxfilter(sc->sc_ah, rfilt); + ath9k_ps_restore(sc); + + ath_dbg(ath9k_hw_common(sc->sc_ah), CONFIG, "Set HW RX filter: 0x%x\n", + rfilt); +} + +static int ath9k_sta_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct ath_softc *sc = hw->priv; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_node *an = (struct ath_node *) sta->drv_priv; + struct ieee80211_key_conf ps_key = { }; + int key; + + ath_node_attach(sc, sta, vif); + + if (vif->type != NL80211_IFTYPE_AP && + vif->type != NL80211_IFTYPE_AP_VLAN) + return 0; + + key = ath_key_config(common, vif, sta, &ps_key); + if (key > 0) + an->ps_key = key; + + return 0; +} + +static void ath9k_del_ps_key(struct ath_softc *sc, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_node *an = (struct ath_node *) sta->drv_priv; + struct ieee80211_key_conf ps_key = { .hw_key_idx = an->ps_key }; + + if (!an->ps_key) + return; + + ath_key_delete(common, &ps_key); + an->ps_key = 0; +} + +static int ath9k_sta_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct ath_softc *sc = hw->priv; + + ath9k_del_ps_key(sc, vif, sta); + ath_node_detach(sc, sta); + + return 0; +} + +static void ath9k_sta_notify(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum sta_notify_cmd cmd, + struct ieee80211_sta *sta) +{ + struct ath_softc *sc = hw->priv; + struct ath_node *an = (struct ath_node *) sta->drv_priv; + + if (!sta->ht_cap.ht_supported) + return; + + switch (cmd) { + case STA_NOTIFY_SLEEP: + an->sleeping = true; + ath_tx_aggr_sleep(sta, sc, an); + break; + case STA_NOTIFY_AWAKE: + an->sleeping = false; + ath_tx_aggr_wakeup(sc, an); + break; + } +} + +static int ath9k_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct ath_softc *sc = hw->priv; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_txq *txq; + struct ath9k_tx_queue_info qi; + int ret = 0; + + if (queue >= IEEE80211_NUM_ACS) + return 0; + + txq = sc->tx.txq_map[queue]; + + ath9k_ps_wakeup(sc); + mutex_lock(&sc->mutex); + + memset(&qi, 0, sizeof(struct ath9k_tx_queue_info)); + + qi.tqi_aifs = params->aifs; + qi.tqi_cwmin = params->cw_min; + qi.tqi_cwmax = params->cw_max; + qi.tqi_burstTime = params->txop * 32; + + ath_dbg(common, CONFIG, + "Configure tx [queue/halq] [%d/%d], aifs: %d, cw_min: %d, cw_max: %d, txop: %d\n", + queue, txq->axq_qnum, params->aifs, params->cw_min, + params->cw_max, params->txop); + + ath_update_max_aggr_framelen(sc, queue, qi.tqi_burstTime); + ret = ath_txq_update(sc, txq->axq_qnum, &qi); + if (ret) + ath_err(common, "TXQ Update failed\n"); + + mutex_unlock(&sc->mutex); + ath9k_ps_restore(sc); + + return ret; +} + +static int ath9k_set_key(struct ieee80211_hw *hw, + enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct ath_softc *sc = hw->priv; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + int ret = 0; + + if (ath9k_modparam_nohwcrypt) + return -ENOSPC; + + if ((vif->type == NL80211_IFTYPE_ADHOC || + vif->type == NL80211_IFTYPE_MESH_POINT) && + (key->cipher == WLAN_CIPHER_SUITE_TKIP || + key->cipher == WLAN_CIPHER_SUITE_CCMP) && + !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) { + /* + * For now, disable hw crypto for the RSN IBSS group keys. This + * could be optimized in the future to use a modified key cache + * design to support per-STA RX GTK, but until that gets + * implemented, use of software crypto for group addressed + * frames is a acceptable to allow RSN IBSS to be used. + */ + return -EOPNOTSUPP; + } + + mutex_lock(&sc->mutex); + ath9k_ps_wakeup(sc); + ath_dbg(common, CONFIG, "Set HW Key\n"); + + switch (cmd) { + case SET_KEY: + if (sta) + ath9k_del_ps_key(sc, vif, sta); + + ret = ath_key_config(common, vif, sta, key); + if (ret >= 0) { + key->hw_key_idx = ret; + /* push IV and Michael MIC generation to stack */ + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + if (key->cipher == WLAN_CIPHER_SUITE_TKIP) + key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC; + if (sc->sc_ah->sw_mgmt_crypto && + key->cipher == WLAN_CIPHER_SUITE_CCMP) + key->flags |= IEEE80211_KEY_FLAG_SW_MGMT_TX; + ret = 0; + } + break; + case DISABLE_KEY: + ath_key_delete(common, key); + break; + default: + ret = -EINVAL; + } + + ath9k_ps_restore(sc); + mutex_unlock(&sc->mutex); + + return ret; +} + +static void ath9k_set_assoc_state(struct ath_softc *sc, + struct ieee80211_vif *vif) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_vif *avp = (void *)vif->drv_priv; + struct ieee80211_bss_conf *bss_conf = &vif->bss_conf; + unsigned long flags; + + set_bit(SC_OP_PRIM_STA_VIF, &sc->sc_flags); + avp->primary_sta_vif = true; + + /* + * Set the AID, BSSID and do beacon-sync only when + * the HW opmode is STATION. + * + * But the primary bit is set above in any case. + */ + if (sc->sc_ah->opmode != NL80211_IFTYPE_STATION) + return; + + memcpy(common->curbssid, bss_conf->bssid, ETH_ALEN); + common->curaid = bss_conf->aid; + ath9k_hw_write_associd(sc->sc_ah); + + sc->last_rssi = ATH_RSSI_DUMMY_MARKER; + sc->sc_ah->stats.avgbrssi = ATH_RSSI_DUMMY_MARKER; + + spin_lock_irqsave(&sc->sc_pm_lock, flags); + sc->ps_flags |= PS_BEACON_SYNC | PS_WAIT_FOR_BEACON; + spin_unlock_irqrestore(&sc->sc_pm_lock, flags); + + if (ath9k_hw_mci_is_enabled(sc->sc_ah)) + ath9k_mci_update_wlan_channels(sc, false); + + ath_dbg(common, CONFIG, + "Primary Station interface: %pM, BSSID: %pM\n", + vif->addr, common->curbssid); +} + +static void ath9k_bss_assoc_iter(void *data, u8 *mac, struct ieee80211_vif *vif) +{ + struct ath_softc *sc = data; + struct ieee80211_bss_conf *bss_conf = &vif->bss_conf; + + if (test_bit(SC_OP_PRIM_STA_VIF, &sc->sc_flags)) + return; + + if (bss_conf->assoc) + ath9k_set_assoc_state(sc, vif); +} + +static void ath9k_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changed) +{ +#define CHECK_ANI \ + (BSS_CHANGED_ASSOC | \ + BSS_CHANGED_IBSS | \ + BSS_CHANGED_BEACON_ENABLED) + + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath_vif *avp = (void *)vif->drv_priv; + int slottime; + + ath9k_ps_wakeup(sc); + mutex_lock(&sc->mutex); + + if (changed & BSS_CHANGED_ASSOC) { + ath_dbg(common, CONFIG, "BSSID %pM Changed ASSOC %d\n", + bss_conf->bssid, bss_conf->assoc); + + if (avp->primary_sta_vif && !bss_conf->assoc) { + clear_bit(SC_OP_PRIM_STA_VIF, &sc->sc_flags); + avp->primary_sta_vif = false; + + if (ah->opmode == NL80211_IFTYPE_STATION) + clear_bit(SC_OP_BEACONS, &sc->sc_flags); + } + + ieee80211_iterate_active_interfaces_atomic( + sc->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + ath9k_bss_assoc_iter, sc); + + if (!test_bit(SC_OP_PRIM_STA_VIF, &sc->sc_flags) && + ah->opmode == NL80211_IFTYPE_STATION) { + memset(common->curbssid, 0, ETH_ALEN); + common->curaid = 0; + ath9k_hw_write_associd(sc->sc_ah); + if (ath9k_hw_mci_is_enabled(sc->sc_ah)) + ath9k_mci_update_wlan_channels(sc, true); + } + } + + if (changed & BSS_CHANGED_IBSS) { + memcpy(common->curbssid, bss_conf->bssid, ETH_ALEN); + common->curaid = bss_conf->aid; + ath9k_hw_write_associd(sc->sc_ah); + } + + if ((changed & BSS_CHANGED_BEACON_ENABLED) || + (changed & BSS_CHANGED_BEACON_INT)) { + if (ah->opmode == NL80211_IFTYPE_AP && + bss_conf->enable_beacon) + ath9k_set_tsfadjust(sc, vif); + if (ath9k_allow_beacon_config(sc, vif)) + ath9k_beacon_config(sc, vif, changed); + } + + if (changed & BSS_CHANGED_ERP_SLOT) { + if (bss_conf->use_short_slot) + slottime = 9; + else + slottime = 20; + if (vif->type == NL80211_IFTYPE_AP) { + /* + * Defer update, so that connected stations can adjust + * their settings at the same time. + * See beacon.c for more details + */ + sc->beacon.slottime = slottime; + sc->beacon.updateslot = UPDATE; + } else { + ah->slottime = slottime; + ath9k_hw_init_global_settings(ah); + } + } + + if (changed & CHECK_ANI) + ath_check_ani(sc); + + mutex_unlock(&sc->mutex); + ath9k_ps_restore(sc); + +#undef CHECK_ANI +} + +static u64 ath9k_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct ath_softc *sc = hw->priv; + u64 tsf; + + mutex_lock(&sc->mutex); + ath9k_ps_wakeup(sc); + tsf = ath9k_hw_gettsf64(sc->sc_ah); + ath9k_ps_restore(sc); + mutex_unlock(&sc->mutex); + + return tsf; +} + +static void ath9k_set_tsf(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u64 tsf) +{ + struct ath_softc *sc = hw->priv; + + mutex_lock(&sc->mutex); + ath9k_ps_wakeup(sc); + ath9k_hw_settsf64(sc->sc_ah, tsf); + ath9k_ps_restore(sc); + mutex_unlock(&sc->mutex); +} + +static void ath9k_reset_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct ath_softc *sc = hw->priv; + + mutex_lock(&sc->mutex); + + ath9k_ps_wakeup(sc); + ath9k_hw_reset_tsf(sc->sc_ah); + ath9k_ps_restore(sc); + + mutex_unlock(&sc->mutex); +} + +static int ath9k_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, + u16 tid, u16 *ssn, u8 buf_size) +{ + struct ath_softc *sc = hw->priv; + bool flush = false; + int ret = 0; + + local_bh_disable(); + + switch (action) { + case IEEE80211_AMPDU_RX_START: + break; + case IEEE80211_AMPDU_RX_STOP: + break; + case IEEE80211_AMPDU_TX_START: + ath9k_ps_wakeup(sc); + ret = ath_tx_aggr_start(sc, sta, tid, ssn); + if (!ret) + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + ath9k_ps_restore(sc); + break; + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + flush = true; + case IEEE80211_AMPDU_TX_STOP_CONT: + ath9k_ps_wakeup(sc); + ath_tx_aggr_stop(sc, sta, tid); + if (!flush) + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + ath9k_ps_restore(sc); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + ath9k_ps_wakeup(sc); + ath_tx_aggr_resume(sc, sta, tid); + ath9k_ps_restore(sc); + break; + default: + ath_err(ath9k_hw_common(sc->sc_ah), "Unknown AMPDU action\n"); + } + + local_bh_enable(); + + return ret; +} + +static int ath9k_get_survey(struct ieee80211_hw *hw, int idx, + struct survey_info *survey) +{ + struct ath_softc *sc = hw->priv; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ieee80211_supported_band *sband; + struct ieee80211_channel *chan; + unsigned long flags; + int pos; + + spin_lock_irqsave(&common->cc_lock, flags); + if (idx == 0) + ath_update_survey_stats(sc); + + sband = hw->wiphy->bands[IEEE80211_BAND_2GHZ]; + if (sband && idx >= sband->n_channels) { + idx -= sband->n_channels; + sband = NULL; + } + + if (!sband) + sband = hw->wiphy->bands[IEEE80211_BAND_5GHZ]; + + if (!sband || idx >= sband->n_channels) { + spin_unlock_irqrestore(&common->cc_lock, flags); + return -ENOENT; + } + + chan = &sband->channels[idx]; + pos = chan->hw_value; + memcpy(survey, &sc->survey[pos], sizeof(*survey)); + survey->channel = chan; + spin_unlock_irqrestore(&common->cc_lock, flags); + + return 0; +} + +static void ath9k_set_coverage_class(struct ieee80211_hw *hw, u8 coverage_class) +{ + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + + mutex_lock(&sc->mutex); + ah->coverage_class = coverage_class; + + ath9k_ps_wakeup(sc); + ath9k_hw_init_global_settings(ah); + ath9k_ps_restore(sc); + + mutex_unlock(&sc->mutex); +} + +static void ath9k_flush(struct ieee80211_hw *hw, u32 queues, bool drop) +{ + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + int timeout = 200; /* ms */ + int i, j; + bool drain_txq; + + mutex_lock(&sc->mutex); + cancel_delayed_work_sync(&sc->tx_complete_work); + + if (ah->ah_flags & AH_UNPLUGGED) { + ath_dbg(common, ANY, "Device has been unplugged!\n"); + mutex_unlock(&sc->mutex); + return; + } + + if (test_bit(SC_OP_INVALID, &sc->sc_flags)) { + ath_dbg(common, ANY, "Device not present\n"); + mutex_unlock(&sc->mutex); + return; + } + + for (j = 0; j < timeout; j++) { + bool npend = false; + + if (j) + usleep_range(1000, 2000); + + for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) { + if (!ATH_TXQ_SETUP(sc, i)) + continue; + + npend = ath9k_has_pending_frames(sc, &sc->tx.txq[i]); + + if (npend) + break; + } + + if (!npend) + break; + } + + if (drop) { + ath9k_ps_wakeup(sc); + spin_lock_bh(&sc->sc_pcu_lock); + drain_txq = ath_drain_all_txq(sc); + spin_unlock_bh(&sc->sc_pcu_lock); + + if (!drain_txq) + ath_reset(sc); + + ath9k_ps_restore(sc); + ieee80211_wake_queues(hw); + } + + ieee80211_queue_delayed_work(hw, &sc->tx_complete_work, 0); + mutex_unlock(&sc->mutex); +} + +static bool ath9k_tx_frames_pending(struct ieee80211_hw *hw) +{ + struct ath_softc *sc = hw->priv; + int i; + + for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) { + if (!ATH_TXQ_SETUP(sc, i)) + continue; + + if (ath9k_has_pending_frames(sc, &sc->tx.txq[i])) + return true; + } + return false; +} + +static int ath9k_tx_last_beacon(struct ieee80211_hw *hw) +{ + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + struct ieee80211_vif *vif; + struct ath_vif *avp; + struct ath_buf *bf; + struct ath_tx_status ts; + bool edma = !!(ah->caps.hw_caps & ATH9K_HW_CAP_EDMA); + int status; + + vif = sc->beacon.bslot[0]; + if (!vif) + return 0; + + if (!vif->bss_conf.enable_beacon) + return 0; + + avp = (void *)vif->drv_priv; + + if (!sc->beacon.tx_processed && !edma) { + tasklet_disable(&sc->bcon_tasklet); + + bf = avp->av_bcbuf; + if (!bf || !bf->bf_mpdu) + goto skip; + + status = ath9k_hw_txprocdesc(ah, bf->bf_desc, &ts); + if (status == -EINPROGRESS) + goto skip; + + sc->beacon.tx_processed = true; + sc->beacon.tx_last = !(ts.ts_status & ATH9K_TXERR_MASK); + +skip: + tasklet_enable(&sc->bcon_tasklet); + } + + return sc->beacon.tx_last; +} + +static int ath9k_get_stats(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + struct ath9k_mib_stats *mib_stats = &ah->ah_mibStats; + + stats->dot11ACKFailureCount = mib_stats->ackrcv_bad; + stats->dot11RTSFailureCount = mib_stats->rts_bad; + stats->dot11FCSErrorCount = mib_stats->fcs_bad; + stats->dot11RTSSuccessCount = mib_stats->rts_good; + return 0; +} + +static u32 fill_chainmask(u32 cap, u32 new) +{ + u32 filled = 0; + int i; + + for (i = 0; cap && new; i++, cap >>= 1) { + if (!(cap & BIT(0))) + continue; + + if (new & BIT(0)) + filled |= BIT(i); + + new >>= 1; + } + + return filled; +} + +static bool validate_antenna_mask(struct ath_hw *ah, u32 val) +{ + if (AR_SREV_9300_20_OR_LATER(ah)) + return true; + + switch (val & 0x7) { + case 0x1: + case 0x3: + case 0x7: + return true; + case 0x2: + return (ah->caps.rx_chainmask == 1); + default: + return false; + } +} + +static int ath9k_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +{ + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + + if (ah->caps.rx_chainmask != 1) + rx_ant |= tx_ant; + + if (!validate_antenna_mask(ah, rx_ant) || !tx_ant) + return -EINVAL; + + sc->ant_rx = rx_ant; + sc->ant_tx = tx_ant; + + if (ah->caps.rx_chainmask == 1) + return 0; + + /* AR9100 runs into calibration issues if not all rx chains are enabled */ + if (AR_SREV_9100(ah)) + ah->rxchainmask = 0x7; + else + ah->rxchainmask = fill_chainmask(ah->caps.rx_chainmask, rx_ant); + + ah->txchainmask = fill_chainmask(ah->caps.tx_chainmask, tx_ant); + ath9k_reload_chainmask_settings(sc); + + return 0; +} + +static int ath9k_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) +{ + struct ath_softc *sc = hw->priv; + + *tx_ant = sc->ant_tx; + *rx_ant = sc->ant_rx; + return 0; +} + +#ifdef CONFIG_PM_SLEEP + +static void ath9k_wow_map_triggers(struct ath_softc *sc, + struct cfg80211_wowlan *wowlan, + u32 *wow_triggers) +{ + if (wowlan->disconnect) + *wow_triggers |= AH_WOW_LINK_CHANGE | + AH_WOW_BEACON_MISS; + if (wowlan->magic_pkt) + *wow_triggers |= AH_WOW_MAGIC_PATTERN_EN; + + if (wowlan->n_patterns) + *wow_triggers |= AH_WOW_USER_PATTERN_EN; + + sc->wow_enabled = *wow_triggers; + +} + +static void ath9k_wow_add_disassoc_deauth_pattern(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_hw_capabilities *pcaps = &ah->caps; + int pattern_count = 0; + int i, byte_cnt; + u8 dis_deauth_pattern[MAX_PATTERN_SIZE]; + u8 dis_deauth_mask[MAX_PATTERN_SIZE]; + + memset(dis_deauth_pattern, 0, MAX_PATTERN_SIZE); + memset(dis_deauth_mask, 0, MAX_PATTERN_SIZE); + + /* + * Create Dissassociate / Deauthenticate packet filter + * + * 2 bytes 2 byte 6 bytes 6 bytes 6 bytes + * +--------------+----------+---------+--------+--------+---- + * + Frame Control+ Duration + DA + SA + BSSID + + * +--------------+----------+---------+--------+--------+---- + * + * The above is the management frame format for disassociate/ + * deauthenticate pattern, from this we need to match the first byte + * of 'Frame Control' and DA, SA, and BSSID fields + * (skipping 2nd byte of FC and Duration feild. + * + * Disassociate pattern + * -------------------- + * Frame control = 00 00 1010 + * DA, SA, BSSID = x:x:x:x:x:x + * Pattern will be A0000000 | x:x:x:x:x:x | x:x:x:x:x:x + * | x:x:x:x:x:x -- 22 bytes + * + * Deauthenticate pattern + * ---------------------- + * Frame control = 00 00 1100 + * DA, SA, BSSID = x:x:x:x:x:x + * Pattern will be C0000000 | x:x:x:x:x:x | x:x:x:x:x:x + * | x:x:x:x:x:x -- 22 bytes + */ + + /* Create Disassociate Pattern first */ + + byte_cnt = 0; + + /* Fill out the mask with all FF's */ + + for (i = 0; i < MAX_PATTERN_MASK_SIZE; i++) + dis_deauth_mask[i] = 0xff; + + /* copy the first byte of frame control field */ + dis_deauth_pattern[byte_cnt] = 0xa0; + byte_cnt++; + + /* skip 2nd byte of frame control and Duration field */ + byte_cnt += 3; + + /* + * need not match the destination mac address, it can be a broadcast + * mac address or an unicast to this station + */ + byte_cnt += 6; + + /* copy the source mac address */ + memcpy((dis_deauth_pattern + byte_cnt), common->curbssid, ETH_ALEN); + + byte_cnt += 6; + + /* copy the bssid, its same as the source mac address */ + + memcpy((dis_deauth_pattern + byte_cnt), common->curbssid, ETH_ALEN); + + /* Create Disassociate pattern mask */ + + if (pcaps->hw_caps & ATH9K_HW_WOW_PATTERN_MATCH_EXACT) { + + if (pcaps->hw_caps & ATH9K_HW_WOW_PATTERN_MATCH_DWORD) { + /* + * for AR9280, because of hardware limitation, the + * first 4 bytes have to be matched for all patterns. + * the mask for disassociation and de-auth pattern + * matching need to enable the first 4 bytes. + * also the duration field needs to be filled. + */ + dis_deauth_mask[0] = 0xf0; + + /* + * fill in duration field + FIXME: what is the exact value ? + */ + dis_deauth_pattern[2] = 0xff; + dis_deauth_pattern[3] = 0xff; + } else { + dis_deauth_mask[0] = 0xfe; + } + + dis_deauth_mask[1] = 0x03; + dis_deauth_mask[2] = 0xc0; + } else { + dis_deauth_mask[0] = 0xef; + dis_deauth_mask[1] = 0x3f; + dis_deauth_mask[2] = 0x00; + dis_deauth_mask[3] = 0xfc; + } + + ath_dbg(common, WOW, "Adding disassoc/deauth patterns for WoW\n"); + + ath9k_hw_wow_apply_pattern(ah, dis_deauth_pattern, dis_deauth_mask, + pattern_count, byte_cnt); + + pattern_count++; + /* + * for de-authenticate pattern, only the first byte of the frame + * control field gets changed from 0xA0 to 0xC0 + */ + dis_deauth_pattern[0] = 0xC0; + + ath9k_hw_wow_apply_pattern(ah, dis_deauth_pattern, dis_deauth_mask, + pattern_count, byte_cnt); + +} + +static void ath9k_wow_add_pattern(struct ath_softc *sc, + struct cfg80211_wowlan *wowlan) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath9k_wow_pattern *wow_pattern = NULL; + struct cfg80211_wowlan_trig_pkt_pattern *patterns = wowlan->patterns; + int mask_len; + s8 i = 0; + + if (!wowlan->n_patterns) + return; + + /* + * Add the new user configured patterns + */ + for (i = 0; i < wowlan->n_patterns; i++) { + + wow_pattern = kzalloc(sizeof(*wow_pattern), GFP_KERNEL); + + if (!wow_pattern) + return; + + /* + * TODO: convert the generic user space pattern to + * appropriate chip specific/802.11 pattern. + */ + + mask_len = DIV_ROUND_UP(wowlan->patterns[i].pattern_len, 8); + memset(wow_pattern->pattern_bytes, 0, MAX_PATTERN_SIZE); + memset(wow_pattern->mask_bytes, 0, MAX_PATTERN_SIZE); + memcpy(wow_pattern->pattern_bytes, patterns[i].pattern, + patterns[i].pattern_len); + memcpy(wow_pattern->mask_bytes, patterns[i].mask, mask_len); + wow_pattern->pattern_len = patterns[i].pattern_len; + + /* + * just need to take care of deauth and disssoc pattern, + * make sure we don't overwrite them. + */ + + ath9k_hw_wow_apply_pattern(ah, wow_pattern->pattern_bytes, + wow_pattern->mask_bytes, + i + 2, + wow_pattern->pattern_len); + kfree(wow_pattern); + + } + +} + +static int ath9k_suspend(struct ieee80211_hw *hw, + struct cfg80211_wowlan *wowlan) +{ + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + u32 wow_triggers_enabled = 0; + int ret = 0; + + mutex_lock(&sc->mutex); + + ath_cancel_work(sc); + ath_stop_ani(sc); + del_timer_sync(&sc->rx_poll_timer); + + if (test_bit(SC_OP_INVALID, &sc->sc_flags)) { + ath_dbg(common, ANY, "Device not present\n"); + ret = -EINVAL; + goto fail_wow; + } + + if (WARN_ON(!wowlan)) { + ath_dbg(common, WOW, "None of the WoW triggers enabled\n"); + ret = -EINVAL; + goto fail_wow; + } + + if (!device_can_wakeup(sc->dev)) { + ath_dbg(common, WOW, "device_can_wakeup failed, WoW is not enabled\n"); + ret = 1; + goto fail_wow; + } + + /* + * none of the sta vifs are associated + * and we are not currently handling multivif + * cases, for instance we have to seperately + * configure 'keep alive frame' for each + * STA. + */ + + if (!test_bit(SC_OP_PRIM_STA_VIF, &sc->sc_flags)) { + ath_dbg(common, WOW, "None of the STA vifs are associated\n"); + ret = 1; + goto fail_wow; + } + + if (sc->nvifs > 1) { + ath_dbg(common, WOW, "WoW for multivif is not yet supported\n"); + ret = 1; + goto fail_wow; + } + + ath9k_wow_map_triggers(sc, wowlan, &wow_triggers_enabled); + + ath_dbg(common, WOW, "WoW triggers enabled 0x%x\n", + wow_triggers_enabled); + + ath9k_ps_wakeup(sc); + + ath9k_stop_btcoex(sc); + + /* + * Enable wake up on recieving disassoc/deauth + * frame by default. + */ + ath9k_wow_add_disassoc_deauth_pattern(sc); + + if (wow_triggers_enabled & AH_WOW_USER_PATTERN_EN) + ath9k_wow_add_pattern(sc, wowlan); + + spin_lock_bh(&sc->sc_pcu_lock); + /* + * To avoid false wake, we enable beacon miss interrupt only + * when we go to sleep. We save the current interrupt mask + * so we can restore it after the system wakes up + */ + sc->wow_intr_before_sleep = ah->imask; + ah->imask &= ~ATH9K_INT_GLOBAL; + ath9k_hw_disable_interrupts(ah); + ah->imask = ATH9K_INT_BMISS | ATH9K_INT_GLOBAL; + ath9k_hw_set_interrupts(ah); + ath9k_hw_enable_interrupts(ah); + + spin_unlock_bh(&sc->sc_pcu_lock); + + /* + * we can now sync irq and kill any running tasklets, since we already + * disabled interrupts and not holding a spin lock + */ + synchronize_irq(sc->irq); + tasklet_kill(&sc->intr_tq); + + ath9k_hw_wow_enable(ah, wow_triggers_enabled); + + ath9k_ps_restore(sc); + ath_dbg(common, ANY, "WoW enabled in ath9k\n"); + atomic_inc(&sc->wow_sleep_proc_intr); + +fail_wow: + mutex_unlock(&sc->mutex); + return ret; +} + +static int ath9k_resume(struct ieee80211_hw *hw) +{ + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + u32 wow_status; + + mutex_lock(&sc->mutex); + + ath9k_ps_wakeup(sc); + + spin_lock_bh(&sc->sc_pcu_lock); + + ath9k_hw_disable_interrupts(ah); + ah->imask = sc->wow_intr_before_sleep; + ath9k_hw_set_interrupts(ah); + ath9k_hw_enable_interrupts(ah); + + spin_unlock_bh(&sc->sc_pcu_lock); + + wow_status = ath9k_hw_wow_wakeup(ah); + + if (atomic_read(&sc->wow_got_bmiss_intr) == 0) { + /* + * some devices may not pick beacon miss + * as the reason they woke up so we add + * that here for that shortcoming. + */ + wow_status |= AH_WOW_BEACON_MISS; + atomic_dec(&sc->wow_got_bmiss_intr); + ath_dbg(common, ANY, "Beacon miss interrupt picked up during WoW sleep\n"); + } + + atomic_dec(&sc->wow_sleep_proc_intr); + + if (wow_status) { + ath_dbg(common, ANY, "Waking up due to WoW triggers %s with WoW status = %x\n", + ath9k_hw_wow_event_to_string(wow_status), wow_status); + } + + ath_restart_work(sc); + ath9k_start_btcoex(sc); + + ath9k_ps_restore(sc); + mutex_unlock(&sc->mutex); + + return 0; +} + +static void ath9k_set_wakeup(struct ieee80211_hw *hw, bool enabled) +{ + struct ath_softc *sc = hw->priv; + + mutex_lock(&sc->mutex); + device_init_wakeup(sc->dev, 1); + device_set_wakeup_enable(sc->dev, enabled); + mutex_unlock(&sc->mutex); +} + +#endif +static void ath9k_sw_scan_start(struct ieee80211_hw *hw) +{ + struct ath_softc *sc = hw->priv; + + sc->scanning = 1; +} + +static void ath9k_sw_scan_complete(struct ieee80211_hw *hw) +{ + struct ath_softc *sc = hw->priv; + + sc->scanning = 0; +} + +struct ieee80211_ops ath9k_ops = { + .tx = ath9k_tx, + .start = ath9k_start, + .stop = ath9k_stop, + .add_interface = ath9k_add_interface, + .change_interface = ath9k_change_interface, + .remove_interface = ath9k_remove_interface, + .config = ath9k_config, + .configure_filter = ath9k_configure_filter, + .sta_add = ath9k_sta_add, + .sta_remove = ath9k_sta_remove, + .sta_notify = ath9k_sta_notify, + .conf_tx = ath9k_conf_tx, + .bss_info_changed = ath9k_bss_info_changed, + .set_key = ath9k_set_key, + .get_tsf = ath9k_get_tsf, + .set_tsf = ath9k_set_tsf, + .reset_tsf = ath9k_reset_tsf, + .ampdu_action = ath9k_ampdu_action, + .get_survey = ath9k_get_survey, + .rfkill_poll = ath9k_rfkill_poll_state, + .set_coverage_class = ath9k_set_coverage_class, + .flush = ath9k_flush, + .tx_frames_pending = ath9k_tx_frames_pending, + .tx_last_beacon = ath9k_tx_last_beacon, + .get_stats = ath9k_get_stats, + .set_antenna = ath9k_set_antenna, + .get_antenna = ath9k_get_antenna, + +#ifdef CONFIG_PM_SLEEP + .suspend = ath9k_suspend, + .resume = ath9k_resume, + .set_wakeup = ath9k_set_wakeup, +#endif + +#ifdef CPTCFG_ATH9K_DEBUGFS + .get_et_sset_count = ath9k_get_et_sset_count, + .get_et_stats = ath9k_get_et_stats, + .get_et_strings = ath9k_get_et_strings, +#endif + +#if defined(CPTCFG_MAC80211_DEBUGFS) && defined(CPTCFG_ATH9K_DEBUGFS) + .sta_add_debugfs = ath9k_sta_add_debugfs, + .sta_remove_debugfs = ath9k_sta_remove_debugfs, +#endif + .sw_scan_start = ath9k_sw_scan_start, + .sw_scan_complete = ath9k_sw_scan_complete, +}; diff --git a/drivers/net/wireless/ath/ath9k/mci.c b/drivers/net/wireless/ath/ath9k/mci.c new file mode 100644 index 0000000..815bee2 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/mci.c @@ -0,0 +1,768 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "ath9k.h" +#include "mci.h" + +static const u8 ath_mci_duty_cycle[] = { 55, 50, 60, 70, 80, 85, 90, 95, 98 }; + +static struct ath_mci_profile_info* +ath_mci_find_profile(struct ath_mci_profile *mci, + struct ath_mci_profile_info *info) +{ + struct ath_mci_profile_info *entry; + + if (list_empty(&mci->info)) + return NULL; + + list_for_each_entry(entry, &mci->info, list) { + if (entry->conn_handle == info->conn_handle) + return entry; + } + return NULL; +} + +static bool ath_mci_add_profile(struct ath_common *common, + struct ath_mci_profile *mci, + struct ath_mci_profile_info *info) +{ + struct ath_mci_profile_info *entry; + u8 voice_priority[] = { 110, 110, 110, 112, 110, 110, 114, 116, 118 }; + + if ((mci->num_sco == ATH_MCI_MAX_SCO_PROFILE) && + (info->type == MCI_GPM_COEX_PROFILE_VOICE)) + return false; + + if (((NUM_PROF(mci) - mci->num_sco) == ATH_MCI_MAX_ACL_PROFILE) && + (info->type != MCI_GPM_COEX_PROFILE_VOICE)) + return false; + + entry = kzalloc(sizeof(*entry), GFP_ATOMIC); + if (!entry) + return false; + + memcpy(entry, info, 10); + INC_PROF(mci, info); + list_add_tail(&entry->list, &mci->info); + if (info->type == MCI_GPM_COEX_PROFILE_VOICE) { + if (info->voice_type < sizeof(voice_priority)) + mci->voice_priority = voice_priority[info->voice_type]; + else + mci->voice_priority = 110; + } + + return true; +} + +static void ath_mci_del_profile(struct ath_common *common, + struct ath_mci_profile *mci, + struct ath_mci_profile_info *entry) +{ + if (!entry) + return; + + DEC_PROF(mci, entry); + list_del(&entry->list); + kfree(entry); +} + +void ath_mci_flush_profile(struct ath_mci_profile *mci) +{ + struct ath_mci_profile_info *info, *tinfo; + + mci->aggr_limit = 0; + mci->num_mgmt = 0; + + if (list_empty(&mci->info)) + return; + + list_for_each_entry_safe(info, tinfo, &mci->info, list) { + list_del(&info->list); + DEC_PROF(mci, info); + kfree(info); + } +} + +static void ath_mci_adjust_aggr_limit(struct ath_btcoex *btcoex) +{ + struct ath_mci_profile *mci = &btcoex->mci; + u32 wlan_airtime = btcoex->btcoex_period * + (100 - btcoex->duty_cycle) / 100; + + /* + * Scale: wlan_airtime is in ms, aggr_limit is in 0.25 ms. + * When wlan_airtime is less than 4ms, aggregation limit has to be + * adjusted half of wlan_airtime to ensure that the aggregation can fit + * without collision with BT traffic. + */ + if ((wlan_airtime <= 4) && + (!mci->aggr_limit || (mci->aggr_limit > (2 * wlan_airtime)))) + mci->aggr_limit = 2 * wlan_airtime; +} + +static void ath_mci_update_scheme(struct ath_softc *sc) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_btcoex *btcoex = &sc->btcoex; + struct ath_mci_profile *mci = &btcoex->mci; + struct ath9k_hw_mci *mci_hw = &sc->sc_ah->btcoex_hw.mci; + struct ath_mci_profile_info *info; + u32 num_profile = NUM_PROF(mci); + + if (mci_hw->config & ATH_MCI_CONFIG_DISABLE_TUNING) + goto skip_tuning; + + mci->aggr_limit = 0; + btcoex->duty_cycle = ath_mci_duty_cycle[num_profile]; + btcoex->btcoex_period = ATH_MCI_DEF_BT_PERIOD; + if (NUM_PROF(mci)) + btcoex->bt_stomp_type = ATH_BTCOEX_STOMP_LOW; + else + btcoex->bt_stomp_type = mci->num_mgmt ? ATH_BTCOEX_STOMP_ALL : + ATH_BTCOEX_STOMP_LOW; + + if (num_profile == 1) { + info = list_first_entry(&mci->info, + struct ath_mci_profile_info, + list); + if (mci->num_sco) { + if (info->T == 12) + mci->aggr_limit = 8; + else if (info->T == 6) { + mci->aggr_limit = 6; + btcoex->duty_cycle = 30; + } else + mci->aggr_limit = 6; + ath_dbg(common, MCI, + "Single SCO, aggregation limit %d 1/4 ms\n", + mci->aggr_limit); + } else if (mci->num_pan || mci->num_other_acl) { + /* + * For single PAN/FTP profile, allocate 35% for BT + * to improve WLAN throughput. + */ + btcoex->duty_cycle = AR_SREV_9565(sc->sc_ah) ? 40 : 35; + btcoex->btcoex_period = 53; + ath_dbg(common, MCI, + "Single PAN/FTP bt period %d ms dutycycle %d\n", + btcoex->duty_cycle, btcoex->btcoex_period); + } else if (mci->num_hid) { + btcoex->duty_cycle = 30; + mci->aggr_limit = 6; + ath_dbg(common, MCI, + "Multiple attempt/timeout single HID " + "aggregation limit 1.5 ms dutycycle 30%%\n"); + } + } else if (num_profile == 2) { + if (mci->num_hid == 2) + btcoex->duty_cycle = 30; + mci->aggr_limit = 6; + ath_dbg(common, MCI, + "Two BT profiles aggr limit 1.5 ms dutycycle %d%%\n", + btcoex->duty_cycle); + } else if (num_profile >= 3) { + mci->aggr_limit = 4; + ath_dbg(common, MCI, + "Three or more profiles aggregation limit 1 ms\n"); + } + +skip_tuning: + if (IS_CHAN_2GHZ(sc->sc_ah->curchan)) { + if (IS_CHAN_HT(sc->sc_ah->curchan)) + ath_mci_adjust_aggr_limit(btcoex); + else + btcoex->btcoex_period >>= 1; + } + + ath9k_btcoex_timer_pause(sc); + ath9k_hw_btcoex_disable(sc->sc_ah); + + if (IS_CHAN_5GHZ(sc->sc_ah->curchan)) + return; + + btcoex->duty_cycle += (mci->num_bdr ? ATH_MCI_BDR_DUTY_CYCLE : 0); + if (btcoex->duty_cycle > ATH_MCI_MAX_DUTY_CYCLE) + btcoex->duty_cycle = ATH_MCI_MAX_DUTY_CYCLE; + + btcoex->btcoex_no_stomp = btcoex->btcoex_period * 1000 * + (100 - btcoex->duty_cycle) / 100; + + ath9k_hw_btcoex_enable(sc->sc_ah); + ath9k_btcoex_timer_resume(sc); +} + +static void ath_mci_cal_msg(struct ath_softc *sc, u8 opcode, u8 *rx_payload) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_hw_mci *mci_hw = &ah->btcoex_hw.mci; + u32 payload[4] = {0, 0, 0, 0}; + + switch (opcode) { + case MCI_GPM_BT_CAL_REQ: + if (mci_hw->bt_state == MCI_BT_AWAKE) { + mci_hw->bt_state = MCI_BT_CAL_START; + ath9k_queue_reset(sc, RESET_TYPE_MCI); + } + ath_dbg(common, MCI, "MCI State : %d\n", mci_hw->bt_state); + break; + case MCI_GPM_BT_CAL_GRANT: + MCI_GPM_SET_CAL_TYPE(payload, MCI_GPM_WLAN_CAL_DONE); + ar9003_mci_send_message(sc->sc_ah, MCI_GPM, 0, payload, + 16, false, true); + break; + default: + ath_dbg(common, MCI, "Unknown GPM CAL message\n"); + break; + } +} + +static void ath9k_mci_work(struct work_struct *work) +{ + struct ath_softc *sc = container_of(work, struct ath_softc, mci_work); + + ath_mci_update_scheme(sc); +} + +static void ath_mci_update_stomp_txprio(u8 cur_txprio, u8 *stomp_prio) +{ + if (cur_txprio < stomp_prio[ATH_BTCOEX_STOMP_NONE]) + stomp_prio[ATH_BTCOEX_STOMP_NONE] = cur_txprio; + + if (cur_txprio > stomp_prio[ATH_BTCOEX_STOMP_ALL]) + stomp_prio[ATH_BTCOEX_STOMP_ALL] = cur_txprio; + + if ((cur_txprio > ATH_MCI_HI_PRIO) && + (cur_txprio < stomp_prio[ATH_BTCOEX_STOMP_LOW])) + stomp_prio[ATH_BTCOEX_STOMP_LOW] = cur_txprio; +} + +static void ath_mci_set_concur_txprio(struct ath_softc *sc) +{ + struct ath_btcoex *btcoex = &sc->btcoex; + struct ath_mci_profile *mci = &btcoex->mci; + u8 stomp_txprio[ATH_BTCOEX_STOMP_MAX]; + + memset(stomp_txprio, 0, sizeof(stomp_txprio)); + if (mci->num_mgmt) { + stomp_txprio[ATH_BTCOEX_STOMP_ALL] = ATH_MCI_INQUIRY_PRIO; + if (!mci->num_pan && !mci->num_other_acl) + stomp_txprio[ATH_BTCOEX_STOMP_NONE] = + ATH_MCI_INQUIRY_PRIO; + } else { + u8 prof_prio[] = { 50, 90, 94, 52 };/* RFCOMM, A2DP, HID, PAN */ + + stomp_txprio[ATH_BTCOEX_STOMP_LOW] = + stomp_txprio[ATH_BTCOEX_STOMP_NONE] = 0xff; + + if (mci->num_sco) + ath_mci_update_stomp_txprio(mci->voice_priority, + stomp_txprio); + if (mci->num_other_acl) + ath_mci_update_stomp_txprio(prof_prio[0], stomp_txprio); + if (mci->num_a2dp) + ath_mci_update_stomp_txprio(prof_prio[1], stomp_txprio); + if (mci->num_hid) + ath_mci_update_stomp_txprio(prof_prio[2], stomp_txprio); + if (mci->num_pan) + ath_mci_update_stomp_txprio(prof_prio[3], stomp_txprio); + + if (stomp_txprio[ATH_BTCOEX_STOMP_NONE] == 0xff) + stomp_txprio[ATH_BTCOEX_STOMP_NONE] = 0; + + if (stomp_txprio[ATH_BTCOEX_STOMP_LOW] == 0xff) + stomp_txprio[ATH_BTCOEX_STOMP_LOW] = 0; + } + ath9k_hw_btcoex_set_concur_txprio(sc->sc_ah, stomp_txprio); +} + +static u8 ath_mci_process_profile(struct ath_softc *sc, + struct ath_mci_profile_info *info) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_btcoex *btcoex = &sc->btcoex; + struct ath_mci_profile *mci = &btcoex->mci; + struct ath_mci_profile_info *entry = NULL; + + entry = ath_mci_find_profile(mci, info); + if (entry) { + /* + * Two MCI interrupts are generated while connecting to + * headset and A2DP profile, but only one MCI interrupt + * is generated with last added profile type while disconnecting + * both profiles. + * So while adding second profile type decrement + * the first one. + */ + if (entry->type != info->type) { + DEC_PROF(mci, entry); + INC_PROF(mci, info); + } + memcpy(entry, info, 10); + } + + if (info->start) { + if (!entry && !ath_mci_add_profile(common, mci, info)) + return 0; + } else + ath_mci_del_profile(common, mci, entry); + + ath_mci_set_concur_txprio(sc); + return 1; +} + +static u8 ath_mci_process_status(struct ath_softc *sc, + struct ath_mci_profile_status *status) +{ + struct ath_btcoex *btcoex = &sc->btcoex; + struct ath_mci_profile *mci = &btcoex->mci; + struct ath_mci_profile_info info; + int i = 0, old_num_mgmt = mci->num_mgmt; + + /* Link status type are not handled */ + if (status->is_link) + return 0; + + info.conn_handle = status->conn_handle; + if (ath_mci_find_profile(mci, &info)) + return 0; + + if (status->conn_handle >= ATH_MCI_MAX_PROFILE) + return 0; + + if (status->is_critical) + __set_bit(status->conn_handle, mci->status); + else + __clear_bit(status->conn_handle, mci->status); + + mci->num_mgmt = 0; + do { + if (test_bit(i, mci->status)) + mci->num_mgmt++; + } while (++i < ATH_MCI_MAX_PROFILE); + + ath_mci_set_concur_txprio(sc); + if (old_num_mgmt != mci->num_mgmt) + return 1; + + return 0; +} + +static void ath_mci_msg(struct ath_softc *sc, u8 opcode, u8 *rx_payload) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_mci_profile_info profile_info; + struct ath_mci_profile_status profile_status; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + u8 major, minor, update_scheme = 0; + u32 seq_num; + + if (ar9003_mci_state(ah, MCI_STATE_NEED_FLUSH_BT_INFO) && + ar9003_mci_state(ah, MCI_STATE_ENABLE)) { + ath_dbg(common, MCI, "(MCI) Need to flush BT profiles\n"); + ath_mci_flush_profile(&sc->btcoex.mci); + ar9003_mci_state(ah, MCI_STATE_SEND_STATUS_QUERY); + } + + switch (opcode) { + case MCI_GPM_COEX_VERSION_QUERY: + ar9003_mci_state(ah, MCI_STATE_SEND_WLAN_COEX_VERSION); + break; + case MCI_GPM_COEX_VERSION_RESPONSE: + major = *(rx_payload + MCI_GPM_COEX_B_MAJOR_VERSION); + minor = *(rx_payload + MCI_GPM_COEX_B_MINOR_VERSION); + ar9003_mci_set_bt_version(ah, major, minor); + break; + case MCI_GPM_COEX_STATUS_QUERY: + ar9003_mci_send_wlan_channels(ah); + break; + case MCI_GPM_COEX_BT_PROFILE_INFO: + memcpy(&profile_info, + (rx_payload + MCI_GPM_COEX_B_PROFILE_TYPE), 10); + + if ((profile_info.type == MCI_GPM_COEX_PROFILE_UNKNOWN) || + (profile_info.type >= MCI_GPM_COEX_PROFILE_MAX)) { + ath_dbg(common, MCI, + "Illegal profile type = %d, state = %d\n", + profile_info.type, + profile_info.start); + break; + } + + update_scheme += ath_mci_process_profile(sc, &profile_info); + break; + case MCI_GPM_COEX_BT_STATUS_UPDATE: + profile_status.is_link = *(rx_payload + + MCI_GPM_COEX_B_STATUS_TYPE); + profile_status.conn_handle = *(rx_payload + + MCI_GPM_COEX_B_STATUS_LINKID); + profile_status.is_critical = *(rx_payload + + MCI_GPM_COEX_B_STATUS_STATE); + + seq_num = *((u32 *)(rx_payload + 12)); + ath_dbg(common, MCI, + "BT_Status_Update: is_link=%d, linkId=%d, state=%d, SEQ=%u\n", + profile_status.is_link, profile_status.conn_handle, + profile_status.is_critical, seq_num); + + update_scheme += ath_mci_process_status(sc, &profile_status); + break; + default: + ath_dbg(common, MCI, "Unknown GPM COEX message = 0x%02x\n", opcode); + break; + } + if (update_scheme) + ieee80211_queue_work(sc->hw, &sc->mci_work); +} + +int ath_mci_setup(struct ath_softc *sc) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_mci_coex *mci = &sc->mci_coex; + struct ath_mci_buf *buf = &mci->sched_buf; + int ret; + + buf->bf_addr = dmam_alloc_coherent(sc->dev, + ATH_MCI_SCHED_BUF_SIZE + ATH_MCI_GPM_BUF_SIZE, + &buf->bf_paddr, GFP_KERNEL); + + if (buf->bf_addr == NULL) { + ath_dbg(common, FATAL, "MCI buffer alloc failed\n"); + return -ENOMEM; + } + + memset(buf->bf_addr, MCI_GPM_RSVD_PATTERN, + ATH_MCI_SCHED_BUF_SIZE + ATH_MCI_GPM_BUF_SIZE); + + mci->sched_buf.bf_len = ATH_MCI_SCHED_BUF_SIZE; + + mci->gpm_buf.bf_len = ATH_MCI_GPM_BUF_SIZE; + mci->gpm_buf.bf_addr = (u8 *)mci->sched_buf.bf_addr + mci->sched_buf.bf_len; + mci->gpm_buf.bf_paddr = mci->sched_buf.bf_paddr + mci->sched_buf.bf_len; + + ret = ar9003_mci_setup(sc->sc_ah, mci->gpm_buf.bf_paddr, + mci->gpm_buf.bf_addr, (mci->gpm_buf.bf_len >> 4), + mci->sched_buf.bf_paddr); + if (ret) { + ath_err(common, "Failed to initialize MCI\n"); + return ret; + } + + INIT_WORK(&sc->mci_work, ath9k_mci_work); + ath_dbg(common, MCI, "MCI Initialized\n"); + + return 0; +} + +void ath_mci_cleanup(struct ath_softc *sc) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_hw *ah = sc->sc_ah; + + ar9003_mci_cleanup(ah); + + ath_dbg(common, MCI, "MCI De-Initialized\n"); +} + +void ath_mci_intr(struct ath_softc *sc) +{ + struct ath_mci_coex *mci = &sc->mci_coex; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath9k_hw_mci *mci_hw = &ah->btcoex_hw.mci; + u32 mci_int, mci_int_rxmsg; + u32 offset, subtype, opcode; + u32 *pgpm; + u32 more_data = MCI_GPM_MORE; + bool skip_gpm = false; + + ar9003_mci_get_interrupt(sc->sc_ah, &mci_int, &mci_int_rxmsg); + + if (ar9003_mci_state(ah, MCI_STATE_ENABLE) == 0) { + ar9003_mci_get_next_gpm_offset(ah, true, NULL); + return; + } + + if (mci_int_rxmsg & AR_MCI_INTERRUPT_RX_MSG_REQ_WAKE) { + u32 payload[4] = { 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffff00}; + + /* + * The following REMOTE_RESET and SYS_WAKING used to sent + * only when BT wake up. Now they are always sent, as a + * recovery method to reset BT MCI's RX alignment. + */ + ar9003_mci_send_message(ah, MCI_REMOTE_RESET, 0, + payload, 16, true, false); + ar9003_mci_send_message(ah, MCI_SYS_WAKING, 0, + NULL, 0, true, false); + + mci_int_rxmsg &= ~AR_MCI_INTERRUPT_RX_MSG_REQ_WAKE; + ar9003_mci_state(ah, MCI_STATE_RESET_REQ_WAKE); + + /* + * always do this for recovery and 2G/5G toggling and LNA_TRANS + */ + ar9003_mci_state(ah, MCI_STATE_SET_BT_AWAKE); + } + + if (mci_int_rxmsg & AR_MCI_INTERRUPT_RX_MSG_SYS_WAKING) { + mci_int_rxmsg &= ~AR_MCI_INTERRUPT_RX_MSG_SYS_WAKING; + + if ((mci_hw->bt_state == MCI_BT_SLEEP) && + (ar9003_mci_state(ah, MCI_STATE_REMOTE_SLEEP) != + MCI_BT_SLEEP)) + ar9003_mci_state(ah, MCI_STATE_SET_BT_AWAKE); + } + + if (mci_int_rxmsg & AR_MCI_INTERRUPT_RX_MSG_SYS_SLEEPING) { + mci_int_rxmsg &= ~AR_MCI_INTERRUPT_RX_MSG_SYS_SLEEPING; + + if ((mci_hw->bt_state == MCI_BT_AWAKE) && + (ar9003_mci_state(ah, MCI_STATE_REMOTE_SLEEP) != + MCI_BT_AWAKE)) + mci_hw->bt_state = MCI_BT_SLEEP; + } + + if ((mci_int & AR_MCI_INTERRUPT_RX_INVALID_HDR) || + (mci_int & AR_MCI_INTERRUPT_CONT_INFO_TIMEOUT)) { + ar9003_mci_state(ah, MCI_STATE_RECOVER_RX); + skip_gpm = true; + } + + if (mci_int_rxmsg & AR_MCI_INTERRUPT_RX_MSG_SCHD_INFO) { + mci_int_rxmsg &= ~AR_MCI_INTERRUPT_RX_MSG_SCHD_INFO; + offset = ar9003_mci_state(ah, MCI_STATE_LAST_SCHD_MSG_OFFSET); + } + + if (mci_int_rxmsg & AR_MCI_INTERRUPT_RX_MSG_GPM) { + mci_int_rxmsg &= ~AR_MCI_INTERRUPT_RX_MSG_GPM; + + while (more_data == MCI_GPM_MORE) { + if (test_bit(SC_OP_HW_RESET, &sc->sc_flags)) + return; + + pgpm = mci->gpm_buf.bf_addr; + offset = ar9003_mci_get_next_gpm_offset(ah, false, + &more_data); + + if (offset == MCI_GPM_INVALID) + break; + + pgpm += (offset >> 2); + + /* + * The first dword is timer. + * The real data starts from 2nd dword. + */ + subtype = MCI_GPM_TYPE(pgpm); + opcode = MCI_GPM_OPCODE(pgpm); + + if (skip_gpm) + goto recycle; + + if (MCI_GPM_IS_CAL_TYPE(subtype)) { + ath_mci_cal_msg(sc, subtype, (u8 *)pgpm); + } else { + switch (subtype) { + case MCI_GPM_COEX_AGENT: + ath_mci_msg(sc, opcode, (u8 *)pgpm); + break; + default: + break; + } + } + recycle: + MCI_GPM_RECYCLE(pgpm); + } + } + + if (mci_int_rxmsg & AR_MCI_INTERRUPT_RX_HW_MSG_MASK) { + if (mci_int_rxmsg & AR_MCI_INTERRUPT_RX_MSG_LNA_CONTROL) + mci_int_rxmsg &= ~AR_MCI_INTERRUPT_RX_MSG_LNA_CONTROL; + + if (mci_int_rxmsg & AR_MCI_INTERRUPT_RX_MSG_LNA_INFO) + mci_int_rxmsg &= ~AR_MCI_INTERRUPT_RX_MSG_LNA_INFO; + + if (mci_int_rxmsg & AR_MCI_INTERRUPT_RX_MSG_CONT_INFO) { + int value_dbm = MS(mci_hw->cont_status, + AR_MCI_CONT_RSSI_POWER); + + mci_int_rxmsg &= ~AR_MCI_INTERRUPT_RX_MSG_CONT_INFO; + + ath_dbg(common, MCI, + "MCI CONT_INFO: (%s) pri = %d pwr = %d dBm\n", + MS(mci_hw->cont_status, AR_MCI_CONT_TXRX) ? + "tx" : "rx", + MS(mci_hw->cont_status, AR_MCI_CONT_PRIORITY), + value_dbm); + } + + if (mci_int_rxmsg & AR_MCI_INTERRUPT_RX_MSG_CONT_NACK) + mci_int_rxmsg &= ~AR_MCI_INTERRUPT_RX_MSG_CONT_NACK; + + if (mci_int_rxmsg & AR_MCI_INTERRUPT_RX_MSG_CONT_RST) + mci_int_rxmsg &= ~AR_MCI_INTERRUPT_RX_MSG_CONT_RST; + } + + if ((mci_int & AR_MCI_INTERRUPT_RX_INVALID_HDR) || + (mci_int & AR_MCI_INTERRUPT_CONT_INFO_TIMEOUT)) { + mci_int &= ~(AR_MCI_INTERRUPT_RX_INVALID_HDR | + AR_MCI_INTERRUPT_CONT_INFO_TIMEOUT); + ath_mci_msg(sc, MCI_GPM_COEX_NOOP, NULL); + } +} + +void ath_mci_enable(struct ath_softc *sc) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + + if (!common->btcoex_enabled) + return; + + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_MCI) + sc->sc_ah->imask |= ATH9K_INT_MCI; +} + +void ath9k_mci_update_wlan_channels(struct ath_softc *sc, bool allow_all) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath9k_hw_mci *mci = &ah->btcoex_hw.mci; + struct ath9k_channel *chan = ah->curchan; + u32 channelmap[] = {0x00000000, 0xffff0000, 0xffffffff, 0x7fffffff}; + int i; + s16 chan_start, chan_end; + u16 wlan_chan; + + if (!chan || !IS_CHAN_2GHZ(chan)) + return; + + if (allow_all) + goto send_wlan_chan; + + wlan_chan = chan->channel - 2402; + + chan_start = wlan_chan - 10; + chan_end = wlan_chan + 10; + + if (chan->chanmode == CHANNEL_G_HT40PLUS) + chan_end += 20; + else if (chan->chanmode == CHANNEL_G_HT40MINUS) + chan_start -= 20; + + /* adjust side band */ + chan_start -= 7; + chan_end += 7; + + if (chan_start <= 0) + chan_start = 0; + if (chan_end >= ATH_MCI_NUM_BT_CHANNELS) + chan_end = ATH_MCI_NUM_BT_CHANNELS - 1; + + ath_dbg(ath9k_hw_common(ah), MCI, + "WLAN current channel %d mask BT channel %d - %d\n", + wlan_chan, chan_start, chan_end); + + for (i = chan_start; i < chan_end; i++) + MCI_GPM_CLR_CHANNEL_BIT(&channelmap, i); + +send_wlan_chan: + /* update and send wlan channels info to BT */ + for (i = 0; i < 4; i++) + mci->wlan_channels[i] = channelmap[i]; + ar9003_mci_send_wlan_channels(ah); + ar9003_mci_state(ah, MCI_STATE_SEND_VERSION_QUERY); +} + +void ath9k_mci_set_txpower(struct ath_softc *sc, bool setchannel, + bool concur_tx) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath9k_hw_mci *mci_hw = &sc->sc_ah->btcoex_hw.mci; + bool old_concur_tx = mci_hw->concur_tx; + + if (!(mci_hw->config & ATH_MCI_CONFIG_CONCUR_TX)) { + mci_hw->concur_tx = false; + return; + } + + if (!IS_CHAN_2GHZ(ah->curchan)) + return; + + if (setchannel) { + struct ath9k_hw_cal_data *caldata = &sc->caldata; + if ((caldata->chanmode == CHANNEL_G_HT40PLUS) && + (ah->curchan->channel > caldata->channel) && + (ah->curchan->channel <= caldata->channel + 20)) + return; + if ((caldata->chanmode == CHANNEL_G_HT40MINUS) && + (ah->curchan->channel < caldata->channel) && + (ah->curchan->channel >= caldata->channel - 20)) + return; + mci_hw->concur_tx = false; + } else + mci_hw->concur_tx = concur_tx; + + if (old_concur_tx != mci_hw->concur_tx) + ath9k_hw_set_txpowerlimit(ah, sc->config.txpowlimit, false); +} + +static void ath9k_mci_stomp_audio(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_btcoex *btcoex = &sc->btcoex; + struct ath_mci_profile *mci = &btcoex->mci; + + if (!mci->num_sco && !mci->num_a2dp) + return; + + if (ah->stats.avgbrssi > 25) { + btcoex->stomp_audio = 0; + return; + } + + btcoex->stomp_audio++; +} +void ath9k_mci_update_rssi(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_btcoex *btcoex = &sc->btcoex; + struct ath9k_hw_mci *mci_hw = &sc->sc_ah->btcoex_hw.mci; + + ath9k_mci_stomp_audio(sc); + + if (!(mci_hw->config & ATH_MCI_CONFIG_CONCUR_TX)) + return; + + if (ah->stats.avgbrssi >= 40) { + if (btcoex->rssi_count < 0) + btcoex->rssi_count = 0; + if (++btcoex->rssi_count >= ATH_MCI_CONCUR_TX_SWITCH) { + btcoex->rssi_count = 0; + ath9k_mci_set_txpower(sc, false, true); + } + } else { + if (btcoex->rssi_count > 0) + btcoex->rssi_count = 0; + if (--btcoex->rssi_count <= -ATH_MCI_CONCUR_TX_SWITCH) { + btcoex->rssi_count = 0; + ath9k_mci_set_txpower(sc, false, false); + } + } +} diff --git a/drivers/net/wireless/ath/ath9k/mci.h b/drivers/net/wireless/ath/ath9k/mci.h new file mode 100644 index 0000000..590e6bb --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/mci.h @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef MCI_H +#define MCI_H + +#include "ar9003_mci.h" + +#define ATH_MCI_SCHED_BUF_SIZE (16 * 16) /* 16 entries, 4 dword each */ +#define ATH_MCI_GPM_MAX_ENTRY 16 +#define ATH_MCI_GPM_BUF_SIZE (ATH_MCI_GPM_MAX_ENTRY * 16) +#define ATH_MCI_DEF_BT_PERIOD 40 +#define ATH_MCI_BDR_DUTY_CYCLE 20 +#define ATH_MCI_MAX_DUTY_CYCLE 90 + +#define ATH_MCI_DEF_AGGR_LIMIT 6 /* in 0.24 ms */ +#define ATH_MCI_MAX_ACL_PROFILE 7 +#define ATH_MCI_MAX_SCO_PROFILE 1 +#define ATH_MCI_MAX_PROFILE (ATH_MCI_MAX_ACL_PROFILE +\ + ATH_MCI_MAX_SCO_PROFILE) + +#define ATH_MCI_INQUIRY_PRIO 62 +#define ATH_MCI_HI_PRIO 60 +#define ATH_MCI_NUM_BT_CHANNELS 79 +#define ATH_MCI_CONCUR_TX_SWITCH 5 + +#define MCI_GPM_SET_CHANNEL_BIT(_p_gpm, _bt_chan) \ + do { \ + if (_bt_chan < ATH_MCI_NUM_BT_CHANNELS) { \ + *(((u8 *)(_p_gpm)) + MCI_GPM_COEX_B_CHANNEL_MAP + \ + (_bt_chan / 8)) |= (1 << (_bt_chan & 7)); \ + } \ + } while (0) + +#define MCI_GPM_CLR_CHANNEL_BIT(_p_gpm, _bt_chan) \ + do { \ + if (_bt_chan < ATH_MCI_NUM_BT_CHANNELS) { \ + *(((u8 *)(_p_gpm)) + MCI_GPM_COEX_B_CHANNEL_MAP + \ + (_bt_chan / 8)) &= ~(1 << (_bt_chan & 7));\ + } \ + } while (0) + +#define INC_PROF(_mci, _info) do { \ + switch (_info->type) { \ + case MCI_GPM_COEX_PROFILE_RFCOMM:\ + _mci->num_other_acl++; \ + break; \ + case MCI_GPM_COEX_PROFILE_A2DP: \ + _mci->num_a2dp++; \ + if (!_info->edr) \ + _mci->num_bdr++; \ + break; \ + case MCI_GPM_COEX_PROFILE_HID: \ + _mci->num_hid++; \ + break; \ + case MCI_GPM_COEX_PROFILE_BNEP: \ + _mci->num_pan++; \ + break; \ + case MCI_GPM_COEX_PROFILE_VOICE: \ + case MCI_GPM_COEX_PROFILE_A2DPVO:\ + _mci->num_sco++; \ + break; \ + default: \ + break; \ + } \ + } while (0) + +#define DEC_PROF(_mci, _info) do { \ + switch (_info->type) { \ + case MCI_GPM_COEX_PROFILE_RFCOMM:\ + _mci->num_other_acl--; \ + break; \ + case MCI_GPM_COEX_PROFILE_A2DP: \ + _mci->num_a2dp--; \ + if (!_info->edr) \ + _mci->num_bdr--; \ + break; \ + case MCI_GPM_COEX_PROFILE_HID: \ + _mci->num_hid--; \ + break; \ + case MCI_GPM_COEX_PROFILE_BNEP: \ + _mci->num_pan--; \ + break; \ + case MCI_GPM_COEX_PROFILE_VOICE: \ + case MCI_GPM_COEX_PROFILE_A2DPVO:\ + _mci->num_sco--; \ + break; \ + default: \ + break; \ + } \ + } while (0) + +#define NUM_PROF(_mci) (_mci->num_other_acl + _mci->num_a2dp + \ + _mci->num_hid + _mci->num_pan + _mci->num_sco) + +struct ath_mci_profile_info { + u8 type; + u8 conn_handle; + bool start; + bool master; + bool edr; + u8 voice_type; + u16 T; /* Voice: Tvoice, HID: Tsniff, in slots */ + u8 W; /* Voice: Wvoice, HID: Sniff timeout, in slots */ + u8 A; /* HID: Sniff attempt, in slots */ + struct list_head list; +}; + +struct ath_mci_profile_status { + bool is_critical; + bool is_link; + u8 conn_handle; +}; + +struct ath_mci_profile { + struct list_head info; + DECLARE_BITMAP(status, ATH_MCI_MAX_PROFILE); + u16 aggr_limit; + u8 num_mgmt; + u8 num_sco; + u8 num_a2dp; + u8 num_hid; + u8 num_pan; + u8 num_other_acl; + u8 num_bdr; + u8 voice_priority; +}; + +struct ath_mci_buf { + void *bf_addr; /* virtual addr of desc */ + dma_addr_t bf_paddr; /* physical addr of buffer */ + u32 bf_len; /* len of data */ +}; + +struct ath_mci_coex { + struct ath_mci_buf sched_buf; + struct ath_mci_buf gpm_buf; +}; + +void ath_mci_flush_profile(struct ath_mci_profile *mci); +int ath_mci_setup(struct ath_softc *sc); +void ath_mci_cleanup(struct ath_softc *sc); +void ath_mci_intr(struct ath_softc *sc); +void ath9k_mci_update_rssi(struct ath_softc *sc); + +#ifdef CPTCFG_ATH9K_BTCOEX_SUPPORT +void ath_mci_enable(struct ath_softc *sc); +void ath9k_mci_update_wlan_channels(struct ath_softc *sc, bool allow_all); +void ath9k_mci_set_txpower(struct ath_softc *sc, bool setchannel, + bool concur_tx); +#else +static inline void ath_mci_enable(struct ath_softc *sc) +{ +} +static inline void ath9k_mci_update_wlan_channels(struct ath_softc *sc, + bool allow_all) +{ +} +static inline void ath9k_mci_set_txpower(struct ath_softc *sc, bool setchannel, + bool concur_tx) +{ +} +#endif /* CPTCFG_ATH9K_BTCOEX_SUPPORT */ + +#endif /* MCI_H*/ diff --git a/drivers/net/wireless/ath/ath9k/pci.c b/drivers/net/wireless/ath/ath9k/pci.c new file mode 100644 index 0000000..547340b --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/pci.c @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include "ath9k.h" + +static DEFINE_PCI_DEVICE_TABLE(ath_pci_id_table) = { + { PCI_VDEVICE(ATHEROS, 0x0023) }, /* PCI */ + { PCI_VDEVICE(ATHEROS, 0x0024) }, /* PCI-E */ + { PCI_VDEVICE(ATHEROS, 0x0027) }, /* PCI */ + { PCI_VDEVICE(ATHEROS, 0x0029) }, /* PCI */ + { PCI_VDEVICE(ATHEROS, 0x002A) }, /* PCI-E */ + { PCI_VDEVICE(ATHEROS, 0x002B) }, /* PCI-E */ + { PCI_VDEVICE(ATHEROS, 0x002C) }, /* PCI-E 802.11n bonded out */ + { PCI_VDEVICE(ATHEROS, 0x002D) }, /* PCI */ + { PCI_VDEVICE(ATHEROS, 0x002E) }, /* PCI-E */ + { PCI_VDEVICE(ATHEROS, 0x0030) }, /* PCI-E AR9300 */ + { PCI_VDEVICE(ATHEROS, 0x0032) }, /* PCI-E AR9485 */ + { PCI_VDEVICE(ATHEROS, 0x0033) }, /* PCI-E AR9580 */ + { PCI_VDEVICE(ATHEROS, 0x0034) }, /* PCI-E AR9462 */ + { PCI_VDEVICE(ATHEROS, 0x0037) }, /* PCI-E AR1111/AR9485 */ + { PCI_VDEVICE(ATHEROS, 0x0036) }, /* PCI-E AR9565 */ + { 0 } +}; + + +/* return bus cachesize in 4B word units */ +static void ath_pci_read_cachesize(struct ath_common *common, int *csz) +{ + struct ath_softc *sc = (struct ath_softc *) common->priv; + u8 u8tmp; + + pci_read_config_byte(to_pci_dev(sc->dev), PCI_CACHE_LINE_SIZE, &u8tmp); + *csz = (int)u8tmp; + + /* + * This check was put in to avoid "unpleasant" consequences if + * the bootrom has not fully initialized all PCI devices. + * Sometimes the cache line size register is not set + */ + + if (*csz == 0) + *csz = DEFAULT_CACHELINE >> 2; /* Use the default size */ +} + +static bool ath_pci_eeprom_read(struct ath_common *common, u32 off, u16 *data) +{ + struct ath_softc *sc = (struct ath_softc *) common->priv; + struct ath9k_platform_data *pdata = sc->dev->platform_data; + + if (pdata) { + if (off >= (ARRAY_SIZE(pdata->eeprom_data))) { + ath_err(common, + "%s: eeprom read failed, offset %08x is out of range\n", + __func__, off); + } + + *data = pdata->eeprom_data[off]; + } else { + struct ath_hw *ah = (struct ath_hw *) common->ah; + + common->ops->read(ah, AR5416_EEPROM_OFFSET + + (off << AR5416_EEPROM_S)); + + if (!ath9k_hw_wait(ah, + AR_EEPROM_STATUS_DATA, + AR_EEPROM_STATUS_DATA_BUSY | + AR_EEPROM_STATUS_DATA_PROT_ACCESS, 0, + AH_WAIT_TIMEOUT)) { + return false; + } + + *data = MS(common->ops->read(ah, AR_EEPROM_STATUS_DATA), + AR_EEPROM_STATUS_DATA_VAL); + } + + return true; +} + +/* Need to be called after we discover btcoex capabilities */ +static void ath_pci_aspm_init(struct ath_common *common) +{ + struct ath_softc *sc = (struct ath_softc *) common->priv; + struct ath_hw *ah = sc->sc_ah; + struct pci_dev *pdev = to_pci_dev(sc->dev); + struct pci_dev *parent; + u16 aspm; + + if (!ah->is_pciexpress) + return; + + parent = pdev->bus->self; + if (!parent) + return; + + if ((ath9k_hw_get_btcoex_scheme(ah) != ATH_BTCOEX_CFG_NONE) && + (AR_SREV_9285(ah))) { + /* Bluetooth coexistence requires disabling ASPM. */ + pcie_capability_clear_word(pdev, PCI_EXP_LNKCTL, + PCI_EXP_LNKCTL_ASPM_L0S | PCI_EXP_LNKCTL_ASPM_L1); + + /* + * Both upstream and downstream PCIe components should + * have the same ASPM settings. + */ + pcie_capability_clear_word(parent, PCI_EXP_LNKCTL, + PCI_EXP_LNKCTL_ASPM_L0S | PCI_EXP_LNKCTL_ASPM_L1); + + ath_info(common, "Disabling ASPM since BTCOEX is enabled\n"); + return; + } + + pcie_capability_read_word(parent, PCI_EXP_LNKCTL, &aspm); + if (aspm & (PCI_EXP_LNKCTL_ASPM_L0S | PCI_EXP_LNKCTL_ASPM_L1)) { + ah->aspm_enabled = true; + /* Initialize PCIe PM and SERDES registers. */ + ath9k_hw_configpcipowersave(ah, false); + ath_info(common, "ASPM enabled: 0x%x\n", aspm); + } +} + +static const struct ath_bus_ops ath_pci_bus_ops = { + .ath_bus_type = ATH_PCI, + .read_cachesize = ath_pci_read_cachesize, + .eeprom_read = ath_pci_eeprom_read, + .aspm_init = ath_pci_aspm_init, +}; + +static int ath_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct ath_softc *sc; + struct ieee80211_hw *hw; + u8 csz; + u32 val; + int ret = 0; + char hw_name[64]; + + if (pcim_enable_device(pdev)) + return -EIO; + + ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + if (ret) { + pr_err("32-bit DMA not available\n"); + return ret; + } + + ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)); + if (ret) { + pr_err("32-bit DMA consistent DMA enable failed\n"); + return ret; + } + + /* + * Cache line size is used to size and align various + * structures used to communicate with the hardware. + */ + pci_read_config_byte(pdev, PCI_CACHE_LINE_SIZE, &csz); + if (csz == 0) { + /* + * Linux 2.4.18 (at least) writes the cache line size + * register as a 16-bit wide register which is wrong. + * We must have this setup properly for rx buffer + * DMA to work so force a reasonable value here if it + * comes up zero. + */ + csz = L1_CACHE_BYTES / sizeof(u32); + pci_write_config_byte(pdev, PCI_CACHE_LINE_SIZE, csz); + } + /* + * The default setting of latency timer yields poor results, + * set it to the value used by other systems. It may be worth + * tweaking this setting more. + */ + pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 0xa8); + + pci_set_master(pdev); + + /* + * Disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state. + */ + pci_read_config_dword(pdev, 0x40, &val); + if ((val & 0x0000ff00) != 0) + pci_write_config_dword(pdev, 0x40, val & 0xffff00ff); + + ret = pcim_iomap_regions(pdev, BIT(0), "ath9k"); + if (ret) { + dev_err(&pdev->dev, "PCI memory region reserve error\n"); + return -ENODEV; + } + + hw = ieee80211_alloc_hw(sizeof(struct ath_softc), &ath9k_ops); + if (!hw) { + dev_err(&pdev->dev, "No memory for ieee80211_hw\n"); + return -ENOMEM; + } + + SET_IEEE80211_DEV(hw, &pdev->dev); + pci_set_drvdata(pdev, hw); + + sc = hw->priv; + sc->hw = hw; + sc->dev = &pdev->dev; + sc->mem = pcim_iomap_table(pdev)[0]; + + /* Will be cleared in ath9k_start() */ + set_bit(SC_OP_INVALID, &sc->sc_flags); + + ret = request_irq(pdev->irq, ath_isr, IRQF_SHARED, "ath9k", sc); + if (ret) { + dev_err(&pdev->dev, "request_irq failed\n"); + goto err_irq; + } + + sc->irq = pdev->irq; + + ret = ath9k_init_device(id->device, sc, &ath_pci_bus_ops); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize device\n"); + goto err_init; + } + + ath9k_hw_name(sc->sc_ah, hw_name, sizeof(hw_name)); + wiphy_info(hw->wiphy, "%s mem=0x%lx, irq=%d\n", + hw_name, (unsigned long)sc->mem, pdev->irq); + + return 0; + +err_init: + free_irq(sc->irq, sc); +err_irq: + ieee80211_free_hw(hw); + return ret; +} + +static void ath_pci_remove(struct pci_dev *pdev) +{ + struct ieee80211_hw *hw = pci_get_drvdata(pdev); + struct ath_softc *sc = hw->priv; + + if (!is_ath9k_unloaded) + sc->sc_ah->ah_flags |= AH_UNPLUGGED; + ath9k_deinit_device(sc); + free_irq(sc->irq, sc); + ieee80211_free_hw(sc->hw); +} + +#ifdef CONFIG_PM_SLEEP + +static int ath_pci_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct ieee80211_hw *hw = pci_get_drvdata(pdev); + struct ath_softc *sc = hw->priv; + + if (sc->wow_enabled) + return 0; + + /* The device has to be moved to FULLSLEEP forcibly. + * Otherwise the chip never moved to full sleep, + * when no interface is up. + */ + ath9k_stop_btcoex(sc); + ath9k_hw_disable(sc->sc_ah); + ath9k_hw_setpower(sc->sc_ah, ATH9K_PM_FULL_SLEEP); + + return 0; +} + +static int ath_pci_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct ieee80211_hw *hw = pci_get_drvdata(pdev); + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + u32 val; + + /* + * Suspend/Resume resets the PCI configuration space, so we have to + * re-disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state + */ + pci_read_config_dword(pdev, 0x40, &val); + if ((val & 0x0000ff00) != 0) + pci_write_config_dword(pdev, 0x40, val & 0xffff00ff); + + ath_pci_aspm_init(common); + ah->reset_power_on = false; + + return 0; +} + +compat_pci_suspend(ath_pci_suspend); +compat_pci_resume(ath_pci_resume); + +static SIMPLE_DEV_PM_OPS(ath9k_pm_ops, ath_pci_suspend, ath_pci_resume); + +#define ATH9K_PM_OPS (&ath9k_pm_ops) + +#else /* !CONFIG_PM_SLEEP */ + +#define ATH9K_PM_OPS NULL + +#endif /* !CONFIG_PM_SLEEP */ + + +MODULE_DEVICE_TABLE(pci, ath_pci_id_table); + +static struct pci_driver ath_pci_driver = { + .name = "ath9k", + .id_table = ath_pci_id_table, + .probe = ath_pci_probe, + .remove = ath_pci_remove, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) + .driver.pm = ATH9K_PM_OPS, +#elif defined(CONFIG_PM_SLEEP) + .suspend = ath_pci_suspend_compat, + .resume = ath_pci_resume_compat, +#endif +}; + +int ath_pci_init(void) +{ + return pci_register_driver(&ath_pci_driver); +} + +void ath_pci_exit(void) +{ + pci_unregister_driver(&ath_pci_driver); +} diff --git a/drivers/net/wireless/ath/ath9k/phy.h b/drivers/net/wireless/ath/ath9k/phy.h new file mode 100644 index 0000000..8b38030 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/phy.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef PHY_H +#define PHY_H + +#define CHANSEL_DIV 15 +#define CHANSEL_2G(_freq) (((_freq) * 0x10000) / CHANSEL_DIV) +#define CHANSEL_5G(_freq) (((_freq) * 0x8000) / CHANSEL_DIV) + +#define AR_PHY_BASE 0x9800 +#define AR_PHY(_n) (AR_PHY_BASE + ((_n)<<2)) + +#define AR_PHY_TX_PWRCTRL_TX_GAIN_TAB_MAX 0x0007E000 +#define AR_PHY_TX_PWRCTRL_TX_GAIN_TAB_MAX_S 13 +#define AR_PHY_TX_GAIN_CLC 0x0000001E +#define AR_PHY_TX_GAIN_CLC_S 1 +#define AR_PHY_TX_GAIN 0x0007F000 +#define AR_PHY_TX_GAIN_S 12 + +#define AR_PHY_CLC_TBL1 0xa35c +#define AR_PHY_CLC_I0 0x07ff0000 +#define AR_PHY_CLC_I0_S 16 +#define AR_PHY_CLC_Q0 0x0000ffd0 +#define AR_PHY_CLC_Q0_S 5 + +#define ANTSWAP_AB 0x0001 +#define REDUCE_CHAIN_0 0x00000050 +#define REDUCE_CHAIN_1 0x00000051 +#define AR_PHY_CHIP_ID 0x9818 + +#define AR_PHY_TIMING11_SPUR_FREQ_SD 0x3FF00000 +#define AR_PHY_TIMING11_SPUR_FREQ_SD_S 20 + +#define AR_PHY_PLL_CONTROL 0x16180 +#define AR_PHY_PLL_MODE 0x16184 + +#endif diff --git a/drivers/net/wireless/ath/ath9k/rc.c b/drivers/net/wireless/ath/ath9k/rc.c new file mode 100644 index 0000000..f4f457c --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/rc.c @@ -0,0 +1,1488 @@ +/* + * Copyright (c) 2004 Video54 Technologies, Inc. + * Copyright (c) 2004-2011 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "ath9k.h" + +static const struct ath_rate_table ar5416_11na_ratetable = { + 68, + 8, /* MCS start */ + { + [0] = { RC_L_SDT, WLAN_RC_PHY_OFDM, 6000, + 5400, 0, 12 }, /* 6 Mb */ + [1] = { RC_L_SDT, WLAN_RC_PHY_OFDM, 9000, + 7800, 1, 18 }, /* 9 Mb */ + [2] = { RC_L_SDT, WLAN_RC_PHY_OFDM, 12000, + 10000, 2, 24 }, /* 12 Mb */ + [3] = { RC_L_SDT, WLAN_RC_PHY_OFDM, 18000, + 13900, 3, 36 }, /* 18 Mb */ + [4] = { RC_L_SDT, WLAN_RC_PHY_OFDM, 24000, + 17300, 4, 48 }, /* 24 Mb */ + [5] = { RC_L_SDT, WLAN_RC_PHY_OFDM, 36000, + 23000, 5, 72 }, /* 36 Mb */ + [6] = { RC_L_SDT, WLAN_RC_PHY_OFDM, 48000, + 27400, 6, 96 }, /* 48 Mb */ + [7] = { RC_L_SDT, WLAN_RC_PHY_OFDM, 54000, + 29300, 7, 108 }, /* 54 Mb */ + [8] = { RC_HT_SDT_2040, WLAN_RC_PHY_HT_20_SS, 6500, + 6400, 0, 0 }, /* 6.5 Mb */ + [9] = { RC_HT_SDT_20, WLAN_RC_PHY_HT_20_SS, 13000, + 12700, 1, 1 }, /* 13 Mb */ + [10] = { RC_HT_SDT_20, WLAN_RC_PHY_HT_20_SS, 19500, + 18800, 2, 2 }, /* 19.5 Mb */ + [11] = { RC_HT_SD_20, WLAN_RC_PHY_HT_20_SS, 26000, + 25000, 3, 3 }, /* 26 Mb */ + [12] = { RC_HT_SD_20, WLAN_RC_PHY_HT_20_SS, 39000, + 36700, 4, 4 }, /* 39 Mb */ + [13] = { RC_HT_S_20, WLAN_RC_PHY_HT_20_SS, 52000, + 48100, 5, 5 }, /* 52 Mb */ + [14] = { RC_HT_S_20, WLAN_RC_PHY_HT_20_SS, 58500, + 53500, 6, 6 }, /* 58.5 Mb */ + [15] = { RC_HT_S_20, WLAN_RC_PHY_HT_20_SS, 65000, + 59000, 7, 7 }, /* 65 Mb */ + [16] = { RC_HT_S_20, WLAN_RC_PHY_HT_20_SS_HGI, 72200, + 65400, 7, 7 }, /* 75 Mb */ + [17] = { RC_INVALID, WLAN_RC_PHY_HT_20_DS, 13000, + 12700, 8, 8 }, /* 13 Mb */ + [18] = { RC_HT_T_20, WLAN_RC_PHY_HT_20_DS, 26000, + 24800, 9, 9 }, /* 26 Mb */ + [19] = { RC_HT_T_20, WLAN_RC_PHY_HT_20_DS, 39000, + 36600, 10, 10 }, /* 39 Mb */ + [20] = { RC_HT_DT_20, WLAN_RC_PHY_HT_20_DS, 52000, + 48100, 11, 11 }, /* 52 Mb */ + [21] = { RC_HT_DT_20, WLAN_RC_PHY_HT_20_DS, 78000, + 69500, 12, 12 }, /* 78 Mb */ + [22] = { RC_HT_DT_20, WLAN_RC_PHY_HT_20_DS, 104000, + 89500, 13, 13 }, /* 104 Mb */ + [23] = { RC_HT_DT_20, WLAN_RC_PHY_HT_20_DS, 117000, + 98900, 14, 14 }, /* 117 Mb */ + [24] = { RC_HT_DT_20, WLAN_RC_PHY_HT_20_DS, 130000, + 108300, 15, 15 }, /* 130 Mb */ + [25] = { RC_HT_DT_20, WLAN_RC_PHY_HT_20_DS_HGI, 144400, + 120000, 15, 15 }, /* 144.4 Mb */ + [26] = { RC_INVALID, WLAN_RC_PHY_HT_20_TS, 19500, + 17400, 16, 16 }, /* 19.5 Mb */ + [27] = { RC_INVALID, WLAN_RC_PHY_HT_20_TS, 39000, + 35100, 17, 17 }, /* 39 Mb */ + [28] = { RC_INVALID, WLAN_RC_PHY_HT_20_TS, 58500, + 52600, 18, 18 }, /* 58.5 Mb */ + [29] = { RC_INVALID, WLAN_RC_PHY_HT_20_TS, 78000, + 70400, 19, 19 }, /* 78 Mb */ + [30] = { RC_INVALID, WLAN_RC_PHY_HT_20_TS, 117000, + 104900, 20, 20 }, /* 117 Mb */ + [31] = { RC_INVALID, WLAN_RC_PHY_HT_20_TS_HGI, 130000, + 115800, 20, 20 }, /* 130 Mb*/ + [32] = { RC_HT_T_20, WLAN_RC_PHY_HT_20_TS, 156000, + 137200, 21, 21 }, /* 156 Mb */ + [33] = { RC_HT_T_20, WLAN_RC_PHY_HT_20_TS_HGI, 173300, + 151100, 21, 21 }, /* 173.3 Mb */ + [34] = { RC_HT_T_20, WLAN_RC_PHY_HT_20_TS, 175500, + 152800, 22, 22 }, /* 175.5 Mb */ + [35] = { RC_HT_T_20, WLAN_RC_PHY_HT_20_TS_HGI, 195000, + 168400, 22, 22 }, /* 195 Mb*/ + [36] = { RC_HT_T_20, WLAN_RC_PHY_HT_20_TS, 195000, + 168400, 23, 23 }, /* 195 Mb */ + [37] = { RC_HT_T_20, WLAN_RC_PHY_HT_20_TS_HGI, 216700, + 185000, 23, 23 }, /* 216.7 Mb */ + [38] = { RC_HT_SDT_40, WLAN_RC_PHY_HT_40_SS, 13500, + 13200, 0, 0 }, /* 13.5 Mb*/ + [39] = { RC_HT_SDT_40, WLAN_RC_PHY_HT_40_SS, 27500, + 25900, 1, 1 }, /* 27.0 Mb*/ + [40] = { RC_HT_SDT_40, WLAN_RC_PHY_HT_40_SS, 40500, + 38600, 2, 2 }, /* 40.5 Mb*/ + [41] = { RC_HT_SD_40, WLAN_RC_PHY_HT_40_SS, 54000, + 49800, 3, 3 }, /* 54 Mb */ + [42] = { RC_HT_SD_40, WLAN_RC_PHY_HT_40_SS, 81500, + 72200, 4, 4 }, /* 81 Mb */ + [43] = { RC_HT_S_40, WLAN_RC_PHY_HT_40_SS, 108000, + 92900, 5, 5 }, /* 108 Mb */ + [44] = { RC_HT_S_40, WLAN_RC_PHY_HT_40_SS, 121500, + 102700, 6, 6 }, /* 121.5 Mb*/ + [45] = { RC_HT_S_40, WLAN_RC_PHY_HT_40_SS, 135000, + 112000, 7, 7 }, /* 135 Mb */ + [46] = { RC_HT_S_40, WLAN_RC_PHY_HT_40_SS_HGI, 150000, + 122000, 7, 7 }, /* 150 Mb */ + [47] = { RC_INVALID, WLAN_RC_PHY_HT_40_DS, 27000, + 25800, 8, 8 }, /* 27 Mb */ + [48] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_DS, 54000, + 49800, 9, 9 }, /* 54 Mb */ + [49] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_DS, 81000, + 71900, 10, 10 }, /* 81 Mb */ + [50] = { RC_HT_DT_40, WLAN_RC_PHY_HT_40_DS, 108000, + 92500, 11, 11 }, /* 108 Mb */ + [51] = { RC_HT_DT_40, WLAN_RC_PHY_HT_40_DS, 162000, + 130300, 12, 12 }, /* 162 Mb */ + [52] = { RC_HT_DT_40, WLAN_RC_PHY_HT_40_DS, 216000, + 162800, 13, 13 }, /* 216 Mb */ + [53] = { RC_HT_DT_40, WLAN_RC_PHY_HT_40_DS, 243000, + 178200, 14, 14 }, /* 243 Mb */ + [54] = { RC_HT_DT_40, WLAN_RC_PHY_HT_40_DS, 270000, + 192100, 15, 15 }, /* 270 Mb */ + [55] = { RC_HT_DT_40, WLAN_RC_PHY_HT_40_DS_HGI, 300000, + 207000, 15, 15 }, /* 300 Mb */ + [56] = { RC_INVALID, WLAN_RC_PHY_HT_40_TS, 40500, + 36100, 16, 16 }, /* 40.5 Mb */ + [57] = { RC_INVALID, WLAN_RC_PHY_HT_40_TS, 81000, + 72900, 17, 17 }, /* 81 Mb */ + [58] = { RC_INVALID, WLAN_RC_PHY_HT_40_TS, 121500, + 108300, 18, 18 }, /* 121.5 Mb */ + [59] = { RC_INVALID, WLAN_RC_PHY_HT_40_TS, 162000, + 142000, 19, 19 }, /* 162 Mb */ + [60] = { RC_INVALID, WLAN_RC_PHY_HT_40_TS, 243000, + 205100, 20, 20 }, /* 243 Mb */ + [61] = { RC_INVALID, WLAN_RC_PHY_HT_40_TS_HGI, 270000, + 224700, 20, 20 }, /* 270 Mb */ + [62] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_TS, 324000, + 263100, 21, 21 }, /* 324 Mb */ + [63] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_TS_HGI, 360000, + 288000, 21, 21 }, /* 360 Mb */ + [64] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_TS, 364500, + 290700, 22, 22 }, /* 364.5 Mb */ + [65] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_TS_HGI, 405000, + 317200, 22, 22 }, /* 405 Mb */ + [66] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_TS, 405000, + 317200, 23, 23 }, /* 405 Mb */ + [67] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_TS_HGI, 450000, + 346400, 23, 23 }, /* 450 Mb */ + }, + 50, /* probe interval */ + WLAN_RC_HT_FLAG, /* Phy rates allowed initially */ +}; + +/* 4ms frame limit not used for NG mode. The values filled + * for HT are the 64K max aggregate limit */ + +static const struct ath_rate_table ar5416_11ng_ratetable = { + 72, + 12, /* MCS start */ + { + [0] = { RC_ALL, WLAN_RC_PHY_CCK, 1000, + 900, 0, 2 }, /* 1 Mb */ + [1] = { RC_ALL, WLAN_RC_PHY_CCK, 2000, + 1900, 1, 4 }, /* 2 Mb */ + [2] = { RC_ALL, WLAN_RC_PHY_CCK, 5500, + 4900, 2, 11 }, /* 5.5 Mb */ + [3] = { RC_ALL, WLAN_RC_PHY_CCK, 11000, + 8100, 3, 22 }, /* 11 Mb */ + [4] = { RC_INVALID, WLAN_RC_PHY_OFDM, 6000, + 5400, 4, 12 }, /* 6 Mb */ + [5] = { RC_INVALID, WLAN_RC_PHY_OFDM, 9000, + 7800, 5, 18 }, /* 9 Mb */ + [6] = { RC_L_SDT, WLAN_RC_PHY_OFDM, 12000, + 10100, 6, 24 }, /* 12 Mb */ + [7] = { RC_L_SDT, WLAN_RC_PHY_OFDM, 18000, + 14100, 7, 36 }, /* 18 Mb */ + [8] = { RC_L_SDT, WLAN_RC_PHY_OFDM, 24000, + 17700, 8, 48 }, /* 24 Mb */ + [9] = { RC_L_SDT, WLAN_RC_PHY_OFDM, 36000, + 23700, 9, 72 }, /* 36 Mb */ + [10] = { RC_L_SDT, WLAN_RC_PHY_OFDM, 48000, + 27400, 10, 96 }, /* 48 Mb */ + [11] = { RC_L_SDT, WLAN_RC_PHY_OFDM, 54000, + 30900, 11, 108 }, /* 54 Mb */ + [12] = { RC_INVALID, WLAN_RC_PHY_HT_20_SS, 6500, + 6400, 0, 0 }, /* 6.5 Mb */ + [13] = { RC_HT_SDT_20, WLAN_RC_PHY_HT_20_SS, 13000, + 12700, 1, 1 }, /* 13 Mb */ + [14] = { RC_HT_SDT_20, WLAN_RC_PHY_HT_20_SS, 19500, + 18800, 2, 2 }, /* 19.5 Mb*/ + [15] = { RC_HT_SD_20, WLAN_RC_PHY_HT_20_SS, 26000, + 25000, 3, 3 }, /* 26 Mb */ + [16] = { RC_HT_SD_20, WLAN_RC_PHY_HT_20_SS, 39000, + 36700, 4, 4 }, /* 39 Mb */ + [17] = { RC_HT_S_20, WLAN_RC_PHY_HT_20_SS, 52000, + 48100, 5, 5 }, /* 52 Mb */ + [18] = { RC_HT_S_20, WLAN_RC_PHY_HT_20_SS, 58500, + 53500, 6, 6 }, /* 58.5 Mb */ + [19] = { RC_HT_S_20, WLAN_RC_PHY_HT_20_SS, 65000, + 59000, 7, 7 }, /* 65 Mb */ + [20] = { RC_HT_S_20, WLAN_RC_PHY_HT_20_SS_HGI, 72200, + 65400, 7, 7 }, /* 65 Mb*/ + [21] = { RC_INVALID, WLAN_RC_PHY_HT_20_DS, 13000, + 12700, 8, 8 }, /* 13 Mb */ + [22] = { RC_HT_T_20, WLAN_RC_PHY_HT_20_DS, 26000, + 24800, 9, 9 }, /* 26 Mb */ + [23] = { RC_HT_T_20, WLAN_RC_PHY_HT_20_DS, 39000, + 36600, 10, 10 }, /* 39 Mb */ + [24] = { RC_HT_DT_20, WLAN_RC_PHY_HT_20_DS, 52000, + 48100, 11, 11 }, /* 52 Mb */ + [25] = { RC_HT_DT_20, WLAN_RC_PHY_HT_20_DS, 78000, + 69500, 12, 12 }, /* 78 Mb */ + [26] = { RC_HT_DT_20, WLAN_RC_PHY_HT_20_DS, 104000, + 89500, 13, 13 }, /* 104 Mb */ + [27] = { RC_HT_DT_20, WLAN_RC_PHY_HT_20_DS, 117000, + 98900, 14, 14 }, /* 117 Mb */ + [28] = { RC_HT_DT_20, WLAN_RC_PHY_HT_20_DS, 130000, + 108300, 15, 15 }, /* 130 Mb */ + [29] = { RC_HT_DT_20, WLAN_RC_PHY_HT_20_DS_HGI, 144400, + 120000, 15, 15 }, /* 144.4 Mb */ + [30] = { RC_INVALID, WLAN_RC_PHY_HT_20_TS, 19500, + 17400, 16, 16 }, /* 19.5 Mb */ + [31] = { RC_INVALID, WLAN_RC_PHY_HT_20_TS, 39000, + 35100, 17, 17 }, /* 39 Mb */ + [32] = { RC_INVALID, WLAN_RC_PHY_HT_20_TS, 58500, + 52600, 18, 18 }, /* 58.5 Mb */ + [33] = { RC_INVALID, WLAN_RC_PHY_HT_20_TS, 78000, + 70400, 19, 19 }, /* 78 Mb */ + [34] = { RC_INVALID, WLAN_RC_PHY_HT_20_TS, 117000, + 104900, 20, 20 }, /* 117 Mb */ + [35] = { RC_INVALID, WLAN_RC_PHY_HT_20_TS_HGI, 130000, + 115800, 20, 20 }, /* 130 Mb */ + [36] = { RC_HT_T_20, WLAN_RC_PHY_HT_20_TS, 156000, + 137200, 21, 21 }, /* 156 Mb */ + [37] = { RC_HT_T_20, WLAN_RC_PHY_HT_20_TS_HGI, 173300, + 151100, 21, 21 }, /* 173.3 Mb */ + [38] = { RC_HT_T_20, WLAN_RC_PHY_HT_20_TS, 175500, + 152800, 22, 22 }, /* 175.5 Mb */ + [39] = { RC_HT_T_20, WLAN_RC_PHY_HT_20_TS_HGI, 195000, + 168400, 22, 22 }, /* 195 Mb */ + [40] = { RC_HT_T_20, WLAN_RC_PHY_HT_20_TS, 195000, + 168400, 23, 23 }, /* 195 Mb */ + [41] = { RC_HT_T_20, WLAN_RC_PHY_HT_20_TS_HGI, 216700, + 185000, 23, 23 }, /* 216.7 Mb */ + [42] = { RC_HT_SDT_40, WLAN_RC_PHY_HT_40_SS, 13500, + 13200, 0, 0 }, /* 13.5 Mb */ + [43] = { RC_HT_SDT_40, WLAN_RC_PHY_HT_40_SS, 27500, + 25900, 1, 1 }, /* 27.0 Mb */ + [44] = { RC_HT_SDT_40, WLAN_RC_PHY_HT_40_SS, 40500, + 38600, 2, 2 }, /* 40.5 Mb */ + [45] = { RC_HT_SD_40, WLAN_RC_PHY_HT_40_SS, 54000, + 49800, 3, 3 }, /* 54 Mb */ + [46] = { RC_HT_SD_40, WLAN_RC_PHY_HT_40_SS, 81500, + 72200, 4, 4 }, /* 81 Mb */ + [47] = { RC_HT_S_40 , WLAN_RC_PHY_HT_40_SS, 108000, + 92900, 5, 5 }, /* 108 Mb */ + [48] = { RC_HT_S_40, WLAN_RC_PHY_HT_40_SS, 121500, + 102700, 6, 6 }, /* 121.5 Mb */ + [49] = { RC_HT_S_40, WLAN_RC_PHY_HT_40_SS, 135000, + 112000, 7, 7 }, /* 135 Mb */ + [50] = { RC_HT_S_40, WLAN_RC_PHY_HT_40_SS_HGI, 150000, + 122000, 7, 7 }, /* 150 Mb */ + [51] = { RC_INVALID, WLAN_RC_PHY_HT_40_DS, 27000, + 25800, 8, 8 }, /* 27 Mb */ + [52] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_DS, 54000, + 49800, 9, 9 }, /* 54 Mb */ + [53] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_DS, 81000, + 71900, 10, 10 }, /* 81 Mb */ + [54] = { RC_HT_DT_40, WLAN_RC_PHY_HT_40_DS, 108000, + 92500, 11, 11 }, /* 108 Mb */ + [55] = { RC_HT_DT_40, WLAN_RC_PHY_HT_40_DS, 162000, + 130300, 12, 12 }, /* 162 Mb */ + [56] = { RC_HT_DT_40, WLAN_RC_PHY_HT_40_DS, 216000, + 162800, 13, 13 }, /* 216 Mb */ + [57] = { RC_HT_DT_40, WLAN_RC_PHY_HT_40_DS, 243000, + 178200, 14, 14 }, /* 243 Mb */ + [58] = { RC_HT_DT_40, WLAN_RC_PHY_HT_40_DS, 270000, + 192100, 15, 15 }, /* 270 Mb */ + [59] = { RC_HT_DT_40, WLAN_RC_PHY_HT_40_DS_HGI, 300000, + 207000, 15, 15 }, /* 300 Mb */ + [60] = { RC_INVALID, WLAN_RC_PHY_HT_40_TS, 40500, + 36100, 16, 16 }, /* 40.5 Mb */ + [61] = { RC_INVALID, WLAN_RC_PHY_HT_40_TS, 81000, + 72900, 17, 17 }, /* 81 Mb */ + [62] = { RC_INVALID, WLAN_RC_PHY_HT_40_TS, 121500, + 108300, 18, 18 }, /* 121.5 Mb */ + [63] = { RC_INVALID, WLAN_RC_PHY_HT_40_TS, 162000, + 142000, 19, 19 }, /* 162 Mb */ + [64] = { RC_INVALID, WLAN_RC_PHY_HT_40_TS, 243000, + 205100, 20, 20 }, /* 243 Mb */ + [65] = { RC_INVALID, WLAN_RC_PHY_HT_40_TS_HGI, 270000, + 224700, 20, 20 }, /* 270 Mb */ + [66] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_TS, 324000, + 263100, 21, 21 }, /* 324 Mb */ + [67] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_TS_HGI, 360000, + 288000, 21, 21 }, /* 360 Mb */ + [68] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_TS, 364500, + 290700, 22, 22 }, /* 364.5 Mb */ + [69] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_TS_HGI, 405000, + 317200, 22, 22 }, /* 405 Mb */ + [70] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_TS, 405000, + 317200, 23, 23 }, /* 405 Mb */ + [71] = { RC_HT_T_40, WLAN_RC_PHY_HT_40_TS_HGI, 450000, + 346400, 23, 23 }, /* 450 Mb */ + }, + 50, /* probe interval */ + WLAN_RC_HT_FLAG, /* Phy rates allowed initially */ +}; + +static const struct ath_rate_table ar5416_11a_ratetable = { + 8, + 0, + { + { RC_L_SDT, WLAN_RC_PHY_OFDM, 6000, /* 6 Mb */ + 5400, 0, 12}, + { RC_L_SDT, WLAN_RC_PHY_OFDM, 9000, /* 9 Mb */ + 7800, 1, 18}, + { RC_L_SDT, WLAN_RC_PHY_OFDM, 12000, /* 12 Mb */ + 10000, 2, 24}, + { RC_L_SDT, WLAN_RC_PHY_OFDM, 18000, /* 18 Mb */ + 13900, 3, 36}, + { RC_L_SDT, WLAN_RC_PHY_OFDM, 24000, /* 24 Mb */ + 17300, 4, 48}, + { RC_L_SDT, WLAN_RC_PHY_OFDM, 36000, /* 36 Mb */ + 23000, 5, 72}, + { RC_L_SDT, WLAN_RC_PHY_OFDM, 48000, /* 48 Mb */ + 27400, 6, 96}, + { RC_L_SDT, WLAN_RC_PHY_OFDM, 54000, /* 54 Mb */ + 29300, 7, 108}, + }, + 50, /* probe interval */ + 0, /* Phy rates allowed initially */ +}; + +static const struct ath_rate_table ar5416_11g_ratetable = { + 12, + 0, + { + { RC_L_SDT, WLAN_RC_PHY_CCK, 1000, /* 1 Mb */ + 900, 0, 2}, + { RC_L_SDT, WLAN_RC_PHY_CCK, 2000, /* 2 Mb */ + 1900, 1, 4}, + { RC_L_SDT, WLAN_RC_PHY_CCK, 5500, /* 5.5 Mb */ + 4900, 2, 11}, + { RC_L_SDT, WLAN_RC_PHY_CCK, 11000, /* 11 Mb */ + 8100, 3, 22}, + { RC_INVALID, WLAN_RC_PHY_OFDM, 6000, /* 6 Mb */ + 5400, 4, 12}, + { RC_INVALID, WLAN_RC_PHY_OFDM, 9000, /* 9 Mb */ + 7800, 5, 18}, + { RC_L_SDT, WLAN_RC_PHY_OFDM, 12000, /* 12 Mb */ + 10000, 6, 24}, + { RC_L_SDT, WLAN_RC_PHY_OFDM, 18000, /* 18 Mb */ + 13900, 7, 36}, + { RC_L_SDT, WLAN_RC_PHY_OFDM, 24000, /* 24 Mb */ + 17300, 8, 48}, + { RC_L_SDT, WLAN_RC_PHY_OFDM, 36000, /* 36 Mb */ + 23000, 9, 72}, + { RC_L_SDT, WLAN_RC_PHY_OFDM, 48000, /* 48 Mb */ + 27400, 10, 96}, + { RC_L_SDT, WLAN_RC_PHY_OFDM, 54000, /* 54 Mb */ + 29300, 11, 108}, + }, + 50, /* probe interval */ + 0, /* Phy rates allowed initially */ +}; + +static int ath_rc_get_rateindex(struct ath_rate_priv *ath_rc_priv, + struct ieee80211_tx_rate *rate) +{ + const struct ath_rate_table *rate_table = ath_rc_priv->rate_table; + int rix, i, idx = 0; + + if (!(rate->flags & IEEE80211_TX_RC_MCS)) + return rate->idx; + + for (i = 0; i < ath_rc_priv->max_valid_rate; i++) { + idx = ath_rc_priv->valid_rate_index[i]; + + if (WLAN_RC_PHY_HT(rate_table->info[idx].phy) && + rate_table->info[idx].ratecode == rate->idx) + break; + } + + rix = idx; + + if (rate->flags & IEEE80211_TX_RC_SHORT_GI) + rix++; + + return rix; +} + +static void ath_rc_sort_validrates(struct ath_rate_priv *ath_rc_priv) +{ + const struct ath_rate_table *rate_table = ath_rc_priv->rate_table; + u8 i, j, idx, idx_next; + + for (i = ath_rc_priv->max_valid_rate - 1; i > 0; i--) { + for (j = 0; j <= i-1; j++) { + idx = ath_rc_priv->valid_rate_index[j]; + idx_next = ath_rc_priv->valid_rate_index[j+1]; + + if (rate_table->info[idx].ratekbps > + rate_table->info[idx_next].ratekbps) { + ath_rc_priv->valid_rate_index[j] = idx_next; + ath_rc_priv->valid_rate_index[j+1] = idx; + } + } + } +} + +static inline +int ath_rc_get_nextvalid_txrate(const struct ath_rate_table *rate_table, + struct ath_rate_priv *ath_rc_priv, + u8 cur_valid_txrate, + u8 *next_idx) +{ + u8 i; + + for (i = 0; i < ath_rc_priv->max_valid_rate - 1; i++) { + if (ath_rc_priv->valid_rate_index[i] == cur_valid_txrate) { + *next_idx = ath_rc_priv->valid_rate_index[i+1]; + return 1; + } + } + + /* No more valid rates */ + *next_idx = 0; + + return 0; +} + +/* Return true only for single stream */ + +static int ath_rc_valid_phyrate(u32 phy, u32 capflag, int ignore_cw) +{ + if (WLAN_RC_PHY_HT(phy) && !(capflag & WLAN_RC_HT_FLAG)) + return 0; + if (WLAN_RC_PHY_DS(phy) && !(capflag & WLAN_RC_DS_FLAG)) + return 0; + if (WLAN_RC_PHY_TS(phy) && !(capflag & WLAN_RC_TS_FLAG)) + return 0; + if (WLAN_RC_PHY_SGI(phy) && !(capflag & WLAN_RC_SGI_FLAG)) + return 0; + if (!ignore_cw && WLAN_RC_PHY_HT(phy)) + if (WLAN_RC_PHY_40(phy) && !(capflag & WLAN_RC_40_FLAG)) + return 0; + return 1; +} + +static inline int +ath_rc_get_lower_rix(struct ath_rate_priv *ath_rc_priv, + u8 cur_valid_txrate, u8 *next_idx) +{ + int8_t i; + + for (i = 1; i < ath_rc_priv->max_valid_rate ; i++) { + if (ath_rc_priv->valid_rate_index[i] == cur_valid_txrate) { + *next_idx = ath_rc_priv->valid_rate_index[i-1]; + return 1; + } + } + + return 0; +} + +static u8 ath_rc_init_validrates(struct ath_rate_priv *ath_rc_priv) +{ + const struct ath_rate_table *rate_table = ath_rc_priv->rate_table; + u8 i, hi = 0; + + for (i = 0; i < rate_table->rate_cnt; i++) { + if (rate_table->info[i].rate_flags & RC_LEGACY) { + u32 phy = rate_table->info[i].phy; + u8 valid_rate_count = 0; + + if (!ath_rc_valid_phyrate(phy, ath_rc_priv->ht_cap, 0)) + continue; + + valid_rate_count = ath_rc_priv->valid_phy_ratecnt[phy]; + + ath_rc_priv->valid_phy_rateidx[phy][valid_rate_count] = i; + ath_rc_priv->valid_phy_ratecnt[phy] += 1; + ath_rc_priv->valid_rate_index[i] = true; + hi = i; + } + } + + return hi; +} + +static inline bool ath_rc_check_legacy(u8 rate, u8 dot11rate, u16 rate_flags, + u32 phy, u32 capflag) +{ + if (rate != dot11rate || WLAN_RC_PHY_HT(phy)) + return false; + + if ((rate_flags & WLAN_RC_CAP_MODE(capflag)) != WLAN_RC_CAP_MODE(capflag)) + return false; + + if (!(rate_flags & WLAN_RC_CAP_STREAM(capflag))) + return false; + + return true; +} + +static inline bool ath_rc_check_ht(u8 rate, u8 dot11rate, u16 rate_flags, + u32 phy, u32 capflag) +{ + if (rate != dot11rate || !WLAN_RC_PHY_HT(phy)) + return false; + + if (!WLAN_RC_PHY_HT_VALID(rate_flags, capflag)) + return false; + + if (!(rate_flags & WLAN_RC_CAP_STREAM(capflag))) + return false; + + return true; +} + +static u8 ath_rc_setvalid_rates(struct ath_rate_priv *ath_rc_priv, bool legacy) +{ + const struct ath_rate_table *rate_table = ath_rc_priv->rate_table; + struct ath_rateset *rateset; + u32 phy, capflag = ath_rc_priv->ht_cap; + u16 rate_flags; + u8 i, j, hi = 0, rate, dot11rate, valid_rate_count; + + if (legacy) + rateset = &ath_rc_priv->neg_rates; + else + rateset = &ath_rc_priv->neg_ht_rates; + + for (i = 0; i < rateset->rs_nrates; i++) { + for (j = 0; j < rate_table->rate_cnt; j++) { + phy = rate_table->info[j].phy; + rate_flags = rate_table->info[j].rate_flags; + rate = rateset->rs_rates[i]; + dot11rate = rate_table->info[j].dot11rate; + + if (legacy && + !ath_rc_check_legacy(rate, dot11rate, + rate_flags, phy, capflag)) + continue; + + if (!legacy && + !ath_rc_check_ht(rate, dot11rate, + rate_flags, phy, capflag)) + continue; + + if (!ath_rc_valid_phyrate(phy, capflag, 0)) + continue; + + valid_rate_count = ath_rc_priv->valid_phy_ratecnt[phy]; + ath_rc_priv->valid_phy_rateidx[phy][valid_rate_count] = j; + ath_rc_priv->valid_phy_ratecnt[phy] += 1; + ath_rc_priv->valid_rate_index[j] = true; + hi = max(hi, j); + } + } + + return hi; +} + +static u8 ath_rc_get_highest_rix(struct ath_rate_priv *ath_rc_priv, + int *is_probing) +{ + const struct ath_rate_table *rate_table = ath_rc_priv->rate_table; + u32 best_thruput, this_thruput, now_msec; + u8 rate, next_rate, best_rate, maxindex, minindex; + int8_t index = 0; + + now_msec = jiffies_to_msecs(jiffies); + *is_probing = 0; + best_thruput = 0; + maxindex = ath_rc_priv->max_valid_rate-1; + minindex = 0; + best_rate = minindex; + + /* + * Try the higher rate first. It will reduce memory moving time + * if we have very good channel characteristics. + */ + for (index = maxindex; index >= minindex ; index--) { + u8 per_thres; + + rate = ath_rc_priv->valid_rate_index[index]; + if (rate > ath_rc_priv->rate_max_phy) + continue; + + /* + * For TCP the average collision rate is around 11%, + * so we ignore PERs less than this. This is to + * prevent the rate we are currently using (whose + * PER might be in the 10-15 range because of TCP + * collisions) looking worse than the next lower + * rate whose PER has decayed close to 0. If we + * used to next lower rate, its PER would grow to + * 10-15 and we would be worse off then staying + * at the current rate. + */ + per_thres = ath_rc_priv->per[rate]; + if (per_thres < 12) + per_thres = 12; + + this_thruput = rate_table->info[rate].user_ratekbps * + (100 - per_thres); + + if (best_thruput <= this_thruput) { + best_thruput = this_thruput; + best_rate = rate; + } + } + + rate = best_rate; + + /* + * Must check the actual rate (ratekbps) to account for + * non-monoticity of 11g's rate table + */ + + if (rate >= ath_rc_priv->rate_max_phy) { + rate = ath_rc_priv->rate_max_phy; + + /* Probe the next allowed phy state */ + if (ath_rc_get_nextvalid_txrate(rate_table, + ath_rc_priv, rate, &next_rate) && + (now_msec - ath_rc_priv->probe_time > + rate_table->probe_interval) && + (ath_rc_priv->hw_maxretry_pktcnt >= 1)) { + rate = next_rate; + ath_rc_priv->probe_rate = rate; + ath_rc_priv->probe_time = now_msec; + ath_rc_priv->hw_maxretry_pktcnt = 0; + *is_probing = 1; + } + } + + if (rate > (ath_rc_priv->rate_table_size - 1)) + rate = ath_rc_priv->rate_table_size - 1; + + if (RC_TS_ONLY(rate_table->info[rate].rate_flags) && + (ath_rc_priv->ht_cap & WLAN_RC_TS_FLAG)) + return rate; + + if (RC_DS_OR_LATER(rate_table->info[rate].rate_flags) && + (ath_rc_priv->ht_cap & (WLAN_RC_DS_FLAG | WLAN_RC_TS_FLAG))) + return rate; + + if (RC_SS_OR_LEGACY(rate_table->info[rate].rate_flags)) + return rate; + + /* This should not happen */ + WARN_ON_ONCE(1); + + rate = ath_rc_priv->valid_rate_index[0]; + + return rate; +} + +static void ath_rc_rate_set_series(const struct ath_rate_table *rate_table, + struct ieee80211_tx_rate *rate, + struct ieee80211_tx_rate_control *txrc, + u8 tries, u8 rix, int rtsctsenable) +{ + rate->count = tries; + rate->idx = rate_table->info[rix].ratecode; + + if (txrc->rts || rtsctsenable) + rate->flags |= IEEE80211_TX_RC_USE_RTS_CTS; + + if (WLAN_RC_PHY_HT(rate_table->info[rix].phy)) { + rate->flags |= IEEE80211_TX_RC_MCS; + if (WLAN_RC_PHY_40(rate_table->info[rix].phy) && + conf_is_ht40(&txrc->hw->conf)) + rate->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; + if (WLAN_RC_PHY_SGI(rate_table->info[rix].phy)) + rate->flags |= IEEE80211_TX_RC_SHORT_GI; + } +} + +static void ath_rc_rate_set_rtscts(struct ath_softc *sc, + const struct ath_rate_table *rate_table, + struct ieee80211_tx_info *tx_info) +{ + struct ieee80211_bss_conf *bss_conf; + + if (!tx_info->control.vif) + return; + /* + * For legacy frames, mac80211 takes care of CTS protection. + */ + if (!(tx_info->control.rates[0].flags & IEEE80211_TX_RC_MCS)) + return; + + bss_conf = &tx_info->control.vif->bss_conf; + + if (!bss_conf->basic_rates) + return; + + /* + * For now, use the lowest allowed basic rate for HT frames. + */ + tx_info->control.rts_cts_rate_idx = __ffs(bss_conf->basic_rates); +} + +static void ath_get_rate(void *priv, struct ieee80211_sta *sta, void *priv_sta, + struct ieee80211_tx_rate_control *txrc) +{ + struct ath_softc *sc = priv; + struct ath_rate_priv *ath_rc_priv = priv_sta; + const struct ath_rate_table *rate_table; + struct sk_buff *skb = txrc->skb; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_tx_rate *rates = tx_info->control.rates; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + __le16 fc = hdr->frame_control; + u8 try_per_rate, i = 0, rix; + int is_probe = 0; + + if (rate_control_send_low(sta, priv_sta, txrc)) + return; + + /* + * For Multi Rate Retry we use a different number of + * retry attempt counts. This ends up looking like this: + * + * MRR[0] = 4 + * MRR[1] = 4 + * MRR[2] = 4 + * MRR[3] = 8 + * + */ + try_per_rate = 4; + + rate_table = ath_rc_priv->rate_table; + rix = ath_rc_get_highest_rix(ath_rc_priv, &is_probe); + + if (conf_is_ht(&sc->hw->conf) && + (sta->ht_cap.cap & IEEE80211_HT_CAP_LDPC_CODING)) + tx_info->flags |= IEEE80211_TX_CTL_LDPC; + + if (conf_is_ht(&sc->hw->conf) && + (sta->ht_cap.cap & IEEE80211_HT_CAP_TX_STBC)) + tx_info->flags |= (1 << IEEE80211_TX_CTL_STBC_SHIFT); + + if (is_probe) { + /* + * Set one try for probe rates. For the + * probes don't enable RTS. + */ + ath_rc_rate_set_series(rate_table, &rates[i++], txrc, + 1, rix, 0); + /* + * Get the next tried/allowed rate. + * No RTS for the next series after the probe rate. + */ + ath_rc_get_lower_rix(ath_rc_priv, rix, &rix); + ath_rc_rate_set_series(rate_table, &rates[i++], txrc, + try_per_rate, rix, 0); + + tx_info->flags |= IEEE80211_TX_CTL_RATE_CTRL_PROBE; + } else { + /* + * Set the chosen rate. No RTS for first series entry. + */ + ath_rc_rate_set_series(rate_table, &rates[i++], txrc, + try_per_rate, rix, 0); + } + + for ( ; i < 4; i++) { + /* + * Use twice the number of tries for the last MRR segment. + */ + if (i + 1 == 4) + try_per_rate = 8; + + ath_rc_get_lower_rix(ath_rc_priv, rix, &rix); + + /* + * All other rates in the series have RTS enabled. + */ + ath_rc_rate_set_series(rate_table, &rates[i], txrc, + try_per_rate, rix, 1); + } + + /* + * NB:Change rate series to enable aggregation when operating + * at lower MCS rates. When first rate in series is MCS2 + * in HT40 @ 2.4GHz, series should look like: + * + * {MCS2, MCS1, MCS0, MCS0}. + * + * When first rate in series is MCS3 in HT20 @ 2.4GHz, series should + * look like: + * + * {MCS3, MCS2, MCS1, MCS1} + * + * So, set fourth rate in series to be same as third one for + * above conditions. + */ + if ((sc->hw->conf.chandef.chan->band == IEEE80211_BAND_2GHZ) && + (conf_is_ht(&sc->hw->conf))) { + u8 dot11rate = rate_table->info[rix].dot11rate; + u8 phy = rate_table->info[rix].phy; + if (i == 4 && + ((dot11rate == 2 && phy == WLAN_RC_PHY_HT_40_SS) || + (dot11rate == 3 && phy == WLAN_RC_PHY_HT_20_SS))) { + rates[3].idx = rates[2].idx; + rates[3].flags = rates[2].flags; + } + } + + /* + * Force hardware to use computed duration for next + * fragment by disabling multi-rate retry, which + * updates duration based on the multi-rate duration table. + * + * FIXME: Fix duration + */ + if (ieee80211_has_morefrags(fc) || + (le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_FRAG)) { + rates[1].count = rates[2].count = rates[3].count = 0; + rates[1].idx = rates[2].idx = rates[3].idx = 0; + rates[0].count = ATH_TXMAXTRY; + } + + ath_rc_rate_set_rtscts(sc, rate_table, tx_info); +} + +static void ath_rc_update_per(struct ath_softc *sc, + const struct ath_rate_table *rate_table, + struct ath_rate_priv *ath_rc_priv, + struct ieee80211_tx_info *tx_info, + int tx_rate, int xretries, int retries, + u32 now_msec) +{ + int count, n_bad_frames; + u8 last_per; + static const u32 nretry_to_per_lookup[10] = { + 100 * 0 / 1, + 100 * 1 / 4, + 100 * 1 / 2, + 100 * 3 / 4, + 100 * 4 / 5, + 100 * 5 / 6, + 100 * 6 / 7, + 100 * 7 / 8, + 100 * 8 / 9, + 100 * 9 / 10 + }; + + last_per = ath_rc_priv->per[tx_rate]; + n_bad_frames = tx_info->status.ampdu_len - tx_info->status.ampdu_ack_len; + + if (xretries) { + if (xretries == 1) { + ath_rc_priv->per[tx_rate] += 30; + if (ath_rc_priv->per[tx_rate] > 100) + ath_rc_priv->per[tx_rate] = 100; + } else { + /* xretries == 2 */ + count = ARRAY_SIZE(nretry_to_per_lookup); + if (retries >= count) + retries = count - 1; + + /* new_PER = 7/8*old_PER + 1/8*(currentPER) */ + ath_rc_priv->per[tx_rate] = + (u8)(last_per - (last_per >> 3) + (100 >> 3)); + } + + /* xretries == 1 or 2 */ + + if (ath_rc_priv->probe_rate == tx_rate) + ath_rc_priv->probe_rate = 0; + + } else { /* xretries == 0 */ + count = ARRAY_SIZE(nretry_to_per_lookup); + if (retries >= count) + retries = count - 1; + + if (n_bad_frames) { + /* new_PER = 7/8*old_PER + 1/8*(currentPER) + * Assuming that n_frames is not 0. The current PER + * from the retries is 100 * retries / (retries+1), + * since the first retries attempts failed, and the + * next one worked. For the one that worked, + * n_bad_frames subframes out of n_frames wored, + * so the PER for that part is + * 100 * n_bad_frames / n_frames, and it contributes + * 100 * n_bad_frames / (n_frames * (retries+1)) to + * the above PER. The expression below is a + * simplified version of the sum of these two terms. + */ + if (tx_info->status.ampdu_len > 0) { + int n_frames, n_bad_tries; + u8 cur_per, new_per; + + n_bad_tries = retries * tx_info->status.ampdu_len + + n_bad_frames; + n_frames = tx_info->status.ampdu_len * (retries + 1); + cur_per = (100 * n_bad_tries / n_frames) >> 3; + new_per = (u8)(last_per - (last_per >> 3) + cur_per); + ath_rc_priv->per[tx_rate] = new_per; + } + } else { + ath_rc_priv->per[tx_rate] = + (u8)(last_per - (last_per >> 3) + + (nretry_to_per_lookup[retries] >> 3)); + } + + + /* + * If we got at most one retry then increase the max rate if + * this was a probe. Otherwise, ignore the probe. + */ + if (ath_rc_priv->probe_rate && ath_rc_priv->probe_rate == tx_rate) { + if (retries > 0 || 2 * n_bad_frames > tx_info->status.ampdu_len) { + /* + * Since we probed with just a single attempt, + * any retries means the probe failed. Also, + * if the attempt worked, but more than half + * the subframes were bad then also consider + * the probe a failure. + */ + ath_rc_priv->probe_rate = 0; + } else { + u8 probe_rate = 0; + + ath_rc_priv->rate_max_phy = + ath_rc_priv->probe_rate; + probe_rate = ath_rc_priv->probe_rate; + + if (ath_rc_priv->per[probe_rate] > 30) + ath_rc_priv->per[probe_rate] = 20; + + ath_rc_priv->probe_rate = 0; + + /* + * Since this probe succeeded, we allow the next + * probe twice as soon. This allows the maxRate + * to move up faster if the probes are + * successful. + */ + ath_rc_priv->probe_time = + now_msec - rate_table->probe_interval / 2; + } + } + + if (retries > 0) { + /* + * Don't update anything. We don't know if + * this was because of collisions or poor signal. + */ + ath_rc_priv->hw_maxretry_pktcnt = 0; + } else { + /* + * It worked with no retries. First ignore bogus (small) + * rssi_ack values. + */ + if (tx_rate == ath_rc_priv->rate_max_phy && + ath_rc_priv->hw_maxretry_pktcnt < 255) { + ath_rc_priv->hw_maxretry_pktcnt++; + } + + } + } +} + +static void ath_rc_update_ht(struct ath_softc *sc, + struct ath_rate_priv *ath_rc_priv, + struct ieee80211_tx_info *tx_info, + int tx_rate, int xretries, int retries) +{ + u32 now_msec = jiffies_to_msecs(jiffies); + int rate; + u8 last_per; + const struct ath_rate_table *rate_table = ath_rc_priv->rate_table; + int size = ath_rc_priv->rate_table_size; + + if ((tx_rate < 0) || (tx_rate > rate_table->rate_cnt)) + return; + + last_per = ath_rc_priv->per[tx_rate]; + + /* Update PER first */ + ath_rc_update_per(sc, rate_table, ath_rc_priv, + tx_info, tx_rate, xretries, + retries, now_msec); + + /* + * If this rate looks bad (high PER) then stop using it for + * a while (except if we are probing). + */ + if (ath_rc_priv->per[tx_rate] >= 55 && tx_rate > 0 && + rate_table->info[tx_rate].ratekbps <= + rate_table->info[ath_rc_priv->rate_max_phy].ratekbps) { + ath_rc_get_lower_rix(ath_rc_priv, (u8)tx_rate, + &ath_rc_priv->rate_max_phy); + + /* Don't probe for a little while. */ + ath_rc_priv->probe_time = now_msec; + } + + /* Make sure the rates below this have lower PER */ + /* Monotonicity is kept only for rates below the current rate. */ + if (ath_rc_priv->per[tx_rate] < last_per) { + for (rate = tx_rate - 1; rate >= 0; rate--) { + + if (ath_rc_priv->per[rate] > + ath_rc_priv->per[rate+1]) { + ath_rc_priv->per[rate] = + ath_rc_priv->per[rate+1]; + } + } + } + + /* Maintain monotonicity for rates above the current rate */ + for (rate = tx_rate; rate < size - 1; rate++) { + if (ath_rc_priv->per[rate+1] < + ath_rc_priv->per[rate]) + ath_rc_priv->per[rate+1] = + ath_rc_priv->per[rate]; + } + + /* Every so often, we reduce the thresholds + * and PER (different for CCK and OFDM). */ + if (now_msec - ath_rc_priv->per_down_time >= + rate_table->probe_interval) { + for (rate = 0; rate < size; rate++) { + ath_rc_priv->per[rate] = + 7 * ath_rc_priv->per[rate] / 8; + } + + ath_rc_priv->per_down_time = now_msec; + } + + ath_debug_stat_retries(ath_rc_priv, tx_rate, xretries, retries, + ath_rc_priv->per[tx_rate]); + +} + +static void ath_rc_tx_status(struct ath_softc *sc, + struct ath_rate_priv *ath_rc_priv, + struct sk_buff *skb) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_tx_rate *rates = tx_info->status.rates; + struct ieee80211_tx_rate *rate; + int final_ts_idx = 0, xretries = 0, long_retry = 0; + u8 flags; + u32 i = 0, rix; + + for (i = 0; i < sc->hw->max_rates; i++) { + rate = &tx_info->status.rates[i]; + if (rate->idx < 0 || !rate->count) + break; + + final_ts_idx = i; + long_retry = rate->count - 1; + } + + if (!(tx_info->flags & IEEE80211_TX_STAT_ACK)) + xretries = 1; + + /* + * If the first rate is not the final index, there + * are intermediate rate failures to be processed. + */ + if (final_ts_idx != 0) { + for (i = 0; i < final_ts_idx ; i++) { + if (rates[i].count != 0 && (rates[i].idx >= 0)) { + flags = rates[i].flags; + + /* If HT40 and we have switched mode from + * 40 to 20 => don't update */ + + if ((flags & IEEE80211_TX_RC_40_MHZ_WIDTH) && + !(ath_rc_priv->ht_cap & WLAN_RC_40_FLAG)) + return; + + rix = ath_rc_get_rateindex(ath_rc_priv, &rates[i]); + ath_rc_update_ht(sc, ath_rc_priv, tx_info, + rix, xretries ? 1 : 2, + rates[i].count); + } + } + } + + flags = rates[final_ts_idx].flags; + + /* If HT40 and we have switched mode from 40 to 20 => don't update */ + if ((flags & IEEE80211_TX_RC_40_MHZ_WIDTH) && + !(ath_rc_priv->ht_cap & WLAN_RC_40_FLAG)) + return; + + rix = ath_rc_get_rateindex(ath_rc_priv, &rates[final_ts_idx]); + ath_rc_update_ht(sc, ath_rc_priv, tx_info, rix, xretries, long_retry); + ath_debug_stat_rc(ath_rc_priv, rix); +} + +static const +struct ath_rate_table *ath_choose_rate_table(struct ath_softc *sc, + enum ieee80211_band band, + bool is_ht) +{ + switch(band) { + case IEEE80211_BAND_2GHZ: + if (is_ht) + return &ar5416_11ng_ratetable; + return &ar5416_11g_ratetable; + case IEEE80211_BAND_5GHZ: + if (is_ht) + return &ar5416_11na_ratetable; + return &ar5416_11a_ratetable; + default: + return NULL; + } +} + +static void ath_rc_init(struct ath_softc *sc, + struct ath_rate_priv *ath_rc_priv) +{ + const struct ath_rate_table *rate_table = ath_rc_priv->rate_table; + struct ath_rateset *rateset = &ath_rc_priv->neg_rates; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + u8 i, j, k, hi = 0, hthi = 0; + + ath_rc_priv->rate_table_size = RATE_TABLE_SIZE; + + for (i = 0 ; i < ath_rc_priv->rate_table_size; i++) { + ath_rc_priv->per[i] = 0; + ath_rc_priv->valid_rate_index[i] = 0; + } + + for (i = 0; i < WLAN_RC_PHY_MAX; i++) { + for (j = 0; j < RATE_TABLE_SIZE; j++) + ath_rc_priv->valid_phy_rateidx[i][j] = 0; + ath_rc_priv->valid_phy_ratecnt[i] = 0; + } + + if (!rateset->rs_nrates) { + hi = ath_rc_init_validrates(ath_rc_priv); + } else { + hi = ath_rc_setvalid_rates(ath_rc_priv, true); + + if (ath_rc_priv->ht_cap & WLAN_RC_HT_FLAG) + hthi = ath_rc_setvalid_rates(ath_rc_priv, false); + + hi = max(hi, hthi); + } + + ath_rc_priv->rate_table_size = hi + 1; + ath_rc_priv->rate_max_phy = 0; + WARN_ON(ath_rc_priv->rate_table_size > RATE_TABLE_SIZE); + + for (i = 0, k = 0; i < WLAN_RC_PHY_MAX; i++) { + for (j = 0; j < ath_rc_priv->valid_phy_ratecnt[i]; j++) { + ath_rc_priv->valid_rate_index[k++] = + ath_rc_priv->valid_phy_rateidx[i][j]; + } + + if (!ath_rc_valid_phyrate(i, rate_table->initial_ratemax, 1) || + !ath_rc_priv->valid_phy_ratecnt[i]) + continue; + + ath_rc_priv->rate_max_phy = ath_rc_priv->valid_phy_rateidx[i][j-1]; + } + WARN_ON(ath_rc_priv->rate_table_size > RATE_TABLE_SIZE); + WARN_ON(k > RATE_TABLE_SIZE); + + ath_rc_priv->max_valid_rate = k; + ath_rc_sort_validrates(ath_rc_priv); + ath_rc_priv->rate_max_phy = (k > 4) ? + ath_rc_priv->valid_rate_index[k-4] : + ath_rc_priv->valid_rate_index[k-1]; + + ath_dbg(common, CONFIG, "RC Initialized with capabilities: 0x%x\n", + ath_rc_priv->ht_cap); +} + +static u8 ath_rc_build_ht_caps(struct ath_softc *sc, struct ieee80211_sta *sta) +{ + u8 caps = 0; + + if (sta->ht_cap.ht_supported) { + caps = WLAN_RC_HT_FLAG; + if (sta->ht_cap.mcs.rx_mask[1] && sta->ht_cap.mcs.rx_mask[2]) + caps |= WLAN_RC_TS_FLAG | WLAN_RC_DS_FLAG; + else if (sta->ht_cap.mcs.rx_mask[1]) + caps |= WLAN_RC_DS_FLAG; + if (sta->bandwidth >= IEEE80211_STA_RX_BW_40) { + caps |= WLAN_RC_40_FLAG; + if (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_40) + caps |= WLAN_RC_SGI_FLAG; + } else { + if (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_20) + caps |= WLAN_RC_SGI_FLAG; + } + } + + return caps; +} + +static bool ath_tx_aggr_check(struct ath_softc *sc, struct ieee80211_sta *sta, + u8 tidno) +{ + struct ath_node *an = (struct ath_node *)sta->drv_priv; + struct ath_atx_tid *txtid; + + if (!sta->ht_cap.ht_supported) + return false; + + txtid = ATH_AN_2_TID(an, tidno); + return !txtid->active; +} + + +/***********************************/ +/* mac80211 Rate Control callbacks */ +/***********************************/ + +static void ath_tx_status(void *priv, struct ieee80211_supported_band *sband, + struct ieee80211_sta *sta, void *priv_sta, + struct sk_buff *skb) +{ + struct ath_softc *sc = priv; + struct ath_rate_priv *ath_rc_priv = priv_sta; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + __le16 fc = hdr->frame_control; + + if (!priv_sta || !ieee80211_is_data(fc)) + return; + + /* This packet was aggregated but doesn't carry status info */ + if ((tx_info->flags & IEEE80211_TX_CTL_AMPDU) && + !(tx_info->flags & IEEE80211_TX_STAT_AMPDU)) + return; + + if (tx_info->flags & IEEE80211_TX_STAT_TX_FILTERED) + return; + + ath_rc_tx_status(sc, ath_rc_priv, skb); + + /* Check if aggregation has to be enabled for this tid */ + if (conf_is_ht(&sc->hw->conf) && + !(skb->protocol == cpu_to_be16(ETH_P_PAE))) { + if (ieee80211_is_data_qos(fc) && + skb_get_queue_mapping(skb) != IEEE80211_AC_VO) { + u8 *qc, tid; + + qc = ieee80211_get_qos_ctl(hdr); + tid = qc[0] & 0xf; + + if(ath_tx_aggr_check(sc, sta, tid)) + ieee80211_start_tx_ba_session(sta, tid, 0); + } + } +} + +static void ath_rate_init(void *priv, struct ieee80211_supported_band *sband, + struct ieee80211_sta *sta, void *priv_sta) +{ + struct ath_softc *sc = priv; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_rate_priv *ath_rc_priv = priv_sta; + int i, j = 0; + + for (i = 0; i < sband->n_bitrates; i++) { + if (sta->supp_rates[sband->band] & BIT(i)) { + ath_rc_priv->neg_rates.rs_rates[j] + = (sband->bitrates[i].bitrate * 2) / 10; + j++; + } + } + ath_rc_priv->neg_rates.rs_nrates = j; + + if (sta->ht_cap.ht_supported) { + for (i = 0, j = 0; i < 77; i++) { + if (sta->ht_cap.mcs.rx_mask[i/8] & (1<<(i%8))) + ath_rc_priv->neg_ht_rates.rs_rates[j++] = i; + if (j == ATH_RATE_MAX) + break; + } + ath_rc_priv->neg_ht_rates.rs_nrates = j; + } + + ath_rc_priv->rate_table = ath_choose_rate_table(sc, sband->band, + sta->ht_cap.ht_supported); + if (!ath_rc_priv->rate_table) { + ath_err(common, "No rate table chosen\n"); + return; + } + + ath_rc_priv->ht_cap = ath_rc_build_ht_caps(sc, sta); + ath_rc_init(sc, priv_sta); +} + +static void ath_rate_update(void *priv, struct ieee80211_supported_band *sband, + struct ieee80211_sta *sta, void *priv_sta, + u32 changed) +{ + struct ath_softc *sc = priv; + struct ath_rate_priv *ath_rc_priv = priv_sta; + + if (changed & IEEE80211_RC_BW_CHANGED) { + ath_rc_priv->ht_cap = ath_rc_build_ht_caps(sc, sta); + ath_rc_init(sc, priv_sta); + + ath_dbg(ath9k_hw_common(sc->sc_ah), CONFIG, + "Operating HT Bandwidth changed to: %d\n", + cfg80211_get_chandef_type(&sc->hw->conf.chandef)); + } +} + +#if defined(CPTCFG_MAC80211_DEBUGFS) && defined(CPTCFG_ATH9K_DEBUGFS) + +void ath_debug_stat_rc(struct ath_rate_priv *rc, int final_rate) +{ + struct ath_rc_stats *stats; + + stats = &rc->rcstats[final_rate]; + stats->success++; +} + +void ath_debug_stat_retries(struct ath_rate_priv *rc, int rix, + int xretries, int retries, u8 per) +{ + struct ath_rc_stats *stats = &rc->rcstats[rix]; + + stats->xretries += xretries; + stats->retries += retries; + stats->per = per; +} + +static ssize_t read_file_rcstat(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ath_rate_priv *rc = file->private_data; + char *buf; + unsigned int len = 0, max; + int rix; + ssize_t retval; + + if (rc->rate_table == NULL) + return 0; + + max = 80 + rc->rate_table_size * 1024 + 1; + buf = kmalloc(max, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + len += sprintf(buf, "%6s %6s %6s " + "%10s %10s %10s %10s\n", + "HT", "MCS", "Rate", + "Success", "Retries", "XRetries", "PER"); + + for (rix = 0; rix < rc->max_valid_rate; rix++) { + u8 i = rc->valid_rate_index[rix]; + u32 ratekbps = rc->rate_table->info[i].ratekbps; + struct ath_rc_stats *stats = &rc->rcstats[i]; + char mcs[5]; + char htmode[5]; + int used_mcs = 0, used_htmode = 0; + + if (WLAN_RC_PHY_HT(rc->rate_table->info[i].phy)) { + used_mcs = snprintf(mcs, 5, "%d", + rc->rate_table->info[i].ratecode); + + if (WLAN_RC_PHY_40(rc->rate_table->info[i].phy)) + used_htmode = snprintf(htmode, 5, "HT40"); + else if (WLAN_RC_PHY_20(rc->rate_table->info[i].phy)) + used_htmode = snprintf(htmode, 5, "HT20"); + else + used_htmode = snprintf(htmode, 5, "????"); + } + + mcs[used_mcs] = '\0'; + htmode[used_htmode] = '\0'; + + len += snprintf(buf + len, max - len, + "%6s %6s %3u.%d: " + "%10u %10u %10u %10u\n", + htmode, + mcs, + ratekbps / 1000, + (ratekbps % 1000) / 100, + stats->success, + stats->retries, + stats->xretries, + stats->per); + } + + if (len > max) + len = max; + + retval = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + return retval; +} + +static const struct file_operations fops_rcstat = { + .read = read_file_rcstat, + .open = simple_open, + .owner = THIS_MODULE +}; + +static void ath_rate_add_sta_debugfs(void *priv, void *priv_sta, + struct dentry *dir) +{ + struct ath_rate_priv *rc = priv_sta; + rc->debugfs_rcstats = debugfs_create_file("rc_stats", S_IRUGO, + dir, rc, &fops_rcstat); +} + +static void ath_rate_remove_sta_debugfs(void *priv, void *priv_sta) +{ + struct ath_rate_priv *rc = priv_sta; + debugfs_remove(rc->debugfs_rcstats); +} + +#endif /* CPTCFG_MAC80211_DEBUGFS && CPTCFG_ATH9K_DEBUGFS */ + +static void *ath_rate_alloc(struct ieee80211_hw *hw, struct dentry *debugfsdir) +{ + return hw->priv; +} + +static void ath_rate_free(void *priv) +{ + return; +} + +static void *ath_rate_alloc_sta(void *priv, struct ieee80211_sta *sta, gfp_t gfp) +{ + return kzalloc(sizeof(struct ath_rate_priv), gfp); +} + +static void ath_rate_free_sta(void *priv, struct ieee80211_sta *sta, + void *priv_sta) +{ + struct ath_rate_priv *rate_priv = priv_sta; + kfree(rate_priv); +} + +static struct rate_control_ops ath_rate_ops = { + .module = NULL, + .name = "ath9k_rate_control", + .tx_status = ath_tx_status, + .get_rate = ath_get_rate, + .rate_init = ath_rate_init, + .rate_update = ath_rate_update, + .alloc = ath_rate_alloc, + .free = ath_rate_free, + .alloc_sta = ath_rate_alloc_sta, + .free_sta = ath_rate_free_sta, + +#if defined(CPTCFG_MAC80211_DEBUGFS) && defined(CPTCFG_ATH9K_DEBUGFS) + .add_sta_debugfs = ath_rate_add_sta_debugfs, + .remove_sta_debugfs = ath_rate_remove_sta_debugfs, +#endif +}; + +int ath_rate_control_register(void) +{ + return ieee80211_rate_control_register(&ath_rate_ops); +} + +void ath_rate_control_unregister(void) +{ + ieee80211_rate_control_unregister(&ath_rate_ops); +} diff --git a/drivers/net/wireless/ath/ath9k/rc.h b/drivers/net/wireless/ath/ath9k/rc.h new file mode 100644 index 0000000..8b87893 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/rc.h @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2004 Sam Leffler, Errno Consulting + * Copyright (c) 2004 Video54 Technologies, Inc. + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef RC_H +#define RC_H + +#include "hw.h" + +struct ath_softc; + +#define ATH_RATE_MAX 30 +#define RATE_TABLE_SIZE 72 + +#define RC_INVALID 0x0000 +#define RC_LEGACY 0x0001 +#define RC_SS 0x0002 +#define RC_DS 0x0004 +#define RC_TS 0x0008 +#define RC_HT_20 0x0010 +#define RC_HT_40 0x0020 + +#define RC_STREAM_MASK 0xe +#define RC_DS_OR_LATER(f) ((((f) & RC_STREAM_MASK) == RC_DS) || \ + (((f) & RC_STREAM_MASK) == (RC_DS | RC_TS))) +#define RC_TS_ONLY(f) (((f) & RC_STREAM_MASK) == RC_TS) +#define RC_SS_OR_LEGACY(f) ((f) & (RC_SS | RC_LEGACY)) + +#define RC_HT_2040 (RC_HT_20 | RC_HT_40) +#define RC_ALL_STREAM (RC_SS | RC_DS | RC_TS) +#define RC_L_SD (RC_LEGACY | RC_SS | RC_DS) +#define RC_L_SDT (RC_LEGACY | RC_SS | RC_DS | RC_TS) +#define RC_HT_S_20 (RC_HT_20 | RC_SS) +#define RC_HT_D_20 (RC_HT_20 | RC_DS) +#define RC_HT_T_20 (RC_HT_20 | RC_TS) +#define RC_HT_S_40 (RC_HT_40 | RC_SS) +#define RC_HT_D_40 (RC_HT_40 | RC_DS) +#define RC_HT_T_40 (RC_HT_40 | RC_TS) + +#define RC_HT_SD_20 (RC_HT_20 | RC_SS | RC_DS) +#define RC_HT_DT_20 (RC_HT_20 | RC_DS | RC_TS) +#define RC_HT_SD_40 (RC_HT_40 | RC_SS | RC_DS) +#define RC_HT_DT_40 (RC_HT_40 | RC_DS | RC_TS) + +#define RC_HT_SD_2040 (RC_HT_2040 | RC_SS | RC_DS) +#define RC_HT_SDT_2040 (RC_HT_2040 | RC_SS | RC_DS | RC_TS) + +#define RC_HT_SDT_20 (RC_HT_20 | RC_SS | RC_DS | RC_TS) +#define RC_HT_SDT_40 (RC_HT_40 | RC_SS | RC_DS | RC_TS) + +#define RC_ALL (RC_LEGACY | RC_HT_2040 | RC_ALL_STREAM) + +enum { + WLAN_RC_PHY_OFDM, + WLAN_RC_PHY_CCK, + WLAN_RC_PHY_HT_20_SS, + WLAN_RC_PHY_HT_20_DS, + WLAN_RC_PHY_HT_20_TS, + WLAN_RC_PHY_HT_40_SS, + WLAN_RC_PHY_HT_40_DS, + WLAN_RC_PHY_HT_40_TS, + WLAN_RC_PHY_HT_20_SS_HGI, + WLAN_RC_PHY_HT_20_DS_HGI, + WLAN_RC_PHY_HT_20_TS_HGI, + WLAN_RC_PHY_HT_40_SS_HGI, + WLAN_RC_PHY_HT_40_DS_HGI, + WLAN_RC_PHY_HT_40_TS_HGI, + WLAN_RC_PHY_MAX +}; + +#define WLAN_RC_PHY_DS(_phy) ((_phy == WLAN_RC_PHY_HT_20_DS) \ + || (_phy == WLAN_RC_PHY_HT_40_DS) \ + || (_phy == WLAN_RC_PHY_HT_20_DS_HGI) \ + || (_phy == WLAN_RC_PHY_HT_40_DS_HGI)) +#define WLAN_RC_PHY_TS(_phy) ((_phy == WLAN_RC_PHY_HT_20_TS) \ + || (_phy == WLAN_RC_PHY_HT_40_TS) \ + || (_phy == WLAN_RC_PHY_HT_20_TS_HGI) \ + || (_phy == WLAN_RC_PHY_HT_40_TS_HGI)) +#define WLAN_RC_PHY_20(_phy) ((_phy == WLAN_RC_PHY_HT_20_SS) \ + || (_phy == WLAN_RC_PHY_HT_20_DS) \ + || (_phy == WLAN_RC_PHY_HT_20_TS) \ + || (_phy == WLAN_RC_PHY_HT_20_SS_HGI) \ + || (_phy == WLAN_RC_PHY_HT_20_DS_HGI) \ + || (_phy == WLAN_RC_PHY_HT_20_TS_HGI)) +#define WLAN_RC_PHY_40(_phy) ((_phy == WLAN_RC_PHY_HT_40_SS) \ + || (_phy == WLAN_RC_PHY_HT_40_DS) \ + || (_phy == WLAN_RC_PHY_HT_40_TS) \ + || (_phy == WLAN_RC_PHY_HT_40_SS_HGI) \ + || (_phy == WLAN_RC_PHY_HT_40_DS_HGI) \ + || (_phy == WLAN_RC_PHY_HT_40_TS_HGI)) +#define WLAN_RC_PHY_SGI(_phy) ((_phy == WLAN_RC_PHY_HT_20_SS_HGI) \ + || (_phy == WLAN_RC_PHY_HT_20_DS_HGI) \ + || (_phy == WLAN_RC_PHY_HT_20_TS_HGI) \ + || (_phy == WLAN_RC_PHY_HT_40_SS_HGI) \ + || (_phy == WLAN_RC_PHY_HT_40_DS_HGI) \ + || (_phy == WLAN_RC_PHY_HT_40_TS_HGI)) + +#define WLAN_RC_PHY_HT(_phy) (_phy >= WLAN_RC_PHY_HT_20_SS) + +#define WLAN_RC_CAP_MODE(capflag) (((capflag & WLAN_RC_HT_FLAG) ? \ + ((capflag & WLAN_RC_40_FLAG) ? RC_HT_40 : RC_HT_20) : RC_LEGACY)) + +#define WLAN_RC_CAP_STREAM(capflag) (((capflag & WLAN_RC_TS_FLAG) ? \ + (RC_TS) : ((capflag & WLAN_RC_DS_FLAG) ? RC_DS : RC_SS))) + +/* Return TRUE if flag supports HT20 && client supports HT20 or + * return TRUE if flag supports HT40 && client supports HT40. + * This is used becos some rates overlap between HT20/HT40. + */ +#define WLAN_RC_PHY_HT_VALID(flag, capflag) \ + (((flag & RC_HT_20) && !(capflag & WLAN_RC_40_FLAG)) || \ + ((flag & RC_HT_40) && (capflag & WLAN_RC_40_FLAG))) + +#define WLAN_RC_DS_FLAG (0x01) +#define WLAN_RC_TS_FLAG (0x02) +#define WLAN_RC_40_FLAG (0x04) +#define WLAN_RC_SGI_FLAG (0x08) +#define WLAN_RC_HT_FLAG (0x10) + +/** + * struct ath_rate_table - Rate Control table + * @rate_cnt: total number of rates for the given wireless mode + * @mcs_start: MCS rate index offset + * @rate_flags: Rate Control flags + * @phy: CCK/OFDM/HT20/HT40 + * @ratekbps: rate in Kbits per second + * @user_ratekbps: user rate in Kbits per second + * @ratecode: rate that goes into HW descriptors + * @dot11rate: value that goes into supported + * rates info element of MLME + * @ctrl_rate: Index of next lower basic rate, used for duration computation + * @cw40index: Index of rates having 40MHz channel width + * @sgi_index: Index of rates having Short Guard Interval + * @ht_index: high throughput rates having 40MHz channel width and + * Short Guard Interval + * @probe_interval: interval for rate control to probe for other rates + * @initial_ratemax: initial ratemax value + */ +struct ath_rate_table { + int rate_cnt; + int mcs_start; + struct { + u16 rate_flags; + u8 phy; + u32 ratekbps; + u32 user_ratekbps; + u8 ratecode; + u8 dot11rate; + } info[RATE_TABLE_SIZE]; + u32 probe_interval; + u8 initial_ratemax; +}; + +struct ath_rateset { + u8 rs_nrates; + u8 rs_rates[ATH_RATE_MAX]; +}; + +struct ath_rc_stats { + u32 success; + u32 retries; + u32 xretries; + u8 per; +}; + +/** + * struct ath_rate_priv - Rate Control priv data + * @state: RC state + * @probe_rate: rate we are probing at + * @probe_time: msec timestamp for last probe + * @hw_maxretry_pktcnt: num of packets since we got HW max retry error + * @max_valid_rate: maximum number of valid rate + * @per_down_time: msec timestamp for last PER down step + * @valid_phy_ratecnt: valid rate count + * @rate_max_phy: phy index for the max rate + * @per: PER for every valid rate in % + * @probe_interval: interval for ratectrl to probe for other rates + * @ht_cap: HT capabilities + * @neg_rates: Negotatied rates + * @neg_ht_rates: Negotiated HT rates + */ +struct ath_rate_priv { + u8 rate_table_size; + u8 probe_rate; + u8 hw_maxretry_pktcnt; + u8 max_valid_rate; + u8 valid_rate_index[RATE_TABLE_SIZE]; + u8 ht_cap; + u8 valid_phy_ratecnt[WLAN_RC_PHY_MAX]; + u8 valid_phy_rateidx[WLAN_RC_PHY_MAX][RATE_TABLE_SIZE]; + u8 rate_max_phy; + u8 per[RATE_TABLE_SIZE]; + u32 probe_time; + u32 per_down_time; + u32 probe_interval; + struct ath_rateset neg_rates; + struct ath_rateset neg_ht_rates; + const struct ath_rate_table *rate_table; + +#if defined(CPTCFG_MAC80211_DEBUGFS) && defined(CPTCFG_ATH9K_DEBUGFS) + struct dentry *debugfs_rcstats; + struct ath_rc_stats rcstats[RATE_TABLE_SIZE]; +#endif +}; + +#if defined(CPTCFG_MAC80211_DEBUGFS) && defined(CPTCFG_ATH9K_DEBUGFS) +void ath_debug_stat_rc(struct ath_rate_priv *rc, int final_rate); +void ath_debug_stat_retries(struct ath_rate_priv *rc, int rix, + int xretries, int retries, u8 per); +#else +static inline void ath_debug_stat_rc(struct ath_rate_priv *rc, int final_rate) +{ +} +static inline void ath_debug_stat_retries(struct ath_rate_priv *rc, int rix, + int xretries, int retries, u8 per) +{ +} +#endif + +#ifdef CPTCFG_ATH9K_LEGACY_RATE_CONTROL +int ath_rate_control_register(void); +void ath_rate_control_unregister(void); +#else +static inline int ath_rate_control_register(void) +{ + return 0; +} + +static inline void ath_rate_control_unregister(void) +{ +} +#endif + +#endif /* RC_H */ diff --git a/drivers/net/wireless/ath/ath9k/recv.c b/drivers/net/wireless/ath/ath9k/recv.c new file mode 100644 index 0000000..6a09a04 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/recv.c @@ -0,0 +1,1401 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include "ath9k.h" +#include "ar9003_mac.h" + +#define SKB_CB_ATHBUF(__skb) (*((struct ath_buf **)__skb->cb)) + +static inline bool ath9k_check_auto_sleep(struct ath_softc *sc) +{ + return sc->ps_enabled && + (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_AUTOSLEEP); +} + +/* + * Setup and link descriptors. + * + * 11N: we can no longer afford to self link the last descriptor. + * MAC acknowledges BA status as long as it copies frames to host + * buffer (or rx fifo). This can incorrectly acknowledge packets + * to a sender if last desc is self-linked. + */ +static void ath_rx_buf_link(struct ath_softc *sc, struct ath_buf *bf) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath_desc *ds; + struct sk_buff *skb; + + ds = bf->bf_desc; + ds->ds_link = 0; /* link to null */ + ds->ds_data = bf->bf_buf_addr; + + /* virtual addr of the beginning of the buffer. */ + skb = bf->bf_mpdu; + BUG_ON(skb == NULL); + ds->ds_vdata = skb->data; + + /* + * setup rx descriptors. The rx_bufsize here tells the hardware + * how much data it can DMA to us and that we are prepared + * to process + */ + ath9k_hw_setuprxdesc(ah, ds, + common->rx_bufsize, + 0); + + if (sc->rx.rxlink == NULL) + ath9k_hw_putrxbuf(ah, bf->bf_daddr); + else + *sc->rx.rxlink = bf->bf_daddr; + + sc->rx.rxlink = &ds->ds_link; +} + +static void ath_rx_buf_relink(struct ath_softc *sc, struct ath_buf *bf) +{ + if (sc->rx.buf_hold) + ath_rx_buf_link(sc, sc->rx.buf_hold); + + sc->rx.buf_hold = bf; +} + +static void ath_setdefantenna(struct ath_softc *sc, u32 antenna) +{ + /* XXX block beacon interrupts */ + ath9k_hw_setantenna(sc->sc_ah, antenna); + sc->rx.defant = antenna; + sc->rx.rxotherant = 0; +} + +static void ath_opmode_init(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + + u32 rfilt, mfilt[2]; + + /* configure rx filter */ + rfilt = ath_calcrxfilter(sc); + ath9k_hw_setrxfilter(ah, rfilt); + + /* configure bssid mask */ + ath_hw_setbssidmask(common); + + /* configure operational mode */ + ath9k_hw_setopmode(ah); + + /* calculate and install multicast filter */ + mfilt[0] = mfilt[1] = ~0; + ath9k_hw_setmcastfilter(ah, mfilt[0], mfilt[1]); +} + +static bool ath_rx_edma_buf_link(struct ath_softc *sc, + enum ath9k_rx_qtype qtype) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_rx_edma *rx_edma; + struct sk_buff *skb; + struct ath_buf *bf; + + rx_edma = &sc->rx.rx_edma[qtype]; + if (skb_queue_len(&rx_edma->rx_fifo) >= rx_edma->rx_fifo_hwsize) + return false; + + bf = list_first_entry(&sc->rx.rxbuf, struct ath_buf, list); + list_del_init(&bf->list); + + skb = bf->bf_mpdu; + + memset(skb->data, 0, ah->caps.rx_status_len); + dma_sync_single_for_device(sc->dev, bf->bf_buf_addr, + ah->caps.rx_status_len, DMA_TO_DEVICE); + + SKB_CB_ATHBUF(skb) = bf; + ath9k_hw_addrxbuf_edma(ah, bf->bf_buf_addr, qtype); + __skb_queue_tail(&rx_edma->rx_fifo, skb); + + return true; +} + +static void ath_rx_addbuffer_edma(struct ath_softc *sc, + enum ath9k_rx_qtype qtype) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_buf *bf, *tbf; + + if (list_empty(&sc->rx.rxbuf)) { + ath_dbg(common, QUEUE, "No free rx buf available\n"); + return; + } + + list_for_each_entry_safe(bf, tbf, &sc->rx.rxbuf, list) + if (!ath_rx_edma_buf_link(sc, qtype)) + break; + +} + +static void ath_rx_remove_buffer(struct ath_softc *sc, + enum ath9k_rx_qtype qtype) +{ + struct ath_buf *bf; + struct ath_rx_edma *rx_edma; + struct sk_buff *skb; + + rx_edma = &sc->rx.rx_edma[qtype]; + + while ((skb = __skb_dequeue(&rx_edma->rx_fifo)) != NULL) { + bf = SKB_CB_ATHBUF(skb); + BUG_ON(!bf); + list_add_tail(&bf->list, &sc->rx.rxbuf); + } +} + +static void ath_rx_edma_cleanup(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath_buf *bf; + + ath_rx_remove_buffer(sc, ATH9K_RX_QUEUE_LP); + ath_rx_remove_buffer(sc, ATH9K_RX_QUEUE_HP); + + list_for_each_entry(bf, &sc->rx.rxbuf, list) { + if (bf->bf_mpdu) { + dma_unmap_single(sc->dev, bf->bf_buf_addr, + common->rx_bufsize, + DMA_BIDIRECTIONAL); + dev_kfree_skb_any(bf->bf_mpdu); + bf->bf_buf_addr = 0; + bf->bf_mpdu = NULL; + } + } +} + +static void ath_rx_edma_init_queue(struct ath_rx_edma *rx_edma, int size) +{ + skb_queue_head_init(&rx_edma->rx_fifo); + rx_edma->rx_fifo_hwsize = size; +} + +static int ath_rx_edma_init(struct ath_softc *sc, int nbufs) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_hw *ah = sc->sc_ah; + struct sk_buff *skb; + struct ath_buf *bf; + int error = 0, i; + u32 size; + + ath9k_hw_set_rx_bufsize(ah, common->rx_bufsize - + ah->caps.rx_status_len); + + ath_rx_edma_init_queue(&sc->rx.rx_edma[ATH9K_RX_QUEUE_LP], + ah->caps.rx_lp_qdepth); + ath_rx_edma_init_queue(&sc->rx.rx_edma[ATH9K_RX_QUEUE_HP], + ah->caps.rx_hp_qdepth); + + size = sizeof(struct ath_buf) * nbufs; + bf = devm_kzalloc(sc->dev, size, GFP_KERNEL); + if (!bf) + return -ENOMEM; + + INIT_LIST_HEAD(&sc->rx.rxbuf); + + for (i = 0; i < nbufs; i++, bf++) { + skb = ath_rxbuf_alloc(common, common->rx_bufsize, GFP_KERNEL); + if (!skb) { + error = -ENOMEM; + goto rx_init_fail; + } + + memset(skb->data, 0, common->rx_bufsize); + bf->bf_mpdu = skb; + + bf->bf_buf_addr = dma_map_single(sc->dev, skb->data, + common->rx_bufsize, + DMA_BIDIRECTIONAL); + if (unlikely(dma_mapping_error(sc->dev, + bf->bf_buf_addr))) { + dev_kfree_skb_any(skb); + bf->bf_mpdu = NULL; + bf->bf_buf_addr = 0; + ath_err(common, + "dma_mapping_error() on RX init\n"); + error = -ENOMEM; + goto rx_init_fail; + } + + list_add_tail(&bf->list, &sc->rx.rxbuf); + } + + return 0; + +rx_init_fail: + ath_rx_edma_cleanup(sc); + return error; +} + +static void ath_edma_start_recv(struct ath_softc *sc) +{ + ath9k_hw_rxena(sc->sc_ah); + ath_rx_addbuffer_edma(sc, ATH9K_RX_QUEUE_HP); + ath_rx_addbuffer_edma(sc, ATH9K_RX_QUEUE_LP); + ath_opmode_init(sc); + ath9k_hw_startpcureceive(sc->sc_ah, !!(sc->hw->conf.flags & IEEE80211_CONF_OFFCHANNEL)); +} + +static void ath_edma_stop_recv(struct ath_softc *sc) +{ + ath_rx_remove_buffer(sc, ATH9K_RX_QUEUE_HP); + ath_rx_remove_buffer(sc, ATH9K_RX_QUEUE_LP); +} + +int ath_rx_init(struct ath_softc *sc, int nbufs) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct sk_buff *skb; + struct ath_buf *bf; + int error = 0; + + spin_lock_init(&sc->sc_pcu_lock); + + common->rx_bufsize = IEEE80211_MAX_MPDU_LEN / 2 + + sc->sc_ah->caps.rx_status_len; + + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) + return ath_rx_edma_init(sc, nbufs); + + ath_dbg(common, CONFIG, "cachelsz %u rxbufsize %u\n", + common->cachelsz, common->rx_bufsize); + + /* Initialize rx descriptors */ + + error = ath_descdma_setup(sc, &sc->rx.rxdma, &sc->rx.rxbuf, + "rx", nbufs, 1, 0); + if (error != 0) { + ath_err(common, + "failed to allocate rx descriptors: %d\n", + error); + goto err; + } + + list_for_each_entry(bf, &sc->rx.rxbuf, list) { + skb = ath_rxbuf_alloc(common, common->rx_bufsize, + GFP_KERNEL); + if (skb == NULL) { + error = -ENOMEM; + goto err; + } + + bf->bf_mpdu = skb; + bf->bf_buf_addr = dma_map_single(sc->dev, skb->data, + common->rx_bufsize, + DMA_FROM_DEVICE); + if (unlikely(dma_mapping_error(sc->dev, + bf->bf_buf_addr))) { + dev_kfree_skb_any(skb); + bf->bf_mpdu = NULL; + bf->bf_buf_addr = 0; + ath_err(common, + "dma_mapping_error() on RX init\n"); + error = -ENOMEM; + goto err; + } + } + sc->rx.rxlink = NULL; +err: + if (error) + ath_rx_cleanup(sc); + + return error; +} + +void ath_rx_cleanup(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct sk_buff *skb; + struct ath_buf *bf; + + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) { + ath_rx_edma_cleanup(sc); + return; + } + + list_for_each_entry(bf, &sc->rx.rxbuf, list) { + skb = bf->bf_mpdu; + if (skb) { + dma_unmap_single(sc->dev, bf->bf_buf_addr, + common->rx_bufsize, + DMA_FROM_DEVICE); + dev_kfree_skb(skb); + bf->bf_buf_addr = 0; + bf->bf_mpdu = NULL; + } + } +} + +/* + * Calculate the receive filter according to the + * operating mode and state: + * + * o always accept unicast, broadcast, and multicast traffic + * o maintain current state of phy error reception (the hal + * may enable phy error frames for noise immunity work) + * o probe request frames are accepted only when operating in + * hostap, adhoc, or monitor modes + * o enable promiscuous mode according to the interface state + * o accept beacons: + * - when operating in adhoc mode so the 802.11 layer creates + * node table entries for peers, + * - when operating in station mode for collecting rssi data when + * the station is otherwise quiet, or + * - when operating as a repeater so we see repeater-sta beacons + * - when scanning + */ + +u32 ath_calcrxfilter(struct ath_softc *sc) +{ + u32 rfilt; + + rfilt = ATH9K_RX_FILTER_UCAST | ATH9K_RX_FILTER_BCAST + | ATH9K_RX_FILTER_MCAST; + + /* if operating on a DFS channel, enable radar pulse detection */ + if (sc->hw->conf.radar_enabled) + rfilt |= ATH9K_RX_FILTER_PHYRADAR | ATH9K_RX_FILTER_PHYERR; + + if (sc->rx.rxfilter & FIF_PROBE_REQ) + rfilt |= ATH9K_RX_FILTER_PROBEREQ; + + /* + * Set promiscuous mode when FIF_PROMISC_IN_BSS is enabled for station + * mode interface or when in monitor mode. AP mode does not need this + * since it receives all in-BSS frames anyway. + */ + if (sc->sc_ah->is_monitoring) + rfilt |= ATH9K_RX_FILTER_PROM; + + if (sc->rx.rxfilter & FIF_CONTROL) + rfilt |= ATH9K_RX_FILTER_CONTROL; + + if ((sc->sc_ah->opmode == NL80211_IFTYPE_STATION) && + (sc->nvifs <= 1) && + !(sc->rx.rxfilter & FIF_BCN_PRBRESP_PROMISC)) + rfilt |= ATH9K_RX_FILTER_MYBEACON; + else + rfilt |= ATH9K_RX_FILTER_BEACON; + + if ((sc->sc_ah->opmode == NL80211_IFTYPE_AP) || + (sc->rx.rxfilter & FIF_PSPOLL)) + rfilt |= ATH9K_RX_FILTER_PSPOLL; + + if (conf_is_ht(&sc->hw->conf)) + rfilt |= ATH9K_RX_FILTER_COMP_BAR; + + if (sc->nvifs > 1 || (sc->rx.rxfilter & FIF_OTHER_BSS)) { + /* This is needed for older chips */ + if (sc->sc_ah->hw_version.macVersion <= AR_SREV_VERSION_9160) + rfilt |= ATH9K_RX_FILTER_PROM; + rfilt |= ATH9K_RX_FILTER_MCAST_BCAST_ALL; + } + + if (AR_SREV_9550(sc->sc_ah)) + rfilt |= ATH9K_RX_FILTER_4ADDRESS; + + return rfilt; + +} + +int ath_startrecv(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_buf *bf, *tbf; + + if (ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) { + ath_edma_start_recv(sc); + return 0; + } + + if (list_empty(&sc->rx.rxbuf)) + goto start_recv; + + sc->rx.buf_hold = NULL; + sc->rx.rxlink = NULL; + list_for_each_entry_safe(bf, tbf, &sc->rx.rxbuf, list) { + ath_rx_buf_link(sc, bf); + } + + /* We could have deleted elements so the list may be empty now */ + if (list_empty(&sc->rx.rxbuf)) + goto start_recv; + + bf = list_first_entry(&sc->rx.rxbuf, struct ath_buf, list); + ath9k_hw_putrxbuf(ah, bf->bf_daddr); + ath9k_hw_rxena(ah); + +start_recv: + ath_opmode_init(sc); + ath9k_hw_startpcureceive(ah, !!(sc->hw->conf.flags & IEEE80211_CONF_OFFCHANNEL)); + + return 0; +} + +static void ath_flushrecv(struct ath_softc *sc) +{ + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) + ath_rx_tasklet(sc, 1, true); + ath_rx_tasklet(sc, 1, false); +} + +bool ath_stoprecv(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + bool stopped, reset = false; + + ath9k_hw_abortpcurecv(ah); + ath9k_hw_setrxfilter(ah, 0); + stopped = ath9k_hw_stopdmarecv(ah, &reset); + + ath_flushrecv(sc); + + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) + ath_edma_stop_recv(sc); + else + sc->rx.rxlink = NULL; + + if (!(ah->ah_flags & AH_UNPLUGGED) && + unlikely(!stopped)) { + ath_err(ath9k_hw_common(sc->sc_ah), + "Could not stop RX, we could be " + "confusing the DMA engine when we start RX up\n"); + ATH_DBG_WARN_ON_ONCE(!stopped); + } + return stopped && !reset; +} + +static bool ath_beacon_dtim_pending_cab(struct sk_buff *skb) +{ + /* Check whether the Beacon frame has DTIM indicating buffered bc/mc */ + struct ieee80211_mgmt *mgmt; + u8 *pos, *end, id, elen; + struct ieee80211_tim_ie *tim; + + mgmt = (struct ieee80211_mgmt *)skb->data; + pos = mgmt->u.beacon.variable; + end = skb->data + skb->len; + + while (pos + 2 < end) { + id = *pos++; + elen = *pos++; + if (pos + elen > end) + break; + + if (id == WLAN_EID_TIM) { + if (elen < sizeof(*tim)) + break; + tim = (struct ieee80211_tim_ie *) pos; + if (tim->dtim_count != 0) + break; + return tim->bitmap_ctrl & 0x01; + } + + pos += elen; + } + + return false; +} + +static void ath_rx_ps_beacon(struct ath_softc *sc, struct sk_buff *skb) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + + if (skb->len < 24 + 8 + 2 + 2) + return; + + sc->ps_flags &= ~PS_WAIT_FOR_BEACON; + + if (sc->ps_flags & PS_BEACON_SYNC) { + sc->ps_flags &= ~PS_BEACON_SYNC; + ath_dbg(common, PS, + "Reconfigure beacon timers based on synchronized timestamp\n"); + ath9k_set_beacon(sc); + } + + if (ath_beacon_dtim_pending_cab(skb)) { + /* + * Remain awake waiting for buffered broadcast/multicast + * frames. If the last broadcast/multicast frame is not + * received properly, the next beacon frame will work as + * a backup trigger for returning into NETWORK SLEEP state, + * so we are waiting for it as well. + */ + ath_dbg(common, PS, + "Received DTIM beacon indicating buffered broadcast/multicast frame(s)\n"); + sc->ps_flags |= PS_WAIT_FOR_CAB | PS_WAIT_FOR_BEACON; + return; + } + + if (sc->ps_flags & PS_WAIT_FOR_CAB) { + /* + * This can happen if a broadcast frame is dropped or the AP + * fails to send a frame indicating that all CAB frames have + * been delivered. + */ + sc->ps_flags &= ~PS_WAIT_FOR_CAB; + ath_dbg(common, PS, "PS wait for CAB frames timed out\n"); + } +} + +static void ath_rx_ps(struct ath_softc *sc, struct sk_buff *skb, bool mybeacon) +{ + struct ieee80211_hdr *hdr; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + + hdr = (struct ieee80211_hdr *)skb->data; + + /* Process Beacon and CAB receive in PS state */ + if (((sc->ps_flags & PS_WAIT_FOR_BEACON) || ath9k_check_auto_sleep(sc)) + && mybeacon) { + ath_rx_ps_beacon(sc, skb); + } else if ((sc->ps_flags & PS_WAIT_FOR_CAB) && + (ieee80211_is_data(hdr->frame_control) || + ieee80211_is_action(hdr->frame_control)) && + is_multicast_ether_addr(hdr->addr1) && + !ieee80211_has_moredata(hdr->frame_control)) { + /* + * No more broadcast/multicast frames to be received at this + * point. + */ + sc->ps_flags &= ~(PS_WAIT_FOR_CAB | PS_WAIT_FOR_BEACON); + ath_dbg(common, PS, + "All PS CAB frames received, back to sleep\n"); + } else if ((sc->ps_flags & PS_WAIT_FOR_PSPOLL_DATA) && + !is_multicast_ether_addr(hdr->addr1) && + !ieee80211_has_morefrags(hdr->frame_control)) { + sc->ps_flags &= ~PS_WAIT_FOR_PSPOLL_DATA; + ath_dbg(common, PS, + "Going back to sleep after having received PS-Poll data (0x%lx)\n", + sc->ps_flags & (PS_WAIT_FOR_BEACON | + PS_WAIT_FOR_CAB | + PS_WAIT_FOR_PSPOLL_DATA | + PS_WAIT_FOR_TX_ACK)); + } +} + +static bool ath_edma_get_buffers(struct ath_softc *sc, + enum ath9k_rx_qtype qtype, + struct ath_rx_status *rs, + struct ath_buf **dest) +{ + struct ath_rx_edma *rx_edma = &sc->rx.rx_edma[qtype]; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct sk_buff *skb; + struct ath_buf *bf; + int ret; + + skb = skb_peek(&rx_edma->rx_fifo); + if (!skb) + return false; + + bf = SKB_CB_ATHBUF(skb); + BUG_ON(!bf); + + dma_sync_single_for_cpu(sc->dev, bf->bf_buf_addr, + common->rx_bufsize, DMA_FROM_DEVICE); + + ret = ath9k_hw_process_rxdesc_edma(ah, rs, skb->data); + if (ret == -EINPROGRESS) { + /*let device gain the buffer again*/ + dma_sync_single_for_device(sc->dev, bf->bf_buf_addr, + common->rx_bufsize, DMA_FROM_DEVICE); + return false; + } + + __skb_unlink(skb, &rx_edma->rx_fifo); + if (ret == -EINVAL) { + /* corrupt descriptor, skip this one and the following one */ + list_add_tail(&bf->list, &sc->rx.rxbuf); + ath_rx_edma_buf_link(sc, qtype); + + skb = skb_peek(&rx_edma->rx_fifo); + if (skb) { + bf = SKB_CB_ATHBUF(skb); + BUG_ON(!bf); + + __skb_unlink(skb, &rx_edma->rx_fifo); + list_add_tail(&bf->list, &sc->rx.rxbuf); + ath_rx_edma_buf_link(sc, qtype); + } + + bf = NULL; + } + + *dest = bf; + return true; +} + +static struct ath_buf *ath_edma_get_next_rx_buf(struct ath_softc *sc, + struct ath_rx_status *rs, + enum ath9k_rx_qtype qtype) +{ + struct ath_buf *bf = NULL; + + while (ath_edma_get_buffers(sc, qtype, rs, &bf)) { + if (!bf) + continue; + + return bf; + } + return NULL; +} + +static struct ath_buf *ath_get_next_rx_buf(struct ath_softc *sc, + struct ath_rx_status *rs) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath_desc *ds; + struct ath_buf *bf; + int ret; + + if (list_empty(&sc->rx.rxbuf)) { + sc->rx.rxlink = NULL; + return NULL; + } + + bf = list_first_entry(&sc->rx.rxbuf, struct ath_buf, list); + if (bf == sc->rx.buf_hold) + return NULL; + + ds = bf->bf_desc; + + /* + * Must provide the virtual address of the current + * descriptor, the physical address, and the virtual + * address of the next descriptor in the h/w chain. + * This allows the HAL to look ahead to see if the + * hardware is done with a descriptor by checking the + * done bit in the following descriptor and the address + * of the current descriptor the DMA engine is working + * on. All this is necessary because of our use of + * a self-linked list to avoid rx overruns. + */ + ret = ath9k_hw_rxprocdesc(ah, ds, rs); + if (ret == -EINPROGRESS) { + struct ath_rx_status trs; + struct ath_buf *tbf; + struct ath_desc *tds; + + memset(&trs, 0, sizeof(trs)); + if (list_is_last(&bf->list, &sc->rx.rxbuf)) { + sc->rx.rxlink = NULL; + return NULL; + } + + tbf = list_entry(bf->list.next, struct ath_buf, list); + + /* + * On some hardware the descriptor status words could + * get corrupted, including the done bit. Because of + * this, check if the next descriptor's done bit is + * set or not. + * + * If the next descriptor's done bit is set, the current + * descriptor has been corrupted. Force s/w to discard + * this descriptor and continue... + */ + + tds = tbf->bf_desc; + ret = ath9k_hw_rxprocdesc(ah, tds, &trs); + if (ret == -EINPROGRESS) + return NULL; + + /* + * mark descriptor as zero-length and set the 'more' + * flag to ensure that both buffers get discarded + */ + rs->rs_datalen = 0; + rs->rs_more = true; + } + + list_del(&bf->list); + if (!bf->bf_mpdu) + return bf; + + /* + * Synchronize the DMA transfer with CPU before + * 1. accessing the frame + * 2. requeueing the same buffer to h/w + */ + dma_sync_single_for_cpu(sc->dev, bf->bf_buf_addr, + common->rx_bufsize, + DMA_FROM_DEVICE); + + return bf; +} + +/* Assumes you've already done the endian to CPU conversion */ +static bool ath9k_rx_accept(struct ath_common *common, + struct ieee80211_hdr *hdr, + struct ieee80211_rx_status *rxs, + struct ath_rx_status *rx_stats, + bool *decrypt_error) +{ + struct ath_softc *sc = (struct ath_softc *) common->priv; + bool is_mc, is_valid_tkip, strip_mic, mic_error; + struct ath_hw *ah = common->ah; + __le16 fc; + u8 rx_status_len = ah->caps.rx_status_len; + + fc = hdr->frame_control; + + is_mc = !!is_multicast_ether_addr(hdr->addr1); + is_valid_tkip = rx_stats->rs_keyix != ATH9K_RXKEYIX_INVALID && + test_bit(rx_stats->rs_keyix, common->tkip_keymap); + strip_mic = is_valid_tkip && ieee80211_is_data(fc) && + ieee80211_has_protected(fc) && + !(rx_stats->rs_status & + (ATH9K_RXERR_DECRYPT | ATH9K_RXERR_CRC | ATH9K_RXERR_MIC | + ATH9K_RXERR_KEYMISS)); + + /* + * Key miss events are only relevant for pairwise keys where the + * descriptor does contain a valid key index. This has been observed + * mostly with CCMP encryption. + */ + if (rx_stats->rs_keyix == ATH9K_RXKEYIX_INVALID || + !test_bit(rx_stats->rs_keyix, common->ccmp_keymap)) + rx_stats->rs_status &= ~ATH9K_RXERR_KEYMISS; + + if (!rx_stats->rs_datalen) { + RX_STAT_INC(rx_len_err); + return false; + } + + /* + * rs_status follows rs_datalen so if rs_datalen is too large + * we can take a hint that hardware corrupted it, so ignore + * those frames. + */ + if (rx_stats->rs_datalen > (common->rx_bufsize - rx_status_len)) { + RX_STAT_INC(rx_len_err); + return false; + } + + /* Only use error bits from the last fragment */ + if (rx_stats->rs_more) + return true; + + mic_error = is_valid_tkip && !ieee80211_is_ctl(fc) && + !ieee80211_has_morefrags(fc) && + !(le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_FRAG) && + (rx_stats->rs_status & ATH9K_RXERR_MIC); + + /* + * The rx_stats->rs_status will not be set until the end of the + * chained descriptors so it can be ignored if rs_more is set. The + * rs_more will be false at the last element of the chained + * descriptors. + */ + if (rx_stats->rs_status != 0) { + u8 status_mask; + + if (rx_stats->rs_status & ATH9K_RXERR_CRC) { + rxs->flag |= RX_FLAG_FAILED_FCS_CRC; + mic_error = false; + } + if (rx_stats->rs_status & ATH9K_RXERR_PHY) + return false; + + if ((rx_stats->rs_status & ATH9K_RXERR_DECRYPT) || + (!is_mc && (rx_stats->rs_status & ATH9K_RXERR_KEYMISS))) { + *decrypt_error = true; + mic_error = false; + } + + /* + * Reject error frames with the exception of + * decryption and MIC failures. For monitor mode, + * we also ignore the CRC error. + */ + status_mask = ATH9K_RXERR_DECRYPT | ATH9K_RXERR_MIC | + ATH9K_RXERR_KEYMISS; + + if (ah->is_monitoring && (sc->rx.rxfilter & FIF_FCSFAIL)) + status_mask |= ATH9K_RXERR_CRC; + + if (rx_stats->rs_status & ~status_mask) + return false; + } + + /* + * For unicast frames the MIC error bit can have false positives, + * so all MIC error reports need to be validated in software. + * False negatives are not common, so skip software verification + * if the hardware considers the MIC valid. + */ + if (strip_mic) + rxs->flag |= RX_FLAG_MMIC_STRIPPED; + else if (is_mc && mic_error) + rxs->flag |= RX_FLAG_MMIC_ERROR; + + return true; +} + +static int ath9k_process_rate(struct ath_common *common, + struct ieee80211_hw *hw, + struct ath_rx_status *rx_stats, + struct ieee80211_rx_status *rxs) +{ + struct ieee80211_supported_band *sband; + enum ieee80211_band band; + unsigned int i = 0; + struct ath_softc __maybe_unused *sc = common->priv; + + band = hw->conf.chandef.chan->band; + sband = hw->wiphy->bands[band]; + + if (rx_stats->rs_rate & 0x80) { + /* HT rate */ + rxs->flag |= RX_FLAG_HT; + if (rx_stats->rs_flags & ATH9K_RX_2040) + rxs->flag |= RX_FLAG_40MHZ; + if (rx_stats->rs_flags & ATH9K_RX_GI) + rxs->flag |= RX_FLAG_SHORT_GI; + rxs->rate_idx = rx_stats->rs_rate & 0x7f; + return 0; + } + + for (i = 0; i < sband->n_bitrates; i++) { + if (sband->bitrates[i].hw_value == rx_stats->rs_rate) { + rxs->rate_idx = i; + return 0; + } + if (sband->bitrates[i].hw_value_short == rx_stats->rs_rate) { + rxs->flag |= RX_FLAG_SHORTPRE; + rxs->rate_idx = i; + return 0; + } + } + + /* + * No valid hardware bitrate found -- we should not get here + * because hardware has already validated this frame as OK. + */ + ath_dbg(common, ANY, + "unsupported hw bitrate detected 0x%02x using 1 Mbit\n", + rx_stats->rs_rate); + RX_STAT_INC(rx_rate_err); + return -EINVAL; +} + +static void ath9k_process_rssi(struct ath_common *common, + struct ieee80211_hw *hw, + struct ieee80211_hdr *hdr, + struct ath_rx_status *rx_stats) +{ + struct ath_softc *sc = hw->priv; + struct ath_hw *ah = common->ah; + int last_rssi; + int rssi = rx_stats->rs_rssi; + + if (!rx_stats->is_mybeacon || + ((ah->opmode != NL80211_IFTYPE_STATION) && + (ah->opmode != NL80211_IFTYPE_ADHOC))) + return; + + if (rx_stats->rs_rssi != ATH9K_RSSI_BAD && !rx_stats->rs_moreaggr) + ATH_RSSI_LPF(sc->last_rssi, rx_stats->rs_rssi); + + last_rssi = sc->last_rssi; + if (likely(last_rssi != ATH_RSSI_DUMMY_MARKER)) + rssi = ATH_EP_RND(last_rssi, ATH_RSSI_EP_MULTIPLIER); + if (rssi < 0) + rssi = 0; + + /* Update Beacon RSSI, this is used by ANI. */ + ah->stats.avgbrssi = rssi; +} + +/* + * For Decrypt or Demic errors, we only mark packet status here and always push + * up the frame up to let mac80211 handle the actual error case, be it no + * decryption key or real decryption error. This let us keep statistics there. + */ +static int ath9k_rx_skb_preprocess(struct ath_softc *sc, + struct ieee80211_hdr *hdr, + struct ath_rx_status *rx_stats, + struct ieee80211_rx_status *rx_status, + bool *decrypt_error) +{ + struct ieee80211_hw *hw = sc->hw; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + bool discard_current = sc->rx.discard_next; + + sc->rx.discard_next = rx_stats->rs_more; + if (discard_current) + return -EINVAL; + + /* + * everything but the rate is checked here, the rate check is done + * separately to avoid doing two lookups for a rate for each frame. + */ + if (!ath9k_rx_accept(common, hdr, rx_status, rx_stats, decrypt_error)) + return -EINVAL; + + /* Only use status info from the last fragment */ + if (rx_stats->rs_more) + return 0; + + ath9k_process_rssi(common, hw, hdr, rx_stats); + + if (ath9k_process_rate(common, hw, rx_stats, rx_status)) + return -EINVAL; + + rx_status->band = hw->conf.chandef.chan->band; + rx_status->freq = hw->conf.chandef.chan->center_freq; + rx_status->signal = ah->noise + rx_stats->rs_rssi; + rx_status->antenna = rx_stats->rs_antenna; + rx_status->flag |= RX_FLAG_MACTIME_END; + if (rx_stats->rs_moreaggr) + rx_status->flag |= RX_FLAG_NO_SIGNAL_VAL; + + sc->rx.discard_next = false; + return 0; +} + +static void ath9k_rx_skb_postprocess(struct ath_common *common, + struct sk_buff *skb, + struct ath_rx_status *rx_stats, + struct ieee80211_rx_status *rxs, + bool decrypt_error) +{ + struct ath_hw *ah = common->ah; + struct ieee80211_hdr *hdr; + int hdrlen, padpos, padsize; + u8 keyix; + __le16 fc; + + /* see if any padding is done by the hw and remove it */ + hdr = (struct ieee80211_hdr *) skb->data; + hdrlen = ieee80211_get_hdrlen_from_skb(skb); + fc = hdr->frame_control; + padpos = ieee80211_hdrlen(fc); + + /* The MAC header is padded to have 32-bit boundary if the + * packet payload is non-zero. The general calculation for + * padsize would take into account odd header lengths: + * padsize = (4 - padpos % 4) % 4; However, since only + * even-length headers are used, padding can only be 0 or 2 + * bytes and we can optimize this a bit. In addition, we must + * not try to remove padding from short control frames that do + * not have payload. */ + padsize = padpos & 3; + if (padsize && skb->len>=padpos+padsize+FCS_LEN) { + memmove(skb->data + padsize, skb->data, padpos); + skb_pull(skb, padsize); + } + + keyix = rx_stats->rs_keyix; + + if (!(keyix == ATH9K_RXKEYIX_INVALID) && !decrypt_error && + ieee80211_has_protected(fc)) { + rxs->flag |= RX_FLAG_DECRYPTED; + } else if (ieee80211_has_protected(fc) + && !decrypt_error && skb->len >= hdrlen + 4) { + keyix = skb->data[hdrlen + 3] >> 6; + + if (test_bit(keyix, common->keymap)) + rxs->flag |= RX_FLAG_DECRYPTED; + } + if (ah->sw_mgmt_crypto && + (rxs->flag & RX_FLAG_DECRYPTED) && + ieee80211_is_mgmt(fc)) + /* Use software decrypt for management frames. */ + rxs->flag &= ~RX_FLAG_DECRYPTED; +} + +#ifdef CPTCFG_ATH9K_DEBUGFS +static s8 fix_rssi_inv_only(u8 rssi_val) +{ + if (rssi_val == 128) + rssi_val = 0; + return (s8) rssi_val; +} +#endif + +/* returns 1 if this was a spectral frame, even if not handled. */ +static int ath_process_fft(struct ath_softc *sc, struct ieee80211_hdr *hdr, + struct ath_rx_status *rs, u64 tsf) +{ +#ifdef CPTCFG_ATH9K_DEBUGFS + struct ath_hw *ah = sc->sc_ah; + u8 bins[SPECTRAL_HT20_NUM_BINS]; + u8 *vdata = (u8 *)hdr; + struct fft_sample_ht20 fft_sample; + struct ath_radar_info *radar_info; + struct ath_ht20_mag_info *mag_info; + int len = rs->rs_datalen; + int dc_pos; + u16 length, max_magnitude; + + /* AR9280 and before report via ATH9K_PHYERR_RADAR, AR93xx and newer + * via ATH9K_PHYERR_SPECTRAL. Haven't seen ATH9K_PHYERR_FALSE_RADAR_EXT + * yet, but this is supposed to be possible as well. + */ + if (rs->rs_phyerr != ATH9K_PHYERR_RADAR && + rs->rs_phyerr != ATH9K_PHYERR_FALSE_RADAR_EXT && + rs->rs_phyerr != ATH9K_PHYERR_SPECTRAL) + return 0; + + /* check if spectral scan bit is set. This does not have to be checked + * if received through a SPECTRAL phy error, but shouldn't hurt. + */ + radar_info = ((struct ath_radar_info *)&vdata[len]) - 1; + if (!(radar_info->pulse_bw_info & SPECTRAL_SCAN_BITMASK)) + return 0; + + /* Variation in the data length is possible and will be fixed later. + * Note that we only support HT20 for now. + * + * TODO: add HT20_40 support as well. + */ + if ((len > SPECTRAL_HT20_TOTAL_DATA_LEN + 2) || + (len < SPECTRAL_HT20_TOTAL_DATA_LEN - 1)) + return 1; + + fft_sample.tlv.type = ATH_FFT_SAMPLE_HT20; + length = sizeof(fft_sample) - sizeof(fft_sample.tlv); + fft_sample.tlv.length = __cpu_to_be16(length); + + fft_sample.freq = __cpu_to_be16(ah->curchan->chan->center_freq); + fft_sample.rssi = fix_rssi_inv_only(rs->rs_rssi_ctl0); + fft_sample.noise = ah->noise; + + switch (len - SPECTRAL_HT20_TOTAL_DATA_LEN) { + case 0: + /* length correct, nothing to do. */ + memcpy(bins, vdata, SPECTRAL_HT20_NUM_BINS); + break; + case -1: + /* first byte missing, duplicate it. */ + memcpy(&bins[1], vdata, SPECTRAL_HT20_NUM_BINS - 1); + bins[0] = vdata[0]; + break; + case 2: + /* MAC added 2 extra bytes at bin 30 and 32, remove them. */ + memcpy(bins, vdata, 30); + bins[30] = vdata[31]; + memcpy(&bins[31], &vdata[33], SPECTRAL_HT20_NUM_BINS - 31); + break; + case 1: + /* MAC added 2 extra bytes AND first byte is missing. */ + bins[0] = vdata[0]; + memcpy(&bins[0], vdata, 30); + bins[31] = vdata[31]; + memcpy(&bins[32], &vdata[33], SPECTRAL_HT20_NUM_BINS - 32); + break; + default: + return 1; + } + + /* DC value (value in the middle) is the blind spot of the spectral + * sample and invalid, interpolate it. + */ + dc_pos = SPECTRAL_HT20_NUM_BINS / 2; + bins[dc_pos] = (bins[dc_pos + 1] + bins[dc_pos - 1]) / 2; + + /* mag data is at the end of the frame, in front of radar_info */ + mag_info = ((struct ath_ht20_mag_info *)radar_info) - 1; + + /* copy raw bins without scaling them */ + memcpy(fft_sample.data, bins, SPECTRAL_HT20_NUM_BINS); + fft_sample.max_exp = mag_info->max_exp & 0xf; + + max_magnitude = spectral_max_magnitude(mag_info->all_bins); + fft_sample.max_magnitude = __cpu_to_be16(max_magnitude); + fft_sample.max_index = spectral_max_index(mag_info->all_bins); + fft_sample.bitmap_weight = spectral_bitmap_weight(mag_info->all_bins); + fft_sample.tsf = __cpu_to_be64(tsf); + + ath_debug_send_fft_sample(sc, &fft_sample.tlv); + return 1; +#else + return 0; +#endif +} + +static void ath9k_apply_ampdu_details(struct ath_softc *sc, + struct ath_rx_status *rs, struct ieee80211_rx_status *rxs) +{ + if (rs->rs_isaggr) { + rxs->flag |= RX_FLAG_AMPDU_DETAILS | RX_FLAG_AMPDU_LAST_KNOWN; + + rxs->ampdu_reference = sc->rx.ampdu_ref; + + if (!rs->rs_moreaggr) { + rxs->flag |= RX_FLAG_AMPDU_IS_LAST; + sc->rx.ampdu_ref++; + } + + if (rs->rs_flags & ATH9K_RX_DELIM_CRC_PRE) + rxs->flag |= RX_FLAG_AMPDU_DELIM_CRC_ERROR; + } +} + +int ath_rx_tasklet(struct ath_softc *sc, int flush, bool hp) +{ + struct ath_buf *bf; + struct sk_buff *skb = NULL, *requeue_skb, *hdr_skb; + struct ieee80211_rx_status *rxs; + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ieee80211_hw *hw = sc->hw; + struct ieee80211_hdr *hdr; + int retval; + struct ath_rx_status rs; + enum ath9k_rx_qtype qtype; + bool edma = !!(ah->caps.hw_caps & ATH9K_HW_CAP_EDMA); + int dma_type; + u8 rx_status_len = ah->caps.rx_status_len; + u64 tsf = 0; + u32 tsf_lower = 0; + unsigned long flags; + dma_addr_t new_buf_addr; + + if (edma) + dma_type = DMA_BIDIRECTIONAL; + else + dma_type = DMA_FROM_DEVICE; + + qtype = hp ? ATH9K_RX_QUEUE_HP : ATH9K_RX_QUEUE_LP; + + tsf = ath9k_hw_gettsf64(ah); + tsf_lower = tsf & 0xffffffff; + + do { + bool decrypt_error = false; + + memset(&rs, 0, sizeof(rs)); + if (edma) + bf = ath_edma_get_next_rx_buf(sc, &rs, qtype); + else + bf = ath_get_next_rx_buf(sc, &rs); + + if (!bf) + break; + + skb = bf->bf_mpdu; + if (!skb) + continue; + + /* + * Take frame header from the first fragment and RX status from + * the last one. + */ + if (sc->rx.frag) + hdr_skb = sc->rx.frag; + else + hdr_skb = skb; + + hdr = (struct ieee80211_hdr *) (hdr_skb->data + rx_status_len); + rxs = IEEE80211_SKB_RXCB(hdr_skb); + if (ieee80211_is_beacon(hdr->frame_control)) { + RX_STAT_INC(rx_beacons); + if (!is_zero_ether_addr(common->curbssid) && + ether_addr_equal(hdr->addr3, common->curbssid)) + rs.is_mybeacon = true; + else + rs.is_mybeacon = false; + } + else + rs.is_mybeacon = false; + + if (ieee80211_is_data_present(hdr->frame_control) && + !ieee80211_is_qos_nullfunc(hdr->frame_control)) + sc->rx.num_pkts++; + + ath_debug_stat_rx(sc, &rs); + + memset(rxs, 0, sizeof(struct ieee80211_rx_status)); + + rxs->mactime = (tsf & ~0xffffffffULL) | rs.rs_tstamp; + if (rs.rs_tstamp > tsf_lower && + unlikely(rs.rs_tstamp - tsf_lower > 0x10000000)) + rxs->mactime -= 0x100000000ULL; + + if (rs.rs_tstamp < tsf_lower && + unlikely(tsf_lower - rs.rs_tstamp > 0x10000000)) + rxs->mactime += 0x100000000ULL; + + if (rs.rs_phyerr == ATH9K_PHYERR_RADAR) + ath9k_dfs_process_phyerr(sc, hdr, &rs, rxs->mactime); + + if (rs.rs_status & ATH9K_RXERR_PHY) { + if (ath_process_fft(sc, hdr, &rs, rxs->mactime)) { + RX_STAT_INC(rx_spectral); + goto requeue_drop_frag; + } + } + + retval = ath9k_rx_skb_preprocess(sc, hdr, &rs, rxs, + &decrypt_error); + if (retval) + goto requeue_drop_frag; + + if (rs.is_mybeacon) { + sc->hw_busy_count = 0; + ath_start_rx_poll(sc, 3); + } + /* Ensure we always have an skb to requeue once we are done + * processing the current buffer's skb */ + requeue_skb = ath_rxbuf_alloc(common, common->rx_bufsize, GFP_ATOMIC); + + /* If there is no memory we ignore the current RX'd frame, + * tell hardware it can give us a new frame using the old + * skb and put it at the tail of the sc->rx.rxbuf list for + * processing. */ + if (!requeue_skb) { + RX_STAT_INC(rx_oom_err); + goto requeue_drop_frag; + } + + /* We will now give hardware our shiny new allocated skb */ + new_buf_addr = dma_map_single(sc->dev, requeue_skb->data, + common->rx_bufsize, dma_type); + if (unlikely(dma_mapping_error(sc->dev, new_buf_addr))) { + dev_kfree_skb_any(requeue_skb); + goto requeue_drop_frag; + } + + /* Unmap the frame */ + dma_unmap_single(sc->dev, bf->bf_buf_addr, + common->rx_bufsize, dma_type); + + bf->bf_mpdu = requeue_skb; + bf->bf_buf_addr = new_buf_addr; + + skb_put(skb, rs.rs_datalen + ah->caps.rx_status_len); + if (ah->caps.rx_status_len) + skb_pull(skb, ah->caps.rx_status_len); + + if (!rs.rs_more) + ath9k_rx_skb_postprocess(common, hdr_skb, &rs, + rxs, decrypt_error); + + if (rs.rs_more) { + RX_STAT_INC(rx_frags); + /* + * rs_more indicates chained descriptors which can be + * used to link buffers together for a sort of + * scatter-gather operation. + */ + if (sc->rx.frag) { + /* too many fragments - cannot handle frame */ + dev_kfree_skb_any(sc->rx.frag); + dev_kfree_skb_any(skb); + RX_STAT_INC(rx_too_many_frags_err); + skb = NULL; + } + sc->rx.frag = skb; + goto requeue; + } + if (rs.rs_status & ATH9K_RXERR_CORRUPT_DESC) + goto requeue_drop_frag; + + if (sc->rx.frag) { + int space = skb->len - skb_tailroom(hdr_skb); + + if (pskb_expand_head(hdr_skb, 0, space, GFP_ATOMIC) < 0) { + dev_kfree_skb(skb); + RX_STAT_INC(rx_oom_err); + goto requeue_drop_frag; + } + + sc->rx.frag = NULL; + + skb_copy_from_linear_data(skb, skb_put(hdr_skb, skb->len), + skb->len); + dev_kfree_skb_any(skb); + skb = hdr_skb; + } + + + if (ah->caps.hw_caps & ATH9K_HW_CAP_ANT_DIV_COMB) { + + /* + * change the default rx antenna if rx diversity + * chooses the other antenna 3 times in a row. + */ + if (sc->rx.defant != rs.rs_antenna) { + if (++sc->rx.rxotherant >= 3) + ath_setdefantenna(sc, rs.rs_antenna); + } else { + sc->rx.rxotherant = 0; + } + + } + + if (rxs->flag & RX_FLAG_MMIC_STRIPPED) + skb_trim(skb, skb->len - 8); + + spin_lock_irqsave(&sc->sc_pm_lock, flags); + if ((sc->ps_flags & (PS_WAIT_FOR_BEACON | + PS_WAIT_FOR_CAB | + PS_WAIT_FOR_PSPOLL_DATA)) || + ath9k_check_auto_sleep(sc)) + ath_rx_ps(sc, skb, rs.is_mybeacon); + spin_unlock_irqrestore(&sc->sc_pm_lock, flags); + + if ((ah->caps.hw_caps & ATH9K_HW_CAP_ANT_DIV_COMB) && sc->ant_rx == 3) + ath_ant_comb_scan(sc, &rs); + + ath9k_apply_ampdu_details(sc, &rs, rxs); + + ieee80211_rx(hw, skb); + +requeue_drop_frag: + if (sc->rx.frag) { + dev_kfree_skb_any(sc->rx.frag); + sc->rx.frag = NULL; + } +requeue: + list_add_tail(&bf->list, &sc->rx.rxbuf); + if (flush) + continue; + + if (edma) { + ath_rx_edma_buf_link(sc, qtype); + } else { + ath_rx_buf_relink(sc, bf); + ath9k_hw_rxena(ah); + } + } while (1); + + if (!(ah->imask & ATH9K_INT_RXEOL)) { + ah->imask |= (ATH9K_INT_RXEOL | ATH9K_INT_RXORN); + ath9k_hw_set_interrupts(ah); + } + + return 0; +} diff --git a/drivers/net/wireless/ath/ath9k/reg.h b/drivers/net/wireless/ath/ath9k/reg.h new file mode 100644 index 0000000..f7c90cc --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/reg.h @@ -0,0 +1,2377 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef REG_H +#define REG_H + +#include "../reg.h" + +#define AR_CR 0x0008 +#define AR_CR_RXE (AR_SREV_9300_20_OR_LATER(ah) ? 0x0000000c : 0x00000004) +#define AR_CR_RXD 0x00000020 +#define AR_CR_SWI 0x00000040 + +#define AR_RXDP 0x000C + +#define AR_CFG 0x0014 +#define AR_CFG_SWTD 0x00000001 +#define AR_CFG_SWTB 0x00000002 +#define AR_CFG_SWRD 0x00000004 +#define AR_CFG_SWRB 0x00000008 +#define AR_CFG_SWRG 0x00000010 +#define AR_CFG_AP_ADHOC_INDICATION 0x00000020 +#define AR_CFG_PHOK 0x00000100 +#define AR_CFG_CLK_GATE_DIS 0x00000400 +#define AR_CFG_EEBS 0x00000200 +#define AR_CFG_PCI_MASTER_REQ_Q_THRESH 0x00060000 +#define AR_CFG_PCI_MASTER_REQ_Q_THRESH_S 17 + +#define AR_RXBP_THRESH 0x0018 +#define AR_RXBP_THRESH_HP 0x0000000f +#define AR_RXBP_THRESH_HP_S 0 +#define AR_RXBP_THRESH_LP 0x00003f00 +#define AR_RXBP_THRESH_LP_S 8 + +#define AR_MIRT 0x0020 +#define AR_MIRT_VAL 0x0000ffff +#define AR_MIRT_VAL_S 16 + +#define AR_IER 0x0024 +#define AR_IER_ENABLE 0x00000001 +#define AR_IER_DISABLE 0x00000000 + +#define AR_TIMT 0x0028 +#define AR_TIMT_LAST 0x0000ffff +#define AR_TIMT_LAST_S 0 +#define AR_TIMT_FIRST 0xffff0000 +#define AR_TIMT_FIRST_S 16 + +#define AR_RIMT 0x002C +#define AR_RIMT_LAST 0x0000ffff +#define AR_RIMT_LAST_S 0 +#define AR_RIMT_FIRST 0xffff0000 +#define AR_RIMT_FIRST_S 16 + +#define AR_DMASIZE_4B 0x00000000 +#define AR_DMASIZE_8B 0x00000001 +#define AR_DMASIZE_16B 0x00000002 +#define AR_DMASIZE_32B 0x00000003 +#define AR_DMASIZE_64B 0x00000004 +#define AR_DMASIZE_128B 0x00000005 +#define AR_DMASIZE_256B 0x00000006 +#define AR_DMASIZE_512B 0x00000007 + +#define AR_TXCFG 0x0030 +#define AR_TXCFG_DMASZ_MASK 0x00000007 +#define AR_TXCFG_DMASZ_4B 0 +#define AR_TXCFG_DMASZ_8B 1 +#define AR_TXCFG_DMASZ_16B 2 +#define AR_TXCFG_DMASZ_32B 3 +#define AR_TXCFG_DMASZ_64B 4 +#define AR_TXCFG_DMASZ_128B 5 +#define AR_TXCFG_DMASZ_256B 6 +#define AR_TXCFG_DMASZ_512B 7 +#define AR_FTRIG 0x000003F0 +#define AR_FTRIG_S 4 +#define AR_FTRIG_IMMED 0x00000000 +#define AR_FTRIG_64B 0x00000010 +#define AR_FTRIG_128B 0x00000020 +#define AR_FTRIG_192B 0x00000030 +#define AR_FTRIG_256B 0x00000040 +#define AR_FTRIG_512B 0x00000080 +#define AR_TXCFG_ADHOC_BEACON_ATIM_TX_POLICY 0x00000800 + +#define AR_RXCFG 0x0034 +#define AR_RXCFG_CHIRP 0x00000008 +#define AR_RXCFG_ZLFDMA 0x00000010 +#define AR_RXCFG_DMASZ_MASK 0x00000007 +#define AR_RXCFG_DMASZ_4B 0 +#define AR_RXCFG_DMASZ_8B 1 +#define AR_RXCFG_DMASZ_16B 2 +#define AR_RXCFG_DMASZ_32B 3 +#define AR_RXCFG_DMASZ_64B 4 +#define AR_RXCFG_DMASZ_128B 5 +#define AR_RXCFG_DMASZ_256B 6 +#define AR_RXCFG_DMASZ_512B 7 + +#define AR_TOPS 0x0044 +#define AR_TOPS_MASK 0x0000FFFF + +#define AR_RXNPTO 0x0048 +#define AR_RXNPTO_MASK 0x000003FF + +#define AR_TXNPTO 0x004C +#define AR_TXNPTO_MASK 0x000003FF +#define AR_TXNPTO_QCU_MASK 0x000FFC00 + +#define AR_RPGTO 0x0050 +#define AR_RPGTO_MASK 0x000003FF + +#define AR_RPCNT 0x0054 +#define AR_RPCNT_MASK 0x0000001F + +#define AR_MACMISC 0x0058 +#define AR_MACMISC_PCI_EXT_FORCE 0x00000010 +#define AR_MACMISC_DMA_OBS 0x000001E0 +#define AR_MACMISC_DMA_OBS_S 5 +#define AR_MACMISC_DMA_OBS_LINE_0 0 +#define AR_MACMISC_DMA_OBS_LINE_1 1 +#define AR_MACMISC_DMA_OBS_LINE_2 2 +#define AR_MACMISC_DMA_OBS_LINE_3 3 +#define AR_MACMISC_DMA_OBS_LINE_4 4 +#define AR_MACMISC_DMA_OBS_LINE_5 5 +#define AR_MACMISC_DMA_OBS_LINE_6 6 +#define AR_MACMISC_DMA_OBS_LINE_7 7 +#define AR_MACMISC_DMA_OBS_LINE_8 8 +#define AR_MACMISC_MISC_OBS 0x00000E00 +#define AR_MACMISC_MISC_OBS_S 9 +#define AR_MACMISC_MISC_OBS_BUS_LSB 0x00007000 +#define AR_MACMISC_MISC_OBS_BUS_LSB_S 12 +#define AR_MACMISC_MISC_OBS_BUS_MSB 0x00038000 +#define AR_MACMISC_MISC_OBS_BUS_MSB_S 15 +#define AR_MACMISC_MISC_OBS_BUS_1 1 + +#define AR_DATABUF_SIZE 0x0060 +#define AR_DATABUF_SIZE_MASK 0x00000FFF + +#define AR_GTXTO 0x0064 +#define AR_GTXTO_TIMEOUT_COUNTER 0x0000FFFF +#define AR_GTXTO_TIMEOUT_LIMIT 0xFFFF0000 +#define AR_GTXTO_TIMEOUT_LIMIT_S 16 + +#define AR_GTTM 0x0068 +#define AR_GTTM_USEC 0x00000001 +#define AR_GTTM_IGNORE_IDLE 0x00000002 +#define AR_GTTM_RESET_IDLE 0x00000004 +#define AR_GTTM_CST_USEC 0x00000008 + +#define AR_CST 0x006C +#define AR_CST_TIMEOUT_COUNTER 0x0000FFFF +#define AR_CST_TIMEOUT_LIMIT 0xFFFF0000 +#define AR_CST_TIMEOUT_LIMIT_S 16 + +#define AR_HP_RXDP 0x0074 +#define AR_LP_RXDP 0x0078 + +#define AR_ISR 0x0080 +#define AR_ISR_RXOK 0x00000001 +#define AR_ISR_RXDESC 0x00000002 +#define AR_ISR_HP_RXOK 0x00000001 +#define AR_ISR_LP_RXOK 0x00000002 +#define AR_ISR_RXERR 0x00000004 +#define AR_ISR_RXNOPKT 0x00000008 +#define AR_ISR_RXEOL 0x00000010 +#define AR_ISR_RXORN 0x00000020 +#define AR_ISR_TXOK 0x00000040 +#define AR_ISR_TXDESC 0x00000080 +#define AR_ISR_TXERR 0x00000100 +#define AR_ISR_TXNOPKT 0x00000200 +#define AR_ISR_TXEOL 0x00000400 +#define AR_ISR_TXURN 0x00000800 +#define AR_ISR_MIB 0x00001000 +#define AR_ISR_SWI 0x00002000 +#define AR_ISR_RXPHY 0x00004000 +#define AR_ISR_RXKCM 0x00008000 +#define AR_ISR_SWBA 0x00010000 +#define AR_ISR_BRSSI 0x00020000 +#define AR_ISR_BMISS 0x00040000 +#define AR_ISR_BNR 0x00100000 +#define AR_ISR_RXCHIRP 0x00200000 +#define AR_ISR_BCNMISC 0x00800000 +#define AR_ISR_TIM 0x00800000 +#define AR_ISR_QCBROVF 0x02000000 +#define AR_ISR_QCBRURN 0x04000000 +#define AR_ISR_QTRIG 0x08000000 +#define AR_ISR_GENTMR 0x10000000 + +#define AR_ISR_TXMINTR 0x00080000 +#define AR_ISR_RXMINTR 0x01000000 +#define AR_ISR_TXINTM 0x40000000 +#define AR_ISR_RXINTM 0x80000000 + +#define AR_ISR_S0 0x0084 +#define AR_ISR_S0_QCU_TXOK 0x000003FF +#define AR_ISR_S0_QCU_TXOK_S 0 +#define AR_ISR_S0_QCU_TXDESC 0x03FF0000 +#define AR_ISR_S0_QCU_TXDESC_S 16 + +#define AR_ISR_S1 0x0088 +#define AR_ISR_S1_QCU_TXERR 0x000003FF +#define AR_ISR_S1_QCU_TXERR_S 0 +#define AR_ISR_S1_QCU_TXEOL 0x03FF0000 +#define AR_ISR_S1_QCU_TXEOL_S 16 + +#define AR_ISR_S2 0x008c +#define AR_ISR_S2_QCU_TXURN 0x000003FF +#define AR_ISR_S2_BB_WATCHDOG 0x00010000 +#define AR_ISR_S2_CST 0x00400000 +#define AR_ISR_S2_GTT 0x00800000 +#define AR_ISR_S2_TIM 0x01000000 +#define AR_ISR_S2_CABEND 0x02000000 +#define AR_ISR_S2_DTIMSYNC 0x04000000 +#define AR_ISR_S2_BCNTO 0x08000000 +#define AR_ISR_S2_CABTO 0x10000000 +#define AR_ISR_S2_DTIM 0x20000000 +#define AR_ISR_S2_TSFOOR 0x40000000 +#define AR_ISR_S2_TBTT_TIME 0x80000000 + +#define AR_ISR_S3 0x0090 +#define AR_ISR_S3_QCU_QCBROVF 0x000003FF +#define AR_ISR_S3_QCU_QCBRURN 0x03FF0000 + +#define AR_ISR_S4 0x0094 +#define AR_ISR_S4_QCU_QTRIG 0x000003FF +#define AR_ISR_S4_RESV0 0xFFFFFC00 + +#define AR_ISR_S5 0x0098 +#define AR_ISR_S5_TIMER_TRIG 0x000000FF +#define AR_ISR_S5_TIMER_THRESH 0x0007FE00 +#define AR_ISR_S5_TIM_TIMER 0x00000010 +#define AR_ISR_S5_DTIM_TIMER 0x00000020 +#define AR_IMR_S5 0x00b8 +#define AR_IMR_S5_TIM_TIMER 0x00000010 +#define AR_IMR_S5_DTIM_TIMER 0x00000020 +#define AR_ISR_S5_GENTIMER_TRIG 0x0000FF80 +#define AR_ISR_S5_GENTIMER_TRIG_S 0 +#define AR_ISR_S5_GENTIMER_THRESH 0xFF800000 +#define AR_ISR_S5_GENTIMER_THRESH_S 16 +#define AR_IMR_S5_GENTIMER_TRIG 0x0000FF80 +#define AR_IMR_S5_GENTIMER_TRIG_S 0 +#define AR_IMR_S5_GENTIMER_THRESH 0xFF800000 +#define AR_IMR_S5_GENTIMER_THRESH_S 16 + +#define AR_IMR 0x00a0 +#define AR_IMR_RXOK 0x00000001 +#define AR_IMR_RXDESC 0x00000002 +#define AR_IMR_RXOK_HP 0x00000001 +#define AR_IMR_RXOK_LP 0x00000002 +#define AR_IMR_RXERR 0x00000004 +#define AR_IMR_RXNOPKT 0x00000008 +#define AR_IMR_RXEOL 0x00000010 +#define AR_IMR_RXORN 0x00000020 +#define AR_IMR_TXOK 0x00000040 +#define AR_IMR_TXDESC 0x00000080 +#define AR_IMR_TXERR 0x00000100 +#define AR_IMR_TXNOPKT 0x00000200 +#define AR_IMR_TXEOL 0x00000400 +#define AR_IMR_TXURN 0x00000800 +#define AR_IMR_MIB 0x00001000 +#define AR_IMR_SWI 0x00002000 +#define AR_IMR_RXPHY 0x00004000 +#define AR_IMR_RXKCM 0x00008000 +#define AR_IMR_SWBA 0x00010000 +#define AR_IMR_BRSSI 0x00020000 +#define AR_IMR_BMISS 0x00040000 +#define AR_IMR_BNR 0x00100000 +#define AR_IMR_RXCHIRP 0x00200000 +#define AR_IMR_BCNMISC 0x00800000 +#define AR_IMR_TIM 0x00800000 +#define AR_IMR_QCBROVF 0x02000000 +#define AR_IMR_QCBRURN 0x04000000 +#define AR_IMR_QTRIG 0x08000000 +#define AR_IMR_GENTMR 0x10000000 + +#define AR_IMR_TXMINTR 0x00080000 +#define AR_IMR_RXMINTR 0x01000000 +#define AR_IMR_TXINTM 0x40000000 +#define AR_IMR_RXINTM 0x80000000 + +#define AR_IMR_S0 0x00a4 +#define AR_IMR_S0_QCU_TXOK 0x000003FF +#define AR_IMR_S0_QCU_TXOK_S 0 +#define AR_IMR_S0_QCU_TXDESC 0x03FF0000 +#define AR_IMR_S0_QCU_TXDESC_S 16 + +#define AR_IMR_S1 0x00a8 +#define AR_IMR_S1_QCU_TXERR 0x000003FF +#define AR_IMR_S1_QCU_TXERR_S 0 +#define AR_IMR_S1_QCU_TXEOL 0x03FF0000 +#define AR_IMR_S1_QCU_TXEOL_S 16 + +#define AR_IMR_S2 0x00ac +#define AR_IMR_S2_QCU_TXURN 0x000003FF +#define AR_IMR_S2_QCU_TXURN_S 0 +#define AR_IMR_S2_CST 0x00400000 +#define AR_IMR_S2_GTT 0x00800000 +#define AR_IMR_S2_TIM 0x01000000 +#define AR_IMR_S2_CABEND 0x02000000 +#define AR_IMR_S2_DTIMSYNC 0x04000000 +#define AR_IMR_S2_BCNTO 0x08000000 +#define AR_IMR_S2_CABTO 0x10000000 +#define AR_IMR_S2_DTIM 0x20000000 +#define AR_IMR_S2_TSFOOR 0x40000000 + +#define AR_IMR_S3 0x00b0 +#define AR_IMR_S3_QCU_QCBROVF 0x000003FF +#define AR_IMR_S3_QCU_QCBRURN 0x03FF0000 +#define AR_IMR_S3_QCU_QCBRURN_S 16 + +#define AR_IMR_S4 0x00b4 +#define AR_IMR_S4_QCU_QTRIG 0x000003FF +#define AR_IMR_S4_RESV0 0xFFFFFC00 + +#define AR_IMR_S5 0x00b8 +#define AR_IMR_S5_TIMER_TRIG 0x000000FF +#define AR_IMR_S5_TIMER_THRESH 0x0000FF00 + + +#define AR_ISR_RAC 0x00c0 +#define AR_ISR_S0_S 0x00c4 +#define AR_ISR_S0_QCU_TXOK 0x000003FF +#define AR_ISR_S0_QCU_TXOK_S 0 +#define AR_ISR_S0_QCU_TXDESC 0x03FF0000 +#define AR_ISR_S0_QCU_TXDESC_S 16 + +#define AR_ISR_S1_S 0x00c8 +#define AR_ISR_S1_QCU_TXERR 0x000003FF +#define AR_ISR_S1_QCU_TXERR_S 0 +#define AR_ISR_S1_QCU_TXEOL 0x03FF0000 +#define AR_ISR_S1_QCU_TXEOL_S 16 + +#define AR_ISR_S2_S (AR_SREV_9300_20_OR_LATER(ah) ? 0x00d0 : 0x00cc) +#define AR_ISR_S3_S (AR_SREV_9300_20_OR_LATER(ah) ? 0x00d4 : 0x00d0) +#define AR_ISR_S4_S (AR_SREV_9300_20_OR_LATER(ah) ? 0x00d8 : 0x00d4) +#define AR_ISR_S5_S (AR_SREV_9300_20_OR_LATER(ah) ? 0x00dc : 0x00d8) +#define AR_DMADBG_0 0x00e0 +#define AR_DMADBG_1 0x00e4 +#define AR_DMADBG_2 0x00e8 +#define AR_DMADBG_3 0x00ec +#define AR_DMADBG_4 0x00f0 +#define AR_DMADBG_5 0x00f4 +#define AR_DMADBG_6 0x00f8 +#define AR_DMADBG_7 0x00fc + +#define AR_NUM_QCU 10 +#define AR_QCU_0 0x0001 +#define AR_QCU_1 0x0002 +#define AR_QCU_2 0x0004 +#define AR_QCU_3 0x0008 +#define AR_QCU_4 0x0010 +#define AR_QCU_5 0x0020 +#define AR_QCU_6 0x0040 +#define AR_QCU_7 0x0080 +#define AR_QCU_8 0x0100 +#define AR_QCU_9 0x0200 + +#define AR_Q0_TXDP 0x0800 +#define AR_Q1_TXDP 0x0804 +#define AR_Q2_TXDP 0x0808 +#define AR_Q3_TXDP 0x080c +#define AR_Q4_TXDP 0x0810 +#define AR_Q5_TXDP 0x0814 +#define AR_Q6_TXDP 0x0818 +#define AR_Q7_TXDP 0x081c +#define AR_Q8_TXDP 0x0820 +#define AR_Q9_TXDP 0x0824 +#define AR_QTXDP(_i) (AR_Q0_TXDP + ((_i)<<2)) + +#define AR_Q_STATUS_RING_START 0x830 +#define AR_Q_STATUS_RING_END 0x834 + +#define AR_Q_TXE 0x0840 +#define AR_Q_TXE_M 0x000003FF + +#define AR_Q_TXD 0x0880 +#define AR_Q_TXD_M 0x000003FF + +#define AR_Q0_CBRCFG 0x08c0 +#define AR_Q1_CBRCFG 0x08c4 +#define AR_Q2_CBRCFG 0x08c8 +#define AR_Q3_CBRCFG 0x08cc +#define AR_Q4_CBRCFG 0x08d0 +#define AR_Q5_CBRCFG 0x08d4 +#define AR_Q6_CBRCFG 0x08d8 +#define AR_Q7_CBRCFG 0x08dc +#define AR_Q8_CBRCFG 0x08e0 +#define AR_Q9_CBRCFG 0x08e4 +#define AR_QCBRCFG(_i) (AR_Q0_CBRCFG + ((_i)<<2)) +#define AR_Q_CBRCFG_INTERVAL 0x00FFFFFF +#define AR_Q_CBRCFG_INTERVAL_S 0 +#define AR_Q_CBRCFG_OVF_THRESH 0xFF000000 +#define AR_Q_CBRCFG_OVF_THRESH_S 24 + +#define AR_Q0_RDYTIMECFG 0x0900 +#define AR_Q1_RDYTIMECFG 0x0904 +#define AR_Q2_RDYTIMECFG 0x0908 +#define AR_Q3_RDYTIMECFG 0x090c +#define AR_Q4_RDYTIMECFG 0x0910 +#define AR_Q5_RDYTIMECFG 0x0914 +#define AR_Q6_RDYTIMECFG 0x0918 +#define AR_Q7_RDYTIMECFG 0x091c +#define AR_Q8_RDYTIMECFG 0x0920 +#define AR_Q9_RDYTIMECFG 0x0924 +#define AR_QRDYTIMECFG(_i) (AR_Q0_RDYTIMECFG + ((_i)<<2)) +#define AR_Q_RDYTIMECFG_DURATION 0x00FFFFFF +#define AR_Q_RDYTIMECFG_DURATION_S 0 +#define AR_Q_RDYTIMECFG_EN 0x01000000 + +#define AR_Q_ONESHOTARM_SC 0x0940 +#define AR_Q_ONESHOTARM_SC_M 0x000003FF +#define AR_Q_ONESHOTARM_SC_RESV0 0xFFFFFC00 + +#define AR_Q_ONESHOTARM_CC 0x0980 +#define AR_Q_ONESHOTARM_CC_M 0x000003FF +#define AR_Q_ONESHOTARM_CC_RESV0 0xFFFFFC00 + +#define AR_Q0_MISC 0x09c0 +#define AR_Q1_MISC 0x09c4 +#define AR_Q2_MISC 0x09c8 +#define AR_Q3_MISC 0x09cc +#define AR_Q4_MISC 0x09d0 +#define AR_Q5_MISC 0x09d4 +#define AR_Q6_MISC 0x09d8 +#define AR_Q7_MISC 0x09dc +#define AR_Q8_MISC 0x09e0 +#define AR_Q9_MISC 0x09e4 +#define AR_QMISC(_i) (AR_Q0_MISC + ((_i)<<2)) +#define AR_Q_MISC_FSP 0x0000000F +#define AR_Q_MISC_FSP_ASAP 0 +#define AR_Q_MISC_FSP_CBR 1 +#define AR_Q_MISC_FSP_DBA_GATED 2 +#define AR_Q_MISC_FSP_TIM_GATED 3 +#define AR_Q_MISC_FSP_BEACON_SENT_GATED 4 +#define AR_Q_MISC_FSP_BEACON_RCVD_GATED 5 +#define AR_Q_MISC_ONE_SHOT_EN 0x00000010 +#define AR_Q_MISC_CBR_INCR_DIS1 0x00000020 +#define AR_Q_MISC_CBR_INCR_DIS0 0x00000040 +#define AR_Q_MISC_BEACON_USE 0x00000080 +#define AR_Q_MISC_CBR_EXP_CNTR_LIMIT_EN 0x00000100 +#define AR_Q_MISC_RDYTIME_EXP_POLICY 0x00000200 +#define AR_Q_MISC_RESET_CBR_EXP_CTR 0x00000400 +#define AR_Q_MISC_DCU_EARLY_TERM_REQ 0x00000800 +#define AR_Q_MISC_RESV0 0xFFFFF000 + +#define AR_Q0_STS 0x0a00 +#define AR_Q1_STS 0x0a04 +#define AR_Q2_STS 0x0a08 +#define AR_Q3_STS 0x0a0c +#define AR_Q4_STS 0x0a10 +#define AR_Q5_STS 0x0a14 +#define AR_Q6_STS 0x0a18 +#define AR_Q7_STS 0x0a1c +#define AR_Q8_STS 0x0a20 +#define AR_Q9_STS 0x0a24 +#define AR_QSTS(_i) (AR_Q0_STS + ((_i)<<2)) +#define AR_Q_STS_PEND_FR_CNT 0x00000003 +#define AR_Q_STS_RESV0 0x000000FC +#define AR_Q_STS_CBR_EXP_CNT 0x0000FF00 +#define AR_Q_STS_RESV1 0xFFFF0000 + +#define AR_Q_RDYTIMESHDN 0x0a40 +#define AR_Q_RDYTIMESHDN_M 0x000003FF + +/* MAC Descriptor CRC check */ +#define AR_Q_DESC_CRCCHK 0xa44 +/* Enable CRC check on the descriptor fetched from host */ +#define AR_Q_DESC_CRCCHK_EN 1 + +#define AR_NUM_DCU 10 +#define AR_DCU_0 0x0001 +#define AR_DCU_1 0x0002 +#define AR_DCU_2 0x0004 +#define AR_DCU_3 0x0008 +#define AR_DCU_4 0x0010 +#define AR_DCU_5 0x0020 +#define AR_DCU_6 0x0040 +#define AR_DCU_7 0x0080 +#define AR_DCU_8 0x0100 +#define AR_DCU_9 0x0200 + +#define AR_D0_QCUMASK 0x1000 +#define AR_D1_QCUMASK 0x1004 +#define AR_D2_QCUMASK 0x1008 +#define AR_D3_QCUMASK 0x100c +#define AR_D4_QCUMASK 0x1010 +#define AR_D5_QCUMASK 0x1014 +#define AR_D6_QCUMASK 0x1018 +#define AR_D7_QCUMASK 0x101c +#define AR_D8_QCUMASK 0x1020 +#define AR_D9_QCUMASK 0x1024 +#define AR_DQCUMASK(_i) (AR_D0_QCUMASK + ((_i)<<2)) +#define AR_D_QCUMASK 0x000003FF +#define AR_D_QCUMASK_RESV0 0xFFFFFC00 + +#define AR_D_TXBLK_CMD 0x1038 +#define AR_D_TXBLK_DATA(i) (AR_D_TXBLK_CMD+(i)) + +#define AR_D0_LCL_IFS 0x1040 +#define AR_D1_LCL_IFS 0x1044 +#define AR_D2_LCL_IFS 0x1048 +#define AR_D3_LCL_IFS 0x104c +#define AR_D4_LCL_IFS 0x1050 +#define AR_D5_LCL_IFS 0x1054 +#define AR_D6_LCL_IFS 0x1058 +#define AR_D7_LCL_IFS 0x105c +#define AR_D8_LCL_IFS 0x1060 +#define AR_D9_LCL_IFS 0x1064 +#define AR_DLCL_IFS(_i) (AR_D0_LCL_IFS + ((_i)<<2)) +#define AR_D_LCL_IFS_CWMIN 0x000003FF +#define AR_D_LCL_IFS_CWMIN_S 0 +#define AR_D_LCL_IFS_CWMAX 0x000FFC00 +#define AR_D_LCL_IFS_CWMAX_S 10 +#define AR_D_LCL_IFS_AIFS 0x0FF00000 +#define AR_D_LCL_IFS_AIFS_S 20 + +#define AR_D_LCL_IFS_RESV0 0xF0000000 + +#define AR_D0_RETRY_LIMIT 0x1080 +#define AR_D1_RETRY_LIMIT 0x1084 +#define AR_D2_RETRY_LIMIT 0x1088 +#define AR_D3_RETRY_LIMIT 0x108c +#define AR_D4_RETRY_LIMIT 0x1090 +#define AR_D5_RETRY_LIMIT 0x1094 +#define AR_D6_RETRY_LIMIT 0x1098 +#define AR_D7_RETRY_LIMIT 0x109c +#define AR_D8_RETRY_LIMIT 0x10a0 +#define AR_D9_RETRY_LIMIT 0x10a4 +#define AR_DRETRY_LIMIT(_i) (AR_D0_RETRY_LIMIT + ((_i)<<2)) +#define AR_D_RETRY_LIMIT_FR_SH 0x0000000F +#define AR_D_RETRY_LIMIT_FR_SH_S 0 +#define AR_D_RETRY_LIMIT_STA_SH 0x00003F00 +#define AR_D_RETRY_LIMIT_STA_SH_S 8 +#define AR_D_RETRY_LIMIT_STA_LG 0x000FC000 +#define AR_D_RETRY_LIMIT_STA_LG_S 14 +#define AR_D_RETRY_LIMIT_RESV0 0xFFF00000 + +#define AR_D0_CHNTIME 0x10c0 +#define AR_D1_CHNTIME 0x10c4 +#define AR_D2_CHNTIME 0x10c8 +#define AR_D3_CHNTIME 0x10cc +#define AR_D4_CHNTIME 0x10d0 +#define AR_D5_CHNTIME 0x10d4 +#define AR_D6_CHNTIME 0x10d8 +#define AR_D7_CHNTIME 0x10dc +#define AR_D8_CHNTIME 0x10e0 +#define AR_D9_CHNTIME 0x10e4 +#define AR_DCHNTIME(_i) (AR_D0_CHNTIME + ((_i)<<2)) +#define AR_D_CHNTIME_DUR 0x000FFFFF +#define AR_D_CHNTIME_DUR_S 0 +#define AR_D_CHNTIME_EN 0x00100000 +#define AR_D_CHNTIME_RESV0 0xFFE00000 + +#define AR_D0_MISC 0x1100 +#define AR_D1_MISC 0x1104 +#define AR_D2_MISC 0x1108 +#define AR_D3_MISC 0x110c +#define AR_D4_MISC 0x1110 +#define AR_D5_MISC 0x1114 +#define AR_D6_MISC 0x1118 +#define AR_D7_MISC 0x111c +#define AR_D8_MISC 0x1120 +#define AR_D9_MISC 0x1124 +#define AR_DMISC(_i) (AR_D0_MISC + ((_i)<<2)) +#define AR_D_MISC_BKOFF_THRESH 0x0000003F +#define AR_D_MISC_RETRY_CNT_RESET_EN 0x00000040 +#define AR_D_MISC_CW_RESET_EN 0x00000080 +#define AR_D_MISC_FRAG_WAIT_EN 0x00000100 +#define AR_D_MISC_FRAG_BKOFF_EN 0x00000200 +#define AR_D_MISC_CW_BKOFF_EN 0x00001000 +#define AR_D_MISC_VIR_COL_HANDLING 0x0000C000 +#define AR_D_MISC_VIR_COL_HANDLING_S 14 +#define AR_D_MISC_VIR_COL_HANDLING_DEFAULT 0 +#define AR_D_MISC_VIR_COL_HANDLING_IGNORE 1 +#define AR_D_MISC_BEACON_USE 0x00010000 +#define AR_D_MISC_ARB_LOCKOUT_CNTRL 0x00060000 +#define AR_D_MISC_ARB_LOCKOUT_CNTRL_S 17 +#define AR_D_MISC_ARB_LOCKOUT_CNTRL_NONE 0 +#define AR_D_MISC_ARB_LOCKOUT_CNTRL_INTRA_FR 1 +#define AR_D_MISC_ARB_LOCKOUT_CNTRL_GLOBAL 2 +#define AR_D_MISC_ARB_LOCKOUT_IGNORE 0x00080000 +#define AR_D_MISC_SEQ_NUM_INCR_DIS 0x00100000 +#define AR_D_MISC_POST_FR_BKOFF_DIS 0x00200000 +#define AR_D_MISC_VIT_COL_CW_BKOFF_EN 0x00400000 +#define AR_D_MISC_BLOWN_IFS_RETRY_EN 0x00800000 +#define AR_D_MISC_RESV0 0xFF000000 + +#define AR_D_SEQNUM 0x1140 + +#define AR_D_GBL_IFS_SIFS 0x1030 +#define AR_D_GBL_IFS_SIFS_M 0x0000FFFF +#define AR_D_GBL_IFS_SIFS_RESV0 0xFFFFFFFF + +#define AR_D_TXBLK_BASE 0x1038 +#define AR_D_TXBLK_WRITE_BITMASK 0x0000FFFF +#define AR_D_TXBLK_WRITE_BITMASK_S 0 +#define AR_D_TXBLK_WRITE_SLICE 0x000F0000 +#define AR_D_TXBLK_WRITE_SLICE_S 16 +#define AR_D_TXBLK_WRITE_DCU 0x00F00000 +#define AR_D_TXBLK_WRITE_DCU_S 20 +#define AR_D_TXBLK_WRITE_COMMAND 0x0F000000 +#define AR_D_TXBLK_WRITE_COMMAND_S 24 + +#define AR_D_GBL_IFS_SLOT 0x1070 +#define AR_D_GBL_IFS_SLOT_M 0x0000FFFF +#define AR_D_GBL_IFS_SLOT_RESV0 0xFFFF0000 + +#define AR_D_GBL_IFS_EIFS 0x10b0 +#define AR_D_GBL_IFS_EIFS_M 0x0000FFFF +#define AR_D_GBL_IFS_EIFS_RESV0 0xFFFF0000 +#define AR_D_GBL_IFS_EIFS_ASYNC_FIFO 363 + +#define AR_D_GBL_IFS_MISC 0x10f0 +#define AR_D_GBL_IFS_MISC_LFSR_SLICE_SEL 0x00000007 +#define AR_D_GBL_IFS_MISC_TURBO_MODE 0x00000008 +#define AR_D_GBL_IFS_MISC_USEC_DURATION 0x000FFC00 +#define AR_D_GBL_IFS_MISC_DCU_ARBITER_DLY 0x00300000 +#define AR_D_GBL_IFS_MISC_RANDOM_LFSR_SLICE_DIS 0x01000000 +#define AR_D_GBL_IFS_MISC_SLOT_XMIT_WIND_LEN 0x06000000 +#define AR_D_GBL_IFS_MISC_FORCE_XMIT_SLOT_BOUND 0x08000000 +#define AR_D_GBL_IFS_MISC_IGNORE_BACKOFF 0x10000000 + +#define AR_D_FPCTL 0x1230 +#define AR_D_FPCTL_DCU 0x0000000F +#define AR_D_FPCTL_DCU_S 0 +#define AR_D_FPCTL_PREFETCH_EN 0x00000010 +#define AR_D_FPCTL_BURST_PREFETCH 0x00007FE0 +#define AR_D_FPCTL_BURST_PREFETCH_S 5 + +#define AR_D_TXPSE 0x1270 +#define AR_D_TXPSE_CTRL 0x000003FF +#define AR_D_TXPSE_RESV0 0x0000FC00 +#define AR_D_TXPSE_STATUS 0x00010000 +#define AR_D_TXPSE_RESV1 0xFFFE0000 + +#define AR_D_TXSLOTMASK 0x12f0 +#define AR_D_TXSLOTMASK_NUM 0x0000000F + +#define AR_CFG_LED 0x1f04 +#define AR_CFG_SCLK_RATE_IND 0x00000003 +#define AR_CFG_SCLK_RATE_IND_S 0 +#define AR_CFG_SCLK_32MHZ 0x00000000 +#define AR_CFG_SCLK_4MHZ 0x00000001 +#define AR_CFG_SCLK_1MHZ 0x00000002 +#define AR_CFG_SCLK_32KHZ 0x00000003 +#define AR_CFG_LED_BLINK_SLOW 0x00000008 +#define AR_CFG_LED_BLINK_THRESH_SEL 0x00000070 +#define AR_CFG_LED_MODE_SEL 0x00000380 +#define AR_CFG_LED_MODE_SEL_S 7 +#define AR_CFG_LED_POWER 0x00000280 +#define AR_CFG_LED_POWER_S 7 +#define AR_CFG_LED_NETWORK 0x00000300 +#define AR_CFG_LED_NETWORK_S 7 +#define AR_CFG_LED_MODE_PROP 0x0 +#define AR_CFG_LED_MODE_RPROP 0x1 +#define AR_CFG_LED_MODE_SPLIT 0x2 +#define AR_CFG_LED_MODE_RAND 0x3 +#define AR_CFG_LED_MODE_POWER_OFF 0x4 +#define AR_CFG_LED_MODE_POWER_ON 0x5 +#define AR_CFG_LED_MODE_NETWORK_OFF 0x4 +#define AR_CFG_LED_MODE_NETWORK_ON 0x6 +#define AR_CFG_LED_ASSOC_CTL 0x00000c00 +#define AR_CFG_LED_ASSOC_CTL_S 10 +#define AR_CFG_LED_ASSOC_NONE 0x0 +#define AR_CFG_LED_ASSOC_ACTIVE 0x1 +#define AR_CFG_LED_ASSOC_PENDING 0x2 + +#define AR_CFG_LED_BLINK_SLOW 0x00000008 +#define AR_CFG_LED_BLINK_SLOW_S 3 + +#define AR_CFG_LED_BLINK_THRESH_SEL 0x00000070 +#define AR_CFG_LED_BLINK_THRESH_SEL_S 4 + +#define AR_MAC_SLEEP 0x1f00 +#define AR_MAC_SLEEP_MAC_AWAKE 0x00000000 +#define AR_MAC_SLEEP_MAC_ASLEEP 0x00000001 + +#define AR_RC 0x4000 +#define AR_RC_AHB 0x00000001 +#define AR_RC_APB 0x00000002 +#define AR_RC_HOSTIF 0x00000100 + +#define AR_WA (AR_SREV_9340(ah) ? 0x40c4 : 0x4004) +#define AR_WA_BIT6 (1 << 6) +#define AR_WA_BIT7 (1 << 7) +#define AR_WA_BIT23 (1 << 23) +#define AR_WA_D3_L1_DISABLE (1 << 14) +#define AR_WA_UNTIE_RESET_EN (1 << 15) /* Enable PCI Reset + to POR (power-on-reset) */ +#define AR_WA_D3_TO_L1_DISABLE_REAL (1 << 16) +#define AR_WA_ASPM_TIMER_BASED_DISABLE (1 << 17) +#define AR_WA_RESET_EN (1 << 18) /* Enable PCI-Reset to + POR (bit 15) */ +#define AR_WA_ANALOG_SHIFT (1 << 20) +#define AR_WA_POR_SHORT (1 << 21) /* PCI-E Phy reset control */ +#define AR_WA_BIT22 (1 << 22) +#define AR9285_WA_DEFAULT 0x004a050b +#define AR9280_WA_DEFAULT 0x0040073b +#define AR_WA_DEFAULT 0x0000073f + + +#define AR_PM_STATE 0x4008 +#define AR_PM_STATE_PME_D3COLD_VAUX 0x00100000 + +#define AR_HOST_TIMEOUT (AR_SREV_9340(ah) ? 0x4008 : 0x4018) +#define AR_HOST_TIMEOUT_APB_CNTR 0x0000FFFF +#define AR_HOST_TIMEOUT_APB_CNTR_S 0 +#define AR_HOST_TIMEOUT_LCL_CNTR 0xFFFF0000 +#define AR_HOST_TIMEOUT_LCL_CNTR_S 16 + +#define AR_EEPROM 0x401c +#define AR_EEPROM_ABSENT 0x00000100 +#define AR_EEPROM_CORRUPT 0x00000200 +#define AR_EEPROM_PROT_MASK 0x03FFFC00 +#define AR_EEPROM_PROT_MASK_S 10 + +#define EEPROM_PROTECT_RP_0_31 0x0001 +#define EEPROM_PROTECT_WP_0_31 0x0002 +#define EEPROM_PROTECT_RP_32_63 0x0004 +#define EEPROM_PROTECT_WP_32_63 0x0008 +#define EEPROM_PROTECT_RP_64_127 0x0010 +#define EEPROM_PROTECT_WP_64_127 0x0020 +#define EEPROM_PROTECT_RP_128_191 0x0040 +#define EEPROM_PROTECT_WP_128_191 0x0080 +#define EEPROM_PROTECT_RP_192_255 0x0100 +#define EEPROM_PROTECT_WP_192_255 0x0200 +#define EEPROM_PROTECT_RP_256_511 0x0400 +#define EEPROM_PROTECT_WP_256_511 0x0800 +#define EEPROM_PROTECT_RP_512_1023 0x1000 +#define EEPROM_PROTECT_WP_512_1023 0x2000 +#define EEPROM_PROTECT_RP_1024_2047 0x4000 +#define EEPROM_PROTECT_WP_1024_2047 0x8000 + +#define AR_SREV \ + ((AR_SREV_9100(ah)) ? 0x0600 : (AR_SREV_9340(ah) \ + ? 0x400c : 0x4020)) + +#define AR_SREV_ID \ + ((AR_SREV_9100(ah)) ? 0x00000FFF : 0x000000FF) +#define AR_SREV_VERSION 0x000000F0 +#define AR_SREV_VERSION_S 4 +#define AR_SREV_REVISION 0x00000007 + +#define AR_SREV_ID2 0xFFFFFFFF +#define AR_SREV_VERSION2 0xFFFC0000 +#define AR_SREV_VERSION2_S 18 +#define AR_SREV_TYPE2 0x0003F000 +#define AR_SREV_TYPE2_S 12 +#define AR_SREV_TYPE2_CHAIN 0x00001000 +#define AR_SREV_TYPE2_HOST_MODE 0x00002000 +#define AR_SREV_REVISION2 0x00000F00 +#define AR_SREV_REVISION2_S 8 + +#define AR_SREV_VERSION_5416_PCI 0xD +#define AR_SREV_VERSION_5416_PCIE 0xC +#define AR_SREV_REVISION_5416_10 0 +#define AR_SREV_REVISION_5416_20 1 +#define AR_SREV_REVISION_5416_22 2 +#define AR_SREV_VERSION_9100 0x14 +#define AR_SREV_VERSION_9160 0x40 +#define AR_SREV_REVISION_9160_10 0 +#define AR_SREV_REVISION_9160_11 1 +#define AR_SREV_VERSION_9280 0x80 +#define AR_SREV_REVISION_9280_10 0 +#define AR_SREV_REVISION_9280_20 1 +#define AR_SREV_REVISION_9280_21 2 +#define AR_SREV_VERSION_9285 0xC0 +#define AR_SREV_REVISION_9285_10 0 +#define AR_SREV_REVISION_9285_11 1 +#define AR_SREV_REVISION_9285_12 2 +#define AR_SREV_VERSION_9287 0x180 +#define AR_SREV_REVISION_9287_10 0 +#define AR_SREV_REVISION_9287_11 1 +#define AR_SREV_REVISION_9287_12 2 +#define AR_SREV_REVISION_9287_13 3 +#define AR_SREV_VERSION_9271 0x140 +#define AR_SREV_REVISION_9271_10 0 +#define AR_SREV_REVISION_9271_11 1 +#define AR_SREV_VERSION_9300 0x1c0 +#define AR_SREV_REVISION_9300_20 2 /* 2.0 and 2.1 */ +#define AR_SREV_REVISION_9300_22 3 +#define AR_SREV_VERSION_9330 0x200 +#define AR_SREV_REVISION_9330_10 0 +#define AR_SREV_REVISION_9330_11 1 +#define AR_SREV_REVISION_9330_12 2 +#define AR_SREV_VERSION_9485 0x240 +#define AR_SREV_REVISION_9485_10 0 +#define AR_SREV_REVISION_9485_11 1 +#define AR_SREV_VERSION_9340 0x300 +#define AR_SREV_REVISION_9340_10 0 +#define AR_SREV_REVISION_9340_11 1 +#define AR_SREV_REVISION_9340_12 2 +#define AR_SREV_REVISION_9340_13 3 +#define AR_SREV_VERSION_9580 0x1C0 +#define AR_SREV_REVISION_9580_10 4 /* AR9580 1.0 */ +#define AR_SREV_VERSION_9462 0x280 +#define AR_SREV_REVISION_9462_20 2 +#define AR_SREV_VERSION_9565 0x2C0 +#define AR_SREV_REVISION_9565_10 0 +#define AR_SREV_VERSION_9550 0x400 + +#define AR_SREV_5416(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_5416_PCI) || \ + ((_ah)->hw_version.macVersion == AR_SREV_VERSION_5416_PCIE)) +#define AR_SREV_5416_22_OR_LATER(_ah) \ + (((AR_SREV_5416(_ah)) && \ + ((_ah)->hw_version.macRev >= AR_SREV_REVISION_5416_22)) || \ + ((_ah)->hw_version.macVersion >= AR_SREV_VERSION_9100)) + +#define AR_SREV_9100(ah) \ + ((ah->hw_version.macVersion) == AR_SREV_VERSION_9100) +#define AR_SREV_9100_OR_LATER(_ah) \ + (((_ah)->hw_version.macVersion >= AR_SREV_VERSION_9100)) + +#define AR_SREV_9160(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9160)) +#define AR_SREV_9160_10_OR_LATER(_ah) \ + (((_ah)->hw_version.macVersion >= AR_SREV_VERSION_9160)) +#define AR_SREV_9160_11(_ah) \ + (AR_SREV_9160(_ah) && \ + ((_ah)->hw_version.macRev == AR_SREV_REVISION_9160_11)) +#define AR_SREV_9280(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9280)) +#define AR_SREV_9280_20_OR_LATER(_ah) \ + (((_ah)->hw_version.macVersion >= AR_SREV_VERSION_9280)) +#define AR_SREV_9280_20(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9280)) + +#define AR_SREV_9285(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9285)) +#define AR_SREV_9285_12_OR_LATER(_ah) \ + (((_ah)->hw_version.macVersion >= AR_SREV_VERSION_9285)) + +#define AR_SREV_9287(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9287)) +#define AR_SREV_9287_11_OR_LATER(_ah) \ + (((_ah)->hw_version.macVersion >= AR_SREV_VERSION_9287)) +#define AR_SREV_9287_11(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9287) && \ + ((_ah)->hw_version.macRev == AR_SREV_REVISION_9287_11)) +#define AR_SREV_9287_12(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9287) && \ + ((_ah)->hw_version.macRev == AR_SREV_REVISION_9287_12)) +#define AR_SREV_9287_12_OR_LATER(_ah) \ + (((_ah)->hw_version.macVersion > AR_SREV_VERSION_9287) || \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9287) && \ + ((_ah)->hw_version.macRev >= AR_SREV_REVISION_9287_12))) +#define AR_SREV_9287_13_OR_LATER(_ah) \ + (((_ah)->hw_version.macVersion > AR_SREV_VERSION_9287) || \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9287) && \ + ((_ah)->hw_version.macRev >= AR_SREV_REVISION_9287_13))) + +#define AR_SREV_9271(_ah) \ + (((_ah))->hw_version.macVersion == AR_SREV_VERSION_9271) +#define AR_SREV_9271_10(_ah) \ + (AR_SREV_9271(_ah) && \ + ((_ah)->hw_version.macRev == AR_SREV_REVISION_9271_10)) +#define AR_SREV_9271_11(_ah) \ + (AR_SREV_9271(_ah) && \ + ((_ah)->hw_version.macRev == AR_SREV_REVISION_9271_11)) + +#define AR_SREV_9300(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9300)) +#define AR_SREV_9300_20_OR_LATER(_ah) \ + ((_ah)->hw_version.macVersion >= AR_SREV_VERSION_9300) +#define AR_SREV_9300_22(_ah) \ + (AR_SREV_9300(ah) && \ + ((_ah)->hw_version.macRev == AR_SREV_REVISION_9300_22)) + +#define AR_SREV_9330(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9330)) +#define AR_SREV_9330_10(_ah) \ + (AR_SREV_9330((_ah)) && \ + ((_ah)->hw_version.macRev == AR_SREV_REVISION_9330_10)) +#define AR_SREV_9330_11(_ah) \ + (AR_SREV_9330((_ah)) && \ + ((_ah)->hw_version.macRev == AR_SREV_REVISION_9330_11)) +#define AR_SREV_9330_12(_ah) \ + (AR_SREV_9330((_ah)) && \ + ((_ah)->hw_version.macRev == AR_SREV_REVISION_9330_12)) + +#define AR_SREV_9485(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9485)) +#define AR_SREV_9485_11(_ah) \ + (AR_SREV_9485(_ah) && \ + ((_ah)->hw_version.macRev == AR_SREV_REVISION_9485_11)) +#define AR_SREV_9485_OR_LATER(_ah) \ + (((_ah)->hw_version.macVersion >= AR_SREV_VERSION_9485)) + +#define AR_SREV_9340(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9340)) + +#define AR_SREV_9340_13_OR_LATER(_ah) \ + (AR_SREV_9340((_ah)) && \ + ((_ah)->hw_version.macRev >= AR_SREV_REVISION_9340_13)) + +#define AR_SREV_9285E_20(_ah) \ + (AR_SREV_9285_12_OR_LATER(_ah) && \ + ((REG_READ(_ah, AR_AN_SYNTH9) & 0x7) == 0x1)) + +#define AR_SREV_9462(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9462)) + +#define AR_SREV_9462_20(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9462) && \ + ((_ah)->hw_version.macRev == AR_SREV_REVISION_9462_20)) + +#define AR_SREV_9565(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9565)) + +#define AR_SREV_9565_10(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9565) && \ + ((_ah)->hw_version.macRev == AR_SREV_REVISION_9565_10)) + +#define AR_SREV_9550(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9550)) + +#define AR_SREV_9580(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9580) && \ + ((_ah)->hw_version.macRev >= AR_SREV_REVISION_9580_10)) + +#define AR_SREV_9580_10(_ah) \ + (((_ah)->hw_version.macVersion == AR_SREV_VERSION_9580) && \ + ((_ah)->hw_version.macRev == AR_SREV_REVISION_9580_10)) + +/* NOTE: When adding chips newer than Peacock, add chip check here */ +#define AR_SREV_9580_10_OR_LATER(_ah) \ + (AR_SREV_9580(_ah)) + +enum ath_usb_dev { + AR9280_USB = 1, /* AR7010 + AR9280, UB94 */ + AR9287_USB = 2, /* AR7010 + AR9287, UB95 */ + STORAGE_DEVICE = 3, +}; + +#define AR_DEVID_7010(_ah) \ + (((_ah)->hw_version.usbdev == AR9280_USB) || \ + ((_ah)->hw_version.usbdev == AR9287_USB)) + +#define AR_RADIO_SREV_MAJOR 0xf0 +#define AR_RAD5133_SREV_MAJOR 0xc0 +#define AR_RAD2133_SREV_MAJOR 0xd0 +#define AR_RAD5122_SREV_MAJOR 0xe0 +#define AR_RAD2122_SREV_MAJOR 0xf0 + +#define AR_AHB_MODE 0x4024 +#define AR_AHB_EXACT_WR_EN 0x00000000 +#define AR_AHB_BUF_WR_EN 0x00000001 +#define AR_AHB_EXACT_RD_EN 0x00000000 +#define AR_AHB_CACHELINE_RD_EN 0x00000002 +#define AR_AHB_PREFETCH_RD_EN 0x00000004 +#define AR_AHB_PAGE_SIZE_1K 0x00000000 +#define AR_AHB_PAGE_SIZE_2K 0x00000008 +#define AR_AHB_PAGE_SIZE_4K 0x00000010 +#define AR_AHB_CUSTOM_BURST_EN 0x000000C0 +#define AR_AHB_CUSTOM_BURST_EN_S 6 +#define AR_AHB_CUSTOM_BURST_ASYNC_FIFO_VAL 3 + +#define AR_INTR_RTC_IRQ 0x00000001 +#define AR_INTR_MAC_IRQ 0x00000002 +#define AR_INTR_EEP_PROT_ACCESS 0x00000004 +#define AR_INTR_MAC_AWAKE 0x00020000 +#define AR_INTR_MAC_ASLEEP 0x00040000 +#define AR_INTR_SPURIOUS 0xFFFFFFFF + + +#define AR_INTR_SYNC_CAUSE (AR_SREV_9340(ah) ? 0x4010 : 0x4028) +#define AR_INTR_SYNC_CAUSE_CLR (AR_SREV_9340(ah) ? 0x4010 : 0x4028) + + +#define AR_INTR_SYNC_ENABLE (AR_SREV_9340(ah) ? 0x4014 : 0x402c) +#define AR_INTR_SYNC_ENABLE_GPIO 0xFFFC0000 +#define AR_INTR_SYNC_ENABLE_GPIO_S 18 + +enum { + AR_INTR_SYNC_RTC_IRQ = 0x00000001, + AR_INTR_SYNC_MAC_IRQ = 0x00000002, + AR_INTR_SYNC_EEPROM_ILLEGAL_ACCESS = 0x00000004, + AR_INTR_SYNC_APB_TIMEOUT = 0x00000008, + AR_INTR_SYNC_PCI_MODE_CONFLICT = 0x00000010, + AR_INTR_SYNC_HOST1_FATAL = 0x00000020, + AR_INTR_SYNC_HOST1_PERR = 0x00000040, + AR_INTR_SYNC_TRCV_FIFO_PERR = 0x00000080, + AR_INTR_SYNC_RADM_CPL_EP = 0x00000100, + AR_INTR_SYNC_RADM_CPL_DLLP_ABORT = 0x00000200, + AR_INTR_SYNC_RADM_CPL_TLP_ABORT = 0x00000400, + AR_INTR_SYNC_RADM_CPL_ECRC_ERR = 0x00000800, + AR_INTR_SYNC_RADM_CPL_TIMEOUT = 0x00001000, + AR_INTR_SYNC_LOCAL_TIMEOUT = 0x00002000, + AR_INTR_SYNC_PM_ACCESS = 0x00004000, + AR_INTR_SYNC_MAC_AWAKE = 0x00008000, + AR_INTR_SYNC_MAC_ASLEEP = 0x00010000, + AR_INTR_SYNC_MAC_SLEEP_ACCESS = 0x00020000, + AR_INTR_SYNC_ALL = 0x0003FFFF, + + + AR_INTR_SYNC_DEFAULT = (AR_INTR_SYNC_HOST1_FATAL | + AR_INTR_SYNC_HOST1_PERR | + AR_INTR_SYNC_RADM_CPL_EP | + AR_INTR_SYNC_RADM_CPL_DLLP_ABORT | + AR_INTR_SYNC_RADM_CPL_TLP_ABORT | + AR_INTR_SYNC_RADM_CPL_ECRC_ERR | + AR_INTR_SYNC_RADM_CPL_TIMEOUT | + AR_INTR_SYNC_LOCAL_TIMEOUT | + AR_INTR_SYNC_MAC_SLEEP_ACCESS), + + AR9340_INTR_SYNC_LOCAL_TIMEOUT = 0x00000010, + + AR_INTR_SYNC_SPURIOUS = 0xFFFFFFFF, + +}; + +#define AR_INTR_ASYNC_MASK (AR_SREV_9340(ah) ? 0x4018 : 0x4030) +#define AR_INTR_ASYNC_MASK_GPIO 0xFFFC0000 +#define AR_INTR_ASYNC_MASK_GPIO_S 18 +#define AR_INTR_ASYNC_MASK_MCI 0x00000080 +#define AR_INTR_ASYNC_MASK_MCI_S 7 + +#define AR_INTR_SYNC_MASK (AR_SREV_9340(ah) ? 0x401c : 0x4034) +#define AR_INTR_SYNC_MASK_GPIO 0xFFFC0000 +#define AR_INTR_SYNC_MASK_GPIO_S 18 + +#define AR_INTR_ASYNC_CAUSE_CLR (AR_SREV_9340(ah) ? 0x4020 : 0x4038) +#define AR_INTR_ASYNC_CAUSE (AR_SREV_9340(ah) ? 0x4020 : 0x4038) +#define AR_INTR_ASYNC_CAUSE_MCI 0x00000080 +#define AR_INTR_ASYNC_USED (AR_INTR_MAC_IRQ | \ + AR_INTR_ASYNC_CAUSE_MCI) + +/* Asynchronous Interrupt Enable Register */ +#define AR_INTR_ASYNC_ENABLE_MCI 0x00000080 +#define AR_INTR_ASYNC_ENABLE_MCI_S 7 + + +#define AR_INTR_ASYNC_ENABLE (AR_SREV_9340(ah) ? 0x4024 : 0x403c) +#define AR_INTR_ASYNC_ENABLE_GPIO 0xFFFC0000 +#define AR_INTR_ASYNC_ENABLE_GPIO_S 18 + +#define AR_PCIE_SERDES 0x4040 +#define AR_PCIE_SERDES2 0x4044 +#define AR_PCIE_PM_CTRL (AR_SREV_9340(ah) ? 0x4004 : 0x4014) +#define AR_PCIE_PM_CTRL_ENA 0x00080000 + +#define AR_PCIE_PHY_REG3 0x18c08 + +#define AR_NUM_GPIO 14 +#define AR928X_NUM_GPIO 10 +#define AR9285_NUM_GPIO 12 +#define AR9287_NUM_GPIO 11 +#define AR9271_NUM_GPIO 16 +#define AR9300_NUM_GPIO 17 +#define AR7010_NUM_GPIO 16 + +#define AR_GPIO_IN_OUT (AR_SREV_9340(ah) ? 0x4028 : 0x4048) +#define AR_GPIO_IN_VAL 0x0FFFC000 +#define AR_GPIO_IN_VAL_S 14 +#define AR928X_GPIO_IN_VAL 0x000FFC00 +#define AR928X_GPIO_IN_VAL_S 10 +#define AR9285_GPIO_IN_VAL 0x00FFF000 +#define AR9285_GPIO_IN_VAL_S 12 +#define AR9287_GPIO_IN_VAL 0x003FF800 +#define AR9287_GPIO_IN_VAL_S 11 +#define AR9271_GPIO_IN_VAL 0xFFFF0000 +#define AR9271_GPIO_IN_VAL_S 16 +#define AR7010_GPIO_IN_VAL 0x0000FFFF +#define AR7010_GPIO_IN_VAL_S 0 + +#define AR_GPIO_IN (AR_SREV_9340(ah) ? 0x402c : 0x404c) +#define AR9300_GPIO_IN_VAL 0x0001FFFF +#define AR9300_GPIO_IN_VAL_S 0 + +#define AR_GPIO_OE_OUT (AR_SREV_9340(ah) ? 0x4030 : \ + (AR_SREV_9300_20_OR_LATER(ah) ? 0x4050 : 0x404c)) +#define AR_GPIO_OE_OUT_DRV 0x3 +#define AR_GPIO_OE_OUT_DRV_NO 0x0 +#define AR_GPIO_OE_OUT_DRV_LOW 0x1 +#define AR_GPIO_OE_OUT_DRV_HI 0x2 +#define AR_GPIO_OE_OUT_DRV_ALL 0x3 + +#define AR7010_GPIO_OE 0x52000 +#define AR7010_GPIO_OE_MASK 0x1 +#define AR7010_GPIO_OE_AS_OUTPUT 0x0 +#define AR7010_GPIO_OE_AS_INPUT 0x1 +#define AR7010_GPIO_IN 0x52004 +#define AR7010_GPIO_OUT 0x52008 +#define AR7010_GPIO_SET 0x5200C +#define AR7010_GPIO_CLEAR 0x52010 +#define AR7010_GPIO_INT 0x52014 +#define AR7010_GPIO_INT_TYPE 0x52018 +#define AR7010_GPIO_INT_POLARITY 0x5201C +#define AR7010_GPIO_PENDING 0x52020 +#define AR7010_GPIO_INT_MASK 0x52024 +#define AR7010_GPIO_FUNCTION 0x52028 + +#define AR_GPIO_INTR_POL (AR_SREV_9340(ah) ? 0x4038 : \ + (AR_SREV_9300_20_OR_LATER(ah) ? 0x4058 : 0x4050)) +#define AR_GPIO_INTR_POL_VAL 0x0001FFFF +#define AR_GPIO_INTR_POL_VAL_S 0 + +#define AR_GPIO_INPUT_EN_VAL (AR_SREV_9340(ah) ? 0x403c : \ + (AR_SREV_9300_20_OR_LATER(ah) ? 0x405c : 0x4054)) +#define AR_GPIO_INPUT_EN_VAL_BT_PRIORITY_DEF 0x00000004 +#define AR_GPIO_INPUT_EN_VAL_BT_PRIORITY_S 2 +#define AR_GPIO_INPUT_EN_VAL_BT_FREQUENCY_DEF 0x00000008 +#define AR_GPIO_INPUT_EN_VAL_BT_FREQUENCY_S 3 +#define AR_GPIO_INPUT_EN_VAL_BT_ACTIVE_DEF 0x00000010 +#define AR_GPIO_INPUT_EN_VAL_BT_ACTIVE_S 4 +#define AR_GPIO_INPUT_EN_VAL_RFSILENT_DEF 0x00000080 +#define AR_GPIO_INPUT_EN_VAL_RFSILENT_DEF_S 7 +#define AR_GPIO_INPUT_EN_VAL_BT_PRIORITY_BB 0x00000400 +#define AR_GPIO_INPUT_EN_VAL_BT_PRIORITY_BB_S 10 +#define AR_GPIO_INPUT_EN_VAL_BT_ACTIVE_BB 0x00001000 +#define AR_GPIO_INPUT_EN_VAL_BT_ACTIVE_BB_S 12 +#define AR_GPIO_INPUT_EN_VAL_RFSILENT_BB 0x00008000 +#define AR_GPIO_INPUT_EN_VAL_RFSILENT_BB_S 15 +#define AR_GPIO_RTC_RESET_OVERRIDE_ENABLE 0x00010000 +#define AR_GPIO_JTAG_DISABLE 0x00020000 + +#define AR_GPIO_INPUT_MUX1 (AR_SREV_9340(ah) ? 0x4040 : \ + (AR_SREV_9300_20_OR_LATER(ah) ? 0x4060 : 0x4058)) +#define AR_GPIO_INPUT_MUX1_BT_ACTIVE 0x000f0000 +#define AR_GPIO_INPUT_MUX1_BT_ACTIVE_S 16 +#define AR_GPIO_INPUT_MUX1_BT_PRIORITY 0x00000f00 +#define AR_GPIO_INPUT_MUX1_BT_PRIORITY_S 8 + +#define AR_GPIO_INPUT_MUX2 (AR_SREV_9340(ah) ? 0x4044 : \ + (AR_SREV_9300_20_OR_LATER(ah) ? 0x4064 : 0x405c)) +#define AR_GPIO_INPUT_MUX2_CLK25 0x0000000f +#define AR_GPIO_INPUT_MUX2_CLK25_S 0 +#define AR_GPIO_INPUT_MUX2_RFSILENT 0x000000f0 +#define AR_GPIO_INPUT_MUX2_RFSILENT_S 4 +#define AR_GPIO_INPUT_MUX2_RTC_RESET 0x00000f00 +#define AR_GPIO_INPUT_MUX2_RTC_RESET_S 8 + +#define AR_GPIO_OUTPUT_MUX1 (AR_SREV_9340(ah) ? 0x4048 : \ + (AR_SREV_9300_20_OR_LATER(ah) ? 0x4068 : 0x4060)) +#define AR_GPIO_OUTPUT_MUX2 (AR_SREV_9340(ah) ? 0x404c : \ + (AR_SREV_9300_20_OR_LATER(ah) ? 0x406c : 0x4064)) +#define AR_GPIO_OUTPUT_MUX3 (AR_SREV_9340(ah) ? 0x4050 : \ + (AR_SREV_9300_20_OR_LATER(ah) ? 0x4070 : 0x4068)) + +#define AR_INPUT_STATE (AR_SREV_9340(ah) ? 0x4054 : \ + (AR_SREV_9300_20_OR_LATER(ah) ? 0x4074 : 0x406c)) + +#define AR_EEPROM_STATUS_DATA (AR_SREV_9340(ah) ? 0x40c8 : \ + (AR_SREV_9300_20_OR_LATER(ah) ? 0x4084 : 0x407c)) +#define AR_EEPROM_STATUS_DATA_VAL 0x0000ffff +#define AR_EEPROM_STATUS_DATA_VAL_S 0 +#define AR_EEPROM_STATUS_DATA_BUSY 0x00010000 +#define AR_EEPROM_STATUS_DATA_BUSY_ACCESS 0x00020000 +#define AR_EEPROM_STATUS_DATA_PROT_ACCESS 0x00040000 +#define AR_EEPROM_STATUS_DATA_ABSENT_ACCESS 0x00080000 + +#define AR_OBS (AR_SREV_9340(ah) ? 0x405c : \ + (AR_SREV_9300_20_OR_LATER(ah) ? 0x4088 : 0x4080)) + +#define AR_GPIO_PDPU (AR_SREV_9300_20_OR_LATER(ah) ? 0x4090 : 0x4088) + +#define AR_PCIE_MSI (AR_SREV_9340(ah) ? 0x40d8 : \ + (AR_SREV_9300_20_OR_LATER(ah) ? 0x40a4 : 0x4094)) +#define AR_PCIE_MSI_ENABLE 0x00000001 + +#define AR_INTR_PRIO_SYNC_ENABLE (AR_SREV_9340(ah) ? 0x4088 : 0x40c4) +#define AR_INTR_PRIO_ASYNC_MASK (AR_SREV_9340(ah) ? 0x408c : 0x40c8) +#define AR_INTR_PRIO_SYNC_MASK (AR_SREV_9340(ah) ? 0x4090 : 0x40cc) +#define AR_INTR_PRIO_ASYNC_ENABLE (AR_SREV_9340(ah) ? 0x4094 : 0x40d4) +#define AR_ENT_OTP 0x40d8 +#define AR_ENT_OTP_CHAIN2_DISABLE 0x00020000 +#define AR_ENT_OTP_49GHZ_DISABLE 0x00100000 +#define AR_ENT_OTP_MIN_PKT_SIZE_DISABLE 0x00800000 + +#define AR_CH0_BB_DPLL1 0x16180 +#define AR_CH0_BB_DPLL1_REFDIV 0xF8000000 +#define AR_CH0_BB_DPLL1_REFDIV_S 27 +#define AR_CH0_BB_DPLL1_NINI 0x07FC0000 +#define AR_CH0_BB_DPLL1_NINI_S 18 +#define AR_CH0_BB_DPLL1_NFRAC 0x0003FFFF +#define AR_CH0_BB_DPLL1_NFRAC_S 0 + +#define AR_CH0_BB_DPLL2 0x16184 +#define AR_CH0_BB_DPLL2_LOCAL_PLL 0x40000000 +#define AR_CH0_BB_DPLL2_LOCAL_PLL_S 30 +#define AR_CH0_DPLL2_KI 0x3C000000 +#define AR_CH0_DPLL2_KI_S 26 +#define AR_CH0_DPLL2_KD 0x03F80000 +#define AR_CH0_DPLL2_KD_S 19 +#define AR_CH0_BB_DPLL2_EN_NEGTRIG 0x00040000 +#define AR_CH0_BB_DPLL2_EN_NEGTRIG_S 18 +#define AR_CH0_BB_DPLL2_PLL_PWD 0x00010000 +#define AR_CH0_BB_DPLL2_PLL_PWD_S 16 +#define AR_CH0_BB_DPLL2_OUTDIV 0x0000E000 +#define AR_CH0_BB_DPLL2_OUTDIV_S 13 + +#define AR_CH0_BB_DPLL3 0x16188 +#define AR_CH0_BB_DPLL3_PHASE_SHIFT 0x3F800000 +#define AR_CH0_BB_DPLL3_PHASE_SHIFT_S 23 + +#define AR_CH0_DDR_DPLL2 0x16244 +#define AR_CH0_DDR_DPLL3 0x16248 +#define AR_CH0_DPLL3_PHASE_SHIFT 0x3F800000 +#define AR_CH0_DPLL3_PHASE_SHIFT_S 23 +#define AR_PHY_CCA_NOM_VAL_2GHZ -118 + +#define AR_RTC_9300_PLL_DIV 0x000003ff +#define AR_RTC_9300_PLL_DIV_S 0 +#define AR_RTC_9300_PLL_REFDIV 0x00003C00 +#define AR_RTC_9300_PLL_REFDIV_S 10 +#define AR_RTC_9300_PLL_CLKSEL 0x0000C000 +#define AR_RTC_9300_PLL_CLKSEL_S 14 + +#define AR_RTC_9160_PLL_DIV 0x000003ff +#define AR_RTC_9160_PLL_DIV_S 0 +#define AR_RTC_9160_PLL_REFDIV 0x00003C00 +#define AR_RTC_9160_PLL_REFDIV_S 10 +#define AR_RTC_9160_PLL_CLKSEL 0x0000C000 +#define AR_RTC_9160_PLL_CLKSEL_S 14 + +#define AR_RTC_BASE 0x00020000 +#define AR_RTC_RC \ + ((AR_SREV_9100(ah)) ? (AR_RTC_BASE + 0x0000) : 0x7000) +#define AR_RTC_RC_M 0x00000003 +#define AR_RTC_RC_MAC_WARM 0x00000001 +#define AR_RTC_RC_MAC_COLD 0x00000002 +#define AR_RTC_RC_COLD_RESET 0x00000004 +#define AR_RTC_RC_WARM_RESET 0x00000008 + +/* Crystal Control */ +#define AR_RTC_XTAL_CONTROL 0x7004 + +/* Reg Control 0 */ +#define AR_RTC_REG_CONTROL0 0x7008 + +/* Reg Control 1 */ +#define AR_RTC_REG_CONTROL1 0x700c +#define AR_RTC_REG_CONTROL1_SWREG_PROGRAM 0x00000001 + +#define AR_RTC_PLL_CONTROL \ + ((AR_SREV_9100(ah)) ? (AR_RTC_BASE + 0x0014) : 0x7014) + +#define AR_RTC_PLL_CONTROL2 0x703c + +#define AR_RTC_PLL_DIV 0x0000001f +#define AR_RTC_PLL_DIV_S 0 +#define AR_RTC_PLL_DIV2 0x00000020 +#define AR_RTC_PLL_REFDIV_5 0x000000c0 +#define AR_RTC_PLL_CLKSEL 0x00000300 +#define AR_RTC_PLL_CLKSEL_S 8 +#define AR_RTC_PLL_BYPASS 0x00010000 +#define AR_RTC_PLL_NOPWD 0x00040000 +#define AR_RTC_PLL_NOPWD_S 18 + +#define PLL3 0x16188 +#define PLL3_DO_MEAS_MASK 0x40000000 +#define PLL4 0x1618c +#define PLL4_MEAS_DONE 0x8 +#define SQSUM_DVC_MASK 0x007ffff8 + +#define AR_RTC_RESET \ + ((AR_SREV_9100(ah)) ? (AR_RTC_BASE + 0x0040) : 0x7040) +#define AR_RTC_RESET_EN (0x00000001) + +#define AR_RTC_STATUS \ + ((AR_SREV_9100(ah)) ? (AR_RTC_BASE + 0x0044) : 0x7044) + +#define AR_RTC_STATUS_M \ + ((AR_SREV_9100(ah)) ? 0x0000003f : 0x0000000f) + +#define AR_RTC_PM_STATUS_M 0x0000000f + +#define AR_RTC_STATUS_SHUTDOWN 0x00000001 +#define AR_RTC_STATUS_ON 0x00000002 +#define AR_RTC_STATUS_SLEEP 0x00000004 +#define AR_RTC_STATUS_WAKEUP 0x00000008 + +#define AR_RTC_SLEEP_CLK \ + ((AR_SREV_9100(ah)) ? (AR_RTC_BASE + 0x0048) : 0x7048) +#define AR_RTC_FORCE_DERIVED_CLK 0x2 +#define AR_RTC_FORCE_SWREG_PRD 0x00000004 + +#define AR_RTC_FORCE_WAKE \ + ((AR_SREV_9100(ah)) ? (AR_RTC_BASE + 0x004c) : 0x704c) +#define AR_RTC_FORCE_WAKE_EN 0x00000001 +#define AR_RTC_FORCE_WAKE_ON_INT 0x00000002 + + +#define AR_RTC_INTR_CAUSE \ + ((AR_SREV_9100(ah)) ? (AR_RTC_BASE + 0x0050) : 0x7050) + +#define AR_RTC_INTR_ENABLE \ + ((AR_SREV_9100(ah)) ? (AR_RTC_BASE + 0x0054) : 0x7054) + +#define AR_RTC_INTR_MASK \ + ((AR_SREV_9100(ah)) ? (AR_RTC_BASE + 0x0058) : 0x7058) + +#define AR_RTC_KEEP_AWAKE 0x7034 + +/* RTC_DERIVED_* - only for AR9100 */ + +#define AR_RTC_DERIVED_CLK \ + (AR_SREV_9100(ah) ? (AR_RTC_BASE + 0x0038) : 0x7038) +#define AR_RTC_DERIVED_CLK_PERIOD 0x0000fffe +#define AR_RTC_DERIVED_CLK_PERIOD_S 1 + +#define AR_SEQ_MASK 0x8060 + +#define AR_AN_RF2G1_CH0 0x7810 +#define AR_AN_RF2G1_CH0_OB 0x03800000 +#define AR_AN_RF2G1_CH0_OB_S 23 +#define AR_AN_RF2G1_CH0_DB 0x1C000000 +#define AR_AN_RF2G1_CH0_DB_S 26 + +#define AR_AN_RF5G1_CH0 0x7818 +#define AR_AN_RF5G1_CH0_OB5 0x00070000 +#define AR_AN_RF5G1_CH0_OB5_S 16 +#define AR_AN_RF5G1_CH0_DB5 0x00380000 +#define AR_AN_RF5G1_CH0_DB5_S 19 + +#define AR_AN_RF2G1_CH1 0x7834 +#define AR_AN_RF2G1_CH1_OB 0x03800000 +#define AR_AN_RF2G1_CH1_OB_S 23 +#define AR_AN_RF2G1_CH1_DB 0x1C000000 +#define AR_AN_RF2G1_CH1_DB_S 26 + +#define AR_AN_RF5G1_CH1 0x783C +#define AR_AN_RF5G1_CH1_OB5 0x00070000 +#define AR_AN_RF5G1_CH1_OB5_S 16 +#define AR_AN_RF5G1_CH1_DB5 0x00380000 +#define AR_AN_RF5G1_CH1_DB5_S 19 + +#define AR_AN_TOP1 0x7890 +#define AR_AN_TOP1_DACIPMODE 0x00040000 +#define AR_AN_TOP1_DACIPMODE_S 18 + +#define AR_AN_TOP2 0x7894 +#define AR_AN_TOP2_XPABIAS_LVL 0xC0000000 +#define AR_AN_TOP2_XPABIAS_LVL_S 30 +#define AR_AN_TOP2_LOCALBIAS 0x00200000 +#define AR_AN_TOP2_LOCALBIAS_S 21 +#define AR_AN_TOP2_PWDCLKIND 0x00400000 +#define AR_AN_TOP2_PWDCLKIND_S 22 + +#define AR_AN_SYNTH9 0x7868 +#define AR_AN_SYNTH9_REFDIVA 0xf8000000 +#define AR_AN_SYNTH9_REFDIVA_S 27 + +#define AR9285_AN_RF2G1 0x7820 +#define AR9285_AN_RF2G1_ENPACAL 0x00000800 +#define AR9285_AN_RF2G1_ENPACAL_S 11 +#define AR9285_AN_RF2G1_PDPADRV1 0x02000000 +#define AR9285_AN_RF2G1_PDPADRV1_S 25 +#define AR9285_AN_RF2G1_PDPADRV2 0x01000000 +#define AR9285_AN_RF2G1_PDPADRV2_S 24 +#define AR9285_AN_RF2G1_PDPAOUT 0x00800000 +#define AR9285_AN_RF2G1_PDPAOUT_S 23 + + +#define AR9285_AN_RF2G2 0x7824 +#define AR9285_AN_RF2G2_OFFCAL 0x00001000 +#define AR9285_AN_RF2G2_OFFCAL_S 12 + +#define AR9285_AN_RF2G3 0x7828 +#define AR9285_AN_RF2G3_PDVCCOMP 0x02000000 +#define AR9285_AN_RF2G3_PDVCCOMP_S 25 +#define AR9285_AN_RF2G3_OB_0 0x00E00000 +#define AR9285_AN_RF2G3_OB_0_S 21 +#define AR9285_AN_RF2G3_OB_1 0x001C0000 +#define AR9285_AN_RF2G3_OB_1_S 18 +#define AR9285_AN_RF2G3_OB_2 0x00038000 +#define AR9285_AN_RF2G3_OB_2_S 15 +#define AR9285_AN_RF2G3_OB_3 0x00007000 +#define AR9285_AN_RF2G3_OB_3_S 12 +#define AR9285_AN_RF2G3_OB_4 0x00000E00 +#define AR9285_AN_RF2G3_OB_4_S 9 + +#define AR9285_AN_RF2G3_DB1_0 0x000001C0 +#define AR9285_AN_RF2G3_DB1_0_S 6 +#define AR9285_AN_RF2G3_DB1_1 0x00000038 +#define AR9285_AN_RF2G3_DB1_1_S 3 +#define AR9285_AN_RF2G3_DB1_2 0x00000007 +#define AR9285_AN_RF2G3_DB1_2_S 0 +#define AR9285_AN_RF2G4 0x782C +#define AR9285_AN_RF2G4_DB1_3 0xE0000000 +#define AR9285_AN_RF2G4_DB1_3_S 29 +#define AR9285_AN_RF2G4_DB1_4 0x1C000000 +#define AR9285_AN_RF2G4_DB1_4_S 26 + +#define AR9285_AN_RF2G4_DB2_0 0x03800000 +#define AR9285_AN_RF2G4_DB2_0_S 23 +#define AR9285_AN_RF2G4_DB2_1 0x00700000 +#define AR9285_AN_RF2G4_DB2_1_S 20 +#define AR9285_AN_RF2G4_DB2_2 0x000E0000 +#define AR9285_AN_RF2G4_DB2_2_S 17 +#define AR9285_AN_RF2G4_DB2_3 0x0001C000 +#define AR9285_AN_RF2G4_DB2_3_S 14 +#define AR9285_AN_RF2G4_DB2_4 0x00003800 +#define AR9285_AN_RF2G4_DB2_4_S 11 + +#define AR9285_RF2G5 0x7830 +#define AR9285_RF2G5_IC50TX 0xfffff8ff +#define AR9285_RF2G5_IC50TX_SET 0x00000400 +#define AR9285_RF2G5_IC50TX_XE_SET 0x00000500 +#define AR9285_RF2G5_IC50TX_CLEAR 0x00000700 +#define AR9285_RF2G5_IC50TX_CLEAR_S 8 + +/* AR9271 : 0x7828, 0x782c different setting from AR9285 */ +#define AR9271_AN_RF2G3_OB_cck 0x001C0000 +#define AR9271_AN_RF2G3_OB_cck_S 18 +#define AR9271_AN_RF2G3_OB_psk 0x00038000 +#define AR9271_AN_RF2G3_OB_psk_S 15 +#define AR9271_AN_RF2G3_OB_qam 0x00007000 +#define AR9271_AN_RF2G3_OB_qam_S 12 + +#define AR9271_AN_RF2G3_DB_1 0x00E00000 +#define AR9271_AN_RF2G3_DB_1_S 21 + +#define AR9271_AN_RF2G3_CCOMP 0xFFF +#define AR9271_AN_RF2G3_CCOMP_S 0 + +#define AR9271_AN_RF2G4_DB_2 0xE0000000 +#define AR9271_AN_RF2G4_DB_2_S 29 + +#define AR9285_AN_RF2G6 0x7834 +#define AR9285_AN_RF2G6_CCOMP 0x00007800 +#define AR9285_AN_RF2G6_CCOMP_S 11 +#define AR9285_AN_RF2G6_OFFS 0x03f00000 +#define AR9285_AN_RF2G6_OFFS_S 20 + +#define AR9271_AN_RF2G6_OFFS 0x07f00000 +#define AR9271_AN_RF2G6_OFFS_S 20 + +#define AR9285_AN_RF2G7 0x7838 +#define AR9285_AN_RF2G7_PWDDB 0x00000002 +#define AR9285_AN_RF2G7_PWDDB_S 1 +#define AR9285_AN_RF2G7_PADRVGN2TAB0 0xE0000000 +#define AR9285_AN_RF2G7_PADRVGN2TAB0_S 29 + +#define AR9285_AN_RF2G8 0x783C +#define AR9285_AN_RF2G8_PADRVGN2TAB0 0x0001C000 +#define AR9285_AN_RF2G8_PADRVGN2TAB0_S 14 + + +#define AR9285_AN_RF2G9 0x7840 +#define AR9285_AN_RXTXBB1 0x7854 +#define AR9285_AN_RXTXBB1_PDRXTXBB1 0x00000020 +#define AR9285_AN_RXTXBB1_PDRXTXBB1_S 5 +#define AR9285_AN_RXTXBB1_PDV2I 0x00000080 +#define AR9285_AN_RXTXBB1_PDV2I_S 7 +#define AR9285_AN_RXTXBB1_PDDACIF 0x00000100 +#define AR9285_AN_RXTXBB1_PDDACIF_S 8 +#define AR9285_AN_RXTXBB1_SPARE9 0x00000001 +#define AR9285_AN_RXTXBB1_SPARE9_S 0 + +#define AR9285_AN_TOP2 0x7868 + +#define AR9285_AN_TOP3 0x786c +#define AR9285_AN_TOP3_XPABIAS_LVL 0x0000000C +#define AR9285_AN_TOP3_XPABIAS_LVL_S 2 +#define AR9285_AN_TOP3_PWDDAC 0x00800000 +#define AR9285_AN_TOP3_PWDDAC_S 23 + +#define AR9285_AN_TOP4 0x7870 +#define AR9285_AN_TOP4_DEFAULT 0x10142c00 + +#define AR9287_AN_RF2G3_CH0 0x7808 +#define AR9287_AN_RF2G3_CH1 0x785c +#define AR9287_AN_RF2G3_DB1 0xE0000000 +#define AR9287_AN_RF2G3_DB1_S 29 +#define AR9287_AN_RF2G3_DB2 0x1C000000 +#define AR9287_AN_RF2G3_DB2_S 26 +#define AR9287_AN_RF2G3_OB_CCK 0x03800000 +#define AR9287_AN_RF2G3_OB_CCK_S 23 +#define AR9287_AN_RF2G3_OB_PSK 0x00700000 +#define AR9287_AN_RF2G3_OB_PSK_S 20 +#define AR9287_AN_RF2G3_OB_QAM 0x000E0000 +#define AR9287_AN_RF2G3_OB_QAM_S 17 +#define AR9287_AN_RF2G3_OB_PAL_OFF 0x0001C000 +#define AR9287_AN_RF2G3_OB_PAL_OFF_S 14 + +#define AR9287_AN_TXPC0 0x7898 +#define AR9287_AN_TXPC0_TXPCMODE 0x0000C000 +#define AR9287_AN_TXPC0_TXPCMODE_S 14 +#define AR9287_AN_TXPC0_TXPCMODE_NORMAL 0 +#define AR9287_AN_TXPC0_TXPCMODE_TEST 1 +#define AR9287_AN_TXPC0_TXPCMODE_TEMPSENSE 2 +#define AR9287_AN_TXPC0_TXPCMODE_ATBTEST 3 + +#define AR9287_AN_TOP2 0x78b4 +#define AR9287_AN_TOP2_XPABIAS_LVL 0xC0000000 +#define AR9287_AN_TOP2_XPABIAS_LVL_S 30 + +/* AR9271 specific stuff */ +#define AR9271_RESET_POWER_DOWN_CONTROL 0x50044 +#define AR9271_RADIO_RF_RST 0x20 +#define AR9271_GATE_MAC_CTL 0x4000 + +#define AR_STA_ID1_STA_AP 0x00010000 +#define AR_STA_ID1_ADHOC 0x00020000 +#define AR_STA_ID1_PWR_SAV 0x00040000 +#define AR_STA_ID1_KSRCHDIS 0x00080000 +#define AR_STA_ID1_PCF 0x00100000 +#define AR_STA_ID1_USE_DEFANT 0x00200000 +#define AR_STA_ID1_DEFANT_UPDATE 0x00400000 +#define AR_STA_ID1_AR9100_BA_FIX 0x00400000 +#define AR_STA_ID1_RTS_USE_DEF 0x00800000 +#define AR_STA_ID1_ACKCTS_6MB 0x01000000 +#define AR_STA_ID1_BASE_RATE_11B 0x02000000 +#define AR_STA_ID1_SECTOR_SELF_GEN 0x04000000 +#define AR_STA_ID1_CRPT_MIC_ENABLE 0x08000000 +#define AR_STA_ID1_KSRCH_MODE 0x10000000 +#define AR_STA_ID1_PRESERVE_SEQNUM 0x20000000 +#define AR_STA_ID1_CBCIV_ENDIAN 0x40000000 +#define AR_STA_ID1_MCAST_KSRCH 0x80000000 + +#define AR_BSS_ID0 0x8008 +#define AR_BSS_ID1 0x800C +#define AR_BSS_ID1_U16 0x0000FFFF +#define AR_BSS_ID1_AID 0x07FF0000 +#define AR_BSS_ID1_AID_S 16 + +#define AR_BCN_RSSI_AVE 0x8010 +#define AR_BCN_RSSI_AVE_MASK 0x00000FFF + +#define AR_TIME_OUT 0x8014 +#define AR_TIME_OUT_ACK 0x00003FFF +#define AR_TIME_OUT_ACK_S 0 +#define AR_TIME_OUT_CTS 0x3FFF0000 +#define AR_TIME_OUT_CTS_S 16 + +#define AR_RSSI_THR 0x8018 +#define AR_RSSI_THR_MASK 0x000000FF +#define AR_RSSI_THR_BM_THR 0x0000FF00 +#define AR_RSSI_THR_BM_THR_S 8 +#define AR_RSSI_BCN_WEIGHT 0x1F000000 +#define AR_RSSI_BCN_WEIGHT_S 24 +#define AR_RSSI_BCN_RSSI_RST 0x20000000 + +#define AR_USEC 0x801c +#define AR_USEC_USEC 0x0000007F +#define AR_USEC_TX_LAT 0x007FC000 +#define AR_USEC_TX_LAT_S 14 +#define AR_USEC_RX_LAT 0x1F800000 +#define AR_USEC_RX_LAT_S 23 +#define AR_USEC_ASYNC_FIFO 0x12E00074 + +#define AR_RESET_TSF 0x8020 +#define AR_RESET_TSF_ONCE 0x01000000 + +#define AR_MAX_CFP_DUR 0x8038 +#define AR_CFP_VAL 0x0000FFFF + +#define AR_RX_FILTER 0x803C + +#define AR_MCAST_FIL0 0x8040 +#define AR_MCAST_FIL1 0x8044 + +/* + * AR_DIAG_SW - Register which can be used for diagnostics and testing purposes. + * + * The force RX abort (AR_DIAG_RX_ABORT, bit 25) can be used in conjunction with + * RX block (AR_DIAG_RX_DIS, bit 5) to help fast channel change to shut down + * receive. The force RX abort bit will kill any frame which is currently being + * transferred between the MAC and baseband. The RX block bit (AR_DIAG_RX_DIS) + * will prevent any new frames from getting started. + */ +#define AR_DIAG_SW 0x8048 +#define AR_DIAG_CACHE_ACK 0x00000001 +#define AR_DIAG_ACK_DIS 0x00000002 +#define AR_DIAG_CTS_DIS 0x00000004 +#define AR_DIAG_ENCRYPT_DIS 0x00000008 +#define AR_DIAG_DECRYPT_DIS 0x00000010 +#define AR_DIAG_RX_DIS 0x00000020 /* RX block */ +#define AR_DIAG_LOOP_BACK 0x00000040 +#define AR_DIAG_CORR_FCS 0x00000080 +#define AR_DIAG_CHAN_INFO 0x00000100 +#define AR_DIAG_SCRAM_SEED 0x0001FE00 +#define AR_DIAG_SCRAM_SEED_S 8 +#define AR_DIAG_FRAME_NV0 0x00020000 +#define AR_DIAG_OBS_PT_SEL1 0x000C0000 +#define AR_DIAG_OBS_PT_SEL1_S 18 +#define AR_DIAG_OBS_PT_SEL2 0x08000000 +#define AR_DIAG_OBS_PT_SEL2_S 27 +#define AR_DIAG_FORCE_RX_CLEAR 0x00100000 /* force rx_clear high */ +#define AR_DIAG_IGNORE_VIRT_CS 0x00200000 +#define AR_DIAG_FORCE_CH_IDLE_HIGH 0x00400000 +#define AR_DIAG_EIFS_CTRL_ENA 0x00800000 +#define AR_DIAG_DUAL_CHAIN_INFO 0x01000000 +#define AR_DIAG_RX_ABORT 0x02000000 /* Force RX abort */ +#define AR_DIAG_SATURATE_CYCLE_CNT 0x04000000 +#define AR_DIAG_OBS_PT_SEL2 0x08000000 +#define AR_DIAG_RX_CLEAR_CTL_LOW 0x10000000 +#define AR_DIAG_RX_CLEAR_EXT_LOW 0x20000000 + +#define AR_TSF_L32 0x804c +#define AR_TSF_U32 0x8050 + +#define AR_TST_ADDAC 0x8054 +#define AR_DEF_ANTENNA 0x8058 + +#define AR_AES_MUTE_MASK0 0x805c +#define AR_AES_MUTE_MASK0_FC 0x0000FFFF +#define AR_AES_MUTE_MASK0_QOS 0xFFFF0000 +#define AR_AES_MUTE_MASK0_QOS_S 16 + +#define AR_AES_MUTE_MASK1 0x8060 +#define AR_AES_MUTE_MASK1_SEQ 0x0000FFFF +#define AR_AES_MUTE_MASK1_FC_MGMT 0xFFFF0000 +#define AR_AES_MUTE_MASK1_FC_MGMT_S 16 + +#define AR_GATED_CLKS 0x8064 +#define AR_GATED_CLKS_TX 0x00000002 +#define AR_GATED_CLKS_RX 0x00000004 +#define AR_GATED_CLKS_REG 0x00000008 + +#define AR_OBS_BUS_CTRL 0x8068 +#define AR_OBS_BUS_SEL_1 0x00040000 +#define AR_OBS_BUS_SEL_2 0x00080000 +#define AR_OBS_BUS_SEL_3 0x000C0000 +#define AR_OBS_BUS_SEL_4 0x08040000 +#define AR_OBS_BUS_SEL_5 0x08080000 + +#define AR_OBS_BUS_1 0x806c +#define AR_OBS_BUS_1_PCU 0x00000001 +#define AR_OBS_BUS_1_RX_END 0x00000002 +#define AR_OBS_BUS_1_RX_WEP 0x00000004 +#define AR_OBS_BUS_1_RX_BEACON 0x00000008 +#define AR_OBS_BUS_1_RX_FILTER 0x00000010 +#define AR_OBS_BUS_1_TX_HCF 0x00000020 +#define AR_OBS_BUS_1_QUIET_TIME 0x00000040 +#define AR_OBS_BUS_1_CHAN_IDLE 0x00000080 +#define AR_OBS_BUS_1_TX_HOLD 0x00000100 +#define AR_OBS_BUS_1_TX_FRAME 0x00000200 +#define AR_OBS_BUS_1_RX_FRAME 0x00000400 +#define AR_OBS_BUS_1_RX_CLEAR 0x00000800 +#define AR_OBS_BUS_1_WEP_STATE 0x0003F000 +#define AR_OBS_BUS_1_WEP_STATE_S 12 +#define AR_OBS_BUS_1_RX_STATE 0x01F00000 +#define AR_OBS_BUS_1_RX_STATE_S 20 +#define AR_OBS_BUS_1_TX_STATE 0x7E000000 +#define AR_OBS_BUS_1_TX_STATE_S 25 + +#define AR_LAST_TSTP 0x8080 +#define AR_NAV 0x8084 +#define AR_RTS_OK 0x8088 +#define AR_RTS_FAIL 0x808c +#define AR_ACK_FAIL 0x8090 +#define AR_FCS_FAIL 0x8094 +#define AR_BEACON_CNT 0x8098 + +#define AR_SLEEP1 0x80d4 +#define AR_SLEEP1_ASSUME_DTIM 0x00080000 +#define AR_SLEEP1_CAB_TIMEOUT 0xFFE00000 +#define AR_SLEEP1_CAB_TIMEOUT_S 21 + +#define AR_SLEEP2 0x80d8 +#define AR_SLEEP2_BEACON_TIMEOUT 0xFFE00000 +#define AR_SLEEP2_BEACON_TIMEOUT_S 21 + +#define AR_TPC 0x80e8 +#define AR_TPC_ACK 0x0000003f +#define AR_TPC_ACK_S 0 +#define AR_TPC_CTS 0x00003f00 +#define AR_TPC_CTS_S 8 +#define AR_TPC_CHIRP 0x003f0000 +#define AR_TPC_CHIRP_S 16 + +#define AR_QUIET1 0x80fc +#define AR_QUIET1_NEXT_QUIET_S 0 +#define AR_QUIET1_NEXT_QUIET_M 0x0000ffff +#define AR_QUIET1_QUIET_ENABLE 0x00010000 +#define AR_QUIET1_QUIET_ACK_CTS_ENABLE 0x00020000 +#define AR_QUIET1_QUIET_ACK_CTS_ENABLE_S 17 +#define AR_QUIET2 0x8100 +#define AR_QUIET2_QUIET_PERIOD_S 0 +#define AR_QUIET2_QUIET_PERIOD_M 0x0000ffff +#define AR_QUIET2_QUIET_DUR_S 16 +#define AR_QUIET2_QUIET_DUR 0xffff0000 + +#define AR_TSF_PARM 0x8104 +#define AR_TSF_INCREMENT_M 0x000000ff +#define AR_TSF_INCREMENT_S 0x00 + +#define AR_QOS_NO_ACK 0x8108 +#define AR_QOS_NO_ACK_TWO_BIT 0x0000000f +#define AR_QOS_NO_ACK_TWO_BIT_S 0 +#define AR_QOS_NO_ACK_BIT_OFF 0x00000070 +#define AR_QOS_NO_ACK_BIT_OFF_S 4 +#define AR_QOS_NO_ACK_BYTE_OFF 0x00000180 +#define AR_QOS_NO_ACK_BYTE_OFF_S 7 + +#define AR_PHY_ERR 0x810c + +#define AR_PHY_ERR_DCHIRP 0x00000008 +#define AR_PHY_ERR_RADAR 0x00000020 +#define AR_PHY_ERR_OFDM_TIMING 0x00020000 +#define AR_PHY_ERR_CCK_TIMING 0x02000000 + +#define AR_RXFIFO_CFG 0x8114 + + +#define AR_MIC_QOS_CONTROL 0x8118 +#define AR_MIC_QOS_SELECT 0x811c + +#define AR_PCU_MISC 0x8120 +#define AR_PCU_FORCE_BSSID_MATCH 0x00000001 +#define AR_PCU_MIC_NEW_LOC_ENA 0x00000004 +#define AR_PCU_TX_ADD_TSF 0x00000008 +#define AR_PCU_CCK_SIFS_MODE 0x00000010 +#define AR_PCU_RX_ANT_UPDT 0x00000800 +#define AR_PCU_TXOP_TBTT_LIMIT_ENA 0x00001000 +#define AR_PCU_MISS_BCN_IN_SLEEP 0x00004000 +#define AR_PCU_BUG_12306_FIX_ENA 0x00020000 +#define AR_PCU_FORCE_QUIET_COLL 0x00040000 +#define AR_PCU_TBTT_PROTECT 0x00200000 +#define AR_PCU_CLEAR_VMF 0x01000000 +#define AR_PCU_CLEAR_BA_VALID 0x04000000 +#define AR_PCU_ALWAYS_PERFORM_KEYSEARCH 0x10000000 + +#define AR_PCU_BT_ANT_PREVENT_RX 0x00100000 +#define AR_PCU_BT_ANT_PREVENT_RX_S 20 + +#define AR_FILT_OFDM 0x8124 +#define AR_FILT_OFDM_COUNT 0x00FFFFFF + +#define AR_FILT_CCK 0x8128 +#define AR_FILT_CCK_COUNT 0x00FFFFFF + +#define AR_PHY_ERR_1 0x812c +#define AR_PHY_ERR_1_COUNT 0x00FFFFFF +#define AR_PHY_ERR_MASK_1 0x8130 + +#define AR_PHY_ERR_2 0x8134 +#define AR_PHY_ERR_2_COUNT 0x00FFFFFF +#define AR_PHY_ERR_MASK_2 0x8138 + +#define AR_PHY_COUNTMAX (3 << 22) +#define AR_MIBCNT_INTRMASK (3 << 22) + +#define AR_TSFOOR_THRESHOLD 0x813c +#define AR_TSFOOR_THRESHOLD_VAL 0x0000FFFF + +#define AR_PHY_ERR_EIFS_MASK 0x8144 + +#define AR_PHY_ERR_3 0x8168 +#define AR_PHY_ERR_3_COUNT 0x00FFFFFF +#define AR_PHY_ERR_MASK_3 0x816c + +#define AR_BT_COEX_MODE 0x8170 +#define AR_BT_TIME_EXTEND 0x000000ff +#define AR_BT_TIME_EXTEND_S 0 +#define AR_BT_TXSTATE_EXTEND 0x00000100 +#define AR_BT_TXSTATE_EXTEND_S 8 +#define AR_BT_TX_FRAME_EXTEND 0x00000200 +#define AR_BT_TX_FRAME_EXTEND_S 9 +#define AR_BT_MODE 0x00000c00 +#define AR_BT_MODE_S 10 +#define AR_BT_QUIET 0x00001000 +#define AR_BT_QUIET_S 12 +#define AR_BT_QCU_THRESH 0x0001e000 +#define AR_BT_QCU_THRESH_S 13 +#define AR_BT_RX_CLEAR_POLARITY 0x00020000 +#define AR_BT_RX_CLEAR_POLARITY_S 17 +#define AR_BT_PRIORITY_TIME 0x00fc0000 +#define AR_BT_PRIORITY_TIME_S 18 +#define AR_BT_FIRST_SLOT_TIME 0xff000000 +#define AR_BT_FIRST_SLOT_TIME_S 24 + +#define AR_BT_COEX_WEIGHT 0x8174 +#define AR_BT_COEX_WGHT 0xff55 +#define AR_STOMP_ALL_WLAN_WGHT 0xfcfc +#define AR_STOMP_LOW_WLAN_WGHT 0xa8a8 +#define AR_STOMP_NONE_WLAN_WGHT 0x0000 +#define AR_BTCOEX_BT_WGHT 0x0000ffff +#define AR_BTCOEX_BT_WGHT_S 0 +#define AR_BTCOEX_WL_WGHT 0xffff0000 +#define AR_BTCOEX_WL_WGHT_S 16 + +#define AR_BT_COEX_WL_WEIGHTS0 0x8174 +#define AR_BT_COEX_WL_WEIGHTS1 0x81c4 +#define AR_MCI_COEX_WL_WEIGHTS(_i) (0x18b0 + (_i << 2)) +#define AR_BT_COEX_BT_WEIGHTS(_i) (0x83ac + (_i << 2)) + +#define AR9300_BT_WGHT 0xcccc4444 + +#define AR_BT_COEX_MODE2 0x817c +#define AR_BT_BCN_MISS_THRESH 0x000000ff +#define AR_BT_BCN_MISS_THRESH_S 0 +#define AR_BT_BCN_MISS_CNT 0x0000ff00 +#define AR_BT_BCN_MISS_CNT_S 8 +#define AR_BT_HOLD_RX_CLEAR 0x00010000 +#define AR_BT_HOLD_RX_CLEAR_S 16 +#define AR_BT_DISABLE_BT_ANT 0x00100000 +#define AR_BT_DISABLE_BT_ANT_S 20 + +#define AR_TXSIFS 0x81d0 +#define AR_TXSIFS_TIME 0x000000FF +#define AR_TXSIFS_TX_LATENCY 0x00000F00 +#define AR_TXSIFS_TX_LATENCY_S 8 +#define AR_TXSIFS_ACK_SHIFT 0x00007000 +#define AR_TXSIFS_ACK_SHIFT_S 12 + +#define AR_TXOP_X 0x81ec +#define AR_TXOP_X_VAL 0x000000FF + + +#define AR_TXOP_0_3 0x81f0 +#define AR_TXOP_4_7 0x81f4 +#define AR_TXOP_8_11 0x81f8 +#define AR_TXOP_12_15 0x81fc + +#define AR_NEXT_NDP2_TIMER 0x8180 +#define AR_GEN_TIMER_BANK_1_LEN 8 +#define AR_FIRST_NDP_TIMER 7 +#define AR_NDP2_PERIOD 0x81a0 +#define AR_NDP2_TIMER_MODE 0x81c0 + +#define AR_GEN_TIMERS(_i) (0x8200 + ((_i) << 2)) +#define AR_NEXT_TBTT_TIMER AR_GEN_TIMERS(0) +#define AR_NEXT_DMA_BEACON_ALERT AR_GEN_TIMERS(1) +#define AR_NEXT_SWBA AR_GEN_TIMERS(2) +#define AR_NEXT_CFP AR_GEN_TIMERS(2) +#define AR_NEXT_HCF AR_GEN_TIMERS(3) +#define AR_NEXT_TIM AR_GEN_TIMERS(4) +#define AR_NEXT_DTIM AR_GEN_TIMERS(5) +#define AR_NEXT_QUIET_TIMER AR_GEN_TIMERS(6) +#define AR_NEXT_NDP_TIMER AR_GEN_TIMERS(7) + +#define AR_BEACON_PERIOD AR_GEN_TIMERS(8) +#define AR_DMA_BEACON_PERIOD AR_GEN_TIMERS(9) +#define AR_SWBA_PERIOD AR_GEN_TIMERS(10) +#define AR_HCF_PERIOD AR_GEN_TIMERS(11) +#define AR_TIM_PERIOD AR_GEN_TIMERS(12) +#define AR_DTIM_PERIOD AR_GEN_TIMERS(13) +#define AR_QUIET_PERIOD AR_GEN_TIMERS(14) +#define AR_NDP_PERIOD AR_GEN_TIMERS(15) + +#define AR_TIMER_MODE 0x8240 +#define AR_TBTT_TIMER_EN 0x00000001 +#define AR_DBA_TIMER_EN 0x00000002 +#define AR_SWBA_TIMER_EN 0x00000004 +#define AR_HCF_TIMER_EN 0x00000008 +#define AR_TIM_TIMER_EN 0x00000010 +#define AR_DTIM_TIMER_EN 0x00000020 +#define AR_QUIET_TIMER_EN 0x00000040 +#define AR_NDP_TIMER_EN 0x00000080 +#define AR_TIMER_OVERFLOW_INDEX 0x00000700 +#define AR_TIMER_OVERFLOW_INDEX_S 8 +#define AR_TIMER_THRESH 0xFFFFF000 +#define AR_TIMER_THRESH_S 12 + +#define AR_SLP32_MODE 0x8244 +#define AR_SLP32_HALF_CLK_LATENCY 0x000FFFFF +#define AR_SLP32_ENA 0x00100000 +#define AR_SLP32_TSF_WRITE_STATUS 0x00200000 + +#define AR_SLP32_WAKE 0x8248 +#define AR_SLP32_WAKE_XTL_TIME 0x0000FFFF + +#define AR_SLP32_INC 0x824c +#define AR_SLP32_TST_INC 0x000FFFFF + +#define AR_SLP_CNT 0x8250 +#define AR_SLP_CYCLE_CNT 0x8254 + +#define AR_SLP_MIB_CTRL 0x8258 +#define AR_SLP_MIB_CLEAR 0x00000001 +#define AR_SLP_MIB_PENDING 0x00000002 + +#define AR_MAC_PCU_LOGIC_ANALYZER 0x8264 +#define AR_MAC_PCU_LOGIC_ANALYZER_DISBUG20768 0x20000000 + + +#define AR_2040_MODE 0x8318 +#define AR_2040_JOINED_RX_CLEAR 0x00000001 + + +#define AR_EXTRCCNT 0x8328 + +#define AR_SELFGEN_MASK 0x832c + +#define AR_PCU_TXBUF_CTRL 0x8340 +#define AR_PCU_TXBUF_CTRL_SIZE_MASK 0x7FF +#define AR_PCU_TXBUF_CTRL_USABLE_SIZE 0x700 +#define AR_9285_PCU_TXBUF_CTRL_USABLE_SIZE 0x380 +#define AR_9340_PCU_TXBUF_CTRL_USABLE_SIZE 0x500 + +#define AR_PCU_MISC_MODE2 0x8344 +#define AR_PCU_MISC_MODE2_MGMT_CRYPTO_ENABLE 0x00000002 +#define AR_PCU_MISC_MODE2_NO_CRYPTO_FOR_NON_DATA_PKT 0x00000004 + +#define AR_PCU_MISC_MODE2_RESERVED 0x00000038 +#define AR_PCU_MISC_MODE2_ADHOC_MCAST_KEYID_ENABLE 0x00000040 +#define AR_PCU_MISC_MODE2_CFP_IGNORE 0x00000080 +#define AR_PCU_MISC_MODE2_MGMT_QOS 0x0000FF00 +#define AR_PCU_MISC_MODE2_MGMT_QOS_S 8 +#define AR_PCU_MISC_MODE2_ENABLE_LOAD_NAV_BEACON_DURATION 0x00010000 +#define AR_PCU_MISC_MODE2_ENABLE_AGGWEP 0x00020000 +#define AR_PCU_MISC_MODE2_HWWAR1 0x00100000 +#define AR_PCU_MISC_MODE2_HWWAR2 0x02000000 +#define AR_PCU_MISC_MODE2_RESERVED2 0xFFFE0000 + +#define AR_PCU_MISC_MODE3 0x83d0 + +#define AR_MAC_PCU_ASYNC_FIFO_REG3 0x8358 +#define AR_MAC_PCU_ASYNC_FIFO_REG3_DATAPATH_SEL 0x00000400 +#define AR_MAC_PCU_ASYNC_FIFO_REG3_SOFT_RESET 0x80000000 +#define AR_MAC_PCU_GEN_TIMER_TSF_SEL 0x83d8 + + +#define AR_AES_MUTE_MASK0 0x805c +#define AR_AES_MUTE_MASK0_FC 0x0000FFFF +#define AR_AES_MUTE_MASK0_QOS 0xFFFF0000 +#define AR_AES_MUTE_MASK0_QOS_S 16 + +#define AR_AES_MUTE_MASK1 0x8060 +#define AR_AES_MUTE_MASK1_SEQ 0x0000FFFF +#define AR_AES_MUTE_MASK1_SEQ_S 0 +#define AR_AES_MUTE_MASK1_FC_MGMT 0xFFFF0000 +#define AR_AES_MUTE_MASK1_FC_MGMT_S 16 + +#define AR_RATE_DURATION_0 0x8700 +#define AR_RATE_DURATION_31 0x87CC +#define AR_RATE_DURATION_32 0x8780 +#define AR_RATE_DURATION(_n) (AR_RATE_DURATION_0 + ((_n)<<2)) + +/* WoW - Wake On Wireless */ + +#define AR_PMCTRL_AUX_PWR_DET 0x10000000 /* Puts Chip in L2 state */ +#define AR_PMCTRL_D3COLD_VAUX 0x00800000 +#define AR_PMCTRL_HOST_PME_EN 0x00400000 /* Send OOB WAKE_L on WoW + event */ +#define AR_PMCTRL_WOW_PME_CLR 0x00200000 /* Clear WoW event */ +#define AR_PMCTRL_PWR_STATE_MASK 0x0f000000 /* Power State Mask */ +#define AR_PMCTRL_PWR_STATE_D1D3 0x0f000000 /* Activate D1 and D3 */ +#define AR_PMCTRL_PWR_STATE_D1D3_REAL 0x0f000000 /* Activate D1 and D3 */ +#define AR_PMCTRL_PWR_STATE_D0 0x08000000 /* Activate D0 */ +#define AR_PMCTRL_PWR_PM_CTRL_ENA 0x00008000 /* Enable power mgmt */ + +#define AR_WOW_BEACON_TIMO_MAX 0xffffffff + +/* + * MAC WoW Registers + */ + +#define AR_WOW_PATTERN 0x825C +#define AR_WOW_COUNT 0x8260 +#define AR_WOW_BCN_EN 0x8270 +#define AR_WOW_BCN_TIMO 0x8274 +#define AR_WOW_KEEP_ALIVE_TIMO 0x8278 +#define AR_WOW_KEEP_ALIVE 0x827c +#define AR_WOW_US_SCALAR 0x8284 +#define AR_WOW_KEEP_ALIVE_DELAY 0x8288 +#define AR_WOW_PATTERN_MATCH 0x828c +#define AR_WOW_PATTERN_OFF1 0x8290 /* pattern bytes 0 -> 3 */ +#define AR_WOW_PATTERN_OFF2 0x8294 /* pattern bytes 4 -> 7 */ + +/* for AR9285 or later version of chips */ +#define AR_WOW_EXACT 0x829c +#define AR_WOW_LENGTH1 0x8360 +#define AR_WOW_LENGTH2 0X8364 +/* register to enable match for less than 256 bytes packets */ +#define AR_WOW_PATTERN_MATCH_LT_256B 0x8368 + +#define AR_SW_WOW_CONTROL 0x20018 +#define AR_SW_WOW_ENABLE 0x1 +#define AR_SWITCH_TO_REFCLK 0x2 +#define AR_RESET_CONTROL 0x4 +#define AR_RESET_VALUE_MASK 0x8 +#define AR_HW_WOW_DISABLE 0x10 +#define AR_CLR_MAC_INTERRUPT 0x20 +#define AR_CLR_KA_INTERRUPT 0x40 + +/* AR_WOW_PATTERN register values */ +#define AR_WOW_BACK_OFF_SHIFT(x) ((x & 0xf) << 28) /* in usecs */ +#define AR_WOW_MAC_INTR_EN 0x00040000 +#define AR_WOW_MAGIC_EN 0x00010000 +#define AR_WOW_PATTERN_EN(x) (x & 0xff) +#define AR_WOW_PAT_FOUND_SHIFT 8 +#define AR_WOW_PATTERN_FOUND(x) (x & (0xff << AR_WOW_PAT_FOUND_SHIFT)) +#define AR_WOW_PATTERN_FOUND_MASK ((0xff) << AR_WOW_PAT_FOUND_SHIFT) +#define AR_WOW_MAGIC_PAT_FOUND 0x00020000 +#define AR_WOW_MAC_INTR 0x00080000 +#define AR_WOW_KEEP_ALIVE_FAIL 0x00100000 +#define AR_WOW_BEACON_FAIL 0x00200000 + +#define AR_WOW_STATUS(x) (x & (AR_WOW_PATTERN_FOUND_MASK | \ + AR_WOW_MAGIC_PAT_FOUND | \ + AR_WOW_KEEP_ALIVE_FAIL | \ + AR_WOW_BEACON_FAIL)) +#define AR_WOW_CLEAR_EVENTS(x) (x & ~(AR_WOW_PATTERN_EN(0xff) | \ + AR_WOW_MAGIC_EN | \ + AR_WOW_MAC_INTR_EN | \ + AR_WOW_BEACON_FAIL | \ + AR_WOW_KEEP_ALIVE_FAIL)) + +/* AR_WOW_COUNT register values */ +#define AR_WOW_AIFS_CNT(x) (x & 0xff) +#define AR_WOW_SLOT_CNT(x) ((x & 0xff) << 8) +#define AR_WOW_KEEP_ALIVE_CNT(x) ((x & 0xff) << 16) + +/* AR_WOW_BCN_EN register */ +#define AR_WOW_BEACON_FAIL_EN 0x00000001 + +/* AR_WOW_BCN_TIMO rgister */ +#define AR_WOW_BEACON_TIMO 0x40000000 /* valid if BCN_EN is set */ + +/* AR_WOW_KEEP_ALIVE_TIMO register */ +#define AR_WOW_KEEP_ALIVE_TIMO_VALUE +#define AR_WOW_KEEP_ALIVE_NEVER 0xffffffff + +/* AR_WOW_KEEP_ALIVE register */ +#define AR_WOW_KEEP_ALIVE_AUTO_DIS 0x00000001 +#define AR_WOW_KEEP_ALIVE_FAIL_DIS 0x00000002 + +/* AR_WOW_KEEP_ALIVE_DELAY register */ +#define AR_WOW_KEEP_ALIVE_DELAY_VALUE 0x000003e8 /* 1 msec */ + + +/* + * keep it long for beacon workaround - ensure no false alarm + */ +#define AR_WOW_BMISSTHRESHOLD 0x20 + +/* AR_WOW_PATTERN_MATCH register */ +#define AR_WOW_PAT_END_OF_PKT(x) (x & 0xf) +#define AR_WOW_PAT_OFF_MATCH(x) ((x & 0xf) << 8) + +/* + * default values for Wow Configuration for backoff, aifs, slot, keep-alive + * to be programmed into various registers. + */ +#define AR_WOW_PAT_BACKOFF 0x00000004 /* AR_WOW_PATTERN_REG */ +#define AR_WOW_CNT_AIFS_CNT 0x00000022 /* AR_WOW_COUNT_REG */ +#define AR_WOW_CNT_SLOT_CNT 0x00000009 /* AR_WOW_COUNT_REG */ +/* + * Keepalive count applicable for AR9280 2.0 and above. + */ +#define AR_WOW_CNT_KA_CNT 0x00000008 /* AR_WOW_COUNT register */ + +/* WoW - Transmit buffer for keep alive frames */ +#define AR_WOW_TRANSMIT_BUFFER 0xe000 /* E000 - EFFC */ + +#define AR_WOW_TXBUF(i) (AR_WOW_TRANSMIT_BUFFER + ((i) << 2)) + +#define AR_WOW_KA_DESC_WORD2 0xe000 + +#define AR_WOW_KA_DATA_WORD0 0xe030 + +/* WoW Transmit Buffer for patterns */ +#define AR_WOW_TB_PATTERN(i) (0xe100 + (i << 8)) +#define AR_WOW_TB_MASK(i) (0xec00 + (i << 5)) + +/* Currently Pattern 0-7 are supported - so bit 0-7 are set */ +#define AR_WOW_PATTERN_SUPPORTED 0xff +#define AR_WOW_LENGTH_MAX 0xff +#define AR_WOW_LEN1_SHIFT(_i) ((0x3 - ((_i) & 0x3)) << 0x3) +#define AR_WOW_LENGTH1_MASK(_i) (AR_WOW_LENGTH_MAX << AR_WOW_LEN1_SHIFT(_i)) +#define AR_WOW_LEN2_SHIFT(_i) ((0x7 - ((_i) & 0x7)) << 0x3) +#define AR_WOW_LENGTH2_MASK(_i) (AR_WOW_LENGTH_MAX << AR_WOW_LEN2_SHIFT(_i)) + +#define AR9271_CORE_CLOCK 117 /* clock to 117Mhz */ +#define AR9271_TARGET_BAUD_RATE 19200 /* 115200 */ + +#define AR_AGG_WEP_ENABLE_FIX 0x00000008 /* This allows the use of AR_AGG_WEP_ENABLE */ +#define AR_ADHOC_MCAST_KEYID_ENABLE 0x00000040 /* This bit enables the Multicast search + * based on both MAC Address and Key ID. + * If bit is 0, then Multicast search is + * based on MAC address only. + * For Merlin and above only. + */ +#define AR_AGG_WEP_ENABLE 0x00020000 /* This field enables AGG_WEP feature, + * when it is enable, AGG_WEP would takes + * charge of the encryption interface of + * pcu_txsm. + */ + +#define AR9300_SM_BASE 0xa200 +#define AR9002_PHY_AGC_CONTROL 0x9860 +#define AR9003_PHY_AGC_CONTROL AR9300_SM_BASE + 0xc4 +#define AR_PHY_AGC_CONTROL (AR_SREV_9300_20_OR_LATER(ah) ? AR9003_PHY_AGC_CONTROL : AR9002_PHY_AGC_CONTROL) +#define AR_PHY_AGC_CONTROL_CAL 0x00000001 /* do internal calibration */ +#define AR_PHY_AGC_CONTROL_NF 0x00000002 /* do noise-floor calibration */ +#define AR_PHY_AGC_CONTROL_OFFSET_CAL 0x00000800 /* allow offset calibration */ +#define AR_PHY_AGC_CONTROL_ENABLE_NF 0x00008000 /* enable noise floor calibration to happen */ +#define AR_PHY_AGC_CONTROL_FLTR_CAL 0x00010000 /* allow tx filter calibration */ +#define AR_PHY_AGC_CONTROL_NO_UPDATE_NF 0x00020000 /* don't update noise floor automatically */ +#define AR_PHY_AGC_CONTROL_EXT_NF_PWR_MEAS 0x00040000 /* extend noise floor power measurement */ +#define AR_PHY_AGC_CONTROL_CLC_SUCCESS 0x00080000 /* carrier leak calibration done */ +#define AR_PHY_AGC_CONTROL_PKDET_CAL 0x00100000 +#define AR_PHY_AGC_CONTROL_YCOK_MAX 0x000003c0 +#define AR_PHY_AGC_CONTROL_YCOK_MAX_S 6 + +/* MCI Registers */ + +#define AR_MCI_COMMAND0 0x1800 +#define AR_MCI_COMMAND0_HEADER 0xFF +#define AR_MCI_COMMAND0_HEADER_S 0 +#define AR_MCI_COMMAND0_LEN 0x1f00 +#define AR_MCI_COMMAND0_LEN_S 8 +#define AR_MCI_COMMAND0_DISABLE_TIMESTAMP 0x2000 +#define AR_MCI_COMMAND0_DISABLE_TIMESTAMP_S 13 + +#define AR_MCI_COMMAND1 0x1804 + +#define AR_MCI_COMMAND2 0x1808 +#define AR_MCI_COMMAND2_RESET_TX 0x01 +#define AR_MCI_COMMAND2_RESET_TX_S 0 +#define AR_MCI_COMMAND2_RESET_RX 0x02 +#define AR_MCI_COMMAND2_RESET_RX_S 1 +#define AR_MCI_COMMAND2_RESET_RX_NUM_CYCLES 0x3FC +#define AR_MCI_COMMAND2_RESET_RX_NUM_CYCLES_S 2 +#define AR_MCI_COMMAND2_RESET_REQ_WAKEUP 0x400 +#define AR_MCI_COMMAND2_RESET_REQ_WAKEUP_S 10 + +#define AR_MCI_RX_CTRL 0x180c + +#define AR_MCI_TX_CTRL 0x1810 +/* 0 = no division, 1 = divide by 2, 2 = divide by 4, 3 = divide by 8 */ +#define AR_MCI_TX_CTRL_CLK_DIV 0x03 +#define AR_MCI_TX_CTRL_CLK_DIV_S 0 +#define AR_MCI_TX_CTRL_DISABLE_LNA_UPDATE 0x04 +#define AR_MCI_TX_CTRL_DISABLE_LNA_UPDATE_S 2 +#define AR_MCI_TX_CTRL_GAIN_UPDATE_FREQ 0xFFFFF8 +#define AR_MCI_TX_CTRL_GAIN_UPDATE_FREQ_S 3 +#define AR_MCI_TX_CTRL_GAIN_UPDATE_NUM 0xF000000 +#define AR_MCI_TX_CTRL_GAIN_UPDATE_NUM_S 24 + +#define AR_MCI_MSG_ATTRIBUTES_TABLE 0x1814 +#define AR_MCI_MSG_ATTRIBUTES_TABLE_CHECKSUM 0xFFFF +#define AR_MCI_MSG_ATTRIBUTES_TABLE_CHECKSUM_S 0 +#define AR_MCI_MSG_ATTRIBUTES_TABLE_INVALID_HDR 0xFFFF0000 +#define AR_MCI_MSG_ATTRIBUTES_TABLE_INVALID_HDR_S 16 + +#define AR_MCI_SCHD_TABLE_0 0x1818 +#define AR_MCI_SCHD_TABLE_1 0x181c +#define AR_MCI_GPM_0 0x1820 +#define AR_MCI_GPM_1 0x1824 +#define AR_MCI_GPM_WRITE_PTR 0xFFFF0000 +#define AR_MCI_GPM_WRITE_PTR_S 16 +#define AR_MCI_GPM_BUF_LEN 0x0000FFFF +#define AR_MCI_GPM_BUF_LEN_S 0 + +#define AR_MCI_INTERRUPT_RAW 0x1828 +#define AR_MCI_INTERRUPT_EN 0x182c +#define AR_MCI_INTERRUPT_SW_MSG_DONE 0x00000001 +#define AR_MCI_INTERRUPT_SW_MSG_DONE_S 0 +#define AR_MCI_INTERRUPT_CPU_INT_MSG 0x00000002 +#define AR_MCI_INTERRUPT_CPU_INT_MSG_S 1 +#define AR_MCI_INTERRUPT_RX_CKSUM_FAIL 0x00000004 +#define AR_MCI_INTERRUPT_RX_CKSUM_FAIL_S 2 +#define AR_MCI_INTERRUPT_RX_INVALID_HDR 0x00000008 +#define AR_MCI_INTERRUPT_RX_INVALID_HDR_S 3 +#define AR_MCI_INTERRUPT_RX_HW_MSG_FAIL 0x00000010 +#define AR_MCI_INTERRUPT_RX_HW_MSG_FAIL_S 4 +#define AR_MCI_INTERRUPT_RX_SW_MSG_FAIL 0x00000020 +#define AR_MCI_INTERRUPT_RX_SW_MSG_FAIL_S 5 +#define AR_MCI_INTERRUPT_TX_HW_MSG_FAIL 0x00000080 +#define AR_MCI_INTERRUPT_TX_HW_MSG_FAIL_S 7 +#define AR_MCI_INTERRUPT_TX_SW_MSG_FAIL 0x00000100 +#define AR_MCI_INTERRUPT_TX_SW_MSG_FAIL_S 8 +#define AR_MCI_INTERRUPT_RX_MSG 0x00000200 +#define AR_MCI_INTERRUPT_RX_MSG_S 9 +#define AR_MCI_INTERRUPT_REMOTE_SLEEP_UPDATE 0x00000400 +#define AR_MCI_INTERRUPT_REMOTE_SLEEP_UPDATE_S 10 +#define AR_MCI_INTERRUPT_BT_PRI 0x07fff800 +#define AR_MCI_INTERRUPT_BT_PRI_S 11 +#define AR_MCI_INTERRUPT_BT_PRI_THRESH 0x08000000 +#define AR_MCI_INTERRUPT_BT_PRI_THRESH_S 27 +#define AR_MCI_INTERRUPT_BT_FREQ 0x10000000 +#define AR_MCI_INTERRUPT_BT_FREQ_S 28 +#define AR_MCI_INTERRUPT_BT_STOMP 0x20000000 +#define AR_MCI_INTERRUPT_BT_STOMP_S 29 +#define AR_MCI_INTERRUPT_BB_AIC_IRQ 0x40000000 +#define AR_MCI_INTERRUPT_BB_AIC_IRQ_S 30 +#define AR_MCI_INTERRUPT_CONT_INFO_TIMEOUT 0x80000000 +#define AR_MCI_INTERRUPT_CONT_INFO_TIMEOUT_S 31 + +#define AR_MCI_INTERRUPT_DEFAULT (AR_MCI_INTERRUPT_SW_MSG_DONE | \ + AR_MCI_INTERRUPT_RX_INVALID_HDR | \ + AR_MCI_INTERRUPT_RX_HW_MSG_FAIL | \ + AR_MCI_INTERRUPT_RX_SW_MSG_FAIL | \ + AR_MCI_INTERRUPT_TX_HW_MSG_FAIL | \ + AR_MCI_INTERRUPT_TX_SW_MSG_FAIL | \ + AR_MCI_INTERRUPT_RX_MSG | \ + AR_MCI_INTERRUPT_REMOTE_SLEEP_UPDATE | \ + AR_MCI_INTERRUPT_CONT_INFO_TIMEOUT) + +#define AR_MCI_INTERRUPT_MSG_FAIL_MASK (AR_MCI_INTERRUPT_RX_HW_MSG_FAIL | \ + AR_MCI_INTERRUPT_RX_SW_MSG_FAIL | \ + AR_MCI_INTERRUPT_TX_HW_MSG_FAIL | \ + AR_MCI_INTERRUPT_TX_SW_MSG_FAIL) + +#define AR_MCI_REMOTE_CPU_INT 0x1830 +#define AR_MCI_REMOTE_CPU_INT_EN 0x1834 +#define AR_MCI_INTERRUPT_RX_MSG_RAW 0x1838 +#define AR_MCI_INTERRUPT_RX_MSG_EN 0x183c +#define AR_MCI_INTERRUPT_RX_MSG_REMOTE_RESET 0x00000001 +#define AR_MCI_INTERRUPT_RX_MSG_REMOTE_RESET_S 0 +#define AR_MCI_INTERRUPT_RX_MSG_LNA_CONTROL 0x00000002 +#define AR_MCI_INTERRUPT_RX_MSG_LNA_CONTROL_S 1 +#define AR_MCI_INTERRUPT_RX_MSG_CONT_NACK 0x00000004 +#define AR_MCI_INTERRUPT_RX_MSG_CONT_NACK_S 2 +#define AR_MCI_INTERRUPT_RX_MSG_CONT_INFO 0x00000008 +#define AR_MCI_INTERRUPT_RX_MSG_CONT_INFO_S 3 +#define AR_MCI_INTERRUPT_RX_MSG_CONT_RST 0x00000010 +#define AR_MCI_INTERRUPT_RX_MSG_CONT_RST_S 4 +#define AR_MCI_INTERRUPT_RX_MSG_SCHD_INFO 0x00000020 +#define AR_MCI_INTERRUPT_RX_MSG_SCHD_INFO_S 5 +#define AR_MCI_INTERRUPT_RX_MSG_CPU_INT 0x00000040 +#define AR_MCI_INTERRUPT_RX_MSG_CPU_INT_S 6 +#define AR_MCI_INTERRUPT_RX_MSG_GPM 0x00000100 +#define AR_MCI_INTERRUPT_RX_MSG_GPM_S 8 +#define AR_MCI_INTERRUPT_RX_MSG_LNA_INFO 0x00000200 +#define AR_MCI_INTERRUPT_RX_MSG_LNA_INFO_S 9 +#define AR_MCI_INTERRUPT_RX_MSG_SYS_SLEEPING 0x00000400 +#define AR_MCI_INTERRUPT_RX_MSG_SYS_SLEEPING_S 10 +#define AR_MCI_INTERRUPT_RX_MSG_SYS_WAKING 0x00000800 +#define AR_MCI_INTERRUPT_RX_MSG_SYS_WAKING_S 11 +#define AR_MCI_INTERRUPT_RX_MSG_REQ_WAKE 0x00001000 +#define AR_MCI_INTERRUPT_RX_MSG_REQ_WAKE_S 12 +#define AR_MCI_INTERRUPT_RX_HW_MSG_MASK (AR_MCI_INTERRUPT_RX_MSG_SCHD_INFO | \ + AR_MCI_INTERRUPT_RX_MSG_LNA_CONTROL| \ + AR_MCI_INTERRUPT_RX_MSG_LNA_INFO | \ + AR_MCI_INTERRUPT_RX_MSG_CONT_NACK | \ + AR_MCI_INTERRUPT_RX_MSG_CONT_INFO | \ + AR_MCI_INTERRUPT_RX_MSG_CONT_RST) + +#define AR_MCI_INTERRUPT_RX_MSG_DEFAULT (AR_MCI_INTERRUPT_RX_MSG_GPM | \ + AR_MCI_INTERRUPT_RX_MSG_REMOTE_RESET| \ + AR_MCI_INTERRUPT_RX_MSG_SYS_WAKING | \ + AR_MCI_INTERRUPT_RX_MSG_SYS_SLEEPING| \ + AR_MCI_INTERRUPT_RX_MSG_REQ_WAKE) + +#define AR_MCI_CPU_INT 0x1840 + +#define AR_MCI_RX_STATUS 0x1844 +#define AR_MCI_RX_LAST_SCHD_MSG_INDEX 0x00000F00 +#define AR_MCI_RX_LAST_SCHD_MSG_INDEX_S 8 +#define AR_MCI_RX_REMOTE_SLEEP 0x00001000 +#define AR_MCI_RX_REMOTE_SLEEP_S 12 +#define AR_MCI_RX_MCI_CLK_REQ 0x00002000 +#define AR_MCI_RX_MCI_CLK_REQ_S 13 + +#define AR_MCI_CONT_STATUS 0x1848 +#define AR_MCI_CONT_RSSI_POWER 0x000000FF +#define AR_MCI_CONT_RSSI_POWER_S 0 +#define AR_MCI_CONT_PRIORITY 0x0000FF00 +#define AR_MCI_CONT_PRIORITY_S 8 +#define AR_MCI_CONT_TXRX 0x00010000 +#define AR_MCI_CONT_TXRX_S 16 + +#define AR_MCI_BT_PRI0 0x184c +#define AR_MCI_BT_PRI1 0x1850 +#define AR_MCI_BT_PRI2 0x1854 +#define AR_MCI_BT_PRI3 0x1858 +#define AR_MCI_BT_PRI 0x185c +#define AR_MCI_WL_FREQ0 0x1860 +#define AR_MCI_WL_FREQ1 0x1864 +#define AR_MCI_WL_FREQ2 0x1868 +#define AR_MCI_GAIN 0x186c +#define AR_MCI_WBTIMER1 0x1870 +#define AR_MCI_WBTIMER2 0x1874 +#define AR_MCI_WBTIMER3 0x1878 +#define AR_MCI_WBTIMER4 0x187c +#define AR_MCI_MAXGAIN 0x1880 +#define AR_MCI_HW_SCHD_TBL_CTL 0x1884 +#define AR_MCI_HW_SCHD_TBL_D0 0x1888 +#define AR_MCI_HW_SCHD_TBL_D1 0x188c +#define AR_MCI_HW_SCHD_TBL_D2 0x1890 +#define AR_MCI_HW_SCHD_TBL_D3 0x1894 +#define AR_MCI_TX_PAYLOAD0 0x1898 +#define AR_MCI_TX_PAYLOAD1 0x189c +#define AR_MCI_TX_PAYLOAD2 0x18a0 +#define AR_MCI_TX_PAYLOAD3 0x18a4 +#define AR_BTCOEX_WBTIMER 0x18a8 + +#define AR_BTCOEX_CTRL 0x18ac +#define AR_BTCOEX_CTRL_AR9462_MODE 0x00000001 +#define AR_BTCOEX_CTRL_AR9462_MODE_S 0 +#define AR_BTCOEX_CTRL_WBTIMER_EN 0x00000002 +#define AR_BTCOEX_CTRL_WBTIMER_EN_S 1 +#define AR_BTCOEX_CTRL_MCI_MODE_EN 0x00000004 +#define AR_BTCOEX_CTRL_MCI_MODE_EN_S 2 +#define AR_BTCOEX_CTRL_LNA_SHARED 0x00000008 +#define AR_BTCOEX_CTRL_LNA_SHARED_S 3 +#define AR_BTCOEX_CTRL_PA_SHARED 0x00000010 +#define AR_BTCOEX_CTRL_PA_SHARED_S 4 +#define AR_BTCOEX_CTRL_ONE_STEP_LOOK_AHEAD_EN 0x00000020 +#define AR_BTCOEX_CTRL_ONE_STEP_LOOK_AHEAD_EN_S 5 +#define AR_BTCOEX_CTRL_TIME_TO_NEXT_BT_THRESH_EN 0x00000040 +#define AR_BTCOEX_CTRL_TIME_TO_NEXT_BT_THRESH_EN_S 6 +#define AR_BTCOEX_CTRL_NUM_ANTENNAS 0x00000180 +#define AR_BTCOEX_CTRL_NUM_ANTENNAS_S 7 +#define AR_BTCOEX_CTRL_RX_CHAIN_MASK 0x00000E00 +#define AR_BTCOEX_CTRL_RX_CHAIN_MASK_S 9 +#define AR_BTCOEX_CTRL_AGGR_THRESH 0x00007000 +#define AR_BTCOEX_CTRL_AGGR_THRESH_S 12 +#define AR_BTCOEX_CTRL_1_CHAIN_BCN 0x00080000 +#define AR_BTCOEX_CTRL_1_CHAIN_BCN_S 19 +#define AR_BTCOEX_CTRL_1_CHAIN_ACK 0x00100000 +#define AR_BTCOEX_CTRL_1_CHAIN_ACK_S 20 +#define AR_BTCOEX_CTRL_WAIT_BA_MARGIN 0x1FE00000 +#define AR_BTCOEX_CTRL_WAIT_BA_MARGIN_S 28 +#define AR_BTCOEX_CTRL_REDUCE_TXPWR 0x20000000 +#define AR_BTCOEX_CTRL_REDUCE_TXPWR_S 29 +#define AR_BTCOEX_CTRL_SPDT_ENABLE_10 0x40000000 +#define AR_BTCOEX_CTRL_SPDT_ENABLE_10_S 30 +#define AR_BTCOEX_CTRL_SPDT_POLARITY 0x80000000 +#define AR_BTCOEX_CTRL_SPDT_POLARITY_S 31 + +#define AR_BTCOEX_MAX_TXPWR(_x) (0x18c0 + ((_x) << 2)) +#define AR_BTCOEX_WL_LNA 0x1940 +#define AR_BTCOEX_RFGAIN_CTRL 0x1944 +#define AR_BTCOEX_WL_LNA_TIMEOUT 0x003FFFFF +#define AR_BTCOEX_WL_LNA_TIMEOUT_S 0 + +#define AR_BTCOEX_CTRL2 0x1948 +#define AR_BTCOEX_CTRL2_TXPWR_THRESH 0x0007F800 +#define AR_BTCOEX_CTRL2_TXPWR_THRESH_S 11 +#define AR_BTCOEX_CTRL2_TX_CHAIN_MASK 0x00380000 +#define AR_BTCOEX_CTRL2_TX_CHAIN_MASK_S 19 +#define AR_BTCOEX_CTRL2_RX_DEWEIGHT 0x00400000 +#define AR_BTCOEX_CTRL2_RX_DEWEIGHT_S 22 +#define AR_BTCOEX_CTRL2_GPIO_OBS_SEL 0x00800000 +#define AR_BTCOEX_CTRL2_GPIO_OBS_SEL_S 23 +#define AR_BTCOEX_CTRL2_MAC_BB_OBS_SEL 0x01000000 +#define AR_BTCOEX_CTRL2_MAC_BB_OBS_SEL_S 24 +#define AR_BTCOEX_CTRL2_DESC_BASED_TXPWR_ENABLE 0x02000000 +#define AR_BTCOEX_CTRL2_DESC_BASED_TXPWR_ENABLE_S 25 + +#define AR_BTCOEX_CTRL_SPDT_ENABLE 0x00000001 +#define AR_BTCOEX_CTRL_SPDT_ENABLE_S 0 +#define AR_BTCOEX_CTRL_BT_OWN_SPDT_CTRL 0x00000002 +#define AR_BTCOEX_CTRL_BT_OWN_SPDT_CTRL_S 1 +#define AR_BTCOEX_CTRL_USE_LATCHED_BT_ANT 0x00000004 +#define AR_BTCOEX_CTRL_USE_LATCHED_BT_ANT_S 2 +#define AR_GLB_WLAN_UART_INTF_EN 0x00020000 +#define AR_GLB_WLAN_UART_INTF_EN_S 17 +#define AR_GLB_DS_JTAG_DISABLE 0x00040000 +#define AR_GLB_DS_JTAG_DISABLE_S 18 + +#define AR_BTCOEX_RC 0x194c +#define AR_BTCOEX_MAX_RFGAIN(_x) (0x1950 + ((_x) << 2)) +#define AR_BTCOEX_DBG 0x1a50 +#define AR_MCI_LAST_HW_MSG_HDR 0x1a54 +#define AR_MCI_LAST_HW_MSG_BDY 0x1a58 + +#define AR_MCI_SCHD_TABLE_2 0x1a5c +#define AR_MCI_SCHD_TABLE_2_MEM_BASED 0x00000001 +#define AR_MCI_SCHD_TABLE_2_MEM_BASED_S 0 +#define AR_MCI_SCHD_TABLE_2_HW_BASED 0x00000002 +#define AR_MCI_SCHD_TABLE_2_HW_BASED_S 1 + +#define AR_BTCOEX_CTRL3 0x1a60 +#define AR_BTCOEX_CTRL3_CONT_INFO_TIMEOUT 0x00000fff +#define AR_BTCOEX_CTRL3_CONT_INFO_TIMEOUT_S 0 + +#define AR_GLB_SWREG_DISCONT_MODE 0x2002c +#define AR_GLB_SWREG_DISCONT_EN_BT_WLAN 0x3 + +#define AR_MCI_MISC 0x1a74 +#define AR_MCI_MISC_HW_FIX_EN 0x00000001 +#define AR_MCI_MISC_HW_FIX_EN_S 0 +#define AR_MCI_DBG_CNT_CTRL 0x1a78 +#define AR_MCI_DBG_CNT_CTRL_ENABLE 0x00000001 +#define AR_MCI_DBG_CNT_CTRL_ENABLE_S 0 + +#endif diff --git a/drivers/net/wireless/ath/ath9k/wmi.c b/drivers/net/wireless/ath/ath9k/wmi.c new file mode 100644 index 0000000..65c8894 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/wmi.c @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "htc.h" + +static const char *wmi_cmd_to_name(enum wmi_cmd_id wmi_cmd) +{ + switch (wmi_cmd) { + case WMI_ECHO_CMDID: + return "WMI_ECHO_CMDID"; + case WMI_ACCESS_MEMORY_CMDID: + return "WMI_ACCESS_MEMORY_CMDID"; + case WMI_GET_FW_VERSION: + return "WMI_GET_FW_VERSION"; + case WMI_DISABLE_INTR_CMDID: + return "WMI_DISABLE_INTR_CMDID"; + case WMI_ENABLE_INTR_CMDID: + return "WMI_ENABLE_INTR_CMDID"; + case WMI_ATH_INIT_CMDID: + return "WMI_ATH_INIT_CMDID"; + case WMI_ABORT_TXQ_CMDID: + return "WMI_ABORT_TXQ_CMDID"; + case WMI_STOP_TX_DMA_CMDID: + return "WMI_STOP_TX_DMA_CMDID"; + case WMI_ABORT_TX_DMA_CMDID: + return "WMI_ABORT_TX_DMA_CMDID"; + case WMI_DRAIN_TXQ_CMDID: + return "WMI_DRAIN_TXQ_CMDID"; + case WMI_DRAIN_TXQ_ALL_CMDID: + return "WMI_DRAIN_TXQ_ALL_CMDID"; + case WMI_START_RECV_CMDID: + return "WMI_START_RECV_CMDID"; + case WMI_STOP_RECV_CMDID: + return "WMI_STOP_RECV_CMDID"; + case WMI_FLUSH_RECV_CMDID: + return "WMI_FLUSH_RECV_CMDID"; + case WMI_SET_MODE_CMDID: + return "WMI_SET_MODE_CMDID"; + case WMI_NODE_CREATE_CMDID: + return "WMI_NODE_CREATE_CMDID"; + case WMI_NODE_REMOVE_CMDID: + return "WMI_NODE_REMOVE_CMDID"; + case WMI_VAP_REMOVE_CMDID: + return "WMI_VAP_REMOVE_CMDID"; + case WMI_VAP_CREATE_CMDID: + return "WMI_VAP_CREATE_CMDID"; + case WMI_REG_READ_CMDID: + return "WMI_REG_READ_CMDID"; + case WMI_REG_WRITE_CMDID: + return "WMI_REG_WRITE_CMDID"; + case WMI_RC_STATE_CHANGE_CMDID: + return "WMI_RC_STATE_CHANGE_CMDID"; + case WMI_RC_RATE_UPDATE_CMDID: + return "WMI_RC_RATE_UPDATE_CMDID"; + case WMI_TARGET_IC_UPDATE_CMDID: + return "WMI_TARGET_IC_UPDATE_CMDID"; + case WMI_TX_AGGR_ENABLE_CMDID: + return "WMI_TX_AGGR_ENABLE_CMDID"; + case WMI_TGT_DETACH_CMDID: + return "WMI_TGT_DETACH_CMDID"; + case WMI_NODE_UPDATE_CMDID: + return "WMI_NODE_UPDATE_CMDID"; + case WMI_INT_STATS_CMDID: + return "WMI_INT_STATS_CMDID"; + case WMI_TX_STATS_CMDID: + return "WMI_TX_STATS_CMDID"; + case WMI_RX_STATS_CMDID: + return "WMI_RX_STATS_CMDID"; + case WMI_BITRATE_MASK_CMDID: + return "WMI_BITRATE_MASK_CMDID"; + } + + return "Bogus"; +} + +struct wmi *ath9k_init_wmi(struct ath9k_htc_priv *priv) +{ + struct wmi *wmi; + + wmi = kzalloc(sizeof(struct wmi), GFP_KERNEL); + if (!wmi) + return NULL; + + wmi->drv_priv = priv; + wmi->stopped = false; + skb_queue_head_init(&wmi->wmi_event_queue); + spin_lock_init(&wmi->wmi_lock); + spin_lock_init(&wmi->event_lock); + mutex_init(&wmi->op_mutex); + mutex_init(&wmi->multi_write_mutex); + init_completion(&wmi->cmd_wait); + INIT_LIST_HEAD(&wmi->pending_tx_events); + tasklet_init(&wmi->wmi_event_tasklet, ath9k_wmi_event_tasklet, + (unsigned long)wmi); + + return wmi; +} + +void ath9k_deinit_wmi(struct ath9k_htc_priv *priv) +{ + struct wmi *wmi = priv->wmi; + + mutex_lock(&wmi->op_mutex); + wmi->stopped = true; + mutex_unlock(&wmi->op_mutex); + + kfree(priv->wmi); +} + +void ath9k_wmi_event_drain(struct ath9k_htc_priv *priv) +{ + unsigned long flags; + + tasklet_kill(&priv->wmi->wmi_event_tasklet); + spin_lock_irqsave(&priv->wmi->wmi_lock, flags); + __skb_queue_purge(&priv->wmi->wmi_event_queue); + spin_unlock_irqrestore(&priv->wmi->wmi_lock, flags); +} + +void ath9k_wmi_event_tasklet(unsigned long data) +{ + struct wmi *wmi = (struct wmi *)data; + struct ath9k_htc_priv *priv = wmi->drv_priv; + struct wmi_cmd_hdr *hdr; + void *wmi_event; + struct wmi_event_swba *swba; + struct sk_buff *skb = NULL; + unsigned long flags; + u16 cmd_id; + + do { + spin_lock_irqsave(&wmi->wmi_lock, flags); + skb = __skb_dequeue(&wmi->wmi_event_queue); + if (!skb) { + spin_unlock_irqrestore(&wmi->wmi_lock, flags); + return; + } + spin_unlock_irqrestore(&wmi->wmi_lock, flags); + + hdr = (struct wmi_cmd_hdr *) skb->data; + cmd_id = be16_to_cpu(hdr->command_id); + wmi_event = skb_pull(skb, sizeof(struct wmi_cmd_hdr)); + + switch (cmd_id) { + case WMI_SWBA_EVENTID: + swba = (struct wmi_event_swba *) wmi_event; + ath9k_htc_swba(priv, swba); + break; + case WMI_FATAL_EVENTID: + ieee80211_queue_work(wmi->drv_priv->hw, + &wmi->drv_priv->fatal_work); + break; + case WMI_TXSTATUS_EVENTID: + spin_lock_bh(&priv->tx.tx_lock); + if (priv->tx.flags & ATH9K_HTC_OP_TX_DRAIN) { + spin_unlock_bh(&priv->tx.tx_lock); + break; + } + spin_unlock_bh(&priv->tx.tx_lock); + + ath9k_htc_txstatus(priv, wmi_event); + break; + default: + break; + } + + kfree_skb(skb); + } while (1); +} + +void ath9k_fatal_work(struct work_struct *work) +{ + struct ath9k_htc_priv *priv = container_of(work, struct ath9k_htc_priv, + fatal_work); + struct ath_common *common = ath9k_hw_common(priv->ah); + + ath_dbg(common, FATAL, "FATAL Event received, resetting device\n"); + ath9k_htc_reset(priv); +} + +static void ath9k_wmi_rsp_callback(struct wmi *wmi, struct sk_buff *skb) +{ + skb_pull(skb, sizeof(struct wmi_cmd_hdr)); + + if (wmi->cmd_rsp_buf != NULL && wmi->cmd_rsp_len != 0) + memcpy(wmi->cmd_rsp_buf, skb->data, wmi->cmd_rsp_len); + + complete(&wmi->cmd_wait); +} + +static void ath9k_wmi_ctrl_rx(void *priv, struct sk_buff *skb, + enum htc_endpoint_id epid) +{ + struct wmi *wmi = (struct wmi *) priv; + struct wmi_cmd_hdr *hdr; + u16 cmd_id; + + if (unlikely(wmi->stopped)) + goto free_skb; + + hdr = (struct wmi_cmd_hdr *) skb->data; + cmd_id = be16_to_cpu(hdr->command_id); + + if (cmd_id & 0x1000) { + spin_lock(&wmi->wmi_lock); + __skb_queue_tail(&wmi->wmi_event_queue, skb); + spin_unlock(&wmi->wmi_lock); + tasklet_schedule(&wmi->wmi_event_tasklet); + return; + } + + /* Check if there has been a timeout. */ + spin_lock(&wmi->wmi_lock); + if (cmd_id != wmi->last_cmd_id) { + spin_unlock(&wmi->wmi_lock); + goto free_skb; + } + spin_unlock(&wmi->wmi_lock); + + /* WMI command response */ + ath9k_wmi_rsp_callback(wmi, skb); + +free_skb: + kfree_skb(skb); +} + +static void ath9k_wmi_ctrl_tx(void *priv, struct sk_buff *skb, + enum htc_endpoint_id epid, bool txok) +{ + kfree_skb(skb); +} + +int ath9k_wmi_connect(struct htc_target *htc, struct wmi *wmi, + enum htc_endpoint_id *wmi_ctrl_epid) +{ + struct htc_service_connreq connect; + int ret; + + wmi->htc = htc; + + memset(&connect, 0, sizeof(connect)); + + connect.ep_callbacks.priv = wmi; + connect.ep_callbacks.tx = ath9k_wmi_ctrl_tx; + connect.ep_callbacks.rx = ath9k_wmi_ctrl_rx; + connect.service_id = WMI_CONTROL_SVC; + + ret = htc_connect_service(htc, &connect, &wmi->ctrl_epid); + if (ret) + return ret; + + *wmi_ctrl_epid = wmi->ctrl_epid; + + return 0; +} + +static int ath9k_wmi_cmd_issue(struct wmi *wmi, + struct sk_buff *skb, + enum wmi_cmd_id cmd, u16 len) +{ + struct wmi_cmd_hdr *hdr; + + hdr = (struct wmi_cmd_hdr *) skb_push(skb, sizeof(struct wmi_cmd_hdr)); + hdr->command_id = cpu_to_be16(cmd); + hdr->seq_no = cpu_to_be16(++wmi->tx_seq_id); + + return htc_send_epid(wmi->htc, skb, wmi->ctrl_epid); +} + +int ath9k_wmi_cmd(struct wmi *wmi, enum wmi_cmd_id cmd_id, + u8 *cmd_buf, u32 cmd_len, + u8 *rsp_buf, u32 rsp_len, + u32 timeout) +{ + struct ath_hw *ah = wmi->drv_priv->ah; + struct ath_common *common = ath9k_hw_common(ah); + u16 headroom = sizeof(struct htc_frame_hdr) + + sizeof(struct wmi_cmd_hdr); + struct sk_buff *skb; + u8 *data; + int time_left, ret = 0; + unsigned long flags; + + if (ah->ah_flags & AH_UNPLUGGED) + return 0; + + skb = alloc_skb(headroom + cmd_len, GFP_ATOMIC); + if (!skb) + return -ENOMEM; + + skb_reserve(skb, headroom); + + if (cmd_len != 0 && cmd_buf != NULL) { + data = (u8 *) skb_put(skb, cmd_len); + memcpy(data, cmd_buf, cmd_len); + } + + mutex_lock(&wmi->op_mutex); + + /* check if wmi stopped flag is set */ + if (unlikely(wmi->stopped)) { + ret = -EPROTO; + goto out; + } + + /* record the rsp buffer and length */ + wmi->cmd_rsp_buf = rsp_buf; + wmi->cmd_rsp_len = rsp_len; + + spin_lock_irqsave(&wmi->wmi_lock, flags); + wmi->last_cmd_id = cmd_id; + spin_unlock_irqrestore(&wmi->wmi_lock, flags); + + ret = ath9k_wmi_cmd_issue(wmi, skb, cmd_id, cmd_len); + if (ret) + goto out; + + time_left = wait_for_completion_timeout(&wmi->cmd_wait, timeout); + if (!time_left) { + ath_dbg(common, WMI, "Timeout waiting for WMI command: %s\n", + wmi_cmd_to_name(cmd_id)); + mutex_unlock(&wmi->op_mutex); + return -ETIMEDOUT; + } + + mutex_unlock(&wmi->op_mutex); + + return 0; + +out: + ath_dbg(common, WMI, "WMI failure for: %s\n", wmi_cmd_to_name(cmd_id)); + mutex_unlock(&wmi->op_mutex); + kfree_skb(skb); + + return ret; +} diff --git a/drivers/net/wireless/ath/ath9k/wmi.h b/drivers/net/wireless/ath/ath9k/wmi.h new file mode 100644 index 0000000..fde6da6 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/wmi.h @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2010-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef WMI_H +#define WMI_H + +struct wmi_event_txrate { + __be32 txrate; + struct { + u8 rssi_thresh; + u8 per; + } rc_stats; +} __packed; + +struct wmi_cmd_hdr { + __be16 command_id; + __be16 seq_no; +} __packed; + +struct wmi_fw_version { + __be16 major; + __be16 minor; + +} __packed; + +struct wmi_event_swba { + __be64 tsf; + u8 beacon_pending; +}; + +/* + * 64 - HTC header - WMI header - 1 / txstatus + * And some other hdr. space is also accounted for. + * 12 seems to be the magic number. + */ +#define HTC_MAX_TX_STATUS 12 + +#define ATH9K_HTC_TXSTAT_ACK BIT(0) +#define ATH9K_HTC_TXSTAT_FILT BIT(1) +#define ATH9K_HTC_TXSTAT_RTC_CTS BIT(2) +#define ATH9K_HTC_TXSTAT_MCS BIT(3) +#define ATH9K_HTC_TXSTAT_CW40 BIT(4) +#define ATH9K_HTC_TXSTAT_SGI BIT(5) + +/* + * Legacy rates are indicated as indices. + * HT rates are indicated as dot11 numbers. + * This allows us to resrict the rate field + * to 4 bits. + */ +#define ATH9K_HTC_TXSTAT_RATE 0x0f +#define ATH9K_HTC_TXSTAT_RATE_S 0 + +#define ATH9K_HTC_TXSTAT_EPID 0xf0 +#define ATH9K_HTC_TXSTAT_EPID_S 4 + +struct __wmi_event_txstatus { + u8 cookie; + u8 ts_rate; /* Also holds EP ID */ + u8 ts_flags; +}; + +struct wmi_event_txstatus { + u8 cnt; + struct __wmi_event_txstatus txstatus[HTC_MAX_TX_STATUS]; +} __packed; + +enum wmi_cmd_id { + WMI_ECHO_CMDID = 0x0001, + WMI_ACCESS_MEMORY_CMDID, + + /* Commands to Target */ + WMI_GET_FW_VERSION, + WMI_DISABLE_INTR_CMDID, + WMI_ENABLE_INTR_CMDID, + WMI_ATH_INIT_CMDID, + WMI_ABORT_TXQ_CMDID, + WMI_STOP_TX_DMA_CMDID, + WMI_ABORT_TX_DMA_CMDID, + WMI_DRAIN_TXQ_CMDID, + WMI_DRAIN_TXQ_ALL_CMDID, + WMI_START_RECV_CMDID, + WMI_STOP_RECV_CMDID, + WMI_FLUSH_RECV_CMDID, + WMI_SET_MODE_CMDID, + WMI_NODE_CREATE_CMDID, + WMI_NODE_REMOVE_CMDID, + WMI_VAP_REMOVE_CMDID, + WMI_VAP_CREATE_CMDID, + WMI_REG_READ_CMDID, + WMI_REG_WRITE_CMDID, + WMI_RC_STATE_CHANGE_CMDID, + WMI_RC_RATE_UPDATE_CMDID, + WMI_TARGET_IC_UPDATE_CMDID, + WMI_TX_AGGR_ENABLE_CMDID, + WMI_TGT_DETACH_CMDID, + WMI_NODE_UPDATE_CMDID, + WMI_INT_STATS_CMDID, + WMI_TX_STATS_CMDID, + WMI_RX_STATS_CMDID, + WMI_BITRATE_MASK_CMDID, +}; + +enum wmi_event_id { + WMI_TGT_RDY_EVENTID = 0x1001, + WMI_SWBA_EVENTID, + WMI_FATAL_EVENTID, + WMI_TXTO_EVENTID, + WMI_BMISS_EVENTID, + WMI_DELBA_EVENTID, + WMI_TXSTATUS_EVENTID, +}; + +#define MAX_CMD_NUMBER 62 + +struct register_write { + __be32 reg; + __be32 val; +}; + +struct ath9k_htc_tx_event { + int count; + struct __wmi_event_txstatus txs; + struct list_head list; +}; + +struct wmi { + struct ath9k_htc_priv *drv_priv; + struct htc_target *htc; + enum htc_endpoint_id ctrl_epid; + struct mutex op_mutex; + struct completion cmd_wait; + enum wmi_cmd_id last_cmd_id; + struct sk_buff_head wmi_event_queue; + struct tasklet_struct wmi_event_tasklet; + u16 tx_seq_id; + u8 *cmd_rsp_buf; + u32 cmd_rsp_len; + bool stopped; + + struct list_head pending_tx_events; + spinlock_t event_lock; + + spinlock_t wmi_lock; + + atomic_t mwrite_cnt; + struct register_write multi_write[MAX_CMD_NUMBER]; + u32 multi_write_idx; + struct mutex multi_write_mutex; +}; + +struct wmi *ath9k_init_wmi(struct ath9k_htc_priv *priv); +void ath9k_deinit_wmi(struct ath9k_htc_priv *priv); +int ath9k_wmi_connect(struct htc_target *htc, struct wmi *wmi, + enum htc_endpoint_id *wmi_ctrl_epid); +int ath9k_wmi_cmd(struct wmi *wmi, enum wmi_cmd_id cmd_id, + u8 *cmd_buf, u32 cmd_len, + u8 *rsp_buf, u32 rsp_len, + u32 timeout); +void ath9k_wmi_event_tasklet(unsigned long data); +void ath9k_fatal_work(struct work_struct *work); +void ath9k_wmi_event_drain(struct ath9k_htc_priv *priv); + +#define WMI_CMD(_wmi_cmd) \ + do { \ + ret = ath9k_wmi_cmd(priv->wmi, _wmi_cmd, NULL, 0, \ + (u8 *) &cmd_rsp, \ + sizeof(cmd_rsp), HZ*2); \ + } while (0) + +#define WMI_CMD_BUF(_wmi_cmd, _buf) \ + do { \ + ret = ath9k_wmi_cmd(priv->wmi, _wmi_cmd, \ + (u8 *) _buf, sizeof(*_buf), \ + &cmd_rsp, sizeof(cmd_rsp), HZ*2); \ + } while (0) + +#endif /* WMI_H */ diff --git a/drivers/net/wireless/ath/ath9k/wow.c b/drivers/net/wireless/ath/ath9k/wow.c new file mode 100644 index 0000000..9f85630 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/wow.c @@ -0,0 +1,532 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "ath9k.h" +#include "reg.h" +#include "hw-ops.h" + +const char *ath9k_hw_wow_event_to_string(u32 wow_event) +{ + if (wow_event & AH_WOW_MAGIC_PATTERN_EN) + return "Magic pattern"; + if (wow_event & AH_WOW_USER_PATTERN_EN) + return "User pattern"; + if (wow_event & AH_WOW_LINK_CHANGE) + return "Link change"; + if (wow_event & AH_WOW_BEACON_MISS) + return "Beacon miss"; + + return "unknown reason"; +} +EXPORT_SYMBOL(ath9k_hw_wow_event_to_string); + +static void ath9k_hw_config_serdes_wow_sleep(struct ath_hw *ah) +{ + int i; + + for (i = 0; i < ah->iniPcieSerdesWow.ia_rows; i++) + REG_WRITE(ah, INI_RA(&ah->iniPcieSerdesWow, i, 0), + INI_RA(&ah->iniPcieSerdesWow, i, 1)); + + usleep_range(1000, 1500); +} + +static void ath9k_hw_set_powermode_wow_sleep(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + + REG_SET_BIT(ah, AR_STA_ID1, AR_STA_ID1_PWR_SAV); + + /* set rx disable bit */ + REG_WRITE(ah, AR_CR, AR_CR_RXD); + + if (!ath9k_hw_wait(ah, AR_CR, AR_CR_RXE, 0, AH_WAIT_TIMEOUT)) { + ath_err(common, "Failed to stop Rx DMA in 10ms AR_CR=0x%08x AR_DIAG_SW=0x%08x\n", + REG_READ(ah, AR_CR), REG_READ(ah, AR_DIAG_SW)); + return; + } else { + if (!AR_SREV_9300_20_OR_LATER(ah)) + REG_WRITE(ah, AR_RXDP, 0x0); + } + + /* AR9280 WoW has sleep issue, do not set it to sleep */ + if (AR_SREV_9280_20(ah)) + return; + + REG_WRITE(ah, AR_RTC_FORCE_WAKE, AR_RTC_FORCE_WAKE_ON_INT); +} + +static void ath9k_wow_create_keep_alive_pattern(struct ath_hw *ah) +{ + struct ath_common *common = ath9k_hw_common(ah); + u8 sta_mac_addr[ETH_ALEN], ap_mac_addr[ETH_ALEN]; + u32 ctl[13] = {0}; + u32 data_word[KAL_NUM_DATA_WORDS]; + u8 i; + u32 wow_ka_data_word0; + + memcpy(sta_mac_addr, common->macaddr, ETH_ALEN); + memcpy(ap_mac_addr, common->curbssid, ETH_ALEN); + + /* set the transmit buffer */ + ctl[0] = (KAL_FRAME_LEN | (MAX_RATE_POWER << 16)); + + if (!(AR_SREV_9300_20_OR_LATER(ah))) + ctl[0] += (KAL_ANTENNA_MODE << 25); + + ctl[1] = 0; + ctl[3] = 0xb; /* OFDM_6M hardware value for this rate */ + ctl[4] = 0; + ctl[7] = (ah->txchainmask) << 2; + + if (AR_SREV_9300_20_OR_LATER(ah)) + ctl[2] = 0xf << 16; /* tx_tries 0 */ + else + ctl[2] = 0x7 << 16; /* tx_tries 0 */ + + + for (i = 0; i < KAL_NUM_DESC_WORDS; i++) + REG_WRITE(ah, (AR_WOW_KA_DESC_WORD2 + i * 4), ctl[i]); + + /* for AR9300 family 13 descriptor words */ + if (AR_SREV_9300_20_OR_LATER(ah)) + REG_WRITE(ah, (AR_WOW_KA_DESC_WORD2 + i * 4), ctl[i]); + + data_word[0] = (KAL_FRAME_TYPE << 2) | (KAL_FRAME_SUB_TYPE << 4) | + (KAL_TO_DS << 8) | (KAL_DURATION_ID << 16); + data_word[1] = (ap_mac_addr[3] << 24) | (ap_mac_addr[2] << 16) | + (ap_mac_addr[1] << 8) | (ap_mac_addr[0]); + data_word[2] = (sta_mac_addr[1] << 24) | (sta_mac_addr[0] << 16) | + (ap_mac_addr[5] << 8) | (ap_mac_addr[4]); + data_word[3] = (sta_mac_addr[5] << 24) | (sta_mac_addr[4] << 16) | + (sta_mac_addr[3] << 8) | (sta_mac_addr[2]); + data_word[4] = (ap_mac_addr[3] << 24) | (ap_mac_addr[2] << 16) | + (ap_mac_addr[1] << 8) | (ap_mac_addr[0]); + data_word[5] = (ap_mac_addr[5] << 8) | (ap_mac_addr[4]); + + if (AR_SREV_9462_20(ah)) { + /* AR9462 2.0 has an extra descriptor word (time based + * discard) compared to other chips */ + REG_WRITE(ah, (AR_WOW_KA_DESC_WORD2 + (12 * 4)), 0); + wow_ka_data_word0 = AR_WOW_TXBUF(13); + } else { + wow_ka_data_word0 = AR_WOW_TXBUF(12); + } + + for (i = 0; i < KAL_NUM_DATA_WORDS; i++) + REG_WRITE(ah, (wow_ka_data_word0 + i*4), data_word[i]); + +} + +void ath9k_hw_wow_apply_pattern(struct ath_hw *ah, u8 *user_pattern, + u8 *user_mask, int pattern_count, + int pattern_len) +{ + int i; + u32 pattern_val, mask_val; + u32 set, clr; + + /* FIXME: should check count by querying the hardware capability */ + if (pattern_count >= MAX_NUM_PATTERN) + return; + + REG_SET_BIT(ah, AR_WOW_PATTERN, BIT(pattern_count)); + + /* set the registers for pattern */ + for (i = 0; i < MAX_PATTERN_SIZE; i += 4) { + memcpy(&pattern_val, user_pattern, 4); + REG_WRITE(ah, (AR_WOW_TB_PATTERN(pattern_count) + i), + pattern_val); + user_pattern += 4; + } + + /* set the registers for mask */ + for (i = 0; i < MAX_PATTERN_MASK_SIZE; i += 4) { + memcpy(&mask_val, user_mask, 4); + REG_WRITE(ah, (AR_WOW_TB_MASK(pattern_count) + i), mask_val); + user_mask += 4; + } + + /* set the pattern length to be matched + * + * AR_WOW_LENGTH1_REG1 + * bit 31:24 pattern 0 length + * bit 23:16 pattern 1 length + * bit 15:8 pattern 2 length + * bit 7:0 pattern 3 length + * + * AR_WOW_LENGTH1_REG2 + * bit 31:24 pattern 4 length + * bit 23:16 pattern 5 length + * bit 15:8 pattern 6 length + * bit 7:0 pattern 7 length + * + * the below logic writes out the new + * pattern length for the corresponding + * pattern_count, while masking out the + * other fields + */ + + ah->wow_event_mask |= BIT(pattern_count + AR_WOW_PAT_FOUND_SHIFT); + + if (!AR_SREV_9285_12_OR_LATER(ah)) + return; + + if (pattern_count < 4) { + /* Pattern 0-3 uses AR_WOW_LENGTH1 register */ + set = (pattern_len & AR_WOW_LENGTH_MAX) << + AR_WOW_LEN1_SHIFT(pattern_count); + clr = AR_WOW_LENGTH1_MASK(pattern_count); + REG_RMW(ah, AR_WOW_LENGTH1, set, clr); + } else { + /* Pattern 4-7 uses AR_WOW_LENGTH2 register */ + set = (pattern_len & AR_WOW_LENGTH_MAX) << + AR_WOW_LEN2_SHIFT(pattern_count); + clr = AR_WOW_LENGTH2_MASK(pattern_count); + REG_RMW(ah, AR_WOW_LENGTH2, set, clr); + } + +} +EXPORT_SYMBOL(ath9k_hw_wow_apply_pattern); + +u32 ath9k_hw_wow_wakeup(struct ath_hw *ah) +{ + u32 wow_status = 0; + u32 val = 0, rval; + /* + * read the WoW status register to know + * the wakeup reason + */ + rval = REG_READ(ah, AR_WOW_PATTERN); + val = AR_WOW_STATUS(rval); + + /* + * mask only the WoW events that we have enabled. Sometimes + * we have spurious WoW events from the AR_WOW_PATTERN + * register. This mask will clean it up. + */ + + val &= ah->wow_event_mask; + + if (val) { + + if (val & AR_WOW_MAGIC_PAT_FOUND) + wow_status |= AH_WOW_MAGIC_PATTERN_EN; + + if (AR_WOW_PATTERN_FOUND(val)) + wow_status |= AH_WOW_USER_PATTERN_EN; + + if (val & AR_WOW_KEEP_ALIVE_FAIL) + wow_status |= AH_WOW_LINK_CHANGE; + + if (val & AR_WOW_BEACON_FAIL) + wow_status |= AH_WOW_BEACON_MISS; + + } + + /* + * set and clear WOW_PME_CLEAR registers for the chip to + * generate next wow signal. + * disable D3 before accessing other registers ? + */ + + /* do we need to check the bit value 0x01000000 (7-10) ?? */ + REG_RMW(ah, AR_PCIE_PM_CTRL, AR_PMCTRL_WOW_PME_CLR, + AR_PMCTRL_PWR_STATE_D1D3); + + /* + * clear all events + */ + REG_WRITE(ah, AR_WOW_PATTERN, + AR_WOW_CLEAR_EVENTS(REG_READ(ah, AR_WOW_PATTERN))); + + /* + * tie reset register for AR9002 family of chipsets + * NB: not tieing it back might have some repurcussions. + */ + + if (!AR_SREV_9300_20_OR_LATER(ah)) { + REG_SET_BIT(ah, AR_WA, AR_WA_UNTIE_RESET_EN | + AR_WA_POR_SHORT | AR_WA_RESET_EN); + } + + + /* + * restore the beacon threshold to init value + */ + REG_WRITE(ah, AR_RSSI_THR, INIT_RSSI_THR); + + /* + * Restore the way the PCI-E reset, Power-On-Reset, external + * PCIE_POR_SHORT pins are tied to its original value. + * Previously just before WoW sleep, we untie the PCI-E + * reset to our Chip's Power On Reset so that any PCI-E + * reset from the bus will not reset our chip + */ + + if (AR_SREV_9280_20_OR_LATER(ah) && ah->is_pciexpress) + ath9k_hw_configpcipowersave(ah, false); + + ah->wow_event_mask = 0; + + return wow_status; +} +EXPORT_SYMBOL(ath9k_hw_wow_wakeup); + +void ath9k_hw_wow_enable(struct ath_hw *ah, u32 pattern_enable) +{ + u32 wow_event_mask; + u32 set, clr; + + /* + * wow_event_mask is a mask to the AR_WOW_PATTERN register to + * indicate which WoW events we have enabled. The WoW events + * are from the 'pattern_enable' in this function and + * 'pattern_count' of ath9k_hw_wow_apply_pattern() + */ + + wow_event_mask = ah->wow_event_mask; + + /* + * Untie Power-on-Reset from the PCI-E-Reset. When we are in + * WOW sleep, we do want the Reset from the PCI-E to disturb + * our hw state + */ + + if (ah->is_pciexpress) { + + /* + * we need to untie the internal POR (power-on-reset) + * to the external PCI-E reset. We also need to tie + * the PCI-E Phy reset to the PCI-E reset. + */ + + if (AR_SREV_9300_20_OR_LATER(ah)) { + set = AR_WA_RESET_EN | AR_WA_POR_SHORT; + clr = AR_WA_UNTIE_RESET_EN | AR_WA_D3_L1_DISABLE; + REG_RMW(ah, AR_WA, set, clr); + } else { + if (AR_SREV_9285(ah) || AR_SREV_9287(ah)) + set = AR9285_WA_DEFAULT; + else + set = AR9280_WA_DEFAULT; + + /* + * In AR9280 and AR9285, bit 14 in WA register + * (disable L1) should only be set when device + * enters D3 state and be cleared when device + * comes back to D0 + */ + + if (ah->config.pcie_waen & AR_WA_D3_L1_DISABLE) + set |= AR_WA_D3_L1_DISABLE; + + clr = AR_WA_UNTIE_RESET_EN; + set |= AR_WA_RESET_EN | AR_WA_POR_SHORT; + REG_RMW(ah, AR_WA, set, clr); + + /* + * for WoW sleep, we reprogram the SerDes so that the + * PLL and CLK REQ are both enabled. This uses more + * power but otherwise WoW sleep is unstable and the + * chip may disappear. + */ + + if (AR_SREV_9285_12_OR_LATER(ah)) + ath9k_hw_config_serdes_wow_sleep(ah); + + } + } + + /* + * set the power states appropriately and enable PME + */ + set = AR_PMCTRL_HOST_PME_EN | AR_PMCTRL_PWR_PM_CTRL_ENA | + AR_PMCTRL_AUX_PWR_DET | AR_PMCTRL_WOW_PME_CLR; + + /* + * set and clear WOW_PME_CLEAR registers for the chip + * to generate next wow signal. + */ + REG_SET_BIT(ah, AR_PCIE_PM_CTRL, set); + clr = AR_PMCTRL_WOW_PME_CLR; + REG_CLR_BIT(ah, AR_PCIE_PM_CTRL, clr); + + /* + * Setup for: + * - beacon misses + * - magic pattern + * - keep alive timeout + * - pattern matching + */ + + /* + * Program default values for pattern backoff, aifs/slot/KAL count, + * beacon miss timeout, KAL timeout, etc. + */ + + set = AR_WOW_BACK_OFF_SHIFT(AR_WOW_PAT_BACKOFF); + REG_SET_BIT(ah, AR_WOW_PATTERN, set); + + set = AR_WOW_AIFS_CNT(AR_WOW_CNT_AIFS_CNT) | + AR_WOW_SLOT_CNT(AR_WOW_CNT_SLOT_CNT) | + AR_WOW_KEEP_ALIVE_CNT(AR_WOW_CNT_KA_CNT); + REG_SET_BIT(ah, AR_WOW_COUNT, set); + + if (pattern_enable & AH_WOW_BEACON_MISS) + set = AR_WOW_BEACON_TIMO; + /* We are not using beacon miss, program a large value */ + else + set = AR_WOW_BEACON_TIMO_MAX; + + REG_WRITE(ah, AR_WOW_BCN_TIMO, set); + + /* + * Keep alive timo in ms except AR9280 + */ + if (!pattern_enable || AR_SREV_9280(ah)) + set = AR_WOW_KEEP_ALIVE_NEVER; + else + set = KAL_TIMEOUT * 32; + + REG_WRITE(ah, AR_WOW_KEEP_ALIVE_TIMO, set); + + /* + * Keep alive delay in us. based on 'power on clock', + * therefore in usec + */ + set = KAL_DELAY * 1000; + REG_WRITE(ah, AR_WOW_KEEP_ALIVE_DELAY, set); + + /* + * Create keep alive pattern to respond to beacons + */ + ath9k_wow_create_keep_alive_pattern(ah); + + /* + * Configure MAC WoW Registers + */ + + set = 0; + /* Send keep alive timeouts anyway */ + clr = AR_WOW_KEEP_ALIVE_AUTO_DIS; + + if (pattern_enable & AH_WOW_LINK_CHANGE) + wow_event_mask |= AR_WOW_KEEP_ALIVE_FAIL; + else + set = AR_WOW_KEEP_ALIVE_FAIL_DIS; + + /* + * FIXME: For now disable keep alive frame + * failure. This seems to sometimes trigger + * unnecessary wake up with AR9485 chipsets. + */ + set = AR_WOW_KEEP_ALIVE_FAIL_DIS; + + REG_RMW(ah, AR_WOW_KEEP_ALIVE, set, clr); + + + /* + * we are relying on a bmiss failure. ensure we have + * enough threshold to prevent false positives + */ + REG_RMW_FIELD(ah, AR_RSSI_THR, AR_RSSI_THR_BM_THR, + AR_WOW_BMISSTHRESHOLD); + + set = 0; + clr = 0; + + if (pattern_enable & AH_WOW_BEACON_MISS) { + set = AR_WOW_BEACON_FAIL_EN; + wow_event_mask |= AR_WOW_BEACON_FAIL; + } else { + clr = AR_WOW_BEACON_FAIL_EN; + } + + REG_RMW(ah, AR_WOW_BCN_EN, set, clr); + + set = 0; + clr = 0; + /* + * Enable the magic packet registers + */ + if (pattern_enable & AH_WOW_MAGIC_PATTERN_EN) { + set = AR_WOW_MAGIC_EN; + wow_event_mask |= AR_WOW_MAGIC_PAT_FOUND; + } else { + clr = AR_WOW_MAGIC_EN; + } + set |= AR_WOW_MAC_INTR_EN; + REG_RMW(ah, AR_WOW_PATTERN, set, clr); + + /* + * For AR9285 and later version of chipsets + * enable WoW pattern match for packets less + * than 256 bytes for all patterns + */ + if (AR_SREV_9285_12_OR_LATER(ah)) + REG_WRITE(ah, AR_WOW_PATTERN_MATCH_LT_256B, + AR_WOW_PATTERN_SUPPORTED); + + /* + * Set the power states appropriately and enable PME + */ + clr = 0; + set = AR_PMCTRL_PWR_STATE_D1D3 | AR_PMCTRL_HOST_PME_EN | + AR_PMCTRL_PWR_PM_CTRL_ENA; + /* + * This is needed for AR9300 chipsets to wake-up + * the host. + */ + if (AR_SREV_9300_20_OR_LATER(ah)) + clr = AR_PCIE_PM_CTRL_ENA; + + REG_RMW(ah, AR_PCIE_PM_CTRL, set, clr); + + if (AR_SREV_9462(ah) || AR_SREV_9565(ah)) { + /* + * this is needed to prevent the chip waking up + * the host within 3-4 seconds with certain + * platform/BIOS. The fix is to enable + * D1 & D3 to match original definition and + * also match the OTP value. Anyway this + * is more related to SW WOW. + */ + clr = AR_PMCTRL_PWR_STATE_D1D3; + REG_CLR_BIT(ah, AR_PCIE_PM_CTRL, clr); + + set = AR_PMCTRL_PWR_STATE_D1D3_REAL; + REG_SET_BIT(ah, AR_PCIE_PM_CTRL, set); + } + + + + REG_CLR_BIT(ah, AR_STA_ID1, AR_STA_ID1_PRESERVE_SEQNUM); + + if (AR_SREV_9300_20_OR_LATER(ah)) { + /* to bring down WOW power low margin */ + set = BIT(13); + REG_SET_BIT(ah, AR_PCIE_PHY_REG3, set); + /* HW WoW */ + clr = BIT(5); + REG_CLR_BIT(ah, AR_PCU_MISC_MODE3, clr); + } + + ath9k_hw_set_powermode_wow_sleep(ah); + ah->wow_event_mask = wow_event_mask; +} +EXPORT_SYMBOL(ath9k_hw_wow_enable); diff --git a/drivers/net/wireless/ath/ath9k/xmit.c b/drivers/net/wireless/ath/ath9k/xmit.c new file mode 100644 index 0000000..e752f5d --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/xmit.c @@ -0,0 +1,2426 @@ +/* + * Copyright (c) 2008-2011 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "ath9k.h" +#include "ar9003_mac.h" + +#define BITS_PER_BYTE 8 +#define OFDM_PLCP_BITS 22 +#define HT_RC_2_STREAMS(_rc) ((((_rc) & 0x78) >> 3) + 1) +#define L_STF 8 +#define L_LTF 8 +#define L_SIG 4 +#define HT_SIG 8 +#define HT_STF 4 +#define HT_LTF(_ns) (4 * (_ns)) +#define SYMBOL_TIME(_ns) ((_ns) << 2) /* ns * 4 us */ +#define SYMBOL_TIME_HALFGI(_ns) (((_ns) * 18 + 4) / 5) /* ns * 3.6 us */ +#define TIME_SYMBOLS(t) ((t) >> 2) +#define TIME_SYMBOLS_HALFGI(t) (((t) * 5 - 4) / 18) +#define NUM_SYMBOLS_PER_USEC(_usec) (_usec >> 2) +#define NUM_SYMBOLS_PER_USEC_HALFGI(_usec) (((_usec*5)-4)/18) + + +static u16 bits_per_symbol[][2] = { + /* 20MHz 40MHz */ + { 26, 54 }, /* 0: BPSK */ + { 52, 108 }, /* 1: QPSK 1/2 */ + { 78, 162 }, /* 2: QPSK 3/4 */ + { 104, 216 }, /* 3: 16-QAM 1/2 */ + { 156, 324 }, /* 4: 16-QAM 3/4 */ + { 208, 432 }, /* 5: 64-QAM 2/3 */ + { 234, 486 }, /* 6: 64-QAM 3/4 */ + { 260, 540 }, /* 7: 64-QAM 5/6 */ +}; + +#define IS_HT_RATE(_rate) ((_rate) & 0x80) + +static void ath_tx_send_normal(struct ath_softc *sc, struct ath_txq *txq, + struct ath_atx_tid *tid, struct sk_buff *skb); +static void ath_tx_complete(struct ath_softc *sc, struct sk_buff *skb, + int tx_flags, struct ath_txq *txq); +static void ath_tx_complete_buf(struct ath_softc *sc, struct ath_buf *bf, + struct ath_txq *txq, struct list_head *bf_q, + struct ath_tx_status *ts, int txok); +static void ath_tx_txqaddbuf(struct ath_softc *sc, struct ath_txq *txq, + struct list_head *head, bool internal); +static void ath_tx_rc_status(struct ath_softc *sc, struct ath_buf *bf, + struct ath_tx_status *ts, int nframes, int nbad, + int txok); +static void ath_tx_update_baw(struct ath_softc *sc, struct ath_atx_tid *tid, + int seqno); +static struct ath_buf *ath_tx_setup_buffer(struct ath_softc *sc, + struct ath_txq *txq, + struct ath_atx_tid *tid, + struct sk_buff *skb); + +enum { + MCS_HT20, + MCS_HT20_SGI, + MCS_HT40, + MCS_HT40_SGI, +}; + +/*********************/ +/* Aggregation logic */ +/*********************/ + +void ath_txq_lock(struct ath_softc *sc, struct ath_txq *txq) + __acquires(&txq->axq_lock) +{ + spin_lock_bh(&txq->axq_lock); +} + +void ath_txq_unlock(struct ath_softc *sc, struct ath_txq *txq) + __releases(&txq->axq_lock) +{ + spin_unlock_bh(&txq->axq_lock); +} + +void ath_txq_unlock_complete(struct ath_softc *sc, struct ath_txq *txq) + __releases(&txq->axq_lock) +{ + struct sk_buff_head q; + struct sk_buff *skb; + + __skb_queue_head_init(&q); + skb_queue_splice_init(&txq->complete_q, &q); + spin_unlock_bh(&txq->axq_lock); + + while ((skb = __skb_dequeue(&q))) + ieee80211_tx_status(sc->hw, skb); +} + +static void ath_tx_queue_tid(struct ath_txq *txq, struct ath_atx_tid *tid) +{ + struct ath_atx_ac *ac = tid->ac; + + if (tid->paused) + return; + + if (tid->sched) + return; + + tid->sched = true; + list_add_tail(&tid->list, &ac->tid_q); + + if (ac->sched) + return; + + ac->sched = true; + list_add_tail(&ac->list, &txq->axq_acq); +} + +static struct ath_frame_info *get_frame_info(struct sk_buff *skb) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + BUILD_BUG_ON(sizeof(struct ath_frame_info) > + sizeof(tx_info->rate_driver_data)); + return (struct ath_frame_info *) &tx_info->rate_driver_data[0]; +} + +static void ath_send_bar(struct ath_atx_tid *tid, u16 seqno) +{ + ieee80211_send_bar(tid->an->vif, tid->an->sta->addr, tid->tidno, + seqno << IEEE80211_SEQ_SEQ_SHIFT); +} + +static void ath_set_rates(struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ath_buf *bf) +{ + ieee80211_get_tx_rates(vif, sta, bf->bf_mpdu, bf->rates, + ARRAY_SIZE(bf->rates)); +} + +static void ath_tx_flush_tid(struct ath_softc *sc, struct ath_atx_tid *tid) +{ + struct ath_txq *txq = tid->ac->txq; + struct sk_buff *skb; + struct ath_buf *bf; + struct list_head bf_head; + struct ath_tx_status ts; + struct ath_frame_info *fi; + bool sendbar = false; + + INIT_LIST_HEAD(&bf_head); + + memset(&ts, 0, sizeof(ts)); + + while ((skb = __skb_dequeue(&tid->buf_q))) { + fi = get_frame_info(skb); + bf = fi->bf; + + if (!bf) { + bf = ath_tx_setup_buffer(sc, txq, tid, skb); + if (!bf) { + ieee80211_free_txskb(sc->hw, skb); + continue; + } + } + + if (fi->retries) { + list_add_tail(&bf->list, &bf_head); + ath_tx_update_baw(sc, tid, bf->bf_state.seqno); + ath_tx_complete_buf(sc, bf, txq, &bf_head, &ts, 0); + sendbar = true; + } else { + ath_set_rates(tid->an->vif, tid->an->sta, bf); + ath_tx_send_normal(sc, txq, NULL, skb); + } + } + + if (sendbar) { + ath_txq_unlock(sc, txq); + ath_send_bar(tid, tid->seq_start); + ath_txq_lock(sc, txq); + } +} + +static void ath_tx_update_baw(struct ath_softc *sc, struct ath_atx_tid *tid, + int seqno) +{ + int index, cindex; + + index = ATH_BA_INDEX(tid->seq_start, seqno); + cindex = (tid->baw_head + index) & (ATH_TID_MAX_BUFS - 1); + + __clear_bit(cindex, tid->tx_buf); + + while (tid->baw_head != tid->baw_tail && !test_bit(tid->baw_head, tid->tx_buf)) { + INCR(tid->seq_start, IEEE80211_SEQ_MAX); + INCR(tid->baw_head, ATH_TID_MAX_BUFS); + if (tid->bar_index >= 0) + tid->bar_index--; + } +} + +static void ath_tx_addto_baw(struct ath_softc *sc, struct ath_atx_tid *tid, + u16 seqno) +{ + int index, cindex; + + index = ATH_BA_INDEX(tid->seq_start, seqno); + cindex = (tid->baw_head + index) & (ATH_TID_MAX_BUFS - 1); + __set_bit(cindex, tid->tx_buf); + + if (index >= ((tid->baw_tail - tid->baw_head) & + (ATH_TID_MAX_BUFS - 1))) { + tid->baw_tail = cindex; + INCR(tid->baw_tail, ATH_TID_MAX_BUFS); + } +} + +/* + * TODO: For frame(s) that are in the retry state, we will reuse the + * sequence number(s) without setting the retry bit. The + * alternative is to give up on these and BAR the receiver's window + * forward. + */ +static void ath_tid_drain(struct ath_softc *sc, struct ath_txq *txq, + struct ath_atx_tid *tid) + +{ + struct sk_buff *skb; + struct ath_buf *bf; + struct list_head bf_head; + struct ath_tx_status ts; + struct ath_frame_info *fi; + + memset(&ts, 0, sizeof(ts)); + INIT_LIST_HEAD(&bf_head); + + while ((skb = __skb_dequeue(&tid->buf_q))) { + fi = get_frame_info(skb); + bf = fi->bf; + + if (!bf) { + ath_tx_complete(sc, skb, ATH_TX_ERROR, txq); + continue; + } + + list_add_tail(&bf->list, &bf_head); + + ath_tx_update_baw(sc, tid, bf->bf_state.seqno); + ath_tx_complete_buf(sc, bf, txq, &bf_head, &ts, 0); + } + + tid->seq_next = tid->seq_start; + tid->baw_tail = tid->baw_head; + tid->bar_index = -1; +} + +static void ath_tx_set_retry(struct ath_softc *sc, struct ath_txq *txq, + struct sk_buff *skb, int count) +{ + struct ath_frame_info *fi = get_frame_info(skb); + struct ath_buf *bf = fi->bf; + struct ieee80211_hdr *hdr; + int prev = fi->retries; + + TX_STAT_INC(txq->axq_qnum, a_retries); + fi->retries += count; + + if (prev > 0) + return; + + hdr = (struct ieee80211_hdr *)skb->data; + hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_RETRY); + dma_sync_single_for_device(sc->dev, bf->bf_buf_addr, + sizeof(*hdr), DMA_TO_DEVICE); +} + +static struct ath_buf *ath_tx_get_buffer(struct ath_softc *sc) +{ + struct ath_buf *bf = NULL; + + spin_lock_bh(&sc->tx.txbuflock); + + if (unlikely(list_empty(&sc->tx.txbuf))) { + spin_unlock_bh(&sc->tx.txbuflock); + return NULL; + } + + bf = list_first_entry(&sc->tx.txbuf, struct ath_buf, list); + list_del(&bf->list); + + spin_unlock_bh(&sc->tx.txbuflock); + + return bf; +} + +static void ath_tx_return_buffer(struct ath_softc *sc, struct ath_buf *bf) +{ + spin_lock_bh(&sc->tx.txbuflock); + list_add_tail(&bf->list, &sc->tx.txbuf); + spin_unlock_bh(&sc->tx.txbuflock); +} + +static struct ath_buf* ath_clone_txbuf(struct ath_softc *sc, struct ath_buf *bf) +{ + struct ath_buf *tbf; + + tbf = ath_tx_get_buffer(sc); + if (WARN_ON(!tbf)) + return NULL; + + ATH_TXBUF_RESET(tbf); + + tbf->bf_mpdu = bf->bf_mpdu; + tbf->bf_buf_addr = bf->bf_buf_addr; + memcpy(tbf->bf_desc, bf->bf_desc, sc->sc_ah->caps.tx_desc_len); + tbf->bf_state = bf->bf_state; + + return tbf; +} + +static void ath_tx_count_frames(struct ath_softc *sc, struct ath_buf *bf, + struct ath_tx_status *ts, int txok, + int *nframes, int *nbad) +{ + struct ath_frame_info *fi; + u16 seq_st = 0; + u32 ba[WME_BA_BMP_SIZE >> 5]; + int ba_index; + int isaggr = 0; + + *nbad = 0; + *nframes = 0; + + isaggr = bf_isaggr(bf); + if (isaggr) { + seq_st = ts->ts_seqnum; + memcpy(ba, &ts->ba_low, WME_BA_BMP_SIZE >> 3); + } + + while (bf) { + fi = get_frame_info(bf->bf_mpdu); + ba_index = ATH_BA_INDEX(seq_st, bf->bf_state.seqno); + + (*nframes)++; + if (!txok || (isaggr && !ATH_BA_ISSET(ba, ba_index))) + (*nbad)++; + + bf = bf->bf_next; + } +} + + +static void ath_tx_complete_aggr(struct ath_softc *sc, struct ath_txq *txq, + struct ath_buf *bf, struct list_head *bf_q, + struct ath_tx_status *ts, int txok) +{ + struct ath_node *an = NULL; + struct sk_buff *skb; + struct ieee80211_sta *sta; + struct ieee80211_hw *hw = sc->hw; + struct ieee80211_hdr *hdr; + struct ieee80211_tx_info *tx_info; + struct ath_atx_tid *tid = NULL; + struct ath_buf *bf_next, *bf_last = bf->bf_lastbf; + struct list_head bf_head; + struct sk_buff_head bf_pending; + u16 seq_st = 0, acked_cnt = 0, txfail_cnt = 0, seq_first; + u32 ba[WME_BA_BMP_SIZE >> 5]; + int isaggr, txfail, txpending, sendbar = 0, needreset = 0, nbad = 0; + bool rc_update = true, isba; + struct ieee80211_tx_rate rates[4]; + struct ath_frame_info *fi; + int nframes; + u8 tidno; + bool flush = !!(ts->ts_status & ATH9K_TX_FLUSH); + int i, retries; + int bar_index = -1; + + skb = bf->bf_mpdu; + hdr = (struct ieee80211_hdr *)skb->data; + + tx_info = IEEE80211_SKB_CB(skb); + + memcpy(rates, bf->rates, sizeof(rates)); + + retries = ts->ts_longretry + 1; + for (i = 0; i < ts->ts_rateindex; i++) + retries += rates[i].count; + + rcu_read_lock(); + + sta = ieee80211_find_sta_by_ifaddr(hw, hdr->addr1, hdr->addr2); + if (!sta) { + rcu_read_unlock(); + + INIT_LIST_HEAD(&bf_head); + while (bf) { + bf_next = bf->bf_next; + + if (!bf->bf_stale || bf_next != NULL) + list_move_tail(&bf->list, &bf_head); + + ath_tx_complete_buf(sc, bf, txq, &bf_head, ts, 0); + + bf = bf_next; + } + return; + } + + an = (struct ath_node *)sta->drv_priv; + tidno = ieee80211_get_qos_ctl(hdr)[0] & IEEE80211_QOS_CTL_TID_MASK; + tid = ATH_AN_2_TID(an, tidno); + seq_first = tid->seq_start; + isba = ts->ts_flags & ATH9K_TX_BA; + + /* + * The hardware occasionally sends a tx status for the wrong TID. + * In this case, the BA status cannot be considered valid and all + * subframes need to be retransmitted + * + * Only BlockAcks have a TID and therefore normal Acks cannot be + * checked + */ + if (isba && tidno != ts->tid) + txok = false; + + isaggr = bf_isaggr(bf); + memset(ba, 0, WME_BA_BMP_SIZE >> 3); + + if (isaggr && txok) { + if (ts->ts_flags & ATH9K_TX_BA) { + seq_st = ts->ts_seqnum; + memcpy(ba, &ts->ba_low, WME_BA_BMP_SIZE >> 3); + } else { + /* + * AR5416 can become deaf/mute when BA + * issue happens. Chip needs to be reset. + * But AP code may have sychronization issues + * when perform internal reset in this routine. + * Only enable reset in STA mode for now. + */ + if (sc->sc_ah->opmode == NL80211_IFTYPE_STATION) + needreset = 1; + } + } + + __skb_queue_head_init(&bf_pending); + + ath_tx_count_frames(sc, bf, ts, txok, &nframes, &nbad); + while (bf) { + u16 seqno = bf->bf_state.seqno; + + txfail = txpending = sendbar = 0; + bf_next = bf->bf_next; + + skb = bf->bf_mpdu; + tx_info = IEEE80211_SKB_CB(skb); + fi = get_frame_info(skb); + + if (!BAW_WITHIN(tid->seq_start, tid->baw_size, seqno)) { + /* + * Outside of the current BlockAck window, + * maybe part of a previous session + */ + txfail = 1; + } else if (ATH_BA_ISSET(ba, ATH_BA_INDEX(seq_st, seqno))) { + /* transmit completion, subframe is + * acked by block ack */ + acked_cnt++; + } else if (!isaggr && txok) { + /* transmit completion */ + acked_cnt++; + } else if (flush) { + txpending = 1; + } else if (fi->retries < ATH_MAX_SW_RETRIES) { + if (txok || !an->sleeping) + ath_tx_set_retry(sc, txq, bf->bf_mpdu, + retries); + + txpending = 1; + } else { + txfail = 1; + txfail_cnt++; + bar_index = max_t(int, bar_index, + ATH_BA_INDEX(seq_first, seqno)); + } + + /* + * Make sure the last desc is reclaimed if it + * not a holding desc. + */ + INIT_LIST_HEAD(&bf_head); + if (bf_next != NULL || !bf_last->bf_stale) + list_move_tail(&bf->list, &bf_head); + + if (!txpending) { + /* + * complete the acked-ones/xretried ones; update + * block-ack window + */ + ath_tx_update_baw(sc, tid, seqno); + + if (rc_update && (acked_cnt == 1 || txfail_cnt == 1)) { + memcpy(tx_info->control.rates, rates, sizeof(rates)); + ath_tx_rc_status(sc, bf, ts, nframes, nbad, txok); + rc_update = false; + } + + ath_tx_complete_buf(sc, bf, txq, &bf_head, ts, + !txfail); + } else { + /* retry the un-acked ones */ + if (bf->bf_next == NULL && bf_last->bf_stale) { + struct ath_buf *tbf; + + tbf = ath_clone_txbuf(sc, bf_last); + /* + * Update tx baw and complete the + * frame with failed status if we + * run out of tx buf. + */ + if (!tbf) { + ath_tx_update_baw(sc, tid, seqno); + + ath_tx_complete_buf(sc, bf, txq, + &bf_head, ts, 0); + bar_index = max_t(int, bar_index, + ATH_BA_INDEX(seq_first, seqno)); + break; + } + + fi->bf = tbf; + } + + /* + * Put this buffer to the temporary pending + * queue to retain ordering + */ + __skb_queue_tail(&bf_pending, skb); + } + + bf = bf_next; + } + + /* prepend un-acked frames to the beginning of the pending frame queue */ + if (!skb_queue_empty(&bf_pending)) { + if (an->sleeping) + ieee80211_sta_set_buffered(sta, tid->tidno, true); + + skb_queue_splice(&bf_pending, &tid->buf_q); + if (!an->sleeping) { + ath_tx_queue_tid(txq, tid); + + if (ts->ts_status & (ATH9K_TXERR_FILT | ATH9K_TXERR_XRETRY)) + tid->ac->clear_ps_filter = true; + } + } + + if (bar_index >= 0) { + u16 bar_seq = ATH_BA_INDEX2SEQ(seq_first, bar_index); + + if (BAW_WITHIN(tid->seq_start, tid->baw_size, bar_seq)) + tid->bar_index = ATH_BA_INDEX(tid->seq_start, bar_seq); + + ath_txq_unlock(sc, txq); + ath_send_bar(tid, ATH_BA_INDEX2SEQ(seq_first, bar_index + 1)); + ath_txq_lock(sc, txq); + } + + rcu_read_unlock(); + + if (needreset) + ath9k_queue_reset(sc, RESET_TYPE_TX_ERROR); +} + +static bool bf_is_ampdu_not_probing(struct ath_buf *bf) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(bf->bf_mpdu); + return bf_isampdu(bf) && !(info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE); +} + +static void ath_tx_process_buffer(struct ath_softc *sc, struct ath_txq *txq, + struct ath_tx_status *ts, struct ath_buf *bf, + struct list_head *bf_head) +{ + struct ieee80211_tx_info *info; + bool txok, flush; + + txok = !(ts->ts_status & ATH9K_TXERR_MASK); + flush = !!(ts->ts_status & ATH9K_TX_FLUSH); + txq->axq_tx_inprogress = false; + + txq->axq_depth--; + if (bf_is_ampdu_not_probing(bf)) + txq->axq_ampdu_depth--; + + if (!bf_isampdu(bf)) { + if (!flush) { + info = IEEE80211_SKB_CB(bf->bf_mpdu); + memcpy(info->control.rates, bf->rates, + sizeof(info->control.rates)); + ath_tx_rc_status(sc, bf, ts, 1, txok ? 0 : 1, txok); + } + ath_tx_complete_buf(sc, bf, txq, bf_head, ts, txok); + } else + ath_tx_complete_aggr(sc, txq, bf, bf_head, ts, txok); + + if ((sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_HT) && !flush) + ath_txq_schedule(sc, txq); +} + +static bool ath_lookup_legacy(struct ath_buf *bf) +{ + struct sk_buff *skb; + struct ieee80211_tx_info *tx_info; + struct ieee80211_tx_rate *rates; + int i; + + skb = bf->bf_mpdu; + tx_info = IEEE80211_SKB_CB(skb); + rates = tx_info->control.rates; + + for (i = 0; i < 4; i++) { + if (!rates[i].count || rates[i].idx < 0) + break; + + if (!(rates[i].flags & IEEE80211_TX_RC_MCS)) + return true; + } + + return false; +} + +static u32 ath_lookup_rate(struct ath_softc *sc, struct ath_buf *bf, + struct ath_atx_tid *tid) +{ + struct sk_buff *skb; + struct ieee80211_tx_info *tx_info; + struct ieee80211_tx_rate *rates; + u32 max_4ms_framelen, frmlen; + u16 aggr_limit, bt_aggr_limit, legacy = 0; + int q = tid->ac->txq->mac80211_qnum; + int i; + + skb = bf->bf_mpdu; + tx_info = IEEE80211_SKB_CB(skb); + rates = bf->rates; + + /* + * Find the lowest frame length among the rate series that will have a + * 4ms (or TXOP limited) transmit duration. + */ + max_4ms_framelen = ATH_AMPDU_LIMIT_MAX; + + for (i = 0; i < 4; i++) { + int modeidx; + + if (!rates[i].count) + continue; + + if (!(rates[i].flags & IEEE80211_TX_RC_MCS)) { + legacy = 1; + break; + } + + if (rates[i].flags & IEEE80211_TX_RC_40_MHZ_WIDTH) + modeidx = MCS_HT40; + else + modeidx = MCS_HT20; + + if (rates[i].flags & IEEE80211_TX_RC_SHORT_GI) + modeidx++; + + frmlen = sc->tx.max_aggr_framelen[q][modeidx][rates[i].idx]; + max_4ms_framelen = min(max_4ms_framelen, frmlen); + } + + /* + * limit aggregate size by the minimum rate if rate selected is + * not a probe rate, if rate selected is a probe rate then + * avoid aggregation of this packet. + */ + if (tx_info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE || legacy) + return 0; + + aggr_limit = min(max_4ms_framelen, (u32)ATH_AMPDU_LIMIT_MAX); + + /* + * Override the default aggregation limit for BTCOEX. + */ + bt_aggr_limit = ath9k_btcoex_aggr_limit(sc, max_4ms_framelen); + if (bt_aggr_limit) + aggr_limit = bt_aggr_limit; + + /* + * h/w can accept aggregates up to 16 bit lengths (65535). + * The IE, however can hold up to 65536, which shows up here + * as zero. Ignore 65536 since we are constrained by hw. + */ + if (tid->an->maxampdu) + aggr_limit = min(aggr_limit, tid->an->maxampdu); + + return aggr_limit; +} + +/* + * Returns the number of delimiters to be added to + * meet the minimum required mpdudensity. + */ +static int ath_compute_num_delims(struct ath_softc *sc, struct ath_atx_tid *tid, + struct ath_buf *bf, u16 frmlen, + bool first_subfrm) +{ +#define FIRST_DESC_NDELIMS 60 + u32 nsymbits, nsymbols; + u16 minlen; + u8 flags, rix; + int width, streams, half_gi, ndelim, mindelim; + struct ath_frame_info *fi = get_frame_info(bf->bf_mpdu); + + /* Select standard number of delimiters based on frame length alone */ + ndelim = ATH_AGGR_GET_NDELIM(frmlen); + + /* + * If encryption enabled, hardware requires some more padding between + * subframes. + * TODO - this could be improved to be dependent on the rate. + * The hardware can keep up at lower rates, but not higher rates + */ + if ((fi->keyix != ATH9K_TXKEYIX_INVALID) && + !(sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA)) + ndelim += ATH_AGGR_ENCRYPTDELIM; + + /* + * Add delimiter when using RTS/CTS with aggregation + * and non enterprise AR9003 card + */ + if (first_subfrm && !AR_SREV_9580_10_OR_LATER(sc->sc_ah) && + (sc->sc_ah->ent_mode & AR_ENT_OTP_MIN_PKT_SIZE_DISABLE)) + ndelim = max(ndelim, FIRST_DESC_NDELIMS); + + /* + * Convert desired mpdu density from microeconds to bytes based + * on highest rate in rate series (i.e. first rate) to determine + * required minimum length for subframe. Take into account + * whether high rate is 20 or 40Mhz and half or full GI. + * + * If there is no mpdu density restriction, no further calculation + * is needed. + */ + + if (tid->an->mpdudensity == 0) + return ndelim; + + rix = bf->rates[0].idx; + flags = bf->rates[0].flags; + width = (flags & IEEE80211_TX_RC_40_MHZ_WIDTH) ? 1 : 0; + half_gi = (flags & IEEE80211_TX_RC_SHORT_GI) ? 1 : 0; + + if (half_gi) + nsymbols = NUM_SYMBOLS_PER_USEC_HALFGI(tid->an->mpdudensity); + else + nsymbols = NUM_SYMBOLS_PER_USEC(tid->an->mpdudensity); + + if (nsymbols == 0) + nsymbols = 1; + + streams = HT_RC_2_STREAMS(rix); + nsymbits = bits_per_symbol[rix % 8][width] * streams; + minlen = (nsymbols * nsymbits) / BITS_PER_BYTE; + + if (frmlen < minlen) { + mindelim = (minlen - frmlen) / ATH_AGGR_DELIM_SZ; + ndelim = max(mindelim, ndelim); + } + + return ndelim; +} + +static enum ATH_AGGR_STATUS ath_tx_form_aggr(struct ath_softc *sc, + struct ath_txq *txq, + struct ath_atx_tid *tid, + struct list_head *bf_q, + int *aggr_len) +{ +#define PADBYTES(_len) ((4 - ((_len) % 4)) % 4) + struct ath_buf *bf, *bf_first = NULL, *bf_prev = NULL; + int rl = 0, nframes = 0, ndelim, prev_al = 0; + u16 aggr_limit = 0, al = 0, bpad = 0, + al_delta, h_baw = tid->baw_size / 2; + enum ATH_AGGR_STATUS status = ATH_AGGR_DONE; + struct ieee80211_tx_info *tx_info; + struct ath_frame_info *fi; + struct sk_buff *skb; + u16 seqno; + + do { + skb = skb_peek(&tid->buf_q); + fi = get_frame_info(skb); + bf = fi->bf; + if (!fi->bf) + bf = ath_tx_setup_buffer(sc, txq, tid, skb); + + if (!bf) { + __skb_unlink(skb, &tid->buf_q); + ieee80211_free_txskb(sc->hw, skb); + continue; + } + + bf->bf_state.bf_type = BUF_AMPDU | BUF_AGGR; + seqno = bf->bf_state.seqno; + + /* do not step over block-ack window */ + if (!BAW_WITHIN(tid->seq_start, tid->baw_size, seqno)) { + status = ATH_AGGR_BAW_CLOSED; + break; + } + + if (tid->bar_index > ATH_BA_INDEX(tid->seq_start, seqno)) { + struct ath_tx_status ts = {}; + struct list_head bf_head; + + INIT_LIST_HEAD(&bf_head); + list_add(&bf->list, &bf_head); + __skb_unlink(skb, &tid->buf_q); + ath_tx_update_baw(sc, tid, seqno); + ath_tx_complete_buf(sc, bf, txq, &bf_head, &ts, 0); + continue; + } + + if (!bf_first) + bf_first = bf; + + if (!rl) { + ath_set_rates(tid->an->vif, tid->an->sta, bf); + aggr_limit = ath_lookup_rate(sc, bf, tid); + rl = 1; + } + + /* do not exceed aggregation limit */ + al_delta = ATH_AGGR_DELIM_SZ + fi->framelen; + + if (nframes && + ((aggr_limit < (al + bpad + al_delta + prev_al)) || + ath_lookup_legacy(bf))) { + status = ATH_AGGR_LIMITED; + break; + } + + tx_info = IEEE80211_SKB_CB(bf->bf_mpdu); + if (nframes && (tx_info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE)) + break; + + /* do not exceed subframe limit */ + if (nframes >= min((int)h_baw, ATH_AMPDU_SUBFRAME_DEFAULT)) { + status = ATH_AGGR_LIMITED; + break; + } + + /* add padding for previous frame to aggregation length */ + al += bpad + al_delta; + + /* + * Get the delimiters needed to meet the MPDU + * density for this node. + */ + ndelim = ath_compute_num_delims(sc, tid, bf_first, fi->framelen, + !nframes); + bpad = PADBYTES(al_delta) + (ndelim << 2); + + nframes++; + bf->bf_next = NULL; + + /* link buffers of this frame to the aggregate */ + if (!fi->retries) + ath_tx_addto_baw(sc, tid, seqno); + bf->bf_state.ndelim = ndelim; + + __skb_unlink(skb, &tid->buf_q); + list_add_tail(&bf->list, bf_q); + if (bf_prev) + bf_prev->bf_next = bf; + + bf_prev = bf; + + } while (!skb_queue_empty(&tid->buf_q)); + + *aggr_len = al; + + return status; +#undef PADBYTES +} + +/* + * rix - rate index + * pktlen - total bytes (delims + data + fcs + pads + pad delims) + * width - 0 for 20 MHz, 1 for 40 MHz + * half_gi - to use 4us v/s 3.6 us for symbol time + */ +static u32 ath_pkt_duration(struct ath_softc *sc, u8 rix, int pktlen, + int width, int half_gi, bool shortPreamble) +{ + u32 nbits, nsymbits, duration, nsymbols; + int streams; + + /* find number of symbols: PLCP + data */ + streams = HT_RC_2_STREAMS(rix); + nbits = (pktlen << 3) + OFDM_PLCP_BITS; + nsymbits = bits_per_symbol[rix % 8][width] * streams; + nsymbols = (nbits + nsymbits - 1) / nsymbits; + + if (!half_gi) + duration = SYMBOL_TIME(nsymbols); + else + duration = SYMBOL_TIME_HALFGI(nsymbols); + + /* addup duration for legacy/ht training and signal fields */ + duration += L_STF + L_LTF + L_SIG + HT_SIG + HT_STF + HT_LTF(streams); + + return duration; +} + +static int ath_max_framelen(int usec, int mcs, bool ht40, bool sgi) +{ + int streams = HT_RC_2_STREAMS(mcs); + int symbols, bits; + int bytes = 0; + + symbols = sgi ? TIME_SYMBOLS_HALFGI(usec) : TIME_SYMBOLS(usec); + bits = symbols * bits_per_symbol[mcs % 8][ht40] * streams; + bits -= OFDM_PLCP_BITS; + bytes = bits / 8; + bytes -= L_STF + L_LTF + L_SIG + HT_SIG + HT_STF + HT_LTF(streams); + if (bytes > 65532) + bytes = 65532; + + return bytes; +} + +void ath_update_max_aggr_framelen(struct ath_softc *sc, int queue, int txop) +{ + u16 *cur_ht20, *cur_ht20_sgi, *cur_ht40, *cur_ht40_sgi; + int mcs; + + /* 4ms is the default (and maximum) duration */ + if (!txop || txop > 4096) + txop = 4096; + + cur_ht20 = sc->tx.max_aggr_framelen[queue][MCS_HT20]; + cur_ht20_sgi = sc->tx.max_aggr_framelen[queue][MCS_HT20_SGI]; + cur_ht40 = sc->tx.max_aggr_framelen[queue][MCS_HT40]; + cur_ht40_sgi = sc->tx.max_aggr_framelen[queue][MCS_HT40_SGI]; + for (mcs = 0; mcs < 32; mcs++) { + cur_ht20[mcs] = ath_max_framelen(txop, mcs, false, false); + cur_ht20_sgi[mcs] = ath_max_framelen(txop, mcs, false, true); + cur_ht40[mcs] = ath_max_framelen(txop, mcs, true, false); + cur_ht40_sgi[mcs] = ath_max_framelen(txop, mcs, true, true); + } +} + +static void ath_buf_set_rate(struct ath_softc *sc, struct ath_buf *bf, + struct ath_tx_info *info, int len) +{ + struct ath_hw *ah = sc->sc_ah; + struct sk_buff *skb; + struct ieee80211_tx_info *tx_info; + struct ieee80211_tx_rate *rates; + const struct ieee80211_rate *rate; + struct ieee80211_hdr *hdr; + struct ath_frame_info *fi = get_frame_info(bf->bf_mpdu); + int i; + u8 rix = 0; + + skb = bf->bf_mpdu; + tx_info = IEEE80211_SKB_CB(skb); + rates = bf->rates; + hdr = (struct ieee80211_hdr *)skb->data; + + /* set dur_update_en for l-sig computation except for PS-Poll frames */ + info->dur_update = !ieee80211_is_pspoll(hdr->frame_control); + info->rtscts_rate = fi->rtscts_rate; + + for (i = 0; i < ARRAY_SIZE(bf->rates); i++) { + bool is_40, is_sgi, is_sp; + int phy; + + if (!rates[i].count || (rates[i].idx < 0)) + continue; + + rix = rates[i].idx; + info->rates[i].Tries = rates[i].count; + + if (rates[i].flags & IEEE80211_TX_RC_USE_RTS_CTS) { + info->rates[i].RateFlags |= ATH9K_RATESERIES_RTS_CTS; + info->flags |= ATH9K_TXDESC_RTSENA; + } else if (rates[i].flags & IEEE80211_TX_RC_USE_CTS_PROTECT) { + info->rates[i].RateFlags |= ATH9K_RATESERIES_RTS_CTS; + info->flags |= ATH9K_TXDESC_CTSENA; + } + + if (rates[i].flags & IEEE80211_TX_RC_40_MHZ_WIDTH) + info->rates[i].RateFlags |= ATH9K_RATESERIES_2040; + if (rates[i].flags & IEEE80211_TX_RC_SHORT_GI) + info->rates[i].RateFlags |= ATH9K_RATESERIES_HALFGI; + + is_sgi = !!(rates[i].flags & IEEE80211_TX_RC_SHORT_GI); + is_40 = !!(rates[i].flags & IEEE80211_TX_RC_40_MHZ_WIDTH); + is_sp = !!(rates[i].flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE); + + if (rates[i].flags & IEEE80211_TX_RC_MCS) { + /* MCS rates */ + info->rates[i].Rate = rix | 0x80; + info->rates[i].ChSel = ath_txchainmask_reduction(sc, + ah->txchainmask, info->rates[i].Rate); + info->rates[i].PktDuration = ath_pkt_duration(sc, rix, len, + is_40, is_sgi, is_sp); + if (rix < 8 && (tx_info->flags & IEEE80211_TX_CTL_STBC)) + info->rates[i].RateFlags |= ATH9K_RATESERIES_STBC; + continue; + } + + /* legacy rates */ + rate = &sc->sbands[tx_info->band].bitrates[rates[i].idx]; + if ((tx_info->band == IEEE80211_BAND_2GHZ) && + !(rate->flags & IEEE80211_RATE_ERP_G)) + phy = WLAN_RC_PHY_CCK; + else + phy = WLAN_RC_PHY_OFDM; + + info->rates[i].Rate = rate->hw_value; + if (rate->hw_value_short) { + if (rates[i].flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE) + info->rates[i].Rate |= rate->hw_value_short; + } else { + is_sp = false; + } + + if (bf->bf_state.bfs_paprd) + info->rates[i].ChSel = ah->txchainmask; + else + info->rates[i].ChSel = ath_txchainmask_reduction(sc, + ah->txchainmask, info->rates[i].Rate); + + info->rates[i].PktDuration = ath9k_hw_computetxtime(sc->sc_ah, + phy, rate->bitrate * 100, len, rix, is_sp); + } + + /* For AR5416 - RTS cannot be followed by a frame larger than 8K */ + if (bf_isaggr(bf) && (len > sc->sc_ah->caps.rts_aggr_limit)) + info->flags &= ~ATH9K_TXDESC_RTSENA; + + /* ATH9K_TXDESC_RTSENA and ATH9K_TXDESC_CTSENA are mutually exclusive. */ + if (info->flags & ATH9K_TXDESC_RTSENA) + info->flags &= ~ATH9K_TXDESC_CTSENA; +} + +static enum ath9k_pkt_type get_hw_packet_type(struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr; + enum ath9k_pkt_type htype; + __le16 fc; + + hdr = (struct ieee80211_hdr *)skb->data; + fc = hdr->frame_control; + + if (ieee80211_is_beacon(fc)) + htype = ATH9K_PKT_TYPE_BEACON; + else if (ieee80211_is_probe_resp(fc)) + htype = ATH9K_PKT_TYPE_PROBE_RESP; + else if (ieee80211_is_atim(fc)) + htype = ATH9K_PKT_TYPE_ATIM; + else if (ieee80211_is_pspoll(fc)) + htype = ATH9K_PKT_TYPE_PSPOLL; + else + htype = ATH9K_PKT_TYPE_NORMAL; + + return htype; +} + +static void ath_tx_fill_desc(struct ath_softc *sc, struct ath_buf *bf, + struct ath_txq *txq, int len) +{ + struct ath_hw *ah = sc->sc_ah; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(bf->bf_mpdu); + struct ath_buf *bf_first = bf; + struct ath_tx_info info; + bool aggr = !!(bf->bf_state.bf_type & BUF_AGGR); + + memset(&info, 0, sizeof(info)); + info.is_first = true; + info.is_last = true; + info.txpower = MAX_RATE_POWER; + info.qcu = txq->axq_qnum; + + info.flags = ATH9K_TXDESC_INTREQ; + if (tx_info->flags & IEEE80211_TX_CTL_NO_ACK) + info.flags |= ATH9K_TXDESC_NOACK; + if (tx_info->flags & IEEE80211_TX_CTL_LDPC) + info.flags |= ATH9K_TXDESC_LDPC; + + ath_buf_set_rate(sc, bf, &info, len); + + if (tx_info->flags & IEEE80211_TX_CTL_CLEAR_PS_FILT) + info.flags |= ATH9K_TXDESC_CLRDMASK; + + if (bf->bf_state.bfs_paprd) + info.flags |= (u32) bf->bf_state.bfs_paprd << ATH9K_TXDESC_PAPRD_S; + + + while (bf) { + struct sk_buff *skb = bf->bf_mpdu; + struct ath_frame_info *fi = get_frame_info(skb); + + info.type = get_hw_packet_type(skb); + if (bf->bf_next) + info.link = bf->bf_next->bf_daddr; + else + info.link = 0; + + info.buf_addr[0] = bf->bf_buf_addr; + info.buf_len[0] = skb->len; + info.pkt_len = fi->framelen; + info.keyix = fi->keyix; + info.keytype = fi->keytype; + + if (aggr) { + if (bf == bf_first) + info.aggr = AGGR_BUF_FIRST; + else if (!bf->bf_next) + info.aggr = AGGR_BUF_LAST; + else + info.aggr = AGGR_BUF_MIDDLE; + + info.ndelim = bf->bf_state.ndelim; + info.aggr_len = len; + } + + ath9k_hw_set_txdesc(ah, bf->bf_desc, &info); + bf = bf->bf_next; + } +} + +static void ath_tx_sched_aggr(struct ath_softc *sc, struct ath_txq *txq, + struct ath_atx_tid *tid) +{ + struct ath_buf *bf; + enum ATH_AGGR_STATUS status; + struct ieee80211_tx_info *tx_info; + struct list_head bf_q; + int aggr_len; + + do { + if (skb_queue_empty(&tid->buf_q)) + return; + + INIT_LIST_HEAD(&bf_q); + + status = ath_tx_form_aggr(sc, txq, tid, &bf_q, &aggr_len); + + /* + * no frames picked up to be aggregated; + * block-ack window is not open. + */ + if (list_empty(&bf_q)) + break; + + bf = list_first_entry(&bf_q, struct ath_buf, list); + bf->bf_lastbf = list_entry(bf_q.prev, struct ath_buf, list); + tx_info = IEEE80211_SKB_CB(bf->bf_mpdu); + + if (tid->ac->clear_ps_filter) { + tid->ac->clear_ps_filter = false; + tx_info->flags |= IEEE80211_TX_CTL_CLEAR_PS_FILT; + } else { + tx_info->flags &= ~IEEE80211_TX_CTL_CLEAR_PS_FILT; + } + + /* if only one frame, send as non-aggregate */ + if (bf == bf->bf_lastbf) { + aggr_len = get_frame_info(bf->bf_mpdu)->framelen; + bf->bf_state.bf_type = BUF_AMPDU; + } else { + TX_STAT_INC(txq->axq_qnum, a_aggr); + } + + ath_tx_fill_desc(sc, bf, txq, aggr_len); + ath_tx_txqaddbuf(sc, txq, &bf_q, false); + } while (txq->axq_ampdu_depth < ATH_AGGR_MIN_QDEPTH && + status != ATH_AGGR_BAW_CLOSED); +} + +int ath_tx_aggr_start(struct ath_softc *sc, struct ieee80211_sta *sta, + u16 tid, u16 *ssn) +{ + struct ath_atx_tid *txtid; + struct ath_node *an; + u8 density; + + an = (struct ath_node *)sta->drv_priv; + txtid = ATH_AN_2_TID(an, tid); + + /* update ampdu factor/density, they may have changed. This may happen + * in HT IBSS when a beacon with HT-info is received after the station + * has already been added. + */ + if (sta->ht_cap.ht_supported) { + an->maxampdu = 1 << (IEEE80211_HT_MAX_AMPDU_FACTOR + + sta->ht_cap.ampdu_factor); + density = ath9k_parse_mpdudensity(sta->ht_cap.ampdu_density); + an->mpdudensity = density; + } + + txtid->active = true; + txtid->paused = true; + *ssn = txtid->seq_start = txtid->seq_next; + txtid->bar_index = -1; + + memset(txtid->tx_buf, 0, sizeof(txtid->tx_buf)); + txtid->baw_head = txtid->baw_tail = 0; + + return 0; +} + +void ath_tx_aggr_stop(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid) +{ + struct ath_node *an = (struct ath_node *)sta->drv_priv; + struct ath_atx_tid *txtid = ATH_AN_2_TID(an, tid); + struct ath_txq *txq = txtid->ac->txq; + + ath_txq_lock(sc, txq); + txtid->active = false; + txtid->paused = true; + ath_tx_flush_tid(sc, txtid); + ath_txq_unlock_complete(sc, txq); +} + +void ath_tx_aggr_sleep(struct ieee80211_sta *sta, struct ath_softc *sc, + struct ath_node *an) +{ + struct ath_atx_tid *tid; + struct ath_atx_ac *ac; + struct ath_txq *txq; + bool buffered; + int tidno; + + for (tidno = 0, tid = &an->tid[tidno]; + tidno < IEEE80211_NUM_TIDS; tidno++, tid++) { + + if (!tid->sched) + continue; + + ac = tid->ac; + txq = ac->txq; + + ath_txq_lock(sc, txq); + + buffered = !skb_queue_empty(&tid->buf_q); + + tid->sched = false; + list_del(&tid->list); + + if (ac->sched) { + ac->sched = false; + list_del(&ac->list); + } + + ath_txq_unlock(sc, txq); + + ieee80211_sta_set_buffered(sta, tidno, buffered); + } +} + +void ath_tx_aggr_wakeup(struct ath_softc *sc, struct ath_node *an) +{ + struct ath_atx_tid *tid; + struct ath_atx_ac *ac; + struct ath_txq *txq; + int tidno; + + for (tidno = 0, tid = &an->tid[tidno]; + tidno < IEEE80211_NUM_TIDS; tidno++, tid++) { + + ac = tid->ac; + txq = ac->txq; + + ath_txq_lock(sc, txq); + ac->clear_ps_filter = true; + + if (!skb_queue_empty(&tid->buf_q) && !tid->paused) { + ath_tx_queue_tid(txq, tid); + ath_txq_schedule(sc, txq); + } + + ath_txq_unlock_complete(sc, txq); + } +} + +void ath_tx_aggr_resume(struct ath_softc *sc, struct ieee80211_sta *sta, + u16 tidno) +{ + struct ath_atx_tid *tid; + struct ath_node *an; + struct ath_txq *txq; + + an = (struct ath_node *)sta->drv_priv; + tid = ATH_AN_2_TID(an, tidno); + txq = tid->ac->txq; + + ath_txq_lock(sc, txq); + + tid->baw_size = IEEE80211_MIN_AMPDU_BUF << sta->ht_cap.ampdu_factor; + tid->paused = false; + + if (!skb_queue_empty(&tid->buf_q)) { + ath_tx_queue_tid(txq, tid); + ath_txq_schedule(sc, txq); + } + + ath_txq_unlock_complete(sc, txq); +} + +/********************/ +/* Queue Management */ +/********************/ + +struct ath_txq *ath_txq_setup(struct ath_softc *sc, int qtype, int subtype) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath9k_tx_queue_info qi; + static const int subtype_txq_to_hwq[] = { + [IEEE80211_AC_BE] = ATH_TXQ_AC_BE, + [IEEE80211_AC_BK] = ATH_TXQ_AC_BK, + [IEEE80211_AC_VI] = ATH_TXQ_AC_VI, + [IEEE80211_AC_VO] = ATH_TXQ_AC_VO, + }; + int axq_qnum, i; + + memset(&qi, 0, sizeof(qi)); + qi.tqi_subtype = subtype_txq_to_hwq[subtype]; + qi.tqi_aifs = ATH9K_TXQ_USEDEFAULT; + qi.tqi_cwmin = ATH9K_TXQ_USEDEFAULT; + qi.tqi_cwmax = ATH9K_TXQ_USEDEFAULT; + qi.tqi_physCompBuf = 0; + + /* + * Enable interrupts only for EOL and DESC conditions. + * We mark tx descriptors to receive a DESC interrupt + * when a tx queue gets deep; otherwise waiting for the + * EOL to reap descriptors. Note that this is done to + * reduce interrupt load and this only defers reaping + * descriptors, never transmitting frames. Aside from + * reducing interrupts this also permits more concurrency. + * The only potential downside is if the tx queue backs + * up in which case the top half of the kernel may backup + * due to a lack of tx descriptors. + * + * The UAPSD queue is an exception, since we take a desc- + * based intr on the EOSP frames. + */ + if (ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) { + qi.tqi_qflags = TXQ_FLAG_TXINT_ENABLE; + } else { + if (qtype == ATH9K_TX_QUEUE_UAPSD) + qi.tqi_qflags = TXQ_FLAG_TXDESCINT_ENABLE; + else + qi.tqi_qflags = TXQ_FLAG_TXEOLINT_ENABLE | + TXQ_FLAG_TXDESCINT_ENABLE; + } + axq_qnum = ath9k_hw_setuptxqueue(ah, qtype, &qi); + if (axq_qnum == -1) { + /* + * NB: don't print a message, this happens + * normally on parts with too few tx queues + */ + return NULL; + } + if (!ATH_TXQ_SETUP(sc, axq_qnum)) { + struct ath_txq *txq = &sc->tx.txq[axq_qnum]; + + txq->axq_qnum = axq_qnum; + txq->mac80211_qnum = -1; + txq->axq_link = NULL; + __skb_queue_head_init(&txq->complete_q); + INIT_LIST_HEAD(&txq->axq_q); + INIT_LIST_HEAD(&txq->axq_acq); + spin_lock_init(&txq->axq_lock); + txq->axq_depth = 0; + txq->axq_ampdu_depth = 0; + txq->axq_tx_inprogress = false; + sc->tx.txqsetup |= 1<txq_headidx = txq->txq_tailidx = 0; + for (i = 0; i < ATH_TXFIFO_DEPTH; i++) + INIT_LIST_HEAD(&txq->txq_fifo[i]); + } + return &sc->tx.txq[axq_qnum]; +} + +int ath_txq_update(struct ath_softc *sc, int qnum, + struct ath9k_tx_queue_info *qinfo) +{ + struct ath_hw *ah = sc->sc_ah; + int error = 0; + struct ath9k_tx_queue_info qi; + + BUG_ON(sc->tx.txq[qnum].axq_qnum != qnum); + + ath9k_hw_get_txq_props(ah, qnum, &qi); + qi.tqi_aifs = qinfo->tqi_aifs; + qi.tqi_cwmin = qinfo->tqi_cwmin; + qi.tqi_cwmax = qinfo->tqi_cwmax; + qi.tqi_burstTime = qinfo->tqi_burstTime; + qi.tqi_readyTime = qinfo->tqi_readyTime; + + if (!ath9k_hw_set_txq_props(ah, qnum, &qi)) { + ath_err(ath9k_hw_common(sc->sc_ah), + "Unable to update hardware queue %u!\n", qnum); + error = -EIO; + } else { + ath9k_hw_resettxqueue(ah, qnum); + } + + return error; +} + +int ath_cabq_update(struct ath_softc *sc) +{ + struct ath9k_tx_queue_info qi; + struct ath_beacon_config *cur_conf = &sc->cur_beacon_conf; + int qnum = sc->beacon.cabq->axq_qnum; + + ath9k_hw_get_txq_props(sc->sc_ah, qnum, &qi); + /* + * Ensure the readytime % is within the bounds. + */ + if (sc->config.cabqReadytime < ATH9K_READY_TIME_LO_BOUND) + sc->config.cabqReadytime = ATH9K_READY_TIME_LO_BOUND; + else if (sc->config.cabqReadytime > ATH9K_READY_TIME_HI_BOUND) + sc->config.cabqReadytime = ATH9K_READY_TIME_HI_BOUND; + + qi.tqi_readyTime = (cur_conf->beacon_interval * + sc->config.cabqReadytime) / 100; + ath_txq_update(sc, qnum, &qi); + + return 0; +} + +static void ath_drain_txq_list(struct ath_softc *sc, struct ath_txq *txq, + struct list_head *list) +{ + struct ath_buf *bf, *lastbf; + struct list_head bf_head; + struct ath_tx_status ts; + + memset(&ts, 0, sizeof(ts)); + ts.ts_status = ATH9K_TX_FLUSH; + INIT_LIST_HEAD(&bf_head); + + while (!list_empty(list)) { + bf = list_first_entry(list, struct ath_buf, list); + + if (bf->bf_stale) { + list_del(&bf->list); + + ath_tx_return_buffer(sc, bf); + continue; + } + + lastbf = bf->bf_lastbf; + list_cut_position(&bf_head, list, &lastbf->list); + ath_tx_process_buffer(sc, txq, &ts, bf, &bf_head); + } +} + +/* + * Drain a given TX queue (could be Beacon or Data) + * + * This assumes output has been stopped and + * we do not need to block ath_tx_tasklet. + */ +void ath_draintxq(struct ath_softc *sc, struct ath_txq *txq) +{ + ath_txq_lock(sc, txq); + + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) { + int idx = txq->txq_tailidx; + + while (!list_empty(&txq->txq_fifo[idx])) { + ath_drain_txq_list(sc, txq, &txq->txq_fifo[idx]); + + INCR(idx, ATH_TXFIFO_DEPTH); + } + txq->txq_tailidx = idx; + } + + txq->axq_link = NULL; + txq->axq_tx_inprogress = false; + ath_drain_txq_list(sc, txq, &txq->axq_q); + + ath_txq_unlock_complete(sc, txq); +} + +bool ath_drain_all_txq(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_txq *txq; + int i; + u32 npend = 0; + + if (test_bit(SC_OP_INVALID, &sc->sc_flags)) + return true; + + ath9k_hw_abort_tx_dma(ah); + + /* Check if any queue remains active */ + for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) { + if (!ATH_TXQ_SETUP(sc, i)) + continue; + + if (ath9k_hw_numtxpending(ah, sc->tx.txq[i].axq_qnum)) + npend |= BIT(i); + } + + if (npend) + ath_err(common, "Failed to stop TX DMA, queues=0x%03x!\n", npend); + + for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) { + if (!ATH_TXQ_SETUP(sc, i)) + continue; + + /* + * The caller will resume queues with ieee80211_wake_queues. + * Mark the queue as not stopped to prevent ath_tx_complete + * from waking the queue too early. + */ + txq = &sc->tx.txq[i]; + txq->stopped = false; + ath_draintxq(sc, txq); + } + + return !npend; +} + +void ath_tx_cleanupq(struct ath_softc *sc, struct ath_txq *txq) +{ + ath9k_hw_releasetxqueue(sc->sc_ah, txq->axq_qnum); + sc->tx.txqsetup &= ~(1<axq_qnum); +} + +/* For each axq_acq entry, for each tid, try to schedule packets + * for transmit until ampdu_depth has reached min Q depth. + */ +void ath_txq_schedule(struct ath_softc *sc, struct ath_txq *txq) +{ + struct ath_atx_ac *ac, *ac_tmp, *last_ac; + struct ath_atx_tid *tid, *last_tid; + + if (test_bit(SC_OP_HW_RESET, &sc->sc_flags) || + list_empty(&txq->axq_acq) || + txq->axq_ampdu_depth >= ATH_AGGR_MIN_QDEPTH) + return; + + rcu_read_lock(); + + ac = list_first_entry(&txq->axq_acq, struct ath_atx_ac, list); + last_ac = list_entry(txq->axq_acq.prev, struct ath_atx_ac, list); + + list_for_each_entry_safe(ac, ac_tmp, &txq->axq_acq, list) { + last_tid = list_entry(ac->tid_q.prev, struct ath_atx_tid, list); + list_del(&ac->list); + ac->sched = false; + + while (!list_empty(&ac->tid_q)) { + tid = list_first_entry(&ac->tid_q, struct ath_atx_tid, + list); + list_del(&tid->list); + tid->sched = false; + + if (tid->paused) + continue; + + ath_tx_sched_aggr(sc, txq, tid); + + /* + * add tid to round-robin queue if more frames + * are pending for the tid + */ + if (!skb_queue_empty(&tid->buf_q)) + ath_tx_queue_tid(txq, tid); + + if (tid == last_tid || + txq->axq_ampdu_depth >= ATH_AGGR_MIN_QDEPTH) + break; + } + + if (!list_empty(&ac->tid_q) && !ac->sched) { + ac->sched = true; + list_add_tail(&ac->list, &txq->axq_acq); + } + + if (ac == last_ac || + txq->axq_ampdu_depth >= ATH_AGGR_MIN_QDEPTH) + break; + } + + rcu_read_unlock(); +} + +/***********/ +/* TX, DMA */ +/***********/ + +/* + * Insert a chain of ath_buf (descriptors) on a txq and + * assume the descriptors are already chained together by caller. + */ +static void ath_tx_txqaddbuf(struct ath_softc *sc, struct ath_txq *txq, + struct list_head *head, bool internal) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath_buf *bf, *bf_last; + bool puttxbuf = false; + bool edma; + + /* + * Insert the frame on the outbound list and + * pass it on to the hardware. + */ + + if (list_empty(head)) + return; + + edma = !!(ah->caps.hw_caps & ATH9K_HW_CAP_EDMA); + bf = list_first_entry(head, struct ath_buf, list); + bf_last = list_entry(head->prev, struct ath_buf, list); + + ath_dbg(common, QUEUE, "qnum: %d, txq depth: %d\n", + txq->axq_qnum, txq->axq_depth); + + if (edma && list_empty(&txq->txq_fifo[txq->txq_headidx])) { + list_splice_tail_init(head, &txq->txq_fifo[txq->txq_headidx]); + INCR(txq->txq_headidx, ATH_TXFIFO_DEPTH); + puttxbuf = true; + } else { + list_splice_tail_init(head, &txq->axq_q); + + if (txq->axq_link) { + ath9k_hw_set_desc_link(ah, txq->axq_link, bf->bf_daddr); + ath_dbg(common, XMIT, "link[%u] (%p)=%llx (%p)\n", + txq->axq_qnum, txq->axq_link, + ito64(bf->bf_daddr), bf->bf_desc); + } else if (!edma) + puttxbuf = true; + + txq->axq_link = bf_last->bf_desc; + } + + if (puttxbuf) { + TX_STAT_INC(txq->axq_qnum, puttxbuf); + ath9k_hw_puttxbuf(ah, txq->axq_qnum, bf->bf_daddr); + ath_dbg(common, XMIT, "TXDP[%u] = %llx (%p)\n", + txq->axq_qnum, ito64(bf->bf_daddr), bf->bf_desc); + } + + if (!edma) { + TX_STAT_INC(txq->axq_qnum, txstart); + ath9k_hw_txstart(ah, txq->axq_qnum); + } + + if (!internal) { + txq->axq_depth++; + if (bf_is_ampdu_not_probing(bf)) + txq->axq_ampdu_depth++; + } +} + +static void ath_tx_send_ampdu(struct ath_softc *sc, struct ath_atx_tid *tid, + struct sk_buff *skb, struct ath_tx_control *txctl) +{ + struct ath_frame_info *fi = get_frame_info(skb); + struct list_head bf_head; + struct ath_buf *bf; + + /* + * Do not queue to h/w when any of the following conditions is true: + * - there are pending frames in software queue + * - the TID is currently paused for ADDBA/BAR request + * - seqno is not within block-ack window + * - h/w queue depth exceeds low water mark + */ + if (!skb_queue_empty(&tid->buf_q) || tid->paused || + !BAW_WITHIN(tid->seq_start, tid->baw_size, tid->seq_next) || + txctl->txq->axq_ampdu_depth >= ATH_AGGR_MIN_QDEPTH) { + /* + * Add this frame to software queue for scheduling later + * for aggregation. + */ + TX_STAT_INC(txctl->txq->axq_qnum, a_queued_sw); + __skb_queue_tail(&tid->buf_q, skb); + if (!txctl->an || !txctl->an->sleeping) + ath_tx_queue_tid(txctl->txq, tid); + return; + } + + bf = ath_tx_setup_buffer(sc, txctl->txq, tid, skb); + if (!bf) { + ieee80211_free_txskb(sc->hw, skb); + return; + } + + ath_set_rates(tid->an->vif, tid->an->sta, bf); + bf->bf_state.bf_type = BUF_AMPDU; + INIT_LIST_HEAD(&bf_head); + list_add(&bf->list, &bf_head); + + /* Add sub-frame to BAW */ + ath_tx_addto_baw(sc, tid, bf->bf_state.seqno); + + /* Queue to h/w without aggregation */ + TX_STAT_INC(txctl->txq->axq_qnum, a_queued_hw); + bf->bf_lastbf = bf; + ath_tx_fill_desc(sc, bf, txctl->txq, fi->framelen); + ath_tx_txqaddbuf(sc, txctl->txq, &bf_head, false); +} + +static void ath_tx_send_normal(struct ath_softc *sc, struct ath_txq *txq, + struct ath_atx_tid *tid, struct sk_buff *skb) +{ + struct ath_frame_info *fi = get_frame_info(skb); + struct list_head bf_head; + struct ath_buf *bf; + + bf = fi->bf; + + INIT_LIST_HEAD(&bf_head); + list_add_tail(&bf->list, &bf_head); + bf->bf_state.bf_type = 0; + + bf->bf_next = NULL; + bf->bf_lastbf = bf; + ath_tx_fill_desc(sc, bf, txq, fi->framelen); + ath_tx_txqaddbuf(sc, txq, &bf_head, false); + TX_STAT_INC(txq->axq_qnum, queued); +} + +static void setup_frame_info(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + struct sk_buff *skb, + int framelen) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_key_conf *hw_key = tx_info->control.hw_key; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + const struct ieee80211_rate *rate; + struct ath_frame_info *fi = get_frame_info(skb); + struct ath_node *an = NULL; + enum ath9k_key_type keytype; + bool short_preamble = false; + + /* + * We check if Short Preamble is needed for the CTS rate by + * checking the BSS's global flag. + * But for the rate series, IEEE80211_TX_RC_USE_SHORT_PREAMBLE is used. + */ + if (tx_info->control.vif && + tx_info->control.vif->bss_conf.use_short_preamble) + short_preamble = true; + + rate = ieee80211_get_rts_cts_rate(hw, tx_info); + keytype = ath9k_cmn_get_hw_crypto_keytype(skb); + + if (sta) + an = (struct ath_node *) sta->drv_priv; + + memset(fi, 0, sizeof(*fi)); + if (hw_key) + fi->keyix = hw_key->hw_key_idx; + else if (an && ieee80211_is_data(hdr->frame_control) && an->ps_key > 0) + fi->keyix = an->ps_key; + else + fi->keyix = ATH9K_TXKEYIX_INVALID; + fi->keytype = keytype; + fi->framelen = framelen; + fi->rtscts_rate = rate->hw_value; + if (short_preamble) + fi->rtscts_rate |= rate->hw_value_short; +} + +u8 ath_txchainmask_reduction(struct ath_softc *sc, u8 chainmask, u32 rate) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath9k_channel *curchan = ah->curchan; + + if ((ah->caps.hw_caps & ATH9K_HW_CAP_APM) && + (curchan->channelFlags & CHANNEL_5GHZ) && + (chainmask == 0x7) && (rate < 0x90)) + return 0x3; + else if (AR_SREV_9462(ah) && ath9k_hw_btcoex_is_enabled(ah) && + IS_CCK_RATE(rate)) + return 0x2; + else + return chainmask; +} + +/* + * Assign a descriptor (and sequence number if necessary, + * and map buffer for DMA. Frees skb on error + */ +static struct ath_buf *ath_tx_setup_buffer(struct ath_softc *sc, + struct ath_txq *txq, + struct ath_atx_tid *tid, + struct sk_buff *skb) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_frame_info *fi = get_frame_info(skb); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ath_buf *bf; + int fragno; + u16 seqno; + + bf = ath_tx_get_buffer(sc); + if (!bf) { + ath_dbg(common, XMIT, "TX buffers are full\n"); + return NULL; + } + + ATH_TXBUF_RESET(bf); + + if (tid) { + fragno = le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_FRAG; + seqno = tid->seq_next; + hdr->seq_ctrl = cpu_to_le16(tid->seq_next << IEEE80211_SEQ_SEQ_SHIFT); + + if (fragno) + hdr->seq_ctrl |= cpu_to_le16(fragno); + + if (!ieee80211_has_morefrags(hdr->frame_control)) + INCR(tid->seq_next, IEEE80211_SEQ_MAX); + + bf->bf_state.seqno = seqno; + } + + bf->bf_mpdu = skb; + + bf->bf_buf_addr = dma_map_single(sc->dev, skb->data, + skb->len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(sc->dev, bf->bf_buf_addr))) { + bf->bf_mpdu = NULL; + bf->bf_buf_addr = 0; + ath_err(ath9k_hw_common(sc->sc_ah), + "dma_mapping_error() on TX\n"); + ath_tx_return_buffer(sc, bf); + return NULL; + } + + fi->bf = bf; + + return bf; +} + +/* Upon failure caller should free skb */ +int ath_tx_start(struct ieee80211_hw *hw, struct sk_buff *skb, + struct ath_tx_control *txctl) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_sta *sta = txctl->sta; + struct ieee80211_vif *vif = info->control.vif; + struct ath_softc *sc = hw->priv; + struct ath_txq *txq = txctl->txq; + struct ath_atx_tid *tid = NULL; + struct ath_buf *bf; + int padpos, padsize; + int frmlen = skb->len + FCS_LEN; + u8 tidno; + int q; + + /* NOTE: sta can be NULL according to net/mac80211.h */ + if (sta) + txctl->an = (struct ath_node *)sta->drv_priv; + + if (info->control.hw_key) + frmlen += info->control.hw_key->icv_len; + + /* + * As a temporary workaround, assign seq# here; this will likely need + * to be cleaned up to work better with Beacon transmission and virtual + * BSSes. + */ + if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) { + if (info->flags & IEEE80211_TX_CTL_FIRST_FRAGMENT) + sc->tx.seq_no += 0x10; + hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); + hdr->seq_ctrl |= cpu_to_le16(sc->tx.seq_no); + } + + /* Add the padding after the header if this is not already done */ + padpos = ieee80211_hdrlen(hdr->frame_control); + padsize = padpos & 3; + if (padsize && skb->len > padpos) { + if (skb_headroom(skb) < padsize) + return -ENOMEM; + + skb_push(skb, padsize); + memmove(skb->data, skb->data + padsize, padpos); + hdr = (struct ieee80211_hdr *) skb->data; + } + + if ((vif && vif->type != NL80211_IFTYPE_AP && + vif->type != NL80211_IFTYPE_AP_VLAN) || + !ieee80211_is_data(hdr->frame_control)) + info->flags |= IEEE80211_TX_CTL_CLEAR_PS_FILT; + + setup_frame_info(hw, sta, skb, frmlen); + + /* + * At this point, the vif, hw_key and sta pointers in the tx control + * info are no longer valid (overwritten by the ath_frame_info data. + */ + + q = skb_get_queue_mapping(skb); + + ath_txq_lock(sc, txq); + if (txq == sc->tx.txq_map[q] && + ++txq->pending_frames > sc->tx.txq_max_pending[q] && + !txq->stopped) { + ieee80211_stop_queue(sc->hw, q); + txq->stopped = true; + } + + if (txctl->an && ieee80211_is_data_qos(hdr->frame_control)) { + tidno = ieee80211_get_qos_ctl(hdr)[0] & + IEEE80211_QOS_CTL_TID_MASK; + tid = ATH_AN_2_TID(txctl->an, tidno); + + WARN_ON(tid->ac->txq != txctl->txq); + } + + if ((info->flags & IEEE80211_TX_CTL_AMPDU) && tid) { + /* + * Try aggregation if it's a unicast data frame + * and the destination is HT capable. + */ + ath_tx_send_ampdu(sc, tid, skb, txctl); + goto out; + } + + bf = ath_tx_setup_buffer(sc, txctl->txq, tid, skb); + if (!bf) { + if (txctl->paprd) + dev_kfree_skb_any(skb); + else + ieee80211_free_txskb(sc->hw, skb); + goto out; + } + + bf->bf_state.bfs_paprd = txctl->paprd; + + if (txctl->paprd) + bf->bf_state.bfs_paprd_timestamp = jiffies; + + ath_set_rates(vif, sta, bf); + ath_tx_send_normal(sc, txctl->txq, tid, skb); + +out: + ath_txq_unlock(sc, txq); + + return 0; +} + +/*****************/ +/* TX Completion */ +/*****************/ + +static void ath_tx_complete(struct ath_softc *sc, struct sk_buff *skb, + int tx_flags, struct ath_txq *txq) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ieee80211_hdr * hdr = (struct ieee80211_hdr *)skb->data; + int q, padpos, padsize; + unsigned long flags; + + ath_dbg(common, XMIT, "TX complete: skb: %p\n", skb); + + if (sc->sc_ah->caldata) + sc->sc_ah->caldata->paprd_packet_sent = true; + + if (!(tx_flags & ATH_TX_ERROR)) + /* Frame was ACKed */ + tx_info->flags |= IEEE80211_TX_STAT_ACK; + + padpos = ieee80211_hdrlen(hdr->frame_control); + padsize = padpos & 3; + if (padsize && skb->len>padpos+padsize) { + /* + * Remove MAC header padding before giving the frame back to + * mac80211. + */ + memmove(skb->data + padsize, skb->data, padpos); + skb_pull(skb, padsize); + } + + spin_lock_irqsave(&sc->sc_pm_lock, flags); + if ((sc->ps_flags & PS_WAIT_FOR_TX_ACK) && !txq->axq_depth) { + sc->ps_flags &= ~PS_WAIT_FOR_TX_ACK; + ath_dbg(common, PS, + "Going back to sleep after having received TX status (0x%lx)\n", + sc->ps_flags & (PS_WAIT_FOR_BEACON | + PS_WAIT_FOR_CAB | + PS_WAIT_FOR_PSPOLL_DATA | + PS_WAIT_FOR_TX_ACK)); + } + spin_unlock_irqrestore(&sc->sc_pm_lock, flags); + + q = skb_get_queue_mapping(skb); + if (txq == sc->tx.txq_map[q]) { + if (WARN_ON(--txq->pending_frames < 0)) + txq->pending_frames = 0; + + if (txq->stopped && + txq->pending_frames < sc->tx.txq_max_pending[q]) { + ieee80211_wake_queue(sc->hw, q); + txq->stopped = false; + } + } + + __skb_queue_tail(&txq->complete_q, skb); +} + +static void ath_tx_complete_buf(struct ath_softc *sc, struct ath_buf *bf, + struct ath_txq *txq, struct list_head *bf_q, + struct ath_tx_status *ts, int txok) +{ + struct sk_buff *skb = bf->bf_mpdu; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + unsigned long flags; + int tx_flags = 0; + + if (!txok) + tx_flags |= ATH_TX_ERROR; + + if (ts->ts_status & ATH9K_TXERR_FILT) + tx_info->flags |= IEEE80211_TX_STAT_TX_FILTERED; + + dma_unmap_single(sc->dev, bf->bf_buf_addr, skb->len, DMA_TO_DEVICE); + bf->bf_buf_addr = 0; + + if (bf->bf_state.bfs_paprd) { + if (time_after(jiffies, + bf->bf_state.bfs_paprd_timestamp + + msecs_to_jiffies(ATH_PAPRD_TIMEOUT))) + dev_kfree_skb_any(skb); + else + complete(&sc->paprd_complete); + } else { + ath_debug_stat_tx(sc, bf, ts, txq, tx_flags); + ath_tx_complete(sc, skb, tx_flags, txq); + } + /* At this point, skb (bf->bf_mpdu) is consumed...make sure we don't + * accidentally reference it later. + */ + bf->bf_mpdu = NULL; + + /* + * Return the list of ath_buf of this mpdu to free queue + */ + spin_lock_irqsave(&sc->tx.txbuflock, flags); + list_splice_tail_init(bf_q, &sc->tx.txbuf); + spin_unlock_irqrestore(&sc->tx.txbuflock, flags); +} + +static void ath_tx_rc_status(struct ath_softc *sc, struct ath_buf *bf, + struct ath_tx_status *ts, int nframes, int nbad, + int txok) +{ + struct sk_buff *skb = bf->bf_mpdu; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_hw *hw = sc->hw; + struct ath_hw *ah = sc->sc_ah; + u8 i, tx_rateindex; + + if (txok) + tx_info->status.ack_signal = ts->ts_rssi; + + tx_rateindex = ts->ts_rateindex; + WARN_ON(tx_rateindex >= hw->max_rates); + + if (tx_info->flags & IEEE80211_TX_CTL_AMPDU) { + tx_info->flags |= IEEE80211_TX_STAT_AMPDU; + + BUG_ON(nbad > nframes); + } + tx_info->status.ampdu_len = nframes; + tx_info->status.ampdu_ack_len = nframes - nbad; + + if ((ts->ts_status & ATH9K_TXERR_FILT) == 0 && + (tx_info->flags & IEEE80211_TX_CTL_NO_ACK) == 0) { + /* + * If an underrun error is seen assume it as an excessive + * retry only if max frame trigger level has been reached + * (2 KB for single stream, and 4 KB for dual stream). + * Adjust the long retry as if the frame was tried + * hw->max_rate_tries times to affect how rate control updates + * PER for the failed rate. + * In case of congestion on the bus penalizing this type of + * underruns should help hardware actually transmit new frames + * successfully by eventually preferring slower rates. + * This itself should also alleviate congestion on the bus. + */ + if (unlikely(ts->ts_flags & (ATH9K_TX_DATA_UNDERRUN | + ATH9K_TX_DELIM_UNDERRUN)) && + ieee80211_is_data(hdr->frame_control) && + ah->tx_trig_level >= sc->sc_ah->config.max_txtrig_level) + tx_info->status.rates[tx_rateindex].count = + hw->max_rate_tries; + } + + for (i = tx_rateindex + 1; i < hw->max_rates; i++) { + tx_info->status.rates[i].count = 0; + tx_info->status.rates[i].idx = -1; + } + + tx_info->status.rates[tx_rateindex].count = ts->ts_longretry + 1; +} + +static void ath_tx_processq(struct ath_softc *sc, struct ath_txq *txq) +{ + struct ath_hw *ah = sc->sc_ah; + struct ath_common *common = ath9k_hw_common(ah); + struct ath_buf *bf, *lastbf, *bf_held = NULL; + struct list_head bf_head; + struct ath_desc *ds; + struct ath_tx_status ts; + int status; + + ath_dbg(common, QUEUE, "tx queue %d (%x), link %p\n", + txq->axq_qnum, ath9k_hw_gettxbuf(sc->sc_ah, txq->axq_qnum), + txq->axq_link); + + ath_txq_lock(sc, txq); + for (;;) { + if (test_bit(SC_OP_HW_RESET, &sc->sc_flags)) + break; + + if (list_empty(&txq->axq_q)) { + txq->axq_link = NULL; + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_HT) + ath_txq_schedule(sc, txq); + break; + } + bf = list_first_entry(&txq->axq_q, struct ath_buf, list); + + /* + * There is a race condition that a BH gets scheduled + * after sw writes TxE and before hw re-load the last + * descriptor to get the newly chained one. + * Software must keep the last DONE descriptor as a + * holding descriptor - software does so by marking + * it with the STALE flag. + */ + bf_held = NULL; + if (bf->bf_stale) { + bf_held = bf; + if (list_is_last(&bf_held->list, &txq->axq_q)) + break; + + bf = list_entry(bf_held->list.next, struct ath_buf, + list); + } + + lastbf = bf->bf_lastbf; + ds = lastbf->bf_desc; + + memset(&ts, 0, sizeof(ts)); + status = ath9k_hw_txprocdesc(ah, ds, &ts); + if (status == -EINPROGRESS) + break; + + TX_STAT_INC(txq->axq_qnum, txprocdesc); + + /* + * Remove ath_buf's of the same transmit unit from txq, + * however leave the last descriptor back as the holding + * descriptor for hw. + */ + lastbf->bf_stale = true; + INIT_LIST_HEAD(&bf_head); + if (!list_is_singular(&lastbf->list)) + list_cut_position(&bf_head, + &txq->axq_q, lastbf->list.prev); + + if (bf_held) { + list_del(&bf_held->list); + ath_tx_return_buffer(sc, bf_held); + } + + ath_tx_process_buffer(sc, txq, &ts, bf, &bf_head); + } + ath_txq_unlock_complete(sc, txq); +} + +void ath_tx_tasklet(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + u32 qcumask = ((1 << ATH9K_NUM_TX_QUEUES) - 1) & ah->intr_txqs; + int i; + + for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) { + if (ATH_TXQ_SETUP(sc, i) && (qcumask & (1 << i))) + ath_tx_processq(sc, &sc->tx.txq[i]); + } +} + +void ath_tx_edma_tasklet(struct ath_softc *sc) +{ + struct ath_tx_status ts; + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + struct ath_hw *ah = sc->sc_ah; + struct ath_txq *txq; + struct ath_buf *bf, *lastbf; + struct list_head bf_head; + struct list_head *fifo_list; + int status; + + for (;;) { + if (test_bit(SC_OP_HW_RESET, &sc->sc_flags)) + break; + + status = ath9k_hw_txprocdesc(ah, NULL, (void *)&ts); + if (status == -EINPROGRESS) + break; + if (status == -EIO) { + ath_dbg(common, XMIT, "Error processing tx status\n"); + break; + } + + /* Process beacon completions separately */ + if (ts.qid == sc->beacon.beaconq) { + sc->beacon.tx_processed = true; + sc->beacon.tx_last = !(ts.ts_status & ATH9K_TXERR_MASK); + continue; + } + + txq = &sc->tx.txq[ts.qid]; + + ath_txq_lock(sc, txq); + + TX_STAT_INC(txq->axq_qnum, txprocdesc); + + fifo_list = &txq->txq_fifo[txq->txq_tailidx]; + if (list_empty(fifo_list)) { + ath_txq_unlock(sc, txq); + return; + } + + bf = list_first_entry(fifo_list, struct ath_buf, list); + if (bf->bf_stale) { + list_del(&bf->list); + ath_tx_return_buffer(sc, bf); + bf = list_first_entry(fifo_list, struct ath_buf, list); + } + + lastbf = bf->bf_lastbf; + + INIT_LIST_HEAD(&bf_head); + if (list_is_last(&lastbf->list, fifo_list)) { + list_splice_tail_init(fifo_list, &bf_head); + INCR(txq->txq_tailidx, ATH_TXFIFO_DEPTH); + + if (!list_empty(&txq->axq_q)) { + struct list_head bf_q; + + INIT_LIST_HEAD(&bf_q); + txq->axq_link = NULL; + list_splice_tail_init(&txq->axq_q, &bf_q); + ath_tx_txqaddbuf(sc, txq, &bf_q, true); + } + } else { + lastbf->bf_stale = true; + if (bf != lastbf) + list_cut_position(&bf_head, fifo_list, + lastbf->list.prev); + } + + ath_tx_process_buffer(sc, txq, &ts, bf, &bf_head); + ath_txq_unlock_complete(sc, txq); + } +} + +/*****************/ +/* Init, Cleanup */ +/*****************/ + +static int ath_txstatus_setup(struct ath_softc *sc, int size) +{ + struct ath_descdma *dd = &sc->txsdma; + u8 txs_len = sc->sc_ah->caps.txs_len; + + dd->dd_desc_len = size * txs_len; + dd->dd_desc = dmam_alloc_coherent(sc->dev, dd->dd_desc_len, + &dd->dd_desc_paddr, GFP_KERNEL); + if (!dd->dd_desc) + return -ENOMEM; + + return 0; +} + +static int ath_tx_edma_init(struct ath_softc *sc) +{ + int err; + + err = ath_txstatus_setup(sc, ATH_TXSTATUS_RING_SIZE); + if (!err) + ath9k_hw_setup_statusring(sc->sc_ah, sc->txsdma.dd_desc, + sc->txsdma.dd_desc_paddr, + ATH_TXSTATUS_RING_SIZE); + + return err; +} + +int ath_tx_init(struct ath_softc *sc, int nbufs) +{ + struct ath_common *common = ath9k_hw_common(sc->sc_ah); + int error = 0; + + spin_lock_init(&sc->tx.txbuflock); + + error = ath_descdma_setup(sc, &sc->tx.txdma, &sc->tx.txbuf, + "tx", nbufs, 1, 1); + if (error != 0) { + ath_err(common, + "Failed to allocate tx descriptors: %d\n", error); + return error; + } + + error = ath_descdma_setup(sc, &sc->beacon.bdma, &sc->beacon.bbuf, + "beacon", ATH_BCBUF, 1, 1); + if (error != 0) { + ath_err(common, + "Failed to allocate beacon descriptors: %d\n", error); + return error; + } + + INIT_DELAYED_WORK(&sc->tx_complete_work, ath_tx_complete_poll_work); + + if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) + error = ath_tx_edma_init(sc); + + return error; +} + +void ath_tx_node_init(struct ath_softc *sc, struct ath_node *an) +{ + struct ath_atx_tid *tid; + struct ath_atx_ac *ac; + int tidno, acno; + + for (tidno = 0, tid = &an->tid[tidno]; + tidno < IEEE80211_NUM_TIDS; + tidno++, tid++) { + tid->an = an; + tid->tidno = tidno; + tid->seq_start = tid->seq_next = 0; + tid->baw_size = WME_MAX_BA; + tid->baw_head = tid->baw_tail = 0; + tid->sched = false; + tid->paused = false; + tid->active = false; + __skb_queue_head_init(&tid->buf_q); + acno = TID_TO_WME_AC(tidno); + tid->ac = &an->ac[acno]; + } + + for (acno = 0, ac = &an->ac[acno]; + acno < IEEE80211_NUM_ACS; acno++, ac++) { + ac->sched = false; + ac->clear_ps_filter = true; + ac->txq = sc->tx.txq_map[acno]; + INIT_LIST_HEAD(&ac->tid_q); + } +} + +void ath_tx_node_cleanup(struct ath_softc *sc, struct ath_node *an) +{ + struct ath_atx_ac *ac; + struct ath_atx_tid *tid; + struct ath_txq *txq; + int tidno; + + for (tidno = 0, tid = &an->tid[tidno]; + tidno < IEEE80211_NUM_TIDS; tidno++, tid++) { + + ac = tid->ac; + txq = ac->txq; + + ath_txq_lock(sc, txq); + + if (tid->sched) { + list_del(&tid->list); + tid->sched = false; + } + + if (ac->sched) { + list_del(&ac->list); + tid->ac->sched = false; + } + + ath_tid_drain(sc, txq, tid); + tid->active = false; + + ath_txq_unlock(sc, txq); + } +} diff --git a/drivers/net/wireless/ath/carl9170/Kconfig b/drivers/net/wireless/ath/carl9170/Kconfig new file mode 100644 index 0000000..d1e68dd --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/Kconfig @@ -0,0 +1,58 @@ +config CARL9170 + depends on !BACKPORT_KERNEL_2_6_29 + tristate "Linux Community AR9170 802.11n USB support" + depends on m + depends on USB && MAC80211 + select ATH_COMMON + select BACKPORT_FW_LOADER + depends on CRC32 + help + This is another driver for the Atheros "otus" 802.11n USB devices. + + This driver provides more features than the original, + but it needs a special firmware (carl9170-1.fw) to do that. + + The firmware can be downloaded from our wiki here: + + + If you choose to build a module, it'll be called carl9170. + +config CARL9170_LEDS + bool "SoftLED Support" + depends on CARL9170 + select MAC80211_LEDS + select BACKPORT_LEDS_CLASS + select BACKPORT_NEW_LEDS + default y + help + This option is necessary, if you want your device' LEDs to blink + + Say Y, unless you need the LEDs for firmware debugging. + +config CARL9170_DEBUGFS + bool "DebugFS Support" + depends on CARL9170 && DEBUG_FS && MAC80211_DEBUGFS + default n + help + Export several driver and device internals to user space. + + Say N. + +config CARL9170_WPC + bool + depends on CARL9170 && (INPUT = y || INPUT = CARL9170) + default y + +config CARL9170_HWRNG + bool "Random number generator" + depends on CARL9170 && (HW_RANDOM = y || HW_RANDOM = CARL9170) + default n + help + Provides a hardware random number generator to the kernel. + + SECURITY WARNING: It's relatively easy to eavesdrop all + generated random numbers from the transport stream with + usbmon [software] or special usb sniffer hardware. + + Say N, unless your setup[i.e.: embedded system] has no + other rng source and you can afford to take the risk. diff --git a/drivers/net/wireless/ath/carl9170/Makefile b/drivers/net/wireless/ath/carl9170/Makefile new file mode 100644 index 0000000..0481aaf --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/Makefile @@ -0,0 +1,4 @@ +carl9170-objs := main.o usb.o cmd.o mac.o phy.o led.o fw.o tx.o rx.o +carl9170-$(CPTCFG_CARL9170_DEBUGFS) += debug.o + +obj-$(CPTCFG_CARL9170) += carl9170.o diff --git a/drivers/net/wireless/ath/carl9170/carl9170.h b/drivers/net/wireless/ath/carl9170/carl9170.h new file mode 100644 index 0000000..4173063 --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/carl9170.h @@ -0,0 +1,666 @@ +/* + * Atheros CARL9170 driver + * + * Driver specific definitions + * + * Copyright 2008, Johannes Berg + * Copyright 2009, 2010, Christian Lamparter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * Copyright (c) 2007-2008 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef __CARL9170_H +#define __CARL9170_H + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CPTCFG_CARL9170_LEDS +#include +#endif /* CPTCFG_CARL9170_LEDS */ +#ifdef CPTCFG_CARL9170_WPC +#include +#endif /* CPTCFG_CARL9170_WPC */ +#include "eeprom.h" +#include "wlan.h" +#include "hw.h" +#include "fwdesc.h" +#include "fwcmd.h" +#include "../regd.h" + +#ifdef CPTCFG_CARL9170_DEBUGFS +#include "debug.h" +#endif /* CPTCFG_CARL9170_DEBUGFS */ + +#define CARL9170FW_NAME "carl9170-1.fw" + +#define PAYLOAD_MAX (CARL9170_MAX_CMD_LEN / 4 - 1) + +static const u8 ar9170_qmap[__AR9170_NUM_TXQ] = { 3, 2, 1, 0 }; + +#define CARL9170_MAX_RX_BUFFER_SIZE 8192 + +enum carl9170_device_state { + CARL9170_UNKNOWN_STATE, + CARL9170_STOPPED, + CARL9170_IDLE, + CARL9170_STARTED, +}; + +#define WME_BA_BMP_SIZE 64 +#define CARL9170_TX_USER_RATE_TRIES 3 + +#define TID_TO_WME_AC(_tid) \ + ((((_tid) == 0) || ((_tid) == 3)) ? IEEE80211_AC_BE : \ + (((_tid) == 1) || ((_tid) == 2)) ? IEEE80211_AC_BK : \ + (((_tid) == 4) || ((_tid) == 5)) ? IEEE80211_AC_VI : \ + IEEE80211_AC_VO) + +#define SEQ_DIFF(_start, _seq) \ + (((_start) - (_seq)) & 0x0fff) +#define SEQ_PREV(_seq) \ + (((_seq) - 1) & 0x0fff) +#define SEQ_NEXT(_seq) \ + (((_seq) + 1) & 0x0fff) +#define BAW_WITHIN(_start, _bawsz, _seqno) \ + ((((_seqno) - (_start)) & 0xfff) < (_bawsz)) + +enum carl9170_tid_state { + CARL9170_TID_STATE_INVALID, + CARL9170_TID_STATE_KILLED, + CARL9170_TID_STATE_SHUTDOWN, + CARL9170_TID_STATE_SUSPEND, + CARL9170_TID_STATE_PROGRESS, + CARL9170_TID_STATE_IDLE, + CARL9170_TID_STATE_XMIT, +}; + +#define CARL9170_BAW_BITS (2 * WME_BA_BMP_SIZE) +#define CARL9170_BAW_SIZE (BITS_TO_LONGS(CARL9170_BAW_BITS)) +#define CARL9170_BAW_LEN (DIV_ROUND_UP(CARL9170_BAW_BITS, BITS_PER_BYTE)) + +struct carl9170_sta_tid { + /* must be the first entry! */ + struct list_head list; + + /* temporary list for RCU unlink procedure */ + struct list_head tmp_list; + + /* lock for the following data structures */ + spinlock_t lock; + + unsigned int counter; + enum carl9170_tid_state state; + u8 tid; /* TID number ( 0 - 15 ) */ + u16 max; /* max. AMPDU size */ + + u16 snx; /* awaiting _next_ frame */ + u16 hsn; /* highest _queued_ sequence */ + u16 bsn; /* base of the tx/agg bitmap */ + unsigned long bitmap[CARL9170_BAW_SIZE]; + + /* Preaggregation reorder queue */ + struct sk_buff_head queue; +}; + +#define CARL9170_QUEUE_TIMEOUT 256 +#define CARL9170_BUMP_QUEUE 1000 +#define CARL9170_TX_TIMEOUT 2500 +#define CARL9170_JANITOR_DELAY 128 +#define CARL9170_QUEUE_STUCK_TIMEOUT 5500 +#define CARL9170_STAT_WORK 30000 + +#define CARL9170_NUM_TX_AGG_MAX 30 + +/* + * Tradeoff between stability/latency and speed. + * + * AR9170_TXQ_DEPTH is devised by dividing the amount of available + * tx buffers with the size of a full ethernet frame + overhead. + * + * Naturally: The higher the limit, the faster the device CAN send. + * However, even a slight over-commitment at the wrong time and the + * hardware is doomed to send all already-queued frames at suboptimal + * rates. This in turn leads to an enormous amount of unsuccessful + * retries => Latency goes up, whereas the throughput goes down. CRASH! + */ +#define CARL9170_NUM_TX_LIMIT_HARD ((AR9170_TXQ_DEPTH * 3) / 2) +#define CARL9170_NUM_TX_LIMIT_SOFT (AR9170_TXQ_DEPTH) + +struct carl9170_tx_queue_stats { + unsigned int count; + unsigned int limit; + unsigned int len; +}; + +struct carl9170_vif { + unsigned int id; + struct ieee80211_vif __rcu *vif; +}; + +struct carl9170_vif_info { + struct list_head list; + bool active; + unsigned int id; + struct sk_buff *beacon; + bool enable_beacon; +}; + +#define AR9170_NUM_RX_URBS 16 +#define AR9170_NUM_RX_URBS_MUL 2 +#define AR9170_NUM_TX_URBS 8 +#define AR9170_NUM_RX_URBS_POOL (AR9170_NUM_RX_URBS_MUL * AR9170_NUM_RX_URBS) + +enum carl9170_device_features { + CARL9170_WPS_BUTTON = BIT(0), + CARL9170_ONE_LED = BIT(1), +}; + +#ifdef CPTCFG_CARL9170_LEDS +struct ar9170; + +struct carl9170_led { + struct ar9170 *ar; + struct led_classdev l; + char name[32]; + unsigned int toggled; + bool last_state; + bool registered; +}; +#endif /* CPTCFG_CARL9170_LEDS */ + +enum carl9170_restart_reasons { + CARL9170_RR_NO_REASON = 0, + CARL9170_RR_FATAL_FIRMWARE_ERROR, + CARL9170_RR_TOO_MANY_FIRMWARE_ERRORS, + CARL9170_RR_WATCHDOG, + CARL9170_RR_STUCK_TX, + CARL9170_RR_UNRESPONSIVE_DEVICE, + CARL9170_RR_COMMAND_TIMEOUT, + CARL9170_RR_TOO_MANY_PHY_ERRORS, + CARL9170_RR_LOST_RSP, + CARL9170_RR_INVALID_RSP, + CARL9170_RR_USER_REQUEST, + + __CARL9170_RR_LAST, +}; + +enum carl9170_erp_modes { + CARL9170_ERP_INVALID, + CARL9170_ERP_AUTO, + CARL9170_ERP_MAC80211, + CARL9170_ERP_OFF, + CARL9170_ERP_CTS, + CARL9170_ERP_RTS, + __CARL9170_ERP_NUM, +}; + +struct ar9170 { + struct ath_common common; + struct ieee80211_hw *hw; + struct mutex mutex; + enum carl9170_device_state state; + spinlock_t state_lock; + enum carl9170_restart_reasons last_reason; + bool registered; + + /* USB */ + struct usb_device *udev; + struct usb_interface *intf; + struct usb_anchor rx_anch; + struct usb_anchor rx_work; + struct usb_anchor rx_pool; + struct usb_anchor tx_wait; + struct usb_anchor tx_anch; + struct usb_anchor tx_cmd; + struct usb_anchor tx_err; + struct tasklet_struct usb_tasklet; + atomic_t tx_cmd_urbs; + atomic_t tx_anch_urbs; + atomic_t rx_anch_urbs; + atomic_t rx_work_urbs; + atomic_t rx_pool_urbs; + kernel_ulong_t features; + + /* firmware settings */ + struct completion fw_load_wait; + struct completion fw_boot_wait; + struct { + const struct carl9170fw_desc_head *desc; + const struct firmware *fw; + unsigned int offset; + unsigned int address; + unsigned int cmd_bufs; + unsigned int api_version; + unsigned int vif_num; + unsigned int err_counter; + unsigned int bug_counter; + u32 beacon_addr; + unsigned int beacon_max_len; + bool rx_stream; + bool tx_stream; + bool rx_filter; + bool hw_counters; + unsigned int mem_blocks; + unsigned int mem_block_size; + unsigned int rx_size; + unsigned int tx_seq_table; + bool ba_filter; + bool disable_offload_fw; + } fw; + + /* interface configuration combinations */ + struct ieee80211_iface_limit if_comb_limits[1]; + struct ieee80211_iface_combination if_combs[1]; + + /* reset / stuck frames/queue detection */ + struct work_struct restart_work; + struct work_struct ping_work; + unsigned int restart_counter; + unsigned long queue_stop_timeout[__AR9170_NUM_TXQ]; + unsigned long max_queue_stop_timeout[__AR9170_NUM_TXQ]; + bool needs_full_reset; + bool force_usb_reset; + atomic_t pending_restarts; + + /* interface mode settings */ + struct list_head vif_list; + unsigned long vif_bitmap; + unsigned int vifs; + struct carl9170_vif vif_priv[AR9170_MAX_VIRTUAL_MAC]; + + /* beaconing */ + spinlock_t beacon_lock; + unsigned int global_pretbtt; + unsigned int global_beacon_int; + struct carl9170_vif_info __rcu *beacon_iter; + unsigned int beacon_enabled; + + /* cryptographic engine */ + u64 usedkeys; + bool rx_software_decryption; + bool disable_offload; + + /* filter settings */ + u64 cur_mc_hash; + u32 cur_filter; + unsigned int filter_state; + unsigned int rx_filter_caps; + bool sniffer_enabled; + + /* MAC */ + enum carl9170_erp_modes erp_mode; + + /* PHY */ + struct ieee80211_channel *channel; + unsigned int num_channels; + int noise[4]; + unsigned int chan_fail; + unsigned int total_chan_fail; + u8 heavy_clip; + u8 ht_settings; + struct { + u64 active; /* usec */ + u64 cca; /* usec */ + u64 tx_time; /* usec */ + u64 rx_total; + u64 rx_overrun; + } tally; + struct delayed_work stat_work; + struct survey_info *survey; + + /* power calibration data */ + u8 power_5G_leg[4]; + u8 power_2G_cck[4]; + u8 power_2G_ofdm[4]; + u8 power_5G_ht20[8]; + u8 power_5G_ht40[8]; + u8 power_2G_ht20[8]; + u8 power_2G_ht40[8]; + +#ifdef CPTCFG_CARL9170_LEDS + /* LED */ + struct delayed_work led_work; + struct carl9170_led leds[AR9170_NUM_LEDS]; +#endif /* CPTCFG_CARL9170_LEDS */ + + /* qos queue settings */ + spinlock_t tx_stats_lock; + struct carl9170_tx_queue_stats tx_stats[__AR9170_NUM_TXQ]; + struct ieee80211_tx_queue_params edcf[5]; + struct completion tx_flush; + + /* CMD */ + int cmd_seq; + int readlen; + u8 *readbuf; + spinlock_t cmd_lock; + struct completion cmd_wait; + union { + __le32 cmd_buf[PAYLOAD_MAX + 1]; + struct carl9170_cmd cmd; + struct carl9170_rsp rsp; + }; + + /* statistics */ + unsigned int tx_dropped; + unsigned int tx_ack_failures; + unsigned int tx_fcs_errors; + unsigned int rx_dropped; + + /* EEPROM */ + struct ar9170_eeprom eeprom; + + /* tx queuing */ + struct sk_buff_head tx_pending[__AR9170_NUM_TXQ]; + struct sk_buff_head tx_status[__AR9170_NUM_TXQ]; + struct delayed_work tx_janitor; + unsigned long tx_janitor_last_run; + bool tx_schedule; + + /* tx ampdu */ + struct work_struct ampdu_work; + spinlock_t tx_ampdu_list_lock; + struct carl9170_sta_tid __rcu *tx_ampdu_iter; + struct list_head tx_ampdu_list; + atomic_t tx_ampdu_upload; + atomic_t tx_ampdu_scheduler; + atomic_t tx_total_pending; + atomic_t tx_total_queued; + unsigned int tx_ampdu_list_len; + int current_density; + int current_factor; + bool tx_ampdu_schedule; + + /* internal memory management */ + spinlock_t mem_lock; + unsigned long *mem_bitmap; + atomic_t mem_free_blocks; + atomic_t mem_allocs; + + /* rxstream mpdu merge */ + struct ar9170_rx_head rx_plcp; + bool rx_has_plcp; + struct sk_buff *rx_failover; + int rx_failover_missing; + u32 ampdu_ref; + + /* FIFO for collecting outstanding BlockAckRequest */ + struct list_head bar_list[__AR9170_NUM_TXQ]; + spinlock_t bar_list_lock[__AR9170_NUM_TXQ]; + +#ifdef CPTCFG_CARL9170_WPC + struct { + bool pbc_state; + struct input_dev *pbc; + char name[32]; + char phys[32]; + } wps; +#endif /* CPTCFG_CARL9170_WPC */ + +#ifdef CPTCFG_CARL9170_DEBUGFS + struct carl9170_debug debug; + struct dentry *debug_dir; +#endif /* CPTCFG_CARL9170_DEBUGFS */ + + /* PSM */ + struct work_struct ps_work; + struct { + unsigned int dtim_counter; + unsigned long last_beacon; + unsigned long last_action; + unsigned long last_slept; + unsigned int sleep_ms; + unsigned int off_override; + bool state; + } ps; + +#ifdef CPTCFG_CARL9170_HWRNG +# define CARL9170_HWRNG_CACHE_SIZE CARL9170_MAX_CMD_PAYLOAD_LEN + struct { + struct hwrng rng; + bool initialized; + char name[30 + 1]; + u16 cache[CARL9170_HWRNG_CACHE_SIZE / sizeof(u16)]; + unsigned int cache_idx; + } rng; +#endif /* CPTCFG_CARL9170_HWRNG */ +}; + +enum carl9170_ps_off_override_reasons { + PS_OFF_VIF = BIT(0), + PS_OFF_BCN = BIT(1), +}; + +struct carl9170_bar_list_entry { + struct list_head list; + struct rcu_head head; + struct sk_buff *skb; +}; + +struct carl9170_ba_stats { + u8 ampdu_len; + u8 ampdu_ack_len; + bool clear; + bool req; +}; + +struct carl9170_sta_info { + bool ht_sta; + bool sleeping; + atomic_t pending_frames; + unsigned int ampdu_max_len; + struct carl9170_sta_tid __rcu *agg[IEEE80211_NUM_TIDS]; + struct carl9170_ba_stats stats[IEEE80211_NUM_TIDS]; +}; + +struct carl9170_tx_info { + unsigned long timeout; + struct ar9170 *ar; + struct kref ref; +}; + +#define CHK_DEV_STATE(a, s) (((struct ar9170 *)a)->state >= (s)) +#define IS_INITIALIZED(a) (CHK_DEV_STATE(a, CARL9170_STOPPED)) +#define IS_ACCEPTING_CMD(a) (CHK_DEV_STATE(a, CARL9170_IDLE)) +#define IS_STARTED(a) (CHK_DEV_STATE(a, CARL9170_STARTED)) + +static inline void __carl9170_set_state(struct ar9170 *ar, + enum carl9170_device_state newstate) +{ + ar->state = newstate; +} + +static inline void carl9170_set_state(struct ar9170 *ar, + enum carl9170_device_state newstate) +{ + unsigned long flags; + + spin_lock_irqsave(&ar->state_lock, flags); + __carl9170_set_state(ar, newstate); + spin_unlock_irqrestore(&ar->state_lock, flags); +} + +static inline void carl9170_set_state_when(struct ar9170 *ar, + enum carl9170_device_state min, enum carl9170_device_state newstate) +{ + unsigned long flags; + + spin_lock_irqsave(&ar->state_lock, flags); + if (CHK_DEV_STATE(ar, min)) + __carl9170_set_state(ar, newstate); + spin_unlock_irqrestore(&ar->state_lock, flags); +} + +/* exported interface */ +void *carl9170_alloc(size_t priv_size); +int carl9170_register(struct ar9170 *ar); +void carl9170_unregister(struct ar9170 *ar); +void carl9170_free(struct ar9170 *ar); +void carl9170_restart(struct ar9170 *ar, const enum carl9170_restart_reasons r); +void carl9170_ps_check(struct ar9170 *ar); + +/* USB back-end */ +int carl9170_usb_open(struct ar9170 *ar); +void carl9170_usb_stop(struct ar9170 *ar); +void carl9170_usb_tx(struct ar9170 *ar, struct sk_buff *skb); +void carl9170_usb_handle_tx_err(struct ar9170 *ar); +int carl9170_exec_cmd(struct ar9170 *ar, const enum carl9170_cmd_oids, + u32 plen, void *payload, u32 rlen, void *resp); +int __carl9170_exec_cmd(struct ar9170 *ar, struct carl9170_cmd *cmd, + const bool free_buf); +int carl9170_usb_restart(struct ar9170 *ar); +void carl9170_usb_reset(struct ar9170 *ar); + +/* MAC */ +int carl9170_init_mac(struct ar9170 *ar); +int carl9170_set_qos(struct ar9170 *ar); +int carl9170_update_multicast(struct ar9170 *ar, const u64 mc_hast); +int carl9170_mod_virtual_mac(struct ar9170 *ar, const unsigned int id, + const u8 *mac); +int carl9170_set_operating_mode(struct ar9170 *ar); +int carl9170_set_beacon_timers(struct ar9170 *ar); +int carl9170_set_dyn_sifs_ack(struct ar9170 *ar); +int carl9170_set_rts_cts_rate(struct ar9170 *ar); +int carl9170_set_ampdu_settings(struct ar9170 *ar); +int carl9170_set_slot_time(struct ar9170 *ar); +int carl9170_set_mac_rates(struct ar9170 *ar); +int carl9170_set_hwretry_limit(struct ar9170 *ar, const u32 max_retry); +int carl9170_upload_key(struct ar9170 *ar, const u8 id, const u8 *mac, + const u8 ktype, const u8 keyidx, const u8 *keydata, const int keylen); +int carl9170_disable_key(struct ar9170 *ar, const u8 id); +int carl9170_set_mac_tpc(struct ar9170 *ar, struct ieee80211_channel *channel); + +/* RX */ +void carl9170_rx(struct ar9170 *ar, void *buf, unsigned int len); +void carl9170_handle_command_response(struct ar9170 *ar, void *buf, u32 len); + +/* TX */ +void carl9170_op_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb); +void carl9170_tx_janitor(struct work_struct *work); +void carl9170_tx_process_status(struct ar9170 *ar, + const struct carl9170_rsp *cmd); +void carl9170_tx_status(struct ar9170 *ar, struct sk_buff *skb, + const bool success); +void carl9170_tx_callback(struct ar9170 *ar, struct sk_buff *skb); +void carl9170_tx_drop(struct ar9170 *ar, struct sk_buff *skb); +void carl9170_tx_scheduler(struct ar9170 *ar); +void carl9170_tx_get_skb(struct sk_buff *skb); +int carl9170_tx_put_skb(struct sk_buff *skb); +int carl9170_update_beacon(struct ar9170 *ar, const bool submit); + +/* LEDs */ +#ifdef CPTCFG_CARL9170_LEDS +int carl9170_led_register(struct ar9170 *ar); +void carl9170_led_unregister(struct ar9170 *ar); +#endif /* CPTCFG_CARL9170_LEDS */ +int carl9170_led_init(struct ar9170 *ar); +int carl9170_led_set_state(struct ar9170 *ar, const u32 led_state); + +/* PHY / RF */ +int carl9170_set_channel(struct ar9170 *ar, struct ieee80211_channel *channel, + enum nl80211_channel_type bw); +int carl9170_get_noisefloor(struct ar9170 *ar); + +/* FW */ +int carl9170_parse_firmware(struct ar9170 *ar); + +extern struct ieee80211_rate __carl9170_ratetable[]; +extern int modparam_noht; + +static inline struct ar9170 *carl9170_get_priv(struct carl9170_vif *carl_vif) +{ + return container_of(carl_vif, struct ar9170, + vif_priv[carl_vif->id]); +} + +static inline struct ieee80211_hdr *carl9170_get_hdr(struct sk_buff *skb) +{ + return (void *)((struct _carl9170_tx_superframe *) + skb->data)->frame_data; +} + +static inline u16 get_seq_h(struct ieee80211_hdr *hdr) +{ + return le16_to_cpu(hdr->seq_ctrl) >> 4; +} + +static inline u16 carl9170_get_seq(struct sk_buff *skb) +{ + return get_seq_h(carl9170_get_hdr(skb)); +} + +static inline u16 get_tid_h(struct ieee80211_hdr *hdr) +{ + return (ieee80211_get_qos_ctl(hdr))[0] & IEEE80211_QOS_CTL_TID_MASK; +} + +static inline u16 carl9170_get_tid(struct sk_buff *skb) +{ + return get_tid_h(carl9170_get_hdr(skb)); +} + +static inline struct ieee80211_vif * +carl9170_get_vif(struct carl9170_vif_info *priv) +{ + return container_of((void *)priv, struct ieee80211_vif, drv_priv); +} + +/* Protected by ar->mutex or RCU */ +static inline struct ieee80211_vif *carl9170_get_main_vif(struct ar9170 *ar) +{ + struct carl9170_vif_info *cvif; + + list_for_each_entry_rcu(cvif, &ar->vif_list, list) { + if (cvif->active) + return carl9170_get_vif(cvif); + } + + return NULL; +} + +static inline bool is_main_vif(struct ar9170 *ar, struct ieee80211_vif *vif) +{ + bool ret; + + rcu_read_lock(); + ret = (carl9170_get_main_vif(ar) == vif); + rcu_read_unlock(); + return ret; +} + +#endif /* __CARL9170_H */ diff --git a/drivers/net/wireless/ath/carl9170/cmd.c b/drivers/net/wireless/ath/carl9170/cmd.c new file mode 100644 index 0000000..39a6387 --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/cmd.c @@ -0,0 +1,222 @@ +/* + * Atheros CARL9170 driver + * + * Basic HW register/memory/command access functions + * + * Copyright 2008, Johannes Berg + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * Copyright (c) 2007-2008 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "carl9170.h" +#include "cmd.h" + +int carl9170_write_reg(struct ar9170 *ar, const u32 reg, const u32 val) +{ + const __le32 buf[2] = { + cpu_to_le32(reg), + cpu_to_le32(val), + }; + int err; + + err = carl9170_exec_cmd(ar, CARL9170_CMD_WREG, sizeof(buf), + (u8 *) buf, 0, NULL); + if (err) { + if (net_ratelimit()) { + wiphy_err(ar->hw->wiphy, "writing reg %#x " + "(val %#x) failed (%d)\n", reg, val, err); + } + } + return err; +} + +int carl9170_read_mreg(struct ar9170 *ar, const int nregs, + const u32 *regs, u32 *out) +{ + int i, err; + __le32 *offs, *res; + + /* abuse "out" for the register offsets, must be same length */ + offs = (__le32 *)out; + for (i = 0; i < nregs; i++) + offs[i] = cpu_to_le32(regs[i]); + + /* also use the same buffer for the input */ + res = (__le32 *)out; + + err = carl9170_exec_cmd(ar, CARL9170_CMD_RREG, + 4 * nregs, (u8 *)offs, + 4 * nregs, (u8 *)res); + if (err) { + if (net_ratelimit()) { + wiphy_err(ar->hw->wiphy, "reading regs failed (%d)\n", + err); + } + return err; + } + + /* convert result to cpu endian */ + for (i = 0; i < nregs; i++) + out[i] = le32_to_cpu(res[i]); + + return 0; +} + +int carl9170_read_reg(struct ar9170 *ar, u32 reg, u32 *val) +{ + return carl9170_read_mreg(ar, 1, ®, val); +} + +int carl9170_echo_test(struct ar9170 *ar, const u32 v) +{ + u32 echores; + int err; + + err = carl9170_exec_cmd(ar, CARL9170_CMD_ECHO, + 4, (u8 *)&v, + 4, (u8 *)&echores); + if (err) + return err; + + if (v != echores) { + wiphy_info(ar->hw->wiphy, "wrong echo %x != %x", v, echores); + return -EINVAL; + } + + return 0; +} + +struct carl9170_cmd *carl9170_cmd_buf(struct ar9170 *ar, + const enum carl9170_cmd_oids cmd, const unsigned int len) +{ + struct carl9170_cmd *tmp; + + tmp = kzalloc(sizeof(struct carl9170_cmd_head) + len, GFP_ATOMIC); + if (tmp) { + tmp->hdr.cmd = cmd; + tmp->hdr.len = len; + } + + return tmp; +} + +int carl9170_reboot(struct ar9170 *ar) +{ + struct carl9170_cmd *cmd; + int err; + + cmd = carl9170_cmd_buf(ar, CARL9170_CMD_REBOOT_ASYNC, 0); + if (!cmd) + return -ENOMEM; + + err = __carl9170_exec_cmd(ar, cmd, true); + return err; +} + +int carl9170_mac_reset(struct ar9170 *ar) +{ + return carl9170_exec_cmd(ar, CARL9170_CMD_SWRST, + 0, NULL, 0, NULL); +} + +int carl9170_bcn_ctrl(struct ar9170 *ar, const unsigned int vif_id, + const u32 mode, const u32 addr, const u32 len) +{ + struct carl9170_cmd *cmd; + + cmd = carl9170_cmd_buf(ar, CARL9170_CMD_BCN_CTRL_ASYNC, + sizeof(struct carl9170_bcn_ctrl_cmd)); + if (!cmd) + return -ENOMEM; + + cmd->bcn_ctrl.vif_id = cpu_to_le32(vif_id); + cmd->bcn_ctrl.mode = cpu_to_le32(mode); + cmd->bcn_ctrl.bcn_addr = cpu_to_le32(addr); + cmd->bcn_ctrl.bcn_len = cpu_to_le32(len); + + return __carl9170_exec_cmd(ar, cmd, true); +} + +int carl9170_collect_tally(struct ar9170 *ar) +{ + struct carl9170_tally_rsp tally; + struct survey_info *info; + unsigned int tick; + int err; + + err = carl9170_exec_cmd(ar, CARL9170_CMD_TALLY, 0, NULL, + sizeof(tally), (u8 *)&tally); + if (err) + return err; + + tick = le32_to_cpu(tally.tick); + if (tick) { + ar->tally.active += le32_to_cpu(tally.active) / tick; + ar->tally.cca += le32_to_cpu(tally.cca) / tick; + ar->tally.tx_time += le32_to_cpu(tally.tx_time) / tick; + ar->tally.rx_total += le32_to_cpu(tally.rx_total); + ar->tally.rx_overrun += le32_to_cpu(tally.rx_overrun); + + if (ar->channel) { + info = &ar->survey[ar->channel->hw_value]; + info->channel_time = ar->tally.active; + info->channel_time_busy = ar->tally.cca; + info->channel_time_tx = ar->tally.tx_time; + do_div(info->channel_time, 1000); + do_div(info->channel_time_busy, 1000); + do_div(info->channel_time_tx, 1000); + } + } + return 0; +} + +int carl9170_powersave(struct ar9170 *ar, const bool ps) +{ + struct carl9170_cmd *cmd; + u32 state; + + cmd = carl9170_cmd_buf(ar, CARL9170_CMD_PSM_ASYNC, + sizeof(struct carl9170_psm)); + if (!cmd) + return -ENOMEM; + + if (ps) { + /* Sleep until next TBTT */ + state = CARL9170_PSM_SLEEP | 1; + } else { + /* wake up immediately */ + state = 1; + } + + cmd->psm.state = cpu_to_le32(state); + return __carl9170_exec_cmd(ar, cmd, true); +} diff --git a/drivers/net/wireless/ath/carl9170/cmd.h b/drivers/net/wireless/ath/carl9170/cmd.h new file mode 100644 index 0000000..65919c9 --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/cmd.h @@ -0,0 +1,174 @@ +/* + * Atheros CARL9170 driver + * + * Basic HW register/memory/command access functions + * + * Copyright 2008, Johannes Berg + * Copyright 2010, Christian Lamparter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * Copyright (c) 2007-2008 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef __CMD_H +#define __CMD_H + +#include "carl9170.h" + +/* basic HW access */ +int carl9170_write_reg(struct ar9170 *ar, const u32 reg, const u32 val); +int carl9170_read_reg(struct ar9170 *ar, const u32 reg, u32 *val); +int carl9170_read_mreg(struct ar9170 *ar, const int nregs, + const u32 *regs, u32 *out); +int carl9170_echo_test(struct ar9170 *ar, u32 v); +int carl9170_reboot(struct ar9170 *ar); +int carl9170_mac_reset(struct ar9170 *ar); +int carl9170_powersave(struct ar9170 *ar, const bool power_on); +int carl9170_collect_tally(struct ar9170 *ar); +int carl9170_bcn_ctrl(struct ar9170 *ar, const unsigned int vif_id, + const u32 mode, const u32 addr, const u32 len); + +static inline int carl9170_flush_cab(struct ar9170 *ar, + const unsigned int vif_id) +{ + return carl9170_bcn_ctrl(ar, vif_id, CARL9170_BCN_CTRL_DRAIN, 0, 0); +} + +static inline int carl9170_rx_filter(struct ar9170 *ar, + const unsigned int _rx_filter) +{ + __le32 rx_filter = cpu_to_le32(_rx_filter); + + return carl9170_exec_cmd(ar, CARL9170_CMD_RX_FILTER, + sizeof(rx_filter), (u8 *)&rx_filter, + 0, NULL); +} + +struct carl9170_cmd *carl9170_cmd_buf(struct ar9170 *ar, + const enum carl9170_cmd_oids cmd, const unsigned int len); + +/* + * Macros to facilitate writing multiple registers in a single + * write-combining USB command. Note that when the first group + * fails the whole thing will fail without any others attempted, + * but you won't know which write in the group failed. + */ +#define carl9170_regwrite_begin(ar) \ +do { \ + int __nreg = 0, __err = 0; \ + struct ar9170 *__ar = ar; + +#define carl9170_regwrite(r, v) do { \ + __ar->cmd_buf[2 * __nreg + 1] = cpu_to_le32(r); \ + __ar->cmd_buf[2 * __nreg + 2] = cpu_to_le32(v); \ + __nreg++; \ + if ((__nreg >= PAYLOAD_MAX / 2)) { \ + if (IS_ACCEPTING_CMD(__ar)) \ + __err = carl9170_exec_cmd(__ar, \ + CARL9170_CMD_WREG, 8 * __nreg, \ + (u8 *) &__ar->cmd_buf[1], 0, NULL); \ + else \ + goto __regwrite_out; \ + \ + __nreg = 0; \ + if (__err) \ + goto __regwrite_out; \ + } \ +} while (0) + +#define carl9170_regwrite_finish() \ +__regwrite_out : \ + if (__err == 0 && __nreg) { \ + if (IS_ACCEPTING_CMD(__ar)) \ + __err = carl9170_exec_cmd(__ar, \ + CARL9170_CMD_WREG, 8 * __nreg, \ + (u8 *) &__ar->cmd_buf[1], 0, NULL); \ + __nreg = 0; \ + } + +#define carl9170_regwrite_result() \ + __err; \ +} while (0) + + +#define carl9170_async_regwrite_get_buf() \ +do { \ + __nreg = 0; \ + __cmd = carl9170_cmd_buf(__carl, CARL9170_CMD_WREG_ASYNC, \ + CARL9170_MAX_CMD_PAYLOAD_LEN); \ + if (__cmd == NULL) { \ + __err = -ENOMEM; \ + goto __async_regwrite_out; \ + } \ +} while (0) + +#define carl9170_async_regwrite_begin(carl) \ +do { \ + struct ar9170 *__carl = carl; \ + struct carl9170_cmd *__cmd; \ + unsigned int __nreg; \ + int __err = 0; \ + carl9170_async_regwrite_get_buf(); \ + +#define carl9170_async_regwrite_flush() \ +do { \ + if (__cmd == NULL || __nreg == 0) \ + break; \ + \ + if (IS_ACCEPTING_CMD(__carl) && __nreg) { \ + __cmd->hdr.len = 8 * __nreg; \ + __err = __carl9170_exec_cmd(__carl, __cmd, true); \ + __cmd = NULL; \ + break; \ + } \ + goto __async_regwrite_out; \ +} while (0) + +#define carl9170_async_regwrite(r, v) do { \ + if (__cmd == NULL) \ + carl9170_async_regwrite_get_buf(); \ + __cmd->wreg.regs[__nreg].addr = cpu_to_le32(r); \ + __cmd->wreg.regs[__nreg].val = cpu_to_le32(v); \ + __nreg++; \ + if ((__nreg >= PAYLOAD_MAX / 2)) \ + carl9170_async_regwrite_flush(); \ +} while (0) + +#define carl9170_async_regwrite_finish() do { \ +__async_regwrite_out: \ + if (__cmd != NULL && __err == 0) \ + carl9170_async_regwrite_flush(); \ + kfree(__cmd); \ +} while (0) \ + +#define carl9170_async_regwrite_result() \ + __err; \ +} while (0) + +#endif /* __CMD_H */ diff --git a/drivers/net/wireless/ath/carl9170/debug.c b/drivers/net/wireless/ath/carl9170/debug.c new file mode 100644 index 0000000..3d70cd2 --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/debug.c @@ -0,0 +1,897 @@ +/* + * Atheros CARL9170 driver + * + * debug(fs) probing + * + * Copyright 2008, Johannes Berg + * Copyright 2009, 2010, Christian Lamparter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * Copyright (c) 2008-2009 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include "carl9170.h" +#include "cmd.h" + +#define ADD(buf, off, max, fmt, args...) \ + off += snprintf(&buf[off], max - off, fmt, ##args); + + +struct carl9170_debugfs_fops { + unsigned int read_bufsize; + umode_t attr; + char *(*read)(struct ar9170 *ar, char *buf, size_t bufsize, + ssize_t *len); + ssize_t (*write)(struct ar9170 *aru, const char *buf, size_t size); + const struct file_operations fops; + + enum carl9170_device_state req_dev_state; +}; + +static ssize_t carl9170_debugfs_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct carl9170_debugfs_fops *dfops; + struct ar9170 *ar; + char *buf = NULL, *res_buf = NULL; + ssize_t ret = 0; + int err = 0; + + if (!count) + return 0; + + ar = file->private_data; + + if (!ar) + return -ENODEV; + dfops = container_of(file->f_op, struct carl9170_debugfs_fops, fops); + + if (!dfops->read) + return -ENOSYS; + + if (dfops->read_bufsize) { + buf = vmalloc(dfops->read_bufsize); + if (!buf) + return -ENOMEM; + } + + mutex_lock(&ar->mutex); + if (!CHK_DEV_STATE(ar, dfops->req_dev_state)) { + err = -ENODEV; + res_buf = buf; + goto out_free; + } + + res_buf = dfops->read(ar, buf, dfops->read_bufsize, &ret); + + if (ret > 0) + err = simple_read_from_buffer(userbuf, count, ppos, + res_buf, ret); + else + err = ret; + + WARN_ON_ONCE(dfops->read_bufsize && (res_buf != buf)); + +out_free: + vfree(res_buf); + mutex_unlock(&ar->mutex); + return err; +} + +static ssize_t carl9170_debugfs_write(struct file *file, + const char __user *userbuf, size_t count, loff_t *ppos) +{ + struct carl9170_debugfs_fops *dfops; + struct ar9170 *ar; + char *buf = NULL; + int err = 0; + + if (!count) + return 0; + + if (count > PAGE_SIZE) + return -E2BIG; + + ar = file->private_data; + + if (!ar) + return -ENODEV; + dfops = container_of(file->f_op, struct carl9170_debugfs_fops, fops); + + if (!dfops->write) + return -ENOSYS; + + buf = vmalloc(count); + if (!buf) + return -ENOMEM; + + if (copy_from_user(buf, userbuf, count)) { + err = -EFAULT; + goto out_free; + } + + if (mutex_trylock(&ar->mutex) == 0) { + err = -EAGAIN; + goto out_free; + } + + if (!CHK_DEV_STATE(ar, dfops->req_dev_state)) { + err = -ENODEV; + goto out_unlock; + } + + err = dfops->write(ar, buf, count); + if (err) + goto out_unlock; + +out_unlock: + mutex_unlock(&ar->mutex); + +out_free: + vfree(buf); + return err; +} + +#define __DEBUGFS_DECLARE_FILE(name, _read, _write, _read_bufsize, \ + _attr, _dstate) \ +static const struct carl9170_debugfs_fops carl_debugfs_##name ##_ops = {\ + .read_bufsize = _read_bufsize, \ + .read = _read, \ + .write = _write, \ + .attr = _attr, \ + .req_dev_state = _dstate, \ + .fops = { \ + .open = simple_open, \ + .read = carl9170_debugfs_read, \ + .write = carl9170_debugfs_write, \ + .owner = THIS_MODULE \ + }, \ +} + +#define DEBUGFS_DECLARE_FILE(name, _read, _write, _read_bufsize, _attr) \ + __DEBUGFS_DECLARE_FILE(name, _read, _write, _read_bufsize, \ + _attr, CARL9170_STARTED) \ + +#define DEBUGFS_DECLARE_RO_FILE(name, _read_bufsize) \ + DEBUGFS_DECLARE_FILE(name, carl9170_debugfs_##name ##_read, \ + NULL, _read_bufsize, S_IRUSR) + +#define DEBUGFS_DECLARE_WO_FILE(name) \ + DEBUGFS_DECLARE_FILE(name, NULL, carl9170_debugfs_##name ##_write,\ + 0, S_IWUSR) + +#define DEBUGFS_DECLARE_RW_FILE(name, _read_bufsize) \ + DEBUGFS_DECLARE_FILE(name, carl9170_debugfs_##name ##_read, \ + carl9170_debugfs_##name ##_write, \ + _read_bufsize, S_IRUSR | S_IWUSR) + +#define __DEBUGFS_DECLARE_RW_FILE(name, _read_bufsize, _dstate) \ + __DEBUGFS_DECLARE_FILE(name, carl9170_debugfs_##name ##_read, \ + carl9170_debugfs_##name ##_write, \ + _read_bufsize, S_IRUSR | S_IWUSR, _dstate) + +#define DEBUGFS_READONLY_FILE(name, _read_bufsize, fmt, value...) \ +static char *carl9170_debugfs_ ##name ## _read(struct ar9170 *ar, \ + char *buf, size_t buf_size,\ + ssize_t *len) \ +{ \ + ADD(buf, *len, buf_size, fmt "\n", ##value); \ + return buf; \ +} \ +DEBUGFS_DECLARE_RO_FILE(name, _read_bufsize) + +static char *carl9170_debugfs_mem_usage_read(struct ar9170 *ar, char *buf, + size_t bufsize, ssize_t *len) +{ + ADD(buf, *len, bufsize, "jar: ["); + + spin_lock_bh(&ar->mem_lock); + + *len += bitmap_scnprintf(&buf[*len], bufsize - *len, + ar->mem_bitmap, ar->fw.mem_blocks); + + ADD(buf, *len, bufsize, "]\n"); + + ADD(buf, *len, bufsize, "cookies: used:%3d / total:%3d, allocs:%d\n", + bitmap_weight(ar->mem_bitmap, ar->fw.mem_blocks), + ar->fw.mem_blocks, atomic_read(&ar->mem_allocs)); + + ADD(buf, *len, bufsize, "memory: free:%3d (%3d KiB) / total:%3d KiB)\n", + atomic_read(&ar->mem_free_blocks), + (atomic_read(&ar->mem_free_blocks) * ar->fw.mem_block_size) / 1024, + (ar->fw.mem_blocks * ar->fw.mem_block_size) / 1024); + + spin_unlock_bh(&ar->mem_lock); + + return buf; +} +DEBUGFS_DECLARE_RO_FILE(mem_usage, 512); + +static char *carl9170_debugfs_qos_stat_read(struct ar9170 *ar, char *buf, + size_t bufsize, ssize_t *len) +{ + ADD(buf, *len, bufsize, "%s QoS AC\n", modparam_noht ? "Hardware" : + "Software"); + + ADD(buf, *len, bufsize, "[ VO VI " + " BE BK ]\n"); + + spin_lock_bh(&ar->tx_stats_lock); + ADD(buf, *len, bufsize, "[length/limit length/limit " + "length/limit length/limit ]\n" + "[ %3d/%3d %3d/%3d " + " %3d/%3d %3d/%3d ]\n\n", + ar->tx_stats[0].len, ar->tx_stats[0].limit, + ar->tx_stats[1].len, ar->tx_stats[1].limit, + ar->tx_stats[2].len, ar->tx_stats[2].limit, + ar->tx_stats[3].len, ar->tx_stats[3].limit); + + ADD(buf, *len, bufsize, "[ total total " + " total total ]\n" + "[%10d %10d %10d %10d ]\n\n", + ar->tx_stats[0].count, ar->tx_stats[1].count, + ar->tx_stats[2].count, ar->tx_stats[3].count); + + spin_unlock_bh(&ar->tx_stats_lock); + + ADD(buf, *len, bufsize, "[ pend/waittx pend/waittx " + " pend/waittx pend/waittx]\n" + "[ %3d/%3d %3d/%3d " + " %3d/%3d %3d/%3d ]\n\n", + skb_queue_len(&ar->tx_pending[0]), + skb_queue_len(&ar->tx_status[0]), + skb_queue_len(&ar->tx_pending[1]), + skb_queue_len(&ar->tx_status[1]), + skb_queue_len(&ar->tx_pending[2]), + skb_queue_len(&ar->tx_status[2]), + skb_queue_len(&ar->tx_pending[3]), + skb_queue_len(&ar->tx_status[3])); + + return buf; +} +DEBUGFS_DECLARE_RO_FILE(qos_stat, 512); + +static void carl9170_debugfs_format_frame(struct ar9170 *ar, + struct sk_buff *skb, const char *prefix, char *buf, + ssize_t *off, ssize_t bufsize) +{ + struct _carl9170_tx_superframe *txc = (void *) skb->data; + struct ieee80211_tx_info *txinfo = IEEE80211_SKB_CB(skb); + struct carl9170_tx_info *arinfo = (void *) txinfo->rate_driver_data; + struct ieee80211_hdr *hdr = (void *) txc->frame_data; + + ADD(buf, *off, bufsize, "%s %p, c:%2x, DA:%pM, sq:%4d, mc:%.4x, " + "pc:%.8x, to:%d ms\n", prefix, skb, txc->s.cookie, + ieee80211_get_DA(hdr), get_seq_h(hdr), + le16_to_cpu(txc->f.mac_control), le32_to_cpu(txc->f.phy_control), + jiffies_to_msecs(jiffies - arinfo->timeout)); +} + + +static char *carl9170_debugfs_ampdu_state_read(struct ar9170 *ar, char *buf, + size_t bufsize, ssize_t *len) +{ + struct carl9170_sta_tid *iter; + struct sk_buff *skb; + int cnt = 0, fc; + int offset; + + rcu_read_lock(); + list_for_each_entry_rcu(iter, &ar->tx_ampdu_list, list) { + + spin_lock_bh(&iter->lock); + ADD(buf, *len, bufsize, "Entry: #%2d TID:%1d, BSN:%4d, " + "SNX:%4d, HSN:%4d, BAW:%2d, state:%1d, toggles:%d\n", + cnt, iter->tid, iter->bsn, iter->snx, iter->hsn, + iter->max, iter->state, iter->counter); + + ADD(buf, *len, bufsize, "\tWindow: ["); + + *len += bitmap_scnprintf(&buf[*len], bufsize - *len, + iter->bitmap, CARL9170_BAW_BITS); + +#define BM_STR_OFF(offset) \ + ((CARL9170_BAW_BITS - (offset) - 1) / 4 + \ + (CARL9170_BAW_BITS - (offset) - 1) / 32 + 1) + + ADD(buf, *len, bufsize, ",W]\n"); + + offset = BM_STR_OFF(0); + ADD(buf, *len, bufsize, "\tBase Seq: %*s\n", offset, "T"); + + offset = BM_STR_OFF(SEQ_DIFF(iter->snx, iter->bsn)); + ADD(buf, *len, bufsize, "\tNext Seq: %*s\n", offset, "W"); + + offset = BM_STR_OFF(((int)iter->hsn - (int)iter->bsn) % + CARL9170_BAW_BITS); + ADD(buf, *len, bufsize, "\tLast Seq: %*s\n", offset, "N"); + + ADD(buf, *len, bufsize, "\tPre-Aggregation reorder buffer: " + " currently queued:%d\n", skb_queue_len(&iter->queue)); + + fc = 0; + skb_queue_walk(&iter->queue, skb) { + char prefix[32]; + + snprintf(prefix, sizeof(prefix), "\t\t%3d :", fc); + carl9170_debugfs_format_frame(ar, skb, prefix, buf, + len, bufsize); + + fc++; + } + spin_unlock_bh(&iter->lock); + cnt++; + } + rcu_read_unlock(); + + return buf; +} +DEBUGFS_DECLARE_RO_FILE(ampdu_state, 8000); + +static void carl9170_debugfs_queue_dump(struct ar9170 *ar, char *buf, + ssize_t *len, size_t bufsize, struct sk_buff_head *queue) +{ + struct sk_buff *skb; + char prefix[16]; + int fc = 0; + + spin_lock_bh(&queue->lock); + skb_queue_walk(queue, skb) { + snprintf(prefix, sizeof(prefix), "%3d :", fc); + carl9170_debugfs_format_frame(ar, skb, prefix, buf, + len, bufsize); + fc++; + } + spin_unlock_bh(&queue->lock); +} + +#define DEBUGFS_QUEUE_DUMP(q, qi) \ +static char *carl9170_debugfs_##q ##_##qi ##_read(struct ar9170 *ar, \ + char *buf, size_t bufsize, ssize_t *len) \ +{ \ + carl9170_debugfs_queue_dump(ar, buf, len, bufsize, &ar->q[qi]); \ + return buf; \ +} \ +DEBUGFS_DECLARE_RO_FILE(q##_##qi, 8000); + +static char *carl9170_debugfs_sta_psm_read(struct ar9170 *ar, char *buf, + size_t bufsize, ssize_t *len) +{ + ADD(buf, *len, bufsize, "psm state: %s\n", (ar->ps.off_override ? + "FORCE CAM" : (ar->ps.state ? "PSM" : "CAM"))); + + ADD(buf, *len, bufsize, "sleep duration: %d ms.\n", ar->ps.sleep_ms); + ADD(buf, *len, bufsize, "last power-state transition: %d ms ago.\n", + jiffies_to_msecs(jiffies - ar->ps.last_action)); + ADD(buf, *len, bufsize, "last CAM->PSM transition: %d ms ago.\n", + jiffies_to_msecs(jiffies - ar->ps.last_slept)); + + return buf; +} +DEBUGFS_DECLARE_RO_FILE(sta_psm, 160); + +static char *carl9170_debugfs_tx_stuck_read(struct ar9170 *ar, char *buf, + size_t bufsize, ssize_t *len) +{ + int i; + + for (i = 0; i < ar->hw->queues; i++) { + ADD(buf, *len, bufsize, "TX queue [%d]: %10d max:%10d ms.\n", + i, ieee80211_queue_stopped(ar->hw, i) ? + jiffies_to_msecs(jiffies - ar->queue_stop_timeout[i]) : 0, + jiffies_to_msecs(ar->max_queue_stop_timeout[i])); + + ar->max_queue_stop_timeout[i] = 0; + } + + return buf; +} +DEBUGFS_DECLARE_RO_FILE(tx_stuck, 180); + +static char *carl9170_debugfs_phy_noise_read(struct ar9170 *ar, char *buf, + size_t bufsize, ssize_t *len) +{ + int err; + + err = carl9170_get_noisefloor(ar); + if (err) { + *len = err; + return buf; + } + + ADD(buf, *len, bufsize, "Chain 0: %10d dBm, ext. chan.:%10d dBm\n", + ar->noise[0], ar->noise[2]); + ADD(buf, *len, bufsize, "Chain 2: %10d dBm, ext. chan.:%10d dBm\n", + ar->noise[1], ar->noise[3]); + + return buf; +} +DEBUGFS_DECLARE_RO_FILE(phy_noise, 180); + +static char *carl9170_debugfs_vif_dump_read(struct ar9170 *ar, char *buf, + size_t bufsize, ssize_t *len) +{ + struct carl9170_vif_info *iter; + int i = 0; + + ADD(buf, *len, bufsize, "registered VIFs:%d \\ %d\n", + ar->vifs, ar->fw.vif_num); + + ADD(buf, *len, bufsize, "VIF bitmap: ["); + + *len += bitmap_scnprintf(&buf[*len], bufsize - *len, + &ar->vif_bitmap, ar->fw.vif_num); + + ADD(buf, *len, bufsize, "]\n"); + + rcu_read_lock(); + list_for_each_entry_rcu(iter, &ar->vif_list, list) { + struct ieee80211_vif *vif = carl9170_get_vif(iter); + ADD(buf, *len, bufsize, "\t%d = [%s VIF, id:%d, type:%x " + " mac:%pM %s]\n", i, (carl9170_get_main_vif(ar) == vif ? + "Master" : " Slave"), iter->id, vif->type, vif->addr, + iter->enable_beacon ? "beaconing " : ""); + i++; + } + rcu_read_unlock(); + + return buf; +} +DEBUGFS_DECLARE_RO_FILE(vif_dump, 8000); + +#define UPDATE_COUNTER(ar, name) ({ \ + u32 __tmp[ARRAY_SIZE(name##_regs)]; \ + unsigned int __i, __err = -ENODEV; \ + \ + for (__i = 0; __i < ARRAY_SIZE(name##_regs); __i++) { \ + __tmp[__i] = name##_regs[__i].reg; \ + ar->debug.stats.name##_counter[__i] = 0; \ + } \ + \ + if (IS_STARTED(ar)) \ + __err = carl9170_read_mreg(ar, ARRAY_SIZE(name##_regs), \ + __tmp, ar->debug.stats.name##_counter); \ + (__err); }) + +#define TALLY_SUM_UP(ar, name) do { \ + unsigned int __i; \ + \ + for (__i = 0; __i < ARRAY_SIZE(name##_regs); __i++) { \ + ar->debug.stats.name##_sum[__i] += \ + ar->debug.stats.name##_counter[__i]; \ + } \ +} while (0) + +#define DEBUGFS_HW_TALLY_FILE(name, f) \ +static char *carl9170_debugfs_##name ## _read(struct ar9170 *ar, \ + char *dum, size_t bufsize, ssize_t *ret) \ +{ \ + char *buf; \ + int i, max_len, err; \ + \ + max_len = ARRAY_SIZE(name##_regs) * 80; \ + buf = vmalloc(max_len); \ + if (!buf) \ + return NULL; \ + \ + err = UPDATE_COUNTER(ar, name); \ + if (err) { \ + *ret = err; \ + return buf; \ + } \ + \ + TALLY_SUM_UP(ar, name); \ + \ + for (i = 0; i < ARRAY_SIZE(name##_regs); i++) { \ + ADD(buf, *ret, max_len, "%22s = %" f "[+%" f "]\n", \ + name##_regs[i].nreg, ar->debug.stats.name ##_sum[i],\ + ar->debug.stats.name ##_counter[i]); \ + } \ + \ + return buf; \ +} \ +DEBUGFS_DECLARE_RO_FILE(name, 0); + +#define DEBUGFS_HW_REG_FILE(name, f) \ +static char *carl9170_debugfs_##name ## _read(struct ar9170 *ar, \ + char *dum, size_t bufsize, ssize_t *ret) \ +{ \ + char *buf; \ + int i, max_len, err; \ + \ + max_len = ARRAY_SIZE(name##_regs) * 80; \ + buf = vmalloc(max_len); \ + if (!buf) \ + return NULL; \ + \ + err = UPDATE_COUNTER(ar, name); \ + if (err) { \ + *ret = err; \ + return buf; \ + } \ + \ + for (i = 0; i < ARRAY_SIZE(name##_regs); i++) { \ + ADD(buf, *ret, max_len, "%22s = %" f "\n", \ + name##_regs[i].nreg, \ + ar->debug.stats.name##_counter[i]); \ + } \ + \ + return buf; \ +} \ +DEBUGFS_DECLARE_RO_FILE(name, 0); + +static ssize_t carl9170_debugfs_hw_ioread32_write(struct ar9170 *ar, + const char *buf, size_t count) +{ + int err = 0, i, n = 0, max_len = 32, res; + unsigned int reg, tmp; + + if (!count) + return 0; + + if (count > max_len) + return -E2BIG; + + res = sscanf(buf, "0x%X %d", ®, &n); + if (res < 1) { + err = -EINVAL; + goto out; + } + + if (res == 1) + n = 1; + + if (n > 15) { + err = -EMSGSIZE; + goto out; + } + + if ((reg >= 0x280000) || ((reg + (n << 2)) >= 0x280000)) { + err = -EADDRNOTAVAIL; + goto out; + } + + if (reg & 3) { + err = -EINVAL; + goto out; + } + + for (i = 0; i < n; i++) { + err = carl9170_read_reg(ar, reg + (i << 2), &tmp); + if (err) + goto out; + + ar->debug.ring[ar->debug.ring_tail].reg = reg + (i << 2); + ar->debug.ring[ar->debug.ring_tail].value = tmp; + ar->debug.ring_tail++; + ar->debug.ring_tail %= CARL9170_DEBUG_RING_SIZE; + } + +out: + return err ? err : count; +} + +static char *carl9170_debugfs_hw_ioread32_read(struct ar9170 *ar, char *buf, + size_t bufsize, ssize_t *ret) +{ + int i = 0; + + while (ar->debug.ring_head != ar->debug.ring_tail) { + ADD(buf, *ret, bufsize, "%.8x = %.8x\n", + ar->debug.ring[ar->debug.ring_head].reg, + ar->debug.ring[ar->debug.ring_head].value); + + ar->debug.ring_head++; + ar->debug.ring_head %= CARL9170_DEBUG_RING_SIZE; + + if (i++ == 64) + break; + } + ar->debug.ring_head = ar->debug.ring_tail; + return buf; +} +DEBUGFS_DECLARE_RW_FILE(hw_ioread32, CARL9170_DEBUG_RING_SIZE * 40); + +static ssize_t carl9170_debugfs_bug_write(struct ar9170 *ar, const char *buf, + size_t count) +{ + int err; + + if (count < 1) + return -EINVAL; + + switch (buf[0]) { + case 'F': + ar->needs_full_reset = true; + break; + + case 'R': + if (!IS_STARTED(ar)) { + err = -EAGAIN; + goto out; + } + + ar->needs_full_reset = false; + break; + + case 'M': + err = carl9170_mac_reset(ar); + if (err < 0) + count = err; + + goto out; + + case 'P': + err = carl9170_set_channel(ar, ar->hw->conf.chandef.chan, + cfg80211_get_chandef_type(&ar->hw->conf.chandef)); + if (err < 0) + count = err; + + goto out; + + default: + return -EINVAL; + } + + carl9170_restart(ar, CARL9170_RR_USER_REQUEST); + +out: + return count; +} + +static char *carl9170_debugfs_bug_read(struct ar9170 *ar, char *buf, + size_t bufsize, ssize_t *ret) +{ + ADD(buf, *ret, bufsize, "[P]hy reinit, [R]estart, [F]ull usb reset, " + "[M]ac reset\n"); + ADD(buf, *ret, bufsize, "firmware restarts:%d, last reason:%d\n", + ar->restart_counter, ar->last_reason); + ADD(buf, *ret, bufsize, "phy reinit errors:%d (%d)\n", + ar->total_chan_fail, ar->chan_fail); + ADD(buf, *ret, bufsize, "reported firmware errors:%d\n", + ar->fw.err_counter); + ADD(buf, *ret, bufsize, "reported firmware BUGs:%d\n", + ar->fw.bug_counter); + ADD(buf, *ret, bufsize, "pending restart requests:%d\n", + atomic_read(&ar->pending_restarts)); + return buf; +} +__DEBUGFS_DECLARE_RW_FILE(bug, 400, CARL9170_STOPPED); + +static const char *const erp_modes[] = { + [CARL9170_ERP_INVALID] = "INVALID", + [CARL9170_ERP_AUTO] = "Automatic", + [CARL9170_ERP_MAC80211] = "Set by MAC80211", + [CARL9170_ERP_OFF] = "Force Off", + [CARL9170_ERP_RTS] = "Force RTS", + [CARL9170_ERP_CTS] = "Force CTS" +}; + +static char *carl9170_debugfs_erp_read(struct ar9170 *ar, char *buf, + size_t bufsize, ssize_t *ret) +{ + ADD(buf, *ret, bufsize, "ERP Setting: (%d) -> %s\n", ar->erp_mode, + erp_modes[ar->erp_mode]); + return buf; +} + +static ssize_t carl9170_debugfs_erp_write(struct ar9170 *ar, const char *buf, + size_t count) +{ + int res, val; + + if (count < 1) + return -EINVAL; + + res = sscanf(buf, "%d", &val); + if (res != 1) + return -EINVAL; + + if (!((val > CARL9170_ERP_INVALID) && + (val < __CARL9170_ERP_NUM))) + return -EINVAL; + + ar->erp_mode = val; + return count; +} + +DEBUGFS_DECLARE_RW_FILE(erp, 80); + +static ssize_t carl9170_debugfs_hw_iowrite32_write(struct ar9170 *ar, + const char *buf, size_t count) +{ + int err = 0, max_len = 22, res; + u32 reg, val; + + if (!count) + return 0; + + if (count > max_len) + return -E2BIG; + + res = sscanf(buf, "0x%X 0x%X", ®, &val); + if (res != 2) { + err = -EINVAL; + goto out; + } + + if (reg <= 0x100000 || reg >= 0x280000) { + err = -EADDRNOTAVAIL; + goto out; + } + + if (reg & 3) { + err = -EINVAL; + goto out; + } + + err = carl9170_write_reg(ar, reg, val); + if (err) + goto out; + +out: + return err ? err : count; +} +DEBUGFS_DECLARE_WO_FILE(hw_iowrite32); + +DEBUGFS_HW_TALLY_FILE(hw_tx_tally, "u"); +DEBUGFS_HW_TALLY_FILE(hw_rx_tally, "u"); +DEBUGFS_HW_TALLY_FILE(hw_phy_errors, "u"); +DEBUGFS_HW_REG_FILE(hw_wlan_queue, ".8x"); +DEBUGFS_HW_REG_FILE(hw_pta_queue, ".8x"); +DEBUGFS_HW_REG_FILE(hw_ampdu_info, ".8x"); +DEBUGFS_QUEUE_DUMP(tx_status, 0); +DEBUGFS_QUEUE_DUMP(tx_status, 1); +DEBUGFS_QUEUE_DUMP(tx_status, 2); +DEBUGFS_QUEUE_DUMP(tx_status, 3); +DEBUGFS_QUEUE_DUMP(tx_pending, 0); +DEBUGFS_QUEUE_DUMP(tx_pending, 1); +DEBUGFS_QUEUE_DUMP(tx_pending, 2); +DEBUGFS_QUEUE_DUMP(tx_pending, 3); +DEBUGFS_READONLY_FILE(usb_tx_anch_urbs, 20, "%d", + atomic_read(&ar->tx_anch_urbs)); +DEBUGFS_READONLY_FILE(usb_rx_anch_urbs, 20, "%d", + atomic_read(&ar->rx_anch_urbs)); +DEBUGFS_READONLY_FILE(usb_rx_work_urbs, 20, "%d", + atomic_read(&ar->rx_work_urbs)); +DEBUGFS_READONLY_FILE(usb_rx_pool_urbs, 20, "%d", + atomic_read(&ar->rx_pool_urbs)); + +DEBUGFS_READONLY_FILE(tx_total_queued, 20, "%d", + atomic_read(&ar->tx_total_queued)); +DEBUGFS_READONLY_FILE(tx_ampdu_scheduler, 20, "%d", + atomic_read(&ar->tx_ampdu_scheduler)); + +DEBUGFS_READONLY_FILE(tx_total_pending, 20, "%d", + atomic_read(&ar->tx_total_pending)); + +DEBUGFS_READONLY_FILE(tx_ampdu_list_len, 20, "%d", + ar->tx_ampdu_list_len); + +DEBUGFS_READONLY_FILE(tx_ampdu_upload, 20, "%d", + atomic_read(&ar->tx_ampdu_upload)); + +DEBUGFS_READONLY_FILE(tx_janitor_last_run, 64, "last run:%d ms ago", + jiffies_to_msecs(jiffies - ar->tx_janitor_last_run)); + +DEBUGFS_READONLY_FILE(tx_dropped, 20, "%d", ar->tx_dropped); + +DEBUGFS_READONLY_FILE(rx_dropped, 20, "%d", ar->rx_dropped); + +DEBUGFS_READONLY_FILE(sniffer_enabled, 20, "%d", ar->sniffer_enabled); +DEBUGFS_READONLY_FILE(rx_software_decryption, 20, "%d", + ar->rx_software_decryption); +DEBUGFS_READONLY_FILE(ampdu_factor, 20, "%d", + ar->current_factor); +DEBUGFS_READONLY_FILE(ampdu_density, 20, "%d", + ar->current_density); + +DEBUGFS_READONLY_FILE(beacon_int, 20, "%d TU", ar->global_beacon_int); +DEBUGFS_READONLY_FILE(pretbtt, 20, "%d TU", ar->global_pretbtt); + +void carl9170_debugfs_register(struct ar9170 *ar) +{ + ar->debug_dir = debugfs_create_dir(KBUILD_MODNAME, + ar->hw->wiphy->debugfsdir); + +#define DEBUGFS_ADD(name) \ + debugfs_create_file(#name, carl_debugfs_##name ##_ops.attr, \ + ar->debug_dir, ar, \ + &carl_debugfs_##name ## _ops.fops); + + DEBUGFS_ADD(usb_tx_anch_urbs); + DEBUGFS_ADD(usb_rx_pool_urbs); + DEBUGFS_ADD(usb_rx_anch_urbs); + DEBUGFS_ADD(usb_rx_work_urbs); + + DEBUGFS_ADD(tx_total_queued); + DEBUGFS_ADD(tx_total_pending); + DEBUGFS_ADD(tx_dropped); + DEBUGFS_ADD(tx_stuck); + DEBUGFS_ADD(tx_ampdu_upload); + DEBUGFS_ADD(tx_ampdu_scheduler); + DEBUGFS_ADD(tx_ampdu_list_len); + + DEBUGFS_ADD(rx_dropped); + DEBUGFS_ADD(sniffer_enabled); + DEBUGFS_ADD(rx_software_decryption); + + DEBUGFS_ADD(mem_usage); + DEBUGFS_ADD(qos_stat); + DEBUGFS_ADD(sta_psm); + DEBUGFS_ADD(ampdu_state); + + DEBUGFS_ADD(hw_tx_tally); + DEBUGFS_ADD(hw_rx_tally); + DEBUGFS_ADD(hw_phy_errors); + DEBUGFS_ADD(phy_noise); + + DEBUGFS_ADD(hw_wlan_queue); + DEBUGFS_ADD(hw_pta_queue); + DEBUGFS_ADD(hw_ampdu_info); + + DEBUGFS_ADD(ampdu_density); + DEBUGFS_ADD(ampdu_factor); + + DEBUGFS_ADD(tx_janitor_last_run); + + DEBUGFS_ADD(tx_status_0); + DEBUGFS_ADD(tx_status_1); + DEBUGFS_ADD(tx_status_2); + DEBUGFS_ADD(tx_status_3); + + DEBUGFS_ADD(tx_pending_0); + DEBUGFS_ADD(tx_pending_1); + DEBUGFS_ADD(tx_pending_2); + DEBUGFS_ADD(tx_pending_3); + + DEBUGFS_ADD(hw_ioread32); + DEBUGFS_ADD(hw_iowrite32); + DEBUGFS_ADD(bug); + + DEBUGFS_ADD(erp); + + DEBUGFS_ADD(vif_dump); + + DEBUGFS_ADD(beacon_int); + DEBUGFS_ADD(pretbtt); + +#undef DEBUGFS_ADD +} + +void carl9170_debugfs_unregister(struct ar9170 *ar) +{ + debugfs_remove_recursive(ar->debug_dir); +} diff --git a/drivers/net/wireless/ath/carl9170/debug.h b/drivers/net/wireless/ath/carl9170/debug.h new file mode 100644 index 0000000..ea4b975 --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/debug.h @@ -0,0 +1,134 @@ +/* + * Atheros CARL9170 driver + * + * debug header + * + * Copyright 2010, Christian Lamparter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * Copyright (c) 2007-2008 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef __DEBUG_H +#define __DEBUG_H + +#include "eeprom.h" +#include "wlan.h" +#include "hw.h" +#include "fwdesc.h" +#include "fwcmd.h" +#include "../regd.h" + +struct hw_stat_reg_entry { + u32 reg; + char nreg[32]; +}; + +#define STAT_MAC_REG(reg) \ + { (AR9170_MAC_REG_##reg), #reg } + +#define STAT_PTA_REG(reg) \ + { (AR9170_PTA_REG_##reg), #reg } + +#define STAT_USB_REG(reg) \ + { (AR9170_USB_REG_##reg), #reg } + +static const struct hw_stat_reg_entry hw_rx_tally_regs[] = { + STAT_MAC_REG(RX_CRC32), STAT_MAC_REG(RX_CRC16), + STAT_MAC_REG(RX_TIMEOUT_COUNT), STAT_MAC_REG(RX_ERR_DECRYPTION_UNI), + STAT_MAC_REG(RX_ERR_DECRYPTION_MUL), STAT_MAC_REG(RX_MPDU), + STAT_MAC_REG(RX_DROPPED_MPDU), STAT_MAC_REG(RX_DEL_MPDU), +}; + +static const struct hw_stat_reg_entry hw_phy_errors_regs[] = { + STAT_MAC_REG(RX_PHY_MISC_ERROR), STAT_MAC_REG(RX_PHY_XR_ERROR), + STAT_MAC_REG(RX_PHY_OFDM_ERROR), STAT_MAC_REG(RX_PHY_CCK_ERROR), + STAT_MAC_REG(RX_PHY_HT_ERROR), STAT_MAC_REG(RX_PHY_TOTAL), +}; + +static const struct hw_stat_reg_entry hw_tx_tally_regs[] = { + STAT_MAC_REG(TX_TOTAL), STAT_MAC_REG(TX_UNDERRUN), + STAT_MAC_REG(TX_RETRY), +}; + +static const struct hw_stat_reg_entry hw_wlan_queue_regs[] = { + STAT_MAC_REG(DMA_STATUS), STAT_MAC_REG(DMA_TRIGGER), + STAT_MAC_REG(DMA_TXQ0_ADDR), STAT_MAC_REG(DMA_TXQ0_CURR_ADDR), + STAT_MAC_REG(DMA_TXQ1_ADDR), STAT_MAC_REG(DMA_TXQ1_CURR_ADDR), + STAT_MAC_REG(DMA_TXQ2_ADDR), STAT_MAC_REG(DMA_TXQ2_CURR_ADDR), + STAT_MAC_REG(DMA_TXQ3_ADDR), STAT_MAC_REG(DMA_TXQ3_CURR_ADDR), + STAT_MAC_REG(DMA_RXQ_ADDR), STAT_MAC_REG(DMA_RXQ_CURR_ADDR), +}; + +static const struct hw_stat_reg_entry hw_ampdu_info_regs[] = { + STAT_MAC_REG(AMPDU_DENSITY), STAT_MAC_REG(AMPDU_FACTOR), +}; + +static const struct hw_stat_reg_entry hw_pta_queue_regs[] = { + STAT_PTA_REG(DN_CURR_ADDRH), STAT_PTA_REG(DN_CURR_ADDRL), + STAT_PTA_REG(UP_CURR_ADDRH), STAT_PTA_REG(UP_CURR_ADDRL), + STAT_PTA_REG(DMA_STATUS), STAT_PTA_REG(DMA_MODE_CTRL), +}; + +#define DEFINE_TALLY(name) \ + u32 name##_sum[ARRAY_SIZE(name##_regs)], \ + name##_counter[ARRAY_SIZE(name##_regs)] \ + +#define DEFINE_STAT(name) \ + u32 name##_counter[ARRAY_SIZE(name##_regs)] \ + +struct ath_stats { + DEFINE_TALLY(hw_tx_tally); + DEFINE_TALLY(hw_rx_tally); + DEFINE_TALLY(hw_phy_errors); + DEFINE_STAT(hw_wlan_queue); + DEFINE_STAT(hw_pta_queue); + DEFINE_STAT(hw_ampdu_info); +}; + +struct carl9170_debug_mem_rbe { + u32 reg; + u32 value; +}; + +#define CARL9170_DEBUG_RING_SIZE 64 + +struct carl9170_debug { + struct ath_stats stats; + struct carl9170_debug_mem_rbe ring[CARL9170_DEBUG_RING_SIZE]; + struct mutex ring_lock; + unsigned int ring_head, ring_tail; + struct delayed_work update_tally; +}; + +struct ar9170; + +void carl9170_debugfs_register(struct ar9170 *ar); +void carl9170_debugfs_unregister(struct ar9170 *ar); +#endif /* __DEBUG_H */ diff --git a/drivers/net/wireless/ath/carl9170/eeprom.h b/drivers/net/wireless/ath/carl9170/eeprom.h new file mode 100644 index 0000000..7cff40a --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/eeprom.h @@ -0,0 +1,216 @@ +/* + * Shared Atheros AR9170 Header + * + * EEPROM layout + * + * Copyright 2008, Johannes Berg + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * Copyright (c) 2007-2008 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef __CARL9170_SHARED_EEPROM_H +#define __CARL9170_SHARED_EEPROM_H + +#define AR9170_EEPROM_START 0x1600 + +#define AR5416_MAX_CHAINS 2 +#define AR5416_MODAL_SPURS 5 + +struct ar9170_eeprom_modal { + __le32 antCtrlChain[AR5416_MAX_CHAINS]; + __le32 antCtrlCommon; + s8 antennaGainCh[AR5416_MAX_CHAINS]; + u8 switchSettling; + u8 txRxAttenCh[AR5416_MAX_CHAINS]; + u8 rxTxMarginCh[AR5416_MAX_CHAINS]; + s8 adcDesiredSize; + s8 pgaDesiredSize; + u8 xlnaGainCh[AR5416_MAX_CHAINS]; + u8 txEndToXpaOff; + u8 txEndToRxOn; + u8 txFrameToXpaOn; + u8 thresh62; + s8 noiseFloorThreshCh[AR5416_MAX_CHAINS]; + u8 xpdGain; + u8 xpd; + s8 iqCalICh[AR5416_MAX_CHAINS]; + s8 iqCalQCh[AR5416_MAX_CHAINS]; + u8 pdGainOverlap; + u8 ob; + u8 db; + u8 xpaBiasLvl; + u8 pwrDecreaseFor2Chain; + u8 pwrDecreaseFor3Chain; + u8 txFrameToDataStart; + u8 txFrameToPaOn; + u8 ht40PowerIncForPdadc; + u8 bswAtten[AR5416_MAX_CHAINS]; + u8 bswMargin[AR5416_MAX_CHAINS]; + u8 swSettleHt40; + u8 reserved[22]; + struct spur_channel { + __le16 spurChan; + u8 spurRangeLow; + u8 spurRangeHigh; + } __packed spur_channels[AR5416_MODAL_SPURS]; +} __packed; + +#define AR5416_NUM_PD_GAINS 4 +#define AR5416_PD_GAIN_ICEPTS 5 + +struct ar9170_calibration_data_per_freq { + u8 pwr_pdg[AR5416_NUM_PD_GAINS][AR5416_PD_GAIN_ICEPTS]; + u8 vpd_pdg[AR5416_NUM_PD_GAINS][AR5416_PD_GAIN_ICEPTS]; +} __packed; + +#define AR5416_NUM_5G_CAL_PIERS 8 +#define AR5416_NUM_2G_CAL_PIERS 4 + +#define AR5416_NUM_5G_TARGET_PWRS 8 +#define AR5416_NUM_2G_CCK_TARGET_PWRS 3 +#define AR5416_NUM_2G_OFDM_TARGET_PWRS 4 +#define AR5416_MAX_NUM_TGT_PWRS 8 + +struct ar9170_calibration_target_power_legacy { + u8 freq; + u8 power[4]; +} __packed; + +struct ar9170_calibration_target_power_ht { + u8 freq; + u8 power[8]; +} __packed; + +#define AR5416_NUM_CTLS 24 + +struct ar9170_calctl_edges { + u8 channel; +#define AR9170_CALCTL_EDGE_FLAGS 0xC0 + u8 power_flags; +} __packed; + +#define AR5416_NUM_BAND_EDGES 8 + +struct ar9170_calctl_data { + struct ar9170_calctl_edges + control_edges[AR5416_MAX_CHAINS][AR5416_NUM_BAND_EDGES]; +} __packed; + +struct ar9170_eeprom { + __le16 length; + __le16 checksum; + __le16 version; + u8 operating_flags; +#define AR9170_OPFLAG_5GHZ 1 +#define AR9170_OPFLAG_2GHZ 2 + u8 misc; + __le16 reg_domain[2]; + u8 mac_address[6]; + u8 rx_mask; + u8 tx_mask; + __le16 rf_silent; + __le16 bluetooth_options; + __le16 device_capabilities; + __le32 build_number; + u8 deviceType; + u8 reserved[33]; + + u8 customer_data[64]; + + struct ar9170_eeprom_modal + modal_header[2]; + + u8 cal_freq_pier_5G[AR5416_NUM_5G_CAL_PIERS]; + u8 cal_freq_pier_2G[AR5416_NUM_2G_CAL_PIERS]; + + struct ar9170_calibration_data_per_freq + cal_pier_data_5G[AR5416_MAX_CHAINS][AR5416_NUM_5G_CAL_PIERS], + cal_pier_data_2G[AR5416_MAX_CHAINS][AR5416_NUM_2G_CAL_PIERS]; + + /* power calibration data */ + struct ar9170_calibration_target_power_legacy + cal_tgt_pwr_5G[AR5416_NUM_5G_TARGET_PWRS]; + struct ar9170_calibration_target_power_ht + cal_tgt_pwr_5G_ht20[AR5416_NUM_5G_TARGET_PWRS], + cal_tgt_pwr_5G_ht40[AR5416_NUM_5G_TARGET_PWRS]; + + struct ar9170_calibration_target_power_legacy + cal_tgt_pwr_2G_cck[AR5416_NUM_2G_CCK_TARGET_PWRS], + cal_tgt_pwr_2G_ofdm[AR5416_NUM_2G_OFDM_TARGET_PWRS]; + struct ar9170_calibration_target_power_ht + cal_tgt_pwr_2G_ht20[AR5416_NUM_2G_OFDM_TARGET_PWRS], + cal_tgt_pwr_2G_ht40[AR5416_NUM_2G_OFDM_TARGET_PWRS]; + + /* conformance testing limits */ + u8 ctl_index[AR5416_NUM_CTLS]; + struct ar9170_calctl_data + ctl_data[AR5416_NUM_CTLS]; + + u8 pad; + __le16 subsystem_id; +} __packed; + +#define AR9170_LED_MODE_POWER_ON 0x0001 +#define AR9170_LED_MODE_RESERVED 0x0002 +#define AR9170_LED_MODE_DISABLE_STATE 0x0004 +#define AR9170_LED_MODE_OFF_IN_PSM 0x0008 + +/* AR9170_LED_MODE BIT is set */ +#define AR9170_LED_MODE_FREQUENCY_S 4 +#define AR9170_LED_MODE_FREQUENCY 0x0030 +#define AR9170_LED_MODE_FREQUENCY_1HZ 0x0000 +#define AR9170_LED_MODE_FREQUENCY_0_5HZ 0x0010 +#define AR9170_LED_MODE_FREQUENCY_0_25HZ 0x0020 +#define AR9170_LED_MODE_FREQUENCY_0_125HZ 0x0030 + +/* AR9170_LED_MODE BIT is not set */ +#define AR9170_LED_MODE_CONN_STATE_S 4 +#define AR9170_LED_MODE_CONN_STATE 0x0030 +#define AR9170_LED_MODE_CONN_STATE_FORCE_OFF 0x0000 +#define AR9170_LED_MODE_CONN_STATE_FORCE_ON 0x0010 +/* Idle off / Active on */ +#define AR9170_LED_MODE_CONN_STATE_IOFF_AON 0x0020 +/* Idle on / Active off */ +#define AR9170_LED_MODE_CONN_STATE_ION_AOFF 0x0010 + +#define AR9170_LED_MODE_MODE 0x0040 +#define AR9170_LED_MODE_RESERVED2 0x0080 + +#define AR9170_LED_MODE_TON_SCAN_S 8 +#define AR9170_LED_MODE_TON_SCAN 0x0f00 + +#define AR9170_LED_MODE_TOFF_SCAN_S 12 +#define AR9170_LED_MODE_TOFF_SCAN 0xf000 + +struct ar9170_led_mode { + __le16 led; +}; + +#endif /* __CARL9170_SHARED_EEPROM_H */ diff --git a/drivers/net/wireless/ath/carl9170/fw.c b/drivers/net/wireless/ath/carl9170/fw.c new file mode 100644 index 0000000..09fd622 --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/fw.c @@ -0,0 +1,447 @@ +/* + * Atheros CARL9170 driver + * + * firmware parser + * + * Copyright 2009, 2010, Christian Lamparter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + */ + +#include +#include +#include +#include +#include "carl9170.h" +#include "fwcmd.h" +#include "version.h" + +static const u8 otus_magic[4] = { OTUS_MAGIC }; + +static const void *carl9170_fw_find_desc(struct ar9170 *ar, const u8 descid[4], + const unsigned int len, const u8 compatible_revision) +{ + const struct carl9170fw_desc_head *iter; + + carl9170fw_for_each_hdr(iter, ar->fw.desc) { + if (carl9170fw_desc_cmp(iter, descid, len, + compatible_revision)) + return (void *)iter; + } + + /* needed to find the LAST desc */ + if (carl9170fw_desc_cmp(iter, descid, len, + compatible_revision)) + return (void *)iter; + + return NULL; +} + +static int carl9170_fw_verify_descs(struct ar9170 *ar, + const struct carl9170fw_desc_head *head, unsigned int max_len) +{ + const struct carl9170fw_desc_head *pos; + unsigned long pos_addr, end_addr; + unsigned int pos_length; + + if (max_len < sizeof(*pos)) + return -ENODATA; + + max_len = min_t(unsigned int, CARL9170FW_DESC_MAX_LENGTH, max_len); + + pos = head; + pos_addr = (unsigned long) pos; + end_addr = pos_addr + max_len; + + while (pos_addr < end_addr) { + if (pos_addr + sizeof(*head) > end_addr) + return -E2BIG; + + pos_length = le16_to_cpu(pos->length); + + if (pos_length < sizeof(*head)) + return -EBADMSG; + + if (pos_length > max_len) + return -EOVERFLOW; + + if (pos_addr + pos_length > end_addr) + return -EMSGSIZE; + + if (carl9170fw_desc_cmp(pos, LAST_MAGIC, + CARL9170FW_LAST_DESC_SIZE, + CARL9170FW_LAST_DESC_CUR_VER)) + return 0; + + pos_addr += pos_length; + pos = (void *)pos_addr; + max_len -= pos_length; + } + return -EINVAL; +} + +static void carl9170_fw_info(struct ar9170 *ar) +{ + const struct carl9170fw_motd_desc *motd_desc; + unsigned int str_ver_len; + u32 fw_date; + + dev_info(&ar->udev->dev, "driver API: %s 2%03d-%02d-%02d [%d-%d]\n", + CARL9170FW_VERSION_GIT, CARL9170FW_VERSION_YEAR, + CARL9170FW_VERSION_MONTH, CARL9170FW_VERSION_DAY, + CARL9170FW_API_MIN_VER, CARL9170FW_API_MAX_VER); + + motd_desc = carl9170_fw_find_desc(ar, MOTD_MAGIC, + sizeof(*motd_desc), CARL9170FW_MOTD_DESC_CUR_VER); + + if (motd_desc) { + str_ver_len = strnlen(motd_desc->release, + CARL9170FW_MOTD_RELEASE_LEN); + + fw_date = le32_to_cpu(motd_desc->fw_year_month_day); + + dev_info(&ar->udev->dev, "firmware API: %.*s 2%03d-%02d-%02d\n", + str_ver_len, motd_desc->release, + CARL9170FW_GET_YEAR(fw_date), + CARL9170FW_GET_MONTH(fw_date), + CARL9170FW_GET_DAY(fw_date)); + + strlcpy(ar->hw->wiphy->fw_version, motd_desc->release, + sizeof(ar->hw->wiphy->fw_version)); + } +} + +static bool valid_dma_addr(const u32 address) +{ + if (address >= AR9170_SRAM_OFFSET && + address < (AR9170_SRAM_OFFSET + AR9170_SRAM_SIZE)) + return true; + + return false; +} + +static bool valid_cpu_addr(const u32 address) +{ + if (valid_dma_addr(address) || (address >= AR9170_PRAM_OFFSET && + address < (AR9170_PRAM_OFFSET + AR9170_PRAM_SIZE))) + return true; + + return false; +} + +static int carl9170_fw_checksum(struct ar9170 *ar, const __u8 *data, + size_t len) +{ + const struct carl9170fw_otus_desc *otus_desc; + const struct carl9170fw_last_desc *last_desc; + const struct carl9170fw_chk_desc *chk_desc; + unsigned long fin, diff; + unsigned int dsc_len; + u32 crc32; + + last_desc = carl9170_fw_find_desc(ar, LAST_MAGIC, + sizeof(*last_desc), CARL9170FW_LAST_DESC_CUR_VER); + if (!last_desc) + return -EINVAL; + + otus_desc = carl9170_fw_find_desc(ar, OTUS_MAGIC, + sizeof(*otus_desc), CARL9170FW_OTUS_DESC_CUR_VER); + if (!otus_desc) { + dev_err(&ar->udev->dev, "failed to find compatible firmware " + "descriptor.\n"); + return -ENODATA; + } + + chk_desc = carl9170_fw_find_desc(ar, CHK_MAGIC, + sizeof(*chk_desc), CARL9170FW_CHK_DESC_CUR_VER); + + if (!chk_desc) { + dev_warn(&ar->udev->dev, "Unprotected firmware image.\n"); + return 0; + } + + dsc_len = min_t(unsigned int, len, + (unsigned long)chk_desc - (unsigned long)otus_desc); + + fin = (unsigned long) last_desc + sizeof(*last_desc); + diff = fin - (unsigned long) otus_desc; + + if (diff < len) + len -= diff; + + if (len < 256) + return -EIO; + + crc32 = crc32_le(~0, data, len); + if (cpu_to_le32(crc32) != chk_desc->fw_crc32) { + dev_err(&ar->udev->dev, "fw checksum test failed.\n"); + return -ENOEXEC; + } + + crc32 = crc32_le(crc32, (void *)otus_desc, dsc_len); + if (cpu_to_le32(crc32) != chk_desc->hdr_crc32) { + dev_err(&ar->udev->dev, "descriptor check failed.\n"); + return -EINVAL; + } + return 0; +} + +static int carl9170_fw_tx_sequence(struct ar9170 *ar) +{ + const struct carl9170fw_txsq_desc *txsq_desc; + + txsq_desc = carl9170_fw_find_desc(ar, TXSQ_MAGIC, sizeof(*txsq_desc), + CARL9170FW_TXSQ_DESC_CUR_VER); + if (txsq_desc) { + ar->fw.tx_seq_table = le32_to_cpu(txsq_desc->seq_table_addr); + if (!valid_cpu_addr(ar->fw.tx_seq_table)) + return -EINVAL; + } else { + ar->fw.tx_seq_table = 0; + } + + return 0; +} + +static void carl9170_fw_set_if_combinations(struct ar9170 *ar, + u16 if_comb_types) +{ + if (ar->fw.vif_num < 2) + return; + + ar->if_comb_limits[0].max = ar->fw.vif_num; + ar->if_comb_limits[0].types = if_comb_types; + + ar->if_combs[0].num_different_channels = 1; + ar->if_combs[0].max_interfaces = ar->fw.vif_num; + ar->if_combs[0].limits = ar->if_comb_limits; + ar->if_combs[0].n_limits = ARRAY_SIZE(ar->if_comb_limits); + + ar->hw->wiphy->iface_combinations = ar->if_combs; + ar->hw->wiphy->n_iface_combinations = ARRAY_SIZE(ar->if_combs); +} + +static int carl9170_fw(struct ar9170 *ar, const __u8 *data, size_t len) +{ + const struct carl9170fw_otus_desc *otus_desc; + int err; + u16 if_comb_types; + + err = carl9170_fw_checksum(ar, data, len); + if (err) + return err; + + otus_desc = carl9170_fw_find_desc(ar, OTUS_MAGIC, + sizeof(*otus_desc), CARL9170FW_OTUS_DESC_CUR_VER); + if (!otus_desc) { + return -ENODATA; + } + +#define SUPP(feat) \ + (carl9170fw_supports(otus_desc->feature_set, feat)) + + if (!SUPP(CARL9170FW_DUMMY_FEATURE)) { + dev_err(&ar->udev->dev, "invalid firmware descriptor " + "format detected.\n"); + return -EINVAL; + } + + ar->fw.api_version = otus_desc->api_ver; + + if (ar->fw.api_version < CARL9170FW_API_MIN_VER || + ar->fw.api_version > CARL9170FW_API_MAX_VER) { + dev_err(&ar->udev->dev, "unsupported firmware api version.\n"); + return -EINVAL; + } + + if (!SUPP(CARL9170FW_COMMAND_PHY) || SUPP(CARL9170FW_UNUSABLE) || + !SUPP(CARL9170FW_HANDLE_BACK_REQ)) { + dev_err(&ar->udev->dev, "firmware does support " + "mandatory features.\n"); + return -ECANCELED; + } + + if (ilog2(le32_to_cpu(otus_desc->feature_set)) >= + __CARL9170FW_FEATURE_NUM) { + dev_warn(&ar->udev->dev, "driver does not support all " + "firmware features.\n"); + } + + if (!SUPP(CARL9170FW_COMMAND_CAM)) { + dev_info(&ar->udev->dev, "crypto offloading is disabled " + "by firmware.\n"); + ar->fw.disable_offload_fw = true; + } + + if (SUPP(CARL9170FW_PSM) && SUPP(CARL9170FW_FIXED_5GHZ_PSM)) + ar->hw->flags |= IEEE80211_HW_SUPPORTS_PS; + + if (!SUPP(CARL9170FW_USB_INIT_FIRMWARE)) { + dev_err(&ar->udev->dev, "firmware does not provide " + "mandatory interfaces.\n"); + return -EINVAL; + } + + if (SUPP(CARL9170FW_MINIBOOT)) + ar->fw.offset = le16_to_cpu(otus_desc->miniboot_size); + else + ar->fw.offset = 0; + + if (SUPP(CARL9170FW_USB_DOWN_STREAM)) { + ar->hw->extra_tx_headroom += sizeof(struct ar9170_stream); + ar->fw.tx_stream = true; + } + + if (SUPP(CARL9170FW_USB_UP_STREAM)) + ar->fw.rx_stream = true; + + if (SUPP(CARL9170FW_RX_FILTER)) { + ar->fw.rx_filter = true; + ar->rx_filter_caps = FIF_FCSFAIL | FIF_PLCPFAIL | + FIF_CONTROL | FIF_PSPOLL | FIF_OTHER_BSS | + FIF_PROMISC_IN_BSS; + } + + if (SUPP(CARL9170FW_HW_COUNTERS)) + ar->fw.hw_counters = true; + + if (SUPP(CARL9170FW_WOL)) + device_set_wakeup_enable(&ar->udev->dev, true); + + if (SUPP(CARL9170FW_RX_BA_FILTER)) + ar->fw.ba_filter = true; + + if_comb_types = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_P2P_CLIENT); + + ar->fw.vif_num = otus_desc->vif_num; + ar->fw.cmd_bufs = otus_desc->cmd_bufs; + ar->fw.address = le32_to_cpu(otus_desc->fw_address); + ar->fw.rx_size = le16_to_cpu(otus_desc->rx_max_frame_len); + ar->fw.mem_blocks = min_t(unsigned int, otus_desc->tx_descs, 0xfe); + atomic_set(&ar->mem_free_blocks, ar->fw.mem_blocks); + ar->fw.mem_block_size = le16_to_cpu(otus_desc->tx_frag_len); + + if (ar->fw.vif_num >= AR9170_MAX_VIRTUAL_MAC || !ar->fw.vif_num || + ar->fw.mem_blocks < 16 || !ar->fw.cmd_bufs || + ar->fw.mem_block_size < 64 || ar->fw.mem_block_size > 512 || + ar->fw.rx_size > 32768 || ar->fw.rx_size < 4096 || + !valid_cpu_addr(ar->fw.address)) { + dev_err(&ar->udev->dev, "firmware shows obvious signs of " + "malicious tampering.\n"); + return -EINVAL; + } + + ar->fw.beacon_addr = le32_to_cpu(otus_desc->bcn_addr); + ar->fw.beacon_max_len = le16_to_cpu(otus_desc->bcn_len); + + if (valid_dma_addr(ar->fw.beacon_addr) && ar->fw.beacon_max_len >= + AR9170_MAC_BCN_LENGTH_MAX) { + ar->hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_ADHOC); + + if (SUPP(CARL9170FW_WLANTX_CAB)) { + if_comb_types |= + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_GO); + +#ifdef CPTCFG_MAC80211_MESH + if_comb_types |= + BIT(NL80211_IFTYPE_MESH_POINT); +#endif /* CPTCFG_MAC80211_MESH */ + } + } + + carl9170_fw_set_if_combinations(ar, if_comb_types); + + ar->hw->wiphy->interface_modes |= if_comb_types; + + ar->hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + + /* As IBSS Encryption is software-based, IBSS RSN is supported. */ + ar->hw->wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL | + WIPHY_FLAG_IBSS_RSN | WIPHY_FLAG_SUPPORTS_TDLS; + +#undef SUPPORTED + return carl9170_fw_tx_sequence(ar); +} + +static struct carl9170fw_desc_head * +carl9170_find_fw_desc(struct ar9170 *ar, const __u8 *fw_data, const size_t len) + +{ + int scan = 0, found = 0; + + if (!carl9170fw_size_check(len)) { + dev_err(&ar->udev->dev, "firmware size is out of bound.\n"); + return NULL; + } + + while (scan < len - sizeof(struct carl9170fw_desc_head)) { + if (fw_data[scan++] == otus_magic[found]) + found++; + else + found = 0; + + if (scan >= len) + break; + + if (found == sizeof(otus_magic)) + break; + } + + if (found != sizeof(otus_magic)) + return NULL; + + return (void *)&fw_data[scan - found]; +} + +int carl9170_parse_firmware(struct ar9170 *ar) +{ + const struct carl9170fw_desc_head *fw_desc = NULL; + const struct firmware *fw = ar->fw.fw; + unsigned long header_offset = 0; + int err; + + if (WARN_ON(!fw)) + return -EINVAL; + + fw_desc = carl9170_find_fw_desc(ar, fw->data, fw->size); + + if (!fw_desc) { + dev_err(&ar->udev->dev, "unsupported firmware.\n"); + return -ENODATA; + } + + header_offset = (unsigned long)fw_desc - (unsigned long)fw->data; + + err = carl9170_fw_verify_descs(ar, fw_desc, fw->size - header_offset); + if (err) { + dev_err(&ar->udev->dev, "damaged firmware (%d).\n", err); + return err; + } + + ar->fw.desc = fw_desc; + + carl9170_fw_info(ar); + + err = carl9170_fw(ar, fw->data, fw->size); + if (err) { + dev_err(&ar->udev->dev, "failed to parse firmware (%d).\n", + err); + return err; + } + + return 0; +} diff --git a/drivers/net/wireless/ath/carl9170/fwcmd.h b/drivers/net/wireless/ath/carl9170/fwcmd.h new file mode 100644 index 0000000..9111d4f --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/fwcmd.h @@ -0,0 +1,326 @@ +/* + * Shared Atheros AR9170 Header + * + * Firmware command interface definitions + * + * Copyright 2008, Johannes Berg + * Copyright 2009-2011 Christian Lamparter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * Copyright (c) 2007-2008 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __CARL9170_SHARED_FWCMD_H +#define __CARL9170_SHARED_FWCMD_H + +#define CARL9170_MAX_CMD_LEN 64 +#define CARL9170_MAX_CMD_PAYLOAD_LEN 60 + +#define CARL9170FW_API_MIN_VER 1 +#define CARL9170FW_API_MAX_VER 1 + +enum carl9170_cmd_oids { + CARL9170_CMD_RREG = 0x00, + CARL9170_CMD_WREG = 0x01, + CARL9170_CMD_ECHO = 0x02, + CARL9170_CMD_SWRST = 0x03, + CARL9170_CMD_REBOOT = 0x04, + CARL9170_CMD_BCN_CTRL = 0x05, + CARL9170_CMD_READ_TSF = 0x06, + CARL9170_CMD_RX_FILTER = 0x07, + CARL9170_CMD_WOL = 0x08, + CARL9170_CMD_TALLY = 0x09, + + /* CAM */ + CARL9170_CMD_EKEY = 0x10, + CARL9170_CMD_DKEY = 0x11, + + /* RF / PHY */ + CARL9170_CMD_FREQUENCY = 0x20, + CARL9170_CMD_RF_INIT = 0x21, + CARL9170_CMD_SYNTH = 0x22, + CARL9170_CMD_FREQ_START = 0x23, + CARL9170_CMD_PSM = 0x24, + + /* Asychronous command flag */ + CARL9170_CMD_ASYNC_FLAG = 0x40, + CARL9170_CMD_WREG_ASYNC = (CARL9170_CMD_WREG | + CARL9170_CMD_ASYNC_FLAG), + CARL9170_CMD_REBOOT_ASYNC = (CARL9170_CMD_REBOOT | + CARL9170_CMD_ASYNC_FLAG), + CARL9170_CMD_BCN_CTRL_ASYNC = (CARL9170_CMD_BCN_CTRL | + CARL9170_CMD_ASYNC_FLAG), + CARL9170_CMD_PSM_ASYNC = (CARL9170_CMD_PSM | + CARL9170_CMD_ASYNC_FLAG), + + /* responses and traps */ + CARL9170_RSP_FLAG = 0xc0, + CARL9170_RSP_PRETBTT = 0xc0, + CARL9170_RSP_TXCOMP = 0xc1, + CARL9170_RSP_BEACON_CONFIG = 0xc2, + CARL9170_RSP_ATIM = 0xc3, + CARL9170_RSP_WATCHDOG = 0xc6, + CARL9170_RSP_TEXT = 0xca, + CARL9170_RSP_HEXDUMP = 0xcc, + CARL9170_RSP_RADAR = 0xcd, + CARL9170_RSP_GPIO = 0xce, + CARL9170_RSP_BOOT = 0xcf, +}; + +struct carl9170_set_key_cmd { + __le16 user; + __le16 keyId; + __le16 type; + u8 macAddr[6]; + u32 key[4]; +} __packed __aligned(4); +#define CARL9170_SET_KEY_CMD_SIZE 28 + +struct carl9170_disable_key_cmd { + __le16 user; + __le16 padding; +} __packed __aligned(4); +#define CARL9170_DISABLE_KEY_CMD_SIZE 4 + +struct carl9170_u32_list { + u32 vals[0]; +} __packed; + +struct carl9170_reg_list { + __le32 regs[0]; +} __packed; + +struct carl9170_write_reg { + struct { + __le32 addr; + __le32 val; + } regs[0] __packed; +} __packed; + +#define CARL9170FW_PHY_HT_ENABLE 0x4 +#define CARL9170FW_PHY_HT_DYN2040 0x8 +#define CARL9170FW_PHY_HT_EXT_CHAN_OFF 0x3 +#define CARL9170FW_PHY_HT_EXT_CHAN_OFF_S 2 + +struct carl9170_rf_init { + __le32 freq; + u8 ht_settings; + u8 padding2[3]; + __le32 delta_slope_coeff_exp; + __le32 delta_slope_coeff_man; + __le32 delta_slope_coeff_exp_shgi; + __le32 delta_slope_coeff_man_shgi; + __le32 finiteLoopCount; +} __packed; +#define CARL9170_RF_INIT_SIZE 28 + +struct carl9170_rf_init_result { + __le32 ret; /* AR9170_PHY_REG_AGC_CONTROL */ +} __packed; +#define CARL9170_RF_INIT_RESULT_SIZE 4 + +#define CARL9170_PSM_SLEEP 0x1000 +#define CARL9170_PSM_SOFTWARE 0 +#define CARL9170_PSM_WAKE 0 /* internally used. */ +#define CARL9170_PSM_COUNTER 0xfff +#define CARL9170_PSM_COUNTER_S 0 + +struct carl9170_psm { + __le32 state; +} __packed; +#define CARL9170_PSM_SIZE 4 + +/* + * Note: If a bit in rx_filter is set, then it + * means that the particular frames which matches + * the condition are FILTERED/REMOVED/DISCARDED! + * (This is can be a bit confusing, especially + * because someone people think it's the exact + * opposite way, so watch out!) + */ +struct carl9170_rx_filter_cmd { + __le32 rx_filter; +} __packed; +#define CARL9170_RX_FILTER_CMD_SIZE 4 + +#define CARL9170_RX_FILTER_BAD 0x01 +#define CARL9170_RX_FILTER_OTHER_RA 0x02 +#define CARL9170_RX_FILTER_DECRY_FAIL 0x04 +#define CARL9170_RX_FILTER_CTL_OTHER 0x08 +#define CARL9170_RX_FILTER_CTL_PSPOLL 0x10 +#define CARL9170_RX_FILTER_CTL_BACKR 0x20 +#define CARL9170_RX_FILTER_MGMT 0x40 +#define CARL9170_RX_FILTER_DATA 0x80 +#define CARL9170_RX_FILTER_EVERYTHING (~0) + +struct carl9170_bcn_ctrl_cmd { + __le32 vif_id; + __le32 mode; + __le32 bcn_addr; + __le32 bcn_len; +} __packed; +#define CARL9170_BCN_CTRL_CMD_SIZE 16 + +#define CARL9170_BCN_CTRL_DRAIN 0 +#define CARL9170_BCN_CTRL_CAB_TRIGGER 1 + +struct carl9170_wol_cmd { + __le32 flags; + u8 mac[6]; + u8 bssid[6]; + __le32 null_interval; + __le32 free_for_use2; + __le32 mask; + u8 pattern[32]; +} __packed; + +#define CARL9170_WOL_CMD_SIZE 60 + +#define CARL9170_WOL_DISCONNECT 1 +#define CARL9170_WOL_MAGIC_PKT 2 + +struct carl9170_cmd_head { + union { + struct { + u8 len; + u8 cmd; + u8 seq; + u8 ext; + } __packed; + + u32 hdr_data; + } __packed; +} __packed; + +struct carl9170_cmd { + struct carl9170_cmd_head hdr; + union { + struct carl9170_set_key_cmd setkey; + struct carl9170_disable_key_cmd disablekey; + struct carl9170_u32_list echo; + struct carl9170_reg_list rreg; + struct carl9170_write_reg wreg; + struct carl9170_rf_init rf_init; + struct carl9170_psm psm; + struct carl9170_wol_cmd wol; + struct carl9170_bcn_ctrl_cmd bcn_ctrl; + struct carl9170_rx_filter_cmd rx_filter; + u8 data[CARL9170_MAX_CMD_PAYLOAD_LEN]; + } __packed; +} __packed __aligned(4); + +#define CARL9170_TX_STATUS_QUEUE 3 +#define CARL9170_TX_STATUS_QUEUE_S 0 +#define CARL9170_TX_STATUS_RIX_S 2 +#define CARL9170_TX_STATUS_RIX (3 << CARL9170_TX_STATUS_RIX_S) +#define CARL9170_TX_STATUS_TRIES_S 4 +#define CARL9170_TX_STATUS_TRIES (7 << CARL9170_TX_STATUS_TRIES_S) +#define CARL9170_TX_STATUS_SUCCESS 0x80 + +#ifdef __CARL9170FW__ +/* + * NOTE: + * Both structs [carl9170_tx_status and _carl9170_tx_status] + * need to be "bit for bit" in sync. + */ +struct carl9170_tx_status { + /* + * Beware of compiler bugs in all gcc pre 4.4! + */ + + u8 cookie; + u8 queue:2; + u8 rix:2; + u8 tries:3; + u8 success:1; +} __packed; +#endif /* __CARL9170FW__ */ + +struct _carl9170_tx_status { + /* + * This version should be immune to all alignment bugs. + */ + + u8 cookie; + u8 info; +} __packed; +#define CARL9170_TX_STATUS_SIZE 2 + +#define CARL9170_RSP_TX_STATUS_NUM (CARL9170_MAX_CMD_PAYLOAD_LEN / \ + sizeof(struct _carl9170_tx_status)) + +#define CARL9170_TX_MAX_RATE_TRIES 7 + +#define CARL9170_TX_MAX_RATES 4 +#define CARL9170_TX_MAX_RETRY_RATES (CARL9170_TX_MAX_RATES - 1) +#define CARL9170_ERR_MAGIC "ERR:" +#define CARL9170_BUG_MAGIC "BUG:" + +struct carl9170_gpio { + __le32 gpio; +} __packed; +#define CARL9170_GPIO_SIZE 4 + +struct carl9170_tsf_rsp { + union { + __le32 tsf[2]; + __le64 tsf_64; + } __packed; +} __packed; +#define CARL9170_TSF_RSP_SIZE 8 + +struct carl9170_tally_rsp { + __le32 active; + __le32 cca; + __le32 tx_time; + __le32 rx_total; + __le32 rx_overrun; + __le32 tick; +} __packed; + +struct carl9170_rsp { + struct carl9170_cmd_head hdr; + + union { + struct carl9170_rf_init_result rf_init_res; + struct carl9170_u32_list rreg_res; + struct carl9170_u32_list echo; +#ifdef __CARL9170FW__ + struct carl9170_tx_status tx_status[0]; +#endif /* __CARL9170FW__ */ + struct _carl9170_tx_status _tx_status[0]; + struct carl9170_gpio gpio; + struct carl9170_tsf_rsp tsf; + struct carl9170_psm psm; + struct carl9170_tally_rsp tally; + u8 data[CARL9170_MAX_CMD_PAYLOAD_LEN]; + } __packed; +} __packed __aligned(4); + +#endif /* __CARL9170_SHARED_FWCMD_H */ diff --git a/drivers/net/wireless/ath/carl9170/fwdesc.h b/drivers/net/wireless/ath/carl9170/fwdesc.h new file mode 100644 index 0000000..66848d4 --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/fwdesc.h @@ -0,0 +1,277 @@ +/* + * Shared CARL9170 Header + * + * Firmware descriptor format + * + * Copyright 2009-2011 Christian Lamparter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + */ + +#ifndef __CARL9170_SHARED_FWDESC_H +#define __CARL9170_SHARED_FWDESC_H + +/* NOTE: Don't mess with the order of the flags! */ +enum carl9170fw_feature_list { + /* Always set */ + CARL9170FW_DUMMY_FEATURE, + + /* + * Indicates that this image has special boot block which prevents + * legacy drivers to drive the firmware. + */ + CARL9170FW_MINIBOOT, + + /* usb registers are initialized by the firmware */ + CARL9170FW_USB_INIT_FIRMWARE, + + /* command traps & notifications are send through EP2 */ + CARL9170FW_USB_RESP_EP2, + + /* usb download (app -> fw) stream */ + CARL9170FW_USB_DOWN_STREAM, + + /* usb upload (fw -> app) stream */ + CARL9170FW_USB_UP_STREAM, + + /* unusable - reserved to flag non-functional debug firmwares */ + CARL9170FW_UNUSABLE, + + /* AR9170_CMD_RF_INIT, AR9170_CMD_FREQ_START, AR9170_CMD_FREQUENCY */ + CARL9170FW_COMMAND_PHY, + + /* AR9170_CMD_EKEY, AR9170_CMD_DKEY */ + CARL9170FW_COMMAND_CAM, + + /* Firmware has a software Content After Beacon Queueing mechanism */ + CARL9170FW_WLANTX_CAB, + + /* The firmware is capable of responding to incoming BAR frames */ + CARL9170FW_HANDLE_BACK_REQ, + + /* GPIO Interrupt | CARL9170_RSP_GPIO */ + CARL9170FW_GPIO_INTERRUPT, + + /* Firmware PSM support | CARL9170_CMD_PSM */ + CARL9170FW_PSM, + + /* Firmware RX filter | CARL9170_CMD_RX_FILTER */ + CARL9170FW_RX_FILTER, + + /* Wake up on WLAN */ + CARL9170FW_WOL, + + /* Firmware supports PSM in the 5GHZ Band */ + CARL9170FW_FIXED_5GHZ_PSM, + + /* HW (ANI, CCA, MIB) tally counters */ + CARL9170FW_HW_COUNTERS, + + /* Firmware will pass BA when BARs are queued */ + CARL9170FW_RX_BA_FILTER, + + /* KEEP LAST */ + __CARL9170FW_FEATURE_NUM +}; + +#define OTUS_MAGIC "OTAR" +#define MOTD_MAGIC "MOTD" +#define FIX_MAGIC "FIX\0" +#define DBG_MAGIC "DBG\0" +#define CHK_MAGIC "CHK\0" +#define TXSQ_MAGIC "TXSQ" +#define WOL_MAGIC "WOL\0" +#define LAST_MAGIC "LAST" + +#define CARL9170FW_SET_DAY(d) (((d) - 1) % 31) +#define CARL9170FW_SET_MONTH(m) ((((m) - 1) % 12) * 31) +#define CARL9170FW_SET_YEAR(y) (((y) - 10) * 372) + +#define CARL9170FW_GET_DAY(d) (((d) % 31) + 1) +#define CARL9170FW_GET_MONTH(m) ((((m) / 31) % 12) + 1) +#define CARL9170FW_GET_YEAR(y) ((y) / 372 + 10) + +#define CARL9170FW_MAGIC_SIZE 4 + +struct carl9170fw_desc_head { + u8 magic[CARL9170FW_MAGIC_SIZE]; + __le16 length; + u8 min_ver; + u8 cur_ver; +} __packed; +#define CARL9170FW_DESC_HEAD_SIZE \ + (sizeof(struct carl9170fw_desc_head)) + +#define CARL9170FW_OTUS_DESC_MIN_VER 6 +#define CARL9170FW_OTUS_DESC_CUR_VER 7 +struct carl9170fw_otus_desc { + struct carl9170fw_desc_head head; + __le32 feature_set; + __le32 fw_address; + __le32 bcn_addr; + __le16 bcn_len; + __le16 miniboot_size; + __le16 tx_frag_len; + __le16 rx_max_frame_len; + u8 tx_descs; + u8 cmd_bufs; + u8 api_ver; + u8 vif_num; +} __packed; +#define CARL9170FW_OTUS_DESC_SIZE \ + (sizeof(struct carl9170fw_otus_desc)) + +#define CARL9170FW_MOTD_STRING_LEN 24 +#define CARL9170FW_MOTD_RELEASE_LEN 20 +#define CARL9170FW_MOTD_DESC_MIN_VER 1 +#define CARL9170FW_MOTD_DESC_CUR_VER 2 +struct carl9170fw_motd_desc { + struct carl9170fw_desc_head head; + __le32 fw_year_month_day; + char desc[CARL9170FW_MOTD_STRING_LEN]; + char release[CARL9170FW_MOTD_RELEASE_LEN]; +} __packed; +#define CARL9170FW_MOTD_DESC_SIZE \ + (sizeof(struct carl9170fw_motd_desc)) + +#define CARL9170FW_FIX_DESC_MIN_VER 1 +#define CARL9170FW_FIX_DESC_CUR_VER 2 +struct carl9170fw_fix_entry { + __le32 address; + __le32 mask; + __le32 value; +} __packed; + +struct carl9170fw_fix_desc { + struct carl9170fw_desc_head head; + struct carl9170fw_fix_entry data[0]; +} __packed; +#define CARL9170FW_FIX_DESC_SIZE \ + (sizeof(struct carl9170fw_fix_desc)) + +#define CARL9170FW_DBG_DESC_MIN_VER 1 +#define CARL9170FW_DBG_DESC_CUR_VER 3 +struct carl9170fw_dbg_desc { + struct carl9170fw_desc_head head; + + __le32 bogoclock_addr; + __le32 counter_addr; + __le32 rx_total_addr; + __le32 rx_overrun_addr; + __le32 rx_filter; + + /* Put your debugging definitions here */ +} __packed; +#define CARL9170FW_DBG_DESC_SIZE \ + (sizeof(struct carl9170fw_dbg_desc)) + +#define CARL9170FW_CHK_DESC_MIN_VER 1 +#define CARL9170FW_CHK_DESC_CUR_VER 2 +struct carl9170fw_chk_desc { + struct carl9170fw_desc_head head; + __le32 fw_crc32; + __le32 hdr_crc32; +} __packed; +#define CARL9170FW_CHK_DESC_SIZE \ + (sizeof(struct carl9170fw_chk_desc)) + +#define CARL9170FW_TXSQ_DESC_MIN_VER 1 +#define CARL9170FW_TXSQ_DESC_CUR_VER 1 +struct carl9170fw_txsq_desc { + struct carl9170fw_desc_head head; + + __le32 seq_table_addr; +} __packed; +#define CARL9170FW_TXSQ_DESC_SIZE \ + (sizeof(struct carl9170fw_txsq_desc)) + +#define CARL9170FW_WOL_DESC_MIN_VER 1 +#define CARL9170FW_WOL_DESC_CUR_VER 1 +struct carl9170fw_wol_desc { + struct carl9170fw_desc_head head; + + __le32 supported_triggers; /* CARL9170_WOL_ */ +} __packed; +#define CARL9170FW_WOL_DESC_SIZE \ + (sizeof(struct carl9170fw_wol_desc)) + +#define CARL9170FW_LAST_DESC_MIN_VER 1 +#define CARL9170FW_LAST_DESC_CUR_VER 2 +struct carl9170fw_last_desc { + struct carl9170fw_desc_head head; +} __packed; +#define CARL9170FW_LAST_DESC_SIZE \ + (sizeof(struct carl9170fw_fix_desc)) + +#define CARL9170FW_DESC_MAX_LENGTH 8192 + +#define CARL9170FW_FILL_DESC(_magic, _length, _min_ver, _cur_ver) \ + .head = { \ + .magic = _magic, \ + .length = cpu_to_le16(_length), \ + .min_ver = _min_ver, \ + .cur_ver = _cur_ver, \ + } + +static inline void carl9170fw_fill_desc(struct carl9170fw_desc_head *head, + u8 magic[CARL9170FW_MAGIC_SIZE], + __le16 length, u8 min_ver, u8 cur_ver) +{ + head->magic[0] = magic[0]; + head->magic[1] = magic[1]; + head->magic[2] = magic[2]; + head->magic[3] = magic[3]; + + head->length = length; + head->min_ver = min_ver; + head->cur_ver = cur_ver; +} + +#define carl9170fw_for_each_hdr(desc, fw_desc) \ + for (desc = fw_desc; \ + memcmp(desc->magic, LAST_MAGIC, CARL9170FW_MAGIC_SIZE) && \ + le16_to_cpu(desc->length) >= CARL9170FW_DESC_HEAD_SIZE && \ + le16_to_cpu(desc->length) < CARL9170FW_DESC_MAX_LENGTH; \ + desc = (void *)((unsigned long)desc + le16_to_cpu(desc->length))) + +#define CHECK_HDR_VERSION(head, _min_ver) \ + (((head)->cur_ver < _min_ver) || ((head)->min_ver > _min_ver)) \ + +static inline bool carl9170fw_supports(__le32 list, u8 feature) +{ + return le32_to_cpu(list) & BIT(feature); +} + +static inline bool carl9170fw_desc_cmp(const struct carl9170fw_desc_head *head, + const u8 descid[CARL9170FW_MAGIC_SIZE], + u16 min_len, u8 compatible_revision) +{ + if (descid[0] == head->magic[0] && descid[1] == head->magic[1] && + descid[2] == head->magic[2] && descid[3] == head->magic[3] && + !CHECK_HDR_VERSION(head, compatible_revision) && + (le16_to_cpu(head->length) >= min_len)) + return true; + + return false; +} + +#define CARL9170FW_MIN_SIZE 32 +#define CARL9170FW_MAX_SIZE 16384 + +static inline bool carl9170fw_size_check(unsigned int len) +{ + return (len <= CARL9170FW_MAX_SIZE && len >= CARL9170FW_MIN_SIZE); +} + +#endif /* __CARL9170_SHARED_FWDESC_H */ diff --git a/drivers/net/wireless/ath/carl9170/hw.h b/drivers/net/wireless/ath/carl9170/hw.h new file mode 100644 index 0000000..0db874a --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/hw.h @@ -0,0 +1,817 @@ +/* + * Shared Atheros AR9170 Header + * + * Register map, hardware-specific definitions + * + * Copyright 2008, Johannes Berg + * Copyright 2009-2011 Christian Lamparter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * Copyright (c) 2007-2008 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __CARL9170_SHARED_HW_H +#define __CARL9170_SHARED_HW_H + +/* High Speed UART */ +#define AR9170_UART_REG_BASE 0x1c0000 + +/* Definitions of interrupt registers */ +#define AR9170_UART_REG_RX_BUFFER (AR9170_UART_REG_BASE + 0x000) +#define AR9170_UART_REG_TX_HOLDING (AR9170_UART_REG_BASE + 0x004) +#define AR9170_UART_REG_FIFO_CONTROL (AR9170_UART_REG_BASE + 0x010) +#define AR9170_UART_FIFO_CTRL_RESET_RX_FIFO 0x02 +#define AR9170_UART_FIFO_CTRL_RESET_TX_FIFO 0x04 + +#define AR9170_UART_REG_LINE_CONTROL (AR9170_UART_REG_BASE + 0x014) +#define AR9170_UART_REG_MODEM_CONTROL (AR9170_UART_REG_BASE + 0x018) +#define AR9170_UART_MODEM_CTRL_DTR_BIT 0x01 +#define AR9170_UART_MODEM_CTRL_RTS_BIT 0x02 +#define AR9170_UART_MODEM_CTRL_INTERNAL_LOOP_BACK 0x10 +#define AR9170_UART_MODEM_CTRL_AUTO_RTS 0x20 +#define AR9170_UART_MODEM_CTRL_AUTO_CTR 0x40 + +#define AR9170_UART_REG_LINE_STATUS (AR9170_UART_REG_BASE + 0x01c) +#define AR9170_UART_LINE_STS_RX_DATA_READY 0x01 +#define AR9170_UART_LINE_STS_RX_BUFFER_OVERRUN 0x02 +#define AR9170_UART_LINE_STS_RX_BREAK_IND 0x10 +#define AR9170_UART_LINE_STS_TX_FIFO_NEAR_EMPTY 0x20 +#define AR9170_UART_LINE_STS_TRANSMITTER_EMPTY 0x40 + +#define AR9170_UART_REG_MODEM_STATUS (AR9170_UART_REG_BASE + 0x020) +#define AR9170_UART_MODEM_STS_CTS_CHANGE 0x01 +#define AR9170_UART_MODEM_STS_DSR_CHANGE 0x02 +#define AR9170_UART_MODEM_STS_DCD_CHANGE 0x08 +#define AR9170_UART_MODEM_STS_CTS_COMPL 0x10 +#define AR9170_UART_MODEM_STS_DSR_COMPL 0x20 +#define AR9170_UART_MODEM_STS_DCD_COMPL 0x80 + +#define AR9170_UART_REG_SCRATCH (AR9170_UART_REG_BASE + 0x024) +#define AR9170_UART_REG_DIVISOR_LSB (AR9170_UART_REG_BASE + 0x028) +#define AR9170_UART_REG_DIVISOR_MSB (AR9170_UART_REG_BASE + 0x02c) +#define AR9170_UART_REG_WORD_RX_BUFFER (AR9170_UART_REG_BASE + 0x034) +#define AR9170_UART_REG_WORD_TX_HOLDING (AR9170_UART_REG_BASE + 0x038) +#define AR9170_UART_REG_FIFO_COUNT (AR9170_UART_REG_BASE + 0x03c) +#define AR9170_UART_REG_REMAINDER (AR9170_UART_REG_BASE + 0x04c) + +/* Timer */ +#define AR9170_TIMER_REG_BASE 0x1c1000 + +#define AR9170_TIMER_REG_WATCH_DOG (AR9170_TIMER_REG_BASE + 0x000) +#define AR9170_TIMER_REG_TIMER0 (AR9170_TIMER_REG_BASE + 0x010) +#define AR9170_TIMER_REG_TIMER1 (AR9170_TIMER_REG_BASE + 0x014) +#define AR9170_TIMER_REG_TIMER2 (AR9170_TIMER_REG_BASE + 0x018) +#define AR9170_TIMER_REG_TIMER3 (AR9170_TIMER_REG_BASE + 0x01c) +#define AR9170_TIMER_REG_TIMER4 (AR9170_TIMER_REG_BASE + 0x020) +#define AR9170_TIMER_REG_CONTROL (AR9170_TIMER_REG_BASE + 0x024) +#define AR9170_TIMER_CTRL_DISABLE_CLOCK 0x100 + +#define AR9170_TIMER_REG_INTERRUPT (AR9170_TIMER_REG_BASE + 0x028) +#define AR9170_TIMER_INT_TIMER0 0x001 +#define AR9170_TIMER_INT_TIMER1 0x002 +#define AR9170_TIMER_INT_TIMER2 0x004 +#define AR9170_TIMER_INT_TIMER3 0x008 +#define AR9170_TIMER_INT_TIMER4 0x010 +#define AR9170_TIMER_INT_TICK_TIMER 0x100 + +#define AR9170_TIMER_REG_TICK_TIMER (AR9170_TIMER_REG_BASE + 0x030) +#define AR9170_TIMER_REG_CLOCK_LOW (AR9170_TIMER_REG_BASE + 0x040) +#define AR9170_TIMER_REG_CLOCK_HIGH (AR9170_TIMER_REG_BASE + 0x044) + +#define AR9170_MAC_REG_BASE 0x1c3000 + +#define AR9170_MAC_REG_POWER_STATE_CTRL (AR9170_MAC_REG_BASE + 0x500) +#define AR9170_MAC_POWER_STATE_CTRL_RESET 0x20 + +#define AR9170_MAC_REG_MAC_POWER_STATE_CTRL (AR9170_MAC_REG_BASE + 0x50c) + +#define AR9170_MAC_REG_INT_CTRL (AR9170_MAC_REG_BASE + 0x510) +#define AR9170_MAC_INT_TXC BIT(0) +#define AR9170_MAC_INT_RXC BIT(1) +#define AR9170_MAC_INT_RETRY_FAIL BIT(2) +#define AR9170_MAC_INT_WAKEUP BIT(3) +#define AR9170_MAC_INT_ATIM BIT(4) +#define AR9170_MAC_INT_DTIM BIT(5) +#define AR9170_MAC_INT_CFG_BCN BIT(6) +#define AR9170_MAC_INT_ABORT BIT(7) +#define AR9170_MAC_INT_QOS BIT(8) +#define AR9170_MAC_INT_MIMO_PS BIT(9) +#define AR9170_MAC_INT_KEY_GEN BIT(10) +#define AR9170_MAC_INT_DECRY_NOUSER BIT(11) +#define AR9170_MAC_INT_RADAR BIT(12) +#define AR9170_MAC_INT_QUIET_FRAME BIT(13) +#define AR9170_MAC_INT_PRETBTT BIT(14) + +#define AR9170_MAC_REG_TSF_L (AR9170_MAC_REG_BASE + 0x514) +#define AR9170_MAC_REG_TSF_H (AR9170_MAC_REG_BASE + 0x518) + +#define AR9170_MAC_REG_ATIM_WINDOW (AR9170_MAC_REG_BASE + 0x51c) +#define AR9170_MAC_ATIM_PERIOD_S 0 +#define AR9170_MAC_ATIM_PERIOD 0x0000ffff + +#define AR9170_MAC_REG_BCN_PERIOD (AR9170_MAC_REG_BASE + 0x520) +#define AR9170_MAC_BCN_PERIOD_S 0 +#define AR9170_MAC_BCN_PERIOD 0x0000ffff +#define AR9170_MAC_BCN_DTIM_S 16 +#define AR9170_MAC_BCN_DTIM 0x00ff0000 +#define AR9170_MAC_BCN_AP_MODE BIT(24) +#define AR9170_MAC_BCN_IBSS_MODE BIT(25) +#define AR9170_MAC_BCN_PWR_MGT BIT(26) +#define AR9170_MAC_BCN_STA_PS BIT(27) + +#define AR9170_MAC_REG_PRETBTT (AR9170_MAC_REG_BASE + 0x524) +#define AR9170_MAC_PRETBTT_S 0 +#define AR9170_MAC_PRETBTT 0x0000ffff +#define AR9170_MAC_PRETBTT2_S 16 +#define AR9170_MAC_PRETBTT2 0xffff0000 + +#define AR9170_MAC_REG_MAC_ADDR_L (AR9170_MAC_REG_BASE + 0x610) +#define AR9170_MAC_REG_MAC_ADDR_H (AR9170_MAC_REG_BASE + 0x614) +#define AR9170_MAC_REG_BSSID_L (AR9170_MAC_REG_BASE + 0x618) +#define AR9170_MAC_REG_BSSID_H (AR9170_MAC_REG_BASE + 0x61c) + +#define AR9170_MAC_REG_GROUP_HASH_TBL_L (AR9170_MAC_REG_BASE + 0x624) +#define AR9170_MAC_REG_GROUP_HASH_TBL_H (AR9170_MAC_REG_BASE + 0x628) + +#define AR9170_MAC_REG_RX_TIMEOUT (AR9170_MAC_REG_BASE + 0x62c) + +#define AR9170_MAC_REG_BASIC_RATE (AR9170_MAC_REG_BASE + 0x630) +#define AR9170_MAC_REG_MANDATORY_RATE (AR9170_MAC_REG_BASE + 0x634) +#define AR9170_MAC_REG_RTS_CTS_RATE (AR9170_MAC_REG_BASE + 0x638) +#define AR9170_MAC_REG_BACKOFF_PROTECT (AR9170_MAC_REG_BASE + 0x63c) +#define AR9170_MAC_REG_RX_THRESHOLD (AR9170_MAC_REG_BASE + 0x640) +#define AR9170_MAC_REG_AFTER_PNP (AR9170_MAC_REG_BASE + 0x648) +#define AR9170_MAC_REG_RX_PE_DELAY (AR9170_MAC_REG_BASE + 0x64c) + +#define AR9170_MAC_REG_DYNAMIC_SIFS_ACK (AR9170_MAC_REG_BASE + 0x658) +#define AR9170_MAC_REG_SNIFFER (AR9170_MAC_REG_BASE + 0x674) +#define AR9170_MAC_SNIFFER_ENABLE_PROMISC BIT(0) +#define AR9170_MAC_SNIFFER_DEFAULTS 0x02000000 +#define AR9170_MAC_REG_ENCRYPTION (AR9170_MAC_REG_BASE + 0x678) +#define AR9170_MAC_ENCRYPTION_MGMT_RX_SOFTWARE BIT(2) +#define AR9170_MAC_ENCRYPTION_RX_SOFTWARE BIT(3) +#define AR9170_MAC_ENCRYPTION_DEFAULTS 0x70 + +#define AR9170_MAC_REG_MISC_680 (AR9170_MAC_REG_BASE + 0x680) +#define AR9170_MAC_REG_MISC_684 (AR9170_MAC_REG_BASE + 0x684) +#define AR9170_MAC_REG_TX_UNDERRUN (AR9170_MAC_REG_BASE + 0x688) + +#define AR9170_MAC_REG_FRAMETYPE_FILTER (AR9170_MAC_REG_BASE + 0x68c) +#define AR9170_MAC_FTF_ASSOC_REQ BIT(0) +#define AR9170_MAC_FTF_ASSOC_RESP BIT(1) +#define AR9170_MAC_FTF_REASSOC_REQ BIT(2) +#define AR9170_MAC_FTF_REASSOC_RESP BIT(3) +#define AR9170_MAC_FTF_PRB_REQ BIT(4) +#define AR9170_MAC_FTF_PRB_RESP BIT(5) +#define AR9170_MAC_FTF_BIT6 BIT(6) +#define AR9170_MAC_FTF_BIT7 BIT(7) +#define AR9170_MAC_FTF_BEACON BIT(8) +#define AR9170_MAC_FTF_ATIM BIT(9) +#define AR9170_MAC_FTF_DEASSOC BIT(10) +#define AR9170_MAC_FTF_AUTH BIT(11) +#define AR9170_MAC_FTF_DEAUTH BIT(12) +#define AR9170_MAC_FTF_BIT13 BIT(13) +#define AR9170_MAC_FTF_BIT14 BIT(14) +#define AR9170_MAC_FTF_BIT15 BIT(15) +#define AR9170_MAC_FTF_BAR BIT(24) +#define AR9170_MAC_FTF_BA BIT(25) +#define AR9170_MAC_FTF_PSPOLL BIT(26) +#define AR9170_MAC_FTF_RTS BIT(27) +#define AR9170_MAC_FTF_CTS BIT(28) +#define AR9170_MAC_FTF_ACK BIT(29) +#define AR9170_MAC_FTF_CFE BIT(30) +#define AR9170_MAC_FTF_CFE_ACK BIT(31) +#define AR9170_MAC_FTF_DEFAULTS 0x0500ffff +#define AR9170_MAC_FTF_MONITOR 0xff00ffff + +#define AR9170_MAC_REG_ACK_EXTENSION (AR9170_MAC_REG_BASE + 0x690) +#define AR9170_MAC_REG_ACK_TPC (AR9170_MAC_REG_BASE + 0x694) +#define AR9170_MAC_REG_EIFS_AND_SIFS (AR9170_MAC_REG_BASE + 0x698) +#define AR9170_MAC_REG_RX_TIMEOUT_COUNT (AR9170_MAC_REG_BASE + 0x69c) +#define AR9170_MAC_REG_RX_TOTAL (AR9170_MAC_REG_BASE + 0x6a0) +#define AR9170_MAC_REG_RX_CRC32 (AR9170_MAC_REG_BASE + 0x6a4) +#define AR9170_MAC_REG_RX_CRC16 (AR9170_MAC_REG_BASE + 0x6a8) +#define AR9170_MAC_REG_RX_ERR_DECRYPTION_UNI (AR9170_MAC_REG_BASE + 0x6ac) +#define AR9170_MAC_REG_RX_OVERRUN (AR9170_MAC_REG_BASE + 0x6b0) +#define AR9170_MAC_REG_RX_ERR_DECRYPTION_MUL (AR9170_MAC_REG_BASE + 0x6bc) +#define AR9170_MAC_REG_TX_BLOCKACKS (AR9170_MAC_REG_BASE + 0x6c0) +#define AR9170_MAC_REG_NAV_COUNT (AR9170_MAC_REG_BASE + 0x6c4) +#define AR9170_MAC_REG_BACKOFF_STATUS (AR9170_MAC_REG_BASE + 0x6c8) +#define AR9170_MAC_BACKOFF_CCA BIT(24) +#define AR9170_MAC_BACKOFF_TX_PEX BIT(25) +#define AR9170_MAC_BACKOFF_RX_PE BIT(26) +#define AR9170_MAC_BACKOFF_MD_READY BIT(27) +#define AR9170_MAC_BACKOFF_TX_PE BIT(28) + +#define AR9170_MAC_REG_TX_RETRY (AR9170_MAC_REG_BASE + 0x6cc) + +#define AR9170_MAC_REG_TX_COMPLETE (AR9170_MAC_REG_BASE + 0x6d4) + +#define AR9170_MAC_REG_CHANNEL_BUSY (AR9170_MAC_REG_BASE + 0x6e8) +#define AR9170_MAC_REG_EXT_BUSY (AR9170_MAC_REG_BASE + 0x6ec) + +#define AR9170_MAC_REG_SLOT_TIME (AR9170_MAC_REG_BASE + 0x6f0) +#define AR9170_MAC_REG_TX_TOTAL (AR9170_MAC_REG_BASE + 0x6f4) +#define AR9170_MAC_REG_ACK_FC (AR9170_MAC_REG_BASE + 0x6f8) + +#define AR9170_MAC_REG_CAM_MODE (AR9170_MAC_REG_BASE + 0x700) +#define AR9170_MAC_CAM_IBSS 0xe0 +#define AR9170_MAC_CAM_AP 0xa1 +#define AR9170_MAC_CAM_STA 0x2 +#define AR9170_MAC_CAM_AP_WDS 0x3 +#define AR9170_MAC_CAM_DEFAULTS (0xf << 24) +#define AR9170_MAC_CAM_HOST_PENDING 0x80000000 + +#define AR9170_MAC_REG_CAM_ROLL_CALL_TBL_L (AR9170_MAC_REG_BASE + 0x704) +#define AR9170_MAC_REG_CAM_ROLL_CALL_TBL_H (AR9170_MAC_REG_BASE + 0x708) + +#define AR9170_MAC_REG_CAM_ADDR (AR9170_MAC_REG_BASE + 0x70c) +#define AR9170_MAC_CAM_ADDR_WRITE 0x80000000 +#define AR9170_MAC_REG_CAM_DATA0 (AR9170_MAC_REG_BASE + 0x720) +#define AR9170_MAC_REG_CAM_DATA1 (AR9170_MAC_REG_BASE + 0x724) +#define AR9170_MAC_REG_CAM_DATA2 (AR9170_MAC_REG_BASE + 0x728) +#define AR9170_MAC_REG_CAM_DATA3 (AR9170_MAC_REG_BASE + 0x72c) + +#define AR9170_MAC_REG_CAM_DBG0 (AR9170_MAC_REG_BASE + 0x730) +#define AR9170_MAC_REG_CAM_DBG1 (AR9170_MAC_REG_BASE + 0x734) +#define AR9170_MAC_REG_CAM_DBG2 (AR9170_MAC_REG_BASE + 0x738) +#define AR9170_MAC_REG_CAM_STATE (AR9170_MAC_REG_BASE + 0x73c) +#define AR9170_MAC_CAM_STATE_READ_PENDING 0x40000000 +#define AR9170_MAC_CAM_STATE_WRITE_PENDING 0x80000000 + +#define AR9170_MAC_REG_CAM_TXKEY (AR9170_MAC_REG_BASE + 0x740) +#define AR9170_MAC_REG_CAM_RXKEY (AR9170_MAC_REG_BASE + 0x750) + +#define AR9170_MAC_REG_CAM_TX_ENC_TYPE (AR9170_MAC_REG_BASE + 0x760) +#define AR9170_MAC_REG_CAM_RX_ENC_TYPE (AR9170_MAC_REG_BASE + 0x770) +#define AR9170_MAC_REG_CAM_TX_SERACH_HIT (AR9170_MAC_REG_BASE + 0x780) +#define AR9170_MAC_REG_CAM_RX_SERACH_HIT (AR9170_MAC_REG_BASE + 0x790) + +#define AR9170_MAC_REG_AC0_CW (AR9170_MAC_REG_BASE + 0xb00) +#define AR9170_MAC_REG_AC1_CW (AR9170_MAC_REG_BASE + 0xb04) +#define AR9170_MAC_REG_AC2_CW (AR9170_MAC_REG_BASE + 0xb08) +#define AR9170_MAC_REG_AC3_CW (AR9170_MAC_REG_BASE + 0xb0c) +#define AR9170_MAC_REG_AC4_CW (AR9170_MAC_REG_BASE + 0xb10) +#define AR9170_MAC_REG_AC2_AC1_AC0_AIFS (AR9170_MAC_REG_BASE + 0xb14) +#define AR9170_MAC_REG_AC4_AC3_AC2_AIFS (AR9170_MAC_REG_BASE + 0xb18) +#define AR9170_MAC_REG_TXOP_ACK_EXTENSION (AR9170_MAC_REG_BASE + 0xb1c) +#define AR9170_MAC_REG_TXOP_ACK_INTERVAL (AR9170_MAC_REG_BASE + 0xb20) +#define AR9170_MAC_REG_CONTENTION_POINT (AR9170_MAC_REG_BASE + 0xb24) +#define AR9170_MAC_REG_RETRY_MAX (AR9170_MAC_REG_BASE + 0xb28) +#define AR9170_MAC_REG_TID_CFACK_CFEND_RATE (AR9170_MAC_REG_BASE + 0xb2c) +#define AR9170_MAC_REG_TXOP_NOT_ENOUGH_IND (AR9170_MAC_REG_BASE + 0xb30) +#define AR9170_MAC_REG_TKIP_TSC (AR9170_MAC_REG_BASE + 0xb34) +#define AR9170_MAC_REG_TXOP_DURATION (AR9170_MAC_REG_BASE + 0xb38) +#define AR9170_MAC_REG_TX_QOS_THRESHOLD (AR9170_MAC_REG_BASE + 0xb3c) +#define AR9170_MAC_REG_QOS_PRIORITY_VIRTUAL_CCA (AR9170_MAC_REG_BASE + 0xb40) +#define AR9170_MAC_VIRTUAL_CCA_Q0 BIT(15) +#define AR9170_MAC_VIRTUAL_CCA_Q1 BIT(16) +#define AR9170_MAC_VIRTUAL_CCA_Q2 BIT(17) +#define AR9170_MAC_VIRTUAL_CCA_Q3 BIT(18) +#define AR9170_MAC_VIRTUAL_CCA_Q4 BIT(19) +#define AR9170_MAC_VIRTUAL_CCA_ALL (0xf8000) + +#define AR9170_MAC_REG_AC1_AC0_TXOP (AR9170_MAC_REG_BASE + 0xb44) +#define AR9170_MAC_REG_AC3_AC2_TXOP (AR9170_MAC_REG_BASE + 0xb48) + +#define AR9170_MAC_REG_AMPDU_COUNT (AR9170_MAC_REG_BASE + 0xb88) +#define AR9170_MAC_REG_MPDU_COUNT (AR9170_MAC_REG_BASE + 0xb8c) + +#define AR9170_MAC_REG_AMPDU_FACTOR (AR9170_MAC_REG_BASE + 0xb9c) +#define AR9170_MAC_AMPDU_FACTOR 0x7f0000 +#define AR9170_MAC_AMPDU_FACTOR_S 16 +#define AR9170_MAC_REG_AMPDU_DENSITY (AR9170_MAC_REG_BASE + 0xba0) +#define AR9170_MAC_AMPDU_DENSITY 0x7 +#define AR9170_MAC_AMPDU_DENSITY_S 0 + +#define AR9170_MAC_REG_FCS_SELECT (AR9170_MAC_REG_BASE + 0xbb0) +#define AR9170_MAC_FCS_SWFCS 0x1 +#define AR9170_MAC_FCS_FIFO_PROT 0x4 + +#define AR9170_MAC_REG_RTS_CTS_TPC (AR9170_MAC_REG_BASE + 0xbb4) +#define AR9170_MAC_REG_CFEND_QOSNULL_TPC (AR9170_MAC_REG_BASE + 0xbb8) + +#define AR9170_MAC_REG_ACK_TABLE (AR9170_MAC_REG_BASE + 0xc00) +#define AR9170_MAC_REG_RX_CONTROL (AR9170_MAC_REG_BASE + 0xc40) +#define AR9170_MAC_RX_CTRL_DEAGG 0x1 +#define AR9170_MAC_RX_CTRL_SHORT_FILTER 0x2 +#define AR9170_MAC_RX_CTRL_SA_DA_SEARCH 0x20 +#define AR9170_MAC_RX_CTRL_PASS_TO_HOST BIT(28) +#define AR9170_MAC_RX_CTRL_ACK_IN_SNIFFER BIT(30) + +#define AR9170_MAC_REG_RX_CONTROL_1 (AR9170_MAC_REG_BASE + 0xc44) + +#define AR9170_MAC_REG_AMPDU_RX_THRESH (AR9170_MAC_REG_BASE + 0xc50) + +#define AR9170_MAC_REG_RX_MPDU (AR9170_MAC_REG_BASE + 0xca0) +#define AR9170_MAC_REG_RX_DROPPED_MPDU (AR9170_MAC_REG_BASE + 0xca4) +#define AR9170_MAC_REG_RX_DEL_MPDU (AR9170_MAC_REG_BASE + 0xca8) +#define AR9170_MAC_REG_RX_PHY_MISC_ERROR (AR9170_MAC_REG_BASE + 0xcac) +#define AR9170_MAC_REG_RX_PHY_XR_ERROR (AR9170_MAC_REG_BASE + 0xcb0) +#define AR9170_MAC_REG_RX_PHY_OFDM_ERROR (AR9170_MAC_REG_BASE + 0xcb4) +#define AR9170_MAC_REG_RX_PHY_CCK_ERROR (AR9170_MAC_REG_BASE + 0xcb8) +#define AR9170_MAC_REG_RX_PHY_HT_ERROR (AR9170_MAC_REG_BASE + 0xcbc) +#define AR9170_MAC_REG_RX_PHY_TOTAL (AR9170_MAC_REG_BASE + 0xcc0) + +#define AR9170_MAC_REG_DMA_TXQ_ADDR (AR9170_MAC_REG_BASE + 0xd00) +#define AR9170_MAC_REG_DMA_TXQ_CURR_ADDR (AR9170_MAC_REG_BASE + 0xd04) +#define AR9170_MAC_REG_DMA_TXQ0_ADDR (AR9170_MAC_REG_BASE + 0xd00) +#define AR9170_MAC_REG_DMA_TXQ0_CURR_ADDR (AR9170_MAC_REG_BASE + 0xd04) +#define AR9170_MAC_REG_DMA_TXQ1_ADDR (AR9170_MAC_REG_BASE + 0xd08) +#define AR9170_MAC_REG_DMA_TXQ1_CURR_ADDR (AR9170_MAC_REG_BASE + 0xd0c) +#define AR9170_MAC_REG_DMA_TXQ2_ADDR (AR9170_MAC_REG_BASE + 0xd10) +#define AR9170_MAC_REG_DMA_TXQ2_CURR_ADDR (AR9170_MAC_REG_BASE + 0xd14) +#define AR9170_MAC_REG_DMA_TXQ3_ADDR (AR9170_MAC_REG_BASE + 0xd18) +#define AR9170_MAC_REG_DMA_TXQ3_CURR_ADDR (AR9170_MAC_REG_BASE + 0xd1c) +#define AR9170_MAC_REG_DMA_TXQ4_ADDR (AR9170_MAC_REG_BASE + 0xd20) +#define AR9170_MAC_REG_DMA_TXQ4_CURR_ADDR (AR9170_MAC_REG_BASE + 0xd24) +#define AR9170_MAC_REG_DMA_RXQ_ADDR (AR9170_MAC_REG_BASE + 0xd28) +#define AR9170_MAC_REG_DMA_RXQ_CURR_ADDR (AR9170_MAC_REG_BASE + 0xd2c) + +#define AR9170_MAC_REG_DMA_TRIGGER (AR9170_MAC_REG_BASE + 0xd30) +#define AR9170_DMA_TRIGGER_TXQ0 BIT(0) +#define AR9170_DMA_TRIGGER_TXQ1 BIT(1) +#define AR9170_DMA_TRIGGER_TXQ2 BIT(2) +#define AR9170_DMA_TRIGGER_TXQ3 BIT(3) +#define AR9170_DMA_TRIGGER_TXQ4 BIT(4) +#define AR9170_DMA_TRIGGER_RXQ BIT(8) + +#define AR9170_MAC_REG_DMA_WLAN_STATUS (AR9170_MAC_REG_BASE + 0xd38) +#define AR9170_MAC_REG_DMA_STATUS (AR9170_MAC_REG_BASE + 0xd3c) +#define AR9170_MAC_REG_DMA_TXQ_LAST_ADDR (AR9170_MAC_REG_BASE + 0xd40) +#define AR9170_MAC_REG_DMA_TXQ0_LAST_ADDR (AR9170_MAC_REG_BASE + 0xd40) +#define AR9170_MAC_REG_DMA_TXQ1_LAST_ADDR (AR9170_MAC_REG_BASE + 0xd44) +#define AR9170_MAC_REG_DMA_TXQ2_LAST_ADDR (AR9170_MAC_REG_BASE + 0xd48) +#define AR9170_MAC_REG_DMA_TXQ3_LAST_ADDR (AR9170_MAC_REG_BASE + 0xd4c) +#define AR9170_MAC_REG_DMA_TXQ4_LAST_ADDR (AR9170_MAC_REG_BASE + 0xd50) +#define AR9170_MAC_REG_DMA_TXQ0Q1_LEN (AR9170_MAC_REG_BASE + 0xd54) +#define AR9170_MAC_REG_DMA_TXQ2Q3_LEN (AR9170_MAC_REG_BASE + 0xd58) +#define AR9170_MAC_REG_DMA_TXQ4_LEN (AR9170_MAC_REG_BASE + 0xd5c) + +#define AR9170_MAC_REG_DMA_TXQX_LAST_ADDR (AR9170_MAC_REG_BASE + 0xd74) +#define AR9170_MAC_REG_DMA_TXQX_FAIL_ADDR (AR9170_MAC_REG_BASE + 0xd78) +#define AR9170_MAC_REG_TXRX_MPI (AR9170_MAC_REG_BASE + 0xd7c) +#define AR9170_MAC_TXRX_MPI_TX_MPI_MASK 0x0000000f +#define AR9170_MAC_TXRX_MPI_TX_TO_MASK 0x0000fff0 +#define AR9170_MAC_TXRX_MPI_RX_MPI_MASK 0x000f0000 +#define AR9170_MAC_TXRX_MPI_RX_TO_MASK 0xfff00000 + +#define AR9170_MAC_REG_BCN_ADDR (AR9170_MAC_REG_BASE + 0xd84) +#define AR9170_MAC_REG_BCN_LENGTH (AR9170_MAC_REG_BASE + 0xd88) +#define AR9170_MAC_BCN_LENGTH_MAX (512 - 32) + +#define AR9170_MAC_REG_BCN_STATUS (AR9170_MAC_REG_BASE + 0xd8c) + +#define AR9170_MAC_REG_BCN_PLCP (AR9170_MAC_REG_BASE + 0xd90) +#define AR9170_MAC_REG_BCN_CTRL (AR9170_MAC_REG_BASE + 0xd94) +#define AR9170_BCN_CTRL_READY 0x01 +#define AR9170_BCN_CTRL_LOCK 0x02 + +#define AR9170_MAC_REG_BCN_CURR_ADDR (AR9170_MAC_REG_BASE + 0xd98) +#define AR9170_MAC_REG_BCN_COUNT (AR9170_MAC_REG_BASE + 0xd9c) +#define AR9170_MAC_REG_BCN_HT1 (AR9170_MAC_REG_BASE + 0xda0) +#define AR9170_MAC_BCN_HT1_HT_EN BIT(0) +#define AR9170_MAC_BCN_HT1_GF_PMB BIT(1) +#define AR9170_MAC_BCN_HT1_SP_EXP BIT(2) +#define AR9170_MAC_BCN_HT1_TX_BF BIT(3) +#define AR9170_MAC_BCN_HT1_PWR_CTRL_S 4 +#define AR9170_MAC_BCN_HT1_PWR_CTRL 0x70 +#define AR9170_MAC_BCN_HT1_TX_ANT1 BIT(7) +#define AR9170_MAC_BCN_HT1_TX_ANT0 BIT(8) +#define AR9170_MAC_BCN_HT1_NUM_LFT_S 9 +#define AR9170_MAC_BCN_HT1_NUM_LFT 0x600 +#define AR9170_MAC_BCN_HT1_BWC_20M_EXT BIT(16) +#define AR9170_MAC_BCN_HT1_BWC_40M_SHARED BIT(17) +#define AR9170_MAC_BCN_HT1_BWC_40M_DUP (BIT(16) | BIT(17)) +#define AR9170_MAC_BCN_HT1_BF_MCS_S 18 +#define AR9170_MAC_BCN_HT1_BF_MCS 0x1c0000 +#define AR9170_MAC_BCN_HT1_TPC_S 21 +#define AR9170_MAC_BCN_HT1_TPC 0x7e00000 +#define AR9170_MAC_BCN_HT1_CHAIN_MASK_S 27 +#define AR9170_MAC_BCN_HT1_CHAIN_MASK 0x38000000 + +#define AR9170_MAC_REG_BCN_HT2 (AR9170_MAC_REG_BASE + 0xda4) +#define AR9170_MAC_BCN_HT2_MCS_S 0 +#define AR9170_MAC_BCN_HT2_MCS 0x7f +#define AR9170_MAC_BCN_HT2_BW40 BIT(8) +#define AR9170_MAC_BCN_HT2_SMOOTHING BIT(9) +#define AR9170_MAC_BCN_HT2_SS BIT(10) +#define AR9170_MAC_BCN_HT2_NSS BIT(11) +#define AR9170_MAC_BCN_HT2_STBC_S 12 +#define AR9170_MAC_BCN_HT2_STBC 0x3000 +#define AR9170_MAC_BCN_HT2_ADV_COD BIT(14) +#define AR9170_MAC_BCN_HT2_SGI BIT(15) +#define AR9170_MAC_BCN_HT2_LEN_S 16 +#define AR9170_MAC_BCN_HT2_LEN 0xffff0000 + +#define AR9170_MAC_REG_DMA_TXQX_ADDR_CURR (AR9170_MAC_REG_BASE + 0xdc0) + +/* Random number generator */ +#define AR9170_RAND_REG_BASE 0x1d0000 + +#define AR9170_RAND_REG_NUM (AR9170_RAND_REG_BASE + 0x000) +#define AR9170_RAND_REG_MODE (AR9170_RAND_REG_BASE + 0x004) +#define AR9170_RAND_MODE_MANUAL 0x000 +#define AR9170_RAND_MODE_FREE 0x001 + +/* GPIO */ +#define AR9170_GPIO_REG_BASE 0x1d0100 +#define AR9170_GPIO_REG_PORT_TYPE (AR9170_GPIO_REG_BASE + 0x000) +#define AR9170_GPIO_REG_PORT_DATA (AR9170_GPIO_REG_BASE + 0x004) +#define AR9170_GPIO_PORT_LED_0 1 +#define AR9170_GPIO_PORT_LED_1 2 +/* WPS Button GPIO for TP-Link TL-WN821N */ +#define AR9170_GPIO_PORT_WPS_BUTTON_PRESSED 4 + +/* Memory Controller */ +#define AR9170_MC_REG_BASE 0x1d1000 + +#define AR9170_MC_REG_FLASH_WAIT_STATE (AR9170_MC_REG_BASE + 0x000) +#define AR9170_MC_REG_SEEPROM_WP0 (AR9170_MC_REG_BASE + 0x400) +#define AR9170_MC_REG_SEEPROM_WP1 (AR9170_MC_REG_BASE + 0x404) +#define AR9170_MC_REG_SEEPROM_WP2 (AR9170_MC_REG_BASE + 0x408) + +/* Interrupt Controller */ +#define AR9170_MAX_INT_SRC 9 +#define AR9170_INT_REG_BASE 0x1d2000 + +#define AR9170_INT_REG_FLAG (AR9170_INT_REG_BASE + 0x000) +#define AR9170_INT_REG_FIQ_MASK (AR9170_INT_REG_BASE + 0x004) +#define AR9170_INT_REG_IRQ_MASK (AR9170_INT_REG_BASE + 0x008) +/* INT_REG_FLAG, INT_REG_FIQ_MASK and INT_REG_IRQ_MASK */ +#define AR9170_INT_FLAG_WLAN 0x001 +#define AR9170_INT_FLAG_PTAB_BIT 0x002 +#define AR9170_INT_FLAG_SE_BIT 0x004 +#define AR9170_INT_FLAG_UART_BIT 0x008 +#define AR9170_INT_FLAG_TIMER_BIT 0x010 +#define AR9170_INT_FLAG_EXT_BIT 0x020 +#define AR9170_INT_FLAG_SW_BIT 0x040 +#define AR9170_INT_FLAG_USB_BIT 0x080 +#define AR9170_INT_FLAG_ETHERNET_BIT 0x100 + +#define AR9170_INT_REG_PRIORITY1 (AR9170_INT_REG_BASE + 0x00c) +#define AR9170_INT_REG_PRIORITY2 (AR9170_INT_REG_BASE + 0x010) +#define AR9170_INT_REG_PRIORITY3 (AR9170_INT_REG_BASE + 0x014) +#define AR9170_INT_REG_EXT_INT_CONTROL (AR9170_INT_REG_BASE + 0x018) +#define AR9170_INT_REG_SW_INT_CONTROL (AR9170_INT_REG_BASE + 0x01c) +#define AR9170_INT_SW_INT_ENABLE 0x1 + +#define AR9170_INT_REG_FIQ_ENCODE (AR9170_INT_REG_BASE + 0x020) +#define AR9170_INT_INT_IRQ_ENCODE (AR9170_INT_REG_BASE + 0x024) + +/* Power Management */ +#define AR9170_PWR_REG_BASE 0x1d4000 + +#define AR9170_PWR_REG_POWER_STATE (AR9170_PWR_REG_BASE + 0x000) + +#define AR9170_PWR_REG_RESET (AR9170_PWR_REG_BASE + 0x004) +#define AR9170_PWR_RESET_COMMIT_RESET_MASK BIT(0) +#define AR9170_PWR_RESET_WLAN_MASK BIT(1) +#define AR9170_PWR_RESET_DMA_MASK BIT(2) +#define AR9170_PWR_RESET_BRIDGE_MASK BIT(3) +#define AR9170_PWR_RESET_AHB_MASK BIT(9) +#define AR9170_PWR_RESET_BB_WARM_RESET BIT(10) +#define AR9170_PWR_RESET_BB_COLD_RESET BIT(11) +#define AR9170_PWR_RESET_ADDA_CLK_COLD_RESET BIT(12) +#define AR9170_PWR_RESET_PLL BIT(13) +#define AR9170_PWR_RESET_USB_PLL BIT(14) + +#define AR9170_PWR_REG_CLOCK_SEL (AR9170_PWR_REG_BASE + 0x008) +#define AR9170_PWR_CLK_AHB_40MHZ 0 +#define AR9170_PWR_CLK_AHB_20_22MHZ 1 +#define AR9170_PWR_CLK_AHB_40_44MHZ 2 +#define AR9170_PWR_CLK_AHB_80_88MHZ 3 +#define AR9170_PWR_CLK_DAC_160_INV_DLY 0x70 + +#define AR9170_PWR_REG_CHIP_REVISION (AR9170_PWR_REG_BASE + 0x010) +#define AR9170_PWR_REG_PLL_ADDAC (AR9170_PWR_REG_BASE + 0x014) +#define AR9170_PWR_PLL_ADDAC_DIV_S 2 +#define AR9170_PWR_PLL_ADDAC_DIV 0xffc +#define AR9170_PWR_REG_WATCH_DOG_MAGIC (AR9170_PWR_REG_BASE + 0x020) + +/* Faraday USB Controller */ +#define AR9170_USB_REG_BASE 0x1e1000 + +#define AR9170_USB_REG_MAIN_CTRL (AR9170_USB_REG_BASE + 0x000) +#define AR9170_USB_MAIN_CTRL_REMOTE_WAKEUP BIT(0) +#define AR9170_USB_MAIN_CTRL_ENABLE_GLOBAL_INT BIT(2) +#define AR9170_USB_MAIN_CTRL_GO_TO_SUSPEND BIT(3) +#define AR9170_USB_MAIN_CTRL_RESET BIT(4) +#define AR9170_USB_MAIN_CTRL_CHIP_ENABLE BIT(5) +#define AR9170_USB_MAIN_CTRL_HIGHSPEED BIT(6) + +#define AR9170_USB_REG_DEVICE_ADDRESS (AR9170_USB_REG_BASE + 0x001) +#define AR9170_USB_DEVICE_ADDRESS_CONFIGURE BIT(7) + +#define AR9170_USB_REG_TEST (AR9170_USB_REG_BASE + 0x002) +#define AR9170_USB_REG_PHY_TEST_SELECT (AR9170_USB_REG_BASE + 0x008) +#define AR9170_USB_REG_CX_CONFIG_STATUS (AR9170_USB_REG_BASE + 0x00b) +#define AR9170_USB_REG_EP0_DATA (AR9170_USB_REG_BASE + 0x00c) +#define AR9170_USB_REG_EP0_DATA1 (AR9170_USB_REG_BASE + 0x00c) +#define AR9170_USB_REG_EP0_DATA2 (AR9170_USB_REG_BASE + 0x00d) + +#define AR9170_USB_REG_INTR_MASK_BYTE_0 (AR9170_USB_REG_BASE + 0x011) +#define AR9170_USB_REG_INTR_MASK_BYTE_1 (AR9170_USB_REG_BASE + 0x012) +#define AR9170_USB_REG_INTR_MASK_BYTE_2 (AR9170_USB_REG_BASE + 0x013) +#define AR9170_USB_REG_INTR_MASK_BYTE_3 (AR9170_USB_REG_BASE + 0x014) +#define AR9170_USB_REG_INTR_MASK_BYTE_4 (AR9170_USB_REG_BASE + 0x015) +#define AR9170_USB_INTR_DISABLE_OUT_INT (BIT(7) | BIT(6)) + +#define AR9170_USB_REG_INTR_MASK_BYTE_5 (AR9170_USB_REG_BASE + 0x016) +#define AR9170_USB_REG_INTR_MASK_BYTE_6 (AR9170_USB_REG_BASE + 0x017) +#define AR9170_USB_INTR_DISABLE_IN_INT BIT(6) + +#define AR9170_USB_REG_INTR_MASK_BYTE_7 (AR9170_USB_REG_BASE + 0x018) + +#define AR9170_USB_REG_INTR_GROUP (AR9170_USB_REG_BASE + 0x020) + +#define AR9170_USB_REG_INTR_SOURCE_0 (AR9170_USB_REG_BASE + 0x021) +#define AR9170_USB_INTR_SRC0_SETUP BIT(0) +#define AR9170_USB_INTR_SRC0_IN BIT(1) +#define AR9170_USB_INTR_SRC0_OUT BIT(2) +#define AR9170_USB_INTR_SRC0_FAIL BIT(3) /* ??? */ +#define AR9170_USB_INTR_SRC0_END BIT(4) /* ??? */ +#define AR9170_USB_INTR_SRC0_ABORT BIT(7) + +#define AR9170_USB_REG_INTR_SOURCE_1 (AR9170_USB_REG_BASE + 0x022) +#define AR9170_USB_REG_INTR_SOURCE_2 (AR9170_USB_REG_BASE + 0x023) +#define AR9170_USB_REG_INTR_SOURCE_3 (AR9170_USB_REG_BASE + 0x024) +#define AR9170_USB_REG_INTR_SOURCE_4 (AR9170_USB_REG_BASE + 0x025) +#define AR9170_USB_REG_INTR_SOURCE_5 (AR9170_USB_REG_BASE + 0x026) +#define AR9170_USB_REG_INTR_SOURCE_6 (AR9170_USB_REG_BASE + 0x027) +#define AR9170_USB_REG_INTR_SOURCE_7 (AR9170_USB_REG_BASE + 0x028) +#define AR9170_USB_INTR_SRC7_USB_RESET BIT(1) +#define AR9170_USB_INTR_SRC7_USB_SUSPEND BIT(2) +#define AR9170_USB_INTR_SRC7_USB_RESUME BIT(3) +#define AR9170_USB_INTR_SRC7_ISO_SEQ_ERR BIT(4) +#define AR9170_USB_INTR_SRC7_ISO_SEQ_ABORT BIT(5) +#define AR9170_USB_INTR_SRC7_TX0BYTE BIT(6) +#define AR9170_USB_INTR_SRC7_RX0BYTE BIT(7) + +#define AR9170_USB_REG_IDLE_COUNT (AR9170_USB_REG_BASE + 0x02f) + +#define AR9170_USB_REG_EP_MAP (AR9170_USB_REG_BASE + 0x030) +#define AR9170_USB_REG_EP1_MAP (AR9170_USB_REG_BASE + 0x030) +#define AR9170_USB_REG_EP2_MAP (AR9170_USB_REG_BASE + 0x031) +#define AR9170_USB_REG_EP3_MAP (AR9170_USB_REG_BASE + 0x032) +#define AR9170_USB_REG_EP4_MAP (AR9170_USB_REG_BASE + 0x033) +#define AR9170_USB_REG_EP5_MAP (AR9170_USB_REG_BASE + 0x034) +#define AR9170_USB_REG_EP6_MAP (AR9170_USB_REG_BASE + 0x035) +#define AR9170_USB_REG_EP7_MAP (AR9170_USB_REG_BASE + 0x036) +#define AR9170_USB_REG_EP8_MAP (AR9170_USB_REG_BASE + 0x037) +#define AR9170_USB_REG_EP9_MAP (AR9170_USB_REG_BASE + 0x038) +#define AR9170_USB_REG_EP10_MAP (AR9170_USB_REG_BASE + 0x039) + +#define AR9170_USB_REG_EP_IN_MAX_SIZE_HIGH (AR9170_USB_REG_BASE + 0x03f) +#define AR9170_USB_EP_IN_TOGGLE 0x10 + +#define AR9170_USB_REG_EP_IN_MAX_SIZE_LOW (AR9170_USB_REG_BASE + 0x03e) + +#define AR9170_USB_REG_EP_OUT_MAX_SIZE_HIGH (AR9170_USB_REG_BASE + 0x05f) +#define AR9170_USB_EP_OUT_TOGGLE 0x10 + +#define AR9170_USB_REG_EP_OUT_MAX_SIZE_LOW (AR9170_USB_REG_BASE + 0x05e) + +#define AR9170_USB_REG_EP3_BYTE_COUNT_HIGH (AR9170_USB_REG_BASE + 0x0ae) +#define AR9170_USB_REG_EP3_BYTE_COUNT_LOW (AR9170_USB_REG_BASE + 0x0be) +#define AR9170_USB_REG_EP4_BYTE_COUNT_HIGH (AR9170_USB_REG_BASE + 0x0af) +#define AR9170_USB_REG_EP4_BYTE_COUNT_LOW (AR9170_USB_REG_BASE + 0x0bf) + +#define AR9170_USB_REG_FIFO_MAP (AR9170_USB_REG_BASE + 0x080) +#define AR9170_USB_REG_FIFO0_MAP (AR9170_USB_REG_BASE + 0x080) +#define AR9170_USB_REG_FIFO1_MAP (AR9170_USB_REG_BASE + 0x081) +#define AR9170_USB_REG_FIFO2_MAP (AR9170_USB_REG_BASE + 0x082) +#define AR9170_USB_REG_FIFO3_MAP (AR9170_USB_REG_BASE + 0x083) +#define AR9170_USB_REG_FIFO4_MAP (AR9170_USB_REG_BASE + 0x084) +#define AR9170_USB_REG_FIFO5_MAP (AR9170_USB_REG_BASE + 0x085) +#define AR9170_USB_REG_FIFO6_MAP (AR9170_USB_REG_BASE + 0x086) +#define AR9170_USB_REG_FIFO7_MAP (AR9170_USB_REG_BASE + 0x087) +#define AR9170_USB_REG_FIFO8_MAP (AR9170_USB_REG_BASE + 0x088) +#define AR9170_USB_REG_FIFO9_MAP (AR9170_USB_REG_BASE + 0x089) + +#define AR9170_USB_REG_FIFO_CONFIG (AR9170_USB_REG_BASE + 0x090) +#define AR9170_USB_REG_FIFO0_CONFIG (AR9170_USB_REG_BASE + 0x090) +#define AR9170_USB_REG_FIFO1_CONFIG (AR9170_USB_REG_BASE + 0x091) +#define AR9170_USB_REG_FIFO2_CONFIG (AR9170_USB_REG_BASE + 0x092) +#define AR9170_USB_REG_FIFO3_CONFIG (AR9170_USB_REG_BASE + 0x093) +#define AR9170_USB_REG_FIFO4_CONFIG (AR9170_USB_REG_BASE + 0x094) +#define AR9170_USB_REG_FIFO5_CONFIG (AR9170_USB_REG_BASE + 0x095) +#define AR9170_USB_REG_FIFO6_CONFIG (AR9170_USB_REG_BASE + 0x096) +#define AR9170_USB_REG_FIFO7_CONFIG (AR9170_USB_REG_BASE + 0x097) +#define AR9170_USB_REG_FIFO8_CONFIG (AR9170_USB_REG_BASE + 0x098) +#define AR9170_USB_REG_FIFO9_CONFIG (AR9170_USB_REG_BASE + 0x099) + +#define AR9170_USB_REG_EP3_DATA (AR9170_USB_REG_BASE + 0x0f8) +#define AR9170_USB_REG_EP4_DATA (AR9170_USB_REG_BASE + 0x0fc) + +#define AR9170_USB_REG_FIFO_SIZE (AR9170_USB_REG_BASE + 0x100) +#define AR9170_USB_REG_DMA_CTL (AR9170_USB_REG_BASE + 0x108) +#define AR9170_USB_DMA_CTL_ENABLE_TO_DEVICE BIT(0) +#define AR9170_USB_DMA_CTL_ENABLE_FROM_DEVICE BIT(1) +#define AR9170_USB_DMA_CTL_HIGH_SPEED BIT(2) +#define AR9170_USB_DMA_CTL_UP_PACKET_MODE BIT(3) +#define AR9170_USB_DMA_CTL_UP_STREAM_S 4 +#define AR9170_USB_DMA_CTL_UP_STREAM (BIT(4) | BIT(5)) +#define AR9170_USB_DMA_CTL_UP_STREAM_4K (0) +#define AR9170_USB_DMA_CTL_UP_STREAM_8K BIT(4) +#define AR9170_USB_DMA_CTL_UP_STREAM_16K BIT(5) +#define AR9170_USB_DMA_CTL_UP_STREAM_32K (BIT(4) | BIT(5)) +#define AR9170_USB_DMA_CTL_DOWN_STREAM BIT(6) + +#define AR9170_USB_REG_DMA_STATUS (AR9170_USB_REG_BASE + 0x10c) +#define AR9170_USB_DMA_STATUS_UP_IDLE BIT(8) +#define AR9170_USB_DMA_STATUS_DN_IDLE BIT(16) + +#define AR9170_USB_REG_MAX_AGG_UPLOAD (AR9170_USB_REG_BASE + 0x110) +#define AR9170_USB_REG_UPLOAD_TIME_CTL (AR9170_USB_REG_BASE + 0x114) + +#define AR9170_USB_REG_WAKE_UP (AR9170_USB_REG_BASE + 0x120) +#define AR9170_USB_WAKE_UP_WAKE BIT(0) + +#define AR9170_USB_REG_CBUS_CTRL (AR9170_USB_REG_BASE + 0x1f0) +#define AR9170_USB_CBUS_CTRL_BUFFER_END (BIT(1)) + +/* PCI/USB to AHB Bridge */ +#define AR9170_PTA_REG_BASE 0x1e2000 + +#define AR9170_PTA_REG_CMD (AR9170_PTA_REG_BASE + 0x000) +#define AR9170_PTA_REG_PARAM1 (AR9170_PTA_REG_BASE + 0x004) +#define AR9170_PTA_REG_PARAM2 (AR9170_PTA_REG_BASE + 0x008) +#define AR9170_PTA_REG_PARAM3 (AR9170_PTA_REG_BASE + 0x00c) +#define AR9170_PTA_REG_RSP (AR9170_PTA_REG_BASE + 0x010) +#define AR9170_PTA_REG_STATUS1 (AR9170_PTA_REG_BASE + 0x014) +#define AR9170_PTA_REG_STATUS2 (AR9170_PTA_REG_BASE + 0x018) +#define AR9170_PTA_REG_STATUS3 (AR9170_PTA_REG_BASE + 0x01c) +#define AR9170_PTA_REG_AHB_INT_FLAG (AR9170_PTA_REG_BASE + 0x020) +#define AR9170_PTA_REG_AHB_INT_MASK (AR9170_PTA_REG_BASE + 0x024) +#define AR9170_PTA_REG_AHB_INT_ACK (AR9170_PTA_REG_BASE + 0x028) +#define AR9170_PTA_REG_AHB_SCRATCH1 (AR9170_PTA_REG_BASE + 0x030) +#define AR9170_PTA_REG_AHB_SCRATCH2 (AR9170_PTA_REG_BASE + 0x034) +#define AR9170_PTA_REG_AHB_SCRATCH3 (AR9170_PTA_REG_BASE + 0x038) +#define AR9170_PTA_REG_AHB_SCRATCH4 (AR9170_PTA_REG_BASE + 0x03c) + +#define AR9170_PTA_REG_SHARE_MEM_CTRL (AR9170_PTA_REG_BASE + 0x124) + +/* + * PCI to AHB Bridge + */ + +#define AR9170_PTA_REG_INT_FLAG (AR9170_PTA_REG_BASE + 0x100) +#define AR9170_PTA_INT_FLAG_DN 0x01 +#define AR9170_PTA_INT_FLAG_UP 0x02 +#define AR9170_PTA_INT_FLAG_CMD 0x04 + +#define AR9170_PTA_REG_INT_MASK (AR9170_PTA_REG_BASE + 0x104) +#define AR9170_PTA_REG_DN_DMA_ADDRL (AR9170_PTA_REG_BASE + 0x108) +#define AR9170_PTA_REG_DN_DMA_ADDRH (AR9170_PTA_REG_BASE + 0x10c) +#define AR9170_PTA_REG_UP_DMA_ADDRL (AR9170_PTA_REG_BASE + 0x110) +#define AR9170_PTA_REG_UP_DMA_ADDRH (AR9170_PTA_REG_BASE + 0x114) +#define AR9170_PTA_REG_DN_PEND_TIME (AR9170_PTA_REG_BASE + 0x118) +#define AR9170_PTA_REG_UP_PEND_TIME (AR9170_PTA_REG_BASE + 0x11c) +#define AR9170_PTA_REG_CONTROL (AR9170_PTA_REG_BASE + 0x120) +#define AR9170_PTA_CTRL_4_BEAT_BURST 0x00 +#define AR9170_PTA_CTRL_8_BEAT_BURST 0x01 +#define AR9170_PTA_CTRL_16_BEAT_BURST 0x02 +#define AR9170_PTA_CTRL_LOOPBACK_MODE 0x10 + +#define AR9170_PTA_REG_MEM_CTRL (AR9170_PTA_REG_BASE + 0x124) +#define AR9170_PTA_REG_MEM_ADDR (AR9170_PTA_REG_BASE + 0x128) +#define AR9170_PTA_REG_DN_DMA_TRIGGER (AR9170_PTA_REG_BASE + 0x12c) +#define AR9170_PTA_REG_UP_DMA_TRIGGER (AR9170_PTA_REG_BASE + 0x130) +#define AR9170_PTA_REG_DMA_STATUS (AR9170_PTA_REG_BASE + 0x134) +#define AR9170_PTA_REG_DN_CURR_ADDRL (AR9170_PTA_REG_BASE + 0x138) +#define AR9170_PTA_REG_DN_CURR_ADDRH (AR9170_PTA_REG_BASE + 0x13c) +#define AR9170_PTA_REG_UP_CURR_ADDRL (AR9170_PTA_REG_BASE + 0x140) +#define AR9170_PTA_REG_UP_CURR_ADDRH (AR9170_PTA_REG_BASE + 0x144) +#define AR9170_PTA_REG_DMA_MODE_CTRL (AR9170_PTA_REG_BASE + 0x148) +#define AR9170_PTA_DMA_MODE_CTRL_RESET BIT(0) +#define AR9170_PTA_DMA_MODE_CTRL_DISABLE_USB BIT(1) + +/* Protocol Controller Module */ +#define AR9170_MAC_REG_PC_REG_BASE (AR9170_MAC_REG_BASE + 0xe00) + + +#define AR9170_NUM_LEDS 2 + +/* CAM */ +#define AR9170_CAM_MAX_USER 64 +#define AR9170_CAM_MAX_KEY_LENGTH 16 + +#define AR9170_SRAM_OFFSET 0x100000 +#define AR9170_SRAM_SIZE 0x18000 + +#define AR9170_PRAM_OFFSET 0x200000 +#define AR9170_PRAM_SIZE 0x8000 + +enum cpu_clock { + AHB_STATIC_40MHZ = 0, + AHB_GMODE_22MHZ = 1, + AHB_AMODE_20MHZ = 1, + AHB_GMODE_44MHZ = 2, + AHB_AMODE_40MHZ = 2, + AHB_GMODE_88MHZ = 3, + AHB_AMODE_80MHZ = 3 +}; + +/* USB endpoints */ +enum ar9170_usb_ep { + /* + * Control EP is always EP 0 (USB SPEC) + * + * The weird thing is: the original firmware has a few + * comments that suggest that the actual EP numbers + * are in the 1 to 10 range?! + */ + AR9170_USB_EP_CTRL = 0, + + AR9170_USB_EP_TX, + AR9170_USB_EP_RX, + AR9170_USB_EP_IRQ, + AR9170_USB_EP_CMD, + AR9170_USB_NUM_EXTRA_EP = 4, + + __AR9170_USB_NUM_EP, + + __AR9170_USB_NUM_MAX_EP = 10 +}; + +enum ar9170_usb_fifo { + __AR9170_USB_NUM_MAX_FIFO = 10 +}; + +enum ar9170_tx_queues { + AR9170_TXQ0 = 0, + AR9170_TXQ1, + AR9170_TXQ2, + AR9170_TXQ3, + AR9170_TXQ_SPECIAL, + + /* keep last */ + __AR9170_NUM_TX_QUEUES = 5 +}; + +#define AR9170_TX_STREAM_TAG 0x697e +#define AR9170_RX_STREAM_TAG 0x4e00 +#define AR9170_RX_STREAM_MAX_SIZE 0xffff + +struct ar9170_stream { + __le16 length; + __le16 tag; + + u8 payload[0]; +} __packed __aligned(4); +#define AR9170_STREAM_LEN 4 + +#define AR9170_MAX_ACKTABLE_ENTRIES 8 +#define AR9170_MAX_VIRTUAL_MAC 7 + +#define AR9170_USB_EP_CTRL_MAX 64 +#define AR9170_USB_EP_TX_MAX 512 +#define AR9170_USB_EP_RX_MAX 512 +#define AR9170_USB_EP_IRQ_MAX 64 +#define AR9170_USB_EP_CMD_MAX 64 + +/* Trigger PRETBTT interrupt 6 Kus earlier */ +#define CARL9170_PRETBTT_KUS 6 + +#define AR5416_MAX_RATE_POWER 63 + +#define SET_VAL(reg, value, newvalue) \ + (value = ((value) & ~reg) | (((newvalue) << reg##_S) & reg)) + +#define SET_CONSTVAL(reg, newvalue) \ + (((newvalue) << reg##_S) & reg) + +#define MOD_VAL(reg, value, newvalue) \ + (((value) & ~reg) | (((newvalue) << reg##_S) & reg)) + +#define GET_VAL(reg, value) \ + (((value) & reg) >> reg##_S) + +#endif /* __CARL9170_SHARED_HW_H */ diff --git a/drivers/net/wireless/ath/carl9170/led.c b/drivers/net/wireless/ath/carl9170/led.c new file mode 100644 index 0000000..a7fc979 --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/led.c @@ -0,0 +1,190 @@ +/* + * Atheros CARL9170 driver + * + * LED handling + * + * Copyright 2008, Johannes Berg + * Copyright 2009, 2010, Christian Lamparer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * Copyright (c) 2007-2008 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "carl9170.h" +#include "cmd.h" + +int carl9170_led_set_state(struct ar9170 *ar, const u32 led_state) +{ + return carl9170_write_reg(ar, AR9170_GPIO_REG_PORT_DATA, led_state); +} + +int carl9170_led_init(struct ar9170 *ar) +{ + int err; + + /* disable LEDs */ + /* GPIO [0/1 mode: output, 2/3: input] */ + err = carl9170_write_reg(ar, AR9170_GPIO_REG_PORT_TYPE, 3); + if (err) + goto out; + + /* GPIO 0/1 value: off */ + err = carl9170_led_set_state(ar, 0); + +out: + return err; +} + +#ifdef CPTCFG_CARL9170_LEDS +static void carl9170_led_update(struct work_struct *work) +{ + struct ar9170 *ar = container_of(work, struct ar9170, led_work.work); + int i, tmp = 300, blink_delay = 1000; + u32 led_val = 0; + bool rerun = false; + + if (!IS_ACCEPTING_CMD(ar)) + return; + + mutex_lock(&ar->mutex); + for (i = 0; i < AR9170_NUM_LEDS; i++) { + if (ar->leds[i].registered) { + if (ar->leds[i].last_state || + ar->leds[i].toggled) { + + if (ar->leds[i].toggled) + tmp = 70 + 200 / (ar->leds[i].toggled); + + if (tmp < blink_delay) + blink_delay = tmp; + + led_val |= 1 << i; + ar->leds[i].toggled = 0; + rerun = true; + } + } + } + + carl9170_led_set_state(ar, led_val); + mutex_unlock(&ar->mutex); + + if (!rerun) + return; + + ieee80211_queue_delayed_work(ar->hw, + &ar->led_work, + msecs_to_jiffies(blink_delay)); +} + +static void carl9170_led_set_brightness(struct led_classdev *led, + enum led_brightness brightness) +{ + struct carl9170_led *arl = container_of(led, struct carl9170_led, l); + struct ar9170 *ar = arl->ar; + + if (!arl->registered) + return; + + if (arl->last_state != !!brightness) { + arl->toggled++; + arl->last_state = !!brightness; + } + + if (likely(IS_ACCEPTING_CMD(ar) && arl->toggled)) + ieee80211_queue_delayed_work(ar->hw, &ar->led_work, HZ / 10); +} + +static int carl9170_led_register_led(struct ar9170 *ar, int i, char *name, + char *trigger) +{ + int err; + + snprintf(ar->leds[i].name, sizeof(ar->leds[i].name), + "carl9170-%s::%s", wiphy_name(ar->hw->wiphy), name); + + ar->leds[i].ar = ar; + ar->leds[i].l.name = ar->leds[i].name; + ar->leds[i].l.brightness_set = carl9170_led_set_brightness; + ar->leds[i].l.brightness = 0; + ar->leds[i].l.default_trigger = trigger; + + err = led_classdev_register(wiphy_dev(ar->hw->wiphy), + &ar->leds[i].l); + if (err) { + wiphy_err(ar->hw->wiphy, "failed to register %s LED (%d).\n", + ar->leds[i].name, err); + } else { + ar->leds[i].registered = true; + } + + return err; +} + +void carl9170_led_unregister(struct ar9170 *ar) +{ + int i; + + for (i = 0; i < AR9170_NUM_LEDS; i++) + if (ar->leds[i].registered) { + led_classdev_unregister(&ar->leds[i].l); + ar->leds[i].registered = false; + ar->leds[i].toggled = 0; + } + + cancel_delayed_work_sync(&ar->led_work); +} + +int carl9170_led_register(struct ar9170 *ar) +{ + int err; + + INIT_DELAYED_WORK(&ar->led_work, carl9170_led_update); + + err = carl9170_led_register_led(ar, 0, "tx", + ieee80211_get_tx_led_name(ar->hw)); + if (err) + goto fail; + + if (ar->features & CARL9170_ONE_LED) + return 0; + + err = carl9170_led_register_led(ar, 1, "assoc", + ieee80211_get_assoc_led_name(ar->hw)); + if (err) + goto fail; + + return 0; + +fail: + carl9170_led_unregister(ar); + return err; +} + +#endif /* CPTCFG_CARL9170_LEDS */ diff --git a/drivers/net/wireless/ath/carl9170/mac.c b/drivers/net/wireless/ath/carl9170/mac.c new file mode 100644 index 0000000..a2f0057 --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/mac.c @@ -0,0 +1,538 @@ +/* + * Atheros CARL9170 driver + * + * MAC programming + * + * Copyright 2008, Johannes Berg + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * Copyright (c) 2007-2008 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "carl9170.h" +#include "cmd.h" + +int carl9170_set_dyn_sifs_ack(struct ar9170 *ar) +{ + u32 val; + + if (conf_is_ht40(&ar->hw->conf)) + val = 0x010a; + else { + if (ar->hw->conf.chandef.chan->band == IEEE80211_BAND_2GHZ) + val = 0x105; + else + val = 0x104; + } + + return carl9170_write_reg(ar, AR9170_MAC_REG_DYNAMIC_SIFS_ACK, val); +} + +int carl9170_set_rts_cts_rate(struct ar9170 *ar) +{ + u32 rts_rate, cts_rate; + + if (conf_is_ht(&ar->hw->conf)) { + /* 12 mbit OFDM */ + rts_rate = 0x1da; + cts_rate = 0x10a; + } else { + if (ar->hw->conf.chandef.chan->band == IEEE80211_BAND_2GHZ) { + /* 11 mbit CCK */ + rts_rate = 033; + cts_rate = 003; + } else { + /* 6 mbit OFDM */ + rts_rate = 0x1bb; + cts_rate = 0x10b; + } + } + + return carl9170_write_reg(ar, AR9170_MAC_REG_RTS_CTS_RATE, + rts_rate | (cts_rate) << 16); +} + +int carl9170_set_slot_time(struct ar9170 *ar) +{ + struct ieee80211_vif *vif; + u32 slottime = 20; + + rcu_read_lock(); + vif = carl9170_get_main_vif(ar); + if (!vif) { + rcu_read_unlock(); + return 0; + } + + if ((ar->hw->conf.chandef.chan->band == IEEE80211_BAND_5GHZ) || + vif->bss_conf.use_short_slot) + slottime = 9; + + rcu_read_unlock(); + + return carl9170_write_reg(ar, AR9170_MAC_REG_SLOT_TIME, + slottime << 10); +} + +int carl9170_set_mac_rates(struct ar9170 *ar) +{ + struct ieee80211_vif *vif; + u32 basic, mandatory; + + rcu_read_lock(); + vif = carl9170_get_main_vif(ar); + + if (!vif) { + rcu_read_unlock(); + return 0; + } + + basic = (vif->bss_conf.basic_rates & 0xf); + basic |= (vif->bss_conf.basic_rates & 0xff0) << 4; + rcu_read_unlock(); + + if (ar->hw->conf.chandef.chan->band == IEEE80211_BAND_5GHZ) + mandatory = 0xff00; /* OFDM 6/9/12/18/24/36/48/54 */ + else + mandatory = 0xff0f; /* OFDM (6/9../54) + CCK (1/2/5.5/11) */ + + carl9170_regwrite_begin(ar); + carl9170_regwrite(AR9170_MAC_REG_BASIC_RATE, basic); + carl9170_regwrite(AR9170_MAC_REG_MANDATORY_RATE, mandatory); + carl9170_regwrite_finish(); + + return carl9170_regwrite_result(); +} + +int carl9170_set_qos(struct ar9170 *ar) +{ + carl9170_regwrite_begin(ar); + + carl9170_regwrite(AR9170_MAC_REG_AC0_CW, ar->edcf[0].cw_min | + (ar->edcf[0].cw_max << 16)); + carl9170_regwrite(AR9170_MAC_REG_AC1_CW, ar->edcf[1].cw_min | + (ar->edcf[1].cw_max << 16)); + carl9170_regwrite(AR9170_MAC_REG_AC2_CW, ar->edcf[2].cw_min | + (ar->edcf[2].cw_max << 16)); + carl9170_regwrite(AR9170_MAC_REG_AC3_CW, ar->edcf[3].cw_min | + (ar->edcf[3].cw_max << 16)); + carl9170_regwrite(AR9170_MAC_REG_AC4_CW, ar->edcf[4].cw_min | + (ar->edcf[4].cw_max << 16)); + + carl9170_regwrite(AR9170_MAC_REG_AC2_AC1_AC0_AIFS, + ((ar->edcf[0].aifs * 9 + 10)) | + ((ar->edcf[1].aifs * 9 + 10) << 12) | + ((ar->edcf[2].aifs * 9 + 10) << 24)); + carl9170_regwrite(AR9170_MAC_REG_AC4_AC3_AC2_AIFS, + ((ar->edcf[2].aifs * 9 + 10) >> 8) | + ((ar->edcf[3].aifs * 9 + 10) << 4) | + ((ar->edcf[4].aifs * 9 + 10) << 16)); + + carl9170_regwrite(AR9170_MAC_REG_AC1_AC0_TXOP, + ar->edcf[0].txop | ar->edcf[1].txop << 16); + carl9170_regwrite(AR9170_MAC_REG_AC3_AC2_TXOP, + ar->edcf[2].txop | ar->edcf[3].txop << 16 | + ar->edcf[4].txop << 24); + + carl9170_regwrite_finish(); + + return carl9170_regwrite_result(); +} + +int carl9170_init_mac(struct ar9170 *ar) +{ + carl9170_regwrite_begin(ar); + + /* switch MAC to OTUS interface */ + carl9170_regwrite(0x1c3600, 0x3); + + carl9170_regwrite(AR9170_MAC_REG_ACK_EXTENSION, 0x40); + + carl9170_regwrite(AR9170_MAC_REG_RETRY_MAX, 0x0); + + carl9170_regwrite(AR9170_MAC_REG_FRAMETYPE_FILTER, + AR9170_MAC_FTF_MONITOR); + + /* enable MMIC */ + carl9170_regwrite(AR9170_MAC_REG_SNIFFER, + AR9170_MAC_SNIFFER_DEFAULTS); + + carl9170_regwrite(AR9170_MAC_REG_RX_THRESHOLD, 0xc1f80); + + carl9170_regwrite(AR9170_MAC_REG_RX_PE_DELAY, 0x70); + carl9170_regwrite(AR9170_MAC_REG_EIFS_AND_SIFS, 0xa144000); + carl9170_regwrite(AR9170_MAC_REG_SLOT_TIME, 9 << 10); + + /* CF-END & CF-ACK rate => 24M OFDM */ + carl9170_regwrite(AR9170_MAC_REG_TID_CFACK_CFEND_RATE, 0x59900000); + + /* NAV protects ACK only (in TXOP) */ + carl9170_regwrite(AR9170_MAC_REG_TXOP_DURATION, 0x201); + + /* Set Beacon PHY CTRL's TPC to 0x7, TA1=1 */ + /* OTUS set AM to 0x1 */ + carl9170_regwrite(AR9170_MAC_REG_BCN_HT1, 0x8000170); + + carl9170_regwrite(AR9170_MAC_REG_BACKOFF_PROTECT, 0x105); + + /* Aggregation MAX number and timeout */ + carl9170_regwrite(AR9170_MAC_REG_AMPDU_FACTOR, 0x8000a); + carl9170_regwrite(AR9170_MAC_REG_AMPDU_DENSITY, 0x140a07); + + carl9170_regwrite(AR9170_MAC_REG_FRAMETYPE_FILTER, + AR9170_MAC_FTF_DEFAULTS); + + carl9170_regwrite(AR9170_MAC_REG_RX_CONTROL, + AR9170_MAC_RX_CTRL_DEAGG | + AR9170_MAC_RX_CTRL_SHORT_FILTER); + + /* rate sets */ + carl9170_regwrite(AR9170_MAC_REG_BASIC_RATE, 0x150f); + carl9170_regwrite(AR9170_MAC_REG_MANDATORY_RATE, 0x150f); + carl9170_regwrite(AR9170_MAC_REG_RTS_CTS_RATE, 0x0030033); + + /* MIMO response control */ + carl9170_regwrite(AR9170_MAC_REG_ACK_TPC, 0x4003c1e); + + carl9170_regwrite(AR9170_MAC_REG_AMPDU_RX_THRESH, 0xffff); + + /* set PHY register read timeout (??) */ + carl9170_regwrite(AR9170_MAC_REG_MISC_680, 0xf00008); + + /* Disable Rx TimeOut, workaround for BB. */ + carl9170_regwrite(AR9170_MAC_REG_RX_TIMEOUT, 0x0); + + /* Set WLAN DMA interrupt mode: generate int per packet */ + carl9170_regwrite(AR9170_MAC_REG_TXRX_MPI, 0x110011); + + carl9170_regwrite(AR9170_MAC_REG_FCS_SELECT, + AR9170_MAC_FCS_FIFO_PROT); + + /* Disables the CF_END frame, undocumented register */ + carl9170_regwrite(AR9170_MAC_REG_TXOP_NOT_ENOUGH_IND, + 0x141e0f48); + + /* reset group hash table */ + carl9170_regwrite(AR9170_MAC_REG_GROUP_HASH_TBL_L, 0xffffffff); + carl9170_regwrite(AR9170_MAC_REG_GROUP_HASH_TBL_H, 0xffffffff); + + /* disable PRETBTT interrupt */ + carl9170_regwrite(AR9170_MAC_REG_PRETBTT, 0x0); + carl9170_regwrite(AR9170_MAC_REG_BCN_PERIOD, 0x0); + + carl9170_regwrite_finish(); + + return carl9170_regwrite_result(); +} + +static int carl9170_set_mac_reg(struct ar9170 *ar, + const u32 reg, const u8 *mac) +{ + static const u8 zero[ETH_ALEN] = { 0 }; + + if (!mac) + mac = zero; + + carl9170_regwrite_begin(ar); + + carl9170_regwrite(reg, get_unaligned_le32(mac)); + carl9170_regwrite(reg + 4, get_unaligned_le16(mac + 4)); + + carl9170_regwrite_finish(); + + return carl9170_regwrite_result(); +} + +int carl9170_mod_virtual_mac(struct ar9170 *ar, const unsigned int id, + const u8 *mac) +{ + if (WARN_ON(id >= ar->fw.vif_num)) + return -EINVAL; + + return carl9170_set_mac_reg(ar, + AR9170_MAC_REG_ACK_TABLE + (id - 1) * 8, mac); +} + +int carl9170_update_multicast(struct ar9170 *ar, const u64 mc_hash) +{ + int err; + + carl9170_regwrite_begin(ar); + carl9170_regwrite(AR9170_MAC_REG_GROUP_HASH_TBL_H, mc_hash >> 32); + carl9170_regwrite(AR9170_MAC_REG_GROUP_HASH_TBL_L, mc_hash); + carl9170_regwrite_finish(); + err = carl9170_regwrite_result(); + if (err) + return err; + + ar->cur_mc_hash = mc_hash; + return 0; +} + +int carl9170_set_operating_mode(struct ar9170 *ar) +{ + struct ieee80211_vif *vif; + struct ath_common *common = &ar->common; + u8 *mac_addr, *bssid; + u32 cam_mode = AR9170_MAC_CAM_DEFAULTS; + u32 enc_mode = AR9170_MAC_ENCRYPTION_DEFAULTS | + AR9170_MAC_ENCRYPTION_MGMT_RX_SOFTWARE; + u32 rx_ctrl = AR9170_MAC_RX_CTRL_DEAGG | + AR9170_MAC_RX_CTRL_SHORT_FILTER; + u32 sniffer = AR9170_MAC_SNIFFER_DEFAULTS; + int err = 0; + + rcu_read_lock(); + vif = carl9170_get_main_vif(ar); + + if (vif) { + mac_addr = common->macaddr; + bssid = common->curbssid; + + switch (vif->type) { + case NL80211_IFTYPE_ADHOC: + cam_mode |= AR9170_MAC_CAM_IBSS; + break; + case NL80211_IFTYPE_MESH_POINT: + case NL80211_IFTYPE_AP: + cam_mode |= AR9170_MAC_CAM_AP; + + /* iwlagn 802.11n STA Workaround */ + rx_ctrl |= AR9170_MAC_RX_CTRL_PASS_TO_HOST; + break; + case NL80211_IFTYPE_WDS: + cam_mode |= AR9170_MAC_CAM_AP_WDS; + rx_ctrl |= AR9170_MAC_RX_CTRL_PASS_TO_HOST; + break; + case NL80211_IFTYPE_STATION: + cam_mode |= AR9170_MAC_CAM_STA; + rx_ctrl |= AR9170_MAC_RX_CTRL_PASS_TO_HOST; + break; + default: + WARN(1, "Unsupported operation mode %x\n", vif->type); + err = -EOPNOTSUPP; + break; + } + } else { + /* + * Enable monitor mode + * + * rx_ctrl |= AR9170_MAC_RX_CTRL_ACK_IN_SNIFFER; + * sniffer |= AR9170_MAC_SNIFFER_ENABLE_PROMISC; + * + * When the hardware is in SNIFFER_PROMISC mode, + * it generates spurious ACKs for every incoming + * frame. This confuses every peer in the + * vicinity and the network throughput will suffer + * badly. + * + * Hence, the hardware will be put into station + * mode and just the rx filters are disabled. + */ + cam_mode |= AR9170_MAC_CAM_STA; + rx_ctrl |= AR9170_MAC_RX_CTRL_PASS_TO_HOST; + mac_addr = common->macaddr; + bssid = NULL; + } + rcu_read_unlock(); + + if (err) + return err; + + if (ar->rx_software_decryption) + enc_mode |= AR9170_MAC_ENCRYPTION_RX_SOFTWARE; + + if (ar->sniffer_enabled) { + enc_mode |= AR9170_MAC_ENCRYPTION_RX_SOFTWARE; + } + + err = carl9170_set_mac_reg(ar, AR9170_MAC_REG_MAC_ADDR_L, mac_addr); + if (err) + return err; + + err = carl9170_set_mac_reg(ar, AR9170_MAC_REG_BSSID_L, bssid); + if (err) + return err; + + carl9170_regwrite_begin(ar); + carl9170_regwrite(AR9170_MAC_REG_SNIFFER, sniffer); + carl9170_regwrite(AR9170_MAC_REG_CAM_MODE, cam_mode); + carl9170_regwrite(AR9170_MAC_REG_ENCRYPTION, enc_mode); + carl9170_regwrite(AR9170_MAC_REG_RX_CONTROL, rx_ctrl); + carl9170_regwrite_finish(); + + return carl9170_regwrite_result(); +} + +int carl9170_set_hwretry_limit(struct ar9170 *ar, const unsigned int max_retry) +{ + u32 tmp = min_t(u32, 0x33333, max_retry * 0x11111); + + return carl9170_write_reg(ar, AR9170_MAC_REG_RETRY_MAX, tmp); +} + +int carl9170_set_beacon_timers(struct ar9170 *ar) +{ + struct ieee80211_vif *vif; + u32 v = 0; + u32 pretbtt = 0; + + rcu_read_lock(); + vif = carl9170_get_main_vif(ar); + + if (vif) { + struct carl9170_vif_info *mvif; + mvif = (void *) vif->drv_priv; + + if (mvif->enable_beacon && !WARN_ON(!ar->beacon_enabled)) { + ar->global_beacon_int = vif->bss_conf.beacon_int / + ar->beacon_enabled; + + SET_VAL(AR9170_MAC_BCN_DTIM, v, + vif->bss_conf.dtim_period); + + switch (vif->type) { + case NL80211_IFTYPE_MESH_POINT: + case NL80211_IFTYPE_ADHOC: + v |= AR9170_MAC_BCN_IBSS_MODE; + break; + case NL80211_IFTYPE_AP: + v |= AR9170_MAC_BCN_AP_MODE; + break; + default: + WARN_ON_ONCE(1); + break; + } + } else if (vif->type == NL80211_IFTYPE_STATION) { + ar->global_beacon_int = vif->bss_conf.beacon_int; + + SET_VAL(AR9170_MAC_BCN_DTIM, v, + ar->hw->conf.ps_dtim_period); + + v |= AR9170_MAC_BCN_STA_PS | + AR9170_MAC_BCN_PWR_MGT; + } + + if (ar->global_beacon_int) { + if (ar->global_beacon_int < 15) { + rcu_read_unlock(); + return -ERANGE; + } + + ar->global_pretbtt = ar->global_beacon_int - + CARL9170_PRETBTT_KUS; + } else { + ar->global_pretbtt = 0; + } + } else { + ar->global_beacon_int = 0; + ar->global_pretbtt = 0; + } + + rcu_read_unlock(); + + SET_VAL(AR9170_MAC_BCN_PERIOD, v, ar->global_beacon_int); + SET_VAL(AR9170_MAC_PRETBTT, pretbtt, ar->global_pretbtt); + SET_VAL(AR9170_MAC_PRETBTT2, pretbtt, ar->global_pretbtt); + + carl9170_regwrite_begin(ar); + carl9170_regwrite(AR9170_MAC_REG_PRETBTT, pretbtt); + carl9170_regwrite(AR9170_MAC_REG_BCN_PERIOD, v); + carl9170_regwrite_finish(); + return carl9170_regwrite_result(); +} + +int carl9170_upload_key(struct ar9170 *ar, const u8 id, const u8 *mac, + const u8 ktype, const u8 keyidx, const u8 *keydata, + const int keylen) +{ + struct carl9170_set_key_cmd key = { }; + static const u8 bcast[ETH_ALEN] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + mac = mac ? : bcast; + + key.user = cpu_to_le16(id); + key.keyId = cpu_to_le16(keyidx); + key.type = cpu_to_le16(ktype); + memcpy(&key.macAddr, mac, ETH_ALEN); + if (keydata) + memcpy(&key.key, keydata, keylen); + + return carl9170_exec_cmd(ar, CARL9170_CMD_EKEY, + sizeof(key), (u8 *)&key, 0, NULL); +} + +int carl9170_disable_key(struct ar9170 *ar, const u8 id) +{ + struct carl9170_disable_key_cmd key = { }; + + key.user = cpu_to_le16(id); + + return carl9170_exec_cmd(ar, CARL9170_CMD_DKEY, + sizeof(key), (u8 *)&key, 0, NULL); +} + +int carl9170_set_mac_tpc(struct ar9170 *ar, struct ieee80211_channel *channel) +{ + unsigned int power, chains; + + if (ar->eeprom.tx_mask != 1) + chains = AR9170_TX_PHY_TXCHAIN_2; + else + chains = AR9170_TX_PHY_TXCHAIN_1; + + switch (channel->band) { + case IEEE80211_BAND_2GHZ: + power = ar->power_2G_ofdm[0] & 0x3f; + break; + case IEEE80211_BAND_5GHZ: + power = ar->power_5G_leg[0] & 0x3f; + break; + default: + BUG_ON(1); + } + + power = min_t(unsigned int, power, ar->hw->conf.power_level * 2); + + carl9170_regwrite_begin(ar); + carl9170_regwrite(AR9170_MAC_REG_ACK_TPC, + 0x3c1e | power << 20 | chains << 26); + carl9170_regwrite(AR9170_MAC_REG_RTS_CTS_TPC, + power << 5 | chains << 11 | + power << 21 | chains << 27); + carl9170_regwrite(AR9170_MAC_REG_CFEND_QOSNULL_TPC, + power << 5 | chains << 11 | + power << 21 | chains << 27); + carl9170_regwrite_finish(); + return carl9170_regwrite_result(); +} diff --git a/drivers/net/wireless/ath/carl9170/main.c b/drivers/net/wireless/ath/carl9170/main.c new file mode 100644 index 0000000..59d04c5 --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/main.c @@ -0,0 +1,2148 @@ +/* + * Atheros CARL9170 driver + * + * mac80211 interaction code + * + * Copyright 2008, Johannes Berg + * Copyright 2009, 2010, Christian Lamparter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * Copyright (c) 2007-2008 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "hw.h" +#include "carl9170.h" +#include "cmd.h" + +static bool modparam_nohwcrypt; +module_param_named(nohwcrypt, modparam_nohwcrypt, bool, S_IRUGO); +MODULE_PARM_DESC(nohwcrypt, "Disable hardware crypto offload."); + +int modparam_noht; +module_param_named(noht, modparam_noht, int, S_IRUGO); +MODULE_PARM_DESC(noht, "Disable MPDU aggregation."); + +#define RATE(_bitrate, _hw_rate, _txpidx, _flags) { \ + .bitrate = (_bitrate), \ + .flags = (_flags), \ + .hw_value = (_hw_rate) | (_txpidx) << 4, \ +} + +struct ieee80211_rate __carl9170_ratetable[] = { + RATE(10, 0, 0, 0), + RATE(20, 1, 1, IEEE80211_RATE_SHORT_PREAMBLE), + RATE(55, 2, 2, IEEE80211_RATE_SHORT_PREAMBLE), + RATE(110, 3, 3, IEEE80211_RATE_SHORT_PREAMBLE), + RATE(60, 0xb, 0, 0), + RATE(90, 0xf, 0, 0), + RATE(120, 0xa, 0, 0), + RATE(180, 0xe, 0, 0), + RATE(240, 0x9, 0, 0), + RATE(360, 0xd, 1, 0), + RATE(480, 0x8, 2, 0), + RATE(540, 0xc, 3, 0), +}; +#undef RATE + +#define carl9170_g_ratetable (__carl9170_ratetable + 0) +#define carl9170_g_ratetable_size 12 +#define carl9170_a_ratetable (__carl9170_ratetable + 4) +#define carl9170_a_ratetable_size 8 + +/* + * NB: The hw_value is used as an index into the carl9170_phy_freq_params + * array in phy.c so that we don't have to do frequency lookups! + */ +#define CHAN(_freq, _idx) { \ + .center_freq = (_freq), \ + .hw_value = (_idx), \ + .max_power = 18, /* XXX */ \ +} + +static struct ieee80211_channel carl9170_2ghz_chantable[] = { + CHAN(2412, 0), + CHAN(2417, 1), + CHAN(2422, 2), + CHAN(2427, 3), + CHAN(2432, 4), + CHAN(2437, 5), + CHAN(2442, 6), + CHAN(2447, 7), + CHAN(2452, 8), + CHAN(2457, 9), + CHAN(2462, 10), + CHAN(2467, 11), + CHAN(2472, 12), + CHAN(2484, 13), +}; + +static struct ieee80211_channel carl9170_5ghz_chantable[] = { + CHAN(4920, 14), + CHAN(4940, 15), + CHAN(4960, 16), + CHAN(4980, 17), + CHAN(5040, 18), + CHAN(5060, 19), + CHAN(5080, 20), + CHAN(5180, 21), + CHAN(5200, 22), + CHAN(5220, 23), + CHAN(5240, 24), + CHAN(5260, 25), + CHAN(5280, 26), + CHAN(5300, 27), + CHAN(5320, 28), + CHAN(5500, 29), + CHAN(5520, 30), + CHAN(5540, 31), + CHAN(5560, 32), + CHAN(5580, 33), + CHAN(5600, 34), + CHAN(5620, 35), + CHAN(5640, 36), + CHAN(5660, 37), + CHAN(5680, 38), + CHAN(5700, 39), + CHAN(5745, 40), + CHAN(5765, 41), + CHAN(5785, 42), + CHAN(5805, 43), + CHAN(5825, 44), + CHAN(5170, 45), + CHAN(5190, 46), + CHAN(5210, 47), + CHAN(5230, 48), +}; +#undef CHAN + +#define CARL9170_HT_CAP \ +{ \ + .ht_supported = true, \ + .cap = IEEE80211_HT_CAP_MAX_AMSDU | \ + IEEE80211_HT_CAP_SUP_WIDTH_20_40 | \ + IEEE80211_HT_CAP_SGI_40 | \ + IEEE80211_HT_CAP_DSSSCCK40 | \ + IEEE80211_HT_CAP_SM_PS, \ + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K, \ + .ampdu_density = IEEE80211_HT_MPDU_DENSITY_8, \ + .mcs = { \ + .rx_mask = { 0xff, 0xff, 0, 0, 0x1, 0, 0, 0, 0, 0, }, \ + .rx_highest = cpu_to_le16(300), \ + .tx_params = IEEE80211_HT_MCS_TX_DEFINED, \ + }, \ +} + +static struct ieee80211_supported_band carl9170_band_2GHz = { + .channels = carl9170_2ghz_chantable, + .n_channels = ARRAY_SIZE(carl9170_2ghz_chantable), + .bitrates = carl9170_g_ratetable, + .n_bitrates = carl9170_g_ratetable_size, + .ht_cap = CARL9170_HT_CAP, +}; + +static struct ieee80211_supported_band carl9170_band_5GHz = { + .channels = carl9170_5ghz_chantable, + .n_channels = ARRAY_SIZE(carl9170_5ghz_chantable), + .bitrates = carl9170_a_ratetable, + .n_bitrates = carl9170_a_ratetable_size, + .ht_cap = CARL9170_HT_CAP, +}; + +static void carl9170_ampdu_gc(struct ar9170 *ar) +{ + struct carl9170_sta_tid *tid_info; + LIST_HEAD(tid_gc); + + rcu_read_lock(); + list_for_each_entry_rcu(tid_info, &ar->tx_ampdu_list, list) { + spin_lock_bh(&ar->tx_ampdu_list_lock); + if (tid_info->state == CARL9170_TID_STATE_SHUTDOWN) { + tid_info->state = CARL9170_TID_STATE_KILLED; + list_del_rcu(&tid_info->list); + ar->tx_ampdu_list_len--; + list_add_tail(&tid_info->tmp_list, &tid_gc); + } + spin_unlock_bh(&ar->tx_ampdu_list_lock); + + } + rcu_assign_pointer(ar->tx_ampdu_iter, tid_info); + rcu_read_unlock(); + + synchronize_rcu(); + + while (!list_empty(&tid_gc)) { + struct sk_buff *skb; + tid_info = list_first_entry(&tid_gc, struct carl9170_sta_tid, + tmp_list); + + while ((skb = __skb_dequeue(&tid_info->queue))) + carl9170_tx_status(ar, skb, false); + + list_del_init(&tid_info->tmp_list); + kfree(tid_info); + } +} + +static void carl9170_flush(struct ar9170 *ar, bool drop_queued) +{ + if (drop_queued) { + int i; + + /* + * We can only drop frames which have not been uploaded + * to the device yet. + */ + + for (i = 0; i < ar->hw->queues; i++) { + struct sk_buff *skb; + + while ((skb = skb_dequeue(&ar->tx_pending[i]))) { + struct ieee80211_tx_info *info; + + info = IEEE80211_SKB_CB(skb); + if (info->flags & IEEE80211_TX_CTL_AMPDU) + atomic_dec(&ar->tx_ampdu_upload); + + carl9170_tx_status(ar, skb, false); + } + } + } + + /* Wait for all other outstanding frames to timeout. */ + if (atomic_read(&ar->tx_total_queued)) + WARN_ON(wait_for_completion_timeout(&ar->tx_flush, HZ) == 0); +} + +static void carl9170_flush_ba(struct ar9170 *ar) +{ + struct sk_buff_head free; + struct carl9170_sta_tid *tid_info; + struct sk_buff *skb; + + __skb_queue_head_init(&free); + + rcu_read_lock(); + spin_lock_bh(&ar->tx_ampdu_list_lock); + list_for_each_entry_rcu(tid_info, &ar->tx_ampdu_list, list) { + if (tid_info->state > CARL9170_TID_STATE_SUSPEND) { + tid_info->state = CARL9170_TID_STATE_SUSPEND; + + spin_lock(&tid_info->lock); + while ((skb = __skb_dequeue(&tid_info->queue))) + __skb_queue_tail(&free, skb); + spin_unlock(&tid_info->lock); + } + } + spin_unlock_bh(&ar->tx_ampdu_list_lock); + rcu_read_unlock(); + + while ((skb = __skb_dequeue(&free))) + carl9170_tx_status(ar, skb, false); +} + +static void carl9170_zap_queues(struct ar9170 *ar) +{ + struct carl9170_vif_info *cvif; + unsigned int i; + + carl9170_ampdu_gc(ar); + + carl9170_flush_ba(ar); + carl9170_flush(ar, true); + + for (i = 0; i < ar->hw->queues; i++) { + spin_lock_bh(&ar->tx_status[i].lock); + while (!skb_queue_empty(&ar->tx_status[i])) { + struct sk_buff *skb; + + skb = skb_peek(&ar->tx_status[i]); + carl9170_tx_get_skb(skb); + spin_unlock_bh(&ar->tx_status[i].lock); + carl9170_tx_drop(ar, skb); + spin_lock_bh(&ar->tx_status[i].lock); + carl9170_tx_put_skb(skb); + } + spin_unlock_bh(&ar->tx_status[i].lock); + } + + BUILD_BUG_ON(CARL9170_NUM_TX_LIMIT_SOFT < 1); + BUILD_BUG_ON(CARL9170_NUM_TX_LIMIT_HARD < CARL9170_NUM_TX_LIMIT_SOFT); + BUILD_BUG_ON(CARL9170_NUM_TX_LIMIT_HARD >= CARL9170_BAW_BITS); + + /* reinitialize queues statistics */ + memset(&ar->tx_stats, 0, sizeof(ar->tx_stats)); + for (i = 0; i < ar->hw->queues; i++) + ar->tx_stats[i].limit = CARL9170_NUM_TX_LIMIT_HARD; + + for (i = 0; i < DIV_ROUND_UP(ar->fw.mem_blocks, BITS_PER_LONG); i++) + ar->mem_bitmap[i] = 0; + + rcu_read_lock(); + list_for_each_entry_rcu(cvif, &ar->vif_list, list) { + spin_lock_bh(&ar->beacon_lock); + dev_kfree_skb_any(cvif->beacon); + cvif->beacon = NULL; + spin_unlock_bh(&ar->beacon_lock); + } + rcu_read_unlock(); + + atomic_set(&ar->tx_ampdu_upload, 0); + atomic_set(&ar->tx_ampdu_scheduler, 0); + atomic_set(&ar->tx_total_pending, 0); + atomic_set(&ar->tx_total_queued, 0); + atomic_set(&ar->mem_free_blocks, ar->fw.mem_blocks); +} + +#define CARL9170_FILL_QUEUE(queue, ai_fs, cwmin, cwmax, _txop) \ +do { \ + queue.aifs = ai_fs; \ + queue.cw_min = cwmin; \ + queue.cw_max = cwmax; \ + queue.txop = _txop; \ +} while (0) + +static int carl9170_op_start(struct ieee80211_hw *hw) +{ + struct ar9170 *ar = hw->priv; + int err, i; + + mutex_lock(&ar->mutex); + + carl9170_zap_queues(ar); + + /* reset QoS defaults */ + CARL9170_FILL_QUEUE(ar->edcf[AR9170_TXQ_VO], 2, 3, 7, 47); + CARL9170_FILL_QUEUE(ar->edcf[AR9170_TXQ_VI], 2, 7, 15, 94); + CARL9170_FILL_QUEUE(ar->edcf[AR9170_TXQ_BE], 3, 15, 1023, 0); + CARL9170_FILL_QUEUE(ar->edcf[AR9170_TXQ_BK], 7, 15, 1023, 0); + CARL9170_FILL_QUEUE(ar->edcf[AR9170_TXQ_SPECIAL], 2, 3, 7, 0); + + ar->current_factor = ar->current_density = -1; + /* "The first key is unique." */ + ar->usedkeys = 1; + ar->filter_state = 0; + ar->ps.last_action = jiffies; + ar->ps.last_slept = jiffies; + ar->erp_mode = CARL9170_ERP_AUTO; + + /* Set "disable hw crypto offload" whenever the module parameter + * nohwcrypt is true or if the firmware does not support it. + */ + ar->disable_offload = modparam_nohwcrypt | + ar->fw.disable_offload_fw; + ar->rx_software_decryption = ar->disable_offload; + + for (i = 0; i < ar->hw->queues; i++) { + ar->queue_stop_timeout[i] = jiffies; + ar->max_queue_stop_timeout[i] = 0; + } + + atomic_set(&ar->mem_allocs, 0); + + err = carl9170_usb_open(ar); + if (err) + goto out; + + err = carl9170_init_mac(ar); + if (err) + goto out; + + err = carl9170_set_qos(ar); + if (err) + goto out; + + if (ar->fw.rx_filter) { + err = carl9170_rx_filter(ar, CARL9170_RX_FILTER_OTHER_RA | + CARL9170_RX_FILTER_CTL_OTHER | CARL9170_RX_FILTER_BAD); + if (err) + goto out; + } + + err = carl9170_write_reg(ar, AR9170_MAC_REG_DMA_TRIGGER, + AR9170_DMA_TRIGGER_RXQ); + if (err) + goto out; + + /* Clear key-cache */ + for (i = 0; i < AR9170_CAM_MAX_USER + 4; i++) { + err = carl9170_upload_key(ar, i, NULL, AR9170_ENC_ALG_NONE, + 0, NULL, 0); + if (err) + goto out; + + err = carl9170_upload_key(ar, i, NULL, AR9170_ENC_ALG_NONE, + 1, NULL, 0); + if (err) + goto out; + + if (i < AR9170_CAM_MAX_USER) { + err = carl9170_disable_key(ar, i); + if (err) + goto out; + } + } + + carl9170_set_state_when(ar, CARL9170_IDLE, CARL9170_STARTED); + + ieee80211_queue_delayed_work(ar->hw, &ar->stat_work, + round_jiffies(msecs_to_jiffies(CARL9170_STAT_WORK))); + + ieee80211_wake_queues(ar->hw); + err = 0; + +out: + mutex_unlock(&ar->mutex); + return err; +} + +static void carl9170_cancel_worker(struct ar9170 *ar) +{ + cancel_delayed_work_sync(&ar->stat_work); + cancel_delayed_work_sync(&ar->tx_janitor); +#ifdef CPTCFG_CARL9170_LEDS + cancel_delayed_work_sync(&ar->led_work); +#endif /* CPTCFG_CARL9170_LEDS */ + cancel_work_sync(&ar->ps_work); + cancel_work_sync(&ar->ping_work); + cancel_work_sync(&ar->ampdu_work); +} + +static void carl9170_op_stop(struct ieee80211_hw *hw) +{ + struct ar9170 *ar = hw->priv; + + carl9170_set_state_when(ar, CARL9170_STARTED, CARL9170_IDLE); + + ieee80211_stop_queues(ar->hw); + + mutex_lock(&ar->mutex); + if (IS_ACCEPTING_CMD(ar)) { + RCU_INIT_POINTER(ar->beacon_iter, NULL); + + carl9170_led_set_state(ar, 0); + + /* stop DMA */ + carl9170_write_reg(ar, AR9170_MAC_REG_DMA_TRIGGER, 0); + carl9170_usb_stop(ar); + } + + carl9170_zap_queues(ar); + mutex_unlock(&ar->mutex); + + carl9170_cancel_worker(ar); +} + +static void carl9170_restart_work(struct work_struct *work) +{ + struct ar9170 *ar = container_of(work, struct ar9170, + restart_work); + int err = -EIO; + + ar->usedkeys = 0; + ar->filter_state = 0; + carl9170_cancel_worker(ar); + + mutex_lock(&ar->mutex); + if (!ar->force_usb_reset) { + err = carl9170_usb_restart(ar); + if (net_ratelimit()) { + if (err) + dev_err(&ar->udev->dev, "Failed to restart device (%d).\n", err); + else + dev_info(&ar->udev->dev, "device restarted successfully.\n"); + } + } + carl9170_zap_queues(ar); + mutex_unlock(&ar->mutex); + + if (!err && !ar->force_usb_reset) { + ar->restart_counter++; + atomic_set(&ar->pending_restarts, 0); + + ieee80211_restart_hw(ar->hw); + } else { + /* + * The reset was unsuccessful and the device seems to + * be dead. But there's still one option: a low-level + * usb subsystem reset... + */ + + carl9170_usb_reset(ar); + } +} + +void carl9170_restart(struct ar9170 *ar, const enum carl9170_restart_reasons r) +{ + carl9170_set_state_when(ar, CARL9170_STARTED, CARL9170_IDLE); + + /* + * Sometimes, an error can trigger several different reset events. + * By ignoring these *surplus* reset events, the device won't be + * killed again, right after it has recovered. + */ + if (atomic_inc_return(&ar->pending_restarts) > 1) { + dev_dbg(&ar->udev->dev, "ignoring restart (%d)\n", r); + return; + } + + ieee80211_stop_queues(ar->hw); + + dev_err(&ar->udev->dev, "restart device (%d)\n", r); + + if (!WARN_ON(r == CARL9170_RR_NO_REASON) || + !WARN_ON(r >= __CARL9170_RR_LAST)) + ar->last_reason = r; + + if (!ar->registered) + return; + + if (!IS_ACCEPTING_CMD(ar) || ar->needs_full_reset) + ar->force_usb_reset = true; + + ieee80211_queue_work(ar->hw, &ar->restart_work); + + /* + * At this point, the device instance might have vanished/disabled. + * So, don't put any code which access the ar9170 struct + * without proper protection. + */ +} + +static void carl9170_ping_work(struct work_struct *work) +{ + struct ar9170 *ar = container_of(work, struct ar9170, ping_work); + int err; + + if (!IS_STARTED(ar)) + return; + + mutex_lock(&ar->mutex); + err = carl9170_echo_test(ar, 0xdeadbeef); + if (err) + carl9170_restart(ar, CARL9170_RR_UNRESPONSIVE_DEVICE); + mutex_unlock(&ar->mutex); +} + +static int carl9170_init_interface(struct ar9170 *ar, + struct ieee80211_vif *vif) +{ + struct ath_common *common = &ar->common; + int err; + + if (!vif) { + WARN_ON_ONCE(IS_STARTED(ar)); + return 0; + } + + memcpy(common->macaddr, vif->addr, ETH_ALEN); + + /* We have to fall back to software crypto, whenever + * the user choose to participates in an IBSS. HW + * offload for IBSS RSN is not supported by this driver. + * + * NOTE: If the previous main interface has already + * disabled hw crypto offload, we have to keep this + * previous disable_offload setting as it was. + * Altough ideally, we should notify mac80211 and tell + * it to forget about any HW crypto offload for now. + */ + ar->disable_offload |= ((vif->type != NL80211_IFTYPE_STATION) && + (vif->type != NL80211_IFTYPE_AP)); + + /* While the driver supports HW offload in a single + * P2P client configuration, it doesn't support HW + * offload in the favourit, concurrent P2P GO+CLIENT + * configuration. Hence, HW offload will always be + * disabled for P2P. + */ + ar->disable_offload |= vif->p2p; + + ar->rx_software_decryption = ar->disable_offload; + + err = carl9170_set_operating_mode(ar); + return err; +} + +static int carl9170_op_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct carl9170_vif_info *vif_priv = (void *) vif->drv_priv; + struct ieee80211_vif *main_vif, *old_main = NULL; + struct ar9170 *ar = hw->priv; + int vif_id = -1, err = 0; + + mutex_lock(&ar->mutex); + rcu_read_lock(); + if (vif_priv->active) { + /* + * Skip the interface structure initialization, + * if the vif survived the _restart call. + */ + vif_id = vif_priv->id; + vif_priv->enable_beacon = false; + + spin_lock_bh(&ar->beacon_lock); + dev_kfree_skb_any(vif_priv->beacon); + vif_priv->beacon = NULL; + spin_unlock_bh(&ar->beacon_lock); + + goto init; + } + + /* Because the AR9170 HW's MAC doesn't provide full support for + * multiple, independent interfaces [of different operation modes]. + * We have to select ONE main interface [main mode of HW], but we + * can have multiple slaves [AKA: entry in the ACK-table]. + * + * The first (from HEAD/TOP) interface in the ar->vif_list is + * always the main intf. All following intfs in this list + * are considered to be slave intfs. + */ + main_vif = carl9170_get_main_vif(ar); + + if (main_vif) { + switch (main_vif->type) { + case NL80211_IFTYPE_STATION: + if (vif->type == NL80211_IFTYPE_STATION) + break; + + /* P2P GO [master] use-case + * Because the P2P GO station is selected dynamically + * by all participating peers of a WIFI Direct network, + * the driver has be able to change the main interface + * operating mode on the fly. + */ + if (main_vif->p2p && vif->p2p && + vif->type == NL80211_IFTYPE_AP) { + old_main = main_vif; + break; + } + + err = -EBUSY; + rcu_read_unlock(); + + goto unlock; + + case NL80211_IFTYPE_MESH_POINT: + case NL80211_IFTYPE_AP: + if ((vif->type == NL80211_IFTYPE_STATION) || + (vif->type == NL80211_IFTYPE_WDS) || + (vif->type == NL80211_IFTYPE_AP) || + (vif->type == NL80211_IFTYPE_MESH_POINT)) + break; + + err = -EBUSY; + rcu_read_unlock(); + goto unlock; + + default: + rcu_read_unlock(); + goto unlock; + } + } + + vif_id = bitmap_find_free_region(&ar->vif_bitmap, ar->fw.vif_num, 0); + + if (vif_id < 0) { + rcu_read_unlock(); + + err = -ENOSPC; + goto unlock; + } + + BUG_ON(ar->vif_priv[vif_id].id != vif_id); + + vif_priv->active = true; + vif_priv->id = vif_id; + vif_priv->enable_beacon = false; + ar->vifs++; + if (old_main) { + /* We end up in here, if the main interface is being replaced. + * Put the new main interface at the HEAD of the list and the + * previous inteface will automatically become second in line. + */ + list_add_rcu(&vif_priv->list, &ar->vif_list); + } else { + /* Add new inteface. If the list is empty, it will become the + * main inteface, otherwise it will be slave. + */ + list_add_tail_rcu(&vif_priv->list, &ar->vif_list); + } + rcu_assign_pointer(ar->vif_priv[vif_id].vif, vif); + +init: + main_vif = carl9170_get_main_vif(ar); + + if (main_vif == vif) { + rcu_assign_pointer(ar->beacon_iter, vif_priv); + rcu_read_unlock(); + + if (old_main) { + struct carl9170_vif_info *old_main_priv = + (void *) old_main->drv_priv; + /* downgrade old main intf to slave intf. + * NOTE: We are no longer under rcu_read_lock. + * But we are still holding ar->mutex, so the + * vif data [id, addr] is safe. + */ + err = carl9170_mod_virtual_mac(ar, old_main_priv->id, + old_main->addr); + if (err) + goto unlock; + } + + err = carl9170_init_interface(ar, vif); + if (err) + goto unlock; + } else { + rcu_read_unlock(); + err = carl9170_mod_virtual_mac(ar, vif_id, vif->addr); + + if (err) + goto unlock; + } + + if (ar->fw.tx_seq_table) { + err = carl9170_write_reg(ar, ar->fw.tx_seq_table + vif_id * 4, + 0); + if (err) + goto unlock; + } + +unlock: + if (err && (vif_id >= 0)) { + vif_priv->active = false; + bitmap_release_region(&ar->vif_bitmap, vif_id, 0); + ar->vifs--; + RCU_INIT_POINTER(ar->vif_priv[vif_id].vif, NULL); + list_del_rcu(&vif_priv->list); + mutex_unlock(&ar->mutex); + synchronize_rcu(); + } else { + if (ar->vifs > 1) + ar->ps.off_override |= PS_OFF_VIF; + + mutex_unlock(&ar->mutex); + } + + return err; +} + +static void carl9170_op_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct carl9170_vif_info *vif_priv = (void *) vif->drv_priv; + struct ieee80211_vif *main_vif; + struct ar9170 *ar = hw->priv; + unsigned int id; + + mutex_lock(&ar->mutex); + + if (WARN_ON_ONCE(!vif_priv->active)) + goto unlock; + + ar->vifs--; + + rcu_read_lock(); + main_vif = carl9170_get_main_vif(ar); + + id = vif_priv->id; + + vif_priv->active = false; + WARN_ON(vif_priv->enable_beacon); + vif_priv->enable_beacon = false; + list_del_rcu(&vif_priv->list); + RCU_INIT_POINTER(ar->vif_priv[id].vif, NULL); + + if (vif == main_vif) { + rcu_read_unlock(); + + if (ar->vifs) { + WARN_ON(carl9170_init_interface(ar, + carl9170_get_main_vif(ar))); + } else { + carl9170_set_operating_mode(ar); + } + } else { + rcu_read_unlock(); + + WARN_ON(carl9170_mod_virtual_mac(ar, id, NULL)); + } + + carl9170_update_beacon(ar, false); + carl9170_flush_cab(ar, id); + + spin_lock_bh(&ar->beacon_lock); + dev_kfree_skb_any(vif_priv->beacon); + vif_priv->beacon = NULL; + spin_unlock_bh(&ar->beacon_lock); + + bitmap_release_region(&ar->vif_bitmap, id, 0); + + carl9170_set_beacon_timers(ar); + + if (ar->vifs == 1) + ar->ps.off_override &= ~PS_OFF_VIF; + +unlock: + mutex_unlock(&ar->mutex); + + synchronize_rcu(); +} + +void carl9170_ps_check(struct ar9170 *ar) +{ + ieee80211_queue_work(ar->hw, &ar->ps_work); +} + +/* caller must hold ar->mutex */ +static int carl9170_ps_update(struct ar9170 *ar) +{ + bool ps = false; + int err = 0; + + if (!ar->ps.off_override) + ps = (ar->hw->conf.flags & IEEE80211_CONF_PS); + + if (ps != ar->ps.state) { + err = carl9170_powersave(ar, ps); + if (err) + return err; + + if (ar->ps.state && !ps) { + ar->ps.sleep_ms = jiffies_to_msecs(jiffies - + ar->ps.last_action); + } + + if (ps) + ar->ps.last_slept = jiffies; + + ar->ps.last_action = jiffies; + ar->ps.state = ps; + } + + return 0; +} + +static void carl9170_ps_work(struct work_struct *work) +{ + struct ar9170 *ar = container_of(work, struct ar9170, + ps_work); + mutex_lock(&ar->mutex); + if (IS_STARTED(ar)) + WARN_ON_ONCE(carl9170_ps_update(ar) != 0); + mutex_unlock(&ar->mutex); +} + +static int carl9170_update_survey(struct ar9170 *ar, bool flush, bool noise) +{ + int err; + + if (noise) { + err = carl9170_get_noisefloor(ar); + if (err) + return err; + } + + if (ar->fw.hw_counters) { + err = carl9170_collect_tally(ar); + if (err) + return err; + } + + if (flush) + memset(&ar->tally, 0, sizeof(ar->tally)); + + return 0; +} + +static void carl9170_stat_work(struct work_struct *work) +{ + struct ar9170 *ar = container_of(work, struct ar9170, stat_work.work); + int err; + + mutex_lock(&ar->mutex); + err = carl9170_update_survey(ar, false, true); + mutex_unlock(&ar->mutex); + + if (err) + return; + + ieee80211_queue_delayed_work(ar->hw, &ar->stat_work, + round_jiffies(msecs_to_jiffies(CARL9170_STAT_WORK))); +} + +static int carl9170_op_config(struct ieee80211_hw *hw, u32 changed) +{ + struct ar9170 *ar = hw->priv; + int err = 0; + + mutex_lock(&ar->mutex); + if (changed & IEEE80211_CONF_CHANGE_LISTEN_INTERVAL) { + /* TODO */ + err = 0; + } + + if (changed & IEEE80211_CONF_CHANGE_PS) { + err = carl9170_ps_update(ar); + if (err) + goto out; + } + + if (changed & IEEE80211_CONF_CHANGE_SMPS) { + /* TODO */ + err = 0; + } + + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + enum nl80211_channel_type channel_type = + cfg80211_get_chandef_type(&hw->conf.chandef); + + /* adjust slot time for 5 GHz */ + err = carl9170_set_slot_time(ar); + if (err) + goto out; + + err = carl9170_update_survey(ar, true, false); + if (err) + goto out; + + err = carl9170_set_channel(ar, hw->conf.chandef.chan, + channel_type); + if (err) + goto out; + + err = carl9170_update_survey(ar, false, true); + if (err) + goto out; + + err = carl9170_set_dyn_sifs_ack(ar); + if (err) + goto out; + + err = carl9170_set_rts_cts_rate(ar); + if (err) + goto out; + } + + if (changed & IEEE80211_CONF_CHANGE_POWER) { + err = carl9170_set_mac_tpc(ar, ar->hw->conf.chandef.chan); + if (err) + goto out; + } + +out: + mutex_unlock(&ar->mutex); + return err; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) +static u64 carl9170_op_prepare_multicast(struct ieee80211_hw *hw, + struct netdev_hw_addr_list *mc_list) +#else +static u64 carl9170_op_prepare_multicast(struct ieee80211_hw *hw, int mc_count, + struct dev_addr_list *ha) +#endif +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + struct netdev_hw_addr *ha; +#else + int i; +#endif + u64 mchash; + + /* always get broadcast frames */ + mchash = 1ULL << (0xff >> 2); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + netdev_hw_addr_list_for_each(ha, mc_list) + mchash |= 1ULL << (ha->addr[5] >> 2); +#else + for (i = 0; i < mc_count; i++) { + if (WARN_ON(!ha)) + break; + mchash |= 1ULL << (ha->dmi_addr[5] >> 2); + ha = ha->next; + } +#endif + + return mchash; +} + +static void carl9170_op_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *new_flags, + u64 multicast) +{ + struct ar9170 *ar = hw->priv; + + /* mask supported flags */ + *new_flags &= FIF_ALLMULTI | ar->rx_filter_caps; + + if (!IS_ACCEPTING_CMD(ar)) + return; + + mutex_lock(&ar->mutex); + + ar->filter_state = *new_flags; + /* + * We can support more by setting the sniffer bit and + * then checking the error flags, later. + */ + + if (*new_flags & FIF_ALLMULTI) + multicast = ~0ULL; + + if (multicast != ar->cur_mc_hash) + WARN_ON(carl9170_update_multicast(ar, multicast)); + + if (changed_flags & (FIF_OTHER_BSS | FIF_PROMISC_IN_BSS)) { + ar->sniffer_enabled = !!(*new_flags & + (FIF_OTHER_BSS | FIF_PROMISC_IN_BSS)); + + WARN_ON(carl9170_set_operating_mode(ar)); + } + + if (ar->fw.rx_filter && changed_flags & ar->rx_filter_caps) { + u32 rx_filter = 0; + + if (!ar->fw.ba_filter) + rx_filter |= CARL9170_RX_FILTER_CTL_OTHER; + + if (!(*new_flags & (FIF_FCSFAIL | FIF_PLCPFAIL))) + rx_filter |= CARL9170_RX_FILTER_BAD; + + if (!(*new_flags & FIF_CONTROL)) + rx_filter |= CARL9170_RX_FILTER_CTL_OTHER; + + if (!(*new_flags & FIF_PSPOLL)) + rx_filter |= CARL9170_RX_FILTER_CTL_PSPOLL; + + if (!(*new_flags & (FIF_OTHER_BSS | FIF_PROMISC_IN_BSS))) { + rx_filter |= CARL9170_RX_FILTER_OTHER_RA; + rx_filter |= CARL9170_RX_FILTER_DECRY_FAIL; + } + + WARN_ON(carl9170_rx_filter(ar, rx_filter)); + } + + mutex_unlock(&ar->mutex); +} + + +static void carl9170_op_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changed) +{ + struct ar9170 *ar = hw->priv; + struct ath_common *common = &ar->common; + int err = 0; + struct carl9170_vif_info *vif_priv; + struct ieee80211_vif *main_vif; + + mutex_lock(&ar->mutex); + vif_priv = (void *) vif->drv_priv; + main_vif = carl9170_get_main_vif(ar); + if (WARN_ON(!main_vif)) + goto out; + + if (changed & BSS_CHANGED_BEACON_ENABLED) { + struct carl9170_vif_info *iter; + int i = 0; + + vif_priv->enable_beacon = bss_conf->enable_beacon; + rcu_read_lock(); + list_for_each_entry_rcu(iter, &ar->vif_list, list) { + if (iter->active && iter->enable_beacon) + i++; + + } + rcu_read_unlock(); + + ar->beacon_enabled = i; + } + + if (changed & BSS_CHANGED_BEACON) { + err = carl9170_update_beacon(ar, false); + if (err) + goto out; + } + + if (changed & (BSS_CHANGED_BEACON_ENABLED | BSS_CHANGED_BEACON | + BSS_CHANGED_BEACON_INT)) { + + if (main_vif != vif) { + bss_conf->beacon_int = main_vif->bss_conf.beacon_int; + bss_conf->dtim_period = main_vif->bss_conf.dtim_period; + } + + /* + * Therefore a hard limit for the broadcast traffic should + * prevent false alarms. + */ + if (vif->type != NL80211_IFTYPE_STATION && + (bss_conf->beacon_int * bss_conf->dtim_period >= + (CARL9170_QUEUE_STUCK_TIMEOUT / 2))) { + err = -EINVAL; + goto out; + } + + err = carl9170_set_beacon_timers(ar); + if (err) + goto out; + } + + if (changed & BSS_CHANGED_HT) { + /* TODO */ + err = 0; + if (err) + goto out; + } + + if (main_vif != vif) + goto out; + + /* + * The following settings can only be changed by the + * master interface. + */ + + if (changed & BSS_CHANGED_BSSID) { + memcpy(common->curbssid, bss_conf->bssid, ETH_ALEN); + err = carl9170_set_operating_mode(ar); + if (err) + goto out; + } + + if (changed & BSS_CHANGED_ASSOC) { + ar->common.curaid = bss_conf->aid; + err = carl9170_set_beacon_timers(ar); + if (err) + goto out; + } + + if (changed & BSS_CHANGED_ERP_SLOT) { + err = carl9170_set_slot_time(ar); + if (err) + goto out; + } + + if (changed & BSS_CHANGED_BASIC_RATES) { + err = carl9170_set_mac_rates(ar); + if (err) + goto out; + } + +out: + WARN_ON_ONCE(err && IS_STARTED(ar)); + mutex_unlock(&ar->mutex); +} + +static u64 carl9170_op_get_tsf(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct ar9170 *ar = hw->priv; + struct carl9170_tsf_rsp tsf; + int err; + + mutex_lock(&ar->mutex); + err = carl9170_exec_cmd(ar, CARL9170_CMD_READ_TSF, + 0, NULL, sizeof(tsf), &tsf); + mutex_unlock(&ar->mutex); + if (WARN_ON(err)) + return 0; + + return le64_to_cpu(tsf.tsf_64); +} + +static int carl9170_op_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct ar9170 *ar = hw->priv; + int err = 0, i; + u8 ktype; + + if (ar->disable_offload || !vif) + return -EOPNOTSUPP; + + /* Fall back to software encryption whenever the driver is connected + * to more than one network. + * + * This is very unfortunate, because some machines cannot handle + * the high througput speed in 802.11n networks. + */ + + if (!is_main_vif(ar, vif)) { + mutex_lock(&ar->mutex); + goto err_softw; + } + + /* + * While the hardware supports *catch-all* key, for offloading + * group-key en-/de-cryption. The way of how the hardware + * decides which keyId maps to which key, remains a mystery... + */ + if ((vif->type != NL80211_IFTYPE_STATION && + vif->type != NL80211_IFTYPE_ADHOC) && + !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) + return -EOPNOTSUPP; + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + ktype = AR9170_ENC_ALG_WEP64; + break; + case WLAN_CIPHER_SUITE_WEP104: + ktype = AR9170_ENC_ALG_WEP128; + break; + case WLAN_CIPHER_SUITE_TKIP: + ktype = AR9170_ENC_ALG_TKIP; + break; + case WLAN_CIPHER_SUITE_CCMP: + ktype = AR9170_ENC_ALG_AESCCMP; + key->flags |= IEEE80211_KEY_FLAG_SW_MGMT_TX; + break; + default: + return -EOPNOTSUPP; + } + + mutex_lock(&ar->mutex); + if (cmd == SET_KEY) { + if (!IS_STARTED(ar)) { + err = -EOPNOTSUPP; + goto out; + } + + if (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) { + sta = NULL; + + i = 64 + key->keyidx; + } else { + for (i = 0; i < 64; i++) + if (!(ar->usedkeys & BIT(i))) + break; + if (i == 64) + goto err_softw; + } + + key->hw_key_idx = i; + + err = carl9170_upload_key(ar, i, sta ? sta->addr : NULL, + ktype, 0, key->key, + min_t(u8, 16, key->keylen)); + if (err) + goto out; + + if (key->cipher == WLAN_CIPHER_SUITE_TKIP) { + err = carl9170_upload_key(ar, i, sta ? sta->addr : + NULL, ktype, 1, + key->key + 16, 16); + if (err) + goto out; + + /* + * hardware is not capable generating MMIC + * of fragmented frames! + */ + key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC; + } + + if (i < 64) + ar->usedkeys |= BIT(i); + + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + } else { + if (!IS_STARTED(ar)) { + /* The device is gone... together with the key ;-) */ + err = 0; + goto out; + } + + if (key->hw_key_idx < 64) { + ar->usedkeys &= ~BIT(key->hw_key_idx); + } else { + err = carl9170_upload_key(ar, key->hw_key_idx, NULL, + AR9170_ENC_ALG_NONE, 0, + NULL, 0); + if (err) + goto out; + + if (key->cipher == WLAN_CIPHER_SUITE_TKIP) { + err = carl9170_upload_key(ar, key->hw_key_idx, + NULL, + AR9170_ENC_ALG_NONE, + 1, NULL, 0); + if (err) + goto out; + } + + } + + err = carl9170_disable_key(ar, key->hw_key_idx); + if (err) + goto out; + } + +out: + mutex_unlock(&ar->mutex); + return err; + +err_softw: + if (!ar->rx_software_decryption) { + ar->rx_software_decryption = true; + carl9170_set_operating_mode(ar); + } + mutex_unlock(&ar->mutex); + return -ENOSPC; +} + +static int carl9170_op_sta_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct carl9170_sta_info *sta_info = (void *) sta->drv_priv; + unsigned int i; + + atomic_set(&sta_info->pending_frames, 0); + + if (sta->ht_cap.ht_supported) { + if (sta->ht_cap.ampdu_density > 6) { + /* + * HW does support 16us AMPDU density. + * No HT-Xmit for station. + */ + + return 0; + } + + for (i = 0; i < ARRAY_SIZE(sta_info->agg); i++) + RCU_INIT_POINTER(sta_info->agg[i], NULL); + + sta_info->ampdu_max_len = 1 << (3 + sta->ht_cap.ampdu_factor); + sta_info->ht_sta = true; + } + + return 0; +} + +static int carl9170_op_sta_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct ar9170 *ar = hw->priv; + struct carl9170_sta_info *sta_info = (void *) sta->drv_priv; + unsigned int i; + bool cleanup = false; + + if (sta->ht_cap.ht_supported) { + + sta_info->ht_sta = false; + + rcu_read_lock(); + for (i = 0; i < ARRAY_SIZE(sta_info->agg); i++) { + struct carl9170_sta_tid *tid_info; + + tid_info = rcu_dereference(sta_info->agg[i]); + RCU_INIT_POINTER(sta_info->agg[i], NULL); + + if (!tid_info) + continue; + + spin_lock_bh(&ar->tx_ampdu_list_lock); + if (tid_info->state > CARL9170_TID_STATE_SHUTDOWN) + tid_info->state = CARL9170_TID_STATE_SHUTDOWN; + spin_unlock_bh(&ar->tx_ampdu_list_lock); + cleanup = true; + } + rcu_read_unlock(); + + if (cleanup) + carl9170_ampdu_gc(ar); + } + + return 0; +} + +static int carl9170_op_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *param) +{ + struct ar9170 *ar = hw->priv; + int ret; + + mutex_lock(&ar->mutex); + if (queue < ar->hw->queues) { + memcpy(&ar->edcf[ar9170_qmap[queue]], param, sizeof(*param)); + ret = carl9170_set_qos(ar); + } else { + ret = -EINVAL; + } + + mutex_unlock(&ar->mutex); + return ret; +} + +static void carl9170_ampdu_work(struct work_struct *work) +{ + struct ar9170 *ar = container_of(work, struct ar9170, + ampdu_work); + + if (!IS_STARTED(ar)) + return; + + mutex_lock(&ar->mutex); + carl9170_ampdu_gc(ar); + mutex_unlock(&ar->mutex); +} + +static int carl9170_op_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, + u16 tid, u16 *ssn, u8 buf_size) +{ + struct ar9170 *ar = hw->priv; + struct carl9170_sta_info *sta_info = (void *) sta->drv_priv; + struct carl9170_sta_tid *tid_info; + + if (modparam_noht) + return -EOPNOTSUPP; + + switch (action) { + case IEEE80211_AMPDU_TX_START: + if (!sta_info->ht_sta) + return -EOPNOTSUPP; + + rcu_read_lock(); + if (rcu_dereference(sta_info->agg[tid])) { + rcu_read_unlock(); + return -EBUSY; + } + + tid_info = kzalloc(sizeof(struct carl9170_sta_tid), + GFP_ATOMIC); + if (!tid_info) { + rcu_read_unlock(); + return -ENOMEM; + } + + tid_info->hsn = tid_info->bsn = tid_info->snx = (*ssn); + tid_info->state = CARL9170_TID_STATE_PROGRESS; + tid_info->tid = tid; + tid_info->max = sta_info->ampdu_max_len; + + INIT_LIST_HEAD(&tid_info->list); + INIT_LIST_HEAD(&tid_info->tmp_list); + skb_queue_head_init(&tid_info->queue); + spin_lock_init(&tid_info->lock); + + spin_lock_bh(&ar->tx_ampdu_list_lock); + ar->tx_ampdu_list_len++; + list_add_tail_rcu(&tid_info->list, &ar->tx_ampdu_list); + rcu_assign_pointer(sta_info->agg[tid], tid_info); + spin_unlock_bh(&ar->tx_ampdu_list_lock); + rcu_read_unlock(); + + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + + case IEEE80211_AMPDU_TX_STOP_CONT: + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + rcu_read_lock(); + tid_info = rcu_dereference(sta_info->agg[tid]); + if (tid_info) { + spin_lock_bh(&ar->tx_ampdu_list_lock); + if (tid_info->state > CARL9170_TID_STATE_SHUTDOWN) + tid_info->state = CARL9170_TID_STATE_SHUTDOWN; + spin_unlock_bh(&ar->tx_ampdu_list_lock); + } + + RCU_INIT_POINTER(sta_info->agg[tid], NULL); + rcu_read_unlock(); + + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + ieee80211_queue_work(ar->hw, &ar->ampdu_work); + break; + + case IEEE80211_AMPDU_TX_OPERATIONAL: + rcu_read_lock(); + tid_info = rcu_dereference(sta_info->agg[tid]); + + sta_info->stats[tid].clear = true; + sta_info->stats[tid].req = false; + + if (tid_info) { + bitmap_zero(tid_info->bitmap, CARL9170_BAW_SIZE); + tid_info->state = CARL9170_TID_STATE_IDLE; + } + rcu_read_unlock(); + + if (WARN_ON_ONCE(!tid_info)) + return -EFAULT; + + break; + + case IEEE80211_AMPDU_RX_START: + case IEEE80211_AMPDU_RX_STOP: + /* Handled by hardware */ + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +#ifdef CPTCFG_CARL9170_WPC +static int carl9170_register_wps_button(struct ar9170 *ar) +{ + struct input_dev *input; + int err; + + if (!(ar->features & CARL9170_WPS_BUTTON)) + return 0; + + input = input_allocate_device(); + if (!input) + return -ENOMEM; + + snprintf(ar->wps.name, sizeof(ar->wps.name), "%s WPS Button", + wiphy_name(ar->hw->wiphy)); + + snprintf(ar->wps.phys, sizeof(ar->wps.phys), + "ieee80211/%s/input0", wiphy_name(ar->hw->wiphy)); + + input->name = ar->wps.name; + input->phys = ar->wps.phys; + input->id.bustype = BUS_USB; + input->dev.parent = &ar->hw->wiphy->dev; + + input_set_capability(input, EV_KEY, KEY_WPS_BUTTON); + + err = input_register_device(input); + if (err) { + input_free_device(input); + return err; + } + + ar->wps.pbc = input; + return 0; +} +#endif /* CPTCFG_CARL9170_WPC */ + +#ifdef CPTCFG_CARL9170_HWRNG +static int carl9170_rng_get(struct ar9170 *ar) +{ + +#define RW (CARL9170_MAX_CMD_PAYLOAD_LEN / sizeof(u32)) +#define RB (CARL9170_MAX_CMD_PAYLOAD_LEN) + + static const __le32 rng_load[RW] = { + [0 ... (RW - 1)] = cpu_to_le32(AR9170_RAND_REG_NUM)}; + + u32 buf[RW]; + + unsigned int i, off = 0, transfer, count; + int err; + + BUILD_BUG_ON(RB > CARL9170_MAX_CMD_PAYLOAD_LEN); + + if (!IS_ACCEPTING_CMD(ar) || !ar->rng.initialized) + return -EAGAIN; + + count = ARRAY_SIZE(ar->rng.cache); + while (count) { + err = carl9170_exec_cmd(ar, CARL9170_CMD_RREG, + RB, (u8 *) rng_load, + RB, (u8 *) buf); + if (err) + return err; + + transfer = min_t(unsigned int, count, RW); + for (i = 0; i < transfer; i++) + ar->rng.cache[off + i] = buf[i]; + + off += transfer; + count -= transfer; + } + + ar->rng.cache_idx = 0; + +#undef RW +#undef RB + return 0; +} + +static int carl9170_rng_read(struct hwrng *rng, u32 *data) +{ + struct ar9170 *ar = (struct ar9170 *)rng->priv; + int ret = -EIO; + + mutex_lock(&ar->mutex); + if (ar->rng.cache_idx >= ARRAY_SIZE(ar->rng.cache)) { + ret = carl9170_rng_get(ar); + if (ret) { + mutex_unlock(&ar->mutex); + return ret; + } + } + + *data = ar->rng.cache[ar->rng.cache_idx++]; + mutex_unlock(&ar->mutex); + + return sizeof(u16); +} + +static void carl9170_unregister_hwrng(struct ar9170 *ar) +{ + if (ar->rng.initialized) { + hwrng_unregister(&ar->rng.rng); + ar->rng.initialized = false; + } +} + +static int carl9170_register_hwrng(struct ar9170 *ar) +{ + int err; + + snprintf(ar->rng.name, ARRAY_SIZE(ar->rng.name), + "%s_%s", KBUILD_MODNAME, wiphy_name(ar->hw->wiphy)); + ar->rng.rng.name = ar->rng.name; + ar->rng.rng.data_read = carl9170_rng_read; + ar->rng.rng.priv = (unsigned long)ar; + + if (WARN_ON(ar->rng.initialized)) + return -EALREADY; + + err = hwrng_register(&ar->rng.rng); + if (err) { + dev_err(&ar->udev->dev, "Failed to register the random " + "number generator (%d)\n", err); + return err; + } + + ar->rng.initialized = true; + + err = carl9170_rng_get(ar); + if (err) { + carl9170_unregister_hwrng(ar); + return err; + } + + return 0; +} +#endif /* CPTCFG_CARL9170_HWRNG */ + +static int carl9170_op_get_survey(struct ieee80211_hw *hw, int idx, + struct survey_info *survey) +{ + struct ar9170 *ar = hw->priv; + struct ieee80211_channel *chan; + struct ieee80211_supported_band *band; + int err, b, i; + + chan = ar->channel; + if (!chan) + return -ENODEV; + + if (idx == chan->hw_value) { + mutex_lock(&ar->mutex); + err = carl9170_update_survey(ar, false, true); + mutex_unlock(&ar->mutex); + if (err) + return err; + } + + for (b = 0; b < IEEE80211_NUM_BANDS; b++) { + band = ar->hw->wiphy->bands[b]; + + if (!band) + continue; + + for (i = 0; i < band->n_channels; i++) { + if (band->channels[i].hw_value == idx) { + chan = &band->channels[i]; + goto found; + } + } + } + return -ENOENT; + +found: + memcpy(survey, &ar->survey[idx], sizeof(*survey)); + + survey->channel = chan; + survey->filled = SURVEY_INFO_NOISE_DBM; + + if (ar->channel == chan) + survey->filled |= SURVEY_INFO_IN_USE; + + if (ar->fw.hw_counters) { + survey->filled |= SURVEY_INFO_CHANNEL_TIME | + SURVEY_INFO_CHANNEL_TIME_BUSY | + SURVEY_INFO_CHANNEL_TIME_TX; + } + + return 0; +} + +static void carl9170_op_flush(struct ieee80211_hw *hw, u32 queues, bool drop) +{ + struct ar9170 *ar = hw->priv; + unsigned int vid; + + mutex_lock(&ar->mutex); + for_each_set_bit(vid, &ar->vif_bitmap, ar->fw.vif_num) + carl9170_flush_cab(ar, vid); + + carl9170_flush(ar, drop); + mutex_unlock(&ar->mutex); +} + +static int carl9170_op_get_stats(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + struct ar9170 *ar = hw->priv; + + memset(stats, 0, sizeof(*stats)); + stats->dot11ACKFailureCount = ar->tx_ack_failures; + stats->dot11FCSErrorCount = ar->tx_fcs_errors; + return 0; +} + +static void carl9170_op_sta_notify(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum sta_notify_cmd cmd, + struct ieee80211_sta *sta) +{ + struct carl9170_sta_info *sta_info = (void *) sta->drv_priv; + + switch (cmd) { + case STA_NOTIFY_SLEEP: + sta_info->sleeping = true; + if (atomic_read(&sta_info->pending_frames)) + ieee80211_sta_block_awake(hw, sta, true); + break; + + case STA_NOTIFY_AWAKE: + sta_info->sleeping = false; + break; + } +} + +static bool carl9170_tx_frames_pending(struct ieee80211_hw *hw) +{ + struct ar9170 *ar = hw->priv; + + return !!atomic_read(&ar->tx_total_queued); +} + +static const struct ieee80211_ops carl9170_ops = { + .start = carl9170_op_start, + .stop = carl9170_op_stop, + .tx = carl9170_op_tx, + .flush = carl9170_op_flush, + .add_interface = carl9170_op_add_interface, + .remove_interface = carl9170_op_remove_interface, + .config = carl9170_op_config, + .prepare_multicast = carl9170_op_prepare_multicast, + .configure_filter = carl9170_op_configure_filter, + .conf_tx = carl9170_op_conf_tx, + .bss_info_changed = carl9170_op_bss_info_changed, + .get_tsf = carl9170_op_get_tsf, + .set_key = carl9170_op_set_key, + .sta_add = carl9170_op_sta_add, + .sta_remove = carl9170_op_sta_remove, + .sta_notify = carl9170_op_sta_notify, + .get_survey = carl9170_op_get_survey, + .get_stats = carl9170_op_get_stats, + .ampdu_action = carl9170_op_ampdu_action, + .tx_frames_pending = carl9170_tx_frames_pending, +}; + +void *carl9170_alloc(size_t priv_size) +{ + struct ieee80211_hw *hw; + struct ar9170 *ar; + struct sk_buff *skb; + int i; + + /* + * this buffer is used for rx stream reconstruction. + * Under heavy load this device (or the transport layer?) + * tends to split the streams into separate rx descriptors. + */ + + skb = __dev_alloc_skb(AR9170_RX_STREAM_MAX_SIZE, GFP_KERNEL); + if (!skb) + goto err_nomem; + + hw = ieee80211_alloc_hw(priv_size, &carl9170_ops); + if (!hw) + goto err_nomem; + + ar = hw->priv; + ar->hw = hw; + ar->rx_failover = skb; + + memset(&ar->rx_plcp, 0, sizeof(struct ar9170_rx_head)); + ar->rx_has_plcp = false; + + /* + * Here's a hidden pitfall! + * + * All 4 AC queues work perfectly well under _legacy_ operation. + * However as soon as aggregation is enabled, the traffic flow + * gets very bumpy. Therefore we have to _switch_ to a + * software AC with a single HW queue. + */ + hw->queues = __AR9170_NUM_TXQ; + + mutex_init(&ar->mutex); + spin_lock_init(&ar->beacon_lock); + spin_lock_init(&ar->cmd_lock); + spin_lock_init(&ar->tx_stats_lock); + spin_lock_init(&ar->tx_ampdu_list_lock); + spin_lock_init(&ar->mem_lock); + spin_lock_init(&ar->state_lock); + atomic_set(&ar->pending_restarts, 0); + ar->vifs = 0; + for (i = 0; i < ar->hw->queues; i++) { + skb_queue_head_init(&ar->tx_status[i]); + skb_queue_head_init(&ar->tx_pending[i]); + + INIT_LIST_HEAD(&ar->bar_list[i]); + spin_lock_init(&ar->bar_list_lock[i]); + } + INIT_WORK(&ar->ps_work, carl9170_ps_work); + INIT_WORK(&ar->ping_work, carl9170_ping_work); + INIT_WORK(&ar->restart_work, carl9170_restart_work); + INIT_WORK(&ar->ampdu_work, carl9170_ampdu_work); + INIT_DELAYED_WORK(&ar->stat_work, carl9170_stat_work); + INIT_DELAYED_WORK(&ar->tx_janitor, carl9170_tx_janitor); + INIT_LIST_HEAD(&ar->tx_ampdu_list); + rcu_assign_pointer(ar->tx_ampdu_iter, + (struct carl9170_sta_tid *) &ar->tx_ampdu_list); + + bitmap_zero(&ar->vif_bitmap, ar->fw.vif_num); + INIT_LIST_HEAD(&ar->vif_list); + init_completion(&ar->tx_flush); + + /* firmware decides which modes we support */ + hw->wiphy->interface_modes = 0; + + hw->flags |= IEEE80211_HW_RX_INCLUDES_FCS | + IEEE80211_HW_MFP_CAPABLE | + IEEE80211_HW_REPORTS_TX_ACK_STATUS | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_PS_NULLFUNC_STACK | + IEEE80211_HW_NEED_DTIM_BEFORE_ASSOC | + IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_SUPPORTS_HT_CCK_RATES; + + if (!modparam_noht) { + /* + * see the comment above, why we allow the user + * to disable HT by a module parameter. + */ + hw->flags |= IEEE80211_HW_AMPDU_AGGREGATION; + } + + hw->extra_tx_headroom = sizeof(struct _carl9170_tx_superframe); + hw->sta_data_size = sizeof(struct carl9170_sta_info); + hw->vif_data_size = sizeof(struct carl9170_vif_info); + + hw->max_rates = CARL9170_TX_MAX_RATES; + hw->max_rate_tries = CARL9170_TX_USER_RATE_TRIES; + + for (i = 0; i < ARRAY_SIZE(ar->noise); i++) + ar->noise[i] = -95; /* ATH_DEFAULT_NOISE_FLOOR */ + + return ar; + +err_nomem: + kfree_skb(skb); + return ERR_PTR(-ENOMEM); +} + +static int carl9170_read_eeprom(struct ar9170 *ar) +{ +#define RW 8 /* number of words to read at once */ +#define RB (sizeof(u32) * RW) + u8 *eeprom = (void *)&ar->eeprom; + __le32 offsets[RW]; + int i, j, err; + + BUILD_BUG_ON(sizeof(ar->eeprom) & 3); + + BUILD_BUG_ON(RB > CARL9170_MAX_CMD_LEN - 4); +#ifndef __CHECKER__ + /* don't want to handle trailing remains */ + BUILD_BUG_ON(sizeof(ar->eeprom) % RB); +#endif + + for (i = 0; i < sizeof(ar->eeprom) / RB; i++) { + for (j = 0; j < RW; j++) + offsets[j] = cpu_to_le32(AR9170_EEPROM_START + + RB * i + 4 * j); + + err = carl9170_exec_cmd(ar, CARL9170_CMD_RREG, + RB, (u8 *) &offsets, + RB, eeprom + RB * i); + if (err) + return err; + } + +#undef RW +#undef RB + return 0; +} + +static int carl9170_parse_eeprom(struct ar9170 *ar) +{ + struct ath_regulatory *regulatory = &ar->common.regulatory; + unsigned int rx_streams, tx_streams, tx_params = 0; + int bands = 0; + int chans = 0; + + if (ar->eeprom.length == cpu_to_le16(0xffff)) + return -ENODATA; + + rx_streams = hweight8(ar->eeprom.rx_mask); + tx_streams = hweight8(ar->eeprom.tx_mask); + + if (rx_streams != tx_streams) { + tx_params = IEEE80211_HT_MCS_TX_RX_DIFF; + + WARN_ON(!(tx_streams >= 1 && tx_streams <= + IEEE80211_HT_MCS_TX_MAX_STREAMS)); + + tx_params = (tx_streams - 1) << + IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT; + + carl9170_band_2GHz.ht_cap.mcs.tx_params |= tx_params; + carl9170_band_5GHz.ht_cap.mcs.tx_params |= tx_params; + } + + if (ar->eeprom.operating_flags & AR9170_OPFLAG_2GHZ) { + ar->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = + &carl9170_band_2GHz; + chans += carl9170_band_2GHz.n_channels; + bands++; + } + if (ar->eeprom.operating_flags & AR9170_OPFLAG_5GHZ) { + ar->hw->wiphy->bands[IEEE80211_BAND_5GHZ] = + &carl9170_band_5GHz; + chans += carl9170_band_5GHz.n_channels; + bands++; + } + + if (!bands) + return -EINVAL; + + ar->survey = kzalloc(sizeof(struct survey_info) * chans, GFP_KERNEL); + if (!ar->survey) + return -ENOMEM; + ar->num_channels = chans; + + /* + * I measured this, a bandswitch takes roughly + * 135 ms and a frequency switch about 80. + * + * FIXME: measure these values again once EEPROM settings + * are used, that will influence them! + */ + if (bands == 2) + ar->hw->channel_change_time = 135 * 1000; + else + ar->hw->channel_change_time = 80 * 1000; + + regulatory->current_rd = le16_to_cpu(ar->eeprom.reg_domain[0]); + + /* second part of wiphy init */ + SET_IEEE80211_PERM_ADDR(ar->hw, ar->eeprom.mac_address); + + return 0; +} + +static void carl9170_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct ar9170 *ar = hw->priv; + + ath_reg_notifier_apply(wiphy, request, &ar->common.regulatory); +} + +int carl9170_register(struct ar9170 *ar) +{ + struct ath_regulatory *regulatory = &ar->common.regulatory; + int err = 0, i; + + if (WARN_ON(ar->mem_bitmap)) + return -EINVAL; + + ar->mem_bitmap = kzalloc(roundup(ar->fw.mem_blocks, BITS_PER_LONG) * + sizeof(unsigned long), GFP_KERNEL); + + if (!ar->mem_bitmap) + return -ENOMEM; + + /* try to read EEPROM, init MAC addr */ + err = carl9170_read_eeprom(ar); + if (err) + return err; + + err = carl9170_parse_eeprom(ar); + if (err) + return err; + + err = ath_regd_init(regulatory, ar->hw->wiphy, + carl9170_reg_notifier); + if (err) + return err; + + if (modparam_noht) { + carl9170_band_2GHz.ht_cap.ht_supported = false; + carl9170_band_5GHz.ht_cap.ht_supported = false; + } + + for (i = 0; i < ar->fw.vif_num; i++) { + ar->vif_priv[i].id = i; + ar->vif_priv[i].vif = NULL; + } + + err = ieee80211_register_hw(ar->hw); + if (err) + return err; + + /* mac80211 interface is now registered */ + ar->registered = true; + + if (!ath_is_world_regd(regulatory)) + regulatory_hint(ar->hw->wiphy, regulatory->alpha2); + +#ifdef CPTCFG_CARL9170_DEBUGFS + carl9170_debugfs_register(ar); +#endif /* CPTCFG_CARL9170_DEBUGFS */ + + err = carl9170_led_init(ar); + if (err) + goto err_unreg; + +#ifdef CPTCFG_CARL9170_LEDS + err = carl9170_led_register(ar); + if (err) + goto err_unreg; +#endif /* CPTCFG_CARL9170_LEDS */ + +#ifdef CPTCFG_CARL9170_WPC + err = carl9170_register_wps_button(ar); + if (err) + goto err_unreg; +#endif /* CPTCFG_CARL9170_WPC */ + +#ifdef CPTCFG_CARL9170_HWRNG + err = carl9170_register_hwrng(ar); + if (err) + goto err_unreg; +#endif /* CPTCFG_CARL9170_HWRNG */ + + dev_info(&ar->udev->dev, "Atheros AR9170 is registered as '%s'\n", + wiphy_name(ar->hw->wiphy)); + + return 0; + +err_unreg: + carl9170_unregister(ar); + return err; +} + +void carl9170_unregister(struct ar9170 *ar) +{ + if (!ar->registered) + return; + + ar->registered = false; + +#ifdef CPTCFG_CARL9170_LEDS + carl9170_led_unregister(ar); +#endif /* CPTCFG_CARL9170_LEDS */ + +#ifdef CPTCFG_CARL9170_DEBUGFS + carl9170_debugfs_unregister(ar); +#endif /* CPTCFG_CARL9170_DEBUGFS */ + +#ifdef CPTCFG_CARL9170_WPC + if (ar->wps.pbc) { + input_unregister_device(ar->wps.pbc); + ar->wps.pbc = NULL; + } +#endif /* CPTCFG_CARL9170_WPC */ + +#ifdef CPTCFG_CARL9170_HWRNG + carl9170_unregister_hwrng(ar); +#endif /* CPTCFG_CARL9170_HWRNG */ + + carl9170_cancel_worker(ar); + cancel_work_sync(&ar->restart_work); + + ieee80211_unregister_hw(ar->hw); +} + +void carl9170_free(struct ar9170 *ar) +{ + WARN_ON(ar->registered); + WARN_ON(IS_INITIALIZED(ar)); + + kfree_skb(ar->rx_failover); + ar->rx_failover = NULL; + + kfree(ar->mem_bitmap); + ar->mem_bitmap = NULL; + + kfree(ar->survey); + ar->survey = NULL; + + mutex_destroy(&ar->mutex); + + ieee80211_free_hw(ar->hw); +} diff --git a/drivers/net/wireless/ath/carl9170/phy.c b/drivers/net/wireless/ath/carl9170/phy.c new file mode 100644 index 0000000..ab4ee7d --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/phy.c @@ -0,0 +1,1730 @@ +/* + * Atheros CARL9170 driver + * + * PHY and RF code + * + * Copyright 2008, Johannes Berg + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * Copyright (c) 2007-2008 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "carl9170.h" +#include "cmd.h" +#include "phy.h" + +static int carl9170_init_power_cal(struct ar9170 *ar) +{ + carl9170_regwrite_begin(ar); + + carl9170_regwrite(AR9170_PHY_REG_POWER_TX_RATE_MAX, 0x7f); + carl9170_regwrite(AR9170_PHY_REG_POWER_TX_RATE1, 0x3f3f3f3f); + carl9170_regwrite(AR9170_PHY_REG_POWER_TX_RATE2, 0x3f3f3f3f); + carl9170_regwrite(AR9170_PHY_REG_POWER_TX_RATE3, 0x3f3f3f3f); + carl9170_regwrite(AR9170_PHY_REG_POWER_TX_RATE4, 0x3f3f3f3f); + carl9170_regwrite(AR9170_PHY_REG_POWER_TX_RATE5, 0x3f3f3f3f); + carl9170_regwrite(AR9170_PHY_REG_POWER_TX_RATE6, 0x3f3f3f3f); + carl9170_regwrite(AR9170_PHY_REG_POWER_TX_RATE7, 0x3f3f3f3f); + carl9170_regwrite(AR9170_PHY_REG_POWER_TX_RATE8, 0x3f3f3f3f); + carl9170_regwrite(AR9170_PHY_REG_POWER_TX_RATE9, 0x3f3f3f3f); + + carl9170_regwrite_finish(); + return carl9170_regwrite_result(); +} + +struct carl9170_phy_init { + u32 reg, _5ghz_20, _5ghz_40, _2ghz_40, _2ghz_20; +}; + +static struct carl9170_phy_init ar5416_phy_init[] = { + { 0x1c5800, 0x00000007, 0x00000007, 0x00000007, 0x00000007, }, + { 0x1c5804, 0x00000300, 0x000003c4, 0x000003c4, 0x00000300, }, + { 0x1c5808, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c580c, 0xad848e19, 0xad848e19, 0xad848e19, 0xad848e19, }, + { 0x1c5810, 0x7d14e000, 0x7d14e000, 0x7d14e000, 0x7d14e000, }, + { 0x1c5814, 0x9c0a9f6b, 0x9c0a9f6b, 0x9c0a9f6b, 0x9c0a9f6b, }, + { 0x1c5818, 0x00000090, 0x00000090, 0x00000090, 0x00000090, }, + { 0x1c581c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5820, 0x02020200, 0x02020200, 0x02020200, 0x02020200, }, + { 0x1c5824, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e, }, + { 0x1c5828, 0x0a020001, 0x0a020001, 0x0a020001, 0x0a020001, }, + { 0x1c582c, 0x0000a000, 0x0000a000, 0x0000a000, 0x0000a000, }, + { 0x1c5830, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5834, 0x00000e0e, 0x00000e0e, 0x00000e0e, 0x00000e0e, }, + { 0x1c5838, 0x00000007, 0x00000007, 0x00000007, 0x00000007, }, + { 0x1c583c, 0x00200400, 0x00200400, 0x00200400, 0x00200400, }, + { 0x1c5840, 0x206a002e, 0x206a002e, 0x206a002e, 0x206a002e, }, + { 0x1c5844, 0x1372161e, 0x13721c1e, 0x13721c24, 0x137216a4, }, + { 0x1c5848, 0x001a6a65, 0x001a6a65, 0x00197a68, 0x00197a68, }, + { 0x1c584c, 0x1284233c, 0x1284233c, 0x1284233c, 0x1284233c, }, + { 0x1c5850, 0x6c48b4e4, 0x6d48b4e4, 0x6d48b0e4, 0x6c48b0e4, }, + { 0x1c5854, 0x00000859, 0x00000859, 0x00000859, 0x00000859, }, + { 0x1c5858, 0x7ec80d2e, 0x7ec80d2e, 0x7ec80d2e, 0x7ec80d2e, }, + { 0x1c585c, 0x31395c5e, 0x3139605e, 0x3139605e, 0x31395c5e, }, + { 0x1c5860, 0x0004dd10, 0x0004dd10, 0x0004dd20, 0x0004dd20, }, + { 0x1c5864, 0x0001c600, 0x0001c600, 0x0001c600, 0x0001c600, }, + { 0x1c5868, 0x409a4190, 0x409a4190, 0x409a4190, 0x409a4190, }, + { 0x1c586c, 0x050cb081, 0x050cb081, 0x050cb081, 0x050cb081, }, + { 0x1c5900, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5904, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5908, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c590c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5914, 0x000007d0, 0x000007d0, 0x00000898, 0x00000898, }, + { 0x1c5918, 0x00000118, 0x00000230, 0x00000268, 0x00000134, }, + { 0x1c591c, 0x10000fff, 0x10000fff, 0x10000fff, 0x10000fff, }, + { 0x1c5920, 0x0510081c, 0x0510081c, 0x0510001c, 0x0510001c, }, + { 0x1c5924, 0xd0058a15, 0xd0058a15, 0xd0058a15, 0xd0058a15, }, + { 0x1c5928, 0x00000001, 0x00000001, 0x00000001, 0x00000001, }, + { 0x1c592c, 0x00000004, 0x00000004, 0x00000004, 0x00000004, }, + { 0x1c5934, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, }, + { 0x1c5938, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, }, + { 0x1c593c, 0x0000007f, 0x0000007f, 0x0000007f, 0x0000007f, }, + { 0x1c5944, 0xdfb81020, 0xdfb81020, 0xdfb81020, 0xdfb81020, }, + { 0x1c5948, 0x9280b212, 0x9280b212, 0x9280b212, 0x9280b212, }, + { 0x1c594c, 0x00020028, 0x00020028, 0x00020028, 0x00020028, }, + { 0x1c5954, 0x5d50e188, 0x5d50e188, 0x5d50e188, 0x5d50e188, }, + { 0x1c5958, 0x00081fff, 0x00081fff, 0x00081fff, 0x00081fff, }, + { 0x1c5960, 0x00009b40, 0x00009b40, 0x00009b40, 0x00009b40, }, + { 0x1c5964, 0x00001120, 0x00001120, 0x00001120, 0x00001120, }, + { 0x1c5970, 0x190fb515, 0x190fb515, 0x190fb515, 0x190fb515, }, + { 0x1c5974, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5978, 0x00000001, 0x00000001, 0x00000001, 0x00000001, }, + { 0x1c597c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5980, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5984, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5988, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c598c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5990, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5994, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5998, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c599c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c59a0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c59a4, 0x00000007, 0x00000007, 0x00000007, 0x00000007, }, + { 0x1c59a8, 0x001fff00, 0x001fff00, 0x001fff00, 0x001fff00, }, + { 0x1c59ac, 0x006f00c4, 0x006f00c4, 0x006f00c4, 0x006f00c4, }, + { 0x1c59b0, 0x03051000, 0x03051000, 0x03051000, 0x03051000, }, + { 0x1c59b4, 0x00000820, 0x00000820, 0x00000820, 0x00000820, }, + { 0x1c59bc, 0x00181400, 0x00181400, 0x00181400, 0x00181400, }, + { 0x1c59c0, 0x038919be, 0x038919be, 0x038919be, 0x038919be, }, + { 0x1c59c4, 0x06336f77, 0x06336f77, 0x06336f77, 0x06336f77, }, + { 0x1c59c8, 0x6af6532c, 0x6af6532c, 0x6af6532c, 0x6af6532c, }, + { 0x1c59cc, 0x08f186c8, 0x08f186c8, 0x08f186c8, 0x08f186c8, }, + { 0x1c59d0, 0x00046384, 0x00046384, 0x00046384, 0x00046384, }, + { 0x1c59d4, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c59d8, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c59dc, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c59e0, 0x00000200, 0x00000200, 0x00000200, 0x00000200, }, + { 0x1c59e4, 0x64646464, 0x64646464, 0x64646464, 0x64646464, }, + { 0x1c59e8, 0x3c787878, 0x3c787878, 0x3c787878, 0x3c787878, }, + { 0x1c59ec, 0x000000aa, 0x000000aa, 0x000000aa, 0x000000aa, }, + { 0x1c59f0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c59fc, 0x00001042, 0x00001042, 0x00001042, 0x00001042, }, + { 0x1c5a00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5a04, 0x00000040, 0x00000040, 0x00000040, 0x00000040, }, + { 0x1c5a08, 0x00000080, 0x00000080, 0x00000080, 0x00000080, }, + { 0x1c5a0c, 0x000001a1, 0x000001a1, 0x00000141, 0x00000141, }, + { 0x1c5a10, 0x000001e1, 0x000001e1, 0x00000181, 0x00000181, }, + { 0x1c5a14, 0x00000021, 0x00000021, 0x000001c1, 0x000001c1, }, + { 0x1c5a18, 0x00000061, 0x00000061, 0x00000001, 0x00000001, }, + { 0x1c5a1c, 0x00000168, 0x00000168, 0x00000041, 0x00000041, }, + { 0x1c5a20, 0x000001a8, 0x000001a8, 0x000001a8, 0x000001a8, }, + { 0x1c5a24, 0x000001e8, 0x000001e8, 0x000001e8, 0x000001e8, }, + { 0x1c5a28, 0x00000028, 0x00000028, 0x00000028, 0x00000028, }, + { 0x1c5a2c, 0x00000068, 0x00000068, 0x00000068, 0x00000068, }, + { 0x1c5a30, 0x00000189, 0x00000189, 0x000000a8, 0x000000a8, }, + { 0x1c5a34, 0x000001c9, 0x000001c9, 0x00000169, 0x00000169, }, + { 0x1c5a38, 0x00000009, 0x00000009, 0x000001a9, 0x000001a9, }, + { 0x1c5a3c, 0x00000049, 0x00000049, 0x000001e9, 0x000001e9, }, + { 0x1c5a40, 0x00000089, 0x00000089, 0x00000029, 0x00000029, }, + { 0x1c5a44, 0x00000170, 0x00000170, 0x00000069, 0x00000069, }, + { 0x1c5a48, 0x000001b0, 0x000001b0, 0x00000190, 0x00000190, }, + { 0x1c5a4c, 0x000001f0, 0x000001f0, 0x000001d0, 0x000001d0, }, + { 0x1c5a50, 0x00000030, 0x00000030, 0x00000010, 0x00000010, }, + { 0x1c5a54, 0x00000070, 0x00000070, 0x00000050, 0x00000050, }, + { 0x1c5a58, 0x00000191, 0x00000191, 0x00000090, 0x00000090, }, + { 0x1c5a5c, 0x000001d1, 0x000001d1, 0x00000151, 0x00000151, }, + { 0x1c5a60, 0x00000011, 0x00000011, 0x00000191, 0x00000191, }, + { 0x1c5a64, 0x00000051, 0x00000051, 0x000001d1, 0x000001d1, }, + { 0x1c5a68, 0x00000091, 0x00000091, 0x00000011, 0x00000011, }, + { 0x1c5a6c, 0x000001b8, 0x000001b8, 0x00000051, 0x00000051, }, + { 0x1c5a70, 0x000001f8, 0x000001f8, 0x00000198, 0x00000198, }, + { 0x1c5a74, 0x00000038, 0x00000038, 0x000001d8, 0x000001d8, }, + { 0x1c5a78, 0x00000078, 0x00000078, 0x00000018, 0x00000018, }, + { 0x1c5a7c, 0x00000199, 0x00000199, 0x00000058, 0x00000058, }, + { 0x1c5a80, 0x000001d9, 0x000001d9, 0x00000098, 0x00000098, }, + { 0x1c5a84, 0x00000019, 0x00000019, 0x00000159, 0x00000159, }, + { 0x1c5a88, 0x00000059, 0x00000059, 0x00000199, 0x00000199, }, + { 0x1c5a8c, 0x00000099, 0x00000099, 0x000001d9, 0x000001d9, }, + { 0x1c5a90, 0x000000d9, 0x000000d9, 0x00000019, 0x00000019, }, + { 0x1c5a94, 0x000000f9, 0x000000f9, 0x00000059, 0x00000059, }, + { 0x1c5a98, 0x000000f9, 0x000000f9, 0x00000099, 0x00000099, }, + { 0x1c5a9c, 0x000000f9, 0x000000f9, 0x000000d9, 0x000000d9, }, + { 0x1c5aa0, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5aa4, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5aa8, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5aac, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5ab0, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5ab4, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5ab8, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5abc, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5ac0, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5ac4, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5ac8, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5acc, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5ad0, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5ad4, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5ad8, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5adc, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5ae0, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5ae4, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5ae8, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5aec, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5af0, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5af4, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5af8, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5afc, 0x000000f9, 0x000000f9, 0x000000f9, 0x000000f9, }, + { 0x1c5b00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5b04, 0x00000001, 0x00000001, 0x00000001, 0x00000001, }, + { 0x1c5b08, 0x00000002, 0x00000002, 0x00000002, 0x00000002, }, + { 0x1c5b0c, 0x00000003, 0x00000003, 0x00000003, 0x00000003, }, + { 0x1c5b10, 0x00000004, 0x00000004, 0x00000004, 0x00000004, }, + { 0x1c5b14, 0x00000005, 0x00000005, 0x00000005, 0x00000005, }, + { 0x1c5b18, 0x00000008, 0x00000008, 0x00000008, 0x00000008, }, + { 0x1c5b1c, 0x00000009, 0x00000009, 0x00000009, 0x00000009, }, + { 0x1c5b20, 0x0000000a, 0x0000000a, 0x0000000a, 0x0000000a, }, + { 0x1c5b24, 0x0000000b, 0x0000000b, 0x0000000b, 0x0000000b, }, + { 0x1c5b28, 0x0000000c, 0x0000000c, 0x0000000c, 0x0000000c, }, + { 0x1c5b2c, 0x0000000d, 0x0000000d, 0x0000000d, 0x0000000d, }, + { 0x1c5b30, 0x00000010, 0x00000010, 0x00000010, 0x00000010, }, + { 0x1c5b34, 0x00000011, 0x00000011, 0x00000011, 0x00000011, }, + { 0x1c5b38, 0x00000012, 0x00000012, 0x00000012, 0x00000012, }, + { 0x1c5b3c, 0x00000013, 0x00000013, 0x00000013, 0x00000013, }, + { 0x1c5b40, 0x00000014, 0x00000014, 0x00000014, 0x00000014, }, + { 0x1c5b44, 0x00000015, 0x00000015, 0x00000015, 0x00000015, }, + { 0x1c5b48, 0x00000018, 0x00000018, 0x00000018, 0x00000018, }, + { 0x1c5b4c, 0x00000019, 0x00000019, 0x00000019, 0x00000019, }, + { 0x1c5b50, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a, }, + { 0x1c5b54, 0x0000001b, 0x0000001b, 0x0000001b, 0x0000001b, }, + { 0x1c5b58, 0x0000001c, 0x0000001c, 0x0000001c, 0x0000001c, }, + { 0x1c5b5c, 0x0000001d, 0x0000001d, 0x0000001d, 0x0000001d, }, + { 0x1c5b60, 0x00000020, 0x00000020, 0x00000020, 0x00000020, }, + { 0x1c5b64, 0x00000021, 0x00000021, 0x00000021, 0x00000021, }, + { 0x1c5b68, 0x00000022, 0x00000022, 0x00000022, 0x00000022, }, + { 0x1c5b6c, 0x00000023, 0x00000023, 0x00000023, 0x00000023, }, + { 0x1c5b70, 0x00000024, 0x00000024, 0x00000024, 0x00000024, }, + { 0x1c5b74, 0x00000025, 0x00000025, 0x00000025, 0x00000025, }, + { 0x1c5b78, 0x00000028, 0x00000028, 0x00000028, 0x00000028, }, + { 0x1c5b7c, 0x00000029, 0x00000029, 0x00000029, 0x00000029, }, + { 0x1c5b80, 0x0000002a, 0x0000002a, 0x0000002a, 0x0000002a, }, + { 0x1c5b84, 0x0000002b, 0x0000002b, 0x0000002b, 0x0000002b, }, + { 0x1c5b88, 0x0000002c, 0x0000002c, 0x0000002c, 0x0000002c, }, + { 0x1c5b8c, 0x0000002d, 0x0000002d, 0x0000002d, 0x0000002d, }, + { 0x1c5b90, 0x00000030, 0x00000030, 0x00000030, 0x00000030, }, + { 0x1c5b94, 0x00000031, 0x00000031, 0x00000031, 0x00000031, }, + { 0x1c5b98, 0x00000032, 0x00000032, 0x00000032, 0x00000032, }, + { 0x1c5b9c, 0x00000033, 0x00000033, 0x00000033, 0x00000033, }, + { 0x1c5ba0, 0x00000034, 0x00000034, 0x00000034, 0x00000034, }, + { 0x1c5ba4, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5ba8, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bac, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bb0, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bb4, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bb8, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bbc, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bc0, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bc4, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bc8, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bcc, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bd0, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bd4, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bd8, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bdc, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5be0, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5be4, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5be8, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bec, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bf0, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bf4, 0x00000035, 0x00000035, 0x00000035, 0x00000035, }, + { 0x1c5bf8, 0x00000010, 0x00000010, 0x00000010, 0x00000010, }, + { 0x1c5bfc, 0x0000001a, 0x0000001a, 0x0000001a, 0x0000001a, }, + { 0x1c5c00, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5c0c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5c10, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5c14, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5c18, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5c1c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5c20, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5c24, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5c28, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5c2c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5c30, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5c34, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5c38, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5c3c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5cf0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5cf4, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5cf8, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c5cfc, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c6200, 0x00000008, 0x00000008, 0x0000000e, 0x0000000e, }, + { 0x1c6204, 0x00000440, 0x00000440, 0x00000440, 0x00000440, }, + { 0x1c6208, 0xd6be4788, 0xd6be4788, 0xd03e4788, 0xd03e4788, }, + { 0x1c620c, 0x012e8160, 0x012e8160, 0x012a8160, 0x012a8160, }, + { 0x1c6210, 0x40806333, 0x40806333, 0x40806333, 0x40806333, }, + { 0x1c6214, 0x00106c10, 0x00106c10, 0x00106c10, 0x00106c10, }, + { 0x1c6218, 0x009c4060, 0x009c4060, 0x009c4060, 0x009c4060, }, + { 0x1c621c, 0x1883800a, 0x1883800a, 0x1883800a, 0x1883800a, }, + { 0x1c6220, 0x018830c6, 0x018830c6, 0x018830c6, 0x018830c6, }, + { 0x1c6224, 0x00000400, 0x00000400, 0x00000400, 0x00000400, }, + { 0x1c6228, 0x000009b5, 0x000009b5, 0x000009b5, 0x000009b5, }, + { 0x1c622c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c6230, 0x00000108, 0x00000210, 0x00000210, 0x00000108, }, + { 0x1c6234, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, }, + { 0x1c6238, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, }, + { 0x1c623c, 0x13c889af, 0x13c889af, 0x13c889af, 0x13c889af, }, + { 0x1c6240, 0x38490a20, 0x38490a20, 0x38490a20, 0x38490a20, }, + { 0x1c6244, 0x00007bb6, 0x00007bb6, 0x00007bb6, 0x00007bb6, }, + { 0x1c6248, 0x0fff3ffc, 0x0fff3ffc, 0x0fff3ffc, 0x0fff3ffc, }, + { 0x1c624c, 0x00000001, 0x00000001, 0x00000001, 0x00000001, }, + { 0x1c6250, 0x0000a000, 0x0000a000, 0x0000a000, 0x0000a000, }, + { 0x1c6254, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c6258, 0x0cc75380, 0x0cc75380, 0x0cc75380, 0x0cc75380, }, + { 0x1c625c, 0x0f0f0f01, 0x0f0f0f01, 0x0f0f0f01, 0x0f0f0f01, }, + { 0x1c6260, 0xdfa91f01, 0xdfa91f01, 0xdfa91f01, 0xdfa91f01, }, + { 0x1c6264, 0x00418a11, 0x00418a11, 0x00418a11, 0x00418a11, }, + { 0x1c6268, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c626c, 0x09249126, 0x09249126, 0x09249126, 0x09249126, }, + { 0x1c6274, 0x0a1a9caa, 0x0a1a9caa, 0x0a1a7caa, 0x0a1a7caa, }, + { 0x1c6278, 0x1ce739ce, 0x1ce739ce, 0x1ce739ce, 0x1ce739ce, }, + { 0x1c627c, 0x051701ce, 0x051701ce, 0x051701ce, 0x051701ce, }, + { 0x1c6300, 0x18010000, 0x18010000, 0x18010000, 0x18010000, }, + { 0x1c6304, 0x30032602, 0x30032602, 0x2e032402, 0x2e032402, }, + { 0x1c6308, 0x48073e06, 0x48073e06, 0x4a0a3c06, 0x4a0a3c06, }, + { 0x1c630c, 0x560b4c0a, 0x560b4c0a, 0x621a540b, 0x621a540b, }, + { 0x1c6310, 0x641a600f, 0x641a600f, 0x764f6c1b, 0x764f6c1b, }, + { 0x1c6314, 0x7a4f6e1b, 0x7a4f6e1b, 0x845b7a5a, 0x845b7a5a, }, + { 0x1c6318, 0x8c5b7e5a, 0x8c5b7e5a, 0x950f8ccf, 0x950f8ccf, }, + { 0x1c631c, 0x9d0f96cf, 0x9d0f96cf, 0xa5cf9b4f, 0xa5cf9b4f, }, + { 0x1c6320, 0xb51fa69f, 0xb51fa69f, 0xbddfaf1f, 0xbddfaf1f, }, + { 0x1c6324, 0xcb3fbd07, 0xcb3fbcbf, 0xd1ffc93f, 0xd1ffc93f, }, + { 0x1c6328, 0x0000d7bf, 0x0000d7bf, 0x00000000, 0x00000000, }, + { 0x1c632c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c6330, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c6334, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c6338, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c633c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c6340, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c6344, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c6348, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, }, + { 0x1c634c, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, }, + { 0x1c6350, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, }, + { 0x1c6354, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, }, + { 0x1c6358, 0x79a8aa1f, 0x79a8aa1f, 0x79a8aa1f, 0x79a8aa1f, }, + { 0x1c6388, 0x08000000, 0x08000000, 0x08000000, 0x08000000, }, + { 0x1c638c, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, }, + { 0x1c6390, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, }, + { 0x1c6394, 0x1ce739ce, 0x1ce739ce, 0x1ce739ce, 0x1ce739ce, }, + { 0x1c6398, 0x000001ce, 0x000001ce, 0x000001ce, 0x000001ce, }, + { 0x1c639c, 0x00000007, 0x00000007, 0x00000007, 0x00000007, }, + { 0x1c63a0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c63a4, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c63a8, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c63ac, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c63b0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c63b4, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c63b8, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c63bc, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c63c0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c63c4, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c63c8, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c63cc, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, }, + { 0x1c63d0, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, }, + { 0x1c63d4, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, 0x3f3f3f3f, }, + { 0x1c63d8, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }, + { 0x1c63dc, 0x1ce739ce, 0x1ce739ce, 0x1ce739ce, 0x1ce739ce, }, + { 0x1c63e0, 0x000000c0, 0x000000c0, 0x000000c0, 0x000000c0, }, + { 0x1c6848, 0x00180a65, 0x00180a65, 0x00180a68, 0x00180a68, }, + { 0x1c6920, 0x0510001c, 0x0510001c, 0x0510001c, 0x0510001c, }, + { 0x1c6960, 0x00009b40, 0x00009b40, 0x00009b40, 0x00009b40, }, + { 0x1c720c, 0x012e8160, 0x012e8160, 0x012a8160, 0x012a8160, }, + { 0x1c726c, 0x09249126, 0x09249126, 0x09249126, 0x09249126, }, + { 0x1c7848, 0x00180a65, 0x00180a65, 0x00180a68, 0x00180a68, }, + { 0x1c7920, 0x0510001c, 0x0510001c, 0x0510001c, 0x0510001c, }, + { 0x1c7960, 0x00009b40, 0x00009b40, 0x00009b40, 0x00009b40, }, + { 0x1c820c, 0x012e8160, 0x012e8160, 0x012a8160, 0x012a8160, }, + { 0x1c826c, 0x09249126, 0x09249126, 0x09249126, 0x09249126, }, +/* { 0x1c8864, 0x0001ce00, 0x0001ce00, 0x0001ce00, 0x0001ce00, }, */ + { 0x1c8864, 0x0001c600, 0x0001c600, 0x0001c600, 0x0001c600, }, + { 0x1c895c, 0x004b6a8e, 0x004b6a8e, 0x004b6a8e, 0x004b6a8e, }, + { 0x1c8968, 0x000003ce, 0x000003ce, 0x000003ce, 0x000003ce, }, + { 0x1c89bc, 0x00181400, 0x00181400, 0x00181400, 0x00181400, }, + { 0x1c9270, 0x00820820, 0x00820820, 0x00820820, 0x00820820, }, + { 0x1c935c, 0x066c420f, 0x066c420f, 0x066c420f, 0x066c420f, }, + { 0x1c9360, 0x0f282207, 0x0f282207, 0x0f282207, 0x0f282207, }, + { 0x1c9364, 0x17601685, 0x17601685, 0x17601685, 0x17601685, }, + { 0x1c9368, 0x1f801104, 0x1f801104, 0x1f801104, 0x1f801104, }, + { 0x1c936c, 0x37a00c03, 0x37a00c03, 0x37a00c03, 0x37a00c03, }, + { 0x1c9370, 0x3fc40883, 0x3fc40883, 0x3fc40883, 0x3fc40883, }, + { 0x1c9374, 0x57c00803, 0x57c00803, 0x57c00803, 0x57c00803, }, + { 0x1c9378, 0x5fd80682, 0x5fd80682, 0x5fd80682, 0x5fd80682, }, + { 0x1c937c, 0x7fe00482, 0x7fe00482, 0x7fe00482, 0x7fe00482, }, + { 0x1c9380, 0x7f3c7bba, 0x7f3c7bba, 0x7f3c7bba, 0x7f3c7bba, }, + { 0x1c9384, 0xf3307ff0, 0xf3307ff0, 0xf3307ff0, 0xf3307ff0, } +}; + +/* + * look up a certain register in ar5416_phy_init[] and return the init. value + * for the band and bandwidth given. Return 0 if register address not found. + */ +static u32 carl9170_def_val(u32 reg, bool is_2ghz, bool is_40mhz) +{ + unsigned int i; + for (i = 0; i < ARRAY_SIZE(ar5416_phy_init); i++) { + if (ar5416_phy_init[i].reg != reg) + continue; + + if (is_2ghz) { + if (is_40mhz) + return ar5416_phy_init[i]._2ghz_40; + else + return ar5416_phy_init[i]._2ghz_20; + } else { + if (is_40mhz) + return ar5416_phy_init[i]._5ghz_40; + else + return ar5416_phy_init[i]._5ghz_20; + } + } + return 0; +} + +/* + * initialize some phy regs from eeprom values in modal_header[] + * acc. to band and bandwidth + */ +static int carl9170_init_phy_from_eeprom(struct ar9170 *ar, + bool is_2ghz, bool is_40mhz) +{ + static const u8 xpd2pd[16] = { + 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x6, 0x2, + 0x2, 0x3, 0x7, 0x2, 0xb, 0x2, 0x2, 0x2 + }; + /* pointer to the modal_header acc. to band */ + struct ar9170_eeprom_modal *m = &ar->eeprom.modal_header[is_2ghz]; + u32 val; + + carl9170_regwrite_begin(ar); + + /* ant common control (index 0) */ + carl9170_regwrite(AR9170_PHY_REG_SWITCH_COM, + le32_to_cpu(m->antCtrlCommon)); + + /* ant control chain 0 (index 1) */ + carl9170_regwrite(AR9170_PHY_REG_SWITCH_CHAIN_0, + le32_to_cpu(m->antCtrlChain[0])); + + /* ant control chain 2 (index 2) */ + carl9170_regwrite(AR9170_PHY_REG_SWITCH_CHAIN_2, + le32_to_cpu(m->antCtrlChain[1])); + + /* SwSettle (index 3) */ + if (!is_40mhz) { + val = carl9170_def_val(AR9170_PHY_REG_SETTLING, + is_2ghz, is_40mhz); + SET_VAL(AR9170_PHY_SETTLING_SWITCH, val, m->switchSettling); + carl9170_regwrite(AR9170_PHY_REG_SETTLING, val); + } + + /* adcDesired, pdaDesired (index 4) */ + val = carl9170_def_val(AR9170_PHY_REG_DESIRED_SZ, is_2ghz, is_40mhz); + SET_VAL(AR9170_PHY_DESIRED_SZ_PGA, val, m->pgaDesiredSize); + SET_VAL(AR9170_PHY_DESIRED_SZ_ADC, val, m->adcDesiredSize); + carl9170_regwrite(AR9170_PHY_REG_DESIRED_SZ, val); + + /* TxEndToXpaOff, TxFrameToXpaOn (index 5) */ + val = carl9170_def_val(AR9170_PHY_REG_RF_CTL4, is_2ghz, is_40mhz); + SET_VAL(AR9170_PHY_RF_CTL4_TX_END_XPAB_OFF, val, m->txEndToXpaOff); + SET_VAL(AR9170_PHY_RF_CTL4_TX_END_XPAA_OFF, val, m->txEndToXpaOff); + SET_VAL(AR9170_PHY_RF_CTL4_FRAME_XPAB_ON, val, m->txFrameToXpaOn); + SET_VAL(AR9170_PHY_RF_CTL4_FRAME_XPAA_ON, val, m->txFrameToXpaOn); + carl9170_regwrite(AR9170_PHY_REG_RF_CTL4, val); + + /* TxEndToRxOn (index 6) */ + val = carl9170_def_val(AR9170_PHY_REG_RF_CTL3, is_2ghz, is_40mhz); + SET_VAL(AR9170_PHY_RF_CTL3_TX_END_TO_A2_RX_ON, val, m->txEndToRxOn); + carl9170_regwrite(AR9170_PHY_REG_RF_CTL3, val); + + /* thresh62 (index 7) */ + val = carl9170_def_val(0x1c8864, is_2ghz, is_40mhz); + val = (val & ~0x7f000) | (m->thresh62 << 12); + carl9170_regwrite(0x1c8864, val); + + /* tx/rx attenuation chain 0 (index 8) */ + val = carl9170_def_val(AR9170_PHY_REG_RXGAIN, is_2ghz, is_40mhz); + SET_VAL(AR9170_PHY_RXGAIN_TXRX_ATTEN, val, m->txRxAttenCh[0]); + carl9170_regwrite(AR9170_PHY_REG_RXGAIN, val); + + /* tx/rx attenuation chain 2 (index 9) */ + val = carl9170_def_val(AR9170_PHY_REG_RXGAIN_CHAIN_2, + is_2ghz, is_40mhz); + SET_VAL(AR9170_PHY_RXGAIN_TXRX_ATTEN, val, m->txRxAttenCh[1]); + carl9170_regwrite(AR9170_PHY_REG_RXGAIN_CHAIN_2, val); + + /* tx/rx margin chain 0 (index 10) */ + val = carl9170_def_val(AR9170_PHY_REG_GAIN_2GHZ, is_2ghz, is_40mhz); + SET_VAL(AR9170_PHY_GAIN_2GHZ_RXTX_MARGIN, val, m->rxTxMarginCh[0]); + /* bsw margin chain 0 for 5GHz only */ + if (!is_2ghz) + SET_VAL(AR9170_PHY_GAIN_2GHZ_BSW_MARGIN, val, m->bswMargin[0]); + carl9170_regwrite(AR9170_PHY_REG_GAIN_2GHZ, val); + + /* tx/rx margin chain 2 (index 11) */ + val = carl9170_def_val(AR9170_PHY_REG_GAIN_2GHZ_CHAIN_2, + is_2ghz, is_40mhz); + SET_VAL(AR9170_PHY_GAIN_2GHZ_RXTX_MARGIN, val, m->rxTxMarginCh[1]); + carl9170_regwrite(AR9170_PHY_REG_GAIN_2GHZ_CHAIN_2, val); + + /* iqCall, iqCallq chain 0 (index 12) */ + val = carl9170_def_val(AR9170_PHY_REG_TIMING_CTRL4(0), + is_2ghz, is_40mhz); + SET_VAL(AR9170_PHY_TIMING_CTRL4_IQCORR_Q_I_COFF, val, m->iqCalICh[0]); + SET_VAL(AR9170_PHY_TIMING_CTRL4_IQCORR_Q_Q_COFF, val, m->iqCalQCh[0]); + carl9170_regwrite(AR9170_PHY_REG_TIMING_CTRL4(0), val); + + /* iqCall, iqCallq chain 2 (index 13) */ + val = carl9170_def_val(AR9170_PHY_REG_TIMING_CTRL4(2), + is_2ghz, is_40mhz); + SET_VAL(AR9170_PHY_TIMING_CTRL4_IQCORR_Q_I_COFF, val, m->iqCalICh[1]); + SET_VAL(AR9170_PHY_TIMING_CTRL4_IQCORR_Q_Q_COFF, val, m->iqCalQCh[1]); + carl9170_regwrite(AR9170_PHY_REG_TIMING_CTRL4(2), val); + + /* xpd gain mask (index 14) */ + val = carl9170_def_val(AR9170_PHY_REG_TPCRG1, is_2ghz, is_40mhz); + SET_VAL(AR9170_PHY_TPCRG1_PD_GAIN_1, val, + xpd2pd[m->xpdGain & 0xf] & 3); + SET_VAL(AR9170_PHY_TPCRG1_PD_GAIN_2, val, + xpd2pd[m->xpdGain & 0xf] >> 2); + carl9170_regwrite(AR9170_PHY_REG_TPCRG1, val); + + carl9170_regwrite(AR9170_PHY_REG_RX_CHAINMASK, ar->eeprom.rx_mask); + carl9170_regwrite(AR9170_PHY_REG_CAL_CHAINMASK, ar->eeprom.rx_mask); + + carl9170_regwrite_finish(); + return carl9170_regwrite_result(); +} + +static int carl9170_init_phy(struct ar9170 *ar, enum ieee80211_band band) +{ + int i, err; + u32 val; + bool is_2ghz = band == IEEE80211_BAND_2GHZ; + bool is_40mhz = conf_is_ht40(&ar->hw->conf); + + carl9170_regwrite_begin(ar); + + for (i = 0; i < ARRAY_SIZE(ar5416_phy_init); i++) { + if (is_40mhz) { + if (is_2ghz) + val = ar5416_phy_init[i]._2ghz_40; + else + val = ar5416_phy_init[i]._5ghz_40; + } else { + if (is_2ghz) + val = ar5416_phy_init[i]._2ghz_20; + else + val = ar5416_phy_init[i]._5ghz_20; + } + + carl9170_regwrite(ar5416_phy_init[i].reg, val); + } + + carl9170_regwrite_finish(); + err = carl9170_regwrite_result(); + if (err) + return err; + + err = carl9170_init_phy_from_eeprom(ar, is_2ghz, is_40mhz); + if (err) + return err; + + err = carl9170_init_power_cal(ar); + if (err) + return err; + + if (!ar->fw.hw_counters) { + err = carl9170_write_reg(ar, AR9170_PWR_REG_PLL_ADDAC, + is_2ghz ? 0x5163 : 0x5143); + } + + return err; +} + +struct carl9170_rf_initvals { + u32 reg, _5ghz, _2ghz; +}; + +static struct carl9170_rf_initvals carl9170_rf_initval[] = { + /* bank 0 */ + { 0x1c58b0, 0x1e5795e5, 0x1e5795e5}, + { 0x1c58e0, 0x02008020, 0x02008020}, + /* bank 1 */ + { 0x1c58b0, 0x02108421, 0x02108421}, + { 0x1c58ec, 0x00000008, 0x00000008}, + /* bank 2 */ + { 0x1c58b0, 0x0e73ff17, 0x0e73ff17}, + { 0x1c58e0, 0x00000420, 0x00000420}, + /* bank 3 */ + { 0x1c58f0, 0x01400018, 0x01c00018}, + /* bank 4 */ + { 0x1c58b0, 0x000001a1, 0x000001a1}, + { 0x1c58e8, 0x00000001, 0x00000001}, + /* bank 5 */ + { 0x1c58b0, 0x00000013, 0x00000013}, + { 0x1c58e4, 0x00000002, 0x00000002}, + /* bank 6 */ + { 0x1c58b0, 0x00000000, 0x00000000}, + { 0x1c58b0, 0x00000000, 0x00000000}, + { 0x1c58b0, 0x00000000, 0x00000000}, + { 0x1c58b0, 0x00000000, 0x00000000}, + { 0x1c58b0, 0x00000000, 0x00000000}, + { 0x1c58b0, 0x00004000, 0x00004000}, + { 0x1c58b0, 0x00006c00, 0x00006c00}, + { 0x1c58b0, 0x00002c00, 0x00002c00}, + { 0x1c58b0, 0x00004800, 0x00004800}, + { 0x1c58b0, 0x00004000, 0x00004000}, + { 0x1c58b0, 0x00006000, 0x00006000}, + { 0x1c58b0, 0x00001000, 0x00001000}, + { 0x1c58b0, 0x00004000, 0x00004000}, + { 0x1c58b0, 0x00007c00, 0x00007c00}, + { 0x1c58b0, 0x00007c00, 0x00007c00}, + { 0x1c58b0, 0x00007c00, 0x00007c00}, + { 0x1c58b0, 0x00007c00, 0x00007c00}, + { 0x1c58b0, 0x00007c00, 0x00007c00}, + { 0x1c58b0, 0x00087c00, 0x00087c00}, + { 0x1c58b0, 0x00007c00, 0x00007c00}, + { 0x1c58b0, 0x00005400, 0x00005400}, + { 0x1c58b0, 0x00000c00, 0x00000c00}, + { 0x1c58b0, 0x00001800, 0x00001800}, + { 0x1c58b0, 0x00007c00, 0x00007c00}, + { 0x1c58b0, 0x00006c00, 0x00006c00}, + { 0x1c58b0, 0x00006c00, 0x00006c00}, + { 0x1c58b0, 0x00007c00, 0x00007c00}, + { 0x1c58b0, 0x00002c00, 0x00002c00}, + { 0x1c58b0, 0x00003c00, 0x00003c00}, + { 0x1c58b0, 0x00003800, 0x00003800}, + { 0x1c58b0, 0x00001c00, 0x00001c00}, + { 0x1c58b0, 0x00000800, 0x00000800}, + { 0x1c58b0, 0x00000408, 0x00000408}, + { 0x1c58b0, 0x00004c15, 0x00004c15}, + { 0x1c58b0, 0x00004188, 0x00004188}, + { 0x1c58b0, 0x0000201e, 0x0000201e}, + { 0x1c58b0, 0x00010408, 0x00010408}, + { 0x1c58b0, 0x00000801, 0x00000801}, + { 0x1c58b0, 0x00000c08, 0x00000c08}, + { 0x1c58b0, 0x0000181e, 0x0000181e}, + { 0x1c58b0, 0x00001016, 0x00001016}, + { 0x1c58b0, 0x00002800, 0x00002800}, + { 0x1c58b0, 0x00004010, 0x00004010}, + { 0x1c58b0, 0x0000081c, 0x0000081c}, + { 0x1c58b0, 0x00000115, 0x00000115}, + { 0x1c58b0, 0x00000015, 0x00000015}, + { 0x1c58b0, 0x00000066, 0x00000066}, + { 0x1c58b0, 0x0000001c, 0x0000001c}, + { 0x1c58b0, 0x00000000, 0x00000000}, + { 0x1c58b0, 0x00000004, 0x00000004}, + { 0x1c58b0, 0x00000015, 0x00000015}, + { 0x1c58b0, 0x0000001f, 0x0000001f}, + { 0x1c58e0, 0x00000000, 0x00000400}, + /* bank 7 */ + { 0x1c58b0, 0x000000a0, 0x000000a0}, + { 0x1c58b0, 0x00000000, 0x00000000}, + { 0x1c58b0, 0x00000040, 0x00000040}, + { 0x1c58f0, 0x0000001c, 0x0000001c}, +}; + +static int carl9170_init_rf_banks_0_7(struct ar9170 *ar, bool band5ghz) +{ + int err, i; + + carl9170_regwrite_begin(ar); + + for (i = 0; i < ARRAY_SIZE(carl9170_rf_initval); i++) + carl9170_regwrite(carl9170_rf_initval[i].reg, + band5ghz ? carl9170_rf_initval[i]._5ghz + : carl9170_rf_initval[i]._2ghz); + + carl9170_regwrite_finish(); + err = carl9170_regwrite_result(); + if (err) + wiphy_err(ar->hw->wiphy, "rf init failed\n"); + + return err; +} + +struct carl9170_phy_freq_params { + u8 coeff_exp; + u16 coeff_man; + u8 coeff_exp_shgi; + u16 coeff_man_shgi; +}; + +enum carl9170_bw { + CARL9170_BW_20, + CARL9170_BW_40_BELOW, + CARL9170_BW_40_ABOVE, + + __CARL9170_NUM_BW, +}; + +struct carl9170_phy_freq_entry { + u16 freq; + struct carl9170_phy_freq_params params[__CARL9170_NUM_BW]; +}; + +/* NB: must be in sync with channel tables in main! */ +static const struct carl9170_phy_freq_entry carl9170_phy_freq_params[] = { +/* + * freq, + * 20MHz, + * 40MHz (below), + * 40Mhz (above), + */ + { 2412, { + { 3, 21737, 3, 19563, }, + { 3, 21827, 3, 19644, }, + { 3, 21647, 3, 19482, }, + } }, + { 2417, { + { 3, 21692, 3, 19523, }, + { 3, 21782, 3, 19604, }, + { 3, 21602, 3, 19442, }, + } }, + { 2422, { + { 3, 21647, 3, 19482, }, + { 3, 21737, 3, 19563, }, + { 3, 21558, 3, 19402, }, + } }, + { 2427, { + { 3, 21602, 3, 19442, }, + { 3, 21692, 3, 19523, }, + { 3, 21514, 3, 19362, }, + } }, + { 2432, { + { 3, 21558, 3, 19402, }, + { 3, 21647, 3, 19482, }, + { 3, 21470, 3, 19323, }, + } }, + { 2437, { + { 3, 21514, 3, 19362, }, + { 3, 21602, 3, 19442, }, + { 3, 21426, 3, 19283, }, + } }, + { 2442, { + { 3, 21470, 3, 19323, }, + { 3, 21558, 3, 19402, }, + { 3, 21382, 3, 19244, }, + } }, + { 2447, { + { 3, 21426, 3, 19283, }, + { 3, 21514, 3, 19362, }, + { 3, 21339, 3, 19205, }, + } }, + { 2452, { + { 3, 21382, 3, 19244, }, + { 3, 21470, 3, 19323, }, + { 3, 21295, 3, 19166, }, + } }, + { 2457, { + { 3, 21339, 3, 19205, }, + { 3, 21426, 3, 19283, }, + { 3, 21252, 3, 19127, }, + } }, + { 2462, { + { 3, 21295, 3, 19166, }, + { 3, 21382, 3, 19244, }, + { 3, 21209, 3, 19088, }, + } }, + { 2467, { + { 3, 21252, 3, 19127, }, + { 3, 21339, 3, 19205, }, + { 3, 21166, 3, 19050, }, + } }, + { 2472, { + { 3, 21209, 3, 19088, }, + { 3, 21295, 3, 19166, }, + { 3, 21124, 3, 19011, }, + } }, + { 2484, { + { 3, 21107, 3, 18996, }, + { 3, 21192, 3, 19073, }, + { 3, 21022, 3, 18920, }, + } }, + { 4920, { + { 4, 21313, 4, 19181, }, + { 4, 21356, 4, 19220, }, + { 4, 21269, 4, 19142, }, + } }, + { 4940, { + { 4, 21226, 4, 19104, }, + { 4, 21269, 4, 19142, }, + { 4, 21183, 4, 19065, }, + } }, + { 4960, { + { 4, 21141, 4, 19027, }, + { 4, 21183, 4, 19065, }, + { 4, 21098, 4, 18988, }, + } }, + { 4980, { + { 4, 21056, 4, 18950, }, + { 4, 21098, 4, 18988, }, + { 4, 21014, 4, 18912, }, + } }, + { 5040, { + { 4, 20805, 4, 18725, }, + { 4, 20846, 4, 18762, }, + { 4, 20764, 4, 18687, }, + } }, + { 5060, { + { 4, 20723, 4, 18651, }, + { 4, 20764, 4, 18687, }, + { 4, 20682, 4, 18614, }, + } }, + { 5080, { + { 4, 20641, 4, 18577, }, + { 4, 20682, 4, 18614, }, + { 4, 20601, 4, 18541, }, + } }, + { 5180, { + { 4, 20243, 4, 18219, }, + { 4, 20282, 4, 18254, }, + { 4, 20204, 4, 18183, }, + } }, + { 5200, { + { 4, 20165, 4, 18148, }, + { 4, 20204, 4, 18183, }, + { 4, 20126, 4, 18114, }, + } }, + { 5220, { + { 4, 20088, 4, 18079, }, + { 4, 20126, 4, 18114, }, + { 4, 20049, 4, 18044, }, + } }, + { 5240, { + { 4, 20011, 4, 18010, }, + { 4, 20049, 4, 18044, }, + { 4, 19973, 4, 17976, }, + } }, + { 5260, { + { 4, 19935, 4, 17941, }, + { 4, 19973, 4, 17976, }, + { 4, 19897, 4, 17907, }, + } }, + { 5280, { + { 4, 19859, 4, 17873, }, + { 4, 19897, 4, 17907, }, + { 4, 19822, 4, 17840, }, + } }, + { 5300, { + { 4, 19784, 4, 17806, }, + { 4, 19822, 4, 17840, }, + { 4, 19747, 4, 17772, }, + } }, + { 5320, { + { 4, 19710, 4, 17739, }, + { 4, 19747, 4, 17772, }, + { 4, 19673, 4, 17706, }, + } }, + { 5500, { + { 4, 19065, 4, 17159, }, + { 4, 19100, 4, 17190, }, + { 4, 19030, 4, 17127, }, + } }, + { 5520, { + { 4, 18996, 4, 17096, }, + { 4, 19030, 4, 17127, }, + { 4, 18962, 4, 17065, }, + } }, + { 5540, { + { 4, 18927, 4, 17035, }, + { 4, 18962, 4, 17065, }, + { 4, 18893, 4, 17004, }, + } }, + { 5560, { + { 4, 18859, 4, 16973, }, + { 4, 18893, 4, 17004, }, + { 4, 18825, 4, 16943, }, + } }, + { 5580, { + { 4, 18792, 4, 16913, }, + { 4, 18825, 4, 16943, }, + { 4, 18758, 4, 16882, }, + } }, + { 5600, { + { 4, 18725, 4, 16852, }, + { 4, 18758, 4, 16882, }, + { 4, 18691, 4, 16822, }, + } }, + { 5620, { + { 4, 18658, 4, 16792, }, + { 4, 18691, 4, 16822, }, + { 4, 18625, 4, 16762, }, + } }, + { 5640, { + { 4, 18592, 4, 16733, }, + { 4, 18625, 4, 16762, }, + { 4, 18559, 4, 16703, }, + } }, + { 5660, { + { 4, 18526, 4, 16673, }, + { 4, 18559, 4, 16703, }, + { 4, 18493, 4, 16644, }, + } }, + { 5680, { + { 4, 18461, 4, 16615, }, + { 4, 18493, 4, 16644, }, + { 4, 18428, 4, 16586, }, + } }, + { 5700, { + { 4, 18396, 4, 16556, }, + { 4, 18428, 4, 16586, }, + { 4, 18364, 4, 16527, }, + } }, + { 5745, { + { 4, 18252, 4, 16427, }, + { 4, 18284, 4, 16455, }, + { 4, 18220, 4, 16398, }, + } }, + { 5765, { + { 4, 18189, 5, 32740, }, + { 4, 18220, 4, 16398, }, + { 4, 18157, 5, 32683, }, + } }, + { 5785, { + { 4, 18126, 5, 32626, }, + { 4, 18157, 5, 32683, }, + { 4, 18094, 5, 32570, }, + } }, + { 5805, { + { 4, 18063, 5, 32514, }, + { 4, 18094, 5, 32570, }, + { 4, 18032, 5, 32458, }, + } }, + { 5825, { + { 4, 18001, 5, 32402, }, + { 4, 18032, 5, 32458, }, + { 4, 17970, 5, 32347, }, + } }, + { 5170, { + { 4, 20282, 4, 18254, }, + { 4, 20321, 4, 18289, }, + { 4, 20243, 4, 18219, }, + } }, + { 5190, { + { 4, 20204, 4, 18183, }, + { 4, 20243, 4, 18219, }, + { 4, 20165, 4, 18148, }, + } }, + { 5210, { + { 4, 20126, 4, 18114, }, + { 4, 20165, 4, 18148, }, + { 4, 20088, 4, 18079, }, + } }, + { 5230, { + { 4, 20049, 4, 18044, }, + { 4, 20088, 4, 18079, }, + { 4, 20011, 4, 18010, }, + } }, +}; + +static int carl9170_init_rf_bank4_pwr(struct ar9170 *ar, bool band5ghz, + u32 freq, enum carl9170_bw bw) +{ + int err; + u32 d0, d1, td0, td1, fd0, fd1; + u8 chansel; + u8 refsel0 = 1, refsel1 = 0; + u8 lf_synth = 0; + + switch (bw) { + case CARL9170_BW_40_ABOVE: + freq += 10; + break; + case CARL9170_BW_40_BELOW: + freq -= 10; + break; + case CARL9170_BW_20: + break; + default: + BUG(); + return -ENOSYS; + } + + if (band5ghz) { + if (freq % 10) { + chansel = (freq - 4800) / 5; + } else { + chansel = ((freq - 4800) / 10) * 2; + refsel0 = 0; + refsel1 = 1; + } + chansel = byte_rev_table[chansel]; + } else { + if (freq == 2484) { + chansel = 10 + (freq - 2274) / 5; + lf_synth = 1; + } else + chansel = 16 + (freq - 2272) / 5; + chansel *= 4; + chansel = byte_rev_table[chansel]; + } + + d1 = chansel; + d0 = 0x21 | + refsel0 << 3 | + refsel1 << 2 | + lf_synth << 1; + td0 = d0 & 0x1f; + td1 = d1 & 0x1f; + fd0 = td1 << 5 | td0; + + td0 = (d0 >> 5) & 0x7; + td1 = (d1 >> 5) & 0x7; + fd1 = td1 << 5 | td0; + + carl9170_regwrite_begin(ar); + + carl9170_regwrite(0x1c58b0, fd0); + carl9170_regwrite(0x1c58e8, fd1); + + carl9170_regwrite_finish(); + err = carl9170_regwrite_result(); + if (err) + return err; + + return 0; +} + +static const struct carl9170_phy_freq_params * +carl9170_get_hw_dyn_params(struct ieee80211_channel *channel, + enum carl9170_bw bw) +{ + unsigned int chanidx = 0; + u16 freq = 2412; + + if (channel) { + chanidx = channel->hw_value; + freq = channel->center_freq; + } + + BUG_ON(chanidx >= ARRAY_SIZE(carl9170_phy_freq_params)); + + BUILD_BUG_ON(__CARL9170_NUM_BW != 3); + + WARN_ON(carl9170_phy_freq_params[chanidx].freq != freq); + + return &carl9170_phy_freq_params[chanidx].params[bw]; +} + +static int carl9170_find_freq_idx(int nfreqs, u8 *freqs, u8 f) +{ + int idx = nfreqs - 2; + + while (idx >= 0) { + if (f >= freqs[idx]) + return idx; + idx--; + } + + return 0; +} + +static s32 carl9170_interpolate_s32(s32 x, s32 x1, s32 y1, s32 x2, s32 y2) +{ + /* nothing to interpolate, it's horizontal */ + if (y2 == y1) + return y1; + + /* check if we hit one of the edges */ + if (x == x1) + return y1; + if (x == x2) + return y2; + + /* x1 == x2 is bad, hopefully == x */ + if (x2 == x1) + return y1; + + return y1 + (((y2 - y1) * (x - x1)) / (x2 - x1)); +} + +static u8 carl9170_interpolate_u8(u8 x, u8 x1, u8 y1, u8 x2, u8 y2) +{ +#define SHIFT 8 + s32 y; + + y = carl9170_interpolate_s32(x << SHIFT, x1 << SHIFT, + y1 << SHIFT, x2 << SHIFT, y2 << SHIFT); + + /* + * XXX: unwrap this expression + * Isn't it just DIV_ROUND_UP(y, 1<> SHIFT) + ((y & (1 << (SHIFT - 1))) >> (SHIFT - 1)); +#undef SHIFT +} + +static u8 carl9170_interpolate_val(u8 x, u8 *x_array, u8 *y_array) +{ + int i; + + for (i = 0; i < 3; i++) { + if (x <= x_array[i + 1]) + break; + } + + return carl9170_interpolate_u8(x, x_array[i], y_array[i], + x_array[i + 1], y_array[i + 1]); +} + +static int carl9170_set_freq_cal_data(struct ar9170 *ar, + struct ieee80211_channel *channel) +{ + u8 *cal_freq_pier; + u8 vpds[2][AR5416_PD_GAIN_ICEPTS]; + u8 pwrs[2][AR5416_PD_GAIN_ICEPTS]; + int chain, idx, i; + u32 phy_data = 0; + u8 f, tmp; + + switch (channel->band) { + case IEEE80211_BAND_2GHZ: + f = channel->center_freq - 2300; + cal_freq_pier = ar->eeprom.cal_freq_pier_2G; + i = AR5416_NUM_2G_CAL_PIERS - 1; + break; + + case IEEE80211_BAND_5GHZ: + f = (channel->center_freq - 4800) / 5; + cal_freq_pier = ar->eeprom.cal_freq_pier_5G; + i = AR5416_NUM_5G_CAL_PIERS - 1; + break; + + default: + return -EINVAL; + break; + } + + for (; i >= 0; i--) { + if (cal_freq_pier[i] != 0xff) + break; + } + if (i < 0) + return -EINVAL; + + idx = carl9170_find_freq_idx(i, cal_freq_pier, f); + + carl9170_regwrite_begin(ar); + + for (chain = 0; chain < AR5416_MAX_CHAINS; chain++) { + for (i = 0; i < AR5416_PD_GAIN_ICEPTS; i++) { + struct ar9170_calibration_data_per_freq *cal_pier_data; + int j; + + switch (channel->band) { + case IEEE80211_BAND_2GHZ: + cal_pier_data = &ar->eeprom. + cal_pier_data_2G[chain][idx]; + break; + + case IEEE80211_BAND_5GHZ: + cal_pier_data = &ar->eeprom. + cal_pier_data_5G[chain][idx]; + break; + + default: + return -EINVAL; + } + + for (j = 0; j < 2; j++) { + vpds[j][i] = carl9170_interpolate_u8(f, + cal_freq_pier[idx], + cal_pier_data->vpd_pdg[j][i], + cal_freq_pier[idx + 1], + cal_pier_data[1].vpd_pdg[j][i]); + + pwrs[j][i] = carl9170_interpolate_u8(f, + cal_freq_pier[idx], + cal_pier_data->pwr_pdg[j][i], + cal_freq_pier[idx + 1], + cal_pier_data[1].pwr_pdg[j][i]) / 2; + } + } + + for (i = 0; i < 76; i++) { + if (i < 25) { + tmp = carl9170_interpolate_val(i, &pwrs[0][0], + &vpds[0][0]); + } else { + tmp = carl9170_interpolate_val(i - 12, + &pwrs[1][0], + &vpds[1][0]); + } + + phy_data |= tmp << ((i & 3) << 3); + if ((i & 3) == 3) { + carl9170_regwrite(0x1c6280 + chain * 0x1000 + + (i & ~3), phy_data); + phy_data = 0; + } + } + + for (i = 19; i < 32; i++) + carl9170_regwrite(0x1c6280 + chain * 0x1000 + (i << 2), + 0x0); + } + + carl9170_regwrite_finish(); + return carl9170_regwrite_result(); +} + +static u8 carl9170_get_max_edge_power(struct ar9170 *ar, + u32 freq, struct ar9170_calctl_edges edges[]) +{ + int i; + u8 rc = AR5416_MAX_RATE_POWER; + u8 f; + if (freq < 3000) + f = freq - 2300; + else + f = (freq - 4800) / 5; + + for (i = 0; i < AR5416_NUM_BAND_EDGES; i++) { + if (edges[i].channel == 0xff) + break; + if (f == edges[i].channel) { + /* exact freq match */ + rc = edges[i].power_flags & ~AR9170_CALCTL_EDGE_FLAGS; + break; + } + if (i > 0 && f < edges[i].channel) { + if (f > edges[i - 1].channel && + edges[i - 1].power_flags & + AR9170_CALCTL_EDGE_FLAGS) { + /* lower channel has the inband flag set */ + rc = edges[i - 1].power_flags & + ~AR9170_CALCTL_EDGE_FLAGS; + } + break; + } + } + + if (i == AR5416_NUM_BAND_EDGES) { + if (f > edges[i - 1].channel && + edges[i - 1].power_flags & AR9170_CALCTL_EDGE_FLAGS) { + /* lower channel has the inband flag set */ + rc = edges[i - 1].power_flags & + ~AR9170_CALCTL_EDGE_FLAGS; + } + } + return rc; +} + +static u8 carl9170_get_heavy_clip(struct ar9170 *ar, u32 freq, + enum carl9170_bw bw, struct ar9170_calctl_edges edges[]) +{ + u8 f; + int i; + u8 rc = 0; + + if (freq < 3000) + f = freq - 2300; + else + f = (freq - 4800) / 5; + + if (bw == CARL9170_BW_40_BELOW || bw == CARL9170_BW_40_ABOVE) + rc |= 0xf0; + + for (i = 0; i < AR5416_NUM_BAND_EDGES; i++) { + if (edges[i].channel == 0xff) + break; + if (f == edges[i].channel) { + if (!(edges[i].power_flags & AR9170_CALCTL_EDGE_FLAGS)) + rc |= 0x0f; + break; + } + } + + return rc; +} + +/* + * calculate the conformance test limits and the heavy clip parameter + * and apply them to ar->power* (derived from otus hal/hpmain.c, line 3706) + */ +static void carl9170_calc_ctl(struct ar9170 *ar, u32 freq, enum carl9170_bw bw) +{ + u8 ctl_grp; /* CTL group */ + u8 ctl_idx; /* CTL index */ + int i, j; + struct ctl_modes { + u8 ctl_mode; + u8 max_power; + u8 *pwr_cal_data; + int pwr_cal_len; + } *modes; + + /* + * order is relevant in the mode_list_*: we fall back to the + * lower indices if any mode is missed in the EEPROM. + */ + struct ctl_modes mode_list_2ghz[] = { + { CTL_11B, 0, ar->power_2G_cck, 4 }, + { CTL_11G, 0, ar->power_2G_ofdm, 4 }, + { CTL_2GHT20, 0, ar->power_2G_ht20, 8 }, + { CTL_2GHT40, 0, ar->power_2G_ht40, 8 }, + }; + struct ctl_modes mode_list_5ghz[] = { + { CTL_11A, 0, ar->power_5G_leg, 4 }, + { CTL_5GHT20, 0, ar->power_5G_ht20, 8 }, + { CTL_5GHT40, 0, ar->power_5G_ht40, 8 }, + }; + int nr_modes; + +#define EDGES(c, n) (ar->eeprom.ctl_data[c].control_edges[n]) + + ar->heavy_clip = 0; + + /* + * TODO: investigate the differences between OTUS' + * hpreg.c::zfHpGetRegulatoryDomain() and + * ath/regd.c::ath_regd_get_band_ctl() - + * e.g. for FCC3_WORLD the OTUS procedure + * always returns CTL_FCC, while the one in ath/ delivers + * CTL_ETSI for 2GHz and CTL_FCC for 5GHz. + */ + ctl_grp = ath_regd_get_band_ctl(&ar->common.regulatory, + ar->hw->conf.chandef.chan->band); + + /* ctl group not found - either invalid band (NO_CTL) or ww roaming */ + if (ctl_grp == NO_CTL || ctl_grp == SD_NO_CTL) + ctl_grp = CTL_FCC; + + if (ctl_grp != CTL_FCC) + /* skip CTL and heavy clip for CTL_MKK and CTL_ETSI */ + return; + + if (ar->hw->conf.chandef.chan->band == IEEE80211_BAND_2GHZ) { + modes = mode_list_2ghz; + nr_modes = ARRAY_SIZE(mode_list_2ghz); + } else { + modes = mode_list_5ghz; + nr_modes = ARRAY_SIZE(mode_list_5ghz); + } + + for (i = 0; i < nr_modes; i++) { + u8 c = ctl_grp | modes[i].ctl_mode; + for (ctl_idx = 0; ctl_idx < AR5416_NUM_CTLS; ctl_idx++) + if (c == ar->eeprom.ctl_index[ctl_idx]) + break; + if (ctl_idx < AR5416_NUM_CTLS) { + int f_off = 0; + + /* + * determine heavy clip parameter + * from the 11G edges array + */ + if (modes[i].ctl_mode == CTL_11G) { + ar->heavy_clip = + carl9170_get_heavy_clip(ar, + freq, bw, EDGES(ctl_idx, 1)); + } + + /* adjust freq for 40MHz */ + if (modes[i].ctl_mode == CTL_2GHT40 || + modes[i].ctl_mode == CTL_5GHT40) { + if (bw == CARL9170_BW_40_BELOW) + f_off = -10; + else + f_off = 10; + } + + modes[i].max_power = + carl9170_get_max_edge_power(ar, + freq + f_off, EDGES(ctl_idx, 1)); + + /* + * TODO: check if the regulatory max. power is + * controlled by cfg80211 for DFS. + * (hpmain applies it to max_power itself for DFS freq) + */ + + } else { + /* + * Workaround in otus driver, hpmain.c, line 3906: + * if no data for 5GHT20 are found, take the + * legacy 5G value. We extend this here to fallback + * from any other HT* or 11G, too. + */ + int k = i; + + modes[i].max_power = AR5416_MAX_RATE_POWER; + while (k-- > 0) { + if (modes[k].max_power != + AR5416_MAX_RATE_POWER) { + modes[i].max_power = modes[k].max_power; + break; + } + } + } + + /* apply max power to pwr_cal_data (ar->power_*) */ + for (j = 0; j < modes[i].pwr_cal_len; j++) { + modes[i].pwr_cal_data[j] = min(modes[i].pwr_cal_data[j], + modes[i].max_power); + } + } + + if (ar->heavy_clip & 0xf0) { + ar->power_2G_ht40[0]--; + ar->power_2G_ht40[1]--; + ar->power_2G_ht40[2]--; + } + if (ar->heavy_clip & 0xf) { + ar->power_2G_ht20[0]++; + ar->power_2G_ht20[1]++; + ar->power_2G_ht20[2]++; + } + +#undef EDGES +} + +static void carl9170_set_power_cal(struct ar9170 *ar, u32 freq, + enum carl9170_bw bw) +{ + struct ar9170_calibration_target_power_legacy *ctpl; + struct ar9170_calibration_target_power_ht *ctph; + u8 *ctpres; + int ntargets; + int idx, i, n; + u8 f; + u8 pwr_freqs[AR5416_MAX_NUM_TGT_PWRS]; + + if (freq < 3000) + f = freq - 2300; + else + f = (freq - 4800) / 5; + + /* + * cycle through the various modes + * + * legacy modes first: 5G, 2G CCK, 2G OFDM + */ + for (i = 0; i < 3; i++) { + switch (i) { + case 0: /* 5 GHz legacy */ + ctpl = &ar->eeprom.cal_tgt_pwr_5G[0]; + ntargets = AR5416_NUM_5G_TARGET_PWRS; + ctpres = ar->power_5G_leg; + break; + case 1: /* 2.4 GHz CCK */ + ctpl = &ar->eeprom.cal_tgt_pwr_2G_cck[0]; + ntargets = AR5416_NUM_2G_CCK_TARGET_PWRS; + ctpres = ar->power_2G_cck; + break; + case 2: /* 2.4 GHz OFDM */ + ctpl = &ar->eeprom.cal_tgt_pwr_2G_ofdm[0]; + ntargets = AR5416_NUM_2G_OFDM_TARGET_PWRS; + ctpres = ar->power_2G_ofdm; + break; + default: + BUG(); + } + + for (n = 0; n < ntargets; n++) { + if (ctpl[n].freq == 0xff) + break; + pwr_freqs[n] = ctpl[n].freq; + } + ntargets = n; + idx = carl9170_find_freq_idx(ntargets, pwr_freqs, f); + for (n = 0; n < 4; n++) + ctpres[n] = carl9170_interpolate_u8(f, + ctpl[idx + 0].freq, ctpl[idx + 0].power[n], + ctpl[idx + 1].freq, ctpl[idx + 1].power[n]); + } + + /* HT modes now: 5G HT20, 5G HT40, 2G CCK, 2G OFDM, 2G HT20, 2G HT40 */ + for (i = 0; i < 4; i++) { + switch (i) { + case 0: /* 5 GHz HT 20 */ + ctph = &ar->eeprom.cal_tgt_pwr_5G_ht20[0]; + ntargets = AR5416_NUM_5G_TARGET_PWRS; + ctpres = ar->power_5G_ht20; + break; + case 1: /* 5 GHz HT 40 */ + ctph = &ar->eeprom.cal_tgt_pwr_5G_ht40[0]; + ntargets = AR5416_NUM_5G_TARGET_PWRS; + ctpres = ar->power_5G_ht40; + break; + case 2: /* 2.4 GHz HT 20 */ + ctph = &ar->eeprom.cal_tgt_pwr_2G_ht20[0]; + ntargets = AR5416_NUM_2G_OFDM_TARGET_PWRS; + ctpres = ar->power_2G_ht20; + break; + case 3: /* 2.4 GHz HT 40 */ + ctph = &ar->eeprom.cal_tgt_pwr_2G_ht40[0]; + ntargets = AR5416_NUM_2G_OFDM_TARGET_PWRS; + ctpres = ar->power_2G_ht40; + break; + default: + BUG(); + } + + for (n = 0; n < ntargets; n++) { + if (ctph[n].freq == 0xff) + break; + pwr_freqs[n] = ctph[n].freq; + } + ntargets = n; + idx = carl9170_find_freq_idx(ntargets, pwr_freqs, f); + for (n = 0; n < 8; n++) + ctpres[n] = carl9170_interpolate_u8(f, + ctph[idx + 0].freq, ctph[idx + 0].power[n], + ctph[idx + 1].freq, ctph[idx + 1].power[n]); + } + + /* calc. conformance test limits and apply to ar->power*[] */ + carl9170_calc_ctl(ar, freq, bw); +} + +int carl9170_get_noisefloor(struct ar9170 *ar) +{ + static const u32 phy_regs[] = { + AR9170_PHY_REG_CCA, AR9170_PHY_REG_CH2_CCA, + AR9170_PHY_REG_EXT_CCA, AR9170_PHY_REG_CH2_EXT_CCA }; + u32 phy_res[ARRAY_SIZE(phy_regs)]; + int err, i; + + BUILD_BUG_ON(ARRAY_SIZE(phy_regs) != ARRAY_SIZE(ar->noise)); + + err = carl9170_read_mreg(ar, ARRAY_SIZE(phy_regs), phy_regs, phy_res); + if (err) + return err; + + for (i = 0; i < 2; i++) { + ar->noise[i] = sign_extend32(GET_VAL( + AR9170_PHY_CCA_MIN_PWR, phy_res[i]), 8); + + ar->noise[i + 2] = sign_extend32(GET_VAL( + AR9170_PHY_EXT_CCA_MIN_PWR, phy_res[i + 2]), 8); + } + + if (ar->channel) + ar->survey[ar->channel->hw_value].noise = ar->noise[0]; + + return 0; +} + +static enum carl9170_bw nl80211_to_carl(enum nl80211_channel_type type) +{ + switch (type) { + case NL80211_CHAN_NO_HT: + case NL80211_CHAN_HT20: + return CARL9170_BW_20; + case NL80211_CHAN_HT40MINUS: + return CARL9170_BW_40_BELOW; + case NL80211_CHAN_HT40PLUS: + return CARL9170_BW_40_ABOVE; + default: + BUG(); + } +} + +int carl9170_set_channel(struct ar9170 *ar, struct ieee80211_channel *channel, + enum nl80211_channel_type _bw) +{ + const struct carl9170_phy_freq_params *freqpar; + struct carl9170_rf_init_result rf_res; + struct carl9170_rf_init rf; + u32 tmp, offs = 0, new_ht = 0; + int err; + enum carl9170_bw bw; + struct ieee80211_channel *old_channel = NULL; + + bw = nl80211_to_carl(_bw); + + if (conf_is_ht(&ar->hw->conf)) + new_ht |= CARL9170FW_PHY_HT_ENABLE; + + if (conf_is_ht40(&ar->hw->conf)) + new_ht |= CARL9170FW_PHY_HT_DYN2040; + + /* may be NULL at first setup */ + if (ar->channel) { + old_channel = ar->channel; + ar->channel = NULL; + } + + /* cold reset BB/ADDA */ + err = carl9170_write_reg(ar, AR9170_PWR_REG_RESET, + AR9170_PWR_RESET_BB_COLD_RESET); + if (err) + return err; + + err = carl9170_write_reg(ar, AR9170_PWR_REG_RESET, 0x0); + if (err) + return err; + + err = carl9170_init_phy(ar, channel->band); + if (err) + return err; + + err = carl9170_init_rf_banks_0_7(ar, + channel->band == IEEE80211_BAND_5GHZ); + if (err) + return err; + + err = carl9170_exec_cmd(ar, CARL9170_CMD_FREQ_START, 0, NULL, 0, NULL); + if (err) + return err; + + err = carl9170_write_reg(ar, AR9170_PHY_REG_HEAVY_CLIP_ENABLE, + 0x200); + if (err) + return err; + + err = carl9170_init_rf_bank4_pwr(ar, + channel->band == IEEE80211_BAND_5GHZ, + channel->center_freq, bw); + if (err) + return err; + + tmp = AR9170_PHY_TURBO_FC_SINGLE_HT_LTF1 | + AR9170_PHY_TURBO_FC_HT_EN; + + switch (bw) { + case CARL9170_BW_20: + break; + case CARL9170_BW_40_BELOW: + tmp |= AR9170_PHY_TURBO_FC_DYN2040_EN | + AR9170_PHY_TURBO_FC_SHORT_GI_40; + offs = 3; + break; + case CARL9170_BW_40_ABOVE: + tmp |= AR9170_PHY_TURBO_FC_DYN2040_EN | + AR9170_PHY_TURBO_FC_SHORT_GI_40 | + AR9170_PHY_TURBO_FC_DYN2040_PRI_CH; + offs = 1; + break; + default: + BUG(); + return -ENOSYS; + } + + if (ar->eeprom.tx_mask != 1) + tmp |= AR9170_PHY_TURBO_FC_WALSH; + + err = carl9170_write_reg(ar, AR9170_PHY_REG_TURBO, tmp); + if (err) + return err; + + err = carl9170_set_freq_cal_data(ar, channel); + if (err) + return err; + + carl9170_set_power_cal(ar, channel->center_freq, bw); + + err = carl9170_set_mac_tpc(ar, channel); + if (err) + return err; + + freqpar = carl9170_get_hw_dyn_params(channel, bw); + + rf.ht_settings = new_ht; + if (conf_is_ht40(&ar->hw->conf)) + SET_VAL(CARL9170FW_PHY_HT_EXT_CHAN_OFF, rf.ht_settings, offs); + + rf.freq = cpu_to_le32(channel->center_freq * 1000); + rf.delta_slope_coeff_exp = cpu_to_le32(freqpar->coeff_exp); + rf.delta_slope_coeff_man = cpu_to_le32(freqpar->coeff_man); + rf.delta_slope_coeff_exp_shgi = cpu_to_le32(freqpar->coeff_exp_shgi); + rf.delta_slope_coeff_man_shgi = cpu_to_le32(freqpar->coeff_man_shgi); + rf.finiteLoopCount = cpu_to_le32(2000); + err = carl9170_exec_cmd(ar, CARL9170_CMD_RF_INIT, sizeof(rf), &rf, + sizeof(rf_res), &rf_res); + if (err) + return err; + + err = le32_to_cpu(rf_res.ret); + if (err != 0) { + ar->chan_fail++; + ar->total_chan_fail++; + + wiphy_err(ar->hw->wiphy, "channel change: %d -> %d " + "failed (%d).\n", old_channel ? + old_channel->center_freq : -1, channel->center_freq, + err); + + if (ar->chan_fail > 3) { + /* We have tried very hard to change to _another_ + * channel and we've failed to do so! + * Chances are that the PHY/RF is no longer + * operable (due to corruptions/fatal events/bugs?) + * and we need to reset at a higher level. + */ + carl9170_restart(ar, CARL9170_RR_TOO_MANY_PHY_ERRORS); + return 0; + } + + err = carl9170_set_channel(ar, channel, _bw); + if (err) + return err; + } else { + ar->chan_fail = 0; + } + + if (ar->heavy_clip) { + err = carl9170_write_reg(ar, AR9170_PHY_REG_HEAVY_CLIP_ENABLE, + 0x200 | ar->heavy_clip); + if (err) { + if (net_ratelimit()) { + wiphy_err(ar->hw->wiphy, "failed to set " + "heavy clip\n"); + } + + return err; + } + } + + ar->channel = channel; + ar->ht_settings = new_ht; + return 0; +} diff --git a/drivers/net/wireless/ath/carl9170/phy.h b/drivers/net/wireless/ath/carl9170/phy.h new file mode 100644 index 0000000..024fb42 --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/phy.h @@ -0,0 +1,564 @@ +/* + * Shared Atheros AR9170 Header + * + * PHY register map + * + * Copyright (c) 2008-2009 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __CARL9170_SHARED_PHY_H +#define __CARL9170_SHARED_PHY_H + +#define AR9170_PHY_REG_BASE (0x1bc000 + 0x9800) +#define AR9170_PHY_REG(_n) (AR9170_PHY_REG_BASE + \ + ((_n) << 2)) + +#define AR9170_PHY_REG_TEST (AR9170_PHY_REG_BASE + 0x0000) +#define AR9170_PHY_TEST_AGC_CLR 0x10000000 +#define AR9170_PHY_TEST_RFSILENT_BB 0x00002000 + +#define AR9170_PHY_REG_TURBO (AR9170_PHY_REG_BASE + 0x0004) +#define AR9170_PHY_TURBO_FC_TURBO_MODE 0x00000001 +#define AR9170_PHY_TURBO_FC_TURBO_SHORT 0x00000002 +#define AR9170_PHY_TURBO_FC_DYN2040_EN 0x00000004 +#define AR9170_PHY_TURBO_FC_DYN2040_PRI_ONLY 0x00000008 +#define AR9170_PHY_TURBO_FC_DYN2040_PRI_CH 0x00000010 +/* For 25 MHz channel spacing -- not used but supported by hw */ +#define AR9170_PHY_TURBO_FC_DYN2040_EXT_CH 0x00000020 +#define AR9170_PHY_TURBO_FC_HT_EN 0x00000040 +#define AR9170_PHY_TURBO_FC_SHORT_GI_40 0x00000080 +#define AR9170_PHY_TURBO_FC_WALSH 0x00000100 +#define AR9170_PHY_TURBO_FC_SINGLE_HT_LTF1 0x00000200 +#define AR9170_PHY_TURBO_FC_ENABLE_DAC_FIFO 0x00000800 + +#define AR9170_PHY_REG_TEST2 (AR9170_PHY_REG_BASE + 0x0008) + +#define AR9170_PHY_REG_TIMING2 (AR9170_PHY_REG_BASE + 0x0010) +#define AR9170_PHY_TIMING2_USE_FORCE 0x00001000 +#define AR9170_PHY_TIMING2_FORCE 0x00000fff +#define AR9170_PHY_TIMING2_FORCE_S 0 + +#define AR9170_PHY_REG_TIMING3 (AR9170_PHY_REG_BASE + 0x0014) +#define AR9170_PHY_TIMING3_DSC_EXP 0x0001e000 +#define AR9170_PHY_TIMING3_DSC_EXP_S 13 +#define AR9170_PHY_TIMING3_DSC_MAN 0xfffe0000 +#define AR9170_PHY_TIMING3_DSC_MAN_S 17 + +#define AR9170_PHY_REG_CHIP_ID (AR9170_PHY_REG_BASE + 0x0018) +#define AR9170_PHY_CHIP_ID_REV_0 0x80 +#define AR9170_PHY_CHIP_ID_REV_1 0x81 +#define AR9170_PHY_CHIP_ID_9160_REV_0 0xb0 + +#define AR9170_PHY_REG_ACTIVE (AR9170_PHY_REG_BASE + 0x001c) +#define AR9170_PHY_ACTIVE_EN 0x00000001 +#define AR9170_PHY_ACTIVE_DIS 0x00000000 + +#define AR9170_PHY_REG_RF_CTL2 (AR9170_PHY_REG_BASE + 0x0024) +#define AR9170_PHY_RF_CTL2_TX_END_DATA_START 0x000000ff +#define AR9170_PHY_RF_CTL2_TX_END_DATA_START_S 0 +#define AR9170_PHY_RF_CTL2_TX_END_PA_ON 0x0000ff00 +#define AR9170_PHY_RF_CTL2_TX_END_PA_ON_S 8 + +#define AR9170_PHY_REG_RF_CTL3 (AR9170_PHY_REG_BASE + 0x0028) +#define AR9170_PHY_RF_CTL3_TX_END_TO_A2_RX_ON 0x00ff0000 +#define AR9170_PHY_RF_CTL3_TX_END_TO_A2_RX_ON_S 16 + +#define AR9170_PHY_REG_ADC_CTL (AR9170_PHY_REG_BASE + 0x002c) +#define AR9170_PHY_ADC_CTL_OFF_INBUFGAIN 0x00000003 +#define AR9170_PHY_ADC_CTL_OFF_INBUFGAIN_S 0 +#define AR9170_PHY_ADC_CTL_OFF_PWDDAC 0x00002000 +#define AR9170_PHY_ADC_CTL_OFF_PWDBANDGAP 0x00004000 +#define AR9170_PHY_ADC_CTL_OFF_PWDADC 0x00008000 +#define AR9170_PHY_ADC_CTL_ON_INBUFGAIN 0x00030000 +#define AR9170_PHY_ADC_CTL_ON_INBUFGAIN_S 16 + +#define AR9170_PHY_REG_ADC_SERIAL_CTL (AR9170_PHY_REG_BASE + 0x0030) +#define AR9170_PHY_ADC_SCTL_SEL_INTERNAL_ADDAC 0x00000000 +#define AR9170_PHY_ADC_SCTL_SEL_EXTERNAL_RADIO 0x00000001 + +#define AR9170_PHY_REG_RF_CTL4 (AR9170_PHY_REG_BASE + 0x0034) +#define AR9170_PHY_RF_CTL4_TX_END_XPAB_OFF 0xff000000 +#define AR9170_PHY_RF_CTL4_TX_END_XPAB_OFF_S 24 +#define AR9170_PHY_RF_CTL4_TX_END_XPAA_OFF 0x00ff0000 +#define AR9170_PHY_RF_CTL4_TX_END_XPAA_OFF_S 16 +#define AR9170_PHY_RF_CTL4_FRAME_XPAB_ON 0x0000ff00 +#define AR9170_PHY_RF_CTL4_FRAME_XPAB_ON_S 8 +#define AR9170_PHY_RF_CTL4_FRAME_XPAA_ON 0x000000ff +#define AR9170_PHY_RF_CTL4_FRAME_XPAA_ON_S 0 + +#define AR9170_PHY_REG_TSTDAC_CONST (AR9170_PHY_REG_BASE + 0x003c) + +#define AR9170_PHY_REG_SETTLING (AR9170_PHY_REG_BASE + 0x0044) +#define AR9170_PHY_SETTLING_SWITCH 0x00003f80 +#define AR9170_PHY_SETTLING_SWITCH_S 7 + +#define AR9170_PHY_REG_RXGAIN (AR9170_PHY_REG_BASE + 0x0048) +#define AR9170_PHY_REG_RXGAIN_CHAIN_2 (AR9170_PHY_REG_BASE + 0x2048) +#define AR9170_PHY_RXGAIN_TXRX_ATTEN 0x0003f000 +#define AR9170_PHY_RXGAIN_TXRX_ATTEN_S 12 +#define AR9170_PHY_RXGAIN_TXRX_RF_MAX 0x007c0000 +#define AR9170_PHY_RXGAIN_TXRX_RF_MAX_S 18 + +#define AR9170_PHY_REG_DESIRED_SZ (AR9170_PHY_REG_BASE + 0x0050) +#define AR9170_PHY_DESIRED_SZ_ADC 0x000000ff +#define AR9170_PHY_DESIRED_SZ_ADC_S 0 +#define AR9170_PHY_DESIRED_SZ_PGA 0x0000ff00 +#define AR9170_PHY_DESIRED_SZ_PGA_S 8 +#define AR9170_PHY_DESIRED_SZ_TOT_DES 0x0ff00000 +#define AR9170_PHY_DESIRED_SZ_TOT_DES_S 20 + +#define AR9170_PHY_REG_FIND_SIG (AR9170_PHY_REG_BASE + 0x0058) +#define AR9170_PHY_FIND_SIG_FIRSTEP 0x0003f000 +#define AR9170_PHY_FIND_SIG_FIRSTEP_S 12 +#define AR9170_PHY_FIND_SIG_FIRPWR 0x03fc0000 +#define AR9170_PHY_FIND_SIG_FIRPWR_S 18 + +#define AR9170_PHY_REG_AGC_CTL1 (AR9170_PHY_REG_BASE + 0x005c) +#define AR9170_PHY_AGC_CTL1_COARSE_LOW 0x00007f80 +#define AR9170_PHY_AGC_CTL1_COARSE_LOW_S 7 +#define AR9170_PHY_AGC_CTL1_COARSE_HIGH 0x003f8000 +#define AR9170_PHY_AGC_CTL1_COARSE_HIGH_S 15 + +#define AR9170_PHY_REG_AGC_CONTROL (AR9170_PHY_REG_BASE + 0x0060) +#define AR9170_PHY_AGC_CONTROL_CAL 0x00000001 +#define AR9170_PHY_AGC_CONTROL_NF 0x00000002 +#define AR9170_PHY_AGC_CONTROL_ENABLE_NF 0x00008000 +#define AR9170_PHY_AGC_CONTROL_FLTR_CAL 0x00010000 +#define AR9170_PHY_AGC_CONTROL_NO_UPDATE_NF 0x00020000 + +#define AR9170_PHY_REG_CCA (AR9170_PHY_REG_BASE + 0x0064) +#define AR9170_PHY_CCA_MIN_PWR 0x0ff80000 +#define AR9170_PHY_CCA_MIN_PWR_S 19 +#define AR9170_PHY_CCA_THRESH62 0x0007f000 +#define AR9170_PHY_CCA_THRESH62_S 12 + +#define AR9170_PHY_REG_SFCORR (AR9170_PHY_REG_BASE + 0x0068) +#define AR9170_PHY_SFCORR_M2COUNT_THR 0x0000001f +#define AR9170_PHY_SFCORR_M2COUNT_THR_S 0 +#define AR9170_PHY_SFCORR_M1_THRESH 0x00fe0000 +#define AR9170_PHY_SFCORR_M1_THRESH_S 17 +#define AR9170_PHY_SFCORR_M2_THRESH 0x7f000000 +#define AR9170_PHY_SFCORR_M2_THRESH_S 24 + +#define AR9170_PHY_REG_SFCORR_LOW (AR9170_PHY_REG_BASE + 0x006c) +#define AR9170_PHY_SFCORR_LOW_USE_SELF_CORR_LOW 0x00000001 +#define AR9170_PHY_SFCORR_LOW_M2COUNT_THR_LOW 0x00003f00 +#define AR9170_PHY_SFCORR_LOW_M2COUNT_THR_LOW_S 8 +#define AR9170_PHY_SFCORR_LOW_M1_THRESH_LOW 0x001fc000 +#define AR9170_PHY_SFCORR_LOW_M1_THRESH_LOW_S 14 +#define AR9170_PHY_SFCORR_LOW_M2_THRESH_LOW 0x0fe00000 +#define AR9170_PHY_SFCORR_LOW_M2_THRESH_LOW_S 21 + +#define AR9170_PHY_REG_SLEEP_CTR_CONTROL (AR9170_PHY_REG_BASE + 0x0070) +#define AR9170_PHY_REG_SLEEP_CTR_LIMIT (AR9170_PHY_REG_BASE + 0x0074) +#define AR9170_PHY_REG_SLEEP_SCAL (AR9170_PHY_REG_BASE + 0x0078) + +#define AR9170_PHY_REG_PLL_CTL (AR9170_PHY_REG_BASE + 0x007c) +#define AR9170_PHY_PLL_CTL_40 0xaa +#define AR9170_PHY_PLL_CTL_40_5413 0x04 +#define AR9170_PHY_PLL_CTL_44 0xab +#define AR9170_PHY_PLL_CTL_44_2133 0xeb +#define AR9170_PHY_PLL_CTL_40_2133 0xea + +#define AR9170_PHY_REG_BIN_MASK_1 (AR9170_PHY_REG_BASE + 0x0100) +#define AR9170_PHY_REG_BIN_MASK_2 (AR9170_PHY_REG_BASE + 0x0104) +#define AR9170_PHY_REG_BIN_MASK_3 (AR9170_PHY_REG_BASE + 0x0108) +#define AR9170_PHY_REG_MASK_CTL (AR9170_PHY_REG_BASE + 0x010c) + +/* analogue power on time (100ns) */ +#define AR9170_PHY_REG_RX_DELAY (AR9170_PHY_REG_BASE + 0x0114) +#define AR9170_PHY_REG_SEARCH_START_DELAY (AR9170_PHY_REG_BASE + 0x0118) +#define AR9170_PHY_RX_DELAY_DELAY 0x00003fff + +#define AR9170_PHY_REG_TIMING_CTRL4(_i) (AR9170_PHY_REG_BASE + \ + (0x0120 + ((_i) << 12))) +#define AR9170_PHY_TIMING_CTRL4_IQCORR_Q_Q_COFF 0x01f +#define AR9170_PHY_TIMING_CTRL4_IQCORR_Q_Q_COFF_S 0 +#define AR9170_PHY_TIMING_CTRL4_IQCORR_Q_I_COFF 0x7e0 +#define AR9170_PHY_TIMING_CTRL4_IQCORR_Q_I_COFF_S 5 +#define AR9170_PHY_TIMING_CTRL4_IQCORR_ENABLE 0x800 +#define AR9170_PHY_TIMING_CTRL4_IQCAL_LOG_COUNT_MAX 0xf000 +#define AR9170_PHY_TIMING_CTRL4_IQCAL_LOG_COUNT_MAX_S 12 +#define AR9170_PHY_TIMING_CTRL4_DO_IQCAL 0x10000 +#define AR9170_PHY_TIMING_CTRL4_ENABLE_SPUR_RSSI 0x80000000 +#define AR9170_PHY_TIMING_CTRL4_ENABLE_SPUR_FILTER 0x40000000 +#define AR9170_PHY_TIMING_CTRL4_ENABLE_CHAN_MASK 0x20000000 +#define AR9170_PHY_TIMING_CTRL4_ENABLE_PILOT_MASK 0x10000000 + +#define AR9170_PHY_REG_TIMING5 (AR9170_PHY_REG_BASE + 0x0124) +#define AR9170_PHY_TIMING5_CYCPWR_THR1 0x000000fe +#define AR9170_PHY_TIMING5_CYCPWR_THR1_S 1 + +#define AR9170_PHY_REG_POWER_TX_RATE1 (AR9170_PHY_REG_BASE + 0x0134) +#define AR9170_PHY_REG_POWER_TX_RATE2 (AR9170_PHY_REG_BASE + 0x0138) +#define AR9170_PHY_REG_POWER_TX_RATE_MAX (AR9170_PHY_REG_BASE + 0x013c) +#define AR9170_PHY_POWER_TX_RATE_MAX_TPC_ENABLE 0x00000040 + +#define AR9170_PHY_REG_FRAME_CTL (AR9170_PHY_REG_BASE + 0x0144) +#define AR9170_PHY_FRAME_CTL_TX_CLIP 0x00000038 +#define AR9170_PHY_FRAME_CTL_TX_CLIP_S 3 + +#define AR9170_PHY_REG_SPUR_REG (AR9170_PHY_REG_BASE + 0x014c) +#define AR9170_PHY_SPUR_REG_MASK_RATE_CNTL (0xff << 18) +#define AR9170_PHY_SPUR_REG_MASK_RATE_CNTL_S 18 +#define AR9170_PHY_SPUR_REG_ENABLE_MASK_PPM 0x20000 +#define AR9170_PHY_SPUR_REG_MASK_RATE_SELECT (0xff << 9) +#define AR9170_PHY_SPUR_REG_MASK_RATE_SELECT_S 9 +#define AR9170_PHY_SPUR_REG_ENABLE_VIT_SPUR_RSSI 0x100 +#define AR9170_PHY_SPUR_REG_SPUR_RSSI_THRESH 0x7f +#define AR9170_PHY_SPUR_REG_SPUR_RSSI_THRESH_S 0 + +#define AR9170_PHY_REG_RADAR_EXT (AR9170_PHY_REG_BASE + 0x0140) +#define AR9170_PHY_RADAR_EXT_ENA 0x00004000 + +#define AR9170_PHY_REG_RADAR_0 (AR9170_PHY_REG_BASE + 0x0154) +#define AR9170_PHY_RADAR_0_ENA 0x00000001 +#define AR9170_PHY_RADAR_0_FFT_ENA 0x80000000 +/* inband pulse threshold */ +#define AR9170_PHY_RADAR_0_INBAND 0x0000003e +#define AR9170_PHY_RADAR_0_INBAND_S 1 +/* pulse RSSI threshold */ +#define AR9170_PHY_RADAR_0_PRSSI 0x00000fc0 +#define AR9170_PHY_RADAR_0_PRSSI_S 6 +/* pulse height threshold */ +#define AR9170_PHY_RADAR_0_HEIGHT 0x0003f000 +#define AR9170_PHY_RADAR_0_HEIGHT_S 12 +/* radar RSSI threshold */ +#define AR9170_PHY_RADAR_0_RRSSI 0x00fc0000 +#define AR9170_PHY_RADAR_0_RRSSI_S 18 +/* radar firepower threshold */ +#define AR9170_PHY_RADAR_0_FIRPWR 0x7f000000 +#define AR9170_PHY_RADAR_0_FIRPWR_S 24 + +#define AR9170_PHY_REG_RADAR_1 (AR9170_PHY_REG_BASE + 0x0158) +#define AR9170_PHY_RADAR_1_RELPWR_ENA 0x00800000 +#define AR9170_PHY_RADAR_1_USE_FIR128 0x00400000 +#define AR9170_PHY_RADAR_1_RELPWR_THRESH 0x003f0000 +#define AR9170_PHY_RADAR_1_RELPWR_THRESH_S 16 +#define AR9170_PHY_RADAR_1_BLOCK_CHECK 0x00008000 +#define AR9170_PHY_RADAR_1_MAX_RRSSI 0x00004000 +#define AR9170_PHY_RADAR_1_RELSTEP_CHECK 0x00002000 +#define AR9170_PHY_RADAR_1_RELSTEP_THRESH 0x00001f00 +#define AR9170_PHY_RADAR_1_RELSTEP_THRESH_S 8 +#define AR9170_PHY_RADAR_1_MAXLEN 0x000000ff +#define AR9170_PHY_RADAR_1_MAXLEN_S 0 + +#define AR9170_PHY_REG_SWITCH_CHAIN_0 (AR9170_PHY_REG_BASE + 0x0160) +#define AR9170_PHY_REG_SWITCH_CHAIN_2 (AR9170_PHY_REG_BASE + 0x2160) + +#define AR9170_PHY_REG_SWITCH_COM (AR9170_PHY_REG_BASE + 0x0164) + +#define AR9170_PHY_REG_CCA_THRESHOLD (AR9170_PHY_REG_BASE + 0x0168) + +#define AR9170_PHY_REG_SIGMA_DELTA (AR9170_PHY_REG_BASE + 0x016c) +#define AR9170_PHY_SIGMA_DELTA_ADC_SEL 0x00000003 +#define AR9170_PHY_SIGMA_DELTA_ADC_SEL_S 0 +#define AR9170_PHY_SIGMA_DELTA_FILT2 0x000000f8 +#define AR9170_PHY_SIGMA_DELTA_FILT2_S 3 +#define AR9170_PHY_SIGMA_DELTA_FILT1 0x00001f00 +#define AR9170_PHY_SIGMA_DELTA_FILT1_S 8 +#define AR9170_PHY_SIGMA_DELTA_ADC_CLIP 0x01ffe000 +#define AR9170_PHY_SIGMA_DELTA_ADC_CLIP_S 13 + +#define AR9170_PHY_REG_RESTART (AR9170_PHY_REG_BASE + 0x0170) +#define AR9170_PHY_RESTART_DIV_GC 0x001c0000 +#define AR9170_PHY_RESTART_DIV_GC_S 18 + +#define AR9170_PHY_REG_RFBUS_REQ (AR9170_PHY_REG_BASE + 0x017c) +#define AR9170_PHY_RFBUS_REQ_EN 0x00000001 + +#define AR9170_PHY_REG_TIMING7 (AR9170_PHY_REG_BASE + 0x0180) +#define AR9170_PHY_REG_TIMING8 (AR9170_PHY_REG_BASE + 0x0184) +#define AR9170_PHY_TIMING8_PILOT_MASK_2 0x000fffff +#define AR9170_PHY_TIMING8_PILOT_MASK_2_S 0 + +#define AR9170_PHY_REG_BIN_MASK2_1 (AR9170_PHY_REG_BASE + 0x0188) +#define AR9170_PHY_REG_BIN_MASK2_2 (AR9170_PHY_REG_BASE + 0x018c) +#define AR9170_PHY_REG_BIN_MASK2_3 (AR9170_PHY_REG_BASE + 0x0190) +#define AR9170_PHY_REG_BIN_MASK2_4 (AR9170_PHY_REG_BASE + 0x0194) +#define AR9170_PHY_BIN_MASK2_4_MASK_4 0x00003fff +#define AR9170_PHY_BIN_MASK2_4_MASK_4_S 0 + +#define AR9170_PHY_REG_TIMING9 (AR9170_PHY_REG_BASE + 0x0198) +#define AR9170_PHY_REG_TIMING10 (AR9170_PHY_REG_BASE + 0x019c) +#define AR9170_PHY_TIMING10_PILOT_MASK_2 0x000fffff +#define AR9170_PHY_TIMING10_PILOT_MASK_2_S 0 + +#define AR9170_PHY_REG_TIMING11 (AR9170_PHY_REG_BASE + 0x01a0) +#define AR9170_PHY_TIMING11_SPUR_DELTA_PHASE 0x000fffff +#define AR9170_PHY_TIMING11_SPUR_DELTA_PHASE_S 0 +#define AR9170_PHY_TIMING11_SPUR_FREQ_SD 0x3ff00000 +#define AR9170_PHY_TIMING11_SPUR_FREQ_SD_S 20 +#define AR9170_PHY_TIMING11_USE_SPUR_IN_AGC 0x40000000 +#define AR9170_PHY_TIMING11_USE_SPUR_IN_SELFCOR 0x80000000 + +#define AR9170_PHY_REG_RX_CHAINMASK (AR9170_PHY_REG_BASE + 0x01a4) +#define AR9170_PHY_REG_NEW_ADC_DC_GAIN_CORR(_i) (AR9170_PHY_REG_BASE + \ + 0x01b4 + ((_i) << 12)) +#define AR9170_PHY_NEW_ADC_GAIN_CORR_ENABLE 0x40000000 +#define AR9170_PHY_NEW_ADC_DC_OFFSET_CORR_ENABLE 0x80000000 + +#define AR9170_PHY_REG_MULTICHAIN_GAIN_CTL (AR9170_PHY_REG_BASE + 0x01ac) +#define AR9170_PHY_9285_ANT_DIV_CTL_ALL 0x7f000000 +#define AR9170_PHY_9285_ANT_DIV_CTL 0x01000000 +#define AR9170_PHY_9285_ANT_DIV_CTL_S 24 +#define AR9170_PHY_9285_ANT_DIV_ALT_LNACONF 0x06000000 +#define AR9170_PHY_9285_ANT_DIV_ALT_LNACONF_S 25 +#define AR9170_PHY_9285_ANT_DIV_MAIN_LNACONF 0x18000000 +#define AR9170_PHY_9285_ANT_DIV_MAIN_LNACONF_S 27 +#define AR9170_PHY_9285_ANT_DIV_ALT_GAINTB 0x20000000 +#define AR9170_PHY_9285_ANT_DIV_ALT_GAINTB_S 29 +#define AR9170_PHY_9285_ANT_DIV_MAIN_GAINTB 0x40000000 +#define AR9170_PHY_9285_ANT_DIV_MAIN_GAINTB_S 30 +#define AR9170_PHY_9285_ANT_DIV_LNA1 2 +#define AR9170_PHY_9285_ANT_DIV_LNA2 1 +#define AR9170_PHY_9285_ANT_DIV_LNA1_PLUS_LNA2 3 +#define AR9170_PHY_9285_ANT_DIV_LNA1_MINUS_LNA2 0 +#define AR9170_PHY_9285_ANT_DIV_GAINTB_0 0 +#define AR9170_PHY_9285_ANT_DIV_GAINTB_1 1 + +#define AR9170_PHY_REG_EXT_CCA0 (AR9170_PHY_REG_BASE + 0x01b8) +#define AR9170_PHY_REG_EXT_CCA0_THRESH62 0x000000ff +#define AR9170_PHY_REG_EXT_CCA0_THRESH62_S 0 + +#define AR9170_PHY_REG_EXT_CCA (AR9170_PHY_REG_BASE + 0x01bc) +#define AR9170_PHY_EXT_CCA_CYCPWR_THR1 0x0000fe00 +#define AR9170_PHY_EXT_CCA_CYCPWR_THR1_S 9 +#define AR9170_PHY_EXT_CCA_THRESH62 0x007f0000 +#define AR9170_PHY_EXT_CCA_THRESH62_S 16 +#define AR9170_PHY_EXT_CCA_MIN_PWR 0xff800000 +#define AR9170_PHY_EXT_CCA_MIN_PWR_S 23 + +#define AR9170_PHY_REG_SFCORR_EXT (AR9170_PHY_REG_BASE + 0x01c0) +#define AR9170_PHY_SFCORR_EXT_M1_THRESH 0x0000007f +#define AR9170_PHY_SFCORR_EXT_M1_THRESH_S 0 +#define AR9170_PHY_SFCORR_EXT_M2_THRESH 0x00003f80 +#define AR9170_PHY_SFCORR_EXT_M2_THRESH_S 7 +#define AR9170_PHY_SFCORR_EXT_M1_THRESH_LOW 0x001fc000 +#define AR9170_PHY_SFCORR_EXT_M1_THRESH_LOW_S 14 +#define AR9170_PHY_SFCORR_EXT_M2_THRESH_LOW 0x0fe00000 +#define AR9170_PHY_SFCORR_EXT_M2_THRESH_LOW_S 21 +#define AR9170_PHY_SFCORR_SPUR_SUBCHNL_SD_S 28 + +#define AR9170_PHY_REG_HALFGI (AR9170_PHY_REG_BASE + 0x01d0) +#define AR9170_PHY_HALFGI_DSC_MAN 0x0007fff0 +#define AR9170_PHY_HALFGI_DSC_MAN_S 4 +#define AR9170_PHY_HALFGI_DSC_EXP 0x0000000f +#define AR9170_PHY_HALFGI_DSC_EXP_S 0 + +#define AR9170_PHY_REG_CHANNEL_MASK_01_30 (AR9170_PHY_REG_BASE + 0x01d4) +#define AR9170_PHY_REG_CHANNEL_MASK_31_60 (AR9170_PHY_REG_BASE + 0x01d8) + +#define AR9170_PHY_REG_CHAN_INFO_MEMORY (AR9170_PHY_REG_BASE + 0x01dc) +#define AR9170_PHY_CHAN_INFO_MEMORY_CAPTURE_MASK 0x0001 + +#define AR9170_PHY_REG_HEAVY_CLIP_ENABLE (AR9170_PHY_REG_BASE + 0x01e0) +#define AR9170_PHY_REG_HEAVY_CLIP_FACTOR_RIFS (AR9170_PHY_REG_BASE + 0x01ec) +#define AR9170_PHY_RIFS_INIT_DELAY 0x03ff0000 + +#define AR9170_PHY_REG_CALMODE (AR9170_PHY_REG_BASE + 0x01f0) +#define AR9170_PHY_CALMODE_IQ 0x00000000 +#define AR9170_PHY_CALMODE_ADC_GAIN 0x00000001 +#define AR9170_PHY_CALMODE_ADC_DC_PER 0x00000002 +#define AR9170_PHY_CALMODE_ADC_DC_INIT 0x00000003 + +#define AR9170_PHY_REG_REFCLKDLY (AR9170_PHY_REG_BASE + 0x01f4) +#define AR9170_PHY_REG_REFCLKPD (AR9170_PHY_REG_BASE + 0x01f8) + + +#define AR9170_PHY_REG_CAL_MEAS_0(_i) (AR9170_PHY_REG_BASE + \ + 0x0410 + ((_i) << 12)) +#define AR9170_PHY_REG_CAL_MEAS_1(_i) (AR9170_PHY_REG_BASE + \ + 0x0414 \ + ((_i) << 12)) +#define AR9170_PHY_REG_CAL_MEAS_2(_i) (AR9170_PHY_REG_BASE + \ + 0x0418 + ((_i) << 12)) +#define AR9170_PHY_REG_CAL_MEAS_3(_i) (AR9170_PHY_REG_BASE + \ + 0x041c + ((_i) << 12)) + +#define AR9170_PHY_REG_CURRENT_RSSI (AR9170_PHY_REG_BASE + 0x041c) + +#define AR9170_PHY_REG_RFBUS_GRANT (AR9170_PHY_REG_BASE + 0x0420) +#define AR9170_PHY_RFBUS_GRANT_EN 0x00000001 + +#define AR9170_PHY_REG_CHAN_INFO_GAIN_DIFF (AR9170_PHY_REG_BASE + 0x04f4) +#define AR9170_PHY_CHAN_INFO_GAIN_DIFF_UPPER_LIMIT 320 + +#define AR9170_PHY_REG_CHAN_INFO_GAIN (AR9170_PHY_REG_BASE + 0x04fc) + +#define AR9170_PHY_REG_MODE (AR9170_PHY_REG_BASE + 0x0a00) +#define AR9170_PHY_MODE_ASYNCFIFO 0x80 +#define AR9170_PHY_MODE_AR2133 0x08 +#define AR9170_PHY_MODE_AR5111 0x00 +#define AR9170_PHY_MODE_AR5112 0x08 +#define AR9170_PHY_MODE_DYNAMIC 0x04 +#define AR9170_PHY_MODE_RF2GHZ 0x02 +#define AR9170_PHY_MODE_RF5GHZ 0x00 +#define AR9170_PHY_MODE_CCK 0x01 +#define AR9170_PHY_MODE_OFDM 0x00 +#define AR9170_PHY_MODE_DYN_CCK_DISABLE 0x100 + +#define AR9170_PHY_REG_CCK_TX_CTRL (AR9170_PHY_REG_BASE + 0x0a04) +#define AR9170_PHY_CCK_TX_CTRL_JAPAN 0x00000010 +#define AR9170_PHY_CCK_TX_CTRL_TX_DAC_SCALE_CCK 0x0000000c +#define AR9170_PHY_CCK_TX_CTRL_TX_DAC_SCALE_CCK_S 2 + +#define AR9170_PHY_REG_CCK_DETECT (AR9170_PHY_REG_BASE + 0x0a08) +#define AR9170_PHY_CCK_DETECT_WEAK_SIG_THR_CCK 0x0000003f +#define AR9170_PHY_CCK_DETECT_WEAK_SIG_THR_CCK_S 0 +/* [12:6] settling time for antenna switch */ +#define AR9170_PHY_CCK_DETECT_ANT_SWITCH_TIME 0x00001fc0 +#define AR9170_PHY_CCK_DETECT_ANT_SWITCH_TIME_S 6 +#define AR9170_PHY_CCK_DETECT_BB_ENABLE_ANT_FAST_DIV 0x2000 +#define AR9170_PHY_CCK_DETECT_BB_ENABLE_ANT_FAST_DIV_S 13 + +#define AR9170_PHY_REG_GAIN_2GHZ (AR9170_PHY_REG_BASE + 0x0a0c) +#define AR9170_PHY_REG_GAIN_2GHZ_CHAIN_2 (AR9170_PHY_REG_BASE + 0x2a0c) +#define AR9170_PHY_GAIN_2GHZ_RXTX_MARGIN 0x00fc0000 +#define AR9170_PHY_GAIN_2GHZ_RXTX_MARGIN_S 18 +#define AR9170_PHY_GAIN_2GHZ_BSW_MARGIN 0x00003c00 +#define AR9170_PHY_GAIN_2GHZ_BSW_MARGIN_S 10 +#define AR9170_PHY_GAIN_2GHZ_BSW_ATTEN 0x0000001f +#define AR9170_PHY_GAIN_2GHZ_BSW_ATTEN_S 0 +#define AR9170_PHY_GAIN_2GHZ_XATTEN2_MARGIN 0x003e0000 +#define AR9170_PHY_GAIN_2GHZ_XATTEN2_MARGIN_S 17 +#define AR9170_PHY_GAIN_2GHZ_XATTEN1_MARGIN 0x0001f000 +#define AR9170_PHY_GAIN_2GHZ_XATTEN1_MARGIN_S 12 +#define AR9170_PHY_GAIN_2GHZ_XATTEN2_DB 0x00000fc0 +#define AR9170_PHY_GAIN_2GHZ_XATTEN2_DB_S 6 +#define AR9170_PHY_GAIN_2GHZ_XATTEN1_DB 0x0000003f +#define AR9170_PHY_GAIN_2GHZ_XATTEN1_DB_S 0 + +#define AR9170_PHY_REG_CCK_RXCTRL4 (AR9170_PHY_REG_BASE + 0x0a1c) +#define AR9170_PHY_CCK_RXCTRL4_FREQ_EST_SHORT 0x01f80000 +#define AR9170_PHY_CCK_RXCTRL4_FREQ_EST_SHORT_S 19 + +#define AR9170_PHY_REG_DAG_CTRLCCK (AR9170_PHY_REG_BASE + 0x0a28) +#define AR9170_REG_DAG_CTRLCCK_EN_RSSI_THR 0x00000200 +#define AR9170_REG_DAG_CTRLCCK_RSSI_THR 0x0001fc00 +#define AR9170_REG_DAG_CTRLCCK_RSSI_THR_S 10 + +#define AR9170_PHY_REG_FORCE_CLKEN_CCK (AR9170_PHY_REG_BASE + 0x0a2c) +#define AR9170_FORCE_CLKEN_CCK_MRC_MUX 0x00000040 + +#define AR9170_PHY_REG_POWER_TX_RATE3 (AR9170_PHY_REG_BASE + 0x0a34) +#define AR9170_PHY_REG_POWER_TX_RATE4 (AR9170_PHY_REG_BASE + 0x0a38) + +#define AR9170_PHY_REG_SCRM_SEQ_XR (AR9170_PHY_REG_BASE + 0x0a3c) +#define AR9170_PHY_REG_HEADER_DETECT_XR (AR9170_PHY_REG_BASE + 0x0a40) +#define AR9170_PHY_REG_CHIRP_DETECTED_XR (AR9170_PHY_REG_BASE + 0x0a44) +#define AR9170_PHY_REG_BLUETOOTH (AR9170_PHY_REG_BASE + 0x0a54) + +#define AR9170_PHY_REG_TPCRG1 (AR9170_PHY_REG_BASE + 0x0a58) +#define AR9170_PHY_TPCRG1_NUM_PD_GAIN 0x0000c000 +#define AR9170_PHY_TPCRG1_NUM_PD_GAIN_S 14 +#define AR9170_PHY_TPCRG1_PD_GAIN_1 0x00030000 +#define AR9170_PHY_TPCRG1_PD_GAIN_1_S 16 +#define AR9170_PHY_TPCRG1_PD_GAIN_2 0x000c0000 +#define AR9170_PHY_TPCRG1_PD_GAIN_2_S 18 +#define AR9170_PHY_TPCRG1_PD_GAIN_3 0x00300000 +#define AR9170_PHY_TPCRG1_PD_GAIN_3_S 20 +#define AR9170_PHY_TPCRG1_PD_CAL_ENABLE 0x00400000 +#define AR9170_PHY_TPCRG1_PD_CAL_ENABLE_S 22 + +#define AR9170_PHY_REG_TX_PWRCTRL4 (AR9170_PHY_REG_BASE + 0x0a64) +#define AR9170_PHY_TX_PWRCTRL_PD_AVG_VALID 0x00000001 +#define AR9170_PHY_TX_PWRCTRL_PD_AVG_VALID_S 0 +#define AR9170_PHY_TX_PWRCTRL_PD_AVG_OUT 0x000001fe +#define AR9170_PHY_TX_PWRCTRL_PD_AVG_OUT_S 1 + +#define AR9170_PHY_REG_ANALOG_SWAP (AR9170_PHY_REG_BASE + 0x0a68) +#define AR9170_PHY_ANALOG_SWAP_AB 0x0001 +#define AR9170_PHY_ANALOG_SWAP_ALT_CHAIN 0x00000040 + +#define AR9170_PHY_REG_TPCRG5 (AR9170_PHY_REG_BASE + 0x0a6c) +#define AR9170_PHY_TPCRG5_PD_GAIN_OVERLAP 0x0000000f +#define AR9170_PHY_TPCRG5_PD_GAIN_OVERLAP_S 0 +#define AR9170_PHY_TPCRG5_PD_GAIN_BOUNDARY_1 0x000003f0 +#define AR9170_PHY_TPCRG5_PD_GAIN_BOUNDARY_1_S 4 +#define AR9170_PHY_TPCRG5_PD_GAIN_BOUNDARY_2 0x0000fc00 +#define AR9170_PHY_TPCRG5_PD_GAIN_BOUNDARY_2_S 10 +#define AR9170_PHY_TPCRG5_PD_GAIN_BOUNDARY_3 0x003f0000 +#define AR9170_PHY_TPCRG5_PD_GAIN_BOUNDARY_3_S 16 +#define AR9170_PHY_TPCRG5_PD_GAIN_BOUNDARY_4 0x0fc00000 +#define AR9170_PHY_TPCRG5_PD_GAIN_BOUNDARY_4_S 22 + +#define AR9170_PHY_REG_TX_PWRCTRL6_0 (AR9170_PHY_REG_BASE + 0x0a70) +#define AR9170_PHY_REG_TX_PWRCTRL6_1 (AR9170_PHY_REG_BASE + 0x1a70) +#define AR9170_PHY_TX_PWRCTRL_ERR_EST_MODE 0x03000000 +#define AR9170_PHY_TX_PWRCTRL_ERR_EST_MODE_S 24 + +#define AR9170_PHY_REG_TX_PWRCTRL7 (AR9170_PHY_REG_BASE + 0x0a74) +#define AR9170_PHY_TX_PWRCTRL_INIT_TX_GAIN 0x01f80000 +#define AR9170_PHY_TX_PWRCTRL_INIT_TX_GAIN_S 19 + +#define AR9170_PHY_REG_TX_PWRCTRL9 (AR9170_PHY_REG_BASE + 0x0a7c) +#define AR9170_PHY_TX_DESIRED_SCALE_CCK 0x00007c00 +#define AR9170_PHY_TX_DESIRED_SCALE_CCK_S 10 +#define AR9170_PHY_TX_PWRCTRL9_RES_DC_REMOVAL 0x80000000 +#define AR9170_PHY_TX_PWRCTRL9_RES_DC_REMOVAL_S 31 + +#define AR9170_PHY_REG_TX_GAIN_TBL1 (AR9170_PHY_REG_BASE + 0x0b00) +#define AR9170_PHY_TX_GAIN 0x0007f000 +#define AR9170_PHY_TX_GAIN_S 12 + +/* Carrier leak calibration control, do it after AGC calibration */ +#define AR9170_PHY_REG_CL_CAL_CTL (AR9170_PHY_REG_BASE + 0x0b58) +#define AR9170_PHY_CL_CAL_ENABLE 0x00000002 +#define AR9170_PHY_CL_CAL_PARALLEL_CAL_ENABLE 0x00000001 + +#define AR9170_PHY_REG_POWER_TX_RATE5 (AR9170_PHY_REG_BASE + 0x0b8c) +#define AR9170_PHY_REG_POWER_TX_RATE6 (AR9170_PHY_REG_BASE + 0x0b90) + +#define AR9170_PHY_REG_CH0_TX_PWRCTRL11 (AR9170_PHY_REG_BASE + 0x0b98) +#define AR9170_PHY_REG_CH1_TX_PWRCTRL11 (AR9170_PHY_REG_BASE + 0x1b98) +#define AR9170_PHY_TX_CHX_PWRCTRL_OLPC_TEMP_COMP 0x0000fc00 +#define AR9170_PHY_TX_CHX_PWRCTRL_OLPC_TEMP_COMP_S 10 + +#define AR9170_PHY_REG_CAL_CHAINMASK (AR9170_PHY_REG_BASE + 0x0b9c) +#define AR9170_PHY_REG_VIT_MASK2_M_46_61 (AR9170_PHY_REG_BASE + 0x0ba0) +#define AR9170_PHY_REG_MASK2_M_31_45 (AR9170_PHY_REG_BASE + 0x0ba4) +#define AR9170_PHY_REG_MASK2_M_16_30 (AR9170_PHY_REG_BASE + 0x0ba8) +#define AR9170_PHY_REG_MASK2_M_00_15 (AR9170_PHY_REG_BASE + 0x0bac) +#define AR9170_PHY_REG_PILOT_MASK_01_30 (AR9170_PHY_REG_BASE + 0x0bb0) +#define AR9170_PHY_REG_PILOT_MASK_31_60 (AR9170_PHY_REG_BASE + 0x0bb4) +#define AR9170_PHY_REG_MASK2_P_15_01 (AR9170_PHY_REG_BASE + 0x0bb8) +#define AR9170_PHY_REG_MASK2_P_30_16 (AR9170_PHY_REG_BASE + 0x0bbc) +#define AR9170_PHY_REG_MASK2_P_45_31 (AR9170_PHY_REG_BASE + 0x0bc0) +#define AR9170_PHY_REG_MASK2_P_61_45 (AR9170_PHY_REG_BASE + 0x0bc4) +#define AR9170_PHY_REG_POWER_TX_SUB (AR9170_PHY_REG_BASE + 0x0bc8) +#define AR9170_PHY_REG_POWER_TX_RATE7 (AR9170_PHY_REG_BASE + 0x0bcc) +#define AR9170_PHY_REG_POWER_TX_RATE8 (AR9170_PHY_REG_BASE + 0x0bd0) +#define AR9170_PHY_REG_POWER_TX_RATE9 (AR9170_PHY_REG_BASE + 0x0bd4) +#define AR9170_PHY_REG_XPA_CFG (AR9170_PHY_REG_BASE + 0x0bd8) +#define AR9170_PHY_FORCE_XPA_CFG 0x000000001 +#define AR9170_PHY_FORCE_XPA_CFG_S 0 + +#define AR9170_PHY_REG_CH1_CCA (AR9170_PHY_REG_BASE + 0x1064) +#define AR9170_PHY_CH1_CCA_MIN_PWR 0x0ff80000 +#define AR9170_PHY_CH1_CCA_MIN_PWR_S 19 + +#define AR9170_PHY_REG_CH2_CCA (AR9170_PHY_REG_BASE + 0x2064) +#define AR9170_PHY_CH2_CCA_MIN_PWR 0x0ff80000 +#define AR9170_PHY_CH2_CCA_MIN_PWR_S 19 + +#define AR9170_PHY_REG_CH1_EXT_CCA (AR9170_PHY_REG_BASE + 0x11bc) +#define AR9170_PHY_CH1_EXT_CCA_MIN_PWR 0xff800000 +#define AR9170_PHY_CH1_EXT_CCA_MIN_PWR_S 23 + +#define AR9170_PHY_REG_CH2_EXT_CCA (AR9170_PHY_REG_BASE + 0x21bc) +#define AR9170_PHY_CH2_EXT_CCA_MIN_PWR 0xff800000 +#define AR9170_PHY_CH2_EXT_CCA_MIN_PWR_S 23 + +#endif /* __CARL9170_SHARED_PHY_H */ diff --git a/drivers/net/wireless/ath/carl9170/rx.c b/drivers/net/wireless/ath/carl9170/rx.c new file mode 100644 index 0000000..191672b --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/rx.c @@ -0,0 +1,1016 @@ +/* + * Atheros CARL9170 driver + * + * 802.11 & command trap routines + * + * Copyright 2008, Johannes Berg + * Copyright 2009, 2010, Christian Lamparter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * Copyright (c) 2007-2008 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include "carl9170.h" +#include "hw.h" +#include "cmd.h" + +static void carl9170_dbg_message(struct ar9170 *ar, const char *buf, u32 len) +{ + bool restart = false; + enum carl9170_restart_reasons reason = CARL9170_RR_NO_REASON; + + if (len > 3) { + if (memcmp(buf, CARL9170_ERR_MAGIC, 3) == 0) { + ar->fw.err_counter++; + if (ar->fw.err_counter > 3) { + restart = true; + reason = CARL9170_RR_TOO_MANY_FIRMWARE_ERRORS; + } + } + + if (memcmp(buf, CARL9170_BUG_MAGIC, 3) == 0) { + ar->fw.bug_counter++; + restart = true; + reason = CARL9170_RR_FATAL_FIRMWARE_ERROR; + } + } + + wiphy_info(ar->hw->wiphy, "FW: %.*s\n", len, buf); + + if (restart) + carl9170_restart(ar, reason); +} + +static void carl9170_handle_ps(struct ar9170 *ar, struct carl9170_rsp *rsp) +{ + u32 ps; + bool new_ps; + + ps = le32_to_cpu(rsp->psm.state); + + new_ps = (ps & CARL9170_PSM_COUNTER) != CARL9170_PSM_WAKE; + if (ar->ps.state != new_ps) { + if (!new_ps) { + ar->ps.sleep_ms = jiffies_to_msecs(jiffies - + ar->ps.last_action); + } + + ar->ps.last_action = jiffies; + + ar->ps.state = new_ps; + } +} + +static int carl9170_check_sequence(struct ar9170 *ar, unsigned int seq) +{ + if (ar->cmd_seq < -1) + return 0; + + /* + * Initialize Counter + */ + if (ar->cmd_seq < 0) + ar->cmd_seq = seq; + + /* + * The sequence is strictly monotonic increasing and it never skips! + * + * Therefore we can safely assume that whenever we received an + * unexpected sequence we have lost some valuable data. + */ + if (seq != ar->cmd_seq) { + int count; + + count = (seq - ar->cmd_seq) % ar->fw.cmd_bufs; + + wiphy_err(ar->hw->wiphy, "lost %d command responses/traps! " + "w:%d g:%d\n", count, ar->cmd_seq, seq); + + carl9170_restart(ar, CARL9170_RR_LOST_RSP); + return -EIO; + } + + ar->cmd_seq = (ar->cmd_seq + 1) % ar->fw.cmd_bufs; + return 0; +} + +static void carl9170_cmd_callback(struct ar9170 *ar, u32 len, void *buffer) +{ + /* + * Some commands may have a variable response length + * and we cannot predict the correct length in advance. + * So we only check if we provided enough space for the data. + */ + if (unlikely(ar->readlen != (len - 4))) { + dev_warn(&ar->udev->dev, "received invalid command response:" + "got %d, instead of %d\n", len - 4, ar->readlen); + print_hex_dump_bytes("carl9170 cmd:", DUMP_PREFIX_OFFSET, + ar->cmd_buf, (ar->cmd.hdr.len + 4) & 0x3f); + print_hex_dump_bytes("carl9170 rsp:", DUMP_PREFIX_OFFSET, + buffer, len); + /* + * Do not complete. The command times out, + * and we get a stack trace from there. + */ + carl9170_restart(ar, CARL9170_RR_INVALID_RSP); + } + + spin_lock(&ar->cmd_lock); + if (ar->readbuf) { + if (len >= 4) + memcpy(ar->readbuf, buffer + 4, len - 4); + + ar->readbuf = NULL; + } + complete(&ar->cmd_wait); + spin_unlock(&ar->cmd_lock); +} + +void carl9170_handle_command_response(struct ar9170 *ar, void *buf, u32 len) +{ + struct carl9170_rsp *cmd = buf; + struct ieee80211_vif *vif; + + if ((cmd->hdr.cmd & CARL9170_RSP_FLAG) != CARL9170_RSP_FLAG) { + if (!(cmd->hdr.cmd & CARL9170_CMD_ASYNC_FLAG)) + carl9170_cmd_callback(ar, len, buf); + + return; + } + + if (unlikely(cmd->hdr.len != (len - 4))) { + if (net_ratelimit()) { + wiphy_err(ar->hw->wiphy, "FW: received over-/under" + "sized event %x (%d, but should be %d).\n", + cmd->hdr.cmd, cmd->hdr.len, len - 4); + + print_hex_dump_bytes("dump:", DUMP_PREFIX_NONE, + buf, len); + } + + return; + } + + /* hardware event handlers */ + switch (cmd->hdr.cmd) { + case CARL9170_RSP_PRETBTT: + /* pre-TBTT event */ + rcu_read_lock(); + vif = carl9170_get_main_vif(ar); + + if (!vif) { + rcu_read_unlock(); + break; + } + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + carl9170_handle_ps(ar, cmd); + break; + + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_MESH_POINT: + carl9170_update_beacon(ar, true); + break; + + default: + break; + } + rcu_read_unlock(); + + break; + + + case CARL9170_RSP_TXCOMP: + /* TX status notification */ + carl9170_tx_process_status(ar, cmd); + break; + + case CARL9170_RSP_BEACON_CONFIG: + /* + * (IBSS) beacon send notification + * bytes: 04 c2 XX YY B4 B3 B2 B1 + * + * XX always 80 + * YY always 00 + * B1-B4 "should" be the number of send out beacons. + */ + break; + + case CARL9170_RSP_ATIM: + /* End of Atim Window */ + break; + + case CARL9170_RSP_WATCHDOG: + /* Watchdog Interrupt */ + carl9170_restart(ar, CARL9170_RR_WATCHDOG); + break; + + case CARL9170_RSP_TEXT: + /* firmware debug */ + carl9170_dbg_message(ar, (char *)buf + 4, len - 4); + break; + + case CARL9170_RSP_HEXDUMP: + wiphy_dbg(ar->hw->wiphy, "FW: HD %d\n", len - 4); + print_hex_dump_bytes("FW:", DUMP_PREFIX_NONE, + (char *)buf + 4, len - 4); + break; + + case CARL9170_RSP_RADAR: + if (!net_ratelimit()) + break; + + wiphy_info(ar->hw->wiphy, "FW: RADAR! Please report this " + "incident to linux-wireless@vger.kernel.org !\n"); + break; + + case CARL9170_RSP_GPIO: +#ifdef CPTCFG_CARL9170_WPC + if (ar->wps.pbc) { + bool state = !!(cmd->gpio.gpio & cpu_to_le32( + AR9170_GPIO_PORT_WPS_BUTTON_PRESSED)); + + if (state != ar->wps.pbc_state) { + ar->wps.pbc_state = state; + input_report_key(ar->wps.pbc, KEY_WPS_BUTTON, + state); + input_sync(ar->wps.pbc); + } + } +#endif /* CPTCFG_CARL9170_WPC */ + break; + + case CARL9170_RSP_BOOT: + complete(&ar->fw_boot_wait); + break; + + default: + wiphy_err(ar->hw->wiphy, "FW: received unhandled event %x\n", + cmd->hdr.cmd); + print_hex_dump_bytes("dump:", DUMP_PREFIX_NONE, buf, len); + break; + } +} + +static int carl9170_rx_mac_status(struct ar9170 *ar, + struct ar9170_rx_head *head, struct ar9170_rx_macstatus *mac, + struct ieee80211_rx_status *status) +{ + struct ieee80211_channel *chan; + u8 error, decrypt; + + BUILD_BUG_ON(sizeof(struct ar9170_rx_head) != 12); + BUILD_BUG_ON(sizeof(struct ar9170_rx_macstatus) != 4); + + error = mac->error; + + if (error & AR9170_RX_ERROR_WRONG_RA) { + if (!ar->sniffer_enabled) + return -EINVAL; + } + + if (error & AR9170_RX_ERROR_PLCP) { + if (!(ar->filter_state & FIF_PLCPFAIL)) + return -EINVAL; + + status->flag |= RX_FLAG_FAILED_PLCP_CRC; + } + + if (error & AR9170_RX_ERROR_FCS) { + ar->tx_fcs_errors++; + + if (!(ar->filter_state & FIF_FCSFAIL)) + return -EINVAL; + + status->flag |= RX_FLAG_FAILED_FCS_CRC; + } + + decrypt = ar9170_get_decrypt_type(mac); + if (!(decrypt & AR9170_RX_ENC_SOFTWARE) && + decrypt != AR9170_ENC_ALG_NONE) { + if ((decrypt == AR9170_ENC_ALG_TKIP) && + (error & AR9170_RX_ERROR_MMIC)) + status->flag |= RX_FLAG_MMIC_ERROR; + + status->flag |= RX_FLAG_DECRYPTED; + } + + if (error & AR9170_RX_ERROR_DECRYPT && !ar->sniffer_enabled) + return -ENODATA; + + error &= ~(AR9170_RX_ERROR_MMIC | + AR9170_RX_ERROR_FCS | + AR9170_RX_ERROR_WRONG_RA | + AR9170_RX_ERROR_DECRYPT | + AR9170_RX_ERROR_PLCP); + + /* drop any other error frames */ + if (unlikely(error)) { + /* TODO: update netdevice's RX dropped/errors statistics */ + + if (net_ratelimit()) + wiphy_dbg(ar->hw->wiphy, "received frame with " + "suspicious error code (%#x).\n", error); + + return -EINVAL; + } + + chan = ar->channel; + if (chan) { + status->band = chan->band; + status->freq = chan->center_freq; + } + + switch (mac->status & AR9170_RX_STATUS_MODULATION) { + case AR9170_RX_STATUS_MODULATION_CCK: + if (mac->status & AR9170_RX_STATUS_SHORT_PREAMBLE) + status->flag |= RX_FLAG_SHORTPRE; + switch (head->plcp[0]) { + case AR9170_RX_PHY_RATE_CCK_1M: + status->rate_idx = 0; + break; + case AR9170_RX_PHY_RATE_CCK_2M: + status->rate_idx = 1; + break; + case AR9170_RX_PHY_RATE_CCK_5M: + status->rate_idx = 2; + break; + case AR9170_RX_PHY_RATE_CCK_11M: + status->rate_idx = 3; + break; + default: + if (net_ratelimit()) { + wiphy_err(ar->hw->wiphy, "invalid plcp cck " + "rate (%x).\n", head->plcp[0]); + } + + return -EINVAL; + } + break; + + case AR9170_RX_STATUS_MODULATION_DUPOFDM: + case AR9170_RX_STATUS_MODULATION_OFDM: + switch (head->plcp[0] & 0xf) { + case AR9170_TXRX_PHY_RATE_OFDM_6M: + status->rate_idx = 0; + break; + case AR9170_TXRX_PHY_RATE_OFDM_9M: + status->rate_idx = 1; + break; + case AR9170_TXRX_PHY_RATE_OFDM_12M: + status->rate_idx = 2; + break; + case AR9170_TXRX_PHY_RATE_OFDM_18M: + status->rate_idx = 3; + break; + case AR9170_TXRX_PHY_RATE_OFDM_24M: + status->rate_idx = 4; + break; + case AR9170_TXRX_PHY_RATE_OFDM_36M: + status->rate_idx = 5; + break; + case AR9170_TXRX_PHY_RATE_OFDM_48M: + status->rate_idx = 6; + break; + case AR9170_TXRX_PHY_RATE_OFDM_54M: + status->rate_idx = 7; + break; + default: + if (net_ratelimit()) { + wiphy_err(ar->hw->wiphy, "invalid plcp ofdm " + "rate (%x).\n", head->plcp[0]); + } + + return -EINVAL; + } + if (status->band == IEEE80211_BAND_2GHZ) + status->rate_idx += 4; + break; + + case AR9170_RX_STATUS_MODULATION_HT: + if (head->plcp[3] & 0x80) + status->flag |= RX_FLAG_40MHZ; + if (head->plcp[6] & 0x80) + status->flag |= RX_FLAG_SHORT_GI; + + status->rate_idx = clamp(0, 75, head->plcp[3] & 0x7f); + status->flag |= RX_FLAG_HT; + break; + + default: + BUG(); + return -ENOSYS; + } + + return 0; +} + +static void carl9170_rx_phy_status(struct ar9170 *ar, + struct ar9170_rx_phystatus *phy, struct ieee80211_rx_status *status) +{ + int i; + + BUILD_BUG_ON(sizeof(struct ar9170_rx_phystatus) != 20); + + for (i = 0; i < 3; i++) + if (phy->rssi[i] != 0x80) + status->antenna |= BIT(i); + + /* post-process RSSI */ + for (i = 0; i < 7; i++) + if (phy->rssi[i] & 0x80) + phy->rssi[i] = ((phy->rssi[i] & 0x7f) + 1) & 0x7f; + + /* TODO: we could do something with phy_errors */ + status->signal = ar->noise[0] + phy->rssi_combined; +} + +static struct sk_buff *carl9170_rx_copy_data(u8 *buf, int len) +{ + struct sk_buff *skb; + int reserved = 0; + struct ieee80211_hdr *hdr = (void *) buf; + + if (ieee80211_is_data_qos(hdr->frame_control)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + reserved += NET_IP_ALIGN; + + if (*qc & IEEE80211_QOS_CTL_A_MSDU_PRESENT) + reserved += NET_IP_ALIGN; + } + + if (ieee80211_has_a4(hdr->frame_control)) + reserved += NET_IP_ALIGN; + + reserved = 32 + (reserved & NET_IP_ALIGN); + + skb = dev_alloc_skb(len + reserved); + if (likely(skb)) { + skb_reserve(skb, reserved); + memcpy(skb_put(skb, len), buf, len); + } + + return skb; +} + +static u8 *carl9170_find_ie(u8 *data, unsigned int len, u8 ie) +{ + struct ieee80211_mgmt *mgmt = (void *)data; + u8 *pos, *end; + + pos = (u8 *)mgmt->u.beacon.variable; + end = data + len; + while (pos < end) { + if (pos + 2 + pos[1] > end) + return NULL; + + if (pos[0] == ie) + return pos; + + pos += 2 + pos[1]; + } + return NULL; +} + +/* + * NOTE: + * + * The firmware is in charge of waking up the device just before + * the AP is expected to transmit the next beacon. + * + * This leaves the driver with the important task of deciding when + * to set the PHY back to bed again. + */ +static void carl9170_ps_beacon(struct ar9170 *ar, void *data, unsigned int len) +{ + struct ieee80211_hdr *hdr = data; + struct ieee80211_tim_ie *tim_ie; + u8 *tim; + u8 tim_len; + bool cam; + + if (likely(!(ar->hw->conf.flags & IEEE80211_CONF_PS))) + return; + + /* check if this really is a beacon */ + if (!ieee80211_is_beacon(hdr->frame_control)) + return; + + /* min. beacon length + FCS_LEN */ + if (len <= 40 + FCS_LEN) + return; + + /* and only beacons from the associated BSSID, please */ + if (!ether_addr_equal(hdr->addr3, ar->common.curbssid) || + !ar->common.curaid) + return; + + ar->ps.last_beacon = jiffies; + + tim = carl9170_find_ie(data, len - FCS_LEN, WLAN_EID_TIM); + if (!tim) + return; + + if (tim[1] < sizeof(*tim_ie)) + return; + + tim_len = tim[1]; + tim_ie = (struct ieee80211_tim_ie *) &tim[2]; + + if (!WARN_ON_ONCE(!ar->hw->conf.ps_dtim_period)) + ar->ps.dtim_counter = (tim_ie->dtim_count - 1) % + ar->hw->conf.ps_dtim_period; + + /* Check whenever the PHY can be turned off again. */ + + /* 1. What about buffered unicast traffic for our AID? */ + cam = ieee80211_check_tim(tim_ie, tim_len, ar->common.curaid); + + /* 2. Maybe the AP wants to send multicast/broadcast data? */ + cam |= !!(tim_ie->bitmap_ctrl & 0x01); + + if (!cam) { + /* back to low-power land. */ + ar->ps.off_override &= ~PS_OFF_BCN; + carl9170_ps_check(ar); + } else { + /* force CAM */ + ar->ps.off_override |= PS_OFF_BCN; + } +} + +static void carl9170_ba_check(struct ar9170 *ar, void *data, unsigned int len) +{ + struct ieee80211_bar *bar = (void *) data; + struct carl9170_bar_list_entry *entry; + unsigned int queue; + + if (likely(!ieee80211_is_back(bar->frame_control))) + return; + + if (len <= sizeof(*bar) + FCS_LEN) + return; + + queue = TID_TO_WME_AC(((le16_to_cpu(bar->control) & + IEEE80211_BAR_CTRL_TID_INFO_MASK) >> + IEEE80211_BAR_CTRL_TID_INFO_SHIFT) & 7); + + rcu_read_lock(); + list_for_each_entry_rcu(entry, &ar->bar_list[queue], list) { + struct sk_buff *entry_skb = entry->skb; + struct _carl9170_tx_superframe *super = (void *)entry_skb->data; + struct ieee80211_bar *entry_bar = (void *)super->frame_data; + +#define TID_CHECK(a, b) ( \ + ((a) & cpu_to_le16(IEEE80211_BAR_CTRL_TID_INFO_MASK)) == \ + ((b) & cpu_to_le16(IEEE80211_BAR_CTRL_TID_INFO_MASK))) \ + + if (bar->start_seq_num == entry_bar->start_seq_num && + TID_CHECK(bar->control, entry_bar->control) && + compare_ether_addr(bar->ra, entry_bar->ta) == 0 && + compare_ether_addr(bar->ta, entry_bar->ra) == 0) { + struct ieee80211_tx_info *tx_info; + + tx_info = IEEE80211_SKB_CB(entry_skb); + tx_info->flags |= IEEE80211_TX_STAT_ACK; + + spin_lock_bh(&ar->bar_list_lock[queue]); + list_del_rcu(&entry->list); + spin_unlock_bh(&ar->bar_list_lock[queue]); + kfree_rcu(entry, head); + break; + } + } + rcu_read_unlock(); + +#undef TID_CHECK +} + +static bool carl9170_ampdu_check(struct ar9170 *ar, u8 *buf, u8 ms, + struct ieee80211_rx_status *rx_status) +{ + __le16 fc; + + if ((ms & AR9170_RX_STATUS_MPDU) == AR9170_RX_STATUS_MPDU_SINGLE) { + /* + * This frame is not part of an aMPDU. + * Therefore it is not subjected to any + * of the following content restrictions. + */ + return true; + } + + rx_status->flag |= RX_FLAG_AMPDU_DETAILS | RX_FLAG_AMPDU_LAST_KNOWN; + rx_status->ampdu_reference = ar->ampdu_ref; + + /* + * "802.11n - 7.4a.3 A-MPDU contents" describes in which contexts + * certain frame types can be part of an aMPDU. + * + * In order to keep the processing cost down, I opted for a + * stateless filter solely based on the frame control field. + */ + + fc = ((struct ieee80211_hdr *)buf)->frame_control; + if (ieee80211_is_data_qos(fc) && ieee80211_is_data_present(fc)) + return true; + + if (ieee80211_is_ack(fc) || ieee80211_is_back(fc) || + ieee80211_is_back_req(fc)) + return true; + + if (ieee80211_is_action(fc)) + return true; + + return false; +} + +static int carl9170_handle_mpdu(struct ar9170 *ar, u8 *buf, int len, + struct ieee80211_rx_status *status) +{ + struct sk_buff *skb; + + /* (driver) frame trap handler + * + * Because power-saving mode handing has to be implemented by + * the driver/firmware. We have to check each incoming beacon + * from the associated AP, if there's new data for us (either + * broadcast/multicast or unicast) we have to react quickly. + * + * So, if you have you want to add additional frame trap + * handlers, this would be the perfect place! + */ + + carl9170_ps_beacon(ar, buf, len); + + carl9170_ba_check(ar, buf, len); + + skb = carl9170_rx_copy_data(buf, len); + if (!skb) + return -ENOMEM; + + memcpy(IEEE80211_SKB_RXCB(skb), status, sizeof(*status)); + ieee80211_rx(ar->hw, skb); + return 0; +} + +/* + * If the frame alignment is right (or the kernel has + * CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS), and there + * is only a single MPDU in the USB frame, then we could + * submit to mac80211 the SKB directly. However, since + * there may be multiple packets in one SKB in stream + * mode, and we need to observe the proper ordering, + * this is non-trivial. + */ +static void carl9170_rx_untie_data(struct ar9170 *ar, u8 *buf, int len) +{ + struct ar9170_rx_head *head; + struct ar9170_rx_macstatus *mac; + struct ar9170_rx_phystatus *phy = NULL; + struct ieee80211_rx_status status; + int mpdu_len; + u8 mac_status; + + if (!IS_STARTED(ar)) + return; + + if (unlikely(len < sizeof(*mac))) + goto drop; + + memset(&status, 0, sizeof(status)); + + mpdu_len = len - sizeof(*mac); + + mac = (void *)(buf + mpdu_len); + mac_status = mac->status; + switch (mac_status & AR9170_RX_STATUS_MPDU) { + case AR9170_RX_STATUS_MPDU_FIRST: + ar->ampdu_ref++; + /* Aggregated MPDUs start with an PLCP header */ + if (likely(mpdu_len >= sizeof(struct ar9170_rx_head))) { + head = (void *) buf; + + /* + * The PLCP header needs to be cached for the + * following MIDDLE + LAST A-MPDU packets. + * + * So, if you are wondering why all frames seem + * to share a common RX status information, + * then you have the answer right here... + */ + memcpy(&ar->rx_plcp, (void *) buf, + sizeof(struct ar9170_rx_head)); + + mpdu_len -= sizeof(struct ar9170_rx_head); + buf += sizeof(struct ar9170_rx_head); + + ar->rx_has_plcp = true; + } else { + if (net_ratelimit()) { + wiphy_err(ar->hw->wiphy, "plcp info " + "is clipped.\n"); + } + + goto drop; + } + break; + + case AR9170_RX_STATUS_MPDU_LAST: + status.flag |= RX_FLAG_AMPDU_IS_LAST; + + /* + * The last frame of an A-MPDU has an extra tail + * which does contain the phy status of the whole + * aggregate. + */ + if (likely(mpdu_len >= sizeof(struct ar9170_rx_phystatus))) { + mpdu_len -= sizeof(struct ar9170_rx_phystatus); + phy = (void *)(buf + mpdu_len); + } else { + if (net_ratelimit()) { + wiphy_err(ar->hw->wiphy, "frame tail " + "is clipped.\n"); + } + + goto drop; + } + + case AR9170_RX_STATUS_MPDU_MIDDLE: + /* These are just data + mac status */ + if (unlikely(!ar->rx_has_plcp)) { + if (!net_ratelimit()) + return; + + wiphy_err(ar->hw->wiphy, "rx stream does not start " + "with a first_mpdu frame tag.\n"); + + goto drop; + } + + head = &ar->rx_plcp; + break; + + case AR9170_RX_STATUS_MPDU_SINGLE: + /* single mpdu has both: plcp (head) and phy status (tail) */ + head = (void *) buf; + + mpdu_len -= sizeof(struct ar9170_rx_head); + mpdu_len -= sizeof(struct ar9170_rx_phystatus); + + buf += sizeof(struct ar9170_rx_head); + phy = (void *)(buf + mpdu_len); + break; + + default: + BUG_ON(1); + break; + } + + /* FC + DU + RA + FCS */ + if (unlikely(mpdu_len < (2 + 2 + ETH_ALEN + FCS_LEN))) + goto drop; + + if (unlikely(carl9170_rx_mac_status(ar, head, mac, &status))) + goto drop; + + if (!carl9170_ampdu_check(ar, buf, mac_status, &status)) + goto drop; + + if (phy) + carl9170_rx_phy_status(ar, phy, &status); + else + status.flag |= RX_FLAG_NO_SIGNAL_VAL; + + if (carl9170_handle_mpdu(ar, buf, mpdu_len, &status)) + goto drop; + + return; +drop: + ar->rx_dropped++; +} + +static void carl9170_rx_untie_cmds(struct ar9170 *ar, const u8 *respbuf, + const unsigned int resplen) +{ + struct carl9170_rsp *cmd; + int i = 0; + + while (i < resplen) { + cmd = (void *) &respbuf[i]; + + i += cmd->hdr.len + 4; + if (unlikely(i > resplen)) + break; + + if (carl9170_check_sequence(ar, cmd->hdr.seq)) + break; + + carl9170_handle_command_response(ar, cmd, cmd->hdr.len + 4); + } + + if (unlikely(i != resplen)) { + if (!net_ratelimit()) + return; + + wiphy_err(ar->hw->wiphy, "malformed firmware trap:\n"); + print_hex_dump_bytes("rxcmd:", DUMP_PREFIX_OFFSET, + respbuf, resplen); + } +} + +static void __carl9170_rx(struct ar9170 *ar, u8 *buf, unsigned int len) +{ + unsigned int i = 0; + + /* weird thing, but this is the same in the original driver */ + while (len > 2 && i < 12 && buf[0] == 0xff && buf[1] == 0xff) { + i += 2; + len -= 2; + buf += 2; + } + + if (unlikely(len < 4)) + return; + + /* found the 6 * 0xffff marker? */ + if (i == 12) + carl9170_rx_untie_cmds(ar, buf, len); + else + carl9170_rx_untie_data(ar, buf, len); +} + +static void carl9170_rx_stream(struct ar9170 *ar, void *buf, unsigned int len) +{ + unsigned int tlen, wlen = 0, clen = 0; + struct ar9170_stream *rx_stream; + u8 *tbuf; + + tbuf = buf; + tlen = len; + + while (tlen >= 4) { + rx_stream = (void *) tbuf; + clen = le16_to_cpu(rx_stream->length); + wlen = ALIGN(clen, 4); + + /* check if this is stream has a valid tag.*/ + if (rx_stream->tag != cpu_to_le16(AR9170_RX_STREAM_TAG)) { + /* + * TODO: handle the highly unlikely event that the + * corrupted stream has the TAG at the right position. + */ + + /* check if the frame can be repaired. */ + if (!ar->rx_failover_missing) { + + /* this is not "short read". */ + if (net_ratelimit()) { + wiphy_err(ar->hw->wiphy, + "missing tag!\n"); + } + + __carl9170_rx(ar, tbuf, tlen); + return; + } + + if (ar->rx_failover_missing > tlen) { + if (net_ratelimit()) { + wiphy_err(ar->hw->wiphy, + "possible multi " + "stream corruption!\n"); + goto err_telluser; + } else { + goto err_silent; + } + } + + memcpy(skb_put(ar->rx_failover, tlen), tbuf, tlen); + ar->rx_failover_missing -= tlen; + + if (ar->rx_failover_missing <= 0) { + /* + * nested carl9170_rx_stream call! + * + * termination is guaranteed, even when the + * combined frame also have an element with + * a bad tag. + */ + + ar->rx_failover_missing = 0; + carl9170_rx_stream(ar, ar->rx_failover->data, + ar->rx_failover->len); + + skb_reset_tail_pointer(ar->rx_failover); + skb_trim(ar->rx_failover, 0); + } + + return; + } + + /* check if stream is clipped */ + if (wlen > tlen - 4) { + if (ar->rx_failover_missing) { + /* TODO: handle double stream corruption. */ + if (net_ratelimit()) { + wiphy_err(ar->hw->wiphy, "double rx " + "stream corruption!\n"); + goto err_telluser; + } else { + goto err_silent; + } + } + + /* + * save incomplete data set. + * the firmware will resend the missing bits when + * the rx - descriptor comes round again. + */ + + memcpy(skb_put(ar->rx_failover, tlen), tbuf, tlen); + ar->rx_failover_missing = clen - tlen; + return; + } + __carl9170_rx(ar, rx_stream->payload, clen); + + tbuf += wlen + 4; + tlen -= wlen + 4; + } + + if (tlen) { + if (net_ratelimit()) { + wiphy_err(ar->hw->wiphy, "%d bytes of unprocessed " + "data left in rx stream!\n", tlen); + } + + goto err_telluser; + } + + return; + +err_telluser: + wiphy_err(ar->hw->wiphy, "damaged RX stream data [want:%d, " + "data:%d, rx:%d, pending:%d ]\n", clen, wlen, tlen, + ar->rx_failover_missing); + + if (ar->rx_failover_missing) + print_hex_dump_bytes("rxbuf:", DUMP_PREFIX_OFFSET, + ar->rx_failover->data, + ar->rx_failover->len); + + print_hex_dump_bytes("stream:", DUMP_PREFIX_OFFSET, + buf, len); + + wiphy_err(ar->hw->wiphy, "please check your hardware and cables, if " + "you see this message frequently.\n"); + +err_silent: + if (ar->rx_failover_missing) { + skb_reset_tail_pointer(ar->rx_failover); + skb_trim(ar->rx_failover, 0); + ar->rx_failover_missing = 0; + } +} + +void carl9170_rx(struct ar9170 *ar, void *buf, unsigned int len) +{ + if (ar->fw.rx_stream) + carl9170_rx_stream(ar, buf, len); + else + __carl9170_rx(ar, buf, len); +} diff --git a/drivers/net/wireless/ath/carl9170/tx.c b/drivers/net/wireless/ath/carl9170/tx.c new file mode 100644 index 0000000..c61cafa --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/tx.c @@ -0,0 +1,1686 @@ +/* + * Atheros CARL9170 driver + * + * 802.11 xmit & status routines + * + * Copyright 2008, Johannes Berg + * Copyright 2009, 2010, Christian Lamparter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * Copyright (c) 2007-2008 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include "carl9170.h" +#include "hw.h" +#include "cmd.h" + +static inline unsigned int __carl9170_get_queue(struct ar9170 *ar, + unsigned int queue) +{ + if (unlikely(modparam_noht)) { + return queue; + } else { + /* + * This is just another workaround, until + * someone figures out how to get QoS and + * AMPDU to play nicely together. + */ + + return 2; /* AC_BE */ + } +} + +static inline unsigned int carl9170_get_queue(struct ar9170 *ar, + struct sk_buff *skb) +{ + return __carl9170_get_queue(ar, skb_get_queue_mapping(skb)); +} + +static bool is_mem_full(struct ar9170 *ar) +{ + return (DIV_ROUND_UP(IEEE80211_MAX_FRAME_LEN, ar->fw.mem_block_size) > + atomic_read(&ar->mem_free_blocks)); +} + +static void carl9170_tx_accounting(struct ar9170 *ar, struct sk_buff *skb) +{ + int queue, i; + bool mem_full; + + atomic_inc(&ar->tx_total_queued); + + queue = skb_get_queue_mapping(skb); + spin_lock_bh(&ar->tx_stats_lock); + + /* + * The driver has to accept the frame, regardless if the queue is + * full to the brim, or not. We have to do the queuing internally, + * since mac80211 assumes that a driver which can operate with + * aggregated frames does not reject frames for this reason. + */ + ar->tx_stats[queue].len++; + ar->tx_stats[queue].count++; + + mem_full = is_mem_full(ar); + for (i = 0; i < ar->hw->queues; i++) { + if (mem_full || ar->tx_stats[i].len >= ar->tx_stats[i].limit) { + ieee80211_stop_queue(ar->hw, i); + ar->queue_stop_timeout[i] = jiffies; + } + } + + spin_unlock_bh(&ar->tx_stats_lock); +} + +/* needs rcu_read_lock */ +static struct ieee80211_sta *__carl9170_get_tx_sta(struct ar9170 *ar, + struct sk_buff *skb) +{ + struct _carl9170_tx_superframe *super = (void *) skb->data; + struct ieee80211_hdr *hdr = (void *) super->frame_data; + struct ieee80211_vif *vif; + unsigned int vif_id; + + vif_id = (super->s.misc & CARL9170_TX_SUPER_MISC_VIF_ID) >> + CARL9170_TX_SUPER_MISC_VIF_ID_S; + + if (WARN_ON_ONCE(vif_id >= AR9170_MAX_VIRTUAL_MAC)) + return NULL; + + vif = rcu_dereference(ar->vif_priv[vif_id].vif); + if (unlikely(!vif)) + return NULL; + + /* + * Normally we should use wrappers like ieee80211_get_DA to get + * the correct peer ieee80211_sta. + * + * But there is a problem with indirect traffic (broadcasts, or + * data which is designated for other stations) in station mode. + * The frame will be directed to the AP for distribution and not + * to the actual destination. + */ + + return ieee80211_find_sta(vif, hdr->addr1); +} + +static void carl9170_tx_ps_unblock(struct ar9170 *ar, struct sk_buff *skb) +{ + struct ieee80211_sta *sta; + struct carl9170_sta_info *sta_info; + + rcu_read_lock(); + sta = __carl9170_get_tx_sta(ar, skb); + if (unlikely(!sta)) + goto out_rcu; + + sta_info = (struct carl9170_sta_info *) sta->drv_priv; + if (atomic_dec_return(&sta_info->pending_frames) == 0) + ieee80211_sta_block_awake(ar->hw, sta, false); + +out_rcu: + rcu_read_unlock(); +} + +static void carl9170_tx_accounting_free(struct ar9170 *ar, struct sk_buff *skb) +{ + int queue; + + queue = skb_get_queue_mapping(skb); + + spin_lock_bh(&ar->tx_stats_lock); + + ar->tx_stats[queue].len--; + + if (!is_mem_full(ar)) { + unsigned int i; + for (i = 0; i < ar->hw->queues; i++) { + if (ar->tx_stats[i].len >= CARL9170_NUM_TX_LIMIT_SOFT) + continue; + + if (ieee80211_queue_stopped(ar->hw, i)) { + unsigned long tmp; + + tmp = jiffies - ar->queue_stop_timeout[i]; + if (tmp > ar->max_queue_stop_timeout[i]) + ar->max_queue_stop_timeout[i] = tmp; + } + + ieee80211_wake_queue(ar->hw, i); + } + } + + spin_unlock_bh(&ar->tx_stats_lock); + + if (atomic_dec_and_test(&ar->tx_total_queued)) + complete(&ar->tx_flush); +} + +static int carl9170_alloc_dev_space(struct ar9170 *ar, struct sk_buff *skb) +{ + struct _carl9170_tx_superframe *super = (void *) skb->data; + unsigned int chunks; + int cookie = -1; + + atomic_inc(&ar->mem_allocs); + + chunks = DIV_ROUND_UP(skb->len, ar->fw.mem_block_size); + if (unlikely(atomic_sub_return(chunks, &ar->mem_free_blocks) < 0)) { + atomic_add(chunks, &ar->mem_free_blocks); + return -ENOSPC; + } + + spin_lock_bh(&ar->mem_lock); + cookie = bitmap_find_free_region(ar->mem_bitmap, ar->fw.mem_blocks, 0); + spin_unlock_bh(&ar->mem_lock); + + if (unlikely(cookie < 0)) { + atomic_add(chunks, &ar->mem_free_blocks); + return -ENOSPC; + } + + super = (void *) skb->data; + + /* + * Cookie #0 serves two special purposes: + * 1. The firmware might use it generate BlockACK frames + * in responds of an incoming BlockAckReqs. + * + * 2. Prevent double-free bugs. + */ + super->s.cookie = (u8) cookie + 1; + return 0; +} + +static void carl9170_release_dev_space(struct ar9170 *ar, struct sk_buff *skb) +{ + struct _carl9170_tx_superframe *super = (void *) skb->data; + int cookie; + + /* make a local copy of the cookie */ + cookie = super->s.cookie; + /* invalidate cookie */ + super->s.cookie = 0; + + /* + * Do a out-of-bounds check on the cookie: + * + * * cookie "0" is reserved and won't be assigned to any + * out-going frame. Internally however, it is used to + * mark no longer/un-accounted frames and serves as a + * cheap way of preventing frames from being freed + * twice by _accident_. NB: There is a tiny race... + * + * * obviously, cookie number is limited by the amount + * of available memory blocks, so the number can + * never execeed the mem_blocks count. + */ + if (unlikely(WARN_ON_ONCE(cookie == 0) || + WARN_ON_ONCE(cookie > ar->fw.mem_blocks))) + return; + + atomic_add(DIV_ROUND_UP(skb->len, ar->fw.mem_block_size), + &ar->mem_free_blocks); + + spin_lock_bh(&ar->mem_lock); + bitmap_release_region(ar->mem_bitmap, cookie - 1, 0); + spin_unlock_bh(&ar->mem_lock); +} + +/* Called from any context */ +static void carl9170_tx_release(struct kref *ref) +{ + struct ar9170 *ar; + struct carl9170_tx_info *arinfo; + struct ieee80211_tx_info *txinfo; + struct sk_buff *skb; + + arinfo = container_of(ref, struct carl9170_tx_info, ref); + txinfo = container_of((void *) arinfo, struct ieee80211_tx_info, + rate_driver_data); + skb = container_of((void *) txinfo, struct sk_buff, cb); + + ar = arinfo->ar; + if (WARN_ON_ONCE(!ar)) + return; + + BUILD_BUG_ON( + offsetof(struct ieee80211_tx_info, status.ack_signal) != 20); + + memset(&txinfo->status.ack_signal, 0, + sizeof(struct ieee80211_tx_info) - + offsetof(struct ieee80211_tx_info, status.ack_signal)); + + if (atomic_read(&ar->tx_total_queued)) + ar->tx_schedule = true; + + if (txinfo->flags & IEEE80211_TX_CTL_AMPDU) { + if (!atomic_read(&ar->tx_ampdu_upload)) + ar->tx_ampdu_schedule = true; + + if (txinfo->flags & IEEE80211_TX_STAT_AMPDU) { + struct _carl9170_tx_superframe *super; + + super = (void *)skb->data; + txinfo->status.ampdu_len = super->s.rix; + txinfo->status.ampdu_ack_len = super->s.cnt; + } else if ((txinfo->flags & IEEE80211_TX_STAT_ACK) && + !(txinfo->flags & IEEE80211_TX_CTL_REQ_TX_STATUS)) { + /* + * drop redundant tx_status reports: + * + * 1. ampdu_ack_len of the final tx_status does + * include the feedback of this particular frame. + * + * 2. tx_status_irqsafe only queues up to 128 + * tx feedback reports and discards the rest. + * + * 3. minstrel_ht is picky, it only accepts + * reports of frames with the TX_STATUS_AMPDU flag. + * + * 4. mac80211 is not particularly interested in + * feedback either [CTL_REQ_TX_STATUS not set] + */ + + ieee80211_free_txskb(ar->hw, skb); + return; + } else { + /* + * Either the frame transmission has failed or + * mac80211 requested tx status. + */ + } + } + + skb_pull(skb, sizeof(struct _carl9170_tx_superframe)); + ieee80211_tx_status_irqsafe(ar->hw, skb); +} + +void carl9170_tx_get_skb(struct sk_buff *skb) +{ + struct carl9170_tx_info *arinfo = (void *) + (IEEE80211_SKB_CB(skb))->rate_driver_data; + kref_get(&arinfo->ref); +} + +int carl9170_tx_put_skb(struct sk_buff *skb) +{ + struct carl9170_tx_info *arinfo = (void *) + (IEEE80211_SKB_CB(skb))->rate_driver_data; + + return kref_put(&arinfo->ref, carl9170_tx_release); +} + +/* Caller must hold the tid_info->lock & rcu_read_lock */ +static void carl9170_tx_shift_bm(struct ar9170 *ar, + struct carl9170_sta_tid *tid_info, u16 seq) +{ + u16 off; + + off = SEQ_DIFF(seq, tid_info->bsn); + + if (WARN_ON_ONCE(off >= CARL9170_BAW_BITS)) + return; + + /* + * Sanity check. For each MPDU we set the bit in bitmap and + * clear it once we received the tx_status. + * But if the bit is already cleared then we've been bitten + * by a bug. + */ + WARN_ON_ONCE(!test_and_clear_bit(off, tid_info->bitmap)); + + off = SEQ_DIFF(tid_info->snx, tid_info->bsn); + if (WARN_ON_ONCE(off >= CARL9170_BAW_BITS)) + return; + + if (!bitmap_empty(tid_info->bitmap, off)) + off = find_first_bit(tid_info->bitmap, off); + + tid_info->bsn += off; + tid_info->bsn &= 0x0fff; + + bitmap_shift_right(tid_info->bitmap, tid_info->bitmap, + off, CARL9170_BAW_BITS); +} + +static void carl9170_tx_status_process_ampdu(struct ar9170 *ar, + struct sk_buff *skb, struct ieee80211_tx_info *txinfo) +{ + struct _carl9170_tx_superframe *super = (void *) skb->data; + struct ieee80211_hdr *hdr = (void *) super->frame_data; + struct ieee80211_sta *sta; + struct carl9170_sta_info *sta_info; + struct carl9170_sta_tid *tid_info; + u8 tid; + + if (!(txinfo->flags & IEEE80211_TX_CTL_AMPDU) || + txinfo->flags & IEEE80211_TX_CTL_INJECTED) + return; + + rcu_read_lock(); + sta = __carl9170_get_tx_sta(ar, skb); + if (unlikely(!sta)) + goto out_rcu; + + tid = get_tid_h(hdr); + + sta_info = (void *) sta->drv_priv; + tid_info = rcu_dereference(sta_info->agg[tid]); + if (!tid_info) + goto out_rcu; + + spin_lock_bh(&tid_info->lock); + if (likely(tid_info->state >= CARL9170_TID_STATE_IDLE)) + carl9170_tx_shift_bm(ar, tid_info, get_seq_h(hdr)); + + if (sta_info->stats[tid].clear) { + sta_info->stats[tid].clear = false; + sta_info->stats[tid].req = false; + sta_info->stats[tid].ampdu_len = 0; + sta_info->stats[tid].ampdu_ack_len = 0; + } + + sta_info->stats[tid].ampdu_len++; + if (txinfo->status.rates[0].count == 1) + sta_info->stats[tid].ampdu_ack_len++; + + if (!(txinfo->flags & IEEE80211_TX_STAT_ACK)) + sta_info->stats[tid].req = true; + + if (super->f.mac_control & cpu_to_le16(AR9170_TX_MAC_IMM_BA)) { + super->s.rix = sta_info->stats[tid].ampdu_len; + super->s.cnt = sta_info->stats[tid].ampdu_ack_len; + txinfo->flags |= IEEE80211_TX_STAT_AMPDU; + if (sta_info->stats[tid].req) + txinfo->flags |= IEEE80211_TX_STAT_AMPDU_NO_BACK; + + sta_info->stats[tid].clear = true; + } + spin_unlock_bh(&tid_info->lock); + +out_rcu: + rcu_read_unlock(); +} + +static void carl9170_tx_bar_status(struct ar9170 *ar, struct sk_buff *skb, + struct ieee80211_tx_info *tx_info) +{ + struct _carl9170_tx_superframe *super = (void *) skb->data; + struct ieee80211_bar *bar = (void *) super->frame_data; + + /* + * Unlike all other frames, the status report for BARs does + * not directly come from the hardware as it is incapable of + * matching a BA to a previously send BAR. + * Instead the RX-path will scan for incoming BAs and set the + * IEEE80211_TX_STAT_ACK if it sees one that was likely + * caused by a BAR from us. + */ + + if (unlikely(ieee80211_is_back_req(bar->frame_control)) && + !(tx_info->flags & IEEE80211_TX_STAT_ACK)) { + struct carl9170_bar_list_entry *entry; + int queue = skb_get_queue_mapping(skb); + + rcu_read_lock(); + list_for_each_entry_rcu(entry, &ar->bar_list[queue], list) { + if (entry->skb == skb) { + spin_lock_bh(&ar->bar_list_lock[queue]); + list_del_rcu(&entry->list); + spin_unlock_bh(&ar->bar_list_lock[queue]); + kfree_rcu(entry, head); + goto out; + } + } + + WARN(1, "bar not found in %d - ra:%pM ta:%pM c:%x ssn:%x\n", + queue, bar->ra, bar->ta, bar->control, + bar->start_seq_num); +out: + rcu_read_unlock(); + } +} + +void carl9170_tx_status(struct ar9170 *ar, struct sk_buff *skb, + const bool success) +{ + struct ieee80211_tx_info *txinfo; + + carl9170_tx_accounting_free(ar, skb); + + txinfo = IEEE80211_SKB_CB(skb); + + carl9170_tx_bar_status(ar, skb, txinfo); + + if (success) + txinfo->flags |= IEEE80211_TX_STAT_ACK; + else + ar->tx_ack_failures++; + + if (txinfo->flags & IEEE80211_TX_CTL_AMPDU) + carl9170_tx_status_process_ampdu(ar, skb, txinfo); + + carl9170_tx_ps_unblock(ar, skb); + carl9170_tx_put_skb(skb); +} + +/* This function may be called form any context */ +void carl9170_tx_callback(struct ar9170 *ar, struct sk_buff *skb) +{ + struct ieee80211_tx_info *txinfo = IEEE80211_SKB_CB(skb); + + atomic_dec(&ar->tx_total_pending); + + if (txinfo->flags & IEEE80211_TX_CTL_AMPDU) + atomic_dec(&ar->tx_ampdu_upload); + + if (carl9170_tx_put_skb(skb)) + tasklet_hi_schedule(&ar->usb_tasklet); +} + +static struct sk_buff *carl9170_get_queued_skb(struct ar9170 *ar, u8 cookie, + struct sk_buff_head *queue) +{ + struct sk_buff *skb; + + spin_lock_bh(&queue->lock); + skb_queue_walk(queue, skb) { + struct _carl9170_tx_superframe *txc = (void *) skb->data; + + if (txc->s.cookie != cookie) + continue; + + __skb_unlink(skb, queue); + spin_unlock_bh(&queue->lock); + + carl9170_release_dev_space(ar, skb); + return skb; + } + spin_unlock_bh(&queue->lock); + + return NULL; +} + +static void carl9170_tx_fill_rateinfo(struct ar9170 *ar, unsigned int rix, + unsigned int tries, struct ieee80211_tx_info *txinfo) +{ + unsigned int i; + + for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { + if (txinfo->status.rates[i].idx < 0) + break; + + if (i == rix) { + txinfo->status.rates[i].count = tries; + i++; + break; + } + } + + for (; i < IEEE80211_TX_MAX_RATES; i++) { + txinfo->status.rates[i].idx = -1; + txinfo->status.rates[i].count = 0; + } +} + +static void carl9170_check_queue_stop_timeout(struct ar9170 *ar) +{ + int i; + struct sk_buff *skb; + struct ieee80211_tx_info *txinfo; + struct carl9170_tx_info *arinfo; + bool restart = false; + + for (i = 0; i < ar->hw->queues; i++) { + spin_lock_bh(&ar->tx_status[i].lock); + + skb = skb_peek(&ar->tx_status[i]); + + if (!skb) + goto next; + + txinfo = IEEE80211_SKB_CB(skb); + arinfo = (void *) txinfo->rate_driver_data; + + if (time_is_before_jiffies(arinfo->timeout + + msecs_to_jiffies(CARL9170_QUEUE_STUCK_TIMEOUT)) == true) + restart = true; + +next: + spin_unlock_bh(&ar->tx_status[i].lock); + } + + if (restart) { + /* + * At least one queue has been stuck for long enough. + * Give the device a kick and hope it gets back to + * work. + * + * possible reasons may include: + * - frames got lost/corrupted (bad connection to the device) + * - stalled rx processing/usb controller hiccups + * - firmware errors/bugs + * - every bug you can think of. + * - all bugs you can't... + * - ... + */ + carl9170_restart(ar, CARL9170_RR_STUCK_TX); + } +} + +static void carl9170_tx_ampdu_timeout(struct ar9170 *ar) +{ + struct carl9170_sta_tid *iter; + struct sk_buff *skb; + struct ieee80211_tx_info *txinfo; + struct carl9170_tx_info *arinfo; + struct ieee80211_sta *sta; + + rcu_read_lock(); + list_for_each_entry_rcu(iter, &ar->tx_ampdu_list, list) { + if (iter->state < CARL9170_TID_STATE_IDLE) + continue; + + spin_lock_bh(&iter->lock); + skb = skb_peek(&iter->queue); + if (!skb) + goto unlock; + + txinfo = IEEE80211_SKB_CB(skb); + arinfo = (void *)txinfo->rate_driver_data; + if (time_is_after_jiffies(arinfo->timeout + + msecs_to_jiffies(CARL9170_QUEUE_TIMEOUT))) + goto unlock; + + sta = __carl9170_get_tx_sta(ar, skb); + if (WARN_ON(!sta)) + goto unlock; + + ieee80211_stop_tx_ba_session(sta, iter->tid); +unlock: + spin_unlock_bh(&iter->lock); + + } + rcu_read_unlock(); +} + +void carl9170_tx_janitor(struct work_struct *work) +{ + struct ar9170 *ar = container_of(work, struct ar9170, + tx_janitor.work); + if (!IS_STARTED(ar)) + return; + + ar->tx_janitor_last_run = jiffies; + + carl9170_check_queue_stop_timeout(ar); + carl9170_tx_ampdu_timeout(ar); + + if (!atomic_read(&ar->tx_total_queued)) + return; + + ieee80211_queue_delayed_work(ar->hw, &ar->tx_janitor, + msecs_to_jiffies(CARL9170_TX_TIMEOUT)); +} + +static void __carl9170_tx_process_status(struct ar9170 *ar, + const uint8_t cookie, const uint8_t info) +{ + struct sk_buff *skb; + struct ieee80211_tx_info *txinfo; + unsigned int r, t, q; + bool success = true; + + q = ar9170_qmap[info & CARL9170_TX_STATUS_QUEUE]; + + skb = carl9170_get_queued_skb(ar, cookie, &ar->tx_status[q]); + if (!skb) { + /* + * We have lost the race to another thread. + */ + + return ; + } + + txinfo = IEEE80211_SKB_CB(skb); + + if (!(info & CARL9170_TX_STATUS_SUCCESS)) + success = false; + + r = (info & CARL9170_TX_STATUS_RIX) >> CARL9170_TX_STATUS_RIX_S; + t = (info & CARL9170_TX_STATUS_TRIES) >> CARL9170_TX_STATUS_TRIES_S; + + carl9170_tx_fill_rateinfo(ar, r, t, txinfo); + carl9170_tx_status(ar, skb, success); +} + +void carl9170_tx_process_status(struct ar9170 *ar, + const struct carl9170_rsp *cmd) +{ + unsigned int i; + + for (i = 0; i < cmd->hdr.ext; i++) { + if (WARN_ON(i > ((cmd->hdr.len / 2) + 1))) { + print_hex_dump_bytes("UU:", DUMP_PREFIX_NONE, + (void *) cmd, cmd->hdr.len + 4); + break; + } + + __carl9170_tx_process_status(ar, cmd->_tx_status[i].cookie, + cmd->_tx_status[i].info); + } +} + +static void carl9170_tx_rate_tpc_chains(struct ar9170 *ar, + struct ieee80211_tx_info *info, struct ieee80211_tx_rate *txrate, + unsigned int *phyrate, unsigned int *tpc, unsigned int *chains) +{ + struct ieee80211_rate *rate = NULL; + u8 *txpower; + unsigned int idx; + + idx = txrate->idx; + *tpc = 0; + *phyrate = 0; + + if (txrate->flags & IEEE80211_TX_RC_MCS) { + if (txrate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH) { + /* +1 dBm for HT40 */ + *tpc += 2; + + if (info->band == IEEE80211_BAND_2GHZ) + txpower = ar->power_2G_ht40; + else + txpower = ar->power_5G_ht40; + } else { + if (info->band == IEEE80211_BAND_2GHZ) + txpower = ar->power_2G_ht20; + else + txpower = ar->power_5G_ht20; + } + + *phyrate = txrate->idx; + *tpc += txpower[idx & 7]; + } else { + if (info->band == IEEE80211_BAND_2GHZ) { + if (idx < 4) + txpower = ar->power_2G_cck; + else + txpower = ar->power_2G_ofdm; + } else { + txpower = ar->power_5G_leg; + idx += 4; + } + + rate = &__carl9170_ratetable[idx]; + *tpc += txpower[(rate->hw_value & 0x30) >> 4]; + *phyrate = rate->hw_value & 0xf; + } + + if (ar->eeprom.tx_mask == 1) { + *chains = AR9170_TX_PHY_TXCHAIN_1; + } else { + if (!(txrate->flags & IEEE80211_TX_RC_MCS) && + rate && rate->bitrate >= 360) + *chains = AR9170_TX_PHY_TXCHAIN_1; + else + *chains = AR9170_TX_PHY_TXCHAIN_2; + } + + *tpc = min_t(unsigned int, *tpc, ar->hw->conf.power_level * 2); +} + +static __le32 carl9170_tx_physet(struct ar9170 *ar, + struct ieee80211_tx_info *info, struct ieee80211_tx_rate *txrate) +{ + unsigned int power = 0, chains = 0, phyrate = 0; + __le32 tmp; + + tmp = cpu_to_le32(0); + + if (txrate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH) + tmp |= cpu_to_le32(AR9170_TX_PHY_BW_40MHZ << + AR9170_TX_PHY_BW_S); + /* this works because 40 MHz is 2 and dup is 3 */ + if (txrate->flags & IEEE80211_TX_RC_DUP_DATA) + tmp |= cpu_to_le32(AR9170_TX_PHY_BW_40MHZ_DUP << + AR9170_TX_PHY_BW_S); + + if (txrate->flags & IEEE80211_TX_RC_SHORT_GI) + tmp |= cpu_to_le32(AR9170_TX_PHY_SHORT_GI); + + if (txrate->flags & IEEE80211_TX_RC_MCS) { + SET_VAL(AR9170_TX_PHY_MCS, phyrate, txrate->idx); + + /* heavy clip control */ + tmp |= cpu_to_le32((txrate->idx & 0x7) << + AR9170_TX_PHY_TX_HEAVY_CLIP_S); + + tmp |= cpu_to_le32(AR9170_TX_PHY_MOD_HT); + + /* + * green field preamble does not work. + * + * if (txrate->flags & IEEE80211_TX_RC_GREEN_FIELD) + * tmp |= cpu_to_le32(AR9170_TX_PHY_GREENFIELD); + */ + } else { + if (info->band == IEEE80211_BAND_2GHZ) { + if (txrate->idx <= AR9170_TX_PHY_RATE_CCK_11M) + tmp |= cpu_to_le32(AR9170_TX_PHY_MOD_CCK); + else + tmp |= cpu_to_le32(AR9170_TX_PHY_MOD_OFDM); + } else { + tmp |= cpu_to_le32(AR9170_TX_PHY_MOD_OFDM); + } + + /* + * short preamble seems to be broken too. + * + * if (txrate->flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE) + * tmp |= cpu_to_le32(AR9170_TX_PHY_SHORT_PREAMBLE); + */ + } + carl9170_tx_rate_tpc_chains(ar, info, txrate, + &phyrate, &power, &chains); + + tmp |= cpu_to_le32(SET_CONSTVAL(AR9170_TX_PHY_MCS, phyrate)); + tmp |= cpu_to_le32(SET_CONSTVAL(AR9170_TX_PHY_TX_PWR, power)); + tmp |= cpu_to_le32(SET_CONSTVAL(AR9170_TX_PHY_TXCHAIN, chains)); + return tmp; +} + +static bool carl9170_tx_rts_check(struct ar9170 *ar, + struct ieee80211_tx_rate *rate, + bool ampdu, bool multi) +{ + switch (ar->erp_mode) { + case CARL9170_ERP_AUTO: + if (ampdu) + break; + + case CARL9170_ERP_MAC80211: + if (!(rate->flags & IEEE80211_TX_RC_USE_RTS_CTS)) + break; + + case CARL9170_ERP_RTS: + if (likely(!multi)) + return true; + + default: + break; + } + + return false; +} + +static bool carl9170_tx_cts_check(struct ar9170 *ar, + struct ieee80211_tx_rate *rate) +{ + switch (ar->erp_mode) { + case CARL9170_ERP_AUTO: + case CARL9170_ERP_MAC80211: + if (!(rate->flags & IEEE80211_TX_RC_USE_CTS_PROTECT)) + break; + + case CARL9170_ERP_CTS: + return true; + + default: + break; + } + + return false; +} + +static int carl9170_tx_prepare(struct ar9170 *ar, + struct ieee80211_sta *sta, + struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr; + struct _carl9170_tx_superframe *txc; + struct carl9170_vif_info *cvif; + struct ieee80211_tx_info *info; + struct ieee80211_tx_rate *txrate; + struct carl9170_tx_info *arinfo; + unsigned int hw_queue; + int i; + __le16 mac_tmp; + u16 len; + bool ampdu, no_ack; + + BUILD_BUG_ON(sizeof(*arinfo) > sizeof(info->rate_driver_data)); + BUILD_BUG_ON(sizeof(struct _carl9170_tx_superdesc) != + CARL9170_TX_SUPERDESC_LEN); + + BUILD_BUG_ON(sizeof(struct _ar9170_tx_hwdesc) != + AR9170_TX_HWDESC_LEN); + + BUILD_BUG_ON(IEEE80211_TX_MAX_RATES < CARL9170_TX_MAX_RATES); + + BUILD_BUG_ON(AR9170_MAX_VIRTUAL_MAC > + ((CARL9170_TX_SUPER_MISC_VIF_ID >> + CARL9170_TX_SUPER_MISC_VIF_ID_S) + 1)); + + hw_queue = ar9170_qmap[carl9170_get_queue(ar, skb)]; + + hdr = (void *)skb->data; + info = IEEE80211_SKB_CB(skb); + len = skb->len; + + /* + * Note: If the frame was sent through a monitor interface, + * the ieee80211_vif pointer can be NULL. + */ + if (likely(info->control.vif)) + cvif = (void *) info->control.vif->drv_priv; + else + cvif = NULL; + + txc = (void *)skb_push(skb, sizeof(*txc)); + memset(txc, 0, sizeof(*txc)); + + SET_VAL(CARL9170_TX_SUPER_MISC_QUEUE, txc->s.misc, hw_queue); + + if (likely(cvif)) + SET_VAL(CARL9170_TX_SUPER_MISC_VIF_ID, txc->s.misc, cvif->id); + + if (unlikely(info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM)) + txc->s.misc |= CARL9170_TX_SUPER_MISC_CAB; + + if (unlikely(info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ)) + txc->s.misc |= CARL9170_TX_SUPER_MISC_ASSIGN_SEQ; + + if (unlikely(ieee80211_is_probe_resp(hdr->frame_control))) + txc->s.misc |= CARL9170_TX_SUPER_MISC_FILL_IN_TSF; + + mac_tmp = cpu_to_le16(AR9170_TX_MAC_HW_DURATION | + AR9170_TX_MAC_BACKOFF); + mac_tmp |= cpu_to_le16((hw_queue << AR9170_TX_MAC_QOS_S) & + AR9170_TX_MAC_QOS); + + no_ack = !!(info->flags & IEEE80211_TX_CTL_NO_ACK); + if (unlikely(no_ack)) + mac_tmp |= cpu_to_le16(AR9170_TX_MAC_NO_ACK); + + if (info->control.hw_key) { + len += info->control.hw_key->icv_len; + + switch (info->control.hw_key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + case WLAN_CIPHER_SUITE_TKIP: + mac_tmp |= cpu_to_le16(AR9170_TX_MAC_ENCR_RC4); + break; + case WLAN_CIPHER_SUITE_CCMP: + mac_tmp |= cpu_to_le16(AR9170_TX_MAC_ENCR_AES); + break; + default: + WARN_ON(1); + goto err_out; + } + } + + ampdu = !!(info->flags & IEEE80211_TX_CTL_AMPDU); + if (ampdu) { + unsigned int density, factor; + + if (unlikely(!sta || !cvif)) + goto err_out; + + factor = min_t(unsigned int, 1u, sta->ht_cap.ampdu_factor); + density = sta->ht_cap.ampdu_density; + + if (density) { + /* + * Watch out! + * + * Otus uses slightly different density values than + * those from the 802.11n spec. + */ + + density = max_t(unsigned int, density + 1, 7u); + } + + SET_VAL(CARL9170_TX_SUPER_AMPDU_DENSITY, + txc->s.ampdu_settings, density); + + SET_VAL(CARL9170_TX_SUPER_AMPDU_FACTOR, + txc->s.ampdu_settings, factor); + } + + /* + * NOTE: For the first rate, the ERP & AMPDU flags are directly + * taken from mac_control. For all fallback rate, the firmware + * updates the mac_control flags from the rate info field. + */ + for (i = 0; i < CARL9170_TX_MAX_RATES; i++) { + __le32 phy_set; + txrate = &info->control.rates[i]; + if (txrate->idx < 0) + break; + + phy_set = carl9170_tx_physet(ar, info, txrate); + if (i == 0) { + /* first rate - part of the hw's frame header */ + txc->f.phy_control = phy_set; + + if (ampdu && txrate->flags & IEEE80211_TX_RC_MCS) + mac_tmp |= cpu_to_le16(AR9170_TX_MAC_AGGR); + if (carl9170_tx_rts_check(ar, txrate, ampdu, no_ack)) + mac_tmp |= cpu_to_le16(AR9170_TX_MAC_PROT_RTS); + else if (carl9170_tx_cts_check(ar, txrate)) + mac_tmp |= cpu_to_le16(AR9170_TX_MAC_PROT_CTS); + + } else { + /* fallback rates are stored in the firmware's + * retry rate set array. + */ + txc->s.rr[i - 1] = phy_set; + } + + SET_VAL(CARL9170_TX_SUPER_RI_TRIES, txc->s.ri[i], + txrate->count); + + if (carl9170_tx_rts_check(ar, txrate, ampdu, no_ack)) + txc->s.ri[i] |= (AR9170_TX_MAC_PROT_RTS << + CARL9170_TX_SUPER_RI_ERP_PROT_S); + else if (carl9170_tx_cts_check(ar, txrate)) + txc->s.ri[i] |= (AR9170_TX_MAC_PROT_CTS << + CARL9170_TX_SUPER_RI_ERP_PROT_S); + + if (ampdu && (txrate->flags & IEEE80211_TX_RC_MCS)) + txc->s.ri[i] |= CARL9170_TX_SUPER_RI_AMPDU; + } + + txc->s.len = cpu_to_le16(skb->len); + txc->f.length = cpu_to_le16(len + FCS_LEN); + txc->f.mac_control = mac_tmp; + + arinfo = (void *)info->rate_driver_data; + arinfo->timeout = jiffies; + arinfo->ar = ar; + kref_init(&arinfo->ref); + return 0; + +err_out: + skb_pull(skb, sizeof(*txc)); + return -EINVAL; +} + +static void carl9170_set_immba(struct ar9170 *ar, struct sk_buff *skb) +{ + struct _carl9170_tx_superframe *super; + + super = (void *) skb->data; + super->f.mac_control |= cpu_to_le16(AR9170_TX_MAC_IMM_BA); +} + +static void carl9170_set_ampdu_params(struct ar9170 *ar, struct sk_buff *skb) +{ + struct _carl9170_tx_superframe *super; + int tmp; + + super = (void *) skb->data; + + tmp = (super->s.ampdu_settings & CARL9170_TX_SUPER_AMPDU_DENSITY) << + CARL9170_TX_SUPER_AMPDU_DENSITY_S; + + /* + * If you haven't noticed carl9170_tx_prepare has already filled + * in all ampdu spacing & factor parameters. + * Now it's the time to check whenever the settings have to be + * updated by the firmware, or if everything is still the same. + * + * There's no sane way to handle different density values with + * this hardware, so we may as well just do the compare in the + * driver. + */ + + if (tmp != ar->current_density) { + ar->current_density = tmp; + super->s.ampdu_settings |= + CARL9170_TX_SUPER_AMPDU_COMMIT_DENSITY; + } + + tmp = (super->s.ampdu_settings & CARL9170_TX_SUPER_AMPDU_FACTOR) << + CARL9170_TX_SUPER_AMPDU_FACTOR_S; + + if (tmp != ar->current_factor) { + ar->current_factor = tmp; + super->s.ampdu_settings |= + CARL9170_TX_SUPER_AMPDU_COMMIT_FACTOR; + } +} + +static bool carl9170_tx_rate_check(struct ar9170 *ar, struct sk_buff *_dest, + struct sk_buff *_src) +{ + struct _carl9170_tx_superframe *dest, *src; + + dest = (void *) _dest->data; + src = (void *) _src->data; + + /* + * The mac80211 rate control algorithm expects that all MPDUs in + * an AMPDU share the same tx vectors. + * This is not really obvious right now, because the hardware + * does the AMPDU setup according to its own rulebook. + * Our nicely assembled, strictly monotonic increasing mpdu + * chains will be broken up, mashed back together... + */ + + return (dest->f.phy_control == src->f.phy_control); +} + +static void carl9170_tx_ampdu(struct ar9170 *ar) +{ + struct sk_buff_head agg; + struct carl9170_sta_tid *tid_info; + struct sk_buff *skb, *first; + unsigned int i = 0, done_ampdus = 0; + u16 seq, queue, tmpssn; + + atomic_inc(&ar->tx_ampdu_scheduler); + ar->tx_ampdu_schedule = false; + + if (atomic_read(&ar->tx_ampdu_upload)) + return; + + if (!ar->tx_ampdu_list_len) + return; + + __skb_queue_head_init(&agg); + + rcu_read_lock(); + tid_info = rcu_dereference(ar->tx_ampdu_iter); + if (WARN_ON_ONCE(!tid_info)) { + rcu_read_unlock(); + return; + } + +retry: + list_for_each_entry_continue_rcu(tid_info, &ar->tx_ampdu_list, list) { + i++; + + if (tid_info->state < CARL9170_TID_STATE_PROGRESS) + continue; + + queue = TID_TO_WME_AC(tid_info->tid); + + spin_lock_bh(&tid_info->lock); + if (tid_info->state != CARL9170_TID_STATE_XMIT) + goto processed; + + tid_info->counter++; + first = skb_peek(&tid_info->queue); + tmpssn = carl9170_get_seq(first); + seq = tid_info->snx; + + if (unlikely(tmpssn != seq)) { + tid_info->state = CARL9170_TID_STATE_IDLE; + + goto processed; + } + + while ((skb = skb_peek(&tid_info->queue))) { + /* strict 0, 1, ..., n - 1, n frame sequence order */ + if (unlikely(carl9170_get_seq(skb) != seq)) + break; + + /* don't upload more than AMPDU FACTOR allows. */ + if (unlikely(SEQ_DIFF(tid_info->snx, tid_info->bsn) >= + (tid_info->max - 1))) + break; + + if (!carl9170_tx_rate_check(ar, skb, first)) + break; + + atomic_inc(&ar->tx_ampdu_upload); + tid_info->snx = seq = SEQ_NEXT(seq); + __skb_unlink(skb, &tid_info->queue); + + __skb_queue_tail(&agg, skb); + + if (skb_queue_len(&agg) >= CARL9170_NUM_TX_AGG_MAX) + break; + } + + if (skb_queue_empty(&tid_info->queue) || + carl9170_get_seq(skb_peek(&tid_info->queue)) != + tid_info->snx) { + /* + * stop TID, if A-MPDU frames are still missing, + * or whenever the queue is empty. + */ + + tid_info->state = CARL9170_TID_STATE_IDLE; + } + done_ampdus++; + +processed: + spin_unlock_bh(&tid_info->lock); + + if (skb_queue_empty(&agg)) + continue; + + /* apply ampdu spacing & factor settings */ + carl9170_set_ampdu_params(ar, skb_peek(&agg)); + + /* set aggregation push bit */ + carl9170_set_immba(ar, skb_peek_tail(&agg)); + + spin_lock_bh(&ar->tx_pending[queue].lock); + skb_queue_splice_tail_init(&agg, &ar->tx_pending[queue]); + spin_unlock_bh(&ar->tx_pending[queue].lock); + ar->tx_schedule = true; + } + if ((done_ampdus++ == 0) && (i++ == 0)) + goto retry; + + rcu_assign_pointer(ar->tx_ampdu_iter, tid_info); + rcu_read_unlock(); +} + +static struct sk_buff *carl9170_tx_pick_skb(struct ar9170 *ar, + struct sk_buff_head *queue) +{ + struct sk_buff *skb; + struct ieee80211_tx_info *info; + struct carl9170_tx_info *arinfo; + + BUILD_BUG_ON(sizeof(*arinfo) > sizeof(info->rate_driver_data)); + + spin_lock_bh(&queue->lock); + skb = skb_peek(queue); + if (unlikely(!skb)) + goto err_unlock; + + if (carl9170_alloc_dev_space(ar, skb)) + goto err_unlock; + + __skb_unlink(skb, queue); + spin_unlock_bh(&queue->lock); + + info = IEEE80211_SKB_CB(skb); + arinfo = (void *) info->rate_driver_data; + + arinfo->timeout = jiffies; + return skb; + +err_unlock: + spin_unlock_bh(&queue->lock); + return NULL; +} + +void carl9170_tx_drop(struct ar9170 *ar, struct sk_buff *skb) +{ + struct _carl9170_tx_superframe *super; + uint8_t q = 0; + + ar->tx_dropped++; + + super = (void *)skb->data; + SET_VAL(CARL9170_TX_SUPER_MISC_QUEUE, q, + ar9170_qmap[carl9170_get_queue(ar, skb)]); + __carl9170_tx_process_status(ar, super->s.cookie, q); +} + +static bool carl9170_tx_ps_drop(struct ar9170 *ar, struct sk_buff *skb) +{ + struct ieee80211_sta *sta; + struct carl9170_sta_info *sta_info; + struct ieee80211_tx_info *tx_info; + + rcu_read_lock(); + sta = __carl9170_get_tx_sta(ar, skb); + if (!sta) + goto out_rcu; + + sta_info = (void *) sta->drv_priv; + tx_info = IEEE80211_SKB_CB(skb); + + if (unlikely(sta_info->sleeping) && + !(tx_info->flags & (IEEE80211_TX_CTL_NO_PS_BUFFER | + IEEE80211_TX_CTL_CLEAR_PS_FILT))) { + rcu_read_unlock(); + + if (tx_info->flags & IEEE80211_TX_CTL_AMPDU) + atomic_dec(&ar->tx_ampdu_upload); + + tx_info->flags |= IEEE80211_TX_STAT_TX_FILTERED; + carl9170_release_dev_space(ar, skb); + carl9170_tx_status(ar, skb, false); + return true; + } + +out_rcu: + rcu_read_unlock(); + return false; +} + +static void carl9170_bar_check(struct ar9170 *ar, struct sk_buff *skb) +{ + struct _carl9170_tx_superframe *super = (void *) skb->data; + struct ieee80211_bar *bar = (void *) super->frame_data; + + if (unlikely(ieee80211_is_back_req(bar->frame_control)) && + skb->len >= sizeof(struct ieee80211_bar)) { + struct carl9170_bar_list_entry *entry; + unsigned int queue = skb_get_queue_mapping(skb); + + entry = kmalloc(sizeof(*entry), GFP_ATOMIC); + if (!WARN_ON_ONCE(!entry)) { + entry->skb = skb; + spin_lock_bh(&ar->bar_list_lock[queue]); + list_add_tail_rcu(&entry->list, &ar->bar_list[queue]); + spin_unlock_bh(&ar->bar_list_lock[queue]); + } + } +} + +static void carl9170_tx(struct ar9170 *ar) +{ + struct sk_buff *skb; + unsigned int i, q; + bool schedule_garbagecollector = false; + + ar->tx_schedule = false; + + if (unlikely(!IS_STARTED(ar))) + return; + + carl9170_usb_handle_tx_err(ar); + + for (i = 0; i < ar->hw->queues; i++) { + while (!skb_queue_empty(&ar->tx_pending[i])) { + skb = carl9170_tx_pick_skb(ar, &ar->tx_pending[i]); + if (unlikely(!skb)) + break; + + if (unlikely(carl9170_tx_ps_drop(ar, skb))) + continue; + + carl9170_bar_check(ar, skb); + + atomic_inc(&ar->tx_total_pending); + + q = __carl9170_get_queue(ar, i); + /* + * NB: tx_status[i] vs. tx_status[q], + * TODO: Move into pick_skb or alloc_dev_space. + */ + skb_queue_tail(&ar->tx_status[q], skb); + + /* + * increase ref count to "2". + * Ref counting is the easiest way to solve the + * race between the urb's completion routine: + * carl9170_tx_callback + * and wlan tx status functions: + * carl9170_tx_status/janitor. + */ + carl9170_tx_get_skb(skb); + + carl9170_usb_tx(ar, skb); + schedule_garbagecollector = true; + } + } + + if (!schedule_garbagecollector) + return; + + ieee80211_queue_delayed_work(ar->hw, &ar->tx_janitor, + msecs_to_jiffies(CARL9170_TX_TIMEOUT)); +} + +static bool carl9170_tx_ampdu_queue(struct ar9170 *ar, + struct ieee80211_sta *sta, struct sk_buff *skb, + struct ieee80211_tx_info *txinfo) +{ + struct carl9170_sta_info *sta_info; + struct carl9170_sta_tid *agg; + struct sk_buff *iter; + u16 tid, seq, qseq, off; + bool run = false; + + tid = carl9170_get_tid(skb); + seq = carl9170_get_seq(skb); + sta_info = (void *) sta->drv_priv; + + rcu_read_lock(); + agg = rcu_dereference(sta_info->agg[tid]); + + if (!agg) + goto err_unlock_rcu; + + spin_lock_bh(&agg->lock); + if (unlikely(agg->state < CARL9170_TID_STATE_IDLE)) + goto err_unlock; + + /* check if sequence is within the BA window */ + if (unlikely(!BAW_WITHIN(agg->bsn, CARL9170_BAW_BITS, seq))) + goto err_unlock; + + if (WARN_ON_ONCE(!BAW_WITHIN(agg->snx, CARL9170_BAW_BITS, seq))) + goto err_unlock; + + off = SEQ_DIFF(seq, agg->bsn); + if (WARN_ON_ONCE(test_and_set_bit(off, agg->bitmap))) + goto err_unlock; + + if (likely(BAW_WITHIN(agg->hsn, CARL9170_BAW_BITS, seq))) { + __skb_queue_tail(&agg->queue, skb); + agg->hsn = seq; + goto queued; + } + + skb_queue_reverse_walk(&agg->queue, iter) { + qseq = carl9170_get_seq(iter); + + if (BAW_WITHIN(qseq, CARL9170_BAW_BITS, seq)) { + __skb_queue_after(&agg->queue, iter, skb); + goto queued; + } + } + + __skb_queue_head(&agg->queue, skb); +queued: + + if (unlikely(agg->state != CARL9170_TID_STATE_XMIT)) { + if (agg->snx == carl9170_get_seq(skb_peek(&agg->queue))) { + agg->state = CARL9170_TID_STATE_XMIT; + run = true; + } + } + + spin_unlock_bh(&agg->lock); + rcu_read_unlock(); + + return run; + +err_unlock: + spin_unlock_bh(&agg->lock); + +err_unlock_rcu: + rcu_read_unlock(); + txinfo->flags &= ~IEEE80211_TX_CTL_AMPDU; + carl9170_tx_status(ar, skb, false); + ar->tx_dropped++; + return false; +} + +void carl9170_op_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct ar9170 *ar = hw->priv; + struct ieee80211_tx_info *info; + struct ieee80211_sta *sta = control->sta; + bool run; + + if (unlikely(!IS_STARTED(ar))) + goto err_free; + + info = IEEE80211_SKB_CB(skb); + + if (unlikely(carl9170_tx_prepare(ar, sta, skb))) + goto err_free; + + carl9170_tx_accounting(ar, skb); + /* + * from now on, one has to use carl9170_tx_status to free + * all ressouces which are associated with the frame. + */ + + if (sta) { + struct carl9170_sta_info *stai = (void *) sta->drv_priv; + atomic_inc(&stai->pending_frames); + } + + if (info->flags & IEEE80211_TX_CTL_AMPDU) { + /* to static code analyzers and reviewers: + * mac80211 guarantees that a valid "sta" + * reference is present, if a frame is to + * be part of an ampdu. Hence any extra + * sta == NULL checks are redundant in this + * special case. + */ + run = carl9170_tx_ampdu_queue(ar, sta, skb, info); + if (run) + carl9170_tx_ampdu(ar); + + } else { + unsigned int queue = skb_get_queue_mapping(skb); + + skb_queue_tail(&ar->tx_pending[queue], skb); + } + + carl9170_tx(ar); + return; + +err_free: + ar->tx_dropped++; + ieee80211_free_txskb(ar->hw, skb); +} + +void carl9170_tx_scheduler(struct ar9170 *ar) +{ + + if (ar->tx_ampdu_schedule) + carl9170_tx_ampdu(ar); + + if (ar->tx_schedule) + carl9170_tx(ar); +} + +/* caller has to take rcu_read_lock */ +static struct carl9170_vif_info *carl9170_pick_beaconing_vif(struct ar9170 *ar) +{ + struct carl9170_vif_info *cvif; + int i = 1; + + /* The AR9170 hardware has no fancy beacon queue or some + * other scheduling mechanism. So, the driver has to make + * due by setting the two beacon timers (pretbtt and tbtt) + * once and then swapping the beacon address in the HW's + * register file each time the pretbtt fires. + */ + + cvif = rcu_dereference(ar->beacon_iter); + if (ar->vifs > 0 && cvif) { + do { + list_for_each_entry_continue_rcu(cvif, &ar->vif_list, + list) { + if (cvif->active && cvif->enable_beacon) + goto out; + } + } while (ar->beacon_enabled && i--); + } + +out: + rcu_assign_pointer(ar->beacon_iter, cvif); + return cvif; +} + +static bool carl9170_tx_beacon_physet(struct ar9170 *ar, struct sk_buff *skb, + u32 *ht1, u32 *plcp) +{ + struct ieee80211_tx_info *txinfo; + struct ieee80211_tx_rate *rate; + unsigned int power, chains; + bool ht_rate; + + txinfo = IEEE80211_SKB_CB(skb); + rate = &txinfo->control.rates[0]; + ht_rate = !!(txinfo->control.rates[0].flags & IEEE80211_TX_RC_MCS); + carl9170_tx_rate_tpc_chains(ar, txinfo, rate, plcp, &power, &chains); + + *ht1 = AR9170_MAC_BCN_HT1_TX_ANT0; + if (chains == AR9170_TX_PHY_TXCHAIN_2) + *ht1 |= AR9170_MAC_BCN_HT1_TX_ANT1; + SET_VAL(AR9170_MAC_BCN_HT1_PWR_CTRL, *ht1, 7); + SET_VAL(AR9170_MAC_BCN_HT1_TPC, *ht1, power); + SET_VAL(AR9170_MAC_BCN_HT1_CHAIN_MASK, *ht1, chains); + + if (ht_rate) { + *ht1 |= AR9170_MAC_BCN_HT1_HT_EN; + if (rate->flags & IEEE80211_TX_RC_SHORT_GI) + *plcp |= AR9170_MAC_BCN_HT2_SGI; + + if (rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH) { + *ht1 |= AR9170_MAC_BCN_HT1_BWC_40M_SHARED; + *plcp |= AR9170_MAC_BCN_HT2_BW40; + } else if (rate->flags & IEEE80211_TX_RC_DUP_DATA) { + *ht1 |= AR9170_MAC_BCN_HT1_BWC_40M_DUP; + *plcp |= AR9170_MAC_BCN_HT2_BW40; + } + + SET_VAL(AR9170_MAC_BCN_HT2_LEN, *plcp, skb->len + FCS_LEN); + } else { + if (*plcp <= AR9170_TX_PHY_RATE_CCK_11M) + *plcp |= ((skb->len + FCS_LEN) << (3 + 16)) + 0x0400; + else + *plcp |= ((skb->len + FCS_LEN) << 16) + 0x0010; + } + + return ht_rate; +} + +int carl9170_update_beacon(struct ar9170 *ar, const bool submit) +{ + struct sk_buff *skb = NULL; + struct carl9170_vif_info *cvif; + __le32 *data, *old = NULL; + u32 word, ht1, plcp, off, addr, len; + int i = 0, err = 0; + bool ht_rate; + + rcu_read_lock(); + cvif = carl9170_pick_beaconing_vif(ar); + if (!cvif) + goto out_unlock; + + skb = ieee80211_beacon_get_tim(ar->hw, carl9170_get_vif(cvif), + NULL, NULL); + + if (!skb) { + err = -ENOMEM; + goto err_free; + } + + spin_lock_bh(&ar->beacon_lock); + data = (__le32 *)skb->data; + if (cvif->beacon) + old = (__le32 *)cvif->beacon->data; + + off = cvif->id * AR9170_MAC_BCN_LENGTH_MAX; + addr = ar->fw.beacon_addr + off; + len = roundup(skb->len + FCS_LEN, 4); + + if ((off + len) > ar->fw.beacon_max_len) { + if (net_ratelimit()) { + wiphy_err(ar->hw->wiphy, "beacon does not " + "fit into device memory!\n"); + } + err = -EINVAL; + goto err_unlock; + } + + if (len > AR9170_MAC_BCN_LENGTH_MAX) { + if (net_ratelimit()) { + wiphy_err(ar->hw->wiphy, "no support for beacons " + "bigger than %d (yours:%d).\n", + AR9170_MAC_BCN_LENGTH_MAX, len); + } + + err = -EMSGSIZE; + goto err_unlock; + } + + ht_rate = carl9170_tx_beacon_physet(ar, skb, &ht1, &plcp); + + carl9170_async_regwrite_begin(ar); + carl9170_async_regwrite(AR9170_MAC_REG_BCN_HT1, ht1); + if (ht_rate) + carl9170_async_regwrite(AR9170_MAC_REG_BCN_HT2, plcp); + else + carl9170_async_regwrite(AR9170_MAC_REG_BCN_PLCP, plcp); + + for (i = 0; i < DIV_ROUND_UP(skb->len, 4); i++) { + /* + * XXX: This accesses beyond skb data for up + * to the last 3 bytes!! + */ + + if (old && (data[i] == old[i])) + continue; + + word = le32_to_cpu(data[i]); + carl9170_async_regwrite(addr + 4 * i, word); + } + carl9170_async_regwrite_finish(); + + dev_kfree_skb_any(cvif->beacon); + cvif->beacon = NULL; + + err = carl9170_async_regwrite_result(); + if (!err) + cvif->beacon = skb; + spin_unlock_bh(&ar->beacon_lock); + if (err) + goto err_free; + + if (submit) { + err = carl9170_bcn_ctrl(ar, cvif->id, + CARL9170_BCN_CTRL_CAB_TRIGGER, + addr, skb->len + FCS_LEN); + + if (err) + goto err_free; + } +out_unlock: + rcu_read_unlock(); + return 0; + +err_unlock: + spin_unlock_bh(&ar->beacon_lock); + +err_free: + rcu_read_unlock(); + dev_kfree_skb_any(skb); + return err; +} diff --git a/drivers/net/wireless/ath/carl9170/usb.c b/drivers/net/wireless/ath/carl9170/usb.c new file mode 100644 index 0000000..ac1ec3b --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/usb.c @@ -0,0 +1,1174 @@ +/* + * Atheros CARL9170 driver + * + * USB - frontend + * + * Copyright 2008, Johannes Berg + * Copyright 2009, 2010, Christian Lamparter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * Copyright (c) 2007-2008 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "carl9170.h" +#include "cmd.h" +#include "hw.h" +#include "fwcmd.h" + +MODULE_AUTHOR("Johannes Berg "); +MODULE_AUTHOR("Christian Lamparter "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Atheros AR9170 802.11n USB wireless"); +MODULE_FIRMWARE(CARL9170FW_NAME); +MODULE_ALIAS("ar9170usb"); +MODULE_ALIAS("arusb_lnx"); + +/* + * Note: + * + * Always update our wiki's device list (located at: + * http://wireless.kernel.org/en/users/Drivers/ar9170/devices ), + * whenever you add a new device. + */ +static struct usb_device_id carl9170_usb_ids[] = { + /* Atheros 9170 */ + { USB_DEVICE(0x0cf3, 0x9170) }, + /* Atheros TG121N */ + { USB_DEVICE(0x0cf3, 0x1001) }, + /* TP-Link TL-WN821N v2 */ + { USB_DEVICE(0x0cf3, 0x1002), .driver_info = CARL9170_WPS_BUTTON | + CARL9170_ONE_LED }, + /* 3Com Dual Band 802.11n USB Adapter */ + { USB_DEVICE(0x0cf3, 0x1010) }, + /* H3C Dual Band 802.11n USB Adapter */ + { USB_DEVICE(0x0cf3, 0x1011) }, + /* Cace Airpcap NX */ + { USB_DEVICE(0xcace, 0x0300) }, + /* D-Link DWA 160 A1 */ + { USB_DEVICE(0x07d1, 0x3c10) }, + /* D-Link DWA 160 A2 */ + { USB_DEVICE(0x07d1, 0x3a09) }, + /* D-Link DWA 130 D */ + { USB_DEVICE(0x07d1, 0x3a0f) }, + /* Netgear WNA1000 */ + { USB_DEVICE(0x0846, 0x9040) }, + /* Netgear WNDA3100 (v1) */ + { USB_DEVICE(0x0846, 0x9010) }, + /* Netgear WN111 v2 */ + { USB_DEVICE(0x0846, 0x9001), .driver_info = CARL9170_ONE_LED }, + /* Zydas ZD1221 */ + { USB_DEVICE(0x0ace, 0x1221) }, + /* Proxim ORiNOCO 802.11n USB */ + { USB_DEVICE(0x1435, 0x0804) }, + /* WNC Generic 11n USB Dongle */ + { USB_DEVICE(0x1435, 0x0326) }, + /* ZyXEL NWD271N */ + { USB_DEVICE(0x0586, 0x3417) }, + /* Z-Com UB81 BG */ + { USB_DEVICE(0x0cde, 0x0023) }, + /* Z-Com UB82 ABG */ + { USB_DEVICE(0x0cde, 0x0026) }, + /* Sphairon Homelink 1202 */ + { USB_DEVICE(0x0cde, 0x0027) }, + /* Arcadyan WN7512 */ + { USB_DEVICE(0x083a, 0xf522) }, + /* Planex GWUS300 */ + { USB_DEVICE(0x2019, 0x5304) }, + /* IO-Data WNGDNUS2 */ + { USB_DEVICE(0x04bb, 0x093f) }, + /* NEC WL300NU-G */ + { USB_DEVICE(0x0409, 0x0249) }, + /* NEC WL300NU-AG */ + { USB_DEVICE(0x0409, 0x02b4) }, + /* AVM FRITZ!WLAN USB Stick N */ + { USB_DEVICE(0x057c, 0x8401) }, + /* AVM FRITZ!WLAN USB Stick N 2.4 */ + { USB_DEVICE(0x057c, 0x8402) }, + /* Qwest/Actiontec 802AIN Wireless N USB Network Adapter */ + { USB_DEVICE(0x1668, 0x1200) }, + /* Airlive X.USB a/b/g/n */ + { USB_DEVICE(0x1b75, 0x9170) }, + + /* terminate */ + {} +}; +MODULE_DEVICE_TABLE(usb, carl9170_usb_ids); + +static void carl9170_usb_submit_data_urb(struct ar9170 *ar) +{ + struct urb *urb; + int err; + + if (atomic_inc_return(&ar->tx_anch_urbs) > AR9170_NUM_TX_URBS) + goto err_acc; + + urb = usb_get_from_anchor(&ar->tx_wait); + if (!urb) + goto err_acc; + + usb_anchor_urb(urb, &ar->tx_anch); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(err)) { + if (net_ratelimit()) { + dev_err(&ar->udev->dev, "tx submit failed (%d)\n", + urb->status); + } + + usb_unanchor_urb(urb); + usb_anchor_urb(urb, &ar->tx_err); + } + + usb_free_urb(urb); + + if (likely(err == 0)) + return; + +err_acc: + atomic_dec(&ar->tx_anch_urbs); +} + +static void carl9170_usb_tx_data_complete(struct urb *urb) +{ + struct ar9170 *ar = usb_get_intfdata(usb_ifnum_to_if(urb->dev, 0)); + + if (WARN_ON_ONCE(!ar)) { + dev_kfree_skb_irq(urb->context); + return; + } + + atomic_dec(&ar->tx_anch_urbs); + + switch (urb->status) { + /* everything is fine */ + case 0: + carl9170_tx_callback(ar, (void *)urb->context); + break; + + /* disconnect */ + case -ENOENT: + case -ECONNRESET: + case -ENODEV: + case -ESHUTDOWN: + /* + * Defer the frame clean-up to the tasklet worker. + * This is necessary, because carl9170_tx_drop + * does not work in an irqsave context. + */ + usb_anchor_urb(urb, &ar->tx_err); + return; + + /* a random transmission error has occurred? */ + default: + if (net_ratelimit()) { + dev_err(&ar->udev->dev, "tx failed (%d)\n", + urb->status); + } + + usb_anchor_urb(urb, &ar->tx_err); + break; + } + + if (likely(IS_STARTED(ar))) + carl9170_usb_submit_data_urb(ar); +} + +static int carl9170_usb_submit_cmd_urb(struct ar9170 *ar) +{ + struct urb *urb; + int err; + + if (atomic_inc_return(&ar->tx_cmd_urbs) != 1) { + atomic_dec(&ar->tx_cmd_urbs); + return 0; + } + + urb = usb_get_from_anchor(&ar->tx_cmd); + if (!urb) { + atomic_dec(&ar->tx_cmd_urbs); + return 0; + } + + usb_anchor_urb(urb, &ar->tx_anch); + err = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(err)) { + usb_unanchor_urb(urb); + atomic_dec(&ar->tx_cmd_urbs); + } + usb_free_urb(urb); + + return err; +} + +static void carl9170_usb_cmd_complete(struct urb *urb) +{ + struct ar9170 *ar = urb->context; + int err = 0; + + if (WARN_ON_ONCE(!ar)) + return; + + atomic_dec(&ar->tx_cmd_urbs); + + switch (urb->status) { + /* everything is fine */ + case 0: + break; + + /* disconnect */ + case -ENOENT: + case -ECONNRESET: + case -ENODEV: + case -ESHUTDOWN: + return; + + default: + err = urb->status; + break; + } + + if (!IS_INITIALIZED(ar)) + return; + + if (err) + dev_err(&ar->udev->dev, "submit cmd cb failed (%d).\n", err); + + err = carl9170_usb_submit_cmd_urb(ar); + if (err) + dev_err(&ar->udev->dev, "submit cmd failed (%d).\n", err); +} + +static void carl9170_usb_rx_irq_complete(struct urb *urb) +{ + struct ar9170 *ar = urb->context; + + if (WARN_ON_ONCE(!ar)) + return; + + switch (urb->status) { + /* everything is fine */ + case 0: + break; + + /* disconnect */ + case -ENOENT: + case -ECONNRESET: + case -ENODEV: + case -ESHUTDOWN: + return; + + default: + goto resubmit; + } + + /* + * While the carl9170 firmware does not use this EP, the + * firmware loader in the EEPROM unfortunately does. + * Therefore we need to be ready to handle out-of-band + * responses and traps in case the firmware crashed and + * the loader took over again. + */ + carl9170_handle_command_response(ar, urb->transfer_buffer, + urb->actual_length); + +resubmit: + usb_anchor_urb(urb, &ar->rx_anch); + if (unlikely(usb_submit_urb(urb, GFP_ATOMIC))) + usb_unanchor_urb(urb); +} + +static int carl9170_usb_submit_rx_urb(struct ar9170 *ar, gfp_t gfp) +{ + struct urb *urb; + int err = 0, runs = 0; + + while ((atomic_read(&ar->rx_anch_urbs) < AR9170_NUM_RX_URBS) && + (runs++ < AR9170_NUM_RX_URBS)) { + err = -ENOSPC; + urb = usb_get_from_anchor(&ar->rx_pool); + if (urb) { + usb_anchor_urb(urb, &ar->rx_anch); + err = usb_submit_urb(urb, gfp); + if (unlikely(err)) { + usb_unanchor_urb(urb); + usb_anchor_urb(urb, &ar->rx_pool); + } else { + atomic_dec(&ar->rx_pool_urbs); + atomic_inc(&ar->rx_anch_urbs); + } + usb_free_urb(urb); + } + } + + return err; +} + +static void carl9170_usb_rx_work(struct ar9170 *ar) +{ + struct urb *urb; + int i; + + for (i = 0; i < AR9170_NUM_RX_URBS_POOL; i++) { + urb = usb_get_from_anchor(&ar->rx_work); + if (!urb) + break; + + atomic_dec(&ar->rx_work_urbs); + if (IS_INITIALIZED(ar)) { + carl9170_rx(ar, urb->transfer_buffer, + urb->actual_length); + } + + usb_anchor_urb(urb, &ar->rx_pool); + atomic_inc(&ar->rx_pool_urbs); + + usb_free_urb(urb); + + carl9170_usb_submit_rx_urb(ar, GFP_ATOMIC); + } +} + +void carl9170_usb_handle_tx_err(struct ar9170 *ar) +{ + struct urb *urb; + + while ((urb = usb_get_from_anchor(&ar->tx_err))) { + struct sk_buff *skb = (void *)urb->context; + + carl9170_tx_drop(ar, skb); + carl9170_tx_callback(ar, skb); + usb_free_urb(urb); + } +} + +static void carl9170_usb_tasklet(unsigned long data) +{ + struct ar9170 *ar = (struct ar9170 *) data; + + if (!IS_INITIALIZED(ar)) + return; + + carl9170_usb_rx_work(ar); + + /* + * Strictly speaking: The tx scheduler is not part of the USB system. + * But the rx worker returns frames back to the mac80211-stack and + * this is the _perfect_ place to generate the next transmissions. + */ + if (IS_STARTED(ar)) + carl9170_tx_scheduler(ar); +} + +static void carl9170_usb_rx_complete(struct urb *urb) +{ + struct ar9170 *ar = (struct ar9170 *)urb->context; + int err; + + if (WARN_ON_ONCE(!ar)) + return; + + atomic_dec(&ar->rx_anch_urbs); + + switch (urb->status) { + case 0: + /* rx path */ + usb_anchor_urb(urb, &ar->rx_work); + atomic_inc(&ar->rx_work_urbs); + break; + + case -ENOENT: + case -ECONNRESET: + case -ENODEV: + case -ESHUTDOWN: + /* handle disconnect events*/ + return; + + default: + /* handle all other errors */ + usb_anchor_urb(urb, &ar->rx_pool); + atomic_inc(&ar->rx_pool_urbs); + break; + } + + err = carl9170_usb_submit_rx_urb(ar, GFP_ATOMIC); + if (unlikely(err)) { + /* + * usb_submit_rx_urb reported a problem. + * In case this is due to a rx buffer shortage, + * elevate the tasklet worker priority to + * the highest available level. + */ + tasklet_hi_schedule(&ar->usb_tasklet); + + if (atomic_read(&ar->rx_anch_urbs) == 0) { + /* + * The system is too slow to cope with + * the enormous workload. We have simply + * run out of active rx urbs and this + * unfortunately leads to an unpredictable + * device. + */ + + ieee80211_queue_work(ar->hw, &ar->ping_work); + } + } else { + /* + * Using anything less than _high_ priority absolutely + * kills the rx performance my UP-System... + */ + tasklet_hi_schedule(&ar->usb_tasklet); + } +} + +static struct urb *carl9170_usb_alloc_rx_urb(struct ar9170 *ar, gfp_t gfp) +{ + struct urb *urb; + void *buf; + + buf = kmalloc(ar->fw.rx_size, gfp); + if (!buf) + return NULL; + + urb = usb_alloc_urb(0, gfp); + if (!urb) { + kfree(buf); + return NULL; + } + + usb_fill_bulk_urb(urb, ar->udev, usb_rcvbulkpipe(ar->udev, + AR9170_USB_EP_RX), buf, ar->fw.rx_size, + carl9170_usb_rx_complete, ar); + + urb->transfer_flags |= URB_FREE_BUFFER; + + return urb; +} + +static int carl9170_usb_send_rx_irq_urb(struct ar9170 *ar) +{ + struct urb *urb = NULL; + void *ibuf; + int err = -ENOMEM; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + goto out; + + ibuf = kmalloc(AR9170_USB_EP_CTRL_MAX, GFP_KERNEL); + if (!ibuf) + goto out; + + usb_fill_int_urb(urb, ar->udev, usb_rcvintpipe(ar->udev, + AR9170_USB_EP_IRQ), ibuf, AR9170_USB_EP_CTRL_MAX, + carl9170_usb_rx_irq_complete, ar, 1); + + urb->transfer_flags |= URB_FREE_BUFFER; + + usb_anchor_urb(urb, &ar->rx_anch); + err = usb_submit_urb(urb, GFP_KERNEL); + if (err) + usb_unanchor_urb(urb); + +out: + usb_free_urb(urb); + return err; +} + +static int carl9170_usb_init_rx_bulk_urbs(struct ar9170 *ar) +{ + struct urb *urb; + int i, err = -EINVAL; + + /* + * The driver actively maintains a second shadow + * pool for inactive, but fully-prepared rx urbs. + * + * The pool should help the driver to master huge + * workload spikes without running the risk of + * undersupplying the hardware or wasting time by + * processing rx data (streams) inside the urb + * completion (hardirq context). + */ + for (i = 0; i < AR9170_NUM_RX_URBS_POOL; i++) { + urb = carl9170_usb_alloc_rx_urb(ar, GFP_KERNEL); + if (!urb) { + err = -ENOMEM; + goto err_out; + } + + usb_anchor_urb(urb, &ar->rx_pool); + atomic_inc(&ar->rx_pool_urbs); + usb_free_urb(urb); + } + + err = carl9170_usb_submit_rx_urb(ar, GFP_KERNEL); + if (err) + goto err_out; + + /* the device now waiting for the firmware. */ + carl9170_set_state_when(ar, CARL9170_STOPPED, CARL9170_IDLE); + return 0; + +err_out: + + usb_scuttle_anchored_urbs(&ar->rx_pool); + usb_scuttle_anchored_urbs(&ar->rx_work); + usb_kill_anchored_urbs(&ar->rx_anch); + return err; +} + +static int carl9170_usb_flush(struct ar9170 *ar) +{ + struct urb *urb; + int ret, err = 0; + + while ((urb = usb_get_from_anchor(&ar->tx_wait))) { + struct sk_buff *skb = (void *)urb->context; + carl9170_tx_drop(ar, skb); + carl9170_tx_callback(ar, skb); + usb_free_urb(urb); + } + + ret = usb_wait_anchor_empty_timeout(&ar->tx_cmd, 1000); + if (ret == 0) + err = -ETIMEDOUT; + + /* lets wait a while until the tx - queues are dried out */ + ret = usb_wait_anchor_empty_timeout(&ar->tx_anch, 1000); + if (ret == 0) + err = -ETIMEDOUT; + + usb_kill_anchored_urbs(&ar->tx_anch); + carl9170_usb_handle_tx_err(ar); + + return err; +} + +static void carl9170_usb_cancel_urbs(struct ar9170 *ar) +{ + int err; + + carl9170_set_state(ar, CARL9170_UNKNOWN_STATE); + + err = carl9170_usb_flush(ar); + if (err) + dev_err(&ar->udev->dev, "stuck tx urbs!\n"); + + usb_poison_anchored_urbs(&ar->tx_anch); + carl9170_usb_handle_tx_err(ar); + usb_poison_anchored_urbs(&ar->rx_anch); + + tasklet_kill(&ar->usb_tasklet); + + usb_scuttle_anchored_urbs(&ar->rx_work); + usb_scuttle_anchored_urbs(&ar->rx_pool); + usb_scuttle_anchored_urbs(&ar->tx_cmd); +} + +int __carl9170_exec_cmd(struct ar9170 *ar, struct carl9170_cmd *cmd, + const bool free_buf) +{ + struct urb *urb; + int err = 0; + + if (!IS_INITIALIZED(ar)) { + err = -EPERM; + goto err_free; + } + + if (WARN_ON(cmd->hdr.len > CARL9170_MAX_CMD_LEN - 4)) { + err = -EINVAL; + goto err_free; + } + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + err = -ENOMEM; + goto err_free; + } + + usb_fill_int_urb(urb, ar->udev, usb_sndintpipe(ar->udev, + AR9170_USB_EP_CMD), cmd, cmd->hdr.len + 4, + carl9170_usb_cmd_complete, ar, 1); + + if (free_buf) + urb->transfer_flags |= URB_FREE_BUFFER; + + usb_anchor_urb(urb, &ar->tx_cmd); + usb_free_urb(urb); + + return carl9170_usb_submit_cmd_urb(ar); + +err_free: + if (free_buf) + kfree(cmd); + + return err; +} + +int carl9170_exec_cmd(struct ar9170 *ar, const enum carl9170_cmd_oids cmd, + unsigned int plen, void *payload, unsigned int outlen, void *out) +{ + int err = -ENOMEM; + + if (!IS_ACCEPTING_CMD(ar)) + return -EIO; + + if (!(cmd & CARL9170_CMD_ASYNC_FLAG)) + might_sleep(); + + ar->cmd.hdr.len = plen; + ar->cmd.hdr.cmd = cmd; + /* writing multiple regs fills this buffer already */ + if (plen && payload != (u8 *)(ar->cmd.data)) + memcpy(ar->cmd.data, payload, plen); + + spin_lock_bh(&ar->cmd_lock); + ar->readbuf = (u8 *)out; + ar->readlen = outlen; + spin_unlock_bh(&ar->cmd_lock); + + err = __carl9170_exec_cmd(ar, &ar->cmd, false); + + if (!(cmd & CARL9170_CMD_ASYNC_FLAG)) { + err = wait_for_completion_timeout(&ar->cmd_wait, HZ); + if (err == 0) { + err = -ETIMEDOUT; + goto err_unbuf; + } + + if (ar->readlen != outlen) { + err = -EMSGSIZE; + goto err_unbuf; + } + } + + return 0; + +err_unbuf: + /* Maybe the device was removed in the moment we were waiting? */ + if (IS_STARTED(ar)) { + dev_err(&ar->udev->dev, "no command feedback " + "received (%d).\n", err); + + /* provide some maybe useful debug information */ + print_hex_dump_bytes("carl9170 cmd: ", DUMP_PREFIX_NONE, + &ar->cmd, plen + 4); + + carl9170_restart(ar, CARL9170_RR_COMMAND_TIMEOUT); + } + + /* invalidate to avoid completing the next command prematurely */ + spin_lock_bh(&ar->cmd_lock); + ar->readbuf = NULL; + ar->readlen = 0; + spin_unlock_bh(&ar->cmd_lock); + + return err; +} + +void carl9170_usb_tx(struct ar9170 *ar, struct sk_buff *skb) +{ + struct urb *urb; + struct ar9170_stream *tx_stream; + void *data; + unsigned int len; + + if (!IS_STARTED(ar)) + goto err_drop; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) + goto err_drop; + + if (ar->fw.tx_stream) { + tx_stream = (void *) (skb->data - sizeof(*tx_stream)); + + len = skb->len + sizeof(*tx_stream); + tx_stream->length = cpu_to_le16(len); + tx_stream->tag = cpu_to_le16(AR9170_TX_STREAM_TAG); + data = tx_stream; + } else { + data = skb->data; + len = skb->len; + } + + usb_fill_bulk_urb(urb, ar->udev, usb_sndbulkpipe(ar->udev, + AR9170_USB_EP_TX), data, len, + carl9170_usb_tx_data_complete, skb); + + urb->transfer_flags |= URB_ZERO_PACKET; + + usb_anchor_urb(urb, &ar->tx_wait); + + usb_free_urb(urb); + + carl9170_usb_submit_data_urb(ar); + return; + +err_drop: + carl9170_tx_drop(ar, skb); + carl9170_tx_callback(ar, skb); +} + +static void carl9170_release_firmware(struct ar9170 *ar) +{ + if (ar->fw.fw) { + release_firmware(ar->fw.fw); + memset(&ar->fw, 0, sizeof(ar->fw)); + } +} + +void carl9170_usb_stop(struct ar9170 *ar) +{ + int ret; + + carl9170_set_state_when(ar, CARL9170_IDLE, CARL9170_STOPPED); + + ret = carl9170_usb_flush(ar); + if (ret) + dev_err(&ar->udev->dev, "kill pending tx urbs.\n"); + + usb_poison_anchored_urbs(&ar->tx_anch); + carl9170_usb_handle_tx_err(ar); + + /* kill any pending command */ + spin_lock_bh(&ar->cmd_lock); + ar->readlen = 0; + spin_unlock_bh(&ar->cmd_lock); + complete_all(&ar->cmd_wait); + + /* This is required to prevent an early completion on _start */ + INIT_COMPLETION(ar->cmd_wait); + + /* + * Note: + * So far we freed all tx urbs, but we won't dare to touch any rx urbs. + * Else we would end up with a unresponsive device... + */ +} + +int carl9170_usb_open(struct ar9170 *ar) +{ + usb_unpoison_anchored_urbs(&ar->tx_anch); + + carl9170_set_state_when(ar, CARL9170_STOPPED, CARL9170_IDLE); + return 0; +} + +static int carl9170_usb_load_firmware(struct ar9170 *ar) +{ + const u8 *data; + u8 *buf; + unsigned int transfer; + size_t len; + u32 addr; + int err = 0; + + buf = kmalloc(4096, GFP_KERNEL); + if (!buf) { + err = -ENOMEM; + goto err_out; + } + + data = ar->fw.fw->data; + len = ar->fw.fw->size; + addr = ar->fw.address; + + /* this removes the miniboot image */ + data += ar->fw.offset; + len -= ar->fw.offset; + + while (len) { + transfer = min_t(unsigned int, len, 4096u); + memcpy(buf, data, transfer); + + err = usb_control_msg(ar->udev, usb_sndctrlpipe(ar->udev, 0), + 0x30 /* FW DL */, 0x40 | USB_DIR_OUT, + addr >> 8, 0, buf, transfer, 100); + + if (err < 0) { + kfree(buf); + goto err_out; + } + + len -= transfer; + data += transfer; + addr += transfer; + } + kfree(buf); + + err = usb_control_msg(ar->udev, usb_sndctrlpipe(ar->udev, 0), + 0x31 /* FW DL COMPLETE */, + 0x40 | USB_DIR_OUT, 0, 0, NULL, 0, 200); + + if (wait_for_completion_timeout(&ar->fw_boot_wait, HZ) == 0) { + err = -ETIMEDOUT; + goto err_out; + } + + err = carl9170_echo_test(ar, 0x4a110123); + if (err) + goto err_out; + + /* now, start the command response counter */ + ar->cmd_seq = -1; + + return 0; + +err_out: + dev_err(&ar->udev->dev, "firmware upload failed (%d).\n", err); + return err; +} + +int carl9170_usb_restart(struct ar9170 *ar) +{ + int err = 0; + + if (ar->intf->condition != USB_INTERFACE_BOUND) + return 0; + + /* + * Disable the command response sequence counter check. + * We already know that the device/firmware is in a bad state. + * So, no extra points are awarded to anyone who reminds the + * driver about that. + */ + ar->cmd_seq = -2; + + err = carl9170_reboot(ar); + + carl9170_usb_stop(ar); + + if (err) + goto err_out; + + tasklet_schedule(&ar->usb_tasklet); + + /* The reboot procedure can take quite a while to complete. */ + msleep(1100); + + err = carl9170_usb_open(ar); + if (err) + goto err_out; + + err = carl9170_usb_load_firmware(ar); + if (err) + goto err_out; + + return 0; + +err_out: + carl9170_usb_cancel_urbs(ar); + return err; +} + +void carl9170_usb_reset(struct ar9170 *ar) +{ + /* + * This is the last resort to get the device going again + * without any *user replugging action*. + * + * But there is a catch: usb_reset really is like a physical + * *reconnect*. The mac80211 state will be lost in the process. + * Therefore a userspace application, which is monitoring + * the link must step in. + */ + carl9170_usb_cancel_urbs(ar); + + carl9170_usb_stop(ar); + + usb_queue_reset_device(ar->intf); +} + +static int carl9170_usb_init_device(struct ar9170 *ar) +{ + int err; + + /* + * The carl9170 firmware let's the driver know when it's + * ready for action. But we have to be prepared to gracefully + * handle all spurious [flushed] messages after each (re-)boot. + * Thus the command response counter remains disabled until it + * can be safely synchronized. + */ + ar->cmd_seq = -2; + + err = carl9170_usb_send_rx_irq_urb(ar); + if (err) + goto err_out; + + err = carl9170_usb_init_rx_bulk_urbs(ar); + if (err) + goto err_unrx; + + err = carl9170_usb_open(ar); + if (err) + goto err_unrx; + + mutex_lock(&ar->mutex); + err = carl9170_usb_load_firmware(ar); + mutex_unlock(&ar->mutex); + if (err) + goto err_stop; + + return 0; + +err_stop: + carl9170_usb_stop(ar); + +err_unrx: + carl9170_usb_cancel_urbs(ar); + +err_out: + return err; +} + +static void carl9170_usb_firmware_failed(struct ar9170 *ar) +{ + struct device *parent = ar->udev->dev.parent; + struct usb_device *udev; + + /* + * Store a copy of the usb_device pointer locally. + * This is because device_release_driver initiates + * carl9170_usb_disconnect, which in turn frees our + * driver context (ar). + */ + udev = ar->udev; + + complete(&ar->fw_load_wait); + + /* unbind anything failed */ + if (parent) + device_lock(parent); + + device_release_driver(&udev->dev); + if (parent) + device_unlock(parent); + + usb_put_dev(udev); +} + +static void carl9170_usb_firmware_finish(struct ar9170 *ar) +{ + int err; + + err = carl9170_parse_firmware(ar); + if (err) + goto err_freefw; + + err = carl9170_usb_init_device(ar); + if (err) + goto err_freefw; + + err = carl9170_register(ar); + + carl9170_usb_stop(ar); + if (err) + goto err_unrx; + + complete(&ar->fw_load_wait); + usb_put_dev(ar->udev); + return; + +err_unrx: + carl9170_usb_cancel_urbs(ar); + +err_freefw: + carl9170_release_firmware(ar); + carl9170_usb_firmware_failed(ar); +} + +static void carl9170_usb_firmware_step2(const struct firmware *fw, + void *context) +{ + struct ar9170 *ar = context; + + if (fw) { + ar->fw.fw = fw; + carl9170_usb_firmware_finish(ar); + return; + } + + dev_err(&ar->udev->dev, "firmware not found.\n"); + carl9170_usb_firmware_failed(ar); +} + +static int carl9170_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct ar9170 *ar; + struct usb_device *udev; + int err; + + err = usb_reset_device(interface_to_usbdev(intf)); + if (err) + return err; + + ar = carl9170_alloc(sizeof(*ar)); + if (IS_ERR(ar)) + return PTR_ERR(ar); + + udev = interface_to_usbdev(intf); + usb_get_dev(udev); + ar->udev = udev; + ar->intf = intf; + ar->features = id->driver_info; + + usb_set_intfdata(intf, ar); + SET_IEEE80211_DEV(ar->hw, &intf->dev); + + init_usb_anchor(&ar->rx_anch); + init_usb_anchor(&ar->rx_pool); + init_usb_anchor(&ar->rx_work); + init_usb_anchor(&ar->tx_wait); + init_usb_anchor(&ar->tx_anch); + init_usb_anchor(&ar->tx_cmd); + init_usb_anchor(&ar->tx_err); + init_completion(&ar->cmd_wait); + init_completion(&ar->fw_boot_wait); + init_completion(&ar->fw_load_wait); + tasklet_init(&ar->usb_tasklet, carl9170_usb_tasklet, + (unsigned long)ar); + + atomic_set(&ar->tx_cmd_urbs, 0); + atomic_set(&ar->tx_anch_urbs, 0); + atomic_set(&ar->rx_work_urbs, 0); + atomic_set(&ar->rx_anch_urbs, 0); + atomic_set(&ar->rx_pool_urbs, 0); + + usb_get_dev(ar->udev); + + carl9170_set_state(ar, CARL9170_STOPPED); + + return request_firmware_nowait(THIS_MODULE, 1, CARL9170FW_NAME, + &ar->udev->dev, GFP_KERNEL, ar, carl9170_usb_firmware_step2); +} + +static void carl9170_usb_disconnect(struct usb_interface *intf) +{ + struct ar9170 *ar = usb_get_intfdata(intf); + struct usb_device *udev; + + if (WARN_ON(!ar)) + return; + + udev = ar->udev; + wait_for_completion(&ar->fw_load_wait); + + if (IS_INITIALIZED(ar)) { + carl9170_reboot(ar); + carl9170_usb_stop(ar); + } + + carl9170_usb_cancel_urbs(ar); + carl9170_unregister(ar); + + usb_set_intfdata(intf, NULL); + + carl9170_release_firmware(ar); + carl9170_free(ar); + usb_put_dev(udev); +} + +#ifdef CONFIG_PM +static int carl9170_usb_suspend(struct usb_interface *intf, + pm_message_t message) +{ + struct ar9170 *ar = usb_get_intfdata(intf); + + if (!ar) + return -ENODEV; + + carl9170_usb_cancel_urbs(ar); + + return 0; +} + +static int carl9170_usb_resume(struct usb_interface *intf) +{ + struct ar9170 *ar = usb_get_intfdata(intf); + int err; + + if (!ar) + return -ENODEV; + + usb_unpoison_anchored_urbs(&ar->rx_anch); + carl9170_set_state(ar, CARL9170_STOPPED); + + /* + * The USB documentation demands that [for suspend] all traffic + * to and from the device has to stop. This would be fine, but + * there's a catch: the device[usb phy] does not come back. + * + * Upon resume the firmware will "kill" itself and the + * boot-code sorts out the magic voodoo. + * Not very nice, but there's not much what could go wrong. + */ + msleep(1100); + + err = carl9170_usb_init_device(ar); + if (err) + goto err_unrx; + + return 0; + +err_unrx: + carl9170_usb_cancel_urbs(ar); + + return err; +} +#endif /* CONFIG_PM */ + +static struct usb_driver carl9170_driver = { + .name = KBUILD_MODNAME, + .probe = carl9170_usb_probe, + .disconnect = carl9170_usb_disconnect, + .id_table = carl9170_usb_ids, + .soft_unbind = 1, +#ifdef CONFIG_PM + .suspend = carl9170_usb_suspend, + .resume = carl9170_usb_resume, + .reset_resume = carl9170_usb_resume, +#endif /* CONFIG_PM */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +module_usb_driver(carl9170_driver); diff --git a/drivers/net/wireless/ath/carl9170/version.h b/drivers/net/wireless/ath/carl9170/version.h new file mode 100644 index 0000000..2282847 --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/version.h @@ -0,0 +1,7 @@ +#ifndef __CARL9170_SHARED_VERSION_H +#define __CARL9170_SHARED_VERSION_H +#define CARL9170FW_VERSION_YEAR 12 +#define CARL9170FW_VERSION_MONTH 12 +#define CARL9170FW_VERSION_DAY 15 +#define CARL9170FW_VERSION_GIT "1.9.7" +#endif /* __CARL9170_SHARED_VERSION_H */ diff --git a/drivers/net/wireless/ath/carl9170/wlan.h b/drivers/net/wireless/ath/carl9170/wlan.h new file mode 100644 index 0000000..ea17995 --- /dev/null +++ b/drivers/net/wireless/ath/carl9170/wlan.h @@ -0,0 +1,435 @@ +/* + * Shared Atheros AR9170 Header + * + * RX/TX meta descriptor format + * + * Copyright 2008, Johannes Berg + * Copyright 2009-2011 Christian Lamparter + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, see + * http://www.gnu.org/licenses/. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * Copyright (c) 2007-2008 Atheros Communications, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __CARL9170_SHARED_WLAN_H +#define __CARL9170_SHARED_WLAN_H + +#include "fwcmd.h" + +#define AR9170_RX_PHY_RATE_CCK_1M 0x0a +#define AR9170_RX_PHY_RATE_CCK_2M 0x14 +#define AR9170_RX_PHY_RATE_CCK_5M 0x37 +#define AR9170_RX_PHY_RATE_CCK_11M 0x6e + +#define AR9170_ENC_ALG_NONE 0x0 +#define AR9170_ENC_ALG_WEP64 0x1 +#define AR9170_ENC_ALG_TKIP 0x2 +#define AR9170_ENC_ALG_AESCCMP 0x4 +#define AR9170_ENC_ALG_WEP128 0x5 +#define AR9170_ENC_ALG_WEP256 0x6 +#define AR9170_ENC_ALG_CENC 0x7 + +#define AR9170_RX_ENC_SOFTWARE 0x8 + +#define AR9170_RX_STATUS_MODULATION 0x03 +#define AR9170_RX_STATUS_MODULATION_S 0 +#define AR9170_RX_STATUS_MODULATION_CCK 0x00 +#define AR9170_RX_STATUS_MODULATION_OFDM 0x01 +#define AR9170_RX_STATUS_MODULATION_HT 0x02 +#define AR9170_RX_STATUS_MODULATION_DUPOFDM 0x03 + +/* depends on modulation */ +#define AR9170_RX_STATUS_SHORT_PREAMBLE 0x08 +#define AR9170_RX_STATUS_GREENFIELD 0x08 + +#define AR9170_RX_STATUS_MPDU 0x30 +#define AR9170_RX_STATUS_MPDU_S 4 +#define AR9170_RX_STATUS_MPDU_SINGLE 0x00 +#define AR9170_RX_STATUS_MPDU_FIRST 0x20 +#define AR9170_RX_STATUS_MPDU_MIDDLE 0x30 +#define AR9170_RX_STATUS_MPDU_LAST 0x10 + +#define AR9170_RX_STATUS_CONT_AGGR 0x40 +#define AR9170_RX_STATUS_TOTAL_ERROR 0x80 + +#define AR9170_RX_ERROR_RXTO 0x01 +#define AR9170_RX_ERROR_OVERRUN 0x02 +#define AR9170_RX_ERROR_DECRYPT 0x04 +#define AR9170_RX_ERROR_FCS 0x08 +#define AR9170_RX_ERROR_WRONG_RA 0x10 +#define AR9170_RX_ERROR_PLCP 0x20 +#define AR9170_RX_ERROR_MMIC 0x40 + +/* these are either-or */ +#define AR9170_TX_MAC_PROT_RTS 0x0001 +#define AR9170_TX_MAC_PROT_CTS 0x0002 +#define AR9170_TX_MAC_PROT 0x0003 + +#define AR9170_TX_MAC_NO_ACK 0x0004 +/* if unset, MAC will only do SIFS space before frame */ +#define AR9170_TX_MAC_BACKOFF 0x0008 +#define AR9170_TX_MAC_BURST 0x0010 +#define AR9170_TX_MAC_AGGR 0x0020 + +/* encryption is a two-bit field */ +#define AR9170_TX_MAC_ENCR_NONE 0x0000 +#define AR9170_TX_MAC_ENCR_RC4 0x0040 +#define AR9170_TX_MAC_ENCR_CENC 0x0080 +#define AR9170_TX_MAC_ENCR_AES 0x00c0 + +#define AR9170_TX_MAC_MMIC 0x0100 +#define AR9170_TX_MAC_HW_DURATION 0x0200 +#define AR9170_TX_MAC_QOS_S 10 +#define AR9170_TX_MAC_QOS 0x0c00 +#define AR9170_TX_MAC_DISABLE_TXOP 0x1000 +#define AR9170_TX_MAC_TXOP_RIFS 0x2000 +#define AR9170_TX_MAC_IMM_BA 0x4000 + +/* either-or */ +#define AR9170_TX_PHY_MOD_CCK 0x00000000 +#define AR9170_TX_PHY_MOD_OFDM 0x00000001 +#define AR9170_TX_PHY_MOD_HT 0x00000002 + +/* depends on modulation */ +#define AR9170_TX_PHY_SHORT_PREAMBLE 0x00000004 +#define AR9170_TX_PHY_GREENFIELD 0x00000004 + +#define AR9170_TX_PHY_BW_S 3 +#define AR9170_TX_PHY_BW (3 << AR9170_TX_PHY_BW_SHIFT) +#define AR9170_TX_PHY_BW_20MHZ 0 +#define AR9170_TX_PHY_BW_40MHZ 2 +#define AR9170_TX_PHY_BW_40MHZ_DUP 3 + +#define AR9170_TX_PHY_TX_HEAVY_CLIP_S 6 +#define AR9170_TX_PHY_TX_HEAVY_CLIP (7 << \ + AR9170_TX_PHY_TX_HEAVY_CLIP_S) + +#define AR9170_TX_PHY_TX_PWR_S 9 +#define AR9170_TX_PHY_TX_PWR (0x3f << \ + AR9170_TX_PHY_TX_PWR_S) + +#define AR9170_TX_PHY_TXCHAIN_S 15 +#define AR9170_TX_PHY_TXCHAIN (7 << \ + AR9170_TX_PHY_TXCHAIN_S) +#define AR9170_TX_PHY_TXCHAIN_1 1 +/* use for cck, ofdm 6/9/12/18/24 and HT if capable */ +#define AR9170_TX_PHY_TXCHAIN_2 5 + +#define AR9170_TX_PHY_MCS_S 18 +#define AR9170_TX_PHY_MCS (0x7f << \ + AR9170_TX_PHY_MCS_S) + +#define AR9170_TX_PHY_RATE_CCK_1M 0x0 +#define AR9170_TX_PHY_RATE_CCK_2M 0x1 +#define AR9170_TX_PHY_RATE_CCK_5M 0x2 +#define AR9170_TX_PHY_RATE_CCK_11M 0x3 + +/* same as AR9170_RX_PHY_RATE */ +#define AR9170_TXRX_PHY_RATE_OFDM_6M 0xb +#define AR9170_TXRX_PHY_RATE_OFDM_9M 0xf +#define AR9170_TXRX_PHY_RATE_OFDM_12M 0xa +#define AR9170_TXRX_PHY_RATE_OFDM_18M 0xe +#define AR9170_TXRX_PHY_RATE_OFDM_24M 0x9 +#define AR9170_TXRX_PHY_RATE_OFDM_36M 0xd +#define AR9170_TXRX_PHY_RATE_OFDM_48M 0x8 +#define AR9170_TXRX_PHY_RATE_OFDM_54M 0xc + +#define AR9170_TXRX_PHY_RATE_HT_MCS0 0x0 +#define AR9170_TXRX_PHY_RATE_HT_MCS1 0x1 +#define AR9170_TXRX_PHY_RATE_HT_MCS2 0x2 +#define AR9170_TXRX_PHY_RATE_HT_MCS3 0x3 +#define AR9170_TXRX_PHY_RATE_HT_MCS4 0x4 +#define AR9170_TXRX_PHY_RATE_HT_MCS5 0x5 +#define AR9170_TXRX_PHY_RATE_HT_MCS6 0x6 +#define AR9170_TXRX_PHY_RATE_HT_MCS7 0x7 +#define AR9170_TXRX_PHY_RATE_HT_MCS8 0x8 +#define AR9170_TXRX_PHY_RATE_HT_MCS9 0x9 +#define AR9170_TXRX_PHY_RATE_HT_MCS10 0xa +#define AR9170_TXRX_PHY_RATE_HT_MCS11 0xb +#define AR9170_TXRX_PHY_RATE_HT_MCS12 0xc +#define AR9170_TXRX_PHY_RATE_HT_MCS13 0xd +#define AR9170_TXRX_PHY_RATE_HT_MCS14 0xe +#define AR9170_TXRX_PHY_RATE_HT_MCS15 0xf + +#define AR9170_TX_PHY_SHORT_GI 0x80000000 + +#ifdef __CARL9170FW__ +struct ar9170_tx_hw_mac_control { + union { + struct { + /* + * Beware of compiler bugs in all gcc pre 4.4! + */ + + u8 erp_prot:2; + u8 no_ack:1; + u8 backoff:1; + u8 burst:1; + u8 ampdu:1; + + u8 enc_mode:2; + + u8 hw_mmic:1; + u8 hw_duration:1; + + u8 qos_queue:2; + + u8 disable_txop:1; + u8 txop_rifs:1; + + u8 ba_end:1; + u8 probe:1; + } __packed; + + __le16 set; + } __packed; +} __packed; + +struct ar9170_tx_hw_phy_control { + union { + struct { + /* + * Beware of compiler bugs in all gcc pre 4.4! + */ + + u8 modulation:2; + u8 preamble:1; + u8 bandwidth:2; + u8:1; + u8 heavy_clip:3; + u8 tx_power:6; + u8 chains:3; + u8 mcs:7; + u8:6; + u8 short_gi:1; + } __packed; + + __le32 set; + } __packed; +} __packed; + +struct ar9170_tx_rate_info { + u8 tries:3; + u8 erp_prot:2; + u8 ampdu:1; + u8 free:2; /* free for use (e.g.:RIFS/TXOP/AMPDU) */ +} __packed; + +struct carl9170_tx_superdesc { + __le16 len; + u8 rix; + u8 cnt; + u8 cookie; + u8 ampdu_density:3; + u8 ampdu_factor:2; + u8 ampdu_commit_density:1; + u8 ampdu_commit_factor:1; + u8 ampdu_unused_bit:1; + u8 queue:2; + u8 assign_seq:1; + u8 vif_id:3; + u8 fill_in_tsf:1; + u8 cab:1; + u8 padding2; + struct ar9170_tx_rate_info ri[CARL9170_TX_MAX_RATES]; + struct ar9170_tx_hw_phy_control rr[CARL9170_TX_MAX_RETRY_RATES]; +} __packed; + +struct ar9170_tx_hwdesc { + __le16 length; + struct ar9170_tx_hw_mac_control mac; + struct ar9170_tx_hw_phy_control phy; +} __packed; + +struct ar9170_tx_frame { + struct ar9170_tx_hwdesc hdr; + + union { + struct ieee80211_hdr i3e; + u8 payload[0]; + } data; +} __packed; + +struct carl9170_tx_superframe { + struct carl9170_tx_superdesc s; + struct ar9170_tx_frame f; +} __packed __aligned(4); + +#endif /* __CARL9170FW__ */ + +struct _ar9170_tx_hwdesc { + __le16 length; + __le16 mac_control; + __le32 phy_control; +} __packed; + +#define CARL9170_TX_SUPER_AMPDU_DENSITY_S 0 +#define CARL9170_TX_SUPER_AMPDU_DENSITY 0x7 +#define CARL9170_TX_SUPER_AMPDU_FACTOR 0x18 +#define CARL9170_TX_SUPER_AMPDU_FACTOR_S 3 +#define CARL9170_TX_SUPER_AMPDU_COMMIT_DENSITY 0x20 +#define CARL9170_TX_SUPER_AMPDU_COMMIT_DENSITY_S 5 +#define CARL9170_TX_SUPER_AMPDU_COMMIT_FACTOR 0x40 +#define CARL9170_TX_SUPER_AMPDU_COMMIT_FACTOR_S 6 + +#define CARL9170_TX_SUPER_MISC_QUEUE 0x3 +#define CARL9170_TX_SUPER_MISC_QUEUE_S 0 +#define CARL9170_TX_SUPER_MISC_ASSIGN_SEQ 0x4 +#define CARL9170_TX_SUPER_MISC_VIF_ID 0x38 +#define CARL9170_TX_SUPER_MISC_VIF_ID_S 3 +#define CARL9170_TX_SUPER_MISC_FILL_IN_TSF 0x40 +#define CARL9170_TX_SUPER_MISC_CAB 0x80 + +#define CARL9170_TX_SUPER_RI_TRIES 0x7 +#define CARL9170_TX_SUPER_RI_TRIES_S 0 +#define CARL9170_TX_SUPER_RI_ERP_PROT 0x18 +#define CARL9170_TX_SUPER_RI_ERP_PROT_S 3 +#define CARL9170_TX_SUPER_RI_AMPDU 0x20 +#define CARL9170_TX_SUPER_RI_AMPDU_S 5 + +struct _carl9170_tx_superdesc { + __le16 len; + u8 rix; + u8 cnt; + u8 cookie; + u8 ampdu_settings; + u8 misc; + u8 padding; + u8 ri[CARL9170_TX_MAX_RATES]; + __le32 rr[CARL9170_TX_MAX_RETRY_RATES]; +} __packed; + +struct _carl9170_tx_superframe { + struct _carl9170_tx_superdesc s; + struct _ar9170_tx_hwdesc f; + u8 frame_data[0]; +} __packed __aligned(4); + +#define CARL9170_TX_SUPERDESC_LEN 24 +#define AR9170_TX_HWDESC_LEN 8 +#define CARL9170_TX_SUPERFRAME_LEN (CARL9170_TX_SUPERDESC_LEN + \ + AR9170_TX_HWDESC_LEN) + +struct ar9170_rx_head { + u8 plcp[12]; +} __packed; + +#define AR9170_RX_HEAD_LEN 12 + +struct ar9170_rx_phystatus { + union { + struct { + u8 rssi_ant0, rssi_ant1, rssi_ant2, + rssi_ant0x, rssi_ant1x, rssi_ant2x, + rssi_combined; + } __packed; + u8 rssi[7]; + } __packed; + + u8 evm_stream0[6], evm_stream1[6]; + u8 phy_err; +} __packed; + +#define AR9170_RX_PHYSTATUS_LEN 20 + +struct ar9170_rx_macstatus { + u8 SAidx, DAidx; + u8 error; + u8 status; +} __packed; + +#define AR9170_RX_MACSTATUS_LEN 4 + +struct ar9170_rx_frame_single { + struct ar9170_rx_head phy_head; + struct ieee80211_hdr i3e; + struct ar9170_rx_phystatus phy_tail; + struct ar9170_rx_macstatus macstatus; +} __packed; + +struct ar9170_rx_frame_head { + struct ar9170_rx_head phy_head; + struct ieee80211_hdr i3e; + struct ar9170_rx_macstatus macstatus; +} __packed; + +struct ar9170_rx_frame_middle { + struct ieee80211_hdr i3e; + struct ar9170_rx_macstatus macstatus; +} __packed; + +struct ar9170_rx_frame_tail { + struct ieee80211_hdr i3e; + struct ar9170_rx_phystatus phy_tail; + struct ar9170_rx_macstatus macstatus; +} __packed; + +struct ar9170_rx_frame { + union { + struct ar9170_rx_frame_single single; + struct ar9170_rx_frame_head head; + struct ar9170_rx_frame_middle middle; + struct ar9170_rx_frame_tail tail; + } __packed; +} __packed; + +static inline u8 ar9170_get_decrypt_type(struct ar9170_rx_macstatus *t) +{ + return (t->SAidx & 0xc0) >> 4 | + (t->DAidx & 0xc0) >> 6; +} + +/* + * This is an workaround for several undocumented bugs. + * Don't mess with the QoS/AC <-> HW Queue map, if you don't + * know what you are doing. + * + * Known problems [hardware]: + * * The MAC does not aggregate frames on anything other + * than the first HW queue. + * * when an AMPDU is placed [in the first hw queue] and + * additional frames are already queued on a different + * hw queue, the MAC will ALWAYS freeze. + * + * In a nutshell: The hardware can either do QoS or + * Aggregation but not both at the same time. As a + * result, this makes the device pretty much useless + * for any serious 802.11n setup. + */ +enum ar9170_txq { + AR9170_TXQ_BK = 0, /* TXQ0 */ + AR9170_TXQ_BE, /* TXQ1 */ + AR9170_TXQ_VI, /* TXQ2 */ + AR9170_TXQ_VO, /* TXQ3 */ + + __AR9170_NUM_TXQ, +}; + +#define AR9170_TXQ_DEPTH 32 + +#endif /* __CARL9170_SHARED_WLAN_H */ diff --git a/drivers/net/wireless/ath/debug.c b/drivers/net/wireless/ath/debug.c new file mode 100644 index 0000000..508eccf --- /dev/null +++ b/drivers/net/wireless/ath/debug.c @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2009 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "ath.h" + +const char *ath_opmode_to_string(enum nl80211_iftype opmode) +{ + switch (opmode) { + case NL80211_IFTYPE_UNSPECIFIED: + return "UNSPEC"; + case NL80211_IFTYPE_ADHOC: + return "ADHOC"; + case NL80211_IFTYPE_STATION: + return "STATION"; + case NL80211_IFTYPE_AP: + return "AP"; + case NL80211_IFTYPE_AP_VLAN: + return "AP-VLAN"; + case NL80211_IFTYPE_WDS: + return "WDS"; + case NL80211_IFTYPE_MONITOR: + return "MONITOR"; + case NL80211_IFTYPE_MESH_POINT: + return "MESH"; + case NL80211_IFTYPE_P2P_CLIENT: + return "P2P-CLIENT"; + case NL80211_IFTYPE_P2P_GO: + return "P2P-GO"; + default: + return "UNKNOWN"; + } +} +EXPORT_SYMBOL(ath_opmode_to_string); diff --git a/drivers/net/wireless/ath/hw.c b/drivers/net/wireless/ath/hw.c new file mode 100644 index 0000000..eae9abf --- /dev/null +++ b/drivers/net/wireless/ath/hw.c @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2009 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "ath.h" +#include "reg.h" + +#define REG_READ (common->ops->read) +#define REG_WRITE(_ah, _reg, _val) (common->ops->write)(_ah, _val, _reg) + +/** + * ath_hw_set_bssid_mask - filter out bssids we listen + * + * @common: the ath_common struct for the device. + * + * BSSID masking is a method used by AR5212 and newer hardware to inform PCU + * which bits of the interface's MAC address should be looked at when trying + * to decide which packets to ACK. In station mode and AP mode with a single + * BSS every bit matters since we lock to only one BSS. In AP mode with + * multiple BSSes (virtual interfaces) not every bit matters because hw must + * accept frames for all BSSes and so we tweak some bits of our mac address + * in order to have multiple BSSes. + * + * NOTE: This is a simple filter and does *not* filter out all + * relevant frames. Some frames that are not for us might get ACKed from us + * by PCU because they just match the mask. + * + * When handling multiple BSSes you can get the BSSID mask by computing the + * set of ~ ( MAC XOR BSSID ) for all bssids we handle. + * + * When you do this you are essentially computing the common bits of all your + * BSSes. Later it is assumed the hardware will "and" (&) the BSSID mask with + * the MAC address to obtain the relevant bits and compare the result with + * (frame's BSSID & mask) to see if they match. + * + * Simple example: on your card you have have two BSSes you have created with + * BSSID-01 and BSSID-02. Lets assume BSSID-01 will not use the MAC address. + * There is another BSSID-03 but you are not part of it. For simplicity's sake, + * assuming only 4 bits for a mac address and for BSSIDs you can then have: + * + * \ + * MAC: 0001 | + * BSSID-01: 0100 | --> Belongs to us + * BSSID-02: 1001 | + * / + * ------------------- + * BSSID-03: 0110 | --> External + * ------------------- + * + * Our bssid_mask would then be: + * + * On loop iteration for BSSID-01: + * ~(0001 ^ 0100) -> ~(0101) + * -> 1010 + * bssid_mask = 1010 + * + * On loop iteration for BSSID-02: + * bssid_mask &= ~(0001 ^ 1001) + * bssid_mask = (1010) & ~(0001 ^ 1001) + * bssid_mask = (1010) & ~(1000) + * bssid_mask = (1010) & (0111) + * bssid_mask = 0010 + * + * A bssid_mask of 0010 means "only pay attention to the second least + * significant bit". This is because its the only bit common + * amongst the MAC and all BSSIDs we support. To findout what the real + * common bit is we can simply "&" the bssid_mask now with any BSSID we have + * or our MAC address (we assume the hardware uses the MAC address). + * + * Now, suppose there's an incoming frame for BSSID-03: + * + * IFRAME-01: 0110 + * + * An easy eye-inspeciton of this already should tell you that this frame + * will not pass our check. This is because the bssid_mask tells the + * hardware to only look at the second least significant bit and the + * common bit amongst the MAC and BSSIDs is 0, this frame has the 2nd LSB + * as 1, which does not match 0. + * + * So with IFRAME-01 we *assume* the hardware will do: + * + * allow = (IFRAME-01 & bssid_mask) == (bssid_mask & MAC) ? 1 : 0; + * --> allow = (0110 & 0010) == (0010 & 0001) ? 1 : 0; + * --> allow = (0010) == 0000 ? 1 : 0; + * --> allow = 0 + * + * Lets now test a frame that should work: + * + * IFRAME-02: 0001 (we should allow) + * + * allow = (IFRAME-02 & bssid_mask) == (bssid_mask & MAC) ? 1 : 0; + * --> allow = (0001 & 0010) == (0010 & 0001) ? 1 :0; + * --> allow = (0000) == (0000) + * --> allow = 1 + * + * Other examples: + * + * IFRAME-03: 0100 --> allowed + * IFRAME-04: 1001 --> allowed + * IFRAME-05: 1101 --> allowed but its not for us!!! + * + */ +void ath_hw_setbssidmask(struct ath_common *common) +{ + void *ah = common->ah; + u32 id1; + + REG_WRITE(ah, AR_STA_ID0, get_unaligned_le32(common->macaddr)); + id1 = REG_READ(ah, AR_STA_ID1) & ~AR_STA_ID1_SADH_MASK; + id1 |= get_unaligned_le16(common->macaddr + 4); + REG_WRITE(ah, AR_STA_ID1, id1); + + REG_WRITE(ah, AR_BSSMSKL, get_unaligned_le32(common->bssidmask)); + REG_WRITE(ah, AR_BSSMSKU, get_unaligned_le16(common->bssidmask + 4)); +} +EXPORT_SYMBOL(ath_hw_setbssidmask); + + +/** + * ath_hw_cycle_counters_update - common function to update cycle counters + * + * @common: the ath_common struct for the device. + * + * This function is used to update all cycle counters in one place. + * It has to be called while holding common->cc_lock! + */ +void ath_hw_cycle_counters_update(struct ath_common *common) +{ + u32 cycles, busy, rx, tx; + void *ah = common->ah; + + /* freeze */ + REG_WRITE(ah, AR_MIBC, AR_MIBC_FMC); + + /* read */ + cycles = REG_READ(ah, AR_CCCNT); + busy = REG_READ(ah, AR_RCCNT); + rx = REG_READ(ah, AR_RFCNT); + tx = REG_READ(ah, AR_TFCNT); + + /* clear */ + REG_WRITE(ah, AR_CCCNT, 0); + REG_WRITE(ah, AR_RFCNT, 0); + REG_WRITE(ah, AR_RCCNT, 0); + REG_WRITE(ah, AR_TFCNT, 0); + + /* unfreeze */ + REG_WRITE(ah, AR_MIBC, 0); + + /* update all cycle counters here */ + common->cc_ani.cycles += cycles; + common->cc_ani.rx_busy += busy; + common->cc_ani.rx_frame += rx; + common->cc_ani.tx_frame += tx; + + common->cc_survey.cycles += cycles; + common->cc_survey.rx_busy += busy; + common->cc_survey.rx_frame += rx; + common->cc_survey.tx_frame += tx; +} +EXPORT_SYMBOL(ath_hw_cycle_counters_update); + +int32_t ath_hw_get_listen_time(struct ath_common *common) +{ + struct ath_cycle_counters *cc = &common->cc_ani; + int32_t listen_time; + + listen_time = (cc->cycles - cc->rx_frame - cc->tx_frame) / + (common->clockrate * 1000); + + memset(cc, 0, sizeof(*cc)); + + return listen_time; +} +EXPORT_SYMBOL(ath_hw_get_listen_time); diff --git a/drivers/net/wireless/ath/key.c b/drivers/net/wireless/ath/key.c new file mode 100644 index 0000000..1816b4e --- /dev/null +++ b/drivers/net/wireless/ath/key.c @@ -0,0 +1,609 @@ +/* + * Copyright (c) 2009 Atheros Communications Inc. + * Copyright (c) 2010 Bruno Randolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include "ath.h" +#include "reg.h" + +#define REG_READ (common->ops->read) +#define REG_WRITE(_ah, _reg, _val) (common->ops->write)(_ah, _val, _reg) +#define ENABLE_REGWRITE_BUFFER(_ah) \ + if (common->ops->enable_write_buffer) \ + common->ops->enable_write_buffer((_ah)); + +#define REGWRITE_BUFFER_FLUSH(_ah) \ + if (common->ops->write_flush) \ + common->ops->write_flush((_ah)); + + +#define IEEE80211_WEP_NKID 4 /* number of key ids */ + +/************************/ +/* Key Cache Management */ +/************************/ + +bool ath_hw_keyreset(struct ath_common *common, u16 entry) +{ + u32 keyType; + void *ah = common->ah; + + if (entry >= common->keymax) { + ath_err(common, "keyreset: keycache entry %u out of range\n", + entry); + return false; + } + + keyType = REG_READ(ah, AR_KEYTABLE_TYPE(entry)); + + ENABLE_REGWRITE_BUFFER(ah); + + REG_WRITE(ah, AR_KEYTABLE_KEY0(entry), 0); + REG_WRITE(ah, AR_KEYTABLE_KEY1(entry), 0); + REG_WRITE(ah, AR_KEYTABLE_KEY2(entry), 0); + REG_WRITE(ah, AR_KEYTABLE_KEY3(entry), 0); + REG_WRITE(ah, AR_KEYTABLE_KEY4(entry), 0); + REG_WRITE(ah, AR_KEYTABLE_TYPE(entry), AR_KEYTABLE_TYPE_CLR); + REG_WRITE(ah, AR_KEYTABLE_MAC0(entry), 0); + REG_WRITE(ah, AR_KEYTABLE_MAC1(entry), 0); + + if (keyType == AR_KEYTABLE_TYPE_TKIP) { + u16 micentry = entry + 64; + + REG_WRITE(ah, AR_KEYTABLE_KEY0(micentry), 0); + REG_WRITE(ah, AR_KEYTABLE_KEY1(micentry), 0); + REG_WRITE(ah, AR_KEYTABLE_KEY2(micentry), 0); + REG_WRITE(ah, AR_KEYTABLE_KEY3(micentry), 0); + if (common->crypt_caps & ATH_CRYPT_CAP_MIC_COMBINED) { + REG_WRITE(ah, AR_KEYTABLE_KEY4(micentry), 0); + REG_WRITE(ah, AR_KEYTABLE_TYPE(micentry), + AR_KEYTABLE_TYPE_CLR); + } + + } + + REGWRITE_BUFFER_FLUSH(ah); + + return true; +} +EXPORT_SYMBOL(ath_hw_keyreset); + +static bool ath_hw_keysetmac(struct ath_common *common, + u16 entry, const u8 *mac) +{ + u32 macHi, macLo; + u32 unicast_flag = AR_KEYTABLE_VALID; + void *ah = common->ah; + + if (entry >= common->keymax) { + ath_err(common, "keysetmac: keycache entry %u out of range\n", + entry); + return false; + } + + if (mac != NULL) { + /* + * AR_KEYTABLE_VALID indicates that the address is a unicast + * address, which must match the transmitter address for + * decrypting frames. + * Not setting this bit allows the hardware to use the key + * for multicast frame decryption. + */ + if (mac[0] & 0x01) + unicast_flag = 0; + + macLo = get_unaligned_le32(mac); + macHi = get_unaligned_le16(mac + 4); + macLo >>= 1; + macLo |= (macHi & 1) << 31; + macHi >>= 1; + } else { + macLo = macHi = 0; + } + ENABLE_REGWRITE_BUFFER(ah); + + REG_WRITE(ah, AR_KEYTABLE_MAC0(entry), macLo); + REG_WRITE(ah, AR_KEYTABLE_MAC1(entry), macHi | unicast_flag); + + REGWRITE_BUFFER_FLUSH(ah); + + return true; +} + +static bool ath_hw_set_keycache_entry(struct ath_common *common, u16 entry, + const struct ath_keyval *k, + const u8 *mac) +{ + void *ah = common->ah; + u32 key0, key1, key2, key3, key4; + u32 keyType; + + if (entry >= common->keymax) { + ath_err(common, "set-entry: keycache entry %u out of range\n", + entry); + return false; + } + + switch (k->kv_type) { + case ATH_CIPHER_AES_OCB: + keyType = AR_KEYTABLE_TYPE_AES; + break; + case ATH_CIPHER_AES_CCM: + if (!(common->crypt_caps & ATH_CRYPT_CAP_CIPHER_AESCCM)) { + ath_dbg(common, ANY, + "AES-CCM not supported by this mac rev\n"); + return false; + } + keyType = AR_KEYTABLE_TYPE_CCM; + break; + case ATH_CIPHER_TKIP: + keyType = AR_KEYTABLE_TYPE_TKIP; + if (entry + 64 >= common->keymax) { + ath_dbg(common, ANY, + "entry %u inappropriate for TKIP\n", entry); + return false; + } + break; + case ATH_CIPHER_WEP: + if (k->kv_len < WLAN_KEY_LEN_WEP40) { + ath_dbg(common, ANY, "WEP key length %u too small\n", + k->kv_len); + return false; + } + if (k->kv_len <= WLAN_KEY_LEN_WEP40) + keyType = AR_KEYTABLE_TYPE_40; + else if (k->kv_len <= WLAN_KEY_LEN_WEP104) + keyType = AR_KEYTABLE_TYPE_104; + else + keyType = AR_KEYTABLE_TYPE_128; + break; + case ATH_CIPHER_CLR: + keyType = AR_KEYTABLE_TYPE_CLR; + break; + default: + ath_err(common, "cipher %u not supported\n", k->kv_type); + return false; + } + + key0 = get_unaligned_le32(k->kv_val + 0); + key1 = get_unaligned_le16(k->kv_val + 4); + key2 = get_unaligned_le32(k->kv_val + 6); + key3 = get_unaligned_le16(k->kv_val + 10); + key4 = get_unaligned_le32(k->kv_val + 12); + if (k->kv_len <= WLAN_KEY_LEN_WEP104) + key4 &= 0xff; + + /* + * Note: Key cache registers access special memory area that requires + * two 32-bit writes to actually update the values in the internal + * memory. Consequently, the exact order and pairs used here must be + * maintained. + */ + + if (keyType == AR_KEYTABLE_TYPE_TKIP) { + u16 micentry = entry + 64; + + /* + * Write inverted key[47:0] first to avoid Michael MIC errors + * on frames that could be sent or received at the same time. + * The correct key will be written in the end once everything + * else is ready. + */ + REG_WRITE(ah, AR_KEYTABLE_KEY0(entry), ~key0); + REG_WRITE(ah, AR_KEYTABLE_KEY1(entry), ~key1); + + /* Write key[95:48] */ + REG_WRITE(ah, AR_KEYTABLE_KEY2(entry), key2); + REG_WRITE(ah, AR_KEYTABLE_KEY3(entry), key3); + + /* Write key[127:96] and key type */ + REG_WRITE(ah, AR_KEYTABLE_KEY4(entry), key4); + REG_WRITE(ah, AR_KEYTABLE_TYPE(entry), keyType); + + /* Write MAC address for the entry */ + (void) ath_hw_keysetmac(common, entry, mac); + + if (common->crypt_caps & ATH_CRYPT_CAP_MIC_COMBINED) { + /* + * TKIP uses two key cache entries: + * Michael MIC TX/RX keys in the same key cache entry + * (idx = main index + 64): + * key0 [31:0] = RX key [31:0] + * key1 [15:0] = TX key [31:16] + * key1 [31:16] = reserved + * key2 [31:0] = RX key [63:32] + * key3 [15:0] = TX key [15:0] + * key3 [31:16] = reserved + * key4 [31:0] = TX key [63:32] + */ + u32 mic0, mic1, mic2, mic3, mic4; + + mic0 = get_unaligned_le32(k->kv_mic + 0); + mic2 = get_unaligned_le32(k->kv_mic + 4); + mic1 = get_unaligned_le16(k->kv_txmic + 2) & 0xffff; + mic3 = get_unaligned_le16(k->kv_txmic + 0) & 0xffff; + mic4 = get_unaligned_le32(k->kv_txmic + 4); + + ENABLE_REGWRITE_BUFFER(ah); + + /* Write RX[31:0] and TX[31:16] */ + REG_WRITE(ah, AR_KEYTABLE_KEY0(micentry), mic0); + REG_WRITE(ah, AR_KEYTABLE_KEY1(micentry), mic1); + + /* Write RX[63:32] and TX[15:0] */ + REG_WRITE(ah, AR_KEYTABLE_KEY2(micentry), mic2); + REG_WRITE(ah, AR_KEYTABLE_KEY3(micentry), mic3); + + /* Write TX[63:32] and keyType(reserved) */ + REG_WRITE(ah, AR_KEYTABLE_KEY4(micentry), mic4); + REG_WRITE(ah, AR_KEYTABLE_TYPE(micentry), + AR_KEYTABLE_TYPE_CLR); + + REGWRITE_BUFFER_FLUSH(ah); + + } else { + /* + * TKIP uses four key cache entries (two for group + * keys): + * Michael MIC TX/RX keys are in different key cache + * entries (idx = main index + 64 for TX and + * main index + 32 + 96 for RX): + * key0 [31:0] = TX/RX MIC key [31:0] + * key1 [31:0] = reserved + * key2 [31:0] = TX/RX MIC key [63:32] + * key3 [31:0] = reserved + * key4 [31:0] = reserved + * + * Upper layer code will call this function separately + * for TX and RX keys when these registers offsets are + * used. + */ + u32 mic0, mic2; + + mic0 = get_unaligned_le32(k->kv_mic + 0); + mic2 = get_unaligned_le32(k->kv_mic + 4); + + ENABLE_REGWRITE_BUFFER(ah); + + /* Write MIC key[31:0] */ + REG_WRITE(ah, AR_KEYTABLE_KEY0(micentry), mic0); + REG_WRITE(ah, AR_KEYTABLE_KEY1(micentry), 0); + + /* Write MIC key[63:32] */ + REG_WRITE(ah, AR_KEYTABLE_KEY2(micentry), mic2); + REG_WRITE(ah, AR_KEYTABLE_KEY3(micentry), 0); + + /* Write TX[63:32] and keyType(reserved) */ + REG_WRITE(ah, AR_KEYTABLE_KEY4(micentry), 0); + REG_WRITE(ah, AR_KEYTABLE_TYPE(micentry), + AR_KEYTABLE_TYPE_CLR); + + REGWRITE_BUFFER_FLUSH(ah); + } + + ENABLE_REGWRITE_BUFFER(ah); + + /* MAC address registers are reserved for the MIC entry */ + REG_WRITE(ah, AR_KEYTABLE_MAC0(micentry), 0); + REG_WRITE(ah, AR_KEYTABLE_MAC1(micentry), 0); + + /* + * Write the correct (un-inverted) key[47:0] last to enable + * TKIP now that all other registers are set with correct + * values. + */ + REG_WRITE(ah, AR_KEYTABLE_KEY0(entry), key0); + REG_WRITE(ah, AR_KEYTABLE_KEY1(entry), key1); + + REGWRITE_BUFFER_FLUSH(ah); + } else { + ENABLE_REGWRITE_BUFFER(ah); + + /* Write key[47:0] */ + REG_WRITE(ah, AR_KEYTABLE_KEY0(entry), key0); + REG_WRITE(ah, AR_KEYTABLE_KEY1(entry), key1); + + /* Write key[95:48] */ + REG_WRITE(ah, AR_KEYTABLE_KEY2(entry), key2); + REG_WRITE(ah, AR_KEYTABLE_KEY3(entry), key3); + + /* Write key[127:96] and key type */ + REG_WRITE(ah, AR_KEYTABLE_KEY4(entry), key4); + REG_WRITE(ah, AR_KEYTABLE_TYPE(entry), keyType); + + REGWRITE_BUFFER_FLUSH(ah); + + /* Write MAC address for the entry */ + (void) ath_hw_keysetmac(common, entry, mac); + } + + return true; +} + +static int ath_setkey_tkip(struct ath_common *common, u16 keyix, const u8 *key, + struct ath_keyval *hk, const u8 *addr, + bool authenticator) +{ + const u8 *key_rxmic; + const u8 *key_txmic; + + key_txmic = key + NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY; + key_rxmic = key + NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY; + + if (addr == NULL) { + /* + * Group key installation - only two key cache entries are used + * regardless of splitmic capability since group key is only + * used either for TX or RX. + */ + if (authenticator) { + memcpy(hk->kv_mic, key_txmic, sizeof(hk->kv_mic)); + memcpy(hk->kv_txmic, key_txmic, sizeof(hk->kv_mic)); + } else { + memcpy(hk->kv_mic, key_rxmic, sizeof(hk->kv_mic)); + memcpy(hk->kv_txmic, key_rxmic, sizeof(hk->kv_mic)); + } + return ath_hw_set_keycache_entry(common, keyix, hk, addr); + } + if (common->crypt_caps & ATH_CRYPT_CAP_MIC_COMBINED) { + /* TX and RX keys share the same key cache entry. */ + memcpy(hk->kv_mic, key_rxmic, sizeof(hk->kv_mic)); + memcpy(hk->kv_txmic, key_txmic, sizeof(hk->kv_txmic)); + return ath_hw_set_keycache_entry(common, keyix, hk, addr); + } + + /* Separate key cache entries for TX and RX */ + + /* TX key goes at first index, RX key at +32. */ + memcpy(hk->kv_mic, key_txmic, sizeof(hk->kv_mic)); + if (!ath_hw_set_keycache_entry(common, keyix, hk, NULL)) { + /* TX MIC entry failed. No need to proceed further */ + ath_err(common, "Setting TX MIC Key Failed\n"); + return 0; + } + + memcpy(hk->kv_mic, key_rxmic, sizeof(hk->kv_mic)); + /* XXX delete tx key on failure? */ + return ath_hw_set_keycache_entry(common, keyix + 32, hk, addr); +} + +static int ath_reserve_key_cache_slot_tkip(struct ath_common *common) +{ + int i; + + for (i = IEEE80211_WEP_NKID; i < common->keymax / 2; i++) { + if (test_bit(i, common->keymap) || + test_bit(i + 64, common->keymap)) + continue; /* At least one part of TKIP key allocated */ + if (!(common->crypt_caps & ATH_CRYPT_CAP_MIC_COMBINED) && + (test_bit(i + 32, common->keymap) || + test_bit(i + 64 + 32, common->keymap))) + continue; /* At least one part of TKIP key allocated */ + + /* Found a free slot for a TKIP key */ + return i; + } + return -1; +} + +static int ath_reserve_key_cache_slot(struct ath_common *common, + u32 cipher) +{ + int i; + + if (cipher == WLAN_CIPHER_SUITE_TKIP) + return ath_reserve_key_cache_slot_tkip(common); + + /* First, try to find slots that would not be available for TKIP. */ + if (!(common->crypt_caps & ATH_CRYPT_CAP_MIC_COMBINED)) { + for (i = IEEE80211_WEP_NKID; i < common->keymax / 4; i++) { + if (!test_bit(i, common->keymap) && + (test_bit(i + 32, common->keymap) || + test_bit(i + 64, common->keymap) || + test_bit(i + 64 + 32, common->keymap))) + return i; + if (!test_bit(i + 32, common->keymap) && + (test_bit(i, common->keymap) || + test_bit(i + 64, common->keymap) || + test_bit(i + 64 + 32, common->keymap))) + return i + 32; + if (!test_bit(i + 64, common->keymap) && + (test_bit(i , common->keymap) || + test_bit(i + 32, common->keymap) || + test_bit(i + 64 + 32, common->keymap))) + return i + 64; + if (!test_bit(i + 64 + 32, common->keymap) && + (test_bit(i, common->keymap) || + test_bit(i + 32, common->keymap) || + test_bit(i + 64, common->keymap))) + return i + 64 + 32; + } + } else { + for (i = IEEE80211_WEP_NKID; i < common->keymax / 2; i++) { + if (!test_bit(i, common->keymap) && + test_bit(i + 64, common->keymap)) + return i; + if (test_bit(i, common->keymap) && + !test_bit(i + 64, common->keymap)) + return i + 64; + } + } + + /* No partially used TKIP slots, pick any available slot */ + for (i = IEEE80211_WEP_NKID; i < common->keymax; i++) { + /* Do not allow slots that could be needed for TKIP group keys + * to be used. This limitation could be removed if we know that + * TKIP will not be used. */ + if (i >= 64 && i < 64 + IEEE80211_WEP_NKID) + continue; + if (!(common->crypt_caps & ATH_CRYPT_CAP_MIC_COMBINED)) { + if (i >= 32 && i < 32 + IEEE80211_WEP_NKID) + continue; + if (i >= 64 + 32 && i < 64 + 32 + IEEE80211_WEP_NKID) + continue; + } + + if (!test_bit(i, common->keymap)) + return i; /* Found a free slot for a key */ + } + + /* No free slot found */ + return -1; +} + +/* + * Configure encryption in the HW. + */ +int ath_key_config(struct ath_common *common, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct ath_keyval hk; + const u8 *mac = NULL; + u8 gmac[ETH_ALEN]; + int ret = 0; + int idx; + + memset(&hk, 0, sizeof(hk)); + + switch (key->cipher) { + case 0: + hk.kv_type = ATH_CIPHER_CLR; + break; + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + hk.kv_type = ATH_CIPHER_WEP; + break; + case WLAN_CIPHER_SUITE_TKIP: + hk.kv_type = ATH_CIPHER_TKIP; + break; + case WLAN_CIPHER_SUITE_CCMP: + hk.kv_type = ATH_CIPHER_AES_CCM; + break; + default: + return -EOPNOTSUPP; + } + + hk.kv_len = key->keylen; + if (key->keylen) + memcpy(hk.kv_val, key->key, key->keylen); + + if (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) { + switch (vif->type) { + case NL80211_IFTYPE_AP: + memcpy(gmac, vif->addr, ETH_ALEN); + gmac[0] |= 0x01; + mac = gmac; + idx = ath_reserve_key_cache_slot(common, key->cipher); + break; + case NL80211_IFTYPE_ADHOC: + if (!sta) { + idx = key->keyidx; + break; + } + memcpy(gmac, sta->addr, ETH_ALEN); + gmac[0] |= 0x01; + mac = gmac; + idx = ath_reserve_key_cache_slot(common, key->cipher); + break; + default: + idx = key->keyidx; + break; + } + } else if (key->keyidx) { + if (WARN_ON(!sta)) + return -EOPNOTSUPP; + mac = sta->addr; + + if (vif->type != NL80211_IFTYPE_AP) { + /* Only keyidx 0 should be used with unicast key, but + * allow this for client mode for now. */ + idx = key->keyidx; + } else + return -EIO; + } else { + if (WARN_ON(!sta)) + return -EOPNOTSUPP; + mac = sta->addr; + + idx = ath_reserve_key_cache_slot(common, key->cipher); + } + + if (idx < 0) + return -ENOSPC; /* no free key cache entries */ + + if (key->cipher == WLAN_CIPHER_SUITE_TKIP) + ret = ath_setkey_tkip(common, idx, key->key, &hk, mac, + vif->type == NL80211_IFTYPE_AP); + else + ret = ath_hw_set_keycache_entry(common, idx, &hk, mac); + + if (!ret) + return -EIO; + + set_bit(idx, common->keymap); + if (key->cipher == WLAN_CIPHER_SUITE_CCMP) + set_bit(idx, common->ccmp_keymap); + + if (key->cipher == WLAN_CIPHER_SUITE_TKIP) { + set_bit(idx + 64, common->keymap); + set_bit(idx, common->tkip_keymap); + set_bit(idx + 64, common->tkip_keymap); + if (!(common->crypt_caps & ATH_CRYPT_CAP_MIC_COMBINED)) { + set_bit(idx + 32, common->keymap); + set_bit(idx + 64 + 32, common->keymap); + set_bit(idx + 32, common->tkip_keymap); + set_bit(idx + 64 + 32, common->tkip_keymap); + } + } + + return idx; +} +EXPORT_SYMBOL(ath_key_config); + +/* + * Delete Key. + */ +void ath_key_delete(struct ath_common *common, struct ieee80211_key_conf *key) +{ + ath_hw_keyreset(common, key->hw_key_idx); + if (key->hw_key_idx < IEEE80211_WEP_NKID) + return; + + clear_bit(key->hw_key_idx, common->keymap); + clear_bit(key->hw_key_idx, common->ccmp_keymap); + if (key->cipher != WLAN_CIPHER_SUITE_TKIP) + return; + + clear_bit(key->hw_key_idx + 64, common->keymap); + + clear_bit(key->hw_key_idx, common->tkip_keymap); + clear_bit(key->hw_key_idx + 64, common->tkip_keymap); + + if (!(common->crypt_caps & ATH_CRYPT_CAP_MIC_COMBINED)) { + ath_hw_keyreset(common, key->hw_key_idx + 32); + clear_bit(key->hw_key_idx + 32, common->keymap); + clear_bit(key->hw_key_idx + 64 + 32, common->keymap); + + clear_bit(key->hw_key_idx + 32, common->tkip_keymap); + clear_bit(key->hw_key_idx + 64 + 32, common->tkip_keymap); + } +} +EXPORT_SYMBOL(ath_key_delete); diff --git a/drivers/net/wireless/ath/main.c b/drivers/net/wireless/ath/main.c new file mode 100644 index 0000000..c638d57 --- /dev/null +++ b/drivers/net/wireless/ath/main.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2009 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include + +#include "ath.h" + +MODULE_AUTHOR("Atheros Communications"); +MODULE_DESCRIPTION("Shared library for Atheros wireless LAN cards."); +MODULE_LICENSE("Dual BSD/GPL"); + +struct sk_buff *ath_rxbuf_alloc(struct ath_common *common, + u32 len, + gfp_t gfp_mask) +{ + struct sk_buff *skb; + u32 off; + + /* + * Cache-line-align. This is important (for the + * 5210 at least) as not doing so causes bogus data + * in rx'd frames. + */ + + /* Note: the kernel can allocate a value greater than + * what we ask it to give us. We really only need 4 KB as that + * is this hardware supports and in fact we need at least 3849 + * as that is the MAX AMSDU size this hardware supports. + * Unfortunately this means we may get 8 KB here from the + * kernel... and that is actually what is observed on some + * systems :( */ + skb = __dev_alloc_skb(len + common->cachelsz - 1, gfp_mask); + if (skb != NULL) { + off = ((unsigned long) skb->data) % common->cachelsz; + if (off != 0) + skb_reserve(skb, common->cachelsz - off); + } else { + pr_err("skbuff alloc of size %u failed\n", len); + return NULL; + } + + return skb; +} +EXPORT_SYMBOL(ath_rxbuf_alloc); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)) +void ath_printk(const char *level, const struct ath_common* common, + const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + if (common && common->hw && common->hw->wiphy) + printk("%sath: %s: %pV", + level, wiphy_name(common->hw->wiphy), &vaf); + else + printk("%sath: %pV", level, &vaf); + + va_end(args); +} +#else +void ath_printk(const char *level, const struct ath_common* common, + const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + + if (common && common->hw && common->hw->wiphy) + printk("%sath: %s: ", + level, wiphy_name(common->hw->wiphy)); + else + printk("%sath: ", level); + + vprintk(fmt, args); + + va_end(args); +} +#endif + +EXPORT_SYMBOL(ath_printk); diff --git a/drivers/net/wireless/ath/reg.h b/drivers/net/wireless/ath/reg.h new file mode 100644 index 0000000..3ad4c77 --- /dev/null +++ b/drivers/net/wireless/ath/reg.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2008-2009 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef ATH_REGISTERS_H +#define ATH_REGISTERS_H + +#define AR_MIBC 0x0040 +#define AR_MIBC_COW 0x00000001 +#define AR_MIBC_FMC 0x00000002 +#define AR_MIBC_CMC 0x00000004 +#define AR_MIBC_MCS 0x00000008 + +#define AR_STA_ID0 0x8000 +#define AR_STA_ID1 0x8004 +#define AR_STA_ID1_SADH_MASK 0x0000ffff + +/* + * BSSID mask registers. See ath_hw_set_bssid_mask() + * for detailed documentation about these registers. + */ +#define AR_BSSMSKL 0x80e0 +#define AR_BSSMSKU 0x80e4 + +#define AR_TFCNT 0x80ec +#define AR_RFCNT 0x80f0 +#define AR_RCCNT 0x80f4 +#define AR_CCCNT 0x80f8 + +#define AR_KEYTABLE_0 0x8800 +#define AR_KEYTABLE(_n) (AR_KEYTABLE_0 + ((_n)*32)) +#define AR_KEY_CACHE_SIZE 128 +#define AR_RSVD_KEYTABLE_ENTRIES 4 +#define AR_KEY_TYPE 0x00000007 +#define AR_KEYTABLE_TYPE_40 0x00000000 +#define AR_KEYTABLE_TYPE_104 0x00000001 +#define AR_KEYTABLE_TYPE_128 0x00000003 +#define AR_KEYTABLE_TYPE_TKIP 0x00000004 +#define AR_KEYTABLE_TYPE_AES 0x00000005 +#define AR_KEYTABLE_TYPE_CCM 0x00000006 +#define AR_KEYTABLE_TYPE_CLR 0x00000007 +#define AR_KEYTABLE_ANT 0x00000008 +#define AR_KEYTABLE_VALID 0x00008000 +#define AR_KEYTABLE_KEY0(_n) (AR_KEYTABLE(_n) + 0) +#define AR_KEYTABLE_KEY1(_n) (AR_KEYTABLE(_n) + 4) +#define AR_KEYTABLE_KEY2(_n) (AR_KEYTABLE(_n) + 8) +#define AR_KEYTABLE_KEY3(_n) (AR_KEYTABLE(_n) + 12) +#define AR_KEYTABLE_KEY4(_n) (AR_KEYTABLE(_n) + 16) +#define AR_KEYTABLE_TYPE(_n) (AR_KEYTABLE(_n) + 20) +#define AR_KEYTABLE_MAC0(_n) (AR_KEYTABLE(_n) + 24) +#define AR_KEYTABLE_MAC1(_n) (AR_KEYTABLE(_n) + 28) + +#endif /* ATH_REGISTERS_H */ diff --git a/drivers/net/wireless/ath/regd.c b/drivers/net/wireless/ath/regd.c new file mode 100644 index 0000000..ccc4c71 --- /dev/null +++ b/drivers/net/wireless/ath/regd.c @@ -0,0 +1,661 @@ +/* + * Copyright (c) 2008-2009 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include "regd.h" +#include "regd_common.h" + +static int __ath_regd_init(struct ath_regulatory *reg); + +/* + * This is a set of common rules used by our world regulatory domains. + * We have 12 world regulatory domains. To save space we consolidate + * the regulatory domains in 5 structures by frequency and change + * the flags on our reg_notifier() on a case by case basis. + */ + +/* Only these channels all allow active scan on all world regulatory domains */ +#define ATH9K_2GHZ_CH01_11 REG_RULE(2412-10, 2462+10, 40, 0, 20, 0) + +/* We enable active scan on these a case by case basis by regulatory domain */ +#define ATH9K_2GHZ_CH12_13 REG_RULE(2467-10, 2472+10, 40, 0, 20,\ + NL80211_RRF_PASSIVE_SCAN) +#define ATH9K_2GHZ_CH14 REG_RULE(2484-10, 2484+10, 40, 0, 20,\ + NL80211_RRF_PASSIVE_SCAN | NL80211_RRF_NO_OFDM) + +/* We allow IBSS on these on a case by case basis by regulatory domain */ +#define ATH9K_5GHZ_5150_5350 REG_RULE(5150-10, 5350+10, 40, 0, 30,\ + NL80211_RRF_PASSIVE_SCAN | NL80211_RRF_NO_IBSS) +#define ATH9K_5GHZ_5470_5850 REG_RULE(5470-10, 5850+10, 40, 0, 30,\ + NL80211_RRF_PASSIVE_SCAN | NL80211_RRF_NO_IBSS) +#define ATH9K_5GHZ_5725_5850 REG_RULE(5725-10, 5850+10, 40, 0, 30,\ + NL80211_RRF_PASSIVE_SCAN | NL80211_RRF_NO_IBSS) + +#define ATH9K_2GHZ_ALL ATH9K_2GHZ_CH01_11, \ + ATH9K_2GHZ_CH12_13, \ + ATH9K_2GHZ_CH14 + +#define ATH9K_5GHZ_ALL ATH9K_5GHZ_5150_5350, \ + ATH9K_5GHZ_5470_5850 + +/* This one skips what we call "mid band" */ +#define ATH9K_5GHZ_NO_MIDBAND ATH9K_5GHZ_5150_5350, \ + ATH9K_5GHZ_5725_5850 + +/* Can be used for: + * 0x60, 0x61, 0x62 */ +static const struct ieee80211_regdomain ath_world_regdom_60_61_62 = { + .n_reg_rules = 5, + .alpha2 = "99", + .reg_rules = { + ATH9K_2GHZ_ALL, + ATH9K_5GHZ_ALL, + } +}; + +/* Can be used by 0x63 and 0x65 */ +static const struct ieee80211_regdomain ath_world_regdom_63_65 = { + .n_reg_rules = 4, + .alpha2 = "99", + .reg_rules = { + ATH9K_2GHZ_CH01_11, + ATH9K_2GHZ_CH12_13, + ATH9K_5GHZ_NO_MIDBAND, + } +}; + +/* Can be used by 0x64 only */ +static const struct ieee80211_regdomain ath_world_regdom_64 = { + .n_reg_rules = 3, + .alpha2 = "99", + .reg_rules = { + ATH9K_2GHZ_CH01_11, + ATH9K_5GHZ_NO_MIDBAND, + } +}; + +/* Can be used by 0x66 and 0x69 */ +static const struct ieee80211_regdomain ath_world_regdom_66_69 = { + .n_reg_rules = 3, + .alpha2 = "99", + .reg_rules = { + ATH9K_2GHZ_CH01_11, + ATH9K_5GHZ_ALL, + } +}; + +/* Can be used by 0x67, 0x68, 0x6A and 0x6C */ +static const struct ieee80211_regdomain ath_world_regdom_67_68_6A_6C = { + .n_reg_rules = 4, + .alpha2 = "99", + .reg_rules = { + ATH9K_2GHZ_CH01_11, + ATH9K_2GHZ_CH12_13, + ATH9K_5GHZ_ALL, + } +}; + +static inline bool is_wwr_sku(u16 regd) +{ + return ((regd & COUNTRY_ERD_FLAG) != COUNTRY_ERD_FLAG) && + (((regd & WORLD_SKU_MASK) == WORLD_SKU_PREFIX) || + (regd == WORLD)); +} + +static u16 ath_regd_get_eepromRD(struct ath_regulatory *reg) +{ + return reg->current_rd & ~WORLDWIDE_ROAMING_FLAG; +} + +bool ath_is_world_regd(struct ath_regulatory *reg) +{ + return is_wwr_sku(ath_regd_get_eepromRD(reg)); +} +EXPORT_SYMBOL(ath_is_world_regd); + +static const struct ieee80211_regdomain *ath_default_world_regdomain(void) +{ + /* this is the most restrictive */ + return &ath_world_regdom_64; +} + +static const struct +ieee80211_regdomain *ath_world_regdomain(struct ath_regulatory *reg) +{ + switch (reg->regpair->regDmnEnum) { + case 0x60: + case 0x61: + case 0x62: + return &ath_world_regdom_60_61_62; + case 0x63: + case 0x65: + return &ath_world_regdom_63_65; + case 0x64: + return &ath_world_regdom_64; + case 0x66: + case 0x69: + return &ath_world_regdom_66_69; + case 0x67: + case 0x68: + case 0x6A: + case 0x6C: + return &ath_world_regdom_67_68_6A_6C; + default: + WARN_ON(1); + return ath_default_world_regdomain(); + } +} + +bool ath_is_49ghz_allowed(u16 regdomain) +{ + /* possibly more */ + return regdomain == MKK9_MKKC; +} +EXPORT_SYMBOL(ath_is_49ghz_allowed); + +/* Frequency is one where radar detection is required */ +static bool ath_is_radar_freq(u16 center_freq) +{ + return (center_freq >= 5260 && center_freq <= 5700); +} + +/* + * N.B: These exception rules do not apply radar freqs. + * + * - We enable adhoc (or beaconing) if allowed by 11d + * - We enable active scan if the channel is allowed by 11d + * - If no country IE has been processed and a we determine we have + * received a beacon on a channel we can enable active scan and + * adhoc (or beaconing). + */ +static void +ath_reg_apply_beaconing_flags(struct wiphy *wiphy, + enum nl80211_reg_initiator initiator) +{ + enum ieee80211_band band; + struct ieee80211_supported_band *sband; + const struct ieee80211_reg_rule *reg_rule; + struct ieee80211_channel *ch; + unsigned int i; + + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { + + if (!wiphy->bands[band]) + continue; + + sband = wiphy->bands[band]; + + for (i = 0; i < sband->n_channels; i++) { + + ch = &sband->channels[i]; + + if (ath_is_radar_freq(ch->center_freq) || + (ch->flags & IEEE80211_CHAN_RADAR)) + continue; + + if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) { + reg_rule = freq_reg_info(wiphy, ch->center_freq); + if (IS_ERR(reg_rule)) + continue; + /* + * If 11d had a rule for this channel ensure + * we enable adhoc/beaconing if it allows us to + * use it. Note that we would have disabled it + * by applying our static world regdomain by + * default during init, prior to calling our + * regulatory_hint(). + */ + if (!(reg_rule->flags & + NL80211_RRF_NO_IBSS)) + ch->flags &= + ~IEEE80211_CHAN_NO_IBSS; + if (!(reg_rule->flags & + NL80211_RRF_PASSIVE_SCAN)) + ch->flags &= + ~IEEE80211_CHAN_PASSIVE_SCAN; + } else { + if (ch->beacon_found) + ch->flags &= ~(IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN); + } + } + } + +} + +/* Allows active scan scan on Ch 12 and 13 */ +static void +ath_reg_apply_active_scan_flags(struct wiphy *wiphy, + enum nl80211_reg_initiator initiator) +{ + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + const struct ieee80211_reg_rule *reg_rule; + + sband = wiphy->bands[IEEE80211_BAND_2GHZ]; + if (!sband) + return; + + /* + * If no country IE has been received always enable active scan + * on these channels. This is only done for specific regulatory SKUs + */ + if (initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) { + ch = &sband->channels[11]; /* CH 12 */ + if (ch->flags & IEEE80211_CHAN_PASSIVE_SCAN) + ch->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN; + ch = &sband->channels[12]; /* CH 13 */ + if (ch->flags & IEEE80211_CHAN_PASSIVE_SCAN) + ch->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN; + return; + } + + /* + * If a country IE has been received check its rule for this + * channel first before enabling active scan. The passive scan + * would have been enforced by the initial processing of our + * custom regulatory domain. + */ + + ch = &sband->channels[11]; /* CH 12 */ + reg_rule = freq_reg_info(wiphy, ch->center_freq); + if (!IS_ERR(reg_rule)) { + if (!(reg_rule->flags & NL80211_RRF_PASSIVE_SCAN)) + if (ch->flags & IEEE80211_CHAN_PASSIVE_SCAN) + ch->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN; + } + + ch = &sband->channels[12]; /* CH 13 */ + reg_rule = freq_reg_info(wiphy, ch->center_freq); + if (!IS_ERR(reg_rule)) { + if (!(reg_rule->flags & NL80211_RRF_PASSIVE_SCAN)) + if (ch->flags & IEEE80211_CHAN_PASSIVE_SCAN) + ch->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN; + } +} + +/* Always apply Radar/DFS rules on freq range 5260 MHz - 5700 MHz */ +static void ath_reg_apply_radar_flags(struct wiphy *wiphy) +{ + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + unsigned int i; + + if (!wiphy->bands[IEEE80211_BAND_5GHZ]) + return; + + sband = wiphy->bands[IEEE80211_BAND_5GHZ]; + + for (i = 0; i < sband->n_channels; i++) { + ch = &sband->channels[i]; + if (!ath_is_radar_freq(ch->center_freq)) + continue; + /* We always enable radar detection/DFS on this + * frequency range. Additionally we also apply on + * this frequency range: + * - If STA mode does not yet have DFS supports disable + * active scanning + * - If adhoc mode does not support DFS yet then + * disable adhoc in the frequency. + * - If AP mode does not yet support radar detection/DFS + * do not allow AP mode + */ + if (!(ch->flags & IEEE80211_CHAN_DISABLED)) + ch->flags |= IEEE80211_CHAN_RADAR | + IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN; + } +} + +static void ath_reg_apply_world_flags(struct wiphy *wiphy, + enum nl80211_reg_initiator initiator, + struct ath_regulatory *reg) +{ + switch (reg->regpair->regDmnEnum) { + case 0x60: + case 0x63: + case 0x66: + case 0x67: + case 0x6C: + ath_reg_apply_beaconing_flags(wiphy, initiator); + break; + case 0x68: + ath_reg_apply_beaconing_flags(wiphy, initiator); + ath_reg_apply_active_scan_flags(wiphy, initiator); + break; + } +} + +static u16 ath_regd_find_country_by_name(char *alpha2) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(allCountries); i++) { + if (!memcmp(allCountries[i].isoName, alpha2, 2)) + return allCountries[i].countryCode; + } + + return -1; +} + +void ath_reg_notifier_apply(struct wiphy *wiphy, + struct regulatory_request *request, + struct ath_regulatory *reg) +{ + struct ath_common *common = container_of(reg, struct ath_common, + regulatory); + u16 country_code; + + /* We always apply this */ + ath_reg_apply_radar_flags(wiphy); + + /* + * This would happen when we have sent a custom regulatory request + * a world regulatory domain and the scheduler hasn't yet processed + * any pending requests in the queue. + */ + if (!request) + return; + + switch (request->initiator) { + case NL80211_REGDOM_SET_BY_CORE: + /* + * If common->reg_world_copy is world roaming it means we *were* + * world roaming... so we now have to restore that data. + */ + if (!ath_is_world_regd(&common->reg_world_copy)) + break; + + memcpy(reg, &common->reg_world_copy, + sizeof(struct ath_regulatory)); + break; + case NL80211_REGDOM_SET_BY_DRIVER: + case NL80211_REGDOM_SET_BY_USER: + break; + case NL80211_REGDOM_SET_BY_COUNTRY_IE: + if (!ath_is_world_regd(reg)) + break; + + country_code = ath_regd_find_country_by_name(request->alpha2); + if (country_code == (u16) -1) + break; + + reg->current_rd = COUNTRY_ERD_FLAG; + reg->current_rd |= country_code; + + printk(KERN_DEBUG "ath: regdomain 0x%0x updated by CountryIE\n", + reg->current_rd); + __ath_regd_init(reg); + + ath_reg_apply_world_flags(wiphy, request->initiator, reg); + + break; + } +} +EXPORT_SYMBOL(ath_reg_notifier_apply); + +static bool ath_regd_is_eeprom_valid(struct ath_regulatory *reg) +{ + u16 rd = ath_regd_get_eepromRD(reg); + int i; + + if (rd & COUNTRY_ERD_FLAG) { + /* EEPROM value is a country code */ + u16 cc = rd & ~COUNTRY_ERD_FLAG; + printk(KERN_DEBUG + "ath: EEPROM indicates we should expect " + "a country code\n"); + for (i = 0; i < ARRAY_SIZE(allCountries); i++) + if (allCountries[i].countryCode == cc) + return true; + } else { + /* EEPROM value is a regpair value */ + if (rd != CTRY_DEFAULT) + printk(KERN_DEBUG "ath: EEPROM indicates we " + "should expect a direct regpair map\n"); + for (i = 0; i < ARRAY_SIZE(regDomainPairs); i++) + if (regDomainPairs[i].regDmnEnum == rd) + return true; + } + printk(KERN_DEBUG + "ath: invalid regulatory domain/country code 0x%x\n", rd); + return false; +} + +/* EEPROM country code to regpair mapping */ +static struct country_code_to_enum_rd* +ath_regd_find_country(u16 countryCode) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(allCountries); i++) { + if (allCountries[i].countryCode == countryCode) + return &allCountries[i]; + } + return NULL; +} + +/* EEPROM rd code to regpair mapping */ +static struct country_code_to_enum_rd* +ath_regd_find_country_by_rd(int regdmn) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(allCountries); i++) { + if (allCountries[i].regDmnEnum == regdmn) + return &allCountries[i]; + } + return NULL; +} + +/* Returns the map of the EEPROM set RD to a country code */ +static u16 ath_regd_get_default_country(u16 rd) +{ + if (rd & COUNTRY_ERD_FLAG) { + struct country_code_to_enum_rd *country = NULL; + u16 cc = rd & ~COUNTRY_ERD_FLAG; + + country = ath_regd_find_country(cc); + if (country != NULL) + return cc; + } + + return CTRY_DEFAULT; +} + +static struct reg_dmn_pair_mapping* +ath_get_regpair(int regdmn) +{ + int i; + + if (regdmn == NO_ENUMRD) + return NULL; + for (i = 0; i < ARRAY_SIZE(regDomainPairs); i++) { + if (regDomainPairs[i].regDmnEnum == regdmn) + return ®DomainPairs[i]; + } + return NULL; +} + +static int +ath_regd_init_wiphy(struct ath_regulatory *reg, + struct wiphy *wiphy, + void (*reg_notifier)(struct wiphy *wiphy, + struct regulatory_request *request)) +{ + const struct ieee80211_regdomain *regd; + + wiphy->reg_notifier = reg_notifier; + wiphy->flags |= WIPHY_FLAG_STRICT_REGULATORY; + + if (ath_is_world_regd(reg)) { + /* + * Anything applied here (prior to wiphy registration) gets + * saved on the wiphy orig_* parameters + */ + regd = ath_world_regdomain(reg); + wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY; + } else { + /* + * This gets applied in the case of the absence of CRDA, + * it's our own custom world regulatory domain, similar to + * cfg80211's but we enable passive scanning. + */ + regd = ath_default_world_regdomain(); + } + wiphy_apply_custom_regulatory(wiphy, regd); + ath_reg_apply_radar_flags(wiphy); + ath_reg_apply_world_flags(wiphy, NL80211_REGDOM_SET_BY_DRIVER, reg); + return 0; +} + +/* + * Some users have reported their EEPROM programmed with + * 0x8000 set, this is not a supported regulatory domain + * but since we have more than one user with it we need + * a solution for them. We default to 0x64, which is the + * default Atheros world regulatory domain. + */ +static void ath_regd_sanitize(struct ath_regulatory *reg) +{ + if (reg->current_rd != COUNTRY_ERD_FLAG) + return; + printk(KERN_DEBUG "ath: EEPROM regdomain sanitized\n"); + reg->current_rd = 0x64; +} + +static int __ath_regd_init(struct ath_regulatory *reg) +{ + struct country_code_to_enum_rd *country = NULL; + u16 regdmn; + + if (!reg) + return -EINVAL; + + ath_regd_sanitize(reg); + + printk(KERN_DEBUG "ath: EEPROM regdomain: 0x%0x\n", reg->current_rd); + + if (!ath_regd_is_eeprom_valid(reg)) { + pr_err("Invalid EEPROM contents\n"); + return -EINVAL; + } + + regdmn = ath_regd_get_eepromRD(reg); + reg->country_code = ath_regd_get_default_country(regdmn); + + if (reg->country_code == CTRY_DEFAULT && + regdmn == CTRY_DEFAULT) { + printk(KERN_DEBUG "ath: EEPROM indicates default " + "country code should be used\n"); + reg->country_code = CTRY_UNITED_STATES; + } + + if (reg->country_code == CTRY_DEFAULT) { + country = NULL; + } else { + printk(KERN_DEBUG "ath: doing EEPROM country->regdmn " + "map search\n"); + country = ath_regd_find_country(reg->country_code); + if (country == NULL) { + printk(KERN_DEBUG + "ath: no valid country maps found for " + "country code: 0x%0x\n", + reg->country_code); + return -EINVAL; + } else { + regdmn = country->regDmnEnum; + printk(KERN_DEBUG "ath: country maps to " + "regdmn code: 0x%0x\n", + regdmn); + } + } + + reg->regpair = ath_get_regpair(regdmn); + + if (!reg->regpair) { + printk(KERN_DEBUG "ath: " + "No regulatory domain pair found, cannot continue\n"); + return -EINVAL; + } + + if (!country) + country = ath_regd_find_country_by_rd(regdmn); + + if (country) { + reg->alpha2[0] = country->isoName[0]; + reg->alpha2[1] = country->isoName[1]; + } else { + reg->alpha2[0] = '0'; + reg->alpha2[1] = '0'; + } + + printk(KERN_DEBUG "ath: Country alpha2 being used: %c%c\n", + reg->alpha2[0], reg->alpha2[1]); + printk(KERN_DEBUG "ath: Regpair used: 0x%0x\n", + reg->regpair->regDmnEnum); + + return 0; +} + +int +ath_regd_init(struct ath_regulatory *reg, + struct wiphy *wiphy, + void (*reg_notifier)(struct wiphy *wiphy, + struct regulatory_request *request)) +{ + struct ath_common *common = container_of(reg, struct ath_common, + regulatory); + int r; + + r = __ath_regd_init(reg); + if (r) + return r; + + if (ath_is_world_regd(reg)) + memcpy(&common->reg_world_copy, reg, + sizeof(struct ath_regulatory)); + + ath_regd_init_wiphy(reg, wiphy, reg_notifier); + + return 0; +} +EXPORT_SYMBOL(ath_regd_init); + +u32 ath_regd_get_band_ctl(struct ath_regulatory *reg, + enum ieee80211_band band) +{ + if (!reg->regpair || + (reg->country_code == CTRY_DEFAULT && + is_wwr_sku(ath_regd_get_eepromRD(reg)))) { + return SD_NO_CTL; + } + + switch (band) { + case IEEE80211_BAND_2GHZ: + return reg->regpair->reg_2ghz_ctl; + case IEEE80211_BAND_5GHZ: + return reg->regpair->reg_5ghz_ctl; + default: + return NO_CTL; + } +} +EXPORT_SYMBOL(ath_regd_get_band_ctl); diff --git a/drivers/net/wireless/ath/regd.h b/drivers/net/wireless/ath/regd.h new file mode 100644 index 0000000..37f53bd --- /dev/null +++ b/drivers/net/wireless/ath/regd.h @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2008-2009 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef REGD_H +#define REGD_H + +#include +#include + +#include "ath.h" + +enum ctl_group { + CTL_FCC = 0x10, + CTL_MKK = 0x40, + CTL_ETSI = 0x30, +}; + +#define NO_CTL 0xff +#define SD_NO_CTL 0xE0 +#define NO_CTL 0xff +#define CTL_11A 0 +#define CTL_11B 1 +#define CTL_11G 2 +#define CTL_2GHT20 5 +#define CTL_5GHT20 6 +#define CTL_2GHT40 7 +#define CTL_5GHT40 8 + +#define CTRY_DEBUG 0x1ff +#define CTRY_DEFAULT 0 + +#define COUNTRY_ERD_FLAG 0x8000 +#define WORLDWIDE_ROAMING_FLAG 0x4000 + +#define MULTI_DOMAIN_MASK 0xFF00 + +#define WORLD_SKU_MASK 0x00F0 +#define WORLD_SKU_PREFIX 0x0060 + +#define CHANNEL_HALF_BW 10 +#define CHANNEL_QUARTER_BW 5 + +struct country_code_to_enum_rd { + u16 countryCode; + u16 regDmnEnum; + const char *isoName; +}; + +enum CountryCode { + CTRY_ALBANIA = 8, + CTRY_ALGERIA = 12, + CTRY_ARGENTINA = 32, + CTRY_ARMENIA = 51, + CTRY_ARUBA = 533, + CTRY_AUSTRALIA = 36, + CTRY_AUSTRIA = 40, + CTRY_AZERBAIJAN = 31, + CTRY_BAHRAIN = 48, + CTRY_BANGLADESH = 50, + CTRY_BARBADOS = 52, + CTRY_BELARUS = 112, + CTRY_BELGIUM = 56, + CTRY_BELIZE = 84, + CTRY_BOLIVIA = 68, + CTRY_BOSNIA_HERZ = 70, + CTRY_BRAZIL = 76, + CTRY_BRUNEI_DARUSSALAM = 96, + CTRY_BULGARIA = 100, + CTRY_CAMBODIA = 116, + CTRY_CANADA = 124, + CTRY_CHILE = 152, + CTRY_CHINA = 156, + CTRY_COLOMBIA = 170, + CTRY_COSTA_RICA = 188, + CTRY_CROATIA = 191, + CTRY_CYPRUS = 196, + CTRY_CZECH = 203, + CTRY_DENMARK = 208, + CTRY_DOMINICAN_REPUBLIC = 214, + CTRY_ECUADOR = 218, + CTRY_EGYPT = 818, + CTRY_EL_SALVADOR = 222, + CTRY_ESTONIA = 233, + CTRY_FAEROE_ISLANDS = 234, + CTRY_FINLAND = 246, + CTRY_FRANCE = 250, + CTRY_GEORGIA = 268, + CTRY_GERMANY = 276, + CTRY_GREECE = 300, + CTRY_GREENLAND = 304, + CTRY_GRENADA = 308, + CTRY_GUAM = 316, + CTRY_GUATEMALA = 320, + CTRY_HAITI = 332, + CTRY_HONDURAS = 340, + CTRY_HONG_KONG = 344, + CTRY_HUNGARY = 348, + CTRY_ICELAND = 352, + CTRY_INDIA = 356, + CTRY_INDONESIA = 360, + CTRY_IRAN = 364, + CTRY_IRAQ = 368, + CTRY_IRELAND = 372, + CTRY_ISRAEL = 376, + CTRY_ITALY = 380, + CTRY_JAMAICA = 388, + CTRY_JAPAN = 392, + CTRY_JORDAN = 400, + CTRY_KAZAKHSTAN = 398, + CTRY_KENYA = 404, + CTRY_KOREA_NORTH = 408, + CTRY_KOREA_ROC = 410, + CTRY_KOREA_ROC2 = 411, + CTRY_KOREA_ROC3 = 412, + CTRY_KUWAIT = 414, + CTRY_LATVIA = 428, + CTRY_LEBANON = 422, + CTRY_LIBYA = 434, + CTRY_LIECHTENSTEIN = 438, + CTRY_LITHUANIA = 440, + CTRY_LUXEMBOURG = 442, + CTRY_MACAU = 446, + CTRY_MACEDONIA = 807, + CTRY_MALAYSIA = 458, + CTRY_MALTA = 470, + CTRY_MEXICO = 484, + CTRY_MONACO = 492, + CTRY_MOROCCO = 504, + CTRY_NEPAL = 524, + CTRY_NETHERLANDS = 528, + CTRY_NETHERLANDS_ANTILLES = 530, + CTRY_NEW_ZEALAND = 554, + CTRY_NICARAGUA = 558, + CTRY_NORWAY = 578, + CTRY_OMAN = 512, + CTRY_PAKISTAN = 586, + CTRY_PANAMA = 591, + CTRY_PAPUA_NEW_GUINEA = 598, + CTRY_PARAGUAY = 600, + CTRY_PERU = 604, + CTRY_PHILIPPINES = 608, + CTRY_POLAND = 616, + CTRY_PORTUGAL = 620, + CTRY_PUERTO_RICO = 630, + CTRY_QATAR = 634, + CTRY_ROMANIA = 642, + CTRY_RUSSIA = 643, + CTRY_SAUDI_ARABIA = 682, + CTRY_SERBIA_MONTENEGRO = 891, + CTRY_SINGAPORE = 702, + CTRY_SLOVAKIA = 703, + CTRY_SLOVENIA = 705, + CTRY_SOUTH_AFRICA = 710, + CTRY_SPAIN = 724, + CTRY_SRI_LANKA = 144, + CTRY_SWEDEN = 752, + CTRY_SWITZERLAND = 756, + CTRY_SYRIA = 760, + CTRY_TAIWAN = 158, + CTRY_THAILAND = 764, + CTRY_TRINIDAD_Y_TOBAGO = 780, + CTRY_TUNISIA = 788, + CTRY_TURKEY = 792, + CTRY_UAE = 784, + CTRY_UKRAINE = 804, + CTRY_UNITED_KINGDOM = 826, + CTRY_UNITED_STATES = 840, + CTRY_UNITED_STATES_FCC49 = 842, + CTRY_URUGUAY = 858, + CTRY_UZBEKISTAN = 860, + CTRY_VENEZUELA = 862, + CTRY_VIET_NAM = 704, + CTRY_YEMEN = 887, + CTRY_ZIMBABWE = 716, + CTRY_JAPAN1 = 393, + CTRY_JAPAN2 = 394, + CTRY_JAPAN3 = 395, + CTRY_JAPAN4 = 396, + CTRY_JAPAN5 = 397, + CTRY_JAPAN6 = 4006, + CTRY_JAPAN7 = 4007, + CTRY_JAPAN8 = 4008, + CTRY_JAPAN9 = 4009, + CTRY_JAPAN10 = 4010, + CTRY_JAPAN11 = 4011, + CTRY_JAPAN12 = 4012, + CTRY_JAPAN13 = 4013, + CTRY_JAPAN14 = 4014, + CTRY_JAPAN15 = 4015, + CTRY_JAPAN16 = 4016, + CTRY_JAPAN17 = 4017, + CTRY_JAPAN18 = 4018, + CTRY_JAPAN19 = 4019, + CTRY_JAPAN20 = 4020, + CTRY_JAPAN21 = 4021, + CTRY_JAPAN22 = 4022, + CTRY_JAPAN23 = 4023, + CTRY_JAPAN24 = 4024, + CTRY_JAPAN25 = 4025, + CTRY_JAPAN26 = 4026, + CTRY_JAPAN27 = 4027, + CTRY_JAPAN28 = 4028, + CTRY_JAPAN29 = 4029, + CTRY_JAPAN30 = 4030, + CTRY_JAPAN31 = 4031, + CTRY_JAPAN32 = 4032, + CTRY_JAPAN33 = 4033, + CTRY_JAPAN34 = 4034, + CTRY_JAPAN35 = 4035, + CTRY_JAPAN36 = 4036, + CTRY_JAPAN37 = 4037, + CTRY_JAPAN38 = 4038, + CTRY_JAPAN39 = 4039, + CTRY_JAPAN40 = 4040, + CTRY_JAPAN41 = 4041, + CTRY_JAPAN42 = 4042, + CTRY_JAPAN43 = 4043, + CTRY_JAPAN44 = 4044, + CTRY_JAPAN45 = 4045, + CTRY_JAPAN46 = 4046, + CTRY_JAPAN47 = 4047, + CTRY_JAPAN48 = 4048, + CTRY_JAPAN49 = 4049, + CTRY_JAPAN50 = 4050, + CTRY_JAPAN51 = 4051, + CTRY_JAPAN52 = 4052, + CTRY_JAPAN53 = 4053, + CTRY_JAPAN54 = 4054, + CTRY_JAPAN55 = 4055, + CTRY_JAPAN56 = 4056, + CTRY_JAPAN57 = 4057, + CTRY_JAPAN58 = 4058, + CTRY_JAPAN59 = 4059, + CTRY_AUSTRALIA2 = 5000, + CTRY_CANADA2 = 5001, + CTRY_BELGIUM2 = 5002 +}; + +bool ath_is_world_regd(struct ath_regulatory *reg); +bool ath_is_49ghz_allowed(u16 redomain); +int ath_regd_init(struct ath_regulatory *reg, struct wiphy *wiphy, + void (*reg_notifier)(struct wiphy *wiphy, + struct regulatory_request *request)); +u32 ath_regd_get_band_ctl(struct ath_regulatory *reg, + enum ieee80211_band band); +void ath_reg_notifier_apply(struct wiphy *wiphy, + struct regulatory_request *request, + struct ath_regulatory *reg); + +#endif diff --git a/drivers/net/wireless/ath/regd_common.h b/drivers/net/wireless/ath/regd_common.h new file mode 100644 index 0000000..bdd2b4d --- /dev/null +++ b/drivers/net/wireless/ath/regd_common.h @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2008-2009 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef REGD_COMMON_H +#define REGD_COMMON_H + +enum EnumRd { + NO_ENUMRD = 0x00, + NULL1_WORLD = 0x03, + NULL1_ETSIB = 0x07, + NULL1_ETSIC = 0x08, + FCC1_FCCA = 0x10, + FCC1_WORLD = 0x11, + FCC4_FCCA = 0x12, + FCC5_FCCA = 0x13, + FCC6_FCCA = 0x14, + + FCC2_FCCA = 0x20, + FCC2_WORLD = 0x21, + FCC2_ETSIC = 0x22, + FCC6_WORLD = 0x23, + FRANCE_RES = 0x31, + FCC3_FCCA = 0x3A, + FCC3_WORLD = 0x3B, + + ETSI1_WORLD = 0x37, + ETSI3_ETSIA = 0x32, + ETSI2_WORLD = 0x35, + ETSI3_WORLD = 0x36, + ETSI4_WORLD = 0x30, + ETSI4_ETSIC = 0x38, + ETSI5_WORLD = 0x39, + ETSI6_WORLD = 0x34, + ETSI_RESERVED = 0x33, + + MKK1_MKKA = 0x40, + MKK1_MKKB = 0x41, + APL4_WORLD = 0x42, + MKK2_MKKA = 0x43, + APL_RESERVED = 0x44, + APL2_WORLD = 0x45, + APL2_APLC = 0x46, + APL3_WORLD = 0x47, + MKK1_FCCA = 0x48, + APL2_APLD = 0x49, + MKK1_MKKA1 = 0x4A, + MKK1_MKKA2 = 0x4B, + MKK1_MKKC = 0x4C, + + APL3_FCCA = 0x50, + APL1_WORLD = 0x52, + APL1_FCCA = 0x53, + APL1_APLA = 0x54, + APL1_ETSIC = 0x55, + APL2_ETSIC = 0x56, + APL5_WORLD = 0x58, + APL6_WORLD = 0x5B, + APL7_FCCA = 0x5C, + APL8_WORLD = 0x5D, + APL9_WORLD = 0x5E, + + WOR0_WORLD = 0x60, + WOR1_WORLD = 0x61, + WOR2_WORLD = 0x62, + WOR3_WORLD = 0x63, + WOR4_WORLD = 0x64, + WOR5_ETSIC = 0x65, + + WOR01_WORLD = 0x66, + WOR02_WORLD = 0x67, + EU1_WORLD = 0x68, + + WOR9_WORLD = 0x69, + WORA_WORLD = 0x6A, + WORB_WORLD = 0x6B, + WORC_WORLD = 0x6C, + + MKK3_MKKB = 0x80, + MKK3_MKKA2 = 0x81, + MKK3_MKKC = 0x82, + + MKK4_MKKB = 0x83, + MKK4_MKKA2 = 0x84, + MKK4_MKKC = 0x85, + + MKK5_MKKB = 0x86, + MKK5_MKKA2 = 0x87, + MKK5_MKKC = 0x88, + + MKK6_MKKB = 0x89, + MKK6_MKKA2 = 0x8A, + MKK6_MKKC = 0x8B, + + MKK7_MKKB = 0x8C, + MKK7_MKKA2 = 0x8D, + MKK7_MKKC = 0x8E, + + MKK8_MKKB = 0x8F, + MKK8_MKKA2 = 0x90, + MKK8_MKKC = 0x91, + + MKK14_MKKA1 = 0x92, + MKK15_MKKA1 = 0x93, + + MKK10_FCCA = 0xD0, + MKK10_MKKA1 = 0xD1, + MKK10_MKKC = 0xD2, + MKK10_MKKA2 = 0xD3, + + MKK11_MKKA = 0xD4, + MKK11_FCCA = 0xD5, + MKK11_MKKA1 = 0xD6, + MKK11_MKKC = 0xD7, + MKK11_MKKA2 = 0xD8, + + MKK12_MKKA = 0xD9, + MKK12_FCCA = 0xDA, + MKK12_MKKA1 = 0xDB, + MKK12_MKKC = 0xDC, + MKK12_MKKA2 = 0xDD, + + MKK13_MKKB = 0xDE, + + MKK3_MKKA = 0xF0, + MKK3_MKKA1 = 0xF1, + MKK3_FCCA = 0xF2, + MKK4_MKKA = 0xF3, + MKK4_MKKA1 = 0xF4, + MKK4_FCCA = 0xF5, + MKK9_MKKA = 0xF6, + MKK10_MKKA = 0xF7, + MKK6_MKKA1 = 0xF8, + MKK6_FCCA = 0xF9, + MKK7_MKKA1 = 0xFA, + MKK7_FCCA = 0xFB, + MKK9_FCCA = 0xFC, + MKK9_MKKA1 = 0xFD, + MKK9_MKKC = 0xFE, + MKK9_MKKA2 = 0xFF, + + WORLD = 0x0199, + DEBUG_REG_DMN = 0x01ff, +}; + +/* Regpair to CTL band mapping */ +static struct reg_dmn_pair_mapping regDomainPairs[] = { + /* regpair, 5 GHz CTL, 2 GHz CTL */ + {NO_ENUMRD, DEBUG_REG_DMN, DEBUG_REG_DMN}, + {NULL1_WORLD, NO_CTL, CTL_ETSI}, + {NULL1_ETSIB, NO_CTL, CTL_ETSI}, + {NULL1_ETSIC, NO_CTL, CTL_ETSI}, + + {FCC2_FCCA, CTL_FCC, CTL_FCC}, + {FCC2_WORLD, CTL_FCC, CTL_ETSI}, + {FCC2_ETSIC, CTL_FCC, CTL_ETSI}, + {FCC3_FCCA, CTL_FCC, CTL_FCC}, + {FCC3_WORLD, CTL_FCC, CTL_ETSI}, + {FCC4_FCCA, CTL_FCC, CTL_FCC}, + {FCC5_FCCA, CTL_FCC, CTL_FCC}, + {FCC6_FCCA, CTL_FCC, CTL_FCC}, + {FCC6_WORLD, CTL_FCC, CTL_ETSI}, + + {ETSI1_WORLD, CTL_ETSI, CTL_ETSI}, + {ETSI2_WORLD, CTL_ETSI, CTL_ETSI}, + {ETSI3_WORLD, CTL_ETSI, CTL_ETSI}, + {ETSI4_WORLD, CTL_ETSI, CTL_ETSI}, + {ETSI5_WORLD, CTL_ETSI, CTL_ETSI}, + {ETSI6_WORLD, CTL_ETSI, CTL_ETSI}, + + /* XXX: For ETSI3_ETSIA, Was NO_CTL meant for the 2 GHz band ? */ + {ETSI3_ETSIA, CTL_ETSI, CTL_ETSI}, + {FRANCE_RES, CTL_ETSI, CTL_ETSI}, + + {FCC1_WORLD, CTL_FCC, CTL_ETSI}, + {FCC1_FCCA, CTL_FCC, CTL_FCC}, + {APL1_WORLD, CTL_FCC, CTL_ETSI}, + {APL2_WORLD, CTL_FCC, CTL_ETSI}, + {APL3_WORLD, CTL_FCC, CTL_ETSI}, + {APL4_WORLD, CTL_FCC, CTL_ETSI}, + {APL5_WORLD, CTL_FCC, CTL_ETSI}, + {APL6_WORLD, CTL_ETSI, CTL_ETSI}, + {APL8_WORLD, CTL_ETSI, CTL_ETSI}, + {APL9_WORLD, CTL_ETSI, CTL_ETSI}, + + {APL3_FCCA, CTL_FCC, CTL_FCC}, + {APL7_FCCA, CTL_FCC, CTL_FCC}, + {APL1_ETSIC, CTL_FCC, CTL_ETSI}, + {APL2_ETSIC, CTL_FCC, CTL_ETSI}, + {APL2_APLD, CTL_FCC, NO_CTL}, + + {MKK1_MKKA, CTL_MKK, CTL_MKK}, + {MKK1_MKKB, CTL_MKK, CTL_MKK}, + {MKK1_FCCA, CTL_MKK, CTL_FCC}, + {MKK1_MKKA1, CTL_MKK, CTL_MKK}, + {MKK1_MKKA2, CTL_MKK, CTL_MKK}, + {MKK1_MKKC, CTL_MKK, CTL_MKK}, + + {MKK2_MKKA, CTL_MKK, CTL_MKK}, + {MKK3_MKKA, CTL_MKK, CTL_MKK}, + {MKK3_MKKB, CTL_MKK, CTL_MKK}, + {MKK3_MKKA1, CTL_MKK, CTL_MKK}, + {MKK3_MKKA2, CTL_MKK, CTL_MKK}, + {MKK3_MKKC, CTL_MKK, CTL_MKK}, + {MKK3_FCCA, CTL_MKK, CTL_FCC}, + + {MKK4_MKKA, CTL_MKK, CTL_MKK}, + {MKK4_MKKB, CTL_MKK, CTL_MKK}, + {MKK4_MKKA1, CTL_MKK, CTL_MKK}, + {MKK4_MKKA2, CTL_MKK, CTL_MKK}, + {MKK4_MKKC, CTL_MKK, CTL_MKK}, + {MKK4_FCCA, CTL_MKK, CTL_FCC}, + + {MKK5_MKKB, CTL_MKK, CTL_MKK}, + {MKK5_MKKA2, CTL_MKK, CTL_MKK}, + {MKK5_MKKC, CTL_MKK, CTL_MKK}, + + {MKK6_MKKB, CTL_MKK, CTL_MKK}, + {MKK6_MKKA1, CTL_MKK, CTL_MKK}, + {MKK6_MKKA2, CTL_MKK, CTL_MKK}, + {MKK6_MKKC, CTL_MKK, CTL_MKK}, + {MKK6_FCCA, CTL_MKK, CTL_FCC}, + + {MKK7_MKKB, CTL_MKK, CTL_MKK}, + {MKK7_MKKA1, CTL_MKK, CTL_MKK}, + {MKK7_MKKA2, CTL_MKK, CTL_MKK}, + {MKK7_MKKC, CTL_MKK, CTL_MKK}, + {MKK7_FCCA, CTL_MKK, CTL_FCC}, + + {MKK8_MKKB, CTL_MKK, CTL_MKK}, + {MKK8_MKKA2, CTL_MKK, CTL_MKK}, + {MKK8_MKKC, CTL_MKK, CTL_MKK}, + + {MKK9_MKKA, CTL_MKK, CTL_MKK}, + {MKK9_FCCA, CTL_MKK, CTL_FCC}, + {MKK9_MKKA1, CTL_MKK, CTL_MKK}, + {MKK9_MKKA2, CTL_MKK, CTL_MKK}, + {MKK9_MKKC, CTL_MKK, CTL_MKK}, + + {MKK10_MKKA, CTL_MKK, CTL_MKK}, + {MKK10_FCCA, CTL_MKK, CTL_FCC}, + {MKK10_MKKA1, CTL_MKK, CTL_MKK}, + {MKK10_MKKA2, CTL_MKK, CTL_MKK}, + {MKK10_MKKC, CTL_MKK, CTL_MKK}, + + {MKK11_MKKA, CTL_MKK, CTL_MKK}, + {MKK11_FCCA, CTL_MKK, CTL_FCC}, + {MKK11_MKKA1, CTL_MKK, CTL_MKK}, + {MKK11_MKKA2, CTL_MKK, CTL_MKK}, + {MKK11_MKKC, CTL_MKK, CTL_MKK}, + + {MKK12_MKKA, CTL_MKK, CTL_MKK}, + {MKK12_FCCA, CTL_MKK, CTL_FCC}, + {MKK12_MKKA1, CTL_MKK, CTL_MKK}, + {MKK12_MKKA2, CTL_MKK, CTL_MKK}, + {MKK12_MKKC, CTL_MKK, CTL_MKK}, + + {MKK13_MKKB, CTL_MKK, CTL_MKK}, + {MKK14_MKKA1, CTL_MKK, CTL_MKK}, + {MKK15_MKKA1, CTL_MKK, CTL_MKK}, + + {WOR0_WORLD, NO_CTL, NO_CTL}, + {WOR1_WORLD, NO_CTL, NO_CTL}, + {WOR2_WORLD, NO_CTL, NO_CTL}, + {WOR3_WORLD, NO_CTL, NO_CTL}, + {WOR4_WORLD, NO_CTL, NO_CTL}, + {WOR5_ETSIC, NO_CTL, NO_CTL}, + {WOR01_WORLD, NO_CTL, NO_CTL}, + {WOR02_WORLD, NO_CTL, NO_CTL}, + {EU1_WORLD, NO_CTL, NO_CTL}, + {WOR9_WORLD, NO_CTL, NO_CTL}, + {WORA_WORLD, NO_CTL, NO_CTL}, + {WORB_WORLD, NO_CTL, NO_CTL}, + {WORC_WORLD, NO_CTL, NO_CTL}, +}; + +static struct country_code_to_enum_rd allCountries[] = { + {CTRY_DEBUG, NO_ENUMRD, "DB"}, + {CTRY_DEFAULT, FCC1_FCCA, "CO"}, + {CTRY_ALBANIA, NULL1_WORLD, "AL"}, + {CTRY_ALGERIA, NULL1_WORLD, "DZ"}, + {CTRY_ARGENTINA, FCC3_WORLD, "AR"}, + {CTRY_ARMENIA, ETSI4_WORLD, "AM"}, + {CTRY_ARUBA, ETSI1_WORLD, "AW"}, + {CTRY_AUSTRALIA, FCC2_WORLD, "AU"}, + {CTRY_AUSTRALIA2, FCC6_WORLD, "AU"}, + {CTRY_AUSTRIA, ETSI1_WORLD, "AT"}, + {CTRY_AZERBAIJAN, ETSI4_WORLD, "AZ"}, + {CTRY_BAHRAIN, APL6_WORLD, "BH"}, + {CTRY_BANGLADESH, NULL1_WORLD, "BD"}, + {CTRY_BARBADOS, FCC2_WORLD, "BB"}, + {CTRY_BELARUS, ETSI1_WORLD, "BY"}, + {CTRY_BELGIUM, ETSI1_WORLD, "BE"}, + {CTRY_BELGIUM2, ETSI4_WORLD, "BL"}, + {CTRY_BELIZE, APL1_ETSIC, "BZ"}, + {CTRY_BOLIVIA, APL1_ETSIC, "BO"}, + {CTRY_BOSNIA_HERZ, ETSI1_WORLD, "BA"}, + {CTRY_BRAZIL, FCC3_WORLD, "BR"}, + {CTRY_BRUNEI_DARUSSALAM, APL1_WORLD, "BN"}, + {CTRY_BULGARIA, ETSI6_WORLD, "BG"}, + {CTRY_CAMBODIA, ETSI1_WORLD, "KH"}, + {CTRY_CANADA, FCC3_FCCA, "CA"}, + {CTRY_CANADA2, FCC6_FCCA, "CA"}, + {CTRY_CHILE, APL6_WORLD, "CL"}, + {CTRY_CHINA, APL1_WORLD, "CN"}, + {CTRY_COLOMBIA, FCC1_FCCA, "CO"}, + {CTRY_COSTA_RICA, FCC1_WORLD, "CR"}, + {CTRY_CROATIA, ETSI1_WORLD, "HR"}, + {CTRY_CYPRUS, ETSI1_WORLD, "CY"}, + {CTRY_CZECH, ETSI3_WORLD, "CZ"}, + {CTRY_DENMARK, ETSI1_WORLD, "DK"}, + {CTRY_DOMINICAN_REPUBLIC, FCC1_FCCA, "DO"}, + {CTRY_ECUADOR, FCC1_WORLD, "EC"}, + {CTRY_EGYPT, ETSI3_WORLD, "EG"}, + {CTRY_EL_SALVADOR, FCC1_WORLD, "SV"}, + {CTRY_ESTONIA, ETSI1_WORLD, "EE"}, + {CTRY_FINLAND, ETSI1_WORLD, "FI"}, + {CTRY_FRANCE, ETSI1_WORLD, "FR"}, + {CTRY_GEORGIA, ETSI4_WORLD, "GE"}, + {CTRY_GERMANY, ETSI1_WORLD, "DE"}, + {CTRY_GREECE, ETSI1_WORLD, "GR"}, + {CTRY_GREENLAND, ETSI1_WORLD, "GL"}, + {CTRY_GRENADA, FCC3_FCCA, "GD"}, + {CTRY_GUAM, FCC1_FCCA, "GU"}, + {CTRY_GUATEMALA, FCC1_FCCA, "GT"}, + {CTRY_HAITI, ETSI1_WORLD, "HT"}, + {CTRY_HONDURAS, NULL1_WORLD, "HN"}, + {CTRY_HONG_KONG, FCC3_WORLD, "HK"}, + {CTRY_HUNGARY, ETSI1_WORLD, "HU"}, + {CTRY_ICELAND, ETSI1_WORLD, "IS"}, + {CTRY_INDIA, APL6_WORLD, "IN"}, + {CTRY_INDONESIA, NULL1_WORLD, "ID"}, + {CTRY_IRAN, APL1_WORLD, "IR"}, + {CTRY_IRELAND, ETSI1_WORLD, "IE"}, + {CTRY_ISRAEL, NULL1_WORLD, "IL"}, + {CTRY_ITALY, ETSI1_WORLD, "IT"}, + {CTRY_JAMAICA, FCC3_WORLD, "JM"}, + + {CTRY_JAPAN, MKK1_MKKA, "JP"}, + {CTRY_JAPAN1, MKK1_MKKB, "JP"}, + {CTRY_JAPAN2, MKK1_FCCA, "JP"}, + {CTRY_JAPAN3, MKK2_MKKA, "JP"}, + {CTRY_JAPAN4, MKK1_MKKA1, "JP"}, + {CTRY_JAPAN5, MKK1_MKKA2, "JP"}, + {CTRY_JAPAN6, MKK1_MKKC, "JP"}, + {CTRY_JAPAN7, MKK3_MKKB, "JP"}, + {CTRY_JAPAN8, MKK3_MKKA2, "JP"}, + {CTRY_JAPAN9, MKK3_MKKC, "JP"}, + {CTRY_JAPAN10, MKK4_MKKB, "JP"}, + {CTRY_JAPAN11, MKK4_MKKA2, "JP"}, + {CTRY_JAPAN12, MKK4_MKKC, "JP"}, + {CTRY_JAPAN13, MKK5_MKKB, "JP"}, + {CTRY_JAPAN14, MKK5_MKKA2, "JP"}, + {CTRY_JAPAN15, MKK5_MKKC, "JP"}, + {CTRY_JAPAN16, MKK6_MKKB, "JP"}, + {CTRY_JAPAN17, MKK6_MKKA2, "JP"}, + {CTRY_JAPAN18, MKK6_MKKC, "JP"}, + {CTRY_JAPAN19, MKK7_MKKB, "JP"}, + {CTRY_JAPAN20, MKK7_MKKA2, "JP"}, + {CTRY_JAPAN21, MKK7_MKKC, "JP"}, + {CTRY_JAPAN22, MKK8_MKKB, "JP"}, + {CTRY_JAPAN23, MKK8_MKKA2, "JP"}, + {CTRY_JAPAN24, MKK8_MKKC, "JP"}, + {CTRY_JAPAN25, MKK3_MKKA, "JP"}, + {CTRY_JAPAN26, MKK3_MKKA1, "JP"}, + {CTRY_JAPAN27, MKK3_FCCA, "JP"}, + {CTRY_JAPAN28, MKK4_MKKA1, "JP"}, + {CTRY_JAPAN29, MKK4_FCCA, "JP"}, + {CTRY_JAPAN30, MKK6_MKKA1, "JP"}, + {CTRY_JAPAN31, MKK6_FCCA, "JP"}, + {CTRY_JAPAN32, MKK7_MKKA1, "JP"}, + {CTRY_JAPAN33, MKK7_FCCA, "JP"}, + {CTRY_JAPAN34, MKK9_MKKA, "JP"}, + {CTRY_JAPAN35, MKK10_MKKA, "JP"}, + {CTRY_JAPAN36, MKK4_MKKA, "JP"}, + {CTRY_JAPAN37, MKK9_FCCA, "JP"}, + {CTRY_JAPAN38, MKK9_MKKA1, "JP"}, + {CTRY_JAPAN39, MKK9_MKKC, "JP"}, + {CTRY_JAPAN40, MKK9_MKKA2, "JP"}, + {CTRY_JAPAN41, MKK10_FCCA, "JP"}, + {CTRY_JAPAN42, MKK10_MKKA1, "JP"}, + {CTRY_JAPAN43, MKK10_MKKC, "JP"}, + {CTRY_JAPAN44, MKK10_MKKA2, "JP"}, + {CTRY_JAPAN45, MKK11_MKKA, "JP"}, + {CTRY_JAPAN46, MKK11_FCCA, "JP"}, + {CTRY_JAPAN47, MKK11_MKKA1, "JP"}, + {CTRY_JAPAN48, MKK11_MKKC, "JP"}, + {CTRY_JAPAN49, MKK11_MKKA2, "JP"}, + {CTRY_JAPAN50, MKK12_MKKA, "JP"}, + {CTRY_JAPAN51, MKK12_FCCA, "JP"}, + {CTRY_JAPAN52, MKK12_MKKA1, "JP"}, + {CTRY_JAPAN53, MKK12_MKKC, "JP"}, + {CTRY_JAPAN54, MKK12_MKKA2, "JP"}, + {CTRY_JAPAN57, MKK13_MKKB, "JP"}, + {CTRY_JAPAN58, MKK14_MKKA1, "JP"}, + {CTRY_JAPAN59, MKK15_MKKA1, "JP"}, + + {CTRY_JORDAN, ETSI2_WORLD, "JO"}, + {CTRY_KAZAKHSTAN, NULL1_WORLD, "KZ"}, + {CTRY_KOREA_NORTH, APL9_WORLD, "KP"}, + {CTRY_KOREA_ROC, APL9_WORLD, "KR"}, + {CTRY_KOREA_ROC2, APL2_WORLD, "K2"}, + {CTRY_KOREA_ROC3, APL9_WORLD, "K3"}, + {CTRY_KUWAIT, ETSI3_WORLD, "KW"}, + {CTRY_LATVIA, ETSI1_WORLD, "LV"}, + {CTRY_LEBANON, NULL1_WORLD, "LB"}, + {CTRY_LIECHTENSTEIN, ETSI1_WORLD, "LI"}, + {CTRY_LITHUANIA, ETSI1_WORLD, "LT"}, + {CTRY_LUXEMBOURG, ETSI1_WORLD, "LU"}, + {CTRY_MACAU, FCC2_WORLD, "MO"}, + {CTRY_MACEDONIA, NULL1_WORLD, "MK"}, + {CTRY_MALAYSIA, APL8_WORLD, "MY"}, + {CTRY_MALTA, ETSI1_WORLD, "MT"}, + {CTRY_MEXICO, FCC1_FCCA, "MX"}, + {CTRY_MONACO, ETSI4_WORLD, "MC"}, + {CTRY_MOROCCO, APL4_WORLD, "MA"}, + {CTRY_NEPAL, APL1_WORLD, "NP"}, + {CTRY_NETHERLANDS, ETSI1_WORLD, "NL"}, + {CTRY_NETHERLANDS_ANTILLES, ETSI1_WORLD, "AN"}, + {CTRY_NEW_ZEALAND, FCC2_ETSIC, "NZ"}, + {CTRY_NORWAY, ETSI1_WORLD, "NO"}, + {CTRY_OMAN, FCC3_WORLD, "OM"}, + {CTRY_PAKISTAN, NULL1_WORLD, "PK"}, + {CTRY_PANAMA, FCC1_FCCA, "PA"}, + {CTRY_PAPUA_NEW_GUINEA, FCC1_WORLD, "PG"}, + {CTRY_PERU, APL1_WORLD, "PE"}, + {CTRY_PHILIPPINES, APL1_WORLD, "PH"}, + {CTRY_POLAND, ETSI1_WORLD, "PL"}, + {CTRY_PORTUGAL, ETSI1_WORLD, "PT"}, + {CTRY_PUERTO_RICO, FCC1_FCCA, "PR"}, + {CTRY_QATAR, APL1_WORLD, "QA"}, + {CTRY_ROMANIA, NULL1_WORLD, "RO"}, + {CTRY_RUSSIA, NULL1_WORLD, "RU"}, + {CTRY_SAUDI_ARABIA, NULL1_WORLD, "SA"}, + {CTRY_SERBIA_MONTENEGRO, ETSI1_WORLD, "CS"}, + {CTRY_SINGAPORE, APL6_WORLD, "SG"}, + {CTRY_SLOVAKIA, ETSI1_WORLD, "SK"}, + {CTRY_SLOVENIA, ETSI1_WORLD, "SI"}, + {CTRY_SOUTH_AFRICA, FCC3_WORLD, "ZA"}, + {CTRY_SPAIN, ETSI1_WORLD, "ES"}, + {CTRY_SRI_LANKA, FCC3_WORLD, "LK"}, + {CTRY_SWEDEN, ETSI1_WORLD, "SE"}, + {CTRY_SWITZERLAND, ETSI1_WORLD, "CH"}, + {CTRY_SYRIA, NULL1_WORLD, "SY"}, + {CTRY_TAIWAN, APL3_FCCA, "TW"}, + {CTRY_THAILAND, FCC3_WORLD, "TH"}, + {CTRY_TRINIDAD_Y_TOBAGO, FCC3_WORLD, "TT"}, + {CTRY_TUNISIA, ETSI3_WORLD, "TN"}, + {CTRY_TURKEY, ETSI3_WORLD, "TR"}, + {CTRY_UKRAINE, NULL1_WORLD, "UA"}, + {CTRY_UAE, NULL1_WORLD, "AE"}, + {CTRY_UNITED_KINGDOM, ETSI1_WORLD, "GB"}, + {CTRY_UNITED_STATES, FCC3_FCCA, "US"}, + /* This "PS" is for US public safety actually... to support this we + * would need to assign new special alpha2 to CRDA db as with the world + * regdomain and use another alpha2 */ + {CTRY_UNITED_STATES_FCC49, FCC4_FCCA, "PS"}, + {CTRY_URUGUAY, FCC3_WORLD, "UY"}, + {CTRY_UZBEKISTAN, FCC3_FCCA, "UZ"}, + {CTRY_VENEZUELA, APL2_ETSIC, "VE"}, + {CTRY_VIET_NAM, NULL1_WORLD, "VN"}, + {CTRY_YEMEN, NULL1_WORLD, "YE"}, + {CTRY_ZIMBABWE, NULL1_WORLD, "ZW"}, +}; + +#endif diff --git a/drivers/net/wireless/ath/wil6210/Kconfig b/drivers/net/wireless/ath/wil6210/Kconfig new file mode 100644 index 0000000..b52b3d1 --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/Kconfig @@ -0,0 +1,31 @@ +config WIL6210 + depends on !BACKPORT_KERNEL_2_6_31 + tristate "Wilocity 60g WiFi card wil6210 support" + depends on m + depends on CFG80211 + depends on PCI + default n + ---help--- + This module adds support for wireless adapter based on + wil6210 chip by Wilocity. It supports operation on the + 60 GHz band, covered by the IEEE802.11ad standard. + + http://wireless.kernel.org/en/users/Drivers/wil6210 + + If you choose to build it as a module, it will be called + wil6210 + +config WIL6210_ISR_COR + bool "Use Clear-On-Read mode for ISR registers for wil6210" + depends on WIL6210 + default y + ---help--- + ISR registers on wil6210 chip may operate in either + COR (Clear-On-Read) or W1C (Write-1-to-Clear) mode. + For production code, use COR (say y); is default since + it saves extra target transaction; + For ISR debug, use W1C (say n); is allows to monitor ISR + registers with debugfs. If COR were used, ISR would + self-clear when accessed for debug purposes, it makes + such monitoring impossible. + Say y unless you debug interrupts diff --git a/drivers/net/wireless/ath/wil6210/Makefile b/drivers/net/wireless/ath/wil6210/Makefile new file mode 100644 index 0000000..f5a64f6 --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/Makefile @@ -0,0 +1,15 @@ +obj-$(CPTCFG_WIL6210) += wil6210.o + +wil6210-objs := main.o +wil6210-objs += netdev.o +wil6210-objs += cfg80211.o +wil6210-objs += pcie_bus.o +wil6210-objs += debugfs.o +wil6210-objs += wmi.o +wil6210-objs += interrupt.o +wil6210-objs += txrx.o + +ifeq (, $(findstring -W,$(EXTRA_CFLAGS))) + subdir-ccflags-y += -Werror +endif +subdir-ccflags-y += -D__CHECK_ENDIAN__ diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c new file mode 100644 index 0000000..c5d4a87 --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/cfg80211.c @@ -0,0 +1,561 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "wil6210.h" +#include "wmi.h" + +#define CHAN60G(_channel, _flags) { \ + .band = IEEE80211_BAND_60GHZ, \ + .center_freq = 56160 + (2160 * (_channel)), \ + .hw_value = (_channel), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 40, \ +} + +static struct ieee80211_channel wil_60ghz_channels[] = { + CHAN60G(1, 0), + CHAN60G(2, 0), + CHAN60G(3, 0), +/* channel 4 not supported yet */ +}; + +static struct ieee80211_supported_band wil_band_60ghz = { + .channels = wil_60ghz_channels, + .n_channels = ARRAY_SIZE(wil_60ghz_channels), + .ht_cap = { + .ht_supported = true, + .cap = 0, /* TODO */ + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K, /* TODO */ + .ampdu_density = IEEE80211_HT_MPDU_DENSITY_8, /* TODO */ + .mcs = { + /* MCS 1..12 - SC PHY */ + .rx_mask = {0xfe, 0x1f}, /* 1..12 */ + .tx_params = IEEE80211_HT_MCS_TX_DEFINED, /* TODO */ + }, + }, +}; + +static const struct ieee80211_txrx_stypes +wil_mgmt_stypes[NUM_NL80211_IFTYPES] = { + [NL80211_IFTYPE_STATION] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + }, + [NL80211_IFTYPE_AP] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + }, + [NL80211_IFTYPE_P2P_CLIENT] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + }, + [NL80211_IFTYPE_P2P_GO] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + }, +}; + +static const u32 wil_cipher_suites[] = { + WLAN_CIPHER_SUITE_GCMP, +}; + +int wil_iftype_nl2wmi(enum nl80211_iftype type) +{ + static const struct { + enum nl80211_iftype nl; + enum wmi_network_type wmi; + } __nl2wmi[] = { + {NL80211_IFTYPE_ADHOC, WMI_NETTYPE_ADHOC}, + {NL80211_IFTYPE_STATION, WMI_NETTYPE_INFRA}, + {NL80211_IFTYPE_AP, WMI_NETTYPE_AP}, + {NL80211_IFTYPE_P2P_CLIENT, WMI_NETTYPE_P2P}, + {NL80211_IFTYPE_P2P_GO, WMI_NETTYPE_P2P}, + {NL80211_IFTYPE_MONITOR, WMI_NETTYPE_ADHOC}, /* FIXME */ + }; + uint i; + + for (i = 0; i < ARRAY_SIZE(__nl2wmi); i++) { + if (__nl2wmi[i].nl == type) + return __nl2wmi[i].wmi; + } + + return -EOPNOTSUPP; +} + +static int wil_cfg80211_get_station(struct wiphy *wiphy, + struct net_device *ndev, + u8 *mac, struct station_info *sinfo) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + int rc; + struct wmi_notify_req_cmd cmd = { + .cid = 0, + .interval_usec = 0, + }; + + if (memcmp(mac, wil->dst_addr[0], ETH_ALEN)) + return -ENOENT; + + /* WMI_NOTIFY_REQ_DONE_EVENTID handler fills wil->stats.bf_mcs */ + rc = wmi_call(wil, WMI_NOTIFY_REQ_CMDID, &cmd, sizeof(cmd), + WMI_NOTIFY_REQ_DONE_EVENTID, NULL, 0, 20); + if (rc) + return rc; + + sinfo->generation = wil->sinfo_gen; + + sinfo->filled |= STATION_INFO_TX_BITRATE; + sinfo->txrate.flags = RATE_INFO_FLAGS_MCS | RATE_INFO_FLAGS_60G; + sinfo->txrate.mcs = wil->stats.bf_mcs; + sinfo->filled |= STATION_INFO_RX_BITRATE; + sinfo->rxrate.flags = RATE_INFO_FLAGS_MCS | RATE_INFO_FLAGS_60G; + sinfo->rxrate.mcs = wil->stats.last_mcs_rx; + + if (test_bit(wil_status_fwconnected, &wil->status)) { + sinfo->filled |= STATION_INFO_SIGNAL; + sinfo->signal = 12; /* TODO: provide real value */ + } + + return 0; +} + +static int wil_cfg80211_change_iface(struct wiphy *wiphy, + struct net_device *ndev, + enum nl80211_iftype type, u32 *flags, + struct vif_params *params) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + struct wireless_dev *wdev = wil->wdev; + + switch (type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_CLIENT: + case NL80211_IFTYPE_P2P_GO: + break; + case NL80211_IFTYPE_MONITOR: + if (flags) + wil->monitor_flags = *flags; + else + wil->monitor_flags = 0; + + break; + default: + return -EOPNOTSUPP; + } + + wdev->iftype = type; + + return 0; +} + +static int wil_cfg80211_scan(struct wiphy *wiphy, + struct cfg80211_scan_request *request) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + struct wireless_dev *wdev = wil->wdev; + struct { + struct wmi_start_scan_cmd cmd; + u16 chnl[4]; + } __packed cmd; + uint i, n; + + if (wil->scan_request) { + wil_err(wil, "Already scanning\n"); + return -EAGAIN; + } + + /* check we are client side */ + switch (wdev->iftype) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + break; + default: + return -EOPNOTSUPP; + } + + /* FW don't support scan after connection attempt */ + if (test_bit(wil_status_dontscan, &wil->status)) { + wil_err(wil, "Scan after connect attempt not supported\n"); + return -EBUSY; + } + + wil->scan_request = request; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd.num_channels = 0; + n = min(request->n_channels, 4U); + for (i = 0; i < n; i++) { + int ch = request->channels[i]->hw_value; + if (ch == 0) { + wil_err(wil, + "Scan requested for unknown frequency %dMhz\n", + request->channels[i]->center_freq); + continue; + } + /* 0-based channel indexes */ + cmd.cmd.channel_list[cmd.cmd.num_channels++].channel = ch - 1; + wil_dbg_misc(wil, "Scan for ch %d : %d MHz\n", ch, + request->channels[i]->center_freq); + } + + return wmi_send(wil, WMI_START_SCAN_CMDID, &cmd, sizeof(cmd.cmd) + + cmd.cmd.num_channels * sizeof(cmd.cmd.channel_list[0])); +} + +static int wil_cfg80211_connect(struct wiphy *wiphy, + struct net_device *ndev, + struct cfg80211_connect_params *sme) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + struct cfg80211_bss *bss; + struct wmi_connect_cmd conn; + const u8 *ssid_eid; + const u8 *rsn_eid; + int ch; + int rc = 0; + + bss = cfg80211_get_bss(wiphy, sme->channel, sme->bssid, + sme->ssid, sme->ssid_len, + WLAN_CAPABILITY_ESS, WLAN_CAPABILITY_ESS); + if (!bss) { + wil_err(wil, "Unable to find BSS\n"); + return -ENOENT; + } + + ssid_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SSID); + if (!ssid_eid) { + wil_err(wil, "No SSID\n"); + rc = -ENOENT; + goto out; + } + + rsn_eid = sme->ie ? + cfg80211_find_ie(WLAN_EID_RSN, sme->ie, sme->ie_len) : + NULL; + if (rsn_eid) { + if (sme->ie_len > WMI_MAX_IE_LEN) { + rc = -ERANGE; + wil_err(wil, "IE too large (%td bytes)\n", + sme->ie_len); + goto out; + } + /* + * For secure assoc, send: + * (1) WMI_DELETE_CIPHER_KEY_CMD + * (2) WMI_SET_APPIE_CMD + */ + rc = wmi_del_cipher_key(wil, 0, bss->bssid); + if (rc) { + wil_err(wil, "WMI_DELETE_CIPHER_KEY_CMD failed\n"); + goto out; + } + /* WMI_SET_APPIE_CMD */ + rc = wmi_set_ie(wil, WMI_FRAME_ASSOC_REQ, sme->ie_len, sme->ie); + if (rc) { + wil_err(wil, "WMI_SET_APPIE_CMD failed\n"); + goto out; + } + } + + /* WMI_CONNECT_CMD */ + memset(&conn, 0, sizeof(conn)); + switch (bss->capability & WLAN_CAPABILITY_DMG_TYPE_MASK) { + case WLAN_CAPABILITY_DMG_TYPE_AP: + conn.network_type = WMI_NETTYPE_INFRA; + break; + case WLAN_CAPABILITY_DMG_TYPE_PBSS: + conn.network_type = WMI_NETTYPE_P2P; + break; + default: + wil_err(wil, "Unsupported BSS type, capability= 0x%04x\n", + bss->capability); + goto out; + } + if (rsn_eid) { + conn.dot11_auth_mode = WMI_AUTH11_SHARED; + conn.auth_mode = WMI_AUTH_WPA2_PSK; + conn.pairwise_crypto_type = WMI_CRYPT_AES_GCMP; + conn.pairwise_crypto_len = 16; + } else { + conn.dot11_auth_mode = WMI_AUTH11_OPEN; + conn.auth_mode = WMI_AUTH_NONE; + } + + conn.ssid_len = min_t(u8, ssid_eid[1], 32); + memcpy(conn.ssid, ssid_eid+2, conn.ssid_len); + + ch = bss->channel->hw_value; + if (ch == 0) { + wil_err(wil, "BSS at unknown frequency %dMhz\n", + bss->channel->center_freq); + rc = -EOPNOTSUPP; + goto out; + } + conn.channel = ch - 1; + + memcpy(conn.bssid, bss->bssid, 6); + memcpy(conn.dst_mac, bss->bssid, 6); + /* + * FW don't support scan after connection attempt + */ + set_bit(wil_status_dontscan, &wil->status); + + rc = wmi_send(wil, WMI_CONNECT_CMDID, &conn, sizeof(conn)); + if (rc == 0) { + /* Connect can take lots of time */ + mod_timer(&wil->connect_timer, + jiffies + msecs_to_jiffies(2000)); + } + + out: + cfg80211_put_bss(wiphy, bss); + + return rc; +} + +static int wil_cfg80211_disconnect(struct wiphy *wiphy, + struct net_device *ndev, + u16 reason_code) +{ + int rc; + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + + rc = wmi_send(wil, WMI_DISCONNECT_CMDID, NULL, 0); + + return rc; +} + +static int wil_cfg80211_set_channel(struct wiphy *wiphy, + struct cfg80211_chan_def *chandef) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + struct wireless_dev *wdev = wil->wdev; + + wdev->preset_chandef = *chandef; + + return 0; +} + +static int wil_cfg80211_add_key(struct wiphy *wiphy, + struct net_device *ndev, + u8 key_index, bool pairwise, + const u8 *mac_addr, + struct key_params *params) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + + /* group key is not used */ + if (!pairwise) + return 0; + + return wmi_add_cipher_key(wil, key_index, mac_addr, + params->key_len, params->key); +} + +static int wil_cfg80211_del_key(struct wiphy *wiphy, + struct net_device *ndev, + u8 key_index, bool pairwise, + const u8 *mac_addr) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + + /* group key is not used */ + if (!pairwise) + return 0; + + return wmi_del_cipher_key(wil, key_index, mac_addr); +} + +/* Need to be present or wiphy_new() will WARN */ +static int wil_cfg80211_set_default_key(struct wiphy *wiphy, + struct net_device *ndev, + u8 key_index, bool unicast, + bool multicast) +{ + return 0; +} + +static int wil_cfg80211_start_ap(struct wiphy *wiphy, + struct net_device *ndev, + struct cfg80211_ap_settings *info) +{ + int rc = 0; + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + struct wireless_dev *wdev = ndev->ieee80211_ptr; + struct ieee80211_channel *channel = info->chandef.chan; + struct cfg80211_beacon_data *bcon = &info->beacon; + u8 wmi_nettype = wil_iftype_nl2wmi(wdev->iftype); + + if (!channel) { + wil_err(wil, "AP: No channel???\n"); + return -EINVAL; + } + + wil_dbg_misc(wil, "AP on Channel %d %d MHz, %s\n", channel->hw_value, + channel->center_freq, info->privacy ? "secure" : "open"); + print_hex_dump_bytes("SSID ", DUMP_PREFIX_OFFSET, + info->ssid, info->ssid_len); + + rc = wil_reset(wil); + if (rc) + return rc; + + rc = wmi_set_ssid(wil, info->ssid_len, info->ssid); + if (rc) + return rc; + + /* MAC address - pre-requisite for other commands */ + wmi_set_mac_address(wil, ndev->dev_addr); + + /* IE's */ + /* bcon 'head IE's are not relevant for 60g band */ + /* + * FW do not form regular beacon, so bcon IE's are not set + * For the DMG bcon, when it will be supported, bcon IE's will + * be reused; add something like: + * wmi_set_ie(wil, WMI_FRAME_BEACON, bcon->beacon_ies_len, + * bcon->beacon_ies); + */ + wmi_set_ie(wil, WMI_FRAME_PROBE_RESP, bcon->proberesp_ies_len, + bcon->proberesp_ies); + wmi_set_ie(wil, WMI_FRAME_ASSOC_RESP, bcon->assocresp_ies_len, + bcon->assocresp_ies); + + wil->secure_pcp = info->privacy; + + rc = wmi_pcp_start(wil, info->beacon_interval, wmi_nettype, + channel->hw_value); + if (rc) + return rc; + + /* Rx VRING. After MAC and beacon */ + rc = wil_rx_init(wil); + + netif_carrier_on(ndev); + + return rc; +} + +static int wil_cfg80211_stop_ap(struct wiphy *wiphy, + struct net_device *ndev) +{ + int rc = 0; + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + + rc = wmi_pcp_stop(wil); + + return rc; +} + +static struct cfg80211_ops wil_cfg80211_ops = { + .scan = wil_cfg80211_scan, + .connect = wil_cfg80211_connect, + .disconnect = wil_cfg80211_disconnect, + .change_virtual_intf = wil_cfg80211_change_iface, + .get_station = wil_cfg80211_get_station, + .set_monitor_channel = wil_cfg80211_set_channel, + .add_key = wil_cfg80211_add_key, + .del_key = wil_cfg80211_del_key, + .set_default_key = wil_cfg80211_set_default_key, + /* AP mode */ + .start_ap = wil_cfg80211_start_ap, + .stop_ap = wil_cfg80211_stop_ap, +}; + +static void wil_wiphy_init(struct wiphy *wiphy) +{ + /* TODO: set real value */ + wiphy->max_scan_ssids = 10; + wiphy->max_num_pmkids = 0 /* TODO: */; + wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_MONITOR); + /* TODO: enable P2P when integrated with supplicant: + * BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO) + */ + wiphy->flags |= WIPHY_FLAG_HAVE_AP_SME | + WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD; + dev_warn(wiphy_dev(wiphy), "%s : flags = 0x%08x\n", + __func__, wiphy->flags); + wiphy->probe_resp_offload = + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS | + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 | + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P; + + wiphy->bands[IEEE80211_BAND_60GHZ] = &wil_band_60ghz; + + /* TODO: figure this out */ + wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + + wiphy->cipher_suites = wil_cipher_suites; + wiphy->n_cipher_suites = ARRAY_SIZE(wil_cipher_suites); + wiphy->mgmt_stypes = wil_mgmt_stypes; +} + +struct wireless_dev *wil_cfg80211_init(struct device *dev) +{ + int rc = 0; + struct wireless_dev *wdev; + + wdev = kzalloc(sizeof(struct wireless_dev), GFP_KERNEL); + if (!wdev) + return ERR_PTR(-ENOMEM); + + wdev->wiphy = wiphy_new(&wil_cfg80211_ops, + sizeof(struct wil6210_priv)); + if (!wdev->wiphy) { + rc = -ENOMEM; + goto out; + } + + set_wiphy_dev(wdev->wiphy, dev); + wil_wiphy_init(wdev->wiphy); + + rc = wiphy_register(wdev->wiphy); + if (rc < 0) + goto out_failed_reg; + + return wdev; + +out_failed_reg: + wiphy_free(wdev->wiphy); +out: + kfree(wdev); + + return ERR_PTR(rc); +} + +void wil_wdev_free(struct wil6210_priv *wil) +{ + struct wireless_dev *wdev = wil_to_wdev(wil); + + if (!wdev) + return; + + wiphy_unregister(wdev->wiphy); + wiphy_free(wdev->wiphy); + kfree(wdev); +} diff --git a/drivers/net/wireless/ath/wil6210/debugfs.c b/drivers/net/wireless/ath/wil6210/debugfs.c new file mode 100644 index 0000000..d57e5be --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/debugfs.c @@ -0,0 +1,639 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "wil6210.h" +#include "txrx.h" + +/* Nasty hack. Better have per device instances */ +static u32 mem_addr; +static u32 dbg_txdesc_index; + +static void wil_print_vring(struct seq_file *s, struct wil6210_priv *wil, + const char *name, struct vring *vring) +{ + void __iomem *x = wmi_addr(wil, vring->hwtail); + + seq_printf(s, "VRING %s = {\n", name); + seq_printf(s, " pa = 0x%016llx\n", (unsigned long long)vring->pa); + seq_printf(s, " va = 0x%p\n", vring->va); + seq_printf(s, " size = %d\n", vring->size); + seq_printf(s, " swtail = %d\n", vring->swtail); + seq_printf(s, " swhead = %d\n", vring->swhead); + seq_printf(s, " hwtail = [0x%08x] -> ", vring->hwtail); + if (x) + seq_printf(s, "0x%08x\n", ioread32(x)); + else + seq_printf(s, "???\n"); + + if (vring->va && (vring->size < 1025)) { + uint i; + for (i = 0; i < vring->size; i++) { + volatile struct vring_tx_desc *d = &vring->va[i].tx; + if ((i % 64) == 0 && (i != 0)) + seq_printf(s, "\n"); + seq_printf(s, "%s", (d->dma.status & BIT(0)) ? + "S" : (vring->ctx[i] ? "H" : "h")); + } + seq_printf(s, "\n"); + } + seq_printf(s, "}\n"); +} + +static int wil_vring_debugfs_show(struct seq_file *s, void *data) +{ + uint i; + struct wil6210_priv *wil = s->private; + + wil_print_vring(s, wil, "rx", &wil->vring_rx); + + for (i = 0; i < ARRAY_SIZE(wil->vring_tx); i++) { + struct vring *vring = &(wil->vring_tx[i]); + if (vring->va) { + char name[10]; + snprintf(name, sizeof(name), "tx_%2d", i); + wil_print_vring(s, wil, name, vring); + } + } + + return 0; +} + +static int wil_vring_seq_open(struct inode *inode, struct file *file) +{ + return single_open(file, wil_vring_debugfs_show, inode->i_private); +} + +static const struct file_operations fops_vring = { + .open = wil_vring_seq_open, + .release = single_release, + .read = seq_read, + .llseek = seq_lseek, +}; + +static void wil_print_ring(struct seq_file *s, const char *prefix, + void __iomem *off) +{ + struct wil6210_priv *wil = s->private; + struct wil6210_mbox_ring r; + int rsize; + uint i; + + wil_memcpy_fromio_32(&r, off, sizeof(r)); + wil_mbox_ring_le2cpus(&r); + /* + * we just read memory block from NIC. This memory may be + * garbage. Check validity before using it. + */ + rsize = r.size / sizeof(struct wil6210_mbox_ring_desc); + + seq_printf(s, "ring %s = {\n", prefix); + seq_printf(s, " base = 0x%08x\n", r.base); + seq_printf(s, " size = 0x%04x bytes -> %d entries\n", r.size, rsize); + seq_printf(s, " tail = 0x%08x\n", r.tail); + seq_printf(s, " head = 0x%08x\n", r.head); + seq_printf(s, " entry size = %d\n", r.entry_size); + + if (r.size % sizeof(struct wil6210_mbox_ring_desc)) { + seq_printf(s, " ??? size is not multiple of %zd, garbage?\n", + sizeof(struct wil6210_mbox_ring_desc)); + goto out; + } + + if (!wmi_addr(wil, r.base) || + !wmi_addr(wil, r.tail) || + !wmi_addr(wil, r.head)) { + seq_printf(s, " ??? pointers are garbage?\n"); + goto out; + } + + for (i = 0; i < rsize; i++) { + struct wil6210_mbox_ring_desc d; + struct wil6210_mbox_hdr hdr; + size_t delta = i * sizeof(d); + void __iomem *x = wil->csr + HOSTADDR(r.base) + delta; + + wil_memcpy_fromio_32(&d, x, sizeof(d)); + + seq_printf(s, " [%2x] %s %s%s 0x%08x", i, + d.sync ? "F" : "E", + (r.tail - r.base == delta) ? "t" : " ", + (r.head - r.base == delta) ? "h" : " ", + le32_to_cpu(d.addr)); + if (0 == wmi_read_hdr(wil, d.addr, &hdr)) { + u16 len = le16_to_cpu(hdr.len); + seq_printf(s, " -> %04x %04x %04x %02x\n", + le16_to_cpu(hdr.seq), len, + le16_to_cpu(hdr.type), hdr.flags); + if (len <= MAX_MBOXITEM_SIZE) { + int n = 0; + char printbuf[16 * 3 + 2]; + unsigned char databuf[MAX_MBOXITEM_SIZE]; + void __iomem *src = wmi_buffer(wil, d.addr) + + sizeof(struct wil6210_mbox_hdr); + /* + * No need to check @src for validity - + * we already validated @d.addr while + * reading header + */ + wil_memcpy_fromio_32(databuf, src, len); + while (n < len) { + int l = min(len - n, 16); + hex_dump_to_buffer(databuf + n, l, + 16, 1, printbuf, + sizeof(printbuf), + false); + seq_printf(s, " : %s\n", printbuf); + n += l; + } + } + } else { + seq_printf(s, "\n"); + } + } + out: + seq_printf(s, "}\n"); +} + +static int wil_mbox_debugfs_show(struct seq_file *s, void *data) +{ + struct wil6210_priv *wil = s->private; + + wil_print_ring(s, "tx", wil->csr + HOST_MBOX + + offsetof(struct wil6210_mbox_ctl, tx)); + wil_print_ring(s, "rx", wil->csr + HOST_MBOX + + offsetof(struct wil6210_mbox_ctl, rx)); + + return 0; +} + +static int wil_mbox_seq_open(struct inode *inode, struct file *file) +{ + return single_open(file, wil_mbox_debugfs_show, inode->i_private); +} + +static const struct file_operations fops_mbox = { + .open = wil_mbox_seq_open, + .release = single_release, + .read = seq_read, + .llseek = seq_lseek, +}; + +static int wil_debugfs_iomem_x32_set(void *data, u64 val) +{ + iowrite32(val, (void __iomem *)data); + wmb(); /* make sure write propagated to HW */ + + return 0; +} + +static int wil_debugfs_iomem_x32_get(void *data, u64 *val) +{ + *val = ioread32((void __iomem *)data); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(fops_iomem_x32, wil_debugfs_iomem_x32_get, + wil_debugfs_iomem_x32_set, "0x%08llx\n"); + +static struct dentry *wil_debugfs_create_iomem_x32(const char *name, + umode_t mode, + struct dentry *parent, + void __iomem *value) +{ + return debugfs_create_file(name, mode, parent, (void * __force)value, + &fops_iomem_x32); +} + +static int wil6210_debugfs_create_ISR(struct wil6210_priv *wil, + const char *name, + struct dentry *parent, u32 off) +{ + struct dentry *d = debugfs_create_dir(name, parent); + + if (IS_ERR_OR_NULL(d)) + return -ENODEV; + + wil_debugfs_create_iomem_x32("ICC", S_IRUGO | S_IWUSR, d, + wil->csr + off); + wil_debugfs_create_iomem_x32("ICR", S_IRUGO | S_IWUSR, d, + wil->csr + off + 4); + wil_debugfs_create_iomem_x32("ICM", S_IRUGO | S_IWUSR, d, + wil->csr + off + 8); + wil_debugfs_create_iomem_x32("ICS", S_IWUSR, d, + wil->csr + off + 12); + wil_debugfs_create_iomem_x32("IMV", S_IRUGO | S_IWUSR, d, + wil->csr + off + 16); + wil_debugfs_create_iomem_x32("IMS", S_IWUSR, d, + wil->csr + off + 20); + wil_debugfs_create_iomem_x32("IMC", S_IWUSR, d, + wil->csr + off + 24); + + return 0; +} + +static int wil6210_debugfs_create_pseudo_ISR(struct wil6210_priv *wil, + struct dentry *parent) +{ + struct dentry *d = debugfs_create_dir("PSEUDO_ISR", parent); + + if (IS_ERR_OR_NULL(d)) + return -ENODEV; + + wil_debugfs_create_iomem_x32("CAUSE", S_IRUGO, d, wil->csr + + HOSTADDR(RGF_DMA_PSEUDO_CAUSE)); + wil_debugfs_create_iomem_x32("MASK_SW", S_IRUGO, d, wil->csr + + HOSTADDR(RGF_DMA_PSEUDO_CAUSE_MASK_SW)); + wil_debugfs_create_iomem_x32("MASK_FW", S_IRUGO, d, wil->csr + + HOSTADDR(RGF_DMA_PSEUDO_CAUSE_MASK_FW)); + + return 0; +} + +static int wil6210_debugfs_create_ITR_CNT(struct wil6210_priv *wil, + struct dentry *parent) +{ + struct dentry *d = debugfs_create_dir("ITR_CNT", parent); + + if (IS_ERR_OR_NULL(d)) + return -ENODEV; + + wil_debugfs_create_iomem_x32("TRSH", S_IRUGO, d, wil->csr + + HOSTADDR(RGF_DMA_ITR_CNT_TRSH)); + wil_debugfs_create_iomem_x32("DATA", S_IRUGO, d, wil->csr + + HOSTADDR(RGF_DMA_ITR_CNT_DATA)); + wil_debugfs_create_iomem_x32("CTL", S_IRUGO, d, wil->csr + + HOSTADDR(RGF_DMA_ITR_CNT_CRL)); + + return 0; +} + +static int wil_memread_debugfs_show(struct seq_file *s, void *data) +{ + struct wil6210_priv *wil = s->private; + void __iomem *a = wmi_buffer(wil, cpu_to_le32(mem_addr)); + + if (a) + seq_printf(s, "[0x%08x] = 0x%08x\n", mem_addr, ioread32(a)); + else + seq_printf(s, "[0x%08x] = INVALID\n", mem_addr); + + return 0; +} + +static int wil_memread_seq_open(struct inode *inode, struct file *file) +{ + return single_open(file, wil_memread_debugfs_show, inode->i_private); +} + +static const struct file_operations fops_memread = { + .open = wil_memread_seq_open, + .release = single_release, + .read = seq_read, + .llseek = seq_lseek, +}; + +static ssize_t wil_read_file_ioblob(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + enum { max_count = 4096 }; + struct debugfs_blob_wrapper *blob = file->private_data; + loff_t pos = *ppos; + size_t available = blob->size; + void *buf; + size_t ret; + + if (pos < 0) + return -EINVAL; + + if (pos >= available || !count) + return 0; + + if (count > available - pos) + count = available - pos; + if (count > max_count) + count = max_count; + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + wil_memcpy_fromio_32(buf, (const volatile void __iomem *)blob->data + + pos, count); + + ret = copy_to_user(user_buf, buf, count); + kfree(buf); + if (ret == count) + return -EFAULT; + + count -= ret; + *ppos = pos + count; + + return count; +} + +static const struct file_operations fops_ioblob = { + .read = wil_read_file_ioblob, + .open = simple_open, + .llseek = default_llseek, +}; + +static +struct dentry *wil_debugfs_create_ioblob(const char *name, + umode_t mode, + struct dentry *parent, + struct debugfs_blob_wrapper *blob) +{ + return debugfs_create_file(name, mode, parent, blob, &fops_ioblob); +} +/*---reset---*/ +static ssize_t wil_write_file_reset(struct file *file, const char __user *buf, + size_t len, loff_t *ppos) +{ + struct wil6210_priv *wil = file->private_data; + struct net_device *ndev = wil_to_ndev(wil); + + /** + * BUG: + * this code does NOT sync device state with the rest of system + * use with care, debug only!!! + */ + rtnl_lock(); + dev_close(ndev); + ndev->flags &= ~IFF_UP; + rtnl_unlock(); + wil_reset(wil); + + return len; +} + +static const struct file_operations fops_reset = { + .write = wil_write_file_reset, + .open = simple_open, +}; +/*---------Tx descriptor------------*/ + +static int wil_txdesc_debugfs_show(struct seq_file *s, void *data) +{ + struct wil6210_priv *wil = s->private; + struct vring *vring = &(wil->vring_tx[0]); + + if (!vring->va) { + seq_printf(s, "No Tx VRING\n"); + return 0; + } + + if (dbg_txdesc_index < vring->size) { + volatile struct vring_tx_desc *d = + &(vring->va[dbg_txdesc_index].tx); + volatile u32 *u = (volatile u32 *)d; + struct sk_buff *skb = vring->ctx[dbg_txdesc_index]; + + seq_printf(s, "Tx[%3d] = {\n", dbg_txdesc_index); + seq_printf(s, " MAC = 0x%08x 0x%08x 0x%08x 0x%08x\n", + u[0], u[1], u[2], u[3]); + seq_printf(s, " DMA = 0x%08x 0x%08x 0x%08x 0x%08x\n", + u[4], u[5], u[6], u[7]); + seq_printf(s, " SKB = %p\n", skb); + + if (skb) { + char printbuf[16 * 3 + 2]; + int i = 0; + int len = skb_headlen(skb); + void *p = skb->data; + + seq_printf(s, " len = %d\n", len); + + while (i < len) { + int l = min(len - i, 16); + hex_dump_to_buffer(p + i, l, 16, 1, printbuf, + sizeof(printbuf), false); + seq_printf(s, " : %s\n", printbuf); + i += l; + } + } + seq_printf(s, "}\n"); + } else { + seq_printf(s, "TxDesc index (%d) >= size (%d)\n", + dbg_txdesc_index, vring->size); + } + + return 0; +} + +static int wil_txdesc_seq_open(struct inode *inode, struct file *file) +{ + return single_open(file, wil_txdesc_debugfs_show, inode->i_private); +} + +static const struct file_operations fops_txdesc = { + .open = wil_txdesc_seq_open, + .release = single_release, + .read = seq_read, + .llseek = seq_lseek, +}; + +/*---------beamforming------------*/ +static int wil_bf_debugfs_show(struct seq_file *s, void *data) +{ + struct wil6210_priv *wil = s->private; + seq_printf(s, + "TSF : 0x%016llx\n" + "TxMCS : %d\n" + "Sectors(rx:tx) my %2d:%2d peer %2d:%2d\n", + wil->stats.tsf, wil->stats.bf_mcs, + wil->stats.my_rx_sector, wil->stats.my_tx_sector, + wil->stats.peer_rx_sector, wil->stats.peer_tx_sector); + return 0; +} + +static int wil_bf_seq_open(struct inode *inode, struct file *file) +{ + return single_open(file, wil_bf_debugfs_show, inode->i_private); +} + +static const struct file_operations fops_bf = { + .open = wil_bf_seq_open, + .release = single_release, + .read = seq_read, + .llseek = seq_lseek, +}; +/*---------SSID------------*/ +static ssize_t wil_read_file_ssid(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wil6210_priv *wil = file->private_data; + struct wireless_dev *wdev = wil_to_wdev(wil); + + return simple_read_from_buffer(user_buf, count, ppos, + wdev->ssid, wdev->ssid_len); +} + +static ssize_t wil_write_file_ssid(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct wil6210_priv *wil = file->private_data; + struct wireless_dev *wdev = wil_to_wdev(wil); + struct net_device *ndev = wil_to_ndev(wil); + + if (*ppos != 0) { + wil_err(wil, "Unable to set SSID substring from [%d]\n", + (int)*ppos); + return -EINVAL; + } + + if (count > sizeof(wdev->ssid)) { + wil_err(wil, "SSID too long, len = %d\n", (int)count); + return -EINVAL; + } + if (netif_running(ndev)) { + wil_err(wil, "Unable to change SSID on running interface\n"); + return -EINVAL; + } + + wdev->ssid_len = count; + return simple_write_to_buffer(wdev->ssid, wdev->ssid_len, ppos, + buf, count); +} + +static const struct file_operations fops_ssid = { + .read = wil_read_file_ssid, + .write = wil_write_file_ssid, + .open = simple_open, +}; + +/*---------temp------------*/ +static void print_temp(struct seq_file *s, const char *prefix, u32 t) +{ + switch (t) { + case 0: + case ~(u32)0: + seq_printf(s, "%s N/A\n", prefix); + break; + default: + seq_printf(s, "%s %d.%03d\n", prefix, t / 1000, t % 1000); + break; + } +} + +static int wil_temp_debugfs_show(struct seq_file *s, void *data) +{ + struct wil6210_priv *wil = s->private; + u32 t_m, t_r; + + int rc = wmi_get_temperature(wil, &t_m, &t_r); + if (rc) { + seq_printf(s, "Failed\n"); + return 0; + } + + print_temp(s, "MAC temperature :", t_m); + print_temp(s, "Radio temperature :", t_r); + + return 0; +} + +static int wil_temp_seq_open(struct inode *inode, struct file *file) +{ + return single_open(file, wil_temp_debugfs_show, inode->i_private); +} + +static const struct file_operations fops_temp = { + .open = wil_temp_seq_open, + .release = single_release, + .read = seq_read, + .llseek = seq_lseek, +}; + +/*----------------*/ +int wil6210_debugfs_init(struct wil6210_priv *wil) +{ + struct dentry *dbg = wil->debug = debugfs_create_dir(WIL_NAME, + wil_to_wiphy(wil)->debugfsdir); + + if (IS_ERR_OR_NULL(dbg)) + return -ENODEV; + + debugfs_create_file("mbox", S_IRUGO, dbg, wil, &fops_mbox); + debugfs_create_file("vrings", S_IRUGO, dbg, wil, &fops_vring); + debugfs_create_file("txdesc", S_IRUGO, dbg, wil, &fops_txdesc); + debugfs_create_u32("txdesc_index", S_IRUGO | S_IWUSR, dbg, + &dbg_txdesc_index); + debugfs_create_file("bf", S_IRUGO, dbg, wil, &fops_bf); + debugfs_create_file("ssid", S_IRUGO | S_IWUSR, dbg, wil, &fops_ssid); + debugfs_create_u32("secure_pcp", S_IRUGO | S_IWUSR, dbg, + &wil->secure_pcp); + + wil6210_debugfs_create_ISR(wil, "USER_ICR", dbg, + HOSTADDR(RGF_USER_USER_ICR)); + wil6210_debugfs_create_ISR(wil, "DMA_EP_TX_ICR", dbg, + HOSTADDR(RGF_DMA_EP_TX_ICR)); + wil6210_debugfs_create_ISR(wil, "DMA_EP_RX_ICR", dbg, + HOSTADDR(RGF_DMA_EP_RX_ICR)); + wil6210_debugfs_create_ISR(wil, "DMA_EP_MISC_ICR", dbg, + HOSTADDR(RGF_DMA_EP_MISC_ICR)); + wil6210_debugfs_create_pseudo_ISR(wil, dbg); + wil6210_debugfs_create_ITR_CNT(wil, dbg); + + debugfs_create_u32("mem_addr", S_IRUGO | S_IWUSR, dbg, &mem_addr); + debugfs_create_file("mem_val", S_IRUGO, dbg, wil, &fops_memread); + + debugfs_create_file("reset", S_IWUSR, dbg, wil, &fops_reset); + debugfs_create_file("temp", S_IRUGO, dbg, wil, &fops_temp); + + wil->rgf_blob.data = (void * __force)wil->csr + 0; + wil->rgf_blob.size = 0xa000; + wil_debugfs_create_ioblob("blob_rgf", S_IRUGO, dbg, &wil->rgf_blob); + + wil->fw_code_blob.data = (void * __force)wil->csr + 0x40000; + wil->fw_code_blob.size = 0x40000; + wil_debugfs_create_ioblob("blob_fw_code", S_IRUGO, dbg, + &wil->fw_code_blob); + + wil->fw_data_blob.data = (void * __force)wil->csr + 0x80000; + wil->fw_data_blob.size = 0x8000; + wil_debugfs_create_ioblob("blob_fw_data", S_IRUGO, dbg, + &wil->fw_data_blob); + + wil->fw_peri_blob.data = (void * __force)wil->csr + 0x88000; + wil->fw_peri_blob.size = 0x18000; + wil_debugfs_create_ioblob("blob_fw_peri", S_IRUGO, dbg, + &wil->fw_peri_blob); + + wil->uc_code_blob.data = (void * __force)wil->csr + 0xa0000; + wil->uc_code_blob.size = 0x10000; + wil_debugfs_create_ioblob("blob_uc_code", S_IRUGO, dbg, + &wil->uc_code_blob); + + wil->uc_data_blob.data = (void * __force)wil->csr + 0xb0000; + wil->uc_data_blob.size = 0x4000; + wil_debugfs_create_ioblob("blob_uc_data", S_IRUGO, dbg, + &wil->uc_data_blob); + + return 0; +} + +void wil6210_debugfs_remove(struct wil6210_priv *wil) +{ + debugfs_remove_recursive(wil->debug); + wil->debug = NULL; +} diff --git a/drivers/net/wireless/ath/wil6210/interrupt.c b/drivers/net/wireless/ath/wil6210/interrupt.c new file mode 100644 index 0000000..0c07f01 --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/interrupt.c @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "wil6210.h" + +/** + * Theory of operation: + * + * There is ISR pseudo-cause register, + * dma_rgf->DMA_RGF.PSEUDO_CAUSE.PSEUDO_CAUSE + * Its bits represents OR'ed bits from 3 real ISR registers: + * TX, RX, and MISC. + * + * Registers may be configured to either "write 1 to clear" or + * "clear on read" mode + * + * When handling interrupt, one have to mask/unmask interrupts for the + * real ISR registers, or hardware may malfunction. + * + */ + +#define WIL6210_IRQ_DISABLE (0xFFFFFFFFUL) +#define WIL6210_IMC_RX BIT_DMA_EP_RX_ICR_RX_DONE +#define WIL6210_IMC_TX (BIT_DMA_EP_TX_ICR_TX_DONE | \ + BIT_DMA_EP_TX_ICR_TX_DONE_N(0)) +#define WIL6210_IMC_MISC (ISR_MISC_FW_READY | \ + ISR_MISC_MBOX_EVT | \ + ISR_MISC_FW_ERROR) + +#define WIL6210_IRQ_PSEUDO_MASK (u32)(~(BIT_DMA_PSEUDO_CAUSE_RX | \ + BIT_DMA_PSEUDO_CAUSE_TX | \ + BIT_DMA_PSEUDO_CAUSE_MISC)) + +#if defined(CPTCFG_WIL6210_ISR_COR) +/* configure to Clear-On-Read mode */ +#define WIL_ICR_ICC_VALUE (0xFFFFFFFFUL) + +static inline void wil_icr_clear(u32 x, void __iomem *addr) +{ +} +#else /* defined(CPTCFG_WIL6210_ISR_COR) */ +/* configure to Write-1-to-Clear mode */ +#define WIL_ICR_ICC_VALUE (0UL) + +static inline void wil_icr_clear(u32 x, void __iomem *addr) +{ + iowrite32(x, addr); +} +#endif /* defined(CPTCFG_WIL6210_ISR_COR) */ + +static inline u32 wil_ioread32_and_clear(void __iomem *addr) +{ + u32 x = ioread32(addr); + + wil_icr_clear(x, addr); + + return x; +} + +static void wil6210_mask_irq_tx(struct wil6210_priv *wil) +{ + iowrite32(WIL6210_IRQ_DISABLE, wil->csr + + HOSTADDR(RGF_DMA_EP_TX_ICR) + + offsetof(struct RGF_ICR, IMS)); +} + +static void wil6210_mask_irq_rx(struct wil6210_priv *wil) +{ + iowrite32(WIL6210_IRQ_DISABLE, wil->csr + + HOSTADDR(RGF_DMA_EP_RX_ICR) + + offsetof(struct RGF_ICR, IMS)); +} + +static void wil6210_mask_irq_misc(struct wil6210_priv *wil) +{ + iowrite32(WIL6210_IRQ_DISABLE, wil->csr + + HOSTADDR(RGF_DMA_EP_MISC_ICR) + + offsetof(struct RGF_ICR, IMS)); +} + +static void wil6210_mask_irq_pseudo(struct wil6210_priv *wil) +{ + wil_dbg_irq(wil, "%s()\n", __func__); + + iowrite32(WIL6210_IRQ_DISABLE, wil->csr + + HOSTADDR(RGF_DMA_PSEUDO_CAUSE_MASK_SW)); + + clear_bit(wil_status_irqen, &wil->status); +} + +static void wil6210_unmask_irq_tx(struct wil6210_priv *wil) +{ + iowrite32(WIL6210_IMC_TX, wil->csr + + HOSTADDR(RGF_DMA_EP_TX_ICR) + + offsetof(struct RGF_ICR, IMC)); +} + +static void wil6210_unmask_irq_rx(struct wil6210_priv *wil) +{ + iowrite32(WIL6210_IMC_RX, wil->csr + + HOSTADDR(RGF_DMA_EP_RX_ICR) + + offsetof(struct RGF_ICR, IMC)); +} + +static void wil6210_unmask_irq_misc(struct wil6210_priv *wil) +{ + iowrite32(WIL6210_IMC_MISC, wil->csr + + HOSTADDR(RGF_DMA_EP_MISC_ICR) + + offsetof(struct RGF_ICR, IMC)); +} + +static void wil6210_unmask_irq_pseudo(struct wil6210_priv *wil) +{ + wil_dbg_irq(wil, "%s()\n", __func__); + + set_bit(wil_status_irqen, &wil->status); + + iowrite32(WIL6210_IRQ_PSEUDO_MASK, wil->csr + + HOSTADDR(RGF_DMA_PSEUDO_CAUSE_MASK_SW)); +} + +void wil6210_disable_irq(struct wil6210_priv *wil) +{ + wil_dbg_irq(wil, "%s()\n", __func__); + + wil6210_mask_irq_tx(wil); + wil6210_mask_irq_rx(wil); + wil6210_mask_irq_misc(wil); + wil6210_mask_irq_pseudo(wil); +} + +void wil6210_enable_irq(struct wil6210_priv *wil) +{ + wil_dbg_irq(wil, "%s()\n", __func__); + + iowrite32(WIL_ICR_ICC_VALUE, wil->csr + HOSTADDR(RGF_DMA_EP_RX_ICR) + + offsetof(struct RGF_ICR, ICC)); + iowrite32(WIL_ICR_ICC_VALUE, wil->csr + HOSTADDR(RGF_DMA_EP_TX_ICR) + + offsetof(struct RGF_ICR, ICC)); + iowrite32(WIL_ICR_ICC_VALUE, wil->csr + HOSTADDR(RGF_DMA_EP_MISC_ICR) + + offsetof(struct RGF_ICR, ICC)); + + wil6210_unmask_irq_pseudo(wil); + wil6210_unmask_irq_tx(wil); + wil6210_unmask_irq_rx(wil); + wil6210_unmask_irq_misc(wil); +} + +static irqreturn_t wil6210_irq_rx(int irq, void *cookie) +{ + struct wil6210_priv *wil = cookie; + u32 isr = wil_ioread32_and_clear(wil->csr + + HOSTADDR(RGF_DMA_EP_RX_ICR) + + offsetof(struct RGF_ICR, ICR)); + + wil_dbg_irq(wil, "ISR RX 0x%08x\n", isr); + + if (!isr) { + wil_err(wil, "spurious IRQ: RX\n"); + return IRQ_NONE; + } + + wil6210_mask_irq_rx(wil); + + if (isr & BIT_DMA_EP_RX_ICR_RX_DONE) { + wil_dbg_irq(wil, "RX done\n"); + isr &= ~BIT_DMA_EP_RX_ICR_RX_DONE; + wil_rx_handle(wil); + } + + if (isr) + wil_err(wil, "un-handled RX ISR bits 0x%08x\n", isr); + + wil6210_unmask_irq_rx(wil); + + return IRQ_HANDLED; +} + +static irqreturn_t wil6210_irq_tx(int irq, void *cookie) +{ + struct wil6210_priv *wil = cookie; + u32 isr = wil_ioread32_and_clear(wil->csr + + HOSTADDR(RGF_DMA_EP_TX_ICR) + + offsetof(struct RGF_ICR, ICR)); + + wil_dbg_irq(wil, "ISR TX 0x%08x\n", isr); + + if (!isr) { + wil_err(wil, "spurious IRQ: TX\n"); + return IRQ_NONE; + } + + wil6210_mask_irq_tx(wil); + + if (isr & BIT_DMA_EP_TX_ICR_TX_DONE) { + uint i; + wil_dbg_irq(wil, "TX done\n"); + isr &= ~BIT_DMA_EP_TX_ICR_TX_DONE; + for (i = 0; i < 24; i++) { + u32 mask = BIT_DMA_EP_TX_ICR_TX_DONE_N(i); + if (isr & mask) { + isr &= ~mask; + wil_dbg_irq(wil, "TX done(%i)\n", i); + wil_tx_complete(wil, i); + } + } + } + + if (isr) + wil_err(wil, "un-handled TX ISR bits 0x%08x\n", isr); + + wil6210_unmask_irq_tx(wil); + + return IRQ_HANDLED; +} + +static void wil_notify_fw_error(struct wil6210_priv *wil) +{ + struct device *dev = &wil_to_ndev(wil)->dev; + char *envp[3] = { + [0] = "SOURCE=wil6210", + [1] = "EVENT=FW_ERROR", + [2] = NULL, + }; + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); +} + +static void wil_cache_mbox_regs(struct wil6210_priv *wil) +{ + /* make shadow copy of registers that should not change on run time */ + wil_memcpy_fromio_32(&wil->mbox_ctl, wil->csr + HOST_MBOX, + sizeof(struct wil6210_mbox_ctl)); + wil_mbox_ring_le2cpus(&wil->mbox_ctl.rx); + wil_mbox_ring_le2cpus(&wil->mbox_ctl.tx); +} + +static irqreturn_t wil6210_irq_misc(int irq, void *cookie) +{ + struct wil6210_priv *wil = cookie; + u32 isr = wil_ioread32_and_clear(wil->csr + + HOSTADDR(RGF_DMA_EP_MISC_ICR) + + offsetof(struct RGF_ICR, ICR)); + + wil_dbg_irq(wil, "ISR MISC 0x%08x\n", isr); + + if (!isr) { + wil_err(wil, "spurious IRQ: MISC\n"); + return IRQ_NONE; + } + + wil6210_mask_irq_misc(wil); + + if (isr & ISR_MISC_FW_ERROR) { + wil_err(wil, "Firmware error detected\n"); + clear_bit(wil_status_fwready, &wil->status); + /* + * do not clear @isr here - we do 2-nd part in thread + * there, user space get notified, and it should be done + * in non-atomic context + */ + } + + if (isr & ISR_MISC_FW_READY) { + wil_dbg_irq(wil, "IRQ: FW ready\n"); + wil_cache_mbox_regs(wil); + set_bit(wil_status_reset_done, &wil->status); + /** + * Actual FW ready indicated by the + * WMI_FW_READY_EVENTID + */ + isr &= ~ISR_MISC_FW_READY; + } + + wil->isr_misc = isr; + + if (isr) { + return IRQ_WAKE_THREAD; + } else { + wil6210_unmask_irq_misc(wil); + return IRQ_HANDLED; + } +} + +static irqreturn_t wil6210_irq_misc_thread(int irq, void *cookie) +{ + struct wil6210_priv *wil = cookie; + u32 isr = wil->isr_misc; + + wil_dbg_irq(wil, "Thread ISR MISC 0x%08x\n", isr); + + if (isr & ISR_MISC_FW_ERROR) { + wil_notify_fw_error(wil); + isr &= ~ISR_MISC_FW_ERROR; + } + + if (isr & ISR_MISC_MBOX_EVT) { + wil_dbg_irq(wil, "MBOX event\n"); + wmi_recv_cmd(wil); + isr &= ~ISR_MISC_MBOX_EVT; + } + + if (isr) + wil_err(wil, "un-handled MISC ISR bits 0x%08x\n", isr); + + wil->isr_misc = 0; + + wil6210_unmask_irq_misc(wil); + + return IRQ_HANDLED; +} + +/** + * thread IRQ handler + */ +static irqreturn_t wil6210_thread_irq(int irq, void *cookie) +{ + struct wil6210_priv *wil = cookie; + + wil_dbg_irq(wil, "Thread IRQ\n"); + /* Discover real IRQ cause */ + if (wil->isr_misc) + wil6210_irq_misc_thread(irq, cookie); + + wil6210_unmask_irq_pseudo(wil); + + return IRQ_HANDLED; +} + +/* DEBUG + * There is subtle bug in hardware that causes IRQ to raise when it should be + * masked. It is quite rare and hard to debug. + * + * Catch irq issue if it happens and print all I can. + */ +static int wil6210_debug_irq_mask(struct wil6210_priv *wil, u32 pseudo_cause) +{ + if (!test_bit(wil_status_irqen, &wil->status)) { + u32 icm_rx = wil_ioread32_and_clear(wil->csr + + HOSTADDR(RGF_DMA_EP_RX_ICR) + + offsetof(struct RGF_ICR, ICM)); + u32 icr_rx = wil_ioread32_and_clear(wil->csr + + HOSTADDR(RGF_DMA_EP_RX_ICR) + + offsetof(struct RGF_ICR, ICR)); + u32 imv_rx = ioread32(wil->csr + + HOSTADDR(RGF_DMA_EP_RX_ICR) + + offsetof(struct RGF_ICR, IMV)); + u32 icm_tx = wil_ioread32_and_clear(wil->csr + + HOSTADDR(RGF_DMA_EP_TX_ICR) + + offsetof(struct RGF_ICR, ICM)); + u32 icr_tx = wil_ioread32_and_clear(wil->csr + + HOSTADDR(RGF_DMA_EP_TX_ICR) + + offsetof(struct RGF_ICR, ICR)); + u32 imv_tx = ioread32(wil->csr + + HOSTADDR(RGF_DMA_EP_TX_ICR) + + offsetof(struct RGF_ICR, IMV)); + u32 icm_misc = wil_ioread32_and_clear(wil->csr + + HOSTADDR(RGF_DMA_EP_MISC_ICR) + + offsetof(struct RGF_ICR, ICM)); + u32 icr_misc = wil_ioread32_and_clear(wil->csr + + HOSTADDR(RGF_DMA_EP_MISC_ICR) + + offsetof(struct RGF_ICR, ICR)); + u32 imv_misc = ioread32(wil->csr + + HOSTADDR(RGF_DMA_EP_MISC_ICR) + + offsetof(struct RGF_ICR, IMV)); + wil_err(wil, "IRQ when it should be masked: pseudo 0x%08x\n" + "Rx icm:icr:imv 0x%08x 0x%08x 0x%08x\n" + "Tx icm:icr:imv 0x%08x 0x%08x 0x%08x\n" + "Misc icm:icr:imv 0x%08x 0x%08x 0x%08x\n", + pseudo_cause, + icm_rx, icr_rx, imv_rx, + icm_tx, icr_tx, imv_tx, + icm_misc, icr_misc, imv_misc); + + return -EINVAL; + } + + return 0; +} + +static irqreturn_t wil6210_hardirq(int irq, void *cookie) +{ + irqreturn_t rc = IRQ_HANDLED; + struct wil6210_priv *wil = cookie; + u32 pseudo_cause = ioread32(wil->csr + HOSTADDR(RGF_DMA_PSEUDO_CAUSE)); + + /** + * pseudo_cause is Clear-On-Read, no need to ACK + */ + if ((pseudo_cause == 0) || ((pseudo_cause & 0xff) == 0xff)) + return IRQ_NONE; + + /* FIXME: IRQ mask debug */ + if (wil6210_debug_irq_mask(wil, pseudo_cause)) + return IRQ_NONE; + + wil_dbg_irq(wil, "Pseudo IRQ 0x%08x\n", pseudo_cause); + + wil6210_mask_irq_pseudo(wil); + + /* Discover real IRQ cause + * There are 2 possible phases for every IRQ: + * - hard IRQ handler called right here + * - threaded handler called later + * + * Hard IRQ handler reads and clears ISR. + * + * If threaded handler requested, hard IRQ handler + * returns IRQ_WAKE_THREAD and saves ISR register value + * for the threaded handler use. + * + * voting for wake thread - need at least 1 vote + */ + if ((pseudo_cause & BIT_DMA_PSEUDO_CAUSE_RX) && + (wil6210_irq_rx(irq, cookie) == IRQ_WAKE_THREAD)) + rc = IRQ_WAKE_THREAD; + + if ((pseudo_cause & BIT_DMA_PSEUDO_CAUSE_TX) && + (wil6210_irq_tx(irq, cookie) == IRQ_WAKE_THREAD)) + rc = IRQ_WAKE_THREAD; + + if ((pseudo_cause & BIT_DMA_PSEUDO_CAUSE_MISC) && + (wil6210_irq_misc(irq, cookie) == IRQ_WAKE_THREAD)) + rc = IRQ_WAKE_THREAD; + + /* if thread is requested, it will unmask IRQ */ + if (rc != IRQ_WAKE_THREAD) + wil6210_unmask_irq_pseudo(wil); + + return rc; +} + +static int wil6210_request_3msi(struct wil6210_priv *wil, int irq) +{ + int rc; + /* + * IRQ's are in the following order: + * - Tx + * - Rx + * - Misc + */ + + rc = request_irq(irq, wil6210_irq_tx, IRQF_SHARED, + WIL_NAME"_tx", wil); + if (rc) + return rc; + + rc = request_irq(irq + 1, wil6210_irq_rx, IRQF_SHARED, + WIL_NAME"_rx", wil); + if (rc) + goto free0; + + rc = request_threaded_irq(irq + 2, wil6210_irq_misc, + wil6210_irq_misc_thread, + IRQF_SHARED, WIL_NAME"_misc", wil); + if (rc) + goto free1; + + return 0; + /* error branch */ +free1: + free_irq(irq + 1, wil); +free0: + free_irq(irq, wil); + + return rc; +} + +int wil6210_init_irq(struct wil6210_priv *wil, int irq) +{ + int rc; + if (wil->n_msi == 3) + rc = wil6210_request_3msi(wil, irq); + else + rc = request_threaded_irq(irq, wil6210_hardirq, + wil6210_thread_irq, + wil->n_msi ? 0 : IRQF_SHARED, + WIL_NAME, wil); + if (rc) + return rc; + + wil6210_enable_irq(wil); + + return 0; +} + +void wil6210_fini_irq(struct wil6210_priv *wil, int irq) +{ + wil6210_disable_irq(wil); + free_irq(irq, wil); + if (wil->n_msi == 3) { + free_irq(irq + 1, wil); + free_irq(irq + 2, wil); + } +} diff --git a/drivers/net/wireless/ath/wil6210/main.c b/drivers/net/wireless/ath/wil6210/main.c new file mode 100644 index 0000000..a0478e2 --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/main.c @@ -0,0 +1,404 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "wil6210.h" + +/* + * Due to a hardware issue, + * one has to read/write to/from NIC in 32-bit chunks; + * regular memcpy_fromio and siblings will + * not work on 64-bit platform - it uses 64-bit transactions + * + * Force 32-bit transactions to enable NIC on 64-bit platforms + * + * To avoid byte swap on big endian host, __raw_{read|write}l + * should be used - {read|write}l would swap bytes to provide + * little endian on PCI value in host endianness. + */ +void wil_memcpy_fromio_32(void *dst, const volatile void __iomem *src, + size_t count) +{ + u32 *d = dst; + const volatile u32 __iomem *s = src; + + /* size_t is unsigned, if (count%4 != 0) it will wrap */ + for (count += 4; count > 4; count -= 4) + *d++ = __raw_readl(s++); +} + +void wil_memcpy_toio_32(volatile void __iomem *dst, const void *src, + size_t count) +{ + volatile u32 __iomem *d = dst; + const u32 *s = src; + + for (count += 4; count > 4; count -= 4) + __raw_writel(*s++, d++); +} + +static void _wil6210_disconnect(struct wil6210_priv *wil, void *bssid) +{ + uint i; + struct net_device *ndev = wil_to_ndev(wil); + struct wireless_dev *wdev = wil->wdev; + + wil_dbg_misc(wil, "%s()\n", __func__); + + wil_link_off(wil); + clear_bit(wil_status_fwconnected, &wil->status); + + switch (wdev->sme_state) { + case CFG80211_SME_CONNECTED: + cfg80211_disconnected(ndev, WLAN_STATUS_UNSPECIFIED_FAILURE, + NULL, 0, GFP_KERNEL); + break; + case CFG80211_SME_CONNECTING: + cfg80211_connect_result(ndev, bssid, NULL, 0, NULL, 0, + WLAN_STATUS_UNSPECIFIED_FAILURE, + GFP_KERNEL); + break; + default: + break; + } + + for (i = 0; i < ARRAY_SIZE(wil->vring_tx); i++) + wil_vring_fini_tx(wil, i); + + clear_bit(wil_status_dontscan, &wil->status); +} + +static void wil_disconnect_worker(struct work_struct *work) +{ + struct wil6210_priv *wil = container_of(work, + struct wil6210_priv, disconnect_worker); + + _wil6210_disconnect(wil, NULL); +} + +static void wil_connect_timer_fn(ulong x) +{ + struct wil6210_priv *wil = (void *)x; + + wil_dbg_misc(wil, "Connect timeout\n"); + + /* reschedule to thread context - disconnect won't + * run from atomic context + */ + schedule_work(&wil->disconnect_worker); +} + +static void wil_connect_worker(struct work_struct *work) +{ + int rc; + struct wil6210_priv *wil = container_of(work, struct wil6210_priv, + connect_worker); + int cid = wil->pending_connect_cid; + + if (cid < 0) { + wil_err(wil, "No connection pending\n"); + return; + } + + wil_dbg_wmi(wil, "Configure for connection CID %d\n", cid); + + rc = wil_vring_init_tx(wil, 0, WIL6210_TX_RING_SIZE, cid, 0); + wil->pending_connect_cid = -1; + if (rc == 0) + wil_link_on(wil); +} + +int wil_priv_init(struct wil6210_priv *wil) +{ + wil_dbg_misc(wil, "%s()\n", __func__); + + mutex_init(&wil->mutex); + mutex_init(&wil->wmi_mutex); + + init_completion(&wil->wmi_ready); + + wil->pending_connect_cid = -1; + setup_timer(&wil->connect_timer, wil_connect_timer_fn, (ulong)wil); + + INIT_WORK(&wil->connect_worker, wil_connect_worker); + INIT_WORK(&wil->disconnect_worker, wil_disconnect_worker); + INIT_WORK(&wil->wmi_event_worker, wmi_event_worker); + + INIT_LIST_HEAD(&wil->pending_wmi_ev); + spin_lock_init(&wil->wmi_ev_lock); + + wil->wmi_wq = create_singlethread_workqueue(WIL_NAME"_wmi"); + if (!wil->wmi_wq) + return -EAGAIN; + + wil->wmi_wq_conn = create_singlethread_workqueue(WIL_NAME"_connect"); + if (!wil->wmi_wq_conn) { + destroy_workqueue(wil->wmi_wq); + return -EAGAIN; + } + + return 0; +} + +void wil6210_disconnect(struct wil6210_priv *wil, void *bssid) +{ + del_timer_sync(&wil->connect_timer); + _wil6210_disconnect(wil, bssid); +} + +void wil_priv_deinit(struct wil6210_priv *wil) +{ + cancel_work_sync(&wil->disconnect_worker); + wil6210_disconnect(wil, NULL); + wmi_event_flush(wil); + destroy_workqueue(wil->wmi_wq_conn); + destroy_workqueue(wil->wmi_wq); +} + +static void wil_target_reset(struct wil6210_priv *wil) +{ + wil_dbg_misc(wil, "Resetting...\n"); + + /* register write */ +#define W(a, v) iowrite32(v, wil->csr + HOSTADDR(a)) + /* register set = read, OR, write */ +#define S(a, v) iowrite32(ioread32(wil->csr + HOSTADDR(a)) | v, \ + wil->csr + HOSTADDR(a)) + + /* hpal_perst_from_pad_src_n_mask */ + S(RGF_USER_CLKS_CTL_SW_RST_MASK_0, BIT(6)); + /* car_perst_rst_src_n_mask */ + S(RGF_USER_CLKS_CTL_SW_RST_MASK_0, BIT(7)); + + W(RGF_USER_MAC_CPU_0, BIT(1)); /* mac_cpu_man_rst */ + W(RGF_USER_USER_CPU_0, BIT(1)); /* user_cpu_man_rst */ + + W(RGF_USER_CLKS_CTL_SW_RST_VEC_2, 0xFE000000); + W(RGF_USER_CLKS_CTL_SW_RST_VEC_1, 0x0000003F); + W(RGF_USER_CLKS_CTL_SW_RST_VEC_3, 0x00000170); + W(RGF_USER_CLKS_CTL_SW_RST_VEC_0, 0xFFE7FC00); + + W(RGF_USER_CLKS_CTL_SW_RST_VEC_3, 0); + W(RGF_USER_CLKS_CTL_SW_RST_VEC_2, 0); + W(RGF_USER_CLKS_CTL_SW_RST_VEC_1, 0); + W(RGF_USER_CLKS_CTL_SW_RST_VEC_0, 0); + + W(RGF_USER_CLKS_CTL_SW_RST_VEC_3, 0x00000001); + W(RGF_USER_CLKS_CTL_SW_RST_VEC_2, 0x00000080); + W(RGF_USER_CLKS_CTL_SW_RST_VEC_0, 0); + + wil_dbg_misc(wil, "Reset completed\n"); + +#undef W +#undef S +} + +void wil_mbox_ring_le2cpus(struct wil6210_mbox_ring *r) +{ + le32_to_cpus(&r->base); + le16_to_cpus(&r->entry_size); + le16_to_cpus(&r->size); + le32_to_cpus(&r->tail); + le32_to_cpus(&r->head); +} + +static int wil_wait_for_fw_ready(struct wil6210_priv *wil) +{ + ulong to = msecs_to_jiffies(1000); + ulong left = wait_for_completion_timeout(&wil->wmi_ready, to); + if (0 == left) { + wil_err(wil, "Firmware not ready\n"); + return -ETIME; + } else { + wil_dbg_misc(wil, "FW ready after %d ms\n", + jiffies_to_msecs(to-left)); + } + return 0; +} + +/* + * We reset all the structures, and we reset the UMAC. + * After calling this routine, you're expected to reload + * the firmware. + */ +int wil_reset(struct wil6210_priv *wil) +{ + int rc; + + cancel_work_sync(&wil->disconnect_worker); + wil6210_disconnect(wil, NULL); + + wil6210_disable_irq(wil); + wil->status = 0; + + wmi_event_flush(wil); + + flush_workqueue(wil->wmi_wq_conn); + flush_workqueue(wil->wmi_wq); + + /* TODO: put MAC in reset */ + wil_target_reset(wil); + + /* init after reset */ + wil->pending_connect_cid = -1; + INIT_COMPLETION(wil->wmi_ready); + + /* TODO: release MAC reset */ + wil6210_enable_irq(wil); + + /* we just started MAC, wait for FW ready */ + rc = wil_wait_for_fw_ready(wil); + + return rc; +} + + +void wil_link_on(struct wil6210_priv *wil) +{ + struct net_device *ndev = wil_to_ndev(wil); + + wil_dbg_misc(wil, "%s()\n", __func__); + + netif_carrier_on(ndev); + netif_tx_wake_all_queues(ndev); +} + +void wil_link_off(struct wil6210_priv *wil) +{ + struct net_device *ndev = wil_to_ndev(wil); + + wil_dbg_misc(wil, "%s()\n", __func__); + + netif_tx_stop_all_queues(ndev); + netif_carrier_off(ndev); +} + +static int __wil_up(struct wil6210_priv *wil) +{ + struct net_device *ndev = wil_to_ndev(wil); + struct wireless_dev *wdev = wil->wdev; + struct ieee80211_channel *channel = wdev->preset_chandef.chan; + int rc; + int bi; + u16 wmi_nettype = wil_iftype_nl2wmi(wdev->iftype); + + rc = wil_reset(wil); + if (rc) + return rc; + + /* FIXME Firmware works now in PBSS mode(ToDS=0, FromDS=0) */ + wmi_nettype = wil_iftype_nl2wmi(NL80211_IFTYPE_ADHOC); + switch (wdev->iftype) { + case NL80211_IFTYPE_STATION: + wil_dbg_misc(wil, "type: STATION\n"); + bi = 0; + ndev->type = ARPHRD_ETHER; + break; + case NL80211_IFTYPE_AP: + wil_dbg_misc(wil, "type: AP\n"); + bi = 100; + ndev->type = ARPHRD_ETHER; + break; + case NL80211_IFTYPE_P2P_CLIENT: + wil_dbg_misc(wil, "type: P2P_CLIENT\n"); + bi = 0; + ndev->type = ARPHRD_ETHER; + break; + case NL80211_IFTYPE_P2P_GO: + wil_dbg_misc(wil, "type: P2P_GO\n"); + bi = 100; + ndev->type = ARPHRD_ETHER; + break; + case NL80211_IFTYPE_MONITOR: + wil_dbg_misc(wil, "type: Monitor\n"); + bi = 0; + ndev->type = ARPHRD_IEEE80211_RADIOTAP; + /* ARPHRD_IEEE80211 or ARPHRD_IEEE80211_RADIOTAP ? */ + break; + default: + return -EOPNOTSUPP; + } + + /* Apply profile in the following order: */ + /* SSID and channel for the AP */ + switch (wdev->iftype) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + if (wdev->ssid_len == 0) { + wil_err(wil, "SSID not set\n"); + return -EINVAL; + } + rc = wmi_set_ssid(wil, wdev->ssid_len, wdev->ssid); + if (rc) + return rc; + break; + default: + break; + } + + /* MAC address - pre-requisite for other commands */ + wmi_set_mac_address(wil, ndev->dev_addr); + + /* Set up beaconing if required. */ + if (bi > 0) { + rc = wmi_pcp_start(wil, bi, wmi_nettype, + (channel ? channel->hw_value : 0)); + if (rc) + return rc; + } + + /* Rx VRING. After MAC and beacon */ + wil_rx_init(wil); + + return 0; +} + +int wil_up(struct wil6210_priv *wil) +{ + int rc; + + mutex_lock(&wil->mutex); + rc = __wil_up(wil); + mutex_unlock(&wil->mutex); + + return rc; +} + +static int __wil_down(struct wil6210_priv *wil) +{ + if (wil->scan_request) { + cfg80211_scan_done(wil->scan_request, true); + wil->scan_request = NULL; + } + + wil6210_disconnect(wil, NULL); + wil_rx_fini(wil); + + return 0; +} + +int wil_down(struct wil6210_priv *wil) +{ + int rc; + + mutex_lock(&wil->mutex); + rc = __wil_down(wil); + mutex_unlock(&wil->mutex); + + return rc; +} diff --git a/drivers/net/wireless/ath/wil6210/netdev.c b/drivers/net/wireless/ath/wil6210/netdev.c new file mode 100644 index 0000000..5c5a990 --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/netdev.c @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "wil6210.h" + +static int wil_open(struct net_device *ndev) +{ + struct wil6210_priv *wil = ndev_to_wil(ndev); + + return wil_up(wil); +} + +static int wil_stop(struct net_device *ndev) +{ + struct wil6210_priv *wil = ndev_to_wil(ndev); + + return wil_down(wil); +} + +static const struct net_device_ops wil_netdev_ops = { + .ndo_open = wil_open, + .ndo_stop = wil_stop, + .ndo_start_xmit = wil_start_xmit, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +void *wil_if_alloc(struct device *dev, void __iomem *csr) +{ + struct net_device *ndev; + struct wireless_dev *wdev; + struct wil6210_priv *wil; + struct ieee80211_channel *ch; + int rc = 0; + + wdev = wil_cfg80211_init(dev); + if (IS_ERR(wdev)) { + dev_err(dev, "wil_cfg80211_init failed\n"); + return wdev; + } + + wil = wdev_to_wil(wdev); + wil->csr = csr; + wil->wdev = wdev; + + rc = wil_priv_init(wil); + if (rc) { + dev_err(dev, "wil_priv_init failed\n"); + goto out_wdev; + } + + wdev->iftype = NL80211_IFTYPE_STATION; /* TODO */ + /* default monitor channel */ + ch = wdev->wiphy->bands[IEEE80211_BAND_60GHZ]->channels; + cfg80211_chandef_create(&wdev->preset_chandef, ch, NL80211_CHAN_NO_HT); + + ndev = alloc_netdev(0, "wlan%d", ether_setup); + if (!ndev) { + dev_err(dev, "alloc_netdev_mqs failed\n"); + rc = -ENOMEM; + goto out_priv; + } + + netdev_attach_ops(ndev, &wil_netdev_ops); + ndev->ieee80211_ptr = wdev; + SET_NETDEV_DEV(ndev, wiphy_dev(wdev->wiphy)); + wdev->netdev = ndev; + + wil_link_off(wil); + + return wil; + + out_priv: + wil_priv_deinit(wil); + + out_wdev: + wil_wdev_free(wil); + + return ERR_PTR(rc); +} + +void wil_if_free(struct wil6210_priv *wil) +{ + struct net_device *ndev = wil_to_ndev(wil); + if (!ndev) + return; + + free_netdev(ndev); + wil_priv_deinit(wil); + wil_wdev_free(wil); +} + +int wil_if_add(struct wil6210_priv *wil) +{ + struct net_device *ndev = wil_to_ndev(wil); + int rc; + + rc = register_netdev(ndev); + if (rc < 0) { + dev_err(&ndev->dev, "Failed to register netdev: %d\n", rc); + return rc; + } + + wil_link_off(wil); + + return 0; +} + +void wil_if_remove(struct wil6210_priv *wil) +{ + struct net_device *ndev = wil_to_ndev(wil); + + unregister_netdev(ndev); +} diff --git a/drivers/net/wireless/ath/wil6210/pcie_bus.c b/drivers/net/wireless/ath/wil6210/pcie_bus.c new file mode 100644 index 0000000..eb1dc7a --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/pcie_bus.c @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include "wil6210.h" + +static int use_msi = 1; +module_param(use_msi, int, S_IRUGO); +MODULE_PARM_DESC(use_msi, + " Use MSI interrupt: " + "0 - don't, 1 - (default) - single, or 3"); + +/* Bus ops */ +static int wil_if_pcie_enable(struct wil6210_priv *wil) +{ + struct pci_dev *pdev = wil->pdev; + int rc; + + pci_set_master(pdev); + + /* + * how many MSI interrupts to request? + */ + switch (use_msi) { + case 3: + case 1: + case 0: + break; + default: + wil_err(wil, "Invalid use_msi=%d, default to 1\n", + use_msi); + use_msi = 1; + } + wil->n_msi = use_msi; + if (wil->n_msi) { + wil_dbg_misc(wil, "Setup %d MSI interrupts\n", use_msi); + rc = pci_enable_msi_block(pdev, wil->n_msi); + if (rc && (wil->n_msi == 3)) { + wil_err(wil, "3 MSI mode failed, try 1 MSI\n"); + wil->n_msi = 1; + rc = pci_enable_msi_block(pdev, wil->n_msi); + } + if (rc) { + wil_err(wil, "pci_enable_msi failed, use INTx\n"); + wil->n_msi = 0; + } + } else { + wil_dbg_misc(wil, "MSI interrupts disabled, use INTx\n"); + } + + rc = wil6210_init_irq(wil, pdev->irq); + if (rc) + goto stop_master; + + /* need reset here to obtain MAC */ + rc = wil_reset(wil); + if (rc) + goto release_irq; + + return 0; + + release_irq: + wil6210_fini_irq(wil, pdev->irq); + /* safe to call if no MSI */ + pci_disable_msi(pdev); + stop_master: + pci_clear_master(pdev); + return rc; +} + +static int wil_if_pcie_disable(struct wil6210_priv *wil) +{ + struct pci_dev *pdev = wil->pdev; + + pci_clear_master(pdev); + /* disable and release IRQ */ + wil6210_fini_irq(wil, pdev->irq); + /* safe to call if no MSI */ + pci_disable_msi(pdev); + /* TODO: disable HW */ + + return 0; +} + +static int wil_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct wil6210_priv *wil; + struct device *dev = &pdev->dev; + void __iomem *csr; + int rc; + + /* check HW */ + dev_info(&pdev->dev, WIL_NAME " device found [%04x:%04x] (rev %x)\n", + (int)pdev->vendor, (int)pdev->device, (int)pdev->revision); + + if (pci_resource_len(pdev, 0) != WIL6210_MEM_SIZE) { + dev_err(&pdev->dev, "Not " WIL_NAME "? " + "BAR0 size is %lu while expecting %lu\n", + (ulong)pci_resource_len(pdev, 0), WIL6210_MEM_SIZE); + return -ENODEV; + } + + rc = pci_enable_device(pdev); + if (rc) { + dev_err(&pdev->dev, "pci_enable_device failed\n"); + return -ENODEV; + } + /* rollback to err_disable_pdev */ + + rc = pci_request_region(pdev, 0, WIL_NAME); + if (rc) { + dev_err(&pdev->dev, "pci_request_region failed\n"); + goto err_disable_pdev; + } + /* rollback to err_release_reg */ + + csr = pci_ioremap_bar(pdev, 0); + if (!csr) { + dev_err(&pdev->dev, "pci_ioremap_bar failed\n"); + rc = -ENODEV; + goto err_release_reg; + } + /* rollback to err_iounmap */ + dev_info(&pdev->dev, "CSR at %pR -> %p\n", &pdev->resource[0], csr); + + wil = wil_if_alloc(dev, csr); + if (IS_ERR(wil)) { + rc = (int)PTR_ERR(wil); + dev_err(dev, "wil_if_alloc failed: %d\n", rc); + goto err_iounmap; + } + /* rollback to if_free */ + + pci_set_drvdata(pdev, wil); + wil->pdev = pdev; + + /* FW should raise IRQ when ready */ + rc = wil_if_pcie_enable(wil); + if (rc) { + wil_err(wil, "Enable device failed\n"); + goto if_free; + } + /* rollback to bus_disable */ + + rc = wil_if_add(wil); + if (rc) { + wil_err(wil, "wil_if_add failed: %d\n", rc); + goto bus_disable; + } + + wil6210_debugfs_init(wil); + + /* check FW is alive */ + wmi_echo(wil); + + return 0; + + bus_disable: + wil_if_pcie_disable(wil); + if_free: + wil_if_free(wil); + err_iounmap: + pci_iounmap(pdev, csr); + err_release_reg: + pci_release_region(pdev, 0); + err_disable_pdev: + pci_disable_device(pdev); + + return rc; +} + +static void wil_pcie_remove(struct pci_dev *pdev) +{ + struct wil6210_priv *wil = pci_get_drvdata(pdev); + + wil6210_debugfs_remove(wil); + wil_if_pcie_disable(wil); + wil_if_remove(wil); + wil_if_free(wil); + pci_iounmap(pdev, wil->csr); + pci_release_region(pdev, 0); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); +} + +static DEFINE_PCI_DEVICE_TABLE(wil6210_pcie_ids) = { + { PCI_DEVICE(0x1ae9, 0x0301) }, + { /* end: all zeroes */ }, +}; +MODULE_DEVICE_TABLE(pci, wil6210_pcie_ids); + +static struct pci_driver wil6210_driver = { + .probe = wil_pcie_probe, + .remove = wil_pcie_remove, + .id_table = wil6210_pcie_ids, + .name = WIL_NAME, +}; + +module_pci_driver(wil6210_driver); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Qualcomm Atheros "); +MODULE_DESCRIPTION("Driver for 60g WiFi WIL6210 card"); diff --git a/drivers/net/wireless/ath/wil6210/txrx.c b/drivers/net/wireless/ath/wil6210/txrx.c new file mode 100644 index 0000000..7970245 --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/txrx.c @@ -0,0 +1,832 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include "wil6210.h" +#include "wmi.h" +#include "txrx.h" + +static bool rtap_include_phy_info; +module_param(rtap_include_phy_info, bool, S_IRUGO); +MODULE_PARM_DESC(rtap_include_phy_info, + " Include PHY info in the radiotap header, default - no"); + +static inline int wil_vring_is_empty(struct vring *vring) +{ + return vring->swhead == vring->swtail; +} + +static inline u32 wil_vring_next_tail(struct vring *vring) +{ + return (vring->swtail + 1) % vring->size; +} + +static inline void wil_vring_advance_head(struct vring *vring, int n) +{ + vring->swhead = (vring->swhead + n) % vring->size; +} + +static inline int wil_vring_is_full(struct vring *vring) +{ + return wil_vring_next_tail(vring) == vring->swhead; +} +/* + * Available space in Tx Vring + */ +static inline int wil_vring_avail_tx(struct vring *vring) +{ + u32 swhead = vring->swhead; + u32 swtail = vring->swtail; + int used = (vring->size + swhead - swtail) % vring->size; + + return vring->size - used - 1; +} + +static int wil_vring_alloc(struct wil6210_priv *wil, struct vring *vring) +{ + struct device *dev = wil_to_dev(wil); + size_t sz = vring->size * sizeof(vring->va[0]); + uint i; + + BUILD_BUG_ON(sizeof(vring->va[0]) != 32); + + vring->swhead = 0; + vring->swtail = 0; + vring->ctx = kzalloc(vring->size * sizeof(vring->ctx[0]), GFP_KERNEL); + if (!vring->ctx) { + vring->va = NULL; + return -ENOMEM; + } + /* + * vring->va should be aligned on its size rounded up to power of 2 + * This is granted by the dma_alloc_coherent + */ + vring->va = dma_alloc_coherent(dev, sz, &vring->pa, GFP_KERNEL); + if (!vring->va) { + kfree(vring->ctx); + vring->ctx = NULL; + return -ENOMEM; + } + /* initially, all descriptors are SW owned + * For Tx and Rx, ownership bit is at the same location, thus + * we can use any + */ + for (i = 0; i < vring->size; i++) { + volatile struct vring_tx_desc *d = &(vring->va[i].tx); + d->dma.status = TX_DMA_STATUS_DU; + } + + wil_dbg_misc(wil, "vring[%d] 0x%p:0x%016llx 0x%p\n", vring->size, + vring->va, (unsigned long long)vring->pa, vring->ctx); + + return 0; +} + +static void wil_vring_free(struct wil6210_priv *wil, struct vring *vring, + int tx) +{ + struct device *dev = wil_to_dev(wil); + size_t sz = vring->size * sizeof(vring->va[0]); + + while (!wil_vring_is_empty(vring)) { + if (tx) { + volatile struct vring_tx_desc *d = + &vring->va[vring->swtail].tx; + dma_addr_t pa = d->dma.addr_low | + ((u64)d->dma.addr_high << 32); + struct sk_buff *skb = vring->ctx[vring->swtail]; + if (skb) { + dma_unmap_single(dev, pa, d->dma.length, + DMA_TO_DEVICE); + dev_kfree_skb_any(skb); + vring->ctx[vring->swtail] = NULL; + } else { + dma_unmap_page(dev, pa, d->dma.length, + DMA_TO_DEVICE); + } + vring->swtail = wil_vring_next_tail(vring); + } else { /* rx */ + volatile struct vring_rx_desc *d = + &vring->va[vring->swtail].rx; + dma_addr_t pa = d->dma.addr_low | + ((u64)d->dma.addr_high << 32); + struct sk_buff *skb = vring->ctx[vring->swhead]; + dma_unmap_single(dev, pa, d->dma.length, + DMA_FROM_DEVICE); + kfree_skb(skb); + wil_vring_advance_head(vring, 1); + } + } + dma_free_coherent(dev, sz, (void *)vring->va, vring->pa); + kfree(vring->ctx); + vring->pa = 0; + vring->va = NULL; + vring->ctx = NULL; +} + +/** + * Allocate one skb for Rx VRING + * + * Safe to call from IRQ + */ +static int wil_vring_alloc_skb(struct wil6210_priv *wil, struct vring *vring, + u32 i, int headroom) +{ + struct device *dev = wil_to_dev(wil); + unsigned int sz = RX_BUF_LEN; + volatile struct vring_rx_desc *d = &(vring->va[i].rx); + dma_addr_t pa; + + /* TODO align */ + struct sk_buff *skb = dev_alloc_skb(sz + headroom); + if (unlikely(!skb)) + return -ENOMEM; + + skb_reserve(skb, headroom); + skb_put(skb, sz); + + pa = dma_map_single(dev, skb->data, skb->len, DMA_FROM_DEVICE); + if (unlikely(dma_mapping_error(dev, pa))) { + kfree_skb(skb); + return -ENOMEM; + } + + d->dma.d0 = BIT(9) | RX_DMA_D0_CMD_DMA_IT; + d->dma.addr_low = lower_32_bits(pa); + d->dma.addr_high = (u16)upper_32_bits(pa); + /* ip_length don't care */ + /* b11 don't care */ + /* error don't care */ + d->dma.status = 0; /* BIT(0) should be 0 for HW_OWNED */ + d->dma.length = sz; + vring->ctx[i] = skb; + + return 0; +} + +/** + * Adds radiotap header + * + * Any error indicated as "Bad FCS" + * + * Vendor data for 04:ce:14-1 (Wilocity-1) consists of: + * - Rx descriptor: 32 bytes + * - Phy info + */ +static void wil_rx_add_radiotap_header(struct wil6210_priv *wil, + struct sk_buff *skb) +{ + struct wireless_dev *wdev = wil->wdev; + struct wil6210_rtap { + struct ieee80211_radiotap_header rthdr; + /* fields should be in the order of bits in rthdr.it_present */ + /* flags */ + u8 flags; + /* channel */ + __le16 chnl_freq __aligned(2); + __le16 chnl_flags; + /* MCS */ + u8 mcs_present; + u8 mcs_flags; + u8 mcs_index; + } __packed; + struct wil6210_rtap_vendor { + struct wil6210_rtap rtap; + /* vendor */ + u8 vendor_oui[3] __aligned(2); + u8 vendor_ns; + __le16 vendor_skip; + u8 vendor_data[0]; + } __packed; + struct vring_rx_desc *d = wil_skb_rxdesc(skb); + struct wil6210_rtap_vendor *rtap_vendor; + int rtap_len = sizeof(struct wil6210_rtap); + int phy_length = 0; /* phy info header size, bytes */ + static char phy_data[128]; + struct ieee80211_channel *ch = wdev->preset_chandef.chan; + + if (rtap_include_phy_info) { + rtap_len = sizeof(*rtap_vendor) + sizeof(*d); + /* calculate additional length */ + if (d->dma.status & RX_DMA_STATUS_PHY_INFO) { + /** + * PHY info starts from 8-byte boundary + * there are 8-byte lines, last line may be partially + * written (HW bug), thus FW configures for last line + * to be excessive. Driver skips this last line. + */ + int len = min_t(int, 8 + sizeof(phy_data), + wil_rxdesc_phy_length(d)); + if (len > 8) { + void *p = skb_tail_pointer(skb); + void *pa = PTR_ALIGN(p, 8); + if (skb_tailroom(skb) >= len + (pa - p)) { + phy_length = len - 8; + memcpy(phy_data, pa, phy_length); + } + } + } + rtap_len += phy_length; + } + + if (skb_headroom(skb) < rtap_len && + pskb_expand_head(skb, rtap_len, 0, GFP_ATOMIC)) { + wil_err(wil, "Unable to expand headrom to %d\n", rtap_len); + return; + } + + rtap_vendor = (void *)skb_push(skb, rtap_len); + memset(rtap_vendor, 0, rtap_len); + + rtap_vendor->rtap.rthdr.it_version = PKTHDR_RADIOTAP_VERSION; + rtap_vendor->rtap.rthdr.it_len = cpu_to_le16(rtap_len); + rtap_vendor->rtap.rthdr.it_present = cpu_to_le32( + (1 << IEEE80211_RADIOTAP_FLAGS) | + (1 << IEEE80211_RADIOTAP_CHANNEL) | + (1 << IEEE80211_RADIOTAP_MCS)); + if (d->dma.status & RX_DMA_STATUS_ERROR) + rtap_vendor->rtap.flags |= IEEE80211_RADIOTAP_F_BADFCS; + + rtap_vendor->rtap.chnl_freq = cpu_to_le16(ch ? ch->center_freq : 58320); + rtap_vendor->rtap.chnl_flags = cpu_to_le16(0); + + rtap_vendor->rtap.mcs_present = IEEE80211_RADIOTAP_MCS_HAVE_MCS; + rtap_vendor->rtap.mcs_flags = 0; + rtap_vendor->rtap.mcs_index = wil_rxdesc_mcs(d); + + if (rtap_include_phy_info) { + rtap_vendor->rtap.rthdr.it_present |= cpu_to_le32(1 << + IEEE80211_RADIOTAP_VENDOR_NAMESPACE); + /* OUI for Wilocity 04:ce:14 */ + rtap_vendor->vendor_oui[0] = 0x04; + rtap_vendor->vendor_oui[1] = 0xce; + rtap_vendor->vendor_oui[2] = 0x14; + rtap_vendor->vendor_ns = 1; + /* Rx descriptor + PHY data */ + rtap_vendor->vendor_skip = cpu_to_le16(sizeof(*d) + + phy_length); + memcpy(rtap_vendor->vendor_data, (void *)d, sizeof(*d)); + memcpy(rtap_vendor->vendor_data + sizeof(*d), phy_data, + phy_length); + } +} + +/* + * Fast swap in place between 2 registers + */ +static void wil_swap_u16(u16 *a, u16 *b) +{ + *a ^= *b; + *b ^= *a; + *a ^= *b; +} + +static void wil_swap_ethaddr(void *data) +{ + struct ethhdr *eth = data; + u16 *s = (u16 *)eth->h_source; + u16 *d = (u16 *)eth->h_dest; + + wil_swap_u16(s++, d++); + wil_swap_u16(s++, d++); + wil_swap_u16(s, d); +} + +/** + * reap 1 frame from @swhead + * + * Rx descriptor copied to skb->cb + * + * Safe to call from IRQ + */ +static struct sk_buff *wil_vring_reap_rx(struct wil6210_priv *wil, + struct vring *vring) +{ + struct device *dev = wil_to_dev(wil); + struct net_device *ndev = wil_to_ndev(wil); + volatile struct vring_rx_desc *d; + struct vring_rx_desc *d1; + struct sk_buff *skb; + dma_addr_t pa; + unsigned int sz = RX_BUF_LEN; + u8 ftype; + u8 ds_bits; + + BUILD_BUG_ON(sizeof(struct vring_rx_desc) > sizeof(skb->cb)); + + if (wil_vring_is_empty(vring)) + return NULL; + + d = &(vring->va[vring->swhead].rx); + if (!(d->dma.status & RX_DMA_STATUS_DU)) { + /* it is not error, we just reached end of Rx done area */ + return NULL; + } + + pa = d->dma.addr_low | ((u64)d->dma.addr_high << 32); + skb = vring->ctx[vring->swhead]; + dma_unmap_single(dev, pa, sz, DMA_FROM_DEVICE); + skb_trim(skb, d->dma.length); + + d1 = wil_skb_rxdesc(skb); + *d1 = *d; + + wil->stats.last_mcs_rx = wil_rxdesc_mcs(d1); + + /* use radiotap header only if required */ + if (ndev->type == ARPHRD_IEEE80211_RADIOTAP) + wil_rx_add_radiotap_header(wil, skb); + + wil_dbg_txrx(wil, "Rx[%3d] : %d bytes\n", vring->swhead, d->dma.length); + wil_hex_dump_txrx("Rx ", DUMP_PREFIX_NONE, 32, 4, + (const void *)d, sizeof(*d), false); + + wil_vring_advance_head(vring, 1); + + /* no extra checks if in sniffer mode */ + if (ndev->type != ARPHRD_ETHER) + return skb; + /* + * Non-data frames may be delivered through Rx DMA channel (ex: BAR) + * Driver should recognize it by frame type, that is found + * in Rx descriptor. If type is not data, it is 802.11 frame as is + */ + ftype = wil_rxdesc_ftype(d1) << 2; + if (ftype != IEEE80211_FTYPE_DATA) { + wil_dbg_txrx(wil, "Non-data frame ftype 0x%08x\n", ftype); + /* TODO: process it */ + kfree_skb(skb); + return NULL; + } + + if (skb->len < ETH_HLEN) { + wil_err(wil, "Short frame, len = %d\n", skb->len); + /* TODO: process it (i.e. BAR) */ + kfree_skb(skb); + return NULL; + } + + ds_bits = wil_rxdesc_ds_bits(d1); + if (ds_bits == 1) { + /* + * HW bug - in ToDS mode, i.e. Rx on AP side, + * addresses get swapped + */ + wil_swap_ethaddr(skb->data); + } + + return skb; +} + +/** + * allocate and fill up to @count buffers in rx ring + * buffers posted at @swtail + */ +static int wil_rx_refill(struct wil6210_priv *wil, int count) +{ + struct net_device *ndev = wil_to_ndev(wil); + struct vring *v = &wil->vring_rx; + u32 next_tail; + int rc = 0; + int headroom = ndev->type == ARPHRD_IEEE80211_RADIOTAP ? + WIL6210_RTAP_SIZE : 0; + + for (; next_tail = wil_vring_next_tail(v), + (next_tail != v->swhead) && (count-- > 0); + v->swtail = next_tail) { + rc = wil_vring_alloc_skb(wil, v, v->swtail, headroom); + if (rc) { + wil_err(wil, "Error %d in wil_rx_refill[%d]\n", + rc, v->swtail); + break; + } + } + iowrite32(v->swtail, wil->csr + HOSTADDR(v->hwtail)); + + return rc; +} + +/* + * Pass Rx packet to the netif. Update statistics. + */ +static void wil_netif_rx_any(struct sk_buff *skb, struct net_device *ndev) +{ + int rc; + unsigned int len = skb->len; + + skb_orphan(skb); + + if (in_interrupt()) + rc = netif_rx(skb); + else + rc = netif_rx_ni(skb); + + if (likely(rc == NET_RX_SUCCESS)) { + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += len; + + } else { + ndev->stats.rx_dropped++; + } +} + +/** + * Proceed all completed skb's from Rx VRING + * + * Safe to call from IRQ + */ +void wil_rx_handle(struct wil6210_priv *wil) +{ + struct net_device *ndev = wil_to_ndev(wil); + struct vring *v = &wil->vring_rx; + struct sk_buff *skb; + + if (!v->va) { + wil_err(wil, "Rx IRQ while Rx not yet initialized\n"); + return; + } + wil_dbg_txrx(wil, "%s()\n", __func__); + while (NULL != (skb = wil_vring_reap_rx(wil, v))) { + wil_hex_dump_txrx("Rx ", DUMP_PREFIX_OFFSET, 16, 1, + skb->data, skb_headlen(skb), false); + + if (wil->wdev->iftype == NL80211_IFTYPE_MONITOR) { + skb->dev = ndev; + skb_reset_mac_header(skb); + skb->ip_summed = CHECKSUM_UNNECESSARY; + skb->pkt_type = PACKET_OTHERHOST; + skb->protocol = htons(ETH_P_802_2); + + } else { + skb->protocol = eth_type_trans(skb, ndev); + } + + wil_netif_rx_any(skb, ndev); + } + wil_rx_refill(wil, v->size); +} + +int wil_rx_init(struct wil6210_priv *wil) +{ + struct vring *vring = &wil->vring_rx; + int rc; + + vring->size = WIL6210_RX_RING_SIZE; + rc = wil_vring_alloc(wil, vring); + if (rc) + return rc; + + rc = wmi_rx_chain_add(wil, vring); + if (rc) + goto err_free; + + rc = wil_rx_refill(wil, vring->size); + if (rc) + goto err_free; + + return 0; + err_free: + wil_vring_free(wil, vring, 0); + + return rc; +} + +void wil_rx_fini(struct wil6210_priv *wil) +{ + struct vring *vring = &wil->vring_rx; + + if (vring->va) + wil_vring_free(wil, vring, 0); +} + +int wil_vring_init_tx(struct wil6210_priv *wil, int id, int size, + int cid, int tid) +{ + int rc; + struct wmi_vring_cfg_cmd cmd = { + .action = cpu_to_le32(WMI_VRING_CMD_ADD), + .vring_cfg = { + .tx_sw_ring = { + .max_mpdu_size = cpu_to_le16(TX_BUF_LEN), + .ring_size = cpu_to_le16(size), + }, + .ringid = id, + .cidxtid = (cid & 0xf) | ((tid & 0xf) << 4), + .encap_trans_type = WMI_VRING_ENC_TYPE_802_3, + .mac_ctrl = 0, + .to_resolution = 0, + .agg_max_wsize = 16, + .schd_params = { + .priority = cpu_to_le16(0), + .timeslot_us = cpu_to_le16(0xfff), + }, + }, + }; + struct { + struct wil6210_mbox_hdr_wmi wmi; + struct wmi_vring_cfg_done_event cmd; + } __packed reply; + struct vring *vring = &wil->vring_tx[id]; + + if (vring->va) { + wil_err(wil, "Tx ring [%d] already allocated\n", id); + rc = -EINVAL; + goto out; + } + + vring->size = size; + rc = wil_vring_alloc(wil, vring); + if (rc) + goto out; + + cmd.vring_cfg.tx_sw_ring.ring_mem_base = cpu_to_le64(vring->pa); + + rc = wmi_call(wil, WMI_VRING_CFG_CMDID, &cmd, sizeof(cmd), + WMI_VRING_CFG_DONE_EVENTID, &reply, sizeof(reply), 100); + if (rc) + goto out_free; + + if (reply.cmd.status != WMI_FW_STATUS_SUCCESS) { + wil_err(wil, "Tx config failed, status 0x%02x\n", + reply.cmd.status); + rc = -EINVAL; + goto out_free; + } + vring->hwtail = le32_to_cpu(reply.cmd.tx_vring_tail_ptr); + + return 0; + out_free: + wil_vring_free(wil, vring, 1); + out: + + return rc; +} + +void wil_vring_fini_tx(struct wil6210_priv *wil, int id) +{ + struct vring *vring = &wil->vring_tx[id]; + + if (!vring->va) + return; + + wil_vring_free(wil, vring, 1); +} + +static struct vring *wil_find_tx_vring(struct wil6210_priv *wil, + struct sk_buff *skb) +{ + struct vring *v = &wil->vring_tx[0]; + + if (v->va) + return v; + + return NULL; +} + +static int wil_tx_desc_map(volatile struct vring_tx_desc *d, + dma_addr_t pa, u32 len) +{ + d->dma.addr_low = lower_32_bits(pa); + d->dma.addr_high = (u16)upper_32_bits(pa); + d->dma.ip_length = 0; + /* 0..6: mac_length; 7:ip_version 0-IP6 1-IP4*/ + d->dma.b11 = 0/*14 | BIT(7)*/; + d->dma.error = 0; + d->dma.status = 0; /* BIT(0) should be 0 for HW_OWNED */ + d->dma.length = len; + d->dma.d0 = 0; + d->mac.d[0] = 0; + d->mac.d[1] = 0; + d->mac.d[2] = 0; + d->mac.ucode_cmd = 0; + /* use dst index 0 */ + d->mac.d[1] |= BIT(MAC_CFG_DESC_TX_1_DST_INDEX_EN_POS) | + (0 << MAC_CFG_DESC_TX_1_DST_INDEX_POS); + /* translation type: 0 - bypass; 1 - 802.3; 2 - native wifi */ + d->mac.d[2] = BIT(MAC_CFG_DESC_TX_2_SNAP_HDR_INSERTION_EN_POS) | + (1 << MAC_CFG_DESC_TX_2_L2_TRANSLATION_TYPE_POS); + + return 0; +} + +static int wil_tx_vring(struct wil6210_priv *wil, struct vring *vring, + struct sk_buff *skb) +{ + struct device *dev = wil_to_dev(wil); + volatile struct vring_tx_desc *d; + u32 swhead = vring->swhead; + int avail = wil_vring_avail_tx(vring); + int nr_frags = skb_shinfo(skb)->nr_frags; + uint f; + int vring_index = vring - wil->vring_tx; + uint i = swhead; + dma_addr_t pa; + + wil_dbg_txrx(wil, "%s()\n", __func__); + + if (avail < vring->size/8) + netif_tx_stop_all_queues(wil_to_ndev(wil)); + if (avail < 1 + nr_frags) { + wil_err(wil, "Tx ring full. No space for %d fragments\n", + 1 + nr_frags); + return -ENOMEM; + } + d = &(vring->va[i].tx); + + /* FIXME FW can accept only unicast frames for the peer */ + memcpy(skb->data, wil->dst_addr[vring_index], ETH_ALEN); + + pa = dma_map_single(dev, skb->data, + skb_headlen(skb), DMA_TO_DEVICE); + + wil_dbg_txrx(wil, "Tx skb %d bytes %p -> %#08llx\n", skb_headlen(skb), + skb->data, (unsigned long long)pa); + wil_hex_dump_txrx("Tx ", DUMP_PREFIX_OFFSET, 16, 1, + skb->data, skb_headlen(skb), false); + + if (unlikely(dma_mapping_error(dev, pa))) + return -EINVAL; + /* 1-st segment */ + wil_tx_desc_map(d, pa, skb_headlen(skb)); + d->mac.d[2] |= ((nr_frags + 1) << + MAC_CFG_DESC_TX_2_NUM_OF_DESCRIPTORS_POS); + /* middle segments */ + for (f = 0; f < nr_frags; f++) { + const struct skb_frag_struct *frag = + &skb_shinfo(skb)->frags[f]; + int len = skb_frag_size(frag); + i = (swhead + f + 1) % vring->size; + d = &(vring->va[i].tx); + pa = skb_frag_dma_map(dev, frag, 0, skb_frag_size(frag), + DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(dev, pa))) + goto dma_error; + wil_tx_desc_map(d, pa, len); + vring->ctx[i] = NULL; + } + /* for the last seg only */ + d->dma.d0 |= BIT(DMA_CFG_DESC_TX_0_CMD_EOP_POS); + d->dma.d0 |= BIT(9); /* BUG: undocumented bit */ + d->dma.d0 |= BIT(DMA_CFG_DESC_TX_0_CMD_DMA_IT_POS); + d->dma.d0 |= (vring_index << DMA_CFG_DESC_TX_0_QID_POS); + + wil_hex_dump_txrx("Tx ", DUMP_PREFIX_NONE, 32, 4, + (const void *)d, sizeof(*d), false); + + /* advance swhead */ + wil_vring_advance_head(vring, nr_frags + 1); + wil_dbg_txrx(wil, "Tx swhead %d -> %d\n", swhead, vring->swhead); + iowrite32(vring->swhead, wil->csr + HOSTADDR(vring->hwtail)); + /* hold reference to skb + * to prevent skb release before accounting + * in case of immediate "tx done" + */ + vring->ctx[i] = skb_get(skb); + + return 0; + dma_error: + /* unmap what we have mapped */ + /* Note: increment @f to operate with positive index */ + for (f++; f > 0; f--) { + i = (swhead + f) % vring->size; + d = &(vring->va[i].tx); + d->dma.status = TX_DMA_STATUS_DU; + pa = d->dma.addr_low | ((u64)d->dma.addr_high << 32); + if (vring->ctx[i]) + dma_unmap_single(dev, pa, d->dma.length, DMA_TO_DEVICE); + else + dma_unmap_page(dev, pa, d->dma.length, DMA_TO_DEVICE); + } + + return -EINVAL; +} + + +netdev_tx_t wil_start_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + struct wil6210_priv *wil = ndev_to_wil(ndev); + struct vring *vring; + int rc; + + wil_dbg_txrx(wil, "%s()\n", __func__); + if (!test_bit(wil_status_fwready, &wil->status)) { + wil_err(wil, "FW not ready\n"); + goto drop; + } + if (!test_bit(wil_status_fwconnected, &wil->status)) { + wil_err(wil, "FW not connected\n"); + goto drop; + } + if (wil->wdev->iftype == NL80211_IFTYPE_MONITOR) { + wil_err(wil, "Xmit in monitor mode not supported\n"); + goto drop; + } + if (skb->protocol == cpu_to_be16(ETH_P_PAE)) { + rc = wmi_tx_eapol(wil, skb); + } else { + /* find vring */ + vring = wil_find_tx_vring(wil, skb); + if (!vring) { + wil_err(wil, "No Tx VRING available\n"); + goto drop; + } + /* set up vring entry */ + rc = wil_tx_vring(wil, vring, skb); + } + switch (rc) { + case 0: + /* statistics will be updated on the tx_complete */ + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + case -ENOMEM: + return NETDEV_TX_BUSY; + default: + break; /* goto drop; */ + } + drop: + netif_tx_stop_all_queues(ndev); + ndev->stats.tx_dropped++; + dev_kfree_skb_any(skb); + + return NET_XMIT_DROP; +} + +/** + * Clean up transmitted skb's from the Tx VRING + * + * Safe to call from IRQ + */ +void wil_tx_complete(struct wil6210_priv *wil, int ringid) +{ + struct net_device *ndev = wil_to_ndev(wil); + struct device *dev = wil_to_dev(wil); + struct vring *vring = &wil->vring_tx[ringid]; + + if (!vring->va) { + wil_err(wil, "Tx irq[%d]: vring not initialized\n", ringid); + return; + } + + wil_dbg_txrx(wil, "%s(%d)\n", __func__, ringid); + + while (!wil_vring_is_empty(vring)) { + volatile struct vring_tx_desc *d1 = + &vring->va[vring->swtail].tx; + struct vring_tx_desc dd, *d = ⅆ + dma_addr_t pa; + struct sk_buff *skb; + + dd = *d1; + + if (!(d->dma.status & TX_DMA_STATUS_DU)) + break; + + wil_dbg_txrx(wil, + "Tx[%3d] : %d bytes, status 0x%02x err 0x%02x\n", + vring->swtail, d->dma.length, d->dma.status, + d->dma.error); + wil_hex_dump_txrx("TxC ", DUMP_PREFIX_NONE, 32, 4, + (const void *)d, sizeof(*d), false); + + pa = d->dma.addr_low | ((u64)d->dma.addr_high << 32); + skb = vring->ctx[vring->swtail]; + if (skb) { + if (d->dma.error == 0) { + ndev->stats.tx_packets++; + ndev->stats.tx_bytes += skb->len; + } else { + ndev->stats.tx_errors++; + } + + dma_unmap_single(dev, pa, d->dma.length, DMA_TO_DEVICE); + dev_kfree_skb_any(skb); + vring->ctx[vring->swtail] = NULL; + } else { + dma_unmap_page(dev, pa, d->dma.length, DMA_TO_DEVICE); + } + d->dma.addr_low = 0; + d->dma.addr_high = 0; + d->dma.length = 0; + d->dma.status = TX_DMA_STATUS_DU; + vring->swtail = wil_vring_next_tail(vring); + } + if (wil_vring_avail_tx(vring) > vring->size/4) + netif_tx_wake_all_queues(wil_to_ndev(wil)); +} diff --git a/drivers/net/wireless/ath/wil6210/txrx.h b/drivers/net/wireless/ath/wil6210/txrx.h new file mode 100644 index 0000000..adef12f --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/txrx.h @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef WIL6210_TXRX_H +#define WIL6210_TXRX_H + +#define BUF_SW_OWNED (1) +#define BUF_HW_OWNED (0) + +/* size of max. Rx packet */ +#define RX_BUF_LEN (2048) +#define TX_BUF_LEN (2048) +/* how many bytes to reserve for rtap header? */ +#define WIL6210_RTAP_SIZE (128) + +/* Tx/Rx path */ +/* + * Tx descriptor - MAC part + * [dword 0] + * bit 0.. 9 : lifetime_expiry_value:10 + * bit 10 : interrup_en:1 + * bit 11 : status_en:1 + * bit 12..13 : txss_override:2 + * bit 14 : timestamp_insertion:1 + * bit 15 : duration_preserve:1 + * bit 16..21 : reserved0:6 + * bit 22..26 : mcs_index:5 + * bit 27 : mcs_en:1 + * bit 28..29 : reserved1:2 + * bit 30 : reserved2:1 + * bit 31 : sn_preserved:1 + * [dword 1] + * bit 0.. 3 : pkt_mode:4 + * bit 4 : pkt_mode_en:1 + * bit 5.. 7 : reserved0:3 + * bit 8..13 : reserved1:6 + * bit 14 : reserved2:1 + * bit 15 : ack_policy_en:1 + * bit 16..19 : dst_index:4 + * bit 20 : dst_index_en:1 + * bit 21..22 : ack_policy:2 + * bit 23 : lifetime_en:1 + * bit 24..30 : max_retry:7 + * bit 31 : max_retry_en:1 + * [dword 2] + * bit 0.. 7 : num_of_descriptors:8 + * bit 8..17 : reserved:10 + * bit 18..19 : l2_translation_type:2 + * bit 20 : snap_hdr_insertion_en:1 + * bit 21 : vlan_removal_en:1 + * bit 22..31 : reserved0:10 + * [dword 3] + * bit 0.. 31: ucode_cmd:32 + */ +struct vring_tx_mac { + u32 d[3]; + u32 ucode_cmd; +} __packed; + +/* TX MAC Dword 0 */ +#define MAC_CFG_DESC_TX_0_LIFETIME_EXPIRY_VALUE_POS 0 +#define MAC_CFG_DESC_TX_0_LIFETIME_EXPIRY_VALUE_LEN 10 +#define MAC_CFG_DESC_TX_0_LIFETIME_EXPIRY_VALUE_MSK 0x3FF + +#define MAC_CFG_DESC_TX_0_INTERRUP_EN_POS 10 +#define MAC_CFG_DESC_TX_0_INTERRUP_EN_LEN 1 +#define MAC_CFG_DESC_TX_0_INTERRUP_EN_MSK 0x400 + +#define MAC_CFG_DESC_TX_0_STATUS_EN_POS 11 +#define MAC_CFG_DESC_TX_0_STATUS_EN_LEN 1 +#define MAC_CFG_DESC_TX_0_STATUS_EN_MSK 0x800 + +#define MAC_CFG_DESC_TX_0_TXSS_OVERRIDE_POS 12 +#define MAC_CFG_DESC_TX_0_TXSS_OVERRIDE_LEN 2 +#define MAC_CFG_DESC_TX_0_TXSS_OVERRIDE_MSK 0x3000 + +#define MAC_CFG_DESC_TX_0_TIMESTAMP_INSERTION_POS 14 +#define MAC_CFG_DESC_TX_0_TIMESTAMP_INSERTION_LEN 1 +#define MAC_CFG_DESC_TX_0_TIMESTAMP_INSERTION_MSK 0x4000 + +#define MAC_CFG_DESC_TX_0_DURATION_PRESERVE_POS 15 +#define MAC_CFG_DESC_TX_0_DURATION_PRESERVE_LEN 1 +#define MAC_CFG_DESC_TX_0_DURATION_PRESERVE_MSK 0x8000 + +#define MAC_CFG_DESC_TX_0_MCS_INDEX_POS 22 +#define MAC_CFG_DESC_TX_0_MCS_INDEX_LEN 5 +#define MAC_CFG_DESC_TX_0_MCS_INDEX_MSK 0x7C00000 + +#define MAC_CFG_DESC_TX_0_MCS_EN_POS 27 +#define MAC_CFG_DESC_TX_0_MCS_EN_LEN 1 +#define MAC_CFG_DESC_TX_0_MCS_EN_MSK 0x8000000 + +#define MAC_CFG_DESC_TX_0_SN_PRESERVED_POS 31 +#define MAC_CFG_DESC_TX_0_SN_PRESERVED_LEN 1 +#define MAC_CFG_DESC_TX_0_SN_PRESERVED_MSK 0x80000000 + +/* TX MAC Dword 1 */ +#define MAC_CFG_DESC_TX_1_PKT_MODE_POS 0 +#define MAC_CFG_DESC_TX_1_PKT_MODE_LEN 4 +#define MAC_CFG_DESC_TX_1_PKT_MODE_MSK 0xF + +#define MAC_CFG_DESC_TX_1_PKT_MODE_EN_POS 4 +#define MAC_CFG_DESC_TX_1_PKT_MODE_EN_LEN 1 +#define MAC_CFG_DESC_TX_1_PKT_MODE_EN_MSK 0x10 + +#define MAC_CFG_DESC_TX_1_ACK_POLICY_EN_POS 15 +#define MAC_CFG_DESC_TX_1_ACK_POLICY_EN_LEN 1 +#define MAC_CFG_DESC_TX_1_ACK_POLICY_EN_MSK 0x8000 + +#define MAC_CFG_DESC_TX_1_DST_INDEX_POS 16 +#define MAC_CFG_DESC_TX_1_DST_INDEX_LEN 4 +#define MAC_CFG_DESC_TX_1_DST_INDEX_MSK 0xF0000 + +#define MAC_CFG_DESC_TX_1_DST_INDEX_EN_POS 20 +#define MAC_CFG_DESC_TX_1_DST_INDEX_EN_LEN 1 +#define MAC_CFG_DESC_TX_1_DST_INDEX_EN_MSK 0x100000 + +#define MAC_CFG_DESC_TX_1_ACK_POLICY_POS 21 +#define MAC_CFG_DESC_TX_1_ACK_POLICY_LEN 2 +#define MAC_CFG_DESC_TX_1_ACK_POLICY_MSK 0x600000 + +#define MAC_CFG_DESC_TX_1_LIFETIME_EN_POS 23 +#define MAC_CFG_DESC_TX_1_LIFETIME_EN_LEN 1 +#define MAC_CFG_DESC_TX_1_LIFETIME_EN_MSK 0x800000 + +#define MAC_CFG_DESC_TX_1_MAX_RETRY_POS 24 +#define MAC_CFG_DESC_TX_1_MAX_RETRY_LEN 7 +#define MAC_CFG_DESC_TX_1_MAX_RETRY_MSK 0x7F000000 + +#define MAC_CFG_DESC_TX_1_MAX_RETRY_EN_POS 31 +#define MAC_CFG_DESC_TX_1_MAX_RETRY_EN_LEN 1 +#define MAC_CFG_DESC_TX_1_MAX_RETRY_EN_MSK 0x80000000 + +/* TX MAC Dword 2 */ +#define MAC_CFG_DESC_TX_2_NUM_OF_DESCRIPTORS_POS 0 +#define MAC_CFG_DESC_TX_2_NUM_OF_DESCRIPTORS_LEN 8 +#define MAC_CFG_DESC_TX_2_NUM_OF_DESCRIPTORS_MSK 0xFF + +#define MAC_CFG_DESC_TX_2_RESERVED_POS 8 +#define MAC_CFG_DESC_TX_2_RESERVED_LEN 10 +#define MAC_CFG_DESC_TX_2_RESERVED_MSK 0x3FF00 + +#define MAC_CFG_DESC_TX_2_L2_TRANSLATION_TYPE_POS 18 +#define MAC_CFG_DESC_TX_2_L2_TRANSLATION_TYPE_LEN 2 +#define MAC_CFG_DESC_TX_2_L2_TRANSLATION_TYPE_MSK 0xC0000 + +#define MAC_CFG_DESC_TX_2_SNAP_HDR_INSERTION_EN_POS 20 +#define MAC_CFG_DESC_TX_2_SNAP_HDR_INSERTION_EN_LEN 1 +#define MAC_CFG_DESC_TX_2_SNAP_HDR_INSERTION_EN_MSK 0x100000 + +#define MAC_CFG_DESC_TX_2_VLAN_REMOVAL_EN_POS 21 +#define MAC_CFG_DESC_TX_2_VLAN_REMOVAL_EN_LEN 1 +#define MAC_CFG_DESC_TX_2_VLAN_REMOVAL_EN_MSK 0x200000 + +/* TX MAC Dword 3 */ +#define MAC_CFG_DESC_TX_3_UCODE_CMD_POS 0 +#define MAC_CFG_DESC_TX_3_UCODE_CMD_LEN 32 +#define MAC_CFG_DESC_TX_3_UCODE_CMD_MSK 0xFFFFFFFF + +/* TX DMA Dword 0 */ +#define DMA_CFG_DESC_TX_0_L4_LENGTH_POS 0 +#define DMA_CFG_DESC_TX_0_L4_LENGTH_LEN 8 +#define DMA_CFG_DESC_TX_0_L4_LENGTH_MSK 0xFF + +#define DMA_CFG_DESC_TX_0_CMD_EOP_POS 8 +#define DMA_CFG_DESC_TX_0_CMD_EOP_LEN 1 +#define DMA_CFG_DESC_TX_0_CMD_EOP_MSK 0x100 + +#define DMA_CFG_DESC_TX_0_CMD_DMA_IT_POS 10 +#define DMA_CFG_DESC_TX_0_CMD_DMA_IT_LEN 1 +#define DMA_CFG_DESC_TX_0_CMD_DMA_IT_MSK 0x400 + +#define DMA_CFG_DESC_TX_0_SEGMENT_BUF_DETAILS_POS 11 +#define DMA_CFG_DESC_TX_0_SEGMENT_BUF_DETAILS_LEN 2 +#define DMA_CFG_DESC_TX_0_SEGMENT_BUF_DETAILS_MSK 0x1800 + +#define DMA_CFG_DESC_TX_0_TCP_SEG_EN_POS 13 +#define DMA_CFG_DESC_TX_0_TCP_SEG_EN_LEN 1 +#define DMA_CFG_DESC_TX_0_TCP_SEG_EN_MSK 0x2000 + +#define DMA_CFG_DESC_TX_0_IPV4_CHECKSUM_EN_POS 14 +#define DMA_CFG_DESC_TX_0_IPV4_CHECKSUM_EN_LEN 1 +#define DMA_CFG_DESC_TX_0_IPV4_CHECKSUM_EN_MSK 0x4000 + +#define DMA_CFG_DESC_TX_0_TCP_UDP_CHECKSUM_EN_POS 15 +#define DMA_CFG_DESC_TX_0_TCP_UDP_CHECKSUM_EN_LEN 1 +#define DMA_CFG_DESC_TX_0_TCP_UDP_CHECKSUM_EN_MSK 0x8000 + +#define DMA_CFG_DESC_TX_0_QID_POS 16 +#define DMA_CFG_DESC_TX_0_QID_LEN 5 +#define DMA_CFG_DESC_TX_0_QID_MSK 0x1F0000 + +#define DMA_CFG_DESC_TX_0_PSEUDO_HEADER_CALC_EN_POS 21 +#define DMA_CFG_DESC_TX_0_PSEUDO_HEADER_CALC_EN_LEN 1 +#define DMA_CFG_DESC_TX_0_PSEUDO_HEADER_CALC_EN_MSK 0x200000 + +#define DMA_CFG_DESC_TX_0_L4_TYPE_POS 30 +#define DMA_CFG_DESC_TX_0_L4_TYPE_LEN 2 +#define DMA_CFG_DESC_TX_0_L4_TYPE_MSK 0xC0000000 + + +#define TX_DMA_STATUS_DU BIT(0) + +struct vring_tx_dma { + u32 d0; + u32 addr_low; + u16 addr_high; + u8 ip_length; + u8 b11; /* 0..6: mac_length; 7:ip_version */ + u8 error; /* 0..2: err; 3..7: reserved; */ + u8 status; /* 0: used; 1..7; reserved */ + u16 length; +} __packed; + +/* + * Rx descriptor - MAC part + * [dword 0] + * bit 0.. 3 : tid:4 The QoS (b3-0) TID Field + * bit 4.. 6 : connection_id:3 :The Source index that was found during + * Parsing the TA. This field is used to define the source of the packet + * bit 7 : reserved:1 + * bit 8.. 9 : mac_id:2 : The MAC virtual Ring number (always zero) + * bit 10..11 : frame_type:2 : The FC Control (b3-2) - MPDU Type + * (management, data, control and extension) + * bit 12..15 : frame_subtype:4 : The FC Control (b7-4) - Frame Subtype + * bit 16..27 : seq_number:12 The received Sequence number field + * bit 28..31 : extended:4 extended subtype + * [dword 1] + * bit 0.. 3 : reserved + * bit 4.. 5 : key_id:2 + * bit 6 : decrypt_bypass:1 + * bit 7 : security:1 + * bit 8.. 9 : ds_bits:2 + * bit 10 : a_msdu_present:1 from qos header + * bit 11 : a_msdu_type:1 from qos header + * bit 12 : a_mpdu:1 part of AMPDU aggregation + * bit 13 : broadcast:1 + * bit 14 : mutlicast:1 + * bit 15 : reserved:1 + * bit 16..20 : rx_mac_qid:5 The Queue Identifier that the packet + * is received from + * bit 21..24 : mcs:4 + * bit 25..28 : mic_icr:4 + * bit 29..31 : reserved:3 + * [dword 2] + * bit 0.. 2 : time_slot:3 The timeslot that the MPDU is received + * bit 3 : fc_protocol_ver:1 The FC Control (b0) - Protocol Version + * bit 4 : fc_order:1 The FC Control (b15) -Order + * bit 5.. 7 : qos_ack_policy:3 The QoS (b6-5) ack policy Field + * bit 8 : esop:1 The QoS (b4) ESOP field + * bit 9 : qos_rdg_more_ppdu:1 The QoS (b9) RDG field + * bit 10..14 : qos_reserved:5 The QoS (b14-10) Reserved field + * bit 15 : qos_ac_constraint:1 + * bit 16..31 : pn_15_0:16 low 2 bytes of PN + * [dword 3] + * bit 0..31 : pn_47_16:32 high 4 bytes of PN + */ +struct vring_rx_mac { + u32 d0; + u32 d1; + u16 w4; + u16 pn_15_0; + u32 pn_47_16; +} __packed; + +/* + * Rx descriptor - DMA part + * [dword 0] + * bit 0.. 7 : l4_length:8 layer 4 length + * bit 8.. 9 : reserved:2 + * bit 10 : cmd_dma_it:1 + * bit 11..15 : reserved:5 + * bit 16..29 : phy_info_length:14 + * bit 30..31 : l4_type:2 valid if the L4I bit is set in the status field + * [dword 1] + * bit 0..31 : addr_low:32 The payload buffer low address + * [dword 2] + * bit 0..15 : addr_high:16 The payload buffer high address + * bit 16..23 : ip_length:8 + * bit 24..30 : mac_length:7 + * bit 31 : ip_version:1 + * [dword 3] + * [byte 12] error + * [byte 13] status + * bit 0 : du:1 + * bit 1 : eop:1 + * bit 2 : error:1 + * bit 3 : mi:1 + * bit 4 : l3_identified:1 + * bit 5 : l4_identified:1 + * bit 6 : phy_info_included:1 + * bit 7 : reserved:1 + * [word 7] length + * + */ + +#define RX_DMA_D0_CMD_DMA_IT BIT(10) + +#define RX_DMA_STATUS_DU BIT(0) +#define RX_DMA_STATUS_ERROR BIT(2) +#define RX_DMA_STATUS_PHY_INFO BIT(6) + +struct vring_rx_dma { + u32 d0; + u32 addr_low; + u16 addr_high; + u8 ip_length; + u8 b11; + u8 error; + u8 status; + u16 length; +} __packed; + +struct vring_tx_desc { + struct vring_tx_mac mac; + struct vring_tx_dma dma; +} __packed; + +struct vring_rx_desc { + struct vring_rx_mac mac; + struct vring_rx_dma dma; +} __packed; + +union vring_desc { + struct vring_tx_desc tx; + struct vring_rx_desc rx; +} __packed; + +static inline int wil_rxdesc_tid(struct vring_rx_desc *d) +{ + return WIL_GET_BITS(d->mac.d0, 0, 3); +} + +static inline int wil_rxdesc_cid(struct vring_rx_desc *d) +{ + return WIL_GET_BITS(d->mac.d0, 4, 6); +} + +static inline int wil_rxdesc_mid(struct vring_rx_desc *d) +{ + return WIL_GET_BITS(d->mac.d0, 8, 9); +} + +static inline int wil_rxdesc_ftype(struct vring_rx_desc *d) +{ + return WIL_GET_BITS(d->mac.d0, 10, 11); +} + +static inline int wil_rxdesc_subtype(struct vring_rx_desc *d) +{ + return WIL_GET_BITS(d->mac.d0, 12, 15); +} + +static inline int wil_rxdesc_seq(struct vring_rx_desc *d) +{ + return WIL_GET_BITS(d->mac.d0, 16, 27); +} + +static inline int wil_rxdesc_ext_subtype(struct vring_rx_desc *d) +{ + return WIL_GET_BITS(d->mac.d0, 28, 31); +} + +static inline int wil_rxdesc_ds_bits(struct vring_rx_desc *d) +{ + return WIL_GET_BITS(d->mac.d1, 8, 9); +} + +static inline int wil_rxdesc_mcs(struct vring_rx_desc *d) +{ + return WIL_GET_BITS(d->mac.d1, 21, 24); +} + +static inline int wil_rxdesc_phy_length(struct vring_rx_desc *d) +{ + return WIL_GET_BITS(d->dma.d0, 16, 29); +} + +static inline struct vring_rx_desc *wil_skb_rxdesc(struct sk_buff *skb) +{ + return (void *)skb->cb; +} + +#endif /* WIL6210_TXRX_H */ diff --git a/drivers/net/wireless/ath/wil6210/wil6210.h b/drivers/net/wireless/ath/wil6210/wil6210.h new file mode 100644 index 0000000..8f76ecd --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/wil6210.h @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __WIL6210_H__ +#define __WIL6210_H__ + +#include +#include +#include + +#define WIL_NAME "wil6210" + +/** + * extract bits [@b0:@b1] (inclusive) from the value @x + * it should be @b0 <= @b1, or result is incorrect + */ +static inline u32 WIL_GET_BITS(u32 x, int b0, int b1) +{ + return (x >> b0) & ((1 << (b1 - b0 + 1)) - 1); +} + +#define WIL6210_MEM_SIZE (2*1024*1024UL) + +#define WIL6210_RX_RING_SIZE (128) +#define WIL6210_TX_RING_SIZE (128) +#define WIL6210_MAX_TX_RINGS (24) + +/* Hardware definitions begin */ + +/* + * Mapping + * RGF File | Host addr | FW addr + * | | + * user_rgf | 0x000000 | 0x880000 + * dma_rgf | 0x001000 | 0x881000 + * pcie_rgf | 0x002000 | 0x882000 + * | | + */ + +/* Where various structures placed in host address space */ +#define WIL6210_FW_HOST_OFF (0x880000UL) + +#define HOSTADDR(fwaddr) (fwaddr - WIL6210_FW_HOST_OFF) + +/* + * Interrupt control registers block + * + * each interrupt controlled by the same bit in all registers + */ +struct RGF_ICR { + u32 ICC; /* Cause Control, RW: 0 - W1C, 1 - COR */ + u32 ICR; /* Cause, W1C/COR depending on ICC */ + u32 ICM; /* Cause masked (ICR & ~IMV), W1C/COR depending on ICC */ + u32 ICS; /* Cause Set, WO */ + u32 IMV; /* Mask, RW+S/C */ + u32 IMS; /* Mask Set, write 1 to set */ + u32 IMC; /* Mask Clear, write 1 to clear */ +} __packed; + +/* registers - FW addresses */ +#define RGF_USER_USER_SCRATCH_PAD (0x8802bc) +#define RGF_USER_USER_ICR (0x880b4c) /* struct RGF_ICR */ + #define BIT_USER_USER_ICR_SW_INT_2 BIT(18) +#define RGF_USER_CLKS_CTL_SW_RST_MASK_0 (0x880b14) +#define RGF_USER_MAC_CPU_0 (0x8801fc) +#define RGF_USER_USER_CPU_0 (0x8801e0) +#define RGF_USER_CLKS_CTL_SW_RST_VEC_0 (0x880b04) +#define RGF_USER_CLKS_CTL_SW_RST_VEC_1 (0x880b08) +#define RGF_USER_CLKS_CTL_SW_RST_VEC_2 (0x880b0c) +#define RGF_USER_CLKS_CTL_SW_RST_VEC_3 (0x880b10) + +#define RGF_DMA_PSEUDO_CAUSE (0x881c68) +#define RGF_DMA_PSEUDO_CAUSE_MASK_SW (0x881c6c) +#define RGF_DMA_PSEUDO_CAUSE_MASK_FW (0x881c70) + #define BIT_DMA_PSEUDO_CAUSE_RX BIT(0) + #define BIT_DMA_PSEUDO_CAUSE_TX BIT(1) + #define BIT_DMA_PSEUDO_CAUSE_MISC BIT(2) + +#define RGF_DMA_EP_TX_ICR (0x881bb4) /* struct RGF_ICR */ + #define BIT_DMA_EP_TX_ICR_TX_DONE BIT(0) + #define BIT_DMA_EP_TX_ICR_TX_DONE_N(n) BIT(n+1) /* n = [0..23] */ +#define RGF_DMA_EP_RX_ICR (0x881bd0) /* struct RGF_ICR */ + #define BIT_DMA_EP_RX_ICR_RX_DONE BIT(0) +#define RGF_DMA_EP_MISC_ICR (0x881bec) /* struct RGF_ICR */ + #define BIT_DMA_EP_MISC_ICR_RX_HTRSH BIT(0) + #define BIT_DMA_EP_MISC_ICR_TX_NO_ACT BIT(1) + #define BIT_DMA_EP_MISC_ICR_FW_INT(n) BIT(28+n) /* n = [0..3] */ + +/* Interrupt moderation control */ +#define RGF_DMA_ITR_CNT_TRSH (0x881c5c) +#define RGF_DMA_ITR_CNT_DATA (0x881c60) +#define RGF_DMA_ITR_CNT_CRL (0x881C64) + #define BIT_DMA_ITR_CNT_CRL_EN BIT(0) + #define BIT_DMA_ITR_CNT_CRL_EXT_TICK BIT(1) + #define BIT_DMA_ITR_CNT_CRL_FOREVER BIT(2) + #define BIT_DMA_ITR_CNT_CRL_CLR BIT(3) + #define BIT_DMA_ITR_CNT_CRL_REACH_TRSH BIT(4) + +/* popular locations */ +#define HOST_MBOX HOSTADDR(RGF_USER_USER_SCRATCH_PAD) +#define HOST_SW_INT (HOSTADDR(RGF_USER_USER_ICR) + \ + offsetof(struct RGF_ICR, ICS)) +#define SW_INT_MBOX BIT_USER_USER_ICR_SW_INT_2 + +/* ISR register bits */ +#define ISR_MISC_FW_READY BIT_DMA_EP_MISC_ICR_FW_INT(0) +#define ISR_MISC_MBOX_EVT BIT_DMA_EP_MISC_ICR_FW_INT(1) +#define ISR_MISC_FW_ERROR BIT_DMA_EP_MISC_ICR_FW_INT(3) + +/* Hardware definitions end */ + +struct wil6210_mbox_ring { + u32 base; + u16 entry_size; /* max. size of mbox entry, incl. all headers */ + u16 size; + u32 tail; + u32 head; +} __packed; + +struct wil6210_mbox_ring_desc { + __le32 sync; + __le32 addr; +} __packed; + +/* at HOST_OFF_WIL6210_MBOX_CTL */ +struct wil6210_mbox_ctl { + struct wil6210_mbox_ring tx; + struct wil6210_mbox_ring rx; +} __packed; + +struct wil6210_mbox_hdr { + __le16 seq; + __le16 len; /* payload, bytes after this header */ + __le16 type; + u8 flags; + u8 reserved; +} __packed; + +#define WIL_MBOX_HDR_TYPE_WMI (0) + +/* max. value for wil6210_mbox_hdr.len */ +#define MAX_MBOXITEM_SIZE (240) + +struct wil6210_mbox_hdr_wmi { + u8 reserved0[2]; + __le16 id; + __le16 info1; /* bits [0..3] - device_id, rest - unused */ + u8 reserved1[2]; +} __packed; + +struct pending_wmi_event { + struct list_head list; + struct { + struct wil6210_mbox_hdr hdr; + struct wil6210_mbox_hdr_wmi wmi; + u8 data[0]; + } __packed event; +}; + +union vring_desc; + +struct vring { + dma_addr_t pa; + volatile union vring_desc *va; /* vring_desc[size], WriteBack by DMA */ + u16 size; /* number of vring_desc elements */ + u32 swtail; + u32 swhead; + u32 hwtail; /* write here to inform hw */ + void **ctx; /* void *ctx[size] - software context */ +}; + +enum { /* for wil6210_priv.status */ + wil_status_fwready = 0, + wil_status_fwconnected, + wil_status_dontscan, + wil_status_reset_done, + wil_status_irqen, /* FIXME: interrupts enabled - for debug */ +}; + +struct pci_dev; + +struct wil6210_stats { + u64 tsf; + u32 snr; + u16 last_mcs_rx; + u16 bf_mcs; /* last BF, used for Tx */ + u16 my_rx_sector; + u16 my_tx_sector; + u16 peer_rx_sector; + u16 peer_tx_sector; +}; + +struct wil6210_priv { + struct pci_dev *pdev; + int n_msi; + struct wireless_dev *wdev; + void __iomem *csr; + ulong status; + u32 fw_version; + u8 n_mids; /* number of additional MIDs as reported by FW */ + /* profile */ + u32 monitor_flags; + u32 secure_pcp; /* create secure PCP? */ + int sinfo_gen; + /* cached ISR registers */ + u32 isr_misc; + /* mailbox related */ + struct mutex wmi_mutex; + struct wil6210_mbox_ctl mbox_ctl; + struct completion wmi_ready; + u16 wmi_seq; + u16 reply_id; /**< wait for this WMI event */ + void *reply_buf; + u16 reply_size; + struct workqueue_struct *wmi_wq; /* for deferred calls */ + struct work_struct wmi_event_worker; + struct workqueue_struct *wmi_wq_conn; /* for connect worker */ + struct work_struct connect_worker; + struct work_struct disconnect_worker; + struct timer_list connect_timer; + int pending_connect_cid; + struct list_head pending_wmi_ev; + /* + * protect pending_wmi_ev + * - fill in IRQ from wil6210_irq_misc, + * - consumed in thread by wmi_event_worker + */ + spinlock_t wmi_ev_lock; + /* DMA related */ + struct vring vring_rx; + struct vring vring_tx[WIL6210_MAX_TX_RINGS]; + u8 dst_addr[WIL6210_MAX_TX_RINGS][ETH_ALEN]; + /* scan */ + struct cfg80211_scan_request *scan_request; + + struct mutex mutex; /* for wil6210_priv access in wil_{up|down} */ + /* statistics */ + struct wil6210_stats stats; + /* debugfs */ + struct dentry *debug; + struct debugfs_blob_wrapper fw_code_blob; + struct debugfs_blob_wrapper fw_data_blob; + struct debugfs_blob_wrapper fw_peri_blob; + struct debugfs_blob_wrapper uc_code_blob; + struct debugfs_blob_wrapper uc_data_blob; + struct debugfs_blob_wrapper rgf_blob; +}; + +#define wil_to_wiphy(i) (i->wdev->wiphy) +#define wil_to_dev(i) (wiphy_dev(wil_to_wiphy(i))) +#define wiphy_to_wil(w) (struct wil6210_priv *)(wiphy_priv(w)) +#define wil_to_wdev(i) (i->wdev) +#define wdev_to_wil(w) (struct wil6210_priv *)(wdev_priv(w)) +#define wil_to_ndev(i) (wil_to_wdev(i)->netdev) +#define ndev_to_wil(n) (wdev_to_wil(n->ieee80211_ptr)) + +#define wil_dbg(wil, fmt, arg...) netdev_dbg(wil_to_ndev(wil), fmt, ##arg) +#define wil_info(wil, fmt, arg...) netdev_info(wil_to_ndev(wil), fmt, ##arg) +#define wil_err(wil, fmt, arg...) netdev_err(wil_to_ndev(wil), fmt, ##arg) + +#define wil_dbg_irq(wil, fmt, arg...) wil_dbg(wil, "DBG[ IRQ]" fmt, ##arg) +#define wil_dbg_txrx(wil, fmt, arg...) wil_dbg(wil, "DBG[TXRX]" fmt, ##arg) +#define wil_dbg_wmi(wil, fmt, arg...) wil_dbg(wil, "DBG[ WMI]" fmt, ##arg) +#define wil_dbg_misc(wil, fmt, arg...) wil_dbg(wil, "DBG[MISC]" fmt, ##arg) + +#define wil_hex_dump_txrx(prefix_str, prefix_type, rowsize, \ + groupsize, buf, len, ascii) \ + print_hex_dump_debug("DBG[TXRX]" prefix_str,\ + prefix_type, rowsize, \ + groupsize, buf, len, ascii) + +#define wil_hex_dump_wmi(prefix_str, prefix_type, rowsize, \ + groupsize, buf, len, ascii) \ + print_hex_dump_debug("DBG[ WMI]" prefix_str,\ + prefix_type, rowsize, \ + groupsize, buf, len, ascii) + +void wil_memcpy_fromio_32(void *dst, const volatile void __iomem *src, + size_t count); +void wil_memcpy_toio_32(volatile void __iomem *dst, const void *src, + size_t count); + +void *wil_if_alloc(struct device *dev, void __iomem *csr); +void wil_if_free(struct wil6210_priv *wil); +int wil_if_add(struct wil6210_priv *wil); +void wil_if_remove(struct wil6210_priv *wil); +int wil_priv_init(struct wil6210_priv *wil); +void wil_priv_deinit(struct wil6210_priv *wil); +int wil_reset(struct wil6210_priv *wil); +void wil_link_on(struct wil6210_priv *wil); +void wil_link_off(struct wil6210_priv *wil); +int wil_up(struct wil6210_priv *wil); +int wil_down(struct wil6210_priv *wil); +void wil_mbox_ring_le2cpus(struct wil6210_mbox_ring *r); + +void __iomem *wmi_buffer(struct wil6210_priv *wil, __le32 ptr); +void __iomem *wmi_addr(struct wil6210_priv *wil, u32 ptr); +int wmi_read_hdr(struct wil6210_priv *wil, __le32 ptr, + struct wil6210_mbox_hdr *hdr); +int wmi_send(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len); +void wmi_recv_cmd(struct wil6210_priv *wil); +int wmi_call(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len, + u16 reply_id, void *reply, u8 reply_size, int to_msec); +void wmi_event_worker(struct work_struct *work); +void wmi_event_flush(struct wil6210_priv *wil); +int wmi_set_ssid(struct wil6210_priv *wil, u8 ssid_len, const void *ssid); +int wmi_get_ssid(struct wil6210_priv *wil, u8 *ssid_len, void *ssid); +int wmi_set_channel(struct wil6210_priv *wil, int channel); +int wmi_get_channel(struct wil6210_priv *wil, int *channel); +int wmi_tx_eapol(struct wil6210_priv *wil, struct sk_buff *skb); +int wmi_del_cipher_key(struct wil6210_priv *wil, u8 key_index, + const void *mac_addr); +int wmi_add_cipher_key(struct wil6210_priv *wil, u8 key_index, + const void *mac_addr, int key_len, const void *key); +int wmi_echo(struct wil6210_priv *wil); +int wmi_set_ie(struct wil6210_priv *wil, u8 type, u16 ie_len, const void *ie); +int wmi_rx_chain_add(struct wil6210_priv *wil, struct vring *vring); +int wmi_p2p_cfg(struct wil6210_priv *wil, int channel); +int wmi_get_temperature(struct wil6210_priv *wil, u32 *t_m, u32 *t_r); + +int wil6210_init_irq(struct wil6210_priv *wil, int irq); +void wil6210_fini_irq(struct wil6210_priv *wil, int irq); +void wil6210_disable_irq(struct wil6210_priv *wil); +void wil6210_enable_irq(struct wil6210_priv *wil); + +int wil6210_debugfs_init(struct wil6210_priv *wil); +void wil6210_debugfs_remove(struct wil6210_priv *wil); + +struct wireless_dev *wil_cfg80211_init(struct device *dev); +void wil_wdev_free(struct wil6210_priv *wil); + +int wmi_set_mac_address(struct wil6210_priv *wil, void *addr); +int wmi_pcp_start(struct wil6210_priv *wil, int bi, u8 wmi_nettype, u8 chan); +int wmi_pcp_stop(struct wil6210_priv *wil); +void wil6210_disconnect(struct wil6210_priv *wil, void *bssid); + +int wil_rx_init(struct wil6210_priv *wil); +void wil_rx_fini(struct wil6210_priv *wil); + +/* TX API */ +int wil_vring_init_tx(struct wil6210_priv *wil, int id, int size, + int cid, int tid); +void wil_vring_fini_tx(struct wil6210_priv *wil, int id); + +netdev_tx_t wil_start_xmit(struct sk_buff *skb, struct net_device *ndev); +void wil_tx_complete(struct wil6210_priv *wil, int ringid); + +/* RX API */ +void wil_rx_handle(struct wil6210_priv *wil); + +int wil_iftype_nl2wmi(enum nl80211_iftype type); + +#endif /* __WIL6210_H__ */ diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c new file mode 100644 index 0000000..45b04e3 --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/wmi.c @@ -0,0 +1,1088 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "wil6210.h" +#include "txrx.h" +#include "wmi.h" + +/** + * WMI event receiving - theory of operations + * + * When firmware about to report WMI event, it fills memory area + * in the mailbox and raises misc. IRQ. Thread interrupt handler invoked for + * the misc IRQ, function @wmi_recv_cmd called by thread IRQ handler. + * + * @wmi_recv_cmd reads event, allocates memory chunk and attaches it to the + * event list @wil->pending_wmi_ev. Then, work queue @wil->wmi_wq wakes up + * and handles events within the @wmi_event_worker. Every event get detached + * from list, processed and deleted. + * + * Purpose for this mechanism is to release IRQ thread; otherwise, + * if WMI event handling involves another WMI command flow, this 2-nd flow + * won't be completed because of blocked IRQ thread. + */ + +/** + * Addressing - theory of operations + * + * There are several buses present on the WIL6210 card. + * Same memory areas are visible at different address on + * the different busses. There are 3 main bus masters: + * - MAC CPU (ucode) + * - User CPU (firmware) + * - AHB (host) + * + * On the PCI bus, there is one BAR (BAR0) of 2Mb size, exposing + * AHB addresses starting from 0x880000 + * + * Internally, firmware uses addresses that allows faster access but + * are invisible from the host. To read from these addresses, alternative + * AHB address must be used. + * + * Memory mapping + * Linker address PCI/Host address + * 0x880000 .. 0xa80000 2Mb BAR0 + * 0x800000 .. 0x807000 0x900000 .. 0x907000 28k DCCM + * 0x840000 .. 0x857000 0x908000 .. 0x91f000 92k PERIPH + */ + +/** + * @fw_mapping provides memory remapping table + */ +static const struct { + u32 from; /* linker address - from, inclusive */ + u32 to; /* linker address - to, exclusive */ + u32 host; /* PCI/Host address - BAR0 + 0x880000 */ +} fw_mapping[] = { + {0x000000, 0x040000, 0x8c0000}, /* FW code RAM 256k */ + {0x800000, 0x808000, 0x900000}, /* FW data RAM 32k */ + {0x840000, 0x860000, 0x908000}, /* peripheral data RAM 128k/96k used */ + {0x880000, 0x88a000, 0x880000}, /* various RGF */ + {0x8c0000, 0x932000, 0x8c0000}, /* trivial mapping for upper area */ + /* + * 920000..930000 ucode code RAM + * 930000..932000 ucode data RAM + */ +}; + +/** + * return AHB address for given firmware/ucode internal (linker) address + * @x - internal address + * If address have no valid AHB mapping, return 0 + */ +static u32 wmi_addr_remap(u32 x) +{ + uint i; + + for (i = 0; i < ARRAY_SIZE(fw_mapping); i++) { + if ((x >= fw_mapping[i].from) && (x < fw_mapping[i].to)) + return x + fw_mapping[i].host - fw_mapping[i].from; + } + + return 0; +} + +/** + * Check address validity for WMI buffer; remap if needed + * @ptr - internal (linker) fw/ucode address + * + * Valid buffer should be DWORD aligned + * + * return address for accessing buffer from the host; + * if buffer is not valid, return NULL. + */ +void __iomem *wmi_buffer(struct wil6210_priv *wil, __le32 ptr_) +{ + u32 off; + u32 ptr = le32_to_cpu(ptr_); + + if (ptr % 4) + return NULL; + + ptr = wmi_addr_remap(ptr); + if (ptr < WIL6210_FW_HOST_OFF) + return NULL; + + off = HOSTADDR(ptr); + if (off > WIL6210_MEM_SIZE - 4) + return NULL; + + return wil->csr + off; +} + +/** + * Check address validity + */ +void __iomem *wmi_addr(struct wil6210_priv *wil, u32 ptr) +{ + u32 off; + + if (ptr % 4) + return NULL; + + if (ptr < WIL6210_FW_HOST_OFF) + return NULL; + + off = HOSTADDR(ptr); + if (off > WIL6210_MEM_SIZE - 4) + return NULL; + + return wil->csr + off; +} + +int wmi_read_hdr(struct wil6210_priv *wil, __le32 ptr, + struct wil6210_mbox_hdr *hdr) +{ + void __iomem *src = wmi_buffer(wil, ptr); + if (!src) + return -EINVAL; + + wil_memcpy_fromio_32(hdr, src, sizeof(*hdr)); + + return 0; +} + +static int __wmi_send(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len) +{ + struct { + struct wil6210_mbox_hdr hdr; + struct wil6210_mbox_hdr_wmi wmi; + } __packed cmd = { + .hdr = { + .type = WIL_MBOX_HDR_TYPE_WMI, + .flags = 0, + .len = cpu_to_le16(sizeof(cmd.wmi) + len), + }, + .wmi = { + .id = cpu_to_le16(cmdid), + .info1 = 0, + }, + }; + struct wil6210_mbox_ring *r = &wil->mbox_ctl.tx; + struct wil6210_mbox_ring_desc d_head; + u32 next_head; + void __iomem *dst; + void __iomem *head = wmi_addr(wil, r->head); + uint retry; + + if (sizeof(cmd) + len > r->entry_size) { + wil_err(wil, "WMI size too large: %d bytes, max is %d\n", + (int)(sizeof(cmd) + len), r->entry_size); + return -ERANGE; + } + + might_sleep(); + + if (!test_bit(wil_status_fwready, &wil->status)) { + wil_err(wil, "FW not ready\n"); + return -EAGAIN; + } + + if (!head) { + wil_err(wil, "WMI head is garbage: 0x%08x\n", r->head); + return -EINVAL; + } + /* read Tx head till it is not busy */ + for (retry = 5; retry > 0; retry--) { + wil_memcpy_fromio_32(&d_head, head, sizeof(d_head)); + if (d_head.sync == 0) + break; + msleep(20); + } + if (d_head.sync != 0) { + wil_err(wil, "WMI head busy\n"); + return -EBUSY; + } + /* next head */ + next_head = r->base + ((r->head - r->base + sizeof(d_head)) % r->size); + wil_dbg_wmi(wil, "Head 0x%08x -> 0x%08x\n", r->head, next_head); + /* wait till FW finish with previous command */ + for (retry = 5; retry > 0; retry--) { + r->tail = ioread32(wil->csr + HOST_MBOX + + offsetof(struct wil6210_mbox_ctl, tx.tail)); + if (next_head != r->tail) + break; + msleep(20); + } + if (next_head == r->tail) { + wil_err(wil, "WMI ring full\n"); + return -EBUSY; + } + dst = wmi_buffer(wil, d_head.addr); + if (!dst) { + wil_err(wil, "invalid WMI buffer: 0x%08x\n", + le32_to_cpu(d_head.addr)); + return -EINVAL; + } + cmd.hdr.seq = cpu_to_le16(++wil->wmi_seq); + /* set command */ + wil_dbg_wmi(wil, "WMI command 0x%04x [%d]\n", cmdid, len); + wil_hex_dump_wmi("Cmd ", DUMP_PREFIX_OFFSET, 16, 1, &cmd, + sizeof(cmd), true); + wil_hex_dump_wmi("cmd ", DUMP_PREFIX_OFFSET, 16, 1, buf, + len, true); + wil_memcpy_toio_32(dst, &cmd, sizeof(cmd)); + wil_memcpy_toio_32(dst + sizeof(cmd), buf, len); + /* mark entry as full */ + iowrite32(1, wil->csr + HOSTADDR(r->head) + + offsetof(struct wil6210_mbox_ring_desc, sync)); + /* advance next ptr */ + iowrite32(r->head = next_head, wil->csr + HOST_MBOX + + offsetof(struct wil6210_mbox_ctl, tx.head)); + + /* interrupt to FW */ + iowrite32(SW_INT_MBOX, wil->csr + HOST_SW_INT); + + return 0; +} + +int wmi_send(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len) +{ + int rc; + + mutex_lock(&wil->wmi_mutex); + rc = __wmi_send(wil, cmdid, buf, len); + mutex_unlock(&wil->wmi_mutex); + + return rc; +} + +/*=== Event handlers ===*/ +static void wmi_evt_ready(struct wil6210_priv *wil, int id, void *d, int len) +{ + struct net_device *ndev = wil_to_ndev(wil); + struct wireless_dev *wdev = wil->wdev; + struct wmi_ready_event *evt = d; + wil->fw_version = le32_to_cpu(evt->sw_version); + wil->n_mids = evt->numof_additional_mids; + + wil_dbg_wmi(wil, "FW ver. %d; MAC %pM; %d MID's\n", wil->fw_version, + evt->mac, wil->n_mids); + + if (!is_valid_ether_addr(ndev->dev_addr)) { + memcpy(ndev->dev_addr, evt->mac, ETH_ALEN); + memcpy(ndev->perm_addr, evt->mac, ETH_ALEN); + } + snprintf(wdev->wiphy->fw_version, sizeof(wdev->wiphy->fw_version), + "%d", wil->fw_version); +} + +static void wmi_evt_fw_ready(struct wil6210_priv *wil, int id, void *d, + int len) +{ + wil_dbg_wmi(wil, "WMI: FW ready\n"); + + set_bit(wil_status_fwready, &wil->status); + /* reuse wmi_ready for the firmware ready indication */ + complete(&wil->wmi_ready); +} + +static void wmi_evt_rx_mgmt(struct wil6210_priv *wil, int id, void *d, int len) +{ + struct wmi_rx_mgmt_packet_event *data = d; + struct wiphy *wiphy = wil_to_wiphy(wil); + struct ieee80211_mgmt *rx_mgmt_frame = + (struct ieee80211_mgmt *)data->payload; + int ch_no = data->info.channel+1; + u32 freq = ieee80211_channel_to_frequency(ch_no, + IEEE80211_BAND_60GHZ); + struct ieee80211_channel *channel = ieee80211_get_channel(wiphy, freq); + /* TODO convert LE to CPU */ + s32 signal = 0; /* TODO */ + __le16 fc = rx_mgmt_frame->frame_control; + u32 d_len = le32_to_cpu(data->info.len); + u16 d_status = le16_to_cpu(data->info.status); + + wil_dbg_wmi(wil, "MGMT: channel %d MCS %d SNR %d\n", + data->info.channel, data->info.mcs, data->info.snr); + wil_dbg_wmi(wil, "status 0x%04x len %d stype %04x\n", d_status, d_len, + le16_to_cpu(data->info.stype)); + wil_dbg_wmi(wil, "qid %d mid %d cid %d\n", + data->info.qid, data->info.mid, data->info.cid); + + if (!channel) { + wil_err(wil, "Frame on unsupported channel\n"); + return; + } + + if (ieee80211_is_beacon(fc) || ieee80211_is_probe_resp(fc)) { + struct cfg80211_bss *bss; + + bss = cfg80211_inform_bss_frame(wiphy, channel, rx_mgmt_frame, + d_len, signal, GFP_KERNEL); + if (bss) { + wil_dbg_wmi(wil, "Added BSS %pM\n", + rx_mgmt_frame->bssid); + cfg80211_put_bss(wiphy, bss); + } else { + wil_err(wil, "cfg80211_inform_bss() failed\n"); + } + } else { + cfg80211_rx_mgmt(wil->wdev, freq, signal, + (void *)rx_mgmt_frame, d_len, GFP_KERNEL); + } +} + +static void wmi_evt_scan_complete(struct wil6210_priv *wil, int id, + void *d, int len) +{ + if (wil->scan_request) { + struct wmi_scan_complete_event *data = d; + bool aborted = (data->status != 0); + + wil_dbg_wmi(wil, "SCAN_COMPLETE(0x%08x)\n", data->status); + cfg80211_scan_done(wil->scan_request, aborted); + wil->scan_request = NULL; + } else { + wil_err(wil, "SCAN_COMPLETE while not scanning\n"); + } +} + +static void wmi_evt_connect(struct wil6210_priv *wil, int id, void *d, int len) +{ + struct net_device *ndev = wil_to_ndev(wil); + struct wireless_dev *wdev = wil->wdev; + struct wmi_connect_event *evt = d; + int ch; /* channel number */ + struct station_info sinfo; + u8 *assoc_req_ie, *assoc_resp_ie; + size_t assoc_req_ielen, assoc_resp_ielen; + /* capinfo(u16) + listen_interval(u16) + IEs */ + const size_t assoc_req_ie_offset = sizeof(u16) * 2; + /* capinfo(u16) + status_code(u16) + associd(u16) + IEs */ + const size_t assoc_resp_ie_offset = sizeof(u16) * 3; + + if (len < sizeof(*evt)) { + wil_err(wil, "Connect event too short : %d bytes\n", len); + return; + } + if (len != sizeof(*evt) + evt->beacon_ie_len + evt->assoc_req_len + + evt->assoc_resp_len) { + wil_err(wil, + "Connect event corrupted : %d != %d + %d + %d + %d\n", + len, (int)sizeof(*evt), evt->beacon_ie_len, + evt->assoc_req_len, evt->assoc_resp_len); + return; + } + ch = evt->channel + 1; + wil_dbg_wmi(wil, "Connect %pM channel [%d] cid %d\n", + evt->bssid, ch, evt->cid); + wil_hex_dump_wmi("connect AI : ", DUMP_PREFIX_OFFSET, 16, 1, + evt->assoc_info, len - sizeof(*evt), true); + + /* figure out IE's */ + assoc_req_ie = &evt->assoc_info[evt->beacon_ie_len + + assoc_req_ie_offset]; + assoc_req_ielen = evt->assoc_req_len - assoc_req_ie_offset; + if (evt->assoc_req_len <= assoc_req_ie_offset) { + assoc_req_ie = NULL; + assoc_req_ielen = 0; + } + + assoc_resp_ie = &evt->assoc_info[evt->beacon_ie_len + + evt->assoc_req_len + + assoc_resp_ie_offset]; + assoc_resp_ielen = evt->assoc_resp_len - assoc_resp_ie_offset; + if (evt->assoc_resp_len <= assoc_resp_ie_offset) { + assoc_resp_ie = NULL; + assoc_resp_ielen = 0; + } + + if ((wdev->iftype == NL80211_IFTYPE_STATION) || + (wdev->iftype == NL80211_IFTYPE_P2P_CLIENT)) { + if (wdev->sme_state != CFG80211_SME_CONNECTING) { + wil_err(wil, "Not in connecting state\n"); + return; + } + del_timer_sync(&wil->connect_timer); + cfg80211_connect_result(ndev, evt->bssid, + assoc_req_ie, assoc_req_ielen, + assoc_resp_ie, assoc_resp_ielen, + WLAN_STATUS_SUCCESS, GFP_KERNEL); + + } else if ((wdev->iftype == NL80211_IFTYPE_AP) || + (wdev->iftype == NL80211_IFTYPE_P2P_GO)) { + memset(&sinfo, 0, sizeof(sinfo)); + + sinfo.generation = wil->sinfo_gen++; + + if (assoc_req_ie) { + sinfo.assoc_req_ies = assoc_req_ie; + sinfo.assoc_req_ies_len = assoc_req_ielen; + sinfo.filled |= STATION_INFO_ASSOC_REQ_IES; + } + + cfg80211_new_sta(ndev, evt->bssid, &sinfo, GFP_KERNEL); + } + set_bit(wil_status_fwconnected, &wil->status); + + /* FIXME FW can transmit only ucast frames to peer */ + /* FIXME real ring_id instead of hard coded 0 */ + memcpy(wil->dst_addr[0], evt->bssid, ETH_ALEN); + + wil->pending_connect_cid = evt->cid; + queue_work(wil->wmi_wq_conn, &wil->connect_worker); +} + +static void wmi_evt_disconnect(struct wil6210_priv *wil, int id, + void *d, int len) +{ + struct wmi_disconnect_event *evt = d; + + wil_dbg_wmi(wil, "Disconnect %pM reason %d proto %d wmi\n", + evt->bssid, + evt->protocol_reason_status, evt->disconnect_reason); + + wil->sinfo_gen++; + + wil6210_disconnect(wil, evt->bssid); +} + +static void wmi_evt_notify(struct wil6210_priv *wil, int id, void *d, int len) +{ + struct wmi_notify_req_done_event *evt = d; + + if (len < sizeof(*evt)) { + wil_err(wil, "Short NOTIFY event\n"); + return; + } + + wil->stats.tsf = le64_to_cpu(evt->tsf); + wil->stats.snr = le32_to_cpu(evt->snr_val); + wil->stats.bf_mcs = le16_to_cpu(evt->bf_mcs); + wil->stats.my_rx_sector = le16_to_cpu(evt->my_rx_sector); + wil->stats.my_tx_sector = le16_to_cpu(evt->my_tx_sector); + wil->stats.peer_rx_sector = le16_to_cpu(evt->other_rx_sector); + wil->stats.peer_tx_sector = le16_to_cpu(evt->other_tx_sector); + wil_dbg_wmi(wil, "Link status, MCS %d TSF 0x%016llx\n" + "BF status 0x%08x SNR 0x%08x\n" + "Tx Tpt %d goodput %d Rx goodput %d\n" + "Sectors(rx:tx) my %d:%d peer %d:%d\n", + wil->stats.bf_mcs, wil->stats.tsf, evt->status, + wil->stats.snr, le32_to_cpu(evt->tx_tpt), + le32_to_cpu(evt->tx_goodput), le32_to_cpu(evt->rx_goodput), + wil->stats.my_rx_sector, wil->stats.my_tx_sector, + wil->stats.peer_rx_sector, wil->stats.peer_tx_sector); +} + +/* + * Firmware reports EAPOL frame using WME event. + * Reconstruct Ethernet frame and deliver it via normal Rx + */ +static void wmi_evt_eapol_rx(struct wil6210_priv *wil, int id, + void *d, int len) +{ + struct net_device *ndev = wil_to_ndev(wil); + struct wmi_eapol_rx_event *evt = d; + u16 eapol_len = le16_to_cpu(evt->eapol_len); + int sz = eapol_len + ETH_HLEN; + struct sk_buff *skb; + struct ethhdr *eth; + + wil_dbg_wmi(wil, "EAPOL len %d from %pM\n", eapol_len, + evt->src_mac); + + if (eapol_len > 196) { /* TODO: revisit size limit */ + wil_err(wil, "EAPOL too large\n"); + return; + } + + skb = alloc_skb(sz, GFP_KERNEL); + if (!skb) { + wil_err(wil, "Failed to allocate skb\n"); + return; + } + eth = (struct ethhdr *)skb_put(skb, ETH_HLEN); + memcpy(eth->h_dest, ndev->dev_addr, ETH_ALEN); + memcpy(eth->h_source, evt->src_mac, ETH_ALEN); + eth->h_proto = cpu_to_be16(ETH_P_PAE); + memcpy(skb_put(skb, eapol_len), evt->eapol, eapol_len); + skb->protocol = eth_type_trans(skb, ndev); + if (likely(netif_rx_ni(skb) == NET_RX_SUCCESS)) { + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += skb->len; + } else { + ndev->stats.rx_dropped++; + } +} + +static void wmi_evt_linkup(struct wil6210_priv *wil, int id, void *d, int len) +{ + struct net_device *ndev = wil_to_ndev(wil); + struct wmi_data_port_open_event *evt = d; + + wil_dbg_wmi(wil, "Link UP for CID %d\n", evt->cid); + + netif_carrier_on(ndev); +} + +static void wmi_evt_linkdown(struct wil6210_priv *wil, int id, void *d, int len) +{ + struct net_device *ndev = wil_to_ndev(wil); + struct wmi_wbe_link_down_event *evt = d; + + wil_dbg_wmi(wil, "Link DOWN for CID %d, reason %d\n", + evt->cid, le32_to_cpu(evt->reason)); + + netif_carrier_off(ndev); +} + +static void wmi_evt_ba_status(struct wil6210_priv *wil, int id, void *d, + int len) +{ + struct wmi_vring_ba_status_event *evt = d; + + wil_dbg_wmi(wil, "BACK[%d] %s {%d} timeout %d\n", + evt->ringid, evt->status ? "N/A" : "OK", evt->agg_wsize, + __le16_to_cpu(evt->ba_timeout)); +} + +static const struct { + int eventid; + void (*handler)(struct wil6210_priv *wil, int eventid, + void *data, int data_len); +} wmi_evt_handlers[] = { + {WMI_READY_EVENTID, wmi_evt_ready}, + {WMI_FW_READY_EVENTID, wmi_evt_fw_ready}, + {WMI_RX_MGMT_PACKET_EVENTID, wmi_evt_rx_mgmt}, + {WMI_SCAN_COMPLETE_EVENTID, wmi_evt_scan_complete}, + {WMI_CONNECT_EVENTID, wmi_evt_connect}, + {WMI_DISCONNECT_EVENTID, wmi_evt_disconnect}, + {WMI_NOTIFY_REQ_DONE_EVENTID, wmi_evt_notify}, + {WMI_EAPOL_RX_EVENTID, wmi_evt_eapol_rx}, + {WMI_DATA_PORT_OPEN_EVENTID, wmi_evt_linkup}, + {WMI_WBE_LINKDOWN_EVENTID, wmi_evt_linkdown}, + {WMI_BA_STATUS_EVENTID, wmi_evt_ba_status}, +}; + +/* + * Run in IRQ context + * Extract WMI command from mailbox. Queue it to the @wil->pending_wmi_ev + * that will be eventually handled by the @wmi_event_worker in the thread + * context of thread "wil6210_wmi" + */ +void wmi_recv_cmd(struct wil6210_priv *wil) +{ + struct wil6210_mbox_ring_desc d_tail; + struct wil6210_mbox_hdr hdr; + struct wil6210_mbox_ring *r = &wil->mbox_ctl.rx; + struct pending_wmi_event *evt; + u8 *cmd; + void __iomem *src; + ulong flags; + + if (!test_bit(wil_status_reset_done, &wil->status)) { + wil_err(wil, "Reset not completed\n"); + return; + } + + for (;;) { + u16 len; + + r->head = ioread32(wil->csr + HOST_MBOX + + offsetof(struct wil6210_mbox_ctl, rx.head)); + if (r->tail == r->head) + return; + + /* read cmd from tail */ + wil_memcpy_fromio_32(&d_tail, wil->csr + HOSTADDR(r->tail), + sizeof(struct wil6210_mbox_ring_desc)); + if (d_tail.sync == 0) { + wil_err(wil, "Mbox evt not owned by FW?\n"); + return; + } + + if (0 != wmi_read_hdr(wil, d_tail.addr, &hdr)) { + wil_err(wil, "Mbox evt at 0x%08x?\n", + le32_to_cpu(d_tail.addr)); + return; + } + + len = le16_to_cpu(hdr.len); + src = wmi_buffer(wil, d_tail.addr) + + sizeof(struct wil6210_mbox_hdr); + evt = kmalloc(ALIGN(offsetof(struct pending_wmi_event, + event.wmi) + len, 4), + GFP_KERNEL); + if (!evt) + return; + + evt->event.hdr = hdr; + cmd = (void *)&evt->event.wmi; + wil_memcpy_fromio_32(cmd, src, len); + /* mark entry as empty */ + iowrite32(0, wil->csr + HOSTADDR(r->tail) + + offsetof(struct wil6210_mbox_ring_desc, sync)); + /* indicate */ + wil_dbg_wmi(wil, "Mbox evt %04x %04x %04x %02x\n", + le16_to_cpu(hdr.seq), len, le16_to_cpu(hdr.type), + hdr.flags); + if ((hdr.type == WIL_MBOX_HDR_TYPE_WMI) && + (len >= sizeof(struct wil6210_mbox_hdr_wmi))) { + wil_dbg_wmi(wil, "WMI event 0x%04x\n", + evt->event.wmi.id); + } + wil_hex_dump_wmi("evt ", DUMP_PREFIX_OFFSET, 16, 1, + &evt->event.hdr, sizeof(hdr) + len, true); + + /* advance tail */ + r->tail = r->base + ((r->tail - r->base + + sizeof(struct wil6210_mbox_ring_desc)) % r->size); + iowrite32(r->tail, wil->csr + HOST_MBOX + + offsetof(struct wil6210_mbox_ctl, rx.tail)); + + /* add to the pending list */ + spin_lock_irqsave(&wil->wmi_ev_lock, flags); + list_add_tail(&evt->list, &wil->pending_wmi_ev); + spin_unlock_irqrestore(&wil->wmi_ev_lock, flags); + { + int q = queue_work(wil->wmi_wq, + &wil->wmi_event_worker); + wil_dbg_wmi(wil, "queue_work -> %d\n", q); + } + } +} + +int wmi_call(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len, + u16 reply_id, void *reply, u8 reply_size, int to_msec) +{ + int rc; + int remain; + + mutex_lock(&wil->wmi_mutex); + + rc = __wmi_send(wil, cmdid, buf, len); + if (rc) + goto out; + + wil->reply_id = reply_id; + wil->reply_buf = reply; + wil->reply_size = reply_size; + remain = wait_for_completion_timeout(&wil->wmi_ready, + msecs_to_jiffies(to_msec)); + if (0 == remain) { + wil_err(wil, "wmi_call(0x%04x->0x%04x) timeout %d msec\n", + cmdid, reply_id, to_msec); + rc = -ETIME; + } else { + wil_dbg_wmi(wil, + "wmi_call(0x%04x->0x%04x) completed in %d msec\n", + cmdid, reply_id, + to_msec - jiffies_to_msecs(remain)); + } + wil->reply_id = 0; + wil->reply_buf = NULL; + wil->reply_size = 0; + out: + mutex_unlock(&wil->wmi_mutex); + + return rc; +} + +int wmi_echo(struct wil6210_priv *wil) +{ + struct wmi_echo_cmd cmd = { + .value = cpu_to_le32(0x12345678), + }; + + return wmi_call(wil, WMI_ECHO_CMDID, &cmd, sizeof(cmd), + WMI_ECHO_RSP_EVENTID, NULL, 0, 20); +} + +int wmi_set_mac_address(struct wil6210_priv *wil, void *addr) +{ + struct wmi_set_mac_address_cmd cmd; + + memcpy(cmd.mac, addr, ETH_ALEN); + + wil_dbg_wmi(wil, "Set MAC %pM\n", addr); + + return wmi_send(wil, WMI_SET_MAC_ADDRESS_CMDID, &cmd, sizeof(cmd)); +} + +int wmi_pcp_start(struct wil6210_priv *wil, int bi, u8 wmi_nettype, u8 chan) +{ + int rc; + + struct wmi_pcp_start_cmd cmd = { + .bcon_interval = cpu_to_le16(bi), + .network_type = wmi_nettype, + .disable_sec_offload = 1, + .channel = chan, + }; + struct { + struct wil6210_mbox_hdr_wmi wmi; + struct wmi_pcp_started_event evt; + } __packed reply; + + if (!wil->secure_pcp) + cmd.disable_sec = 1; + + rc = wmi_call(wil, WMI_PCP_START_CMDID, &cmd, sizeof(cmd), + WMI_PCP_STARTED_EVENTID, &reply, sizeof(reply), 100); + if (rc) + return rc; + + if (reply.evt.status != WMI_FW_STATUS_SUCCESS) + rc = -EINVAL; + + return rc; +} + +int wmi_pcp_stop(struct wil6210_priv *wil) +{ + return wmi_call(wil, WMI_PCP_STOP_CMDID, NULL, 0, + WMI_PCP_STOPPED_EVENTID, NULL, 0, 20); +} + +int wmi_set_ssid(struct wil6210_priv *wil, u8 ssid_len, const void *ssid) +{ + struct wmi_set_ssid_cmd cmd = { + .ssid_len = cpu_to_le32(ssid_len), + }; + + if (ssid_len > sizeof(cmd.ssid)) + return -EINVAL; + + memcpy(cmd.ssid, ssid, ssid_len); + + return wmi_send(wil, WMI_SET_SSID_CMDID, &cmd, sizeof(cmd)); +} + +int wmi_get_ssid(struct wil6210_priv *wil, u8 *ssid_len, void *ssid) +{ + int rc; + struct { + struct wil6210_mbox_hdr_wmi wmi; + struct wmi_set_ssid_cmd cmd; + } __packed reply; + int len; /* reply.cmd.ssid_len in CPU order */ + + rc = wmi_call(wil, WMI_GET_SSID_CMDID, NULL, 0, WMI_GET_SSID_EVENTID, + &reply, sizeof(reply), 20); + if (rc) + return rc; + + len = le32_to_cpu(reply.cmd.ssid_len); + if (len > sizeof(reply.cmd.ssid)) + return -EINVAL; + + *ssid_len = len; + memcpy(ssid, reply.cmd.ssid, len); + + return 0; +} + +int wmi_set_channel(struct wil6210_priv *wil, int channel) +{ + struct wmi_set_pcp_channel_cmd cmd = { + .channel = channel - 1, + }; + + return wmi_send(wil, WMI_SET_PCP_CHANNEL_CMDID, &cmd, sizeof(cmd)); +} + +int wmi_get_channel(struct wil6210_priv *wil, int *channel) +{ + int rc; + struct { + struct wil6210_mbox_hdr_wmi wmi; + struct wmi_set_pcp_channel_cmd cmd; + } __packed reply; + + rc = wmi_call(wil, WMI_GET_PCP_CHANNEL_CMDID, NULL, 0, + WMI_GET_PCP_CHANNEL_EVENTID, &reply, sizeof(reply), 20); + if (rc) + return rc; + + if (reply.cmd.channel > 3) + return -EINVAL; + + *channel = reply.cmd.channel + 1; + + return 0; +} + +int wmi_p2p_cfg(struct wil6210_priv *wil, int channel) +{ + struct wmi_p2p_cfg_cmd cmd = { + .discovery_mode = WMI_DISCOVERY_MODE_NON_OFFLOAD, + .channel = channel - 1, + }; + + return wmi_send(wil, WMI_P2P_CFG_CMDID, &cmd, sizeof(cmd)); +} + +int wmi_tx_eapol(struct wil6210_priv *wil, struct sk_buff *skb) +{ + struct wmi_eapol_tx_cmd *cmd; + struct ethhdr *eth; + u16 eapol_len = skb->len - ETH_HLEN; + void *eapol = skb->data + ETH_HLEN; + uint i; + int rc; + + skb_set_mac_header(skb, 0); + eth = eth_hdr(skb); + wil_dbg_wmi(wil, "EAPOL %d bytes to %pM\n", eapol_len, eth->h_dest); + for (i = 0; i < ARRAY_SIZE(wil->vring_tx); i++) { + if (memcmp(wil->dst_addr[i], eth->h_dest, ETH_ALEN) == 0) + goto found_dest; + } + + return -EINVAL; + + found_dest: + /* find out eapol data & len */ + cmd = kzalloc(sizeof(*cmd) + eapol_len, GFP_KERNEL); + if (!cmd) + return -EINVAL; + + memcpy(cmd->dst_mac, eth->h_dest, ETH_ALEN); + cmd->eapol_len = cpu_to_le16(eapol_len); + memcpy(cmd->eapol, eapol, eapol_len); + rc = wmi_send(wil, WMI_EAPOL_TX_CMDID, cmd, sizeof(*cmd) + eapol_len); + kfree(cmd); + + return rc; +} + +int wmi_del_cipher_key(struct wil6210_priv *wil, u8 key_index, + const void *mac_addr) +{ + struct wmi_delete_cipher_key_cmd cmd = { + .key_index = key_index, + }; + + if (mac_addr) + memcpy(cmd.mac, mac_addr, WMI_MAC_LEN); + + return wmi_send(wil, WMI_DELETE_CIPHER_KEY_CMDID, &cmd, sizeof(cmd)); +} + +int wmi_add_cipher_key(struct wil6210_priv *wil, u8 key_index, + const void *mac_addr, int key_len, const void *key) +{ + struct wmi_add_cipher_key_cmd cmd = { + .key_index = key_index, + .key_usage = WMI_KEY_USE_PAIRWISE, + .key_len = key_len, + }; + + if (!key || (key_len > sizeof(cmd.key))) + return -EINVAL; + + memcpy(cmd.key, key, key_len); + if (mac_addr) + memcpy(cmd.mac, mac_addr, WMI_MAC_LEN); + + return wmi_send(wil, WMI_ADD_CIPHER_KEY_CMDID, &cmd, sizeof(cmd)); +} + +int wmi_set_ie(struct wil6210_priv *wil, u8 type, u16 ie_len, const void *ie) +{ + int rc; + u16 len = sizeof(struct wmi_set_appie_cmd) + ie_len; + struct wmi_set_appie_cmd *cmd = kzalloc(len, GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + cmd->mgmt_frm_type = type; + /* BUG: FW API define ieLen as u8. Will fix FW */ + cmd->ie_len = cpu_to_le16(ie_len); + memcpy(cmd->ie_info, ie, ie_len); + rc = wmi_send(wil, WMI_SET_APPIE_CMDID, cmd, len); + kfree(cmd); + + return rc; +} + +int wmi_rx_chain_add(struct wil6210_priv *wil, struct vring *vring) +{ + struct wireless_dev *wdev = wil->wdev; + struct net_device *ndev = wil_to_ndev(wil); + struct wmi_cfg_rx_chain_cmd cmd = { + .action = WMI_RX_CHAIN_ADD, + .rx_sw_ring = { + .max_mpdu_size = cpu_to_le16(RX_BUF_LEN), + .ring_mem_base = cpu_to_le64(vring->pa), + .ring_size = cpu_to_le16(vring->size), + }, + .mid = 0, /* TODO - what is it? */ + .decap_trans_type = WMI_DECAP_TYPE_802_3, + }; + struct { + struct wil6210_mbox_hdr_wmi wmi; + struct wmi_cfg_rx_chain_done_event evt; + } __packed evt; + int rc; + + if (wdev->iftype == NL80211_IFTYPE_MONITOR) { + struct ieee80211_channel *ch = wdev->preset_chandef.chan; + + cmd.sniffer_cfg.mode = cpu_to_le32(WMI_SNIFFER_ON); + if (ch) + cmd.sniffer_cfg.channel = ch->hw_value - 1; + cmd.sniffer_cfg.phy_info_mode = + cpu_to_le32(ndev->type == ARPHRD_IEEE80211_RADIOTAP); + cmd.sniffer_cfg.phy_support = + cpu_to_le32((wil->monitor_flags & MONITOR_FLAG_CONTROL) + ? WMI_SNIFFER_CP : WMI_SNIFFER_DP); + } + /* typical time for secure PCP is 840ms */ + rc = wmi_call(wil, WMI_CFG_RX_CHAIN_CMDID, &cmd, sizeof(cmd), + WMI_CFG_RX_CHAIN_DONE_EVENTID, &evt, sizeof(evt), 2000); + if (rc) + return rc; + + vring->hwtail = le32_to_cpu(evt.evt.rx_ring_tail_ptr); + + wil_dbg_misc(wil, "Rx init: status %d tail 0x%08x\n", + le32_to_cpu(evt.evt.status), vring->hwtail); + + if (le32_to_cpu(evt.evt.status) != WMI_CFG_RX_CHAIN_SUCCESS) + rc = -EINVAL; + + return rc; +} + +int wmi_get_temperature(struct wil6210_priv *wil, u32 *t_m, u32 *t_r) +{ + int rc; + struct wmi_temp_sense_cmd cmd = { + .measure_marlon_m_en = cpu_to_le32(!!t_m), + .measure_marlon_r_en = cpu_to_le32(!!t_r), + }; + struct { + struct wil6210_mbox_hdr_wmi wmi; + struct wmi_temp_sense_done_event evt; + } __packed reply; + + rc = wmi_call(wil, WMI_TEMP_SENSE_CMDID, &cmd, sizeof(cmd), + WMI_TEMP_SENSE_DONE_EVENTID, &reply, sizeof(reply), 100); + if (rc) + return rc; + + if (t_m) + *t_m = le32_to_cpu(reply.evt.marlon_m_t1000); + if (t_r) + *t_r = le32_to_cpu(reply.evt.marlon_r_t1000); + + return 0; +} + +void wmi_event_flush(struct wil6210_priv *wil) +{ + struct pending_wmi_event *evt, *t; + + wil_dbg_wmi(wil, "%s()\n", __func__); + + list_for_each_entry_safe(evt, t, &wil->pending_wmi_ev, list) { + list_del(&evt->list); + kfree(evt); + } +} + +static bool wmi_evt_call_handler(struct wil6210_priv *wil, int id, + void *d, int len) +{ + uint i; + + for (i = 0; i < ARRAY_SIZE(wmi_evt_handlers); i++) { + if (wmi_evt_handlers[i].eventid == id) { + wmi_evt_handlers[i].handler(wil, id, d, len); + return true; + } + } + + return false; +} + +static void wmi_event_handle(struct wil6210_priv *wil, + struct wil6210_mbox_hdr *hdr) +{ + u16 len = le16_to_cpu(hdr->len); + + if ((hdr->type == WIL_MBOX_HDR_TYPE_WMI) && + (len >= sizeof(struct wil6210_mbox_hdr_wmi))) { + struct wil6210_mbox_hdr_wmi *wmi = (void *)(&hdr[1]); + void *evt_data = (void *)(&wmi[1]); + u16 id = le16_to_cpu(wmi->id); + /* check if someone waits for this event */ + if (wil->reply_id && wil->reply_id == id) { + if (wil->reply_buf) { + memcpy(wil->reply_buf, wmi, + min(len, wil->reply_size)); + } else { + wmi_evt_call_handler(wil, id, evt_data, + len - sizeof(*wmi)); + } + wil_dbg_wmi(wil, "Complete WMI 0x%04x\n", id); + complete(&wil->wmi_ready); + return; + } + /* unsolicited event */ + /* search for handler */ + if (!wmi_evt_call_handler(wil, id, evt_data, + len - sizeof(*wmi))) { + wil_err(wil, "Unhandled event 0x%04x\n", id); + } + } else { + wil_err(wil, "Unknown event type\n"); + print_hex_dump(KERN_ERR, "evt?? ", DUMP_PREFIX_OFFSET, 16, 1, + hdr, sizeof(*hdr) + len, true); + } +} + +/* + * Retrieve next WMI event from the pending list + */ +static struct list_head *next_wmi_ev(struct wil6210_priv *wil) +{ + ulong flags; + struct list_head *ret = NULL; + + spin_lock_irqsave(&wil->wmi_ev_lock, flags); + + if (!list_empty(&wil->pending_wmi_ev)) { + ret = wil->pending_wmi_ev.next; + list_del(ret); + } + + spin_unlock_irqrestore(&wil->wmi_ev_lock, flags); + + return ret; +} + +/* + * Handler for the WMI events + */ +void wmi_event_worker(struct work_struct *work) +{ + struct wil6210_priv *wil = container_of(work, struct wil6210_priv, + wmi_event_worker); + struct pending_wmi_event *evt; + struct list_head *lh; + + while ((lh = next_wmi_ev(wil)) != NULL) { + evt = list_entry(lh, struct pending_wmi_event, list); + wmi_event_handle(wil, &evt->event.hdr); + kfree(evt); + } +} diff --git a/drivers/net/wireless/ath/wil6210/wmi.h b/drivers/net/wireless/ath/wil6210/wmi.h new file mode 100644 index 0000000..50b8528 --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/wmi.h @@ -0,0 +1,1281 @@ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * Copyright (c) 2006-2012 Wilocity . + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file contains the definitions of the WMI protocol specified in the + * Wireless Module Interface (WMI) for the Wilocity + * MARLON 60 Gigabit wireless solution. + * It includes definitions of all the commands and events. + * Commands are messages from the host to the WM. + * Events are messages from the WM to the host. + */ + +#ifndef __WILOCITY_WMI_H__ +#define __WILOCITY_WMI_H__ + +/* General */ + +#define WMI_MAC_LEN (6) +#define WMI_PROX_RANGE_NUM (3) + +/* List of Commands */ +enum wmi_command_id { + WMI_CONNECT_CMDID = 0x0001, + WMI_DISCONNECT_CMDID = 0x0003, + WMI_DISCONNECT_STA_CMDID = 0x0004, + WMI_START_SCAN_CMDID = 0x0007, + WMI_SET_BSS_FILTER_CMDID = 0x0009, + WMI_SET_PROBED_SSID_CMDID = 0x000a, + WMI_SET_LISTEN_INT_CMDID = 0x000b, + WMI_BCON_CTRL_CMDID = 0x000f, + WMI_ADD_CIPHER_KEY_CMDID = 0x0016, + WMI_DELETE_CIPHER_KEY_CMDID = 0x0017, + WMI_SET_APPIE_CMDID = 0x003f, + WMI_SET_WSC_STATUS_CMDID = 0x0041, + WMI_PXMT_RANGE_CFG_CMDID = 0x0042, + WMI_PXMT_SNR2_RANGE_CFG_CMDID = 0x0043, + WMI_FAST_MEM_ACC_MODE_CMDID = 0x0300, + WMI_MEM_READ_CMDID = 0x0800, + WMI_MEM_WR_CMDID = 0x0801, + WMI_ECHO_CMDID = 0x0803, + WMI_DEEP_ECHO_CMDID = 0x0804, + WMI_CONFIG_MAC_CMDID = 0x0805, + WMI_CONFIG_PHY_DEBUG_CMDID = 0x0806, + WMI_ADD_DEBUG_TX_PCKT_CMDID = 0x0808, + WMI_PHY_GET_STATISTICS_CMDID = 0x0809, + WMI_FS_TUNE_CMDID = 0x080a, + WMI_CORR_MEASURE_CMDID = 0x080b, + WMI_READ_RSSI_CMDID = 0x080c, + WMI_TEMP_SENSE_CMDID = 0x080e, + WMI_DC_CALIB_CMDID = 0x080f, + WMI_SEND_TONE_CMDID = 0x0810, + WMI_IQ_TX_CALIB_CMDID = 0x0811, + WMI_IQ_RX_CALIB_CMDID = 0x0812, + WMI_SET_UCODE_IDLE_CMDID = 0x0813, + WMI_SET_WORK_MODE_CMDID = 0x0815, + WMI_LO_LEAKAGE_CALIB_CMDID = 0x0816, + WMI_MARLON_R_ACTIVATE_CMDID = 0x0817, + WMI_MARLON_R_READ_CMDID = 0x0818, + WMI_MARLON_R_WRITE_CMDID = 0x0819, + WMI_MARLON_R_TXRX_SEL_CMDID = 0x081a, + MAC_IO_STATIC_PARAMS_CMDID = 0x081b, + MAC_IO_DYNAMIC_PARAMS_CMDID = 0x081c, + WMI_SILENT_RSSI_CALIB_CMDID = 0x081d, + WMI_RF_RX_TEST_CMDID = 0x081e, + WMI_CFG_RX_CHAIN_CMDID = 0x0820, + WMI_VRING_CFG_CMDID = 0x0821, + WMI_VRING_BA_EN_CMDID = 0x0823, + WMI_VRING_BA_DIS_CMDID = 0x0824, + WMI_RCP_ADDBA_RESP_CMDID = 0x0825, + WMI_RCP_DELBA_CMDID = 0x0826, + WMI_SET_SSID_CMDID = 0x0827, + WMI_GET_SSID_CMDID = 0x0828, + WMI_SET_PCP_CHANNEL_CMDID = 0x0829, + WMI_GET_PCP_CHANNEL_CMDID = 0x082a, + WMI_SW_TX_REQ_CMDID = 0x082b, + WMI_READ_MAC_RXQ_CMDID = 0x0830, + WMI_READ_MAC_TXQ_CMDID = 0x0831, + WMI_WRITE_MAC_RXQ_CMDID = 0x0832, + WMI_WRITE_MAC_TXQ_CMDID = 0x0833, + WMI_WRITE_MAC_XQ_FIELD_CMDID = 0x0834, + WMI_MLME_PUSH_CMDID = 0x0835, + WMI_BEAMFORMING_MGMT_CMDID = 0x0836, + WMI_BF_TXSS_MGMT_CMDID = 0x0837, + WMI_BF_SM_MGMT_CMDID = 0x0838, + WMI_BF_RXSS_MGMT_CMDID = 0x0839, + WMI_SET_SECTORS_CMDID = 0x0849, + WMI_MAINTAIN_PAUSE_CMDID = 0x0850, + WMI_MAINTAIN_RESUME_CMDID = 0x0851, + WMI_RS_MGMT_CMDID = 0x0852, + WMI_RF_MGMT_CMDID = 0x0853, + /* Performance monitoring commands */ + WMI_BF_CTRL_CMDID = 0x0862, + WMI_NOTIFY_REQ_CMDID = 0x0863, + WMI_GET_STATUS_CMDID = 0x0864, + WMI_UNIT_TEST_CMDID = 0x0900, + WMI_HICCUP_CMDID = 0x0901, + WMI_FLASH_READ_CMDID = 0x0902, + WMI_FLASH_WRITE_CMDID = 0x0903, + WMI_SECURITY_UNIT_TEST_CMDID = 0x0904, + /*P2P*/ + WMI_P2P_CFG_CMDID = 0x0910, + WMI_PORT_ALLOCATE_CMDID = 0x0911, + WMI_PORT_DELETE_CMDID = 0x0912, + WMI_POWER_MGMT_CFG_CMDID = 0x0913, + WMI_START_LISTEN_CMDID = 0x0914, + WMI_START_SEARCH_CMDID = 0x0915, + WMI_DISCOVERY_START_CMDID = 0x0916, + WMI_DISCOVERY_STOP_CMDID = 0x0917, + WMI_PCP_START_CMDID = 0x0918, + WMI_PCP_STOP_CMDID = 0x0919, + WMI_GET_PCP_FACTOR_CMDID = 0x091b, + + WMI_SET_MAC_ADDRESS_CMDID = 0xf003, + WMI_ABORT_SCAN_CMDID = 0xf007, + WMI_SET_PMK_CMDID = 0xf028, + + WMI_SET_PROMISCUOUS_MODE_CMDID = 0xf041, + WMI_GET_PMK_CMDID = 0xf048, + WMI_SET_PASSPHRASE_CMDID = 0xf049, + WMI_SEND_ASSOC_RES_CMDID = 0xf04a, + WMI_SET_ASSOC_REQ_RELAY_CMDID = 0xf04b, + WMI_EAPOL_TX_CMDID = 0xf04c, + WMI_MAC_ADDR_REQ_CMDID = 0xf04d, + WMI_FW_VER_CMDID = 0xf04e, +}; + +/* + * Commands data structures + */ + +/* + * WMI_CONNECT_CMDID + */ +enum wmi_network_type { + WMI_NETTYPE_INFRA = 0x01, + WMI_NETTYPE_ADHOC = 0x02, + WMI_NETTYPE_ADHOC_CREATOR = 0x04, + WMI_NETTYPE_AP = 0x10, + WMI_NETTYPE_P2P = 0x20, + WMI_NETTYPE_WBE = 0x40, /* PCIE over 60g */ +}; + +enum wmi_dot11_auth_mode { + WMI_AUTH11_OPEN = 0x01, + WMI_AUTH11_SHARED = 0x02, + WMI_AUTH11_LEAP = 0x04, + WMI_AUTH11_WSC = 0x08, +}; + +enum wmi_auth_mode { + WMI_AUTH_NONE = 0x01, + WMI_AUTH_WPA = 0x02, + WMI_AUTH_WPA2 = 0x04, + WMI_AUTH_WPA_PSK = 0x08, + WMI_AUTH_WPA2_PSK = 0x10, + WMI_AUTH_WPA_CCKM = 0x20, + WMI_AUTH_WPA2_CCKM = 0x40, +}; + +enum wmi_crypto_type { + WMI_CRYPT_NONE = 0x01, + WMI_CRYPT_WEP = 0x02, + WMI_CRYPT_TKIP = 0x04, + WMI_CRYPT_AES = 0x08, + WMI_CRYPT_AES_GCMP = 0x20, +}; + + +enum wmi_connect_ctrl_flag_bits { + WMI_CONNECT_ASSOC_POLICY_USER = 0x0001, + WMI_CONNECT_SEND_REASSOC = 0x0002, + WMI_CONNECT_IGNORE_WPA_GROUP_CIPHER = 0x0004, + WMI_CONNECT_PROFILE_MATCH_DONE = 0x0008, + WMI_CONNECT_IGNORE_AAC_BEACON = 0x0010, + WMI_CONNECT_CSA_FOLLOW_BSS = 0x0020, + WMI_CONNECT_DO_WPA_OFFLOAD = 0x0040, + WMI_CONNECT_DO_NOT_DEAUTH = 0x0080, +}; + +#define WMI_MAX_SSID_LEN (32) + +struct wmi_connect_cmd { + u8 network_type; + u8 dot11_auth_mode; + u8 auth_mode; + u8 pairwise_crypto_type; + u8 pairwise_crypto_len; + u8 group_crypto_type; + u8 group_crypto_len; + u8 ssid_len; + u8 ssid[WMI_MAX_SSID_LEN]; + u8 channel; + u8 reserved0; + u8 bssid[WMI_MAC_LEN]; + __le32 ctrl_flags; + u8 dst_mac[WMI_MAC_LEN]; + u8 reserved1[2]; +} __packed; + +/* + * WMI_DISCONNECT_STA_CMDID + */ +struct wmi_disconnect_sta_cmd { + u8 dst_mac[WMI_MAC_LEN]; + __le16 disconnect_reason; +} __packed; + +/* + * WMI_RECONNECT_CMDID + */ +struct wmi_reconnect_cmd { + u8 channel; /* hint */ + u8 reserved; + u8 bssid[WMI_MAC_LEN]; /* mandatory if set */ +} __packed; + + +/* + * WMI_SET_PMK_CMDID + */ + +#define WMI_MIN_KEY_INDEX (0) +#define WMI_MAX_KEY_INDEX (3) +#define WMI_MAX_KEY_LEN (32) +#define WMI_PASSPHRASE_LEN (64) +#define WMI_PMK_LEN (32) + +struct wmi_set_pmk_cmd { + u8 pmk[WMI_PMK_LEN]; +} __packed; + + +/* + * WMI_SET_PASSPHRASE_CMDID + */ +struct wmi_set_passphrase_cmd { + u8 ssid[WMI_MAX_SSID_LEN]; + u8 passphrase[WMI_PASSPHRASE_LEN]; + u8 ssid_len; + u8 passphrase_len; +} __packed; + +/* + * WMI_ADD_CIPHER_KEY_CMDID + */ +enum wmi_key_usage { + WMI_KEY_USE_PAIRWISE = 0, + WMI_KEY_USE_GROUP = 1, + WMI_KEY_USE_TX = 2, /* default Tx Key - Static WEP only */ +}; + +struct wmi_add_cipher_key_cmd { + u8 key_index; + u8 key_type; + u8 key_usage; /* enum wmi_key_usage */ + u8 key_len; + u8 key_rsc[8]; /* key replay sequence counter */ + u8 key[WMI_MAX_KEY_LEN]; + u8 key_op_ctrl; /* Additional Key Control information */ + u8 mac[WMI_MAC_LEN]; +} __packed; + +/* + * WMI_DELETE_CIPHER_KEY_CMDID + */ +struct wmi_delete_cipher_key_cmd { + u8 key_index; + u8 mac[WMI_MAC_LEN]; +} __packed; + + +/* + * WMI_START_SCAN_CMDID + * + * Start L1 scan operation + * + * Returned events: + * - WMI_RX_MGMT_PACKET_EVENTID - for every probe resp. + * - WMI_SCAN_COMPLETE_EVENTID + */ +enum wmi_scan_type { + WMI_LONG_SCAN = 0, + WMI_SHORT_SCAN = 1, + WMI_PBC_SCAN = 2, +}; + +struct wmi_start_scan_cmd { + u8 reserved[8]; + + __le32 home_dwell_time; /* Max duration in the home channel(ms) */ + __le32 force_scan_interval; /* Time interval between scans (ms)*/ + u8 scan_type; /* wmi_scan_type */ + u8 num_channels; /* how many channels follow */ + struct { + u8 channel; + u8 reserved; + } channel_list[0]; /* channels ID's */ + /* 0 - 58320 MHz */ + /* 1 - 60480 MHz */ + /* 2 - 62640 MHz */ +} __packed; + +/* + * WMI_SET_PROBED_SSID_CMDID + */ +#define MAX_PROBED_SSID_INDEX (3) + +enum wmi_ssid_flag { + WMI_SSID_FLAG_DISABLE = 0, /* disables entry */ + WMI_SSID_FLAG_SPECIFIC = 1, /* probes specified ssid */ + WMI_SSID_FLAG_ANY = 2, /* probes for any ssid */ +}; + +struct wmi_probed_ssid_cmd { + u8 entry_index; /* 0 to MAX_PROBED_SSID_INDEX */ + u8 flag; /* enum wmi_ssid_flag */ + u8 ssid_len; + u8 ssid[WMI_MAX_SSID_LEN]; +} __packed; + +/* + * WMI_SET_APPIE_CMDID + * Add Application specified IE to a management frame + */ +#define WMI_MAX_IE_LEN (1024) + +/* + * Frame Types + */ +enum wmi_mgmt_frame_type { + WMI_FRAME_BEACON = 0, + WMI_FRAME_PROBE_REQ = 1, + WMI_FRAME_PROBE_RESP = 2, + WMI_FRAME_ASSOC_REQ = 3, + WMI_FRAME_ASSOC_RESP = 4, + WMI_NUM_MGMT_FRAME, +}; + +struct wmi_set_appie_cmd { + u8 mgmt_frm_type; /* enum wmi_mgmt_frame_type */ + u8 reserved; + __le16 ie_len; /* Length of the IE to be added to MGMT frame */ + u8 ie_info[0]; +} __packed; + + +/* + * WMI_PXMT_RANGE_CFG_CMDID + */ +struct wmi_pxmt_range_cfg_cmd { + u8 dst_mac[WMI_MAC_LEN]; + __le16 range; +} __packed; + +/* + * WMI_PXMT_SNR2_RANGE_CFG_CMDID + */ +struct wmi_pxmt_snr2_range_cfg_cmd { + s8 snr2range_arr[WMI_PROX_RANGE_NUM-1]; +} __packed; + +/* + * WMI_RF_MGMT_CMDID + */ +enum wmi_rf_mgmt_type { + WMI_RF_MGMT_W_DISABLE = 0, + WMI_RF_MGMT_W_ENABLE = 1, + WMI_RF_MGMT_GET_STATUS = 2, +}; + +struct wmi_rf_mgmt_cmd { + __le32 rf_mgmt_type; +} __packed; + + +/* + * WMI_RF_RX_TEST_CMDID + */ +struct wmi_rf_rx_test_cmd { + __le32 sector; +} __packed; + +/* + * WMI_CORR_MEASURE_CMDID + */ +struct wmi_corr_measure_cmd { + s32 freq_mhz; + __le32 length_samples; + __le32 iterations; +} __packed; + +/* + * WMI_SET_SSID_CMDID + */ +struct wmi_set_ssid_cmd { + __le32 ssid_len; + u8 ssid[WMI_MAX_SSID_LEN]; +} __packed; + +/* + * WMI_SET_PCP_CHANNEL_CMDID + */ +struct wmi_set_pcp_channel_cmd { + u8 channel; + u8 reserved[3]; +} __packed; + +/* + * WMI_BCON_CTRL_CMDID + */ +struct wmi_bcon_ctrl_cmd { + __le16 bcon_interval; + __le16 frag_num; + __le64 ss_mask; + u8 network_type; + u8 reserved; + u8 disable_sec_offload; + u8 disable_sec; +} __packed; + + +/******* P2P ***********/ + +/* + * WMI_PORT_ALLOCATE_CMDID + */ +enum wmi_port_role { + WMI_PORT_STA = 0, + WMI_PORT_PCP = 1, + WMI_PORT_AP = 2, + WMI_PORT_P2P_DEV = 3, + WMI_PORT_P2P_CLIENT = 4, + WMI_PORT_P2P_GO = 5, +}; + +struct wmi_port_allocate_cmd { + u8 mac[WMI_MAC_LEN]; + u8 port_role; + u8 midid; +} __packed; + +/* + * WMI_PORT_DELETE_CMDID + */ +struct wmi_delete_port_cmd { + u8 mid; + u8 reserved[3]; +} __packed; + +/* + * WMI_P2P_CFG_CMDID + */ +enum wmi_discovery_mode { + WMI_DISCOVERY_MODE_NON_OFFLOAD = 0, + WMI_DISCOVERY_MODE_OFFLOAD = 1, +}; + +struct wmi_p2p_cfg_cmd { + u8 discovery_mode; /* wmi_discovery_mode */ + u8 channel; + __le16 bcon_interval; /* base to listen/search duration calculation */ +} __packed; + +/* + * WMI_POWER_MGMT_CFG_CMDID + */ +enum wmi_power_source_type { + WMI_POWER_SOURCE_BATTERY = 0, + WMI_POWER_SOURCE_OTHER = 1, +}; + +struct wmi_power_mgmt_cfg_cmd { + u8 power_source; /* wmi_power_source_type */ + u8 reserved[3]; +} __packed; + +/* + * WMI_PCP_START_CMDID + */ +struct wmi_pcp_start_cmd { + __le16 bcon_interval; + u8 reserved0[10]; + u8 network_type; + u8 channel; + u8 disable_sec_offload; + u8 disable_sec; +} __packed; + +/* + * WMI_SW_TX_REQ_CMDID + */ +struct wmi_sw_tx_req_cmd { + u8 dst_mac[WMI_MAC_LEN]; + __le16 len; + u8 payload[0]; +} __packed; + +/* + * WMI_VRING_CFG_CMDID + */ + +struct wmi_sw_ring_cfg { + __le64 ring_mem_base; + __le16 ring_size; + __le16 max_mpdu_size; +} __packed; + +struct wmi_vring_cfg_schd { + __le16 priority; + __le16 timeslot_us; +} __packed; + +enum wmi_vring_cfg_encap_trans_type { + WMI_VRING_ENC_TYPE_802_3 = 0, + WMI_VRING_ENC_TYPE_NATIVE_WIFI = 1, +}; + +enum wmi_vring_cfg_ds_cfg { + WMI_VRING_DS_PBSS = 0, + WMI_VRING_DS_STATION = 1, + WMI_VRING_DS_AP = 2, + WMI_VRING_DS_ADDR4 = 3, +}; + +enum wmi_vring_cfg_nwifi_ds_trans_type { + WMI_NWIFI_TX_TRANS_MODE_NO = 0, + WMI_NWIFI_TX_TRANS_MODE_AP2PBSS = 1, + WMI_NWIFI_TX_TRANS_MODE_STA2PBSS = 2, +}; + +enum wmi_vring_cfg_schd_params_priority { + WMI_SCH_PRIO_REGULAR = 0, + WMI_SCH_PRIO_HIGH = 1, +}; + +#define CIDXTID_CID_POS (0) +#define CIDXTID_CID_LEN (4) +#define CIDXTID_CID_MSK (0xF) +#define CIDXTID_TID_POS (4) +#define CIDXTID_TID_LEN (4) +#define CIDXTID_TID_MSK (0xF0) + +struct wmi_vring_cfg { + struct wmi_sw_ring_cfg tx_sw_ring; + u8 ringid; /* 0-23 vrings */ + + u8 cidxtid; + + u8 encap_trans_type; + u8 ds_cfg; /* 802.3 DS cfg */ + u8 nwifi_ds_trans_type; + + #define VRING_CFG_MAC_CTRL_LIFETIME_EN_POS (0) + #define VRING_CFG_MAC_CTRL_LIFETIME_EN_LEN (1) + #define VRING_CFG_MAC_CTRL_LIFETIME_EN_MSK (0x1) + #define VRING_CFG_MAC_CTRL_AGGR_EN_POS (1) + #define VRING_CFG_MAC_CTRL_AGGR_EN_LEN (1) + #define VRING_CFG_MAC_CTRL_AGGR_EN_MSK (0x2) + u8 mac_ctrl; + + #define VRING_CFG_TO_RESOLUTION_VALUE_POS (0) + #define VRING_CFG_TO_RESOLUTION_VALUE_LEN (6) + #define VRING_CFG_TO_RESOLUTION_VALUE_MSK (0x3F) + u8 to_resolution; + u8 agg_max_wsize; + struct wmi_vring_cfg_schd schd_params; +} __packed; + +enum wmi_vring_cfg_cmd_action { + WMI_VRING_CMD_ADD = 0, + WMI_VRING_CMD_MODIFY = 1, + WMI_VRING_CMD_DELETE = 2, +}; + +struct wmi_vring_cfg_cmd { + __le32 action; + struct wmi_vring_cfg vring_cfg; +} __packed; + +/* + * WMI_VRING_BA_EN_CMDID + */ +struct wmi_vring_ba_en_cmd { + u8 ringid; + u8 agg_max_wsize; + __le16 ba_timeout; +} __packed; + +/* + * WMI_VRING_BA_DIS_CMDID + */ +struct wmi_vring_ba_dis_cmd { + u8 ringid; + u8 reserved; + __le16 reason; +} __packed; + +/* + * WMI_NOTIFY_REQ_CMDID + */ +struct wmi_notify_req_cmd { + u8 cid; + u8 year; + u8 month; + u8 day; + __le32 interval_usec; + u8 hour; + u8 minute; + u8 second; + u8 miliseconds; +} __packed; + +/* + * WMI_CFG_RX_CHAIN_CMDID + */ +enum wmi_sniffer_cfg_mode { + WMI_SNIFFER_OFF = 0, + WMI_SNIFFER_ON = 1, +}; + +enum wmi_sniffer_cfg_phy_info_mode { + WMI_SNIFFER_PHY_INFO_DISABLED = 0, + WMI_SNIFFER_PHY_INFO_ENABLED = 1, +}; + +enum wmi_sniffer_cfg_phy_support { + WMI_SNIFFER_CP = 0, + WMI_SNIFFER_DP = 1, + WMI_SNIFFER_BOTH_PHYS = 2, +}; + +struct wmi_sniffer_cfg { + __le32 mode; /* enum wmi_sniffer_cfg_mode */ + __le32 phy_info_mode; /* enum wmi_sniffer_cfg_phy_info_mode */ + __le32 phy_support; /* enum wmi_sniffer_cfg_phy_support */ + u8 channel; + u8 reserved[3]; +} __packed; + +enum wmi_cfg_rx_chain_cmd_action { + WMI_RX_CHAIN_ADD = 0, + WMI_RX_CHAIN_DEL = 1, +}; + +enum wmi_cfg_rx_chain_cmd_decap_trans_type { + WMI_DECAP_TYPE_802_3 = 0, + WMI_DECAP_TYPE_NATIVE_WIFI = 1, +}; + +enum wmi_cfg_rx_chain_cmd_nwifi_ds_trans_type { + WMI_NWIFI_RX_TRANS_MODE_NO = 0, + WMI_NWIFI_RX_TRANS_MODE_PBSS2AP = 1, + WMI_NWIFI_RX_TRANS_MODE_PBSS2STA = 2, +}; + +enum wmi_cfg_rx_chain_cmd_reorder_type { + WMI_RX_HW_REORDER = 0, + WMI_RX_SW_REORDER = 1, +}; + +struct wmi_cfg_rx_chain_cmd { + __le32 action; + struct wmi_sw_ring_cfg rx_sw_ring; + u8 mid; + u8 decap_trans_type; + + #define L2_802_3_OFFLOAD_CTRL_VLAN_TAG_INSERTION_POS (0) + #define L2_802_3_OFFLOAD_CTRL_VLAN_TAG_INSERTION_LEN (1) + #define L2_802_3_OFFLOAD_CTRL_VLAN_TAG_INSERTION_MSK (0x1) + u8 l2_802_3_offload_ctrl; + + #define L2_NWIFI_OFFLOAD_CTRL_REMOVE_QOS_POS (0) + #define L2_NWIFI_OFFLOAD_CTRL_REMOVE_QOS_LEN (1) + #define L2_NWIFI_OFFLOAD_CTRL_REMOVE_QOS_MSK (0x1) + #define L2_NWIFI_OFFLOAD_CTRL_REMOVE_PN_POS (1) + #define L2_NWIFI_OFFLOAD_CTRL_REMOVE_PN_LEN (1) + #define L2_NWIFI_OFFLOAD_CTRL_REMOVE_PN_MSK (0x2) + u8 l2_nwifi_offload_ctrl; + + u8 vlan_id; + u8 nwifi_ds_trans_type; + + #define L3_L4_CTRL_IPV4_CHECKSUM_EN_POS (0) + #define L3_L4_CTRL_IPV4_CHECKSUM_EN_LEN (1) + #define L3_L4_CTRL_IPV4_CHECKSUM_EN_MSK (0x1) + #define L3_L4_CTRL_TCPIP_CHECKSUM_EN_POS (1) + #define L3_L4_CTRL_TCPIP_CHECKSUM_EN_LEN (1) + #define L3_L4_CTRL_TCPIP_CHECKSUM_EN_MSK (0x2) + u8 l3_l4_ctrl; + + #define RING_CTRL_OVERRIDE_PREFETCH_THRSH_POS (0) + #define RING_CTRL_OVERRIDE_PREFETCH_THRSH_LEN (1) + #define RING_CTRL_OVERRIDE_PREFETCH_THRSH_MSK (0x1) + #define RING_CTRL_OVERRIDE_WB_THRSH_POS (1) + #define RING_CTRL_OVERRIDE_WB_THRSH_LEN (1) + #define RING_CTRL_OVERRIDE_WB_THRSH_MSK (0x2) + #define RING_CTRL_OVERRIDE_ITR_THRSH_POS (2) + #define RING_CTRL_OVERRIDE_ITR_THRSH_LEN (1) + #define RING_CTRL_OVERRIDE_ITR_THRSH_MSK (0x4) + #define RING_CTRL_OVERRIDE_HOST_THRSH_POS (3) + #define RING_CTRL_OVERRIDE_HOST_THRSH_LEN (1) + #define RING_CTRL_OVERRIDE_HOST_THRSH_MSK (0x8) + u8 ring_ctrl; + + __le16 prefetch_thrsh; + __le16 wb_thrsh; + __le32 itr_value; + __le16 host_thrsh; + u8 reorder_type; + u8 reserved; + struct wmi_sniffer_cfg sniffer_cfg; +} __packed; + +/* + * WMI_RCP_ADDBA_RESP_CMDID + */ +struct wmi_rcp_addba_resp_cmd { + u8 cidxtid; + u8 dialog_token; + __le16 status_code; + __le16 ba_param_set; /* ieee80211_ba_parameterset field to send */ + __le16 ba_timeout; +} __packed; + +/* + * WMI_RCP_DELBA_CMDID + */ +struct wmi_rcp_delba_cmd { + u8 cidxtid; + u8 reserved; + __le16 reason; +} __packed; + +/* + * WMI_RCP_ADDBA_REQ_CMDID + */ +struct wmi_rcp_addba_req_cmd { + u8 cidxtid; + u8 dialog_token; + /* ieee80211_ba_parameterset field as it received */ + __le16 ba_param_set; + __le16 ba_timeout; + /* ieee80211_ba_seqstrl field as it received */ + __le16 ba_seq_ctrl; +} __packed; + +/* + * WMI_SET_MAC_ADDRESS_CMDID + */ +struct wmi_set_mac_address_cmd { + u8 mac[WMI_MAC_LEN]; + u8 reserved[2]; +} __packed; + +/* +* WMI_EAPOL_TX_CMDID +*/ +struct wmi_eapol_tx_cmd { + u8 dst_mac[WMI_MAC_LEN]; + __le16 eapol_len; + u8 eapol[0]; +} __packed; + +/* + * WMI_ECHO_CMDID + * + * Check FW is alive + * + * WMI_DEEP_ECHO_CMDID + * + * Check FW and ucode are alive + * + * Returned event: WMI_ECHO_RSP_EVENTID + * same event for both commands + */ +struct wmi_echo_cmd { + __le32 value; +} __packed; + +/* + * WMI_TEMP_SENSE_CMDID + * + * Measure MAC and radio temperatures + */ +struct wmi_temp_sense_cmd { + __le32 measure_marlon_m_en; + __le32 measure_marlon_r_en; +} __packed; + + +/* + * WMI Events + */ + +/* + * List of Events (target to host) + */ +enum wmi_event_id { + WMI_READY_EVENTID = 0x1001, + WMI_CONNECT_EVENTID = 0x1002, + WMI_DISCONNECT_EVENTID = 0x1003, + WMI_SCAN_COMPLETE_EVENTID = 0x100a, + WMI_REPORT_STATISTICS_EVENTID = 0x100b, + WMI_RD_MEM_RSP_EVENTID = 0x1800, + WMI_FW_READY_EVENTID = 0x1801, + WMI_EXIT_FAST_MEM_ACC_MODE_EVENTID = 0x0200, + WMI_ECHO_RSP_EVENTID = 0x1803, + WMI_FS_TUNE_DONE_EVENTID = 0x180a, + WMI_CORR_MEASURE_EVENTID = 0x180b, + WMI_READ_RSSI_EVENTID = 0x180c, + WMI_TEMP_SENSE_DONE_EVENTID = 0x180e, + WMI_DC_CALIB_DONE_EVENTID = 0x180f, + WMI_IQ_TX_CALIB_DONE_EVENTID = 0x1811, + WMI_IQ_RX_CALIB_DONE_EVENTID = 0x1812, + WMI_SET_WORK_MODE_DONE_EVENTID = 0x1815, + WMI_LO_LEAKAGE_CALIB_DONE_EVENTID = 0x1816, + WMI_MARLON_R_ACTIVATE_DONE_EVENTID = 0x1817, + WMI_MARLON_R_READ_DONE_EVENTID = 0x1818, + WMI_MARLON_R_WRITE_DONE_EVENTID = 0x1819, + WMI_MARLON_R_TXRX_SEL_DONE_EVENTID = 0x181a, + WMI_SILENT_RSSI_CALIB_DONE_EVENTID = 0x181d, + WMI_RF_RX_TEST_DONE_EVENTID = 0x181e, + WMI_CFG_RX_CHAIN_DONE_EVENTID = 0x1820, + WMI_VRING_CFG_DONE_EVENTID = 0x1821, + WMI_BA_STATUS_EVENTID = 0x1823, + WMI_RCP_ADDBA_REQ_EVENTID = 0x1824, + WMI_ADDBA_RESP_SENT_EVENTID = 0x1825, + WMI_DELBA_EVENTID = 0x1826, + WMI_GET_SSID_EVENTID = 0x1828, + WMI_GET_PCP_CHANNEL_EVENTID = 0x182a, + WMI_SW_TX_COMPLETE_EVENTID = 0x182b, + + WMI_READ_MAC_RXQ_EVENTID = 0x1830, + WMI_READ_MAC_TXQ_EVENTID = 0x1831, + WMI_WRITE_MAC_RXQ_EVENTID = 0x1832, + WMI_WRITE_MAC_TXQ_EVENTID = 0x1833, + WMI_WRITE_MAC_XQ_FIELD_EVENTID = 0x1834, + + WMI_BEAFORMING_MGMT_DONE_EVENTID = 0x1836, + WMI_BF_TXSS_MGMT_DONE_EVENTID = 0x1837, + WMI_BF_RXSS_MGMT_DONE_EVENTID = 0x1839, + WMI_RS_MGMT_DONE_EVENTID = 0x1852, + WMI_RF_MGMT_STATUS_EVENTID = 0x1853, + WMI_BF_SM_MGMT_DONE_EVENTID = 0x1838, + WMI_RX_MGMT_PACKET_EVENTID = 0x1840, + + /* Performance monitoring events */ + WMI_DATA_PORT_OPEN_EVENTID = 0x1860, + WMI_WBE_LINKDOWN_EVENTID = 0x1861, + + WMI_BF_CTRL_DONE_EVENTID = 0x1862, + WMI_NOTIFY_REQ_DONE_EVENTID = 0x1863, + WMI_GET_STATUS_DONE_EVENTID = 0x1864, + + WMI_UNIT_TEST_EVENTID = 0x1900, + WMI_FLASH_READ_DONE_EVENTID = 0x1902, + WMI_FLASH_WRITE_DONE_EVENTID = 0x1903, + /*P2P*/ + WMI_PORT_ALLOCATED_EVENTID = 0x1911, + WMI_PORT_DELETED_EVENTID = 0x1912, + WMI_LISTEN_STARTED_EVENTID = 0x1914, + WMI_SEARCH_STARTED_EVENTID = 0x1915, + WMI_DISCOVERY_STARTED_EVENTID = 0x1916, + WMI_DISCOVERY_STOPPED_EVENTID = 0x1917, + WMI_PCP_STARTED_EVENTID = 0x1918, + WMI_PCP_STOPPED_EVENTID = 0x1919, + WMI_PCP_FACTOR_EVENTID = 0x191a, + WMI_SET_CHANNEL_EVENTID = 0x9000, + WMI_ASSOC_REQ_EVENTID = 0x9001, + WMI_EAPOL_RX_EVENTID = 0x9002, + WMI_MAC_ADDR_RESP_EVENTID = 0x9003, + WMI_FW_VER_EVENTID = 0x9004, +}; + +/* + * Events data structures + */ + + +enum wmi_fw_status { + WMI_FW_STATUS_SUCCESS, + WMI_FW_STATUS_FAILURE, +}; + +/* + * WMI_RF_MGMT_STATUS_EVENTID + */ +enum wmi_rf_status { + WMI_RF_ENABLED = 0, + WMI_RF_DISABLED_HW = 1, + WMI_RF_DISABLED_SW = 2, + WMI_RF_DISABLED_HW_SW = 3, +}; + +struct wmi_rf_mgmt_status_event { + __le32 rf_status; +} __packed; + +/* + * WMI_GET_STATUS_DONE_EVENTID + */ +struct wmi_get_status_done_event { + __le32 is_associated; + u8 cid; + u8 reserved0[3]; + u8 bssid[WMI_MAC_LEN]; + u8 channel; + u8 reserved1; + u8 network_type; + u8 reserved2[3]; + __le32 ssid_len; + u8 ssid[WMI_MAX_SSID_LEN]; + __le32 rf_status; + __le32 is_secured; +} __packed; + +/* + * WMI_FW_VER_EVENTID + */ +struct wmi_fw_ver_event { + u8 major; + u8 minor; + __le16 subminor; + __le16 build; +} __packed; + +/* +* WMI_MAC_ADDR_RESP_EVENTID +*/ +struct wmi_mac_addr_resp_event { + u8 mac[WMI_MAC_LEN]; + u8 auth_mode; + u8 crypt_mode; + __le32 offload_mode; +} __packed; + +/* +* WMI_EAPOL_RX_EVENTID +*/ +struct wmi_eapol_rx_event { + u8 src_mac[WMI_MAC_LEN]; + __le16 eapol_len; + u8 eapol[0]; +} __packed; + +/* +* WMI_READY_EVENTID +*/ +enum wmi_phy_capability { + WMI_11A_CAPABILITY = 1, + WMI_11G_CAPABILITY = 2, + WMI_11AG_CAPABILITY = 3, + WMI_11NA_CAPABILITY = 4, + WMI_11NG_CAPABILITY = 5, + WMI_11NAG_CAPABILITY = 6, + WMI_11AD_CAPABILITY = 7, + WMI_11N_CAPABILITY_OFFSET = WMI_11NA_CAPABILITY - WMI_11A_CAPABILITY, +}; + +struct wmi_ready_event { + __le32 sw_version; + __le32 abi_version; + u8 mac[WMI_MAC_LEN]; + u8 phy_capability; /* enum wmi_phy_capability */ + u8 numof_additional_mids; +} __packed; + +/* + * WMI_NOTIFY_REQ_DONE_EVENTID + */ +struct wmi_notify_req_done_event { + __le32 status; + __le64 tsf; + __le32 snr_val; + __le32 tx_tpt; + __le32 tx_goodput; + __le32 rx_goodput; + __le16 bf_mcs; + __le16 my_rx_sector; + __le16 my_tx_sector; + __le16 other_rx_sector; + __le16 other_tx_sector; + __le16 range; + u8 sqi; + u8 reserved[3]; +} __packed; + +/* + * WMI_CONNECT_EVENTID + */ +struct wmi_connect_event { + u8 channel; + u8 reserved0; + u8 bssid[WMI_MAC_LEN]; + __le16 listen_interval; + __le16 beacon_interval; + u8 network_type; + u8 reserved1[3]; + u8 beacon_ie_len; + u8 assoc_req_len; + u8 assoc_resp_len; + u8 cid; + u8 reserved2[3]; + u8 assoc_info[0]; +} __packed; + +/* + * WMI_DISCONNECT_EVENTID + */ +enum wmi_disconnect_reason { + WMI_DIS_REASON_NO_NETWORK_AVAIL = 1, + WMI_DIS_REASON_LOST_LINK = 2, /* bmiss */ + WMI_DIS_REASON_DISCONNECT_CMD = 3, + WMI_DIS_REASON_BSS_DISCONNECTED = 4, + WMI_DIS_REASON_AUTH_FAILED = 5, + WMI_DIS_REASON_ASSOC_FAILED = 6, + WMI_DIS_REASON_NO_RESOURCES_AVAIL = 7, + WMI_DIS_REASON_CSERV_DISCONNECT = 8, + WMI_DIS_REASON_INVALID_PROFILE = 10, + WMI_DIS_REASON_DOT11H_CHANNEL_SWITCH = 11, + WMI_DIS_REASON_PROFILE_MISMATCH = 12, + WMI_DIS_REASON_CONNECTION_EVICTED = 13, + WMI_DIS_REASON_IBSS_MERGE = 14, +}; + +struct wmi_disconnect_event { + __le16 protocol_reason_status; /* reason code, see 802.11 spec. */ + u8 bssid[WMI_MAC_LEN]; /* set if known */ + u8 disconnect_reason; /* see wmi_disconnect_reason_e */ + u8 assoc_resp_len; + u8 assoc_info[0]; +} __packed; + +/* + * WMI_SCAN_COMPLETE_EVENTID + */ +struct wmi_scan_complete_event { + __le32 status; +} __packed; + +/* + * WMI_BA_STATUS_EVENTID + */ +enum wmi_vring_ba_status { + WMI_BA_AGREED = 0, + WMI_BA_NON_AGREED = 1, +}; + +struct wmi_vring_ba_status_event { + __le16 status; + u8 reserved[2]; + u8 ringid; + u8 agg_wsize; + __le16 ba_timeout; +} __packed; + +/* + * WMI_DELBA_EVENTID + */ +struct wmi_delba_event { + u8 cidxtid; + u8 from_initiator; + __le16 reason; +} __packed; + + +/* + * WMI_VRING_CFG_DONE_EVENTID + */ +struct wmi_vring_cfg_done_event { + u8 ringid; + u8 status; + u8 reserved[2]; + __le32 tx_vring_tail_ptr; +} __packed; + +/* + * WMI_ADDBA_RESP_SENT_EVENTID + */ +struct wmi_rcp_addba_resp_sent_event { + u8 cidxtid; + u8 reserved; + __le16 status; +} __packed; + +/* + * WMI_RCP_ADDBA_REQ_EVENTID + */ +struct wmi_rcp_addba_req_event { + u8 cidxtid; + u8 dialog_token; + __le16 ba_param_set; /* ieee80211_ba_parameterset as it received */ + __le16 ba_timeout; + __le16 ba_seq_ctrl; /* ieee80211_ba_seqstrl field as it received */ +} __packed; + +/* + * WMI_CFG_RX_CHAIN_DONE_EVENTID + */ +enum wmi_cfg_rx_chain_done_event_status { + WMI_CFG_RX_CHAIN_SUCCESS = 1, +}; + +struct wmi_cfg_rx_chain_done_event { + __le32 rx_ring_tail_ptr; /* Rx V-Ring Tail pointer */ + __le32 status; +} __packed; + +/* + * WMI_WBE_LINKDOWN_EVENTID + */ +enum wmi_wbe_link_down_event_reason { + WMI_WBE_REASON_USER_REQUEST = 0, + WMI_WBE_REASON_RX_DISASSOC = 1, + WMI_WBE_REASON_BAD_PHY_LINK = 2, +}; + +struct wmi_wbe_link_down_event { + u8 cid; + u8 reserved[3]; + __le32 reason; +} __packed; + +/* + * WMI_DATA_PORT_OPEN_EVENTID + */ +struct wmi_data_port_open_event { + u8 cid; + u8 reserved[3]; +} __packed; + + +/* + * WMI_GET_PCP_CHANNEL_EVENTID + */ +struct wmi_get_pcp_channel_event { + u8 channel; + u8 reserved[3]; +} __packed; + + +/* +* WMI_PORT_ALLOCATED_EVENTID +*/ +struct wmi_port_allocated_event { + u8 status; /* wmi_fw_status */ + u8 reserved[3]; +} __packed; + +/* +* WMI_PORT_DELETED_EVENTID +*/ +struct wmi_port_deleted_event { + u8 status; /* wmi_fw_status */ + u8 reserved[3]; +} __packed; + +/* + * WMI_LISTEN_STARTED_EVENTID + */ +struct wmi_listen_started_event { + u8 status; /* wmi_fw_status */ + u8 reserved[3]; +} __packed; + +/* + * WMI_SEARCH_STARTED_EVENTID + */ +struct wmi_search_started_event { + u8 status; /* wmi_fw_status */ + u8 reserved[3]; +} __packed; + +/* + * WMI_PCP_STARTED_EVENTID + */ +struct wmi_pcp_started_event { + u8 status; /* wmi_fw_status */ + u8 reserved[3]; +} __packed; + +/* + * WMI_PCP_FACTOR_EVENTID + */ +struct wmi_pcp_factor_event { + __le32 pcp_factor; +} __packed; + +/* + * WMI_SW_TX_COMPLETE_EVENTID + */ +enum wmi_sw_tx_status { + WMI_TX_SW_STATUS_SUCCESS = 0, + WMI_TX_SW_STATUS_FAILED_NO_RESOURCES = 1, + WMI_TX_SW_STATUS_FAILED_TX = 2, +}; + +struct wmi_sw_tx_complete_event { + u8 status; /* enum wmi_sw_tx_status */ + u8 reserved[3]; +} __packed; + +/* + * WMI_CORR_MEASURE_EVENTID + */ +struct wmi_corr_measure_event { + s32 i; + s32 q; + s32 image_i; + s32 image_q; +} __packed; + +/* + * WMI_READ_RSSI_EVENTID + */ +struct wmi_read_rssi_event { + __le32 ina_rssi_adc_dbm; +} __packed; + +/* + * WMI_GET_SSID_EVENTID + */ +struct wmi_get_ssid_event { + __le32 ssid_len; + u8 ssid[WMI_MAX_SSID_LEN]; +} __packed; + +/* + * WMI_RX_MGMT_PACKET_EVENTID + */ +struct wmi_rx_mgmt_info { + u8 mcs; + s8 snr; + u8 range; + u8 sqi; + __le16 stype; + __le16 status; + __le32 len; + u8 qid; + u8 mid; + u8 cid; + u8 channel; /* From Radio MNGR */ +} __packed; + +struct wmi_rx_mgmt_packet_event { + struct wmi_rx_mgmt_info info; + u8 payload[0]; +} __packed; + +/* + * WMI_ECHO_RSP_EVENTID + */ +struct wmi_echo_event { + __le32 echoed_value; +} __packed; + +/* + * WMI_TEMP_SENSE_DONE_EVENTID + * + * Measure MAC and radio temperatures + */ +struct wmi_temp_sense_done_event { + __le32 marlon_m_t1000; + __le32 marlon_r_t1000; +} __packed; + +#endif /* __WILOCITY_WMI_H__ */ diff --git a/drivers/net/wireless/b43/Kconfig b/drivers/net/wireless/b43/Kconfig new file mode 100644 index 0000000..fc89e06 --- /dev/null +++ b/drivers/net/wireless/b43/Kconfig @@ -0,0 +1,178 @@ +config B43 + tristate "Broadcom 43xx wireless support (mac80211 stack)" + depends on m + depends on SSB_POSSIBLE && MAC80211 && HAS_DMA + select SSB + select BACKPORT_FW_LOADER + ---help--- + b43 is a driver for the Broadcom 43xx series wireless devices. + + Check "lspci" for something like + "Broadcom Corporation BCM43XX 802.11 Wireless LAN Controller" + to determine whether you own such a device. + + This driver supports the new BCM43xx IEEE 802.11G devices, but not + the old IEEE 802.11B devices. Old devices are supported by + the b43legacy driver. + Note that this has nothing to do with the standard that your AccessPoint + supports (A, B, G or a combination). + IEEE 802.11G devices can talk to IEEE 802.11B AccessPoints. + + It is safe to include both b43 and b43legacy as the underlying glue + layer will automatically load the correct version for your device. + + This driver uses V4 firmware, which must be installed separately using + b43-fwcutter. + + This driver can be built as a module (recommended) that will be called "b43". + If unsure, say M. + +config B43_BCMA + bool "Support for BCMA bus" + depends on B43 && (BCMA = y || BCMA = B43) + default y + +config B43_BCMA_EXTRA + bool "Hardware support that overlaps with the brcmsmac driver" + depends on B43_BCMA + default n if BRCMSMAC + default y + +config B43_SSB + bool + depends on B43 && (SSB = y || SSB = B43) + default y + +# Auto-select SSB PCI-HOST support, if possible +config B43_PCI_AUTOSELECT + bool + depends on B43 && SSB_PCIHOST_POSSIBLE + select SSB_PCIHOST + select SSB_B43_PCI_BRIDGE + default y + +# Auto-select SSB PCICORE driver, if possible +config B43_PCICORE_AUTOSELECT + bool + depends on B43 && SSB_DRIVER_PCICORE_POSSIBLE + select SSB_DRIVER_PCICORE + default y + +config B43_PCMCIA + bool "Broadcom 43xx PCMCIA device support" + depends on B43 && SSB_PCMCIAHOST_POSSIBLE + select SSB_PCMCIAHOST + ---help--- + Broadcom 43xx PCMCIA device support. + + Support for 16bit PCMCIA devices. + Please note that most PC-CARD devices are _NOT_ 16bit PCMCIA + devices, but 32bit CardBUS devices. CardBUS devices are supported + out of the box by b43. + + With this config option you can drive b43 cards in + CompactFlash formfactor in a PCMCIA adaptor. + CF b43 cards can sometimes be found in handheld PCs. + + It's safe to select Y here, even if you don't have a B43 PCMCIA device. + + If unsure, say N. + +config B43_SDIO + bool "Broadcom 43xx SDIO device support" + depends on B43 && SSB_SDIOHOST_POSSIBLE + select SSB_SDIOHOST + ---help--- + Broadcom 43xx device support for Soft-MAC SDIO devices. + + With this config option you can drive Soft-MAC b43 cards with a + Secure Digital I/O interface. + This includes the WLAN daughter card found on the Nintendo Wii + video game console. + Note that this does not support Broadcom 43xx Full-MAC devices. + + It's safe to select Y here, even if you don't have a B43 SDIO device. + + If unsure, say N. + +#Data transfers to the device via PIO. We want it as a fallback even +# if we can do DMA. +config B43_BCMA_PIO + bool + depends on B43_BCMA + select BCMA_BLOCKIO + default y + +config B43_PIO + bool + depends on B43 + select SSB_BLOCKIO + default y + +config B43_PHY_N + bool "Support for 802.11n (N-PHY) devices" + depends on B43 + ---help--- + Support for the N-PHY. + + This enables support for devices with N-PHY. + + Say N if you expect high stability and performance. Saying Y will not + affect other devices support and may provide support for basic needs. + +config B43_PHY_LP + bool "Support for low-power (LP-PHY) devices" + depends on B43 + default y + ---help--- + Support for the LP-PHY. + The LP-PHY is a low-power PHY built into some notebooks + and embedded devices. It supports 802.11a/b/g + (802.11a support is optional, and currently disabled). + +config B43_PHY_HT + bool "Support for HT-PHY (high throughput) devices" + depends on B43 && B43_BCMA + ---help--- + Support for the HT-PHY. + + Enables support for BCM4331 and possibly other chipsets with that PHY. + +config B43_PHY_LCN + bool "Support for LCN-PHY devices (BROKEN)" + depends on B43 && BROKEN + ---help--- + Support for the LCN-PHY. + + Say N, this is BROKEN and crashes driver. + +# This config option automatically enables b43 LEDS support, +# if it's possible. +config B43_LEDS + bool + depends on B43 && MAC80211_LEDS && (LEDS_CLASS = y || LEDS_CLASS = B43) + default y + +# This config option automatically enables b43 HW-RNG support, +# if the HW-RNG core is enabled. +config B43_HWRNG + bool + depends on B43 && (HW_RANDOM = y || HW_RANDOM = B43) + default y + +config B43_DEBUG + bool "Broadcom 43xx debugging" + depends on B43 + ---help--- + Broadcom 43xx debugging. + + This adds additional runtime sanity checks and statistics to the driver. + These checks and statistics might be expensive and hurt the runtime + performance of your system. + This also adds the b43 debugfs interface. + + Do not enable this, unless you are debugging the driver. + + Say N, if you are a distributor or user building a release kernel + for production use. + Only say Y, if you are debugging a problem in the b43 driver sourcecode. diff --git a/drivers/net/wireless/b43/Makefile b/drivers/net/wireless/b43/Makefile new file mode 100644 index 0000000..83be7db --- /dev/null +++ b/drivers/net/wireless/b43/Makefile @@ -0,0 +1,30 @@ +b43-y += main.o +b43-y += bus.o +b43-y += tables.o +b43-$(CPTCFG_B43_PHY_N) += tables_nphy.o +b43-$(CPTCFG_B43_PHY_N) += radio_2055.o +b43-$(CPTCFG_B43_PHY_N) += radio_2056.o +b43-$(CPTCFG_B43_PHY_N) += radio_2057.o +b43-y += phy_common.o +b43-y += phy_g.o +b43-y += phy_a.o +b43-$(CPTCFG_B43_PHY_N) += phy_n.o +b43-$(CPTCFG_B43_PHY_LP) += phy_lp.o +b43-$(CPTCFG_B43_PHY_LP) += tables_lpphy.o +b43-$(CPTCFG_B43_PHY_HT) += phy_ht.o +b43-$(CPTCFG_B43_PHY_HT) += tables_phy_ht.o +b43-$(CPTCFG_B43_PHY_HT) += radio_2059.o +b43-$(CPTCFG_B43_PHY_LCN) += phy_lcn.o tables_phy_lcn.o +b43-y += sysfs.o +b43-y += xmit.o +b43-y += lo.o +b43-y += wa.o +b43-y += dma.o +b43-y += pio.o +b43-y += rfkill.o +b43-$(CPTCFG_B43_LEDS) += leds.o +b43-$(CPTCFG_B43_PCMCIA) += pcmcia.o +b43-$(CPTCFG_B43_SDIO) += sdio.o +b43-$(CPTCFG_B43_DEBUG) += debugfs.o + +obj-$(CPTCFG_B43) += b43.o diff --git a/drivers/net/wireless/b43/b43.h b/drivers/net/wireless/b43/b43.h new file mode 100644 index 0000000..9718cee --- /dev/null +++ b/drivers/net/wireless/b43/b43.h @@ -0,0 +1,1085 @@ +#ifndef B43_H_ +#define B43_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "debugfs.h" +#include "leds.h" +#include "rfkill.h" +#include "bus.h" +#include "lo.h" +#include "phy_common.h" + + +#ifdef CPTCFG_B43_DEBUG +# define B43_DEBUG 1 +#else +# define B43_DEBUG 0 +#endif + +/* MMIO offsets */ +#define B43_MMIO_DMA0_REASON 0x20 +#define B43_MMIO_DMA0_IRQ_MASK 0x24 +#define B43_MMIO_DMA1_REASON 0x28 +#define B43_MMIO_DMA1_IRQ_MASK 0x2C +#define B43_MMIO_DMA2_REASON 0x30 +#define B43_MMIO_DMA2_IRQ_MASK 0x34 +#define B43_MMIO_DMA3_REASON 0x38 +#define B43_MMIO_DMA3_IRQ_MASK 0x3C +#define B43_MMIO_DMA4_REASON 0x40 +#define B43_MMIO_DMA4_IRQ_MASK 0x44 +#define B43_MMIO_DMA5_REASON 0x48 +#define B43_MMIO_DMA5_IRQ_MASK 0x4C +#define B43_MMIO_MACCTL 0x120 /* MAC control */ +#define B43_MMIO_MACCMD 0x124 /* MAC command */ +#define B43_MMIO_GEN_IRQ_REASON 0x128 +#define B43_MMIO_GEN_IRQ_MASK 0x12C +#define B43_MMIO_RAM_CONTROL 0x130 +#define B43_MMIO_RAM_DATA 0x134 +#define B43_MMIO_PS_STATUS 0x140 +#define B43_MMIO_RADIO_HWENABLED_HI 0x158 +#define B43_MMIO_SHM_CONTROL 0x160 +#define B43_MMIO_SHM_DATA 0x164 +#define B43_MMIO_SHM_DATA_UNALIGNED 0x166 +#define B43_MMIO_XMITSTAT_0 0x170 +#define B43_MMIO_XMITSTAT_1 0x174 +#define B43_MMIO_REV3PLUS_TSF_LOW 0x180 /* core rev >= 3 only */ +#define B43_MMIO_REV3PLUS_TSF_HIGH 0x184 /* core rev >= 3 only */ +#define B43_MMIO_TSF_CFP_REP 0x188 +#define B43_MMIO_TSF_CFP_START 0x18C +#define B43_MMIO_TSF_CFP_MAXDUR 0x190 + +/* 32-bit DMA */ +#define B43_MMIO_DMA32_BASE0 0x200 +#define B43_MMIO_DMA32_BASE1 0x220 +#define B43_MMIO_DMA32_BASE2 0x240 +#define B43_MMIO_DMA32_BASE3 0x260 +#define B43_MMIO_DMA32_BASE4 0x280 +#define B43_MMIO_DMA32_BASE5 0x2A0 +/* 64-bit DMA */ +#define B43_MMIO_DMA64_BASE0 0x200 +#define B43_MMIO_DMA64_BASE1 0x240 +#define B43_MMIO_DMA64_BASE2 0x280 +#define B43_MMIO_DMA64_BASE3 0x2C0 +#define B43_MMIO_DMA64_BASE4 0x300 +#define B43_MMIO_DMA64_BASE5 0x340 + +/* PIO on core rev < 11 */ +#define B43_MMIO_PIO_BASE0 0x300 +#define B43_MMIO_PIO_BASE1 0x310 +#define B43_MMIO_PIO_BASE2 0x320 +#define B43_MMIO_PIO_BASE3 0x330 +#define B43_MMIO_PIO_BASE4 0x340 +#define B43_MMIO_PIO_BASE5 0x350 +#define B43_MMIO_PIO_BASE6 0x360 +#define B43_MMIO_PIO_BASE7 0x370 +/* PIO on core rev >= 11 */ +#define B43_MMIO_PIO11_BASE0 0x200 +#define B43_MMIO_PIO11_BASE1 0x240 +#define B43_MMIO_PIO11_BASE2 0x280 +#define B43_MMIO_PIO11_BASE3 0x2C0 +#define B43_MMIO_PIO11_BASE4 0x300 +#define B43_MMIO_PIO11_BASE5 0x340 + +#define B43_MMIO_RADIO24_CONTROL 0x3D8 /* core rev >= 24 only */ +#define B43_MMIO_RADIO24_DATA 0x3DA /* core rev >= 24 only */ +#define B43_MMIO_PHY_VER 0x3E0 +#define B43_MMIO_PHY_RADIO 0x3E2 +#define B43_MMIO_PHY0 0x3E6 +#define B43_MMIO_ANTENNA 0x3E8 +#define B43_MMIO_CHANNEL 0x3F0 +#define B43_MMIO_CHANNEL_EXT 0x3F4 +#define B43_MMIO_RADIO_CONTROL 0x3F6 +#define B43_MMIO_RADIO_DATA_HIGH 0x3F8 +#define B43_MMIO_RADIO_DATA_LOW 0x3FA +#define B43_MMIO_PHY_CONTROL 0x3FC +#define B43_MMIO_PHY_DATA 0x3FE +#define B43_MMIO_MACFILTER_CONTROL 0x420 +#define B43_MMIO_MACFILTER_DATA 0x422 +#define B43_MMIO_RCMTA_COUNT 0x43C +#define B43_MMIO_PSM_PHY_HDR 0x492 +#define B43_MMIO_RADIO_HWENABLED_LO 0x49A +#define B43_MMIO_GPIO_CONTROL 0x49C +#define B43_MMIO_GPIO_MASK 0x49E +#define B43_MMIO_TXE0_CTL 0x500 +#define B43_MMIO_TXE0_AUX 0x502 +#define B43_MMIO_TXE0_TS_LOC 0x504 +#define B43_MMIO_TXE0_TIME_OUT 0x506 +#define B43_MMIO_TXE0_WM_0 0x508 +#define B43_MMIO_TXE0_WM_1 0x50A +#define B43_MMIO_TXE0_PHYCTL 0x50C +#define B43_MMIO_TXE0_STATUS 0x50E +#define B43_MMIO_TXE0_MMPLCP0 0x510 +#define B43_MMIO_TXE0_MMPLCP1 0x512 +#define B43_MMIO_TXE0_PHYCTL1 0x514 +#define B43_MMIO_XMTFIFODEF 0x520 +#define B43_MMIO_XMTFIFO_FRAME_CNT 0x522 /* core rev>= 16 only */ +#define B43_MMIO_XMTFIFO_BYTE_CNT 0x524 /* core rev>= 16 only */ +#define B43_MMIO_XMTFIFO_HEAD 0x526 /* core rev>= 16 only */ +#define B43_MMIO_XMTFIFO_RD_PTR 0x528 /* core rev>= 16 only */ +#define B43_MMIO_XMTFIFO_WR_PTR 0x52A /* core rev>= 16 only */ +#define B43_MMIO_XMTFIFODEF1 0x52C /* core rev>= 16 only */ +#define B43_MMIO_XMTFIFOCMD 0x540 +#define B43_MMIO_XMTFIFOFLUSH 0x542 +#define B43_MMIO_XMTFIFOTHRESH 0x544 +#define B43_MMIO_XMTFIFORDY 0x546 +#define B43_MMIO_XMTFIFOPRIRDY 0x548 +#define B43_MMIO_XMTFIFORQPRI 0x54A +#define B43_MMIO_XMTTPLATETXPTR 0x54C +#define B43_MMIO_XMTTPLATEPTR 0x550 +#define B43_MMIO_SMPL_CLCT_STRPTR 0x552 /* core rev>= 22 only */ +#define B43_MMIO_SMPL_CLCT_STPPTR 0x554 /* core rev>= 22 only */ +#define B43_MMIO_SMPL_CLCT_CURPTR 0x556 /* core rev>= 22 only */ +#define B43_MMIO_XMTTPLATEDATALO 0x560 +#define B43_MMIO_XMTTPLATEDATAHI 0x562 +#define B43_MMIO_XMTSEL 0x568 +#define B43_MMIO_XMTTXCNT 0x56A +#define B43_MMIO_XMTTXSHMADDR 0x56C +#define B43_MMIO_TSF_CFP_START_LOW 0x604 +#define B43_MMIO_TSF_CFP_START_HIGH 0x606 +#define B43_MMIO_TSF_CFP_PRETBTT 0x612 +#define B43_MMIO_TSF_CLK_FRAC_LOW 0x62E +#define B43_MMIO_TSF_CLK_FRAC_HIGH 0x630 +#define B43_MMIO_TSF_0 0x632 /* core rev < 3 only */ +#define B43_MMIO_TSF_1 0x634 /* core rev < 3 only */ +#define B43_MMIO_TSF_2 0x636 /* core rev < 3 only */ +#define B43_MMIO_TSF_3 0x638 /* core rev < 3 only */ +#define B43_MMIO_RNG 0x65A +#define B43_MMIO_IFSSLOT 0x684 /* Interframe slot time */ +#define B43_MMIO_IFSCTL 0x688 /* Interframe space control */ +#define B43_MMIO_IFSSTAT 0x690 +#define B43_MMIO_IFSMEDBUSYCTL 0x692 +#define B43_MMIO_IFTXDUR 0x694 +#define B43_MMIO_IFSCTL_USE_EDCF 0x0004 +#define B43_MMIO_POWERUP_DELAY 0x6A8 +#define B43_MMIO_BTCOEX_CTL 0x6B4 /* Bluetooth Coexistence Control */ +#define B43_MMIO_BTCOEX_STAT 0x6B6 /* Bluetooth Coexistence Status */ +#define B43_MMIO_BTCOEX_TXCTL 0x6B8 /* Bluetooth Coexistence Transmit Control */ +#define B43_MMIO_WEPCTL 0x7C0 + +/* SPROM boardflags_lo values */ +#define B43_BFL_BTCOEXIST 0x0001 /* implements Bluetooth coexistance */ +#define B43_BFL_PACTRL 0x0002 /* GPIO 9 controlling the PA */ +#define B43_BFL_AIRLINEMODE 0x0004 /* implements GPIO 13 radio disable indication */ +#define B43_BFL_RSSI 0x0008 /* software calculates nrssi slope. */ +#define B43_BFL_ENETSPI 0x0010 /* has ephy roboswitch spi */ +#define B43_BFL_XTAL_NOSLOW 0x0020 /* no slow clock available */ +#define B43_BFL_CCKHIPWR 0x0040 /* can do high power CCK transmission */ +#define B43_BFL_ENETADM 0x0080 /* has ADMtek switch */ +#define B43_BFL_ENETVLAN 0x0100 /* can do vlan */ +#define B43_BFL_AFTERBURNER 0x0200 /* supports Afterburner mode */ +#define B43_BFL_NOPCI 0x0400 /* leaves PCI floating */ +#define B43_BFL_FEM 0x0800 /* supports the Front End Module */ +#define B43_BFL_EXTLNA 0x1000 /* has an external LNA */ +#define B43_BFL_HGPA 0x2000 /* had high gain PA */ +#define B43_BFL_BTCMOD 0x4000 /* BFL_BTCOEXIST is given in alternate GPIOs */ +#define B43_BFL_ALTIQ 0x8000 /* alternate I/Q settings */ + +/* SPROM boardflags_hi values */ +#define B43_BFH_NOPA 0x0001 /* has no PA */ +#define B43_BFH_RSSIINV 0x0002 /* RSSI uses positive slope (not TSSI) */ +#define B43_BFH_PAREF 0x0004 /* uses the PARef LDO */ +#define B43_BFH_3TSWITCH 0x0008 /* uses a triple throw switch shared + * with bluetooth */ +#define B43_BFH_PHASESHIFT 0x0010 /* can support phase shifter */ +#define B43_BFH_BUCKBOOST 0x0020 /* has buck/booster */ +#define B43_BFH_FEM_BT 0x0040 /* has FEM and switch to share antenna + * with bluetooth */ +#define B43_BFH_NOCBUCK 0x0080 +#define B43_BFH_PALDO 0x0200 +#define B43_BFH_EXTLNA_5GHZ 0x1000 /* has an external LNA (5GHz mode) */ + +/* SPROM boardflags2_lo values */ +#define B43_BFL2_RXBB_INT_REG_DIS 0x0001 /* external RX BB regulator present */ +#define B43_BFL2_APLL_WAR 0x0002 /* alternative A-band PLL settings implemented */ +#define B43_BFL2_TXPWRCTRL_EN 0x0004 /* permits enabling TX Power Control */ +#define B43_BFL2_2X4_DIV 0x0008 /* 2x4 diversity switch */ +#define B43_BFL2_5G_PWRGAIN 0x0010 /* supports 5G band power gain */ +#define B43_BFL2_PCIEWAR_OVR 0x0020 /* overrides ASPM and Clkreq settings */ +#define B43_BFL2_CAESERS_BRD 0x0040 /* is Caesers board (unused) */ +#define B43_BFL2_BTC3WIRE 0x0080 /* used 3-wire bluetooth coexist */ +#define B43_BFL2_SKWRKFEM_BRD 0x0100 /* 4321mcm93 uses Skyworks FEM */ +#define B43_BFL2_SPUR_WAR 0x0200 /* has a workaround for clock-harmonic spurs */ +#define B43_BFL2_GPLL_WAR 0x0400 /* altenative G-band PLL settings implemented */ +#define B43_BFL2_SINGLEANT_CCK 0x1000 +#define B43_BFL2_2G_SPUR_WAR 0x2000 + +/* SPROM boardflags2_hi values */ +#define B43_BFH2_GPLL_WAR2 0x0001 +#define B43_BFH2_IPALVLSHIFT_3P3 0x0002 +#define B43_BFH2_INTERNDET_TXIQCAL 0x0004 +#define B43_BFH2_XTALBUFOUTEN 0x0008 + +/* GPIO register offset, in both ChipCommon and PCI core. */ +#define B43_GPIO_CONTROL 0x6c + +/* SHM Routing */ +enum { + B43_SHM_UCODE, /* Microcode memory */ + B43_SHM_SHARED, /* Shared memory */ + B43_SHM_SCRATCH, /* Scratch memory */ + B43_SHM_HW, /* Internal hardware register */ + B43_SHM_RCMTA, /* Receive match transmitter address (rev >= 5 only) */ +}; +/* SHM Routing modifiers */ +#define B43_SHM_AUTOINC_R 0x0200 /* Auto-increment address on read */ +#define B43_SHM_AUTOINC_W 0x0100 /* Auto-increment address on write */ +#define B43_SHM_AUTOINC_RW (B43_SHM_AUTOINC_R | \ + B43_SHM_AUTOINC_W) + +/* Misc SHM_SHARED offsets */ +#define B43_SHM_SH_WLCOREREV 0x0016 /* 802.11 core revision */ +#define B43_SHM_SH_PCTLWDPOS 0x0008 +#define B43_SHM_SH_RXPADOFF 0x0034 /* RX Padding data offset (PIO only) */ +#define B43_SHM_SH_FWCAPA 0x0042 /* Firmware capabilities (Opensource firmware only) */ +#define B43_SHM_SH_PHYVER 0x0050 /* PHY version */ +#define B43_SHM_SH_PHYTYPE 0x0052 /* PHY type */ +#define B43_SHM_SH_ANTSWAP 0x005C /* Antenna swap threshold */ +#define B43_SHM_SH_HOSTF1 0x005E /* Hostflags 1 for ucode options */ +#define B43_SHM_SH_HOSTF2 0x0060 /* Hostflags 2 for ucode options */ +#define B43_SHM_SH_HOSTF3 0x0062 /* Hostflags 3 for ucode options */ +#define B43_SHM_SH_RFATT 0x0064 /* Current radio attenuation value */ +#define B43_SHM_SH_RADAR 0x0066 /* Radar register */ +#define B43_SHM_SH_PHYTXNOI 0x006E /* PHY noise directly after TX (lower 8bit only) */ +#define B43_SHM_SH_RFRXSP1 0x0072 /* RF RX SP Register 1 */ +#define B43_SHM_SH_HOSTF4 0x0078 /* Hostflags 4 for ucode options */ +#define B43_SHM_SH_CHAN 0x00A0 /* Current channel (low 8bit only) */ +#define B43_SHM_SH_CHAN_5GHZ 0x0100 /* Bit set, if 5 Ghz channel */ +#define B43_SHM_SH_CHAN_40MHZ 0x0200 /* Bit set, if 40 Mhz channel width */ +#define B43_SHM_SH_HOSTF5 0x00D4 /* Hostflags 5 for ucode options */ +#define B43_SHM_SH_BCMCFIFOID 0x0108 /* Last posted cookie to the bcast/mcast FIFO */ +/* TSSI information */ +#define B43_SHM_SH_TSSI_CCK 0x0058 /* TSSI for last 4 CCK frames (32bit) */ +#define B43_SHM_SH_TSSI_OFDM_A 0x0068 /* TSSI for last 4 OFDM frames (32bit) */ +#define B43_SHM_SH_TSSI_OFDM_G 0x0070 /* TSSI for last 4 OFDM frames (32bit) */ +#define B43_TSSI_MAX 0x7F /* Max value for one TSSI value */ +/* SHM_SHARED TX FIFO variables */ +#define B43_SHM_SH_SIZE01 0x0098 /* TX FIFO size for FIFO 0 (low) and 1 (high) */ +#define B43_SHM_SH_SIZE23 0x009A /* TX FIFO size for FIFO 2 and 3 */ +#define B43_SHM_SH_SIZE45 0x009C /* TX FIFO size for FIFO 4 and 5 */ +#define B43_SHM_SH_SIZE67 0x009E /* TX FIFO size for FIFO 6 and 7 */ +/* SHM_SHARED background noise */ +#define B43_SHM_SH_JSSI0 0x0088 /* Measure JSSI 0 */ +#define B43_SHM_SH_JSSI1 0x008A /* Measure JSSI 1 */ +#define B43_SHM_SH_JSSIAUX 0x008C /* Measure JSSI AUX */ +/* SHM_SHARED crypto engine */ +#define B43_SHM_SH_DEFAULTIV 0x003C /* Default IV location */ +#define B43_SHM_SH_NRRXTRANS 0x003E /* # of soft RX transmitter addresses (max 8) */ +#define B43_SHM_SH_KTP 0x0056 /* Key table pointer */ +#define B43_SHM_SH_TKIPTSCTTAK 0x0318 +#define B43_SHM_SH_KEYIDXBLOCK 0x05D4 /* Key index/algorithm block (v4 firmware) */ +#define B43_SHM_SH_PSM 0x05F4 /* PSM transmitter address match block (rev < 5) */ +/* SHM_SHARED WME variables */ +#define B43_SHM_SH_EDCFSTAT 0x000E /* EDCF status */ +#define B43_SHM_SH_TXFCUR 0x0030 /* TXF current index */ +#define B43_SHM_SH_EDCFQ 0x0240 /* EDCF Q info */ +/* SHM_SHARED powersave mode related */ +#define B43_SHM_SH_SLOTT 0x0010 /* Slot time */ +#define B43_SHM_SH_DTIMPER 0x0012 /* DTIM period */ +#define B43_SHM_SH_NOSLPZNATDTIM 0x004C /* NOSLPZNAT DTIM */ +/* SHM_SHARED beacon/AP variables */ +#define B43_SHM_SH_BT_BASE0 0x0068 /* Beacon template base 0 */ +#define B43_SHM_SH_BTL0 0x0018 /* Beacon template length 0 */ +#define B43_SHM_SH_BT_BASE1 0x0468 /* Beacon template base 1 */ +#define B43_SHM_SH_BTL1 0x001A /* Beacon template length 1 */ +#define B43_SHM_SH_BTSFOFF 0x001C /* Beacon TSF offset */ +#define B43_SHM_SH_TIMBPOS 0x001E /* TIM B position in beacon */ +#define B43_SHM_SH_DTIMP 0x0012 /* DTIP period */ +#define B43_SHM_SH_MCASTCOOKIE 0x00A8 /* Last bcast/mcast frame ID */ +#define B43_SHM_SH_SFFBLIM 0x0044 /* Short frame fallback retry limit */ +#define B43_SHM_SH_LFFBLIM 0x0046 /* Long frame fallback retry limit */ +#define B43_SHM_SH_BEACPHYCTL 0x0054 /* Beacon PHY TX control word (see PHY TX control) */ +#define B43_SHM_SH_EXTNPHYCTL 0x00B0 /* Extended bytes for beacon PHY control (N) */ +/* SHM_SHARED ACK/CTS control */ +#define B43_SHM_SH_ACKCTSPHYCTL 0x0022 /* ACK/CTS PHY control word (see PHY TX control) */ +/* SHM_SHARED probe response variables */ +#define B43_SHM_SH_PRSSID 0x0160 /* Probe Response SSID */ +#define B43_SHM_SH_PRSSIDLEN 0x0048 /* Probe Response SSID length */ +#define B43_SHM_SH_PRTLEN 0x004A /* Probe Response template length */ +#define B43_SHM_SH_PRMAXTIME 0x0074 /* Probe Response max time */ +#define B43_SHM_SH_PRPHYCTL 0x0188 /* Probe Response PHY TX control word */ +/* SHM_SHARED rate tables */ +#define B43_SHM_SH_OFDMDIRECT 0x01C0 /* Pointer to OFDM direct map */ +#define B43_SHM_SH_OFDMBASIC 0x01E0 /* Pointer to OFDM basic rate map */ +#define B43_SHM_SH_CCKDIRECT 0x0200 /* Pointer to CCK direct map */ +#define B43_SHM_SH_CCKBASIC 0x0220 /* Pointer to CCK basic rate map */ +/* SHM_SHARED microcode soft registers */ +#define B43_SHM_SH_UCODEREV 0x0000 /* Microcode revision */ +#define B43_SHM_SH_UCODEPATCH 0x0002 /* Microcode patchlevel */ +#define B43_SHM_SH_UCODEDATE 0x0004 /* Microcode date */ +#define B43_SHM_SH_UCODETIME 0x0006 /* Microcode time */ +#define B43_SHM_SH_UCODESTAT 0x0040 /* Microcode debug status code */ +#define B43_SHM_SH_UCODESTAT_INVALID 0 +#define B43_SHM_SH_UCODESTAT_INIT 1 +#define B43_SHM_SH_UCODESTAT_ACTIVE 2 +#define B43_SHM_SH_UCODESTAT_SUSP 3 /* suspended */ +#define B43_SHM_SH_UCODESTAT_SLEEP 4 /* asleep (PS) */ +#define B43_SHM_SH_MAXBFRAMES 0x0080 /* Maximum number of frames in a burst */ +#define B43_SHM_SH_SPUWKUP 0x0094 /* pre-wakeup for synth PU in us */ +#define B43_SHM_SH_PRETBTT 0x0096 /* pre-TBTT in us */ +/* SHM_SHARED tx iq workarounds */ +#define B43_SHM_SH_NPHY_TXIQW0 0x0700 +#define B43_SHM_SH_NPHY_TXIQW1 0x0702 +#define B43_SHM_SH_NPHY_TXIQW2 0x0704 +#define B43_SHM_SH_NPHY_TXIQW3 0x0706 +/* SHM_SHARED tx pwr ctrl */ +#define B43_SHM_SH_NPHY_TXPWR_INDX0 0x0708 +#define B43_SHM_SH_NPHY_TXPWR_INDX1 0x070E + +/* SHM_SCRATCH offsets */ +#define B43_SHM_SC_MINCONT 0x0003 /* Minimum contention window */ +#define B43_SHM_SC_MAXCONT 0x0004 /* Maximum contention window */ +#define B43_SHM_SC_CURCONT 0x0005 /* Current contention window */ +#define B43_SHM_SC_SRLIMIT 0x0006 /* Short retry count limit */ +#define B43_SHM_SC_LRLIMIT 0x0007 /* Long retry count limit */ +#define B43_SHM_SC_DTIMC 0x0008 /* Current DTIM count */ +#define B43_SHM_SC_BTL0LEN 0x0015 /* Beacon 0 template length */ +#define B43_SHM_SC_BTL1LEN 0x0016 /* Beacon 1 template length */ +#define B43_SHM_SC_SCFB 0x0017 /* Short frame transmit count threshold for rate fallback */ +#define B43_SHM_SC_LCFB 0x0018 /* Long frame transmit count threshold for rate fallback */ + +/* Hardware Radio Enable masks */ +#define B43_MMIO_RADIO_HWENABLED_HI_MASK (1 << 16) +#define B43_MMIO_RADIO_HWENABLED_LO_MASK (1 << 4) + +/* HostFlags. See b43_hf_read/write() */ +#define B43_HF_ANTDIVHELP 0x000000000001ULL /* ucode antenna div helper */ +#define B43_HF_SYMW 0x000000000002ULL /* G-PHY SYM workaround */ +#define B43_HF_RXPULLW 0x000000000004ULL /* RX pullup workaround */ +#define B43_HF_CCKBOOST 0x000000000008ULL /* 4dB CCK power boost (exclusive with OFDM boost) */ +#define B43_HF_BTCOEX 0x000000000010ULL /* Bluetooth coexistance */ +#define B43_HF_GDCW 0x000000000020ULL /* G-PHY DC canceller filter bw workaround */ +#define B43_HF_OFDMPABOOST 0x000000000040ULL /* Enable PA gain boost for OFDM */ +#define B43_HF_ACPR 0x000000000080ULL /* Disable for Japan, channel 14 */ +#define B43_HF_EDCF 0x000000000100ULL /* on if WME and MAC suspended */ +#define B43_HF_TSSIRPSMW 0x000000000200ULL /* TSSI reset PSM ucode workaround */ +#define B43_HF_20IN40IQW 0x000000000200ULL /* 20 in 40 MHz I/Q workaround (rev >= 13 only) */ +#define B43_HF_DSCRQ 0x000000000400ULL /* Disable slow clock request in ucode */ +#define B43_HF_ACIW 0x000000000800ULL /* ACI workaround: shift bits by 2 on PHY CRS */ +#define B43_HF_2060W 0x000000001000ULL /* 2060 radio workaround */ +#define B43_HF_RADARW 0x000000002000ULL /* Radar workaround */ +#define B43_HF_USEDEFKEYS 0x000000004000ULL /* Enable use of default keys */ +#define B43_HF_AFTERBURNER 0x000000008000ULL /* Afterburner enabled */ +#define B43_HF_BT4PRIOCOEX 0x000000010000ULL /* Bluetooth 4-priority coexistance */ +#define B43_HF_FWKUP 0x000000020000ULL /* Fast wake-up ucode */ +#define B43_HF_VCORECALC 0x000000040000ULL /* Force VCO recalculation when powering up synthpu */ +#define B43_HF_PCISCW 0x000000080000ULL /* PCI slow clock workaround */ +#define B43_HF_4318TSSI 0x000000200000ULL /* 4318 TSSI */ +#define B43_HF_FBCMCFIFO 0x000000400000ULL /* Flush bcast/mcast FIFO immediately */ +#define B43_HF_HWPCTL 0x000000800000ULL /* Enable hardwarre power control */ +#define B43_HF_BTCOEXALT 0x000001000000ULL /* Bluetooth coexistance in alternate pins */ +#define B43_HF_TXBTCHECK 0x000002000000ULL /* Bluetooth check during transmission */ +#define B43_HF_SKCFPUP 0x000004000000ULL /* Skip CFP update */ +#define B43_HF_N40W 0x000008000000ULL /* N PHY 40 MHz workaround (rev >= 13 only) */ +#define B43_HF_ANTSEL 0x000020000000ULL /* Antenna selection (for testing antenna div.) */ +#define B43_HF_BT3COEXT 0x000020000000ULL /* Bluetooth 3-wire coexistence (rev >= 13 only) */ +#define B43_HF_BTCANT 0x000040000000ULL /* Bluetooth coexistence (antenna mode) (rev >= 13 only) */ +#define B43_HF_ANTSELEN 0x000100000000ULL /* Antenna selection enabled (rev >= 13 only) */ +#define B43_HF_ANTSELMODE 0x000200000000ULL /* Antenna selection mode (rev >= 13 only) */ +#define B43_HF_MLADVW 0x001000000000ULL /* N PHY ML ADV workaround (rev >= 13 only) */ +#define B43_HF_PR45960W 0x080000000000ULL /* PR 45960 workaround (rev >= 13 only) */ + +/* Firmware capabilities field in SHM (Opensource firmware only) */ +#define B43_FWCAPA_HWCRYPTO 0x0001 +#define B43_FWCAPA_QOS 0x0002 + +/* MacFilter offsets. */ +#define B43_MACFILTER_SELF 0x0000 +#define B43_MACFILTER_BSSID 0x0003 + +/* PowerControl */ +#define B43_PCTL_IN 0xB0 +#define B43_PCTL_OUT 0xB4 +#define B43_PCTL_OUTENABLE 0xB8 +#define B43_PCTL_XTAL_POWERUP 0x40 +#define B43_PCTL_PLL_POWERDOWN 0x80 + +/* PowerControl Clock Modes */ +#define B43_PCTL_CLK_FAST 0x00 +#define B43_PCTL_CLK_SLOW 0x01 +#define B43_PCTL_CLK_DYNAMIC 0x02 + +#define B43_PCTL_FORCE_SLOW 0x0800 +#define B43_PCTL_FORCE_PLL 0x1000 +#define B43_PCTL_DYN_XTAL 0x2000 + +/* PHYVersioning */ +#define B43_PHYTYPE_A 0x00 +#define B43_PHYTYPE_B 0x01 +#define B43_PHYTYPE_G 0x02 +#define B43_PHYTYPE_N 0x04 +#define B43_PHYTYPE_LP 0x05 +#define B43_PHYTYPE_SSLPN 0x06 +#define B43_PHYTYPE_HT 0x07 +#define B43_PHYTYPE_LCN 0x08 +#define B43_PHYTYPE_LCNXN 0x09 +#define B43_PHYTYPE_LCN40 0x0a +#define B43_PHYTYPE_AC 0x0b + +/* PHYRegisters */ +#define B43_PHY_ILT_A_CTRL 0x0072 +#define B43_PHY_ILT_A_DATA1 0x0073 +#define B43_PHY_ILT_A_DATA2 0x0074 +#define B43_PHY_G_LO_CONTROL 0x0810 +#define B43_PHY_ILT_G_CTRL 0x0472 +#define B43_PHY_ILT_G_DATA1 0x0473 +#define B43_PHY_ILT_G_DATA2 0x0474 +#define B43_PHY_A_PCTL 0x007B +#define B43_PHY_G_PCTL 0x0029 +#define B43_PHY_A_CRS 0x0029 +#define B43_PHY_RADIO_BITFIELD 0x0401 +#define B43_PHY_G_CRS 0x0429 +#define B43_PHY_NRSSILT_CTRL 0x0803 +#define B43_PHY_NRSSILT_DATA 0x0804 + +/* RadioRegisters */ +#define B43_RADIOCTL_ID 0x01 + +/* MAC Control bitfield */ +#define B43_MACCTL_ENABLED 0x00000001 /* MAC Enabled */ +#define B43_MACCTL_PSM_RUN 0x00000002 /* Run Microcode */ +#define B43_MACCTL_PSM_JMP0 0x00000004 /* Microcode jump to 0 */ +#define B43_MACCTL_SHM_ENABLED 0x00000100 /* SHM Enabled */ +#define B43_MACCTL_SHM_UPPER 0x00000200 /* SHM Upper */ +#define B43_MACCTL_IHR_ENABLED 0x00000400 /* IHR Region Enabled */ +#define B43_MACCTL_PSM_DBG 0x00002000 /* Microcode debugging enabled */ +#define B43_MACCTL_GPOUTSMSK 0x0000C000 /* GPOUT Select Mask */ +#define B43_MACCTL_BE 0x00010000 /* Big Endian mode */ +#define B43_MACCTL_INFRA 0x00020000 /* Infrastructure mode */ +#define B43_MACCTL_AP 0x00040000 /* AccessPoint mode */ +#define B43_MACCTL_RADIOLOCK 0x00080000 /* Radio lock */ +#define B43_MACCTL_BEACPROMISC 0x00100000 /* Beacon Promiscuous */ +#define B43_MACCTL_KEEP_BADPLCP 0x00200000 /* Keep frames with bad PLCP */ +#define B43_MACCTL_KEEP_CTL 0x00400000 /* Keep control frames */ +#define B43_MACCTL_KEEP_BAD 0x00800000 /* Keep bad frames (FCS) */ +#define B43_MACCTL_PROMISC 0x01000000 /* Promiscuous mode */ +#define B43_MACCTL_HWPS 0x02000000 /* Hardware Power Saving */ +#define B43_MACCTL_AWAKE 0x04000000 /* Device is awake */ +#define B43_MACCTL_CLOSEDNET 0x08000000 /* Closed net (no SSID bcast) */ +#define B43_MACCTL_TBTTHOLD 0x10000000 /* TBTT Hold */ +#define B43_MACCTL_DISCTXSTAT 0x20000000 /* Discard TX status */ +#define B43_MACCTL_DISCPMQ 0x40000000 /* Discard Power Management Queue */ +#define B43_MACCTL_GMODE 0x80000000 /* G Mode */ + +/* MAC Command bitfield */ +#define B43_MACCMD_BEACON0_VALID 0x00000001 /* Beacon 0 in template RAM is busy/valid */ +#define B43_MACCMD_BEACON1_VALID 0x00000002 /* Beacon 1 in template RAM is busy/valid */ +#define B43_MACCMD_DFQ_VALID 0x00000004 /* Directed frame queue valid (IBSS PS mode, ATIM) */ +#define B43_MACCMD_CCA 0x00000008 /* Clear channel assessment */ +#define B43_MACCMD_BGNOISE 0x00000010 /* Background noise */ + +/* See BCMA_CLKCTLST_EXTRESREQ and BCMA_CLKCTLST_EXTRESST */ +#define B43_BCMA_CLKCTLST_80211_PLL_REQ 0x00000100 +#define B43_BCMA_CLKCTLST_PHY_PLL_REQ 0x00000200 +#define B43_BCMA_CLKCTLST_80211_PLL_ST 0x01000000 +#define B43_BCMA_CLKCTLST_PHY_PLL_ST 0x02000000 + +/* BCMA 802.11 core specific IO Control (BCMA_IOCTL) flags */ +#define B43_BCMA_IOCTL_PHY_CLKEN 0x00000004 /* PHY Clock Enable */ +#define B43_BCMA_IOCTL_PHY_RESET 0x00000008 /* PHY Reset */ +#define B43_BCMA_IOCTL_MACPHYCLKEN 0x00000010 /* MAC PHY Clock Control Enable */ +#define B43_BCMA_IOCTL_PLLREFSEL 0x00000020 /* PLL Frequency Reference Select */ +#define B43_BCMA_IOCTL_PHY_BW 0x000000C0 /* PHY band width and clock speed mask (N-PHY+ only?) */ +#define B43_BCMA_IOCTL_PHY_BW_10MHZ 0x00000000 /* 10 MHz bandwidth, 40 MHz PHY */ +#define B43_BCMA_IOCTL_PHY_BW_20MHZ 0x00000040 /* 20 MHz bandwidth, 80 MHz PHY */ +#define B43_BCMA_IOCTL_PHY_BW_40MHZ 0x00000080 /* 40 MHz bandwidth, 160 MHz PHY */ +#define B43_BCMA_IOCTL_GMODE 0x00002000 /* G Mode Enable */ + +/* BCMA 802.11 core specific IO status (BCMA_IOST) flags */ +#define B43_BCMA_IOST_2G_PHY 0x00000001 /* 2.4G capable phy */ +#define B43_BCMA_IOST_5G_PHY 0x00000002 /* 5G capable phy */ +#define B43_BCMA_IOST_FASTCLKA 0x00000004 /* Fast Clock Available */ +#define B43_BCMA_IOST_DUALB_PHY 0x00000008 /* Dualband phy */ + +/* 802.11 core specific TM State Low (SSB_TMSLOW) flags */ +#define B43_TMSLOW_GMODE 0x20000000 /* G Mode Enable */ +#define B43_TMSLOW_PHY_BANDWIDTH 0x00C00000 /* PHY band width and clock speed mask (N-PHY only) */ +#define B43_TMSLOW_PHY_BANDWIDTH_10MHZ 0x00000000 /* 10 MHz bandwidth, 40 MHz PHY */ +#define B43_TMSLOW_PHY_BANDWIDTH_20MHZ 0x00400000 /* 20 MHz bandwidth, 80 MHz PHY */ +#define B43_TMSLOW_PHY_BANDWIDTH_40MHZ 0x00800000 /* 40 MHz bandwidth, 160 MHz PHY */ +#define B43_TMSLOW_PLLREFSEL 0x00200000 /* PLL Frequency Reference Select (rev >= 5) */ +#define B43_TMSLOW_MACPHYCLKEN 0x00100000 /* MAC PHY Clock Control Enable (rev >= 5) */ +#define B43_TMSLOW_PHYRESET 0x00080000 /* PHY Reset */ +#define B43_TMSLOW_PHYCLKEN 0x00040000 /* PHY Clock Enable */ + +/* 802.11 core specific TM State High (SSB_TMSHIGH) flags */ +#define B43_TMSHIGH_DUALBAND_PHY 0x00080000 /* Dualband PHY available */ +#define B43_TMSHIGH_FCLOCK 0x00040000 /* Fast Clock Available (rev >= 5) */ +#define B43_TMSHIGH_HAVE_5GHZ_PHY 0x00020000 /* 5 GHz PHY available (rev >= 5) */ +#define B43_TMSHIGH_HAVE_2GHZ_PHY 0x00010000 /* 2.4 GHz PHY available (rev >= 5) */ + +/* Generic-Interrupt reasons. */ +#define B43_IRQ_MAC_SUSPENDED 0x00000001 +#define B43_IRQ_BEACON 0x00000002 +#define B43_IRQ_TBTT_INDI 0x00000004 +#define B43_IRQ_BEACON_TX_OK 0x00000008 +#define B43_IRQ_BEACON_CANCEL 0x00000010 +#define B43_IRQ_ATIM_END 0x00000020 +#define B43_IRQ_PMQ 0x00000040 +#define B43_IRQ_PIO_WORKAROUND 0x00000100 +#define B43_IRQ_MAC_TXERR 0x00000200 +#define B43_IRQ_PHY_TXERR 0x00000800 +#define B43_IRQ_PMEVENT 0x00001000 +#define B43_IRQ_TIMER0 0x00002000 +#define B43_IRQ_TIMER1 0x00004000 +#define B43_IRQ_DMA 0x00008000 +#define B43_IRQ_TXFIFO_FLUSH_OK 0x00010000 +#define B43_IRQ_CCA_MEASURE_OK 0x00020000 +#define B43_IRQ_NOISESAMPLE_OK 0x00040000 +#define B43_IRQ_UCODE_DEBUG 0x08000000 +#define B43_IRQ_RFKILL 0x10000000 +#define B43_IRQ_TX_OK 0x20000000 +#define B43_IRQ_PHY_G_CHANGED 0x40000000 +#define B43_IRQ_TIMEOUT 0x80000000 + +#define B43_IRQ_ALL 0xFFFFFFFF +#define B43_IRQ_MASKTEMPLATE (B43_IRQ_TBTT_INDI | \ + B43_IRQ_ATIM_END | \ + B43_IRQ_PMQ | \ + B43_IRQ_MAC_TXERR | \ + B43_IRQ_PHY_TXERR | \ + B43_IRQ_DMA | \ + B43_IRQ_TXFIFO_FLUSH_OK | \ + B43_IRQ_NOISESAMPLE_OK | \ + B43_IRQ_UCODE_DEBUG | \ + B43_IRQ_RFKILL | \ + B43_IRQ_TX_OK) + +/* The firmware register to fetch the debug-IRQ reason from. */ +#define B43_DEBUGIRQ_REASON_REG 63 +/* Debug-IRQ reasons. */ +#define B43_DEBUGIRQ_PANIC 0 /* The firmware panic'ed */ +#define B43_DEBUGIRQ_DUMP_SHM 1 /* Dump shared SHM */ +#define B43_DEBUGIRQ_DUMP_REGS 2 /* Dump the microcode registers */ +#define B43_DEBUGIRQ_MARKER 3 /* A "marker" was thrown by the firmware. */ +#define B43_DEBUGIRQ_ACK 0xFFFF /* The host writes that to ACK the IRQ */ + +/* The firmware register that contains the "marker" line. */ +#define B43_MARKER_ID_REG 2 +#define B43_MARKER_LINE_REG 3 + +/* The firmware register to fetch the panic reason from. */ +#define B43_FWPANIC_REASON_REG 3 +/* Firmware panic reason codes */ +#define B43_FWPANIC_DIE 0 /* Firmware died. Don't auto-restart it. */ +#define B43_FWPANIC_RESTART 1 /* Firmware died. Schedule a controller reset. */ + +/* The firmware register that contains the watchdog counter. */ +#define B43_WATCHDOG_REG 1 + +/* Device specific rate values. + * The actual values defined here are (rate_in_mbps * 2). + * Some code depends on this. Don't change it. */ +#define B43_CCK_RATE_1MB 0x02 +#define B43_CCK_RATE_2MB 0x04 +#define B43_CCK_RATE_5MB 0x0B +#define B43_CCK_RATE_11MB 0x16 +#define B43_OFDM_RATE_6MB 0x0C +#define B43_OFDM_RATE_9MB 0x12 +#define B43_OFDM_RATE_12MB 0x18 +#define B43_OFDM_RATE_18MB 0x24 +#define B43_OFDM_RATE_24MB 0x30 +#define B43_OFDM_RATE_36MB 0x48 +#define B43_OFDM_RATE_48MB 0x60 +#define B43_OFDM_RATE_54MB 0x6C +/* Convert a b43 rate value to a rate in 100kbps */ +#define B43_RATE_TO_BASE100KBPS(rate) (((rate) * 10) / 2) + +#define B43_DEFAULT_SHORT_RETRY_LIMIT 7 +#define B43_DEFAULT_LONG_RETRY_LIMIT 4 + +#define B43_PHY_TX_BADNESS_LIMIT 1000 + +/* Max size of a security key */ +#define B43_SEC_KEYSIZE 16 +/* Max number of group keys */ +#define B43_NR_GROUP_KEYS 4 +/* Max number of pairwise keys */ +#define B43_NR_PAIRWISE_KEYS 50 +/* Security algorithms. */ +enum { + B43_SEC_ALGO_NONE = 0, /* unencrypted, as of TX header. */ + B43_SEC_ALGO_WEP40, + B43_SEC_ALGO_TKIP, + B43_SEC_ALGO_AES, + B43_SEC_ALGO_WEP104, + B43_SEC_ALGO_AES_LEGACY, +}; + +struct b43_dmaring; + +/* The firmware file header */ +#define B43_FW_TYPE_UCODE 'u' +#define B43_FW_TYPE_PCM 'p' +#define B43_FW_TYPE_IV 'i' +struct b43_fw_header { + /* File type */ + u8 type; + /* File format version */ + u8 ver; + u8 __padding[2]; + /* Size of the data. For ucode and PCM this is in bytes. + * For IV this is number-of-ivs. */ + __be32 size; +} __packed; + +/* Initial Value file format */ +#define B43_IV_OFFSET_MASK 0x7FFF +#define B43_IV_32BIT 0x8000 +struct b43_iv { + __be16 offset_size; + union { + __be16 d16; + __be32 d32; + } data __packed; +} __packed; + + +/* Data structures for DMA transmission, per 80211 core. */ +struct b43_dma { + struct b43_dmaring *tx_ring_AC_BK; /* Background */ + struct b43_dmaring *tx_ring_AC_BE; /* Best Effort */ + struct b43_dmaring *tx_ring_AC_VI; /* Video */ + struct b43_dmaring *tx_ring_AC_VO; /* Voice */ + struct b43_dmaring *tx_ring_mcast; /* Multicast */ + + struct b43_dmaring *rx_ring; + + u32 translation; /* Routing bits */ + bool translation_in_low; /* Should translation bit go into low addr? */ + bool parity; /* Check for parity */ +}; + +struct b43_pio_txqueue; +struct b43_pio_rxqueue; + +/* Data structures for PIO transmission, per 80211 core. */ +struct b43_pio { + struct b43_pio_txqueue *tx_queue_AC_BK; /* Background */ + struct b43_pio_txqueue *tx_queue_AC_BE; /* Best Effort */ + struct b43_pio_txqueue *tx_queue_AC_VI; /* Video */ + struct b43_pio_txqueue *tx_queue_AC_VO; /* Voice */ + struct b43_pio_txqueue *tx_queue_mcast; /* Multicast */ + + struct b43_pio_rxqueue *rx_queue; +}; + +/* Context information for a noise calculation (Link Quality). */ +struct b43_noise_calculation { + bool calculation_running; + u8 nr_samples; + s8 samples[8][4]; +}; + +struct b43_stats { + u8 link_noise; +}; + +struct b43_key { + /* If keyconf is NULL, this key is disabled. + * keyconf is a cookie. Don't derefenrence it outside of the set_key + * path, because b43 doesn't own it. */ + struct ieee80211_key_conf *keyconf; + u8 algorithm; +}; + +/* SHM offsets to the QOS data structures for the 4 different queues. */ +#define B43_QOS_QUEUE_NUM 4 +#define B43_QOS_PARAMS(queue) (B43_SHM_SH_EDCFQ + \ + (B43_NR_QOSPARAMS * sizeof(u16) * (queue))) +#define B43_QOS_BACKGROUND B43_QOS_PARAMS(0) +#define B43_QOS_BESTEFFORT B43_QOS_PARAMS(1) +#define B43_QOS_VIDEO B43_QOS_PARAMS(2) +#define B43_QOS_VOICE B43_QOS_PARAMS(3) + +/* QOS parameter hardware data structure offsets. */ +#define B43_NR_QOSPARAMS 16 +enum { + B43_QOSPARAM_TXOP = 0, + B43_QOSPARAM_CWMIN, + B43_QOSPARAM_CWMAX, + B43_QOSPARAM_CWCUR, + B43_QOSPARAM_AIFS, + B43_QOSPARAM_BSLOTS, + B43_QOSPARAM_REGGAP, + B43_QOSPARAM_STATUS, +}; + +/* QOS parameters for a queue. */ +struct b43_qos_params { + /* The QOS parameters */ + struct ieee80211_tx_queue_params p; +}; + +struct b43_wl; + +/* The type of the firmware file. */ +enum b43_firmware_file_type { + B43_FWTYPE_PROPRIETARY, + B43_FWTYPE_OPENSOURCE, + B43_NR_FWTYPES, +}; + +/* Context data for fetching firmware. */ +struct b43_request_fw_context { + /* The device we are requesting the fw for. */ + struct b43_wldev *dev; + /* a completion event structure needed if this call is asynchronous */ + struct completion fw_load_complete; + /* a pointer to the firmware object */ + const struct firmware *blob; + /* The type of firmware to request. */ + enum b43_firmware_file_type req_type; + /* Error messages for each firmware type. */ + char errors[B43_NR_FWTYPES][128]; + /* Temporary buffer for storing the firmware name. */ + char fwname[64]; + /* A fatal error occurred while requesting. Firmware request + * can not continue, as any other request will also fail. */ + int fatal_failure; +}; + +/* In-memory representation of a cached microcode file. */ +struct b43_firmware_file { + const char *filename; + const struct firmware *data; + /* Type of the firmware file name. Note that this does only indicate + * the type by the firmware name. NOT the file contents. + * If you want to check for proprietary vs opensource, use (struct b43_firmware)->opensource + * instead! The (struct b43_firmware)->opensource flag is derived from the actual firmware + * binary code, not just the filename. + */ + enum b43_firmware_file_type type; +}; + +enum b43_firmware_hdr_format { + B43_FW_HDR_598, + B43_FW_HDR_410, + B43_FW_HDR_351, +}; + +/* Pointers to the firmware data and meta information about it. */ +struct b43_firmware { + /* Microcode */ + struct b43_firmware_file ucode; + /* PCM code */ + struct b43_firmware_file pcm; + /* Initial MMIO values for the firmware */ + struct b43_firmware_file initvals; + /* Initial MMIO values for the firmware, band-specific */ + struct b43_firmware_file initvals_band; + + /* Firmware revision */ + u16 rev; + /* Firmware patchlevel */ + u16 patch; + + /* Format of header used by firmware */ + enum b43_firmware_hdr_format hdr_format; + + /* Set to true, if we are using an opensource firmware. + * Use this to check for proprietary vs opensource. */ + bool opensource; + /* Set to true, if the core needs a PCM firmware, but + * we failed to load one. This is always false for + * core rev > 10, as these don't need PCM firmware. */ + bool pcm_request_failed; +}; + +/* Device (802.11 core) initialization status. */ +enum { + B43_STAT_UNINIT = 0, /* Uninitialized. */ + B43_STAT_INITIALIZED = 1, /* Initialized, but not started, yet. */ + B43_STAT_STARTED = 2, /* Up and running. */ +}; +#define b43_status(wldev) atomic_read(&(wldev)->__init_status) +#define b43_set_status(wldev, stat) do { \ + atomic_set(&(wldev)->__init_status, (stat)); \ + smp_wmb(); \ + } while (0) + +/* Data structure for one wireless device (802.11 core) */ +struct b43_wldev { + struct b43_bus_dev *dev; + struct b43_wl *wl; + + /* The device initialization status. + * Use b43_status() to query. */ + atomic_t __init_status; + + bool bad_frames_preempt; /* Use "Bad Frames Preemption" (default off) */ + bool dfq_valid; /* Directed frame queue valid (IBSS PS mode, ATIM) */ + bool radio_hw_enable; /* saved state of radio hardware enabled state */ + bool qos_enabled; /* TRUE, if QoS is used. */ + bool hwcrypto_enabled; /* TRUE, if HW crypto acceleration is enabled. */ + bool use_pio; /* TRUE if next init should use PIO */ + + /* PHY/Radio device. */ + struct b43_phy phy; + + union { + /* DMA engines. */ + struct b43_dma dma; + /* PIO engines. */ + struct b43_pio pio; + }; + /* Use b43_using_pio_transfers() to check whether we are using + * DMA or PIO data transfers. */ + bool __using_pio_transfers; + + /* Various statistics about the physical device. */ + struct b43_stats stats; + + /* Reason code of the last interrupt. */ + u32 irq_reason; + u32 dma_reason[6]; + /* The currently active generic-interrupt mask. */ + u32 irq_mask; + + /* Link Quality calculation context. */ + struct b43_noise_calculation noisecalc; + /* if > 0 MAC is suspended. if == 0 MAC is enabled. */ + int mac_suspended; + + /* Periodic tasks */ + struct delayed_work periodic_work; + unsigned int periodic_state; + + struct work_struct restart_work; + + /* encryption/decryption */ + u16 ktp; /* Key table pointer */ + struct b43_key key[B43_NR_GROUP_KEYS * 2 + B43_NR_PAIRWISE_KEYS]; + + /* Firmware data */ + struct b43_firmware fw; + + /* Devicelist in struct b43_wl (all 802.11 cores) */ + struct list_head list; + + /* Debugging stuff follows. */ +#ifdef CPTCFG_B43_DEBUG + struct b43_dfsentry *dfsentry; + unsigned int irq_count; + unsigned int irq_bit_count[32]; + unsigned int tx_count; + unsigned int rx_count; +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31) + struct compat_threaded_irq irq_compat; +#endif +}; + +/* Data structure for the WLAN parts (802.11 cores) of the b43 chip. */ +struct b43_wl { + /* Pointer to the active wireless device on this chip */ + struct b43_wldev *current_dev; + /* Pointer to the ieee80211 hardware data structure */ + struct ieee80211_hw *hw; + + /* Global driver mutex. Every operation must run with this mutex locked. */ + struct mutex mutex; + /* Hard-IRQ spinlock. This lock protects things used in the hard-IRQ + * handler, only. This basically is just the IRQ mask register. */ + spinlock_t hardirq_lock; + + /* Set this if we call ieee80211_register_hw() and check if we call + * ieee80211_unregister_hw(). */ + bool hw_registred; + + /* We can only have one operating interface (802.11 core) + * at a time. General information about this interface follows. + */ + + struct ieee80211_vif *vif; + /* The MAC address of the operating interface. */ + u8 mac_addr[ETH_ALEN]; + /* Current BSSID */ + u8 bssid[ETH_ALEN]; + /* Interface type. (NL80211_IFTYPE_XXX) */ + int if_type; + /* Is the card operating in AP, STA or IBSS mode? */ + bool operating; + /* filter flags */ + unsigned int filter_flags; + /* Stats about the wireless interface */ + struct ieee80211_low_level_stats ieee_stats; + +#ifdef CPTCFG_B43_HWRNG + struct hwrng rng; + bool rng_initialized; + char rng_name[30 + 1]; +#endif /* CPTCFG_B43_HWRNG */ + + /* List of all wireless devices on this chip */ + struct list_head devlist; + u8 nr_devs; + + bool radiotap_enabled; + bool radio_enabled; + + /* The beacon we are currently using (AP or IBSS mode). */ + struct sk_buff *current_beacon; + bool beacon0_uploaded; + bool beacon1_uploaded; + bool beacon_templates_virgin; /* Never wrote the templates? */ + struct work_struct beacon_update_trigger; + + /* The current QOS parameters for the 4 queues. */ + struct b43_qos_params qos_params[B43_QOS_QUEUE_NUM]; + + /* Work for adjustment of the transmission power. + * This is scheduled when we determine that the actual TX output + * power doesn't match what we want. */ + struct work_struct txpower_adjust_work; + + /* Packet transmit work */ + struct work_struct tx_work; + + /* Queue of packets to be transmitted. */ + struct sk_buff_head tx_queue[B43_QOS_QUEUE_NUM]; + + /* Flag that implement the queues stopping. */ + bool tx_queue_stopped[B43_QOS_QUEUE_NUM]; + + /* firmware loading work */ + struct work_struct firmware_load; + + /* The device LEDs. */ + struct b43_leds leds; + + /* Kmalloc'ed scratch space for PIO TX/RX. Protected by wl->mutex. */ + u8 pio_scratchspace[118] __attribute__((__aligned__(8))); + u8 pio_tailspace[4] __attribute__((__aligned__(8))); +}; + +static inline struct b43_wl *hw_to_b43_wl(struct ieee80211_hw *hw) +{ + return hw->priv; +} + +static inline struct b43_wldev *dev_to_b43_wldev(struct device *dev) +{ + struct ssb_device *ssb_dev = dev_to_ssb_dev(dev); + return ssb_get_drvdata(ssb_dev); +} + +/* Is the device operating in a specified mode (NL80211_IFTYPE_XXX). */ +static inline int b43_is_mode(struct b43_wl *wl, int type) +{ + return (wl->operating && wl->if_type == type); +} + +/** + * b43_current_band - Returns the currently used band. + * Returns one of IEEE80211_BAND_2GHZ and IEEE80211_BAND_5GHZ. + */ +static inline enum ieee80211_band b43_current_band(struct b43_wl *wl) +{ + return wl->hw->conf.chandef.chan->band; +} + +static inline int b43_bus_may_powerdown(struct b43_wldev *wldev) +{ + return wldev->dev->bus_may_powerdown(wldev->dev); +} +static inline int b43_bus_powerup(struct b43_wldev *wldev, bool dynamic_pctl) +{ + return wldev->dev->bus_powerup(wldev->dev, dynamic_pctl); +} +static inline int b43_device_is_enabled(struct b43_wldev *wldev) +{ + return wldev->dev->device_is_enabled(wldev->dev); +} +static inline void b43_device_enable(struct b43_wldev *wldev, + u32 core_specific_flags) +{ + wldev->dev->device_enable(wldev->dev, core_specific_flags); +} +static inline void b43_device_disable(struct b43_wldev *wldev, + u32 core_specific_flags) +{ + wldev->dev->device_disable(wldev->dev, core_specific_flags); +} + +static inline u16 b43_read16(struct b43_wldev *dev, u16 offset) +{ + return dev->dev->read16(dev->dev, offset); +} + +static inline void b43_write16(struct b43_wldev *dev, u16 offset, u16 value) +{ + dev->dev->write16(dev->dev, offset, value); +} + +static inline void b43_maskset16(struct b43_wldev *dev, u16 offset, u16 mask, + u16 set) +{ + b43_write16(dev, offset, (b43_read16(dev, offset) & mask) | set); +} + +static inline u32 b43_read32(struct b43_wldev *dev, u16 offset) +{ + return dev->dev->read32(dev->dev, offset); +} + +static inline void b43_write32(struct b43_wldev *dev, u16 offset, u32 value) +{ + dev->dev->write32(dev->dev, offset, value); +} + +static inline void b43_maskset32(struct b43_wldev *dev, u16 offset, u32 mask, + u32 set) +{ + b43_write32(dev, offset, (b43_read32(dev, offset) & mask) | set); +} + +static inline void b43_block_read(struct b43_wldev *dev, void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + dev->dev->block_read(dev->dev, buffer, count, offset, reg_width); +} + +static inline void b43_block_write(struct b43_wldev *dev, const void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + dev->dev->block_write(dev->dev, buffer, count, offset, reg_width); +} + +static inline bool b43_using_pio_transfers(struct b43_wldev *dev) +{ + return dev->__using_pio_transfers; +} + +/* Message printing */ +__printf(2, 3) void b43info(struct b43_wl *wl, const char *fmt, ...); +__printf(2, 3) void b43err(struct b43_wl *wl, const char *fmt, ...); +__printf(2, 3) void b43warn(struct b43_wl *wl, const char *fmt, ...); +__printf(2, 3) void b43dbg(struct b43_wl *wl, const char *fmt, ...); + + +/* A WARN_ON variant that vanishes when b43 debugging is disabled. + * This _also_ evaluates the arg with debugging disabled. */ +#if B43_DEBUG +# define B43_WARN_ON(x) WARN_ON(x) +#else +static inline bool __b43_warn_on_dummy(bool x) { return x; } +# define B43_WARN_ON(x) __b43_warn_on_dummy(unlikely(!!(x))) +#endif + +/* Convert an integer to a Q5.2 value */ +#define INT_TO_Q52(i) ((i) << 2) +/* Convert a Q5.2 value to an integer (precision loss!) */ +#define Q52_TO_INT(q52) ((q52) >> 2) +/* Macros for printing a value in Q5.2 format */ +#define Q52_FMT "%u.%u" +#define Q52_ARG(q52) Q52_TO_INT(q52), ((((q52) & 0x3) * 100) / 4) + +#endif /* B43_H_ */ diff --git a/drivers/net/wireless/b43/bus.c b/drivers/net/wireless/b43/bus.c new file mode 100644 index 0000000..30d7313 --- /dev/null +++ b/drivers/net/wireless/b43/bus.c @@ -0,0 +1,255 @@ +/* + + Broadcom B43 wireless driver + Bus abstraction layer + + Copyright (c) 2011 RafaÅ‚ MiÅ‚ecki + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" +#include "bus.h" + +/* BCMA */ +#ifdef CPTCFG_B43_BCMA +static int b43_bus_bcma_bus_may_powerdown(struct b43_bus_dev *dev) +{ + return 0; /* bcma_bus_may_powerdown(dev->bdev->bus); */ +} +static int b43_bus_bcma_bus_powerup(struct b43_bus_dev *dev, + bool dynamic_pctl) +{ + return 0; /* bcma_bus_powerup(dev->sdev->bus, dynamic_pctl); */ +} +static int b43_bus_bcma_device_is_enabled(struct b43_bus_dev *dev) +{ + return bcma_core_is_enabled(dev->bdev); +} +static void b43_bus_bcma_device_enable(struct b43_bus_dev *dev, + u32 core_specific_flags) +{ + bcma_core_enable(dev->bdev, core_specific_flags); +} +static void b43_bus_bcma_device_disable(struct b43_bus_dev *dev, + u32 core_specific_flags) +{ + bcma_core_disable(dev->bdev, core_specific_flags); +} +static u16 b43_bus_bcma_read16(struct b43_bus_dev *dev, u16 offset) +{ + return bcma_read16(dev->bdev, offset); +} +static u32 b43_bus_bcma_read32(struct b43_bus_dev *dev, u16 offset) +{ + return bcma_read32(dev->bdev, offset); +} +static +void b43_bus_bcma_write16(struct b43_bus_dev *dev, u16 offset, u16 value) +{ + bcma_write16(dev->bdev, offset, value); +} +static +void b43_bus_bcma_write32(struct b43_bus_dev *dev, u16 offset, u32 value) +{ + bcma_write32(dev->bdev, offset, value); +} +static +void b43_bus_bcma_block_read(struct b43_bus_dev *dev, void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + bcma_block_read(dev->bdev, buffer, count, offset, reg_width); +} +static +void b43_bus_bcma_block_write(struct b43_bus_dev *dev, const void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + bcma_block_write(dev->bdev, buffer, count, offset, reg_width); +} + +struct b43_bus_dev *b43_bus_dev_bcma_init(struct bcma_device *core) +{ + struct b43_bus_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return NULL; + + dev->bus_type = B43_BUS_BCMA; + dev->bdev = core; + + dev->bus_may_powerdown = b43_bus_bcma_bus_may_powerdown; + dev->bus_powerup = b43_bus_bcma_bus_powerup; + dev->device_is_enabled = b43_bus_bcma_device_is_enabled; + dev->device_enable = b43_bus_bcma_device_enable; + dev->device_disable = b43_bus_bcma_device_disable; + + dev->read16 = b43_bus_bcma_read16; + dev->read32 = b43_bus_bcma_read32; + dev->write16 = b43_bus_bcma_write16; + dev->write32 = b43_bus_bcma_write32; + dev->block_read = b43_bus_bcma_block_read; + dev->block_write = b43_bus_bcma_block_write; + + dev->dev = &core->dev; + dev->dma_dev = core->dma_dev; + dev->irq = core->irq; + + dev->board_vendor = core->bus->boardinfo.vendor; + dev->board_type = core->bus->boardinfo.type; + dev->board_rev = core->bus->sprom.board_rev; + + dev->chip_id = core->bus->chipinfo.id; + dev->chip_rev = core->bus->chipinfo.rev; + dev->chip_pkg = core->bus->chipinfo.pkg; + + dev->bus_sprom = &core->bus->sprom; + + dev->core_id = core->id.id; + dev->core_rev = core->id.rev; + + return dev; +} +#endif /* CPTCFG_B43_BCMA */ + +/* SSB */ +#ifdef CPTCFG_B43_SSB +static int b43_bus_ssb_bus_may_powerdown(struct b43_bus_dev *dev) +{ + return ssb_bus_may_powerdown(dev->sdev->bus); +} +static int b43_bus_ssb_bus_powerup(struct b43_bus_dev *dev, + bool dynamic_pctl) +{ + return ssb_bus_powerup(dev->sdev->bus, dynamic_pctl); +} +static int b43_bus_ssb_device_is_enabled(struct b43_bus_dev *dev) +{ + return ssb_device_is_enabled(dev->sdev); +} +static void b43_bus_ssb_device_enable(struct b43_bus_dev *dev, + u32 core_specific_flags) +{ + ssb_device_enable(dev->sdev, core_specific_flags); +} +static void b43_bus_ssb_device_disable(struct b43_bus_dev *dev, + u32 core_specific_flags) +{ + ssb_device_disable(dev->sdev, core_specific_flags); +} + +static u16 b43_bus_ssb_read16(struct b43_bus_dev *dev, u16 offset) +{ + return ssb_read16(dev->sdev, offset); +} +static u32 b43_bus_ssb_read32(struct b43_bus_dev *dev, u16 offset) +{ + return ssb_read32(dev->sdev, offset); +} +static void b43_bus_ssb_write16(struct b43_bus_dev *dev, u16 offset, u16 value) +{ + ssb_write16(dev->sdev, offset, value); +} +static void b43_bus_ssb_write32(struct b43_bus_dev *dev, u16 offset, u32 value) +{ + ssb_write32(dev->sdev, offset, value); +} +static void b43_bus_ssb_block_read(struct b43_bus_dev *dev, void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + ssb_block_read(dev->sdev, buffer, count, offset, reg_width); +} +static +void b43_bus_ssb_block_write(struct b43_bus_dev *dev, const void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + ssb_block_write(dev->sdev, buffer, count, offset, reg_width); +} + +struct b43_bus_dev *b43_bus_dev_ssb_init(struct ssb_device *sdev) +{ + struct b43_bus_dev *dev; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return NULL; + + dev->bus_type = B43_BUS_SSB; + dev->sdev = sdev; + + dev->bus_may_powerdown = b43_bus_ssb_bus_may_powerdown; + dev->bus_powerup = b43_bus_ssb_bus_powerup; + dev->device_is_enabled = b43_bus_ssb_device_is_enabled; + dev->device_enable = b43_bus_ssb_device_enable; + dev->device_disable = b43_bus_ssb_device_disable; + + dev->read16 = b43_bus_ssb_read16; + dev->read32 = b43_bus_ssb_read32; + dev->write16 = b43_bus_ssb_write16; + dev->write32 = b43_bus_ssb_write32; + dev->block_read = b43_bus_ssb_block_read; + dev->block_write = b43_bus_ssb_block_write; + + dev->dev = sdev->dev; + dev->dma_dev = sdev->dma_dev; + dev->irq = sdev->irq; + + dev->board_vendor = sdev->bus->boardinfo.vendor; + dev->board_type = sdev->bus->boardinfo.type; + dev->board_rev = sdev->bus->sprom.board_rev; + + dev->chip_id = sdev->bus->chip_id; + dev->chip_rev = sdev->bus->chip_rev; + dev->chip_pkg = sdev->bus->chip_package; + + dev->bus_sprom = &sdev->bus->sprom; + + dev->core_id = sdev->id.coreid; + dev->core_rev = sdev->id.revision; + + return dev; +} +#endif /* CPTCFG_B43_SSB */ + +void *b43_bus_get_wldev(struct b43_bus_dev *dev) +{ + switch (dev->bus_type) { +#ifdef CPTCFG_B43_BCMA + case B43_BUS_BCMA: + return bcma_get_drvdata(dev->bdev); +#endif +#ifdef CPTCFG_B43_SSB + case B43_BUS_SSB: + return ssb_get_drvdata(dev->sdev); +#endif + } + return NULL; +} + +void b43_bus_set_wldev(struct b43_bus_dev *dev, void *wldev) +{ + switch (dev->bus_type) { +#ifdef CPTCFG_B43_BCMA + case B43_BUS_BCMA: + bcma_set_drvdata(dev->bdev, wldev); + break; +#endif +#ifdef CPTCFG_B43_SSB + case B43_BUS_SSB: + ssb_set_drvdata(dev->sdev, wldev); + break; +#endif + } +} diff --git a/drivers/net/wireless/b43/bus.h b/drivers/net/wireless/b43/bus.h new file mode 100644 index 0000000..95eab46 --- /dev/null +++ b/drivers/net/wireless/b43/bus.h @@ -0,0 +1,70 @@ +#ifndef B43_BUS_H_ +#define B43_BUS_H_ + +enum b43_bus_type { +#ifdef CPTCFG_B43_BCMA + B43_BUS_BCMA, +#endif + B43_BUS_SSB, +}; + +struct b43_bus_dev { + enum b43_bus_type bus_type; + union { + struct bcma_device *bdev; + struct ssb_device *sdev; + }; + + int (*bus_may_powerdown)(struct b43_bus_dev *dev); + int (*bus_powerup)(struct b43_bus_dev *dev, bool dynamic_pctl); + int (*device_is_enabled)(struct b43_bus_dev *dev); + void (*device_enable)(struct b43_bus_dev *dev, + u32 core_specific_flags); + void (*device_disable)(struct b43_bus_dev *dev, + u32 core_specific_flags); + + u16 (*read16)(struct b43_bus_dev *dev, u16 offset); + u32 (*read32)(struct b43_bus_dev *dev, u16 offset); + void (*write16)(struct b43_bus_dev *dev, u16 offset, u16 value); + void (*write32)(struct b43_bus_dev *dev, u16 offset, u32 value); + void (*block_read)(struct b43_bus_dev *dev, void *buffer, + size_t count, u16 offset, u8 reg_width); + void (*block_write)(struct b43_bus_dev *dev, const void *buffer, + size_t count, u16 offset, u8 reg_width); + + struct device *dev; + struct device *dma_dev; + unsigned int irq; + + u16 board_vendor; + u16 board_type; + u16 board_rev; + + u16 chip_id; + u8 chip_rev; + u8 chip_pkg; + + struct ssb_sprom *bus_sprom; + + u16 core_id; + u8 core_rev; +}; + +static inline bool b43_bus_host_is_pcmcia(struct b43_bus_dev *dev) +{ + return (dev->bus_type == B43_BUS_SSB && + dev->sdev->bus->bustype == SSB_BUSTYPE_PCMCIA); +} +static inline bool b43_bus_host_is_sdio(struct b43_bus_dev *dev) +{ + return (dev->bus_type == B43_BUS_SSB && + dev->sdev->bus->bustype == SSB_BUSTYPE_SDIO); +} + +struct b43_bus_dev *b43_bus_dev_bcma_init(struct bcma_device *core); +struct b43_bus_dev *b43_bus_dev_ssb_init(struct ssb_device *sdev); + +void *b43_bus_get_wldev(struct b43_bus_dev *dev); +void b43_bus_set_wldev(struct b43_bus_dev *dev, void *data); + +#endif /* B43_BUS_H_ */ diff --git a/drivers/net/wireless/b43/debugfs.c b/drivers/net/wireless/b43/debugfs.c new file mode 100644 index 0000000..e807bd9 --- /dev/null +++ b/drivers/net/wireless/b43/debugfs.c @@ -0,0 +1,826 @@ +/* + + Broadcom B43 wireless driver + + debugfs driver debugging code + + Copyright (c) 2005-2007 Michael Buesch + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include +#include +#include +#include +#include +#include + +#include "b43.h" +#include "main.h" +#include "debugfs.h" +#include "dma.h" +#include "xmit.h" + + +/* The root directory. */ +static struct dentry *rootdir; + +struct b43_debugfs_fops { + ssize_t (*read)(struct b43_wldev *dev, char *buf, size_t bufsize); + int (*write)(struct b43_wldev *dev, const char *buf, size_t count); + struct file_operations fops; + /* Offset of struct b43_dfs_file in struct b43_dfsentry */ + size_t file_struct_offset; +}; + +static inline +struct b43_dfs_file *fops_to_dfs_file(struct b43_wldev *dev, + const struct b43_debugfs_fops *dfops) +{ + void *p; + + p = dev->dfsentry; + p += dfops->file_struct_offset; + + return p; +} + + +#define fappend(fmt, x...) \ + do { \ + if (bufsize - count) \ + count += snprintf(buf + count, \ + bufsize - count, \ + fmt , ##x); \ + else \ + printk(KERN_ERR "b43: fappend overflow\n"); \ + } while (0) + + +/* The biggest address values for SHM access from the debugfs files. */ +#define B43_MAX_SHM_ROUTING 4 +#define B43_MAX_SHM_ADDR 0xFFFF + +static ssize_t shm16read__read_file(struct b43_wldev *dev, + char *buf, size_t bufsize) +{ + ssize_t count = 0; + unsigned int routing, addr; + u16 val; + + routing = dev->dfsentry->shm16read_routing_next; + addr = dev->dfsentry->shm16read_addr_next; + if ((routing > B43_MAX_SHM_ROUTING) || + (addr > B43_MAX_SHM_ADDR)) + return -EDESTADDRREQ; + + val = b43_shm_read16(dev, routing, addr); + fappend("0x%04X\n", val); + + return count; +} + +static int shm16read__write_file(struct b43_wldev *dev, + const char *buf, size_t count) +{ + unsigned int routing, addr; + int res; + + res = sscanf(buf, "0x%X 0x%X", &routing, &addr); + if (res != 2) + return -EINVAL; + if (routing > B43_MAX_SHM_ROUTING) + return -EADDRNOTAVAIL; + if (addr > B43_MAX_SHM_ADDR) + return -EADDRNOTAVAIL; + if (routing == B43_SHM_SHARED) { + if ((addr % 2) != 0) + return -EADDRNOTAVAIL; + } + + dev->dfsentry->shm16read_routing_next = routing; + dev->dfsentry->shm16read_addr_next = addr; + + return 0; +} + +static int shm16write__write_file(struct b43_wldev *dev, + const char *buf, size_t count) +{ + unsigned int routing, addr, mask, set; + u16 val; + int res; + + res = sscanf(buf, "0x%X 0x%X 0x%X 0x%X", + &routing, &addr, &mask, &set); + if (res != 4) + return -EINVAL; + if (routing > B43_MAX_SHM_ROUTING) + return -EADDRNOTAVAIL; + if (addr > B43_MAX_SHM_ADDR) + return -EADDRNOTAVAIL; + if (routing == B43_SHM_SHARED) { + if ((addr % 2) != 0) + return -EADDRNOTAVAIL; + } + if ((mask > 0xFFFF) || (set > 0xFFFF)) + return -E2BIG; + + if (mask == 0) + val = 0; + else + val = b43_shm_read16(dev, routing, addr); + val &= mask; + val |= set; + b43_shm_write16(dev, routing, addr, val); + + return 0; +} + +static ssize_t shm32read__read_file(struct b43_wldev *dev, + char *buf, size_t bufsize) +{ + ssize_t count = 0; + unsigned int routing, addr; + u32 val; + + routing = dev->dfsentry->shm32read_routing_next; + addr = dev->dfsentry->shm32read_addr_next; + if ((routing > B43_MAX_SHM_ROUTING) || + (addr > B43_MAX_SHM_ADDR)) + return -EDESTADDRREQ; + + val = b43_shm_read32(dev, routing, addr); + fappend("0x%08X\n", val); + + return count; +} + +static int shm32read__write_file(struct b43_wldev *dev, + const char *buf, size_t count) +{ + unsigned int routing, addr; + int res; + + res = sscanf(buf, "0x%X 0x%X", &routing, &addr); + if (res != 2) + return -EINVAL; + if (routing > B43_MAX_SHM_ROUTING) + return -EADDRNOTAVAIL; + if (addr > B43_MAX_SHM_ADDR) + return -EADDRNOTAVAIL; + if (routing == B43_SHM_SHARED) { + if ((addr % 2) != 0) + return -EADDRNOTAVAIL; + } + + dev->dfsentry->shm32read_routing_next = routing; + dev->dfsentry->shm32read_addr_next = addr; + + return 0; +} + +static int shm32write__write_file(struct b43_wldev *dev, + const char *buf, size_t count) +{ + unsigned int routing, addr, mask, set; + u32 val; + int res; + + res = sscanf(buf, "0x%X 0x%X 0x%X 0x%X", + &routing, &addr, &mask, &set); + if (res != 4) + return -EINVAL; + if (routing > B43_MAX_SHM_ROUTING) + return -EADDRNOTAVAIL; + if (addr > B43_MAX_SHM_ADDR) + return -EADDRNOTAVAIL; + if (routing == B43_SHM_SHARED) { + if ((addr % 2) != 0) + return -EADDRNOTAVAIL; + } + if ((mask > 0xFFFFFFFF) || (set > 0xFFFFFFFF)) + return -E2BIG; + + if (mask == 0) + val = 0; + else + val = b43_shm_read32(dev, routing, addr); + val &= mask; + val |= set; + b43_shm_write32(dev, routing, addr, val); + + return 0; +} + +/* The biggest MMIO address that we allow access to from the debugfs files. */ +#define B43_MAX_MMIO_ACCESS (0xF00 - 1) + +static ssize_t mmio16read__read_file(struct b43_wldev *dev, + char *buf, size_t bufsize) +{ + ssize_t count = 0; + unsigned int addr; + u16 val; + + addr = dev->dfsentry->mmio16read_next; + if (addr > B43_MAX_MMIO_ACCESS) + return -EDESTADDRREQ; + + val = b43_read16(dev, addr); + fappend("0x%04X\n", val); + + return count; +} + +static int mmio16read__write_file(struct b43_wldev *dev, + const char *buf, size_t count) +{ + unsigned int addr; + int res; + + res = sscanf(buf, "0x%X", &addr); + if (res != 1) + return -EINVAL; + if (addr > B43_MAX_MMIO_ACCESS) + return -EADDRNOTAVAIL; + if ((addr % 2) != 0) + return -EINVAL; + + dev->dfsentry->mmio16read_next = addr; + + return 0; +} + +static int mmio16write__write_file(struct b43_wldev *dev, + const char *buf, size_t count) +{ + unsigned int addr, mask, set; + int res; + u16 val; + + res = sscanf(buf, "0x%X 0x%X 0x%X", &addr, &mask, &set); + if (res != 3) + return -EINVAL; + if (addr > B43_MAX_MMIO_ACCESS) + return -EADDRNOTAVAIL; + if ((mask > 0xFFFF) || (set > 0xFFFF)) + return -E2BIG; + if ((addr % 2) != 0) + return -EINVAL; + + if (mask == 0) + val = 0; + else + val = b43_read16(dev, addr); + val &= mask; + val |= set; + b43_write16(dev, addr, val); + + return 0; +} + +static ssize_t mmio32read__read_file(struct b43_wldev *dev, + char *buf, size_t bufsize) +{ + ssize_t count = 0; + unsigned int addr; + u32 val; + + addr = dev->dfsentry->mmio32read_next; + if (addr > B43_MAX_MMIO_ACCESS) + return -EDESTADDRREQ; + + val = b43_read32(dev, addr); + fappend("0x%08X\n", val); + + return count; +} + +static int mmio32read__write_file(struct b43_wldev *dev, + const char *buf, size_t count) +{ + unsigned int addr; + int res; + + res = sscanf(buf, "0x%X", &addr); + if (res != 1) + return -EINVAL; + if (addr > B43_MAX_MMIO_ACCESS) + return -EADDRNOTAVAIL; + if ((addr % 4) != 0) + return -EINVAL; + + dev->dfsentry->mmio32read_next = addr; + + return 0; +} + +static int mmio32write__write_file(struct b43_wldev *dev, + const char *buf, size_t count) +{ + unsigned int addr, mask, set; + int res; + u32 val; + + res = sscanf(buf, "0x%X 0x%X 0x%X", &addr, &mask, &set); + if (res != 3) + return -EINVAL; + if (addr > B43_MAX_MMIO_ACCESS) + return -EADDRNOTAVAIL; + if ((mask > 0xFFFFFFFF) || (set > 0xFFFFFFFF)) + return -E2BIG; + if ((addr % 4) != 0) + return -EINVAL; + + if (mask == 0) + val = 0; + else + val = b43_read32(dev, addr); + val &= mask; + val |= set; + b43_write32(dev, addr, val); + + return 0; +} + +static ssize_t txstat_read_file(struct b43_wldev *dev, + char *buf, size_t bufsize) +{ + struct b43_txstatus_log *log = &dev->dfsentry->txstatlog; + ssize_t count = 0; + int i, idx; + struct b43_txstatus *stat; + + if (log->end < 0) { + fappend("Nothing transmitted, yet\n"); + goto out; + } + fappend("b43 TX status reports:\n\n" + "index | cookie | seq | phy_stat | frame_count | " + "rts_count | supp_reason | pm_indicated | " + "intermediate | for_ampdu | acked\n" "---\n"); + i = log->end + 1; + idx = 0; + while (1) { + if (i == B43_NR_LOGGED_TXSTATUS) + i = 0; + stat = &(log->log[i]); + if (stat->cookie) { + fappend("%03d | " + "0x%04X | 0x%04X | 0x%02X | " + "0x%X | 0x%X | " + "%u | %u | " + "%u | %u | %u\n", + idx, + stat->cookie, stat->seq, stat->phy_stat, + stat->frame_count, stat->rts_count, + stat->supp_reason, stat->pm_indicated, + stat->intermediate, stat->for_ampdu, + stat->acked); + idx++; + } + if (i == log->end) + break; + i++; + } +out: + + return count; +} + +static int restart_write_file(struct b43_wldev *dev, + const char *buf, size_t count) +{ + int err = 0; + + if (count > 0 && buf[0] == '1') { + b43_controller_restart(dev, "manually restarted"); + } else + err = -EINVAL; + + return err; +} + +static unsigned long calc_expire_secs(unsigned long now, + unsigned long time, + unsigned long expire) +{ + expire = time + expire; + + if (time_after(now, expire)) + return 0; /* expired */ + if (expire < now) { + /* jiffies wrapped */ + expire -= MAX_JIFFY_OFFSET; + now -= MAX_JIFFY_OFFSET; + } + B43_WARN_ON(expire < now); + + return (expire - now) / HZ; +} + +static ssize_t loctls_read_file(struct b43_wldev *dev, + char *buf, size_t bufsize) +{ + ssize_t count = 0; + struct b43_txpower_lo_control *lo; + int i, err = 0; + struct b43_lo_calib *cal; + unsigned long now = jiffies; + struct b43_phy *phy = &dev->phy; + + if (phy->type != B43_PHYTYPE_G) { + fappend("Device is not a G-PHY\n"); + err = -ENODEV; + goto out; + } + lo = phy->g->lo_control; + fappend("-- Local Oscillator calibration data --\n\n"); + fappend("HW-power-control enabled: %d\n", + dev->phy.hardware_power_control); + fappend("TX Bias: 0x%02X, TX Magn: 0x%02X (expire in %lu sec)\n", + lo->tx_bias, lo->tx_magn, + calc_expire_secs(now, lo->txctl_measured_time, + B43_LO_TXCTL_EXPIRE)); + fappend("Power Vector: 0x%08X%08X (expires in %lu sec)\n", + (unsigned int)((lo->power_vector & 0xFFFFFFFF00000000ULL) >> 32), + (unsigned int)(lo->power_vector & 0x00000000FFFFFFFFULL), + calc_expire_secs(now, lo->pwr_vec_read_time, + B43_LO_PWRVEC_EXPIRE)); + + fappend("\nCalibrated settings:\n"); + list_for_each_entry(cal, &lo->calib_list, list) { + bool active; + + active = (b43_compare_bbatt(&cal->bbatt, &phy->g->bbatt) && + b43_compare_rfatt(&cal->rfatt, &phy->g->rfatt)); + fappend("BB(%d), RF(%d,%d) -> I=%d, Q=%d " + "(expires in %lu sec)%s\n", + cal->bbatt.att, + cal->rfatt.att, cal->rfatt.with_padmix, + cal->ctl.i, cal->ctl.q, + calc_expire_secs(now, cal->calib_time, + B43_LO_CALIB_EXPIRE), + active ? " ACTIVE" : ""); + } + + fappend("\nUsed RF attenuation values: Value(WithPadmix flag)\n"); + for (i = 0; i < lo->rfatt_list.len; i++) { + fappend("%u(%d), ", + lo->rfatt_list.list[i].att, + lo->rfatt_list.list[i].with_padmix); + } + fappend("\n"); + fappend("\nUsed Baseband attenuation values:\n"); + for (i = 0; i < lo->bbatt_list.len; i++) { + fappend("%u, ", + lo->bbatt_list.list[i].att); + } + fappend("\n"); + +out: + return err ? err : count; +} + +#undef fappend + +static ssize_t b43_debugfs_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct b43_wldev *dev; + struct b43_debugfs_fops *dfops; + struct b43_dfs_file *dfile; + ssize_t uninitialized_var(ret); + char *buf; + const size_t bufsize = 1024 * 16; /* 16 kiB buffer */ + const size_t buforder = get_order(bufsize); + int err = 0; + + if (!count) + return 0; + dev = file->private_data; + if (!dev) + return -ENODEV; + + mutex_lock(&dev->wl->mutex); + if (b43_status(dev) < B43_STAT_INITIALIZED) { + err = -ENODEV; + goto out_unlock; + } + + dfops = container_of(file->f_op, struct b43_debugfs_fops, fops); + if (!dfops->read) { + err = -ENOSYS; + goto out_unlock; + } + dfile = fops_to_dfs_file(dev, dfops); + + if (!dfile->buffer) { + buf = (char *)__get_free_pages(GFP_KERNEL, buforder); + if (!buf) { + err = -ENOMEM; + goto out_unlock; + } + memset(buf, 0, bufsize); + ret = dfops->read(dev, buf, bufsize); + if (ret <= 0) { + free_pages((unsigned long)buf, buforder); + err = ret; + goto out_unlock; + } + dfile->data_len = ret; + dfile->buffer = buf; + } + + ret = simple_read_from_buffer(userbuf, count, ppos, + dfile->buffer, + dfile->data_len); + if (*ppos >= dfile->data_len) { + free_pages((unsigned long)dfile->buffer, buforder); + dfile->buffer = NULL; + dfile->data_len = 0; + } +out_unlock: + mutex_unlock(&dev->wl->mutex); + + return err ? err : ret; +} + +static ssize_t b43_debugfs_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct b43_wldev *dev; + struct b43_debugfs_fops *dfops; + char *buf; + int err = 0; + + if (!count) + return 0; + if (count > PAGE_SIZE) + return -E2BIG; + dev = file->private_data; + if (!dev) + return -ENODEV; + + mutex_lock(&dev->wl->mutex); + if (b43_status(dev) < B43_STAT_INITIALIZED) { + err = -ENODEV; + goto out_unlock; + } + + dfops = container_of(file->f_op, struct b43_debugfs_fops, fops); + if (!dfops->write) { + err = -ENOSYS; + goto out_unlock; + } + + buf = (char *)get_zeroed_page(GFP_KERNEL); + if (!buf) { + err = -ENOMEM; + goto out_unlock; + } + if (copy_from_user(buf, userbuf, count)) { + err = -EFAULT; + goto out_freepage; + } + err = dfops->write(dev, buf, count); + if (err) + goto out_freepage; + +out_freepage: + free_page((unsigned long)buf); +out_unlock: + mutex_unlock(&dev->wl->mutex); + + return err ? err : count; +} + + +#define B43_DEBUGFS_FOPS(name, _read, _write) \ + static struct b43_debugfs_fops fops_##name = { \ + .read = _read, \ + .write = _write, \ + .fops = { \ + .open = simple_open, \ + .read = b43_debugfs_read, \ + .write = b43_debugfs_write, \ + .llseek = generic_file_llseek, \ + }, \ + .file_struct_offset = offsetof(struct b43_dfsentry, \ + file_##name), \ + } + +B43_DEBUGFS_FOPS(shm16read, shm16read__read_file, shm16read__write_file); +B43_DEBUGFS_FOPS(shm16write, NULL, shm16write__write_file); +B43_DEBUGFS_FOPS(shm32read, shm32read__read_file, shm32read__write_file); +B43_DEBUGFS_FOPS(shm32write, NULL, shm32write__write_file); +B43_DEBUGFS_FOPS(mmio16read, mmio16read__read_file, mmio16read__write_file); +B43_DEBUGFS_FOPS(mmio16write, NULL, mmio16write__write_file); +B43_DEBUGFS_FOPS(mmio32read, mmio32read__read_file, mmio32read__write_file); +B43_DEBUGFS_FOPS(mmio32write, NULL, mmio32write__write_file); +B43_DEBUGFS_FOPS(txstat, txstat_read_file, NULL); +B43_DEBUGFS_FOPS(restart, NULL, restart_write_file); +B43_DEBUGFS_FOPS(loctls, loctls_read_file, NULL); + + +bool b43_debug(struct b43_wldev *dev, enum b43_dyndbg feature) +{ + bool enabled; + + enabled = (dev->dfsentry && dev->dfsentry->dyn_debug[feature]); + if (unlikely(enabled)) { + /* Force full debugging messages, if the user enabled + * some dynamic debugging feature. */ + b43_modparam_verbose = B43_VERBOSITY_MAX; + } + + return enabled; +} + +static void b43_remove_dynamic_debug(struct b43_wldev *dev) +{ + struct b43_dfsentry *e = dev->dfsentry; + int i; + + for (i = 0; i < __B43_NR_DYNDBG; i++) + debugfs_remove(e->dyn_debug_dentries[i]); +} + +static void b43_add_dynamic_debug(struct b43_wldev *dev) +{ + struct b43_dfsentry *e = dev->dfsentry; + struct dentry *d; + +#define add_dyn_dbg(name, id, initstate) do { \ + e->dyn_debug[id] = (initstate); \ + d = debugfs_create_bool(name, 0600, e->subdir, \ + &(e->dyn_debug[id])); \ + if (!IS_ERR(d)) \ + e->dyn_debug_dentries[id] = d; \ + } while (0) + + add_dyn_dbg("debug_xmitpower", B43_DBG_XMITPOWER, 0); + add_dyn_dbg("debug_dmaoverflow", B43_DBG_DMAOVERFLOW, 0); + add_dyn_dbg("debug_dmaverbose", B43_DBG_DMAVERBOSE, 0); + add_dyn_dbg("debug_pwork_fast", B43_DBG_PWORK_FAST, 0); + add_dyn_dbg("debug_pwork_stop", B43_DBG_PWORK_STOP, 0); + add_dyn_dbg("debug_lo", B43_DBG_LO, 0); + add_dyn_dbg("debug_firmware", B43_DBG_FIRMWARE, 0); + add_dyn_dbg("debug_keys", B43_DBG_KEYS, 0); + add_dyn_dbg("debug_verbose_stats", B43_DBG_VERBOSESTATS, 0); + +#undef add_dyn_dbg +} + +void b43_debugfs_add_device(struct b43_wldev *dev) +{ + struct b43_dfsentry *e; + struct b43_txstatus_log *log; + char devdir[16]; + + B43_WARN_ON(!dev); + e = kzalloc(sizeof(*e), GFP_KERNEL); + if (!e) { + b43err(dev->wl, "debugfs: add device OOM\n"); + return; + } + e->dev = dev; + log = &e->txstatlog; + log->log = kcalloc(B43_NR_LOGGED_TXSTATUS, + sizeof(struct b43_txstatus), GFP_KERNEL); + if (!log->log) { + b43err(dev->wl, "debugfs: add device txstatus OOM\n"); + kfree(e); + return; + } + log->end = -1; + + dev->dfsentry = e; + + snprintf(devdir, sizeof(devdir), "%s", wiphy_name(dev->wl->hw->wiphy)); + e->subdir = debugfs_create_dir(devdir, rootdir); + if (!e->subdir || IS_ERR(e->subdir)) { + if (e->subdir == ERR_PTR(-ENODEV)) { + b43dbg(dev->wl, "DebugFS (CONFIG_DEBUG_FS) not " + "enabled in kernel config\n"); + } else { + b43err(dev->wl, "debugfs: cannot create %s directory\n", + devdir); + } + dev->dfsentry = NULL; + kfree(log->log); + kfree(e); + return; + } + + e->mmio16read_next = 0xFFFF; /* invalid address */ + e->mmio32read_next = 0xFFFF; /* invalid address */ + e->shm16read_routing_next = 0xFFFFFFFF; /* invalid routing */ + e->shm16read_addr_next = 0xFFFFFFFF; /* invalid address */ + e->shm32read_routing_next = 0xFFFFFFFF; /* invalid routing */ + e->shm32read_addr_next = 0xFFFFFFFF; /* invalid address */ + +#define ADD_FILE(name, mode) \ + do { \ + struct dentry *d; \ + d = debugfs_create_file(__stringify(name), \ + mode, e->subdir, dev, \ + &fops_##name.fops); \ + e->file_##name.dentry = NULL; \ + if (!IS_ERR(d)) \ + e->file_##name.dentry = d; \ + } while (0) + + + ADD_FILE(shm16read, 0600); + ADD_FILE(shm16write, 0200); + ADD_FILE(shm32read, 0600); + ADD_FILE(shm32write, 0200); + ADD_FILE(mmio16read, 0600); + ADD_FILE(mmio16write, 0200); + ADD_FILE(mmio32read, 0600); + ADD_FILE(mmio32write, 0200); + ADD_FILE(txstat, 0400); + ADD_FILE(restart, 0200); + ADD_FILE(loctls, 0400); + +#undef ADD_FILE + + b43_add_dynamic_debug(dev); +} + +void b43_debugfs_remove_device(struct b43_wldev *dev) +{ + struct b43_dfsentry *e; + + if (!dev) + return; + e = dev->dfsentry; + if (!e) + return; + b43_remove_dynamic_debug(dev); + + debugfs_remove(e->file_shm16read.dentry); + debugfs_remove(e->file_shm16write.dentry); + debugfs_remove(e->file_shm32read.dentry); + debugfs_remove(e->file_shm32write.dentry); + debugfs_remove(e->file_mmio16read.dentry); + debugfs_remove(e->file_mmio16write.dentry); + debugfs_remove(e->file_mmio32read.dentry); + debugfs_remove(e->file_mmio32write.dentry); + debugfs_remove(e->file_txstat.dentry); + debugfs_remove(e->file_restart.dentry); + debugfs_remove(e->file_loctls.dentry); + + debugfs_remove(e->subdir); + kfree(e->txstatlog.log); + kfree(e); +} + +void b43_debugfs_log_txstat(struct b43_wldev *dev, + const struct b43_txstatus *status) +{ + struct b43_dfsentry *e = dev->dfsentry; + struct b43_txstatus_log *log; + struct b43_txstatus *cur; + int i; + + if (!e) + return; + log = &e->txstatlog; + i = log->end + 1; + if (i == B43_NR_LOGGED_TXSTATUS) + i = 0; + log->end = i; + cur = &(log->log[i]); + memcpy(cur, status, sizeof(*cur)); +} + +void b43_debugfs_init(void) +{ + rootdir = debugfs_create_dir(KBUILD_MODNAME, NULL); + if (IS_ERR(rootdir)) + rootdir = NULL; +} + +void b43_debugfs_exit(void) +{ + debugfs_remove(rootdir); +} diff --git a/drivers/net/wireless/b43/debugfs.h b/drivers/net/wireless/b43/debugfs.h new file mode 100644 index 0000000..a8219b1 --- /dev/null +++ b/drivers/net/wireless/b43/debugfs.h @@ -0,0 +1,111 @@ +#ifndef B43_DEBUGFS_H_ +#define B43_DEBUGFS_H_ + +struct b43_wldev; +struct b43_txstatus; + +enum b43_dyndbg { /* Dynamic debugging features */ + B43_DBG_XMITPOWER, + B43_DBG_DMAOVERFLOW, + B43_DBG_DMAVERBOSE, + B43_DBG_PWORK_FAST, + B43_DBG_PWORK_STOP, + B43_DBG_LO, + B43_DBG_FIRMWARE, + B43_DBG_KEYS, + B43_DBG_VERBOSESTATS, + __B43_NR_DYNDBG, +}; + +#ifdef CPTCFG_B43_DEBUG + +struct dentry; + +#define B43_NR_LOGGED_TXSTATUS 100 + +struct b43_txstatus_log { + /* This structure is protected by wl->mutex */ + + struct b43_txstatus *log; + int end; +}; + +struct b43_dfs_file { + struct dentry *dentry; + char *buffer; + size_t data_len; +}; + +struct b43_dfsentry { + struct b43_wldev *dev; + struct dentry *subdir; + + struct b43_dfs_file file_shm16read; + struct b43_dfs_file file_shm16write; + struct b43_dfs_file file_shm32read; + struct b43_dfs_file file_shm32write; + struct b43_dfs_file file_mmio16read; + struct b43_dfs_file file_mmio16write; + struct b43_dfs_file file_mmio32read; + struct b43_dfs_file file_mmio32write; + struct b43_dfs_file file_txstat; + struct b43_dfs_file file_txpower_g; + struct b43_dfs_file file_restart; + struct b43_dfs_file file_loctls; + + struct b43_txstatus_log txstatlog; + + /* The cached address for the next mmio16read file read */ + u16 mmio16read_next; + /* The cached address for the next mmio32read file read */ + u16 mmio32read_next; + + /* The cached address for the next shm16read file read */ + u32 shm16read_routing_next; + u32 shm16read_addr_next; + /* The cached address for the next shm32read file read */ + u32 shm32read_routing_next; + u32 shm32read_addr_next; + + /* Enabled/Disabled list for the dynamic debugging features. */ + u32 dyn_debug[__B43_NR_DYNDBG]; + /* Dentries for the dynamic debugging entries. */ + struct dentry *dyn_debug_dentries[__B43_NR_DYNDBG]; +}; + +bool b43_debug(struct b43_wldev *dev, enum b43_dyndbg feature); + +void b43_debugfs_init(void); +void b43_debugfs_exit(void); +void b43_debugfs_add_device(struct b43_wldev *dev); +void b43_debugfs_remove_device(struct b43_wldev *dev); +void b43_debugfs_log_txstat(struct b43_wldev *dev, + const struct b43_txstatus *status); + +#else /* CPTCFG_B43_DEBUG */ + +static inline bool b43_debug(struct b43_wldev *dev, enum b43_dyndbg feature) +{ + return 0; +} + +static inline void b43_debugfs_init(void) +{ +} +static inline void b43_debugfs_exit(void) +{ +} +static inline void b43_debugfs_add_device(struct b43_wldev *dev) +{ +} +static inline void b43_debugfs_remove_device(struct b43_wldev *dev) +{ +} +static inline void b43_debugfs_log_txstat(struct b43_wldev *dev, + const struct b43_txstatus *status) +{ +} + +#endif /* CPTCFG_B43_DEBUG */ + +#endif /* B43_DEBUGFS_H_ */ diff --git a/drivers/net/wireless/b43/dma.c b/drivers/net/wireless/b43/dma.c new file mode 100644 index 0000000..3ae8bd1 --- /dev/null +++ b/drivers/net/wireless/b43/dma.c @@ -0,0 +1,1834 @@ +/* + + Broadcom B43 wireless driver + + DMA ringbuffer and descriptor allocation/management + + Copyright (c) 2005, 2006 Michael Buesch + + Some code in this file is derived from the b44.c driver + Copyright (C) 2002 David S. Miller + Copyright (C) Pekka Pietikainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" +#include "dma.h" +#include "main.h" +#include "debugfs.h" +#include "xmit.h" + +#include +#include +#include +#include +#include +#include +#include + + +/* Required number of TX DMA slots per TX frame. + * This currently is 2, because we put the header and the ieee80211 frame + * into separate slots. */ +#define TX_SLOTS_PER_FRAME 2 + +static u32 b43_dma_address(struct b43_dma *dma, dma_addr_t dmaaddr, + enum b43_addrtype addrtype) +{ + u32 uninitialized_var(addr); + + switch (addrtype) { + case B43_DMA_ADDR_LOW: + addr = lower_32_bits(dmaaddr); + if (dma->translation_in_low) { + addr &= ~SSB_DMA_TRANSLATION_MASK; + addr |= dma->translation; + } + break; + case B43_DMA_ADDR_HIGH: + addr = upper_32_bits(dmaaddr); + if (!dma->translation_in_low) { + addr &= ~SSB_DMA_TRANSLATION_MASK; + addr |= dma->translation; + } + break; + case B43_DMA_ADDR_EXT: + if (dma->translation_in_low) + addr = lower_32_bits(dmaaddr); + else + addr = upper_32_bits(dmaaddr); + addr &= SSB_DMA_TRANSLATION_MASK; + addr >>= SSB_DMA_TRANSLATION_SHIFT; + break; + } + + return addr; +} + +/* 32bit DMA ops. */ +static +struct b43_dmadesc_generic *op32_idx2desc(struct b43_dmaring *ring, + int slot, + struct b43_dmadesc_meta **meta) +{ + struct b43_dmadesc32 *desc; + + *meta = &(ring->meta[slot]); + desc = ring->descbase; + desc = &(desc[slot]); + + return (struct b43_dmadesc_generic *)desc; +} + +static void op32_fill_descriptor(struct b43_dmaring *ring, + struct b43_dmadesc_generic *desc, + dma_addr_t dmaaddr, u16 bufsize, + int start, int end, int irq) +{ + struct b43_dmadesc32 *descbase = ring->descbase; + int slot; + u32 ctl; + u32 addr; + u32 addrext; + + slot = (int)(&(desc->dma32) - descbase); + B43_WARN_ON(!(slot >= 0 && slot < ring->nr_slots)); + + addr = b43_dma_address(&ring->dev->dma, dmaaddr, B43_DMA_ADDR_LOW); + addrext = b43_dma_address(&ring->dev->dma, dmaaddr, B43_DMA_ADDR_EXT); + + ctl = bufsize & B43_DMA32_DCTL_BYTECNT; + if (slot == ring->nr_slots - 1) + ctl |= B43_DMA32_DCTL_DTABLEEND; + if (start) + ctl |= B43_DMA32_DCTL_FRAMESTART; + if (end) + ctl |= B43_DMA32_DCTL_FRAMEEND; + if (irq) + ctl |= B43_DMA32_DCTL_IRQ; + ctl |= (addrext << B43_DMA32_DCTL_ADDREXT_SHIFT) + & B43_DMA32_DCTL_ADDREXT_MASK; + + desc->dma32.control = cpu_to_le32(ctl); + desc->dma32.address = cpu_to_le32(addr); +} + +static void op32_poke_tx(struct b43_dmaring *ring, int slot) +{ + b43_dma_write(ring, B43_DMA32_TXINDEX, + (u32) (slot * sizeof(struct b43_dmadesc32))); +} + +static void op32_tx_suspend(struct b43_dmaring *ring) +{ + b43_dma_write(ring, B43_DMA32_TXCTL, b43_dma_read(ring, B43_DMA32_TXCTL) + | B43_DMA32_TXSUSPEND); +} + +static void op32_tx_resume(struct b43_dmaring *ring) +{ + b43_dma_write(ring, B43_DMA32_TXCTL, b43_dma_read(ring, B43_DMA32_TXCTL) + & ~B43_DMA32_TXSUSPEND); +} + +static int op32_get_current_rxslot(struct b43_dmaring *ring) +{ + u32 val; + + val = b43_dma_read(ring, B43_DMA32_RXSTATUS); + val &= B43_DMA32_RXDPTR; + + return (val / sizeof(struct b43_dmadesc32)); +} + +static void op32_set_current_rxslot(struct b43_dmaring *ring, int slot) +{ + b43_dma_write(ring, B43_DMA32_RXINDEX, + (u32) (slot * sizeof(struct b43_dmadesc32))); +} + +static const struct b43_dma_ops dma32_ops = { + .idx2desc = op32_idx2desc, + .fill_descriptor = op32_fill_descriptor, + .poke_tx = op32_poke_tx, + .tx_suspend = op32_tx_suspend, + .tx_resume = op32_tx_resume, + .get_current_rxslot = op32_get_current_rxslot, + .set_current_rxslot = op32_set_current_rxslot, +}; + +/* 64bit DMA ops. */ +static +struct b43_dmadesc_generic *op64_idx2desc(struct b43_dmaring *ring, + int slot, + struct b43_dmadesc_meta **meta) +{ + struct b43_dmadesc64 *desc; + + *meta = &(ring->meta[slot]); + desc = ring->descbase; + desc = &(desc[slot]); + + return (struct b43_dmadesc_generic *)desc; +} + +static void op64_fill_descriptor(struct b43_dmaring *ring, + struct b43_dmadesc_generic *desc, + dma_addr_t dmaaddr, u16 bufsize, + int start, int end, int irq) +{ + struct b43_dmadesc64 *descbase = ring->descbase; + int slot; + u32 ctl0 = 0, ctl1 = 0; + u32 addrlo, addrhi; + u32 addrext; + + slot = (int)(&(desc->dma64) - descbase); + B43_WARN_ON(!(slot >= 0 && slot < ring->nr_slots)); + + addrlo = b43_dma_address(&ring->dev->dma, dmaaddr, B43_DMA_ADDR_LOW); + addrhi = b43_dma_address(&ring->dev->dma, dmaaddr, B43_DMA_ADDR_HIGH); + addrext = b43_dma_address(&ring->dev->dma, dmaaddr, B43_DMA_ADDR_EXT); + + if (slot == ring->nr_slots - 1) + ctl0 |= B43_DMA64_DCTL0_DTABLEEND; + if (start) + ctl0 |= B43_DMA64_DCTL0_FRAMESTART; + if (end) + ctl0 |= B43_DMA64_DCTL0_FRAMEEND; + if (irq) + ctl0 |= B43_DMA64_DCTL0_IRQ; + ctl1 |= bufsize & B43_DMA64_DCTL1_BYTECNT; + ctl1 |= (addrext << B43_DMA64_DCTL1_ADDREXT_SHIFT) + & B43_DMA64_DCTL1_ADDREXT_MASK; + + desc->dma64.control0 = cpu_to_le32(ctl0); + desc->dma64.control1 = cpu_to_le32(ctl1); + desc->dma64.address_low = cpu_to_le32(addrlo); + desc->dma64.address_high = cpu_to_le32(addrhi); +} + +static void op64_poke_tx(struct b43_dmaring *ring, int slot) +{ + b43_dma_write(ring, B43_DMA64_TXINDEX, + (u32) (slot * sizeof(struct b43_dmadesc64))); +} + +static void op64_tx_suspend(struct b43_dmaring *ring) +{ + b43_dma_write(ring, B43_DMA64_TXCTL, b43_dma_read(ring, B43_DMA64_TXCTL) + | B43_DMA64_TXSUSPEND); +} + +static void op64_tx_resume(struct b43_dmaring *ring) +{ + b43_dma_write(ring, B43_DMA64_TXCTL, b43_dma_read(ring, B43_DMA64_TXCTL) + & ~B43_DMA64_TXSUSPEND); +} + +static int op64_get_current_rxslot(struct b43_dmaring *ring) +{ + u32 val; + + val = b43_dma_read(ring, B43_DMA64_RXSTATUS); + val &= B43_DMA64_RXSTATDPTR; + + return (val / sizeof(struct b43_dmadesc64)); +} + +static void op64_set_current_rxslot(struct b43_dmaring *ring, int slot) +{ + b43_dma_write(ring, B43_DMA64_RXINDEX, + (u32) (slot * sizeof(struct b43_dmadesc64))); +} + +static const struct b43_dma_ops dma64_ops = { + .idx2desc = op64_idx2desc, + .fill_descriptor = op64_fill_descriptor, + .poke_tx = op64_poke_tx, + .tx_suspend = op64_tx_suspend, + .tx_resume = op64_tx_resume, + .get_current_rxslot = op64_get_current_rxslot, + .set_current_rxslot = op64_set_current_rxslot, +}; + +static inline int free_slots(struct b43_dmaring *ring) +{ + return (ring->nr_slots - ring->used_slots); +} + +static inline int next_slot(struct b43_dmaring *ring, int slot) +{ + B43_WARN_ON(!(slot >= -1 && slot <= ring->nr_slots - 1)); + if (slot == ring->nr_slots - 1) + return 0; + return slot + 1; +} + +static inline int prev_slot(struct b43_dmaring *ring, int slot) +{ + B43_WARN_ON(!(slot >= 0 && slot <= ring->nr_slots - 1)); + if (slot == 0) + return ring->nr_slots - 1; + return slot - 1; +} + +#ifdef CPTCFG_B43_DEBUG +static void update_max_used_slots(struct b43_dmaring *ring, + int current_used_slots) +{ + if (current_used_slots <= ring->max_used_slots) + return; + ring->max_used_slots = current_used_slots; + if (b43_debug(ring->dev, B43_DBG_DMAVERBOSE)) { + b43dbg(ring->dev->wl, + "max_used_slots increased to %d on %s ring %d\n", + ring->max_used_slots, + ring->tx ? "TX" : "RX", ring->index); + } +} +#else +static inline + void update_max_used_slots(struct b43_dmaring *ring, int current_used_slots) +{ +} +#endif /* DEBUG */ + +/* Request a slot for usage. */ +static inline int request_slot(struct b43_dmaring *ring) +{ + int slot; + + B43_WARN_ON(!ring->tx); + B43_WARN_ON(ring->stopped); + B43_WARN_ON(free_slots(ring) == 0); + + slot = next_slot(ring, ring->current_slot); + ring->current_slot = slot; + ring->used_slots++; + + update_max_used_slots(ring, ring->used_slots); + + return slot; +} + +static u16 b43_dmacontroller_base(enum b43_dmatype type, int controller_idx) +{ + static const u16 map64[] = { + B43_MMIO_DMA64_BASE0, + B43_MMIO_DMA64_BASE1, + B43_MMIO_DMA64_BASE2, + B43_MMIO_DMA64_BASE3, + B43_MMIO_DMA64_BASE4, + B43_MMIO_DMA64_BASE5, + }; + static const u16 map32[] = { + B43_MMIO_DMA32_BASE0, + B43_MMIO_DMA32_BASE1, + B43_MMIO_DMA32_BASE2, + B43_MMIO_DMA32_BASE3, + B43_MMIO_DMA32_BASE4, + B43_MMIO_DMA32_BASE5, + }; + + if (type == B43_DMA_64BIT) { + B43_WARN_ON(!(controller_idx >= 0 && + controller_idx < ARRAY_SIZE(map64))); + return map64[controller_idx]; + } + B43_WARN_ON(!(controller_idx >= 0 && + controller_idx < ARRAY_SIZE(map32))); + return map32[controller_idx]; +} + +static inline + dma_addr_t map_descbuffer(struct b43_dmaring *ring, + unsigned char *buf, size_t len, int tx) +{ + dma_addr_t dmaaddr; + + if (tx) { + dmaaddr = dma_map_single(ring->dev->dev->dma_dev, + buf, len, DMA_TO_DEVICE); + } else { + dmaaddr = dma_map_single(ring->dev->dev->dma_dev, + buf, len, DMA_FROM_DEVICE); + } + + return dmaaddr; +} + +static inline + void unmap_descbuffer(struct b43_dmaring *ring, + dma_addr_t addr, size_t len, int tx) +{ + if (tx) { + dma_unmap_single(ring->dev->dev->dma_dev, + addr, len, DMA_TO_DEVICE); + } else { + dma_unmap_single(ring->dev->dev->dma_dev, + addr, len, DMA_FROM_DEVICE); + } +} + +static inline + void sync_descbuffer_for_cpu(struct b43_dmaring *ring, + dma_addr_t addr, size_t len) +{ + B43_WARN_ON(ring->tx); + dma_sync_single_for_cpu(ring->dev->dev->dma_dev, + addr, len, DMA_FROM_DEVICE); +} + +static inline + void sync_descbuffer_for_device(struct b43_dmaring *ring, + dma_addr_t addr, size_t len) +{ + B43_WARN_ON(ring->tx); + dma_sync_single_for_device(ring->dev->dev->dma_dev, + addr, len, DMA_FROM_DEVICE); +} + +static inline + void free_descriptor_buffer(struct b43_dmaring *ring, + struct b43_dmadesc_meta *meta) +{ + if (meta->skb) { + if (ring->tx) + ieee80211_free_txskb(ring->dev->wl->hw, meta->skb); + else + dev_kfree_skb_any(meta->skb); + meta->skb = NULL; + } +} + +static int alloc_ringmemory(struct b43_dmaring *ring) +{ + /* The specs call for 4K buffers for 30- and 32-bit DMA with 4K + * alignment and 8K buffers for 64-bit DMA with 8K alignment. + * In practice we could use smaller buffers for the latter, but the + * alignment is really important because of the hardware bug. If bit + * 0x00001000 is used in DMA address, some hardware (like BCM4331) + * copies that bit into B43_DMA64_RXSTATUS and we get false values from + * B43_DMA64_RXSTATDPTR. Let's just use 8K buffers even if we don't use + * more than 256 slots for ring. + */ + u16 ring_mem_size = (ring->type == B43_DMA_64BIT) ? + B43_DMA64_RINGMEMSIZE : B43_DMA32_RINGMEMSIZE; + + ring->descbase = dma_alloc_coherent(ring->dev->dev->dma_dev, + ring_mem_size, &(ring->dmabase), + GFP_KERNEL | __GFP_ZERO); + if (!ring->descbase) + return -ENOMEM; + + return 0; +} + +static void free_ringmemory(struct b43_dmaring *ring) +{ + u16 ring_mem_size = (ring->type == B43_DMA_64BIT) ? + B43_DMA64_RINGMEMSIZE : B43_DMA32_RINGMEMSIZE; + dma_free_coherent(ring->dev->dev->dma_dev, ring_mem_size, + ring->descbase, ring->dmabase); +} + +/* Reset the RX DMA channel */ +static int b43_dmacontroller_rx_reset(struct b43_wldev *dev, u16 mmio_base, + enum b43_dmatype type) +{ + int i; + u32 value; + u16 offset; + + might_sleep(); + + offset = (type == B43_DMA_64BIT) ? B43_DMA64_RXCTL : B43_DMA32_RXCTL; + b43_write32(dev, mmio_base + offset, 0); + for (i = 0; i < 10; i++) { + offset = (type == B43_DMA_64BIT) ? B43_DMA64_RXSTATUS : + B43_DMA32_RXSTATUS; + value = b43_read32(dev, mmio_base + offset); + if (type == B43_DMA_64BIT) { + value &= B43_DMA64_RXSTAT; + if (value == B43_DMA64_RXSTAT_DISABLED) { + i = -1; + break; + } + } else { + value &= B43_DMA32_RXSTATE; + if (value == B43_DMA32_RXSTAT_DISABLED) { + i = -1; + break; + } + } + msleep(1); + } + if (i != -1) { + b43err(dev->wl, "DMA RX reset timed out\n"); + return -ENODEV; + } + + return 0; +} + +/* Reset the TX DMA channel */ +static int b43_dmacontroller_tx_reset(struct b43_wldev *dev, u16 mmio_base, + enum b43_dmatype type) +{ + int i; + u32 value; + u16 offset; + + might_sleep(); + + for (i = 0; i < 10; i++) { + offset = (type == B43_DMA_64BIT) ? B43_DMA64_TXSTATUS : + B43_DMA32_TXSTATUS; + value = b43_read32(dev, mmio_base + offset); + if (type == B43_DMA_64BIT) { + value &= B43_DMA64_TXSTAT; + if (value == B43_DMA64_TXSTAT_DISABLED || + value == B43_DMA64_TXSTAT_IDLEWAIT || + value == B43_DMA64_TXSTAT_STOPPED) + break; + } else { + value &= B43_DMA32_TXSTATE; + if (value == B43_DMA32_TXSTAT_DISABLED || + value == B43_DMA32_TXSTAT_IDLEWAIT || + value == B43_DMA32_TXSTAT_STOPPED) + break; + } + msleep(1); + } + offset = (type == B43_DMA_64BIT) ? B43_DMA64_TXCTL : B43_DMA32_TXCTL; + b43_write32(dev, mmio_base + offset, 0); + for (i = 0; i < 10; i++) { + offset = (type == B43_DMA_64BIT) ? B43_DMA64_TXSTATUS : + B43_DMA32_TXSTATUS; + value = b43_read32(dev, mmio_base + offset); + if (type == B43_DMA_64BIT) { + value &= B43_DMA64_TXSTAT; + if (value == B43_DMA64_TXSTAT_DISABLED) { + i = -1; + break; + } + } else { + value &= B43_DMA32_TXSTATE; + if (value == B43_DMA32_TXSTAT_DISABLED) { + i = -1; + break; + } + } + msleep(1); + } + if (i != -1) { + b43err(dev->wl, "DMA TX reset timed out\n"); + return -ENODEV; + } + /* ensure the reset is completed. */ + msleep(1); + + return 0; +} + +/* Check if a DMA mapping address is invalid. */ +static bool b43_dma_mapping_error(struct b43_dmaring *ring, + dma_addr_t addr, + size_t buffersize, bool dma_to_device) +{ + if (unlikely(dma_mapping_error(ring->dev->dev->dma_dev, addr))) + return 1; + + switch (ring->type) { + case B43_DMA_30BIT: + if ((u64)addr + buffersize > (1ULL << 30)) + goto address_error; + break; + case B43_DMA_32BIT: + if ((u64)addr + buffersize > (1ULL << 32)) + goto address_error; + break; + case B43_DMA_64BIT: + /* Currently we can't have addresses beyond + * 64bit in the kernel. */ + break; + } + + /* The address is OK. */ + return 0; + +address_error: + /* We can't support this address. Unmap it again. */ + unmap_descbuffer(ring, addr, buffersize, dma_to_device); + + return 1; +} + +static bool b43_rx_buffer_is_poisoned(struct b43_dmaring *ring, struct sk_buff *skb) +{ + unsigned char *f = skb->data + ring->frameoffset; + + return ((f[0] & f[1] & f[2] & f[3] & f[4] & f[5] & f[6] & f[7]) == 0xFF); +} + +static void b43_poison_rx_buffer(struct b43_dmaring *ring, struct sk_buff *skb) +{ + struct b43_rxhdr_fw4 *rxhdr; + unsigned char *frame; + + /* This poisons the RX buffer to detect DMA failures. */ + + rxhdr = (struct b43_rxhdr_fw4 *)(skb->data); + rxhdr->frame_len = 0; + + B43_WARN_ON(ring->rx_buffersize < ring->frameoffset + sizeof(struct b43_plcp_hdr6) + 2); + frame = skb->data + ring->frameoffset; + memset(frame, 0xFF, sizeof(struct b43_plcp_hdr6) + 2 /* padding */); +} + +static int setup_rx_descbuffer(struct b43_dmaring *ring, + struct b43_dmadesc_generic *desc, + struct b43_dmadesc_meta *meta, gfp_t gfp_flags) +{ + dma_addr_t dmaaddr; + struct sk_buff *skb; + + B43_WARN_ON(ring->tx); + + skb = __dev_alloc_skb(ring->rx_buffersize, gfp_flags); + if (unlikely(!skb)) + return -ENOMEM; + b43_poison_rx_buffer(ring, skb); + dmaaddr = map_descbuffer(ring, skb->data, ring->rx_buffersize, 0); + if (b43_dma_mapping_error(ring, dmaaddr, ring->rx_buffersize, 0)) { + /* ugh. try to realloc in zone_dma */ + gfp_flags |= GFP_DMA; + + dev_kfree_skb_any(skb); + + skb = __dev_alloc_skb(ring->rx_buffersize, gfp_flags); + if (unlikely(!skb)) + return -ENOMEM; + b43_poison_rx_buffer(ring, skb); + dmaaddr = map_descbuffer(ring, skb->data, + ring->rx_buffersize, 0); + if (b43_dma_mapping_error(ring, dmaaddr, ring->rx_buffersize, 0)) { + b43err(ring->dev->wl, "RX DMA buffer allocation failed\n"); + dev_kfree_skb_any(skb); + return -EIO; + } + } + + meta->skb = skb; + meta->dmaaddr = dmaaddr; + ring->ops->fill_descriptor(ring, desc, dmaaddr, + ring->rx_buffersize, 0, 0, 0); + + return 0; +} + +/* Allocate the initial descbuffers. + * This is used for an RX ring only. + */ +static int alloc_initial_descbuffers(struct b43_dmaring *ring) +{ + int i, err = -ENOMEM; + struct b43_dmadesc_generic *desc; + struct b43_dmadesc_meta *meta; + + for (i = 0; i < ring->nr_slots; i++) { + desc = ring->ops->idx2desc(ring, i, &meta); + + err = setup_rx_descbuffer(ring, desc, meta, GFP_KERNEL); + if (err) { + b43err(ring->dev->wl, + "Failed to allocate initial descbuffers\n"); + goto err_unwind; + } + } + mb(); + ring->used_slots = ring->nr_slots; + err = 0; + out: + return err; + + err_unwind: + for (i--; i >= 0; i--) { + desc = ring->ops->idx2desc(ring, i, &meta); + + unmap_descbuffer(ring, meta->dmaaddr, ring->rx_buffersize, 0); + dev_kfree_skb(meta->skb); + } + goto out; +} + +/* Do initial setup of the DMA controller. + * Reset the controller, write the ring busaddress + * and switch the "enable" bit on. + */ +static int dmacontroller_setup(struct b43_dmaring *ring) +{ + int err = 0; + u32 value; + u32 addrext; + bool parity = ring->dev->dma.parity; + u32 addrlo; + u32 addrhi; + + if (ring->tx) { + if (ring->type == B43_DMA_64BIT) { + u64 ringbase = (u64) (ring->dmabase); + addrext = b43_dma_address(&ring->dev->dma, ringbase, B43_DMA_ADDR_EXT); + addrlo = b43_dma_address(&ring->dev->dma, ringbase, B43_DMA_ADDR_LOW); + addrhi = b43_dma_address(&ring->dev->dma, ringbase, B43_DMA_ADDR_HIGH); + + value = B43_DMA64_TXENABLE; + value |= (addrext << B43_DMA64_TXADDREXT_SHIFT) + & B43_DMA64_TXADDREXT_MASK; + if (!parity) + value |= B43_DMA64_TXPARITYDISABLE; + b43_dma_write(ring, B43_DMA64_TXCTL, value); + b43_dma_write(ring, B43_DMA64_TXRINGLO, addrlo); + b43_dma_write(ring, B43_DMA64_TXRINGHI, addrhi); + } else { + u32 ringbase = (u32) (ring->dmabase); + addrext = b43_dma_address(&ring->dev->dma, ringbase, B43_DMA_ADDR_EXT); + addrlo = b43_dma_address(&ring->dev->dma, ringbase, B43_DMA_ADDR_LOW); + + value = B43_DMA32_TXENABLE; + value |= (addrext << B43_DMA32_TXADDREXT_SHIFT) + & B43_DMA32_TXADDREXT_MASK; + if (!parity) + value |= B43_DMA32_TXPARITYDISABLE; + b43_dma_write(ring, B43_DMA32_TXCTL, value); + b43_dma_write(ring, B43_DMA32_TXRING, addrlo); + } + } else { + err = alloc_initial_descbuffers(ring); + if (err) + goto out; + if (ring->type == B43_DMA_64BIT) { + u64 ringbase = (u64) (ring->dmabase); + addrext = b43_dma_address(&ring->dev->dma, ringbase, B43_DMA_ADDR_EXT); + addrlo = b43_dma_address(&ring->dev->dma, ringbase, B43_DMA_ADDR_LOW); + addrhi = b43_dma_address(&ring->dev->dma, ringbase, B43_DMA_ADDR_HIGH); + + value = (ring->frameoffset << B43_DMA64_RXFROFF_SHIFT); + value |= B43_DMA64_RXENABLE; + value |= (addrext << B43_DMA64_RXADDREXT_SHIFT) + & B43_DMA64_RXADDREXT_MASK; + if (!parity) + value |= B43_DMA64_RXPARITYDISABLE; + b43_dma_write(ring, B43_DMA64_RXCTL, value); + b43_dma_write(ring, B43_DMA64_RXRINGLO, addrlo); + b43_dma_write(ring, B43_DMA64_RXRINGHI, addrhi); + b43_dma_write(ring, B43_DMA64_RXINDEX, ring->nr_slots * + sizeof(struct b43_dmadesc64)); + } else { + u32 ringbase = (u32) (ring->dmabase); + addrext = b43_dma_address(&ring->dev->dma, ringbase, B43_DMA_ADDR_EXT); + addrlo = b43_dma_address(&ring->dev->dma, ringbase, B43_DMA_ADDR_LOW); + + value = (ring->frameoffset << B43_DMA32_RXFROFF_SHIFT); + value |= B43_DMA32_RXENABLE; + value |= (addrext << B43_DMA32_RXADDREXT_SHIFT) + & B43_DMA32_RXADDREXT_MASK; + if (!parity) + value |= B43_DMA32_RXPARITYDISABLE; + b43_dma_write(ring, B43_DMA32_RXCTL, value); + b43_dma_write(ring, B43_DMA32_RXRING, addrlo); + b43_dma_write(ring, B43_DMA32_RXINDEX, ring->nr_slots * + sizeof(struct b43_dmadesc32)); + } + } + +out: + return err; +} + +/* Shutdown the DMA controller. */ +static void dmacontroller_cleanup(struct b43_dmaring *ring) +{ + if (ring->tx) { + b43_dmacontroller_tx_reset(ring->dev, ring->mmio_base, + ring->type); + if (ring->type == B43_DMA_64BIT) { + b43_dma_write(ring, B43_DMA64_TXRINGLO, 0); + b43_dma_write(ring, B43_DMA64_TXRINGHI, 0); + } else + b43_dma_write(ring, B43_DMA32_TXRING, 0); + } else { + b43_dmacontroller_rx_reset(ring->dev, ring->mmio_base, + ring->type); + if (ring->type == B43_DMA_64BIT) { + b43_dma_write(ring, B43_DMA64_RXRINGLO, 0); + b43_dma_write(ring, B43_DMA64_RXRINGHI, 0); + } else + b43_dma_write(ring, B43_DMA32_RXRING, 0); + } +} + +static void free_all_descbuffers(struct b43_dmaring *ring) +{ + struct b43_dmadesc_meta *meta; + int i; + + if (!ring->used_slots) + return; + for (i = 0; i < ring->nr_slots; i++) { + /* get meta - ignore returned value */ + ring->ops->idx2desc(ring, i, &meta); + + if (!meta->skb || b43_dma_ptr_is_poisoned(meta->skb)) { + B43_WARN_ON(!ring->tx); + continue; + } + if (ring->tx) { + unmap_descbuffer(ring, meta->dmaaddr, + meta->skb->len, 1); + } else { + unmap_descbuffer(ring, meta->dmaaddr, + ring->rx_buffersize, 0); + } + free_descriptor_buffer(ring, meta); + } +} + +static u64 supported_dma_mask(struct b43_wldev *dev) +{ + u32 tmp; + u16 mmio_base; + + switch (dev->dev->bus_type) { +#ifdef CPTCFG_B43_BCMA + case B43_BUS_BCMA: + tmp = bcma_aread32(dev->dev->bdev, BCMA_IOST); + if (tmp & BCMA_IOST_DMA64) + return DMA_BIT_MASK(64); + break; +#endif +#ifdef CPTCFG_B43_SSB + case B43_BUS_SSB: + tmp = ssb_read32(dev->dev->sdev, SSB_TMSHIGH); + if (tmp & SSB_TMSHIGH_DMA64) + return DMA_BIT_MASK(64); + break; +#endif + } + + mmio_base = b43_dmacontroller_base(0, 0); + b43_write32(dev, mmio_base + B43_DMA32_TXCTL, B43_DMA32_TXADDREXT_MASK); + tmp = b43_read32(dev, mmio_base + B43_DMA32_TXCTL); + if (tmp & B43_DMA32_TXADDREXT_MASK) + return DMA_BIT_MASK(32); + + return DMA_BIT_MASK(30); +} + +static enum b43_dmatype dma_mask_to_engine_type(u64 dmamask) +{ + if (dmamask == DMA_BIT_MASK(30)) + return B43_DMA_30BIT; + if (dmamask == DMA_BIT_MASK(32)) + return B43_DMA_32BIT; + if (dmamask == DMA_BIT_MASK(64)) + return B43_DMA_64BIT; + B43_WARN_ON(1); + return B43_DMA_30BIT; +} + +/* Main initialization function. */ +static +struct b43_dmaring *b43_setup_dmaring(struct b43_wldev *dev, + int controller_index, + int for_tx, + enum b43_dmatype type) +{ + struct b43_dmaring *ring; + int i, err; + dma_addr_t dma_test; + + ring = kzalloc(sizeof(*ring), GFP_KERNEL); + if (!ring) + goto out; + + ring->nr_slots = B43_RXRING_SLOTS; + if (for_tx) + ring->nr_slots = B43_TXRING_SLOTS; + + ring->meta = kcalloc(ring->nr_slots, sizeof(struct b43_dmadesc_meta), + GFP_KERNEL); + if (!ring->meta) + goto err_kfree_ring; + for (i = 0; i < ring->nr_slots; i++) + ring->meta->skb = B43_DMA_PTR_POISON; + + ring->type = type; + ring->dev = dev; + ring->mmio_base = b43_dmacontroller_base(type, controller_index); + ring->index = controller_index; + if (type == B43_DMA_64BIT) + ring->ops = &dma64_ops; + else + ring->ops = &dma32_ops; + if (for_tx) { + ring->tx = true; + ring->current_slot = -1; + } else { + if (ring->index == 0) { + switch (dev->fw.hdr_format) { + case B43_FW_HDR_598: + ring->rx_buffersize = B43_DMA0_RX_FW598_BUFSIZE; + ring->frameoffset = B43_DMA0_RX_FW598_FO; + break; + case B43_FW_HDR_410: + case B43_FW_HDR_351: + ring->rx_buffersize = B43_DMA0_RX_FW351_BUFSIZE; + ring->frameoffset = B43_DMA0_RX_FW351_FO; + break; + } + } else + B43_WARN_ON(1); + } +#ifdef CPTCFG_B43_DEBUG + ring->last_injected_overflow = jiffies; +#endif + + if (for_tx) { + /* Assumption: B43_TXRING_SLOTS can be divided by TX_SLOTS_PER_FRAME */ + BUILD_BUG_ON(B43_TXRING_SLOTS % TX_SLOTS_PER_FRAME != 0); + + ring->txhdr_cache = kcalloc(ring->nr_slots / TX_SLOTS_PER_FRAME, + b43_txhdr_size(dev), + GFP_KERNEL); + if (!ring->txhdr_cache) + goto err_kfree_meta; + + /* test for ability to dma to txhdr_cache */ + dma_test = dma_map_single(dev->dev->dma_dev, + ring->txhdr_cache, + b43_txhdr_size(dev), + DMA_TO_DEVICE); + + if (b43_dma_mapping_error(ring, dma_test, + b43_txhdr_size(dev), 1)) { + /* ugh realloc */ + kfree(ring->txhdr_cache); + ring->txhdr_cache = kcalloc(ring->nr_slots / TX_SLOTS_PER_FRAME, + b43_txhdr_size(dev), + GFP_KERNEL | GFP_DMA); + if (!ring->txhdr_cache) + goto err_kfree_meta; + + dma_test = dma_map_single(dev->dev->dma_dev, + ring->txhdr_cache, + b43_txhdr_size(dev), + DMA_TO_DEVICE); + + if (b43_dma_mapping_error(ring, dma_test, + b43_txhdr_size(dev), 1)) { + + b43err(dev->wl, + "TXHDR DMA allocation failed\n"); + goto err_kfree_txhdr_cache; + } + } + + dma_unmap_single(dev->dev->dma_dev, + dma_test, b43_txhdr_size(dev), + DMA_TO_DEVICE); + } + + err = alloc_ringmemory(ring); + if (err) + goto err_kfree_txhdr_cache; + err = dmacontroller_setup(ring); + if (err) + goto err_free_ringmemory; + + out: + return ring; + + err_free_ringmemory: + free_ringmemory(ring); + err_kfree_txhdr_cache: + kfree(ring->txhdr_cache); + err_kfree_meta: + kfree(ring->meta); + err_kfree_ring: + kfree(ring); + ring = NULL; + goto out; +} + +#define divide(a, b) ({ \ + typeof(a) __a = a; \ + do_div(__a, b); \ + __a; \ + }) + +#define modulo(a, b) ({ \ + typeof(a) __a = a; \ + do_div(__a, b); \ + }) + +/* Main cleanup function. */ +static void b43_destroy_dmaring(struct b43_dmaring *ring, + const char *ringname) +{ + if (!ring) + return; + +#ifdef CPTCFG_B43_DEBUG + { + /* Print some statistics. */ + u64 failed_packets = ring->nr_failed_tx_packets; + u64 succeed_packets = ring->nr_succeed_tx_packets; + u64 nr_packets = failed_packets + succeed_packets; + u64 permille_failed = 0, average_tries = 0; + + if (nr_packets) + permille_failed = divide(failed_packets * 1000, nr_packets); + if (nr_packets) + average_tries = divide(ring->nr_total_packet_tries * 100, nr_packets); + + b43dbg(ring->dev->wl, "DMA-%u %s: " + "Used slots %d/%d, Failed frames %llu/%llu = %llu.%01llu%%, " + "Average tries %llu.%02llu\n", + (unsigned int)(ring->type), ringname, + ring->max_used_slots, + ring->nr_slots, + (unsigned long long)failed_packets, + (unsigned long long)nr_packets, + (unsigned long long)divide(permille_failed, 10), + (unsigned long long)modulo(permille_failed, 10), + (unsigned long long)divide(average_tries, 100), + (unsigned long long)modulo(average_tries, 100)); + } +#endif /* DEBUG */ + + /* Device IRQs are disabled prior entering this function, + * so no need to take care of concurrency with rx handler stuff. + */ + dmacontroller_cleanup(ring); + free_all_descbuffers(ring); + free_ringmemory(ring); + + kfree(ring->txhdr_cache); + kfree(ring->meta); + kfree(ring); +} + +#define destroy_ring(dma, ring) do { \ + b43_destroy_dmaring((dma)->ring, __stringify(ring)); \ + (dma)->ring = NULL; \ + } while (0) + +void b43_dma_free(struct b43_wldev *dev) +{ + struct b43_dma *dma; + + if (b43_using_pio_transfers(dev)) + return; + dma = &dev->dma; + + destroy_ring(dma, rx_ring); + destroy_ring(dma, tx_ring_AC_BK); + destroy_ring(dma, tx_ring_AC_BE); + destroy_ring(dma, tx_ring_AC_VI); + destroy_ring(dma, tx_ring_AC_VO); + destroy_ring(dma, tx_ring_mcast); +} + +static int b43_dma_set_mask(struct b43_wldev *dev, u64 mask) +{ + u64 orig_mask = mask; + bool fallback = false; + int err; + + /* Try to set the DMA mask. If it fails, try falling back to a + * lower mask, as we can always also support a lower one. */ + while (1) { + err = dma_set_mask(dev->dev->dma_dev, mask); + if (!err) { + err = dma_set_coherent_mask(dev->dev->dma_dev, mask); + if (!err) + break; + } + if (mask == DMA_BIT_MASK(64)) { + mask = DMA_BIT_MASK(32); + fallback = true; + continue; + } + if (mask == DMA_BIT_MASK(32)) { + mask = DMA_BIT_MASK(30); + fallback = true; + continue; + } + b43err(dev->wl, "The machine/kernel does not support " + "the required %u-bit DMA mask\n", + (unsigned int)dma_mask_to_engine_type(orig_mask)); + return -EOPNOTSUPP; + } + if (fallback) { + b43info(dev->wl, "DMA mask fallback from %u-bit to %u-bit\n", + (unsigned int)dma_mask_to_engine_type(orig_mask), + (unsigned int)dma_mask_to_engine_type(mask)); + } + + return 0; +} + +/* Some hardware with 64-bit DMA seems to be bugged and looks for translation + * bit in low address word instead of high one. + */ +static bool b43_dma_translation_in_low_word(struct b43_wldev *dev, + enum b43_dmatype type) +{ + if (type != B43_DMA_64BIT) + return 1; + +#ifdef CPTCFG_B43_SSB + if (dev->dev->bus_type == B43_BUS_SSB && + dev->dev->sdev->bus->bustype == SSB_BUSTYPE_PCI && + !(pci_is_pcie(dev->dev->sdev->bus->host_pci) && + ssb_read32(dev->dev->sdev, SSB_TMSHIGH) & SSB_TMSHIGH_DMA64)) + return 1; +#endif + return 0; +} + +int b43_dma_init(struct b43_wldev *dev) +{ + struct b43_dma *dma = &dev->dma; + int err; + u64 dmamask; + enum b43_dmatype type; + + dmamask = supported_dma_mask(dev); + type = dma_mask_to_engine_type(dmamask); + err = b43_dma_set_mask(dev, dmamask); + if (err) + return err; + + switch (dev->dev->bus_type) { +#ifdef CPTCFG_B43_BCMA + case B43_BUS_BCMA: + dma->translation = bcma_core_dma_translation(dev->dev->bdev); + break; +#endif +#ifdef CPTCFG_B43_SSB + case B43_BUS_SSB: + dma->translation = ssb_dma_translation(dev->dev->sdev); + break; +#endif + } + dma->translation_in_low = b43_dma_translation_in_low_word(dev, type); + + dma->parity = true; +#ifdef CPTCFG_B43_BCMA + /* TODO: find out which SSB devices need disabling parity */ + if (dev->dev->bus_type == B43_BUS_BCMA) + dma->parity = false; +#endif + + err = -ENOMEM; + /* setup TX DMA channels. */ + dma->tx_ring_AC_BK = b43_setup_dmaring(dev, 0, 1, type); + if (!dma->tx_ring_AC_BK) + goto out; + + dma->tx_ring_AC_BE = b43_setup_dmaring(dev, 1, 1, type); + if (!dma->tx_ring_AC_BE) + goto err_destroy_bk; + + dma->tx_ring_AC_VI = b43_setup_dmaring(dev, 2, 1, type); + if (!dma->tx_ring_AC_VI) + goto err_destroy_be; + + dma->tx_ring_AC_VO = b43_setup_dmaring(dev, 3, 1, type); + if (!dma->tx_ring_AC_VO) + goto err_destroy_vi; + + dma->tx_ring_mcast = b43_setup_dmaring(dev, 4, 1, type); + if (!dma->tx_ring_mcast) + goto err_destroy_vo; + + /* setup RX DMA channel. */ + dma->rx_ring = b43_setup_dmaring(dev, 0, 0, type); + if (!dma->rx_ring) + goto err_destroy_mcast; + + /* No support for the TX status DMA ring. */ + B43_WARN_ON(dev->dev->core_rev < 5); + + b43dbg(dev->wl, "%u-bit DMA initialized\n", + (unsigned int)type); + err = 0; +out: + return err; + +err_destroy_mcast: + destroy_ring(dma, tx_ring_mcast); +err_destroy_vo: + destroy_ring(dma, tx_ring_AC_VO); +err_destroy_vi: + destroy_ring(dma, tx_ring_AC_VI); +err_destroy_be: + destroy_ring(dma, tx_ring_AC_BE); +err_destroy_bk: + destroy_ring(dma, tx_ring_AC_BK); + return err; +} + +/* Generate a cookie for the TX header. */ +static u16 generate_cookie(struct b43_dmaring *ring, int slot) +{ + u16 cookie; + + /* Use the upper 4 bits of the cookie as + * DMA controller ID and store the slot number + * in the lower 12 bits. + * Note that the cookie must never be 0, as this + * is a special value used in RX path. + * It can also not be 0xFFFF because that is special + * for multicast frames. + */ + cookie = (((u16)ring->index + 1) << 12); + B43_WARN_ON(slot & ~0x0FFF); + cookie |= (u16)slot; + + return cookie; +} + +/* Inspect a cookie and find out to which controller/slot it belongs. */ +static +struct b43_dmaring *parse_cookie(struct b43_wldev *dev, u16 cookie, int *slot) +{ + struct b43_dma *dma = &dev->dma; + struct b43_dmaring *ring = NULL; + + switch (cookie & 0xF000) { + case 0x1000: + ring = dma->tx_ring_AC_BK; + break; + case 0x2000: + ring = dma->tx_ring_AC_BE; + break; + case 0x3000: + ring = dma->tx_ring_AC_VI; + break; + case 0x4000: + ring = dma->tx_ring_AC_VO; + break; + case 0x5000: + ring = dma->tx_ring_mcast; + break; + } + *slot = (cookie & 0x0FFF); + if (unlikely(!ring || *slot < 0 || *slot >= ring->nr_slots)) { + b43dbg(dev->wl, "TX-status contains " + "invalid cookie: 0x%04X\n", cookie); + return NULL; + } + + return ring; +} + +static int dma_tx_fragment(struct b43_dmaring *ring, + struct sk_buff *skb) +{ + const struct b43_dma_ops *ops = ring->ops; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct b43_private_tx_info *priv_info = b43_get_priv_tx_info(info); + u8 *header; + int slot, old_top_slot, old_used_slots; + int err; + struct b43_dmadesc_generic *desc; + struct b43_dmadesc_meta *meta; + struct b43_dmadesc_meta *meta_hdr; + u16 cookie; + size_t hdrsize = b43_txhdr_size(ring->dev); + + /* Important note: If the number of used DMA slots per TX frame + * is changed here, the TX_SLOTS_PER_FRAME definition at the top of + * the file has to be updated, too! + */ + + old_top_slot = ring->current_slot; + old_used_slots = ring->used_slots; + + /* Get a slot for the header. */ + slot = request_slot(ring); + desc = ops->idx2desc(ring, slot, &meta_hdr); + memset(meta_hdr, 0, sizeof(*meta_hdr)); + + header = &(ring->txhdr_cache[(slot / TX_SLOTS_PER_FRAME) * hdrsize]); + cookie = generate_cookie(ring, slot); + err = b43_generate_txhdr(ring->dev, header, + skb, info, cookie); + if (unlikely(err)) { + ring->current_slot = old_top_slot; + ring->used_slots = old_used_slots; + return err; + } + + meta_hdr->dmaaddr = map_descbuffer(ring, (unsigned char *)header, + hdrsize, 1); + if (b43_dma_mapping_error(ring, meta_hdr->dmaaddr, hdrsize, 1)) { + ring->current_slot = old_top_slot; + ring->used_slots = old_used_slots; + return -EIO; + } + ops->fill_descriptor(ring, desc, meta_hdr->dmaaddr, + hdrsize, 1, 0, 0); + + /* Get a slot for the payload. */ + slot = request_slot(ring); + desc = ops->idx2desc(ring, slot, &meta); + memset(meta, 0, sizeof(*meta)); + + meta->skb = skb; + meta->is_last_fragment = true; + priv_info->bouncebuffer = NULL; + + meta->dmaaddr = map_descbuffer(ring, skb->data, skb->len, 1); + /* create a bounce buffer in zone_dma on mapping failure. */ + if (b43_dma_mapping_error(ring, meta->dmaaddr, skb->len, 1)) { + priv_info->bouncebuffer = kmemdup(skb->data, skb->len, + GFP_ATOMIC | GFP_DMA); + if (!priv_info->bouncebuffer) { + ring->current_slot = old_top_slot; + ring->used_slots = old_used_slots; + err = -ENOMEM; + goto out_unmap_hdr; + } + + meta->dmaaddr = map_descbuffer(ring, priv_info->bouncebuffer, skb->len, 1); + if (b43_dma_mapping_error(ring, meta->dmaaddr, skb->len, 1)) { + kfree(priv_info->bouncebuffer); + priv_info->bouncebuffer = NULL; + ring->current_slot = old_top_slot; + ring->used_slots = old_used_slots; + err = -EIO; + goto out_unmap_hdr; + } + } + + ops->fill_descriptor(ring, desc, meta->dmaaddr, skb->len, 0, 1, 1); + + if (info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM) { + /* Tell the firmware about the cookie of the last + * mcast frame, so it can clear the more-data bit in it. */ + b43_shm_write16(ring->dev, B43_SHM_SHARED, + B43_SHM_SH_MCASTCOOKIE, cookie); + } + /* Now transfer the whole frame. */ + wmb(); + ops->poke_tx(ring, next_slot(ring, slot)); + return 0; + +out_unmap_hdr: + unmap_descbuffer(ring, meta_hdr->dmaaddr, + hdrsize, 1); + return err; +} + +static inline int should_inject_overflow(struct b43_dmaring *ring) +{ +#ifdef CPTCFG_B43_DEBUG + if (unlikely(b43_debug(ring->dev, B43_DBG_DMAOVERFLOW))) { + /* Check if we should inject another ringbuffer overflow + * to test handling of this situation in the stack. */ + unsigned long next_overflow; + + next_overflow = ring->last_injected_overflow + HZ; + if (time_after(jiffies, next_overflow)) { + ring->last_injected_overflow = jiffies; + b43dbg(ring->dev->wl, + "Injecting TX ring overflow on " + "DMA controller %d\n", ring->index); + return 1; + } + } +#endif /* CPTCFG_B43_DEBUG */ + return 0; +} + +/* Static mapping of mac80211's queues (priorities) to b43 DMA rings. */ +static struct b43_dmaring *select_ring_by_priority(struct b43_wldev *dev, + u8 queue_prio) +{ + struct b43_dmaring *ring; + + if (dev->qos_enabled) { + /* 0 = highest priority */ + switch (queue_prio) { + default: + B43_WARN_ON(1); + /* fallthrough */ + case 0: + ring = dev->dma.tx_ring_AC_VO; + break; + case 1: + ring = dev->dma.tx_ring_AC_VI; + break; + case 2: + ring = dev->dma.tx_ring_AC_BE; + break; + case 3: + ring = dev->dma.tx_ring_AC_BK; + break; + } + } else + ring = dev->dma.tx_ring_AC_BE; + + return ring; +} + +int b43_dma_tx(struct b43_wldev *dev, struct sk_buff *skb) +{ + struct b43_dmaring *ring; + struct ieee80211_hdr *hdr; + int err = 0; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + hdr = (struct ieee80211_hdr *)skb->data; + if (info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM) { + /* The multicast ring will be sent after the DTIM */ + ring = dev->dma.tx_ring_mcast; + /* Set the more-data bit. Ucode will clear it on + * the last frame for us. */ + hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_MOREDATA); + } else { + /* Decide by priority where to put this frame. */ + ring = select_ring_by_priority( + dev, skb_get_queue_mapping(skb)); + } + + B43_WARN_ON(!ring->tx); + + if (unlikely(ring->stopped)) { + /* We get here only because of a bug in mac80211. + * Because of a race, one packet may be queued after + * the queue is stopped, thus we got called when we shouldn't. + * For now, just refuse the transmit. */ + if (b43_debug(dev, B43_DBG_DMAVERBOSE)) + b43err(dev->wl, "Packet after queue stopped\n"); + err = -ENOSPC; + goto out; + } + + if (unlikely(WARN_ON(free_slots(ring) < TX_SLOTS_PER_FRAME))) { + /* If we get here, we have a real error with the queue + * full, but queues not stopped. */ + b43err(dev->wl, "DMA queue overflow\n"); + err = -ENOSPC; + goto out; + } + + /* Assign the queue number to the ring (if not already done before) + * so TX status handling can use it. The queue to ring mapping is + * static, so we don't need to store it per frame. */ + ring->queue_prio = skb_get_queue_mapping(skb); + + err = dma_tx_fragment(ring, skb); + if (unlikely(err == -ENOKEY)) { + /* Drop this packet, as we don't have the encryption key + * anymore and must not transmit it unencrypted. */ + ieee80211_free_txskb(dev->wl->hw, skb); + err = 0; + goto out; + } + if (unlikely(err)) { + b43err(dev->wl, "DMA tx mapping failure\n"); + goto out; + } + if ((free_slots(ring) < TX_SLOTS_PER_FRAME) || + should_inject_overflow(ring)) { + /* This TX ring is full. */ + unsigned int skb_mapping = skb_get_queue_mapping(skb); + ieee80211_stop_queue(dev->wl->hw, skb_mapping); + dev->wl->tx_queue_stopped[skb_mapping] = 1; + ring->stopped = true; + if (b43_debug(dev, B43_DBG_DMAVERBOSE)) { + b43dbg(dev->wl, "Stopped TX ring %d\n", ring->index); + } + } +out: + + return err; +} + +void b43_dma_handle_txstatus(struct b43_wldev *dev, + const struct b43_txstatus *status) +{ + const struct b43_dma_ops *ops; + struct b43_dmaring *ring; + struct b43_dmadesc_meta *meta; + static const struct b43_txstatus fake; /* filled with 0 */ + const struct b43_txstatus *txstat; + int slot, firstused; + bool frame_succeed; + int skip; + static u8 err_out1, err_out2; + + ring = parse_cookie(dev, status->cookie, &slot); + if (unlikely(!ring)) + return; + B43_WARN_ON(!ring->tx); + + /* Sanity check: TX packets are processed in-order on one ring. + * Check if the slot deduced from the cookie really is the first + * used slot. */ + firstused = ring->current_slot - ring->used_slots + 1; + if (firstused < 0) + firstused = ring->nr_slots + firstused; + + skip = 0; + if (unlikely(slot != firstused)) { + /* This possibly is a firmware bug and will result in + * malfunction, memory leaks and/or stall of DMA functionality. + */ + if (slot == next_slot(ring, next_slot(ring, firstused))) { + /* If a single header/data pair was missed, skip over + * the first two slots in an attempt to recover. + */ + slot = firstused; + skip = 2; + if (!err_out1) { + /* Report the error once. */ + b43dbg(dev->wl, + "Skip on DMA ring %d slot %d.\n", + ring->index, slot); + err_out1 = 1; + } + } else { + /* More than a single header/data pair were missed. + * Report this error once. + */ + if (!err_out2) + b43dbg(dev->wl, + "Out of order TX status report on DMA ring %d. Expected %d, but got %d\n", + ring->index, firstused, slot); + err_out2 = 1; + return; + } + } + + ops = ring->ops; + while (1) { + B43_WARN_ON(slot < 0 || slot >= ring->nr_slots); + /* get meta - ignore returned value */ + ops->idx2desc(ring, slot, &meta); + + if (b43_dma_ptr_is_poisoned(meta->skb)) { + b43dbg(dev->wl, "Poisoned TX slot %d (first=%d) " + "on ring %d\n", + slot, firstused, ring->index); + break; + } + + if (meta->skb) { + struct b43_private_tx_info *priv_info = + b43_get_priv_tx_info(IEEE80211_SKB_CB(meta->skb)); + + unmap_descbuffer(ring, meta->dmaaddr, + meta->skb->len, 1); + kfree(priv_info->bouncebuffer); + priv_info->bouncebuffer = NULL; + } else { + unmap_descbuffer(ring, meta->dmaaddr, + b43_txhdr_size(dev), 1); + } + + if (meta->is_last_fragment) { + struct ieee80211_tx_info *info; + + if (unlikely(!meta->skb)) { + /* This is a scatter-gather fragment of a frame, + * so the skb pointer must not be NULL. + */ + b43dbg(dev->wl, "TX status unexpected NULL skb " + "at slot %d (first=%d) on ring %d\n", + slot, firstused, ring->index); + break; + } + + info = IEEE80211_SKB_CB(meta->skb); + + /* + * Call back to inform the ieee80211 subsystem about + * the status of the transmission. When skipping over + * a missed TX status report, use a status structure + * filled with zeros to indicate that the frame was not + * sent (frame_count 0) and not acknowledged + */ + if (unlikely(skip)) + txstat = &fake; + else + txstat = status; + + frame_succeed = b43_fill_txstatus_report(dev, info, + txstat); +#ifdef CPTCFG_B43_DEBUG + if (frame_succeed) + ring->nr_succeed_tx_packets++; + else + ring->nr_failed_tx_packets++; + ring->nr_total_packet_tries += status->frame_count; +#endif /* DEBUG */ + ieee80211_tx_status(dev->wl->hw, meta->skb); + + /* skb will be freed by ieee80211_tx_status(). + * Poison our pointer. */ + meta->skb = B43_DMA_PTR_POISON; + } else { + /* No need to call free_descriptor_buffer here, as + * this is only the txhdr, which is not allocated. + */ + if (unlikely(meta->skb)) { + b43dbg(dev->wl, "TX status unexpected non-NULL skb " + "at slot %d (first=%d) on ring %d\n", + slot, firstused, ring->index); + break; + } + } + + /* Everything unmapped and free'd. So it's not used anymore. */ + ring->used_slots--; + + if (meta->is_last_fragment && !skip) { + /* This is the last scatter-gather + * fragment of the frame. We are done. */ + break; + } + slot = next_slot(ring, slot); + if (skip > 0) + --skip; + } + if (ring->stopped) { + B43_WARN_ON(free_slots(ring) < TX_SLOTS_PER_FRAME); + ring->stopped = false; + } + + if (dev->wl->tx_queue_stopped[ring->queue_prio]) { + dev->wl->tx_queue_stopped[ring->queue_prio] = 0; + } else { + /* If the driver queue is running wake the corresponding + * mac80211 queue. */ + ieee80211_wake_queue(dev->wl->hw, ring->queue_prio); + if (b43_debug(dev, B43_DBG_DMAVERBOSE)) { + b43dbg(dev->wl, "Woke up TX ring %d\n", ring->index); + } + } + /* Add work to the queue. */ + ieee80211_queue_work(dev->wl->hw, &dev->wl->tx_work); +} + +static void dma_rx(struct b43_dmaring *ring, int *slot) +{ + const struct b43_dma_ops *ops = ring->ops; + struct b43_dmadesc_generic *desc; + struct b43_dmadesc_meta *meta; + struct b43_rxhdr_fw4 *rxhdr; + struct sk_buff *skb; + u16 len; + int err; + dma_addr_t dmaaddr; + + desc = ops->idx2desc(ring, *slot, &meta); + + sync_descbuffer_for_cpu(ring, meta->dmaaddr, ring->rx_buffersize); + skb = meta->skb; + + rxhdr = (struct b43_rxhdr_fw4 *)skb->data; + len = le16_to_cpu(rxhdr->frame_len); + if (len == 0) { + int i = 0; + + do { + udelay(2); + barrier(); + len = le16_to_cpu(rxhdr->frame_len); + } while (len == 0 && i++ < 5); + if (unlikely(len == 0)) { + dmaaddr = meta->dmaaddr; + goto drop_recycle_buffer; + } + } + if (unlikely(b43_rx_buffer_is_poisoned(ring, skb))) { + /* Something went wrong with the DMA. + * The device did not touch the buffer and did not overwrite the poison. */ + b43dbg(ring->dev->wl, "DMA RX: Dropping poisoned buffer.\n"); + dmaaddr = meta->dmaaddr; + goto drop_recycle_buffer; + } + if (unlikely(len + ring->frameoffset > ring->rx_buffersize)) { + /* The data did not fit into one descriptor buffer + * and is split over multiple buffers. + * This should never happen, as we try to allocate buffers + * big enough. So simply ignore this packet. + */ + int cnt = 0; + s32 tmp = len; + + while (1) { + desc = ops->idx2desc(ring, *slot, &meta); + /* recycle the descriptor buffer. */ + b43_poison_rx_buffer(ring, meta->skb); + sync_descbuffer_for_device(ring, meta->dmaaddr, + ring->rx_buffersize); + *slot = next_slot(ring, *slot); + cnt++; + tmp -= ring->rx_buffersize; + if (tmp <= 0) + break; + } + b43err(ring->dev->wl, "DMA RX buffer too small " + "(len: %u, buffer: %u, nr-dropped: %d)\n", + len, ring->rx_buffersize, cnt); + goto drop; + } + + dmaaddr = meta->dmaaddr; + err = setup_rx_descbuffer(ring, desc, meta, GFP_ATOMIC); + if (unlikely(err)) { + b43dbg(ring->dev->wl, "DMA RX: setup_rx_descbuffer() failed\n"); + goto drop_recycle_buffer; + } + + unmap_descbuffer(ring, dmaaddr, ring->rx_buffersize, 0); + skb_put(skb, len + ring->frameoffset); + skb_pull(skb, ring->frameoffset); + + b43_rx(ring->dev, skb, rxhdr); +drop: + return; + +drop_recycle_buffer: + /* Poison and recycle the RX buffer. */ + b43_poison_rx_buffer(ring, skb); + sync_descbuffer_for_device(ring, dmaaddr, ring->rx_buffersize); +} + +void b43_dma_handle_rx_overflow(struct b43_dmaring *ring) +{ + int current_slot, previous_slot; + + B43_WARN_ON(ring->tx); + + /* Device has filled all buffers, drop all packets and let TCP + * decrease speed. + * Decrement RX index by one will let the device to see all slots + * as free again + */ + /* + *TODO: How to increase rx_drop in mac80211? + */ + current_slot = ring->ops->get_current_rxslot(ring); + previous_slot = prev_slot(ring, current_slot); + ring->ops->set_current_rxslot(ring, previous_slot); +} + +void b43_dma_rx(struct b43_dmaring *ring) +{ + const struct b43_dma_ops *ops = ring->ops; + int slot, current_slot; + int used_slots = 0; + + B43_WARN_ON(ring->tx); + current_slot = ops->get_current_rxslot(ring); + B43_WARN_ON(!(current_slot >= 0 && current_slot < ring->nr_slots)); + + slot = ring->current_slot; + for (; slot != current_slot; slot = next_slot(ring, slot)) { + dma_rx(ring, &slot); + update_max_used_slots(ring, ++used_slots); + } + wmb(); + ops->set_current_rxslot(ring, slot); + ring->current_slot = slot; +} + +static void b43_dma_tx_suspend_ring(struct b43_dmaring *ring) +{ + B43_WARN_ON(!ring->tx); + ring->ops->tx_suspend(ring); +} + +static void b43_dma_tx_resume_ring(struct b43_dmaring *ring) +{ + B43_WARN_ON(!ring->tx); + ring->ops->tx_resume(ring); +} + +void b43_dma_tx_suspend(struct b43_wldev *dev) +{ + b43_power_saving_ctl_bits(dev, B43_PS_AWAKE); + b43_dma_tx_suspend_ring(dev->dma.tx_ring_AC_BK); + b43_dma_tx_suspend_ring(dev->dma.tx_ring_AC_BE); + b43_dma_tx_suspend_ring(dev->dma.tx_ring_AC_VI); + b43_dma_tx_suspend_ring(dev->dma.tx_ring_AC_VO); + b43_dma_tx_suspend_ring(dev->dma.tx_ring_mcast); +} + +void b43_dma_tx_resume(struct b43_wldev *dev) +{ + b43_dma_tx_resume_ring(dev->dma.tx_ring_mcast); + b43_dma_tx_resume_ring(dev->dma.tx_ring_AC_VO); + b43_dma_tx_resume_ring(dev->dma.tx_ring_AC_VI); + b43_dma_tx_resume_ring(dev->dma.tx_ring_AC_BE); + b43_dma_tx_resume_ring(dev->dma.tx_ring_AC_BK); + b43_power_saving_ctl_bits(dev, 0); +} + +static void direct_fifo_rx(struct b43_wldev *dev, enum b43_dmatype type, + u16 mmio_base, bool enable) +{ + u32 ctl; + + if (type == B43_DMA_64BIT) { + ctl = b43_read32(dev, mmio_base + B43_DMA64_RXCTL); + ctl &= ~B43_DMA64_RXDIRECTFIFO; + if (enable) + ctl |= B43_DMA64_RXDIRECTFIFO; + b43_write32(dev, mmio_base + B43_DMA64_RXCTL, ctl); + } else { + ctl = b43_read32(dev, mmio_base + B43_DMA32_RXCTL); + ctl &= ~B43_DMA32_RXDIRECTFIFO; + if (enable) + ctl |= B43_DMA32_RXDIRECTFIFO; + b43_write32(dev, mmio_base + B43_DMA32_RXCTL, ctl); + } +} + +/* Enable/Disable Direct FIFO Receive Mode (PIO) on a RX engine. + * This is called from PIO code, so DMA structures are not available. */ +void b43_dma_direct_fifo_rx(struct b43_wldev *dev, + unsigned int engine_index, bool enable) +{ + enum b43_dmatype type; + u16 mmio_base; + + type = dma_mask_to_engine_type(supported_dma_mask(dev)); + + mmio_base = b43_dmacontroller_base(type, engine_index); + direct_fifo_rx(dev, type, mmio_base, enable); +} diff --git a/drivers/net/wireless/b43/dma.h b/drivers/net/wireless/b43/dma.h new file mode 100644 index 0000000..9252bfe --- /dev/null +++ b/drivers/net/wireless/b43/dma.h @@ -0,0 +1,305 @@ +#ifndef B43_DMA_H_ +#define B43_DMA_H_ + +#include + +#include "b43.h" + + +/* DMA-Interrupt reasons. */ +#define B43_DMAIRQ_FATALMASK ((1 << 10) | (1 << 11) | (1 << 12) \ + | (1 << 14) | (1 << 15)) +#define B43_DMAIRQ_RDESC_UFLOW (1 << 13) +#define B43_DMAIRQ_RX_DONE (1 << 16) + +/*** 32-bit DMA Engine. ***/ + +/* 32-bit DMA controller registers. */ +#define B43_DMA32_TXCTL 0x00 +#define B43_DMA32_TXENABLE 0x00000001 +#define B43_DMA32_TXSUSPEND 0x00000002 +#define B43_DMA32_TXLOOPBACK 0x00000004 +#define B43_DMA32_TXFLUSH 0x00000010 +#define B43_DMA32_TXPARITYDISABLE 0x00000800 +#define B43_DMA32_TXADDREXT_MASK 0x00030000 +#define B43_DMA32_TXADDREXT_SHIFT 16 +#define B43_DMA32_TXRING 0x04 +#define B43_DMA32_TXINDEX 0x08 +#define B43_DMA32_TXSTATUS 0x0C +#define B43_DMA32_TXDPTR 0x00000FFF +#define B43_DMA32_TXSTATE 0x0000F000 +#define B43_DMA32_TXSTAT_DISABLED 0x00000000 +#define B43_DMA32_TXSTAT_ACTIVE 0x00001000 +#define B43_DMA32_TXSTAT_IDLEWAIT 0x00002000 +#define B43_DMA32_TXSTAT_STOPPED 0x00003000 +#define B43_DMA32_TXSTAT_SUSP 0x00004000 +#define B43_DMA32_TXERROR 0x000F0000 +#define B43_DMA32_TXERR_NOERR 0x00000000 +#define B43_DMA32_TXERR_PROT 0x00010000 +#define B43_DMA32_TXERR_UNDERRUN 0x00020000 +#define B43_DMA32_TXERR_BUFREAD 0x00030000 +#define B43_DMA32_TXERR_DESCREAD 0x00040000 +#define B43_DMA32_TXACTIVE 0xFFF00000 +#define B43_DMA32_RXCTL 0x10 +#define B43_DMA32_RXENABLE 0x00000001 +#define B43_DMA32_RXFROFF_MASK 0x000000FE +#define B43_DMA32_RXFROFF_SHIFT 1 +#define B43_DMA32_RXDIRECTFIFO 0x00000100 +#define B43_DMA32_RXPARITYDISABLE 0x00000800 +#define B43_DMA32_RXADDREXT_MASK 0x00030000 +#define B43_DMA32_RXADDREXT_SHIFT 16 +#define B43_DMA32_RXRING 0x14 +#define B43_DMA32_RXINDEX 0x18 +#define B43_DMA32_RXSTATUS 0x1C +#define B43_DMA32_RXDPTR 0x00000FFF +#define B43_DMA32_RXSTATE 0x0000F000 +#define B43_DMA32_RXSTAT_DISABLED 0x00000000 +#define B43_DMA32_RXSTAT_ACTIVE 0x00001000 +#define B43_DMA32_RXSTAT_IDLEWAIT 0x00002000 +#define B43_DMA32_RXSTAT_STOPPED 0x00003000 +#define B43_DMA32_RXERROR 0x000F0000 +#define B43_DMA32_RXERR_NOERR 0x00000000 +#define B43_DMA32_RXERR_PROT 0x00010000 +#define B43_DMA32_RXERR_OVERFLOW 0x00020000 +#define B43_DMA32_RXERR_BUFWRITE 0x00030000 +#define B43_DMA32_RXERR_DESCREAD 0x00040000 +#define B43_DMA32_RXACTIVE 0xFFF00000 + +/* 32-bit DMA descriptor. */ +struct b43_dmadesc32 { + __le32 control; + __le32 address; +} __packed; +#define B43_DMA32_DCTL_BYTECNT 0x00001FFF +#define B43_DMA32_DCTL_ADDREXT_MASK 0x00030000 +#define B43_DMA32_DCTL_ADDREXT_SHIFT 16 +#define B43_DMA32_DCTL_DTABLEEND 0x10000000 +#define B43_DMA32_DCTL_IRQ 0x20000000 +#define B43_DMA32_DCTL_FRAMEEND 0x40000000 +#define B43_DMA32_DCTL_FRAMESTART 0x80000000 + +/*** 64-bit DMA Engine. ***/ + +/* 64-bit DMA controller registers. */ +#define B43_DMA64_TXCTL 0x00 +#define B43_DMA64_TXENABLE 0x00000001 +#define B43_DMA64_TXSUSPEND 0x00000002 +#define B43_DMA64_TXLOOPBACK 0x00000004 +#define B43_DMA64_TXFLUSH 0x00000010 +#define B43_DMA64_TXPARITYDISABLE 0x00000800 +#define B43_DMA64_TXADDREXT_MASK 0x00030000 +#define B43_DMA64_TXADDREXT_SHIFT 16 +#define B43_DMA64_TXINDEX 0x04 +#define B43_DMA64_TXRINGLO 0x08 +#define B43_DMA64_TXRINGHI 0x0C +#define B43_DMA64_TXSTATUS 0x10 +#define B43_DMA64_TXSTATDPTR 0x00001FFF +#define B43_DMA64_TXSTAT 0xF0000000 +#define B43_DMA64_TXSTAT_DISABLED 0x00000000 +#define B43_DMA64_TXSTAT_ACTIVE 0x10000000 +#define B43_DMA64_TXSTAT_IDLEWAIT 0x20000000 +#define B43_DMA64_TXSTAT_STOPPED 0x30000000 +#define B43_DMA64_TXSTAT_SUSP 0x40000000 +#define B43_DMA64_TXERROR 0x14 +#define B43_DMA64_TXERRDPTR 0x0001FFFF +#define B43_DMA64_TXERR 0xF0000000 +#define B43_DMA64_TXERR_NOERR 0x00000000 +#define B43_DMA64_TXERR_PROT 0x10000000 +#define B43_DMA64_TXERR_UNDERRUN 0x20000000 +#define B43_DMA64_TXERR_TRANSFER 0x30000000 +#define B43_DMA64_TXERR_DESCREAD 0x40000000 +#define B43_DMA64_TXERR_CORE 0x50000000 +#define B43_DMA64_RXCTL 0x20 +#define B43_DMA64_RXENABLE 0x00000001 +#define B43_DMA64_RXFROFF_MASK 0x000000FE +#define B43_DMA64_RXFROFF_SHIFT 1 +#define B43_DMA64_RXDIRECTFIFO 0x00000100 +#define B43_DMA64_RXPARITYDISABLE 0x00000800 +#define B43_DMA64_RXADDREXT_MASK 0x00030000 +#define B43_DMA64_RXADDREXT_SHIFT 16 +#define B43_DMA64_RXINDEX 0x24 +#define B43_DMA64_RXRINGLO 0x28 +#define B43_DMA64_RXRINGHI 0x2C +#define B43_DMA64_RXSTATUS 0x30 +#define B43_DMA64_RXSTATDPTR 0x00001FFF +#define B43_DMA64_RXSTAT 0xF0000000 +#define B43_DMA64_RXSTAT_DISABLED 0x00000000 +#define B43_DMA64_RXSTAT_ACTIVE 0x10000000 +#define B43_DMA64_RXSTAT_IDLEWAIT 0x20000000 +#define B43_DMA64_RXSTAT_STOPPED 0x30000000 +#define B43_DMA64_RXSTAT_SUSP 0x40000000 +#define B43_DMA64_RXERROR 0x34 +#define B43_DMA64_RXERRDPTR 0x0001FFFF +#define B43_DMA64_RXERR 0xF0000000 +#define B43_DMA64_RXERR_NOERR 0x00000000 +#define B43_DMA64_RXERR_PROT 0x10000000 +#define B43_DMA64_RXERR_UNDERRUN 0x20000000 +#define B43_DMA64_RXERR_TRANSFER 0x30000000 +#define B43_DMA64_RXERR_DESCREAD 0x40000000 +#define B43_DMA64_RXERR_CORE 0x50000000 + +/* 64-bit DMA descriptor. */ +struct b43_dmadesc64 { + __le32 control0; + __le32 control1; + __le32 address_low; + __le32 address_high; +} __packed; +#define B43_DMA64_DCTL0_DTABLEEND 0x10000000 +#define B43_DMA64_DCTL0_IRQ 0x20000000 +#define B43_DMA64_DCTL0_FRAMEEND 0x40000000 +#define B43_DMA64_DCTL0_FRAMESTART 0x80000000 +#define B43_DMA64_DCTL1_BYTECNT 0x00001FFF +#define B43_DMA64_DCTL1_ADDREXT_MASK 0x00030000 +#define B43_DMA64_DCTL1_ADDREXT_SHIFT 16 + +struct b43_dmadesc_generic { + union { + struct b43_dmadesc32 dma32; + struct b43_dmadesc64 dma64; + } __packed; +} __packed; + +/* Misc DMA constants */ +#define B43_DMA32_RINGMEMSIZE 4096 +#define B43_DMA64_RINGMEMSIZE 8192 +/* Offset of frame with actual data */ +#define B43_DMA0_RX_FW598_FO 38 +#define B43_DMA0_RX_FW351_FO 30 + +/* DMA engine tuning knobs */ +#define B43_TXRING_SLOTS 256 +#define B43_RXRING_SLOTS 256 +#define B43_DMA0_RX_FW598_BUFSIZE (B43_DMA0_RX_FW598_FO + IEEE80211_MAX_FRAME_LEN) +#define B43_DMA0_RX_FW351_BUFSIZE (B43_DMA0_RX_FW351_FO + IEEE80211_MAX_FRAME_LEN) + +/* Pointer poison */ +#define B43_DMA_PTR_POISON ((void *)ERR_PTR(-ENOMEM)) +#define b43_dma_ptr_is_poisoned(ptr) (unlikely((ptr) == B43_DMA_PTR_POISON)) + + +struct sk_buff; +struct b43_private; +struct b43_txstatus; + +struct b43_dmadesc_meta { + /* The kernel DMA-able buffer. */ + struct sk_buff *skb; + /* DMA base bus-address of the descriptor buffer. */ + dma_addr_t dmaaddr; + /* ieee80211 TX status. Only used once per 802.11 frag. */ + bool is_last_fragment; +}; + +struct b43_dmaring; + +/* Lowlevel DMA operations that differ between 32bit and 64bit DMA. */ +struct b43_dma_ops { + struct b43_dmadesc_generic *(*idx2desc) (struct b43_dmaring * ring, + int slot, + struct b43_dmadesc_meta ** + meta); + void (*fill_descriptor) (struct b43_dmaring * ring, + struct b43_dmadesc_generic * desc, + dma_addr_t dmaaddr, u16 bufsize, int start, + int end, int irq); + void (*poke_tx) (struct b43_dmaring * ring, int slot); + void (*tx_suspend) (struct b43_dmaring * ring); + void (*tx_resume) (struct b43_dmaring * ring); + int (*get_current_rxslot) (struct b43_dmaring * ring); + void (*set_current_rxslot) (struct b43_dmaring * ring, int slot); +}; + +enum b43_dmatype { + B43_DMA_30BIT = 30, + B43_DMA_32BIT = 32, + B43_DMA_64BIT = 64, +}; + +enum b43_addrtype { + B43_DMA_ADDR_LOW, + B43_DMA_ADDR_HIGH, + B43_DMA_ADDR_EXT, +}; + +struct b43_dmaring { + /* Lowlevel DMA ops. */ + const struct b43_dma_ops *ops; + /* Kernel virtual base address of the ring memory. */ + void *descbase; + /* Meta data about all descriptors. */ + struct b43_dmadesc_meta *meta; + /* Cache of TX headers for each TX frame. + * This is to avoid an allocation on each TX. + * This is NULL for an RX ring. + */ + u8 *txhdr_cache; + /* (Unadjusted) DMA base bus-address of the ring memory. */ + dma_addr_t dmabase; + /* Number of descriptor slots in the ring. */ + int nr_slots; + /* Number of used descriptor slots. */ + int used_slots; + /* Currently used slot in the ring. */ + int current_slot; + /* Frameoffset in octets. */ + u32 frameoffset; + /* Descriptor buffer size. */ + u16 rx_buffersize; + /* The MMIO base register of the DMA controller. */ + u16 mmio_base; + /* DMA controller index number (0-5). */ + int index; + /* Boolean. Is this a TX ring? */ + bool tx; + /* The type of DMA engine used. */ + enum b43_dmatype type; + /* Boolean. Is this ring stopped at ieee80211 level? */ + bool stopped; + /* The QOS priority assigned to this ring. Only used for TX rings. + * This is the mac80211 "queue" value. */ + u8 queue_prio; + struct b43_wldev *dev; +#ifdef CPTCFG_B43_DEBUG + /* Maximum number of used slots. */ + int max_used_slots; + /* Last time we injected a ring overflow. */ + unsigned long last_injected_overflow; + /* Statistics: Number of successfully transmitted packets */ + u64 nr_succeed_tx_packets; + /* Statistics: Number of failed TX packets */ + u64 nr_failed_tx_packets; + /* Statistics: Total number of TX plus all retries. */ + u64 nr_total_packet_tries; +#endif /* CPTCFG_B43_DEBUG */ +}; + +static inline u32 b43_dma_read(struct b43_dmaring *ring, u16 offset) +{ + return b43_read32(ring->dev, ring->mmio_base + offset); +} + +static inline void b43_dma_write(struct b43_dmaring *ring, u16 offset, u32 value) +{ + b43_write32(ring->dev, ring->mmio_base + offset, value); +} + +int b43_dma_init(struct b43_wldev *dev); +void b43_dma_free(struct b43_wldev *dev); + +void b43_dma_tx_suspend(struct b43_wldev *dev); +void b43_dma_tx_resume(struct b43_wldev *dev); + +int b43_dma_tx(struct b43_wldev *dev, + struct sk_buff *skb); +void b43_dma_handle_txstatus(struct b43_wldev *dev, + const struct b43_txstatus *status); + +void b43_dma_handle_rx_overflow(struct b43_dmaring *ring); + +void b43_dma_rx(struct b43_dmaring *ring); + +void b43_dma_direct_fifo_rx(struct b43_wldev *dev, + unsigned int engine_index, bool enable); + +#endif /* B43_DMA_H_ */ diff --git a/drivers/net/wireless/b43/leds.c b/drivers/net/wireless/b43/leds.c new file mode 100644 index 0000000..d79ab2a --- /dev/null +++ b/drivers/net/wireless/b43/leds.c @@ -0,0 +1,359 @@ +/* + + Broadcom B43 wireless driver + LED control + + Copyright (c) 2005 Martin Langer , + Copyright (c) 2005 Stefano Brivio + Copyright (c) 2005-2007 Michael Buesch + Copyright (c) 2005 Danny van Dyk + Copyright (c) 2005 Andreas Jaggi + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" +#include "leds.h" +#include "rfkill.h" + + +static void b43_led_turn_on(struct b43_wldev *dev, u8 led_index, + bool activelow) +{ + u16 ctl; + + ctl = b43_read16(dev, B43_MMIO_GPIO_CONTROL); + if (activelow) + ctl &= ~(1 << led_index); + else + ctl |= (1 << led_index); + b43_write16(dev, B43_MMIO_GPIO_CONTROL, ctl); +} + +static void b43_led_turn_off(struct b43_wldev *dev, u8 led_index, + bool activelow) +{ + u16 ctl; + + ctl = b43_read16(dev, B43_MMIO_GPIO_CONTROL); + if (activelow) + ctl |= (1 << led_index); + else + ctl &= ~(1 << led_index); + b43_write16(dev, B43_MMIO_GPIO_CONTROL, ctl); +} + +static void b43_led_update(struct b43_wldev *dev, + struct b43_led *led) +{ + bool radio_enabled; + bool turn_on; + + if (!led->wl) + return; + + radio_enabled = (dev->phy.radio_on && dev->radio_hw_enable); + + /* The led->state read is racy, but we don't care. In case we raced + * with the brightness_set handler, we will be called again soon + * to fixup our state. */ + if (radio_enabled) + turn_on = atomic_read(&led->state) != LED_OFF; + else + turn_on = false; + if (turn_on == led->hw_state) + return; + led->hw_state = turn_on; + + if (turn_on) + b43_led_turn_on(dev, led->index, led->activelow); + else + b43_led_turn_off(dev, led->index, led->activelow); +} + +static void b43_leds_work(struct work_struct *work) +{ + struct b43_leds *leds = container_of(work, struct b43_leds, work); + struct b43_wl *wl = container_of(leds, struct b43_wl, leds); + struct b43_wldev *dev; + + mutex_lock(&wl->mutex); + dev = wl->current_dev; + if (unlikely(!dev || b43_status(dev) < B43_STAT_STARTED)) + goto out_unlock; + + b43_led_update(dev, &wl->leds.led_tx); + b43_led_update(dev, &wl->leds.led_rx); + b43_led_update(dev, &wl->leds.led_radio); + b43_led_update(dev, &wl->leds.led_assoc); + +out_unlock: + mutex_unlock(&wl->mutex); +} + +/* Callback from the LED subsystem. */ +static void b43_led_brightness_set(struct led_classdev *led_dev, + enum led_brightness brightness) +{ + struct b43_led *led = container_of(led_dev, struct b43_led, led_dev); + struct b43_wl *wl = led->wl; + + if (likely(!wl->leds.stop)) { + atomic_set(&led->state, brightness); + ieee80211_queue_work(wl->hw, &wl->leds.work); + } +} + +static int b43_register_led(struct b43_wldev *dev, struct b43_led *led, + const char *name, const char *default_trigger, + u8 led_index, bool activelow) +{ + int err; + + if (led->wl) + return -EEXIST; + if (!default_trigger) + return -EINVAL; + led->wl = dev->wl; + led->index = led_index; + led->activelow = activelow; + strncpy(led->name, name, sizeof(led->name)); + atomic_set(&led->state, 0); + + led->led_dev.name = led->name; + led->led_dev.default_trigger = default_trigger; + led->led_dev.brightness_set = b43_led_brightness_set; + + err = led_classdev_register(dev->dev->dev, &led->led_dev); + if (err) { + b43warn(dev->wl, "LEDs: Failed to register %s\n", name); + led->wl = NULL; + return err; + } + + return 0; +} + +static void b43_unregister_led(struct b43_led *led) +{ + if (!led->wl) + return; + led_classdev_unregister(&led->led_dev); + led->wl = NULL; +} + +static void b43_map_led(struct b43_wldev *dev, + u8 led_index, + enum b43_led_behaviour behaviour, + bool activelow) +{ + struct ieee80211_hw *hw = dev->wl->hw; + char name[B43_LED_MAX_NAME_LEN + 1]; + + /* Map the b43 specific LED behaviour value to the + * generic LED triggers. */ + switch (behaviour) { + case B43_LED_INACTIVE: + case B43_LED_OFF: + case B43_LED_ON: + break; + case B43_LED_ACTIVITY: + case B43_LED_TRANSFER: + case B43_LED_APTRANSFER: + snprintf(name, sizeof(name), + "b43-%s::tx", wiphy_name(hw->wiphy)); + b43_register_led(dev, &dev->wl->leds.led_tx, name, + ieee80211_get_tx_led_name(hw), + led_index, activelow); + snprintf(name, sizeof(name), + "b43-%s::rx", wiphy_name(hw->wiphy)); + b43_register_led(dev, &dev->wl->leds.led_rx, name, + ieee80211_get_rx_led_name(hw), + led_index, activelow); + break; + case B43_LED_RADIO_ALL: + case B43_LED_RADIO_A: + case B43_LED_RADIO_B: + case B43_LED_MODE_BG: + snprintf(name, sizeof(name), + "b43-%s::radio", wiphy_name(hw->wiphy)); + b43_register_led(dev, &dev->wl->leds.led_radio, name, + ieee80211_get_radio_led_name(hw), + led_index, activelow); + break; + case B43_LED_WEIRD: + case B43_LED_ASSOC: + snprintf(name, sizeof(name), + "b43-%s::assoc", wiphy_name(hw->wiphy)); + b43_register_led(dev, &dev->wl->leds.led_assoc, name, + ieee80211_get_assoc_led_name(hw), + led_index, activelow); + break; + default: + b43warn(dev->wl, "LEDs: Unknown behaviour 0x%02X\n", + behaviour); + break; + } +} + +static void b43_led_get_sprominfo(struct b43_wldev *dev, + unsigned int led_index, + enum b43_led_behaviour *behaviour, + bool *activelow) +{ + u8 sprom[4]; + + sprom[0] = dev->dev->bus_sprom->gpio0; + sprom[1] = dev->dev->bus_sprom->gpio1; + sprom[2] = dev->dev->bus_sprom->gpio2; + sprom[3] = dev->dev->bus_sprom->gpio3; + + if (sprom[led_index] == 0xFF) { + /* There is no LED information in the SPROM + * for this LED. Hardcode it here. */ + *activelow = false; + switch (led_index) { + case 0: + *behaviour = B43_LED_ACTIVITY; + *activelow = true; + if (dev->dev->board_vendor == PCI_VENDOR_ID_COMPAQ) + *behaviour = B43_LED_RADIO_ALL; + break; + case 1: + *behaviour = B43_LED_RADIO_B; + if (dev->dev->board_vendor == PCI_VENDOR_ID_ASUSTEK) + *behaviour = B43_LED_ASSOC; + break; + case 2: + *behaviour = B43_LED_RADIO_A; + break; + case 3: + *behaviour = B43_LED_OFF; + break; + default: + *behaviour = B43_LED_OFF; + B43_WARN_ON(1); + return; + } + } else { + *behaviour = sprom[led_index] & B43_LED_BEHAVIOUR; + *activelow = !!(sprom[led_index] & B43_LED_ACTIVELOW); + } +} + +void b43_leds_init(struct b43_wldev *dev) +{ + struct b43_led *led; + unsigned int i; + enum b43_led_behaviour behaviour; + bool activelow; + + /* Sync the RF-kill LED state (if we have one) with radio and switch states. */ + led = &dev->wl->leds.led_radio; + if (led->wl) { + if (dev->phy.radio_on && b43_is_hw_radio_enabled(dev)) { + b43_led_turn_on(dev, led->index, led->activelow); + led->hw_state = true; + atomic_set(&led->state, 1); + } else { + b43_led_turn_off(dev, led->index, led->activelow); + led->hw_state = false; + atomic_set(&led->state, 0); + } + } + + /* Initialize TX/RX/ASSOC leds */ + led = &dev->wl->leds.led_tx; + if (led->wl) { + b43_led_turn_off(dev, led->index, led->activelow); + led->hw_state = false; + atomic_set(&led->state, 0); + } + led = &dev->wl->leds.led_rx; + if (led->wl) { + b43_led_turn_off(dev, led->index, led->activelow); + led->hw_state = false; + atomic_set(&led->state, 0); + } + led = &dev->wl->leds.led_assoc; + if (led->wl) { + b43_led_turn_off(dev, led->index, led->activelow); + led->hw_state = false; + atomic_set(&led->state, 0); + } + + /* Initialize other LED states. */ + for (i = 0; i < B43_MAX_NR_LEDS; i++) { + b43_led_get_sprominfo(dev, i, &behaviour, &activelow); + switch (behaviour) { + case B43_LED_OFF: + b43_led_turn_off(dev, i, activelow); + break; + case B43_LED_ON: + b43_led_turn_on(dev, i, activelow); + break; + default: + /* Leave others as-is. */ + break; + } + } + + dev->wl->leds.stop = 0; +} + +void b43_leds_exit(struct b43_wldev *dev) +{ + struct b43_leds *leds = &dev->wl->leds; + + b43_led_turn_off(dev, leds->led_tx.index, leds->led_tx.activelow); + b43_led_turn_off(dev, leds->led_rx.index, leds->led_rx.activelow); + b43_led_turn_off(dev, leds->led_assoc.index, leds->led_assoc.activelow); + b43_led_turn_off(dev, leds->led_radio.index, leds->led_radio.activelow); +} + +void b43_leds_stop(struct b43_wldev *dev) +{ + struct b43_leds *leds = &dev->wl->leds; + + leds->stop = 1; + cancel_work_sync(&leds->work); +} + +void b43_leds_register(struct b43_wldev *dev) +{ + unsigned int i; + enum b43_led_behaviour behaviour; + bool activelow; + + INIT_WORK(&dev->wl->leds.work, b43_leds_work); + + /* Register the LEDs to the LED subsystem. */ + for (i = 0; i < B43_MAX_NR_LEDS; i++) { + b43_led_get_sprominfo(dev, i, &behaviour, &activelow); + b43_map_led(dev, i, behaviour, activelow); + } +} + +void b43_leds_unregister(struct b43_wl *wl) +{ + struct b43_leds *leds = &wl->leds; + + b43_unregister_led(&leds->led_tx); + b43_unregister_led(&leds->led_rx); + b43_unregister_led(&leds->led_assoc); + b43_unregister_led(&leds->led_radio); +} diff --git a/drivers/net/wireless/b43/leds.h b/drivers/net/wireless/b43/leds.h new file mode 100644 index 0000000..66748e9 --- /dev/null +++ b/drivers/net/wireless/b43/leds.h @@ -0,0 +1,94 @@ +#ifndef B43_LEDS_H_ +#define B43_LEDS_H_ + +struct b43_wl; +struct b43_wldev; + +#ifdef CPTCFG_B43_LEDS + +#include +#include +#include + + +#define B43_LED_MAX_NAME_LEN 31 + +struct b43_led { + struct b43_wl *wl; + /* The LED class device */ + struct led_classdev led_dev; + /* The index number of the LED. */ + u8 index; + /* If activelow is true, the LED is ON if the + * bit is switched off. */ + bool activelow; + /* The unique name string for this LED device. */ + char name[B43_LED_MAX_NAME_LEN + 1]; + /* The current status of the LED. This is updated locklessly. */ + atomic_t state; + /* The active state in hardware. */ + bool hw_state; +}; + +struct b43_leds { + struct b43_led led_tx; + struct b43_led led_rx; + struct b43_led led_radio; + struct b43_led led_assoc; + + bool stop; + struct work_struct work; +}; + +#define B43_MAX_NR_LEDS 4 + +#define B43_LED_BEHAVIOUR 0x7F +#define B43_LED_ACTIVELOW 0x80 +/* LED behaviour values */ +enum b43_led_behaviour { + B43_LED_OFF, + B43_LED_ON, + B43_LED_ACTIVITY, + B43_LED_RADIO_ALL, + B43_LED_RADIO_A, + B43_LED_RADIO_B, + B43_LED_MODE_BG, + B43_LED_TRANSFER, + B43_LED_APTRANSFER, + B43_LED_WEIRD, //FIXME + B43_LED_ASSOC, + B43_LED_INACTIVE, +}; + +void b43_leds_register(struct b43_wldev *dev); +void b43_leds_unregister(struct b43_wl *wl); +void b43_leds_init(struct b43_wldev *dev); +void b43_leds_exit(struct b43_wldev *dev); +void b43_leds_stop(struct b43_wldev *dev); + + +#else /* CPTCFG_B43_LEDS */ +/* LED support disabled */ + +struct b43_leds { + /* empty */ +}; + +static inline void b43_leds_register(struct b43_wldev *dev) +{ +} +static inline void b43_leds_unregister(struct b43_wl *wl) +{ +} +static inline void b43_leds_init(struct b43_wldev *dev) +{ +} +static inline void b43_leds_exit(struct b43_wldev *dev) +{ +} +static inline void b43_leds_stop(struct b43_wldev *dev) +{ +} +#endif /* CPTCFG_B43_LEDS */ + +#endif /* B43_LEDS_H_ */ diff --git a/drivers/net/wireless/b43/lo.c b/drivers/net/wireless/b43/lo.c new file mode 100644 index 0000000..916123a --- /dev/null +++ b/drivers/net/wireless/b43/lo.c @@ -0,0 +1,1016 @@ +/* + + Broadcom B43 wireless driver + + G PHY LO (LocalOscillator) Measuring and Control routines + + Copyright (c) 2005 Martin Langer , + Copyright (c) 2005, 2006 Stefano Brivio + Copyright (c) 2005-2007 Michael Buesch + Copyright (c) 2005, 2006 Danny van Dyk + Copyright (c) 2005, 2006 Andreas Jaggi + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" +#include "lo.h" +#include "phy_g.h" +#include "main.h" + +#include +#include +#include + + +static struct b43_lo_calib *b43_find_lo_calib(struct b43_txpower_lo_control *lo, + const struct b43_bbatt *bbatt, + const struct b43_rfatt *rfatt) +{ + struct b43_lo_calib *c; + + list_for_each_entry(c, &lo->calib_list, list) { + if (!b43_compare_bbatt(&c->bbatt, bbatt)) + continue; + if (!b43_compare_rfatt(&c->rfatt, rfatt)) + continue; + return c; + } + + return NULL; +} + +/* Write the LocalOscillator Control (adjust) value-pair. */ +static void b43_lo_write(struct b43_wldev *dev, struct b43_loctl *control) +{ + struct b43_phy *phy = &dev->phy; + u16 value; + + if (B43_DEBUG) { + if (unlikely(abs(control->i) > 16 || abs(control->q) > 16)) { + b43dbg(dev->wl, "Invalid LO control pair " + "(I: %d, Q: %d)\n", control->i, control->q); + dump_stack(); + return; + } + } + B43_WARN_ON(phy->type != B43_PHYTYPE_G); + + value = (u8) (control->q); + value |= ((u8) (control->i)) << 8; + b43_phy_write(dev, B43_PHY_LO_CTL, value); +} + +static u16 lo_measure_feedthrough(struct b43_wldev *dev, + u16 lna, u16 pga, u16 trsw_rx) +{ + struct b43_phy *phy = &dev->phy; + u16 rfover; + u16 feedthrough; + + if (phy->gmode) { + lna <<= B43_PHY_RFOVERVAL_LNA_SHIFT; + pga <<= B43_PHY_RFOVERVAL_PGA_SHIFT; + + B43_WARN_ON(lna & ~B43_PHY_RFOVERVAL_LNA); + B43_WARN_ON(pga & ~B43_PHY_RFOVERVAL_PGA); +/*FIXME This assertion fails B43_WARN_ON(trsw_rx & ~(B43_PHY_RFOVERVAL_TRSWRX | + B43_PHY_RFOVERVAL_BW)); +*/ + trsw_rx &= (B43_PHY_RFOVERVAL_TRSWRX | B43_PHY_RFOVERVAL_BW); + + /* Construct the RF Override Value */ + rfover = B43_PHY_RFOVERVAL_UNK; + rfover |= pga; + rfover |= lna; + rfover |= trsw_rx; + if ((dev->dev->bus_sprom->boardflags_lo & B43_BFL_EXTLNA) + && phy->rev > 6) + rfover |= B43_PHY_RFOVERVAL_EXTLNA; + + b43_phy_write(dev, B43_PHY_PGACTL, 0xE300); + b43_phy_write(dev, B43_PHY_RFOVERVAL, rfover); + udelay(10); + rfover |= B43_PHY_RFOVERVAL_BW_LBW; + b43_phy_write(dev, B43_PHY_RFOVERVAL, rfover); + udelay(10); + rfover |= B43_PHY_RFOVERVAL_BW_LPF; + b43_phy_write(dev, B43_PHY_RFOVERVAL, rfover); + udelay(10); + b43_phy_write(dev, B43_PHY_PGACTL, 0xF300); + } else { + pga |= B43_PHY_PGACTL_UNKNOWN; + b43_phy_write(dev, B43_PHY_PGACTL, pga); + udelay(10); + pga |= B43_PHY_PGACTL_LOWBANDW; + b43_phy_write(dev, B43_PHY_PGACTL, pga); + udelay(10); + pga |= B43_PHY_PGACTL_LPF; + b43_phy_write(dev, B43_PHY_PGACTL, pga); + } + udelay(21); + feedthrough = b43_phy_read(dev, B43_PHY_LO_LEAKAGE); + + /* This is a good place to check if we need to relax a bit, + * as this is the main function called regularly + * in the LO calibration. */ + cond_resched(); + + return feedthrough; +} + +/* TXCTL Register and Value Table. + * Returns the "TXCTL Register". + * "value" is the "TXCTL Value". + * "pad_mix_gain" is the PAD Mixer Gain. + */ +static u16 lo_txctl_register_table(struct b43_wldev *dev, + u16 *value, u16 *pad_mix_gain) +{ + struct b43_phy *phy = &dev->phy; + u16 reg, v, padmix; + + if (phy->type == B43_PHYTYPE_B) { + v = 0x30; + if (phy->radio_rev <= 5) { + reg = 0x43; + padmix = 0; + } else { + reg = 0x52; + padmix = 5; + } + } else { + if (phy->rev >= 2 && phy->radio_rev == 8) { + reg = 0x43; + v = 0x10; + padmix = 2; + } else { + reg = 0x52; + v = 0x30; + padmix = 5; + } + } + if (value) + *value = v; + if (pad_mix_gain) + *pad_mix_gain = padmix; + + return reg; +} + +static void lo_measure_txctl_values(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + struct b43_txpower_lo_control *lo = gphy->lo_control; + u16 reg, mask; + u16 trsw_rx, pga; + u16 radio_pctl_reg; + + static const u8 tx_bias_values[] = { + 0x09, 0x08, 0x0A, 0x01, 0x00, + 0x02, 0x05, 0x04, 0x06, + }; + static const u8 tx_magn_values[] = { + 0x70, 0x40, + }; + + if (!has_loopback_gain(phy)) { + radio_pctl_reg = 6; + trsw_rx = 2; + pga = 0; + } else { + int lb_gain; /* Loopback gain (in dB) */ + + trsw_rx = 0; + lb_gain = gphy->max_lb_gain / 2; + if (lb_gain > 10) { + radio_pctl_reg = 0; + pga = abs(10 - lb_gain) / 6; + pga = clamp_val(pga, 0, 15); + } else { + int cmp_val; + int tmp; + + pga = 0; + cmp_val = 0x24; + if ((phy->rev >= 2) && + (phy->radio_ver == 0x2050) && (phy->radio_rev == 8)) + cmp_val = 0x3C; + tmp = lb_gain; + if ((10 - lb_gain) < cmp_val) + tmp = (10 - lb_gain); + if (tmp < 0) + tmp += 6; + else + tmp += 3; + cmp_val /= 4; + tmp /= 4; + if (tmp >= cmp_val) + radio_pctl_reg = cmp_val; + else + radio_pctl_reg = tmp; + } + } + b43_radio_maskset(dev, 0x43, 0xFFF0, radio_pctl_reg); + b43_gphy_set_baseband_attenuation(dev, 2); + + reg = lo_txctl_register_table(dev, &mask, NULL); + mask = ~mask; + b43_radio_mask(dev, reg, mask); + + if (has_tx_magnification(phy)) { + int i, j; + int feedthrough; + int min_feedth = 0xFFFF; + u8 tx_magn, tx_bias; + + for (i = 0; i < ARRAY_SIZE(tx_magn_values); i++) { + tx_magn = tx_magn_values[i]; + b43_radio_maskset(dev, 0x52, 0xFF0F, tx_magn); + for (j = 0; j < ARRAY_SIZE(tx_bias_values); j++) { + tx_bias = tx_bias_values[j]; + b43_radio_maskset(dev, 0x52, 0xFFF0, tx_bias); + feedthrough = + lo_measure_feedthrough(dev, 0, pga, + trsw_rx); + if (feedthrough < min_feedth) { + lo->tx_bias = tx_bias; + lo->tx_magn = tx_magn; + min_feedth = feedthrough; + } + if (lo->tx_bias == 0) + break; + } + b43_radio_write16(dev, 0x52, + (b43_radio_read16(dev, 0x52) + & 0xFF00) | lo->tx_bias | lo-> + tx_magn); + } + } else { + lo->tx_magn = 0; + lo->tx_bias = 0; + b43_radio_mask(dev, 0x52, 0xFFF0); /* TX bias == 0 */ + } + lo->txctl_measured_time = jiffies; +} + +static void lo_read_power_vector(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + struct b43_txpower_lo_control *lo = gphy->lo_control; + int i; + u64 tmp; + u64 power_vector = 0; + + for (i = 0; i < 8; i += 2) { + tmp = b43_shm_read16(dev, B43_SHM_SHARED, 0x310 + i); + power_vector |= (tmp << (i * 8)); + /* Clear the vector on the device. */ + b43_shm_write16(dev, B43_SHM_SHARED, 0x310 + i, 0); + } + if (power_vector) + lo->power_vector = power_vector; + lo->pwr_vec_read_time = jiffies; +} + +/* 802.11/LO/GPHY/MeasuringGains */ +static void lo_measure_gain_values(struct b43_wldev *dev, + s16 max_rx_gain, int use_trsw_rx) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + u16 tmp; + + if (max_rx_gain < 0) + max_rx_gain = 0; + + if (has_loopback_gain(phy)) { + int trsw_rx_gain; + + if (use_trsw_rx) { + trsw_rx_gain = gphy->trsw_rx_gain / 2; + if (max_rx_gain >= trsw_rx_gain) { + trsw_rx_gain = max_rx_gain - trsw_rx_gain; + } + } else + trsw_rx_gain = max_rx_gain; + if (trsw_rx_gain < 9) { + gphy->lna_lod_gain = 0; + } else { + gphy->lna_lod_gain = 1; + trsw_rx_gain -= 8; + } + trsw_rx_gain = clamp_val(trsw_rx_gain, 0, 0x2D); + gphy->pga_gain = trsw_rx_gain / 3; + if (gphy->pga_gain >= 5) { + gphy->pga_gain -= 5; + gphy->lna_gain = 2; + } else + gphy->lna_gain = 0; + } else { + gphy->lna_gain = 0; + gphy->trsw_rx_gain = 0x20; + if (max_rx_gain >= 0x14) { + gphy->lna_lod_gain = 1; + gphy->pga_gain = 2; + } else if (max_rx_gain >= 0x12) { + gphy->lna_lod_gain = 1; + gphy->pga_gain = 1; + } else if (max_rx_gain >= 0xF) { + gphy->lna_lod_gain = 1; + gphy->pga_gain = 0; + } else { + gphy->lna_lod_gain = 0; + gphy->pga_gain = 0; + } + } + + tmp = b43_radio_read16(dev, 0x7A); + if (gphy->lna_lod_gain == 0) + tmp &= ~0x0008; + else + tmp |= 0x0008; + b43_radio_write16(dev, 0x7A, tmp); +} + +struct lo_g_saved_values { + u8 old_channel; + + /* Core registers */ + u16 reg_3F4; + u16 reg_3E2; + + /* PHY registers */ + u16 phy_lo_mask; + u16 phy_extg_01; + u16 phy_dacctl_hwpctl; + u16 phy_dacctl; + u16 phy_cck_14; + u16 phy_hpwr_tssictl; + u16 phy_analogover; + u16 phy_analogoverval; + u16 phy_rfover; + u16 phy_rfoverval; + u16 phy_classctl; + u16 phy_cck_3E; + u16 phy_crs0; + u16 phy_pgactl; + u16 phy_cck_2A; + u16 phy_syncctl; + u16 phy_cck_30; + u16 phy_cck_06; + + /* Radio registers */ + u16 radio_43; + u16 radio_7A; + u16 radio_52; +}; + +static void lo_measure_setup(struct b43_wldev *dev, + struct lo_g_saved_values *sav) +{ + struct ssb_sprom *sprom = dev->dev->bus_sprom; + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + struct b43_txpower_lo_control *lo = gphy->lo_control; + u16 tmp; + + if (b43_has_hardware_pctl(dev)) { + sav->phy_lo_mask = b43_phy_read(dev, B43_PHY_LO_MASK); + sav->phy_extg_01 = b43_phy_read(dev, B43_PHY_EXTG(0x01)); + sav->phy_dacctl_hwpctl = b43_phy_read(dev, B43_PHY_DACCTL); + sav->phy_cck_14 = b43_phy_read(dev, B43_PHY_CCK(0x14)); + sav->phy_hpwr_tssictl = b43_phy_read(dev, B43_PHY_HPWR_TSSICTL); + + b43_phy_set(dev, B43_PHY_HPWR_TSSICTL, 0x100); + b43_phy_set(dev, B43_PHY_EXTG(0x01), 0x40); + b43_phy_set(dev, B43_PHY_DACCTL, 0x40); + b43_phy_set(dev, B43_PHY_CCK(0x14), 0x200); + } + if (phy->type == B43_PHYTYPE_B && + phy->radio_ver == 0x2050 && phy->radio_rev < 6) { + b43_phy_write(dev, B43_PHY_CCK(0x16), 0x410); + b43_phy_write(dev, B43_PHY_CCK(0x17), 0x820); + } + if (phy->rev >= 2) { + sav->phy_analogover = b43_phy_read(dev, B43_PHY_ANALOGOVER); + sav->phy_analogoverval = + b43_phy_read(dev, B43_PHY_ANALOGOVERVAL); + sav->phy_rfover = b43_phy_read(dev, B43_PHY_RFOVER); + sav->phy_rfoverval = b43_phy_read(dev, B43_PHY_RFOVERVAL); + sav->phy_classctl = b43_phy_read(dev, B43_PHY_CLASSCTL); + sav->phy_cck_3E = b43_phy_read(dev, B43_PHY_CCK(0x3E)); + sav->phy_crs0 = b43_phy_read(dev, B43_PHY_CRS0); + + b43_phy_mask(dev, B43_PHY_CLASSCTL, 0xFFFC); + b43_phy_mask(dev, B43_PHY_CRS0, 0x7FFF); + b43_phy_set(dev, B43_PHY_ANALOGOVER, 0x0003); + b43_phy_mask(dev, B43_PHY_ANALOGOVERVAL, 0xFFFC); + if (phy->type == B43_PHYTYPE_G) { + if ((phy->rev >= 7) && + (sprom->boardflags_lo & B43_BFL_EXTLNA)) { + b43_phy_write(dev, B43_PHY_RFOVER, 0x933); + } else { + b43_phy_write(dev, B43_PHY_RFOVER, 0x133); + } + } else { + b43_phy_write(dev, B43_PHY_RFOVER, 0); + } + b43_phy_write(dev, B43_PHY_CCK(0x3E), 0); + } + sav->reg_3F4 = b43_read16(dev, 0x3F4); + sav->reg_3E2 = b43_read16(dev, 0x3E2); + sav->radio_43 = b43_radio_read16(dev, 0x43); + sav->radio_7A = b43_radio_read16(dev, 0x7A); + sav->phy_pgactl = b43_phy_read(dev, B43_PHY_PGACTL); + sav->phy_cck_2A = b43_phy_read(dev, B43_PHY_CCK(0x2A)); + sav->phy_syncctl = b43_phy_read(dev, B43_PHY_SYNCCTL); + sav->phy_dacctl = b43_phy_read(dev, B43_PHY_DACCTL); + + if (!has_tx_magnification(phy)) { + sav->radio_52 = b43_radio_read16(dev, 0x52); + sav->radio_52 &= 0x00F0; + } + if (phy->type == B43_PHYTYPE_B) { + sav->phy_cck_30 = b43_phy_read(dev, B43_PHY_CCK(0x30)); + sav->phy_cck_06 = b43_phy_read(dev, B43_PHY_CCK(0x06)); + b43_phy_write(dev, B43_PHY_CCK(0x30), 0x00FF); + b43_phy_write(dev, B43_PHY_CCK(0x06), 0x3F3F); + } else { + b43_write16(dev, 0x3E2, b43_read16(dev, 0x3E2) + | 0x8000); + } + b43_write16(dev, 0x3F4, b43_read16(dev, 0x3F4) + & 0xF000); + + tmp = + (phy->type == B43_PHYTYPE_G) ? B43_PHY_LO_MASK : B43_PHY_CCK(0x2E); + b43_phy_write(dev, tmp, 0x007F); + + tmp = sav->phy_syncctl; + b43_phy_write(dev, B43_PHY_SYNCCTL, tmp & 0xFF7F); + tmp = sav->radio_7A; + b43_radio_write16(dev, 0x007A, tmp & 0xFFF0); + + b43_phy_write(dev, B43_PHY_CCK(0x2A), 0x8A3); + if (phy->type == B43_PHYTYPE_G || + (phy->type == B43_PHYTYPE_B && + phy->radio_ver == 0x2050 && phy->radio_rev >= 6)) { + b43_phy_write(dev, B43_PHY_CCK(0x2B), 0x1003); + } else + b43_phy_write(dev, B43_PHY_CCK(0x2B), 0x0802); + if (phy->rev >= 2) + b43_dummy_transmission(dev, false, true); + b43_gphy_channel_switch(dev, 6, 0); + b43_radio_read16(dev, 0x51); /* dummy read */ + if (phy->type == B43_PHYTYPE_G) + b43_phy_write(dev, B43_PHY_CCK(0x2F), 0); + + /* Re-measure the txctl values, if needed. */ + if (time_before(lo->txctl_measured_time, + jiffies - B43_LO_TXCTL_EXPIRE)) + lo_measure_txctl_values(dev); + + if (phy->type == B43_PHYTYPE_G && phy->rev >= 3) { + b43_phy_write(dev, B43_PHY_LO_MASK, 0xC078); + } else { + if (phy->type == B43_PHYTYPE_B) + b43_phy_write(dev, B43_PHY_CCK(0x2E), 0x8078); + else + b43_phy_write(dev, B43_PHY_LO_MASK, 0x8078); + } +} + +static void lo_measure_restore(struct b43_wldev *dev, + struct lo_g_saved_values *sav) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + u16 tmp; + + if (phy->rev >= 2) { + b43_phy_write(dev, B43_PHY_PGACTL, 0xE300); + tmp = (gphy->pga_gain << 8); + b43_phy_write(dev, B43_PHY_RFOVERVAL, tmp | 0xA0); + udelay(5); + b43_phy_write(dev, B43_PHY_RFOVERVAL, tmp | 0xA2); + udelay(2); + b43_phy_write(dev, B43_PHY_RFOVERVAL, tmp | 0xA3); + } else { + tmp = (gphy->pga_gain | 0xEFA0); + b43_phy_write(dev, B43_PHY_PGACTL, tmp); + } + if (phy->type == B43_PHYTYPE_G) { + if (phy->rev >= 3) + b43_phy_write(dev, B43_PHY_CCK(0x2E), 0xC078); + else + b43_phy_write(dev, B43_PHY_CCK(0x2E), 0x8078); + if (phy->rev >= 2) + b43_phy_write(dev, B43_PHY_CCK(0x2F), 0x0202); + else + b43_phy_write(dev, B43_PHY_CCK(0x2F), 0x0101); + } + b43_write16(dev, 0x3F4, sav->reg_3F4); + b43_phy_write(dev, B43_PHY_PGACTL, sav->phy_pgactl); + b43_phy_write(dev, B43_PHY_CCK(0x2A), sav->phy_cck_2A); + b43_phy_write(dev, B43_PHY_SYNCCTL, sav->phy_syncctl); + b43_phy_write(dev, B43_PHY_DACCTL, sav->phy_dacctl); + b43_radio_write16(dev, 0x43, sav->radio_43); + b43_radio_write16(dev, 0x7A, sav->radio_7A); + if (!has_tx_magnification(phy)) { + tmp = sav->radio_52; + b43_radio_maskset(dev, 0x52, 0xFF0F, tmp); + } + b43_write16(dev, 0x3E2, sav->reg_3E2); + if (phy->type == B43_PHYTYPE_B && + phy->radio_ver == 0x2050 && phy->radio_rev <= 5) { + b43_phy_write(dev, B43_PHY_CCK(0x30), sav->phy_cck_30); + b43_phy_write(dev, B43_PHY_CCK(0x06), sav->phy_cck_06); + } + if (phy->rev >= 2) { + b43_phy_write(dev, B43_PHY_ANALOGOVER, sav->phy_analogover); + b43_phy_write(dev, B43_PHY_ANALOGOVERVAL, + sav->phy_analogoverval); + b43_phy_write(dev, B43_PHY_CLASSCTL, sav->phy_classctl); + b43_phy_write(dev, B43_PHY_RFOVER, sav->phy_rfover); + b43_phy_write(dev, B43_PHY_RFOVERVAL, sav->phy_rfoverval); + b43_phy_write(dev, B43_PHY_CCK(0x3E), sav->phy_cck_3E); + b43_phy_write(dev, B43_PHY_CRS0, sav->phy_crs0); + } + if (b43_has_hardware_pctl(dev)) { + tmp = (sav->phy_lo_mask & 0xBFFF); + b43_phy_write(dev, B43_PHY_LO_MASK, tmp); + b43_phy_write(dev, B43_PHY_EXTG(0x01), sav->phy_extg_01); + b43_phy_write(dev, B43_PHY_DACCTL, sav->phy_dacctl_hwpctl); + b43_phy_write(dev, B43_PHY_CCK(0x14), sav->phy_cck_14); + b43_phy_write(dev, B43_PHY_HPWR_TSSICTL, sav->phy_hpwr_tssictl); + } + b43_gphy_channel_switch(dev, sav->old_channel, 1); +} + +struct b43_lo_g_statemachine { + int current_state; + int nr_measured; + int state_val_multiplier; + u16 lowest_feedth; + struct b43_loctl min_loctl; +}; + +/* Loop over each possible value in this state. */ +static int lo_probe_possible_loctls(struct b43_wldev *dev, + struct b43_loctl *probe_loctl, + struct b43_lo_g_statemachine *d) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + struct b43_loctl test_loctl; + struct b43_loctl orig_loctl; + struct b43_loctl prev_loctl = { + .i = -100, + .q = -100, + }; + int i; + int begin, end; + int found_lower = 0; + u16 feedth; + + static const struct b43_loctl modifiers[] = { + {.i = 1,.q = 1,}, + {.i = 1,.q = 0,}, + {.i = 1,.q = -1,}, + {.i = 0,.q = -1,}, + {.i = -1,.q = -1,}, + {.i = -1,.q = 0,}, + {.i = -1,.q = 1,}, + {.i = 0,.q = 1,}, + }; + + if (d->current_state == 0) { + begin = 1; + end = 8; + } else if (d->current_state % 2 == 0) { + begin = d->current_state - 1; + end = d->current_state + 1; + } else { + begin = d->current_state - 2; + end = d->current_state + 2; + } + if (begin < 1) + begin += 8; + if (end > 8) + end -= 8; + + memcpy(&orig_loctl, probe_loctl, sizeof(struct b43_loctl)); + i = begin; + d->current_state = i; + while (1) { + B43_WARN_ON(!(i >= 1 && i <= 8)); + memcpy(&test_loctl, &orig_loctl, sizeof(struct b43_loctl)); + test_loctl.i += modifiers[i - 1].i * d->state_val_multiplier; + test_loctl.q += modifiers[i - 1].q * d->state_val_multiplier; + if ((test_loctl.i != prev_loctl.i || + test_loctl.q != prev_loctl.q) && + (abs(test_loctl.i) <= 16 && abs(test_loctl.q) <= 16)) { + b43_lo_write(dev, &test_loctl); + feedth = lo_measure_feedthrough(dev, gphy->lna_gain, + gphy->pga_gain, + gphy->trsw_rx_gain); + if (feedth < d->lowest_feedth) { + memcpy(probe_loctl, &test_loctl, + sizeof(struct b43_loctl)); + found_lower = 1; + d->lowest_feedth = feedth; + if ((d->nr_measured < 2) && + !has_loopback_gain(phy)) + break; + } + } + memcpy(&prev_loctl, &test_loctl, sizeof(prev_loctl)); + if (i == end) + break; + if (i == 8) + i = 1; + else + i++; + d->current_state = i; + } + + return found_lower; +} + +static void lo_probe_loctls_statemachine(struct b43_wldev *dev, + struct b43_loctl *loctl, + int *max_rx_gain) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + struct b43_lo_g_statemachine d; + u16 feedth; + int found_lower; + struct b43_loctl probe_loctl; + int max_repeat = 1, repeat_cnt = 0; + + d.nr_measured = 0; + d.state_val_multiplier = 1; + if (has_loopback_gain(phy)) + d.state_val_multiplier = 3; + + memcpy(&d.min_loctl, loctl, sizeof(struct b43_loctl)); + if (has_loopback_gain(phy)) + max_repeat = 4; + do { + b43_lo_write(dev, &d.min_loctl); + feedth = lo_measure_feedthrough(dev, gphy->lna_gain, + gphy->pga_gain, + gphy->trsw_rx_gain); + if (feedth < 0x258) { + if (feedth >= 0x12C) + *max_rx_gain += 6; + else + *max_rx_gain += 3; + feedth = lo_measure_feedthrough(dev, gphy->lna_gain, + gphy->pga_gain, + gphy->trsw_rx_gain); + } + d.lowest_feedth = feedth; + + d.current_state = 0; + do { + B43_WARN_ON(! + (d.current_state >= 0 + && d.current_state <= 8)); + memcpy(&probe_loctl, &d.min_loctl, + sizeof(struct b43_loctl)); + found_lower = + lo_probe_possible_loctls(dev, &probe_loctl, &d); + if (!found_lower) + break; + if ((probe_loctl.i == d.min_loctl.i) && + (probe_loctl.q == d.min_loctl.q)) + break; + memcpy(&d.min_loctl, &probe_loctl, + sizeof(struct b43_loctl)); + d.nr_measured++; + } while (d.nr_measured < 24); + memcpy(loctl, &d.min_loctl, sizeof(struct b43_loctl)); + + if (has_loopback_gain(phy)) { + if (d.lowest_feedth > 0x1194) + *max_rx_gain -= 6; + else if (d.lowest_feedth < 0x5DC) + *max_rx_gain += 3; + if (repeat_cnt == 0) { + if (d.lowest_feedth <= 0x5DC) { + d.state_val_multiplier = 1; + repeat_cnt++; + } else + d.state_val_multiplier = 2; + } else if (repeat_cnt == 2) + d.state_val_multiplier = 1; + } + lo_measure_gain_values(dev, *max_rx_gain, + has_loopback_gain(phy)); + } while (++repeat_cnt < max_repeat); +} + +static +struct b43_lo_calib *b43_calibrate_lo_setting(struct b43_wldev *dev, + const struct b43_bbatt *bbatt, + const struct b43_rfatt *rfatt) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + struct b43_loctl loctl = { + .i = 0, + .q = 0, + }; + int max_rx_gain; + struct b43_lo_calib *cal; + struct lo_g_saved_values uninitialized_var(saved_regs); + /* Values from the "TXCTL Register and Value Table" */ + u16 txctl_reg; + u16 txctl_value; + u16 pad_mix_gain; + + saved_regs.old_channel = phy->channel; + b43_mac_suspend(dev); + lo_measure_setup(dev, &saved_regs); + + txctl_reg = lo_txctl_register_table(dev, &txctl_value, &pad_mix_gain); + + b43_radio_maskset(dev, 0x43, 0xFFF0, rfatt->att); + b43_radio_maskset(dev, txctl_reg, ~txctl_value, (rfatt->with_padmix ? txctl_value :0)); + + max_rx_gain = rfatt->att * 2; + max_rx_gain += bbatt->att / 2; + if (rfatt->with_padmix) + max_rx_gain -= pad_mix_gain; + if (has_loopback_gain(phy)) + max_rx_gain += gphy->max_lb_gain; + lo_measure_gain_values(dev, max_rx_gain, + has_loopback_gain(phy)); + + b43_gphy_set_baseband_attenuation(dev, bbatt->att); + lo_probe_loctls_statemachine(dev, &loctl, &max_rx_gain); + + lo_measure_restore(dev, &saved_regs); + b43_mac_enable(dev); + + if (b43_debug(dev, B43_DBG_LO)) { + b43dbg(dev->wl, "LO: Calibrated for BB(%u), RF(%u,%u) " + "=> I=%d Q=%d\n", + bbatt->att, rfatt->att, rfatt->with_padmix, + loctl.i, loctl.q); + } + + cal = kmalloc(sizeof(*cal), GFP_KERNEL); + if (!cal) { + b43warn(dev->wl, "LO calib: out of memory\n"); + return NULL; + } + memcpy(&cal->bbatt, bbatt, sizeof(*bbatt)); + memcpy(&cal->rfatt, rfatt, sizeof(*rfatt)); + memcpy(&cal->ctl, &loctl, sizeof(loctl)); + cal->calib_time = jiffies; + INIT_LIST_HEAD(&cal->list); + + return cal; +} + +/* Get a calibrated LO setting for the given attenuation values. + * Might return a NULL pointer under OOM! */ +static +struct b43_lo_calib *b43_get_calib_lo_settings(struct b43_wldev *dev, + const struct b43_bbatt *bbatt, + const struct b43_rfatt *rfatt) +{ + struct b43_txpower_lo_control *lo = dev->phy.g->lo_control; + struct b43_lo_calib *c; + + c = b43_find_lo_calib(lo, bbatt, rfatt); + if (c) + return c; + /* Not in the list of calibrated LO settings. + * Calibrate it now. */ + c = b43_calibrate_lo_setting(dev, bbatt, rfatt); + if (!c) + return NULL; + list_add(&c->list, &lo->calib_list); + + return c; +} + +void b43_gphy_dc_lt_init(struct b43_wldev *dev, bool update_all) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + struct b43_txpower_lo_control *lo = gphy->lo_control; + int i; + int rf_offset, bb_offset; + const struct b43_rfatt *rfatt; + const struct b43_bbatt *bbatt; + u64 power_vector; + bool table_changed = false; + + BUILD_BUG_ON(B43_DC_LT_SIZE != 32); + B43_WARN_ON(lo->rfatt_list.len * lo->bbatt_list.len > 64); + + power_vector = lo->power_vector; + if (!update_all && !power_vector) + return; /* Nothing to do. */ + + /* Suspend the MAC now to avoid continuous suspend/enable + * cycles in the loop. */ + b43_mac_suspend(dev); + + for (i = 0; i < B43_DC_LT_SIZE * 2; i++) { + struct b43_lo_calib *cal; + int idx; + u16 val; + + if (!update_all && !(power_vector & (((u64)1ULL) << i))) + continue; + /* Update the table entry for this power_vector bit. + * The table rows are RFatt entries and columns are BBatt. */ + bb_offset = i / lo->rfatt_list.len; + rf_offset = i % lo->rfatt_list.len; + bbatt = &(lo->bbatt_list.list[bb_offset]); + rfatt = &(lo->rfatt_list.list[rf_offset]); + + cal = b43_calibrate_lo_setting(dev, bbatt, rfatt); + if (!cal) { + b43warn(dev->wl, "LO: Could not " + "calibrate DC table entry\n"); + continue; + } + /*FIXME: Is Q really in the low nibble? */ + val = (u8)(cal->ctl.q); + val |= ((u8)(cal->ctl.i)) << 4; + kfree(cal); + + /* Get the index into the hardware DC LT. */ + idx = i / 2; + /* Change the table in memory. */ + if (i % 2) { + /* Change the high byte. */ + lo->dc_lt[idx] = (lo->dc_lt[idx] & 0x00FF) + | ((val & 0x00FF) << 8); + } else { + /* Change the low byte. */ + lo->dc_lt[idx] = (lo->dc_lt[idx] & 0xFF00) + | (val & 0x00FF); + } + table_changed = true; + } + if (table_changed) { + /* The table changed in memory. Update the hardware table. */ + for (i = 0; i < B43_DC_LT_SIZE; i++) + b43_phy_write(dev, 0x3A0 + i, lo->dc_lt[i]); + } + b43_mac_enable(dev); +} + +/* Fixup the RF attenuation value for the case where we are + * using the PAD mixer. */ +static inline void b43_lo_fixup_rfatt(struct b43_rfatt *rf) +{ + if (!rf->with_padmix) + return; + if ((rf->att != 1) && (rf->att != 2) && (rf->att != 3)) + rf->att = 4; +} + +void b43_lo_g_adjust(struct b43_wldev *dev) +{ + struct b43_phy_g *gphy = dev->phy.g; + struct b43_lo_calib *cal; + struct b43_rfatt rf; + + memcpy(&rf, &gphy->rfatt, sizeof(rf)); + b43_lo_fixup_rfatt(&rf); + + cal = b43_get_calib_lo_settings(dev, &gphy->bbatt, &rf); + if (!cal) + return; + b43_lo_write(dev, &cal->ctl); +} + +void b43_lo_g_adjust_to(struct b43_wldev *dev, + u16 rfatt, u16 bbatt, u16 tx_control) +{ + struct b43_rfatt rf; + struct b43_bbatt bb; + struct b43_lo_calib *cal; + + memset(&rf, 0, sizeof(rf)); + memset(&bb, 0, sizeof(bb)); + rf.att = rfatt; + bb.att = bbatt; + b43_lo_fixup_rfatt(&rf); + cal = b43_get_calib_lo_settings(dev, &bb, &rf); + if (!cal) + return; + b43_lo_write(dev, &cal->ctl); +} + +/* Periodic LO maintanance work */ +void b43_lo_g_maintanance_work(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + struct b43_txpower_lo_control *lo = gphy->lo_control; + unsigned long now; + unsigned long expire; + struct b43_lo_calib *cal, *tmp; + bool current_item_expired = false; + bool hwpctl; + + if (!lo) + return; + now = jiffies; + hwpctl = b43_has_hardware_pctl(dev); + + if (hwpctl) { + /* Read the power vector and update it, if needed. */ + expire = now - B43_LO_PWRVEC_EXPIRE; + if (time_before(lo->pwr_vec_read_time, expire)) { + lo_read_power_vector(dev); + b43_gphy_dc_lt_init(dev, 0); + } + //FIXME Recalc the whole DC table from time to time? + } + + if (hwpctl) + return; + /* Search for expired LO settings. Remove them. + * Recalibrate the current setting, if expired. */ + expire = now - B43_LO_CALIB_EXPIRE; + list_for_each_entry_safe(cal, tmp, &lo->calib_list, list) { + if (!time_before(cal->calib_time, expire)) + continue; + /* This item expired. */ + if (b43_compare_bbatt(&cal->bbatt, &gphy->bbatt) && + b43_compare_rfatt(&cal->rfatt, &gphy->rfatt)) { + B43_WARN_ON(current_item_expired); + current_item_expired = true; + } + if (b43_debug(dev, B43_DBG_LO)) { + b43dbg(dev->wl, "LO: Item BB(%u), RF(%u,%u), " + "I=%d, Q=%d expired\n", + cal->bbatt.att, cal->rfatt.att, + cal->rfatt.with_padmix, + cal->ctl.i, cal->ctl.q); + } + list_del(&cal->list); + kfree(cal); + } + if (current_item_expired || unlikely(list_empty(&lo->calib_list))) { + /* Recalibrate currently used LO setting. */ + if (b43_debug(dev, B43_DBG_LO)) + b43dbg(dev->wl, "LO: Recalibrating current LO setting\n"); + cal = b43_calibrate_lo_setting(dev, &gphy->bbatt, &gphy->rfatt); + if (cal) { + list_add(&cal->list, &lo->calib_list); + b43_lo_write(dev, &cal->ctl); + } else + b43warn(dev->wl, "Failed to recalibrate current LO setting\n"); + } +} + +void b43_lo_g_cleanup(struct b43_wldev *dev) +{ + struct b43_txpower_lo_control *lo = dev->phy.g->lo_control; + struct b43_lo_calib *cal, *tmp; + + if (!lo) + return; + list_for_each_entry_safe(cal, tmp, &lo->calib_list, list) { + list_del(&cal->list); + kfree(cal); + } +} + +/* LO Initialization */ +void b43_lo_g_init(struct b43_wldev *dev) +{ + if (b43_has_hardware_pctl(dev)) { + lo_read_power_vector(dev); + b43_gphy_dc_lt_init(dev, 1); + } +} diff --git a/drivers/net/wireless/b43/lo.h b/drivers/net/wireless/b43/lo.h new file mode 100644 index 0000000..3b27e20 --- /dev/null +++ b/drivers/net/wireless/b43/lo.h @@ -0,0 +1,87 @@ +#ifndef B43_LO_H_ +#define B43_LO_H_ + +/* G-PHY Local Oscillator */ + +#include "phy_g.h" + +struct b43_wldev; + +/* Local Oscillator control value-pair. */ +struct b43_loctl { + /* Control values. */ + s8 i; + s8 q; +}; +/* Debugging: Poison value for i and q values. */ +#define B43_LOCTL_POISON 111 + +/* This struct holds calibrated LO settings for a set of + * Baseband and RF attenuation settings. */ +struct b43_lo_calib { + /* The set of attenuation values this set of LO + * control values is calibrated for. */ + struct b43_bbatt bbatt; + struct b43_rfatt rfatt; + /* The set of control values for the LO. */ + struct b43_loctl ctl; + /* The time when these settings were calibrated (in jiffies) */ + unsigned long calib_time; + /* List. */ + struct list_head list; +}; + +/* Size of the DC Lookup Table in 16bit words. */ +#define B43_DC_LT_SIZE 32 + +/* Local Oscillator calibration information */ +struct b43_txpower_lo_control { + /* Lists of RF and BB attenuation values for this device. + * Used for building hardware power control tables. */ + struct b43_rfatt_list rfatt_list; + struct b43_bbatt_list bbatt_list; + + /* The DC Lookup Table is cached in memory here. + * Note that this is only used for Hardware Power Control. */ + u16 dc_lt[B43_DC_LT_SIZE]; + + /* List of calibrated control values (struct b43_lo_calib). */ + struct list_head calib_list; + /* Last time the power vector was read (jiffies). */ + unsigned long pwr_vec_read_time; + /* Last time the txctl values were measured (jiffies). */ + unsigned long txctl_measured_time; + + /* Current TX Bias value */ + u8 tx_bias; + /* Current TX Magnification Value (if used by the device) */ + u8 tx_magn; + + /* Saved device PowerVector */ + u64 power_vector; +}; + +/* Calibration expire timeouts. + * Timeouts must be multiple of 15 seconds. To make sure + * the item really expired when the 15 second timer hits, we + * subtract two additional seconds from the timeout. */ +#define B43_LO_CALIB_EXPIRE (HZ * (30 - 2)) +#define B43_LO_PWRVEC_EXPIRE (HZ * (30 - 2)) +#define B43_LO_TXCTL_EXPIRE (HZ * (180 - 4)) + + +/* Adjust the Local Oscillator to the saved attenuation + * and txctl values. + */ +void b43_lo_g_adjust(struct b43_wldev *dev); +/* Adjust to specific values. */ +void b43_lo_g_adjust_to(struct b43_wldev *dev, + u16 rfatt, u16 bbatt, u16 tx_control); + +void b43_gphy_dc_lt_init(struct b43_wldev *dev, bool update_all); + +void b43_lo_g_maintanance_work(struct b43_wldev *dev); +void b43_lo_g_cleanup(struct b43_wldev *dev); +void b43_lo_g_init(struct b43_wldev *dev); + +#endif /* B43_LO_H_ */ diff --git a/drivers/net/wireless/b43/main.c b/drivers/net/wireless/b43/main.c new file mode 100644 index 0000000..bc68a0c --- /dev/null +++ b/drivers/net/wireless/b43/main.c @@ -0,0 +1,5646 @@ +/* + + Broadcom B43 wireless driver + + Copyright (c) 2005 Martin Langer + Copyright (c) 2005 Stefano Brivio + Copyright (c) 2005-2009 Michael Buesch + Copyright (c) 2005 Danny van Dyk + Copyright (c) 2005 Andreas Jaggi + Copyright (c) 2010-2011 RafaÅ‚ MiÅ‚ecki + + SDIO support + Copyright (c) 2009 Albert Herranz + + Some parts of the code in this file are derived from the ipw2200 + driver Copyright(c) 2003 - 2004 Intel Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "b43.h" +#include "main.h" +#include "debugfs.h" +#include "phy_common.h" +#include "phy_g.h" +#include "phy_n.h" +#include "dma.h" +#include "pio.h" +#include "sysfs.h" +#include "xmit.h" +#include "lo.h" +#include "pcmcia.h" +#include "sdio.h" +#include + +MODULE_DESCRIPTION("Broadcom B43 wireless driver"); +MODULE_AUTHOR("Martin Langer"); +MODULE_AUTHOR("Stefano Brivio"); +MODULE_AUTHOR("Michael Buesch"); +MODULE_AUTHOR("Gábor Stefanik"); +MODULE_AUTHOR("RafaÅ‚ MiÅ‚ecki"); +MODULE_LICENSE("GPL"); + +MODULE_FIRMWARE("b43/ucode11.fw"); +MODULE_FIRMWARE("b43/ucode13.fw"); +MODULE_FIRMWARE("b43/ucode14.fw"); +MODULE_FIRMWARE("b43/ucode15.fw"); +MODULE_FIRMWARE("b43/ucode16_mimo.fw"); +MODULE_FIRMWARE("b43/ucode5.fw"); +MODULE_FIRMWARE("b43/ucode9.fw"); + +static int modparam_bad_frames_preempt; +module_param_named(bad_frames_preempt, modparam_bad_frames_preempt, int, 0444); +MODULE_PARM_DESC(bad_frames_preempt, + "enable(1) / disable(0) Bad Frames Preemption"); + +static char modparam_fwpostfix[16]; +module_param_string(fwpostfix, modparam_fwpostfix, 16, 0444); +MODULE_PARM_DESC(fwpostfix, "Postfix for the .fw files to load."); + +static int modparam_hwpctl; +module_param_named(hwpctl, modparam_hwpctl, int, 0444); +MODULE_PARM_DESC(hwpctl, "Enable hardware-side power control (default off)"); + +static int modparam_nohwcrypt; +module_param_named(nohwcrypt, modparam_nohwcrypt, int, 0444); +MODULE_PARM_DESC(nohwcrypt, "Disable hardware encryption."); + +static int modparam_hwtkip; +module_param_named(hwtkip, modparam_hwtkip, int, 0444); +MODULE_PARM_DESC(hwtkip, "Enable hardware tkip."); + +static int modparam_qos = 1; +module_param_named(qos, modparam_qos, int, 0444); +MODULE_PARM_DESC(qos, "Enable QOS support (default on)"); + +static int modparam_btcoex = 1; +module_param_named(btcoex, modparam_btcoex, int, 0444); +MODULE_PARM_DESC(btcoex, "Enable Bluetooth coexistence (default on)"); + +int b43_modparam_verbose = B43_VERBOSITY_DEFAULT; +module_param_named(verbose, b43_modparam_verbose, int, 0644); +MODULE_PARM_DESC(verbose, "Log message verbosity: 0=error, 1=warn, 2=info(default), 3=debug"); + +static int b43_modparam_pio = 0; +module_param_named(pio, b43_modparam_pio, int, 0644); +MODULE_PARM_DESC(pio, "Use PIO accesses by default: 0=DMA, 1=PIO"); + +#ifdef CPTCFG_B43_BCMA +static const struct bcma_device_id b43_bcma_tbl[] = { + BCMA_CORE(BCMA_MANUF_BCM, BCMA_CORE_80211, 0x11, BCMA_ANY_CLASS), +#ifdef CPTCFG_B43_BCMA_EXTRA + BCMA_CORE(BCMA_MANUF_BCM, BCMA_CORE_80211, 0x17, BCMA_ANY_CLASS), + BCMA_CORE(BCMA_MANUF_BCM, BCMA_CORE_80211, 0x18, BCMA_ANY_CLASS), +#endif + BCMA_CORE(BCMA_MANUF_BCM, BCMA_CORE_80211, 0x1D, BCMA_ANY_CLASS), + BCMA_CORETABLE_END +}; +MODULE_DEVICE_TABLE(bcma, b43_bcma_tbl); +#endif + +#ifdef CPTCFG_B43_SSB +static const struct ssb_device_id b43_ssb_tbl[] = { + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_80211, 5), + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_80211, 6), + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_80211, 7), + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_80211, 9), + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_80211, 10), + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_80211, 11), + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_80211, 12), + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_80211, 13), + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_80211, 15), + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_80211, 16), + SSB_DEVTABLE_END +}; +MODULE_DEVICE_TABLE(ssb, b43_ssb_tbl); +#endif + +/* Channel and ratetables are shared for all devices. + * They can't be const, because ieee80211 puts some precalculated + * data in there. This data is the same for all devices, so we don't + * get concurrency issues */ +#define RATETAB_ENT(_rateid, _flags) \ + { \ + .bitrate = B43_RATE_TO_BASE100KBPS(_rateid), \ + .hw_value = (_rateid), \ + .flags = (_flags), \ + } + +/* + * NOTE: When changing this, sync with xmit.c's + * b43_plcp_get_bitrate_idx_* functions! + */ +static struct ieee80211_rate __b43_ratetable[] = { + RATETAB_ENT(B43_CCK_RATE_1MB, 0), + RATETAB_ENT(B43_CCK_RATE_2MB, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(B43_CCK_RATE_5MB, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(B43_CCK_RATE_11MB, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(B43_OFDM_RATE_6MB, 0), + RATETAB_ENT(B43_OFDM_RATE_9MB, 0), + RATETAB_ENT(B43_OFDM_RATE_12MB, 0), + RATETAB_ENT(B43_OFDM_RATE_18MB, 0), + RATETAB_ENT(B43_OFDM_RATE_24MB, 0), + RATETAB_ENT(B43_OFDM_RATE_36MB, 0), + RATETAB_ENT(B43_OFDM_RATE_48MB, 0), + RATETAB_ENT(B43_OFDM_RATE_54MB, 0), +}; + +#define b43_a_ratetable (__b43_ratetable + 4) +#define b43_a_ratetable_size 8 +#define b43_b_ratetable (__b43_ratetable + 0) +#define b43_b_ratetable_size 4 +#define b43_g_ratetable (__b43_ratetable + 0) +#define b43_g_ratetable_size 12 + +#define CHAN4G(_channel, _freq, _flags) { \ + .band = IEEE80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_channel), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} +static struct ieee80211_channel b43_2ghz_chantable[] = { + CHAN4G(1, 2412, 0), + CHAN4G(2, 2417, 0), + CHAN4G(3, 2422, 0), + CHAN4G(4, 2427, 0), + CHAN4G(5, 2432, 0), + CHAN4G(6, 2437, 0), + CHAN4G(7, 2442, 0), + CHAN4G(8, 2447, 0), + CHAN4G(9, 2452, 0), + CHAN4G(10, 2457, 0), + CHAN4G(11, 2462, 0), + CHAN4G(12, 2467, 0), + CHAN4G(13, 2472, 0), + CHAN4G(14, 2484, 0), +}; +#undef CHAN4G + +#define CHAN5G(_channel, _flags) { \ + .band = IEEE80211_BAND_5GHZ, \ + .center_freq = 5000 + (5 * (_channel)), \ + .hw_value = (_channel), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} +static struct ieee80211_channel b43_5ghz_nphy_chantable[] = { + CHAN5G(32, 0), CHAN5G(34, 0), + CHAN5G(36, 0), CHAN5G(38, 0), + CHAN5G(40, 0), CHAN5G(42, 0), + CHAN5G(44, 0), CHAN5G(46, 0), + CHAN5G(48, 0), CHAN5G(50, 0), + CHAN5G(52, 0), CHAN5G(54, 0), + CHAN5G(56, 0), CHAN5G(58, 0), + CHAN5G(60, 0), CHAN5G(62, 0), + CHAN5G(64, 0), CHAN5G(66, 0), + CHAN5G(68, 0), CHAN5G(70, 0), + CHAN5G(72, 0), CHAN5G(74, 0), + CHAN5G(76, 0), CHAN5G(78, 0), + CHAN5G(80, 0), CHAN5G(82, 0), + CHAN5G(84, 0), CHAN5G(86, 0), + CHAN5G(88, 0), CHAN5G(90, 0), + CHAN5G(92, 0), CHAN5G(94, 0), + CHAN5G(96, 0), CHAN5G(98, 0), + CHAN5G(100, 0), CHAN5G(102, 0), + CHAN5G(104, 0), CHAN5G(106, 0), + CHAN5G(108, 0), CHAN5G(110, 0), + CHAN5G(112, 0), CHAN5G(114, 0), + CHAN5G(116, 0), CHAN5G(118, 0), + CHAN5G(120, 0), CHAN5G(122, 0), + CHAN5G(124, 0), CHAN5G(126, 0), + CHAN5G(128, 0), CHAN5G(130, 0), + CHAN5G(132, 0), CHAN5G(134, 0), + CHAN5G(136, 0), CHAN5G(138, 0), + CHAN5G(140, 0), CHAN5G(142, 0), + CHAN5G(144, 0), CHAN5G(145, 0), + CHAN5G(146, 0), CHAN5G(147, 0), + CHAN5G(148, 0), CHAN5G(149, 0), + CHAN5G(150, 0), CHAN5G(151, 0), + CHAN5G(152, 0), CHAN5G(153, 0), + CHAN5G(154, 0), CHAN5G(155, 0), + CHAN5G(156, 0), CHAN5G(157, 0), + CHAN5G(158, 0), CHAN5G(159, 0), + CHAN5G(160, 0), CHAN5G(161, 0), + CHAN5G(162, 0), CHAN5G(163, 0), + CHAN5G(164, 0), CHAN5G(165, 0), + CHAN5G(166, 0), CHAN5G(168, 0), + CHAN5G(170, 0), CHAN5G(172, 0), + CHAN5G(174, 0), CHAN5G(176, 0), + CHAN5G(178, 0), CHAN5G(180, 0), + CHAN5G(182, 0), CHAN5G(184, 0), + CHAN5G(186, 0), CHAN5G(188, 0), + CHAN5G(190, 0), CHAN5G(192, 0), + CHAN5G(194, 0), CHAN5G(196, 0), + CHAN5G(198, 0), CHAN5G(200, 0), + CHAN5G(202, 0), CHAN5G(204, 0), + CHAN5G(206, 0), CHAN5G(208, 0), + CHAN5G(210, 0), CHAN5G(212, 0), + CHAN5G(214, 0), CHAN5G(216, 0), + CHAN5G(218, 0), CHAN5G(220, 0), + CHAN5G(222, 0), CHAN5G(224, 0), + CHAN5G(226, 0), CHAN5G(228, 0), +}; + +static struct ieee80211_channel b43_5ghz_aphy_chantable[] = { + CHAN5G(34, 0), CHAN5G(36, 0), + CHAN5G(38, 0), CHAN5G(40, 0), + CHAN5G(42, 0), CHAN5G(44, 0), + CHAN5G(46, 0), CHAN5G(48, 0), + CHAN5G(52, 0), CHAN5G(56, 0), + CHAN5G(60, 0), CHAN5G(64, 0), + CHAN5G(100, 0), CHAN5G(104, 0), + CHAN5G(108, 0), CHAN5G(112, 0), + CHAN5G(116, 0), CHAN5G(120, 0), + CHAN5G(124, 0), CHAN5G(128, 0), + CHAN5G(132, 0), CHAN5G(136, 0), + CHAN5G(140, 0), CHAN5G(149, 0), + CHAN5G(153, 0), CHAN5G(157, 0), + CHAN5G(161, 0), CHAN5G(165, 0), + CHAN5G(184, 0), CHAN5G(188, 0), + CHAN5G(192, 0), CHAN5G(196, 0), + CHAN5G(200, 0), CHAN5G(204, 0), + CHAN5G(208, 0), CHAN5G(212, 0), + CHAN5G(216, 0), +}; +#undef CHAN5G + +static struct ieee80211_supported_band b43_band_5GHz_nphy = { + .band = IEEE80211_BAND_5GHZ, + .channels = b43_5ghz_nphy_chantable, + .n_channels = ARRAY_SIZE(b43_5ghz_nphy_chantable), + .bitrates = b43_a_ratetable, + .n_bitrates = b43_a_ratetable_size, +}; + +static struct ieee80211_supported_band b43_band_5GHz_aphy = { + .band = IEEE80211_BAND_5GHZ, + .channels = b43_5ghz_aphy_chantable, + .n_channels = ARRAY_SIZE(b43_5ghz_aphy_chantable), + .bitrates = b43_a_ratetable, + .n_bitrates = b43_a_ratetable_size, +}; + +static struct ieee80211_supported_band b43_band_2GHz = { + .band = IEEE80211_BAND_2GHZ, + .channels = b43_2ghz_chantable, + .n_channels = ARRAY_SIZE(b43_2ghz_chantable), + .bitrates = b43_g_ratetable, + .n_bitrates = b43_g_ratetable_size, +}; + +static void b43_wireless_core_exit(struct b43_wldev *dev); +static int b43_wireless_core_init(struct b43_wldev *dev); +static struct b43_wldev * b43_wireless_core_stop(struct b43_wldev *dev); +static int b43_wireless_core_start(struct b43_wldev *dev); +static void b43_op_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *conf, + u32 changed); + +static int b43_ratelimit(struct b43_wl *wl) +{ + if (!wl || !wl->current_dev) + return 1; + if (b43_status(wl->current_dev) < B43_STAT_STARTED) + return 1; + /* We are up and running. + * Ratelimit the messages to avoid DoS over the net. */ + return net_ratelimit(); +} + +void b43info(struct b43_wl *wl, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + if (b43_modparam_verbose < B43_VERBOSITY_INFO) + return; + if (!b43_ratelimit(wl)) + return; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + printk(KERN_INFO "b43-%s: %pV", + (wl && wl->hw) ? wiphy_name(wl->hw->wiphy) : "wlan", &vaf); + + va_end(args); +} + +void b43err(struct b43_wl *wl, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + if (b43_modparam_verbose < B43_VERBOSITY_ERROR) + return; + if (!b43_ratelimit(wl)) + return; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + printk(KERN_ERR "b43-%s ERROR: %pV", + (wl && wl->hw) ? wiphy_name(wl->hw->wiphy) : "wlan", &vaf); + + va_end(args); +} + +void b43warn(struct b43_wl *wl, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + if (b43_modparam_verbose < B43_VERBOSITY_WARN) + return; + if (!b43_ratelimit(wl)) + return; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + printk(KERN_WARNING "b43-%s warning: %pV", + (wl && wl->hw) ? wiphy_name(wl->hw->wiphy) : "wlan", &vaf); + + va_end(args); +} + +void b43dbg(struct b43_wl *wl, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + if (b43_modparam_verbose < B43_VERBOSITY_DEBUG) + return; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + printk(KERN_DEBUG "b43-%s debug: %pV", + (wl && wl->hw) ? wiphy_name(wl->hw->wiphy) : "wlan", &vaf); + + va_end(args); +} + +static void b43_ram_write(struct b43_wldev *dev, u16 offset, u32 val) +{ + u32 macctl; + + B43_WARN_ON(offset % 4 != 0); + + macctl = b43_read32(dev, B43_MMIO_MACCTL); + if (macctl & B43_MACCTL_BE) + val = swab32(val); + + b43_write32(dev, B43_MMIO_RAM_CONTROL, offset); + mmiowb(); + b43_write32(dev, B43_MMIO_RAM_DATA, val); +} + +static inline void b43_shm_control_word(struct b43_wldev *dev, + u16 routing, u16 offset) +{ + u32 control; + + /* "offset" is the WORD offset. */ + control = routing; + control <<= 16; + control |= offset; + b43_write32(dev, B43_MMIO_SHM_CONTROL, control); +} + +u32 b43_shm_read32(struct b43_wldev *dev, u16 routing, u16 offset) +{ + u32 ret; + + if (routing == B43_SHM_SHARED) { + B43_WARN_ON(offset & 0x0001); + if (offset & 0x0003) { + /* Unaligned access */ + b43_shm_control_word(dev, routing, offset >> 2); + ret = b43_read16(dev, B43_MMIO_SHM_DATA_UNALIGNED); + b43_shm_control_word(dev, routing, (offset >> 2) + 1); + ret |= ((u32)b43_read16(dev, B43_MMIO_SHM_DATA)) << 16; + + goto out; + } + offset >>= 2; + } + b43_shm_control_word(dev, routing, offset); + ret = b43_read32(dev, B43_MMIO_SHM_DATA); +out: + return ret; +} + +u16 b43_shm_read16(struct b43_wldev *dev, u16 routing, u16 offset) +{ + u16 ret; + + if (routing == B43_SHM_SHARED) { + B43_WARN_ON(offset & 0x0001); + if (offset & 0x0003) { + /* Unaligned access */ + b43_shm_control_word(dev, routing, offset >> 2); + ret = b43_read16(dev, B43_MMIO_SHM_DATA_UNALIGNED); + + goto out; + } + offset >>= 2; + } + b43_shm_control_word(dev, routing, offset); + ret = b43_read16(dev, B43_MMIO_SHM_DATA); +out: + return ret; +} + +void b43_shm_write32(struct b43_wldev *dev, u16 routing, u16 offset, u32 value) +{ + if (routing == B43_SHM_SHARED) { + B43_WARN_ON(offset & 0x0001); + if (offset & 0x0003) { + /* Unaligned access */ + b43_shm_control_word(dev, routing, offset >> 2); + b43_write16(dev, B43_MMIO_SHM_DATA_UNALIGNED, + value & 0xFFFF); + b43_shm_control_word(dev, routing, (offset >> 2) + 1); + b43_write16(dev, B43_MMIO_SHM_DATA, + (value >> 16) & 0xFFFF); + return; + } + offset >>= 2; + } + b43_shm_control_word(dev, routing, offset); + b43_write32(dev, B43_MMIO_SHM_DATA, value); +} + +void b43_shm_write16(struct b43_wldev *dev, u16 routing, u16 offset, u16 value) +{ + if (routing == B43_SHM_SHARED) { + B43_WARN_ON(offset & 0x0001); + if (offset & 0x0003) { + /* Unaligned access */ + b43_shm_control_word(dev, routing, offset >> 2); + b43_write16(dev, B43_MMIO_SHM_DATA_UNALIGNED, value); + return; + } + offset >>= 2; + } + b43_shm_control_word(dev, routing, offset); + b43_write16(dev, B43_MMIO_SHM_DATA, value); +} + +/* Read HostFlags */ +u64 b43_hf_read(struct b43_wldev *dev) +{ + u64 ret; + + ret = b43_shm_read16(dev, B43_SHM_SHARED, B43_SHM_SH_HOSTF3); + ret <<= 16; + ret |= b43_shm_read16(dev, B43_SHM_SHARED, B43_SHM_SH_HOSTF2); + ret <<= 16; + ret |= b43_shm_read16(dev, B43_SHM_SHARED, B43_SHM_SH_HOSTF1); + + return ret; +} + +/* Write HostFlags */ +void b43_hf_write(struct b43_wldev *dev, u64 value) +{ + u16 lo, mi, hi; + + lo = (value & 0x00000000FFFFULL); + mi = (value & 0x0000FFFF0000ULL) >> 16; + hi = (value & 0xFFFF00000000ULL) >> 32; + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_HOSTF1, lo); + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_HOSTF2, mi); + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_HOSTF3, hi); +} + +/* Read the firmware capabilities bitmask (Opensource firmware only) */ +static u16 b43_fwcapa_read(struct b43_wldev *dev) +{ + B43_WARN_ON(!dev->fw.opensource); + return b43_shm_read16(dev, B43_SHM_SHARED, B43_SHM_SH_FWCAPA); +} + +void b43_tsf_read(struct b43_wldev *dev, u64 *tsf) +{ + u32 low, high; + + B43_WARN_ON(dev->dev->core_rev < 3); + + /* The hardware guarantees us an atomic read, if we + * read the low register first. */ + low = b43_read32(dev, B43_MMIO_REV3PLUS_TSF_LOW); + high = b43_read32(dev, B43_MMIO_REV3PLUS_TSF_HIGH); + + *tsf = high; + *tsf <<= 32; + *tsf |= low; +} + +static void b43_time_lock(struct b43_wldev *dev) +{ + b43_maskset32(dev, B43_MMIO_MACCTL, ~0, B43_MACCTL_TBTTHOLD); + /* Commit the write */ + b43_read32(dev, B43_MMIO_MACCTL); +} + +static void b43_time_unlock(struct b43_wldev *dev) +{ + b43_maskset32(dev, B43_MMIO_MACCTL, ~B43_MACCTL_TBTTHOLD, 0); + /* Commit the write */ + b43_read32(dev, B43_MMIO_MACCTL); +} + +static void b43_tsf_write_locked(struct b43_wldev *dev, u64 tsf) +{ + u32 low, high; + + B43_WARN_ON(dev->dev->core_rev < 3); + + low = tsf; + high = (tsf >> 32); + /* The hardware guarantees us an atomic write, if we + * write the low register first. */ + b43_write32(dev, B43_MMIO_REV3PLUS_TSF_LOW, low); + mmiowb(); + b43_write32(dev, B43_MMIO_REV3PLUS_TSF_HIGH, high); + mmiowb(); +} + +void b43_tsf_write(struct b43_wldev *dev, u64 tsf) +{ + b43_time_lock(dev); + b43_tsf_write_locked(dev, tsf); + b43_time_unlock(dev); +} + +static +void b43_macfilter_set(struct b43_wldev *dev, u16 offset, const u8 *mac) +{ + static const u8 zero_addr[ETH_ALEN] = { 0 }; + u16 data; + + if (!mac) + mac = zero_addr; + + offset |= 0x0020; + b43_write16(dev, B43_MMIO_MACFILTER_CONTROL, offset); + + data = mac[0]; + data |= mac[1] << 8; + b43_write16(dev, B43_MMIO_MACFILTER_DATA, data); + data = mac[2]; + data |= mac[3] << 8; + b43_write16(dev, B43_MMIO_MACFILTER_DATA, data); + data = mac[4]; + data |= mac[5] << 8; + b43_write16(dev, B43_MMIO_MACFILTER_DATA, data); +} + +static void b43_write_mac_bssid_templates(struct b43_wldev *dev) +{ + const u8 *mac; + const u8 *bssid; + u8 mac_bssid[ETH_ALEN * 2]; + int i; + u32 tmp; + + bssid = dev->wl->bssid; + mac = dev->wl->mac_addr; + + b43_macfilter_set(dev, B43_MACFILTER_BSSID, bssid); + + memcpy(mac_bssid, mac, ETH_ALEN); + memcpy(mac_bssid + ETH_ALEN, bssid, ETH_ALEN); + + /* Write our MAC address and BSSID to template ram */ + for (i = 0; i < ARRAY_SIZE(mac_bssid); i += sizeof(u32)) { + tmp = (u32) (mac_bssid[i + 0]); + tmp |= (u32) (mac_bssid[i + 1]) << 8; + tmp |= (u32) (mac_bssid[i + 2]) << 16; + tmp |= (u32) (mac_bssid[i + 3]) << 24; + b43_ram_write(dev, 0x20 + i, tmp); + } +} + +static void b43_upload_card_macaddress(struct b43_wldev *dev) +{ + b43_write_mac_bssid_templates(dev); + b43_macfilter_set(dev, B43_MACFILTER_SELF, dev->wl->mac_addr); +} + +static void b43_set_slot_time(struct b43_wldev *dev, u16 slot_time) +{ + /* slot_time is in usec. */ + /* This test used to exit for all but a G PHY. */ + if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) + return; + b43_write16(dev, B43_MMIO_IFSSLOT, 510 + slot_time); + /* Shared memory location 0x0010 is the slot time and should be + * set to slot_time; however, this register is initially 0 and changing + * the value adversely affects the transmit rate for BCM4311 + * devices. Until this behavior is unterstood, delete this step + * + * b43_shm_write16(dev, B43_SHM_SHARED, 0x0010, slot_time); + */ +} + +static void b43_short_slot_timing_enable(struct b43_wldev *dev) +{ + b43_set_slot_time(dev, 9); +} + +static void b43_short_slot_timing_disable(struct b43_wldev *dev) +{ + b43_set_slot_time(dev, 20); +} + +/* DummyTransmission function, as documented on + * http://bcm-v4.sipsolutions.net/802.11/DummyTransmission + */ +void b43_dummy_transmission(struct b43_wldev *dev, bool ofdm, bool pa_on) +{ + struct b43_phy *phy = &dev->phy; + unsigned int i, max_loop; + u16 value; + u32 buffer[5] = { + 0x00000000, + 0x00D40000, + 0x00000000, + 0x01000000, + 0x00000000, + }; + + if (ofdm) { + max_loop = 0x1E; + buffer[0] = 0x000201CC; + } else { + max_loop = 0xFA; + buffer[0] = 0x000B846E; + } + + for (i = 0; i < 5; i++) + b43_ram_write(dev, i * 4, buffer[i]); + + b43_write16(dev, B43_MMIO_XMTSEL, 0x0000); + + if (dev->dev->core_rev < 11) + b43_write16(dev, B43_MMIO_WEPCTL, 0x0000); + else + b43_write16(dev, B43_MMIO_WEPCTL, 0x0100); + + value = (ofdm ? 0x41 : 0x40); + b43_write16(dev, B43_MMIO_TXE0_PHYCTL, value); + if (phy->type == B43_PHYTYPE_N || phy->type == B43_PHYTYPE_LP || + phy->type == B43_PHYTYPE_LCN) + b43_write16(dev, B43_MMIO_TXE0_PHYCTL1, 0x1A02); + + b43_write16(dev, B43_MMIO_TXE0_WM_0, 0x0000); + b43_write16(dev, B43_MMIO_TXE0_WM_1, 0x0000); + + b43_write16(dev, B43_MMIO_XMTTPLATETXPTR, 0x0000); + b43_write16(dev, B43_MMIO_XMTTXCNT, 0x0014); + b43_write16(dev, B43_MMIO_XMTSEL, 0x0826); + b43_write16(dev, B43_MMIO_TXE0_CTL, 0x0000); + + if (!pa_on && phy->type == B43_PHYTYPE_N) + ; /*b43_nphy_pa_override(dev, false) */ + + switch (phy->type) { + case B43_PHYTYPE_N: + case B43_PHYTYPE_LCN: + b43_write16(dev, B43_MMIO_TXE0_AUX, 0x00D0); + break; + case B43_PHYTYPE_LP: + b43_write16(dev, B43_MMIO_TXE0_AUX, 0x0050); + break; + default: + b43_write16(dev, B43_MMIO_TXE0_AUX, 0x0030); + } + b43_read16(dev, B43_MMIO_TXE0_AUX); + + if (phy->radio_ver == 0x2050 && phy->radio_rev <= 0x5) + b43_radio_write16(dev, 0x0051, 0x0017); + for (i = 0x00; i < max_loop; i++) { + value = b43_read16(dev, B43_MMIO_TXE0_STATUS); + if (value & 0x0080) + break; + udelay(10); + } + for (i = 0x00; i < 0x0A; i++) { + value = b43_read16(dev, B43_MMIO_TXE0_STATUS); + if (value & 0x0400) + break; + udelay(10); + } + for (i = 0x00; i < 0x19; i++) { + value = b43_read16(dev, B43_MMIO_IFSSTAT); + if (!(value & 0x0100)) + break; + udelay(10); + } + if (phy->radio_ver == 0x2050 && phy->radio_rev <= 0x5) + b43_radio_write16(dev, 0x0051, 0x0037); +} + +static void key_write(struct b43_wldev *dev, + u8 index, u8 algorithm, const u8 *key) +{ + unsigned int i; + u32 offset; + u16 value; + u16 kidx; + + /* Key index/algo block */ + kidx = b43_kidx_to_fw(dev, index); + value = ((kidx << 4) | algorithm); + b43_shm_write16(dev, B43_SHM_SHARED, + B43_SHM_SH_KEYIDXBLOCK + (kidx * 2), value); + + /* Write the key to the Key Table Pointer offset */ + offset = dev->ktp + (index * B43_SEC_KEYSIZE); + for (i = 0; i < B43_SEC_KEYSIZE; i += 2) { + value = key[i]; + value |= (u16) (key[i + 1]) << 8; + b43_shm_write16(dev, B43_SHM_SHARED, offset + i, value); + } +} + +static void keymac_write(struct b43_wldev *dev, u8 index, const u8 *addr) +{ + u32 addrtmp[2] = { 0, 0, }; + u8 pairwise_keys_start = B43_NR_GROUP_KEYS * 2; + + if (b43_new_kidx_api(dev)) + pairwise_keys_start = B43_NR_GROUP_KEYS; + + B43_WARN_ON(index < pairwise_keys_start); + /* We have four default TX keys and possibly four default RX keys. + * Physical mac 0 is mapped to physical key 4 or 8, depending + * on the firmware version. + * So we must adjust the index here. + */ + index -= pairwise_keys_start; + B43_WARN_ON(index >= B43_NR_PAIRWISE_KEYS); + + if (addr) { + addrtmp[0] = addr[0]; + addrtmp[0] |= ((u32) (addr[1]) << 8); + addrtmp[0] |= ((u32) (addr[2]) << 16); + addrtmp[0] |= ((u32) (addr[3]) << 24); + addrtmp[1] = addr[4]; + addrtmp[1] |= ((u32) (addr[5]) << 8); + } + + /* Receive match transmitter address (RCMTA) mechanism */ + b43_shm_write32(dev, B43_SHM_RCMTA, + (index * 2) + 0, addrtmp[0]); + b43_shm_write16(dev, B43_SHM_RCMTA, + (index * 2) + 1, addrtmp[1]); +} + +/* The ucode will use phase1 key with TEK key to decrypt rx packets. + * When a packet is received, the iv32 is checked. + * - if it doesn't the packet is returned without modification (and software + * decryption can be done). That's what happen when iv16 wrap. + * - if it does, the rc4 key is computed, and decryption is tried. + * Either it will success and B43_RX_MAC_DEC is returned, + * either it fails and B43_RX_MAC_DEC|B43_RX_MAC_DECERR is returned + * and the packet is not usable (it got modified by the ucode). + * So in order to never have B43_RX_MAC_DECERR, we should provide + * a iv32 and phase1key that match. Because we drop packets in case of + * B43_RX_MAC_DECERR, if we have a correct iv32 but a wrong phase1key, all + * packets will be lost without higher layer knowing (ie no resync possible + * until next wrap). + * + * NOTE : this should support 50 key like RCMTA because + * (B43_SHM_SH_KEYIDXBLOCK - B43_SHM_SH_TKIPTSCTTAK)/14 = 50 + */ +static void rx_tkip_phase1_write(struct b43_wldev *dev, u8 index, u32 iv32, + u16 *phase1key) +{ + unsigned int i; + u32 offset; + u8 pairwise_keys_start = B43_NR_GROUP_KEYS * 2; + + if (!modparam_hwtkip) + return; + + if (b43_new_kidx_api(dev)) + pairwise_keys_start = B43_NR_GROUP_KEYS; + + B43_WARN_ON(index < pairwise_keys_start); + /* We have four default TX keys and possibly four default RX keys. + * Physical mac 0 is mapped to physical key 4 or 8, depending + * on the firmware version. + * So we must adjust the index here. + */ + index -= pairwise_keys_start; + B43_WARN_ON(index >= B43_NR_PAIRWISE_KEYS); + + if (b43_debug(dev, B43_DBG_KEYS)) { + b43dbg(dev->wl, "rx_tkip_phase1_write : idx 0x%x, iv32 0x%x\n", + index, iv32); + } + /* Write the key to the RX tkip shared mem */ + offset = B43_SHM_SH_TKIPTSCTTAK + index * (10 + 4); + for (i = 0; i < 10; i += 2) { + b43_shm_write16(dev, B43_SHM_SHARED, offset + i, + phase1key ? phase1key[i / 2] : 0); + } + b43_shm_write16(dev, B43_SHM_SHARED, offset + i, iv32); + b43_shm_write16(dev, B43_SHM_SHARED, offset + i + 2, iv32 >> 16); +} + +static void b43_op_update_tkip_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, + u32 iv32, u16 *phase1key) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + struct b43_wldev *dev; + int index = keyconf->hw_key_idx; + + if (B43_WARN_ON(!modparam_hwtkip)) + return; + + /* This is only called from the RX path through mac80211, where + * our mutex is already locked. */ + B43_WARN_ON(!mutex_is_locked(&wl->mutex)); + dev = wl->current_dev; + B43_WARN_ON(!dev || b43_status(dev) < B43_STAT_INITIALIZED); + + keymac_write(dev, index, NULL); /* First zero out mac to avoid race */ + + rx_tkip_phase1_write(dev, index, iv32, phase1key); + /* only pairwise TKIP keys are supported right now */ + if (WARN_ON(!sta)) + return; + keymac_write(dev, index, sta->addr); +} + +static void do_key_write(struct b43_wldev *dev, + u8 index, u8 algorithm, + const u8 *key, size_t key_len, const u8 *mac_addr) +{ + u8 buf[B43_SEC_KEYSIZE] = { 0, }; + u8 pairwise_keys_start = B43_NR_GROUP_KEYS * 2; + + if (b43_new_kidx_api(dev)) + pairwise_keys_start = B43_NR_GROUP_KEYS; + + B43_WARN_ON(index >= ARRAY_SIZE(dev->key)); + B43_WARN_ON(key_len > B43_SEC_KEYSIZE); + + if (index >= pairwise_keys_start) + keymac_write(dev, index, NULL); /* First zero out mac. */ + if (algorithm == B43_SEC_ALGO_TKIP) { + /* + * We should provide an initial iv32, phase1key pair. + * We could start with iv32=0 and compute the corresponding + * phase1key, but this means calling ieee80211_get_tkip_key + * with a fake skb (or export other tkip function). + * Because we are lazy we hope iv32 won't start with + * 0xffffffff and let's b43_op_update_tkip_key provide a + * correct pair. + */ + rx_tkip_phase1_write(dev, index, 0xffffffff, (u16*)buf); + } else if (index >= pairwise_keys_start) /* clear it */ + rx_tkip_phase1_write(dev, index, 0, NULL); + if (key) + memcpy(buf, key, key_len); + key_write(dev, index, algorithm, buf); + if (index >= pairwise_keys_start) + keymac_write(dev, index, mac_addr); + + dev->key[index].algorithm = algorithm; +} + +static int b43_key_write(struct b43_wldev *dev, + int index, u8 algorithm, + const u8 *key, size_t key_len, + const u8 *mac_addr, + struct ieee80211_key_conf *keyconf) +{ + int i; + int pairwise_keys_start; + + /* For ALG_TKIP the key is encoded as a 256-bit (32 byte) data block: + * - Temporal Encryption Key (128 bits) + * - Temporal Authenticator Tx MIC Key (64 bits) + * - Temporal Authenticator Rx MIC Key (64 bits) + * + * Hardware only store TEK + */ + if (algorithm == B43_SEC_ALGO_TKIP && key_len == 32) + key_len = 16; + if (key_len > B43_SEC_KEYSIZE) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(dev->key); i++) { + /* Check that we don't already have this key. */ + B43_WARN_ON(dev->key[i].keyconf == keyconf); + } + if (index < 0) { + /* Pairwise key. Get an empty slot for the key. */ + if (b43_new_kidx_api(dev)) + pairwise_keys_start = B43_NR_GROUP_KEYS; + else + pairwise_keys_start = B43_NR_GROUP_KEYS * 2; + for (i = pairwise_keys_start; + i < pairwise_keys_start + B43_NR_PAIRWISE_KEYS; + i++) { + B43_WARN_ON(i >= ARRAY_SIZE(dev->key)); + if (!dev->key[i].keyconf) { + /* found empty */ + index = i; + break; + } + } + if (index < 0) { + b43warn(dev->wl, "Out of hardware key memory\n"); + return -ENOSPC; + } + } else + B43_WARN_ON(index > 3); + + do_key_write(dev, index, algorithm, key, key_len, mac_addr); + if ((index <= 3) && !b43_new_kidx_api(dev)) { + /* Default RX key */ + B43_WARN_ON(mac_addr); + do_key_write(dev, index + 4, algorithm, key, key_len, NULL); + } + keyconf->hw_key_idx = index; + dev->key[index].keyconf = keyconf; + + return 0; +} + +static int b43_key_clear(struct b43_wldev *dev, int index) +{ + if (B43_WARN_ON((index < 0) || (index >= ARRAY_SIZE(dev->key)))) + return -EINVAL; + do_key_write(dev, index, B43_SEC_ALGO_NONE, + NULL, B43_SEC_KEYSIZE, NULL); + if ((index <= 3) && !b43_new_kidx_api(dev)) { + do_key_write(dev, index + 4, B43_SEC_ALGO_NONE, + NULL, B43_SEC_KEYSIZE, NULL); + } + dev->key[index].keyconf = NULL; + + return 0; +} + +static void b43_clear_keys(struct b43_wldev *dev) +{ + int i, count; + + if (b43_new_kidx_api(dev)) + count = B43_NR_GROUP_KEYS + B43_NR_PAIRWISE_KEYS; + else + count = B43_NR_GROUP_KEYS * 2 + B43_NR_PAIRWISE_KEYS; + for (i = 0; i < count; i++) + b43_key_clear(dev, i); +} + +static void b43_dump_keymemory(struct b43_wldev *dev) +{ + unsigned int i, index, count, offset, pairwise_keys_start; + u8 mac[ETH_ALEN]; + u16 algo; + u32 rcmta0; + u16 rcmta1; + u64 hf; + struct b43_key *key; + + if (!b43_debug(dev, B43_DBG_KEYS)) + return; + + hf = b43_hf_read(dev); + b43dbg(dev->wl, "Hardware key memory dump: USEDEFKEYS=%u\n", + !!(hf & B43_HF_USEDEFKEYS)); + if (b43_new_kidx_api(dev)) { + pairwise_keys_start = B43_NR_GROUP_KEYS; + count = B43_NR_GROUP_KEYS + B43_NR_PAIRWISE_KEYS; + } else { + pairwise_keys_start = B43_NR_GROUP_KEYS * 2; + count = B43_NR_GROUP_KEYS * 2 + B43_NR_PAIRWISE_KEYS; + } + for (index = 0; index < count; index++) { + key = &(dev->key[index]); + printk(KERN_DEBUG "Key slot %02u: %s", + index, (key->keyconf == NULL) ? " " : "*"); + offset = dev->ktp + (index * B43_SEC_KEYSIZE); + for (i = 0; i < B43_SEC_KEYSIZE; i += 2) { + u16 tmp = b43_shm_read16(dev, B43_SHM_SHARED, offset + i); + printk("%02X%02X", (tmp & 0xFF), ((tmp >> 8) & 0xFF)); + } + + algo = b43_shm_read16(dev, B43_SHM_SHARED, + B43_SHM_SH_KEYIDXBLOCK + (index * 2)); + printk(" Algo: %04X/%02X", algo, key->algorithm); + + if (index >= pairwise_keys_start) { + if (key->algorithm == B43_SEC_ALGO_TKIP) { + printk(" TKIP: "); + offset = B43_SHM_SH_TKIPTSCTTAK + (index - 4) * (10 + 4); + for (i = 0; i < 14; i += 2) { + u16 tmp = b43_shm_read16(dev, B43_SHM_SHARED, offset + i); + printk("%02X%02X", (tmp & 0xFF), ((tmp >> 8) & 0xFF)); + } + } + rcmta0 = b43_shm_read32(dev, B43_SHM_RCMTA, + ((index - pairwise_keys_start) * 2) + 0); + rcmta1 = b43_shm_read16(dev, B43_SHM_RCMTA, + ((index - pairwise_keys_start) * 2) + 1); + *((__le32 *)(&mac[0])) = cpu_to_le32(rcmta0); + *((__le16 *)(&mac[4])) = cpu_to_le16(rcmta1); + printk(" MAC: %pM", mac); + } else + printk(" DEFAULT KEY"); + printk("\n"); + } +} + +void b43_power_saving_ctl_bits(struct b43_wldev *dev, unsigned int ps_flags) +{ + u32 macctl; + u16 ucstat; + bool hwps; + bool awake; + int i; + + B43_WARN_ON((ps_flags & B43_PS_ENABLED) && + (ps_flags & B43_PS_DISABLED)); + B43_WARN_ON((ps_flags & B43_PS_AWAKE) && (ps_flags & B43_PS_ASLEEP)); + + if (ps_flags & B43_PS_ENABLED) { + hwps = true; + } else if (ps_flags & B43_PS_DISABLED) { + hwps = false; + } else { + //TODO: If powersave is not off and FIXME is not set and we are not in adhoc + // and thus is not an AP and we are associated, set bit 25 + } + if (ps_flags & B43_PS_AWAKE) { + awake = true; + } else if (ps_flags & B43_PS_ASLEEP) { + awake = false; + } else { + //TODO: If the device is awake or this is an AP, or we are scanning, or FIXME, + // or we are associated, or FIXME, or the latest PS-Poll packet sent was + // successful, set bit26 + } + +/* FIXME: For now we force awake-on and hwps-off */ + hwps = false; + awake = true; + + macctl = b43_read32(dev, B43_MMIO_MACCTL); + if (hwps) + macctl |= B43_MACCTL_HWPS; + else + macctl &= ~B43_MACCTL_HWPS; + if (awake) + macctl |= B43_MACCTL_AWAKE; + else + macctl &= ~B43_MACCTL_AWAKE; + b43_write32(dev, B43_MMIO_MACCTL, macctl); + /* Commit write */ + b43_read32(dev, B43_MMIO_MACCTL); + if (awake && dev->dev->core_rev >= 5) { + /* Wait for the microcode to wake up. */ + for (i = 0; i < 100; i++) { + ucstat = b43_shm_read16(dev, B43_SHM_SHARED, + B43_SHM_SH_UCODESTAT); + if (ucstat != B43_SHM_SH_UCODESTAT_SLEEP) + break; + udelay(10); + } + } +} + +#ifdef CPTCFG_B43_BCMA +static void b43_bcma_phy_reset(struct b43_wldev *dev) +{ + u32 flags; + + /* Put PHY into reset */ + flags = bcma_aread32(dev->dev->bdev, BCMA_IOCTL); + flags |= B43_BCMA_IOCTL_PHY_RESET; + flags |= B43_BCMA_IOCTL_PHY_BW_20MHZ; /* Make 20 MHz def */ + bcma_awrite32(dev->dev->bdev, BCMA_IOCTL, flags); + udelay(2); + + /* Take PHY out of reset */ + flags = bcma_aread32(dev->dev->bdev, BCMA_IOCTL); + flags &= ~B43_BCMA_IOCTL_PHY_RESET; + flags |= BCMA_IOCTL_FGC; + bcma_awrite32(dev->dev->bdev, BCMA_IOCTL, flags); + udelay(1); + + /* Do not force clock anymore */ + flags = bcma_aread32(dev->dev->bdev, BCMA_IOCTL); + flags &= ~BCMA_IOCTL_FGC; + bcma_awrite32(dev->dev->bdev, BCMA_IOCTL, flags); + udelay(1); +} + +static void b43_bcma_wireless_core_reset(struct b43_wldev *dev, bool gmode) +{ + u32 req = B43_BCMA_CLKCTLST_80211_PLL_REQ | + B43_BCMA_CLKCTLST_PHY_PLL_REQ; + u32 status = B43_BCMA_CLKCTLST_80211_PLL_ST | + B43_BCMA_CLKCTLST_PHY_PLL_ST; + + b43_device_enable(dev, B43_BCMA_IOCTL_PHY_CLKEN); + bcma_core_set_clockmode(dev->dev->bdev, BCMA_CLKMODE_FAST); + b43_bcma_phy_reset(dev); + bcma_core_pll_ctl(dev->dev->bdev, req, status, true); +} +#endif + +static void b43_ssb_wireless_core_reset(struct b43_wldev *dev, bool gmode) +{ + struct ssb_device *sdev = dev->dev->sdev; + u32 tmslow; + u32 flags = 0; + + if (gmode) + flags |= B43_TMSLOW_GMODE; + flags |= B43_TMSLOW_PHYCLKEN; + flags |= B43_TMSLOW_PHYRESET; + if (dev->phy.type == B43_PHYTYPE_N) + flags |= B43_TMSLOW_PHY_BANDWIDTH_20MHZ; /* Make 20 MHz def */ + b43_device_enable(dev, flags); + msleep(2); /* Wait for the PLL to turn on. */ + + /* Now take the PHY out of Reset again */ + tmslow = ssb_read32(sdev, SSB_TMSLOW); + tmslow |= SSB_TMSLOW_FGC; + tmslow &= ~B43_TMSLOW_PHYRESET; + ssb_write32(sdev, SSB_TMSLOW, tmslow); + ssb_read32(sdev, SSB_TMSLOW); /* flush */ + msleep(1); + tmslow &= ~SSB_TMSLOW_FGC; + ssb_write32(sdev, SSB_TMSLOW, tmslow); + ssb_read32(sdev, SSB_TMSLOW); /* flush */ + msleep(1); +} + +void b43_wireless_core_reset(struct b43_wldev *dev, bool gmode) +{ + u32 macctl; + + switch (dev->dev->bus_type) { +#ifdef CPTCFG_B43_BCMA + case B43_BUS_BCMA: + b43_bcma_wireless_core_reset(dev, gmode); + break; +#endif +#ifdef CPTCFG_B43_SSB + case B43_BUS_SSB: + b43_ssb_wireless_core_reset(dev, gmode); + break; +#endif + } + + /* Turn Analog ON, but only if we already know the PHY-type. + * This protects against very early setup where we don't know the + * PHY-type, yet. wireless_core_reset will be called once again later, + * when we know the PHY-type. */ + if (dev->phy.ops) + dev->phy.ops->switch_analog(dev, 1); + + macctl = b43_read32(dev, B43_MMIO_MACCTL); + macctl &= ~B43_MACCTL_GMODE; + if (gmode) + macctl |= B43_MACCTL_GMODE; + macctl |= B43_MACCTL_IHR_ENABLED; + b43_write32(dev, B43_MMIO_MACCTL, macctl); +} + +static void handle_irq_transmit_status(struct b43_wldev *dev) +{ + u32 v0, v1; + u16 tmp; + struct b43_txstatus stat; + + while (1) { + v0 = b43_read32(dev, B43_MMIO_XMITSTAT_0); + if (!(v0 & 0x00000001)) + break; + v1 = b43_read32(dev, B43_MMIO_XMITSTAT_1); + + stat.cookie = (v0 >> 16); + stat.seq = (v1 & 0x0000FFFF); + stat.phy_stat = ((v1 & 0x00FF0000) >> 16); + tmp = (v0 & 0x0000FFFF); + stat.frame_count = ((tmp & 0xF000) >> 12); + stat.rts_count = ((tmp & 0x0F00) >> 8); + stat.supp_reason = ((tmp & 0x001C) >> 2); + stat.pm_indicated = !!(tmp & 0x0080); + stat.intermediate = !!(tmp & 0x0040); + stat.for_ampdu = !!(tmp & 0x0020); + stat.acked = !!(tmp & 0x0002); + + b43_handle_txstatus(dev, &stat); + } +} + +static void drain_txstatus_queue(struct b43_wldev *dev) +{ + u32 dummy; + + if (dev->dev->core_rev < 5) + return; + /* Read all entries from the microcode TXstatus FIFO + * and throw them away. + */ + while (1) { + dummy = b43_read32(dev, B43_MMIO_XMITSTAT_0); + if (!(dummy & 0x00000001)) + break; + dummy = b43_read32(dev, B43_MMIO_XMITSTAT_1); + } +} + +static u32 b43_jssi_read(struct b43_wldev *dev) +{ + u32 val = 0; + + val = b43_shm_read16(dev, B43_SHM_SHARED, B43_SHM_SH_JSSI1); + val <<= 16; + val |= b43_shm_read16(dev, B43_SHM_SHARED, B43_SHM_SH_JSSI0); + + return val; +} + +static void b43_jssi_write(struct b43_wldev *dev, u32 jssi) +{ + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_JSSI0, + (jssi & 0x0000FFFF)); + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_JSSI1, + (jssi & 0xFFFF0000) >> 16); +} + +static void b43_generate_noise_sample(struct b43_wldev *dev) +{ + b43_jssi_write(dev, 0x7F7F7F7F); + b43_write32(dev, B43_MMIO_MACCMD, + b43_read32(dev, B43_MMIO_MACCMD) | B43_MACCMD_BGNOISE); +} + +static void b43_calculate_link_quality(struct b43_wldev *dev) +{ + /* Top half of Link Quality calculation. */ + + if (dev->phy.type != B43_PHYTYPE_G) + return; + if (dev->noisecalc.calculation_running) + return; + dev->noisecalc.calculation_running = true; + dev->noisecalc.nr_samples = 0; + + b43_generate_noise_sample(dev); +} + +static void handle_irq_noise(struct b43_wldev *dev) +{ + struct b43_phy_g *phy = dev->phy.g; + u16 tmp; + u8 noise[4]; + u8 i, j; + s32 average; + + /* Bottom half of Link Quality calculation. */ + + if (dev->phy.type != B43_PHYTYPE_G) + return; + + /* Possible race condition: It might be possible that the user + * changed to a different channel in the meantime since we + * started the calculation. We ignore that fact, since it's + * not really that much of a problem. The background noise is + * an estimation only anyway. Slightly wrong results will get damped + * by the averaging of the 8 sample rounds. Additionally the + * value is shortlived. So it will be replaced by the next noise + * calculation round soon. */ + + B43_WARN_ON(!dev->noisecalc.calculation_running); + *((__le32 *)noise) = cpu_to_le32(b43_jssi_read(dev)); + if (noise[0] == 0x7F || noise[1] == 0x7F || + noise[2] == 0x7F || noise[3] == 0x7F) + goto generate_new; + + /* Get the noise samples. */ + B43_WARN_ON(dev->noisecalc.nr_samples >= 8); + i = dev->noisecalc.nr_samples; + noise[0] = clamp_val(noise[0], 0, ARRAY_SIZE(phy->nrssi_lt) - 1); + noise[1] = clamp_val(noise[1], 0, ARRAY_SIZE(phy->nrssi_lt) - 1); + noise[2] = clamp_val(noise[2], 0, ARRAY_SIZE(phy->nrssi_lt) - 1); + noise[3] = clamp_val(noise[3], 0, ARRAY_SIZE(phy->nrssi_lt) - 1); + dev->noisecalc.samples[i][0] = phy->nrssi_lt[noise[0]]; + dev->noisecalc.samples[i][1] = phy->nrssi_lt[noise[1]]; + dev->noisecalc.samples[i][2] = phy->nrssi_lt[noise[2]]; + dev->noisecalc.samples[i][3] = phy->nrssi_lt[noise[3]]; + dev->noisecalc.nr_samples++; + if (dev->noisecalc.nr_samples == 8) { + /* Calculate the Link Quality by the noise samples. */ + average = 0; + for (i = 0; i < 8; i++) { + for (j = 0; j < 4; j++) + average += dev->noisecalc.samples[i][j]; + } + average /= (8 * 4); + average *= 125; + average += 64; + average /= 128; + tmp = b43_shm_read16(dev, B43_SHM_SHARED, 0x40C); + tmp = (tmp / 128) & 0x1F; + if (tmp >= 8) + average += 2; + else + average -= 25; + if (tmp == 8) + average -= 72; + else + average -= 48; + + dev->stats.link_noise = average; + dev->noisecalc.calculation_running = false; + return; + } +generate_new: + b43_generate_noise_sample(dev); +} + +static void handle_irq_tbtt_indication(struct b43_wldev *dev) +{ + if (b43_is_mode(dev->wl, NL80211_IFTYPE_AP)) { + ///TODO: PS TBTT + } else { + if (1 /*FIXME: the last PSpoll frame was sent successfully */ ) + b43_power_saving_ctl_bits(dev, 0); + } + if (b43_is_mode(dev->wl, NL80211_IFTYPE_ADHOC)) + dev->dfq_valid = true; +} + +static void handle_irq_atim_end(struct b43_wldev *dev) +{ + if (dev->dfq_valid) { + b43_write32(dev, B43_MMIO_MACCMD, + b43_read32(dev, B43_MMIO_MACCMD) + | B43_MACCMD_DFQ_VALID); + dev->dfq_valid = false; + } +} + +static void handle_irq_pmq(struct b43_wldev *dev) +{ + u32 tmp; + + //TODO: AP mode. + + while (1) { + tmp = b43_read32(dev, B43_MMIO_PS_STATUS); + if (!(tmp & 0x00000008)) + break; + } + /* 16bit write is odd, but correct. */ + b43_write16(dev, B43_MMIO_PS_STATUS, 0x0002); +} + +static void b43_write_template_common(struct b43_wldev *dev, + const u8 *data, u16 size, + u16 ram_offset, + u16 shm_size_offset, u8 rate) +{ + u32 i, tmp; + struct b43_plcp_hdr4 plcp; + + plcp.data = 0; + b43_generate_plcp_hdr(&plcp, size + FCS_LEN, rate); + b43_ram_write(dev, ram_offset, le32_to_cpu(plcp.data)); + ram_offset += sizeof(u32); + /* The PLCP is 6 bytes long, but we only wrote 4 bytes, yet. + * So leave the first two bytes of the next write blank. + */ + tmp = (u32) (data[0]) << 16; + tmp |= (u32) (data[1]) << 24; + b43_ram_write(dev, ram_offset, tmp); + ram_offset += sizeof(u32); + for (i = 2; i < size; i += sizeof(u32)) { + tmp = (u32) (data[i + 0]); + if (i + 1 < size) + tmp |= (u32) (data[i + 1]) << 8; + if (i + 2 < size) + tmp |= (u32) (data[i + 2]) << 16; + if (i + 3 < size) + tmp |= (u32) (data[i + 3]) << 24; + b43_ram_write(dev, ram_offset + i - 2, tmp); + } + b43_shm_write16(dev, B43_SHM_SHARED, shm_size_offset, + size + sizeof(struct b43_plcp_hdr6)); +} + +/* Check if the use of the antenna that ieee80211 told us to + * use is possible. This will fall back to DEFAULT. + * "antenna_nr" is the antenna identifier we got from ieee80211. */ +u8 b43_ieee80211_antenna_sanitize(struct b43_wldev *dev, + u8 antenna_nr) +{ + u8 antenna_mask; + + if (antenna_nr == 0) { + /* Zero means "use default antenna". That's always OK. */ + return 0; + } + + /* Get the mask of available antennas. */ + if (dev->phy.gmode) + antenna_mask = dev->dev->bus_sprom->ant_available_bg; + else + antenna_mask = dev->dev->bus_sprom->ant_available_a; + + if (!(antenna_mask & (1 << (antenna_nr - 1)))) { + /* This antenna is not available. Fall back to default. */ + return 0; + } + + return antenna_nr; +} + +/* Convert a b43 antenna number value to the PHY TX control value. */ +static u16 b43_antenna_to_phyctl(int antenna) +{ + switch (antenna) { + case B43_ANTENNA0: + return B43_TXH_PHY_ANT0; + case B43_ANTENNA1: + return B43_TXH_PHY_ANT1; + case B43_ANTENNA2: + return B43_TXH_PHY_ANT2; + case B43_ANTENNA3: + return B43_TXH_PHY_ANT3; + case B43_ANTENNA_AUTO0: + case B43_ANTENNA_AUTO1: + return B43_TXH_PHY_ANT01AUTO; + } + B43_WARN_ON(1); + return 0; +} + +static void b43_write_beacon_template(struct b43_wldev *dev, + u16 ram_offset, + u16 shm_size_offset) +{ + unsigned int i, len, variable_len; + const struct ieee80211_mgmt *bcn; + const u8 *ie; + bool tim_found = false; + unsigned int rate; + u16 ctl; + int antenna; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(dev->wl->current_beacon); + + bcn = (const struct ieee80211_mgmt *)(dev->wl->current_beacon->data); + len = min((size_t) dev->wl->current_beacon->len, + 0x200 - sizeof(struct b43_plcp_hdr6)); + rate = ieee80211_get_tx_rate(dev->wl->hw, info)->hw_value; + + b43_write_template_common(dev, (const u8 *)bcn, + len, ram_offset, shm_size_offset, rate); + + /* Write the PHY TX control parameters. */ + antenna = B43_ANTENNA_DEFAULT; + antenna = b43_antenna_to_phyctl(antenna); + ctl = b43_shm_read16(dev, B43_SHM_SHARED, B43_SHM_SH_BEACPHYCTL); + /* We can't send beacons with short preamble. Would get PHY errors. */ + ctl &= ~B43_TXH_PHY_SHORTPRMBL; + ctl &= ~B43_TXH_PHY_ANT; + ctl &= ~B43_TXH_PHY_ENC; + ctl |= antenna; + if (b43_is_cck_rate(rate)) + ctl |= B43_TXH_PHY_ENC_CCK; + else + ctl |= B43_TXH_PHY_ENC_OFDM; + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_BEACPHYCTL, ctl); + + /* Find the position of the TIM and the DTIM_period value + * and write them to SHM. */ + ie = bcn->u.beacon.variable; + variable_len = len - offsetof(struct ieee80211_mgmt, u.beacon.variable); + for (i = 0; i < variable_len - 2; ) { + uint8_t ie_id, ie_len; + + ie_id = ie[i]; + ie_len = ie[i + 1]; + if (ie_id == 5) { + u16 tim_position; + u16 dtim_period; + /* This is the TIM Information Element */ + + /* Check whether the ie_len is in the beacon data range. */ + if (variable_len < ie_len + 2 + i) + break; + /* A valid TIM is at least 4 bytes long. */ + if (ie_len < 4) + break; + tim_found = true; + + tim_position = sizeof(struct b43_plcp_hdr6); + tim_position += offsetof(struct ieee80211_mgmt, u.beacon.variable); + tim_position += i; + + dtim_period = ie[i + 3]; + + b43_shm_write16(dev, B43_SHM_SHARED, + B43_SHM_SH_TIMBPOS, tim_position); + b43_shm_write16(dev, B43_SHM_SHARED, + B43_SHM_SH_DTIMPER, dtim_period); + break; + } + i += ie_len + 2; + } + if (!tim_found) { + /* + * If ucode wants to modify TIM do it behind the beacon, this + * will happen, for example, when doing mesh networking. + */ + b43_shm_write16(dev, B43_SHM_SHARED, + B43_SHM_SH_TIMBPOS, + len + sizeof(struct b43_plcp_hdr6)); + b43_shm_write16(dev, B43_SHM_SHARED, + B43_SHM_SH_DTIMPER, 0); + } + b43dbg(dev->wl, "Updated beacon template at 0x%x\n", ram_offset); +} + +static void b43_upload_beacon0(struct b43_wldev *dev) +{ + struct b43_wl *wl = dev->wl; + + if (wl->beacon0_uploaded) + return; + b43_write_beacon_template(dev, B43_SHM_SH_BT_BASE0, B43_SHM_SH_BTL0); + wl->beacon0_uploaded = true; +} + +static void b43_upload_beacon1(struct b43_wldev *dev) +{ + struct b43_wl *wl = dev->wl; + + if (wl->beacon1_uploaded) + return; + b43_write_beacon_template(dev, B43_SHM_SH_BT_BASE1, B43_SHM_SH_BTL1); + wl->beacon1_uploaded = true; +} + +static void handle_irq_beacon(struct b43_wldev *dev) +{ + struct b43_wl *wl = dev->wl; + u32 cmd, beacon0_valid, beacon1_valid; + + if (!b43_is_mode(wl, NL80211_IFTYPE_AP) && + !b43_is_mode(wl, NL80211_IFTYPE_MESH_POINT) && + !b43_is_mode(wl, NL80211_IFTYPE_ADHOC)) + return; + + /* This is the bottom half of the asynchronous beacon update. */ + + /* Ignore interrupt in the future. */ + dev->irq_mask &= ~B43_IRQ_BEACON; + + cmd = b43_read32(dev, B43_MMIO_MACCMD); + beacon0_valid = (cmd & B43_MACCMD_BEACON0_VALID); + beacon1_valid = (cmd & B43_MACCMD_BEACON1_VALID); + + /* Schedule interrupt manually, if busy. */ + if (beacon0_valid && beacon1_valid) { + b43_write32(dev, B43_MMIO_GEN_IRQ_REASON, B43_IRQ_BEACON); + dev->irq_mask |= B43_IRQ_BEACON; + return; + } + + if (unlikely(wl->beacon_templates_virgin)) { + /* We never uploaded a beacon before. + * Upload both templates now, but only mark one valid. */ + wl->beacon_templates_virgin = false; + b43_upload_beacon0(dev); + b43_upload_beacon1(dev); + cmd = b43_read32(dev, B43_MMIO_MACCMD); + cmd |= B43_MACCMD_BEACON0_VALID; + b43_write32(dev, B43_MMIO_MACCMD, cmd); + } else { + if (!beacon0_valid) { + b43_upload_beacon0(dev); + cmd = b43_read32(dev, B43_MMIO_MACCMD); + cmd |= B43_MACCMD_BEACON0_VALID; + b43_write32(dev, B43_MMIO_MACCMD, cmd); + } else if (!beacon1_valid) { + b43_upload_beacon1(dev); + cmd = b43_read32(dev, B43_MMIO_MACCMD); + cmd |= B43_MACCMD_BEACON1_VALID; + b43_write32(dev, B43_MMIO_MACCMD, cmd); + } + } +} + +static void b43_do_beacon_update_trigger_work(struct b43_wldev *dev) +{ + u32 old_irq_mask = dev->irq_mask; + + /* update beacon right away or defer to irq */ + handle_irq_beacon(dev); + if (old_irq_mask != dev->irq_mask) { + /* The handler updated the IRQ mask. */ + B43_WARN_ON(!dev->irq_mask); + if (b43_read32(dev, B43_MMIO_GEN_IRQ_MASK)) { + b43_write32(dev, B43_MMIO_GEN_IRQ_MASK, dev->irq_mask); + } else { + /* Device interrupts are currently disabled. That means + * we just ran the hardirq handler and scheduled the + * IRQ thread. The thread will write the IRQ mask when + * it finished, so there's nothing to do here. Writing + * the mask _here_ would incorrectly re-enable IRQs. */ + } + } +} + +static void b43_beacon_update_trigger_work(struct work_struct *work) +{ + struct b43_wl *wl = container_of(work, struct b43_wl, + beacon_update_trigger); + struct b43_wldev *dev; + + mutex_lock(&wl->mutex); + dev = wl->current_dev; + if (likely(dev && (b43_status(dev) >= B43_STAT_INITIALIZED))) { + if (b43_bus_host_is_sdio(dev->dev)) { + /* wl->mutex is enough. */ + b43_do_beacon_update_trigger_work(dev); + mmiowb(); + } else { + spin_lock_irq(&wl->hardirq_lock); + b43_do_beacon_update_trigger_work(dev); + mmiowb(); + spin_unlock_irq(&wl->hardirq_lock); + } + } + mutex_unlock(&wl->mutex); +} + +/* Asynchronously update the packet templates in template RAM. + * Locking: Requires wl->mutex to be locked. */ +static void b43_update_templates(struct b43_wl *wl) +{ + struct sk_buff *beacon; + + /* This is the top half of the ansynchronous beacon update. + * The bottom half is the beacon IRQ. + * Beacon update must be asynchronous to avoid sending an + * invalid beacon. This can happen for example, if the firmware + * transmits a beacon while we are updating it. */ + + /* We could modify the existing beacon and set the aid bit in + * the TIM field, but that would probably require resizing and + * moving of data within the beacon template. + * Simply request a new beacon and let mac80211 do the hard work. */ + beacon = ieee80211_beacon_get(wl->hw, wl->vif); + if (unlikely(!beacon)) + return; + + if (wl->current_beacon) + dev_kfree_skb_any(wl->current_beacon); + wl->current_beacon = beacon; + wl->beacon0_uploaded = false; + wl->beacon1_uploaded = false; + ieee80211_queue_work(wl->hw, &wl->beacon_update_trigger); +} + +static void b43_set_beacon_int(struct b43_wldev *dev, u16 beacon_int) +{ + b43_time_lock(dev); + if (dev->dev->core_rev >= 3) { + b43_write32(dev, B43_MMIO_TSF_CFP_REP, (beacon_int << 16)); + b43_write32(dev, B43_MMIO_TSF_CFP_START, (beacon_int << 10)); + } else { + b43_write16(dev, 0x606, (beacon_int >> 6)); + b43_write16(dev, 0x610, beacon_int); + } + b43_time_unlock(dev); + b43dbg(dev->wl, "Set beacon interval to %u\n", beacon_int); +} + +static void b43_handle_firmware_panic(struct b43_wldev *dev) +{ + u16 reason; + + /* Read the register that contains the reason code for the panic. */ + reason = b43_shm_read16(dev, B43_SHM_SCRATCH, B43_FWPANIC_REASON_REG); + b43err(dev->wl, "Whoopsy, firmware panic! Reason: %u\n", reason); + + switch (reason) { + default: + b43dbg(dev->wl, "The panic reason is unknown.\n"); + /* fallthrough */ + case B43_FWPANIC_DIE: + /* Do not restart the controller or firmware. + * The device is nonfunctional from now on. + * Restarting would result in this panic to trigger again, + * so we avoid that recursion. */ + break; + case B43_FWPANIC_RESTART: + b43_controller_restart(dev, "Microcode panic"); + break; + } +} + +static void handle_irq_ucode_debug(struct b43_wldev *dev) +{ + unsigned int i, cnt; + u16 reason, marker_id, marker_line; + __le16 *buf; + + /* The proprietary firmware doesn't have this IRQ. */ + if (!dev->fw.opensource) + return; + + /* Read the register that contains the reason code for this IRQ. */ + reason = b43_shm_read16(dev, B43_SHM_SCRATCH, B43_DEBUGIRQ_REASON_REG); + + switch (reason) { + case B43_DEBUGIRQ_PANIC: + b43_handle_firmware_panic(dev); + break; + case B43_DEBUGIRQ_DUMP_SHM: + if (!B43_DEBUG) + break; /* Only with driver debugging enabled. */ + buf = kmalloc(4096, GFP_ATOMIC); + if (!buf) { + b43dbg(dev->wl, "SHM-dump: Failed to allocate memory\n"); + goto out; + } + for (i = 0; i < 4096; i += 2) { + u16 tmp = b43_shm_read16(dev, B43_SHM_SHARED, i); + buf[i / 2] = cpu_to_le16(tmp); + } + b43info(dev->wl, "Shared memory dump:\n"); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, + 16, 2, buf, 4096, 1); + kfree(buf); + break; + case B43_DEBUGIRQ_DUMP_REGS: + if (!B43_DEBUG) + break; /* Only with driver debugging enabled. */ + b43info(dev->wl, "Microcode register dump:\n"); + for (i = 0, cnt = 0; i < 64; i++) { + u16 tmp = b43_shm_read16(dev, B43_SHM_SCRATCH, i); + if (cnt == 0) + printk(KERN_INFO); + printk("r%02u: 0x%04X ", i, tmp); + cnt++; + if (cnt == 6) { + printk("\n"); + cnt = 0; + } + } + printk("\n"); + break; + case B43_DEBUGIRQ_MARKER: + if (!B43_DEBUG) + break; /* Only with driver debugging enabled. */ + marker_id = b43_shm_read16(dev, B43_SHM_SCRATCH, + B43_MARKER_ID_REG); + marker_line = b43_shm_read16(dev, B43_SHM_SCRATCH, + B43_MARKER_LINE_REG); + b43info(dev->wl, "The firmware just executed the MARKER(%u) " + "at line number %u\n", + marker_id, marker_line); + break; + default: + b43dbg(dev->wl, "Debug-IRQ triggered for unknown reason: %u\n", + reason); + } +out: + /* Acknowledge the debug-IRQ, so the firmware can continue. */ + b43_shm_write16(dev, B43_SHM_SCRATCH, + B43_DEBUGIRQ_REASON_REG, B43_DEBUGIRQ_ACK); +} + +static void b43_do_interrupt_thread(struct b43_wldev *dev) +{ + u32 reason; + u32 dma_reason[ARRAY_SIZE(dev->dma_reason)]; + u32 merged_dma_reason = 0; + int i; + + if (unlikely(b43_status(dev) != B43_STAT_STARTED)) + return; + + reason = dev->irq_reason; + for (i = 0; i < ARRAY_SIZE(dma_reason); i++) { + dma_reason[i] = dev->dma_reason[i]; + merged_dma_reason |= dma_reason[i]; + } + + if (unlikely(reason & B43_IRQ_MAC_TXERR)) + b43err(dev->wl, "MAC transmission error\n"); + + if (unlikely(reason & B43_IRQ_PHY_TXERR)) { + b43err(dev->wl, "PHY transmission error\n"); + rmb(); + if (unlikely(atomic_dec_and_test(&dev->phy.txerr_cnt))) { + atomic_set(&dev->phy.txerr_cnt, + B43_PHY_TX_BADNESS_LIMIT); + b43err(dev->wl, "Too many PHY TX errors, " + "restarting the controller\n"); + b43_controller_restart(dev, "PHY TX errors"); + } + } + + if (unlikely(merged_dma_reason & (B43_DMAIRQ_FATALMASK))) { + b43err(dev->wl, + "Fatal DMA error: 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X\n", + dma_reason[0], dma_reason[1], + dma_reason[2], dma_reason[3], + dma_reason[4], dma_reason[5]); + b43err(dev->wl, "This device does not support DMA " + "on your system. It will now be switched to PIO.\n"); + /* Fall back to PIO transfers if we get fatal DMA errors! */ + dev->use_pio = true; + b43_controller_restart(dev, "DMA error"); + return; + } + + if (unlikely(reason & B43_IRQ_UCODE_DEBUG)) + handle_irq_ucode_debug(dev); + if (reason & B43_IRQ_TBTT_INDI) + handle_irq_tbtt_indication(dev); + if (reason & B43_IRQ_ATIM_END) + handle_irq_atim_end(dev); + if (reason & B43_IRQ_BEACON) + handle_irq_beacon(dev); + if (reason & B43_IRQ_PMQ) + handle_irq_pmq(dev); + if (reason & B43_IRQ_TXFIFO_FLUSH_OK) + ;/* TODO */ + if (reason & B43_IRQ_NOISESAMPLE_OK) + handle_irq_noise(dev); + + /* Check the DMA reason registers for received data. */ + if (dma_reason[0] & B43_DMAIRQ_RDESC_UFLOW) { + if (B43_DEBUG) + b43warn(dev->wl, "RX descriptor underrun\n"); + b43_dma_handle_rx_overflow(dev->dma.rx_ring); + } + if (dma_reason[0] & B43_DMAIRQ_RX_DONE) { + if (b43_using_pio_transfers(dev)) + b43_pio_rx(dev->pio.rx_queue); + else + b43_dma_rx(dev->dma.rx_ring); + } + B43_WARN_ON(dma_reason[1] & B43_DMAIRQ_RX_DONE); + B43_WARN_ON(dma_reason[2] & B43_DMAIRQ_RX_DONE); + B43_WARN_ON(dma_reason[3] & B43_DMAIRQ_RX_DONE); + B43_WARN_ON(dma_reason[4] & B43_DMAIRQ_RX_DONE); + B43_WARN_ON(dma_reason[5] & B43_DMAIRQ_RX_DONE); + + if (reason & B43_IRQ_TX_OK) + handle_irq_transmit_status(dev); + + /* Re-enable interrupts on the device by restoring the current interrupt mask. */ + b43_write32(dev, B43_MMIO_GEN_IRQ_MASK, dev->irq_mask); + +#if B43_DEBUG + if (b43_debug(dev, B43_DBG_VERBOSESTATS)) { + dev->irq_count++; + for (i = 0; i < ARRAY_SIZE(dev->irq_bit_count); i++) { + if (reason & (1 << i)) + dev->irq_bit_count[i]++; + } + } +#endif +} + +/* Interrupt thread handler. Handles device interrupts in thread context. */ +static irqreturn_t b43_interrupt_thread_handler(int irq, void *dev_id) +{ + struct b43_wldev *dev = dev_id; + + mutex_lock(&dev->wl->mutex); + b43_do_interrupt_thread(dev); + mmiowb(); + mutex_unlock(&dev->wl->mutex); + + return IRQ_HANDLED; +} + +static irqreturn_t b43_do_interrupt(struct b43_wldev *dev) +{ + u32 reason; + + /* This code runs under wl->hardirq_lock, but _only_ on non-SDIO busses. + * On SDIO, this runs under wl->mutex. */ + + reason = b43_read32(dev, B43_MMIO_GEN_IRQ_REASON); + if (reason == 0xffffffff) /* shared IRQ */ + return IRQ_NONE; + reason &= dev->irq_mask; + if (!reason) + return IRQ_NONE; + + dev->dma_reason[0] = b43_read32(dev, B43_MMIO_DMA0_REASON) + & 0x0001FC00; + dev->dma_reason[1] = b43_read32(dev, B43_MMIO_DMA1_REASON) + & 0x0000DC00; + dev->dma_reason[2] = b43_read32(dev, B43_MMIO_DMA2_REASON) + & 0x0000DC00; + dev->dma_reason[3] = b43_read32(dev, B43_MMIO_DMA3_REASON) + & 0x0001DC00; + dev->dma_reason[4] = b43_read32(dev, B43_MMIO_DMA4_REASON) + & 0x0000DC00; +/* Unused ring + dev->dma_reason[5] = b43_read32(dev, B43_MMIO_DMA5_REASON) + & 0x0000DC00; +*/ + + /* ACK the interrupt. */ + b43_write32(dev, B43_MMIO_GEN_IRQ_REASON, reason); + b43_write32(dev, B43_MMIO_DMA0_REASON, dev->dma_reason[0]); + b43_write32(dev, B43_MMIO_DMA1_REASON, dev->dma_reason[1]); + b43_write32(dev, B43_MMIO_DMA2_REASON, dev->dma_reason[2]); + b43_write32(dev, B43_MMIO_DMA3_REASON, dev->dma_reason[3]); + b43_write32(dev, B43_MMIO_DMA4_REASON, dev->dma_reason[4]); +/* Unused ring + b43_write32(dev, B43_MMIO_DMA5_REASON, dev->dma_reason[5]); +*/ + + /* Disable IRQs on the device. The IRQ thread handler will re-enable them. */ + b43_write32(dev, B43_MMIO_GEN_IRQ_MASK, 0); + /* Save the reason bitmasks for the IRQ thread handler. */ + dev->irq_reason = reason; + + return IRQ_WAKE_THREAD; +} + +/* Interrupt handler top-half. This runs with interrupts disabled. */ +static irqreturn_t b43_interrupt_handler(int irq, void *dev_id) +{ + struct b43_wldev *dev = dev_id; + irqreturn_t ret; + + if (unlikely(b43_status(dev) < B43_STAT_STARTED)) + return IRQ_NONE; + + spin_lock(&dev->wl->hardirq_lock); + ret = b43_do_interrupt(dev); + mmiowb(); + spin_unlock(&dev->wl->hardirq_lock); + + return ret; +} + +/* SDIO interrupt handler. This runs in process context. */ +static void b43_sdio_interrupt_handler(struct b43_wldev *dev) +{ + struct b43_wl *wl = dev->wl; + irqreturn_t ret; + + mutex_lock(&wl->mutex); + + ret = b43_do_interrupt(dev); + if (ret == IRQ_WAKE_THREAD) + b43_do_interrupt_thread(dev); + + mutex_unlock(&wl->mutex); +} + +void b43_do_release_fw(struct b43_firmware_file *fw) +{ + release_firmware(fw->data); + fw->data = NULL; + fw->filename = NULL; +} + +static void b43_release_firmware(struct b43_wldev *dev) +{ + b43_do_release_fw(&dev->fw.ucode); + b43_do_release_fw(&dev->fw.pcm); + b43_do_release_fw(&dev->fw.initvals); + b43_do_release_fw(&dev->fw.initvals_band); +} + +static void b43_print_fw_helptext(struct b43_wl *wl, bool error) +{ + const char text[] = + "You must go to " \ + "http://wireless.kernel.org/en/users/Drivers/b43#devicefirmware " \ + "and download the correct firmware for this driver version. " \ + "Please carefully read all instructions on this website.\n"; + + if (error) + b43err(wl, text); + else + b43warn(wl, text); +} + +static void b43_fw_cb(const struct firmware *firmware, void *context) +{ + struct b43_request_fw_context *ctx = context; + + ctx->blob = firmware; + complete(&ctx->fw_load_complete); +} + +int b43_do_request_fw(struct b43_request_fw_context *ctx, + const char *name, + struct b43_firmware_file *fw, bool async) +{ + struct b43_fw_header *hdr; + u32 size; + int err; + + if (!name) { + /* Don't fetch anything. Free possibly cached firmware. */ + /* FIXME: We should probably keep it anyway, to save some headache + * on suspend/resume with multiband devices. */ + b43_do_release_fw(fw); + return 0; + } + if (fw->filename) { + if ((fw->type == ctx->req_type) && + (strcmp(fw->filename, name) == 0)) + return 0; /* Already have this fw. */ + /* Free the cached firmware first. */ + /* FIXME: We should probably do this later after we successfully + * got the new fw. This could reduce headache with multiband devices. + * We could also redesign this to cache the firmware for all possible + * bands all the time. */ + b43_do_release_fw(fw); + } + + switch (ctx->req_type) { + case B43_FWTYPE_PROPRIETARY: + snprintf(ctx->fwname, sizeof(ctx->fwname), + "b43%s/%s.fw", + modparam_fwpostfix, name); + break; + case B43_FWTYPE_OPENSOURCE: + snprintf(ctx->fwname, sizeof(ctx->fwname), + "b43-open%s/%s.fw", + modparam_fwpostfix, name); + break; + default: + B43_WARN_ON(1); + return -ENOSYS; + } + if (async) { + /* do this part asynchronously */ + init_completion(&ctx->fw_load_complete); + err = request_firmware_nowait(THIS_MODULE, 1, ctx->fwname, + ctx->dev->dev->dev, GFP_KERNEL, + ctx, b43_fw_cb); + if (err < 0) { + pr_err("Unable to load firmware\n"); + return err; + } + /* stall here until fw ready */ + wait_for_completion(&ctx->fw_load_complete); + if (ctx->blob) + goto fw_ready; + /* On some ARM systems, the async request will fail, but the next sync + * request works. For this reason, we dall through here + */ + } + err = request_firmware(&ctx->blob, ctx->fwname, + ctx->dev->dev->dev); + if (err == -ENOENT) { + snprintf(ctx->errors[ctx->req_type], + sizeof(ctx->errors[ctx->req_type]), + "Firmware file \"%s\" not found\n", + ctx->fwname); + return err; + } else if (err) { + snprintf(ctx->errors[ctx->req_type], + sizeof(ctx->errors[ctx->req_type]), + "Firmware file \"%s\" request failed (err=%d)\n", + ctx->fwname, err); + return err; + } +fw_ready: + if (ctx->blob->size < sizeof(struct b43_fw_header)) + goto err_format; + hdr = (struct b43_fw_header *)(ctx->blob->data); + switch (hdr->type) { + case B43_FW_TYPE_UCODE: + case B43_FW_TYPE_PCM: + size = be32_to_cpu(hdr->size); + if (size != ctx->blob->size - sizeof(struct b43_fw_header)) + goto err_format; + /* fallthrough */ + case B43_FW_TYPE_IV: + if (hdr->ver != 1) + goto err_format; + break; + default: + goto err_format; + } + + fw->data = ctx->blob; + fw->filename = name; + fw->type = ctx->req_type; + + return 0; + +err_format: + snprintf(ctx->errors[ctx->req_type], + sizeof(ctx->errors[ctx->req_type]), + "Firmware file \"%s\" format error.\n", ctx->fwname); + release_firmware(ctx->blob); + + return -EPROTO; +} + +static int b43_try_request_fw(struct b43_request_fw_context *ctx) +{ + struct b43_wldev *dev = ctx->dev; + struct b43_firmware *fw = &ctx->dev->fw; + const u8 rev = ctx->dev->dev->core_rev; + const char *filename; + u32 tmshigh; + int err; + + /* Files for HT and LCN were found by trying one by one */ + + /* Get microcode */ + if ((rev >= 5) && (rev <= 10)) { + filename = "ucode5"; + } else if ((rev >= 11) && (rev <= 12)) { + filename = "ucode11"; + } else if (rev == 13) { + filename = "ucode13"; + } else if (rev == 14) { + filename = "ucode14"; + } else if (rev == 15) { + filename = "ucode15"; + } else { + switch (dev->phy.type) { + case B43_PHYTYPE_N: + if (rev >= 16) + filename = "ucode16_mimo"; + else + goto err_no_ucode; + break; + case B43_PHYTYPE_HT: + if (rev == 29) + filename = "ucode29_mimo"; + else + goto err_no_ucode; + break; + case B43_PHYTYPE_LCN: + if (rev == 24) + filename = "ucode24_mimo"; + else + goto err_no_ucode; + break; + default: + goto err_no_ucode; + } + } + err = b43_do_request_fw(ctx, filename, &fw->ucode, true); + if (err) + goto err_load; + + /* Get PCM code */ + if ((rev >= 5) && (rev <= 10)) + filename = "pcm5"; + else if (rev >= 11) + filename = NULL; + else + goto err_no_pcm; + fw->pcm_request_failed = false; + err = b43_do_request_fw(ctx, filename, &fw->pcm, false); + if (err == -ENOENT) { + /* We did not find a PCM file? Not fatal, but + * core rev <= 10 must do without hwcrypto then. */ + fw->pcm_request_failed = true; + } else if (err) + goto err_load; + + /* Get initvals */ + switch (dev->phy.type) { + case B43_PHYTYPE_A: + if ((rev >= 5) && (rev <= 10)) { + tmshigh = ssb_read32(dev->dev->sdev, SSB_TMSHIGH); + if (tmshigh & B43_TMSHIGH_HAVE_2GHZ_PHY) + filename = "a0g1initvals5"; + else + filename = "a0g0initvals5"; + } else + goto err_no_initvals; + break; + case B43_PHYTYPE_G: + if ((rev >= 5) && (rev <= 10)) + filename = "b0g0initvals5"; + else if (rev >= 13) + filename = "b0g0initvals13"; + else + goto err_no_initvals; + break; + case B43_PHYTYPE_N: + if (rev >= 16) + filename = "n0initvals16"; + else if ((rev >= 11) && (rev <= 12)) + filename = "n0initvals11"; + else + goto err_no_initvals; + break; + case B43_PHYTYPE_LP: + if (rev == 13) + filename = "lp0initvals13"; + else if (rev == 14) + filename = "lp0initvals14"; + else if (rev >= 15) + filename = "lp0initvals15"; + else + goto err_no_initvals; + break; + case B43_PHYTYPE_HT: + if (rev == 29) + filename = "ht0initvals29"; + else + goto err_no_initvals; + break; + case B43_PHYTYPE_LCN: + if (rev == 24) + filename = "lcn0initvals24"; + else + goto err_no_initvals; + break; + default: + goto err_no_initvals; + } + err = b43_do_request_fw(ctx, filename, &fw->initvals, false); + if (err) + goto err_load; + + /* Get bandswitch initvals */ + switch (dev->phy.type) { + case B43_PHYTYPE_A: + if ((rev >= 5) && (rev <= 10)) { + tmshigh = ssb_read32(dev->dev->sdev, SSB_TMSHIGH); + if (tmshigh & B43_TMSHIGH_HAVE_2GHZ_PHY) + filename = "a0g1bsinitvals5"; + else + filename = "a0g0bsinitvals5"; + } else if (rev >= 11) + filename = NULL; + else + goto err_no_initvals; + break; + case B43_PHYTYPE_G: + if ((rev >= 5) && (rev <= 10)) + filename = "b0g0bsinitvals5"; + else if (rev >= 11) + filename = NULL; + else + goto err_no_initvals; + break; + case B43_PHYTYPE_N: + if (rev >= 16) + filename = "n0bsinitvals16"; + else if ((rev >= 11) && (rev <= 12)) + filename = "n0bsinitvals11"; + else + goto err_no_initvals; + break; + case B43_PHYTYPE_LP: + if (rev == 13) + filename = "lp0bsinitvals13"; + else if (rev == 14) + filename = "lp0bsinitvals14"; + else if (rev >= 15) + filename = "lp0bsinitvals15"; + else + goto err_no_initvals; + break; + case B43_PHYTYPE_HT: + if (rev == 29) + filename = "ht0bsinitvals29"; + else + goto err_no_initvals; + break; + case B43_PHYTYPE_LCN: + if (rev == 24) + filename = "lcn0bsinitvals24"; + else + goto err_no_initvals; + break; + default: + goto err_no_initvals; + } + err = b43_do_request_fw(ctx, filename, &fw->initvals_band, false); + if (err) + goto err_load; + + fw->opensource = (ctx->req_type == B43_FWTYPE_OPENSOURCE); + + return 0; + +err_no_ucode: + err = ctx->fatal_failure = -EOPNOTSUPP; + b43err(dev->wl, "The driver does not know which firmware (ucode) " + "is required for your device (wl-core rev %u)\n", rev); + goto error; + +err_no_pcm: + err = ctx->fatal_failure = -EOPNOTSUPP; + b43err(dev->wl, "The driver does not know which firmware (PCM) " + "is required for your device (wl-core rev %u)\n", rev); + goto error; + +err_no_initvals: + err = ctx->fatal_failure = -EOPNOTSUPP; + b43err(dev->wl, "The driver does not know which firmware (initvals) " + "is required for your device (wl-core rev %u)\n", rev); + goto error; + +err_load: + /* We failed to load this firmware image. The error message + * already is in ctx->errors. Return and let our caller decide + * what to do. */ + goto error; + +error: + b43_release_firmware(dev); + return err; +} + +static int b43_one_core_attach(struct b43_bus_dev *dev, struct b43_wl *wl); +static void b43_one_core_detach(struct b43_bus_dev *dev); + +static void b43_request_firmware(struct work_struct *work) +{ + struct b43_wl *wl = container_of(work, + struct b43_wl, firmware_load); + struct b43_wldev *dev = wl->current_dev; + struct b43_request_fw_context *ctx; + unsigned int i; + int err; + const char *errmsg; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return; + ctx->dev = dev; + + ctx->req_type = B43_FWTYPE_PROPRIETARY; + err = b43_try_request_fw(ctx); + if (!err) + goto start_ieee80211; /* Successfully loaded it. */ + /* Was fw version known? */ + if (ctx->fatal_failure) + goto out; + + /* proprietary fw not found, try open source */ + ctx->req_type = B43_FWTYPE_OPENSOURCE; + err = b43_try_request_fw(ctx); + if (!err) + goto start_ieee80211; /* Successfully loaded it. */ + if(ctx->fatal_failure) + goto out; + + /* Could not find a usable firmware. Print the errors. */ + for (i = 0; i < B43_NR_FWTYPES; i++) { + errmsg = ctx->errors[i]; + if (strlen(errmsg)) + b43err(dev->wl, "%s", errmsg); + } + b43_print_fw_helptext(dev->wl, 1); + goto out; + +start_ieee80211: + wl->hw->queues = B43_QOS_QUEUE_NUM; + if (!modparam_qos || dev->fw.opensource) + wl->hw->queues = 1; + + err = ieee80211_register_hw(wl->hw); + if (err) + goto err_one_core_detach; + wl->hw_registred = true; + b43_leds_register(wl->current_dev); + goto out; + +err_one_core_detach: + b43_one_core_detach(dev->dev); + +out: + kfree(ctx); +} + +static int b43_upload_microcode(struct b43_wldev *dev) +{ + struct wiphy *wiphy = dev->wl->hw->wiphy; + const size_t hdr_len = sizeof(struct b43_fw_header); + const __be32 *data; + unsigned int i, len; + u16 fwrev, fwpatch, fwdate, fwtime; + u32 tmp, macctl; + int err = 0; + + /* Jump the microcode PSM to offset 0 */ + macctl = b43_read32(dev, B43_MMIO_MACCTL); + B43_WARN_ON(macctl & B43_MACCTL_PSM_RUN); + macctl |= B43_MACCTL_PSM_JMP0; + b43_write32(dev, B43_MMIO_MACCTL, macctl); + /* Zero out all microcode PSM registers and shared memory. */ + for (i = 0; i < 64; i++) + b43_shm_write16(dev, B43_SHM_SCRATCH, i, 0); + for (i = 0; i < 4096; i += 2) + b43_shm_write16(dev, B43_SHM_SHARED, i, 0); + + /* Upload Microcode. */ + data = (__be32 *) (dev->fw.ucode.data->data + hdr_len); + len = (dev->fw.ucode.data->size - hdr_len) / sizeof(__be32); + b43_shm_control_word(dev, B43_SHM_UCODE | B43_SHM_AUTOINC_W, 0x0000); + for (i = 0; i < len; i++) { + b43_write32(dev, B43_MMIO_SHM_DATA, be32_to_cpu(data[i])); + udelay(10); + } + + if (dev->fw.pcm.data) { + /* Upload PCM data. */ + data = (__be32 *) (dev->fw.pcm.data->data + hdr_len); + len = (dev->fw.pcm.data->size - hdr_len) / sizeof(__be32); + b43_shm_control_word(dev, B43_SHM_HW, 0x01EA); + b43_write32(dev, B43_MMIO_SHM_DATA, 0x00004000); + /* No need for autoinc bit in SHM_HW */ + b43_shm_control_word(dev, B43_SHM_HW, 0x01EB); + for (i = 0; i < len; i++) { + b43_write32(dev, B43_MMIO_SHM_DATA, be32_to_cpu(data[i])); + udelay(10); + } + } + + b43_write32(dev, B43_MMIO_GEN_IRQ_REASON, B43_IRQ_ALL); + + /* Start the microcode PSM */ + b43_maskset32(dev, B43_MMIO_MACCTL, ~B43_MACCTL_PSM_JMP0, + B43_MACCTL_PSM_RUN); + + /* Wait for the microcode to load and respond */ + i = 0; + while (1) { + tmp = b43_read32(dev, B43_MMIO_GEN_IRQ_REASON); + if (tmp == B43_IRQ_MAC_SUSPENDED) + break; + i++; + if (i >= 20) { + b43err(dev->wl, "Microcode not responding\n"); + b43_print_fw_helptext(dev->wl, 1); + err = -ENODEV; + goto error; + } + msleep(50); + } + b43_read32(dev, B43_MMIO_GEN_IRQ_REASON); /* dummy read */ + + /* Get and check the revisions. */ + fwrev = b43_shm_read16(dev, B43_SHM_SHARED, B43_SHM_SH_UCODEREV); + fwpatch = b43_shm_read16(dev, B43_SHM_SHARED, B43_SHM_SH_UCODEPATCH); + fwdate = b43_shm_read16(dev, B43_SHM_SHARED, B43_SHM_SH_UCODEDATE); + fwtime = b43_shm_read16(dev, B43_SHM_SHARED, B43_SHM_SH_UCODETIME); + + if (fwrev <= 0x128) { + b43err(dev->wl, "YOUR FIRMWARE IS TOO OLD. Firmware from " + "binary drivers older than version 4.x is unsupported. " + "You must upgrade your firmware files.\n"); + b43_print_fw_helptext(dev->wl, 1); + err = -EOPNOTSUPP; + goto error; + } + dev->fw.rev = fwrev; + dev->fw.patch = fwpatch; + if (dev->fw.rev >= 598) + dev->fw.hdr_format = B43_FW_HDR_598; + else if (dev->fw.rev >= 410) + dev->fw.hdr_format = B43_FW_HDR_410; + else + dev->fw.hdr_format = B43_FW_HDR_351; + WARN_ON(dev->fw.opensource != (fwdate == 0xFFFF)); + + dev->qos_enabled = dev->wl->hw->queues > 1; + /* Default to firmware/hardware crypto acceleration. */ + dev->hwcrypto_enabled = true; + + if (dev->fw.opensource) { + u16 fwcapa; + + /* Patchlevel info is encoded in the "time" field. */ + dev->fw.patch = fwtime; + b43info(dev->wl, "Loading OpenSource firmware version %u.%u\n", + dev->fw.rev, dev->fw.patch); + + fwcapa = b43_fwcapa_read(dev); + if (!(fwcapa & B43_FWCAPA_HWCRYPTO) || dev->fw.pcm_request_failed) { + b43info(dev->wl, "Hardware crypto acceleration not supported by firmware\n"); + /* Disable hardware crypto and fall back to software crypto. */ + dev->hwcrypto_enabled = false; + } + /* adding QoS support should use an offline discovery mechanism */ + WARN(fwcapa & B43_FWCAPA_QOS, "QoS in OpenFW not supported\n"); + } else { + b43info(dev->wl, "Loading firmware version %u.%u " + "(20%.2i-%.2i-%.2i %.2i:%.2i:%.2i)\n", + fwrev, fwpatch, + (fwdate >> 12) & 0xF, (fwdate >> 8) & 0xF, fwdate & 0xFF, + (fwtime >> 11) & 0x1F, (fwtime >> 5) & 0x3F, fwtime & 0x1F); + if (dev->fw.pcm_request_failed) { + b43warn(dev->wl, "No \"pcm5.fw\" firmware file found. " + "Hardware accelerated cryptography is disabled.\n"); + b43_print_fw_helptext(dev->wl, 0); + } + } + + snprintf(wiphy->fw_version, sizeof(wiphy->fw_version), "%u.%u", + dev->fw.rev, dev->fw.patch); + wiphy->hw_version = dev->dev->core_id; + + if (dev->fw.hdr_format == B43_FW_HDR_351) { + /* We're over the deadline, but we keep support for old fw + * until it turns out to be in major conflict with something new. */ + b43warn(dev->wl, "You are using an old firmware image. " + "Support for old firmware will be removed soon " + "(official deadline was July 2008).\n"); + b43_print_fw_helptext(dev->wl, 0); + } + + return 0; + +error: + /* Stop the microcode PSM. */ + b43_maskset32(dev, B43_MMIO_MACCTL, ~B43_MACCTL_PSM_RUN, + B43_MACCTL_PSM_JMP0); + + return err; +} + +static int b43_write_initvals(struct b43_wldev *dev, + const struct b43_iv *ivals, + size_t count, + size_t array_size) +{ + const struct b43_iv *iv; + u16 offset; + size_t i; + bool bit32; + + BUILD_BUG_ON(sizeof(struct b43_iv) != 6); + iv = ivals; + for (i = 0; i < count; i++) { + if (array_size < sizeof(iv->offset_size)) + goto err_format; + array_size -= sizeof(iv->offset_size); + offset = be16_to_cpu(iv->offset_size); + bit32 = !!(offset & B43_IV_32BIT); + offset &= B43_IV_OFFSET_MASK; + if (offset >= 0x1000) + goto err_format; + if (bit32) { + u32 value; + + if (array_size < sizeof(iv->data.d32)) + goto err_format; + array_size -= sizeof(iv->data.d32); + + value = get_unaligned_be32(&iv->data.d32); + b43_write32(dev, offset, value); + + iv = (const struct b43_iv *)((const uint8_t *)iv + + sizeof(__be16) + + sizeof(__be32)); + } else { + u16 value; + + if (array_size < sizeof(iv->data.d16)) + goto err_format; + array_size -= sizeof(iv->data.d16); + + value = be16_to_cpu(iv->data.d16); + b43_write16(dev, offset, value); + + iv = (const struct b43_iv *)((const uint8_t *)iv + + sizeof(__be16) + + sizeof(__be16)); + } + } + if (array_size) + goto err_format; + + return 0; + +err_format: + b43err(dev->wl, "Initial Values Firmware file-format error.\n"); + b43_print_fw_helptext(dev->wl, 1); + + return -EPROTO; +} + +static int b43_upload_initvals(struct b43_wldev *dev) +{ + const size_t hdr_len = sizeof(struct b43_fw_header); + const struct b43_fw_header *hdr; + struct b43_firmware *fw = &dev->fw; + const struct b43_iv *ivals; + size_t count; + int err; + + hdr = (const struct b43_fw_header *)(fw->initvals.data->data); + ivals = (const struct b43_iv *)(fw->initvals.data->data + hdr_len); + count = be32_to_cpu(hdr->size); + err = b43_write_initvals(dev, ivals, count, + fw->initvals.data->size - hdr_len); + if (err) + goto out; + if (fw->initvals_band.data) { + hdr = (const struct b43_fw_header *)(fw->initvals_band.data->data); + ivals = (const struct b43_iv *)(fw->initvals_band.data->data + hdr_len); + count = be32_to_cpu(hdr->size); + err = b43_write_initvals(dev, ivals, count, + fw->initvals_band.data->size - hdr_len); + if (err) + goto out; + } +out: + + return err; +} + +/* Initialize the GPIOs + * http://bcm-specs.sipsolutions.net/GPIO + */ +static struct ssb_device *b43_ssb_gpio_dev(struct b43_wldev *dev) +{ + struct ssb_bus *bus = dev->dev->sdev->bus; + +#ifdef CPTCFG_SSB_DRIVER_PCICORE + return (bus->chipco.dev ? bus->chipco.dev : bus->pcicore.dev); +#else + return bus->chipco.dev; +#endif +} + +static int b43_gpio_init(struct b43_wldev *dev) +{ + struct ssb_device *gpiodev; + u32 mask, set; + + b43_maskset32(dev, B43_MMIO_MACCTL, ~B43_MACCTL_GPOUTSMSK, 0); + b43_maskset16(dev, B43_MMIO_GPIO_MASK, ~0, 0xF); + + mask = 0x0000001F; + set = 0x0000000F; + if (dev->dev->chip_id == 0x4301) { + mask |= 0x0060; + set |= 0x0060; + } else if (dev->dev->chip_id == 0x5354) { + /* Don't allow overtaking buttons GPIOs */ + set &= 0x2; /* 0x2 is LED GPIO on BCM5354 */ + } + + if (0 /* FIXME: conditional unknown */ ) { + b43_write16(dev, B43_MMIO_GPIO_MASK, + b43_read16(dev, B43_MMIO_GPIO_MASK) + | 0x0100); + /* BT Coexistance Input */ + mask |= 0x0080; + set |= 0x0080; + /* BT Coexistance Out */ + mask |= 0x0100; + set |= 0x0100; + } + if (dev->dev->bus_sprom->boardflags_lo & B43_BFL_PACTRL) { + /* PA is controlled by gpio 9, let ucode handle it */ + b43_write16(dev, B43_MMIO_GPIO_MASK, + b43_read16(dev, B43_MMIO_GPIO_MASK) + | 0x0200); + mask |= 0x0200; + set |= 0x0200; + } + + switch (dev->dev->bus_type) { +#ifdef CPTCFG_B43_BCMA + case B43_BUS_BCMA: + bcma_chipco_gpio_control(&dev->dev->bdev->bus->drv_cc, mask, set); + break; +#endif +#ifdef CPTCFG_B43_SSB + case B43_BUS_SSB: + gpiodev = b43_ssb_gpio_dev(dev); + if (gpiodev) + ssb_write32(gpiodev, B43_GPIO_CONTROL, + (ssb_read32(gpiodev, B43_GPIO_CONTROL) + & ~mask) | set); + break; +#endif + } + + return 0; +} + +/* Turn off all GPIO stuff. Call this on module unload, for example. */ +static void b43_gpio_cleanup(struct b43_wldev *dev) +{ + struct ssb_device *gpiodev; + + switch (dev->dev->bus_type) { +#ifdef CPTCFG_B43_BCMA + case B43_BUS_BCMA: + bcma_chipco_gpio_control(&dev->dev->bdev->bus->drv_cc, ~0, 0); + break; +#endif +#ifdef CPTCFG_B43_SSB + case B43_BUS_SSB: + gpiodev = b43_ssb_gpio_dev(dev); + if (gpiodev) + ssb_write32(gpiodev, B43_GPIO_CONTROL, 0); + break; +#endif + } +} + +/* http://bcm-specs.sipsolutions.net/EnableMac */ +void b43_mac_enable(struct b43_wldev *dev) +{ + if (b43_debug(dev, B43_DBG_FIRMWARE)) { + u16 fwstate; + + fwstate = b43_shm_read16(dev, B43_SHM_SHARED, + B43_SHM_SH_UCODESTAT); + if ((fwstate != B43_SHM_SH_UCODESTAT_SUSP) && + (fwstate != B43_SHM_SH_UCODESTAT_SLEEP)) { + b43err(dev->wl, "b43_mac_enable(): The firmware " + "should be suspended, but current state is %u\n", + fwstate); + } + } + + dev->mac_suspended--; + B43_WARN_ON(dev->mac_suspended < 0); + if (dev->mac_suspended == 0) { + b43_maskset32(dev, B43_MMIO_MACCTL, ~0, B43_MACCTL_ENABLED); + b43_write32(dev, B43_MMIO_GEN_IRQ_REASON, + B43_IRQ_MAC_SUSPENDED); + /* Commit writes */ + b43_read32(dev, B43_MMIO_MACCTL); + b43_read32(dev, B43_MMIO_GEN_IRQ_REASON); + b43_power_saving_ctl_bits(dev, 0); + } +} + +/* http://bcm-specs.sipsolutions.net/SuspendMAC */ +void b43_mac_suspend(struct b43_wldev *dev) +{ + int i; + u32 tmp; + + might_sleep(); + B43_WARN_ON(dev->mac_suspended < 0); + + if (dev->mac_suspended == 0) { + b43_power_saving_ctl_bits(dev, B43_PS_AWAKE); + b43_maskset32(dev, B43_MMIO_MACCTL, ~B43_MACCTL_ENABLED, 0); + /* force pci to flush the write */ + b43_read32(dev, B43_MMIO_MACCTL); + for (i = 35; i; i--) { + tmp = b43_read32(dev, B43_MMIO_GEN_IRQ_REASON); + if (tmp & B43_IRQ_MAC_SUSPENDED) + goto out; + udelay(10); + } + /* Hm, it seems this will take some time. Use msleep(). */ + for (i = 40; i; i--) { + tmp = b43_read32(dev, B43_MMIO_GEN_IRQ_REASON); + if (tmp & B43_IRQ_MAC_SUSPENDED) + goto out; + msleep(1); + } + b43err(dev->wl, "MAC suspend failed\n"); + } +out: + dev->mac_suspended++; +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/MacPhyClkSet */ +void b43_mac_phy_clock_set(struct b43_wldev *dev, bool on) +{ + u32 tmp; + + switch (dev->dev->bus_type) { +#ifdef CPTCFG_B43_BCMA + case B43_BUS_BCMA: + tmp = bcma_aread32(dev->dev->bdev, BCMA_IOCTL); + if (on) + tmp |= B43_BCMA_IOCTL_MACPHYCLKEN; + else + tmp &= ~B43_BCMA_IOCTL_MACPHYCLKEN; + bcma_awrite32(dev->dev->bdev, BCMA_IOCTL, tmp); + break; +#endif +#ifdef CPTCFG_B43_SSB + case B43_BUS_SSB: + tmp = ssb_read32(dev->dev->sdev, SSB_TMSLOW); + if (on) + tmp |= B43_TMSLOW_MACPHYCLKEN; + else + tmp &= ~B43_TMSLOW_MACPHYCLKEN; + ssb_write32(dev->dev->sdev, SSB_TMSLOW, tmp); + break; +#endif + } +} + +static void b43_adjust_opmode(struct b43_wldev *dev) +{ + struct b43_wl *wl = dev->wl; + u32 ctl; + u16 cfp_pretbtt; + + ctl = b43_read32(dev, B43_MMIO_MACCTL); + /* Reset status to STA infrastructure mode. */ + ctl &= ~B43_MACCTL_AP; + ctl &= ~B43_MACCTL_KEEP_CTL; + ctl &= ~B43_MACCTL_KEEP_BADPLCP; + ctl &= ~B43_MACCTL_KEEP_BAD; + ctl &= ~B43_MACCTL_PROMISC; + ctl &= ~B43_MACCTL_BEACPROMISC; + ctl |= B43_MACCTL_INFRA; + + if (b43_is_mode(wl, NL80211_IFTYPE_AP) || + b43_is_mode(wl, NL80211_IFTYPE_MESH_POINT)) + ctl |= B43_MACCTL_AP; + else if (b43_is_mode(wl, NL80211_IFTYPE_ADHOC)) + ctl &= ~B43_MACCTL_INFRA; + + if (wl->filter_flags & FIF_CONTROL) + ctl |= B43_MACCTL_KEEP_CTL; + if (wl->filter_flags & FIF_FCSFAIL) + ctl |= B43_MACCTL_KEEP_BAD; + if (wl->filter_flags & FIF_PLCPFAIL) + ctl |= B43_MACCTL_KEEP_BADPLCP; + if (wl->filter_flags & FIF_PROMISC_IN_BSS) + ctl |= B43_MACCTL_PROMISC; + if (wl->filter_flags & FIF_BCN_PRBRESP_PROMISC) + ctl |= B43_MACCTL_BEACPROMISC; + + /* Workaround: On old hardware the HW-MAC-address-filter + * doesn't work properly, so always run promisc in filter + * it in software. */ + if (dev->dev->core_rev <= 4) + ctl |= B43_MACCTL_PROMISC; + + b43_write32(dev, B43_MMIO_MACCTL, ctl); + + cfp_pretbtt = 2; + if ((ctl & B43_MACCTL_INFRA) && !(ctl & B43_MACCTL_AP)) { + if (dev->dev->chip_id == 0x4306 && + dev->dev->chip_rev == 3) + cfp_pretbtt = 100; + else + cfp_pretbtt = 50; + } + b43_write16(dev, 0x612, cfp_pretbtt); + + /* FIXME: We don't currently implement the PMQ mechanism, + * so always disable it. If we want to implement PMQ, + * we need to enable it here (clear DISCPMQ) in AP mode. + */ + if (0 /* ctl & B43_MACCTL_AP */) + b43_maskset32(dev, B43_MMIO_MACCTL, ~B43_MACCTL_DISCPMQ, 0); + else + b43_maskset32(dev, B43_MMIO_MACCTL, ~0, B43_MACCTL_DISCPMQ); +} + +static void b43_rate_memory_write(struct b43_wldev *dev, u16 rate, int is_ofdm) +{ + u16 offset; + + if (is_ofdm) { + offset = 0x480; + offset += (b43_plcp_get_ratecode_ofdm(rate) & 0x000F) * 2; + } else { + offset = 0x4C0; + offset += (b43_plcp_get_ratecode_cck(rate) & 0x000F) * 2; + } + b43_shm_write16(dev, B43_SHM_SHARED, offset + 0x20, + b43_shm_read16(dev, B43_SHM_SHARED, offset)); +} + +static void b43_rate_memory_init(struct b43_wldev *dev) +{ + switch (dev->phy.type) { + case B43_PHYTYPE_A: + case B43_PHYTYPE_G: + case B43_PHYTYPE_N: + case B43_PHYTYPE_LP: + case B43_PHYTYPE_HT: + case B43_PHYTYPE_LCN: + b43_rate_memory_write(dev, B43_OFDM_RATE_6MB, 1); + b43_rate_memory_write(dev, B43_OFDM_RATE_12MB, 1); + b43_rate_memory_write(dev, B43_OFDM_RATE_18MB, 1); + b43_rate_memory_write(dev, B43_OFDM_RATE_24MB, 1); + b43_rate_memory_write(dev, B43_OFDM_RATE_36MB, 1); + b43_rate_memory_write(dev, B43_OFDM_RATE_48MB, 1); + b43_rate_memory_write(dev, B43_OFDM_RATE_54MB, 1); + if (dev->phy.type == B43_PHYTYPE_A) + break; + /* fallthrough */ + case B43_PHYTYPE_B: + b43_rate_memory_write(dev, B43_CCK_RATE_1MB, 0); + b43_rate_memory_write(dev, B43_CCK_RATE_2MB, 0); + b43_rate_memory_write(dev, B43_CCK_RATE_5MB, 0); + b43_rate_memory_write(dev, B43_CCK_RATE_11MB, 0); + break; + default: + B43_WARN_ON(1); + } +} + +/* Set the default values for the PHY TX Control Words. */ +static void b43_set_phytxctl_defaults(struct b43_wldev *dev) +{ + u16 ctl = 0; + + ctl |= B43_TXH_PHY_ENC_CCK; + ctl |= B43_TXH_PHY_ANT01AUTO; + ctl |= B43_TXH_PHY_TXPWR; + + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_BEACPHYCTL, ctl); + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_ACKCTSPHYCTL, ctl); + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_PRPHYCTL, ctl); +} + +/* Set the TX-Antenna for management frames sent by firmware. */ +static void b43_mgmtframe_txantenna(struct b43_wldev *dev, int antenna) +{ + u16 ant; + u16 tmp; + + ant = b43_antenna_to_phyctl(antenna); + + /* For ACK/CTS */ + tmp = b43_shm_read16(dev, B43_SHM_SHARED, B43_SHM_SH_ACKCTSPHYCTL); + tmp = (tmp & ~B43_TXH_PHY_ANT) | ant; + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_ACKCTSPHYCTL, tmp); + /* For Probe Resposes */ + tmp = b43_shm_read16(dev, B43_SHM_SHARED, B43_SHM_SH_PRPHYCTL); + tmp = (tmp & ~B43_TXH_PHY_ANT) | ant; + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_PRPHYCTL, tmp); +} + +/* This is the opposite of b43_chip_init() */ +static void b43_chip_exit(struct b43_wldev *dev) +{ + b43_phy_exit(dev); + b43_gpio_cleanup(dev); + /* firmware is released later */ +} + +/* Initialize the chip + * http://bcm-specs.sipsolutions.net/ChipInit + */ +static int b43_chip_init(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + int err; + u32 macctl; + u16 value16; + + /* Initialize the MAC control */ + macctl = B43_MACCTL_IHR_ENABLED | B43_MACCTL_SHM_ENABLED; + if (dev->phy.gmode) + macctl |= B43_MACCTL_GMODE; + macctl |= B43_MACCTL_INFRA; + b43_write32(dev, B43_MMIO_MACCTL, macctl); + + err = b43_upload_microcode(dev); + if (err) + goto out; /* firmware is released later */ + + err = b43_gpio_init(dev); + if (err) + goto out; /* firmware is released later */ + + err = b43_upload_initvals(dev); + if (err) + goto err_gpio_clean; + + /* Turn the Analog on and initialize the PHY. */ + phy->ops->switch_analog(dev, 1); + err = b43_phy_init(dev); + if (err) + goto err_gpio_clean; + + /* Disable Interference Mitigation. */ + if (phy->ops->interf_mitigation) + phy->ops->interf_mitigation(dev, B43_INTERFMODE_NONE); + + /* Select the antennae */ + if (phy->ops->set_rx_antenna) + phy->ops->set_rx_antenna(dev, B43_ANTENNA_DEFAULT); + b43_mgmtframe_txantenna(dev, B43_ANTENNA_DEFAULT); + + if (phy->type == B43_PHYTYPE_B) { + value16 = b43_read16(dev, 0x005E); + value16 |= 0x0004; + b43_write16(dev, 0x005E, value16); + } + b43_write32(dev, 0x0100, 0x01000000); + if (dev->dev->core_rev < 5) + b43_write32(dev, 0x010C, 0x01000000); + + b43_maskset32(dev, B43_MMIO_MACCTL, ~B43_MACCTL_INFRA, 0); + b43_maskset32(dev, B43_MMIO_MACCTL, ~0, B43_MACCTL_INFRA); + + /* Probe Response Timeout value */ + /* FIXME: Default to 0, has to be set by ioctl probably... :-/ */ + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_PRMAXTIME, 0); + + /* Initially set the wireless operation mode. */ + b43_adjust_opmode(dev); + + if (dev->dev->core_rev < 3) { + b43_write16(dev, 0x060E, 0x0000); + b43_write16(dev, 0x0610, 0x8000); + b43_write16(dev, 0x0604, 0x0000); + b43_write16(dev, 0x0606, 0x0200); + } else { + b43_write32(dev, 0x0188, 0x80000000); + b43_write32(dev, 0x018C, 0x02000000); + } + b43_write32(dev, B43_MMIO_GEN_IRQ_REASON, 0x00004000); + b43_write32(dev, B43_MMIO_DMA0_IRQ_MASK, 0x0001FC00); + b43_write32(dev, B43_MMIO_DMA1_IRQ_MASK, 0x0000DC00); + b43_write32(dev, B43_MMIO_DMA2_IRQ_MASK, 0x0000DC00); + b43_write32(dev, B43_MMIO_DMA3_IRQ_MASK, 0x0001DC00); + b43_write32(dev, B43_MMIO_DMA4_IRQ_MASK, 0x0000DC00); + b43_write32(dev, B43_MMIO_DMA5_IRQ_MASK, 0x0000DC00); + + b43_mac_phy_clock_set(dev, true); + + switch (dev->dev->bus_type) { +#ifdef CPTCFG_B43_BCMA + case B43_BUS_BCMA: + /* FIXME: 0xE74 is quite common, but should be read from CC */ + b43_write16(dev, B43_MMIO_POWERUP_DELAY, 0xE74); + break; +#endif +#ifdef CPTCFG_B43_SSB + case B43_BUS_SSB: + b43_write16(dev, B43_MMIO_POWERUP_DELAY, + dev->dev->sdev->bus->chipco.fast_pwrup_delay); + break; +#endif + } + + err = 0; + b43dbg(dev->wl, "Chip initialized\n"); +out: + return err; + +err_gpio_clean: + b43_gpio_cleanup(dev); + return err; +} + +static void b43_periodic_every60sec(struct b43_wldev *dev) +{ + const struct b43_phy_operations *ops = dev->phy.ops; + + if (ops->pwork_60sec) + ops->pwork_60sec(dev); + + /* Force check the TX power emission now. */ + b43_phy_txpower_check(dev, B43_TXPWR_IGNORE_TIME); +} + +static void b43_periodic_every30sec(struct b43_wldev *dev) +{ + /* Update device statistics. */ + b43_calculate_link_quality(dev); +} + +static void b43_periodic_every15sec(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + u16 wdr; + + if (dev->fw.opensource) { + /* Check if the firmware is still alive. + * It will reset the watchdog counter to 0 in its idle loop. */ + wdr = b43_shm_read16(dev, B43_SHM_SCRATCH, B43_WATCHDOG_REG); + if (unlikely(wdr)) { + b43err(dev->wl, "Firmware watchdog: The firmware died!\n"); + b43_controller_restart(dev, "Firmware watchdog"); + return; + } else { + b43_shm_write16(dev, B43_SHM_SCRATCH, + B43_WATCHDOG_REG, 1); + } + } + + if (phy->ops->pwork_15sec) + phy->ops->pwork_15sec(dev); + + atomic_set(&phy->txerr_cnt, B43_PHY_TX_BADNESS_LIMIT); + wmb(); + +#if B43_DEBUG + if (b43_debug(dev, B43_DBG_VERBOSESTATS)) { + unsigned int i; + + b43dbg(dev->wl, "Stats: %7u IRQs/sec, %7u TX/sec, %7u RX/sec\n", + dev->irq_count / 15, + dev->tx_count / 15, + dev->rx_count / 15); + dev->irq_count = 0; + dev->tx_count = 0; + dev->rx_count = 0; + for (i = 0; i < ARRAY_SIZE(dev->irq_bit_count); i++) { + if (dev->irq_bit_count[i]) { + b43dbg(dev->wl, "Stats: %7u IRQ-%02u/sec (0x%08X)\n", + dev->irq_bit_count[i] / 15, i, (1 << i)); + dev->irq_bit_count[i] = 0; + } + } + } +#endif +} + +static void do_periodic_work(struct b43_wldev *dev) +{ + unsigned int state; + + state = dev->periodic_state; + if (state % 4 == 0) + b43_periodic_every60sec(dev); + if (state % 2 == 0) + b43_periodic_every30sec(dev); + b43_periodic_every15sec(dev); +} + +/* Periodic work locking policy: + * The whole periodic work handler is protected by + * wl->mutex. If another lock is needed somewhere in the + * pwork callchain, it's acquired in-place, where it's needed. + */ +static void b43_periodic_work_handler(struct work_struct *work) +{ + struct b43_wldev *dev = container_of(work, struct b43_wldev, + periodic_work.work); + struct b43_wl *wl = dev->wl; + unsigned long delay; + + mutex_lock(&wl->mutex); + + if (unlikely(b43_status(dev) != B43_STAT_STARTED)) + goto out; + if (b43_debug(dev, B43_DBG_PWORK_STOP)) + goto out_requeue; + + do_periodic_work(dev); + + dev->periodic_state++; +out_requeue: + if (b43_debug(dev, B43_DBG_PWORK_FAST)) + delay = msecs_to_jiffies(50); + else + delay = round_jiffies_relative(HZ * 15); + ieee80211_queue_delayed_work(wl->hw, &dev->periodic_work, delay); +out: + mutex_unlock(&wl->mutex); +} + +static void b43_periodic_tasks_setup(struct b43_wldev *dev) +{ + struct delayed_work *work = &dev->periodic_work; + + dev->periodic_state = 0; + INIT_DELAYED_WORK(work, b43_periodic_work_handler); + ieee80211_queue_delayed_work(dev->wl->hw, work, 0); +} + +/* Check if communication with the device works correctly. */ +static int b43_validate_chipaccess(struct b43_wldev *dev) +{ + u32 v, backup0, backup4; + + backup0 = b43_shm_read32(dev, B43_SHM_SHARED, 0); + backup4 = b43_shm_read32(dev, B43_SHM_SHARED, 4); + + /* Check for read/write and endianness problems. */ + b43_shm_write32(dev, B43_SHM_SHARED, 0, 0x55AAAA55); + if (b43_shm_read32(dev, B43_SHM_SHARED, 0) != 0x55AAAA55) + goto error; + b43_shm_write32(dev, B43_SHM_SHARED, 0, 0xAA5555AA); + if (b43_shm_read32(dev, B43_SHM_SHARED, 0) != 0xAA5555AA) + goto error; + + /* Check if unaligned 32bit SHM_SHARED access works properly. + * However, don't bail out on failure, because it's noncritical. */ + b43_shm_write16(dev, B43_SHM_SHARED, 0, 0x1122); + b43_shm_write16(dev, B43_SHM_SHARED, 2, 0x3344); + b43_shm_write16(dev, B43_SHM_SHARED, 4, 0x5566); + b43_shm_write16(dev, B43_SHM_SHARED, 6, 0x7788); + if (b43_shm_read32(dev, B43_SHM_SHARED, 2) != 0x55663344) + b43warn(dev->wl, "Unaligned 32bit SHM read access is broken\n"); + b43_shm_write32(dev, B43_SHM_SHARED, 2, 0xAABBCCDD); + if (b43_shm_read16(dev, B43_SHM_SHARED, 0) != 0x1122 || + b43_shm_read16(dev, B43_SHM_SHARED, 2) != 0xCCDD || + b43_shm_read16(dev, B43_SHM_SHARED, 4) != 0xAABB || + b43_shm_read16(dev, B43_SHM_SHARED, 6) != 0x7788) + b43warn(dev->wl, "Unaligned 32bit SHM write access is broken\n"); + + b43_shm_write32(dev, B43_SHM_SHARED, 0, backup0); + b43_shm_write32(dev, B43_SHM_SHARED, 4, backup4); + + if ((dev->dev->core_rev >= 3) && (dev->dev->core_rev <= 10)) { + /* The 32bit register shadows the two 16bit registers + * with update sideeffects. Validate this. */ + b43_write16(dev, B43_MMIO_TSF_CFP_START, 0xAAAA); + b43_write32(dev, B43_MMIO_TSF_CFP_START, 0xCCCCBBBB); + if (b43_read16(dev, B43_MMIO_TSF_CFP_START_LOW) != 0xBBBB) + goto error; + if (b43_read16(dev, B43_MMIO_TSF_CFP_START_HIGH) != 0xCCCC) + goto error; + } + b43_write32(dev, B43_MMIO_TSF_CFP_START, 0); + + v = b43_read32(dev, B43_MMIO_MACCTL); + v |= B43_MACCTL_GMODE; + if (v != (B43_MACCTL_GMODE | B43_MACCTL_IHR_ENABLED)) + goto error; + + return 0; +error: + b43err(dev->wl, "Failed to validate the chipaccess\n"); + return -ENODEV; +} + +static void b43_security_init(struct b43_wldev *dev) +{ + dev->ktp = b43_shm_read16(dev, B43_SHM_SHARED, B43_SHM_SH_KTP); + /* KTP is a word address, but we address SHM bytewise. + * So multiply by two. + */ + dev->ktp *= 2; + /* Number of RCMTA address slots */ + b43_write16(dev, B43_MMIO_RCMTA_COUNT, B43_NR_PAIRWISE_KEYS); + /* Clear the key memory. */ + b43_clear_keys(dev); +} + +#ifdef CPTCFG_B43_HWRNG +static int b43_rng_read(struct hwrng *rng, u32 *data) +{ + struct b43_wl *wl = (struct b43_wl *)rng->priv; + struct b43_wldev *dev; + int count = -ENODEV; + + mutex_lock(&wl->mutex); + dev = wl->current_dev; + if (likely(dev && b43_status(dev) >= B43_STAT_INITIALIZED)) { + *data = b43_read16(dev, B43_MMIO_RNG); + count = sizeof(u16); + } + mutex_unlock(&wl->mutex); + + return count; +} +#endif /* CPTCFG_B43_HWRNG */ + +static void b43_rng_exit(struct b43_wl *wl) +{ +#ifdef CPTCFG_B43_HWRNG + if (wl->rng_initialized) + hwrng_unregister(&wl->rng); +#endif /* CPTCFG_B43_HWRNG */ +} + +static int b43_rng_init(struct b43_wl *wl) +{ + int err = 0; + +#ifdef CPTCFG_B43_HWRNG + snprintf(wl->rng_name, ARRAY_SIZE(wl->rng_name), + "%s_%s", KBUILD_MODNAME, wiphy_name(wl->hw->wiphy)); + wl->rng.name = wl->rng_name; + wl->rng.data_read = b43_rng_read; + wl->rng.priv = (unsigned long)wl; + wl->rng_initialized = true; + err = hwrng_register(&wl->rng); + if (err) { + wl->rng_initialized = false; + b43err(wl, "Failed to register the random " + "number generator (%d)\n", err); + } +#endif /* CPTCFG_B43_HWRNG */ + + return err; +} + +static void b43_tx_work(struct work_struct *work) +{ + struct b43_wl *wl = container_of(work, struct b43_wl, tx_work); + struct b43_wldev *dev; + struct sk_buff *skb; + int queue_num; + int err = 0; + + mutex_lock(&wl->mutex); + dev = wl->current_dev; + if (unlikely(!dev || b43_status(dev) < B43_STAT_STARTED)) { + mutex_unlock(&wl->mutex); + return; + } + + for (queue_num = 0; queue_num < B43_QOS_QUEUE_NUM; queue_num++) { + while (skb_queue_len(&wl->tx_queue[queue_num])) { + skb = skb_dequeue(&wl->tx_queue[queue_num]); + if (b43_using_pio_transfers(dev)) + err = b43_pio_tx(dev, skb); + else + err = b43_dma_tx(dev, skb); + if (err == -ENOSPC) { + wl->tx_queue_stopped[queue_num] = 1; + ieee80211_stop_queue(wl->hw, queue_num); + skb_queue_head(&wl->tx_queue[queue_num], skb); + break; + } + if (unlikely(err)) + ieee80211_free_txskb(wl->hw, skb); + err = 0; + } + + if (!err) + wl->tx_queue_stopped[queue_num] = 0; + } + +#if B43_DEBUG + dev->tx_count++; +#endif + mutex_unlock(&wl->mutex); +} + +static void b43_op_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + + if (unlikely(skb->len < 2 + 2 + 6)) { + /* Too short, this can't be a valid frame. */ + ieee80211_free_txskb(hw, skb); + return; + } + B43_WARN_ON(skb_shinfo(skb)->nr_frags); + + skb_queue_tail(&wl->tx_queue[skb_get_queue_mapping(skb)], skb); + if (!wl->tx_queue_stopped[skb_get_queue_mapping(skb)]) { + ieee80211_queue_work(wl->hw, &wl->tx_work); + } else { + ieee80211_stop_queue(wl->hw, skb_get_queue_mapping(skb)); + } +} + +static void b43_qos_params_upload(struct b43_wldev *dev, + const struct ieee80211_tx_queue_params *p, + u16 shm_offset) +{ + u16 params[B43_NR_QOSPARAMS]; + int bslots, tmp; + unsigned int i; + + if (!dev->qos_enabled) + return; + + bslots = b43_read16(dev, B43_MMIO_RNG) & p->cw_min; + + memset(¶ms, 0, sizeof(params)); + + params[B43_QOSPARAM_TXOP] = p->txop * 32; + params[B43_QOSPARAM_CWMIN] = p->cw_min; + params[B43_QOSPARAM_CWMAX] = p->cw_max; + params[B43_QOSPARAM_CWCUR] = p->cw_min; + params[B43_QOSPARAM_AIFS] = p->aifs; + params[B43_QOSPARAM_BSLOTS] = bslots; + params[B43_QOSPARAM_REGGAP] = bslots + p->aifs; + + for (i = 0; i < ARRAY_SIZE(params); i++) { + if (i == B43_QOSPARAM_STATUS) { + tmp = b43_shm_read16(dev, B43_SHM_SHARED, + shm_offset + (i * 2)); + /* Mark the parameters as updated. */ + tmp |= 0x100; + b43_shm_write16(dev, B43_SHM_SHARED, + shm_offset + (i * 2), + tmp); + } else { + b43_shm_write16(dev, B43_SHM_SHARED, + shm_offset + (i * 2), + params[i]); + } + } +} + +/* Mapping of mac80211 queue numbers to b43 QoS SHM offsets. */ +static const u16 b43_qos_shm_offsets[] = { + /* [mac80211-queue-nr] = SHM_OFFSET, */ + [0] = B43_QOS_VOICE, + [1] = B43_QOS_VIDEO, + [2] = B43_QOS_BESTEFFORT, + [3] = B43_QOS_BACKGROUND, +}; + +/* Update all QOS parameters in hardware. */ +static void b43_qos_upload_all(struct b43_wldev *dev) +{ + struct b43_wl *wl = dev->wl; + struct b43_qos_params *params; + unsigned int i; + + if (!dev->qos_enabled) + return; + + BUILD_BUG_ON(ARRAY_SIZE(b43_qos_shm_offsets) != + ARRAY_SIZE(wl->qos_params)); + + b43_mac_suspend(dev); + for (i = 0; i < ARRAY_SIZE(wl->qos_params); i++) { + params = &(wl->qos_params[i]); + b43_qos_params_upload(dev, &(params->p), + b43_qos_shm_offsets[i]); + } + b43_mac_enable(dev); +} + +static void b43_qos_clear(struct b43_wl *wl) +{ + struct b43_qos_params *params; + unsigned int i; + + /* Initialize QoS parameters to sane defaults. */ + + BUILD_BUG_ON(ARRAY_SIZE(b43_qos_shm_offsets) != + ARRAY_SIZE(wl->qos_params)); + + for (i = 0; i < ARRAY_SIZE(wl->qos_params); i++) { + params = &(wl->qos_params[i]); + + switch (b43_qos_shm_offsets[i]) { + case B43_QOS_VOICE: + params->p.txop = 0; + params->p.aifs = 2; + params->p.cw_min = 0x0001; + params->p.cw_max = 0x0001; + break; + case B43_QOS_VIDEO: + params->p.txop = 0; + params->p.aifs = 2; + params->p.cw_min = 0x0001; + params->p.cw_max = 0x0001; + break; + case B43_QOS_BESTEFFORT: + params->p.txop = 0; + params->p.aifs = 3; + params->p.cw_min = 0x0001; + params->p.cw_max = 0x03FF; + break; + case B43_QOS_BACKGROUND: + params->p.txop = 0; + params->p.aifs = 7; + params->p.cw_min = 0x0001; + params->p.cw_max = 0x03FF; + break; + default: + B43_WARN_ON(1); + } + } +} + +/* Initialize the core's QOS capabilities */ +static void b43_qos_init(struct b43_wldev *dev) +{ + if (!dev->qos_enabled) { + /* Disable QOS support. */ + b43_hf_write(dev, b43_hf_read(dev) & ~B43_HF_EDCF); + b43_write16(dev, B43_MMIO_IFSCTL, + b43_read16(dev, B43_MMIO_IFSCTL) + & ~B43_MMIO_IFSCTL_USE_EDCF); + b43dbg(dev->wl, "QoS disabled\n"); + return; + } + + /* Upload the current QOS parameters. */ + b43_qos_upload_all(dev); + + /* Enable QOS support. */ + b43_hf_write(dev, b43_hf_read(dev) | B43_HF_EDCF); + b43_write16(dev, B43_MMIO_IFSCTL, + b43_read16(dev, B43_MMIO_IFSCTL) + | B43_MMIO_IFSCTL_USE_EDCF); + b43dbg(dev->wl, "QoS enabled\n"); +} + +static int b43_op_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 _queue, + const struct ieee80211_tx_queue_params *params) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + struct b43_wldev *dev; + unsigned int queue = (unsigned int)_queue; + int err = -ENODEV; + + if (queue >= ARRAY_SIZE(wl->qos_params)) { + /* Queue not available or don't support setting + * params on this queue. Return success to not + * confuse mac80211. */ + return 0; + } + BUILD_BUG_ON(ARRAY_SIZE(b43_qos_shm_offsets) != + ARRAY_SIZE(wl->qos_params)); + + mutex_lock(&wl->mutex); + dev = wl->current_dev; + if (unlikely(!dev || (b43_status(dev) < B43_STAT_INITIALIZED))) + goto out_unlock; + + memcpy(&(wl->qos_params[queue].p), params, sizeof(*params)); + b43_mac_suspend(dev); + b43_qos_params_upload(dev, &(wl->qos_params[queue].p), + b43_qos_shm_offsets[queue]); + b43_mac_enable(dev); + err = 0; + +out_unlock: + mutex_unlock(&wl->mutex); + + return err; +} + +static int b43_op_get_stats(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + + mutex_lock(&wl->mutex); + memcpy(stats, &wl->ieee_stats, sizeof(*stats)); + mutex_unlock(&wl->mutex); + + return 0; +} + +static u64 b43_op_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + struct b43_wldev *dev; + u64 tsf; + + mutex_lock(&wl->mutex); + dev = wl->current_dev; + + if (dev && (b43_status(dev) >= B43_STAT_INITIALIZED)) + b43_tsf_read(dev, &tsf); + else + tsf = 0; + + mutex_unlock(&wl->mutex); + + return tsf; +} + +static void b43_op_set_tsf(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u64 tsf) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + struct b43_wldev *dev; + + mutex_lock(&wl->mutex); + dev = wl->current_dev; + + if (dev && (b43_status(dev) >= B43_STAT_INITIALIZED)) + b43_tsf_write(dev, tsf); + + mutex_unlock(&wl->mutex); +} + +static void b43_put_phy_into_reset(struct b43_wldev *dev) +{ + u32 tmp; + + switch (dev->dev->bus_type) { +#ifdef CPTCFG_B43_BCMA + case B43_BUS_BCMA: + b43err(dev->wl, + "Putting PHY into reset not supported on BCMA\n"); + break; +#endif +#ifdef CPTCFG_B43_SSB + case B43_BUS_SSB: + tmp = ssb_read32(dev->dev->sdev, SSB_TMSLOW); + tmp &= ~B43_TMSLOW_GMODE; + tmp |= B43_TMSLOW_PHYRESET; + tmp |= SSB_TMSLOW_FGC; + ssb_write32(dev->dev->sdev, SSB_TMSLOW, tmp); + msleep(1); + + tmp = ssb_read32(dev->dev->sdev, SSB_TMSLOW); + tmp &= ~SSB_TMSLOW_FGC; + tmp |= B43_TMSLOW_PHYRESET; + ssb_write32(dev->dev->sdev, SSB_TMSLOW, tmp); + msleep(1); + + break; +#endif + } +} + +static const char *band_to_string(enum ieee80211_band band) +{ + switch (band) { + case IEEE80211_BAND_5GHZ: + return "5"; + case IEEE80211_BAND_2GHZ: + return "2.4"; + default: + break; + } + B43_WARN_ON(1); + return ""; +} + +/* Expects wl->mutex locked */ +static int b43_switch_band(struct b43_wl *wl, struct ieee80211_channel *chan) +{ + struct b43_wldev *up_dev = NULL; + struct b43_wldev *down_dev; + struct b43_wldev *d; + int err; + bool uninitialized_var(gmode); + int prev_status; + + /* Find a device and PHY which supports the band. */ + list_for_each_entry(d, &wl->devlist, list) { + switch (chan->band) { + case IEEE80211_BAND_5GHZ: + if (d->phy.supports_5ghz) { + up_dev = d; + gmode = false; + } + break; + case IEEE80211_BAND_2GHZ: + if (d->phy.supports_2ghz) { + up_dev = d; + gmode = true; + } + break; + default: + B43_WARN_ON(1); + return -EINVAL; + } + if (up_dev) + break; + } + if (!up_dev) { + b43err(wl, "Could not find a device for %s-GHz band operation\n", + band_to_string(chan->band)); + return -ENODEV; + } + if ((up_dev == wl->current_dev) && + (!!wl->current_dev->phy.gmode == !!gmode)) { + /* This device is already running. */ + return 0; + } + b43dbg(wl, "Switching to %s-GHz band\n", + band_to_string(chan->band)); + down_dev = wl->current_dev; + + prev_status = b43_status(down_dev); + /* Shutdown the currently running core. */ + if (prev_status >= B43_STAT_STARTED) + down_dev = b43_wireless_core_stop(down_dev); + if (prev_status >= B43_STAT_INITIALIZED) + b43_wireless_core_exit(down_dev); + + if (down_dev != up_dev) { + /* We switch to a different core, so we put PHY into + * RESET on the old core. */ + b43_put_phy_into_reset(down_dev); + } + + /* Now start the new core. */ + up_dev->phy.gmode = gmode; + if (prev_status >= B43_STAT_INITIALIZED) { + err = b43_wireless_core_init(up_dev); + if (err) { + b43err(wl, "Fatal: Could not initialize device for " + "selected %s-GHz band\n", + band_to_string(chan->band)); + goto init_failure; + } + } + if (prev_status >= B43_STAT_STARTED) { + err = b43_wireless_core_start(up_dev); + if (err) { + b43err(wl, "Fatal: Could not start device for " + "selected %s-GHz band\n", + band_to_string(chan->band)); + b43_wireless_core_exit(up_dev); + goto init_failure; + } + } + B43_WARN_ON(b43_status(up_dev) != prev_status); + + wl->current_dev = up_dev; + + return 0; +init_failure: + /* Whoops, failed to init the new core. No core is operating now. */ + wl->current_dev = NULL; + return err; +} + +/* Write the short and long frame retry limit values. */ +static void b43_set_retry_limits(struct b43_wldev *dev, + unsigned int short_retry, + unsigned int long_retry) +{ + /* The retry limit is a 4-bit counter. Enforce this to avoid overflowing + * the chip-internal counter. */ + short_retry = min(short_retry, (unsigned int)0xF); + long_retry = min(long_retry, (unsigned int)0xF); + + b43_shm_write16(dev, B43_SHM_SCRATCH, B43_SHM_SC_SRLIMIT, + short_retry); + b43_shm_write16(dev, B43_SHM_SCRATCH, B43_SHM_SC_LRLIMIT, + long_retry); +} + +static int b43_op_config(struct ieee80211_hw *hw, u32 changed) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + struct b43_wldev *dev; + struct b43_phy *phy; + struct ieee80211_conf *conf = &hw->conf; + int antenna; + int err = 0; + bool reload_bss = false; + + mutex_lock(&wl->mutex); + + dev = wl->current_dev; + + /* Switch the band (if necessary). This might change the active core. */ + err = b43_switch_band(wl, conf->chandef.chan); + if (err) + goto out_unlock_mutex; + + /* Need to reload all settings if the core changed */ + if (dev != wl->current_dev) { + dev = wl->current_dev; + changed = ~0; + reload_bss = true; + } + + phy = &dev->phy; + + if (conf_is_ht(conf)) + phy->is_40mhz = + (conf_is_ht40_minus(conf) || conf_is_ht40_plus(conf)); + else + phy->is_40mhz = false; + + b43_mac_suspend(dev); + + if (changed & IEEE80211_CONF_CHANGE_RETRY_LIMITS) + b43_set_retry_limits(dev, conf->short_frame_max_tx_count, + conf->long_frame_max_tx_count); + changed &= ~IEEE80211_CONF_CHANGE_RETRY_LIMITS; + if (!changed) + goto out_mac_enable; + + /* Switch to the requested channel. + * The firmware takes care of races with the TX handler. */ + if (conf->chandef.chan->hw_value != phy->channel) + b43_switch_channel(dev, conf->chandef.chan->hw_value); + + dev->wl->radiotap_enabled = !!(conf->flags & IEEE80211_CONF_MONITOR); + + /* Adjust the desired TX power level. */ + if (conf->power_level != 0) { + if (conf->power_level != phy->desired_txpower) { + phy->desired_txpower = conf->power_level; + b43_phy_txpower_check(dev, B43_TXPWR_IGNORE_TIME | + B43_TXPWR_IGNORE_TSSI); + } + } + + /* Antennas for RX and management frame TX. */ + antenna = B43_ANTENNA_DEFAULT; + b43_mgmtframe_txantenna(dev, antenna); + antenna = B43_ANTENNA_DEFAULT; + if (phy->ops->set_rx_antenna) + phy->ops->set_rx_antenna(dev, antenna); + + if (wl->radio_enabled != phy->radio_on) { + if (wl->radio_enabled) { + b43_software_rfkill(dev, false); + b43info(dev->wl, "Radio turned on by software\n"); + if (!dev->radio_hw_enable) { + b43info(dev->wl, "The hardware RF-kill button " + "still turns the radio physically off. " + "Press the button to turn it on.\n"); + } + } else { + b43_software_rfkill(dev, true); + b43info(dev->wl, "Radio turned off by software\n"); + } + } + +out_mac_enable: + b43_mac_enable(dev); +out_unlock_mutex: + mutex_unlock(&wl->mutex); + + if (wl->vif && reload_bss) + b43_op_bss_info_changed(hw, wl->vif, &wl->vif->bss_conf, ~0); + + return err; +} + +static void b43_update_basic_rates(struct b43_wldev *dev, u32 brates) +{ + struct ieee80211_supported_band *sband = + dev->wl->hw->wiphy->bands[b43_current_band(dev->wl)]; + struct ieee80211_rate *rate; + int i; + u16 basic, direct, offset, basic_offset, rateptr; + + for (i = 0; i < sband->n_bitrates; i++) { + rate = &sband->bitrates[i]; + + if (b43_is_cck_rate(rate->hw_value)) { + direct = B43_SHM_SH_CCKDIRECT; + basic = B43_SHM_SH_CCKBASIC; + offset = b43_plcp_get_ratecode_cck(rate->hw_value); + offset &= 0xF; + } else { + direct = B43_SHM_SH_OFDMDIRECT; + basic = B43_SHM_SH_OFDMBASIC; + offset = b43_plcp_get_ratecode_ofdm(rate->hw_value); + offset &= 0xF; + } + + rate = ieee80211_get_response_rate(sband, brates, rate->bitrate); + + if (b43_is_cck_rate(rate->hw_value)) { + basic_offset = b43_plcp_get_ratecode_cck(rate->hw_value); + basic_offset &= 0xF; + } else { + basic_offset = b43_plcp_get_ratecode_ofdm(rate->hw_value); + basic_offset &= 0xF; + } + + /* + * Get the pointer that we need to point to + * from the direct map + */ + rateptr = b43_shm_read16(dev, B43_SHM_SHARED, + direct + 2 * basic_offset); + /* and write it to the basic map */ + b43_shm_write16(dev, B43_SHM_SHARED, basic + 2 * offset, + rateptr); + } +} + +static void b43_op_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *conf, + u32 changed) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + struct b43_wldev *dev; + + mutex_lock(&wl->mutex); + + dev = wl->current_dev; + if (!dev || b43_status(dev) < B43_STAT_STARTED) + goto out_unlock_mutex; + + B43_WARN_ON(wl->vif != vif); + + if (changed & BSS_CHANGED_BSSID) { + if (conf->bssid) + memcpy(wl->bssid, conf->bssid, ETH_ALEN); + else + memset(wl->bssid, 0, ETH_ALEN); + } + + if (b43_status(dev) >= B43_STAT_INITIALIZED) { + if (changed & BSS_CHANGED_BEACON && + (b43_is_mode(wl, NL80211_IFTYPE_AP) || + b43_is_mode(wl, NL80211_IFTYPE_MESH_POINT) || + b43_is_mode(wl, NL80211_IFTYPE_ADHOC))) + b43_update_templates(wl); + + if (changed & BSS_CHANGED_BSSID) + b43_write_mac_bssid_templates(dev); + } + + b43_mac_suspend(dev); + + /* Update templates for AP/mesh mode. */ + if (changed & BSS_CHANGED_BEACON_INT && + (b43_is_mode(wl, NL80211_IFTYPE_AP) || + b43_is_mode(wl, NL80211_IFTYPE_MESH_POINT) || + b43_is_mode(wl, NL80211_IFTYPE_ADHOC)) && + conf->beacon_int) + b43_set_beacon_int(dev, conf->beacon_int); + + if (changed & BSS_CHANGED_BASIC_RATES) + b43_update_basic_rates(dev, conf->basic_rates); + + if (changed & BSS_CHANGED_ERP_SLOT) { + if (conf->use_short_slot) + b43_short_slot_timing_enable(dev); + else + b43_short_slot_timing_disable(dev); + } + + b43_mac_enable(dev); +out_unlock_mutex: + mutex_unlock(&wl->mutex); +} + +static int b43_op_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + struct b43_wldev *dev; + u8 algorithm; + u8 index; + int err; + static const u8 bcast_addr[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + if (modparam_nohwcrypt) + return -ENOSPC; /* User disabled HW-crypto */ + + if ((vif->type == NL80211_IFTYPE_ADHOC || + vif->type == NL80211_IFTYPE_MESH_POINT) && + (key->cipher == WLAN_CIPHER_SUITE_TKIP || + key->cipher == WLAN_CIPHER_SUITE_CCMP) && + !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) { + /* + * For now, disable hw crypto for the RSN IBSS group keys. This + * could be optimized in the future, but until that gets + * implemented, use of software crypto for group addressed + * frames is a acceptable to allow RSN IBSS to be used. + */ + return -EOPNOTSUPP; + } + + mutex_lock(&wl->mutex); + + dev = wl->current_dev; + err = -ENODEV; + if (!dev || b43_status(dev) < B43_STAT_INITIALIZED) + goto out_unlock; + + if (dev->fw.pcm_request_failed || !dev->hwcrypto_enabled) { + /* We don't have firmware for the crypto engine. + * Must use software-crypto. */ + err = -EOPNOTSUPP; + goto out_unlock; + } + + err = -EINVAL; + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + algorithm = B43_SEC_ALGO_WEP40; + break; + case WLAN_CIPHER_SUITE_WEP104: + algorithm = B43_SEC_ALGO_WEP104; + break; + case WLAN_CIPHER_SUITE_TKIP: + algorithm = B43_SEC_ALGO_TKIP; + break; + case WLAN_CIPHER_SUITE_CCMP: + algorithm = B43_SEC_ALGO_AES; + break; + default: + B43_WARN_ON(1); + goto out_unlock; + } + index = (u8) (key->keyidx); + if (index > 3) + goto out_unlock; + + switch (cmd) { + case SET_KEY: + if (algorithm == B43_SEC_ALGO_TKIP && + (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE) || + !modparam_hwtkip)) { + /* We support only pairwise key */ + err = -EOPNOTSUPP; + goto out_unlock; + } + + if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) { + if (WARN_ON(!sta)) { + err = -EOPNOTSUPP; + goto out_unlock; + } + /* Pairwise key with an assigned MAC address. */ + err = b43_key_write(dev, -1, algorithm, + key->key, key->keylen, + sta->addr, key); + } else { + /* Group key */ + err = b43_key_write(dev, index, algorithm, + key->key, key->keylen, NULL, key); + } + if (err) + goto out_unlock; + + if (algorithm == B43_SEC_ALGO_WEP40 || + algorithm == B43_SEC_ALGO_WEP104) { + b43_hf_write(dev, b43_hf_read(dev) | B43_HF_USEDEFKEYS); + } else { + b43_hf_write(dev, + b43_hf_read(dev) & ~B43_HF_USEDEFKEYS); + } + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + if (algorithm == B43_SEC_ALGO_TKIP) + key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC; + break; + case DISABLE_KEY: { + err = b43_key_clear(dev, key->hw_key_idx); + if (err) + goto out_unlock; + break; + } + default: + B43_WARN_ON(1); + } + +out_unlock: + if (!err) { + b43dbg(wl, "%s hardware based encryption for keyidx: %d, " + "mac: %pM\n", + cmd == SET_KEY ? "Using" : "Disabling", key->keyidx, + sta ? sta->addr : bcast_addr); + b43_dump_keymemory(dev); + } + mutex_unlock(&wl->mutex); + + return err; +} + +static void b43_op_configure_filter(struct ieee80211_hw *hw, + unsigned int changed, unsigned int *fflags, + u64 multicast) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + struct b43_wldev *dev; + + mutex_lock(&wl->mutex); + dev = wl->current_dev; + if (!dev) { + *fflags = 0; + goto out_unlock; + } + + *fflags &= FIF_PROMISC_IN_BSS | + FIF_ALLMULTI | + FIF_FCSFAIL | + FIF_PLCPFAIL | + FIF_CONTROL | + FIF_OTHER_BSS | + FIF_BCN_PRBRESP_PROMISC; + + changed &= FIF_PROMISC_IN_BSS | + FIF_ALLMULTI | + FIF_FCSFAIL | + FIF_PLCPFAIL | + FIF_CONTROL | + FIF_OTHER_BSS | + FIF_BCN_PRBRESP_PROMISC; + + wl->filter_flags = *fflags; + + if (changed && b43_status(dev) >= B43_STAT_INITIALIZED) + b43_adjust_opmode(dev); + +out_unlock: + mutex_unlock(&wl->mutex); +} + +/* Locking: wl->mutex + * Returns the current dev. This might be different from the passed in dev, + * because the core might be gone away while we unlocked the mutex. */ +static struct b43_wldev * b43_wireless_core_stop(struct b43_wldev *dev) +{ + struct b43_wl *wl; + struct b43_wldev *orig_dev; + u32 mask; + int queue_num; + + if (!dev) + return NULL; + wl = dev->wl; +redo: + if (!dev || b43_status(dev) < B43_STAT_STARTED) + return dev; + + /* Cancel work. Unlock to avoid deadlocks. */ + mutex_unlock(&wl->mutex); + cancel_delayed_work_sync(&dev->periodic_work); + cancel_work_sync(&wl->tx_work); + mutex_lock(&wl->mutex); + dev = wl->current_dev; + if (!dev || b43_status(dev) < B43_STAT_STARTED) { + /* Whoops, aliens ate up the device while we were unlocked. */ + return dev; + } + + /* Disable interrupts on the device. */ + b43_set_status(dev, B43_STAT_INITIALIZED); + if (b43_bus_host_is_sdio(dev->dev)) { + /* wl->mutex is locked. That is enough. */ + b43_write32(dev, B43_MMIO_GEN_IRQ_MASK, 0); + b43_read32(dev, B43_MMIO_GEN_IRQ_MASK); /* Flush */ + } else { + spin_lock_irq(&wl->hardirq_lock); + b43_write32(dev, B43_MMIO_GEN_IRQ_MASK, 0); + b43_read32(dev, B43_MMIO_GEN_IRQ_MASK); /* Flush */ + spin_unlock_irq(&wl->hardirq_lock); + } + /* Synchronize and free the interrupt handlers. Unlock to avoid deadlocks. */ + orig_dev = dev; + mutex_unlock(&wl->mutex); + if (b43_bus_host_is_sdio(dev->dev)) { + b43_sdio_free_irq(dev); + } else { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31) + compat_synchronize_threaded_irq(&dev->irq_compat); + compat_free_threaded_irq(&dev->irq_compat); +#else + synchronize_irq(dev->dev->irq); + free_irq(dev->dev->irq, dev); +#endif + } + mutex_lock(&wl->mutex); + dev = wl->current_dev; + if (!dev) + return dev; + if (dev != orig_dev) { + if (b43_status(dev) >= B43_STAT_STARTED) + goto redo; + return dev; + } + mask = b43_read32(dev, B43_MMIO_GEN_IRQ_MASK); + B43_WARN_ON(mask != 0xFFFFFFFF && mask); + + /* Drain all TX queues. */ + for (queue_num = 0; queue_num < B43_QOS_QUEUE_NUM; queue_num++) { + while (skb_queue_len(&wl->tx_queue[queue_num])) { + struct sk_buff *skb; + + skb = skb_dequeue(&wl->tx_queue[queue_num]); + ieee80211_free_txskb(wl->hw, skb); + } + } + + b43_mac_suspend(dev); + b43_leds_exit(dev); + b43dbg(wl, "Wireless interface stopped\n"); + + return dev; +} + +/* Locking: wl->mutex */ +static int b43_wireless_core_start(struct b43_wldev *dev) +{ + int err; + + B43_WARN_ON(b43_status(dev) != B43_STAT_INITIALIZED); + + drain_txstatus_queue(dev); + if (b43_bus_host_is_sdio(dev->dev)) { + err = b43_sdio_request_irq(dev, b43_sdio_interrupt_handler); + if (err) { + b43err(dev->wl, "Cannot request SDIO IRQ\n"); + goto out; + } + } else { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31) + err = compat_request_threaded_irq(&dev->irq_compat, + dev->dev->irq, + b43_interrupt_handler, + b43_interrupt_thread_handler, + IRQF_SHARED, KBUILD_MODNAME, dev); +#else + err = request_threaded_irq(dev->dev->irq, b43_interrupt_handler, + b43_interrupt_thread_handler, + IRQF_SHARED, KBUILD_MODNAME, dev); +#endif + if (err) { + b43err(dev->wl, "Cannot request IRQ-%d\n", + dev->dev->irq); + goto out; + } + } + + /* We are ready to run. */ + ieee80211_wake_queues(dev->wl->hw); + b43_set_status(dev, B43_STAT_STARTED); + + /* Start data flow (TX/RX). */ + b43_mac_enable(dev); + b43_write32(dev, B43_MMIO_GEN_IRQ_MASK, dev->irq_mask); + + /* Start maintenance work */ + b43_periodic_tasks_setup(dev); + + b43_leds_init(dev); + + b43dbg(dev->wl, "Wireless interface started\n"); +out: + return err; +} + +static char *b43_phy_name(struct b43_wldev *dev, u8 phy_type) +{ + switch (phy_type) { + case B43_PHYTYPE_A: + return "A"; + case B43_PHYTYPE_B: + return "B"; + case B43_PHYTYPE_G: + return "G"; + case B43_PHYTYPE_N: + return "N"; + case B43_PHYTYPE_LP: + return "LP"; + case B43_PHYTYPE_SSLPN: + return "SSLPN"; + case B43_PHYTYPE_HT: + return "HT"; + case B43_PHYTYPE_LCN: + return "LCN"; + case B43_PHYTYPE_LCNXN: + return "LCNXN"; + case B43_PHYTYPE_LCN40: + return "LCN40"; + case B43_PHYTYPE_AC: + return "AC"; + } + return "UNKNOWN"; +} + +/* Get PHY and RADIO versioning numbers */ +static int b43_phy_versioning(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + u32 tmp; + u8 analog_type; + u8 phy_type; + u8 phy_rev; + u16 radio_manuf; + u16 radio_ver; + u16 radio_rev; + int unsupported = 0; + + /* Get PHY versioning */ + tmp = b43_read16(dev, B43_MMIO_PHY_VER); + analog_type = (tmp & B43_PHYVER_ANALOG) >> B43_PHYVER_ANALOG_SHIFT; + phy_type = (tmp & B43_PHYVER_TYPE) >> B43_PHYVER_TYPE_SHIFT; + phy_rev = (tmp & B43_PHYVER_VERSION); + switch (phy_type) { + case B43_PHYTYPE_A: + if (phy_rev >= 4) + unsupported = 1; + break; + case B43_PHYTYPE_B: + if (phy_rev != 2 && phy_rev != 4 && phy_rev != 6 + && phy_rev != 7) + unsupported = 1; + break; + case B43_PHYTYPE_G: + if (phy_rev > 9) + unsupported = 1; + break; +#ifdef CPTCFG_B43_PHY_N + case B43_PHYTYPE_N: + if (phy_rev > 9) + unsupported = 1; + break; +#endif +#ifdef CPTCFG_B43_PHY_LP + case B43_PHYTYPE_LP: + if (phy_rev > 2) + unsupported = 1; + break; +#endif +#ifdef CPTCFG_B43_PHY_HT + case B43_PHYTYPE_HT: + if (phy_rev > 1) + unsupported = 1; + break; +#endif +#ifdef CPTCFG_B43_PHY_LCN + case B43_PHYTYPE_LCN: + if (phy_rev > 1) + unsupported = 1; + break; +#endif + default: + unsupported = 1; + } + if (unsupported) { + b43err(dev->wl, "FOUND UNSUPPORTED PHY (Analog %u, Type %d (%s), Revision %u)\n", + analog_type, phy_type, b43_phy_name(dev, phy_type), + phy_rev); + return -EOPNOTSUPP; + } + b43info(dev->wl, "Found PHY: Analog %u, Type %d (%s), Revision %u\n", + analog_type, phy_type, b43_phy_name(dev, phy_type), phy_rev); + + /* Get RADIO versioning */ + if (dev->dev->core_rev >= 24) { + u16 radio24[3]; + + for (tmp = 0; tmp < 3; tmp++) { + b43_write16(dev, B43_MMIO_RADIO24_CONTROL, tmp); + radio24[tmp] = b43_read16(dev, B43_MMIO_RADIO24_DATA); + } + + /* Broadcom uses "id" for our "ver" and has separated "ver" */ + /* radio_ver = (radio24[0] & 0xF0) >> 4; */ + + radio_manuf = 0x17F; + radio_ver = (radio24[2] << 8) | radio24[1]; + radio_rev = (radio24[0] & 0xF); + } else { + if (dev->dev->chip_id == 0x4317) { + if (dev->dev->chip_rev == 0) + tmp = 0x3205017F; + else if (dev->dev->chip_rev == 1) + tmp = 0x4205017F; + else + tmp = 0x5205017F; + } else { + b43_write16(dev, B43_MMIO_RADIO_CONTROL, + B43_RADIOCTL_ID); + tmp = b43_read16(dev, B43_MMIO_RADIO_DATA_LOW); + b43_write16(dev, B43_MMIO_RADIO_CONTROL, + B43_RADIOCTL_ID); + tmp |= (u32)b43_read16(dev, B43_MMIO_RADIO_DATA_HIGH) + << 16; + } + radio_manuf = (tmp & 0x00000FFF); + radio_ver = (tmp & 0x0FFFF000) >> 12; + radio_rev = (tmp & 0xF0000000) >> 28; + } + + if (radio_manuf != 0x17F /* Broadcom */) + unsupported = 1; + switch (phy_type) { + case B43_PHYTYPE_A: + if (radio_ver != 0x2060) + unsupported = 1; + if (radio_rev != 1) + unsupported = 1; + if (radio_manuf != 0x17F) + unsupported = 1; + break; + case B43_PHYTYPE_B: + if ((radio_ver & 0xFFF0) != 0x2050) + unsupported = 1; + break; + case B43_PHYTYPE_G: + if (radio_ver != 0x2050) + unsupported = 1; + break; + case B43_PHYTYPE_N: + if (radio_ver != 0x2055 && radio_ver != 0x2056) + unsupported = 1; + break; + case B43_PHYTYPE_LP: + if (radio_ver != 0x2062 && radio_ver != 0x2063) + unsupported = 1; + break; + case B43_PHYTYPE_HT: + if (radio_ver != 0x2059) + unsupported = 1; + break; + case B43_PHYTYPE_LCN: + if (radio_ver != 0x2064) + unsupported = 1; + break; + default: + B43_WARN_ON(1); + } + if (unsupported) { + b43err(dev->wl, "FOUND UNSUPPORTED RADIO " + "(Manuf 0x%X, Version 0x%X, Revision %u)\n", + radio_manuf, radio_ver, radio_rev); + return -EOPNOTSUPP; + } + b43dbg(dev->wl, "Found Radio: Manuf 0x%X, Version 0x%X, Revision %u\n", + radio_manuf, radio_ver, radio_rev); + + phy->radio_manuf = radio_manuf; + phy->radio_ver = radio_ver; + phy->radio_rev = radio_rev; + + phy->analog = analog_type; + phy->type = phy_type; + phy->rev = phy_rev; + + return 0; +} + +static void setup_struct_phy_for_init(struct b43_wldev *dev, + struct b43_phy *phy) +{ + phy->hardware_power_control = !!modparam_hwpctl; + phy->next_txpwr_check_time = jiffies; + /* PHY TX errors counter. */ + atomic_set(&phy->txerr_cnt, B43_PHY_TX_BADNESS_LIMIT); + +#if B43_DEBUG + phy->phy_locked = false; + phy->radio_locked = false; +#endif +} + +static void setup_struct_wldev_for_init(struct b43_wldev *dev) +{ + dev->dfq_valid = false; + + /* Assume the radio is enabled. If it's not enabled, the state will + * immediately get fixed on the first periodic work run. */ + dev->radio_hw_enable = true; + + /* Stats */ + memset(&dev->stats, 0, sizeof(dev->stats)); + + setup_struct_phy_for_init(dev, &dev->phy); + + /* IRQ related flags */ + dev->irq_reason = 0; + memset(dev->dma_reason, 0, sizeof(dev->dma_reason)); + dev->irq_mask = B43_IRQ_MASKTEMPLATE; + if (b43_modparam_verbose < B43_VERBOSITY_DEBUG) + dev->irq_mask &= ~B43_IRQ_PHY_TXERR; + + dev->mac_suspended = 1; + + /* Noise calculation context */ + memset(&dev->noisecalc, 0, sizeof(dev->noisecalc)); +} + +static void b43_bluetooth_coext_enable(struct b43_wldev *dev) +{ + struct ssb_sprom *sprom = dev->dev->bus_sprom; + u64 hf; + + if (!modparam_btcoex) + return; + if (!(sprom->boardflags_lo & B43_BFL_BTCOEXIST)) + return; + if (dev->phy.type != B43_PHYTYPE_B && !dev->phy.gmode) + return; + + hf = b43_hf_read(dev); + if (sprom->boardflags_lo & B43_BFL_BTCMOD) + hf |= B43_HF_BTCOEXALT; + else + hf |= B43_HF_BTCOEX; + b43_hf_write(dev, hf); +} + +static void b43_bluetooth_coext_disable(struct b43_wldev *dev) +{ + if (!modparam_btcoex) + return; + //TODO +} + +static void b43_imcfglo_timeouts_workaround(struct b43_wldev *dev) +{ + struct ssb_bus *bus; + u32 tmp; + + if (dev->dev->bus_type != B43_BUS_SSB) + return; + + bus = dev->dev->sdev->bus; + + if ((bus->chip_id == 0x4311 && bus->chip_rev == 2) || + (bus->chip_id == 0x4312)) { + tmp = ssb_read32(dev->dev->sdev, SSB_IMCFGLO); + tmp &= ~SSB_IMCFGLO_REQTO; + tmp &= ~SSB_IMCFGLO_SERTO; + tmp |= 0x3; + ssb_write32(dev->dev->sdev, SSB_IMCFGLO, tmp); + ssb_commit_settings(bus); + } +} + +static void b43_set_synth_pu_delay(struct b43_wldev *dev, bool idle) +{ + u16 pu_delay; + + /* The time value is in microseconds. */ + if (dev->phy.type == B43_PHYTYPE_A) + pu_delay = 3700; + else + pu_delay = 1050; + if (b43_is_mode(dev->wl, NL80211_IFTYPE_ADHOC) || idle) + pu_delay = 500; + if ((dev->phy.radio_ver == 0x2050) && (dev->phy.radio_rev == 8)) + pu_delay = max(pu_delay, (u16)2400); + + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_SPUWKUP, pu_delay); +} + +/* Set the TSF CFP pre-TargetBeaconTransmissionTime. */ +static void b43_set_pretbtt(struct b43_wldev *dev) +{ + u16 pretbtt; + + /* The time value is in microseconds. */ + if (b43_is_mode(dev->wl, NL80211_IFTYPE_ADHOC)) { + pretbtt = 2; + } else { + if (dev->phy.type == B43_PHYTYPE_A) + pretbtt = 120; + else + pretbtt = 250; + } + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_PRETBTT, pretbtt); + b43_write16(dev, B43_MMIO_TSF_CFP_PRETBTT, pretbtt); +} + +/* Shutdown a wireless core */ +/* Locking: wl->mutex */ +static void b43_wireless_core_exit(struct b43_wldev *dev) +{ + B43_WARN_ON(dev && b43_status(dev) > B43_STAT_INITIALIZED); + if (!dev || b43_status(dev) != B43_STAT_INITIALIZED) + return; + + /* Unregister HW RNG driver */ + b43_rng_exit(dev->wl); + + b43_set_status(dev, B43_STAT_UNINIT); + + /* Stop the microcode PSM. */ + b43_maskset32(dev, B43_MMIO_MACCTL, ~B43_MACCTL_PSM_RUN, + B43_MACCTL_PSM_JMP0); + + b43_dma_free(dev); + b43_pio_free(dev); + b43_chip_exit(dev); + dev->phy.ops->switch_analog(dev, 0); + if (dev->wl->current_beacon) { + dev_kfree_skb_any(dev->wl->current_beacon); + dev->wl->current_beacon = NULL; + } + + b43_device_disable(dev, 0); + b43_bus_may_powerdown(dev); +} + +/* Initialize a wireless core */ +static int b43_wireless_core_init(struct b43_wldev *dev) +{ + struct ssb_sprom *sprom = dev->dev->bus_sprom; + struct b43_phy *phy = &dev->phy; + int err; + u64 hf; + + B43_WARN_ON(b43_status(dev) != B43_STAT_UNINIT); + + err = b43_bus_powerup(dev, 0); + if (err) + goto out; + if (!b43_device_is_enabled(dev)) + b43_wireless_core_reset(dev, phy->gmode); + + /* Reset all data structures. */ + setup_struct_wldev_for_init(dev); + phy->ops->prepare_structs(dev); + + /* Enable IRQ routing to this device. */ + switch (dev->dev->bus_type) { +#ifdef CPTCFG_B43_BCMA + case B43_BUS_BCMA: + bcma_core_pci_irq_ctl(&dev->dev->bdev->bus->drv_pci[0], + dev->dev->bdev, true); + break; +#endif +#ifdef CPTCFG_B43_SSB + case B43_BUS_SSB: + ssb_pcicore_dev_irqvecs_enable(&dev->dev->sdev->bus->pcicore, + dev->dev->sdev); + break; +#endif + } + + b43_imcfglo_timeouts_workaround(dev); + b43_bluetooth_coext_disable(dev); + if (phy->ops->prepare_hardware) { + err = phy->ops->prepare_hardware(dev); + if (err) + goto err_busdown; + } + err = b43_chip_init(dev); + if (err) + goto err_busdown; + b43_shm_write16(dev, B43_SHM_SHARED, + B43_SHM_SH_WLCOREREV, dev->dev->core_rev); + hf = b43_hf_read(dev); + if (phy->type == B43_PHYTYPE_G) { + hf |= B43_HF_SYMW; + if (phy->rev == 1) + hf |= B43_HF_GDCW; + if (sprom->boardflags_lo & B43_BFL_PACTRL) + hf |= B43_HF_OFDMPABOOST; + } + if (phy->radio_ver == 0x2050) { + if (phy->radio_rev == 6) + hf |= B43_HF_4318TSSI; + if (phy->radio_rev < 6) + hf |= B43_HF_VCORECALC; + } + if (sprom->boardflags_lo & B43_BFL_XTAL_NOSLOW) + hf |= B43_HF_DSCRQ; /* Disable slowclock requests from ucode. */ +#ifdef CPTCFG_SSB_DRIVER_PCICORE + if (dev->dev->bus_type == B43_BUS_SSB && + dev->dev->sdev->bus->bustype == SSB_BUSTYPE_PCI && + dev->dev->sdev->bus->pcicore.dev->id.revision <= 10) + hf |= B43_HF_PCISCW; /* PCI slow clock workaround. */ +#endif + hf &= ~B43_HF_SKCFPUP; + b43_hf_write(dev, hf); + + b43_set_retry_limits(dev, B43_DEFAULT_SHORT_RETRY_LIMIT, + B43_DEFAULT_LONG_RETRY_LIMIT); + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_SFFBLIM, 3); + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_LFFBLIM, 2); + + /* Disable sending probe responses from firmware. + * Setting the MaxTime to one usec will always trigger + * a timeout, so we never send any probe resp. + * A timeout of zero is infinite. */ + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_PRMAXTIME, 1); + + b43_rate_memory_init(dev); + b43_set_phytxctl_defaults(dev); + + /* Minimum Contention Window */ + if (phy->type == B43_PHYTYPE_B) + b43_shm_write16(dev, B43_SHM_SCRATCH, B43_SHM_SC_MINCONT, 0x1F); + else + b43_shm_write16(dev, B43_SHM_SCRATCH, B43_SHM_SC_MINCONT, 0xF); + /* Maximum Contention Window */ + b43_shm_write16(dev, B43_SHM_SCRATCH, B43_SHM_SC_MAXCONT, 0x3FF); + + if (b43_bus_host_is_pcmcia(dev->dev) || + b43_bus_host_is_sdio(dev->dev)) { + dev->__using_pio_transfers = true; + err = b43_pio_init(dev); + } else if (dev->use_pio) { + b43warn(dev->wl, "Forced PIO by use_pio module parameter. " + "This should not be needed and will result in lower " + "performance.\n"); + dev->__using_pio_transfers = true; + err = b43_pio_init(dev); + } else { + dev->__using_pio_transfers = false; + err = b43_dma_init(dev); + } + if (err) + goto err_chip_exit; + b43_qos_init(dev); + b43_set_synth_pu_delay(dev, 1); + b43_bluetooth_coext_enable(dev); + + b43_bus_powerup(dev, !(sprom->boardflags_lo & B43_BFL_XTAL_NOSLOW)); + b43_upload_card_macaddress(dev); + b43_security_init(dev); + + ieee80211_wake_queues(dev->wl->hw); + + b43_set_status(dev, B43_STAT_INITIALIZED); + + /* Register HW RNG driver */ + b43_rng_init(dev->wl); + +out: + return err; + +err_chip_exit: + b43_chip_exit(dev); +err_busdown: + b43_bus_may_powerdown(dev); + B43_WARN_ON(b43_status(dev) != B43_STAT_UNINIT); + return err; +} + +static int b43_op_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + struct b43_wldev *dev; + int err = -EOPNOTSUPP; + + /* TODO: allow WDS/AP devices to coexist */ + + if (vif->type != NL80211_IFTYPE_AP && + vif->type != NL80211_IFTYPE_MESH_POINT && + vif->type != NL80211_IFTYPE_STATION && + vif->type != NL80211_IFTYPE_WDS && + vif->type != NL80211_IFTYPE_ADHOC) + return -EOPNOTSUPP; + + mutex_lock(&wl->mutex); + if (wl->operating) + goto out_mutex_unlock; + + b43dbg(wl, "Adding Interface type %d\n", vif->type); + + dev = wl->current_dev; + wl->operating = true; + wl->vif = vif; + wl->if_type = vif->type; + memcpy(wl->mac_addr, vif->addr, ETH_ALEN); + + b43_adjust_opmode(dev); + b43_set_pretbtt(dev); + b43_set_synth_pu_delay(dev, 0); + b43_upload_card_macaddress(dev); + + err = 0; + out_mutex_unlock: + mutex_unlock(&wl->mutex); + + if (err == 0) + b43_op_bss_info_changed(hw, vif, &vif->bss_conf, ~0); + + return err; +} + +static void b43_op_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + struct b43_wldev *dev = wl->current_dev; + + b43dbg(wl, "Removing Interface type %d\n", vif->type); + + mutex_lock(&wl->mutex); + + B43_WARN_ON(!wl->operating); + B43_WARN_ON(wl->vif != vif); + wl->vif = NULL; + + wl->operating = false; + + b43_adjust_opmode(dev); + memset(wl->mac_addr, 0, ETH_ALEN); + b43_upload_card_macaddress(dev); + + mutex_unlock(&wl->mutex); +} + +static int b43_op_start(struct ieee80211_hw *hw) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + struct b43_wldev *dev = wl->current_dev; + int did_init = 0; + int err = 0; + + /* Kill all old instance specific information to make sure + * the card won't use it in the short timeframe between start + * and mac80211 reconfiguring it. */ + memset(wl->bssid, 0, ETH_ALEN); + memset(wl->mac_addr, 0, ETH_ALEN); + wl->filter_flags = 0; + wl->radiotap_enabled = false; + b43_qos_clear(wl); + wl->beacon0_uploaded = false; + wl->beacon1_uploaded = false; + wl->beacon_templates_virgin = true; + wl->radio_enabled = true; + + mutex_lock(&wl->mutex); + + if (b43_status(dev) < B43_STAT_INITIALIZED) { + err = b43_wireless_core_init(dev); + if (err) + goto out_mutex_unlock; + did_init = 1; + } + + if (b43_status(dev) < B43_STAT_STARTED) { + err = b43_wireless_core_start(dev); + if (err) { + if (did_init) + b43_wireless_core_exit(dev); + goto out_mutex_unlock; + } + } + + /* XXX: only do if device doesn't support rfkill irq */ + wiphy_rfkill_start_polling(hw->wiphy); + + out_mutex_unlock: + mutex_unlock(&wl->mutex); + + /* + * Configuration may have been overwritten during initialization. + * Reload the configuration, but only if initialization was + * successful. Reloading the configuration after a failed init + * may hang the system. + */ + if (!err) + b43_op_config(hw, ~0); + + return err; +} + +static void b43_op_stop(struct ieee80211_hw *hw) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + struct b43_wldev *dev = wl->current_dev; + + cancel_work_sync(&(wl->beacon_update_trigger)); + + if (!dev) + goto out; + + mutex_lock(&wl->mutex); + if (b43_status(dev) >= B43_STAT_STARTED) { + dev = b43_wireless_core_stop(dev); + if (!dev) + goto out_unlock; + } + b43_wireless_core_exit(dev); + wl->radio_enabled = false; + +out_unlock: + mutex_unlock(&wl->mutex); +out: + cancel_work_sync(&(wl->txpower_adjust_work)); +} + +static int b43_op_beacon_set_tim(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, bool set) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + + /* FIXME: add locking */ + b43_update_templates(wl); + + return 0; +} + +static void b43_op_sta_notify(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum sta_notify_cmd notify_cmd, + struct ieee80211_sta *sta) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + + B43_WARN_ON(!vif || wl->vif != vif); +} + +static void b43_op_sw_scan_start_notifier(struct ieee80211_hw *hw) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + struct b43_wldev *dev; + + mutex_lock(&wl->mutex); + dev = wl->current_dev; + if (dev && (b43_status(dev) >= B43_STAT_INITIALIZED)) { + /* Disable CFP update during scan on other channels. */ + b43_hf_write(dev, b43_hf_read(dev) | B43_HF_SKCFPUP); + } + mutex_unlock(&wl->mutex); +} + +static void b43_op_sw_scan_complete_notifier(struct ieee80211_hw *hw) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + struct b43_wldev *dev; + + mutex_lock(&wl->mutex); + dev = wl->current_dev; + if (dev && (b43_status(dev) >= B43_STAT_INITIALIZED)) { + /* Re-enable CFP update. */ + b43_hf_write(dev, b43_hf_read(dev) & ~B43_HF_SKCFPUP); + } + mutex_unlock(&wl->mutex); +} + +static int b43_op_get_survey(struct ieee80211_hw *hw, int idx, + struct survey_info *survey) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + struct b43_wldev *dev = wl->current_dev; + struct ieee80211_conf *conf = &hw->conf; + + if (idx != 0) + return -ENOENT; + + survey->channel = conf->chandef.chan; + survey->filled = SURVEY_INFO_NOISE_DBM; + survey->noise = dev->stats.link_noise; + + return 0; +} + +static const struct ieee80211_ops b43_hw_ops = { + .tx = b43_op_tx, + .conf_tx = b43_op_conf_tx, + .add_interface = b43_op_add_interface, + .remove_interface = b43_op_remove_interface, + .config = b43_op_config, + .bss_info_changed = b43_op_bss_info_changed, + .configure_filter = b43_op_configure_filter, + .set_key = b43_op_set_key, + .update_tkip_key = b43_op_update_tkip_key, + .get_stats = b43_op_get_stats, + .get_tsf = b43_op_get_tsf, + .set_tsf = b43_op_set_tsf, + .start = b43_op_start, + .stop = b43_op_stop, + .set_tim = b43_op_beacon_set_tim, + .sta_notify = b43_op_sta_notify, + .sw_scan_start = b43_op_sw_scan_start_notifier, + .sw_scan_complete = b43_op_sw_scan_complete_notifier, + .get_survey = b43_op_get_survey, + .rfkill_poll = b43_rfkill_poll, +}; + +/* Hard-reset the chip. Do not call this directly. + * Use b43_controller_restart() + */ +static void b43_chip_reset(struct work_struct *work) +{ + struct b43_wldev *dev = + container_of(work, struct b43_wldev, restart_work); + struct b43_wl *wl = dev->wl; + int err = 0; + int prev_status; + + mutex_lock(&wl->mutex); + + prev_status = b43_status(dev); + /* Bring the device down... */ + if (prev_status >= B43_STAT_STARTED) { + dev = b43_wireless_core_stop(dev); + if (!dev) { + err = -ENODEV; + goto out; + } + } + if (prev_status >= B43_STAT_INITIALIZED) + b43_wireless_core_exit(dev); + + /* ...and up again. */ + if (prev_status >= B43_STAT_INITIALIZED) { + err = b43_wireless_core_init(dev); + if (err) + goto out; + } + if (prev_status >= B43_STAT_STARTED) { + err = b43_wireless_core_start(dev); + if (err) { + b43_wireless_core_exit(dev); + goto out; + } + } +out: + if (err) + wl->current_dev = NULL; /* Failed to init the dev. */ + mutex_unlock(&wl->mutex); + + if (err) { + b43err(wl, "Controller restart FAILED\n"); + return; + } + + /* reload configuration */ + b43_op_config(wl->hw, ~0); + if (wl->vif) + b43_op_bss_info_changed(wl->hw, wl->vif, &wl->vif->bss_conf, ~0); + + b43info(wl, "Controller restarted\n"); +} + +static int b43_setup_bands(struct b43_wldev *dev, + bool have_2ghz_phy, bool have_5ghz_phy) +{ + struct ieee80211_hw *hw = dev->wl->hw; + + if (have_2ghz_phy) + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &b43_band_2GHz; + if (dev->phy.type == B43_PHYTYPE_N) { + if (have_5ghz_phy) + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = &b43_band_5GHz_nphy; + } else { + if (have_5ghz_phy) + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = &b43_band_5GHz_aphy; + } + + dev->phy.supports_2ghz = have_2ghz_phy; + dev->phy.supports_5ghz = have_5ghz_phy; + + return 0; +} + +static void b43_wireless_core_detach(struct b43_wldev *dev) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31) + if (dev->dev->sdev->bus->bustype != SSB_BUSTYPE_SDIO) + compat_destroy_threaded_irq(&dev->irq_compat); +#endif + /* We release firmware that late to not be required to re-request + * is all the time when we reinit the core. */ + b43_release_firmware(dev); + b43_phy_free(dev); +} + +static int b43_wireless_core_attach(struct b43_wldev *dev) +{ + struct b43_wl *wl = dev->wl; + struct pci_dev *pdev = NULL; + int err; + u32 tmp; + bool have_2ghz_phy = false, have_5ghz_phy = false; + + /* Do NOT do any device initialization here. + * Do it in wireless_core_init() instead. + * This function is for gathering basic information about the HW, only. + * Also some structs may be set up here. But most likely you want to have + * that in core_init(), too. + */ + +#ifdef CPTCFG_B43_SSB + if (dev->dev->bus_type == B43_BUS_SSB && + dev->dev->sdev->bus->bustype == SSB_BUSTYPE_PCI) + pdev = dev->dev->sdev->bus->host_pci; +#endif + + err = b43_bus_powerup(dev, 0); + if (err) { + b43err(wl, "Bus powerup failed\n"); + goto out; + } + + /* Get the PHY type. */ + switch (dev->dev->bus_type) { +#ifdef CPTCFG_B43_BCMA + case B43_BUS_BCMA: + tmp = bcma_aread32(dev->dev->bdev, BCMA_IOST); + have_2ghz_phy = !!(tmp & B43_BCMA_IOST_2G_PHY); + have_5ghz_phy = !!(tmp & B43_BCMA_IOST_5G_PHY); + break; +#endif +#ifdef CPTCFG_B43_SSB + case B43_BUS_SSB: + if (dev->dev->core_rev >= 5) { + tmp = ssb_read32(dev->dev->sdev, SSB_TMSHIGH); + have_2ghz_phy = !!(tmp & B43_TMSHIGH_HAVE_2GHZ_PHY); + have_5ghz_phy = !!(tmp & B43_TMSHIGH_HAVE_5GHZ_PHY); + } else + B43_WARN_ON(1); + break; +#endif + } + + dev->phy.gmode = have_2ghz_phy; + dev->phy.radio_on = true; + b43_wireless_core_reset(dev, dev->phy.gmode); + + err = b43_phy_versioning(dev); + if (err) + goto err_powerdown; + /* Check if this device supports multiband. */ + if (!pdev || + (pdev->device != 0x4312 && + pdev->device != 0x4319 && pdev->device != 0x4324)) { + /* No multiband support. */ + have_2ghz_phy = false; + have_5ghz_phy = false; + switch (dev->phy.type) { + case B43_PHYTYPE_A: + have_5ghz_phy = true; + break; + case B43_PHYTYPE_LP: //FIXME not always! +#if 0 //FIXME enabling 5GHz causes a NULL pointer dereference + have_5ghz_phy = 1; +#endif + case B43_PHYTYPE_G: + case B43_PHYTYPE_N: + case B43_PHYTYPE_HT: + case B43_PHYTYPE_LCN: + have_2ghz_phy = true; + break; + default: + B43_WARN_ON(1); + } + } + if (dev->phy.type == B43_PHYTYPE_A) { + /* FIXME */ + b43err(wl, "IEEE 802.11a devices are unsupported\n"); + err = -EOPNOTSUPP; + goto err_powerdown; + } + if (1 /* disable A-PHY */) { + /* FIXME: For now we disable the A-PHY on multi-PHY devices. */ + if (dev->phy.type != B43_PHYTYPE_N && + dev->phy.type != B43_PHYTYPE_LP) { + have_2ghz_phy = true; + have_5ghz_phy = false; + } + } + + err = b43_phy_allocate(dev); + if (err) + goto err_powerdown; + + dev->phy.gmode = have_2ghz_phy; + b43_wireless_core_reset(dev, dev->phy.gmode); + + err = b43_validate_chipaccess(dev); + if (err) + goto err_phy_free; + err = b43_setup_bands(dev, have_2ghz_phy, have_5ghz_phy); + if (err) + goto err_phy_free; + + /* Now set some default "current_dev" */ + if (!wl->current_dev) + wl->current_dev = dev; + INIT_WORK(&dev->restart_work, b43_chip_reset); + + dev->phy.ops->switch_analog(dev, 0); + b43_device_disable(dev, 0); + b43_bus_may_powerdown(dev); + +out: + return err; + +err_phy_free: + b43_phy_free(dev); +err_powerdown: + b43_bus_may_powerdown(dev); + return err; +} + +static void b43_one_core_detach(struct b43_bus_dev *dev) +{ + struct b43_wldev *wldev; + struct b43_wl *wl; + + /* Do not cancel ieee80211-workqueue based work here. + * See comment in b43_remove(). */ + + wldev = b43_bus_get_wldev(dev); + wl = wldev->wl; + b43_debugfs_remove_device(wldev); + b43_wireless_core_detach(wldev); + list_del(&wldev->list); + wl->nr_devs--; + b43_bus_set_wldev(dev, NULL); + kfree(wldev); +} + +static int b43_one_core_attach(struct b43_bus_dev *dev, struct b43_wl *wl) +{ + struct b43_wldev *wldev; + int err = -ENOMEM; + + wldev = kzalloc(sizeof(*wldev), GFP_KERNEL); + if (!wldev) + goto out; + + wldev->use_pio = b43_modparam_pio; + wldev->dev = dev; + wldev->wl = wl; + b43_set_status(wldev, B43_STAT_UNINIT); + wldev->bad_frames_preempt = modparam_bad_frames_preempt; + INIT_LIST_HEAD(&wldev->list); + + err = b43_wireless_core_attach(wldev); + if (err) + goto err_kfree_wldev; + + list_add(&wldev->list, &wl->devlist); + wl->nr_devs++; + b43_bus_set_wldev(dev, wldev); + b43_debugfs_add_device(wldev); + + out: + return err; + + err_kfree_wldev: + kfree(wldev); + return err; +} + +#define IS_PDEV(pdev, _vendor, _device, _subvendor, _subdevice) ( \ + (pdev->vendor == PCI_VENDOR_ID_##_vendor) && \ + (pdev->device == _device) && \ + (pdev->subsystem_vendor == PCI_VENDOR_ID_##_subvendor) && \ + (pdev->subsystem_device == _subdevice) ) + +static void b43_sprom_fixup(struct ssb_bus *bus) +{ + struct pci_dev *pdev; + + /* boardflags workarounds */ + if (bus->boardinfo.vendor == SSB_BOARDVENDOR_DELL && + bus->chip_id == 0x4301 && bus->sprom.board_rev == 0x74) + bus->sprom.boardflags_lo |= B43_BFL_BTCOEXIST; + if (bus->boardinfo.vendor == PCI_VENDOR_ID_APPLE && + bus->boardinfo.type == 0x4E && bus->sprom.board_rev > 0x40) + bus->sprom.boardflags_lo |= B43_BFL_PACTRL; + if (bus->bustype == SSB_BUSTYPE_PCI) { + pdev = bus->host_pci; + if (IS_PDEV(pdev, BROADCOM, 0x4318, ASUSTEK, 0x100F) || + IS_PDEV(pdev, BROADCOM, 0x4320, DELL, 0x0003) || + IS_PDEV(pdev, BROADCOM, 0x4320, HP, 0x12f8) || + IS_PDEV(pdev, BROADCOM, 0x4320, LINKSYS, 0x0015) || + IS_PDEV(pdev, BROADCOM, 0x4320, LINKSYS, 0x0014) || + IS_PDEV(pdev, BROADCOM, 0x4320, LINKSYS, 0x0013) || + IS_PDEV(pdev, BROADCOM, 0x4320, MOTOROLA, 0x7010)) + bus->sprom.boardflags_lo &= ~B43_BFL_BTCOEXIST; + } +} + +static void b43_wireless_exit(struct b43_bus_dev *dev, struct b43_wl *wl) +{ + struct ieee80211_hw *hw = wl->hw; + + ssb_set_devtypedata(dev->sdev, NULL); + ieee80211_free_hw(hw); +} + +static struct b43_wl *b43_wireless_init(struct b43_bus_dev *dev) +{ + struct ssb_sprom *sprom = dev->bus_sprom; + struct ieee80211_hw *hw; + struct b43_wl *wl; + char chip_name[6]; + int queue_num; + + hw = ieee80211_alloc_hw(sizeof(*wl), &b43_hw_ops); + if (!hw) { + b43err(NULL, "Could not allocate ieee80211 device\n"); + return ERR_PTR(-ENOMEM); + } + wl = hw_to_b43_wl(hw); + + /* fill hw info */ + hw->flags = IEEE80211_HW_RX_INCLUDES_FCS | + IEEE80211_HW_SIGNAL_DBM; + + hw->wiphy->interface_modes = + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_MESH_POINT) | + BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_WDS) | + BIT(NL80211_IFTYPE_ADHOC); + + hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN; + + wl->hw_registred = false; + hw->max_rates = 2; + SET_IEEE80211_DEV(hw, dev->dev); + if (is_valid_ether_addr(sprom->et1mac)) + SET_IEEE80211_PERM_ADDR(hw, sprom->et1mac); + else + SET_IEEE80211_PERM_ADDR(hw, sprom->il0mac); + + /* Initialize struct b43_wl */ + wl->hw = hw; + mutex_init(&wl->mutex); + spin_lock_init(&wl->hardirq_lock); + INIT_LIST_HEAD(&wl->devlist); + INIT_WORK(&wl->beacon_update_trigger, b43_beacon_update_trigger_work); + INIT_WORK(&wl->txpower_adjust_work, b43_phy_txpower_adjust_work); + INIT_WORK(&wl->tx_work, b43_tx_work); + + /* Initialize queues and flags. */ + for (queue_num = 0; queue_num < B43_QOS_QUEUE_NUM; queue_num++) { + skb_queue_head_init(&wl->tx_queue[queue_num]); + wl->tx_queue_stopped[queue_num] = 0; + } + + snprintf(chip_name, ARRAY_SIZE(chip_name), + (dev->chip_id > 0x9999) ? "%d" : "%04X", dev->chip_id); + b43info(wl, "Broadcom %s WLAN found (core revision %u)\n", chip_name, + dev->core_rev); + return wl; +} + +#ifdef CPTCFG_B43_BCMA +static int b43_bcma_probe(struct bcma_device *core) +{ + struct b43_bus_dev *dev; + struct b43_wl *wl; + int err; + + dev = b43_bus_dev_bcma_init(core); + if (!dev) + return -ENODEV; + + wl = b43_wireless_init(dev); + if (IS_ERR(wl)) { + err = PTR_ERR(wl); + goto bcma_out; + } + + err = b43_one_core_attach(dev, wl); + if (err) + goto bcma_err_wireless_exit; + + /* setup and start work to load firmware */ + INIT_WORK(&wl->firmware_load, b43_request_firmware); + schedule_work(&wl->firmware_load); + +bcma_out: + return err; + +bcma_err_wireless_exit: + ieee80211_free_hw(wl->hw); + return err; +} + +static void b43_bcma_remove(struct bcma_device *core) +{ + struct b43_wldev *wldev = bcma_get_drvdata(core); + struct b43_wl *wl = wldev->wl; + + /* We must cancel any work here before unregistering from ieee80211, + * as the ieee80211 unreg will destroy the workqueue. */ + cancel_work_sync(&wldev->restart_work); + cancel_work_sync(&wl->firmware_load); + + B43_WARN_ON(!wl); + if (!wldev->fw.ucode.data) + return; /* NULL if firmware never loaded */ + if (wl->current_dev == wldev && wl->hw_registred) { + b43_leds_stop(wldev); + ieee80211_unregister_hw(wl->hw); + } + + b43_one_core_detach(wldev->dev); + + b43_leds_unregister(wl); + + ieee80211_free_hw(wl->hw); +} + +static struct bcma_driver b43_bcma_driver = { + .name = KBUILD_MODNAME, + .id_table = b43_bcma_tbl, + .probe = b43_bcma_probe, + .remove = b43_bcma_remove, +}; +#endif + +#ifdef CPTCFG_B43_SSB +static +int b43_ssb_probe(struct ssb_device *sdev, const struct ssb_device_id *id) +{ + struct b43_bus_dev *dev; + struct b43_wl *wl; + int err; + int first = 0; + + dev = b43_bus_dev_ssb_init(sdev); + if (!dev) + return -ENOMEM; + + wl = ssb_get_devtypedata(sdev); + if (!wl) { + /* Probing the first core. Must setup common struct b43_wl */ + first = 1; + b43_sprom_fixup(sdev->bus); + wl = b43_wireless_init(dev); + if (IS_ERR(wl)) { + err = PTR_ERR(wl); + goto out; + } + ssb_set_devtypedata(sdev, wl); + B43_WARN_ON(ssb_get_devtypedata(sdev) != wl); + } + err = b43_one_core_attach(dev, wl); + if (err) + goto err_wireless_exit; + + /* setup and start work to load firmware */ + INIT_WORK(&wl->firmware_load, b43_request_firmware); + schedule_work(&wl->firmware_load); + + out: + return err; + + err_wireless_exit: + if (first) + b43_wireless_exit(dev, wl); + return err; +} + +static void b43_ssb_remove(struct ssb_device *sdev) +{ + struct b43_wl *wl = ssb_get_devtypedata(sdev); + struct b43_wldev *wldev = ssb_get_drvdata(sdev); + struct b43_bus_dev *dev = wldev->dev; + + /* We must cancel any work here before unregistering from ieee80211, + * as the ieee80211 unreg will destroy the workqueue. */ + cancel_work_sync(&wldev->restart_work); + cancel_work_sync(&wl->firmware_load); + + B43_WARN_ON(!wl); + if (!wldev->fw.ucode.data) + return; /* NULL if firmware never loaded */ + if (wl->current_dev == wldev && wl->hw_registred) { + b43_leds_stop(wldev); + ieee80211_unregister_hw(wl->hw); + } + + b43_one_core_detach(dev); + + if (list_empty(&wl->devlist)) { + b43_leds_unregister(wl); + /* Last core on the chip unregistered. + * We can destroy common struct b43_wl. + */ + b43_wireless_exit(dev, wl); + } +} + +static struct ssb_driver b43_ssb_driver = { + .name = KBUILD_MODNAME, + .id_table = b43_ssb_tbl, + .probe = b43_ssb_probe, + .remove = b43_ssb_remove, +}; +#endif /* CPTCFG_B43_SSB */ + +/* Perform a hardware reset. This can be called from any context. */ +void b43_controller_restart(struct b43_wldev *dev, const char *reason) +{ + /* Must avoid requeueing, if we are in shutdown. */ + if (b43_status(dev) < B43_STAT_INITIALIZED) + return; + b43info(dev->wl, "Controller RESET (%s) ...\n", reason); + ieee80211_queue_work(dev->wl->hw, &dev->restart_work); +} + +static void b43_print_driverinfo(void) +{ + const char *feat_pci = "", *feat_pcmcia = "", *feat_nphy = "", + *feat_leds = "", *feat_sdio = ""; + +#ifdef CPTCFG_B43_PCI_AUTOSELECT + feat_pci = "P"; +#endif +#ifdef CPTCFG_B43_PCMCIA + feat_pcmcia = "M"; +#endif +#ifdef CPTCFG_B43_PHY_N + feat_nphy = "N"; +#endif +#ifdef CPTCFG_B43_LEDS + feat_leds = "L"; +#endif +#ifdef CPTCFG_B43_SDIO + feat_sdio = "S"; +#endif + printk(KERN_INFO "Broadcom 43xx driver loaded " + "[ Features: %s%s%s%s%s ]\n", + feat_pci, feat_pcmcia, feat_nphy, + feat_leds, feat_sdio); +} + +static int __init b43_init(void) +{ + int err; + + b43_debugfs_init(); + err = b43_pcmcia_init(); + if (err) + goto err_dfs_exit; + err = b43_sdio_init(); + if (err) + goto err_pcmcia_exit; +#ifdef CPTCFG_B43_BCMA + err = bcma_driver_register(&b43_bcma_driver); + if (err) + goto err_sdio_exit; +#endif +#ifdef CPTCFG_B43_SSB + err = ssb_driver_register(&b43_ssb_driver); + if (err) + goto err_bcma_driver_exit; +#endif + b43_print_driverinfo(); + + return err; + +#ifdef CPTCFG_B43_SSB +err_bcma_driver_exit: +#endif +#ifdef CPTCFG_B43_BCMA + bcma_driver_unregister(&b43_bcma_driver); +err_sdio_exit: +#endif + b43_sdio_exit(); +err_pcmcia_exit: + b43_pcmcia_exit(); +err_dfs_exit: + b43_debugfs_exit(); + return err; +} + +static void __exit b43_exit(void) +{ +#ifdef CPTCFG_B43_SSB + ssb_driver_unregister(&b43_ssb_driver); +#endif +#ifdef CPTCFG_B43_BCMA + bcma_driver_unregister(&b43_bcma_driver); +#endif + b43_sdio_exit(); + b43_pcmcia_exit(); + b43_debugfs_exit(); +} + +module_init(b43_init) +module_exit(b43_exit) diff --git a/drivers/net/wireless/b43/main.h b/drivers/net/wireless/b43/main.h new file mode 100644 index 0000000..abac25e --- /dev/null +++ b/drivers/net/wireless/b43/main.h @@ -0,0 +1,144 @@ +/* + + Broadcom B43 wireless driver + + Copyright (c) 2005 Martin Langer , + Stefano Brivio + Michael Buesch + Danny van Dyk + Andreas Jaggi + + Some parts of the code in this file are derived from the ipw2200 + driver Copyright(c) 2003 - 2004 Intel Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#ifndef B43_MAIN_H_ +#define B43_MAIN_H_ + +#include "b43.h" + +#define P4D_BYT3S(magic, nr_bytes) u8 __p4dding##magic[nr_bytes] +#define P4D_BYTES(line, nr_bytes) P4D_BYT3S(line, nr_bytes) +/* Magic helper macro to pad structures. Ignore those above. It's magic. */ +#define PAD_BYTES(nr_bytes) P4D_BYTES( __LINE__ , (nr_bytes)) + + +extern int b43_modparam_verbose; + +/* Logmessage verbosity levels. Update the b43_modparam_verbose helptext, if + * you add or remove levels. */ +enum b43_verbosity { + B43_VERBOSITY_ERROR, + B43_VERBOSITY_WARN, + B43_VERBOSITY_INFO, + B43_VERBOSITY_DEBUG, + __B43_VERBOSITY_AFTERLAST, /* keep last */ + + B43_VERBOSITY_MAX = __B43_VERBOSITY_AFTERLAST - 1, +#if B43_DEBUG + B43_VERBOSITY_DEFAULT = B43_VERBOSITY_DEBUG, +#else + B43_VERBOSITY_DEFAULT = B43_VERBOSITY_INFO, +#endif +}; + + +/* Lightweight function to convert a frequency (in Mhz) to a channel number. */ +static inline u8 b43_freq_to_channel_5ghz(int freq) +{ + return ((freq - 5000) / 5); +} +static inline u8 b43_freq_to_channel_2ghz(int freq) +{ + u8 channel; + + if (freq == 2484) + channel = 14; + else + channel = (freq - 2407) / 5; + + return channel; +} + +/* Lightweight function to convert a channel number to a frequency (in Mhz). */ +static inline int b43_channel_to_freq_5ghz(u8 channel) +{ + return (5000 + (5 * channel)); +} +static inline int b43_channel_to_freq_2ghz(u8 channel) +{ + int freq; + + if (channel == 14) + freq = 2484; + else + freq = 2407 + (5 * channel); + + return freq; +} + +static inline int b43_is_cck_rate(int rate) +{ + return (rate == B43_CCK_RATE_1MB || + rate == B43_CCK_RATE_2MB || + rate == B43_CCK_RATE_5MB || rate == B43_CCK_RATE_11MB); +} + +static inline int b43_is_ofdm_rate(int rate) +{ + return !b43_is_cck_rate(rate); +} + +u8 b43_ieee80211_antenna_sanitize(struct b43_wldev *dev, + u8 antenna_nr); + +void b43_tsf_read(struct b43_wldev *dev, u64 * tsf); +void b43_tsf_write(struct b43_wldev *dev, u64 tsf); + +u32 b43_shm_read32(struct b43_wldev *dev, u16 routing, u16 offset); +u16 b43_shm_read16(struct b43_wldev *dev, u16 routing, u16 offset); +void b43_shm_write32(struct b43_wldev *dev, u16 routing, u16 offset, u32 value); +void b43_shm_write16(struct b43_wldev *dev, u16 routing, u16 offset, u16 value); + +u64 b43_hf_read(struct b43_wldev *dev); +void b43_hf_write(struct b43_wldev *dev, u64 value); + +void b43_dummy_transmission(struct b43_wldev *dev, bool ofdm, bool pa_on); + +void b43_wireless_core_reset(struct b43_wldev *dev, bool gmode); + +void b43_controller_restart(struct b43_wldev *dev, const char *reason); + +#define B43_PS_ENABLED (1 << 0) /* Force enable hardware power saving */ +#define B43_PS_DISABLED (1 << 1) /* Force disable hardware power saving */ +#define B43_PS_AWAKE (1 << 2) /* Force device awake */ +#define B43_PS_ASLEEP (1 << 3) /* Force device asleep */ +void b43_power_saving_ctl_bits(struct b43_wldev *dev, unsigned int ps_flags); + +void b43_mac_suspend(struct b43_wldev *dev); +void b43_mac_enable(struct b43_wldev *dev); +void b43_mac_phy_clock_set(struct b43_wldev *dev, bool on); + + +struct b43_request_fw_context; +int b43_do_request_fw(struct b43_request_fw_context *ctx, const char *name, + struct b43_firmware_file *fw, bool async); +void b43_do_release_fw(struct b43_firmware_file *fw); + +#endif /* B43_MAIN_H_ */ diff --git a/drivers/net/wireless/b43/pcmcia.c b/drivers/net/wireless/b43/pcmcia.c new file mode 100644 index 0000000..48f96a4 --- /dev/null +++ b/drivers/net/wireless/b43/pcmcia.c @@ -0,0 +1,180 @@ +/* + + Broadcom B43 wireless driver + + Copyright (c) 2007 Michael Buesch + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "pcmcia.h" + +#include +#include +#include + +#include +#include +#include +#include + + +static const struct pcmcia_device_id b43_pcmcia_tbl[] = { + PCMCIA_DEVICE_MANF_CARD(0x2D0, 0x448), + PCMCIA_DEVICE_MANF_CARD(0x2D0, 0x476), + PCMCIA_DEVICE_NULL, +}; + +MODULE_DEVICE_TABLE(pcmcia, b43_pcmcia_tbl); + +#ifdef CONFIG_PM +static int b43_pcmcia_suspend(struct pcmcia_device *dev) +{ + struct ssb_bus *ssb = dev->priv; + + return ssb_bus_suspend(ssb); +} + +static int b43_pcmcia_resume(struct pcmcia_device *dev) +{ + struct ssb_bus *ssb = dev->priv; + + return ssb_bus_resume(ssb); +} +#else /* CONFIG_PM */ +# define b43_pcmcia_suspend NULL +# define b43_pcmcia_resume NULL +#endif /* CONFIG_PM */ + +static int b43_pcmcia_probe(struct pcmcia_device *dev) +{ + struct ssb_bus *ssb; +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37)) + win_req_t win; +#endif + int err = -ENOMEM; + int res = 0; + + ssb = kzalloc(sizeof(*ssb), GFP_KERNEL); + if (!ssb) + goto out_error; + + err = -ENODEV; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)) + dev->config_flags |= CONF_ENABLE_IRQ; + + dev->resource[2]->flags |= WIN_ENABLE | WIN_DATA_WIDTH_16 | + WIN_USE_WAIT; + dev->resource[2]->start = 0; + dev->resource[2]->end = SSB_CORE_SIZE; + res = pcmcia_request_window(dev, dev->resource[2], 250); +#else + dev->conf.Attributes = CONF_ENABLE_IRQ; + dev->conf.IntType = INT_MEMORY_AND_IO; + + win.Attributes = WIN_ENABLE | WIN_DATA_WIDTH_16 | + WIN_USE_WAIT; + win.Base = 0; + win.Size = SSB_CORE_SIZE; + win.AccessSpeed = 250; + res = pcmcia_request_window(dev, &win, &dev->win); +#endif + if (res != 0) + goto err_kfree_ssb; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)) + res = pcmcia_map_mem_page(dev, dev->resource[2], 0); +#else + res = pcmcia_map_mem_page(dev, dev->win, 0); +#endif + if (res != 0) + goto err_disable; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + if (!dev->irq) +#else + dev->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING; + dev->irq.Handler = NULL; /* The handler is registered later. */ + res = pcmcia_request_irq(dev, &dev->irq); + if (res != 0) +#endif + goto err_disable; + + res = pcmcia_enable_device(dev); + if (res != 0) + goto err_disable; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)) + err = ssb_bus_pcmciabus_register(ssb, dev, dev->resource[2]->start); +#else + err = ssb_bus_pcmciabus_register(ssb, dev, win.Base); +#endif + if (err) + goto err_disable; + dev->priv = ssb; + + return 0; + +err_disable: + pcmcia_disable_device(dev); +err_kfree_ssb: + kfree(ssb); +out_error: + printk(KERN_ERR "b43-pcmcia: Initialization failed (%d, %d)\n", + res, err); + return err; +} + +static void b43_pcmcia_remove(struct pcmcia_device *dev) +{ + struct ssb_bus *ssb = dev->priv; + + ssb_bus_unregister(ssb); + pcmcia_disable_device(dev); + kfree(ssb); + dev->priv = NULL; +} + +static struct pcmcia_driver b43_pcmcia_driver = { + .owner = THIS_MODULE, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)) + .name = "b43-pcmcia", +#else + .drv = { + .name = "b43-pcmcia", + }, +#endif + .id_table = b43_pcmcia_tbl, + .probe = b43_pcmcia_probe, + .remove = b43_pcmcia_remove, + .suspend = b43_pcmcia_suspend, + .resume = b43_pcmcia_resume, +}; + +/* + * These are not module init/exit functions! + * The module_pcmcia_driver() helper cannot be used here. + */ +int b43_pcmcia_init(void) +{ + return pcmcia_register_driver(&b43_pcmcia_driver); +} + +void b43_pcmcia_exit(void) +{ + pcmcia_unregister_driver(&b43_pcmcia_driver); +} diff --git a/drivers/net/wireless/b43/pcmcia.h b/drivers/net/wireless/b43/pcmcia.h new file mode 100644 index 0000000..d8253d0 --- /dev/null +++ b/drivers/net/wireless/b43/pcmcia.h @@ -0,0 +1,20 @@ +#ifndef B43_PCMCIA_H_ +#define B43_PCMCIA_H_ + +#ifdef CPTCFG_B43_PCMCIA + +int b43_pcmcia_init(void); +void b43_pcmcia_exit(void); + +#else /* CPTCFG_B43_PCMCIA */ + +static inline int b43_pcmcia_init(void) +{ + return 0; +} +static inline void b43_pcmcia_exit(void) +{ +} + +#endif /* CPTCFG_B43_PCMCIA */ +#endif /* B43_PCMCIA_H_ */ diff --git a/drivers/net/wireless/b43/phy_a.c b/drivers/net/wireless/b43/phy_a.c new file mode 100644 index 0000000..a6c3810 --- /dev/null +++ b/drivers/net/wireless/b43/phy_a.c @@ -0,0 +1,595 @@ +/* + + Broadcom B43 wireless driver + IEEE 802.11a PHY driver + + Copyright (c) 2005 Martin Langer , + Copyright (c) 2005-2007 Stefano Brivio + Copyright (c) 2005-2008 Michael Buesch + Copyright (c) 2005, 2006 Danny van Dyk + Copyright (c) 2005, 2006 Andreas Jaggi + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include + +#include "b43.h" +#include "phy_a.h" +#include "phy_common.h" +#include "wa.h" +#include "tables.h" +#include "main.h" + + +/* Get the freq, as it has to be written to the device. */ +static inline u16 channel2freq_a(u8 channel) +{ + B43_WARN_ON(channel > 200); + + return (5000 + 5 * channel); +} + +static inline u16 freq_r3A_value(u16 frequency) +{ + u16 value; + + if (frequency < 5091) + value = 0x0040; + else if (frequency < 5321) + value = 0x0000; + else if (frequency < 5806) + value = 0x0080; + else + value = 0x0040; + + return value; +} + +#if 0 +/* This function converts a TSSI value to dBm in Q5.2 */ +static s8 b43_aphy_estimate_power_out(struct b43_wldev *dev, s8 tssi) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_a *aphy = phy->a; + s8 dbm = 0; + s32 tmp; + + tmp = (aphy->tgt_idle_tssi - aphy->cur_idle_tssi + tssi); + tmp += 0x80; + tmp = clamp_val(tmp, 0x00, 0xFF); + dbm = aphy->tssi2dbm[tmp]; + //TODO: There's a FIXME on the specs + + return dbm; +} +#endif + +static void b43_radio_set_tx_iq(struct b43_wldev *dev) +{ + static const u8 data_high[5] = { 0x00, 0x40, 0x80, 0x90, 0xD0 }; + static const u8 data_low[5] = { 0x00, 0x01, 0x05, 0x06, 0x0A }; + u16 tmp = b43_radio_read16(dev, 0x001E); + int i, j; + + for (i = 0; i < 5; i++) { + for (j = 0; j < 5; j++) { + if (tmp == (data_high[i] << 4 | data_low[j])) { + b43_phy_write(dev, 0x0069, + (i - j) << 8 | 0x00C0); + return; + } + } + } +} + +static void aphy_channel_switch(struct b43_wldev *dev, unsigned int channel) +{ + u16 freq, r8, tmp; + + freq = channel2freq_a(channel); + + r8 = b43_radio_read16(dev, 0x0008); + b43_write16(dev, 0x03F0, freq); + b43_radio_write16(dev, 0x0008, r8); + + //TODO: write max channel TX power? to Radio 0x2D + tmp = b43_radio_read16(dev, 0x002E); + tmp &= 0x0080; + //TODO: OR tmp with the Power out estimation for this channel? + b43_radio_write16(dev, 0x002E, tmp); + + if (freq >= 4920 && freq <= 5500) { + /* + * r8 = (((freq * 15 * 0xE1FC780F) >> 32) / 29) & 0x0F; + * = (freq * 0.025862069 + */ + r8 = 3 * freq / 116; /* is equal to r8 = freq * 0.025862 */ + } + b43_radio_write16(dev, 0x0007, (r8 << 4) | r8); + b43_radio_write16(dev, 0x0020, (r8 << 4) | r8); + b43_radio_write16(dev, 0x0021, (r8 << 4) | r8); + b43_radio_maskset(dev, 0x0022, 0x000F, (r8 << 4)); + b43_radio_write16(dev, 0x002A, (r8 << 4)); + b43_radio_write16(dev, 0x002B, (r8 << 4)); + b43_radio_maskset(dev, 0x0008, 0x00F0, (r8 << 4)); + b43_radio_maskset(dev, 0x0029, 0xFF0F, 0x00B0); + b43_radio_write16(dev, 0x0035, 0x00AA); + b43_radio_write16(dev, 0x0036, 0x0085); + b43_radio_maskset(dev, 0x003A, 0xFF20, freq_r3A_value(freq)); + b43_radio_mask(dev, 0x003D, 0x00FF); + b43_radio_maskset(dev, 0x0081, 0xFF7F, 0x0080); + b43_radio_mask(dev, 0x0035, 0xFFEF); + b43_radio_maskset(dev, 0x0035, 0xFFEF, 0x0010); + b43_radio_set_tx_iq(dev); + //TODO: TSSI2dbm workaround +//FIXME b43_phy_xmitpower(dev); +} + +static void b43_radio_init2060(struct b43_wldev *dev) +{ + b43_radio_write16(dev, 0x0004, 0x00C0); + b43_radio_write16(dev, 0x0005, 0x0008); + b43_radio_write16(dev, 0x0009, 0x0040); + b43_radio_write16(dev, 0x0005, 0x00AA); + b43_radio_write16(dev, 0x0032, 0x008F); + b43_radio_write16(dev, 0x0006, 0x008F); + b43_radio_write16(dev, 0x0034, 0x008F); + b43_radio_write16(dev, 0x002C, 0x0007); + b43_radio_write16(dev, 0x0082, 0x0080); + b43_radio_write16(dev, 0x0080, 0x0000); + b43_radio_write16(dev, 0x003F, 0x00DA); + b43_radio_mask(dev, 0x0005, ~0x0008); + b43_radio_mask(dev, 0x0081, ~0x0010); + b43_radio_mask(dev, 0x0081, ~0x0020); + b43_radio_mask(dev, 0x0081, ~0x0020); + msleep(1); /* delay 400usec */ + + b43_radio_maskset(dev, 0x0081, ~0x0020, 0x0010); + msleep(1); /* delay 400usec */ + + b43_radio_maskset(dev, 0x0005, ~0x0008, 0x0008); + b43_radio_mask(dev, 0x0085, ~0x0010); + b43_radio_mask(dev, 0x0005, ~0x0008); + b43_radio_mask(dev, 0x0081, ~0x0040); + b43_radio_maskset(dev, 0x0081, ~0x0040, 0x0040); + b43_radio_write16(dev, 0x0005, + (b43_radio_read16(dev, 0x0081) & ~0x0008) | 0x0008); + b43_phy_write(dev, 0x0063, 0xDDC6); + b43_phy_write(dev, 0x0069, 0x07BE); + b43_phy_write(dev, 0x006A, 0x0000); + + aphy_channel_switch(dev, dev->phy.ops->get_default_chan(dev)); + + msleep(1); +} + +static void b43_phy_rssiagc(struct b43_wldev *dev, u8 enable) +{ + int i; + + if (dev->phy.rev < 3) { + if (enable) + for (i = 0; i < B43_TAB_RSSIAGC1_SIZE; i++) { + b43_ofdmtab_write16(dev, + B43_OFDMTAB_LNAHPFGAIN1, i, 0xFFF8); + b43_ofdmtab_write16(dev, + B43_OFDMTAB_WRSSI, i, 0xFFF8); + } + else + for (i = 0; i < B43_TAB_RSSIAGC1_SIZE; i++) { + b43_ofdmtab_write16(dev, + B43_OFDMTAB_LNAHPFGAIN1, i, b43_tab_rssiagc1[i]); + b43_ofdmtab_write16(dev, + B43_OFDMTAB_WRSSI, i, b43_tab_rssiagc1[i]); + } + } else { + if (enable) + for (i = 0; i < B43_TAB_RSSIAGC1_SIZE; i++) + b43_ofdmtab_write16(dev, + B43_OFDMTAB_WRSSI, i, 0x0820); + else + for (i = 0; i < B43_TAB_RSSIAGC2_SIZE; i++) + b43_ofdmtab_write16(dev, + B43_OFDMTAB_WRSSI, i, b43_tab_rssiagc2[i]); + } +} + +static void b43_phy_ww(struct b43_wldev *dev) +{ + u16 b, curr_s, best_s = 0xFFFF; + int i; + + b43_phy_mask(dev, B43_PHY_CRS0, ~B43_PHY_CRS0_EN); + b43_phy_set(dev, B43_PHY_OFDM(0x1B), 0x1000); + b43_phy_maskset(dev, B43_PHY_OFDM(0x82), 0xF0FF, 0x0300); + b43_radio_set(dev, 0x0009, 0x0080); + b43_radio_maskset(dev, 0x0012, 0xFFFC, 0x0002); + b43_wa_initgains(dev); + b43_phy_write(dev, B43_PHY_OFDM(0xBA), 0x3ED5); + b = b43_phy_read(dev, B43_PHY_PWRDOWN); + b43_phy_write(dev, B43_PHY_PWRDOWN, (b & 0xFFF8) | 0x0005); + b43_radio_set(dev, 0x0004, 0x0004); + for (i = 0x10; i <= 0x20; i++) { + b43_radio_write16(dev, 0x0013, i); + curr_s = b43_phy_read(dev, B43_PHY_OTABLEQ) & 0x00FF; + if (!curr_s) { + best_s = 0x0000; + break; + } else if (curr_s >= 0x0080) + curr_s = 0x0100 - curr_s; + if (curr_s < best_s) + best_s = curr_s; + } + b43_phy_write(dev, B43_PHY_PWRDOWN, b); + b43_radio_mask(dev, 0x0004, 0xFFFB); + b43_radio_write16(dev, 0x0013, best_s); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC1_R1, 0, 0xFFEC); + b43_phy_write(dev, B43_PHY_OFDM(0xB7), 0x1E80); + b43_phy_write(dev, B43_PHY_OFDM(0xB6), 0x1C00); + b43_phy_write(dev, B43_PHY_OFDM(0xB5), 0x0EC0); + b43_phy_write(dev, B43_PHY_OFDM(0xB2), 0x00C0); + b43_phy_write(dev, B43_PHY_OFDM(0xB9), 0x1FFF); + b43_phy_maskset(dev, B43_PHY_OFDM(0xBB), 0xF000, 0x0053); + b43_phy_maskset(dev, B43_PHY_OFDM61, 0xFE1F, 0x0120); + b43_phy_maskset(dev, B43_PHY_OFDM(0x13), 0x0FFF, 0x3000); + b43_phy_maskset(dev, B43_PHY_OFDM(0x14), 0x0FFF, 0x3000); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC1, 6, 0x0017); + for (i = 0; i < 6; i++) + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC1, i, 0x000F); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC1, 0x0D, 0x000E); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC1, 0x0E, 0x0011); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC1, 0x0F, 0x0013); + b43_phy_write(dev, B43_PHY_OFDM(0x33), 0x5030); + b43_phy_set(dev, B43_PHY_CRS0, B43_PHY_CRS0_EN); +} + +static void hardware_pctl_init_aphy(struct b43_wldev *dev) +{ + //TODO +} + +void b43_phy_inita(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + + /* This lowlevel A-PHY init is also called from G-PHY init. + * So we must not access phy->a, if called from G-PHY code. + */ + B43_WARN_ON((phy->type != B43_PHYTYPE_A) && + (phy->type != B43_PHYTYPE_G)); + + might_sleep(); + + if (phy->rev >= 6) { + if (phy->type == B43_PHYTYPE_A) + b43_phy_mask(dev, B43_PHY_OFDM(0x1B), ~0x1000); + if (b43_phy_read(dev, B43_PHY_ENCORE) & B43_PHY_ENCORE_EN) + b43_phy_set(dev, B43_PHY_ENCORE, 0x0010); + else + b43_phy_mask(dev, B43_PHY_ENCORE, ~0x1010); + } + + b43_wa_all(dev); + + if (phy->type == B43_PHYTYPE_A) { + if (phy->gmode && (phy->rev < 3)) + b43_phy_set(dev, 0x0034, 0x0001); + b43_phy_rssiagc(dev, 0); + + b43_phy_set(dev, B43_PHY_CRS0, B43_PHY_CRS0_EN); + + b43_radio_init2060(dev); + + if ((dev->dev->board_vendor == SSB_BOARDVENDOR_BCM) && + ((dev->dev->board_type == SSB_BOARD_BU4306) || + (dev->dev->board_type == SSB_BOARD_BU4309))) { + ; //TODO: A PHY LO + } + + if (phy->rev >= 3) + b43_phy_ww(dev); + + hardware_pctl_init_aphy(dev); + + //TODO: radar detection + } + + if ((phy->type == B43_PHYTYPE_G) && + (dev->dev->bus_sprom->boardflags_lo & B43_BFL_PACTRL)) { + b43_phy_maskset(dev, B43_PHY_OFDM(0x6E), 0xE000, 0x3CF); + } +} + +/* Initialise the TSSI->dBm lookup table */ +static int b43_aphy_init_tssi2dbm_table(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_a *aphy = phy->a; + s16 pab0, pab1, pab2; + + pab0 = (s16) (dev->dev->bus_sprom->pa1b0); + pab1 = (s16) (dev->dev->bus_sprom->pa1b1); + pab2 = (s16) (dev->dev->bus_sprom->pa1b2); + + if (pab0 != 0 && pab1 != 0 && pab2 != 0 && + pab0 != -1 && pab1 != -1 && pab2 != -1) { + /* The pabX values are set in SPROM. Use them. */ + if ((s8) dev->dev->bus_sprom->itssi_a != 0 && + (s8) dev->dev->bus_sprom->itssi_a != -1) + aphy->tgt_idle_tssi = + (s8) (dev->dev->bus_sprom->itssi_a); + else + aphy->tgt_idle_tssi = 62; + aphy->tssi2dbm = b43_generate_dyn_tssi2dbm_tab(dev, pab0, + pab1, pab2); + if (!aphy->tssi2dbm) + return -ENOMEM; + } else { + /* pabX values not set in SPROM, + * but APHY needs a generated table. */ + aphy->tssi2dbm = NULL; + b43err(dev->wl, "Could not generate tssi2dBm " + "table (wrong SPROM info)!\n"); + return -ENODEV; + } + + return 0; +} + +static int b43_aphy_op_allocate(struct b43_wldev *dev) +{ + struct b43_phy_a *aphy; + int err; + + aphy = kzalloc(sizeof(*aphy), GFP_KERNEL); + if (!aphy) + return -ENOMEM; + dev->phy.a = aphy; + + err = b43_aphy_init_tssi2dbm_table(dev); + if (err) + goto err_free_aphy; + + return 0; + +err_free_aphy: + kfree(aphy); + dev->phy.a = NULL; + + return err; +} + +static void b43_aphy_op_prepare_structs(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_a *aphy = phy->a; + const void *tssi2dbm; + int tgt_idle_tssi; + + /* tssi2dbm table is constant, so it is initialized at alloc time. + * Save a copy of the pointer. */ + tssi2dbm = aphy->tssi2dbm; + tgt_idle_tssi = aphy->tgt_idle_tssi; + + /* Zero out the whole PHY structure. */ + memset(aphy, 0, sizeof(*aphy)); + + aphy->tssi2dbm = tssi2dbm; + aphy->tgt_idle_tssi = tgt_idle_tssi; + + //TODO init struct b43_phy_a + +} + +static void b43_aphy_op_free(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_a *aphy = phy->a; + + kfree(aphy->tssi2dbm); + aphy->tssi2dbm = NULL; + + kfree(aphy); + dev->phy.a = NULL; +} + +static int b43_aphy_op_init(struct b43_wldev *dev) +{ + b43_phy_inita(dev); + + return 0; +} + +static inline u16 adjust_phyreg(struct b43_wldev *dev, u16 offset) +{ + /* OFDM registers are base-registers for the A-PHY. */ + if ((offset & B43_PHYROUTE) == B43_PHYROUTE_OFDM_GPHY) { + offset &= ~B43_PHYROUTE; + offset |= B43_PHYROUTE_BASE; + } + +#if B43_DEBUG + if ((offset & B43_PHYROUTE) == B43_PHYROUTE_EXT_GPHY) { + /* Ext-G registers are only available on G-PHYs */ + b43err(dev->wl, "Invalid EXT-G PHY access at " + "0x%04X on A-PHY\n", offset); + dump_stack(); + } + if ((offset & B43_PHYROUTE) == B43_PHYROUTE_N_BMODE) { + /* N-BMODE registers are only available on N-PHYs */ + b43err(dev->wl, "Invalid N-BMODE PHY access at " + "0x%04X on A-PHY\n", offset); + dump_stack(); + } +#endif /* B43_DEBUG */ + + return offset; +} + +static u16 b43_aphy_op_read(struct b43_wldev *dev, u16 reg) +{ + reg = adjust_phyreg(dev, reg); + b43_write16(dev, B43_MMIO_PHY_CONTROL, reg); + return b43_read16(dev, B43_MMIO_PHY_DATA); +} + +static void b43_aphy_op_write(struct b43_wldev *dev, u16 reg, u16 value) +{ + reg = adjust_phyreg(dev, reg); + b43_write16(dev, B43_MMIO_PHY_CONTROL, reg); + b43_write16(dev, B43_MMIO_PHY_DATA, value); +} + +static u16 b43_aphy_op_radio_read(struct b43_wldev *dev, u16 reg) +{ + /* Register 1 is a 32-bit register. */ + B43_WARN_ON(reg == 1); + /* A-PHY needs 0x40 for read access */ + reg |= 0x40; + + b43_write16(dev, B43_MMIO_RADIO_CONTROL, reg); + return b43_read16(dev, B43_MMIO_RADIO_DATA_LOW); +} + +static void b43_aphy_op_radio_write(struct b43_wldev *dev, u16 reg, u16 value) +{ + /* Register 1 is a 32-bit register. */ + B43_WARN_ON(reg == 1); + + b43_write16(dev, B43_MMIO_RADIO_CONTROL, reg); + b43_write16(dev, B43_MMIO_RADIO_DATA_LOW, value); +} + +static bool b43_aphy_op_supports_hwpctl(struct b43_wldev *dev) +{ + return (dev->phy.rev >= 5); +} + +static void b43_aphy_op_software_rfkill(struct b43_wldev *dev, + bool blocked) +{ + struct b43_phy *phy = &dev->phy; + + if (!blocked) { + if (phy->radio_on) + return; + b43_radio_write16(dev, 0x0004, 0x00C0); + b43_radio_write16(dev, 0x0005, 0x0008); + b43_phy_mask(dev, 0x0010, 0xFFF7); + b43_phy_mask(dev, 0x0011, 0xFFF7); + b43_radio_init2060(dev); + } else { + b43_radio_write16(dev, 0x0004, 0x00FF); + b43_radio_write16(dev, 0x0005, 0x00FB); + b43_phy_set(dev, 0x0010, 0x0008); + b43_phy_set(dev, 0x0011, 0x0008); + } +} + +static int b43_aphy_op_switch_channel(struct b43_wldev *dev, + unsigned int new_channel) +{ + if (new_channel > 200) + return -EINVAL; + aphy_channel_switch(dev, new_channel); + + return 0; +} + +static unsigned int b43_aphy_op_get_default_chan(struct b43_wldev *dev) +{ + return 36; /* Default to channel 36 */ +} + +static void b43_aphy_op_set_rx_antenna(struct b43_wldev *dev, int antenna) +{//TODO + struct b43_phy *phy = &dev->phy; + u16 tmp; + int autodiv = 0; + + if (antenna == B43_ANTENNA_AUTO0 || antenna == B43_ANTENNA_AUTO1) + autodiv = 1; + + b43_hf_write(dev, b43_hf_read(dev) & ~B43_HF_ANTDIVHELP); + + b43_phy_maskset(dev, B43_PHY_BBANDCFG, ~B43_PHY_BBANDCFG_RXANT, + (autodiv ? B43_ANTENNA_AUTO1 : antenna) << + B43_PHY_BBANDCFG_RXANT_SHIFT); + + if (autodiv) { + tmp = b43_phy_read(dev, B43_PHY_ANTDWELL); + if (antenna == B43_ANTENNA_AUTO1) + tmp &= ~B43_PHY_ANTDWELL_AUTODIV1; + else + tmp |= B43_PHY_ANTDWELL_AUTODIV1; + b43_phy_write(dev, B43_PHY_ANTDWELL, tmp); + } + if (phy->rev < 3) + b43_phy_maskset(dev, B43_PHY_ANTDWELL, 0xFF00, 0x24); + else { + b43_phy_set(dev, B43_PHY_OFDM61, 0x10); + if (phy->rev == 3) { + b43_phy_write(dev, B43_PHY_CLIPPWRDOWNT, 0x1D); + b43_phy_write(dev, B43_PHY_ADIVRELATED, 8); + } else { + b43_phy_write(dev, B43_PHY_CLIPPWRDOWNT, 0x3A); + b43_phy_maskset(dev, B43_PHY_ADIVRELATED, 0xFF00, 8); + } + } + + b43_hf_write(dev, b43_hf_read(dev) | B43_HF_ANTDIVHELP); +} + +static void b43_aphy_op_adjust_txpower(struct b43_wldev *dev) +{//TODO +} + +static enum b43_txpwr_result b43_aphy_op_recalc_txpower(struct b43_wldev *dev, + bool ignore_tssi) +{//TODO + return B43_TXPWR_RES_DONE; +} + +static void b43_aphy_op_pwork_15sec(struct b43_wldev *dev) +{//TODO +} + +static void b43_aphy_op_pwork_60sec(struct b43_wldev *dev) +{//TODO +} + +const struct b43_phy_operations b43_phyops_a = { + .allocate = b43_aphy_op_allocate, + .free = b43_aphy_op_free, + .prepare_structs = b43_aphy_op_prepare_structs, + .init = b43_aphy_op_init, + .phy_read = b43_aphy_op_read, + .phy_write = b43_aphy_op_write, + .radio_read = b43_aphy_op_radio_read, + .radio_write = b43_aphy_op_radio_write, + .supports_hwpctl = b43_aphy_op_supports_hwpctl, + .software_rfkill = b43_aphy_op_software_rfkill, + .switch_analog = b43_phyop_switch_analog_generic, + .switch_channel = b43_aphy_op_switch_channel, + .get_default_chan = b43_aphy_op_get_default_chan, + .set_rx_antenna = b43_aphy_op_set_rx_antenna, + .recalc_txpower = b43_aphy_op_recalc_txpower, + .adjust_txpower = b43_aphy_op_adjust_txpower, + .pwork_15sec = b43_aphy_op_pwork_15sec, + .pwork_60sec = b43_aphy_op_pwork_60sec, +}; diff --git a/drivers/net/wireless/b43/phy_a.h b/drivers/net/wireless/b43/phy_a.h new file mode 100644 index 0000000..5cfaab7 --- /dev/null +++ b/drivers/net/wireless/b43/phy_a.h @@ -0,0 +1,130 @@ +#ifndef LINUX_B43_PHY_A_H_ +#define LINUX_B43_PHY_A_H_ + +#include "phy_common.h" + + +/* OFDM (A) PHY Registers */ +#define B43_PHY_VERSION_OFDM B43_PHY_OFDM(0x00) /* Versioning register for A-PHY */ +#define B43_PHY_BBANDCFG B43_PHY_OFDM(0x01) /* Baseband config */ +#define B43_PHY_BBANDCFG_RXANT 0x180 /* RX Antenna selection */ +#define B43_PHY_BBANDCFG_RXANT_SHIFT 7 +#define B43_PHY_PWRDOWN B43_PHY_OFDM(0x03) /* Powerdown */ +#define B43_PHY_CRSTHRES1_R1 B43_PHY_OFDM(0x06) /* CRS Threshold 1 (phy.rev 1 only) */ +#define B43_PHY_LNAHPFCTL B43_PHY_OFDM(0x1C) /* LNA/HPF control */ +#define B43_PHY_LPFGAINCTL B43_PHY_OFDM(0x20) /* LPF Gain control */ +#define B43_PHY_ADIVRELATED B43_PHY_OFDM(0x27) /* FIXME rename */ +#define B43_PHY_CRS0 B43_PHY_OFDM(0x29) +#define B43_PHY_CRS0_EN 0x4000 +#define B43_PHY_PEAK_COUNT B43_PHY_OFDM(0x30) +#define B43_PHY_ANTDWELL B43_PHY_OFDM(0x2B) /* Antenna dwell */ +#define B43_PHY_ANTDWELL_AUTODIV1 0x0100 /* Automatic RX diversity start antenna */ +#define B43_PHY_ENCORE B43_PHY_OFDM(0x49) /* "Encore" (RangeMax / BroadRange) */ +#define B43_PHY_ENCORE_EN 0x0200 /* Encore enable */ +#define B43_PHY_LMS B43_PHY_OFDM(0x55) +#define B43_PHY_OFDM61 B43_PHY_OFDM(0x61) /* FIXME rename */ +#define B43_PHY_OFDM61_10 0x0010 /* FIXME rename */ +#define B43_PHY_IQBAL B43_PHY_OFDM(0x69) /* I/Q balance */ +#define B43_PHY_BBTXDC_BIAS B43_PHY_OFDM(0x6B) /* Baseband TX DC bias */ +#define B43_PHY_OTABLECTL B43_PHY_OFDM(0x72) /* OFDM table control (see below) */ +#define B43_PHY_OTABLEOFF 0x03FF /* OFDM table offset (see below) */ +#define B43_PHY_OTABLENR 0xFC00 /* OFDM table number (see below) */ +#define B43_PHY_OTABLENR_SHIFT 10 +#define B43_PHY_OTABLEI B43_PHY_OFDM(0x73) /* OFDM table data I */ +#define B43_PHY_OTABLEQ B43_PHY_OFDM(0x74) /* OFDM table data Q */ +#define B43_PHY_HPWR_TSSICTL B43_PHY_OFDM(0x78) /* Hardware power TSSI control */ +#define B43_PHY_ADCCTL B43_PHY_OFDM(0x7A) /* ADC control */ +#define B43_PHY_IDLE_TSSI B43_PHY_OFDM(0x7B) +#define B43_PHY_A_TEMP_SENSE B43_PHY_OFDM(0x7C) /* A PHY temperature sense */ +#define B43_PHY_NRSSITHRES B43_PHY_OFDM(0x8A) /* NRSSI threshold */ +#define B43_PHY_ANTWRSETT B43_PHY_OFDM(0x8C) /* Antenna WR settle */ +#define B43_PHY_ANTWRSETT_ARXDIV 0x2000 /* Automatic RX diversity enabled */ +#define B43_PHY_CLIPPWRDOWNT B43_PHY_OFDM(0x93) /* Clip powerdown threshold */ +#define B43_PHY_OFDM9B B43_PHY_OFDM(0x9B) /* FIXME rename */ +#define B43_PHY_N1P1GAIN B43_PHY_OFDM(0xA0) +#define B43_PHY_P1P2GAIN B43_PHY_OFDM(0xA1) +#define B43_PHY_N1N2GAIN B43_PHY_OFDM(0xA2) +#define B43_PHY_CLIPTHRES B43_PHY_OFDM(0xA3) +#define B43_PHY_CLIPN1P2THRES B43_PHY_OFDM(0xA4) +#define B43_PHY_CCKSHIFTBITS_WA B43_PHY_OFDM(0xA5) /* CCK shiftbits workaround, FIXME rename */ +#define B43_PHY_CCKSHIFTBITS B43_PHY_OFDM(0xA7) /* FIXME rename */ +#define B43_PHY_DIVSRCHIDX B43_PHY_OFDM(0xA8) /* Divider search gain/index */ +#define B43_PHY_CLIPP2THRES B43_PHY_OFDM(0xA9) +#define B43_PHY_CLIPP3THRES B43_PHY_OFDM(0xAA) +#define B43_PHY_DIVP1P2GAIN B43_PHY_OFDM(0xAB) +#define B43_PHY_DIVSRCHGAINBACK B43_PHY_OFDM(0xAD) /* Divider search gain back */ +#define B43_PHY_DIVSRCHGAINCHNG B43_PHY_OFDM(0xAE) /* Divider search gain change */ +#define B43_PHY_CRSTHRES1 B43_PHY_OFDM(0xC0) /* CRS Threshold 1 (phy.rev >= 2 only) */ +#define B43_PHY_CRSTHRES2 B43_PHY_OFDM(0xC1) /* CRS Threshold 2 (phy.rev >= 2 only) */ +#define B43_PHY_TSSIP_LTBASE B43_PHY_OFDM(0x380) /* TSSI power lookup table base */ +#define B43_PHY_DC_LTBASE B43_PHY_OFDM(0x3A0) /* DC lookup table base */ +#define B43_PHY_GAIN_LTBASE B43_PHY_OFDM(0x3C0) /* Gain lookup table base */ + +/*** OFDM table numbers ***/ +#define B43_OFDMTAB(number, offset) (((number) << B43_PHY_OTABLENR_SHIFT) | (offset)) +#define B43_OFDMTAB_AGC1 B43_OFDMTAB(0x00, 0) +#define B43_OFDMTAB_GAIN0 B43_OFDMTAB(0x00, 0) +#define B43_OFDMTAB_GAINX B43_OFDMTAB(0x01, 0) //TODO rename +#define B43_OFDMTAB_GAIN1 B43_OFDMTAB(0x01, 4) +#define B43_OFDMTAB_AGC3 B43_OFDMTAB(0x02, 0) +#define B43_OFDMTAB_GAIN2 B43_OFDMTAB(0x02, 3) +#define B43_OFDMTAB_LNAHPFGAIN1 B43_OFDMTAB(0x03, 0) +#define B43_OFDMTAB_WRSSI B43_OFDMTAB(0x04, 0) +#define B43_OFDMTAB_LNAHPFGAIN2 B43_OFDMTAB(0x04, 0) +#define B43_OFDMTAB_NOISESCALE B43_OFDMTAB(0x05, 0) +#define B43_OFDMTAB_AGC2 B43_OFDMTAB(0x06, 0) +#define B43_OFDMTAB_ROTOR B43_OFDMTAB(0x08, 0) +#define B43_OFDMTAB_ADVRETARD B43_OFDMTAB(0x09, 0) +#define B43_OFDMTAB_DAC B43_OFDMTAB(0x0C, 0) +#define B43_OFDMTAB_DC B43_OFDMTAB(0x0E, 7) +#define B43_OFDMTAB_PWRDYN2 B43_OFDMTAB(0x0E, 12) +#define B43_OFDMTAB_LNAGAIN B43_OFDMTAB(0x0E, 13) +#define B43_OFDMTAB_UNKNOWN_0F B43_OFDMTAB(0x0F, 0) //TODO rename +#define B43_OFDMTAB_UNKNOWN_APHY B43_OFDMTAB(0x0F, 7) //TODO rename +#define B43_OFDMTAB_LPFGAIN B43_OFDMTAB(0x0F, 12) +#define B43_OFDMTAB_RSSI B43_OFDMTAB(0x10, 0) +#define B43_OFDMTAB_UNKNOWN_11 B43_OFDMTAB(0x11, 4) //TODO rename +#define B43_OFDMTAB_AGC1_R1 B43_OFDMTAB(0x13, 0) +#define B43_OFDMTAB_GAINX_R1 B43_OFDMTAB(0x14, 0) //TODO remove! +#define B43_OFDMTAB_MINSIGSQ B43_OFDMTAB(0x14, 0) +#define B43_OFDMTAB_AGC3_R1 B43_OFDMTAB(0x15, 0) +#define B43_OFDMTAB_WRSSI_R1 B43_OFDMTAB(0x15, 4) +#define B43_OFDMTAB_TSSI B43_OFDMTAB(0x15, 0) +#define B43_OFDMTAB_DACRFPABB B43_OFDMTAB(0x16, 0) +#define B43_OFDMTAB_DACOFF B43_OFDMTAB(0x17, 0) +#define B43_OFDMTAB_DCBIAS B43_OFDMTAB(0x18, 0) + +u16 b43_ofdmtab_read16(struct b43_wldev *dev, u16 table, u16 offset); +void b43_ofdmtab_write16(struct b43_wldev *dev, u16 table, + u16 offset, u16 value); +u32 b43_ofdmtab_read32(struct b43_wldev *dev, u16 table, u16 offset); +void b43_ofdmtab_write32(struct b43_wldev *dev, u16 table, + u16 offset, u32 value); + + +struct b43_phy_a { + /* Pointer to the table used to convert a + * TSSI value to dBm-Q5.2 */ + const s8 *tssi2dbm; + /* Target idle TSSI */ + int tgt_idle_tssi; + /* Current idle TSSI */ + int cur_idle_tssi;//FIXME value currently not set + + /* A-PHY TX Power control value. */ + u16 txpwr_offset; + + //TODO lots of missing stuff +}; + +/** + * b43_phy_inita - Lowlevel A-PHY init routine. + * This is _only_ used by the G-PHY code. + */ +void b43_phy_inita(struct b43_wldev *dev); + + +struct b43_phy_operations; +extern const struct b43_phy_operations b43_phyops_a; + +#endif /* LINUX_B43_PHY_A_H_ */ diff --git a/drivers/net/wireless/b43/phy_common.c b/drivers/net/wireless/b43/phy_common.c new file mode 100644 index 0000000..2ec96ec --- /dev/null +++ b/drivers/net/wireless/b43/phy_common.c @@ -0,0 +1,543 @@ +/* + + Broadcom B43 wireless driver + Common PHY routines + + Copyright (c) 2005 Martin Langer , + Copyright (c) 2005-2007 Stefano Brivio + Copyright (c) 2005-2008 Michael Buesch + Copyright (c) 2005, 2006 Danny van Dyk + Copyright (c) 2005, 2006 Andreas Jaggi + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "phy_common.h" +#include "phy_g.h" +#include "phy_a.h" +#include "phy_n.h" +#include "phy_lp.h" +#include "phy_ht.h" +#include "phy_lcn.h" +#include "b43.h" +#include "main.h" + + +int b43_phy_allocate(struct b43_wldev *dev) +{ + struct b43_phy *phy = &(dev->phy); + int err; + + phy->ops = NULL; + + switch (phy->type) { + case B43_PHYTYPE_A: + phy->ops = &b43_phyops_a; + break; + case B43_PHYTYPE_G: + phy->ops = &b43_phyops_g; + break; + case B43_PHYTYPE_N: +#ifdef CPTCFG_B43_PHY_N + phy->ops = &b43_phyops_n; +#endif + break; + case B43_PHYTYPE_LP: +#ifdef CPTCFG_B43_PHY_LP + phy->ops = &b43_phyops_lp; +#endif + break; + case B43_PHYTYPE_HT: +#ifdef CPTCFG_B43_PHY_HT + phy->ops = &b43_phyops_ht; +#endif + break; + case B43_PHYTYPE_LCN: +#ifdef CPTCFG_B43_PHY_LCN + phy->ops = &b43_phyops_lcn; +#endif + break; + } + if (B43_WARN_ON(!phy->ops)) + return -ENODEV; + + err = phy->ops->allocate(dev); + if (err) + phy->ops = NULL; + + return err; +} + +void b43_phy_free(struct b43_wldev *dev) +{ + dev->phy.ops->free(dev); + dev->phy.ops = NULL; +} + +int b43_phy_init(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + const struct b43_phy_operations *ops = phy->ops; + int err; + + phy->channel = ops->get_default_chan(dev); + + ops->software_rfkill(dev, false); + err = ops->init(dev); + if (err) { + b43err(dev->wl, "PHY init failed\n"); + goto err_block_rf; + } + /* Make sure to switch hardware and firmware (SHM) to + * the default channel. */ + err = b43_switch_channel(dev, ops->get_default_chan(dev)); + if (err) { + b43err(dev->wl, "PHY init: Channel switch to default failed\n"); + goto err_phy_exit; + } + + return 0; + +err_phy_exit: + if (ops->exit) + ops->exit(dev); +err_block_rf: + ops->software_rfkill(dev, true); + + return err; +} + +void b43_phy_exit(struct b43_wldev *dev) +{ + const struct b43_phy_operations *ops = dev->phy.ops; + + ops->software_rfkill(dev, true); + if (ops->exit) + ops->exit(dev); +} + +bool b43_has_hardware_pctl(struct b43_wldev *dev) +{ + if (!dev->phy.hardware_power_control) + return 0; + if (!dev->phy.ops->supports_hwpctl) + return 0; + return dev->phy.ops->supports_hwpctl(dev); +} + +void b43_radio_lock(struct b43_wldev *dev) +{ + u32 macctl; + +#if B43_DEBUG + B43_WARN_ON(dev->phy.radio_locked); + dev->phy.radio_locked = true; +#endif + + macctl = b43_read32(dev, B43_MMIO_MACCTL); + macctl |= B43_MACCTL_RADIOLOCK; + b43_write32(dev, B43_MMIO_MACCTL, macctl); + /* Commit the write and wait for the firmware + * to finish any radio register access. */ + b43_read32(dev, B43_MMIO_MACCTL); + udelay(10); +} + +void b43_radio_unlock(struct b43_wldev *dev) +{ + u32 macctl; + +#if B43_DEBUG + B43_WARN_ON(!dev->phy.radio_locked); + dev->phy.radio_locked = false; +#endif + + /* Commit any write */ + b43_read16(dev, B43_MMIO_PHY_VER); + /* unlock */ + macctl = b43_read32(dev, B43_MMIO_MACCTL); + macctl &= ~B43_MACCTL_RADIOLOCK; + b43_write32(dev, B43_MMIO_MACCTL, macctl); +} + +void b43_phy_lock(struct b43_wldev *dev) +{ +#if B43_DEBUG + B43_WARN_ON(dev->phy.phy_locked); + dev->phy.phy_locked = true; +#endif + B43_WARN_ON(dev->dev->core_rev < 3); + + if (!b43_is_mode(dev->wl, NL80211_IFTYPE_AP)) + b43_power_saving_ctl_bits(dev, B43_PS_AWAKE); +} + +void b43_phy_unlock(struct b43_wldev *dev) +{ +#if B43_DEBUG + B43_WARN_ON(!dev->phy.phy_locked); + dev->phy.phy_locked = false; +#endif + B43_WARN_ON(dev->dev->core_rev < 3); + + if (!b43_is_mode(dev->wl, NL80211_IFTYPE_AP)) + b43_power_saving_ctl_bits(dev, 0); +} + +static inline void assert_mac_suspended(struct b43_wldev *dev) +{ + if (!B43_DEBUG) + return; + if ((b43_status(dev) >= B43_STAT_INITIALIZED) && + (dev->mac_suspended <= 0)) { + b43dbg(dev->wl, "PHY/RADIO register access with " + "enabled MAC.\n"); + dump_stack(); + } +} + +u16 b43_radio_read(struct b43_wldev *dev, u16 reg) +{ + assert_mac_suspended(dev); + return dev->phy.ops->radio_read(dev, reg); +} + +void b43_radio_write(struct b43_wldev *dev, u16 reg, u16 value) +{ + assert_mac_suspended(dev); + dev->phy.ops->radio_write(dev, reg, value); +} + +void b43_radio_mask(struct b43_wldev *dev, u16 offset, u16 mask) +{ + b43_radio_write16(dev, offset, + b43_radio_read16(dev, offset) & mask); +} + +void b43_radio_set(struct b43_wldev *dev, u16 offset, u16 set) +{ + b43_radio_write16(dev, offset, + b43_radio_read16(dev, offset) | set); +} + +void b43_radio_maskset(struct b43_wldev *dev, u16 offset, u16 mask, u16 set) +{ + b43_radio_write16(dev, offset, + (b43_radio_read16(dev, offset) & mask) | set); +} + +bool b43_radio_wait_value(struct b43_wldev *dev, u16 offset, u16 mask, + u16 value, int delay, int timeout) +{ + u16 val; + int i; + + for (i = 0; i < timeout; i += delay) { + val = b43_radio_read(dev, offset); + if ((val & mask) == value) + return true; + udelay(delay); + } + return false; +} + +u16 b43_phy_read(struct b43_wldev *dev, u16 reg) +{ + assert_mac_suspended(dev); + dev->phy.writes_counter = 0; + return dev->phy.ops->phy_read(dev, reg); +} + +void b43_phy_write(struct b43_wldev *dev, u16 reg, u16 value) +{ + assert_mac_suspended(dev); + dev->phy.ops->phy_write(dev, reg, value); + if (++dev->phy.writes_counter == B43_MAX_WRITES_IN_ROW) { + b43_read16(dev, B43_MMIO_PHY_VER); + dev->phy.writes_counter = 0; + } +} + +void b43_phy_copy(struct b43_wldev *dev, u16 destreg, u16 srcreg) +{ + assert_mac_suspended(dev); + dev->phy.ops->phy_write(dev, destreg, + dev->phy.ops->phy_read(dev, srcreg)); +} + +void b43_phy_mask(struct b43_wldev *dev, u16 offset, u16 mask) +{ + if (dev->phy.ops->phy_maskset) { + assert_mac_suspended(dev); + dev->phy.ops->phy_maskset(dev, offset, mask, 0); + } else { + b43_phy_write(dev, offset, + b43_phy_read(dev, offset) & mask); + } +} + +void b43_phy_set(struct b43_wldev *dev, u16 offset, u16 set) +{ + if (dev->phy.ops->phy_maskset) { + assert_mac_suspended(dev); + dev->phy.ops->phy_maskset(dev, offset, 0xFFFF, set); + } else { + b43_phy_write(dev, offset, + b43_phy_read(dev, offset) | set); + } +} + +void b43_phy_maskset(struct b43_wldev *dev, u16 offset, u16 mask, u16 set) +{ + if (dev->phy.ops->phy_maskset) { + assert_mac_suspended(dev); + dev->phy.ops->phy_maskset(dev, offset, mask, set); + } else { + b43_phy_write(dev, offset, + (b43_phy_read(dev, offset) & mask) | set); + } +} + +int b43_switch_channel(struct b43_wldev *dev, unsigned int new_channel) +{ + struct b43_phy *phy = &(dev->phy); + u16 channelcookie, savedcookie; + int err; + + if (new_channel == B43_DEFAULT_CHANNEL) + new_channel = phy->ops->get_default_chan(dev); + + /* First we set the channel radio code to prevent the + * firmware from sending ghost packets. + */ + channelcookie = new_channel; + if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) + channelcookie |= B43_SHM_SH_CHAN_5GHZ; + /* FIXME: set 40Mhz flag if required */ + if (0) + channelcookie |= B43_SHM_SH_CHAN_40MHZ; + savedcookie = b43_shm_read16(dev, B43_SHM_SHARED, B43_SHM_SH_CHAN); + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_CHAN, channelcookie); + + /* Now try to switch the PHY hardware channel. */ + err = phy->ops->switch_channel(dev, new_channel); + if (err) + goto err_restore_cookie; + + dev->phy.channel = new_channel; + /* Wait for the radio to tune to the channel and stabilize. */ + msleep(8); + + return 0; + +err_restore_cookie: + b43_shm_write16(dev, B43_SHM_SHARED, + B43_SHM_SH_CHAN, savedcookie); + + return err; +} + +void b43_software_rfkill(struct b43_wldev *dev, bool blocked) +{ + struct b43_phy *phy = &dev->phy; + + b43_mac_suspend(dev); + phy->ops->software_rfkill(dev, blocked); + phy->radio_on = !blocked; + b43_mac_enable(dev); +} + +/** + * b43_phy_txpower_adjust_work - TX power workqueue. + * + * Workqueue for updating the TX power parameters in hardware. + */ +void b43_phy_txpower_adjust_work(struct work_struct *work) +{ + struct b43_wl *wl = container_of(work, struct b43_wl, + txpower_adjust_work); + struct b43_wldev *dev; + + mutex_lock(&wl->mutex); + dev = wl->current_dev; + + if (likely(dev && (b43_status(dev) >= B43_STAT_STARTED))) + dev->phy.ops->adjust_txpower(dev); + + mutex_unlock(&wl->mutex); +} + +void b43_phy_txpower_check(struct b43_wldev *dev, unsigned int flags) +{ + struct b43_phy *phy = &dev->phy; + unsigned long now = jiffies; + enum b43_txpwr_result result; + + if (!(flags & B43_TXPWR_IGNORE_TIME)) { + /* Check if it's time for a TXpower check. */ + if (time_before(now, phy->next_txpwr_check_time)) + return; /* Not yet */ + } + /* The next check will be needed in two seconds, or later. */ + phy->next_txpwr_check_time = round_jiffies(now + (HZ * 2)); + + if ((dev->dev->board_vendor == SSB_BOARDVENDOR_BCM) && + (dev->dev->board_type == SSB_BOARD_BU4306)) + return; /* No software txpower adjustment needed */ + + result = phy->ops->recalc_txpower(dev, !!(flags & B43_TXPWR_IGNORE_TSSI)); + if (result == B43_TXPWR_RES_DONE) + return; /* We are done. */ + B43_WARN_ON(result != B43_TXPWR_RES_NEED_ADJUST); + B43_WARN_ON(phy->ops->adjust_txpower == NULL); + + /* We must adjust the transmission power in hardware. + * Schedule b43_phy_txpower_adjust_work(). */ + ieee80211_queue_work(dev->wl->hw, &dev->wl->txpower_adjust_work); +} + +int b43_phy_shm_tssi_read(struct b43_wldev *dev, u16 shm_offset) +{ + const bool is_ofdm = (shm_offset != B43_SHM_SH_TSSI_CCK); + unsigned int a, b, c, d; + unsigned int average; + u32 tmp; + + tmp = b43_shm_read32(dev, B43_SHM_SHARED, shm_offset); + a = tmp & 0xFF; + b = (tmp >> 8) & 0xFF; + c = (tmp >> 16) & 0xFF; + d = (tmp >> 24) & 0xFF; + if (a == 0 || a == B43_TSSI_MAX || + b == 0 || b == B43_TSSI_MAX || + c == 0 || c == B43_TSSI_MAX || + d == 0 || d == B43_TSSI_MAX) + return -ENOENT; + /* The values are OK. Clear them. */ + tmp = B43_TSSI_MAX | (B43_TSSI_MAX << 8) | + (B43_TSSI_MAX << 16) | (B43_TSSI_MAX << 24); + b43_shm_write32(dev, B43_SHM_SHARED, shm_offset, tmp); + + if (is_ofdm) { + a = (a + 32) & 0x3F; + b = (b + 32) & 0x3F; + c = (c + 32) & 0x3F; + d = (d + 32) & 0x3F; + } + + /* Get the average of the values with 0.5 added to each value. */ + average = (a + b + c + d + 2) / 4; + if (is_ofdm) { + /* Adjust for CCK-boost */ + if (b43_shm_read16(dev, B43_SHM_SHARED, B43_SHM_SH_HOSTF1) + & B43_HF_CCKBOOST) + average = (average >= 13) ? (average - 13) : 0; + } + + return average; +} + +void b43_phyop_switch_analog_generic(struct b43_wldev *dev, bool on) +{ + b43_write16(dev, B43_MMIO_PHY0, on ? 0 : 0xF4); +} + + +bool b43_channel_type_is_40mhz(enum nl80211_channel_type channel_type) +{ + return (channel_type == NL80211_CHAN_HT40MINUS || + channel_type == NL80211_CHAN_HT40PLUS); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/BmacPhyClkFgc */ +void b43_phy_force_clock(struct b43_wldev *dev, bool force) +{ + u32 tmp; + + WARN_ON(dev->phy.type != B43_PHYTYPE_N && + dev->phy.type != B43_PHYTYPE_HT); + + switch (dev->dev->bus_type) { +#ifdef CPTCFG_B43_BCMA + case B43_BUS_BCMA: + tmp = bcma_aread32(dev->dev->bdev, BCMA_IOCTL); + if (force) + tmp |= BCMA_IOCTL_FGC; + else + tmp &= ~BCMA_IOCTL_FGC; + bcma_awrite32(dev->dev->bdev, BCMA_IOCTL, tmp); + break; +#endif +#ifdef CPTCFG_B43_SSB + case B43_BUS_SSB: + tmp = ssb_read32(dev->dev->sdev, SSB_TMSLOW); + if (force) + tmp |= SSB_TMSLOW_FGC; + else + tmp &= ~SSB_TMSLOW_FGC; + ssb_write32(dev->dev->sdev, SSB_TMSLOW, tmp); + break; +#endif + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/Cordic */ +struct b43_c32 b43_cordic(int theta) +{ + static const u32 arctg[] = { + 2949120, 1740967, 919879, 466945, 234379, 117304, + 58666, 29335, 14668, 7334, 3667, 1833, + 917, 458, 229, 115, 57, 29, + }; + u8 i; + s32 tmp; + s8 signx = 1; + u32 angle = 0; + struct b43_c32 ret = { .i = 39797, .q = 0, }; + + while (theta > (180 << 16)) + theta -= (360 << 16); + while (theta < -(180 << 16)) + theta += (360 << 16); + + if (theta > (90 << 16)) { + theta -= (180 << 16); + signx = -1; + } else if (theta < -(90 << 16)) { + theta += (180 << 16); + signx = -1; + } + + for (i = 0; i <= 17; i++) { + if (theta > angle) { + tmp = ret.i - (ret.q >> i); + ret.q += ret.i >> i; + ret.i = tmp; + angle += arctg[i]; + } else { + tmp = ret.i + (ret.q >> i); + ret.q -= ret.i >> i; + ret.i = tmp; + angle -= arctg[i]; + } + } + + ret.i *= signx; + ret.q *= signx; + + return ret; +} diff --git a/drivers/net/wireless/b43/phy_common.h b/drivers/net/wireless/b43/phy_common.h new file mode 100644 index 0000000..562b22a --- /dev/null +++ b/drivers/net/wireless/b43/phy_common.h @@ -0,0 +1,460 @@ +#ifndef LINUX_B43_PHY_COMMON_H_ +#define LINUX_B43_PHY_COMMON_H_ + +#include +#include +#if (LINUX_VERSION_CODE == KERNEL_VERSION(2,6,28)) +#include +#endif + +struct b43_wldev; + +/* Complex number using 2 32-bit signed integers */ +struct b43_c32 { s32 i, q; }; + +#define CORDIC_CONVERT(value) (((value) >= 0) ? \ + ((((value) >> 15) + 1) >> 1) : \ + -((((-(value)) >> 15) + 1) >> 1)) + +/* PHY register routing bits */ +#define B43_PHYROUTE 0x0C00 /* PHY register routing bits mask */ +#define B43_PHYROUTE_BASE 0x0000 /* Base registers */ +#define B43_PHYROUTE_OFDM_GPHY 0x0400 /* OFDM register routing for G-PHYs */ +#define B43_PHYROUTE_EXT_GPHY 0x0800 /* Extended G-PHY registers */ +#define B43_PHYROUTE_N_BMODE 0x0C00 /* N-PHY BMODE registers */ + +/* CCK (B-PHY) registers. */ +#define B43_PHY_CCK(reg) ((reg) | B43_PHYROUTE_BASE) +/* N-PHY registers. */ +#define B43_PHY_N(reg) ((reg) | B43_PHYROUTE_BASE) +/* N-PHY BMODE registers. */ +#define B43_PHY_N_BMODE(reg) ((reg) | B43_PHYROUTE_N_BMODE) +/* OFDM (A-PHY) registers. */ +#define B43_PHY_OFDM(reg) ((reg) | B43_PHYROUTE_OFDM_GPHY) +/* Extended G-PHY registers. */ +#define B43_PHY_EXTG(reg) ((reg) | B43_PHYROUTE_EXT_GPHY) + + +/* Masks for the PHY versioning registers. */ +#define B43_PHYVER_ANALOG 0xF000 +#define B43_PHYVER_ANALOG_SHIFT 12 +#define B43_PHYVER_TYPE 0x0F00 +#define B43_PHYVER_TYPE_SHIFT 8 +#define B43_PHYVER_VERSION 0x00FF + +/* PHY writes need to be flushed if we reach limit */ +#define B43_MAX_WRITES_IN_ROW 24 + +/** + * enum b43_interference_mitigation - Interference Mitigation mode + * + * @B43_INTERFMODE_NONE: Disabled + * @B43_INTERFMODE_NONWLAN: Non-WLAN Interference Mitigation + * @B43_INTERFMODE_MANUALWLAN: WLAN Interference Mitigation + * @B43_INTERFMODE_AUTOWLAN: Automatic WLAN Interference Mitigation + */ +enum b43_interference_mitigation { + B43_INTERFMODE_NONE, + B43_INTERFMODE_NONWLAN, + B43_INTERFMODE_MANUALWLAN, + B43_INTERFMODE_AUTOWLAN, +}; + +/* Antenna identifiers */ +enum { + B43_ANTENNA0 = 0, /* Antenna 0 */ + B43_ANTENNA1 = 1, /* Antenna 1 */ + B43_ANTENNA_AUTO0 = 2, /* Automatic, starting with antenna 0 */ + B43_ANTENNA_AUTO1 = 3, /* Automatic, starting with antenna 1 */ + B43_ANTENNA2 = 4, + B43_ANTENNA3 = 8, + + B43_ANTENNA_AUTO = B43_ANTENNA_AUTO0, + B43_ANTENNA_DEFAULT = B43_ANTENNA_AUTO, +}; + +/** + * enum b43_txpwr_result - Return value for the recalc_txpower PHY op. + * + * @B43_TXPWR_RES_NEED_ADJUST: Values changed. Hardware adjustment is needed. + * @B43_TXPWR_RES_DONE: No more work to do. Everything is done. + */ +enum b43_txpwr_result { + B43_TXPWR_RES_NEED_ADJUST, + B43_TXPWR_RES_DONE, +}; + +/** + * struct b43_phy_operations - Function pointers for PHY ops. + * + * @allocate: Allocate and initialise the PHY data structures. + * Must not be NULL. + * @free: Destroy and free the PHY data structures. + * Must not be NULL. + * + * @prepare_structs: Prepare the PHY data structures. + * The data structures allocated in @allocate are + * initialized here. + * Must not be NULL. + * @prepare_hardware: Prepare the PHY. This is called before b43_chip_init to + * do some early early PHY hardware init. + * Can be NULL, if not required. + * @init: Initialize the PHY. + * Must not be NULL. + * @exit: Shutdown the PHY. + * Can be NULL, if not required. + * + * @phy_read: Read from a PHY register. + * Must not be NULL. + * @phy_write: Write to a PHY register. + * Must not be NULL. + * @phy_maskset: Maskset a PHY register, taking shortcuts. + * If it is NULL, a generic algorithm is used. + * @radio_read: Read from a Radio register. + * Must not be NULL. + * @radio_write: Write to a Radio register. + * Must not be NULL. + * + * @supports_hwpctl: Returns a boolean whether Hardware Power Control + * is supported or not. + * If NULL, hwpctl is assumed to be never supported. + * @software_rfkill: Turn the radio ON or OFF. + * Possible state values are + * RFKILL_STATE_SOFT_BLOCKED or + * RFKILL_STATE_UNBLOCKED + * Must not be NULL. + * @switch_analog: Turn the Analog on/off. + * Must not be NULL. + * @switch_channel: Switch the radio to another channel. + * Must not be NULL. + * @get_default_chan: Just returns the default channel number. + * Must not be NULL. + * @set_rx_antenna: Set the antenna used for RX. + * Can be NULL, if not supported. + * @interf_mitigation: Switch the Interference Mitigation mode. + * Can be NULL, if not supported. + * + * @recalc_txpower: Recalculate the transmission power parameters. + * This callback has to recalculate the TX power settings, + * but does not need to write them to the hardware, yet. + * Returns enum b43_txpwr_result to indicate whether the hardware + * needs to be adjusted. + * If B43_TXPWR_NEED_ADJUST is returned, @adjust_txpower + * will be called later. + * If the parameter "ignore_tssi" is true, the TSSI values should + * be ignored and a recalculation of the power settings should be + * done even if the TSSI values did not change. + * This function may sleep, but should not. + * Must not be NULL. + * @adjust_txpower: Write the previously calculated TX power settings + * (from @recalc_txpower) to the hardware. + * This function may sleep. + * Can be NULL, if (and ONLY if) @recalc_txpower _always_ + * returns B43_TXPWR_RES_DONE. + * + * @pwork_15sec: Periodic work. Called every 15 seconds. + * Can be NULL, if not required. + * @pwork_60sec: Periodic work. Called every 60 seconds. + * Can be NULL, if not required. + */ +struct b43_phy_operations { + /* Initialisation */ + int (*allocate)(struct b43_wldev *dev); + void (*free)(struct b43_wldev *dev); + void (*prepare_structs)(struct b43_wldev *dev); + int (*prepare_hardware)(struct b43_wldev *dev); + int (*init)(struct b43_wldev *dev); + void (*exit)(struct b43_wldev *dev); + + /* Register access */ + u16 (*phy_read)(struct b43_wldev *dev, u16 reg); + void (*phy_write)(struct b43_wldev *dev, u16 reg, u16 value); + void (*phy_maskset)(struct b43_wldev *dev, u16 reg, u16 mask, u16 set); + u16 (*radio_read)(struct b43_wldev *dev, u16 reg); + void (*radio_write)(struct b43_wldev *dev, u16 reg, u16 value); + + /* Radio */ + bool (*supports_hwpctl)(struct b43_wldev *dev); + void (*software_rfkill)(struct b43_wldev *dev, bool blocked); + void (*switch_analog)(struct b43_wldev *dev, bool on); + int (*switch_channel)(struct b43_wldev *dev, unsigned int new_channel); + unsigned int (*get_default_chan)(struct b43_wldev *dev); + void (*set_rx_antenna)(struct b43_wldev *dev, int antenna); + int (*interf_mitigation)(struct b43_wldev *dev, + enum b43_interference_mitigation new_mode); + + /* Transmission power adjustment */ + enum b43_txpwr_result (*recalc_txpower)(struct b43_wldev *dev, + bool ignore_tssi); + void (*adjust_txpower)(struct b43_wldev *dev); + + /* Misc */ + void (*pwork_15sec)(struct b43_wldev *dev); + void (*pwork_60sec)(struct b43_wldev *dev); +}; + +struct b43_phy_a; +struct b43_phy_g; +struct b43_phy_n; +struct b43_phy_lp; +struct b43_phy_ht; +struct b43_phy_lcn; + +struct b43_phy { + /* Hardware operation callbacks. */ + const struct b43_phy_operations *ops; + + /* Most hardware context information is stored in the standard- + * specific data structures pointed to by the pointers below. + * Only one of them is valid (the currently enabled PHY). */ +#ifdef CPTCFG_B43_DEBUG + /* No union for debug build to force NULL derefs in buggy code. */ + struct { +#else + union { +#endif + /* A-PHY specific information */ + struct b43_phy_a *a; + /* G-PHY specific information */ + struct b43_phy_g *g; + /* N-PHY specific information */ + struct b43_phy_n *n; + /* LP-PHY specific information */ + struct b43_phy_lp *lp; + /* HT-PHY specific information */ + struct b43_phy_ht *ht; + /* LCN-PHY specific information */ + struct b43_phy_lcn *lcn; + }; + + /* Band support flags. */ + bool supports_2ghz; + bool supports_5ghz; + + /* HT info */ + bool is_40mhz; + + /* GMODE bit enabled? */ + bool gmode; + + /* Analog Type */ + u8 analog; + /* B43_PHYTYPE_ */ + u8 type; + /* PHY revision number. */ + u8 rev; + + /* Count writes since last read */ + u8 writes_counter; + + /* Radio versioning */ + u16 radio_manuf; /* Radio manufacturer */ + u16 radio_ver; /* Radio version */ + u8 radio_rev; /* Radio revision */ + + /* Software state of the radio */ + bool radio_on; + + /* Desired TX power level (in dBm). + * This is set by the user and adjusted in b43_phy_xmitpower(). */ + int desired_txpower; + + /* Hardware Power Control enabled? */ + bool hardware_power_control; + + /* The time (in absolute jiffies) when the next TX power output + * check is needed. */ + unsigned long next_txpwr_check_time; + + /* Current channel */ + unsigned int channel; + u16 channel_freq; + enum nl80211_channel_type channel_type; + + /* PHY TX errors counter. */ + atomic_t txerr_cnt; + +#ifdef CPTCFG_B43_DEBUG + /* PHY registers locked (w.r.t. firmware) */ + bool phy_locked; + /* Radio registers locked (w.r.t. firmware) */ + bool radio_locked; +#endif /* B43_DEBUG */ +}; + + +/** + * b43_phy_allocate - Allocate PHY structs + * Allocate the PHY data structures, based on the current dev->phy.type + */ +int b43_phy_allocate(struct b43_wldev *dev); + +/** + * b43_phy_free - Free PHY structs + */ +void b43_phy_free(struct b43_wldev *dev); + +/** + * b43_phy_init - Initialise the PHY + */ +int b43_phy_init(struct b43_wldev *dev); + +/** + * b43_phy_exit - Cleanup PHY + */ +void b43_phy_exit(struct b43_wldev *dev); + +/** + * b43_has_hardware_pctl - Hardware Power Control supported? + * Returns a boolean, whether hardware power control is supported. + */ +bool b43_has_hardware_pctl(struct b43_wldev *dev); + +/** + * b43_phy_read - 16bit PHY register read access + */ +u16 b43_phy_read(struct b43_wldev *dev, u16 reg); + +/** + * b43_phy_write - 16bit PHY register write access + */ +void b43_phy_write(struct b43_wldev *dev, u16 reg, u16 value); + +/** + * b43_phy_copy - copy contents of 16bit PHY register to another + */ +void b43_phy_copy(struct b43_wldev *dev, u16 destreg, u16 srcreg); + +/** + * b43_phy_mask - Mask a PHY register with a mask + */ +void b43_phy_mask(struct b43_wldev *dev, u16 offset, u16 mask); + +/** + * b43_phy_set - OR a PHY register with a bitmap + */ +void b43_phy_set(struct b43_wldev *dev, u16 offset, u16 set); + +/** + * b43_phy_maskset - Mask and OR a PHY register with a mask and bitmap + */ +void b43_phy_maskset(struct b43_wldev *dev, u16 offset, u16 mask, u16 set); + +/** + * b43_radio_read - 16bit Radio register read access + */ +u16 b43_radio_read(struct b43_wldev *dev, u16 reg); +#define b43_radio_read16 b43_radio_read /* DEPRECATED */ + +/** + * b43_radio_write - 16bit Radio register write access + */ +void b43_radio_write(struct b43_wldev *dev, u16 reg, u16 value); +#define b43_radio_write16 b43_radio_write /* DEPRECATED */ + +/** + * b43_radio_mask - Mask a 16bit radio register with a mask + */ +void b43_radio_mask(struct b43_wldev *dev, u16 offset, u16 mask); + +/** + * b43_radio_set - OR a 16bit radio register with a bitmap + */ +void b43_radio_set(struct b43_wldev *dev, u16 offset, u16 set); + +/** + * b43_radio_maskset - Mask and OR a radio register with a mask and bitmap + */ +void b43_radio_maskset(struct b43_wldev *dev, u16 offset, u16 mask, u16 set); + +/** + * b43_radio_wait_value - Waits for a given value in masked register read + */ +bool b43_radio_wait_value(struct b43_wldev *dev, u16 offset, u16 mask, + u16 value, int delay, int timeout); + +/** + * b43_radio_lock - Lock firmware radio register access + */ +void b43_radio_lock(struct b43_wldev *dev); + +/** + * b43_radio_unlock - Unlock firmware radio register access + */ +void b43_radio_unlock(struct b43_wldev *dev); + +/** + * b43_phy_lock - Lock firmware PHY register access + */ +void b43_phy_lock(struct b43_wldev *dev); + +/** + * b43_phy_unlock - Unlock firmware PHY register access + */ +void b43_phy_unlock(struct b43_wldev *dev); + +/** + * b43_switch_channel - Switch to another channel + */ +int b43_switch_channel(struct b43_wldev *dev, unsigned int new_channel); +/** + * B43_DEFAULT_CHANNEL - Switch to the default channel. + */ +#define B43_DEFAULT_CHANNEL UINT_MAX + +/** + * b43_software_rfkill - Turn the radio ON or OFF in software. + */ +void b43_software_rfkill(struct b43_wldev *dev, bool blocked); + +/** + * b43_phy_txpower_check - Check TX power output. + * + * Compare the current TX power output to the desired power emission + * and schedule an adjustment in case it mismatches. + * + * @flags: OR'ed enum b43_phy_txpower_check_flags flags. + * See the docs below. + */ +void b43_phy_txpower_check(struct b43_wldev *dev, unsigned int flags); +/** + * enum b43_phy_txpower_check_flags - Flags for b43_phy_txpower_check() + * + * @B43_TXPWR_IGNORE_TIME: Ignore the schedule time and force-redo + * the check now. + * @B43_TXPWR_IGNORE_TSSI: Redo the recalculation, even if the average + * TSSI did not change. + */ +enum b43_phy_txpower_check_flags { + B43_TXPWR_IGNORE_TIME = (1 << 0), + B43_TXPWR_IGNORE_TSSI = (1 << 1), +}; + +struct work_struct; +void b43_phy_txpower_adjust_work(struct work_struct *work); + +/** + * b43_phy_shm_tssi_read - Read the average of the last 4 TSSI from SHM. + * + * @shm_offset: The SHM address to read the values from. + * + * Returns the average of the 4 TSSI values, or a negative error code. + */ +int b43_phy_shm_tssi_read(struct b43_wldev *dev, u16 shm_offset); + +/** + * b43_phy_switch_analog_generic - Generic PHY operation for switching the Analog. + * + * It does the switching based on the PHY0 core register. + * Do _not_ call this directly. Only use it as a switch_analog callback + * for struct b43_phy_operations. + */ +void b43_phyop_switch_analog_generic(struct b43_wldev *dev, bool on); + +bool b43_channel_type_is_40mhz(enum nl80211_channel_type channel_type); + +void b43_phy_force_clock(struct b43_wldev *dev, bool force); + +struct b43_c32 b43_cordic(int theta); + +#endif /* LINUX_B43_PHY_COMMON_H_ */ diff --git a/drivers/net/wireless/b43/phy_g.c b/drivers/net/wireless/b43/phy_g.c new file mode 100644 index 0000000..12f467b --- /dev/null +++ b/drivers/net/wireless/b43/phy_g.c @@ -0,0 +1,3055 @@ +/* + + Broadcom B43 wireless driver + IEEE 802.11g PHY driver + + Copyright (c) 2005 Martin Langer , + Copyright (c) 2005-2007 Stefano Brivio + Copyright (c) 2005-2008 Michael Buesch + Copyright (c) 2005, 2006 Danny van Dyk + Copyright (c) 2005, 2006 Andreas Jaggi + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" +#include "phy_g.h" +#include "phy_common.h" +#include "lo.h" +#include "main.h" + +#include +#include + + +static const s8 b43_tssi2dbm_g_table[] = { + 77, 77, 77, 76, + 76, 76, 75, 75, + 74, 74, 73, 73, + 73, 72, 72, 71, + 71, 70, 70, 69, + 68, 68, 67, 67, + 66, 65, 65, 64, + 63, 63, 62, 61, + 60, 59, 58, 57, + 56, 55, 54, 53, + 52, 50, 49, 47, + 45, 43, 40, 37, + 33, 28, 22, 14, + 5, -7, -20, -20, + -20, -20, -20, -20, + -20, -20, -20, -20, +}; + +static const u8 b43_radio_channel_codes_bg[] = { + 12, 17, 22, 27, + 32, 37, 42, 47, + 52, 57, 62, 67, + 72, 84, +}; + + +static void b43_calc_nrssi_threshold(struct b43_wldev *dev); + + +#define bitrev4(tmp) (bitrev8(tmp) >> 4) + + +/* Get the freq, as it has to be written to the device. */ +static inline u16 channel2freq_bg(u8 channel) +{ + B43_WARN_ON(!(channel >= 1 && channel <= 14)); + + return b43_radio_channel_codes_bg[channel - 1]; +} + +static void generate_rfatt_list(struct b43_wldev *dev, + struct b43_rfatt_list *list) +{ + struct b43_phy *phy = &dev->phy; + + /* APHY.rev < 5 || GPHY.rev < 6 */ + static const struct b43_rfatt rfatt_0[] = { + {.att = 3,.with_padmix = 0,}, + {.att = 1,.with_padmix = 0,}, + {.att = 5,.with_padmix = 0,}, + {.att = 7,.with_padmix = 0,}, + {.att = 9,.with_padmix = 0,}, + {.att = 2,.with_padmix = 0,}, + {.att = 0,.with_padmix = 0,}, + {.att = 4,.with_padmix = 0,}, + {.att = 6,.with_padmix = 0,}, + {.att = 8,.with_padmix = 0,}, + {.att = 1,.with_padmix = 1,}, + {.att = 2,.with_padmix = 1,}, + {.att = 3,.with_padmix = 1,}, + {.att = 4,.with_padmix = 1,}, + }; + /* Radio.rev == 8 && Radio.version == 0x2050 */ + static const struct b43_rfatt rfatt_1[] = { + {.att = 2,.with_padmix = 1,}, + {.att = 4,.with_padmix = 1,}, + {.att = 6,.with_padmix = 1,}, + {.att = 8,.with_padmix = 1,}, + {.att = 10,.with_padmix = 1,}, + {.att = 12,.with_padmix = 1,}, + {.att = 14,.with_padmix = 1,}, + }; + /* Otherwise */ + static const struct b43_rfatt rfatt_2[] = { + {.att = 0,.with_padmix = 1,}, + {.att = 2,.with_padmix = 1,}, + {.att = 4,.with_padmix = 1,}, + {.att = 6,.with_padmix = 1,}, + {.att = 8,.with_padmix = 1,}, + {.att = 9,.with_padmix = 1,}, + {.att = 9,.with_padmix = 1,}, + }; + + if (!b43_has_hardware_pctl(dev)) { + /* Software pctl */ + list->list = rfatt_0; + list->len = ARRAY_SIZE(rfatt_0); + list->min_val = 0; + list->max_val = 9; + return; + } + if (phy->radio_ver == 0x2050 && phy->radio_rev == 8) { + /* Hardware pctl */ + list->list = rfatt_1; + list->len = ARRAY_SIZE(rfatt_1); + list->min_val = 0; + list->max_val = 14; + return; + } + /* Hardware pctl */ + list->list = rfatt_2; + list->len = ARRAY_SIZE(rfatt_2); + list->min_val = 0; + list->max_val = 9; +} + +static void generate_bbatt_list(struct b43_wldev *dev, + struct b43_bbatt_list *list) +{ + static const struct b43_bbatt bbatt_0[] = { + {.att = 0,}, + {.att = 1,}, + {.att = 2,}, + {.att = 3,}, + {.att = 4,}, + {.att = 5,}, + {.att = 6,}, + {.att = 7,}, + {.att = 8,}, + }; + + list->list = bbatt_0; + list->len = ARRAY_SIZE(bbatt_0); + list->min_val = 0; + list->max_val = 8; +} + +static void b43_shm_clear_tssi(struct b43_wldev *dev) +{ + b43_shm_write16(dev, B43_SHM_SHARED, 0x0058, 0x7F7F); + b43_shm_write16(dev, B43_SHM_SHARED, 0x005a, 0x7F7F); + b43_shm_write16(dev, B43_SHM_SHARED, 0x0070, 0x7F7F); + b43_shm_write16(dev, B43_SHM_SHARED, 0x0072, 0x7F7F); +} + +/* Synthetic PU workaround */ +static void b43_synth_pu_workaround(struct b43_wldev *dev, u8 channel) +{ + struct b43_phy *phy = &dev->phy; + + might_sleep(); + + if (phy->radio_ver != 0x2050 || phy->radio_rev >= 6) { + /* We do not need the workaround. */ + return; + } + + if (channel <= 10) { + b43_write16(dev, B43_MMIO_CHANNEL, + channel2freq_bg(channel + 4)); + } else { + b43_write16(dev, B43_MMIO_CHANNEL, channel2freq_bg(1)); + } + msleep(1); + b43_write16(dev, B43_MMIO_CHANNEL, channel2freq_bg(channel)); +} + +/* Set the baseband attenuation value on chip. */ +void b43_gphy_set_baseband_attenuation(struct b43_wldev *dev, + u16 baseband_attenuation) +{ + struct b43_phy *phy = &dev->phy; + + if (phy->analog == 0) { + b43_write16(dev, B43_MMIO_PHY0, (b43_read16(dev, B43_MMIO_PHY0) + & 0xFFF0) | + baseband_attenuation); + } else if (phy->analog > 1) { + b43_phy_maskset(dev, B43_PHY_DACCTL, 0xFFC3, (baseband_attenuation << 2)); + } else { + b43_phy_maskset(dev, B43_PHY_DACCTL, 0xFF87, (baseband_attenuation << 3)); + } +} + +/* Adjust the transmission power output (G-PHY) */ +static void b43_set_txpower_g(struct b43_wldev *dev, + const struct b43_bbatt *bbatt, + const struct b43_rfatt *rfatt, u8 tx_control) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + struct b43_txpower_lo_control *lo = gphy->lo_control; + u16 bb, rf; + u16 tx_bias, tx_magn; + + bb = bbatt->att; + rf = rfatt->att; + tx_bias = lo->tx_bias; + tx_magn = lo->tx_magn; + if (unlikely(tx_bias == 0xFF)) + tx_bias = 0; + + /* Save the values for later. Use memmove, because it's valid + * to pass &gphy->rfatt as rfatt pointer argument. Same for bbatt. */ + gphy->tx_control = tx_control; + memmove(&gphy->rfatt, rfatt, sizeof(*rfatt)); + gphy->rfatt.with_padmix = !!(tx_control & B43_TXCTL_TXMIX); + memmove(&gphy->bbatt, bbatt, sizeof(*bbatt)); + + if (b43_debug(dev, B43_DBG_XMITPOWER)) { + b43dbg(dev->wl, "Tuning TX-power to bbatt(%u), " + "rfatt(%u), tx_control(0x%02X), " + "tx_bias(0x%02X), tx_magn(0x%02X)\n", + bb, rf, tx_control, tx_bias, tx_magn); + } + + b43_gphy_set_baseband_attenuation(dev, bb); + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_RFATT, rf); + if (phy->radio_ver == 0x2050 && phy->radio_rev == 8) { + b43_radio_write16(dev, 0x43, + (rf & 0x000F) | (tx_control & 0x0070)); + } else { + b43_radio_maskset(dev, 0x43, 0xFFF0, (rf & 0x000F)); + b43_radio_maskset(dev, 0x52, ~0x0070, (tx_control & 0x0070)); + } + if (has_tx_magnification(phy)) { + b43_radio_write16(dev, 0x52, tx_magn | tx_bias); + } else { + b43_radio_maskset(dev, 0x52, 0xFFF0, (tx_bias & 0x000F)); + } + b43_lo_g_adjust(dev); +} + +/* GPHY_TSSI_Power_Lookup_Table_Init */ +static void b43_gphy_tssi_power_lt_init(struct b43_wldev *dev) +{ + struct b43_phy_g *gphy = dev->phy.g; + int i; + u16 value; + + for (i = 0; i < 32; i++) + b43_ofdmtab_write16(dev, 0x3C20, i, gphy->tssi2dbm[i]); + for (i = 32; i < 64; i++) + b43_ofdmtab_write16(dev, 0x3C00, i - 32, gphy->tssi2dbm[i]); + for (i = 0; i < 64; i += 2) { + value = (u16) gphy->tssi2dbm[i]; + value |= ((u16) gphy->tssi2dbm[i + 1]) << 8; + b43_phy_write(dev, 0x380 + (i / 2), value); + } +} + +/* GPHY_Gain_Lookup_Table_Init */ +static void b43_gphy_gain_lt_init(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + struct b43_txpower_lo_control *lo = gphy->lo_control; + u16 nr_written = 0; + u16 tmp; + u8 rf, bb; + + for (rf = 0; rf < lo->rfatt_list.len; rf++) { + for (bb = 0; bb < lo->bbatt_list.len; bb++) { + if (nr_written >= 0x40) + return; + tmp = lo->bbatt_list.list[bb].att; + tmp <<= 8; + if (phy->radio_rev == 8) + tmp |= 0x50; + else + tmp |= 0x40; + tmp |= lo->rfatt_list.list[rf].att; + b43_phy_write(dev, 0x3C0 + nr_written, tmp); + nr_written++; + } + } +} + +static void b43_set_all_gains(struct b43_wldev *dev, + s16 first, s16 second, s16 third) +{ + struct b43_phy *phy = &dev->phy; + u16 i; + u16 start = 0x08, end = 0x18; + u16 tmp; + u16 table; + + if (phy->rev <= 1) { + start = 0x10; + end = 0x20; + } + + table = B43_OFDMTAB_GAINX; + if (phy->rev <= 1) + table = B43_OFDMTAB_GAINX_R1; + for (i = 0; i < 4; i++) + b43_ofdmtab_write16(dev, table, i, first); + + for (i = start; i < end; i++) + b43_ofdmtab_write16(dev, table, i, second); + + if (third != -1) { + tmp = ((u16) third << 14) | ((u16) third << 6); + b43_phy_maskset(dev, 0x04A0, 0xBFBF, tmp); + b43_phy_maskset(dev, 0x04A1, 0xBFBF, tmp); + b43_phy_maskset(dev, 0x04A2, 0xBFBF, tmp); + } + b43_dummy_transmission(dev, false, true); +} + +static void b43_set_original_gains(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + u16 i, tmp; + u16 table; + u16 start = 0x0008, end = 0x0018; + + if (phy->rev <= 1) { + start = 0x0010; + end = 0x0020; + } + + table = B43_OFDMTAB_GAINX; + if (phy->rev <= 1) + table = B43_OFDMTAB_GAINX_R1; + for (i = 0; i < 4; i++) { + tmp = (i & 0xFFFC); + tmp |= (i & 0x0001) << 1; + tmp |= (i & 0x0002) >> 1; + + b43_ofdmtab_write16(dev, table, i, tmp); + } + + for (i = start; i < end; i++) + b43_ofdmtab_write16(dev, table, i, i - start); + + b43_phy_maskset(dev, 0x04A0, 0xBFBF, 0x4040); + b43_phy_maskset(dev, 0x04A1, 0xBFBF, 0x4040); + b43_phy_maskset(dev, 0x04A2, 0xBFBF, 0x4000); + b43_dummy_transmission(dev, false, true); +} + +/* http://bcm-specs.sipsolutions.net/NRSSILookupTable */ +static void b43_nrssi_hw_write(struct b43_wldev *dev, u16 offset, s16 val) +{ + b43_phy_write(dev, B43_PHY_NRSSILT_CTRL, offset); + b43_phy_write(dev, B43_PHY_NRSSILT_DATA, (u16) val); +} + +/* http://bcm-specs.sipsolutions.net/NRSSILookupTable */ +static s16 b43_nrssi_hw_read(struct b43_wldev *dev, u16 offset) +{ + u16 val; + + b43_phy_write(dev, B43_PHY_NRSSILT_CTRL, offset); + val = b43_phy_read(dev, B43_PHY_NRSSILT_DATA); + + return (s16) val; +} + +/* http://bcm-specs.sipsolutions.net/NRSSILookupTable */ +static void b43_nrssi_hw_update(struct b43_wldev *dev, u16 val) +{ + u16 i; + s16 tmp; + + for (i = 0; i < 64; i++) { + tmp = b43_nrssi_hw_read(dev, i); + tmp -= val; + tmp = clamp_val(tmp, -32, 31); + b43_nrssi_hw_write(dev, i, tmp); + } +} + +/* http://bcm-specs.sipsolutions.net/NRSSILookupTable */ +static void b43_nrssi_mem_update(struct b43_wldev *dev) +{ + struct b43_phy_g *gphy = dev->phy.g; + s16 i, delta; + s32 tmp; + + delta = 0x1F - gphy->nrssi[0]; + for (i = 0; i < 64; i++) { + tmp = (i - delta) * gphy->nrssislope; + tmp /= 0x10000; + tmp += 0x3A; + tmp = clamp_val(tmp, 0, 0x3F); + gphy->nrssi_lt[i] = tmp; + } +} + +static void b43_calc_nrssi_offset(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + u16 backup[20] = { 0 }; + s16 v47F; + u16 i; + u16 saved = 0xFFFF; + + backup[0] = b43_phy_read(dev, 0x0001); + backup[1] = b43_phy_read(dev, 0x0811); + backup[2] = b43_phy_read(dev, 0x0812); + if (phy->rev != 1) { /* Not in specs, but needed to prevent PPC machine check */ + backup[3] = b43_phy_read(dev, 0x0814); + backup[4] = b43_phy_read(dev, 0x0815); + } + backup[5] = b43_phy_read(dev, 0x005A); + backup[6] = b43_phy_read(dev, 0x0059); + backup[7] = b43_phy_read(dev, 0x0058); + backup[8] = b43_phy_read(dev, 0x000A); + backup[9] = b43_phy_read(dev, 0x0003); + backup[10] = b43_radio_read16(dev, 0x007A); + backup[11] = b43_radio_read16(dev, 0x0043); + + b43_phy_mask(dev, 0x0429, 0x7FFF); + b43_phy_maskset(dev, 0x0001, 0x3FFF, 0x4000); + b43_phy_set(dev, 0x0811, 0x000C); + b43_phy_maskset(dev, 0x0812, 0xFFF3, 0x0004); + b43_phy_mask(dev, 0x0802, ~(0x1 | 0x2)); + if (phy->rev >= 6) { + backup[12] = b43_phy_read(dev, 0x002E); + backup[13] = b43_phy_read(dev, 0x002F); + backup[14] = b43_phy_read(dev, 0x080F); + backup[15] = b43_phy_read(dev, 0x0810); + backup[16] = b43_phy_read(dev, 0x0801); + backup[17] = b43_phy_read(dev, 0x0060); + backup[18] = b43_phy_read(dev, 0x0014); + backup[19] = b43_phy_read(dev, 0x0478); + + b43_phy_write(dev, 0x002E, 0); + b43_phy_write(dev, 0x002F, 0); + b43_phy_write(dev, 0x080F, 0); + b43_phy_write(dev, 0x0810, 0); + b43_phy_set(dev, 0x0478, 0x0100); + b43_phy_set(dev, 0x0801, 0x0040); + b43_phy_set(dev, 0x0060, 0x0040); + b43_phy_set(dev, 0x0014, 0x0200); + } + b43_radio_set(dev, 0x007A, 0x0070); + b43_radio_set(dev, 0x007A, 0x0080); + udelay(30); + + v47F = (s16) ((b43_phy_read(dev, 0x047F) >> 8) & 0x003F); + if (v47F >= 0x20) + v47F -= 0x40; + if (v47F == 31) { + for (i = 7; i >= 4; i--) { + b43_radio_write16(dev, 0x007B, i); + udelay(20); + v47F = + (s16) ((b43_phy_read(dev, 0x047F) >> 8) & 0x003F); + if (v47F >= 0x20) + v47F -= 0x40; + if (v47F < 31 && saved == 0xFFFF) + saved = i; + } + if (saved == 0xFFFF) + saved = 4; + } else { + b43_radio_mask(dev, 0x007A, 0x007F); + if (phy->rev != 1) { /* Not in specs, but needed to prevent PPC machine check */ + b43_phy_set(dev, 0x0814, 0x0001); + b43_phy_mask(dev, 0x0815, 0xFFFE); + } + b43_phy_set(dev, 0x0811, 0x000C); + b43_phy_set(dev, 0x0812, 0x000C); + b43_phy_set(dev, 0x0811, 0x0030); + b43_phy_set(dev, 0x0812, 0x0030); + b43_phy_write(dev, 0x005A, 0x0480); + b43_phy_write(dev, 0x0059, 0x0810); + b43_phy_write(dev, 0x0058, 0x000D); + if (phy->rev == 0) { + b43_phy_write(dev, 0x0003, 0x0122); + } else { + b43_phy_set(dev, 0x000A, 0x2000); + } + if (phy->rev != 1) { /* Not in specs, but needed to prevent PPC machine check */ + b43_phy_set(dev, 0x0814, 0x0004); + b43_phy_mask(dev, 0x0815, 0xFFFB); + } + b43_phy_maskset(dev, 0x0003, 0xFF9F, 0x0040); + b43_radio_set(dev, 0x007A, 0x000F); + b43_set_all_gains(dev, 3, 0, 1); + b43_radio_maskset(dev, 0x0043, 0x00F0, 0x000F); + udelay(30); + v47F = (s16) ((b43_phy_read(dev, 0x047F) >> 8) & 0x003F); + if (v47F >= 0x20) + v47F -= 0x40; + if (v47F == -32) { + for (i = 0; i < 4; i++) { + b43_radio_write16(dev, 0x007B, i); + udelay(20); + v47F = + (s16) ((b43_phy_read(dev, 0x047F) >> 8) & + 0x003F); + if (v47F >= 0x20) + v47F -= 0x40; + if (v47F > -31 && saved == 0xFFFF) + saved = i; + } + if (saved == 0xFFFF) + saved = 3; + } else + saved = 0; + } + b43_radio_write16(dev, 0x007B, saved); + + if (phy->rev >= 6) { + b43_phy_write(dev, 0x002E, backup[12]); + b43_phy_write(dev, 0x002F, backup[13]); + b43_phy_write(dev, 0x080F, backup[14]); + b43_phy_write(dev, 0x0810, backup[15]); + } + if (phy->rev != 1) { /* Not in specs, but needed to prevent PPC machine check */ + b43_phy_write(dev, 0x0814, backup[3]); + b43_phy_write(dev, 0x0815, backup[4]); + } + b43_phy_write(dev, 0x005A, backup[5]); + b43_phy_write(dev, 0x0059, backup[6]); + b43_phy_write(dev, 0x0058, backup[7]); + b43_phy_write(dev, 0x000A, backup[8]); + b43_phy_write(dev, 0x0003, backup[9]); + b43_radio_write16(dev, 0x0043, backup[11]); + b43_radio_write16(dev, 0x007A, backup[10]); + b43_phy_write(dev, 0x0802, b43_phy_read(dev, 0x0802) | 0x1 | 0x2); + b43_phy_set(dev, 0x0429, 0x8000); + b43_set_original_gains(dev); + if (phy->rev >= 6) { + b43_phy_write(dev, 0x0801, backup[16]); + b43_phy_write(dev, 0x0060, backup[17]); + b43_phy_write(dev, 0x0014, backup[18]); + b43_phy_write(dev, 0x0478, backup[19]); + } + b43_phy_write(dev, 0x0001, backup[0]); + b43_phy_write(dev, 0x0812, backup[2]); + b43_phy_write(dev, 0x0811, backup[1]); +} + +static void b43_calc_nrssi_slope(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + u16 backup[18] = { 0 }; + u16 tmp; + s16 nrssi0, nrssi1; + + B43_WARN_ON(phy->type != B43_PHYTYPE_G); + + if (phy->radio_rev >= 9) + return; + if (phy->radio_rev == 8) + b43_calc_nrssi_offset(dev); + + b43_phy_mask(dev, B43_PHY_G_CRS, 0x7FFF); + b43_phy_mask(dev, 0x0802, 0xFFFC); + backup[7] = b43_read16(dev, 0x03E2); + b43_write16(dev, 0x03E2, b43_read16(dev, 0x03E2) | 0x8000); + backup[0] = b43_radio_read16(dev, 0x007A); + backup[1] = b43_radio_read16(dev, 0x0052); + backup[2] = b43_radio_read16(dev, 0x0043); + backup[3] = b43_phy_read(dev, 0x0015); + backup[4] = b43_phy_read(dev, 0x005A); + backup[5] = b43_phy_read(dev, 0x0059); + backup[6] = b43_phy_read(dev, 0x0058); + backup[8] = b43_read16(dev, 0x03E6); + backup[9] = b43_read16(dev, B43_MMIO_CHANNEL_EXT); + if (phy->rev >= 3) { + backup[10] = b43_phy_read(dev, 0x002E); + backup[11] = b43_phy_read(dev, 0x002F); + backup[12] = b43_phy_read(dev, 0x080F); + backup[13] = b43_phy_read(dev, B43_PHY_G_LO_CONTROL); + backup[14] = b43_phy_read(dev, 0x0801); + backup[15] = b43_phy_read(dev, 0x0060); + backup[16] = b43_phy_read(dev, 0x0014); + backup[17] = b43_phy_read(dev, 0x0478); + b43_phy_write(dev, 0x002E, 0); + b43_phy_write(dev, B43_PHY_G_LO_CONTROL, 0); + switch (phy->rev) { + case 4: + case 6: + case 7: + b43_phy_set(dev, 0x0478, 0x0100); + b43_phy_set(dev, 0x0801, 0x0040); + break; + case 3: + case 5: + b43_phy_mask(dev, 0x0801, 0xFFBF); + break; + } + b43_phy_set(dev, 0x0060, 0x0040); + b43_phy_set(dev, 0x0014, 0x0200); + } + b43_radio_set(dev, 0x007A, 0x0070); + b43_set_all_gains(dev, 0, 8, 0); + b43_radio_mask(dev, 0x007A, 0x00F7); + if (phy->rev >= 2) { + b43_phy_maskset(dev, 0x0811, 0xFFCF, 0x0030); + b43_phy_maskset(dev, 0x0812, 0xFFCF, 0x0010); + } + b43_radio_set(dev, 0x007A, 0x0080); + udelay(20); + + nrssi0 = (s16) ((b43_phy_read(dev, 0x047F) >> 8) & 0x003F); + if (nrssi0 >= 0x0020) + nrssi0 -= 0x0040; + + b43_radio_mask(dev, 0x007A, 0x007F); + if (phy->rev >= 2) { + b43_phy_maskset(dev, 0x0003, 0xFF9F, 0x0040); + } + + b43_write16(dev, B43_MMIO_CHANNEL_EXT, + b43_read16(dev, B43_MMIO_CHANNEL_EXT) + | 0x2000); + b43_radio_set(dev, 0x007A, 0x000F); + b43_phy_write(dev, 0x0015, 0xF330); + if (phy->rev >= 2) { + b43_phy_maskset(dev, 0x0812, 0xFFCF, 0x0020); + b43_phy_maskset(dev, 0x0811, 0xFFCF, 0x0020); + } + + b43_set_all_gains(dev, 3, 0, 1); + if (phy->radio_rev == 8) { + b43_radio_write16(dev, 0x0043, 0x001F); + } else { + tmp = b43_radio_read16(dev, 0x0052) & 0xFF0F; + b43_radio_write16(dev, 0x0052, tmp | 0x0060); + tmp = b43_radio_read16(dev, 0x0043) & 0xFFF0; + b43_radio_write16(dev, 0x0043, tmp | 0x0009); + } + b43_phy_write(dev, 0x005A, 0x0480); + b43_phy_write(dev, 0x0059, 0x0810); + b43_phy_write(dev, 0x0058, 0x000D); + udelay(20); + nrssi1 = (s16) ((b43_phy_read(dev, 0x047F) >> 8) & 0x003F); + if (nrssi1 >= 0x0020) + nrssi1 -= 0x0040; + if (nrssi0 == nrssi1) + gphy->nrssislope = 0x00010000; + else + gphy->nrssislope = 0x00400000 / (nrssi0 - nrssi1); + if (nrssi0 >= -4) { + gphy->nrssi[0] = nrssi1; + gphy->nrssi[1] = nrssi0; + } + if (phy->rev >= 3) { + b43_phy_write(dev, 0x002E, backup[10]); + b43_phy_write(dev, 0x002F, backup[11]); + b43_phy_write(dev, 0x080F, backup[12]); + b43_phy_write(dev, B43_PHY_G_LO_CONTROL, backup[13]); + } + if (phy->rev >= 2) { + b43_phy_mask(dev, 0x0812, 0xFFCF); + b43_phy_mask(dev, 0x0811, 0xFFCF); + } + + b43_radio_write16(dev, 0x007A, backup[0]); + b43_radio_write16(dev, 0x0052, backup[1]); + b43_radio_write16(dev, 0x0043, backup[2]); + b43_write16(dev, 0x03E2, backup[7]); + b43_write16(dev, 0x03E6, backup[8]); + b43_write16(dev, B43_MMIO_CHANNEL_EXT, backup[9]); + b43_phy_write(dev, 0x0015, backup[3]); + b43_phy_write(dev, 0x005A, backup[4]); + b43_phy_write(dev, 0x0059, backup[5]); + b43_phy_write(dev, 0x0058, backup[6]); + b43_synth_pu_workaround(dev, phy->channel); + b43_phy_set(dev, 0x0802, (0x0001 | 0x0002)); + b43_set_original_gains(dev); + b43_phy_set(dev, B43_PHY_G_CRS, 0x8000); + if (phy->rev >= 3) { + b43_phy_write(dev, 0x0801, backup[14]); + b43_phy_write(dev, 0x0060, backup[15]); + b43_phy_write(dev, 0x0014, backup[16]); + b43_phy_write(dev, 0x0478, backup[17]); + } + b43_nrssi_mem_update(dev); + b43_calc_nrssi_threshold(dev); +} + +static void b43_calc_nrssi_threshold(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + s32 a, b; + s16 tmp16; + u16 tmp_u16; + + B43_WARN_ON(phy->type != B43_PHYTYPE_G); + + if (!phy->gmode || + !(dev->dev->bus_sprom->boardflags_lo & B43_BFL_RSSI)) { + tmp16 = b43_nrssi_hw_read(dev, 0x20); + if (tmp16 >= 0x20) + tmp16 -= 0x40; + if (tmp16 < 3) { + b43_phy_maskset(dev, 0x048A, 0xF000, 0x09EB); + } else { + b43_phy_maskset(dev, 0x048A, 0xF000, 0x0AED); + } + } else { + if (gphy->interfmode == B43_INTERFMODE_NONWLAN) { + a = 0xE; + b = 0xA; + } else if (!gphy->aci_wlan_automatic && gphy->aci_enable) { + a = 0x13; + b = 0x12; + } else { + a = 0xE; + b = 0x11; + } + + a = a * (gphy->nrssi[1] - gphy->nrssi[0]); + a += (gphy->nrssi[0] << 6); + if (a < 32) + a += 31; + else + a += 32; + a = a >> 6; + a = clamp_val(a, -31, 31); + + b = b * (gphy->nrssi[1] - gphy->nrssi[0]); + b += (gphy->nrssi[0] << 6); + if (b < 32) + b += 31; + else + b += 32; + b = b >> 6; + b = clamp_val(b, -31, 31); + + tmp_u16 = b43_phy_read(dev, 0x048A) & 0xF000; + tmp_u16 |= ((u32) b & 0x0000003F); + tmp_u16 |= (((u32) a & 0x0000003F) << 6); + b43_phy_write(dev, 0x048A, tmp_u16); + } +} + +/* Stack implementation to save/restore values from the + * interference mitigation code. + * It is save to restore values in random order. + */ +static void _stack_save(u32 *_stackptr, size_t *stackidx, + u8 id, u16 offset, u16 value) +{ + u32 *stackptr = &(_stackptr[*stackidx]); + + B43_WARN_ON(offset & 0xF000); + B43_WARN_ON(id & 0xF0); + *stackptr = offset; + *stackptr |= ((u32) id) << 12; + *stackptr |= ((u32) value) << 16; + (*stackidx)++; + B43_WARN_ON(*stackidx >= B43_INTERFSTACK_SIZE); +} + +static u16 _stack_restore(u32 *stackptr, u8 id, u16 offset) +{ + size_t i; + + B43_WARN_ON(offset & 0xF000); + B43_WARN_ON(id & 0xF0); + for (i = 0; i < B43_INTERFSTACK_SIZE; i++, stackptr++) { + if ((*stackptr & 0x00000FFF) != offset) + continue; + if (((*stackptr & 0x0000F000) >> 12) != id) + continue; + return ((*stackptr & 0xFFFF0000) >> 16); + } + B43_WARN_ON(1); + + return 0; +} + +#define phy_stacksave(offset) \ + do { \ + _stack_save(stack, &stackidx, 0x1, (offset), \ + b43_phy_read(dev, (offset))); \ + } while (0) +#define phy_stackrestore(offset) \ + do { \ + b43_phy_write(dev, (offset), \ + _stack_restore(stack, 0x1, \ + (offset))); \ + } while (0) +#define radio_stacksave(offset) \ + do { \ + _stack_save(stack, &stackidx, 0x2, (offset), \ + b43_radio_read16(dev, (offset))); \ + } while (0) +#define radio_stackrestore(offset) \ + do { \ + b43_radio_write16(dev, (offset), \ + _stack_restore(stack, 0x2, \ + (offset))); \ + } while (0) +#define ofdmtab_stacksave(table, offset) \ + do { \ + _stack_save(stack, &stackidx, 0x3, (offset)|(table), \ + b43_ofdmtab_read16(dev, (table), (offset))); \ + } while (0) +#define ofdmtab_stackrestore(table, offset) \ + do { \ + b43_ofdmtab_write16(dev, (table), (offset), \ + _stack_restore(stack, 0x3, \ + (offset)|(table))); \ + } while (0) + +static void +b43_radio_interference_mitigation_enable(struct b43_wldev *dev, int mode) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + u16 tmp, flipped; + size_t stackidx = 0; + u32 *stack = gphy->interfstack; + + switch (mode) { + case B43_INTERFMODE_NONWLAN: + if (phy->rev != 1) { + b43_phy_set(dev, 0x042B, 0x0800); + b43_phy_mask(dev, B43_PHY_G_CRS, ~0x4000); + break; + } + radio_stacksave(0x0078); + tmp = (b43_radio_read16(dev, 0x0078) & 0x001E); + B43_WARN_ON(tmp > 15); + flipped = bitrev4(tmp); + if (flipped < 10 && flipped >= 8) + flipped = 7; + else if (flipped >= 10) + flipped -= 3; + flipped = (bitrev4(flipped) << 1) | 0x0020; + b43_radio_write16(dev, 0x0078, flipped); + + b43_calc_nrssi_threshold(dev); + + phy_stacksave(0x0406); + b43_phy_write(dev, 0x0406, 0x7E28); + + b43_phy_set(dev, 0x042B, 0x0800); + b43_phy_set(dev, B43_PHY_RADIO_BITFIELD, 0x1000); + + phy_stacksave(0x04A0); + b43_phy_maskset(dev, 0x04A0, 0xC0C0, 0x0008); + phy_stacksave(0x04A1); + b43_phy_maskset(dev, 0x04A1, 0xC0C0, 0x0605); + phy_stacksave(0x04A2); + b43_phy_maskset(dev, 0x04A2, 0xC0C0, 0x0204); + phy_stacksave(0x04A8); + b43_phy_maskset(dev, 0x04A8, 0xC0C0, 0x0803); + phy_stacksave(0x04AB); + b43_phy_maskset(dev, 0x04AB, 0xC0C0, 0x0605); + + phy_stacksave(0x04A7); + b43_phy_write(dev, 0x04A7, 0x0002); + phy_stacksave(0x04A3); + b43_phy_write(dev, 0x04A3, 0x287A); + phy_stacksave(0x04A9); + b43_phy_write(dev, 0x04A9, 0x2027); + phy_stacksave(0x0493); + b43_phy_write(dev, 0x0493, 0x32F5); + phy_stacksave(0x04AA); + b43_phy_write(dev, 0x04AA, 0x2027); + phy_stacksave(0x04AC); + b43_phy_write(dev, 0x04AC, 0x32F5); + break; + case B43_INTERFMODE_MANUALWLAN: + if (b43_phy_read(dev, 0x0033) & 0x0800) + break; + + gphy->aci_enable = true; + + phy_stacksave(B43_PHY_RADIO_BITFIELD); + phy_stacksave(B43_PHY_G_CRS); + if (phy->rev < 2) { + phy_stacksave(0x0406); + } else { + phy_stacksave(0x04C0); + phy_stacksave(0x04C1); + } + phy_stacksave(0x0033); + phy_stacksave(0x04A7); + phy_stacksave(0x04A3); + phy_stacksave(0x04A9); + phy_stacksave(0x04AA); + phy_stacksave(0x04AC); + phy_stacksave(0x0493); + phy_stacksave(0x04A1); + phy_stacksave(0x04A0); + phy_stacksave(0x04A2); + phy_stacksave(0x048A); + phy_stacksave(0x04A8); + phy_stacksave(0x04AB); + if (phy->rev == 2) { + phy_stacksave(0x04AD); + phy_stacksave(0x04AE); + } else if (phy->rev >= 3) { + phy_stacksave(0x04AD); + phy_stacksave(0x0415); + phy_stacksave(0x0416); + phy_stacksave(0x0417); + ofdmtab_stacksave(0x1A00, 0x2); + ofdmtab_stacksave(0x1A00, 0x3); + } + phy_stacksave(0x042B); + phy_stacksave(0x048C); + + b43_phy_mask(dev, B43_PHY_RADIO_BITFIELD, ~0x1000); + b43_phy_maskset(dev, B43_PHY_G_CRS, 0xFFFC, 0x0002); + + b43_phy_write(dev, 0x0033, 0x0800); + b43_phy_write(dev, 0x04A3, 0x2027); + b43_phy_write(dev, 0x04A9, 0x1CA8); + b43_phy_write(dev, 0x0493, 0x287A); + b43_phy_write(dev, 0x04AA, 0x1CA8); + b43_phy_write(dev, 0x04AC, 0x287A); + + b43_phy_maskset(dev, 0x04A0, 0xFFC0, 0x001A); + b43_phy_write(dev, 0x04A7, 0x000D); + + if (phy->rev < 2) { + b43_phy_write(dev, 0x0406, 0xFF0D); + } else if (phy->rev == 2) { + b43_phy_write(dev, 0x04C0, 0xFFFF); + b43_phy_write(dev, 0x04C1, 0x00A9); + } else { + b43_phy_write(dev, 0x04C0, 0x00C1); + b43_phy_write(dev, 0x04C1, 0x0059); + } + + b43_phy_maskset(dev, 0x04A1, 0xC0FF, 0x1800); + b43_phy_maskset(dev, 0x04A1, 0xFFC0, 0x0015); + b43_phy_maskset(dev, 0x04A8, 0xCFFF, 0x1000); + b43_phy_maskset(dev, 0x04A8, 0xF0FF, 0x0A00); + b43_phy_maskset(dev, 0x04AB, 0xCFFF, 0x1000); + b43_phy_maskset(dev, 0x04AB, 0xF0FF, 0x0800); + b43_phy_maskset(dev, 0x04AB, 0xFFCF, 0x0010); + b43_phy_maskset(dev, 0x04AB, 0xFFF0, 0x0005); + b43_phy_maskset(dev, 0x04A8, 0xFFCF, 0x0010); + b43_phy_maskset(dev, 0x04A8, 0xFFF0, 0x0006); + b43_phy_maskset(dev, 0x04A2, 0xF0FF, 0x0800); + b43_phy_maskset(dev, 0x04A0, 0xF0FF, 0x0500); + b43_phy_maskset(dev, 0x04A2, 0xFFF0, 0x000B); + + if (phy->rev >= 3) { + b43_phy_mask(dev, 0x048A, 0x7FFF); + b43_phy_maskset(dev, 0x0415, 0x8000, 0x36D8); + b43_phy_maskset(dev, 0x0416, 0x8000, 0x36D8); + b43_phy_maskset(dev, 0x0417, 0xFE00, 0x016D); + } else { + b43_phy_set(dev, 0x048A, 0x1000); + b43_phy_maskset(dev, 0x048A, 0x9FFF, 0x2000); + b43_hf_write(dev, b43_hf_read(dev) | B43_HF_ACIW); + } + if (phy->rev >= 2) { + b43_phy_set(dev, 0x042B, 0x0800); + } + b43_phy_maskset(dev, 0x048C, 0xF0FF, 0x0200); + if (phy->rev == 2) { + b43_phy_maskset(dev, 0x04AE, 0xFF00, 0x007F); + b43_phy_maskset(dev, 0x04AD, 0x00FF, 0x1300); + } else if (phy->rev >= 6) { + b43_ofdmtab_write16(dev, 0x1A00, 0x3, 0x007F); + b43_ofdmtab_write16(dev, 0x1A00, 0x2, 0x007F); + b43_phy_mask(dev, 0x04AD, 0x00FF); + } + b43_calc_nrssi_slope(dev); + break; + default: + B43_WARN_ON(1); + } +} + +static void +b43_radio_interference_mitigation_disable(struct b43_wldev *dev, int mode) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + u32 *stack = gphy->interfstack; + + switch (mode) { + case B43_INTERFMODE_NONWLAN: + if (phy->rev != 1) { + b43_phy_mask(dev, 0x042B, ~0x0800); + b43_phy_set(dev, B43_PHY_G_CRS, 0x4000); + break; + } + radio_stackrestore(0x0078); + b43_calc_nrssi_threshold(dev); + phy_stackrestore(0x0406); + b43_phy_mask(dev, 0x042B, ~0x0800); + if (!dev->bad_frames_preempt) { + b43_phy_mask(dev, B43_PHY_RADIO_BITFIELD, ~(1 << 11)); + } + b43_phy_set(dev, B43_PHY_G_CRS, 0x4000); + phy_stackrestore(0x04A0); + phy_stackrestore(0x04A1); + phy_stackrestore(0x04A2); + phy_stackrestore(0x04A8); + phy_stackrestore(0x04AB); + phy_stackrestore(0x04A7); + phy_stackrestore(0x04A3); + phy_stackrestore(0x04A9); + phy_stackrestore(0x0493); + phy_stackrestore(0x04AA); + phy_stackrestore(0x04AC); + break; + case B43_INTERFMODE_MANUALWLAN: + if (!(b43_phy_read(dev, 0x0033) & 0x0800)) + break; + + gphy->aci_enable = false; + + phy_stackrestore(B43_PHY_RADIO_BITFIELD); + phy_stackrestore(B43_PHY_G_CRS); + phy_stackrestore(0x0033); + phy_stackrestore(0x04A3); + phy_stackrestore(0x04A9); + phy_stackrestore(0x0493); + phy_stackrestore(0x04AA); + phy_stackrestore(0x04AC); + phy_stackrestore(0x04A0); + phy_stackrestore(0x04A7); + if (phy->rev >= 2) { + phy_stackrestore(0x04C0); + phy_stackrestore(0x04C1); + } else + phy_stackrestore(0x0406); + phy_stackrestore(0x04A1); + phy_stackrestore(0x04AB); + phy_stackrestore(0x04A8); + if (phy->rev == 2) { + phy_stackrestore(0x04AD); + phy_stackrestore(0x04AE); + } else if (phy->rev >= 3) { + phy_stackrestore(0x04AD); + phy_stackrestore(0x0415); + phy_stackrestore(0x0416); + phy_stackrestore(0x0417); + ofdmtab_stackrestore(0x1A00, 0x2); + ofdmtab_stackrestore(0x1A00, 0x3); + } + phy_stackrestore(0x04A2); + phy_stackrestore(0x048A); + phy_stackrestore(0x042B); + phy_stackrestore(0x048C); + b43_hf_write(dev, b43_hf_read(dev) & ~B43_HF_ACIW); + b43_calc_nrssi_slope(dev); + break; + default: + B43_WARN_ON(1); + } +} + +#undef phy_stacksave +#undef phy_stackrestore +#undef radio_stacksave +#undef radio_stackrestore +#undef ofdmtab_stacksave +#undef ofdmtab_stackrestore + +static u16 b43_radio_core_calibration_value(struct b43_wldev *dev) +{ + u16 reg, index, ret; + + static const u8 rcc_table[] = { + 0x02, 0x03, 0x01, 0x0F, + 0x06, 0x07, 0x05, 0x0F, + 0x0A, 0x0B, 0x09, 0x0F, + 0x0E, 0x0F, 0x0D, 0x0F, + }; + + reg = b43_radio_read16(dev, 0x60); + index = (reg & 0x001E) >> 1; + ret = rcc_table[index] << 1; + ret |= (reg & 0x0001); + ret |= 0x0020; + + return ret; +} + +#define LPD(L, P, D) (((L) << 2) | ((P) << 1) | ((D) << 0)) +static u16 radio2050_rfover_val(struct b43_wldev *dev, + u16 phy_register, unsigned int lpd) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + struct ssb_sprom *sprom = dev->dev->bus_sprom; + + if (!phy->gmode) + return 0; + + if (has_loopback_gain(phy)) { + int max_lb_gain = gphy->max_lb_gain; + u16 extlna; + u16 i; + + if (phy->radio_rev == 8) + max_lb_gain += 0x3E; + else + max_lb_gain += 0x26; + if (max_lb_gain >= 0x46) { + extlna = 0x3000; + max_lb_gain -= 0x46; + } else if (max_lb_gain >= 0x3A) { + extlna = 0x1000; + max_lb_gain -= 0x3A; + } else if (max_lb_gain >= 0x2E) { + extlna = 0x2000; + max_lb_gain -= 0x2E; + } else { + extlna = 0; + max_lb_gain -= 0x10; + } + + for (i = 0; i < 16; i++) { + max_lb_gain -= (i * 6); + if (max_lb_gain < 6) + break; + } + + if ((phy->rev < 7) || + !(sprom->boardflags_lo & B43_BFL_EXTLNA)) { + if (phy_register == B43_PHY_RFOVER) { + return 0x1B3; + } else if (phy_register == B43_PHY_RFOVERVAL) { + extlna |= (i << 8); + switch (lpd) { + case LPD(0, 1, 1): + return 0x0F92; + case LPD(0, 0, 1): + case LPD(1, 0, 1): + return (0x0092 | extlna); + case LPD(1, 0, 0): + return (0x0093 | extlna); + } + B43_WARN_ON(1); + } + B43_WARN_ON(1); + } else { + if (phy_register == B43_PHY_RFOVER) { + return 0x9B3; + } else if (phy_register == B43_PHY_RFOVERVAL) { + if (extlna) + extlna |= 0x8000; + extlna |= (i << 8); + switch (lpd) { + case LPD(0, 1, 1): + return 0x8F92; + case LPD(0, 0, 1): + return (0x8092 | extlna); + case LPD(1, 0, 1): + return (0x2092 | extlna); + case LPD(1, 0, 0): + return (0x2093 | extlna); + } + B43_WARN_ON(1); + } + B43_WARN_ON(1); + } + } else { + if ((phy->rev < 7) || + !(sprom->boardflags_lo & B43_BFL_EXTLNA)) { + if (phy_register == B43_PHY_RFOVER) { + return 0x1B3; + } else if (phy_register == B43_PHY_RFOVERVAL) { + switch (lpd) { + case LPD(0, 1, 1): + return 0x0FB2; + case LPD(0, 0, 1): + return 0x00B2; + case LPD(1, 0, 1): + return 0x30B2; + case LPD(1, 0, 0): + return 0x30B3; + } + B43_WARN_ON(1); + } + B43_WARN_ON(1); + } else { + if (phy_register == B43_PHY_RFOVER) { + return 0x9B3; + } else if (phy_register == B43_PHY_RFOVERVAL) { + switch (lpd) { + case LPD(0, 1, 1): + return 0x8FB2; + case LPD(0, 0, 1): + return 0x80B2; + case LPD(1, 0, 1): + return 0x20B2; + case LPD(1, 0, 0): + return 0x20B3; + } + B43_WARN_ON(1); + } + B43_WARN_ON(1); + } + } + return 0; +} + +struct init2050_saved_values { + /* Core registers */ + u16 reg_3EC; + u16 reg_3E6; + u16 reg_3F4; + /* Radio registers */ + u16 radio_43; + u16 radio_51; + u16 radio_52; + /* PHY registers */ + u16 phy_pgactl; + u16 phy_cck_5A; + u16 phy_cck_59; + u16 phy_cck_58; + u16 phy_cck_30; + u16 phy_rfover; + u16 phy_rfoverval; + u16 phy_analogover; + u16 phy_analogoverval; + u16 phy_crs0; + u16 phy_classctl; + u16 phy_lo_mask; + u16 phy_lo_ctl; + u16 phy_syncctl; +}; + +static u16 b43_radio_init2050(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct init2050_saved_values sav; + u16 rcc; + u16 radio78; + u16 ret; + u16 i, j; + u32 tmp1 = 0, tmp2 = 0; + + memset(&sav, 0, sizeof(sav)); /* get rid of "may be used uninitialized..." */ + + sav.radio_43 = b43_radio_read16(dev, 0x43); + sav.radio_51 = b43_radio_read16(dev, 0x51); + sav.radio_52 = b43_radio_read16(dev, 0x52); + sav.phy_pgactl = b43_phy_read(dev, B43_PHY_PGACTL); + sav.phy_cck_5A = b43_phy_read(dev, B43_PHY_CCK(0x5A)); + sav.phy_cck_59 = b43_phy_read(dev, B43_PHY_CCK(0x59)); + sav.phy_cck_58 = b43_phy_read(dev, B43_PHY_CCK(0x58)); + + if (phy->type == B43_PHYTYPE_B) { + sav.phy_cck_30 = b43_phy_read(dev, B43_PHY_CCK(0x30)); + sav.reg_3EC = b43_read16(dev, 0x3EC); + + b43_phy_write(dev, B43_PHY_CCK(0x30), 0xFF); + b43_write16(dev, 0x3EC, 0x3F3F); + } else if (phy->gmode || phy->rev >= 2) { + sav.phy_rfover = b43_phy_read(dev, B43_PHY_RFOVER); + sav.phy_rfoverval = b43_phy_read(dev, B43_PHY_RFOVERVAL); + sav.phy_analogover = b43_phy_read(dev, B43_PHY_ANALOGOVER); + sav.phy_analogoverval = + b43_phy_read(dev, B43_PHY_ANALOGOVERVAL); + sav.phy_crs0 = b43_phy_read(dev, B43_PHY_CRS0); + sav.phy_classctl = b43_phy_read(dev, B43_PHY_CLASSCTL); + + b43_phy_set(dev, B43_PHY_ANALOGOVER, 0x0003); + b43_phy_mask(dev, B43_PHY_ANALOGOVERVAL, 0xFFFC); + b43_phy_mask(dev, B43_PHY_CRS0, 0x7FFF); + b43_phy_mask(dev, B43_PHY_CLASSCTL, 0xFFFC); + if (has_loopback_gain(phy)) { + sav.phy_lo_mask = b43_phy_read(dev, B43_PHY_LO_MASK); + sav.phy_lo_ctl = b43_phy_read(dev, B43_PHY_LO_CTL); + + if (phy->rev >= 3) + b43_phy_write(dev, B43_PHY_LO_MASK, 0xC020); + else + b43_phy_write(dev, B43_PHY_LO_MASK, 0x8020); + b43_phy_write(dev, B43_PHY_LO_CTL, 0); + } + + b43_phy_write(dev, B43_PHY_RFOVERVAL, + radio2050_rfover_val(dev, B43_PHY_RFOVERVAL, + LPD(0, 1, 1))); + b43_phy_write(dev, B43_PHY_RFOVER, + radio2050_rfover_val(dev, B43_PHY_RFOVER, 0)); + } + b43_write16(dev, 0x3E2, b43_read16(dev, 0x3E2) | 0x8000); + + sav.phy_syncctl = b43_phy_read(dev, B43_PHY_SYNCCTL); + b43_phy_mask(dev, B43_PHY_SYNCCTL, 0xFF7F); + sav.reg_3E6 = b43_read16(dev, 0x3E6); + sav.reg_3F4 = b43_read16(dev, 0x3F4); + + if (phy->analog == 0) { + b43_write16(dev, 0x03E6, 0x0122); + } else { + if (phy->analog >= 2) { + b43_phy_maskset(dev, B43_PHY_CCK(0x03), 0xFFBF, 0x40); + } + b43_write16(dev, B43_MMIO_CHANNEL_EXT, + (b43_read16(dev, B43_MMIO_CHANNEL_EXT) | 0x2000)); + } + + rcc = b43_radio_core_calibration_value(dev); + + if (phy->type == B43_PHYTYPE_B) + b43_radio_write16(dev, 0x78, 0x26); + if (phy->gmode || phy->rev >= 2) { + b43_phy_write(dev, B43_PHY_RFOVERVAL, + radio2050_rfover_val(dev, B43_PHY_RFOVERVAL, + LPD(0, 1, 1))); + } + b43_phy_write(dev, B43_PHY_PGACTL, 0xBFAF); + b43_phy_write(dev, B43_PHY_CCK(0x2B), 0x1403); + if (phy->gmode || phy->rev >= 2) { + b43_phy_write(dev, B43_PHY_RFOVERVAL, + radio2050_rfover_val(dev, B43_PHY_RFOVERVAL, + LPD(0, 0, 1))); + } + b43_phy_write(dev, B43_PHY_PGACTL, 0xBFA0); + b43_radio_set(dev, 0x51, 0x0004); + if (phy->radio_rev == 8) { + b43_radio_write16(dev, 0x43, 0x1F); + } else { + b43_radio_write16(dev, 0x52, 0); + b43_radio_maskset(dev, 0x43, 0xFFF0, 0x0009); + } + b43_phy_write(dev, B43_PHY_CCK(0x58), 0); + + for (i = 0; i < 16; i++) { + b43_phy_write(dev, B43_PHY_CCK(0x5A), 0x0480); + b43_phy_write(dev, B43_PHY_CCK(0x59), 0xC810); + b43_phy_write(dev, B43_PHY_CCK(0x58), 0x000D); + if (phy->gmode || phy->rev >= 2) { + b43_phy_write(dev, B43_PHY_RFOVERVAL, + radio2050_rfover_val(dev, + B43_PHY_RFOVERVAL, + LPD(1, 0, 1))); + } + b43_phy_write(dev, B43_PHY_PGACTL, 0xAFB0); + udelay(10); + if (phy->gmode || phy->rev >= 2) { + b43_phy_write(dev, B43_PHY_RFOVERVAL, + radio2050_rfover_val(dev, + B43_PHY_RFOVERVAL, + LPD(1, 0, 1))); + } + b43_phy_write(dev, B43_PHY_PGACTL, 0xEFB0); + udelay(10); + if (phy->gmode || phy->rev >= 2) { + b43_phy_write(dev, B43_PHY_RFOVERVAL, + radio2050_rfover_val(dev, + B43_PHY_RFOVERVAL, + LPD(1, 0, 0))); + } + b43_phy_write(dev, B43_PHY_PGACTL, 0xFFF0); + udelay(20); + tmp1 += b43_phy_read(dev, B43_PHY_LO_LEAKAGE); + b43_phy_write(dev, B43_PHY_CCK(0x58), 0); + if (phy->gmode || phy->rev >= 2) { + b43_phy_write(dev, B43_PHY_RFOVERVAL, + radio2050_rfover_val(dev, + B43_PHY_RFOVERVAL, + LPD(1, 0, 1))); + } + b43_phy_write(dev, B43_PHY_PGACTL, 0xAFB0); + } + udelay(10); + + b43_phy_write(dev, B43_PHY_CCK(0x58), 0); + tmp1++; + tmp1 >>= 9; + + for (i = 0; i < 16; i++) { + radio78 = (bitrev4(i) << 1) | 0x0020; + b43_radio_write16(dev, 0x78, radio78); + udelay(10); + for (j = 0; j < 16; j++) { + b43_phy_write(dev, B43_PHY_CCK(0x5A), 0x0D80); + b43_phy_write(dev, B43_PHY_CCK(0x59), 0xC810); + b43_phy_write(dev, B43_PHY_CCK(0x58), 0x000D); + if (phy->gmode || phy->rev >= 2) { + b43_phy_write(dev, B43_PHY_RFOVERVAL, + radio2050_rfover_val(dev, + B43_PHY_RFOVERVAL, + LPD(1, 0, + 1))); + } + b43_phy_write(dev, B43_PHY_PGACTL, 0xAFB0); + udelay(10); + if (phy->gmode || phy->rev >= 2) { + b43_phy_write(dev, B43_PHY_RFOVERVAL, + radio2050_rfover_val(dev, + B43_PHY_RFOVERVAL, + LPD(1, 0, + 1))); + } + b43_phy_write(dev, B43_PHY_PGACTL, 0xEFB0); + udelay(10); + if (phy->gmode || phy->rev >= 2) { + b43_phy_write(dev, B43_PHY_RFOVERVAL, + radio2050_rfover_val(dev, + B43_PHY_RFOVERVAL, + LPD(1, 0, + 0))); + } + b43_phy_write(dev, B43_PHY_PGACTL, 0xFFF0); + udelay(10); + tmp2 += b43_phy_read(dev, B43_PHY_LO_LEAKAGE); + b43_phy_write(dev, B43_PHY_CCK(0x58), 0); + if (phy->gmode || phy->rev >= 2) { + b43_phy_write(dev, B43_PHY_RFOVERVAL, + radio2050_rfover_val(dev, + B43_PHY_RFOVERVAL, + LPD(1, 0, + 1))); + } + b43_phy_write(dev, B43_PHY_PGACTL, 0xAFB0); + } + tmp2++; + tmp2 >>= 8; + if (tmp1 < tmp2) + break; + } + + /* Restore the registers */ + b43_phy_write(dev, B43_PHY_PGACTL, sav.phy_pgactl); + b43_radio_write16(dev, 0x51, sav.radio_51); + b43_radio_write16(dev, 0x52, sav.radio_52); + b43_radio_write16(dev, 0x43, sav.radio_43); + b43_phy_write(dev, B43_PHY_CCK(0x5A), sav.phy_cck_5A); + b43_phy_write(dev, B43_PHY_CCK(0x59), sav.phy_cck_59); + b43_phy_write(dev, B43_PHY_CCK(0x58), sav.phy_cck_58); + b43_write16(dev, 0x3E6, sav.reg_3E6); + if (phy->analog != 0) + b43_write16(dev, 0x3F4, sav.reg_3F4); + b43_phy_write(dev, B43_PHY_SYNCCTL, sav.phy_syncctl); + b43_synth_pu_workaround(dev, phy->channel); + if (phy->type == B43_PHYTYPE_B) { + b43_phy_write(dev, B43_PHY_CCK(0x30), sav.phy_cck_30); + b43_write16(dev, 0x3EC, sav.reg_3EC); + } else if (phy->gmode) { + b43_write16(dev, B43_MMIO_PHY_RADIO, + b43_read16(dev, B43_MMIO_PHY_RADIO) + & 0x7FFF); + b43_phy_write(dev, B43_PHY_RFOVER, sav.phy_rfover); + b43_phy_write(dev, B43_PHY_RFOVERVAL, sav.phy_rfoverval); + b43_phy_write(dev, B43_PHY_ANALOGOVER, sav.phy_analogover); + b43_phy_write(dev, B43_PHY_ANALOGOVERVAL, + sav.phy_analogoverval); + b43_phy_write(dev, B43_PHY_CRS0, sav.phy_crs0); + b43_phy_write(dev, B43_PHY_CLASSCTL, sav.phy_classctl); + if (has_loopback_gain(phy)) { + b43_phy_write(dev, B43_PHY_LO_MASK, sav.phy_lo_mask); + b43_phy_write(dev, B43_PHY_LO_CTL, sav.phy_lo_ctl); + } + } + if (i > 15) + ret = radio78; + else + ret = rcc; + + return ret; +} + +static void b43_phy_initb5(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + u16 offset, value; + u8 old_channel; + + if (phy->analog == 1) { + b43_radio_set(dev, 0x007A, 0x0050); + } + if ((dev->dev->board_vendor != SSB_BOARDVENDOR_BCM) && + (dev->dev->board_type != SSB_BOARD_BU4306)) { + value = 0x2120; + for (offset = 0x00A8; offset < 0x00C7; offset++) { + b43_phy_write(dev, offset, value); + value += 0x202; + } + } + b43_phy_maskset(dev, 0x0035, 0xF0FF, 0x0700); + if (phy->radio_ver == 0x2050) + b43_phy_write(dev, 0x0038, 0x0667); + + if (phy->gmode || phy->rev >= 2) { + if (phy->radio_ver == 0x2050) { + b43_radio_set(dev, 0x007A, 0x0020); + b43_radio_set(dev, 0x0051, 0x0004); + } + b43_write16(dev, B43_MMIO_PHY_RADIO, 0x0000); + + b43_phy_set(dev, 0x0802, 0x0100); + b43_phy_set(dev, 0x042B, 0x2000); + + b43_phy_write(dev, 0x001C, 0x186A); + + b43_phy_maskset(dev, 0x0013, 0x00FF, 0x1900); + b43_phy_maskset(dev, 0x0035, 0xFFC0, 0x0064); + b43_phy_maskset(dev, 0x005D, 0xFF80, 0x000A); + } + + if (dev->bad_frames_preempt) { + b43_phy_set(dev, B43_PHY_RADIO_BITFIELD, (1 << 11)); + } + + if (phy->analog == 1) { + b43_phy_write(dev, 0x0026, 0xCE00); + b43_phy_write(dev, 0x0021, 0x3763); + b43_phy_write(dev, 0x0022, 0x1BC3); + b43_phy_write(dev, 0x0023, 0x06F9); + b43_phy_write(dev, 0x0024, 0x037E); + } else + b43_phy_write(dev, 0x0026, 0xCC00); + b43_phy_write(dev, 0x0030, 0x00C6); + b43_write16(dev, 0x03EC, 0x3F22); + + if (phy->analog == 1) + b43_phy_write(dev, 0x0020, 0x3E1C); + else + b43_phy_write(dev, 0x0020, 0x301C); + + if (phy->analog == 0) + b43_write16(dev, 0x03E4, 0x3000); + + old_channel = phy->channel; + /* Force to channel 7, even if not supported. */ + b43_gphy_channel_switch(dev, 7, 0); + + if (phy->radio_ver != 0x2050) { + b43_radio_write16(dev, 0x0075, 0x0080); + b43_radio_write16(dev, 0x0079, 0x0081); + } + + b43_radio_write16(dev, 0x0050, 0x0020); + b43_radio_write16(dev, 0x0050, 0x0023); + + if (phy->radio_ver == 0x2050) { + b43_radio_write16(dev, 0x0050, 0x0020); + b43_radio_write16(dev, 0x005A, 0x0070); + } + + b43_radio_write16(dev, 0x005B, 0x007B); + b43_radio_write16(dev, 0x005C, 0x00B0); + + b43_radio_set(dev, 0x007A, 0x0007); + + b43_gphy_channel_switch(dev, old_channel, 0); + + b43_phy_write(dev, 0x0014, 0x0080); + b43_phy_write(dev, 0x0032, 0x00CA); + b43_phy_write(dev, 0x002A, 0x88A3); + + b43_set_txpower_g(dev, &gphy->bbatt, &gphy->rfatt, gphy->tx_control); + + if (phy->radio_ver == 0x2050) + b43_radio_write16(dev, 0x005D, 0x000D); + + b43_write16(dev, 0x03E4, (b43_read16(dev, 0x03E4) & 0xFFC0) | 0x0004); +} + +static void b43_phy_initb6(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + u16 offset, val; + u8 old_channel; + + b43_phy_write(dev, 0x003E, 0x817A); + b43_radio_write16(dev, 0x007A, + (b43_radio_read16(dev, 0x007A) | 0x0058)); + if (phy->radio_rev == 4 || phy->radio_rev == 5) { + b43_radio_write16(dev, 0x51, 0x37); + b43_radio_write16(dev, 0x52, 0x70); + b43_radio_write16(dev, 0x53, 0xB3); + b43_radio_write16(dev, 0x54, 0x9B); + b43_radio_write16(dev, 0x5A, 0x88); + b43_radio_write16(dev, 0x5B, 0x88); + b43_radio_write16(dev, 0x5D, 0x88); + b43_radio_write16(dev, 0x5E, 0x88); + b43_radio_write16(dev, 0x7D, 0x88); + b43_hf_write(dev, b43_hf_read(dev) + | B43_HF_TSSIRPSMW); + } + B43_WARN_ON(phy->radio_rev == 6 || phy->radio_rev == 7); /* We had code for these revs here... */ + if (phy->radio_rev == 8) { + b43_radio_write16(dev, 0x51, 0); + b43_radio_write16(dev, 0x52, 0x40); + b43_radio_write16(dev, 0x53, 0xB7); + b43_radio_write16(dev, 0x54, 0x98); + b43_radio_write16(dev, 0x5A, 0x88); + b43_radio_write16(dev, 0x5B, 0x6B); + b43_radio_write16(dev, 0x5C, 0x0F); + if (dev->dev->bus_sprom->boardflags_lo & B43_BFL_ALTIQ) { + b43_radio_write16(dev, 0x5D, 0xFA); + b43_radio_write16(dev, 0x5E, 0xD8); + } else { + b43_radio_write16(dev, 0x5D, 0xF5); + b43_radio_write16(dev, 0x5E, 0xB8); + } + b43_radio_write16(dev, 0x0073, 0x0003); + b43_radio_write16(dev, 0x007D, 0x00A8); + b43_radio_write16(dev, 0x007C, 0x0001); + b43_radio_write16(dev, 0x007E, 0x0008); + } + val = 0x1E1F; + for (offset = 0x0088; offset < 0x0098; offset++) { + b43_phy_write(dev, offset, val); + val -= 0x0202; + } + val = 0x3E3F; + for (offset = 0x0098; offset < 0x00A8; offset++) { + b43_phy_write(dev, offset, val); + val -= 0x0202; + } + val = 0x2120; + for (offset = 0x00A8; offset < 0x00C8; offset++) { + b43_phy_write(dev, offset, (val & 0x3F3F)); + val += 0x0202; + } + if (phy->type == B43_PHYTYPE_G) { + b43_radio_set(dev, 0x007A, 0x0020); + b43_radio_set(dev, 0x0051, 0x0004); + b43_phy_set(dev, 0x0802, 0x0100); + b43_phy_set(dev, 0x042B, 0x2000); + b43_phy_write(dev, 0x5B, 0); + b43_phy_write(dev, 0x5C, 0); + } + + old_channel = phy->channel; + if (old_channel >= 8) + b43_gphy_channel_switch(dev, 1, 0); + else + b43_gphy_channel_switch(dev, 13, 0); + + b43_radio_write16(dev, 0x0050, 0x0020); + b43_radio_write16(dev, 0x0050, 0x0023); + udelay(40); + if (phy->radio_rev < 6 || phy->radio_rev == 8) { + b43_radio_write16(dev, 0x7C, (b43_radio_read16(dev, 0x7C) + | 0x0002)); + b43_radio_write16(dev, 0x50, 0x20); + } + if (phy->radio_rev <= 2) { + b43_radio_write16(dev, 0x7C, 0x20); + b43_radio_write16(dev, 0x5A, 0x70); + b43_radio_write16(dev, 0x5B, 0x7B); + b43_radio_write16(dev, 0x5C, 0xB0); + } + b43_radio_maskset(dev, 0x007A, 0x00F8, 0x0007); + + b43_gphy_channel_switch(dev, old_channel, 0); + + b43_phy_write(dev, 0x0014, 0x0200); + if (phy->radio_rev >= 6) + b43_phy_write(dev, 0x2A, 0x88C2); + else + b43_phy_write(dev, 0x2A, 0x8AC0); + b43_phy_write(dev, 0x0038, 0x0668); + b43_set_txpower_g(dev, &gphy->bbatt, &gphy->rfatt, gphy->tx_control); + if (phy->radio_rev <= 5) { + b43_phy_maskset(dev, 0x5D, 0xFF80, 0x0003); + } + if (phy->radio_rev <= 2) + b43_radio_write16(dev, 0x005D, 0x000D); + + if (phy->analog == 4) { + b43_write16(dev, 0x3E4, 9); + b43_phy_mask(dev, 0x61, 0x0FFF); + } else { + b43_phy_maskset(dev, 0x0002, 0xFFC0, 0x0004); + } + if (phy->type == B43_PHYTYPE_B) + B43_WARN_ON(1); + else if (phy->type == B43_PHYTYPE_G) + b43_write16(dev, 0x03E6, 0x0); +} + +static void b43_calc_loopback_gain(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + u16 backup_phy[16] = { 0 }; + u16 backup_radio[3]; + u16 backup_bband; + u16 i, j, loop_i_max; + u16 trsw_rx; + u16 loop1_outer_done, loop1_inner_done; + + backup_phy[0] = b43_phy_read(dev, B43_PHY_CRS0); + backup_phy[1] = b43_phy_read(dev, B43_PHY_CCKBBANDCFG); + backup_phy[2] = b43_phy_read(dev, B43_PHY_RFOVER); + backup_phy[3] = b43_phy_read(dev, B43_PHY_RFOVERVAL); + if (phy->rev != 1) { /* Not in specs, but needed to prevent PPC machine check */ + backup_phy[4] = b43_phy_read(dev, B43_PHY_ANALOGOVER); + backup_phy[5] = b43_phy_read(dev, B43_PHY_ANALOGOVERVAL); + } + backup_phy[6] = b43_phy_read(dev, B43_PHY_CCK(0x5A)); + backup_phy[7] = b43_phy_read(dev, B43_PHY_CCK(0x59)); + backup_phy[8] = b43_phy_read(dev, B43_PHY_CCK(0x58)); + backup_phy[9] = b43_phy_read(dev, B43_PHY_CCK(0x0A)); + backup_phy[10] = b43_phy_read(dev, B43_PHY_CCK(0x03)); + backup_phy[11] = b43_phy_read(dev, B43_PHY_LO_MASK); + backup_phy[12] = b43_phy_read(dev, B43_PHY_LO_CTL); + backup_phy[13] = b43_phy_read(dev, B43_PHY_CCK(0x2B)); + backup_phy[14] = b43_phy_read(dev, B43_PHY_PGACTL); + backup_phy[15] = b43_phy_read(dev, B43_PHY_LO_LEAKAGE); + backup_bband = gphy->bbatt.att; + backup_radio[0] = b43_radio_read16(dev, 0x52); + backup_radio[1] = b43_radio_read16(dev, 0x43); + backup_radio[2] = b43_radio_read16(dev, 0x7A); + + b43_phy_mask(dev, B43_PHY_CRS0, 0x3FFF); + b43_phy_set(dev, B43_PHY_CCKBBANDCFG, 0x8000); + b43_phy_set(dev, B43_PHY_RFOVER, 0x0002); + b43_phy_mask(dev, B43_PHY_RFOVERVAL, 0xFFFD); + b43_phy_set(dev, B43_PHY_RFOVER, 0x0001); + b43_phy_mask(dev, B43_PHY_RFOVERVAL, 0xFFFE); + if (phy->rev != 1) { /* Not in specs, but needed to prevent PPC machine check */ + b43_phy_set(dev, B43_PHY_ANALOGOVER, 0x0001); + b43_phy_mask(dev, B43_PHY_ANALOGOVERVAL, 0xFFFE); + b43_phy_set(dev, B43_PHY_ANALOGOVER, 0x0002); + b43_phy_mask(dev, B43_PHY_ANALOGOVERVAL, 0xFFFD); + } + b43_phy_set(dev, B43_PHY_RFOVER, 0x000C); + b43_phy_set(dev, B43_PHY_RFOVERVAL, 0x000C); + b43_phy_set(dev, B43_PHY_RFOVER, 0x0030); + b43_phy_maskset(dev, B43_PHY_RFOVERVAL, 0xFFCF, 0x10); + + b43_phy_write(dev, B43_PHY_CCK(0x5A), 0x0780); + b43_phy_write(dev, B43_PHY_CCK(0x59), 0xC810); + b43_phy_write(dev, B43_PHY_CCK(0x58), 0x000D); + + b43_phy_set(dev, B43_PHY_CCK(0x0A), 0x2000); + if (phy->rev != 1) { /* Not in specs, but needed to prevent PPC machine check */ + b43_phy_set(dev, B43_PHY_ANALOGOVER, 0x0004); + b43_phy_mask(dev, B43_PHY_ANALOGOVERVAL, 0xFFFB); + } + b43_phy_maskset(dev, B43_PHY_CCK(0x03), 0xFF9F, 0x40); + + if (phy->radio_rev == 8) { + b43_radio_write16(dev, 0x43, 0x000F); + } else { + b43_radio_write16(dev, 0x52, 0); + b43_radio_maskset(dev, 0x43, 0xFFF0, 0x9); + } + b43_gphy_set_baseband_attenuation(dev, 11); + + if (phy->rev >= 3) + b43_phy_write(dev, B43_PHY_LO_MASK, 0xC020); + else + b43_phy_write(dev, B43_PHY_LO_MASK, 0x8020); + b43_phy_write(dev, B43_PHY_LO_CTL, 0); + + b43_phy_maskset(dev, B43_PHY_CCK(0x2B), 0xFFC0, 0x01); + b43_phy_maskset(dev, B43_PHY_CCK(0x2B), 0xC0FF, 0x800); + + b43_phy_set(dev, B43_PHY_RFOVER, 0x0100); + b43_phy_mask(dev, B43_PHY_RFOVERVAL, 0xCFFF); + + if (dev->dev->bus_sprom->boardflags_lo & B43_BFL_EXTLNA) { + if (phy->rev >= 7) { + b43_phy_set(dev, B43_PHY_RFOVER, 0x0800); + b43_phy_set(dev, B43_PHY_RFOVERVAL, 0x8000); + } + } + b43_radio_mask(dev, 0x7A, 0x00F7); + + j = 0; + loop_i_max = (phy->radio_rev == 8) ? 15 : 9; + for (i = 0; i < loop_i_max; i++) { + for (j = 0; j < 16; j++) { + b43_radio_write16(dev, 0x43, i); + b43_phy_maskset(dev, B43_PHY_RFOVERVAL, 0xF0FF, (j << 8)); + b43_phy_maskset(dev, B43_PHY_PGACTL, 0x0FFF, 0xA000); + b43_phy_set(dev, B43_PHY_PGACTL, 0xF000); + udelay(20); + if (b43_phy_read(dev, B43_PHY_LO_LEAKAGE) >= 0xDFC) + goto exit_loop1; + } + } + exit_loop1: + loop1_outer_done = i; + loop1_inner_done = j; + if (j >= 8) { + b43_phy_set(dev, B43_PHY_RFOVERVAL, 0x30); + trsw_rx = 0x1B; + for (j = j - 8; j < 16; j++) { + b43_phy_maskset(dev, B43_PHY_RFOVERVAL, 0xF0FF, (j << 8)); + b43_phy_maskset(dev, B43_PHY_PGACTL, 0x0FFF, 0xA000); + b43_phy_set(dev, B43_PHY_PGACTL, 0xF000); + udelay(20); + trsw_rx -= 3; + if (b43_phy_read(dev, B43_PHY_LO_LEAKAGE) >= 0xDFC) + goto exit_loop2; + } + } else + trsw_rx = 0x18; + exit_loop2: + + if (phy->rev != 1) { /* Not in specs, but needed to prevent PPC machine check */ + b43_phy_write(dev, B43_PHY_ANALOGOVER, backup_phy[4]); + b43_phy_write(dev, B43_PHY_ANALOGOVERVAL, backup_phy[5]); + } + b43_phy_write(dev, B43_PHY_CCK(0x5A), backup_phy[6]); + b43_phy_write(dev, B43_PHY_CCK(0x59), backup_phy[7]); + b43_phy_write(dev, B43_PHY_CCK(0x58), backup_phy[8]); + b43_phy_write(dev, B43_PHY_CCK(0x0A), backup_phy[9]); + b43_phy_write(dev, B43_PHY_CCK(0x03), backup_phy[10]); + b43_phy_write(dev, B43_PHY_LO_MASK, backup_phy[11]); + b43_phy_write(dev, B43_PHY_LO_CTL, backup_phy[12]); + b43_phy_write(dev, B43_PHY_CCK(0x2B), backup_phy[13]); + b43_phy_write(dev, B43_PHY_PGACTL, backup_phy[14]); + + b43_gphy_set_baseband_attenuation(dev, backup_bband); + + b43_radio_write16(dev, 0x52, backup_radio[0]); + b43_radio_write16(dev, 0x43, backup_radio[1]); + b43_radio_write16(dev, 0x7A, backup_radio[2]); + + b43_phy_write(dev, B43_PHY_RFOVER, backup_phy[2] | 0x0003); + udelay(10); + b43_phy_write(dev, B43_PHY_RFOVER, backup_phy[2]); + b43_phy_write(dev, B43_PHY_RFOVERVAL, backup_phy[3]); + b43_phy_write(dev, B43_PHY_CRS0, backup_phy[0]); + b43_phy_write(dev, B43_PHY_CCKBBANDCFG, backup_phy[1]); + + gphy->max_lb_gain = + ((loop1_inner_done * 6) - (loop1_outer_done * 4)) - 11; + gphy->trsw_rx_gain = trsw_rx * 2; +} + +static void b43_hardware_pctl_early_init(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + + if (!b43_has_hardware_pctl(dev)) { + b43_phy_write(dev, 0x047A, 0xC111); + return; + } + + b43_phy_mask(dev, 0x0036, 0xFEFF); + b43_phy_write(dev, 0x002F, 0x0202); + b43_phy_set(dev, 0x047C, 0x0002); + b43_phy_set(dev, 0x047A, 0xF000); + if (phy->radio_ver == 0x2050 && phy->radio_rev == 8) { + b43_phy_maskset(dev, 0x047A, 0xFF0F, 0x0010); + b43_phy_set(dev, 0x005D, 0x8000); + b43_phy_maskset(dev, 0x004E, 0xFFC0, 0x0010); + b43_phy_write(dev, 0x002E, 0xC07F); + b43_phy_set(dev, 0x0036, 0x0400); + } else { + b43_phy_set(dev, 0x0036, 0x0200); + b43_phy_set(dev, 0x0036, 0x0400); + b43_phy_mask(dev, 0x005D, 0x7FFF); + b43_phy_mask(dev, 0x004F, 0xFFFE); + b43_phy_maskset(dev, 0x004E, 0xFFC0, 0x0010); + b43_phy_write(dev, 0x002E, 0xC07F); + b43_phy_maskset(dev, 0x047A, 0xFF0F, 0x0010); + } +} + +/* Hardware power control for G-PHY */ +static void b43_hardware_pctl_init_gphy(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + + if (!b43_has_hardware_pctl(dev)) { + /* No hardware power control */ + b43_hf_write(dev, b43_hf_read(dev) & ~B43_HF_HWPCTL); + return; + } + + b43_phy_maskset(dev, 0x0036, 0xFFC0, (gphy->tgt_idle_tssi - gphy->cur_idle_tssi)); + b43_phy_maskset(dev, 0x0478, 0xFF00, (gphy->tgt_idle_tssi - gphy->cur_idle_tssi)); + b43_gphy_tssi_power_lt_init(dev); + b43_gphy_gain_lt_init(dev); + b43_phy_mask(dev, 0x0060, 0xFFBF); + b43_phy_write(dev, 0x0014, 0x0000); + + B43_WARN_ON(phy->rev < 6); + b43_phy_set(dev, 0x0478, 0x0800); + b43_phy_mask(dev, 0x0478, 0xFEFF); + b43_phy_mask(dev, 0x0801, 0xFFBF); + + b43_gphy_dc_lt_init(dev, 1); + + /* Enable hardware pctl in firmware. */ + b43_hf_write(dev, b43_hf_read(dev) | B43_HF_HWPCTL); +} + +/* Initialize B/G PHY power control */ +static void b43_phy_init_pctl(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + struct b43_rfatt old_rfatt; + struct b43_bbatt old_bbatt; + u8 old_tx_control = 0; + + B43_WARN_ON(phy->type != B43_PHYTYPE_G); + + if ((dev->dev->board_vendor == SSB_BOARDVENDOR_BCM) && + (dev->dev->board_type == SSB_BOARD_BU4306)) + return; + + b43_phy_write(dev, 0x0028, 0x8018); + + /* This does something with the Analog... */ + b43_write16(dev, B43_MMIO_PHY0, b43_read16(dev, B43_MMIO_PHY0) + & 0xFFDF); + + if (!phy->gmode) + return; + b43_hardware_pctl_early_init(dev); + if (gphy->cur_idle_tssi == 0) { + if (phy->radio_ver == 0x2050 && phy->analog == 0) { + b43_radio_maskset(dev, 0x0076, 0x00F7, 0x0084); + } else { + struct b43_rfatt rfatt; + struct b43_bbatt bbatt; + + memcpy(&old_rfatt, &gphy->rfatt, sizeof(old_rfatt)); + memcpy(&old_bbatt, &gphy->bbatt, sizeof(old_bbatt)); + old_tx_control = gphy->tx_control; + + bbatt.att = 11; + if (phy->radio_rev == 8) { + rfatt.att = 15; + rfatt.with_padmix = true; + } else { + rfatt.att = 9; + rfatt.with_padmix = false; + } + b43_set_txpower_g(dev, &bbatt, &rfatt, 0); + } + b43_dummy_transmission(dev, false, true); + gphy->cur_idle_tssi = b43_phy_read(dev, B43_PHY_ITSSI); + if (B43_DEBUG) { + /* Current-Idle-TSSI sanity check. */ + if (abs(gphy->cur_idle_tssi - gphy->tgt_idle_tssi) >= 20) { + b43dbg(dev->wl, + "!WARNING! Idle-TSSI phy->cur_idle_tssi " + "measuring failed. (cur=%d, tgt=%d). Disabling TX power " + "adjustment.\n", gphy->cur_idle_tssi, + gphy->tgt_idle_tssi); + gphy->cur_idle_tssi = 0; + } + } + if (phy->radio_ver == 0x2050 && phy->analog == 0) { + b43_radio_mask(dev, 0x0076, 0xFF7B); + } else { + b43_set_txpower_g(dev, &old_bbatt, + &old_rfatt, old_tx_control); + } + } + b43_hardware_pctl_init_gphy(dev); + b43_shm_clear_tssi(dev); +} + +static void b43_phy_initg(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + u16 tmp; + + if (phy->rev == 1) + b43_phy_initb5(dev); + else + b43_phy_initb6(dev); + + if (phy->rev >= 2 || phy->gmode) + b43_phy_inita(dev); + + if (phy->rev >= 2) { + b43_phy_write(dev, B43_PHY_ANALOGOVER, 0); + b43_phy_write(dev, B43_PHY_ANALOGOVERVAL, 0); + } + if (phy->rev == 2) { + b43_phy_write(dev, B43_PHY_RFOVER, 0); + b43_phy_write(dev, B43_PHY_PGACTL, 0xC0); + } + if (phy->rev > 5) { + b43_phy_write(dev, B43_PHY_RFOVER, 0x400); + b43_phy_write(dev, B43_PHY_PGACTL, 0xC0); + } + if (phy->gmode || phy->rev >= 2) { + tmp = b43_phy_read(dev, B43_PHY_VERSION_OFDM); + tmp &= B43_PHYVER_VERSION; + if (tmp == 3 || tmp == 5) { + b43_phy_write(dev, B43_PHY_OFDM(0xC2), 0x1816); + b43_phy_write(dev, B43_PHY_OFDM(0xC3), 0x8006); + } + if (tmp == 5) { + b43_phy_maskset(dev, B43_PHY_OFDM(0xCC), 0x00FF, 0x1F00); + } + } + if ((phy->rev <= 2 && phy->gmode) || phy->rev >= 2) + b43_phy_write(dev, B43_PHY_OFDM(0x7E), 0x78); + if (phy->radio_rev == 8) { + b43_phy_set(dev, B43_PHY_EXTG(0x01), 0x80); + b43_phy_set(dev, B43_PHY_OFDM(0x3E), 0x4); + } + if (has_loopback_gain(phy)) + b43_calc_loopback_gain(dev); + + if (phy->radio_rev != 8) { + if (gphy->initval == 0xFFFF) + gphy->initval = b43_radio_init2050(dev); + else + b43_radio_write16(dev, 0x0078, gphy->initval); + } + b43_lo_g_init(dev); + if (has_tx_magnification(phy)) { + b43_radio_write16(dev, 0x52, + (b43_radio_read16(dev, 0x52) & 0xFF00) + | gphy->lo_control->tx_bias | gphy-> + lo_control->tx_magn); + } else { + b43_radio_maskset(dev, 0x52, 0xFFF0, gphy->lo_control->tx_bias); + } + if (phy->rev >= 6) { + b43_phy_maskset(dev, B43_PHY_CCK(0x36), 0x0FFF, (gphy->lo_control->tx_bias << 12)); + } + if (dev->dev->bus_sprom->boardflags_lo & B43_BFL_PACTRL) + b43_phy_write(dev, B43_PHY_CCK(0x2E), 0x8075); + else + b43_phy_write(dev, B43_PHY_CCK(0x2E), 0x807F); + if (phy->rev < 2) + b43_phy_write(dev, B43_PHY_CCK(0x2F), 0x101); + else + b43_phy_write(dev, B43_PHY_CCK(0x2F), 0x202); + if (phy->gmode || phy->rev >= 2) { + b43_lo_g_adjust(dev); + b43_phy_write(dev, B43_PHY_LO_MASK, 0x8078); + } + + if (!(dev->dev->bus_sprom->boardflags_lo & B43_BFL_RSSI)) { + /* The specs state to update the NRSSI LT with + * the value 0x7FFFFFFF here. I think that is some weird + * compiler optimization in the original driver. + * Essentially, what we do here is resetting all NRSSI LT + * entries to -32 (see the clamp_val() in nrssi_hw_update()) + */ + b43_nrssi_hw_update(dev, 0xFFFF); //FIXME? + b43_calc_nrssi_threshold(dev); + } else if (phy->gmode || phy->rev >= 2) { + if (gphy->nrssi[0] == -1000) { + B43_WARN_ON(gphy->nrssi[1] != -1000); + b43_calc_nrssi_slope(dev); + } else + b43_calc_nrssi_threshold(dev); + } + if (phy->radio_rev == 8) + b43_phy_write(dev, B43_PHY_EXTG(0x05), 0x3230); + b43_phy_init_pctl(dev); + /* FIXME: The spec says in the following if, the 0 should be replaced + 'if OFDM may not be used in the current locale' + but OFDM is legal everywhere */ + if ((dev->dev->chip_id == 0x4306 + && dev->dev->chip_pkg == 2) || 0) { + b43_phy_mask(dev, B43_PHY_CRS0, 0xBFFF); + b43_phy_mask(dev, B43_PHY_OFDM(0xC3), 0x7FFF); + } +} + +void b43_gphy_channel_switch(struct b43_wldev *dev, + unsigned int channel, + bool synthetic_pu_workaround) +{ + if (synthetic_pu_workaround) + b43_synth_pu_workaround(dev, channel); + + b43_write16(dev, B43_MMIO_CHANNEL, channel2freq_bg(channel)); + + if (channel == 14) { + if (dev->dev->bus_sprom->country_code == + SSB_SPROM1CCODE_JAPAN) + b43_hf_write(dev, + b43_hf_read(dev) & ~B43_HF_ACPR); + else + b43_hf_write(dev, + b43_hf_read(dev) | B43_HF_ACPR); + b43_write16(dev, B43_MMIO_CHANNEL_EXT, + b43_read16(dev, B43_MMIO_CHANNEL_EXT) + | (1 << 11)); + } else { + b43_write16(dev, B43_MMIO_CHANNEL_EXT, + b43_read16(dev, B43_MMIO_CHANNEL_EXT) + & 0xF7BF); + } +} + +static void default_baseband_attenuation(struct b43_wldev *dev, + struct b43_bbatt *bb) +{ + struct b43_phy *phy = &dev->phy; + + if (phy->radio_ver == 0x2050 && phy->radio_rev < 6) + bb->att = 0; + else + bb->att = 2; +} + +static void default_radio_attenuation(struct b43_wldev *dev, + struct b43_rfatt *rf) +{ + struct b43_bus_dev *bdev = dev->dev; + struct b43_phy *phy = &dev->phy; + + rf->with_padmix = false; + + if (dev->dev->board_vendor == SSB_BOARDVENDOR_BCM && + dev->dev->board_type == SSB_BOARD_BCM4309G) { + if (dev->dev->board_rev < 0x43) { + rf->att = 2; + return; + } else if (dev->dev->board_rev < 0x51) { + rf->att = 3; + return; + } + } + + if (phy->type == B43_PHYTYPE_A) { + rf->att = 0x60; + return; + } + + switch (phy->radio_ver) { + case 0x2053: + switch (phy->radio_rev) { + case 1: + rf->att = 6; + return; + } + break; + case 0x2050: + switch (phy->radio_rev) { + case 0: + rf->att = 5; + return; + case 1: + if (phy->type == B43_PHYTYPE_G) { + if (bdev->board_vendor == SSB_BOARDVENDOR_BCM + && bdev->board_type == SSB_BOARD_BCM4309G + && bdev->board_rev >= 30) + rf->att = 3; + else if (bdev->board_vendor == + SSB_BOARDVENDOR_BCM + && bdev->board_type == + SSB_BOARD_BU4306) + rf->att = 3; + else + rf->att = 1; + } else { + if (bdev->board_vendor == SSB_BOARDVENDOR_BCM + && bdev->board_type == SSB_BOARD_BCM4309G + && bdev->board_rev >= 30) + rf->att = 7; + else + rf->att = 6; + } + return; + case 2: + if (phy->type == B43_PHYTYPE_G) { + if (bdev->board_vendor == SSB_BOARDVENDOR_BCM + && bdev->board_type == SSB_BOARD_BCM4309G + && bdev->board_rev >= 30) + rf->att = 3; + else if (bdev->board_vendor == + SSB_BOARDVENDOR_BCM + && bdev->board_type == + SSB_BOARD_BU4306) + rf->att = 5; + else if (bdev->chip_id == 0x4320) + rf->att = 4; + else + rf->att = 3; + } else + rf->att = 6; + return; + case 3: + rf->att = 5; + return; + case 4: + case 5: + rf->att = 1; + return; + case 6: + case 7: + rf->att = 5; + return; + case 8: + rf->att = 0xA; + rf->with_padmix = true; + return; + case 9: + default: + rf->att = 5; + return; + } + } + rf->att = 5; +} + +static u16 default_tx_control(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + + if (phy->radio_ver != 0x2050) + return 0; + if (phy->radio_rev == 1) + return B43_TXCTL_PA2DB | B43_TXCTL_TXMIX; + if (phy->radio_rev < 6) + return B43_TXCTL_PA2DB; + if (phy->radio_rev == 8) + return B43_TXCTL_TXMIX; + return 0; +} + +static u8 b43_gphy_aci_detect(struct b43_wldev *dev, u8 channel) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + u8 ret = 0; + u16 saved, rssi, temp; + int i, j = 0; + + saved = b43_phy_read(dev, 0x0403); + b43_switch_channel(dev, channel); + b43_phy_write(dev, 0x0403, (saved & 0xFFF8) | 5); + if (gphy->aci_hw_rssi) + rssi = b43_phy_read(dev, 0x048A) & 0x3F; + else + rssi = saved & 0x3F; + /* clamp temp to signed 5bit */ + if (rssi > 32) + rssi -= 64; + for (i = 0; i < 100; i++) { + temp = (b43_phy_read(dev, 0x047F) >> 8) & 0x3F; + if (temp > 32) + temp -= 64; + if (temp < rssi) + j++; + if (j >= 20) + ret = 1; + } + b43_phy_write(dev, 0x0403, saved); + + return ret; +} + +static u8 b43_gphy_aci_scan(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + u8 ret[13]; + unsigned int channel = phy->channel; + unsigned int i, j, start, end; + + if (!((phy->type == B43_PHYTYPE_G) && (phy->rev > 0))) + return 0; + + b43_phy_lock(dev); + b43_radio_lock(dev); + b43_phy_mask(dev, 0x0802, 0xFFFC); + b43_phy_mask(dev, B43_PHY_G_CRS, 0x7FFF); + b43_set_all_gains(dev, 3, 8, 1); + + start = (channel - 5 > 0) ? channel - 5 : 1; + end = (channel + 5 < 14) ? channel + 5 : 13; + + for (i = start; i <= end; i++) { + if (abs(channel - i) > 2) + ret[i - 1] = b43_gphy_aci_detect(dev, i); + } + b43_switch_channel(dev, channel); + b43_phy_maskset(dev, 0x0802, 0xFFFC, 0x0003); + b43_phy_mask(dev, 0x0403, 0xFFF8); + b43_phy_set(dev, B43_PHY_G_CRS, 0x8000); + b43_set_original_gains(dev); + for (i = 0; i < 13; i++) { + if (!ret[i]) + continue; + end = (i + 5 < 13) ? i + 5 : 13; + for (j = i; j < end; j++) + ret[j] = 1; + } + b43_radio_unlock(dev); + b43_phy_unlock(dev); + + return ret[channel - 1]; +} + +static s32 b43_tssi2dbm_ad(s32 num, s32 den) +{ + if (num < 0) + return num / den; + else + return (num + den / 2) / den; +} + +static s8 b43_tssi2dbm_entry(s8 entry[], u8 index, + s16 pab0, s16 pab1, s16 pab2) +{ + s32 m1, m2, f = 256, q, delta; + s8 i = 0; + + m1 = b43_tssi2dbm_ad(16 * pab0 + index * pab1, 32); + m2 = max(b43_tssi2dbm_ad(32768 + index * pab2, 256), 1); + do { + if (i > 15) + return -EINVAL; + q = b43_tssi2dbm_ad(f * 4096 - + b43_tssi2dbm_ad(m2 * f, 16) * f, 2048); + delta = abs(q - f); + f = q; + i++; + } while (delta >= 2); + entry[index] = clamp_val(b43_tssi2dbm_ad(m1 * f, 8192), -127, 128); + return 0; +} + +u8 *b43_generate_dyn_tssi2dbm_tab(struct b43_wldev *dev, + s16 pab0, s16 pab1, s16 pab2) +{ + unsigned int i; + u8 *tab; + int err; + + tab = kmalloc(64, GFP_KERNEL); + if (!tab) { + b43err(dev->wl, "Could not allocate memory " + "for tssi2dbm table\n"); + return NULL; + } + for (i = 0; i < 64; i++) { + err = b43_tssi2dbm_entry(tab, i, pab0, pab1, pab2); + if (err) { + b43err(dev->wl, "Could not generate " + "tssi2dBm table\n"); + kfree(tab); + return NULL; + } + } + + return tab; +} + +/* Initialise the TSSI->dBm lookup table */ +static int b43_gphy_init_tssi2dbm_table(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + s16 pab0, pab1, pab2; + + pab0 = (s16) (dev->dev->bus_sprom->pa0b0); + pab1 = (s16) (dev->dev->bus_sprom->pa0b1); + pab2 = (s16) (dev->dev->bus_sprom->pa0b2); + + B43_WARN_ON((dev->dev->chip_id == 0x4301) && + (phy->radio_ver != 0x2050)); /* Not supported anymore */ + + gphy->dyn_tssi_tbl = false; + + if (pab0 != 0 && pab1 != 0 && pab2 != 0 && + pab0 != -1 && pab1 != -1 && pab2 != -1) { + /* The pabX values are set in SPROM. Use them. */ + if ((s8) dev->dev->bus_sprom->itssi_bg != 0 && + (s8) dev->dev->bus_sprom->itssi_bg != -1) { + gphy->tgt_idle_tssi = + (s8) (dev->dev->bus_sprom->itssi_bg); + } else + gphy->tgt_idle_tssi = 62; + gphy->tssi2dbm = b43_generate_dyn_tssi2dbm_tab(dev, pab0, + pab1, pab2); + if (!gphy->tssi2dbm) + return -ENOMEM; + gphy->dyn_tssi_tbl = true; + } else { + /* pabX values not set in SPROM. */ + gphy->tgt_idle_tssi = 52; + gphy->tssi2dbm = b43_tssi2dbm_g_table; + } + + return 0; +} + +static int b43_gphy_op_allocate(struct b43_wldev *dev) +{ + struct b43_phy_g *gphy; + struct b43_txpower_lo_control *lo; + int err; + + gphy = kzalloc(sizeof(*gphy), GFP_KERNEL); + if (!gphy) { + err = -ENOMEM; + goto error; + } + dev->phy.g = gphy; + + lo = kzalloc(sizeof(*lo), GFP_KERNEL); + if (!lo) { + err = -ENOMEM; + goto err_free_gphy; + } + gphy->lo_control = lo; + + err = b43_gphy_init_tssi2dbm_table(dev); + if (err) + goto err_free_lo; + + return 0; + +err_free_lo: + kfree(lo); +err_free_gphy: + kfree(gphy); +error: + return err; +} + +static void b43_gphy_op_prepare_structs(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + const void *tssi2dbm; + int tgt_idle_tssi; + struct b43_txpower_lo_control *lo; + unsigned int i; + + /* tssi2dbm table is constant, so it is initialized at alloc time. + * Save a copy of the pointer. */ + tssi2dbm = gphy->tssi2dbm; + tgt_idle_tssi = gphy->tgt_idle_tssi; + /* Save the LO pointer. */ + lo = gphy->lo_control; + + /* Zero out the whole PHY structure. */ + memset(gphy, 0, sizeof(*gphy)); + + /* Restore pointers. */ + gphy->tssi2dbm = tssi2dbm; + gphy->tgt_idle_tssi = tgt_idle_tssi; + gphy->lo_control = lo; + + memset(gphy->minlowsig, 0xFF, sizeof(gphy->minlowsig)); + + /* NRSSI */ + for (i = 0; i < ARRAY_SIZE(gphy->nrssi); i++) + gphy->nrssi[i] = -1000; + for (i = 0; i < ARRAY_SIZE(gphy->nrssi_lt); i++) + gphy->nrssi_lt[i] = i; + + gphy->lofcal = 0xFFFF; + gphy->initval = 0xFFFF; + + gphy->interfmode = B43_INTERFMODE_NONE; + + /* OFDM-table address caching. */ + gphy->ofdmtab_addr_direction = B43_OFDMTAB_DIRECTION_UNKNOWN; + + gphy->average_tssi = 0xFF; + + /* Local Osciallator structure */ + lo->tx_bias = 0xFF; + INIT_LIST_HEAD(&lo->calib_list); +} + +static void b43_gphy_op_free(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + + kfree(gphy->lo_control); + + if (gphy->dyn_tssi_tbl) + kfree(gphy->tssi2dbm); + gphy->dyn_tssi_tbl = false; + gphy->tssi2dbm = NULL; + + kfree(gphy); + dev->phy.g = NULL; +} + +static int b43_gphy_op_prepare_hardware(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + struct b43_txpower_lo_control *lo = gphy->lo_control; + + B43_WARN_ON(phy->type != B43_PHYTYPE_G); + + default_baseband_attenuation(dev, &gphy->bbatt); + default_radio_attenuation(dev, &gphy->rfatt); + gphy->tx_control = (default_tx_control(dev) << 4); + generate_rfatt_list(dev, &lo->rfatt_list); + generate_bbatt_list(dev, &lo->bbatt_list); + + /* Commit previous writes */ + b43_read32(dev, B43_MMIO_MACCTL); + + if (phy->rev == 1) { + /* Workaround: Temporarly disable gmode through the early init + * phase, as the gmode stuff is not needed for phy rev 1 */ + phy->gmode = false; + b43_wireless_core_reset(dev, 0); + b43_phy_initg(dev); + phy->gmode = true; + b43_wireless_core_reset(dev, 1); + } + + return 0; +} + +static int b43_gphy_op_init(struct b43_wldev *dev) +{ + b43_phy_initg(dev); + + return 0; +} + +static void b43_gphy_op_exit(struct b43_wldev *dev) +{ + b43_lo_g_cleanup(dev); +} + +static u16 b43_gphy_op_read(struct b43_wldev *dev, u16 reg) +{ + b43_write16(dev, B43_MMIO_PHY_CONTROL, reg); + return b43_read16(dev, B43_MMIO_PHY_DATA); +} + +static void b43_gphy_op_write(struct b43_wldev *dev, u16 reg, u16 value) +{ + b43_write16(dev, B43_MMIO_PHY_CONTROL, reg); + b43_write16(dev, B43_MMIO_PHY_DATA, value); +} + +static u16 b43_gphy_op_radio_read(struct b43_wldev *dev, u16 reg) +{ + /* Register 1 is a 32-bit register. */ + B43_WARN_ON(reg == 1); + /* G-PHY needs 0x80 for read access. */ + reg |= 0x80; + + b43_write16(dev, B43_MMIO_RADIO_CONTROL, reg); + return b43_read16(dev, B43_MMIO_RADIO_DATA_LOW); +} + +static void b43_gphy_op_radio_write(struct b43_wldev *dev, u16 reg, u16 value) +{ + /* Register 1 is a 32-bit register. */ + B43_WARN_ON(reg == 1); + + b43_write16(dev, B43_MMIO_RADIO_CONTROL, reg); + b43_write16(dev, B43_MMIO_RADIO_DATA_LOW, value); +} + +static bool b43_gphy_op_supports_hwpctl(struct b43_wldev *dev) +{ + return (dev->phy.rev >= 6); +} + +static void b43_gphy_op_software_rfkill(struct b43_wldev *dev, + bool blocked) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + unsigned int channel; + + might_sleep(); + + if (!blocked) { + /* Turn radio ON */ + if (phy->radio_on) + return; + + b43_phy_write(dev, 0x0015, 0x8000); + b43_phy_write(dev, 0x0015, 0xCC00); + b43_phy_write(dev, 0x0015, (phy->gmode ? 0x00C0 : 0x0000)); + if (gphy->radio_off_context.valid) { + /* Restore the RFover values. */ + b43_phy_write(dev, B43_PHY_RFOVER, + gphy->radio_off_context.rfover); + b43_phy_write(dev, B43_PHY_RFOVERVAL, + gphy->radio_off_context.rfoverval); + gphy->radio_off_context.valid = false; + } + channel = phy->channel; + b43_gphy_channel_switch(dev, 6, 1); + b43_gphy_channel_switch(dev, channel, 0); + } else { + /* Turn radio OFF */ + u16 rfover, rfoverval; + + rfover = b43_phy_read(dev, B43_PHY_RFOVER); + rfoverval = b43_phy_read(dev, B43_PHY_RFOVERVAL); + gphy->radio_off_context.rfover = rfover; + gphy->radio_off_context.rfoverval = rfoverval; + gphy->radio_off_context.valid = true; + b43_phy_write(dev, B43_PHY_RFOVER, rfover | 0x008C); + b43_phy_write(dev, B43_PHY_RFOVERVAL, rfoverval & 0xFF73); + } +} + +static int b43_gphy_op_switch_channel(struct b43_wldev *dev, + unsigned int new_channel) +{ + if ((new_channel < 1) || (new_channel > 14)) + return -EINVAL; + b43_gphy_channel_switch(dev, new_channel, 0); + + return 0; +} + +static unsigned int b43_gphy_op_get_default_chan(struct b43_wldev *dev) +{ + return 1; /* Default to channel 1 */ +} + +static void b43_gphy_op_set_rx_antenna(struct b43_wldev *dev, int antenna) +{ + struct b43_phy *phy = &dev->phy; + u16 tmp; + int autodiv = 0; + + if (antenna == B43_ANTENNA_AUTO0 || antenna == B43_ANTENNA_AUTO1) + autodiv = 1; + + b43_hf_write(dev, b43_hf_read(dev) & ~B43_HF_ANTDIVHELP); + + b43_phy_maskset(dev, B43_PHY_BBANDCFG, ~B43_PHY_BBANDCFG_RXANT, + (autodiv ? B43_ANTENNA_AUTO1 : antenna) << + B43_PHY_BBANDCFG_RXANT_SHIFT); + + if (autodiv) { + tmp = b43_phy_read(dev, B43_PHY_ANTDWELL); + if (antenna == B43_ANTENNA_AUTO1) + tmp &= ~B43_PHY_ANTDWELL_AUTODIV1; + else + tmp |= B43_PHY_ANTDWELL_AUTODIV1; + b43_phy_write(dev, B43_PHY_ANTDWELL, tmp); + } + + tmp = b43_phy_read(dev, B43_PHY_ANTWRSETT); + if (autodiv) + tmp |= B43_PHY_ANTWRSETT_ARXDIV; + else + tmp &= ~B43_PHY_ANTWRSETT_ARXDIV; + b43_phy_write(dev, B43_PHY_ANTWRSETT, tmp); + + if (autodiv) + b43_phy_set(dev, B43_PHY_ANTWRSETT, B43_PHY_ANTWRSETT_ARXDIV); + else { + b43_phy_mask(dev, B43_PHY_ANTWRSETT, + B43_PHY_ANTWRSETT_ARXDIV); + } + + if (phy->rev >= 2) { + b43_phy_set(dev, B43_PHY_OFDM61, B43_PHY_OFDM61_10); + b43_phy_maskset(dev, B43_PHY_DIVSRCHGAINBACK, 0xFF00, 0x15); + + if (phy->rev == 2) + b43_phy_write(dev, B43_PHY_ADIVRELATED, 8); + else + b43_phy_maskset(dev, B43_PHY_ADIVRELATED, 0xFF00, 8); + } + if (phy->rev >= 6) + b43_phy_write(dev, B43_PHY_OFDM9B, 0xDC); + + b43_hf_write(dev, b43_hf_read(dev) | B43_HF_ANTDIVHELP); +} + +static int b43_gphy_op_interf_mitigation(struct b43_wldev *dev, + enum b43_interference_mitigation mode) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + int currentmode; + + B43_WARN_ON(phy->type != B43_PHYTYPE_G); + if ((phy->rev == 0) || (!phy->gmode)) + return -ENODEV; + + gphy->aci_wlan_automatic = false; + switch (mode) { + case B43_INTERFMODE_AUTOWLAN: + gphy->aci_wlan_automatic = true; + if (gphy->aci_enable) + mode = B43_INTERFMODE_MANUALWLAN; + else + mode = B43_INTERFMODE_NONE; + break; + case B43_INTERFMODE_NONE: + case B43_INTERFMODE_NONWLAN: + case B43_INTERFMODE_MANUALWLAN: + break; + default: + return -EINVAL; + } + + currentmode = gphy->interfmode; + if (currentmode == mode) + return 0; + if (currentmode != B43_INTERFMODE_NONE) + b43_radio_interference_mitigation_disable(dev, currentmode); + + if (mode == B43_INTERFMODE_NONE) { + gphy->aci_enable = false; + gphy->aci_hw_rssi = false; + } else + b43_radio_interference_mitigation_enable(dev, mode); + gphy->interfmode = mode; + + return 0; +} + +/* http://bcm-specs.sipsolutions.net/EstimatePowerOut + * This function converts a TSSI value to dBm in Q5.2 + */ +static s8 b43_gphy_estimate_power_out(struct b43_wldev *dev, s8 tssi) +{ + struct b43_phy_g *gphy = dev->phy.g; + s8 dbm; + s32 tmp; + + tmp = (gphy->tgt_idle_tssi - gphy->cur_idle_tssi + tssi); + tmp = clamp_val(tmp, 0x00, 0x3F); + dbm = gphy->tssi2dbm[tmp]; + + return dbm; +} + +static void b43_put_attenuation_into_ranges(struct b43_wldev *dev, + int *_bbatt, int *_rfatt) +{ + int rfatt = *_rfatt; + int bbatt = *_bbatt; + struct b43_txpower_lo_control *lo = dev->phy.g->lo_control; + + /* Get baseband and radio attenuation values into their permitted ranges. + * Radio attenuation affects power level 4 times as much as baseband. */ + + /* Range constants */ + const int rf_min = lo->rfatt_list.min_val; + const int rf_max = lo->rfatt_list.max_val; + const int bb_min = lo->bbatt_list.min_val; + const int bb_max = lo->bbatt_list.max_val; + + while (1) { + if (rfatt > rf_max && bbatt > bb_max - 4) + break; /* Can not get it into ranges */ + if (rfatt < rf_min && bbatt < bb_min + 4) + break; /* Can not get it into ranges */ + if (bbatt > bb_max && rfatt > rf_max - 1) + break; /* Can not get it into ranges */ + if (bbatt < bb_min && rfatt < rf_min + 1) + break; /* Can not get it into ranges */ + + if (bbatt > bb_max) { + bbatt -= 4; + rfatt += 1; + continue; + } + if (bbatt < bb_min) { + bbatt += 4; + rfatt -= 1; + continue; + } + if (rfatt > rf_max) { + rfatt -= 1; + bbatt += 4; + continue; + } + if (rfatt < rf_min) { + rfatt += 1; + bbatt -= 4; + continue; + } + break; + } + + *_rfatt = clamp_val(rfatt, rf_min, rf_max); + *_bbatt = clamp_val(bbatt, bb_min, bb_max); +} + +static void b43_gphy_op_adjust_txpower(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + int rfatt, bbatt; + u8 tx_control; + + b43_mac_suspend(dev); + + /* Calculate the new attenuation values. */ + bbatt = gphy->bbatt.att; + bbatt += gphy->bbatt_delta; + rfatt = gphy->rfatt.att; + rfatt += gphy->rfatt_delta; + + b43_put_attenuation_into_ranges(dev, &bbatt, &rfatt); + tx_control = gphy->tx_control; + if ((phy->radio_ver == 0x2050) && (phy->radio_rev == 2)) { + if (rfatt <= 1) { + if (tx_control == 0) { + tx_control = + B43_TXCTL_PA2DB | + B43_TXCTL_TXMIX; + rfatt += 2; + bbatt += 2; + } else if (dev->dev->bus_sprom-> + boardflags_lo & + B43_BFL_PACTRL) { + bbatt += 4 * (rfatt - 2); + rfatt = 2; + } + } else if (rfatt > 4 && tx_control) { + tx_control = 0; + if (bbatt < 3) { + rfatt -= 3; + bbatt += 2; + } else { + rfatt -= 2; + bbatt -= 2; + } + } + } + /* Save the control values */ + gphy->tx_control = tx_control; + b43_put_attenuation_into_ranges(dev, &bbatt, &rfatt); + gphy->rfatt.att = rfatt; + gphy->bbatt.att = bbatt; + + if (b43_debug(dev, B43_DBG_XMITPOWER)) + b43dbg(dev->wl, "Adjusting TX power\n"); + + /* Adjust the hardware */ + b43_phy_lock(dev); + b43_radio_lock(dev); + b43_set_txpower_g(dev, &gphy->bbatt, &gphy->rfatt, + gphy->tx_control); + b43_radio_unlock(dev); + b43_phy_unlock(dev); + + b43_mac_enable(dev); +} + +static enum b43_txpwr_result b43_gphy_op_recalc_txpower(struct b43_wldev *dev, + bool ignore_tssi) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + unsigned int average_tssi; + int cck_result, ofdm_result; + int estimated_pwr, desired_pwr, pwr_adjust; + int rfatt_delta, bbatt_delta; + unsigned int max_pwr; + + /* First get the average TSSI */ + cck_result = b43_phy_shm_tssi_read(dev, B43_SHM_SH_TSSI_CCK); + ofdm_result = b43_phy_shm_tssi_read(dev, B43_SHM_SH_TSSI_OFDM_G); + if ((cck_result < 0) && (ofdm_result < 0)) { + /* No TSSI information available */ + if (!ignore_tssi) + goto no_adjustment_needed; + cck_result = 0; + ofdm_result = 0; + } + if (cck_result < 0) + average_tssi = ofdm_result; + else if (ofdm_result < 0) + average_tssi = cck_result; + else + average_tssi = (cck_result + ofdm_result) / 2; + /* Merge the average with the stored value. */ + if (likely(gphy->average_tssi != 0xFF)) + average_tssi = (average_tssi + gphy->average_tssi) / 2; + gphy->average_tssi = average_tssi; + B43_WARN_ON(average_tssi >= B43_TSSI_MAX); + + /* Estimate the TX power emission based on the TSSI */ + estimated_pwr = b43_gphy_estimate_power_out(dev, average_tssi); + + B43_WARN_ON(phy->type != B43_PHYTYPE_G); + max_pwr = dev->dev->bus_sprom->maxpwr_bg; + if (dev->dev->bus_sprom->boardflags_lo & B43_BFL_PACTRL) + max_pwr -= 3; /* minus 0.75 */ + if (unlikely(max_pwr >= INT_TO_Q52(30/*dBm*/))) { + b43warn(dev->wl, + "Invalid max-TX-power value in SPROM.\n"); + max_pwr = INT_TO_Q52(20); /* fake it */ + dev->dev->bus_sprom->maxpwr_bg = max_pwr; + } + + /* Get desired power (in Q5.2) */ + if (phy->desired_txpower < 0) + desired_pwr = INT_TO_Q52(0); + else + desired_pwr = INT_TO_Q52(phy->desired_txpower); + /* And limit it. max_pwr already is Q5.2 */ + desired_pwr = clamp_val(desired_pwr, 0, max_pwr); + if (b43_debug(dev, B43_DBG_XMITPOWER)) { + b43dbg(dev->wl, + "[TX power] current = " Q52_FMT + " dBm, desired = " Q52_FMT + " dBm, max = " Q52_FMT "\n", + Q52_ARG(estimated_pwr), + Q52_ARG(desired_pwr), + Q52_ARG(max_pwr)); + } + + /* Calculate the adjustment delta. */ + pwr_adjust = desired_pwr - estimated_pwr; + if (pwr_adjust == 0) + goto no_adjustment_needed; + + /* RF attenuation delta. */ + rfatt_delta = ((pwr_adjust + 7) / 8); + /* Lower attenuation => Bigger power output. Negate it. */ + rfatt_delta = -rfatt_delta; + + /* Baseband attenuation delta. */ + bbatt_delta = pwr_adjust / 2; + /* Lower attenuation => Bigger power output. Negate it. */ + bbatt_delta = -bbatt_delta; + /* RF att affects power level 4 times as much as + * Baseband attennuation. Subtract it. */ + bbatt_delta -= 4 * rfatt_delta; + +#if B43_DEBUG + if (b43_debug(dev, B43_DBG_XMITPOWER)) { + int dbm = pwr_adjust < 0 ? -pwr_adjust : pwr_adjust; + b43dbg(dev->wl, + "[TX power deltas] %s" Q52_FMT " dBm => " + "bbatt-delta = %d, rfatt-delta = %d\n", + (pwr_adjust < 0 ? "-" : ""), Q52_ARG(dbm), + bbatt_delta, rfatt_delta); + } +#endif /* DEBUG */ + + /* So do we finally need to adjust something in hardware? */ + if ((rfatt_delta == 0) && (bbatt_delta == 0)) + goto no_adjustment_needed; + + /* Save the deltas for later when we adjust the power. */ + gphy->bbatt_delta = bbatt_delta; + gphy->rfatt_delta = rfatt_delta; + + /* We need to adjust the TX power on the device. */ + return B43_TXPWR_RES_NEED_ADJUST; + +no_adjustment_needed: + return B43_TXPWR_RES_DONE; +} + +static void b43_gphy_op_pwork_15sec(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + + b43_mac_suspend(dev); + //TODO: update_aci_moving_average + if (gphy->aci_enable && gphy->aci_wlan_automatic) { + if (!gphy->aci_enable && 1 /*TODO: not scanning? */ ) { + if (0 /*TODO: bunch of conditions */ ) { + phy->ops->interf_mitigation(dev, + B43_INTERFMODE_MANUALWLAN); + } + } else if (0 /*TODO*/) { + if (/*(aci_average > 1000) &&*/ !b43_gphy_aci_scan(dev)) + phy->ops->interf_mitigation(dev, B43_INTERFMODE_NONE); + } + } else if (gphy->interfmode == B43_INTERFMODE_NONWLAN && + phy->rev == 1) { + //TODO: implement rev1 workaround + } + b43_lo_g_maintanance_work(dev); + b43_mac_enable(dev); +} + +static void b43_gphy_op_pwork_60sec(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + + if (!(dev->dev->bus_sprom->boardflags_lo & B43_BFL_RSSI)) + return; + + b43_mac_suspend(dev); + b43_calc_nrssi_slope(dev); + if ((phy->radio_ver == 0x2050) && (phy->radio_rev == 8)) { + u8 old_chan = phy->channel; + + /* VCO Calibration */ + if (old_chan >= 8) + b43_switch_channel(dev, 1); + else + b43_switch_channel(dev, 13); + b43_switch_channel(dev, old_chan); + } + b43_mac_enable(dev); +} + +const struct b43_phy_operations b43_phyops_g = { + .allocate = b43_gphy_op_allocate, + .free = b43_gphy_op_free, + .prepare_structs = b43_gphy_op_prepare_structs, + .prepare_hardware = b43_gphy_op_prepare_hardware, + .init = b43_gphy_op_init, + .exit = b43_gphy_op_exit, + .phy_read = b43_gphy_op_read, + .phy_write = b43_gphy_op_write, + .radio_read = b43_gphy_op_radio_read, + .radio_write = b43_gphy_op_radio_write, + .supports_hwpctl = b43_gphy_op_supports_hwpctl, + .software_rfkill = b43_gphy_op_software_rfkill, + .switch_analog = b43_phyop_switch_analog_generic, + .switch_channel = b43_gphy_op_switch_channel, + .get_default_chan = b43_gphy_op_get_default_chan, + .set_rx_antenna = b43_gphy_op_set_rx_antenna, + .interf_mitigation = b43_gphy_op_interf_mitigation, + .recalc_txpower = b43_gphy_op_recalc_txpower, + .adjust_txpower = b43_gphy_op_adjust_txpower, + .pwork_15sec = b43_gphy_op_pwork_15sec, + .pwork_60sec = b43_gphy_op_pwork_60sec, +}; diff --git a/drivers/net/wireless/b43/phy_g.h b/drivers/net/wireless/b43/phy_g.h new file mode 100644 index 0000000..5413c90 --- /dev/null +++ b/drivers/net/wireless/b43/phy_g.h @@ -0,0 +1,208 @@ +#ifndef LINUX_B43_PHY_G_H_ +#define LINUX_B43_PHY_G_H_ + +/* OFDM PHY registers are defined in the A-PHY header. */ +#include "phy_a.h" + +/* CCK (B) PHY Registers */ +#define B43_PHY_VERSION_CCK B43_PHY_CCK(0x00) /* Versioning register for B-PHY */ +#define B43_PHY_CCKBBANDCFG B43_PHY_CCK(0x01) /* Contains antenna 0/1 control bit */ +#define B43_PHY_PGACTL B43_PHY_CCK(0x15) /* PGA control */ +#define B43_PHY_PGACTL_LPF 0x1000 /* Low pass filter (?) */ +#define B43_PHY_PGACTL_LOWBANDW 0x0040 /* Low bandwidth flag */ +#define B43_PHY_PGACTL_UNKNOWN 0xEFA0 +#define B43_PHY_FBCTL1 B43_PHY_CCK(0x18) /* Frequency bandwidth control 1 */ +#define B43_PHY_ITSSI B43_PHY_CCK(0x29) /* Idle TSSI */ +#define B43_PHY_LO_LEAKAGE B43_PHY_CCK(0x2D) /* Measured LO leakage */ +#define B43_PHY_ENERGY B43_PHY_CCK(0x33) /* Energy */ +#define B43_PHY_SYNCCTL B43_PHY_CCK(0x35) +#define B43_PHY_FBCTL2 B43_PHY_CCK(0x38) /* Frequency bandwidth control 2 */ +#define B43_PHY_DACCTL B43_PHY_CCK(0x60) /* DAC control */ +#define B43_PHY_RCCALOVER B43_PHY_CCK(0x78) /* RC calibration override */ + +/* Extended G-PHY Registers */ +#define B43_PHY_CLASSCTL B43_PHY_EXTG(0x02) /* Classify control */ +#define B43_PHY_GTABCTL B43_PHY_EXTG(0x03) /* G-PHY table control (see below) */ +#define B43_PHY_GTABOFF 0x03FF /* G-PHY table offset (see below) */ +#define B43_PHY_GTABNR 0xFC00 /* G-PHY table number (see below) */ +#define B43_PHY_GTABNR_SHIFT 10 +#define B43_PHY_GTABDATA B43_PHY_EXTG(0x04) /* G-PHY table data */ +#define B43_PHY_LO_MASK B43_PHY_EXTG(0x0F) /* Local Oscillator control mask */ +#define B43_PHY_LO_CTL B43_PHY_EXTG(0x10) /* Local Oscillator control */ +#define B43_PHY_RFOVER B43_PHY_EXTG(0x11) /* RF override */ +#define B43_PHY_RFOVERVAL B43_PHY_EXTG(0x12) /* RF override value */ +#define B43_PHY_RFOVERVAL_EXTLNA 0x8000 +#define B43_PHY_RFOVERVAL_LNA 0x7000 +#define B43_PHY_RFOVERVAL_LNA_SHIFT 12 +#define B43_PHY_RFOVERVAL_PGA 0x0F00 +#define B43_PHY_RFOVERVAL_PGA_SHIFT 8 +#define B43_PHY_RFOVERVAL_UNK 0x0010 /* Unknown, always set. */ +#define B43_PHY_RFOVERVAL_TRSWRX 0x00E0 +#define B43_PHY_RFOVERVAL_BW 0x0003 /* Bandwidth flags */ +#define B43_PHY_RFOVERVAL_BW_LPF 0x0001 /* Low Pass Filter */ +#define B43_PHY_RFOVERVAL_BW_LBW 0x0002 /* Low Bandwidth (when set), high when unset */ +#define B43_PHY_ANALOGOVER B43_PHY_EXTG(0x14) /* Analog override */ +#define B43_PHY_ANALOGOVERVAL B43_PHY_EXTG(0x15) /* Analog override value */ + + +/*** G-PHY table numbers */ +#define B43_GTAB(number, offset) (((number) << B43_PHY_GTABNR_SHIFT) | (offset)) +#define B43_GTAB_NRSSI B43_GTAB(0x00, 0) +#define B43_GTAB_TRFEMW B43_GTAB(0x0C, 0x120) +#define B43_GTAB_ORIGTR B43_GTAB(0x2E, 0x298) + +u16 b43_gtab_read(struct b43_wldev *dev, u16 table, u16 offset); +void b43_gtab_write(struct b43_wldev *dev, u16 table, u16 offset, u16 value); + + +/* Returns the boolean whether "TX Magnification" is enabled. */ +#define has_tx_magnification(phy) \ + (((phy)->rev >= 2) && \ + ((phy)->radio_ver == 0x2050) && \ + ((phy)->radio_rev == 8)) +/* Card uses the loopback gain stuff */ +#define has_loopback_gain(phy) \ + (((phy)->rev > 1) || ((phy)->gmode)) + +/* Radio Attenuation (RF Attenuation) */ +struct b43_rfatt { + u8 att; /* Attenuation value */ + bool with_padmix; /* Flag, PAD Mixer enabled. */ +}; +struct b43_rfatt_list { + /* Attenuation values list */ + const struct b43_rfatt *list; + u8 len; + /* Minimum/Maximum attenuation values */ + u8 min_val; + u8 max_val; +}; + +/* Returns true, if the values are the same. */ +static inline bool b43_compare_rfatt(const struct b43_rfatt *a, + const struct b43_rfatt *b) +{ + return ((a->att == b->att) && + (a->with_padmix == b->with_padmix)); +} + +/* Baseband Attenuation */ +struct b43_bbatt { + u8 att; /* Attenuation value */ +}; +struct b43_bbatt_list { + /* Attenuation values list */ + const struct b43_bbatt *list; + u8 len; + /* Minimum/Maximum attenuation values */ + u8 min_val; + u8 max_val; +}; + +/* Returns true, if the values are the same. */ +static inline bool b43_compare_bbatt(const struct b43_bbatt *a, + const struct b43_bbatt *b) +{ + return (a->att == b->att); +} + +/* tx_control bits. */ +#define B43_TXCTL_PA3DB 0x40 /* PA Gain 3dB */ +#define B43_TXCTL_PA2DB 0x20 /* PA Gain 2dB */ +#define B43_TXCTL_TXMIX 0x10 /* TX Mixer Gain */ + +struct b43_txpower_lo_control; + +struct b43_phy_g { + /* ACI (adjacent channel interference) flags. */ + bool aci_enable; + bool aci_wlan_automatic; + bool aci_hw_rssi; + + /* Radio switched on/off */ + bool radio_on; + struct { + /* Values saved when turning the radio off. + * They are needed when turning it on again. */ + bool valid; + u16 rfover; + u16 rfoverval; + } radio_off_context; + + u16 minlowsig[2]; + u16 minlowsigpos[2]; + + /* Pointer to the table used to convert a + * TSSI value to dBm-Q5.2 */ + const s8 *tssi2dbm; + /* tssi2dbm is kmalloc()ed. Only used for free()ing. */ + bool dyn_tssi_tbl; + /* Target idle TSSI */ + int tgt_idle_tssi; + /* Current idle TSSI */ + int cur_idle_tssi; + /* The current average TSSI. */ + u8 average_tssi; + /* Current TX power level attenuation control values */ + struct b43_bbatt bbatt; + struct b43_rfatt rfatt; + u8 tx_control; /* B43_TXCTL_XXX */ + /* The calculated attenuation deltas that are used later + * when adjusting the actual power output. */ + int bbatt_delta; + int rfatt_delta; + + /* LocalOscillator control values. */ + struct b43_txpower_lo_control *lo_control; + /* Values from b43_calc_loopback_gain() */ + s16 max_lb_gain; /* Maximum Loopback gain in hdB */ + s16 trsw_rx_gain; /* TRSW RX gain in hdB */ + s16 lna_lod_gain; /* LNA lod */ + s16 lna_gain; /* LNA */ + s16 pga_gain; /* PGA */ + + /* Current Interference Mitigation mode */ + int interfmode; + /* Stack of saved values from the Interference Mitigation code. + * Each value in the stack is laid out as follows: + * bit 0-11: offset + * bit 12-15: register ID + * bit 16-32: value + * register ID is: 0x1 PHY, 0x2 Radio, 0x3 ILT + */ +#define B43_INTERFSTACK_SIZE 26 + u32 interfstack[B43_INTERFSTACK_SIZE]; //FIXME: use a data structure + + /* Saved values from the NRSSI Slope calculation */ + s16 nrssi[2]; + s32 nrssislope; + /* In memory nrssi lookup table. */ + s8 nrssi_lt[64]; + + u16 lofcal; + + u16 initval; //FIXME rename? + + /* The device does address auto increment for the OFDM tables. + * We cache the previously used address here and omit the address + * write on the next table access, if possible. */ + u16 ofdmtab_addr; /* The address currently set in hardware. */ + enum { /* The last data flow direction. */ + B43_OFDMTAB_DIRECTION_UNKNOWN = 0, + B43_OFDMTAB_DIRECTION_READ, + B43_OFDMTAB_DIRECTION_WRITE, + } ofdmtab_addr_direction; +}; + +void b43_gphy_set_baseband_attenuation(struct b43_wldev *dev, + u16 baseband_attenuation); +void b43_gphy_channel_switch(struct b43_wldev *dev, + unsigned int channel, + bool synthetic_pu_workaround); +u8 * b43_generate_dyn_tssi2dbm_tab(struct b43_wldev *dev, + s16 pab0, s16 pab1, s16 pab2); + +struct b43_phy_operations; +extern const struct b43_phy_operations b43_phyops_g; + +#endif /* LINUX_B43_PHY_G_H_ */ diff --git a/drivers/net/wireless/b43/phy_ht.c b/drivers/net/wireless/b43/phy_ht.c new file mode 100644 index 0000000..5d6833f --- /dev/null +++ b/drivers/net/wireless/b43/phy_ht.c @@ -0,0 +1,1140 @@ +/* + + Broadcom B43 wireless driver + IEEE 802.11n HT-PHY support + + Copyright (c) 2011 RafaÅ‚ MiÅ‚ecki + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include + +#include "b43.h" +#include "phy_ht.h" +#include "tables_phy_ht.h" +#include "radio_2059.h" +#include "main.h" + +/* Force values to keep compatibility with wl */ +enum ht_rssi_type { + HT_RSSI_W1 = 0, + HT_RSSI_W2 = 1, + HT_RSSI_NB = 2, + HT_RSSI_IQ = 3, + HT_RSSI_TSSI_2G = 4, + HT_RSSI_TSSI_5G = 5, + HT_RSSI_TBD = 6, +}; + +/************************************************** + * Radio 2059. + **************************************************/ + +static void b43_radio_2059_channel_setup(struct b43_wldev *dev, + const struct b43_phy_ht_channeltab_e_radio2059 *e) +{ + static const u16 routing[] = { R2059_C1, R2059_C2, R2059_C3, }; + u16 r; + int core; + + b43_radio_write(dev, 0x16, e->radio_syn16); + b43_radio_write(dev, 0x17, e->radio_syn17); + b43_radio_write(dev, 0x22, e->radio_syn22); + b43_radio_write(dev, 0x25, e->radio_syn25); + b43_radio_write(dev, 0x27, e->radio_syn27); + b43_radio_write(dev, 0x28, e->radio_syn28); + b43_radio_write(dev, 0x29, e->radio_syn29); + b43_radio_write(dev, 0x2c, e->radio_syn2c); + b43_radio_write(dev, 0x2d, e->radio_syn2d); + b43_radio_write(dev, 0x37, e->radio_syn37); + b43_radio_write(dev, 0x41, e->radio_syn41); + b43_radio_write(dev, 0x43, e->radio_syn43); + b43_radio_write(dev, 0x47, e->radio_syn47); + + for (core = 0; core < 3; core++) { + r = routing[core]; + b43_radio_write(dev, r | 0x4a, e->radio_rxtx4a); + b43_radio_write(dev, r | 0x58, e->radio_rxtx58); + b43_radio_write(dev, r | 0x5a, e->radio_rxtx5a); + b43_radio_write(dev, r | 0x6a, e->radio_rxtx6a); + b43_radio_write(dev, r | 0x6d, e->radio_rxtx6d); + b43_radio_write(dev, r | 0x6e, e->radio_rxtx6e); + b43_radio_write(dev, r | 0x92, e->radio_rxtx92); + b43_radio_write(dev, r | 0x98, e->radio_rxtx98); + } + + udelay(50); + + /* Calibration */ + b43_radio_mask(dev, 0x2b, ~0x1); + b43_radio_mask(dev, 0x2e, ~0x4); + b43_radio_set(dev, 0x2e, 0x4); + b43_radio_set(dev, 0x2b, 0x1); + + udelay(300); +} + +static void b43_radio_2059_init(struct b43_wldev *dev) +{ + const u16 routing[] = { R2059_C1, R2059_C2, R2059_C3 }; + const u16 radio_values[3][2] = { + { 0x61, 0xE9 }, { 0x69, 0xD5 }, { 0x73, 0x99 }, + }; + u16 i, j; + + b43_radio_write(dev, R2059_ALL | 0x51, 0x0070); + b43_radio_write(dev, R2059_ALL | 0x5a, 0x0003); + + for (i = 0; i < ARRAY_SIZE(routing); i++) + b43_radio_set(dev, routing[i] | 0x146, 0x3); + + b43_radio_set(dev, 0x2e, 0x0078); + b43_radio_set(dev, 0xc0, 0x0080); + msleep(2); + b43_radio_mask(dev, 0x2e, ~0x0078); + b43_radio_mask(dev, 0xc0, ~0x0080); + + if (1) { /* FIXME */ + b43_radio_set(dev, R2059_C3 | 0x4, 0x1); + udelay(10); + b43_radio_set(dev, R2059_C3 | 0x0BF, 0x1); + b43_radio_maskset(dev, R2059_C3 | 0x19B, 0x3, 0x2); + + b43_radio_set(dev, R2059_C3 | 0x4, 0x2); + udelay(100); + b43_radio_mask(dev, R2059_C3 | 0x4, ~0x2); + + for (i = 0; i < 10000; i++) { + if (b43_radio_read(dev, R2059_C3 | 0x145) & 1) { + i = 0; + break; + } + udelay(100); + } + if (i) + b43err(dev->wl, "radio 0x945 timeout\n"); + + b43_radio_mask(dev, R2059_C3 | 0x4, ~0x1); + b43_radio_set(dev, 0xa, 0x60); + + for (i = 0; i < 3; i++) { + b43_radio_write(dev, 0x17F, radio_values[i][0]); + b43_radio_write(dev, 0x13D, 0x6E); + b43_radio_write(dev, 0x13E, radio_values[i][1]); + b43_radio_write(dev, 0x13C, 0x55); + + for (j = 0; j < 10000; j++) { + if (b43_radio_read(dev, 0x140) & 2) { + j = 0; + break; + } + udelay(500); + } + if (j) + b43err(dev->wl, "radio 0x140 timeout\n"); + + b43_radio_write(dev, 0x13C, 0x15); + } + + b43_radio_mask(dev, 0x17F, ~0x1); + } + + b43_radio_mask(dev, 0x11, ~0x0008); +} + +/************************************************** + * RF + **************************************************/ + +static void b43_phy_ht_force_rf_sequence(struct b43_wldev *dev, u16 rf_seq) +{ + u8 i; + + u16 save_seq_mode = b43_phy_read(dev, B43_PHY_HT_RF_SEQ_MODE); + b43_phy_set(dev, B43_PHY_HT_RF_SEQ_MODE, 0x3); + + b43_phy_set(dev, B43_PHY_HT_RF_SEQ_TRIG, rf_seq); + for (i = 0; i < 200; i++) { + if (!(b43_phy_read(dev, B43_PHY_HT_RF_SEQ_STATUS) & rf_seq)) { + i = 0; + break; + } + msleep(1); + } + if (i) + b43err(dev->wl, "Forcing RF sequence timeout\n"); + + b43_phy_write(dev, B43_PHY_HT_RF_SEQ_MODE, save_seq_mode); +} + +static void b43_phy_ht_pa_override(struct b43_wldev *dev, bool enable) +{ + struct b43_phy_ht *htphy = dev->phy.ht; + static const u16 regs[3] = { B43_PHY_HT_RF_CTL_INT_C1, + B43_PHY_HT_RF_CTL_INT_C2, + B43_PHY_HT_RF_CTL_INT_C3 }; + int i; + + if (enable) { + for (i = 0; i < 3; i++) + b43_phy_write(dev, regs[i], htphy->rf_ctl_int_save[i]); + } else { + for (i = 0; i < 3; i++) + htphy->rf_ctl_int_save[i] = b43_phy_read(dev, regs[i]); + /* TODO: Does 5GHz band use different value (not 0x0400)? */ + for (i = 0; i < 3; i++) + b43_phy_write(dev, regs[i], 0x0400); + } +} + +/************************************************** + * Various PHY ops + **************************************************/ + +static u16 b43_phy_ht_classifier(struct b43_wldev *dev, u16 mask, u16 val) +{ + u16 tmp; + u16 allowed = B43_PHY_HT_CLASS_CTL_CCK_EN | + B43_PHY_HT_CLASS_CTL_OFDM_EN | + B43_PHY_HT_CLASS_CTL_WAITED_EN; + + tmp = b43_phy_read(dev, B43_PHY_HT_CLASS_CTL); + tmp &= allowed; + tmp &= ~mask; + tmp |= (val & mask); + b43_phy_maskset(dev, B43_PHY_HT_CLASS_CTL, ~allowed, tmp); + + return tmp; +} + +static void b43_phy_ht_reset_cca(struct b43_wldev *dev) +{ + u16 bbcfg; + + b43_phy_force_clock(dev, true); + bbcfg = b43_phy_read(dev, B43_PHY_HT_BBCFG); + b43_phy_write(dev, B43_PHY_HT_BBCFG, bbcfg | B43_PHY_HT_BBCFG_RSTCCA); + udelay(1); + b43_phy_write(dev, B43_PHY_HT_BBCFG, bbcfg & ~B43_PHY_HT_BBCFG_RSTCCA); + b43_phy_force_clock(dev, false); + + b43_phy_ht_force_rf_sequence(dev, B43_PHY_HT_RF_SEQ_TRIG_RST2RX); +} + +static void b43_phy_ht_zero_extg(struct b43_wldev *dev) +{ + u8 i, j; + u16 base[] = { 0x40, 0x60, 0x80 }; + + for (i = 0; i < ARRAY_SIZE(base); i++) { + for (j = 0; j < 4; j++) + b43_phy_write(dev, B43_PHY_EXTG(base[i] + j), 0); + } + + for (i = 0; i < ARRAY_SIZE(base); i++) + b43_phy_write(dev, B43_PHY_EXTG(base[i] + 0xc), 0); +} + +/* Some unknown AFE (Analog Frondned) op */ +static void b43_phy_ht_afe_unk1(struct b43_wldev *dev) +{ + u8 i; + + static const u16 ctl_regs[3][2] = { + { B43_PHY_HT_AFE_C1_OVER, B43_PHY_HT_AFE_C1 }, + { B43_PHY_HT_AFE_C2_OVER, B43_PHY_HT_AFE_C2 }, + { B43_PHY_HT_AFE_C3_OVER, B43_PHY_HT_AFE_C3}, + }; + + for (i = 0; i < 3; i++) { + /* TODO: verify masks&sets */ + b43_phy_set(dev, ctl_regs[i][1], 0x4); + b43_phy_set(dev, ctl_regs[i][0], 0x4); + b43_phy_mask(dev, ctl_regs[i][1], ~0x1); + b43_phy_set(dev, ctl_regs[i][0], 0x1); + b43_httab_write(dev, B43_HTTAB16(8, 5 + (i * 0x10)), 0); + b43_phy_mask(dev, ctl_regs[i][0], ~0x4); + } +} + +static void b43_phy_ht_read_clip_detection(struct b43_wldev *dev, u16 *clip_st) +{ + clip_st[0] = b43_phy_read(dev, B43_PHY_HT_C1_CLIP1THRES); + clip_st[1] = b43_phy_read(dev, B43_PHY_HT_C2_CLIP1THRES); + clip_st[2] = b43_phy_read(dev, B43_PHY_HT_C3_CLIP1THRES); +} + +static void b43_phy_ht_bphy_init(struct b43_wldev *dev) +{ + unsigned int i; + u16 val; + + val = 0x1E1F; + for (i = 0; i < 16; i++) { + b43_phy_write(dev, B43_PHY_N_BMODE(0x88 + i), val); + val -= 0x202; + } + val = 0x3E3F; + for (i = 0; i < 16; i++) { + b43_phy_write(dev, B43_PHY_N_BMODE(0x98 + i), val); + val -= 0x202; + } + b43_phy_write(dev, B43_PHY_N_BMODE(0x38), 0x668); +} + +/************************************************** + * Samples + **************************************************/ + +static void b43_phy_ht_stop_playback(struct b43_wldev *dev) +{ + struct b43_phy_ht *phy_ht = dev->phy.ht; + u16 tmp; + int i; + + tmp = b43_phy_read(dev, B43_PHY_HT_SAMP_STAT); + if (tmp & 0x1) + b43_phy_set(dev, B43_PHY_HT_SAMP_CMD, B43_PHY_HT_SAMP_CMD_STOP); + else if (tmp & 0x2) + b43_phy_mask(dev, B43_PHY_HT_IQLOCAL_CMDGCTL, 0x7FFF); + + b43_phy_mask(dev, B43_PHY_HT_SAMP_CMD, ~0x0004); + + for (i = 0; i < 3; i++) { + if (phy_ht->bb_mult_save[i] >= 0) { + b43_httab_write(dev, B43_HTTAB16(13, 0x63 + i * 4), + phy_ht->bb_mult_save[i]); + b43_httab_write(dev, B43_HTTAB16(13, 0x67 + i * 4), + phy_ht->bb_mult_save[i]); + } + } +} + +static u16 b43_phy_ht_load_samples(struct b43_wldev *dev) +{ + int i; + u16 len = 20 << 3; + + b43_phy_write(dev, B43_PHY_HT_TABLE_ADDR, 0x4400); + + for (i = 0; i < len; i++) { + b43_phy_write(dev, B43_PHY_HT_TABLE_DATAHI, 0); + b43_phy_write(dev, B43_PHY_HT_TABLE_DATALO, 0); + } + + return len; +} + +static void b43_phy_ht_run_samples(struct b43_wldev *dev, u16 samps, u16 loops, + u16 wait) +{ + struct b43_phy_ht *phy_ht = dev->phy.ht; + u16 save_seq_mode; + int i; + + for (i = 0; i < 3; i++) { + if (phy_ht->bb_mult_save[i] < 0) + phy_ht->bb_mult_save[i] = b43_httab_read(dev, B43_HTTAB16(13, 0x63 + i * 4)); + } + + b43_phy_write(dev, B43_PHY_HT_SAMP_DEP_CNT, samps - 1); + if (loops != 0xFFFF) + loops--; + b43_phy_write(dev, B43_PHY_HT_SAMP_LOOP_CNT, loops); + b43_phy_write(dev, B43_PHY_HT_SAMP_WAIT_CNT, wait); + + save_seq_mode = b43_phy_read(dev, B43_PHY_HT_RF_SEQ_MODE); + b43_phy_set(dev, B43_PHY_HT_RF_SEQ_MODE, + B43_PHY_HT_RF_SEQ_MODE_CA_OVER); + + /* TODO: find out mask bits! Do we need more function arguments? */ + b43_phy_mask(dev, B43_PHY_HT_SAMP_CMD, ~0); + b43_phy_mask(dev, B43_PHY_HT_SAMP_CMD, ~0); + b43_phy_mask(dev, B43_PHY_HT_IQLOCAL_CMDGCTL, ~0); + b43_phy_set(dev, B43_PHY_HT_SAMP_CMD, 0x1); + + for (i = 0; i < 100; i++) { + if (!(b43_phy_read(dev, B43_PHY_HT_RF_SEQ_STATUS) & 1)) { + i = 0; + break; + } + udelay(10); + } + if (i) + b43err(dev->wl, "run samples timeout\n"); + + b43_phy_write(dev, B43_PHY_HT_RF_SEQ_MODE, save_seq_mode); +} + +static void b43_phy_ht_tx_tone(struct b43_wldev *dev) +{ + u16 samp; + + samp = b43_phy_ht_load_samples(dev); + b43_phy_ht_run_samples(dev, samp, 0xFFFF, 0); +} + +/************************************************** + * RSSI + **************************************************/ + +static void b43_phy_ht_rssi_select(struct b43_wldev *dev, u8 core_sel, + enum ht_rssi_type rssi_type) +{ + static const u16 ctl_regs[3][2] = { + { B43_PHY_HT_AFE_C1, B43_PHY_HT_AFE_C1_OVER, }, + { B43_PHY_HT_AFE_C2, B43_PHY_HT_AFE_C2_OVER, }, + { B43_PHY_HT_AFE_C3, B43_PHY_HT_AFE_C3_OVER, }, + }; + static const u16 radio_r[] = { R2059_C1, R2059_C2, R2059_C3, }; + int core; + + if (core_sel == 0) { + b43err(dev->wl, "RSSI selection for core off not implemented yet\n"); + } else { + for (core = 0; core < 3; core++) { + /* Check if caller requested a one specific core */ + if ((core_sel == 1 && core != 0) || + (core_sel == 2 && core != 1) || + (core_sel == 3 && core != 2)) + continue; + + switch (rssi_type) { + case HT_RSSI_TSSI_2G: + b43_phy_set(dev, ctl_regs[core][0], 0x3 << 8); + b43_phy_set(dev, ctl_regs[core][0], 0x3 << 10); + b43_phy_set(dev, ctl_regs[core][1], 0x1 << 9); + b43_phy_set(dev, ctl_regs[core][1], 0x1 << 10); + + b43_radio_set(dev, R2059_C3 | 0xbf, 0x1); + b43_radio_write(dev, radio_r[core] | 0x159, + 0x11); + break; + default: + b43err(dev->wl, "RSSI selection for type %d not implemented yet\n", + rssi_type); + } + } + } +} + +static void b43_phy_ht_poll_rssi(struct b43_wldev *dev, enum ht_rssi_type type, + s32 *buf, u8 nsamp) +{ + u16 phy_regs_values[12]; + static const u16 phy_regs_to_save[] = { + B43_PHY_HT_AFE_C1, B43_PHY_HT_AFE_C1_OVER, + 0x848, 0x841, + B43_PHY_HT_AFE_C2, B43_PHY_HT_AFE_C2_OVER, + 0x868, 0x861, + B43_PHY_HT_AFE_C3, B43_PHY_HT_AFE_C3_OVER, + 0x888, 0x881, + }; + u16 tmp[3]; + int i; + + for (i = 0; i < 12; i++) + phy_regs_values[i] = b43_phy_read(dev, phy_regs_to_save[i]); + + b43_phy_ht_rssi_select(dev, 5, type); + + for (i = 0; i < 6; i++) + buf[i] = 0; + + for (i = 0; i < nsamp; i++) { + tmp[0] = b43_phy_read(dev, B43_PHY_HT_RSSI_C1); + tmp[1] = b43_phy_read(dev, B43_PHY_HT_RSSI_C2); + tmp[2] = b43_phy_read(dev, B43_PHY_HT_RSSI_C3); + + buf[0] += ((s8)((tmp[0] & 0x3F) << 2)) >> 2; + buf[1] += ((s8)(((tmp[0] >> 8) & 0x3F) << 2)) >> 2; + buf[2] += ((s8)((tmp[1] & 0x3F) << 2)) >> 2; + buf[3] += ((s8)(((tmp[1] >> 8) & 0x3F) << 2)) >> 2; + buf[4] += ((s8)((tmp[2] & 0x3F) << 2)) >> 2; + buf[5] += ((s8)(((tmp[2] >> 8) & 0x3F) << 2)) >> 2; + } + + for (i = 0; i < 12; i++) + b43_phy_write(dev, phy_regs_to_save[i], phy_regs_values[i]); +} + +/************************************************** + * Tx/Rx + **************************************************/ + +static void b43_phy_ht_tx_power_fix(struct b43_wldev *dev) +{ + int i; + + for (i = 0; i < 3; i++) { + u16 mask; + u32 tmp = b43_httab_read(dev, B43_HTTAB32(26, 0xE8)); + + if (0) /* FIXME */ + mask = 0x2 << (i * 4); + else + mask = 0; + b43_phy_mask(dev, B43_PHY_EXTG(0x108), mask); + + b43_httab_write(dev, B43_HTTAB16(7, 0x110 + i), tmp >> 16); + b43_httab_write(dev, B43_HTTAB8(13, 0x63 + (i * 4)), + tmp & 0xFF); + b43_httab_write(dev, B43_HTTAB8(13, 0x73 + (i * 4)), + tmp & 0xFF); + } +} + +static void b43_phy_ht_tx_power_ctl(struct b43_wldev *dev, bool enable) +{ + struct b43_phy_ht *phy_ht = dev->phy.ht; + u16 en_bits = B43_PHY_HT_TXPCTL_CMD_C1_COEFF | + B43_PHY_HT_TXPCTL_CMD_C1_HWPCTLEN | + B43_PHY_HT_TXPCTL_CMD_C1_PCTLEN; + static const u16 cmd_regs[3] = { B43_PHY_HT_TXPCTL_CMD_C1, + B43_PHY_HT_TXPCTL_CMD_C2, + B43_PHY_HT_TXPCTL_CMD_C3 }; + static const u16 status_regs[3] = { B43_PHY_HT_TX_PCTL_STATUS_C1, + B43_PHY_HT_TX_PCTL_STATUS_C2, + B43_PHY_HT_TX_PCTL_STATUS_C3 }; + int i; + + if (!enable) { + if (b43_phy_read(dev, B43_PHY_HT_TXPCTL_CMD_C1) & en_bits) { + /* We disable enabled TX pwr ctl, save it's state */ + for (i = 0; i < 3; i++) + phy_ht->tx_pwr_idx[i] = + b43_phy_read(dev, status_regs[i]); + } + b43_phy_mask(dev, B43_PHY_HT_TXPCTL_CMD_C1, ~en_bits); + } else { + b43_phy_set(dev, B43_PHY_HT_TXPCTL_CMD_C1, en_bits); + + if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) { + for (i = 0; i < 3; i++) + b43_phy_write(dev, cmd_regs[i], 0x32); + } + + for (i = 0; i < 3; i++) + if (phy_ht->tx_pwr_idx[i] <= + B43_PHY_HT_TXPCTL_CMD_C1_INIT) + b43_phy_write(dev, cmd_regs[i], + phy_ht->tx_pwr_idx[i]); + } + + phy_ht->tx_pwr_ctl = enable; +} + +static void b43_phy_ht_tx_power_ctl_idle_tssi(struct b43_wldev *dev) +{ + struct b43_phy_ht *phy_ht = dev->phy.ht; + static const u16 base[] = { 0x840, 0x860, 0x880 }; + u16 save_regs[3][3]; + s32 rssi_buf[6]; + int core; + + for (core = 0; core < 3; core++) { + save_regs[core][1] = b43_phy_read(dev, base[core] + 6); + save_regs[core][2] = b43_phy_read(dev, base[core] + 7); + save_regs[core][0] = b43_phy_read(dev, base[core] + 0); + + b43_phy_write(dev, base[core] + 6, 0); + b43_phy_mask(dev, base[core] + 7, ~0xF); /* 0xF? Or just 0x6? */ + b43_phy_set(dev, base[core] + 0, 0x0400); + b43_phy_set(dev, base[core] + 0, 0x1000); + } + + b43_phy_ht_tx_tone(dev); + udelay(20); + b43_phy_ht_poll_rssi(dev, HT_RSSI_TSSI_2G, rssi_buf, 1); + b43_phy_ht_stop_playback(dev); + b43_phy_ht_reset_cca(dev); + + phy_ht->idle_tssi[0] = rssi_buf[0] & 0xff; + phy_ht->idle_tssi[1] = rssi_buf[2] & 0xff; + phy_ht->idle_tssi[2] = rssi_buf[4] & 0xff; + + for (core = 0; core < 3; core++) { + b43_phy_write(dev, base[core] + 0, save_regs[core][0]); + b43_phy_write(dev, base[core] + 6, save_regs[core][1]); + b43_phy_write(dev, base[core] + 7, save_regs[core][2]); + } +} + +static void b43_phy_ht_tssi_setup(struct b43_wldev *dev) +{ + static const u16 routing[] = { R2059_C1, R2059_C2, R2059_C3, }; + int core; + + /* 0x159 is probably TX_SSI_MUX or TSSIG (by comparing to N-PHY) */ + for (core = 0; core < 3; core++) { + b43_radio_set(dev, 0x8bf, 0x1); + b43_radio_write(dev, routing[core] | 0x0159, 0x0011); + } +} + +static void b43_phy_ht_tx_power_ctl_setup(struct b43_wldev *dev) +{ + struct b43_phy_ht *phy_ht = dev->phy.ht; + struct ssb_sprom *sprom = dev->dev->bus_sprom; + + u8 *idle = phy_ht->idle_tssi; + u8 target[3]; + s16 a1[3], b0[3], b1[3]; + + u16 freq = dev->phy.channel_freq; + int i, c; + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + for (c = 0; c < 3; c++) { + target[c] = sprom->core_pwr_info[c].maxpwr_2g; + a1[c] = sprom->core_pwr_info[c].pa_2g[0]; + b0[c] = sprom->core_pwr_info[c].pa_2g[1]; + b1[c] = sprom->core_pwr_info[c].pa_2g[2]; + } + } else if (freq >= 4900 && freq < 5100) { + for (c = 0; c < 3; c++) { + target[c] = sprom->core_pwr_info[c].maxpwr_5gl; + a1[c] = sprom->core_pwr_info[c].pa_5gl[0]; + b0[c] = sprom->core_pwr_info[c].pa_5gl[1]; + b1[c] = sprom->core_pwr_info[c].pa_5gl[2]; + } + } else if (freq >= 5100 && freq < 5500) { + for (c = 0; c < 3; c++) { + target[c] = sprom->core_pwr_info[c].maxpwr_5g; + a1[c] = sprom->core_pwr_info[c].pa_5g[0]; + b0[c] = sprom->core_pwr_info[c].pa_5g[1]; + b1[c] = sprom->core_pwr_info[c].pa_5g[2]; + } + } else if (freq >= 5500) { + for (c = 0; c < 3; c++) { + target[c] = sprom->core_pwr_info[c].maxpwr_5gh; + a1[c] = sprom->core_pwr_info[c].pa_5gh[0]; + b0[c] = sprom->core_pwr_info[c].pa_5gh[1]; + b1[c] = sprom->core_pwr_info[c].pa_5gh[2]; + } + } else { + target[0] = target[1] = target[2] = 52; + a1[0] = a1[1] = a1[2] = -424; + b0[0] = b0[1] = b0[2] = 5612; + b1[0] = b1[1] = b1[2] = -1393; + } + + b43_phy_set(dev, B43_PHY_HT_TSSIMODE, B43_PHY_HT_TSSIMODE_EN); + b43_phy_mask(dev, B43_PHY_HT_TXPCTL_CMD_C1, + ~B43_PHY_HT_TXPCTL_CMD_C1_PCTLEN & 0xFFFF); + + /* TODO: Does it depend on sprom->fem.ghz2.tssipos? */ + b43_phy_set(dev, B43_PHY_HT_TXPCTL_IDLE_TSSI, 0x4000); + + b43_phy_maskset(dev, B43_PHY_HT_TXPCTL_CMD_C1, + ~B43_PHY_HT_TXPCTL_CMD_C1_INIT, 0x19); + b43_phy_maskset(dev, B43_PHY_HT_TXPCTL_CMD_C2, + ~B43_PHY_HT_TXPCTL_CMD_C2_INIT, 0x19); + b43_phy_maskset(dev, B43_PHY_HT_TXPCTL_CMD_C3, + ~B43_PHY_HT_TXPCTL_CMD_C3_INIT, 0x19); + + b43_phy_set(dev, B43_PHY_HT_TXPCTL_IDLE_TSSI, + B43_PHY_HT_TXPCTL_IDLE_TSSI_BINF); + + b43_phy_maskset(dev, B43_PHY_HT_TXPCTL_IDLE_TSSI, + ~B43_PHY_HT_TXPCTL_IDLE_TSSI_C1, + idle[0] << B43_PHY_HT_TXPCTL_IDLE_TSSI_C1_SHIFT); + b43_phy_maskset(dev, B43_PHY_HT_TXPCTL_IDLE_TSSI, + ~B43_PHY_HT_TXPCTL_IDLE_TSSI_C2, + idle[1] << B43_PHY_HT_TXPCTL_IDLE_TSSI_C2_SHIFT); + b43_phy_maskset(dev, B43_PHY_HT_TXPCTL_IDLE_TSSI2, + ~B43_PHY_HT_TXPCTL_IDLE_TSSI2_C3, + idle[2] << B43_PHY_HT_TXPCTL_IDLE_TSSI2_C3_SHIFT); + + b43_phy_maskset(dev, B43_PHY_HT_TXPCTL_N, ~B43_PHY_HT_TXPCTL_N_TSSID, + 0xf0); + b43_phy_maskset(dev, B43_PHY_HT_TXPCTL_N, ~B43_PHY_HT_TXPCTL_N_NPTIL2, + 0x3 << B43_PHY_HT_TXPCTL_N_NPTIL2_SHIFT); +#if 0 + /* TODO: what to mask/set? */ + b43_phy_maskset(dev, B43_PHY_HT_TXPCTL_CMD_C1, 0x800, 0) + b43_phy_maskset(dev, B43_PHY_HT_TXPCTL_CMD_C1, 0x400, 0) +#endif + + b43_phy_maskset(dev, B43_PHY_HT_TXPCTL_TARG_PWR, + ~B43_PHY_HT_TXPCTL_TARG_PWR_C1, + target[0] << B43_PHY_HT_TXPCTL_TARG_PWR_C1_SHIFT); + b43_phy_maskset(dev, B43_PHY_HT_TXPCTL_TARG_PWR, + ~B43_PHY_HT_TXPCTL_TARG_PWR_C2 & 0xFFFF, + target[1] << B43_PHY_HT_TXPCTL_TARG_PWR_C2_SHIFT); + b43_phy_maskset(dev, B43_PHY_HT_TXPCTL_TARG_PWR2, + ~B43_PHY_HT_TXPCTL_TARG_PWR2_C3, + target[2] << B43_PHY_HT_TXPCTL_TARG_PWR2_C3_SHIFT); + + for (c = 0; c < 3; c++) { + s32 num, den, pwr; + u32 regval[64]; + + for (i = 0; i < 64; i++) { + num = 8 * (16 * b0[c] + b1[c] * i); + den = 32768 + a1[c] * i; + pwr = max((4 * num + den / 2) / den, -8); + regval[i] = pwr; + } + b43_httab_write_bulk(dev, B43_HTTAB16(26 + c, 0), 64, regval); + } +} + +/************************************************** + * Channel switching ops. + **************************************************/ + +static void b43_phy_ht_spur_avoid(struct b43_wldev *dev, + struct ieee80211_channel *new_channel) +{ + struct bcma_device *core = dev->dev->bdev; + int spuravoid = 0; + u16 tmp; + + /* Check for 13 and 14 is just a guess, we don't have enough logs. */ + if (new_channel->hw_value == 13 || new_channel->hw_value == 14) + spuravoid = 1; + bcma_core_pll_ctl(core, B43_BCMA_CLKCTLST_PHY_PLL_REQ, 0, false); + bcma_pmu_spuravoid_pllupdate(&core->bus->drv_cc, spuravoid); + bcma_core_pll_ctl(core, + B43_BCMA_CLKCTLST_80211_PLL_REQ | + B43_BCMA_CLKCTLST_PHY_PLL_REQ, + B43_BCMA_CLKCTLST_80211_PLL_ST | + B43_BCMA_CLKCTLST_PHY_PLL_ST, false); + + /* Values has been taken from wlc_bmac_switch_macfreq comments */ + switch (spuravoid) { + case 2: /* 126MHz */ + tmp = 0x2082; + break; + case 1: /* 123MHz */ + tmp = 0x5341; + break; + default: /* 120MHz */ + tmp = 0x8889; + } + + b43_write16(dev, B43_MMIO_TSF_CLK_FRAC_LOW, tmp); + b43_write16(dev, B43_MMIO_TSF_CLK_FRAC_HIGH, 0x8); + + /* TODO: reset PLL */ + + if (spuravoid) + b43_phy_set(dev, B43_PHY_HT_BBCFG, B43_PHY_HT_BBCFG_RSTRX); + else + b43_phy_mask(dev, B43_PHY_HT_BBCFG, + ~B43_PHY_HT_BBCFG_RSTRX & 0xFFFF); + + b43_phy_ht_reset_cca(dev); +} + +static void b43_phy_ht_channel_setup(struct b43_wldev *dev, + const struct b43_phy_ht_channeltab_e_phy *e, + struct ieee80211_channel *new_channel) +{ + bool old_band_5ghz; + + old_band_5ghz = b43_phy_read(dev, B43_PHY_HT_BANDCTL) & 0; /* FIXME */ + if (new_channel->band == IEEE80211_BAND_5GHZ && !old_band_5ghz) { + /* TODO */ + } else if (new_channel->band == IEEE80211_BAND_2GHZ && old_band_5ghz) { + /* TODO */ + } + + b43_phy_write(dev, B43_PHY_HT_BW1, e->bw1); + b43_phy_write(dev, B43_PHY_HT_BW2, e->bw2); + b43_phy_write(dev, B43_PHY_HT_BW3, e->bw3); + b43_phy_write(dev, B43_PHY_HT_BW4, e->bw4); + b43_phy_write(dev, B43_PHY_HT_BW5, e->bw5); + b43_phy_write(dev, B43_PHY_HT_BW6, e->bw6); + + if (new_channel->hw_value == 14) { + b43_phy_ht_classifier(dev, B43_PHY_HT_CLASS_CTL_OFDM_EN, 0); + b43_phy_set(dev, B43_PHY_HT_TEST, 0x0800); + } else { + b43_phy_ht_classifier(dev, B43_PHY_HT_CLASS_CTL_OFDM_EN, + B43_PHY_HT_CLASS_CTL_OFDM_EN); + if (new_channel->band == IEEE80211_BAND_2GHZ) + b43_phy_mask(dev, B43_PHY_HT_TEST, ~0x840); + } + + if (1) /* TODO: On N it's for early devices only, what about HT? */ + b43_phy_ht_tx_power_fix(dev); + + b43_phy_ht_spur_avoid(dev, new_channel); + + b43_phy_write(dev, 0x017e, 0x3830); +} + +static int b43_phy_ht_set_channel(struct b43_wldev *dev, + struct ieee80211_channel *channel, + enum nl80211_channel_type channel_type) +{ + struct b43_phy *phy = &dev->phy; + + const struct b43_phy_ht_channeltab_e_radio2059 *chent_r2059 = NULL; + + if (phy->radio_ver == 0x2059) { + chent_r2059 = b43_phy_ht_get_channeltab_e_r2059(dev, + channel->center_freq); + if (!chent_r2059) + return -ESRCH; + } else { + return -ESRCH; + } + + /* TODO: In case of N-PHY some bandwidth switching goes here */ + + if (phy->radio_ver == 0x2059) { + b43_radio_2059_channel_setup(dev, chent_r2059); + b43_phy_ht_channel_setup(dev, &(chent_r2059->phy_regs), + channel); + } else { + return -ESRCH; + } + + return 0; +} + +/************************************************** + * Basic PHY ops. + **************************************************/ + +static int b43_phy_ht_op_allocate(struct b43_wldev *dev) +{ + struct b43_phy_ht *phy_ht; + + phy_ht = kzalloc(sizeof(*phy_ht), GFP_KERNEL); + if (!phy_ht) + return -ENOMEM; + dev->phy.ht = phy_ht; + + return 0; +} + +static void b43_phy_ht_op_prepare_structs(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_ht *phy_ht = phy->ht; + int i; + + memset(phy_ht, 0, sizeof(*phy_ht)); + + phy_ht->tx_pwr_ctl = true; + for (i = 0; i < 3; i++) + phy_ht->tx_pwr_idx[i] = B43_PHY_HT_TXPCTL_CMD_C1_INIT + 1; + + for (i = 0; i < 3; i++) + phy_ht->bb_mult_save[i] = -1; +} + +static int b43_phy_ht_op_init(struct b43_wldev *dev) +{ + struct b43_phy_ht *phy_ht = dev->phy.ht; + u16 tmp; + u16 clip_state[3]; + bool saved_tx_pwr_ctl; + + if (dev->dev->bus_type != B43_BUS_BCMA) { + b43err(dev->wl, "HT-PHY is supported only on BCMA bus!\n"); + return -EOPNOTSUPP; + } + + b43_phy_ht_tables_init(dev); + + b43_phy_mask(dev, 0x0be, ~0x2); + b43_phy_set(dev, 0x23f, 0x7ff); + b43_phy_set(dev, 0x240, 0x7ff); + b43_phy_set(dev, 0x241, 0x7ff); + + b43_phy_ht_zero_extg(dev); + + b43_phy_mask(dev, B43_PHY_EXTG(0), ~0x3); + + b43_phy_write(dev, B43_PHY_HT_AFE_C1_OVER, 0); + b43_phy_write(dev, B43_PHY_HT_AFE_C2_OVER, 0); + b43_phy_write(dev, B43_PHY_HT_AFE_C3_OVER, 0); + + b43_phy_write(dev, B43_PHY_EXTG(0x103), 0x20); + b43_phy_write(dev, B43_PHY_EXTG(0x101), 0x20); + b43_phy_write(dev, 0x20d, 0xb8); + b43_phy_write(dev, B43_PHY_EXTG(0x14f), 0xc8); + b43_phy_write(dev, 0x70, 0x50); + b43_phy_write(dev, 0x1ff, 0x30); + + if (0) /* TODO: condition */ + ; /* TODO: PHY op on reg 0x217 */ + + if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) + b43_phy_ht_classifier(dev, B43_PHY_HT_CLASS_CTL_CCK_EN, 0); + else + b43_phy_ht_classifier(dev, B43_PHY_HT_CLASS_CTL_CCK_EN, + B43_PHY_HT_CLASS_CTL_CCK_EN); + + b43_phy_set(dev, 0xb1, 0x91); + b43_phy_write(dev, 0x32f, 0x0003); + b43_phy_write(dev, 0x077, 0x0010); + b43_phy_write(dev, 0x0b4, 0x0258); + b43_phy_mask(dev, 0x17e, ~0x4000); + + b43_phy_write(dev, 0x0b9, 0x0072); + + b43_httab_write_few(dev, B43_HTTAB16(7, 0x14e), 2, 0x010f, 0x010f); + b43_httab_write_few(dev, B43_HTTAB16(7, 0x15e), 2, 0x010f, 0x010f); + b43_httab_write_few(dev, B43_HTTAB16(7, 0x16e), 2, 0x010f, 0x010f); + + b43_phy_ht_afe_unk1(dev); + + b43_httab_write_few(dev, B43_HTTAB16(7, 0x130), 9, 0x777, 0x111, 0x111, + 0x777, 0x111, 0x111, 0x777, 0x111, 0x111); + + b43_httab_write(dev, B43_HTTAB16(7, 0x120), 0x0777); + b43_httab_write(dev, B43_HTTAB16(7, 0x124), 0x0777); + + b43_httab_write(dev, B43_HTTAB16(8, 0x00), 0x02); + b43_httab_write(dev, B43_HTTAB16(8, 0x10), 0x02); + b43_httab_write(dev, B43_HTTAB16(8, 0x20), 0x02); + + b43_httab_write_few(dev, B43_HTTAB16(8, 0x08), 4, + 0x8e, 0x96, 0x96, 0x96); + b43_httab_write_few(dev, B43_HTTAB16(8, 0x18), 4, + 0x8f, 0x9f, 0x9f, 0x9f); + b43_httab_write_few(dev, B43_HTTAB16(8, 0x28), 4, + 0x8f, 0x9f, 0x9f, 0x9f); + + b43_httab_write_few(dev, B43_HTTAB16(8, 0x0c), 4, 0x2, 0x2, 0x2, 0x2); + b43_httab_write_few(dev, B43_HTTAB16(8, 0x1c), 4, 0x2, 0x2, 0x2, 0x2); + b43_httab_write_few(dev, B43_HTTAB16(8, 0x2c), 4, 0x2, 0x2, 0x2, 0x2); + + b43_phy_maskset(dev, 0x0280, 0xff00, 0x3e); + b43_phy_maskset(dev, 0x0283, 0xff00, 0x3e); + b43_phy_maskset(dev, B43_PHY_OFDM(0x0141), 0xff00, 0x46); + b43_phy_maskset(dev, 0x0283, 0xff00, 0x40); + + b43_httab_write_few(dev, B43_HTTAB16(00, 0x8), 4, + 0x09, 0x0e, 0x13, 0x18); + b43_httab_write_few(dev, B43_HTTAB16(01, 0x8), 4, + 0x09, 0x0e, 0x13, 0x18); + /* TODO: Did wl mean 2 instead of 40? */ + b43_httab_write_few(dev, B43_HTTAB16(40, 0x8), 4, + 0x09, 0x0e, 0x13, 0x18); + + b43_phy_maskset(dev, B43_PHY_OFDM(0x24), 0x3f, 0xd); + b43_phy_maskset(dev, B43_PHY_OFDM(0x64), 0x3f, 0xd); + b43_phy_maskset(dev, B43_PHY_OFDM(0xa4), 0x3f, 0xd); + + b43_phy_set(dev, B43_PHY_EXTG(0x060), 0x1); + b43_phy_set(dev, B43_PHY_EXTG(0x064), 0x1); + b43_phy_set(dev, B43_PHY_EXTG(0x080), 0x1); + b43_phy_set(dev, B43_PHY_EXTG(0x084), 0x1); + + /* Copy some tables entries */ + tmp = b43_httab_read(dev, B43_HTTAB16(7, 0x144)); + b43_httab_write(dev, B43_HTTAB16(7, 0x14a), tmp); + tmp = b43_httab_read(dev, B43_HTTAB16(7, 0x154)); + b43_httab_write(dev, B43_HTTAB16(7, 0x15a), tmp); + tmp = b43_httab_read(dev, B43_HTTAB16(7, 0x164)); + b43_httab_write(dev, B43_HTTAB16(7, 0x16a), tmp); + + /* Reset CCA */ + b43_phy_force_clock(dev, true); + tmp = b43_phy_read(dev, B43_PHY_HT_BBCFG); + b43_phy_write(dev, B43_PHY_HT_BBCFG, tmp | B43_PHY_HT_BBCFG_RSTCCA); + b43_phy_write(dev, B43_PHY_HT_BBCFG, tmp & ~B43_PHY_HT_BBCFG_RSTCCA); + b43_phy_force_clock(dev, false); + + b43_mac_phy_clock_set(dev, true); + + b43_phy_ht_pa_override(dev, false); + b43_phy_ht_force_rf_sequence(dev, B43_PHY_HT_RF_SEQ_TRIG_RX2TX); + b43_phy_ht_force_rf_sequence(dev, B43_PHY_HT_RF_SEQ_TRIG_RST2RX); + b43_phy_ht_pa_override(dev, true); + + /* TODO: Should we restore it? Or store it in global PHY info? */ + b43_phy_ht_classifier(dev, 0, 0); + b43_phy_ht_read_clip_detection(dev, clip_state); + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + b43_phy_ht_bphy_init(dev); + + b43_httab_write_bulk(dev, B43_HTTAB32(0x1a, 0xc0), + B43_HTTAB_1A_C0_LATE_SIZE, b43_httab_0x1a_0xc0_late); + + saved_tx_pwr_ctl = phy_ht->tx_pwr_ctl; + b43_phy_ht_tx_power_fix(dev); + b43_phy_ht_tx_power_ctl(dev, false); + b43_phy_ht_tx_power_ctl_idle_tssi(dev); + b43_phy_ht_tx_power_ctl_setup(dev); + b43_phy_ht_tssi_setup(dev); + b43_phy_ht_tx_power_ctl(dev, saved_tx_pwr_ctl); + + return 0; +} + +static void b43_phy_ht_op_free(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_ht *phy_ht = phy->ht; + + kfree(phy_ht); + phy->ht = NULL; +} + +/* http://bcm-v4.sipsolutions.net/802.11/Radio/Switch%20Radio */ +static void b43_phy_ht_op_software_rfkill(struct b43_wldev *dev, + bool blocked) +{ + if (b43_read32(dev, B43_MMIO_MACCTL) & B43_MACCTL_ENABLED) + b43err(dev->wl, "MAC not suspended\n"); + + /* In the following PHY ops we copy wl's dummy behaviour. + * TODO: Find out if reads (currently hidden in masks/masksets) are + * needed and replace following ops with just writes or w&r. + * Note: B43_PHY_HT_RF_CTL1 register is tricky, wrong operation can + * cause delayed (!) machine lock up. */ + if (blocked) { + b43_phy_mask(dev, B43_PHY_HT_RF_CTL1, 0); + } else { + b43_phy_mask(dev, B43_PHY_HT_RF_CTL1, 0); + b43_phy_maskset(dev, B43_PHY_HT_RF_CTL1, 0, 0x1); + b43_phy_mask(dev, B43_PHY_HT_RF_CTL1, 0); + b43_phy_maskset(dev, B43_PHY_HT_RF_CTL1, 0, 0x2); + + if (dev->phy.radio_ver == 0x2059) + b43_radio_2059_init(dev); + else + B43_WARN_ON(1); + + b43_switch_channel(dev, dev->phy.channel); + } +} + +static void b43_phy_ht_op_switch_analog(struct b43_wldev *dev, bool on) +{ + if (on) { + b43_phy_write(dev, B43_PHY_HT_AFE_C1, 0x00cd); + b43_phy_write(dev, B43_PHY_HT_AFE_C1_OVER, 0x0000); + b43_phy_write(dev, B43_PHY_HT_AFE_C2, 0x00cd); + b43_phy_write(dev, B43_PHY_HT_AFE_C2_OVER, 0x0000); + b43_phy_write(dev, B43_PHY_HT_AFE_C3, 0x00cd); + b43_phy_write(dev, B43_PHY_HT_AFE_C3_OVER, 0x0000); + } else { + b43_phy_write(dev, B43_PHY_HT_AFE_C1_OVER, 0x07ff); + b43_phy_write(dev, B43_PHY_HT_AFE_C1, 0x00fd); + b43_phy_write(dev, B43_PHY_HT_AFE_C2_OVER, 0x07ff); + b43_phy_write(dev, B43_PHY_HT_AFE_C2, 0x00fd); + b43_phy_write(dev, B43_PHY_HT_AFE_C3_OVER, 0x07ff); + b43_phy_write(dev, B43_PHY_HT_AFE_C3, 0x00fd); + } +} + +static int b43_phy_ht_op_switch_channel(struct b43_wldev *dev, + unsigned int new_channel) +{ + struct ieee80211_channel *channel = dev->wl->hw->conf.chandef.chan; + enum nl80211_channel_type channel_type = + cfg80211_get_chandef_type(&dev->wl->hw->conf.chandef); + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + if ((new_channel < 1) || (new_channel > 14)) + return -EINVAL; + } else { + return -EINVAL; + } + + return b43_phy_ht_set_channel(dev, channel, channel_type); +} + +static unsigned int b43_phy_ht_op_get_default_chan(struct b43_wldev *dev) +{ + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + return 11; + return 36; +} + +/************************************************** + * R/W ops. + **************************************************/ + +static u16 b43_phy_ht_op_read(struct b43_wldev *dev, u16 reg) +{ + b43_write16(dev, B43_MMIO_PHY_CONTROL, reg); + return b43_read16(dev, B43_MMIO_PHY_DATA); +} + +static void b43_phy_ht_op_write(struct b43_wldev *dev, u16 reg, u16 value) +{ + b43_write16(dev, B43_MMIO_PHY_CONTROL, reg); + b43_write16(dev, B43_MMIO_PHY_DATA, value); +} + +static void b43_phy_ht_op_maskset(struct b43_wldev *dev, u16 reg, u16 mask, + u16 set) +{ + b43_write16(dev, B43_MMIO_PHY_CONTROL, reg); + b43_write16(dev, B43_MMIO_PHY_DATA, + (b43_read16(dev, B43_MMIO_PHY_DATA) & mask) | set); +} + +static u16 b43_phy_ht_op_radio_read(struct b43_wldev *dev, u16 reg) +{ + /* HT-PHY needs 0x200 for read access */ + reg |= 0x200; + + b43_write16(dev, B43_MMIO_RADIO24_CONTROL, reg); + return b43_read16(dev, B43_MMIO_RADIO24_DATA); +} + +static void b43_phy_ht_op_radio_write(struct b43_wldev *dev, u16 reg, + u16 value) +{ + b43_write16(dev, B43_MMIO_RADIO24_CONTROL, reg); + b43_write16(dev, B43_MMIO_RADIO24_DATA, value); +} + +static enum b43_txpwr_result +b43_phy_ht_op_recalc_txpower(struct b43_wldev *dev, bool ignore_tssi) +{ + return B43_TXPWR_RES_DONE; +} + +static void b43_phy_ht_op_adjust_txpower(struct b43_wldev *dev) +{ +} + +/************************************************** + * PHY ops struct. + **************************************************/ + +const struct b43_phy_operations b43_phyops_ht = { + .allocate = b43_phy_ht_op_allocate, + .free = b43_phy_ht_op_free, + .prepare_structs = b43_phy_ht_op_prepare_structs, + .init = b43_phy_ht_op_init, + .phy_read = b43_phy_ht_op_read, + .phy_write = b43_phy_ht_op_write, + .phy_maskset = b43_phy_ht_op_maskset, + .radio_read = b43_phy_ht_op_radio_read, + .radio_write = b43_phy_ht_op_radio_write, + .software_rfkill = b43_phy_ht_op_software_rfkill, + .switch_analog = b43_phy_ht_op_switch_analog, + .switch_channel = b43_phy_ht_op_switch_channel, + .get_default_chan = b43_phy_ht_op_get_default_chan, + .recalc_txpower = b43_phy_ht_op_recalc_txpower, + .adjust_txpower = b43_phy_ht_op_adjust_txpower, +}; diff --git a/drivers/net/wireless/b43/phy_ht.h b/drivers/net/wireless/b43/phy_ht.h new file mode 100644 index 0000000..6cae370 --- /dev/null +++ b/drivers/net/wireless/b43/phy_ht.h @@ -0,0 +1,136 @@ +#ifndef B43_PHY_HT_H_ +#define B43_PHY_HT_H_ + +#include "phy_common.h" + + +#define B43_PHY_HT_BBCFG 0x001 /* BB config */ +#define B43_PHY_HT_BBCFG_RSTCCA 0x4000 /* Reset CCA */ +#define B43_PHY_HT_BBCFG_RSTRX 0x8000 /* Reset RX */ +#define B43_PHY_HT_BANDCTL 0x009 /* Band control */ +#define B43_PHY_HT_BANDCTL_5GHZ 0x0001 /* Use the 5GHz band */ +#define B43_PHY_HT_TABLE_ADDR 0x072 /* Table address */ +#define B43_PHY_HT_TABLE_DATALO 0x073 /* Table data low */ +#define B43_PHY_HT_TABLE_DATAHI 0x074 /* Table data high */ +#define B43_PHY_HT_CLASS_CTL 0x0B0 /* Classifier control */ +#define B43_PHY_HT_CLASS_CTL_CCK_EN 0x0001 /* CCK enable */ +#define B43_PHY_HT_CLASS_CTL_OFDM_EN 0x0002 /* OFDM enable */ +#define B43_PHY_HT_CLASS_CTL_WAITED_EN 0x0004 /* Waited enable */ +#define B43_PHY_HT_IQLOCAL_CMDGCTL 0x0C2 /* I/Q LO cal command G control */ +#define B43_PHY_HT_SAMP_CMD 0x0C3 /* Sample command */ +#define B43_PHY_HT_SAMP_CMD_STOP 0x0002 /* Stop */ +#define B43_PHY_HT_SAMP_LOOP_CNT 0x0C4 /* Sample loop count */ +#define B43_PHY_HT_SAMP_WAIT_CNT 0x0C5 /* Sample wait count */ +#define B43_PHY_HT_SAMP_DEP_CNT 0x0C6 /* Sample depth count */ +#define B43_PHY_HT_SAMP_STAT 0x0C7 /* Sample status */ +#define B43_PHY_HT_EST_PWR_C1 0x118 +#define B43_PHY_HT_EST_PWR_C2 0x119 +#define B43_PHY_HT_EST_PWR_C3 0x11A +#define B43_PHY_HT_TSSIMODE 0x122 /* TSSI mode */ +#define B43_PHY_HT_TSSIMODE_EN 0x0001 /* TSSI enable */ +#define B43_PHY_HT_TSSIMODE_PDEN 0x0002 /* Power det enable */ +#define B43_PHY_HT_BW1 0x1CE +#define B43_PHY_HT_BW2 0x1CF +#define B43_PHY_HT_BW3 0x1D0 +#define B43_PHY_HT_BW4 0x1D1 +#define B43_PHY_HT_BW5 0x1D2 +#define B43_PHY_HT_BW6 0x1D3 +#define B43_PHY_HT_TXPCTL_CMD_C1 0x1E7 /* TX power control command */ +#define B43_PHY_HT_TXPCTL_CMD_C1_INIT 0x007F /* Init */ +#define B43_PHY_HT_TXPCTL_CMD_C1_COEFF 0x2000 /* Power control coefficients */ +#define B43_PHY_HT_TXPCTL_CMD_C1_HWPCTLEN 0x4000 /* Hardware TX power control enable */ +#define B43_PHY_HT_TXPCTL_CMD_C1_PCTLEN 0x8000 /* TX power control enable */ +#define B43_PHY_HT_TXPCTL_N 0x1E8 /* TX power control N num */ +#define B43_PHY_HT_TXPCTL_N_TSSID 0x00FF /* N TSSI delay */ +#define B43_PHY_HT_TXPCTL_N_TSSID_SHIFT 0 +#define B43_PHY_HT_TXPCTL_N_NPTIL2 0x0700 /* N PT integer log2 */ +#define B43_PHY_HT_TXPCTL_N_NPTIL2_SHIFT 8 +#define B43_PHY_HT_TXPCTL_IDLE_TSSI 0x1E9 /* TX power control idle TSSI */ +#define B43_PHY_HT_TXPCTL_IDLE_TSSI_C1 0x003F +#define B43_PHY_HT_TXPCTL_IDLE_TSSI_C1_SHIFT 0 +#define B43_PHY_HT_TXPCTL_IDLE_TSSI_C2 0x3F00 +#define B43_PHY_HT_TXPCTL_IDLE_TSSI_C2_SHIFT 8 +#define B43_PHY_HT_TXPCTL_IDLE_TSSI_BINF 0x8000 /* Raw TSSI offset bin format */ +#define B43_PHY_HT_TXPCTL_TARG_PWR 0x1EA /* TX power control target power */ +#define B43_PHY_HT_TXPCTL_TARG_PWR_C1 0x00FF /* Power 0 */ +#define B43_PHY_HT_TXPCTL_TARG_PWR_C1_SHIFT 0 +#define B43_PHY_HT_TXPCTL_TARG_PWR_C2 0xFF00 /* Power 1 */ +#define B43_PHY_HT_TXPCTL_TARG_PWR_C2_SHIFT 8 +#define B43_PHY_HT_TX_PCTL_STATUS_C1 0x1ED +#define B43_PHY_HT_TX_PCTL_STATUS_C2 0x1EE +#define B43_PHY_HT_TXPCTL_CMD_C2 0x222 +#define B43_PHY_HT_TXPCTL_CMD_C2_INIT 0x007F +#define B43_PHY_HT_RSSI_C1 0x219 +#define B43_PHY_HT_RSSI_C2 0x21A +#define B43_PHY_HT_RSSI_C3 0x21B + +#define B43_PHY_HT_C1_CLIP1THRES B43_PHY_OFDM(0x00E) +#define B43_PHY_HT_C2_CLIP1THRES B43_PHY_OFDM(0x04E) +#define B43_PHY_HT_C3_CLIP1THRES B43_PHY_OFDM(0x08E) + +#define B43_PHY_HT_RF_SEQ_MODE B43_PHY_EXTG(0x000) +#define B43_PHY_HT_RF_SEQ_MODE_CA_OVER 0x0001 /* Core active override */ +#define B43_PHY_HT_RF_SEQ_MODE_TR_OVER 0x0002 /* Trigger override */ +#define B43_PHY_HT_RF_SEQ_TRIG B43_PHY_EXTG(0x003) +#define B43_PHY_HT_RF_SEQ_TRIG_RX2TX 0x0001 /* RX2TX */ +#define B43_PHY_HT_RF_SEQ_TRIG_TX2RX 0x0002 /* TX2RX */ +#define B43_PHY_HT_RF_SEQ_TRIG_UPGH 0x0004 /* Update gain H */ +#define B43_PHY_HT_RF_SEQ_TRIG_UPGL 0x0008 /* Update gain L */ +#define B43_PHY_HT_RF_SEQ_TRIG_UPGU 0x0010 /* Update gain U */ +#define B43_PHY_HT_RF_SEQ_TRIG_RST2RX 0x0020 /* Reset to RX */ +#define B43_PHY_HT_RF_SEQ_STATUS B43_PHY_EXTG(0x004) +/* Values for the status are the same as for the trigger */ + +#define B43_PHY_HT_RF_CTL1 B43_PHY_EXTG(0x010) + +#define B43_PHY_HT_RF_CTL_INT_C1 B43_PHY_EXTG(0x04c) +#define B43_PHY_HT_RF_CTL_INT_C2 B43_PHY_EXTG(0x06c) +#define B43_PHY_HT_RF_CTL_INT_C3 B43_PHY_EXTG(0x08c) + +#define B43_PHY_HT_AFE_C1_OVER B43_PHY_EXTG(0x110) +#define B43_PHY_HT_AFE_C1 B43_PHY_EXTG(0x111) +#define B43_PHY_HT_AFE_C2_OVER B43_PHY_EXTG(0x114) +#define B43_PHY_HT_AFE_C2 B43_PHY_EXTG(0x115) +#define B43_PHY_HT_AFE_C3_OVER B43_PHY_EXTG(0x118) +#define B43_PHY_HT_AFE_C3 B43_PHY_EXTG(0x119) + +#define B43_PHY_HT_TXPCTL_CMD_C3 B43_PHY_EXTG(0x164) +#define B43_PHY_HT_TXPCTL_CMD_C3_INIT 0x007F +#define B43_PHY_HT_TXPCTL_IDLE_TSSI2 B43_PHY_EXTG(0x165) /* TX power control idle TSSI */ +#define B43_PHY_HT_TXPCTL_IDLE_TSSI2_C3 0x003F +#define B43_PHY_HT_TXPCTL_IDLE_TSSI2_C3_SHIFT 0 +#define B43_PHY_HT_TXPCTL_TARG_PWR2 B43_PHY_EXTG(0x166) /* TX power control target power */ +#define B43_PHY_HT_TXPCTL_TARG_PWR2_C3 0x00FF +#define B43_PHY_HT_TXPCTL_TARG_PWR2_C3_SHIFT 0 +#define B43_PHY_HT_TX_PCTL_STATUS_C3 B43_PHY_EXTG(0x169) + +#define B43_PHY_HT_TEST B43_PHY_N_BMODE(0x00A) + + +/* Values for PHY registers used on channel switching */ +struct b43_phy_ht_channeltab_e_phy { + u16 bw1; + u16 bw2; + u16 bw3; + u16 bw4; + u16 bw5; + u16 bw6; +}; + + +struct b43_phy_ht { + u16 rf_ctl_int_save[3]; + + bool tx_pwr_ctl; + u8 tx_pwr_idx[3]; + + s32 bb_mult_save[3]; + + u8 idle_tssi[3]; +}; + + +struct b43_phy_operations; +extern const struct b43_phy_operations b43_phyops_ht; + +#endif /* B43_PHY_HT_H_ */ diff --git a/drivers/net/wireless/b43/phy_lcn.c b/drivers/net/wireless/b43/phy_lcn.c new file mode 100644 index 0000000..0bafa3b --- /dev/null +++ b/drivers/net/wireless/b43/phy_lcn.c @@ -0,0 +1,902 @@ +/* + + Broadcom B43 wireless driver + IEEE 802.11n LCN-PHY support + + Copyright (c) 2011 RafaÅ‚ MiÅ‚ecki + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + + This file incorporates work covered by the following copyright and + permission notice: + + Copyright (c) 2010 Broadcom Corporation + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. +*/ + +#include + +#include "b43.h" +#include "phy_lcn.h" +#include "tables_phy_lcn.h" +#include "main.h" + +struct lcn_tx_gains { + u16 gm_gain; + u16 pga_gain; + u16 pad_gain; + u16 dac_gain; +}; + +struct lcn_tx_iir_filter { + u8 type; + u16 values[16]; +}; + +enum lcn_sense_type { + B43_SENSE_TEMP, + B43_SENSE_VBAT, +}; + +/* In theory it's PHY common function, move if needed */ +/* brcms_b_switch_macfreq */ +static void b43_phy_switch_macfreq(struct b43_wldev *dev, u8 spurmode) +{ + if (dev->dev->chip_id == 43224 || dev->dev->chip_id == 43225) { + switch (spurmode) { + case 2: /* 126 Mhz */ + b43_write16(dev, B43_MMIO_TSF_CLK_FRAC_LOW, 0x2082); + b43_write16(dev, B43_MMIO_TSF_CLK_FRAC_HIGH, 0x8); + break; + case 1: /* 123 Mhz */ + b43_write16(dev, B43_MMIO_TSF_CLK_FRAC_LOW, 0x5341); + b43_write16(dev, B43_MMIO_TSF_CLK_FRAC_HIGH, 0x8); + break; + default: /* 120 Mhz */ + b43_write16(dev, B43_MMIO_TSF_CLK_FRAC_LOW, 0x8889); + b43_write16(dev, B43_MMIO_TSF_CLK_FRAC_HIGH, 0x8); + break; + } + } else if (dev->phy.type == B43_PHYTYPE_LCN) { + switch (spurmode) { + case 1: /* 82 Mhz */ + b43_write16(dev, B43_MMIO_TSF_CLK_FRAC_LOW, 0x7CE0); + b43_write16(dev, B43_MMIO_TSF_CLK_FRAC_HIGH, 0xC); + break; + default: /* 80 Mhz */ + b43_write16(dev, B43_MMIO_TSF_CLK_FRAC_LOW, 0xCCCD); + b43_write16(dev, B43_MMIO_TSF_CLK_FRAC_HIGH, 0xC); + break; + } + } +} + +/************************************************** + * Radio 2064. + **************************************************/ + +/* wlc_lcnphy_radio_2064_channel_tune_4313 */ +static void b43_radio_2064_channel_setup(struct b43_wldev *dev) +{ + u16 save[2]; + + b43_radio_set(dev, 0x09d, 0x4); + b43_radio_write(dev, 0x09e, 0xf); + + /* Channel specific values in theory, in practice always the same */ + b43_radio_write(dev, 0x02a, 0xb); + b43_radio_maskset(dev, 0x030, ~0x3, 0xa); + b43_radio_maskset(dev, 0x091, ~0x3, 0); + b43_radio_maskset(dev, 0x038, ~0xf, 0x7); + b43_radio_maskset(dev, 0x030, ~0xc, 0x8); + b43_radio_maskset(dev, 0x05e, ~0xf, 0x8); + b43_radio_maskset(dev, 0x05e, ~0xf0, 0x80); + b43_radio_write(dev, 0x06c, 0x80); + + save[0] = b43_radio_read(dev, 0x044); + save[1] = b43_radio_read(dev, 0x12b); + + b43_radio_set(dev, 0x044, 0x7); + b43_radio_set(dev, 0x12b, 0xe); + + /* TODO */ + + b43_radio_write(dev, 0x040, 0xfb); + + b43_radio_write(dev, 0x041, 0x9a); + b43_radio_write(dev, 0x042, 0xa3); + b43_radio_write(dev, 0x043, 0x0c); + + /* TODO */ + + b43_radio_set(dev, 0x044, 0x0c); + udelay(1); + + b43_radio_write(dev, 0x044, save[0]); + b43_radio_write(dev, 0x12b, save[1]); + + if (dev->phy.rev == 1) { + /* brcmsmac uses outdated 0x3 for 0x038 */ + b43_radio_write(dev, 0x038, 0x0); + b43_radio_write(dev, 0x091, 0x7); + } +} + +/* wlc_radio_2064_init */ +static void b43_radio_2064_init(struct b43_wldev *dev) +{ + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + b43_radio_write(dev, 0x09c, 0x0020); + b43_radio_write(dev, 0x105, 0x0008); + } else { + /* TODO */ + } + b43_radio_write(dev, 0x032, 0x0062); + b43_radio_write(dev, 0x033, 0x0019); + b43_radio_write(dev, 0x090, 0x0010); + b43_radio_write(dev, 0x010, 0x0000); + if (dev->phy.rev == 1) { + b43_radio_write(dev, 0x060, 0x007f); + b43_radio_write(dev, 0x061, 0x0072); + b43_radio_write(dev, 0x062, 0x007f); + } + b43_radio_write(dev, 0x01d, 0x0002); + b43_radio_write(dev, 0x01e, 0x0006); + + b43_phy_write(dev, 0x4ea, 0x4688); + b43_phy_maskset(dev, 0x4eb, ~0x7, 0x2); + b43_phy_mask(dev, 0x4eb, ~0x01c0); + b43_phy_maskset(dev, 0x46a, 0xff00, 0x19); + + b43_lcntab_write(dev, B43_LCNTAB16(0x00, 0x55), 0); + + b43_radio_mask(dev, 0x05b, (u16) ~0xff02); + b43_radio_set(dev, 0x004, 0x40); + b43_radio_set(dev, 0x120, 0x10); + b43_radio_set(dev, 0x078, 0x80); + b43_radio_set(dev, 0x129, 0x2); + b43_radio_set(dev, 0x057, 0x1); + b43_radio_set(dev, 0x05b, 0x2); + + /* TODO: wait for some bit to be set */ + b43_radio_read(dev, 0x05c); + + b43_radio_mask(dev, 0x05b, (u16) ~0xff02); + b43_radio_mask(dev, 0x057, (u16) ~0xff01); + + b43_phy_write(dev, 0x933, 0x2d6b); + b43_phy_write(dev, 0x934, 0x2d6b); + b43_phy_write(dev, 0x935, 0x2d6b); + b43_phy_write(dev, 0x936, 0x2d6b); + b43_phy_write(dev, 0x937, 0x016b); + + b43_radio_mask(dev, 0x057, (u16) ~0xff02); + b43_radio_write(dev, 0x0c2, 0x006f); +} + +/************************************************** + * Various PHY ops + **************************************************/ + +/* wlc_lcnphy_toggle_afe_pwdn */ +static void b43_phy_lcn_afe_set_unset(struct b43_wldev *dev) +{ + u16 afe_ctl2 = b43_phy_read(dev, B43_PHY_LCN_AFE_CTL2); + u16 afe_ctl1 = b43_phy_read(dev, B43_PHY_LCN_AFE_CTL1); + + b43_phy_write(dev, B43_PHY_LCN_AFE_CTL2, afe_ctl2 | 0x1); + b43_phy_write(dev, B43_PHY_LCN_AFE_CTL1, afe_ctl1 | 0x1); + + b43_phy_write(dev, B43_PHY_LCN_AFE_CTL2, afe_ctl2 & ~0x1); + b43_phy_write(dev, B43_PHY_LCN_AFE_CTL1, afe_ctl1 & ~0x1); + + b43_phy_write(dev, B43_PHY_LCN_AFE_CTL2, afe_ctl2); + b43_phy_write(dev, B43_PHY_LCN_AFE_CTL1, afe_ctl1); +} + +/* wlc_lcnphy_get_pa_gain */ +static u16 b43_phy_lcn_get_pa_gain(struct b43_wldev *dev) +{ + return (b43_phy_read(dev, 0x4fb) & 0x7f00) >> 8; +} + +/* wlc_lcnphy_set_dac_gain */ +static void b43_phy_lcn_set_dac_gain(struct b43_wldev *dev, u16 dac_gain) +{ + u16 dac_ctrl; + + dac_ctrl = b43_phy_read(dev, 0x439); + dac_ctrl = dac_ctrl & 0xc7f; + dac_ctrl = dac_ctrl | (dac_gain << 7); + b43_phy_maskset(dev, 0x439, ~0xfff, dac_ctrl); +} + +/* wlc_lcnphy_set_bbmult */ +static void b43_phy_lcn_set_bbmult(struct b43_wldev *dev, u8 m0) +{ + b43_lcntab_write(dev, B43_LCNTAB16(0x00, 0x57), m0 << 8); +} + +/* wlc_lcnphy_clear_tx_power_offsets */ +static void b43_phy_lcn_clear_tx_power_offsets(struct b43_wldev *dev) +{ + u8 i; + + if (1) { /* FIXME */ + b43_phy_write(dev, B43_PHY_LCN_TABLE_ADDR, (0x7 << 10) | 0x340); + for (i = 0; i < 30; i++) { + b43_phy_write(dev, B43_PHY_LCN_TABLE_DATAHI, 0); + b43_phy_write(dev, B43_PHY_LCN_TABLE_DATALO, 0); + } + } + + b43_phy_write(dev, B43_PHY_LCN_TABLE_ADDR, (0x7 << 10) | 0x80); + for (i = 0; i < 64; i++) { + b43_phy_write(dev, B43_PHY_LCN_TABLE_DATAHI, 0); + b43_phy_write(dev, B43_PHY_LCN_TABLE_DATALO, 0); + } +} + +/* wlc_lcnphy_rev0_baseband_init */ +static void b43_phy_lcn_rev0_baseband_init(struct b43_wldev *dev) +{ + b43_radio_write(dev, 0x11c, 0); + + b43_phy_write(dev, 0x43b, 0); + b43_phy_write(dev, 0x43c, 0); + b43_phy_write(dev, 0x44c, 0); + b43_phy_write(dev, 0x4e6, 0); + b43_phy_write(dev, 0x4f9, 0); + b43_phy_write(dev, 0x4b0, 0); + b43_phy_write(dev, 0x938, 0); + b43_phy_write(dev, 0x4b0, 0); + b43_phy_write(dev, 0x44e, 0); + + b43_phy_set(dev, 0x567, 0x03); + + b43_phy_set(dev, 0x44a, 0x44); + b43_phy_write(dev, 0x44a, 0x80); + + if (!(dev->dev->bus_sprom->boardflags_lo & B43_BFL_FEM)) + ; /* TODO */ + b43_phy_maskset(dev, 0x634, ~0xff, 0xc); + if (dev->dev->bus_sprom->boardflags_lo & B43_BFL_FEM) { + b43_phy_maskset(dev, 0x634, ~0xff, 0xa); + b43_phy_write(dev, 0x910, 0x1); + } + + b43_phy_write(dev, 0x910, 0x1); + + b43_phy_maskset(dev, 0x448, ~0x300, 0x100); + b43_phy_maskset(dev, 0x608, ~0xff, 0x17); + b43_phy_maskset(dev, 0x604, ~0x7ff, 0x3ea); +} + +/* wlc_lcnphy_bu_tweaks */ +static void b43_phy_lcn_bu_tweaks(struct b43_wldev *dev) +{ + b43_phy_set(dev, 0x805, 0x1); + + b43_phy_maskset(dev, 0x42f, ~0x7, 0x3); + b43_phy_maskset(dev, 0x030, ~0x7, 0x3); + + b43_phy_write(dev, 0x414, 0x1e10); + b43_phy_write(dev, 0x415, 0x0640); + + b43_phy_maskset(dev, 0x4df, (u16) ~0xff00, 0xf700); + + b43_phy_set(dev, 0x44a, 0x44); + b43_phy_write(dev, 0x44a, 0x80); + + b43_phy_maskset(dev, 0x434, ~0xff, 0xfd); + b43_phy_maskset(dev, 0x420, ~0xff, 0x10); + + if (dev->dev->bus_sprom->board_rev >= 0x1204) + b43_radio_set(dev, 0x09b, 0xf0); + + b43_phy_write(dev, 0x7d6, 0x0902); + + b43_phy_maskset(dev, 0x429, ~0xf, 0x9); + b43_phy_maskset(dev, 0x429, ~(0x3f << 4), 0xe << 4); + + if (dev->phy.rev == 1) { + b43_phy_maskset(dev, 0x423, ~0xff, 0x46); + b43_phy_maskset(dev, 0x411, ~0xff, 1); + b43_phy_set(dev, 0x434, 0xff); /* FIXME: update to wl */ + + /* TODO: wl operates on PHY 0x416, brcmsmac is outdated here */ + + b43_phy_maskset(dev, 0x656, ~0xf, 2); + b43_phy_set(dev, 0x44d, 4); + + b43_radio_set(dev, 0x0f7, 0x4); + b43_radio_mask(dev, 0x0f1, ~0x3); + b43_radio_maskset(dev, 0x0f2, ~0xf8, 0x90); + b43_radio_maskset(dev, 0x0f3, ~0x3, 0x2); + b43_radio_maskset(dev, 0x0f3, ~0xf0, 0xa0); + + b43_radio_set(dev, 0x11f, 0x2); + + b43_phy_lcn_clear_tx_power_offsets(dev); + + /* TODO: something more? */ + } +} + +/* wlc_lcnphy_vbat_temp_sense_setup */ +static void b43_phy_lcn_sense_setup(struct b43_wldev *dev, + enum lcn_sense_type sense_type) +{ + u8 auxpga_vmidcourse, auxpga_vmidfine, auxpga_gain; + u16 auxpga_vmid; + u8 tx_pwr_idx; + u8 i; + + u16 save_radio_regs[6][2] = { + { 0x007, 0 }, { 0x0ff, 0 }, { 0x11f, 0 }, { 0x005, 0 }, + { 0x025, 0 }, { 0x112, 0 }, + }; + u16 save_phy_regs[14][2] = { + { 0x503, 0 }, { 0x4a4, 0 }, { 0x4d0, 0 }, { 0x4d9, 0 }, + { 0x4da, 0 }, { 0x4a6, 0 }, { 0x938, 0 }, { 0x939, 0 }, + { 0x4d8, 0 }, { 0x4d0, 0 }, { 0x4d7, 0 }, { 0x4a5, 0 }, + { 0x40d, 0 }, { 0x4a2, 0 }, + }; + u16 save_radio_4a4; + + msleep(1); + + /* Save */ + for (i = 0; i < 6; i++) + save_radio_regs[i][1] = b43_radio_read(dev, + save_radio_regs[i][0]); + for (i = 0; i < 14; i++) + save_phy_regs[i][1] = b43_phy_read(dev, save_phy_regs[i][0]); + b43_mac_suspend(dev); + save_radio_4a4 = b43_radio_read(dev, 0x4a4); + /* wlc_lcnphy_set_tx_pwr_ctrl(pi, LCNPHY_TX_PWR_CTRL_OFF); */ + tx_pwr_idx = dev->phy.lcn->tx_pwr_curr_idx; + + /* Setup */ + /* TODO: wlc_lcnphy_set_tx_pwr_by_index(pi, 127); */ + b43_radio_set(dev, 0x007, 0x1); + b43_radio_set(dev, 0x0ff, 0x10); + b43_radio_set(dev, 0x11f, 0x4); + + b43_phy_mask(dev, 0x503, ~0x1); + b43_phy_mask(dev, 0x503, ~0x4); + b43_phy_mask(dev, 0x4a4, ~0x4000); + b43_phy_mask(dev, 0x4a4, (u16) ~0x8000); + b43_phy_mask(dev, 0x4d0, ~0x20); + b43_phy_set(dev, 0x4a5, 0xff); + b43_phy_maskset(dev, 0x4a5, ~0x7000, 0x5000); + b43_phy_mask(dev, 0x4a5, ~0x700); + b43_phy_maskset(dev, 0x40d, ~0xff, 64); + b43_phy_maskset(dev, 0x40d, ~0x700, 0x600); + b43_phy_maskset(dev, 0x4a2, ~0xff, 64); + b43_phy_maskset(dev, 0x4a2, ~0x700, 0x600); + b43_phy_maskset(dev, 0x4d9, ~0x70, 0x20); + b43_phy_maskset(dev, 0x4d9, ~0x700, 0x300); + b43_phy_maskset(dev, 0x4d9, ~0x7000, 0x1000); + b43_phy_mask(dev, 0x4da, ~0x1000); + b43_phy_set(dev, 0x4da, 0x2000); + b43_phy_set(dev, 0x4a6, 0x8000); + + b43_radio_write(dev, 0x025, 0xc); + b43_radio_set(dev, 0x005, 0x8); + b43_phy_set(dev, 0x938, 0x4); + b43_phy_set(dev, 0x939, 0x4); + b43_phy_set(dev, 0x4a4, 0x1000); + + /* FIXME: don't hardcode */ + b43_lcntab_write(dev, B43_LCNTAB16(0x8, 0x6), 0x640); + + switch (sense_type) { + case B43_SENSE_TEMP: + b43_phy_set(dev, 0x4d7, 0x8); + b43_phy_maskset(dev, 0x4d7, ~0x7000, 0x1000); + auxpga_vmidcourse = 8; + auxpga_vmidfine = 0x4; + auxpga_gain = 2; + b43_radio_set(dev, 0x082, 0x20); + break; + case B43_SENSE_VBAT: + b43_phy_set(dev, 0x4d7, 0x8); + b43_phy_maskset(dev, 0x4d7, ~0x7000, 0x3000); + auxpga_vmidcourse = 7; + auxpga_vmidfine = 0xa; + auxpga_gain = 2; + break; + } + auxpga_vmid = (0x200 | (auxpga_vmidcourse << 4) | auxpga_vmidfine); + + b43_phy_set(dev, 0x4d8, 0x1); + b43_phy_maskset(dev, 0x4d8, ~(0x3ff << 2), auxpga_vmid << 2); + b43_phy_set(dev, 0x4d8, 0x2); + b43_phy_maskset(dev, 0x4d8, ~(0x7 << 12), auxpga_gain << 12); + b43_phy_set(dev, 0x4d0, 0x20); + b43_radio_write(dev, 0x112, 0x6); + + b43_dummy_transmission(dev, true, false); + /* Wait if not done */ + if (!(b43_phy_read(dev, 0x476) & 0x8000)) + udelay(10); + + /* Restore */ + for (i = 0; i < 6; i++) + b43_radio_write(dev, save_radio_regs[i][0], + save_radio_regs[i][1]); + for (i = 0; i < 14; i++) + b43_phy_write(dev, save_phy_regs[i][0], save_phy_regs[i][1]); + /* TODO: wlc_lcnphy_set_tx_pwr_by_index(tx_pwr_idx) */ + b43_radio_write(dev, 0x4a4, save_radio_4a4); + + b43_mac_enable(dev); + + msleep(1); +} + +static bool b43_phy_lcn_load_tx_iir_cck_filter(struct b43_wldev *dev, + u8 filter_type) +{ + int i, j; + u16 phy_regs[] = { 0x910, 0x91e, 0x91f, 0x924, 0x925, 0x926, 0x920, + 0x921, 0x927, 0x928, 0x929, 0x922, 0x923, 0x930, + 0x931, 0x932 }; + /* Table is from brcmsmac, values for type 25 were outdated, probably + * others need updating too */ + struct lcn_tx_iir_filter tx_iir_filters_cck[] = { + { 0, { 1, 415, 1874, 64, 128, 64, 792, 1656, 64, 128, 64, 778, + 1582, 64, 128, 64 } }, + { 1, { 1, 402, 1847, 259, 59, 259, 671, 1794, 68, 54, 68, 608, + 1863, 93, 167, 93 } }, + { 2, { 1, 415, 1874, 64, 128, 64, 792, 1656, 192, 384, 192, + 778, 1582, 64, 128, 64 } }, + { 3, { 1, 302, 1841, 129, 258, 129, 658, 1720, 205, 410, 205, + 754, 1760, 170, 340, 170 } }, + { 20, { 1, 360, 1884, 242, 1734, 242, 752, 1720, 205, 1845, 205, + 767, 1760, 256, 185, 256 } }, + { 21, { 1, 360, 1884, 149, 1874, 149, 752, 1720, 205, 1883, 205, + 767, 1760, 256, 273, 256 } }, + { 22, { 1, 360, 1884, 98, 1948, 98, 752, 1720, 205, 1924, 205, + 767, 1760, 256, 352, 256 } }, + { 23, { 1, 350, 1884, 116, 1966, 116, 752, 1720, 205, 2008, 205, + 767, 1760, 128, 233, 128 } }, + { 24, { 1, 325, 1884, 32, 40, 32, 756, 1720, 256, 471, 256, 766, + 1760, 256, 1881, 256 } }, + { 25, { 1, 299, 1884, 51, 64, 51, 736, 1720, 256, 471, 256, 765, + 1760, 262, 1878, 262 } }, + /* brcmsmac version { 25, { 1, 299, 1884, 51, 64, 51, 736, 1720, + * 256, 471, 256, 765, 1760, 256, 1881, 256 } }, */ + { 26, { 1, 277, 1943, 39, 117, 88, 637, 1838, 64, 192, 144, 614, + 1864, 128, 384, 288 } }, + { 27, { 1, 245, 1943, 49, 147, 110, 626, 1838, 256, 768, 576, + 613, 1864, 128, 384, 288 } }, + { 30, { 1, 302, 1841, 61, 122, 61, 658, 1720, 205, 410, 205, + 754, 1760, 170, 340, 170 } }, + }; + + for (i = 0; i < ARRAY_SIZE(tx_iir_filters_cck); i++) { + if (tx_iir_filters_cck[i].type == filter_type) { + for (j = 0; j < 16; j++) + b43_phy_write(dev, phy_regs[j], + tx_iir_filters_cck[i].values[j]); + return true; + } + } + + return false; +} + +static bool b43_phy_lcn_load_tx_iir_ofdm_filter(struct b43_wldev *dev, + u8 filter_type) +{ + int i, j; + u16 phy_regs[] = { 0x90f, 0x900, 0x901, 0x906, 0x907, 0x908, 0x902, + 0x903, 0x909, 0x90a, 0x90b, 0x904, 0x905, 0x90c, + 0x90d, 0x90e }; + struct lcn_tx_iir_filter tx_iir_filters_ofdm[] = { + { 0, { 0, 0xa2, 0x0, 0x100, 0x100, 0x0, 0x0, 0x0, 0x100, 0x0, + 0x0, 0x278, 0xfea0, 0x80, 0x100, 0x80 } }, + { 1, { 0, 374, 0xFF79, 16, 32, 16, 799, 0xFE74, 50, 32, 50, 750, + 0xFE2B, 212, 0xFFCE, 212 } }, + { 2, { 0, 375, 0xFF16, 37, 76, 37, 799, 0xFE74, 32, 20, 32, 748, + 0xFEF2, 128, 0xFFE2, 128 } }, + }; + + for (i = 0; i < ARRAY_SIZE(tx_iir_filters_ofdm); i++) { + if (tx_iir_filters_ofdm[i].type == filter_type) { + for (j = 0; j < 16; j++) + b43_phy_write(dev, phy_regs[j], + tx_iir_filters_ofdm[i].values[j]); + return true; + } + } + + return false; +} + +/* wlc_lcnphy_set_tx_gain_override */ +static void b43_phy_lcn_set_tx_gain_override(struct b43_wldev *dev, bool enable) +{ + b43_phy_maskset(dev, 0x4b0, ~(0x1 << 7), enable << 7); + b43_phy_maskset(dev, 0x4b0, ~(0x1 << 14), enable << 14); + b43_phy_maskset(dev, 0x43b, ~(0x1 << 6), enable << 6); +} + +/* wlc_lcnphy_set_tx_gain */ +static void b43_phy_lcn_set_tx_gain(struct b43_wldev *dev, + struct lcn_tx_gains *target_gains) +{ + u16 pa_gain = b43_phy_lcn_get_pa_gain(dev); + + b43_phy_write(dev, 0x4b5, + (target_gains->gm_gain | (target_gains->pga_gain << 8))); + b43_phy_maskset(dev, 0x4fb, ~0x7fff, + (target_gains->pad_gain | (pa_gain << 8))); + b43_phy_write(dev, 0x4fc, + (target_gains->gm_gain | (target_gains->pga_gain << 8))); + b43_phy_maskset(dev, 0x4fd, ~0x7fff, + (target_gains->pad_gain | (pa_gain << 8))); + + b43_phy_lcn_set_dac_gain(dev, target_gains->dac_gain); + b43_phy_lcn_set_tx_gain_override(dev, true); +} + +/* wlc_lcnphy_tx_pwr_ctrl_init */ +static void b43_phy_lcn_tx_pwr_ctl_init(struct b43_wldev *dev) +{ + struct lcn_tx_gains tx_gains; + u8 bbmult; + + b43_mac_suspend(dev); + + if (!dev->phy.lcn->hw_pwr_ctl_capable) { + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + tx_gains.gm_gain = 4; + tx_gains.pga_gain = 12; + tx_gains.pad_gain = 12; + tx_gains.dac_gain = 0; + bbmult = 150; + } else { + tx_gains.gm_gain = 7; + tx_gains.pga_gain = 15; + tx_gains.pad_gain = 14; + tx_gains.dac_gain = 0; + bbmult = 150; + } + b43_phy_lcn_set_tx_gain(dev, &tx_gains); + b43_phy_lcn_set_bbmult(dev, bbmult); + b43_phy_lcn_sense_setup(dev, B43_SENSE_TEMP); + } else { + b43err(dev->wl, "TX power control not supported for this HW\n"); + } + + b43_mac_enable(dev); +} + +/* wlc_lcnphy_txrx_spur_avoidance_mode */ +static void b43_phy_lcn_txrx_spur_avoidance_mode(struct b43_wldev *dev, + bool enable) +{ + if (enable) { + b43_phy_write(dev, 0x942, 0x7); + b43_phy_write(dev, 0x93b, ((1 << 13) + 23)); + b43_phy_write(dev, 0x93c, ((1 << 13) + 1989)); + + b43_phy_write(dev, 0x44a, 0x084); + b43_phy_write(dev, 0x44a, 0x080); + b43_phy_write(dev, 0x6d3, 0x2222); + b43_phy_write(dev, 0x6d3, 0x2220); + } else { + b43_phy_write(dev, 0x942, 0x0); + b43_phy_write(dev, 0x93b, ((0 << 13) + 23)); + b43_phy_write(dev, 0x93c, ((0 << 13) + 1989)); + } + b43_phy_switch_macfreq(dev, enable); +} + +/************************************************** + * Channel switching ops. + **************************************************/ + +/* wlc_lcnphy_set_chanspec_tweaks */ +static void b43_phy_lcn_set_channel_tweaks(struct b43_wldev *dev, int channel) +{ + struct bcma_drv_cc *cc = &dev->dev->bdev->bus->drv_cc; + + b43_phy_maskset(dev, 0x448, ~0x300, (channel == 14) ? 0x200 : 0x100); + + if (channel == 1 || channel == 2 || channel == 3 || channel == 4 || + channel == 9 || channel == 10 || channel == 11 || channel == 12) { + bcma_chipco_pll_write(cc, 0x2, 0x03000c04); + bcma_chipco_pll_maskset(cc, 0x3, 0x00ffffff, 0x0); + bcma_chipco_pll_write(cc, 0x4, 0x200005c0); + + bcma_cc_set32(cc, BCMA_CC_PMU_CTL, 0x400); + + b43_phy_write(dev, 0x942, 0); + + b43_phy_lcn_txrx_spur_avoidance_mode(dev, false); + b43_phy_maskset(dev, 0x424, (u16) ~0xff00, 0x1b00); + b43_phy_write(dev, 0x425, 0x5907); + } else { + bcma_chipco_pll_write(cc, 0x2, 0x03140c04); + bcma_chipco_pll_maskset(cc, 0x3, 0x00ffffff, 0x333333); + bcma_chipco_pll_write(cc, 0x4, 0x202c2820); + + bcma_cc_set32(cc, BCMA_CC_PMU_CTL, 0x400); + + b43_phy_write(dev, 0x942, 0); + + b43_phy_lcn_txrx_spur_avoidance_mode(dev, true); + b43_phy_maskset(dev, 0x424, (u16) ~0xff00, 0x1f00); + b43_phy_write(dev, 0x425, 0x590a); + } + + b43_phy_set(dev, 0x44a, 0x44); + b43_phy_write(dev, 0x44a, 0x80); +} + +/* wlc_phy_chanspec_set_lcnphy */ +static int b43_phy_lcn_set_channel(struct b43_wldev *dev, + struct ieee80211_channel *channel, + enum nl80211_channel_type channel_type) +{ + static const u16 sfo_cfg[14][2] = { + {965, 1087}, {967, 1085}, {969, 1082}, {971, 1080}, {973, 1078}, + {975, 1076}, {977, 1073}, {979, 1071}, {981, 1069}, {983, 1067}, + {985, 1065}, {987, 1063}, {989, 1060}, {994, 1055}, + }; + + b43_phy_lcn_set_channel_tweaks(dev, channel->hw_value); + + b43_phy_set(dev, 0x44a, 0x44); + b43_phy_write(dev, 0x44a, 0x80); + + b43_radio_2064_channel_setup(dev); + mdelay(1); + + b43_phy_lcn_afe_set_unset(dev); + + b43_phy_write(dev, 0x657, sfo_cfg[channel->hw_value - 1][0]); + b43_phy_write(dev, 0x658, sfo_cfg[channel->hw_value - 1][1]); + + if (channel->hw_value == 14) { + b43_phy_maskset(dev, 0x448, ~(0x3 << 8), (2) << 8); + b43_phy_lcn_load_tx_iir_cck_filter(dev, 3); + } else { + b43_phy_maskset(dev, 0x448, ~(0x3 << 8), (1) << 8); + /* brcmsmac uses filter_type 2, we follow wl with 25 */ + b43_phy_lcn_load_tx_iir_cck_filter(dev, 25); + } + /* brcmsmac uses filter_type 2, we follow wl with 0 */ + b43_phy_lcn_load_tx_iir_ofdm_filter(dev, 0); + + b43_phy_maskset(dev, 0x4eb, ~(0x7 << 3), 0x1 << 3); + + return 0; +} + +/************************************************** + * Basic PHY ops. + **************************************************/ + +static int b43_phy_lcn_op_allocate(struct b43_wldev *dev) +{ + struct b43_phy_lcn *phy_lcn; + + phy_lcn = kzalloc(sizeof(*phy_lcn), GFP_KERNEL); + if (!phy_lcn) + return -ENOMEM; + dev->phy.lcn = phy_lcn; + + return 0; +} + +static void b43_phy_lcn_op_free(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_lcn *phy_lcn = phy->lcn; + + kfree(phy_lcn); + phy->lcn = NULL; +} + +static void b43_phy_lcn_op_prepare_structs(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_lcn *phy_lcn = phy->lcn; + + memset(phy_lcn, 0, sizeof(*phy_lcn)); +} + +/* wlc_phy_init_lcnphy */ +static int b43_phy_lcn_op_init(struct b43_wldev *dev) +{ + struct bcma_drv_cc *cc = &dev->dev->bdev->bus->drv_cc; + + b43_phy_set(dev, 0x44a, 0x80); + b43_phy_mask(dev, 0x44a, 0x7f); + b43_phy_set(dev, 0x6d1, 0x80); + b43_phy_write(dev, 0x6d0, 0x7); + + b43_phy_lcn_afe_set_unset(dev); + + b43_phy_write(dev, 0x60a, 0xa0); + b43_phy_write(dev, 0x46a, 0x19); + b43_phy_maskset(dev, 0x663, 0xFF00, 0x64); + + b43_phy_lcn_tables_init(dev); + + b43_phy_lcn_rev0_baseband_init(dev); + b43_phy_lcn_bu_tweaks(dev); + + if (dev->phy.radio_ver == 0x2064) + b43_radio_2064_init(dev); + else + B43_WARN_ON(1); + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + b43_phy_lcn_tx_pwr_ctl_init(dev); + + b43_switch_channel(dev, dev->phy.channel); + + bcma_chipco_regctl_maskset(cc, 0, 0xf, 0x9); + bcma_chipco_chipctl_maskset(cc, 0, 0, 0x03cddddd); + + /* TODO */ + + b43_phy_set(dev, 0x448, 0x4000); + udelay(100); + b43_phy_mask(dev, 0x448, ~0x4000); + + /* TODO */ + + return 0; +} + +static void b43_phy_lcn_op_software_rfkill(struct b43_wldev *dev, + bool blocked) +{ + if (b43_read32(dev, B43_MMIO_MACCTL) & B43_MACCTL_ENABLED) + b43err(dev->wl, "MAC not suspended\n"); + + if (blocked) { + b43_phy_mask(dev, B43_PHY_LCN_RF_CTL2, ~0x7c00); + b43_phy_set(dev, B43_PHY_LCN_RF_CTL1, 0x1f00); + + b43_phy_mask(dev, B43_PHY_LCN_RF_CTL5, ~0x7f00); + b43_phy_mask(dev, B43_PHY_LCN_RF_CTL4, ~0x2); + b43_phy_set(dev, B43_PHY_LCN_RF_CTL3, 0x808); + + b43_phy_mask(dev, B43_PHY_LCN_RF_CTL7, ~0x8); + b43_phy_set(dev, B43_PHY_LCN_RF_CTL6, 0x8); + } else { + b43_phy_mask(dev, B43_PHY_LCN_RF_CTL1, ~0x1f00); + b43_phy_mask(dev, B43_PHY_LCN_RF_CTL3, ~0x808); + b43_phy_mask(dev, B43_PHY_LCN_RF_CTL6, ~0x8); + } +} + +static void b43_phy_lcn_op_switch_analog(struct b43_wldev *dev, bool on) +{ + if (on) { + b43_phy_mask(dev, B43_PHY_LCN_AFE_CTL1, ~0x7); + } else { + b43_phy_set(dev, B43_PHY_LCN_AFE_CTL2, 0x7); + b43_phy_set(dev, B43_PHY_LCN_AFE_CTL1, 0x7); + } +} + +static int b43_phy_lcn_op_switch_channel(struct b43_wldev *dev, + unsigned int new_channel) +{ + struct ieee80211_channel *channel = dev->wl->hw->conf.chandef.chan; + enum nl80211_channel_type channel_type = + cfg80211_get_chandef_type(&dev->wl->hw->conf.chandef); + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + if ((new_channel < 1) || (new_channel > 14)) + return -EINVAL; + } else { + return -EINVAL; + } + + return b43_phy_lcn_set_channel(dev, channel, channel_type); +} + +static unsigned int b43_phy_lcn_op_get_default_chan(struct b43_wldev *dev) +{ + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + return 1; + return 36; +} + +static enum b43_txpwr_result +b43_phy_lcn_op_recalc_txpower(struct b43_wldev *dev, bool ignore_tssi) +{ + return B43_TXPWR_RES_DONE; +} + +static void b43_phy_lcn_op_adjust_txpower(struct b43_wldev *dev) +{ +} + +/************************************************** + * R/W ops. + **************************************************/ + +static u16 b43_phy_lcn_op_read(struct b43_wldev *dev, u16 reg) +{ + b43_write16(dev, B43_MMIO_PHY_CONTROL, reg); + return b43_read16(dev, B43_MMIO_PHY_DATA); +} + +static void b43_phy_lcn_op_write(struct b43_wldev *dev, u16 reg, u16 value) +{ + b43_write16(dev, B43_MMIO_PHY_CONTROL, reg); + b43_write16(dev, B43_MMIO_PHY_DATA, value); +} + +static void b43_phy_lcn_op_maskset(struct b43_wldev *dev, u16 reg, u16 mask, + u16 set) +{ + b43_write16(dev, B43_MMIO_PHY_CONTROL, reg); + b43_write16(dev, B43_MMIO_PHY_DATA, + (b43_read16(dev, B43_MMIO_PHY_DATA) & mask) | set); +} + +static u16 b43_phy_lcn_op_radio_read(struct b43_wldev *dev, u16 reg) +{ + /* LCN-PHY needs 0x200 for read access */ + reg |= 0x200; + + b43_write16(dev, B43_MMIO_RADIO24_CONTROL, reg); + return b43_read16(dev, B43_MMIO_RADIO24_DATA); +} + +static void b43_phy_lcn_op_radio_write(struct b43_wldev *dev, u16 reg, + u16 value) +{ + b43_write16(dev, B43_MMIO_RADIO24_CONTROL, reg); + b43_write16(dev, B43_MMIO_RADIO24_DATA, value); +} + +/************************************************** + * PHY ops struct. + **************************************************/ + +const struct b43_phy_operations b43_phyops_lcn = { + .allocate = b43_phy_lcn_op_allocate, + .free = b43_phy_lcn_op_free, + .prepare_structs = b43_phy_lcn_op_prepare_structs, + .init = b43_phy_lcn_op_init, + .phy_read = b43_phy_lcn_op_read, + .phy_write = b43_phy_lcn_op_write, + .phy_maskset = b43_phy_lcn_op_maskset, + .radio_read = b43_phy_lcn_op_radio_read, + .radio_write = b43_phy_lcn_op_radio_write, + .software_rfkill = b43_phy_lcn_op_software_rfkill, + .switch_analog = b43_phy_lcn_op_switch_analog, + .switch_channel = b43_phy_lcn_op_switch_channel, + .get_default_chan = b43_phy_lcn_op_get_default_chan, + .recalc_txpower = b43_phy_lcn_op_recalc_txpower, + .adjust_txpower = b43_phy_lcn_op_adjust_txpower, +}; diff --git a/drivers/net/wireless/b43/phy_lcn.h b/drivers/net/wireless/b43/phy_lcn.h new file mode 100644 index 0000000..6a7092e --- /dev/null +++ b/drivers/net/wireless/b43/phy_lcn.h @@ -0,0 +1,31 @@ +#ifndef B43_PHY_LCN_H_ +#define B43_PHY_LCN_H_ + +#include "phy_common.h" + + +#define B43_PHY_LCN_AFE_CTL1 B43_PHY_OFDM(0x03B) +#define B43_PHY_LCN_AFE_CTL2 B43_PHY_OFDM(0x03C) +#define B43_PHY_LCN_RF_CTL1 B43_PHY_OFDM(0x04C) +#define B43_PHY_LCN_RF_CTL2 B43_PHY_OFDM(0x04D) +#define B43_PHY_LCN_TABLE_ADDR B43_PHY_OFDM(0x055) /* Table address */ +#define B43_PHY_LCN_TABLE_DATALO B43_PHY_OFDM(0x056) /* Table data low */ +#define B43_PHY_LCN_TABLE_DATAHI B43_PHY_OFDM(0x057) /* Table data high */ +#define B43_PHY_LCN_RF_CTL3 B43_PHY_OFDM(0x0B0) +#define B43_PHY_LCN_RF_CTL4 B43_PHY_OFDM(0x0B1) +#define B43_PHY_LCN_RF_CTL5 B43_PHY_OFDM(0x0B7) +#define B43_PHY_LCN_RF_CTL6 B43_PHY_OFDM(0x0F9) +#define B43_PHY_LCN_RF_CTL7 B43_PHY_OFDM(0x0FA) + + +struct b43_phy_lcn { + bool hw_pwr_ctl; + bool hw_pwr_ctl_capable; + u8 tx_pwr_curr_idx; +}; + + +struct b43_phy_operations; +extern const struct b43_phy_operations b43_phyops_lcn; + +#endif /* B43_PHY_LCN_H_ */ diff --git a/drivers/net/wireless/b43/phy_lp.c b/drivers/net/wireless/b43/phy_lp.c new file mode 100644 index 0000000..92190da --- /dev/null +++ b/drivers/net/wireless/b43/phy_lp.c @@ -0,0 +1,2730 @@ +/* + + Broadcom B43 wireless driver + IEEE 802.11a/g LP-PHY driver + + Copyright (c) 2008-2009 Michael Buesch + Copyright (c) 2009 Gábor Stefanik + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include + +#include "b43.h" +#include "main.h" +#include "phy_lp.h" +#include "phy_common.h" +#include "tables_lpphy.h" + + +static inline u16 channel2freq_lp(u8 channel) +{ + if (channel < 14) + return (2407 + 5 * channel); + else if (channel == 14) + return 2484; + else if (channel < 184) + return (5000 + 5 * channel); + else + return (4000 + 5 * channel); +} + +static unsigned int b43_lpphy_op_get_default_chan(struct b43_wldev *dev) +{ + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + return 1; + return 36; +} + +static int b43_lpphy_op_allocate(struct b43_wldev *dev) +{ + struct b43_phy_lp *lpphy; + + lpphy = kzalloc(sizeof(*lpphy), GFP_KERNEL); + if (!lpphy) + return -ENOMEM; + dev->phy.lp = lpphy; + + return 0; +} + +static void b43_lpphy_op_prepare_structs(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_lp *lpphy = phy->lp; + + memset(lpphy, 0, sizeof(*lpphy)); + lpphy->antenna = B43_ANTENNA_DEFAULT; + + //TODO +} + +static void b43_lpphy_op_free(struct b43_wldev *dev) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + + kfree(lpphy); + dev->phy.lp = NULL; +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/LP/ReadBandSrom */ +static void lpphy_read_band_sprom(struct b43_wldev *dev) +{ + struct ssb_sprom *sprom = dev->dev->bus_sprom; + struct b43_phy_lp *lpphy = dev->phy.lp; + u16 cckpo, maxpwr; + u32 ofdmpo; + int i; + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + lpphy->tx_isolation_med_band = sprom->tri2g; + lpphy->bx_arch = sprom->bxa2g; + lpphy->rx_pwr_offset = sprom->rxpo2g; + lpphy->rssi_vf = sprom->rssismf2g; + lpphy->rssi_vc = sprom->rssismc2g; + lpphy->rssi_gs = sprom->rssisav2g; + lpphy->txpa[0] = sprom->pa0b0; + lpphy->txpa[1] = sprom->pa0b1; + lpphy->txpa[2] = sprom->pa0b2; + maxpwr = sprom->maxpwr_bg; + lpphy->max_tx_pwr_med_band = maxpwr; + cckpo = sprom->cck2gpo; + if (cckpo) { + ofdmpo = sprom->ofdm2gpo; + for (i = 0; i < 4; i++) { + lpphy->tx_max_rate[i] = + maxpwr - (ofdmpo & 0xF) * 2; + ofdmpo >>= 4; + } + ofdmpo = sprom->ofdm2gpo; + for (i = 4; i < 15; i++) { + lpphy->tx_max_rate[i] = + maxpwr - (ofdmpo & 0xF) * 2; + ofdmpo >>= 4; + } + } else { + u8 opo = sprom->opo; + for (i = 0; i < 4; i++) + lpphy->tx_max_rate[i] = maxpwr; + for (i = 4; i < 15; i++) + lpphy->tx_max_rate[i] = maxpwr - opo; + } + } else { /* 5GHz */ + lpphy->tx_isolation_low_band = sprom->tri5gl; + lpphy->tx_isolation_med_band = sprom->tri5g; + lpphy->tx_isolation_hi_band = sprom->tri5gh; + lpphy->bx_arch = sprom->bxa5g; + lpphy->rx_pwr_offset = sprom->rxpo5g; + lpphy->rssi_vf = sprom->rssismf5g; + lpphy->rssi_vc = sprom->rssismc5g; + lpphy->rssi_gs = sprom->rssisav5g; + lpphy->txpa[0] = sprom->pa1b0; + lpphy->txpa[1] = sprom->pa1b1; + lpphy->txpa[2] = sprom->pa1b2; + lpphy->txpal[0] = sprom->pa1lob0; + lpphy->txpal[1] = sprom->pa1lob1; + lpphy->txpal[2] = sprom->pa1lob2; + lpphy->txpah[0] = sprom->pa1hib0; + lpphy->txpah[1] = sprom->pa1hib1; + lpphy->txpah[2] = sprom->pa1hib2; + maxpwr = sprom->maxpwr_al; + ofdmpo = sprom->ofdm5glpo; + lpphy->max_tx_pwr_low_band = maxpwr; + for (i = 4; i < 12; i++) { + lpphy->tx_max_ratel[i] = maxpwr - (ofdmpo & 0xF) * 2; + ofdmpo >>= 4; + } + maxpwr = sprom->maxpwr_a; + ofdmpo = sprom->ofdm5gpo; + lpphy->max_tx_pwr_med_band = maxpwr; + for (i = 4; i < 12; i++) { + lpphy->tx_max_rate[i] = maxpwr - (ofdmpo & 0xF) * 2; + ofdmpo >>= 4; + } + maxpwr = sprom->maxpwr_ah; + ofdmpo = sprom->ofdm5ghpo; + lpphy->max_tx_pwr_hi_band = maxpwr; + for (i = 4; i < 12; i++) { + lpphy->tx_max_rateh[i] = maxpwr - (ofdmpo & 0xF) * 2; + ofdmpo >>= 4; + } + } +} + +static void lpphy_adjust_gain_table(struct b43_wldev *dev, u32 freq) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + u16 temp[3]; + u16 isolation; + + B43_WARN_ON(dev->phy.rev >= 2); + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + isolation = lpphy->tx_isolation_med_band; + else if (freq <= 5320) + isolation = lpphy->tx_isolation_low_band; + else if (freq <= 5700) + isolation = lpphy->tx_isolation_med_band; + else + isolation = lpphy->tx_isolation_hi_band; + + temp[0] = ((isolation - 26) / 12) << 12; + temp[1] = temp[0] + 0x1000; + temp[2] = temp[0] + 0x2000; + + b43_lptab_write_bulk(dev, B43_LPTAB16(13, 0), 3, temp); + b43_lptab_write_bulk(dev, B43_LPTAB16(12, 0), 3, temp); +} + +static void lpphy_table_init(struct b43_wldev *dev) +{ + u32 freq = channel2freq_lp(b43_lpphy_op_get_default_chan(dev)); + + if (dev->phy.rev < 2) + lpphy_rev0_1_table_init(dev); + else + lpphy_rev2plus_table_init(dev); + + lpphy_init_tx_gain_table(dev); + + if (dev->phy.rev < 2) + lpphy_adjust_gain_table(dev, freq); +} + +static void lpphy_baseband_rev0_1_init(struct b43_wldev *dev) +{ + struct ssb_bus *bus = dev->dev->sdev->bus; + struct ssb_sprom *sprom = dev->dev->bus_sprom; + struct b43_phy_lp *lpphy = dev->phy.lp; + u16 tmp, tmp2; + + b43_phy_mask(dev, B43_LPPHY_AFE_DAC_CTL, 0xF7FF); + b43_phy_write(dev, B43_LPPHY_AFE_CTL, 0); + b43_phy_write(dev, B43_LPPHY_AFE_CTL_OVR, 0); + b43_phy_write(dev, B43_LPPHY_RF_OVERRIDE_0, 0); + b43_phy_write(dev, B43_LPPHY_RF_OVERRIDE_2, 0); + b43_phy_set(dev, B43_LPPHY_AFE_DAC_CTL, 0x0004); + b43_phy_maskset(dev, B43_LPPHY_OFDMSYNCTHRESH0, 0xFF00, 0x0078); + b43_phy_maskset(dev, B43_LPPHY_CLIPCTRTHRESH, 0x83FF, 0x5800); + b43_phy_write(dev, B43_LPPHY_ADC_COMPENSATION_CTL, 0x0016); + b43_phy_maskset(dev, B43_LPPHY_AFE_ADC_CTL_0, 0xFFF8, 0x0004); + b43_phy_maskset(dev, B43_LPPHY_VERYLOWGAINDB, 0x00FF, 0x5400); + b43_phy_maskset(dev, B43_LPPHY_HIGAINDB, 0x00FF, 0x2400); + b43_phy_maskset(dev, B43_LPPHY_LOWGAINDB, 0x00FF, 0x2100); + b43_phy_maskset(dev, B43_LPPHY_VERYLOWGAINDB, 0xFF00, 0x0006); + b43_phy_mask(dev, B43_LPPHY_RX_RADIO_CTL, 0xFFFE); + b43_phy_maskset(dev, B43_LPPHY_CLIPCTRTHRESH, 0xFFE0, 0x0005); + b43_phy_maskset(dev, B43_LPPHY_CLIPCTRTHRESH, 0xFC1F, 0x0180); + b43_phy_maskset(dev, B43_LPPHY_CLIPCTRTHRESH, 0x83FF, 0x3C00); + b43_phy_maskset(dev, B43_LPPHY_GAINDIRECTMISMATCH, 0xFFF0, 0x0005); + b43_phy_maskset(dev, B43_LPPHY_GAIN_MISMATCH_LIMIT, 0xFFC0, 0x001A); + b43_phy_maskset(dev, B43_LPPHY_CRS_ED_THRESH, 0xFF00, 0x00B3); + b43_phy_maskset(dev, B43_LPPHY_CRS_ED_THRESH, 0x00FF, 0xAD00); + b43_phy_maskset(dev, B43_LPPHY_INPUT_PWRDB, + 0xFF00, lpphy->rx_pwr_offset); + if ((sprom->boardflags_lo & B43_BFL_FEM) && + ((b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) || + (sprom->boardflags_hi & B43_BFH_PAREF))) { + ssb_pmu_set_ldo_voltage(&bus->chipco, LDO_PAREF, 0x28); + ssb_pmu_set_ldo_paref(&bus->chipco, true); + if (dev->phy.rev == 0) { + b43_phy_maskset(dev, B43_LPPHY_LP_RF_SIGNAL_LUT, + 0xFFCF, 0x0010); + } + b43_lptab_write(dev, B43_LPTAB16(11, 7), 60); + } else { + ssb_pmu_set_ldo_paref(&bus->chipco, false); + b43_phy_maskset(dev, B43_LPPHY_LP_RF_SIGNAL_LUT, + 0xFFCF, 0x0020); + b43_lptab_write(dev, B43_LPTAB16(11, 7), 100); + } + tmp = lpphy->rssi_vf | lpphy->rssi_vc << 4 | 0xA000; + b43_phy_write(dev, B43_LPPHY_AFE_RSSI_CTL_0, tmp); + if (sprom->boardflags_hi & B43_BFH_RSSIINV) + b43_phy_maskset(dev, B43_LPPHY_AFE_RSSI_CTL_1, 0xF000, 0x0AAA); + else + b43_phy_maskset(dev, B43_LPPHY_AFE_RSSI_CTL_1, 0xF000, 0x02AA); + b43_lptab_write(dev, B43_LPTAB16(11, 1), 24); + b43_phy_maskset(dev, B43_LPPHY_RX_RADIO_CTL, + 0xFFF9, (lpphy->bx_arch << 1)); + if (dev->phy.rev == 1 && + (sprom->boardflags_hi & B43_BFH_FEM_BT)) { + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_1, 0xFFC0, 0x000A); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_1, 0x3F00, 0x0900); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_2, 0xFFC0, 0x000A); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_2, 0xC0FF, 0x0B00); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_3, 0xFFC0, 0x000A); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_3, 0xC0FF, 0x0400); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_4, 0xFFC0, 0x000A); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_4, 0xC0FF, 0x0B00); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_5, 0xFFC0, 0x000A); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_5, 0xC0FF, 0x0900); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_6, 0xFFC0, 0x000A); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_6, 0xC0FF, 0x0B00); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_7, 0xFFC0, 0x000A); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_7, 0xC0FF, 0x0900); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_8, 0xFFC0, 0x000A); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_8, 0xC0FF, 0x0B00); + } else if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ || + (dev->dev->board_type == SSB_BOARD_BU4312) || + (dev->phy.rev == 0 && (sprom->boardflags_lo & B43_BFL_FEM))) { + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_1, 0xFFC0, 0x0001); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_1, 0xC0FF, 0x0400); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_2, 0xFFC0, 0x0001); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_2, 0xC0FF, 0x0500); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_3, 0xFFC0, 0x0002); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_3, 0xC0FF, 0x0800); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_4, 0xFFC0, 0x0002); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_4, 0xC0FF, 0x0A00); + } else if (dev->phy.rev == 1 || + (sprom->boardflags_lo & B43_BFL_FEM)) { + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_1, 0xFFC0, 0x0004); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_1, 0xC0FF, 0x0800); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_2, 0xFFC0, 0x0004); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_2, 0xC0FF, 0x0C00); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_3, 0xFFC0, 0x0002); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_3, 0xC0FF, 0x0100); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_4, 0xFFC0, 0x0002); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_4, 0xC0FF, 0x0300); + } else { + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_1, 0xFFC0, 0x000A); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_1, 0xC0FF, 0x0900); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_2, 0xFFC0, 0x000A); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_2, 0xC0FF, 0x0B00); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_3, 0xFFC0, 0x0006); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_3, 0xC0FF, 0x0500); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_4, 0xFFC0, 0x0006); + b43_phy_maskset(dev, B43_LPPHY_TR_LOOKUP_4, 0xC0FF, 0x0700); + } + if (dev->phy.rev == 1 && (sprom->boardflags_hi & B43_BFH_PAREF)) { + b43_phy_copy(dev, B43_LPPHY_TR_LOOKUP_5, B43_LPPHY_TR_LOOKUP_1); + b43_phy_copy(dev, B43_LPPHY_TR_LOOKUP_6, B43_LPPHY_TR_LOOKUP_2); + b43_phy_copy(dev, B43_LPPHY_TR_LOOKUP_7, B43_LPPHY_TR_LOOKUP_3); + b43_phy_copy(dev, B43_LPPHY_TR_LOOKUP_8, B43_LPPHY_TR_LOOKUP_4); + } + if ((sprom->boardflags_hi & B43_BFH_FEM_BT) && + (dev->dev->chip_id == 0x5354) && + (dev->dev->chip_pkg == SSB_CHIPPACK_BCM4712S)) { + b43_phy_set(dev, B43_LPPHY_CRSGAIN_CTL, 0x0006); + b43_phy_write(dev, B43_LPPHY_GPIO_SELECT, 0x0005); + b43_phy_write(dev, B43_LPPHY_GPIO_OUTEN, 0xFFFF); + //FIXME the Broadcom driver caches & delays this HF write! + b43_hf_write(dev, b43_hf_read(dev) | B43_HF_PR45960W); + } + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + b43_phy_set(dev, B43_LPPHY_LP_PHY_CTL, 0x8000); + b43_phy_set(dev, B43_LPPHY_CRSGAIN_CTL, 0x0040); + b43_phy_maskset(dev, B43_LPPHY_MINPWR_LEVEL, 0x00FF, 0xA400); + b43_phy_maskset(dev, B43_LPPHY_CRSGAIN_CTL, 0xF0FF, 0x0B00); + b43_phy_maskset(dev, B43_LPPHY_SYNCPEAKCNT, 0xFFF8, 0x0007); + b43_phy_maskset(dev, B43_LPPHY_DSSS_CONFIRM_CNT, 0xFFF8, 0x0003); + b43_phy_maskset(dev, B43_LPPHY_DSSS_CONFIRM_CNT, 0xFFC7, 0x0020); + b43_phy_mask(dev, B43_LPPHY_IDLEAFTERPKTRXTO, 0x00FF); + } else { /* 5GHz */ + b43_phy_mask(dev, B43_LPPHY_LP_PHY_CTL, 0x7FFF); + b43_phy_mask(dev, B43_LPPHY_CRSGAIN_CTL, 0xFFBF); + } + if (dev->phy.rev == 1) { + tmp = b43_phy_read(dev, B43_LPPHY_CLIPCTRTHRESH); + tmp2 = (tmp & 0x03E0) >> 5; + tmp2 |= tmp2 << 5; + b43_phy_write(dev, B43_LPPHY_4C3, tmp2); + tmp = b43_phy_read(dev, B43_LPPHY_GAINDIRECTMISMATCH); + tmp2 = (tmp & 0x1F00) >> 8; + tmp2 |= tmp2 << 5; + b43_phy_write(dev, B43_LPPHY_4C4, tmp2); + tmp = b43_phy_read(dev, B43_LPPHY_VERYLOWGAINDB); + tmp2 = tmp & 0x00FF; + tmp2 |= tmp << 8; + b43_phy_write(dev, B43_LPPHY_4C5, tmp2); + } +} + +static void lpphy_save_dig_flt_state(struct b43_wldev *dev) +{ + static const u16 addr[] = { + B43_PHY_OFDM(0xC1), + B43_PHY_OFDM(0xC2), + B43_PHY_OFDM(0xC3), + B43_PHY_OFDM(0xC4), + B43_PHY_OFDM(0xC5), + B43_PHY_OFDM(0xC6), + B43_PHY_OFDM(0xC7), + B43_PHY_OFDM(0xC8), + B43_PHY_OFDM(0xCF), + }; + + static const u16 coefs[] = { + 0xDE5E, 0xE832, 0xE331, 0x4D26, + 0x0026, 0x1420, 0x0020, 0xFE08, + 0x0008, + }; + + struct b43_phy_lp *lpphy = dev->phy.lp; + int i; + + for (i = 0; i < ARRAY_SIZE(addr); i++) { + lpphy->dig_flt_state[i] = b43_phy_read(dev, addr[i]); + b43_phy_write(dev, addr[i], coefs[i]); + } +} + +static void lpphy_restore_dig_flt_state(struct b43_wldev *dev) +{ + static const u16 addr[] = { + B43_PHY_OFDM(0xC1), + B43_PHY_OFDM(0xC2), + B43_PHY_OFDM(0xC3), + B43_PHY_OFDM(0xC4), + B43_PHY_OFDM(0xC5), + B43_PHY_OFDM(0xC6), + B43_PHY_OFDM(0xC7), + B43_PHY_OFDM(0xC8), + B43_PHY_OFDM(0xCF), + }; + + struct b43_phy_lp *lpphy = dev->phy.lp; + int i; + + for (i = 0; i < ARRAY_SIZE(addr); i++) + b43_phy_write(dev, addr[i], lpphy->dig_flt_state[i]); +} + +static void lpphy_baseband_rev2plus_init(struct b43_wldev *dev) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + + b43_phy_write(dev, B43_LPPHY_AFE_DAC_CTL, 0x50); + b43_phy_write(dev, B43_LPPHY_AFE_CTL, 0x8800); + b43_phy_write(dev, B43_LPPHY_AFE_CTL_OVR, 0); + b43_phy_write(dev, B43_LPPHY_AFE_CTL_OVRVAL, 0); + b43_phy_write(dev, B43_LPPHY_RF_OVERRIDE_0, 0); + b43_phy_write(dev, B43_LPPHY_RF_OVERRIDE_2, 0); + b43_phy_write(dev, B43_PHY_OFDM(0xF9), 0); + b43_phy_write(dev, B43_LPPHY_TR_LOOKUP_1, 0); + b43_phy_set(dev, B43_LPPHY_ADC_COMPENSATION_CTL, 0x10); + b43_phy_maskset(dev, B43_LPPHY_OFDMSYNCTHRESH0, 0xFF00, 0xB4); + b43_phy_maskset(dev, B43_LPPHY_DCOFFSETTRANSIENT, 0xF8FF, 0x200); + b43_phy_maskset(dev, B43_LPPHY_DCOFFSETTRANSIENT, 0xFF00, 0x7F); + b43_phy_maskset(dev, B43_LPPHY_GAINDIRECTMISMATCH, 0xFF0F, 0x40); + b43_phy_maskset(dev, B43_LPPHY_PREAMBLECONFIRMTO, 0xFF00, 0x2); + b43_phy_mask(dev, B43_LPPHY_CRSGAIN_CTL, ~0x4000); + b43_phy_mask(dev, B43_LPPHY_CRSGAIN_CTL, ~0x2000); + b43_phy_set(dev, B43_PHY_OFDM(0x10A), 0x1); + if (dev->dev->board_rev >= 0x18) { + b43_lptab_write(dev, B43_LPTAB32(17, 65), 0xEC); + b43_phy_maskset(dev, B43_PHY_OFDM(0x10A), 0xFF01, 0x14); + } else { + b43_phy_maskset(dev, B43_PHY_OFDM(0x10A), 0xFF01, 0x10); + } + b43_phy_maskset(dev, B43_PHY_OFDM(0xDF), 0xFF00, 0xF4); + b43_phy_maskset(dev, B43_PHY_OFDM(0xDF), 0x00FF, 0xF100); + b43_phy_write(dev, B43_LPPHY_CLIPTHRESH, 0x48); + b43_phy_maskset(dev, B43_LPPHY_HIGAINDB, 0xFF00, 0x46); + b43_phy_maskset(dev, B43_PHY_OFDM(0xE4), 0xFF00, 0x10); + b43_phy_maskset(dev, B43_LPPHY_PWR_THRESH1, 0xFFF0, 0x9); + b43_phy_mask(dev, B43_LPPHY_GAINDIRECTMISMATCH, ~0xF); + b43_phy_maskset(dev, B43_LPPHY_VERYLOWGAINDB, 0x00FF, 0x5500); + b43_phy_maskset(dev, B43_LPPHY_CLIPCTRTHRESH, 0xFC1F, 0xA0); + b43_phy_maskset(dev, B43_LPPHY_GAINDIRECTMISMATCH, 0xE0FF, 0x300); + b43_phy_maskset(dev, B43_LPPHY_HIGAINDB, 0x00FF, 0x2A00); + if ((dev->dev->chip_id == 0x4325) && (dev->dev->chip_rev == 0)) { + b43_phy_maskset(dev, B43_LPPHY_LOWGAINDB, 0x00FF, 0x2100); + b43_phy_maskset(dev, B43_LPPHY_VERYLOWGAINDB, 0xFF00, 0xA); + } else { + b43_phy_maskset(dev, B43_LPPHY_LOWGAINDB, 0x00FF, 0x1E00); + b43_phy_maskset(dev, B43_LPPHY_VERYLOWGAINDB, 0xFF00, 0xD); + } + b43_phy_maskset(dev, B43_PHY_OFDM(0xFE), 0xFFE0, 0x1F); + b43_phy_maskset(dev, B43_PHY_OFDM(0xFF), 0xFFE0, 0xC); + b43_phy_maskset(dev, B43_PHY_OFDM(0x100), 0xFF00, 0x19); + b43_phy_maskset(dev, B43_PHY_OFDM(0xFF), 0x03FF, 0x3C00); + b43_phy_maskset(dev, B43_PHY_OFDM(0xFE), 0xFC1F, 0x3E0); + b43_phy_maskset(dev, B43_PHY_OFDM(0xFF), 0xFFE0, 0xC); + b43_phy_maskset(dev, B43_PHY_OFDM(0x100), 0x00FF, 0x1900); + b43_phy_maskset(dev, B43_LPPHY_CLIPCTRTHRESH, 0x83FF, 0x5800); + b43_phy_maskset(dev, B43_LPPHY_CLIPCTRTHRESH, 0xFFE0, 0x12); + b43_phy_maskset(dev, B43_LPPHY_GAINMISMATCH, 0x0FFF, 0x9000); + + if ((dev->dev->chip_id == 0x4325) && (dev->dev->chip_rev == 0)) { + b43_lptab_write(dev, B43_LPTAB16(0x08, 0x14), 0); + b43_lptab_write(dev, B43_LPTAB16(0x08, 0x12), 0x40); + } + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + b43_phy_set(dev, B43_LPPHY_CRSGAIN_CTL, 0x40); + b43_phy_maskset(dev, B43_LPPHY_CRSGAIN_CTL, 0xF0FF, 0xB00); + b43_phy_maskset(dev, B43_LPPHY_SYNCPEAKCNT, 0xFFF8, 0x6); + b43_phy_maskset(dev, B43_LPPHY_MINPWR_LEVEL, 0x00FF, 0x9D00); + b43_phy_maskset(dev, B43_LPPHY_MINPWR_LEVEL, 0xFF00, 0xA1); + b43_phy_mask(dev, B43_LPPHY_IDLEAFTERPKTRXTO, 0x00FF); + } else /* 5GHz */ + b43_phy_mask(dev, B43_LPPHY_CRSGAIN_CTL, ~0x40); + + b43_phy_maskset(dev, B43_LPPHY_CRS_ED_THRESH, 0xFF00, 0xB3); + b43_phy_maskset(dev, B43_LPPHY_CRS_ED_THRESH, 0x00FF, 0xAD00); + b43_phy_maskset(dev, B43_LPPHY_INPUT_PWRDB, 0xFF00, lpphy->rx_pwr_offset); + b43_phy_set(dev, B43_LPPHY_RESET_CTL, 0x44); + b43_phy_write(dev, B43_LPPHY_RESET_CTL, 0x80); + b43_phy_write(dev, B43_LPPHY_AFE_RSSI_CTL_0, 0xA954); + b43_phy_write(dev, B43_LPPHY_AFE_RSSI_CTL_1, + 0x2000 | ((u16)lpphy->rssi_gs << 10) | + ((u16)lpphy->rssi_vc << 4) | lpphy->rssi_vf); + + if ((dev->dev->chip_id == 0x4325) && (dev->dev->chip_rev == 0)) { + b43_phy_set(dev, B43_LPPHY_AFE_ADC_CTL_0, 0x1C); + b43_phy_maskset(dev, B43_LPPHY_AFE_CTL, 0x00FF, 0x8800); + b43_phy_maskset(dev, B43_LPPHY_AFE_ADC_CTL_1, 0xFC3C, 0x0400); + } + + lpphy_save_dig_flt_state(dev); +} + +static void lpphy_baseband_init(struct b43_wldev *dev) +{ + lpphy_table_init(dev); + if (dev->phy.rev >= 2) + lpphy_baseband_rev2plus_init(dev); + else + lpphy_baseband_rev0_1_init(dev); +} + +struct b2062_freqdata { + u16 freq; + u8 data[6]; +}; + +/* Initialize the 2062 radio. */ +static void lpphy_2062_init(struct b43_wldev *dev) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + struct ssb_bus *bus = dev->dev->sdev->bus; + u32 crystalfreq, tmp, ref; + unsigned int i; + const struct b2062_freqdata *fd = NULL; + + static const struct b2062_freqdata freqdata_tab[] = { + { .freq = 12000, .data[0] = 6, .data[1] = 6, .data[2] = 6, + .data[3] = 6, .data[4] = 10, .data[5] = 6, }, + { .freq = 13000, .data[0] = 4, .data[1] = 4, .data[2] = 4, + .data[3] = 4, .data[4] = 11, .data[5] = 7, }, + { .freq = 14400, .data[0] = 3, .data[1] = 3, .data[2] = 3, + .data[3] = 3, .data[4] = 12, .data[5] = 7, }, + { .freq = 16200, .data[0] = 3, .data[1] = 3, .data[2] = 3, + .data[3] = 3, .data[4] = 13, .data[5] = 8, }, + { .freq = 18000, .data[0] = 2, .data[1] = 2, .data[2] = 2, + .data[3] = 2, .data[4] = 14, .data[5] = 8, }, + { .freq = 19200, .data[0] = 1, .data[1] = 1, .data[2] = 1, + .data[3] = 1, .data[4] = 14, .data[5] = 9, }, + }; + + b2062_upload_init_table(dev); + + b43_radio_write(dev, B2062_N_TX_CTL3, 0); + b43_radio_write(dev, B2062_N_TX_CTL4, 0); + b43_radio_write(dev, B2062_N_TX_CTL5, 0); + b43_radio_write(dev, B2062_N_TX_CTL6, 0); + b43_radio_write(dev, B2062_N_PDN_CTL0, 0x40); + b43_radio_write(dev, B2062_N_PDN_CTL0, 0); + b43_radio_write(dev, B2062_N_CALIB_TS, 0x10); + b43_radio_write(dev, B2062_N_CALIB_TS, 0); + if (dev->phy.rev > 0) { + b43_radio_write(dev, B2062_S_BG_CTL1, + (b43_radio_read(dev, B2062_N_COMM2) >> 1) | 0x80); + } + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + b43_radio_set(dev, B2062_N_TSSI_CTL0, 0x1); + else + b43_radio_mask(dev, B2062_N_TSSI_CTL0, ~0x1); + + /* Get the crystal freq, in Hz. */ + crystalfreq = bus->chipco.pmu.crystalfreq * 1000; + + B43_WARN_ON(!(bus->chipco.capabilities & SSB_CHIPCO_CAP_PMU)); + B43_WARN_ON(crystalfreq == 0); + + if (crystalfreq <= 30000000) { + lpphy->pdiv = 1; + b43_radio_mask(dev, B2062_S_RFPLL_CTL1, 0xFFFB); + } else { + lpphy->pdiv = 2; + b43_radio_set(dev, B2062_S_RFPLL_CTL1, 0x4); + } + + tmp = (((800000000 * lpphy->pdiv + crystalfreq) / + (2 * crystalfreq)) - 8) & 0xFF; + b43_radio_write(dev, B2062_S_RFPLL_CTL7, tmp); + + tmp = (((100 * crystalfreq + 16000000 * lpphy->pdiv) / + (32000000 * lpphy->pdiv)) - 1) & 0xFF; + b43_radio_write(dev, B2062_S_RFPLL_CTL18, tmp); + + tmp = (((2 * crystalfreq + 1000000 * lpphy->pdiv) / + (2000000 * lpphy->pdiv)) - 1) & 0xFF; + b43_radio_write(dev, B2062_S_RFPLL_CTL19, tmp); + + ref = (1000 * lpphy->pdiv + 2 * crystalfreq) / (2000 * lpphy->pdiv); + ref &= 0xFFFF; + for (i = 0; i < ARRAY_SIZE(freqdata_tab); i++) { + if (ref < freqdata_tab[i].freq) { + fd = &freqdata_tab[i]; + break; + } + } + if (!fd) + fd = &freqdata_tab[ARRAY_SIZE(freqdata_tab) - 1]; + b43dbg(dev->wl, "b2062: Using crystal tab entry %u kHz.\n", + fd->freq); /* FIXME: Keep this printk until the code is fully debugged. */ + + b43_radio_write(dev, B2062_S_RFPLL_CTL8, + ((u16)(fd->data[1]) << 4) | fd->data[0]); + b43_radio_write(dev, B2062_S_RFPLL_CTL9, + ((u16)(fd->data[3]) << 4) | fd->data[2]); + b43_radio_write(dev, B2062_S_RFPLL_CTL10, fd->data[4]); + b43_radio_write(dev, B2062_S_RFPLL_CTL11, fd->data[5]); +} + +/* Initialize the 2063 radio. */ +static void lpphy_2063_init(struct b43_wldev *dev) +{ + b2063_upload_init_table(dev); + b43_radio_write(dev, B2063_LOGEN_SP5, 0); + b43_radio_set(dev, B2063_COMM8, 0x38); + b43_radio_write(dev, B2063_REG_SP1, 0x56); + b43_radio_mask(dev, B2063_RX_BB_CTL2, ~0x2); + b43_radio_write(dev, B2063_PA_SP7, 0); + b43_radio_write(dev, B2063_TX_RF_SP6, 0x20); + b43_radio_write(dev, B2063_TX_RF_SP9, 0x40); + if (dev->phy.rev == 2) { + b43_radio_write(dev, B2063_PA_SP3, 0xa0); + b43_radio_write(dev, B2063_PA_SP4, 0xa0); + b43_radio_write(dev, B2063_PA_SP2, 0x18); + } else { + b43_radio_write(dev, B2063_PA_SP3, 0x20); + b43_radio_write(dev, B2063_PA_SP2, 0x20); + } +} + +struct lpphy_stx_table_entry { + u16 phy_offset; + u16 phy_shift; + u16 rf_addr; + u16 rf_shift; + u16 mask; +}; + +static const struct lpphy_stx_table_entry lpphy_stx_table[] = { + { .phy_offset = 2, .phy_shift = 6, .rf_addr = 0x3d, .rf_shift = 3, .mask = 0x01, }, + { .phy_offset = 1, .phy_shift = 12, .rf_addr = 0x4c, .rf_shift = 1, .mask = 0x01, }, + { .phy_offset = 1, .phy_shift = 8, .rf_addr = 0x50, .rf_shift = 0, .mask = 0x7f, }, + { .phy_offset = 0, .phy_shift = 8, .rf_addr = 0x44, .rf_shift = 0, .mask = 0xff, }, + { .phy_offset = 1, .phy_shift = 0, .rf_addr = 0x4a, .rf_shift = 0, .mask = 0xff, }, + { .phy_offset = 0, .phy_shift = 4, .rf_addr = 0x4d, .rf_shift = 0, .mask = 0xff, }, + { .phy_offset = 1, .phy_shift = 4, .rf_addr = 0x4e, .rf_shift = 0, .mask = 0xff, }, + { .phy_offset = 0, .phy_shift = 12, .rf_addr = 0x4f, .rf_shift = 0, .mask = 0x0f, }, + { .phy_offset = 1, .phy_shift = 0, .rf_addr = 0x4f, .rf_shift = 4, .mask = 0x0f, }, + { .phy_offset = 3, .phy_shift = 0, .rf_addr = 0x49, .rf_shift = 0, .mask = 0x0f, }, + { .phy_offset = 4, .phy_shift = 3, .rf_addr = 0x46, .rf_shift = 4, .mask = 0x07, }, + { .phy_offset = 3, .phy_shift = 15, .rf_addr = 0x46, .rf_shift = 0, .mask = 0x01, }, + { .phy_offset = 4, .phy_shift = 0, .rf_addr = 0x46, .rf_shift = 1, .mask = 0x07, }, + { .phy_offset = 3, .phy_shift = 8, .rf_addr = 0x48, .rf_shift = 4, .mask = 0x07, }, + { .phy_offset = 3, .phy_shift = 11, .rf_addr = 0x48, .rf_shift = 0, .mask = 0x0f, }, + { .phy_offset = 3, .phy_shift = 4, .rf_addr = 0x49, .rf_shift = 4, .mask = 0x0f, }, + { .phy_offset = 2, .phy_shift = 15, .rf_addr = 0x45, .rf_shift = 0, .mask = 0x01, }, + { .phy_offset = 5, .phy_shift = 13, .rf_addr = 0x52, .rf_shift = 4, .mask = 0x07, }, + { .phy_offset = 6, .phy_shift = 0, .rf_addr = 0x52, .rf_shift = 7, .mask = 0x01, }, + { .phy_offset = 5, .phy_shift = 3, .rf_addr = 0x41, .rf_shift = 5, .mask = 0x07, }, + { .phy_offset = 5, .phy_shift = 6, .rf_addr = 0x41, .rf_shift = 0, .mask = 0x0f, }, + { .phy_offset = 5, .phy_shift = 10, .rf_addr = 0x42, .rf_shift = 5, .mask = 0x07, }, + { .phy_offset = 4, .phy_shift = 15, .rf_addr = 0x42, .rf_shift = 0, .mask = 0x01, }, + { .phy_offset = 5, .phy_shift = 0, .rf_addr = 0x42, .rf_shift = 1, .mask = 0x07, }, + { .phy_offset = 4, .phy_shift = 11, .rf_addr = 0x43, .rf_shift = 4, .mask = 0x0f, }, + { .phy_offset = 4, .phy_shift = 7, .rf_addr = 0x43, .rf_shift = 0, .mask = 0x0f, }, + { .phy_offset = 4, .phy_shift = 6, .rf_addr = 0x45, .rf_shift = 1, .mask = 0x01, }, + { .phy_offset = 2, .phy_shift = 7, .rf_addr = 0x40, .rf_shift = 4, .mask = 0x0f, }, + { .phy_offset = 2, .phy_shift = 11, .rf_addr = 0x40, .rf_shift = 0, .mask = 0x0f, }, +}; + +static void lpphy_sync_stx(struct b43_wldev *dev) +{ + const struct lpphy_stx_table_entry *e; + unsigned int i; + u16 tmp; + + for (i = 0; i < ARRAY_SIZE(lpphy_stx_table); i++) { + e = &lpphy_stx_table[i]; + tmp = b43_radio_read(dev, e->rf_addr); + tmp >>= e->rf_shift; + tmp <<= e->phy_shift; + b43_phy_maskset(dev, B43_PHY_OFDM(0xF2 + e->phy_offset), + ~(e->mask << e->phy_shift), tmp); + } +} + +static void lpphy_radio_init(struct b43_wldev *dev) +{ + /* The radio is attached through the 4wire bus. */ + b43_phy_set(dev, B43_LPPHY_FOURWIRE_CTL, 0x2); + udelay(1); + b43_phy_mask(dev, B43_LPPHY_FOURWIRE_CTL, 0xFFFD); + udelay(1); + + if (dev->phy.radio_ver == 0x2062) { + lpphy_2062_init(dev); + } else { + lpphy_2063_init(dev); + lpphy_sync_stx(dev); + b43_phy_write(dev, B43_PHY_OFDM(0xF0), 0x5F80); + b43_phy_write(dev, B43_PHY_OFDM(0xF1), 0); + if (dev->dev->chip_id == 0x4325) { + // TODO SSB PMU recalibration + } + } +} + +struct lpphy_iq_est { u32 iq_prod, i_pwr, q_pwr; }; + +static void lpphy_set_rc_cap(struct b43_wldev *dev) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + + u8 rc_cap = (lpphy->rc_cap & 0x1F) >> 1; + + if (dev->phy.rev == 1) //FIXME check channel 14! + rc_cap = min_t(u8, rc_cap + 5, 15); + + b43_radio_write(dev, B2062_N_RXBB_CALIB2, + max_t(u8, lpphy->rc_cap - 4, 0x80)); + b43_radio_write(dev, B2062_N_TX_CTL_A, rc_cap | 0x80); + b43_radio_write(dev, B2062_S_RXG_CNT16, + ((lpphy->rc_cap & 0x1F) >> 2) | 0x80); +} + +static u8 lpphy_get_bb_mult(struct b43_wldev *dev) +{ + return (b43_lptab_read(dev, B43_LPTAB16(0, 87)) & 0xFF00) >> 8; +} + +static void lpphy_set_bb_mult(struct b43_wldev *dev, u8 bb_mult) +{ + b43_lptab_write(dev, B43_LPTAB16(0, 87), (u16)bb_mult << 8); +} + +static void lpphy_set_deaf(struct b43_wldev *dev, bool user) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + + if (user) + lpphy->crs_usr_disable = true; + else + lpphy->crs_sys_disable = true; + b43_phy_maskset(dev, B43_LPPHY_CRSGAIN_CTL, 0xFF1F, 0x80); +} + +static void lpphy_clear_deaf(struct b43_wldev *dev, bool user) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + + if (user) + lpphy->crs_usr_disable = false; + else + lpphy->crs_sys_disable = false; + + if (!lpphy->crs_usr_disable && !lpphy->crs_sys_disable) { + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + b43_phy_maskset(dev, B43_LPPHY_CRSGAIN_CTL, + 0xFF1F, 0x60); + else + b43_phy_maskset(dev, B43_LPPHY_CRSGAIN_CTL, + 0xFF1F, 0x20); + } +} + +static void lpphy_set_trsw_over(struct b43_wldev *dev, bool tx, bool rx) +{ + u16 trsw = (tx << 1) | rx; + b43_phy_maskset(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, 0xFFFC, trsw); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x3); +} + +static void lpphy_disable_crs(struct b43_wldev *dev, bool user) +{ + lpphy_set_deaf(dev, user); + lpphy_set_trsw_over(dev, false, true); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, 0xFFFB); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x4); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, 0xFFF7); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x8); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, 0x10); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x10); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, 0xFFDF); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x20); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, 0xFFBF); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x40); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, 0x7); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, 0x38); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, 0xFF3F); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, 0x100); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, 0xFDFF); + b43_phy_write(dev, B43_LPPHY_PS_CTL_OVERRIDE_VAL0, 0); + b43_phy_write(dev, B43_LPPHY_PS_CTL_OVERRIDE_VAL1, 1); + b43_phy_write(dev, B43_LPPHY_PS_CTL_OVERRIDE_VAL2, 0x20); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, 0xFBFF); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, 0xF7FF); + b43_phy_write(dev, B43_LPPHY_TX_GAIN_CTL_OVERRIDE_VAL, 0); + b43_phy_write(dev, B43_LPPHY_RX_GAIN_CTL_OVERRIDE_VAL, 0x45AF); + b43_phy_write(dev, B43_LPPHY_RF_OVERRIDE_2, 0x3FF); +} + +static void lpphy_restore_crs(struct b43_wldev *dev, bool user) +{ + lpphy_clear_deaf(dev, user); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_0, 0xFF80); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_2, 0xFC00); +} + +struct lpphy_tx_gains { u16 gm, pga, pad, dac; }; + +static void lpphy_disable_rx_gain_override(struct b43_wldev *dev) +{ + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_0, 0xFFFE); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_0, 0xFFEF); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_0, 0xFFBF); + if (dev->phy.rev >= 2) { + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_2, 0xFEFF); + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_2, 0xFBFF); + b43_phy_mask(dev, B43_PHY_OFDM(0xE5), 0xFFF7); + } + } else { + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_2, 0xFDFF); + } +} + +static void lpphy_enable_rx_gain_override(struct b43_wldev *dev) +{ + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x1); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x10); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x40); + if (dev->phy.rev >= 2) { + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_2, 0x100); + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_2, 0x400); + b43_phy_set(dev, B43_PHY_OFDM(0xE5), 0x8); + } + } else { + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_2, 0x200); + } +} + +static void lpphy_disable_tx_gain_override(struct b43_wldev *dev) +{ + if (dev->phy.rev < 2) + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_2, 0xFEFF); + else { + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_2, 0xFF7F); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_2, 0xBFFF); + } + b43_phy_mask(dev, B43_LPPHY_AFE_CTL_OVR, 0xFFBF); +} + +static void lpphy_enable_tx_gain_override(struct b43_wldev *dev) +{ + if (dev->phy.rev < 2) + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_2, 0x100); + else { + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_2, 0x80); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_2, 0x4000); + } + b43_phy_set(dev, B43_LPPHY_AFE_CTL_OVR, 0x40); +} + +static struct lpphy_tx_gains lpphy_get_tx_gains(struct b43_wldev *dev) +{ + struct lpphy_tx_gains gains; + u16 tmp; + + gains.dac = (b43_phy_read(dev, B43_LPPHY_AFE_DAC_CTL) & 0x380) >> 7; + if (dev->phy.rev < 2) { + tmp = b43_phy_read(dev, + B43_LPPHY_TX_GAIN_CTL_OVERRIDE_VAL) & 0x7FF; + gains.gm = tmp & 0x0007; + gains.pga = (tmp & 0x0078) >> 3; + gains.pad = (tmp & 0x780) >> 7; + } else { + tmp = b43_phy_read(dev, B43_LPPHY_TX_GAIN_CTL_OVERRIDE_VAL); + gains.pad = b43_phy_read(dev, B43_PHY_OFDM(0xFB)) & 0xFF; + gains.gm = tmp & 0xFF; + gains.pga = (tmp >> 8) & 0xFF; + } + + return gains; +} + +static void lpphy_set_dac_gain(struct b43_wldev *dev, u16 dac) +{ + u16 ctl = b43_phy_read(dev, B43_LPPHY_AFE_DAC_CTL) & 0xC7F; + ctl |= dac << 7; + b43_phy_maskset(dev, B43_LPPHY_AFE_DAC_CTL, 0xF000, ctl); +} + +static u16 lpphy_get_pa_gain(struct b43_wldev *dev) +{ + return b43_phy_read(dev, B43_PHY_OFDM(0xFB)) & 0x7F; +} + +static void lpphy_set_pa_gain(struct b43_wldev *dev, u16 gain) +{ + b43_phy_maskset(dev, B43_PHY_OFDM(0xFB), 0xE03F, gain << 6); + b43_phy_maskset(dev, B43_PHY_OFDM(0xFD), 0x80FF, gain << 8); +} + +static void lpphy_set_tx_gains(struct b43_wldev *dev, + struct lpphy_tx_gains gains) +{ + u16 rf_gain, pa_gain; + + if (dev->phy.rev < 2) { + rf_gain = (gains.pad << 7) | (gains.pga << 3) | gains.gm; + b43_phy_maskset(dev, B43_LPPHY_TX_GAIN_CTL_OVERRIDE_VAL, + 0xF800, rf_gain); + } else { + pa_gain = lpphy_get_pa_gain(dev); + b43_phy_write(dev, B43_LPPHY_TX_GAIN_CTL_OVERRIDE_VAL, + (gains.pga << 8) | gains.gm); + /* + * SPEC FIXME The spec calls for (pa_gain << 8) here, but that + * conflicts with the spec for set_pa_gain! Vendor driver bug? + */ + b43_phy_maskset(dev, B43_PHY_OFDM(0xFB), + 0x8000, gains.pad | (pa_gain << 6)); + b43_phy_write(dev, B43_PHY_OFDM(0xFC), + (gains.pga << 8) | gains.gm); + b43_phy_maskset(dev, B43_PHY_OFDM(0xFD), + 0x8000, gains.pad | (pa_gain << 8)); + } + lpphy_set_dac_gain(dev, gains.dac); + lpphy_enable_tx_gain_override(dev); +} + +static void lpphy_rev0_1_set_rx_gain(struct b43_wldev *dev, u32 gain) +{ + u16 trsw = gain & 0x1; + u16 lna = (gain & 0xFFFC) | ((gain & 0xC) >> 2); + u16 ext_lna = (gain & 2) >> 1; + + b43_phy_maskset(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, 0xFFFE, trsw); + b43_phy_maskset(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, + 0xFBFF, ext_lna << 10); + b43_phy_maskset(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, + 0xF7FF, ext_lna << 11); + b43_phy_write(dev, B43_LPPHY_RX_GAIN_CTL_OVERRIDE_VAL, lna); +} + +static void lpphy_rev2plus_set_rx_gain(struct b43_wldev *dev, u32 gain) +{ + u16 low_gain = gain & 0xFFFF; + u16 high_gain = (gain >> 16) & 0xF; + u16 ext_lna = (gain >> 21) & 0x1; + u16 trsw = ~(gain >> 20) & 0x1; + u16 tmp; + + b43_phy_maskset(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, 0xFFFE, trsw); + b43_phy_maskset(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, + 0xFDFF, ext_lna << 9); + b43_phy_maskset(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, + 0xFBFF, ext_lna << 10); + b43_phy_write(dev, B43_LPPHY_RX_GAIN_CTL_OVERRIDE_VAL, low_gain); + b43_phy_maskset(dev, B43_LPPHY_AFE_DDFS, 0xFFF0, high_gain); + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + tmp = (gain >> 2) & 0x3; + b43_phy_maskset(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, + 0xE7FF, tmp<<11); + b43_phy_maskset(dev, B43_PHY_OFDM(0xE6), 0xFFE7, tmp << 3); + } +} + +static void lpphy_set_rx_gain(struct b43_wldev *dev, u32 gain) +{ + if (dev->phy.rev < 2) + lpphy_rev0_1_set_rx_gain(dev, gain); + else + lpphy_rev2plus_set_rx_gain(dev, gain); + lpphy_enable_rx_gain_override(dev); +} + +static void lpphy_set_rx_gain_by_index(struct b43_wldev *dev, u16 idx) +{ + u32 gain = b43_lptab_read(dev, B43_LPTAB16(12, idx)); + lpphy_set_rx_gain(dev, gain); +} + +static void lpphy_stop_ddfs(struct b43_wldev *dev) +{ + b43_phy_mask(dev, B43_LPPHY_AFE_DDFS, 0xFFFD); + b43_phy_mask(dev, B43_LPPHY_LP_PHY_CTL, 0xFFDF); +} + +static void lpphy_run_ddfs(struct b43_wldev *dev, int i_on, int q_on, + int incr1, int incr2, int scale_idx) +{ + lpphy_stop_ddfs(dev); + b43_phy_mask(dev, B43_LPPHY_AFE_DDFS_POINTER_INIT, 0xFF80); + b43_phy_mask(dev, B43_LPPHY_AFE_DDFS_POINTER_INIT, 0x80FF); + b43_phy_maskset(dev, B43_LPPHY_AFE_DDFS_INCR_INIT, 0xFF80, incr1); + b43_phy_maskset(dev, B43_LPPHY_AFE_DDFS_INCR_INIT, 0x80FF, incr2 << 8); + b43_phy_maskset(dev, B43_LPPHY_AFE_DDFS, 0xFFF7, i_on << 3); + b43_phy_maskset(dev, B43_LPPHY_AFE_DDFS, 0xFFEF, q_on << 4); + b43_phy_maskset(dev, B43_LPPHY_AFE_DDFS, 0xFF9F, scale_idx << 5); + b43_phy_mask(dev, B43_LPPHY_AFE_DDFS, 0xFFFB); + b43_phy_set(dev, B43_LPPHY_AFE_DDFS, 0x2); + b43_phy_set(dev, B43_LPPHY_LP_PHY_CTL, 0x20); +} + +static bool lpphy_rx_iq_est(struct b43_wldev *dev, u16 samples, u8 time, + struct lpphy_iq_est *iq_est) +{ + int i; + + b43_phy_mask(dev, B43_LPPHY_CRSGAIN_CTL, 0xFFF7); + b43_phy_write(dev, B43_LPPHY_IQ_NUM_SMPLS_ADDR, samples); + b43_phy_maskset(dev, B43_LPPHY_IQ_ENABLE_WAIT_TIME_ADDR, 0xFF00, time); + b43_phy_mask(dev, B43_LPPHY_IQ_ENABLE_WAIT_TIME_ADDR, 0xFEFF); + b43_phy_set(dev, B43_LPPHY_IQ_ENABLE_WAIT_TIME_ADDR, 0x200); + + for (i = 0; i < 500; i++) { + if (!(b43_phy_read(dev, + B43_LPPHY_IQ_ENABLE_WAIT_TIME_ADDR) & 0x200)) + break; + msleep(1); + } + + if ((b43_phy_read(dev, B43_LPPHY_IQ_ENABLE_WAIT_TIME_ADDR) & 0x200)) { + b43_phy_set(dev, B43_LPPHY_CRSGAIN_CTL, 0x8); + return false; + } + + iq_est->iq_prod = b43_phy_read(dev, B43_LPPHY_IQ_ACC_HI_ADDR); + iq_est->iq_prod <<= 16; + iq_est->iq_prod |= b43_phy_read(dev, B43_LPPHY_IQ_ACC_LO_ADDR); + + iq_est->i_pwr = b43_phy_read(dev, B43_LPPHY_IQ_I_PWR_ACC_HI_ADDR); + iq_est->i_pwr <<= 16; + iq_est->i_pwr |= b43_phy_read(dev, B43_LPPHY_IQ_I_PWR_ACC_LO_ADDR); + + iq_est->q_pwr = b43_phy_read(dev, B43_LPPHY_IQ_Q_PWR_ACC_HI_ADDR); + iq_est->q_pwr <<= 16; + iq_est->q_pwr |= b43_phy_read(dev, B43_LPPHY_IQ_Q_PWR_ACC_LO_ADDR); + + b43_phy_set(dev, B43_LPPHY_CRSGAIN_CTL, 0x8); + return true; +} + +static int lpphy_loopback(struct b43_wldev *dev) +{ + struct lpphy_iq_est iq_est; + int i, index = -1; + u32 tmp; + + memset(&iq_est, 0, sizeof(iq_est)); + + lpphy_set_trsw_over(dev, true, true); + b43_phy_set(dev, B43_LPPHY_AFE_CTL_OVR, 1); + b43_phy_mask(dev, B43_LPPHY_AFE_CTL_OVRVAL, 0xFFFE); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x800); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, 0x800); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x8); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, 0x8); + b43_radio_write(dev, B2062_N_TX_CTL_A, 0x80); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x80); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, 0x80); + for (i = 0; i < 32; i++) { + lpphy_set_rx_gain_by_index(dev, i); + lpphy_run_ddfs(dev, 1, 1, 5, 5, 0); + if (!(lpphy_rx_iq_est(dev, 1000, 32, &iq_est))) + continue; + tmp = (iq_est.i_pwr + iq_est.q_pwr) / 1000; + if ((tmp > 4000) && (tmp < 10000)) { + index = i; + break; + } + } + lpphy_stop_ddfs(dev); + return index; +} + +/* Fixed-point division algorithm using only integer math. */ +static u32 lpphy_qdiv_roundup(u32 dividend, u32 divisor, u8 precision) +{ + u32 quotient, remainder; + + if (divisor == 0) + return 0; + + quotient = dividend / divisor; + remainder = dividend % divisor; + + while (precision > 0) { + quotient <<= 1; + if (remainder << 1 >= divisor) { + quotient++; + remainder = (remainder << 1) - divisor; + } + precision--; + } + + if (remainder << 1 >= divisor) + quotient++; + + return quotient; +} + +/* Read the TX power control mode from hardware. */ +static void lpphy_read_tx_pctl_mode_from_hardware(struct b43_wldev *dev) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + u16 ctl; + + ctl = b43_phy_read(dev, B43_LPPHY_TX_PWR_CTL_CMD); + switch (ctl & B43_LPPHY_TX_PWR_CTL_CMD_MODE) { + case B43_LPPHY_TX_PWR_CTL_CMD_MODE_OFF: + lpphy->txpctl_mode = B43_LPPHY_TXPCTL_OFF; + break; + case B43_LPPHY_TX_PWR_CTL_CMD_MODE_SW: + lpphy->txpctl_mode = B43_LPPHY_TXPCTL_SW; + break; + case B43_LPPHY_TX_PWR_CTL_CMD_MODE_HW: + lpphy->txpctl_mode = B43_LPPHY_TXPCTL_HW; + break; + default: + lpphy->txpctl_mode = B43_LPPHY_TXPCTL_UNKNOWN; + B43_WARN_ON(1); + break; + } +} + +/* Set the TX power control mode in hardware. */ +static void lpphy_write_tx_pctl_mode_to_hardware(struct b43_wldev *dev) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + u16 ctl; + + switch (lpphy->txpctl_mode) { + case B43_LPPHY_TXPCTL_OFF: + ctl = B43_LPPHY_TX_PWR_CTL_CMD_MODE_OFF; + break; + case B43_LPPHY_TXPCTL_HW: + ctl = B43_LPPHY_TX_PWR_CTL_CMD_MODE_HW; + break; + case B43_LPPHY_TXPCTL_SW: + ctl = B43_LPPHY_TX_PWR_CTL_CMD_MODE_SW; + break; + default: + ctl = 0; + B43_WARN_ON(1); + } + b43_phy_maskset(dev, B43_LPPHY_TX_PWR_CTL_CMD, + ~B43_LPPHY_TX_PWR_CTL_CMD_MODE & 0xFFFF, ctl); +} + +static void lpphy_set_tx_power_control(struct b43_wldev *dev, + enum b43_lpphy_txpctl_mode mode) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + enum b43_lpphy_txpctl_mode oldmode; + + lpphy_read_tx_pctl_mode_from_hardware(dev); + oldmode = lpphy->txpctl_mode; + if (oldmode == mode) + return; + lpphy->txpctl_mode = mode; + + if (oldmode == B43_LPPHY_TXPCTL_HW) { + //TODO Update TX Power NPT + //TODO Clear all TX Power offsets + } else { + if (mode == B43_LPPHY_TXPCTL_HW) { + //TODO Recalculate target TX power + b43_phy_maskset(dev, B43_LPPHY_TX_PWR_CTL_CMD, + 0xFF80, lpphy->tssi_idx); + b43_phy_maskset(dev, B43_LPPHY_TX_PWR_CTL_NNUM, + 0x8FFF, ((u16)lpphy->tssi_npt << 16)); + //TODO Set "TSSI Transmit Count" variable to total transmitted frame count + lpphy_disable_tx_gain_override(dev); + lpphy->tx_pwr_idx_over = -1; + } + } + if (dev->phy.rev >= 2) { + if (mode == B43_LPPHY_TXPCTL_HW) + b43_phy_set(dev, B43_PHY_OFDM(0xD0), 0x2); + else + b43_phy_mask(dev, B43_PHY_OFDM(0xD0), 0xFFFD); + } + lpphy_write_tx_pctl_mode_to_hardware(dev); +} + +static int b43_lpphy_op_switch_channel(struct b43_wldev *dev, + unsigned int new_channel); + +static void lpphy_rev0_1_rc_calib(struct b43_wldev *dev) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + struct lpphy_iq_est iq_est; + struct lpphy_tx_gains tx_gains; + static const u32 ideal_pwr_table[21] = { + 0x10000, 0x10557, 0x10e2d, 0x113e0, 0x10f22, 0x0ff64, + 0x0eda2, 0x0e5d4, 0x0efd1, 0x0fbe8, 0x0b7b8, 0x04b35, + 0x01a5e, 0x00a0b, 0x00444, 0x001fd, 0x000ff, 0x00088, + 0x0004c, 0x0002c, 0x0001a, + }; + bool old_txg_ovr; + u8 old_bbmult; + u16 old_rf_ovr, old_rf_ovrval, old_afe_ovr, old_afe_ovrval, + old_rf2_ovr, old_rf2_ovrval, old_phy_ctl; + enum b43_lpphy_txpctl_mode old_txpctl; + u32 normal_pwr, ideal_pwr, mean_sq_pwr, tmp = 0, mean_sq_pwr_min = 0; + int loopback, i, j, inner_sum, err; + + memset(&iq_est, 0, sizeof(iq_est)); + + err = b43_lpphy_op_switch_channel(dev, 7); + if (err) { + b43dbg(dev->wl, + "RC calib: Failed to switch to channel 7, error = %d\n", + err); + } + old_txg_ovr = !!(b43_phy_read(dev, B43_LPPHY_AFE_CTL_OVR) & 0x40); + old_bbmult = lpphy_get_bb_mult(dev); + if (old_txg_ovr) + tx_gains = lpphy_get_tx_gains(dev); + old_rf_ovr = b43_phy_read(dev, B43_LPPHY_RF_OVERRIDE_0); + old_rf_ovrval = b43_phy_read(dev, B43_LPPHY_RF_OVERRIDE_VAL_0); + old_afe_ovr = b43_phy_read(dev, B43_LPPHY_AFE_CTL_OVR); + old_afe_ovrval = b43_phy_read(dev, B43_LPPHY_AFE_CTL_OVRVAL); + old_rf2_ovr = b43_phy_read(dev, B43_LPPHY_RF_OVERRIDE_2); + old_rf2_ovrval = b43_phy_read(dev, B43_LPPHY_RF_OVERRIDE_2_VAL); + old_phy_ctl = b43_phy_read(dev, B43_LPPHY_LP_PHY_CTL); + lpphy_read_tx_pctl_mode_from_hardware(dev); + old_txpctl = lpphy->txpctl_mode; + + lpphy_set_tx_power_control(dev, B43_LPPHY_TXPCTL_OFF); + lpphy_disable_crs(dev, true); + loopback = lpphy_loopback(dev); + if (loopback == -1) + goto finish; + lpphy_set_rx_gain_by_index(dev, loopback); + b43_phy_maskset(dev, B43_LPPHY_LP_PHY_CTL, 0xFFBF, 0x40); + b43_phy_maskset(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, 0xFFF8, 0x1); + b43_phy_maskset(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, 0xFFC7, 0x8); + b43_phy_maskset(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, 0xFF3F, 0xC0); + for (i = 128; i <= 159; i++) { + b43_radio_write(dev, B2062_N_RXBB_CALIB2, i); + inner_sum = 0; + for (j = 5; j <= 25; j++) { + lpphy_run_ddfs(dev, 1, 1, j, j, 0); + if (!(lpphy_rx_iq_est(dev, 1000, 32, &iq_est))) + goto finish; + mean_sq_pwr = iq_est.i_pwr + iq_est.q_pwr; + if (j == 5) + tmp = mean_sq_pwr; + ideal_pwr = ((ideal_pwr_table[j-5] >> 3) + 1) >> 1; + normal_pwr = lpphy_qdiv_roundup(mean_sq_pwr, tmp, 12); + mean_sq_pwr = ideal_pwr - normal_pwr; + mean_sq_pwr *= mean_sq_pwr; + inner_sum += mean_sq_pwr; + if ((i == 128) || (inner_sum < mean_sq_pwr_min)) { + lpphy->rc_cap = i; + mean_sq_pwr_min = inner_sum; + } + } + } + lpphy_stop_ddfs(dev); + +finish: + lpphy_restore_crs(dev, true); + b43_phy_write(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, old_rf_ovrval); + b43_phy_write(dev, B43_LPPHY_RF_OVERRIDE_0, old_rf_ovr); + b43_phy_write(dev, B43_LPPHY_AFE_CTL_OVRVAL, old_afe_ovrval); + b43_phy_write(dev, B43_LPPHY_AFE_CTL_OVR, old_afe_ovr); + b43_phy_write(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, old_rf2_ovrval); + b43_phy_write(dev, B43_LPPHY_RF_OVERRIDE_2, old_rf2_ovr); + b43_phy_write(dev, B43_LPPHY_LP_PHY_CTL, old_phy_ctl); + + lpphy_set_bb_mult(dev, old_bbmult); + if (old_txg_ovr) { + /* + * SPEC FIXME: The specs say "get_tx_gains" here, which is + * illogical. According to lwfinger, vendor driver v4.150.10.5 + * has a Set here, while v4.174.64.19 has a Get - regression in + * the vendor driver? This should be tested this once the code + * is testable. + */ + lpphy_set_tx_gains(dev, tx_gains); + } + lpphy_set_tx_power_control(dev, old_txpctl); + if (lpphy->rc_cap) + lpphy_set_rc_cap(dev); +} + +static void lpphy_rev2plus_rc_calib(struct b43_wldev *dev) +{ + struct ssb_bus *bus = dev->dev->sdev->bus; + u32 crystal_freq = bus->chipco.pmu.crystalfreq * 1000; + u8 tmp = b43_radio_read(dev, B2063_RX_BB_SP8) & 0xFF; + int i; + + b43_radio_write(dev, B2063_RX_BB_SP8, 0x0); + b43_radio_write(dev, B2063_RC_CALIB_CTL1, 0x7E); + b43_radio_mask(dev, B2063_PLL_SP1, 0xF7); + b43_radio_write(dev, B2063_RC_CALIB_CTL1, 0x7C); + b43_radio_write(dev, B2063_RC_CALIB_CTL2, 0x15); + b43_radio_write(dev, B2063_RC_CALIB_CTL3, 0x70); + b43_radio_write(dev, B2063_RC_CALIB_CTL4, 0x52); + b43_radio_write(dev, B2063_RC_CALIB_CTL5, 0x1); + b43_radio_write(dev, B2063_RC_CALIB_CTL1, 0x7D); + + for (i = 0; i < 10000; i++) { + if (b43_radio_read(dev, B2063_RC_CALIB_CTL6) & 0x2) + break; + msleep(1); + } + + if (!(b43_radio_read(dev, B2063_RC_CALIB_CTL6) & 0x2)) + b43_radio_write(dev, B2063_RX_BB_SP8, tmp); + + tmp = b43_radio_read(dev, B2063_TX_BB_SP3) & 0xFF; + + b43_radio_write(dev, B2063_TX_BB_SP3, 0x0); + b43_radio_write(dev, B2063_RC_CALIB_CTL1, 0x7E); + b43_radio_write(dev, B2063_RC_CALIB_CTL1, 0x7C); + b43_radio_write(dev, B2063_RC_CALIB_CTL2, 0x55); + b43_radio_write(dev, B2063_RC_CALIB_CTL3, 0x76); + + if (crystal_freq == 24000000) { + b43_radio_write(dev, B2063_RC_CALIB_CTL4, 0xFC); + b43_radio_write(dev, B2063_RC_CALIB_CTL5, 0x0); + } else { + b43_radio_write(dev, B2063_RC_CALIB_CTL4, 0x13); + b43_radio_write(dev, B2063_RC_CALIB_CTL5, 0x1); + } + + b43_radio_write(dev, B2063_PA_SP7, 0x7D); + + for (i = 0; i < 10000; i++) { + if (b43_radio_read(dev, B2063_RC_CALIB_CTL6) & 0x2) + break; + msleep(1); + } + + if (!(b43_radio_read(dev, B2063_RC_CALIB_CTL6) & 0x2)) + b43_radio_write(dev, B2063_TX_BB_SP3, tmp); + + b43_radio_write(dev, B2063_RC_CALIB_CTL1, 0x7E); +} + +static void lpphy_calibrate_rc(struct b43_wldev *dev) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + + if (dev->phy.rev >= 2) { + lpphy_rev2plus_rc_calib(dev); + } else if (!lpphy->rc_cap) { + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + lpphy_rev0_1_rc_calib(dev); + } else { + lpphy_set_rc_cap(dev); + } +} + +static void b43_lpphy_op_set_rx_antenna(struct b43_wldev *dev, int antenna) +{ + if (dev->phy.rev >= 2) + return; // rev2+ doesn't support antenna diversity + + if (B43_WARN_ON(antenna > B43_ANTENNA_AUTO1)) + return; + + b43_hf_write(dev, b43_hf_read(dev) & ~B43_HF_ANTDIVHELP); + + b43_phy_maskset(dev, B43_LPPHY_CRSGAIN_CTL, 0xFFFD, antenna & 0x2); + b43_phy_maskset(dev, B43_LPPHY_CRSGAIN_CTL, 0xFFFE, antenna & 0x1); + + b43_hf_write(dev, b43_hf_read(dev) | B43_HF_ANTDIVHELP); + + dev->phy.lp->antenna = antenna; +} + +static void lpphy_set_tx_iqcc(struct b43_wldev *dev, u16 a, u16 b) +{ + u16 tmp[2]; + + tmp[0] = a; + tmp[1] = b; + b43_lptab_write_bulk(dev, B43_LPTAB16(0, 80), 2, tmp); +} + +static void lpphy_set_tx_power_by_index(struct b43_wldev *dev, u8 index) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + struct lpphy_tx_gains gains; + u32 iq_comp, tx_gain, coeff, rf_power; + + lpphy->tx_pwr_idx_over = index; + lpphy_read_tx_pctl_mode_from_hardware(dev); + if (lpphy->txpctl_mode != B43_LPPHY_TXPCTL_OFF) + lpphy_set_tx_power_control(dev, B43_LPPHY_TXPCTL_SW); + if (dev->phy.rev >= 2) { + iq_comp = b43_lptab_read(dev, B43_LPTAB32(7, index + 320)); + tx_gain = b43_lptab_read(dev, B43_LPTAB32(7, index + 192)); + gains.pad = (tx_gain >> 16) & 0xFF; + gains.gm = tx_gain & 0xFF; + gains.pga = (tx_gain >> 8) & 0xFF; + gains.dac = (iq_comp >> 28) & 0xFF; + lpphy_set_tx_gains(dev, gains); + } else { + iq_comp = b43_lptab_read(dev, B43_LPTAB32(10, index + 320)); + tx_gain = b43_lptab_read(dev, B43_LPTAB32(10, index + 192)); + b43_phy_maskset(dev, B43_LPPHY_TX_GAIN_CTL_OVERRIDE_VAL, + 0xF800, (tx_gain >> 4) & 0x7FFF); + lpphy_set_dac_gain(dev, tx_gain & 0x7); + lpphy_set_pa_gain(dev, (tx_gain >> 24) & 0x7F); + } + lpphy_set_bb_mult(dev, (iq_comp >> 20) & 0xFF); + lpphy_set_tx_iqcc(dev, (iq_comp >> 10) & 0x3FF, iq_comp & 0x3FF); + if (dev->phy.rev >= 2) { + coeff = b43_lptab_read(dev, B43_LPTAB32(7, index + 448)); + } else { + coeff = b43_lptab_read(dev, B43_LPTAB32(10, index + 448)); + } + b43_lptab_write(dev, B43_LPTAB16(0, 85), coeff & 0xFFFF); + if (dev->phy.rev >= 2) { + rf_power = b43_lptab_read(dev, B43_LPTAB32(7, index + 576)); + b43_phy_maskset(dev, B43_LPPHY_RF_PWR_OVERRIDE, 0xFF00, + rf_power & 0xFFFF);//SPEC FIXME mask & set != 0 + } + lpphy_enable_tx_gain_override(dev); +} + +static void lpphy_btcoex_override(struct b43_wldev *dev) +{ + b43_write16(dev, B43_MMIO_BTCOEX_CTL, 0x3); + b43_write16(dev, B43_MMIO_BTCOEX_TXCTL, 0xFF); +} + +static void b43_lpphy_op_software_rfkill(struct b43_wldev *dev, + bool blocked) +{ + //TODO check MAC control register + if (blocked) { + if (dev->phy.rev >= 2) { + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, 0x83FF); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x1F00); + b43_phy_mask(dev, B43_LPPHY_AFE_DDFS, 0x80FF); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, 0xDFFF); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_2, 0x0808); + } else { + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, 0xE0FF); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x1F00); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_2_VAL, 0xFCFF); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_2, 0x0018); + } + } else { + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_0, 0xE0FF); + if (dev->phy.rev >= 2) + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_2, 0xF7F7); + else + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_2, 0xFFE7); + } +} + +/* This was previously called lpphy_japan_filter */ +static void lpphy_set_analog_filter(struct b43_wldev *dev, int channel) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + u16 tmp = (channel == 14); //SPEC FIXME check japanwidefilter! + + if (dev->phy.rev < 2) { //SPEC FIXME Isn't this rev0/1-specific? + b43_phy_maskset(dev, B43_LPPHY_LP_PHY_CTL, 0xFCFF, tmp << 9); + if ((dev->phy.rev == 1) && (lpphy->rc_cap)) + lpphy_set_rc_cap(dev); + } else { + b43_radio_write(dev, B2063_TX_BB_SP3, 0x3F); + } +} + +static void lpphy_set_tssi_mux(struct b43_wldev *dev, enum tssi_mux_mode mode) +{ + if (mode != TSSI_MUX_EXT) { + b43_radio_set(dev, B2063_PA_SP1, 0x2); + b43_phy_set(dev, B43_PHY_OFDM(0xF3), 0x1000); + b43_radio_write(dev, B2063_PA_CTL10, 0x51); + if (mode == TSSI_MUX_POSTPA) { + b43_radio_mask(dev, B2063_PA_SP1, 0xFFFE); + b43_phy_mask(dev, B43_LPPHY_AFE_CTL_OVRVAL, 0xFFC7); + } else { + b43_radio_maskset(dev, B2063_PA_SP1, 0xFFFE, 0x1); + b43_phy_maskset(dev, B43_LPPHY_AFE_CTL_OVRVAL, + 0xFFC7, 0x20); + } + } else { + B43_WARN_ON(1); + } +} + +static void lpphy_tx_pctl_init_hw(struct b43_wldev *dev) +{ + u16 tmp; + int i; + + //SPEC TODO Call LP PHY Clear TX Power offsets + for (i = 0; i < 64; i++) { + if (dev->phy.rev >= 2) + b43_lptab_write(dev, B43_LPTAB32(7, i + 1), i); + else + b43_lptab_write(dev, B43_LPTAB32(10, i + 1), i); + } + + b43_phy_maskset(dev, B43_LPPHY_TX_PWR_CTL_NNUM, 0xFF00, 0xFF); + b43_phy_maskset(dev, B43_LPPHY_TX_PWR_CTL_NNUM, 0x8FFF, 0x5000); + b43_phy_maskset(dev, B43_LPPHY_TX_PWR_CTL_IDLETSSI, 0xFFC0, 0x1F); + if (dev->phy.rev < 2) { + b43_phy_mask(dev, B43_LPPHY_LP_PHY_CTL, 0xEFFF); + b43_phy_maskset(dev, B43_LPPHY_LP_PHY_CTL, 0xDFFF, 0x2000); + } else { + b43_phy_mask(dev, B43_PHY_OFDM(0x103), 0xFFFE); + b43_phy_maskset(dev, B43_PHY_OFDM(0x103), 0xFFFB, 0x4); + b43_phy_maskset(dev, B43_PHY_OFDM(0x103), 0xFFEF, 0x10); + b43_radio_maskset(dev, B2063_IQ_CALIB_CTL2, 0xF3, 0x1); + lpphy_set_tssi_mux(dev, TSSI_MUX_POSTPA); + } + b43_phy_maskset(dev, B43_LPPHY_TX_PWR_CTL_IDLETSSI, 0x7FFF, 0x8000); + b43_phy_mask(dev, B43_LPPHY_TX_PWR_CTL_DELTAPWR_LIMIT, 0xFF); + b43_phy_write(dev, B43_LPPHY_TX_PWR_CTL_DELTAPWR_LIMIT, 0xA); + b43_phy_maskset(dev, B43_LPPHY_TX_PWR_CTL_CMD, + ~B43_LPPHY_TX_PWR_CTL_CMD_MODE & 0xFFFF, + B43_LPPHY_TX_PWR_CTL_CMD_MODE_OFF); + b43_phy_mask(dev, B43_LPPHY_TX_PWR_CTL_NNUM, 0xF8FF); + b43_phy_maskset(dev, B43_LPPHY_TX_PWR_CTL_CMD, + ~B43_LPPHY_TX_PWR_CTL_CMD_MODE & 0xFFFF, + B43_LPPHY_TX_PWR_CTL_CMD_MODE_SW); + + if (dev->phy.rev < 2) { + b43_phy_maskset(dev, B43_LPPHY_RF_OVERRIDE_0, 0xEFFF, 0x1000); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, 0xEFFF); + } else { + lpphy_set_tx_power_by_index(dev, 0x7F); + } + + b43_dummy_transmission(dev, true, true); + + tmp = b43_phy_read(dev, B43_LPPHY_TX_PWR_CTL_STAT); + if (tmp & 0x8000) { + b43_phy_maskset(dev, B43_LPPHY_TX_PWR_CTL_IDLETSSI, + 0xFFC0, (tmp & 0xFF) - 32); + } + + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_0, 0xEFFF); + + // (SPEC?) TODO Set "Target TX frequency" variable to 0 + // SPEC FIXME "Set BB Multiplier to 0xE000" impossible - bb_mult is u8! +} + +static void lpphy_tx_pctl_init_sw(struct b43_wldev *dev) +{ + struct lpphy_tx_gains gains; + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + gains.gm = 4; + gains.pad = 12; + gains.pga = 12; + gains.dac = 0; + } else { + gains.gm = 7; + gains.pad = 14; + gains.pga = 15; + gains.dac = 0; + } + lpphy_set_tx_gains(dev, gains); + lpphy_set_bb_mult(dev, 150); +} + +/* Initialize TX power control */ +static void lpphy_tx_pctl_init(struct b43_wldev *dev) +{ + if (0/*FIXME HWPCTL capable */) { + lpphy_tx_pctl_init_hw(dev); + } else { /* This device is only software TX power control capable. */ + lpphy_tx_pctl_init_sw(dev); + } +} + +static void lpphy_pr41573_workaround(struct b43_wldev *dev) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + u32 *saved_tab; + const unsigned int saved_tab_size = 256; + enum b43_lpphy_txpctl_mode txpctl_mode; + s8 tx_pwr_idx_over; + u16 tssi_npt, tssi_idx; + + saved_tab = kcalloc(saved_tab_size, sizeof(saved_tab[0]), GFP_KERNEL); + if (!saved_tab) { + b43err(dev->wl, "PR41573 failed. Out of memory!\n"); + return; + } + + lpphy_read_tx_pctl_mode_from_hardware(dev); + txpctl_mode = lpphy->txpctl_mode; + tx_pwr_idx_over = lpphy->tx_pwr_idx_over; + tssi_npt = lpphy->tssi_npt; + tssi_idx = lpphy->tssi_idx; + + if (dev->phy.rev < 2) { + b43_lptab_read_bulk(dev, B43_LPTAB32(10, 0x140), + saved_tab_size, saved_tab); + } else { + b43_lptab_read_bulk(dev, B43_LPTAB32(7, 0x140), + saved_tab_size, saved_tab); + } + //FIXME PHY reset + lpphy_table_init(dev); //FIXME is table init needed? + lpphy_baseband_init(dev); + lpphy_tx_pctl_init(dev); + b43_lpphy_op_software_rfkill(dev, false); + lpphy_set_tx_power_control(dev, B43_LPPHY_TXPCTL_OFF); + if (dev->phy.rev < 2) { + b43_lptab_write_bulk(dev, B43_LPTAB32(10, 0x140), + saved_tab_size, saved_tab); + } else { + b43_lptab_write_bulk(dev, B43_LPTAB32(7, 0x140), + saved_tab_size, saved_tab); + } + b43_write16(dev, B43_MMIO_CHANNEL, lpphy->channel); + lpphy->tssi_npt = tssi_npt; + lpphy->tssi_idx = tssi_idx; + lpphy_set_analog_filter(dev, lpphy->channel); + if (tx_pwr_idx_over != -1) + lpphy_set_tx_power_by_index(dev, tx_pwr_idx_over); + if (lpphy->rc_cap) + lpphy_set_rc_cap(dev); + b43_lpphy_op_set_rx_antenna(dev, lpphy->antenna); + lpphy_set_tx_power_control(dev, txpctl_mode); + kfree(saved_tab); +} + +struct lpphy_rx_iq_comp { u8 chan; s8 c1, c0; }; + +static const struct lpphy_rx_iq_comp lpphy_5354_iq_table[] = { + { .chan = 1, .c1 = -66, .c0 = 15, }, + { .chan = 2, .c1 = -66, .c0 = 15, }, + { .chan = 3, .c1 = -66, .c0 = 15, }, + { .chan = 4, .c1 = -66, .c0 = 15, }, + { .chan = 5, .c1 = -66, .c0 = 15, }, + { .chan = 6, .c1 = -66, .c0 = 15, }, + { .chan = 7, .c1 = -66, .c0 = 14, }, + { .chan = 8, .c1 = -66, .c0 = 14, }, + { .chan = 9, .c1 = -66, .c0 = 14, }, + { .chan = 10, .c1 = -66, .c0 = 14, }, + { .chan = 11, .c1 = -66, .c0 = 14, }, + { .chan = 12, .c1 = -66, .c0 = 13, }, + { .chan = 13, .c1 = -66, .c0 = 13, }, + { .chan = 14, .c1 = -66, .c0 = 13, }, +}; + +static const struct lpphy_rx_iq_comp lpphy_rev0_1_iq_table[] = { + { .chan = 1, .c1 = -64, .c0 = 13, }, + { .chan = 2, .c1 = -64, .c0 = 13, }, + { .chan = 3, .c1 = -64, .c0 = 13, }, + { .chan = 4, .c1 = -64, .c0 = 13, }, + { .chan = 5, .c1 = -64, .c0 = 12, }, + { .chan = 6, .c1 = -64, .c0 = 12, }, + { .chan = 7, .c1 = -64, .c0 = 12, }, + { .chan = 8, .c1 = -64, .c0 = 12, }, + { .chan = 9, .c1 = -64, .c0 = 12, }, + { .chan = 10, .c1 = -64, .c0 = 11, }, + { .chan = 11, .c1 = -64, .c0 = 11, }, + { .chan = 12, .c1 = -64, .c0 = 11, }, + { .chan = 13, .c1 = -64, .c0 = 11, }, + { .chan = 14, .c1 = -64, .c0 = 10, }, + { .chan = 34, .c1 = -62, .c0 = 24, }, + { .chan = 38, .c1 = -62, .c0 = 24, }, + { .chan = 42, .c1 = -62, .c0 = 24, }, + { .chan = 46, .c1 = -62, .c0 = 23, }, + { .chan = 36, .c1 = -62, .c0 = 24, }, + { .chan = 40, .c1 = -62, .c0 = 24, }, + { .chan = 44, .c1 = -62, .c0 = 23, }, + { .chan = 48, .c1 = -62, .c0 = 23, }, + { .chan = 52, .c1 = -62, .c0 = 23, }, + { .chan = 56, .c1 = -62, .c0 = 22, }, + { .chan = 60, .c1 = -62, .c0 = 22, }, + { .chan = 64, .c1 = -62, .c0 = 22, }, + { .chan = 100, .c1 = -62, .c0 = 16, }, + { .chan = 104, .c1 = -62, .c0 = 16, }, + { .chan = 108, .c1 = -62, .c0 = 15, }, + { .chan = 112, .c1 = -62, .c0 = 14, }, + { .chan = 116, .c1 = -62, .c0 = 14, }, + { .chan = 120, .c1 = -62, .c0 = 13, }, + { .chan = 124, .c1 = -62, .c0 = 12, }, + { .chan = 128, .c1 = -62, .c0 = 12, }, + { .chan = 132, .c1 = -62, .c0 = 12, }, + { .chan = 136, .c1 = -62, .c0 = 11, }, + { .chan = 140, .c1 = -62, .c0 = 10, }, + { .chan = 149, .c1 = -61, .c0 = 9, }, + { .chan = 153, .c1 = -61, .c0 = 9, }, + { .chan = 157, .c1 = -61, .c0 = 9, }, + { .chan = 161, .c1 = -61, .c0 = 8, }, + { .chan = 165, .c1 = -61, .c0 = 8, }, + { .chan = 184, .c1 = -62, .c0 = 25, }, + { .chan = 188, .c1 = -62, .c0 = 25, }, + { .chan = 192, .c1 = -62, .c0 = 25, }, + { .chan = 196, .c1 = -62, .c0 = 25, }, + { .chan = 200, .c1 = -62, .c0 = 25, }, + { .chan = 204, .c1 = -62, .c0 = 25, }, + { .chan = 208, .c1 = -62, .c0 = 25, }, + { .chan = 212, .c1 = -62, .c0 = 25, }, + { .chan = 216, .c1 = -62, .c0 = 26, }, +}; + +static const struct lpphy_rx_iq_comp lpphy_rev2plus_iq_comp = { + .chan = 0, + .c1 = -64, + .c0 = 0, +}; + +static int lpphy_calc_rx_iq_comp(struct b43_wldev *dev, u16 samples) +{ + struct lpphy_iq_est iq_est; + u16 c0, c1; + int prod, ipwr, qpwr, prod_msb, q_msb, tmp1, tmp2, tmp3, tmp4, ret; + + c1 = b43_phy_read(dev, B43_LPPHY_RX_COMP_COEFF_S); + c0 = c1 >> 8; + c1 |= 0xFF; + + b43_phy_maskset(dev, B43_LPPHY_RX_COMP_COEFF_S, 0xFF00, 0x00C0); + b43_phy_mask(dev, B43_LPPHY_RX_COMP_COEFF_S, 0x00FF); + + ret = lpphy_rx_iq_est(dev, samples, 32, &iq_est); + if (!ret) + goto out; + + prod = iq_est.iq_prod; + ipwr = iq_est.i_pwr; + qpwr = iq_est.q_pwr; + + if (ipwr + qpwr < 2) { + ret = 0; + goto out; + } + + prod_msb = fls(abs(prod)); + q_msb = fls(abs(qpwr)); + tmp1 = prod_msb - 20; + + if (tmp1 >= 0) { + tmp3 = ((prod << (30 - prod_msb)) + (ipwr >> (1 + tmp1))) / + (ipwr >> tmp1); + } else { + tmp3 = ((prod << (30 - prod_msb)) + (ipwr << (-1 - tmp1))) / + (ipwr << -tmp1); + } + + tmp2 = q_msb - 11; + + if (tmp2 >= 0) + tmp4 = (qpwr << (31 - q_msb)) / (ipwr >> tmp2); + else + tmp4 = (qpwr << (31 - q_msb)) / (ipwr << -tmp2); + + tmp4 -= tmp3 * tmp3; + tmp4 = -int_sqrt(tmp4); + + c0 = tmp3 >> 3; + c1 = tmp4 >> 4; + +out: + b43_phy_maskset(dev, B43_LPPHY_RX_COMP_COEFF_S, 0xFF00, c1); + b43_phy_maskset(dev, B43_LPPHY_RX_COMP_COEFF_S, 0x00FF, c0 << 8); + return ret; +} + +static void lpphy_run_samples(struct b43_wldev *dev, u16 samples, u16 loops, + u16 wait) +{ + b43_phy_maskset(dev, B43_LPPHY_SMPL_PLAY_BUFFER_CTL, + 0xFFC0, samples - 1); + if (loops != 0xFFFF) + loops--; + b43_phy_maskset(dev, B43_LPPHY_SMPL_PLAY_COUNT, 0xF000, loops); + b43_phy_maskset(dev, B43_LPPHY_SMPL_PLAY_BUFFER_CTL, 0x3F, wait << 6); + b43_phy_set(dev, B43_LPPHY_A_PHY_CTL_ADDR, 0x1); +} + +//SPEC FIXME what does a negative freq mean? +static void lpphy_start_tx_tone(struct b43_wldev *dev, s32 freq, u16 max) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + u16 buf[64]; + int i, samples = 0, angle = 0; + int rotation = (((36 * freq) / 20) << 16) / 100; + struct b43_c32 sample; + + lpphy->tx_tone_freq = freq; + + if (freq) { + /* Find i for which abs(freq) integrally divides 20000 * i */ + for (i = 1; samples * abs(freq) != 20000 * i; i++) { + samples = (20000 * i) / abs(freq); + if(B43_WARN_ON(samples > 63)) + return; + } + } else { + samples = 2; + } + + for (i = 0; i < samples; i++) { + sample = b43_cordic(angle); + angle += rotation; + buf[i] = CORDIC_CONVERT((sample.i * max) & 0xFF) << 8; + buf[i] |= CORDIC_CONVERT((sample.q * max) & 0xFF); + } + + b43_lptab_write_bulk(dev, B43_LPTAB16(5, 0), samples, buf); + + lpphy_run_samples(dev, samples, 0xFFFF, 0); +} + +static void lpphy_stop_tx_tone(struct b43_wldev *dev) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + int i; + + lpphy->tx_tone_freq = 0; + + b43_phy_mask(dev, B43_LPPHY_SMPL_PLAY_COUNT, 0xF000); + for (i = 0; i < 31; i++) { + if (!(b43_phy_read(dev, B43_LPPHY_A_PHY_CTL_ADDR) & 0x1)) + break; + udelay(100); + } +} + + +static void lpphy_papd_cal(struct b43_wldev *dev, struct lpphy_tx_gains gains, + int mode, bool useindex, u8 index) +{ + //TODO +} + +static void lpphy_papd_cal_txpwr(struct b43_wldev *dev) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + struct lpphy_tx_gains gains, oldgains; + int old_txpctl, old_afe_ovr, old_rf, old_bbmult; + + lpphy_read_tx_pctl_mode_from_hardware(dev); + old_txpctl = lpphy->txpctl_mode; + old_afe_ovr = b43_phy_read(dev, B43_LPPHY_AFE_CTL_OVR) & 0x40; + if (old_afe_ovr) + oldgains = lpphy_get_tx_gains(dev); + old_rf = b43_phy_read(dev, B43_LPPHY_RF_PWR_OVERRIDE) & 0xFF; + old_bbmult = lpphy_get_bb_mult(dev); + + lpphy_set_tx_power_control(dev, B43_LPPHY_TXPCTL_OFF); + + if (dev->dev->chip_id == 0x4325 && dev->dev->chip_rev == 0) + lpphy_papd_cal(dev, gains, 0, 1, 30); + else + lpphy_papd_cal(dev, gains, 0, 1, 65); + + if (old_afe_ovr) + lpphy_set_tx_gains(dev, oldgains); + lpphy_set_bb_mult(dev, old_bbmult); + lpphy_set_tx_power_control(dev, old_txpctl); + b43_phy_maskset(dev, B43_LPPHY_RF_PWR_OVERRIDE, 0xFF00, old_rf); +} + +static int lpphy_rx_iq_cal(struct b43_wldev *dev, bool noise, bool tx, + bool rx, bool pa, struct lpphy_tx_gains *gains) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + const struct lpphy_rx_iq_comp *iqcomp = NULL; + struct lpphy_tx_gains nogains, oldgains; + u16 tmp; + int i, ret; + + memset(&nogains, 0, sizeof(nogains)); + memset(&oldgains, 0, sizeof(oldgains)); + + if (dev->dev->chip_id == 0x5354) { + for (i = 0; i < ARRAY_SIZE(lpphy_5354_iq_table); i++) { + if (lpphy_5354_iq_table[i].chan == lpphy->channel) { + iqcomp = &lpphy_5354_iq_table[i]; + } + } + } else if (dev->phy.rev >= 2) { + iqcomp = &lpphy_rev2plus_iq_comp; + } else { + for (i = 0; i < ARRAY_SIZE(lpphy_rev0_1_iq_table); i++) { + if (lpphy_rev0_1_iq_table[i].chan == lpphy->channel) { + iqcomp = &lpphy_rev0_1_iq_table[i]; + } + } + } + + if (B43_WARN_ON(!iqcomp)) + return 0; + + b43_phy_maskset(dev, B43_LPPHY_RX_COMP_COEFF_S, 0xFF00, iqcomp->c1); + b43_phy_maskset(dev, B43_LPPHY_RX_COMP_COEFF_S, + 0x00FF, iqcomp->c0 << 8); + + if (noise) { + tx = true; + rx = false; + pa = false; + } + + lpphy_set_trsw_over(dev, tx, rx); + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x8); + b43_phy_maskset(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, + 0xFFF7, pa << 3); + } else { + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x20); + b43_phy_maskset(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, + 0xFFDF, pa << 5); + } + + tmp = b43_phy_read(dev, B43_LPPHY_AFE_CTL_OVR) & 0x40; + + if (noise) + lpphy_set_rx_gain(dev, 0x2D5D); + else { + if (tmp) + oldgains = lpphy_get_tx_gains(dev); + if (!gains) + gains = &nogains; + lpphy_set_tx_gains(dev, *gains); + } + + b43_phy_mask(dev, B43_LPPHY_AFE_CTL_OVR, 0xFFFE); + b43_phy_mask(dev, B43_LPPHY_AFE_CTL_OVRVAL, 0xFFFE); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_0, 0x800); + b43_phy_set(dev, B43_LPPHY_RF_OVERRIDE_VAL_0, 0x800); + lpphy_set_deaf(dev, false); + if (noise) + ret = lpphy_calc_rx_iq_comp(dev, 0xFFF0); + else { + lpphy_start_tx_tone(dev, 4000, 100); + ret = lpphy_calc_rx_iq_comp(dev, 0x4000); + lpphy_stop_tx_tone(dev); + } + lpphy_clear_deaf(dev, false); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_0, 0xFFFC); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_0, 0xFFF7); + b43_phy_mask(dev, B43_LPPHY_RF_OVERRIDE_0, 0xFFDF); + if (!noise) { + if (tmp) + lpphy_set_tx_gains(dev, oldgains); + else + lpphy_disable_tx_gain_override(dev); + } + lpphy_disable_rx_gain_override(dev); + b43_phy_mask(dev, B43_LPPHY_AFE_CTL_OVR, 0xFFFE); + b43_phy_mask(dev, B43_LPPHY_AFE_CTL_OVRVAL, 0xF7FF); + return ret; +} + +static void lpphy_calibration(struct b43_wldev *dev) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + enum b43_lpphy_txpctl_mode saved_pctl_mode; + bool full_cal = false; + + if (lpphy->full_calib_chan != lpphy->channel) { + full_cal = true; + lpphy->full_calib_chan = lpphy->channel; + } + + b43_mac_suspend(dev); + + lpphy_btcoex_override(dev); + if (dev->phy.rev >= 2) + lpphy_save_dig_flt_state(dev); + lpphy_read_tx_pctl_mode_from_hardware(dev); + saved_pctl_mode = lpphy->txpctl_mode; + lpphy_set_tx_power_control(dev, B43_LPPHY_TXPCTL_OFF); + //TODO Perform transmit power table I/Q LO calibration + if ((dev->phy.rev == 0) && (saved_pctl_mode != B43_LPPHY_TXPCTL_OFF)) + lpphy_pr41573_workaround(dev); + if ((dev->phy.rev >= 2) && full_cal) { + lpphy_papd_cal_txpwr(dev); + } + lpphy_set_tx_power_control(dev, saved_pctl_mode); + if (dev->phy.rev >= 2) + lpphy_restore_dig_flt_state(dev); + lpphy_rx_iq_cal(dev, true, true, false, false, NULL); + + b43_mac_enable(dev); +} + +static u16 b43_lpphy_op_read(struct b43_wldev *dev, u16 reg) +{ + b43_write16(dev, B43_MMIO_PHY_CONTROL, reg); + return b43_read16(dev, B43_MMIO_PHY_DATA); +} + +static void b43_lpphy_op_write(struct b43_wldev *dev, u16 reg, u16 value) +{ + b43_write16(dev, B43_MMIO_PHY_CONTROL, reg); + b43_write16(dev, B43_MMIO_PHY_DATA, value); +} + +static void b43_lpphy_op_maskset(struct b43_wldev *dev, u16 reg, u16 mask, + u16 set) +{ + b43_write16(dev, B43_MMIO_PHY_CONTROL, reg); + b43_write16(dev, B43_MMIO_PHY_DATA, + (b43_read16(dev, B43_MMIO_PHY_DATA) & mask) | set); +} + +static u16 b43_lpphy_op_radio_read(struct b43_wldev *dev, u16 reg) +{ + /* Register 1 is a 32-bit register. */ + B43_WARN_ON(reg == 1); + /* LP-PHY needs a special bit set for read access */ + if (dev->phy.rev < 2) { + if (reg != 0x4001) + reg |= 0x100; + } else + reg |= 0x200; + + b43_write16(dev, B43_MMIO_RADIO_CONTROL, reg); + return b43_read16(dev, B43_MMIO_RADIO_DATA_LOW); +} + +static void b43_lpphy_op_radio_write(struct b43_wldev *dev, u16 reg, u16 value) +{ + /* Register 1 is a 32-bit register. */ + B43_WARN_ON(reg == 1); + + b43_write16(dev, B43_MMIO_RADIO_CONTROL, reg); + b43_write16(dev, B43_MMIO_RADIO_DATA_LOW, value); +} + +struct b206x_channel { + u8 channel; + u16 freq; + u8 data[12]; +}; + +static const struct b206x_channel b2062_chantbl[] = { + { .channel = 1, .freq = 2412, .data[0] = 0xFF, .data[1] = 0xFF, + .data[2] = 0xB5, .data[3] = 0x1B, .data[4] = 0x24, .data[5] = 0x32, + .data[6] = 0x32, .data[7] = 0x88, .data[8] = 0x88, }, + { .channel = 2, .freq = 2417, .data[0] = 0xFF, .data[1] = 0xFF, + .data[2] = 0xB5, .data[3] = 0x1B, .data[4] = 0x24, .data[5] = 0x32, + .data[6] = 0x32, .data[7] = 0x88, .data[8] = 0x88, }, + { .channel = 3, .freq = 2422, .data[0] = 0xFF, .data[1] = 0xFF, + .data[2] = 0xB5, .data[3] = 0x1B, .data[4] = 0x24, .data[5] = 0x32, + .data[6] = 0x32, .data[7] = 0x88, .data[8] = 0x88, }, + { .channel = 4, .freq = 2427, .data[0] = 0xFF, .data[1] = 0xFF, + .data[2] = 0xB5, .data[3] = 0x1B, .data[4] = 0x24, .data[5] = 0x32, + .data[6] = 0x32, .data[7] = 0x88, .data[8] = 0x88, }, + { .channel = 5, .freq = 2432, .data[0] = 0xFF, .data[1] = 0xFF, + .data[2] = 0xB5, .data[3] = 0x1B, .data[4] = 0x24, .data[5] = 0x32, + .data[6] = 0x32, .data[7] = 0x88, .data[8] = 0x88, }, + { .channel = 6, .freq = 2437, .data[0] = 0xFF, .data[1] = 0xFF, + .data[2] = 0xB5, .data[3] = 0x1B, .data[4] = 0x24, .data[5] = 0x32, + .data[6] = 0x32, .data[7] = 0x88, .data[8] = 0x88, }, + { .channel = 7, .freq = 2442, .data[0] = 0xFF, .data[1] = 0xFF, + .data[2] = 0xB5, .data[3] = 0x1B, .data[4] = 0x24, .data[5] = 0x32, + .data[6] = 0x32, .data[7] = 0x88, .data[8] = 0x88, }, + { .channel = 8, .freq = 2447, .data[0] = 0xFF, .data[1] = 0xFF, + .data[2] = 0xB5, .data[3] = 0x1B, .data[4] = 0x24, .data[5] = 0x32, + .data[6] = 0x32, .data[7] = 0x88, .data[8] = 0x88, }, + { .channel = 9, .freq = 2452, .data[0] = 0xFF, .data[1] = 0xFF, + .data[2] = 0xB5, .data[3] = 0x1B, .data[4] = 0x24, .data[5] = 0x32, + .data[6] = 0x32, .data[7] = 0x88, .data[8] = 0x88, }, + { .channel = 10, .freq = 2457, .data[0] = 0xFF, .data[1] = 0xFF, + .data[2] = 0xB5, .data[3] = 0x1B, .data[4] = 0x24, .data[5] = 0x32, + .data[6] = 0x32, .data[7] = 0x88, .data[8] = 0x88, }, + { .channel = 11, .freq = 2462, .data[0] = 0xFF, .data[1] = 0xFF, + .data[2] = 0xB5, .data[3] = 0x1B, .data[4] = 0x24, .data[5] = 0x32, + .data[6] = 0x32, .data[7] = 0x88, .data[8] = 0x88, }, + { .channel = 12, .freq = 2467, .data[0] = 0xFF, .data[1] = 0xFF, + .data[2] = 0xB5, .data[3] = 0x1B, .data[4] = 0x24, .data[5] = 0x32, + .data[6] = 0x32, .data[7] = 0x88, .data[8] = 0x88, }, + { .channel = 13, .freq = 2472, .data[0] = 0xFF, .data[1] = 0xFF, + .data[2] = 0xB5, .data[3] = 0x1B, .data[4] = 0x24, .data[5] = 0x32, + .data[6] = 0x32, .data[7] = 0x88, .data[8] = 0x88, }, + { .channel = 14, .freq = 2484, .data[0] = 0xFF, .data[1] = 0xFF, + .data[2] = 0xB5, .data[3] = 0x1B, .data[4] = 0x24, .data[5] = 0x32, + .data[6] = 0x32, .data[7] = 0x88, .data[8] = 0x88, }, + { .channel = 34, .freq = 5170, .data[0] = 0x00, .data[1] = 0x22, + .data[2] = 0x20, .data[3] = 0x84, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 38, .freq = 5190, .data[0] = 0x00, .data[1] = 0x11, + .data[2] = 0x10, .data[3] = 0x83, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 42, .freq = 5210, .data[0] = 0x00, .data[1] = 0x11, + .data[2] = 0x10, .data[3] = 0x83, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 46, .freq = 5230, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x83, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 36, .freq = 5180, .data[0] = 0x00, .data[1] = 0x11, + .data[2] = 0x20, .data[3] = 0x83, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 40, .freq = 5200, .data[0] = 0x00, .data[1] = 0x11, + .data[2] = 0x10, .data[3] = 0x84, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 44, .freq = 5220, .data[0] = 0x00, .data[1] = 0x11, + .data[2] = 0x00, .data[3] = 0x83, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 48, .freq = 5240, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x83, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 52, .freq = 5260, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x83, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 56, .freq = 5280, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x83, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 60, .freq = 5300, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x63, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 64, .freq = 5320, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x62, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 100, .freq = 5500, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x30, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x37, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 104, .freq = 5520, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x20, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x37, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 108, .freq = 5540, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x20, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x37, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 112, .freq = 5560, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x20, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x37, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 116, .freq = 5580, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x10, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x37, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 120, .freq = 5600, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x00, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x37, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 124, .freq = 5620, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x00, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x37, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 128, .freq = 5640, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x00, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x37, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 132, .freq = 5660, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x00, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x37, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 136, .freq = 5680, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x00, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x37, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 140, .freq = 5700, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x00, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x37, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 149, .freq = 5745, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x00, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x37, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 153, .freq = 5765, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x00, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x37, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 157, .freq = 5785, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x00, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x37, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 161, .freq = 5805, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x00, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x37, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 165, .freq = 5825, .data[0] = 0x00, .data[1] = 0x00, + .data[2] = 0x00, .data[3] = 0x00, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x37, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 184, .freq = 4920, .data[0] = 0x55, .data[1] = 0x77, + .data[2] = 0x90, .data[3] = 0xF7, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0xFF, }, + { .channel = 188, .freq = 4940, .data[0] = 0x44, .data[1] = 0x77, + .data[2] = 0x80, .data[3] = 0xE7, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0xFF, }, + { .channel = 192, .freq = 4960, .data[0] = 0x44, .data[1] = 0x66, + .data[2] = 0x80, .data[3] = 0xE7, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0xFF, }, + { .channel = 196, .freq = 4980, .data[0] = 0x33, .data[1] = 0x66, + .data[2] = 0x70, .data[3] = 0xC7, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0xFF, }, + { .channel = 200, .freq = 5000, .data[0] = 0x22, .data[1] = 0x55, + .data[2] = 0x60, .data[3] = 0xD7, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0xFF, }, + { .channel = 204, .freq = 5020, .data[0] = 0x22, .data[1] = 0x55, + .data[2] = 0x60, .data[3] = 0xC7, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0xFF, }, + { .channel = 208, .freq = 5040, .data[0] = 0x22, .data[1] = 0x44, + .data[2] = 0x50, .data[3] = 0xC7, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0xFF, }, + { .channel = 212, .freq = 5060, .data[0] = 0x11, .data[1] = 0x44, + .data[2] = 0x50, .data[3] = 0xA5, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0x88, }, + { .channel = 216, .freq = 5080, .data[0] = 0x00, .data[1] = 0x44, + .data[2] = 0x40, .data[3] = 0xB6, .data[4] = 0x3C, .data[5] = 0x77, + .data[6] = 0x35, .data[7] = 0xFF, .data[8] = 0x88, }, +}; + +static const struct b206x_channel b2063_chantbl[] = { + { .channel = 1, .freq = 2412, .data[0] = 0x6F, .data[1] = 0x3C, + .data[2] = 0x3C, .data[3] = 0x04, .data[4] = 0x05, .data[5] = 0x05, + .data[6] = 0x05, .data[7] = 0x05, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x80, .data[11] = 0x70, }, + { .channel = 2, .freq = 2417, .data[0] = 0x6F, .data[1] = 0x3C, + .data[2] = 0x3C, .data[3] = 0x04, .data[4] = 0x05, .data[5] = 0x05, + .data[6] = 0x05, .data[7] = 0x05, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x80, .data[11] = 0x70, }, + { .channel = 3, .freq = 2422, .data[0] = 0x6F, .data[1] = 0x3C, + .data[2] = 0x3C, .data[3] = 0x04, .data[4] = 0x05, .data[5] = 0x05, + .data[6] = 0x05, .data[7] = 0x05, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x80, .data[11] = 0x70, }, + { .channel = 4, .freq = 2427, .data[0] = 0x6F, .data[1] = 0x2C, + .data[2] = 0x2C, .data[3] = 0x04, .data[4] = 0x05, .data[5] = 0x05, + .data[6] = 0x05, .data[7] = 0x05, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x80, .data[11] = 0x70, }, + { .channel = 5, .freq = 2432, .data[0] = 0x6F, .data[1] = 0x2C, + .data[2] = 0x2C, .data[3] = 0x04, .data[4] = 0x05, .data[5] = 0x05, + .data[6] = 0x05, .data[7] = 0x05, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x80, .data[11] = 0x70, }, + { .channel = 6, .freq = 2437, .data[0] = 0x6F, .data[1] = 0x2C, + .data[2] = 0x2C, .data[3] = 0x04, .data[4] = 0x05, .data[5] = 0x05, + .data[6] = 0x05, .data[7] = 0x05, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x80, .data[11] = 0x70, }, + { .channel = 7, .freq = 2442, .data[0] = 0x6F, .data[1] = 0x2C, + .data[2] = 0x2C, .data[3] = 0x04, .data[4] = 0x05, .data[5] = 0x05, + .data[6] = 0x05, .data[7] = 0x05, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x80, .data[11] = 0x70, }, + { .channel = 8, .freq = 2447, .data[0] = 0x6F, .data[1] = 0x2C, + .data[2] = 0x2C, .data[3] = 0x04, .data[4] = 0x05, .data[5] = 0x05, + .data[6] = 0x05, .data[7] = 0x05, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x80, .data[11] = 0x70, }, + { .channel = 9, .freq = 2452, .data[0] = 0x6F, .data[1] = 0x1C, + .data[2] = 0x1C, .data[3] = 0x04, .data[4] = 0x05, .data[5] = 0x05, + .data[6] = 0x05, .data[7] = 0x05, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x80, .data[11] = 0x70, }, + { .channel = 10, .freq = 2457, .data[0] = 0x6F, .data[1] = 0x1C, + .data[2] = 0x1C, .data[3] = 0x04, .data[4] = 0x05, .data[5] = 0x05, + .data[6] = 0x05, .data[7] = 0x05, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x80, .data[11] = 0x70, }, + { .channel = 11, .freq = 2462, .data[0] = 0x6E, .data[1] = 0x1C, + .data[2] = 0x1C, .data[3] = 0x04, .data[4] = 0x05, .data[5] = 0x05, + .data[6] = 0x05, .data[7] = 0x05, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x80, .data[11] = 0x70, }, + { .channel = 12, .freq = 2467, .data[0] = 0x6E, .data[1] = 0x1C, + .data[2] = 0x1C, .data[3] = 0x04, .data[4] = 0x05, .data[5] = 0x05, + .data[6] = 0x05, .data[7] = 0x05, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x80, .data[11] = 0x70, }, + { .channel = 13, .freq = 2472, .data[0] = 0x6E, .data[1] = 0x1C, + .data[2] = 0x1C, .data[3] = 0x04, .data[4] = 0x05, .data[5] = 0x05, + .data[6] = 0x05, .data[7] = 0x05, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x80, .data[11] = 0x70, }, + { .channel = 14, .freq = 2484, .data[0] = 0x6E, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x04, .data[4] = 0x05, .data[5] = 0x05, + .data[6] = 0x05, .data[7] = 0x05, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x80, .data[11] = 0x70, }, + { .channel = 34, .freq = 5170, .data[0] = 0x6A, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x02, .data[5] = 0x05, + .data[6] = 0x0D, .data[7] = 0x0D, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x20, .data[11] = 0x00, }, + { .channel = 36, .freq = 5180, .data[0] = 0x6A, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x01, .data[5] = 0x05, + .data[6] = 0x0D, .data[7] = 0x0C, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x20, .data[11] = 0x00, }, + { .channel = 38, .freq = 5190, .data[0] = 0x6A, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x01, .data[5] = 0x04, + .data[6] = 0x0C, .data[7] = 0x0C, .data[8] = 0x77, .data[9] = 0x80, + .data[10] = 0x20, .data[11] = 0x00, }, + { .channel = 40, .freq = 5200, .data[0] = 0x69, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x01, .data[5] = 0x04, + .data[6] = 0x0C, .data[7] = 0x0C, .data[8] = 0x77, .data[9] = 0x70, + .data[10] = 0x20, .data[11] = 0x00, }, + { .channel = 42, .freq = 5210, .data[0] = 0x69, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x01, .data[5] = 0x04, + .data[6] = 0x0B, .data[7] = 0x0C, .data[8] = 0x77, .data[9] = 0x70, + .data[10] = 0x20, .data[11] = 0x00, }, + { .channel = 44, .freq = 5220, .data[0] = 0x69, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x04, + .data[6] = 0x0B, .data[7] = 0x0B, .data[8] = 0x77, .data[9] = 0x60, + .data[10] = 0x20, .data[11] = 0x00, }, + { .channel = 46, .freq = 5230, .data[0] = 0x69, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x03, + .data[6] = 0x0A, .data[7] = 0x0B, .data[8] = 0x77, .data[9] = 0x60, + .data[10] = 0x20, .data[11] = 0x00, }, + { .channel = 48, .freq = 5240, .data[0] = 0x69, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x03, + .data[6] = 0x0A, .data[7] = 0x0A, .data[8] = 0x77, .data[9] = 0x60, + .data[10] = 0x20, .data[11] = 0x00, }, + { .channel = 52, .freq = 5260, .data[0] = 0x68, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x02, + .data[6] = 0x09, .data[7] = 0x09, .data[8] = 0x77, .data[9] = 0x60, + .data[10] = 0x20, .data[11] = 0x00, }, + { .channel = 56, .freq = 5280, .data[0] = 0x68, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x01, + .data[6] = 0x08, .data[7] = 0x08, .data[8] = 0x77, .data[9] = 0x50, + .data[10] = 0x10, .data[11] = 0x00, }, + { .channel = 60, .freq = 5300, .data[0] = 0x68, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x01, + .data[6] = 0x08, .data[7] = 0x08, .data[8] = 0x77, .data[9] = 0x50, + .data[10] = 0x10, .data[11] = 0x00, }, + { .channel = 64, .freq = 5320, .data[0] = 0x67, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x08, .data[7] = 0x08, .data[8] = 0x77, .data[9] = 0x50, + .data[10] = 0x10, .data[11] = 0x00, }, + { .channel = 100, .freq = 5500, .data[0] = 0x64, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x02, .data[7] = 0x01, .data[8] = 0x77, .data[9] = 0x20, + .data[10] = 0x00, .data[11] = 0x00, }, + { .channel = 104, .freq = 5520, .data[0] = 0x64, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x01, .data[7] = 0x01, .data[8] = 0x77, .data[9] = 0x20, + .data[10] = 0x00, .data[11] = 0x00, }, + { .channel = 108, .freq = 5540, .data[0] = 0x63, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x01, .data[7] = 0x00, .data[8] = 0x77, .data[9] = 0x10, + .data[10] = 0x00, .data[11] = 0x00, }, + { .channel = 112, .freq = 5560, .data[0] = 0x63, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x00, .data[7] = 0x00, .data[8] = 0x77, .data[9] = 0x10, + .data[10] = 0x00, .data[11] = 0x00, }, + { .channel = 116, .freq = 5580, .data[0] = 0x62, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x00, .data[7] = 0x00, .data[8] = 0x77, .data[9] = 0x10, + .data[10] = 0x00, .data[11] = 0x00, }, + { .channel = 120, .freq = 5600, .data[0] = 0x62, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x00, .data[7] = 0x00, .data[8] = 0x77, .data[9] = 0x00, + .data[10] = 0x00, .data[11] = 0x00, }, + { .channel = 124, .freq = 5620, .data[0] = 0x62, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x00, .data[7] = 0x00, .data[8] = 0x77, .data[9] = 0x00, + .data[10] = 0x00, .data[11] = 0x00, }, + { .channel = 128, .freq = 5640, .data[0] = 0x61, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x00, .data[7] = 0x00, .data[8] = 0x77, .data[9] = 0x00, + .data[10] = 0x00, .data[11] = 0x00, }, + { .channel = 132, .freq = 5660, .data[0] = 0x61, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x00, .data[7] = 0x00, .data[8] = 0x77, .data[9] = 0x00, + .data[10] = 0x00, .data[11] = 0x00, }, + { .channel = 136, .freq = 5680, .data[0] = 0x61, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x00, .data[7] = 0x00, .data[8] = 0x77, .data[9] = 0x00, + .data[10] = 0x00, .data[11] = 0x00, }, + { .channel = 140, .freq = 5700, .data[0] = 0x60, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x00, .data[7] = 0x00, .data[8] = 0x77, .data[9] = 0x00, + .data[10] = 0x00, .data[11] = 0x00, }, + { .channel = 149, .freq = 5745, .data[0] = 0x60, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x00, .data[7] = 0x00, .data[8] = 0x77, .data[9] = 0x00, + .data[10] = 0x00, .data[11] = 0x00, }, + { .channel = 153, .freq = 5765, .data[0] = 0x60, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x00, .data[7] = 0x00, .data[8] = 0x77, .data[9] = 0x00, + .data[10] = 0x00, .data[11] = 0x00, }, + { .channel = 157, .freq = 5785, .data[0] = 0x60, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x00, .data[7] = 0x00, .data[8] = 0x77, .data[9] = 0x00, + .data[10] = 0x00, .data[11] = 0x00, }, + { .channel = 161, .freq = 5805, .data[0] = 0x60, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x00, .data[7] = 0x00, .data[8] = 0x77, .data[9] = 0x00, + .data[10] = 0x00, .data[11] = 0x00, }, + { .channel = 165, .freq = 5825, .data[0] = 0x60, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x00, .data[5] = 0x00, + .data[6] = 0x00, .data[7] = 0x00, .data[8] = 0x77, .data[9] = 0x00, + .data[10] = 0x00, .data[11] = 0x00, }, + { .channel = 184, .freq = 4920, .data[0] = 0x6E, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x09, .data[5] = 0x0E, + .data[6] = 0x0F, .data[7] = 0x0F, .data[8] = 0x77, .data[9] = 0xC0, + .data[10] = 0x50, .data[11] = 0x00, }, + { .channel = 188, .freq = 4940, .data[0] = 0x6E, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x09, .data[5] = 0x0D, + .data[6] = 0x0F, .data[7] = 0x0F, .data[8] = 0x77, .data[9] = 0xB0, + .data[10] = 0x50, .data[11] = 0x00, }, + { .channel = 192, .freq = 4960, .data[0] = 0x6E, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x08, .data[5] = 0x0C, + .data[6] = 0x0F, .data[7] = 0x0F, .data[8] = 0x77, .data[9] = 0xB0, + .data[10] = 0x50, .data[11] = 0x00, }, + { .channel = 196, .freq = 4980, .data[0] = 0x6D, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x08, .data[5] = 0x0C, + .data[6] = 0x0F, .data[7] = 0x0F, .data[8] = 0x77, .data[9] = 0xA0, + .data[10] = 0x40, .data[11] = 0x00, }, + { .channel = 200, .freq = 5000, .data[0] = 0x6D, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x08, .data[5] = 0x0B, + .data[6] = 0x0F, .data[7] = 0x0F, .data[8] = 0x77, .data[9] = 0xA0, + .data[10] = 0x40, .data[11] = 0x00, }, + { .channel = 204, .freq = 5020, .data[0] = 0x6D, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x08, .data[5] = 0x0A, + .data[6] = 0x0F, .data[7] = 0x0F, .data[8] = 0x77, .data[9] = 0xA0, + .data[10] = 0x40, .data[11] = 0x00, }, + { .channel = 208, .freq = 5040, .data[0] = 0x6C, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x07, .data[5] = 0x09, + .data[6] = 0x0F, .data[7] = 0x0F, .data[8] = 0x77, .data[9] = 0x90, + .data[10] = 0x40, .data[11] = 0x00, }, + { .channel = 212, .freq = 5060, .data[0] = 0x6C, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x06, .data[5] = 0x08, + .data[6] = 0x0F, .data[7] = 0x0F, .data[8] = 0x77, .data[9] = 0x90, + .data[10] = 0x40, .data[11] = 0x00, }, + { .channel = 216, .freq = 5080, .data[0] = 0x6C, .data[1] = 0x0C, + .data[2] = 0x0C, .data[3] = 0x00, .data[4] = 0x05, .data[5] = 0x08, + .data[6] = 0x0F, .data[7] = 0x0F, .data[8] = 0x77, .data[9] = 0x90, + .data[10] = 0x40, .data[11] = 0x00, }, +}; + +static void lpphy_b2062_reset_pll_bias(struct b43_wldev *dev) +{ + b43_radio_write(dev, B2062_S_RFPLL_CTL2, 0xFF); + udelay(20); + if (dev->dev->chip_id == 0x5354) { + b43_radio_write(dev, B2062_N_COMM1, 4); + b43_radio_write(dev, B2062_S_RFPLL_CTL2, 4); + } else { + b43_radio_write(dev, B2062_S_RFPLL_CTL2, 0); + } + udelay(5); +} + +static void lpphy_b2062_vco_calib(struct b43_wldev *dev) +{ + b43_radio_write(dev, B2062_S_RFPLL_CTL21, 0x42); + b43_radio_write(dev, B2062_S_RFPLL_CTL21, 0x62); + udelay(200); +} + +static int lpphy_b2062_tune(struct b43_wldev *dev, + unsigned int channel) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + struct ssb_bus *bus = dev->dev->sdev->bus; + const struct b206x_channel *chandata = NULL; + u32 crystal_freq = bus->chipco.pmu.crystalfreq * 1000; + u32 tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9; + int i, err = 0; + + for (i = 0; i < ARRAY_SIZE(b2062_chantbl); i++) { + if (b2062_chantbl[i].channel == channel) { + chandata = &b2062_chantbl[i]; + break; + } + } + + if (B43_WARN_ON(!chandata)) + return -EINVAL; + + b43_radio_set(dev, B2062_S_RFPLL_CTL14, 0x04); + b43_radio_write(dev, B2062_N_LGENA_TUNE0, chandata->data[0]); + b43_radio_write(dev, B2062_N_LGENA_TUNE2, chandata->data[1]); + b43_radio_write(dev, B2062_N_LGENA_TUNE3, chandata->data[2]); + b43_radio_write(dev, B2062_N_TX_TUNE, chandata->data[3]); + b43_radio_write(dev, B2062_S_LGENG_CTL1, chandata->data[4]); + b43_radio_write(dev, B2062_N_LGENA_CTL5, chandata->data[5]); + b43_radio_write(dev, B2062_N_LGENA_CTL6, chandata->data[6]); + b43_radio_write(dev, B2062_N_TX_PGA, chandata->data[7]); + b43_radio_write(dev, B2062_N_TX_PAD, chandata->data[8]); + + tmp1 = crystal_freq / 1000; + tmp2 = lpphy->pdiv * 1000; + b43_radio_write(dev, B2062_S_RFPLL_CTL33, 0xCC); + b43_radio_write(dev, B2062_S_RFPLL_CTL34, 0x07); + lpphy_b2062_reset_pll_bias(dev); + tmp3 = tmp2 * channel2freq_lp(channel); + if (channel2freq_lp(channel) < 4000) + tmp3 *= 2; + tmp4 = 48 * tmp1; + tmp6 = tmp3 / tmp4; + tmp7 = tmp3 % tmp4; + b43_radio_write(dev, B2062_S_RFPLL_CTL26, tmp6); + tmp5 = tmp7 * 0x100; + tmp6 = tmp5 / tmp4; + tmp7 = tmp5 % tmp4; + b43_radio_write(dev, B2062_S_RFPLL_CTL27, tmp6); + tmp5 = tmp7 * 0x100; + tmp6 = tmp5 / tmp4; + tmp7 = tmp5 % tmp4; + b43_radio_write(dev, B2062_S_RFPLL_CTL28, tmp6); + tmp5 = tmp7 * 0x100; + tmp6 = tmp5 / tmp4; + tmp7 = tmp5 % tmp4; + b43_radio_write(dev, B2062_S_RFPLL_CTL29, tmp6 + ((2 * tmp7) / tmp4)); + tmp8 = b43_radio_read(dev, B2062_S_RFPLL_CTL19); + tmp9 = ((2 * tmp3 * (tmp8 + 1)) + (3 * tmp1)) / (6 * tmp1); + b43_radio_write(dev, B2062_S_RFPLL_CTL23, (tmp9 >> 8) + 16); + b43_radio_write(dev, B2062_S_RFPLL_CTL24, tmp9 & 0xFF); + + lpphy_b2062_vco_calib(dev); + if (b43_radio_read(dev, B2062_S_RFPLL_CTL3) & 0x10) { + b43_radio_write(dev, B2062_S_RFPLL_CTL33, 0xFC); + b43_radio_write(dev, B2062_S_RFPLL_CTL34, 0); + lpphy_b2062_reset_pll_bias(dev); + lpphy_b2062_vco_calib(dev); + if (b43_radio_read(dev, B2062_S_RFPLL_CTL3) & 0x10) + err = -EIO; + } + + b43_radio_mask(dev, B2062_S_RFPLL_CTL14, ~0x04); + return err; +} + +static void lpphy_b2063_vco_calib(struct b43_wldev *dev) +{ + u16 tmp; + + b43_radio_mask(dev, B2063_PLL_SP1, ~0x40); + tmp = b43_radio_read(dev, B2063_PLL_JTAG_CALNRST) & 0xF8; + b43_radio_write(dev, B2063_PLL_JTAG_CALNRST, tmp); + udelay(1); + b43_radio_write(dev, B2063_PLL_JTAG_CALNRST, tmp | 0x4); + udelay(1); + b43_radio_write(dev, B2063_PLL_JTAG_CALNRST, tmp | 0x6); + udelay(1); + b43_radio_write(dev, B2063_PLL_JTAG_CALNRST, tmp | 0x7); + udelay(300); + b43_radio_set(dev, B2063_PLL_SP1, 0x40); +} + +static int lpphy_b2063_tune(struct b43_wldev *dev, + unsigned int channel) +{ + struct ssb_bus *bus = dev->dev->sdev->bus; + + static const struct b206x_channel *chandata = NULL; + u32 crystal_freq = bus->chipco.pmu.crystalfreq * 1000; + u32 freqref, vco_freq, val1, val2, val3, timeout, timeoutref, count; + u16 old_comm15, scale; + u32 tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; + int i, div = (crystal_freq <= 26000000 ? 1 : 2); + + for (i = 0; i < ARRAY_SIZE(b2063_chantbl); i++) { + if (b2063_chantbl[i].channel == channel) { + chandata = &b2063_chantbl[i]; + break; + } + } + + if (B43_WARN_ON(!chandata)) + return -EINVAL; + + b43_radio_write(dev, B2063_LOGEN_VCOBUF1, chandata->data[0]); + b43_radio_write(dev, B2063_LOGEN_MIXER2, chandata->data[1]); + b43_radio_write(dev, B2063_LOGEN_BUF2, chandata->data[2]); + b43_radio_write(dev, B2063_LOGEN_RCCR1, chandata->data[3]); + b43_radio_write(dev, B2063_A_RX_1ST3, chandata->data[4]); + b43_radio_write(dev, B2063_A_RX_2ND1, chandata->data[5]); + b43_radio_write(dev, B2063_A_RX_2ND4, chandata->data[6]); + b43_radio_write(dev, B2063_A_RX_2ND7, chandata->data[7]); + b43_radio_write(dev, B2063_A_RX_PS6, chandata->data[8]); + b43_radio_write(dev, B2063_TX_RF_CTL2, chandata->data[9]); + b43_radio_write(dev, B2063_TX_RF_CTL5, chandata->data[10]); + b43_radio_write(dev, B2063_PA_CTL11, chandata->data[11]); + + old_comm15 = b43_radio_read(dev, B2063_COMM15); + b43_radio_set(dev, B2063_COMM15, 0x1E); + + if (chandata->freq > 4000) /* spec says 2484, but 4000 is safer */ + vco_freq = chandata->freq << 1; + else + vco_freq = chandata->freq << 2; + + freqref = crystal_freq * 3; + val1 = lpphy_qdiv_roundup(crystal_freq, 1000000, 16); + val2 = lpphy_qdiv_roundup(crystal_freq, 1000000 * div, 16); + val3 = lpphy_qdiv_roundup(vco_freq, 3, 16); + timeout = ((((8 * crystal_freq) / (div * 5000000)) + 1) >> 1) - 1; + b43_radio_write(dev, B2063_PLL_JTAG_PLL_VCO_CALIB3, 0x2); + b43_radio_maskset(dev, B2063_PLL_JTAG_PLL_VCO_CALIB6, + 0xFFF8, timeout >> 2); + b43_radio_maskset(dev, B2063_PLL_JTAG_PLL_VCO_CALIB7, + 0xFF9F,timeout << 5); + + timeoutref = ((((8 * crystal_freq) / (div * (timeout + 1))) + + 999999) / 1000000) + 1; + b43_radio_write(dev, B2063_PLL_JTAG_PLL_VCO_CALIB5, timeoutref); + + count = lpphy_qdiv_roundup(val3, val2 + 16, 16); + count *= (timeout + 1) * (timeoutref + 1); + count--; + b43_radio_maskset(dev, B2063_PLL_JTAG_PLL_VCO_CALIB7, + 0xF0, count >> 8); + b43_radio_write(dev, B2063_PLL_JTAG_PLL_VCO_CALIB8, count & 0xFF); + + tmp1 = ((val3 * 62500) / freqref) << 4; + tmp2 = ((val3 * 62500) % freqref) << 4; + while (tmp2 >= freqref) { + tmp1++; + tmp2 -= freqref; + } + b43_radio_maskset(dev, B2063_PLL_JTAG_PLL_SG1, 0xFFE0, tmp1 >> 4); + b43_radio_maskset(dev, B2063_PLL_JTAG_PLL_SG2, 0xFE0F, tmp1 << 4); + b43_radio_maskset(dev, B2063_PLL_JTAG_PLL_SG2, 0xFFF0, tmp1 >> 16); + b43_radio_write(dev, B2063_PLL_JTAG_PLL_SG3, (tmp2 >> 8) & 0xFF); + b43_radio_write(dev, B2063_PLL_JTAG_PLL_SG4, tmp2 & 0xFF); + + b43_radio_write(dev, B2063_PLL_JTAG_PLL_LF1, 0xB9); + b43_radio_write(dev, B2063_PLL_JTAG_PLL_LF2, 0x88); + b43_radio_write(dev, B2063_PLL_JTAG_PLL_LF3, 0x28); + b43_radio_write(dev, B2063_PLL_JTAG_PLL_LF4, 0x63); + + tmp3 = ((41 * (val3 - 3000)) /1200) + 27; + tmp4 = lpphy_qdiv_roundup(132000 * tmp1, 8451, 16); + + if ((tmp4 + tmp3 - 1) / tmp3 > 60) { + scale = 1; + tmp5 = ((tmp4 + tmp3) / (tmp3 << 1)) - 8; + } else { + scale = 0; + tmp5 = ((tmp4 + (tmp3 >> 1)) / tmp3) - 8; + } + b43_radio_maskset(dev, B2063_PLL_JTAG_PLL_CP2, 0xFFC0, tmp5); + b43_radio_maskset(dev, B2063_PLL_JTAG_PLL_CP2, 0xFFBF, scale << 6); + + tmp6 = lpphy_qdiv_roundup(100 * val1, val3, 16); + tmp6 *= (tmp5 * 8) * (scale + 1); + if (tmp6 > 150) + tmp6 = 0; + + b43_radio_maskset(dev, B2063_PLL_JTAG_PLL_CP3, 0xFFE0, tmp6); + b43_radio_maskset(dev, B2063_PLL_JTAG_PLL_CP3, 0xFFDF, scale << 5); + + b43_radio_maskset(dev, B2063_PLL_JTAG_PLL_XTAL_12, 0xFFFB, 0x4); + if (crystal_freq > 26000000) + b43_radio_set(dev, B2063_PLL_JTAG_PLL_XTAL_12, 0x2); + else + b43_radio_mask(dev, B2063_PLL_JTAG_PLL_XTAL_12, 0xFD); + + if (val1 == 45) + b43_radio_set(dev, B2063_PLL_JTAG_PLL_VCO1, 0x2); + else + b43_radio_mask(dev, B2063_PLL_JTAG_PLL_VCO1, 0xFD); + + b43_radio_set(dev, B2063_PLL_SP2, 0x3); + udelay(1); + b43_radio_mask(dev, B2063_PLL_SP2, 0xFFFC); + lpphy_b2063_vco_calib(dev); + b43_radio_write(dev, B2063_COMM15, old_comm15); + + return 0; +} + +static int b43_lpphy_op_switch_channel(struct b43_wldev *dev, + unsigned int new_channel) +{ + struct b43_phy_lp *lpphy = dev->phy.lp; + int err; + + if (dev->phy.radio_ver == 0x2063) { + err = lpphy_b2063_tune(dev, new_channel); + if (err) + return err; + } else { + err = lpphy_b2062_tune(dev, new_channel); + if (err) + return err; + lpphy_set_analog_filter(dev, new_channel); + lpphy_adjust_gain_table(dev, channel2freq_lp(new_channel)); + } + + lpphy->channel = new_channel; + b43_write16(dev, B43_MMIO_CHANNEL, new_channel); + + return 0; +} + +static int b43_lpphy_op_init(struct b43_wldev *dev) +{ + int err; + + if (dev->dev->bus_type != B43_BUS_SSB) { + b43err(dev->wl, "LP-PHY is supported only on SSB!\n"); + return -EOPNOTSUPP; + } + + lpphy_read_band_sprom(dev); //FIXME should this be in prepare_structs? + lpphy_baseband_init(dev); + lpphy_radio_init(dev); + lpphy_calibrate_rc(dev); + err = b43_lpphy_op_switch_channel(dev, 7); + if (err) { + b43dbg(dev->wl, "Switch to channel 7 failed, error = %d.\n", + err); + } + lpphy_tx_pctl_init(dev); + lpphy_calibration(dev); + //TODO ACI init + + return 0; +} + +static void b43_lpphy_op_adjust_txpower(struct b43_wldev *dev) +{ + //TODO +} + +static enum b43_txpwr_result b43_lpphy_op_recalc_txpower(struct b43_wldev *dev, + bool ignore_tssi) +{ + //TODO + return B43_TXPWR_RES_DONE; +} + +static void b43_lpphy_op_switch_analog(struct b43_wldev *dev, bool on) +{ + if (on) { + b43_phy_mask(dev, B43_LPPHY_AFE_CTL_OVR, 0xfff8); + } else { + b43_phy_set(dev, B43_LPPHY_AFE_CTL_OVRVAL, 0x0007); + b43_phy_set(dev, B43_LPPHY_AFE_CTL_OVR, 0x0007); + } +} + +static void b43_lpphy_op_pwork_15sec(struct b43_wldev *dev) +{ + //TODO +} + +const struct b43_phy_operations b43_phyops_lp = { + .allocate = b43_lpphy_op_allocate, + .free = b43_lpphy_op_free, + .prepare_structs = b43_lpphy_op_prepare_structs, + .init = b43_lpphy_op_init, + .phy_read = b43_lpphy_op_read, + .phy_write = b43_lpphy_op_write, + .phy_maskset = b43_lpphy_op_maskset, + .radio_read = b43_lpphy_op_radio_read, + .radio_write = b43_lpphy_op_radio_write, + .software_rfkill = b43_lpphy_op_software_rfkill, + .switch_analog = b43_lpphy_op_switch_analog, + .switch_channel = b43_lpphy_op_switch_channel, + .get_default_chan = b43_lpphy_op_get_default_chan, + .set_rx_antenna = b43_lpphy_op_set_rx_antenna, + .recalc_txpower = b43_lpphy_op_recalc_txpower, + .adjust_txpower = b43_lpphy_op_adjust_txpower, + .pwork_15sec = b43_lpphy_op_pwork_15sec, + .pwork_60sec = lpphy_calibration, +}; diff --git a/drivers/net/wireless/b43/phy_lp.h b/drivers/net/wireless/b43/phy_lp.h new file mode 100644 index 0000000..62737f7 --- /dev/null +++ b/drivers/net/wireless/b43/phy_lp.h @@ -0,0 +1,912 @@ +#ifndef LINUX_B43_PHY_LP_H_ +#define LINUX_B43_PHY_LP_H_ + +/* Definitions for the LP-PHY */ + + +/* The CCK PHY register range. */ +#define B43_LPPHY_B_VERSION B43_PHY_CCK(0x00) /* B PHY version */ +#define B43_LPPHY_B_BBCONFIG B43_PHY_CCK(0x01) /* B PHY BBConfig */ +#define B43_LPPHY_B_RX_STAT0 B43_PHY_CCK(0x04) /* B PHY RX Status0 */ +#define B43_LPPHY_B_RX_STAT1 B43_PHY_CCK(0x05) /* B PHY RX Status1 */ +#define B43_LPPHY_B_CRS_THRESH B43_PHY_CCK(0x06) /* B PHY CRS Thresh */ +#define B43_LPPHY_B_TXERROR B43_PHY_CCK(0x07) /* B PHY TxError */ +#define B43_LPPHY_B_CHANNEL B43_PHY_CCK(0x08) /* B PHY Channel */ +#define B43_LPPHY_B_WORKAROUND B43_PHY_CCK(0x09) /* B PHY workaround */ +#define B43_LPPHY_B_TEST B43_PHY_CCK(0x0A) /* B PHY Test */ +#define B43_LPPHY_B_FOURWIRE_ADDR B43_PHY_CCK(0x0B) /* B PHY Fourwire Address */ +#define B43_LPPHY_B_FOURWIRE_DATA_HI B43_PHY_CCK(0x0C) /* B PHY Fourwire Data Hi */ +#define B43_LPPHY_B_FOURWIRE_DATA_LO B43_PHY_CCK(0x0D) /* B PHY Fourwire Data Lo */ +#define B43_LPPHY_B_BIST_STAT B43_PHY_CCK(0x0E) /* B PHY Bist Status */ +#define B43_LPPHY_PA_RAMP_TX_TO B43_PHY_CCK(0x10) /* PA Ramp TX Timeout */ +#define B43_LPPHY_RF_SYNTH_DC_TIMER B43_PHY_CCK(0x11) /* RF Synth DC Timer */ +#define B43_LPPHY_PA_RAMP_TX_TIME_IN B43_PHY_CCK(0x12) /* PA ramp TX Time in */ +#define B43_LPPHY_RX_FILTER_TIME_IN B43_PHY_CCK(0x13) /* RX Filter Time in */ +#define B43_LPPHY_PLL_COEFF_S B43_PHY_CCK(0x18) /* PLL Coefficient(s) */ +#define B43_LPPHY_PLL_OUT B43_PHY_CCK(0x19) /* PLL Out */ +#define B43_LPPHY_RSSI_THRES B43_PHY_CCK(0x20) /* RSSI Threshold */ +#define B43_LPPHY_IQ_THRES_HH B43_PHY_CCK(0x21) /* IQ Threshold HH */ +#define B43_LPPHY_IQ_THRES_H B43_PHY_CCK(0x22) /* IQ Threshold H */ +#define B43_LPPHY_IQ_THRES_L B43_PHY_CCK(0x23) /* IQ Threshold L */ +#define B43_LPPHY_IQ_THRES_LL B43_PHY_CCK(0x24) /* IQ Threshold LL */ +#define B43_LPPHY_AGC_GAIN B43_PHY_CCK(0x25) /* AGC Gain */ +#define B43_LPPHY_LNA_GAIN_RANGE B43_PHY_CCK(0x26) /* LNA Gain Range */ +#define B43_LPPHY_JSSI B43_PHY_CCK(0x27) /* JSSI */ +#define B43_LPPHY_TSSI_CTL B43_PHY_CCK(0x28) /* TSSI Control */ +#define B43_LPPHY_TSSI B43_PHY_CCK(0x29) /* TSSI */ +#define B43_LPPHY_TR_LOSS B43_PHY_CCK(0x2A) /* TR Loss */ +#define B43_LPPHY_LO_LEAKAGE B43_PHY_CCK(0x2B) /* LO Leakage */ +#define B43_LPPHY_LO_RSSIACC B43_PHY_CCK(0x2C) /* LO RSSIAcc */ +#define B43_LPPHY_LO_IQ_MAG_ACC B43_PHY_CCK(0x2D) /* LO IQ Mag Acc */ +#define B43_LPPHY_TX_DCOFFSET1 B43_PHY_CCK(0x2E) /* TX DCOffset1 */ +#define B43_LPPHY_TX_DCOFFSET2 B43_PHY_CCK(0x2F) /* TX DCOffset2 */ +#define B43_LPPHY_SYNCPEAKCNT B43_PHY_CCK(0x30) /* SyncPeakCnt */ +#define B43_LPPHY_SYNCFREQ B43_PHY_CCK(0x31) /* SyncFreq */ +#define B43_LPPHY_SYNCDIVERSITYCTL B43_PHY_CCK(0x32) /* SyncDiversityControl */ +#define B43_LPPHY_PEAKENERGYL B43_PHY_CCK(0x33) /* PeakEnergyL */ +#define B43_LPPHY_PEAKENERGYH B43_PHY_CCK(0x34) /* PeakEnergyH */ +#define B43_LPPHY_SYNCCTL B43_PHY_CCK(0x35) /* SyncControl */ +#define B43_LPPHY_DSSSSTEP B43_PHY_CCK(0x38) /* DsssStep */ +#define B43_LPPHY_DSSSWARMUP B43_PHY_CCK(0x39) /* DsssWarmup */ +#define B43_LPPHY_DSSSSIGPOW B43_PHY_CCK(0x3D) /* DsssSigPow */ +#define B43_LPPHY_SFDDETECTBLOCKTIME B43_PHY_CCK(0x40) /* SfdDetectBlockTIme */ +#define B43_LPPHY_SFDTO B43_PHY_CCK(0x41) /* SFDTimeOut */ +#define B43_LPPHY_SFDCTL B43_PHY_CCK(0x42) /* SFDControl */ +#define B43_LPPHY_RXDBG B43_PHY_CCK(0x43) /* rxDebug */ +#define B43_LPPHY_RX_DELAYCOMP B43_PHY_CCK(0x44) /* RX DelayComp */ +#define B43_LPPHY_CRSDROPOUTTO B43_PHY_CCK(0x45) /* CRSDropoutTimeout */ +#define B43_LPPHY_PSEUDOSHORTTO B43_PHY_CCK(0x46) /* PseudoShortTimeout */ +#define B43_LPPHY_PR3931 B43_PHY_CCK(0x47) /* PR3931 */ +#define B43_LPPHY_DSSSCOEFF1 B43_PHY_CCK(0x48) /* DSSSCoeff1 */ +#define B43_LPPHY_DSSSCOEFF2 B43_PHY_CCK(0x49) /* DSSSCoeff2 */ +#define B43_LPPHY_CCKCOEFF1 B43_PHY_CCK(0x4A) /* CCKCoeff1 */ +#define B43_LPPHY_CCKCOEFF2 B43_PHY_CCK(0x4B) /* CCKCoeff2 */ +#define B43_LPPHY_TRCORR B43_PHY_CCK(0x4C) /* TRCorr */ +#define B43_LPPHY_ANGLESCALE B43_PHY_CCK(0x4D) /* AngleScale */ +#define B43_LPPHY_OPTIONALMODES2 B43_PHY_CCK(0x4F) /* OptionalModes2 */ +#define B43_LPPHY_CCKLMSSTEPSIZE B43_PHY_CCK(0x50) /* CCKLMSStepSize */ +#define B43_LPPHY_DFEBYPASS B43_PHY_CCK(0x51) /* DFEBypass */ +#define B43_LPPHY_CCKSTARTDELAYLONG B43_PHY_CCK(0x52) /* CCKStartDelayLong */ +#define B43_LPPHY_CCKSTARTDELAYSHORT B43_PHY_CCK(0x53) /* CCKStartDelayShort */ +#define B43_LPPHY_PPROCCHDELAY B43_PHY_CCK(0x54) /* PprocChDelay */ +#define B43_LPPHY_PPROCONOFF B43_PHY_CCK(0x55) /* PProcOnOff */ +#define B43_LPPHY_LNAGAINTWOBIT10 B43_PHY_CCK(0x5B) /* LNAGainTwoBit10 */ +#define B43_LPPHY_LNAGAINTWOBIT32 B43_PHY_CCK(0x5C) /* LNAGainTwoBit32 */ +#define B43_LPPHY_OPTIONALMODES B43_PHY_CCK(0x5D) /* OptionalModes */ +#define B43_LPPHY_B_RX_STAT2 B43_PHY_CCK(0x5E) /* B PHY RX Status2 */ +#define B43_LPPHY_B_RX_STAT3 B43_PHY_CCK(0x5F) /* B PHY RX Status3 */ +#define B43_LPPHY_PWDNDACDELAY B43_PHY_CCK(0x63) /* pwdnDacDelay */ +#define B43_LPPHY_FINEDIGIGAIN_CTL B43_PHY_CCK(0x67) /* FineDigiGain Control */ +#define B43_LPPHY_LG2GAINTBLLNA8 B43_PHY_CCK(0x68) /* Lg2GainTblLNA8 */ +#define B43_LPPHY_LG2GAINTBLLNA28 B43_PHY_CCK(0x69) /* Lg2GainTblLNA28 */ +#define B43_LPPHY_GAINTBLLNATRSW B43_PHY_CCK(0x6A) /* GainTblLNATrSw */ +#define B43_LPPHY_PEAKENERGY B43_PHY_CCK(0x6B) /* PeakEnergy */ +#define B43_LPPHY_LG2INITGAIN B43_PHY_CCK(0x6C) /* lg2InitGain */ +#define B43_LPPHY_BLANKCOUNTLNAPGA B43_PHY_CCK(0x6D) /* BlankCountLnaPga */ +#define B43_LPPHY_LNAGAINTWOBIT54 B43_PHY_CCK(0x6E) /* LNAGainTwoBit54 */ +#define B43_LPPHY_LNAGAINTWOBIT76 B43_PHY_CCK(0x6F) /* LNAGainTwoBit76 */ +#define B43_LPPHY_JSSICTL B43_PHY_CCK(0x70) /* JSSIControl */ +#define B43_LPPHY_LG2GAINTBLLNA44 B43_PHY_CCK(0x71) /* Lg2GainTblLNA44 */ +#define B43_LPPHY_LG2GAINTBLLNA62 B43_PHY_CCK(0x72) /* Lg2GainTblLNA62 */ + +/* The OFDM PHY register range. */ +#define B43_LPPHY_VERSION B43_PHY_OFDM(0x00) /* Version */ +#define B43_LPPHY_BBCONFIG B43_PHY_OFDM(0x01) /* BBConfig */ +#define B43_LPPHY_RX_STAT0 B43_PHY_OFDM(0x04) /* RX Status0 */ +#define B43_LPPHY_RX_STAT1 B43_PHY_OFDM(0x05) /* RX Status1 */ +#define B43_LPPHY_TX_ERROR B43_PHY_OFDM(0x07) /* TX Error */ +#define B43_LPPHY_CHANNEL B43_PHY_OFDM(0x08) /* Channel */ +#define B43_LPPHY_WORKAROUND B43_PHY_OFDM(0x09) /* workaround */ +#define B43_LPPHY_FOURWIRE_ADDR B43_PHY_OFDM(0x0B) /* Fourwire Address */ +#define B43_LPPHY_FOURWIREDATAHI B43_PHY_OFDM(0x0C) /* FourwireDataHi */ +#define B43_LPPHY_FOURWIREDATALO B43_PHY_OFDM(0x0D) /* FourwireDataLo */ +#define B43_LPPHY_BISTSTAT0 B43_PHY_OFDM(0x0E) /* BistStatus0 */ +#define B43_LPPHY_BISTSTAT1 B43_PHY_OFDM(0x0F) /* BistStatus1 */ +#define B43_LPPHY_CRSGAIN_CTL B43_PHY_OFDM(0x10) /* crsgain Control */ +#define B43_LPPHY_OFDMPWR_THRESH0 B43_PHY_OFDM(0x11) /* ofdmPower Thresh0 */ +#define B43_LPPHY_OFDMPWR_THRESH1 B43_PHY_OFDM(0x12) /* ofdmPower Thresh1 */ +#define B43_LPPHY_OFDMPWR_THRESH2 B43_PHY_OFDM(0x13) /* ofdmPower Thresh2 */ +#define B43_LPPHY_DSSSPWR_THRESH0 B43_PHY_OFDM(0x14) /* dsssPower Thresh0 */ +#define B43_LPPHY_DSSSPWR_THRESH1 B43_PHY_OFDM(0x15) /* dsssPower Thresh1 */ +#define B43_LPPHY_MINPWR_LEVEL B43_PHY_OFDM(0x16) /* MinPower Level */ +#define B43_LPPHY_OFDMSYNCTHRESH0 B43_PHY_OFDM(0x17) /* ofdmSyncThresh0 */ +#define B43_LPPHY_OFDMSYNCTHRESH1 B43_PHY_OFDM(0x18) /* ofdmSyncThresh1 */ +#define B43_LPPHY_FINEFREQEST B43_PHY_OFDM(0x19) /* FineFreqEst */ +#define B43_LPPHY_IDLEAFTERPKTRXTO B43_PHY_OFDM(0x1A) /* IDLEafterPktRXTimeout */ +#define B43_LPPHY_LTRN_CTL B43_PHY_OFDM(0x1B) /* LTRN Control */ +#define B43_LPPHY_DCOFFSETTRANSIENT B43_PHY_OFDM(0x1C) /* DCOffsetTransient */ +#define B43_LPPHY_PREAMBLEINTO B43_PHY_OFDM(0x1D) /* PreambleInTimeout */ +#define B43_LPPHY_PREAMBLECONFIRMTO B43_PHY_OFDM(0x1E) /* PreambleConfirmTimeout */ +#define B43_LPPHY_CLIPTHRESH B43_PHY_OFDM(0x1F) /* ClipThresh */ +#define B43_LPPHY_CLIPCTRTHRESH B43_PHY_OFDM(0x20) /* ClipCtrThresh */ +#define B43_LPPHY_OFDMSYNCTIMER_CTL B43_PHY_OFDM(0x21) /* ofdmSyncTimer Control */ +#define B43_LPPHY_WAITFORPHYSELTO B43_PHY_OFDM(0x22) /* WaitforPHYSelTimeout */ +#define B43_LPPHY_HIGAINDB B43_PHY_OFDM(0x23) /* HiGainDB */ +#define B43_LPPHY_LOWGAINDB B43_PHY_OFDM(0x24) /* LowGainDB */ +#define B43_LPPHY_VERYLOWGAINDB B43_PHY_OFDM(0x25) /* VeryLowGainDB */ +#define B43_LPPHY_GAINMISMATCH B43_PHY_OFDM(0x26) /* gainMismatch */ +#define B43_LPPHY_GAINDIRECTMISMATCH B43_PHY_OFDM(0x27) /* gaindirectMismatch */ +#define B43_LPPHY_PWR_THRESH0 B43_PHY_OFDM(0x28) /* Power Thresh0 */ +#define B43_LPPHY_PWR_THRESH1 B43_PHY_OFDM(0x29) /* Power Thresh1 */ +#define B43_LPPHY_DETECTOR_DELAY_ADJUST B43_PHY_OFDM(0x2A) /* Detector Delay Adjust */ +#define B43_LPPHY_REDUCED_DETECTOR_DELAY B43_PHY_OFDM(0x2B) /* Reduced Detector Delay */ +#define B43_LPPHY_DATA_TO B43_PHY_OFDM(0x2C) /* data Timeout */ +#define B43_LPPHY_CORRELATOR_DIS_DELAY B43_PHY_OFDM(0x2D) /* correlator Dis Delay */ +#define B43_LPPHY_DIVERSITY_GAINBACK B43_PHY_OFDM(0x2E) /* Diversity GainBack */ +#define B43_LPPHY_DSSS_CONFIRM_CNT B43_PHY_OFDM(0x2F) /* DSSS Confirm Cnt */ +#define B43_LPPHY_DC_BLANK_INT B43_PHY_OFDM(0x30) /* DC Blank Interval */ +#define B43_LPPHY_GAIN_MISMATCH_LIMIT B43_PHY_OFDM(0x31) /* gain Mismatch Limit */ +#define B43_LPPHY_CRS_ED_THRESH B43_PHY_OFDM(0x32) /* crs ed thresh */ +#define B43_LPPHY_PHASE_SHIFT_CTL B43_PHY_OFDM(0x33) /* phase shift Control */ +#define B43_LPPHY_INPUT_PWRDB B43_PHY_OFDM(0x34) /* Input PowerDB */ +#define B43_LPPHY_OFDM_SYNC_CTL B43_PHY_OFDM(0x35) /* ofdm sync Control */ +#define B43_LPPHY_AFE_ADC_CTL_0 B43_PHY_OFDM(0x36) /* Afe ADC Control 0 */ +#define B43_LPPHY_AFE_ADC_CTL_1 B43_PHY_OFDM(0x37) /* Afe ADC Control 1 */ +#define B43_LPPHY_AFE_ADC_CTL_2 B43_PHY_OFDM(0x38) /* Afe ADC Control 2 */ +#define B43_LPPHY_AFE_DAC_CTL B43_PHY_OFDM(0x39) /* Afe DAC Control */ +#define B43_LPPHY_AFE_CTL B43_PHY_OFDM(0x3A) /* Afe Control */ +#define B43_LPPHY_AFE_CTL_OVR B43_PHY_OFDM(0x3B) /* Afe Control Ovr */ +#define B43_LPPHY_AFE_CTL_OVRVAL B43_PHY_OFDM(0x3C) /* Afe Control OvrVal */ +#define B43_LPPHY_AFE_RSSI_CTL_0 B43_PHY_OFDM(0x3D) /* Afe RSSI Control 0 */ +#define B43_LPPHY_AFE_RSSI_CTL_1 B43_PHY_OFDM(0x3E) /* Afe RSSI Control 1 */ +#define B43_LPPHY_AFE_RSSI_SEL B43_PHY_OFDM(0x3F) /* Afe RSSI Sel */ +#define B43_LPPHY_RADAR_THRESH B43_PHY_OFDM(0x40) /* Radar Thresh */ +#define B43_LPPHY_RADAR_BLANK_INT B43_PHY_OFDM(0x41) /* Radar blank Interval */ +#define B43_LPPHY_RADAR_MIN_FM_INT B43_PHY_OFDM(0x42) /* Radar min fm Interval */ +#define B43_LPPHY_RADAR_GAIN_TO B43_PHY_OFDM(0x43) /* Radar gain timeout */ +#define B43_LPPHY_RADAR_PULSE_TO B43_PHY_OFDM(0x44) /* Radar pulse timeout */ +#define B43_LPPHY_RADAR_DETECT_FM_CTL B43_PHY_OFDM(0x45) /* Radar detect FM Control */ +#define B43_LPPHY_RADAR_DETECT_EN B43_PHY_OFDM(0x46) /* Radar detect En */ +#define B43_LPPHY_RADAR_RD_DATA_REG B43_PHY_OFDM(0x47) /* Radar Rd Data Reg */ +#define B43_LPPHY_LP_PHY_CTL B43_PHY_OFDM(0x48) /* LP PHY Control */ +#define B43_LPPHY_CLASSIFIER_CTL B43_PHY_OFDM(0x49) /* classifier Control */ +#define B43_LPPHY_RESET_CTL B43_PHY_OFDM(0x4A) /* reset Control */ +#define B43_LPPHY_CLKEN_CTL B43_PHY_OFDM(0x4B) /* ClkEn Control */ +#define B43_LPPHY_RF_OVERRIDE_0 B43_PHY_OFDM(0x4C) /* RF Override 0 */ +#define B43_LPPHY_RF_OVERRIDE_VAL_0 B43_PHY_OFDM(0x4D) /* RF Override Val 0 */ +#define B43_LPPHY_TR_LOOKUP_1 B43_PHY_OFDM(0x4E) /* TR Lookup 1 */ +#define B43_LPPHY_TR_LOOKUP_2 B43_PHY_OFDM(0x4F) /* TR Lookup 2 */ +#define B43_LPPHY_RSSISELLOOKUP1 B43_PHY_OFDM(0x50) /* RssiSelLookup1 */ +#define B43_LPPHY_IQLO_CAL_CMD B43_PHY_OFDM(0x51) /* iqlo Cal Cmd */ +#define B43_LPPHY_IQLO_CAL_CMD_N_NUM B43_PHY_OFDM(0x52) /* iqlo Cal Cmd N num */ +#define B43_LPPHY_IQLO_CAL_CMD_G_CTL B43_PHY_OFDM(0x53) /* iqlo Cal Cmd G control */ +#define B43_LPPHY_MACINT_DBG_REGISTER B43_PHY_OFDM(0x54) /* macint Debug Register */ +#define B43_LPPHY_TABLE_ADDR B43_PHY_OFDM(0x55) /* Table Address */ +#define B43_LPPHY_TABLEDATALO B43_PHY_OFDM(0x56) /* TabledataLo */ +#define B43_LPPHY_TABLEDATAHI B43_PHY_OFDM(0x57) /* TabledataHi */ +#define B43_LPPHY_PHY_CRS_ENABLE_ADDR B43_PHY_OFDM(0x58) /* phy CRS Enable Address */ +#define B43_LPPHY_IDLETIME_CTL B43_PHY_OFDM(0x59) /* Idletime Control */ +#define B43_LPPHY_IDLETIME_CRS_ON_LO B43_PHY_OFDM(0x5A) /* Idletime CRS On Lo */ +#define B43_LPPHY_IDLETIME_CRS_ON_HI B43_PHY_OFDM(0x5B) /* Idletime CRS On Hi */ +#define B43_LPPHY_IDLETIME_MEAS_TIME_LO B43_PHY_OFDM(0x5C) /* Idletime Meas Time Lo */ +#define B43_LPPHY_IDLETIME_MEAS_TIME_HI B43_PHY_OFDM(0x5D) /* Idletime Meas Time Hi */ +#define B43_LPPHY_RESET_LEN_OFDM_TX_ADDR B43_PHY_OFDM(0x5E) /* Reset len Ofdm TX Address */ +#define B43_LPPHY_RESET_LEN_OFDM_RX_ADDR B43_PHY_OFDM(0x5F) /* Reset len Ofdm RX Address */ +#define B43_LPPHY_REG_CRS_ENABLE B43_PHY_OFDM(0x60) /* reg crs enable */ +#define B43_LPPHY_PLCP_TMT_STR0_CTR_MIN B43_PHY_OFDM(0x61) /* PLCP Tmt Str0 Ctr Min */ +#define B43_LPPHY_PKT_FSM_RESET_LEN_VAL B43_PHY_OFDM(0x62) /* Pkt fsm Reset Len Value */ +#define B43_LPPHY_READSYM2RESET_CTL B43_PHY_OFDM(0x63) /* readsym2reset Control */ +#define B43_LPPHY_DC_FILTER_DELAY1 B43_PHY_OFDM(0x64) /* Dc filter delay1 */ +#define B43_LPPHY_PACKET_RX_ACTIVE_TO B43_PHY_OFDM(0x65) /* packet rx Active timeout */ +#define B43_LPPHY_ED_TOVAL B43_PHY_OFDM(0x66) /* ed timeoutValue */ +#define B43_LPPHY_HOLD_CRS_ON_VAL B43_PHY_OFDM(0x67) /* hold CRS On Value */ +#define B43_LPPHY_OFDM_TX_PHY_CRS_DELAY_VAL B43_PHY_OFDM(0x69) /* ofdm tx phy CRS Delay Value */ +#define B43_LPPHY_CCK_TX_PHY_CRS_DELAY_VAL B43_PHY_OFDM(0x6A) /* cck tx phy CRS Delay Value */ +#define B43_LPPHY_ED_ON_CONFIRM_TIMER_VAL B43_PHY_OFDM(0x6B) /* Ed on confirm Timer Value */ +#define B43_LPPHY_ED_OFFSET_CONFIRM_TIMER_VAL B43_PHY_OFDM(0x6C) /* Ed offset confirm Timer Value */ +#define B43_LPPHY_PHY_CRS_OFFSET_TIMER_VAL B43_PHY_OFDM(0x6D) /* phy CRS offset Timer Value */ +#define B43_LPPHY_ADC_COMPENSATION_CTL B43_PHY_OFDM(0x70) /* ADC Compensation Control */ +#define B43_LPPHY_LOG2_RBPSK_ADDR B43_PHY_OFDM(0x71) /* log2 RBPSK Address */ +#define B43_LPPHY_LOG2_RQPSK_ADDR B43_PHY_OFDM(0x72) /* log2 RQPSK Address */ +#define B43_LPPHY_LOG2_R16QAM_ADDR B43_PHY_OFDM(0x73) /* log2 R16QAM Address */ +#define B43_LPPHY_LOG2_R64QAM_ADDR B43_PHY_OFDM(0x74) /* log2 R64QAM Address */ +#define B43_LPPHY_OFFSET_BPSK_ADDR B43_PHY_OFDM(0x75) /* offset BPSK Address */ +#define B43_LPPHY_OFFSET_QPSK_ADDR B43_PHY_OFDM(0x76) /* offset QPSK Address */ +#define B43_LPPHY_OFFSET_16QAM_ADDR B43_PHY_OFDM(0x77) /* offset 16QAM Address */ +#define B43_LPPHY_OFFSET_64QAM_ADDR B43_PHY_OFDM(0x78) /* offset 64QAM Address */ +#define B43_LPPHY_ALPHA1 B43_PHY_OFDM(0x79) /* Alpha1 */ +#define B43_LPPHY_ALPHA2 B43_PHY_OFDM(0x7A) /* Alpha2 */ +#define B43_LPPHY_BETA1 B43_PHY_OFDM(0x7B) /* Beta1 */ +#define B43_LPPHY_BETA2 B43_PHY_OFDM(0x7C) /* Beta2 */ +#define B43_LPPHY_LOOP_NUM_ADDR B43_PHY_OFDM(0x7D) /* Loop Num Address */ +#define B43_LPPHY_STR_COLLMAX_SMPL_ADDR B43_PHY_OFDM(0x7E) /* Str Collmax Sample Address */ +#define B43_LPPHY_MAX_SMPL_COARSE_FINE_ADDR B43_PHY_OFDM(0x7F) /* Max Sample Coarse/Fine Address */ +#define B43_LPPHY_MAX_SMPL_COARSE_STR0CTR_ADDR B43_PHY_OFDM(0x80) /* Max Sample Coarse/Str0Ctr Address */ +#define B43_LPPHY_IQ_ENABLE_WAIT_TIME_ADDR B43_PHY_OFDM(0x81) /* IQ Enable Wait Time Address */ +#define B43_LPPHY_IQ_NUM_SMPLS_ADDR B43_PHY_OFDM(0x82) /* IQ Num Samples Address */ +#define B43_LPPHY_IQ_ACC_HI_ADDR B43_PHY_OFDM(0x83) /* IQ Acc Hi Address */ +#define B43_LPPHY_IQ_ACC_LO_ADDR B43_PHY_OFDM(0x84) /* IQ Acc Lo Address */ +#define B43_LPPHY_IQ_I_PWR_ACC_HI_ADDR B43_PHY_OFDM(0x85) /* IQ I PWR Acc Hi Address */ +#define B43_LPPHY_IQ_I_PWR_ACC_LO_ADDR B43_PHY_OFDM(0x86) /* IQ I PWR Acc Lo Address */ +#define B43_LPPHY_IQ_Q_PWR_ACC_HI_ADDR B43_PHY_OFDM(0x87) /* IQ Q PWR Acc Hi Address */ +#define B43_LPPHY_IQ_Q_PWR_ACC_LO_ADDR B43_PHY_OFDM(0x88) /* IQ Q PWR Acc Lo Address */ +#define B43_LPPHY_MAXNUMSTEPS B43_PHY_OFDM(0x89) /* MaxNumsteps */ +#define B43_LPPHY_ROTORPHASE_ADDR B43_PHY_OFDM(0x8A) /* RotorPhase Address */ +#define B43_LPPHY_ADVANCEDRETARDROTOR_ADDR B43_PHY_OFDM(0x8B) /* AdvancedRetardRotor Address */ +#define B43_LPPHY_RSSIADCDELAY_CTL_ADDR B43_PHY_OFDM(0x8D) /* rssiAdcdelay Control Address */ +#define B43_LPPHY_TSSISTAT_ADDR B43_PHY_OFDM(0x8E) /* tssiStatus Address */ +#define B43_LPPHY_TEMPSENSESTAT_ADDR B43_PHY_OFDM(0x8F) /* tempsenseStatus Address */ +#define B43_LPPHY_TEMPSENSE_CTL_ADDR B43_PHY_OFDM(0x90) /* tempsense Control Address */ +#define B43_LPPHY_WRSSISTAT_ADDR B43_PHY_OFDM(0x91) /* wrssistatus Address */ +#define B43_LPPHY_MUFACTORADDR B43_PHY_OFDM(0x92) /* mufactoraddr */ +#define B43_LPPHY_SCRAMSTATE_ADDR B43_PHY_OFDM(0x93) /* scramstate Address */ +#define B43_LPPHY_TXHOLDOFFADDR B43_PHY_OFDM(0x94) /* txholdoffaddr */ +#define B43_LPPHY_PKTGAINVAL_ADDR B43_PHY_OFDM(0x95) /* pktgainval Address */ +#define B43_LPPHY_COARSEESTIM_ADDR B43_PHY_OFDM(0x96) /* Coarseestim Address */ +#define B43_LPPHY_STATE_TRANSITION_ADDR B43_PHY_OFDM(0x97) /* state Transition Address */ +#define B43_LPPHY_TRN_OFFSET_ADDR B43_PHY_OFDM(0x98) /* TRN offset Address */ +#define B43_LPPHY_NUM_ROTOR_ADDR B43_PHY_OFDM(0x99) /* Num Rotor Address */ +#define B43_LPPHY_VITERBI_OFFSET_ADDR B43_PHY_OFDM(0x9A) /* Viterbi Offset Address */ +#define B43_LPPHY_SMPL_COLLECT_WAIT_ADDR B43_PHY_OFDM(0x9B) /* Sample collect wait Address */ +#define B43_LPPHY_A_PHY_CTL_ADDR B43_PHY_OFDM(0x9C) /* A PHY Control Address */ +#define B43_LPPHY_NUM_PASS_THROUGH_ADDR B43_PHY_OFDM(0x9D) /* Num Pass Through Address */ +#define B43_LPPHY_RX_COMP_COEFF_S B43_PHY_OFDM(0x9E) /* RX Comp coefficient(s) */ +#define B43_LPPHY_CPAROTATEVAL B43_PHY_OFDM(0x9F) /* cpaRotateValue */ +#define B43_LPPHY_SMPL_PLAY_COUNT B43_PHY_OFDM(0xA0) /* Sample play count */ +#define B43_LPPHY_SMPL_PLAY_BUFFER_CTL B43_PHY_OFDM(0xA1) /* Sample play Buffer Control */ +#define B43_LPPHY_FOURWIRE_CTL B43_PHY_OFDM(0xA2) /* fourwire Control */ +#define B43_LPPHY_CPA_TAILCOUNT_VAL B43_PHY_OFDM(0xA3) /* CPA TailCount Value */ +#define B43_LPPHY_TX_PWR_CTL_CMD B43_PHY_OFDM(0xA4) /* TX Power Control Cmd */ +#define B43_LPPHY_TX_PWR_CTL_CMD_MODE 0xE000 /* TX power control mode mask */ +#define B43_LPPHY_TX_PWR_CTL_CMD_MODE_OFF 0x0000 /* TX power control is OFF */ +#define B43_LPPHY_TX_PWR_CTL_CMD_MODE_SW 0x8000 /* TX power control is SOFTWARE */ +#define B43_LPPHY_TX_PWR_CTL_CMD_MODE_HW 0xE000 /* TX power control is HARDWARE */ +#define B43_LPPHY_TX_PWR_CTL_NNUM B43_PHY_OFDM(0xA5) /* TX Power Control Nnum */ +#define B43_LPPHY_TX_PWR_CTL_IDLETSSI B43_PHY_OFDM(0xA6) /* TX Power Control IdleTssi */ +#define B43_LPPHY_TX_PWR_CTL_TARGETPWR B43_PHY_OFDM(0xA7) /* TX Power Control TargetPower */ +#define B43_LPPHY_TX_PWR_CTL_DELTAPWR_LIMIT B43_PHY_OFDM(0xA8) /* TX Power Control DeltaPower Limit */ +#define B43_LPPHY_TX_PWR_CTL_BASEINDEX B43_PHY_OFDM(0xA9) /* TX Power Control BaseIndex */ +#define B43_LPPHY_TX_PWR_CTL_PWR_INDEX B43_PHY_OFDM(0xAA) /* TX Power Control Power Index */ +#define B43_LPPHY_TX_PWR_CTL_STAT B43_PHY_OFDM(0xAB) /* TX Power Control Status */ +#define B43_LPPHY_LP_RF_SIGNAL_LUT B43_PHY_OFDM(0xAC) /* LP RF signal LUT */ +#define B43_LPPHY_RX_RADIO_CTL_FILTER_STATE B43_PHY_OFDM(0xAD) /* RX Radio Control Filter State */ +#define B43_LPPHY_RX_RADIO_CTL B43_PHY_OFDM(0xAE) /* RX Radio Control */ +#define B43_LPPHY_NRSSI_STAT_ADDR B43_PHY_OFDM(0xAF) /* NRSSI status Address */ +#define B43_LPPHY_RF_OVERRIDE_2 B43_PHY_OFDM(0xB0) /* RF override 2 */ +#define B43_LPPHY_RF_OVERRIDE_2_VAL B43_PHY_OFDM(0xB1) /* RF override 2 val */ +#define B43_LPPHY_PS_CTL_OVERRIDE_VAL0 B43_PHY_OFDM(0xB2) /* PS Control override val0 */ +#define B43_LPPHY_PS_CTL_OVERRIDE_VAL1 B43_PHY_OFDM(0xB3) /* PS Control override val1 */ +#define B43_LPPHY_PS_CTL_OVERRIDE_VAL2 B43_PHY_OFDM(0xB4) /* PS Control override val2 */ +#define B43_LPPHY_TX_GAIN_CTL_OVERRIDE_VAL B43_PHY_OFDM(0xB5) /* TX gain Control override val */ +#define B43_LPPHY_RX_GAIN_CTL_OVERRIDE_VAL B43_PHY_OFDM(0xB6) /* RX gain Control override val */ +#define B43_LPPHY_AFE_DDFS B43_PHY_OFDM(0xB7) /* AFE DDFS */ +#define B43_LPPHY_AFE_DDFS_POINTER_INIT B43_PHY_OFDM(0xB8) /* AFE DDFS pointer init */ +#define B43_LPPHY_AFE_DDFS_INCR_INIT B43_PHY_OFDM(0xB9) /* AFE DDFS incr init */ +#define B43_LPPHY_MRCNOISEREDUCTION B43_PHY_OFDM(0xBA) /* mrcNoiseReduction */ +#define B43_LPPHY_TR_LOOKUP_3 B43_PHY_OFDM(0xBB) /* TR Lookup 3 */ +#define B43_LPPHY_TR_LOOKUP_4 B43_PHY_OFDM(0xBC) /* TR Lookup 4 */ +#define B43_LPPHY_RADAR_FIFO_STAT B43_PHY_OFDM(0xBD) /* Radar FIFO Status */ +#define B43_LPPHY_GPIO_OUTEN B43_PHY_OFDM(0xBE) /* GPIO Out enable */ +#define B43_LPPHY_GPIO_SELECT B43_PHY_OFDM(0xBF) /* GPIO Select */ +#define B43_LPPHY_GPIO_OUT B43_PHY_OFDM(0xC0) /* GPIO Out */ +#define B43_LPPHY_4C3 B43_PHY_OFDM(0xC3) /* unknown, used during BB init */ +#define B43_LPPHY_4C4 B43_PHY_OFDM(0xC4) /* unknown, used during BB init */ +#define B43_LPPHY_4C5 B43_PHY_OFDM(0xC5) /* unknown, used during BB init */ +#define B43_LPPHY_TR_LOOKUP_5 B43_PHY_OFDM(0xC7) /* TR Lookup 5 */ +#define B43_LPPHY_TR_LOOKUP_6 B43_PHY_OFDM(0xC8) /* TR Lookup 6 */ +#define B43_LPPHY_TR_LOOKUP_7 B43_PHY_OFDM(0xC9) /* TR Lookup 7 */ +#define B43_LPPHY_TR_LOOKUP_8 B43_PHY_OFDM(0xCA) /* TR Lookup 8 */ +#define B43_LPPHY_RF_PWR_OVERRIDE B43_PHY_OFDM(0xD3) /* RF power override */ + + + +/* Radio register access decorators. */ +#define B43_LP_RADIO(radio_reg) (radio_reg) +#define B43_LP_NORTH(radio_reg) B43_LP_RADIO(radio_reg) +#define B43_LP_SOUTH(radio_reg) B43_LP_RADIO((radio_reg) | 0x4000) + + +/*** Broadcom 2062 NORTH radio registers ***/ +#define B2062_N_COMM1 B43_LP_NORTH(0x000) /* Common 01 (north) */ +#define B2062_N_COMM2 B43_LP_NORTH(0x002) /* Common 02 (north) */ +#define B2062_N_COMM3 B43_LP_NORTH(0x003) /* Common 03 (north) */ +#define B2062_N_COMM4 B43_LP_NORTH(0x004) /* Common 04 (north) */ +#define B2062_N_COMM5 B43_LP_NORTH(0x005) /* Common 05 (north) */ +#define B2062_N_COMM6 B43_LP_NORTH(0x006) /* Common 06 (north) */ +#define B2062_N_COMM7 B43_LP_NORTH(0x007) /* Common 07 (north) */ +#define B2062_N_COMM8 B43_LP_NORTH(0x008) /* Common 08 (north) */ +#define B2062_N_COMM9 B43_LP_NORTH(0x009) /* Common 09 (north) */ +#define B2062_N_COMM10 B43_LP_NORTH(0x00A) /* Common 10 (north) */ +#define B2062_N_COMM11 B43_LP_NORTH(0x00B) /* Common 11 (north) */ +#define B2062_N_COMM12 B43_LP_NORTH(0x00C) /* Common 12 (north) */ +#define B2062_N_COMM13 B43_LP_NORTH(0x00D) /* Common 13 (north) */ +#define B2062_N_COMM14 B43_LP_NORTH(0x00E) /* Common 14 (north) */ +#define B2062_N_COMM15 B43_LP_NORTH(0x00F) /* Common 15 (north) */ +#define B2062_N_PDN_CTL0 B43_LP_NORTH(0x010) /* PDN Control 0 (north) */ +#define B2062_N_PDN_CTL1 B43_LP_NORTH(0x011) /* PDN Control 1 (north) */ +#define B2062_N_PDN_CTL2 B43_LP_NORTH(0x012) /* PDN Control 2 (north) */ +#define B2062_N_PDN_CTL3 B43_LP_NORTH(0x013) /* PDN Control 3 (north) */ +#define B2062_N_PDN_CTL4 B43_LP_NORTH(0x014) /* PDN Control 4 (north) */ +#define B2062_N_GEN_CTL0 B43_LP_NORTH(0x015) /* GEN Control 0 (north) */ +#define B2062_N_IQ_CALIB B43_LP_NORTH(0x016) /* IQ Calibration (north) */ +#define B2062_N_LGENC B43_LP_NORTH(0x017) /* LGENC (north) */ +#define B2062_N_LGENA_LPF B43_LP_NORTH(0x018) /* LGENA LPF (north) */ +#define B2062_N_LGENA_BIAS0 B43_LP_NORTH(0x019) /* LGENA Bias 0 (north) */ +#define B2062_N_LGNEA_BIAS1 B43_LP_NORTH(0x01A) /* LGNEA Bias 1 (north) */ +#define B2062_N_LGENA_CTL0 B43_LP_NORTH(0x01B) /* LGENA Control 0 (north) */ +#define B2062_N_LGENA_CTL1 B43_LP_NORTH(0x01C) /* LGENA Control 1 (north) */ +#define B2062_N_LGENA_CTL2 B43_LP_NORTH(0x01D) /* LGENA Control 2 (north) */ +#define B2062_N_LGENA_TUNE0 B43_LP_NORTH(0x01E) /* LGENA Tune 0 (north) */ +#define B2062_N_LGENA_TUNE1 B43_LP_NORTH(0x01F) /* LGENA Tune 1 (north) */ +#define B2062_N_LGENA_TUNE2 B43_LP_NORTH(0x020) /* LGENA Tune 2 (north) */ +#define B2062_N_LGENA_TUNE3 B43_LP_NORTH(0x021) /* LGENA Tune 3 (north) */ +#define B2062_N_LGENA_CTL3 B43_LP_NORTH(0x022) /* LGENA Control 3 (north) */ +#define B2062_N_LGENA_CTL4 B43_LP_NORTH(0x023) /* LGENA Control 4 (north) */ +#define B2062_N_LGENA_CTL5 B43_LP_NORTH(0x024) /* LGENA Control 5 (north) */ +#define B2062_N_LGENA_CTL6 B43_LP_NORTH(0x025) /* LGENA Control 6 (north) */ +#define B2062_N_LGENA_CTL7 B43_LP_NORTH(0x026) /* LGENA Control 7 (north) */ +#define B2062_N_RXA_CTL0 B43_LP_NORTH(0x027) /* RXA Control 0 (north) */ +#define B2062_N_RXA_CTL1 B43_LP_NORTH(0x028) /* RXA Control 1 (north) */ +#define B2062_N_RXA_CTL2 B43_LP_NORTH(0x029) /* RXA Control 2 (north) */ +#define B2062_N_RXA_CTL3 B43_LP_NORTH(0x02A) /* RXA Control 3 (north) */ +#define B2062_N_RXA_CTL4 B43_LP_NORTH(0x02B) /* RXA Control 4 (north) */ +#define B2062_N_RXA_CTL5 B43_LP_NORTH(0x02C) /* RXA Control 5 (north) */ +#define B2062_N_RXA_CTL6 B43_LP_NORTH(0x02D) /* RXA Control 6 (north) */ +#define B2062_N_RXA_CTL7 B43_LP_NORTH(0x02E) /* RXA Control 7 (north) */ +#define B2062_N_RXBB_CTL0 B43_LP_NORTH(0x02F) /* RXBB Control 0 (north) */ +#define B2062_N_RXBB_CTL1 B43_LP_NORTH(0x030) /* RXBB Control 1 (north) */ +#define B2062_N_RXBB_CTL2 B43_LP_NORTH(0x031) /* RXBB Control 2 (north) */ +#define B2062_N_RXBB_GAIN0 B43_LP_NORTH(0x032) /* RXBB Gain 0 (north) */ +#define B2062_N_RXBB_GAIN1 B43_LP_NORTH(0x033) /* RXBB Gain 1 (north) */ +#define B2062_N_RXBB_GAIN2 B43_LP_NORTH(0x034) /* RXBB Gain 2 (north) */ +#define B2062_N_RXBB_GAIN3 B43_LP_NORTH(0x035) /* RXBB Gain 3 (north) */ +#define B2062_N_RXBB_RSSI0 B43_LP_NORTH(0x036) /* RXBB RSSI 0 (north) */ +#define B2062_N_RXBB_RSSI1 B43_LP_NORTH(0x037) /* RXBB RSSI 1 (north) */ +#define B2062_N_RXBB_CALIB0 B43_LP_NORTH(0x038) /* RXBB Calibration0 (north) */ +#define B2062_N_RXBB_CALIB1 B43_LP_NORTH(0x039) /* RXBB Calibration1 (north) */ +#define B2062_N_RXBB_CALIB2 B43_LP_NORTH(0x03A) /* RXBB Calibration2 (north) */ +#define B2062_N_RXBB_BIAS0 B43_LP_NORTH(0x03B) /* RXBB Bias 0 (north) */ +#define B2062_N_RXBB_BIAS1 B43_LP_NORTH(0x03C) /* RXBB Bias 1 (north) */ +#define B2062_N_RXBB_BIAS2 B43_LP_NORTH(0x03D) /* RXBB Bias 2 (north) */ +#define B2062_N_RXBB_BIAS3 B43_LP_NORTH(0x03E) /* RXBB Bias 3 (north) */ +#define B2062_N_RXBB_BIAS4 B43_LP_NORTH(0x03F) /* RXBB Bias 4 (north) */ +#define B2062_N_RXBB_BIAS5 B43_LP_NORTH(0x040) /* RXBB Bias 5 (north) */ +#define B2062_N_RXBB_RSSI2 B43_LP_NORTH(0x041) /* RXBB RSSI 2 (north) */ +#define B2062_N_RXBB_RSSI3 B43_LP_NORTH(0x042) /* RXBB RSSI 3 (north) */ +#define B2062_N_RXBB_RSSI4 B43_LP_NORTH(0x043) /* RXBB RSSI 4 (north) */ +#define B2062_N_RXBB_RSSI5 B43_LP_NORTH(0x044) /* RXBB RSSI 5 (north) */ +#define B2062_N_TX_CTL0 B43_LP_NORTH(0x045) /* TX Control 0 (north) */ +#define B2062_N_TX_CTL1 B43_LP_NORTH(0x046) /* TX Control 1 (north) */ +#define B2062_N_TX_CTL2 B43_LP_NORTH(0x047) /* TX Control 2 (north) */ +#define B2062_N_TX_CTL3 B43_LP_NORTH(0x048) /* TX Control 3 (north) */ +#define B2062_N_TX_CTL4 B43_LP_NORTH(0x049) /* TX Control 4 (north) */ +#define B2062_N_TX_CTL5 B43_LP_NORTH(0x04A) /* TX Control 5 (north) */ +#define B2062_N_TX_CTL6 B43_LP_NORTH(0x04B) /* TX Control 6 (north) */ +#define B2062_N_TX_CTL7 B43_LP_NORTH(0x04C) /* TX Control 7 (north) */ +#define B2062_N_TX_CTL8 B43_LP_NORTH(0x04D) /* TX Control 8 (north) */ +#define B2062_N_TX_CTL9 B43_LP_NORTH(0x04E) /* TX Control 9 (north) */ +#define B2062_N_TX_CTL_A B43_LP_NORTH(0x04F) /* TX Control A (north) */ +#define B2062_N_TX_GC2G B43_LP_NORTH(0x050) /* TX GC2G (north) */ +#define B2062_N_TX_GC5G B43_LP_NORTH(0x051) /* TX GC5G (north) */ +#define B2062_N_TX_TUNE B43_LP_NORTH(0x052) /* TX Tune (north) */ +#define B2062_N_TX_PAD B43_LP_NORTH(0x053) /* TX PAD (north) */ +#define B2062_N_TX_PGA B43_LP_NORTH(0x054) /* TX PGA (north) */ +#define B2062_N_TX_PADAUX B43_LP_NORTH(0x055) /* TX PADAUX (north) */ +#define B2062_N_TX_PGAAUX B43_LP_NORTH(0x056) /* TX PGAAUX (north) */ +#define B2062_N_TSSI_CTL0 B43_LP_NORTH(0x057) /* TSSI Control 0 (north) */ +#define B2062_N_TSSI_CTL1 B43_LP_NORTH(0x058) /* TSSI Control 1 (north) */ +#define B2062_N_TSSI_CTL2 B43_LP_NORTH(0x059) /* TSSI Control 2 (north) */ +#define B2062_N_IQ_CALIB_CTL0 B43_LP_NORTH(0x05A) /* IQ Calibration Control 0 (north) */ +#define B2062_N_IQ_CALIB_CTL1 B43_LP_NORTH(0x05B) /* IQ Calibration Control 1 (north) */ +#define B2062_N_IQ_CALIB_CTL2 B43_LP_NORTH(0x05C) /* IQ Calibration Control 2 (north) */ +#define B2062_N_CALIB_TS B43_LP_NORTH(0x05D) /* Calibration TS (north) */ +#define B2062_N_CALIB_CTL0 B43_LP_NORTH(0x05E) /* Calibration Control 0 (north) */ +#define B2062_N_CALIB_CTL1 B43_LP_NORTH(0x05F) /* Calibration Control 1 (north) */ +#define B2062_N_CALIB_CTL2 B43_LP_NORTH(0x060) /* Calibration Control 2 (north) */ +#define B2062_N_CALIB_CTL3 B43_LP_NORTH(0x061) /* Calibration Control 3 (north) */ +#define B2062_N_CALIB_CTL4 B43_LP_NORTH(0x062) /* Calibration Control 4 (north) */ +#define B2062_N_CALIB_DBG0 B43_LP_NORTH(0x063) /* Calibration Debug 0 (north) */ +#define B2062_N_CALIB_DBG1 B43_LP_NORTH(0x064) /* Calibration Debug 1 (north) */ +#define B2062_N_CALIB_DBG2 B43_LP_NORTH(0x065) /* Calibration Debug 2 (north) */ +#define B2062_N_CALIB_DBG3 B43_LP_NORTH(0x066) /* Calibration Debug 3 (north) */ +#define B2062_N_PSENSE_CTL0 B43_LP_NORTH(0x069) /* PSENSE Control 0 (north) */ +#define B2062_N_PSENSE_CTL1 B43_LP_NORTH(0x06A) /* PSENSE Control 1 (north) */ +#define B2062_N_PSENSE_CTL2 B43_LP_NORTH(0x06B) /* PSENSE Control 2 (north) */ +#define B2062_N_TEST_BUF0 B43_LP_NORTH(0x06C) /* TEST BUF0 (north) */ + +/*** Broadcom 2062 SOUTH radio registers ***/ +#define B2062_S_COMM1 B43_LP_SOUTH(0x000) /* Common 01 (south) */ +#define B2062_S_RADIO_ID_CODE B43_LP_SOUTH(0x001) /* Radio ID code (south) */ +#define B2062_S_COMM2 B43_LP_SOUTH(0x002) /* Common 02 (south) */ +#define B2062_S_COMM3 B43_LP_SOUTH(0x003) /* Common 03 (south) */ +#define B2062_S_COMM4 B43_LP_SOUTH(0x004) /* Common 04 (south) */ +#define B2062_S_COMM5 B43_LP_SOUTH(0x005) /* Common 05 (south) */ +#define B2062_S_COMM6 B43_LP_SOUTH(0x006) /* Common 06 (south) */ +#define B2062_S_COMM7 B43_LP_SOUTH(0x007) /* Common 07 (south) */ +#define B2062_S_COMM8 B43_LP_SOUTH(0x008) /* Common 08 (south) */ +#define B2062_S_COMM9 B43_LP_SOUTH(0x009) /* Common 09 (south) */ +#define B2062_S_COMM10 B43_LP_SOUTH(0x00A) /* Common 10 (south) */ +#define B2062_S_COMM11 B43_LP_SOUTH(0x00B) /* Common 11 (south) */ +#define B2062_S_COMM12 B43_LP_SOUTH(0x00C) /* Common 12 (south) */ +#define B2062_S_COMM13 B43_LP_SOUTH(0x00D) /* Common 13 (south) */ +#define B2062_S_COMM14 B43_LP_SOUTH(0x00E) /* Common 14 (south) */ +#define B2062_S_COMM15 B43_LP_SOUTH(0x00F) /* Common 15 (south) */ +#define B2062_S_PDS_CTL0 B43_LP_SOUTH(0x010) /* PDS Control 0 (south) */ +#define B2062_S_PDS_CTL1 B43_LP_SOUTH(0x011) /* PDS Control 1 (south) */ +#define B2062_S_PDS_CTL2 B43_LP_SOUTH(0x012) /* PDS Control 2 (south) */ +#define B2062_S_PDS_CTL3 B43_LP_SOUTH(0x013) /* PDS Control 3 (south) */ +#define B2062_S_BG_CTL0 B43_LP_SOUTH(0x014) /* BG Control 0 (south) */ +#define B2062_S_BG_CTL1 B43_LP_SOUTH(0x015) /* BG Control 1 (south) */ +#define B2062_S_BG_CTL2 B43_LP_SOUTH(0x016) /* BG Control 2 (south) */ +#define B2062_S_LGENG_CTL0 B43_LP_SOUTH(0x017) /* LGENG Control 00 (south) */ +#define B2062_S_LGENG_CTL1 B43_LP_SOUTH(0x018) /* LGENG Control 01 (south) */ +#define B2062_S_LGENG_CTL2 B43_LP_SOUTH(0x019) /* LGENG Control 02 (south) */ +#define B2062_S_LGENG_CTL3 B43_LP_SOUTH(0x01A) /* LGENG Control 03 (south) */ +#define B2062_S_LGENG_CTL4 B43_LP_SOUTH(0x01B) /* LGENG Control 04 (south) */ +#define B2062_S_LGENG_CTL5 B43_LP_SOUTH(0x01C) /* LGENG Control 05 (south) */ +#define B2062_S_LGENG_CTL6 B43_LP_SOUTH(0x01D) /* LGENG Control 06 (south) */ +#define B2062_S_LGENG_CTL7 B43_LP_SOUTH(0x01E) /* LGENG Control 07 (south) */ +#define B2062_S_LGENG_CTL8 B43_LP_SOUTH(0x01F) /* LGENG Control 08 (south) */ +#define B2062_S_LGENG_CTL9 B43_LP_SOUTH(0x020) /* LGENG Control 09 (south) */ +#define B2062_S_LGENG_CTL10 B43_LP_SOUTH(0x021) /* LGENG Control 10 (south) */ +#define B2062_S_LGENG_CTL11 B43_LP_SOUTH(0x022) /* LGENG Control 11 (south) */ +#define B2062_S_REFPLL_CTL0 B43_LP_SOUTH(0x023) /* REFPLL Control 00 (south) */ +#define B2062_S_REFPLL_CTL1 B43_LP_SOUTH(0x024) /* REFPLL Control 01 (south) */ +#define B2062_S_REFPLL_CTL2 B43_LP_SOUTH(0x025) /* REFPLL Control 02 (south) */ +#define B2062_S_REFPLL_CTL3 B43_LP_SOUTH(0x026) /* REFPLL Control 03 (south) */ +#define B2062_S_REFPLL_CTL4 B43_LP_SOUTH(0x027) /* REFPLL Control 04 (south) */ +#define B2062_S_REFPLL_CTL5 B43_LP_SOUTH(0x028) /* REFPLL Control 05 (south) */ +#define B2062_S_REFPLL_CTL6 B43_LP_SOUTH(0x029) /* REFPLL Control 06 (south) */ +#define B2062_S_REFPLL_CTL7 B43_LP_SOUTH(0x02A) /* REFPLL Control 07 (south) */ +#define B2062_S_REFPLL_CTL8 B43_LP_SOUTH(0x02B) /* REFPLL Control 08 (south) */ +#define B2062_S_REFPLL_CTL9 B43_LP_SOUTH(0x02C) /* REFPLL Control 09 (south) */ +#define B2062_S_REFPLL_CTL10 B43_LP_SOUTH(0x02D) /* REFPLL Control 10 (south) */ +#define B2062_S_REFPLL_CTL11 B43_LP_SOUTH(0x02E) /* REFPLL Control 11 (south) */ +#define B2062_S_REFPLL_CTL12 B43_LP_SOUTH(0x02F) /* REFPLL Control 12 (south) */ +#define B2062_S_REFPLL_CTL13 B43_LP_SOUTH(0x030) /* REFPLL Control 13 (south) */ +#define B2062_S_REFPLL_CTL14 B43_LP_SOUTH(0x031) /* REFPLL Control 14 (south) */ +#define B2062_S_REFPLL_CTL15 B43_LP_SOUTH(0x032) /* REFPLL Control 15 (south) */ +#define B2062_S_REFPLL_CTL16 B43_LP_SOUTH(0x033) /* REFPLL Control 16 (south) */ +#define B2062_S_RFPLL_CTL0 B43_LP_SOUTH(0x034) /* RFPLL Control 00 (south) */ +#define B2062_S_RFPLL_CTL1 B43_LP_SOUTH(0x035) /* RFPLL Control 01 (south) */ +#define B2062_S_RFPLL_CTL2 B43_LP_SOUTH(0x036) /* RFPLL Control 02 (south) */ +#define B2062_S_RFPLL_CTL3 B43_LP_SOUTH(0x037) /* RFPLL Control 03 (south) */ +#define B2062_S_RFPLL_CTL4 B43_LP_SOUTH(0x038) /* RFPLL Control 04 (south) */ +#define B2062_S_RFPLL_CTL5 B43_LP_SOUTH(0x039) /* RFPLL Control 05 (south) */ +#define B2062_S_RFPLL_CTL6 B43_LP_SOUTH(0x03A) /* RFPLL Control 06 (south) */ +#define B2062_S_RFPLL_CTL7 B43_LP_SOUTH(0x03B) /* RFPLL Control 07 (south) */ +#define B2062_S_RFPLL_CTL8 B43_LP_SOUTH(0x03C) /* RFPLL Control 08 (south) */ +#define B2062_S_RFPLL_CTL9 B43_LP_SOUTH(0x03D) /* RFPLL Control 09 (south) */ +#define B2062_S_RFPLL_CTL10 B43_LP_SOUTH(0x03E) /* RFPLL Control 10 (south) */ +#define B2062_S_RFPLL_CTL11 B43_LP_SOUTH(0x03F) /* RFPLL Control 11 (south) */ +#define B2062_S_RFPLL_CTL12 B43_LP_SOUTH(0x040) /* RFPLL Control 12 (south) */ +#define B2062_S_RFPLL_CTL13 B43_LP_SOUTH(0x041) /* RFPLL Control 13 (south) */ +#define B2062_S_RFPLL_CTL14 B43_LP_SOUTH(0x042) /* RFPLL Control 14 (south) */ +#define B2062_S_RFPLL_CTL15 B43_LP_SOUTH(0x043) /* RFPLL Control 15 (south) */ +#define B2062_S_RFPLL_CTL16 B43_LP_SOUTH(0x044) /* RFPLL Control 16 (south) */ +#define B2062_S_RFPLL_CTL17 B43_LP_SOUTH(0x045) /* RFPLL Control 17 (south) */ +#define B2062_S_RFPLL_CTL18 B43_LP_SOUTH(0x046) /* RFPLL Control 18 (south) */ +#define B2062_S_RFPLL_CTL19 B43_LP_SOUTH(0x047) /* RFPLL Control 19 (south) */ +#define B2062_S_RFPLL_CTL20 B43_LP_SOUTH(0x048) /* RFPLL Control 20 (south) */ +#define B2062_S_RFPLL_CTL21 B43_LP_SOUTH(0x049) /* RFPLL Control 21 (south) */ +#define B2062_S_RFPLL_CTL22 B43_LP_SOUTH(0x04A) /* RFPLL Control 22 (south) */ +#define B2062_S_RFPLL_CTL23 B43_LP_SOUTH(0x04B) /* RFPLL Control 23 (south) */ +#define B2062_S_RFPLL_CTL24 B43_LP_SOUTH(0x04C) /* RFPLL Control 24 (south) */ +#define B2062_S_RFPLL_CTL25 B43_LP_SOUTH(0x04D) /* RFPLL Control 25 (south) */ +#define B2062_S_RFPLL_CTL26 B43_LP_SOUTH(0x04E) /* RFPLL Control 26 (south) */ +#define B2062_S_RFPLL_CTL27 B43_LP_SOUTH(0x04F) /* RFPLL Control 27 (south) */ +#define B2062_S_RFPLL_CTL28 B43_LP_SOUTH(0x050) /* RFPLL Control 28 (south) */ +#define B2062_S_RFPLL_CTL29 B43_LP_SOUTH(0x051) /* RFPLL Control 29 (south) */ +#define B2062_S_RFPLL_CTL30 B43_LP_SOUTH(0x052) /* RFPLL Control 30 (south) */ +#define B2062_S_RFPLL_CTL31 B43_LP_SOUTH(0x053) /* RFPLL Control 31 (south) */ +#define B2062_S_RFPLL_CTL32 B43_LP_SOUTH(0x054) /* RFPLL Control 32 (south) */ +#define B2062_S_RFPLL_CTL33 B43_LP_SOUTH(0x055) /* RFPLL Control 33 (south) */ +#define B2062_S_RFPLL_CTL34 B43_LP_SOUTH(0x056) /* RFPLL Control 34 (south) */ +#define B2062_S_RXG_CNT0 B43_LP_SOUTH(0x057) /* RXG Counter 00 (south) */ +#define B2062_S_RXG_CNT1 B43_LP_SOUTH(0x058) /* RXG Counter 01 (south) */ +#define B2062_S_RXG_CNT2 B43_LP_SOUTH(0x059) /* RXG Counter 02 (south) */ +#define B2062_S_RXG_CNT3 B43_LP_SOUTH(0x05A) /* RXG Counter 03 (south) */ +#define B2062_S_RXG_CNT4 B43_LP_SOUTH(0x05B) /* RXG Counter 04 (south) */ +#define B2062_S_RXG_CNT5 B43_LP_SOUTH(0x05C) /* RXG Counter 05 (south) */ +#define B2062_S_RXG_CNT6 B43_LP_SOUTH(0x05D) /* RXG Counter 06 (south) */ +#define B2062_S_RXG_CNT7 B43_LP_SOUTH(0x05E) /* RXG Counter 07 (south) */ +#define B2062_S_RXG_CNT8 B43_LP_SOUTH(0x05F) /* RXG Counter 08 (south) */ +#define B2062_S_RXG_CNT9 B43_LP_SOUTH(0x060) /* RXG Counter 09 (south) */ +#define B2062_S_RXG_CNT10 B43_LP_SOUTH(0x061) /* RXG Counter 10 (south) */ +#define B2062_S_RXG_CNT11 B43_LP_SOUTH(0x062) /* RXG Counter 11 (south) */ +#define B2062_S_RXG_CNT12 B43_LP_SOUTH(0x063) /* RXG Counter 12 (south) */ +#define B2062_S_RXG_CNT13 B43_LP_SOUTH(0x064) /* RXG Counter 13 (south) */ +#define B2062_S_RXG_CNT14 B43_LP_SOUTH(0x065) /* RXG Counter 14 (south) */ +#define B2062_S_RXG_CNT15 B43_LP_SOUTH(0x066) /* RXG Counter 15 (south) */ +#define B2062_S_RXG_CNT16 B43_LP_SOUTH(0x067) /* RXG Counter 16 (south) */ +#define B2062_S_RXG_CNT17 B43_LP_SOUTH(0x068) /* RXG Counter 17 (south) */ + + + +/*** Broadcom 2063 radio registers ***/ +#define B2063_RADIO_ID_CODE B43_LP_RADIO(0x001) /* Radio ID code */ +#define B2063_COMM1 B43_LP_RADIO(0x000) /* Common 01 */ +#define B2063_COMM2 B43_LP_RADIO(0x002) /* Common 02 */ +#define B2063_COMM3 B43_LP_RADIO(0x003) /* Common 03 */ +#define B2063_COMM4 B43_LP_RADIO(0x004) /* Common 04 */ +#define B2063_COMM5 B43_LP_RADIO(0x005) /* Common 05 */ +#define B2063_COMM6 B43_LP_RADIO(0x006) /* Common 06 */ +#define B2063_COMM7 B43_LP_RADIO(0x007) /* Common 07 */ +#define B2063_COMM8 B43_LP_RADIO(0x008) /* Common 08 */ +#define B2063_COMM9 B43_LP_RADIO(0x009) /* Common 09 */ +#define B2063_COMM10 B43_LP_RADIO(0x00A) /* Common 10 */ +#define B2063_COMM11 B43_LP_RADIO(0x00B) /* Common 11 */ +#define B2063_COMM12 B43_LP_RADIO(0x00C) /* Common 12 */ +#define B2063_COMM13 B43_LP_RADIO(0x00D) /* Common 13 */ +#define B2063_COMM14 B43_LP_RADIO(0x00E) /* Common 14 */ +#define B2063_COMM15 B43_LP_RADIO(0x00F) /* Common 15 */ +#define B2063_COMM16 B43_LP_RADIO(0x010) /* Common 16 */ +#define B2063_COMM17 B43_LP_RADIO(0x011) /* Common 17 */ +#define B2063_COMM18 B43_LP_RADIO(0x012) /* Common 18 */ +#define B2063_COMM19 B43_LP_RADIO(0x013) /* Common 19 */ +#define B2063_COMM20 B43_LP_RADIO(0x014) /* Common 20 */ +#define B2063_COMM21 B43_LP_RADIO(0x015) /* Common 21 */ +#define B2063_COMM22 B43_LP_RADIO(0x016) /* Common 22 */ +#define B2063_COMM23 B43_LP_RADIO(0x017) /* Common 23 */ +#define B2063_COMM24 B43_LP_RADIO(0x018) /* Common 24 */ +#define B2063_PWR_SWITCH_CTL B43_LP_RADIO(0x019) /* POWER SWITCH Control */ +#define B2063_PLL_SP1 B43_LP_RADIO(0x01A) /* PLL SP 1 */ +#define B2063_PLL_SP2 B43_LP_RADIO(0x01B) /* PLL SP 2 */ +#define B2063_LOGEN_SP1 B43_LP_RADIO(0x01C) /* LOGEN SP 1 */ +#define B2063_LOGEN_SP2 B43_LP_RADIO(0x01D) /* LOGEN SP 2 */ +#define B2063_LOGEN_SP3 B43_LP_RADIO(0x01E) /* LOGEN SP 3 */ +#define B2063_LOGEN_SP4 B43_LP_RADIO(0x01F) /* LOGEN SP 4 */ +#define B2063_LOGEN_SP5 B43_LP_RADIO(0x020) /* LOGEN SP 5 */ +#define B2063_G_RX_SP1 B43_LP_RADIO(0x021) /* G RX SP 1 */ +#define B2063_G_RX_SP2 B43_LP_RADIO(0x022) /* G RX SP 2 */ +#define B2063_G_RX_SP3 B43_LP_RADIO(0x023) /* G RX SP 3 */ +#define B2063_G_RX_SP4 B43_LP_RADIO(0x024) /* G RX SP 4 */ +#define B2063_G_RX_SP5 B43_LP_RADIO(0x025) /* G RX SP 5 */ +#define B2063_G_RX_SP6 B43_LP_RADIO(0x026) /* G RX SP 6 */ +#define B2063_G_RX_SP7 B43_LP_RADIO(0x027) /* G RX SP 7 */ +#define B2063_G_RX_SP8 B43_LP_RADIO(0x028) /* G RX SP 8 */ +#define B2063_G_RX_SP9 B43_LP_RADIO(0x029) /* G RX SP 9 */ +#define B2063_G_RX_SP10 B43_LP_RADIO(0x02A) /* G RX SP 10 */ +#define B2063_G_RX_SP11 B43_LP_RADIO(0x02B) /* G RX SP 11 */ +#define B2063_A_RX_SP1 B43_LP_RADIO(0x02C) /* A RX SP 1 */ +#define B2063_A_RX_SP2 B43_LP_RADIO(0x02D) /* A RX SP 2 */ +#define B2063_A_RX_SP3 B43_LP_RADIO(0x02E) /* A RX SP 3 */ +#define B2063_A_RX_SP4 B43_LP_RADIO(0x02F) /* A RX SP 4 */ +#define B2063_A_RX_SP5 B43_LP_RADIO(0x030) /* A RX SP 5 */ +#define B2063_A_RX_SP6 B43_LP_RADIO(0x031) /* A RX SP 6 */ +#define B2063_A_RX_SP7 B43_LP_RADIO(0x032) /* A RX SP 7 */ +#define B2063_RX_BB_SP1 B43_LP_RADIO(0x033) /* RX BB SP 1 */ +#define B2063_RX_BB_SP2 B43_LP_RADIO(0x034) /* RX BB SP 2 */ +#define B2063_RX_BB_SP3 B43_LP_RADIO(0x035) /* RX BB SP 3 */ +#define B2063_RX_BB_SP4 B43_LP_RADIO(0x036) /* RX BB SP 4 */ +#define B2063_RX_BB_SP5 B43_LP_RADIO(0x037) /* RX BB SP 5 */ +#define B2063_RX_BB_SP6 B43_LP_RADIO(0x038) /* RX BB SP 6 */ +#define B2063_RX_BB_SP7 B43_LP_RADIO(0x039) /* RX BB SP 7 */ +#define B2063_RX_BB_SP8 B43_LP_RADIO(0x03A) /* RX BB SP 8 */ +#define B2063_TX_RF_SP1 B43_LP_RADIO(0x03B) /* TX RF SP 1 */ +#define B2063_TX_RF_SP2 B43_LP_RADIO(0x03C) /* TX RF SP 2 */ +#define B2063_TX_RF_SP3 B43_LP_RADIO(0x03D) /* TX RF SP 3 */ +#define B2063_TX_RF_SP4 B43_LP_RADIO(0x03E) /* TX RF SP 4 */ +#define B2063_TX_RF_SP5 B43_LP_RADIO(0x03F) /* TX RF SP 5 */ +#define B2063_TX_RF_SP6 B43_LP_RADIO(0x040) /* TX RF SP 6 */ +#define B2063_TX_RF_SP7 B43_LP_RADIO(0x041) /* TX RF SP 7 */ +#define B2063_TX_RF_SP8 B43_LP_RADIO(0x042) /* TX RF SP 8 */ +#define B2063_TX_RF_SP9 B43_LP_RADIO(0x043) /* TX RF SP 9 */ +#define B2063_TX_RF_SP10 B43_LP_RADIO(0x044) /* TX RF SP 10 */ +#define B2063_TX_RF_SP11 B43_LP_RADIO(0x045) /* TX RF SP 11 */ +#define B2063_TX_RF_SP12 B43_LP_RADIO(0x046) /* TX RF SP 12 */ +#define B2063_TX_RF_SP13 B43_LP_RADIO(0x047) /* TX RF SP 13 */ +#define B2063_TX_RF_SP14 B43_LP_RADIO(0x048) /* TX RF SP 14 */ +#define B2063_TX_RF_SP15 B43_LP_RADIO(0x049) /* TX RF SP 15 */ +#define B2063_TX_RF_SP16 B43_LP_RADIO(0x04A) /* TX RF SP 16 */ +#define B2063_TX_RF_SP17 B43_LP_RADIO(0x04B) /* TX RF SP 17 */ +#define B2063_PA_SP1 B43_LP_RADIO(0x04C) /* PA SP 1 */ +#define B2063_PA_SP2 B43_LP_RADIO(0x04D) /* PA SP 2 */ +#define B2063_PA_SP3 B43_LP_RADIO(0x04E) /* PA SP 3 */ +#define B2063_PA_SP4 B43_LP_RADIO(0x04F) /* PA SP 4 */ +#define B2063_PA_SP5 B43_LP_RADIO(0x050) /* PA SP 5 */ +#define B2063_PA_SP6 B43_LP_RADIO(0x051) /* PA SP 6 */ +#define B2063_PA_SP7 B43_LP_RADIO(0x052) /* PA SP 7 */ +#define B2063_TX_BB_SP1 B43_LP_RADIO(0x053) /* TX BB SP 1 */ +#define B2063_TX_BB_SP2 B43_LP_RADIO(0x054) /* TX BB SP 2 */ +#define B2063_TX_BB_SP3 B43_LP_RADIO(0x055) /* TX BB SP 3 */ +#define B2063_REG_SP1 B43_LP_RADIO(0x056) /* REG SP 1 */ +#define B2063_BANDGAP_CTL1 B43_LP_RADIO(0x057) /* BANDGAP Control 1 */ +#define B2063_BANDGAP_CTL2 B43_LP_RADIO(0x058) /* BANDGAP Control 2 */ +#define B2063_LPO_CTL1 B43_LP_RADIO(0x059) /* LPO Control 1 */ +#define B2063_RC_CALIB_CTL1 B43_LP_RADIO(0x05A) /* RC Calibration Control 1 */ +#define B2063_RC_CALIB_CTL2 B43_LP_RADIO(0x05B) /* RC Calibration Control 2 */ +#define B2063_RC_CALIB_CTL3 B43_LP_RADIO(0x05C) /* RC Calibration Control 3 */ +#define B2063_RC_CALIB_CTL4 B43_LP_RADIO(0x05D) /* RC Calibration Control 4 */ +#define B2063_RC_CALIB_CTL5 B43_LP_RADIO(0x05E) /* RC Calibration Control 5 */ +#define B2063_RC_CALIB_CTL6 B43_LP_RADIO(0x05F) /* RC Calibration Control 6 */ +#define B2063_RC_CALIB_CTL7 B43_LP_RADIO(0x060) /* RC Calibration Control 7 */ +#define B2063_RC_CALIB_CTL8 B43_LP_RADIO(0x061) /* RC Calibration Control 8 */ +#define B2063_RC_CALIB_CTL9 B43_LP_RADIO(0x062) /* RC Calibration Control 9 */ +#define B2063_RC_CALIB_CTL10 B43_LP_RADIO(0x063) /* RC Calibration Control 10 */ +#define B2063_PLL_JTAG_CALNRST B43_LP_RADIO(0x064) /* PLL JTAG CALNRST */ +#define B2063_PLL_JTAG_IN_PLL1 B43_LP_RADIO(0x065) /* PLL JTAG IN PLL 1 */ +#define B2063_PLL_JTAG_IN_PLL2 B43_LP_RADIO(0x066) /* PLL JTAG IN PLL 2 */ +#define B2063_PLL_JTAG_PLL_CP1 B43_LP_RADIO(0x067) /* PLL JTAG PLL CP 1 */ +#define B2063_PLL_JTAG_PLL_CP2 B43_LP_RADIO(0x068) /* PLL JTAG PLL CP 2 */ +#define B2063_PLL_JTAG_PLL_CP3 B43_LP_RADIO(0x069) /* PLL JTAG PLL CP 3 */ +#define B2063_PLL_JTAG_PLL_CP4 B43_LP_RADIO(0x06A) /* PLL JTAG PLL CP 4 */ +#define B2063_PLL_JTAG_PLL_CTL1 B43_LP_RADIO(0x06B) /* PLL JTAG PLL Control 1 */ +#define B2063_PLL_JTAG_PLL_LF1 B43_LP_RADIO(0x06C) /* PLL JTAG PLL LF 1 */ +#define B2063_PLL_JTAG_PLL_LF2 B43_LP_RADIO(0x06D) /* PLL JTAG PLL LF 2 */ +#define B2063_PLL_JTAG_PLL_LF3 B43_LP_RADIO(0x06E) /* PLL JTAG PLL LF 3 */ +#define B2063_PLL_JTAG_PLL_LF4 B43_LP_RADIO(0x06F) /* PLL JTAG PLL LF 4 */ +#define B2063_PLL_JTAG_PLL_SG1 B43_LP_RADIO(0x070) /* PLL JTAG PLL SG 1 */ +#define B2063_PLL_JTAG_PLL_SG2 B43_LP_RADIO(0x071) /* PLL JTAG PLL SG 2 */ +#define B2063_PLL_JTAG_PLL_SG3 B43_LP_RADIO(0x072) /* PLL JTAG PLL SG 3 */ +#define B2063_PLL_JTAG_PLL_SG4 B43_LP_RADIO(0x073) /* PLL JTAG PLL SG 4 */ +#define B2063_PLL_JTAG_PLL_SG5 B43_LP_RADIO(0x074) /* PLL JTAG PLL SG 5 */ +#define B2063_PLL_JTAG_PLL_VCO1 B43_LP_RADIO(0x075) /* PLL JTAG PLL VCO 1 */ +#define B2063_PLL_JTAG_PLL_VCO2 B43_LP_RADIO(0x076) /* PLL JTAG PLL VCO 2 */ +#define B2063_PLL_JTAG_PLL_VCO_CALIB1 B43_LP_RADIO(0x077) /* PLL JTAG PLL VCO Calibration 1 */ +#define B2063_PLL_JTAG_PLL_VCO_CALIB2 B43_LP_RADIO(0x078) /* PLL JTAG PLL VCO Calibration 2 */ +#define B2063_PLL_JTAG_PLL_VCO_CALIB3 B43_LP_RADIO(0x079) /* PLL JTAG PLL VCO Calibration 3 */ +#define B2063_PLL_JTAG_PLL_VCO_CALIB4 B43_LP_RADIO(0x07A) /* PLL JTAG PLL VCO Calibration 4 */ +#define B2063_PLL_JTAG_PLL_VCO_CALIB5 B43_LP_RADIO(0x07B) /* PLL JTAG PLL VCO Calibration 5 */ +#define B2063_PLL_JTAG_PLL_VCO_CALIB6 B43_LP_RADIO(0x07C) /* PLL JTAG PLL VCO Calibration 6 */ +#define B2063_PLL_JTAG_PLL_VCO_CALIB7 B43_LP_RADIO(0x07D) /* PLL JTAG PLL VCO Calibration 7 */ +#define B2063_PLL_JTAG_PLL_VCO_CALIB8 B43_LP_RADIO(0x07E) /* PLL JTAG PLL VCO Calibration 8 */ +#define B2063_PLL_JTAG_PLL_VCO_CALIB9 B43_LP_RADIO(0x07F) /* PLL JTAG PLL VCO Calibration 9 */ +#define B2063_PLL_JTAG_PLL_VCO_CALIB10 B43_LP_RADIO(0x080) /* PLL JTAG PLL VCO Calibration 10 */ +#define B2063_PLL_JTAG_PLL_XTAL_12 B43_LP_RADIO(0x081) /* PLL JTAG PLL XTAL 1 2 */ +#define B2063_PLL_JTAG_PLL_XTAL3 B43_LP_RADIO(0x082) /* PLL JTAG PLL XTAL 3 */ +#define B2063_LOGEN_ACL1 B43_LP_RADIO(0x083) /* LOGEN ACL 1 */ +#define B2063_LOGEN_ACL2 B43_LP_RADIO(0x084) /* LOGEN ACL 2 */ +#define B2063_LOGEN_ACL3 B43_LP_RADIO(0x085) /* LOGEN ACL 3 */ +#define B2063_LOGEN_ACL4 B43_LP_RADIO(0x086) /* LOGEN ACL 4 */ +#define B2063_LOGEN_ACL5 B43_LP_RADIO(0x087) /* LOGEN ACL 5 */ +#define B2063_LO_CALIB_INPUTS B43_LP_RADIO(0x088) /* LO Calibration INPUTS */ +#define B2063_LO_CALIB_CTL1 B43_LP_RADIO(0x089) /* LO Calibration Control 1 */ +#define B2063_LO_CALIB_CTL2 B43_LP_RADIO(0x08A) /* LO Calibration Control 2 */ +#define B2063_LO_CALIB_CTL3 B43_LP_RADIO(0x08B) /* LO Calibration Control 3 */ +#define B2063_LO_CALIB_WAITCNT B43_LP_RADIO(0x08C) /* LO Calibration WAITCNT */ +#define B2063_LO_CALIB_OVR1 B43_LP_RADIO(0x08D) /* LO Calibration OVR 1 */ +#define B2063_LO_CALIB_OVR2 B43_LP_RADIO(0x08E) /* LO Calibration OVR 2 */ +#define B2063_LO_CALIB_OVAL1 B43_LP_RADIO(0x08F) /* LO Calibration OVAL 1 */ +#define B2063_LO_CALIB_OVAL2 B43_LP_RADIO(0x090) /* LO Calibration OVAL 2 */ +#define B2063_LO_CALIB_OVAL3 B43_LP_RADIO(0x091) /* LO Calibration OVAL 3 */ +#define B2063_LO_CALIB_OVAL4 B43_LP_RADIO(0x092) /* LO Calibration OVAL 4 */ +#define B2063_LO_CALIB_OVAL5 B43_LP_RADIO(0x093) /* LO Calibration OVAL 5 */ +#define B2063_LO_CALIB_OVAL6 B43_LP_RADIO(0x094) /* LO Calibration OVAL 6 */ +#define B2063_LO_CALIB_OVAL7 B43_LP_RADIO(0x095) /* LO Calibration OVAL 7 */ +#define B2063_LO_CALIB_CALVLD1 B43_LP_RADIO(0x096) /* LO Calibration CALVLD 1 */ +#define B2063_LO_CALIB_CALVLD2 B43_LP_RADIO(0x097) /* LO Calibration CALVLD 2 */ +#define B2063_LO_CALIB_CVAL1 B43_LP_RADIO(0x098) /* LO Calibration CVAL 1 */ +#define B2063_LO_CALIB_CVAL2 B43_LP_RADIO(0x099) /* LO Calibration CVAL 2 */ +#define B2063_LO_CALIB_CVAL3 B43_LP_RADIO(0x09A) /* LO Calibration CVAL 3 */ +#define B2063_LO_CALIB_CVAL4 B43_LP_RADIO(0x09B) /* LO Calibration CVAL 4 */ +#define B2063_LO_CALIB_CVAL5 B43_LP_RADIO(0x09C) /* LO Calibration CVAL 5 */ +#define B2063_LO_CALIB_CVAL6 B43_LP_RADIO(0x09D) /* LO Calibration CVAL 6 */ +#define B2063_LO_CALIB_CVAL7 B43_LP_RADIO(0x09E) /* LO Calibration CVAL 7 */ +#define B2063_LOGEN_CALIB_EN B43_LP_RADIO(0x09F) /* LOGEN Calibration EN */ +#define B2063_LOGEN_PEAKDET1 B43_LP_RADIO(0x0A0) /* LOGEN PEAKDET 1 */ +#define B2063_LOGEN_RCCR1 B43_LP_RADIO(0x0A1) /* LOGEN RCCR 1 */ +#define B2063_LOGEN_VCOBUF1 B43_LP_RADIO(0x0A2) /* LOGEN VCOBUF 1 */ +#define B2063_LOGEN_MIXER1 B43_LP_RADIO(0x0A3) /* LOGEN MIXER 1 */ +#define B2063_LOGEN_MIXER2 B43_LP_RADIO(0x0A4) /* LOGEN MIXER 2 */ +#define B2063_LOGEN_BUF1 B43_LP_RADIO(0x0A5) /* LOGEN BUF 1 */ +#define B2063_LOGEN_BUF2 B43_LP_RADIO(0x0A6) /* LOGEN BUF 2 */ +#define B2063_LOGEN_DIV1 B43_LP_RADIO(0x0A7) /* LOGEN DIV 1 */ +#define B2063_LOGEN_DIV2 B43_LP_RADIO(0x0A8) /* LOGEN DIV 2 */ +#define B2063_LOGEN_DIV3 B43_LP_RADIO(0x0A9) /* LOGEN DIV 3 */ +#define B2063_LOGEN_CBUFRX1 B43_LP_RADIO(0x0AA) /* LOGEN CBUFRX 1 */ +#define B2063_LOGEN_CBUFRX2 B43_LP_RADIO(0x0AB) /* LOGEN CBUFRX 2 */ +#define B2063_LOGEN_CBUFTX1 B43_LP_RADIO(0x0AC) /* LOGEN CBUFTX 1 */ +#define B2063_LOGEN_CBUFTX2 B43_LP_RADIO(0x0AD) /* LOGEN CBUFTX 2 */ +#define B2063_LOGEN_IDAC1 B43_LP_RADIO(0x0AE) /* LOGEN IDAC 1 */ +#define B2063_LOGEN_SPARE1 B43_LP_RADIO(0x0AF) /* LOGEN SPARE 1 */ +#define B2063_LOGEN_SPARE2 B43_LP_RADIO(0x0B0) /* LOGEN SPARE 2 */ +#define B2063_LOGEN_SPARE3 B43_LP_RADIO(0x0B1) /* LOGEN SPARE 3 */ +#define B2063_G_RX_1ST1 B43_LP_RADIO(0x0B2) /* G RX 1ST 1 */ +#define B2063_G_RX_1ST2 B43_LP_RADIO(0x0B3) /* G RX 1ST 2 */ +#define B2063_G_RX_1ST3 B43_LP_RADIO(0x0B4) /* G RX 1ST 3 */ +#define B2063_G_RX_2ND1 B43_LP_RADIO(0x0B5) /* G RX 2ND 1 */ +#define B2063_G_RX_2ND2 B43_LP_RADIO(0x0B6) /* G RX 2ND 2 */ +#define B2063_G_RX_2ND3 B43_LP_RADIO(0x0B7) /* G RX 2ND 3 */ +#define B2063_G_RX_2ND4 B43_LP_RADIO(0x0B8) /* G RX 2ND 4 */ +#define B2063_G_RX_2ND5 B43_LP_RADIO(0x0B9) /* G RX 2ND 5 */ +#define B2063_G_RX_2ND6 B43_LP_RADIO(0x0BA) /* G RX 2ND 6 */ +#define B2063_G_RX_2ND7 B43_LP_RADIO(0x0BB) /* G RX 2ND 7 */ +#define B2063_G_RX_2ND8 B43_LP_RADIO(0x0BC) /* G RX 2ND 8 */ +#define B2063_G_RX_PS1 B43_LP_RADIO(0x0BD) /* G RX PS 1 */ +#define B2063_G_RX_PS2 B43_LP_RADIO(0x0BE) /* G RX PS 2 */ +#define B2063_G_RX_PS3 B43_LP_RADIO(0x0BF) /* G RX PS 3 */ +#define B2063_G_RX_PS4 B43_LP_RADIO(0x0C0) /* G RX PS 4 */ +#define B2063_G_RX_PS5 B43_LP_RADIO(0x0C1) /* G RX PS 5 */ +#define B2063_G_RX_MIX1 B43_LP_RADIO(0x0C2) /* G RX MIX 1 */ +#define B2063_G_RX_MIX2 B43_LP_RADIO(0x0C3) /* G RX MIX 2 */ +#define B2063_G_RX_MIX3 B43_LP_RADIO(0x0C4) /* G RX MIX 3 */ +#define B2063_G_RX_MIX4 B43_LP_RADIO(0x0C5) /* G RX MIX 4 */ +#define B2063_G_RX_MIX5 B43_LP_RADIO(0x0C6) /* G RX MIX 5 */ +#define B2063_G_RX_MIX6 B43_LP_RADIO(0x0C7) /* G RX MIX 6 */ +#define B2063_G_RX_MIX7 B43_LP_RADIO(0x0C8) /* G RX MIX 7 */ +#define B2063_G_RX_MIX8 B43_LP_RADIO(0x0C9) /* G RX MIX 8 */ +#define B2063_G_RX_PDET1 B43_LP_RADIO(0x0CA) /* G RX PDET 1 */ +#define B2063_G_RX_SPARES1 B43_LP_RADIO(0x0CB) /* G RX SPARES 1 */ +#define B2063_G_RX_SPARES2 B43_LP_RADIO(0x0CC) /* G RX SPARES 2 */ +#define B2063_G_RX_SPARES3 B43_LP_RADIO(0x0CD) /* G RX SPARES 3 */ +#define B2063_A_RX_1ST1 B43_LP_RADIO(0x0CE) /* A RX 1ST 1 */ +#define B2063_A_RX_1ST2 B43_LP_RADIO(0x0CF) /* A RX 1ST 2 */ +#define B2063_A_RX_1ST3 B43_LP_RADIO(0x0D0) /* A RX 1ST 3 */ +#define B2063_A_RX_1ST4 B43_LP_RADIO(0x0D1) /* A RX 1ST 4 */ +#define B2063_A_RX_1ST5 B43_LP_RADIO(0x0D2) /* A RX 1ST 5 */ +#define B2063_A_RX_2ND1 B43_LP_RADIO(0x0D3) /* A RX 2ND 1 */ +#define B2063_A_RX_2ND2 B43_LP_RADIO(0x0D4) /* A RX 2ND 2 */ +#define B2063_A_RX_2ND3 B43_LP_RADIO(0x0D5) /* A RX 2ND 3 */ +#define B2063_A_RX_2ND4 B43_LP_RADIO(0x0D6) /* A RX 2ND 4 */ +#define B2063_A_RX_2ND5 B43_LP_RADIO(0x0D7) /* A RX 2ND 5 */ +#define B2063_A_RX_2ND6 B43_LP_RADIO(0x0D8) /* A RX 2ND 6 */ +#define B2063_A_RX_2ND7 B43_LP_RADIO(0x0D9) /* A RX 2ND 7 */ +#define B2063_A_RX_PS1 B43_LP_RADIO(0x0DA) /* A RX PS 1 */ +#define B2063_A_RX_PS2 B43_LP_RADIO(0x0DB) /* A RX PS 2 */ +#define B2063_A_RX_PS3 B43_LP_RADIO(0x0DC) /* A RX PS 3 */ +#define B2063_A_RX_PS4 B43_LP_RADIO(0x0DD) /* A RX PS 4 */ +#define B2063_A_RX_PS5 B43_LP_RADIO(0x0DE) /* A RX PS 5 */ +#define B2063_A_RX_PS6 B43_LP_RADIO(0x0DF) /* A RX PS 6 */ +#define B2063_A_RX_MIX1 B43_LP_RADIO(0x0E0) /* A RX MIX 1 */ +#define B2063_A_RX_MIX2 B43_LP_RADIO(0x0E1) /* A RX MIX 2 */ +#define B2063_A_RX_MIX3 B43_LP_RADIO(0x0E2) /* A RX MIX 3 */ +#define B2063_A_RX_MIX4 B43_LP_RADIO(0x0E3) /* A RX MIX 4 */ +#define B2063_A_RX_MIX5 B43_LP_RADIO(0x0E4) /* A RX MIX 5 */ +#define B2063_A_RX_MIX6 B43_LP_RADIO(0x0E5) /* A RX MIX 6 */ +#define B2063_A_RX_MIX7 B43_LP_RADIO(0x0E6) /* A RX MIX 7 */ +#define B2063_A_RX_MIX8 B43_LP_RADIO(0x0E7) /* A RX MIX 8 */ +#define B2063_A_RX_PWRDET1 B43_LP_RADIO(0x0E8) /* A RX PWRDET 1 */ +#define B2063_A_RX_SPARE1 B43_LP_RADIO(0x0E9) /* A RX SPARE 1 */ +#define B2063_A_RX_SPARE2 B43_LP_RADIO(0x0EA) /* A RX SPARE 2 */ +#define B2063_A_RX_SPARE3 B43_LP_RADIO(0x0EB) /* A RX SPARE 3 */ +#define B2063_RX_TIA_CTL1 B43_LP_RADIO(0x0EC) /* RX TIA Control 1 */ +#define B2063_RX_TIA_CTL2 B43_LP_RADIO(0x0ED) /* RX TIA Control 2 */ +#define B2063_RX_TIA_CTL3 B43_LP_RADIO(0x0EE) /* RX TIA Control 3 */ +#define B2063_RX_TIA_CTL4 B43_LP_RADIO(0x0EF) /* RX TIA Control 4 */ +#define B2063_RX_TIA_CTL5 B43_LP_RADIO(0x0F0) /* RX TIA Control 5 */ +#define B2063_RX_TIA_CTL6 B43_LP_RADIO(0x0F1) /* RX TIA Control 6 */ +#define B2063_RX_BB_CTL1 B43_LP_RADIO(0x0F2) /* RX BB Control 1 */ +#define B2063_RX_BB_CTL2 B43_LP_RADIO(0x0F3) /* RX BB Control 2 */ +#define B2063_RX_BB_CTL3 B43_LP_RADIO(0x0F4) /* RX BB Control 3 */ +#define B2063_RX_BB_CTL4 B43_LP_RADIO(0x0F5) /* RX BB Control 4 */ +#define B2063_RX_BB_CTL5 B43_LP_RADIO(0x0F6) /* RX BB Control 5 */ +#define B2063_RX_BB_CTL6 B43_LP_RADIO(0x0F7) /* RX BB Control 6 */ +#define B2063_RX_BB_CTL7 B43_LP_RADIO(0x0F8) /* RX BB Control 7 */ +#define B2063_RX_BB_CTL8 B43_LP_RADIO(0x0F9) /* RX BB Control 8 */ +#define B2063_RX_BB_CTL9 B43_LP_RADIO(0x0FA) /* RX BB Control 9 */ +#define B2063_TX_RF_CTL1 B43_LP_RADIO(0x0FB) /* TX RF Control 1 */ +#define B2063_TX_RF_IDAC_LO_RF_I B43_LP_RADIO(0x0FC) /* TX RF IDAC LO RF I */ +#define B2063_TX_RF_IDAC_LO_RF_Q B43_LP_RADIO(0x0FD) /* TX RF IDAC LO RF Q */ +#define B2063_TX_RF_IDAC_LO_BB_I B43_LP_RADIO(0x0FE) /* TX RF IDAC LO BB I */ +#define B2063_TX_RF_IDAC_LO_BB_Q B43_LP_RADIO(0x0FF) /* TX RF IDAC LO BB Q */ +#define B2063_TX_RF_CTL2 B43_LP_RADIO(0x100) /* TX RF Control 2 */ +#define B2063_TX_RF_CTL3 B43_LP_RADIO(0x101) /* TX RF Control 3 */ +#define B2063_TX_RF_CTL4 B43_LP_RADIO(0x102) /* TX RF Control 4 */ +#define B2063_TX_RF_CTL5 B43_LP_RADIO(0x103) /* TX RF Control 5 */ +#define B2063_TX_RF_CTL6 B43_LP_RADIO(0x104) /* TX RF Control 6 */ +#define B2063_TX_RF_CTL7 B43_LP_RADIO(0x105) /* TX RF Control 7 */ +#define B2063_TX_RF_CTL8 B43_LP_RADIO(0x106) /* TX RF Control 8 */ +#define B2063_TX_RF_CTL9 B43_LP_RADIO(0x107) /* TX RF Control 9 */ +#define B2063_TX_RF_CTL10 B43_LP_RADIO(0x108) /* TX RF Control 10 */ +#define B2063_TX_RF_CTL14 B43_LP_RADIO(0x109) /* TX RF Control 14 */ +#define B2063_TX_RF_CTL15 B43_LP_RADIO(0x10A) /* TX RF Control 15 */ +#define B2063_PA_CTL1 B43_LP_RADIO(0x10B) /* PA Control 1 */ +#define B2063_PA_CTL2 B43_LP_RADIO(0x10C) /* PA Control 2 */ +#define B2063_PA_CTL3 B43_LP_RADIO(0x10D) /* PA Control 3 */ +#define B2063_PA_CTL4 B43_LP_RADIO(0x10E) /* PA Control 4 */ +#define B2063_PA_CTL5 B43_LP_RADIO(0x10F) /* PA Control 5 */ +#define B2063_PA_CTL6 B43_LP_RADIO(0x110) /* PA Control 6 */ +#define B2063_PA_CTL7 B43_LP_RADIO(0x111) /* PA Control 7 */ +#define B2063_PA_CTL8 B43_LP_RADIO(0x112) /* PA Control 8 */ +#define B2063_PA_CTL9 B43_LP_RADIO(0x113) /* PA Control 9 */ +#define B2063_PA_CTL10 B43_LP_RADIO(0x114) /* PA Control 10 */ +#define B2063_PA_CTL11 B43_LP_RADIO(0x115) /* PA Control 11 */ +#define B2063_PA_CTL12 B43_LP_RADIO(0x116) /* PA Control 12 */ +#define B2063_PA_CTL13 B43_LP_RADIO(0x117) /* PA Control 13 */ +#define B2063_TX_BB_CTL1 B43_LP_RADIO(0x118) /* TX BB Control 1 */ +#define B2063_TX_BB_CTL2 B43_LP_RADIO(0x119) /* TX BB Control 2 */ +#define B2063_TX_BB_CTL3 B43_LP_RADIO(0x11A) /* TX BB Control 3 */ +#define B2063_TX_BB_CTL4 B43_LP_RADIO(0x11B) /* TX BB Control 4 */ +#define B2063_GPIO_CTL1 B43_LP_RADIO(0x11C) /* GPIO Control 1 */ +#define B2063_VREG_CTL1 B43_LP_RADIO(0x11D) /* VREG Control 1 */ +#define B2063_AMUX_CTL1 B43_LP_RADIO(0x11E) /* AMUX Control 1 */ +#define B2063_IQ_CALIB_GVAR B43_LP_RADIO(0x11F) /* IQ Calibration GVAR */ +#define B2063_IQ_CALIB_CTL1 B43_LP_RADIO(0x120) /* IQ Calibration Control 1 */ +#define B2063_IQ_CALIB_CTL2 B43_LP_RADIO(0x121) /* IQ Calibration Control 2 */ +#define B2063_TEMPSENSE_CTL1 B43_LP_RADIO(0x122) /* TEMPSENSE Control 1 */ +#define B2063_TEMPSENSE_CTL2 B43_LP_RADIO(0x123) /* TEMPSENSE Control 2 */ +#define B2063_TX_RX_LOOPBACK1 B43_LP_RADIO(0x124) /* TX/RX LOOPBACK 1 */ +#define B2063_TX_RX_LOOPBACK2 B43_LP_RADIO(0x125) /* TX/RX LOOPBACK 2 */ +#define B2063_EXT_TSSI_CTL1 B43_LP_RADIO(0x126) /* EXT TSSI Control 1 */ +#define B2063_EXT_TSSI_CTL2 B43_LP_RADIO(0x127) /* EXT TSSI Control 2 */ +#define B2063_AFE_CTL B43_LP_RADIO(0x128) /* AFE Control */ + + + +enum b43_lpphy_txpctl_mode { + B43_LPPHY_TXPCTL_UNKNOWN = 0, + B43_LPPHY_TXPCTL_OFF, /* TX power control is OFF */ + B43_LPPHY_TXPCTL_SW, /* TX power control is set to Software */ + B43_LPPHY_TXPCTL_HW, /* TX power control is set to Hardware */ +}; + +struct b43_phy_lp { + /* Current TX power control mode. */ + enum b43_lpphy_txpctl_mode txpctl_mode; + + /* Transmit isolation medium band */ + u8 tx_isolation_med_band; + /* Transmit isolation low band */ + u8 tx_isolation_low_band; + /* Transmit isolation high band */ + u8 tx_isolation_hi_band; + + /* Max transmit power medium band */ + u16 max_tx_pwr_med_band; + /* Max transmit power low band */ + u16 max_tx_pwr_low_band; + /* Max transmit power high band */ + u16 max_tx_pwr_hi_band; + + /* FIXME What are these used for? */ + /* FIXME Is 15 the correct array size? */ + u16 tx_max_rate[15]; + u16 tx_max_ratel[15]; + u16 tx_max_rateh[15]; + + /* Transmit power arrays */ + s16 txpa[3], txpal[3], txpah[3]; + + /* Receive power offset */ + u8 rx_pwr_offset; + + /* TSSI transmit count */ + u16 tssi_tx_count; + /* TSSI index */ + u16 tssi_idx; /* FIXME initial value? */ + /* TSSI npt */ + u16 tssi_npt; /* FIXME initial value? */ + + /* Target TX frequency */ + u16 tgt_tx_freq; /* FIXME initial value? */ + + /* Transmit power index override */ + s8 tx_pwr_idx_over; /* FIXME initial value? */ + + /* RSSI vf */ + u8 rssi_vf; + /* RSSI vc */ + u8 rssi_vc; + /* RSSI gs */ + u8 rssi_gs; + + /* RC cap */ + u8 rc_cap; + /* BX arch */ + u8 bx_arch; + + /* Full calibration channel */ + u8 full_calib_chan; + + /* Transmit iqlocal best coeffs */ + bool tx_iqloc_best_coeffs_valid; + u8 tx_iqloc_best_coeffs[11]; + + /* Used for "Save/Restore Dig Filt State" */ + u16 dig_flt_state[9]; + + bool crs_usr_disable, crs_sys_disable; + + unsigned int pdiv; + + /* The channel we are tuned to */ + u8 channel; + + /* The active antenna diversity mode */ + int antenna; + + /* Frequency of the active TX tone */ + int tx_tone_freq; +}; + +enum tssi_mux_mode { + TSSI_MUX_PREPA, + TSSI_MUX_POSTPA, + TSSI_MUX_EXT, +}; + +struct b43_phy_operations; +extern const struct b43_phy_operations b43_phyops_lp; + +#endif /* LINUX_B43_PHY_LP_H_ */ diff --git a/drivers/net/wireless/b43/phy_n.c b/drivers/net/wireless/b43/phy_n.c new file mode 100644 index 0000000..e1b3912 --- /dev/null +++ b/drivers/net/wireless/b43/phy_n.c @@ -0,0 +1,5565 @@ +/* + + Broadcom B43 wireless driver + IEEE 802.11n PHY support + + Copyright (c) 2008 Michael Buesch + Copyright (c) 2010-2011 RafaÅ‚ MiÅ‚ecki + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include +#include +#include + +#include "b43.h" +#include "phy_n.h" +#include "tables_nphy.h" +#include "radio_2055.h" +#include "radio_2056.h" +#include "radio_2057.h" +#include "main.h" + +struct nphy_txgains { + u16 txgm[2]; + u16 pga[2]; + u16 pad[2]; + u16 ipa[2]; +}; + +struct nphy_iqcal_params { + u16 txgm; + u16 pga; + u16 pad; + u16 ipa; + u16 cal_gain; + u16 ncorr[5]; +}; + +struct nphy_iq_est { + s32 iq0_prod; + u32 i0_pwr; + u32 q0_pwr; + s32 iq1_prod; + u32 i1_pwr; + u32 q1_pwr; +}; + +enum b43_nphy_rf_sequence { + B43_RFSEQ_RX2TX, + B43_RFSEQ_TX2RX, + B43_RFSEQ_RESET2RX, + B43_RFSEQ_UPDATE_GAINH, + B43_RFSEQ_UPDATE_GAINL, + B43_RFSEQ_UPDATE_GAINU, +}; + +enum n_intc_override { + N_INTC_OVERRIDE_OFF = 0, + N_INTC_OVERRIDE_TRSW = 1, + N_INTC_OVERRIDE_PA = 2, + N_INTC_OVERRIDE_EXT_LNA_PU = 3, + N_INTC_OVERRIDE_EXT_LNA_GAIN = 4, +}; + +enum n_rssi_type { + N_RSSI_W1 = 0, + N_RSSI_W2, + N_RSSI_NB, + N_RSSI_IQ, + N_RSSI_TSSI_2G, + N_RSSI_TSSI_5G, + N_RSSI_TBD, +}; + +enum n_rail_type { + N_RAIL_I = 0, + N_RAIL_Q = 1, +}; + +static inline bool b43_nphy_ipa(struct b43_wldev *dev) +{ + enum ieee80211_band band = b43_current_band(dev->wl); + return ((dev->phy.n->ipa2g_on && band == IEEE80211_BAND_2GHZ) || + (dev->phy.n->ipa5g_on && band == IEEE80211_BAND_5GHZ)); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/RxCoreGetState */ +static u8 b43_nphy_get_rx_core_state(struct b43_wldev *dev) +{ + return (b43_phy_read(dev, B43_NPHY_RFSEQCA) & B43_NPHY_RFSEQCA_RXEN) >> + B43_NPHY_RFSEQCA_RXEN_SHIFT; +} + +/************************************************** + * RF (just without b43_nphy_rf_ctl_intc_override) + **************************************************/ + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/ForceRFSeq */ +static void b43_nphy_force_rf_sequence(struct b43_wldev *dev, + enum b43_nphy_rf_sequence seq) +{ + static const u16 trigger[] = { + [B43_RFSEQ_RX2TX] = B43_NPHY_RFSEQTR_RX2TX, + [B43_RFSEQ_TX2RX] = B43_NPHY_RFSEQTR_TX2RX, + [B43_RFSEQ_RESET2RX] = B43_NPHY_RFSEQTR_RST2RX, + [B43_RFSEQ_UPDATE_GAINH] = B43_NPHY_RFSEQTR_UPGH, + [B43_RFSEQ_UPDATE_GAINL] = B43_NPHY_RFSEQTR_UPGL, + [B43_RFSEQ_UPDATE_GAINU] = B43_NPHY_RFSEQTR_UPGU, + }; + int i; + u16 seq_mode = b43_phy_read(dev, B43_NPHY_RFSEQMODE); + + B43_WARN_ON(seq >= ARRAY_SIZE(trigger)); + + b43_phy_set(dev, B43_NPHY_RFSEQMODE, + B43_NPHY_RFSEQMODE_CAOVER | B43_NPHY_RFSEQMODE_TROVER); + b43_phy_set(dev, B43_NPHY_RFSEQTR, trigger[seq]); + for (i = 0; i < 200; i++) { + if (!(b43_phy_read(dev, B43_NPHY_RFSEQST) & trigger[seq])) + goto ok; + msleep(1); + } + b43err(dev->wl, "RF sequence status timeout\n"); +ok: + b43_phy_write(dev, B43_NPHY_RFSEQMODE, seq_mode); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/RFCtrlOverrideRev7 */ +static void b43_nphy_rf_ctl_override_rev7(struct b43_wldev *dev, u16 field, + u16 value, u8 core, bool off, + u8 override) +{ + const struct nphy_rf_control_override_rev7 *e; + u16 en_addrs[3][2] = { + { 0x0E7, 0x0EC }, { 0x342, 0x343 }, { 0x346, 0x347 } + }; + u16 en_addr; + u16 en_mask = field; + u16 val_addr; + u8 i; + + /* Remember: we can get NULL! */ + e = b43_nphy_get_rf_ctl_over_rev7(dev, field, override); + + for (i = 0; i < 2; i++) { + if (override >= ARRAY_SIZE(en_addrs)) { + b43err(dev->wl, "Invalid override value %d\n", override); + return; + } + en_addr = en_addrs[override][i]; + + val_addr = (i == 0) ? e->val_addr_core0 : e->val_addr_core1; + + if (off) { + b43_phy_mask(dev, en_addr, ~en_mask); + if (e) /* Do it safer, better than wl */ + b43_phy_mask(dev, val_addr, ~e->val_mask); + } else { + if (!core || (core & (1 << i))) { + b43_phy_set(dev, en_addr, en_mask); + if (e) + b43_phy_maskset(dev, val_addr, ~e->val_mask, (value << e->val_shift)); + } + } + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/RFCtrlOverride */ +static void b43_nphy_rf_ctl_override(struct b43_wldev *dev, u16 field, + u16 value, u8 core, bool off) +{ + int i; + u8 index = fls(field); + u8 addr, en_addr, val_addr; + /* we expect only one bit set */ + B43_WARN_ON(field & (~(1 << (index - 1)))); + + if (dev->phy.rev >= 3) { + const struct nphy_rf_control_override_rev3 *rf_ctrl; + for (i = 0; i < 2; i++) { + if (index == 0 || index == 16) { + b43err(dev->wl, + "Unsupported RF Ctrl Override call\n"); + return; + } + + rf_ctrl = &tbl_rf_control_override_rev3[index - 1]; + en_addr = B43_PHY_N((i == 0) ? + rf_ctrl->en_addr0 : rf_ctrl->en_addr1); + val_addr = B43_PHY_N((i == 0) ? + rf_ctrl->val_addr0 : rf_ctrl->val_addr1); + + if (off) { + b43_phy_mask(dev, en_addr, ~(field)); + b43_phy_mask(dev, val_addr, + ~(rf_ctrl->val_mask)); + } else { + if (core == 0 || ((1 << i) & core)) { + b43_phy_set(dev, en_addr, field); + b43_phy_maskset(dev, val_addr, + ~(rf_ctrl->val_mask), + (value << rf_ctrl->val_shift)); + } + } + } + } else { + const struct nphy_rf_control_override_rev2 *rf_ctrl; + if (off) { + b43_phy_mask(dev, B43_NPHY_RFCTL_OVER, ~(field)); + value = 0; + } else { + b43_phy_set(dev, B43_NPHY_RFCTL_OVER, field); + } + + for (i = 0; i < 2; i++) { + if (index <= 1 || index == 16) { + b43err(dev->wl, + "Unsupported RF Ctrl Override call\n"); + return; + } + + if (index == 2 || index == 10 || + (index >= 13 && index <= 15)) { + core = 1; + } + + rf_ctrl = &tbl_rf_control_override_rev2[index - 2]; + addr = B43_PHY_N((i == 0) ? + rf_ctrl->addr0 : rf_ctrl->addr1); + + if ((1 << i) & core) + b43_phy_maskset(dev, addr, ~(rf_ctrl->bmask), + (value << rf_ctrl->shift)); + + b43_phy_set(dev, B43_NPHY_RFCTL_OVER, 0x1); + b43_phy_set(dev, B43_NPHY_RFCTL_CMD, + B43_NPHY_RFCTL_CMD_START); + udelay(1); + b43_phy_mask(dev, B43_NPHY_RFCTL_OVER, 0xFFFE); + } + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/RFCtrlIntcOverride */ +static void b43_nphy_rf_ctl_intc_override(struct b43_wldev *dev, + enum n_intc_override intc_override, + u16 value, u8 core) +{ + u8 i, j; + u16 reg, tmp, val; + + B43_WARN_ON(dev->phy.rev < 3); + + for (i = 0; i < 2; i++) { + if ((core == 1 && i == 1) || (core == 2 && !i)) + continue; + + reg = (i == 0) ? + B43_NPHY_RFCTL_INTC1 : B43_NPHY_RFCTL_INTC2; + b43_phy_set(dev, reg, 0x400); + + switch (intc_override) { + case N_INTC_OVERRIDE_OFF: + b43_phy_write(dev, reg, 0); + b43_nphy_force_rf_sequence(dev, B43_RFSEQ_RESET2RX); + break; + case N_INTC_OVERRIDE_TRSW: + if (!i) { + b43_phy_maskset(dev, B43_NPHY_RFCTL_INTC1, + 0xFC3F, (value << 6)); + b43_phy_maskset(dev, B43_NPHY_TXF_40CO_B1S1, + 0xFFFE, 1); + b43_phy_set(dev, B43_NPHY_RFCTL_CMD, + B43_NPHY_RFCTL_CMD_START); + for (j = 0; j < 100; j++) { + if (!(b43_phy_read(dev, B43_NPHY_RFCTL_CMD) & B43_NPHY_RFCTL_CMD_START)) { + j = 0; + break; + } + udelay(10); + } + if (j) + b43err(dev->wl, + "intc override timeout\n"); + b43_phy_mask(dev, B43_NPHY_TXF_40CO_B1S1, + 0xFFFE); + } else { + b43_phy_maskset(dev, B43_NPHY_RFCTL_INTC2, + 0xFC3F, (value << 6)); + b43_phy_maskset(dev, B43_NPHY_RFCTL_OVER, + 0xFFFE, 1); + b43_phy_set(dev, B43_NPHY_RFCTL_CMD, + B43_NPHY_RFCTL_CMD_RXTX); + for (j = 0; j < 100; j++) { + if (!(b43_phy_read(dev, B43_NPHY_RFCTL_CMD) & B43_NPHY_RFCTL_CMD_RXTX)) { + j = 0; + break; + } + udelay(10); + } + if (j) + b43err(dev->wl, + "intc override timeout\n"); + b43_phy_mask(dev, B43_NPHY_RFCTL_OVER, + 0xFFFE); + } + break; + case N_INTC_OVERRIDE_PA: + if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) { + tmp = 0x0020; + val = value << 5; + } else { + tmp = 0x0010; + val = value << 4; + } + b43_phy_maskset(dev, reg, ~tmp, val); + break; + case N_INTC_OVERRIDE_EXT_LNA_PU: + if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) { + tmp = 0x0001; + val = value; + } else { + tmp = 0x0004; + val = value << 2; + } + b43_phy_maskset(dev, reg, ~tmp, val); + break; + case N_INTC_OVERRIDE_EXT_LNA_GAIN: + if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) { + tmp = 0x0002; + val = value << 1; + } else { + tmp = 0x0008; + val = value << 3; + } + b43_phy_maskset(dev, reg, ~tmp, val); + break; + } + } +} + +/************************************************** + * Various PHY ops + **************************************************/ + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/clip-detection */ +static void b43_nphy_write_clip_detection(struct b43_wldev *dev, + const u16 *clip_st) +{ + b43_phy_write(dev, B43_NPHY_C1_CLIP1THRES, clip_st[0]); + b43_phy_write(dev, B43_NPHY_C2_CLIP1THRES, clip_st[1]); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/clip-detection */ +static void b43_nphy_read_clip_detection(struct b43_wldev *dev, u16 *clip_st) +{ + clip_st[0] = b43_phy_read(dev, B43_NPHY_C1_CLIP1THRES); + clip_st[1] = b43_phy_read(dev, B43_NPHY_C2_CLIP1THRES); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/classifier */ +static u16 b43_nphy_classifier(struct b43_wldev *dev, u16 mask, u16 val) +{ + u16 tmp; + + if (dev->dev->core_rev == 16) + b43_mac_suspend(dev); + + tmp = b43_phy_read(dev, B43_NPHY_CLASSCTL); + tmp &= (B43_NPHY_CLASSCTL_CCKEN | B43_NPHY_CLASSCTL_OFDMEN | + B43_NPHY_CLASSCTL_WAITEDEN); + tmp &= ~mask; + tmp |= (val & mask); + b43_phy_maskset(dev, B43_NPHY_CLASSCTL, 0xFFF8, tmp); + + if (dev->dev->core_rev == 16) + b43_mac_enable(dev); + + return tmp; +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/CCA */ +static void b43_nphy_reset_cca(struct b43_wldev *dev) +{ + u16 bbcfg; + + b43_phy_force_clock(dev, 1); + bbcfg = b43_phy_read(dev, B43_NPHY_BBCFG); + b43_phy_write(dev, B43_NPHY_BBCFG, bbcfg | B43_NPHY_BBCFG_RSTCCA); + udelay(1); + b43_phy_write(dev, B43_NPHY_BBCFG, bbcfg & ~B43_NPHY_BBCFG_RSTCCA); + b43_phy_force_clock(dev, 0); + b43_nphy_force_rf_sequence(dev, B43_RFSEQ_RESET2RX); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/carriersearch */ +static void b43_nphy_stay_in_carrier_search(struct b43_wldev *dev, bool enable) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_n *nphy = phy->n; + + if (enable) { + static const u16 clip[] = { 0xFFFF, 0xFFFF }; + if (nphy->deaf_count++ == 0) { + nphy->classifier_state = b43_nphy_classifier(dev, 0, 0); + b43_nphy_classifier(dev, 0x7, 0); + b43_nphy_read_clip_detection(dev, nphy->clip_state); + b43_nphy_write_clip_detection(dev, clip); + } + b43_nphy_reset_cca(dev); + } else { + if (--nphy->deaf_count == 0) { + b43_nphy_classifier(dev, 0x7, nphy->classifier_state); + b43_nphy_write_clip_detection(dev, nphy->clip_state); + } + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/AdjustLnaGainTbl */ +static void b43_nphy_adjust_lna_gain_table(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + + u8 i; + s16 tmp; + u16 data[4]; + s16 gain[2]; + u16 minmax[2]; + static const u16 lna_gain[4] = { -2, 10, 19, 25 }; + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, 1); + + if (nphy->gain_boost) { + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + gain[0] = 6; + gain[1] = 6; + } else { + tmp = 40370 - 315 * dev->phy.channel; + gain[0] = ((tmp >> 13) + ((tmp >> 12) & 1)); + tmp = 23242 - 224 * dev->phy.channel; + gain[1] = ((tmp >> 13) + ((tmp >> 12) & 1)); + } + } else { + gain[0] = 0; + gain[1] = 0; + } + + for (i = 0; i < 2; i++) { + if (nphy->elna_gain_config) { + data[0] = 19 + gain[i]; + data[1] = 25 + gain[i]; + data[2] = 25 + gain[i]; + data[3] = 25 + gain[i]; + } else { + data[0] = lna_gain[0] + gain[i]; + data[1] = lna_gain[1] + gain[i]; + data[2] = lna_gain[2] + gain[i]; + data[3] = lna_gain[3] + gain[i]; + } + b43_ntab_write_bulk(dev, B43_NTAB16(i, 8), 4, data); + + minmax[i] = 23 + gain[i]; + } + + b43_phy_maskset(dev, B43_NPHY_C1_MINMAX_GAIN, ~B43_NPHY_C1_MINGAIN, + minmax[0] << B43_NPHY_C1_MINGAIN_SHIFT); + b43_phy_maskset(dev, B43_NPHY_C2_MINMAX_GAIN, ~B43_NPHY_C2_MINGAIN, + minmax[1] << B43_NPHY_C2_MINGAIN_SHIFT); + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, 0); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/SetRfSeq */ +static void b43_nphy_set_rf_sequence(struct b43_wldev *dev, u8 cmd, + u8 *events, u8 *delays, u8 length) +{ + struct b43_phy_n *nphy = dev->phy.n; + u8 i; + u8 end = (dev->phy.rev >= 3) ? 0x1F : 0x0F; + u16 offset1 = cmd << 4; + u16 offset2 = offset1 + 0x80; + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, true); + + b43_ntab_write_bulk(dev, B43_NTAB8(7, offset1), length, events); + b43_ntab_write_bulk(dev, B43_NTAB8(7, offset2), length, delays); + + for (i = length; i < 16; i++) { + b43_ntab_write(dev, B43_NTAB8(7, offset1 + i), end); + b43_ntab_write(dev, B43_NTAB8(7, offset2 + i), 1); + } + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, false); +} + +/************************************************** + * Radio 0x2057 + **************************************************/ + +/* http://bcm-v4.sipsolutions.net/PHY/radio2057_rcal */ +static u8 b43_radio_2057_rcal(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + u16 tmp; + + if (phy->radio_rev == 5) { + b43_phy_mask(dev, 0x342, ~0x2); + udelay(10); + b43_radio_set(dev, R2057_IQTEST_SEL_PU, 0x1); + b43_radio_maskset(dev, 0x1ca, ~0x2, 0x1); + } + + b43_radio_set(dev, R2057_RCAL_CONFIG, 0x1); + udelay(10); + b43_radio_set(dev, R2057_RCAL_CONFIG, 0x3); + if (!b43_radio_wait_value(dev, R2057_RCCAL_N1_1, 1, 1, 100, 1000000)) { + b43err(dev->wl, "Radio 0x2057 rcal timeout\n"); + return 0; + } + b43_radio_mask(dev, R2057_RCAL_CONFIG, ~0x2); + tmp = b43_radio_read(dev, R2057_RCAL_STATUS) & 0x3E; + b43_radio_mask(dev, R2057_RCAL_CONFIG, ~0x1); + + if (phy->radio_rev == 5) { + b43_radio_mask(dev, R2057_IPA2G_CASCONV_CORE0, ~0x1); + b43_radio_mask(dev, 0x1ca, ~0x2); + } + if (phy->radio_rev <= 4 || phy->radio_rev == 6) { + b43_radio_maskset(dev, R2057_TEMPSENSE_CONFIG, ~0x3C, tmp); + b43_radio_maskset(dev, R2057_BANDGAP_RCAL_TRIM, ~0xF0, + tmp << 2); + } + + return tmp & 0x3e; +} + +/* http://bcm-v4.sipsolutions.net/PHY/radio2057_rccal */ +static u16 b43_radio_2057_rccal(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + bool special = (phy->radio_rev == 3 || phy->radio_rev == 4 || + phy->radio_rev == 6); + u16 tmp; + + if (special) { + b43_radio_write(dev, R2057_RCCAL_MASTER, 0x61); + b43_radio_write(dev, R2057_RCCAL_TRC0, 0xC0); + } else { + b43_radio_write(dev, 0x1AE, 0x61); + b43_radio_write(dev, R2057_RCCAL_TRC0, 0xE1); + } + b43_radio_write(dev, R2057_RCCAL_X1, 0x6E); + b43_radio_write(dev, R2057_RCCAL_START_R1_Q1_P1, 0x55); + if (!b43_radio_wait_value(dev, R2057_RCCAL_DONE_OSCCAP, 1, 1, 500, + 5000000)) + b43dbg(dev->wl, "Radio 0x2057 rccal timeout\n"); + b43_radio_write(dev, R2057_RCCAL_START_R1_Q1_P1, 0x15); + if (special) { + b43_radio_write(dev, R2057_RCCAL_MASTER, 0x69); + b43_radio_write(dev, R2057_RCCAL_TRC0, 0xB0); + } else { + b43_radio_write(dev, 0x1AE, 0x69); + b43_radio_write(dev, R2057_RCCAL_TRC0, 0xD5); + } + b43_radio_write(dev, R2057_RCCAL_X1, 0x6E); + b43_radio_write(dev, R2057_RCCAL_START_R1_Q1_P1, 0x55); + if (!b43_radio_wait_value(dev, R2057_RCCAL_DONE_OSCCAP, 1, 1, 500, + 5000000)) + b43dbg(dev->wl, "Radio 0x2057 rccal timeout\n"); + b43_radio_write(dev, R2057_RCCAL_START_R1_Q1_P1, 0x15); + if (special) { + b43_radio_write(dev, R2057_RCCAL_MASTER, 0x73); + b43_radio_write(dev, R2057_RCCAL_X1, 0x28); + b43_radio_write(dev, R2057_RCCAL_TRC0, 0xB0); + } else { + b43_radio_write(dev, 0x1AE, 0x73); + b43_radio_write(dev, R2057_RCCAL_X1, 0x6E); + b43_radio_write(dev, R2057_RCCAL_TRC0, 0x99); + } + b43_radio_write(dev, R2057_RCCAL_START_R1_Q1_P1, 0x55); + if (!b43_radio_wait_value(dev, R2057_RCCAL_DONE_OSCCAP, 1, 1, 500, + 5000000)) { + b43err(dev->wl, "Radio 0x2057 rcal timeout\n"); + return 0; + } + tmp = b43_radio_read(dev, R2057_RCCAL_DONE_OSCCAP); + b43_radio_write(dev, R2057_RCCAL_START_R1_Q1_P1, 0x15); + return tmp; +} + +static void b43_radio_2057_init_pre(struct b43_wldev *dev) +{ + b43_phy_mask(dev, B43_NPHY_RFCTL_CMD, ~B43_NPHY_RFCTL_CMD_CHIP0PU); + /* Maybe wl meant to reset and set (order?) RFCTL_CMD_OEPORFORCE? */ + b43_phy_mask(dev, B43_NPHY_RFCTL_CMD, B43_NPHY_RFCTL_CMD_OEPORFORCE); + b43_phy_set(dev, B43_NPHY_RFCTL_CMD, ~B43_NPHY_RFCTL_CMD_OEPORFORCE); + b43_phy_set(dev, B43_NPHY_RFCTL_CMD, B43_NPHY_RFCTL_CMD_CHIP0PU); +} + +static void b43_radio_2057_init_post(struct b43_wldev *dev) +{ + b43_radio_set(dev, R2057_XTALPUOVR_PINCTRL, 0x1); + + b43_radio_set(dev, R2057_RFPLL_MISC_CAL_RESETN, 0x78); + b43_radio_set(dev, R2057_XTAL_CONFIG2, 0x80); + mdelay(2); + b43_radio_mask(dev, R2057_RFPLL_MISC_CAL_RESETN, ~0x78); + b43_radio_mask(dev, R2057_XTAL_CONFIG2, ~0x80); + + if (dev->phy.n->init_por) { + b43_radio_2057_rcal(dev); + b43_radio_2057_rccal(dev); + } + b43_radio_mask(dev, R2057_RFPLL_MASTER, ~0x8); + + dev->phy.n->init_por = false; +} + +/* http://bcm-v4.sipsolutions.net/802.11/Radio/2057/Init */ +static void b43_radio_2057_init(struct b43_wldev *dev) +{ + b43_radio_2057_init_pre(dev); + r2057_upload_inittabs(dev); + b43_radio_2057_init_post(dev); +} + +/************************************************** + * Radio 0x2056 + **************************************************/ + +static void b43_chantab_radio_2056_upload(struct b43_wldev *dev, + const struct b43_nphy_channeltab_entry_rev3 *e) +{ + b43_radio_write(dev, B2056_SYN_PLL_VCOCAL1, e->radio_syn_pll_vcocal1); + b43_radio_write(dev, B2056_SYN_PLL_VCOCAL2, e->radio_syn_pll_vcocal2); + b43_radio_write(dev, B2056_SYN_PLL_REFDIV, e->radio_syn_pll_refdiv); + b43_radio_write(dev, B2056_SYN_PLL_MMD2, e->radio_syn_pll_mmd2); + b43_radio_write(dev, B2056_SYN_PLL_MMD1, e->radio_syn_pll_mmd1); + b43_radio_write(dev, B2056_SYN_PLL_LOOPFILTER1, + e->radio_syn_pll_loopfilter1); + b43_radio_write(dev, B2056_SYN_PLL_LOOPFILTER2, + e->radio_syn_pll_loopfilter2); + b43_radio_write(dev, B2056_SYN_PLL_LOOPFILTER3, + e->radio_syn_pll_loopfilter3); + b43_radio_write(dev, B2056_SYN_PLL_LOOPFILTER4, + e->radio_syn_pll_loopfilter4); + b43_radio_write(dev, B2056_SYN_PLL_LOOPFILTER5, + e->radio_syn_pll_loopfilter5); + b43_radio_write(dev, B2056_SYN_RESERVED_ADDR27, + e->radio_syn_reserved_addr27); + b43_radio_write(dev, B2056_SYN_RESERVED_ADDR28, + e->radio_syn_reserved_addr28); + b43_radio_write(dev, B2056_SYN_RESERVED_ADDR29, + e->radio_syn_reserved_addr29); + b43_radio_write(dev, B2056_SYN_LOGEN_VCOBUF1, + e->radio_syn_logen_vcobuf1); + b43_radio_write(dev, B2056_SYN_LOGEN_MIXER2, e->radio_syn_logen_mixer2); + b43_radio_write(dev, B2056_SYN_LOGEN_BUF3, e->radio_syn_logen_buf3); + b43_radio_write(dev, B2056_SYN_LOGEN_BUF4, e->radio_syn_logen_buf4); + + b43_radio_write(dev, B2056_RX0 | B2056_RX_LNAA_TUNE, + e->radio_rx0_lnaa_tune); + b43_radio_write(dev, B2056_RX0 | B2056_RX_LNAG_TUNE, + e->radio_rx0_lnag_tune); + + b43_radio_write(dev, B2056_TX0 | B2056_TX_INTPAA_BOOST_TUNE, + e->radio_tx0_intpaa_boost_tune); + b43_radio_write(dev, B2056_TX0 | B2056_TX_INTPAG_BOOST_TUNE, + e->radio_tx0_intpag_boost_tune); + b43_radio_write(dev, B2056_TX0 | B2056_TX_PADA_BOOST_TUNE, + e->radio_tx0_pada_boost_tune); + b43_radio_write(dev, B2056_TX0 | B2056_TX_PADG_BOOST_TUNE, + e->radio_tx0_padg_boost_tune); + b43_radio_write(dev, B2056_TX0 | B2056_TX_PGAA_BOOST_TUNE, + e->radio_tx0_pgaa_boost_tune); + b43_radio_write(dev, B2056_TX0 | B2056_TX_PGAG_BOOST_TUNE, + e->radio_tx0_pgag_boost_tune); + b43_radio_write(dev, B2056_TX0 | B2056_TX_MIXA_BOOST_TUNE, + e->radio_tx0_mixa_boost_tune); + b43_radio_write(dev, B2056_TX0 | B2056_TX_MIXG_BOOST_TUNE, + e->radio_tx0_mixg_boost_tune); + + b43_radio_write(dev, B2056_RX1 | B2056_RX_LNAA_TUNE, + e->radio_rx1_lnaa_tune); + b43_radio_write(dev, B2056_RX1 | B2056_RX_LNAG_TUNE, + e->radio_rx1_lnag_tune); + + b43_radio_write(dev, B2056_TX1 | B2056_TX_INTPAA_BOOST_TUNE, + e->radio_tx1_intpaa_boost_tune); + b43_radio_write(dev, B2056_TX1 | B2056_TX_INTPAG_BOOST_TUNE, + e->radio_tx1_intpag_boost_tune); + b43_radio_write(dev, B2056_TX1 | B2056_TX_PADA_BOOST_TUNE, + e->radio_tx1_pada_boost_tune); + b43_radio_write(dev, B2056_TX1 | B2056_TX_PADG_BOOST_TUNE, + e->radio_tx1_padg_boost_tune); + b43_radio_write(dev, B2056_TX1 | B2056_TX_PGAA_BOOST_TUNE, + e->radio_tx1_pgaa_boost_tune); + b43_radio_write(dev, B2056_TX1 | B2056_TX_PGAG_BOOST_TUNE, + e->radio_tx1_pgag_boost_tune); + b43_radio_write(dev, B2056_TX1 | B2056_TX_MIXA_BOOST_TUNE, + e->radio_tx1_mixa_boost_tune); + b43_radio_write(dev, B2056_TX1 | B2056_TX_MIXG_BOOST_TUNE, + e->radio_tx1_mixg_boost_tune); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/Radio/2056Setup */ +static void b43_radio_2056_setup(struct b43_wldev *dev, + const struct b43_nphy_channeltab_entry_rev3 *e) +{ + struct ssb_sprom *sprom = dev->dev->bus_sprom; + enum ieee80211_band band = b43_current_band(dev->wl); + u16 offset; + u8 i; + u16 bias, cbias; + u16 pag_boost, padg_boost, pgag_boost, mixg_boost; + u16 paa_boost, pada_boost, pgaa_boost, mixa_boost; + + B43_WARN_ON(dev->phy.rev < 3); + + b43_chantab_radio_2056_upload(dev, e); + b2056_upload_syn_pll_cp2(dev, band == IEEE80211_BAND_5GHZ); + + if (sprom->boardflags2_lo & B43_BFL2_GPLL_WAR && + b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + b43_radio_write(dev, B2056_SYN_PLL_LOOPFILTER1, 0x1F); + b43_radio_write(dev, B2056_SYN_PLL_LOOPFILTER2, 0x1F); + if (dev->dev->chip_id == 0x4716) { + b43_radio_write(dev, B2056_SYN_PLL_LOOPFILTER4, 0x14); + b43_radio_write(dev, B2056_SYN_PLL_CP2, 0); + } else { + b43_radio_write(dev, B2056_SYN_PLL_LOOPFILTER4, 0x0B); + b43_radio_write(dev, B2056_SYN_PLL_CP2, 0x14); + } + } + if (sprom->boardflags2_lo & B43_BFL2_APLL_WAR && + b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) { + b43_radio_write(dev, B2056_SYN_PLL_LOOPFILTER1, 0x1F); + b43_radio_write(dev, B2056_SYN_PLL_LOOPFILTER2, 0x1F); + b43_radio_write(dev, B2056_SYN_PLL_LOOPFILTER4, 0x05); + b43_radio_write(dev, B2056_SYN_PLL_CP2, 0x0C); + } + + if (dev->phy.n->ipa2g_on && band == IEEE80211_BAND_2GHZ) { + for (i = 0; i < 2; i++) { + offset = i ? B2056_TX1 : B2056_TX0; + if (dev->phy.rev >= 5) { + b43_radio_write(dev, + offset | B2056_TX_PADG_IDAC, 0xcc); + + if (dev->dev->chip_id == 0x4716) { + bias = 0x40; + cbias = 0x45; + pag_boost = 0x5; + pgag_boost = 0x33; + mixg_boost = 0x55; + } else { + bias = 0x25; + cbias = 0x20; + pag_boost = 0x4; + pgag_boost = 0x03; + mixg_boost = 0x65; + } + padg_boost = 0x77; + + b43_radio_write(dev, + offset | B2056_TX_INTPAG_IMAIN_STAT, + bias); + b43_radio_write(dev, + offset | B2056_TX_INTPAG_IAUX_STAT, + bias); + b43_radio_write(dev, + offset | B2056_TX_INTPAG_CASCBIAS, + cbias); + b43_radio_write(dev, + offset | B2056_TX_INTPAG_BOOST_TUNE, + pag_boost); + b43_radio_write(dev, + offset | B2056_TX_PGAG_BOOST_TUNE, + pgag_boost); + b43_radio_write(dev, + offset | B2056_TX_PADG_BOOST_TUNE, + padg_boost); + b43_radio_write(dev, + offset | B2056_TX_MIXG_BOOST_TUNE, + mixg_boost); + } else { + bias = dev->phy.is_40mhz ? 0x40 : 0x20; + b43_radio_write(dev, + offset | B2056_TX_INTPAG_IMAIN_STAT, + bias); + b43_radio_write(dev, + offset | B2056_TX_INTPAG_IAUX_STAT, + bias); + b43_radio_write(dev, + offset | B2056_TX_INTPAG_CASCBIAS, + 0x30); + } + b43_radio_write(dev, offset | B2056_TX_PA_SPARE1, 0xee); + } + } else if (dev->phy.n->ipa5g_on && band == IEEE80211_BAND_5GHZ) { + u16 freq = dev->phy.channel_freq; + if (freq < 5100) { + paa_boost = 0xA; + pada_boost = 0x77; + pgaa_boost = 0xF; + mixa_boost = 0xF; + } else if (freq < 5340) { + paa_boost = 0x8; + pada_boost = 0x77; + pgaa_boost = 0xFB; + mixa_boost = 0xF; + } else if (freq < 5650) { + paa_boost = 0x0; + pada_boost = 0x77; + pgaa_boost = 0xB; + mixa_boost = 0xF; + } else { + paa_boost = 0x0; + pada_boost = 0x77; + if (freq != 5825) + pgaa_boost = -(freq - 18) / 36 + 168; + else + pgaa_boost = 6; + mixa_boost = 0xF; + } + + for (i = 0; i < 2; i++) { + offset = i ? B2056_TX1 : B2056_TX0; + + b43_radio_write(dev, + offset | B2056_TX_INTPAA_BOOST_TUNE, paa_boost); + b43_radio_write(dev, + offset | B2056_TX_PADA_BOOST_TUNE, pada_boost); + b43_radio_write(dev, + offset | B2056_TX_PGAA_BOOST_TUNE, pgaa_boost); + b43_radio_write(dev, + offset | B2056_TX_MIXA_BOOST_TUNE, mixa_boost); + b43_radio_write(dev, + offset | B2056_TX_TXSPARE1, 0x30); + b43_radio_write(dev, + offset | B2056_TX_PA_SPARE2, 0xee); + b43_radio_write(dev, + offset | B2056_TX_PADA_CASCBIAS, 0x03); + b43_radio_write(dev, + offset | B2056_TX_INTPAA_IAUX_STAT, 0x50); + b43_radio_write(dev, + offset | B2056_TX_INTPAA_IMAIN_STAT, 0x50); + b43_radio_write(dev, + offset | B2056_TX_INTPAA_CASCBIAS, 0x30); + } + } + + udelay(50); + /* VCO calibration */ + b43_radio_write(dev, B2056_SYN_PLL_VCOCAL12, 0x00); + b43_radio_write(dev, B2056_TX_INTPAA_PA_MISC, 0x38); + b43_radio_write(dev, B2056_TX_INTPAA_PA_MISC, 0x18); + b43_radio_write(dev, B2056_TX_INTPAA_PA_MISC, 0x38); + b43_radio_write(dev, B2056_TX_INTPAA_PA_MISC, 0x39); + udelay(300); +} + +static u8 b43_radio_2056_rcal(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + u16 mast2, tmp; + + if (phy->rev != 3) + return 0; + + mast2 = b43_radio_read(dev, B2056_SYN_PLL_MAST2); + b43_radio_write(dev, B2056_SYN_PLL_MAST2, mast2 | 0x7); + + udelay(10); + b43_radio_write(dev, B2056_SYN_RCAL_MASTER, 0x01); + udelay(10); + b43_radio_write(dev, B2056_SYN_RCAL_MASTER, 0x09); + + if (!b43_radio_wait_value(dev, B2056_SYN_RCAL_CODE_OUT, 0x80, 0x80, 100, + 1000000)) { + b43err(dev->wl, "Radio recalibration timeout\n"); + return 0; + } + + b43_radio_write(dev, B2056_SYN_RCAL_MASTER, 0x01); + tmp = b43_radio_read(dev, B2056_SYN_RCAL_CODE_OUT); + b43_radio_write(dev, B2056_SYN_RCAL_MASTER, 0x00); + + b43_radio_write(dev, B2056_SYN_PLL_MAST2, mast2); + + return tmp & 0x1f; +} + +static void b43_radio_init2056_pre(struct b43_wldev *dev) +{ + b43_phy_mask(dev, B43_NPHY_RFCTL_CMD, + ~B43_NPHY_RFCTL_CMD_CHIP0PU); + /* Maybe wl meant to reset and set (order?) RFCTL_CMD_OEPORFORCE? */ + b43_phy_mask(dev, B43_NPHY_RFCTL_CMD, + B43_NPHY_RFCTL_CMD_OEPORFORCE); + b43_phy_set(dev, B43_NPHY_RFCTL_CMD, + ~B43_NPHY_RFCTL_CMD_OEPORFORCE); + b43_phy_set(dev, B43_NPHY_RFCTL_CMD, + B43_NPHY_RFCTL_CMD_CHIP0PU); +} + +static void b43_radio_init2056_post(struct b43_wldev *dev) +{ + b43_radio_set(dev, B2056_SYN_COM_CTRL, 0xB); + b43_radio_set(dev, B2056_SYN_COM_PU, 0x2); + b43_radio_set(dev, B2056_SYN_COM_RESET, 0x2); + msleep(1); + b43_radio_mask(dev, B2056_SYN_COM_RESET, ~0x2); + b43_radio_mask(dev, B2056_SYN_PLL_MAST2, ~0xFC); + b43_radio_mask(dev, B2056_SYN_RCCAL_CTRL0, ~0x1); + if (dev->phy.n->init_por) + b43_radio_2056_rcal(dev); +} + +/* + * Initialize a Broadcom 2056 N-radio + * http://bcm-v4.sipsolutions.net/802.11/Radio/2056/Init + */ +static void b43_radio_init2056(struct b43_wldev *dev) +{ + b43_radio_init2056_pre(dev); + b2056_upload_inittabs(dev, 0, 0); + b43_radio_init2056_post(dev); + + dev->phy.n->init_por = false; +} + +/************************************************** + * Radio 0x2055 + **************************************************/ + +static void b43_chantab_radio_upload(struct b43_wldev *dev, + const struct b43_nphy_channeltab_entry_rev2 *e) +{ + b43_radio_write(dev, B2055_PLL_REF, e->radio_pll_ref); + b43_radio_write(dev, B2055_RF_PLLMOD0, e->radio_rf_pllmod0); + b43_radio_write(dev, B2055_RF_PLLMOD1, e->radio_rf_pllmod1); + b43_radio_write(dev, B2055_VCO_CAPTAIL, e->radio_vco_captail); + b43_read32(dev, B43_MMIO_MACCTL); /* flush writes */ + + b43_radio_write(dev, B2055_VCO_CAL1, e->radio_vco_cal1); + b43_radio_write(dev, B2055_VCO_CAL2, e->radio_vco_cal2); + b43_radio_write(dev, B2055_PLL_LFC1, e->radio_pll_lfc1); + b43_radio_write(dev, B2055_PLL_LFR1, e->radio_pll_lfr1); + b43_read32(dev, B43_MMIO_MACCTL); /* flush writes */ + + b43_radio_write(dev, B2055_PLL_LFC2, e->radio_pll_lfc2); + b43_radio_write(dev, B2055_LGBUF_CENBUF, e->radio_lgbuf_cenbuf); + b43_radio_write(dev, B2055_LGEN_TUNE1, e->radio_lgen_tune1); + b43_radio_write(dev, B2055_LGEN_TUNE2, e->radio_lgen_tune2); + b43_read32(dev, B43_MMIO_MACCTL); /* flush writes */ + + b43_radio_write(dev, B2055_C1_LGBUF_ATUNE, e->radio_c1_lgbuf_atune); + b43_radio_write(dev, B2055_C1_LGBUF_GTUNE, e->radio_c1_lgbuf_gtune); + b43_radio_write(dev, B2055_C1_RX_RFR1, e->radio_c1_rx_rfr1); + b43_radio_write(dev, B2055_C1_TX_PGAPADTN, e->radio_c1_tx_pgapadtn); + b43_read32(dev, B43_MMIO_MACCTL); /* flush writes */ + + b43_radio_write(dev, B2055_C1_TX_MXBGTRIM, e->radio_c1_tx_mxbgtrim); + b43_radio_write(dev, B2055_C2_LGBUF_ATUNE, e->radio_c2_lgbuf_atune); + b43_radio_write(dev, B2055_C2_LGBUF_GTUNE, e->radio_c2_lgbuf_gtune); + b43_radio_write(dev, B2055_C2_RX_RFR1, e->radio_c2_rx_rfr1); + b43_read32(dev, B43_MMIO_MACCTL); /* flush writes */ + + b43_radio_write(dev, B2055_C2_TX_PGAPADTN, e->radio_c2_tx_pgapadtn); + b43_radio_write(dev, B2055_C2_TX_MXBGTRIM, e->radio_c2_tx_mxbgtrim); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/Radio/2055Setup */ +static void b43_radio_2055_setup(struct b43_wldev *dev, + const struct b43_nphy_channeltab_entry_rev2 *e) +{ + B43_WARN_ON(dev->phy.rev >= 3); + + b43_chantab_radio_upload(dev, e); + udelay(50); + b43_radio_write(dev, B2055_VCO_CAL10, 0x05); + b43_radio_write(dev, B2055_VCO_CAL10, 0x45); + b43_read32(dev, B43_MMIO_MACCTL); /* flush writes */ + b43_radio_write(dev, B2055_VCO_CAL10, 0x65); + udelay(300); +} + +static void b43_radio_init2055_pre(struct b43_wldev *dev) +{ + b43_phy_mask(dev, B43_NPHY_RFCTL_CMD, + ~B43_NPHY_RFCTL_CMD_PORFORCE); + b43_phy_set(dev, B43_NPHY_RFCTL_CMD, + B43_NPHY_RFCTL_CMD_CHIP0PU | + B43_NPHY_RFCTL_CMD_OEPORFORCE); + b43_phy_set(dev, B43_NPHY_RFCTL_CMD, + B43_NPHY_RFCTL_CMD_PORFORCE); +} + +static void b43_radio_init2055_post(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + struct ssb_sprom *sprom = dev->dev->bus_sprom; + bool workaround = false; + + if (sprom->revision < 4) + workaround = (dev->dev->board_vendor != PCI_VENDOR_ID_BROADCOM + && dev->dev->board_type == SSB_BOARD_CB2_4321 + && dev->dev->board_rev >= 0x41); + else + workaround = + !(sprom->boardflags2_lo & B43_BFL2_RXBB_INT_REG_DIS); + + b43_radio_mask(dev, B2055_MASTER1, 0xFFF3); + if (workaround) { + b43_radio_mask(dev, B2055_C1_RX_BB_REG, 0x7F); + b43_radio_mask(dev, B2055_C2_RX_BB_REG, 0x7F); + } + b43_radio_maskset(dev, B2055_RRCCAL_NOPTSEL, 0xFFC0, 0x2C); + b43_radio_write(dev, B2055_CAL_MISC, 0x3C); + b43_radio_mask(dev, B2055_CAL_MISC, 0xFFBE); + b43_radio_set(dev, B2055_CAL_LPOCTL, 0x80); + b43_radio_set(dev, B2055_CAL_MISC, 0x1); + msleep(1); + b43_radio_set(dev, B2055_CAL_MISC, 0x40); + if (!b43_radio_wait_value(dev, B2055_CAL_COUT2, 0x80, 0x80, 10, 2000)) + b43err(dev->wl, "radio post init timeout\n"); + b43_radio_mask(dev, B2055_CAL_LPOCTL, 0xFF7F); + b43_switch_channel(dev, dev->phy.channel); + b43_radio_write(dev, B2055_C1_RX_BB_LPF, 0x9); + b43_radio_write(dev, B2055_C2_RX_BB_LPF, 0x9); + b43_radio_write(dev, B2055_C1_RX_BB_MIDACHP, 0x83); + b43_radio_write(dev, B2055_C2_RX_BB_MIDACHP, 0x83); + b43_radio_maskset(dev, B2055_C1_LNA_GAINBST, 0xFFF8, 0x6); + b43_radio_maskset(dev, B2055_C2_LNA_GAINBST, 0xFFF8, 0x6); + if (!nphy->gain_boost) { + b43_radio_set(dev, B2055_C1_RX_RFSPC1, 0x2); + b43_radio_set(dev, B2055_C2_RX_RFSPC1, 0x2); + } else { + b43_radio_mask(dev, B2055_C1_RX_RFSPC1, 0xFFFD); + b43_radio_mask(dev, B2055_C2_RX_RFSPC1, 0xFFFD); + } + udelay(2); +} + +/* + * Initialize a Broadcom 2055 N-radio + * http://bcm-v4.sipsolutions.net/802.11/Radio/2055/Init + */ +static void b43_radio_init2055(struct b43_wldev *dev) +{ + b43_radio_init2055_pre(dev); + if (b43_status(dev) < B43_STAT_INITIALIZED) { + /* Follow wl, not specs. Do not force uploading all regs */ + b2055_upload_inittab(dev, 0, 0); + } else { + bool ghz5 = b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ; + b2055_upload_inittab(dev, ghz5, 0); + } + b43_radio_init2055_post(dev); +} + +/************************************************** + * Samples + **************************************************/ + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/LoadSampleTable */ +static int b43_nphy_load_samples(struct b43_wldev *dev, + struct b43_c32 *samples, u16 len) { + struct b43_phy_n *nphy = dev->phy.n; + u16 i; + u32 *data; + + data = kzalloc(len * sizeof(u32), GFP_KERNEL); + if (!data) { + b43err(dev->wl, "allocation for samples loading failed\n"); + return -ENOMEM; + } + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, 1); + + for (i = 0; i < len; i++) { + data[i] = (samples[i].i & 0x3FF << 10); + data[i] |= samples[i].q & 0x3FF; + } + b43_ntab_write_bulk(dev, B43_NTAB32(17, 0), len, data); + + kfree(data); + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, 0); + return 0; +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/GenLoadSamples */ +static u16 b43_nphy_gen_load_samples(struct b43_wldev *dev, u32 freq, u16 max, + bool test) +{ + int i; + u16 bw, len, rot, angle; + struct b43_c32 *samples; + + + bw = (dev->phy.is_40mhz) ? 40 : 20; + len = bw << 3; + + if (test) { + if (b43_phy_read(dev, B43_NPHY_BBCFG) & B43_NPHY_BBCFG_RSTRX) + bw = 82; + else + bw = 80; + + if (dev->phy.is_40mhz) + bw <<= 1; + + len = bw << 1; + } + + samples = kcalloc(len, sizeof(struct b43_c32), GFP_KERNEL); + if (!samples) { + b43err(dev->wl, "allocation for samples generation failed\n"); + return 0; + } + rot = (((freq * 36) / bw) << 16) / 100; + angle = 0; + + for (i = 0; i < len; i++) { + samples[i] = b43_cordic(angle); + angle += rot; + samples[i].q = CORDIC_CONVERT(samples[i].q * max); + samples[i].i = CORDIC_CONVERT(samples[i].i * max); + } + + i = b43_nphy_load_samples(dev, samples, len); + kfree(samples); + return (i < 0) ? 0 : len; +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/RunSamples */ +static void b43_nphy_run_samples(struct b43_wldev *dev, u16 samps, u16 loops, + u16 wait, bool iqmode, bool dac_test) +{ + struct b43_phy_n *nphy = dev->phy.n; + int i; + u16 seq_mode; + u32 tmp; + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, true); + + if ((nphy->bb_mult_save & 0x80000000) == 0) { + tmp = b43_ntab_read(dev, B43_NTAB16(15, 87)); + nphy->bb_mult_save = (tmp & 0xFFFF) | 0x80000000; + } + + if (!dev->phy.is_40mhz) + tmp = 0x6464; + else + tmp = 0x4747; + b43_ntab_write(dev, B43_NTAB16(15, 87), tmp); + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, false); + + b43_phy_write(dev, B43_NPHY_SAMP_DEPCNT, (samps - 1)); + + if (loops != 0xFFFF) + b43_phy_write(dev, B43_NPHY_SAMP_LOOPCNT, (loops - 1)); + else + b43_phy_write(dev, B43_NPHY_SAMP_LOOPCNT, loops); + + b43_phy_write(dev, B43_NPHY_SAMP_WAITCNT, wait); + + seq_mode = b43_phy_read(dev, B43_NPHY_RFSEQMODE); + + b43_phy_set(dev, B43_NPHY_RFSEQMODE, B43_NPHY_RFSEQMODE_CAOVER); + if (iqmode) { + b43_phy_mask(dev, B43_NPHY_IQLOCAL_CMDGCTL, 0x7FFF); + b43_phy_set(dev, B43_NPHY_IQLOCAL_CMDGCTL, 0x8000); + } else { + if (dac_test) + b43_phy_write(dev, B43_NPHY_SAMP_CMD, 5); + else + b43_phy_write(dev, B43_NPHY_SAMP_CMD, 1); + } + for (i = 0; i < 100; i++) { + if (!(b43_phy_read(dev, B43_NPHY_RFSEQST) & 1)) { + i = 0; + break; + } + udelay(10); + } + if (i) + b43err(dev->wl, "run samples timeout\n"); + + b43_phy_write(dev, B43_NPHY_RFSEQMODE, seq_mode); +} + +/************************************************** + * RSSI + **************************************************/ + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/ScaleOffsetRssi */ +static void b43_nphy_scale_offset_rssi(struct b43_wldev *dev, u16 scale, + s8 offset, u8 core, + enum n_rail_type rail, + enum n_rssi_type rssi_type) +{ + u16 tmp; + bool core1or5 = (core == 1) || (core == 5); + bool core2or5 = (core == 2) || (core == 5); + + offset = clamp_val(offset, -32, 31); + tmp = ((scale & 0x3F) << 8) | (offset & 0x3F); + + switch (rssi_type) { + case N_RSSI_NB: + if (core1or5 && rail == N_RAIL_I) + b43_phy_write(dev, B43_NPHY_RSSIMC_0I_RSSI_Z, tmp); + if (core1or5 && rail == N_RAIL_Q) + b43_phy_write(dev, B43_NPHY_RSSIMC_0Q_RSSI_Z, tmp); + if (core2or5 && rail == N_RAIL_I) + b43_phy_write(dev, B43_NPHY_RSSIMC_1I_RSSI_Z, tmp); + if (core2or5 && rail == N_RAIL_Q) + b43_phy_write(dev, B43_NPHY_RSSIMC_1Q_RSSI_Z, tmp); + break; + case N_RSSI_W1: + if (core1or5 && rail == N_RAIL_I) + b43_phy_write(dev, B43_NPHY_RSSIMC_0I_RSSI_X, tmp); + if (core1or5 && rail == N_RAIL_Q) + b43_phy_write(dev, B43_NPHY_RSSIMC_0Q_RSSI_X, tmp); + if (core2or5 && rail == N_RAIL_I) + b43_phy_write(dev, B43_NPHY_RSSIMC_1I_RSSI_X, tmp); + if (core2or5 && rail == N_RAIL_Q) + b43_phy_write(dev, B43_NPHY_RSSIMC_1Q_RSSI_X, tmp); + break; + case N_RSSI_W2: + if (core1or5 && rail == N_RAIL_I) + b43_phy_write(dev, B43_NPHY_RSSIMC_0I_RSSI_Y, tmp); + if (core1or5 && rail == N_RAIL_Q) + b43_phy_write(dev, B43_NPHY_RSSIMC_0Q_RSSI_Y, tmp); + if (core2or5 && rail == N_RAIL_I) + b43_phy_write(dev, B43_NPHY_RSSIMC_1I_RSSI_Y, tmp); + if (core2or5 && rail == N_RAIL_Q) + b43_phy_write(dev, B43_NPHY_RSSIMC_1Q_RSSI_Y, tmp); + break; + case N_RSSI_TBD: + if (core1or5 && rail == N_RAIL_I) + b43_phy_write(dev, B43_NPHY_RSSIMC_0I_TBD, tmp); + if (core1or5 && rail == N_RAIL_Q) + b43_phy_write(dev, B43_NPHY_RSSIMC_0Q_TBD, tmp); + if (core2or5 && rail == N_RAIL_I) + b43_phy_write(dev, B43_NPHY_RSSIMC_1I_TBD, tmp); + if (core2or5 && rail == N_RAIL_Q) + b43_phy_write(dev, B43_NPHY_RSSIMC_1Q_TBD, tmp); + break; + case N_RSSI_IQ: + if (core1or5 && rail == N_RAIL_I) + b43_phy_write(dev, B43_NPHY_RSSIMC_0I_PWRDET, tmp); + if (core1or5 && rail == N_RAIL_Q) + b43_phy_write(dev, B43_NPHY_RSSIMC_0Q_PWRDET, tmp); + if (core2or5 && rail == N_RAIL_I) + b43_phy_write(dev, B43_NPHY_RSSIMC_1I_PWRDET, tmp); + if (core2or5 && rail == N_RAIL_Q) + b43_phy_write(dev, B43_NPHY_RSSIMC_1Q_PWRDET, tmp); + break; + case N_RSSI_TSSI_2G: + if (core1or5) + b43_phy_write(dev, B43_NPHY_RSSIMC_0I_TSSI, tmp); + if (core2or5) + b43_phy_write(dev, B43_NPHY_RSSIMC_1I_TSSI, tmp); + break; + case N_RSSI_TSSI_5G: + if (core1or5) + b43_phy_write(dev, B43_NPHY_RSSIMC_0Q_TSSI, tmp); + if (core2or5) + b43_phy_write(dev, B43_NPHY_RSSIMC_1Q_TSSI, tmp); + break; + } +} + +static void b43_nphy_rev3_rssi_select(struct b43_wldev *dev, u8 code, + enum n_rssi_type rssi_type) +{ + u8 i; + u16 reg, val; + + if (code == 0) { + b43_phy_mask(dev, B43_NPHY_AFECTL_OVER1, 0xFDFF); + b43_phy_mask(dev, B43_NPHY_AFECTL_OVER, 0xFDFF); + b43_phy_mask(dev, B43_NPHY_AFECTL_C1, 0xFCFF); + b43_phy_mask(dev, B43_NPHY_AFECTL_C2, 0xFCFF); + b43_phy_mask(dev, B43_NPHY_TXF_40CO_B1S0, 0xFFDF); + b43_phy_mask(dev, B43_NPHY_TXF_40CO_B32S1, 0xFFDF); + b43_phy_mask(dev, B43_NPHY_RFCTL_LUT_TRSW_UP1, 0xFFC3); + b43_phy_mask(dev, B43_NPHY_RFCTL_LUT_TRSW_UP2, 0xFFC3); + } else { + for (i = 0; i < 2; i++) { + if ((code == 1 && i == 1) || (code == 2 && !i)) + continue; + + reg = (i == 0) ? + B43_NPHY_AFECTL_OVER1 : B43_NPHY_AFECTL_OVER; + b43_phy_maskset(dev, reg, 0xFDFF, 0x0200); + + if (rssi_type == N_RSSI_W1 || + rssi_type == N_RSSI_W2 || + rssi_type == N_RSSI_NB) { + reg = (i == 0) ? + B43_NPHY_AFECTL_C1 : + B43_NPHY_AFECTL_C2; + b43_phy_maskset(dev, reg, 0xFCFF, 0); + + reg = (i == 0) ? + B43_NPHY_RFCTL_LUT_TRSW_UP1 : + B43_NPHY_RFCTL_LUT_TRSW_UP2; + b43_phy_maskset(dev, reg, 0xFFC3, 0); + + if (rssi_type == N_RSSI_W1) + val = (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) ? 4 : 8; + else if (rssi_type == N_RSSI_W2) + val = 16; + else + val = 32; + b43_phy_set(dev, reg, val); + + reg = (i == 0) ? + B43_NPHY_TXF_40CO_B1S0 : + B43_NPHY_TXF_40CO_B32S1; + b43_phy_set(dev, reg, 0x0020); + } else { + if (rssi_type == N_RSSI_TBD) + val = 0x0100; + else if (rssi_type == N_RSSI_IQ) + val = 0x0200; + else + val = 0x0300; + + reg = (i == 0) ? + B43_NPHY_AFECTL_C1 : + B43_NPHY_AFECTL_C2; + + b43_phy_maskset(dev, reg, 0xFCFF, val); + b43_phy_maskset(dev, reg, 0xF3FF, val << 2); + + if (rssi_type != N_RSSI_IQ && + rssi_type != N_RSSI_TBD) { + enum ieee80211_band band = + b43_current_band(dev->wl); + + if (b43_nphy_ipa(dev)) + val = (band == IEEE80211_BAND_5GHZ) ? 0xC : 0xE; + else + val = 0x11; + reg = (i == 0) ? 0x2000 : 0x3000; + reg |= B2055_PADDRV; + b43_radio_write(dev, reg, val); + + reg = (i == 0) ? + B43_NPHY_AFECTL_OVER1 : + B43_NPHY_AFECTL_OVER; + b43_phy_set(dev, reg, 0x0200); + } + } + } + } +} + +static void b43_nphy_rev2_rssi_select(struct b43_wldev *dev, u8 code, + enum n_rssi_type rssi_type) +{ + u16 val; + bool rssi_w1_w2_nb = false; + + switch (rssi_type) { + case N_RSSI_W1: + case N_RSSI_W2: + case N_RSSI_NB: + val = 0; + rssi_w1_w2_nb = true; + break; + case N_RSSI_TBD: + val = 1; + break; + case N_RSSI_IQ: + val = 2; + break; + default: + val = 3; + } + + val = (val << 12) | (val << 14); + b43_phy_maskset(dev, B43_NPHY_AFECTL_C1, 0x0FFF, val); + b43_phy_maskset(dev, B43_NPHY_AFECTL_C2, 0x0FFF, val); + + if (rssi_w1_w2_nb) { + b43_phy_maskset(dev, B43_NPHY_RFCTL_RSSIO1, 0xFFCF, + (rssi_type + 1) << 4); + b43_phy_maskset(dev, B43_NPHY_RFCTL_RSSIO2, 0xFFCF, + (rssi_type + 1) << 4); + } + + if (code == 0) { + b43_phy_mask(dev, B43_NPHY_AFECTL_OVER, ~0x3000); + if (rssi_w1_w2_nb) { + b43_phy_mask(dev, B43_NPHY_RFCTL_CMD, + ~(B43_NPHY_RFCTL_CMD_RXEN | + B43_NPHY_RFCTL_CMD_CORESEL)); + b43_phy_mask(dev, B43_NPHY_RFCTL_OVER, + ~(0x1 << 12 | + 0x1 << 5 | + 0x1 << 1 | + 0x1)); + b43_phy_mask(dev, B43_NPHY_RFCTL_CMD, + ~B43_NPHY_RFCTL_CMD_START); + udelay(20); + b43_phy_mask(dev, B43_NPHY_RFCTL_OVER, ~0x1); + } + } else { + b43_phy_set(dev, B43_NPHY_AFECTL_OVER, 0x3000); + if (rssi_w1_w2_nb) { + b43_phy_maskset(dev, B43_NPHY_RFCTL_CMD, + ~(B43_NPHY_RFCTL_CMD_RXEN | + B43_NPHY_RFCTL_CMD_CORESEL), + (B43_NPHY_RFCTL_CMD_RXEN | + code << B43_NPHY_RFCTL_CMD_CORESEL_SHIFT)); + b43_phy_set(dev, B43_NPHY_RFCTL_OVER, + (0x1 << 12 | + 0x1 << 5 | + 0x1 << 1 | + 0x1)); + b43_phy_set(dev, B43_NPHY_RFCTL_CMD, + B43_NPHY_RFCTL_CMD_START); + udelay(20); + b43_phy_mask(dev, B43_NPHY_RFCTL_OVER, ~0x1); + } + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/RSSISel */ +static void b43_nphy_rssi_select(struct b43_wldev *dev, u8 code, + enum n_rssi_type type) +{ + if (dev->phy.rev >= 3) + b43_nphy_rev3_rssi_select(dev, code, type); + else + b43_nphy_rev2_rssi_select(dev, code, type); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/SetRssi2055Vcm */ +static void b43_nphy_set_rssi_2055_vcm(struct b43_wldev *dev, + enum n_rssi_type rssi_type, u8 *buf) +{ + int i; + for (i = 0; i < 2; i++) { + if (rssi_type == N_RSSI_NB) { + if (i == 0) { + b43_radio_maskset(dev, B2055_C1_B0NB_RSSIVCM, + 0xFC, buf[0]); + b43_radio_maskset(dev, B2055_C1_RX_BB_RSSICTL5, + 0xFC, buf[1]); + } else { + b43_radio_maskset(dev, B2055_C2_B0NB_RSSIVCM, + 0xFC, buf[2 * i]); + b43_radio_maskset(dev, B2055_C2_RX_BB_RSSICTL5, + 0xFC, buf[2 * i + 1]); + } + } else { + if (i == 0) + b43_radio_maskset(dev, B2055_C1_RX_BB_RSSICTL5, + 0xF3, buf[0] << 2); + else + b43_radio_maskset(dev, B2055_C2_RX_BB_RSSICTL5, + 0xF3, buf[2 * i + 1] << 2); + } + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/PollRssi */ +static int b43_nphy_poll_rssi(struct b43_wldev *dev, enum n_rssi_type rssi_type, + s32 *buf, u8 nsamp) +{ + int i; + int out; + u16 save_regs_phy[9]; + u16 s[2]; + + if (dev->phy.rev >= 3) { + save_regs_phy[0] = b43_phy_read(dev, B43_NPHY_AFECTL_C1); + save_regs_phy[1] = b43_phy_read(dev, B43_NPHY_AFECTL_C2); + save_regs_phy[2] = b43_phy_read(dev, + B43_NPHY_RFCTL_LUT_TRSW_UP1); + save_regs_phy[3] = b43_phy_read(dev, + B43_NPHY_RFCTL_LUT_TRSW_UP2); + save_regs_phy[4] = b43_phy_read(dev, B43_NPHY_AFECTL_OVER1); + save_regs_phy[5] = b43_phy_read(dev, B43_NPHY_AFECTL_OVER); + save_regs_phy[6] = b43_phy_read(dev, B43_NPHY_TXF_40CO_B1S0); + save_regs_phy[7] = b43_phy_read(dev, B43_NPHY_TXF_40CO_B32S1); + save_regs_phy[8] = 0; + } else { + save_regs_phy[0] = b43_phy_read(dev, B43_NPHY_AFECTL_C1); + save_regs_phy[1] = b43_phy_read(dev, B43_NPHY_AFECTL_C2); + save_regs_phy[2] = b43_phy_read(dev, B43_NPHY_AFECTL_OVER); + save_regs_phy[3] = b43_phy_read(dev, B43_NPHY_RFCTL_CMD); + save_regs_phy[4] = b43_phy_read(dev, B43_NPHY_RFCTL_OVER); + save_regs_phy[5] = b43_phy_read(dev, B43_NPHY_RFCTL_RSSIO1); + save_regs_phy[6] = b43_phy_read(dev, B43_NPHY_RFCTL_RSSIO2); + save_regs_phy[7] = 0; + save_regs_phy[8] = 0; + } + + b43_nphy_rssi_select(dev, 5, rssi_type); + + if (dev->phy.rev < 2) { + save_regs_phy[8] = b43_phy_read(dev, B43_NPHY_GPIO_SEL); + b43_phy_write(dev, B43_NPHY_GPIO_SEL, 5); + } + + for (i = 0; i < 4; i++) + buf[i] = 0; + + for (i = 0; i < nsamp; i++) { + if (dev->phy.rev < 2) { + s[0] = b43_phy_read(dev, B43_NPHY_GPIO_LOOUT); + s[1] = b43_phy_read(dev, B43_NPHY_GPIO_HIOUT); + } else { + s[0] = b43_phy_read(dev, B43_NPHY_RSSI1); + s[1] = b43_phy_read(dev, B43_NPHY_RSSI2); + } + + buf[0] += ((s8)((s[0] & 0x3F) << 2)) >> 2; + buf[1] += ((s8)(((s[0] >> 8) & 0x3F) << 2)) >> 2; + buf[2] += ((s8)((s[1] & 0x3F) << 2)) >> 2; + buf[3] += ((s8)(((s[1] >> 8) & 0x3F) << 2)) >> 2; + } + out = (buf[0] & 0xFF) << 24 | (buf[1] & 0xFF) << 16 | + (buf[2] & 0xFF) << 8 | (buf[3] & 0xFF); + + if (dev->phy.rev < 2) + b43_phy_write(dev, B43_NPHY_GPIO_SEL, save_regs_phy[8]); + + if (dev->phy.rev >= 3) { + b43_phy_write(dev, B43_NPHY_AFECTL_C1, save_regs_phy[0]); + b43_phy_write(dev, B43_NPHY_AFECTL_C2, save_regs_phy[1]); + b43_phy_write(dev, B43_NPHY_RFCTL_LUT_TRSW_UP1, + save_regs_phy[2]); + b43_phy_write(dev, B43_NPHY_RFCTL_LUT_TRSW_UP2, + save_regs_phy[3]); + b43_phy_write(dev, B43_NPHY_AFECTL_OVER1, save_regs_phy[4]); + b43_phy_write(dev, B43_NPHY_AFECTL_OVER, save_regs_phy[5]); + b43_phy_write(dev, B43_NPHY_TXF_40CO_B1S0, save_regs_phy[6]); + b43_phy_write(dev, B43_NPHY_TXF_40CO_B32S1, save_regs_phy[7]); + } else { + b43_phy_write(dev, B43_NPHY_AFECTL_C1, save_regs_phy[0]); + b43_phy_write(dev, B43_NPHY_AFECTL_C2, save_regs_phy[1]); + b43_phy_write(dev, B43_NPHY_AFECTL_OVER, save_regs_phy[2]); + b43_phy_write(dev, B43_NPHY_RFCTL_CMD, save_regs_phy[3]); + b43_phy_write(dev, B43_NPHY_RFCTL_OVER, save_regs_phy[4]); + b43_phy_write(dev, B43_NPHY_RFCTL_RSSIO1, save_regs_phy[5]); + b43_phy_write(dev, B43_NPHY_RFCTL_RSSIO2, save_regs_phy[6]); + } + + return out; +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/RSSICalRev3 */ +static void b43_nphy_rev3_rssi_cal(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + + u16 saved_regs_phy_rfctl[2]; + u16 saved_regs_phy[13]; + u16 regs_to_store[] = { + B43_NPHY_AFECTL_OVER1, B43_NPHY_AFECTL_OVER, + B43_NPHY_AFECTL_C1, B43_NPHY_AFECTL_C2, + B43_NPHY_TXF_40CO_B1S1, B43_NPHY_RFCTL_OVER, + B43_NPHY_TXF_40CO_B1S0, B43_NPHY_TXF_40CO_B32S1, + B43_NPHY_RFCTL_CMD, + B43_NPHY_RFCTL_LUT_TRSW_UP1, B43_NPHY_RFCTL_LUT_TRSW_UP2, + B43_NPHY_RFCTL_RSSIO1, B43_NPHY_RFCTL_RSSIO2 + }; + + u16 class; + + u16 clip_state[2]; + u16 clip_off[2] = { 0xFFFF, 0xFFFF }; + + u8 vcm_final = 0; + s32 offset[4]; + s32 results[8][4] = { }; + s32 results_min[4] = { }; + s32 poll_results[4] = { }; + + u16 *rssical_radio_regs = NULL; + u16 *rssical_phy_regs = NULL; + + u16 r; /* routing */ + u8 rx_core_state; + int core, i, j, vcm; + + class = b43_nphy_classifier(dev, 0, 0); + b43_nphy_classifier(dev, 7, 4); + b43_nphy_read_clip_detection(dev, clip_state); + b43_nphy_write_clip_detection(dev, clip_off); + + saved_regs_phy_rfctl[0] = b43_phy_read(dev, B43_NPHY_RFCTL_INTC1); + saved_regs_phy_rfctl[1] = b43_phy_read(dev, B43_NPHY_RFCTL_INTC2); + for (i = 0; i < ARRAY_SIZE(regs_to_store); i++) + saved_regs_phy[i] = b43_phy_read(dev, regs_to_store[i]); + + b43_nphy_rf_ctl_intc_override(dev, N_INTC_OVERRIDE_OFF, 0, 7); + b43_nphy_rf_ctl_intc_override(dev, N_INTC_OVERRIDE_TRSW, 1, 7); + b43_nphy_rf_ctl_override(dev, 0x1, 0, 0, false); + b43_nphy_rf_ctl_override(dev, 0x2, 1, 0, false); + b43_nphy_rf_ctl_override(dev, 0x80, 1, 0, false); + b43_nphy_rf_ctl_override(dev, 0x40, 1, 0, false); + + if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) { + b43_nphy_rf_ctl_override(dev, 0x20, 0, 0, false); + b43_nphy_rf_ctl_override(dev, 0x10, 1, 0, false); + } else { + b43_nphy_rf_ctl_override(dev, 0x10, 0, 0, false); + b43_nphy_rf_ctl_override(dev, 0x20, 1, 0, false); + } + + rx_core_state = b43_nphy_get_rx_core_state(dev); + for (core = 0; core < 2; core++) { + if (!(rx_core_state & (1 << core))) + continue; + r = core ? B2056_RX1 : B2056_RX0; + b43_nphy_scale_offset_rssi(dev, 0, 0, core + 1, N_RAIL_I, + N_RSSI_NB); + b43_nphy_scale_offset_rssi(dev, 0, 0, core + 1, N_RAIL_Q, + N_RSSI_NB); + + /* Grab RSSI results for every possible VCM */ + for (vcm = 0; vcm < 8; vcm++) { + b43_radio_maskset(dev, r | B2056_RX_RSSI_MISC, 0xE3, + vcm << 2); + b43_nphy_poll_rssi(dev, N_RSSI_NB, results[vcm], 8); + } + + /* Find out which VCM got the best results */ + for (i = 0; i < 4; i += 2) { + s32 currd; + s32 mind = 0x100000; + s32 minpoll = 249; + u8 minvcm = 0; + if (2 * core != i) + continue; + for (vcm = 0; vcm < 8; vcm++) { + currd = results[vcm][i] * results[vcm][i] + + results[vcm][i + 1] * results[vcm][i]; + if (currd < mind) { + mind = currd; + minvcm = vcm; + } + if (results[vcm][i] < minpoll) + minpoll = results[vcm][i]; + } + vcm_final = minvcm; + results_min[i] = minpoll; + } + + /* Select the best VCM */ + b43_radio_maskset(dev, r | B2056_RX_RSSI_MISC, 0xE3, + vcm_final << 2); + + for (i = 0; i < 4; i++) { + if (core != i / 2) + continue; + offset[i] = -results[vcm_final][i]; + if (offset[i] < 0) + offset[i] = -((abs(offset[i]) + 4) / 8); + else + offset[i] = (offset[i] + 4) / 8; + if (results_min[i] == 248) + offset[i] = -32; + b43_nphy_scale_offset_rssi(dev, 0, offset[i], + (i / 2 == 0) ? 1 : 2, + (i % 2 == 0) ? N_RAIL_I : N_RAIL_Q, + N_RSSI_NB); + } + } + + for (core = 0; core < 2; core++) { + if (!(rx_core_state & (1 << core))) + continue; + for (i = 0; i < 2; i++) { + b43_nphy_scale_offset_rssi(dev, 0, 0, core + 1, + N_RAIL_I, i); + b43_nphy_scale_offset_rssi(dev, 0, 0, core + 1, + N_RAIL_Q, i); + b43_nphy_poll_rssi(dev, i, poll_results, 8); + for (j = 0; j < 4; j++) { + if (j / 2 == core) { + offset[j] = 232 - poll_results[j]; + if (offset[j] < 0) + offset[j] = -(abs(offset[j] + 4) / 8); + else + offset[j] = (offset[j] + 4) / 8; + b43_nphy_scale_offset_rssi(dev, 0, + offset[2 * core], core + 1, j % 2, i); + } + } + } + } + + b43_phy_write(dev, B43_NPHY_RFCTL_INTC1, saved_regs_phy_rfctl[0]); + b43_phy_write(dev, B43_NPHY_RFCTL_INTC2, saved_regs_phy_rfctl[1]); + + b43_nphy_force_rf_sequence(dev, B43_RFSEQ_RESET2RX); + + b43_phy_set(dev, B43_NPHY_TXF_40CO_B1S1, 0x1); + b43_phy_set(dev, B43_NPHY_RFCTL_CMD, B43_NPHY_RFCTL_CMD_START); + b43_phy_mask(dev, B43_NPHY_TXF_40CO_B1S1, ~0x1); + + b43_phy_set(dev, B43_NPHY_RFCTL_OVER, 0x1); + b43_phy_set(dev, B43_NPHY_RFCTL_CMD, B43_NPHY_RFCTL_CMD_RXTX); + b43_phy_mask(dev, B43_NPHY_TXF_40CO_B1S1, ~0x1); + + for (i = 0; i < ARRAY_SIZE(regs_to_store); i++) + b43_phy_write(dev, regs_to_store[i], saved_regs_phy[i]); + + /* Store for future configuration */ + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + rssical_radio_regs = nphy->rssical_cache.rssical_radio_regs_2G; + rssical_phy_regs = nphy->rssical_cache.rssical_phy_regs_2G; + } else { + rssical_radio_regs = nphy->rssical_cache.rssical_radio_regs_5G; + rssical_phy_regs = nphy->rssical_cache.rssical_phy_regs_5G; + } + if (dev->phy.rev >= 7) { + } else { + rssical_radio_regs[0] = b43_radio_read(dev, B2056_RX0 | + B2056_RX_RSSI_MISC); + rssical_radio_regs[1] = b43_radio_read(dev, B2056_RX1 | + B2056_RX_RSSI_MISC); + } + rssical_phy_regs[0] = b43_phy_read(dev, B43_NPHY_RSSIMC_0I_RSSI_Z); + rssical_phy_regs[1] = b43_phy_read(dev, B43_NPHY_RSSIMC_0Q_RSSI_Z); + rssical_phy_regs[2] = b43_phy_read(dev, B43_NPHY_RSSIMC_1I_RSSI_Z); + rssical_phy_regs[3] = b43_phy_read(dev, B43_NPHY_RSSIMC_1Q_RSSI_Z); + rssical_phy_regs[4] = b43_phy_read(dev, B43_NPHY_RSSIMC_0I_RSSI_X); + rssical_phy_regs[5] = b43_phy_read(dev, B43_NPHY_RSSIMC_0Q_RSSI_X); + rssical_phy_regs[6] = b43_phy_read(dev, B43_NPHY_RSSIMC_1I_RSSI_X); + rssical_phy_regs[7] = b43_phy_read(dev, B43_NPHY_RSSIMC_1Q_RSSI_X); + rssical_phy_regs[8] = b43_phy_read(dev, B43_NPHY_RSSIMC_0I_RSSI_Y); + rssical_phy_regs[9] = b43_phy_read(dev, B43_NPHY_RSSIMC_0Q_RSSI_Y); + rssical_phy_regs[10] = b43_phy_read(dev, B43_NPHY_RSSIMC_1I_RSSI_Y); + rssical_phy_regs[11] = b43_phy_read(dev, B43_NPHY_RSSIMC_1Q_RSSI_Y); + + /* Remember for which channel we store configuration */ + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + nphy->rssical_chanspec_2G.center_freq = dev->phy.channel_freq; + else + nphy->rssical_chanspec_5G.center_freq = dev->phy.channel_freq; + + /* End of calibration, restore configuration */ + b43_nphy_classifier(dev, 7, class); + b43_nphy_write_clip_detection(dev, clip_state); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/RSSICal */ +static void b43_nphy_rev2_rssi_cal(struct b43_wldev *dev, enum n_rssi_type type) +{ + int i, j, vcm; + u8 state[4]; + u8 code, val; + u16 class, override; + u8 regs_save_radio[2]; + u16 regs_save_phy[2]; + + s32 offset[4]; + u8 core; + u8 rail; + + u16 clip_state[2]; + u16 clip_off[2] = { 0xFFFF, 0xFFFF }; + s32 results_min[4] = { }; + u8 vcm_final[4] = { }; + s32 results[4][4] = { }; + s32 miniq[4][2] = { }; + + if (type == N_RSSI_NB) { + code = 0; + val = 6; + } else if (type == N_RSSI_W1 || type == N_RSSI_W2) { + code = 25; + val = 4; + } else { + B43_WARN_ON(1); + return; + } + + class = b43_nphy_classifier(dev, 0, 0); + b43_nphy_classifier(dev, 7, 4); + b43_nphy_read_clip_detection(dev, clip_state); + b43_nphy_write_clip_detection(dev, clip_off); + + if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) + override = 0x140; + else + override = 0x110; + + regs_save_phy[0] = b43_phy_read(dev, B43_NPHY_RFCTL_INTC1); + regs_save_radio[0] = b43_radio_read(dev, B2055_C1_PD_RXTX); + b43_phy_write(dev, B43_NPHY_RFCTL_INTC1, override); + b43_radio_write(dev, B2055_C1_PD_RXTX, val); + + regs_save_phy[1] = b43_phy_read(dev, B43_NPHY_RFCTL_INTC2); + regs_save_radio[1] = b43_radio_read(dev, B2055_C2_PD_RXTX); + b43_phy_write(dev, B43_NPHY_RFCTL_INTC2, override); + b43_radio_write(dev, B2055_C2_PD_RXTX, val); + + state[0] = b43_radio_read(dev, B2055_C1_PD_RSSIMISC) & 0x07; + state[1] = b43_radio_read(dev, B2055_C2_PD_RSSIMISC) & 0x07; + b43_radio_mask(dev, B2055_C1_PD_RSSIMISC, 0xF8); + b43_radio_mask(dev, B2055_C2_PD_RSSIMISC, 0xF8); + state[2] = b43_radio_read(dev, B2055_C1_SP_RSSI) & 0x07; + state[3] = b43_radio_read(dev, B2055_C2_SP_RSSI) & 0x07; + + b43_nphy_rssi_select(dev, 5, type); + b43_nphy_scale_offset_rssi(dev, 0, 0, 5, N_RAIL_I, type); + b43_nphy_scale_offset_rssi(dev, 0, 0, 5, N_RAIL_Q, type); + + for (vcm = 0; vcm < 4; vcm++) { + u8 tmp[4]; + for (j = 0; j < 4; j++) + tmp[j] = vcm; + if (type != N_RSSI_W2) + b43_nphy_set_rssi_2055_vcm(dev, type, tmp); + b43_nphy_poll_rssi(dev, type, results[vcm], 8); + if (type == N_RSSI_W1 || type == N_RSSI_W2) + for (j = 0; j < 2; j++) + miniq[vcm][j] = min(results[vcm][2 * j], + results[vcm][2 * j + 1]); + } + + for (i = 0; i < 4; i++) { + s32 mind = 0x100000; + u8 minvcm = 0; + s32 minpoll = 249; + s32 currd; + for (vcm = 0; vcm < 4; vcm++) { + if (type == N_RSSI_NB) + currd = abs(results[vcm][i] - code * 8); + else + currd = abs(miniq[vcm][i / 2] - code * 8); + + if (currd < mind) { + mind = currd; + minvcm = vcm; + } + + if (results[vcm][i] < minpoll) + minpoll = results[vcm][i]; + } + results_min[i] = minpoll; + vcm_final[i] = minvcm; + } + + if (type != N_RSSI_W2) + b43_nphy_set_rssi_2055_vcm(dev, type, vcm_final); + + for (i = 0; i < 4; i++) { + offset[i] = (code * 8) - results[vcm_final[i]][i]; + + if (offset[i] < 0) + offset[i] = -((abs(offset[i]) + 4) / 8); + else + offset[i] = (offset[i] + 4) / 8; + + if (results_min[i] == 248) + offset[i] = code - 32; + + core = (i / 2) ? 2 : 1; + rail = (i % 2) ? N_RAIL_Q : N_RAIL_I; + + b43_nphy_scale_offset_rssi(dev, 0, offset[i], core, rail, + type); + } + + b43_radio_maskset(dev, B2055_C1_PD_RSSIMISC, 0xF8, state[0]); + b43_radio_maskset(dev, B2055_C2_PD_RSSIMISC, 0xF8, state[1]); + + switch (state[2]) { + case 1: + b43_nphy_rssi_select(dev, 1, N_RSSI_NB); + break; + case 4: + b43_nphy_rssi_select(dev, 1, N_RSSI_W1); + break; + case 2: + b43_nphy_rssi_select(dev, 1, N_RSSI_W2); + break; + default: + b43_nphy_rssi_select(dev, 1, N_RSSI_W2); + break; + } + + switch (state[3]) { + case 1: + b43_nphy_rssi_select(dev, 2, N_RSSI_NB); + break; + case 4: + b43_nphy_rssi_select(dev, 2, N_RSSI_W1); + break; + default: + b43_nphy_rssi_select(dev, 2, N_RSSI_W2); + break; + } + + b43_nphy_rssi_select(dev, 0, type); + + b43_phy_write(dev, B43_NPHY_RFCTL_INTC1, regs_save_phy[0]); + b43_radio_write(dev, B2055_C1_PD_RXTX, regs_save_radio[0]); + b43_phy_write(dev, B43_NPHY_RFCTL_INTC2, regs_save_phy[1]); + b43_radio_write(dev, B2055_C2_PD_RXTX, regs_save_radio[1]); + + b43_nphy_classifier(dev, 7, class); + b43_nphy_write_clip_detection(dev, clip_state); + /* Specs don't say about reset here, but it makes wl and b43 dumps + identical, it really seems wl performs this */ + b43_nphy_reset_cca(dev); +} + +/* + * RSSI Calibration + * http://bcm-v4.sipsolutions.net/802.11/PHY/N/RSSICal + */ +static void b43_nphy_rssi_cal(struct b43_wldev *dev) +{ + if (dev->phy.rev >= 3) { + b43_nphy_rev3_rssi_cal(dev); + } else { + b43_nphy_rev2_rssi_cal(dev, N_RSSI_NB); + b43_nphy_rev2_rssi_cal(dev, N_RSSI_W1); + b43_nphy_rev2_rssi_cal(dev, N_RSSI_W2); + } +} + +/************************************************** + * Workarounds + **************************************************/ + +static void b43_nphy_gain_ctl_workarounds_rev3plus(struct b43_wldev *dev) +{ + struct ssb_sprom *sprom = dev->dev->bus_sprom; + + bool ghz5; + bool ext_lna; + u16 rssi_gain; + struct nphy_gain_ctl_workaround_entry *e; + u8 lpf_gain[6] = { 0x00, 0x06, 0x0C, 0x12, 0x12, 0x12 }; + u8 lpf_bits[6] = { 0, 1, 2, 3, 3, 3 }; + + /* Prepare values */ + ghz5 = b43_phy_read(dev, B43_NPHY_BANDCTL) + & B43_NPHY_BANDCTL_5GHZ; + ext_lna = ghz5 ? sprom->boardflags_hi & B43_BFH_EXTLNA_5GHZ : + sprom->boardflags_lo & B43_BFL_EXTLNA; + e = b43_nphy_get_gain_ctl_workaround_ent(dev, ghz5, ext_lna); + if (ghz5 && dev->phy.rev >= 5) + rssi_gain = 0x90; + else + rssi_gain = 0x50; + + b43_phy_set(dev, B43_NPHY_RXCTL, 0x0040); + + /* Set Clip 2 detect */ + b43_phy_set(dev, B43_NPHY_C1_CGAINI, B43_NPHY_C1_CGAINI_CL2DETECT); + b43_phy_set(dev, B43_NPHY_C2_CGAINI, B43_NPHY_C2_CGAINI_CL2DETECT); + + b43_radio_write(dev, B2056_RX0 | B2056_RX_BIASPOLE_LNAG1_IDAC, + 0x17); + b43_radio_write(dev, B2056_RX1 | B2056_RX_BIASPOLE_LNAG1_IDAC, + 0x17); + b43_radio_write(dev, B2056_RX0 | B2056_RX_LNAG2_IDAC, 0xF0); + b43_radio_write(dev, B2056_RX1 | B2056_RX_LNAG2_IDAC, 0xF0); + b43_radio_write(dev, B2056_RX0 | B2056_RX_RSSI_POLE, 0x00); + b43_radio_write(dev, B2056_RX1 | B2056_RX_RSSI_POLE, 0x00); + b43_radio_write(dev, B2056_RX0 | B2056_RX_RSSI_GAIN, + rssi_gain); + b43_radio_write(dev, B2056_RX1 | B2056_RX_RSSI_GAIN, + rssi_gain); + b43_radio_write(dev, B2056_RX0 | B2056_RX_BIASPOLE_LNAA1_IDAC, + 0x17); + b43_radio_write(dev, B2056_RX1 | B2056_RX_BIASPOLE_LNAA1_IDAC, + 0x17); + b43_radio_write(dev, B2056_RX0 | B2056_RX_LNAA2_IDAC, 0xFF); + b43_radio_write(dev, B2056_RX1 | B2056_RX_LNAA2_IDAC, 0xFF); + + b43_ntab_write_bulk(dev, B43_NTAB8(0, 8), 4, e->lna1_gain); + b43_ntab_write_bulk(dev, B43_NTAB8(1, 8), 4, e->lna1_gain); + b43_ntab_write_bulk(dev, B43_NTAB8(0, 16), 4, e->lna2_gain); + b43_ntab_write_bulk(dev, B43_NTAB8(1, 16), 4, e->lna2_gain); + b43_ntab_write_bulk(dev, B43_NTAB8(0, 32), 10, e->gain_db); + b43_ntab_write_bulk(dev, B43_NTAB8(1, 32), 10, e->gain_db); + b43_ntab_write_bulk(dev, B43_NTAB8(2, 32), 10, e->gain_bits); + b43_ntab_write_bulk(dev, B43_NTAB8(3, 32), 10, e->gain_bits); + b43_ntab_write_bulk(dev, B43_NTAB8(0, 0x40), 6, lpf_gain); + b43_ntab_write_bulk(dev, B43_NTAB8(1, 0x40), 6, lpf_gain); + b43_ntab_write_bulk(dev, B43_NTAB8(2, 0x40), 6, lpf_bits); + b43_ntab_write_bulk(dev, B43_NTAB8(3, 0x40), 6, lpf_bits); + + b43_phy_write(dev, B43_NPHY_REV3_C1_INITGAIN_A, e->init_gain); + b43_phy_write(dev, B43_NPHY_REV3_C2_INITGAIN_A, e->init_gain); + + b43_ntab_write_bulk(dev, B43_NTAB16(7, 0x106), 2, + e->rfseq_init); + + b43_phy_write(dev, B43_NPHY_REV3_C1_CLIP_HIGAIN_A, e->cliphi_gain); + b43_phy_write(dev, B43_NPHY_REV3_C2_CLIP_HIGAIN_A, e->cliphi_gain); + b43_phy_write(dev, B43_NPHY_REV3_C1_CLIP_MEDGAIN_A, e->clipmd_gain); + b43_phy_write(dev, B43_NPHY_REV3_C2_CLIP_MEDGAIN_A, e->clipmd_gain); + b43_phy_write(dev, B43_NPHY_REV3_C1_CLIP_LOGAIN_A, e->cliplo_gain); + b43_phy_write(dev, B43_NPHY_REV3_C2_CLIP_LOGAIN_A, e->cliplo_gain); + + b43_phy_maskset(dev, B43_NPHY_CRSMINPOWER0, 0xFF00, e->crsmin); + b43_phy_maskset(dev, B43_NPHY_CRSMINPOWERL0, 0xFF00, e->crsminl); + b43_phy_maskset(dev, B43_NPHY_CRSMINPOWERU0, 0xFF00, e->crsminu); + b43_phy_write(dev, B43_NPHY_C1_NBCLIPTHRES, e->nbclip); + b43_phy_write(dev, B43_NPHY_C2_NBCLIPTHRES, e->nbclip); + b43_phy_maskset(dev, B43_NPHY_C1_CLIPWBTHRES, + ~B43_NPHY_C1_CLIPWBTHRES_CLIP2, e->wlclip); + b43_phy_maskset(dev, B43_NPHY_C2_CLIPWBTHRES, + ~B43_NPHY_C2_CLIPWBTHRES_CLIP2, e->wlclip); + b43_phy_write(dev, B43_NPHY_CCK_SHIFTB_REF, 0x809C); +} + +static void b43_nphy_gain_ctl_workarounds_rev1_2(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + + u8 i, j; + u8 code; + u16 tmp; + u8 rfseq_events[3] = { 6, 8, 7 }; + u8 rfseq_delays[3] = { 10, 30, 1 }; + + /* Set Clip 2 detect */ + b43_phy_set(dev, B43_NPHY_C1_CGAINI, B43_NPHY_C1_CGAINI_CL2DETECT); + b43_phy_set(dev, B43_NPHY_C2_CGAINI, B43_NPHY_C2_CGAINI_CL2DETECT); + + /* Set narrowband clip threshold */ + b43_phy_write(dev, B43_NPHY_C1_NBCLIPTHRES, 0x84); + b43_phy_write(dev, B43_NPHY_C2_NBCLIPTHRES, 0x84); + + if (!dev->phy.is_40mhz) { + /* Set dwell lengths */ + b43_phy_write(dev, B43_NPHY_CLIP1_NBDWELL_LEN, 0x002B); + b43_phy_write(dev, B43_NPHY_CLIP2_NBDWELL_LEN, 0x002B); + b43_phy_write(dev, B43_NPHY_W1CLIP1_DWELL_LEN, 0x0009); + b43_phy_write(dev, B43_NPHY_W1CLIP2_DWELL_LEN, 0x0009); + } + + /* Set wideband clip 2 threshold */ + b43_phy_maskset(dev, B43_NPHY_C1_CLIPWBTHRES, + ~B43_NPHY_C1_CLIPWBTHRES_CLIP2, 21); + b43_phy_maskset(dev, B43_NPHY_C2_CLIPWBTHRES, + ~B43_NPHY_C2_CLIPWBTHRES_CLIP2, 21); + + if (!dev->phy.is_40mhz) { + b43_phy_maskset(dev, B43_NPHY_C1_CGAINI, + ~B43_NPHY_C1_CGAINI_GAINBKOFF, 0x1); + b43_phy_maskset(dev, B43_NPHY_C2_CGAINI, + ~B43_NPHY_C2_CGAINI_GAINBKOFF, 0x1); + b43_phy_maskset(dev, B43_NPHY_C1_CCK_CGAINI, + ~B43_NPHY_C1_CCK_CGAINI_GAINBKOFF, 0x1); + b43_phy_maskset(dev, B43_NPHY_C2_CCK_CGAINI, + ~B43_NPHY_C2_CCK_CGAINI_GAINBKOFF, 0x1); + } + + b43_phy_write(dev, B43_NPHY_CCK_SHIFTB_REF, 0x809C); + + if (nphy->gain_boost) { + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ && + dev->phy.is_40mhz) + code = 4; + else + code = 5; + } else { + code = dev->phy.is_40mhz ? 6 : 7; + } + + /* Set HPVGA2 index */ + b43_phy_maskset(dev, B43_NPHY_C1_INITGAIN, ~B43_NPHY_C1_INITGAIN_HPVGA2, + code << B43_NPHY_C1_INITGAIN_HPVGA2_SHIFT); + b43_phy_maskset(dev, B43_NPHY_C2_INITGAIN, ~B43_NPHY_C2_INITGAIN_HPVGA2, + code << B43_NPHY_C2_INITGAIN_HPVGA2_SHIFT); + + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, 0x1D06); + /* specs say about 2 loops, but wl does 4 */ + for (i = 0; i < 4; i++) + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, (code << 8 | 0x7C)); + + b43_nphy_adjust_lna_gain_table(dev); + + if (nphy->elna_gain_config) { + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, 0x0808); + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, 0x0); + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, 0x1); + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, 0x1); + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, 0x1); + + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, 0x0C08); + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, 0x0); + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, 0x1); + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, 0x1); + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, 0x1); + + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, 0x1D06); + /* specs say about 2 loops, but wl does 4 */ + for (i = 0; i < 4; i++) + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, + (code << 8 | 0x74)); + } + + if (dev->phy.rev == 2) { + for (i = 0; i < 4; i++) { + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, + (0x0400 * i) + 0x0020); + for (j = 0; j < 21; j++) { + tmp = j * (i < 2 ? 3 : 1); + b43_phy_write(dev, + B43_NPHY_TABLE_DATALO, tmp); + } + } + } + + b43_nphy_set_rf_sequence(dev, 5, rfseq_events, rfseq_delays, 3); + b43_phy_maskset(dev, B43_NPHY_OVER_DGAIN1, + ~B43_NPHY_OVER_DGAIN_CCKDGECV & 0xFFFF, + 0x5A << B43_NPHY_OVER_DGAIN_CCKDGECV_SHIFT); + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + b43_phy_maskset(dev, B43_PHY_N(0xC5D), 0xFF80, 4); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/WorkaroundsGainCtrl */ +static void b43_nphy_gain_ctl_workarounds(struct b43_wldev *dev) +{ + if (dev->phy.rev >= 7) + ; /* TODO */ + else if (dev->phy.rev >= 3) + b43_nphy_gain_ctl_workarounds_rev3plus(dev); + else + b43_nphy_gain_ctl_workarounds_rev1_2(dev); +} + +/* http://bcm-v4.sipsolutions.net/PHY/N/Read_Lpf_Bw_Ctl */ +static u16 b43_nphy_read_lpf_ctl(struct b43_wldev *dev, u16 offset) +{ + if (!offset) + offset = (dev->phy.is_40mhz) ? 0x159 : 0x154; + return b43_ntab_read(dev, B43_NTAB16(7, offset)) & 0x7; +} + +static void b43_nphy_workarounds_rev7plus(struct b43_wldev *dev) +{ + struct ssb_sprom *sprom = dev->dev->bus_sprom; + struct b43_phy *phy = &dev->phy; + + u8 rx2tx_events_ipa[9] = { 0x0, 0x1, 0x2, 0x8, 0x5, 0x6, 0xF, 0x3, + 0x1F }; + u8 rx2tx_delays_ipa[9] = { 8, 6, 6, 4, 4, 16, 43, 1, 1 }; + + u16 ntab7_15e_16e[] = { 0x10f, 0x10f }; + u8 ntab7_138_146[] = { 0x11, 0x11 }; + u8 ntab7_133[] = { 0x77, 0x11, 0x11 }; + + u16 lpf_20, lpf_40, lpf_11b; + u16 bcap_val, bcap_val_11b, bcap_val_11n_20, bcap_val_11n_40; + u16 scap_val, scap_val_11b, scap_val_11n_20, scap_val_11n_40; + bool rccal_ovrd = false; + + u16 rx2tx_lut_20_11b, rx2tx_lut_20_11n, rx2tx_lut_40_11n; + u16 bias, conv, filt; + + u32 tmp32; + u8 core; + + if (phy->rev == 7) { + b43_phy_set(dev, B43_NPHY_FINERX2_CGC, 0x10); + b43_phy_maskset(dev, B43_NPHY_FREQGAIN0, 0xFF80, 0x0020); + b43_phy_maskset(dev, B43_NPHY_FREQGAIN0, 0x80FF, 0x2700); + b43_phy_maskset(dev, B43_NPHY_FREQGAIN1, 0xFF80, 0x002E); + b43_phy_maskset(dev, B43_NPHY_FREQGAIN1, 0x80FF, 0x3300); + b43_phy_maskset(dev, B43_NPHY_FREQGAIN2, 0xFF80, 0x0037); + b43_phy_maskset(dev, B43_NPHY_FREQGAIN2, 0x80FF, 0x3A00); + b43_phy_maskset(dev, B43_NPHY_FREQGAIN3, 0xFF80, 0x003C); + b43_phy_maskset(dev, B43_NPHY_FREQGAIN3, 0x80FF, 0x3E00); + b43_phy_maskset(dev, B43_NPHY_FREQGAIN4, 0xFF80, 0x003E); + b43_phy_maskset(dev, B43_NPHY_FREQGAIN4, 0x80FF, 0x3F00); + b43_phy_maskset(dev, B43_NPHY_FREQGAIN5, 0xFF80, 0x0040); + b43_phy_maskset(dev, B43_NPHY_FREQGAIN5, 0x80FF, 0x4000); + b43_phy_maskset(dev, B43_NPHY_FREQGAIN6, 0xFF80, 0x0040); + b43_phy_maskset(dev, B43_NPHY_FREQGAIN6, 0x80FF, 0x4000); + b43_phy_maskset(dev, B43_NPHY_FREQGAIN7, 0xFF80, 0x0040); + b43_phy_maskset(dev, B43_NPHY_FREQGAIN7, 0x80FF, 0x4000); + } + if (phy->rev <= 8) { + b43_phy_write(dev, B43_NPHY_FORCEFRONT0, 0x1B0); + b43_phy_write(dev, B43_NPHY_FORCEFRONT1, 0x1B0); + } + if (phy->rev >= 8) + b43_phy_maskset(dev, B43_NPHY_TXTAILCNT, ~0xFF, 0x72); + + b43_ntab_write(dev, B43_NTAB16(8, 0x00), 2); + b43_ntab_write(dev, B43_NTAB16(8, 0x10), 2); + tmp32 = b43_ntab_read(dev, B43_NTAB32(30, 0)); + tmp32 &= 0xffffff; + b43_ntab_write(dev, B43_NTAB32(30, 0), tmp32); + b43_ntab_write_bulk(dev, B43_NTAB16(7, 0x15e), 2, ntab7_15e_16e); + b43_ntab_write_bulk(dev, B43_NTAB16(7, 0x16e), 2, ntab7_15e_16e); + + if (b43_nphy_ipa(dev)) + b43_nphy_set_rf_sequence(dev, 0, rx2tx_events_ipa, + rx2tx_delays_ipa, ARRAY_SIZE(rx2tx_events_ipa)); + + b43_phy_maskset(dev, B43_NPHY_EPS_OVERRIDEI_0, 0x3FFF, 0x4000); + b43_phy_maskset(dev, B43_NPHY_EPS_OVERRIDEI_1, 0x3FFF, 0x4000); + + lpf_20 = b43_nphy_read_lpf_ctl(dev, 0x154); + lpf_40 = b43_nphy_read_lpf_ctl(dev, 0x159); + lpf_11b = b43_nphy_read_lpf_ctl(dev, 0x152); + if (b43_nphy_ipa(dev)) { + if ((phy->radio_rev == 5 && phy->is_40mhz) || + phy->radio_rev == 7 || phy->radio_rev == 8) { + bcap_val = b43_radio_read(dev, 0x16b); + scap_val = b43_radio_read(dev, 0x16a); + scap_val_11b = scap_val; + bcap_val_11b = bcap_val; + if (phy->radio_rev == 5 && phy->is_40mhz) { + scap_val_11n_20 = scap_val; + bcap_val_11n_20 = bcap_val; + scap_val_11n_40 = bcap_val_11n_40 = 0xc; + rccal_ovrd = true; + } else { /* Rev 7/8 */ + lpf_20 = 4; + lpf_11b = 1; + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + scap_val_11n_20 = 0xc; + bcap_val_11n_20 = 0xc; + scap_val_11n_40 = 0xa; + bcap_val_11n_40 = 0xa; + } else { + scap_val_11n_20 = 0x14; + bcap_val_11n_20 = 0x14; + scap_val_11n_40 = 0xf; + bcap_val_11n_40 = 0xf; + } + rccal_ovrd = true; + } + } + } else { + if (phy->radio_rev == 5) { + lpf_20 = 1; + lpf_40 = 3; + bcap_val = b43_radio_read(dev, 0x16b); + scap_val = b43_radio_read(dev, 0x16a); + scap_val_11b = scap_val; + bcap_val_11b = bcap_val; + scap_val_11n_20 = 0x11; + scap_val_11n_40 = 0x11; + bcap_val_11n_20 = 0x13; + bcap_val_11n_40 = 0x13; + rccal_ovrd = true; + } + } + if (rccal_ovrd) { + rx2tx_lut_20_11b = (bcap_val_11b << 8) | + (scap_val_11b << 3) | + lpf_11b; + rx2tx_lut_20_11n = (bcap_val_11n_20 << 8) | + (scap_val_11n_20 << 3) | + lpf_20; + rx2tx_lut_40_11n = (bcap_val_11n_40 << 8) | + (scap_val_11n_40 << 3) | + lpf_40; + for (core = 0; core < 2; core++) { + b43_ntab_write(dev, B43_NTAB16(7, 0x152 + core * 16), + rx2tx_lut_20_11b); + b43_ntab_write(dev, B43_NTAB16(7, 0x153 + core * 16), + rx2tx_lut_20_11n); + b43_ntab_write(dev, B43_NTAB16(7, 0x154 + core * 16), + rx2tx_lut_20_11n); + b43_ntab_write(dev, B43_NTAB16(7, 0x155 + core * 16), + rx2tx_lut_40_11n); + b43_ntab_write(dev, B43_NTAB16(7, 0x156 + core * 16), + rx2tx_lut_40_11n); + b43_ntab_write(dev, B43_NTAB16(7, 0x157 + core * 16), + rx2tx_lut_40_11n); + b43_ntab_write(dev, B43_NTAB16(7, 0x158 + core * 16), + rx2tx_lut_40_11n); + b43_ntab_write(dev, B43_NTAB16(7, 0x159 + core * 16), + rx2tx_lut_40_11n); + } + b43_nphy_rf_ctl_override_rev7(dev, 16, 1, 3, false, 2); + } + b43_phy_write(dev, 0x32F, 0x3); + if (phy->radio_rev == 4 || phy->radio_rev == 6) + b43_nphy_rf_ctl_override_rev7(dev, 4, 1, 3, false, 0); + + if (phy->radio_rev == 3 || phy->radio_rev == 4 || phy->radio_rev == 6) { + if (sprom->revision && + sprom->boardflags2_hi & B43_BFH2_IPALVLSHIFT_3P3) { + b43_radio_write(dev, 0x5, 0x05); + b43_radio_write(dev, 0x6, 0x30); + b43_radio_write(dev, 0x7, 0x00); + b43_radio_set(dev, 0x4f, 0x1); + b43_radio_set(dev, 0xd4, 0x1); + bias = 0x1f; + conv = 0x6f; + filt = 0xaa; + } else { + bias = 0x2b; + conv = 0x7f; + filt = 0xee; + } + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + for (core = 0; core < 2; core++) { + if (core == 0) { + b43_radio_write(dev, 0x5F, bias); + b43_radio_write(dev, 0x64, conv); + b43_radio_write(dev, 0x66, filt); + } else { + b43_radio_write(dev, 0xE8, bias); + b43_radio_write(dev, 0xE9, conv); + b43_radio_write(dev, 0xEB, filt); + } + } + } + } + + if (b43_nphy_ipa(dev)) { + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + if (phy->radio_rev == 3 || phy->radio_rev == 4 || + phy->radio_rev == 6) { + for (core = 0; core < 2; core++) { + if (core == 0) + b43_radio_write(dev, 0x51, + 0x7f); + else + b43_radio_write(dev, 0xd6, + 0x7f); + } + } + if (phy->radio_rev == 3) { + for (core = 0; core < 2; core++) { + if (core == 0) { + b43_radio_write(dev, 0x64, + 0x13); + b43_radio_write(dev, 0x5F, + 0x1F); + b43_radio_write(dev, 0x66, + 0xEE); + b43_radio_write(dev, 0x59, + 0x8A); + b43_radio_write(dev, 0x80, + 0x3E); + } else { + b43_radio_write(dev, 0x69, + 0x13); + b43_radio_write(dev, 0xE8, + 0x1F); + b43_radio_write(dev, 0xEB, + 0xEE); + b43_radio_write(dev, 0xDE, + 0x8A); + b43_radio_write(dev, 0x105, + 0x3E); + } + } + } else if (phy->radio_rev == 7 || phy->radio_rev == 8) { + if (!phy->is_40mhz) { + b43_radio_write(dev, 0x5F, 0x14); + b43_radio_write(dev, 0xE8, 0x12); + } else { + b43_radio_write(dev, 0x5F, 0x16); + b43_radio_write(dev, 0xE8, 0x16); + } + } + } else { + u16 freq = phy->channel_freq; + if ((freq >= 5180 && freq <= 5230) || + (freq >= 5745 && freq <= 5805)) { + b43_radio_write(dev, 0x7D, 0xFF); + b43_radio_write(dev, 0xFE, 0xFF); + } + } + } else { + if (phy->radio_rev != 5) { + for (core = 0; core < 2; core++) { + if (core == 0) { + b43_radio_write(dev, 0x5c, 0x61); + b43_radio_write(dev, 0x51, 0x70); + } else { + b43_radio_write(dev, 0xe1, 0x61); + b43_radio_write(dev, 0xd6, 0x70); + } + } + } + } + + if (phy->radio_rev == 4) { + b43_ntab_write(dev, B43_NTAB16(8, 0x05), 0x20); + b43_ntab_write(dev, B43_NTAB16(8, 0x15), 0x20); + for (core = 0; core < 2; core++) { + if (core == 0) { + b43_radio_write(dev, 0x1a1, 0x00); + b43_radio_write(dev, 0x1a2, 0x3f); + b43_radio_write(dev, 0x1a6, 0x3f); + } else { + b43_radio_write(dev, 0x1a7, 0x00); + b43_radio_write(dev, 0x1ab, 0x3f); + b43_radio_write(dev, 0x1ac, 0x3f); + } + } + } else { + b43_phy_set(dev, B43_NPHY_AFECTL_C1, 0x4); + b43_phy_set(dev, B43_NPHY_AFECTL_OVER1, 0x4); + b43_phy_set(dev, B43_NPHY_AFECTL_C2, 0x4); + b43_phy_set(dev, B43_NPHY_AFECTL_OVER, 0x4); + + b43_phy_mask(dev, B43_NPHY_AFECTL_C1, ~0x1); + b43_phy_set(dev, B43_NPHY_AFECTL_OVER1, 0x1); + b43_phy_mask(dev, B43_NPHY_AFECTL_C2, ~0x1); + b43_phy_set(dev, B43_NPHY_AFECTL_OVER, 0x1); + b43_ntab_write(dev, B43_NTAB16(8, 0x05), 0x20); + b43_ntab_write(dev, B43_NTAB16(8, 0x15), 0x20); + + b43_phy_mask(dev, B43_NPHY_AFECTL_C1, ~0x4); + b43_phy_mask(dev, B43_NPHY_AFECTL_OVER1, ~0x4); + b43_phy_mask(dev, B43_NPHY_AFECTL_C2, ~0x4); + b43_phy_mask(dev, B43_NPHY_AFECTL_OVER, ~0x4); + } + + b43_phy_write(dev, B43_NPHY_ENDROP_TLEN, 0x2); + + b43_ntab_write(dev, B43_NTAB32(16, 0x100), 20); + b43_ntab_write_bulk(dev, B43_NTAB16(7, 0x138), 2, ntab7_138_146); + b43_ntab_write(dev, B43_NTAB16(7, 0x141), 0x77); + b43_ntab_write_bulk(dev, B43_NTAB16(7, 0x133), 3, ntab7_133); + b43_ntab_write_bulk(dev, B43_NTAB16(7, 0x146), 2, ntab7_138_146); + b43_ntab_write(dev, B43_NTAB16(7, 0x123), 0x77); + b43_ntab_write(dev, B43_NTAB16(7, 0x12A), 0x77); + + if (!phy->is_40mhz) { + b43_ntab_write(dev, B43_NTAB32(16, 0x03), 0x18D); + b43_ntab_write(dev, B43_NTAB32(16, 0x7F), 0x18D); + } else { + b43_ntab_write(dev, B43_NTAB32(16, 0x03), 0x14D); + b43_ntab_write(dev, B43_NTAB32(16, 0x7F), 0x14D); + } + + b43_nphy_gain_ctl_workarounds(dev); + + /* TODO + b43_ntab_write_bulk(dev, B43_NTAB16(8, 0x08), 4, + aux_adc_vmid_rev7_core0); + b43_ntab_write_bulk(dev, B43_NTAB16(8, 0x18), 4, + aux_adc_vmid_rev7_core1); + b43_ntab_write_bulk(dev, B43_NTAB16(8, 0x0C), 4, + aux_adc_gain_rev7); + b43_ntab_write_bulk(dev, B43_NTAB16(8, 0x1C), 4, + aux_adc_gain_rev7); + */ +} + +static void b43_nphy_workarounds_rev3plus(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + struct ssb_sprom *sprom = dev->dev->bus_sprom; + + /* TX to RX */ + u8 tx2rx_events[8] = { 0x4, 0x3, 0x6, 0x5, 0x2, 0x1, 0x8, 0x1F }; + u8 tx2rx_delays[8] = { 8, 4, 2, 2, 4, 4, 6, 1 }; + /* RX to TX */ + u8 rx2tx_events_ipa[9] = { 0x0, 0x1, 0x2, 0x8, 0x5, 0x6, 0xF, 0x3, + 0x1F }; + u8 rx2tx_delays_ipa[9] = { 8, 6, 6, 4, 4, 16, 43, 1, 1 }; + u8 rx2tx_events[9] = { 0x0, 0x1, 0x2, 0x8, 0x5, 0x6, 0x3, 0x4, 0x1F }; + u8 rx2tx_delays[9] = { 8, 6, 6, 4, 4, 18, 42, 1, 1 }; + + u16 tmp16; + u32 tmp32; + + b43_phy_write(dev, B43_NPHY_FORCEFRONT0, 0x1f8); + b43_phy_write(dev, B43_NPHY_FORCEFRONT1, 0x1f8); + + tmp32 = b43_ntab_read(dev, B43_NTAB32(30, 0)); + tmp32 &= 0xffffff; + b43_ntab_write(dev, B43_NTAB32(30, 0), tmp32); + + b43_phy_write(dev, B43_NPHY_PHASETR_A0, 0x0125); + b43_phy_write(dev, B43_NPHY_PHASETR_A1, 0x01B3); + b43_phy_write(dev, B43_NPHY_PHASETR_A2, 0x0105); + b43_phy_write(dev, B43_NPHY_PHASETR_B0, 0x016E); + b43_phy_write(dev, B43_NPHY_PHASETR_B1, 0x00CD); + b43_phy_write(dev, B43_NPHY_PHASETR_B2, 0x0020); + + b43_phy_write(dev, B43_NPHY_REV3_C1_CLIP_LOGAIN_B, 0x000C); + b43_phy_write(dev, B43_NPHY_REV3_C2_CLIP_LOGAIN_B, 0x000C); + + /* TX to RX */ + b43_nphy_set_rf_sequence(dev, 1, tx2rx_events, tx2rx_delays, + ARRAY_SIZE(tx2rx_events)); + + /* RX to TX */ + if (b43_nphy_ipa(dev)) + b43_nphy_set_rf_sequence(dev, 0, rx2tx_events_ipa, + rx2tx_delays_ipa, ARRAY_SIZE(rx2tx_events_ipa)); + if (nphy->hw_phyrxchain != 3 && + nphy->hw_phyrxchain != nphy->hw_phytxchain) { + if (b43_nphy_ipa(dev)) { + rx2tx_delays[5] = 59; + rx2tx_delays[6] = 1; + rx2tx_events[7] = 0x1F; + } + b43_nphy_set_rf_sequence(dev, 0, rx2tx_events, rx2tx_delays, + ARRAY_SIZE(rx2tx_events)); + } + + tmp16 = (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) ? + 0x2 : 0x9C40; + b43_phy_write(dev, B43_NPHY_ENDROP_TLEN, tmp16); + + b43_phy_maskset(dev, B43_NPHY_SGILTRNOFFSET, 0xF0FF, 0x0700); + + if (!dev->phy.is_40mhz) { + b43_ntab_write(dev, B43_NTAB32(16, 3), 0x18D); + b43_ntab_write(dev, B43_NTAB32(16, 127), 0x18D); + } else { + b43_ntab_write(dev, B43_NTAB32(16, 3), 0x14D); + b43_ntab_write(dev, B43_NTAB32(16, 127), 0x14D); + } + + b43_nphy_gain_ctl_workarounds(dev); + + b43_ntab_write(dev, B43_NTAB16(8, 0), 2); + b43_ntab_write(dev, B43_NTAB16(8, 16), 2); + + /* TODO */ + + b43_radio_write(dev, B2056_RX0 | B2056_RX_MIXA_MAST_BIAS, 0x00); + b43_radio_write(dev, B2056_RX1 | B2056_RX_MIXA_MAST_BIAS, 0x00); + b43_radio_write(dev, B2056_RX0 | B2056_RX_MIXA_BIAS_MAIN, 0x06); + b43_radio_write(dev, B2056_RX1 | B2056_RX_MIXA_BIAS_MAIN, 0x06); + b43_radio_write(dev, B2056_RX0 | B2056_RX_MIXA_BIAS_AUX, 0x07); + b43_radio_write(dev, B2056_RX1 | B2056_RX_MIXA_BIAS_AUX, 0x07); + b43_radio_write(dev, B2056_RX0 | B2056_RX_MIXA_LOB_BIAS, 0x88); + b43_radio_write(dev, B2056_RX1 | B2056_RX_MIXA_LOB_BIAS, 0x88); + b43_radio_write(dev, B2056_RX0 | B2056_RX_MIXA_CMFB_IDAC, 0x00); + b43_radio_write(dev, B2056_RX1 | B2056_RX_MIXA_CMFB_IDAC, 0x00); + b43_radio_write(dev, B2056_RX0 | B2056_RX_MIXG_CMFB_IDAC, 0x00); + b43_radio_write(dev, B2056_RX1 | B2056_RX_MIXG_CMFB_IDAC, 0x00); + + /* N PHY WAR TX Chain Update with hw_phytxchain as argument */ + + if ((sprom->boardflags2_lo & B43_BFL2_APLL_WAR && + b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) || + (sprom->boardflags2_lo & B43_BFL2_GPLL_WAR && + b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ)) + tmp32 = 0x00088888; + else + tmp32 = 0x88888888; + b43_ntab_write(dev, B43_NTAB32(30, 1), tmp32); + b43_ntab_write(dev, B43_NTAB32(30, 2), tmp32); + b43_ntab_write(dev, B43_NTAB32(30, 3), tmp32); + + if (dev->phy.rev == 4 && + b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) { + b43_radio_write(dev, B2056_TX0 | B2056_TX_GMBB_IDAC, + 0x70); + b43_radio_write(dev, B2056_TX1 | B2056_TX_GMBB_IDAC, + 0x70); + } + + /* Dropped probably-always-true condition */ + b43_phy_write(dev, B43_NPHY_ED_CRS40ASSERTTHRESH0, 0x03eb); + b43_phy_write(dev, B43_NPHY_ED_CRS40ASSERTTHRESH1, 0x03eb); + b43_phy_write(dev, B43_NPHY_ED_CRS40DEASSERTTHRESH1, 0x0341); + b43_phy_write(dev, B43_NPHY_ED_CRS40DEASSERTTHRESH1, 0x0341); + b43_phy_write(dev, B43_NPHY_ED_CRS20LASSERTTHRESH0, 0x042b); + b43_phy_write(dev, B43_NPHY_ED_CRS20LASSERTTHRESH1, 0x042b); + b43_phy_write(dev, B43_NPHY_ED_CRS20LDEASSERTTHRESH0, 0x0381); + b43_phy_write(dev, B43_NPHY_ED_CRS20LDEASSERTTHRESH1, 0x0381); + b43_phy_write(dev, B43_NPHY_ED_CRS20UASSERTTHRESH0, 0x042b); + b43_phy_write(dev, B43_NPHY_ED_CRS20UASSERTTHRESH1, 0x042b); + b43_phy_write(dev, B43_NPHY_ED_CRS20UDEASSERTTHRESH0, 0x0381); + b43_phy_write(dev, B43_NPHY_ED_CRS20UDEASSERTTHRESH1, 0x0381); + + if (dev->phy.rev >= 6 && sprom->boardflags2_lo & B43_BFL2_SINGLEANT_CCK) + ; /* TODO: 0x0080000000000000 HF */ +} + +static void b43_nphy_workarounds_rev1_2(struct b43_wldev *dev) +{ + struct ssb_sprom *sprom = dev->dev->bus_sprom; + struct b43_phy *phy = &dev->phy; + struct b43_phy_n *nphy = phy->n; + + u8 events1[7] = { 0x0, 0x1, 0x2, 0x8, 0x4, 0x5, 0x3 }; + u8 delays1[7] = { 0x8, 0x6, 0x6, 0x2, 0x4, 0x3C, 0x1 }; + + u8 events2[7] = { 0x0, 0x3, 0x5, 0x4, 0x2, 0x1, 0x8 }; + u8 delays2[7] = { 0x8, 0x6, 0x2, 0x4, 0x4, 0x6, 0x1 }; + + if (sprom->boardflags2_lo & B43_BFL2_SKWRKFEM_BRD || + dev->dev->board_type == BCMA_BOARD_TYPE_BCM943224M93) { + delays1[0] = 0x1; + delays1[5] = 0x14; + } + + if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ && + nphy->band5g_pwrgain) { + b43_radio_mask(dev, B2055_C1_TX_RF_SPARE, ~0x8); + b43_radio_mask(dev, B2055_C2_TX_RF_SPARE, ~0x8); + } else { + b43_radio_set(dev, B2055_C1_TX_RF_SPARE, 0x8); + b43_radio_set(dev, B2055_C2_TX_RF_SPARE, 0x8); + } + + b43_ntab_write(dev, B43_NTAB16(8, 0x00), 0x000A); + b43_ntab_write(dev, B43_NTAB16(8, 0x10), 0x000A); + if (dev->phy.rev < 3) { + b43_ntab_write(dev, B43_NTAB16(8, 0x02), 0xCDAA); + b43_ntab_write(dev, B43_NTAB16(8, 0x12), 0xCDAA); + } + + if (dev->phy.rev < 2) { + b43_ntab_write(dev, B43_NTAB16(8, 0x08), 0x0000); + b43_ntab_write(dev, B43_NTAB16(8, 0x18), 0x0000); + b43_ntab_write(dev, B43_NTAB16(8, 0x07), 0x7AAB); + b43_ntab_write(dev, B43_NTAB16(8, 0x17), 0x7AAB); + b43_ntab_write(dev, B43_NTAB16(8, 0x06), 0x0800); + b43_ntab_write(dev, B43_NTAB16(8, 0x16), 0x0800); + } + + b43_phy_write(dev, B43_NPHY_RFCTL_LUT_TRSW_LO1, 0x2D8); + b43_phy_write(dev, B43_NPHY_RFCTL_LUT_TRSW_UP1, 0x301); + b43_phy_write(dev, B43_NPHY_RFCTL_LUT_TRSW_LO2, 0x2D8); + b43_phy_write(dev, B43_NPHY_RFCTL_LUT_TRSW_UP2, 0x301); + + b43_nphy_set_rf_sequence(dev, 0, events1, delays1, 7); + b43_nphy_set_rf_sequence(dev, 1, events2, delays2, 7); + + b43_nphy_gain_ctl_workarounds(dev); + + if (dev->phy.rev < 2) { + if (b43_phy_read(dev, B43_NPHY_RXCTL) & 0x2) + b43_hf_write(dev, b43_hf_read(dev) | + B43_HF_MLADVW); + } else if (dev->phy.rev == 2) { + b43_phy_write(dev, B43_NPHY_CRSCHECK2, 0); + b43_phy_write(dev, B43_NPHY_CRSCHECK3, 0); + } + + if (dev->phy.rev < 2) + b43_phy_mask(dev, B43_NPHY_SCRAM_SIGCTL, + ~B43_NPHY_SCRAM_SIGCTL_SCM); + + /* Set phase track alpha and beta */ + b43_phy_write(dev, B43_NPHY_PHASETR_A0, 0x125); + b43_phy_write(dev, B43_NPHY_PHASETR_A1, 0x1B3); + b43_phy_write(dev, B43_NPHY_PHASETR_A2, 0x105); + b43_phy_write(dev, B43_NPHY_PHASETR_B0, 0x16E); + b43_phy_write(dev, B43_NPHY_PHASETR_B1, 0xCD); + b43_phy_write(dev, B43_NPHY_PHASETR_B2, 0x20); + + if (dev->phy.rev < 3) { + b43_phy_mask(dev, B43_NPHY_PIL_DW1, + ~B43_NPHY_PIL_DW_64QAM & 0xFFFF); + b43_phy_write(dev, B43_NPHY_TXF_20CO_S2B1, 0xB5); + b43_phy_write(dev, B43_NPHY_TXF_20CO_S2B2, 0xA4); + b43_phy_write(dev, B43_NPHY_TXF_20CO_S2B3, 0x00); + } + + if (dev->phy.rev == 2) + b43_phy_set(dev, B43_NPHY_FINERX2_CGC, + B43_NPHY_FINERX2_CGC_DECGC); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/Workarounds */ +static void b43_nphy_workarounds(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_n *nphy = phy->n; + + if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) + b43_nphy_classifier(dev, 1, 0); + else + b43_nphy_classifier(dev, 1, 1); + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, 1); + + b43_phy_set(dev, B43_NPHY_IQFLIP, + B43_NPHY_IQFLIP_ADC1 | B43_NPHY_IQFLIP_ADC2); + + if (dev->phy.rev >= 7) + b43_nphy_workarounds_rev7plus(dev); + else if (dev->phy.rev >= 3) + b43_nphy_workarounds_rev3plus(dev); + else + b43_nphy_workarounds_rev1_2(dev); + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, 0); +} + +/************************************************** + * Tx/Rx common + **************************************************/ + +/* + * Transmits a known value for LO calibration + * http://bcm-v4.sipsolutions.net/802.11/PHY/N/TXTone + */ +static int b43_nphy_tx_tone(struct b43_wldev *dev, u32 freq, u16 max_val, + bool iqmode, bool dac_test) +{ + u16 samp = b43_nphy_gen_load_samples(dev, freq, max_val, dac_test); + if (samp == 0) + return -1; + b43_nphy_run_samples(dev, samp, 0xFFFF, 0, iqmode, dac_test); + return 0; +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/Chains */ +static void b43_nphy_update_txrx_chain(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + + bool override = false; + u16 chain = 0x33; + + if (nphy->txrx_chain == 0) { + chain = 0x11; + override = true; + } else if (nphy->txrx_chain == 1) { + chain = 0x22; + override = true; + } + + b43_phy_maskset(dev, B43_NPHY_RFSEQCA, + ~(B43_NPHY_RFSEQCA_TXEN | B43_NPHY_RFSEQCA_RXEN), + chain); + + if (override) + b43_phy_set(dev, B43_NPHY_RFSEQMODE, + B43_NPHY_RFSEQMODE_CAOVER); + else + b43_phy_mask(dev, B43_NPHY_RFSEQMODE, + ~B43_NPHY_RFSEQMODE_CAOVER); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/stop-playback */ +static void b43_nphy_stop_playback(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + u16 tmp; + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, 1); + + tmp = b43_phy_read(dev, B43_NPHY_SAMP_STAT); + if (tmp & 0x1) + b43_phy_set(dev, B43_NPHY_SAMP_CMD, B43_NPHY_SAMP_CMD_STOP); + else if (tmp & 0x2) + b43_phy_mask(dev, B43_NPHY_IQLOCAL_CMDGCTL, 0x7FFF); + + b43_phy_mask(dev, B43_NPHY_SAMP_CMD, ~0x0004); + + if (nphy->bb_mult_save & 0x80000000) { + tmp = nphy->bb_mult_save & 0xFFFF; + b43_ntab_write(dev, B43_NTAB16(15, 87), tmp); + nphy->bb_mult_save = 0; + } + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, 0); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/IqCalGainParams */ +static void b43_nphy_iq_cal_gain_params(struct b43_wldev *dev, u16 core, + struct nphy_txgains target, + struct nphy_iqcal_params *params) +{ + int i, j, indx; + u16 gain; + + if (dev->phy.rev >= 3) { + params->txgm = target.txgm[core]; + params->pga = target.pga[core]; + params->pad = target.pad[core]; + params->ipa = target.ipa[core]; + params->cal_gain = (params->txgm << 12) | (params->pga << 8) | + (params->pad << 4) | (params->ipa); + for (j = 0; j < 5; j++) + params->ncorr[j] = 0x79; + } else { + gain = (target.pad[core]) | (target.pga[core] << 4) | + (target.txgm[core] << 8); + + indx = (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) ? + 1 : 0; + for (i = 0; i < 9; i++) + if (tbl_iqcal_gainparams[indx][i][0] == gain) + break; + i = min(i, 8); + + params->txgm = tbl_iqcal_gainparams[indx][i][1]; + params->pga = tbl_iqcal_gainparams[indx][i][2]; + params->pad = tbl_iqcal_gainparams[indx][i][3]; + params->cal_gain = (params->txgm << 7) | (params->pga << 4) | + (params->pad << 2); + for (j = 0; j < 4; j++) + params->ncorr[j] = tbl_iqcal_gainparams[indx][i][4 + j]; + } +} + +/************************************************** + * Tx and Rx + **************************************************/ + +static void b43_nphy_op_adjust_txpower(struct b43_wldev *dev) +{//TODO +} + +static enum b43_txpwr_result b43_nphy_op_recalc_txpower(struct b43_wldev *dev, + bool ignore_tssi) +{//TODO + return B43_TXPWR_RES_DONE; +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/TxPwrCtrlEnable */ +static void b43_nphy_tx_power_ctrl(struct b43_wldev *dev, bool enable) +{ + struct b43_phy_n *nphy = dev->phy.n; + u8 i; + u16 bmask, val, tmp; + enum ieee80211_band band = b43_current_band(dev->wl); + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, 1); + + nphy->txpwrctrl = enable; + if (!enable) { + if (dev->phy.rev >= 3 && + (b43_phy_read(dev, B43_NPHY_TXPCTL_CMD) & + (B43_NPHY_TXPCTL_CMD_COEFF | + B43_NPHY_TXPCTL_CMD_HWPCTLEN | + B43_NPHY_TXPCTL_CMD_PCTLEN))) { + /* We disable enabled TX pwr ctl, save it's state */ + nphy->tx_pwr_idx[0] = b43_phy_read(dev, + B43_NPHY_C1_TXPCTL_STAT) & 0x7f; + nphy->tx_pwr_idx[1] = b43_phy_read(dev, + B43_NPHY_C2_TXPCTL_STAT) & 0x7f; + } + + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, 0x6840); + for (i = 0; i < 84; i++) + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, 0); + + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, 0x6C40); + for (i = 0; i < 84; i++) + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, 0); + + tmp = B43_NPHY_TXPCTL_CMD_COEFF | B43_NPHY_TXPCTL_CMD_HWPCTLEN; + if (dev->phy.rev >= 3) + tmp |= B43_NPHY_TXPCTL_CMD_PCTLEN; + b43_phy_mask(dev, B43_NPHY_TXPCTL_CMD, ~tmp); + + if (dev->phy.rev >= 3) { + b43_phy_set(dev, B43_NPHY_AFECTL_OVER1, 0x0100); + b43_phy_set(dev, B43_NPHY_AFECTL_OVER, 0x0100); + } else { + b43_phy_set(dev, B43_NPHY_AFECTL_OVER, 0x4000); + } + + if (dev->phy.rev == 2) + b43_phy_maskset(dev, B43_NPHY_BPHY_CTL3, + ~B43_NPHY_BPHY_CTL3_SCALE, 0x53); + else if (dev->phy.rev < 2) + b43_phy_maskset(dev, B43_NPHY_BPHY_CTL3, + ~B43_NPHY_BPHY_CTL3_SCALE, 0x5A); + + if (dev->phy.rev < 2 && dev->phy.is_40mhz) + b43_hf_write(dev, b43_hf_read(dev) | B43_HF_TSSIRPSMW); + } else { + b43_ntab_write_bulk(dev, B43_NTAB16(26, 64), 84, + nphy->adj_pwr_tbl); + b43_ntab_write_bulk(dev, B43_NTAB16(27, 64), 84, + nphy->adj_pwr_tbl); + + bmask = B43_NPHY_TXPCTL_CMD_COEFF | + B43_NPHY_TXPCTL_CMD_HWPCTLEN; + /* wl does useless check for "enable" param here */ + val = B43_NPHY_TXPCTL_CMD_COEFF | B43_NPHY_TXPCTL_CMD_HWPCTLEN; + if (dev->phy.rev >= 3) { + bmask |= B43_NPHY_TXPCTL_CMD_PCTLEN; + if (val) + val |= B43_NPHY_TXPCTL_CMD_PCTLEN; + } + b43_phy_maskset(dev, B43_NPHY_TXPCTL_CMD, ~(bmask), val); + + if (band == IEEE80211_BAND_5GHZ) { + b43_phy_maskset(dev, B43_NPHY_TXPCTL_CMD, + ~B43_NPHY_TXPCTL_CMD_INIT, 0x64); + if (dev->phy.rev > 1) + b43_phy_maskset(dev, B43_NPHY_TXPCTL_INIT, + ~B43_NPHY_TXPCTL_INIT_PIDXI1, + 0x64); + } + + if (dev->phy.rev >= 3) { + if (nphy->tx_pwr_idx[0] != 128 && + nphy->tx_pwr_idx[1] != 128) { + /* Recover TX pwr ctl state */ + b43_phy_maskset(dev, B43_NPHY_TXPCTL_CMD, + ~B43_NPHY_TXPCTL_CMD_INIT, + nphy->tx_pwr_idx[0]); + if (dev->phy.rev > 1) + b43_phy_maskset(dev, + B43_NPHY_TXPCTL_INIT, + ~0xff, nphy->tx_pwr_idx[1]); + } + } + + if (dev->phy.rev >= 3) { + b43_phy_mask(dev, B43_NPHY_AFECTL_OVER1, ~0x100); + b43_phy_mask(dev, B43_NPHY_AFECTL_OVER, ~0x100); + } else { + b43_phy_mask(dev, B43_NPHY_AFECTL_OVER, ~0x4000); + } + + if (dev->phy.rev == 2) + b43_phy_maskset(dev, B43_NPHY_BPHY_CTL3, ~0xFF, 0x3b); + else if (dev->phy.rev < 2) + b43_phy_maskset(dev, B43_NPHY_BPHY_CTL3, ~0xFF, 0x40); + + if (dev->phy.rev < 2 && dev->phy.is_40mhz) + b43_hf_write(dev, b43_hf_read(dev) & ~B43_HF_TSSIRPSMW); + + if (b43_nphy_ipa(dev)) { + b43_phy_mask(dev, B43_NPHY_PAPD_EN0, ~0x4); + b43_phy_mask(dev, B43_NPHY_PAPD_EN1, ~0x4); + } + } + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, 0); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/TxPwrFix */ +static void b43_nphy_tx_power_fix(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + struct ssb_sprom *sprom = dev->dev->bus_sprom; + + u8 txpi[2], bbmult, i; + u16 tmp, radio_gain, dac_gain; + u16 freq = dev->phy.channel_freq; + u32 txgain; + /* u32 gaintbl; rev3+ */ + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, 1); + + if (dev->phy.rev >= 7) { + txpi[0] = txpi[1] = 30; + } else if (dev->phy.rev >= 3) { + txpi[0] = 40; + txpi[1] = 40; + } else if (sprom->revision < 4) { + txpi[0] = 72; + txpi[1] = 72; + } else { + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + txpi[0] = sprom->txpid2g[0]; + txpi[1] = sprom->txpid2g[1]; + } else if (freq >= 4900 && freq < 5100) { + txpi[0] = sprom->txpid5gl[0]; + txpi[1] = sprom->txpid5gl[1]; + } else if (freq >= 5100 && freq < 5500) { + txpi[0] = sprom->txpid5g[0]; + txpi[1] = sprom->txpid5g[1]; + } else if (freq >= 5500) { + txpi[0] = sprom->txpid5gh[0]; + txpi[1] = sprom->txpid5gh[1]; + } else { + txpi[0] = 91; + txpi[1] = 91; + } + } + if (dev->phy.rev < 7 && + (txpi[0] < 40 || txpi[0] > 100 || txpi[1] < 40 || txpi[1] > 100)) + txpi[0] = txpi[1] = 91; + + /* + for (i = 0; i < 2; i++) { + nphy->txpwrindex[i].index_internal = txpi[i]; + nphy->txpwrindex[i].index_internal_save = txpi[i]; + } + */ + + for (i = 0; i < 2; i++) { + txgain = *(b43_nphy_get_tx_gain_table(dev) + txpi[i]); + + if (dev->phy.rev >= 3) + radio_gain = (txgain >> 16) & 0x1FFFF; + else + radio_gain = (txgain >> 16) & 0x1FFF; + + if (dev->phy.rev >= 7) + dac_gain = (txgain >> 8) & 0x7; + else + dac_gain = (txgain >> 8) & 0x3F; + bbmult = txgain & 0xFF; + + if (dev->phy.rev >= 3) { + if (i == 0) + b43_phy_set(dev, B43_NPHY_AFECTL_OVER1, 0x0100); + else + b43_phy_set(dev, B43_NPHY_AFECTL_OVER, 0x0100); + } else { + b43_phy_set(dev, B43_NPHY_AFECTL_OVER, 0x4000); + } + + if (i == 0) + b43_phy_write(dev, B43_NPHY_AFECTL_DACGAIN1, dac_gain); + else + b43_phy_write(dev, B43_NPHY_AFECTL_DACGAIN2, dac_gain); + + b43_ntab_write(dev, B43_NTAB16(0x7, 0x110 + i), radio_gain); + + tmp = b43_ntab_read(dev, B43_NTAB16(0xF, 0x57)); + if (i == 0) + tmp = (tmp & 0x00FF) | (bbmult << 8); + else + tmp = (tmp & 0xFF00) | bbmult; + b43_ntab_write(dev, B43_NTAB16(0xF, 0x57), tmp); + + if (b43_nphy_ipa(dev)) { + u32 tmp32; + u16 reg = (i == 0) ? + B43_NPHY_PAPD_EN0 : B43_NPHY_PAPD_EN1; + tmp32 = b43_ntab_read(dev, B43_NTAB32(26 + i, + 576 + txpi[i])); + b43_phy_maskset(dev, reg, 0xE00F, (u32) tmp32 << 4); + b43_phy_set(dev, reg, 0x4); + } + } + + b43_phy_mask(dev, B43_NPHY_BPHY_CTL2, ~B43_NPHY_BPHY_CTL2_LUT); + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, 0); +} + +static void b43_nphy_ipa_internal_tssi_setup(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + + u8 core; + u16 r; /* routing */ + + if (phy->rev >= 7) { + for (core = 0; core < 2; core++) { + r = core ? 0x190 : 0x170; + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + b43_radio_write(dev, r + 0x5, 0x5); + b43_radio_write(dev, r + 0x9, 0xE); + if (phy->rev != 5) + b43_radio_write(dev, r + 0xA, 0); + if (phy->rev != 7) + b43_radio_write(dev, r + 0xB, 1); + else + b43_radio_write(dev, r + 0xB, 0x31); + } else { + b43_radio_write(dev, r + 0x5, 0x9); + b43_radio_write(dev, r + 0x9, 0xC); + b43_radio_write(dev, r + 0xB, 0x0); + if (phy->rev != 5) + b43_radio_write(dev, r + 0xA, 1); + else + b43_radio_write(dev, r + 0xA, 0x31); + } + b43_radio_write(dev, r + 0x6, 0); + b43_radio_write(dev, r + 0x7, 0); + b43_radio_write(dev, r + 0x8, 3); + b43_radio_write(dev, r + 0xC, 0); + } + } else { + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + b43_radio_write(dev, B2056_SYN_RESERVED_ADDR31, 0x128); + else + b43_radio_write(dev, B2056_SYN_RESERVED_ADDR31, 0x80); + b43_radio_write(dev, B2056_SYN_RESERVED_ADDR30, 0); + b43_radio_write(dev, B2056_SYN_GPIO_MASTER1, 0x29); + + for (core = 0; core < 2; core++) { + r = core ? B2056_TX1 : B2056_TX0; + + b43_radio_write(dev, r | B2056_TX_IQCAL_VCM_HG, 0); + b43_radio_write(dev, r | B2056_TX_IQCAL_IDAC, 0); + b43_radio_write(dev, r | B2056_TX_TSSI_VCM, 3); + b43_radio_write(dev, r | B2056_TX_TX_AMP_DET, 0); + b43_radio_write(dev, r | B2056_TX_TSSI_MISC1, 8); + b43_radio_write(dev, r | B2056_TX_TSSI_MISC2, 0); + b43_radio_write(dev, r | B2056_TX_TSSI_MISC3, 0); + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + b43_radio_write(dev, r | B2056_TX_TX_SSI_MASTER, + 0x5); + if (phy->rev != 5) + b43_radio_write(dev, r | B2056_TX_TSSIA, + 0x00); + if (phy->rev >= 5) + b43_radio_write(dev, r | B2056_TX_TSSIG, + 0x31); + else + b43_radio_write(dev, r | B2056_TX_TSSIG, + 0x11); + b43_radio_write(dev, r | B2056_TX_TX_SSI_MUX, + 0xE); + } else { + b43_radio_write(dev, r | B2056_TX_TX_SSI_MASTER, + 0x9); + b43_radio_write(dev, r | B2056_TX_TSSIA, 0x31); + b43_radio_write(dev, r | B2056_TX_TSSIG, 0x0); + b43_radio_write(dev, r | B2056_TX_TX_SSI_MUX, + 0xC); + } + } + } +} + +/* + * Stop radio and transmit known signal. Then check received signal strength to + * get TSSI (Transmit Signal Strength Indicator). + * http://bcm-v4.sipsolutions.net/802.11/PHY/N/TxPwrCtrlIdleTssi + */ +static void b43_nphy_tx_power_ctl_idle_tssi(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_n *nphy = dev->phy.n; + + u32 tmp; + s32 rssi[4] = { }; + + /* TODO: check if we can transmit */ + + if (b43_nphy_ipa(dev)) + b43_nphy_ipa_internal_tssi_setup(dev); + + if (phy->rev >= 7) + b43_nphy_rf_ctl_override_rev7(dev, 0x2000, 0, 3, false, 0); + else if (phy->rev >= 3) + b43_nphy_rf_ctl_override(dev, 0x2000, 0, 3, false); + + b43_nphy_stop_playback(dev); + b43_nphy_tx_tone(dev, 0xFA0, 0, false, false); + udelay(20); + tmp = b43_nphy_poll_rssi(dev, N_RSSI_TSSI_2G, rssi, 1); + b43_nphy_stop_playback(dev); + b43_nphy_rssi_select(dev, 0, N_RSSI_W1); + + if (phy->rev >= 7) + b43_nphy_rf_ctl_override_rev7(dev, 0x2000, 0, 3, true, 0); + else if (phy->rev >= 3) + b43_nphy_rf_ctl_override(dev, 0x2000, 0, 3, true); + + if (phy->rev >= 3) { + nphy->pwr_ctl_info[0].idle_tssi_5g = (tmp >> 24) & 0xFF; + nphy->pwr_ctl_info[1].idle_tssi_5g = (tmp >> 8) & 0xFF; + } else { + nphy->pwr_ctl_info[0].idle_tssi_5g = (tmp >> 16) & 0xFF; + nphy->pwr_ctl_info[1].idle_tssi_5g = tmp & 0xFF; + } + nphy->pwr_ctl_info[0].idle_tssi_2g = (tmp >> 24) & 0xFF; + nphy->pwr_ctl_info[1].idle_tssi_2g = (tmp >> 8) & 0xFF; +} + +/* http://bcm-v4.sipsolutions.net/PHY/N/TxPwrLimitToTbl */ +static void b43_nphy_tx_prepare_adjusted_power_table(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + + u8 idx, delta; + u8 i, stf_mode; + + for (i = 0; i < 4; i++) + nphy->adj_pwr_tbl[i] = nphy->tx_power_offset[i]; + + for (stf_mode = 0; stf_mode < 4; stf_mode++) { + delta = 0; + switch (stf_mode) { + case 0: + if (dev->phy.is_40mhz && dev->phy.rev >= 5) { + idx = 68; + } else { + delta = 1; + idx = dev->phy.is_40mhz ? 52 : 4; + } + break; + case 1: + idx = dev->phy.is_40mhz ? 76 : 28; + break; + case 2: + idx = dev->phy.is_40mhz ? 84 : 36; + break; + case 3: + idx = dev->phy.is_40mhz ? 92 : 44; + break; + } + + for (i = 0; i < 20; i++) { + nphy->adj_pwr_tbl[4 + 4 * i + stf_mode] = + nphy->tx_power_offset[idx]; + if (i == 0) + idx += delta; + if (i == 14) + idx += 1 - delta; + if (i == 3 || i == 4 || i == 7 || i == 8 || i == 11 || + i == 13) + idx += 1; + } + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/TxPwrCtrlSetup */ +static void b43_nphy_tx_power_ctl_setup(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + struct ssb_sprom *sprom = dev->dev->bus_sprom; + + s16 a1[2], b0[2], b1[2]; + u8 idle[2]; + s8 target[2]; + s32 num, den, pwr; + u32 regval[64]; + + u16 freq = dev->phy.channel_freq; + u16 tmp; + u16 r; /* routing */ + u8 i, c; + + if (dev->dev->core_rev == 11 || dev->dev->core_rev == 12) { + b43_maskset32(dev, B43_MMIO_MACCTL, ~0, 0x200000); + b43_read32(dev, B43_MMIO_MACCTL); + udelay(1); + } + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, true); + + b43_phy_set(dev, B43_NPHY_TSSIMODE, B43_NPHY_TSSIMODE_EN); + if (dev->phy.rev >= 3) + b43_phy_mask(dev, B43_NPHY_TXPCTL_CMD, + ~B43_NPHY_TXPCTL_CMD_PCTLEN & 0xFFFF); + else + b43_phy_set(dev, B43_NPHY_TXPCTL_CMD, + B43_NPHY_TXPCTL_CMD_PCTLEN); + + if (dev->dev->core_rev == 11 || dev->dev->core_rev == 12) + b43_maskset32(dev, B43_MMIO_MACCTL, ~0x200000, 0); + + if (sprom->revision < 4) { + idle[0] = nphy->pwr_ctl_info[0].idle_tssi_2g; + idle[1] = nphy->pwr_ctl_info[1].idle_tssi_2g; + target[0] = target[1] = 52; + a1[0] = a1[1] = -424; + b0[0] = b0[1] = 5612; + b1[0] = b1[1] = -1393; + } else { + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + for (c = 0; c < 2; c++) { + idle[c] = nphy->pwr_ctl_info[c].idle_tssi_2g; + target[c] = sprom->core_pwr_info[c].maxpwr_2g; + a1[c] = sprom->core_pwr_info[c].pa_2g[0]; + b0[c] = sprom->core_pwr_info[c].pa_2g[1]; + b1[c] = sprom->core_pwr_info[c].pa_2g[2]; + } + } else if (freq >= 4900 && freq < 5100) { + for (c = 0; c < 2; c++) { + idle[c] = nphy->pwr_ctl_info[c].idle_tssi_5g; + target[c] = sprom->core_pwr_info[c].maxpwr_5gl; + a1[c] = sprom->core_pwr_info[c].pa_5gl[0]; + b0[c] = sprom->core_pwr_info[c].pa_5gl[1]; + b1[c] = sprom->core_pwr_info[c].pa_5gl[2]; + } + } else if (freq >= 5100 && freq < 5500) { + for (c = 0; c < 2; c++) { + idle[c] = nphy->pwr_ctl_info[c].idle_tssi_5g; + target[c] = sprom->core_pwr_info[c].maxpwr_5g; + a1[c] = sprom->core_pwr_info[c].pa_5g[0]; + b0[c] = sprom->core_pwr_info[c].pa_5g[1]; + b1[c] = sprom->core_pwr_info[c].pa_5g[2]; + } + } else if (freq >= 5500) { + for (c = 0; c < 2; c++) { + idle[c] = nphy->pwr_ctl_info[c].idle_tssi_5g; + target[c] = sprom->core_pwr_info[c].maxpwr_5gh; + a1[c] = sprom->core_pwr_info[c].pa_5gh[0]; + b0[c] = sprom->core_pwr_info[c].pa_5gh[1]; + b1[c] = sprom->core_pwr_info[c].pa_5gh[2]; + } + } else { + idle[0] = nphy->pwr_ctl_info[0].idle_tssi_5g; + idle[1] = nphy->pwr_ctl_info[1].idle_tssi_5g; + target[0] = target[1] = 52; + a1[0] = a1[1] = -424; + b0[0] = b0[1] = 5612; + b1[0] = b1[1] = -1393; + } + } + /* target[0] = target[1] = nphy->tx_power_max; */ + + if (dev->phy.rev >= 3) { + if (sprom->fem.ghz2.tssipos) + b43_phy_set(dev, B43_NPHY_TXPCTL_ITSSI, 0x4000); + if (dev->phy.rev >= 7) { + for (c = 0; c < 2; c++) { + r = c ? 0x190 : 0x170; + if (b43_nphy_ipa(dev)) + b43_radio_write(dev, r + 0x9, (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) ? 0xE : 0xC); + } + } else { + if (b43_nphy_ipa(dev)) { + tmp = (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) ? 0xC : 0xE; + b43_radio_write(dev, + B2056_TX0 | B2056_TX_TX_SSI_MUX, tmp); + b43_radio_write(dev, + B2056_TX1 | B2056_TX_TX_SSI_MUX, tmp); + } else { + b43_radio_write(dev, + B2056_TX0 | B2056_TX_TX_SSI_MUX, 0x11); + b43_radio_write(dev, + B2056_TX1 | B2056_TX_TX_SSI_MUX, 0x11); + } + } + } + + if (dev->dev->core_rev == 11 || dev->dev->core_rev == 12) { + b43_maskset32(dev, B43_MMIO_MACCTL, ~0, 0x200000); + b43_read32(dev, B43_MMIO_MACCTL); + udelay(1); + } + + if (dev->phy.rev >= 7) { + b43_phy_maskset(dev, B43_NPHY_TXPCTL_CMD, + ~B43_NPHY_TXPCTL_CMD_INIT, 0x19); + b43_phy_maskset(dev, B43_NPHY_TXPCTL_INIT, + ~B43_NPHY_TXPCTL_INIT_PIDXI1, 0x19); + } else { + b43_phy_maskset(dev, B43_NPHY_TXPCTL_CMD, + ~B43_NPHY_TXPCTL_CMD_INIT, 0x40); + if (dev->phy.rev > 1) + b43_phy_maskset(dev, B43_NPHY_TXPCTL_INIT, + ~B43_NPHY_TXPCTL_INIT_PIDXI1, 0x40); + } + + if (dev->dev->core_rev == 11 || dev->dev->core_rev == 12) + b43_maskset32(dev, B43_MMIO_MACCTL, ~0x200000, 0); + + b43_phy_write(dev, B43_NPHY_TXPCTL_N, + 0xF0 << B43_NPHY_TXPCTL_N_TSSID_SHIFT | + 3 << B43_NPHY_TXPCTL_N_NPTIL2_SHIFT); + b43_phy_write(dev, B43_NPHY_TXPCTL_ITSSI, + idle[0] << B43_NPHY_TXPCTL_ITSSI_0_SHIFT | + idle[1] << B43_NPHY_TXPCTL_ITSSI_1_SHIFT | + B43_NPHY_TXPCTL_ITSSI_BINF); + b43_phy_write(dev, B43_NPHY_TXPCTL_TPWR, + target[0] << B43_NPHY_TXPCTL_TPWR_0_SHIFT | + target[1] << B43_NPHY_TXPCTL_TPWR_1_SHIFT); + + for (c = 0; c < 2; c++) { + for (i = 0; i < 64; i++) { + num = 8 * (16 * b0[c] + b1[c] * i); + den = 32768 + a1[c] * i; + pwr = max((4 * num + den / 2) / den, -8); + if (dev->phy.rev < 3 && (i <= (31 - idle[c] + 1))) + pwr = max(pwr, target[c] + 1); + regval[i] = pwr; + } + b43_ntab_write_bulk(dev, B43_NTAB32(26 + c, 0), 64, regval); + } + + b43_nphy_tx_prepare_adjusted_power_table(dev); + /* + b43_ntab_write_bulk(dev, B43_NTAB16(26, 64), 84, nphy->adj_pwr_tbl); + b43_ntab_write_bulk(dev, B43_NTAB16(27, 64), 84, nphy->adj_pwr_tbl); + */ + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, false); +} + +static void b43_nphy_tx_gain_table_upload(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + + const u32 *table = NULL; + u32 rfpwr_offset; + u8 pga_gain; + int i; + + table = b43_nphy_get_tx_gain_table(dev); + b43_ntab_write_bulk(dev, B43_NTAB32(26, 192), 128, table); + b43_ntab_write_bulk(dev, B43_NTAB32(27, 192), 128, table); + + if (phy->rev >= 3) { +#if 0 + nphy->gmval = (table[0] >> 16) & 0x7000; +#endif + + for (i = 0; i < 128; i++) { + pga_gain = (table[i] >> 24) & 0xF; + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + rfpwr_offset = + b43_ntab_papd_pga_gain_delta_ipa_2g[pga_gain]; + else + rfpwr_offset = + 0; /* FIXME */ + b43_ntab_write(dev, B43_NTAB32(26, 576 + i), + rfpwr_offset); + b43_ntab_write(dev, B43_NTAB32(27, 576 + i), + rfpwr_offset); + } + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/PA%20override */ +static void b43_nphy_pa_override(struct b43_wldev *dev, bool enable) +{ + struct b43_phy_n *nphy = dev->phy.n; + enum ieee80211_band band; + u16 tmp; + + if (!enable) { + nphy->rfctrl_intc1_save = b43_phy_read(dev, + B43_NPHY_RFCTL_INTC1); + nphy->rfctrl_intc2_save = b43_phy_read(dev, + B43_NPHY_RFCTL_INTC2); + band = b43_current_band(dev->wl); + if (dev->phy.rev >= 3) { + if (band == IEEE80211_BAND_5GHZ) + tmp = 0x600; + else + tmp = 0x480; + } else { + if (band == IEEE80211_BAND_5GHZ) + tmp = 0x180; + else + tmp = 0x120; + } + b43_phy_write(dev, B43_NPHY_RFCTL_INTC1, tmp); + b43_phy_write(dev, B43_NPHY_RFCTL_INTC2, tmp); + } else { + b43_phy_write(dev, B43_NPHY_RFCTL_INTC1, + nphy->rfctrl_intc1_save); + b43_phy_write(dev, B43_NPHY_RFCTL_INTC2, + nphy->rfctrl_intc2_save); + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/TxLpFbw */ +static void b43_nphy_tx_lp_fbw(struct b43_wldev *dev) +{ + u16 tmp; + + if (dev->phy.rev >= 3) { + if (b43_nphy_ipa(dev)) { + tmp = 4; + b43_phy_write(dev, B43_NPHY_TXF_40CO_B32S2, + (((((tmp << 3) | tmp) << 3) | tmp) << 3) | tmp); + } + + tmp = 1; + b43_phy_write(dev, B43_NPHY_TXF_40CO_B1S2, + (((((tmp << 3) | tmp) << 3) | tmp) << 3) | tmp); + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/RxIqEst */ +static void b43_nphy_rx_iq_est(struct b43_wldev *dev, struct nphy_iq_est *est, + u16 samps, u8 time, bool wait) +{ + int i; + u16 tmp; + + b43_phy_write(dev, B43_NPHY_IQEST_SAMCNT, samps); + b43_phy_maskset(dev, B43_NPHY_IQEST_WT, ~B43_NPHY_IQEST_WT_VAL, time); + if (wait) + b43_phy_set(dev, B43_NPHY_IQEST_CMD, B43_NPHY_IQEST_CMD_MODE); + else + b43_phy_mask(dev, B43_NPHY_IQEST_CMD, ~B43_NPHY_IQEST_CMD_MODE); + + b43_phy_set(dev, B43_NPHY_IQEST_CMD, B43_NPHY_IQEST_CMD_START); + + for (i = 1000; i; i--) { + tmp = b43_phy_read(dev, B43_NPHY_IQEST_CMD); + if (!(tmp & B43_NPHY_IQEST_CMD_START)) { + est->i0_pwr = (b43_phy_read(dev, B43_NPHY_IQEST_IPACC_HI0) << 16) | + b43_phy_read(dev, B43_NPHY_IQEST_IPACC_LO0); + est->q0_pwr = (b43_phy_read(dev, B43_NPHY_IQEST_QPACC_HI0) << 16) | + b43_phy_read(dev, B43_NPHY_IQEST_QPACC_LO0); + est->iq0_prod = (b43_phy_read(dev, B43_NPHY_IQEST_IQACC_HI0) << 16) | + b43_phy_read(dev, B43_NPHY_IQEST_IQACC_LO0); + + est->i1_pwr = (b43_phy_read(dev, B43_NPHY_IQEST_IPACC_HI1) << 16) | + b43_phy_read(dev, B43_NPHY_IQEST_IPACC_LO1); + est->q1_pwr = (b43_phy_read(dev, B43_NPHY_IQEST_QPACC_HI1) << 16) | + b43_phy_read(dev, B43_NPHY_IQEST_QPACC_LO1); + est->iq1_prod = (b43_phy_read(dev, B43_NPHY_IQEST_IQACC_HI1) << 16) | + b43_phy_read(dev, B43_NPHY_IQEST_IQACC_LO1); + return; + } + udelay(10); + } + memset(est, 0, sizeof(*est)); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/RxIqCoeffs */ +static void b43_nphy_rx_iq_coeffs(struct b43_wldev *dev, bool write, + struct b43_phy_n_iq_comp *pcomp) +{ + if (write) { + b43_phy_write(dev, B43_NPHY_C1_RXIQ_COMPA0, pcomp->a0); + b43_phy_write(dev, B43_NPHY_C1_RXIQ_COMPB0, pcomp->b0); + b43_phy_write(dev, B43_NPHY_C2_RXIQ_COMPA1, pcomp->a1); + b43_phy_write(dev, B43_NPHY_C2_RXIQ_COMPB1, pcomp->b1); + } else { + pcomp->a0 = b43_phy_read(dev, B43_NPHY_C1_RXIQ_COMPA0); + pcomp->b0 = b43_phy_read(dev, B43_NPHY_C1_RXIQ_COMPB0); + pcomp->a1 = b43_phy_read(dev, B43_NPHY_C2_RXIQ_COMPA1); + pcomp->b1 = b43_phy_read(dev, B43_NPHY_C2_RXIQ_COMPB1); + } +} + +#if 0 +/* Ready but not used anywhere */ +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/RxCalPhyCleanup */ +static void b43_nphy_rx_cal_phy_cleanup(struct b43_wldev *dev, u8 core) +{ + u16 *regs = dev->phy.n->tx_rx_cal_phy_saveregs; + + b43_phy_write(dev, B43_NPHY_RFSEQCA, regs[0]); + if (core == 0) { + b43_phy_write(dev, B43_NPHY_AFECTL_C1, regs[1]); + b43_phy_write(dev, B43_NPHY_AFECTL_OVER1, regs[2]); + } else { + b43_phy_write(dev, B43_NPHY_AFECTL_C2, regs[1]); + b43_phy_write(dev, B43_NPHY_AFECTL_OVER, regs[2]); + } + b43_phy_write(dev, B43_NPHY_RFCTL_INTC1, regs[3]); + b43_phy_write(dev, B43_NPHY_RFCTL_INTC2, regs[4]); + b43_phy_write(dev, B43_NPHY_RFCTL_RSSIO1, regs[5]); + b43_phy_write(dev, B43_NPHY_RFCTL_RSSIO2, regs[6]); + b43_phy_write(dev, B43_NPHY_TXF_40CO_B1S1, regs[7]); + b43_phy_write(dev, B43_NPHY_RFCTL_OVER, regs[8]); + b43_phy_write(dev, B43_NPHY_PAPD_EN0, regs[9]); + b43_phy_write(dev, B43_NPHY_PAPD_EN1, regs[10]); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/RxCalPhySetup */ +static void b43_nphy_rx_cal_phy_setup(struct b43_wldev *dev, u8 core) +{ + u8 rxval, txval; + u16 *regs = dev->phy.n->tx_rx_cal_phy_saveregs; + + regs[0] = b43_phy_read(dev, B43_NPHY_RFSEQCA); + if (core == 0) { + regs[1] = b43_phy_read(dev, B43_NPHY_AFECTL_C1); + regs[2] = b43_phy_read(dev, B43_NPHY_AFECTL_OVER1); + } else { + regs[1] = b43_phy_read(dev, B43_NPHY_AFECTL_C2); + regs[2] = b43_phy_read(dev, B43_NPHY_AFECTL_OVER); + } + regs[3] = b43_phy_read(dev, B43_NPHY_RFCTL_INTC1); + regs[4] = b43_phy_read(dev, B43_NPHY_RFCTL_INTC2); + regs[5] = b43_phy_read(dev, B43_NPHY_RFCTL_RSSIO1); + regs[6] = b43_phy_read(dev, B43_NPHY_RFCTL_RSSIO2); + regs[7] = b43_phy_read(dev, B43_NPHY_TXF_40CO_B1S1); + regs[8] = b43_phy_read(dev, B43_NPHY_RFCTL_OVER); + regs[9] = b43_phy_read(dev, B43_NPHY_PAPD_EN0); + regs[10] = b43_phy_read(dev, B43_NPHY_PAPD_EN1); + + b43_phy_mask(dev, B43_NPHY_PAPD_EN0, ~0x0001); + b43_phy_mask(dev, B43_NPHY_PAPD_EN1, ~0x0001); + + b43_phy_maskset(dev, B43_NPHY_RFSEQCA, + ~B43_NPHY_RFSEQCA_RXDIS & 0xFFFF, + ((1 - core) << B43_NPHY_RFSEQCA_RXDIS_SHIFT)); + b43_phy_maskset(dev, B43_NPHY_RFSEQCA, ~B43_NPHY_RFSEQCA_TXEN, + ((1 - core) << B43_NPHY_RFSEQCA_TXEN_SHIFT)); + b43_phy_maskset(dev, B43_NPHY_RFSEQCA, ~B43_NPHY_RFSEQCA_RXEN, + (core << B43_NPHY_RFSEQCA_RXEN_SHIFT)); + b43_phy_maskset(dev, B43_NPHY_RFSEQCA, ~B43_NPHY_RFSEQCA_TXDIS, + (core << B43_NPHY_RFSEQCA_TXDIS_SHIFT)); + + if (core == 0) { + b43_phy_mask(dev, B43_NPHY_AFECTL_C1, ~0x0007); + b43_phy_set(dev, B43_NPHY_AFECTL_OVER1, 0x0007); + } else { + b43_phy_mask(dev, B43_NPHY_AFECTL_C2, ~0x0007); + b43_phy_set(dev, B43_NPHY_AFECTL_OVER, 0x0007); + } + + b43_nphy_rf_ctl_intc_override(dev, N_INTC_OVERRIDE_PA, 0, 3); + b43_nphy_rf_ctl_override(dev, 8, 0, 3, false); + b43_nphy_force_rf_sequence(dev, B43_RFSEQ_RX2TX); + + if (core == 0) { + rxval = 1; + txval = 8; + } else { + rxval = 4; + txval = 2; + } + b43_nphy_rf_ctl_intc_override(dev, N_INTC_OVERRIDE_TRSW, rxval, + core + 1); + b43_nphy_rf_ctl_intc_override(dev, N_INTC_OVERRIDE_TRSW, txval, + 2 - core); +} +#endif + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/CalcRxIqComp */ +static void b43_nphy_calc_rx_iq_comp(struct b43_wldev *dev, u8 mask) +{ + int i; + s32 iq; + u32 ii; + u32 qq; + int iq_nbits, qq_nbits; + int arsh, brsh; + u16 tmp, a, b; + + struct nphy_iq_est est; + struct b43_phy_n_iq_comp old; + struct b43_phy_n_iq_comp new = { }; + bool error = false; + + if (mask == 0) + return; + + b43_nphy_rx_iq_coeffs(dev, false, &old); + b43_nphy_rx_iq_coeffs(dev, true, &new); + b43_nphy_rx_iq_est(dev, &est, 0x4000, 32, false); + new = old; + + for (i = 0; i < 2; i++) { + if (i == 0 && (mask & 1)) { + iq = est.iq0_prod; + ii = est.i0_pwr; + qq = est.q0_pwr; + } else if (i == 1 && (mask & 2)) { + iq = est.iq1_prod; + ii = est.i1_pwr; + qq = est.q1_pwr; + } else { + continue; + } + + if (ii + qq < 2) { + error = true; + break; + } + + iq_nbits = fls(abs(iq)); + qq_nbits = fls(qq); + + arsh = iq_nbits - 20; + if (arsh >= 0) { + a = -((iq << (30 - iq_nbits)) + (ii >> (1 + arsh))); + tmp = ii >> arsh; + } else { + a = -((iq << (30 - iq_nbits)) + (ii << (-1 - arsh))); + tmp = ii << -arsh; + } + if (tmp == 0) { + error = true; + break; + } + a /= tmp; + + brsh = qq_nbits - 11; + if (brsh >= 0) { + b = (qq << (31 - qq_nbits)); + tmp = ii >> brsh; + } else { + b = (qq << (31 - qq_nbits)); + tmp = ii << -brsh; + } + if (tmp == 0) { + error = true; + break; + } + b = int_sqrt(b / tmp - a * a) - (1 << 10); + + if (i == 0 && (mask & 0x1)) { + if (dev->phy.rev >= 3) { + new.a0 = a & 0x3FF; + new.b0 = b & 0x3FF; + } else { + new.a0 = b & 0x3FF; + new.b0 = a & 0x3FF; + } + } else if (i == 1 && (mask & 0x2)) { + if (dev->phy.rev >= 3) { + new.a1 = a & 0x3FF; + new.b1 = b & 0x3FF; + } else { + new.a1 = b & 0x3FF; + new.b1 = a & 0x3FF; + } + } + } + + if (error) + new = old; + + b43_nphy_rx_iq_coeffs(dev, true, &new); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/TxIqWar */ +static void b43_nphy_tx_iq_workaround(struct b43_wldev *dev) +{ + u16 array[4]; + b43_ntab_read_bulk(dev, B43_NTAB16(0xF, 0x50), 4, array); + + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_NPHY_TXIQW0, array[0]); + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_NPHY_TXIQW1, array[1]); + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_NPHY_TXIQW2, array[2]); + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_NPHY_TXIQW3, array[3]); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/SpurWar */ +static void b43_nphy_spur_workaround(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + + u8 channel = dev->phy.channel; + int tone[2] = { 57, 58 }; + u32 noise[2] = { 0x3FF, 0x3FF }; + + B43_WARN_ON(dev->phy.rev < 3); + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, 1); + + if (nphy->gband_spurwar_en) { + /* TODO: N PHY Adjust Analog Pfbw (7) */ + if (channel == 11 && dev->phy.is_40mhz) + ; /* TODO: N PHY Adjust Min Noise Var(2, tone, noise)*/ + else + ; /* TODO: N PHY Adjust Min Noise Var(0, NULL, NULL)*/ + /* TODO: N PHY Adjust CRS Min Power (0x1E) */ + } + + if (nphy->aband_spurwar_en) { + if (channel == 54) { + tone[0] = 0x20; + noise[0] = 0x25F; + } else if (channel == 38 || channel == 102 || channel == 118) { + if (0 /* FIXME */) { + tone[0] = 0x20; + noise[0] = 0x21F; + } else { + tone[0] = 0; + noise[0] = 0; + } + } else if (channel == 134) { + tone[0] = 0x20; + noise[0] = 0x21F; + } else if (channel == 151) { + tone[0] = 0x10; + noise[0] = 0x23F; + } else if (channel == 153 || channel == 161) { + tone[0] = 0x30; + noise[0] = 0x23F; + } else { + tone[0] = 0; + noise[0] = 0; + } + + if (!tone[0] && !noise[0]) + ; /* TODO: N PHY Adjust Min Noise Var(1, tone, noise)*/ + else + ; /* TODO: N PHY Adjust Min Noise Var(0, NULL, NULL)*/ + } + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, 0); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/TxPwrCtrlCoefSetup */ +static void b43_nphy_tx_pwr_ctrl_coef_setup(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + int i, j; + u32 tmp; + u32 cur_real, cur_imag, real_part, imag_part; + + u16 buffer[7]; + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, true); + + b43_ntab_read_bulk(dev, B43_NTAB16(15, 80), 7, buffer); + + for (i = 0; i < 2; i++) { + tmp = ((buffer[i * 2] & 0x3FF) << 10) | + (buffer[i * 2 + 1] & 0x3FF); + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, + (((i + 26) << 10) | 320)); + for (j = 0; j < 128; j++) { + b43_phy_write(dev, B43_NPHY_TABLE_DATAHI, + ((tmp >> 16) & 0xFFFF)); + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, + (tmp & 0xFFFF)); + } + } + + for (i = 0; i < 2; i++) { + tmp = buffer[5 + i]; + real_part = (tmp >> 8) & 0xFF; + imag_part = (tmp & 0xFF); + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, + (((i + 26) << 10) | 448)); + + if (dev->phy.rev >= 3) { + cur_real = real_part; + cur_imag = imag_part; + tmp = ((cur_real & 0xFF) << 8) | (cur_imag & 0xFF); + } + + for (j = 0; j < 128; j++) { + if (dev->phy.rev < 3) { + cur_real = (real_part * loscale[j] + 128) >> 8; + cur_imag = (imag_part * loscale[j] + 128) >> 8; + tmp = ((cur_real & 0xFF) << 8) | + (cur_imag & 0xFF); + } + b43_phy_write(dev, B43_NPHY_TABLE_DATAHI, + ((tmp >> 16) & 0xFFFF)); + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, + (tmp & 0xFFFF)); + } + } + + if (dev->phy.rev >= 3) { + b43_shm_write16(dev, B43_SHM_SHARED, + B43_SHM_SH_NPHY_TXPWR_INDX0, 0xFFFF); + b43_shm_write16(dev, B43_SHM_SHARED, + B43_SHM_SH_NPHY_TXPWR_INDX1, 0xFFFF); + } + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, false); +} + +/* + * Restore RSSI Calibration + * http://bcm-v4.sipsolutions.net/802.11/PHY/N/RestoreRssiCal + */ +static void b43_nphy_restore_rssi_cal(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + + u16 *rssical_radio_regs = NULL; + u16 *rssical_phy_regs = NULL; + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + if (!nphy->rssical_chanspec_2G.center_freq) + return; + rssical_radio_regs = nphy->rssical_cache.rssical_radio_regs_2G; + rssical_phy_regs = nphy->rssical_cache.rssical_phy_regs_2G; + } else { + if (!nphy->rssical_chanspec_5G.center_freq) + return; + rssical_radio_regs = nphy->rssical_cache.rssical_radio_regs_5G; + rssical_phy_regs = nphy->rssical_cache.rssical_phy_regs_5G; + } + + if (dev->phy.rev >= 7) { + } else { + b43_radio_maskset(dev, B2056_RX0 | B2056_RX_RSSI_MISC, 0xE3, + rssical_radio_regs[0]); + b43_radio_maskset(dev, B2056_RX1 | B2056_RX_RSSI_MISC, 0xE3, + rssical_radio_regs[1]); + } + + b43_phy_write(dev, B43_NPHY_RSSIMC_0I_RSSI_Z, rssical_phy_regs[0]); + b43_phy_write(dev, B43_NPHY_RSSIMC_0Q_RSSI_Z, rssical_phy_regs[1]); + b43_phy_write(dev, B43_NPHY_RSSIMC_1I_RSSI_Z, rssical_phy_regs[2]); + b43_phy_write(dev, B43_NPHY_RSSIMC_1Q_RSSI_Z, rssical_phy_regs[3]); + + b43_phy_write(dev, B43_NPHY_RSSIMC_0I_RSSI_X, rssical_phy_regs[4]); + b43_phy_write(dev, B43_NPHY_RSSIMC_0Q_RSSI_X, rssical_phy_regs[5]); + b43_phy_write(dev, B43_NPHY_RSSIMC_1I_RSSI_X, rssical_phy_regs[6]); + b43_phy_write(dev, B43_NPHY_RSSIMC_1Q_RSSI_X, rssical_phy_regs[7]); + + b43_phy_write(dev, B43_NPHY_RSSIMC_0I_RSSI_Y, rssical_phy_regs[8]); + b43_phy_write(dev, B43_NPHY_RSSIMC_0Q_RSSI_Y, rssical_phy_regs[9]); + b43_phy_write(dev, B43_NPHY_RSSIMC_1I_RSSI_Y, rssical_phy_regs[10]); + b43_phy_write(dev, B43_NPHY_RSSIMC_1Q_RSSI_Y, rssical_phy_regs[11]); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/TxCalRadioSetup */ +static void b43_nphy_tx_cal_radio_setup(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + u16 *save = nphy->tx_rx_cal_radio_saveregs; + u16 tmp; + u8 offset, i; + + if (dev->phy.rev >= 3) { + for (i = 0; i < 2; i++) { + tmp = (i == 0) ? 0x2000 : 0x3000; + offset = i * 11; + + save[offset + 0] = b43_radio_read(dev, B2055_CAL_RVARCTL); + save[offset + 1] = b43_radio_read(dev, B2055_CAL_LPOCTL); + save[offset + 2] = b43_radio_read(dev, B2055_CAL_TS); + save[offset + 3] = b43_radio_read(dev, B2055_CAL_RCCALRTS); + save[offset + 4] = b43_radio_read(dev, B2055_CAL_RCALRTS); + save[offset + 5] = b43_radio_read(dev, B2055_PADDRV); + save[offset + 6] = b43_radio_read(dev, B2055_XOCTL1); + save[offset + 7] = b43_radio_read(dev, B2055_XOCTL2); + save[offset + 8] = b43_radio_read(dev, B2055_XOREGUL); + save[offset + 9] = b43_radio_read(dev, B2055_XOMISC); + save[offset + 10] = b43_radio_read(dev, B2055_PLL_LFC1); + + if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) { + b43_radio_write(dev, tmp | B2055_CAL_RVARCTL, 0x0A); + b43_radio_write(dev, tmp | B2055_CAL_LPOCTL, 0x40); + b43_radio_write(dev, tmp | B2055_CAL_TS, 0x55); + b43_radio_write(dev, tmp | B2055_CAL_RCCALRTS, 0); + b43_radio_write(dev, tmp | B2055_CAL_RCALRTS, 0); + if (nphy->ipa5g_on) { + b43_radio_write(dev, tmp | B2055_PADDRV, 4); + b43_radio_write(dev, tmp | B2055_XOCTL1, 1); + } else { + b43_radio_write(dev, tmp | B2055_PADDRV, 0); + b43_radio_write(dev, tmp | B2055_XOCTL1, 0x2F); + } + b43_radio_write(dev, tmp | B2055_XOCTL2, 0); + } else { + b43_radio_write(dev, tmp | B2055_CAL_RVARCTL, 0x06); + b43_radio_write(dev, tmp | B2055_CAL_LPOCTL, 0x40); + b43_radio_write(dev, tmp | B2055_CAL_TS, 0x55); + b43_radio_write(dev, tmp | B2055_CAL_RCCALRTS, 0); + b43_radio_write(dev, tmp | B2055_CAL_RCALRTS, 0); + b43_radio_write(dev, tmp | B2055_XOCTL1, 0); + if (nphy->ipa2g_on) { + b43_radio_write(dev, tmp | B2055_PADDRV, 6); + b43_radio_write(dev, tmp | B2055_XOCTL2, + (dev->phy.rev < 5) ? 0x11 : 0x01); + } else { + b43_radio_write(dev, tmp | B2055_PADDRV, 0); + b43_radio_write(dev, tmp | B2055_XOCTL2, 0); + } + } + b43_radio_write(dev, tmp | B2055_XOREGUL, 0); + b43_radio_write(dev, tmp | B2055_XOMISC, 0); + b43_radio_write(dev, tmp | B2055_PLL_LFC1, 0); + } + } else { + save[0] = b43_radio_read(dev, B2055_C1_TX_RF_IQCAL1); + b43_radio_write(dev, B2055_C1_TX_RF_IQCAL1, 0x29); + + save[1] = b43_radio_read(dev, B2055_C1_TX_RF_IQCAL2); + b43_radio_write(dev, B2055_C1_TX_RF_IQCAL2, 0x54); + + save[2] = b43_radio_read(dev, B2055_C2_TX_RF_IQCAL1); + b43_radio_write(dev, B2055_C2_TX_RF_IQCAL1, 0x29); + + save[3] = b43_radio_read(dev, B2055_C2_TX_RF_IQCAL2); + b43_radio_write(dev, B2055_C2_TX_RF_IQCAL2, 0x54); + + save[3] = b43_radio_read(dev, B2055_C1_PWRDET_RXTX); + save[4] = b43_radio_read(dev, B2055_C2_PWRDET_RXTX); + + if (!(b43_phy_read(dev, B43_NPHY_BANDCTL) & + B43_NPHY_BANDCTL_5GHZ)) { + b43_radio_write(dev, B2055_C1_PWRDET_RXTX, 0x04); + b43_radio_write(dev, B2055_C2_PWRDET_RXTX, 0x04); + } else { + b43_radio_write(dev, B2055_C1_PWRDET_RXTX, 0x20); + b43_radio_write(dev, B2055_C2_PWRDET_RXTX, 0x20); + } + + if (dev->phy.rev < 2) { + b43_radio_set(dev, B2055_C1_TX_BB_MXGM, 0x20); + b43_radio_set(dev, B2055_C2_TX_BB_MXGM, 0x20); + } else { + b43_radio_mask(dev, B2055_C1_TX_BB_MXGM, ~0x20); + b43_radio_mask(dev, B2055_C2_TX_BB_MXGM, ~0x20); + } + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/UpdateTxCalLadder */ +static void b43_nphy_update_tx_cal_ladder(struct b43_wldev *dev, u16 core) +{ + struct b43_phy_n *nphy = dev->phy.n; + int i; + u16 scale, entry; + + u16 tmp = nphy->txcal_bbmult; + if (core == 0) + tmp >>= 8; + tmp &= 0xff; + + for (i = 0; i < 18; i++) { + scale = (ladder_lo[i].percent * tmp) / 100; + entry = ((scale & 0xFF) << 8) | ladder_lo[i].g_env; + b43_ntab_write(dev, B43_NTAB16(15, i), entry); + + scale = (ladder_iq[i].percent * tmp) / 100; + entry = ((scale & 0xFF) << 8) | ladder_iq[i].g_env; + b43_ntab_write(dev, B43_NTAB16(15, i + 32), entry); + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/ExtPaSetTxDigiFilts */ +static void b43_nphy_ext_pa_set_tx_dig_filters(struct b43_wldev *dev) +{ + int i; + for (i = 0; i < 15; i++) + b43_phy_write(dev, B43_PHY_N(0x2C5 + i), + tbl_tx_filter_coef_rev4[2][i]); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/IpaSetTxDigiFilts */ +static void b43_nphy_int_pa_set_tx_dig_filters(struct b43_wldev *dev) +{ + int i, j; + /* B43_NPHY_TXF_20CO_S0A1, B43_NPHY_TXF_40CO_S0A1, unknown */ + static const u16 offset[] = { 0x186, 0x195, 0x2C5 }; + + for (i = 0; i < 3; i++) + for (j = 0; j < 15; j++) + b43_phy_write(dev, B43_PHY_N(offset[i] + j), + tbl_tx_filter_coef_rev4[i][j]); + + if (dev->phy.is_40mhz) { + for (j = 0; j < 15; j++) + b43_phy_write(dev, B43_PHY_N(offset[0] + j), + tbl_tx_filter_coef_rev4[3][j]); + } else if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) { + for (j = 0; j < 15; j++) + b43_phy_write(dev, B43_PHY_N(offset[0] + j), + tbl_tx_filter_coef_rev4[5][j]); + } + + if (dev->phy.channel == 14) + for (j = 0; j < 15; j++) + b43_phy_write(dev, B43_PHY_N(offset[0] + j), + tbl_tx_filter_coef_rev4[6][j]); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/GetTxGain */ +static struct nphy_txgains b43_nphy_get_tx_gains(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + + u16 curr_gain[2]; + struct nphy_txgains target; + const u32 *table = NULL; + + if (!nphy->txpwrctrl) { + int i; + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, true); + b43_ntab_read_bulk(dev, B43_NTAB16(7, 0x110), 2, curr_gain); + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, false); + + for (i = 0; i < 2; ++i) { + if (dev->phy.rev >= 3) { + target.ipa[i] = curr_gain[i] & 0x000F; + target.pad[i] = (curr_gain[i] & 0x00F0) >> 4; + target.pga[i] = (curr_gain[i] & 0x0F00) >> 8; + target.txgm[i] = (curr_gain[i] & 0x7000) >> 12; + } else { + target.ipa[i] = curr_gain[i] & 0x0003; + target.pad[i] = (curr_gain[i] & 0x000C) >> 2; + target.pga[i] = (curr_gain[i] & 0x0070) >> 4; + target.txgm[i] = (curr_gain[i] & 0x0380) >> 7; + } + } + } else { + int i; + u16 index[2]; + index[0] = (b43_phy_read(dev, B43_NPHY_C1_TXPCTL_STAT) & + B43_NPHY_TXPCTL_STAT_BIDX) >> + B43_NPHY_TXPCTL_STAT_BIDX_SHIFT; + index[1] = (b43_phy_read(dev, B43_NPHY_C2_TXPCTL_STAT) & + B43_NPHY_TXPCTL_STAT_BIDX) >> + B43_NPHY_TXPCTL_STAT_BIDX_SHIFT; + + for (i = 0; i < 2; ++i) { + table = b43_nphy_get_tx_gain_table(dev); + if (dev->phy.rev >= 3) { + target.ipa[i] = (table[index[i]] >> 16) & 0xF; + target.pad[i] = (table[index[i]] >> 20) & 0xF; + target.pga[i] = (table[index[i]] >> 24) & 0xF; + target.txgm[i] = (table[index[i]] >> 28) & 0xF; + } else { + target.ipa[i] = (table[index[i]] >> 16) & 0x3; + target.pad[i] = (table[index[i]] >> 18) & 0x3; + target.pga[i] = (table[index[i]] >> 20) & 0x7; + target.txgm[i] = (table[index[i]] >> 23) & 0x7; + } + } + } + + return target; +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/TxCalPhyCleanup */ +static void b43_nphy_tx_cal_phy_cleanup(struct b43_wldev *dev) +{ + u16 *regs = dev->phy.n->tx_rx_cal_phy_saveregs; + + if (dev->phy.rev >= 3) { + b43_phy_write(dev, B43_NPHY_AFECTL_C1, regs[0]); + b43_phy_write(dev, B43_NPHY_AFECTL_C2, regs[1]); + b43_phy_write(dev, B43_NPHY_AFECTL_OVER1, regs[2]); + b43_phy_write(dev, B43_NPHY_AFECTL_OVER, regs[3]); + b43_phy_write(dev, B43_NPHY_BBCFG, regs[4]); + b43_ntab_write(dev, B43_NTAB16(8, 3), regs[5]); + b43_ntab_write(dev, B43_NTAB16(8, 19), regs[6]); + b43_phy_write(dev, B43_NPHY_RFCTL_INTC1, regs[7]); + b43_phy_write(dev, B43_NPHY_RFCTL_INTC2, regs[8]); + b43_phy_write(dev, B43_NPHY_PAPD_EN0, regs[9]); + b43_phy_write(dev, B43_NPHY_PAPD_EN1, regs[10]); + b43_nphy_reset_cca(dev); + } else { + b43_phy_maskset(dev, B43_NPHY_AFECTL_C1, 0x0FFF, regs[0]); + b43_phy_maskset(dev, B43_NPHY_AFECTL_C2, 0x0FFF, regs[1]); + b43_phy_write(dev, B43_NPHY_AFECTL_OVER, regs[2]); + b43_ntab_write(dev, B43_NTAB16(8, 2), regs[3]); + b43_ntab_write(dev, B43_NTAB16(8, 18), regs[4]); + b43_phy_write(dev, B43_NPHY_RFCTL_INTC1, regs[5]); + b43_phy_write(dev, B43_NPHY_RFCTL_INTC2, regs[6]); + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/TxCalPhySetup */ +static void b43_nphy_tx_cal_phy_setup(struct b43_wldev *dev) +{ + u16 *regs = dev->phy.n->tx_rx_cal_phy_saveregs; + u16 tmp; + + regs[0] = b43_phy_read(dev, B43_NPHY_AFECTL_C1); + regs[1] = b43_phy_read(dev, B43_NPHY_AFECTL_C2); + if (dev->phy.rev >= 3) { + b43_phy_maskset(dev, B43_NPHY_AFECTL_C1, 0xF0FF, 0x0A00); + b43_phy_maskset(dev, B43_NPHY_AFECTL_C2, 0xF0FF, 0x0A00); + + tmp = b43_phy_read(dev, B43_NPHY_AFECTL_OVER1); + regs[2] = tmp; + b43_phy_write(dev, B43_NPHY_AFECTL_OVER1, tmp | 0x0600); + + tmp = b43_phy_read(dev, B43_NPHY_AFECTL_OVER); + regs[3] = tmp; + b43_phy_write(dev, B43_NPHY_AFECTL_OVER, tmp | 0x0600); + + regs[4] = b43_phy_read(dev, B43_NPHY_BBCFG); + b43_phy_mask(dev, B43_NPHY_BBCFG, + ~B43_NPHY_BBCFG_RSTRX & 0xFFFF); + + tmp = b43_ntab_read(dev, B43_NTAB16(8, 3)); + regs[5] = tmp; + b43_ntab_write(dev, B43_NTAB16(8, 3), 0); + + tmp = b43_ntab_read(dev, B43_NTAB16(8, 19)); + regs[6] = tmp; + b43_ntab_write(dev, B43_NTAB16(8, 19), 0); + regs[7] = b43_phy_read(dev, B43_NPHY_RFCTL_INTC1); + regs[8] = b43_phy_read(dev, B43_NPHY_RFCTL_INTC2); + + b43_nphy_rf_ctl_intc_override(dev, N_INTC_OVERRIDE_PA, 1, 3); + b43_nphy_rf_ctl_intc_override(dev, N_INTC_OVERRIDE_TRSW, 2, 1); + b43_nphy_rf_ctl_intc_override(dev, N_INTC_OVERRIDE_TRSW, 8, 2); + + regs[9] = b43_phy_read(dev, B43_NPHY_PAPD_EN0); + regs[10] = b43_phy_read(dev, B43_NPHY_PAPD_EN1); + b43_phy_mask(dev, B43_NPHY_PAPD_EN0, ~0x0001); + b43_phy_mask(dev, B43_NPHY_PAPD_EN1, ~0x0001); + } else { + b43_phy_maskset(dev, B43_NPHY_AFECTL_C1, 0x0FFF, 0xA000); + b43_phy_maskset(dev, B43_NPHY_AFECTL_C2, 0x0FFF, 0xA000); + tmp = b43_phy_read(dev, B43_NPHY_AFECTL_OVER); + regs[2] = tmp; + b43_phy_write(dev, B43_NPHY_AFECTL_OVER, tmp | 0x3000); + tmp = b43_ntab_read(dev, B43_NTAB16(8, 2)); + regs[3] = tmp; + tmp |= 0x2000; + b43_ntab_write(dev, B43_NTAB16(8, 2), tmp); + tmp = b43_ntab_read(dev, B43_NTAB16(8, 18)); + regs[4] = tmp; + tmp |= 0x2000; + b43_ntab_write(dev, B43_NTAB16(8, 18), tmp); + regs[5] = b43_phy_read(dev, B43_NPHY_RFCTL_INTC1); + regs[6] = b43_phy_read(dev, B43_NPHY_RFCTL_INTC2); + if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) + tmp = 0x0180; + else + tmp = 0x0120; + b43_phy_write(dev, B43_NPHY_RFCTL_INTC1, tmp); + b43_phy_write(dev, B43_NPHY_RFCTL_INTC2, tmp); + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/SaveCal */ +static void b43_nphy_save_cal(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + + struct b43_phy_n_iq_comp *rxcal_coeffs = NULL; + u16 *txcal_radio_regs = NULL; + struct b43_chanspec *iqcal_chanspec; + u16 *table = NULL; + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, 1); + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + rxcal_coeffs = &nphy->cal_cache.rxcal_coeffs_2G; + txcal_radio_regs = nphy->cal_cache.txcal_radio_regs_2G; + iqcal_chanspec = &nphy->iqcal_chanspec_2G; + table = nphy->cal_cache.txcal_coeffs_2G; + } else { + rxcal_coeffs = &nphy->cal_cache.rxcal_coeffs_5G; + txcal_radio_regs = nphy->cal_cache.txcal_radio_regs_5G; + iqcal_chanspec = &nphy->iqcal_chanspec_5G; + table = nphy->cal_cache.txcal_coeffs_5G; + } + + b43_nphy_rx_iq_coeffs(dev, false, rxcal_coeffs); + /* TODO use some definitions */ + if (dev->phy.rev >= 3) { + txcal_radio_regs[0] = b43_radio_read(dev, 0x2021); + txcal_radio_regs[1] = b43_radio_read(dev, 0x2022); + txcal_radio_regs[2] = b43_radio_read(dev, 0x3021); + txcal_radio_regs[3] = b43_radio_read(dev, 0x3022); + txcal_radio_regs[4] = b43_radio_read(dev, 0x2023); + txcal_radio_regs[5] = b43_radio_read(dev, 0x2024); + txcal_radio_regs[6] = b43_radio_read(dev, 0x3023); + txcal_radio_regs[7] = b43_radio_read(dev, 0x3024); + } else { + txcal_radio_regs[0] = b43_radio_read(dev, 0x8B); + txcal_radio_regs[1] = b43_radio_read(dev, 0xBA); + txcal_radio_regs[2] = b43_radio_read(dev, 0x8D); + txcal_radio_regs[3] = b43_radio_read(dev, 0xBC); + } + iqcal_chanspec->center_freq = dev->phy.channel_freq; + iqcal_chanspec->channel_type = dev->phy.channel_type; + b43_ntab_read_bulk(dev, B43_NTAB16(15, 80), 8, table); + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, 0); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/RestoreCal */ +static void b43_nphy_restore_cal(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + + u16 coef[4]; + u16 *loft = NULL; + u16 *table = NULL; + + int i; + u16 *txcal_radio_regs = NULL; + struct b43_phy_n_iq_comp *rxcal_coeffs = NULL; + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + if (!nphy->iqcal_chanspec_2G.center_freq) + return; + table = nphy->cal_cache.txcal_coeffs_2G; + loft = &nphy->cal_cache.txcal_coeffs_2G[5]; + } else { + if (!nphy->iqcal_chanspec_5G.center_freq) + return; + table = nphy->cal_cache.txcal_coeffs_5G; + loft = &nphy->cal_cache.txcal_coeffs_5G[5]; + } + + b43_ntab_write_bulk(dev, B43_NTAB16(15, 80), 4, table); + + for (i = 0; i < 4; i++) { + if (dev->phy.rev >= 3) + table[i] = coef[i]; + else + coef[i] = 0; + } + + b43_ntab_write_bulk(dev, B43_NTAB16(15, 88), 4, coef); + b43_ntab_write_bulk(dev, B43_NTAB16(15, 85), 2, loft); + b43_ntab_write_bulk(dev, B43_NTAB16(15, 93), 2, loft); + + if (dev->phy.rev < 2) + b43_nphy_tx_iq_workaround(dev); + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + txcal_radio_regs = nphy->cal_cache.txcal_radio_regs_2G; + rxcal_coeffs = &nphy->cal_cache.rxcal_coeffs_2G; + } else { + txcal_radio_regs = nphy->cal_cache.txcal_radio_regs_5G; + rxcal_coeffs = &nphy->cal_cache.rxcal_coeffs_5G; + } + + /* TODO use some definitions */ + if (dev->phy.rev >= 3) { + b43_radio_write(dev, 0x2021, txcal_radio_regs[0]); + b43_radio_write(dev, 0x2022, txcal_radio_regs[1]); + b43_radio_write(dev, 0x3021, txcal_radio_regs[2]); + b43_radio_write(dev, 0x3022, txcal_radio_regs[3]); + b43_radio_write(dev, 0x2023, txcal_radio_regs[4]); + b43_radio_write(dev, 0x2024, txcal_radio_regs[5]); + b43_radio_write(dev, 0x3023, txcal_radio_regs[6]); + b43_radio_write(dev, 0x3024, txcal_radio_regs[7]); + } else { + b43_radio_write(dev, 0x8B, txcal_radio_regs[0]); + b43_radio_write(dev, 0xBA, txcal_radio_regs[1]); + b43_radio_write(dev, 0x8D, txcal_radio_regs[2]); + b43_radio_write(dev, 0xBC, txcal_radio_regs[3]); + } + b43_nphy_rx_iq_coeffs(dev, true, rxcal_coeffs); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/CalTxIqlo */ +static int b43_nphy_cal_tx_iq_lo(struct b43_wldev *dev, + struct nphy_txgains target, + bool full, bool mphase) +{ + struct b43_phy_n *nphy = dev->phy.n; + int i; + int error = 0; + int freq; + bool avoid = false; + u8 length; + u16 tmp, core, type, count, max, numb, last = 0, cmd; + const u16 *table; + bool phy6or5x; + + u16 buffer[11]; + u16 diq_start = 0; + u16 save[2]; + u16 gain[2]; + struct nphy_iqcal_params params[2]; + bool updated[2] = { }; + + b43_nphy_stay_in_carrier_search(dev, true); + + if (dev->phy.rev >= 4) { + avoid = nphy->hang_avoid; + nphy->hang_avoid = false; + } + + b43_ntab_read_bulk(dev, B43_NTAB16(7, 0x110), 2, save); + + for (i = 0; i < 2; i++) { + b43_nphy_iq_cal_gain_params(dev, i, target, ¶ms[i]); + gain[i] = params[i].cal_gain; + } + + b43_ntab_write_bulk(dev, B43_NTAB16(7, 0x110), 2, gain); + + b43_nphy_tx_cal_radio_setup(dev); + b43_nphy_tx_cal_phy_setup(dev); + + phy6or5x = dev->phy.rev >= 6 || + (dev->phy.rev == 5 && nphy->ipa2g_on && + b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ); + if (phy6or5x) { + if (dev->phy.is_40mhz) { + b43_ntab_write_bulk(dev, B43_NTAB16(15, 0), 18, + tbl_tx_iqlo_cal_loft_ladder_40); + b43_ntab_write_bulk(dev, B43_NTAB16(15, 32), 18, + tbl_tx_iqlo_cal_iqimb_ladder_40); + } else { + b43_ntab_write_bulk(dev, B43_NTAB16(15, 0), 18, + tbl_tx_iqlo_cal_loft_ladder_20); + b43_ntab_write_bulk(dev, B43_NTAB16(15, 32), 18, + tbl_tx_iqlo_cal_iqimb_ladder_20); + } + } + + b43_phy_write(dev, B43_NPHY_IQLOCAL_CMDGCTL, 0x8AA9); + + if (!dev->phy.is_40mhz) + freq = 2500; + else + freq = 5000; + + if (nphy->mphase_cal_phase_id > 2) + b43_nphy_run_samples(dev, (dev->phy.is_40mhz ? 40 : 20) * 8, + 0xFFFF, 0, true, false); + else + error = b43_nphy_tx_tone(dev, freq, 250, true, false); + + if (error == 0) { + if (nphy->mphase_cal_phase_id > 2) { + table = nphy->mphase_txcal_bestcoeffs; + length = 11; + if (dev->phy.rev < 3) + length -= 2; + } else { + if (!full && nphy->txiqlocal_coeffsvalid) { + table = nphy->txiqlocal_bestc; + length = 11; + if (dev->phy.rev < 3) + length -= 2; + } else { + full = true; + if (dev->phy.rev >= 3) { + table = tbl_tx_iqlo_cal_startcoefs_nphyrev3; + length = B43_NTAB_TX_IQLO_CAL_STARTCOEFS_REV3; + } else { + table = tbl_tx_iqlo_cal_startcoefs; + length = B43_NTAB_TX_IQLO_CAL_STARTCOEFS; + } + } + } + + b43_ntab_write_bulk(dev, B43_NTAB16(15, 64), length, table); + + if (full) { + if (dev->phy.rev >= 3) + max = B43_NTAB_TX_IQLO_CAL_CMDS_FULLCAL_REV3; + else + max = B43_NTAB_TX_IQLO_CAL_CMDS_FULLCAL; + } else { + if (dev->phy.rev >= 3) + max = B43_NTAB_TX_IQLO_CAL_CMDS_RECAL_REV3; + else + max = B43_NTAB_TX_IQLO_CAL_CMDS_RECAL; + } + + if (mphase) { + count = nphy->mphase_txcal_cmdidx; + numb = min(max, + (u16)(count + nphy->mphase_txcal_numcmds)); + } else { + count = 0; + numb = max; + } + + for (; count < numb; count++) { + if (full) { + if (dev->phy.rev >= 3) + cmd = tbl_tx_iqlo_cal_cmds_fullcal_nphyrev3[count]; + else + cmd = tbl_tx_iqlo_cal_cmds_fullcal[count]; + } else { + if (dev->phy.rev >= 3) + cmd = tbl_tx_iqlo_cal_cmds_recal_nphyrev3[count]; + else + cmd = tbl_tx_iqlo_cal_cmds_recal[count]; + } + + core = (cmd & 0x3000) >> 12; + type = (cmd & 0x0F00) >> 8; + + if (phy6or5x && updated[core] == 0) { + b43_nphy_update_tx_cal_ladder(dev, core); + updated[core] = true; + } + + tmp = (params[core].ncorr[type] << 8) | 0x66; + b43_phy_write(dev, B43_NPHY_IQLOCAL_CMDNNUM, tmp); + + if (type == 1 || type == 3 || type == 4) { + buffer[0] = b43_ntab_read(dev, + B43_NTAB16(15, 69 + core)); + diq_start = buffer[0]; + buffer[0] = 0; + b43_ntab_write(dev, B43_NTAB16(15, 69 + core), + 0); + } + + b43_phy_write(dev, B43_NPHY_IQLOCAL_CMD, cmd); + for (i = 0; i < 2000; i++) { + tmp = b43_phy_read(dev, B43_NPHY_IQLOCAL_CMD); + if (tmp & 0xC000) + break; + udelay(10); + } + + b43_ntab_read_bulk(dev, B43_NTAB16(15, 96), length, + buffer); + b43_ntab_write_bulk(dev, B43_NTAB16(15, 64), length, + buffer); + + if (type == 1 || type == 3 || type == 4) + buffer[0] = diq_start; + } + + if (mphase) + nphy->mphase_txcal_cmdidx = (numb >= max) ? 0 : numb; + + last = (dev->phy.rev < 3) ? 6 : 7; + + if (!mphase || nphy->mphase_cal_phase_id == last) { + b43_ntab_write_bulk(dev, B43_NTAB16(15, 96), 4, buffer); + b43_ntab_read_bulk(dev, B43_NTAB16(15, 80), 4, buffer); + if (dev->phy.rev < 3) { + buffer[0] = 0; + buffer[1] = 0; + buffer[2] = 0; + buffer[3] = 0; + } + b43_ntab_write_bulk(dev, B43_NTAB16(15, 88), 4, + buffer); + b43_ntab_read_bulk(dev, B43_NTAB16(15, 101), 2, + buffer); + b43_ntab_write_bulk(dev, B43_NTAB16(15, 85), 2, + buffer); + b43_ntab_write_bulk(dev, B43_NTAB16(15, 93), 2, + buffer); + length = 11; + if (dev->phy.rev < 3) + length -= 2; + b43_ntab_read_bulk(dev, B43_NTAB16(15, 96), length, + nphy->txiqlocal_bestc); + nphy->txiqlocal_coeffsvalid = true; + nphy->txiqlocal_chanspec.center_freq = + dev->phy.channel_freq; + nphy->txiqlocal_chanspec.channel_type = + dev->phy.channel_type; + } else { + length = 11; + if (dev->phy.rev < 3) + length -= 2; + b43_ntab_read_bulk(dev, B43_NTAB16(15, 96), length, + nphy->mphase_txcal_bestcoeffs); + } + + b43_nphy_stop_playback(dev); + b43_phy_write(dev, B43_NPHY_IQLOCAL_CMDGCTL, 0); + } + + b43_nphy_tx_cal_phy_cleanup(dev); + b43_ntab_write_bulk(dev, B43_NTAB16(7, 0x110), 2, save); + + if (dev->phy.rev < 2 && (!mphase || nphy->mphase_cal_phase_id == last)) + b43_nphy_tx_iq_workaround(dev); + + if (dev->phy.rev >= 4) + nphy->hang_avoid = avoid; + + b43_nphy_stay_in_carrier_search(dev, false); + + return error; +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/ReapplyTxCalCoeffs */ +static void b43_nphy_reapply_tx_cal_coeffs(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy = dev->phy.n; + u8 i; + u16 buffer[7]; + bool equal = true; + + if (!nphy->txiqlocal_coeffsvalid || + nphy->txiqlocal_chanspec.center_freq != dev->phy.channel_freq || + nphy->txiqlocal_chanspec.channel_type != dev->phy.channel_type) + return; + + b43_ntab_read_bulk(dev, B43_NTAB16(15, 80), 7, buffer); + for (i = 0; i < 4; i++) { + if (buffer[i] != nphy->txiqlocal_bestc[i]) { + equal = false; + break; + } + } + + if (!equal) { + b43_ntab_write_bulk(dev, B43_NTAB16(15, 80), 4, + nphy->txiqlocal_bestc); + for (i = 0; i < 4; i++) + buffer[i] = 0; + b43_ntab_write_bulk(dev, B43_NTAB16(15, 88), 4, + buffer); + b43_ntab_write_bulk(dev, B43_NTAB16(15, 85), 2, + &nphy->txiqlocal_bestc[5]); + b43_ntab_write_bulk(dev, B43_NTAB16(15, 93), 2, + &nphy->txiqlocal_bestc[5]); + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/CalRxIqRev2 */ +static int b43_nphy_rev2_cal_rx_iq(struct b43_wldev *dev, + struct nphy_txgains target, u8 type, bool debug) +{ + struct b43_phy_n *nphy = dev->phy.n; + int i, j, index; + u8 rfctl[2]; + u8 afectl_core; + u16 tmp[6]; + u16 uninitialized_var(cur_hpf1), uninitialized_var(cur_hpf2), cur_lna; + u32 real, imag; + enum ieee80211_band band; + + u8 use; + u16 cur_hpf; + u16 lna[3] = { 3, 3, 1 }; + u16 hpf1[3] = { 7, 2, 0 }; + u16 hpf2[3] = { 2, 0, 0 }; + u32 power[3] = { }; + u16 gain_save[2]; + u16 cal_gain[2]; + struct nphy_iqcal_params cal_params[2]; + struct nphy_iq_est est; + int ret = 0; + bool playtone = true; + int desired = 13; + + b43_nphy_stay_in_carrier_search(dev, 1); + + if (dev->phy.rev < 2) + b43_nphy_reapply_tx_cal_coeffs(dev); + b43_ntab_read_bulk(dev, B43_NTAB16(7, 0x110), 2, gain_save); + for (i = 0; i < 2; i++) { + b43_nphy_iq_cal_gain_params(dev, i, target, &cal_params[i]); + cal_gain[i] = cal_params[i].cal_gain; + } + b43_ntab_write_bulk(dev, B43_NTAB16(7, 0x110), 2, cal_gain); + + for (i = 0; i < 2; i++) { + if (i == 0) { + rfctl[0] = B43_NPHY_RFCTL_INTC1; + rfctl[1] = B43_NPHY_RFCTL_INTC2; + afectl_core = B43_NPHY_AFECTL_C1; + } else { + rfctl[0] = B43_NPHY_RFCTL_INTC2; + rfctl[1] = B43_NPHY_RFCTL_INTC1; + afectl_core = B43_NPHY_AFECTL_C2; + } + + tmp[1] = b43_phy_read(dev, B43_NPHY_RFSEQCA); + tmp[2] = b43_phy_read(dev, afectl_core); + tmp[3] = b43_phy_read(dev, B43_NPHY_AFECTL_OVER); + tmp[4] = b43_phy_read(dev, rfctl[0]); + tmp[5] = b43_phy_read(dev, rfctl[1]); + + b43_phy_maskset(dev, B43_NPHY_RFSEQCA, + ~B43_NPHY_RFSEQCA_RXDIS & 0xFFFF, + ((1 - i) << B43_NPHY_RFSEQCA_RXDIS_SHIFT)); + b43_phy_maskset(dev, B43_NPHY_RFSEQCA, ~B43_NPHY_RFSEQCA_TXEN, + (1 - i)); + b43_phy_set(dev, afectl_core, 0x0006); + b43_phy_set(dev, B43_NPHY_AFECTL_OVER, 0x0006); + + band = b43_current_band(dev->wl); + + if (nphy->rxcalparams & 0xFF000000) { + if (band == IEEE80211_BAND_5GHZ) + b43_phy_write(dev, rfctl[0], 0x140); + else + b43_phy_write(dev, rfctl[0], 0x110); + } else { + if (band == IEEE80211_BAND_5GHZ) + b43_phy_write(dev, rfctl[0], 0x180); + else + b43_phy_write(dev, rfctl[0], 0x120); + } + + if (band == IEEE80211_BAND_5GHZ) + b43_phy_write(dev, rfctl[1], 0x148); + else + b43_phy_write(dev, rfctl[1], 0x114); + + if (nphy->rxcalparams & 0x10000) { + b43_radio_maskset(dev, B2055_C1_GENSPARE2, 0xFC, + (i + 1)); + b43_radio_maskset(dev, B2055_C2_GENSPARE2, 0xFC, + (2 - i)); + } + + for (j = 0; j < 4; j++) { + if (j < 3) { + cur_lna = lna[j]; + cur_hpf1 = hpf1[j]; + cur_hpf2 = hpf2[j]; + } else { + if (power[1] > 10000) { + use = 1; + cur_hpf = cur_hpf1; + index = 2; + } else { + if (power[0] > 10000) { + use = 1; + cur_hpf = cur_hpf1; + index = 1; + } else { + index = 0; + use = 2; + cur_hpf = cur_hpf2; + } + } + cur_lna = lna[index]; + cur_hpf1 = hpf1[index]; + cur_hpf2 = hpf2[index]; + cur_hpf += desired - hweight32(power[index]); + cur_hpf = clamp_val(cur_hpf, 0, 10); + if (use == 1) + cur_hpf1 = cur_hpf; + else + cur_hpf2 = cur_hpf; + } + + tmp[0] = ((cur_hpf2 << 8) | (cur_hpf1 << 4) | + (cur_lna << 2)); + b43_nphy_rf_ctl_override(dev, 0x400, tmp[0], 3, + false); + b43_nphy_force_rf_sequence(dev, B43_RFSEQ_RESET2RX); + b43_nphy_stop_playback(dev); + + if (playtone) { + ret = b43_nphy_tx_tone(dev, 4000, + (nphy->rxcalparams & 0xFFFF), + false, false); + playtone = false; + } else { + b43_nphy_run_samples(dev, 160, 0xFFFF, 0, + false, false); + } + + if (ret == 0) { + if (j < 3) { + b43_nphy_rx_iq_est(dev, &est, 1024, 32, + false); + if (i == 0) { + real = est.i0_pwr; + imag = est.q0_pwr; + } else { + real = est.i1_pwr; + imag = est.q1_pwr; + } + power[i] = ((real + imag) / 1024) + 1; + } else { + b43_nphy_calc_rx_iq_comp(dev, 1 << i); + } + b43_nphy_stop_playback(dev); + } + + if (ret != 0) + break; + } + + b43_radio_mask(dev, B2055_C1_GENSPARE2, 0xFC); + b43_radio_mask(dev, B2055_C2_GENSPARE2, 0xFC); + b43_phy_write(dev, rfctl[1], tmp[5]); + b43_phy_write(dev, rfctl[0], tmp[4]); + b43_phy_write(dev, B43_NPHY_AFECTL_OVER, tmp[3]); + b43_phy_write(dev, afectl_core, tmp[2]); + b43_phy_write(dev, B43_NPHY_RFSEQCA, tmp[1]); + + if (ret != 0) + break; + } + + b43_nphy_rf_ctl_override(dev, 0x400, 0, 3, true); + b43_nphy_force_rf_sequence(dev, B43_RFSEQ_RESET2RX); + b43_ntab_write_bulk(dev, B43_NTAB16(7, 0x110), 2, gain_save); + + b43_nphy_stay_in_carrier_search(dev, 0); + + return ret; +} + +static int b43_nphy_rev3_cal_rx_iq(struct b43_wldev *dev, + struct nphy_txgains target, u8 type, bool debug) +{ + return -1; +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/CalRxIq */ +static int b43_nphy_cal_rx_iq(struct b43_wldev *dev, + struct nphy_txgains target, u8 type, bool debug) +{ + if (dev->phy.rev >= 3) + return b43_nphy_rev3_cal_rx_iq(dev, target, type, debug); + else + return b43_nphy_rev2_cal_rx_iq(dev, target, type, debug); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/RxCoreSetState */ +static void b43_nphy_set_rx_core_state(struct b43_wldev *dev, u8 mask) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_n *nphy = phy->n; + /* u16 buf[16]; it's rev3+ */ + + nphy->phyrxchain = mask; + + if (0 /* FIXME clk */) + return; + + b43_mac_suspend(dev); + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, true); + + b43_phy_maskset(dev, B43_NPHY_RFSEQCA, ~B43_NPHY_RFSEQCA_RXEN, + (mask & 0x3) << B43_NPHY_RFSEQCA_RXEN_SHIFT); + + if ((mask & 0x3) != 0x3) { + b43_phy_write(dev, B43_NPHY_HPANT_SWTHRES, 1); + if (dev->phy.rev >= 3) { + /* TODO */ + } + } else { + b43_phy_write(dev, B43_NPHY_HPANT_SWTHRES, 0x1E); + if (dev->phy.rev >= 3) { + /* TODO */ + } + } + + b43_nphy_force_rf_sequence(dev, B43_RFSEQ_RESET2RX); + + if (nphy->hang_avoid) + b43_nphy_stay_in_carrier_search(dev, false); + + b43_mac_enable(dev); +} + +/************************************************** + * N-PHY init + **************************************************/ + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/MIMOConfig */ +static void b43_nphy_update_mimo_config(struct b43_wldev *dev, s32 preamble) +{ + u16 mimocfg = b43_phy_read(dev, B43_NPHY_MIMOCFG); + + mimocfg |= B43_NPHY_MIMOCFG_AUTO; + if (preamble == 1) + mimocfg |= B43_NPHY_MIMOCFG_GFMIX; + else + mimocfg &= ~B43_NPHY_MIMOCFG_GFMIX; + + b43_phy_write(dev, B43_NPHY_MIMOCFG, mimocfg); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/BPHYInit */ +static void b43_nphy_bphy_init(struct b43_wldev *dev) +{ + unsigned int i; + u16 val; + + val = 0x1E1F; + for (i = 0; i < 16; i++) { + b43_phy_write(dev, B43_PHY_N_BMODE(0x88 + i), val); + val -= 0x202; + } + val = 0x3E3F; + for (i = 0; i < 16; i++) { + b43_phy_write(dev, B43_PHY_N_BMODE(0x98 + i), val); + val -= 0x202; + } + b43_phy_write(dev, B43_PHY_N_BMODE(0x38), 0x668); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/SuperSwitchInit */ +static void b43_nphy_superswitch_init(struct b43_wldev *dev, bool init) +{ + if (dev->phy.rev >= 3) { + if (!init) + return; + if (0 /* FIXME */) { + b43_ntab_write(dev, B43_NTAB16(9, 2), 0x211); + b43_ntab_write(dev, B43_NTAB16(9, 3), 0x222); + b43_ntab_write(dev, B43_NTAB16(9, 8), 0x144); + b43_ntab_write(dev, B43_NTAB16(9, 12), 0x188); + } + } else { + b43_phy_write(dev, B43_NPHY_GPIO_LOOEN, 0); + b43_phy_write(dev, B43_NPHY_GPIO_HIOEN, 0); + + switch (dev->dev->bus_type) { +#ifdef CPTCFG_B43_BCMA + case B43_BUS_BCMA: + bcma_chipco_gpio_control(&dev->dev->bdev->bus->drv_cc, + 0xFC00, 0xFC00); + break; +#endif +#ifdef CPTCFG_B43_SSB + case B43_BUS_SSB: + ssb_chipco_gpio_control(&dev->dev->sdev->bus->chipco, + 0xFC00, 0xFC00); + break; +#endif + } + + b43_maskset32(dev, B43_MMIO_MACCTL, ~B43_MACCTL_GPOUTSMSK, 0); + b43_maskset16(dev, B43_MMIO_GPIO_MASK, ~0, 0xFC00); + b43_maskset16(dev, B43_MMIO_GPIO_CONTROL, (~0xFC00 & 0xFFFF), + 0); + + if (init) { + b43_phy_write(dev, B43_NPHY_RFCTL_LUT_TRSW_LO1, 0x2D8); + b43_phy_write(dev, B43_NPHY_RFCTL_LUT_TRSW_UP1, 0x301); + b43_phy_write(dev, B43_NPHY_RFCTL_LUT_TRSW_LO2, 0x2D8); + b43_phy_write(dev, B43_NPHY_RFCTL_LUT_TRSW_UP2, 0x301); + } + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/Init/N */ +static int b43_phy_initn(struct b43_wldev *dev) +{ + struct ssb_sprom *sprom = dev->dev->bus_sprom; + struct b43_phy *phy = &dev->phy; + struct b43_phy_n *nphy = phy->n; + u8 tx_pwr_state; + struct nphy_txgains target; + u16 tmp; + enum ieee80211_band tmp2; + bool do_rssi_cal; + + u16 clip[2]; + bool do_cal = false; + + if ((dev->phy.rev >= 3) && + (sprom->boardflags_lo & B43_BFL_EXTLNA) && + (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ)) { + switch (dev->dev->bus_type) { +#ifdef CPTCFG_B43_BCMA + case B43_BUS_BCMA: + bcma_cc_set32(&dev->dev->bdev->bus->drv_cc, + BCMA_CC_CHIPCTL, 0x40); + break; +#endif +#ifdef CPTCFG_B43_SSB + case B43_BUS_SSB: + chipco_set32(&dev->dev->sdev->bus->chipco, + SSB_CHIPCO_CHIPCTL, 0x40); + break; +#endif + } + } + nphy->deaf_count = 0; + b43_nphy_tables_init(dev); + nphy->crsminpwr_adjusted = false; + nphy->noisevars_adjusted = false; + + /* Clear all overrides */ + if (dev->phy.rev >= 3) { + b43_phy_write(dev, B43_NPHY_TXF_40CO_B1S1, 0); + b43_phy_write(dev, B43_NPHY_RFCTL_OVER, 0); + b43_phy_write(dev, B43_NPHY_TXF_40CO_B1S0, 0); + b43_phy_write(dev, B43_NPHY_TXF_40CO_B32S1, 0); + } else { + b43_phy_write(dev, B43_NPHY_RFCTL_OVER, 0); + } + b43_phy_write(dev, B43_NPHY_RFCTL_INTC1, 0); + b43_phy_write(dev, B43_NPHY_RFCTL_INTC2, 0); + if (dev->phy.rev < 6) { + b43_phy_write(dev, B43_NPHY_RFCTL_INTC3, 0); + b43_phy_write(dev, B43_NPHY_RFCTL_INTC4, 0); + } + b43_phy_mask(dev, B43_NPHY_RFSEQMODE, + ~(B43_NPHY_RFSEQMODE_CAOVER | + B43_NPHY_RFSEQMODE_TROVER)); + if (dev->phy.rev >= 3) + b43_phy_write(dev, B43_NPHY_AFECTL_OVER1, 0); + b43_phy_write(dev, B43_NPHY_AFECTL_OVER, 0); + + if (dev->phy.rev <= 2) { + tmp = (dev->phy.rev == 2) ? 0x3B : 0x40; + b43_phy_maskset(dev, B43_NPHY_BPHY_CTL3, + ~B43_NPHY_BPHY_CTL3_SCALE, + tmp << B43_NPHY_BPHY_CTL3_SCALE_SHIFT); + } + b43_phy_write(dev, B43_NPHY_AFESEQ_TX2RX_PUD_20M, 0x20); + b43_phy_write(dev, B43_NPHY_AFESEQ_TX2RX_PUD_40M, 0x20); + + if (sprom->boardflags2_lo & B43_BFL2_SKWRKFEM_BRD || + (dev->dev->board_vendor == PCI_VENDOR_ID_APPLE && + dev->dev->board_type == BCMA_BOARD_TYPE_BCM943224M93)) + b43_phy_write(dev, B43_NPHY_TXREALFD, 0xA0); + else + b43_phy_write(dev, B43_NPHY_TXREALFD, 0xB8); + b43_phy_write(dev, B43_NPHY_MIMO_CRSTXEXT, 0xC8); + b43_phy_write(dev, B43_NPHY_PLOAD_CSENSE_EXTLEN, 0x50); + b43_phy_write(dev, B43_NPHY_TXRIFS_FRDEL, 0x30); + + b43_nphy_update_mimo_config(dev, nphy->preamble_override); + b43_nphy_update_txrx_chain(dev); + + if (phy->rev < 2) { + b43_phy_write(dev, B43_NPHY_DUP40_GFBL, 0xAA8); + b43_phy_write(dev, B43_NPHY_DUP40_BL, 0x9A4); + } + + tmp2 = b43_current_band(dev->wl); + if (b43_nphy_ipa(dev)) { + b43_phy_set(dev, B43_NPHY_PAPD_EN0, 0x1); + b43_phy_maskset(dev, B43_NPHY_EPS_TABLE_ADJ0, 0x007F, + nphy->papd_epsilon_offset[0] << 7); + b43_phy_set(dev, B43_NPHY_PAPD_EN1, 0x1); + b43_phy_maskset(dev, B43_NPHY_EPS_TABLE_ADJ1, 0x007F, + nphy->papd_epsilon_offset[1] << 7); + b43_nphy_int_pa_set_tx_dig_filters(dev); + } else if (phy->rev >= 5) { + b43_nphy_ext_pa_set_tx_dig_filters(dev); + } + + b43_nphy_workarounds(dev); + + /* Reset CCA, in init code it differs a little from standard way */ + b43_phy_force_clock(dev, 1); + tmp = b43_phy_read(dev, B43_NPHY_BBCFG); + b43_phy_write(dev, B43_NPHY_BBCFG, tmp | B43_NPHY_BBCFG_RSTCCA); + b43_phy_write(dev, B43_NPHY_BBCFG, tmp & ~B43_NPHY_BBCFG_RSTCCA); + b43_phy_force_clock(dev, 0); + + b43_mac_phy_clock_set(dev, true); + + b43_nphy_pa_override(dev, false); + b43_nphy_force_rf_sequence(dev, B43_RFSEQ_RX2TX); + b43_nphy_force_rf_sequence(dev, B43_RFSEQ_RESET2RX); + b43_nphy_pa_override(dev, true); + + b43_nphy_classifier(dev, 0, 0); + b43_nphy_read_clip_detection(dev, clip); + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + b43_nphy_bphy_init(dev); + + tx_pwr_state = nphy->txpwrctrl; + b43_nphy_tx_power_ctrl(dev, false); + b43_nphy_tx_power_fix(dev); + b43_nphy_tx_power_ctl_idle_tssi(dev); + b43_nphy_tx_power_ctl_setup(dev); + b43_nphy_tx_gain_table_upload(dev); + + if (nphy->phyrxchain != 3) + b43_nphy_set_rx_core_state(dev, nphy->phyrxchain); + if (nphy->mphase_cal_phase_id > 0) + ;/* TODO PHY Periodic Calibration Multi-Phase Restart */ + + do_rssi_cal = false; + if (phy->rev >= 3) { + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + do_rssi_cal = !nphy->rssical_chanspec_2G.center_freq; + else + do_rssi_cal = !nphy->rssical_chanspec_5G.center_freq; + + if (do_rssi_cal) + b43_nphy_rssi_cal(dev); + else + b43_nphy_restore_rssi_cal(dev); + } else { + b43_nphy_rssi_cal(dev); + } + + if (!((nphy->measure_hold & 0x6) != 0)) { + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + do_cal = !nphy->iqcal_chanspec_2G.center_freq; + else + do_cal = !nphy->iqcal_chanspec_5G.center_freq; + + if (nphy->mute) + do_cal = false; + + if (do_cal) { + target = b43_nphy_get_tx_gains(dev); + + if (nphy->antsel_type == 2) + b43_nphy_superswitch_init(dev, true); + if (nphy->perical != 2) { + b43_nphy_rssi_cal(dev); + if (phy->rev >= 3) { + nphy->cal_orig_pwr_idx[0] = + nphy->txpwrindex[0].index_internal; + nphy->cal_orig_pwr_idx[1] = + nphy->txpwrindex[1].index_internal; + /* TODO N PHY Pre Calibrate TX Gain */ + target = b43_nphy_get_tx_gains(dev); + } + if (!b43_nphy_cal_tx_iq_lo(dev, target, true, false)) + if (b43_nphy_cal_rx_iq(dev, target, 2, 0) == 0) + b43_nphy_save_cal(dev); + } else if (nphy->mphase_cal_phase_id == 0) + ;/* N PHY Periodic Calibration with arg 3 */ + } else { + b43_nphy_restore_cal(dev); + } + } + + b43_nphy_tx_pwr_ctrl_coef_setup(dev); + b43_nphy_tx_power_ctrl(dev, tx_pwr_state); + b43_phy_write(dev, B43_NPHY_TXMACIF_HOLDOFF, 0x0015); + b43_phy_write(dev, B43_NPHY_TXMACDELAY, 0x0320); + if (phy->rev >= 3 && phy->rev <= 6) + b43_phy_write(dev, B43_NPHY_PLOAD_CSENSE_EXTLEN, 0x0014); + b43_nphy_tx_lp_fbw(dev); + if (phy->rev >= 3) + b43_nphy_spur_workaround(dev); + + return 0; +} + +/************************************************** + * Channel switching ops. + **************************************************/ + +static void b43_chantab_phy_upload(struct b43_wldev *dev, + const struct b43_phy_n_sfo_cfg *e) +{ + b43_phy_write(dev, B43_NPHY_BW1A, e->phy_bw1a); + b43_phy_write(dev, B43_NPHY_BW2, e->phy_bw2); + b43_phy_write(dev, B43_NPHY_BW3, e->phy_bw3); + b43_phy_write(dev, B43_NPHY_BW4, e->phy_bw4); + b43_phy_write(dev, B43_NPHY_BW5, e->phy_bw5); + b43_phy_write(dev, B43_NPHY_BW6, e->phy_bw6); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PmuSpurAvoid */ +static void b43_nphy_pmu_spur_avoid(struct b43_wldev *dev, bool avoid) +{ + switch (dev->dev->bus_type) { +#ifdef CPTCFG_B43_BCMA + case B43_BUS_BCMA: + bcma_pmu_spuravoid_pllupdate(&dev->dev->bdev->bus->drv_cc, + avoid); + break; +#endif +#ifdef CPTCFG_B43_SSB + case B43_BUS_SSB: + ssb_pmu_spuravoid_pllupdate(&dev->dev->sdev->bus->chipco, + avoid); + break; +#endif + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/ChanspecSetup */ +static void b43_nphy_channel_setup(struct b43_wldev *dev, + const struct b43_phy_n_sfo_cfg *e, + struct ieee80211_channel *new_channel) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_n *nphy = dev->phy.n; + int ch = new_channel->hw_value; + + u16 old_band_5ghz; + u32 tmp32; + + old_band_5ghz = + b43_phy_read(dev, B43_NPHY_BANDCTL) & B43_NPHY_BANDCTL_5GHZ; + if (new_channel->band == IEEE80211_BAND_5GHZ && !old_band_5ghz) { + tmp32 = b43_read32(dev, B43_MMIO_PSM_PHY_HDR); + b43_write32(dev, B43_MMIO_PSM_PHY_HDR, tmp32 | 4); + b43_phy_set(dev, B43_PHY_B_BBCFG, 0xC000); + b43_write32(dev, B43_MMIO_PSM_PHY_HDR, tmp32); + b43_phy_set(dev, B43_NPHY_BANDCTL, B43_NPHY_BANDCTL_5GHZ); + } else if (new_channel->band == IEEE80211_BAND_2GHZ && old_band_5ghz) { + b43_phy_mask(dev, B43_NPHY_BANDCTL, ~B43_NPHY_BANDCTL_5GHZ); + tmp32 = b43_read32(dev, B43_MMIO_PSM_PHY_HDR); + b43_write32(dev, B43_MMIO_PSM_PHY_HDR, tmp32 | 4); + b43_phy_mask(dev, B43_PHY_B_BBCFG, 0x3FFF); + b43_write32(dev, B43_MMIO_PSM_PHY_HDR, tmp32); + } + + b43_chantab_phy_upload(dev, e); + + if (new_channel->hw_value == 14) { + b43_nphy_classifier(dev, 2, 0); + b43_phy_set(dev, B43_PHY_B_TEST, 0x0800); + } else { + b43_nphy_classifier(dev, 2, 2); + if (new_channel->band == IEEE80211_BAND_2GHZ) + b43_phy_mask(dev, B43_PHY_B_TEST, ~0x840); + } + + if (!nphy->txpwrctrl) + b43_nphy_tx_power_fix(dev); + + if (dev->phy.rev < 3) + b43_nphy_adjust_lna_gain_table(dev); + + b43_nphy_tx_lp_fbw(dev); + + if (dev->phy.rev >= 3 && + dev->phy.n->spur_avoid != B43_SPUR_AVOID_DISABLE) { + bool avoid = false; + if (dev->phy.n->spur_avoid == B43_SPUR_AVOID_FORCE) { + avoid = true; + } else if (!b43_channel_type_is_40mhz(phy->channel_type)) { + if ((ch >= 5 && ch <= 8) || ch == 13 || ch == 14) + avoid = true; + } else { /* 40MHz */ + if (nphy->aband_spurwar_en && + (ch == 38 || ch == 102 || ch == 118)) + avoid = dev->dev->chip_id == 0x4716; + } + + b43_nphy_pmu_spur_avoid(dev, avoid); + + if (dev->dev->chip_id == 43222 || dev->dev->chip_id == 43224 || + dev->dev->chip_id == 43225) { + b43_write16(dev, B43_MMIO_TSF_CLK_FRAC_LOW, + avoid ? 0x5341 : 0x8889); + b43_write16(dev, B43_MMIO_TSF_CLK_FRAC_HIGH, 0x8); + } + + if (dev->phy.rev == 3 || dev->phy.rev == 4) + ; /* TODO: reset PLL */ + + if (avoid) + b43_phy_set(dev, B43_NPHY_BBCFG, B43_NPHY_BBCFG_RSTRX); + else + b43_phy_mask(dev, B43_NPHY_BBCFG, + ~B43_NPHY_BBCFG_RSTRX & 0xFFFF); + + b43_nphy_reset_cca(dev); + + /* wl sets useless phy_isspuravoid here */ + } + + b43_phy_write(dev, B43_NPHY_NDATAT_DUP40, 0x3830); + + if (phy->rev >= 3) + b43_nphy_spur_workaround(dev); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/SetChanspec */ +static int b43_nphy_set_channel(struct b43_wldev *dev, + struct ieee80211_channel *channel, + enum nl80211_channel_type channel_type) +{ + struct b43_phy *phy = &dev->phy; + + const struct b43_nphy_channeltab_entry_rev2 *tabent_r2 = NULL; + const struct b43_nphy_channeltab_entry_rev3 *tabent_r3 = NULL; + + u8 tmp; + + if (dev->phy.rev >= 3) { + tabent_r3 = b43_nphy_get_chantabent_rev3(dev, + channel->center_freq); + if (!tabent_r3) + return -ESRCH; + } else { + tabent_r2 = b43_nphy_get_chantabent_rev2(dev, + channel->hw_value); + if (!tabent_r2) + return -ESRCH; + } + + /* Channel is set later in common code, but we need to set it on our + own to let this function's subcalls work properly. */ + phy->channel = channel->hw_value; + phy->channel_freq = channel->center_freq; + + if (b43_channel_type_is_40mhz(phy->channel_type) != + b43_channel_type_is_40mhz(channel_type)) + ; /* TODO: BMAC BW Set (channel_type) */ + + if (channel_type == NL80211_CHAN_HT40PLUS) + b43_phy_set(dev, B43_NPHY_RXCTL, + B43_NPHY_RXCTL_BSELU20); + else if (channel_type == NL80211_CHAN_HT40MINUS) + b43_phy_mask(dev, B43_NPHY_RXCTL, + ~B43_NPHY_RXCTL_BSELU20); + + if (dev->phy.rev >= 3) { + tmp = (channel->band == IEEE80211_BAND_5GHZ) ? 4 : 0; + b43_radio_maskset(dev, 0x08, 0xFFFB, tmp); + b43_radio_2056_setup(dev, tabent_r3); + b43_nphy_channel_setup(dev, &(tabent_r3->phy_regs), channel); + } else { + tmp = (channel->band == IEEE80211_BAND_5GHZ) ? 0x0020 : 0x0050; + b43_radio_maskset(dev, B2055_MASTER1, 0xFF8F, tmp); + b43_radio_2055_setup(dev, tabent_r2); + b43_nphy_channel_setup(dev, &(tabent_r2->phy_regs), channel); + } + + return 0; +} + +/************************************************** + * Basic PHY ops. + **************************************************/ + +static int b43_nphy_op_allocate(struct b43_wldev *dev) +{ + struct b43_phy_n *nphy; + + nphy = kzalloc(sizeof(*nphy), GFP_KERNEL); + if (!nphy) + return -ENOMEM; + dev->phy.n = nphy; + + return 0; +} + +static void b43_nphy_op_prepare_structs(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_n *nphy = phy->n; + struct ssb_sprom *sprom = dev->dev->bus_sprom; + + memset(nphy, 0, sizeof(*nphy)); + + nphy->hang_avoid = (phy->rev == 3 || phy->rev == 4); + nphy->spur_avoid = (phy->rev >= 3) ? + B43_SPUR_AVOID_AUTO : B43_SPUR_AVOID_DISABLE; + nphy->init_por = true; + nphy->gain_boost = true; /* this way we follow wl, assume it is true */ + nphy->txrx_chain = 2; /* sth different than 0 and 1 for now */ + nphy->phyrxchain = 3; /* to avoid b43_nphy_set_rx_core_state like wl */ + nphy->perical = 2; /* avoid additional rssi cal on init (like wl) */ + /* 128 can mean disabled-by-default state of TX pwr ctl. Max value is + * 0x7f == 127 and we check for 128 when restoring TX pwr ctl. */ + nphy->tx_pwr_idx[0] = 128; + nphy->tx_pwr_idx[1] = 128; + + /* Hardware TX power control and 5GHz power gain */ + nphy->txpwrctrl = false; + nphy->pwg_gain_5ghz = false; + if (dev->phy.rev >= 3 || + (dev->dev->board_vendor == PCI_VENDOR_ID_APPLE && + (dev->dev->core_rev == 11 || dev->dev->core_rev == 12))) { + nphy->txpwrctrl = true; + nphy->pwg_gain_5ghz = true; + } else if (sprom->revision >= 4) { + if (dev->phy.rev >= 2 && + (sprom->boardflags2_lo & B43_BFL2_TXPWRCTRL_EN)) { + nphy->txpwrctrl = true; +#ifdef CPTCFG_B43_SSB + if (dev->dev->bus_type == B43_BUS_SSB && + dev->dev->sdev->bus->bustype == SSB_BUSTYPE_PCI) { + struct pci_dev *pdev = + dev->dev->sdev->bus->host_pci; + if (pdev->device == 0x4328 || + pdev->device == 0x432a) + nphy->pwg_gain_5ghz = true; + } +#endif + } else if (sprom->boardflags2_lo & B43_BFL2_5G_PWRGAIN) { + nphy->pwg_gain_5ghz = true; + } + } + + if (dev->phy.rev >= 3) { + nphy->ipa2g_on = sprom->fem.ghz2.extpa_gain == 2; + nphy->ipa5g_on = sprom->fem.ghz5.extpa_gain == 2; + } + + nphy->init_por = true; +} + +static void b43_nphy_op_free(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_n *nphy = phy->n; + + kfree(nphy); + phy->n = NULL; +} + +static int b43_nphy_op_init(struct b43_wldev *dev) +{ + return b43_phy_initn(dev); +} + +static inline void check_phyreg(struct b43_wldev *dev, u16 offset) +{ +#if B43_DEBUG + if ((offset & B43_PHYROUTE) == B43_PHYROUTE_OFDM_GPHY) { + /* OFDM registers are onnly available on A/G-PHYs */ + b43err(dev->wl, "Invalid OFDM PHY access at " + "0x%04X on N-PHY\n", offset); + dump_stack(); + } + if ((offset & B43_PHYROUTE) == B43_PHYROUTE_EXT_GPHY) { + /* Ext-G registers are only available on G-PHYs */ + b43err(dev->wl, "Invalid EXT-G PHY access at " + "0x%04X on N-PHY\n", offset); + dump_stack(); + } +#endif /* B43_DEBUG */ +} + +static u16 b43_nphy_op_read(struct b43_wldev *dev, u16 reg) +{ + check_phyreg(dev, reg); + b43_write16(dev, B43_MMIO_PHY_CONTROL, reg); + return b43_read16(dev, B43_MMIO_PHY_DATA); +} + +static void b43_nphy_op_write(struct b43_wldev *dev, u16 reg, u16 value) +{ + check_phyreg(dev, reg); + b43_write16(dev, B43_MMIO_PHY_CONTROL, reg); + b43_write16(dev, B43_MMIO_PHY_DATA, value); +} + +static void b43_nphy_op_maskset(struct b43_wldev *dev, u16 reg, u16 mask, + u16 set) +{ + check_phyreg(dev, reg); + b43_write16(dev, B43_MMIO_PHY_CONTROL, reg); + b43_maskset16(dev, B43_MMIO_PHY_DATA, mask, set); +} + +static u16 b43_nphy_op_radio_read(struct b43_wldev *dev, u16 reg) +{ + /* Register 1 is a 32-bit register. */ + B43_WARN_ON(reg == 1); + /* N-PHY needs 0x100 for read access */ + reg |= 0x100; + + b43_write16(dev, B43_MMIO_RADIO_CONTROL, reg); + return b43_read16(dev, B43_MMIO_RADIO_DATA_LOW); +} + +static void b43_nphy_op_radio_write(struct b43_wldev *dev, u16 reg, u16 value) +{ + /* Register 1 is a 32-bit register. */ + B43_WARN_ON(reg == 1); + + b43_write16(dev, B43_MMIO_RADIO_CONTROL, reg); + b43_write16(dev, B43_MMIO_RADIO_DATA_LOW, value); +} + +/* http://bcm-v4.sipsolutions.net/802.11/Radio/Switch%20Radio */ +static void b43_nphy_op_software_rfkill(struct b43_wldev *dev, + bool blocked) +{ + if (b43_read32(dev, B43_MMIO_MACCTL) & B43_MACCTL_ENABLED) + b43err(dev->wl, "MAC not suspended\n"); + + if (blocked) { + b43_phy_mask(dev, B43_NPHY_RFCTL_CMD, + ~B43_NPHY_RFCTL_CMD_CHIP0PU); + if (dev->phy.rev >= 7) { + /* TODO */ + } else if (dev->phy.rev >= 3) { + b43_radio_mask(dev, 0x09, ~0x2); + + b43_radio_write(dev, 0x204D, 0); + b43_radio_write(dev, 0x2053, 0); + b43_radio_write(dev, 0x2058, 0); + b43_radio_write(dev, 0x205E, 0); + b43_radio_mask(dev, 0x2062, ~0xF0); + b43_radio_write(dev, 0x2064, 0); + + b43_radio_write(dev, 0x304D, 0); + b43_radio_write(dev, 0x3053, 0); + b43_radio_write(dev, 0x3058, 0); + b43_radio_write(dev, 0x305E, 0); + b43_radio_mask(dev, 0x3062, ~0xF0); + b43_radio_write(dev, 0x3064, 0); + } + } else { + if (dev->phy.rev >= 7) { + b43_radio_2057_init(dev); + b43_switch_channel(dev, dev->phy.channel); + } else if (dev->phy.rev >= 3) { + b43_radio_init2056(dev); + b43_switch_channel(dev, dev->phy.channel); + } else { + b43_radio_init2055(dev); + } + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/Anacore */ +static void b43_nphy_op_switch_analog(struct b43_wldev *dev, bool on) +{ + u16 override = on ? 0x0 : 0x7FFF; + u16 core = on ? 0xD : 0x00FD; + + if (dev->phy.rev >= 3) { + if (on) { + b43_phy_write(dev, B43_NPHY_AFECTL_C1, core); + b43_phy_write(dev, B43_NPHY_AFECTL_OVER1, override); + b43_phy_write(dev, B43_NPHY_AFECTL_C2, core); + b43_phy_write(dev, B43_NPHY_AFECTL_OVER, override); + } else { + b43_phy_write(dev, B43_NPHY_AFECTL_OVER1, override); + b43_phy_write(dev, B43_NPHY_AFECTL_C1, core); + b43_phy_write(dev, B43_NPHY_AFECTL_OVER, override); + b43_phy_write(dev, B43_NPHY_AFECTL_C2, core); + } + } else { + b43_phy_write(dev, B43_NPHY_AFECTL_OVER, override); + } +} + +static int b43_nphy_op_switch_channel(struct b43_wldev *dev, + unsigned int new_channel) +{ + struct ieee80211_channel *channel = dev->wl->hw->conf.chandef.chan; + enum nl80211_channel_type channel_type = + cfg80211_get_chandef_type(&dev->wl->hw->conf.chandef); + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + if ((new_channel < 1) || (new_channel > 14)) + return -EINVAL; + } else { + if (new_channel > 200) + return -EINVAL; + } + + return b43_nphy_set_channel(dev, channel, channel_type); +} + +static unsigned int b43_nphy_op_get_default_chan(struct b43_wldev *dev) +{ + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + return 1; + return 36; +} + +const struct b43_phy_operations b43_phyops_n = { + .allocate = b43_nphy_op_allocate, + .free = b43_nphy_op_free, + .prepare_structs = b43_nphy_op_prepare_structs, + .init = b43_nphy_op_init, + .phy_read = b43_nphy_op_read, + .phy_write = b43_nphy_op_write, + .phy_maskset = b43_nphy_op_maskset, + .radio_read = b43_nphy_op_radio_read, + .radio_write = b43_nphy_op_radio_write, + .software_rfkill = b43_nphy_op_software_rfkill, + .switch_analog = b43_nphy_op_switch_analog, + .switch_channel = b43_nphy_op_switch_channel, + .get_default_chan = b43_nphy_op_get_default_chan, + .recalc_txpower = b43_nphy_op_recalc_txpower, + .adjust_txpower = b43_nphy_op_adjust_txpower, +}; diff --git a/drivers/net/wireless/b43/phy_n.h b/drivers/net/wireless/b43/phy_n.h new file mode 100644 index 0000000..9a5b6bc --- /dev/null +++ b/drivers/net/wireless/b43/phy_n.h @@ -0,0 +1,989 @@ +#ifndef B43_NPHY_H_ +#define B43_NPHY_H_ + +#include "phy_common.h" + + +/* N-PHY registers. */ + +#define B43_NPHY_BBCFG B43_PHY_N(0x001) /* BB config */ +#define B43_NPHY_BBCFG_RSTCCA 0x4000 /* Reset CCA */ +#define B43_NPHY_BBCFG_RSTRX 0x8000 /* Reset RX */ +#define B43_NPHY_CHANNEL B43_PHY_N(0x005) /* Channel */ +#define B43_NPHY_TXERR B43_PHY_N(0x007) /* TX error */ +#define B43_NPHY_BANDCTL B43_PHY_N(0x009) /* Band control */ +#define B43_NPHY_BANDCTL_5GHZ 0x0001 /* Use the 5GHz band */ +#define B43_NPHY_4WI_ADDR B43_PHY_N(0x00B) /* Four-wire bus address */ +#define B43_NPHY_4WI_DATAHI B43_PHY_N(0x00C) /* Four-wire bus data high */ +#define B43_NPHY_4WI_DATALO B43_PHY_N(0x00D) /* Four-wire bus data low */ +#define B43_NPHY_BIST_STAT0 B43_PHY_N(0x00E) /* Built-in self test status 0 */ +#define B43_NPHY_BIST_STAT1 B43_PHY_N(0x00F) /* Built-in self test status 1 */ + +#define B43_NPHY_C1_DESPWR B43_PHY_N(0x018) /* Core 1 desired power */ +#define B43_NPHY_C1_CCK_DESPWR B43_PHY_N(0x019) /* Core 1 CCK desired power */ +#define B43_NPHY_C1_BCLIPBKOFF B43_PHY_N(0x01A) /* Core 1 barely clip backoff */ +#define B43_NPHY_C1_CCK_BCLIPBKOFF B43_PHY_N(0x01B) /* Core 1 CCK barely clip backoff */ +#define B43_NPHY_C1_CGAINI B43_PHY_N(0x01C) /* Core 1 compute gain info */ +#define B43_NPHY_C1_CGAINI_GAINBKOFF 0x001F /* Gain backoff */ +#define B43_NPHY_C1_CGAINI_GAINBKOFF_SHIFT 0 +#define B43_NPHY_C1_CGAINI_CLIPGBKOFF 0x03E0 /* Clip gain backoff */ +#define B43_NPHY_C1_CGAINI_CLIPGBKOFF_SHIFT 5 +#define B43_NPHY_C1_CGAINI_GAINSTEP 0x1C00 /* Gain step */ +#define B43_NPHY_C1_CGAINI_GAINSTEP_SHIFT 10 +#define B43_NPHY_C1_CGAINI_CL2DETECT 0x2000 /* Clip 2 detect mask */ +#define B43_NPHY_C1_CCK_CGAINI B43_PHY_N(0x01D) /* Core 1 CCK compute gain info */ +#define B43_NPHY_C1_CCK_CGAINI_GAINBKOFF 0x001F /* Gain backoff */ +#define B43_NPHY_C1_CCK_CGAINI_CLIPGBKOFF 0x01E0 /* CCK barely clip gain backoff */ +#define B43_NPHY_C1_MINMAX_GAIN B43_PHY_N(0x01E) /* Core 1 min/max gain */ +#define B43_NPHY_C1_MINGAIN 0x00FF /* Minimum gain */ +#define B43_NPHY_C1_MINGAIN_SHIFT 0 +#define B43_NPHY_C1_MAXGAIN 0xFF00 /* Maximum gain */ +#define B43_NPHY_C1_MAXGAIN_SHIFT 8 +#define B43_NPHY_C1_CCK_MINMAX_GAIN B43_PHY_N(0x01F) /* Core 1 CCK min/max gain */ +#define B43_NPHY_C1_CCK_MINGAIN 0x00FF /* Minimum gain */ +#define B43_NPHY_C1_CCK_MINGAIN_SHIFT 0 +#define B43_NPHY_C1_CCK_MAXGAIN 0xFF00 /* Maximum gain */ +#define B43_NPHY_C1_CCK_MAXGAIN_SHIFT 8 +#define B43_NPHY_C1_INITGAIN B43_PHY_N(0x020) /* Core 1 initial gain code */ +#define B43_NPHY_C1_INITGAIN_EXTLNA 0x0001 /* External LNA index */ +#define B43_NPHY_C1_INITGAIN_LNA 0x0006 /* LNA index */ +#define B43_NPHY_C1_INITGAIN_LNAIDX_SHIFT 1 +#define B43_NPHY_C1_INITGAIN_HPVGA1 0x0078 /* HPVGA1 index */ +#define B43_NPHY_C1_INITGAIN_HPVGA1_SHIFT 3 +#define B43_NPHY_C1_INITGAIN_HPVGA2 0x0F80 /* HPVGA2 index */ +#define B43_NPHY_C1_INITGAIN_HPVGA2_SHIFT 7 +#define B43_NPHY_C1_INITGAIN_TRRX 0x1000 /* TR RX index */ +#define B43_NPHY_C1_INITGAIN_TRTX 0x2000 /* TR TX index */ +#define B43_NPHY_REV3_C1_INITGAIN_A B43_PHY_N(0x020) +#define B43_NPHY_C1_CLIP1_HIGAIN B43_PHY_N(0x021) /* Core 1 clip1 high gain code */ +#define B43_NPHY_REV3_C1_INITGAIN_B B43_PHY_N(0x021) +#define B43_NPHY_C1_CLIP1_MEDGAIN B43_PHY_N(0x022) /* Core 1 clip1 medium gain code */ +#define B43_NPHY_REV3_C1_CLIP_HIGAIN_A B43_PHY_N(0x022) +#define B43_NPHY_C1_CLIP1_LOGAIN B43_PHY_N(0x023) /* Core 1 clip1 low gain code */ +#define B43_NPHY_REV3_C1_CLIP_HIGAIN_B B43_PHY_N(0x023) +#define B43_NPHY_C1_CLIP2_GAIN B43_PHY_N(0x024) /* Core 1 clip2 gain code */ +#define B43_NPHY_REV3_C1_CLIP_MEDGAIN_A B43_PHY_N(0x024) +#define B43_NPHY_C1_FILTERGAIN B43_PHY_N(0x025) /* Core 1 filter gain */ +#define B43_NPHY_C1_LPF_QHPF_BW B43_PHY_N(0x026) /* Core 1 LPF Q HP F bandwidth */ +#define B43_NPHY_C1_CLIPWBTHRES B43_PHY_N(0x027) /* Core 1 clip wideband threshold */ +#define B43_NPHY_C1_CLIPWBTHRES_CLIP2 0x003F /* Clip 2 */ +#define B43_NPHY_C1_CLIPWBTHRES_CLIP2_SHIFT 0 +#define B43_NPHY_C1_CLIPWBTHRES_CLIP1 0x0FC0 /* Clip 1 */ +#define B43_NPHY_C1_CLIPWBTHRES_CLIP1_SHIFT 6 +#define B43_NPHY_C1_W1THRES B43_PHY_N(0x028) /* Core 1 W1 threshold */ +#define B43_NPHY_C1_EDTHRES B43_PHY_N(0x029) /* Core 1 ED threshold */ +#define B43_NPHY_C1_SMSIGTHRES B43_PHY_N(0x02A) /* Core 1 small sig threshold */ +#define B43_NPHY_C1_NBCLIPTHRES B43_PHY_N(0x02B) /* Core 1 NB clip threshold */ +#define B43_NPHY_C1_CLIP1THRES B43_PHY_N(0x02C) /* Core 1 clip1 threshold */ +#define B43_NPHY_C1_CLIP2THRES B43_PHY_N(0x02D) /* Core 1 clip2 threshold */ + +#define B43_NPHY_C2_DESPWR B43_PHY_N(0x02E) /* Core 2 desired power */ +#define B43_NPHY_C2_CCK_DESPWR B43_PHY_N(0x02F) /* Core 2 CCK desired power */ +#define B43_NPHY_C2_BCLIPBKOFF B43_PHY_N(0x030) /* Core 2 barely clip backoff */ +#define B43_NPHY_C2_CCK_BCLIPBKOFF B43_PHY_N(0x031) /* Core 2 CCK barely clip backoff */ +#define B43_NPHY_C2_CGAINI B43_PHY_N(0x032) /* Core 2 compute gain info */ +#define B43_NPHY_C2_CGAINI_GAINBKOFF 0x001F /* Gain backoff */ +#define B43_NPHY_C2_CGAINI_GAINBKOFF_SHIFT 0 +#define B43_NPHY_C2_CGAINI_CLIPGBKOFF 0x03E0 /* Clip gain backoff */ +#define B43_NPHY_C2_CGAINI_CLIPGBKOFF_SHIFT 5 +#define B43_NPHY_C2_CGAINI_GAINSTEP 0x1C00 /* Gain step */ +#define B43_NPHY_C2_CGAINI_GAINSTEP_SHIFT 10 +#define B43_NPHY_C2_CGAINI_CL2DETECT 0x2000 /* Clip 2 detect mask */ +#define B43_NPHY_C2_CCK_CGAINI B43_PHY_N(0x033) /* Core 2 CCK compute gain info */ +#define B43_NPHY_C2_CCK_CGAINI_GAINBKOFF 0x001F /* Gain backoff */ +#define B43_NPHY_C2_CCK_CGAINI_CLIPGBKOFF 0x01E0 /* CCK barely clip gain backoff */ +#define B43_NPHY_C2_MINMAX_GAIN B43_PHY_N(0x034) /* Core 2 min/max gain */ +#define B43_NPHY_C2_MINGAIN 0x00FF /* Minimum gain */ +#define B43_NPHY_C2_MINGAIN_SHIFT 0 +#define B43_NPHY_C2_MAXGAIN 0xFF00 /* Maximum gain */ +#define B43_NPHY_C2_MAXGAIN_SHIFT 8 +#define B43_NPHY_C2_CCK_MINMAX_GAIN B43_PHY_N(0x035) /* Core 2 CCK min/max gain */ +#define B43_NPHY_C2_CCK_MINGAIN 0x00FF /* Minimum gain */ +#define B43_NPHY_C2_CCK_MINGAIN_SHIFT 0 +#define B43_NPHY_C2_CCK_MAXGAIN 0xFF00 /* Maximum gain */ +#define B43_NPHY_C2_CCK_MAXGAIN_SHIFT 8 +#define B43_NPHY_C2_INITGAIN B43_PHY_N(0x036) /* Core 2 initial gain code */ +#define B43_NPHY_C2_INITGAIN_EXTLNA 0x0001 /* External LNA index */ +#define B43_NPHY_C2_INITGAIN_LNA 0x0006 /* LNA index */ +#define B43_NPHY_C2_INITGAIN_LNAIDX_SHIFT 1 +#define B43_NPHY_C2_INITGAIN_HPVGA1 0x0078 /* HPVGA1 index */ +#define B43_NPHY_C2_INITGAIN_HPVGA1_SHIFT 3 +#define B43_NPHY_C2_INITGAIN_HPVGA2 0x0F80 /* HPVGA2 index */ +#define B43_NPHY_C2_INITGAIN_HPVGA2_SHIFT 7 +#define B43_NPHY_C2_INITGAIN_TRRX 0x1000 /* TR RX index */ +#define B43_NPHY_C2_INITGAIN_TRTX 0x2000 /* TR TX index */ +#define B43_NPHY_REV3_C1_CLIP_MEDGAIN_B B43_PHY_N(0x036) +#define B43_NPHY_C2_CLIP1_HIGAIN B43_PHY_N(0x037) /* Core 2 clip1 high gain code */ +#define B43_NPHY_REV3_C1_CLIP_LOGAIN_A B43_PHY_N(0x037) +#define B43_NPHY_C2_CLIP1_MEDGAIN B43_PHY_N(0x038) /* Core 2 clip1 medium gain code */ +#define B43_NPHY_REV3_C1_CLIP_LOGAIN_B B43_PHY_N(0x038) +#define B43_NPHY_C2_CLIP1_LOGAIN B43_PHY_N(0x039) /* Core 2 clip1 low gain code */ +#define B43_NPHY_REV3_C1_CLIP2_GAIN_A B43_PHY_N(0x039) +#define B43_NPHY_C2_CLIP2_GAIN B43_PHY_N(0x03A) /* Core 2 clip2 gain code */ +#define B43_NPHY_REV3_C1_CLIP2_GAIN_B B43_PHY_N(0x03A) +#define B43_NPHY_C2_FILTERGAIN B43_PHY_N(0x03B) /* Core 2 filter gain */ +#define B43_NPHY_C2_LPF_QHPF_BW B43_PHY_N(0x03C) /* Core 2 LPF Q HP F bandwidth */ +#define B43_NPHY_C2_CLIPWBTHRES B43_PHY_N(0x03D) /* Core 2 clip wideband threshold */ +#define B43_NPHY_C2_CLIPWBTHRES_CLIP2 0x003F /* Clip 2 */ +#define B43_NPHY_C2_CLIPWBTHRES_CLIP2_SHIFT 0 +#define B43_NPHY_C2_CLIPWBTHRES_CLIP1 0x0FC0 /* Clip 1 */ +#define B43_NPHY_C2_CLIPWBTHRES_CLIP1_SHIFT 6 +#define B43_NPHY_C2_W1THRES B43_PHY_N(0x03E) /* Core 2 W1 threshold */ +#define B43_NPHY_C2_EDTHRES B43_PHY_N(0x03F) /* Core 2 ED threshold */ +#define B43_NPHY_C2_SMSIGTHRES B43_PHY_N(0x040) /* Core 2 small sig threshold */ +#define B43_NPHY_C2_NBCLIPTHRES B43_PHY_N(0x041) /* Core 2 NB clip threshold */ +#define B43_NPHY_C2_CLIP1THRES B43_PHY_N(0x042) /* Core 2 clip1 threshold */ +#define B43_NPHY_C2_CLIP2THRES B43_PHY_N(0x043) /* Core 2 clip2 threshold */ + +#define B43_NPHY_CRS_THRES1 B43_PHY_N(0x044) /* CRS threshold 1 */ +#define B43_NPHY_CRS_THRES2 B43_PHY_N(0x045) /* CRS threshold 2 */ +#define B43_NPHY_CRS_THRES3 B43_PHY_N(0x046) /* CRS threshold 3 */ +#define B43_NPHY_CRSCTL B43_PHY_N(0x047) /* CRS control */ +#define B43_NPHY_DCFADDR B43_PHY_N(0x048) /* DC filter address */ +#define B43_NPHY_RXF20_NUM0 B43_PHY_N(0x049) /* RX filter 20 numerator 0 */ +#define B43_NPHY_RXF20_NUM1 B43_PHY_N(0x04A) /* RX filter 20 numerator 1 */ +#define B43_NPHY_RXF20_NUM2 B43_PHY_N(0x04B) /* RX filter 20 numerator 2 */ +#define B43_NPHY_RXF20_DENOM0 B43_PHY_N(0x04C) /* RX filter 20 denominator 0 */ +#define B43_NPHY_RXF20_DENOM1 B43_PHY_N(0x04D) /* RX filter 20 denominator 1 */ +#define B43_NPHY_RXF20_NUM10 B43_PHY_N(0x04E) /* RX filter 20 numerator 10 */ +#define B43_NPHY_RXF20_NUM11 B43_PHY_N(0x04F) /* RX filter 20 numerator 11 */ +#define B43_NPHY_RXF20_NUM12 B43_PHY_N(0x050) /* RX filter 20 numerator 12 */ +#define B43_NPHY_RXF20_DENOM10 B43_PHY_N(0x051) /* RX filter 20 denominator 10 */ +#define B43_NPHY_RXF20_DENOM11 B43_PHY_N(0x052) /* RX filter 20 denominator 11 */ +#define B43_NPHY_RXF40_NUM0 B43_PHY_N(0x053) /* RX filter 40 numerator 0 */ +#define B43_NPHY_RXF40_NUM1 B43_PHY_N(0x054) /* RX filter 40 numerator 1 */ +#define B43_NPHY_RXF40_NUM2 B43_PHY_N(0x055) /* RX filter 40 numerator 2 */ +#define B43_NPHY_RXF40_DENOM0 B43_PHY_N(0x056) /* RX filter 40 denominator 0 */ +#define B43_NPHY_RXF40_DENOM1 B43_PHY_N(0x057) /* RX filter 40 denominator 1 */ +#define B43_NPHY_RXF40_NUM10 B43_PHY_N(0x058) /* RX filter 40 numerator 10 */ +#define B43_NPHY_RXF40_NUM11 B43_PHY_N(0x059) /* RX filter 40 numerator 11 */ +#define B43_NPHY_RXF40_NUM12 B43_PHY_N(0x05A) /* RX filter 40 numerator 12 */ +#define B43_NPHY_RXF40_DENOM10 B43_PHY_N(0x05B) /* RX filter 40 denominator 10 */ +#define B43_NPHY_RXF40_DENOM11 B43_PHY_N(0x05C) /* RX filter 40 denominator 11 */ +#define B43_NPHY_PPROC_RSTLEN B43_PHY_N(0x060) /* Packet processing reset length */ +#define B43_NPHY_INITCARR_DLEN B43_PHY_N(0x061) /* Initial carrier detection length */ +#define B43_NPHY_CLIP1CARR_DLEN B43_PHY_N(0x062) /* Clip1 carrier detection length */ +#define B43_NPHY_CLIP2CARR_DLEN B43_PHY_N(0x063) /* Clip2 carrier detection length */ +#define B43_NPHY_INITGAIN_SLEN B43_PHY_N(0x064) /* Initial gain settle length */ +#define B43_NPHY_CLIP1GAIN_SLEN B43_PHY_N(0x065) /* Clip1 gain settle length */ +#define B43_NPHY_CLIP2GAIN_SLEN B43_PHY_N(0x066) /* Clip2 gain settle length */ +#define B43_NPHY_PACKGAIN_SLEN B43_PHY_N(0x067) /* Packet gain settle length */ +#define B43_NPHY_CARRSRC_TLEN B43_PHY_N(0x068) /* Carrier search timeout length */ +#define B43_NPHY_TISRC_TLEN B43_PHY_N(0x069) /* Timing search timeout length */ +#define B43_NPHY_ENDROP_TLEN B43_PHY_N(0x06A) /* Energy drop timeout length */ +#define B43_NPHY_CLIP1_NBDWELL_LEN B43_PHY_N(0x06B) /* Clip1 NB dwell length */ +#define B43_NPHY_CLIP2_NBDWELL_LEN B43_PHY_N(0x06C) /* Clip2 NB dwell length */ +#define B43_NPHY_W1CLIP1_DWELL_LEN B43_PHY_N(0x06D) /* W1 clip1 dwell length */ +#define B43_NPHY_W1CLIP2_DWELL_LEN B43_PHY_N(0x06E) /* W1 clip2 dwell length */ +#define B43_NPHY_W2CLIP1_DWELL_LEN B43_PHY_N(0x06F) /* W2 clip1 dwell length */ +#define B43_NPHY_PLOAD_CSENSE_EXTLEN B43_PHY_N(0x070) /* Payload carrier sense extension length */ +#define B43_NPHY_EDROP_CSENSE_EXTLEN B43_PHY_N(0x071) /* Energy drop carrier sense extension length */ +#define B43_NPHY_TABLE_ADDR B43_PHY_N(0x072) /* Table address */ +#define B43_NPHY_TABLE_DATALO B43_PHY_N(0x073) /* Table data low */ +#define B43_NPHY_TABLE_DATAHI B43_PHY_N(0x074) /* Table data high */ +#define B43_NPHY_WWISE_LENIDX B43_PHY_N(0x075) /* WWiSE length index */ +#define B43_NPHY_TGNSYNC_LENIDX B43_PHY_N(0x076) /* TGNsync length index */ +#define B43_NPHY_TXMACIF_HOLDOFF B43_PHY_N(0x077) /* TX MAC IF Hold off */ +#define B43_NPHY_RFCTL_CMD B43_PHY_N(0x078) /* RF control (command) */ +#define B43_NPHY_RFCTL_CMD_START 0x0001 /* Start sequence */ +#define B43_NPHY_RFCTL_CMD_RXTX 0x0002 /* RX/TX */ +#define B43_NPHY_RFCTL_CMD_CORESEL 0x0038 /* Core select */ +#define B43_NPHY_RFCTL_CMD_CORESEL_SHIFT 3 +#define B43_NPHY_RFCTL_CMD_PORFORCE 0x0040 /* POR force */ +#define B43_NPHY_RFCTL_CMD_OEPORFORCE 0x0080 /* OE POR force */ +#define B43_NPHY_RFCTL_CMD_RXEN 0x0100 /* RX enable */ +#define B43_NPHY_RFCTL_CMD_TXEN 0x0200 /* TX enable */ +#define B43_NPHY_RFCTL_CMD_CHIP0PU 0x0400 /* Chip0 PU */ +#define B43_NPHY_RFCTL_CMD_EN 0x0800 /* Radio enabled */ +#define B43_NPHY_RFCTL_CMD_SEQENCORE 0xF000 /* Seq en core */ +#define B43_NPHY_RFCTL_CMD_SEQENCORE_SHIFT 12 +#define B43_NPHY_RFCTL_RSSIO1 B43_PHY_N(0x07A) /* RF control (RSSI others 1) */ +#define B43_NPHY_RFCTL_RSSIO1_RXPD 0x0001 /* RX PD */ +#define B43_NPHY_RFCTL_RSSIO1_TXPD 0x0002 /* TX PD */ +#define B43_NPHY_RFCTL_RSSIO1_PAPD 0x0004 /* PA PD */ +#define B43_NPHY_RFCTL_RSSIO1_RSSICTL 0x0030 /* RSSI control */ +#define B43_NPHY_RFCTL_RSSIO1_LPFBW 0x00C0 /* LPF bandwidth */ +#define B43_NPHY_RFCTL_RSSIO1_HPFBWHI 0x0100 /* HPF bandwidth high */ +#define B43_NPHY_RFCTL_RSSIO1_HIQDISCO 0x0200 /* HIQ dis core */ +#define B43_NPHY_RFCTL_RXG1 B43_PHY_N(0x07B) /* RF control (RX gain 1) */ +#define B43_NPHY_RFCTL_TXG1 B43_PHY_N(0x07C) /* RF control (TX gain 1) */ +#define B43_NPHY_RFCTL_RSSIO2 B43_PHY_N(0x07D) /* RF control (RSSI others 2) */ +#define B43_NPHY_RFCTL_RSSIO2_RXPD 0x0001 /* RX PD */ +#define B43_NPHY_RFCTL_RSSIO2_TXPD 0x0002 /* TX PD */ +#define B43_NPHY_RFCTL_RSSIO2_PAPD 0x0004 /* PA PD */ +#define B43_NPHY_RFCTL_RSSIO2_RSSICTL 0x0030 /* RSSI control */ +#define B43_NPHY_RFCTL_RSSIO2_LPFBW 0x00C0 /* LPF bandwidth */ +#define B43_NPHY_RFCTL_RSSIO2_HPFBWHI 0x0100 /* HPF bandwidth high */ +#define B43_NPHY_RFCTL_RSSIO2_HIQDISCO 0x0200 /* HIQ dis core */ +#define B43_NPHY_RFCTL_RXG2 B43_PHY_N(0x07E) /* RF control (RX gain 2) */ +#define B43_NPHY_RFCTL_TXG2 B43_PHY_N(0x07F) /* RF control (TX gain 2) */ +#define B43_NPHY_RFCTL_RSSIO3 B43_PHY_N(0x080) /* RF control (RSSI others 3) */ +#define B43_NPHY_RFCTL_RSSIO3_RXPD 0x0001 /* RX PD */ +#define B43_NPHY_RFCTL_RSSIO3_TXPD 0x0002 /* TX PD */ +#define B43_NPHY_RFCTL_RSSIO3_PAPD 0x0004 /* PA PD */ +#define B43_NPHY_RFCTL_RSSIO3_RSSICTL 0x0030 /* RSSI control */ +#define B43_NPHY_RFCTL_RSSIO3_LPFBW 0x00C0 /* LPF bandwidth */ +#define B43_NPHY_RFCTL_RSSIO3_HPFBWHI 0x0100 /* HPF bandwidth high */ +#define B43_NPHY_RFCTL_RSSIO3_HIQDISCO 0x0200 /* HIQ dis core */ +#define B43_NPHY_RFCTL_RXG3 B43_PHY_N(0x081) /* RF control (RX gain 3) */ +#define B43_NPHY_RFCTL_TXG3 B43_PHY_N(0x082) /* RF control (TX gain 3) */ +#define B43_NPHY_RFCTL_RSSIO4 B43_PHY_N(0x083) /* RF control (RSSI others 4) */ +#define B43_NPHY_RFCTL_RSSIO4_RXPD 0x0001 /* RX PD */ +#define B43_NPHY_RFCTL_RSSIO4_TXPD 0x0002 /* TX PD */ +#define B43_NPHY_RFCTL_RSSIO4_PAPD 0x0004 /* PA PD */ +#define B43_NPHY_RFCTL_RSSIO4_RSSICTL 0x0030 /* RSSI control */ +#define B43_NPHY_RFCTL_RSSIO4_LPFBW 0x00C0 /* LPF bandwidth */ +#define B43_NPHY_RFCTL_RSSIO4_HPFBWHI 0x0100 /* HPF bandwidth high */ +#define B43_NPHY_RFCTL_RSSIO4_HIQDISCO 0x0200 /* HIQ dis core */ +#define B43_NPHY_RFCTL_RXG4 B43_PHY_N(0x084) /* RF control (RX gain 4) */ +#define B43_NPHY_RFCTL_TXG4 B43_PHY_N(0x085) /* RF control (TX gain 4) */ +#define B43_NPHY_C1_TXIQ_COMP_OFF B43_PHY_N(0x087) /* Core 1 TX I/Q comp offset */ +#define B43_NPHY_C2_TXIQ_COMP_OFF B43_PHY_N(0x088) /* Core 2 TX I/Q comp offset */ +#define B43_NPHY_C1_TXCTL B43_PHY_N(0x08B) /* Core 1 TX control */ +#define B43_NPHY_C2_TXCTL B43_PHY_N(0x08C) /* Core 2 TX control */ +#define B43_NPHY_AFECTL_OVER1 B43_PHY_N(0x08F) /* AFE control override 1 */ +#define B43_NPHY_SCRAM_SIGCTL B43_PHY_N(0x090) /* Scram signal control */ +#define B43_NPHY_SCRAM_SIGCTL_INITST 0x007F /* Initial state value */ +#define B43_NPHY_SCRAM_SIGCTL_INITST_SHIFT 0 +#define B43_NPHY_SCRAM_SIGCTL_SCM 0x0080 /* Scram control mode */ +#define B43_NPHY_SCRAM_SIGCTL_SICE 0x0100 /* Scram index control enable */ +#define B43_NPHY_SCRAM_SIGCTL_START 0xFE00 /* Scram start bit */ +#define B43_NPHY_SCRAM_SIGCTL_START_SHIFT 9 +#define B43_NPHY_RFCTL_INTC1 B43_PHY_N(0x091) /* RF control (intc 1) */ +#define B43_NPHY_RFCTL_INTC2 B43_PHY_N(0x092) /* RF control (intc 2) */ +#define B43_NPHY_RFCTL_INTC3 B43_PHY_N(0x093) /* RF control (intc 3) */ +#define B43_NPHY_RFCTL_INTC4 B43_PHY_N(0x094) /* RF control (intc 4) */ +#define B43_NPHY_NRDTO_WWISE B43_PHY_N(0x095) /* # datatones WWiSE */ +#define B43_NPHY_NRDTO_TGNSYNC B43_PHY_N(0x096) /* # datatones TGNsync */ +#define B43_NPHY_SIGFMOD_WWISE B43_PHY_N(0x097) /* Signal field mod WWiSE */ +#define B43_NPHY_LEG_SIGFMOD_11N B43_PHY_N(0x098) /* Legacy signal field mod 11n */ +#define B43_NPHY_HT_SIGFMOD_11N B43_PHY_N(0x099) /* HT signal field mod 11n */ +#define B43_NPHY_C1_RXIQ_COMPA0 B43_PHY_N(0x09A) /* Core 1 RX I/Q comp A0 */ +#define B43_NPHY_C1_RXIQ_COMPB0 B43_PHY_N(0x09B) /* Core 1 RX I/Q comp B0 */ +#define B43_NPHY_C2_RXIQ_COMPA1 B43_PHY_N(0x09C) /* Core 2 RX I/Q comp A1 */ +#define B43_NPHY_C2_RXIQ_COMPB1 B43_PHY_N(0x09D) /* Core 2 RX I/Q comp B1 */ +#define B43_NPHY_RXCTL B43_PHY_N(0x0A0) /* RX control */ +#define B43_NPHY_RXCTL_BSELU20 0x0010 /* Band select upper 20 */ +#define B43_NPHY_RXCTL_RIFSEN 0x0080 /* RIFS enable */ +#define B43_NPHY_RFSEQMODE B43_PHY_N(0x0A1) /* RF seq mode */ +#define B43_NPHY_RFSEQMODE_CAOVER 0x0001 /* Core active override */ +#define B43_NPHY_RFSEQMODE_TROVER 0x0002 /* Trigger override */ +#define B43_NPHY_RFSEQCA B43_PHY_N(0x0A2) /* RF seq core active */ +#define B43_NPHY_RFSEQCA_TXEN 0x000F /* TX enable */ +#define B43_NPHY_RFSEQCA_TXEN_SHIFT 0 +#define B43_NPHY_RFSEQCA_RXEN 0x00F0 /* RX enable */ +#define B43_NPHY_RFSEQCA_RXEN_SHIFT 4 +#define B43_NPHY_RFSEQCA_TXDIS 0x0F00 /* TX disable */ +#define B43_NPHY_RFSEQCA_TXDIS_SHIFT 8 +#define B43_NPHY_RFSEQCA_RXDIS 0xF000 /* RX disable */ +#define B43_NPHY_RFSEQCA_RXDIS_SHIFT 12 +#define B43_NPHY_RFSEQTR B43_PHY_N(0x0A3) /* RF seq trigger */ +#define B43_NPHY_RFSEQTR_RX2TX 0x0001 /* RX2TX */ +#define B43_NPHY_RFSEQTR_TX2RX 0x0002 /* TX2RX */ +#define B43_NPHY_RFSEQTR_UPGH 0x0004 /* Update gain H */ +#define B43_NPHY_RFSEQTR_UPGL 0x0008 /* Update gain L */ +#define B43_NPHY_RFSEQTR_UPGU 0x0010 /* Update gain U */ +#define B43_NPHY_RFSEQTR_RST2RX 0x0020 /* Reset to RX */ +#define B43_NPHY_RFSEQST B43_PHY_N(0x0A4) /* RF seq status. Values same as trigger. */ +#define B43_NPHY_AFECTL_OVER B43_PHY_N(0x0A5) /* AFE control override */ +#define B43_NPHY_AFECTL_C1 B43_PHY_N(0x0A6) /* AFE control core 1 */ +#define B43_NPHY_AFECTL_C2 B43_PHY_N(0x0A7) /* AFE control core 2 */ +#define B43_NPHY_AFECTL_C3 B43_PHY_N(0x0A8) /* AFE control core 3 */ +#define B43_NPHY_AFECTL_C4 B43_PHY_N(0x0A9) /* AFE control core 4 */ +#define B43_NPHY_AFECTL_DACGAIN1 B43_PHY_N(0x0AA) /* AFE control DAC gain 1 */ +#define B43_NPHY_AFECTL_DACGAIN2 B43_PHY_N(0x0AB) /* AFE control DAC gain 2 */ +#define B43_NPHY_AFECTL_DACGAIN3 B43_PHY_N(0x0AC) /* AFE control DAC gain 3 */ +#define B43_NPHY_AFECTL_DACGAIN4 B43_PHY_N(0x0AD) /* AFE control DAC gain 4 */ +#define B43_NPHY_STR_ADDR1 B43_PHY_N(0x0AE) /* STR address 1 */ +#define B43_NPHY_STR_ADDR2 B43_PHY_N(0x0AF) /* STR address 2 */ +#define B43_NPHY_CLASSCTL B43_PHY_N(0x0B0) /* Classifier control */ +#define B43_NPHY_CLASSCTL_CCKEN 0x0001 /* CCK enable */ +#define B43_NPHY_CLASSCTL_OFDMEN 0x0002 /* OFDM enable */ +#define B43_NPHY_CLASSCTL_WAITEDEN 0x0004 /* Waited enable */ +#define B43_NPHY_IQFLIP B43_PHY_N(0x0B1) /* I/Q flip */ +#define B43_NPHY_IQFLIP_ADC1 0x0001 /* ADC1 */ +#define B43_NPHY_IQFLIP_ADC2 0x0010 /* ADC2 */ +#define B43_NPHY_SISO_SNR_THRES B43_PHY_N(0x0B2) /* SISO SNR threshold */ +#define B43_NPHY_SIGMA_N_MULT B43_PHY_N(0x0B3) /* Sigma N multiplier */ +#define B43_NPHY_TXMACDELAY B43_PHY_N(0x0B4) /* TX MAC delay */ +#define B43_NPHY_TXFRAMEDELAY B43_PHY_N(0x0B5) /* TX frame delay */ +#define B43_NPHY_MLPARM B43_PHY_N(0x0B6) /* ML parameters */ +#define B43_NPHY_MLCTL B43_PHY_N(0x0B7) /* ML control */ +#define B43_NPHY_WWISE_20NCYCDAT B43_PHY_N(0x0B8) /* WWiSE 20 N cyc data */ +#define B43_NPHY_WWISE_40NCYCDAT B43_PHY_N(0x0B9) /* WWiSE 40 N cyc data */ +#define B43_NPHY_TGNSYNC_20NCYCDAT B43_PHY_N(0x0BA) /* TGNsync 20 N cyc data */ +#define B43_NPHY_TGNSYNC_40NCYCDAT B43_PHY_N(0x0BB) /* TGNsync 40 N cyc data */ +#define B43_NPHY_INITSWIZP B43_PHY_N(0x0BC) /* Initial swizzle pattern */ +#define B43_NPHY_TXTAILCNT B43_PHY_N(0x0BD) /* TX tail count value */ +#define B43_NPHY_BPHY_CTL1 B43_PHY_N(0x0BE) /* B PHY control 1 */ +#define B43_NPHY_BPHY_CTL2 B43_PHY_N(0x0BF) /* B PHY control 2 */ +#define B43_NPHY_BPHY_CTL2_LUT 0x001F /* LUT index */ +#define B43_NPHY_BPHY_CTL2_LUT_SHIFT 0 +#define B43_NPHY_BPHY_CTL2_MACDEL 0x7FE0 /* MAC delay */ +#define B43_NPHY_BPHY_CTL2_MACDEL_SHIFT 5 +#define B43_NPHY_IQLOCAL_CMD B43_PHY_N(0x0C0) /* I/Q LO cal command */ +#define B43_NPHY_IQLOCAL_CMD_EN 0x8000 +#define B43_NPHY_IQLOCAL_CMDNNUM B43_PHY_N(0x0C1) /* I/Q LO cal command N num */ +#define B43_NPHY_IQLOCAL_CMDGCTL B43_PHY_N(0x0C2) /* I/Q LO cal command G control */ +#define B43_NPHY_SAMP_CMD B43_PHY_N(0x0C3) /* Sample command */ +#define B43_NPHY_SAMP_CMD_STOP 0x0002 /* Stop */ +#define B43_NPHY_SAMP_LOOPCNT B43_PHY_N(0x0C4) /* Sample loop count */ +#define B43_NPHY_SAMP_WAITCNT B43_PHY_N(0x0C5) /* Sample wait count */ +#define B43_NPHY_SAMP_DEPCNT B43_PHY_N(0x0C6) /* Sample depth count */ +#define B43_NPHY_SAMP_STAT B43_PHY_N(0x0C7) /* Sample status */ +#define B43_NPHY_GPIO_LOOEN B43_PHY_N(0x0C8) /* GPIO low out enable */ +#define B43_NPHY_GPIO_HIOEN B43_PHY_N(0x0C9) /* GPIO high out enable */ +#define B43_NPHY_GPIO_SEL B43_PHY_N(0x0CA) /* GPIO select */ +#define B43_NPHY_GPIO_CLKCTL B43_PHY_N(0x0CB) /* GPIO clock control */ +#define B43_NPHY_TXF_20CO_AS0 B43_PHY_N(0x0CC) /* TX filter 20 coeff A stage 0 */ +#define B43_NPHY_TXF_20CO_AS1 B43_PHY_N(0x0CD) /* TX filter 20 coeff A stage 1 */ +#define B43_NPHY_TXF_20CO_AS2 B43_PHY_N(0x0CE) /* TX filter 20 coeff A stage 2 */ +#define B43_NPHY_TXF_20CO_B32S0 B43_PHY_N(0x0CF) /* TX filter 20 coeff B32 stage 0 */ +#define B43_NPHY_TXF_20CO_B1S0 B43_PHY_N(0x0D0) /* TX filter 20 coeff B1 stage 0 */ +#define B43_NPHY_TXF_20CO_B32S1 B43_PHY_N(0x0D1) /* TX filter 20 coeff B32 stage 1 */ +#define B43_NPHY_TXF_20CO_B1S1 B43_PHY_N(0x0D2) /* TX filter 20 coeff B1 stage 1 */ +#define B43_NPHY_TXF_20CO_B32S2 B43_PHY_N(0x0D3) /* TX filter 20 coeff B32 stage 2 */ +#define B43_NPHY_TXF_20CO_B1S2 B43_PHY_N(0x0D4) /* TX filter 20 coeff B1 stage 2 */ +#define B43_NPHY_SIGFLDTOL B43_PHY_N(0x0D5) /* Signal fld tolerance */ +#define B43_NPHY_TXSERFLD B43_PHY_N(0x0D6) /* TX service field */ +#define B43_NPHY_AFESEQ_RX2TX_PUD B43_PHY_N(0x0D7) /* AFE seq RX2TX power up/down delay */ +#define B43_NPHY_AFESEQ_TX2RX_PUD B43_PHY_N(0x0D8) /* AFE seq TX2RX power up/down delay */ +#define B43_NPHY_TGNSYNC_SCRAMI0 B43_PHY_N(0x0D9) /* TGNsync scram init 0 */ +#define B43_NPHY_TGNSYNC_SCRAMI1 B43_PHY_N(0x0DA) /* TGNsync scram init 1 */ +#define B43_NPHY_INITSWIZPATTLEG B43_PHY_N(0x0DB) /* Initial swizzle pattern leg */ +#define B43_NPHY_BPHY_CTL3 B43_PHY_N(0x0DC) /* B PHY control 3 */ +#define B43_NPHY_BPHY_CTL3_SCALE 0x00FF /* Scale */ +#define B43_NPHY_BPHY_CTL3_SCALE_SHIFT 0 +#define B43_NPHY_BPHY_CTL3_FSC 0xFF00 /* Frame start count value */ +#define B43_NPHY_BPHY_CTL3_FSC_SHIFT 8 +#define B43_NPHY_BPHY_CTL4 B43_PHY_N(0x0DD) /* B PHY control 4 */ +#define B43_NPHY_C1_TXBBMULT B43_PHY_N(0x0DE) /* Core 1 TX BB multiplier */ +#define B43_NPHY_C2_TXBBMULT B43_PHY_N(0x0DF) /* Core 2 TX BB multiplier */ +#define B43_NPHY_TXF_40CO_AS0 B43_PHY_N(0x0E1) /* TX filter 40 coeff A stage 0 */ +#define B43_NPHY_TXF_40CO_AS1 B43_PHY_N(0x0E2) /* TX filter 40 coeff A stage 1 */ +#define B43_NPHY_TXF_40CO_AS2 B43_PHY_N(0x0E3) /* TX filter 40 coeff A stage 2 */ +#define B43_NPHY_TXF_40CO_B32S0 B43_PHY_N(0x0E4) /* TX filter 40 coeff B32 stage 0 */ +#define B43_NPHY_TXF_40CO_B1S0 B43_PHY_N(0x0E5) /* TX filter 40 coeff B1 stage 0 */ +#define B43_NPHY_TXF_40CO_B32S1 B43_PHY_N(0x0E6) /* TX filter 40 coeff B32 stage 1 */ +#define B43_NPHY_TXF_40CO_B1S1 B43_PHY_N(0x0E7) /* TX filter 40 coeff B1 stage 1 */ +#define B43_NPHY_TXF_40CO_B32S2 B43_PHY_N(0x0E8) /* TX filter 40 coeff B32 stage 2 */ +#define B43_NPHY_TXF_40CO_B1S2 B43_PHY_N(0x0E9) /* TX filter 40 coeff B1 stage 2 */ +#define B43_NPHY_BIST_STAT2 B43_PHY_N(0x0EA) /* BIST status 2 */ +#define B43_NPHY_BIST_STAT3 B43_PHY_N(0x0EB) /* BIST status 3 */ +#define B43_NPHY_RFCTL_OVER B43_PHY_N(0x0EC) /* RF control override */ +#define B43_NPHY_MIMOCFG B43_PHY_N(0x0ED) /* MIMO config */ +#define B43_NPHY_MIMOCFG_GFMIX 0x0004 /* Greenfield or mixed mode */ +#define B43_NPHY_MIMOCFG_AUTO 0x0100 /* Greenfield/mixed mode auto */ +#define B43_NPHY_RADAR_BLNKCTL B43_PHY_N(0x0EE) /* Radar blank control */ +#define B43_NPHY_A0RADAR_FIFOCTL B43_PHY_N(0x0EF) /* Antenna 0 radar FIFO control */ +#define B43_NPHY_A1RADAR_FIFOCTL B43_PHY_N(0x0F0) /* Antenna 1 radar FIFO control */ +#define B43_NPHY_A0RADAR_FIFODAT B43_PHY_N(0x0F1) /* Antenna 0 radar FIFO data */ +#define B43_NPHY_A1RADAR_FIFODAT B43_PHY_N(0x0F2) /* Antenna 1 radar FIFO data */ +#define B43_NPHY_RADAR_THRES0 B43_PHY_N(0x0F3) /* Radar threshold 0 */ +#define B43_NPHY_RADAR_THRES1 B43_PHY_N(0x0F4) /* Radar threshold 1 */ +#define B43_NPHY_RADAR_THRES0R B43_PHY_N(0x0F5) /* Radar threshold 0R */ +#define B43_NPHY_RADAR_THRES1R B43_PHY_N(0x0F6) /* Radar threshold 1R */ +#define B43_NPHY_CSEN_20IN40_DLEN B43_PHY_N(0x0F7) /* Carrier sense 20 in 40 dwell length */ +#define B43_NPHY_RFCTL_LUT_TRSW_LO1 B43_PHY_N(0x0F8) /* RF control LUT TRSW lower 1 */ +#define B43_NPHY_RFCTL_LUT_TRSW_UP1 B43_PHY_N(0x0F9) /* RF control LUT TRSW upper 1 */ +#define B43_NPHY_RFCTL_LUT_TRSW_LO2 B43_PHY_N(0x0FA) /* RF control LUT TRSW lower 2 */ +#define B43_NPHY_RFCTL_LUT_TRSW_UP2 B43_PHY_N(0x0FB) /* RF control LUT TRSW upper 2 */ +#define B43_NPHY_RFCTL_LUT_TRSW_LO3 B43_PHY_N(0x0FC) /* RF control LUT TRSW lower 3 */ +#define B43_NPHY_RFCTL_LUT_TRSW_UP3 B43_PHY_N(0x0FD) /* RF control LUT TRSW upper 3 */ +#define B43_NPHY_RFCTL_LUT_TRSW_LO4 B43_PHY_N(0x0FE) /* RF control LUT TRSW lower 4 */ +#define B43_NPHY_RFCTL_LUT_TRSW_UP4 B43_PHY_N(0x0FF) /* RF control LUT TRSW upper 4 */ +#define B43_NPHY_RFCTL_LUT_LNAPA1 B43_PHY_N(0x100) /* RF control LUT LNA PA 1 */ +#define B43_NPHY_RFCTL_LUT_LNAPA2 B43_PHY_N(0x101) /* RF control LUT LNA PA 2 */ +#define B43_NPHY_RFCTL_LUT_LNAPA3 B43_PHY_N(0x102) /* RF control LUT LNA PA 3 */ +#define B43_NPHY_RFCTL_LUT_LNAPA4 B43_PHY_N(0x103) /* RF control LUT LNA PA 4 */ +#define B43_NPHY_TGNSYNC_CRCM0 B43_PHY_N(0x104) /* TGNsync CRC mask 0 */ +#define B43_NPHY_TGNSYNC_CRCM1 B43_PHY_N(0x105) /* TGNsync CRC mask 1 */ +#define B43_NPHY_TGNSYNC_CRCM2 B43_PHY_N(0x106) /* TGNsync CRC mask 2 */ +#define B43_NPHY_TGNSYNC_CRCM3 B43_PHY_N(0x107) /* TGNsync CRC mask 3 */ +#define B43_NPHY_TGNSYNC_CRCM4 B43_PHY_N(0x108) /* TGNsync CRC mask 4 */ +#define B43_NPHY_CRCPOLY B43_PHY_N(0x109) /* CRC polynomial */ +#define B43_NPHY_SIGCNT B43_PHY_N(0x10A) /* # sig count */ +#define B43_NPHY_SIGSTARTBIT_CTL B43_PHY_N(0x10B) /* Sig start bit control */ +#define B43_NPHY_CRCPOLY_ORDER B43_PHY_N(0x10C) /* CRC polynomial order */ +#define B43_NPHY_RFCTL_CST0 B43_PHY_N(0x10D) /* RF control core swap table 0 */ +#define B43_NPHY_RFCTL_CST1 B43_PHY_N(0x10E) /* RF control core swap table 1 */ +#define B43_NPHY_RFCTL_CST2O B43_PHY_N(0x10F) /* RF control core swap table 2 + others */ +#define B43_NPHY_BPHY_CTL5 B43_PHY_N(0x111) /* B PHY control 5 */ +#define B43_NPHY_RFSEQ_LPFBW B43_PHY_N(0x112) /* RF seq LPF bandwidth */ +#define B43_NPHY_TSSIBIAS1 B43_PHY_N(0x114) /* TSSI bias val 1 */ +#define B43_NPHY_TSSIBIAS2 B43_PHY_N(0x115) /* TSSI bias val 2 */ +#define B43_NPHY_TSSIBIAS_BIAS 0x00FF /* Bias */ +#define B43_NPHY_TSSIBIAS_BIAS_SHIFT 0 +#define B43_NPHY_TSSIBIAS_VAL 0xFF00 /* Value */ +#define B43_NPHY_TSSIBIAS_VAL_SHIFT 8 +#define B43_NPHY_ESTPWR1 B43_PHY_N(0x118) /* Estimated power 1 */ +#define B43_NPHY_ESTPWR2 B43_PHY_N(0x119) /* Estimated power 2 */ +#define B43_NPHY_ESTPWR_PWR 0x00FF /* Estimated power */ +#define B43_NPHY_ESTPWR_PWR_SHIFT 0 +#define B43_NPHY_ESTPWR_VALID 0x0100 /* Estimated power valid */ +#define B43_NPHY_TSSI_MAXTXFDT B43_PHY_N(0x11C) /* TSSI max TX frame delay time */ +#define B43_NPHY_TSSI_MAXTXFDT_VAL 0x00FF /* max TX frame delay time */ +#define B43_NPHY_TSSI_MAXTXFDT_VAL_SHIFT 0 +#define B43_NPHY_TSSI_MAXTDT B43_PHY_N(0x11D) /* TSSI max TSSI delay time */ +#define B43_NPHY_TSSI_MAXTDT_VAL 0x00FF /* max TSSI delay time */ +#define B43_NPHY_TSSI_MAXTDT_VAL_SHIFT 0 +#define B43_NPHY_ITSSI1 B43_PHY_N(0x11E) /* TSSI idle 1 */ +#define B43_NPHY_ITSSI2 B43_PHY_N(0x11F) /* TSSI idle 2 */ +#define B43_NPHY_ITSSI_VAL 0x00FF /* Idle TSSI */ +#define B43_NPHY_ITSSI_VAL_SHIFT 0 +#define B43_NPHY_TSSIMODE B43_PHY_N(0x122) /* TSSI mode */ +#define B43_NPHY_TSSIMODE_EN 0x0001 /* TSSI enable */ +#define B43_NPHY_TSSIMODE_PDEN 0x0002 /* Power det enable */ +#define B43_NPHY_RXMACIFM B43_PHY_N(0x123) /* RX Macif mode */ +#define B43_NPHY_CRSIT_COCNT_LO B43_PHY_N(0x124) /* CRS idle time CRS-on count (low) */ +#define B43_NPHY_CRSIT_COCNT_HI B43_PHY_N(0x125) /* CRS idle time CRS-on count (high) */ +#define B43_NPHY_CRSIT_MTCNT_LO B43_PHY_N(0x126) /* CRS idle time measure time count (low) */ +#define B43_NPHY_CRSIT_MTCNT_HI B43_PHY_N(0x127) /* CRS idle time measure time count (high) */ +#define B43_NPHY_SAMTWC B43_PHY_N(0x128) /* Sample tail wait count */ +#define B43_NPHY_IQEST_CMD B43_PHY_N(0x129) /* I/Q estimate command */ +#define B43_NPHY_IQEST_CMD_START 0x0001 /* Start */ +#define B43_NPHY_IQEST_CMD_MODE 0x0002 /* Mode */ +#define B43_NPHY_IQEST_WT B43_PHY_N(0x12A) /* I/Q estimate wait time */ +#define B43_NPHY_IQEST_WT_VAL 0x00FF /* Wait time */ +#define B43_NPHY_IQEST_WT_VAL_SHIFT 0 +#define B43_NPHY_IQEST_SAMCNT B43_PHY_N(0x12B) /* I/Q estimate sample count */ +#define B43_NPHY_IQEST_IQACC_LO0 B43_PHY_N(0x12C) /* I/Q estimate I/Q acc lo 0 */ +#define B43_NPHY_IQEST_IQACC_HI0 B43_PHY_N(0x12D) /* I/Q estimate I/Q acc hi 0 */ +#define B43_NPHY_IQEST_IPACC_LO0 B43_PHY_N(0x12E) /* I/Q estimate I power acc lo 0 */ +#define B43_NPHY_IQEST_IPACC_HI0 B43_PHY_N(0x12F) /* I/Q estimate I power acc hi 0 */ +#define B43_NPHY_IQEST_QPACC_LO0 B43_PHY_N(0x130) /* I/Q estimate Q power acc lo 0 */ +#define B43_NPHY_IQEST_QPACC_HI0 B43_PHY_N(0x131) /* I/Q estimate Q power acc hi 0 */ +#define B43_NPHY_IQEST_IQACC_LO1 B43_PHY_N(0x134) /* I/Q estimate I/Q acc lo 1 */ +#define B43_NPHY_IQEST_IQACC_HI1 B43_PHY_N(0x135) /* I/Q estimate I/Q acc hi 1 */ +#define B43_NPHY_IQEST_IPACC_LO1 B43_PHY_N(0x136) /* I/Q estimate I power acc lo 1 */ +#define B43_NPHY_IQEST_IPACC_HI1 B43_PHY_N(0x137) /* I/Q estimate I power acc hi 1 */ +#define B43_NPHY_IQEST_QPACC_LO1 B43_PHY_N(0x138) /* I/Q estimate Q power acc lo 1 */ +#define B43_NPHY_IQEST_QPACC_HI1 B43_PHY_N(0x139) /* I/Q estimate Q power acc hi 1 */ +#define B43_NPHY_MIMO_CRSTXEXT B43_PHY_N(0x13A) /* MIMO PHY CRS TX extension */ +#define B43_NPHY_PWRDET1 B43_PHY_N(0x13B) /* Power det 1 */ +#define B43_NPHY_PWRDET2 B43_PHY_N(0x13C) /* Power det 2 */ +#define B43_NPHY_MAXRSSI_DTIME B43_PHY_N(0x13F) /* RSSI max RSSI delay time */ +#define B43_NPHY_PIL_DW0 B43_PHY_N(0x141) /* Pilot data weight 0 */ +#define B43_NPHY_PIL_DW1 B43_PHY_N(0x142) /* Pilot data weight 1 */ +#define B43_NPHY_PIL_DW2 B43_PHY_N(0x143) /* Pilot data weight 2 */ +#define B43_NPHY_PIL_DW_BPSK 0x000F /* BPSK */ +#define B43_NPHY_PIL_DW_BPSK_SHIFT 0 +#define B43_NPHY_PIL_DW_QPSK 0x00F0 /* QPSK */ +#define B43_NPHY_PIL_DW_QPSK_SHIFT 4 +#define B43_NPHY_PIL_DW_16QAM 0x0F00 /* 16-QAM */ +#define B43_NPHY_PIL_DW_16QAM_SHIFT 8 +#define B43_NPHY_PIL_DW_64QAM 0xF000 /* 64-QAM */ +#define B43_NPHY_PIL_DW_64QAM_SHIFT 12 +#define B43_NPHY_FMDEM_CFG B43_PHY_N(0x144) /* FM demodulation config */ +#define B43_NPHY_PHASETR_A0 B43_PHY_N(0x145) /* Phase track alpha 0 */ +#define B43_NPHY_PHASETR_A1 B43_PHY_N(0x146) /* Phase track alpha 1 */ +#define B43_NPHY_PHASETR_A2 B43_PHY_N(0x147) /* Phase track alpha 2 */ +#define B43_NPHY_PHASETR_B0 B43_PHY_N(0x148) /* Phase track beta 0 */ +#define B43_NPHY_PHASETR_B1 B43_PHY_N(0x149) /* Phase track beta 1 */ +#define B43_NPHY_PHASETR_B2 B43_PHY_N(0x14A) /* Phase track beta 2 */ +#define B43_NPHY_PHASETR_CHG0 B43_PHY_N(0x14B) /* Phase track change 0 */ +#define B43_NPHY_PHASETR_CHG1 B43_PHY_N(0x14C) /* Phase track change 1 */ +#define B43_NPHY_PHASETW_OFF B43_PHY_N(0x14D) /* Phase track offset */ +#define B43_NPHY_RFCTL_DBG B43_PHY_N(0x14E) /* RF control debug */ +#define B43_NPHY_CCK_SHIFTB_REF B43_PHY_N(0x150) /* CCK shiftbits reference var */ +#define B43_NPHY_OVER_DGAIN0 B43_PHY_N(0x152) /* Override digital gain 0 */ +#define B43_NPHY_OVER_DGAIN1 B43_PHY_N(0x153) /* Override digital gain 1 */ +#define B43_NPHY_OVER_DGAIN_FDGV 0x0007 /* Force digital gain value */ +#define B43_NPHY_OVER_DGAIN_FDGV_SHIFT 0 +#define B43_NPHY_OVER_DGAIN_FDGEN 0x0008 /* Force digital gain enable */ +#define B43_NPHY_OVER_DGAIN_CCKDGECV 0xFF00 /* CCK digital gain enable count value */ +#define B43_NPHY_OVER_DGAIN_CCKDGECV_SHIFT 8 +#define B43_NPHY_BIST_STAT4 B43_PHY_N(0x156) /* BIST status 4 */ +#define B43_NPHY_RADAR_MAL B43_PHY_N(0x157) /* Radar MA length */ +#define B43_NPHY_RADAR_SRCCTL B43_PHY_N(0x158) /* Radar search control */ +#define B43_NPHY_VLD_DTSIG B43_PHY_N(0x159) /* VLD data tones sig */ +#define B43_NPHY_VLD_DTDAT B43_PHY_N(0x15A) /* VLD data tones data */ +#define B43_NPHY_C1_BPHY_RXIQCA0 B43_PHY_N(0x15B) /* Core 1 B PHY RX I/Q comp A0 */ +#define B43_NPHY_C1_BPHY_RXIQCB0 B43_PHY_N(0x15C) /* Core 1 B PHY RX I/Q comp B0 */ +#define B43_NPHY_C2_BPHY_RXIQCA1 B43_PHY_N(0x15D) /* Core 2 B PHY RX I/Q comp A1 */ +#define B43_NPHY_C2_BPHY_RXIQCB1 B43_PHY_N(0x15E) /* Core 2 B PHY RX I/Q comp B1 */ +#define B43_NPHY_FREQGAIN0 B43_PHY_N(0x160) /* Frequency gain 0 */ +#define B43_NPHY_FREQGAIN1 B43_PHY_N(0x161) /* Frequency gain 1 */ +#define B43_NPHY_FREQGAIN2 B43_PHY_N(0x162) /* Frequency gain 2 */ +#define B43_NPHY_FREQGAIN3 B43_PHY_N(0x163) /* Frequency gain 3 */ +#define B43_NPHY_FREQGAIN4 B43_PHY_N(0x164) /* Frequency gain 4 */ +#define B43_NPHY_FREQGAIN5 B43_PHY_N(0x165) /* Frequency gain 5 */ +#define B43_NPHY_FREQGAIN6 B43_PHY_N(0x166) /* Frequency gain 6 */ +#define B43_NPHY_FREQGAIN7 B43_PHY_N(0x167) /* Frequency gain 7 */ +#define B43_NPHY_FREQGAIN_BYPASS B43_PHY_N(0x168) /* Frequency gain bypass */ +#define B43_NPHY_TRLOSS B43_PHY_N(0x169) /* TR loss value */ +#define B43_NPHY_C1_ADCCLIP B43_PHY_N(0x16A) /* Core 1 ADC clip */ +#define B43_NPHY_C2_ADCCLIP B43_PHY_N(0x16B) /* Core 2 ADC clip */ +#define B43_NPHY_LTRN_OFFGAIN B43_PHY_N(0x16F) /* LTRN offset gain */ +#define B43_NPHY_LTRN_OFF B43_PHY_N(0x170) /* LTRN offset */ +#define B43_NPHY_NRDATAT_WWISE20SIG B43_PHY_N(0x171) /* # data tones WWiSE 20 sig */ +#define B43_NPHY_NRDATAT_WWISE40SIG B43_PHY_N(0x172) /* # data tones WWiSE 40 sig */ +#define B43_NPHY_NRDATAT_TGNSYNC20SIG B43_PHY_N(0x173) /* # data tones TGNsync 20 sig */ +#define B43_NPHY_NRDATAT_TGNSYNC40SIG B43_PHY_N(0x174) /* # data tones TGNsync 40 sig */ +#define B43_NPHY_WWISE_CRCM0 B43_PHY_N(0x175) /* WWiSE CRC mask 0 */ +#define B43_NPHY_WWISE_CRCM1 B43_PHY_N(0x176) /* WWiSE CRC mask 1 */ +#define B43_NPHY_WWISE_CRCM2 B43_PHY_N(0x177) /* WWiSE CRC mask 2 */ +#define B43_NPHY_WWISE_CRCM3 B43_PHY_N(0x178) /* WWiSE CRC mask 3 */ +#define B43_NPHY_WWISE_CRCM4 B43_PHY_N(0x179) /* WWiSE CRC mask 4 */ +#define B43_NPHY_CHANEST_CDDSH B43_PHY_N(0x17A) /* Channel estimate CDD shift */ +#define B43_NPHY_HTAGC_WCNT B43_PHY_N(0x17B) /* HT ADC wait counters */ +#define B43_NPHY_SQPARM B43_PHY_N(0x17C) /* SQ params */ +#define B43_NPHY_MCSDUP6M B43_PHY_N(0x17D) /* MCS dup 6M */ +#define B43_NPHY_NDATAT_DUP40 B43_PHY_N(0x17E) /* # data tones dup 40 */ +#define B43_NPHY_DUP40_TGNSYNC_CYCD B43_PHY_N(0x17F) /* Dup40 TGNsync cycle data */ +#define B43_NPHY_DUP40_GFBL B43_PHY_N(0x180) /* Dup40 GF format BL address */ +#define B43_NPHY_DUP40_BL B43_PHY_N(0x181) /* Dup40 format BL address */ +#define B43_NPHY_LEGDUP_FTA B43_PHY_N(0x182) /* Legacy dup frm table address */ +#define B43_NPHY_PACPROC_DBG B43_PHY_N(0x183) /* Packet processing debug */ +#define B43_NPHY_PIL_CYC1 B43_PHY_N(0x184) /* Pilot cycle counter 1 */ +#define B43_NPHY_PIL_CYC2 B43_PHY_N(0x185) /* Pilot cycle counter 2 */ +#define B43_NPHY_TXF_20CO_S0A1 B43_PHY_N(0x186) /* TX filter 20 coeff stage 0 A1 */ +#define B43_NPHY_TXF_20CO_S0A2 B43_PHY_N(0x187) /* TX filter 20 coeff stage 0 A2 */ +#define B43_NPHY_TXF_20CO_S1A1 B43_PHY_N(0x188) /* TX filter 20 coeff stage 1 A1 */ +#define B43_NPHY_TXF_20CO_S1A2 B43_PHY_N(0x189) /* TX filter 20 coeff stage 1 A2 */ +#define B43_NPHY_TXF_20CO_S2A1 B43_PHY_N(0x18A) /* TX filter 20 coeff stage 2 A1 */ +#define B43_NPHY_TXF_20CO_S2A2 B43_PHY_N(0x18B) /* TX filter 20 coeff stage 2 A2 */ +#define B43_NPHY_TXF_20CO_S0B1 B43_PHY_N(0x18C) /* TX filter 20 coeff stage 0 B1 */ +#define B43_NPHY_TXF_20CO_S0B2 B43_PHY_N(0x18D) /* TX filter 20 coeff stage 0 B2 */ +#define B43_NPHY_TXF_20CO_S0B3 B43_PHY_N(0x18E) /* TX filter 20 coeff stage 0 B3 */ +#define B43_NPHY_TXF_20CO_S1B1 B43_PHY_N(0x18F) /* TX filter 20 coeff stage 1 B1 */ +#define B43_NPHY_TXF_20CO_S1B2 B43_PHY_N(0x190) /* TX filter 20 coeff stage 1 B2 */ +#define B43_NPHY_TXF_20CO_S1B3 B43_PHY_N(0x191) /* TX filter 20 coeff stage 1 B3 */ +#define B43_NPHY_TXF_20CO_S2B1 B43_PHY_N(0x192) /* TX filter 20 coeff stage 2 B1 */ +#define B43_NPHY_TXF_20CO_S2B2 B43_PHY_N(0x193) /* TX filter 20 coeff stage 2 B2 */ +#define B43_NPHY_TXF_20CO_S2B3 B43_PHY_N(0x194) /* TX filter 20 coeff stage 2 B3 */ +#define B43_NPHY_TXF_40CO_S0A1 B43_PHY_N(0x195) /* TX filter 40 coeff stage 0 A1 */ +#define B43_NPHY_TXF_40CO_S0A2 B43_PHY_N(0x196) /* TX filter 40 coeff stage 0 A2 */ +#define B43_NPHY_TXF_40CO_S1A1 B43_PHY_N(0x197) /* TX filter 40 coeff stage 1 A1 */ +#define B43_NPHY_TXF_40CO_S1A2 B43_PHY_N(0x198) /* TX filter 40 coeff stage 1 A2 */ +#define B43_NPHY_TXF_40CO_S2A1 B43_PHY_N(0x199) /* TX filter 40 coeff stage 2 A1 */ +#define B43_NPHY_TXF_40CO_S2A2 B43_PHY_N(0x19A) /* TX filter 40 coeff stage 2 A2 */ +#define B43_NPHY_TXF_40CO_S0B1 B43_PHY_N(0x19B) /* TX filter 40 coeff stage 0 B1 */ +#define B43_NPHY_TXF_40CO_S0B2 B43_PHY_N(0x19C) /* TX filter 40 coeff stage 0 B2 */ +#define B43_NPHY_TXF_40CO_S0B3 B43_PHY_N(0x19D) /* TX filter 40 coeff stage 0 B3 */ +#define B43_NPHY_TXF_40CO_S1B1 B43_PHY_N(0x19E) /* TX filter 40 coeff stage 1 B1 */ +#define B43_NPHY_TXF_40CO_S1B2 B43_PHY_N(0x19F) /* TX filter 40 coeff stage 1 B2 */ +#define B43_NPHY_TXF_40CO_S1B3 B43_PHY_N(0x1A0) /* TX filter 40 coeff stage 1 B3 */ +#define B43_NPHY_TXF_40CO_S2B1 B43_PHY_N(0x1A1) /* TX filter 40 coeff stage 2 B1 */ +#define B43_NPHY_TXF_40CO_S2B2 B43_PHY_N(0x1A2) /* TX filter 40 coeff stage 2 B2 */ +#define B43_NPHY_TXF_40CO_S2B3 B43_PHY_N(0x1A3) /* TX filter 40 coeff stage 2 B3 */ +#define B43_NPHY_RSSIMC_0I_RSSI_X B43_PHY_N(0x1A4) /* RSSI multiplication coefficient 0 I RSSI X */ +#define B43_NPHY_RSSIMC_0I_RSSI_Y B43_PHY_N(0x1A5) /* RSSI multiplication coefficient 0 I RSSI Y */ +#define B43_NPHY_RSSIMC_0I_RSSI_Z B43_PHY_N(0x1A6) /* RSSI multiplication coefficient 0 I RSSI Z */ +#define B43_NPHY_RSSIMC_0I_TBD B43_PHY_N(0x1A7) /* RSSI multiplication coefficient 0 I TBD */ +#define B43_NPHY_RSSIMC_0I_PWRDET B43_PHY_N(0x1A8) /* RSSI multiplication coefficient 0 I power det */ +#define B43_NPHY_RSSIMC_0I_TSSI B43_PHY_N(0x1A9) /* RSSI multiplication coefficient 0 I TSSI */ +#define B43_NPHY_RSSIMC_0Q_RSSI_X B43_PHY_N(0x1AA) /* RSSI multiplication coefficient 0 Q RSSI X */ +#define B43_NPHY_RSSIMC_0Q_RSSI_Y B43_PHY_N(0x1AB) /* RSSI multiplication coefficient 0 Q RSSI Y */ +#define B43_NPHY_RSSIMC_0Q_RSSI_Z B43_PHY_N(0x1AC) /* RSSI multiplication coefficient 0 Q RSSI Z */ +#define B43_NPHY_RSSIMC_0Q_TBD B43_PHY_N(0x1AD) /* RSSI multiplication coefficient 0 Q TBD */ +#define B43_NPHY_RSSIMC_0Q_PWRDET B43_PHY_N(0x1AE) /* RSSI multiplication coefficient 0 Q power det */ +#define B43_NPHY_RSSIMC_0Q_TSSI B43_PHY_N(0x1AF) /* RSSI multiplication coefficient 0 Q TSSI */ +#define B43_NPHY_RSSIMC_1I_RSSI_X B43_PHY_N(0x1B0) /* RSSI multiplication coefficient 1 I RSSI X */ +#define B43_NPHY_RSSIMC_1I_RSSI_Y B43_PHY_N(0x1B1) /* RSSI multiplication coefficient 1 I RSSI Y */ +#define B43_NPHY_RSSIMC_1I_RSSI_Z B43_PHY_N(0x1B2) /* RSSI multiplication coefficient 1 I RSSI Z */ +#define B43_NPHY_RSSIMC_1I_TBD B43_PHY_N(0x1B3) /* RSSI multiplication coefficient 1 I TBD */ +#define B43_NPHY_RSSIMC_1I_PWRDET B43_PHY_N(0x1B4) /* RSSI multiplication coefficient 1 I power det */ +#define B43_NPHY_RSSIMC_1I_TSSI B43_PHY_N(0x1B5) /* RSSI multiplication coefficient 1 I TSSI */ +#define B43_NPHY_RSSIMC_1Q_RSSI_X B43_PHY_N(0x1B6) /* RSSI multiplication coefficient 1 Q RSSI X */ +#define B43_NPHY_RSSIMC_1Q_RSSI_Y B43_PHY_N(0x1B7) /* RSSI multiplication coefficient 1 Q RSSI Y */ +#define B43_NPHY_RSSIMC_1Q_RSSI_Z B43_PHY_N(0x1B8) /* RSSI multiplication coefficient 1 Q RSSI Z */ +#define B43_NPHY_RSSIMC_1Q_TBD B43_PHY_N(0x1B9) /* RSSI multiplication coefficient 1 Q TBD */ +#define B43_NPHY_RSSIMC_1Q_PWRDET B43_PHY_N(0x1BA) /* RSSI multiplication coefficient 1 Q power det */ +#define B43_NPHY_RSSIMC_1Q_TSSI B43_PHY_N(0x1BB) /* RSSI multiplication coefficient 1 Q TSSI */ +#define B43_NPHY_SAMC_WCNT B43_PHY_N(0x1BC) /* Sample collect wait counter */ +#define B43_NPHY_PTHROUGH_CNT B43_PHY_N(0x1BD) /* Pass-through counter */ +#define B43_NPHY_LTRN_OFF_G20L B43_PHY_N(0x1C4) /* LTRN offset gain 20L */ +#define B43_NPHY_LTRN_OFF_20L B43_PHY_N(0x1C5) /* LTRN offset 20L */ +#define B43_NPHY_LTRN_OFF_G20U B43_PHY_N(0x1C6) /* LTRN offset gain 20U */ +#define B43_NPHY_LTRN_OFF_20U B43_PHY_N(0x1C7) /* LTRN offset 20U */ +#define B43_NPHY_DSSSCCK_GAINSL B43_PHY_N(0x1C8) /* DSSS/CCK gain settle length */ +#define B43_NPHY_GPIO_LOOUT B43_PHY_N(0x1C9) /* GPIO low out */ +#define B43_NPHY_GPIO_HIOUT B43_PHY_N(0x1CA) /* GPIO high out */ +#define B43_NPHY_CRS_CHECK B43_PHY_N(0x1CB) /* CRS check */ +#define B43_NPHY_ML_LOGSS_RAT B43_PHY_N(0x1CC) /* ML/logss ratio */ +#define B43_NPHY_DUPSCALE B43_PHY_N(0x1CD) /* Dup scale */ +#define B43_NPHY_BW1A B43_PHY_N(0x1CE) /* BW 1A */ +#define B43_NPHY_BW2 B43_PHY_N(0x1CF) /* BW 2 */ +#define B43_NPHY_BW3 B43_PHY_N(0x1D0) /* BW 3 */ +#define B43_NPHY_BW4 B43_PHY_N(0x1D1) /* BW 4 */ +#define B43_NPHY_BW5 B43_PHY_N(0x1D2) /* BW 5 */ +#define B43_NPHY_BW6 B43_PHY_N(0x1D3) /* BW 6 */ +#define B43_NPHY_COALEN0 B43_PHY_N(0x1D4) /* Coarse length 0 */ +#define B43_NPHY_COALEN1 B43_PHY_N(0x1D5) /* Coarse length 1 */ +#define B43_NPHY_CRSTHRES_1U B43_PHY_N(0x1D6) /* CRS threshold 1 U */ +#define B43_NPHY_CRSTHRES_2U B43_PHY_N(0x1D7) /* CRS threshold 2 U */ +#define B43_NPHY_CRSTHRES_3U B43_PHY_N(0x1D8) /* CRS threshold 3 U */ +#define B43_NPHY_CRSCTL_U B43_PHY_N(0x1D9) /* CRS control U */ +#define B43_NPHY_CRSTHRES_1L B43_PHY_N(0x1DA) /* CRS threshold 1 L */ +#define B43_NPHY_CRSTHRES_2L B43_PHY_N(0x1DB) /* CRS threshold 2 L */ +#define B43_NPHY_CRSTHRES_3L B43_PHY_N(0x1DC) /* CRS threshold 3 L */ +#define B43_NPHY_CRSCTL_L B43_PHY_N(0x1DD) /* CRS control L */ +#define B43_NPHY_STRA_1U B43_PHY_N(0x1DE) /* STR address 1 U */ +#define B43_NPHY_STRA_2U B43_PHY_N(0x1DF) /* STR address 2 U */ +#define B43_NPHY_STRA_1L B43_PHY_N(0x1E0) /* STR address 1 L */ +#define B43_NPHY_STRA_2L B43_PHY_N(0x1E1) /* STR address 2 L */ +#define B43_NPHY_CRSCHECK1 B43_PHY_N(0x1E2) /* CRS check 1 */ +#define B43_NPHY_CRSCHECK2 B43_PHY_N(0x1E3) /* CRS check 2 */ +#define B43_NPHY_CRSCHECK3 B43_PHY_N(0x1E4) /* CRS check 3 */ +#define B43_NPHY_JMPSTP0 B43_PHY_N(0x1E5) /* Jump step 0 */ +#define B43_NPHY_JMPSTP1 B43_PHY_N(0x1E6) /* Jump step 1 */ +#define B43_NPHY_TXPCTL_CMD B43_PHY_N(0x1E7) /* TX power control command */ +#define B43_NPHY_TXPCTL_CMD_INIT 0x007F /* Init */ +#define B43_NPHY_TXPCTL_CMD_INIT_SHIFT 0 +#define B43_NPHY_TXPCTL_CMD_COEFF 0x2000 /* Power control coefficients */ +#define B43_NPHY_TXPCTL_CMD_HWPCTLEN 0x4000 /* Hardware TX power control enable */ +#define B43_NPHY_TXPCTL_CMD_PCTLEN 0x8000 /* TX power control enable */ +#define B43_NPHY_TXPCTL_N B43_PHY_N(0x1E8) /* TX power control N num */ +#define B43_NPHY_TXPCTL_N_TSSID 0x00FF /* N TSSI delay */ +#define B43_NPHY_TXPCTL_N_TSSID_SHIFT 0 +#define B43_NPHY_TXPCTL_N_NPTIL2 0x0700 /* N PT integer log2 */ +#define B43_NPHY_TXPCTL_N_NPTIL2_SHIFT 8 +#define B43_NPHY_TXPCTL_ITSSI B43_PHY_N(0x1E9) /* TX power control idle TSSI */ +#define B43_NPHY_TXPCTL_ITSSI_0 0x003F /* Idle TSSI 0 */ +#define B43_NPHY_TXPCTL_ITSSI_0_SHIFT 0 +#define B43_NPHY_TXPCTL_ITSSI_1 0x3F00 /* Idle TSSI 1 */ +#define B43_NPHY_TXPCTL_ITSSI_1_SHIFT 8 +#define B43_NPHY_TXPCTL_ITSSI_BINF 0x8000 /* Raw TSSI offset bin format */ +#define B43_NPHY_TXPCTL_TPWR B43_PHY_N(0x1EA) /* TX power control target power */ +#define B43_NPHY_TXPCTL_TPWR_0 0x00FF /* Power 0 */ +#define B43_NPHY_TXPCTL_TPWR_0_SHIFT 0 +#define B43_NPHY_TXPCTL_TPWR_1 0xFF00 /* Power 1 */ +#define B43_NPHY_TXPCTL_TPWR_1_SHIFT 8 +#define B43_NPHY_TXPCTL_BIDX B43_PHY_N(0x1EB) /* TX power control base index */ +#define B43_NPHY_TXPCTL_BIDX_0 0x007F /* uC base index 0 */ +#define B43_NPHY_TXPCTL_BIDX_0_SHIFT 0 +#define B43_NPHY_TXPCTL_BIDX_1 0x7F00 /* uC base index 1 */ +#define B43_NPHY_TXPCTL_BIDX_1_SHIFT 8 +#define B43_NPHY_TXPCTL_BIDX_LOAD 0x8000 /* Load base index */ +#define B43_NPHY_TXPCTL_PIDX B43_PHY_N(0x1EC) /* TX power control power index */ +#define B43_NPHY_TXPCTL_PIDX_0 0x007F /* uC power index 0 */ +#define B43_NPHY_TXPCTL_PIDX_0_SHIFT 0 +#define B43_NPHY_TXPCTL_PIDX_1 0x7F00 /* uC power index 1 */ +#define B43_NPHY_TXPCTL_PIDX_1_SHIFT 8 +#define B43_NPHY_C1_TXPCTL_STAT B43_PHY_N(0x1ED) /* Core 1 TX power control status */ +#define B43_NPHY_C2_TXPCTL_STAT B43_PHY_N(0x1EE) /* Core 2 TX power control status */ +#define B43_NPHY_TXPCTL_STAT_EST 0x00FF /* Estimated power */ +#define B43_NPHY_TXPCTL_STAT_EST_SHIFT 0 +#define B43_NPHY_TXPCTL_STAT_BIDX 0x7F00 /* Base index */ +#define B43_NPHY_TXPCTL_STAT_BIDX_SHIFT 8 +#define B43_NPHY_TXPCTL_STAT_ESTVALID 0x8000 /* Estimated power valid */ +#define B43_NPHY_SMALLSGS_LEN B43_PHY_N(0x1EF) /* Small sig gain settle length */ +#define B43_NPHY_PHYSTAT_GAIN0 B43_PHY_N(0x1F0) /* PHY stats gain info 0 */ +#define B43_NPHY_PHYSTAT_GAIN1 B43_PHY_N(0x1F1) /* PHY stats gain info 1 */ +#define B43_NPHY_PHYSTAT_FREQEST B43_PHY_N(0x1F2) /* PHY stats frequency estimate */ +#define B43_NPHY_PHYSTAT_ADVRET B43_PHY_N(0x1F3) /* PHY stats ADV retard */ +#define B43_NPHY_PHYLB_MODE B43_PHY_N(0x1F4) /* PHY loopback mode */ +#define B43_NPHY_TONE_MIDX20_1 B43_PHY_N(0x1F5) /* Tone map index 20/1 */ +#define B43_NPHY_TONE_MIDX20_2 B43_PHY_N(0x1F6) /* Tone map index 20/2 */ +#define B43_NPHY_TONE_MIDX20_3 B43_PHY_N(0x1F7) /* Tone map index 20/3 */ +#define B43_NPHY_TONE_MIDX40_1 B43_PHY_N(0x1F8) /* Tone map index 40/1 */ +#define B43_NPHY_TONE_MIDX40_2 B43_PHY_N(0x1F9) /* Tone map index 40/2 */ +#define B43_NPHY_TONE_MIDX40_3 B43_PHY_N(0x1FA) /* Tone map index 40/3 */ +#define B43_NPHY_TONE_MIDX40_4 B43_PHY_N(0x1FB) /* Tone map index 40/4 */ +#define B43_NPHY_PILTONE_MIDX1 B43_PHY_N(0x1FC) /* Pilot tone map index 1 */ +#define B43_NPHY_PILTONE_MIDX2 B43_PHY_N(0x1FD) /* Pilot tone map index 2 */ +#define B43_NPHY_PILTONE_MIDX3 B43_PHY_N(0x1FE) /* Pilot tone map index 3 */ +#define B43_NPHY_TXRIFS_FRDEL B43_PHY_N(0x1FF) /* TX RIFS frame delay */ +#define B43_NPHY_AFESEQ_RX2TX_PUD_40M B43_PHY_N(0x200) /* AFE seq rx2tx power up/down delay 40M */ +#define B43_NPHY_AFESEQ_TX2RX_PUD_40M B43_PHY_N(0x201) /* AFE seq tx2rx power up/down delay 40M */ +#define B43_NPHY_AFESEQ_RX2TX_PUD_20M B43_PHY_N(0x202) /* AFE seq rx2tx power up/down delay 20M */ +#define B43_NPHY_AFESEQ_TX2RX_PUD_20M B43_PHY_N(0x203) /* AFE seq tx2rx power up/down delay 20M */ +#define B43_NPHY_RX_SIGCTL B43_PHY_N(0x204) /* RX signal control */ +#define B43_NPHY_RXPIL_CYCNT0 B43_PHY_N(0x205) /* RX pilot cycle counter 0 */ +#define B43_NPHY_RXPIL_CYCNT1 B43_PHY_N(0x206) /* RX pilot cycle counter 1 */ +#define B43_NPHY_RXPIL_CYCNT2 B43_PHY_N(0x207) /* RX pilot cycle counter 2 */ +#define B43_NPHY_AFESEQ_RX2TX_PUD_10M B43_PHY_N(0x208) /* AFE seq rx2tx power up/down delay 10M */ +#define B43_NPHY_AFESEQ_TX2RX_PUD_10M B43_PHY_N(0x209) /* AFE seq tx2rx power up/down delay 10M */ +#define B43_NPHY_DSSSCCK_CRSEXTL B43_PHY_N(0x20A) /* DSSS/CCK CRS extension length */ +#define B43_NPHY_ML_LOGSS_RATSLOPE B43_PHY_N(0x20B) /* ML/logss ratio slope */ +#define B43_NPHY_RIFS_SRCTL B43_PHY_N(0x20C) /* RIFS search timeout length */ +#define B43_NPHY_TXREALFD B43_PHY_N(0x20D) /* TX real frame delay */ +#define B43_NPHY_HPANT_SWTHRES B43_PHY_N(0x20E) /* High power antenna switch threshold */ +#define B43_NPHY_EDCRS_ASSTHRES0 B43_PHY_N(0x210) /* ED CRS assert threshold 0 */ +#define B43_NPHY_EDCRS_ASSTHRES1 B43_PHY_N(0x211) /* ED CRS assert threshold 1 */ +#define B43_NPHY_EDCRS_DEASSTHRES0 B43_PHY_N(0x212) /* ED CRS deassert threshold 0 */ +#define B43_NPHY_EDCRS_DEASSTHRES1 B43_PHY_N(0x213) /* ED CRS deassert threshold 1 */ +#define B43_NPHY_STR_WTIME20U B43_PHY_N(0x214) /* STR wait time 20U */ +#define B43_NPHY_STR_WTIME20L B43_PHY_N(0x215) /* STR wait time 20L */ +#define B43_NPHY_TONE_MIDX657M B43_PHY_N(0x216) /* Tone map index 657M */ +#define B43_NPHY_HTSIGTONES B43_PHY_N(0x217) /* HT signal tones */ +#define B43_NPHY_RSSI1 B43_PHY_N(0x219) /* RSSI value 1 */ +#define B43_NPHY_RSSI2 B43_PHY_N(0x21A) /* RSSI value 2 */ +#define B43_NPHY_CHAN_ESTHANG B43_PHY_N(0x21D) /* Channel estimate hang */ +#define B43_NPHY_FINERX2_CGC B43_PHY_N(0x221) /* Fine RX 2 clock gate control */ +#define B43_NPHY_FINERX2_CGC_DECGC 0x0008 /* Decode gated clocks */ +#define B43_NPHY_TXPCTL_INIT B43_PHY_N(0x222) /* TX power control init */ +#define B43_NPHY_TXPCTL_INIT_PIDXI1 0x00FF /* Power index init 1 */ +#define B43_NPHY_TXPCTL_INIT_PIDXI1_SHIFT 0 +#define B43_NPHY_ED_CRSEN B43_PHY_N(0x223) +#define B43_NPHY_ED_CRS40ASSERTTHRESH0 B43_PHY_N(0x224) +#define B43_NPHY_ED_CRS40ASSERTTHRESH1 B43_PHY_N(0x225) +#define B43_NPHY_ED_CRS40DEASSERTTHRESH0 B43_PHY_N(0x226) +#define B43_NPHY_ED_CRS40DEASSERTTHRESH1 B43_PHY_N(0x227) +#define B43_NPHY_ED_CRS20LASSERTTHRESH0 B43_PHY_N(0x228) +#define B43_NPHY_ED_CRS20LASSERTTHRESH1 B43_PHY_N(0x229) +#define B43_NPHY_ED_CRS20LDEASSERTTHRESH0 B43_PHY_N(0x22A) +#define B43_NPHY_ED_CRS20LDEASSERTTHRESH1 B43_PHY_N(0x22B) +#define B43_NPHY_ED_CRS20UASSERTTHRESH0 B43_PHY_N(0x22C) +#define B43_NPHY_ED_CRS20UASSERTTHRESH1 B43_PHY_N(0x22D) +#define B43_NPHY_ED_CRS20UDEASSERTTHRESH0 B43_PHY_N(0x22E) +#define B43_NPHY_ED_CRS20UDEASSERTTHRESH1 B43_PHY_N(0x22F) +#define B43_NPHY_ED_CRS B43_PHY_N(0x230) +#define B43_NPHY_TIMEOUTEN B43_PHY_N(0x231) +#define B43_NPHY_OFDMPAYDECODETIMEOUTLEN B43_PHY_N(0x232) +#define B43_NPHY_CCKPAYDECODETIMEOUTLEN B43_PHY_N(0x233) +#define B43_NPHY_NONPAYDECODETIMEOUTLEN B43_PHY_N(0x234) +#define B43_NPHY_TIMEOUTSTATUS B43_PHY_N(0x235) +#define B43_NPHY_RFCTRLCORE0GPIO0 B43_PHY_N(0x236) +#define B43_NPHY_RFCTRLCORE0GPIO1 B43_PHY_N(0x237) +#define B43_NPHY_RFCTRLCORE0GPIO2 B43_PHY_N(0x238) +#define B43_NPHY_RFCTRLCORE0GPIO3 B43_PHY_N(0x239) +#define B43_NPHY_RFCTRLCORE1GPIO0 B43_PHY_N(0x23A) +#define B43_NPHY_RFCTRLCORE1GPIO1 B43_PHY_N(0x23B) +#define B43_NPHY_RFCTRLCORE1GPIO2 B43_PHY_N(0x23C) +#define B43_NPHY_RFCTRLCORE1GPIO3 B43_PHY_N(0x23D) +#define B43_NPHY_BPHYTESTCONTROL B43_PHY_N(0x23E) +/* REV3+ */ +#define B43_NPHY_FORCEFRONT0 B43_PHY_N(0x23F) +#define B43_NPHY_FORCEFRONT1 B43_PHY_N(0x240) +#define B43_NPHY_NORMVARHYSTTH B43_PHY_N(0x241) +#define B43_NPHY_TXCCKERROR B43_PHY_N(0x242) +#define B43_NPHY_AFESEQINITDACGAIN B43_PHY_N(0x243) +#define B43_NPHY_TXANTSWLUT B43_PHY_N(0x244) +#define B43_NPHY_CORECONFIG B43_PHY_N(0x245) +#define B43_NPHY_ANTENNADIVDWELLTIME B43_PHY_N(0x246) +#define B43_NPHY_ANTENNACCKDIVDWELLTIME B43_PHY_N(0x247) +#define B43_NPHY_ANTENNADIVBACKOFFGAIN B43_PHY_N(0x248) +#define B43_NPHY_ANTENNADIVMINGAIN B43_PHY_N(0x249) +#define B43_NPHY_BRDSEL_NORMVARHYSTTH B43_PHY_N(0x24A) +#define B43_NPHY_RXANTSWITCHCTRL B43_PHY_N(0x24B) +#define B43_NPHY_ENERGYDROPTIMEOUTLEN2 B43_PHY_N(0x24C) +#define B43_NPHY_ML_LOG_TXEVM0 B43_PHY_N(0x250) +#define B43_NPHY_ML_LOG_TXEVM1 B43_PHY_N(0x251) +#define B43_NPHY_ML_LOG_TXEVM2 B43_PHY_N(0x252) +#define B43_NPHY_ML_LOG_TXEVM3 B43_PHY_N(0x253) +#define B43_NPHY_ML_LOG_TXEVM4 B43_PHY_N(0x254) +#define B43_NPHY_ML_LOG_TXEVM5 B43_PHY_N(0x255) +#define B43_NPHY_ML_LOG_TXEVM6 B43_PHY_N(0x256) +#define B43_NPHY_ML_LOG_TXEVM7 B43_PHY_N(0x257) +#define B43_NPHY_ML_SCALE_TWEAK B43_PHY_N(0x258) +#define B43_NPHY_MLUA B43_PHY_N(0x259) +#define B43_NPHY_ZFUA B43_PHY_N(0x25A) +#define B43_NPHY_CHANUPSYM01 B43_PHY_N(0x25B) +#define B43_NPHY_CHANUPSYM2 B43_PHY_N(0x25C) +#define B43_NPHY_RXSTRNFILT20NUM00 B43_PHY_N(0x25D) +#define B43_NPHY_RXSTRNFILT20NUM01 B43_PHY_N(0x25E) +#define B43_NPHY_RXSTRNFILT20NUM02 B43_PHY_N(0x25F) +#define B43_NPHY_RXSTRNFILT20DEN00 B43_PHY_N(0x260) +#define B43_NPHY_RXSTRNFILT20DEN01 B43_PHY_N(0x261) +#define B43_NPHY_RXSTRNFILT20NUM10 B43_PHY_N(0x262) +#define B43_NPHY_RXSTRNFILT20NUM11 B43_PHY_N(0x263) +#define B43_NPHY_RXSTRNFILT20NUM12 B43_PHY_N(0x264) +#define B43_NPHY_RXSTRNFILT20DEN10 B43_PHY_N(0x265) +#define B43_NPHY_RXSTRNFILT20DEN11 B43_PHY_N(0x266) +#define B43_NPHY_RXSTRNFILT40NUM00 B43_PHY_N(0x267) +#define B43_NPHY_RXSTRNFILT40NUM01 B43_PHY_N(0x268) +#define B43_NPHY_RXSTRNFILT40NUM02 B43_PHY_N(0x269) +#define B43_NPHY_RXSTRNFILT40DEN00 B43_PHY_N(0x26A) +#define B43_NPHY_RXSTRNFILT40DEN01 B43_PHY_N(0x26B) +#define B43_NPHY_RXSTRNFILT40NUM10 B43_PHY_N(0x26C) +#define B43_NPHY_RXSTRNFILT40NUM11 B43_PHY_N(0x26D) +#define B43_NPHY_RXSTRNFILT40NUM12 B43_PHY_N(0x26E) +#define B43_NPHY_RXSTRNFILT40DEN10 B43_PHY_N(0x26F) +#define B43_NPHY_RXSTRNFILT40DEN11 B43_PHY_N(0x270) +#define B43_NPHY_CRSHIGHPOWTHRESHOLD1 B43_PHY_N(0x271) +#define B43_NPHY_CRSHIGHPOWTHRESHOLD2 B43_PHY_N(0x272) +#define B43_NPHY_CRSHIGHLOWPOWTHRESHOLD B43_PHY_N(0x273) +#define B43_NPHY_CRSHIGHPOWTHRESHOLD1L B43_PHY_N(0x274) +#define B43_NPHY_CRSHIGHPOWTHRESHOLD2L B43_PHY_N(0x275) +#define B43_NPHY_CRSHIGHLOWPOWTHRESHOLDL B43_PHY_N(0x276) +#define B43_NPHY_CRSHIGHPOWTHRESHOLD1U B43_PHY_N(0x277) +#define B43_NPHY_CRSHIGHPOWTHRESHOLD2U B43_PHY_N(0x278) +#define B43_NPHY_CRSHIGHLOWPOWTHRESHOLDU B43_PHY_N(0x279) +#define B43_NPHY_CRSACIDETECTTHRESH B43_PHY_N(0x27A) +#define B43_NPHY_CRSACIDETECTTHRESHL B43_PHY_N(0x27B) +#define B43_NPHY_CRSACIDETECTTHRESHU B43_PHY_N(0x27C) +#define B43_NPHY_CRSMINPOWER0 B43_PHY_N(0x27D) +#define B43_NPHY_CRSMINPOWER1 B43_PHY_N(0x27E) +#define B43_NPHY_CRSMINPOWER2 B43_PHY_N(0x27F) +#define B43_NPHY_CRSMINPOWERL0 B43_PHY_N(0x280) +#define B43_NPHY_CRSMINPOWERL1 B43_PHY_N(0x281) +#define B43_NPHY_CRSMINPOWERL2 B43_PHY_N(0x282) +#define B43_NPHY_CRSMINPOWERU0 B43_PHY_N(0x283) +#define B43_NPHY_CRSMINPOWERU1 B43_PHY_N(0x284) +#define B43_NPHY_CRSMINPOWERU2 B43_PHY_N(0x285) +#define B43_NPHY_STRPARAM B43_PHY_N(0x286) +#define B43_NPHY_STRPARAML B43_PHY_N(0x287) +#define B43_NPHY_STRPARAMU B43_PHY_N(0x288) +#define B43_NPHY_BPHYCRSMINPOWER0 B43_PHY_N(0x289) +#define B43_NPHY_BPHYCRSMINPOWER1 B43_PHY_N(0x28A) +#define B43_NPHY_BPHYCRSMINPOWER2 B43_PHY_N(0x28B) +#define B43_NPHY_BPHYFILTDEN0COEF B43_PHY_N(0x28C) +#define B43_NPHY_BPHYFILTDEN1COEF B43_PHY_N(0x28D) +#define B43_NPHY_BPHYFILTDEN2COEF B43_PHY_N(0x28E) +#define B43_NPHY_BPHYFILTNUM0COEF B43_PHY_N(0x28F) +#define B43_NPHY_BPHYFILTNUM1COEF B43_PHY_N(0x290) +#define B43_NPHY_BPHYFILTNUM2COEF B43_PHY_N(0x291) +#define B43_NPHY_BPHYFILTNUM01COEF2 B43_PHY_N(0x292) +#define B43_NPHY_BPHYFILTBYPASS B43_PHY_N(0x293) +#define B43_NPHY_SGILTRNOFFSET B43_PHY_N(0x294) +#define B43_NPHY_RADAR_T2_MIN B43_PHY_N(0x295) +#define B43_NPHY_TXPWRCTRLDAMPING B43_PHY_N(0x296) +#define B43_NPHY_PAPD_EN0 B43_PHY_N(0x297) /* PAPD Enable0 TBD */ +#define B43_NPHY_EPS_TABLE_ADJ0 B43_PHY_N(0x298) /* EPS Table Adj0 TBD */ +#define B43_NPHY_EPS_OVERRIDEI_0 B43_PHY_N(0x299) +#define B43_NPHY_EPS_OVERRIDEQ_0 B43_PHY_N(0x29A) +#define B43_NPHY_PAPD_EN1 B43_PHY_N(0x29B) /* PAPD Enable1 TBD */ +#define B43_NPHY_EPS_TABLE_ADJ1 B43_PHY_N(0x29C) /* EPS Table Adj1 TBD */ +#define B43_NPHY_EPS_OVERRIDEI_1 B43_PHY_N(0x29D) +#define B43_NPHY_EPS_OVERRIDEQ_1 B43_PHY_N(0x29E) +#define B43_NPHY_PAPD_CAL_ADDRESS B43_PHY_N(0x29F) +#define B43_NPHY_PAPD_CAL_YREFEPSILON B43_PHY_N(0x2A0) +#define B43_NPHY_PAPD_CAL_SETTLE B43_PHY_N(0x2A1) +#define B43_NPHY_PAPD_CAL_CORRELATE B43_PHY_N(0x2A2) +#define B43_NPHY_PAPD_CAL_SHIFTS0 B43_PHY_N(0x2A3) +#define B43_NPHY_PAPD_CAL_SHIFTS1 B43_PHY_N(0x2A4) +#define B43_NPHY_SAMPLE_START_ADDR B43_PHY_N(0x2A5) +#define B43_NPHY_RADAR_ADC_TO_DBM B43_PHY_N(0x2A6) +#define B43_NPHY_REV3_C2_INITGAIN_A B43_PHY_N(0x2A7) +#define B43_NPHY_REV3_C2_INITGAIN_B B43_PHY_N(0x2A8) +#define B43_NPHY_REV3_C2_CLIP_HIGAIN_A B43_PHY_N(0x2A9) +#define B43_NPHY_REV3_C2_CLIP_HIGAIN_B B43_PHY_N(0x2AA) +#define B43_NPHY_REV3_C2_CLIP_MEDGAIN_A B43_PHY_N(0x2AB) +#define B43_NPHY_REV3_C2_CLIP_MEDGAIN_B B43_PHY_N(0x2AC) +#define B43_NPHY_REV3_C2_CLIP_LOGAIN_A B43_PHY_N(0x2AD) +#define B43_NPHY_REV3_C2_CLIP_LOGAIN_B B43_PHY_N(0x2AE) +#define B43_NPHY_REV3_C2_CLIP2_GAIN_A B43_PHY_N(0x2AF) +#define B43_NPHY_REV3_C2_CLIP2_GAIN_B B43_PHY_N(0x2B0) + +#define B43_PHY_B_BBCFG B43_PHY_N_BMODE(0x001) /* BB config */ +#define B43_PHY_B_TEST B43_PHY_N_BMODE(0x00A) + +struct b43_wldev; + +enum b43_nphy_spur_avoid { + B43_SPUR_AVOID_DISABLE, + B43_SPUR_AVOID_AUTO, + B43_SPUR_AVOID_FORCE, +}; + +struct b43_chanspec { + u16 center_freq; + enum nl80211_channel_type channel_type; +}; + +struct b43_phy_n_iq_comp { + s16 a0; + s16 b0; + s16 a1; + s16 b1; +}; + +struct b43_phy_n_rssical_cache { + u16 rssical_radio_regs_2G[2]; + u16 rssical_phy_regs_2G[12]; + + u16 rssical_radio_regs_5G[2]; + u16 rssical_phy_regs_5G[12]; +}; + +struct b43_phy_n_cal_cache { + u16 txcal_radio_regs_2G[8]; + u16 txcal_coeffs_2G[8]; + struct b43_phy_n_iq_comp rxcal_coeffs_2G; + + u16 txcal_radio_regs_5G[8]; + u16 txcal_coeffs_5G[8]; + struct b43_phy_n_iq_comp rxcal_coeffs_5G; +}; + +struct b43_phy_n_txpwrindex { + s8 index; + s8 index_internal; + s8 index_internal_save; + u16 AfectrlOverride; + u16 AfeCtrlDacGain; + u16 rad_gain; + u8 bbmult; + u16 iqcomp_a; + u16 iqcomp_b; + u16 locomp; +}; + +struct b43_phy_n_pwr_ctl_info { + u8 idle_tssi_2g; + u8 idle_tssi_5g; +}; + +struct b43_phy_n { + u8 antsel_type; + u8 cal_orig_pwr_idx[2]; + u8 measure_hold; + u8 phyrxchain; + u8 hw_phyrxchain; + u8 hw_phytxchain; + u8 perical; + u32 deaf_count; + u32 rxcalparams; + bool hang_avoid; + bool mute; + u16 papd_epsilon_offset[2]; + s32 preamble_override; + u32 bb_mult_save; + bool init_por; + + bool gain_boost; + bool elna_gain_config; + bool band5g_pwrgain; + + u8 mphase_cal_phase_id; + u16 mphase_txcal_cmdidx; + u16 mphase_txcal_numcmds; + u16 mphase_txcal_bestcoeffs[11]; + + bool txpwrctrl; + bool pwg_gain_5ghz; + u8 tx_pwr_idx[2]; + s8 tx_power_offset[101]; + u16 adj_pwr_tbl[84]; + u16 txcal_bbmult; + u16 txiqlocal_bestc[11]; + bool txiqlocal_coeffsvalid; + struct b43_phy_n_txpwrindex txpwrindex[2]; + struct b43_phy_n_pwr_ctl_info pwr_ctl_info[2]; + struct b43_chanspec txiqlocal_chanspec; + + u8 txrx_chain; + u16 tx_rx_cal_phy_saveregs[11]; + u16 tx_rx_cal_radio_saveregs[22]; + + u16 rfctrl_intc1_save; + u16 rfctrl_intc2_save; + + u16 classifier_state; + u16 clip_state[2]; + + enum b43_nphy_spur_avoid spur_avoid; + bool aband_spurwar_en; + bool gband_spurwar_en; + + bool ipa2g_on; + struct b43_chanspec iqcal_chanspec_2G; + struct b43_chanspec rssical_chanspec_2G; + + bool ipa5g_on; + struct b43_chanspec iqcal_chanspec_5G; + struct b43_chanspec rssical_chanspec_5G; + + struct b43_phy_n_rssical_cache rssical_cache; + struct b43_phy_n_cal_cache cal_cache; + bool crsminpwr_adjusted; + bool noisevars_adjusted; +}; + + +struct b43_phy_operations; +extern const struct b43_phy_operations b43_phyops_n; + +#endif /* B43_NPHY_H_ */ diff --git a/drivers/net/wireless/b43/pio.c b/drivers/net/wireless/b43/pio.c new file mode 100644 index 0000000..a73ff8c --- /dev/null +++ b/drivers/net/wireless/b43/pio.c @@ -0,0 +1,834 @@ +/* + + Broadcom B43 wireless driver + + PIO data transfer + + Copyright (c) 2005-2008 Michael Buesch + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" +#include "pio.h" +#include "dma.h" +#include "main.h" +#include "xmit.h" + +#include +#include +#include + + +static u16 generate_cookie(struct b43_pio_txqueue *q, + struct b43_pio_txpacket *pack) +{ + u16 cookie; + + /* Use the upper 4 bits of the cookie as + * PIO controller ID and store the packet index number + * in the lower 12 bits. + * Note that the cookie must never be 0, as this + * is a special value used in RX path. + * It can also not be 0xFFFF because that is special + * for multicast frames. + */ + cookie = (((u16)q->index + 1) << 12); + cookie |= pack->index; + + return cookie; +} + +static +struct b43_pio_txqueue *parse_cookie(struct b43_wldev *dev, + u16 cookie, + struct b43_pio_txpacket **pack) +{ + struct b43_pio *pio = &dev->pio; + struct b43_pio_txqueue *q = NULL; + unsigned int pack_index; + + switch (cookie & 0xF000) { + case 0x1000: + q = pio->tx_queue_AC_BK; + break; + case 0x2000: + q = pio->tx_queue_AC_BE; + break; + case 0x3000: + q = pio->tx_queue_AC_VI; + break; + case 0x4000: + q = pio->tx_queue_AC_VO; + break; + case 0x5000: + q = pio->tx_queue_mcast; + break; + } + if (B43_WARN_ON(!q)) + return NULL; + pack_index = (cookie & 0x0FFF); + if (B43_WARN_ON(pack_index >= ARRAY_SIZE(q->packets))) + return NULL; + *pack = &q->packets[pack_index]; + + return q; +} + +static u16 index_to_pioqueue_base(struct b43_wldev *dev, + unsigned int index) +{ + static const u16 bases[] = { + B43_MMIO_PIO_BASE0, + B43_MMIO_PIO_BASE1, + B43_MMIO_PIO_BASE2, + B43_MMIO_PIO_BASE3, + B43_MMIO_PIO_BASE4, + B43_MMIO_PIO_BASE5, + B43_MMIO_PIO_BASE6, + B43_MMIO_PIO_BASE7, + }; + static const u16 bases_rev11[] = { + B43_MMIO_PIO11_BASE0, + B43_MMIO_PIO11_BASE1, + B43_MMIO_PIO11_BASE2, + B43_MMIO_PIO11_BASE3, + B43_MMIO_PIO11_BASE4, + B43_MMIO_PIO11_BASE5, + }; + + if (dev->dev->core_rev >= 11) { + B43_WARN_ON(index >= ARRAY_SIZE(bases_rev11)); + return bases_rev11[index]; + } + B43_WARN_ON(index >= ARRAY_SIZE(bases)); + return bases[index]; +} + +static u16 pio_txqueue_offset(struct b43_wldev *dev) +{ + if (dev->dev->core_rev >= 11) + return 0x18; + return 0; +} + +static u16 pio_rxqueue_offset(struct b43_wldev *dev) +{ + if (dev->dev->core_rev >= 11) + return 0x38; + return 8; +} + +static struct b43_pio_txqueue *b43_setup_pioqueue_tx(struct b43_wldev *dev, + unsigned int index) +{ + struct b43_pio_txqueue *q; + struct b43_pio_txpacket *p; + unsigned int i; + + q = kzalloc(sizeof(*q), GFP_KERNEL); + if (!q) + return NULL; + q->dev = dev; + q->rev = dev->dev->core_rev; + q->mmio_base = index_to_pioqueue_base(dev, index) + + pio_txqueue_offset(dev); + q->index = index; + + q->free_packet_slots = B43_PIO_MAX_NR_TXPACKETS; + if (q->rev >= 8) { + q->buffer_size = 1920; //FIXME this constant is wrong. + } else { + q->buffer_size = b43_piotx_read16(q, B43_PIO_TXQBUFSIZE); + q->buffer_size -= 80; + } + + INIT_LIST_HEAD(&q->packets_list); + for (i = 0; i < ARRAY_SIZE(q->packets); i++) { + p = &(q->packets[i]); + INIT_LIST_HEAD(&p->list); + p->index = i; + p->queue = q; + list_add(&p->list, &q->packets_list); + } + + return q; +} + +static struct b43_pio_rxqueue *b43_setup_pioqueue_rx(struct b43_wldev *dev, + unsigned int index) +{ + struct b43_pio_rxqueue *q; + + q = kzalloc(sizeof(*q), GFP_KERNEL); + if (!q) + return NULL; + q->dev = dev; + q->rev = dev->dev->core_rev; + q->mmio_base = index_to_pioqueue_base(dev, index) + + pio_rxqueue_offset(dev); + + /* Enable Direct FIFO RX (PIO) on the engine. */ + b43_dma_direct_fifo_rx(dev, index, 1); + + return q; +} + +static void b43_pio_cancel_tx_packets(struct b43_pio_txqueue *q) +{ + struct b43_pio_txpacket *pack; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(q->packets); i++) { + pack = &(q->packets[i]); + if (pack->skb) { + ieee80211_free_txskb(q->dev->wl->hw, pack->skb); + pack->skb = NULL; + } + } +} + +static void b43_destroy_pioqueue_tx(struct b43_pio_txqueue *q, + const char *name) +{ + if (!q) + return; + b43_pio_cancel_tx_packets(q); + kfree(q); +} + +static void b43_destroy_pioqueue_rx(struct b43_pio_rxqueue *q, + const char *name) +{ + if (!q) + return; + kfree(q); +} + +#define destroy_queue_tx(pio, queue) do { \ + b43_destroy_pioqueue_tx((pio)->queue, __stringify(queue)); \ + (pio)->queue = NULL; \ + } while (0) + +#define destroy_queue_rx(pio, queue) do { \ + b43_destroy_pioqueue_rx((pio)->queue, __stringify(queue)); \ + (pio)->queue = NULL; \ + } while (0) + +void b43_pio_free(struct b43_wldev *dev) +{ + struct b43_pio *pio; + + if (!b43_using_pio_transfers(dev)) + return; + pio = &dev->pio; + + destroy_queue_rx(pio, rx_queue); + destroy_queue_tx(pio, tx_queue_mcast); + destroy_queue_tx(pio, tx_queue_AC_VO); + destroy_queue_tx(pio, tx_queue_AC_VI); + destroy_queue_tx(pio, tx_queue_AC_BE); + destroy_queue_tx(pio, tx_queue_AC_BK); +} + +int b43_pio_init(struct b43_wldev *dev) +{ + struct b43_pio *pio = &dev->pio; + int err = -ENOMEM; + + b43_write32(dev, B43_MMIO_MACCTL, b43_read32(dev, B43_MMIO_MACCTL) + & ~B43_MACCTL_BE); + b43_shm_write16(dev, B43_SHM_SHARED, B43_SHM_SH_RXPADOFF, 0); + + pio->tx_queue_AC_BK = b43_setup_pioqueue_tx(dev, 0); + if (!pio->tx_queue_AC_BK) + goto out; + + pio->tx_queue_AC_BE = b43_setup_pioqueue_tx(dev, 1); + if (!pio->tx_queue_AC_BE) + goto err_destroy_bk; + + pio->tx_queue_AC_VI = b43_setup_pioqueue_tx(dev, 2); + if (!pio->tx_queue_AC_VI) + goto err_destroy_be; + + pio->tx_queue_AC_VO = b43_setup_pioqueue_tx(dev, 3); + if (!pio->tx_queue_AC_VO) + goto err_destroy_vi; + + pio->tx_queue_mcast = b43_setup_pioqueue_tx(dev, 4); + if (!pio->tx_queue_mcast) + goto err_destroy_vo; + + pio->rx_queue = b43_setup_pioqueue_rx(dev, 0); + if (!pio->rx_queue) + goto err_destroy_mcast; + + b43dbg(dev->wl, "PIO initialized\n"); + err = 0; +out: + return err; + +err_destroy_mcast: + destroy_queue_tx(pio, tx_queue_mcast); +err_destroy_vo: + destroy_queue_tx(pio, tx_queue_AC_VO); +err_destroy_vi: + destroy_queue_tx(pio, tx_queue_AC_VI); +err_destroy_be: + destroy_queue_tx(pio, tx_queue_AC_BE); +err_destroy_bk: + destroy_queue_tx(pio, tx_queue_AC_BK); + return err; +} + +/* Static mapping of mac80211's queues (priorities) to b43 PIO queues. */ +static struct b43_pio_txqueue *select_queue_by_priority(struct b43_wldev *dev, + u8 queue_prio) +{ + struct b43_pio_txqueue *q; + + if (dev->qos_enabled) { + /* 0 = highest priority */ + switch (queue_prio) { + default: + B43_WARN_ON(1); + /* fallthrough */ + case 0: + q = dev->pio.tx_queue_AC_VO; + break; + case 1: + q = dev->pio.tx_queue_AC_VI; + break; + case 2: + q = dev->pio.tx_queue_AC_BE; + break; + case 3: + q = dev->pio.tx_queue_AC_BK; + break; + } + } else + q = dev->pio.tx_queue_AC_BE; + + return q; +} + +static u16 tx_write_2byte_queue(struct b43_pio_txqueue *q, + u16 ctl, + const void *_data, + unsigned int data_len) +{ + struct b43_wldev *dev = q->dev; + struct b43_wl *wl = dev->wl; + const u8 *data = _data; + + ctl |= B43_PIO_TXCTL_WRITELO | B43_PIO_TXCTL_WRITEHI; + b43_piotx_write16(q, B43_PIO_TXCTL, ctl); + + b43_block_write(dev, data, (data_len & ~1), + q->mmio_base + B43_PIO_TXDATA, + sizeof(u16)); + if (data_len & 1) { + u8 *tail = wl->pio_tailspace; + BUILD_BUG_ON(sizeof(wl->pio_tailspace) < 2); + + /* Write the last byte. */ + ctl &= ~B43_PIO_TXCTL_WRITEHI; + b43_piotx_write16(q, B43_PIO_TXCTL, ctl); + tail[0] = data[data_len - 1]; + tail[1] = 0; + b43_block_write(dev, tail, 2, + q->mmio_base + B43_PIO_TXDATA, + sizeof(u16)); + } + + return ctl; +} + +static void pio_tx_frame_2byte_queue(struct b43_pio_txpacket *pack, + const u8 *hdr, unsigned int hdrlen) +{ + struct b43_pio_txqueue *q = pack->queue; + const char *frame = pack->skb->data; + unsigned int frame_len = pack->skb->len; + u16 ctl; + + ctl = b43_piotx_read16(q, B43_PIO_TXCTL); + ctl |= B43_PIO_TXCTL_FREADY; + ctl &= ~B43_PIO_TXCTL_EOF; + + /* Transfer the header data. */ + ctl = tx_write_2byte_queue(q, ctl, hdr, hdrlen); + /* Transfer the frame data. */ + ctl = tx_write_2byte_queue(q, ctl, frame, frame_len); + + ctl |= B43_PIO_TXCTL_EOF; + b43_piotx_write16(q, B43_PIO_TXCTL, ctl); +} + +static u32 tx_write_4byte_queue(struct b43_pio_txqueue *q, + u32 ctl, + const void *_data, + unsigned int data_len) +{ + struct b43_wldev *dev = q->dev; + struct b43_wl *wl = dev->wl; + const u8 *data = _data; + + ctl |= B43_PIO8_TXCTL_0_7 | B43_PIO8_TXCTL_8_15 | + B43_PIO8_TXCTL_16_23 | B43_PIO8_TXCTL_24_31; + b43_piotx_write32(q, B43_PIO8_TXCTL, ctl); + + b43_block_write(dev, data, (data_len & ~3), + q->mmio_base + B43_PIO8_TXDATA, + sizeof(u32)); + if (data_len & 3) { + u8 *tail = wl->pio_tailspace; + BUILD_BUG_ON(sizeof(wl->pio_tailspace) < 4); + + memset(tail, 0, 4); + /* Write the last few bytes. */ + ctl &= ~(B43_PIO8_TXCTL_8_15 | B43_PIO8_TXCTL_16_23 | + B43_PIO8_TXCTL_24_31); + switch (data_len & 3) { + case 3: + ctl |= B43_PIO8_TXCTL_16_23 | B43_PIO8_TXCTL_8_15; + tail[0] = data[data_len - 3]; + tail[1] = data[data_len - 2]; + tail[2] = data[data_len - 1]; + break; + case 2: + ctl |= B43_PIO8_TXCTL_8_15; + tail[0] = data[data_len - 2]; + tail[1] = data[data_len - 1]; + break; + case 1: + tail[0] = data[data_len - 1]; + break; + } + b43_piotx_write32(q, B43_PIO8_TXCTL, ctl); + b43_block_write(dev, tail, 4, + q->mmio_base + B43_PIO8_TXDATA, + sizeof(u32)); + } + + return ctl; +} + +static void pio_tx_frame_4byte_queue(struct b43_pio_txpacket *pack, + const u8 *hdr, unsigned int hdrlen) +{ + struct b43_pio_txqueue *q = pack->queue; + const char *frame = pack->skb->data; + unsigned int frame_len = pack->skb->len; + u32 ctl; + + ctl = b43_piotx_read32(q, B43_PIO8_TXCTL); + ctl |= B43_PIO8_TXCTL_FREADY; + ctl &= ~B43_PIO8_TXCTL_EOF; + + /* Transfer the header data. */ + ctl = tx_write_4byte_queue(q, ctl, hdr, hdrlen); + /* Transfer the frame data. */ + ctl = tx_write_4byte_queue(q, ctl, frame, frame_len); + + ctl |= B43_PIO8_TXCTL_EOF; + b43_piotx_write32(q, B43_PIO_TXCTL, ctl); +} + +static int pio_tx_frame(struct b43_pio_txqueue *q, + struct sk_buff *skb) +{ + struct b43_wldev *dev = q->dev; + struct b43_wl *wl = dev->wl; + struct b43_pio_txpacket *pack; + u16 cookie; + int err; + unsigned int hdrlen; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct b43_txhdr *txhdr = (struct b43_txhdr *)wl->pio_scratchspace; + + B43_WARN_ON(list_empty(&q->packets_list)); + pack = list_entry(q->packets_list.next, + struct b43_pio_txpacket, list); + + cookie = generate_cookie(q, pack); + hdrlen = b43_txhdr_size(dev); + BUILD_BUG_ON(sizeof(wl->pio_scratchspace) < sizeof(struct b43_txhdr)); + B43_WARN_ON(sizeof(wl->pio_scratchspace) < hdrlen); + err = b43_generate_txhdr(dev, (u8 *)txhdr, skb, + info, cookie); + if (err) + return err; + + if (info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM) { + /* Tell the firmware about the cookie of the last + * mcast frame, so it can clear the more-data bit in it. */ + b43_shm_write16(dev, B43_SHM_SHARED, + B43_SHM_SH_MCASTCOOKIE, cookie); + } + + pack->skb = skb; + if (q->rev >= 8) + pio_tx_frame_4byte_queue(pack, (const u8 *)txhdr, hdrlen); + else + pio_tx_frame_2byte_queue(pack, (const u8 *)txhdr, hdrlen); + + /* Remove it from the list of available packet slots. + * It will be put back when we receive the status report. */ + list_del(&pack->list); + + /* Update the queue statistics. */ + q->buffer_used += roundup(skb->len + hdrlen, 4); + q->free_packet_slots -= 1; + + return 0; +} + +int b43_pio_tx(struct b43_wldev *dev, struct sk_buff *skb) +{ + struct b43_pio_txqueue *q; + struct ieee80211_hdr *hdr; + unsigned int hdrlen, total_len; + int err = 0; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + hdr = (struct ieee80211_hdr *)skb->data; + + if (info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM) { + /* The multicast queue will be sent after the DTIM. */ + q = dev->pio.tx_queue_mcast; + /* Set the frame More-Data bit. Ucode will clear it + * for us on the last frame. */ + hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_MOREDATA); + } else { + /* Decide by priority where to put this frame. */ + q = select_queue_by_priority(dev, skb_get_queue_mapping(skb)); + } + + hdrlen = b43_txhdr_size(dev); + total_len = roundup(skb->len + hdrlen, 4); + + if (unlikely(total_len > q->buffer_size)) { + err = -ENOBUFS; + b43dbg(dev->wl, "PIO: TX packet longer than queue.\n"); + goto out; + } + if (unlikely(q->free_packet_slots == 0)) { + err = -ENOBUFS; + b43warn(dev->wl, "PIO: TX packet overflow.\n"); + goto out; + } + B43_WARN_ON(q->buffer_used > q->buffer_size); + + if (total_len > (q->buffer_size - q->buffer_used)) { + /* Not enough memory on the queue. */ + err = -EBUSY; + ieee80211_stop_queue(dev->wl->hw, skb_get_queue_mapping(skb)); + q->stopped = true; + goto out; + } + + /* Assign the queue number to the ring (if not already done before) + * so TX status handling can use it. The mac80211-queue to b43-queue + * mapping is static, so we don't need to store it per frame. */ + q->queue_prio = skb_get_queue_mapping(skb); + + err = pio_tx_frame(q, skb); + if (unlikely(err == -ENOKEY)) { + /* Drop this packet, as we don't have the encryption key + * anymore and must not transmit it unencrypted. */ + ieee80211_free_txskb(dev->wl->hw, skb); + err = 0; + goto out; + } + if (unlikely(err)) { + b43err(dev->wl, "PIO transmission failure\n"); + goto out; + } + + B43_WARN_ON(q->buffer_used > q->buffer_size); + if (((q->buffer_size - q->buffer_used) < roundup(2 + 2 + 6, 4)) || + (q->free_packet_slots == 0)) { + /* The queue is full. */ + ieee80211_stop_queue(dev->wl->hw, skb_get_queue_mapping(skb)); + q->stopped = true; + } + +out: + return err; +} + +void b43_pio_handle_txstatus(struct b43_wldev *dev, + const struct b43_txstatus *status) +{ + struct b43_pio_txqueue *q; + struct b43_pio_txpacket *pack = NULL; + unsigned int total_len; + struct ieee80211_tx_info *info; + + q = parse_cookie(dev, status->cookie, &pack); + if (unlikely(!q)) + return; + B43_WARN_ON(!pack); + + info = IEEE80211_SKB_CB(pack->skb); + + b43_fill_txstatus_report(dev, info, status); + + total_len = pack->skb->len + b43_txhdr_size(dev); + total_len = roundup(total_len, 4); + q->buffer_used -= total_len; + q->free_packet_slots += 1; + + ieee80211_tx_status(dev->wl->hw, pack->skb); + pack->skb = NULL; + list_add(&pack->list, &q->packets_list); + + if (q->stopped) { + ieee80211_wake_queue(dev->wl->hw, q->queue_prio); + q->stopped = false; + } +} + +/* Returns whether we should fetch another frame. */ +static bool pio_rx_frame(struct b43_pio_rxqueue *q) +{ + struct b43_wldev *dev = q->dev; + struct b43_wl *wl = dev->wl; + u16 len; + u32 macstat = 0; + unsigned int i, padding; + struct sk_buff *skb; + const char *err_msg = NULL; + struct b43_rxhdr_fw4 *rxhdr = + (struct b43_rxhdr_fw4 *)wl->pio_scratchspace; + size_t rxhdr_size = sizeof(*rxhdr); + + BUILD_BUG_ON(sizeof(wl->pio_scratchspace) < sizeof(*rxhdr)); + switch (dev->fw.hdr_format) { + case B43_FW_HDR_410: + case B43_FW_HDR_351: + rxhdr_size -= sizeof(rxhdr->format_598) - + sizeof(rxhdr->format_351); + break; + case B43_FW_HDR_598: + break; + } + memset(rxhdr, 0, rxhdr_size); + + /* Check if we have data and wait for it to get ready. */ + if (q->rev >= 8) { + u32 ctl; + + ctl = b43_piorx_read32(q, B43_PIO8_RXCTL); + if (!(ctl & B43_PIO8_RXCTL_FRAMERDY)) + return 0; + b43_piorx_write32(q, B43_PIO8_RXCTL, + B43_PIO8_RXCTL_FRAMERDY); + for (i = 0; i < 10; i++) { + ctl = b43_piorx_read32(q, B43_PIO8_RXCTL); + if (ctl & B43_PIO8_RXCTL_DATARDY) + goto data_ready; + udelay(10); + } + } else { + u16 ctl; + + ctl = b43_piorx_read16(q, B43_PIO_RXCTL); + if (!(ctl & B43_PIO_RXCTL_FRAMERDY)) + return 0; + b43_piorx_write16(q, B43_PIO_RXCTL, + B43_PIO_RXCTL_FRAMERDY); + for (i = 0; i < 10; i++) { + ctl = b43_piorx_read16(q, B43_PIO_RXCTL); + if (ctl & B43_PIO_RXCTL_DATARDY) + goto data_ready; + udelay(10); + } + } + b43dbg(q->dev->wl, "PIO RX timed out\n"); + return 1; +data_ready: + + /* Get the preamble (RX header) */ + if (q->rev >= 8) { + b43_block_read(dev, rxhdr, rxhdr_size, + q->mmio_base + B43_PIO8_RXDATA, + sizeof(u32)); + } else { + b43_block_read(dev, rxhdr, rxhdr_size, + q->mmio_base + B43_PIO_RXDATA, + sizeof(u16)); + } + /* Sanity checks. */ + len = le16_to_cpu(rxhdr->frame_len); + if (unlikely(len > 0x700)) { + err_msg = "len > 0x700"; + goto rx_error; + } + if (unlikely(len == 0)) { + err_msg = "len == 0"; + goto rx_error; + } + + switch (dev->fw.hdr_format) { + case B43_FW_HDR_598: + macstat = le32_to_cpu(rxhdr->format_598.mac_status); + break; + case B43_FW_HDR_410: + case B43_FW_HDR_351: + macstat = le32_to_cpu(rxhdr->format_351.mac_status); + break; + } + if (macstat & B43_RX_MAC_FCSERR) { + if (!(q->dev->wl->filter_flags & FIF_FCSFAIL)) { + /* Drop frames with failed FCS. */ + err_msg = "Frame FCS error"; + goto rx_error; + } + } + + /* We always pad 2 bytes, as that's what upstream code expects + * due to the RX-header being 30 bytes. In case the frame is + * unaligned, we pad another 2 bytes. */ + padding = (macstat & B43_RX_MAC_PADDING) ? 2 : 0; + skb = dev_alloc_skb(len + padding + 2); + if (unlikely(!skb)) { + err_msg = "Out of memory"; + goto rx_error; + } + skb_reserve(skb, 2); + skb_put(skb, len + padding); + if (q->rev >= 8) { + b43_block_read(dev, skb->data + padding, (len & ~3), + q->mmio_base + B43_PIO8_RXDATA, + sizeof(u32)); + if (len & 3) { + u8 *tail = wl->pio_tailspace; + BUILD_BUG_ON(sizeof(wl->pio_tailspace) < 4); + + /* Read the last few bytes. */ + b43_block_read(dev, tail, 4, + q->mmio_base + B43_PIO8_RXDATA, + sizeof(u32)); + switch (len & 3) { + case 3: + skb->data[len + padding - 3] = tail[0]; + skb->data[len + padding - 2] = tail[1]; + skb->data[len + padding - 1] = tail[2]; + break; + case 2: + skb->data[len + padding - 2] = tail[0]; + skb->data[len + padding - 1] = tail[1]; + break; + case 1: + skb->data[len + padding - 1] = tail[0]; + break; + } + } + } else { + b43_block_read(dev, skb->data + padding, (len & ~1), + q->mmio_base + B43_PIO_RXDATA, + sizeof(u16)); + if (len & 1) { + u8 *tail = wl->pio_tailspace; + BUILD_BUG_ON(sizeof(wl->pio_tailspace) < 2); + + /* Read the last byte. */ + b43_block_read(dev, tail, 2, + q->mmio_base + B43_PIO_RXDATA, + sizeof(u16)); + skb->data[len + padding - 1] = tail[0]; + } + } + + b43_rx(q->dev, skb, rxhdr); + + return 1; + +rx_error: + if (err_msg) + b43dbg(q->dev->wl, "PIO RX error: %s\n", err_msg); + if (q->rev >= 8) + b43_piorx_write32(q, B43_PIO8_RXCTL, B43_PIO8_RXCTL_DATARDY); + else + b43_piorx_write16(q, B43_PIO_RXCTL, B43_PIO_RXCTL_DATARDY); + + return 1; +} + +void b43_pio_rx(struct b43_pio_rxqueue *q) +{ + unsigned int count = 0; + bool stop; + + while (1) { + stop = (pio_rx_frame(q) == 0); + if (stop) + break; + cond_resched(); + if (WARN_ON_ONCE(++count > 10000)) + break; + } +} + +static void b43_pio_tx_suspend_queue(struct b43_pio_txqueue *q) +{ + if (q->rev >= 8) { + b43_piotx_write32(q, B43_PIO8_TXCTL, + b43_piotx_read32(q, B43_PIO8_TXCTL) + | B43_PIO8_TXCTL_SUSPREQ); + } else { + b43_piotx_write16(q, B43_PIO_TXCTL, + b43_piotx_read16(q, B43_PIO_TXCTL) + | B43_PIO_TXCTL_SUSPREQ); + } +} + +static void b43_pio_tx_resume_queue(struct b43_pio_txqueue *q) +{ + if (q->rev >= 8) { + b43_piotx_write32(q, B43_PIO8_TXCTL, + b43_piotx_read32(q, B43_PIO8_TXCTL) + & ~B43_PIO8_TXCTL_SUSPREQ); + } else { + b43_piotx_write16(q, B43_PIO_TXCTL, + b43_piotx_read16(q, B43_PIO_TXCTL) + & ~B43_PIO_TXCTL_SUSPREQ); + } +} + +void b43_pio_tx_suspend(struct b43_wldev *dev) +{ + b43_power_saving_ctl_bits(dev, B43_PS_AWAKE); + b43_pio_tx_suspend_queue(dev->pio.tx_queue_AC_BK); + b43_pio_tx_suspend_queue(dev->pio.tx_queue_AC_BE); + b43_pio_tx_suspend_queue(dev->pio.tx_queue_AC_VI); + b43_pio_tx_suspend_queue(dev->pio.tx_queue_AC_VO); + b43_pio_tx_suspend_queue(dev->pio.tx_queue_mcast); +} + +void b43_pio_tx_resume(struct b43_wldev *dev) +{ + b43_pio_tx_resume_queue(dev->pio.tx_queue_mcast); + b43_pio_tx_resume_queue(dev->pio.tx_queue_AC_VO); + b43_pio_tx_resume_queue(dev->pio.tx_queue_AC_VI); + b43_pio_tx_resume_queue(dev->pio.tx_queue_AC_BE); + b43_pio_tx_resume_queue(dev->pio.tx_queue_AC_BK); + b43_power_saving_ctl_bits(dev, 0); +} diff --git a/drivers/net/wireless/b43/pio.h b/drivers/net/wireless/b43/pio.h new file mode 100644 index 0000000..1e51614 --- /dev/null +++ b/drivers/net/wireless/b43/pio.h @@ -0,0 +1,165 @@ +#ifndef B43_PIO_H_ +#define B43_PIO_H_ + +#include "b43.h" + +#include +#include +#include +#include + + +/*** Registers for PIO queues up to revision 7. ***/ +/* TX queue. */ +#define B43_PIO_TXCTL 0x00 +#define B43_PIO_TXCTL_WRITELO 0x0001 +#define B43_PIO_TXCTL_WRITEHI 0x0002 +#define B43_PIO_TXCTL_EOF 0x0004 +#define B43_PIO_TXCTL_FREADY 0x0008 +#define B43_PIO_TXCTL_FLUSHREQ 0x0020 +#define B43_PIO_TXCTL_FLUSHPEND 0x0040 +#define B43_PIO_TXCTL_SUSPREQ 0x0080 +#define B43_PIO_TXCTL_QSUSP 0x0100 +#define B43_PIO_TXCTL_COMMCNT 0xFC00 +#define B43_PIO_TXCTL_COMMCNT_SHIFT 10 +#define B43_PIO_TXDATA 0x02 +#define B43_PIO_TXQBUFSIZE 0x04 +/* RX queue. */ +#define B43_PIO_RXCTL 0x00 +#define B43_PIO_RXCTL_FRAMERDY 0x0001 +#define B43_PIO_RXCTL_DATARDY 0x0002 +#define B43_PIO_RXDATA 0x02 + +/*** Registers for PIO queues revision 8 and later. ***/ +/* TX queue */ +#define B43_PIO8_TXCTL 0x00 +#define B43_PIO8_TXCTL_0_7 0x00000001 +#define B43_PIO8_TXCTL_8_15 0x00000002 +#define B43_PIO8_TXCTL_16_23 0x00000004 +#define B43_PIO8_TXCTL_24_31 0x00000008 +#define B43_PIO8_TXCTL_EOF 0x00000010 +#define B43_PIO8_TXCTL_FREADY 0x00000080 +#define B43_PIO8_TXCTL_SUSPREQ 0x00000100 +#define B43_PIO8_TXCTL_QSUSP 0x00000200 +#define B43_PIO8_TXCTL_FLUSHREQ 0x00000400 +#define B43_PIO8_TXCTL_FLUSHPEND 0x00000800 +#define B43_PIO8_TXDATA 0x04 +/* RX queue */ +#define B43_PIO8_RXCTL 0x00 +#define B43_PIO8_RXCTL_FRAMERDY 0x00000001 +#define B43_PIO8_RXCTL_DATARDY 0x00000002 +#define B43_PIO8_RXDATA 0x04 + + +/* The maximum number of TX-packets the HW can handle. */ +#define B43_PIO_MAX_NR_TXPACKETS 32 + + +struct b43_pio_txpacket { + /* Pointer to the TX queue we belong to. */ + struct b43_pio_txqueue *queue; + /* The TX data packet. */ + struct sk_buff *skb; + /* Index in the (struct b43_pio_txqueue)->packets array. */ + u8 index; + + struct list_head list; +}; + +struct b43_pio_txqueue { + struct b43_wldev *dev; + u16 mmio_base; + + /* The device queue buffer size in bytes. */ + u16 buffer_size; + /* The number of used bytes in the device queue buffer. */ + u16 buffer_used; + /* The number of packets that can still get queued. + * This is decremented on queueing a packet and incremented + * after receiving the transmit status. */ + u16 free_packet_slots; + + /* True, if the mac80211 queue was stopped due to overflow at TX. */ + bool stopped; + /* Our b43 queue index number */ + u8 index; + /* The mac80211 QoS queue priority. */ + u8 queue_prio; + + /* Buffer for TX packet meta data. */ + struct b43_pio_txpacket packets[B43_PIO_MAX_NR_TXPACKETS]; + struct list_head packets_list; + + /* Shortcut to the 802.11 core revision. This is to + * avoid horrible pointer dereferencing in the fastpaths. */ + u8 rev; +}; + +struct b43_pio_rxqueue { + struct b43_wldev *dev; + u16 mmio_base; + + /* Shortcut to the 802.11 core revision. This is to + * avoid horrible pointer dereferencing in the fastpaths. */ + u8 rev; +}; + + +static inline u16 b43_piotx_read16(struct b43_pio_txqueue *q, u16 offset) +{ + return b43_read16(q->dev, q->mmio_base + offset); +} + +static inline u32 b43_piotx_read32(struct b43_pio_txqueue *q, u16 offset) +{ + return b43_read32(q->dev, q->mmio_base + offset); +} + +static inline void b43_piotx_write16(struct b43_pio_txqueue *q, + u16 offset, u16 value) +{ + b43_write16(q->dev, q->mmio_base + offset, value); +} + +static inline void b43_piotx_write32(struct b43_pio_txqueue *q, + u16 offset, u32 value) +{ + b43_write32(q->dev, q->mmio_base + offset, value); +} + + +static inline u16 b43_piorx_read16(struct b43_pio_rxqueue *q, u16 offset) +{ + return b43_read16(q->dev, q->mmio_base + offset); +} + +static inline u32 b43_piorx_read32(struct b43_pio_rxqueue *q, u16 offset) +{ + return b43_read32(q->dev, q->mmio_base + offset); +} + +static inline void b43_piorx_write16(struct b43_pio_rxqueue *q, + u16 offset, u16 value) +{ + b43_write16(q->dev, q->mmio_base + offset, value); +} + +static inline void b43_piorx_write32(struct b43_pio_rxqueue *q, + u16 offset, u32 value) +{ + b43_write32(q->dev, q->mmio_base + offset, value); +} + + +int b43_pio_init(struct b43_wldev *dev); +void b43_pio_free(struct b43_wldev *dev); + +int b43_pio_tx(struct b43_wldev *dev, struct sk_buff *skb); +void b43_pio_handle_txstatus(struct b43_wldev *dev, + const struct b43_txstatus *status); +void b43_pio_rx(struct b43_pio_rxqueue *q); + +void b43_pio_tx_suspend(struct b43_wldev *dev); +void b43_pio_tx_resume(struct b43_wldev *dev); + +#endif /* B43_PIO_H_ */ diff --git a/drivers/net/wireless/b43/radio_2055.c b/drivers/net/wireless/b43/radio_2055.c new file mode 100644 index 0000000..5289a18 --- /dev/null +++ b/drivers/net/wireless/b43/radio_2055.c @@ -0,0 +1,1335 @@ +/* + + Broadcom B43 wireless driver + IEEE 802.11n PHY and radio device data tables + + Copyright (c) 2008 Michael Buesch + Copyright (c) 2010 RafaÅ‚ MiÅ‚ecki + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" +#include "radio_2055.h" +#include "phy_common.h" + +struct b2055_inittab_entry { + /* Value to write if we use the 5GHz band. */ + u16 ghz5; + /* Value to write if we use the 2.4GHz band. */ + u16 ghz2; + /* Flags */ + u8 flags; +#define B2055_INITTAB_ENTRY_OK 0x01 +#define B2055_INITTAB_UPLOAD 0x02 +}; +#define UPLOAD .flags = B2055_INITTAB_ENTRY_OK | B2055_INITTAB_UPLOAD +#define NOUPLOAD .flags = B2055_INITTAB_ENTRY_OK + +static const struct b2055_inittab_entry b2055_inittab [] = { + [B2055_SP_PINPD] = { .ghz5 = 0x0080, .ghz2 = 0x0080, NOUPLOAD, }, + [B2055_C1_SP_RSSI] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C1_SP_PDMISC] = { .ghz5 = 0x0027, .ghz2 = 0x0027, NOUPLOAD, }, + [B2055_C2_SP_RSSI] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C2_SP_PDMISC] = { .ghz5 = 0x0027, .ghz2 = 0x0027, NOUPLOAD, }, + [B2055_C1_SP_RXGC1] = { .ghz5 = 0x007F, .ghz2 = 0x007F, UPLOAD, }, + [B2055_C1_SP_RXGC2] = { .ghz5 = 0x0007, .ghz2 = 0x0007, UPLOAD, }, + [B2055_C2_SP_RXGC1] = { .ghz5 = 0x007F, .ghz2 = 0x007F, UPLOAD, }, + [B2055_C2_SP_RXGC2] = { .ghz5 = 0x0007, .ghz2 = 0x0007, UPLOAD, }, + [B2055_C1_SP_LPFBWSEL] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2055_C2_SP_LPFBWSEL] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2055_C1_SP_TXGC1] = { .ghz5 = 0x004F, .ghz2 = 0x004F, UPLOAD, }, + [B2055_C1_SP_TXGC2] = { .ghz5 = 0x0005, .ghz2 = 0x0005, UPLOAD, }, + [B2055_C2_SP_TXGC1] = { .ghz5 = 0x004F, .ghz2 = 0x004F, UPLOAD, }, + [B2055_C2_SP_TXGC2] = { .ghz5 = 0x0005, .ghz2 = 0x0005, UPLOAD, }, + [B2055_MASTER1] = { .ghz5 = 0x00D0, .ghz2 = 0x00D0, NOUPLOAD, }, + [B2055_MASTER2] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2055_PD_LGEN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_PD_PLLTS] = { .ghz5 = 0x0040, .ghz2 = 0x0040, NOUPLOAD, }, + [B2055_C1_PD_LGBUF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C1_PD_TX] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C1_PD_RXTX] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C1_PD_RSSIMISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C2_PD_LGBUF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C2_PD_TX] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C2_PD_RXTX] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C2_PD_RSSIMISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_PWRDET_LGEN] = { .ghz5 = 0x00C0, .ghz2 = 0x00C0, NOUPLOAD, }, + [B2055_C1_PWRDET_LGBUF] = { .ghz5 = 0x00FF, .ghz2 = 0x00FF, NOUPLOAD, }, + [B2055_C1_PWRDET_RXTX] = { .ghz5 = 0x00C0, .ghz2 = 0x00C0, NOUPLOAD, }, + [B2055_C2_PWRDET_LGBUF] = { .ghz5 = 0x00FF, .ghz2 = 0x00FF, NOUPLOAD, }, + [B2055_C2_PWRDET_RXTX] = { .ghz5 = 0x00C0, .ghz2 = 0x00C0, NOUPLOAD, }, + [B2055_RRCCAL_CS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_RRCCAL_NOPTSEL] = { .ghz5 = 0x002C, .ghz2 = 0x002C, NOUPLOAD, }, + [B2055_CAL_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_CAL_COUT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_CAL_COUT2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_CAL_CVARCTL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_CAL_RVARCTL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_CAL_LPOCTL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_CAL_TS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_CAL_RCCALRTS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_CAL_RCALRTS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_PADDRV] = { .ghz5 = 0x00A4, .ghz2 = 0x00A4, NOUPLOAD, }, + [B2055_XOCTL1] = { .ghz5 = 0x0038, .ghz2 = 0x0038, NOUPLOAD, }, + [B2055_XOCTL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_XOREGUL] = { .ghz5 = 0x0004, .ghz2 = 0x0004, UPLOAD, }, + [B2055_XOMISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_PLL_LFC1] = { .ghz5 = 0x000A, .ghz2 = 0x000A, NOUPLOAD, }, + [B2055_PLL_CALVTH] = { .ghz5 = 0x0087, .ghz2 = 0x0087, NOUPLOAD, }, + [B2055_PLL_LFC2] = { .ghz5 = 0x0009, .ghz2 = 0x0009, NOUPLOAD, }, + [B2055_PLL_REF] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2055_PLL_LFR1] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2055_PLL_PFDCP] = { .ghz5 = 0x0018, .ghz2 = 0x0018, UPLOAD, }, + [B2055_PLL_IDAC_CPOPAMP] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2055_PLL_CPREG] = { .ghz5 = 0x0004, .ghz2 = 0x0004, UPLOAD, }, + [B2055_PLL_RCAL] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2055_RF_PLLMOD0] = { .ghz5 = 0x009E, .ghz2 = 0x009E, NOUPLOAD, }, + [B2055_RF_PLLMOD1] = { .ghz5 = 0x0009, .ghz2 = 0x0009, NOUPLOAD, }, + [B2055_RF_MMDIDAC1] = { .ghz5 = 0x00C8, .ghz2 = 0x00C8, UPLOAD, }, + [B2055_RF_MMDIDAC0] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2055_RF_MMDSP] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_VCO_CAL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_VCO_CAL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_VCO_CAL3] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2055_VCO_CAL4] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2055_VCO_CAL5] = { .ghz5 = 0x0096, .ghz2 = 0x0096, NOUPLOAD, }, + [B2055_VCO_CAL6] = { .ghz5 = 0x003E, .ghz2 = 0x003E, NOUPLOAD, }, + [B2055_VCO_CAL7] = { .ghz5 = 0x003E, .ghz2 = 0x003E, NOUPLOAD, }, + [B2055_VCO_CAL8] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2055_VCO_CAL9] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2055_VCO_CAL10] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2055_VCO_CAL11] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2055_VCO_CAL12] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_VCO_CAL13] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_VCO_CAL14] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_VCO_CAL15] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_VCO_CAL16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_VCO_KVCO] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2055_VCO_CAPTAIL] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2055_VCO_IDACVCO] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2055_VCO_REG] = { .ghz5 = 0x0084, .ghz2 = 0x0084, UPLOAD, }, + [B2055_PLL_RFVTH] = { .ghz5 = 0x00C3, .ghz2 = 0x00C3, NOUPLOAD, }, + [B2055_LGBUF_CENBUF] = { .ghz5 = 0x008F, .ghz2 = 0x008F, NOUPLOAD, }, + [B2055_LGEN_TUNE1] = { .ghz5 = 0x00FF, .ghz2 = 0x00FF, NOUPLOAD, }, + [B2055_LGEN_TUNE2] = { .ghz5 = 0x00FF, .ghz2 = 0x00FF, NOUPLOAD, }, + [B2055_LGEN_IDAC1] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2055_LGEN_IDAC2] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2055_LGEN_BIASC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_LGEN_BIASIDAC] = { .ghz5 = 0x00CC, .ghz2 = 0x00CC, NOUPLOAD, }, + [B2055_LGEN_RCAL] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2055_LGEN_DIV] = { .ghz5 = 0x0080, .ghz2 = 0x0080, NOUPLOAD, }, + [B2055_LGEN_SPARE2] = { .ghz5 = 0x0080, .ghz2 = 0x0080, NOUPLOAD, }, + [B2055_C1_LGBUF_ATUNE] = { .ghz5 = 0x00F8, .ghz2 = 0x00F8, NOUPLOAD, }, + [B2055_C1_LGBUF_GTUNE] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2055_C1_LGBUF_DIV] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2055_C1_LGBUF_AIDAC] = { .ghz5 = 0x0088, .ghz2 = 0x0008, UPLOAD, }, + [B2055_C1_LGBUF_GIDAC] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2055_C1_LGBUF_IDACFO] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C1_LGBUF_SPARE] = { .ghz5 = 0x0001, .ghz2 = 0x0001, UPLOAD, }, + [B2055_C1_RX_RFSPC1] = { .ghz5 = 0x008A, .ghz2 = 0x008A, NOUPLOAD, }, + [B2055_C1_RX_RFR1] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2055_C1_RX_RFR2] = { .ghz5 = 0x0083, .ghz2 = 0x0083, NOUPLOAD, }, + [B2055_C1_RX_RFRCAL] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2055_C1_RX_BB_BLCMP] = { .ghz5 = 0x00A0, .ghz2 = 0x00A0, NOUPLOAD, }, + [B2055_C1_RX_BB_LPF] = { .ghz5 = 0x000A, .ghz2 = 0x000A, NOUPLOAD, }, + [B2055_C1_RX_BB_MIDACHP] = { .ghz5 = 0x0087, .ghz2 = 0x0087, UPLOAD, }, + [B2055_C1_RX_BB_VGA1IDAC] = { .ghz5 = 0x002A, .ghz2 = 0x002A, NOUPLOAD, }, + [B2055_C1_RX_BB_VGA2IDAC] = { .ghz5 = 0x002A, .ghz2 = 0x002A, NOUPLOAD, }, + [B2055_C1_RX_BB_VGA3IDAC] = { .ghz5 = 0x002A, .ghz2 = 0x002A, NOUPLOAD, }, + [B2055_C1_RX_BB_BUFOCTL] = { .ghz5 = 0x002A, .ghz2 = 0x002A, NOUPLOAD, }, + [B2055_C1_RX_BB_RCCALCTL] = { .ghz5 = 0x0018, .ghz2 = 0x0018, NOUPLOAD, }, + [B2055_C1_RX_BB_RSSICTL1] = { .ghz5 = 0x006A, .ghz2 = 0x006A, UPLOAD, }, + [B2055_C1_RX_BB_RSSICTL2] = { .ghz5 = 0x00AB, .ghz2 = 0x00AB, UPLOAD, }, + [B2055_C1_RX_BB_RSSICTL3] = { .ghz5 = 0x0013, .ghz2 = 0x0013, UPLOAD, }, + [B2055_C1_RX_BB_RSSICTL4] = { .ghz5 = 0x00C1, .ghz2 = 0x00C1, UPLOAD, }, + [B2055_C1_RX_BB_RSSICTL5] = { .ghz5 = 0x00AA, .ghz2 = 0x00AA, UPLOAD, }, + [B2055_C1_RX_BB_REG] = { .ghz5 = 0x0087, .ghz2 = 0x0087, UPLOAD, }, + [B2055_C1_RX_BB_SPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C1_RX_TXBBRCAL] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2055_C1_TX_RF_SPGA] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2055_C1_TX_RF_SPAD] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2055_C1_TX_RF_CNTPGA1] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2055_C1_TX_RF_CNTPAD1] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2055_C1_TX_RF_PGAIDAC] = { .ghz5 = 0x0097, .ghz2 = 0x0097, UPLOAD, }, + [B2055_C1_TX_PGAPADTN] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2055_C1_TX_PADIDAC1] = { .ghz5 = 0x0014, .ghz2 = 0x0014, UPLOAD, }, + [B2055_C1_TX_PADIDAC2] = { .ghz5 = 0x0033, .ghz2 = 0x0033, NOUPLOAD, }, + [B2055_C1_TX_MXBGTRIM] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2055_C1_TX_RF_RCAL] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2055_C1_TX_RF_PADTSSI1] = { .ghz5 = 0x0003, .ghz2 = 0x0003, UPLOAD, }, + [B2055_C1_TX_RF_PADTSSI2] = { .ghz5 = 0x000A, .ghz2 = 0x000A, NOUPLOAD, }, + [B2055_C1_TX_RF_SPARE] = { .ghz5 = 0x0003, .ghz2 = 0x0003, UPLOAD, }, + [B2055_C1_TX_RF_IQCAL1] = { .ghz5 = 0x002A, .ghz2 = 0x002A, NOUPLOAD, }, + [B2055_C1_TX_RF_IQCAL2] = { .ghz5 = 0x00A4, .ghz2 = 0x00A4, NOUPLOAD, }, + [B2055_C1_TXBB_RCCAL] = { .ghz5 = 0x0018, .ghz2 = 0x0018, NOUPLOAD, }, + [B2055_C1_TXBB_LPF1] = { .ghz5 = 0x0028, .ghz2 = 0x0028, NOUPLOAD, }, + [B2055_C1_TX_VOSCNCL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C1_TX_LPF_MXGMIDAC] = { .ghz5 = 0x004A, .ghz2 = 0x004A, NOUPLOAD, }, + [B2055_C1_TX_BB_MXGM] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C2_LGBUF_ATUNE] = { .ghz5 = 0x00F8, .ghz2 = 0x00F8, NOUPLOAD, }, + [B2055_C2_LGBUF_GTUNE] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2055_C2_LGBUF_DIV] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2055_C2_LGBUF_AIDAC] = { .ghz5 = 0x0088, .ghz2 = 0x0008, UPLOAD, }, + [B2055_C2_LGBUF_GIDAC] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2055_C2_LGBUF_IDACFO] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C2_LGBUF_SPARE] = { .ghz5 = 0x0001, .ghz2 = 0x0001, UPLOAD, }, + [B2055_C2_RX_RFSPC1] = { .ghz5 = 0x008A, .ghz2 = 0x008A, NOUPLOAD, }, + [B2055_C2_RX_RFR1] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2055_C2_RX_RFR2] = { .ghz5 = 0x0083, .ghz2 = 0x0083, NOUPLOAD, }, + [B2055_C2_RX_RFRCAL] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2055_C2_RX_BB_BLCMP] = { .ghz5 = 0x00A0, .ghz2 = 0x00A0, NOUPLOAD, }, + [B2055_C2_RX_BB_LPF] = { .ghz5 = 0x000A, .ghz2 = 0x000A, NOUPLOAD, }, + [B2055_C2_RX_BB_MIDACHP] = { .ghz5 = 0x0087, .ghz2 = 0x0087, UPLOAD, }, + [B2055_C2_RX_BB_VGA1IDAC] = { .ghz5 = 0x002A, .ghz2 = 0x002A, NOUPLOAD, }, + [B2055_C2_RX_BB_VGA2IDAC] = { .ghz5 = 0x002A, .ghz2 = 0x002A, NOUPLOAD, }, + [B2055_C2_RX_BB_VGA3IDAC] = { .ghz5 = 0x002A, .ghz2 = 0x002A, NOUPLOAD, }, + [B2055_C2_RX_BB_BUFOCTL] = { .ghz5 = 0x002A, .ghz2 = 0x002A, NOUPLOAD, }, + [B2055_C2_RX_BB_RCCALCTL] = { .ghz5 = 0x0018, .ghz2 = 0x0018, NOUPLOAD, }, + [B2055_C2_RX_BB_RSSICTL1] = { .ghz5 = 0x006A, .ghz2 = 0x006A, UPLOAD, }, + [B2055_C2_RX_BB_RSSICTL2] = { .ghz5 = 0x00AB, .ghz2 = 0x00AB, UPLOAD, }, + [B2055_C2_RX_BB_RSSICTL3] = { .ghz5 = 0x0013, .ghz2 = 0x0013, UPLOAD, }, + [B2055_C2_RX_BB_RSSICTL4] = { .ghz5 = 0x00C1, .ghz2 = 0x00C1, UPLOAD, }, + [B2055_C2_RX_BB_RSSICTL5] = { .ghz5 = 0x00AA, .ghz2 = 0x00AA, UPLOAD, }, + [B2055_C2_RX_BB_REG] = { .ghz5 = 0x0087, .ghz2 = 0x0087, UPLOAD, }, + [B2055_C2_RX_BB_SPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C2_RX_TXBBRCAL] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2055_C2_TX_RF_SPGA] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2055_C2_TX_RF_SPAD] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2055_C2_TX_RF_CNTPGA1] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2055_C2_TX_RF_CNTPAD1] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2055_C2_TX_RF_PGAIDAC] = { .ghz5 = 0x0097, .ghz2 = 0x0097, UPLOAD, }, + [B2055_C2_TX_PGAPADTN] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2055_C2_TX_PADIDAC1] = { .ghz5 = 0x0014, .ghz2 = 0x0014, UPLOAD, }, + [B2055_C2_TX_PADIDAC2] = { .ghz5 = 0x0033, .ghz2 = 0x0033, NOUPLOAD, }, + [B2055_C2_TX_MXBGTRIM] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2055_C2_TX_RF_RCAL] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2055_C2_TX_RF_PADTSSI1] = { .ghz5 = 0x0003, .ghz2 = 0x0003, UPLOAD, }, + [B2055_C2_TX_RF_PADTSSI2] = { .ghz5 = 0x000A, .ghz2 = 0x000A, NOUPLOAD, }, + [B2055_C2_TX_RF_SPARE] = { .ghz5 = 0x0003, .ghz2 = 0x0003, UPLOAD, }, + [B2055_C2_TX_RF_IQCAL1] = { .ghz5 = 0x002A, .ghz2 = 0x002A, NOUPLOAD, }, + [B2055_C2_TX_RF_IQCAL2] = { .ghz5 = 0x00A4, .ghz2 = 0x00A4, NOUPLOAD, }, + [B2055_C2_TXBB_RCCAL] = { .ghz5 = 0x0018, .ghz2 = 0x0018, NOUPLOAD, }, + [B2055_C2_TXBB_LPF1] = { .ghz5 = 0x0028, .ghz2 = 0x0028, NOUPLOAD, }, + [B2055_C2_TX_VOSCNCL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C2_TX_LPF_MXGMIDAC] = { .ghz5 = 0x004A, .ghz2 = 0x004A, NOUPLOAD, }, + [B2055_C2_TX_BB_MXGM] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_PRG_GCHP21] = { .ghz5 = 0x0071, .ghz2 = 0x0071, NOUPLOAD, }, + [B2055_PRG_GCHP22] = { .ghz5 = 0x0072, .ghz2 = 0x0072, NOUPLOAD, }, + [B2055_PRG_GCHP23] = { .ghz5 = 0x0073, .ghz2 = 0x0073, NOUPLOAD, }, + [B2055_PRG_GCHP24] = { .ghz5 = 0x0074, .ghz2 = 0x0074, NOUPLOAD, }, + [B2055_PRG_GCHP25] = { .ghz5 = 0x0075, .ghz2 = 0x0075, NOUPLOAD, }, + [B2055_PRG_GCHP26] = { .ghz5 = 0x0076, .ghz2 = 0x0076, NOUPLOAD, }, + [B2055_PRG_GCHP27] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2055_PRG_GCHP28] = { .ghz5 = 0x0078, .ghz2 = 0x0078, NOUPLOAD, }, + [B2055_PRG_GCHP29] = { .ghz5 = 0x0079, .ghz2 = 0x0079, NOUPLOAD, }, + [B2055_PRG_GCHP30] = { .ghz5 = 0x007A, .ghz2 = 0x007A, NOUPLOAD, }, + [0xC7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xC8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xC9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xCA] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xCB] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xCC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C1_LNA_GAINBST] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xCE] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [0xCF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xD0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xD1] = { .ghz5 = 0x0018, .ghz2 = 0x0018, NOUPLOAD, }, + [B2055_C1_B0NB_RSSIVCM] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [0xD3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xD4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xD5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C1_GENSPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xD7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xD8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C2_LNA_GAINBST] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xDA] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [0xDB] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xDC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xDD] = { .ghz5 = 0x0018, .ghz2 = 0x0018, NOUPLOAD, }, + [B2055_C2_B0NB_RSSIVCM] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [0xDF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xE0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [0xE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2055_C2_GENSPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, +}; + +#define RADIOREGS(r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, \ + r12, r13, r14, r15, r16, r17, r18, r19, r20, r21) \ + .radio_pll_ref = r0, \ + .radio_rf_pllmod0 = r1, \ + .radio_rf_pllmod1 = r2, \ + .radio_vco_captail = r3, \ + .radio_vco_cal1 = r4, \ + .radio_vco_cal2 = r5, \ + .radio_pll_lfc1 = r6, \ + .radio_pll_lfr1 = r7, \ + .radio_pll_lfc2 = r8, \ + .radio_lgbuf_cenbuf = r9, \ + .radio_lgen_tune1 = r10, \ + .radio_lgen_tune2 = r11, \ + .radio_c1_lgbuf_atune = r12, \ + .radio_c1_lgbuf_gtune = r13, \ + .radio_c1_rx_rfr1 = r14, \ + .radio_c1_tx_pgapadtn = r15, \ + .radio_c1_tx_mxbgtrim = r16, \ + .radio_c2_lgbuf_atune = r17, \ + .radio_c2_lgbuf_gtune = r18, \ + .radio_c2_rx_rfr1 = r19, \ + .radio_c2_tx_pgapadtn = r20, \ + .radio_c2_tx_mxbgtrim = r21 + +#define PHYREGS(r0, r1, r2, r3, r4, r5) \ + .phy_regs.phy_bw1a = r0, \ + .phy_regs.phy_bw2 = r1, \ + .phy_regs.phy_bw3 = r2, \ + .phy_regs.phy_bw4 = r3, \ + .phy_regs.phy_bw5 = r4, \ + .phy_regs.phy_bw6 = r5 + +static const struct b43_nphy_channeltab_entry_rev2 b43_nphy_channeltab_rev2[] = { + { .channel = 184, + .freq = 4920, /* MHz */ + .unk2 = 3280, + RADIOREGS(0x71, 0xEC, 0x01, 0x0F, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0x0F, + 0x8F, 0xFF, 0x00, 0x0F, 0x0F, 0x8F), + PHYREGS(0x07B4, 0x07B0, 0x07AC, 0x0214, 0x0215, 0x0216), + }, + { .channel = 186, + .freq = 4930, /* MHz */ + .unk2 = 3287, + RADIOREGS(0x71, 0xED, 0x01, 0x0F, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0x0F, + 0x8F, 0xFF, 0x00, 0x0F, 0x0F, 0x8F), + PHYREGS(0x07B8, 0x07B4, 0x07B0, 0x0213, 0x0214, 0x0215), + }, + { .channel = 188, + .freq = 4940, /* MHz */ + .unk2 = 3293, + RADIOREGS(0x71, 0xEE, 0x01, 0x0F, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xEE, 0xEE, 0xFF, 0x00, 0x0F, 0x0F, + 0x8F, 0xFF, 0x00, 0x0F, 0x0F, 0x8F), + PHYREGS(0x07BC, 0x07B8, 0x07B4, 0x0212, 0x0213, 0x0214), + }, + { .channel = 190, + .freq = 4950, /* MHz */ + .unk2 = 3300, + RADIOREGS(0x71, 0xEF, 0x01, 0x0F, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xEE, 0xEE, 0xFF, 0x00, 0x0F, 0x0F, + 0x8F, 0xFF, 0x00, 0x0F, 0x0F, 0x8F), + PHYREGS(0x07C0, 0x07BC, 0x07B8, 0x0211, 0x0212, 0x0213), + }, + { .channel = 192, + .freq = 4960, /* MHz */ + .unk2 = 3307, + RADIOREGS(0x71, 0xF0, 0x01, 0x0F, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xEE, 0xEE, 0xFF, 0x00, 0x0F, 0x0F, + 0x8F, 0xFF, 0x00, 0x0F, 0x0F, 0x8F), + PHYREGS(0x07C4, 0x07C0, 0x07BC, 0x020F, 0x0211, 0x0212), + }, + { .channel = 194, + .freq = 4970, /* MHz */ + .unk2 = 3313, + RADIOREGS(0x71, 0xF1, 0x01, 0x0F, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xEE, 0xEE, 0xFF, 0x00, 0x0F, 0x0F, + 0x8F, 0xFF, 0x00, 0x0F, 0x0F, 0x8F), + PHYREGS(0x07C8, 0x07C4, 0x07C0, 0x020E, 0x020F, 0x0211), + }, + { .channel = 196, + .freq = 4980, /* MHz */ + .unk2 = 3320, + RADIOREGS(0x71, 0xF2, 0x01, 0x0E, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xDD, 0xDD, 0xFF, 0x00, 0x0F, 0x0F, + 0x8F, 0xFF, 0x00, 0x0F, 0x0F, 0x8F), + PHYREGS(0x07CC, 0x07C8, 0x07C4, 0x020D, 0x020E, 0x020F), + }, + { .channel = 198, + .freq = 4990, /* MHz */ + .unk2 = 3327, + RADIOREGS(0x71, 0xF3, 0x01, 0x0E, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xDD, 0xDD, 0xFF, 0x00, 0x0F, 0x0F, + 0x8F, 0xFF, 0x00, 0x0F, 0x0F, 0x8F), + PHYREGS(0x07D0, 0x07CC, 0x07C8, 0x020C, 0x020D, 0x020E), + }, + { .channel = 200, + .freq = 5000, /* MHz */ + .unk2 = 3333, + RADIOREGS(0x71, 0xF4, 0x01, 0x0E, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xDD, 0xDD, 0xFF, 0x00, 0x0F, 0x0F, + 0x8F, 0xFF, 0x00, 0x0F, 0x0F, 0x8F), + PHYREGS(0x07D4, 0x07D0, 0x07CC, 0x020B, 0x020C, 0x020D), + }, + { .channel = 202, + .freq = 5010, /* MHz */ + .unk2 = 3340, + RADIOREGS(0x71, 0xF5, 0x01, 0x0E, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xDD, 0xDD, 0xFF, 0x00, 0x0F, 0x0F, + 0x8F, 0xFF, 0x00, 0x0F, 0x0F, 0x8F), + PHYREGS(0x07D8, 0x07D4, 0x07D0, 0x020A, 0x020B, 0x020C), + }, + { .channel = 204, + .freq = 5020, /* MHz */ + .unk2 = 3347, + RADIOREGS(0x71, 0xF6, 0x01, 0x0E, 0xF7, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xCC, 0xCC, 0xFF, 0x00, 0x0F, 0x0F, + 0x8F, 0xFF, 0x00, 0x0F, 0x0F, 0x8F), + PHYREGS(0x07DC, 0x07D8, 0x07D4, 0x0209, 0x020A, 0x020B), + }, + { .channel = 206, + .freq = 5030, /* MHz */ + .unk2 = 3353, + RADIOREGS(0x71, 0xF7, 0x01, 0x0E, 0xF7, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xCC, 0xCC, 0xFF, 0x00, 0x0F, 0x0F, + 0x8F, 0xFF, 0x00, 0x0F, 0x0F, 0x8F), + PHYREGS(0x07E0, 0x07DC, 0x07D8, 0x0208, 0x0209, 0x020A), + }, + { .channel = 208, + .freq = 5040, /* MHz */ + .unk2 = 3360, + RADIOREGS(0x71, 0xF8, 0x01, 0x0D, 0xEF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xCC, 0xCC, 0xFF, 0x00, 0x0F, 0x0F, + 0x8F, 0xFF, 0x00, 0x0F, 0x0F, 0x8F), + PHYREGS(0x07E4, 0x07E0, 0x07DC, 0x0207, 0x0208, 0x0209), + }, + { .channel = 210, + .freq = 5050, /* MHz */ + .unk2 = 3367, + RADIOREGS(0x71, 0xF9, 0x01, 0x0D, 0xEF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xCC, 0xCC, 0xFF, 0x00, 0x0F, 0x0F, + 0x8F, 0xFF, 0x00, 0x0F, 0x0F, 0x8F), + PHYREGS(0x07E8, 0x07E4, 0x07E0, 0x0206, 0x0207, 0x0208), + }, + { .channel = 212, + .freq = 5060, /* MHz */ + .unk2 = 3373, + RADIOREGS(0x71, 0xFA, 0x01, 0x0D, 0xE6, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xBB, 0xBB, 0xFF, 0x00, 0x0E, 0x0F, + 0x8E, 0xFF, 0x00, 0x0E, 0x0F, 0x8E), + PHYREGS(0x07EC, 0x07E8, 0x07E4, 0x0205, 0x0206, 0x0207), + }, + { .channel = 214, + .freq = 5070, /* MHz */ + .unk2 = 3380, + RADIOREGS(0x71, 0xFB, 0x01, 0x0D, 0xE6, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xBB, 0xBB, 0xFF, 0x00, 0x0E, 0x0F, + 0x8E, 0xFF, 0x00, 0x0E, 0x0F, 0x8E), + PHYREGS(0x07F0, 0x07EC, 0x07E8, 0x0204, 0x0205, 0x0206), + }, + { .channel = 216, + .freq = 5080, /* MHz */ + .unk2 = 3387, + RADIOREGS(0x71, 0xFC, 0x01, 0x0D, 0xDE, 0x01, 0x04, 0x0A, + 0x00, 0x8E, 0xBB, 0xBB, 0xEE, 0x00, 0x0E, 0x0F, + 0x8D, 0xEE, 0x00, 0x0E, 0x0F, 0x8D), + PHYREGS(0x07F4, 0x07F0, 0x07EC, 0x0203, 0x0204, 0x0205), + }, + { .channel = 218, + .freq = 5090, /* MHz */ + .unk2 = 3393, + RADIOREGS(0x71, 0xFD, 0x01, 0x0D, 0xDE, 0x01, 0x04, 0x0A, + 0x00, 0x8E, 0xBB, 0xBB, 0xEE, 0x00, 0x0E, 0x0F, + 0x8D, 0xEE, 0x00, 0x0E, 0x0F, 0x8D), + PHYREGS(0x07F8, 0x07F4, 0x07F0, 0x0202, 0x0203, 0x0204), + }, + { .channel = 220, + .freq = 5100, /* MHz */ + .unk2 = 3400, + RADIOREGS(0x71, 0xFE, 0x01, 0x0C, 0xD6, 0x01, 0x04, 0x0A, + 0x00, 0x8E, 0xAA, 0xAA, 0xEE, 0x00, 0x0D, 0x0F, + 0x8D, 0xEE, 0x00, 0x0D, 0x0F, 0x8D), + PHYREGS(0x07FC, 0x07F8, 0x07F4, 0x0201, 0x0202, 0x0203), + }, + { .channel = 222, + .freq = 5110, /* MHz */ + .unk2 = 3407, + RADIOREGS(0x71, 0xFF, 0x01, 0x0C, 0xD6, 0x01, 0x04, 0x0A, + 0x00, 0x8E, 0xAA, 0xAA, 0xEE, 0x00, 0x0D, 0x0F, + 0x8D, 0xEE, 0x00, 0x0D, 0x0F, 0x8D), + PHYREGS(0x0800, 0x07FC, 0x07F8, 0x0200, 0x0201, 0x0202), + }, + { .channel = 224, + .freq = 5120, /* MHz */ + .unk2 = 3413, + RADIOREGS(0x71, 0x00, 0x02, 0x0C, 0xCE, 0x01, 0x04, 0x0A, + 0x00, 0x8D, 0xAA, 0xAA, 0xDD, 0x00, 0x0D, 0x0F, + 0x8C, 0xDD, 0x00, 0x0D, 0x0F, 0x8C), + PHYREGS(0x0804, 0x0800, 0x07FC, 0x01FF, 0x0200, 0x0201), + }, + { .channel = 226, + .freq = 5130, /* MHz */ + .unk2 = 3420, + RADIOREGS(0x71, 0x01, 0x02, 0x0C, 0xCE, 0x01, 0x04, 0x0A, + 0x00, 0x8D, 0xAA, 0xAA, 0xDD, 0x00, 0x0D, 0x0F, + 0x8C, 0xDD, 0x00, 0x0D, 0x0F, 0x8C), + PHYREGS(0x0808, 0x0804, 0x0800, 0x01FE, 0x01FF, 0x0200), + }, + { .channel = 228, + .freq = 5140, /* MHz */ + .unk2 = 3427, + RADIOREGS(0x71, 0x02, 0x02, 0x0C, 0xC6, 0x01, 0x04, 0x0A, + 0x00, 0x8D, 0x99, 0x99, 0xDD, 0x00, 0x0C, 0x0E, + 0x8B, 0xDD, 0x00, 0x0C, 0x0E, 0x8B), + PHYREGS(0x080C, 0x0808, 0x0804, 0x01FD, 0x01FE, 0x01FF), + }, + { .channel = 32, + .freq = 5160, /* MHz */ + .unk2 = 3440, + RADIOREGS(0x71, 0x04, 0x02, 0x0B, 0xBE, 0x01, 0x04, 0x0A, + 0x00, 0x8C, 0x99, 0x99, 0xCC, 0x00, 0x0B, 0x0D, + 0x8A, 0xCC, 0x00, 0x0B, 0x0D, 0x8A), + PHYREGS(0x0814, 0x0810, 0x080C, 0x01FB, 0x01FC, 0x01FD), + }, + { .channel = 34, + .freq = 5170, /* MHz */ + .unk2 = 3447, + RADIOREGS(0x71, 0x05, 0x02, 0x0B, 0xBE, 0x01, 0x04, 0x0A, + 0x00, 0x8C, 0x99, 0x99, 0xCC, 0x00, 0x0B, 0x0D, + 0x8A, 0xCC, 0x00, 0x0B, 0x0D, 0x8A), + PHYREGS(0x0818, 0x0814, 0x0810, 0x01FA, 0x01FB, 0x01FC), + }, + { .channel = 36, + .freq = 5180, /* MHz */ + .unk2 = 3453, + RADIOREGS(0x71, 0x06, 0x02, 0x0B, 0xB6, 0x01, 0x04, 0x0A, + 0x00, 0x8C, 0x88, 0x88, 0xCC, 0x00, 0x0B, 0x0C, + 0x89, 0xCC, 0x00, 0x0B, 0x0C, 0x89), + PHYREGS(0x081C, 0x0818, 0x0814, 0x01F9, 0x01FA, 0x01FB), + }, + { .channel = 38, + .freq = 5190, /* MHz */ + .unk2 = 3460, + RADIOREGS(0x71, 0x07, 0x02, 0x0B, 0xB6, 0x01, 0x04, 0x0A, + 0x00, 0x8C, 0x88, 0x88, 0xCC, 0x00, 0x0B, 0x0C, + 0x89, 0xCC, 0x00, 0x0B, 0x0C, 0x89), + PHYREGS(0x0820, 0x081C, 0x0818, 0x01F8, 0x01F9, 0x01FA), + }, + { .channel = 40, + .freq = 5200, /* MHz */ + .unk2 = 3467, + RADIOREGS(0x71, 0x08, 0x02, 0x0B, 0xAF, 0x01, 0x04, 0x0A, + 0x00, 0x8B, 0x88, 0x88, 0xBB, 0x00, 0x0A, 0x0B, + 0x89, 0xBB, 0x00, 0x0A, 0x0B, 0x89), + PHYREGS(0x0824, 0x0820, 0x081C, 0x01F7, 0x01F8, 0x01F9), + }, + { .channel = 42, + .freq = 5210, /* MHz */ + .unk2 = 3473, + RADIOREGS(0x71, 0x09, 0x02, 0x0B, 0xAF, 0x01, 0x04, 0x0A, + 0x00, 0x8B, 0x88, 0x88, 0xBB, 0x00, 0x0A, 0x0B, + 0x89, 0xBB, 0x00, 0x0A, 0x0B, 0x89), + PHYREGS(0x0828, 0x0824, 0x0820, 0x01F6, 0x01F7, 0x01F8), + }, + { .channel = 44, + .freq = 5220, /* MHz */ + .unk2 = 3480, + RADIOREGS(0x71, 0x0A, 0x02, 0x0A, 0xA7, 0x01, 0x04, 0x0A, + 0x00, 0x8B, 0x77, 0x77, 0xBB, 0x00, 0x09, 0x0A, + 0x88, 0xBB, 0x00, 0x09, 0x0A, 0x88), + PHYREGS(0x082C, 0x0828, 0x0824, 0x01F5, 0x01F6, 0x01F7), + }, + { .channel = 46, + .freq = 5230, /* MHz */ + .unk2 = 3487, + RADIOREGS(0x71, 0x0B, 0x02, 0x0A, 0xA7, 0x01, 0x04, 0x0A, + 0x00, 0x8B, 0x77, 0x77, 0xBB, 0x00, 0x09, 0x0A, + 0x88, 0xBB, 0x00, 0x09, 0x0A, 0x88), + PHYREGS(0x0830, 0x082C, 0x0828, 0x01F4, 0x01F5, 0x01F6), + }, + { .channel = 48, + .freq = 5240, /* MHz */ + .unk2 = 3493, + RADIOREGS(0x71, 0x0C, 0x02, 0x0A, 0xA0, 0x01, 0x04, 0x0A, + 0x00, 0x8A, 0x77, 0x77, 0xAA, 0x00, 0x09, 0x0A, + 0x87, 0xAA, 0x00, 0x09, 0x0A, 0x87), + PHYREGS(0x0834, 0x0830, 0x082C, 0x01F3, 0x01F4, 0x01F5), + }, + { .channel = 50, + .freq = 5250, /* MHz */ + .unk2 = 3500, + RADIOREGS(0x71, 0x0D, 0x02, 0x0A, 0xA0, 0x01, 0x04, 0x0A, + 0x00, 0x8A, 0x77, 0x77, 0xAA, 0x00, 0x09, 0x0A, + 0x87, 0xAA, 0x00, 0x09, 0x0A, 0x87), + PHYREGS(0x0838, 0x0834, 0x0830, 0x01F2, 0x01F3, 0x01F4), + }, + { .channel = 52, + .freq = 5260, /* MHz */ + .unk2 = 3507, + RADIOREGS(0x71, 0x0E, 0x02, 0x0A, 0x98, 0x01, 0x04, 0x0A, + 0x00, 0x8A, 0x66, 0x66, 0xAA, 0x00, 0x08, 0x09, + 0x87, 0xAA, 0x00, 0x08, 0x09, 0x87), + PHYREGS(0x083C, 0x0838, 0x0834, 0x01F1, 0x01F2, 0x01F3), + }, + { .channel = 54, + .freq = 5270, /* MHz */ + .unk2 = 3513, + RADIOREGS(0x71, 0x0F, 0x02, 0x0A, 0x98, 0x01, 0x04, 0x0A, + 0x00, 0x8A, 0x66, 0x66, 0xAA, 0x00, 0x08, 0x09, + 0x87, 0xAA, 0x00, 0x08, 0x09, 0x87), + PHYREGS(0x0840, 0x083C, 0x0838, 0x01F0, 0x01F1, 0x01F2), + }, + { .channel = 56, + .freq = 5280, /* MHz */ + .unk2 = 3520, + RADIOREGS(0x71, 0x10, 0x02, 0x09, 0x91, 0x01, 0x04, 0x0A, + 0x00, 0x89, 0x66, 0x66, 0x99, 0x00, 0x08, 0x08, + 0x86, 0x99, 0x00, 0x08, 0x08, 0x86), + PHYREGS(0x0844, 0x0840, 0x083C, 0x01F0, 0x01F0, 0x01F1), + }, + { .channel = 58, + .freq = 5290, /* MHz */ + .unk2 = 3527, + RADIOREGS(0x71, 0x11, 0x02, 0x09, 0x91, 0x01, 0x04, 0x0A, + 0x00, 0x89, 0x66, 0x66, 0x99, 0x00, 0x08, 0x08, + 0x86, 0x99, 0x00, 0x08, 0x08, 0x86), + PHYREGS(0x0848, 0x0844, 0x0840, 0x01EF, 0x01F0, 0x01F0), + }, + { .channel = 60, + .freq = 5300, /* MHz */ + .unk2 = 3533, + RADIOREGS(0x71, 0x12, 0x02, 0x09, 0x8A, 0x01, 0x04, 0x0A, + 0x00, 0x89, 0x55, 0x55, 0x99, 0x00, 0x08, 0x07, + 0x85, 0x99, 0x00, 0x08, 0x07, 0x85), + PHYREGS(0x084C, 0x0848, 0x0844, 0x01EE, 0x01EF, 0x01F0), + }, + { .channel = 62, + .freq = 5310, /* MHz */ + .unk2 = 3540, + RADIOREGS(0x71, 0x13, 0x02, 0x09, 0x8A, 0x01, 0x04, 0x0A, + 0x00, 0x89, 0x55, 0x55, 0x99, 0x00, 0x08, 0x07, + 0x85, 0x99, 0x00, 0x08, 0x07, 0x85), + PHYREGS(0x0850, 0x084C, 0x0848, 0x01ED, 0x01EE, 0x01EF), + }, + { .channel = 64, + .freq = 5320, /* MHz */ + .unk2 = 3547, + RADIOREGS(0x71, 0x14, 0x02, 0x09, 0x83, 0x01, 0x04, 0x0A, + 0x00, 0x88, 0x55, 0x55, 0x88, 0x00, 0x07, 0x07, + 0x84, 0x88, 0x00, 0x07, 0x07, 0x84), + PHYREGS(0x0854, 0x0850, 0x084C, 0x01EC, 0x01ED, 0x01EE), + }, + { .channel = 66, + .freq = 5330, /* MHz */ + .unk2 = 3553, + RADIOREGS(0x71, 0x15, 0x02, 0x09, 0x83, 0x01, 0x04, 0x0A, + 0x00, 0x88, 0x55, 0x55, 0x88, 0x00, 0x07, 0x07, + 0x84, 0x88, 0x00, 0x07, 0x07, 0x84), + PHYREGS(0x0858, 0x0854, 0x0850, 0x01EB, 0x01EC, 0x01ED), + }, + { .channel = 68, + .freq = 5340, /* MHz */ + .unk2 = 3560, + RADIOREGS(0x71, 0x16, 0x02, 0x08, 0x7C, 0x01, 0x04, 0x0A, + 0x00, 0x88, 0x44, 0x44, 0x88, 0x00, 0x07, 0x06, + 0x84, 0x88, 0x00, 0x07, 0x06, 0x84), + PHYREGS(0x085C, 0x0858, 0x0854, 0x01EA, 0x01EB, 0x01EC), + }, + { .channel = 70, + .freq = 5350, /* MHz */ + .unk2 = 3567, + RADIOREGS(0x71, 0x17, 0x02, 0x08, 0x7C, 0x01, 0x04, 0x0A, + 0x00, 0x88, 0x44, 0x44, 0x88, 0x00, 0x07, 0x06, + 0x84, 0x88, 0x00, 0x07, 0x06, 0x84), + PHYREGS(0x0860, 0x085C, 0x0858, 0x01E9, 0x01EA, 0x01EB), + }, + { .channel = 72, + .freq = 5360, /* MHz */ + .unk2 = 3573, + RADIOREGS(0x71, 0x18, 0x02, 0x08, 0x75, 0x01, 0x04, 0x0A, + 0x00, 0x87, 0x44, 0x44, 0x77, 0x00, 0x06, 0x05, + 0x83, 0x77, 0x00, 0x06, 0x05, 0x83), + PHYREGS(0x0864, 0x0860, 0x085C, 0x01E8, 0x01E9, 0x01EA), + }, + { .channel = 74, + .freq = 5370, /* MHz */ + .unk2 = 3580, + RADIOREGS(0x71, 0x19, 0x02, 0x08, 0x75, 0x01, 0x04, 0x0A, + 0x00, 0x87, 0x44, 0x44, 0x77, 0x00, 0x06, 0x05, + 0x83, 0x77, 0x00, 0x06, 0x05, 0x83), + PHYREGS(0x0868, 0x0864, 0x0860, 0x01E7, 0x01E8, 0x01E9), + }, + { .channel = 76, + .freq = 5380, /* MHz */ + .unk2 = 3587, + RADIOREGS(0x71, 0x1A, 0x02, 0x08, 0x6E, 0x01, 0x04, 0x0A, + 0x00, 0x87, 0x33, 0x33, 0x77, 0x00, 0x06, 0x04, + 0x82, 0x77, 0x00, 0x06, 0x04, 0x82), + PHYREGS(0x086C, 0x0868, 0x0864, 0x01E6, 0x01E7, 0x01E8), + }, + { .channel = 78, + .freq = 5390, /* MHz */ + .unk2 = 3593, + RADIOREGS(0x71, 0x1B, 0x02, 0x08, 0x6E, 0x01, 0x04, 0x0A, + 0x00, 0x87, 0x33, 0x33, 0x77, 0x00, 0x06, 0x04, + 0x82, 0x77, 0x00, 0x06, 0x04, 0x82), + PHYREGS(0x0870, 0x086C, 0x0868, 0x01E5, 0x01E6, 0x01E7), + }, + { .channel = 80, + .freq = 5400, /* MHz */ + .unk2 = 3600, + RADIOREGS(0x71, 0x1C, 0x02, 0x07, 0x67, 0x01, 0x04, 0x0A, + 0x00, 0x86, 0x33, 0x33, 0x66, 0x00, 0x05, 0x04, + 0x81, 0x66, 0x00, 0x05, 0x04, 0x81), + PHYREGS(0x0874, 0x0870, 0x086C, 0x01E5, 0x01E5, 0x01E6), + }, + { .channel = 82, + .freq = 5410, /* MHz */ + .unk2 = 3607, + RADIOREGS(0x71, 0x1D, 0x02, 0x07, 0x67, 0x01, 0x04, 0x0A, + 0x00, 0x86, 0x33, 0x33, 0x66, 0x00, 0x05, 0x04, + 0x81, 0x66, 0x00, 0x05, 0x04, 0x81), + PHYREGS(0x0878, 0x0874, 0x0870, 0x01E4, 0x01E5, 0x01E5), + }, + { .channel = 84, + .freq = 5420, /* MHz */ + .unk2 = 3613, + RADIOREGS(0x71, 0x1E, 0x02, 0x07, 0x61, 0x01, 0x04, 0x0A, + 0x00, 0x86, 0x22, 0x22, 0x66, 0x00, 0x05, 0x03, + 0x80, 0x66, 0x00, 0x05, 0x03, 0x80), + PHYREGS(0x087C, 0x0878, 0x0874, 0x01E3, 0x01E4, 0x01E5), + }, + { .channel = 86, + .freq = 5430, /* MHz */ + .unk2 = 3620, + RADIOREGS(0x71, 0x1F, 0x02, 0x07, 0x61, 0x01, 0x04, 0x0A, + 0x00, 0x86, 0x22, 0x22, 0x66, 0x00, 0x05, 0x03, + 0x80, 0x66, 0x00, 0x05, 0x03, 0x80), + PHYREGS(0x0880, 0x087C, 0x0878, 0x01E2, 0x01E3, 0x01E4), + }, + { .channel = 88, + .freq = 5440, /* MHz */ + .unk2 = 3627, + RADIOREGS(0x71, 0x20, 0x02, 0x07, 0x5A, 0x01, 0x04, 0x0A, + 0x00, 0x85, 0x22, 0x22, 0x55, 0x00, 0x04, 0x02, + 0x80, 0x55, 0x00, 0x04, 0x02, 0x80), + PHYREGS(0x0884, 0x0880, 0x087C, 0x01E1, 0x01E2, 0x01E3), + }, + { .channel = 90, + .freq = 5450, /* MHz */ + .unk2 = 3633, + RADIOREGS(0x71, 0x21, 0x02, 0x07, 0x5A, 0x01, 0x04, 0x0A, + 0x00, 0x85, 0x22, 0x22, 0x55, 0x00, 0x04, 0x02, + 0x80, 0x55, 0x00, 0x04, 0x02, 0x80), + PHYREGS(0x0888, 0x0884, 0x0880, 0x01E0, 0x01E1, 0x01E2), + }, + { .channel = 92, + .freq = 5460, /* MHz */ + .unk2 = 3640, + RADIOREGS(0x71, 0x22, 0x02, 0x06, 0x53, 0x01, 0x04, 0x0A, + 0x00, 0x85, 0x11, 0x11, 0x55, 0x00, 0x04, 0x01, + 0x80, 0x55, 0x00, 0x04, 0x01, 0x80), + PHYREGS(0x088C, 0x0888, 0x0884, 0x01DF, 0x01E0, 0x01E1), + }, + { .channel = 94, + .freq = 5470, /* MHz */ + .unk2 = 3647, + RADIOREGS(0x71, 0x23, 0x02, 0x06, 0x53, 0x01, 0x04, 0x0A, + 0x00, 0x85, 0x11, 0x11, 0x55, 0x00, 0x04, 0x01, + 0x80, 0x55, 0x00, 0x04, 0x01, 0x80), + PHYREGS(0x0890, 0x088C, 0x0888, 0x01DE, 0x01DF, 0x01E0), + }, + { .channel = 96, + .freq = 5480, /* MHz */ + .unk2 = 3653, + RADIOREGS(0x71, 0x24, 0x02, 0x06, 0x4D, 0x01, 0x04, 0x0A, + 0x00, 0x84, 0x11, 0x11, 0x44, 0x00, 0x03, 0x00, + 0x80, 0x44, 0x00, 0x03, 0x00, 0x80), + PHYREGS(0x0894, 0x0890, 0x088C, 0x01DD, 0x01DE, 0x01DF), + }, + { .channel = 98, + .freq = 5490, /* MHz */ + .unk2 = 3660, + RADIOREGS(0x71, 0x25, 0x02, 0x06, 0x4D, 0x01, 0x04, 0x0A, + 0x00, 0x84, 0x11, 0x11, 0x44, 0x00, 0x03, 0x00, + 0x80, 0x44, 0x00, 0x03, 0x00, 0x80), + PHYREGS(0x0898, 0x0894, 0x0890, 0x01DD, 0x01DD, 0x01DE), + }, + { .channel = 100, + .freq = 5500, /* MHz */ + .unk2 = 3667, + RADIOREGS(0x71, 0x26, 0x02, 0x06, 0x47, 0x01, 0x04, 0x0A, + 0x00, 0x84, 0x00, 0x00, 0x44, 0x00, 0x03, 0x00, + 0x80, 0x44, 0x00, 0x03, 0x00, 0x80), + PHYREGS(0x089C, 0x0898, 0x0894, 0x01DC, 0x01DD, 0x01DD), + }, + { .channel = 102, + .freq = 5510, /* MHz */ + .unk2 = 3673, + RADIOREGS(0x71, 0x27, 0x02, 0x06, 0x47, 0x01, 0x04, 0x0A, + 0x00, 0x84, 0x00, 0x00, 0x44, 0x00, 0x03, 0x00, + 0x80, 0x44, 0x00, 0x03, 0x00, 0x80), + PHYREGS(0x08A0, 0x089C, 0x0898, 0x01DB, 0x01DC, 0x01DD), + }, + { .channel = 104, + .freq = 5520, /* MHz */ + .unk2 = 3680, + RADIOREGS(0x71, 0x28, 0x02, 0x05, 0x40, 0x01, 0x04, 0x0A, + 0x00, 0x83, 0x00, 0x00, 0x33, 0x00, 0x02, 0x00, + 0x80, 0x33, 0x00, 0x02, 0x00, 0x80), + PHYREGS(0x08A4, 0x08A0, 0x089C, 0x01DA, 0x01DB, 0x01DC), + }, + { .channel = 106, + .freq = 5530, /* MHz */ + .unk2 = 3687, + RADIOREGS(0x71, 0x29, 0x02, 0x05, 0x40, 0x01, 0x04, 0x0A, + 0x00, 0x83, 0x00, 0x00, 0x33, 0x00, 0x02, 0x00, + 0x80, 0x33, 0x00, 0x02, 0x00, 0x80), + PHYREGS(0x08A8, 0x08A4, 0x08A0, 0x01D9, 0x01DA, 0x01DB), + }, + { .channel = 108, + .freq = 5540, /* MHz */ + .unk2 = 3693, + RADIOREGS(0x71, 0x2A, 0x02, 0x05, 0x3A, 0x01, 0x04, 0x0A, + 0x00, 0x83, 0x00, 0x00, 0x33, 0x00, 0x02, 0x00, + 0x80, 0x33, 0x00, 0x02, 0x00, 0x80), + PHYREGS(0x08AC, 0x08A8, 0x08A4, 0x01D8, 0x01D9, 0x01DA), + }, + { .channel = 110, + .freq = 5550, /* MHz */ + .unk2 = 3700, + RADIOREGS(0x71, 0x2B, 0x02, 0x05, 0x3A, 0x01, 0x04, 0x0A, + 0x00, 0x83, 0x00, 0x00, 0x33, 0x00, 0x02, 0x00, + 0x80, 0x33, 0x00, 0x02, 0x00, 0x80), + PHYREGS(0x08B0, 0x08AC, 0x08A8, 0x01D7, 0x01D8, 0x01D9), + }, + { .channel = 112, + .freq = 5560, /* MHz */ + .unk2 = 3707, + RADIOREGS(0x71, 0x2C, 0x02, 0x05, 0x34, 0x01, 0x04, 0x0A, + 0x00, 0x82, 0x00, 0x00, 0x22, 0x00, 0x01, 0x00, + 0x80, 0x22, 0x00, 0x01, 0x00, 0x80), + PHYREGS(0x08B4, 0x08B0, 0x08AC, 0x01D7, 0x01D7, 0x01D8), + }, + { .channel = 114, + .freq = 5570, /* MHz */ + .unk2 = 3713, + RADIOREGS(0x71, 0x2D, 0x02, 0x05, 0x34, 0x01, 0x04, 0x0A, + 0x00, 0x82, 0x00, 0x00, 0x22, 0x00, 0x01, 0x00, + 0x80, 0x22, 0x00, 0x01, 0x00, 0x80), + PHYREGS(0x08B8, 0x08B4, 0x08B0, 0x01D6, 0x01D7, 0x01D7), + }, + { .channel = 116, + .freq = 5580, /* MHz */ + .unk2 = 3720, + RADIOREGS(0x71, 0x2E, 0x02, 0x04, 0x2E, 0x01, 0x04, 0x0A, + 0x00, 0x82, 0x00, 0x00, 0x22, 0x00, 0x01, 0x00, + 0x80, 0x22, 0x00, 0x01, 0x00, 0x80), + PHYREGS(0x08BC, 0x08B8, 0x08B4, 0x01D5, 0x01D6, 0x01D7), + }, + { .channel = 118, + .freq = 5590, /* MHz */ + .unk2 = 3727, + RADIOREGS(0x71, 0x2F, 0x02, 0x04, 0x2E, 0x01, 0x04, 0x0A, + 0x00, 0x82, 0x00, 0x00, 0x22, 0x00, 0x01, 0x00, + 0x80, 0x22, 0x00, 0x01, 0x00, 0x80), + PHYREGS(0x08C0, 0x08BC, 0x08B8, 0x01D4, 0x01D5, 0x01D6), + }, + { .channel = 120, + .freq = 5600, /* MHz */ + .unk2 = 3733, + RADIOREGS(0x71, 0x30, 0x02, 0x04, 0x28, 0x01, 0x04, 0x0A, + 0x00, 0x81, 0x00, 0x00, 0x11, 0x00, 0x01, 0x00, + 0x80, 0x11, 0x00, 0x01, 0x00, 0x80), + PHYREGS(0x08C4, 0x08C0, 0x08BC, 0x01D3, 0x01D4, 0x01D5), + }, + { .channel = 122, + .freq = 5610, /* MHz */ + .unk2 = 3740, + RADIOREGS(0x71, 0x31, 0x02, 0x04, 0x28, 0x01, 0x04, 0x0A, + 0x00, 0x81, 0x00, 0x00, 0x11, 0x00, 0x01, 0x00, + 0x80, 0x11, 0x00, 0x01, 0x00, 0x80), + PHYREGS(0x08C8, 0x08C4, 0x08C0, 0x01D2, 0x01D3, 0x01D4), + }, + { .channel = 124, + .freq = 5620, /* MHz */ + .unk2 = 3747, + RADIOREGS(0x71, 0x32, 0x02, 0x04, 0x21, 0x01, 0x04, 0x0A, + 0x00, 0x81, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x80, 0x11, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x08CC, 0x08C8, 0x08C4, 0x01D2, 0x01D2, 0x01D3), + }, + { .channel = 126, + .freq = 5630, /* MHz */ + .unk2 = 3753, + RADIOREGS(0x71, 0x33, 0x02, 0x04, 0x21, 0x01, 0x04, 0x0A, + 0x00, 0x81, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x80, 0x11, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x08D0, 0x08CC, 0x08C8, 0x01D1, 0x01D2, 0x01D2), + }, + { .channel = 128, + .freq = 5640, /* MHz */ + .unk2 = 3760, + RADIOREGS(0x71, 0x34, 0x02, 0x03, 0x1C, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x08D4, 0x08D0, 0x08CC, 0x01D0, 0x01D1, 0x01D2), + }, + { .channel = 130, + .freq = 5650, /* MHz */ + .unk2 = 3767, + RADIOREGS(0x71, 0x35, 0x02, 0x03, 0x1C, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x08D8, 0x08D4, 0x08D0, 0x01CF, 0x01D0, 0x01D1), + }, + { .channel = 132, + .freq = 5660, /* MHz */ + .unk2 = 3773, + RADIOREGS(0x71, 0x36, 0x02, 0x03, 0x16, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x08DC, 0x08D8, 0x08D4, 0x01CE, 0x01CF, 0x01D0), + }, + { .channel = 134, + .freq = 5670, /* MHz */ + .unk2 = 3780, + RADIOREGS(0x71, 0x37, 0x02, 0x03, 0x16, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x08E0, 0x08DC, 0x08D8, 0x01CE, 0x01CE, 0x01CF), + }, + { .channel = 136, + .freq = 5680, /* MHz */ + .unk2 = 3787, + RADIOREGS(0x71, 0x38, 0x02, 0x03, 0x10, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x08E4, 0x08E0, 0x08DC, 0x01CD, 0x01CE, 0x01CE), + }, + { .channel = 138, + .freq = 5690, /* MHz */ + .unk2 = 3793, + RADIOREGS(0x71, 0x39, 0x02, 0x03, 0x10, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x08E8, 0x08E4, 0x08E0, 0x01CC, 0x01CD, 0x01CE), + }, + { .channel = 140, + .freq = 5700, /* MHz */ + .unk2 = 3800, + RADIOREGS(0x71, 0x3A, 0x02, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x08EC, 0x08E8, 0x08E4, 0x01CB, 0x01CC, 0x01CD), + }, + { .channel = 142, + .freq = 5710, /* MHz */ + .unk2 = 3807, + RADIOREGS(0x71, 0x3B, 0x02, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x08F0, 0x08EC, 0x08E8, 0x01CA, 0x01CB, 0x01CC), + }, + { .channel = 144, + .freq = 5720, /* MHz */ + .unk2 = 3813, + RADIOREGS(0x71, 0x3C, 0x02, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x08F4, 0x08F0, 0x08EC, 0x01C9, 0x01CA, 0x01CB), + }, + { .channel = 145, + .freq = 5725, /* MHz */ + .unk2 = 3817, + RADIOREGS(0x72, 0x79, 0x04, 0x02, 0x03, 0x01, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x08F6, 0x08F2, 0x08EE, 0x01C9, 0x01CA, 0x01CB), + }, + { .channel = 146, + .freq = 5730, /* MHz */ + .unk2 = 3820, + RADIOREGS(0x71, 0x3D, 0x02, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x08F8, 0x08F4, 0x08F0, 0x01C9, 0x01C9, 0x01CA), + }, + { .channel = 147, + .freq = 5735, /* MHz */ + .unk2 = 3823, + RADIOREGS(0x72, 0x7B, 0x04, 0x02, 0x03, 0x01, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x08FA, 0x08F6, 0x08F2, 0x01C8, 0x01C9, 0x01CA), + }, + { .channel = 148, + .freq = 5740, /* MHz */ + .unk2 = 3827, + RADIOREGS(0x71, 0x3E, 0x02, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x08FC, 0x08F8, 0x08F4, 0x01C8, 0x01C9, 0x01C9), + }, + { .channel = 149, + .freq = 5745, /* MHz */ + .unk2 = 3830, + RADIOREGS(0x72, 0x7D, 0x04, 0x02, 0xFE, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x08FE, 0x08FA, 0x08F6, 0x01C8, 0x01C8, 0x01C9), + }, + { .channel = 150, + .freq = 5750, /* MHz */ + .unk2 = 3833, + RADIOREGS(0x71, 0x3F, 0x02, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0900, 0x08FC, 0x08F8, 0x01C7, 0x01C8, 0x01C9), + }, + { .channel = 151, + .freq = 5755, /* MHz */ + .unk2 = 3837, + RADIOREGS(0x72, 0x7F, 0x04, 0x02, 0xFE, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0902, 0x08FE, 0x08FA, 0x01C7, 0x01C8, 0x01C8), + }, + { .channel = 152, + .freq = 5760, /* MHz */ + .unk2 = 3840, + RADIOREGS(0x71, 0x40, 0x02, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0904, 0x0900, 0x08FC, 0x01C6, 0x01C7, 0x01C8), + }, + { .channel = 153, + .freq = 5765, /* MHz */ + .unk2 = 3843, + RADIOREGS(0x72, 0x81, 0x04, 0x02, 0xF8, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0906, 0x0902, 0x08FE, 0x01C6, 0x01C7, 0x01C8), + }, + { .channel = 154, + .freq = 5770, /* MHz */ + .unk2 = 3847, + RADIOREGS(0x71, 0x41, 0x02, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0908, 0x0904, 0x0900, 0x01C6, 0x01C6, 0x01C7), + }, + { .channel = 155, + .freq = 5775, /* MHz */ + .unk2 = 3850, + RADIOREGS(0x72, 0x83, 0x04, 0x02, 0xF8, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x090A, 0x0906, 0x0902, 0x01C5, 0x01C6, 0x01C7), + }, + { .channel = 156, + .freq = 5780, /* MHz */ + .unk2 = 3853, + RADIOREGS(0x71, 0x42, 0x02, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x090C, 0x0908, 0x0904, 0x01C5, 0x01C6, 0x01C6), + }, + { .channel = 157, + .freq = 5785, /* MHz */ + .unk2 = 3857, + RADIOREGS(0x72, 0x85, 0x04, 0x02, 0xF2, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x090E, 0x090A, 0x0906, 0x01C4, 0x01C5, 0x01C6), + }, + { .channel = 158, + .freq = 5790, /* MHz */ + .unk2 = 3860, + RADIOREGS(0x71, 0x43, 0x02, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0910, 0x090C, 0x0908, 0x01C4, 0x01C5, 0x01C6), + }, + { .channel = 159, + .freq = 5795, /* MHz */ + .unk2 = 3863, + RADIOREGS(0x72, 0x87, 0x04, 0x02, 0xF2, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0912, 0x090E, 0x090A, 0x01C4, 0x01C4, 0x01C5), + }, + { .channel = 160, + .freq = 5800, /* MHz */ + .unk2 = 3867, + RADIOREGS(0x71, 0x44, 0x02, 0x01, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0914, 0x0910, 0x090C, 0x01C3, 0x01C4, 0x01C5), + }, + { .channel = 161, + .freq = 5805, /* MHz */ + .unk2 = 3870, + RADIOREGS(0x72, 0x89, 0x04, 0x01, 0xED, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0916, 0x0912, 0x090E, 0x01C3, 0x01C4, 0x01C4), + }, + { .channel = 162, + .freq = 5810, /* MHz */ + .unk2 = 3873, + RADIOREGS(0x71, 0x45, 0x02, 0x01, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0918, 0x0914, 0x0910, 0x01C2, 0x01C3, 0x01C4), + }, + { .channel = 163, + .freq = 5815, /* MHz */ + .unk2 = 3877, + RADIOREGS(0x72, 0x8B, 0x04, 0x01, 0xED, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x091A, 0x0916, 0x0912, 0x01C2, 0x01C3, 0x01C4), + }, + { .channel = 164, + .freq = 5820, /* MHz */ + .unk2 = 3880, + RADIOREGS(0x71, 0x46, 0x02, 0x01, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x091C, 0x0918, 0x0914, 0x01C2, 0x01C2, 0x01C3), + }, + { .channel = 165, + .freq = 5825, /* MHz */ + .unk2 = 3883, + RADIOREGS(0x72, 0x8D, 0x04, 0x01, 0xED, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x091E, 0x091A, 0x0916, 0x01C1, 0x01C2, 0x01C3), + }, + { .channel = 166, + .freq = 5830, /* MHz */ + .unk2 = 3887, + RADIOREGS(0x71, 0x47, 0x02, 0x01, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0920, 0x091C, 0x0918, 0x01C1, 0x01C2, 0x01C2), + }, + { .channel = 168, + .freq = 5840, /* MHz */ + .unk2 = 3893, + RADIOREGS(0x71, 0x48, 0x02, 0x01, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0924, 0x0920, 0x091C, 0x01C0, 0x01C1, 0x01C2), + }, + { .channel = 170, + .freq = 5850, /* MHz */ + .unk2 = 3900, + RADIOREGS(0x71, 0x49, 0x02, 0x01, 0xE0, 0x00, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0928, 0x0924, 0x0920, 0x01BF, 0x01C0, 0x01C1), + }, + { .channel = 172, + .freq = 5860, /* MHz */ + .unk2 = 3907, + RADIOREGS(0x71, 0x4A, 0x02, 0x01, 0xDE, 0x00, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x092C, 0x0928, 0x0924, 0x01BF, 0x01BF, 0x01C0), + }, + { .channel = 174, + .freq = 5870, /* MHz */ + .unk2 = 3913, + RADIOREGS(0x71, 0x4B, 0x02, 0x00, 0xDB, 0x00, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0930, 0x092C, 0x0928, 0x01BE, 0x01BF, 0x01BF), + }, + { .channel = 176, + .freq = 5880, /* MHz */ + .unk2 = 3920, + RADIOREGS(0x71, 0x4C, 0x02, 0x00, 0xD8, 0x00, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0934, 0x0930, 0x092C, 0x01BD, 0x01BE, 0x01BF), + }, + { .channel = 178, + .freq = 5890, /* MHz */ + .unk2 = 3927, + RADIOREGS(0x71, 0x4D, 0x02, 0x00, 0xD6, 0x00, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0938, 0x0934, 0x0930, 0x01BC, 0x01BD, 0x01BE), + }, + { .channel = 180, + .freq = 5900, /* MHz */ + .unk2 = 3933, + RADIOREGS(0x71, 0x4E, 0x02, 0x00, 0xD3, 0x00, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x093C, 0x0938, 0x0934, 0x01BC, 0x01BC, 0x01BD), + }, + { .channel = 182, + .freq = 5910, /* MHz */ + .unk2 = 3940, + RADIOREGS(0x71, 0x4F, 0x02, 0x00, 0xD6, 0x00, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80), + PHYREGS(0x0940, 0x093C, 0x0938, 0x01BB, 0x01BC, 0x01BC), + }, + { .channel = 1, + .freq = 2412, /* MHz */ + .unk2 = 3216, + RADIOREGS(0x73, 0x6C, 0x09, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x0D, 0x0C, + 0x80, 0xFF, 0x88, 0x0D, 0x0C, 0x80), + PHYREGS(0x03C9, 0x03C5, 0x03C1, 0x043A, 0x043F, 0x0443), + }, + { .channel = 2, + .freq = 2417, /* MHz */ + .unk2 = 3223, + RADIOREGS(0x73, 0x71, 0x09, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x0C, 0x0B, + 0x80, 0xFF, 0x88, 0x0C, 0x0B, 0x80), + PHYREGS(0x03CB, 0x03C7, 0x03C3, 0x0438, 0x043D, 0x0441), + }, + { .channel = 3, + .freq = 2422, /* MHz */ + .unk2 = 3229, + RADIOREGS(0x73, 0x76, 0x09, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x0C, 0x0A, + 0x80, 0xFF, 0x88, 0x0C, 0x0A, 0x80), + PHYREGS(0x03CD, 0x03C9, 0x03C5, 0x0436, 0x043A, 0x043F), + }, + { .channel = 4, + .freq = 2427, /* MHz */ + .unk2 = 3236, + RADIOREGS(0x73, 0x7B, 0x09, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x0C, 0x0A, + 0x80, 0xFF, 0x88, 0x0C, 0x0A, 0x80), + PHYREGS(0x03CF, 0x03CB, 0x03C7, 0x0434, 0x0438, 0x043D), + }, + { .channel = 5, + .freq = 2432, /* MHz */ + .unk2 = 3243, + RADIOREGS(0x73, 0x80, 0x09, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x0C, 0x09, + 0x80, 0xFF, 0x88, 0x0C, 0x09, 0x80), + PHYREGS(0x03D1, 0x03CD, 0x03C9, 0x0431, 0x0436, 0x043A), + }, + { .channel = 6, + .freq = 2437, /* MHz */ + .unk2 = 3249, + RADIOREGS(0x73, 0x85, 0x09, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x0B, 0x08, + 0x80, 0xFF, 0x88, 0x0B, 0x08, 0x80), + PHYREGS(0x03D3, 0x03CF, 0x03CB, 0x042F, 0x0434, 0x0438), + }, + { .channel = 7, + .freq = 2442, /* MHz */ + .unk2 = 3256, + RADIOREGS(0x73, 0x8A, 0x09, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x0A, 0x07, + 0x80, 0xFF, 0x88, 0x0A, 0x07, 0x80), + PHYREGS(0x03D5, 0x03D1, 0x03CD, 0x042D, 0x0431, 0x0436), + }, + { .channel = 8, + .freq = 2447, /* MHz */ + .unk2 = 3263, + RADIOREGS(0x73, 0x8F, 0x09, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x0A, 0x06, + 0x80, 0xFF, 0x88, 0x0A, 0x06, 0x80), + PHYREGS(0x03D7, 0x03D3, 0x03CF, 0x042B, 0x042F, 0x0434), + }, + { .channel = 9, + .freq = 2452, /* MHz */ + .unk2 = 3269, + RADIOREGS(0x73, 0x94, 0x09, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x09, 0x06, + 0x80, 0xFF, 0x88, 0x09, 0x06, 0x80), + PHYREGS(0x03D9, 0x03D5, 0x03D1, 0x0429, 0x042D, 0x0431), + }, + { .channel = 10, + .freq = 2457, /* MHz */ + .unk2 = 3276, + RADIOREGS(0x73, 0x99, 0x09, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x08, 0x05, + 0x80, 0xFF, 0x88, 0x08, 0x05, 0x80), + PHYREGS(0x03DB, 0x03D7, 0x03D3, 0x0427, 0x042B, 0x042F), + }, + { .channel = 11, + .freq = 2462, /* MHz */ + .unk2 = 3283, + RADIOREGS(0x73, 0x9E, 0x09, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x08, 0x04, + 0x80, 0xFF, 0x88, 0x08, 0x04, 0x80), + PHYREGS(0x03DD, 0x03D9, 0x03D5, 0x0424, 0x0429, 0x042D), + }, + { .channel = 12, + .freq = 2467, /* MHz */ + .unk2 = 3289, + RADIOREGS(0x73, 0xA3, 0x09, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x08, 0x03, + 0x80, 0xFF, 0x88, 0x08, 0x03, 0x80), + PHYREGS(0x03DF, 0x03DB, 0x03D7, 0x0422, 0x0427, 0x042B), + }, + { .channel = 13, + .freq = 2472, /* MHz */ + .unk2 = 3296, + RADIOREGS(0x73, 0xA8, 0x09, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x07, 0x03, + 0x80, 0xFF, 0x88, 0x07, 0x03, 0x80), + PHYREGS(0x03E1, 0x03DD, 0x03D9, 0x0420, 0x0424, 0x0429), + }, + { .channel = 14, + .freq = 2484, /* MHz */ + .unk2 = 3312, + RADIOREGS(0x73, 0xB4, 0x09, 0x0F, 0xFF, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x07, 0x01, + 0x80, 0xFF, 0x88, 0x07, 0x01, 0x80), + PHYREGS(0x03E6, 0x03E2, 0x03DE, 0x041B, 0x041F, 0x0424), + }, +}; + +void b2055_upload_inittab(struct b43_wldev *dev, + bool ghz5, bool ignore_uploadflag) +{ + const struct b2055_inittab_entry *e; + unsigned int i, writes = 0; + u16 value; + + for (i = 0; i < ARRAY_SIZE(b2055_inittab); i++) { + e = &(b2055_inittab[i]); + if (!(e->flags & B2055_INITTAB_ENTRY_OK)) + continue; + if ((e->flags & B2055_INITTAB_UPLOAD) || ignore_uploadflag) { + if (ghz5) + value = e->ghz5; + else + value = e->ghz2; + b43_radio_write16(dev, i, value); + if (++writes % 4 == 0) + b43_read32(dev, B43_MMIO_MACCTL); /* flush */ + } + } +} + +const struct b43_nphy_channeltab_entry_rev2 * +b43_nphy_get_chantabent_rev2(struct b43_wldev *dev, u8 channel) +{ + const struct b43_nphy_channeltab_entry_rev2 *e; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(b43_nphy_channeltab_rev2); i++) { + e = &(b43_nphy_channeltab_rev2[i]); + if (e->channel == channel) + return e; + } + + return NULL; +} diff --git a/drivers/net/wireless/b43/radio_2055.h b/drivers/net/wireless/b43/radio_2055.h new file mode 100644 index 0000000..67f9612 --- /dev/null +++ b/drivers/net/wireless/b43/radio_2055.h @@ -0,0 +1,259 @@ +#ifndef B43_RADIO_2055_H_ +#define B43_RADIO_2055_H_ + +#include + +#include "tables_nphy.h" + +#define B2055_GEN_SPARE 0x00 /* GEN spare */ +#define B2055_SP_PINPD 0x02 /* SP PIN PD */ +#define B2055_C1_SP_RSSI 0x03 /* SP RSSI Core 1 */ +#define B2055_C1_SP_PDMISC 0x04 /* SP PD MISC Core 1 */ +#define B2055_C2_SP_RSSI 0x05 /* SP RSSI Core 2 */ +#define B2055_C2_SP_PDMISC 0x06 /* SP PD MISC Core 2 */ +#define B2055_C1_SP_RXGC1 0x07 /* SP RX GC1 Core 1 */ +#define B2055_C1_SP_RXGC2 0x08 /* SP RX GC2 Core 1 */ +#define B2055_C2_SP_RXGC1 0x09 /* SP RX GC1 Core 2 */ +#define B2055_C2_SP_RXGC2 0x0A /* SP RX GC2 Core 2 */ +#define B2055_C1_SP_LPFBWSEL 0x0B /* SP LPF BW select Core 1 */ +#define B2055_C2_SP_LPFBWSEL 0x0C /* SP LPF BW select Core 2 */ +#define B2055_C1_SP_TXGC1 0x0D /* SP TX GC1 Core 1 */ +#define B2055_C1_SP_TXGC2 0x0E /* SP TX GC2 Core 1 */ +#define B2055_C2_SP_TXGC1 0x0F /* SP TX GC1 Core 2 */ +#define B2055_C2_SP_TXGC2 0x10 /* SP TX GC2 Core 2 */ +#define B2055_MASTER1 0x11 /* Master control 1 */ +#define B2055_MASTER2 0x12 /* Master control 2 */ +#define B2055_PD_LGEN 0x13 /* PD LGEN */ +#define B2055_PD_PLLTS 0x14 /* PD PLL TS */ +#define B2055_C1_PD_LGBUF 0x15 /* PD Core 1 LGBUF */ +#define B2055_C1_PD_TX 0x16 /* PD Core 1 TX */ +#define B2055_C1_PD_RXTX 0x17 /* PD Core 1 RXTX */ +#define B2055_C1_PD_RSSIMISC 0x18 /* PD Core 1 RSSI MISC */ +#define B2055_C2_PD_LGBUF 0x19 /* PD Core 2 LGBUF */ +#define B2055_C2_PD_TX 0x1A /* PD Core 2 TX */ +#define B2055_C2_PD_RXTX 0x1B /* PD Core 2 RXTX */ +#define B2055_C2_PD_RSSIMISC 0x1C /* PD Core 2 RSSI MISC */ +#define B2055_PWRDET_LGEN 0x1D /* PWRDET LGEN */ +#define B2055_C1_PWRDET_LGBUF 0x1E /* PWRDET LGBUF Core 1 */ +#define B2055_C1_PWRDET_RXTX 0x1F /* PWRDET RXTX Core 1 */ +#define B2055_C2_PWRDET_LGBUF 0x20 /* PWRDET LGBUF Core 2 */ +#define B2055_C2_PWRDET_RXTX 0x21 /* PWRDET RXTX Core 2 */ +#define B2055_RRCCAL_CS 0x22 /* RRCCAL Control spare */ +#define B2055_RRCCAL_NOPTSEL 0x23 /* RRCCAL N OPT SEL */ +#define B2055_CAL_MISC 0x24 /* CAL MISC */ +#define B2055_CAL_COUT 0x25 /* CAL Counter out */ +#define B2055_CAL_COUT2 0x26 /* CAL Counter out 2 */ +#define B2055_CAL_CVARCTL 0x27 /* CAL CVAR Control */ +#define B2055_CAL_RVARCTL 0x28 /* CAL RVAR Control */ +#define B2055_CAL_LPOCTL 0x29 /* CAL LPO Control */ +#define B2055_CAL_TS 0x2A /* CAL TS */ +#define B2055_CAL_RCCALRTS 0x2B /* CAL RCCAL READ TS */ +#define B2055_CAL_RCALRTS 0x2C /* CAL RCAL READ TS */ +#define B2055_PADDRV 0x2D /* PAD driver */ +#define B2055_XOCTL1 0x2E /* XO Control 1 */ +#define B2055_XOCTL2 0x2F /* XO Control 2 */ +#define B2055_XOREGUL 0x30 /* XO Regulator */ +#define B2055_XOMISC 0x31 /* XO misc */ +#define B2055_PLL_LFC1 0x32 /* PLL LF C1 */ +#define B2055_PLL_CALVTH 0x33 /* PLL CAL VTH */ +#define B2055_PLL_LFC2 0x34 /* PLL LF C2 */ +#define B2055_PLL_REF 0x35 /* PLL reference */ +#define B2055_PLL_LFR1 0x36 /* PLL LF R1 */ +#define B2055_PLL_PFDCP 0x37 /* PLL PFD CP */ +#define B2055_PLL_IDAC_CPOPAMP 0x38 /* PLL IDAC CPOPAMP */ +#define B2055_PLL_CPREG 0x39 /* PLL CP Regulator */ +#define B2055_PLL_RCAL 0x3A /* PLL RCAL */ +#define B2055_RF_PLLMOD0 0x3B /* RF PLL MOD0 */ +#define B2055_RF_PLLMOD1 0x3C /* RF PLL MOD1 */ +#define B2055_RF_MMDIDAC1 0x3D /* RF MMD IDAC 1 */ +#define B2055_RF_MMDIDAC0 0x3E /* RF MMD IDAC 0 */ +#define B2055_RF_MMDSP 0x3F /* RF MMD spare */ +#define B2055_VCO_CAL1 0x40 /* VCO cal 1 */ +#define B2055_VCO_CAL2 0x41 /* VCO cal 2 */ +#define B2055_VCO_CAL3 0x42 /* VCO cal 3 */ +#define B2055_VCO_CAL4 0x43 /* VCO cal 4 */ +#define B2055_VCO_CAL5 0x44 /* VCO cal 5 */ +#define B2055_VCO_CAL6 0x45 /* VCO cal 6 */ +#define B2055_VCO_CAL7 0x46 /* VCO cal 7 */ +#define B2055_VCO_CAL8 0x47 /* VCO cal 8 */ +#define B2055_VCO_CAL9 0x48 /* VCO cal 9 */ +#define B2055_VCO_CAL10 0x49 /* VCO cal 10 */ +#define B2055_VCO_CAL11 0x4A /* VCO cal 11 */ +#define B2055_VCO_CAL12 0x4B /* VCO cal 12 */ +#define B2055_VCO_CAL13 0x4C /* VCO cal 13 */ +#define B2055_VCO_CAL14 0x4D /* VCO cal 14 */ +#define B2055_VCO_CAL15 0x4E /* VCO cal 15 */ +#define B2055_VCO_CAL16 0x4F /* VCO cal 16 */ +#define B2055_VCO_KVCO 0x50 /* VCO KVCO */ +#define B2055_VCO_CAPTAIL 0x51 /* VCO CAP TAIL */ +#define B2055_VCO_IDACVCO 0x52 /* VCO IDAC VCO */ +#define B2055_VCO_REG 0x53 /* VCO Regulator */ +#define B2055_PLL_RFVTH 0x54 /* PLL RF VTH */ +#define B2055_LGBUF_CENBUF 0x55 /* LGBUF CEN BUF */ +#define B2055_LGEN_TUNE1 0x56 /* LGEN tune 1 */ +#define B2055_LGEN_TUNE2 0x57 /* LGEN tune 2 */ +#define B2055_LGEN_IDAC1 0x58 /* LGEN IDAC 1 */ +#define B2055_LGEN_IDAC2 0x59 /* LGEN IDAC 2 */ +#define B2055_LGEN_BIASC 0x5A /* LGEN BIAS counter */ +#define B2055_LGEN_BIASIDAC 0x5B /* LGEN BIAS IDAC */ +#define B2055_LGEN_RCAL 0x5C /* LGEN RCAL */ +#define B2055_LGEN_DIV 0x5D /* LGEN div */ +#define B2055_LGEN_SPARE2 0x5E /* LGEN spare 2 */ +#define B2055_C1_LGBUF_ATUNE 0x5F /* Core 1 LGBUF A tune */ +#define B2055_C1_LGBUF_GTUNE 0x60 /* Core 1 LGBUF G tune */ +#define B2055_C1_LGBUF_DIV 0x61 /* Core 1 LGBUF div */ +#define B2055_C1_LGBUF_AIDAC 0x62 /* Core 1 LGBUF A IDAC */ +#define B2055_C1_LGBUF_GIDAC 0x63 /* Core 1 LGBUF G IDAC */ +#define B2055_C1_LGBUF_IDACFO 0x64 /* Core 1 LGBUF IDAC filter override */ +#define B2055_C1_LGBUF_SPARE 0x65 /* Core 1 LGBUF spare */ +#define B2055_C1_RX_RFSPC1 0x66 /* Core 1 RX RF SPC1 */ +#define B2055_C1_RX_RFR1 0x67 /* Core 1 RX RF reg 1 */ +#define B2055_C1_RX_RFR2 0x68 /* Core 1 RX RF reg 2 */ +#define B2055_C1_RX_RFRCAL 0x69 /* Core 1 RX RF RCAL */ +#define B2055_C1_RX_BB_BLCMP 0x6A /* Core 1 RX Baseband BUFI LPF CMP */ +#define B2055_C1_RX_BB_LPF 0x6B /* Core 1 RX Baseband LPF */ +#define B2055_C1_RX_BB_MIDACHP 0x6C /* Core 1 RX Baseband MIDAC High-pass */ +#define B2055_C1_RX_BB_VGA1IDAC 0x6D /* Core 1 RX Baseband VGA1 IDAC */ +#define B2055_C1_RX_BB_VGA2IDAC 0x6E /* Core 1 RX Baseband VGA2 IDAC */ +#define B2055_C1_RX_BB_VGA3IDAC 0x6F /* Core 1 RX Baseband VGA3 IDAC */ +#define B2055_C1_RX_BB_BUFOCTL 0x70 /* Core 1 RX Baseband BUFO Control */ +#define B2055_C1_RX_BB_RCCALCTL 0x71 /* Core 1 RX Baseband RCCAL Control */ +#define B2055_C1_RX_BB_RSSICTL1 0x72 /* Core 1 RX Baseband RSSI Control 1 */ +#define B2055_C1_RX_BB_RSSICTL2 0x73 /* Core 1 RX Baseband RSSI Control 2 */ +#define B2055_C1_RX_BB_RSSICTL3 0x74 /* Core 1 RX Baseband RSSI Control 3 */ +#define B2055_C1_RX_BB_RSSICTL4 0x75 /* Core 1 RX Baseband RSSI Control 4 */ +#define B2055_C1_RX_BB_RSSICTL5 0x76 /* Core 1 RX Baseband RSSI Control 5 */ +#define B2055_C1_RX_BB_REG 0x77 /* Core 1 RX Baseband Regulator */ +#define B2055_C1_RX_BB_SPARE1 0x78 /* Core 1 RX Baseband spare 1 */ +#define B2055_C1_RX_TXBBRCAL 0x79 /* Core 1 RX TX BB RCAL */ +#define B2055_C1_TX_RF_SPGA 0x7A /* Core 1 TX RF SGM PGA */ +#define B2055_C1_TX_RF_SPAD 0x7B /* Core 1 TX RF SGM PAD */ +#define B2055_C1_TX_RF_CNTPGA1 0x7C /* Core 1 TX RF counter PGA 1 */ +#define B2055_C1_TX_RF_CNTPAD1 0x7D /* Core 1 TX RF counter PAD 1 */ +#define B2055_C1_TX_RF_PGAIDAC 0x7E /* Core 1 TX RF PGA IDAC */ +#define B2055_C1_TX_PGAPADTN 0x7F /* Core 1 TX PGA PAD TN */ +#define B2055_C1_TX_PADIDAC1 0x80 /* Core 1 TX PAD IDAC 1 */ +#define B2055_C1_TX_PADIDAC2 0x81 /* Core 1 TX PAD IDAC 2 */ +#define B2055_C1_TX_MXBGTRIM 0x82 /* Core 1 TX MX B/G TRIM */ +#define B2055_C1_TX_RF_RCAL 0x83 /* Core 1 TX RF RCAL */ +#define B2055_C1_TX_RF_PADTSSI1 0x84 /* Core 1 TX RF PAD TSSI1 */ +#define B2055_C1_TX_RF_PADTSSI2 0x85 /* Core 1 TX RF PAD TSSI2 */ +#define B2055_C1_TX_RF_SPARE 0x86 /* Core 1 TX RF spare */ +#define B2055_C1_TX_RF_IQCAL1 0x87 /* Core 1 TX RF I/Q CAL 1 */ +#define B2055_C1_TX_RF_IQCAL2 0x88 /* Core 1 TX RF I/Q CAL 2 */ +#define B2055_C1_TXBB_RCCAL 0x89 /* Core 1 TXBB RC CAL Control */ +#define B2055_C1_TXBB_LPF1 0x8A /* Core 1 TXBB LPF 1 */ +#define B2055_C1_TX_VOSCNCL 0x8B /* Core 1 TX VOS CNCL */ +#define B2055_C1_TX_LPF_MXGMIDAC 0x8C /* Core 1 TX LPF MXGM IDAC */ +#define B2055_C1_TX_BB_MXGM 0x8D /* Core 1 TX BB MXGM */ +#define B2055_C2_LGBUF_ATUNE 0x8E /* Core 2 LGBUF A tune */ +#define B2055_C2_LGBUF_GTUNE 0x8F /* Core 2 LGBUF G tune */ +#define B2055_C2_LGBUF_DIV 0x90 /* Core 2 LGBUF div */ +#define B2055_C2_LGBUF_AIDAC 0x91 /* Core 2 LGBUF A IDAC */ +#define B2055_C2_LGBUF_GIDAC 0x92 /* Core 2 LGBUF G IDAC */ +#define B2055_C2_LGBUF_IDACFO 0x93 /* Core 2 LGBUF IDAC filter override */ +#define B2055_C2_LGBUF_SPARE 0x94 /* Core 2 LGBUF spare */ +#define B2055_C2_RX_RFSPC1 0x95 /* Core 2 RX RF SPC1 */ +#define B2055_C2_RX_RFR1 0x96 /* Core 2 RX RF reg 1 */ +#define B2055_C2_RX_RFR2 0x97 /* Core 2 RX RF reg 2 */ +#define B2055_C2_RX_RFRCAL 0x98 /* Core 2 RX RF RCAL */ +#define B2055_C2_RX_BB_BLCMP 0x99 /* Core 2 RX Baseband BUFI LPF CMP */ +#define B2055_C2_RX_BB_LPF 0x9A /* Core 2 RX Baseband LPF */ +#define B2055_C2_RX_BB_MIDACHP 0x9B /* Core 2 RX Baseband MIDAC High-pass */ +#define B2055_C2_RX_BB_VGA1IDAC 0x9C /* Core 2 RX Baseband VGA1 IDAC */ +#define B2055_C2_RX_BB_VGA2IDAC 0x9D /* Core 2 RX Baseband VGA2 IDAC */ +#define B2055_C2_RX_BB_VGA3IDAC 0x9E /* Core 2 RX Baseband VGA3 IDAC */ +#define B2055_C2_RX_BB_BUFOCTL 0x9F /* Core 2 RX Baseband BUFO Control */ +#define B2055_C2_RX_BB_RCCALCTL 0xA0 /* Core 2 RX Baseband RCCAL Control */ +#define B2055_C2_RX_BB_RSSICTL1 0xA1 /* Core 2 RX Baseband RSSI Control 1 */ +#define B2055_C2_RX_BB_RSSICTL2 0xA2 /* Core 2 RX Baseband RSSI Control 2 */ +#define B2055_C2_RX_BB_RSSICTL3 0xA3 /* Core 2 RX Baseband RSSI Control 3 */ +#define B2055_C2_RX_BB_RSSICTL4 0xA4 /* Core 2 RX Baseband RSSI Control 4 */ +#define B2055_C2_RX_BB_RSSICTL5 0xA5 /* Core 2 RX Baseband RSSI Control 5 */ +#define B2055_C2_RX_BB_REG 0xA6 /* Core 2 RX Baseband Regulator */ +#define B2055_C2_RX_BB_SPARE1 0xA7 /* Core 2 RX Baseband spare 1 */ +#define B2055_C2_RX_TXBBRCAL 0xA8 /* Core 2 RX TX BB RCAL */ +#define B2055_C2_TX_RF_SPGA 0xA9 /* Core 2 TX RF SGM PGA */ +#define B2055_C2_TX_RF_SPAD 0xAA /* Core 2 TX RF SGM PAD */ +#define B2055_C2_TX_RF_CNTPGA1 0xAB /* Core 2 TX RF counter PGA 1 */ +#define B2055_C2_TX_RF_CNTPAD1 0xAC /* Core 2 TX RF counter PAD 1 */ +#define B2055_C2_TX_RF_PGAIDAC 0xAD /* Core 2 TX RF PGA IDAC */ +#define B2055_C2_TX_PGAPADTN 0xAE /* Core 2 TX PGA PAD TN */ +#define B2055_C2_TX_PADIDAC1 0xAF /* Core 2 TX PAD IDAC 1 */ +#define B2055_C2_TX_PADIDAC2 0xB0 /* Core 2 TX PAD IDAC 2 */ +#define B2055_C2_TX_MXBGTRIM 0xB1 /* Core 2 TX MX B/G TRIM */ +#define B2055_C2_TX_RF_RCAL 0xB2 /* Core 2 TX RF RCAL */ +#define B2055_C2_TX_RF_PADTSSI1 0xB3 /* Core 2 TX RF PAD TSSI1 */ +#define B2055_C2_TX_RF_PADTSSI2 0xB4 /* Core 2 TX RF PAD TSSI2 */ +#define B2055_C2_TX_RF_SPARE 0xB5 /* Core 2 TX RF spare */ +#define B2055_C2_TX_RF_IQCAL1 0xB6 /* Core 2 TX RF I/Q CAL 1 */ +#define B2055_C2_TX_RF_IQCAL2 0xB7 /* Core 2 TX RF I/Q CAL 2 */ +#define B2055_C2_TXBB_RCCAL 0xB8 /* Core 2 TXBB RC CAL Control */ +#define B2055_C2_TXBB_LPF1 0xB9 /* Core 2 TXBB LPF 1 */ +#define B2055_C2_TX_VOSCNCL 0xBA /* Core 2 TX VOS CNCL */ +#define B2055_C2_TX_LPF_MXGMIDAC 0xBB /* Core 2 TX LPF MXGM IDAC */ +#define B2055_C2_TX_BB_MXGM 0xBC /* Core 2 TX BB MXGM */ +#define B2055_PRG_GCHP21 0xBD /* PRG GC HPVGA23 21 */ +#define B2055_PRG_GCHP22 0xBE /* PRG GC HPVGA23 22 */ +#define B2055_PRG_GCHP23 0xBF /* PRG GC HPVGA23 23 */ +#define B2055_PRG_GCHP24 0xC0 /* PRG GC HPVGA23 24 */ +#define B2055_PRG_GCHP25 0xC1 /* PRG GC HPVGA23 25 */ +#define B2055_PRG_GCHP26 0xC2 /* PRG GC HPVGA23 26 */ +#define B2055_PRG_GCHP27 0xC3 /* PRG GC HPVGA23 27 */ +#define B2055_PRG_GCHP28 0xC4 /* PRG GC HPVGA23 28 */ +#define B2055_PRG_GCHP29 0xC5 /* PRG GC HPVGA23 29 */ +#define B2055_PRG_GCHP30 0xC6 /* PRG GC HPVGA23 30 */ +#define B2055_C1_LNA_GAINBST 0xCD /* Core 1 LNA GAINBST */ +#define B2055_C1_B0NB_RSSIVCM 0xD2 /* Core 1 B0 narrow-band RSSI VCM */ +#define B2055_C1_GENSPARE2 0xD6 /* Core 1 GEN spare 2 */ +#define B2055_C2_LNA_GAINBST 0xD9 /* Core 2 LNA GAINBST */ +#define B2055_C2_B0NB_RSSIVCM 0xDE /* Core 2 B0 narrow-band RSSI VCM */ +#define B2055_C2_GENSPARE2 0xE2 /* Core 2 GEN spare 2 */ + +struct b43_nphy_channeltab_entry_rev2 { + /* The channel number */ + u8 channel; + /* The channel frequency in MHz */ + u16 freq; + /* An unknown value */ + u16 unk2; + /* Radio register values on channelswitch */ + u8 radio_pll_ref; + u8 radio_rf_pllmod0; + u8 radio_rf_pllmod1; + u8 radio_vco_captail; + u8 radio_vco_cal1; + u8 radio_vco_cal2; + u8 radio_pll_lfc1; + u8 radio_pll_lfr1; + u8 radio_pll_lfc2; + u8 radio_lgbuf_cenbuf; + u8 radio_lgen_tune1; + u8 radio_lgen_tune2; + u8 radio_c1_lgbuf_atune; + u8 radio_c1_lgbuf_gtune; + u8 radio_c1_rx_rfr1; + u8 radio_c1_tx_pgapadtn; + u8 radio_c1_tx_mxbgtrim; + u8 radio_c2_lgbuf_atune; + u8 radio_c2_lgbuf_gtune; + u8 radio_c2_rx_rfr1; + u8 radio_c2_tx_pgapadtn; + u8 radio_c2_tx_mxbgtrim; + /* PHY register values on channelswitch */ + struct b43_phy_n_sfo_cfg phy_regs; +}; + +/* Upload the default register value table. + * If "ghz5" is true, we upload the 5Ghz table. Otherwise the 2.4Ghz + * table is uploaded. If "ignore_uploadflag" is true, we upload any value + * and ignore the "UPLOAD" flag. */ +void b2055_upload_inittab(struct b43_wldev *dev, + bool ghz5, bool ignore_uploadflag); + +/* Get the NPHY Channel Switch Table entry for a channel. + * Returns NULL on failure to find an entry. */ +const struct b43_nphy_channeltab_entry_rev2 * +b43_nphy_get_chantabent_rev2(struct b43_wldev *dev, u8 channel); + +#endif /* B43_RADIO_2055_H_ */ diff --git a/drivers/net/wireless/b43/radio_2056.c b/drivers/net/wireless/b43/radio_2056.c new file mode 100644 index 0000000..b4fd934 --- /dev/null +++ b/drivers/net/wireless/b43/radio_2056.c @@ -0,0 +1,9116 @@ +/* + + Broadcom B43 wireless driver + IEEE 802.11n 2056 radio device data tables + + Copyright (c) 2010 Rafał Miłecki + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" +#include "radio_2056.h" +#include "phy_common.h" + +struct b2056_inittab_entry { + /* Value to write if we use the 5GHz band. */ + u16 ghz5; + /* Value to write if we use the 2.4GHz band. */ + u16 ghz2; + /* Flags */ + u8 flags; +}; +#define B2056_INITTAB_ENTRY_OK 0x01 +#define B2056_INITTAB_UPLOAD 0x02 +#define UPLOAD .flags = B2056_INITTAB_ENTRY_OK | B2056_INITTAB_UPLOAD +#define NOUPLOAD .flags = B2056_INITTAB_ENTRY_OK + +struct b2056_inittabs_pts { + const struct b2056_inittab_entry *syn; + unsigned int syn_length; + const struct b2056_inittab_entry *tx; + unsigned int tx_length; + const struct b2056_inittab_entry *rx; + unsigned int rx_length; +}; + +static const struct b2056_inittab_entry b2056_inittab_rev3_syn[] = { + [B2056_SYN_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_PU] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_GPIO_MASTER1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_GPIO_MASTER2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TOPBIAS_MASTER] = { .ghz5 = 0x0060, .ghz2 = 0x0060, NOUPLOAD, }, + [B2056_SYN_TOPBIAS_RCAL] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_AFEREG] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSE] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSEIDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSERCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LPO] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_VDDCAL_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_VDDCAL_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_VDDCAL_STATUS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCAL_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCAL_CODE_OUT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL0] = { .ghz5 = 0x000d, .ghz2 = 0x000d, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL1] = { .ghz5 = 0x001f, .ghz2 = 0x001f, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL2] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL3] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_ZCAL_SPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_ZCAL_SPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_MAST1] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_SYN_PLL_MAST2] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_PLL_MAST3] = { .ghz5 = 0x0018, .ghz2 = 0x0018, NOUPLOAD, }, + [B2056_SYN_PLL_BIAS_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL1] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL3] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL5] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL6] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_SYN_PLL_REFDIV] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_PLL_PFD] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_CP1] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_PLL_CP2] = { .ghz5 = 0x0030, .ghz2 = 0x0030, NOUPLOAD, }, + [B2056_SYN_PLL_CP3] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER1] = { .ghz5 = 0x000d, .ghz2 = 0x000d, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER2] = { .ghz5 = 0x000d, .ghz2 = 0x000d, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER3] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER4] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER5] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_PLL_MMD1] = { .ghz5 = 0x001c, .ghz2 = 0x001c, NOUPLOAD, }, + [B2056_SYN_PLL_MMD2] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCO1] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCO2] = { .ghz5 = 0x00f7, .ghz2 = 0x00f7, UPLOAD, }, + [B2056_SYN_PLL_MONITOR1] = { .ghz5 = 0x00b4, .ghz2 = 0x00b4, NOUPLOAD, }, + [B2056_SYN_PLL_MONITOR2] = { .ghz5 = 0x00d2, .ghz2 = 0x00d2, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL4] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL5] = { .ghz5 = 0x0096, .ghz2 = 0x0096, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL6] = { .ghz5 = 0x003e, .ghz2 = 0x003e, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL7] = { .ghz5 = 0x003e, .ghz2 = 0x003e, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL8] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL9] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL11] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL12] = { .ghz5 = 0x0007, .ghz2 = 0x0007, UPLOAD, }, + [B2056_SYN_PLL_VCOCAL13] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_SYN_PLL_VREG] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU2] = { .ghz5 = 0x0040, .ghz2 = 0x0040, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU8] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_LOGEN_BIAS_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RCCR1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF1] = { .ghz5 = 0x0060, .ghz2 = 0x0060, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER1] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER2] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF1] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGENBUF2] = { .ghz5 = 0x008f, .ghz2 = 0x008f, UPLOAD, }, + [B2056_SYN_LOGEN_BUF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF4] = { .ghz5 = 0x00cc, .ghz2 = 0x00cc, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV1] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV2] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV3] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLOUT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_CALEN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PEAKDET1] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, NOUPLOAD, }, + [B2056_SYN_LOGEN_CORE_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_DIFF_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_DIFF_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_CMOS_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_CMOS_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF2_OVRVAL]= { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER3_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF5_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF6_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL_WAITCNT] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_SYN_LOGEN_CORE_CALVALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_CMOS_CALVALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_CMOS_VALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev3_tx[] = { + [B2056_TX_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_IQCAL_GAIN_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_LOFT_FINE_I] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_FINE_Q] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_COARSE_I] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_COARSE_Q] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_TX_COM_MASTER1] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_TX_COM_MASTER2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RXIQCAL_TXMUX] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_TX_SSI_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_IQCAL_VCM_HG] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_IQCAL_IDAC] = { .ghz5 = 0x0037, .ghz2 = 0x0037, NOUPLOAD, }, + [B2056_TX_TSSI_VCM] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_TX_AMP_DET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TX_SSI_MUX] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSIA] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_TSSIG] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_TSSI_MISC1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSI_MISC2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSI_MISC3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PA_SPARE1] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_TX_PA_SPARE2] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_TX_INTPAA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_BOOST_TUNE] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAA_IAUX_STAT] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_TX_INTPAA_IAUX_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_IMAIN_STAT] = { .ghz5 = 0x002d, .ghz2 = 0x002d, NOUPLOAD, }, + [B2056_TX_INTPAA_IMAIN_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_CASCBIAS] = { .ghz5 = 0x006e, .ghz2 = 0x006e, NOUPLOAD, }, + [B2056_TX_INTPAA_PASLOPE] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_TX_INTPAA_PA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_BOOST_TUNE] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAG_IAUX_STAT] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAG_IAUX_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_IMAIN_STAT] = { .ghz5 = 0x001e, .ghz2 = 0x001e, NOUPLOAD, }, + [B2056_TX_INTPAG_IMAIN_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_CASCBIAS] = { .ghz5 = 0x006e, .ghz2 = 0x006e, NOUPLOAD, }, + [B2056_TX_INTPAG_PASLOPE] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_TX_INTPAG_PA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADA_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PADA_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_TX_PADA_CASCBIAS] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_PADA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADA_BOOST_TUNE] = { .ghz5 = 0x0038, .ghz2 = 0x0038, NOUPLOAD, }, + [B2056_TX_PADA_SLOPE] = { .ghz5 = 0x0070, .ghz2 = 0x0070, UPLOAD, }, + [B2056_TX_PADG_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PADG_IDAC] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_PADG_CASCBIAS] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_PADG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADG_BOOST_TUNE] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_TX_PADG_SLOPE] = { .ghz5 = 0x0070, .ghz2 = 0x0070, UPLOAD, }, + [B2056_TX_PGAA_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PGAA_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_TX_PGAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAA_BOOST_TUNE] = { .ghz5 = 0x0083, .ghz2 = 0x0083, NOUPLOAD, }, + [B2056_TX_PGAA_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, UPLOAD, }, + [B2056_TX_PGAA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAG_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PGAG_IDAC] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_PGAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAG_BOOST_TUNE] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_TX_PGAG_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, UPLOAD, }, + [B2056_TX_PGAG_MISC] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_MIXA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_MIXA_BOOST_TUNE] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_TX_MIXG] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_MIXG_BOOST_TUNE] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_TX_BB_GM_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_GMBB_GM] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC] = { .ghz5 = 0x0074, .ghz2 = 0x0074, UPLOAD, }, + [B2056_TX_TXLPF_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_BW] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_TXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_0] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_1] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_2] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_3] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_4] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_5] = { .ghz5 = 0x001b, .ghz2 = 0x001b, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_6] = { .ghz5 = 0x001b, .ghz2 = 0x001b, NOUPLOAD, }, + [B2056_TX_TXLPF_OPAMP_IDAC] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_TX_TXLPF_MISC] = { .ghz5 = 0x005b, .ghz2 = 0x005b, NOUPLOAD, }, + [B2056_TX_TXSPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE12] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE13] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE14] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE15] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_INTPA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_PAD_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_PGA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_GM_TXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_TXLPF_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_TXLPF_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev3_rx[] = { + [B2056_RX_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXIQCAL_RXMUX] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_RX_RSSI_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RSSI_SEL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RSSI_GAIN] = { .ghz5 = 0x0090, .ghz2 = 0x0090, NOUPLOAD, }, + [B2056_RX_RSSI_NB_IDAC] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_RX_RSSI_WB2I_IDAC_1] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_RX_RSSI_WB2I_IDAC_2] = { .ghz5 = 0x0005, .ghz2 = 0x0005, NOUPLOAD, }, + [B2056_RX_RSSI_WB2Q_IDAC_1] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_RX_RSSI_WB2Q_IDAC_2] = { .ghz5 = 0x0005, .ghz2 = 0x0005, NOUPLOAD, }, + [B2056_RX_RSSI_POLE] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_RSSI_WB1_IDAC] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_RX_RSSI_MISC] = { .ghz5 = 0x0090, .ghz2 = 0x0090, NOUPLOAD, }, + [B2056_RX_LNAA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_LNAA_TUNE] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_RX_LNAA_GAIN] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_RX_LNA_A_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_RX_BIASPOLE_LNAA1_IDAC] = { .ghz5 = 0x0017, .ghz2 = 0x0017, UPLOAD, }, + [B2056_RX_LNAA2_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_RX_LNA1A_MISC] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_LNAG_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_LNAG_TUNE] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_RX_LNAG_GAIN] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_RX_LNA_G_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_RX_BIASPOLE_LNAG1_IDAC] = { .ghz5 = 0x0017, .ghz2 = 0x0017, UPLOAD, }, + [B2056_RX_LNAG2_IDAC] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_RX_LNA1G_MISC] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_MIXA_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_MIXA_VCM] = { .ghz5 = 0x0099, .ghz2 = 0x0099, NOUPLOAD, }, + [B2056_RX_MIXA_CTRLPTAT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXA_LOB_BIAS] = { .ghz5 = 0x0044, .ghz2 = 0x0044, UPLOAD, }, + [B2056_RX_MIXA_CORE_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXA_CMFB_IDAC] = { .ghz5 = 0x0044, .ghz2 = 0x0044, NOUPLOAD, }, + [B2056_RX_MIXA_BIAS_AUX] = { .ghz5 = 0x000f, .ghz2 = 0x000f, UPLOAD, }, + [B2056_RX_MIXA_BIAS_MAIN] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_RX_MIXA_BIAS_MISC] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_RX_MIXA_MAST_BIAS] = { .ghz5 = 0x0050, .ghz2 = 0x0050, UPLOAD, }, + [B2056_RX_MIXG_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_MIXG_VCM] = { .ghz5 = 0x0099, .ghz2 = 0x0099, NOUPLOAD, }, + [B2056_RX_MIXG_CTRLPTAT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXG_LOB_BIAS] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_RX_MIXG_CORE_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXG_CMFB_IDAC] = { .ghz5 = 0x0044, .ghz2 = 0x0044, NOUPLOAD, }, + [B2056_RX_MIXG_BIAS_AUX] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_MIXG_BIAS_MAIN] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_RX_MIXG_BIAS_MISC] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_RX_MIXG_MAST_BIAS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_IOPAMP] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_RX_TIA_QOPAMP] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_RX_TIA_IMISC] = { .ghz5 = 0x0057, .ghz2 = 0x0057, NOUPLOAD, }, + [B2056_RX_TIA_QMISC] = { .ghz5 = 0x0057, .ghz2 = 0x0057, NOUPLOAD, }, + [B2056_RX_TIA_GAIN] = { .ghz5 = 0x0044, .ghz2 = 0x0044, NOUPLOAD, }, + [B2056_RX_TIA_SPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_SPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_BB_LPF_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_AACI_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_RXLPF_IDAC] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_RXLPF_OPAMPBIAS_LOWQ] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_RXLPF_OPAMPBIAS_HIGHQ]= { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_RXLPF_BIAS_DCCANCEL] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_RX_RXLPF_OUTVCM] = { .ghz5 = 0x0023, .ghz2 = 0x0023, NOUPLOAD, }, + [B2056_RX_RXLPF_INVCM_BODY] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_RXLPF_CC_OP] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_RX_RXLPF_GAIN] = { .ghz5 = 0x0023, .ghz2 = 0x0023, NOUPLOAD, }, + [B2056_RX_RXLPF_Q_BW] = { .ghz5 = 0x0041, .ghz2 = 0x0041, NOUPLOAD, }, + [B2056_RX_RXLPF_HP_CORNER_BW] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_RX_RXLPF_RCCAL_HPC] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_RCCAL_LPC] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_UNUSED] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_VGA_MASTER] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGA_BIAS] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGA_BIAS_DCCANCEL] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_RX_VGA_GAIN] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_RX_VGA_HP_CORNER_BW] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_RX_VGABUF_BIAS] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGABUF_GAIN_BW] = { .ghz5 = 0x0030, .ghz2 = 0x0030, NOUPLOAD, }, + [B2056_RX_TXFBMIX_A] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TXFBMIX_G] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE12] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE13] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE14] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE15] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_LNAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_LNAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_MIXTIA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_VGA_BUF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_Q] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_BUF_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_VGA_HPC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_HPC_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev4_syn[] = { + [B2056_SYN_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_PU] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_GPIO_MASTER1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_GPIO_MASTER2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TOPBIAS_MASTER] = { .ghz5 = 0x0060, .ghz2 = 0x0060, NOUPLOAD, }, + [B2056_SYN_TOPBIAS_RCAL] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_AFEREG] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSE] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSEIDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSERCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LPO] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_VDDCAL_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_VDDCAL_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_VDDCAL_STATUS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCAL_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCAL_CODE_OUT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL0] = { .ghz5 = 0x000d, .ghz2 = 0x000d, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL1] = { .ghz5 = 0x001f, .ghz2 = 0x001f, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL2] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL3] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_ZCAL_SPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_ZCAL_SPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_MAST1] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_SYN_PLL_MAST2] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_PLL_MAST3] = { .ghz5 = 0x0018, .ghz2 = 0x0018, NOUPLOAD, }, + [B2056_SYN_PLL_BIAS_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL1] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL3] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL5] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL6] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_SYN_PLL_REFDIV] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_PLL_PFD] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_CP1] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_PLL_CP2] = { .ghz5 = 0x0030, .ghz2 = 0x0030, NOUPLOAD, }, + [B2056_SYN_PLL_CP3] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER1] = { .ghz5 = 0x000d, .ghz2 = 0x000d, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER2] = { .ghz5 = 0x000d, .ghz2 = 0x000d, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER3] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER4] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER5] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_PLL_MMD1] = { .ghz5 = 0x001c, .ghz2 = 0x001c, NOUPLOAD, }, + [B2056_SYN_PLL_MMD2] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCO1] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCO2] = { .ghz5 = 0x00f7, .ghz2 = 0x00f7, UPLOAD, }, + [B2056_SYN_PLL_MONITOR1] = { .ghz5 = 0x00b4, .ghz2 = 0x00b4, NOUPLOAD, }, + [B2056_SYN_PLL_MONITOR2] = { .ghz5 = 0x00d2, .ghz2 = 0x00d2, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL4] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL5] = { .ghz5 = 0x0096, .ghz2 = 0x0096, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL6] = { .ghz5 = 0x003e, .ghz2 = 0x003e, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL7] = { .ghz5 = 0x003e, .ghz2 = 0x003e, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL8] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL9] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL11] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL12] = { .ghz5 = 0x0007, .ghz2 = 0x0007, UPLOAD, }, + [B2056_SYN_PLL_VCOCAL13] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_SYN_PLL_VREG] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU2] = { .ghz5 = 0x0040, .ghz2 = 0x0040, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU8] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_LOGEN_BIAS_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RCCR1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF1] = { .ghz5 = 0x0060, .ghz2 = 0x0060, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER1] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER2] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF1] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGENBUF2] = { .ghz5 = 0x008f, .ghz2 = 0x008f, UPLOAD, }, + [B2056_SYN_LOGEN_BUF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF4] = { .ghz5 = 0x00cc, .ghz2 = 0x00cc, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV1] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV2] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV3] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLOUT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_CALEN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PEAKDET1] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, NOUPLOAD, }, + [B2056_SYN_LOGEN_CORE_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_DIFF_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_DIFF_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_CMOS_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_CMOS_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF2_OVRVAL]= { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER3_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF5_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF6_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL_WAITCNT] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_SYN_LOGEN_CORE_CALVALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_CMOS_CALVALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_CMOS_VALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev4_tx[] = { + [B2056_TX_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_IQCAL_GAIN_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_LOFT_FINE_I] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_FINE_Q] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_COARSE_I] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_COARSE_Q] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_TX_COM_MASTER1] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_TX_COM_MASTER2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RXIQCAL_TXMUX] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_TX_SSI_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_IQCAL_VCM_HG] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_IQCAL_IDAC] = { .ghz5 = 0x0037, .ghz2 = 0x0037, NOUPLOAD, }, + [B2056_TX_TSSI_VCM] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_TX_AMP_DET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TX_SSI_MUX] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSIA] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_TSSIG] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_TSSI_MISC1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSI_MISC2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSI_MISC3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PA_SPARE1] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_TX_PA_SPARE2] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_TX_INTPAA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_BOOST_TUNE] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAA_IAUX_STAT] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_TX_INTPAA_IAUX_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_IMAIN_STAT] = { .ghz5 = 0x002d, .ghz2 = 0x002d, NOUPLOAD, }, + [B2056_TX_INTPAA_IMAIN_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_CASCBIAS] = { .ghz5 = 0x006e, .ghz2 = 0x006e, NOUPLOAD, }, + [B2056_TX_INTPAA_PASLOPE] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_TX_INTPAA_PA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_BOOST_TUNE] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAG_IAUX_STAT] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAG_IAUX_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_IMAIN_STAT] = { .ghz5 = 0x001e, .ghz2 = 0x001e, NOUPLOAD, }, + [B2056_TX_INTPAG_IMAIN_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_CASCBIAS] = { .ghz5 = 0x006e, .ghz2 = 0x006e, NOUPLOAD, }, + [B2056_TX_INTPAG_PASLOPE] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_TX_INTPAG_PA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADA_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PADA_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_TX_PADA_CASCBIAS] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_PADA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADA_BOOST_TUNE] = { .ghz5 = 0x0038, .ghz2 = 0x0038, NOUPLOAD, }, + [B2056_TX_PADA_SLOPE] = { .ghz5 = 0x0070, .ghz2 = 0x0070, UPLOAD, }, + [B2056_TX_PADG_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PADG_IDAC] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_PADG_CASCBIAS] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_PADG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADG_BOOST_TUNE] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_TX_PADG_SLOPE] = { .ghz5 = 0x0070, .ghz2 = 0x0070, UPLOAD, }, + [B2056_TX_PGAA_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PGAA_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_TX_PGAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAA_BOOST_TUNE] = { .ghz5 = 0x0083, .ghz2 = 0x0083, NOUPLOAD, }, + [B2056_TX_PGAA_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, UPLOAD, }, + [B2056_TX_PGAA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAG_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PGAG_IDAC] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_PGAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAG_BOOST_TUNE] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_TX_PGAG_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, UPLOAD, }, + [B2056_TX_PGAG_MISC] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_MIXA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_MIXA_BOOST_TUNE] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_TX_MIXG] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_MIXG_BOOST_TUNE] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_TX_BB_GM_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_GMBB_GM] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC] = { .ghz5 = 0x0072, .ghz2 = 0x0072, UPLOAD, }, + [B2056_TX_TXLPF_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_BW] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_TXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_0] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_1] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_2] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_3] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_4] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_5] = { .ghz5 = 0x001b, .ghz2 = 0x001b, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_6] = { .ghz5 = 0x001b, .ghz2 = 0x001b, NOUPLOAD, }, + [B2056_TX_TXLPF_OPAMP_IDAC] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_TX_TXLPF_MISC] = { .ghz5 = 0x005b, .ghz2 = 0x005b, NOUPLOAD, }, + [B2056_TX_TXSPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE12] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE13] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE14] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE15] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_INTPA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_PAD_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_PGA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_GM_TXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_TXLPF_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_TXLPF_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev4_rx[] = { + [B2056_RX_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXIQCAL_RXMUX] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_RX_RSSI_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RSSI_SEL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RSSI_GAIN] = { .ghz5 = 0x0090, .ghz2 = 0x0090, NOUPLOAD, }, + [B2056_RX_RSSI_NB_IDAC] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_RX_RSSI_WB2I_IDAC_1] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_RX_RSSI_WB2I_IDAC_2] = { .ghz5 = 0x0005, .ghz2 = 0x0005, NOUPLOAD, }, + [B2056_RX_RSSI_WB2Q_IDAC_1] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_RX_RSSI_WB2Q_IDAC_2] = { .ghz5 = 0x0005, .ghz2 = 0x0005, NOUPLOAD, }, + [B2056_RX_RSSI_POLE] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_RSSI_WB1_IDAC] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_RX_RSSI_MISC] = { .ghz5 = 0x0090, .ghz2 = 0x0090, NOUPLOAD, }, + [B2056_RX_LNAA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_LNAA_TUNE] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_RX_LNAA_GAIN] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_RX_LNA_A_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_RX_BIASPOLE_LNAA1_IDAC] = { .ghz5 = 0x0017, .ghz2 = 0x0017, UPLOAD, }, + [B2056_RX_LNAA2_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_RX_LNA1A_MISC] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_LNAG_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_LNAG_TUNE] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_RX_LNAG_GAIN] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_RX_LNA_G_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_RX_BIASPOLE_LNAG1_IDAC] = { .ghz5 = 0x0017, .ghz2 = 0x0017, UPLOAD, }, + [B2056_RX_LNAG2_IDAC] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_RX_LNA1G_MISC] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_MIXA_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_MIXA_VCM] = { .ghz5 = 0x0055, .ghz2 = 0x0055, UPLOAD, }, + [B2056_RX_MIXA_CTRLPTAT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXA_LOB_BIAS] = { .ghz5 = 0x0044, .ghz2 = 0x0044, UPLOAD, }, + [B2056_RX_MIXA_CORE_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXA_CMFB_IDAC] = { .ghz5 = 0x0044, .ghz2 = 0x0044, NOUPLOAD, }, + [B2056_RX_MIXA_BIAS_AUX] = { .ghz5 = 0x000f, .ghz2 = 0x000f, UPLOAD, }, + [B2056_RX_MIXA_BIAS_MAIN] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_RX_MIXA_BIAS_MISC] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_RX_MIXA_MAST_BIAS] = { .ghz5 = 0x0050, .ghz2 = 0x0050, UPLOAD, }, + [B2056_RX_MIXG_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_MIXG_VCM] = { .ghz5 = 0x0055, .ghz2 = 0x0055, UPLOAD, }, + [B2056_RX_MIXG_CTRLPTAT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXG_LOB_BIAS] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_RX_MIXG_CORE_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXG_CMFB_IDAC] = { .ghz5 = 0x0044, .ghz2 = 0x0044, NOUPLOAD, }, + [B2056_RX_MIXG_BIAS_AUX] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_MIXG_BIAS_MAIN] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_RX_MIXG_BIAS_MISC] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_RX_MIXG_MAST_BIAS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_IOPAMP] = { .ghz5 = 0x0026, .ghz2 = 0x0026, UPLOAD, }, + [B2056_RX_TIA_QOPAMP] = { .ghz5 = 0x0026, .ghz2 = 0x0026, UPLOAD, }, + [B2056_RX_TIA_IMISC] = { .ghz5 = 0x000f, .ghz2 = 0x000f, UPLOAD, }, + [B2056_RX_TIA_QMISC] = { .ghz5 = 0x000f, .ghz2 = 0x000f, UPLOAD, }, + [B2056_RX_TIA_GAIN] = { .ghz5 = 0x0044, .ghz2 = 0x0044, NOUPLOAD, }, + [B2056_RX_TIA_SPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_SPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_BB_LPF_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_AACI_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_RXLPF_IDAC] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_RXLPF_OPAMPBIAS_LOWQ] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_RXLPF_OPAMPBIAS_HIGHQ]= { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_RXLPF_BIAS_DCCANCEL] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_RX_RXLPF_OUTVCM] = { .ghz5 = 0x002f, .ghz2 = 0x002f, UPLOAD, }, + [B2056_RX_RXLPF_INVCM_BODY] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_RXLPF_CC_OP] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_RX_RXLPF_GAIN] = { .ghz5 = 0x0023, .ghz2 = 0x0023, NOUPLOAD, }, + [B2056_RX_RXLPF_Q_BW] = { .ghz5 = 0x0041, .ghz2 = 0x0041, NOUPLOAD, }, + [B2056_RX_RXLPF_HP_CORNER_BW] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_RX_RXLPF_RCCAL_HPC] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_RCCAL_LPC] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_UNUSED] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_VGA_MASTER] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGA_BIAS] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGA_BIAS_DCCANCEL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, UPLOAD, }, + [B2056_RX_VGA_GAIN] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_RX_VGA_HP_CORNER_BW] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_RX_VGABUF_BIAS] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGABUF_GAIN_BW] = { .ghz5 = 0x0030, .ghz2 = 0x0030, NOUPLOAD, }, + [B2056_RX_TXFBMIX_A] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TXFBMIX_G] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE12] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE13] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE14] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE15] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_LNAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_LNAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_MIXTIA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_VGA_BUF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_Q] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_BUF_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_VGA_HPC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_HPC_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev5_syn[] = { + [B2056_SYN_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_PU] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_GPIO_MASTER1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_GPIO_MASTER2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TOPBIAS_MASTER] = { .ghz5 = 0x0060, .ghz2 = 0x0060, NOUPLOAD, }, + [B2056_SYN_TOPBIAS_RCAL] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_AFEREG] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSE] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSEIDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSERCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LPO] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_VDDCAL_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_VDDCAL_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_VDDCAL_STATUS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCAL_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCAL_CODE_OUT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL1] = { .ghz5 = 0x001f, .ghz2 = 0x001f, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL2] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL3] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_ZCAL_SPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_ZCAL_SPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_MAST1] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_SYN_PLL_MAST2] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_PLL_MAST3] = { .ghz5 = 0x0018, .ghz2 = 0x0018, NOUPLOAD, }, + [B2056_SYN_PLL_BIAS_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL1] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL3] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL5] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL6] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_SYN_PLL_REFDIV] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_PLL_PFD] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_CP1] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_PLL_CP2] = { .ghz5 = 0x0030, .ghz2 = 0x0030, NOUPLOAD, }, + [B2056_SYN_PLL_CP3] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER1] = { .ghz5 = 0x000d, .ghz2 = 0x000d, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER2] = { .ghz5 = 0x000d, .ghz2 = 0x000d, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER3] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER4] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER5] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_PLL_MMD1] = { .ghz5 = 0x001c, .ghz2 = 0x001c, NOUPLOAD, }, + [B2056_SYN_PLL_MMD2] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCO1] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCO2] = { .ghz5 = 0x00f7, .ghz2 = 0x00f7, UPLOAD, }, + [B2056_SYN_PLL_MONITOR1] = { .ghz5 = 0x00b4, .ghz2 = 0x00b4, NOUPLOAD, }, + [B2056_SYN_PLL_MONITOR2] = { .ghz5 = 0x00d2, .ghz2 = 0x00d2, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL4] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL5] = { .ghz5 = 0x0096, .ghz2 = 0x0096, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL6] = { .ghz5 = 0x003e, .ghz2 = 0x003e, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL7] = { .ghz5 = 0x003e, .ghz2 = 0x003e, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL8] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL9] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL11] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL12] = { .ghz5 = 0x0007, .ghz2 = 0x0007, UPLOAD, }, + [B2056_SYN_PLL_VCOCAL13] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_SYN_PLL_VREG] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU2] = { .ghz5 = 0x0040, .ghz2 = 0x0040, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU8] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_LOGEN_BIAS_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RCCR1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF1] = { .ghz5 = 0x0060, .ghz2 = 0x0060, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER1] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER2] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF1] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGENBUF2] = { .ghz5 = 0x008f, .ghz2 = 0x008f, UPLOAD, }, + [B2056_SYN_LOGEN_BUF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF4] = { .ghz5 = 0x00cc, .ghz2 = 0x00cc, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV1] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV2] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV3] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLOUT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_CALEN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PEAKDET1] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, NOUPLOAD, }, + [B2056_SYN_LOGEN_CORE_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_DIFF_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_DIFF_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_CMOS_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_CMOS_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF2_OVRVAL]= { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER3_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF5_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF6_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL_WAITCNT] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_SYN_LOGEN_CORE_CALVALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_CMOS_CALVALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_CMOS_VALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev5_tx[] = { + [B2056_TX_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_IQCAL_GAIN_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_LOFT_FINE_I] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_FINE_Q] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_COARSE_I] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_COARSE_Q] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_TX_COM_MASTER1] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_TX_COM_MASTER2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RXIQCAL_TXMUX] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_TX_SSI_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_IQCAL_VCM_HG] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_IQCAL_IDAC] = { .ghz5 = 0x0037, .ghz2 = 0x0037, NOUPLOAD, }, + [B2056_TX_TSSI_VCM] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_TX_AMP_DET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TX_SSI_MUX] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSIA] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_TSSIG] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_TSSI_MISC1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSI_MISC2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSI_MISC3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PA_SPARE1] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_TX_PA_SPARE2] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_TX_INTPAA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_BOOST_TUNE] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAA_IAUX_STAT] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_TX_INTPAA_IAUX_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_IMAIN_STAT] = { .ghz5 = 0x002d, .ghz2 = 0x002d, NOUPLOAD, }, + [B2056_TX_INTPAA_IMAIN_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_CASCBIAS] = { .ghz5 = 0x006e, .ghz2 = 0x006e, NOUPLOAD, }, + [B2056_TX_INTPAA_PASLOPE] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_TX_INTPAA_PA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_BOOST_TUNE] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAG_IAUX_STAT] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAG_IAUX_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_IMAIN_STAT] = { .ghz5 = 0x001e, .ghz2 = 0x001e, NOUPLOAD, }, + [B2056_TX_INTPAG_IMAIN_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_CASCBIAS] = { .ghz5 = 0x006e, .ghz2 = 0x006e, NOUPLOAD, }, + [B2056_TX_INTPAG_PASLOPE] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_TX_INTPAG_PA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADA_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PADA_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_TX_PADA_CASCBIAS] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_PADA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADA_BOOST_TUNE] = { .ghz5 = 0x0038, .ghz2 = 0x0038, NOUPLOAD, }, + [B2056_TX_PADA_SLOPE] = { .ghz5 = 0x0070, .ghz2 = 0x0070, UPLOAD, }, + [B2056_TX_PADG_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PADG_IDAC] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_PADG_CASCBIAS] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_PADG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADG_BOOST_TUNE] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_TX_PADG_SLOPE] = { .ghz5 = 0x0070, .ghz2 = 0x0070, UPLOAD, }, + [B2056_TX_PGAA_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PGAA_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_TX_PGAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAA_BOOST_TUNE] = { .ghz5 = 0x0083, .ghz2 = 0x0083, NOUPLOAD, }, + [B2056_TX_PGAA_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, UPLOAD, }, + [B2056_TX_PGAA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAG_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PGAG_IDAC] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_PGAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAG_BOOST_TUNE] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_TX_PGAG_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, UPLOAD, }, + [B2056_TX_PGAG_MISC] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_MIXA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_MIXA_BOOST_TUNE] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_TX_MIXG] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_MIXG_BOOST_TUNE] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_TX_BB_GM_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_GMBB_GM] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, UPLOAD, }, + [B2056_TX_TXLPF_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_BW] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_TXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_0] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_1] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_2] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_3] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_4] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_5] = { .ghz5 = 0x001b, .ghz2 = 0x001b, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_6] = { .ghz5 = 0x001b, .ghz2 = 0x001b, NOUPLOAD, }, + [B2056_TX_TXLPF_OPAMP_IDAC] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_TX_TXLPF_MISC] = { .ghz5 = 0x005b, .ghz2 = 0x005b, NOUPLOAD, }, + [B2056_TX_TXSPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE12] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE13] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE14] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE15] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_INTPA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_PAD_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_PGA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_GM_TXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_TXLPF_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_TXLPF_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC0] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC1] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC2] = { .ghz5 = 0x0071, .ghz2 = 0x0071, UPLOAD, }, + [B2056_TX_GMBB_IDAC3] = { .ghz5 = 0x0071, .ghz2 = 0x0071, UPLOAD, }, + [B2056_TX_GMBB_IDAC4] = { .ghz5 = 0x0072, .ghz2 = 0x0072, UPLOAD, }, + [B2056_TX_GMBB_IDAC5] = { .ghz5 = 0x0073, .ghz2 = 0x0073, UPLOAD, }, + [B2056_TX_GMBB_IDAC6] = { .ghz5 = 0x0074, .ghz2 = 0x0074, UPLOAD, }, + [B2056_TX_GMBB_IDAC7] = { .ghz5 = 0x0075, .ghz2 = 0x0075, UPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev5_rx[] = { + [B2056_RX_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXIQCAL_RXMUX] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_RX_RSSI_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RSSI_SEL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RSSI_GAIN] = { .ghz5 = 0x0090, .ghz2 = 0x0090, NOUPLOAD, }, + [B2056_RX_RSSI_NB_IDAC] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_RX_RSSI_WB2I_IDAC_1] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_RX_RSSI_WB2I_IDAC_2] = { .ghz5 = 0x0005, .ghz2 = 0x0005, NOUPLOAD, }, + [B2056_RX_RSSI_WB2Q_IDAC_1] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_RX_RSSI_WB2Q_IDAC_2] = { .ghz5 = 0x0005, .ghz2 = 0x0005, NOUPLOAD, }, + [B2056_RX_RSSI_POLE] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_RSSI_WB1_IDAC] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_RX_RSSI_MISC] = { .ghz5 = 0x0090, .ghz2 = 0x0090, NOUPLOAD, }, + [B2056_RX_LNAA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_LNAA_TUNE] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_RX_LNAA_GAIN] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_RX_LNA_A_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_RX_BIASPOLE_LNAA1_IDAC] = { .ghz5 = 0x0017, .ghz2 = 0x0017, UPLOAD, }, + [B2056_RX_LNAA2_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_RX_LNA1A_MISC] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_LNAG_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_LNAG_TUNE] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_RX_LNAG_GAIN] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_RX_LNA_G_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_RX_BIASPOLE_LNAG1_IDAC] = { .ghz5 = 0x0017, .ghz2 = 0x0017, UPLOAD, }, + [B2056_RX_LNAG2_IDAC] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_RX_LNA1G_MISC] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_MIXA_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_MIXA_VCM] = { .ghz5 = 0x0055, .ghz2 = 0x0055, UPLOAD, }, + [B2056_RX_MIXA_CTRLPTAT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXA_LOB_BIAS] = { .ghz5 = 0x0088, .ghz2 = 0x0088, UPLOAD, }, + [B2056_RX_MIXA_CORE_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXA_CMFB_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, UPLOAD, }, + [B2056_RX_MIXA_BIAS_AUX] = { .ghz5 = 0x0007, .ghz2 = 0x0007, UPLOAD, }, + [B2056_RX_MIXA_BIAS_MAIN] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_RX_MIXA_BIAS_MISC] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_RX_MIXA_MAST_BIAS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXG_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_MIXG_VCM] = { .ghz5 = 0x0055, .ghz2 = 0x0055, UPLOAD, }, + [B2056_RX_MIXG_CTRLPTAT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXG_LOB_BIAS] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_RX_MIXG_CORE_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXG_CMFB_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, UPLOAD, }, + [B2056_RX_MIXG_BIAS_AUX] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_MIXG_BIAS_MAIN] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_RX_MIXG_BIAS_MISC] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_RX_MIXG_MAST_BIAS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_IOPAMP] = { .ghz5 = 0x0026, .ghz2 = 0x0026, UPLOAD, }, + [B2056_RX_TIA_QOPAMP] = { .ghz5 = 0x0026, .ghz2 = 0x0026, UPLOAD, }, + [B2056_RX_TIA_IMISC] = { .ghz5 = 0x000f, .ghz2 = 0x000f, UPLOAD, }, + [B2056_RX_TIA_QMISC] = { .ghz5 = 0x000f, .ghz2 = 0x000f, UPLOAD, }, + [B2056_RX_TIA_GAIN] = { .ghz5 = 0x0044, .ghz2 = 0x0044, NOUPLOAD, }, + [B2056_RX_TIA_SPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_SPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_BB_LPF_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_AACI_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_RXLPF_IDAC] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_RXLPF_OPAMPBIAS_LOWQ] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_RXLPF_OPAMPBIAS_HIGHQ]= { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_RXLPF_BIAS_DCCANCEL] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_RX_RXLPF_OUTVCM] = { .ghz5 = 0x0004, .ghz2 = 0x0004, UPLOAD, }, + [B2056_RX_RXLPF_INVCM_BODY] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_RXLPF_CC_OP] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_RX_RXLPF_GAIN] = { .ghz5 = 0x0023, .ghz2 = 0x0023, NOUPLOAD, }, + [B2056_RX_RXLPF_Q_BW] = { .ghz5 = 0x0041, .ghz2 = 0x0041, NOUPLOAD, }, + [B2056_RX_RXLPF_HP_CORNER_BW] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_RX_RXLPF_RCCAL_HPC] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_RCCAL_LPC] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_UNUSED] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_VGA_MASTER] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGA_BIAS] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGA_BIAS_DCCANCEL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, UPLOAD, }, + [B2056_RX_VGA_GAIN] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_RX_VGA_HP_CORNER_BW] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_RX_VGABUF_BIAS] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGABUF_GAIN_BW] = { .ghz5 = 0x0030, .ghz2 = 0x0030, NOUPLOAD, }, + [B2056_RX_TXFBMIX_A] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TXFBMIX_G] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE12] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE13] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE14] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE15] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_LNAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_LNAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_MIXTIA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_VGA_BUF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_Q] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_BUF_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_VGA_HPC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_HPC_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev6_syn[] = { + [B2056_SYN_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_PU] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_GPIO_MASTER1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_GPIO_MASTER2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TOPBIAS_MASTER] = { .ghz5 = 0x0060, .ghz2 = 0x0060, NOUPLOAD, }, + [B2056_SYN_TOPBIAS_RCAL] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_AFEREG] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSE] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSEIDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSERCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LPO] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_VDDCAL_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_VDDCAL_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_VDDCAL_STATUS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCAL_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCAL_CODE_OUT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL1] = { .ghz5 = 0x001f, .ghz2 = 0x001f, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL2] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL3] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_ZCAL_SPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_ZCAL_SPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_MAST1] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_SYN_PLL_MAST2] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_PLL_MAST3] = { .ghz5 = 0x0018, .ghz2 = 0x0018, NOUPLOAD, }, + [B2056_SYN_PLL_BIAS_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL1] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL3] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL5] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL6] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_SYN_PLL_REFDIV] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_PLL_PFD] = { .ghz5 = 0x0006, .ghz2 = 0x0006, UPLOAD, }, + [B2056_SYN_PLL_CP1] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_PLL_CP2] = { .ghz5 = 0x003f, .ghz2 = 0x003f, UPLOAD, }, + [B2056_SYN_PLL_CP3] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER1] = { .ghz5 = 0x0006, .ghz2 = 0x0006, UPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER2] = { .ghz5 = 0x0006, .ghz2 = 0x0006, UPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER3] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER4] = { .ghz5 = 0x002b, .ghz2 = 0x002b, UPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER5] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_PLL_MMD1] = { .ghz5 = 0x001c, .ghz2 = 0x001c, NOUPLOAD, }, + [B2056_SYN_PLL_MMD2] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCO1] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCO2] = { .ghz5 = 0x00f7, .ghz2 = 0x00f7, UPLOAD, }, + [B2056_SYN_PLL_MONITOR1] = { .ghz5 = 0x00b4, .ghz2 = 0x00b4, NOUPLOAD, }, + [B2056_SYN_PLL_MONITOR2] = { .ghz5 = 0x00d2, .ghz2 = 0x00d2, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL4] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL5] = { .ghz5 = 0x0096, .ghz2 = 0x0096, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL6] = { .ghz5 = 0x003e, .ghz2 = 0x003e, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL7] = { .ghz5 = 0x003e, .ghz2 = 0x003e, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL8] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL9] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL11] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL12] = { .ghz5 = 0x0007, .ghz2 = 0x0007, UPLOAD, }, + [B2056_SYN_PLL_VCOCAL13] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_SYN_PLL_VREG] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU2] = { .ghz5 = 0x0040, .ghz2 = 0x0040, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU8] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_LOGEN_BIAS_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RCCR1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF1] = { .ghz5 = 0x0060, .ghz2 = 0x0060, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER1] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER2] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF1] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGENBUF2] = { .ghz5 = 0x008f, .ghz2 = 0x008f, UPLOAD, }, + [B2056_SYN_LOGEN_BUF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF4] = { .ghz5 = 0x00cc, .ghz2 = 0x00cc, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV1] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV2] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV3] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLOUT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_CALEN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PEAKDET1] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, NOUPLOAD, }, + [B2056_SYN_LOGEN_CORE_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_DIFF_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_DIFF_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_CMOS_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_CMOS_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF2_OVRVAL]= { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER3_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF5_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF6_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL_WAITCNT] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_SYN_LOGEN_CORE_CALVALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_CMOS_CALVALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_CMOS_VALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev6_tx[] = { + [B2056_TX_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_IQCAL_GAIN_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_LOFT_FINE_I] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_FINE_Q] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_COARSE_I] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_COARSE_Q] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_TX_COM_MASTER1] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_TX_COM_MASTER2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RXIQCAL_TXMUX] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_TX_SSI_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_IQCAL_VCM_HG] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_IQCAL_IDAC] = { .ghz5 = 0x0037, .ghz2 = 0x0037, NOUPLOAD, }, + [B2056_TX_TSSI_VCM] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_TX_AMP_DET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TX_SSI_MUX] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSIA] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_TSSIG] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_TSSI_MISC1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSI_MISC2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSI_MISC3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PA_SPARE1] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_TX_PA_SPARE2] = { .ghz5 = 0x00ee, .ghz2 = 0x00ee, UPLOAD, }, + [B2056_TX_INTPAA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_BOOST_TUNE] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAA_IAUX_STAT] = { .ghz5 = 0x0050, .ghz2 = 0x0050, UPLOAD, }, + [B2056_TX_INTPAA_IAUX_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_IMAIN_STAT] = { .ghz5 = 0x0050, .ghz2 = 0x0050, UPLOAD, }, + [B2056_TX_INTPAA_IMAIN_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_CASCBIAS] = { .ghz5 = 0x006e, .ghz2 = 0x006e, NOUPLOAD, }, + [B2056_TX_INTPAA_PASLOPE] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_TX_INTPAA_PA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_BOOST_TUNE] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAG_IAUX_STAT] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAG_IAUX_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_IMAIN_STAT] = { .ghz5 = 0x001e, .ghz2 = 0x001e, NOUPLOAD, }, + [B2056_TX_INTPAG_IMAIN_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_CASCBIAS] = { .ghz5 = 0x006e, .ghz2 = 0x006e, NOUPLOAD, }, + [B2056_TX_INTPAG_PASLOPE] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_TX_INTPAG_PA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADA_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PADA_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_TX_PADA_CASCBIAS] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_PADA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADA_BOOST_TUNE] = { .ghz5 = 0x0038, .ghz2 = 0x0038, NOUPLOAD, }, + [B2056_TX_PADA_SLOPE] = { .ghz5 = 0x0070, .ghz2 = 0x0070, UPLOAD, }, + [B2056_TX_PADG_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PADG_IDAC] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_PADG_CASCBIAS] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_PADG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADG_BOOST_TUNE] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_TX_PADG_SLOPE] = { .ghz5 = 0x0070, .ghz2 = 0x0070, UPLOAD, }, + [B2056_TX_PGAA_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PGAA_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_TX_PGAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAA_BOOST_TUNE] = { .ghz5 = 0x0083, .ghz2 = 0x0083, NOUPLOAD, }, + [B2056_TX_PGAA_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, UPLOAD, }, + [B2056_TX_PGAA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAG_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PGAG_IDAC] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_PGAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAG_BOOST_TUNE] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_TX_PGAG_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, UPLOAD, }, + [B2056_TX_PGAG_MISC] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_MIXA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_MIXA_BOOST_TUNE] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_TX_MIXG] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_MIXG_BOOST_TUNE] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_TX_BB_GM_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_GMBB_GM] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, UPLOAD, }, + [B2056_TX_TXLPF_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_BW] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_TXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_0] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_1] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_2] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_3] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_4] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_5] = { .ghz5 = 0x001b, .ghz2 = 0x001b, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_6] = { .ghz5 = 0x001b, .ghz2 = 0x001b, NOUPLOAD, }, + [B2056_TX_TXLPF_OPAMP_IDAC] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_TX_TXLPF_MISC] = { .ghz5 = 0x005b, .ghz2 = 0x005b, NOUPLOAD, }, + [B2056_TX_TXSPARE1] = { .ghz5 = 0x0030, .ghz2 = 0x0030, UPLOAD, }, + [B2056_TX_TXSPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE12] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE13] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE14] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE15] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_INTPA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_PAD_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_PGA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_GM_TXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_TXLPF_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_TXLPF_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC0] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC1] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC2] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC3] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC4] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC5] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC6] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC7] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev6_rx[] = { + [B2056_RX_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXIQCAL_RXMUX] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_RX_RSSI_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RSSI_SEL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RSSI_GAIN] = { .ghz5 = 0x0090, .ghz2 = 0x0090, NOUPLOAD, }, + [B2056_RX_RSSI_NB_IDAC] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_RX_RSSI_WB2I_IDAC_1] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_RX_RSSI_WB2I_IDAC_2] = { .ghz5 = 0x0005, .ghz2 = 0x0005, NOUPLOAD, }, + [B2056_RX_RSSI_WB2Q_IDAC_1] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_RX_RSSI_WB2Q_IDAC_2] = { .ghz5 = 0x0005, .ghz2 = 0x0005, NOUPLOAD, }, + [B2056_RX_RSSI_POLE] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_RSSI_WB1_IDAC] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_RX_RSSI_MISC] = { .ghz5 = 0x0090, .ghz2 = 0x0090, NOUPLOAD, }, + [B2056_RX_LNAA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_LNAA_TUNE] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_RX_LNAA_GAIN] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_RX_LNA_A_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_RX_BIASPOLE_LNAA1_IDAC] = { .ghz5 = 0x0017, .ghz2 = 0x0017, UPLOAD, }, + [B2056_RX_LNAA2_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_RX_LNA1A_MISC] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_LNAG_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_LNAG_TUNE] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_RX_LNAG_GAIN] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_RX_LNA_G_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_RX_BIASPOLE_LNAG1_IDAC] = { .ghz5 = 0x0017, .ghz2 = 0x0017, UPLOAD, }, + [B2056_RX_LNAG2_IDAC] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_RX_LNA1G_MISC] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_MIXA_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_MIXA_VCM] = { .ghz5 = 0x0055, .ghz2 = 0x0055, UPLOAD, }, + [B2056_RX_MIXA_CTRLPTAT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXA_LOB_BIAS] = { .ghz5 = 0x0088, .ghz2 = 0x0088, UPLOAD, }, + [B2056_RX_MIXA_CORE_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXA_CMFB_IDAC] = { .ghz5 = 0x0044, .ghz2 = 0x0044, NOUPLOAD, }, + [B2056_RX_MIXA_BIAS_AUX] = { .ghz5 = 0x0007, .ghz2 = 0x0007, UPLOAD, }, + [B2056_RX_MIXA_BIAS_MAIN] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_RX_MIXA_BIAS_MISC] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_RX_MIXA_MAST_BIAS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXG_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_MIXG_VCM] = { .ghz5 = 0x0055, .ghz2 = 0x0055, UPLOAD, }, + [B2056_RX_MIXG_CTRLPTAT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXG_LOB_BIAS] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_RX_MIXG_CORE_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXG_CMFB_IDAC] = { .ghz5 = 0x0044, .ghz2 = 0x0044, NOUPLOAD, }, + [B2056_RX_MIXG_BIAS_AUX] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_MIXG_BIAS_MAIN] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_RX_MIXG_BIAS_MISC] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_RX_MIXG_MAST_BIAS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_IOPAMP] = { .ghz5 = 0x0026, .ghz2 = 0x0026, UPLOAD, }, + [B2056_RX_TIA_QOPAMP] = { .ghz5 = 0x0026, .ghz2 = 0x0026, UPLOAD, }, + [B2056_RX_TIA_IMISC] = { .ghz5 = 0x000f, .ghz2 = 0x000f, UPLOAD, }, + [B2056_RX_TIA_QMISC] = { .ghz5 = 0x000f, .ghz2 = 0x000f, UPLOAD, }, + [B2056_RX_TIA_GAIN] = { .ghz5 = 0x0044, .ghz2 = 0x0044, NOUPLOAD, }, + [B2056_RX_TIA_SPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_SPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_BB_LPF_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_AACI_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_RXLPF_IDAC] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_RXLPF_OPAMPBIAS_LOWQ] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_RXLPF_OPAMPBIAS_HIGHQ]= { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_RXLPF_BIAS_DCCANCEL] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_RX_RXLPF_OUTVCM] = { .ghz5 = 0x0004, .ghz2 = 0x0004, UPLOAD, }, + [B2056_RX_RXLPF_INVCM_BODY] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_RXLPF_CC_OP] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_RX_RXLPF_GAIN] = { .ghz5 = 0x0023, .ghz2 = 0x0023, NOUPLOAD, }, + [B2056_RX_RXLPF_Q_BW] = { .ghz5 = 0x0041, .ghz2 = 0x0041, NOUPLOAD, }, + [B2056_RX_RXLPF_HP_CORNER_BW] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_RX_RXLPF_RCCAL_HPC] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_RCCAL_LPC] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_UNUSED] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_VGA_MASTER] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGA_BIAS] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGA_BIAS_DCCANCEL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, UPLOAD, }, + [B2056_RX_VGA_GAIN] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_RX_VGA_HP_CORNER_BW] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_RX_VGABUF_BIAS] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGABUF_GAIN_BW] = { .ghz5 = 0x0030, .ghz2 = 0x0030, NOUPLOAD, }, + [B2056_RX_TXFBMIX_A] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TXFBMIX_G] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE3] = { .ghz5 = 0x0005, .ghz2 = 0x0005, UPLOAD, }, + [B2056_RX_RXSPARE4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE12] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE13] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE14] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE15] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_LNAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_LNAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_MIXTIA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_VGA_BUF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_Q] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_BUF_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_VGA_HPC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_HPC_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev7_syn[] = { + [B2056_SYN_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_PU] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_GPIO_MASTER1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_GPIO_MASTER2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TOPBIAS_MASTER] = { .ghz5 = 0x0060, .ghz2 = 0x0060, NOUPLOAD, }, + [B2056_SYN_TOPBIAS_RCAL] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_AFEREG] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSE] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSEIDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSERCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LPO] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_VDDCAL_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_VDDCAL_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_VDDCAL_STATUS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCAL_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCAL_CODE_OUT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL1] = { .ghz5 = 0x001f, .ghz2 = 0x001f, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL2] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL3] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_ZCAL_SPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_ZCAL_SPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_MAST1] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_SYN_PLL_MAST2] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_PLL_MAST3] = { .ghz5 = 0x0018, .ghz2 = 0x0018, NOUPLOAD, }, + [B2056_SYN_PLL_BIAS_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL1] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL3] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL5] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL6] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_SYN_PLL_REFDIV] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_PLL_PFD] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_CP1] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_PLL_CP2] = { .ghz5 = 0x0030, .ghz2 = 0x0030, NOUPLOAD, }, + [B2056_SYN_PLL_CP3] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER1] = { .ghz5 = 0x000d, .ghz2 = 0x000d, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER2] = { .ghz5 = 0x000d, .ghz2 = 0x000d, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER3] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER4] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER5] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_PLL_MMD1] = { .ghz5 = 0x001c, .ghz2 = 0x001c, NOUPLOAD, }, + [B2056_SYN_PLL_MMD2] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCO1] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCO2] = { .ghz5 = 0x00f7, .ghz2 = 0x00f7, UPLOAD, }, + [B2056_SYN_PLL_MONITOR1] = { .ghz5 = 0x00b4, .ghz2 = 0x00b4, NOUPLOAD, }, + [B2056_SYN_PLL_MONITOR2] = { .ghz5 = 0x00d2, .ghz2 = 0x00d2, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL4] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL5] = { .ghz5 = 0x0096, .ghz2 = 0x0096, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL6] = { .ghz5 = 0x003e, .ghz2 = 0x003e, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL7] = { .ghz5 = 0x003e, .ghz2 = 0x003e, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL8] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL9] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL11] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL12] = { .ghz5 = 0x0007, .ghz2 = 0x0007, UPLOAD, }, + [B2056_SYN_PLL_VCOCAL13] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_SYN_PLL_VREG] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU2] = { .ghz5 = 0x0040, .ghz2 = 0x0040, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU8] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_LOGEN_BIAS_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RCCR1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF1] = { .ghz5 = 0x0060, .ghz2 = 0x0060, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER1] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER2] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF1] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGENBUF2] = { .ghz5 = 0x008f, .ghz2 = 0x008f, UPLOAD, }, + [B2056_SYN_LOGEN_BUF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF4] = { .ghz5 = 0x00cc, .ghz2 = 0x00cc, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV1] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV2] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV3] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLOUT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_CALEN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PEAKDET1] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, NOUPLOAD, }, + [B2056_SYN_LOGEN_CORE_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_DIFF_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_DIFF_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_CMOS_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_CMOS_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF2_OVRVAL]= { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER3_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF5_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF6_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL_WAITCNT] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_SYN_LOGEN_CORE_CALVALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_CMOS_CALVALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_CMOS_VALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev7_tx[] = { + [B2056_TX_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_IQCAL_GAIN_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_LOFT_FINE_I] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_FINE_Q] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_COARSE_I] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_COARSE_Q] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_TX_COM_MASTER1] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_TX_COM_MASTER2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RXIQCAL_TXMUX] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_TX_SSI_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_IQCAL_VCM_HG] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_IQCAL_IDAC] = { .ghz5 = 0x0037, .ghz2 = 0x0037, NOUPLOAD, }, + [B2056_TX_TSSI_VCM] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_TX_AMP_DET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TX_SSI_MUX] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSIA] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_TSSIG] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_TSSI_MISC1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSI_MISC2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSI_MISC3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PA_SPARE1] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_TX_PA_SPARE2] = { .ghz5 = 0x00ee, .ghz2 = 0x00ee, UPLOAD, }, + [B2056_TX_INTPAA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_BOOST_TUNE] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAA_IAUX_STAT] = { .ghz5 = 0x0050, .ghz2 = 0x0050, UPLOAD, }, + [B2056_TX_INTPAA_IAUX_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_IMAIN_STAT] = { .ghz5 = 0x0050, .ghz2 = 0x0050, UPLOAD, }, + [B2056_TX_INTPAA_IMAIN_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_CASCBIAS] = { .ghz5 = 0x006e, .ghz2 = 0x006e, NOUPLOAD, }, + [B2056_TX_INTPAA_PASLOPE] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_TX_INTPAA_PA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_BOOST_TUNE] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAG_IAUX_STAT] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAG_IAUX_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_IMAIN_STAT] = { .ghz5 = 0x001e, .ghz2 = 0x001e, NOUPLOAD, }, + [B2056_TX_INTPAG_IMAIN_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_CASCBIAS] = { .ghz5 = 0x006e, .ghz2 = 0x006e, NOUPLOAD, }, + [B2056_TX_INTPAG_PASLOPE] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_TX_INTPAG_PA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADA_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PADA_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_TX_PADA_CASCBIAS] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_PADA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADA_BOOST_TUNE] = { .ghz5 = 0x0038, .ghz2 = 0x0038, NOUPLOAD, }, + [B2056_TX_PADA_SLOPE] = { .ghz5 = 0x0070, .ghz2 = 0x0070, UPLOAD, }, + [B2056_TX_PADG_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PADG_IDAC] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_PADG_CASCBIAS] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_PADG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADG_BOOST_TUNE] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_TX_PADG_SLOPE] = { .ghz5 = 0x0070, .ghz2 = 0x0070, UPLOAD, }, + [B2056_TX_PGAA_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PGAA_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_TX_PGAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAA_BOOST_TUNE] = { .ghz5 = 0x0083, .ghz2 = 0x0083, NOUPLOAD, }, + [B2056_TX_PGAA_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, UPLOAD, }, + [B2056_TX_PGAA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAG_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PGAG_IDAC] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_PGAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAG_BOOST_TUNE] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_TX_PGAG_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, UPLOAD, }, + [B2056_TX_PGAG_MISC] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_MIXA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_MIXA_BOOST_TUNE] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_TX_MIXG] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_MIXG_BOOST_TUNE] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_TX_BB_GM_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_GMBB_GM] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, UPLOAD, }, + [B2056_TX_TXLPF_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_BW] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_TXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_0] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_1] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_2] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_3] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_4] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_5] = { .ghz5 = 0x001b, .ghz2 = 0x001b, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_6] = { .ghz5 = 0x001b, .ghz2 = 0x001b, NOUPLOAD, }, + [B2056_TX_TXLPF_OPAMP_IDAC] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_TX_TXLPF_MISC] = { .ghz5 = 0x005b, .ghz2 = 0x005b, NOUPLOAD, }, + [B2056_TX_TXSPARE1] = { .ghz5 = 0x0030, .ghz2 = 0x0030, UPLOAD, }, + [B2056_TX_TXSPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE12] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE13] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE14] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE15] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_INTPA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_PAD_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_PGA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_GM_TXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_TXLPF_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_TXLPF_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC0] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC1] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC2] = { .ghz5 = 0x0071, .ghz2 = 0x0071, UPLOAD, }, + [B2056_TX_GMBB_IDAC3] = { .ghz5 = 0x0071, .ghz2 = 0x0071, UPLOAD, }, + [B2056_TX_GMBB_IDAC4] = { .ghz5 = 0x0072, .ghz2 = 0x0072, UPLOAD, }, + [B2056_TX_GMBB_IDAC5] = { .ghz5 = 0x0073, .ghz2 = 0x0073, UPLOAD, }, + [B2056_TX_GMBB_IDAC6] = { .ghz5 = 0x0074, .ghz2 = 0x0074, UPLOAD, }, + [B2056_TX_GMBB_IDAC7] = { .ghz5 = 0x0075, .ghz2 = 0x0075, UPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev7_rx[] = { + [B2056_RX_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXIQCAL_RXMUX] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_RX_RSSI_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RSSI_SEL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RSSI_GAIN] = { .ghz5 = 0x0090, .ghz2 = 0x0090, NOUPLOAD, }, + [B2056_RX_RSSI_NB_IDAC] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_RX_RSSI_WB2I_IDAC_1] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_RX_RSSI_WB2I_IDAC_2] = { .ghz5 = 0x0005, .ghz2 = 0x0005, NOUPLOAD, }, + [B2056_RX_RSSI_WB2Q_IDAC_1] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_RX_RSSI_WB2Q_IDAC_2] = { .ghz5 = 0x0005, .ghz2 = 0x0005, NOUPLOAD, }, + [B2056_RX_RSSI_POLE] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_RSSI_WB1_IDAC] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_RX_RSSI_MISC] = { .ghz5 = 0x0090, .ghz2 = 0x0090, NOUPLOAD, }, + [B2056_RX_LNAA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_LNAA_TUNE] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_RX_LNAA_GAIN] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_RX_LNA_A_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_RX_BIASPOLE_LNAA1_IDAC] = { .ghz5 = 0x0017, .ghz2 = 0x0017, UPLOAD, }, + [B2056_RX_LNAA2_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_RX_LNA1A_MISC] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_LNAG_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_LNAG_TUNE] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_RX_LNAG_GAIN] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_RX_LNA_G_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_RX_BIASPOLE_LNAG1_IDAC] = { .ghz5 = 0x0017, .ghz2 = 0x0017, UPLOAD, }, + [B2056_RX_LNAG2_IDAC] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_RX_LNA1G_MISC] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_MIXA_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_MIXA_VCM] = { .ghz5 = 0x0055, .ghz2 = 0x0055, UPLOAD, }, + [B2056_RX_MIXA_CTRLPTAT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXA_LOB_BIAS] = { .ghz5 = 0x0088, .ghz2 = 0x0088, UPLOAD, }, + [B2056_RX_MIXA_CORE_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXA_CMFB_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, UPLOAD, }, + [B2056_RX_MIXA_BIAS_AUX] = { .ghz5 = 0x0007, .ghz2 = 0x0007, UPLOAD, }, + [B2056_RX_MIXA_BIAS_MAIN] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_RX_MIXA_BIAS_MISC] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_RX_MIXA_MAST_BIAS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXG_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_MIXG_VCM] = { .ghz5 = 0x0055, .ghz2 = 0x0055, UPLOAD, }, + [B2056_RX_MIXG_CTRLPTAT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXG_LOB_BIAS] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_RX_MIXG_CORE_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXG_CMFB_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, UPLOAD, }, + [B2056_RX_MIXG_BIAS_AUX] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_MIXG_BIAS_MAIN] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_RX_MIXG_BIAS_MISC] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_RX_MIXG_MAST_BIAS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_IOPAMP] = { .ghz5 = 0x0026, .ghz2 = 0x0026, UPLOAD, }, + [B2056_RX_TIA_QOPAMP] = { .ghz5 = 0x0026, .ghz2 = 0x0026, UPLOAD, }, + [B2056_RX_TIA_IMISC] = { .ghz5 = 0x000f, .ghz2 = 0x000f, UPLOAD, }, + [B2056_RX_TIA_QMISC] = { .ghz5 = 0x000f, .ghz2 = 0x000f, UPLOAD, }, + [B2056_RX_TIA_GAIN] = { .ghz5 = 0x0044, .ghz2 = 0x0044, NOUPLOAD, }, + [B2056_RX_TIA_SPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_SPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_BB_LPF_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_AACI_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_RXLPF_IDAC] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_RXLPF_OPAMPBIAS_LOWQ] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_RXLPF_OPAMPBIAS_HIGHQ]= { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_RXLPF_BIAS_DCCANCEL] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_RX_RXLPF_OUTVCM] = { .ghz5 = 0x0004, .ghz2 = 0x0004, UPLOAD, }, + [B2056_RX_RXLPF_INVCM_BODY] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_RXLPF_CC_OP] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_RX_RXLPF_GAIN] = { .ghz5 = 0x0023, .ghz2 = 0x0023, NOUPLOAD, }, + [B2056_RX_RXLPF_Q_BW] = { .ghz5 = 0x0041, .ghz2 = 0x0041, NOUPLOAD, }, + [B2056_RX_RXLPF_HP_CORNER_BW] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_RX_RXLPF_RCCAL_HPC] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_RCCAL_LPC] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_UNUSED] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_VGA_MASTER] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGA_BIAS] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGA_BIAS_DCCANCEL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, UPLOAD, }, + [B2056_RX_VGA_GAIN] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_RX_VGA_HP_CORNER_BW] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_RX_VGABUF_BIAS] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGABUF_GAIN_BW] = { .ghz5 = 0x0030, .ghz2 = 0x0030, NOUPLOAD, }, + [B2056_RX_TXFBMIX_A] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TXFBMIX_G] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE12] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE13] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE14] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE15] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_LNAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_LNAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_MIXTIA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_VGA_BUF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_Q] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_BUF_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_VGA_HPC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_HPC_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev8_syn[] = { + [B2056_SYN_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_PU] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_GPIO_MASTER1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_GPIO_MASTER2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TOPBIAS_MASTER] = { .ghz5 = 0x0060, .ghz2 = 0x0060, NOUPLOAD, }, + [B2056_SYN_TOPBIAS_RCAL] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_AFEREG] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSE] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSEIDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_TEMPPROCSENSERCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LPO] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_VDDCAL_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_VDDCAL_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_VDDCAL_STATUS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCAL_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCAL_CODE_OUT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL1] = { .ghz5 = 0x001f, .ghz2 = 0x001f, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL2] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL3] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_RCCAL_CTRL11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_ZCAL_SPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_ZCAL_SPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_MAST1] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_SYN_PLL_MAST2] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_PLL_MAST3] = { .ghz5 = 0x0018, .ghz2 = 0x0018, NOUPLOAD, }, + [B2056_SYN_PLL_BIAS_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL1] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL3] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL5] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_SYN_PLL_XTAL6] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_SYN_PLL_REFDIV] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_PLL_PFD] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_CP1] = { .ghz5 = 0x000f, .ghz2 = 0x000f, NOUPLOAD, }, + [B2056_SYN_PLL_CP2] = { .ghz5 = 0x0030, .ghz2 = 0x0030, NOUPLOAD, }, + [B2056_SYN_PLL_CP3] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER1] = { .ghz5 = 0x000d, .ghz2 = 0x000d, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER2] = { .ghz5 = 0x000d, .ghz2 = 0x000d, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER3] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER4] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_PLL_LOOPFILTER5] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_PLL_MMD1] = { .ghz5 = 0x001c, .ghz2 = 0x001c, NOUPLOAD, }, + [B2056_SYN_PLL_MMD2] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCO1] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCO2] = { .ghz5 = 0x00f7, .ghz2 = 0x00f7, UPLOAD, }, + [B2056_SYN_PLL_MONITOR1] = { .ghz5 = 0x00b4, .ghz2 = 0x00b4, NOUPLOAD, }, + [B2056_SYN_PLL_MONITOR2] = { .ghz5 = 0x00d2, .ghz2 = 0x00d2, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL4] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL5] = { .ghz5 = 0x0096, .ghz2 = 0x0096, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL6] = { .ghz5 = 0x003e, .ghz2 = 0x003e, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL7] = { .ghz5 = 0x003e, .ghz2 = 0x003e, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL8] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL9] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL11] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_SYN_PLL_VCOCAL12] = { .ghz5 = 0x0007, .ghz2 = 0x0007, UPLOAD, }, + [B2056_SYN_PLL_VCOCAL13] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_SYN_PLL_VREG] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_PLL_STATUS3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU2] = { .ghz5 = 0x0040, .ghz2 = 0x0040, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PU8] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_LOGEN_BIAS_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RCCR1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF1] = { .ghz5 = 0x0060, .ghz2 = 0x0060, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER1] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER2] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF1] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGENBUF2] = { .ghz5 = 0x008f, .ghz2 = 0x008f, UPLOAD, }, + [B2056_SYN_LOGEN_BUF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF4] = { .ghz5 = 0x00cc, .ghz2 = 0x00cc, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV1] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV2] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_DIV3] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLOUT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACLCAL3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_CALEN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_PEAKDET1] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, NOUPLOAD, }, + [B2056_SYN_LOGEN_CORE_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_DIFF_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_DIFF_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_CMOS_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_CMOS_ACL_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_VCOBUF2_OVRVAL]= { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_SYN_LOGEN_MIXER3_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF5_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_BUF6_OVRVAL] = { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFRX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CBUFTX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSRX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX1_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX2_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX3_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_CMOSTX4_OVRVAL]= { .ghz5 = 0x0066, .ghz2 = 0x0066, NOUPLOAD, }, + [B2056_SYN_LOGEN_ACL_WAITCNT] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_SYN_LOGEN_CORE_CALVALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_RX_CMOS_CALVALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_SYN_LOGEN_TX_CMOS_VALID] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev8_tx[] = { + [B2056_TX_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_IQCAL_GAIN_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_LOFT_FINE_I] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_FINE_Q] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_COARSE_I] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_LOFT_COARSE_Q] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_TX_COM_MASTER1] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_TX_COM_MASTER2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_RXIQCAL_TXMUX] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_TX_SSI_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_IQCAL_VCM_HG] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_IQCAL_IDAC] = { .ghz5 = 0x0037, .ghz2 = 0x0037, NOUPLOAD, }, + [B2056_TX_TSSI_VCM] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_TX_AMP_DET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TX_SSI_MUX] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSIA] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_TSSIG] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_TSSI_MISC1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSI_MISC2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TSSI_MISC3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PA_SPARE1] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_TX_PA_SPARE2] = { .ghz5 = 0x00ee, .ghz2 = 0x00ee, UPLOAD, }, + [B2056_TX_INTPAA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_BOOST_TUNE] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAA_IAUX_STAT] = { .ghz5 = 0x0050, .ghz2 = 0x0050, UPLOAD, }, + [B2056_TX_INTPAA_IAUX_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_IMAIN_STAT] = { .ghz5 = 0x0050, .ghz2 = 0x0050, UPLOAD, }, + [B2056_TX_INTPAA_IMAIN_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAA_CASCBIAS] = { .ghz5 = 0x006e, .ghz2 = 0x006e, NOUPLOAD, }, + [B2056_TX_INTPAA_PASLOPE] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_TX_INTPAA_PA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_BOOST_TUNE] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAG_IAUX_STAT] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_TX_INTPAG_IAUX_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_IMAIN_STAT] = { .ghz5 = 0x001e, .ghz2 = 0x001e, NOUPLOAD, }, + [B2056_TX_INTPAG_IMAIN_DYN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_INTPAG_CASCBIAS] = { .ghz5 = 0x006e, .ghz2 = 0x006e, NOUPLOAD, }, + [B2056_TX_INTPAG_PASLOPE] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_TX_INTPAG_PA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADA_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PADA_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_TX_PADA_CASCBIAS] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_PADA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADA_BOOST_TUNE] = { .ghz5 = 0x0038, .ghz2 = 0x0038, NOUPLOAD, }, + [B2056_TX_PADA_SLOPE] = { .ghz5 = 0x0070, .ghz2 = 0x0070, UPLOAD, }, + [B2056_TX_PADG_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PADG_IDAC] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_PADG_CASCBIAS] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_TX_PADG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PADG_BOOST_TUNE] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_TX_PADG_SLOPE] = { .ghz5 = 0x0070, .ghz2 = 0x0070, UPLOAD, }, + [B2056_TX_PGAA_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PGAA_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_TX_PGAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAA_BOOST_TUNE] = { .ghz5 = 0x0083, .ghz2 = 0x0083, NOUPLOAD, }, + [B2056_TX_PGAA_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, UPLOAD, }, + [B2056_TX_PGAA_MISC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAG_MASTER] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_PGAG_IDAC] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_TX_PGAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_PGAG_BOOST_TUNE] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_TX_PGAG_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, UPLOAD, }, + [B2056_TX_PGAG_MISC] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_TX_MIXA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_MIXA_BOOST_TUNE] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_TX_MIXG] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_MIXG_BOOST_TUNE] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_TX_BB_GM_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_GMBB_GM] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, UPLOAD, }, + [B2056_TX_TXLPF_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_RCCAL_OFF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_BW] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_TX_TXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_0] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_1] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_2] = { .ghz5 = 0x000e, .ghz2 = 0x000e, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_3] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_4] = { .ghz5 = 0x0013, .ghz2 = 0x0013, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_5] = { .ghz5 = 0x001b, .ghz2 = 0x001b, NOUPLOAD, }, + [B2056_TX_TXLPF_IDAC_6] = { .ghz5 = 0x001b, .ghz2 = 0x001b, NOUPLOAD, }, + [B2056_TX_TXLPF_OPAMP_IDAC] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_TX_TXLPF_MISC] = { .ghz5 = 0x005b, .ghz2 = 0x005b, NOUPLOAD, }, + [B2056_TX_TXSPARE1] = { .ghz5 = 0x0030, .ghz2 = 0x0030, UPLOAD, }, + [B2056_TX_TXSPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE12] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE13] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE14] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE15] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_TXSPARE16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_INTPA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_PAD_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_PGA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_GM_TXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_TXLPF_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_STATUS_TXLPF_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC0] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC1] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC2] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC3] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC4] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC5] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC6] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, + [B2056_TX_GMBB_IDAC7] = { .ghz5 = 0x0070, .ghz2 = 0x0070, NOUPLOAD, }, +}; + +static const struct b2056_inittab_entry b2056_inittab_rev8_rx[] = { + [B2056_RX_RESERVED_ADDR2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_CTRL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_OVR] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RESET] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RCAL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_RXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_TXLPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_COM_RC_RXHPF] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR17] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR18] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR19] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR20] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR21] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR22] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR23] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR24] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR25] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR26] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR27] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR28] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR29] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR30] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RESERVED_ADDR31] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXIQCAL_RXMUX] = { .ghz5 = 0x0003, .ghz2 = 0x0003, NOUPLOAD, }, + [B2056_RX_RSSI_PU] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RSSI_SEL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RSSI_GAIN] = { .ghz5 = 0x0090, .ghz2 = 0x0090, NOUPLOAD, }, + [B2056_RX_RSSI_NB_IDAC] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_RX_RSSI_WB2I_IDAC_1] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_RX_RSSI_WB2I_IDAC_2] = { .ghz5 = 0x0005, .ghz2 = 0x0005, NOUPLOAD, }, + [B2056_RX_RSSI_WB2Q_IDAC_1] = { .ghz5 = 0x0015, .ghz2 = 0x0015, NOUPLOAD, }, + [B2056_RX_RSSI_WB2Q_IDAC_2] = { .ghz5 = 0x0005, .ghz2 = 0x0005, NOUPLOAD, }, + [B2056_RX_RSSI_POLE] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_RSSI_WB1_IDAC] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_RX_RSSI_MISC] = { .ghz5 = 0x0090, .ghz2 = 0x0090, NOUPLOAD, }, + [B2056_RX_LNAA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_LNAA_TUNE] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_RX_LNAA_GAIN] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_RX_LNA_A_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_RX_BIASPOLE_LNAA1_IDAC] = { .ghz5 = 0x0017, .ghz2 = 0x0017, UPLOAD, }, + [B2056_RX_LNAA2_IDAC] = { .ghz5 = 0x00ff, .ghz2 = 0x00ff, UPLOAD, }, + [B2056_RX_LNA1A_MISC] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_LNAG_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_LNAG_TUNE] = { .ghz5 = 0x0088, .ghz2 = 0x0088, NOUPLOAD, }, + [B2056_RX_LNAG_GAIN] = { .ghz5 = 0x0032, .ghz2 = 0x0032, NOUPLOAD, }, + [B2056_RX_LNA_G_SLOPE] = { .ghz5 = 0x0077, .ghz2 = 0x0077, NOUPLOAD, }, + [B2056_RX_BIASPOLE_LNAG1_IDAC] = { .ghz5 = 0x0017, .ghz2 = 0x0017, UPLOAD, }, + [B2056_RX_LNAG2_IDAC] = { .ghz5 = 0x00f0, .ghz2 = 0x00f0, UPLOAD, }, + [B2056_RX_LNA1G_MISC] = { .ghz5 = 0x0020, .ghz2 = 0x0020, NOUPLOAD, }, + [B2056_RX_MIXA_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_MIXA_VCM] = { .ghz5 = 0x0055, .ghz2 = 0x0055, UPLOAD, }, + [B2056_RX_MIXA_CTRLPTAT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXA_LOB_BIAS] = { .ghz5 = 0x0088, .ghz2 = 0x0088, UPLOAD, }, + [B2056_RX_MIXA_CORE_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXA_CMFB_IDAC] = { .ghz5 = 0x0044, .ghz2 = 0x0044, NOUPLOAD, }, + [B2056_RX_MIXA_BIAS_AUX] = { .ghz5 = 0x0007, .ghz2 = 0x0007, UPLOAD, }, + [B2056_RX_MIXA_BIAS_MAIN] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_RX_MIXA_BIAS_MISC] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_RX_MIXA_MAST_BIAS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXG_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_MIXG_VCM] = { .ghz5 = 0x0055, .ghz2 = 0x0055, UPLOAD, }, + [B2056_RX_MIXG_CTRLPTAT] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXG_LOB_BIAS] = { .ghz5 = 0x0011, .ghz2 = 0x0011, NOUPLOAD, }, + [B2056_RX_MIXG_CORE_IDAC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_MIXG_CMFB_IDAC] = { .ghz5 = 0x0044, .ghz2 = 0x0044, NOUPLOAD, }, + [B2056_RX_MIXG_BIAS_AUX] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_MIXG_BIAS_MAIN] = { .ghz5 = 0x0006, .ghz2 = 0x0006, NOUPLOAD, }, + [B2056_RX_MIXG_BIAS_MISC] = { .ghz5 = 0x0004, .ghz2 = 0x0004, NOUPLOAD, }, + [B2056_RX_MIXG_MAST_BIAS] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_MASTER] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_IOPAMP] = { .ghz5 = 0x0026, .ghz2 = 0x0026, UPLOAD, }, + [B2056_RX_TIA_QOPAMP] = { .ghz5 = 0x0026, .ghz2 = 0x0026, UPLOAD, }, + [B2056_RX_TIA_IMISC] = { .ghz5 = 0x000f, .ghz2 = 0x000f, UPLOAD, }, + [B2056_RX_TIA_QMISC] = { .ghz5 = 0x000f, .ghz2 = 0x000f, UPLOAD, }, + [B2056_RX_TIA_GAIN] = { .ghz5 = 0x0044, .ghz2 = 0x0044, NOUPLOAD, }, + [B2056_RX_TIA_SPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TIA_SPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_BB_LPF_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_AACI_MASTER] = { .ghz5 = 0x0008, .ghz2 = 0x0008, NOUPLOAD, }, + [B2056_RX_RXLPF_IDAC] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_RXLPF_OPAMPBIAS_LOWQ] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_RXLPF_OPAMPBIAS_HIGHQ]= { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_RXLPF_BIAS_DCCANCEL] = { .ghz5 = 0x0002, .ghz2 = 0x0002, NOUPLOAD, }, + [B2056_RX_RXLPF_OUTVCM] = { .ghz5 = 0x0004, .ghz2 = 0x0004, UPLOAD, }, + [B2056_RX_RXLPF_INVCM_BODY] = { .ghz5 = 0x0007, .ghz2 = 0x0007, NOUPLOAD, }, + [B2056_RX_RXLPF_CC_OP] = { .ghz5 = 0x0055, .ghz2 = 0x0055, NOUPLOAD, }, + [B2056_RX_RXLPF_GAIN] = { .ghz5 = 0x0023, .ghz2 = 0x0023, NOUPLOAD, }, + [B2056_RX_RXLPF_Q_BW] = { .ghz5 = 0x0041, .ghz2 = 0x0041, NOUPLOAD, }, + [B2056_RX_RXLPF_HP_CORNER_BW] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_RX_RXLPF_RCCAL_HPC] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXHPF_OFF7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_RCCAL_LPC] = { .ghz5 = 0x000c, .ghz2 = 0x000c, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_0] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_3] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXLPF_OFF_4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_UNUSED] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_VGA_MASTER] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGA_BIAS] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGA_BIAS_DCCANCEL] = { .ghz5 = 0x0000, .ghz2 = 0x0000, UPLOAD, }, + [B2056_RX_VGA_GAIN] = { .ghz5 = 0x000a, .ghz2 = 0x000a, NOUPLOAD, }, + [B2056_RX_VGA_HP_CORNER_BW] = { .ghz5 = 0x0001, .ghz2 = 0x0001, NOUPLOAD, }, + [B2056_RX_VGABUF_BIAS] = { .ghz5 = 0x0022, .ghz2 = 0x0022, NOUPLOAD, }, + [B2056_RX_VGABUF_GAIN_BW] = { .ghz5 = 0x0030, .ghz2 = 0x0030, NOUPLOAD, }, + [B2056_RX_TXFBMIX_A] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_TXFBMIX_G] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE1] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE2] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE3] = { .ghz5 = 0x0005, .ghz2 = 0x0005, UPLOAD, }, + [B2056_RX_RXSPARE4] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE5] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE6] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE7] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE8] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE9] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE10] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE11] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE12] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE13] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE14] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE15] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_RXSPARE16] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_LNAA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_LNAG_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_MIXTIA_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_VGA_BUF_GAIN] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_Q] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_BUF_BW] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_VGA_HPC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_RXLPF_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, + [B2056_RX_STATUS_HPC_RC] = { .ghz5 = 0x0000, .ghz2 = 0x0000, NOUPLOAD, }, +}; + +#define INITTABSPTS(prefix) \ + .syn = prefix##_syn, \ + .syn_length = ARRAY_SIZE(prefix##_syn), \ + .tx = prefix##_tx, \ + .tx_length = ARRAY_SIZE(prefix##_tx), \ + .rx = prefix##_rx, \ + .rx_length = ARRAY_SIZE(prefix##_rx) + +static const struct b2056_inittabs_pts b2056_inittabs[] = { + [3] = { INITTABSPTS(b2056_inittab_rev3) }, + [4] = { INITTABSPTS(b2056_inittab_rev4) }, + [5] = { INITTABSPTS(b2056_inittab_rev5) }, + [6] = { INITTABSPTS(b2056_inittab_rev6) }, + [7] = { INITTABSPTS(b2056_inittab_rev7) }, + [8] = { INITTABSPTS(b2056_inittab_rev8) }, + [9] = { INITTABSPTS(b2056_inittab_rev7) }, +}; + +#define RADIOREGS3(r00, r01, r02, r03, r04, r05, r06, r07, r08, r09, \ + r10, r11, r12, r13, r14, r15, r16, r17, r18, r19, \ + r20, r21, r22, r23, r24, r25, r26, r27, r28, r29, \ + r30, r31, r32, r33, r34, r35, r36) \ + .radio_syn_pll_vcocal1 = r00, \ + .radio_syn_pll_vcocal2 = r01, \ + .radio_syn_pll_refdiv = r02, \ + .radio_syn_pll_mmd2 = r03, \ + .radio_syn_pll_mmd1 = r04, \ + .radio_syn_pll_loopfilter1 = r05, \ + .radio_syn_pll_loopfilter2 = r06, \ + .radio_syn_pll_loopfilter3 = r07, \ + .radio_syn_pll_loopfilter4 = r08, \ + .radio_syn_pll_loopfilter5 = r09, \ + .radio_syn_reserved_addr27 = r10, \ + .radio_syn_reserved_addr28 = r11, \ + .radio_syn_reserved_addr29 = r12, \ + .radio_syn_logen_vcobuf1 = r13, \ + .radio_syn_logen_mixer2 = r14, \ + .radio_syn_logen_buf3 = r15, \ + .radio_syn_logen_buf4 = r16, \ + .radio_rx0_lnaa_tune = r17, \ + .radio_rx0_lnag_tune = r18, \ + .radio_tx0_intpaa_boost_tune = r19, \ + .radio_tx0_intpag_boost_tune = r20, \ + .radio_tx0_pada_boost_tune = r21, \ + .radio_tx0_padg_boost_tune = r22, \ + .radio_tx0_pgaa_boost_tune = r23, \ + .radio_tx0_pgag_boost_tune = r24, \ + .radio_tx0_mixa_boost_tune = r25, \ + .radio_tx0_mixg_boost_tune = r26, \ + .radio_rx1_lnaa_tune = r27, \ + .radio_rx1_lnag_tune = r28, \ + .radio_tx1_intpaa_boost_tune = r29, \ + .radio_tx1_intpag_boost_tune = r30, \ + .radio_tx1_pada_boost_tune = r31, \ + .radio_tx1_padg_boost_tune = r32, \ + .radio_tx1_pgaa_boost_tune = r33, \ + .radio_tx1_pgag_boost_tune = r34, \ + .radio_tx1_mixa_boost_tune = r35, \ + .radio_tx1_mixg_boost_tune = r36 + +#define PHYREGS(r0, r1, r2, r3, r4, r5) \ + .phy_regs.phy_bw1a = r0, \ + .phy_regs.phy_bw2 = r1, \ + .phy_regs.phy_bw3 = r2, \ + .phy_regs.phy_bw4 = r3, \ + .phy_regs.phy_bw5 = r4, \ + .phy_regs.phy_bw6 = r5 + +/* http://bcm-v4.sipsolutions.net/802.11/Radio/2056/ChannelTable */ +static const struct b43_nphy_channeltab_entry_rev3 b43_nphy_channeltab_rev3[] = { + { .freq = 4920, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xec, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07b4, 0x07b0, 0x07ac, 0x0214, 0x0215, 0x0216), + }, + { .freq = 4930, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xed, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07b8, 0x07b4, 0x07b0, 0x0213, 0x0214, 0x0215), + }, + { .freq = 4940, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xee, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07bc, 0x07b8, 0x07b4, 0x0212, 0x0213, 0x0214), + }, + { .freq = 4950, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xef, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07c0, 0x07bc, 0x07b8, 0x0211, 0x0212, 0x0213), + }, + { .freq = 4960, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf0, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07c4, 0x07c0, 0x07bc, 0x020f, 0x0211, 0x0212), + }, + { .freq = 4970, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf1, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07c8, 0x07c4, 0x07c0, 0x020e, 0x020f, 0x0211), + }, + { .freq = 4980, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf2, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07cc, 0x07c8, 0x07c4, 0x020d, 0x020e, 0x020f), + }, + { .freq = 4990, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf3, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07d0, 0x07cc, 0x07c8, 0x020c, 0x020d, 0x020e), + }, + { .freq = 5000, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf4, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07d4, 0x07d0, 0x07cc, 0x020b, 0x020c, 0x020d), + }, + { .freq = 5010, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf5, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07d8, 0x07d4, 0x07d0, 0x020a, 0x020b, 0x020c), + }, + { .freq = 5020, + RADIOREGS3(0xf7, 0x01, 0x01, 0x01, 0xf6, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07dc, 0x07d8, 0x07d4, 0x0209, 0x020a, 0x020b), + }, + { .freq = 5030, + RADIOREGS3(0xf7, 0x01, 0x01, 0x01, 0xf7, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07e0, 0x07dc, 0x07d8, 0x0208, 0x0209, 0x020a), + }, + { .freq = 5040, + RADIOREGS3(0xef, 0x01, 0x01, 0x01, 0xf8, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07e4, 0x07e0, 0x07dc, 0x0207, 0x0208, 0x0209), + }, + { .freq = 5050, + RADIOREGS3(0xef, 0x01, 0x01, 0x01, 0xf9, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07e8, 0x07e4, 0x07e0, 0x0206, 0x0207, 0x0208), + }, + { .freq = 5060, + RADIOREGS3(0xe6, 0x01, 0x01, 0x01, 0xfa, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07ec, 0x07e8, 0x07e4, 0x0205, 0x0206, 0x0207), + }, + { .freq = 5070, + RADIOREGS3(0xe6, 0x01, 0x01, 0x01, 0xfb, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07f0, 0x07ec, 0x07e8, 0x0204, 0x0205, 0x0206), + }, + { .freq = 5080, + RADIOREGS3(0xde, 0x01, 0x01, 0x01, 0xfc, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07f4, 0x07f0, 0x07ec, 0x0203, 0x0204, 0x0205), + }, + { .freq = 5090, + RADIOREGS3(0xde, 0x01, 0x01, 0x01, 0xfd, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07f8, 0x07f4, 0x07f0, 0x0202, 0x0203, 0x0204), + }, + { .freq = 5100, + RADIOREGS3(0xd6, 0x01, 0x01, 0x01, 0xfe, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00), + PHYREGS(0x07fc, 0x07f8, 0x07f4, 0x0201, 0x0202, 0x0203), + }, + { .freq = 5110, + RADIOREGS3(0xd6, 0x01, 0x01, 0x01, 0xff, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xfc, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xfc, 0x00), + PHYREGS(0x0800, 0x07fc, 0x07f8, 0x0200, 0x0201, 0x0202), + }, + { .freq = 5120, + RADIOREGS3(0xce, 0x01, 0x01, 0x02, 0x00, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xfc, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xfc, 0x00), + PHYREGS(0x0804, 0x0800, 0x07fc, 0x01ff, 0x0200, 0x0201), + }, + { .freq = 5130, + RADIOREGS3(0xce, 0x01, 0x01, 0x02, 0x01, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xfc, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xfc, 0x00), + PHYREGS(0x0808, 0x0804, 0x0800, 0x01fe, 0x01ff, 0x0200), + }, + { .freq = 5140, + RADIOREGS3(0xc6, 0x01, 0x01, 0x02, 0x02, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xfc, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xfc, 0x00), + PHYREGS(0x080c, 0x0808, 0x0804, 0x01fd, 0x01fe, 0x01ff), + }, + { .freq = 5160, + RADIOREGS3(0xbe, 0x01, 0x01, 0x02, 0x04, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xfc, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xfc, 0x00), + PHYREGS(0x0814, 0x0810, 0x080c, 0x01fb, 0x01fc, 0x01fd), + }, + { .freq = 5170, + RADIOREGS3(0xbe, 0x01, 0x01, 0x02, 0x05, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xfc, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xfc, 0x00), + PHYREGS(0x0818, 0x0814, 0x0810, 0x01fa, 0x01fb, 0x01fc), + }, + { .freq = 5180, + RADIOREGS3(0xb6, 0x01, 0x01, 0x02, 0x06, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xef, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xfc, 0x00, 0xef, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xfc, 0x00), + PHYREGS(0x081c, 0x0818, 0x0814, 0x01f9, 0x01fa, 0x01fb), + }, + { .freq = 5190, + RADIOREGS3(0xb6, 0x01, 0x01, 0x02, 0x07, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xef, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xfc, 0x00, 0xef, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xfc, 0x00), + PHYREGS(0x0820, 0x081c, 0x0818, 0x01f8, 0x01f9, 0x01fa), + }, + { .freq = 5200, + RADIOREGS3(0xaf, 0x01, 0x01, 0x02, 0x08, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xef, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0xef, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00), + PHYREGS(0x0824, 0x0820, 0x081c, 0x01f7, 0x01f8, 0x01f9), + }, + { .freq = 5210, + RADIOREGS3(0xaf, 0x01, 0x01, 0x02, 0x09, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xdf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0xdf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00), + PHYREGS(0x0828, 0x0824, 0x0820, 0x01f6, 0x01f7, 0x01f8), + }, + { .freq = 5220, + RADIOREGS3(0xa7, 0x01, 0x01, 0x02, 0x0a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xdf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0xdf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00), + PHYREGS(0x082c, 0x0828, 0x0824, 0x01f5, 0x01f6, 0x01f7), + }, + { .freq = 5230, + RADIOREGS3(0xa7, 0x01, 0x01, 0x02, 0x0b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xdf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0xdf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00), + PHYREGS(0x0830, 0x082c, 0x0828, 0x01f4, 0x01f5, 0x01f6), + }, + { .freq = 5240, + RADIOREGS3(0xa0, 0x01, 0x01, 0x02, 0x0c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xcf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0xcf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00), + PHYREGS(0x0834, 0x0830, 0x082c, 0x01f3, 0x01f4, 0x01f5), + }, + { .freq = 5250, + RADIOREGS3(0xa0, 0x01, 0x01, 0x02, 0x0d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xcf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0xcf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00), + PHYREGS(0x0838, 0x0834, 0x0830, 0x01f2, 0x01f3, 0x01f4), + }, + { .freq = 5260, + RADIOREGS3(0x98, 0x01, 0x01, 0x02, 0x0e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xcf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0xcf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00), + PHYREGS(0x083c, 0x0838, 0x0834, 0x01f1, 0x01f2, 0x01f3), + }, + { .freq = 5270, + RADIOREGS3(0x98, 0x01, 0x01, 0x02, 0x0f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, + 0xff, 0xcf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0xcf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00), + PHYREGS(0x0840, 0x083c, 0x0838, 0x01f0, 0x01f1, 0x01f2), + }, + { .freq = 5280, + RADIOREGS3(0x91, 0x01, 0x01, 0x02, 0x10, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, + 0xff, 0xbf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0xbf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00), + PHYREGS(0x0844, 0x0840, 0x083c, 0x01f0, 0x01f0, 0x01f1), + }, + { .freq = 5290, + RADIOREGS3(0x91, 0x01, 0x01, 0x02, 0x11, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, + 0xff, 0xbf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0xbf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00), + PHYREGS(0x0848, 0x0844, 0x0840, 0x01ef, 0x01f0, 0x01f0), + }, + { .freq = 5300, + RADIOREGS3(0x8a, 0x01, 0x01, 0x02, 0x12, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xbf, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfc, 0x00, 0xbf, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfc, 0x00), + PHYREGS(0x084c, 0x0848, 0x0844, 0x01ee, 0x01ef, 0x01f0), + }, + { .freq = 5310, + RADIOREGS3(0x8a, 0x01, 0x01, 0x02, 0x13, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xbf, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0xbf, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00), + PHYREGS(0x0850, 0x084c, 0x0848, 0x01ed, 0x01ee, 0x01ef), + }, + { .freq = 5320, + RADIOREGS3(0x83, 0x01, 0x01, 0x02, 0x14, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xbf, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0xbf, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00), + PHYREGS(0x0854, 0x0850, 0x084c, 0x01ec, 0x01ed, 0x01ee), + }, + { .freq = 5330, + RADIOREGS3(0x83, 0x01, 0x01, 0x02, 0x15, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xaf, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0xaf, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00), + PHYREGS(0x0858, 0x0854, 0x0850, 0x01eb, 0x01ec, 0x01ed), + }, + { .freq = 5340, + RADIOREGS3(0x7c, 0x01, 0x01, 0x02, 0x16, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xaf, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0xaf, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00), + PHYREGS(0x085c, 0x0858, 0x0854, 0x01ea, 0x01eb, 0x01ec), + }, + { .freq = 5350, + RADIOREGS3(0x7c, 0x01, 0x01, 0x02, 0x17, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0x9f, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0x9f, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00), + PHYREGS(0x0860, 0x085c, 0x0858, 0x01e9, 0x01ea, 0x01eb), + }, + { .freq = 5360, + RADIOREGS3(0x75, 0x01, 0x01, 0x02, 0x18, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0x9f, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0x9f, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00), + PHYREGS(0x0864, 0x0860, 0x085c, 0x01e8, 0x01e9, 0x01ea), + }, + { .freq = 5370, + RADIOREGS3(0x75, 0x01, 0x01, 0x02, 0x19, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0x9f, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0x9f, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00), + PHYREGS(0x0868, 0x0864, 0x0860, 0x01e7, 0x01e8, 0x01e9), + }, + { .freq = 5380, + RADIOREGS3(0x6e, 0x01, 0x01, 0x02, 0x1a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0x9f, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0x9f, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00), + PHYREGS(0x086c, 0x0868, 0x0864, 0x01e6, 0x01e7, 0x01e8), + }, + { .freq = 5390, + RADIOREGS3(0x6e, 0x01, 0x01, 0x02, 0x1b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0x8f, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0x8f, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00), + PHYREGS(0x0870, 0x086c, 0x0868, 0x01e5, 0x01e6, 0x01e7), + }, + { .freq = 5400, + RADIOREGS3(0x67, 0x01, 0x01, 0x02, 0x1c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xc8, 0x8f, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xfa, 0x00, 0x8f, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xfa, 0x00), + PHYREGS(0x0874, 0x0870, 0x086c, 0x01e5, 0x01e5, 0x01e6), + }, + { .freq = 5410, + RADIOREGS3(0x67, 0x01, 0x01, 0x02, 0x1d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xc8, 0x8f, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xfa, 0x00, 0x8f, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xfa, 0x00), + PHYREGS(0x0878, 0x0874, 0x0870, 0x01e4, 0x01e5, 0x01e5), + }, + { .freq = 5420, + RADIOREGS3(0x61, 0x01, 0x01, 0x02, 0x1e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xc8, 0x8e, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xfa, 0x00, 0x8e, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xfa, 0x00), + PHYREGS(0x087c, 0x0878, 0x0874, 0x01e3, 0x01e4, 0x01e5), + }, + { .freq = 5430, + RADIOREGS3(0x61, 0x01, 0x01, 0x02, 0x1f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xc8, 0x8e, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xfa, 0x00, 0x8e, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xfa, 0x00), + PHYREGS(0x0880, 0x087c, 0x0878, 0x01e2, 0x01e3, 0x01e4), + }, + { .freq = 5440, + RADIOREGS3(0x5a, 0x01, 0x01, 0x02, 0x20, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x7e, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xfa, 0x00, 0x7e, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xfa, 0x00), + PHYREGS(0x0884, 0x0880, 0x087c, 0x01e1, 0x01e2, 0x01e3), + }, + { .freq = 5450, + RADIOREGS3(0x5a, 0x01, 0x01, 0x02, 0x21, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x7d, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xfa, 0x00, 0x7d, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xfa, 0x00), + PHYREGS(0x0888, 0x0884, 0x0880, 0x01e0, 0x01e1, 0x01e2), + }, + { .freq = 5460, + RADIOREGS3(0x53, 0x01, 0x01, 0x02, 0x22, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x6d, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xf8, 0x00, 0x6d, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xf8, 0x00), + PHYREGS(0x088c, 0x0888, 0x0884, 0x01df, 0x01e0, 0x01e1), + }, + { .freq = 5470, + RADIOREGS3(0x53, 0x01, 0x01, 0x02, 0x23, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x6d, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xf8, 0x00, 0x6d, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xf8, 0x00), + PHYREGS(0x0890, 0x088c, 0x0888, 0x01de, 0x01df, 0x01e0), + }, + { .freq = 5480, + RADIOREGS3(0x4d, 0x01, 0x01, 0x02, 0x24, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x5d, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xf8, 0x00, 0x5d, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xf8, 0x00), + PHYREGS(0x0894, 0x0890, 0x088c, 0x01dd, 0x01de, 0x01df), + }, + { .freq = 5490, + RADIOREGS3(0x4d, 0x01, 0x01, 0x02, 0x25, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x5c, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xf8, 0x00, 0x5c, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xf8, 0x00), + PHYREGS(0x0898, 0x0894, 0x0890, 0x01dd, 0x01dd, 0x01de), + }, + { .freq = 5500, + RADIOREGS3(0x47, 0x01, 0x01, 0x02, 0x26, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x5c, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x5c, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00), + PHYREGS(0x089c, 0x0898, 0x0894, 0x01dc, 0x01dd, 0x01dd), + }, + { .freq = 5510, + RADIOREGS3(0x47, 0x01, 0x01, 0x02, 0x27, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x4c, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x4c, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00), + PHYREGS(0x08a0, 0x089c, 0x0898, 0x01db, 0x01dc, 0x01dd), + }, + { .freq = 5520, + RADIOREGS3(0x40, 0x01, 0x01, 0x02, 0x28, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x4c, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x4c, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00), + PHYREGS(0x08a4, 0x08a0, 0x089c, 0x01da, 0x01db, 0x01dc), + }, + { .freq = 5530, + RADIOREGS3(0x40, 0x01, 0x01, 0x02, 0x29, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x3b, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x3b, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00), + PHYREGS(0x08a8, 0x08a4, 0x08a0, 0x01d9, 0x01da, 0x01db), + }, + { .freq = 5540, + RADIOREGS3(0x3a, 0x01, 0x01, 0x02, 0x2a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x3b, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x3b, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00), + PHYREGS(0x08ac, 0x08a8, 0x08a4, 0x01d8, 0x01d9, 0x01da), + }, + { .freq = 5550, + RADIOREGS3(0x3a, 0x01, 0x01, 0x02, 0x2b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x3b, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x3b, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00), + PHYREGS(0x08b0, 0x08ac, 0x08a8, 0x01d7, 0x01d8, 0x01d9), + }, + { .freq = 5560, + RADIOREGS3(0x34, 0x01, 0x01, 0x02, 0x2c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x2b, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x2b, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00), + PHYREGS(0x08b4, 0x08b0, 0x08ac, 0x01d7, 0x01d7, 0x01d8), + }, + { .freq = 5570, + RADIOREGS3(0x34, 0x01, 0x01, 0x02, 0x2d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x2a, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x2a, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00), + PHYREGS(0x08b8, 0x08b4, 0x08b0, 0x01d6, 0x01d7, 0x01d7), + }, + { .freq = 5580, + RADIOREGS3(0x2e, 0x01, 0x01, 0x02, 0x2e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x1a, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x1a, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00), + PHYREGS(0x08bc, 0x08b8, 0x08b4, 0x01d5, 0x01d6, 0x01d7), + }, + { .freq = 5590, + RADIOREGS3(0x2e, 0x01, 0x01, 0x02, 0x2f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x1a, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x1a, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00), + PHYREGS(0x08c0, 0x08bc, 0x08b8, 0x01d4, 0x01d5, 0x01d6), + }, + { .freq = 5600, + RADIOREGS3(0x28, 0x01, 0x01, 0x02, 0x30, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x1a, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x1a, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00), + PHYREGS(0x08c4, 0x08c0, 0x08bc, 0x01d3, 0x01d4, 0x01d5), + }, + { .freq = 5610, + RADIOREGS3(0x28, 0x01, 0x01, 0x02, 0x31, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x19, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x19, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00), + PHYREGS(0x08c8, 0x08c4, 0x08c0, 0x01d2, 0x01d3, 0x01d4), + }, + { .freq = 5620, + RADIOREGS3(0x21, 0x01, 0x01, 0x02, 0x32, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x19, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x19, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00), + PHYREGS(0x08cc, 0x08c8, 0x08c4, 0x01d2, 0x01d2, 0x01d3), + }, + { .freq = 5630, + RADIOREGS3(0x21, 0x01, 0x01, 0x02, 0x33, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x09, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x09, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00), + PHYREGS(0x08d0, 0x08cc, 0x08c8, 0x01d1, 0x01d2, 0x01d2), + }, + { .freq = 5640, + RADIOREGS3(0x1c, 0x01, 0x01, 0x02, 0x34, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x09, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x09, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00), + PHYREGS(0x08d4, 0x08d0, 0x08cc, 0x01d0, 0x01d1, 0x01d2), + }, + { .freq = 5650, + RADIOREGS3(0x1c, 0x01, 0x01, 0x02, 0x35, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x08, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x08, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00), + PHYREGS(0x08d8, 0x08d4, 0x08d0, 0x01cf, 0x01d0, 0x01d1), + }, + { .freq = 5660, + RADIOREGS3(0x16, 0x01, 0x01, 0x02, 0x36, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x08, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf6, 0x00, 0x08, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf6, 0x00), + PHYREGS(0x08dc, 0x08d8, 0x08d4, 0x01ce, 0x01cf, 0x01d0), + }, + { .freq = 5670, + RADIOREGS3(0x16, 0x01, 0x01, 0x02, 0x37, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x08, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf6, 0x00, 0x08, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf6, 0x00), + PHYREGS(0x08e0, 0x08dc, 0x08d8, 0x01ce, 0x01ce, 0x01cf), + }, + { .freq = 5680, + RADIOREGS3(0x10, 0x01, 0x01, 0x02, 0x38, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x08, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf6, 0x00, 0x08, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf6, 0x00), + PHYREGS(0x08e4, 0x08e0, 0x08dc, 0x01cd, 0x01ce, 0x01ce), + }, + { .freq = 5690, + RADIOREGS3(0x10, 0x01, 0x01, 0x02, 0x39, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x07, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf6, 0x00, 0x07, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf6, 0x00), + PHYREGS(0x08e8, 0x08e4, 0x08e0, 0x01cc, 0x01cd, 0x01ce), + }, + { .freq = 5700, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x07, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf6, 0x00, 0x07, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf6, 0x00), + PHYREGS(0x08ec, 0x08e8, 0x08e4, 0x01cb, 0x01cc, 0x01cd), + }, + { .freq = 5710, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x07, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x07, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x08f0, 0x08ec, 0x08e8, 0x01ca, 0x01cb, 0x01cc), + }, + { .freq = 5720, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x07, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x07, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x08f4, 0x08f0, 0x08ec, 0x01c9, 0x01ca, 0x01cb), + }, + { .freq = 5725, + RADIOREGS3(0x03, 0x01, 0x02, 0x04, 0x79, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x06, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x06, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x08f6, 0x08f2, 0x08ee, 0x01c9, 0x01ca, 0x01cb), + }, + { .freq = 5730, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x06, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x06, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x08f8, 0x08f4, 0x08f0, 0x01c9, 0x01c9, 0x01ca), + }, + { .freq = 5735, + RADIOREGS3(0x03, 0x01, 0x02, 0x04, 0x7b, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x06, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x06, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x08fa, 0x08f6, 0x08f2, 0x01c8, 0x01c9, 0x01ca), + }, + { .freq = 5740, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x06, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x06, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x08fc, 0x08f8, 0x08f4, 0x01c8, 0x01c9, 0x01c9), + }, + { .freq = 5745, + RADIOREGS3(0xfe, 0x00, 0x02, 0x04, 0x7d, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x06, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x06, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x08fe, 0x08fa, 0x08f6, 0x01c8, 0x01c8, 0x01c9), + }, + { .freq = 5750, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x06, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x06, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x0900, 0x08fc, 0x08f8, 0x01c7, 0x01c8, 0x01c9), + }, + { .freq = 5755, + RADIOREGS3(0xfe, 0x00, 0x02, 0x04, 0x7f, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x05, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x05, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x0902, 0x08fe, 0x08fa, 0x01c7, 0x01c8, 0x01c8), + }, + { .freq = 5760, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x40, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x05, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x05, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x0904, 0x0900, 0x08fc, 0x01c6, 0x01c7, 0x01c8), + }, + { .freq = 5765, + RADIOREGS3(0xf8, 0x00, 0x02, 0x04, 0x81, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x05, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x05, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x0906, 0x0902, 0x08fe, 0x01c6, 0x01c7, 0x01c8), + }, + { .freq = 5770, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x41, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x05, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x05, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x0908, 0x0904, 0x0900, 0x01c6, 0x01c6, 0x01c7), + }, + { .freq = 5775, + RADIOREGS3(0xf8, 0x00, 0x02, 0x04, 0x83, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x05, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x05, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x090a, 0x0906, 0x0902, 0x01c5, 0x01c6, 0x01c7), + }, + { .freq = 5780, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x42, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x05, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x05, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x090c, 0x0908, 0x0904, 0x01c5, 0x01c6, 0x01c6), + }, + { .freq = 5785, + RADIOREGS3(0xf2, 0x00, 0x02, 0x04, 0x85, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, + 0x40, 0x04, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x04, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x090e, 0x090a, 0x0906, 0x01c4, 0x01c5, 0x01c6), + }, + { .freq = 5790, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x43, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, + 0x40, 0x04, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x04, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x0910, 0x090c, 0x0908, 0x01c4, 0x01c5, 0x01c6), + }, + { .freq = 5795, + RADIOREGS3(0xf2, 0x00, 0x02, 0x04, 0x87, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, + 0x40, 0x04, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x04, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x0912, 0x090e, 0x090a, 0x01c4, 0x01c4, 0x01c5), + }, + { .freq = 5800, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x44, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x04, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x0914, 0x0910, 0x090c, 0x01c3, 0x01c4, 0x01c5), + }, + { .freq = 5805, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x89, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x04, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x0916, 0x0912, 0x090e, 0x01c3, 0x01c4, 0x01c4), + }, + { .freq = 5810, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x45, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x04, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x0918, 0x0914, 0x0910, 0x01c2, 0x01c3, 0x01c4), + }, + { .freq = 5815, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x8b, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x04, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x091a, 0x0916, 0x0912, 0x01c2, 0x01c3, 0x01c4), + }, + { .freq = 5820, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x46, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x03, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x03, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x091c, 0x0918, 0x0914, 0x01c2, 0x01c2, 0x01c3), + }, + { .freq = 5825, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x8d, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x03, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x03, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x091e, 0x091a, 0x0916, 0x01c1, 0x01c2, 0x01c3), + }, + { .freq = 5830, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x47, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x03, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x03, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x0920, 0x091c, 0x0918, 0x01c1, 0x01c2, 0x01c2), + }, + { .freq = 5840, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x48, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x03, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x03, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x0924, 0x0920, 0x091c, 0x01c0, 0x01c1, 0x01c2), + }, + { .freq = 5850, + RADIOREGS3(0xe0, 0x00, 0x01, 0x02, 0x49, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x03, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x03, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00), + PHYREGS(0x0928, 0x0924, 0x0920, 0x01bf, 0x01c0, 0x01c1), + }, + { .freq = 5860, + RADIOREGS3(0xde, 0x00, 0x01, 0x02, 0x4a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x03, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf2, 0x00, 0x03, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf2, 0x00), + PHYREGS(0x092c, 0x0928, 0x0924, 0x01bf, 0x01bf, 0x01c0), + }, + { .freq = 5870, + RADIOREGS3(0xdb, 0x00, 0x01, 0x02, 0x4b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x02, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf2, 0x00, 0x02, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf2, 0x00), + PHYREGS(0x0930, 0x092c, 0x0928, 0x01be, 0x01bf, 0x01bf), + }, + { .freq = 5880, + RADIOREGS3(0xd8, 0x00, 0x01, 0x02, 0x4c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x02, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf2, 0x00, 0x02, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf2, 0x00), + PHYREGS(0x0934, 0x0930, 0x092c, 0x01bd, 0x01be, 0x01bf), + }, + { .freq = 5890, + RADIOREGS3(0xd6, 0x00, 0x01, 0x02, 0x4d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x02, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf2, 0x00, 0x02, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf2, 0x00), + PHYREGS(0x0938, 0x0934, 0x0930, 0x01bc, 0x01bd, 0x01be), + }, + { .freq = 5900, + RADIOREGS3(0xd3, 0x00, 0x01, 0x02, 0x4e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x87, 0x03, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x05, + 0x00, 0xf2, 0x00, 0x02, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x05, 0x00, 0xf2, 0x00), + PHYREGS(0x093c, 0x0938, 0x0934, 0x01bc, 0x01bc, 0x01bd), + }, + { .freq = 5910, + RADIOREGS3(0xd6, 0x00, 0x01, 0x02, 0x4f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x87, 0x03, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x05, + 0x00, 0xf2, 0x00, 0x01, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x05, 0x00, 0xf2, 0x00), + PHYREGS(0x0940, 0x093c, 0x0938, 0x01bb, 0x01bc, 0x01bc), + }, + { .freq = 2412, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x6c, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x04, 0x04, 0x04, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xff, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x00, 0xff, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f), + PHYREGS(0x03c9, 0x03c5, 0x03c1, 0x043a, 0x043f, 0x0443), + }, + { .freq = 2417, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x71, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xff, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x00, 0xff, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f), + PHYREGS(0x03cb, 0x03c7, 0x03c3, 0x0438, 0x043d, 0x0441), + }, + { .freq = 2422, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x76, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xff, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x00, 0xff, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f), + PHYREGS(0x03cd, 0x03c9, 0x03c5, 0x0436, 0x043a, 0x043f), + }, + { .freq = 2427, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x7b, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xfd, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x00, 0xfd, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f), + PHYREGS(0x03cf, 0x03cb, 0x03c7, 0x0434, 0x0438, 0x043d), + }, + { .freq = 2432, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x80, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xfb, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x00, 0xfb, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f), + PHYREGS(0x03d1, 0x03cd, 0x03c9, 0x0431, 0x0436, 0x043a), + }, + { .freq = 2437, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x85, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xfa, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x00, 0xfa, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f), + PHYREGS(0x03d3, 0x03cf, 0x03cb, 0x042f, 0x0434, 0x0438), + }, + { .freq = 2442, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x8a, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xf8, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x00, 0xf8, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f), + PHYREGS(0x03d5, 0x03d1, 0x03cd, 0x042d, 0x0431, 0x0436), + }, + { .freq = 2447, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x8f, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xf7, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x00, 0xf7, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f), + PHYREGS(0x03d7, 0x03d3, 0x03cf, 0x042b, 0x042f, 0x0434), + }, + { .freq = 2452, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x94, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xf6, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x00, 0xf6, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f), + PHYREGS(0x03d9, 0x03d5, 0x03d1, 0x0429, 0x042d, 0x0431), + }, + { .freq = 2457, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x99, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xf5, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0d, 0x00, 0xf5, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0d), + PHYREGS(0x03db, 0x03d7, 0x03d3, 0x0427, 0x042b, 0x042f), + }, + { .freq = 2462, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x9e, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xf4, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0d, 0x00, 0xf4, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0d), + PHYREGS(0x03dd, 0x03d9, 0x03d5, 0x0424, 0x0429, 0x042d), + }, + { .freq = 2467, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0xa3, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xf3, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0d, 0x00, 0xf3, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0d), + PHYREGS(0x03df, 0x03db, 0x03d7, 0x0422, 0x0427, 0x042b), + }, + { .freq = 2472, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0xa8, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xf2, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0d, 0x00, 0xf2, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0d), + PHYREGS(0x03e1, 0x03dd, 0x03d9, 0x0420, 0x0424, 0x0429), + }, + { .freq = 2484, + RADIOREGS3(0xff, 0x01, 0x03, 0x09, 0xb4, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0d, 0x00, 0xf0, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0d), + PHYREGS(0x03e6, 0x03e2, 0x03de, 0x041b, 0x041f, 0x0424), + }, +}; + +static const struct b43_nphy_channeltab_entry_rev3 b43_nphy_channeltab_rev4[] = { + { .freq = 4920, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xec, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0e, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0e, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07b4, 0x07b0, 0x07ac, 0x0214, 0x0215, 0x0216), + }, + { .freq = 4930, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xed, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0e, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0e, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07b8, 0x07b4, 0x07b0, 0x0213, 0x0214, 0x0215), + }, + { .freq = 4940, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xee, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0e, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0e, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07bc, 0x07b8, 0x07b4, 0x0212, 0x0213, 0x0214), + }, + { .freq = 4950, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xef, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0e, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0e, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07c0, 0x07bc, 0x07b8, 0x0211, 0x0212, 0x0213), + }, + { .freq = 4960, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf0, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0e, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0e, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07c4, 0x07c0, 0x07bc, 0x020f, 0x0211, 0x0212), + }, + { .freq = 4970, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf1, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0e, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0e, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07c8, 0x07c4, 0x07c0, 0x020e, 0x020f, 0x0211), + }, + { .freq = 4980, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf2, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0e, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0e, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07cc, 0x07c8, 0x07c4, 0x020d, 0x020e, 0x020f), + }, + { .freq = 4990, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf3, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0e, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0e, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07d0, 0x07cc, 0x07c8, 0x020c, 0x020d, 0x020e), + }, + { .freq = 5000, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf4, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07d4, 0x07d0, 0x07cc, 0x020b, 0x020c, 0x020d), + }, + { .freq = 5010, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf5, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07d8, 0x07d4, 0x07d0, 0x020a, 0x020b, 0x020c), + }, + { .freq = 5020, + RADIOREGS3(0xf7, 0x01, 0x01, 0x01, 0xf6, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07dc, 0x07d8, 0x07d4, 0x0209, 0x020a, 0x020b), + }, + { .freq = 5030, + RADIOREGS3(0xf7, 0x01, 0x01, 0x01, 0xf7, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07e0, 0x07dc, 0x07d8, 0x0208, 0x0209, 0x020a), + }, + { .freq = 5040, + RADIOREGS3(0xef, 0x01, 0x01, 0x01, 0xf8, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07e4, 0x07e0, 0x07dc, 0x0207, 0x0208, 0x0209), + }, + { .freq = 5050, + RADIOREGS3(0xef, 0x01, 0x01, 0x01, 0xf9, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07e8, 0x07e4, 0x07e0, 0x0206, 0x0207, 0x0208), + }, + { .freq = 5060, + RADIOREGS3(0xe6, 0x01, 0x01, 0x01, 0xfa, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07ec, 0x07e8, 0x07e4, 0x0205, 0x0206, 0x0207), + }, + { .freq = 5070, + RADIOREGS3(0xe6, 0x01, 0x01, 0x01, 0xfb, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07f0, 0x07ec, 0x07e8, 0x0204, 0x0205, 0x0206), + }, + { .freq = 5080, + RADIOREGS3(0xde, 0x01, 0x01, 0x01, 0xfc, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07f4, 0x07f0, 0x07ec, 0x0203, 0x0204, 0x0205), + }, + { .freq = 5090, + RADIOREGS3(0xde, 0x01, 0x01, 0x01, 0xfd, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00), + PHYREGS(0x07f8, 0x07f4, 0x07f0, 0x0202, 0x0203, 0x0204), + }, + { .freq = 5100, + RADIOREGS3(0xd6, 0x01, 0x01, 0x01, 0xfe, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0xff, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00), + PHYREGS(0x07fc, 0x07f8, 0x07f4, 0x0201, 0x0202, 0x0203), + }, + { .freq = 5110, + RADIOREGS3(0xd6, 0x01, 0x01, 0x01, 0xff, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0xff, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00), + PHYREGS(0x0800, 0x07fc, 0x07f8, 0x0200, 0x0201, 0x0202), + }, + { .freq = 5120, + RADIOREGS3(0xce, 0x01, 0x01, 0x02, 0x00, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0xff, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00), + PHYREGS(0x0804, 0x0800, 0x07fc, 0x01ff, 0x0200, 0x0201), + }, + { .freq = 5130, + RADIOREGS3(0xce, 0x01, 0x01, 0x02, 0x01, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0xff, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00), + PHYREGS(0x0808, 0x0804, 0x0800, 0x01fe, 0x01ff, 0x0200), + }, + { .freq = 5140, + RADIOREGS3(0xc6, 0x01, 0x01, 0x02, 0x02, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0xff, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00), + PHYREGS(0x080c, 0x0808, 0x0804, 0x01fd, 0x01fe, 0x01ff), + }, + { .freq = 5160, + RADIOREGS3(0xbe, 0x01, 0x01, 0x02, 0x04, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0xff, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00), + PHYREGS(0x0814, 0x0810, 0x080c, 0x01fb, 0x01fc, 0x01fd), + }, + { .freq = 5170, + RADIOREGS3(0xbe, 0x01, 0x01, 0x02, 0x05, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0xff, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00), + PHYREGS(0x0818, 0x0814, 0x0810, 0x01fa, 0x01fb, 0x01fc), + }, + { .freq = 5180, + RADIOREGS3(0xb6, 0x01, 0x01, 0x02, 0x06, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xef, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0xef, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00), + PHYREGS(0x081c, 0x0818, 0x0814, 0x01f9, 0x01fa, 0x01fb), + }, + { .freq = 5190, + RADIOREGS3(0xb6, 0x01, 0x01, 0x02, 0x07, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xef, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0xef, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00), + PHYREGS(0x0820, 0x081c, 0x0818, 0x01f8, 0x01f9, 0x01fa), + }, + { .freq = 5200, + RADIOREGS3(0xaf, 0x01, 0x01, 0x02, 0x08, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xef, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0xef, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00), + PHYREGS(0x0824, 0x0820, 0x081c, 0x01f7, 0x01f8, 0x01f9), + }, + { .freq = 5210, + RADIOREGS3(0xaf, 0x01, 0x01, 0x02, 0x09, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xdf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0xdf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00), + PHYREGS(0x0828, 0x0824, 0x0820, 0x01f6, 0x01f7, 0x01f8), + }, + { .freq = 5220, + RADIOREGS3(0xa7, 0x01, 0x01, 0x02, 0x0a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xdf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0xdf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00), + PHYREGS(0x082c, 0x0828, 0x0824, 0x01f5, 0x01f6, 0x01f7), + }, + { .freq = 5230, + RADIOREGS3(0xa7, 0x01, 0x01, 0x02, 0x0b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xdf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0xdf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00), + PHYREGS(0x0830, 0x082c, 0x0828, 0x01f4, 0x01f5, 0x01f6), + }, + { .freq = 5240, + RADIOREGS3(0xa0, 0x01, 0x01, 0x02, 0x0c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xcf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0xcf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00), + PHYREGS(0x0834, 0x0830, 0x082c, 0x01f3, 0x01f4, 0x01f5), + }, + { .freq = 5250, + RADIOREGS3(0xa0, 0x01, 0x01, 0x02, 0x0d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xcf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0xcf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00), + PHYREGS(0x0838, 0x0834, 0x0830, 0x01f2, 0x01f3, 0x01f4), + }, + { .freq = 5260, + RADIOREGS3(0x98, 0x01, 0x01, 0x02, 0x0e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xcf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0xcf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00), + PHYREGS(0x083c, 0x0838, 0x0834, 0x01f1, 0x01f2, 0x01f3), + }, + { .freq = 5270, + RADIOREGS3(0x98, 0x01, 0x01, 0x02, 0x0f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, + 0xff, 0xcf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0xcf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00), + PHYREGS(0x0840, 0x083c, 0x0838, 0x01f0, 0x01f1, 0x01f2), + }, + { .freq = 5280, + RADIOREGS3(0x91, 0x01, 0x01, 0x02, 0x10, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, + 0xff, 0xbf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0xbf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00), + PHYREGS(0x0844, 0x0840, 0x083c, 0x01f0, 0x01f0, 0x01f1), + }, + { .freq = 5290, + RADIOREGS3(0x91, 0x01, 0x01, 0x02, 0x11, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, + 0xff, 0xbf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0xbf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00), + PHYREGS(0x0848, 0x0844, 0x0840, 0x01ef, 0x01f0, 0x01f0), + }, + { .freq = 5300, + RADIOREGS3(0x8a, 0x01, 0x01, 0x02, 0x12, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xbf, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0xbf, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00), + PHYREGS(0x084c, 0x0848, 0x0844, 0x01ee, 0x01ef, 0x01f0), + }, + { .freq = 5310, + RADIOREGS3(0x8a, 0x01, 0x01, 0x02, 0x13, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xbf, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0xbf, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00), + PHYREGS(0x0850, 0x084c, 0x0848, 0x01ed, 0x01ee, 0x01ef), + }, + { .freq = 5320, + RADIOREGS3(0x83, 0x01, 0x01, 0x02, 0x14, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xbf, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0xbf, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00), + PHYREGS(0x0854, 0x0850, 0x084c, 0x01ec, 0x01ed, 0x01ee), + }, + { .freq = 5330, + RADIOREGS3(0x83, 0x01, 0x01, 0x02, 0x15, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xaf, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0xaf, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00), + PHYREGS(0x0858, 0x0854, 0x0850, 0x01eb, 0x01ec, 0x01ed), + }, + { .freq = 5340, + RADIOREGS3(0x7c, 0x01, 0x01, 0x02, 0x16, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xaf, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0xaf, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00), + PHYREGS(0x085c, 0x0858, 0x0854, 0x01ea, 0x01eb, 0x01ec), + }, + { .freq = 5350, + RADIOREGS3(0x7c, 0x01, 0x01, 0x02, 0x17, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0x9f, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0x9f, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00), + PHYREGS(0x0860, 0x085c, 0x0858, 0x01e9, 0x01ea, 0x01eb), + }, + { .freq = 5360, + RADIOREGS3(0x75, 0x01, 0x01, 0x02, 0x18, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0x9f, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0x9f, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00), + PHYREGS(0x0864, 0x0860, 0x085c, 0x01e8, 0x01e9, 0x01ea), + }, + { .freq = 5370, + RADIOREGS3(0x75, 0x01, 0x01, 0x02, 0x19, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0x9f, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0x9f, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00), + PHYREGS(0x0868, 0x0864, 0x0860, 0x01e7, 0x01e8, 0x01e9), + }, + { .freq = 5380, + RADIOREGS3(0x6e, 0x01, 0x01, 0x02, 0x1a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0x9f, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0x9f, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00), + PHYREGS(0x086c, 0x0868, 0x0864, 0x01e6, 0x01e7, 0x01e8), + }, + { .freq = 5390, + RADIOREGS3(0x6e, 0x01, 0x01, 0x02, 0x1b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0x8f, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0x8f, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00), + PHYREGS(0x0870, 0x086c, 0x0868, 0x01e5, 0x01e6, 0x01e7), + }, + { .freq = 5400, + RADIOREGS3(0x67, 0x01, 0x01, 0x02, 0x1c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xc8, 0x8f, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x8f, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00), + PHYREGS(0x0874, 0x0870, 0x086c, 0x01e5, 0x01e5, 0x01e6), + }, + { .freq = 5410, + RADIOREGS3(0x67, 0x01, 0x01, 0x02, 0x1d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xc8, 0x8f, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x8f, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00), + PHYREGS(0x0878, 0x0874, 0x0870, 0x01e4, 0x01e5, 0x01e5), + }, + { .freq = 5420, + RADIOREGS3(0x61, 0x01, 0x01, 0x02, 0x1e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xc8, 0x8e, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x8e, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00), + PHYREGS(0x087c, 0x0878, 0x0874, 0x01e3, 0x01e4, 0x01e5), + }, + { .freq = 5430, + RADIOREGS3(0x61, 0x01, 0x01, 0x02, 0x1f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xc8, 0x8e, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x8e, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00), + PHYREGS(0x0880, 0x087c, 0x0878, 0x01e2, 0x01e3, 0x01e4), + }, + { .freq = 5440, + RADIOREGS3(0x5a, 0x01, 0x01, 0x02, 0x20, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x7e, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x7e, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00), + PHYREGS(0x0884, 0x0880, 0x087c, 0x01e1, 0x01e2, 0x01e3), + }, + { .freq = 5450, + RADIOREGS3(0x5a, 0x01, 0x01, 0x02, 0x21, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x7d, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x7d, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00), + PHYREGS(0x0888, 0x0884, 0x0880, 0x01e0, 0x01e1, 0x01e2), + }, + { .freq = 5460, + RADIOREGS3(0x53, 0x01, 0x01, 0x02, 0x22, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x6d, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x6d, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00), + PHYREGS(0x088c, 0x0888, 0x0884, 0x01df, 0x01e0, 0x01e1), + }, + { .freq = 5470, + RADIOREGS3(0x53, 0x01, 0x01, 0x02, 0x23, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x6d, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x6d, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00), + PHYREGS(0x0890, 0x088c, 0x0888, 0x01de, 0x01df, 0x01e0), + }, + { .freq = 5480, + RADIOREGS3(0x4d, 0x01, 0x01, 0x02, 0x24, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x5d, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x5d, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00), + PHYREGS(0x0894, 0x0890, 0x088c, 0x01dd, 0x01de, 0x01df), + }, + { .freq = 5490, + RADIOREGS3(0x4d, 0x01, 0x01, 0x02, 0x25, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x5c, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x5c, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00), + PHYREGS(0x0898, 0x0894, 0x0890, 0x01dd, 0x01dd, 0x01de), + }, + { .freq = 5500, + RADIOREGS3(0x47, 0x01, 0x01, 0x02, 0x26, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x5c, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x5c, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00), + PHYREGS(0x089c, 0x0898, 0x0894, 0x01dc, 0x01dd, 0x01dd), + }, + { .freq = 5510, + RADIOREGS3(0x47, 0x01, 0x01, 0x02, 0x27, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x4c, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x4c, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00), + PHYREGS(0x08a0, 0x089c, 0x0898, 0x01db, 0x01dc, 0x01dd), + }, + { .freq = 5520, + RADIOREGS3(0x40, 0x01, 0x01, 0x02, 0x28, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x4c, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x4c, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00), + PHYREGS(0x08a4, 0x08a0, 0x089c, 0x01da, 0x01db, 0x01dc), + }, + { .freq = 5530, + RADIOREGS3(0x40, 0x01, 0x01, 0x02, 0x29, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x3b, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x3b, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00), + PHYREGS(0x08a8, 0x08a4, 0x08a0, 0x01d9, 0x01da, 0x01db), + }, + { .freq = 5540, + RADIOREGS3(0x3a, 0x01, 0x01, 0x02, 0x2a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x3b, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x3b, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00), + PHYREGS(0x08ac, 0x08a8, 0x08a4, 0x01d8, 0x01d9, 0x01da), + }, + { .freq = 5550, + RADIOREGS3(0x3a, 0x01, 0x01, 0x02, 0x2b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x3b, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x3b, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00), + PHYREGS(0x08b0, 0x08ac, 0x08a8, 0x01d7, 0x01d8, 0x01d9), + }, + { .freq = 5560, + RADIOREGS3(0x34, 0x01, 0x01, 0x02, 0x2c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x2b, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x2b, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00), + PHYREGS(0x08b4, 0x08b0, 0x08ac, 0x01d7, 0x01d7, 0x01d8), + }, + { .freq = 5570, + RADIOREGS3(0x34, 0x01, 0x01, 0x02, 0x2d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x2a, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x2a, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00), + PHYREGS(0x08b8, 0x08b4, 0x08b0, 0x01d6, 0x01d7, 0x01d7), + }, + { .freq = 5580, + RADIOREGS3(0x2e, 0x01, 0x01, 0x02, 0x2e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x1a, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x1a, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00), + PHYREGS(0x08bc, 0x08b8, 0x08b4, 0x01d5, 0x01d6, 0x01d7), + }, + { .freq = 5590, + RADIOREGS3(0x2e, 0x01, 0x01, 0x02, 0x2f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x1a, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x1a, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00), + PHYREGS(0x08c0, 0x08bc, 0x08b8, 0x01d4, 0x01d5, 0x01d6), + }, + { .freq = 5600, + RADIOREGS3(0x28, 0x01, 0x01, 0x02, 0x30, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x1a, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x1a, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00), + PHYREGS(0x08c4, 0x08c0, 0x08bc, 0x01d3, 0x01d4, 0x01d5), + }, + { .freq = 5610, + RADIOREGS3(0x28, 0x01, 0x01, 0x02, 0x31, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x19, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x19, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00), + PHYREGS(0x08c8, 0x08c4, 0x08c0, 0x01d2, 0x01d3, 0x01d4), + }, + { .freq = 5620, + RADIOREGS3(0x21, 0x01, 0x01, 0x02, 0x32, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x19, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x19, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00), + PHYREGS(0x08cc, 0x08c8, 0x08c4, 0x01d2, 0x01d2, 0x01d3), + }, + { .freq = 5630, + RADIOREGS3(0x21, 0x01, 0x01, 0x02, 0x33, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x09, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x09, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00), + PHYREGS(0x08d0, 0x08cc, 0x08c8, 0x01d1, 0x01d2, 0x01d2), + }, + { .freq = 5640, + RADIOREGS3(0x1c, 0x01, 0x01, 0x02, 0x34, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x09, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x09, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00), + PHYREGS(0x08d4, 0x08d0, 0x08cc, 0x01d0, 0x01d1, 0x01d2), + }, + { .freq = 5650, + RADIOREGS3(0x1c, 0x01, 0x01, 0x02, 0x35, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x08, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x08, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00), + PHYREGS(0x08d8, 0x08d4, 0x08d0, 0x01cf, 0x01d0, 0x01d1), + }, + { .freq = 5660, + RADIOREGS3(0x16, 0x01, 0x01, 0x02, 0x36, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x08, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x08, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00), + PHYREGS(0x08dc, 0x08d8, 0x08d4, 0x01ce, 0x01cf, 0x01d0), + }, + { .freq = 5670, + RADIOREGS3(0x16, 0x01, 0x01, 0x02, 0x37, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x08, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x08, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00), + PHYREGS(0x08e0, 0x08dc, 0x08d8, 0x01ce, 0x01ce, 0x01cf), + }, + { .freq = 5680, + RADIOREGS3(0x10, 0x01, 0x01, 0x02, 0x38, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x08, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x08, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00), + PHYREGS(0x08e4, 0x08e0, 0x08dc, 0x01cd, 0x01ce, 0x01ce), + }, + { .freq = 5690, + RADIOREGS3(0x10, 0x01, 0x01, 0x02, 0x39, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x07, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x07, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00), + PHYREGS(0x08e8, 0x08e4, 0x08e0, 0x01cc, 0x01cd, 0x01ce), + }, + { .freq = 5700, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x07, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x07, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x08ec, 0x08e8, 0x08e4, 0x01cb, 0x01cc, 0x01cd), + }, + { .freq = 5710, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x07, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x07, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x08f0, 0x08ec, 0x08e8, 0x01ca, 0x01cb, 0x01cc), + }, + { .freq = 5720, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x07, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x07, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x08f4, 0x08f0, 0x08ec, 0x01c9, 0x01ca, 0x01cb), + }, + { .freq = 5725, + RADIOREGS3(0x03, 0x01, 0x02, 0x04, 0x79, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x06, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x06, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x08f6, 0x08f2, 0x08ee, 0x01c9, 0x01ca, 0x01cb), + }, + { .freq = 5730, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x06, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x06, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x08f8, 0x08f4, 0x08f0, 0x01c9, 0x01c9, 0x01ca), + }, + { .freq = 5735, + RADIOREGS3(0x03, 0x01, 0x02, 0x04, 0x7b, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x06, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x06, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x08fa, 0x08f6, 0x08f2, 0x01c8, 0x01c9, 0x01ca), + }, + { .freq = 5740, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x06, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x06, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x08fc, 0x08f8, 0x08f4, 0x01c8, 0x01c9, 0x01c9), + }, + { .freq = 5745, + RADIOREGS3(0xfe, 0x00, 0x02, 0x04, 0x7d, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x06, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x06, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x08fe, 0x08fa, 0x08f6, 0x01c8, 0x01c8, 0x01c9), + }, + { .freq = 5750, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x06, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x06, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x0900, 0x08fc, 0x08f8, 0x01c7, 0x01c8, 0x01c9), + }, + { .freq = 5755, + RADIOREGS3(0xfe, 0x00, 0x02, 0x04, 0x7f, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x05, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x05, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x0902, 0x08fe, 0x08fa, 0x01c7, 0x01c8, 0x01c8), + }, + { .freq = 5760, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x40, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x05, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x05, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x0904, 0x0900, 0x08fc, 0x01c6, 0x01c7, 0x01c8), + }, + { .freq = 5765, + RADIOREGS3(0xf8, 0x00, 0x02, 0x04, 0x81, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x05, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x05, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x0906, 0x0902, 0x08fe, 0x01c6, 0x01c7, 0x01c8), + }, + { .freq = 5770, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x41, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x05, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x05, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x0908, 0x0904, 0x0900, 0x01c6, 0x01c6, 0x01c7), + }, + { .freq = 5775, + RADIOREGS3(0xf8, 0x00, 0x02, 0x04, 0x83, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x05, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x05, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x090a, 0x0906, 0x0902, 0x01c5, 0x01c6, 0x01c7), + }, + { .freq = 5780, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x42, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x05, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x05, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x090c, 0x0908, 0x0904, 0x01c5, 0x01c6, 0x01c6), + }, + { .freq = 5785, + RADIOREGS3(0xf2, 0x00, 0x02, 0x04, 0x85, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, + 0x40, 0x04, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x04, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x090e, 0x090a, 0x0906, 0x01c4, 0x01c5, 0x01c6), + }, + { .freq = 5790, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x43, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, + 0x40, 0x04, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x04, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x0910, 0x090c, 0x0908, 0x01c4, 0x01c5, 0x01c6), + }, + { .freq = 5795, + RADIOREGS3(0xf2, 0x00, 0x02, 0x04, 0x87, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, + 0x40, 0x04, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x04, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00), + PHYREGS(0x0912, 0x090e, 0x090a, 0x01c4, 0x01c4, 0x01c5), + }, + { .freq = 5800, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x44, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x04, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x04, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00), + PHYREGS(0x0914, 0x0910, 0x090c, 0x01c3, 0x01c4, 0x01c5), + }, + { .freq = 5805, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x89, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x04, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x04, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00), + PHYREGS(0x0916, 0x0912, 0x090e, 0x01c3, 0x01c4, 0x01c4), + }, + { .freq = 5810, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x45, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x04, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x04, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00), + PHYREGS(0x0918, 0x0914, 0x0910, 0x01c2, 0x01c3, 0x01c4), + }, + { .freq = 5815, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x8b, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x04, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x04, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00), + PHYREGS(0x091a, 0x0916, 0x0912, 0x01c2, 0x01c3, 0x01c4), + }, + { .freq = 5820, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x46, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x03, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x03, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00), + PHYREGS(0x091c, 0x0918, 0x0914, 0x01c2, 0x01c2, 0x01c3), + }, + { .freq = 5825, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x8d, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x03, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x03, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00), + PHYREGS(0x091e, 0x091a, 0x0916, 0x01c1, 0x01c2, 0x01c3), + }, + { .freq = 5830, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x47, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x03, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x03, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00), + PHYREGS(0x0920, 0x091c, 0x0918, 0x01c1, 0x01c2, 0x01c2), + }, + { .freq = 5840, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x48, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x03, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x03, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00), + PHYREGS(0x0924, 0x0920, 0x091c, 0x01c0, 0x01c1, 0x01c2), + }, + { .freq = 5850, + RADIOREGS3(0xe0, 0x00, 0x01, 0x02, 0x49, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x03, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x03, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00), + PHYREGS(0x0928, 0x0924, 0x0920, 0x01bf, 0x01c0, 0x01c1), + }, + { .freq = 5860, + RADIOREGS3(0xde, 0x00, 0x01, 0x02, 0x4a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x03, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x03, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00), + PHYREGS(0x092c, 0x0928, 0x0924, 0x01bf, 0x01bf, 0x01c0), + }, + { .freq = 5870, + RADIOREGS3(0xdb, 0x00, 0x01, 0x02, 0x4b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x02, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x02, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00), + PHYREGS(0x0930, 0x092c, 0x0928, 0x01be, 0x01bf, 0x01bf), + }, + { .freq = 5880, + RADIOREGS3(0xd8, 0x00, 0x01, 0x02, 0x4c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x02, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x02, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00), + PHYREGS(0x0934, 0x0930, 0x092c, 0x01bd, 0x01be, 0x01bf), + }, + { .freq = 5890, + RADIOREGS3(0xd6, 0x00, 0x01, 0x02, 0x4d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x02, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x02, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00), + PHYREGS(0x0938, 0x0934, 0x0930, 0x01bc, 0x01bd, 0x01be), + }, + { .freq = 5900, + RADIOREGS3(0xd3, 0x00, 0x01, 0x02, 0x4e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x87, 0x03, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf0, 0x00, 0x02, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf0, 0x00), + PHYREGS(0x093c, 0x0938, 0x0934, 0x01bc, 0x01bc, 0x01bd), + }, + { .freq = 5910, + RADIOREGS3(0xd6, 0x00, 0x01, 0x02, 0x4f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x87, 0x03, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf0, 0x00), + PHYREGS(0x0940, 0x093c, 0x0938, 0x01bb, 0x01bc, 0x01bc), + }, + { .freq = 2412, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x6c, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x04, 0x04, 0x04, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xff, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x00, 0xff, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e), + PHYREGS(0x03c9, 0x03c5, 0x03c1, 0x043a, 0x043f, 0x0443), + }, + { .freq = 2417, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x71, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xff, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x00, 0xff, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e), + PHYREGS(0x03cb, 0x03c7, 0x03c3, 0x0438, 0x043d, 0x0441), + }, + { .freq = 2422, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x76, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xff, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x00, 0xff, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e), + PHYREGS(0x03cd, 0x03c9, 0x03c5, 0x0436, 0x043a, 0x043f), + }, + { .freq = 2427, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x7b, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xfd, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x00, 0xfd, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e), + PHYREGS(0x03cf, 0x03cb, 0x03c7, 0x0434, 0x0438, 0x043d), + }, + { .freq = 2432, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x80, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xfb, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x00, 0xfb, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e), + PHYREGS(0x03d1, 0x03cd, 0x03c9, 0x0431, 0x0436, 0x043a), + }, + { .freq = 2437, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x85, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x00, 0xfa, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e), + PHYREGS(0x03d3, 0x03cf, 0x03cb, 0x042f, 0x0434, 0x0438), + }, + { .freq = 2442, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x8a, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xf8, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x00, 0xf8, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e), + PHYREGS(0x03d5, 0x03d1, 0x03cd, 0x042d, 0x0431, 0x0436), + }, + { .freq = 2447, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x8f, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xf7, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x00, 0xf7, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e), + PHYREGS(0x03d7, 0x03d3, 0x03cf, 0x042b, 0x042f, 0x0434), + }, + { .freq = 2452, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x94, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xf6, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x00, 0xf6, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e), + PHYREGS(0x03d9, 0x03d5, 0x03d1, 0x0429, 0x042d, 0x0431), + }, + { .freq = 2457, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x99, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xf5, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x00, 0xf5, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e), + PHYREGS(0x03db, 0x03d7, 0x03d3, 0x0427, 0x042b, 0x042f), + }, + { .freq = 2462, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x9e, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xf4, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x00, 0xf4, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e), + PHYREGS(0x03dd, 0x03d9, 0x03d5, 0x0424, 0x0429, 0x042d), + }, + { .freq = 2467, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0xa3, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xf3, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x00, 0xf3, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e), + PHYREGS(0x03df, 0x03db, 0x03d7, 0x0422, 0x0427, 0x042b), + }, + { .freq = 2472, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0xa8, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xf2, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x00, 0xf2, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e), + PHYREGS(0x03e1, 0x03dd, 0x03d9, 0x0420, 0x0424, 0x0429), + }, + { .freq = 2484, + RADIOREGS3(0xff, 0x01, 0x03, 0x09, 0xb4, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x00, 0xf0, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e), + PHYREGS(0x03e6, 0x03e2, 0x03de, 0x041b, 0x041f, 0x0424), + }, +}; + +static const struct b43_nphy_channeltab_entry_rev3 b43_nphy_channeltab_rev5[] = { + { .freq = 4920, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xec, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0b, 0x00, 0x70, 0x00, 0x0f, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0b, 0x00, 0x70, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07b4, 0x07b0, 0x07ac, 0x0214, 0x0215, 0x0216), + }, + { .freq = 4930, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xed, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0b, 0x00, 0x70, 0x00, 0x0e, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0b, 0x00, 0x70, + 0x00, 0x0e, 0x00, 0x6f, 0x00), + PHYREGS(0x07b8, 0x07b4, 0x07b0, 0x0213, 0x0214, 0x0215), + }, + { .freq = 4940, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xee, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0b, 0x00, 0x70, 0x00, 0x0e, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0b, 0x00, 0x70, + 0x00, 0x0e, 0x00, 0x6f, 0x00), + PHYREGS(0x07bc, 0x07b8, 0x07b4, 0x0212, 0x0213, 0x0214), + }, + { .freq = 4950, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xef, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0b, 0x00, 0x70, 0x00, 0x0e, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0b, 0x00, 0x70, + 0x00, 0x0e, 0x00, 0x6f, 0x00), + PHYREGS(0x07c0, 0x07bc, 0x07b8, 0x0211, 0x0212, 0x0213), + }, + { .freq = 4960, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf0, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0e, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0e, 0x00, 0x6f, 0x00), + PHYREGS(0x07c4, 0x07c0, 0x07bc, 0x020f, 0x0211, 0x0212), + }, + { .freq = 4970, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf1, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x07c8, 0x07c4, 0x07c0, 0x020e, 0x020f, 0x0211), + }, + { .freq = 4980, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf2, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x07cc, 0x07c8, 0x07c4, 0x020d, 0x020e, 0x020f), + }, + { .freq = 4990, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf3, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x07d0, 0x07cc, 0x07c8, 0x020c, 0x020d, 0x020e), + }, + { .freq = 5000, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf4, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x07d4, 0x07d0, 0x07cc, 0x020b, 0x020c, 0x020d), + }, + { .freq = 5010, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf5, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x07d8, 0x07d4, 0x07d0, 0x020a, 0x020b, 0x020c), + }, + { .freq = 5020, + RADIOREGS3(0xf7, 0x01, 0x01, 0x01, 0xf6, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x07dc, 0x07d8, 0x07d4, 0x0209, 0x020a, 0x020b), + }, + { .freq = 5030, + RADIOREGS3(0xf7, 0x01, 0x01, 0x01, 0xf7, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0c, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x07e0, 0x07dc, 0x07d8, 0x0208, 0x0209, 0x020a), + }, + { .freq = 5040, + RADIOREGS3(0xef, 0x01, 0x01, 0x01, 0xf8, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0c, + 0x00, 0x9f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x07e4, 0x07e0, 0x07dc, 0x0207, 0x0208, 0x0209), + }, + { .freq = 5050, + RADIOREGS3(0xef, 0x01, 0x01, 0x01, 0xf9, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0c, + 0x00, 0x9f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x07e8, 0x07e4, 0x07e0, 0x0206, 0x0207, 0x0208), + }, + { .freq = 5060, + RADIOREGS3(0xe6, 0x01, 0x01, 0x01, 0xfa, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfd, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0c, + 0x00, 0x9f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x07ec, 0x07e8, 0x07e4, 0x0205, 0x0206, 0x0207), + }, + { .freq = 5070, + RADIOREGS3(0xe6, 0x01, 0x01, 0x01, 0xfb, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfd, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x9f, 0x00, 0xfd, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x07f0, 0x07ec, 0x07e8, 0x0204, 0x0205, 0x0206), + }, + { .freq = 5080, + RADIOREGS3(0xde, 0x01, 0x01, 0x01, 0xfc, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x07f4, 0x07f0, 0x07ec, 0x0203, 0x0204, 0x0205), + }, + { .freq = 5090, + RADIOREGS3(0xde, 0x01, 0x01, 0x01, 0xfd, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x07f8, 0x07f4, 0x07f0, 0x0202, 0x0203, 0x0204), + }, + { .freq = 5100, + RADIOREGS3(0xd6, 0x01, 0x01, 0x01, 0xfe, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x07fc, 0x07f8, 0x07f4, 0x0201, 0x0202, 0x0203), + }, + { .freq = 5110, + RADIOREGS3(0xd6, 0x01, 0x01, 0x01, 0xff, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x0800, 0x07fc, 0x07f8, 0x0200, 0x0201, 0x0202), + }, + { .freq = 5120, + RADIOREGS3(0xce, 0x01, 0x01, 0x02, 0x00, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x0804, 0x0800, 0x07fc, 0x01ff, 0x0200, 0x0201), + }, + { .freq = 5130, + RADIOREGS3(0xce, 0x01, 0x01, 0x02, 0x01, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfb, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0a, + 0x00, 0x9f, 0x00, 0xfb, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x0808, 0x0804, 0x0800, 0x01fe, 0x01ff, 0x0200), + }, + { .freq = 5140, + RADIOREGS3(0xc6, 0x01, 0x01, 0x02, 0x02, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfb, 0x00, 0x07, 0x00, 0x70, 0x00, 0x0a, + 0x00, 0x9f, 0x00, 0xfb, 0x00, 0x07, 0x00, 0x70, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x080c, 0x0808, 0x0804, 0x01fd, 0x01fe, 0x01ff), + }, + { .freq = 5160, + RADIOREGS3(0xbe, 0x01, 0x01, 0x02, 0x04, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfb, 0x00, 0x07, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x9e, 0x00, 0xfb, 0x00, 0x07, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x6e, 0x00), + PHYREGS(0x0814, 0x0810, 0x080c, 0x01fb, 0x01fc, 0x01fd), + }, + { .freq = 5170, + RADIOREGS3(0xbe, 0x01, 0x01, 0x02, 0x05, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfb, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x9e, 0x00, 0xfb, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x6e, 0x00), + PHYREGS(0x0818, 0x0814, 0x0810, 0x01fa, 0x01fb, 0x01fc), + }, + { .freq = 5180, + RADIOREGS3(0xb6, 0x01, 0x01, 0x02, 0x06, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x6e, 0x00), + PHYREGS(0x081c, 0x0818, 0x0814, 0x01f9, 0x01fa, 0x01fb), + }, + { .freq = 5190, + RADIOREGS3(0xb6, 0x01, 0x01, 0x02, 0x07, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x6e, 0x00), + PHYREGS(0x0820, 0x081c, 0x0818, 0x01f8, 0x01f9, 0x01fa), + }, + { .freq = 5200, + RADIOREGS3(0xaf, 0x01, 0x01, 0x02, 0x08, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x6e, 0x00), + PHYREGS(0x0824, 0x0820, 0x081c, 0x01f7, 0x01f8, 0x01f9), + }, + { .freq = 5210, + RADIOREGS3(0xaf, 0x01, 0x01, 0x02, 0x09, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x6e, 0x00), + PHYREGS(0x0828, 0x0824, 0x0820, 0x01f6, 0x01f7, 0x01f8), + }, + { .freq = 5220, + RADIOREGS3(0xa7, 0x01, 0x01, 0x02, 0x0a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x6e, 0x00), + PHYREGS(0x082c, 0x0828, 0x0824, 0x01f5, 0x01f6, 0x01f7), + }, + { .freq = 5230, + RADIOREGS3(0xa7, 0x01, 0x01, 0x02, 0x0b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xea, 0x00, 0x06, 0x00, 0x70, 0x00, 0x08, + 0x00, 0x9e, 0x00, 0xea, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x08, 0x00, 0x6e, 0x00), + PHYREGS(0x0830, 0x082c, 0x0828, 0x01f4, 0x01f5, 0x01f6), + }, + { .freq = 5240, + RADIOREGS3(0xa0, 0x01, 0x01, 0x02, 0x0c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xe9, 0x00, 0x05, 0x00, 0x70, 0x00, 0x08, + 0x00, 0x9d, 0x00, 0xe9, 0x00, 0x05, 0x00, 0x70, + 0x00, 0x08, 0x00, 0x6d, 0x00), + PHYREGS(0x0834, 0x0830, 0x082c, 0x01f3, 0x01f4, 0x01f5), + }, + { .freq = 5250, + RADIOREGS3(0xa0, 0x01, 0x01, 0x02, 0x0d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xe9, 0x00, 0x05, 0x00, 0x70, 0x00, 0x08, + 0x00, 0x9d, 0x00, 0xe9, 0x00, 0x05, 0x00, 0x70, + 0x00, 0x08, 0x00, 0x6d, 0x00), + PHYREGS(0x0838, 0x0834, 0x0830, 0x01f2, 0x01f3, 0x01f4), + }, + { .freq = 5260, + RADIOREGS3(0x98, 0x01, 0x01, 0x02, 0x0e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xd9, 0x00, 0x05, 0x00, 0x70, 0x00, 0x08, + 0x00, 0x9d, 0x00, 0xd9, 0x00, 0x05, 0x00, 0x70, + 0x00, 0x08, 0x00, 0x6d, 0x00), + PHYREGS(0x083c, 0x0838, 0x0834, 0x01f1, 0x01f2, 0x01f3), + }, + { .freq = 5270, + RADIOREGS3(0x98, 0x01, 0x01, 0x02, 0x0f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, + 0xff, 0xd8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x9c, 0x00, 0xd8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x6c, 0x00), + PHYREGS(0x0840, 0x083c, 0x0838, 0x01f0, 0x01f1, 0x01f2), + }, + { .freq = 5280, + RADIOREGS3(0x91, 0x01, 0x01, 0x02, 0x10, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, + 0xff, 0xc8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x9c, 0x00, 0xc8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x6c, 0x00), + PHYREGS(0x0844, 0x0840, 0x083c, 0x01f0, 0x01f0, 0x01f1), + }, + { .freq = 5290, + RADIOREGS3(0x91, 0x01, 0x01, 0x02, 0x11, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, + 0xff, 0xc8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x9c, 0x00, 0xc8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x6c, 0x00), + PHYREGS(0x0848, 0x0844, 0x0840, 0x01ef, 0x01f0, 0x01f0), + }, + { .freq = 5300, + RADIOREGS3(0x8a, 0x01, 0x01, 0x02, 0x12, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xc8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x9c, 0x00, 0xc8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x6c, 0x00), + PHYREGS(0x084c, 0x0848, 0x0844, 0x01ee, 0x01ef, 0x01f0), + }, + { .freq = 5310, + RADIOREGS3(0x8a, 0x01, 0x01, 0x02, 0x13, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xc8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x9c, 0x00, 0xc8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x6c, 0x00), + PHYREGS(0x0850, 0x084c, 0x0848, 0x01ed, 0x01ee, 0x01ef), + }, + { .freq = 5320, + RADIOREGS3(0x83, 0x01, 0x01, 0x02, 0x14, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xb8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x9c, 0x00, 0xb8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x6c, 0x00), + PHYREGS(0x0854, 0x0850, 0x084c, 0x01ec, 0x01ed, 0x01ee), + }, + { .freq = 5330, + RADIOREGS3(0x83, 0x01, 0x01, 0x02, 0x15, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xb7, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x9b, 0x00, 0xb7, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x6b, 0x00), + PHYREGS(0x0858, 0x0854, 0x0850, 0x01eb, 0x01ec, 0x01ed), + }, + { .freq = 5340, + RADIOREGS3(0x7c, 0x01, 0x01, 0x02, 0x16, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xb7, 0x00, 0x03, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x9b, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x6b, 0x00), + PHYREGS(0x085c, 0x0858, 0x0854, 0x01ea, 0x01eb, 0x01ec), + }, + { .freq = 5350, + RADIOREGS3(0x7c, 0x01, 0x01, 0x02, 0x17, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xa7, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x9b, 0x00, 0xa7, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x6b, 0x00), + PHYREGS(0x0860, 0x085c, 0x0858, 0x01e9, 0x01ea, 0x01eb), + }, + { .freq = 5360, + RADIOREGS3(0x75, 0x01, 0x01, 0x02, 0x18, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xa6, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x9b, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x6b, 0x00), + PHYREGS(0x0864, 0x0860, 0x085c, 0x01e8, 0x01e9, 0x01ea), + }, + { .freq = 5370, + RADIOREGS3(0x75, 0x01, 0x01, 0x02, 0x19, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0xa6, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x9b, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x5b, 0x00), + PHYREGS(0x0868, 0x0864, 0x0860, 0x01e7, 0x01e8, 0x01e9), + }, + { .freq = 5380, + RADIOREGS3(0x6e, 0x01, 0x01, 0x02, 0x1a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0x96, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x9a, 0x00, 0x96, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x5a, 0x00), + PHYREGS(0x086c, 0x0868, 0x0864, 0x01e6, 0x01e7, 0x01e8), + }, + { .freq = 5390, + RADIOREGS3(0x6e, 0x01, 0x01, 0x02, 0x1b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, + 0xff, 0x95, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x9a, 0x00, 0x95, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x5a, 0x00), + PHYREGS(0x0870, 0x086c, 0x0868, 0x01e5, 0x01e6, 0x01e7), + }, + { .freq = 5400, + RADIOREGS3(0x67, 0x01, 0x01, 0x02, 0x1c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xc8, 0x95, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x9a, 0x00, 0x95, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x5a, 0x00), + PHYREGS(0x0874, 0x0870, 0x086c, 0x01e5, 0x01e5, 0x01e6), + }, + { .freq = 5410, + RADIOREGS3(0x67, 0x01, 0x01, 0x02, 0x1d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xc8, 0x95, 0x00, 0x03, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x9a, 0x00, 0x95, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x5a, 0x00), + PHYREGS(0x0878, 0x0874, 0x0870, 0x01e4, 0x01e5, 0x01e5), + }, + { .freq = 5420, + RADIOREGS3(0x61, 0x01, 0x01, 0x02, 0x1e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xc8, 0x95, 0x00, 0x03, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x9a, 0x00, 0x95, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x5a, 0x00), + PHYREGS(0x087c, 0x0878, 0x0874, 0x01e3, 0x01e4, 0x01e5), + }, + { .freq = 5430, + RADIOREGS3(0x61, 0x01, 0x01, 0x02, 0x1f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xc8, 0x85, 0x00, 0x02, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x99, 0x00, 0x85, 0x00, 0x02, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x59, 0x00), + PHYREGS(0x0880, 0x087c, 0x0878, 0x01e2, 0x01e3, 0x01e4), + }, + { .freq = 5440, + RADIOREGS3(0x5a, 0x01, 0x01, 0x02, 0x20, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x84, 0x00, 0x02, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x99, 0x00, 0x84, 0x00, 0x02, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x59, 0x00), + PHYREGS(0x0884, 0x0880, 0x087c, 0x01e1, 0x01e2, 0x01e3), + }, + { .freq = 5450, + RADIOREGS3(0x5a, 0x01, 0x01, 0x02, 0x21, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x84, 0x00, 0x02, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x99, 0x00, 0x84, 0x00, 0x02, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x59, 0x00), + PHYREGS(0x0888, 0x0884, 0x0880, 0x01e0, 0x01e1, 0x01e2), + }, + { .freq = 5460, + RADIOREGS3(0x53, 0x01, 0x01, 0x02, 0x22, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x84, 0x00, 0x02, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x99, 0x00, 0x84, 0x00, 0x02, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x69, 0x00), + PHYREGS(0x088c, 0x0888, 0x0884, 0x01df, 0x01e0, 0x01e1), + }, + { .freq = 5470, + RADIOREGS3(0x53, 0x01, 0x01, 0x02, 0x23, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x74, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x99, 0x00, 0x74, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x69, 0x00), + PHYREGS(0x0890, 0x088c, 0x0888, 0x01de, 0x01df, 0x01e0), + }, + { .freq = 5480, + RADIOREGS3(0x4d, 0x01, 0x01, 0x02, 0x24, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x68, 0x00), + PHYREGS(0x0894, 0x0890, 0x088c, 0x01dd, 0x01de, 0x01df), + }, + { .freq = 5490, + RADIOREGS3(0x4d, 0x01, 0x01, 0x02, 0x25, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, + 0xc8, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x68, 0x00), + PHYREGS(0x0898, 0x0894, 0x0890, 0x01dd, 0x01dd, 0x01de), + }, + { .freq = 5500, + RADIOREGS3(0x47, 0x01, 0x01, 0x02, 0x26, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x78, 0x00), + PHYREGS(0x089c, 0x0898, 0x0894, 0x01dc, 0x01dd, 0x01dd), + }, + { .freq = 5510, + RADIOREGS3(0x47, 0x01, 0x01, 0x02, 0x27, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x78, 0x00), + PHYREGS(0x08a0, 0x089c, 0x0898, 0x01db, 0x01dc, 0x01dd), + }, + { .freq = 5520, + RADIOREGS3(0x40, 0x01, 0x01, 0x02, 0x28, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x78, 0x00), + PHYREGS(0x08a4, 0x08a0, 0x089c, 0x01da, 0x01db, 0x01dc), + }, + { .freq = 5530, + RADIOREGS3(0x40, 0x01, 0x01, 0x02, 0x29, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x63, 0x00, 0x01, 0x00, 0x70, 0x00, 0x03, + 0x00, 0x98, 0x00, 0x63, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x03, 0x00, 0x78, 0x00), + PHYREGS(0x08a8, 0x08a4, 0x08a0, 0x01d9, 0x01da, 0x01db), + }, + { .freq = 5540, + RADIOREGS3(0x3a, 0x01, 0x01, 0x02, 0x2a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x62, 0x00, 0x00, 0x00, 0x70, 0x00, 0x03, + 0x00, 0x97, 0x00, 0x62, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x03, 0x00, 0x77, 0x00), + PHYREGS(0x08ac, 0x08a8, 0x08a4, 0x01d8, 0x01d9, 0x01da), + }, + { .freq = 5550, + RADIOREGS3(0x3a, 0x01, 0x01, 0x02, 0x2b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x62, 0x00, 0x00, 0x00, 0x70, 0x00, 0x03, + 0x00, 0x97, 0x00, 0x62, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x03, 0x00, 0x77, 0x00), + PHYREGS(0x08b0, 0x08ac, 0x08a8, 0x01d7, 0x01d8, 0x01d9), + }, + { .freq = 5560, + RADIOREGS3(0x34, 0x01, 0x01, 0x02, 0x2c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x62, 0x00, 0x00, 0x00, 0x70, 0x00, 0x03, + 0x00, 0x97, 0x00, 0x62, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x03, 0x00, 0x77, 0x00), + PHYREGS(0x08b4, 0x08b0, 0x08ac, 0x01d7, 0x01d7, 0x01d8), + }, + { .freq = 5570, + RADIOREGS3(0x34, 0x01, 0x01, 0x02, 0x2d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x52, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x96, 0x00, 0x52, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x76, 0x00), + PHYREGS(0x08b8, 0x08b4, 0x08b0, 0x01d6, 0x01d7, 0x01d7), + }, + { .freq = 5580, + RADIOREGS3(0x2e, 0x01, 0x01, 0x02, 0x2e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x52, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x96, 0x00, 0x52, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x76, 0x00), + PHYREGS(0x08bc, 0x08b8, 0x08b4, 0x01d5, 0x01d6, 0x01d7), + }, + { .freq = 5590, + RADIOREGS3(0x2e, 0x01, 0x01, 0x02, 0x2f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, + 0x84, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x76, 0x00), + PHYREGS(0x08c0, 0x08bc, 0x08b8, 0x01d4, 0x01d5, 0x01d6), + }, + { .freq = 5600, + RADIOREGS3(0x28, 0x01, 0x01, 0x02, 0x30, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x76, 0x00), + PHYREGS(0x08c4, 0x08c0, 0x08bc, 0x01d3, 0x01d4, 0x01d5), + }, + { .freq = 5610, + RADIOREGS3(0x28, 0x01, 0x01, 0x02, 0x31, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x76, 0x00), + PHYREGS(0x08c8, 0x08c4, 0x08c0, 0x01d2, 0x01d3, 0x01d4), + }, + { .freq = 5620, + RADIOREGS3(0x21, 0x01, 0x01, 0x02, 0x32, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x76, 0x00), + PHYREGS(0x08cc, 0x08c8, 0x08c4, 0x01d2, 0x01d2, 0x01d3), + }, + { .freq = 5630, + RADIOREGS3(0x21, 0x01, 0x01, 0x02, 0x33, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x76, 0x00), + PHYREGS(0x08d0, 0x08cc, 0x08c8, 0x01d1, 0x01d2, 0x01d2), + }, + { .freq = 5640, + RADIOREGS3(0x1c, 0x01, 0x01, 0x02, 0x34, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x95, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x75, 0x00), + PHYREGS(0x08d4, 0x08d0, 0x08cc, 0x01d0, 0x01d1, 0x01d2), + }, + { .freq = 5650, + RADIOREGS3(0x1c, 0x01, 0x01, 0x02, 0x35, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x50, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x95, 0x00, 0x50, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x75, 0x00), + PHYREGS(0x08d8, 0x08d4, 0x08d0, 0x01cf, 0x01d0, 0x01d1), + }, + { .freq = 5660, + RADIOREGS3(0x16, 0x01, 0x01, 0x02, 0x36, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x50, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x95, 0x00, 0x50, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x75, 0x00), + PHYREGS(0x08dc, 0x08d8, 0x08d4, 0x01ce, 0x01cf, 0x01d0), + }, + { .freq = 5670, + RADIOREGS3(0x16, 0x01, 0x01, 0x02, 0x37, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x74, 0x00), + PHYREGS(0x08e0, 0x08dc, 0x08d8, 0x01ce, 0x01ce, 0x01cf), + }, + { .freq = 5680, + RADIOREGS3(0x10, 0x01, 0x01, 0x02, 0x38, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x74, 0x00), + PHYREGS(0x08e4, 0x08e0, 0x08dc, 0x01cd, 0x01ce, 0x01ce), + }, + { .freq = 5690, + RADIOREGS3(0x10, 0x01, 0x01, 0x02, 0x39, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, + 0x70, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x74, 0x00), + PHYREGS(0x08e8, 0x08e4, 0x08e0, 0x01cc, 0x01cd, 0x01ce), + }, + { .freq = 5700, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x74, 0x00), + PHYREGS(0x08ec, 0x08e8, 0x08e4, 0x01cb, 0x01cc, 0x01cd), + }, + { .freq = 5710, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x74, 0x00), + PHYREGS(0x08f0, 0x08ec, 0x08e8, 0x01ca, 0x01cb, 0x01cc), + }, + { .freq = 5720, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x74, 0x00), + PHYREGS(0x08f4, 0x08f0, 0x08ec, 0x01c9, 0x01ca, 0x01cb), + }, + { .freq = 5725, + RADIOREGS3(0x03, 0x01, 0x02, 0x04, 0x79, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x74, 0x00), + PHYREGS(0x08f6, 0x08f2, 0x08ee, 0x01c9, 0x01ca, 0x01cb), + }, + { .freq = 5730, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x84, 0x00), + PHYREGS(0x08f8, 0x08f4, 0x08f0, 0x01c9, 0x01c9, 0x01ca), + }, + { .freq = 5735, + RADIOREGS3(0x03, 0x01, 0x02, 0x04, 0x7b, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x83, 0x00), + PHYREGS(0x08fa, 0x08f6, 0x08f2, 0x01c8, 0x01c9, 0x01ca), + }, + { .freq = 5740, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x83, 0x00), + PHYREGS(0x08fc, 0x08f8, 0x08f4, 0x01c8, 0x01c9, 0x01c9), + }, + { .freq = 5745, + RADIOREGS3(0xfe, 0x00, 0x02, 0x04, 0x7d, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x83, 0x00), + PHYREGS(0x08fe, 0x08fa, 0x08f6, 0x01c8, 0x01c8, 0x01c9), + }, + { .freq = 5750, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x83, 0x00), + PHYREGS(0x0900, 0x08fc, 0x08f8, 0x01c7, 0x01c8, 0x01c9), + }, + { .freq = 5755, + RADIOREGS3(0xfe, 0x00, 0x02, 0x04, 0x7f, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x83, 0x00), + PHYREGS(0x0902, 0x08fe, 0x08fa, 0x01c7, 0x01c8, 0x01c8), + }, + { .freq = 5760, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x40, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x83, 0x00), + PHYREGS(0x0904, 0x0900, 0x08fc, 0x01c6, 0x01c7, 0x01c8), + }, + { .freq = 5765, + RADIOREGS3(0xf8, 0x00, 0x02, 0x04, 0x81, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x82, 0x00), + PHYREGS(0x0906, 0x0902, 0x08fe, 0x01c6, 0x01c7, 0x01c8), + }, + { .freq = 5770, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x41, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x82, 0x00), + PHYREGS(0x0908, 0x0904, 0x0900, 0x01c6, 0x01c6, 0x01c7), + }, + { .freq = 5775, + RADIOREGS3(0xf8, 0x00, 0x02, 0x04, 0x83, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x82, 0x00), + PHYREGS(0x090a, 0x0906, 0x0902, 0x01c5, 0x01c6, 0x01c7), + }, + { .freq = 5780, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x42, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, + 0x40, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x82, 0x00), + PHYREGS(0x090c, 0x0908, 0x0904, 0x01c5, 0x01c6, 0x01c6), + }, + { .freq = 5785, + RADIOREGS3(0xf2, 0x00, 0x02, 0x04, 0x85, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, + 0x40, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x82, 0x00), + PHYREGS(0x090e, 0x090a, 0x0906, 0x01c4, 0x01c5, 0x01c6), + }, + { .freq = 5790, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x43, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, + 0x40, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x82, 0x00), + PHYREGS(0x0910, 0x090c, 0x0908, 0x01c4, 0x01c5, 0x01c6), + }, + { .freq = 5795, + RADIOREGS3(0xf2, 0x00, 0x02, 0x04, 0x87, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, + 0x40, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x82, 0x00), + PHYREGS(0x0912, 0x090e, 0x090a, 0x01c4, 0x01c4, 0x01c5), + }, + { .freq = 5800, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x44, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x82, 0x00), + PHYREGS(0x0914, 0x0910, 0x090c, 0x01c3, 0x01c4, 0x01c5), + }, + { .freq = 5805, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x89, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x82, 0x00), + PHYREGS(0x0916, 0x0912, 0x090e, 0x01c3, 0x01c4, 0x01c4), + }, + { .freq = 5810, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x45, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x82, 0x00), + PHYREGS(0x0918, 0x0914, 0x0910, 0x01c2, 0x01c3, 0x01c4), + }, + { .freq = 5815, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x8b, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x82, 0x00), + PHYREGS(0x091a, 0x0916, 0x0912, 0x01c2, 0x01c3, 0x01c4), + }, + { .freq = 5820, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x46, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x82, 0x00), + PHYREGS(0x091c, 0x0918, 0x0914, 0x01c2, 0x01c2, 0x01c3), + }, + { .freq = 5825, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x8d, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x82, 0x00), + PHYREGS(0x091e, 0x091a, 0x0916, 0x01c1, 0x01c2, 0x01c3), + }, + { .freq = 5830, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x47, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x72, 0x00), + PHYREGS(0x0920, 0x091c, 0x0918, 0x01c1, 0x01c2, 0x01c2), + }, + { .freq = 5840, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x48, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x72, 0x00), + PHYREGS(0x0924, 0x0920, 0x091c, 0x01c0, 0x01c1, 0x01c2), + }, + { .freq = 5850, + RADIOREGS3(0xe0, 0x00, 0x01, 0x02, 0x49, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x72, 0x00), + PHYREGS(0x0928, 0x0924, 0x0920, 0x01bf, 0x01c0, 0x01c1), + }, + { .freq = 5860, + RADIOREGS3(0xde, 0x00, 0x01, 0x02, 0x4a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x72, 0x00), + PHYREGS(0x092c, 0x0928, 0x0924, 0x01bf, 0x01bf, 0x01c0), + }, + { .freq = 5870, + RADIOREGS3(0xdb, 0x00, 0x01, 0x02, 0x4b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x71, 0x00), + PHYREGS(0x0930, 0x092c, 0x0928, 0x01be, 0x01bf, 0x01bf), + }, + { .freq = 5880, + RADIOREGS3(0xd8, 0x00, 0x01, 0x02, 0x4c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x71, 0x00), + PHYREGS(0x0934, 0x0930, 0x092c, 0x01bd, 0x01be, 0x01bf), + }, + { .freq = 5890, + RADIOREGS3(0xd6, 0x00, 0x01, 0x02, 0x4d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x71, 0x00), + PHYREGS(0x0938, 0x0934, 0x0930, 0x01bc, 0x01bd, 0x01be), + }, + { .freq = 5900, + RADIOREGS3(0xd3, 0x00, 0x01, 0x02, 0x4e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x87, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x71, 0x00), + PHYREGS(0x093c, 0x0938, 0x0934, 0x01bc, 0x01bc, 0x01bd), + }, + { .freq = 5910, + RADIOREGS3(0xd6, 0x00, 0x01, 0x02, 0x4f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x87, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x71, 0x00), + PHYREGS(0x0940, 0x093c, 0x0938, 0x01bb, 0x01bc, 0x01bc), + }, + { .freq = 2412, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x6c, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x04, 0x04, 0x04, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x1f, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0b, 0x00, 0x1f, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0b), + PHYREGS(0x03c9, 0x03c5, 0x03c1, 0x043a, 0x043f, 0x0443), + }, + { .freq = 2417, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x71, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x1f, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0a, 0x00, 0x1f, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0a), + PHYREGS(0x03cb, 0x03c7, 0x03c3, 0x0438, 0x043d, 0x0441), + }, + { .freq = 2422, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x76, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0a, 0x00, 0x0e, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0a), + PHYREGS(0x03cd, 0x03c9, 0x03c5, 0x0436, 0x043a, 0x043f), + }, + { .freq = 2427, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x7b, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x0d, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x0a, 0x00, 0x0d, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x0a), + PHYREGS(0x03cf, 0x03cb, 0x03c7, 0x0434, 0x0438, 0x043d), + }, + { .freq = 2432, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x80, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x0a), + PHYREGS(0x03d1, 0x03cd, 0x03c9, 0x0431, 0x0436, 0x043a), + }, + { .freq = 2437, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x85, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x0b, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x0a, 0x00, 0x0b, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x0a), + PHYREGS(0x03d3, 0x03cf, 0x03cb, 0x042f, 0x0434, 0x0438), + }, + { .freq = 2442, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x8a, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x09, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x0a), + PHYREGS(0x03d5, 0x03d1, 0x03cd, 0x042d, 0x0431, 0x0436), + }, + { .freq = 2447, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x8f, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x09, 0x00, 0x08, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x09), + PHYREGS(0x03d7, 0x03d3, 0x03cf, 0x042b, 0x042f, 0x0434), + }, + { .freq = 2452, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x94, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x07, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x09, 0x00, 0x07, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x09), + PHYREGS(0x03d9, 0x03d5, 0x03d1, 0x0429, 0x042d, 0x0431), + }, + { .freq = 2457, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x99, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x09, 0x00, 0x06, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x09), + PHYREGS(0x03db, 0x03d7, 0x03d3, 0x0427, 0x042b, 0x042f), + }, + { .freq = 2462, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x9e, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x09, 0x00, 0x05, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x09), + PHYREGS(0x03dd, 0x03d9, 0x03d5, 0x0424, 0x0429, 0x042d), + }, + { .freq = 2467, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0xa3, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x08), + PHYREGS(0x03df, 0x03db, 0x03d7, 0x0422, 0x0427, 0x042b), + }, + { .freq = 2472, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0xa8, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x08, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x08), + PHYREGS(0x03e1, 0x03dd, 0x03d9, 0x0420, 0x0424, 0x0429), + }, + { .freq = 2484, + RADIOREGS3(0xff, 0x01, 0x03, 0x09, 0xb4, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x08), + PHYREGS(0x03e6, 0x03e2, 0x03de, 0x041b, 0x041f, 0x0424), + }, +}; + +static const struct b43_nphy_channeltab_entry_rev3 b43_nphy_channeltab_rev6[] = { + { .freq = 4920, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xec, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07b4, 0x07b0, 0x07ac, 0x0214, 0x0215, 0x0216), + }, + { .freq = 4930, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xed, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07b8, 0x07b4, 0x07b0, 0x0213, 0x0214, 0x0215), + }, + { .freq = 4940, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xee, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07bc, 0x07b8, 0x07b4, 0x0212, 0x0213, 0x0214), + }, + { .freq = 4950, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xef, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07c0, 0x07bc, 0x07b8, 0x0211, 0x0212, 0x0213), + }, + { .freq = 4960, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf0, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07c4, 0x07c0, 0x07bc, 0x020f, 0x0211, 0x0212), + }, + { .freq = 4970, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf1, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07c8, 0x07c4, 0x07c0, 0x020e, 0x020f, 0x0211), + }, + { .freq = 4980, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf2, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07cc, 0x07c8, 0x07c4, 0x020d, 0x020e, 0x020f), + }, + { .freq = 4990, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf3, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07d0, 0x07cc, 0x07c8, 0x020c, 0x020d, 0x020e), + }, + { .freq = 5000, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf4, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07d4, 0x07d0, 0x07cc, 0x020b, 0x020c, 0x020d), + }, + { .freq = 5010, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf5, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07d8, 0x07d4, 0x07d0, 0x020a, 0x020b, 0x020c), + }, + { .freq = 5020, + RADIOREGS3(0xf7, 0x01, 0x01, 0x01, 0xf6, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07dc, 0x07d8, 0x07d4, 0x0209, 0x020a, 0x020b), + }, + { .freq = 5030, + RADIOREGS3(0xf7, 0x01, 0x01, 0x01, 0xf7, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07e0, 0x07dc, 0x07d8, 0x0208, 0x0209, 0x020a), + }, + { .freq = 5040, + RADIOREGS3(0xef, 0x01, 0x01, 0x01, 0xf8, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07e4, 0x07e0, 0x07dc, 0x0207, 0x0208, 0x0209), + }, + { .freq = 5050, + RADIOREGS3(0xef, 0x01, 0x01, 0x01, 0xf9, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07e8, 0x07e4, 0x07e0, 0x0206, 0x0207, 0x0208), + }, + { .freq = 5060, + RADIOREGS3(0xe6, 0x01, 0x01, 0x01, 0xfa, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07ec, 0x07e8, 0x07e4, 0x0205, 0x0206, 0x0207), + }, + { .freq = 5070, + RADIOREGS3(0xe6, 0x01, 0x01, 0x01, 0xfb, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfd, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07f0, 0x07ec, 0x07e8, 0x0204, 0x0205, 0x0206), + }, + { .freq = 5080, + RADIOREGS3(0xde, 0x01, 0x01, 0x01, 0xfc, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfd, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07f4, 0x07f0, 0x07ec, 0x0203, 0x0204, 0x0205), + }, + { .freq = 5090, + RADIOREGS3(0xde, 0x01, 0x01, 0x01, 0xfd, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfd, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07f8, 0x07f4, 0x07f0, 0x0202, 0x0203, 0x0204), + }, + { .freq = 5100, + RADIOREGS3(0xd6, 0x01, 0x01, 0x01, 0xfe, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfd, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07fc, 0x07f8, 0x07f4, 0x0201, 0x0202, 0x0203), + }, + { .freq = 5110, + RADIOREGS3(0xd6, 0x01, 0x01, 0x01, 0xff, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfc, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x0800, 0x07fc, 0x07f8, 0x0200, 0x0201, 0x0202), + }, + { .freq = 5120, + RADIOREGS3(0xce, 0x01, 0x01, 0x02, 0x00, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfc, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x0804, 0x0800, 0x07fc, 0x01ff, 0x0200, 0x0201), + }, + { .freq = 5130, + RADIOREGS3(0xce, 0x01, 0x01, 0x02, 0x01, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfc, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x0808, 0x0804, 0x0800, 0x01fe, 0x01ff, 0x0200), + }, + { .freq = 5140, + RADIOREGS3(0xc6, 0x01, 0x01, 0x02, 0x02, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfb, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfb, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x080c, 0x0808, 0x0804, 0x01fd, 0x01fe, 0x01ff), + }, + { .freq = 5160, + RADIOREGS3(0xbe, 0x01, 0x01, 0x02, 0x04, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfa, 0x00, 0x07, 0x00, 0x77, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0xfa, 0x00, 0x07, 0x00, 0x77, + 0x00, 0x0e, 0x00, 0x6f, 0x00), + PHYREGS(0x0814, 0x0810, 0x080c, 0x01fb, 0x01fc, 0x01fd), + }, + { .freq = 5170, + RADIOREGS3(0xbe, 0x01, 0x01, 0x02, 0x05, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfa, 0x00, 0x07, 0x00, 0x77, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0xfa, 0x00, 0x07, 0x00, 0x77, + 0x00, 0x0e, 0x00, 0x6f, 0x00), + PHYREGS(0x0818, 0x0814, 0x0810, 0x01fa, 0x01fb, 0x01fc), + }, + { .freq = 5180, + RADIOREGS3(0xb6, 0x01, 0x01, 0x02, 0x06, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xf9, 0x00, 0x06, 0x00, 0x77, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x06, 0x00, 0x77, + 0x00, 0x0e, 0x00, 0x6f, 0x00), + PHYREGS(0x081c, 0x0818, 0x0814, 0x01f9, 0x01fa, 0x01fb), + }, + { .freq = 5190, + RADIOREGS3(0xb6, 0x01, 0x01, 0x02, 0x07, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xf9, 0x00, 0x06, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x06, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x0820, 0x081c, 0x0818, 0x01f8, 0x01f9, 0x01fa), + }, + { .freq = 5200, + RADIOREGS3(0xaf, 0x01, 0x01, 0x02, 0x08, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xf9, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x0824, 0x0820, 0x081c, 0x01f7, 0x01f8, 0x01f9), + }, + { .freq = 5210, + RADIOREGS3(0xaf, 0x01, 0x01, 0x02, 0x09, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xf9, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x0828, 0x0824, 0x0820, 0x01f6, 0x01f7, 0x01f8), + }, + { .freq = 5220, + RADIOREGS3(0xa7, 0x01, 0x01, 0x02, 0x0a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, + 0xfe, 0xd8, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0xd8, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x082c, 0x0828, 0x0824, 0x01f5, 0x01f6, 0x01f7), + }, + { .freq = 5230, + RADIOREGS3(0xa7, 0x01, 0x01, 0x02, 0x0b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, + 0xee, 0xd8, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0xd8, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x0830, 0x082c, 0x0828, 0x01f4, 0x01f5, 0x01f6), + }, + { .freq = 5240, + RADIOREGS3(0xa0, 0x01, 0x01, 0x02, 0x0c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, + 0xee, 0xc8, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0xc8, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x0834, 0x0830, 0x082c, 0x01f3, 0x01f4, 0x01f5), + }, + { .freq = 5250, + RADIOREGS3(0xa0, 0x01, 0x01, 0x02, 0x0d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, + 0xed, 0xc7, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0xc7, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x0838, 0x0834, 0x0830, 0x01f2, 0x01f3, 0x01f4), + }, + { .freq = 5260, + RADIOREGS3(0x98, 0x01, 0x01, 0x02, 0x0e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8e, 0x0e, 0x00, + 0xed, 0xc7, 0x00, 0x04, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0xc7, 0x00, 0x04, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x083c, 0x0838, 0x0834, 0x01f1, 0x01f2, 0x01f3), + }, + { .freq = 5270, + RADIOREGS3(0x98, 0x01, 0x01, 0x02, 0x0f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8e, 0x0e, 0x00, + 0xed, 0xc7, 0x00, 0x04, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0xc7, 0x00, 0x04, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x0840, 0x083c, 0x0838, 0x01f0, 0x01f1, 0x01f2), + }, + { .freq = 5280, + RADIOREGS3(0x91, 0x01, 0x01, 0x02, 0x10, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, + 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x0844, 0x0840, 0x083c, 0x01f0, 0x01f0, 0x01f1), + }, + { .freq = 5290, + RADIOREGS3(0x91, 0x01, 0x01, 0x02, 0x11, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, + 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x0848, 0x0844, 0x0840, 0x01ef, 0x01f0, 0x01f0), + }, + { .freq = 5300, + RADIOREGS3(0x8a, 0x01, 0x01, 0x02, 0x12, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, + 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x084c, 0x0848, 0x0844, 0x01ee, 0x01ef, 0x01f0), + }, + { .freq = 5310, + RADIOREGS3(0x8a, 0x01, 0x01, 0x02, 0x13, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, + 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x0850, 0x084c, 0x0848, 0x01ed, 0x01ee, 0x01ef), + }, + { .freq = 5320, + RADIOREGS3(0x83, 0x01, 0x01, 0x02, 0x14, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, + 0xdb, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x0854, 0x0850, 0x084c, 0x01ec, 0x01ed, 0x01ee), + }, + { .freq = 5330, + RADIOREGS3(0x83, 0x01, 0x01, 0x02, 0x15, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xcb, 0xa6, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x0858, 0x0854, 0x0850, 0x01eb, 0x01ec, 0x01ed), + }, + { .freq = 5340, + RADIOREGS3(0x7c, 0x01, 0x01, 0x02, 0x16, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xca, 0xa6, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x085c, 0x0858, 0x0854, 0x01ea, 0x01eb, 0x01ec), + }, + { .freq = 5350, + RADIOREGS3(0x7c, 0x01, 0x01, 0x02, 0x17, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, + 0xca, 0xa6, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x0860, 0x085c, 0x0858, 0x01e9, 0x01ea, 0x01eb), + }, + { .freq = 5360, + RADIOREGS3(0x75, 0x01, 0x01, 0x02, 0x18, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, + 0xc9, 0x95, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x95, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x0864, 0x0860, 0x085c, 0x01e8, 0x01e9, 0x01ea), + }, + { .freq = 5370, + RADIOREGS3(0x75, 0x01, 0x01, 0x02, 0x19, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, + 0xc9, 0x95, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x95, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x0868, 0x0864, 0x0860, 0x01e7, 0x01e8, 0x01e9), + }, + { .freq = 5380, + RADIOREGS3(0x6e, 0x01, 0x01, 0x02, 0x1a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, + 0xb8, 0x95, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x95, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x086c, 0x0868, 0x0864, 0x01e6, 0x01e7, 0x01e8), + }, + { .freq = 5390, + RADIOREGS3(0x6e, 0x01, 0x01, 0x02, 0x1b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, + 0xb8, 0x84, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x84, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x0870, 0x086c, 0x0868, 0x01e5, 0x01e6, 0x01e7), + }, + { .freq = 5400, + RADIOREGS3(0x67, 0x01, 0x01, 0x02, 0x1c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, + 0xb8, 0x84, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x84, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x0874, 0x0870, 0x086c, 0x01e5, 0x01e5, 0x01e6), + }, + { .freq = 5410, + RADIOREGS3(0x67, 0x01, 0x01, 0x02, 0x1d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, + 0xb7, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x0878, 0x0874, 0x0870, 0x01e4, 0x01e5, 0x01e5), + }, + { .freq = 5420, + RADIOREGS3(0x61, 0x01, 0x01, 0x02, 0x1e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, + 0xa7, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x087c, 0x0878, 0x0874, 0x01e3, 0x01e4, 0x01e5), + }, + { .freq = 5430, + RADIOREGS3(0x61, 0x01, 0x01, 0x02, 0x1f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0b, 0x00, + 0xa6, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x0880, 0x087c, 0x0878, 0x01e2, 0x01e3, 0x01e4), + }, + { .freq = 5440, + RADIOREGS3(0x5a, 0x01, 0x01, 0x02, 0x20, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, + 0xa6, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x0884, 0x0880, 0x087c, 0x01e1, 0x01e2, 0x01e3), + }, + { .freq = 5450, + RADIOREGS3(0x5a, 0x01, 0x01, 0x02, 0x21, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, + 0x95, 0x84, 0x00, 0x01, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x84, 0x00, 0x01, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x0888, 0x0884, 0x0880, 0x01e0, 0x01e1, 0x01e2), + }, + { .freq = 5460, + RADIOREGS3(0x53, 0x01, 0x01, 0x02, 0x22, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, + 0x95, 0x84, 0x00, 0x01, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x84, 0x00, 0x01, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x088c, 0x0888, 0x0884, 0x01df, 0x01e0, 0x01e1), + }, + { .freq = 5470, + RADIOREGS3(0x53, 0x01, 0x01, 0x02, 0x23, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, + 0x94, 0x73, 0x00, 0x01, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x01, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x0890, 0x088c, 0x0888, 0x01de, 0x01df, 0x01e0), + }, + { .freq = 5480, + RADIOREGS3(0x4d, 0x01, 0x01, 0x02, 0x24, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, + 0x84, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x0894, 0x0890, 0x088c, 0x01dd, 0x01de, 0x01df), + }, + { .freq = 5490, + RADIOREGS3(0x4d, 0x01, 0x01, 0x02, 0x25, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, + 0x83, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x0898, 0x0894, 0x0890, 0x01dd, 0x01dd, 0x01de), + }, + { .freq = 5500, + RADIOREGS3(0x47, 0x01, 0x01, 0x02, 0x26, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, + 0x82, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x089c, 0x0898, 0x0894, 0x01dc, 0x01dd, 0x01dd), + }, + { .freq = 5510, + RADIOREGS3(0x47, 0x01, 0x01, 0x02, 0x27, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, + 0x82, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x08a0, 0x089c, 0x0898, 0x01db, 0x01dc, 0x01dd), + }, + { .freq = 5520, + RADIOREGS3(0x40, 0x01, 0x01, 0x02, 0x28, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, + 0x72, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x08a4, 0x08a0, 0x089c, 0x01da, 0x01db, 0x01dc), + }, + { .freq = 5530, + RADIOREGS3(0x40, 0x01, 0x01, 0x02, 0x29, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x09, 0x00, + 0x72, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x08a8, 0x08a4, 0x08a0, 0x01d9, 0x01da, 0x01db), + }, + { .freq = 5540, + RADIOREGS3(0x3a, 0x01, 0x01, 0x02, 0x2a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x09, 0x00, + 0x71, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x08ac, 0x08a8, 0x08a4, 0x01d8, 0x01d9, 0x01da), + }, + { .freq = 5550, + RADIOREGS3(0x3a, 0x01, 0x01, 0x02, 0x2b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, + 0x61, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x08b0, 0x08ac, 0x08a8, 0x01d7, 0x01d8, 0x01d9), + }, + { .freq = 5560, + RADIOREGS3(0x34, 0x01, 0x01, 0x02, 0x2c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, + 0x61, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x08b4, 0x08b0, 0x08ac, 0x01d7, 0x01d7, 0x01d8), + }, + { .freq = 5570, + RADIOREGS3(0x34, 0x01, 0x01, 0x02, 0x2d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, + 0x61, 0x62, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x62, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x08b8, 0x08b4, 0x08b0, 0x01d6, 0x01d7, 0x01d7), + }, + { .freq = 5580, + RADIOREGS3(0x2e, 0x01, 0x01, 0x02, 0x2e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x89, 0x08, 0x00, + 0x60, 0x62, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x62, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00), + PHYREGS(0x08bc, 0x08b8, 0x08b4, 0x01d5, 0x01d6, 0x01d7), + }, + { .freq = 5590, + RADIOREGS3(0x2e, 0x01, 0x01, 0x02, 0x2f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x89, 0x08, 0x00, + 0x50, 0x61, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x61, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00), + PHYREGS(0x08c0, 0x08bc, 0x08b8, 0x01d4, 0x01d5, 0x01d6), + }, + { .freq = 5600, + RADIOREGS3(0x28, 0x01, 0x01, 0x02, 0x30, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, + 0x50, 0x51, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x51, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00), + PHYREGS(0x08c4, 0x08c0, 0x08bc, 0x01d3, 0x01d4, 0x01d5), + }, + { .freq = 5610, + RADIOREGS3(0x28, 0x01, 0x01, 0x02, 0x31, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, + 0x50, 0x51, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x51, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00), + PHYREGS(0x08c8, 0x08c4, 0x08c0, 0x01d2, 0x01d3, 0x01d4), + }, + { .freq = 5620, + RADIOREGS3(0x21, 0x01, 0x01, 0x02, 0x32, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, + 0x50, 0x50, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x50, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00), + PHYREGS(0x08cc, 0x08c8, 0x08c4, 0x01d2, 0x01d2, 0x01d3), + }, + { .freq = 5630, + RADIOREGS3(0x21, 0x01, 0x01, 0x02, 0x33, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, + 0x50, 0x50, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x50, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00), + PHYREGS(0x08d0, 0x08cc, 0x08c8, 0x01d1, 0x01d2, 0x01d2), + }, + { .freq = 5640, + RADIOREGS3(0x1c, 0x01, 0x01, 0x02, 0x34, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, + 0x40, 0x50, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x50, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00), + PHYREGS(0x08d4, 0x08d0, 0x08cc, 0x01d0, 0x01d1, 0x01d2), + }, + { .freq = 5650, + RADIOREGS3(0x1c, 0x01, 0x01, 0x02, 0x35, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, + 0x40, 0x40, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x40, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00), + PHYREGS(0x08d8, 0x08d4, 0x08d0, 0x01cf, 0x01d0, 0x01d1), + }, + { .freq = 5660, + RADIOREGS3(0x16, 0x01, 0x01, 0x02, 0x36, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, + 0x40, 0x40, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x40, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00), + PHYREGS(0x08dc, 0x08d8, 0x08d4, 0x01ce, 0x01cf, 0x01d0), + }, + { .freq = 5670, + RADIOREGS3(0x16, 0x01, 0x01, 0x02, 0x37, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, + 0x40, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00), + PHYREGS(0x08e0, 0x08dc, 0x08d8, 0x01ce, 0x01ce, 0x01cf), + }, + { .freq = 5680, + RADIOREGS3(0x10, 0x01, 0x01, 0x02, 0x38, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00), + PHYREGS(0x08e4, 0x08e0, 0x08dc, 0x01cd, 0x01ce, 0x01ce), + }, + { .freq = 5690, + RADIOREGS3(0x10, 0x01, 0x01, 0x02, 0x39, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00), + PHYREGS(0x08e8, 0x08e4, 0x08e0, 0x01cc, 0x01cd, 0x01ce), + }, + { .freq = 5700, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00), + PHYREGS(0x08ec, 0x08e8, 0x08e4, 0x01cb, 0x01cc, 0x01cd), + }, + { .freq = 5710, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00), + PHYREGS(0x08f0, 0x08ec, 0x08e8, 0x01ca, 0x01cb, 0x01cc), + }, + { .freq = 5720, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00), + PHYREGS(0x08f4, 0x08f0, 0x08ec, 0x01c9, 0x01ca, 0x01cb), + }, + { .freq = 5725, + RADIOREGS3(0x03, 0x01, 0x02, 0x04, 0x79, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00), + PHYREGS(0x08f6, 0x08f2, 0x08ee, 0x01c9, 0x01ca, 0x01cb), + }, + { .freq = 5730, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00), + PHYREGS(0x08f8, 0x08f4, 0x08f0, 0x01c9, 0x01c9, 0x01ca), + }, + { .freq = 5735, + RADIOREGS3(0x03, 0x01, 0x02, 0x04, 0x7b, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6d, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6d, 0x00), + PHYREGS(0x08fa, 0x08f6, 0x08f2, 0x01c8, 0x01c9, 0x01ca), + }, + { .freq = 5740, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6d, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6d, 0x00), + PHYREGS(0x08fc, 0x08f8, 0x08f4, 0x01c8, 0x01c9, 0x01c9), + }, + { .freq = 5745, + RADIOREGS3(0xfe, 0x00, 0x02, 0x04, 0x7d, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6d, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6d, 0x00), + PHYREGS(0x08fe, 0x08fa, 0x08f6, 0x01c8, 0x01c8, 0x01c9), + }, + { .freq = 5750, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x20, 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6d, 0x00, 0x20, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6d, 0x00), + PHYREGS(0x0900, 0x08fc, 0x08f8, 0x01c7, 0x01c8, 0x01c9), + }, + { .freq = 5755, + RADIOREGS3(0xfe, 0x00, 0x02, 0x04, 0x7f, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x10, 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6c, 0x00, 0x20, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6c, 0x00), + PHYREGS(0x0902, 0x08fe, 0x08fa, 0x01c7, 0x01c8, 0x01c8), + }, + { .freq = 5760, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x40, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x86, 0x05, 0x00, + 0x10, 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6c, 0x00, 0x20, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6c, 0x00), + PHYREGS(0x0904, 0x0900, 0x08fc, 0x01c6, 0x01c7, 0x01c8), + }, + { .freq = 5765, + RADIOREGS3(0xf8, 0x00, 0x02, 0x04, 0x81, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x86, 0x05, 0x00, + 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6c, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6c, 0x00), + PHYREGS(0x0906, 0x0902, 0x08fe, 0x01c6, 0x01c7, 0x01c8), + }, + { .freq = 5770, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x41, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, + 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00), + PHYREGS(0x0908, 0x0904, 0x0900, 0x01c6, 0x01c6, 0x01c7), + }, + { .freq = 5775, + RADIOREGS3(0xf8, 0x00, 0x02, 0x04, 0x83, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, + 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00), + PHYREGS(0x090a, 0x0906, 0x0902, 0x01c5, 0x01c6, 0x01c7), + }, + { .freq = 5780, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x42, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, + 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00), + PHYREGS(0x090c, 0x0908, 0x0904, 0x01c5, 0x01c6, 0x01c6), + }, + { .freq = 5785, + RADIOREGS3(0xf2, 0x00, 0x02, 0x04, 0x85, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00), + PHYREGS(0x090e, 0x090a, 0x0906, 0x01c4, 0x01c5, 0x01c6), + }, + { .freq = 5790, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x43, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00), + PHYREGS(0x0910, 0x090c, 0x0908, 0x01c4, 0x01c5, 0x01c6), + }, + { .freq = 5795, + RADIOREGS3(0xf2, 0x00, 0x02, 0x04, 0x87, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00), + PHYREGS(0x0912, 0x090e, 0x090a, 0x01c4, 0x01c4, 0x01c5), + }, + { .freq = 5800, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x44, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00), + PHYREGS(0x0914, 0x0910, 0x090c, 0x01c3, 0x01c4, 0x01c5), + }, + { .freq = 5805, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x89, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00), + PHYREGS(0x0916, 0x0912, 0x090e, 0x01c3, 0x01c4, 0x01c4), + }, + { .freq = 5810, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x45, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00), + PHYREGS(0x0918, 0x0914, 0x0910, 0x01c2, 0x01c3, 0x01c4), + }, + { .freq = 5815, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x8b, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00), + PHYREGS(0x091a, 0x0916, 0x0912, 0x01c2, 0x01c3, 0x01c4), + }, + { .freq = 5820, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x46, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00), + PHYREGS(0x091c, 0x0918, 0x0914, 0x01c2, 0x01c2, 0x01c3), + }, + { .freq = 5825, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x8d, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x69, 0x00), + PHYREGS(0x091e, 0x091a, 0x0916, 0x01c1, 0x01c2, 0x01c3), + }, + { .freq = 5830, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x47, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x69, 0x00), + PHYREGS(0x0920, 0x091c, 0x0918, 0x01c1, 0x01c2, 0x01c2), + }, + { .freq = 5840, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x48, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x69, 0x00), + PHYREGS(0x0924, 0x0920, 0x091c, 0x01c0, 0x01c1, 0x01c2), + }, + { .freq = 5850, + RADIOREGS3(0xe0, 0x00, 0x01, 0x02, 0x49, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x69, 0x00), + PHYREGS(0x0928, 0x0924, 0x0920, 0x01bf, 0x01c0, 0x01c1), + }, + { .freq = 5860, + RADIOREGS3(0xde, 0x00, 0x01, 0x02, 0x4a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x69, 0x00), + PHYREGS(0x092c, 0x0928, 0x0924, 0x01bf, 0x01bf, 0x01c0), + }, + { .freq = 5870, + RADIOREGS3(0xdb, 0x00, 0x01, 0x02, 0x4b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00), + PHYREGS(0x0930, 0x092c, 0x0928, 0x01be, 0x01bf, 0x01bf), + }, + { .freq = 5880, + RADIOREGS3(0xd8, 0x00, 0x01, 0x02, 0x4c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00), + PHYREGS(0x0934, 0x0930, 0x092c, 0x01bd, 0x01be, 0x01bf), + }, + { .freq = 5890, + RADIOREGS3(0xd6, 0x00, 0x01, 0x02, 0x4d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00), + PHYREGS(0x0938, 0x0934, 0x0930, 0x01bc, 0x01bd, 0x01be), + }, + { .freq = 5900, + RADIOREGS3(0xd3, 0x00, 0x01, 0x02, 0x4e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00), + PHYREGS(0x093c, 0x0938, 0x0934, 0x01bc, 0x01bc, 0x01bd), + }, + { .freq = 5910, + RADIOREGS3(0xd6, 0x00, 0x01, 0x02, 0x4f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00), + PHYREGS(0x0940, 0x093c, 0x0938, 0x01bb, 0x01bc, 0x01bc), + }, + { .freq = 2412, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x6c, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x04, 0x04, 0x04, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x78, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0b, 0x00, 0x0a, 0x00, 0x78, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0b, 0x00, 0x0a), + PHYREGS(0x03c9, 0x03c5, 0x03c1, 0x043a, 0x043f, 0x0443), + }, + { .freq = 2417, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x71, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x78, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0b, 0x00, 0x0a, 0x00, 0x78, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0b, 0x00, 0x0a), + PHYREGS(0x03cb, 0x03c7, 0x03c3, 0x0438, 0x043d, 0x0441), + }, + { .freq = 2422, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x76, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x67, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0b, 0x00, 0x0a, 0x00, 0x67, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0b, 0x00, 0x0a), + PHYREGS(0x03cd, 0x03c9, 0x03c5, 0x0436, 0x043a, 0x043f), + }, + { .freq = 2427, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x7b, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x57, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x00, 0x57, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a), + PHYREGS(0x03cf, 0x03cb, 0x03c7, 0x0434, 0x0438, 0x043d), + }, + { .freq = 2432, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x80, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x56, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x00, 0x56, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a), + PHYREGS(0x03d1, 0x03cd, 0x03c9, 0x0431, 0x0436, 0x043a), + }, + { .freq = 2437, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x85, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x46, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x00, 0x46, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a), + PHYREGS(0x03d3, 0x03cf, 0x03cb, 0x042f, 0x0434, 0x0438), + }, + { .freq = 2442, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x8a, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x45, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x00, 0x45, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a), + PHYREGS(0x03d5, 0x03d1, 0x03cd, 0x042d, 0x0431, 0x0436), + }, + { .freq = 2447, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x8f, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x34, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x34, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x09), + PHYREGS(0x03d7, 0x03d3, 0x03cf, 0x042b, 0x042f, 0x0434), + }, + { .freq = 2452, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x94, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x23, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x23, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x09), + PHYREGS(0x03d9, 0x03d5, 0x03d1, 0x0429, 0x042d, 0x0431), + }, + { .freq = 2457, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x99, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x12, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x12, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x09), + PHYREGS(0x03db, 0x03d7, 0x03d3, 0x0427, 0x042b, 0x042f), + }, + { .freq = 2462, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x9e, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09), + PHYREGS(0x03dd, 0x03d9, 0x03d5, 0x0424, 0x0429, 0x042d), + }, + { .freq = 2467, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0xa3, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09), + PHYREGS(0x03df, 0x03db, 0x03d7, 0x0422, 0x0427, 0x042b), + }, + { .freq = 2472, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0xa8, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09), + PHYREGS(0x03e1, 0x03dd, 0x03d9, 0x0420, 0x0424, 0x0429), + }, + { .freq = 2484, + RADIOREGS3(0xff, 0x01, 0x03, 0x09, 0xb4, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x07, 0x07, 0x07, 0x8f, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09), + PHYREGS(0x03e6, 0x03e2, 0x03de, 0x041b, 0x041f, 0x0424), + }, +}; + +static const struct b43_nphy_channeltab_entry_rev3 b43_nphy_channeltab_rev7_9[] = { + { .freq = 4920, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xec, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0b, 0x00, 0x70, 0x00, 0x0f, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0b, 0x00, 0x70, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07b4, 0x07b0, 0x07ac, 0x0214, 0x0215, 0x0216), + }, + { .freq = 4930, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xed, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0b, 0x00, 0x70, 0x00, 0x0e, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0b, 0x00, 0x70, + 0x00, 0x0e, 0x00, 0x6f, 0x00), + PHYREGS(0x07b8, 0x07b4, 0x07b0, 0x0213, 0x0214, 0x0215), + }, + { .freq = 4940, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xee, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0b, 0x00, 0x70, 0x00, 0x0e, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0b, 0x00, 0x70, + 0x00, 0x0e, 0x00, 0x6f, 0x00), + PHYREGS(0x07bc, 0x07b8, 0x07b4, 0x0212, 0x0213, 0x0214), + }, + { .freq = 4950, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xef, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0b, 0x00, 0x70, 0x00, 0x0e, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0b, 0x00, 0x70, + 0x00, 0x0e, 0x00, 0x6f, 0x00), + PHYREGS(0x07c0, 0x07bc, 0x07b8, 0x0211, 0x0212, 0x0213), + }, + { .freq = 4960, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf0, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0e, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0e, 0x00, 0x6f, 0x00), + PHYREGS(0x07c4, 0x07c0, 0x07bc, 0x020f, 0x0211, 0x0212), + }, + { .freq = 4970, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf1, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x07c8, 0x07c4, 0x07c0, 0x020e, 0x020f, 0x0211), + }, + { .freq = 4980, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf2, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x07cc, 0x07c8, 0x07c4, 0x020d, 0x020e, 0x020f), + }, + { .freq = 4990, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf3, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x07d0, 0x07cc, 0x07c8, 0x020c, 0x020d, 0x020e), + }, + { .freq = 5000, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf4, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x07d4, 0x07d0, 0x07cc, 0x020b, 0x020c, 0x020d), + }, + { .freq = 5010, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf5, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x07d8, 0x07d4, 0x07d0, 0x020a, 0x020b, 0x020c), + }, + { .freq = 5020, + RADIOREGS3(0xf7, 0x01, 0x01, 0x01, 0xf6, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x07dc, 0x07d8, 0x07d4, 0x0209, 0x020a, 0x020b), + }, + { .freq = 5030, + RADIOREGS3(0xf7, 0x01, 0x01, 0x01, 0xf7, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xff, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0c, + 0x00, 0x9f, 0x00, 0xff, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x07e0, 0x07dc, 0x07d8, 0x0208, 0x0209, 0x020a), + }, + { .freq = 5040, + RADIOREGS3(0xef, 0x01, 0x01, 0x01, 0xf8, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0c, + 0x00, 0x9f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x07e4, 0x07e0, 0x07dc, 0x0207, 0x0208, 0x0209), + }, + { .freq = 5050, + RADIOREGS3(0xef, 0x01, 0x01, 0x01, 0xf9, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0c, + 0x00, 0x9f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x07e8, 0x07e4, 0x07e0, 0x0206, 0x0207, 0x0208), + }, + { .freq = 5060, + RADIOREGS3(0xe6, 0x01, 0x01, 0x01, 0xfa, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfd, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0c, + 0x00, 0x9f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x07ec, 0x07e8, 0x07e4, 0x0205, 0x0206, 0x0207), + }, + { .freq = 5070, + RADIOREGS3(0xe6, 0x01, 0x01, 0x01, 0xfb, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfd, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x9f, 0x00, 0xfd, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x07f0, 0x07ec, 0x07e8, 0x0204, 0x0205, 0x0206), + }, + { .freq = 5080, + RADIOREGS3(0xde, 0x01, 0x01, 0x01, 0xfc, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x07f4, 0x07f0, 0x07ec, 0x0203, 0x0204, 0x0205), + }, + { .freq = 5090, + RADIOREGS3(0xde, 0x01, 0x01, 0x01, 0xfd, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x07f8, 0x07f4, 0x07f0, 0x0202, 0x0203, 0x0204), + }, + { .freq = 5100, + RADIOREGS3(0xd6, 0x01, 0x01, 0x01, 0xfe, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x07fc, 0x07f8, 0x07f4, 0x0201, 0x0202, 0x0203), + }, + { .freq = 5110, + RADIOREGS3(0xd6, 0x01, 0x01, 0x01, 0xff, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x0800, 0x07fc, 0x07f8, 0x0200, 0x0201, 0x0202), + }, + { .freq = 5120, + RADIOREGS3(0xce, 0x01, 0x01, 0x02, 0x00, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x0804, 0x0800, 0x07fc, 0x01ff, 0x0200, 0x0201), + }, + { .freq = 5130, + RADIOREGS3(0xce, 0x01, 0x01, 0x02, 0x01, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfb, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0a, + 0x00, 0x9f, 0x00, 0xfb, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x0808, 0x0804, 0x0800, 0x01fe, 0x01ff, 0x0200), + }, + { .freq = 5140, + RADIOREGS3(0xc6, 0x01, 0x01, 0x02, 0x02, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfb, 0x00, 0x07, 0x00, 0x70, 0x00, 0x0a, + 0x00, 0x9f, 0x00, 0xfb, 0x00, 0x07, 0x00, 0x70, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x080c, 0x0808, 0x0804, 0x01fd, 0x01fe, 0x01ff), + }, + { .freq = 5160, + RADIOREGS3(0xbe, 0x01, 0x01, 0x02, 0x04, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfb, 0x00, 0x07, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x9e, 0x00, 0xfb, 0x00, 0x07, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x6e, 0x00), + PHYREGS(0x0814, 0x0810, 0x080c, 0x01fb, 0x01fc, 0x01fd), + }, + { .freq = 5170, + RADIOREGS3(0xbe, 0x01, 0x01, 0x02, 0x05, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfb, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x9e, 0x00, 0xfb, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x6e, 0x00), + PHYREGS(0x0818, 0x0814, 0x0810, 0x01fa, 0x01fb, 0x01fc), + }, + { .freq = 5180, + RADIOREGS3(0xb6, 0x01, 0x01, 0x02, 0x06, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x6e, 0x00), + PHYREGS(0x081c, 0x0818, 0x0814, 0x01f9, 0x01fa, 0x01fb), + }, + { .freq = 5190, + RADIOREGS3(0xb6, 0x01, 0x01, 0x02, 0x07, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x6e, 0x00), + PHYREGS(0x0820, 0x081c, 0x0818, 0x01f8, 0x01f9, 0x01fa), + }, + { .freq = 5200, + RADIOREGS3(0xaf, 0x01, 0x01, 0x02, 0x08, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x6e, 0x00), + PHYREGS(0x0824, 0x0820, 0x081c, 0x01f7, 0x01f8, 0x01f9), + }, + { .freq = 5210, + RADIOREGS3(0xaf, 0x01, 0x01, 0x02, 0x09, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x6e, 0x00), + PHYREGS(0x0828, 0x0824, 0x0820, 0x01f6, 0x01f7, 0x01f8), + }, + { .freq = 5220, + RADIOREGS3(0xa7, 0x01, 0x01, 0x02, 0x0a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, + 0xfe, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x6e, 0x00), + PHYREGS(0x082c, 0x0828, 0x0824, 0x01f5, 0x01f6, 0x01f7), + }, + { .freq = 5230, + RADIOREGS3(0xa7, 0x01, 0x01, 0x02, 0x0b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, + 0xee, 0xea, 0x00, 0x06, 0x00, 0x70, 0x00, 0x08, + 0x00, 0x9e, 0x00, 0xea, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x08, 0x00, 0x6e, 0x00), + PHYREGS(0x0830, 0x082c, 0x0828, 0x01f4, 0x01f5, 0x01f6), + }, + { .freq = 5240, + RADIOREGS3(0xa0, 0x01, 0x01, 0x02, 0x0c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, + 0xee, 0xe9, 0x00, 0x05, 0x00, 0x70, 0x00, 0x08, + 0x00, 0x9d, 0x00, 0xe9, 0x00, 0x05, 0x00, 0x70, + 0x00, 0x08, 0x00, 0x6d, 0x00), + PHYREGS(0x0834, 0x0830, 0x082c, 0x01f3, 0x01f4, 0x01f5), + }, + { .freq = 5250, + RADIOREGS3(0xa0, 0x01, 0x01, 0x02, 0x0d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, + 0xed, 0xe9, 0x00, 0x05, 0x00, 0x70, 0x00, 0x08, + 0x00, 0x9d, 0x00, 0xe9, 0x00, 0x05, 0x00, 0x70, + 0x00, 0x08, 0x00, 0x6d, 0x00), + PHYREGS(0x0838, 0x0834, 0x0830, 0x01f2, 0x01f3, 0x01f4), + }, + { .freq = 5260, + RADIOREGS3(0x98, 0x01, 0x01, 0x02, 0x0e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8e, 0x0e, 0x00, + 0xed, 0xd9, 0x00, 0x05, 0x00, 0x70, 0x00, 0x08, + 0x00, 0x9d, 0x00, 0xd9, 0x00, 0x05, 0x00, 0x70, + 0x00, 0x08, 0x00, 0x6d, 0x00), + PHYREGS(0x083c, 0x0838, 0x0834, 0x01f1, 0x01f2, 0x01f3), + }, + { .freq = 5270, + RADIOREGS3(0x98, 0x01, 0x01, 0x02, 0x0f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8e, 0x0e, 0x00, + 0xed, 0xd8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x9c, 0x00, 0xd8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x6c, 0x00), + PHYREGS(0x0840, 0x083c, 0x0838, 0x01f0, 0x01f1, 0x01f2), + }, + { .freq = 5280, + RADIOREGS3(0x91, 0x01, 0x01, 0x02, 0x10, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, + 0xdc, 0xc8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x9c, 0x00, 0xc8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x6c, 0x00), + PHYREGS(0x0844, 0x0840, 0x083c, 0x01f0, 0x01f0, 0x01f1), + }, + { .freq = 5290, + RADIOREGS3(0x91, 0x01, 0x01, 0x02, 0x11, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, + 0xdc, 0xc8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x9c, 0x00, 0xc8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x6c, 0x00), + PHYREGS(0x0848, 0x0844, 0x0840, 0x01ef, 0x01f0, 0x01f0), + }, + { .freq = 5300, + RADIOREGS3(0x8a, 0x01, 0x01, 0x02, 0x12, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, + 0xdc, 0xc8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x9c, 0x00, 0xc8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x6c, 0x00), + PHYREGS(0x084c, 0x0848, 0x0844, 0x01ee, 0x01ef, 0x01f0), + }, + { .freq = 5310, + RADIOREGS3(0x8a, 0x01, 0x01, 0x02, 0x13, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, + 0xdc, 0xc8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x9c, 0x00, 0xc8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x6c, 0x00), + PHYREGS(0x0850, 0x084c, 0x0848, 0x01ed, 0x01ee, 0x01ef), + }, + { .freq = 5320, + RADIOREGS3(0x83, 0x01, 0x01, 0x02, 0x14, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, + 0xdb, 0xb8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x9c, 0x00, 0xb8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x6c, 0x00), + PHYREGS(0x0854, 0x0850, 0x084c, 0x01ec, 0x01ed, 0x01ee), + }, + { .freq = 5330, + RADIOREGS3(0x83, 0x01, 0x01, 0x02, 0x15, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xcb, 0xb7, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x9b, 0x00, 0xb7, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x6b, 0x00), + PHYREGS(0x0858, 0x0854, 0x0850, 0x01eb, 0x01ec, 0x01ed), + }, + { .freq = 5340, + RADIOREGS3(0x7c, 0x01, 0x01, 0x02, 0x16, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xca, 0xb7, 0x00, 0x03, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x9b, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x6b, 0x00), + PHYREGS(0x085c, 0x0858, 0x0854, 0x01ea, 0x01eb, 0x01ec), + }, + { .freq = 5350, + RADIOREGS3(0x7c, 0x01, 0x01, 0x02, 0x17, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, + 0xca, 0xa7, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x9b, 0x00, 0xa7, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x6b, 0x00), + PHYREGS(0x0860, 0x085c, 0x0858, 0x01e9, 0x01ea, 0x01eb), + }, + { .freq = 5360, + RADIOREGS3(0x75, 0x01, 0x01, 0x02, 0x18, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, + 0xc9, 0xa6, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x9b, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x6b, 0x00), + PHYREGS(0x0864, 0x0860, 0x085c, 0x01e8, 0x01e9, 0x01ea), + }, + { .freq = 5370, + RADIOREGS3(0x75, 0x01, 0x01, 0x02, 0x19, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, + 0xc9, 0xa6, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x9b, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x7b, 0x00), + PHYREGS(0x0868, 0x0864, 0x0860, 0x01e7, 0x01e8, 0x01e9), + }, + { .freq = 5380, + RADIOREGS3(0x6e, 0x01, 0x01, 0x02, 0x1a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, + 0xb8, 0x96, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x9a, 0x00, 0x96, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x7a, 0x00), + PHYREGS(0x086c, 0x0868, 0x0864, 0x01e6, 0x01e7, 0x01e8), + }, + { .freq = 5390, + RADIOREGS3(0x6e, 0x01, 0x01, 0x02, 0x1b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, + 0xb8, 0x95, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x9a, 0x00, 0x95, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x7a, 0x00), + PHYREGS(0x0870, 0x086c, 0x0868, 0x01e5, 0x01e6, 0x01e7), + }, + { .freq = 5400, + RADIOREGS3(0x67, 0x01, 0x01, 0x02, 0x1c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, + 0xb8, 0x95, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x9a, 0x00, 0x95, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x7a, 0x00), + PHYREGS(0x0874, 0x0870, 0x086c, 0x01e5, 0x01e5, 0x01e6), + }, + { .freq = 5410, + RADIOREGS3(0x67, 0x01, 0x01, 0x02, 0x1d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, + 0xb7, 0x95, 0x00, 0x03, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x9a, 0x00, 0x95, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x7a, 0x00), + PHYREGS(0x0878, 0x0874, 0x0870, 0x01e4, 0x01e5, 0x01e5), + }, + { .freq = 5420, + RADIOREGS3(0x61, 0x01, 0x01, 0x02, 0x1e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, + 0xa7, 0x95, 0x00, 0x03, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x9a, 0x00, 0x95, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x7a, 0x00), + PHYREGS(0x087c, 0x0878, 0x0874, 0x01e3, 0x01e4, 0x01e5), + }, + { .freq = 5430, + RADIOREGS3(0x61, 0x01, 0x01, 0x02, 0x1f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0b, 0x00, + 0xa6, 0x85, 0x00, 0x02, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x99, 0x00, 0x85, 0x00, 0x02, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x79, 0x00), + PHYREGS(0x0880, 0x087c, 0x0878, 0x01e2, 0x01e3, 0x01e4), + }, + { .freq = 5440, + RADIOREGS3(0x5a, 0x01, 0x01, 0x02, 0x20, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, + 0xa6, 0x84, 0x00, 0x02, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x99, 0x00, 0x84, 0x00, 0x02, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x79, 0x00), + PHYREGS(0x0884, 0x0880, 0x087c, 0x01e1, 0x01e2, 0x01e3), + }, + { .freq = 5450, + RADIOREGS3(0x5a, 0x01, 0x01, 0x02, 0x21, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, + 0x95, 0x84, 0x00, 0x02, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x99, 0x00, 0x84, 0x00, 0x02, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x79, 0x00), + PHYREGS(0x0888, 0x0884, 0x0880, 0x01e0, 0x01e1, 0x01e2), + }, + { .freq = 5460, + RADIOREGS3(0x53, 0x01, 0x01, 0x02, 0x22, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, + 0x95, 0x84, 0x00, 0x02, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x99, 0x00, 0x84, 0x00, 0x02, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x79, 0x00), + PHYREGS(0x088c, 0x0888, 0x0884, 0x01df, 0x01e0, 0x01e1), + }, + { .freq = 5470, + RADIOREGS3(0x53, 0x01, 0x01, 0x02, 0x23, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, + 0x94, 0x74, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x99, 0x00, 0x74, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x79, 0x00), + PHYREGS(0x0890, 0x088c, 0x0888, 0x01de, 0x01df, 0x01e0), + }, + { .freq = 5480, + RADIOREGS3(0x4d, 0x01, 0x01, 0x02, 0x24, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, + 0x84, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x78, 0x00), + PHYREGS(0x0894, 0x0890, 0x088c, 0x01dd, 0x01de, 0x01df), + }, + { .freq = 5490, + RADIOREGS3(0x4d, 0x01, 0x01, 0x02, 0x25, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, + 0x83, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x78, 0x00), + PHYREGS(0x0898, 0x0894, 0x0890, 0x01dd, 0x01dd, 0x01de), + }, + { .freq = 5500, + RADIOREGS3(0x47, 0x01, 0x01, 0x02, 0x26, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, + 0x82, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x78, 0x00), + PHYREGS(0x089c, 0x0898, 0x0894, 0x01dc, 0x01dd, 0x01dd), + }, + { .freq = 5510, + RADIOREGS3(0x47, 0x01, 0x01, 0x02, 0x27, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, + 0x82, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x78, 0x00), + PHYREGS(0x08a0, 0x089c, 0x0898, 0x01db, 0x01dc, 0x01dd), + }, + { .freq = 5520, + RADIOREGS3(0x40, 0x01, 0x01, 0x02, 0x28, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, + 0x72, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x78, 0x00), + PHYREGS(0x08a4, 0x08a0, 0x089c, 0x01da, 0x01db, 0x01dc), + }, + { .freq = 5530, + RADIOREGS3(0x40, 0x01, 0x01, 0x02, 0x29, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x09, 0x00, + 0x72, 0x63, 0x00, 0x01, 0x00, 0x70, 0x00, 0x03, + 0x00, 0x98, 0x00, 0x63, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x03, 0x00, 0x78, 0x00), + PHYREGS(0x08a8, 0x08a4, 0x08a0, 0x01d9, 0x01da, 0x01db), + }, + { .freq = 5540, + RADIOREGS3(0x3a, 0x01, 0x01, 0x02, 0x2a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x09, 0x00, + 0x71, 0x62, 0x00, 0x00, 0x00, 0x70, 0x00, 0x03, + 0x00, 0x97, 0x00, 0x62, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x03, 0x00, 0x77, 0x00), + PHYREGS(0x08ac, 0x08a8, 0x08a4, 0x01d8, 0x01d9, 0x01da), + }, + { .freq = 5550, + RADIOREGS3(0x3a, 0x01, 0x01, 0x02, 0x2b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, + 0x61, 0x62, 0x00, 0x00, 0x00, 0x70, 0x00, 0x03, + 0x00, 0x97, 0x00, 0x62, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x03, 0x00, 0x77, 0x00), + PHYREGS(0x08b0, 0x08ac, 0x08a8, 0x01d7, 0x01d8, 0x01d9), + }, + { .freq = 5560, + RADIOREGS3(0x34, 0x01, 0x01, 0x02, 0x2c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, + 0x61, 0x62, 0x00, 0x00, 0x00, 0x70, 0x00, 0x03, + 0x00, 0x97, 0x00, 0x62, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x03, 0x00, 0x77, 0x00), + PHYREGS(0x08b4, 0x08b0, 0x08ac, 0x01d7, 0x01d7, 0x01d8), + }, + { .freq = 5570, + RADIOREGS3(0x34, 0x01, 0x01, 0x02, 0x2d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, + 0x61, 0x52, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x96, 0x00, 0x52, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x76, 0x00), + PHYREGS(0x08b8, 0x08b4, 0x08b0, 0x01d6, 0x01d7, 0x01d7), + }, + { .freq = 5580, + RADIOREGS3(0x2e, 0x01, 0x01, 0x02, 0x2e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x89, 0x08, 0x00, + 0x60, 0x52, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x96, 0x00, 0x52, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x86, 0x00), + PHYREGS(0x08bc, 0x08b8, 0x08b4, 0x01d5, 0x01d6, 0x01d7), + }, + { .freq = 5590, + RADIOREGS3(0x2e, 0x01, 0x01, 0x02, 0x2f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x89, 0x08, 0x00, + 0x50, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x86, 0x00), + PHYREGS(0x08c0, 0x08bc, 0x08b8, 0x01d4, 0x01d5, 0x01d6), + }, + { .freq = 5600, + RADIOREGS3(0x28, 0x01, 0x01, 0x02, 0x30, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, + 0x50, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x86, 0x00), + PHYREGS(0x08c4, 0x08c0, 0x08bc, 0x01d3, 0x01d4, 0x01d5), + }, + { .freq = 5610, + RADIOREGS3(0x28, 0x01, 0x01, 0x02, 0x31, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, + 0x50, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x86, 0x00), + PHYREGS(0x08c8, 0x08c4, 0x08c0, 0x01d2, 0x01d3, 0x01d4), + }, + { .freq = 5620, + RADIOREGS3(0x21, 0x01, 0x01, 0x02, 0x32, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, + 0x50, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x86, 0x00), + PHYREGS(0x08cc, 0x08c8, 0x08c4, 0x01d2, 0x01d2, 0x01d3), + }, + { .freq = 5630, + RADIOREGS3(0x21, 0x01, 0x01, 0x02, 0x33, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, + 0x50, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x86, 0x00), + PHYREGS(0x08d0, 0x08cc, 0x08c8, 0x01d1, 0x01d2, 0x01d2), + }, + { .freq = 5640, + RADIOREGS3(0x1c, 0x01, 0x01, 0x02, 0x34, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, + 0x40, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x95, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x85, 0x00), + PHYREGS(0x08d4, 0x08d0, 0x08cc, 0x01d0, 0x01d1, 0x01d2), + }, + { .freq = 5650, + RADIOREGS3(0x1c, 0x01, 0x01, 0x02, 0x35, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, + 0x40, 0x50, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x95, 0x00, 0x50, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x85, 0x00), + PHYREGS(0x08d8, 0x08d4, 0x08d0, 0x01cf, 0x01d0, 0x01d1), + }, + { .freq = 5660, + RADIOREGS3(0x16, 0x01, 0x01, 0x02, 0x36, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, + 0x40, 0x50, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x95, 0x00, 0x50, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x85, 0x00), + PHYREGS(0x08dc, 0x08d8, 0x08d4, 0x01ce, 0x01cf, 0x01d0), + }, + { .freq = 5670, + RADIOREGS3(0x16, 0x01, 0x01, 0x02, 0x37, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, + 0x40, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x84, 0x00), + PHYREGS(0x08e0, 0x08dc, 0x08d8, 0x01ce, 0x01ce, 0x01cf), + }, + { .freq = 5680, + RADIOREGS3(0x10, 0x01, 0x01, 0x02, 0x38, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x84, 0x00), + PHYREGS(0x08e4, 0x08e0, 0x08dc, 0x01cd, 0x01ce, 0x01ce), + }, + { .freq = 5690, + RADIOREGS3(0x10, 0x01, 0x01, 0x02, 0x39, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00), + PHYREGS(0x08e8, 0x08e4, 0x08e0, 0x01cc, 0x01cd, 0x01ce), + }, + { .freq = 5700, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00), + PHYREGS(0x08ec, 0x08e8, 0x08e4, 0x01cb, 0x01cc, 0x01cd), + }, + { .freq = 5710, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00), + PHYREGS(0x08f0, 0x08ec, 0x08e8, 0x01ca, 0x01cb, 0x01cc), + }, + { .freq = 5720, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00), + PHYREGS(0x08f4, 0x08f0, 0x08ec, 0x01c9, 0x01ca, 0x01cb), + }, + { .freq = 5725, + RADIOREGS3(0x03, 0x01, 0x02, 0x04, 0x79, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00), + PHYREGS(0x08f6, 0x08f2, 0x08ee, 0x01c9, 0x01ca, 0x01cb), + }, + { .freq = 5730, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x20, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00), + PHYREGS(0x08f8, 0x08f4, 0x08f0, 0x01c9, 0x01c9, 0x01ca), + }, + { .freq = 5735, + RADIOREGS3(0x03, 0x01, 0x02, 0x04, 0x7b, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x20, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00), + PHYREGS(0x08fa, 0x08f6, 0x08f2, 0x01c8, 0x01c9, 0x01ca), + }, + { .freq = 5740, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x20, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00), + PHYREGS(0x08fc, 0x08f8, 0x08f4, 0x01c8, 0x01c9, 0x01c9), + }, + { .freq = 5745, + RADIOREGS3(0xfe, 0x00, 0x02, 0x04, 0x7d, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x20, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00), + PHYREGS(0x08fe, 0x08fa, 0x08f6, 0x01c8, 0x01c8, 0x01c9), + }, + { .freq = 5750, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x20, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00), + PHYREGS(0x0900, 0x08fc, 0x08f8, 0x01c7, 0x01c8, 0x01c9), + }, + { .freq = 5755, + RADIOREGS3(0xfe, 0x00, 0x02, 0x04, 0x7f, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x10, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00), + PHYREGS(0x0902, 0x08fe, 0x08fa, 0x01c7, 0x01c8, 0x01c8), + }, + { .freq = 5760, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x40, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x86, 0x05, 0x00, + 0x10, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00), + PHYREGS(0x0904, 0x0900, 0x08fc, 0x01c6, 0x01c7, 0x01c8), + }, + { .freq = 5765, + RADIOREGS3(0xf8, 0x00, 0x02, 0x04, 0x81, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x86, 0x05, 0x00, + 0x10, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x0906, 0x0902, 0x08fe, 0x01c6, 0x01c7, 0x01c8), + }, + { .freq = 5770, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x41, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, + 0x10, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x0908, 0x0904, 0x0900, 0x01c6, 0x01c6, 0x01c7), + }, + { .freq = 5775, + RADIOREGS3(0xf8, 0x00, 0x02, 0x04, 0x83, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, + 0x10, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x090a, 0x0906, 0x0902, 0x01c5, 0x01c6, 0x01c7), + }, + { .freq = 5780, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x42, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, + 0x10, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x090c, 0x0908, 0x0904, 0x01c5, 0x01c6, 0x01c6), + }, + { .freq = 5785, + RADIOREGS3(0xf2, 0x00, 0x02, 0x04, 0x85, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x090e, 0x090a, 0x0906, 0x01c4, 0x01c5, 0x01c6), + }, + { .freq = 5790, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x43, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x0910, 0x090c, 0x0908, 0x01c4, 0x01c5, 0x01c6), + }, + { .freq = 5795, + RADIOREGS3(0xf2, 0x00, 0x02, 0x04, 0x87, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x0912, 0x090e, 0x090a, 0x01c4, 0x01c4, 0x01c5), + }, + { .freq = 5800, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x44, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x0914, 0x0910, 0x090c, 0x01c3, 0x01c4, 0x01c5), + }, + { .freq = 5805, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x89, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x0916, 0x0912, 0x090e, 0x01c3, 0x01c4, 0x01c4), + }, + { .freq = 5810, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x45, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x0918, 0x0914, 0x0910, 0x01c2, 0x01c3, 0x01c4), + }, + { .freq = 5815, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x8b, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x091a, 0x0916, 0x0912, 0x01c2, 0x01c3, 0x01c4), + }, + { .freq = 5820, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x46, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x091c, 0x0918, 0x0914, 0x01c2, 0x01c2, 0x01c3), + }, + { .freq = 5825, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x8d, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x091e, 0x091a, 0x0916, 0x01c1, 0x01c2, 0x01c3), + }, + { .freq = 5830, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x47, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x0920, 0x091c, 0x0918, 0x01c1, 0x01c2, 0x01c2), + }, + { .freq = 5840, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x48, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x0924, 0x0920, 0x091c, 0x01c0, 0x01c1, 0x01c2), + }, + { .freq = 5850, + RADIOREGS3(0xe0, 0x00, 0x01, 0x02, 0x49, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x0928, 0x0924, 0x0920, 0x01bf, 0x01c0, 0x01c1), + }, + { .freq = 5860, + RADIOREGS3(0xde, 0x00, 0x01, 0x02, 0x4a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00), + PHYREGS(0x092c, 0x0928, 0x0924, 0x01bf, 0x01bf, 0x01c0), + }, + { .freq = 5870, + RADIOREGS3(0xdb, 0x00, 0x01, 0x02, 0x4b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x91, 0x00), + PHYREGS(0x0930, 0x092c, 0x0928, 0x01be, 0x01bf, 0x01bf), + }, + { .freq = 5880, + RADIOREGS3(0xd8, 0x00, 0x01, 0x02, 0x4c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x91, 0x00), + PHYREGS(0x0934, 0x0930, 0x092c, 0x01bd, 0x01be, 0x01bf), + }, + { .freq = 5890, + RADIOREGS3(0xd6, 0x00, 0x01, 0x02, 0x4d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x91, 0x00), + PHYREGS(0x0938, 0x0934, 0x0930, 0x01bc, 0x01bd, 0x01be), + }, + { .freq = 5900, + RADIOREGS3(0xd3, 0x00, 0x01, 0x02, 0x4e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x91, 0x00), + PHYREGS(0x093c, 0x0938, 0x0934, 0x01bc, 0x01bc, 0x01bd), + }, + { .freq = 5910, + RADIOREGS3(0xd6, 0x00, 0x01, 0x02, 0x4f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x91, 0x00), + PHYREGS(0x0940, 0x093c, 0x0938, 0x01bb, 0x01bc, 0x01bc), + }, + { .freq = 2412, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x6c, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x04, 0x04, 0x04, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x89, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0b, 0x00, 0x89, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0b), + PHYREGS(0x03c9, 0x03c5, 0x03c1, 0x043a, 0x043f, 0x0443), + }, + { .freq = 2417, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x71, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x89, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0a, 0x00, 0x89, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0a), + PHYREGS(0x03cb, 0x03c7, 0x03c3, 0x0438, 0x043d, 0x0441), + }, + { .freq = 2422, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x76, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x89, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0a, 0x00, 0x89, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0a), + PHYREGS(0x03cd, 0x03c9, 0x03c5, 0x0436, 0x043a, 0x043f), + }, + { .freq = 2427, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x7b, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x78, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x0a, 0x00, 0x78, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x0a), + PHYREGS(0x03cf, 0x03cb, 0x03c7, 0x0434, 0x0438, 0x043d), + }, + { .freq = 2432, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x80, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x77, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x0a, 0x00, 0x77, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x0a), + PHYREGS(0x03d1, 0x03cd, 0x03c9, 0x0431, 0x0436, 0x043a), + }, + { .freq = 2437, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x85, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x76, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x0a, 0x00, 0x76, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x0a), + PHYREGS(0x03d3, 0x03cf, 0x03cb, 0x042f, 0x0434, 0x0438), + }, + { .freq = 2442, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x8a, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x66, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x0a, 0x00, 0x66, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x0a), + PHYREGS(0x03d5, 0x03d1, 0x03cd, 0x042d, 0x0431, 0x0436), + }, + { .freq = 2447, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x8f, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x55, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x09, 0x00, 0x55, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x09), + PHYREGS(0x03d7, 0x03d3, 0x03cf, 0x042b, 0x042f, 0x0434), + }, + { .freq = 2452, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x94, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x45, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x09, 0x00, 0x45, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x09), + PHYREGS(0x03d9, 0x03d5, 0x03d1, 0x0429, 0x042d, 0x0431), + }, + { .freq = 2457, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x99, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x34, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x09, 0x00, 0x34, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x09), + PHYREGS(0x03db, 0x03d7, 0x03d3, 0x0427, 0x042b, 0x042f), + }, + { .freq = 2462, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x9e, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x33, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x09, 0x00, 0x33, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x09), + PHYREGS(0x03dd, 0x03d9, 0x03d5, 0x0424, 0x0429, 0x042d), + }, + { .freq = 2467, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0xa3, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x22, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x08, 0x00, 0x22, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x08), + PHYREGS(0x03df, 0x03db, 0x03d7, 0x0422, 0x0427, 0x042b), + }, + { .freq = 2472, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0xa8, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x08, 0x00, 0x11, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x08), + PHYREGS(0x03e1, 0x03dd, 0x03d9, 0x0420, 0x0424, 0x0429), + }, + { .freq = 2484, + RADIOREGS3(0xff, 0x01, 0x03, 0x09, 0xb4, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x07, 0x07, 0x07, 0x8f, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x08), + PHYREGS(0x03e6, 0x03e2, 0x03de, 0x041b, 0x041f, 0x0424), + }, +}; + +static const struct b43_nphy_channeltab_entry_rev3 b43_nphy_channeltab_rev8[] = { + { .freq = 4920, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xec, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07b4, 0x07b0, 0x07ac, 0x0214, 0x0215, 0x0216), + }, + { .freq = 4930, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xed, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07b8, 0x07b4, 0x07b0, 0x0213, 0x0214, 0x0215), + }, + { .freq = 4940, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xee, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07bc, 0x07b8, 0x07b4, 0x0212, 0x0213, 0x0214), + }, + { .freq = 4950, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xef, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07c0, 0x07bc, 0x07b8, 0x0211, 0x0212, 0x0213), + }, + { .freq = 4960, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf0, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07c4, 0x07c0, 0x07bc, 0x020f, 0x0211, 0x0212), + }, + { .freq = 4970, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf1, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07c8, 0x07c4, 0x07c0, 0x020e, 0x020f, 0x0211), + }, + { .freq = 4980, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf2, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07cc, 0x07c8, 0x07c4, 0x020d, 0x020e, 0x020f), + }, + { .freq = 4990, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf3, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07d0, 0x07cc, 0x07c8, 0x020c, 0x020d, 0x020e), + }, + { .freq = 5000, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf4, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07d4, 0x07d0, 0x07cc, 0x020b, 0x020c, 0x020d), + }, + { .freq = 5010, + RADIOREGS3(0xff, 0x01, 0x01, 0x01, 0xf5, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07d8, 0x07d4, 0x07d0, 0x020a, 0x020b, 0x020c), + }, + { .freq = 5020, + RADIOREGS3(0xf7, 0x01, 0x01, 0x01, 0xf6, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07dc, 0x07d8, 0x07d4, 0x0209, 0x020a, 0x020b), + }, + { .freq = 5030, + RADIOREGS3(0xf7, 0x01, 0x01, 0x01, 0xf7, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07e0, 0x07dc, 0x07d8, 0x0208, 0x0209, 0x020a), + }, + { .freq = 5040, + RADIOREGS3(0xef, 0x01, 0x01, 0x01, 0xf8, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07e4, 0x07e0, 0x07dc, 0x0207, 0x0208, 0x0209), + }, + { .freq = 5050, + RADIOREGS3(0xef, 0x01, 0x01, 0x01, 0xf9, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07e8, 0x07e4, 0x07e0, 0x0206, 0x0207, 0x0208), + }, + { .freq = 5060, + RADIOREGS3(0xe6, 0x01, 0x01, 0x01, 0xfa, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07ec, 0x07e8, 0x07e4, 0x0205, 0x0206, 0x0207), + }, + { .freq = 5070, + RADIOREGS3(0xe6, 0x01, 0x01, 0x01, 0xfb, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfd, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07f0, 0x07ec, 0x07e8, 0x0204, 0x0205, 0x0206), + }, + { .freq = 5080, + RADIOREGS3(0xde, 0x01, 0x01, 0x01, 0xfc, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfd, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07f4, 0x07f0, 0x07ec, 0x0203, 0x0204, 0x0205), + }, + { .freq = 5090, + RADIOREGS3(0xde, 0x01, 0x01, 0x01, 0xfd, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, + 0xff, 0xfd, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07f8, 0x07f4, 0x07f0, 0x0202, 0x0203, 0x0204), + }, + { .freq = 5100, + RADIOREGS3(0xd6, 0x01, 0x01, 0x01, 0xfe, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfd, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x07fc, 0x07f8, 0x07f4, 0x0201, 0x0202, 0x0203), + }, + { .freq = 5110, + RADIOREGS3(0xd6, 0x01, 0x01, 0x01, 0xff, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfc, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x0800, 0x07fc, 0x07f8, 0x0200, 0x0201, 0x0202), + }, + { .freq = 5120, + RADIOREGS3(0xce, 0x01, 0x01, 0x02, 0x00, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfc, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x0804, 0x0800, 0x07fc, 0x01ff, 0x0200, 0x0201), + }, + { .freq = 5130, + RADIOREGS3(0xce, 0x01, 0x01, 0x02, 0x01, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfc, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x0808, 0x0804, 0x0800, 0x01fe, 0x01ff, 0x0200), + }, + { .freq = 5140, + RADIOREGS3(0xc6, 0x01, 0x01, 0x02, 0x02, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfb, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0xfb, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00), + PHYREGS(0x080c, 0x0808, 0x0804, 0x01fd, 0x01fe, 0x01ff), + }, + { .freq = 5160, + RADIOREGS3(0xbe, 0x01, 0x01, 0x02, 0x04, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfa, 0x00, 0x07, 0x00, 0x77, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0xfa, 0x00, 0x07, 0x00, 0x77, + 0x00, 0x0e, 0x00, 0x6f, 0x00), + PHYREGS(0x0814, 0x0810, 0x080c, 0x01fb, 0x01fc, 0x01fd), + }, + { .freq = 5170, + RADIOREGS3(0xbe, 0x01, 0x01, 0x02, 0x05, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xfa, 0x00, 0x07, 0x00, 0x77, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0xfa, 0x00, 0x07, 0x00, 0x77, + 0x00, 0x0e, 0x00, 0x6f, 0x00), + PHYREGS(0x0818, 0x0814, 0x0810, 0x01fa, 0x01fb, 0x01fc), + }, + { .freq = 5180, + RADIOREGS3(0xb6, 0x01, 0x01, 0x02, 0x06, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xf9, 0x00, 0x06, 0x00, 0x77, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x06, 0x00, 0x77, + 0x00, 0x0e, 0x00, 0x6f, 0x00), + PHYREGS(0x081c, 0x0818, 0x0814, 0x01f9, 0x01fa, 0x01fb), + }, + { .freq = 5190, + RADIOREGS3(0xb6, 0x01, 0x01, 0x02, 0x07, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xf9, 0x00, 0x06, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x06, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x0820, 0x081c, 0x0818, 0x01f8, 0x01f9, 0x01fa), + }, + { .freq = 5200, + RADIOREGS3(0xaf, 0x01, 0x01, 0x02, 0x08, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xf9, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x0824, 0x0820, 0x081c, 0x01f7, 0x01f8, 0x01f9), + }, + { .freq = 5210, + RADIOREGS3(0xaf, 0x01, 0x01, 0x02, 0x09, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, + 0xff, 0xf9, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x0828, 0x0824, 0x0820, 0x01f6, 0x01f7, 0x01f8), + }, + { .freq = 5220, + RADIOREGS3(0xa7, 0x01, 0x01, 0x02, 0x0a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, + 0xfe, 0xd8, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0xd8, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x082c, 0x0828, 0x0824, 0x01f5, 0x01f6, 0x01f7), + }, + { .freq = 5230, + RADIOREGS3(0xa7, 0x01, 0x01, 0x02, 0x0b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, + 0xee, 0xd8, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0xd8, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x0830, 0x082c, 0x0828, 0x01f4, 0x01f5, 0x01f6), + }, + { .freq = 5240, + RADIOREGS3(0xa0, 0x01, 0x01, 0x02, 0x0c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, + 0xee, 0xc8, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0xc8, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x0834, 0x0830, 0x082c, 0x01f3, 0x01f4, 0x01f5), + }, + { .freq = 5250, + RADIOREGS3(0xa0, 0x01, 0x01, 0x02, 0x0d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, + 0xed, 0xc7, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0xc7, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x0838, 0x0834, 0x0830, 0x01f2, 0x01f3, 0x01f4), + }, + { .freq = 5260, + RADIOREGS3(0x98, 0x01, 0x01, 0x02, 0x0e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x02, 0x02, 0x02, 0x8e, 0x0e, 0x00, + 0xed, 0xc7, 0x00, 0x04, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0xc7, 0x00, 0x04, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00), + PHYREGS(0x083c, 0x0838, 0x0834, 0x01f1, 0x01f2, 0x01f3), + }, + { .freq = 5270, + RADIOREGS3(0x98, 0x01, 0x01, 0x02, 0x0f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8e, 0x0e, 0x00, + 0xed, 0xc7, 0x00, 0x04, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0xc7, 0x00, 0x04, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x0840, 0x083c, 0x0838, 0x01f0, 0x01f1, 0x01f2), + }, + { .freq = 5280, + RADIOREGS3(0x91, 0x01, 0x01, 0x02, 0x10, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, + 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x0844, 0x0840, 0x083c, 0x01f0, 0x01f0, 0x01f1), + }, + { .freq = 5290, + RADIOREGS3(0x91, 0x01, 0x01, 0x02, 0x11, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, + 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x0848, 0x0844, 0x0840, 0x01ef, 0x01f0, 0x01f0), + }, + { .freq = 5300, + RADIOREGS3(0x8a, 0x01, 0x01, 0x02, 0x12, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, + 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x084c, 0x0848, 0x0844, 0x01ee, 0x01ef, 0x01f0), + }, + { .freq = 5310, + RADIOREGS3(0x8a, 0x01, 0x01, 0x02, 0x13, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, + 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x0850, 0x084c, 0x0848, 0x01ed, 0x01ee, 0x01ef), + }, + { .freq = 5320, + RADIOREGS3(0x83, 0x01, 0x01, 0x02, 0x14, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, + 0xdb, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00), + PHYREGS(0x0854, 0x0850, 0x084c, 0x01ec, 0x01ed, 0x01ee), + }, + { .freq = 5330, + RADIOREGS3(0x83, 0x01, 0x01, 0x02, 0x15, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xcb, 0xa6, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x0858, 0x0854, 0x0850, 0x01eb, 0x01ec, 0x01ed), + }, + { .freq = 5340, + RADIOREGS3(0x7c, 0x01, 0x01, 0x02, 0x16, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, + 0xca, 0xa6, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x085c, 0x0858, 0x0854, 0x01ea, 0x01eb, 0x01ec), + }, + { .freq = 5350, + RADIOREGS3(0x7c, 0x01, 0x01, 0x02, 0x17, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, + 0xca, 0xa6, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0b, 0x00, 0x6f, 0x00), + PHYREGS(0x0860, 0x085c, 0x0858, 0x01e9, 0x01ea, 0x01eb), + }, + { .freq = 5360, + RADIOREGS3(0x75, 0x01, 0x01, 0x02, 0x18, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, + 0xc9, 0x95, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x95, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x0864, 0x0860, 0x085c, 0x01e8, 0x01e9, 0x01ea), + }, + { .freq = 5370, + RADIOREGS3(0x75, 0x01, 0x01, 0x02, 0x19, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, + 0xc9, 0x95, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x95, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x0868, 0x0864, 0x0860, 0x01e7, 0x01e8, 0x01e9), + }, + { .freq = 5380, + RADIOREGS3(0x6e, 0x01, 0x01, 0x02, 0x1a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, + 0xb8, 0x95, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x95, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x086c, 0x0868, 0x0864, 0x01e6, 0x01e7, 0x01e8), + }, + { .freq = 5390, + RADIOREGS3(0x6e, 0x01, 0x01, 0x02, 0x1b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, + 0xb8, 0x84, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x84, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x0870, 0x086c, 0x0868, 0x01e5, 0x01e6, 0x01e7), + }, + { .freq = 5400, + RADIOREGS3(0x67, 0x01, 0x01, 0x02, 0x1c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, + 0xb8, 0x84, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x84, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x0874, 0x0870, 0x086c, 0x01e5, 0x01e5, 0x01e6), + }, + { .freq = 5410, + RADIOREGS3(0x67, 0x01, 0x01, 0x02, 0x1d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, + 0xb7, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x0878, 0x0874, 0x0870, 0x01e4, 0x01e5, 0x01e5), + }, + { .freq = 5420, + RADIOREGS3(0x61, 0x01, 0x01, 0x02, 0x1e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, + 0xa7, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x087c, 0x0878, 0x0874, 0x01e3, 0x01e4, 0x01e5), + }, + { .freq = 5430, + RADIOREGS3(0x61, 0x01, 0x01, 0x02, 0x1f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x03, 0x03, 0x03, 0x8c, 0x0b, 0x00, + 0xa6, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00), + PHYREGS(0x0880, 0x087c, 0x0878, 0x01e2, 0x01e3, 0x01e4), + }, + { .freq = 5440, + RADIOREGS3(0x5a, 0x01, 0x01, 0x02, 0x20, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, + 0xa6, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x0884, 0x0880, 0x087c, 0x01e1, 0x01e2, 0x01e3), + }, + { .freq = 5450, + RADIOREGS3(0x5a, 0x01, 0x01, 0x02, 0x21, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, + 0x95, 0x84, 0x00, 0x01, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x84, 0x00, 0x01, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x0888, 0x0884, 0x0880, 0x01e0, 0x01e1, 0x01e2), + }, + { .freq = 5460, + RADIOREGS3(0x53, 0x01, 0x01, 0x02, 0x22, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, + 0x95, 0x84, 0x00, 0x01, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x84, 0x00, 0x01, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x088c, 0x0888, 0x0884, 0x01df, 0x01e0, 0x01e1), + }, + { .freq = 5470, + RADIOREGS3(0x53, 0x01, 0x01, 0x02, 0x23, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, + 0x94, 0x73, 0x00, 0x01, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x01, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x0890, 0x088c, 0x0888, 0x01de, 0x01df, 0x01e0), + }, + { .freq = 5480, + RADIOREGS3(0x4d, 0x01, 0x01, 0x02, 0x24, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, + 0x84, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x0894, 0x0890, 0x088c, 0x01dd, 0x01de, 0x01df), + }, + { .freq = 5490, + RADIOREGS3(0x4d, 0x01, 0x01, 0x02, 0x25, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, + 0x83, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x0898, 0x0894, 0x0890, 0x01dd, 0x01dd, 0x01de), + }, + { .freq = 5500, + RADIOREGS3(0x47, 0x01, 0x01, 0x02, 0x26, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, + 0x82, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x089c, 0x0898, 0x0894, 0x01dc, 0x01dd, 0x01dd), + }, + { .freq = 5510, + RADIOREGS3(0x47, 0x01, 0x01, 0x02, 0x27, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, + 0x82, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x08a0, 0x089c, 0x0898, 0x01db, 0x01dc, 0x01dd), + }, + { .freq = 5520, + RADIOREGS3(0x40, 0x01, 0x01, 0x02, 0x28, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, + 0x72, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x08a4, 0x08a0, 0x089c, 0x01da, 0x01db, 0x01dc), + }, + { .freq = 5530, + RADIOREGS3(0x40, 0x01, 0x01, 0x02, 0x29, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x09, 0x00, + 0x72, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x08a8, 0x08a4, 0x08a0, 0x01d9, 0x01da, 0x01db), + }, + { .freq = 5540, + RADIOREGS3(0x3a, 0x01, 0x01, 0x02, 0x2a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x8a, 0x09, 0x00, + 0x71, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x08ac, 0x08a8, 0x08a4, 0x01d8, 0x01d9, 0x01da), + }, + { .freq = 5550, + RADIOREGS3(0x3a, 0x01, 0x01, 0x02, 0x2b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, + 0x61, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x08b0, 0x08ac, 0x08a8, 0x01d7, 0x01d8, 0x01d9), + }, + { .freq = 5560, + RADIOREGS3(0x34, 0x01, 0x01, 0x02, 0x2c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, + 0x61, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x08b4, 0x08b0, 0x08ac, 0x01d7, 0x01d7, 0x01d8), + }, + { .freq = 5570, + RADIOREGS3(0x34, 0x01, 0x01, 0x02, 0x2d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, + 0x61, 0x62, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x62, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00), + PHYREGS(0x08b8, 0x08b4, 0x08b0, 0x01d6, 0x01d7, 0x01d7), + }, + { .freq = 5580, + RADIOREGS3(0x2e, 0x01, 0x01, 0x02, 0x2e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x89, 0x08, 0x00, + 0x60, 0x62, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x62, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00), + PHYREGS(0x08bc, 0x08b8, 0x08b4, 0x01d5, 0x01d6, 0x01d7), + }, + { .freq = 5590, + RADIOREGS3(0x2e, 0x01, 0x01, 0x02, 0x2f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x04, 0x04, 0x04, 0x89, 0x08, 0x00, + 0x50, 0x61, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x61, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00), + PHYREGS(0x08c0, 0x08bc, 0x08b8, 0x01d4, 0x01d5, 0x01d6), + }, + { .freq = 5600, + RADIOREGS3(0x28, 0x01, 0x01, 0x02, 0x30, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, + 0x50, 0x51, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x51, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00), + PHYREGS(0x08c4, 0x08c0, 0x08bc, 0x01d3, 0x01d4, 0x01d5), + }, + { .freq = 5610, + RADIOREGS3(0x28, 0x01, 0x01, 0x02, 0x31, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, + 0x50, 0x51, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x51, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00), + PHYREGS(0x08c8, 0x08c4, 0x08c0, 0x01d2, 0x01d3, 0x01d4), + }, + { .freq = 5620, + RADIOREGS3(0x21, 0x01, 0x01, 0x02, 0x32, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, + 0x50, 0x50, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x50, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00), + PHYREGS(0x08cc, 0x08c8, 0x08c4, 0x01d2, 0x01d2, 0x01d3), + }, + { .freq = 5630, + RADIOREGS3(0x21, 0x01, 0x01, 0x02, 0x33, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, + 0x50, 0x50, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x50, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00), + PHYREGS(0x08d0, 0x08cc, 0x08c8, 0x01d1, 0x01d2, 0x01d2), + }, + { .freq = 5640, + RADIOREGS3(0x1c, 0x01, 0x01, 0x02, 0x34, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, + 0x40, 0x50, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x50, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00), + PHYREGS(0x08d4, 0x08d0, 0x08cc, 0x01d0, 0x01d1, 0x01d2), + }, + { .freq = 5650, + RADIOREGS3(0x1c, 0x01, 0x01, 0x02, 0x35, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, + 0x40, 0x40, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x40, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00), + PHYREGS(0x08d8, 0x08d4, 0x08d0, 0x01cf, 0x01d0, 0x01d1), + }, + { .freq = 5660, + RADIOREGS3(0x16, 0x01, 0x01, 0x02, 0x36, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, + 0x40, 0x40, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x40, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00), + PHYREGS(0x08dc, 0x08d8, 0x08d4, 0x01ce, 0x01cf, 0x01d0), + }, + { .freq = 5670, + RADIOREGS3(0x16, 0x01, 0x01, 0x02, 0x37, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, + 0x40, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00), + PHYREGS(0x08e0, 0x08dc, 0x08d8, 0x01ce, 0x01ce, 0x01cf), + }, + { .freq = 5680, + RADIOREGS3(0x10, 0x01, 0x01, 0x02, 0x38, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00), + PHYREGS(0x08e4, 0x08e0, 0x08dc, 0x01cd, 0x01ce, 0x01ce), + }, + { .freq = 5690, + RADIOREGS3(0x10, 0x01, 0x01, 0x02, 0x39, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00), + PHYREGS(0x08e8, 0x08e4, 0x08e0, 0x01cc, 0x01cd, 0x01ce), + }, + { .freq = 5700, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00), + PHYREGS(0x08ec, 0x08e8, 0x08e4, 0x01cb, 0x01cc, 0x01cd), + }, + { .freq = 5710, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00), + PHYREGS(0x08f0, 0x08ec, 0x08e8, 0x01ca, 0x01cb, 0x01cc), + }, + { .freq = 5720, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00), + PHYREGS(0x08f4, 0x08f0, 0x08ec, 0x01c9, 0x01ca, 0x01cb), + }, + { .freq = 5725, + RADIOREGS3(0x03, 0x01, 0x02, 0x04, 0x79, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, + 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00), + PHYREGS(0x08f6, 0x08f2, 0x08ee, 0x01c9, 0x01ca, 0x01cb), + }, + { .freq = 5730, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00), + PHYREGS(0x08f8, 0x08f4, 0x08f0, 0x01c9, 0x01c9, 0x01ca), + }, + { .freq = 5735, + RADIOREGS3(0x03, 0x01, 0x02, 0x04, 0x7b, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6d, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6d, 0x00), + PHYREGS(0x08fa, 0x08f6, 0x08f2, 0x01c8, 0x01c9, 0x01ca), + }, + { .freq = 5740, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6d, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6d, 0x00), + PHYREGS(0x08fc, 0x08f8, 0x08f4, 0x01c8, 0x01c9, 0x01c9), + }, + { .freq = 5745, + RADIOREGS3(0xfe, 0x00, 0x02, 0x04, 0x7d, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6d, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6d, 0x00), + PHYREGS(0x08fe, 0x08fa, 0x08f6, 0x01c8, 0x01c8, 0x01c9), + }, + { .freq = 5750, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x3f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x20, 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6d, 0x00, 0x20, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6d, 0x00), + PHYREGS(0x0900, 0x08fc, 0x08f8, 0x01c7, 0x01c8, 0x01c9), + }, + { .freq = 5755, + RADIOREGS3(0xfe, 0x00, 0x02, 0x04, 0x7f, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, + 0x10, 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6c, 0x00, 0x20, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6c, 0x00), + PHYREGS(0x0902, 0x08fe, 0x08fa, 0x01c7, 0x01c8, 0x01c8), + }, + { .freq = 5760, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x40, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x86, 0x05, 0x00, + 0x10, 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6c, 0x00, 0x20, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6c, 0x00), + PHYREGS(0x0904, 0x0900, 0x08fc, 0x01c6, 0x01c7, 0x01c8), + }, + { .freq = 5765, + RADIOREGS3(0xf8, 0x00, 0x02, 0x04, 0x81, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x86, 0x05, 0x00, + 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6c, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6c, 0x00), + PHYREGS(0x0906, 0x0902, 0x08fe, 0x01c6, 0x01c7, 0x01c8), + }, + { .freq = 5770, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x41, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, + 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00), + PHYREGS(0x0908, 0x0904, 0x0900, 0x01c6, 0x01c6, 0x01c7), + }, + { .freq = 5775, + RADIOREGS3(0xf8, 0x00, 0x02, 0x04, 0x83, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, + 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00), + PHYREGS(0x090a, 0x0906, 0x0902, 0x01c5, 0x01c6, 0x01c7), + }, + { .freq = 5780, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x42, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, + 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00), + PHYREGS(0x090c, 0x0908, 0x0904, 0x01c5, 0x01c6, 0x01c6), + }, + { .freq = 5785, + RADIOREGS3(0xf2, 0x00, 0x02, 0x04, 0x85, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00), + PHYREGS(0x090e, 0x090a, 0x0906, 0x01c4, 0x01c5, 0x01c6), + }, + { .freq = 5790, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x43, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00), + PHYREGS(0x0910, 0x090c, 0x0908, 0x01c4, 0x01c5, 0x01c6), + }, + { .freq = 5795, + RADIOREGS3(0xf2, 0x00, 0x02, 0x04, 0x87, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00), + PHYREGS(0x0912, 0x090e, 0x090a, 0x01c4, 0x01c4, 0x01c5), + }, + { .freq = 5800, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x44, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00), + PHYREGS(0x0914, 0x0910, 0x090c, 0x01c3, 0x01c4, 0x01c5), + }, + { .freq = 5805, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x89, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00), + PHYREGS(0x0916, 0x0912, 0x090e, 0x01c3, 0x01c4, 0x01c4), + }, + { .freq = 5810, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x45, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00), + PHYREGS(0x0918, 0x0914, 0x0910, 0x01c2, 0x01c3, 0x01c4), + }, + { .freq = 5815, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x8b, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00), + PHYREGS(0x091a, 0x0916, 0x0912, 0x01c2, 0x01c3, 0x01c4), + }, + { .freq = 5820, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x46, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00), + PHYREGS(0x091c, 0x0918, 0x0914, 0x01c2, 0x01c2, 0x01c3), + }, + { .freq = 5825, + RADIOREGS3(0xed, 0x00, 0x02, 0x04, 0x8d, 0x07, 0x07, 0x04, + 0x10, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x69, 0x00), + PHYREGS(0x091e, 0x091a, 0x0916, 0x01c1, 0x01c2, 0x01c3), + }, + { .freq = 5830, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x47, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x69, 0x00), + PHYREGS(0x0920, 0x091c, 0x0918, 0x01c1, 0x01c2, 0x01c2), + }, + { .freq = 5840, + RADIOREGS3(0x0a, 0x01, 0x01, 0x02, 0x48, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x69, 0x00), + PHYREGS(0x0924, 0x0920, 0x091c, 0x01c0, 0x01c1, 0x01c2), + }, + { .freq = 5850, + RADIOREGS3(0xe0, 0x00, 0x01, 0x02, 0x49, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x69, 0x00), + PHYREGS(0x0928, 0x0924, 0x0920, 0x01bf, 0x01c0, 0x01c1), + }, + { .freq = 5860, + RADIOREGS3(0xde, 0x00, 0x01, 0x02, 0x4a, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x69, 0x00), + PHYREGS(0x092c, 0x0928, 0x0924, 0x01bf, 0x01bf, 0x01c0), + }, + { .freq = 5870, + RADIOREGS3(0xdb, 0x00, 0x01, 0x02, 0x4b, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00), + PHYREGS(0x0930, 0x092c, 0x0928, 0x01be, 0x01bf, 0x01bf), + }, + { .freq = 5880, + RADIOREGS3(0xd8, 0x00, 0x01, 0x02, 0x4c, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00), + PHYREGS(0x0934, 0x0930, 0x092c, 0x01bd, 0x01be, 0x01bf), + }, + { .freq = 5890, + RADIOREGS3(0xd6, 0x00, 0x01, 0x02, 0x4d, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00), + PHYREGS(0x0938, 0x0934, 0x0930, 0x01bc, 0x01bd, 0x01be), + }, + { .freq = 5900, + RADIOREGS3(0xd3, 0x00, 0x01, 0x02, 0x4e, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00), + PHYREGS(0x093c, 0x0938, 0x0934, 0x01bc, 0x01bc, 0x01bd), + }, + { .freq = 5910, + RADIOREGS3(0xd6, 0x00, 0x01, 0x02, 0x4f, 0x05, 0x05, 0x04, + 0x0c, 0x01, 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00), + PHYREGS(0x0940, 0x093c, 0x0938, 0x01bb, 0x01bc, 0x01bc), + }, + { .freq = 2412, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x6c, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x04, 0x04, 0x04, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x78, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0b, 0x00, 0x0a, 0x00, 0x89, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0b, 0x00, 0x0a), + PHYREGS(0x03c9, 0x03c5, 0x03c1, 0x043a, 0x043f, 0x0443), + }, + { .freq = 2417, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x71, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x78, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0b, 0x00, 0x0a, 0x00, 0x89, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0b, 0x00, 0x0a), + PHYREGS(0x03cb, 0x03c7, 0x03c3, 0x0438, 0x043d, 0x0441), + }, + { .freq = 2422, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x76, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x67, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0b, 0x00, 0x0a, 0x00, 0x89, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0b, 0x00, 0x0a), + PHYREGS(0x03cd, 0x03c9, 0x03c5, 0x0436, 0x043a, 0x043f), + }, + { .freq = 2427, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x7b, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x57, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x00, 0x78, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a), + PHYREGS(0x03cf, 0x03cb, 0x03c7, 0x0434, 0x0438, 0x043d), + }, + { .freq = 2432, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x80, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x56, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x00, 0x77, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a), + PHYREGS(0x03d1, 0x03cd, 0x03c9, 0x0431, 0x0436, 0x043a), + }, + { .freq = 2437, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x85, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x46, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x00, 0x76, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a), + PHYREGS(0x03d3, 0x03cf, 0x03cb, 0x042f, 0x0434, 0x0438), + }, + { .freq = 2442, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x8a, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x45, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x00, 0x66, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a), + PHYREGS(0x03d5, 0x03d1, 0x03cd, 0x042d, 0x0431, 0x0436), + }, + { .freq = 2447, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x8f, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x34, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x55, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x09), + PHYREGS(0x03d7, 0x03d3, 0x03cf, 0x042b, 0x042f, 0x0434), + }, + { .freq = 2452, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x94, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x23, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x45, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x09), + PHYREGS(0x03d9, 0x03d5, 0x03d1, 0x0429, 0x042d, 0x0431), + }, + { .freq = 2457, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x99, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x12, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x09, 0x00, 0x34, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x09), + PHYREGS(0x03db, 0x03d7, 0x03d3, 0x0427, 0x042b, 0x042f), + }, + { .freq = 2462, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0x9e, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x33, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09), + PHYREGS(0x03dd, 0x03d9, 0x03d5, 0x0424, 0x0429, 0x042d), + }, + { .freq = 2467, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0xa3, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x22, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09), + PHYREGS(0x03df, 0x03db, 0x03d7, 0x0422, 0x0427, 0x042b), + }, + { .freq = 2472, + RADIOREGS3(0x00, 0x01, 0x03, 0x09, 0xa8, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x11, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09), + PHYREGS(0x03e1, 0x03dd, 0x03d9, 0x0420, 0x0424, 0x0429), + }, + { .freq = 2484, + RADIOREGS3(0xff, 0x01, 0x03, 0x09, 0xb4, 0x08, 0x08, 0x04, + 0x16, 0x01, 0x07, 0x07, 0x07, 0x8f, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09), + PHYREGS(0x03e6, 0x03e2, 0x03de, 0x041b, 0x041f, 0x0424), + }, +}; + +static void b2056_upload_inittab(struct b43_wldev *dev, bool ghz5, + bool ignore_uploadflag, u16 routing, + const struct b2056_inittab_entry *e, + unsigned int length) +{ + unsigned int i; + u16 value; + + for (i = 0; i < length; i++, e++) { + if (!(e->flags & B2056_INITTAB_ENTRY_OK)) + continue; + if ((e->flags & B2056_INITTAB_UPLOAD) || ignore_uploadflag) { + if (ghz5) + value = e->ghz5; + else + value = e->ghz2; + b43_radio_write(dev, routing | i, value); + } + } +} + +void b2056_upload_inittabs(struct b43_wldev *dev, + bool ghz5, bool ignore_uploadflag) +{ + const struct b2056_inittabs_pts *pts; + + if (dev->phy.rev >= ARRAY_SIZE(b2056_inittabs)) { + B43_WARN_ON(1); + return; + } + pts = &b2056_inittabs[dev->phy.rev]; + + b2056_upload_inittab(dev, ghz5, ignore_uploadflag, + B2056_SYN, pts->syn, pts->syn_length); + b2056_upload_inittab(dev, ghz5, ignore_uploadflag, + B2056_TX0, pts->tx, pts->tx_length); + b2056_upload_inittab(dev, ghz5, ignore_uploadflag, + B2056_TX1, pts->tx, pts->tx_length); + b2056_upload_inittab(dev, ghz5, ignore_uploadflag, + B2056_RX0, pts->rx, pts->rx_length); + b2056_upload_inittab(dev, ghz5, ignore_uploadflag, + B2056_RX1, pts->rx, pts->rx_length); +} + +void b2056_upload_syn_pll_cp2(struct b43_wldev *dev, bool ghz5) +{ + const struct b2056_inittabs_pts *pts; + const struct b2056_inittab_entry *e; + + if (dev->phy.rev >= ARRAY_SIZE(b2056_inittabs)) { + B43_WARN_ON(1); + return; + } + pts = &b2056_inittabs[dev->phy.rev]; + e = &pts->syn[B2056_SYN_PLL_CP2]; + + b43_radio_write(dev, B2056_SYN_PLL_CP2, ghz5 ? e->ghz5 : e->ghz2); +} + +const struct b43_nphy_channeltab_entry_rev3 * +b43_nphy_get_chantabent_rev3(struct b43_wldev *dev, u16 freq) +{ + const struct b43_nphy_channeltab_entry_rev3 *e; + unsigned int length, i; + + switch (dev->phy.rev) { + case 3: + e = b43_nphy_channeltab_rev3; + length = ARRAY_SIZE(b43_nphy_channeltab_rev3); + break; + case 4: + e = b43_nphy_channeltab_rev4; + length = ARRAY_SIZE(b43_nphy_channeltab_rev4); + break; + case 5: + e = b43_nphy_channeltab_rev5; + length = ARRAY_SIZE(b43_nphy_channeltab_rev5); + break; + case 6: + e = b43_nphy_channeltab_rev6; + length = ARRAY_SIZE(b43_nphy_channeltab_rev6); + break; + case 7: + case 9: + e = b43_nphy_channeltab_rev7_9; + length = ARRAY_SIZE(b43_nphy_channeltab_rev7_9); + break; + case 8: + e = b43_nphy_channeltab_rev8; + length = ARRAY_SIZE(b43_nphy_channeltab_rev8); + break; + default: + B43_WARN_ON(1); + return NULL; + } + + for (i = 0; i < length; i++, e++) { + if (e->freq == freq) + return e; + } + + return NULL; +} diff --git a/drivers/net/wireless/b43/radio_2056.h b/drivers/net/wireless/b43/radio_2056.h new file mode 100644 index 0000000..5b86673 --- /dev/null +++ b/drivers/net/wireless/b43/radio_2056.h @@ -0,0 +1,1100 @@ +#ifndef B43_RADIO_2056_H_ +#define B43_RADIO_2056_H_ + +#include + +#include "tables_nphy.h" + +#define B2056_SYN (0x0 << 12) +#define B2056_TX0 (0x2 << 12) +#define B2056_TX1 (0x3 << 12) +#define B2056_RX0 (0x6 << 12) +#define B2056_RX1 (0x7 << 12) +#define B2056_ALLTX (0xE << 12) +#define B2056_ALLRX (0xF << 12) + +#define B2056_SYN_RESERVED_ADDR0 0x00 +#define B2056_SYN_IDCODE 0x01 +#define B2056_SYN_RESERVED_ADDR2 0x02 +#define B2056_SYN_RESERVED_ADDR3 0x03 +#define B2056_SYN_RESERVED_ADDR4 0x04 +#define B2056_SYN_RESERVED_ADDR5 0x05 +#define B2056_SYN_RESERVED_ADDR6 0x06 +#define B2056_SYN_RESERVED_ADDR7 0x07 +#define B2056_SYN_COM_CTRL 0x08 +#define B2056_SYN_COM_PU 0x09 +#define B2056_SYN_COM_OVR 0x0A +#define B2056_SYN_COM_RESET 0x0B +#define B2056_SYN_COM_RCAL 0x0C +#define B2056_SYN_COM_RC_RXLPF 0x0D +#define B2056_SYN_COM_RC_TXLPF 0x0E +#define B2056_SYN_COM_RC_RXHPF 0x0F +#define B2056_SYN_RESERVED_ADDR16 0x10 +#define B2056_SYN_RESERVED_ADDR17 0x11 +#define B2056_SYN_RESERVED_ADDR18 0x12 +#define B2056_SYN_RESERVED_ADDR19 0x13 +#define B2056_SYN_RESERVED_ADDR20 0x14 +#define B2056_SYN_RESERVED_ADDR21 0x15 +#define B2056_SYN_RESERVED_ADDR22 0x16 +#define B2056_SYN_RESERVED_ADDR23 0x17 +#define B2056_SYN_RESERVED_ADDR24 0x18 +#define B2056_SYN_RESERVED_ADDR25 0x19 +#define B2056_SYN_RESERVED_ADDR26 0x1A +#define B2056_SYN_RESERVED_ADDR27 0x1B +#define B2056_SYN_RESERVED_ADDR28 0x1C +#define B2056_SYN_RESERVED_ADDR29 0x1D +#define B2056_SYN_RESERVED_ADDR30 0x1E +#define B2056_SYN_RESERVED_ADDR31 0x1F +#define B2056_SYN_GPIO_MASTER1 0x20 +#define B2056_SYN_GPIO_MASTER2 0x21 +#define B2056_SYN_TOPBIAS_MASTER 0x22 +#define B2056_SYN_TOPBIAS_RCAL 0x23 +#define B2056_SYN_AFEREG 0x24 +#define B2056_SYN_TEMPPROCSENSE 0x25 +#define B2056_SYN_TEMPPROCSENSEIDAC 0x26 +#define B2056_SYN_TEMPPROCSENSERCAL 0x27 +#define B2056_SYN_LPO 0x28 +#define B2056_SYN_VDDCAL_MASTER 0x29 +#define B2056_SYN_VDDCAL_IDAC 0x2A +#define B2056_SYN_VDDCAL_STATUS 0x2B +#define B2056_SYN_RCAL_MASTER 0x2C +#define B2056_SYN_RCAL_CODE_OUT 0x2D +#define B2056_SYN_RCCAL_CTRL0 0x2E +#define B2056_SYN_RCCAL_CTRL1 0x2F +#define B2056_SYN_RCCAL_CTRL2 0x30 +#define B2056_SYN_RCCAL_CTRL3 0x31 +#define B2056_SYN_RCCAL_CTRL4 0x32 +#define B2056_SYN_RCCAL_CTRL5 0x33 +#define B2056_SYN_RCCAL_CTRL6 0x34 +#define B2056_SYN_RCCAL_CTRL7 0x35 +#define B2056_SYN_RCCAL_CTRL8 0x36 +#define B2056_SYN_RCCAL_CTRL9 0x37 +#define B2056_SYN_RCCAL_CTRL10 0x38 +#define B2056_SYN_RCCAL_CTRL11 0x39 +#define B2056_SYN_ZCAL_SPARE1 0x3A +#define B2056_SYN_ZCAL_SPARE2 0x3B +#define B2056_SYN_PLL_MAST1 0x3C +#define B2056_SYN_PLL_MAST2 0x3D +#define B2056_SYN_PLL_MAST3 0x3E +#define B2056_SYN_PLL_BIAS_RESET 0x3F +#define B2056_SYN_PLL_XTAL0 0x40 +#define B2056_SYN_PLL_XTAL1 0x41 +#define B2056_SYN_PLL_XTAL3 0x42 +#define B2056_SYN_PLL_XTAL4 0x43 +#define B2056_SYN_PLL_XTAL5 0x44 +#define B2056_SYN_PLL_XTAL6 0x45 +#define B2056_SYN_PLL_REFDIV 0x46 +#define B2056_SYN_PLL_PFD 0x47 +#define B2056_SYN_PLL_CP1 0x48 +#define B2056_SYN_PLL_CP2 0x49 +#define B2056_SYN_PLL_CP3 0x4A +#define B2056_SYN_PLL_LOOPFILTER1 0x4B +#define B2056_SYN_PLL_LOOPFILTER2 0x4C +#define B2056_SYN_PLL_LOOPFILTER3 0x4D +#define B2056_SYN_PLL_LOOPFILTER4 0x4E +#define B2056_SYN_PLL_LOOPFILTER5 0x4F +#define B2056_SYN_PLL_MMD1 0x50 +#define B2056_SYN_PLL_MMD2 0x51 +#define B2056_SYN_PLL_VCO1 0x52 +#define B2056_SYN_PLL_VCO2 0x53 +#define B2056_SYN_PLL_MONITOR1 0x54 +#define B2056_SYN_PLL_MONITOR2 0x55 +#define B2056_SYN_PLL_VCOCAL1 0x56 +#define B2056_SYN_PLL_VCOCAL2 0x57 +#define B2056_SYN_PLL_VCOCAL4 0x58 +#define B2056_SYN_PLL_VCOCAL5 0x59 +#define B2056_SYN_PLL_VCOCAL6 0x5A +#define B2056_SYN_PLL_VCOCAL7 0x5B +#define B2056_SYN_PLL_VCOCAL8 0x5C +#define B2056_SYN_PLL_VCOCAL9 0x5D +#define B2056_SYN_PLL_VCOCAL10 0x5E +#define B2056_SYN_PLL_VCOCAL11 0x5F +#define B2056_SYN_PLL_VCOCAL12 0x60 +#define B2056_SYN_PLL_VCOCAL13 0x61 +#define B2056_SYN_PLL_VREG 0x62 +#define B2056_SYN_PLL_STATUS1 0x63 +#define B2056_SYN_PLL_STATUS2 0x64 +#define B2056_SYN_PLL_STATUS3 0x65 +#define B2056_SYN_LOGEN_PU0 0x66 +#define B2056_SYN_LOGEN_PU1 0x67 +#define B2056_SYN_LOGEN_PU2 0x68 +#define B2056_SYN_LOGEN_PU3 0x69 +#define B2056_SYN_LOGEN_PU5 0x6A +#define B2056_SYN_LOGEN_PU6 0x6B +#define B2056_SYN_LOGEN_PU7 0x6C +#define B2056_SYN_LOGEN_PU8 0x6D +#define B2056_SYN_LOGEN_BIAS_RESET 0x6E +#define B2056_SYN_LOGEN_RCCR1 0x6F +#define B2056_SYN_LOGEN_VCOBUF1 0x70 +#define B2056_SYN_LOGEN_MIXER1 0x71 +#define B2056_SYN_LOGEN_MIXER2 0x72 +#define B2056_SYN_LOGEN_BUF1 0x73 +#define B2056_SYN_LOGENBUF2 0x74 +#define B2056_SYN_LOGEN_BUF3 0x75 +#define B2056_SYN_LOGEN_BUF4 0x76 +#define B2056_SYN_LOGEN_DIV1 0x77 +#define B2056_SYN_LOGEN_DIV2 0x78 +#define B2056_SYN_LOGEN_DIV3 0x79 +#define B2056_SYN_LOGEN_ACL1 0x7A +#define B2056_SYN_LOGEN_ACL2 0x7B +#define B2056_SYN_LOGEN_ACL3 0x7C +#define B2056_SYN_LOGEN_ACL4 0x7D +#define B2056_SYN_LOGEN_ACL5 0x7E +#define B2056_SYN_LOGEN_ACL6 0x7F +#define B2056_SYN_LOGEN_ACLOUT 0x80 +#define B2056_SYN_LOGEN_ACLCAL1 0x81 +#define B2056_SYN_LOGEN_ACLCAL2 0x82 +#define B2056_SYN_LOGEN_ACLCAL3 0x83 +#define B2056_SYN_CALEN 0x84 +#define B2056_SYN_LOGEN_PEAKDET1 0x85 +#define B2056_SYN_LOGEN_CORE_ACL_OVR 0x86 +#define B2056_SYN_LOGEN_RX_DIFF_ACL_OVR 0x87 +#define B2056_SYN_LOGEN_TX_DIFF_ACL_OVR 0x88 +#define B2056_SYN_LOGEN_RX_CMOS_ACL_OVR 0x89 +#define B2056_SYN_LOGEN_TX_CMOS_ACL_OVR 0x8A +#define B2056_SYN_LOGEN_VCOBUF2 0x8B +#define B2056_SYN_LOGEN_MIXER3 0x8C +#define B2056_SYN_LOGEN_BUF5 0x8D +#define B2056_SYN_LOGEN_BUF6 0x8E +#define B2056_SYN_LOGEN_CBUFRX1 0x8F +#define B2056_SYN_LOGEN_CBUFRX2 0x90 +#define B2056_SYN_LOGEN_CBUFRX3 0x91 +#define B2056_SYN_LOGEN_CBUFRX4 0x92 +#define B2056_SYN_LOGEN_CBUFTX1 0x93 +#define B2056_SYN_LOGEN_CBUFTX2 0x94 +#define B2056_SYN_LOGEN_CBUFTX3 0x95 +#define B2056_SYN_LOGEN_CBUFTX4 0x96 +#define B2056_SYN_LOGEN_CMOSRX1 0x97 +#define B2056_SYN_LOGEN_CMOSRX2 0x98 +#define B2056_SYN_LOGEN_CMOSRX3 0x99 +#define B2056_SYN_LOGEN_CMOSRX4 0x9A +#define B2056_SYN_LOGEN_CMOSTX1 0x9B +#define B2056_SYN_LOGEN_CMOSTX2 0x9C +#define B2056_SYN_LOGEN_CMOSTX3 0x9D +#define B2056_SYN_LOGEN_CMOSTX4 0x9E +#define B2056_SYN_LOGEN_VCOBUF2_OVRVAL 0x9F +#define B2056_SYN_LOGEN_MIXER3_OVRVAL 0xA0 +#define B2056_SYN_LOGEN_BUF5_OVRVAL 0xA1 +#define B2056_SYN_LOGEN_BUF6_OVRVAL 0xA2 +#define B2056_SYN_LOGEN_CBUFRX1_OVRVAL 0xA3 +#define B2056_SYN_LOGEN_CBUFRX2_OVRVAL 0xA4 +#define B2056_SYN_LOGEN_CBUFRX3_OVRVAL 0xA5 +#define B2056_SYN_LOGEN_CBUFRX4_OVRVAL 0xA6 +#define B2056_SYN_LOGEN_CBUFTX1_OVRVAL 0xA7 +#define B2056_SYN_LOGEN_CBUFTX2_OVRVAL 0xA8 +#define B2056_SYN_LOGEN_CBUFTX3_OVRVAL 0xA9 +#define B2056_SYN_LOGEN_CBUFTX4_OVRVAL 0xAA +#define B2056_SYN_LOGEN_CMOSRX1_OVRVAL 0xAB +#define B2056_SYN_LOGEN_CMOSRX2_OVRVAL 0xAC +#define B2056_SYN_LOGEN_CMOSRX3_OVRVAL 0xAD +#define B2056_SYN_LOGEN_CMOSRX4_OVRVAL 0xAE +#define B2056_SYN_LOGEN_CMOSTX1_OVRVAL 0xAF +#define B2056_SYN_LOGEN_CMOSTX2_OVRVAL 0xB0 +#define B2056_SYN_LOGEN_CMOSTX3_OVRVAL 0xB1 +#define B2056_SYN_LOGEN_CMOSTX4_OVRVAL 0xB2 +#define B2056_SYN_LOGEN_ACL_WAITCNT 0xB3 +#define B2056_SYN_LOGEN_CORE_CALVALID 0xB4 +#define B2056_SYN_LOGEN_RX_CMOS_CALVALID 0xB5 +#define B2056_SYN_LOGEN_TX_CMOS_VALID 0xB6 + +#define B2056_TX_RESERVED_ADDR0 0x00 +#define B2056_TX_IDCODE 0x01 +#define B2056_TX_RESERVED_ADDR2 0x02 +#define B2056_TX_RESERVED_ADDR3 0x03 +#define B2056_TX_RESERVED_ADDR4 0x04 +#define B2056_TX_RESERVED_ADDR5 0x05 +#define B2056_TX_RESERVED_ADDR6 0x06 +#define B2056_TX_RESERVED_ADDR7 0x07 +#define B2056_TX_COM_CTRL 0x08 +#define B2056_TX_COM_PU 0x09 +#define B2056_TX_COM_OVR 0x0A +#define B2056_TX_COM_RESET 0x0B +#define B2056_TX_COM_RCAL 0x0C +#define B2056_TX_COM_RC_RXLPF 0x0D +#define B2056_TX_COM_RC_TXLPF 0x0E +#define B2056_TX_COM_RC_RXHPF 0x0F +#define B2056_TX_RESERVED_ADDR16 0x10 +#define B2056_TX_RESERVED_ADDR17 0x11 +#define B2056_TX_RESERVED_ADDR18 0x12 +#define B2056_TX_RESERVED_ADDR19 0x13 +#define B2056_TX_RESERVED_ADDR20 0x14 +#define B2056_TX_RESERVED_ADDR21 0x15 +#define B2056_TX_RESERVED_ADDR22 0x16 +#define B2056_TX_RESERVED_ADDR23 0x17 +#define B2056_TX_RESERVED_ADDR24 0x18 +#define B2056_TX_RESERVED_ADDR25 0x19 +#define B2056_TX_RESERVED_ADDR26 0x1A +#define B2056_TX_RESERVED_ADDR27 0x1B +#define B2056_TX_RESERVED_ADDR28 0x1C +#define B2056_TX_RESERVED_ADDR29 0x1D +#define B2056_TX_RESERVED_ADDR30 0x1E +#define B2056_TX_RESERVED_ADDR31 0x1F +#define B2056_TX_IQCAL_GAIN_BW 0x20 +#define B2056_TX_LOFT_FINE_I 0x21 +#define B2056_TX_LOFT_FINE_Q 0x22 +#define B2056_TX_LOFT_COARSE_I 0x23 +#define B2056_TX_LOFT_COARSE_Q 0x24 +#define B2056_TX_TX_COM_MASTER1 0x25 +#define B2056_TX_TX_COM_MASTER2 0x26 +#define B2056_TX_RXIQCAL_TXMUX 0x27 +#define B2056_TX_TX_SSI_MASTER 0x28 +#define B2056_TX_IQCAL_VCM_HG 0x29 +#define B2056_TX_IQCAL_IDAC 0x2A +#define B2056_TX_TSSI_VCM 0x2B +#define B2056_TX_TX_AMP_DET 0x2C +#define B2056_TX_TX_SSI_MUX 0x2D +#define B2056_TX_TSSIA 0x2E +#define B2056_TX_TSSIG 0x2F +#define B2056_TX_TSSI_MISC1 0x30 +#define B2056_TX_TSSI_MISC2 0x31 +#define B2056_TX_TSSI_MISC3 0x32 +#define B2056_TX_PA_SPARE1 0x33 +#define B2056_TX_PA_SPARE2 0x34 +#define B2056_TX_INTPAA_MASTER 0x35 +#define B2056_TX_INTPAA_GAIN 0x36 +#define B2056_TX_INTPAA_BOOST_TUNE 0x37 +#define B2056_TX_INTPAA_IAUX_STAT 0x38 +#define B2056_TX_INTPAA_IAUX_DYN 0x39 +#define B2056_TX_INTPAA_IMAIN_STAT 0x3A +#define B2056_TX_INTPAA_IMAIN_DYN 0x3B +#define B2056_TX_INTPAA_CASCBIAS 0x3C +#define B2056_TX_INTPAA_PASLOPE 0x3D +#define B2056_TX_INTPAA_PA_MISC 0x3E +#define B2056_TX_INTPAG_MASTER 0x3F +#define B2056_TX_INTPAG_GAIN 0x40 +#define B2056_TX_INTPAG_BOOST_TUNE 0x41 +#define B2056_TX_INTPAG_IAUX_STAT 0x42 +#define B2056_TX_INTPAG_IAUX_DYN 0x43 +#define B2056_TX_INTPAG_IMAIN_STAT 0x44 +#define B2056_TX_INTPAG_IMAIN_DYN 0x45 +#define B2056_TX_INTPAG_CASCBIAS 0x46 +#define B2056_TX_INTPAG_PASLOPE 0x47 +#define B2056_TX_INTPAG_PA_MISC 0x48 +#define B2056_TX_PADA_MASTER 0x49 +#define B2056_TX_PADA_IDAC 0x4A +#define B2056_TX_PADA_CASCBIAS 0x4B +#define B2056_TX_PADA_GAIN 0x4C +#define B2056_TX_PADA_BOOST_TUNE 0x4D +#define B2056_TX_PADA_SLOPE 0x4E +#define B2056_TX_PADG_MASTER 0x4F +#define B2056_TX_PADG_IDAC 0x50 +#define B2056_TX_PADG_CASCBIAS 0x51 +#define B2056_TX_PADG_GAIN 0x52 +#define B2056_TX_PADG_BOOST_TUNE 0x53 +#define B2056_TX_PADG_SLOPE 0x54 +#define B2056_TX_PGAA_MASTER 0x55 +#define B2056_TX_PGAA_IDAC 0x56 +#define B2056_TX_PGAA_GAIN 0x57 +#define B2056_TX_PGAA_BOOST_TUNE 0x58 +#define B2056_TX_PGAA_SLOPE 0x59 +#define B2056_TX_PGAA_MISC 0x5A +#define B2056_TX_PGAG_MASTER 0x5B +#define B2056_TX_PGAG_IDAC 0x5C +#define B2056_TX_PGAG_GAIN 0x5D +#define B2056_TX_PGAG_BOOST_TUNE 0x5E +#define B2056_TX_PGAG_SLOPE 0x5F +#define B2056_TX_PGAG_MISC 0x60 +#define B2056_TX_MIXA_MASTER 0x61 +#define B2056_TX_MIXA_BOOST_TUNE 0x62 +#define B2056_TX_MIXG 0x63 +#define B2056_TX_MIXG_BOOST_TUNE 0x64 +#define B2056_TX_BB_GM_MASTER 0x65 +#define B2056_TX_GMBB_GM 0x66 +#define B2056_TX_GMBB_IDAC 0x67 +#define B2056_TX_TXLPF_MASTER 0x68 +#define B2056_TX_TXLPF_RCCAL 0x69 +#define B2056_TX_TXLPF_RCCAL_OFF0 0x6A +#define B2056_TX_TXLPF_RCCAL_OFF1 0x6B +#define B2056_TX_TXLPF_RCCAL_OFF2 0x6C +#define B2056_TX_TXLPF_RCCAL_OFF3 0x6D +#define B2056_TX_TXLPF_RCCAL_OFF4 0x6E +#define B2056_TX_TXLPF_RCCAL_OFF5 0x6F +#define B2056_TX_TXLPF_RCCAL_OFF6 0x70 +#define B2056_TX_TXLPF_BW 0x71 +#define B2056_TX_TXLPF_GAIN 0x72 +#define B2056_TX_TXLPF_IDAC 0x73 +#define B2056_TX_TXLPF_IDAC_0 0x74 +#define B2056_TX_TXLPF_IDAC_1 0x75 +#define B2056_TX_TXLPF_IDAC_2 0x76 +#define B2056_TX_TXLPF_IDAC_3 0x77 +#define B2056_TX_TXLPF_IDAC_4 0x78 +#define B2056_TX_TXLPF_IDAC_5 0x79 +#define B2056_TX_TXLPF_IDAC_6 0x7A +#define B2056_TX_TXLPF_OPAMP_IDAC 0x7B +#define B2056_TX_TXLPF_MISC 0x7C +#define B2056_TX_TXSPARE1 0x7D +#define B2056_TX_TXSPARE2 0x7E +#define B2056_TX_TXSPARE3 0x7F +#define B2056_TX_TXSPARE4 0x80 +#define B2056_TX_TXSPARE5 0x81 +#define B2056_TX_TXSPARE6 0x82 +#define B2056_TX_TXSPARE7 0x83 +#define B2056_TX_TXSPARE8 0x84 +#define B2056_TX_TXSPARE9 0x85 +#define B2056_TX_TXSPARE10 0x86 +#define B2056_TX_TXSPARE11 0x87 +#define B2056_TX_TXSPARE12 0x88 +#define B2056_TX_TXSPARE13 0x89 +#define B2056_TX_TXSPARE14 0x8A +#define B2056_TX_TXSPARE15 0x8B +#define B2056_TX_TXSPARE16 0x8C +#define B2056_TX_STATUS_INTPA_GAIN 0x8D +#define B2056_TX_STATUS_PAD_GAIN 0x8E +#define B2056_TX_STATUS_PGA_GAIN 0x8F +#define B2056_TX_STATUS_GM_TXLPF_GAIN 0x90 +#define B2056_TX_STATUS_TXLPF_BW 0x91 +#define B2056_TX_STATUS_TXLPF_RC 0x92 +#define B2056_TX_GMBB_IDAC0 0x93 +#define B2056_TX_GMBB_IDAC1 0x94 +#define B2056_TX_GMBB_IDAC2 0x95 +#define B2056_TX_GMBB_IDAC3 0x96 +#define B2056_TX_GMBB_IDAC4 0x97 +#define B2056_TX_GMBB_IDAC5 0x98 +#define B2056_TX_GMBB_IDAC6 0x99 +#define B2056_TX_GMBB_IDAC7 0x9A + +#define B2056_RX_RESERVED_ADDR0 0x00 +#define B2056_RX_IDCODE 0x01 +#define B2056_RX_RESERVED_ADDR2 0x02 +#define B2056_RX_RESERVED_ADDR3 0x03 +#define B2056_RX_RESERVED_ADDR4 0x04 +#define B2056_RX_RESERVED_ADDR5 0x05 +#define B2056_RX_RESERVED_ADDR6 0x06 +#define B2056_RX_RESERVED_ADDR7 0x07 +#define B2056_RX_COM_CTRL 0x08 +#define B2056_RX_COM_PU 0x09 +#define B2056_RX_COM_OVR 0x0A +#define B2056_RX_COM_RESET 0x0B +#define B2056_RX_COM_RCAL 0x0C +#define B2056_RX_COM_RC_RXLPF 0x0D +#define B2056_RX_COM_RC_TXLPF 0x0E +#define B2056_RX_COM_RC_RXHPF 0x0F +#define B2056_RX_RESERVED_ADDR16 0x10 +#define B2056_RX_RESERVED_ADDR17 0x11 +#define B2056_RX_RESERVED_ADDR18 0x12 +#define B2056_RX_RESERVED_ADDR19 0x13 +#define B2056_RX_RESERVED_ADDR20 0x14 +#define B2056_RX_RESERVED_ADDR21 0x15 +#define B2056_RX_RESERVED_ADDR22 0x16 +#define B2056_RX_RESERVED_ADDR23 0x17 +#define B2056_RX_RESERVED_ADDR24 0x18 +#define B2056_RX_RESERVED_ADDR25 0x19 +#define B2056_RX_RESERVED_ADDR26 0x1A +#define B2056_RX_RESERVED_ADDR27 0x1B +#define B2056_RX_RESERVED_ADDR28 0x1C +#define B2056_RX_RESERVED_ADDR29 0x1D +#define B2056_RX_RESERVED_ADDR30 0x1E +#define B2056_RX_RESERVED_ADDR31 0x1F +#define B2056_RX_RXIQCAL_RXMUX 0x20 +#define B2056_RX_RSSI_PU 0x21 +#define B2056_RX_RSSI_SEL 0x22 +#define B2056_RX_RSSI_GAIN 0x23 +#define B2056_RX_RSSI_NB_IDAC 0x24 +#define B2056_RX_RSSI_WB2I_IDAC_1 0x25 +#define B2056_RX_RSSI_WB2I_IDAC_2 0x26 +#define B2056_RX_RSSI_WB2Q_IDAC_1 0x27 +#define B2056_RX_RSSI_WB2Q_IDAC_2 0x28 +#define B2056_RX_RSSI_POLE 0x29 +#define B2056_RX_RSSI_WB1_IDAC 0x2A +#define B2056_RX_RSSI_MISC 0x2B +#define B2056_RX_LNAA_MASTER 0x2C +#define B2056_RX_LNAA_TUNE 0x2D +#define B2056_RX_LNAA_GAIN 0x2E +#define B2056_RX_LNA_A_SLOPE 0x2F +#define B2056_RX_BIASPOLE_LNAA1_IDAC 0x30 +#define B2056_RX_LNAA2_IDAC 0x31 +#define B2056_RX_LNA1A_MISC 0x32 +#define B2056_RX_LNAG_MASTER 0x33 +#define B2056_RX_LNAG_TUNE 0x34 +#define B2056_RX_LNAG_GAIN 0x35 +#define B2056_RX_LNA_G_SLOPE 0x36 +#define B2056_RX_BIASPOLE_LNAG1_IDAC 0x37 +#define B2056_RX_LNAG2_IDAC 0x38 +#define B2056_RX_LNA1G_MISC 0x39 +#define B2056_RX_MIXA_MASTER 0x3A +#define B2056_RX_MIXA_VCM 0x3B +#define B2056_RX_MIXA_CTRLPTAT 0x3C +#define B2056_RX_MIXA_LOB_BIAS 0x3D +#define B2056_RX_MIXA_CORE_IDAC 0x3E +#define B2056_RX_MIXA_CMFB_IDAC 0x3F +#define B2056_RX_MIXA_BIAS_AUX 0x40 +#define B2056_RX_MIXA_BIAS_MAIN 0x41 +#define B2056_RX_MIXA_BIAS_MISC 0x42 +#define B2056_RX_MIXA_MAST_BIAS 0x43 +#define B2056_RX_MIXG_MASTER 0x44 +#define B2056_RX_MIXG_VCM 0x45 +#define B2056_RX_MIXG_CTRLPTAT 0x46 +#define B2056_RX_MIXG_LOB_BIAS 0x47 +#define B2056_RX_MIXG_CORE_IDAC 0x48 +#define B2056_RX_MIXG_CMFB_IDAC 0x49 +#define B2056_RX_MIXG_BIAS_AUX 0x4A +#define B2056_RX_MIXG_BIAS_MAIN 0x4B +#define B2056_RX_MIXG_BIAS_MISC 0x4C +#define B2056_RX_MIXG_MAST_BIAS 0x4D +#define B2056_RX_TIA_MASTER 0x4E +#define B2056_RX_TIA_IOPAMP 0x4F +#define B2056_RX_TIA_QOPAMP 0x50 +#define B2056_RX_TIA_IMISC 0x51 +#define B2056_RX_TIA_QMISC 0x52 +#define B2056_RX_TIA_GAIN 0x53 +#define B2056_RX_TIA_SPARE1 0x54 +#define B2056_RX_TIA_SPARE2 0x55 +#define B2056_RX_BB_LPF_MASTER 0x56 +#define B2056_RX_AACI_MASTER 0x57 +#define B2056_RX_RXLPF_IDAC 0x58 +#define B2056_RX_RXLPF_OPAMPBIAS_LOWQ 0x59 +#define B2056_RX_RXLPF_OPAMPBIAS_HIGHQ 0x5A +#define B2056_RX_RXLPF_BIAS_DCCANCEL 0x5B +#define B2056_RX_RXLPF_OUTVCM 0x5C +#define B2056_RX_RXLPF_INVCM_BODY 0x5D +#define B2056_RX_RXLPF_CC_OP 0x5E +#define B2056_RX_RXLPF_GAIN 0x5F +#define B2056_RX_RXLPF_Q_BW 0x60 +#define B2056_RX_RXLPF_HP_CORNER_BW 0x61 +#define B2056_RX_RXLPF_RCCAL_HPC 0x62 +#define B2056_RX_RXHPF_OFF0 0x63 +#define B2056_RX_RXHPF_OFF1 0x64 +#define B2056_RX_RXHPF_OFF2 0x65 +#define B2056_RX_RXHPF_OFF3 0x66 +#define B2056_RX_RXHPF_OFF4 0x67 +#define B2056_RX_RXHPF_OFF5 0x68 +#define B2056_RX_RXHPF_OFF6 0x69 +#define B2056_RX_RXHPF_OFF7 0x6A +#define B2056_RX_RXLPF_RCCAL_LPC 0x6B +#define B2056_RX_RXLPF_OFF_0 0x6C +#define B2056_RX_RXLPF_OFF_1 0x6D +#define B2056_RX_RXLPF_OFF_2 0x6E +#define B2056_RX_RXLPF_OFF_3 0x6F +#define B2056_RX_RXLPF_OFF_4 0x70 +#define B2056_RX_UNUSED 0x71 +#define B2056_RX_VGA_MASTER 0x72 +#define B2056_RX_VGA_BIAS 0x73 +#define B2056_RX_VGA_BIAS_DCCANCEL 0x74 +#define B2056_RX_VGA_GAIN 0x75 +#define B2056_RX_VGA_HP_CORNER_BW 0x76 +#define B2056_RX_VGABUF_BIAS 0x77 +#define B2056_RX_VGABUF_GAIN_BW 0x78 +#define B2056_RX_TXFBMIX_A 0x79 +#define B2056_RX_TXFBMIX_G 0x7A +#define B2056_RX_RXSPARE1 0x7B +#define B2056_RX_RXSPARE2 0x7C +#define B2056_RX_RXSPARE3 0x7D +#define B2056_RX_RXSPARE4 0x7E +#define B2056_RX_RXSPARE5 0x7F +#define B2056_RX_RXSPARE6 0x80 +#define B2056_RX_RXSPARE7 0x81 +#define B2056_RX_RXSPARE8 0x82 +#define B2056_RX_RXSPARE9 0x83 +#define B2056_RX_RXSPARE10 0x84 +#define B2056_RX_RXSPARE11 0x85 +#define B2056_RX_RXSPARE12 0x86 +#define B2056_RX_RXSPARE13 0x87 +#define B2056_RX_RXSPARE14 0x88 +#define B2056_RX_RXSPARE15 0x89 +#define B2056_RX_RXSPARE16 0x8A +#define B2056_RX_STATUS_LNAA_GAIN 0x8B +#define B2056_RX_STATUS_LNAG_GAIN 0x8C +#define B2056_RX_STATUS_MIXTIA_GAIN 0x8D +#define B2056_RX_STATUS_RXLPF_GAIN 0x8E +#define B2056_RX_STATUS_VGA_BUF_GAIN 0x8F +#define B2056_RX_STATUS_RXLPF_Q 0x90 +#define B2056_RX_STATUS_RXLPF_BUF_BW 0x91 +#define B2056_RX_STATUS_RXLPF_VGA_HPC 0x92 +#define B2056_RX_STATUS_RXLPF_RC 0x93 +#define B2056_RX_STATUS_HPC_RC 0x94 + +#define B2056_LNA1_A_PU 0x01 +#define B2056_LNA2_A_PU 0x02 +#define B2056_LNA1_G_PU 0x01 +#define B2056_LNA2_G_PU 0x02 +#define B2056_MIXA_PU_I 0x01 +#define B2056_MIXA_PU_Q 0x02 +#define B2056_MIXA_PU_GM 0x10 +#define B2056_MIXG_PU_I 0x01 +#define B2056_MIXG_PU_Q 0x02 +#define B2056_MIXG_PU_GM 0x10 +#define B2056_TIA_PU 0x01 +#define B2056_BB_LPF_PU 0x20 +#define B2056_W1_PU 0x02 +#define B2056_W2_PU 0x04 +#define B2056_NB_PU 0x08 +#define B2056_RSSI_W1_SEL 0x02 +#define B2056_RSSI_W2_SEL 0x04 +#define B2056_RSSI_NB_SEL 0x08 +#define B2056_VCM_MASK 0x1C +#define B2056_RSSI_VCM_SHIFT 0x02 + +#define B2056_SYN (0x0 << 12) +#define B2056_TX0 (0x2 << 12) +#define B2056_TX1 (0x3 << 12) +#define B2056_RX0 (0x6 << 12) +#define B2056_RX1 (0x7 << 12) +#define B2056_ALLTX (0xE << 12) +#define B2056_ALLRX (0xF << 12) + +#define B2056_SYN_RESERVED_ADDR0 0x00 +#define B2056_SYN_IDCODE 0x01 +#define B2056_SYN_RESERVED_ADDR2 0x02 +#define B2056_SYN_RESERVED_ADDR3 0x03 +#define B2056_SYN_RESERVED_ADDR4 0x04 +#define B2056_SYN_RESERVED_ADDR5 0x05 +#define B2056_SYN_RESERVED_ADDR6 0x06 +#define B2056_SYN_RESERVED_ADDR7 0x07 +#define B2056_SYN_COM_CTRL 0x08 +#define B2056_SYN_COM_PU 0x09 +#define B2056_SYN_COM_OVR 0x0A +#define B2056_SYN_COM_RESET 0x0B +#define B2056_SYN_COM_RCAL 0x0C +#define B2056_SYN_COM_RC_RXLPF 0x0D +#define B2056_SYN_COM_RC_TXLPF 0x0E +#define B2056_SYN_COM_RC_RXHPF 0x0F +#define B2056_SYN_RESERVED_ADDR16 0x10 +#define B2056_SYN_RESERVED_ADDR17 0x11 +#define B2056_SYN_RESERVED_ADDR18 0x12 +#define B2056_SYN_RESERVED_ADDR19 0x13 +#define B2056_SYN_RESERVED_ADDR20 0x14 +#define B2056_SYN_RESERVED_ADDR21 0x15 +#define B2056_SYN_RESERVED_ADDR22 0x16 +#define B2056_SYN_RESERVED_ADDR23 0x17 +#define B2056_SYN_RESERVED_ADDR24 0x18 +#define B2056_SYN_RESERVED_ADDR25 0x19 +#define B2056_SYN_RESERVED_ADDR26 0x1A +#define B2056_SYN_RESERVED_ADDR27 0x1B +#define B2056_SYN_RESERVED_ADDR28 0x1C +#define B2056_SYN_RESERVED_ADDR29 0x1D +#define B2056_SYN_RESERVED_ADDR30 0x1E +#define B2056_SYN_RESERVED_ADDR31 0x1F +#define B2056_SYN_GPIO_MASTER1 0x20 +#define B2056_SYN_GPIO_MASTER2 0x21 +#define B2056_SYN_TOPBIAS_MASTER 0x22 +#define B2056_SYN_TOPBIAS_RCAL 0x23 +#define B2056_SYN_AFEREG 0x24 +#define B2056_SYN_TEMPPROCSENSE 0x25 +#define B2056_SYN_TEMPPROCSENSEIDAC 0x26 +#define B2056_SYN_TEMPPROCSENSERCAL 0x27 +#define B2056_SYN_LPO 0x28 +#define B2056_SYN_VDDCAL_MASTER 0x29 +#define B2056_SYN_VDDCAL_IDAC 0x2A +#define B2056_SYN_VDDCAL_STATUS 0x2B +#define B2056_SYN_RCAL_MASTER 0x2C +#define B2056_SYN_RCAL_CODE_OUT 0x2D +#define B2056_SYN_RCCAL_CTRL0 0x2E +#define B2056_SYN_RCCAL_CTRL1 0x2F +#define B2056_SYN_RCCAL_CTRL2 0x30 +#define B2056_SYN_RCCAL_CTRL3 0x31 +#define B2056_SYN_RCCAL_CTRL4 0x32 +#define B2056_SYN_RCCAL_CTRL5 0x33 +#define B2056_SYN_RCCAL_CTRL6 0x34 +#define B2056_SYN_RCCAL_CTRL7 0x35 +#define B2056_SYN_RCCAL_CTRL8 0x36 +#define B2056_SYN_RCCAL_CTRL9 0x37 +#define B2056_SYN_RCCAL_CTRL10 0x38 +#define B2056_SYN_RCCAL_CTRL11 0x39 +#define B2056_SYN_ZCAL_SPARE1 0x3A +#define B2056_SYN_ZCAL_SPARE2 0x3B +#define B2056_SYN_PLL_MAST1 0x3C +#define B2056_SYN_PLL_MAST2 0x3D +#define B2056_SYN_PLL_MAST3 0x3E +#define B2056_SYN_PLL_BIAS_RESET 0x3F +#define B2056_SYN_PLL_XTAL0 0x40 +#define B2056_SYN_PLL_XTAL1 0x41 +#define B2056_SYN_PLL_XTAL3 0x42 +#define B2056_SYN_PLL_XTAL4 0x43 +#define B2056_SYN_PLL_XTAL5 0x44 +#define B2056_SYN_PLL_XTAL6 0x45 +#define B2056_SYN_PLL_REFDIV 0x46 +#define B2056_SYN_PLL_PFD 0x47 +#define B2056_SYN_PLL_CP1 0x48 +#define B2056_SYN_PLL_CP2 0x49 +#define B2056_SYN_PLL_CP3 0x4A +#define B2056_SYN_PLL_LOOPFILTER1 0x4B +#define B2056_SYN_PLL_LOOPFILTER2 0x4C +#define B2056_SYN_PLL_LOOPFILTER3 0x4D +#define B2056_SYN_PLL_LOOPFILTER4 0x4E +#define B2056_SYN_PLL_LOOPFILTER5 0x4F +#define B2056_SYN_PLL_MMD1 0x50 +#define B2056_SYN_PLL_MMD2 0x51 +#define B2056_SYN_PLL_VCO1 0x52 +#define B2056_SYN_PLL_VCO2 0x53 +#define B2056_SYN_PLL_MONITOR1 0x54 +#define B2056_SYN_PLL_MONITOR2 0x55 +#define B2056_SYN_PLL_VCOCAL1 0x56 +#define B2056_SYN_PLL_VCOCAL2 0x57 +#define B2056_SYN_PLL_VCOCAL4 0x58 +#define B2056_SYN_PLL_VCOCAL5 0x59 +#define B2056_SYN_PLL_VCOCAL6 0x5A +#define B2056_SYN_PLL_VCOCAL7 0x5B +#define B2056_SYN_PLL_VCOCAL8 0x5C +#define B2056_SYN_PLL_VCOCAL9 0x5D +#define B2056_SYN_PLL_VCOCAL10 0x5E +#define B2056_SYN_PLL_VCOCAL11 0x5F +#define B2056_SYN_PLL_VCOCAL12 0x60 +#define B2056_SYN_PLL_VCOCAL13 0x61 +#define B2056_SYN_PLL_VREG 0x62 +#define B2056_SYN_PLL_STATUS1 0x63 +#define B2056_SYN_PLL_STATUS2 0x64 +#define B2056_SYN_PLL_STATUS3 0x65 +#define B2056_SYN_LOGEN_PU0 0x66 +#define B2056_SYN_LOGEN_PU1 0x67 +#define B2056_SYN_LOGEN_PU2 0x68 +#define B2056_SYN_LOGEN_PU3 0x69 +#define B2056_SYN_LOGEN_PU5 0x6A +#define B2056_SYN_LOGEN_PU6 0x6B +#define B2056_SYN_LOGEN_PU7 0x6C +#define B2056_SYN_LOGEN_PU8 0x6D +#define B2056_SYN_LOGEN_BIAS_RESET 0x6E +#define B2056_SYN_LOGEN_RCCR1 0x6F +#define B2056_SYN_LOGEN_VCOBUF1 0x70 +#define B2056_SYN_LOGEN_MIXER1 0x71 +#define B2056_SYN_LOGEN_MIXER2 0x72 +#define B2056_SYN_LOGEN_BUF1 0x73 +#define B2056_SYN_LOGENBUF2 0x74 +#define B2056_SYN_LOGEN_BUF3 0x75 +#define B2056_SYN_LOGEN_BUF4 0x76 +#define B2056_SYN_LOGEN_DIV1 0x77 +#define B2056_SYN_LOGEN_DIV2 0x78 +#define B2056_SYN_LOGEN_DIV3 0x79 +#define B2056_SYN_LOGEN_ACL1 0x7A +#define B2056_SYN_LOGEN_ACL2 0x7B +#define B2056_SYN_LOGEN_ACL3 0x7C +#define B2056_SYN_LOGEN_ACL4 0x7D +#define B2056_SYN_LOGEN_ACL5 0x7E +#define B2056_SYN_LOGEN_ACL6 0x7F +#define B2056_SYN_LOGEN_ACLOUT 0x80 +#define B2056_SYN_LOGEN_ACLCAL1 0x81 +#define B2056_SYN_LOGEN_ACLCAL2 0x82 +#define B2056_SYN_LOGEN_ACLCAL3 0x83 +#define B2056_SYN_CALEN 0x84 +#define B2056_SYN_LOGEN_PEAKDET1 0x85 +#define B2056_SYN_LOGEN_CORE_ACL_OVR 0x86 +#define B2056_SYN_LOGEN_RX_DIFF_ACL_OVR 0x87 +#define B2056_SYN_LOGEN_TX_DIFF_ACL_OVR 0x88 +#define B2056_SYN_LOGEN_RX_CMOS_ACL_OVR 0x89 +#define B2056_SYN_LOGEN_TX_CMOS_ACL_OVR 0x8A +#define B2056_SYN_LOGEN_VCOBUF2 0x8B +#define B2056_SYN_LOGEN_MIXER3 0x8C +#define B2056_SYN_LOGEN_BUF5 0x8D +#define B2056_SYN_LOGEN_BUF6 0x8E +#define B2056_SYN_LOGEN_CBUFRX1 0x8F +#define B2056_SYN_LOGEN_CBUFRX2 0x90 +#define B2056_SYN_LOGEN_CBUFRX3 0x91 +#define B2056_SYN_LOGEN_CBUFRX4 0x92 +#define B2056_SYN_LOGEN_CBUFTX1 0x93 +#define B2056_SYN_LOGEN_CBUFTX2 0x94 +#define B2056_SYN_LOGEN_CBUFTX3 0x95 +#define B2056_SYN_LOGEN_CBUFTX4 0x96 +#define B2056_SYN_LOGEN_CMOSRX1 0x97 +#define B2056_SYN_LOGEN_CMOSRX2 0x98 +#define B2056_SYN_LOGEN_CMOSRX3 0x99 +#define B2056_SYN_LOGEN_CMOSRX4 0x9A +#define B2056_SYN_LOGEN_CMOSTX1 0x9B +#define B2056_SYN_LOGEN_CMOSTX2 0x9C +#define B2056_SYN_LOGEN_CMOSTX3 0x9D +#define B2056_SYN_LOGEN_CMOSTX4 0x9E +#define B2056_SYN_LOGEN_VCOBUF2_OVRVAL 0x9F +#define B2056_SYN_LOGEN_MIXER3_OVRVAL 0xA0 +#define B2056_SYN_LOGEN_BUF5_OVRVAL 0xA1 +#define B2056_SYN_LOGEN_BUF6_OVRVAL 0xA2 +#define B2056_SYN_LOGEN_CBUFRX1_OVRVAL 0xA3 +#define B2056_SYN_LOGEN_CBUFRX2_OVRVAL 0xA4 +#define B2056_SYN_LOGEN_CBUFRX3_OVRVAL 0xA5 +#define B2056_SYN_LOGEN_CBUFRX4_OVRVAL 0xA6 +#define B2056_SYN_LOGEN_CBUFTX1_OVRVAL 0xA7 +#define B2056_SYN_LOGEN_CBUFTX2_OVRVAL 0xA8 +#define B2056_SYN_LOGEN_CBUFTX3_OVRVAL 0xA9 +#define B2056_SYN_LOGEN_CBUFTX4_OVRVAL 0xAA +#define B2056_SYN_LOGEN_CMOSRX1_OVRVAL 0xAB +#define B2056_SYN_LOGEN_CMOSRX2_OVRVAL 0xAC +#define B2056_SYN_LOGEN_CMOSRX3_OVRVAL 0xAD +#define B2056_SYN_LOGEN_CMOSRX4_OVRVAL 0xAE +#define B2056_SYN_LOGEN_CMOSTX1_OVRVAL 0xAF +#define B2056_SYN_LOGEN_CMOSTX2_OVRVAL 0xB0 +#define B2056_SYN_LOGEN_CMOSTX3_OVRVAL 0xB1 +#define B2056_SYN_LOGEN_CMOSTX4_OVRVAL 0xB2 +#define B2056_SYN_LOGEN_ACL_WAITCNT 0xB3 +#define B2056_SYN_LOGEN_CORE_CALVALID 0xB4 +#define B2056_SYN_LOGEN_RX_CMOS_CALVALID 0xB5 +#define B2056_SYN_LOGEN_TX_CMOS_VALID 0xB6 + +#define B2056_TX_RESERVED_ADDR0 0x00 +#define B2056_TX_IDCODE 0x01 +#define B2056_TX_RESERVED_ADDR2 0x02 +#define B2056_TX_RESERVED_ADDR3 0x03 +#define B2056_TX_RESERVED_ADDR4 0x04 +#define B2056_TX_RESERVED_ADDR5 0x05 +#define B2056_TX_RESERVED_ADDR6 0x06 +#define B2056_TX_RESERVED_ADDR7 0x07 +#define B2056_TX_COM_CTRL 0x08 +#define B2056_TX_COM_PU 0x09 +#define B2056_TX_COM_OVR 0x0A +#define B2056_TX_COM_RESET 0x0B +#define B2056_TX_COM_RCAL 0x0C +#define B2056_TX_COM_RC_RXLPF 0x0D +#define B2056_TX_COM_RC_TXLPF 0x0E +#define B2056_TX_COM_RC_RXHPF 0x0F +#define B2056_TX_RESERVED_ADDR16 0x10 +#define B2056_TX_RESERVED_ADDR17 0x11 +#define B2056_TX_RESERVED_ADDR18 0x12 +#define B2056_TX_RESERVED_ADDR19 0x13 +#define B2056_TX_RESERVED_ADDR20 0x14 +#define B2056_TX_RESERVED_ADDR21 0x15 +#define B2056_TX_RESERVED_ADDR22 0x16 +#define B2056_TX_RESERVED_ADDR23 0x17 +#define B2056_TX_RESERVED_ADDR24 0x18 +#define B2056_TX_RESERVED_ADDR25 0x19 +#define B2056_TX_RESERVED_ADDR26 0x1A +#define B2056_TX_RESERVED_ADDR27 0x1B +#define B2056_TX_RESERVED_ADDR28 0x1C +#define B2056_TX_RESERVED_ADDR29 0x1D +#define B2056_TX_RESERVED_ADDR30 0x1E +#define B2056_TX_RESERVED_ADDR31 0x1F +#define B2056_TX_IQCAL_GAIN_BW 0x20 +#define B2056_TX_LOFT_FINE_I 0x21 +#define B2056_TX_LOFT_FINE_Q 0x22 +#define B2056_TX_LOFT_COARSE_I 0x23 +#define B2056_TX_LOFT_COARSE_Q 0x24 +#define B2056_TX_TX_COM_MASTER1 0x25 +#define B2056_TX_TX_COM_MASTER2 0x26 +#define B2056_TX_RXIQCAL_TXMUX 0x27 +#define B2056_TX_TX_SSI_MASTER 0x28 +#define B2056_TX_IQCAL_VCM_HG 0x29 +#define B2056_TX_IQCAL_IDAC 0x2A +#define B2056_TX_TSSI_VCM 0x2B +#define B2056_TX_TX_AMP_DET 0x2C +#define B2056_TX_TX_SSI_MUX 0x2D +#define B2056_TX_TSSIA 0x2E +#define B2056_TX_TSSIG 0x2F +#define B2056_TX_TSSI_MISC1 0x30 +#define B2056_TX_TSSI_MISC2 0x31 +#define B2056_TX_TSSI_MISC3 0x32 +#define B2056_TX_PA_SPARE1 0x33 +#define B2056_TX_PA_SPARE2 0x34 +#define B2056_TX_INTPAA_MASTER 0x35 +#define B2056_TX_INTPAA_GAIN 0x36 +#define B2056_TX_INTPAA_BOOST_TUNE 0x37 +#define B2056_TX_INTPAA_IAUX_STAT 0x38 +#define B2056_TX_INTPAA_IAUX_DYN 0x39 +#define B2056_TX_INTPAA_IMAIN_STAT 0x3A +#define B2056_TX_INTPAA_IMAIN_DYN 0x3B +#define B2056_TX_INTPAA_CASCBIAS 0x3C +#define B2056_TX_INTPAA_PASLOPE 0x3D +#define B2056_TX_INTPAA_PA_MISC 0x3E +#define B2056_TX_INTPAG_MASTER 0x3F +#define B2056_TX_INTPAG_GAIN 0x40 +#define B2056_TX_INTPAG_BOOST_TUNE 0x41 +#define B2056_TX_INTPAG_IAUX_STAT 0x42 +#define B2056_TX_INTPAG_IAUX_DYN 0x43 +#define B2056_TX_INTPAG_IMAIN_STAT 0x44 +#define B2056_TX_INTPAG_IMAIN_DYN 0x45 +#define B2056_TX_INTPAG_CASCBIAS 0x46 +#define B2056_TX_INTPAG_PASLOPE 0x47 +#define B2056_TX_INTPAG_PA_MISC 0x48 +#define B2056_TX_PADA_MASTER 0x49 +#define B2056_TX_PADA_IDAC 0x4A +#define B2056_TX_PADA_CASCBIAS 0x4B +#define B2056_TX_PADA_GAIN 0x4C +#define B2056_TX_PADA_BOOST_TUNE 0x4D +#define B2056_TX_PADA_SLOPE 0x4E +#define B2056_TX_PADG_MASTER 0x4F +#define B2056_TX_PADG_IDAC 0x50 +#define B2056_TX_PADG_CASCBIAS 0x51 +#define B2056_TX_PADG_GAIN 0x52 +#define B2056_TX_PADG_BOOST_TUNE 0x53 +#define B2056_TX_PADG_SLOPE 0x54 +#define B2056_TX_PGAA_MASTER 0x55 +#define B2056_TX_PGAA_IDAC 0x56 +#define B2056_TX_PGAA_GAIN 0x57 +#define B2056_TX_PGAA_BOOST_TUNE 0x58 +#define B2056_TX_PGAA_SLOPE 0x59 +#define B2056_TX_PGAA_MISC 0x5A +#define B2056_TX_PGAG_MASTER 0x5B +#define B2056_TX_PGAG_IDAC 0x5C +#define B2056_TX_PGAG_GAIN 0x5D +#define B2056_TX_PGAG_BOOST_TUNE 0x5E +#define B2056_TX_PGAG_SLOPE 0x5F +#define B2056_TX_PGAG_MISC 0x60 +#define B2056_TX_MIXA_MASTER 0x61 +#define B2056_TX_MIXA_BOOST_TUNE 0x62 +#define B2056_TX_MIXG 0x63 +#define B2056_TX_MIXG_BOOST_TUNE 0x64 +#define B2056_TX_BB_GM_MASTER 0x65 +#define B2056_TX_GMBB_GM 0x66 +#define B2056_TX_GMBB_IDAC 0x67 +#define B2056_TX_TXLPF_MASTER 0x68 +#define B2056_TX_TXLPF_RCCAL 0x69 +#define B2056_TX_TXLPF_RCCAL_OFF0 0x6A +#define B2056_TX_TXLPF_RCCAL_OFF1 0x6B +#define B2056_TX_TXLPF_RCCAL_OFF2 0x6C +#define B2056_TX_TXLPF_RCCAL_OFF3 0x6D +#define B2056_TX_TXLPF_RCCAL_OFF4 0x6E +#define B2056_TX_TXLPF_RCCAL_OFF5 0x6F +#define B2056_TX_TXLPF_RCCAL_OFF6 0x70 +#define B2056_TX_TXLPF_BW 0x71 +#define B2056_TX_TXLPF_GAIN 0x72 +#define B2056_TX_TXLPF_IDAC 0x73 +#define B2056_TX_TXLPF_IDAC_0 0x74 +#define B2056_TX_TXLPF_IDAC_1 0x75 +#define B2056_TX_TXLPF_IDAC_2 0x76 +#define B2056_TX_TXLPF_IDAC_3 0x77 +#define B2056_TX_TXLPF_IDAC_4 0x78 +#define B2056_TX_TXLPF_IDAC_5 0x79 +#define B2056_TX_TXLPF_IDAC_6 0x7A +#define B2056_TX_TXLPF_OPAMP_IDAC 0x7B +#define B2056_TX_TXLPF_MISC 0x7C +#define B2056_TX_TXSPARE1 0x7D +#define B2056_TX_TXSPARE2 0x7E +#define B2056_TX_TXSPARE3 0x7F +#define B2056_TX_TXSPARE4 0x80 +#define B2056_TX_TXSPARE5 0x81 +#define B2056_TX_TXSPARE6 0x82 +#define B2056_TX_TXSPARE7 0x83 +#define B2056_TX_TXSPARE8 0x84 +#define B2056_TX_TXSPARE9 0x85 +#define B2056_TX_TXSPARE10 0x86 +#define B2056_TX_TXSPARE11 0x87 +#define B2056_TX_TXSPARE12 0x88 +#define B2056_TX_TXSPARE13 0x89 +#define B2056_TX_TXSPARE14 0x8A +#define B2056_TX_TXSPARE15 0x8B +#define B2056_TX_TXSPARE16 0x8C +#define B2056_TX_STATUS_INTPA_GAIN 0x8D +#define B2056_TX_STATUS_PAD_GAIN 0x8E +#define B2056_TX_STATUS_PGA_GAIN 0x8F +#define B2056_TX_STATUS_GM_TXLPF_GAIN 0x90 +#define B2056_TX_STATUS_TXLPF_BW 0x91 +#define B2056_TX_STATUS_TXLPF_RC 0x92 +#define B2056_TX_GMBB_IDAC0 0x93 +#define B2056_TX_GMBB_IDAC1 0x94 +#define B2056_TX_GMBB_IDAC2 0x95 +#define B2056_TX_GMBB_IDAC3 0x96 +#define B2056_TX_GMBB_IDAC4 0x97 +#define B2056_TX_GMBB_IDAC5 0x98 +#define B2056_TX_GMBB_IDAC6 0x99 +#define B2056_TX_GMBB_IDAC7 0x9A + +#define B2056_RX_RESERVED_ADDR0 0x00 +#define B2056_RX_IDCODE 0x01 +#define B2056_RX_RESERVED_ADDR2 0x02 +#define B2056_RX_RESERVED_ADDR3 0x03 +#define B2056_RX_RESERVED_ADDR4 0x04 +#define B2056_RX_RESERVED_ADDR5 0x05 +#define B2056_RX_RESERVED_ADDR6 0x06 +#define B2056_RX_RESERVED_ADDR7 0x07 +#define B2056_RX_COM_CTRL 0x08 +#define B2056_RX_COM_PU 0x09 +#define B2056_RX_COM_OVR 0x0A +#define B2056_RX_COM_RESET 0x0B +#define B2056_RX_COM_RCAL 0x0C +#define B2056_RX_COM_RC_RXLPF 0x0D +#define B2056_RX_COM_RC_TXLPF 0x0E +#define B2056_RX_COM_RC_RXHPF 0x0F +#define B2056_RX_RESERVED_ADDR16 0x10 +#define B2056_RX_RESERVED_ADDR17 0x11 +#define B2056_RX_RESERVED_ADDR18 0x12 +#define B2056_RX_RESERVED_ADDR19 0x13 +#define B2056_RX_RESERVED_ADDR20 0x14 +#define B2056_RX_RESERVED_ADDR21 0x15 +#define B2056_RX_RESERVED_ADDR22 0x16 +#define B2056_RX_RESERVED_ADDR23 0x17 +#define B2056_RX_RESERVED_ADDR24 0x18 +#define B2056_RX_RESERVED_ADDR25 0x19 +#define B2056_RX_RESERVED_ADDR26 0x1A +#define B2056_RX_RESERVED_ADDR27 0x1B +#define B2056_RX_RESERVED_ADDR28 0x1C +#define B2056_RX_RESERVED_ADDR29 0x1D +#define B2056_RX_RESERVED_ADDR30 0x1E +#define B2056_RX_RESERVED_ADDR31 0x1F +#define B2056_RX_RXIQCAL_RXMUX 0x20 +#define B2056_RX_RSSI_PU 0x21 +#define B2056_RX_RSSI_SEL 0x22 +#define B2056_RX_RSSI_GAIN 0x23 +#define B2056_RX_RSSI_NB_IDAC 0x24 +#define B2056_RX_RSSI_WB2I_IDAC_1 0x25 +#define B2056_RX_RSSI_WB2I_IDAC_2 0x26 +#define B2056_RX_RSSI_WB2Q_IDAC_1 0x27 +#define B2056_RX_RSSI_WB2Q_IDAC_2 0x28 +#define B2056_RX_RSSI_POLE 0x29 +#define B2056_RX_RSSI_WB1_IDAC 0x2A +#define B2056_RX_RSSI_MISC 0x2B +#define B2056_RX_LNAA_MASTER 0x2C +#define B2056_RX_LNAA_TUNE 0x2D +#define B2056_RX_LNAA_GAIN 0x2E +#define B2056_RX_LNA_A_SLOPE 0x2F +#define B2056_RX_BIASPOLE_LNAA1_IDAC 0x30 +#define B2056_RX_LNAA2_IDAC 0x31 +#define B2056_RX_LNA1A_MISC 0x32 +#define B2056_RX_LNAG_MASTER 0x33 +#define B2056_RX_LNAG_TUNE 0x34 +#define B2056_RX_LNAG_GAIN 0x35 +#define B2056_RX_LNA_G_SLOPE 0x36 +#define B2056_RX_BIASPOLE_LNAG1_IDAC 0x37 +#define B2056_RX_LNAG2_IDAC 0x38 +#define B2056_RX_LNA1G_MISC 0x39 +#define B2056_RX_MIXA_MASTER 0x3A +#define B2056_RX_MIXA_VCM 0x3B +#define B2056_RX_MIXA_CTRLPTAT 0x3C +#define B2056_RX_MIXA_LOB_BIAS 0x3D +#define B2056_RX_MIXA_CORE_IDAC 0x3E +#define B2056_RX_MIXA_CMFB_IDAC 0x3F +#define B2056_RX_MIXA_BIAS_AUX 0x40 +#define B2056_RX_MIXA_BIAS_MAIN 0x41 +#define B2056_RX_MIXA_BIAS_MISC 0x42 +#define B2056_RX_MIXA_MAST_BIAS 0x43 +#define B2056_RX_MIXG_MASTER 0x44 +#define B2056_RX_MIXG_VCM 0x45 +#define B2056_RX_MIXG_CTRLPTAT 0x46 +#define B2056_RX_MIXG_LOB_BIAS 0x47 +#define B2056_RX_MIXG_CORE_IDAC 0x48 +#define B2056_RX_MIXG_CMFB_IDAC 0x49 +#define B2056_RX_MIXG_BIAS_AUX 0x4A +#define B2056_RX_MIXG_BIAS_MAIN 0x4B +#define B2056_RX_MIXG_BIAS_MISC 0x4C +#define B2056_RX_MIXG_MAST_BIAS 0x4D +#define B2056_RX_TIA_MASTER 0x4E +#define B2056_RX_TIA_IOPAMP 0x4F +#define B2056_RX_TIA_QOPAMP 0x50 +#define B2056_RX_TIA_IMISC 0x51 +#define B2056_RX_TIA_QMISC 0x52 +#define B2056_RX_TIA_GAIN 0x53 +#define B2056_RX_TIA_SPARE1 0x54 +#define B2056_RX_TIA_SPARE2 0x55 +#define B2056_RX_BB_LPF_MASTER 0x56 +#define B2056_RX_AACI_MASTER 0x57 +#define B2056_RX_RXLPF_IDAC 0x58 +#define B2056_RX_RXLPF_OPAMPBIAS_LOWQ 0x59 +#define B2056_RX_RXLPF_OPAMPBIAS_HIGHQ 0x5A +#define B2056_RX_RXLPF_BIAS_DCCANCEL 0x5B +#define B2056_RX_RXLPF_OUTVCM 0x5C +#define B2056_RX_RXLPF_INVCM_BODY 0x5D +#define B2056_RX_RXLPF_CC_OP 0x5E +#define B2056_RX_RXLPF_GAIN 0x5F +#define B2056_RX_RXLPF_Q_BW 0x60 +#define B2056_RX_RXLPF_HP_CORNER_BW 0x61 +#define B2056_RX_RXLPF_RCCAL_HPC 0x62 +#define B2056_RX_RXHPF_OFF0 0x63 +#define B2056_RX_RXHPF_OFF1 0x64 +#define B2056_RX_RXHPF_OFF2 0x65 +#define B2056_RX_RXHPF_OFF3 0x66 +#define B2056_RX_RXHPF_OFF4 0x67 +#define B2056_RX_RXHPF_OFF5 0x68 +#define B2056_RX_RXHPF_OFF6 0x69 +#define B2056_RX_RXHPF_OFF7 0x6A +#define B2056_RX_RXLPF_RCCAL_LPC 0x6B +#define B2056_RX_RXLPF_OFF_0 0x6C +#define B2056_RX_RXLPF_OFF_1 0x6D +#define B2056_RX_RXLPF_OFF_2 0x6E +#define B2056_RX_RXLPF_OFF_3 0x6F +#define B2056_RX_RXLPF_OFF_4 0x70 +#define B2056_RX_UNUSED 0x71 +#define B2056_RX_VGA_MASTER 0x72 +#define B2056_RX_VGA_BIAS 0x73 +#define B2056_RX_VGA_BIAS_DCCANCEL 0x74 +#define B2056_RX_VGA_GAIN 0x75 +#define B2056_RX_VGA_HP_CORNER_BW 0x76 +#define B2056_RX_VGABUF_BIAS 0x77 +#define B2056_RX_VGABUF_GAIN_BW 0x78 +#define B2056_RX_TXFBMIX_A 0x79 +#define B2056_RX_TXFBMIX_G 0x7A +#define B2056_RX_RXSPARE1 0x7B +#define B2056_RX_RXSPARE2 0x7C +#define B2056_RX_RXSPARE3 0x7D +#define B2056_RX_RXSPARE4 0x7E +#define B2056_RX_RXSPARE5 0x7F +#define B2056_RX_RXSPARE6 0x80 +#define B2056_RX_RXSPARE7 0x81 +#define B2056_RX_RXSPARE8 0x82 +#define B2056_RX_RXSPARE9 0x83 +#define B2056_RX_RXSPARE10 0x84 +#define B2056_RX_RXSPARE11 0x85 +#define B2056_RX_RXSPARE12 0x86 +#define B2056_RX_RXSPARE13 0x87 +#define B2056_RX_RXSPARE14 0x88 +#define B2056_RX_RXSPARE15 0x89 +#define B2056_RX_RXSPARE16 0x8A +#define B2056_RX_STATUS_LNAA_GAIN 0x8B +#define B2056_RX_STATUS_LNAG_GAIN 0x8C +#define B2056_RX_STATUS_MIXTIA_GAIN 0x8D +#define B2056_RX_STATUS_RXLPF_GAIN 0x8E +#define B2056_RX_STATUS_VGA_BUF_GAIN 0x8F +#define B2056_RX_STATUS_RXLPF_Q 0x90 +#define B2056_RX_STATUS_RXLPF_BUF_BW 0x91 +#define B2056_RX_STATUS_RXLPF_VGA_HPC 0x92 +#define B2056_RX_STATUS_RXLPF_RC 0x93 +#define B2056_RX_STATUS_HPC_RC 0x94 + +#define B2056_LNA1_A_PU 0x01 +#define B2056_LNA2_A_PU 0x02 +#define B2056_LNA1_G_PU 0x01 +#define B2056_LNA2_G_PU 0x02 +#define B2056_MIXA_PU_I 0x01 +#define B2056_MIXA_PU_Q 0x02 +#define B2056_MIXA_PU_GM 0x10 +#define B2056_MIXG_PU_I 0x01 +#define B2056_MIXG_PU_Q 0x02 +#define B2056_MIXG_PU_GM 0x10 +#define B2056_TIA_PU 0x01 +#define B2056_BB_LPF_PU 0x20 +#define B2056_W1_PU 0x02 +#define B2056_W2_PU 0x04 +#define B2056_NB_PU 0x08 +#define B2056_RSSI_W1_SEL 0x02 +#define B2056_RSSI_W2_SEL 0x04 +#define B2056_RSSI_NB_SEL 0x08 +#define B2056_VCM_MASK 0x1C +#define B2056_RSSI_VCM_SHIFT 0x02 + +struct b43_nphy_channeltab_entry_rev3 { + /* The channel frequency in MHz */ + u16 freq; + /* Radio register values on channelswitch */ + u8 radio_syn_pll_vcocal1; + u8 radio_syn_pll_vcocal2; + u8 radio_syn_pll_refdiv; + u8 radio_syn_pll_mmd2; + u8 radio_syn_pll_mmd1; + u8 radio_syn_pll_loopfilter1; + u8 radio_syn_pll_loopfilter2; + u8 radio_syn_pll_loopfilter3; + u8 radio_syn_pll_loopfilter4; + u8 radio_syn_pll_loopfilter5; + u8 radio_syn_reserved_addr27; + u8 radio_syn_reserved_addr28; + u8 radio_syn_reserved_addr29; + u8 radio_syn_logen_vcobuf1; + u8 radio_syn_logen_mixer2; + u8 radio_syn_logen_buf3; + u8 radio_syn_logen_buf4; + u8 radio_rx0_lnaa_tune; + u8 radio_rx0_lnag_tune; + u8 radio_tx0_intpaa_boost_tune; + u8 radio_tx0_intpag_boost_tune; + u8 radio_tx0_pada_boost_tune; + u8 radio_tx0_padg_boost_tune; + u8 radio_tx0_pgaa_boost_tune; + u8 radio_tx0_pgag_boost_tune; + u8 radio_tx0_mixa_boost_tune; + u8 radio_tx0_mixg_boost_tune; + u8 radio_rx1_lnaa_tune; + u8 radio_rx1_lnag_tune; + u8 radio_tx1_intpaa_boost_tune; + u8 radio_tx1_intpag_boost_tune; + u8 radio_tx1_pada_boost_tune; + u8 radio_tx1_padg_boost_tune; + u8 radio_tx1_pgaa_boost_tune; + u8 radio_tx1_pgag_boost_tune; + u8 radio_tx1_mixa_boost_tune; + u8 radio_tx1_mixg_boost_tune; + /* PHY register values on channelswitch */ + struct b43_phy_n_sfo_cfg phy_regs; +}; + +void b2056_upload_inittabs(struct b43_wldev *dev, + bool ghz5, bool ignore_uploadflag); +void b2056_upload_syn_pll_cp2(struct b43_wldev *dev, bool ghz5); + +/* Get the NPHY Channel Switch Table entry for a channel. + * Returns NULL on failure to find an entry. */ +const struct b43_nphy_channeltab_entry_rev3 * +b43_nphy_get_chantabent_rev3(struct b43_wldev *dev, u16 freq); + +#endif /* B43_RADIO_2056_H_ */ diff --git a/drivers/net/wireless/b43/radio_2057.c b/drivers/net/wireless/b43/radio_2057.c new file mode 100644 index 0000000..d61d683 --- /dev/null +++ b/drivers/net/wireless/b43/radio_2057.c @@ -0,0 +1,141 @@ +/* + + Broadcom B43 wireless driver + IEEE 802.11n 2057 radio device data tables + + Copyright (c) 2010 Rafał Miłecki + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" +#include "radio_2057.h" +#include "phy_common.h" + +static u16 r2057_rev4_init[42][2] = { + { 0x0E, 0x20 }, { 0x31, 0x00 }, { 0x32, 0x00 }, { 0x33, 0x00 }, + { 0x35, 0x26 }, { 0x3C, 0xff }, { 0x3D, 0xff }, { 0x3E, 0xff }, + { 0x3F, 0xff }, { 0x62, 0x33 }, { 0x8A, 0xf0 }, { 0x8B, 0x10 }, + { 0x8C, 0xf0 }, { 0x91, 0x3f }, { 0x92, 0x36 }, { 0xA4, 0x8c }, + { 0xA8, 0x55 }, { 0xAF, 0x01 }, { 0x10F, 0xf0 }, { 0x110, 0x10 }, + { 0x111, 0xf0 }, { 0x116, 0x3f }, { 0x117, 0x36 }, { 0x129, 0x8c }, + { 0x12D, 0x55 }, { 0x134, 0x01 }, { 0x15E, 0x00 }, { 0x15F, 0x00 }, + { 0x160, 0x00 }, { 0x161, 0x00 }, { 0x162, 0x00 }, { 0x163, 0x00 }, + { 0x169, 0x02 }, { 0x16A, 0x00 }, { 0x16B, 0x00 }, { 0x16C, 0x00 }, + { 0x1A4, 0x00 }, { 0x1A5, 0x00 }, { 0x1A6, 0x00 }, { 0x1AA, 0x00 }, + { 0x1AB, 0x00 }, { 0x1AC, 0x00 }, +}; + +static u16 r2057_rev5_init[44][2] = { + { 0x00, 0x00 }, { 0x01, 0x57 }, { 0x02, 0x20 }, { 0x23, 0x6 }, + { 0x31, 0x00 }, { 0x32, 0x00 }, { 0x33, 0x00 }, { 0x51, 0x70 }, + { 0x59, 0x88 }, { 0x5C, 0x20 }, { 0x62, 0x33 }, { 0x63, 0x0f }, + { 0x64, 0x0f }, { 0x81, 0x01 }, { 0x91, 0x3f }, { 0x92, 0x36 }, + { 0xA1, 0x20 }, { 0xD6, 0x70 }, { 0xDE, 0x88 }, { 0xE1, 0x20 }, + { 0xE8, 0x0f }, { 0xE9, 0x0f }, { 0x106, 0x01 }, { 0x116, 0x3f }, + { 0x117, 0x36 }, { 0x126, 0x20 }, { 0x15E, 0x00 }, { 0x15F, 0x00 }, + { 0x160, 0x00 }, { 0x161, 0x00 }, { 0x162, 0x00 }, { 0x163, 0x00 }, + { 0x16A, 0x00 }, { 0x16B, 0x00 }, { 0x16C, 0x00 }, { 0x1A4, 0x00 }, + { 0x1A5, 0x00 }, { 0x1A6, 0x00 }, { 0x1AA, 0x00 }, { 0x1AB, 0x00 }, + { 0x1AC, 0x00 }, { 0x1B7, 0x0c }, { 0x1C1, 0x01 }, { 0x1C2, 0x80 }, +}; + +static u16 r2057_rev5a_init[45][2] = { + { 0x00, 0x15 }, { 0x01, 0x57 }, { 0x02, 0x20 }, { 0x23, 0x6 }, + { 0x31, 0x00 }, { 0x32, 0x00 }, { 0x33, 0x00 }, { 0x51, 0x70 }, + { 0x59, 0x88 }, { 0x5C, 0x20 }, { 0x62, 0x33 }, { 0x63, 0x0f }, + { 0x64, 0x0f }, { 0x81, 0x01 }, { 0x91, 0x3f }, { 0x92, 0x36 }, + { 0xC9, 0x01 }, { 0xD6, 0x70 }, { 0xDE, 0x88 }, { 0xE1, 0x20 }, + { 0xE8, 0x0f }, { 0xE9, 0x0f }, { 0x106, 0x01 }, { 0x116, 0x3f }, + { 0x117, 0x36 }, { 0x126, 0x20 }, { 0x14E, 0x01 }, { 0x15E, 0x00 }, + { 0x15F, 0x00 }, { 0x160, 0x00 }, { 0x161, 0x00 }, { 0x162, 0x00 }, + { 0x163, 0x00 }, { 0x16A, 0x00 }, { 0x16B, 0x00 }, { 0x16C, 0x00 }, + { 0x1A4, 0x00 }, { 0x1A5, 0x00 }, { 0x1A6, 0x00 }, { 0x1AA, 0x00 }, + { 0x1AB, 0x00 }, { 0x1AC, 0x00 }, { 0x1B7, 0x0c }, { 0x1C1, 0x01 }, + { 0x1C2, 0x80 }, +}; + +static u16 r2057_rev7_init[54][2] = { + { 0x00, 0x00 }, { 0x01, 0x57 }, { 0x02, 0x20 }, { 0x31, 0x00 }, + { 0x32, 0x00 }, { 0x33, 0x00 }, { 0x51, 0x70 }, { 0x59, 0x88 }, + { 0x5C, 0x20 }, { 0x62, 0x33 }, { 0x63, 0x0f }, { 0x64, 0x13 }, + { 0x66, 0xee }, { 0x6E, 0x58 }, { 0x75, 0x13 }, { 0x7B, 0x13 }, + { 0x7C, 0x14 }, { 0x7D, 0xee }, { 0x81, 0x01 }, { 0x91, 0x3f }, + { 0x92, 0x36 }, { 0xA1, 0x20 }, { 0xD6, 0x70 }, { 0xDE, 0x88 }, + { 0xE1, 0x20 }, { 0xE8, 0x0f }, { 0xE9, 0x13 }, { 0xEB, 0xee }, + { 0xF3, 0x58 }, { 0xFA, 0x13 }, { 0x100, 0x13 }, { 0x101, 0x14 }, + { 0x102, 0xee }, { 0x106, 0x01 }, { 0x116, 0x3f }, { 0x117, 0x36 }, + { 0x126, 0x20 }, { 0x15E, 0x00 }, { 0x15F, 0x00 }, { 0x160, 0x00 }, + { 0x161, 0x00 }, { 0x162, 0x00 }, { 0x163, 0x00 }, { 0x16A, 0x00 }, + { 0x16B, 0x00 }, { 0x16C, 0x00 }, { 0x1A4, 0x00 }, { 0x1A5, 0x00 }, + { 0x1A6, 0x00 }, { 0x1AA, 0x00 }, { 0x1AB, 0x00 }, { 0x1AC, 0x00 }, + { 0x1B7, 0x05 }, { 0x1C2, 0xa0 }, +}; + +static u16 r2057_rev8_init[54][2] = { + { 0x00, 0x08 }, { 0x01, 0x57 }, { 0x02, 0x20 }, { 0x31, 0x00 }, + { 0x32, 0x00 }, { 0x33, 0x00 }, { 0x51, 0x70 }, { 0x59, 0x88 }, + { 0x5C, 0x20 }, { 0x62, 0x33 }, { 0x63, 0x0f }, { 0x64, 0x0f }, + { 0x6E, 0x58 }, { 0x75, 0x13 }, { 0x7B, 0x13 }, { 0x7C, 0x0f }, + { 0x7D, 0xee }, { 0x81, 0x01 }, { 0x91, 0x3f }, { 0x92, 0x36 }, + { 0xA1, 0x20 }, { 0xC9, 0x01 }, { 0xD6, 0x70 }, { 0xDE, 0x88 }, + { 0xE1, 0x20 }, { 0xE8, 0x0f }, { 0xE9, 0x0f }, { 0xF3, 0x58 }, + { 0xFA, 0x13 }, { 0x100, 0x13 }, { 0x101, 0x0f }, { 0x102, 0xee }, + { 0x106, 0x01 }, { 0x116, 0x3f }, { 0x117, 0x36 }, { 0x126, 0x20 }, + { 0x14E, 0x01 }, { 0x15E, 0x00 }, { 0x15F, 0x00 }, { 0x160, 0x00 }, + { 0x161, 0x00 }, { 0x162, 0x00 }, { 0x163, 0x00 }, { 0x16A, 0x00 }, + { 0x16B, 0x00 }, { 0x16C, 0x00 }, { 0x1A4, 0x00 }, { 0x1A5, 0x00 }, + { 0x1A6, 0x00 }, { 0x1AA, 0x00 }, { 0x1AB, 0x00 }, { 0x1AC, 0x00 }, + { 0x1B7, 0x05 }, { 0x1C2, 0xa0 }, +}; + +void r2057_upload_inittabs(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + u16 *table = NULL; + u16 size, i; + + if (phy->rev == 7) { + table = r2057_rev4_init[0]; + size = ARRAY_SIZE(r2057_rev4_init); + } else if (phy->rev == 8 || phy->rev == 9) { + if (phy->radio_rev == 5) { + if (phy->radio_rev == 8) { + table = r2057_rev5_init[0]; + size = ARRAY_SIZE(r2057_rev5_init); + } else { + table = r2057_rev5a_init[0]; + size = ARRAY_SIZE(r2057_rev5a_init); + } + } else if (phy->radio_rev == 7) { + table = r2057_rev7_init[0]; + size = ARRAY_SIZE(r2057_rev7_init); + } else if (phy->radio_rev == 9) { + table = r2057_rev8_init[0]; + size = ARRAY_SIZE(r2057_rev8_init); + } + } + + if (table) { + for (i = 0; i < 10; i++) { + pr_info("radio_write 0x%X ", *table); + table++; + pr_info("0x%X\n", *table); + table++; + } + } +} diff --git a/drivers/net/wireless/b43/radio_2057.h b/drivers/net/wireless/b43/radio_2057.h new file mode 100644 index 0000000..eeebd8f --- /dev/null +++ b/drivers/net/wireless/b43/radio_2057.h @@ -0,0 +1,430 @@ +#ifndef B43_RADIO_2057_H_ +#define B43_RADIO_2057_H_ + +#include + +#include "tables_nphy.h" + +#define R2057_DACBUF_VINCM_CORE0 0x000 +#define R2057_IDCODE 0x001 +#define R2057_RCCAL_MASTER 0x002 +#define R2057_RCCAL_CAP_SIZE 0x003 +#define R2057_RCAL_CONFIG 0x004 +#define R2057_GPAIO_CONFIG 0x005 +#define R2057_GPAIO_SEL1 0x006 +#define R2057_GPAIO_SEL0 0x007 +#define R2057_CLPO_CONFIG 0x008 +#define R2057_BANDGAP_CONFIG 0x009 +#define R2057_BANDGAP_RCAL_TRIM 0x00a +#define R2057_AFEREG_CONFIG 0x00b +#define R2057_TEMPSENSE_CONFIG 0x00c +#define R2057_XTAL_CONFIG1 0x00d +#define R2057_XTAL_ICORE_SIZE 0x00e +#define R2057_XTAL_BUF_SIZE 0x00f +#define R2057_XTAL_PULLCAP_SIZE 0x010 +#define R2057_RFPLL_MASTER 0x011 +#define R2057_VCOMONITOR_VTH_L 0x012 +#define R2057_VCOMONITOR_VTH_H 0x013 +#define R2057_VCOCAL_BIASRESET_RFPLLREG_VOUT 0x014 +#define R2057_VCO_VARCSIZE_IDAC 0x015 +#define R2057_VCOCAL_COUNTVAL0 0x016 +#define R2057_VCOCAL_COUNTVAL1 0x017 +#define R2057_VCOCAL_INTCLK_COUNT 0x018 +#define R2057_VCOCAL_MASTER 0x019 +#define R2057_VCOCAL_NUMCAPCHANGE 0x01a +#define R2057_VCOCAL_WINSIZE 0x01b +#define R2057_VCOCAL_DELAY_AFTER_REFRESH 0x01c +#define R2057_VCOCAL_DELAY_AFTER_CLOSELOOP 0x01d +#define R2057_VCOCAL_DELAY_AFTER_OPENLOOP 0x01e +#define R2057_VCOCAL_DELAY_BEFORE_OPENLOOP 0x01f +#define R2057_VCO_FORCECAPEN_FORCECAP1 0x020 +#define R2057_VCO_FORCECAP0 0x021 +#define R2057_RFPLL_REFMASTER_SPAREXTALSIZE 0x022 +#define R2057_RFPLL_PFD_RESET_PW 0x023 +#define R2057_RFPLL_LOOPFILTER_R2 0x024 +#define R2057_RFPLL_LOOPFILTER_R1 0x025 +#define R2057_RFPLL_LOOPFILTER_C3 0x026 +#define R2057_RFPLL_LOOPFILTER_C2 0x027 +#define R2057_RFPLL_LOOPFILTER_C1 0x028 +#define R2057_CP_KPD_IDAC 0x029 +#define R2057_RFPLL_IDACS 0x02a +#define R2057_RFPLL_MISC_EN 0x02b +#define R2057_RFPLL_MMD0 0x02c +#define R2057_RFPLL_MMD1 0x02d +#define R2057_RFPLL_MISC_CAL_RESETN 0x02e +#define R2057_JTAGXTAL_SIZE_CPBIAS_FILTRES 0x02f +#define R2057_VCO_ALCREF_BBPLLXTAL_SIZE 0x030 +#define R2057_VCOCAL_READCAP0 0x031 +#define R2057_VCOCAL_READCAP1 0x032 +#define R2057_VCOCAL_STATUS 0x033 +#define R2057_LOGEN_PUS 0x034 +#define R2057_LOGEN_PTAT_RESETS 0x035 +#define R2057_VCOBUF_IDACS 0x036 +#define R2057_VCOBUF_TUNE 0x037 +#define R2057_CMOSBUF_TX2GQ_IDACS 0x038 +#define R2057_CMOSBUF_TX2GI_IDACS 0x039 +#define R2057_CMOSBUF_TX5GQ_IDACS 0x03a +#define R2057_CMOSBUF_TX5GI_IDACS 0x03b +#define R2057_CMOSBUF_RX2GQ_IDACS 0x03c +#define R2057_CMOSBUF_RX2GI_IDACS 0x03d +#define R2057_CMOSBUF_RX5GQ_IDACS 0x03e +#define R2057_CMOSBUF_RX5GI_IDACS 0x03f +#define R2057_LOGEN_MX2G_IDACS 0x040 +#define R2057_LOGEN_MX2G_TUNE 0x041 +#define R2057_LOGEN_MX5G_IDACS 0x042 +#define R2057_LOGEN_MX5G_TUNE 0x043 +#define R2057_LOGEN_MX5G_RCCR 0x044 +#define R2057_LOGEN_INDBUF2G_IDAC 0x045 +#define R2057_LOGEN_INDBUF2G_IBOOST 0x046 +#define R2057_LOGEN_INDBUF2G_TUNE 0x047 +#define R2057_LOGEN_INDBUF5G_IDAC 0x048 +#define R2057_LOGEN_INDBUF5G_IBOOST 0x049 +#define R2057_LOGEN_INDBUF5G_TUNE 0x04a +#define R2057_CMOSBUF_TX_RCCR 0x04b +#define R2057_CMOSBUF_RX_RCCR 0x04c +#define R2057_LOGEN_SEL_PKDET 0x04d +#define R2057_CMOSBUF_SHAREIQ_PTAT 0x04e +#define R2057_RXTXBIAS_CONFIG_CORE0 0x04f +#define R2057_TXGM_TXRF_PUS_CORE0 0x050 +#define R2057_TXGM_IDAC_BLEED_CORE0 0x051 +#define R2057_TXGM_GAIN_CORE0 0x056 +#define R2057_TXGM2G_PKDET_PUS_CORE0 0x057 +#define R2057_PAD2G_PTATS_CORE0 0x058 +#define R2057_PAD2G_IDACS_CORE0 0x059 +#define R2057_PAD2G_BOOST_PU_CORE0 0x05a +#define R2057_PAD2G_CASCV_GAIN_CORE0 0x05b +#define R2057_TXMIX2G_TUNE_BOOST_PU_CORE0 0x05c +#define R2057_TXMIX2G_LODC_CORE0 0x05d +#define R2057_PAD2G_TUNE_PUS_CORE0 0x05e +#define R2057_IPA2G_GAIN_CORE0 0x05f +#define R2057_TSSI2G_SPARE1_CORE0 0x060 +#define R2057_TSSI2G_SPARE2_CORE0 0x061 +#define R2057_IPA2G_TUNEV_CASCV_PTAT_CORE0 0x062 +#define R2057_IPA2G_IMAIN_CORE0 0x063 +#define R2057_IPA2G_CASCONV_CORE0 0x064 +#define R2057_IPA2G_CASCOFFV_CORE0 0x065 +#define R2057_IPA2G_BIAS_FILTER_CORE0 0x066 +#define R2057_TX5G_PKDET_CORE0 0x069 +#define R2057_PGA_PTAT_TXGM5G_PU_CORE0 0x06a +#define R2057_PAD5G_PTATS1_CORE0 0x06b +#define R2057_PAD5G_CLASS_PTATS2_CORE0 0x06c +#define R2057_PGA_BOOSTPTAT_IMAIN_CORE0 0x06d +#define R2057_PAD5G_CASCV_IMAIN_CORE0 0x06e +#define R2057_TXMIX5G_IBOOST_PAD_IAUX_CORE0 0x06f +#define R2057_PGA_BOOST_TUNE_CORE0 0x070 +#define R2057_PGA_GAIN_CORE0 0x071 +#define R2057_PAD5G_CASCOFFV_GAIN_PUS_CORE0 0x072 +#define R2057_TXMIX5G_BOOST_TUNE_CORE0 0x073 +#define R2057_PAD5G_TUNE_MISC_PUS_CORE0 0x074 +#define R2057_IPA5G_IAUX_CORE0 0x075 +#define R2057_IPA5G_GAIN_CORE0 0x076 +#define R2057_TSSI5G_SPARE1_CORE0 0x077 +#define R2057_TSSI5G_SPARE2_CORE0 0x078 +#define R2057_IPA5G_CASCOFFV_PU_CORE0 0x079 +#define R2057_IPA5G_PTAT_CORE0 0x07a +#define R2057_IPA5G_IMAIN_CORE0 0x07b +#define R2057_IPA5G_CASCONV_CORE0 0x07c +#define R2057_IPA5G_BIAS_FILTER_CORE0 0x07d +#define R2057_PAD_BIAS_FILTER_BWS_CORE0 0x080 +#define R2057_TR2G_CONFIG1_CORE0_NU 0x081 +#define R2057_TR2G_CONFIG2_CORE0_NU 0x082 +#define R2057_LNA5G_RFEN_CORE0 0x083 +#define R2057_TR5G_CONFIG2_CORE0_NU 0x084 +#define R2057_RXRFBIAS_IBOOST_PU_CORE0 0x085 +#define R2057_RXRF_IABAND_RXGM_IMAIN_PTAT_CORE0 0x086 +#define R2057_RXGM_CMFBITAIL_AUXPTAT_CORE0 0x087 +#define R2057_RXMIX_ICORE_RXGM_IAUX_CORE0 0x088 +#define R2057_RXMIX_CMFBITAIL_PU_CORE0 0x089 +#define R2057_LNA2_IMAIN_PTAT_PU_CORE0 0x08a +#define R2057_LNA2_IAUX_PTAT_CORE0 0x08b +#define R2057_LNA1_IMAIN_PTAT_PU_CORE0 0x08c +#define R2057_LNA15G_INPUT_MATCH_TUNE_CORE0 0x08d +#define R2057_RXRFBIAS_BANDSEL_CORE0 0x08e +#define R2057_TIA_CONFIG_CORE0 0x08f +#define R2057_TIA_IQGAIN_CORE0 0x090 +#define R2057_TIA_IBIAS2_CORE0 0x091 +#define R2057_TIA_IBIAS1_CORE0 0x092 +#define R2057_TIA_SPARE_Q_CORE0 0x093 +#define R2057_TIA_SPARE_I_CORE0 0x094 +#define R2057_RXMIX2G_PUS_CORE0 0x095 +#define R2057_RXMIX2G_VCMREFS_CORE0 0x096 +#define R2057_RXMIX2G_LODC_QI_CORE0 0x097 +#define R2057_W12G_BW_LNA2G_PUS_CORE0 0x098 +#define R2057_LNA2G_GAIN_CORE0 0x099 +#define R2057_LNA2G_TUNE_CORE0 0x09a +#define R2057_RXMIX5G_PUS_CORE0 0x09b +#define R2057_RXMIX5G_VCMREFS_CORE0 0x09c +#define R2057_RXMIX5G_LODC_QI_CORE0 0x09d +#define R2057_W15G_BW_LNA5G_PUS_CORE0 0x09e +#define R2057_LNA5G_GAIN_CORE0 0x09f +#define R2057_LNA5G_TUNE_CORE0 0x0a0 +#define R2057_LPFSEL_TXRX_RXBB_PUS_CORE0 0x0a1 +#define R2057_RXBB_BIAS_MASTER_CORE0 0x0a2 +#define R2057_RXBB_VGABUF_IDACS_CORE0 0x0a3 +#define R2057_LPF_VCMREF_TXBUF_VCMREF_CORE0 0x0a4 +#define R2057_TXBUF_VINCM_CORE0 0x0a5 +#define R2057_TXBUF_IDACS_CORE0 0x0a6 +#define R2057_LPF_RESP_RXBUF_BW_CORE0 0x0a7 +#define R2057_RXBB_CC_CORE0 0x0a8 +#define R2057_RXBB_SPARE3_CORE0 0x0a9 +#define R2057_RXBB_RCCAL_HPC_CORE0 0x0aa +#define R2057_LPF_IDACS_CORE0 0x0ab +#define R2057_LPFBYP_DCLOOP_BYP_IDAC_CORE0 0x0ac +#define R2057_TXBUF_GAIN_CORE0 0x0ad +#define R2057_AFELOOPBACK_AACI_RESP_CORE0 0x0ae +#define R2057_RXBUF_DEGEN_CORE0 0x0af +#define R2057_RXBB_SPARE2_CORE0 0x0b0 +#define R2057_RXBB_SPARE1_CORE0 0x0b1 +#define R2057_RSSI_MASTER_CORE0 0x0b2 +#define R2057_W2_MASTER_CORE0 0x0b3 +#define R2057_NB_MASTER_CORE0 0x0b4 +#define R2057_W2_IDACS0_Q_CORE0 0x0b5 +#define R2057_W2_IDACS1_Q_CORE0 0x0b6 +#define R2057_W2_IDACS0_I_CORE0 0x0b7 +#define R2057_W2_IDACS1_I_CORE0 0x0b8 +#define R2057_RSSI_GPAIOSEL_W1_IDACS_CORE0 0x0b9 +#define R2057_NB_IDACS_Q_CORE0 0x0ba +#define R2057_NB_IDACS_I_CORE0 0x0bb +#define R2057_BACKUP4_CORE0 0x0c1 +#define R2057_BACKUP3_CORE0 0x0c2 +#define R2057_BACKUP2_CORE0 0x0c3 +#define R2057_BACKUP1_CORE0 0x0c4 +#define R2057_SPARE16_CORE0 0x0c5 +#define R2057_SPARE15_CORE0 0x0c6 +#define R2057_SPARE14_CORE0 0x0c7 +#define R2057_SPARE13_CORE0 0x0c8 +#define R2057_SPARE12_CORE0 0x0c9 +#define R2057_SPARE11_CORE0 0x0ca +#define R2057_TX2G_BIAS_RESETS_CORE0 0x0cb +#define R2057_TX5G_BIAS_RESETS_CORE0 0x0cc +#define R2057_IQTEST_SEL_PU 0x0cd +#define R2057_XTAL_CONFIG2 0x0ce +#define R2057_BUFS_MISC_LPFBW_CORE0 0x0cf +#define R2057_TXLPF_RCCAL_CORE0 0x0d0 +#define R2057_RXBB_GPAIOSEL_RXLPF_RCCAL_CORE0 0x0d1 +#define R2057_LPF_GAIN_CORE0 0x0d2 +#define R2057_DACBUF_IDACS_BW_CORE0 0x0d3 +#define R2057_RXTXBIAS_CONFIG_CORE1 0x0d4 +#define R2057_TXGM_TXRF_PUS_CORE1 0x0d5 +#define R2057_TXGM_IDAC_BLEED_CORE1 0x0d6 +#define R2057_TXGM_GAIN_CORE1 0x0db +#define R2057_TXGM2G_PKDET_PUS_CORE1 0x0dc +#define R2057_PAD2G_PTATS_CORE1 0x0dd +#define R2057_PAD2G_IDACS_CORE1 0x0de +#define R2057_PAD2G_BOOST_PU_CORE1 0x0df +#define R2057_PAD2G_CASCV_GAIN_CORE1 0x0e0 +#define R2057_TXMIX2G_TUNE_BOOST_PU_CORE1 0x0e1 +#define R2057_TXMIX2G_LODC_CORE1 0x0e2 +#define R2057_PAD2G_TUNE_PUS_CORE1 0x0e3 +#define R2057_IPA2G_GAIN_CORE1 0x0e4 +#define R2057_TSSI2G_SPARE1_CORE1 0x0e5 +#define R2057_TSSI2G_SPARE2_CORE1 0x0e6 +#define R2057_IPA2G_TUNEV_CASCV_PTAT_CORE1 0x0e7 +#define R2057_IPA2G_IMAIN_CORE1 0x0e8 +#define R2057_IPA2G_CASCONV_CORE1 0x0e9 +#define R2057_IPA2G_CASCOFFV_CORE1 0x0ea +#define R2057_IPA2G_BIAS_FILTER_CORE1 0x0eb +#define R2057_TX5G_PKDET_CORE1 0x0ee +#define R2057_PGA_PTAT_TXGM5G_PU_CORE1 0x0ef +#define R2057_PAD5G_PTATS1_CORE1 0x0f0 +#define R2057_PAD5G_CLASS_PTATS2_CORE1 0x0f1 +#define R2057_PGA_BOOSTPTAT_IMAIN_CORE1 0x0f2 +#define R2057_PAD5G_CASCV_IMAIN_CORE1 0x0f3 +#define R2057_TXMIX5G_IBOOST_PAD_IAUX_CORE1 0x0f4 +#define R2057_PGA_BOOST_TUNE_CORE1 0x0f5 +#define R2057_PGA_GAIN_CORE1 0x0f6 +#define R2057_PAD5G_CASCOFFV_GAIN_PUS_CORE1 0x0f7 +#define R2057_TXMIX5G_BOOST_TUNE_CORE1 0x0f8 +#define R2057_PAD5G_TUNE_MISC_PUS_CORE1 0x0f9 +#define R2057_IPA5G_IAUX_CORE1 0x0fa +#define R2057_IPA5G_GAIN_CORE1 0x0fb +#define R2057_TSSI5G_SPARE1_CORE1 0x0fc +#define R2057_TSSI5G_SPARE2_CORE1 0x0fd +#define R2057_IPA5G_CASCOFFV_PU_CORE1 0x0fe +#define R2057_IPA5G_PTAT_CORE1 0x0ff +#define R2057_IPA5G_IMAIN_CORE1 0x100 +#define R2057_IPA5G_CASCONV_CORE1 0x101 +#define R2057_IPA5G_BIAS_FILTER_CORE1 0x102 +#define R2057_PAD_BIAS_FILTER_BWS_CORE1 0x105 +#define R2057_TR2G_CONFIG1_CORE1_NU 0x106 +#define R2057_TR2G_CONFIG2_CORE1_NU 0x107 +#define R2057_LNA5G_RFEN_CORE1 0x108 +#define R2057_TR5G_CONFIG2_CORE1_NU 0x109 +#define R2057_RXRFBIAS_IBOOST_PU_CORE1 0x10a +#define R2057_RXRF_IABAND_RXGM_IMAIN_PTAT_CORE1 0x10b +#define R2057_RXGM_CMFBITAIL_AUXPTAT_CORE1 0x10c +#define R2057_RXMIX_ICORE_RXGM_IAUX_CORE1 0x10d +#define R2057_RXMIX_CMFBITAIL_PU_CORE1 0x10e +#define R2057_LNA2_IMAIN_PTAT_PU_CORE1 0x10f +#define R2057_LNA2_IAUX_PTAT_CORE1 0x110 +#define R2057_LNA1_IMAIN_PTAT_PU_CORE1 0x111 +#define R2057_LNA15G_INPUT_MATCH_TUNE_CORE1 0x112 +#define R2057_RXRFBIAS_BANDSEL_CORE1 0x113 +#define R2057_TIA_CONFIG_CORE1 0x114 +#define R2057_TIA_IQGAIN_CORE1 0x115 +#define R2057_TIA_IBIAS2_CORE1 0x116 +#define R2057_TIA_IBIAS1_CORE1 0x117 +#define R2057_TIA_SPARE_Q_CORE1 0x118 +#define R2057_TIA_SPARE_I_CORE1 0x119 +#define R2057_RXMIX2G_PUS_CORE1 0x11a +#define R2057_RXMIX2G_VCMREFS_CORE1 0x11b +#define R2057_RXMIX2G_LODC_QI_CORE1 0x11c +#define R2057_W12G_BW_LNA2G_PUS_CORE1 0x11d +#define R2057_LNA2G_GAIN_CORE1 0x11e +#define R2057_LNA2G_TUNE_CORE1 0x11f +#define R2057_RXMIX5G_PUS_CORE1 0x120 +#define R2057_RXMIX5G_VCMREFS_CORE1 0x121 +#define R2057_RXMIX5G_LODC_QI_CORE1 0x122 +#define R2057_W15G_BW_LNA5G_PUS_CORE1 0x123 +#define R2057_LNA5G_GAIN_CORE1 0x124 +#define R2057_LNA5G_TUNE_CORE1 0x125 +#define R2057_LPFSEL_TXRX_RXBB_PUS_CORE1 0x126 +#define R2057_RXBB_BIAS_MASTER_CORE1 0x127 +#define R2057_RXBB_VGABUF_IDACS_CORE1 0x128 +#define R2057_LPF_VCMREF_TXBUF_VCMREF_CORE1 0x129 +#define R2057_TXBUF_VINCM_CORE1 0x12a +#define R2057_TXBUF_IDACS_CORE1 0x12b +#define R2057_LPF_RESP_RXBUF_BW_CORE1 0x12c +#define R2057_RXBB_CC_CORE1 0x12d +#define R2057_RXBB_SPARE3_CORE1 0x12e +#define R2057_RXBB_RCCAL_HPC_CORE1 0x12f +#define R2057_LPF_IDACS_CORE1 0x130 +#define R2057_LPFBYP_DCLOOP_BYP_IDAC_CORE1 0x131 +#define R2057_TXBUF_GAIN_CORE1 0x132 +#define R2057_AFELOOPBACK_AACI_RESP_CORE1 0x133 +#define R2057_RXBUF_DEGEN_CORE1 0x134 +#define R2057_RXBB_SPARE2_CORE1 0x135 +#define R2057_RXBB_SPARE1_CORE1 0x136 +#define R2057_RSSI_MASTER_CORE1 0x137 +#define R2057_W2_MASTER_CORE1 0x138 +#define R2057_NB_MASTER_CORE1 0x139 +#define R2057_W2_IDACS0_Q_CORE1 0x13a +#define R2057_W2_IDACS1_Q_CORE1 0x13b +#define R2057_W2_IDACS0_I_CORE1 0x13c +#define R2057_W2_IDACS1_I_CORE1 0x13d +#define R2057_RSSI_GPAIOSEL_W1_IDACS_CORE1 0x13e +#define R2057_NB_IDACS_Q_CORE1 0x13f +#define R2057_NB_IDACS_I_CORE1 0x140 +#define R2057_BACKUP4_CORE1 0x146 +#define R2057_BACKUP3_CORE1 0x147 +#define R2057_BACKUP2_CORE1 0x148 +#define R2057_BACKUP1_CORE1 0x149 +#define R2057_SPARE16_CORE1 0x14a +#define R2057_SPARE15_CORE1 0x14b +#define R2057_SPARE14_CORE1 0x14c +#define R2057_SPARE13_CORE1 0x14d +#define R2057_SPARE12_CORE1 0x14e +#define R2057_SPARE11_CORE1 0x14f +#define R2057_TX2G_BIAS_RESETS_CORE1 0x150 +#define R2057_TX5G_BIAS_RESETS_CORE1 0x151 +#define R2057_SPARE8_CORE1 0x152 +#define R2057_SPARE7_CORE1 0x153 +#define R2057_BUFS_MISC_LPFBW_CORE1 0x154 +#define R2057_TXLPF_RCCAL_CORE1 0x155 +#define R2057_RXBB_GPAIOSEL_RXLPF_RCCAL_CORE1 0x156 +#define R2057_LPF_GAIN_CORE1 0x157 +#define R2057_DACBUF_IDACS_BW_CORE1 0x158 +#define R2057_DACBUF_VINCM_CORE1 0x159 +#define R2057_RCCAL_START_R1_Q1_P1 0x15a +#define R2057_RCCAL_X1 0x15b +#define R2057_RCCAL_TRC0 0x15c +#define R2057_RCCAL_TRC1 0x15d +#define R2057_RCCAL_DONE_OSCCAP 0x15e +#define R2057_RCCAL_N0_0 0x15f +#define R2057_RCCAL_N0_1 0x160 +#define R2057_RCCAL_N1_0 0x161 +#define R2057_RCCAL_N1_1 0x162 +#define R2057_RCAL_STATUS 0x163 +#define R2057_XTALPUOVR_PINCTRL 0x164 +#define R2057_OVR_REG0 0x165 +#define R2057_OVR_REG1 0x166 +#define R2057_OVR_REG2 0x167 +#define R2057_OVR_REG3 0x168 +#define R2057_OVR_REG4 0x169 +#define R2057_RCCAL_SCAP_VAL 0x16a +#define R2057_RCCAL_BCAP_VAL 0x16b +#define R2057_RCCAL_HPC_VAL 0x16c +#define R2057_RCCAL_OVERRIDES 0x16d +#define R2057_TX0_IQCAL_GAIN_BW 0x170 +#define R2057_TX0_LOFT_FINE_I 0x171 +#define R2057_TX0_LOFT_FINE_Q 0x172 +#define R2057_TX0_LOFT_COARSE_I 0x173 +#define R2057_TX0_LOFT_COARSE_Q 0x174 +#define R2057_TX0_TX_SSI_MASTER 0x175 +#define R2057_TX0_IQCAL_VCM_HG 0x176 +#define R2057_TX0_IQCAL_IDAC 0x177 +#define R2057_TX0_TSSI_VCM 0x178 +#define R2057_TX0_TX_SSI_MUX 0x179 +#define R2057_TX0_TSSIA 0x17a +#define R2057_TX0_TSSIG 0x17b +#define R2057_TX0_TSSI_MISC1 0x17c +#define R2057_TX0_TXRXCOUPLE_2G_ATTEN 0x17d +#define R2057_TX0_TXRXCOUPLE_2G_PWRUP 0x17e +#define R2057_TX0_TXRXCOUPLE_5G_ATTEN 0x17f +#define R2057_TX0_TXRXCOUPLE_5G_PWRUP 0x180 +#define R2057_TX1_IQCAL_GAIN_BW 0x190 +#define R2057_TX1_LOFT_FINE_I 0x191 +#define R2057_TX1_LOFT_FINE_Q 0x192 +#define R2057_TX1_LOFT_COARSE_I 0x193 +#define R2057_TX1_LOFT_COARSE_Q 0x194 +#define R2057_TX1_TX_SSI_MASTER 0x195 +#define R2057_TX1_IQCAL_VCM_HG 0x196 +#define R2057_TX1_IQCAL_IDAC 0x197 +#define R2057_TX1_TSSI_VCM 0x198 +#define R2057_TX1_TX_SSI_MUX 0x199 +#define R2057_TX1_TSSIA 0x19a +#define R2057_TX1_TSSIG 0x19b +#define R2057_TX1_TSSI_MISC1 0x19c +#define R2057_TX1_TXRXCOUPLE_2G_ATTEN 0x19d +#define R2057_TX1_TXRXCOUPLE_2G_PWRUP 0x19e +#define R2057_TX1_TXRXCOUPLE_5G_ATTEN 0x19f +#define R2057_TX1_TXRXCOUPLE_5G_PWRUP 0x1a0 +#define R2057_AFE_VCM_CAL_MASTER_CORE0 0x1a1 +#define R2057_AFE_SET_VCM_I_CORE0 0x1a2 +#define R2057_AFE_SET_VCM_Q_CORE0 0x1a3 +#define R2057_AFE_STATUS_VCM_IQADC_CORE0 0x1a4 +#define R2057_AFE_STATUS_VCM_I_CORE0 0x1a5 +#define R2057_AFE_STATUS_VCM_Q_CORE0 0x1a6 +#define R2057_AFE_VCM_CAL_MASTER_CORE1 0x1a7 +#define R2057_AFE_SET_VCM_I_CORE1 0x1a8 +#define R2057_AFE_SET_VCM_Q_CORE1 0x1a9 +#define R2057_AFE_STATUS_VCM_IQADC_CORE1 0x1aa +#define R2057_AFE_STATUS_VCM_I_CORE1 0x1ab +#define R2057_AFE_STATUS_VCM_Q_CORE1 0x1ac + +#define R2057v7_DACBUF_VINCM_CORE0 0x1ad +#define R2057v7_RCCAL_MASTER 0x1ae +#define R2057v7_TR2G_CONFIG3_CORE0_NU 0x1af +#define R2057v7_TR2G_CONFIG3_CORE1_NU 0x1b0 +#define R2057v7_LOGEN_PUS1 0x1b1 +#define R2057v7_OVR_REG5 0x1b2 +#define R2057v7_OVR_REG6 0x1b3 +#define R2057v7_OVR_REG7 0x1b4 +#define R2057v7_OVR_REG8 0x1b5 +#define R2057v7_OVR_REG9 0x1b6 +#define R2057v7_OVR_REG10 0x1b7 +#define R2057v7_OVR_REG11 0x1b8 +#define R2057v7_OVR_REG12 0x1b9 +#define R2057v7_OVR_REG13 0x1ba +#define R2057v7_OVR_REG14 0x1bb +#define R2057v7_OVR_REG15 0x1bc +#define R2057v7_OVR_REG16 0x1bd +#define R2057v7_OVR_REG1 0x1be +#define R2057v7_OVR_REG18 0x1bf +#define R2057v7_OVR_REG19 0x1c0 +#define R2057v7_OVR_REG20 0x1c1 +#define R2057v7_OVR_REG21 0x1c2 +#define R2057v7_OVR_REG2 0x1c3 +#define R2057v7_OVR_REG23 0x1c4 +#define R2057v7_OVR_REG24 0x1c5 +#define R2057v7_OVR_REG25 0x1c6 +#define R2057v7_OVR_REG26 0x1c7 +#define R2057v7_OVR_REG27 0x1c8 +#define R2057v7_OVR_REG28 0x1c9 +#define R2057v7_IQTEST_SEL_PU2 0x1ca + +#define R2057_VCM_MASK 0x7 + +void r2057_upload_inittabs(struct b43_wldev *dev); + +#endif /* B43_RADIO_2057_H_ */ diff --git a/drivers/net/wireless/b43/radio_2059.c b/drivers/net/wireless/b43/radio_2059.c new file mode 100644 index 0000000..38e31d8 --- /dev/null +++ b/drivers/net/wireless/b43/radio_2059.c @@ -0,0 +1,155 @@ +/* + + Broadcom B43 wireless driver + IEEE 802.11n 2059 radio device data tables + + Copyright (c) 2011 Rafał Miłecki + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" +#include "radio_2059.h" + +#define RADIOREGS(r00, r01, r02, r03, r04, r05, r06, r07, r08, r09, \ + r10, r11, r12, r13, r14, r15, r16, r17, r18, r19, \ + r20) \ + .radio_syn16 = r00, \ + .radio_syn17 = r01, \ + .radio_syn22 = r02, \ + .radio_syn25 = r03, \ + .radio_syn27 = r04, \ + .radio_syn28 = r05, \ + .radio_syn29 = r06, \ + .radio_syn2c = r07, \ + .radio_syn2d = r08, \ + .radio_syn37 = r09, \ + .radio_syn41 = r10, \ + .radio_syn43 = r11, \ + .radio_syn47 = r12, \ + .radio_rxtx4a = r13, \ + .radio_rxtx58 = r14, \ + .radio_rxtx5a = r15, \ + .radio_rxtx6a = r16, \ + .radio_rxtx6d = r17, \ + .radio_rxtx6e = r18, \ + .radio_rxtx92 = r19, \ + .radio_rxtx98 = r20 + +#define PHYREGS(r0, r1, r2, r3, r4, r5) \ + .phy_regs.bw1 = r0, \ + .phy_regs.bw2 = r1, \ + .phy_regs.bw3 = r2, \ + .phy_regs.bw4 = r3, \ + .phy_regs.bw5 = r4, \ + .phy_regs.bw6 = r5 + +static const struct b43_phy_ht_channeltab_e_radio2059 b43_phy_ht_channeltab_radio2059[] = { + { .freq = 2412, + RADIOREGS(0x48, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x6c, + 0x09, 0x0f, 0x0a, 0x00, 0x0a, 0x00, 0x61, 0x03, + 0x00, 0x00, 0x00, 0xf0, 0x00), + PHYREGS(0x03c9, 0x03c5, 0x03c1, 0x043a, 0x043f, 0x0443), + }, + { .freq = 2417, + RADIOREGS(0x4b, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x71, + 0x09, 0x0f, 0x0a, 0x00, 0x0a, 0x00, 0x61, 0x03, + 0x00, 0x00, 0x00, 0xf0, 0x00), + PHYREGS(0x03cb, 0x03c7, 0x03c3, 0x0438, 0x043d, 0x0441), + }, + { .freq = 2422, + RADIOREGS(0x4e, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x76, + 0x09, 0x0f, 0x09, 0x00, 0x09, 0x00, 0x61, 0x03, + 0x00, 0x00, 0x00, 0xf0, 0x00), + PHYREGS(0x03cd, 0x03c9, 0x03c5, 0x0436, 0x043a, 0x043f), + }, + { .freq = 2427, + RADIOREGS(0x52, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x7b, + 0x09, 0x0f, 0x09, 0x00, 0x09, 0x00, 0x61, 0x03, + 0x00, 0x00, 0x00, 0xf0, 0x00), + PHYREGS(0x03cf, 0x03cb, 0x03c7, 0x0434, 0x0438, 0x043d), + }, + { .freq = 2432, + RADIOREGS(0x55, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x80, + 0x09, 0x0f, 0x08, 0x00, 0x08, 0x00, 0x61, 0x03, + 0x00, 0x00, 0x00, 0xf0, 0x00), + PHYREGS(0x03d1, 0x03cd, 0x03c9, 0x0431, 0x0436, 0x043a), + }, + { .freq = 2437, + RADIOREGS(0x58, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x85, + 0x09, 0x0f, 0x08, 0x00, 0x08, 0x00, 0x61, 0x03, + 0x00, 0x00, 0x00, 0xf0, 0x00), + PHYREGS(0x03d3, 0x03cf, 0x03cb, 0x042f, 0x0434, 0x0438), + }, + { .freq = 2442, + RADIOREGS(0x5c, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x8a, + 0x09, 0x0f, 0x07, 0x00, 0x07, 0x00, 0x61, 0x03, + 0x00, 0x00, 0x00, 0xf0, 0x00), + PHYREGS(0x03d5, 0x03d1, 0x03cd, 0x042d, 0x0431, 0x0436), + }, + { .freq = 2447, + RADIOREGS(0x5f, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x8f, + 0x09, 0x0f, 0x07, 0x00, 0x07, 0x00, 0x61, 0x03, + 0x00, 0x00, 0x00, 0xf0, 0x00), + PHYREGS(0x03d7, 0x03d3, 0x03cf, 0x042b, 0x042f, 0x0434), + }, + { .freq = 2452, + RADIOREGS(0x62, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x94, + 0x09, 0x0f, 0x07, 0x00, 0x07, 0x00, 0x61, 0x03, + 0x00, 0x00, 0x00, 0xf0, 0x00), + PHYREGS(0x03d9, 0x03d5, 0x03d1, 0x0429, 0x042d, 0x0431), + }, + { .freq = 2457, + RADIOREGS(0x66, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x99, + 0x09, 0x0f, 0x06, 0x00, 0x06, 0x00, 0x61, 0x03, + 0x00, 0x00, 0x00, 0xf0, 0x00), + PHYREGS(0x03db, 0x03d7, 0x03d3, 0x0427, 0x042b, 0x042f), + }, + { .freq = 2462, + RADIOREGS(0x69, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x9e, + 0x09, 0x0f, 0x06, 0x00, 0x06, 0x00, 0x61, 0x03, + 0x00, 0x00, 0x00, 0xf0, 0x00), + PHYREGS(0x03dd, 0x03d9, 0x03d5, 0x0424, 0x0429, 0x042d), + }, + { .freq = 2467, + RADIOREGS(0x6c, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xa3, + 0x09, 0x0f, 0x05, 0x00, 0x05, 0x00, 0x61, 0x03, + 0x00, 0x00, 0x00, 0xf0, 0x00), + PHYREGS(0x03df, 0x03db, 0x03d7, 0x0422, 0x0427, 0x042b), + }, + { .freq = 2472, + RADIOREGS(0x70, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xa8, + 0x09, 0x0f, 0x05, 0x00, 0x05, 0x00, 0x61, 0x03, + 0x00, 0x00, 0x00, 0xf0, 0x00), + PHYREGS(0x03e1, 0x03dd, 0x03d9, 0x0420, 0x0424, 0x0429), + }, +}; + +const struct b43_phy_ht_channeltab_e_radio2059 +*b43_phy_ht_get_channeltab_e_r2059(struct b43_wldev *dev, u16 freq) +{ + const struct b43_phy_ht_channeltab_e_radio2059 *e; + unsigned int i; + + e = b43_phy_ht_channeltab_radio2059; + for (i = 0; i < ARRAY_SIZE(b43_phy_ht_channeltab_radio2059); i++, e++) { + if (e->freq == freq) + return e; + } + + return NULL; +} diff --git a/drivers/net/wireless/b43/radio_2059.h b/drivers/net/wireless/b43/radio_2059.h new file mode 100644 index 0000000..40a82d7 --- /dev/null +++ b/drivers/net/wireless/b43/radio_2059.h @@ -0,0 +1,46 @@ +#ifndef B43_RADIO_2059_H_ +#define B43_RADIO_2059_H_ + +#include + +#include "phy_ht.h" + +#define R2059_C1 0x000 +#define R2059_C2 0x400 +#define R2059_C3 0x800 +#define R2059_ALL 0xC00 + +/* Values for various registers uploaded on channel switching */ +struct b43_phy_ht_channeltab_e_radio2059 { + /* The channel frequency in MHz */ + u16 freq; + /* Values for radio registers */ + u8 radio_syn16; + u8 radio_syn17; + u8 radio_syn22; + u8 radio_syn25; + u8 radio_syn27; + u8 radio_syn28; + u8 radio_syn29; + u8 radio_syn2c; + u8 radio_syn2d; + u8 radio_syn37; + u8 radio_syn41; + u8 radio_syn43; + u8 radio_syn47; + u8 radio_rxtx4a; + u8 radio_rxtx58; + u8 radio_rxtx5a; + u8 radio_rxtx6a; + u8 radio_rxtx6d; + u8 radio_rxtx6e; + u8 radio_rxtx92; + u8 radio_rxtx98; + /* Values for PHY registers */ + struct b43_phy_ht_channeltab_e_phy phy_regs; +}; + +const struct b43_phy_ht_channeltab_e_radio2059 +*b43_phy_ht_get_channeltab_e_r2059(struct b43_wldev *dev, u16 freq); + +#endif /* B43_RADIO_2059_H_ */ diff --git a/drivers/net/wireless/b43/rfkill.c b/drivers/net/wireless/b43/rfkill.c new file mode 100644 index 0000000..70c2fce --- /dev/null +++ b/drivers/net/wireless/b43/rfkill.c @@ -0,0 +1,70 @@ +/* + + Broadcom B43 wireless driver + RFKILL support + + Copyright (c) 2007 Michael Buesch + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" + + +/* Returns TRUE, if the radio is enabled in hardware. */ +bool b43_is_hw_radio_enabled(struct b43_wldev *dev) +{ + return !(b43_read32(dev, B43_MMIO_RADIO_HWENABLED_HI) + & B43_MMIO_RADIO_HWENABLED_HI_MASK); +} + +/* The poll callback for the hardware button. */ +void b43_rfkill_poll(struct ieee80211_hw *hw) +{ + struct b43_wl *wl = hw_to_b43_wl(hw); + struct b43_wldev *dev = wl->current_dev; + bool enabled; + bool brought_up = false; + + mutex_lock(&wl->mutex); + if (unlikely(b43_status(dev) < B43_STAT_INITIALIZED)) { + if (b43_bus_powerup(dev, 0)) { + mutex_unlock(&wl->mutex); + return; + } + b43_device_enable(dev, 0); + brought_up = true; + } + + enabled = b43_is_hw_radio_enabled(dev); + + if (unlikely(enabled != dev->radio_hw_enable)) { + dev->radio_hw_enable = enabled; + b43info(wl, "Radio hardware status changed to %s\n", + enabled ? "ENABLED" : "DISABLED"); + wiphy_rfkill_set_hw_state(hw->wiphy, !enabled); + if (enabled != dev->phy.radio_on) + b43_software_rfkill(dev, !enabled); + } + + if (brought_up) { + b43_device_disable(dev, 0); + b43_bus_may_powerdown(dev); + } + + mutex_unlock(&wl->mutex); +} diff --git a/drivers/net/wireless/b43/rfkill.h b/drivers/net/wireless/b43/rfkill.h new file mode 100644 index 0000000..f046c3c --- /dev/null +++ b/drivers/net/wireless/b43/rfkill.h @@ -0,0 +1,11 @@ +#ifndef B43_RFKILL_H_ +#define B43_RFKILL_H_ + +struct ieee80211_hw; +struct b43_wldev; + +void b43_rfkill_poll(struct ieee80211_hw *hw); + +bool b43_is_hw_radio_enabled(struct b43_wldev *dev); + +#endif /* B43_RFKILL_H_ */ diff --git a/drivers/net/wireless/b43/sdio.c b/drivers/net/wireless/b43/sdio.c new file mode 100644 index 0000000..59a5218 --- /dev/null +++ b/drivers/net/wireless/b43/sdio.c @@ -0,0 +1,207 @@ +/* + * Broadcom B43 wireless driver + * + * SDIO over Sonics Silicon Backplane bus glue for b43. + * + * Copyright (C) 2009 Albert Herranz + * Copyright (C) 2009 Michael Buesch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include +#include +#include +#include +#include +#include + +#include "sdio.h" +#include "b43.h" + + +#define HNBU_CHIPID 0x01 /* vendor & device id */ + +#define B43_SDIO_BLOCK_SIZE 64 /* rx fifo max size in bytes */ + + +static const struct b43_sdio_quirk { + u16 vendor; + u16 device; + unsigned int quirks; +} b43_sdio_quirks[] = { + { 0x14E4, 0x4318, SSB_QUIRK_SDIO_READ_AFTER_WRITE32, }, + { }, +}; + + +static unsigned int b43_sdio_get_quirks(u16 vendor, u16 device) +{ + const struct b43_sdio_quirk *q; + + for (q = b43_sdio_quirks; q->quirks; q++) { + if (vendor == q->vendor && device == q->device) + return q->quirks; + } + + return 0; +} + +static void b43_sdio_interrupt_dispatcher(struct sdio_func *func) +{ + struct b43_sdio *sdio = sdio_get_drvdata(func); + struct b43_wldev *dev = sdio->irq_handler_opaque; + + if (unlikely(b43_status(dev) < B43_STAT_STARTED)) + return; + + sdio_release_host(func); + sdio->irq_handler(dev); + sdio_claim_host(func); +} + +int b43_sdio_request_irq(struct b43_wldev *dev, + void (*handler)(struct b43_wldev *dev)) +{ + struct ssb_bus *bus = dev->dev->sdev->bus; + struct sdio_func *func = bus->host_sdio; + struct b43_sdio *sdio = sdio_get_drvdata(func); + int err; + + sdio->irq_handler_opaque = dev; + sdio->irq_handler = handler; + sdio_claim_host(func); + err = sdio_claim_irq(func, b43_sdio_interrupt_dispatcher); + sdio_release_host(func); + + return err; +} + +void b43_sdio_free_irq(struct b43_wldev *dev) +{ + struct ssb_bus *bus = dev->dev->sdev->bus; + struct sdio_func *func = bus->host_sdio; + struct b43_sdio *sdio = sdio_get_drvdata(func); + + sdio_claim_host(func); + sdio_release_irq(func); + sdio_release_host(func); + sdio->irq_handler_opaque = NULL; + sdio->irq_handler = NULL; +} + +static int b43_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct b43_sdio *sdio; + struct sdio_func_tuple *tuple; + u16 vendor = 0, device = 0; + int error; + + /* Look for the card chip identifier. */ + tuple = func->tuples; + while (tuple) { + switch (tuple->code) { + case 0x80: + switch (tuple->data[0]) { + case HNBU_CHIPID: + if (tuple->size != 5) + break; + vendor = tuple->data[1] | (tuple->data[2]<<8); + device = tuple->data[3] | (tuple->data[4]<<8); + dev_info(&func->dev, "Chip ID %04x:%04x\n", + vendor, device); + break; + default: + break; + } + break; + default: + break; + } + tuple = tuple->next; + } + if (!vendor || !device) { + error = -ENODEV; + goto out; + } + + sdio_claim_host(func); + error = sdio_set_block_size(func, B43_SDIO_BLOCK_SIZE); + if (error) { + dev_err(&func->dev, "failed to set block size to %u bytes," + " error %d\n", B43_SDIO_BLOCK_SIZE, error); + goto err_release_host; + } + error = sdio_enable_func(func); + if (error) { + dev_err(&func->dev, "failed to enable func, error %d\n", error); + goto err_release_host; + } + sdio_release_host(func); + + sdio = kzalloc(sizeof(*sdio), GFP_KERNEL); + if (!sdio) { + error = -ENOMEM; + dev_err(&func->dev, "failed to allocate ssb bus\n"); + goto err_disable_func; + } + error = ssb_bus_sdiobus_register(&sdio->ssb, func, + b43_sdio_get_quirks(vendor, device)); + if (error) { + dev_err(&func->dev, "failed to register ssb sdio bus," + " error %d\n", error); + goto err_free_ssb; + } + sdio_set_drvdata(func, sdio); + + return 0; + +err_free_ssb: + kfree(sdio); +err_disable_func: + sdio_claim_host(func); + sdio_disable_func(func); +err_release_host: + sdio_release_host(func); +out: + return error; +} + +static void b43_sdio_remove(struct sdio_func *func) +{ + struct b43_sdio *sdio = sdio_get_drvdata(func); + + ssb_bus_unregister(&sdio->ssb); + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); + kfree(sdio); + sdio_set_drvdata(func, NULL); +} + +static const struct sdio_device_id b43_sdio_ids[] = { + { SDIO_DEVICE(0x02d0, 0x044b) }, /* Nintendo Wii WLAN daughter card */ + { SDIO_DEVICE(0x0092, 0x0004) }, /* C-guys, Inc. EW-CG1102GC */ + { }, +}; + +static struct sdio_driver b43_sdio_driver = { + .name = "b43-sdio", + .id_table = b43_sdio_ids, + .probe = b43_sdio_probe, + .remove = b43_sdio_remove, +}; + +int b43_sdio_init(void) +{ + return sdio_register_driver(&b43_sdio_driver); +} + +void b43_sdio_exit(void) +{ + sdio_unregister_driver(&b43_sdio_driver); +} diff --git a/drivers/net/wireless/b43/sdio.h b/drivers/net/wireless/b43/sdio.h new file mode 100644 index 0000000..cd58acc --- /dev/null +++ b/drivers/net/wireless/b43/sdio.h @@ -0,0 +1,45 @@ +#ifndef B43_SDIO_H_ +#define B43_SDIO_H_ + +#include + +struct b43_wldev; + + +#ifdef CPTCFG_B43_SDIO + +struct b43_sdio { + struct ssb_bus ssb; + void *irq_handler_opaque; + void (*irq_handler)(struct b43_wldev *dev); +}; + +int b43_sdio_request_irq(struct b43_wldev *dev, + void (*handler)(struct b43_wldev *dev)); +void b43_sdio_free_irq(struct b43_wldev *dev); + +int b43_sdio_init(void); +void b43_sdio_exit(void); + + +#else /* CPTCFG_B43_SDIO */ + + +static inline int b43_sdio_request_irq(struct b43_wldev *dev, + void (*handler)(struct b43_wldev *dev)) +{ + return -ENODEV; +} +static inline void b43_sdio_free_irq(struct b43_wldev *dev) +{ +} +static inline int b43_sdio_init(void) +{ + return 0; +} +static inline void b43_sdio_exit(void) +{ +} + +#endif /* CPTCFG_B43_SDIO */ +#endif /* B43_SDIO_H_ */ diff --git a/drivers/net/wireless/b43/sysfs.c b/drivers/net/wireless/b43/sysfs.c new file mode 100644 index 0000000..8e8431d --- /dev/null +++ b/drivers/net/wireless/b43/sysfs.c @@ -0,0 +1,155 @@ +/* + + Broadcom B43 wireless driver + + SYSFS support routines + + Copyright (c) 2006 Michael Buesch + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include +#include + +#include "b43.h" +#include "sysfs.h" +#include "main.h" +#include "phy_common.h" + +#define GENERIC_FILESIZE 64 + +static int get_integer(const char *buf, size_t count) +{ + char tmp[10 + 1] = { 0 }; + int ret = -EINVAL; + + if (count == 0) + goto out; + count = min(count, (size_t) 10); + memcpy(tmp, buf, count); + ret = simple_strtol(tmp, NULL, 10); + out: + return ret; +} + +static ssize_t b43_attr_interfmode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct b43_wldev *wldev = dev_to_b43_wldev(dev); + ssize_t count = 0; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + mutex_lock(&wldev->wl->mutex); + + if (wldev->phy.type != B43_PHYTYPE_G) { + mutex_unlock(&wldev->wl->mutex); + return -ENOSYS; + } + + switch (wldev->phy.g->interfmode) { + case B43_INTERFMODE_NONE: + count = + snprintf(buf, PAGE_SIZE, + "0 (No Interference Mitigation)\n"); + break; + case B43_INTERFMODE_NONWLAN: + count = + snprintf(buf, PAGE_SIZE, + "1 (Non-WLAN Interference Mitigation)\n"); + break; + case B43_INTERFMODE_MANUALWLAN: + count = + snprintf(buf, PAGE_SIZE, + "2 (WLAN Interference Mitigation)\n"); + break; + default: + B43_WARN_ON(1); + } + + mutex_unlock(&wldev->wl->mutex); + + return count; +} + +static ssize_t b43_attr_interfmode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct b43_wldev *wldev = dev_to_b43_wldev(dev); + int err; + int mode; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + mode = get_integer(buf, count); + switch (mode) { + case 0: + mode = B43_INTERFMODE_NONE; + break; + case 1: + mode = B43_INTERFMODE_NONWLAN; + break; + case 2: + mode = B43_INTERFMODE_MANUALWLAN; + break; + case 3: + mode = B43_INTERFMODE_AUTOWLAN; + break; + default: + return -EINVAL; + } + + mutex_lock(&wldev->wl->mutex); + + if (wldev->phy.ops->interf_mitigation) { + err = wldev->phy.ops->interf_mitigation(wldev, mode); + if (err) { + b43err(wldev->wl, "Interference Mitigation not " + "supported by device\n"); + } + } else + err = -ENOSYS; + + mmiowb(); + mutex_unlock(&wldev->wl->mutex); + + return err ? err : count; +} + +static DEVICE_ATTR(interference, 0644, + b43_attr_interfmode_show, b43_attr_interfmode_store); + +int b43_sysfs_register(struct b43_wldev *wldev) +{ + struct device *dev = wldev->dev->dev; + + B43_WARN_ON(b43_status(wldev) != B43_STAT_INITIALIZED); + + return device_create_file(dev, &dev_attr_interference); +} + +void b43_sysfs_unregister(struct b43_wldev *wldev) +{ + struct device *dev = wldev->dev->dev; + + device_remove_file(dev, &dev_attr_interference); +} diff --git a/drivers/net/wireless/b43/sysfs.h b/drivers/net/wireless/b43/sysfs.h new file mode 100644 index 0000000..12bda9e --- /dev/null +++ b/drivers/net/wireless/b43/sysfs.h @@ -0,0 +1,9 @@ +#ifndef B43_SYSFS_H_ +#define B43_SYSFS_H_ + +struct b43_wldev; + +int b43_sysfs_register(struct b43_wldev *dev); +void b43_sysfs_unregister(struct b43_wldev *dev); + +#endif /* B43_SYSFS_H_ */ diff --git a/drivers/net/wireless/b43/tables.c b/drivers/net/wireless/b43/tables.c new file mode 100644 index 0000000..ea288df --- /dev/null +++ b/drivers/net/wireless/b43/tables.c @@ -0,0 +1,466 @@ +/* + + Broadcom B43 wireless driver + + Copyright (c) 2005 Martin Langer , + Copyright (c) 2005-2007 Stefano Brivio + Copyright (c) 2006, 2006 Michael Buesch + Copyright (c) 2005 Danny van Dyk + Copyright (c) 2005 Andreas Jaggi + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" +#include "tables.h" +#include "phy_g.h" + + +const u32 b43_tab_rotor[] = { + 0xFEB93FFD, 0xFEC63FFD, /* 0 */ + 0xFED23FFD, 0xFEDF3FFD, + 0xFEEC3FFE, 0xFEF83FFE, + 0xFF053FFE, 0xFF113FFE, + 0xFF1E3FFE, 0xFF2A3FFF, /* 8 */ + 0xFF373FFF, 0xFF443FFF, + 0xFF503FFF, 0xFF5D3FFF, + 0xFF693FFF, 0xFF763FFF, + 0xFF824000, 0xFF8F4000, /* 16 */ + 0xFF9B4000, 0xFFA84000, + 0xFFB54000, 0xFFC14000, + 0xFFCE4000, 0xFFDA4000, + 0xFFE74000, 0xFFF34000, /* 24 */ + 0x00004000, 0x000D4000, + 0x00194000, 0x00264000, + 0x00324000, 0x003F4000, + 0x004B4000, 0x00584000, /* 32 */ + 0x00654000, 0x00714000, + 0x007E4000, 0x008A3FFF, + 0x00973FFF, 0x00A33FFF, + 0x00B03FFF, 0x00BC3FFF, /* 40 */ + 0x00C93FFF, 0x00D63FFF, + 0x00E23FFE, 0x00EF3FFE, + 0x00FB3FFE, 0x01083FFE, + 0x01143FFE, 0x01213FFD, /* 48 */ + 0x012E3FFD, 0x013A3FFD, + 0x01473FFD, +}; + +const u32 b43_tab_retard[] = { + 0xDB93CB87, 0xD666CF64, /* 0 */ + 0xD1FDD358, 0xCDA6D826, + 0xCA38DD9F, 0xC729E2B4, + 0xC469E88E, 0xC26AEE2B, + 0xC0DEF46C, 0xC073FA62, /* 8 */ + 0xC01D00D5, 0xC0760743, + 0xC1560D1E, 0xC2E51369, + 0xC4ED18FF, 0xC7AC1ED7, + 0xCB2823B2, 0xCEFA28D9, /* 16 */ + 0xD2F62D3F, 0xD7BB3197, + 0xDCE53568, 0xE1FE3875, + 0xE7D13B35, 0xED663D35, + 0xF39B3EC4, 0xF98E3FA7, /* 24 */ + 0x00004000, 0x06723FA7, + 0x0C653EC4, 0x129A3D35, + 0x182F3B35, 0x1E023875, + 0x231B3568, 0x28453197, /* 32 */ + 0x2D0A2D3F, 0x310628D9, + 0x34D823B2, 0x38541ED7, + 0x3B1318FF, 0x3D1B1369, + 0x3EAA0D1E, 0x3F8A0743, /* 40 */ + 0x3FE300D5, 0x3F8DFA62, + 0x3F22F46C, 0x3D96EE2B, + 0x3B97E88E, 0x38D7E2B4, + 0x35C8DD9F, 0x325AD826, /* 48 */ + 0x2E03D358, 0x299ACF64, + 0x246DCB87, +}; + +const u16 b43_tab_finefreqa[] = { + 0x0082, 0x0082, 0x0102, 0x0182, /* 0 */ + 0x0202, 0x0282, 0x0302, 0x0382, + 0x0402, 0x0482, 0x0502, 0x0582, + 0x05E2, 0x0662, 0x06E2, 0x0762, + 0x07E2, 0x0842, 0x08C2, 0x0942, /* 16 */ + 0x09C2, 0x0A22, 0x0AA2, 0x0B02, + 0x0B82, 0x0BE2, 0x0C62, 0x0CC2, + 0x0D42, 0x0DA2, 0x0E02, 0x0E62, + 0x0EE2, 0x0F42, 0x0FA2, 0x1002, /* 32 */ + 0x1062, 0x10C2, 0x1122, 0x1182, + 0x11E2, 0x1242, 0x12A2, 0x12E2, + 0x1342, 0x13A2, 0x1402, 0x1442, + 0x14A2, 0x14E2, 0x1542, 0x1582, /* 48 */ + 0x15E2, 0x1622, 0x1662, 0x16C1, + 0x1701, 0x1741, 0x1781, 0x17E1, + 0x1821, 0x1861, 0x18A1, 0x18E1, + 0x1921, 0x1961, 0x19A1, 0x19E1, /* 64 */ + 0x1A21, 0x1A61, 0x1AA1, 0x1AC1, + 0x1B01, 0x1B41, 0x1B81, 0x1BA1, + 0x1BE1, 0x1C21, 0x1C41, 0x1C81, + 0x1CA1, 0x1CE1, 0x1D01, 0x1D41, /* 80 */ + 0x1D61, 0x1DA1, 0x1DC1, 0x1E01, + 0x1E21, 0x1E61, 0x1E81, 0x1EA1, + 0x1EE1, 0x1F01, 0x1F21, 0x1F41, + 0x1F81, 0x1FA1, 0x1FC1, 0x1FE1, /* 96 */ + 0x2001, 0x2041, 0x2061, 0x2081, + 0x20A1, 0x20C1, 0x20E1, 0x2101, + 0x2121, 0x2141, 0x2161, 0x2181, + 0x21A1, 0x21C1, 0x21E1, 0x2201, /* 112 */ + 0x2221, 0x2241, 0x2261, 0x2281, + 0x22A1, 0x22C1, 0x22C1, 0x22E1, + 0x2301, 0x2321, 0x2341, 0x2361, + 0x2361, 0x2381, 0x23A1, 0x23C1, /* 128 */ + 0x23E1, 0x23E1, 0x2401, 0x2421, + 0x2441, 0x2441, 0x2461, 0x2481, + 0x2481, 0x24A1, 0x24C1, 0x24C1, + 0x24E1, 0x2501, 0x2501, 0x2521, /* 144 */ + 0x2541, 0x2541, 0x2561, 0x2561, + 0x2581, 0x25A1, 0x25A1, 0x25C1, + 0x25C1, 0x25E1, 0x2601, 0x2601, + 0x2621, 0x2621, 0x2641, 0x2641, /* 160 */ + 0x2661, 0x2661, 0x2681, 0x2681, + 0x26A1, 0x26A1, 0x26C1, 0x26C1, + 0x26E1, 0x26E1, 0x2701, 0x2701, + 0x2721, 0x2721, 0x2740, 0x2740, /* 176 */ + 0x2760, 0x2760, 0x2780, 0x2780, + 0x2780, 0x27A0, 0x27A0, 0x27C0, + 0x27C0, 0x27E0, 0x27E0, 0x27E0, + 0x2800, 0x2800, 0x2820, 0x2820, /* 192 */ + 0x2820, 0x2840, 0x2840, 0x2840, + 0x2860, 0x2860, 0x2880, 0x2880, + 0x2880, 0x28A0, 0x28A0, 0x28A0, + 0x28C0, 0x28C0, 0x28C0, 0x28E0, /* 208 */ + 0x28E0, 0x28E0, 0x2900, 0x2900, + 0x2900, 0x2920, 0x2920, 0x2920, + 0x2940, 0x2940, 0x2940, 0x2960, + 0x2960, 0x2960, 0x2960, 0x2980, /* 224 */ + 0x2980, 0x2980, 0x29A0, 0x29A0, + 0x29A0, 0x29A0, 0x29C0, 0x29C0, + 0x29C0, 0x29E0, 0x29E0, 0x29E0, + 0x29E0, 0x2A00, 0x2A00, 0x2A00, /* 240 */ + 0x2A00, 0x2A20, 0x2A20, 0x2A20, + 0x2A20, 0x2A40, 0x2A40, 0x2A40, + 0x2A40, 0x2A60, 0x2A60, 0x2A60, +}; + +const u16 b43_tab_finefreqg[] = { + 0x0089, 0x02E9, 0x0409, 0x04E9, /* 0 */ + 0x05A9, 0x0669, 0x0709, 0x0789, + 0x0829, 0x08A9, 0x0929, 0x0989, + 0x0A09, 0x0A69, 0x0AC9, 0x0B29, + 0x0BA9, 0x0BE9, 0x0C49, 0x0CA9, /* 16 */ + 0x0D09, 0x0D69, 0x0DA9, 0x0E09, + 0x0E69, 0x0EA9, 0x0F09, 0x0F49, + 0x0FA9, 0x0FE9, 0x1029, 0x1089, + 0x10C9, 0x1109, 0x1169, 0x11A9, /* 32 */ + 0x11E9, 0x1229, 0x1289, 0x12C9, + 0x1309, 0x1349, 0x1389, 0x13C9, + 0x1409, 0x1449, 0x14A9, 0x14E9, + 0x1529, 0x1569, 0x15A9, 0x15E9, /* 48 */ + 0x1629, 0x1669, 0x16A9, 0x16E8, + 0x1728, 0x1768, 0x17A8, 0x17E8, + 0x1828, 0x1868, 0x18A8, 0x18E8, + 0x1928, 0x1968, 0x19A8, 0x19E8, /* 64 */ + 0x1A28, 0x1A68, 0x1AA8, 0x1AE8, + 0x1B28, 0x1B68, 0x1BA8, 0x1BE8, + 0x1C28, 0x1C68, 0x1CA8, 0x1CE8, + 0x1D28, 0x1D68, 0x1DC8, 0x1E08, /* 80 */ + 0x1E48, 0x1E88, 0x1EC8, 0x1F08, + 0x1F48, 0x1F88, 0x1FE8, 0x2028, + 0x2068, 0x20A8, 0x2108, 0x2148, + 0x2188, 0x21C8, 0x2228, 0x2268, /* 96 */ + 0x22C8, 0x2308, 0x2348, 0x23A8, + 0x23E8, 0x2448, 0x24A8, 0x24E8, + 0x2548, 0x25A8, 0x2608, 0x2668, + 0x26C8, 0x2728, 0x2787, 0x27E7, /* 112 */ + 0x2847, 0x28C7, 0x2947, 0x29A7, + 0x2A27, 0x2AC7, 0x2B47, 0x2BE7, + 0x2CA7, 0x2D67, 0x2E47, 0x2F67, + 0x3247, 0x3526, 0x3646, 0x3726, /* 128 */ + 0x3806, 0x38A6, 0x3946, 0x39E6, + 0x3A66, 0x3AE6, 0x3B66, 0x3BC6, + 0x3C45, 0x3CA5, 0x3D05, 0x3D85, + 0x3DE5, 0x3E45, 0x3EA5, 0x3EE5, /* 144 */ + 0x3F45, 0x3FA5, 0x4005, 0x4045, + 0x40A5, 0x40E5, 0x4145, 0x4185, + 0x41E5, 0x4225, 0x4265, 0x42C5, + 0x4305, 0x4345, 0x43A5, 0x43E5, /* 160 */ + 0x4424, 0x4464, 0x44C4, 0x4504, + 0x4544, 0x4584, 0x45C4, 0x4604, + 0x4644, 0x46A4, 0x46E4, 0x4724, + 0x4764, 0x47A4, 0x47E4, 0x4824, /* 176 */ + 0x4864, 0x48A4, 0x48E4, 0x4924, + 0x4964, 0x49A4, 0x49E4, 0x4A24, + 0x4A64, 0x4AA4, 0x4AE4, 0x4B23, + 0x4B63, 0x4BA3, 0x4BE3, 0x4C23, /* 192 */ + 0x4C63, 0x4CA3, 0x4CE3, 0x4D23, + 0x4D63, 0x4DA3, 0x4DE3, 0x4E23, + 0x4E63, 0x4EA3, 0x4EE3, 0x4F23, + 0x4F63, 0x4FC3, 0x5003, 0x5043, /* 208 */ + 0x5083, 0x50C3, 0x5103, 0x5143, + 0x5183, 0x51E2, 0x5222, 0x5262, + 0x52A2, 0x52E2, 0x5342, 0x5382, + 0x53C2, 0x5402, 0x5462, 0x54A2, /* 224 */ + 0x5502, 0x5542, 0x55A2, 0x55E2, + 0x5642, 0x5682, 0x56E2, 0x5722, + 0x5782, 0x57E1, 0x5841, 0x58A1, + 0x5901, 0x5961, 0x59C1, 0x5A21, /* 240 */ + 0x5AA1, 0x5B01, 0x5B81, 0x5BE1, + 0x5C61, 0x5D01, 0x5D80, 0x5E20, + 0x5EE0, 0x5FA0, 0x6080, 0x61C0, +}; + +const u16 b43_tab_noisea2[] = { + 0x0001, 0x0001, 0x0001, 0xFFFE, + 0xFFFE, 0x3FFF, 0x1000, 0x0393, +}; + +const u16 b43_tab_noisea3[] = { + 0x5E5E, 0x5E5E, 0x5E5E, 0x3F48, + 0x4C4C, 0x4C4C, 0x4C4C, 0x2D36, +}; + +const u16 b43_tab_noiseg1[] = { + 0x013C, 0x01F5, 0x031A, 0x0631, + 0x0001, 0x0001, 0x0001, 0x0001, +}; + +const u16 b43_tab_noiseg2[] = { + 0x5484, 0x3C40, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, +}; + +const u16 b43_tab_noisescalea2[] = { + 0x6767, 0x6767, 0x6767, 0x6767, /* 0 */ + 0x6767, 0x6767, 0x6767, 0x6767, + 0x6767, 0x6767, 0x6767, 0x6767, + 0x6767, 0x6700, 0x6767, 0x6767, + 0x6767, 0x6767, 0x6767, 0x6767, /* 16 */ + 0x6767, 0x6767, 0x6767, 0x6767, + 0x6767, 0x6767, 0x0067, +}; + +const u16 b43_tab_noisescalea3[] = { + 0x2323, 0x2323, 0x2323, 0x2323, /* 0 */ + 0x2323, 0x2323, 0x2323, 0x2323, + 0x2323, 0x2323, 0x2323, 0x2323, + 0x2323, 0x2300, 0x2323, 0x2323, + 0x2323, 0x2323, 0x2323, 0x2323, /* 16 */ + 0x2323, 0x2323, 0x2323, 0x2323, + 0x2323, 0x2323, 0x0023, +}; + +const u16 b43_tab_noisescaleg1[] = { + 0x6C77, 0x5162, 0x3B40, 0x3335, /* 0 */ + 0x2F2D, 0x2A2A, 0x2527, 0x1F21, + 0x1A1D, 0x1719, 0x1616, 0x1414, + 0x1414, 0x1400, 0x1414, 0x1614, + 0x1716, 0x1A19, 0x1F1D, 0x2521, /* 16 */ + 0x2A27, 0x2F2A, 0x332D, 0x3B35, + 0x5140, 0x6C62, 0x0077, +}; + +const u16 b43_tab_noisescaleg2[] = { + 0xD8DD, 0xCBD4, 0xBCC0, 0xB6B7, /* 0 */ + 0xB2B0, 0xADAD, 0xA7A9, 0x9FA1, + 0x969B, 0x9195, 0x8F8F, 0x8A8A, + 0x8A8A, 0x8A00, 0x8A8A, 0x8F8A, + 0x918F, 0x9695, 0x9F9B, 0xA7A1, /* 16 */ + 0xADA9, 0xB2AD, 0xB6B0, 0xBCB7, + 0xCBC0, 0xD8D4, 0x00DD, +}; + +const u16 b43_tab_noisescaleg3[] = { + 0xA4A4, 0xA4A4, 0xA4A4, 0xA4A4, /* 0 */ + 0xA4A4, 0xA4A4, 0xA4A4, 0xA4A4, + 0xA4A4, 0xA4A4, 0xA4A4, 0xA4A4, + 0xA4A4, 0xA400, 0xA4A4, 0xA4A4, + 0xA4A4, 0xA4A4, 0xA4A4, 0xA4A4, /* 16 */ + 0xA4A4, 0xA4A4, 0xA4A4, 0xA4A4, + 0xA4A4, 0xA4A4, 0x00A4, +}; + +const u16 b43_tab_sigmasqr1[] = { + 0x007A, 0x0075, 0x0071, 0x006C, /* 0 */ + 0x0067, 0x0063, 0x005E, 0x0059, + 0x0054, 0x0050, 0x004B, 0x0046, + 0x0042, 0x003D, 0x003D, 0x003D, + 0x003D, 0x003D, 0x003D, 0x003D, /* 16 */ + 0x003D, 0x003D, 0x003D, 0x003D, + 0x003D, 0x003D, 0x0000, 0x003D, + 0x003D, 0x003D, 0x003D, 0x003D, + 0x003D, 0x003D, 0x003D, 0x003D, /* 32 */ + 0x003D, 0x003D, 0x003D, 0x003D, + 0x0042, 0x0046, 0x004B, 0x0050, + 0x0054, 0x0059, 0x005E, 0x0063, + 0x0067, 0x006C, 0x0071, 0x0075, /* 48 */ + 0x007A, +}; + +const u16 b43_tab_sigmasqr2[] = { + 0x00DE, 0x00DC, 0x00DA, 0x00D8, /* 0 */ + 0x00D6, 0x00D4, 0x00D2, 0x00CF, + 0x00CD, 0x00CA, 0x00C7, 0x00C4, + 0x00C1, 0x00BE, 0x00BE, 0x00BE, + 0x00BE, 0x00BE, 0x00BE, 0x00BE, /* 16 */ + 0x00BE, 0x00BE, 0x00BE, 0x00BE, + 0x00BE, 0x00BE, 0x0000, 0x00BE, + 0x00BE, 0x00BE, 0x00BE, 0x00BE, + 0x00BE, 0x00BE, 0x00BE, 0x00BE, /* 32 */ + 0x00BE, 0x00BE, 0x00BE, 0x00BE, + 0x00C1, 0x00C4, 0x00C7, 0x00CA, + 0x00CD, 0x00CF, 0x00D2, 0x00D4, + 0x00D6, 0x00D8, 0x00DA, 0x00DC, /* 48 */ + 0x00DE, +}; + +const u16 b43_tab_rssiagc1[] = { + 0xFFF8, 0xFFF8, 0xFFF8, 0xFFF8, /* 0 */ + 0xFFF8, 0xFFF9, 0xFFFC, 0xFFFE, + 0xFFF8, 0xFFF8, 0xFFF8, 0xFFF8, + 0xFFF8, 0xFFF8, 0xFFF8, 0xFFF8, +}; + +const u16 b43_tab_rssiagc2[] = { + 0x0820, 0x0820, 0x0920, 0x0C38, /* 0 */ + 0x0820, 0x0820, 0x0820, 0x0820, + 0x0820, 0x0820, 0x0920, 0x0A38, + 0x0820, 0x0820, 0x0820, 0x0820, + 0x0820, 0x0820, 0x0920, 0x0A38, /* 16 */ + 0x0820, 0x0820, 0x0820, 0x0820, + 0x0820, 0x0820, 0x0920, 0x0A38, + 0x0820, 0x0820, 0x0820, 0x0820, + 0x0820, 0x0820, 0x0920, 0x0A38, /* 32 */ + 0x0820, 0x0820, 0x0820, 0x0820, + 0x0820, 0x0820, 0x0920, 0x0A38, + 0x0820, 0x0820, 0x0820, 0x0820, +}; + +static inline void assert_sizes(void) +{ + BUILD_BUG_ON(B43_TAB_ROTOR_SIZE != ARRAY_SIZE(b43_tab_rotor)); + BUILD_BUG_ON(B43_TAB_RETARD_SIZE != ARRAY_SIZE(b43_tab_retard)); + BUILD_BUG_ON(B43_TAB_FINEFREQA_SIZE != ARRAY_SIZE(b43_tab_finefreqa)); + BUILD_BUG_ON(B43_TAB_FINEFREQG_SIZE != ARRAY_SIZE(b43_tab_finefreqg)); + BUILD_BUG_ON(B43_TAB_NOISEA2_SIZE != ARRAY_SIZE(b43_tab_noisea2)); + BUILD_BUG_ON(B43_TAB_NOISEA3_SIZE != ARRAY_SIZE(b43_tab_noisea3)); + BUILD_BUG_ON(B43_TAB_NOISEG1_SIZE != ARRAY_SIZE(b43_tab_noiseg1)); + BUILD_BUG_ON(B43_TAB_NOISEG2_SIZE != ARRAY_SIZE(b43_tab_noiseg2)); + BUILD_BUG_ON(B43_TAB_NOISESCALE_SIZE != + ARRAY_SIZE(b43_tab_noisescalea2)); + BUILD_BUG_ON(B43_TAB_NOISESCALE_SIZE != + ARRAY_SIZE(b43_tab_noisescalea3)); + BUILD_BUG_ON(B43_TAB_NOISESCALE_SIZE != + ARRAY_SIZE(b43_tab_noisescaleg1)); + BUILD_BUG_ON(B43_TAB_NOISESCALE_SIZE != + ARRAY_SIZE(b43_tab_noisescaleg2)); + BUILD_BUG_ON(B43_TAB_NOISESCALE_SIZE != + ARRAY_SIZE(b43_tab_noisescaleg3)); + BUILD_BUG_ON(B43_TAB_SIGMASQR_SIZE != ARRAY_SIZE(b43_tab_sigmasqr1)); + BUILD_BUG_ON(B43_TAB_SIGMASQR_SIZE != ARRAY_SIZE(b43_tab_sigmasqr2)); + BUILD_BUG_ON(B43_TAB_RSSIAGC1_SIZE != ARRAY_SIZE(b43_tab_rssiagc1)); + BUILD_BUG_ON(B43_TAB_RSSIAGC2_SIZE != ARRAY_SIZE(b43_tab_rssiagc2)); +} + +u16 b43_ofdmtab_read16(struct b43_wldev *dev, u16 table, u16 offset) +{ + struct b43_phy_g *gphy = dev->phy.g; + u16 addr; + + addr = table + offset; + if ((gphy->ofdmtab_addr_direction != B43_OFDMTAB_DIRECTION_READ) || + (addr - 1 != gphy->ofdmtab_addr)) { + /* The hardware has a different address in memory. Update it. */ + b43_phy_write(dev, B43_PHY_OTABLECTL, addr); + gphy->ofdmtab_addr_direction = B43_OFDMTAB_DIRECTION_READ; + } + gphy->ofdmtab_addr = addr; + + return b43_phy_read(dev, B43_PHY_OTABLEI); + + /* Some compiletime assertions... */ + assert_sizes(); +} + +void b43_ofdmtab_write16(struct b43_wldev *dev, u16 table, + u16 offset, u16 value) +{ + struct b43_phy_g *gphy = dev->phy.g; + u16 addr; + + addr = table + offset; + if ((gphy->ofdmtab_addr_direction != B43_OFDMTAB_DIRECTION_WRITE) || + (addr - 1 != gphy->ofdmtab_addr)) { + /* The hardware has a different address in memory. Update it. */ + b43_phy_write(dev, B43_PHY_OTABLECTL, addr); + gphy->ofdmtab_addr_direction = B43_OFDMTAB_DIRECTION_WRITE; + } + gphy->ofdmtab_addr = addr; + b43_phy_write(dev, B43_PHY_OTABLEI, value); +} + +u32 b43_ofdmtab_read32(struct b43_wldev *dev, u16 table, u16 offset) +{ + struct b43_phy_g *gphy = dev->phy.g; + u32 ret; + u16 addr; + + addr = table + offset; + if ((gphy->ofdmtab_addr_direction != B43_OFDMTAB_DIRECTION_READ) || + (addr - 1 != gphy->ofdmtab_addr)) { + /* The hardware has a different address in memory. Update it. */ + b43_phy_write(dev, B43_PHY_OTABLECTL, addr); + gphy->ofdmtab_addr_direction = B43_OFDMTAB_DIRECTION_READ; + } + gphy->ofdmtab_addr = addr; + ret = b43_phy_read(dev, B43_PHY_OTABLEQ); + ret <<= 16; + ret |= b43_phy_read(dev, B43_PHY_OTABLEI); + + return ret; +} + +void b43_ofdmtab_write32(struct b43_wldev *dev, u16 table, + u16 offset, u32 value) +{ + struct b43_phy_g *gphy = dev->phy.g; + u16 addr; + + addr = table + offset; + if ((gphy->ofdmtab_addr_direction != B43_OFDMTAB_DIRECTION_WRITE) || + (addr - 1 != gphy->ofdmtab_addr)) { + /* The hardware has a different address in memory. Update it. */ + b43_phy_write(dev, B43_PHY_OTABLECTL, addr); + gphy->ofdmtab_addr_direction = B43_OFDMTAB_DIRECTION_WRITE; + } + gphy->ofdmtab_addr = addr; + + b43_phy_write(dev, B43_PHY_OTABLEI, value); + b43_phy_write(dev, B43_PHY_OTABLEQ, (value >> 16)); +} + +u16 b43_gtab_read(struct b43_wldev *dev, u16 table, u16 offset) +{ + b43_phy_write(dev, B43_PHY_GTABCTL, table + offset); + return b43_phy_read(dev, B43_PHY_GTABDATA); +} + +void b43_gtab_write(struct b43_wldev *dev, u16 table, u16 offset, u16 value) +{ + b43_phy_write(dev, B43_PHY_GTABCTL, table + offset); + b43_phy_write(dev, B43_PHY_GTABDATA, value); +} diff --git a/drivers/net/wireless/b43/tables.h b/drivers/net/wireless/b43/tables.h new file mode 100644 index 0000000..80e73c7 --- /dev/null +++ b/drivers/net/wireless/b43/tables.h @@ -0,0 +1,34 @@ +#ifndef B43_TABLES_H_ +#define B43_TABLES_H_ + +#define B43_TAB_ROTOR_SIZE 53 +extern const u32 b43_tab_rotor[]; +#define B43_TAB_RETARD_SIZE 53 +extern const u32 b43_tab_retard[]; +#define B43_TAB_FINEFREQA_SIZE 256 +extern const u16 b43_tab_finefreqa[]; +#define B43_TAB_FINEFREQG_SIZE 256 +extern const u16 b43_tab_finefreqg[]; +#define B43_TAB_NOISEA2_SIZE 8 +extern const u16 b43_tab_noisea2[]; +#define B43_TAB_NOISEA3_SIZE 8 +extern const u16 b43_tab_noisea3[]; +#define B43_TAB_NOISEG1_SIZE 8 +extern const u16 b43_tab_noiseg1[]; +#define B43_TAB_NOISEG2_SIZE 8 +extern const u16 b43_tab_noiseg2[]; +#define B43_TAB_NOISESCALE_SIZE 27 +extern const u16 b43_tab_noisescalea2[]; +extern const u16 b43_tab_noisescalea3[]; +extern const u16 b43_tab_noisescaleg1[]; +extern const u16 b43_tab_noisescaleg2[]; +extern const u16 b43_tab_noisescaleg3[]; +#define B43_TAB_SIGMASQR_SIZE 53 +extern const u16 b43_tab_sigmasqr1[]; +extern const u16 b43_tab_sigmasqr2[]; +#define B43_TAB_RSSIAGC1_SIZE 16 +extern const u16 b43_tab_rssiagc1[]; +#define B43_TAB_RSSIAGC2_SIZE 48 +extern const u16 b43_tab_rssiagc2[]; + +#endif /* B43_TABLES_H_ */ diff --git a/drivers/net/wireless/b43/tables_lpphy.c b/drivers/net/wireless/b43/tables_lpphy.c new file mode 100644 index 0000000..cff187c --- /dev/null +++ b/drivers/net/wireless/b43/tables_lpphy.c @@ -0,0 +1,2456 @@ +/* + + Broadcom B43 wireless driver + IEEE 802.11a/g LP-PHY and radio device data tables + + Copyright (c) 2009 Michael Buesch + Copyright (c) 2009 Gábor Stefanik + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" +#include "tables_lpphy.h" +#include "phy_common.h" +#include "phy_lp.h" + + +/* Entry of the 2062/2063 radio init table */ +struct b206x_init_tab_entry { + u16 offset; + u16 value_a; + u16 value_g; + u8 flags; +}; +#define B206X_FLAG_A 0x01 /* Flag: Init in A mode */ +#define B206X_FLAG_G 0x02 /* Flag: Init in G mode */ + +static const struct b206x_init_tab_entry b2062_init_tab[] = { + /* { .offset = B2062_N_COMM1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = 0x0001, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_COMM2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_COMM3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2062_N_COMM4, .value_a = 0x0001, .value_g = 0x0000, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_N_COMM5, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_COMM6, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_COMM7, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_COMM8, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_COMM9, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_COMM10, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_COMM11, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_COMM12, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_COMM13, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_COMM14, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_COMM15, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_PDN_CTL0, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2062_N_PDN_CTL1, .value_a = 0x0000, .value_g = 0x00CA, .flags = B206X_FLAG_G, }, + /* { .offset = B2062_N_PDN_CTL2, .value_a = 0x0018, .value_g = 0x0018, .flags = 0, }, */ + { .offset = B2062_N_PDN_CTL3, .value_a = 0x0000, .value_g = 0x0000, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_N_PDN_CTL4, .value_a = 0x0015, .value_g = 0x002A, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_N_GEN_CTL0, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_IQ_CALIB, .value_a = 0x0001, .value_g = 0x0001, .flags = 0, }, */ + { .offset = B2062_N_LGENC, .value_a = 0x00DB, .value_g = 0x00FF, .flags = B206X_FLAG_A, }, + /* { .offset = B2062_N_LGENA_LPF, .value_a = 0x0001, .value_g = 0x0001, .flags = 0, }, */ + /* { .offset = B2062_N_LGENA_BIAS0, .value_a = 0x0041, .value_g = 0x0041, .flags = 0, }, */ + /* { .offset = B2062_N_LGNEA_BIAS1, .value_a = 0x0002, .value_g = 0x0002, .flags = 0, }, */ + /* { .offset = B2062_N_LGENA_CTL0, .value_a = 0x0032, .value_g = 0x0032, .flags = 0, }, */ + /* { .offset = B2062_N_LGENA_CTL1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_LGENA_CTL2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2062_N_LGENA_TUNE0, .value_a = 0x00DD, .value_g = 0x0000, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_N_LGENA_TUNE1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2062_N_LGENA_TUNE2, .value_a = 0x00DD, .value_g = 0x0000, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_N_LGENA_TUNE3, .value_a = 0x0077, .value_g = 0x00B5, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_N_LGENA_CTL3, .value_a = 0x0000, .value_g = 0x00FF, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_N_LGENA_CTL4, .value_a = 0x001F, .value_g = 0x001F, .flags = 0, }, */ + /* { .offset = B2062_N_LGENA_CTL5, .value_a = 0x0032, .value_g = 0x0032, .flags = 0, }, */ + /* { .offset = B2062_N_LGENA_CTL6, .value_a = 0x0032, .value_g = 0x0032, .flags = 0, }, */ + { .offset = B2062_N_LGENA_CTL7, .value_a = 0x0033, .value_g = 0x0033, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_N_RXA_CTL0, .value_a = 0x0009, .value_g = 0x0009, .flags = 0, }, */ + { .offset = B2062_N_RXA_CTL1, .value_a = 0x0000, .value_g = 0x0000, .flags = B206X_FLAG_G, }, + /* { .offset = B2062_N_RXA_CTL2, .value_a = 0x0018, .value_g = 0x0018, .flags = 0, }, */ + /* { .offset = B2062_N_RXA_CTL3, .value_a = 0x0027, .value_g = 0x0027, .flags = 0, }, */ + /* { .offset = B2062_N_RXA_CTL4, .value_a = 0x0028, .value_g = 0x0028, .flags = 0, }, */ + /* { .offset = B2062_N_RXA_CTL5, .value_a = 0x0007, .value_g = 0x0007, .flags = 0, }, */ + /* { .offset = B2062_N_RXA_CTL6, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_RXA_CTL7, .value_a = 0x0008, .value_g = 0x0008, .flags = 0, }, */ + { .offset = B2062_N_RXBB_CTL0, .value_a = 0x0082, .value_g = 0x0080, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_N_RXBB_CTL1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_CTL2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_GAIN0, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2062_N_RXBB_GAIN1, .value_a = 0x0004, .value_g = 0x0004, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_N_RXBB_GAIN2, .value_a = 0x0000, .value_g = 0x0000, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_N_RXBB_GAIN3, .value_a = 0x0011, .value_g = 0x0011, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_RSSI0, .value_a = 0x0043, .value_g = 0x0043, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_RSSI1, .value_a = 0x0033, .value_g = 0x0033, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_CALIB0, .value_a = 0x0010, .value_g = 0x0010, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_CALIB1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_CALIB2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_BIAS0, .value_a = 0x0006, .value_g = 0x0006, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_BIAS1, .value_a = 0x002A, .value_g = 0x002A, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_BIAS2, .value_a = 0x00AA, .value_g = 0x00AA, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_BIAS3, .value_a = 0x0021, .value_g = 0x0021, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_BIAS4, .value_a = 0x00AA, .value_g = 0x00AA, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_BIAS5, .value_a = 0x0022, .value_g = 0x0022, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_RSSI2, .value_a = 0x0001, .value_g = 0x0001, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_RSSI3, .value_a = 0x0055, .value_g = 0x0055, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_RSSI4, .value_a = 0x0001, .value_g = 0x0001, .flags = 0, }, */ + /* { .offset = B2062_N_RXBB_RSSI5, .value_a = 0x0055, .value_g = 0x0055, .flags = 0, }, */ + /* { .offset = B2062_N_TX_CTL0, .value_a = 0x0001, .value_g = 0x0001, .flags = 0, }, */ + /* { .offset = B2062_N_TX_CTL1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_TX_CTL2, .value_a = 0x0084, .value_g = 0x0084, .flags = 0, }, */ + /* { .offset = B2062_N_TX_CTL3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2062_N_TX_CTL4, .value_a = 0x0003, .value_g = 0x0003, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_N_TX_CTL5, .value_a = 0x0002, .value_g = 0x0002, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_N_TX_CTL6, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_TX_CTL7, .value_a = 0x0058, .value_g = 0x0058, .flags = 0, }, */ + /* { .offset = B2062_N_TX_CTL8, .value_a = 0x0082, .value_g = 0x0082, .flags = 0, }, */ + /* { .offset = B2062_N_TX_CTL9, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_TX_CTL_A, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_TX_GC2G, .value_a = 0x00FF, .value_g = 0x00FF, .flags = 0, }, */ + /* { .offset = B2062_N_TX_GC5G, .value_a = 0x00FF, .value_g = 0x00FF, .flags = 0, }, */ + { .offset = B2062_N_TX_TUNE, .value_a = 0x0088, .value_g = 0x001B, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_N_TX_PAD, .value_a = 0x0088, .value_g = 0x0088, .flags = 0, }, */ + /* { .offset = B2062_N_TX_PGA, .value_a = 0x0088, .value_g = 0x0088, .flags = 0, }, */ + /* { .offset = B2062_N_TX_PADAUX, .value_a = 0x0033, .value_g = 0x0033, .flags = 0, }, */ + /* { .offset = B2062_N_TX_PGAAUX, .value_a = 0x0033, .value_g = 0x0033, .flags = 0, }, */ + /* { .offset = B2062_N_TSSI_CTL0, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_TSSI_CTL1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_TSSI_CTL2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_IQ_CALIB_CTL0, .value_a = 0x0033, .value_g = 0x0033, .flags = 0, }, */ + /* { .offset = B2062_N_IQ_CALIB_CTL1, .value_a = 0x0055, .value_g = 0x0055, .flags = 0, }, */ + /* { .offset = B2062_N_IQ_CALIB_CTL2, .value_a = 0x0032, .value_g = 0x0032, .flags = 0, }, */ + /* { .offset = B2062_N_CALIB_TS, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_CALIB_CTL0, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_CALIB_CTL1, .value_a = 0x0015, .value_g = 0x0015, .flags = 0, }, */ + /* { .offset = B2062_N_CALIB_CTL2, .value_a = 0x000F, .value_g = 0x000F, .flags = 0, }, */ + /* { .offset = B2062_N_CALIB_CTL3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_CALIB_CTL4, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_CALIB_DBG0, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_CALIB_DBG1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_CALIB_DBG2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_CALIB_DBG3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_PSENSE_CTL0, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_PSENSE_CTL1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_PSENSE_CTL2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_N_TEST_BUF0, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_COMM1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_RADIO_ID_CODE, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_COMM2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_COMM3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2062_S_COMM4, .value_a = 0x0001, .value_g = 0x0000, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_S_COMM5, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_COMM6, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_COMM7, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_COMM8, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_COMM9, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_COMM10, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_COMM11, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_COMM12, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_COMM13, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_COMM14, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_COMM15, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2062_S_PDS_CTL0, .value_a = 0x00FF, .value_g = 0x00FF, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_S_PDS_CTL1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_PDS_CTL2, .value_a = 0x008E, .value_g = 0x008E, .flags = 0, }, */ + /* { .offset = B2062_S_PDS_CTL3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_BG_CTL0, .value_a = 0x0006, .value_g = 0x0006, .flags = 0, }, */ + /* { .offset = B2062_S_BG_CTL1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_BG_CTL2, .value_a = 0x0011, .value_g = 0x0011, .flags = 0, }, */ + { .offset = B2062_S_LGENG_CTL0, .value_a = 0x00F8, .value_g = 0x00D8, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_LGENG_CTL1, .value_a = 0x003C, .value_g = 0x0024, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_S_LGENG_CTL2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_LGENG_CTL3, .value_a = 0x0041, .value_g = 0x0041, .flags = 0, }, */ + /* { .offset = B2062_S_LGENG_CTL4, .value_a = 0x0002, .value_g = 0x0002, .flags = 0, }, */ + /* { .offset = B2062_S_LGENG_CTL5, .value_a = 0x0033, .value_g = 0x0033, .flags = 0, }, */ + /* { .offset = B2062_S_LGENG_CTL6, .value_a = 0x0022, .value_g = 0x0022, .flags = 0, }, */ + /* { .offset = B2062_S_LGENG_CTL7, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2062_S_LGENG_CTL8, .value_a = 0x0088, .value_g = 0x0080, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_S_LGENG_CTL9, .value_a = 0x0088, .value_g = 0x0088, .flags = 0, }, */ + { .offset = B2062_S_LGENG_CTL10, .value_a = 0x0088, .value_g = 0x0080, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_S_LGENG_CTL11, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL0, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL1, .value_a = 0x0007, .value_g = 0x0007, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL2, .value_a = 0x00AF, .value_g = 0x00AF, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL3, .value_a = 0x0012, .value_g = 0x0012, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL4, .value_a = 0x000B, .value_g = 0x000B, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL5, .value_a = 0x005F, .value_g = 0x005F, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL6, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL7, .value_a = 0x0040, .value_g = 0x0040, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL8, .value_a = 0x0052, .value_g = 0x0052, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL9, .value_a = 0x0026, .value_g = 0x0026, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL10, .value_a = 0x0003, .value_g = 0x0003, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL11, .value_a = 0x0036, .value_g = 0x0036, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL12, .value_a = 0x0057, .value_g = 0x0057, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL13, .value_a = 0x0011, .value_g = 0x0011, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL14, .value_a = 0x0075, .value_g = 0x0075, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL15, .value_a = 0x00B4, .value_g = 0x00B4, .flags = 0, }, */ + /* { .offset = B2062_S_REFPLL_CTL16, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2062_S_RFPLL_CTL0, .value_a = 0x0098, .value_g = 0x0098, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL1, .value_a = 0x0010, .value_g = 0x0010, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_S_RFPLL_CTL2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_RFPLL_CTL3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_RFPLL_CTL4, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2062_S_RFPLL_CTL5, .value_a = 0x0043, .value_g = 0x0043, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL6, .value_a = 0x0047, .value_g = 0x0047, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL7, .value_a = 0x000C, .value_g = 0x000C, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL8, .value_a = 0x0011, .value_g = 0x0011, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL9, .value_a = 0x0011, .value_g = 0x0011, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL10, .value_a = 0x000E, .value_g = 0x000E, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL11, .value_a = 0x0008, .value_g = 0x0008, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL12, .value_a = 0x0033, .value_g = 0x0033, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL13, .value_a = 0x000A, .value_g = 0x000A, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL14, .value_a = 0x0006, .value_g = 0x0006, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_S_RFPLL_CTL15, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_RFPLL_CTL16, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_RFPLL_CTL17, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2062_S_RFPLL_CTL18, .value_a = 0x003E, .value_g = 0x003E, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL19, .value_a = 0x0013, .value_g = 0x0013, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_S_RFPLL_CTL20, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2062_S_RFPLL_CTL21, .value_a = 0x0062, .value_g = 0x0062, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL22, .value_a = 0x0007, .value_g = 0x0007, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL23, .value_a = 0x0016, .value_g = 0x0016, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL24, .value_a = 0x005C, .value_g = 0x005C, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL25, .value_a = 0x0095, .value_g = 0x0095, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_S_RFPLL_CTL26, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_RFPLL_CTL27, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_RFPLL_CTL28, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_RFPLL_CTL29, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2062_S_RFPLL_CTL30, .value_a = 0x00A0, .value_g = 0x00A0, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL31, .value_a = 0x0004, .value_g = 0x0004, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_S_RFPLL_CTL32, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2062_S_RFPLL_CTL33, .value_a = 0x00CC, .value_g = 0x00CC, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2062_S_RFPLL_CTL34, .value_a = 0x0007, .value_g = 0x0007, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2062_S_RXG_CNT0, .value_a = 0x0010, .value_g = 0x0010, .flags = 0, }, */ + /* { .offset = B2062_S_RXG_CNT1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_RXG_CNT2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_RXG_CNT3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_RXG_CNT4, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_RXG_CNT5, .value_a = 0x0055, .value_g = 0x0055, .flags = 0, }, */ + /* { .offset = B2062_S_RXG_CNT6, .value_a = 0x0055, .value_g = 0x0055, .flags = 0, }, */ + /* { .offset = B2062_S_RXG_CNT7, .value_a = 0x0005, .value_g = 0x0005, .flags = 0, }, */ + { .offset = B2062_S_RXG_CNT8, .value_a = 0x000F, .value_g = 0x000F, .flags = B206X_FLAG_A, }, + /* { .offset = B2062_S_RXG_CNT9, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_RXG_CNT10, .value_a = 0x0055, .value_g = 0x0055, .flags = 0, }, */ + /* { .offset = B2062_S_RXG_CNT11, .value_a = 0x0066, .value_g = 0x0066, .flags = 0, }, */ + /* { .offset = B2062_S_RXG_CNT12, .value_a = 0x0055, .value_g = 0x0055, .flags = 0, }, */ + /* { .offset = B2062_S_RXG_CNT13, .value_a = 0x0044, .value_g = 0x0044, .flags = 0, }, */ + /* { .offset = B2062_S_RXG_CNT14, .value_a = 0x00A0, .value_g = 0x00A0, .flags = 0, }, */ + /* { .offset = B2062_S_RXG_CNT15, .value_a = 0x0004, .value_g = 0x0004, .flags = 0, }, */ + /* { .offset = B2062_S_RXG_CNT16, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2062_S_RXG_CNT17, .value_a = 0x0055, .value_g = 0x0055, .flags = 0, }, */ +}; + +static const struct b206x_init_tab_entry b2063_init_tab[] = { + { .offset = B2063_COMM1, .value_a = 0x0000, .value_g = 0x0000, .flags = B206X_FLAG_G, }, + /* { .offset = B2063_COMM2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_COMM3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_COMM4, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_COMM5, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_COMM6, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_COMM7, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_COMM8, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_COMM9, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2063_COMM10, .value_a = 0x0001, .value_g = 0x0000, .flags = B206X_FLAG_A, }, + /* { .offset = B2063_COMM11, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_COMM12, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_COMM13, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_COMM14, .value_a = 0x0006, .value_g = 0x0006, .flags = 0, }, */ + /* { .offset = B2063_COMM15, .value_a = 0x000f, .value_g = 0x000f, .flags = 0, }, */ + { .offset = B2063_COMM16, .value_a = 0x0000, .value_g = 0x0000, .flags = B206X_FLAG_G, }, + { .offset = B2063_COMM17, .value_a = 0x0000, .value_g = 0x0000, .flags = B206X_FLAG_G, }, + { .offset = B2063_COMM18, .value_a = 0x0000, .value_g = 0x0000, .flags = B206X_FLAG_G, }, + { .offset = B2063_COMM19, .value_a = 0x0000, .value_g = 0x0000, .flags = B206X_FLAG_G, }, + { .offset = B2063_COMM20, .value_a = 0x0000, .value_g = 0x0000, .flags = B206X_FLAG_G, }, + { .offset = B2063_COMM21, .value_a = 0x0000, .value_g = 0x0000, .flags = B206X_FLAG_G, }, + { .offset = B2063_COMM22, .value_a = 0x0000, .value_g = 0x0000, .flags = B206X_FLAG_G, }, + { .offset = B2063_COMM23, .value_a = 0x0000, .value_g = 0x0000, .flags = B206X_FLAG_G, }, + { .offset = B2063_COMM24, .value_a = 0x0000, .value_g = 0x0000, .flags = B206X_FLAG_G, }, + /* { .offset = B2063_PWR_SWITCH_CTL, .value_a = 0x007f, .value_g = 0x007f, .flags = 0, }, */ + /* { .offset = B2063_PLL_SP1, .value_a = 0x003f, .value_g = 0x003f, .flags = 0, }, */ + /* { .offset = B2063_PLL_SP2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2063_LOGEN_SP1, .value_a = 0x00e8, .value_g = 0x00d4, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2063_LOGEN_SP2, .value_a = 0x00a7, .value_g = 0x0053, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_LOGEN_SP3, .value_a = 0x00ff, .value_g = 0x00ff, .flags = 0, }, */ + { .offset = B2063_LOGEN_SP4, .value_a = 0x00f0, .value_g = 0x000f, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_LOGEN_SP5, .value_a = 0x0001, .value_g = 0x0001, .flags = 0, }, */ + { .offset = B2063_G_RX_SP1, .value_a = 0x001f, .value_g = 0x005e, .flags = B206X_FLAG_G, }, + { .offset = B2063_G_RX_SP2, .value_a = 0x007f, .value_g = 0x007e, .flags = B206X_FLAG_G, }, + { .offset = B2063_G_RX_SP3, .value_a = 0x0030, .value_g = 0x00f0, .flags = B206X_FLAG_G, }, + /* { .offset = B2063_G_RX_SP4, .value_a = 0x0035, .value_g = 0x0035, .flags = 0, }, */ + /* { .offset = B2063_G_RX_SP5, .value_a = 0x003f, .value_g = 0x003f, .flags = 0, }, */ + /* { .offset = B2063_G_RX_SP6, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2063_G_RX_SP7, .value_a = 0x007f, .value_g = 0x007f, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_G_RX_SP8, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_G_RX_SP9, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2063_G_RX_SP10, .value_a = 0x000c, .value_g = 0x000c, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_G_RX_SP11, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2063_A_RX_SP1, .value_a = 0x003c, .value_g = 0x003f, .flags = B206X_FLAG_A, }, + { .offset = B2063_A_RX_SP2, .value_a = 0x00fc, .value_g = 0x00fe, .flags = B206X_FLAG_A, }, + /* { .offset = B2063_A_RX_SP3, .value_a = 0x00ff, .value_g = 0x00ff, .flags = 0, }, */ + /* { .offset = B2063_A_RX_SP4, .value_a = 0x00ff, .value_g = 0x00ff, .flags = 0, }, */ + /* { .offset = B2063_A_RX_SP5, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_A_RX_SP6, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2063_A_RX_SP7, .value_a = 0x0008, .value_g = 0x0008, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_RX_BB_SP1, .value_a = 0x000f, .value_g = 0x000f, .flags = 0, }, */ + /* { .offset = B2063_RX_BB_SP2, .value_a = 0x0022, .value_g = 0x0022, .flags = 0, }, */ + /* { .offset = B2063_RX_BB_SP3, .value_a = 0x00a8, .value_g = 0x00a8, .flags = 0, }, */ + { .offset = B2063_RX_BB_SP4, .value_a = 0x0060, .value_g = 0x0060, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_RX_BB_SP5, .value_a = 0x0011, .value_g = 0x0011, .flags = 0, }, */ + /* { .offset = B2063_RX_BB_SP6, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_RX_BB_SP7, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2063_RX_BB_SP8, .value_a = 0x0030, .value_g = 0x0030, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_TX_RF_SP1, .value_a = 0x0001, .value_g = 0x0001, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_SP2, .value_a = 0x0003, .value_g = 0x0003, .flags = 0, }, */ + { .offset = B2063_TX_RF_SP3, .value_a = 0x000c, .value_g = 0x000b, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2063_TX_RF_SP4, .value_a = 0x0010, .value_g = 0x000f, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_TX_RF_SP5, .value_a = 0x000f, .value_g = 0x000f, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_SP6, .value_a = 0x0080, .value_g = 0x0080, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_SP7, .value_a = 0x0068, .value_g = 0x0068, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_SP8, .value_a = 0x0068, .value_g = 0x0068, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_SP9, .value_a = 0x0080, .value_g = 0x0080, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_SP10, .value_a = 0x00ff, .value_g = 0x00ff, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_SP11, .value_a = 0x0003, .value_g = 0x0003, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_SP12, .value_a = 0x0038, .value_g = 0x0038, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_SP13, .value_a = 0x00ff, .value_g = 0x00ff, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_SP14, .value_a = 0x0038, .value_g = 0x0038, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_SP15, .value_a = 0x00c0, .value_g = 0x00c0, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_SP16, .value_a = 0x00ff, .value_g = 0x00ff, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_SP17, .value_a = 0x00ff, .value_g = 0x00ff, .flags = 0, }, */ + { .offset = B2063_PA_SP1, .value_a = 0x003d, .value_g = 0x00fd, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_PA_SP2, .value_a = 0x000c, .value_g = 0x000c, .flags = 0, }, */ + /* { .offset = B2063_PA_SP3, .value_a = 0x0096, .value_g = 0x0096, .flags = 0, }, */ + /* { .offset = B2063_PA_SP4, .value_a = 0x005a, .value_g = 0x005a, .flags = 0, }, */ + /* { .offset = B2063_PA_SP5, .value_a = 0x007f, .value_g = 0x007f, .flags = 0, }, */ + /* { .offset = B2063_PA_SP6, .value_a = 0x007f, .value_g = 0x007f, .flags = 0, }, */ + /* { .offset = B2063_PA_SP7, .value_a = 0x0033, .value_g = 0x0033, .flags = 0, }, */ + { .offset = B2063_TX_BB_SP1, .value_a = 0x0002, .value_g = 0x0002, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_TX_BB_SP2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_TX_BB_SP3, .value_a = 0x0030, .value_g = 0x0030, .flags = 0, }, */ + /* { .offset = B2063_REG_SP1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2063_BANDGAP_CTL1, .value_a = 0x0056, .value_g = 0x0056, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_BANDGAP_CTL2, .value_a = 0x0006, .value_g = 0x0006, .flags = 0, }, */ + /* { .offset = B2063_LPO_CTL1, .value_a = 0x000e, .value_g = 0x000e, .flags = 0, }, */ + /* { .offset = B2063_RC_CALIB_CTL1, .value_a = 0x007e, .value_g = 0x007e, .flags = 0, }, */ + /* { .offset = B2063_RC_CALIB_CTL2, .value_a = 0x0015, .value_g = 0x0015, .flags = 0, }, */ + /* { .offset = B2063_RC_CALIB_CTL3, .value_a = 0x000f, .value_g = 0x000f, .flags = 0, }, */ + /* { .offset = B2063_RC_CALIB_CTL4, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_RC_CALIB_CTL5, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_RC_CALIB_CTL6, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_RC_CALIB_CTL7, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_RC_CALIB_CTL8, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_RC_CALIB_CTL9, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_RC_CALIB_CTL10, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_CALNRST, .value_a = 0x0004, .value_g = 0x0004, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_IN_PLL1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_IN_PLL2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_CP1, .value_a = 0x00cf, .value_g = 0x00cf, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_CP2, .value_a = 0x0059, .value_g = 0x0059, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_CP3, .value_a = 0x0007, .value_g = 0x0007, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_CP4, .value_a = 0x0042, .value_g = 0x0042, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_CTL1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_LF1, .value_a = 0x00db, .value_g = 0x00db, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_LF2, .value_a = 0x0094, .value_g = 0x0094, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_LF3, .value_a = 0x0028, .value_g = 0x0028, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_LF4, .value_a = 0x0063, .value_g = 0x0063, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_SG1, .value_a = 0x0007, .value_g = 0x0007, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_SG2, .value_a = 0x00d3, .value_g = 0x00d3, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_SG3, .value_a = 0x00b1, .value_g = 0x00b1, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_SG4, .value_a = 0x003b, .value_g = 0x003b, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_SG5, .value_a = 0x0006, .value_g = 0x0006, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_VCO1, .value_a = 0x0058, .value_g = 0x0058, .flags = 0, }, */ + { .offset = B2063_PLL_JTAG_PLL_VCO2, .value_a = 0x00f7, .value_g = 0x00f7, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_PLL_JTAG_PLL_VCO_CALIB1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_VCO_CALIB2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_VCO_CALIB3, .value_a = 0x0002, .value_g = 0x0002, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_VCO_CALIB4, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_VCO_CALIB5, .value_a = 0x0009, .value_g = 0x0009, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_VCO_CALIB6, .value_a = 0x0005, .value_g = 0x0005, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_VCO_CALIB7, .value_a = 0x0016, .value_g = 0x0016, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_VCO_CALIB8, .value_a = 0x006b, .value_g = 0x006b, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_VCO_CALIB9, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_VCO_CALIB10, .value_a = 0x00b3, .value_g = 0x00b3, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_XTAL_12, .value_a = 0x0004, .value_g = 0x0004, .flags = 0, }, */ + /* { .offset = B2063_PLL_JTAG_PLL_XTAL3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_ACL1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_ACL2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_ACL3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_ACL4, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_ACL5, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_INPUTS, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_CTL1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_CTL2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_CTL3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_WAITCNT, .value_a = 0x0002, .value_g = 0x0002, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_OVR1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_OVR2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_OVAL1, .value_a = 0x0066, .value_g = 0x0066, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_OVAL2, .value_a = 0x0066, .value_g = 0x0066, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_OVAL3, .value_a = 0x0066, .value_g = 0x0066, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_OVAL4, .value_a = 0x0066, .value_g = 0x0066, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_OVAL5, .value_a = 0x0066, .value_g = 0x0066, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_OVAL6, .value_a = 0x0066, .value_g = 0x0066, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_OVAL7, .value_a = 0x0066, .value_g = 0x0066, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_CALVLD1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_CALVLD2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_CVAL1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_CVAL2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_CVAL3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_CVAL4, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_CVAL5, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_CVAL6, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LO_CALIB_CVAL7, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_CALIB_EN, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_PEAKDET1, .value_a = 0x00ff, .value_g = 0x00ff, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_RCCR1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_VCOBUF1, .value_a = 0x0060, .value_g = 0x0060, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_MIXER1, .value_a = 0x0066, .value_g = 0x0066, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_MIXER2, .value_a = 0x000c, .value_g = 0x000c, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_BUF1, .value_a = 0x0066, .value_g = 0x0066, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_BUF2, .value_a = 0x000c, .value_g = 0x000c, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_DIV1, .value_a = 0x0001, .value_g = 0x0001, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_DIV2, .value_a = 0x0066, .value_g = 0x0066, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_DIV3, .value_a = 0x0066, .value_g = 0x0066, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_CBUFRX1, .value_a = 0x0066, .value_g = 0x0066, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_CBUFRX2, .value_a = 0x0066, .value_g = 0x0066, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_CBUFTX1, .value_a = 0x0066, .value_g = 0x0066, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_CBUFTX2, .value_a = 0x0066, .value_g = 0x0066, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_IDAC1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_SPARE1, .value_a = 0x0001, .value_g = 0x0001, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_SPARE2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_LOGEN_SPARE3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_G_RX_1ST1, .value_a = 0x0033, .value_g = 0x0033, .flags = 0, }, */ + /* { .offset = B2063_G_RX_1ST2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_G_RX_1ST3, .value_a = 0x0005, .value_g = 0x0005, .flags = 0, }, */ + /* { .offset = B2063_G_RX_2ND1, .value_a = 0x0030, .value_g = 0x0030, .flags = 0, }, */ + /* { .offset = B2063_G_RX_2ND2, .value_a = 0x0055, .value_g = 0x0055, .flags = 0, }, */ + /* { .offset = B2063_G_RX_2ND3, .value_a = 0x0033, .value_g = 0x0033, .flags = 0, }, */ + /* { .offset = B2063_G_RX_2ND4, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_G_RX_2ND5, .value_a = 0x0033, .value_g = 0x0033, .flags = 0, }, */ + /* { .offset = B2063_G_RX_2ND6, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_G_RX_2ND7, .value_a = 0x0035, .value_g = 0x0035, .flags = 0, }, */ + /* { .offset = B2063_G_RX_2ND8, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_G_RX_PS1, .value_a = 0x0033, .value_g = 0x0033, .flags = 0, }, */ + /* { .offset = B2063_G_RX_PS2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_G_RX_PS3, .value_a = 0x0033, .value_g = 0x0033, .flags = 0, }, */ + /* { .offset = B2063_G_RX_PS4, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_G_RX_PS5, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_G_RX_MIX1, .value_a = 0x0044, .value_g = 0x0044, .flags = 0, }, */ + /* { .offset = B2063_G_RX_MIX2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2063_G_RX_MIX3, .value_a = 0x0071, .value_g = 0x0071, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2063_G_RX_MIX4, .value_a = 0x0071, .value_g = 0x0071, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_G_RX_MIX5, .value_a = 0x0003, .value_g = 0x0003, .flags = 0, }, */ + /* { .offset = B2063_G_RX_MIX6, .value_a = 0x0088, .value_g = 0x0088, .flags = 0, }, */ + /* { .offset = B2063_G_RX_MIX7, .value_a = 0x0044, .value_g = 0x0044, .flags = 0, }, */ + /* { .offset = B2063_G_RX_MIX8, .value_a = 0x0001, .value_g = 0x0001, .flags = 0, }, */ + /* { .offset = B2063_G_RX_PDET1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_G_RX_SPARES1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_G_RX_SPARES2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_G_RX_SPARES3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_A_RX_1ST1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2063_A_RX_1ST2, .value_a = 0x00f0, .value_g = 0x0030, .flags = B206X_FLAG_A, }, + /* { .offset = B2063_A_RX_1ST3, .value_a = 0x0005, .value_g = 0x0005, .flags = 0, }, */ + /* { .offset = B2063_A_RX_1ST4, .value_a = 0x0033, .value_g = 0x0033, .flags = 0, }, */ + /* { .offset = B2063_A_RX_1ST5, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_A_RX_2ND1, .value_a = 0x0005, .value_g = 0x0005, .flags = 0, }, */ + /* { .offset = B2063_A_RX_2ND2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_A_RX_2ND3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_A_RX_2ND4, .value_a = 0x0005, .value_g = 0x0005, .flags = 0, }, */ + /* { .offset = B2063_A_RX_2ND5, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_A_RX_2ND6, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_A_RX_2ND7, .value_a = 0x0005, .value_g = 0x0005, .flags = 0, }, */ + /* { .offset = B2063_A_RX_PS1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_A_RX_PS2, .value_a = 0x0033, .value_g = 0x0033, .flags = 0, }, */ + /* { .offset = B2063_A_RX_PS3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_A_RX_PS4, .value_a = 0x0033, .value_g = 0x0033, .flags = 0, }, */ + /* { .offset = B2063_A_RX_PS5, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2063_A_RX_PS6, .value_a = 0x0077, .value_g = 0x0077, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_A_RX_MIX1, .value_a = 0x0088, .value_g = 0x0088, .flags = 0, }, */ + /* { .offset = B2063_A_RX_MIX2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_A_RX_MIX3, .value_a = 0x0044, .value_g = 0x0044, .flags = 0, }, */ + { .offset = B2063_A_RX_MIX4, .value_a = 0x0003, .value_g = 0x0003, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2063_A_RX_MIX5, .value_a = 0x000f, .value_g = 0x000f, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + { .offset = B2063_A_RX_MIX6, .value_a = 0x000f, .value_g = 0x000f, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_A_RX_MIX7, .value_a = 0x0044, .value_g = 0x0044, .flags = 0, }, */ + /* { .offset = B2063_A_RX_MIX8, .value_a = 0x0001, .value_g = 0x0001, .flags = 0, }, */ + /* { .offset = B2063_A_RX_PWRDET1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_A_RX_SPARE1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_A_RX_SPARE2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_A_RX_SPARE3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2063_RX_TIA_CTL1, .value_a = 0x0077, .value_g = 0x0077, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_RX_TIA_CTL2, .value_a = 0x0058, .value_g = 0x0058, .flags = 0, }, */ + { .offset = B2063_RX_TIA_CTL3, .value_a = 0x0077, .value_g = 0x0077, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_RX_TIA_CTL4, .value_a = 0x0058, .value_g = 0x0058, .flags = 0, }, */ + /* { .offset = B2063_RX_TIA_CTL5, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_RX_TIA_CTL6, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_RX_BB_CTL1, .value_a = 0x0074, .value_g = 0x0074, .flags = 0, }, */ + { .offset = B2063_RX_BB_CTL2, .value_a = 0x0004, .value_g = 0x0004, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_RX_BB_CTL3, .value_a = 0x00a2, .value_g = 0x00a2, .flags = 0, }, */ + /* { .offset = B2063_RX_BB_CTL4, .value_a = 0x00aa, .value_g = 0x00aa, .flags = 0, }, */ + /* { .offset = B2063_RX_BB_CTL5, .value_a = 0x0024, .value_g = 0x0024, .flags = 0, }, */ + /* { .offset = B2063_RX_BB_CTL6, .value_a = 0x00a9, .value_g = 0x00a9, .flags = 0, }, */ + /* { .offset = B2063_RX_BB_CTL7, .value_a = 0x0028, .value_g = 0x0028, .flags = 0, }, */ + /* { .offset = B2063_RX_BB_CTL8, .value_a = 0x0010, .value_g = 0x0010, .flags = 0, }, */ + /* { .offset = B2063_RX_BB_CTL9, .value_a = 0x0055, .value_g = 0x0055, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_CTL1, .value_a = 0x0080, .value_g = 0x0080, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_IDAC_LO_RF_I, .value_a = 0x0088, .value_g = 0x0088, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_IDAC_LO_RF_Q, .value_a = 0x0088, .value_g = 0x0088, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_IDAC_LO_BB_I, .value_a = 0x0088, .value_g = 0x0088, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_IDAC_LO_BB_Q, .value_a = 0x0088, .value_g = 0x0088, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_CTL2, .value_a = 0x0080, .value_g = 0x0080, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_CTL3, .value_a = 0x0038, .value_g = 0x0038, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_CTL4, .value_a = 0x00b8, .value_g = 0x00b8, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_CTL5, .value_a = 0x0080, .value_g = 0x0080, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_CTL6, .value_a = 0x0038, .value_g = 0x0038, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_CTL7, .value_a = 0x0078, .value_g = 0x0078, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_CTL8, .value_a = 0x00c0, .value_g = 0x00c0, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_CTL9, .value_a = 0x0003, .value_g = 0x0003, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_CTL10, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_CTL14, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_TX_RF_CTL15, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2063_PA_CTL1, .value_a = 0x0000, .value_g = 0x0004, .flags = B206X_FLAG_A, }, + /* { .offset = B2063_PA_CTL2, .value_a = 0x000c, .value_g = 0x000c, .flags = 0, }, */ + /* { .offset = B2063_PA_CTL3, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_PA_CTL4, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_PA_CTL5, .value_a = 0x0096, .value_g = 0x0096, .flags = 0, }, */ + /* { .offset = B2063_PA_CTL6, .value_a = 0x0077, .value_g = 0x0077, .flags = 0, }, */ + /* { .offset = B2063_PA_CTL7, .value_a = 0x005a, .value_g = 0x005a, .flags = 0, }, */ + /* { .offset = B2063_PA_CTL8, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_PA_CTL9, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_PA_CTL10, .value_a = 0x0021, .value_g = 0x0021, .flags = 0, }, */ + /* { .offset = B2063_PA_CTL11, .value_a = 0x0070, .value_g = 0x0070, .flags = 0, }, */ + /* { .offset = B2063_PA_CTL12, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_PA_CTL13, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_TX_BB_CTL1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_TX_BB_CTL2, .value_a = 0x00b3, .value_g = 0x00b3, .flags = 0, }, */ + /* { .offset = B2063_TX_BB_CTL3, .value_a = 0x0055, .value_g = 0x0055, .flags = 0, }, */ + /* { .offset = B2063_TX_BB_CTL4, .value_a = 0x000b, .value_g = 0x000b, .flags = 0, }, */ + /* { .offset = B2063_GPIO_CTL1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + { .offset = B2063_VREG_CTL1, .value_a = 0x0003, .value_g = 0x0003, .flags = B206X_FLAG_A | B206X_FLAG_G, }, + /* { .offset = B2063_AMUX_CTL1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_IQ_CALIB_GVAR, .value_a = 0x00b3, .value_g = 0x00b3, .flags = 0, }, */ + /* { .offset = B2063_IQ_CALIB_CTL1, .value_a = 0x0055, .value_g = 0x0055, .flags = 0, }, */ + /* { .offset = B2063_IQ_CALIB_CTL2, .value_a = 0x0030, .value_g = 0x0030, .flags = 0, }, */ + /* { .offset = B2063_TEMPSENSE_CTL1, .value_a = 0x0046, .value_g = 0x0046, .flags = 0, }, */ + /* { .offset = B2063_TEMPSENSE_CTL2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_TX_RX_LOOPBACK1, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_TX_RX_LOOPBACK2, .value_a = 0x0000, .value_g = 0x0000, .flags = 0, }, */ + /* { .offset = B2063_EXT_TSSI_CTL1, .value_a = 0x0021, .value_g = 0x0021, .flags = 0, }, */ + /* { .offset = B2063_EXT_TSSI_CTL2, .value_a = 0x0023, .value_g = 0x0023, .flags = 0, }, */ + /* { .offset = B2063_AFE_CTL , .value_a = 0x0002, .value_g = 0x0002, .flags = 0, }, */ +}; + +void b2062_upload_init_table(struct b43_wldev *dev) +{ + const struct b206x_init_tab_entry *e; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(b2062_init_tab); i++) { + e = &b2062_init_tab[i]; + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + if (!(e->flags & B206X_FLAG_G)) + continue; + b43_radio_write(dev, e->offset, e->value_g); + } else { + if (!(e->flags & B206X_FLAG_A)) + continue; + b43_radio_write(dev, e->offset, e->value_a); + } + } +} + +void b2063_upload_init_table(struct b43_wldev *dev) +{ + const struct b206x_init_tab_entry *e; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(b2063_init_tab); i++) { + e = &b2063_init_tab[i]; + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + if (!(e->flags & B206X_FLAG_G)) + continue; + b43_radio_write(dev, e->offset, e->value_g); + } else { + if (!(e->flags & B206X_FLAG_A)) + continue; + b43_radio_write(dev, e->offset, e->value_a); + } + } +} + +u32 b43_lptab_read(struct b43_wldev *dev, u32 offset) +{ + u32 type, value; + + type = offset & B43_LPTAB_TYPEMASK; + offset &= ~B43_LPTAB_TYPEMASK; + B43_WARN_ON(offset > 0xFFFF); + + switch (type) { + case B43_LPTAB_8BIT: + b43_phy_write(dev, B43_LPPHY_TABLE_ADDR, offset); + value = b43_phy_read(dev, B43_LPPHY_TABLEDATALO) & 0xFF; + break; + case B43_LPTAB_16BIT: + b43_phy_write(dev, B43_LPPHY_TABLE_ADDR, offset); + value = b43_phy_read(dev, B43_LPPHY_TABLEDATALO); + break; + case B43_LPTAB_32BIT: + b43_phy_write(dev, B43_LPPHY_TABLE_ADDR, offset); + value = b43_phy_read(dev, B43_LPPHY_TABLEDATAHI); + value <<= 16; + value |= b43_phy_read(dev, B43_LPPHY_TABLEDATALO); + break; + default: + B43_WARN_ON(1); + value = 0; + } + + return value; +} + +void b43_lptab_read_bulk(struct b43_wldev *dev, u32 offset, + unsigned int nr_elements, void *_data) +{ + u32 type; + u8 *data = _data; + unsigned int i; + + type = offset & B43_LPTAB_TYPEMASK; + offset &= ~B43_LPTAB_TYPEMASK; + B43_WARN_ON(offset > 0xFFFF); + + b43_phy_write(dev, B43_LPPHY_TABLE_ADDR, offset); + + for (i = 0; i < nr_elements; i++) { + switch (type) { + case B43_LPTAB_8BIT: + *data = b43_phy_read(dev, B43_LPPHY_TABLEDATALO) & 0xFF; + data++; + break; + case B43_LPTAB_16BIT: + *((u16 *)data) = b43_phy_read(dev, B43_LPPHY_TABLEDATALO); + data += 2; + break; + case B43_LPTAB_32BIT: + *((u32 *)data) = b43_phy_read(dev, B43_LPPHY_TABLEDATAHI); + *((u32 *)data) <<= 16; + *((u32 *)data) |= b43_phy_read(dev, B43_LPPHY_TABLEDATALO); + data += 4; + break; + default: + B43_WARN_ON(1); + } + } +} + +void b43_lptab_write(struct b43_wldev *dev, u32 offset, u32 value) +{ + u32 type; + + type = offset & B43_LPTAB_TYPEMASK; + offset &= ~B43_LPTAB_TYPEMASK; + B43_WARN_ON(offset > 0xFFFF); + + switch (type) { + case B43_LPTAB_8BIT: + B43_WARN_ON(value & ~0xFF); + b43_phy_write(dev, B43_LPPHY_TABLE_ADDR, offset); + b43_phy_write(dev, B43_LPPHY_TABLEDATALO, value); + break; + case B43_LPTAB_16BIT: + B43_WARN_ON(value & ~0xFFFF); + b43_phy_write(dev, B43_LPPHY_TABLE_ADDR, offset); + b43_phy_write(dev, B43_LPPHY_TABLEDATALO, value); + break; + case B43_LPTAB_32BIT: + b43_phy_write(dev, B43_LPPHY_TABLE_ADDR, offset); + b43_phy_write(dev, B43_LPPHY_TABLEDATAHI, value >> 16); + b43_phy_write(dev, B43_LPPHY_TABLEDATALO, value); + break; + default: + B43_WARN_ON(1); + } +} + +void b43_lptab_write_bulk(struct b43_wldev *dev, u32 offset, + unsigned int nr_elements, const void *_data) +{ + u32 type, value; + const u8 *data = _data; + unsigned int i; + + type = offset & B43_LPTAB_TYPEMASK; + offset &= ~B43_LPTAB_TYPEMASK; + B43_WARN_ON(offset > 0xFFFF); + + b43_phy_write(dev, B43_LPPHY_TABLE_ADDR, offset); + + for (i = 0; i < nr_elements; i++) { + switch (type) { + case B43_LPTAB_8BIT: + value = *data; + data++; + B43_WARN_ON(value & ~0xFF); + b43_phy_write(dev, B43_LPPHY_TABLEDATALO, value); + break; + case B43_LPTAB_16BIT: + value = *((u16 *)data); + data += 2; + B43_WARN_ON(value & ~0xFFFF); + b43_phy_write(dev, B43_LPPHY_TABLEDATALO, value); + break; + case B43_LPTAB_32BIT: + value = *((u32 *)data); + data += 4; + b43_phy_write(dev, B43_LPPHY_TABLEDATAHI, value >> 16); + b43_phy_write(dev, B43_LPPHY_TABLEDATALO, value); + break; + default: + B43_WARN_ON(1); + } + } +} + +static const u8 lpphy_min_sig_sq_table[] = { + 0xde, 0xdc, 0xda, 0xd8, 0xd6, 0xd4, 0xd2, 0xcf, 0xcd, + 0xca, 0xc7, 0xc4, 0xc1, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0x00, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xc1, 0xc4, 0xc7, 0xca, 0xcd, + 0xcf, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, +}; + +static const u16 lpphy_rev01_noise_scale_table[] = { + 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, + 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa400, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, + 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0x00a4, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4c00, 0x2d36, + 0x0000, 0x0000, 0x4c00, 0x2d36, +}; + +static const u16 lpphy_rev2plus_noise_scale_table[] = { + 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, + 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, + 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x0000, + 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, + 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, + 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, 0x00a4, + 0x00a4, +}; + +static const u16 lpphy_crs_gain_nft_table[] = { + 0x0366, 0x036a, 0x036f, 0x0364, 0x0367, 0x036d, 0x0374, 0x037f, 0x036f, + 0x037b, 0x038a, 0x0378, 0x0367, 0x036d, 0x0375, 0x0381, 0x0374, 0x0381, + 0x0392, 0x03a9, 0x03c4, 0x03e1, 0x0001, 0x001f, 0x0040, 0x005e, 0x007f, + 0x009e, 0x00bd, 0x00dd, 0x00fd, 0x011d, 0x013d, +}; + +static const u16 lpphy_rev01_filter_control_table[] = { + 0xa0fc, 0x10fc, 0x10db, 0x20b7, 0xff93, 0x10bf, 0x109b, 0x2077, 0xff53, + 0x0127, +}; + +static const u32 lpphy_rev2plus_filter_control_table[] = { + 0x000141fc, 0x000021fc, 0x000021b7, 0x0000416f, 0x0001ff27, 0x0000217f, + 0x00002137, 0x000040ef, 0x0001fea7, 0x0000024f, +}; + +static const u32 lpphy_rev01_ps_control_table[] = { + 0x00010000, 0x000000a0, 0x00040000, 0x00000048, 0x08080101, 0x00000080, + 0x08080101, 0x00000040, 0x08080101, 0x000000c0, 0x08a81501, 0x000000c0, + 0x0fe8fd01, 0x000000c0, 0x08300105, 0x000000c0, 0x08080201, 0x000000c0, + 0x08280205, 0x000000c0, 0xe80802fe, 0x000000c7, 0x28080206, 0x000000c0, + 0x08080202, 0x000000c0, 0x0ba87602, 0x000000c0, 0x1068013d, 0x000000c0, + 0x10280105, 0x000000c0, 0x08880102, 0x000000c0, 0x08280106, 0x000000c0, + 0xe80801fd, 0x000000c7, 0xa8080115, 0x000000c0, +}; + +static const u32 lpphy_rev2plus_ps_control_table[] = { + 0x00e38e08, 0x00e08e38, 0x00000000, 0x00000000, 0x00000000, 0x00002080, + 0x00006180, 0x00003002, 0x00000040, 0x00002042, 0x00180047, 0x00080043, + 0x00000041, 0x000020c1, 0x00046006, 0x00042002, 0x00040000, 0x00002003, + 0x00180006, 0x00080002, +}; + +static const u8 lpphy_pll_fraction_table[] = { + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, +}; + +static const u16 lpphy_iqlo_cal_table[] = { + 0x0200, 0x0300, 0x0400, 0x0600, 0x0800, 0x0b00, 0x1000, 0x1001, 0x1002, + 0x1003, 0x1004, 0x1005, 0x1006, 0x1007, 0x1707, 0x2007, 0x2d07, 0x4007, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0200, 0x0300, 0x0400, 0x0600, + 0x0800, 0x0b00, 0x1000, 0x1001, 0x1002, 0x1003, 0x1004, 0x1005, 0x1006, + 0x1007, 0x1707, 0x2007, 0x2d07, 0x4007, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, +}; + +static const u16 lpphy_rev0_ofdm_cck_gain_table[] = { + 0x0001, 0x0001, 0x0001, 0x0001, 0x1001, 0x2001, 0x3001, 0x4001, 0x5001, + 0x6001, 0x7001, 0x7011, 0x7021, 0x2035, 0x2045, 0x2055, 0x2065, 0x2075, + 0x006d, 0x007d, 0x014d, 0x015d, 0x115d, 0x035d, 0x135d, 0x055d, 0x155d, + 0x0d5d, 0x1d5d, 0x2d5d, 0x555d, 0x655d, 0x755d, +}; + +static const u16 lpphy_rev1_ofdm_cck_gain_table[] = { + 0x5000, 0x6000, 0x7000, 0x0001, 0x1001, 0x2001, 0x3001, 0x4001, 0x5001, + 0x6001, 0x7001, 0x7011, 0x7021, 0x2035, 0x2045, 0x2055, 0x2065, 0x2075, + 0x006d, 0x007d, 0x014d, 0x015d, 0x115d, 0x035d, 0x135d, 0x055d, 0x155d, + 0x0d5d, 0x1d5d, 0x2d5d, 0x555d, 0x655d, 0x755d, +}; + +static const u16 lpphy_gain_delta_table[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, +}; + +static const u32 lpphy_tx_power_control_table[] = { + 0x00000050, 0x0000004f, 0x0000004e, 0x0000004d, 0x0000004c, 0x0000004b, + 0x0000004a, 0x00000049, 0x00000048, 0x00000047, 0x00000046, 0x00000045, + 0x00000044, 0x00000043, 0x00000042, 0x00000041, 0x00000040, 0x0000003f, + 0x0000003e, 0x0000003d, 0x0000003c, 0x0000003b, 0x0000003a, 0x00000039, + 0x00000038, 0x00000037, 0x00000036, 0x00000035, 0x00000034, 0x00000033, + 0x00000032, 0x00000031, 0x00000030, 0x0000002f, 0x0000002e, 0x0000002d, + 0x0000002c, 0x0000002b, 0x0000002a, 0x00000029, 0x00000028, 0x00000027, + 0x00000026, 0x00000025, 0x00000024, 0x00000023, 0x00000022, 0x00000021, + 0x00000020, 0x0000001f, 0x0000001e, 0x0000001d, 0x0000001c, 0x0000001b, + 0x0000001a, 0x00000019, 0x00000018, 0x00000017, 0x00000016, 0x00000015, + 0x00000014, 0x00000013, 0x00000012, 0x00000011, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x000075a0, 0x000075a0, 0x000075a1, 0x000075a1, 0x000075a2, 0x000075a2, + 0x000075a3, 0x000075a3, 0x000074b0, 0x000074b0, 0x000074b1, 0x000074b1, + 0x000074b2, 0x000074b2, 0x000074b3, 0x000074b3, 0x00006d20, 0x00006d20, + 0x00006d21, 0x00006d21, 0x00006d22, 0x00006d22, 0x00006d23, 0x00006d23, + 0x00004660, 0x00004660, 0x00004661, 0x00004661, 0x00004662, 0x00004662, + 0x00004663, 0x00004663, 0x00003e60, 0x00003e60, 0x00003e61, 0x00003e61, + 0x00003e62, 0x00003e62, 0x00003e63, 0x00003e63, 0x00003660, 0x00003660, + 0x00003661, 0x00003661, 0x00003662, 0x00003662, 0x00003663, 0x00003663, + 0x00002e60, 0x00002e60, 0x00002e61, 0x00002e61, 0x00002e62, 0x00002e62, + 0x00002e63, 0x00002e63, 0x00002660, 0x00002660, 0x00002661, 0x00002661, + 0x00002662, 0x00002662, 0x00002663, 0x00002663, 0x000025e0, 0x000025e0, + 0x000025e1, 0x000025e1, 0x000025e2, 0x000025e2, 0x000025e3, 0x000025e3, + 0x00001de0, 0x00001de0, 0x00001de1, 0x00001de1, 0x00001de2, 0x00001de2, + 0x00001de3, 0x00001de3, 0x00001d60, 0x00001d60, 0x00001d61, 0x00001d61, + 0x00001d62, 0x00001d62, 0x00001d63, 0x00001d63, 0x00001560, 0x00001560, + 0x00001561, 0x00001561, 0x00001562, 0x00001562, 0x00001563, 0x00001563, + 0x00000d60, 0x00000d60, 0x00000d61, 0x00000d61, 0x00000d62, 0x00000d62, + 0x00000d63, 0x00000d63, 0x00000ce0, 0x00000ce0, 0x00000ce1, 0x00000ce1, + 0x00000ce2, 0x00000ce2, 0x00000ce3, 0x00000ce3, 0x00000e10, 0x00000e10, + 0x00000e11, 0x00000e11, 0x00000e12, 0x00000e12, 0x00000e13, 0x00000e13, + 0x00000bf0, 0x00000bf0, 0x00000bf1, 0x00000bf1, 0x00000bf2, 0x00000bf2, + 0x00000bf3, 0x00000bf3, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x04200000, 0x04000000, + 0x04200000, 0x04000000, 0x04200000, 0x04000000, 0x000000ff, 0x000002fc, + 0x0000fa08, 0x00000305, 0x00000206, 0x00000304, 0x0000fb04, 0x0000fcff, + 0x000005fb, 0x0000fd01, 0x00000401, 0x00000006, 0x0000ff03, 0x000007fc, + 0x0000fc08, 0x00000203, 0x0000fffb, 0x00000600, 0x0000fa01, 0x0000fc03, + 0x0000fe06, 0x0000fe00, 0x00000102, 0x000007fd, 0x000004fb, 0x000006ff, + 0x000004fd, 0x0000fdfa, 0x000007fb, 0x0000fdfa, 0x0000fa06, 0x00000500, + 0x0000f902, 0x000007fa, 0x0000fafa, 0x00000500, 0x000007fa, 0x00000700, + 0x00000305, 0x000004ff, 0x00000801, 0x00000503, 0x000005f9, 0x00000404, + 0x0000fb08, 0x000005fd, 0x00000501, 0x00000405, 0x0000fb03, 0x000007fc, + 0x00000403, 0x00000303, 0x00000402, 0x0000faff, 0x0000fe05, 0x000005fd, + 0x0000fe01, 0x000007fa, 0x00000202, 0x00000504, 0x00000102, 0x000008fe, + 0x0000fa04, 0x0000fafc, 0x0000fe08, 0x000000f9, 0x000002fa, 0x000003fe, + 0x00000304, 0x000004f9, 0x00000100, 0x0000fd06, 0x000008fc, 0x00000701, + 0x00000504, 0x0000fdfe, 0x0000fdfc, 0x000003fe, 0x00000704, 0x000002fc, + 0x000004f9, 0x0000fdfd, 0x0000fa07, 0x00000205, 0x000003fd, 0x000005fb, + 0x000004f9, 0x00000804, 0x0000fc06, 0x0000fcf9, 0x00000100, 0x0000fe05, + 0x00000408, 0x0000fb02, 0x00000304, 0x000006fe, 0x000004fa, 0x00000305, + 0x000008fc, 0x00000102, 0x000001fd, 0x000004fc, 0x0000fe03, 0x00000701, + 0x000001fb, 0x000001f9, 0x00000206, 0x000006fd, 0x00000508, 0x00000700, + 0x00000304, 0x000005fe, 0x000005ff, 0x0000fa04, 0x00000303, 0x0000fefb, + 0x000007f9, 0x0000fefc, 0x000004fd, 0x000005fc, 0x0000fffd, 0x0000fc08, + 0x0000fbf9, 0x0000fd07, 0x000008fb, 0x0000fe02, 0x000006fb, 0x00000702, +}; + +static const u32 lpphy_gain_idx_table[] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x10000001, 0x00000000, 0x20000082, 0x00000000, 0x40000104, 0x00000000, + 0x60004207, 0x00000001, 0x7000838a, 0x00000001, 0xd021050d, 0x00000001, + 0xe041c683, 0x00000001, 0x50828805, 0x00000000, 0x80e34288, 0x00000000, + 0xb144040b, 0x00000000, 0xe1a6058e, 0x00000000, 0x12064711, 0x00000001, + 0xb0a18612, 0x00000010, 0xe1024794, 0x00000010, 0x11630915, 0x00000011, + 0x31c3ca1b, 0x00000011, 0xc1848a9c, 0x00000018, 0xf1e50da0, 0x00000018, + 0x22468e21, 0x00000019, 0x4286d023, 0x00000019, 0xa347d0a4, 0x00000019, + 0xb36811a6, 0x00000019, 0xf3e89227, 0x00000019, 0x0408d329, 0x0000001a, + 0x244953aa, 0x0000001a, 0x346994ab, 0x0000001a, 0x54aa152c, 0x0000001a, + 0x64ca55ad, 0x0000001a, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x10000001, 0x00000000, 0x20000082, 0x00000000, + 0x40000104, 0x00000000, 0x60004207, 0x00000001, 0x7000838a, 0x00000001, + 0xd021050d, 0x00000001, 0xe041c683, 0x00000001, 0x50828805, 0x00000000, + 0x80e34288, 0x00000000, 0xb144040b, 0x00000000, 0xe1a6058e, 0x00000000, + 0x12064711, 0x00000001, 0xb0a18612, 0x00000010, 0xe1024794, 0x00000010, + 0x11630915, 0x00000011, 0x31c3ca1b, 0x00000011, 0xc1848a9c, 0x00000018, + 0xf1e50da0, 0x00000018, 0x22468e21, 0x00000019, 0x4286d023, 0x00000019, + 0xa347d0a4, 0x00000019, 0xb36811a6, 0x00000019, 0xf3e89227, 0x00000019, + 0x0408d329, 0x0000001a, 0x244953aa, 0x0000001a, 0x346994ab, 0x0000001a, + 0x54aa152c, 0x0000001a, 0x64ca55ad, 0x0000001a, +}; + +static const u16 lpphy_aux_gain_idx_table[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0001, 0x0002, 0x0004, 0x0016, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0016, +}; + +static const u32 lpphy_gain_value_table[] = { + 0x00000008, 0x0000000e, 0x00000014, 0x0000001a, 0x000000fb, 0x00000004, + 0x00000008, 0x0000000d, 0x00000001, 0x00000004, 0x00000007, 0x0000000a, + 0x0000000d, 0x00000010, 0x00000012, 0x00000015, 0x00000000, 0x00000006, + 0x0000000c, 0x00000000, 0x00000000, 0x00000000, 0x00000012, 0x00000000, + 0x00000000, 0x00000000, 0x00000018, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x0000001e, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000003, 0x00000006, 0x00000009, 0x0000000c, 0x0000000f, + 0x00000012, 0x00000015, 0x00000018, 0x0000001b, 0x0000001e, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000009, 0x000000f1, + 0x00000000, 0x00000000, +}; + +static const u16 lpphy_gain_table[] = { + 0x0000, 0x0400, 0x0800, 0x0802, 0x0804, 0x0806, 0x0807, 0x0808, 0x080a, + 0x080b, 0x080c, 0x080e, 0x080f, 0x0810, 0x0812, 0x0813, 0x0814, 0x0816, + 0x0817, 0x081a, 0x081b, 0x081f, 0x0820, 0x0824, 0x0830, 0x0834, 0x0837, + 0x083b, 0x083f, 0x0840, 0x0844, 0x0857, 0x085b, 0x085f, 0x08d7, 0x08db, + 0x08df, 0x0957, 0x095b, 0x095f, 0x0b57, 0x0b5b, 0x0b5f, 0x0f5f, 0x135f, + 0x175f, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, +}; + +static const u32 lpphy_a0_gain_idx_table[] = { + 0x001111e0, 0x00652051, 0x00606055, 0x005b005a, 0x00555060, 0x00511065, + 0x004c806b, 0x0047d072, 0x00444078, 0x00400080, 0x003ca087, 0x0039408f, + 0x0035e098, 0x0032e0a1, 0x003030aa, 0x002d80b4, 0x002ae0bf, 0x002880ca, + 0x002640d6, 0x002410e3, 0x002220f0, 0x002020ff, 0x001e510e, 0x001ca11e, + 0x001b012f, 0x00199140, 0x00182153, 0x0016c168, 0x0015817d, 0x00145193, + 0x001321ab, 0x001211c5, 0x001111e0, 0x001021fc, 0x000f321a, 0x000e523a, + 0x000d925c, 0x000cd27f, 0x000c12a5, 0x000b62cd, 0x000ac2f8, 0x000a2325, + 0x00099355, 0x00091387, 0x000883bd, 0x000813f5, 0x0007a432, 0x00073471, + 0x0006c4b5, 0x000664fc, 0x00061547, 0x0005b598, 0x000565ec, 0x00051646, + 0x0004d6a5, 0x0004870a, 0x00044775, 0x000407e6, 0x0003d85e, 0x000398dd, + 0x00036963, 0x000339f2, 0x00030a89, 0x0002db28, +}; + +static const u16 lpphy_a0_aux_gain_idx_table[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0002, 0x0014, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0002, 0x0014, +}; + +static const u32 lpphy_a0_gain_value_table[] = { + 0x00000008, 0x0000000e, 0x00000014, 0x0000001a, 0x000000fb, 0x00000004, + 0x00000008, 0x0000000d, 0x00000001, 0x00000004, 0x00000007, 0x0000000a, + 0x0000000d, 0x00000010, 0x00000012, 0x00000015, 0x00000000, 0x00000006, + 0x0000000c, 0x00000000, 0x00000000, 0x00000000, 0x00000012, 0x00000000, + 0x00000000, 0x00000000, 0x00000018, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x0000001e, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000003, 0x00000006, 0x00000009, 0x0000000c, 0x0000000f, + 0x00000012, 0x00000015, 0x00000018, 0x0000001b, 0x0000001e, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0000000f, 0x000000f7, + 0x00000000, 0x00000000, +}; + +static const u16 lpphy_a0_gain_table[] = { + 0x0000, 0x0002, 0x0004, 0x0006, 0x0007, 0x0008, 0x000a, 0x000b, 0x000c, + 0x000e, 0x000f, 0x0010, 0x0012, 0x0013, 0x0014, 0x0016, 0x0017, 0x001a, + 0x001b, 0x001f, 0x0020, 0x0024, 0x0030, 0x0034, 0x0037, 0x003b, 0x003f, + 0x0040, 0x0044, 0x0057, 0x005b, 0x005f, 0x00d7, 0x00db, 0x00df, 0x0157, + 0x015b, 0x015f, 0x0357, 0x035b, 0x035f, 0x075f, 0x0b5f, 0x0f5f, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, +}; + +static const u16 lpphy_sw_control_table[] = { + 0x0128, 0x0128, 0x0009, 0x0009, 0x0028, 0x0028, 0x0028, 0x0028, 0x0128, + 0x0128, 0x0009, 0x0009, 0x0028, 0x0028, 0x0028, 0x0028, 0x0009, 0x0009, + 0x0009, 0x0009, 0x0009, 0x0009, 0x0009, 0x0009, 0x0018, 0x0018, 0x0018, + 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0128, 0x0128, 0x0009, 0x0009, + 0x0028, 0x0028, 0x0028, 0x0028, 0x0128, 0x0128, 0x0009, 0x0009, 0x0028, + 0x0028, 0x0028, 0x0028, 0x0009, 0x0009, 0x0009, 0x0009, 0x0009, 0x0009, + 0x0009, 0x0009, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, + 0x0018, +}; + +static const u8 lpphy_hf_table[] = { + 0x4b, 0x36, 0x24, 0x18, 0x49, 0x34, 0x23, 0x17, 0x48, + 0x33, 0x23, 0x17, 0x48, 0x33, 0x23, 0x17, +}; + +static const u32 lpphy_papd_eps_table[] = { + 0x00000000, 0x00013ffc, 0x0001dff3, 0x0001bff0, 0x00023fe9, 0x00021fdf, + 0x00028fdf, 0x00033fd2, 0x00039fcb, 0x00043fc7, 0x0004efc2, 0x00055fb5, + 0x0005cfb0, 0x00063fa8, 0x00068fa3, 0x00071f98, 0x0007ef92, 0x00084f8b, + 0x0008df82, 0x00097f77, 0x0009df69, 0x000a3f62, 0x000adf57, 0x000b6f4c, + 0x000bff41, 0x000c9f39, 0x000cff30, 0x000dbf27, 0x000e4f1e, 0x000edf16, + 0x000f7f13, 0x00102f11, 0x00110f10, 0x0011df11, 0x0012ef15, 0x00143f1c, + 0x00158f27, 0x00172f35, 0x00193f47, 0x001baf5f, 0x001e6f7e, 0x0021cfa4, + 0x0025bfd2, 0x002a2008, 0x002fb047, 0x00360090, 0x003d40e0, 0x0045c135, + 0x004fb189, 0x005ae1d7, 0x0067221d, 0x0075025a, 0x007ff291, 0x007ff2bf, + 0x007ff2e3, 0x007ff2ff, 0x007ff315, 0x007ff329, 0x007ff33f, 0x007ff356, + 0x007ff36e, 0x007ff39c, 0x007ff441, 0x007ff506, +}; + +static const u32 lpphy_papd_mult_table[] = { + 0x001111e0, 0x00652051, 0x00606055, 0x005b005a, 0x00555060, 0x00511065, + 0x004c806b, 0x0047d072, 0x00444078, 0x00400080, 0x003ca087, 0x0039408f, + 0x0035e098, 0x0032e0a1, 0x003030aa, 0x002d80b4, 0x002ae0bf, 0x002880ca, + 0x002640d6, 0x002410e3, 0x002220f0, 0x002020ff, 0x001e510e, 0x001ca11e, + 0x001b012f, 0x00199140, 0x00182153, 0x0016c168, 0x0015817d, 0x00145193, + 0x001321ab, 0x001211c5, 0x001111e0, 0x001021fc, 0x000f321a, 0x000e523a, + 0x000d925c, 0x000cd27f, 0x000c12a5, 0x000b62cd, 0x000ac2f8, 0x000a2325, + 0x00099355, 0x00091387, 0x000883bd, 0x000813f5, 0x0007a432, 0x00073471, + 0x0006c4b5, 0x000664fc, 0x00061547, 0x0005b598, 0x000565ec, 0x00051646, + 0x0004d6a5, 0x0004870a, 0x00044775, 0x000407e6, 0x0003d85e, 0x000398dd, + 0x00036963, 0x000339f2, 0x00030a89, 0x0002db28, +}; + +static struct lpphy_tx_gain_table_entry lpphy_rev0_nopa_tx_gain_table[] = { + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 152, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 147, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 143, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 139, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 135, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 131, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 128, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 124, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 121, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 117, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 114, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 111, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 107, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 104, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 101, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 99, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 96, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 93, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 90, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 88, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 85, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 83, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 81, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 78, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 76, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 74, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 71, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 69, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 67, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 58, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 58, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 71, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 69, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 67, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 58, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 56, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 73, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 71, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 69, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 67, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 73, }, + { .gm = 7, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 71, }, +}; + +static struct lpphy_tx_gain_table_entry lpphy_rev0_2ghz_tx_gain_table[] = { + { .gm = 4, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 63, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 61, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 57, }, + { .gm = 4, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 61, }, + { .gm = 4, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 73, }, + { .gm = 4, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 71, }, + { .gm = 4, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 69, }, + { .gm = 4, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 67, }, + { .gm = 4, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 65, }, + { .gm = 4, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 63, }, + { .gm = 4, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 61, }, + { .gm = 4, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 65, }, + { .gm = 4, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 63, }, + { .gm = 4, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 61, }, + { .gm = 4, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 73, }, + { .gm = 4, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 71, }, + { .gm = 4, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 69, }, + { .gm = 4, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 67, }, + { .gm = 4, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 65, }, + { .gm = 4, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 63, }, + { .gm = 4, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 61, }, + { .gm = 4, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 10, .pad = 6, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 10, .pad = 6, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 10, .pad = 6, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 10, .pad = 6, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 10, .pad = 6, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 10, .pad = 6, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 10, .pad = 6, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 10, .pad = 6, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 10, .pad = 5, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 10, .pad = 5, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 10, .pad = 5, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 10, .pad = 5, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 10, .pad = 5, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 10, .pad = 5, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 10, .pad = 5, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 10, .pad = 5, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 9, .pad = 5, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 9, .pad = 5, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 9, .pad = 5, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 9, .pad = 5, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 9, .pad = 5, .dac = 0, .bb_mult = 63, }, + { .gm = 4, .pga = 9, .pad = 5, .dac = 0, .bb_mult = 61, }, + { .gm = 4, .pga = 9, .pad = 5, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 9, .pad = 4, .dac = 0, .bb_mult = 71, }, + { .gm = 4, .pga = 9, .pad = 4, .dac = 0, .bb_mult = 69, }, + { .gm = 4, .pga = 9, .pad = 4, .dac = 0, .bb_mult = 67, }, + { .gm = 4, .pga = 9, .pad = 4, .dac = 0, .bb_mult = 65, }, + { .gm = 4, .pga = 9, .pad = 4, .dac = 0, .bb_mult = 63, }, + { .gm = 4, .pga = 9, .pad = 4, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 9, .pad = 4, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 9, .pad = 4, .dac = 0, .bb_mult = 58, }, + { .gm = 4, .pga = 8, .pad = 4, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 8, .pad = 4, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 8, .pad = 4, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 8, .pad = 4, .dac = 0, .bb_mult = 65, }, + { .gm = 4, .pga = 8, .pad = 4, .dac = 0, .bb_mult = 63, }, + { .gm = 4, .pga = 8, .pad = 4, .dac = 0, .bb_mult = 61, }, + { .gm = 4, .pga = 8, .pad = 4, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 7, .pad = 4, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 7, .pad = 4, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 7, .pad = 4, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 7, .pad = 4, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 7, .pad = 4, .dac = 0, .bb_mult = 61, }, + { .gm = 4, .pga = 7, .pad = 4, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 7, .pad = 3, .dac = 0, .bb_mult = 67, }, + { .gm = 4, .pga = 7, .pad = 3, .dac = 0, .bb_mult = 65, }, + { .gm = 4, .pga = 7, .pad = 3, .dac = 0, .bb_mult = 63, }, + { .gm = 4, .pga = 7, .pad = 3, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 7, .pad = 3, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 6, .pad = 3, .dac = 0, .bb_mult = 65, }, + { .gm = 4, .pga = 6, .pad = 3, .dac = 0, .bb_mult = 63, }, + { .gm = 4, .pga = 6, .pad = 3, .dac = 0, .bb_mult = 61, }, + { .gm = 4, .pga = 6, .pad = 3, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 6, .pad = 3, .dac = 0, .bb_mult = 58, }, + { .gm = 4, .pga = 5, .pad = 3, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 5, .pad = 3, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 5, .pad = 3, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 5, .pad = 3, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 5, .pad = 3, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 5, .pad = 3, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 5, .pad = 3, .dac = 0, .bb_mult = 57, }, + { .gm = 4, .pga = 4, .pad = 2, .dac = 0, .bb_mult = 83, }, + { .gm = 4, .pga = 4, .pad = 2, .dac = 0, .bb_mult = 81, }, + { .gm = 4, .pga = 4, .pad = 2, .dac = 0, .bb_mult = 78, }, + { .gm = 4, .pga = 4, .pad = 2, .dac = 0, .bb_mult = 76, }, + { .gm = 4, .pga = 4, .pad = 2, .dac = 0, .bb_mult = 74, }, + { .gm = 4, .pga = 4, .pad = 2, .dac = 0, .bb_mult = 72, }, +}; + +static struct lpphy_tx_gain_table_entry lpphy_rev0_5ghz_tx_gain_table[] = { + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 99, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 96, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 93, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 90, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 88, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 85, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 83, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 81, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 78, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 76, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 74, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 55, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 58, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 56, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 55, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 71, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 69, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 67, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 58, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 56, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 73, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 71, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 69, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 67, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 58, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 71, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 69, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 67, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 58, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 56, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 58, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 69, }, + { .gm = 7, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 67, }, + { .gm = 7, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 60, }, +}; + +static struct lpphy_tx_gain_table_entry lpphy_rev1_nopa_tx_gain_table[] = { + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 152, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 147, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 143, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 139, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 135, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 131, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 128, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 124, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 121, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 117, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 114, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 111, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 107, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 104, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 101, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 99, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 96, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 93, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 90, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 88, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 85, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 83, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 81, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 78, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 76, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 74, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 71, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 69, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 67, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 58, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 58, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 71, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 69, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 67, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 58, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 56, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 73, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 71, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 69, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 67, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 73, }, + { .gm = 7, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 71, }, +}; + +static struct lpphy_tx_gain_table_entry lpphy_rev1_2ghz_tx_gain_table[] = { + { .gm = 4, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 90, }, + { .gm = 4, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 88, }, + { .gm = 4, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 85, }, + { .gm = 4, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 83, }, + { .gm = 4, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 81, }, + { .gm = 4, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 78, }, + { .gm = 4, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 76, }, + { .gm = 4, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 74, }, + { .gm = 4, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 63, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 61, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 57, }, + { .gm = 4, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 61, }, + { .gm = 4, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 73, }, + { .gm = 4, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 71, }, + { .gm = 4, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 69, }, + { .gm = 4, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 67, }, + { .gm = 4, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 65, }, + { .gm = 4, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 63, }, + { .gm = 4, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 61, }, + { .gm = 4, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 65, }, + { .gm = 4, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 63, }, + { .gm = 4, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 61, }, + { .gm = 4, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 59, }, + { .gm = 4, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 73, }, + { .gm = 4, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 71, }, + { .gm = 4, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 69, }, + { .gm = 4, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 67, }, + { .gm = 4, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 65, }, + { .gm = 4, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 63, }, + { .gm = 4, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 61, }, + { .gm = 4, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 60, }, + { .gm = 4, .pga = 10, .pad = 6, .dac = 0, .bb_mult = 72, }, + { .gm = 4, .pga = 10, .pad = 6, .dac = 0, .bb_mult = 70, }, + { .gm = 4, .pga = 10, .pad = 6, .dac = 0, .bb_mult = 68, }, + { .gm = 4, .pga = 10, .pad = 6, .dac = 0, .bb_mult = 66, }, + { .gm = 4, .pga = 10, .pad = 6, .dac = 0, .bb_mult = 64, }, + { .gm = 4, .pga = 10, .pad = 6, .dac = 0, .bb_mult = 62, }, + { .gm = 4, .pga = 10, .pad = 6, .dac = 0, .bb_mult = 60, }, +}; + +static struct lpphy_tx_gain_table_entry lpphy_rev1_5ghz_tx_gain_table[] = { + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 99, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 96, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 93, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 90, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 88, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 85, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 83, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 81, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 78, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 76, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 74, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 15, .pad = 15, .dac = 0, .bb_mult = 55, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 58, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 56, }, + { .gm = 7, .pga = 15, .pad = 14, .dac = 0, .bb_mult = 55, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 71, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 69, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 67, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 58, }, + { .gm = 7, .pga = 15, .pad = 13, .dac = 0, .bb_mult = 56, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 72, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 15, .pad = 12, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 73, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 71, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 69, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 67, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 11, .dac = 0, .bb_mult = 58, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 71, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 69, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 67, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 15, .pad = 10, .dac = 0, .bb_mult = 58, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 15, .pad = 9, .dac = 0, .bb_mult = 56, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 14, .pad = 9, .dac = 0, .bb_mult = 58, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 13, .pad = 9, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 60, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 13, .pad = 8, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 12, .pad = 8, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 12, .pad = 7, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 70, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 68, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 66, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 61, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 59, }, + { .gm = 7, .pga = 11, .pad = 7, .dac = 0, .bb_mult = 57, }, + { .gm = 7, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 69, }, + { .gm = 7, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 67, }, + { .gm = 7, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 65, }, + { .gm = 7, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 63, }, + { .gm = 7, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 62, }, + { .gm = 7, .pga = 11, .pad = 6, .dac = 0, .bb_mult = 60, }, +}; + +static struct lpphy_tx_gain_table_entry lpphy_rev2_nopa_tx_gain_table[] = { + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 152, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 147, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 143, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 139, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 135, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 131, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 128, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 124, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 121, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 117, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 114, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 111, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 107, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 104, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 101, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 99, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 96, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 93, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 90, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 88, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 85, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 83, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 81, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 78, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 76, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 74, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 72, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 70, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 68, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 66, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 197, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 192, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 186, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 181, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 176, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 171, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 166, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 161, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 157, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 152, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 148, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 144, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 140, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 136, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 132, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 128, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 124, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 121, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 117, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 114, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 111, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 108, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 105, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 102, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 99, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 96, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 93, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 91, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 88, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 86, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 83, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 81, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 79, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 76, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 74, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 72, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 70, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 68, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 66, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 64, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 248, .pad = 64, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 248, .pad = 62, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 241, .pad = 62, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 241, .pad = 60, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 234, .pad = 60, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 234, .pad = 59, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 227, .pad = 59, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 227, .pad = 57, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 221, .pad = 57, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 221, .pad = 55, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 215, .pad = 55, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 215, .pad = 54, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 208, .pad = 54, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 208, .pad = 52, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 203, .pad = 52, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 203, .pad = 51, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 197, .pad = 51, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 197, .pad = 49, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 191, .pad = 49, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 191, .pad = 48, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 186, .pad = 48, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 186, .pad = 47, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 181, .pad = 47, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 181, .pad = 45, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 175, .pad = 45, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 175, .pad = 44, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 170, .pad = 44, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 170, .pad = 43, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 166, .pad = 43, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 166, .pad = 42, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 161, .pad = 42, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 161, .pad = 40, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 156, .pad = 40, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 156, .pad = 39, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 152, .pad = 39, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 152, .pad = 38, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 148, .pad = 38, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 148, .pad = 37, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 143, .pad = 37, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 143, .pad = 36, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 139, .pad = 36, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 139, .pad = 35, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 135, .pad = 35, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 135, .pad = 34, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 132, .pad = 34, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 132, .pad = 33, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 128, .pad = 33, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 128, .pad = 32, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 124, .pad = 32, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 124, .pad = 31, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 121, .pad = 31, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 121, .pad = 30, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 117, .pad = 30, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 117, .pad = 29, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 114, .pad = 29, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 114, .pad = 29, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 111, .pad = 29, .dac = 0, .bb_mult = 64, }, +}; + +static struct lpphy_tx_gain_table_entry lpphy_rev2_2ghz_tx_gain_table[] = { + { .gm = 7, .pga = 99, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 96, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 93, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 90, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 88, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 85, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 83, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 81, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 78, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 76, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 74, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 72, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 70, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 68, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 66, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 64, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 64, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 62, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 62, .pad = 248, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 60, .pad = 248, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 60, .pad = 241, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 59, .pad = 241, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 59, .pad = 234, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 57, .pad = 234, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 57, .pad = 227, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 55, .pad = 227, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 55, .pad = 221, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 54, .pad = 221, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 54, .pad = 215, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 52, .pad = 215, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 52, .pad = 208, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 51, .pad = 208, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 51, .pad = 203, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 49, .pad = 203, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 49, .pad = 197, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 48, .pad = 197, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 48, .pad = 191, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 47, .pad = 191, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 47, .pad = 186, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 45, .pad = 186, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 45, .pad = 181, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 44, .pad = 181, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 44, .pad = 175, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 43, .pad = 175, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 43, .pad = 170, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 42, .pad = 170, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 42, .pad = 166, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 40, .pad = 166, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 40, .pad = 161, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 39, .pad = 161, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 39, .pad = 156, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 38, .pad = 156, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 38, .pad = 152, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 37, .pad = 152, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 37, .pad = 148, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 36, .pad = 148, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 36, .pad = 143, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 35, .pad = 143, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 35, .pad = 139, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 34, .pad = 139, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 34, .pad = 135, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 33, .pad = 135, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 33, .pad = 132, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 32, .pad = 132, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 32, .pad = 128, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 31, .pad = 128, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 31, .pad = 124, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 30, .pad = 124, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 30, .pad = 121, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 29, .pad = 121, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 29, .pad = 117, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 29, .pad = 117, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 29, .pad = 114, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 28, .pad = 114, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 28, .pad = 111, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 27, .pad = 111, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 27, .pad = 108, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 26, .pad = 108, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 26, .pad = 104, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 25, .pad = 104, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 25, .pad = 102, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 25, .pad = 102, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 25, .pad = 99, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 24, .pad = 99, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 24, .pad = 96, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 23, .pad = 96, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 23, .pad = 93, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 23, .pad = 93, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 23, .pad = 90, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 22, .pad = 90, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 22, .pad = 88, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 21, .pad = 88, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 21, .pad = 85, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 21, .pad = 85, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 21, .pad = 83, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 20, .pad = 83, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 20, .pad = 81, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 20, .pad = 81, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 20, .pad = 78, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 19, .pad = 78, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 19, .pad = 76, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 19, .pad = 76, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 19, .pad = 74, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 18, .pad = 74, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 18, .pad = 72, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 18, .pad = 72, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 18, .pad = 70, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 17, .pad = 70, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 17, .pad = 68, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 17, .pad = 68, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 17, .pad = 66, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 16, .pad = 66, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 16, .pad = 64, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 16, .pad = 64, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 16, .pad = 62, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 62, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 60, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 60, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 15, .pad = 59, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 14, .pad = 59, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 14, .pad = 57, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 14, .pad = 57, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 14, .pad = 55, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 14, .pad = 55, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 14, .pad = 54, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 13, .pad = 54, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 13, .pad = 52, .dac = 0, .bb_mult = 64, }, + { .gm = 7, .pga = 13, .pad = 52, .dac = 0, .bb_mult = 64, }, +}; + +static struct lpphy_tx_gain_table_entry lpphy_rev2_5ghz_tx_gain_table[] = { + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 152, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 147, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 143, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 139, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 135, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 131, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 128, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 124, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 121, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 117, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 114, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 111, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 107, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 104, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 101, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 99, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 96, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 93, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 90, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 88, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 85, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 83, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 81, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 78, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 76, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 74, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 72, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 70, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 68, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 66, }, + { .gm = 255, .pga = 255, .pad = 255, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 248, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 241, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 234, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 227, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 221, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 215, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 208, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 203, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 197, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 191, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 186, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 181, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 175, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 170, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 166, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 161, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 156, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 152, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 148, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 143, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 139, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 135, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 132, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 128, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 124, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 121, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 117, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 114, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 111, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 108, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 104, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 102, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 99, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 96, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 93, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 90, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 88, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 85, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 83, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 81, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 78, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 76, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 74, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 72, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 70, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 68, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 66, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 64, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 64, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 255, .pad = 62, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 248, .pad = 62, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 248, .pad = 60, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 241, .pad = 60, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 241, .pad = 59, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 234, .pad = 59, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 234, .pad = 57, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 227, .pad = 57, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 227, .pad = 55, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 221, .pad = 55, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 221, .pad = 54, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 215, .pad = 54, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 215, .pad = 52, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 208, .pad = 52, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 208, .pad = 51, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 203, .pad = 51, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 203, .pad = 49, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 197, .pad = 49, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 197, .pad = 48, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 191, .pad = 48, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 191, .pad = 47, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 186, .pad = 47, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 186, .pad = 45, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 181, .pad = 45, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 181, .pad = 44, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 175, .pad = 44, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 175, .pad = 43, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 170, .pad = 43, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 170, .pad = 42, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 166, .pad = 42, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 166, .pad = 40, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 161, .pad = 40, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 161, .pad = 39, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 156, .pad = 39, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 156, .pad = 38, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 152, .pad = 38, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 152, .pad = 37, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 148, .pad = 37, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 148, .pad = 36, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 143, .pad = 36, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 143, .pad = 35, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 139, .pad = 35, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 139, .pad = 34, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 135, .pad = 34, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 135, .pad = 33, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 132, .pad = 33, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 132, .pad = 32, .dac = 0, .bb_mult = 64, }, + { .gm = 255, .pga = 128, .pad = 32, .dac = 0, .bb_mult = 64, }, +}; + +void lpphy_rev0_1_table_init(struct b43_wldev *dev) +{ + B43_WARN_ON(dev->phy.rev >= 2); + + b43_lptab_write_bulk(dev, B43_LPTAB8(2, 0), + ARRAY_SIZE(lpphy_min_sig_sq_table), lpphy_min_sig_sq_table); + b43_lptab_write_bulk(dev, B43_LPTAB16(1, 0), + ARRAY_SIZE(lpphy_rev01_noise_scale_table), lpphy_rev01_noise_scale_table); + b43_lptab_write_bulk(dev, B43_LPTAB16(14, 0), + ARRAY_SIZE(lpphy_crs_gain_nft_table), lpphy_crs_gain_nft_table); + b43_lptab_write_bulk(dev, B43_LPTAB16(8, 0), + ARRAY_SIZE(lpphy_rev01_filter_control_table), lpphy_rev01_filter_control_table); + b43_lptab_write_bulk(dev, B43_LPTAB32(9, 0), + ARRAY_SIZE(lpphy_rev01_ps_control_table), lpphy_rev01_ps_control_table); + b43_lptab_write_bulk(dev, B43_LPTAB8(6, 0), + ARRAY_SIZE(lpphy_pll_fraction_table), lpphy_pll_fraction_table); + b43_lptab_write_bulk(dev, B43_LPTAB16(0, 0), + ARRAY_SIZE(lpphy_iqlo_cal_table), lpphy_iqlo_cal_table); + if (dev->phy.rev == 0) { + b43_lptab_write_bulk(dev, B43_LPTAB16(13, 0), + ARRAY_SIZE(lpphy_rev0_ofdm_cck_gain_table), lpphy_rev0_ofdm_cck_gain_table); + b43_lptab_write_bulk(dev, B43_LPTAB16(12, 0), + ARRAY_SIZE(lpphy_rev0_ofdm_cck_gain_table), lpphy_rev0_ofdm_cck_gain_table); + } else { + b43_lptab_write_bulk(dev, B43_LPTAB16(13, 0), + ARRAY_SIZE(lpphy_rev1_ofdm_cck_gain_table), lpphy_rev1_ofdm_cck_gain_table); + b43_lptab_write_bulk(dev, B43_LPTAB16(12, 0), + ARRAY_SIZE(lpphy_rev1_ofdm_cck_gain_table), lpphy_rev1_ofdm_cck_gain_table); +} + b43_lptab_write_bulk(dev, B43_LPTAB16(15, 0), + ARRAY_SIZE(lpphy_gain_delta_table), lpphy_gain_delta_table); + b43_lptab_write_bulk(dev, B43_LPTAB32(10, 0), + ARRAY_SIZE(lpphy_tx_power_control_table), lpphy_tx_power_control_table); +} + +void lpphy_rev2plus_table_init(struct b43_wldev *dev) +{ + int i; + + B43_WARN_ON(dev->phy.rev < 2); + + for (i = 0; i < 704; i++) + b43_lptab_write(dev, B43_LPTAB32(7, i), 0); + + b43_lptab_write_bulk(dev, B43_LPTAB8(2, 0), + ARRAY_SIZE(lpphy_min_sig_sq_table), lpphy_min_sig_sq_table); + b43_lptab_write_bulk(dev, B43_LPTAB16(1, 0), + ARRAY_SIZE(lpphy_rev2plus_noise_scale_table), lpphy_rev2plus_noise_scale_table); + b43_lptab_write_bulk(dev, B43_LPTAB32(11, 0), + ARRAY_SIZE(lpphy_rev2plus_filter_control_table), lpphy_rev2plus_filter_control_table); + b43_lptab_write_bulk(dev, B43_LPTAB32(12, 0), + ARRAY_SIZE(lpphy_rev2plus_ps_control_table), lpphy_rev2plus_ps_control_table); + b43_lptab_write_bulk(dev, B43_LPTAB32(13, 0), + ARRAY_SIZE(lpphy_gain_idx_table), lpphy_gain_idx_table); + b43_lptab_write_bulk(dev, B43_LPTAB16(14, 0), + ARRAY_SIZE(lpphy_aux_gain_idx_table), lpphy_aux_gain_idx_table); + b43_lptab_write_bulk(dev, B43_LPTAB16(15, 0), + ARRAY_SIZE(lpphy_sw_control_table), lpphy_sw_control_table); + b43_lptab_write_bulk(dev, B43_LPTAB8(16, 0), + ARRAY_SIZE(lpphy_hf_table), lpphy_hf_table); + b43_lptab_write_bulk(dev, B43_LPTAB32(17, 0), + ARRAY_SIZE(lpphy_gain_value_table), lpphy_gain_value_table); + b43_lptab_write_bulk(dev, B43_LPTAB16(18, 0), + ARRAY_SIZE(lpphy_gain_table), lpphy_gain_table); + b43_lptab_write_bulk(dev, B43_LPTAB8(6, 0), + ARRAY_SIZE(lpphy_pll_fraction_table), lpphy_pll_fraction_table); + b43_lptab_write_bulk(dev, B43_LPTAB16(0, 0), + ARRAY_SIZE(lpphy_iqlo_cal_table), lpphy_iqlo_cal_table); + b43_lptab_write_bulk(dev, B43_LPTAB32(9, 0), + ARRAY_SIZE(lpphy_papd_eps_table), lpphy_papd_eps_table); + b43_lptab_write_bulk(dev, B43_LPTAB32(10, 0), + ARRAY_SIZE(lpphy_papd_mult_table), lpphy_papd_mult_table); + + if ((dev->dev->chip_id == 0x4325) && (dev->dev->chip_rev == 0)) { + b43_lptab_write_bulk(dev, B43_LPTAB32(13, 0), + ARRAY_SIZE(lpphy_a0_gain_idx_table), lpphy_a0_gain_idx_table); + b43_lptab_write_bulk(dev, B43_LPTAB16(14, 0), + ARRAY_SIZE(lpphy_a0_aux_gain_idx_table), lpphy_a0_aux_gain_idx_table); + b43_lptab_write_bulk(dev, B43_LPTAB32(17, 0), + ARRAY_SIZE(lpphy_a0_gain_value_table), lpphy_a0_gain_value_table); + b43_lptab_write_bulk(dev, B43_LPTAB16(18, 0), + ARRAY_SIZE(lpphy_a0_gain_table), lpphy_a0_gain_table); + } +} + +static void lpphy_rev0_1_write_gain_table(struct b43_wldev *dev, int offset, + struct lpphy_tx_gain_table_entry data) +{ + u32 tmp; + + B43_WARN_ON(dev->phy.rev >= 2); + + tmp = data.pad << 11; + tmp |= data.pga << 7; + tmp |= data.gm << 4; + tmp |= data.dac; + b43_lptab_write(dev, B43_LPTAB32(10, 0xC0 + offset), tmp); + tmp = data.bb_mult << 20; + b43_lptab_write(dev, B43_LPTAB32(10, 0x140 + offset), tmp); +} + +static void lpphy_rev2plus_write_gain_table(struct b43_wldev *dev, int offset, + struct lpphy_tx_gain_table_entry data) +{ + u32 tmp; + + B43_WARN_ON(dev->phy.rev < 2); + + tmp = data.pad << 16; + tmp |= data.pga << 8; + tmp |= data.gm; + if (dev->phy.rev >= 3) { + if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) + tmp |= 0x10 << 24; + else + tmp |= 0x70 << 24; + } else { + if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) + tmp |= 0x14 << 24; + else + tmp |= 0x7F << 24; + } + b43_lptab_write(dev, B43_LPTAB32(7, 0xC0 + offset), tmp); + tmp = data.bb_mult << 20; + tmp |= data.dac << 28; + b43_lptab_write(dev, B43_LPTAB32(7, 0x140 + offset), tmp); +} + +void lpphy_write_gain_table(struct b43_wldev *dev, int offset, + struct lpphy_tx_gain_table_entry data) +{ + if (dev->phy.rev >= 2) + lpphy_rev2plus_write_gain_table(dev, offset, data); + else + lpphy_rev0_1_write_gain_table(dev, offset, data); +} + +void lpphy_write_gain_table_bulk(struct b43_wldev *dev, int offset, int count, + struct lpphy_tx_gain_table_entry *table) +{ + int i; + + for (i = offset; i < count; i++) + lpphy_write_gain_table(dev, i, table[i]); +} + +void lpphy_init_tx_gain_table(struct b43_wldev *dev) +{ + struct ssb_sprom *sprom = dev->dev->bus_sprom; + + switch (dev->phy.rev) { + case 0: + if ((sprom->boardflags_hi & B43_BFH_NOPA) || + (sprom->boardflags_lo & B43_BFL_HGPA)) + lpphy_write_gain_table_bulk(dev, 0, 128, + lpphy_rev0_nopa_tx_gain_table); + else if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + lpphy_write_gain_table_bulk(dev, 0, 128, + lpphy_rev0_2ghz_tx_gain_table); + else + lpphy_write_gain_table_bulk(dev, 0, 128, + lpphy_rev0_5ghz_tx_gain_table); + break; + case 1: + if ((sprom->boardflags_hi & B43_BFH_NOPA) || + (sprom->boardflags_lo & B43_BFL_HGPA)) + lpphy_write_gain_table_bulk(dev, 0, 128, + lpphy_rev1_nopa_tx_gain_table); + else if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + lpphy_write_gain_table_bulk(dev, 0, 128, + lpphy_rev1_2ghz_tx_gain_table); + else + lpphy_write_gain_table_bulk(dev, 0, 128, + lpphy_rev1_5ghz_tx_gain_table); + break; + default: + if (sprom->boardflags_hi & B43_BFH_NOPA) + lpphy_write_gain_table_bulk(dev, 0, 128, + lpphy_rev2_nopa_tx_gain_table); + else if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) + lpphy_write_gain_table_bulk(dev, 0, 128, + lpphy_rev2_2ghz_tx_gain_table); + else + lpphy_write_gain_table_bulk(dev, 0, 128, + lpphy_rev2_5ghz_tx_gain_table); + } +} diff --git a/drivers/net/wireless/b43/tables_lpphy.h b/drivers/net/wireless/b43/tables_lpphy.h new file mode 100644 index 0000000..84f1d26 --- /dev/null +++ b/drivers/net/wireless/b43/tables_lpphy.h @@ -0,0 +1,44 @@ +#ifndef B43_TABLES_LPPHY_H_ +#define B43_TABLES_LPPHY_H_ + + +#define B43_LPTAB_TYPEMASK 0xF0000000 +#define B43_LPTAB_8BIT 0x10000000 +#define B43_LPTAB_16BIT 0x20000000 +#define B43_LPTAB_32BIT 0x30000000 +#define B43_LPTAB8(table, offset) (((table) << 10) | (offset) | B43_LPTAB_8BIT) +#define B43_LPTAB16(table, offset) (((table) << 10) | (offset) | B43_LPTAB_16BIT) +#define B43_LPTAB32(table, offset) (((table) << 10) | (offset) | B43_LPTAB_32BIT) + +/* Table definitions */ +#define B43_LPTAB_TXPWR_R2PLUS B43_LPTAB32(0x07, 0) /* TX power lookup table (rev >= 2) */ +#define B43_LPTAB_TXPWR_R0_1 B43_LPTAB32(0xA0, 0) /* TX power lookup table (rev < 2) */ + +u32 b43_lptab_read(struct b43_wldev *dev, u32 offset); +void b43_lptab_write(struct b43_wldev *dev, u32 offset, u32 value); + +/* Bulk table access. Note that these functions return the bulk data in + * host endianness! The returned data is _not_ a bytearray, but an array + * consisting of nr_elements of the data type. */ +void b43_lptab_read_bulk(struct b43_wldev *dev, u32 offset, + unsigned int nr_elements, void *data); +void b43_lptab_write_bulk(struct b43_wldev *dev, u32 offset, + unsigned int nr_elements, const void *data); + +void b2062_upload_init_table(struct b43_wldev *dev); +void b2063_upload_init_table(struct b43_wldev *dev); + +struct lpphy_tx_gain_table_entry { + u8 gm, pga, pad, dac, bb_mult; +}; + +void lpphy_write_gain_table(struct b43_wldev *dev, int offset, + struct lpphy_tx_gain_table_entry data); +void lpphy_write_gain_table_bulk(struct b43_wldev *dev, int offset, int count, + struct lpphy_tx_gain_table_entry *table); + +void lpphy_rev0_1_table_init(struct b43_wldev *dev); +void lpphy_rev2plus_table_init(struct b43_wldev *dev); +void lpphy_init_tx_gain_table(struct b43_wldev *dev); + +#endif /* B43_TABLES_LPPHY_H_ */ diff --git a/drivers/net/wireless/b43/tables_nphy.c b/drivers/net/wireless/b43/tables_nphy.c new file mode 100644 index 0000000..94c755f --- /dev/null +++ b/drivers/net/wireless/b43/tables_nphy.c @@ -0,0 +1,3322 @@ +/* + + Broadcom B43 wireless driver + IEEE 802.11n PHY data tables + + Copyright (c) 2008 Michael Buesch + Copyright (c) 2010 Rafał Miłecki + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" +#include "tables_nphy.h" +#include "phy_common.h" +#include "phy_n.h" + +static const u8 b43_ntab_adjustpower0[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const u8 b43_ntab_adjustpower1[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const u16 b43_ntab_bdi[] = { + 0x0070, 0x0126, 0x012C, 0x0246, 0x048D, 0x04D2, +}; + +static const u32 b43_ntab_channelest[] = { + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, +}; + +static const u8 b43_ntab_estimatepowerlt0[] = { + 0x50, 0x4F, 0x4E, 0x4D, 0x4C, 0x4B, 0x4A, 0x49, + 0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41, + 0x40, 0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, + 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, + 0x30, 0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, + 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, + 0x20, 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, + 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, +}; + +static const u8 b43_ntab_estimatepowerlt1[] = { + 0x50, 0x4F, 0x4E, 0x4D, 0x4C, 0x4B, 0x4A, 0x49, + 0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41, + 0x40, 0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, + 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, + 0x30, 0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, + 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, + 0x20, 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, + 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, +}; + +static const u8 b43_ntab_framelookup[] = { + 0x02, 0x04, 0x14, 0x14, 0x03, 0x05, 0x16, 0x16, + 0x0A, 0x0C, 0x1C, 0x1C, 0x0B, 0x0D, 0x1E, 0x1E, + 0x06, 0x08, 0x18, 0x18, 0x07, 0x09, 0x1A, 0x1A, + 0x0E, 0x10, 0x20, 0x28, 0x0F, 0x11, 0x22, 0x2A, +}; + +static const u32 b43_ntab_framestruct[] = { + 0x08004A04, 0x00100000, 0x01000A05, 0x00100020, + 0x09804506, 0x00100030, 0x09804507, 0x00100030, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x08004A0C, 0x00100004, 0x01000A0D, 0x00100024, + 0x0980450E, 0x00100034, 0x0980450F, 0x00100034, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000A04, 0x00100000, 0x11008A05, 0x00100020, + 0x1980C506, 0x00100030, 0x21810506, 0x00100030, + 0x21810506, 0x00100030, 0x01800504, 0x00100030, + 0x11808505, 0x00100030, 0x29814507, 0x01100030, + 0x00000A04, 0x00100000, 0x11008A05, 0x00100020, + 0x21810506, 0x00100030, 0x21810506, 0x00100030, + 0x29814507, 0x01100030, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000A0C, 0x00100008, 0x11008A0D, 0x00100028, + 0x1980C50E, 0x00100038, 0x2181050E, 0x00100038, + 0x2181050E, 0x00100038, 0x0180050C, 0x00100038, + 0x1180850D, 0x00100038, 0x2981450F, 0x01100038, + 0x00000A0C, 0x00100008, 0x11008A0D, 0x00100028, + 0x2181050E, 0x00100038, 0x2181050E, 0x00100038, + 0x2981450F, 0x01100038, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x08004A04, 0x00100000, 0x01000A05, 0x00100020, + 0x1980C506, 0x00100030, 0x1980C506, 0x00100030, + 0x11808504, 0x00100030, 0x3981CA05, 0x00100030, + 0x29814507, 0x01100030, 0x00000000, 0x00000000, + 0x10008A04, 0x00100000, 0x3981CA05, 0x00100030, + 0x1980C506, 0x00100030, 0x29814507, 0x01100030, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x08004A0C, 0x00100008, 0x01000A0D, 0x00100028, + 0x1980C50E, 0x00100038, 0x1980C50E, 0x00100038, + 0x1180850C, 0x00100038, 0x3981CA0D, 0x00100038, + 0x2981450F, 0x01100038, 0x00000000, 0x00000000, + 0x10008A0C, 0x00100008, 0x3981CA0D, 0x00100038, + 0x1980C50E, 0x00100038, 0x2981450F, 0x01100038, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x40021404, 0x00100000, 0x02001405, 0x00100040, + 0x0B004A06, 0x01900060, 0x13008A06, 0x01900060, + 0x13008A06, 0x01900060, 0x43020A04, 0x00100060, + 0x1B00CA05, 0x00100060, 0x23010A07, 0x01500060, + 0x40021404, 0x00100000, 0x1A00D405, 0x00100040, + 0x13008A06, 0x01900060, 0x13008A06, 0x01900060, + 0x23010A07, 0x01500060, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x4002140C, 0x00100010, 0x0200140D, 0x00100050, + 0x0B004A0E, 0x01900070, 0x13008A0E, 0x01900070, + 0x13008A0E, 0x01900070, 0x43020A0C, 0x00100070, + 0x1B00CA0D, 0x00100070, 0x23010A0F, 0x01500070, + 0x4002140C, 0x00100010, 0x1A00D40D, 0x00100050, + 0x13008A0E, 0x01900070, 0x13008A0E, 0x01900070, + 0x23010A0F, 0x01500070, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x50029404, 0x00100000, 0x32019405, 0x00100040, + 0x0B004A06, 0x01900060, 0x0B004A06, 0x01900060, + 0x5B02CA04, 0x00100060, 0x3B01D405, 0x00100060, + 0x23010A07, 0x01500060, 0x00000000, 0x00000000, + 0x5802D404, 0x00100000, 0x3B01D405, 0x00100060, + 0x0B004A06, 0x01900060, 0x23010A07, 0x01500060, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x5002940C, 0x00100010, 0x3201940D, 0x00100050, + 0x0B004A0E, 0x01900070, 0x0B004A0E, 0x01900070, + 0x5B02CA0C, 0x00100070, 0x3B01D40D, 0x00100070, + 0x23010A0F, 0x01500070, 0x00000000, 0x00000000, + 0x5802D40C, 0x00100010, 0x3B01D40D, 0x00100070, + 0x0B004A0E, 0x01900070, 0x23010A0F, 0x01500070, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x40021404, 0x000F4800, 0x62031405, 0x00100040, + 0x53028A06, 0x01900060, 0x53028A07, 0x01900060, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x4002140C, 0x000F4808, 0x6203140D, 0x00100048, + 0x53028A0E, 0x01900068, 0x53028A0F, 0x01900068, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000A0C, 0x00100004, 0x11008A0D, 0x00100024, + 0x1980C50E, 0x00100034, 0x2181050E, 0x00100034, + 0x2181050E, 0x00100034, 0x0180050C, 0x00100038, + 0x1180850D, 0x00100038, 0x1181850D, 0x00100038, + 0x2981450F, 0x01100038, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000A0C, 0x00100008, 0x11008A0D, 0x00100028, + 0x2181050E, 0x00100038, 0x2181050E, 0x00100038, + 0x1181850D, 0x00100038, 0x2981450F, 0x01100038, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x08004A04, 0x00100000, 0x01000A05, 0x00100020, + 0x0180C506, 0x00100030, 0x0180C506, 0x00100030, + 0x2180C50C, 0x00100030, 0x49820A0D, 0x0016A130, + 0x41824A0D, 0x0016A130, 0x2981450F, 0x01100030, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x2000CA0C, 0x00100000, 0x49820A0D, 0x0016A130, + 0x1980C50E, 0x00100030, 0x41824A0D, 0x0016A130, + 0x2981450F, 0x01100030, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x4002140C, 0x00100008, 0x0200140D, 0x00100048, + 0x0B004A0E, 0x01900068, 0x13008A0E, 0x01900068, + 0x13008A0E, 0x01900068, 0x43020A0C, 0x00100070, + 0x1B00CA0D, 0x00100070, 0x1B014A0D, 0x00100070, + 0x23010A0F, 0x01500070, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x4002140C, 0x00100010, 0x1A00D40D, 0x00100050, + 0x13008A0E, 0x01900070, 0x13008A0E, 0x01900070, + 0x1B014A0D, 0x00100070, 0x23010A0F, 0x01500070, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x50029404, 0x00100000, 0x32019405, 0x00100040, + 0x03004A06, 0x01900060, 0x03004A06, 0x01900060, + 0x6B030A0C, 0x00100060, 0x4B02140D, 0x0016A160, + 0x4302540D, 0x0016A160, 0x23010A0F, 0x01500060, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x6B03140C, 0x00100060, 0x4B02140D, 0x0016A160, + 0x0B004A0E, 0x01900060, 0x4302540D, 0x0016A160, + 0x23010A0F, 0x01500060, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x40021404, 0x00100000, 0x1A00D405, 0x00100040, + 0x53028A06, 0x01900060, 0x5B02CA06, 0x01900060, + 0x5B02CA06, 0x01900060, 0x43020A04, 0x00100060, + 0x1B00CA05, 0x00100060, 0x53028A07, 0x0190C060, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x4002140C, 0x00100010, 0x1A00D40D, 0x00100050, + 0x53028A0E, 0x01900070, 0x5B02CA0E, 0x01900070, + 0x5B02CA0E, 0x01900070, 0x43020A0C, 0x00100070, + 0x1B00CA0D, 0x00100070, 0x53028A0F, 0x0190C070, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x40021404, 0x00100000, 0x1A00D405, 0x00100040, + 0x5B02CA06, 0x01900060, 0x5B02CA06, 0x01900060, + 0x53028A07, 0x0190C060, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x4002140C, 0x00100010, 0x1A00D40D, 0x00100050, + 0x5B02CA0E, 0x01900070, 0x5B02CA0E, 0x01900070, + 0x53028A0F, 0x0190C070, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, +}; + +static const u32 b43_ntab_gainctl0[] = { + 0x03CC2B44, 0x03CC2B42, 0x03CC2B40, 0x03CC2B3E, + 0x03CC2B3D, 0x03CC2B3B, 0x03C82B44, 0x03C82B42, + 0x03C82B40, 0x03C82B3E, 0x03C82B3D, 0x03C82B3B, + 0x03C82B39, 0x03C82B38, 0x03C82B36, 0x03C82B34, + 0x03C42B44, 0x03C42B42, 0x03C42B40, 0x03C42B3E, + 0x03C42B3D, 0x03C42B3B, 0x03C42B39, 0x03C42B38, + 0x03C42B36, 0x03C42B34, 0x03C42B33, 0x03C42B32, + 0x03C42B30, 0x03C42B2F, 0x03C42B2D, 0x03C02B44, + 0x03C02B42, 0x03C02B40, 0x03C02B3E, 0x03C02B3D, + 0x03C02B3B, 0x03C02B39, 0x03C02B38, 0x03C02B36, + 0x03C02B34, 0x03B02B44, 0x03B02B42, 0x03B02B40, + 0x03B02B3E, 0x03B02B3D, 0x03B02B3B, 0x03B02B39, + 0x03B02B38, 0x03B02B36, 0x03B02B34, 0x03B02B33, + 0x03B02B32, 0x03B02B30, 0x03B02B2F, 0x03B02B2D, + 0x03A02B44, 0x03A02B42, 0x03A02B40, 0x03A02B3E, + 0x03A02B3D, 0x03A02B3B, 0x03A02B39, 0x03A02B38, + 0x03A02B36, 0x03A02B34, 0x03902B44, 0x03902B42, + 0x03902B40, 0x03902B3E, 0x03902B3D, 0x03902B3B, + 0x03902B39, 0x03902B38, 0x03902B36, 0x03902B34, + 0x03902B33, 0x03902B32, 0x03902B30, 0x03802B44, + 0x03802B42, 0x03802B40, 0x03802B3E, 0x03802B3D, + 0x03802B3B, 0x03802B39, 0x03802B38, 0x03802B36, + 0x03802B34, 0x03802B33, 0x03802B32, 0x03802B30, + 0x03802B2F, 0x03802B2D, 0x03802B2C, 0x03802B2B, + 0x03802B2A, 0x03802B29, 0x03802B27, 0x03802B26, + 0x03802B25, 0x03802B24, 0x03802B23, 0x03802B22, + 0x03802B21, 0x03802B20, 0x03802B1F, 0x03802B1E, + 0x03802B1E, 0x03802B1D, 0x03802B1C, 0x03802B1B, + 0x03802B1A, 0x03802B1A, 0x03802B19, 0x03802B18, + 0x03802B18, 0x03802B18, 0x03802B18, 0x03802B18, + 0x03802B18, 0x03802B18, 0x03802B18, 0x03802B18, + 0x03802B18, 0x03802B18, 0x03802B18, 0x00002B00, +}; + +static const u32 b43_ntab_gainctl1[] = { + 0x03CC2B44, 0x03CC2B42, 0x03CC2B40, 0x03CC2B3E, + 0x03CC2B3D, 0x03CC2B3B, 0x03C82B44, 0x03C82B42, + 0x03C82B40, 0x03C82B3E, 0x03C82B3D, 0x03C82B3B, + 0x03C82B39, 0x03C82B38, 0x03C82B36, 0x03C82B34, + 0x03C42B44, 0x03C42B42, 0x03C42B40, 0x03C42B3E, + 0x03C42B3D, 0x03C42B3B, 0x03C42B39, 0x03C42B38, + 0x03C42B36, 0x03C42B34, 0x03C42B33, 0x03C42B32, + 0x03C42B30, 0x03C42B2F, 0x03C42B2D, 0x03C02B44, + 0x03C02B42, 0x03C02B40, 0x03C02B3E, 0x03C02B3D, + 0x03C02B3B, 0x03C02B39, 0x03C02B38, 0x03C02B36, + 0x03C02B34, 0x03B02B44, 0x03B02B42, 0x03B02B40, + 0x03B02B3E, 0x03B02B3D, 0x03B02B3B, 0x03B02B39, + 0x03B02B38, 0x03B02B36, 0x03B02B34, 0x03B02B33, + 0x03B02B32, 0x03B02B30, 0x03B02B2F, 0x03B02B2D, + 0x03A02B44, 0x03A02B42, 0x03A02B40, 0x03A02B3E, + 0x03A02B3D, 0x03A02B3B, 0x03A02B39, 0x03A02B38, + 0x03A02B36, 0x03A02B34, 0x03902B44, 0x03902B42, + 0x03902B40, 0x03902B3E, 0x03902B3D, 0x03902B3B, + 0x03902B39, 0x03902B38, 0x03902B36, 0x03902B34, + 0x03902B33, 0x03902B32, 0x03902B30, 0x03802B44, + 0x03802B42, 0x03802B40, 0x03802B3E, 0x03802B3D, + 0x03802B3B, 0x03802B39, 0x03802B38, 0x03802B36, + 0x03802B34, 0x03802B33, 0x03802B32, 0x03802B30, + 0x03802B2F, 0x03802B2D, 0x03802B2C, 0x03802B2B, + 0x03802B2A, 0x03802B29, 0x03802B27, 0x03802B26, + 0x03802B25, 0x03802B24, 0x03802B23, 0x03802B22, + 0x03802B21, 0x03802B20, 0x03802B1F, 0x03802B1E, + 0x03802B1E, 0x03802B1D, 0x03802B1C, 0x03802B1B, + 0x03802B1A, 0x03802B1A, 0x03802B19, 0x03802B18, + 0x03802B18, 0x03802B18, 0x03802B18, 0x03802B18, + 0x03802B18, 0x03802B18, 0x03802B18, 0x03802B18, + 0x03802B18, 0x03802B18, 0x03802B18, 0x00002B00, +}; + +static const u32 b43_ntab_intlevel[] = { + 0x00802070, 0x0671188D, 0x0A60192C, 0x0A300E46, + 0x00C1188D, 0x080024D2, 0x00000070, +}; + +static const u32 b43_ntab_iqlt0[] = { + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, +}; + +static const u32 b43_ntab_iqlt1[] = { + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, + 0x0000007F, 0x0000007F, 0x0000007F, 0x0000007F, +}; + +static const u16 b43_ntab_loftlt0[] = { + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, +}; + +static const u16 b43_ntab_loftlt1[] = { + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, 0x0000, 0x0101, 0x0002, 0x0103, + 0x0000, 0x0101, 0x0002, 0x0103, 0x0000, 0x0101, + 0x0002, 0x0103, +}; + +static const u8 b43_ntab_mcs[] = { + 0x00, 0x08, 0x0A, 0x10, 0x12, 0x19, 0x1A, 0x1C, + 0x40, 0x48, 0x4A, 0x50, 0x52, 0x59, 0x5A, 0x5C, + 0x80, 0x88, 0x8A, 0x90, 0x92, 0x99, 0x9A, 0x9C, + 0xC0, 0xC8, 0xCA, 0xD0, 0xD2, 0xD9, 0xDA, 0xDC, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x04, 0x08, 0x09, 0x0A, 0x0C, + 0x10, 0x11, 0x12, 0x14, 0x18, 0x19, 0x1A, 0x1C, + 0x20, 0x21, 0x22, 0x24, 0x40, 0x41, 0x42, 0x44, + 0x48, 0x49, 0x4A, 0x4C, 0x50, 0x51, 0x52, 0x54, + 0x58, 0x59, 0x5A, 0x5C, 0x60, 0x61, 0x62, 0x64, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const u32 b43_ntab_noisevar10[] = { + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, +}; + +static const u32 b43_ntab_noisevar11[] = { + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, + 0x020C020C, 0x0000014D, 0x020C020C, 0x0000014D, +}; + +static const u16 b43_ntab_pilot[] = { + 0xFF08, 0xFF08, 0xFF08, 0xFF08, 0xFF08, 0xFF08, + 0xFF08, 0xFF08, 0x80D5, 0x80D5, 0x80D5, 0x80D5, + 0x80D5, 0x80D5, 0x80D5, 0x80D5, 0xFF0A, 0xFF82, + 0xFFA0, 0xFF28, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFF82, 0xFFA0, 0xFF28, 0xFF0A, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xF83F, 0xFA1F, 0xFA97, 0xFAB5, + 0xF2BD, 0xF0BF, 0xFFFF, 0xFFFF, 0xF017, 0xF815, + 0xF215, 0xF095, 0xF035, 0xF01D, 0xFFFF, 0xFFFF, + 0xFF08, 0xFF02, 0xFF80, 0xFF20, 0xFF08, 0xFF02, + 0xFF80, 0xFF20, 0xF01F, 0xF817, 0xFA15, 0xF295, + 0xF0B5, 0xF03D, 0xFFFF, 0xFFFF, 0xF82A, 0xFA0A, + 0xFA82, 0xFAA0, 0xF2A8, 0xF0AA, 0xFFFF, 0xFFFF, + 0xF002, 0xF800, 0xF200, 0xF080, 0xF020, 0xF008, + 0xFFFF, 0xFFFF, 0xF00A, 0xF802, 0xFA00, 0xF280, + 0xF0A0, 0xF028, 0xFFFF, 0xFFFF, +}; + +static const u32 b43_ntab_pilotlt[] = { + 0x76540123, 0x62407351, 0x76543201, 0x76540213, + 0x76540123, 0x76430521, +}; + +static const u32 b43_ntab_tdi20a0[] = { + 0x00091226, 0x000A1429, 0x000B56AD, 0x000C58B0, + 0x000D5AB3, 0x000E9CB6, 0x000F9EBA, 0x0000C13D, + 0x00020301, 0x00030504, 0x00040708, 0x0005090B, + 0x00064B8E, 0x00095291, 0x000A5494, 0x000B9718, + 0x000C9927, 0x000D9B2A, 0x000EDD2E, 0x000FDF31, + 0x000101B4, 0x000243B7, 0x000345BB, 0x000447BE, + 0x00058982, 0x00068C05, 0x00099309, 0x000A950C, + 0x000BD78F, 0x000CD992, 0x000DDB96, 0x000F1D99, + 0x00005FA8, 0x0001422C, 0x0002842F, 0x00038632, + 0x00048835, 0x0005CA38, 0x0006CCBC, 0x0009D3BF, + 0x000B1603, 0x000C1806, 0x000D1A0A, 0x000E1C0D, + 0x000F5E10, 0x00008093, 0x00018297, 0x0002C49A, + 0x0003C680, 0x0004C880, 0x00060B00, 0x00070D00, + 0x00000000, 0x00000000, 0x00000000, +}; + +static const u32 b43_ntab_tdi20a1[] = { + 0x00014B26, 0x00028D29, 0x000393AD, 0x00049630, + 0x0005D833, 0x0006DA36, 0x00099C3A, 0x000A9E3D, + 0x000BC081, 0x000CC284, 0x000DC488, 0x000F068B, + 0x0000488E, 0x00018B91, 0x0002D214, 0x0003D418, + 0x0004D6A7, 0x000618AA, 0x00071AAE, 0x0009DCB1, + 0x000B1EB4, 0x000C0137, 0x000D033B, 0x000E053E, + 0x000F4702, 0x00008905, 0x00020C09, 0x0003128C, + 0x0004148F, 0x00051712, 0x00065916, 0x00091B19, + 0x000A1D28, 0x000B5F2C, 0x000C41AF, 0x000D43B2, + 0x000E85B5, 0x000F87B8, 0x0000C9BC, 0x00024CBF, + 0x00035303, 0x00045506, 0x0005978A, 0x0006998D, + 0x00095B90, 0x000A5D93, 0x000B9F97, 0x000C821A, + 0x000D8400, 0x000EC600, 0x000FC800, 0x00010A00, + 0x00000000, 0x00000000, 0x00000000, +}; + +static const u32 b43_ntab_tdi40a0[] = { + 0x0011A346, 0x00136CCF, 0x0014F5D9, 0x001641E2, + 0x0017CB6B, 0x00195475, 0x001B2383, 0x001CAD0C, + 0x001E7616, 0x0000821F, 0x00020BA8, 0x0003D4B2, + 0x00056447, 0x00072DD0, 0x0008B6DA, 0x000A02E3, + 0x000B8C6C, 0x000D15F6, 0x0011E484, 0x0013AE0D, + 0x00153717, 0x00168320, 0x00180CA9, 0x00199633, + 0x001B6548, 0x001CEED1, 0x001EB7DB, 0x0000C3E4, + 0x00024D6D, 0x000416F7, 0x0005A585, 0x00076F0F, + 0x0008F818, 0x000A4421, 0x000BCDAB, 0x000D9734, + 0x00122649, 0x0013EFD2, 0x001578DC, 0x0016C4E5, + 0x00184E6E, 0x001A17F8, 0x001BA686, 0x001D3010, + 0x001EF999, 0x00010522, 0x00028EAC, 0x00045835, + 0x0005E74A, 0x0007B0D3, 0x00093A5D, 0x000A85E6, + 0x000C0F6F, 0x000DD8F9, 0x00126787, 0x00143111, + 0x0015BA9A, 0x00170623, 0x00188FAD, 0x001A5936, + 0x001BE84B, 0x001DB1D4, 0x001F3B5E, 0x000146E7, + 0x00031070, 0x000499FA, 0x00062888, 0x0007F212, + 0x00097B9B, 0x000AC7A4, 0x000C50AE, 0x000E1A37, + 0x0012A94C, 0x001472D5, 0x0015FC5F, 0x00174868, + 0x0018D171, 0x001A9AFB, 0x001C2989, 0x001DF313, + 0x001F7C9C, 0x000188A5, 0x000351AF, 0x0004DB38, + 0x0006AA4D, 0x000833D7, 0x0009BD60, 0x000B0969, + 0x000C9273, 0x000E5BFC, 0x00132A8A, 0x0014B414, + 0x00163D9D, 0x001789A6, 0x001912B0, 0x001ADC39, + 0x001C6BCE, 0x001E34D8, 0x001FBE61, 0x0001CA6A, + 0x00039374, 0x00051CFD, 0x0006EC0B, 0x00087515, + 0x0009FE9E, 0x000B4AA7, 0x000CD3B1, 0x000E9D3A, + 0x00000000, 0x00000000, +}; + +static const u32 b43_ntab_tdi40a1[] = { + 0x001EDB36, 0x000129CA, 0x0002B353, 0x00047CDD, + 0x0005C8E6, 0x000791EF, 0x00091BF9, 0x000AAA07, + 0x000C3391, 0x000DFD1A, 0x00120923, 0x0013D22D, + 0x00155C37, 0x0016EACB, 0x00187454, 0x001A3DDE, + 0x001B89E7, 0x001D12F0, 0x001F1CFA, 0x00016B88, + 0x00033492, 0x0004BE1B, 0x00060A24, 0x0007D32E, + 0x00095D38, 0x000AEC4C, 0x000C7555, 0x000E3EDF, + 0x00124AE8, 0x001413F1, 0x0015A37B, 0x00172C89, + 0x0018B593, 0x001A419C, 0x001BCB25, 0x001D942F, + 0x001F63B9, 0x0001AD4D, 0x00037657, 0x0004C260, + 0x00068BE9, 0x000814F3, 0x0009A47C, 0x000B2D8A, + 0x000CB694, 0x000E429D, 0x00128C26, 0x001455B0, + 0x0015E4BA, 0x00176E4E, 0x0018F758, 0x001A8361, + 0x001C0CEA, 0x001DD674, 0x001FA57D, 0x0001EE8B, + 0x0003B795, 0x0005039E, 0x0006CD27, 0x000856B1, + 0x0009E5C6, 0x000B6F4F, 0x000CF859, 0x000E8462, + 0x00130DEB, 0x00149775, 0x00162603, 0x0017AF8C, + 0x00193896, 0x001AC49F, 0x001C4E28, 0x001E17B2, + 0x0000A6C7, 0x00023050, 0x0003F9DA, 0x00054563, + 0x00070EEC, 0x00089876, 0x000A2704, 0x000BB08D, + 0x000D3A17, 0x001185A0, 0x00134F29, 0x0014D8B3, + 0x001667C8, 0x0017F151, 0x00197ADB, 0x001B0664, + 0x001C8FED, 0x001E5977, 0x0000E805, 0x0002718F, + 0x00043B18, 0x000586A1, 0x0007502B, 0x0008D9B4, + 0x000A68C9, 0x000BF252, 0x000DBBDC, 0x0011C7E5, + 0x001390EE, 0x00151A78, 0x0016A906, 0x00183290, + 0x0019BC19, 0x001B4822, 0x001CD12C, 0x001E9AB5, + 0x00000000, 0x00000000, +}; + +static const u32 b43_ntab_tdtrn[] = { + 0x061C061C, 0x0050EE68, 0xF592FE36, 0xFE5212F6, + 0x00000C38, 0xFE5212F6, 0xF592FE36, 0x0050EE68, + 0x061C061C, 0xEE680050, 0xFE36F592, 0x12F6FE52, + 0x0C380000, 0x12F6FE52, 0xFE36F592, 0xEE680050, + 0x061C061C, 0x0050EE68, 0xF592FE36, 0xFE5212F6, + 0x00000C38, 0xFE5212F6, 0xF592FE36, 0x0050EE68, + 0x061C061C, 0xEE680050, 0xFE36F592, 0x12F6FE52, + 0x0C380000, 0x12F6FE52, 0xFE36F592, 0xEE680050, + 0x05E305E3, 0x004DEF0C, 0xF5F3FE47, 0xFE611246, + 0x00000BC7, 0xFE611246, 0xF5F3FE47, 0x004DEF0C, + 0x05E305E3, 0xEF0C004D, 0xFE47F5F3, 0x1246FE61, + 0x0BC70000, 0x1246FE61, 0xFE47F5F3, 0xEF0C004D, + 0x05E305E3, 0x004DEF0C, 0xF5F3FE47, 0xFE611246, + 0x00000BC7, 0xFE611246, 0xF5F3FE47, 0x004DEF0C, + 0x05E305E3, 0xEF0C004D, 0xFE47F5F3, 0x1246FE61, + 0x0BC70000, 0x1246FE61, 0xFE47F5F3, 0xEF0C004D, + 0xFA58FA58, 0xF895043B, 0xFF4C09C0, 0xFBC6FFA8, + 0xFB84F384, 0x0798F6F9, 0x05760122, 0x058409F6, + 0x0B500000, 0x05B7F542, 0x08860432, 0x06DDFEE7, + 0xFB84F384, 0xF9D90664, 0xF7E8025C, 0x00FFF7BD, + 0x05A805A8, 0xF7BD00FF, 0x025CF7E8, 0x0664F9D9, + 0xF384FB84, 0xFEE706DD, 0x04320886, 0xF54205B7, + 0x00000B50, 0x09F60584, 0x01220576, 0xF6F90798, + 0xF384FB84, 0xFFA8FBC6, 0x09C0FF4C, 0x043BF895, + 0x02D402D4, 0x07DE0270, 0xFC96079C, 0xF90AFE94, + 0xFE00FF2C, 0x02D4065D, 0x092A0096, 0x0014FBB8, + 0xFD2CFD2C, 0x076AFB3C, 0x0096F752, 0xF991FD87, + 0xFB2C0200, 0xFEB8F960, 0x08E0FC96, 0x049802A8, + 0xFD2CFD2C, 0x02A80498, 0xFC9608E0, 0xF960FEB8, + 0x0200FB2C, 0xFD87F991, 0xF7520096, 0xFB3C076A, + 0xFD2CFD2C, 0xFBB80014, 0x0096092A, 0x065D02D4, + 0xFF2CFE00, 0xFE94F90A, 0x079CFC96, 0x027007DE, + 0x02D402D4, 0x027007DE, 0x079CFC96, 0xFE94F90A, + 0xFF2CFE00, 0x065D02D4, 0x0096092A, 0xFBB80014, + 0xFD2CFD2C, 0xFB3C076A, 0xF7520096, 0xFD87F991, + 0x0200FB2C, 0xF960FEB8, 0xFC9608E0, 0x02A80498, + 0xFD2CFD2C, 0x049802A8, 0x08E0FC96, 0xFEB8F960, + 0xFB2C0200, 0xF991FD87, 0x0096F752, 0x076AFB3C, + 0xFD2CFD2C, 0x0014FBB8, 0x092A0096, 0x02D4065D, + 0xFE00FF2C, 0xF90AFE94, 0xFC96079C, 0x07DE0270, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x062A0000, 0xFEFA0759, 0x08B80908, 0xF396FC2D, + 0xF9D6045C, 0xFC4EF608, 0xF748F596, 0x07B207BF, + 0x062A062A, 0xF84EF841, 0xF748F596, 0x03B209F8, + 0xF9D6045C, 0x0C6A03D3, 0x08B80908, 0x0106F8A7, + 0x062A0000, 0xFEFAF8A7, 0x08B8F6F8, 0xF39603D3, + 0xF9D6FBA4, 0xFC4E09F8, 0xF7480A6A, 0x07B2F841, + 0x062AF9D6, 0xF84E07BF, 0xF7480A6A, 0x03B2F608, + 0xF9D6FBA4, 0x0C6AFC2D, 0x08B8F6F8, 0x01060759, + 0x062A0000, 0xFEFA0759, 0x08B80908, 0xF396FC2D, + 0xF9D6045C, 0xFC4EF608, 0xF748F596, 0x07B207BF, + 0x062A062A, 0xF84EF841, 0xF748F596, 0x03B209F8, + 0xF9D6045C, 0x0C6A03D3, 0x08B80908, 0x0106F8A7, + 0x062A0000, 0xFEFAF8A7, 0x08B8F6F8, 0xF39603D3, + 0xF9D6FBA4, 0xFC4E09F8, 0xF7480A6A, 0x07B2F841, + 0x062AF9D6, 0xF84E07BF, 0xF7480A6A, 0x03B2F608, + 0xF9D6FBA4, 0x0C6AFC2D, 0x08B8F6F8, 0x01060759, + 0x061C061C, 0xFF30009D, 0xFFB21141, 0xFD87FB54, + 0xF65DFE59, 0x02EEF99E, 0x0166F03C, 0xFFF809B6, + 0x000008A4, 0x000AF42B, 0x00EFF577, 0xFA840BF2, + 0xFC02FF51, 0x08260F67, 0xFFF0036F, 0x0842F9C3, + 0x00000000, 0x063DF7BE, 0xFC910010, 0xF099F7DA, + 0x00AF03FE, 0xF40E057C, 0x0A89FF11, 0x0BD5FFF6, + 0xF75C0000, 0xF64A0008, 0x0FC4FE9A, 0x0662FD12, + 0x01A709A3, 0x04AC0279, 0xEEBF004E, 0xFF6300D0, + 0xF9E4F9E4, 0x00D0FF63, 0x004EEEBF, 0x027904AC, + 0x09A301A7, 0xFD120662, 0xFE9A0FC4, 0x0008F64A, + 0x0000F75C, 0xFFF60BD5, 0xFF110A89, 0x057CF40E, + 0x03FE00AF, 0xF7DAF099, 0x0010FC91, 0xF7BE063D, + 0x00000000, 0xF9C30842, 0x036FFFF0, 0x0F670826, + 0xFF51FC02, 0x0BF2FA84, 0xF57700EF, 0xF42B000A, + 0x08A40000, 0x09B6FFF8, 0xF03C0166, 0xF99E02EE, + 0xFE59F65D, 0xFB54FD87, 0x1141FFB2, 0x009DFF30, + 0x05E30000, 0xFF060705, 0x085408A0, 0xF425FC59, + 0xFA1D042A, 0xFC78F67A, 0xF7ACF60E, 0x075A0766, + 0x05E305E3, 0xF8A6F89A, 0xF7ACF60E, 0x03880986, + 0xFA1D042A, 0x0BDB03A7, 0x085408A0, 0x00FAF8FB, + 0x05E30000, 0xFF06F8FB, 0x0854F760, 0xF42503A7, + 0xFA1DFBD6, 0xFC780986, 0xF7AC09F2, 0x075AF89A, + 0x05E3FA1D, 0xF8A60766, 0xF7AC09F2, 0x0388F67A, + 0xFA1DFBD6, 0x0BDBFC59, 0x0854F760, 0x00FA0705, + 0x05E30000, 0xFF060705, 0x085408A0, 0xF425FC59, + 0xFA1D042A, 0xFC78F67A, 0xF7ACF60E, 0x075A0766, + 0x05E305E3, 0xF8A6F89A, 0xF7ACF60E, 0x03880986, + 0xFA1D042A, 0x0BDB03A7, 0x085408A0, 0x00FAF8FB, + 0x05E30000, 0xFF06F8FB, 0x0854F760, 0xF42503A7, + 0xFA1DFBD6, 0xFC780986, 0xF7AC09F2, 0x075AF89A, + 0x05E3FA1D, 0xF8A60766, 0xF7AC09F2, 0x0388F67A, + 0xFA1DFBD6, 0x0BDBFC59, 0x0854F760, 0x00FA0705, + 0xFA58FA58, 0xF8F0FE00, 0x0448073D, 0xFDC9FE46, + 0xF9910258, 0x089D0407, 0xFD5CF71A, 0x02AFFDE0, + 0x083E0496, 0xFF5A0740, 0xFF7AFD97, 0x00FE01F1, + 0x0009082E, 0xFA94FF75, 0xFECDF8EA, 0xFFB0F693, + 0xFD2CFA58, 0x0433FF16, 0xFBA405DD, 0xFA610341, + 0x06A606CB, 0x0039FD2D, 0x0677FA97, 0x01FA05E0, + 0xF896003E, 0x075A068B, 0x012CFC3E, 0xFA23F98D, + 0xFC7CFD43, 0xFF90FC0D, 0x01C10982, 0x00C601D6, + 0xFD2CFD2C, 0x01D600C6, 0x098201C1, 0xFC0DFF90, + 0xFD43FC7C, 0xF98DFA23, 0xFC3E012C, 0x068B075A, + 0x003EF896, 0x05E001FA, 0xFA970677, 0xFD2D0039, + 0x06CB06A6, 0x0341FA61, 0x05DDFBA4, 0xFF160433, + 0xFA58FD2C, 0xF693FFB0, 0xF8EAFECD, 0xFF75FA94, + 0x082E0009, 0x01F100FE, 0xFD97FF7A, 0x0740FF5A, + 0x0496083E, 0xFDE002AF, 0xF71AFD5C, 0x0407089D, + 0x0258F991, 0xFE46FDC9, 0x073D0448, 0xFE00F8F0, + 0xFD2CFD2C, 0xFCE00500, 0xFC09FDDC, 0xFE680157, + 0x04C70571, 0xFC3AFF21, 0xFCD70228, 0x056D0277, + 0x0200FE00, 0x0022F927, 0xFE3C032B, 0xFC44FF3C, + 0x03E9FBDB, 0x04570313, 0x04C9FF5C, 0x000D03B8, + 0xFA580000, 0xFBE900D2, 0xF9D0FE0B, 0x0125FDF9, + 0x042501BF, 0x0328FA2B, 0xFFA902F0, 0xFA250157, + 0x0200FE00, 0x03740438, 0xFF0405FD, 0x030CFE52, + 0x0037FB39, 0xFF6904C5, 0x04F8FD23, 0xFD31FC1B, + 0xFD2CFD2C, 0xFC1BFD31, 0xFD2304F8, 0x04C5FF69, + 0xFB390037, 0xFE52030C, 0x05FDFF04, 0x04380374, + 0xFE000200, 0x0157FA25, 0x02F0FFA9, 0xFA2B0328, + 0x01BF0425, 0xFDF90125, 0xFE0BF9D0, 0x00D2FBE9, + 0x0000FA58, 0x03B8000D, 0xFF5C04C9, 0x03130457, + 0xFBDB03E9, 0xFF3CFC44, 0x032BFE3C, 0xF9270022, + 0xFE000200, 0x0277056D, 0x0228FCD7, 0xFF21FC3A, + 0x057104C7, 0x0157FE68, 0xFDDCFC09, 0x0500FCE0, + 0xFD2CFD2C, 0x0500FCE0, 0xFDDCFC09, 0x0157FE68, + 0x057104C7, 0xFF21FC3A, 0x0228FCD7, 0x0277056D, + 0xFE000200, 0xF9270022, 0x032BFE3C, 0xFF3CFC44, + 0xFBDB03E9, 0x03130457, 0xFF5C04C9, 0x03B8000D, + 0x0000FA58, 0x00D2FBE9, 0xFE0BF9D0, 0xFDF90125, + 0x01BF0425, 0xFA2B0328, 0x02F0FFA9, 0x0157FA25, + 0xFE000200, 0x04380374, 0x05FDFF04, 0xFE52030C, + 0xFB390037, 0x04C5FF69, 0xFD2304F8, 0xFC1BFD31, + 0xFD2CFD2C, 0xFD31FC1B, 0x04F8FD23, 0xFF6904C5, + 0x0037FB39, 0x030CFE52, 0xFF0405FD, 0x03740438, + 0x0200FE00, 0xFA250157, 0xFFA902F0, 0x0328FA2B, + 0x042501BF, 0x0125FDF9, 0xF9D0FE0B, 0xFBE900D2, + 0xFA580000, 0x000D03B8, 0x04C9FF5C, 0x04570313, + 0x03E9FBDB, 0xFC44FF3C, 0xFE3C032B, 0x0022F927, + 0x0200FE00, 0x056D0277, 0xFCD70228, 0xFC3AFF21, + 0x04C70571, 0xFE680157, 0xFC09FDDC, 0xFCE00500, + 0x05A80000, 0xFF1006BE, 0x0800084A, 0xF49CFC7E, + 0xFA580400, 0xFC9CF6DA, 0xF800F672, 0x0710071C, + 0x05A805A8, 0xF8F0F8E4, 0xF800F672, 0x03640926, + 0xFA580400, 0x0B640382, 0x0800084A, 0x00F0F942, + 0x05A80000, 0xFF10F942, 0x0800F7B6, 0xF49C0382, + 0xFA58FC00, 0xFC9C0926, 0xF800098E, 0x0710F8E4, + 0x05A8FA58, 0xF8F0071C, 0xF800098E, 0x0364F6DA, + 0xFA58FC00, 0x0B64FC7E, 0x0800F7B6, 0x00F006BE, + 0x05A80000, 0xFF1006BE, 0x0800084A, 0xF49CFC7E, + 0xFA580400, 0xFC9CF6DA, 0xF800F672, 0x0710071C, + 0x05A805A8, 0xF8F0F8E4, 0xF800F672, 0x03640926, + 0xFA580400, 0x0B640382, 0x0800084A, 0x00F0F942, + 0x05A80000, 0xFF10F942, 0x0800F7B6, 0xF49C0382, + 0xFA58FC00, 0xFC9C0926, 0xF800098E, 0x0710F8E4, + 0x05A8FA58, 0xF8F0071C, 0xF800098E, 0x0364F6DA, + 0xFA58FC00, 0x0B64FC7E, 0x0800F7B6, 0x00F006BE, +}; + +static const u32 b43_ntab_tmap[] = { + 0x8A88AA80, 0x8AAAAA8A, 0x8A8A8AA8, 0x00000888, + 0x88000000, 0x8A8A88AA, 0x8AA88888, 0x8888A8A8, + 0xF1111110, 0x11111111, 0x11F11111, 0x00000111, + 0x11000000, 0x1111F111, 0x11111111, 0x111111F1, + 0x8A88AA80, 0x8AAAAA8A, 0x8A8A8AA8, 0x000AA888, + 0x88880000, 0x8A8A88AA, 0x8AA88888, 0x8888A8A8, + 0xA1111110, 0x11111111, 0x11C11111, 0x00000111, + 0x11000000, 0x1111A111, 0x11111111, 0x111111A1, + 0xA2222220, 0x22222222, 0x22C22222, 0x00000222, + 0x22000000, 0x2222A222, 0x22222222, 0x222222A2, + 0xF1111110, 0x11111111, 0x11F11111, 0x00011111, + 0x11110000, 0x1111F111, 0x11111111, 0x111111F1, + 0xA8AA88A0, 0xA88888A8, 0xA8A8A88A, 0x00088AAA, + 0xAAAA0000, 0xA8A8AA88, 0xA88AAAAA, 0xAAAA8A8A, + 0xAAA8AAA0, 0x8AAA8AAA, 0xAA8A8A8A, 0x000AAA88, + 0x8AAA0000, 0xAAA8A888, 0x8AA88A8A, 0x8A88A888, + 0x08080A00, 0x0A08080A, 0x080A0A08, 0x00080808, + 0x080A0000, 0x080A0808, 0x080A0808, 0x0A0A0A08, + 0xA0A0A0A0, 0x80A0A080, 0x8080A0A0, 0x00008080, + 0x80A00000, 0x80A080A0, 0xA080A0A0, 0x8080A0A0, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x99999000, 0x9B9B99BB, 0x9BB99999, 0x9999B9B9, + 0x9B99BB90, 0x9BBBBB9B, 0x9B9B9BB9, 0x00000999, + 0x88000000, 0x8A8A88AA, 0x8AA88888, 0x8888A8A8, + 0x8A88AA80, 0x8AAAAA8A, 0x8A8A8AA8, 0x00AAA888, + 0x22000000, 0x2222B222, 0x22222222, 0x222222B2, + 0xB2222220, 0x22222222, 0x22D22222, 0x00000222, + 0x11000000, 0x1111A111, 0x11111111, 0x111111A1, + 0xA1111110, 0x11111111, 0x11C11111, 0x00000111, + 0x33000000, 0x3333B333, 0x33333333, 0x333333B3, + 0xB3333330, 0x33333333, 0x33D33333, 0x00000333, + 0x22000000, 0x2222A222, 0x22222222, 0x222222A2, + 0xA2222220, 0x22222222, 0x22C22222, 0x00000222, + 0x99B99B00, 0x9B9B99BB, 0x9BB99999, 0x9999B9B9, + 0x9B99BB99, 0x9BBBBB9B, 0x9B9B9BB9, 0x00000999, + 0x88000000, 0x8A8A88AA, 0x8AA88888, 0x8888A8A8, + 0x8A88AA88, 0x8AAAAA8A, 0x8A8A8AA8, 0x08AAA888, + 0x22222200, 0x2222F222, 0x22222222, 0x222222F2, + 0x22222222, 0x22222222, 0x22F22222, 0x00000222, + 0x11000000, 0x1111F111, 0x11111111, 0x11111111, + 0xF1111111, 0x11111111, 0x11F11111, 0x01111111, + 0xBB9BB900, 0xB9B9BB99, 0xB99BBBBB, 0xBBBB9B9B, + 0xB9BB99BB, 0xB99999B9, 0xB9B9B99B, 0x00000BBB, + 0xAA000000, 0xA8A8AA88, 0xA88AAAAA, 0xAAAA8A8A, + 0xA8AA88AA, 0xA88888A8, 0xA8A8A88A, 0x0A888AAA, + 0xAA000000, 0xA8A8AA88, 0xA88AAAAA, 0xAAAA8A8A, + 0xA8AA88A0, 0xA88888A8, 0xA8A8A88A, 0x00000AAA, + 0x88000000, 0x8A8A88AA, 0x8AA88888, 0x8888A8A8, + 0x8A88AA80, 0x8AAAAA8A, 0x8A8A8AA8, 0x00000888, + 0xBBBBBB00, 0x999BBBBB, 0x9BB99B9B, 0xB9B9B9BB, + 0xB9B99BBB, 0xB9B9B9BB, 0xB9BB9B99, 0x00000999, + 0x8A000000, 0xAA88A888, 0xA88888AA, 0xA88A8A88, + 0xA88AA88A, 0x88A8AAAA, 0xA8AA8AAA, 0x0888A88A, + 0x0B0B0B00, 0x090B0B0B, 0x0B090B0B, 0x0909090B, + 0x09090B0B, 0x09090B0B, 0x09090B09, 0x00000909, + 0x0A000000, 0x0A080808, 0x080A080A, 0x080A0A08, + 0x080A080A, 0x0808080A, 0x0A0A0A08, 0x0808080A, + 0xB0B0B000, 0x9090B0B0, 0x90B09090, 0xB0B0B090, + 0xB0B090B0, 0x90B0B0B0, 0xB0B09090, 0x00000090, + 0x80000000, 0xA080A080, 0xA08080A0, 0xA0808080, + 0xA080A080, 0x80A0A0A0, 0xA0A080A0, 0x00A0A0A0, + 0x22000000, 0x2222F222, 0x22222222, 0x222222F2, + 0xF2222220, 0x22222222, 0x22F22222, 0x00000222, + 0x11000000, 0x1111F111, 0x11111111, 0x111111F1, + 0xF1111110, 0x11111111, 0x11F11111, 0x00000111, + 0x33000000, 0x3333F333, 0x33333333, 0x333333F3, + 0xF3333330, 0x33333333, 0x33F33333, 0x00000333, + 0x22000000, 0x2222F222, 0x22222222, 0x222222F2, + 0xF2222220, 0x22222222, 0x22F22222, 0x00000222, + 0x99000000, 0x9B9B99BB, 0x9BB99999, 0x9999B9B9, + 0x9B99BB90, 0x9BBBBB9B, 0x9B9B9BB9, 0x00000999, + 0x88000000, 0x8A8A88AA, 0x8AA88888, 0x8888A8A8, + 0x8A88AA80, 0x8AAAAA8A, 0x8A8A8AA8, 0x00000888, + 0x88888000, 0x8A8A88AA, 0x8AA88888, 0x8888A8A8, + 0x8A88AA80, 0x8AAAAA8A, 0x8A8A8AA8, 0x00000888, + 0x88000000, 0x8A8A88AA, 0x8AA88888, 0x8888A8A8, + 0x8A88AA80, 0x8AAAAA8A, 0x8A8A8AA8, 0x00AAA888, + 0x88A88A00, 0x8A8A88AA, 0x8AA88888, 0x8888A8A8, + 0x8A88AA88, 0x8AAAAA8A, 0x8A8A8AA8, 0x00000888, + 0x88000000, 0x8A8A88AA, 0x8AA88888, 0x8888A8A8, + 0x8A88AA88, 0x8AAAAA8A, 0x8A8A8AA8, 0x08AAA888, + 0x11000000, 0x1111A111, 0x11111111, 0x111111A1, + 0xA1111110, 0x11111111, 0x11C11111, 0x00000111, + 0x11000000, 0x1111A111, 0x11111111, 0x111111A1, + 0xA1111110, 0x11111111, 0x11C11111, 0x00000111, + 0x88000000, 0x8A8A88AA, 0x8AA88888, 0x8888A8A8, + 0x8A88AA80, 0x8AAAAA8A, 0x8A8A8AA8, 0x00000888, + 0x88000000, 0x8A8A88AA, 0x8AA88888, 0x8888A8A8, + 0x8A88AA80, 0x8AAAAA8A, 0x8A8A8AA8, 0x00000888, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, +}; + +/* static tables, PHY revision >= 3 */ +static const u32 b43_ntab_framestruct_r3[] = { + 0x08004a04, 0x00100000, 0x01000a05, 0x00100020, + 0x09804506, 0x00100030, 0x09804507, 0x00100030, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x08004a0c, 0x00100004, 0x01000a0d, 0x00100024, + 0x0980450e, 0x00100034, 0x0980450f, 0x00100034, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000a04, 0x00100000, 0x11008a05, 0x00100020, + 0x1980c506, 0x00100030, 0x21810506, 0x00100030, + 0x21810506, 0x00100030, 0x01800504, 0x00100030, + 0x11808505, 0x00100030, 0x29814507, 0x01100030, + 0x00000a04, 0x00100000, 0x11008a05, 0x00100020, + 0x21810506, 0x00100030, 0x21810506, 0x00100030, + 0x29814507, 0x01100030, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000a0c, 0x00100008, 0x11008a0d, 0x00100028, + 0x1980c50e, 0x00100038, 0x2181050e, 0x00100038, + 0x2181050e, 0x00100038, 0x0180050c, 0x00100038, + 0x1180850d, 0x00100038, 0x2981450f, 0x01100038, + 0x00000a0c, 0x00100008, 0x11008a0d, 0x00100028, + 0x2181050e, 0x00100038, 0x2181050e, 0x00100038, + 0x2981450f, 0x01100038, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x08004a04, 0x00100000, 0x01000a05, 0x00100020, + 0x1980c506, 0x00100030, 0x1980c506, 0x00100030, + 0x11808504, 0x00100030, 0x3981ca05, 0x00100030, + 0x29814507, 0x01100030, 0x00000000, 0x00000000, + 0x10008a04, 0x00100000, 0x3981ca05, 0x00100030, + 0x1980c506, 0x00100030, 0x29814507, 0x01100030, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x08004a0c, 0x00100008, 0x01000a0d, 0x00100028, + 0x1980c50e, 0x00100038, 0x1980c50e, 0x00100038, + 0x1180850c, 0x00100038, 0x3981ca0d, 0x00100038, + 0x2981450f, 0x01100038, 0x00000000, 0x00000000, + 0x10008a0c, 0x00100008, 0x3981ca0d, 0x00100038, + 0x1980c50e, 0x00100038, 0x2981450f, 0x01100038, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x40021404, 0x00100000, 0x02001405, 0x00100040, + 0x0b004a06, 0x01900060, 0x13008a06, 0x01900060, + 0x13008a06, 0x01900060, 0x43020a04, 0x00100060, + 0x1b00ca05, 0x00100060, 0x23010a07, 0x01500060, + 0x40021404, 0x00100000, 0x1a00d405, 0x00100040, + 0x13008a06, 0x01900060, 0x13008a06, 0x01900060, + 0x23010a07, 0x01500060, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x4002140c, 0x00100010, 0x0200140d, 0x00100050, + 0x0b004a0e, 0x01900070, 0x13008a0e, 0x01900070, + 0x13008a0e, 0x01900070, 0x43020a0c, 0x00100070, + 0x1b00ca0d, 0x00100070, 0x23010a0f, 0x01500070, + 0x4002140c, 0x00100010, 0x1a00d40d, 0x00100050, + 0x13008a0e, 0x01900070, 0x13008a0e, 0x01900070, + 0x23010a0f, 0x01500070, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x50029404, 0x00100000, 0x32019405, 0x00100040, + 0x0b004a06, 0x01900060, 0x0b004a06, 0x01900060, + 0x5b02ca04, 0x00100060, 0x3b01d405, 0x00100060, + 0x23010a07, 0x01500060, 0x00000000, 0x00000000, + 0x5802d404, 0x00100000, 0x3b01d405, 0x00100060, + 0x0b004a06, 0x01900060, 0x23010a07, 0x01500060, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x5002940c, 0x00100010, 0x3201940d, 0x00100050, + 0x0b004a0e, 0x01900070, 0x0b004a0e, 0x01900070, + 0x5b02ca0c, 0x00100070, 0x3b01d40d, 0x00100070, + 0x23010a0f, 0x01500070, 0x00000000, 0x00000000, + 0x5802d40c, 0x00100010, 0x3b01d40d, 0x00100070, + 0x0b004a0e, 0x01900070, 0x23010a0f, 0x01500070, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x40021404, 0x000f4800, 0x62031405, 0x00100040, + 0x53028a06, 0x01900060, 0x53028a07, 0x01900060, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x4002140c, 0x000f4808, 0x6203140d, 0x00100048, + 0x53028a0e, 0x01900068, 0x53028a0f, 0x01900068, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000a0c, 0x00100004, 0x11008a0d, 0x00100024, + 0x1980c50e, 0x00100034, 0x2181050e, 0x00100034, + 0x2181050e, 0x00100034, 0x0180050c, 0x00100038, + 0x1180850d, 0x00100038, 0x1181850d, 0x00100038, + 0x2981450f, 0x01100038, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000a0c, 0x00100008, 0x11008a0d, 0x00100028, + 0x2181050e, 0x00100038, 0x2181050e, 0x00100038, + 0x1181850d, 0x00100038, 0x2981450f, 0x01100038, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x08004a04, 0x00100000, 0x01000a05, 0x00100020, + 0x0180c506, 0x00100030, 0x0180c506, 0x00100030, + 0x2180c50c, 0x00100030, 0x49820a0d, 0x0016a130, + 0x41824a0d, 0x0016a130, 0x2981450f, 0x01100030, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x2000ca0c, 0x00100000, 0x49820a0d, 0x0016a130, + 0x1980c50e, 0x00100030, 0x41824a0d, 0x0016a130, + 0x2981450f, 0x01100030, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x4002140c, 0x00100008, 0x0200140d, 0x00100048, + 0x0b004a0e, 0x01900068, 0x13008a0e, 0x01900068, + 0x13008a0e, 0x01900068, 0x43020a0c, 0x00100070, + 0x1b00ca0d, 0x00100070, 0x1b014a0d, 0x00100070, + 0x23010a0f, 0x01500070, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x4002140c, 0x00100010, 0x1a00d40d, 0x00100050, + 0x13008a0e, 0x01900070, 0x13008a0e, 0x01900070, + 0x1b014a0d, 0x00100070, 0x23010a0f, 0x01500070, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x50029404, 0x00100000, 0x32019405, 0x00100040, + 0x03004a06, 0x01900060, 0x03004a06, 0x01900060, + 0x6b030a0c, 0x00100060, 0x4b02140d, 0x0016a160, + 0x4302540d, 0x0016a160, 0x23010a0f, 0x01500060, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x6b03140c, 0x00100060, 0x4b02140d, 0x0016a160, + 0x0b004a0e, 0x01900060, 0x4302540d, 0x0016a160, + 0x23010a0f, 0x01500060, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x40021404, 0x00100000, 0x1a00d405, 0x00100040, + 0x53028a06, 0x01900060, 0x5b02ca06, 0x01900060, + 0x5b02ca06, 0x01900060, 0x43020a04, 0x00100060, + 0x1b00ca05, 0x00100060, 0x53028a07, 0x0190c060, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x4002140c, 0x00100010, 0x1a00d40d, 0x00100050, + 0x53028a0e, 0x01900070, 0x5b02ca0e, 0x01900070, + 0x5b02ca0e, 0x01900070, 0x43020a0c, 0x00100070, + 0x1b00ca0d, 0x00100070, 0x53028a0f, 0x0190c070, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x40021404, 0x00100000, 0x1a00d405, 0x00100040, + 0x5b02ca06, 0x01900060, 0x5b02ca06, 0x01900060, + 0x53028a07, 0x0190c060, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x4002140c, 0x00100010, 0x1a00d40d, 0x00100050, + 0x5b02ca0e, 0x01900070, 0x5b02ca0e, 0x01900070, + 0x53028a0f, 0x0190c070, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, +}; + +static const u16 b43_ntab_pilot_r3[] = { + 0xff08, 0xff08, 0xff08, 0xff08, 0xff08, 0xff08, + 0xff08, 0xff08, 0x80d5, 0x80d5, 0x80d5, 0x80d5, + 0x80d5, 0x80d5, 0x80d5, 0x80d5, 0xff0a, 0xff82, + 0xffa0, 0xff28, 0xffff, 0xffff, 0xffff, 0xffff, + 0xff82, 0xffa0, 0xff28, 0xff0a, 0xffff, 0xffff, + 0xffff, 0xffff, 0xf83f, 0xfa1f, 0xfa97, 0xfab5, + 0xf2bd, 0xf0bf, 0xffff, 0xffff, 0xf017, 0xf815, + 0xf215, 0xf095, 0xf035, 0xf01d, 0xffff, 0xffff, + 0xff08, 0xff02, 0xff80, 0xff20, 0xff08, 0xff02, + 0xff80, 0xff20, 0xf01f, 0xf817, 0xfa15, 0xf295, + 0xf0b5, 0xf03d, 0xffff, 0xffff, 0xf82a, 0xfa0a, + 0xfa82, 0xfaa0, 0xf2a8, 0xf0aa, 0xffff, 0xffff, + 0xf002, 0xf800, 0xf200, 0xf080, 0xf020, 0xf008, + 0xffff, 0xffff, 0xf00a, 0xf802, 0xfa00, 0xf280, + 0xf0a0, 0xf028, 0xffff, 0xffff, +}; + +static const u32 b43_ntab_tmap_r3[] = { + 0x8a88aa80, 0x8aaaaa8a, 0x8a8a8aa8, 0x00000888, + 0x88000000, 0x8a8a88aa, 0x8aa88888, 0x8888a8a8, + 0xf1111110, 0x11111111, 0x11f11111, 0x00000111, + 0x11000000, 0x1111f111, 0x11111111, 0x111111f1, + 0x8a88aa80, 0x8aaaaa8a, 0x8a8a8aa8, 0x000aa888, + 0x88880000, 0x8a8a88aa, 0x8aa88888, 0x8888a8a8, + 0xa1111110, 0x11111111, 0x11c11111, 0x00000111, + 0x11000000, 0x1111a111, 0x11111111, 0x111111a1, + 0xa2222220, 0x22222222, 0x22c22222, 0x00000222, + 0x22000000, 0x2222a222, 0x22222222, 0x222222a2, + 0xf1111110, 0x11111111, 0x11f11111, 0x00011111, + 0x11110000, 0x1111f111, 0x11111111, 0x111111f1, + 0xa8aa88a0, 0xa88888a8, 0xa8a8a88a, 0x00088aaa, + 0xaaaa0000, 0xa8a8aa88, 0xa88aaaaa, 0xaaaa8a8a, + 0xaaa8aaa0, 0x8aaa8aaa, 0xaa8a8a8a, 0x000aaa88, + 0x8aaa0000, 0xaaa8a888, 0x8aa88a8a, 0x8a88a888, + 0x08080a00, 0x0a08080a, 0x080a0a08, 0x00080808, + 0x080a0000, 0x080a0808, 0x080a0808, 0x0a0a0a08, + 0xa0a0a0a0, 0x80a0a080, 0x8080a0a0, 0x00008080, + 0x80a00000, 0x80a080a0, 0xa080a0a0, 0x8080a0a0, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x99999000, 0x9b9b99bb, 0x9bb99999, 0x9999b9b9, + 0x9b99bb90, 0x9bbbbb9b, 0x9b9b9bb9, 0x00000999, + 0x88000000, 0x8a8a88aa, 0x8aa88888, 0x8888a8a8, + 0x8a88aa80, 0x8aaaaa8a, 0x8a8a8aa8, 0x00aaa888, + 0x22000000, 0x2222b222, 0x22222222, 0x222222b2, + 0xb2222220, 0x22222222, 0x22d22222, 0x00000222, + 0x11000000, 0x1111a111, 0x11111111, 0x111111a1, + 0xa1111110, 0x11111111, 0x11c11111, 0x00000111, + 0x33000000, 0x3333b333, 0x33333333, 0x333333b3, + 0xb3333330, 0x33333333, 0x33d33333, 0x00000333, + 0x22000000, 0x2222a222, 0x22222222, 0x222222a2, + 0xa2222220, 0x22222222, 0x22c22222, 0x00000222, + 0x99b99b00, 0x9b9b99bb, 0x9bb99999, 0x9999b9b9, + 0x9b99bb99, 0x9bbbbb9b, 0x9b9b9bb9, 0x00000999, + 0x88000000, 0x8a8a88aa, 0x8aa88888, 0x8888a8a8, + 0x8a88aa88, 0x8aaaaa8a, 0x8a8a8aa8, 0x08aaa888, + 0x22222200, 0x2222f222, 0x22222222, 0x222222f2, + 0x22222222, 0x22222222, 0x22f22222, 0x00000222, + 0x11000000, 0x1111f111, 0x11111111, 0x11111111, + 0xf1111111, 0x11111111, 0x11f11111, 0x01111111, + 0xbb9bb900, 0xb9b9bb99, 0xb99bbbbb, 0xbbbb9b9b, + 0xb9bb99bb, 0xb99999b9, 0xb9b9b99b, 0x00000bbb, + 0xaa000000, 0xa8a8aa88, 0xa88aaaaa, 0xaaaa8a8a, + 0xa8aa88aa, 0xa88888a8, 0xa8a8a88a, 0x0a888aaa, + 0xaa000000, 0xa8a8aa88, 0xa88aaaaa, 0xaaaa8a8a, + 0xa8aa88a0, 0xa88888a8, 0xa8a8a88a, 0x00000aaa, + 0x88000000, 0x8a8a88aa, 0x8aa88888, 0x8888a8a8, + 0x8a88aa80, 0x8aaaaa8a, 0x8a8a8aa8, 0x00000888, + 0xbbbbbb00, 0x999bbbbb, 0x9bb99b9b, 0xb9b9b9bb, + 0xb9b99bbb, 0xb9b9b9bb, 0xb9bb9b99, 0x00000999, + 0x8a000000, 0xaa88a888, 0xa88888aa, 0xa88a8a88, + 0xa88aa88a, 0x88a8aaaa, 0xa8aa8aaa, 0x0888a88a, + 0x0b0b0b00, 0x090b0b0b, 0x0b090b0b, 0x0909090b, + 0x09090b0b, 0x09090b0b, 0x09090b09, 0x00000909, + 0x0a000000, 0x0a080808, 0x080a080a, 0x080a0a08, + 0x080a080a, 0x0808080a, 0x0a0a0a08, 0x0808080a, + 0xb0b0b000, 0x9090b0b0, 0x90b09090, 0xb0b0b090, + 0xb0b090b0, 0x90b0b0b0, 0xb0b09090, 0x00000090, + 0x80000000, 0xa080a080, 0xa08080a0, 0xa0808080, + 0xa080a080, 0x80a0a0a0, 0xa0a080a0, 0x00a0a0a0, + 0x22000000, 0x2222f222, 0x22222222, 0x222222f2, + 0xf2222220, 0x22222222, 0x22f22222, 0x00000222, + 0x11000000, 0x1111f111, 0x11111111, 0x111111f1, + 0xf1111110, 0x11111111, 0x11f11111, 0x00000111, + 0x33000000, 0x3333f333, 0x33333333, 0x333333f3, + 0xf3333330, 0x33333333, 0x33f33333, 0x00000333, + 0x22000000, 0x2222f222, 0x22222222, 0x222222f2, + 0xf2222220, 0x22222222, 0x22f22222, 0x00000222, + 0x99000000, 0x9b9b99bb, 0x9bb99999, 0x9999b9b9, + 0x9b99bb90, 0x9bbbbb9b, 0x9b9b9bb9, 0x00000999, + 0x88000000, 0x8a8a88aa, 0x8aa88888, 0x8888a8a8, + 0x8a88aa80, 0x8aaaaa8a, 0x8a8a8aa8, 0x00000888, + 0x88888000, 0x8a8a88aa, 0x8aa88888, 0x8888a8a8, + 0x8a88aa80, 0x8aaaaa8a, 0x8a8a8aa8, 0x00000888, + 0x88000000, 0x8a8a88aa, 0x8aa88888, 0x8888a8a8, + 0x8a88aa80, 0x8aaaaa8a, 0x8a8a8aa8, 0x00aaa888, + 0x88a88a00, 0x8a8a88aa, 0x8aa88888, 0x8888a8a8, + 0x8a88aa88, 0x8aaaaa8a, 0x8a8a8aa8, 0x00000888, + 0x88000000, 0x8a8a88aa, 0x8aa88888, 0x8888a8a8, + 0x8a88aa88, 0x8aaaaa8a, 0x8a8a8aa8, 0x08aaa888, + 0x11000000, 0x1111a111, 0x11111111, 0x111111a1, + 0xa1111110, 0x11111111, 0x11c11111, 0x00000111, + 0x11000000, 0x1111a111, 0x11111111, 0x111111a1, + 0xa1111110, 0x11111111, 0x11c11111, 0x00000111, + 0x88000000, 0x8a8a88aa, 0x8aa88888, 0x8888a8a8, + 0x8a88aa80, 0x8aaaaa8a, 0x8a8a8aa8, 0x00000888, + 0x88000000, 0x8a8a88aa, 0x8aa88888, 0x8888a8a8, + 0x8a88aa80, 0x8aaaaa8a, 0x8a8a8aa8, 0x00000888, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, +}; + +static const u32 b43_ntab_intlevel_r3[] = { + 0x00802070, 0x0671188d, 0x0a60192c, 0x0a300e46, + 0x00c1188d, 0x080024d2, 0x00000070, +}; + +static const u32 b43_ntab_tdtrn_r3[] = { + 0x061c061c, 0x0050ee68, 0xf592fe36, 0xfe5212f6, + 0x00000c38, 0xfe5212f6, 0xf592fe36, 0x0050ee68, + 0x061c061c, 0xee680050, 0xfe36f592, 0x12f6fe52, + 0x0c380000, 0x12f6fe52, 0xfe36f592, 0xee680050, + 0x061c061c, 0x0050ee68, 0xf592fe36, 0xfe5212f6, + 0x00000c38, 0xfe5212f6, 0xf592fe36, 0x0050ee68, + 0x061c061c, 0xee680050, 0xfe36f592, 0x12f6fe52, + 0x0c380000, 0x12f6fe52, 0xfe36f592, 0xee680050, + 0x05e305e3, 0x004def0c, 0xf5f3fe47, 0xfe611246, + 0x00000bc7, 0xfe611246, 0xf5f3fe47, 0x004def0c, + 0x05e305e3, 0xef0c004d, 0xfe47f5f3, 0x1246fe61, + 0x0bc70000, 0x1246fe61, 0xfe47f5f3, 0xef0c004d, + 0x05e305e3, 0x004def0c, 0xf5f3fe47, 0xfe611246, + 0x00000bc7, 0xfe611246, 0xf5f3fe47, 0x004def0c, + 0x05e305e3, 0xef0c004d, 0xfe47f5f3, 0x1246fe61, + 0x0bc70000, 0x1246fe61, 0xfe47f5f3, 0xef0c004d, + 0xfa58fa58, 0xf895043b, 0xff4c09c0, 0xfbc6ffa8, + 0xfb84f384, 0x0798f6f9, 0x05760122, 0x058409f6, + 0x0b500000, 0x05b7f542, 0x08860432, 0x06ddfee7, + 0xfb84f384, 0xf9d90664, 0xf7e8025c, 0x00fff7bd, + 0x05a805a8, 0xf7bd00ff, 0x025cf7e8, 0x0664f9d9, + 0xf384fb84, 0xfee706dd, 0x04320886, 0xf54205b7, + 0x00000b50, 0x09f60584, 0x01220576, 0xf6f90798, + 0xf384fb84, 0xffa8fbc6, 0x09c0ff4c, 0x043bf895, + 0x02d402d4, 0x07de0270, 0xfc96079c, 0xf90afe94, + 0xfe00ff2c, 0x02d4065d, 0x092a0096, 0x0014fbb8, + 0xfd2cfd2c, 0x076afb3c, 0x0096f752, 0xf991fd87, + 0xfb2c0200, 0xfeb8f960, 0x08e0fc96, 0x049802a8, + 0xfd2cfd2c, 0x02a80498, 0xfc9608e0, 0xf960feb8, + 0x0200fb2c, 0xfd87f991, 0xf7520096, 0xfb3c076a, + 0xfd2cfd2c, 0xfbb80014, 0x0096092a, 0x065d02d4, + 0xff2cfe00, 0xfe94f90a, 0x079cfc96, 0x027007de, + 0x02d402d4, 0x027007de, 0x079cfc96, 0xfe94f90a, + 0xff2cfe00, 0x065d02d4, 0x0096092a, 0xfbb80014, + 0xfd2cfd2c, 0xfb3c076a, 0xf7520096, 0xfd87f991, + 0x0200fb2c, 0xf960feb8, 0xfc9608e0, 0x02a80498, + 0xfd2cfd2c, 0x049802a8, 0x08e0fc96, 0xfeb8f960, + 0xfb2c0200, 0xf991fd87, 0x0096f752, 0x076afb3c, + 0xfd2cfd2c, 0x0014fbb8, 0x092a0096, 0x02d4065d, + 0xfe00ff2c, 0xf90afe94, 0xfc96079c, 0x07de0270, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x062a0000, 0xfefa0759, 0x08b80908, 0xf396fc2d, + 0xf9d6045c, 0xfc4ef608, 0xf748f596, 0x07b207bf, + 0x062a062a, 0xf84ef841, 0xf748f596, 0x03b209f8, + 0xf9d6045c, 0x0c6a03d3, 0x08b80908, 0x0106f8a7, + 0x062a0000, 0xfefaf8a7, 0x08b8f6f8, 0xf39603d3, + 0xf9d6fba4, 0xfc4e09f8, 0xf7480a6a, 0x07b2f841, + 0x062af9d6, 0xf84e07bf, 0xf7480a6a, 0x03b2f608, + 0xf9d6fba4, 0x0c6afc2d, 0x08b8f6f8, 0x01060759, + 0x062a0000, 0xfefa0759, 0x08b80908, 0xf396fc2d, + 0xf9d6045c, 0xfc4ef608, 0xf748f596, 0x07b207bf, + 0x062a062a, 0xf84ef841, 0xf748f596, 0x03b209f8, + 0xf9d6045c, 0x0c6a03d3, 0x08b80908, 0x0106f8a7, + 0x062a0000, 0xfefaf8a7, 0x08b8f6f8, 0xf39603d3, + 0xf9d6fba4, 0xfc4e09f8, 0xf7480a6a, 0x07b2f841, + 0x062af9d6, 0xf84e07bf, 0xf7480a6a, 0x03b2f608, + 0xf9d6fba4, 0x0c6afc2d, 0x08b8f6f8, 0x01060759, + 0x061c061c, 0xff30009d, 0xffb21141, 0xfd87fb54, + 0xf65dfe59, 0x02eef99e, 0x0166f03c, 0xfff809b6, + 0x000008a4, 0x000af42b, 0x00eff577, 0xfa840bf2, + 0xfc02ff51, 0x08260f67, 0xfff0036f, 0x0842f9c3, + 0x00000000, 0x063df7be, 0xfc910010, 0xf099f7da, + 0x00af03fe, 0xf40e057c, 0x0a89ff11, 0x0bd5fff6, + 0xf75c0000, 0xf64a0008, 0x0fc4fe9a, 0x0662fd12, + 0x01a709a3, 0x04ac0279, 0xeebf004e, 0xff6300d0, + 0xf9e4f9e4, 0x00d0ff63, 0x004eeebf, 0x027904ac, + 0x09a301a7, 0xfd120662, 0xfe9a0fc4, 0x0008f64a, + 0x0000f75c, 0xfff60bd5, 0xff110a89, 0x057cf40e, + 0x03fe00af, 0xf7daf099, 0x0010fc91, 0xf7be063d, + 0x00000000, 0xf9c30842, 0x036ffff0, 0x0f670826, + 0xff51fc02, 0x0bf2fa84, 0xf57700ef, 0xf42b000a, + 0x08a40000, 0x09b6fff8, 0xf03c0166, 0xf99e02ee, + 0xfe59f65d, 0xfb54fd87, 0x1141ffb2, 0x009dff30, + 0x05e30000, 0xff060705, 0x085408a0, 0xf425fc59, + 0xfa1d042a, 0xfc78f67a, 0xf7acf60e, 0x075a0766, + 0x05e305e3, 0xf8a6f89a, 0xf7acf60e, 0x03880986, + 0xfa1d042a, 0x0bdb03a7, 0x085408a0, 0x00faf8fb, + 0x05e30000, 0xff06f8fb, 0x0854f760, 0xf42503a7, + 0xfa1dfbd6, 0xfc780986, 0xf7ac09f2, 0x075af89a, + 0x05e3fa1d, 0xf8a60766, 0xf7ac09f2, 0x0388f67a, + 0xfa1dfbd6, 0x0bdbfc59, 0x0854f760, 0x00fa0705, + 0x05e30000, 0xff060705, 0x085408a0, 0xf425fc59, + 0xfa1d042a, 0xfc78f67a, 0xf7acf60e, 0x075a0766, + 0x05e305e3, 0xf8a6f89a, 0xf7acf60e, 0x03880986, + 0xfa1d042a, 0x0bdb03a7, 0x085408a0, 0x00faf8fb, + 0x05e30000, 0xff06f8fb, 0x0854f760, 0xf42503a7, + 0xfa1dfbd6, 0xfc780986, 0xf7ac09f2, 0x075af89a, + 0x05e3fa1d, 0xf8a60766, 0xf7ac09f2, 0x0388f67a, + 0xfa1dfbd6, 0x0bdbfc59, 0x0854f760, 0x00fa0705, + 0xfa58fa58, 0xf8f0fe00, 0x0448073d, 0xfdc9fe46, + 0xf9910258, 0x089d0407, 0xfd5cf71a, 0x02affde0, + 0x083e0496, 0xff5a0740, 0xff7afd97, 0x00fe01f1, + 0x0009082e, 0xfa94ff75, 0xfecdf8ea, 0xffb0f693, + 0xfd2cfa58, 0x0433ff16, 0xfba405dd, 0xfa610341, + 0x06a606cb, 0x0039fd2d, 0x0677fa97, 0x01fa05e0, + 0xf896003e, 0x075a068b, 0x012cfc3e, 0xfa23f98d, + 0xfc7cfd43, 0xff90fc0d, 0x01c10982, 0x00c601d6, + 0xfd2cfd2c, 0x01d600c6, 0x098201c1, 0xfc0dff90, + 0xfd43fc7c, 0xf98dfa23, 0xfc3e012c, 0x068b075a, + 0x003ef896, 0x05e001fa, 0xfa970677, 0xfd2d0039, + 0x06cb06a6, 0x0341fa61, 0x05ddfba4, 0xff160433, + 0xfa58fd2c, 0xf693ffb0, 0xf8eafecd, 0xff75fa94, + 0x082e0009, 0x01f100fe, 0xfd97ff7a, 0x0740ff5a, + 0x0496083e, 0xfde002af, 0xf71afd5c, 0x0407089d, + 0x0258f991, 0xfe46fdc9, 0x073d0448, 0xfe00f8f0, + 0xfd2cfd2c, 0xfce00500, 0xfc09fddc, 0xfe680157, + 0x04c70571, 0xfc3aff21, 0xfcd70228, 0x056d0277, + 0x0200fe00, 0x0022f927, 0xfe3c032b, 0xfc44ff3c, + 0x03e9fbdb, 0x04570313, 0x04c9ff5c, 0x000d03b8, + 0xfa580000, 0xfbe900d2, 0xf9d0fe0b, 0x0125fdf9, + 0x042501bf, 0x0328fa2b, 0xffa902f0, 0xfa250157, + 0x0200fe00, 0x03740438, 0xff0405fd, 0x030cfe52, + 0x0037fb39, 0xff6904c5, 0x04f8fd23, 0xfd31fc1b, + 0xfd2cfd2c, 0xfc1bfd31, 0xfd2304f8, 0x04c5ff69, + 0xfb390037, 0xfe52030c, 0x05fdff04, 0x04380374, + 0xfe000200, 0x0157fa25, 0x02f0ffa9, 0xfa2b0328, + 0x01bf0425, 0xfdf90125, 0xfe0bf9d0, 0x00d2fbe9, + 0x0000fa58, 0x03b8000d, 0xff5c04c9, 0x03130457, + 0xfbdb03e9, 0xff3cfc44, 0x032bfe3c, 0xf9270022, + 0xfe000200, 0x0277056d, 0x0228fcd7, 0xff21fc3a, + 0x057104c7, 0x0157fe68, 0xfddcfc09, 0x0500fce0, + 0xfd2cfd2c, 0x0500fce0, 0xfddcfc09, 0x0157fe68, + 0x057104c7, 0xff21fc3a, 0x0228fcd7, 0x0277056d, + 0xfe000200, 0xf9270022, 0x032bfe3c, 0xff3cfc44, + 0xfbdb03e9, 0x03130457, 0xff5c04c9, 0x03b8000d, + 0x0000fa58, 0x00d2fbe9, 0xfe0bf9d0, 0xfdf90125, + 0x01bf0425, 0xfa2b0328, 0x02f0ffa9, 0x0157fa25, + 0xfe000200, 0x04380374, 0x05fdff04, 0xfe52030c, + 0xfb390037, 0x04c5ff69, 0xfd2304f8, 0xfc1bfd31, + 0xfd2cfd2c, 0xfd31fc1b, 0x04f8fd23, 0xff6904c5, + 0x0037fb39, 0x030cfe52, 0xff0405fd, 0x03740438, + 0x0200fe00, 0xfa250157, 0xffa902f0, 0x0328fa2b, + 0x042501bf, 0x0125fdf9, 0xf9d0fe0b, 0xfbe900d2, + 0xfa580000, 0x000d03b8, 0x04c9ff5c, 0x04570313, + 0x03e9fbdb, 0xfc44ff3c, 0xfe3c032b, 0x0022f927, + 0x0200fe00, 0x056d0277, 0xfcd70228, 0xfc3aff21, + 0x04c70571, 0xfe680157, 0xfc09fddc, 0xfce00500, + 0x05a80000, 0xff1006be, 0x0800084a, 0xf49cfc7e, + 0xfa580400, 0xfc9cf6da, 0xf800f672, 0x0710071c, + 0x05a805a8, 0xf8f0f8e4, 0xf800f672, 0x03640926, + 0xfa580400, 0x0b640382, 0x0800084a, 0x00f0f942, + 0x05a80000, 0xff10f942, 0x0800f7b6, 0xf49c0382, + 0xfa58fc00, 0xfc9c0926, 0xf800098e, 0x0710f8e4, + 0x05a8fa58, 0xf8f0071c, 0xf800098e, 0x0364f6da, + 0xfa58fc00, 0x0b64fc7e, 0x0800f7b6, 0x00f006be, + 0x05a80000, 0xff1006be, 0x0800084a, 0xf49cfc7e, + 0xfa580400, 0xfc9cf6da, 0xf800f672, 0x0710071c, + 0x05a805a8, 0xf8f0f8e4, 0xf800f672, 0x03640926, + 0xfa580400, 0x0b640382, 0x0800084a, 0x00f0f942, + 0x05a80000, 0xff10f942, 0x0800f7b6, 0xf49c0382, + 0xfa58fc00, 0xfc9c0926, 0xf800098e, 0x0710f8e4, + 0x05a8fa58, 0xf8f0071c, 0xf800098e, 0x0364f6da, + 0xfa58fc00, 0x0b64fc7e, 0x0800f7b6, 0x00f006be, +}; + +static const u32 b43_ntab_noisevar0_r3[] = { + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, +}; + +static const u32 b43_ntab_noisevar1_r3[] = { + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, + 0x02110211, 0x0000014d, 0x02110211, 0x0000014d, +}; + +static const u16 b43_ntab_mcs_r3[] = { + 0x0000, 0x0008, 0x000a, 0x0010, 0x0012, 0x0019, + 0x001a, 0x001c, 0x0080, 0x0088, 0x008a, 0x0090, + 0x0092, 0x0099, 0x009a, 0x009c, 0x0100, 0x0108, + 0x010a, 0x0110, 0x0112, 0x0119, 0x011a, 0x011c, + 0x0180, 0x0188, 0x018a, 0x0190, 0x0192, 0x0199, + 0x019a, 0x019c, 0x0000, 0x0098, 0x00a0, 0x00a8, + 0x009a, 0x00a2, 0x00aa, 0x0120, 0x0128, 0x0128, + 0x0130, 0x0138, 0x0138, 0x0140, 0x0122, 0x012a, + 0x012a, 0x0132, 0x013a, 0x013a, 0x0142, 0x01a8, + 0x01b0, 0x01b8, 0x01b0, 0x01b8, 0x01c0, 0x01c8, + 0x01c0, 0x01c8, 0x01d0, 0x01d0, 0x01d8, 0x01aa, + 0x01b2, 0x01ba, 0x01b2, 0x01ba, 0x01c2, 0x01ca, + 0x01c2, 0x01ca, 0x01d2, 0x01d2, 0x01da, 0x0001, + 0x0002, 0x0004, 0x0009, 0x000c, 0x0011, 0x0014, + 0x0018, 0x0020, 0x0021, 0x0022, 0x0024, 0x0081, + 0x0082, 0x0084, 0x0089, 0x008c, 0x0091, 0x0094, + 0x0098, 0x00a0, 0x00a1, 0x00a2, 0x00a4, 0x0007, + 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, + 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, + 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, + 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, + 0x0007, 0x0007, +}; + +static const u32 b43_ntab_tdi20a0_r3[] = { + 0x00091226, 0x000a1429, 0x000b56ad, 0x000c58b0, + 0x000d5ab3, 0x000e9cb6, 0x000f9eba, 0x0000c13d, + 0x00020301, 0x00030504, 0x00040708, 0x0005090b, + 0x00064b8e, 0x00095291, 0x000a5494, 0x000b9718, + 0x000c9927, 0x000d9b2a, 0x000edd2e, 0x000fdf31, + 0x000101b4, 0x000243b7, 0x000345bb, 0x000447be, + 0x00058982, 0x00068c05, 0x00099309, 0x000a950c, + 0x000bd78f, 0x000cd992, 0x000ddb96, 0x000f1d99, + 0x00005fa8, 0x0001422c, 0x0002842f, 0x00038632, + 0x00048835, 0x0005ca38, 0x0006ccbc, 0x0009d3bf, + 0x000b1603, 0x000c1806, 0x000d1a0a, 0x000e1c0d, + 0x000f5e10, 0x00008093, 0x00018297, 0x0002c49a, + 0x0003c680, 0x0004c880, 0x00060b00, 0x00070d00, + 0x00000000, 0x00000000, 0x00000000, +}; + +static const u32 b43_ntab_tdi20a1_r3[] = { + 0x00014b26, 0x00028d29, 0x000393ad, 0x00049630, + 0x0005d833, 0x0006da36, 0x00099c3a, 0x000a9e3d, + 0x000bc081, 0x000cc284, 0x000dc488, 0x000f068b, + 0x0000488e, 0x00018b91, 0x0002d214, 0x0003d418, + 0x0004d6a7, 0x000618aa, 0x00071aae, 0x0009dcb1, + 0x000b1eb4, 0x000c0137, 0x000d033b, 0x000e053e, + 0x000f4702, 0x00008905, 0x00020c09, 0x0003128c, + 0x0004148f, 0x00051712, 0x00065916, 0x00091b19, + 0x000a1d28, 0x000b5f2c, 0x000c41af, 0x000d43b2, + 0x000e85b5, 0x000f87b8, 0x0000c9bc, 0x00024cbf, + 0x00035303, 0x00045506, 0x0005978a, 0x0006998d, + 0x00095b90, 0x000a5d93, 0x000b9f97, 0x000c821a, + 0x000d8400, 0x000ec600, 0x000fc800, 0x00010a00, + 0x00000000, 0x00000000, 0x00000000, +}; + +static const u32 b43_ntab_tdi40a0_r3[] = { + 0x0011a346, 0x00136ccf, 0x0014f5d9, 0x001641e2, + 0x0017cb6b, 0x00195475, 0x001b2383, 0x001cad0c, + 0x001e7616, 0x0000821f, 0x00020ba8, 0x0003d4b2, + 0x00056447, 0x00072dd0, 0x0008b6da, 0x000a02e3, + 0x000b8c6c, 0x000d15f6, 0x0011e484, 0x0013ae0d, + 0x00153717, 0x00168320, 0x00180ca9, 0x00199633, + 0x001b6548, 0x001ceed1, 0x001eb7db, 0x0000c3e4, + 0x00024d6d, 0x000416f7, 0x0005a585, 0x00076f0f, + 0x0008f818, 0x000a4421, 0x000bcdab, 0x000d9734, + 0x00122649, 0x0013efd2, 0x001578dc, 0x0016c4e5, + 0x00184e6e, 0x001a17f8, 0x001ba686, 0x001d3010, + 0x001ef999, 0x00010522, 0x00028eac, 0x00045835, + 0x0005e74a, 0x0007b0d3, 0x00093a5d, 0x000a85e6, + 0x000c0f6f, 0x000dd8f9, 0x00126787, 0x00143111, + 0x0015ba9a, 0x00170623, 0x00188fad, 0x001a5936, + 0x001be84b, 0x001db1d4, 0x001f3b5e, 0x000146e7, + 0x00031070, 0x000499fa, 0x00062888, 0x0007f212, + 0x00097b9b, 0x000ac7a4, 0x000c50ae, 0x000e1a37, + 0x0012a94c, 0x001472d5, 0x0015fc5f, 0x00174868, + 0x0018d171, 0x001a9afb, 0x001c2989, 0x001df313, + 0x001f7c9c, 0x000188a5, 0x000351af, 0x0004db38, + 0x0006aa4d, 0x000833d7, 0x0009bd60, 0x000b0969, + 0x000c9273, 0x000e5bfc, 0x00132a8a, 0x0014b414, + 0x00163d9d, 0x001789a6, 0x001912b0, 0x001adc39, + 0x001c6bce, 0x001e34d8, 0x001fbe61, 0x0001ca6a, + 0x00039374, 0x00051cfd, 0x0006ec0b, 0x00087515, + 0x0009fe9e, 0x000b4aa7, 0x000cd3b1, 0x000e9d3a, + 0x00000000, 0x00000000, +}; + +static const u32 b43_ntab_tdi40a1_r3[] = { + 0x001edb36, 0x000129ca, 0x0002b353, 0x00047cdd, + 0x0005c8e6, 0x000791ef, 0x00091bf9, 0x000aaa07, + 0x000c3391, 0x000dfd1a, 0x00120923, 0x0013d22d, + 0x00155c37, 0x0016eacb, 0x00187454, 0x001a3dde, + 0x001b89e7, 0x001d12f0, 0x001f1cfa, 0x00016b88, + 0x00033492, 0x0004be1b, 0x00060a24, 0x0007d32e, + 0x00095d38, 0x000aec4c, 0x000c7555, 0x000e3edf, + 0x00124ae8, 0x001413f1, 0x0015a37b, 0x00172c89, + 0x0018b593, 0x001a419c, 0x001bcb25, 0x001d942f, + 0x001f63b9, 0x0001ad4d, 0x00037657, 0x0004c260, + 0x00068be9, 0x000814f3, 0x0009a47c, 0x000b2d8a, + 0x000cb694, 0x000e429d, 0x00128c26, 0x001455b0, + 0x0015e4ba, 0x00176e4e, 0x0018f758, 0x001a8361, + 0x001c0cea, 0x001dd674, 0x001fa57d, 0x0001ee8b, + 0x0003b795, 0x0005039e, 0x0006cd27, 0x000856b1, + 0x0009e5c6, 0x000b6f4f, 0x000cf859, 0x000e8462, + 0x00130deb, 0x00149775, 0x00162603, 0x0017af8c, + 0x00193896, 0x001ac49f, 0x001c4e28, 0x001e17b2, + 0x0000a6c7, 0x00023050, 0x0003f9da, 0x00054563, + 0x00070eec, 0x00089876, 0x000a2704, 0x000bb08d, + 0x000d3a17, 0x001185a0, 0x00134f29, 0x0014d8b3, + 0x001667c8, 0x0017f151, 0x00197adb, 0x001b0664, + 0x001c8fed, 0x001e5977, 0x0000e805, 0x0002718f, + 0x00043b18, 0x000586a1, 0x0007502b, 0x0008d9b4, + 0x000a68c9, 0x000bf252, 0x000dbbdc, 0x0011c7e5, + 0x001390ee, 0x00151a78, 0x0016a906, 0x00183290, + 0x0019bc19, 0x001b4822, 0x001cd12c, 0x001e9ab5, + 0x00000000, 0x00000000, +}; + +static const u32 b43_ntab_pilotlt_r3[] = { + 0x76540213, 0x62407351, 0x76543210, 0x76540213, + 0x76540213, 0x76430521, +}; + +static const u32 b43_ntab_channelest_r3[] = { + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, + 0x10101010, 0x10101010, 0x10101010, 0x10101010, +}; + +static const u8 b43_ntab_framelookup_r3[] = { + 0x02, 0x04, 0x14, 0x14, 0x03, 0x05, 0x16, 0x16, + 0x0a, 0x0c, 0x1c, 0x1c, 0x0b, 0x0d, 0x1e, 0x1e, + 0x06, 0x08, 0x18, 0x18, 0x07, 0x09, 0x1a, 0x1a, + 0x0e, 0x10, 0x20, 0x28, 0x0f, 0x11, 0x22, 0x2a, +}; + +static const u8 b43_ntab_estimatepowerlt0_r3[] = { + 0x55, 0x54, 0x54, 0x53, 0x52, 0x52, 0x51, 0x51, + 0x50, 0x4f, 0x4f, 0x4e, 0x4e, 0x4d, 0x4c, 0x4c, + 0x4b, 0x4a, 0x49, 0x49, 0x48, 0x47, 0x46, 0x46, + 0x45, 0x44, 0x43, 0x42, 0x41, 0x40, 0x40, 0x3f, + 0x3e, 0x3d, 0x3c, 0x3a, 0x39, 0x38, 0x37, 0x36, + 0x35, 0x33, 0x32, 0x31, 0x2f, 0x2e, 0x2c, 0x2b, + 0x29, 0x27, 0x25, 0x23, 0x21, 0x1f, 0x1d, 0x1a, + 0x18, 0x15, 0x12, 0x0e, 0x0b, 0x07, 0x02, 0xfd, +}; + +static const u8 b43_ntab_estimatepowerlt1_r3[] = { + 0x55, 0x54, 0x54, 0x53, 0x52, 0x52, 0x51, 0x51, + 0x50, 0x4f, 0x4f, 0x4e, 0x4e, 0x4d, 0x4c, 0x4c, + 0x4b, 0x4a, 0x49, 0x49, 0x48, 0x47, 0x46, 0x46, + 0x45, 0x44, 0x43, 0x42, 0x41, 0x40, 0x40, 0x3f, + 0x3e, 0x3d, 0x3c, 0x3a, 0x39, 0x38, 0x37, 0x36, + 0x35, 0x33, 0x32, 0x31, 0x2f, 0x2e, 0x2c, 0x2b, + 0x29, 0x27, 0x25, 0x23, 0x21, 0x1f, 0x1d, 0x1a, + 0x18, 0x15, 0x12, 0x0e, 0x0b, 0x07, 0x02, 0xfd, +}; + +static const u8 b43_ntab_adjustpower0_r3[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const u8 b43_ntab_adjustpower1_r3[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const u32 b43_ntab_gainctl0_r3[] = { + 0x5bf70044, 0x5bf70042, 0x5bf70040, 0x5bf7003e, + 0x5bf7003c, 0x5bf7003b, 0x5bf70039, 0x5bf70037, + 0x5bf70036, 0x5bf70034, 0x5bf70033, 0x5bf70031, + 0x5bf70030, 0x5ba70044, 0x5ba70042, 0x5ba70040, + 0x5ba7003e, 0x5ba7003c, 0x5ba7003b, 0x5ba70039, + 0x5ba70037, 0x5ba70036, 0x5ba70034, 0x5ba70033, + 0x5b770044, 0x5b770042, 0x5b770040, 0x5b77003e, + 0x5b77003c, 0x5b77003b, 0x5b770039, 0x5b770037, + 0x5b770036, 0x5b770034, 0x5b770033, 0x5b770031, + 0x5b770030, 0x5b77002f, 0x5b77002d, 0x5b77002c, + 0x5b470044, 0x5b470042, 0x5b470040, 0x5b47003e, + 0x5b47003c, 0x5b47003b, 0x5b470039, 0x5b470037, + 0x5b470036, 0x5b470034, 0x5b470033, 0x5b470031, + 0x5b470030, 0x5b47002f, 0x5b47002d, 0x5b47002c, + 0x5b47002b, 0x5b47002a, 0x5b270044, 0x5b270042, + 0x5b270040, 0x5b27003e, 0x5b27003c, 0x5b27003b, + 0x5b270039, 0x5b270037, 0x5b270036, 0x5b270034, + 0x5b270033, 0x5b270031, 0x5b270030, 0x5b27002f, + 0x5b170044, 0x5b170042, 0x5b170040, 0x5b17003e, + 0x5b17003c, 0x5b17003b, 0x5b170039, 0x5b170037, + 0x5b170036, 0x5b170034, 0x5b170033, 0x5b170031, + 0x5b170030, 0x5b17002f, 0x5b17002d, 0x5b17002c, + 0x5b17002b, 0x5b17002a, 0x5b170028, 0x5b170027, + 0x5b170026, 0x5b170025, 0x5b170024, 0x5b170023, + 0x5b070044, 0x5b070042, 0x5b070040, 0x5b07003e, + 0x5b07003c, 0x5b07003b, 0x5b070039, 0x5b070037, + 0x5b070036, 0x5b070034, 0x5b070033, 0x5b070031, + 0x5b070030, 0x5b07002f, 0x5b07002d, 0x5b07002c, + 0x5b07002b, 0x5b07002a, 0x5b070028, 0x5b070027, + 0x5b070026, 0x5b070025, 0x5b070024, 0x5b070023, + 0x5b070022, 0x5b070021, 0x5b070020, 0x5b07001f, + 0x5b07001e, 0x5b07001d, 0x5b07001d, 0x5b07001c, +}; + +static const u32 b43_ntab_gainctl1_r3[] = { + 0x5bf70044, 0x5bf70042, 0x5bf70040, 0x5bf7003e, + 0x5bf7003c, 0x5bf7003b, 0x5bf70039, 0x5bf70037, + 0x5bf70036, 0x5bf70034, 0x5bf70033, 0x5bf70031, + 0x5bf70030, 0x5ba70044, 0x5ba70042, 0x5ba70040, + 0x5ba7003e, 0x5ba7003c, 0x5ba7003b, 0x5ba70039, + 0x5ba70037, 0x5ba70036, 0x5ba70034, 0x5ba70033, + 0x5b770044, 0x5b770042, 0x5b770040, 0x5b77003e, + 0x5b77003c, 0x5b77003b, 0x5b770039, 0x5b770037, + 0x5b770036, 0x5b770034, 0x5b770033, 0x5b770031, + 0x5b770030, 0x5b77002f, 0x5b77002d, 0x5b77002c, + 0x5b470044, 0x5b470042, 0x5b470040, 0x5b47003e, + 0x5b47003c, 0x5b47003b, 0x5b470039, 0x5b470037, + 0x5b470036, 0x5b470034, 0x5b470033, 0x5b470031, + 0x5b470030, 0x5b47002f, 0x5b47002d, 0x5b47002c, + 0x5b47002b, 0x5b47002a, 0x5b270044, 0x5b270042, + 0x5b270040, 0x5b27003e, 0x5b27003c, 0x5b27003b, + 0x5b270039, 0x5b270037, 0x5b270036, 0x5b270034, + 0x5b270033, 0x5b270031, 0x5b270030, 0x5b27002f, + 0x5b170044, 0x5b170042, 0x5b170040, 0x5b17003e, + 0x5b17003c, 0x5b17003b, 0x5b170039, 0x5b170037, + 0x5b170036, 0x5b170034, 0x5b170033, 0x5b170031, + 0x5b170030, 0x5b17002f, 0x5b17002d, 0x5b17002c, + 0x5b17002b, 0x5b17002a, 0x5b170028, 0x5b170027, + 0x5b170026, 0x5b170025, 0x5b170024, 0x5b170023, + 0x5b070044, 0x5b070042, 0x5b070040, 0x5b07003e, + 0x5b07003c, 0x5b07003b, 0x5b070039, 0x5b070037, + 0x5b070036, 0x5b070034, 0x5b070033, 0x5b070031, + 0x5b070030, 0x5b07002f, 0x5b07002d, 0x5b07002c, + 0x5b07002b, 0x5b07002a, 0x5b070028, 0x5b070027, + 0x5b070026, 0x5b070025, 0x5b070024, 0x5b070023, + 0x5b070022, 0x5b070021, 0x5b070020, 0x5b07001f, + 0x5b07001e, 0x5b07001d, 0x5b07001d, 0x5b07001c, +}; + +static const u32 b43_ntab_iqlt0_r3[] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, +}; + +static const u32 b43_ntab_iqlt1_r3[] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, +}; + +static const u16 b43_ntab_loftlt0_r3[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, +}; + +static const u16 b43_ntab_loftlt1_r3[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, +}; + +/* volatile tables, PHY revision >= 3 */ + +/* indexed by antswctl2g */ +static const u16 b43_ntab_antswctl_r3[4][32] = { + { + 0x0082, 0x0082, 0x0211, 0x0222, 0x0328, + 0x0000, 0x0000, 0x0000, 0x0144, 0x0000, + 0x0000, 0x0000, 0x0188, 0x0000, 0x0000, + 0x0000, 0x0082, 0x0082, 0x0211, 0x0222, + 0x0328, 0x0000, 0x0000, 0x0000, 0x0144, + 0x0000, 0x0000, 0x0000, 0x0188, 0x0000, + 0x0000, 0x0000, + }, + { + 0x0022, 0x0022, 0x0011, 0x0022, 0x0022, + 0x0000, 0x0000, 0x0000, 0x0011, 0x0000, + 0x0000, 0x0000, 0x0022, 0x0000, 0x0000, + 0x0000, 0x0022, 0x0022, 0x0011, 0x0022, + 0x0022, 0x0000, 0x0000, 0x0000, 0x0011, + 0x0000, 0x0000, 0x0000, 0x0022, 0x0000, + 0x0000, 0x0000, + }, + { + 0x0088, 0x0088, 0x0044, 0x0088, 0x0088, + 0x0000, 0x0000, 0x0000, 0x0044, 0x0000, + 0x0000, 0x0000, 0x0088, 0x0000, 0x0000, + 0x0000, 0x0088, 0x0088, 0x0044, 0x0088, + 0x0088, 0x0000, 0x0000, 0x0000, 0x0044, + 0x0000, 0x0000, 0x0000, 0x0088, 0x0000, + 0x0000, 0x0000, + }, + { + 0x0022, 0x0022, 0x0011, 0x0022, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0011, 0x0000, + 0x0000, 0x0000, 0x0022, 0x0000, 0x0000, + 0x03cc, 0x0022, 0x0022, 0x0011, 0x0022, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0011, + 0x0000, 0x0000, 0x0000, 0x0022, 0x0000, + 0x0000, 0x03cc, + } +}; + +/* TX gain tables */ +static const u32 b43_ntab_tx_gain_rev0_1_2[] = { + 0x03cc2b44, 0x03cc2b42, 0x03cc2a44, 0x03cc2a42, + 0x03cc2944, 0x03c82b44, 0x03c82b42, 0x03c82a44, + 0x03c82a42, 0x03c82944, 0x03c82942, 0x03c82844, + 0x03c82842, 0x03c42b44, 0x03c42b42, 0x03c42a44, + 0x03c42a42, 0x03c42944, 0x03c42942, 0x03c42844, + 0x03c42842, 0x03c42744, 0x03c42742, 0x03c42644, + 0x03c42642, 0x03c42544, 0x03c42542, 0x03c42444, + 0x03c42442, 0x03c02b44, 0x03c02b42, 0x03c02a44, + 0x03c02a42, 0x03c02944, 0x03c02942, 0x03c02844, + 0x03c02842, 0x03c02744, 0x03c02742, 0x03b02b44, + 0x03b02b42, 0x03b02a44, 0x03b02a42, 0x03b02944, + 0x03b02942, 0x03b02844, 0x03b02842, 0x03b02744, + 0x03b02742, 0x03b02644, 0x03b02642, 0x03b02544, + 0x03b02542, 0x03a02b44, 0x03a02b42, 0x03a02a44, + 0x03a02a42, 0x03a02944, 0x03a02942, 0x03a02844, + 0x03a02842, 0x03a02744, 0x03a02742, 0x03902b44, + 0x03902b42, 0x03902a44, 0x03902a42, 0x03902944, + 0x03902942, 0x03902844, 0x03902842, 0x03902744, + 0x03902742, 0x03902644, 0x03902642, 0x03902544, + 0x03902542, 0x03802b44, 0x03802b42, 0x03802a44, + 0x03802a42, 0x03802944, 0x03802942, 0x03802844, + 0x03802842, 0x03802744, 0x03802742, 0x03802644, + 0x03802642, 0x03802544, 0x03802542, 0x03802444, + 0x03802442, 0x03802344, 0x03802342, 0x03802244, + 0x03802242, 0x03802144, 0x03802142, 0x03802044, + 0x03802042, 0x03801f44, 0x03801f42, 0x03801e44, + 0x03801e42, 0x03801d44, 0x03801d42, 0x03801c44, + 0x03801c42, 0x03801b44, 0x03801b42, 0x03801a44, + 0x03801a42, 0x03801944, 0x03801942, 0x03801844, + 0x03801842, 0x03801744, 0x03801742, 0x03801644, + 0x03801642, 0x03801544, 0x03801542, 0x03801444, + 0x03801442, 0x03801344, 0x03801342, 0x00002b00, +}; + +static const u32 b43_ntab_tx_gain_rev3plus_2ghz[] = { + 0x1f410044, 0x1f410042, 0x1f410040, 0x1f41003e, + 0x1f41003c, 0x1f41003b, 0x1f410039, 0x1f410037, + 0x1e410044, 0x1e410042, 0x1e410040, 0x1e41003e, + 0x1e41003c, 0x1e41003b, 0x1e410039, 0x1e410037, + 0x1d410044, 0x1d410042, 0x1d410040, 0x1d41003e, + 0x1d41003c, 0x1d41003b, 0x1d410039, 0x1d410037, + 0x1c410044, 0x1c410042, 0x1c410040, 0x1c41003e, + 0x1c41003c, 0x1c41003b, 0x1c410039, 0x1c410037, + 0x1b410044, 0x1b410042, 0x1b410040, 0x1b41003e, + 0x1b41003c, 0x1b41003b, 0x1b410039, 0x1b410037, + 0x1a410044, 0x1a410042, 0x1a410040, 0x1a41003e, + 0x1a41003c, 0x1a41003b, 0x1a410039, 0x1a410037, + 0x19410044, 0x19410042, 0x19410040, 0x1941003e, + 0x1941003c, 0x1941003b, 0x19410039, 0x19410037, + 0x18410044, 0x18410042, 0x18410040, 0x1841003e, + 0x1841003c, 0x1841003b, 0x18410039, 0x18410037, + 0x17410044, 0x17410042, 0x17410040, 0x1741003e, + 0x1741003c, 0x1741003b, 0x17410039, 0x17410037, + 0x16410044, 0x16410042, 0x16410040, 0x1641003e, + 0x1641003c, 0x1641003b, 0x16410039, 0x16410037, + 0x15410044, 0x15410042, 0x15410040, 0x1541003e, + 0x1541003c, 0x1541003b, 0x15410039, 0x15410037, + 0x14410044, 0x14410042, 0x14410040, 0x1441003e, + 0x1441003c, 0x1441003b, 0x14410039, 0x14410037, + 0x13410044, 0x13410042, 0x13410040, 0x1341003e, + 0x1341003c, 0x1341003b, 0x13410039, 0x13410037, + 0x12410044, 0x12410042, 0x12410040, 0x1241003e, + 0x1241003c, 0x1241003b, 0x12410039, 0x12410037, + 0x11410044, 0x11410042, 0x11410040, 0x1141003e, + 0x1141003c, 0x1141003b, 0x11410039, 0x11410037, + 0x10410044, 0x10410042, 0x10410040, 0x1041003e, + 0x1041003c, 0x1041003b, 0x10410039, 0x10410037, +}; + +static const u32 b43_ntab_tx_gain_rev3_5ghz[] = { + 0xcff70044, 0xcff70042, 0xcff70040, 0xcff7003e, + 0xcff7003c, 0xcff7003b, 0xcff70039, 0xcff70037, + 0xcef70044, 0xcef70042, 0xcef70040, 0xcef7003e, + 0xcef7003c, 0xcef7003b, 0xcef70039, 0xcef70037, + 0xcdf70044, 0xcdf70042, 0xcdf70040, 0xcdf7003e, + 0xcdf7003c, 0xcdf7003b, 0xcdf70039, 0xcdf70037, + 0xccf70044, 0xccf70042, 0xccf70040, 0xccf7003e, + 0xccf7003c, 0xccf7003b, 0xccf70039, 0xccf70037, + 0xcbf70044, 0xcbf70042, 0xcbf70040, 0xcbf7003e, + 0xcbf7003c, 0xcbf7003b, 0xcbf70039, 0xcbf70037, + 0xcaf70044, 0xcaf70042, 0xcaf70040, 0xcaf7003e, + 0xcaf7003c, 0xcaf7003b, 0xcaf70039, 0xcaf70037, + 0xc9f70044, 0xc9f70042, 0xc9f70040, 0xc9f7003e, + 0xc9f7003c, 0xc9f7003b, 0xc9f70039, 0xc9f70037, + 0xc8f70044, 0xc8f70042, 0xc8f70040, 0xc8f7003e, + 0xc8f7003c, 0xc8f7003b, 0xc8f70039, 0xc8f70037, + 0xc7f70044, 0xc7f70042, 0xc7f70040, 0xc7f7003e, + 0xc7f7003c, 0xc7f7003b, 0xc7f70039, 0xc7f70037, + 0xc6f70044, 0xc6f70042, 0xc6f70040, 0xc6f7003e, + 0xc6f7003c, 0xc6f7003b, 0xc6f70039, 0xc6f70037, + 0xc5f70044, 0xc5f70042, 0xc5f70040, 0xc5f7003e, + 0xc5f7003c, 0xc5f7003b, 0xc5f70039, 0xc5f70037, + 0xc4f70044, 0xc4f70042, 0xc4f70040, 0xc4f7003e, + 0xc4f7003c, 0xc4f7003b, 0xc4f70039, 0xc4f70037, + 0xc3f70044, 0xc3f70042, 0xc3f70040, 0xc3f7003e, + 0xc3f7003c, 0xc3f7003b, 0xc3f70039, 0xc3f70037, + 0xc2f70044, 0xc2f70042, 0xc2f70040, 0xc2f7003e, + 0xc2f7003c, 0xc2f7003b, 0xc2f70039, 0xc2f70037, + 0xc1f70044, 0xc1f70042, 0xc1f70040, 0xc1f7003e, + 0xc1f7003c, 0xc1f7003b, 0xc1f70039, 0xc1f70037, + 0xc0f70044, 0xc0f70042, 0xc0f70040, 0xc0f7003e, + 0xc0f7003c, 0xc0f7003b, 0xc0f70039, 0xc0f70037, +}; + +static const u32 b43_ntab_tx_gain_rev4_5ghz[] = { + 0x2ff20044, 0x2ff20042, 0x2ff20040, 0x2ff2003e, + 0x2ff2003c, 0x2ff2003b, 0x2ff20039, 0x2ff20037, + 0x2ef20044, 0x2ef20042, 0x2ef20040, 0x2ef2003e, + 0x2ef2003c, 0x2ef2003b, 0x2ef20039, 0x2ef20037, + 0x2df20044, 0x2df20042, 0x2df20040, 0x2df2003e, + 0x2df2003c, 0x2df2003b, 0x2df20039, 0x2df20037, + 0x2cf20044, 0x2cf20042, 0x2cf20040, 0x2cf2003e, + 0x2cf2003c, 0x2cf2003b, 0x2cf20039, 0x2cf20037, + 0x2bf20044, 0x2bf20042, 0x2bf20040, 0x2bf2003e, + 0x2bf2003c, 0x2bf2003b, 0x2bf20039, 0x2bf20037, + 0x2af20044, 0x2af20042, 0x2af20040, 0x2af2003e, + 0x2af2003c, 0x2af2003b, 0x2af20039, 0x2af20037, + 0x29f20044, 0x29f20042, 0x29f20040, 0x29f2003e, + 0x29f2003c, 0x29f2003b, 0x29f20039, 0x29f20037, + 0x28f20044, 0x28f20042, 0x28f20040, 0x28f2003e, + 0x28f2003c, 0x28f2003b, 0x28f20039, 0x28f20037, + 0x27f20044, 0x27f20042, 0x27f20040, 0x27f2003e, + 0x27f2003c, 0x27f2003b, 0x27f20039, 0x27f20037, + 0x26f20044, 0x26f20042, 0x26f20040, 0x26f2003e, + 0x26f2003c, 0x26f2003b, 0x26f20039, 0x26f20037, + 0x25f20044, 0x25f20042, 0x25f20040, 0x25f2003e, + 0x25f2003c, 0x25f2003b, 0x25f20039, 0x25f20037, + 0x24f20044, 0x24f20042, 0x24f20040, 0x24f2003e, + 0x24f2003c, 0x24f2003b, 0x24f20039, 0x24f20038, + 0x23f20041, 0x23f20040, 0x23f2003f, 0x23f2003e, + 0x23f2003c, 0x23f2003b, 0x23f20039, 0x23f20037, + 0x22f20044, 0x22f20042, 0x22f20040, 0x22f2003e, + 0x22f2003c, 0x22f2003b, 0x22f20039, 0x22f20037, + 0x21f20044, 0x21f20042, 0x21f20040, 0x21f2003e, + 0x21f2003c, 0x21f2003b, 0x21f20039, 0x21f20037, + 0x20d20043, 0x20d20041, 0x20d2003e, 0x20d2003c, + 0x20d2003a, 0x20d20038, 0x20d20036, 0x20d20034, +}; + +static const u32 b43_ntab_tx_gain_rev5plus_5ghz[] = { + 0x0f62004a, 0x0f620048, 0x0f620046, 0x0f620044, + 0x0f620042, 0x0f620040, 0x0f62003e, 0x0f62003c, + 0x0e620044, 0x0e620042, 0x0e620040, 0x0e62003e, + 0x0e62003c, 0x0e62003d, 0x0e62003b, 0x0e62003a, + 0x0d620043, 0x0d620041, 0x0d620040, 0x0d62003e, + 0x0d62003d, 0x0d62003c, 0x0d62003b, 0x0d62003a, + 0x0c620041, 0x0c620040, 0x0c62003f, 0x0c62003e, + 0x0c62003c, 0x0c62003b, 0x0c620039, 0x0c620037, + 0x0b620046, 0x0b620044, 0x0b620042, 0x0b620040, + 0x0b62003e, 0x0b62003c, 0x0b62003b, 0x0b62003a, + 0x0a620041, 0x0a620040, 0x0a62003e, 0x0a62003c, + 0x0a62003b, 0x0a62003a, 0x0a620039, 0x0a620038, + 0x0962003e, 0x0962003d, 0x0962003c, 0x0962003b, + 0x09620039, 0x09620037, 0x09620035, 0x09620033, + 0x08620044, 0x08620042, 0x08620040, 0x0862003e, + 0x0862003c, 0x0862003b, 0x0862003a, 0x08620039, + 0x07620043, 0x07620042, 0x07620040, 0x0762003f, + 0x0762003d, 0x0762003b, 0x0762003a, 0x07620039, + 0x0662003e, 0x0662003d, 0x0662003c, 0x0662003b, + 0x06620039, 0x06620037, 0x06620035, 0x06620033, + 0x05620046, 0x05620044, 0x05620042, 0x05620040, + 0x0562003e, 0x0562003c, 0x0562003b, 0x05620039, + 0x04620044, 0x04620042, 0x04620040, 0x0462003e, + 0x0462003c, 0x0462003b, 0x04620039, 0x04620038, + 0x0362003c, 0x0362003b, 0x0362003a, 0x03620039, + 0x03620038, 0x03620037, 0x03620035, 0x03620033, + 0x0262004c, 0x0262004a, 0x02620048, 0x02620047, + 0x02620046, 0x02620044, 0x02620043, 0x02620042, + 0x0162004a, 0x01620048, 0x01620046, 0x01620044, + 0x01620043, 0x01620042, 0x01620041, 0x01620040, + 0x00620042, 0x00620040, 0x0062003e, 0x0062003c, + 0x0062003b, 0x00620039, 0x00620037, 0x00620035, +}; + +static const u32 txpwrctrl_tx_gain_ipa[] = { + 0x5ff7002d, 0x5ff7002b, 0x5ff7002a, 0x5ff70029, + 0x5ff70028, 0x5ff70027, 0x5ff70026, 0x5ff70025, + 0x5ef7002d, 0x5ef7002b, 0x5ef7002a, 0x5ef70029, + 0x5ef70028, 0x5ef70027, 0x5ef70026, 0x5ef70025, + 0x5df7002d, 0x5df7002b, 0x5df7002a, 0x5df70029, + 0x5df70028, 0x5df70027, 0x5df70026, 0x5df70025, + 0x5cf7002d, 0x5cf7002b, 0x5cf7002a, 0x5cf70029, + 0x5cf70028, 0x5cf70027, 0x5cf70026, 0x5cf70025, + 0x5bf7002d, 0x5bf7002b, 0x5bf7002a, 0x5bf70029, + 0x5bf70028, 0x5bf70027, 0x5bf70026, 0x5bf70025, + 0x5af7002d, 0x5af7002b, 0x5af7002a, 0x5af70029, + 0x5af70028, 0x5af70027, 0x5af70026, 0x5af70025, + 0x59f7002d, 0x59f7002b, 0x59f7002a, 0x59f70029, + 0x59f70028, 0x59f70027, 0x59f70026, 0x59f70025, + 0x58f7002d, 0x58f7002b, 0x58f7002a, 0x58f70029, + 0x58f70028, 0x58f70027, 0x58f70026, 0x58f70025, + 0x57f7002d, 0x57f7002b, 0x57f7002a, 0x57f70029, + 0x57f70028, 0x57f70027, 0x57f70026, 0x57f70025, + 0x56f7002d, 0x56f7002b, 0x56f7002a, 0x56f70029, + 0x56f70028, 0x56f70027, 0x56f70026, 0x56f70025, + 0x55f7002d, 0x55f7002b, 0x55f7002a, 0x55f70029, + 0x55f70028, 0x55f70027, 0x55f70026, 0x55f70025, + 0x54f7002d, 0x54f7002b, 0x54f7002a, 0x54f70029, + 0x54f70028, 0x54f70027, 0x54f70026, 0x54f70025, + 0x53f7002d, 0x53f7002b, 0x53f7002a, 0x53f70029, + 0x53f70028, 0x53f70027, 0x53f70026, 0x53f70025, + 0x52f7002d, 0x52f7002b, 0x52f7002a, 0x52f70029, + 0x52f70028, 0x52f70027, 0x52f70026, 0x52f70025, + 0x51f7002d, 0x51f7002b, 0x51f7002a, 0x51f70029, + 0x51f70028, 0x51f70027, 0x51f70026, 0x51f70025, + 0x50f7002d, 0x50f7002b, 0x50f7002a, 0x50f70029, + 0x50f70028, 0x50f70027, 0x50f70026, 0x50f70025, +}; + +static const u32 txpwrctrl_tx_gain_ipa_rev5[] = { + 0x1ff7002d, 0x1ff7002b, 0x1ff7002a, 0x1ff70029, + 0x1ff70028, 0x1ff70027, 0x1ff70026, 0x1ff70025, + 0x1ef7002d, 0x1ef7002b, 0x1ef7002a, 0x1ef70029, + 0x1ef70028, 0x1ef70027, 0x1ef70026, 0x1ef70025, + 0x1df7002d, 0x1df7002b, 0x1df7002a, 0x1df70029, + 0x1df70028, 0x1df70027, 0x1df70026, 0x1df70025, + 0x1cf7002d, 0x1cf7002b, 0x1cf7002a, 0x1cf70029, + 0x1cf70028, 0x1cf70027, 0x1cf70026, 0x1cf70025, + 0x1bf7002d, 0x1bf7002b, 0x1bf7002a, 0x1bf70029, + 0x1bf70028, 0x1bf70027, 0x1bf70026, 0x1bf70025, + 0x1af7002d, 0x1af7002b, 0x1af7002a, 0x1af70029, + 0x1af70028, 0x1af70027, 0x1af70026, 0x1af70025, + 0x19f7002d, 0x19f7002b, 0x19f7002a, 0x19f70029, + 0x19f70028, 0x19f70027, 0x19f70026, 0x19f70025, + 0x18f7002d, 0x18f7002b, 0x18f7002a, 0x18f70029, + 0x18f70028, 0x18f70027, 0x18f70026, 0x18f70025, + 0x17f7002d, 0x17f7002b, 0x17f7002a, 0x17f70029, + 0x17f70028, 0x17f70027, 0x17f70026, 0x17f70025, + 0x16f7002d, 0x16f7002b, 0x16f7002a, 0x16f70029, + 0x16f70028, 0x16f70027, 0x16f70026, 0x16f70025, + 0x15f7002d, 0x15f7002b, 0x15f7002a, 0x15f70029, + 0x15f70028, 0x15f70027, 0x15f70026, 0x15f70025, + 0x14f7002d, 0x14f7002b, 0x14f7002a, 0x14f70029, + 0x14f70028, 0x14f70027, 0x14f70026, 0x14f70025, + 0x13f7002d, 0x13f7002b, 0x13f7002a, 0x13f70029, + 0x13f70028, 0x13f70027, 0x13f70026, 0x13f70025, + 0x12f7002d, 0x12f7002b, 0x12f7002a, 0x12f70029, + 0x12f70028, 0x12f70027, 0x12f70026, 0x12f70025, + 0x11f7002d, 0x11f7002b, 0x11f7002a, 0x11f70029, + 0x11f70028, 0x11f70027, 0x11f70026, 0x11f70025, + 0x10f7002d, 0x10f7002b, 0x10f7002a, 0x10f70029, + 0x10f70028, 0x10f70027, 0x10f70026, 0x10f70025, +}; + +static const u32 txpwrctrl_tx_gain_ipa_rev6[] = { + 0x0ff7002d, 0x0ff7002b, 0x0ff7002a, 0x0ff70029, + 0x0ff70028, 0x0ff70027, 0x0ff70026, 0x0ff70025, + 0x0ef7002d, 0x0ef7002b, 0x0ef7002a, 0x0ef70029, + 0x0ef70028, 0x0ef70027, 0x0ef70026, 0x0ef70025, + 0x0df7002d, 0x0df7002b, 0x0df7002a, 0x0df70029, + 0x0df70028, 0x0df70027, 0x0df70026, 0x0df70025, + 0x0cf7002d, 0x0cf7002b, 0x0cf7002a, 0x0cf70029, + 0x0cf70028, 0x0cf70027, 0x0cf70026, 0x0cf70025, + 0x0bf7002d, 0x0bf7002b, 0x0bf7002a, 0x0bf70029, + 0x0bf70028, 0x0bf70027, 0x0bf70026, 0x0bf70025, + 0x0af7002d, 0x0af7002b, 0x0af7002a, 0x0af70029, + 0x0af70028, 0x0af70027, 0x0af70026, 0x0af70025, + 0x09f7002d, 0x09f7002b, 0x09f7002a, 0x09f70029, + 0x09f70028, 0x09f70027, 0x09f70026, 0x09f70025, + 0x08f7002d, 0x08f7002b, 0x08f7002a, 0x08f70029, + 0x08f70028, 0x08f70027, 0x08f70026, 0x08f70025, + 0x07f7002d, 0x07f7002b, 0x07f7002a, 0x07f70029, + 0x07f70028, 0x07f70027, 0x07f70026, 0x07f70025, + 0x06f7002d, 0x06f7002b, 0x06f7002a, 0x06f70029, + 0x06f70028, 0x06f70027, 0x06f70026, 0x06f70025, + 0x05f7002d, 0x05f7002b, 0x05f7002a, 0x05f70029, + 0x05f70028, 0x05f70027, 0x05f70026, 0x05f70025, + 0x04f7002d, 0x04f7002b, 0x04f7002a, 0x04f70029, + 0x04f70028, 0x04f70027, 0x04f70026, 0x04f70025, + 0x03f7002d, 0x03f7002b, 0x03f7002a, 0x03f70029, + 0x03f70028, 0x03f70027, 0x03f70026, 0x03f70025, + 0x02f7002d, 0x02f7002b, 0x02f7002a, 0x02f70029, + 0x02f70028, 0x02f70027, 0x02f70026, 0x02f70025, + 0x01f7002d, 0x01f7002b, 0x01f7002a, 0x01f70029, + 0x01f70028, 0x01f70027, 0x01f70026, 0x01f70025, + 0x00f7002d, 0x00f7002b, 0x00f7002a, 0x00f70029, + 0x00f70028, 0x00f70027, 0x00f70026, 0x00f70025, +}; + +static const u32 txpwrctrl_tx_gain_ipa_5g[] = { + 0x7ff70035, 0x7ff70033, 0x7ff70032, 0x7ff70031, + 0x7ff7002f, 0x7ff7002e, 0x7ff7002d, 0x7ff7002b, + 0x7ff7002a, 0x7ff70029, 0x7ff70028, 0x7ff70027, + 0x7ff70026, 0x7ff70024, 0x7ff70023, 0x7ff70022, + 0x7ef70028, 0x7ef70027, 0x7ef70026, 0x7ef70025, + 0x7ef70024, 0x7ef70023, 0x7df70028, 0x7df70027, + 0x7df70026, 0x7df70025, 0x7df70024, 0x7df70023, + 0x7df70022, 0x7cf70029, 0x7cf70028, 0x7cf70027, + 0x7cf70026, 0x7cf70025, 0x7cf70023, 0x7cf70022, + 0x7bf70029, 0x7bf70028, 0x7bf70026, 0x7bf70025, + 0x7bf70024, 0x7bf70023, 0x7bf70022, 0x7bf70021, + 0x7af70029, 0x7af70028, 0x7af70027, 0x7af70026, + 0x7af70025, 0x7af70024, 0x7af70023, 0x7af70022, + 0x79f70029, 0x79f70028, 0x79f70027, 0x79f70026, + 0x79f70025, 0x79f70024, 0x79f70023, 0x79f70022, + 0x78f70029, 0x78f70028, 0x78f70027, 0x78f70026, + 0x78f70025, 0x78f70024, 0x78f70023, 0x78f70022, + 0x77f70029, 0x77f70028, 0x77f70027, 0x77f70026, + 0x77f70025, 0x77f70024, 0x77f70023, 0x77f70022, + 0x76f70029, 0x76f70028, 0x76f70027, 0x76f70026, + 0x76f70024, 0x76f70023, 0x76f70022, 0x76f70021, + 0x75f70029, 0x75f70028, 0x75f70027, 0x75f70026, + 0x75f70025, 0x75f70024, 0x75f70023, 0x74f70029, + 0x74f70028, 0x74f70026, 0x74f70025, 0x74f70024, + 0x74f70023, 0x74f70022, 0x73f70029, 0x73f70027, + 0x73f70026, 0x73f70025, 0x73f70024, 0x73f70023, + 0x73f70022, 0x72f70028, 0x72f70027, 0x72f70026, + 0x72f70025, 0x72f70024, 0x72f70023, 0x72f70022, + 0x71f70028, 0x71f70027, 0x71f70026, 0x71f70025, + 0x71f70024, 0x71f70023, 0x70f70028, 0x70f70027, + 0x70f70026, 0x70f70024, 0x70f70023, 0x70f70022, + 0x70f70021, 0x70f70020, 0x70f70020, 0x70f7001f, +}; + +const s8 b43_ntab_papd_pga_gain_delta_ipa_2g[] = { + -114, -108, -98, -91, -84, -78, -70, -62, + -54, -46, -39, -31, -23, -15, -8, 0 +}; + +const u16 tbl_iqcal_gainparams[2][9][8] = { + { + { 0x000, 0, 0, 2, 0x69, 0x69, 0x69, 0x69 }, + { 0x700, 7, 0, 0, 0x69, 0x69, 0x69, 0x69 }, + { 0x710, 7, 1, 0, 0x68, 0x68, 0x68, 0x68 }, + { 0x720, 7, 2, 0, 0x67, 0x67, 0x67, 0x67 }, + { 0x730, 7, 3, 0, 0x66, 0x66, 0x66, 0x66 }, + { 0x740, 7, 4, 0, 0x65, 0x65, 0x65, 0x65 }, + { 0x741, 7, 4, 1, 0x65, 0x65, 0x65, 0x65 }, + { 0x742, 7, 4, 2, 0x65, 0x65, 0x65, 0x65 }, + { 0x743, 7, 4, 3, 0x65, 0x65, 0x65, 0x65 } + }, + { + { 0x000, 7, 0, 0, 0x79, 0x79, 0x79, 0x79 }, + { 0x700, 7, 0, 0, 0x79, 0x79, 0x79, 0x79 }, + { 0x710, 7, 1, 0, 0x79, 0x79, 0x79, 0x79 }, + { 0x720, 7, 2, 0, 0x78, 0x78, 0x78, 0x78 }, + { 0x730, 7, 3, 0, 0x78, 0x78, 0x78, 0x78 }, + { 0x740, 7, 4, 0, 0x78, 0x78, 0x78, 0x78 }, + { 0x741, 7, 4, 1, 0x78, 0x78, 0x78, 0x78 }, + { 0x742, 7, 4, 2, 0x78, 0x78, 0x78, 0x78 }, + { 0x743, 7, 4, 3, 0x78, 0x78, 0x78, 0x78 } + } +}; + +const struct nphy_txiqcal_ladder ladder_lo[] = { + { 3, 0 }, + { 4, 0 }, + { 6, 0 }, + { 9, 0 }, + { 13, 0 }, + { 18, 0 }, + { 25, 0 }, + { 25, 1 }, + { 25, 2 }, + { 25, 3 }, + { 25, 4 }, + { 25, 5 }, + { 25, 6 }, + { 25, 7 }, + { 35, 7 }, + { 50, 7 }, + { 71, 7 }, + { 100, 7 } +}; + +const struct nphy_txiqcal_ladder ladder_iq[] = { + { 3, 0 }, + { 4, 0 }, + { 6, 0 }, + { 9, 0 }, + { 13, 0 }, + { 18, 0 }, + { 25, 0 }, + { 35, 0 }, + { 50, 0 }, + { 71, 0 }, + { 100, 0 }, + { 100, 1 }, + { 100, 2 }, + { 100, 3 }, + { 100, 4 }, + { 100, 5 }, + { 100, 6 }, + { 100, 7 } +}; + +const u16 loscale[] = { + 256, 256, 271, 271, + 287, 256, 256, 271, + 271, 287, 287, 304, + 304, 256, 256, 271, + 271, 287, 287, 304, + 304, 322, 322, 341, + 341, 362, 362, 383, + 383, 256, 256, 271, + 271, 287, 287, 304, + 304, 322, 322, 256, + 256, 271, 271, 287, + 287, 304, 304, 322, + 322, 341, 341, 362, + 362, 256, 256, 271, + 271, 287, 287, 304, + 304, 322, 322, 256, + 256, 271, 271, 287, + 287, 304, 304, 322, + 322, 341, 341, 362, + 362, 256, 256, 271, + 271, 287, 287, 304, + 304, 322, 322, 341, + 341, 362, 362, 383, + 383, 406, 406, 430, + 430, 455, 455, 482, + 482, 511, 511, 541, + 541, 573, 573, 607, + 607, 643, 643, 681, + 681, 722, 722, 764, + 764, 810, 810, 858, + 858, 908, 908, 962, + 962, 1019, 1019, 256 +}; + +const u16 tbl_tx_iqlo_cal_loft_ladder_40[] = { + 0x0200, 0x0300, 0x0400, 0x0700, + 0x0900, 0x0c00, 0x1200, 0x1201, + 0x1202, 0x1203, 0x1204, 0x1205, + 0x1206, 0x1207, 0x1907, 0x2307, + 0x3207, 0x4707 +}; + +const u16 tbl_tx_iqlo_cal_loft_ladder_20[] = { + 0x0300, 0x0500, 0x0700, 0x0900, + 0x0d00, 0x1100, 0x1900, 0x1901, + 0x1902, 0x1903, 0x1904, 0x1905, + 0x1906, 0x1907, 0x2407, 0x3207, + 0x4607, 0x6407 +}; + +const u16 tbl_tx_iqlo_cal_iqimb_ladder_40[] = { + 0x0100, 0x0200, 0x0400, 0x0700, + 0x0900, 0x0c00, 0x1200, 0x1900, + 0x2300, 0x3200, 0x4700, 0x4701, + 0x4702, 0x4703, 0x4704, 0x4705, + 0x4706, 0x4707 +}; + +const u16 tbl_tx_iqlo_cal_iqimb_ladder_20[] = { + 0x0200, 0x0300, 0x0600, 0x0900, + 0x0d00, 0x1100, 0x1900, 0x2400, + 0x3200, 0x4600, 0x6400, 0x6401, + 0x6402, 0x6403, 0x6404, 0x6405, + 0x6406, 0x6407 +}; + +const u16 tbl_tx_iqlo_cal_startcoefs_nphyrev3[B43_NTAB_TX_IQLO_CAL_STARTCOEFS_REV3] = { }; + +const u16 tbl_tx_iqlo_cal_startcoefs[B43_NTAB_TX_IQLO_CAL_STARTCOEFS] = { }; + +const u16 tbl_tx_iqlo_cal_cmds_recal_nphyrev3[] = { + 0x8423, 0x8323, 0x8073, 0x8256, + 0x8045, 0x8223, 0x9423, 0x9323, + 0x9073, 0x9256, 0x9045, 0x9223 +}; + +const u16 tbl_tx_iqlo_cal_cmds_recal[] = { + 0x8101, 0x8253, 0x8053, 0x8234, + 0x8034, 0x9101, 0x9253, 0x9053, + 0x9234, 0x9034 +}; + +const u16 tbl_tx_iqlo_cal_cmds_fullcal[] = { + 0x8123, 0x8264, 0x8086, 0x8245, + 0x8056, 0x9123, 0x9264, 0x9086, + 0x9245, 0x9056 +}; + +const u16 tbl_tx_iqlo_cal_cmds_fullcal_nphyrev3[] = { + 0x8434, 0x8334, 0x8084, 0x8267, + 0x8056, 0x8234, 0x9434, 0x9334, + 0x9084, 0x9267, 0x9056, 0x9234 +}; + +const s16 tbl_tx_filter_coef_rev4[7][15] = { + { -377, 137, -407, 208, -1527, + 956, 93, 186, 93, 230, + -44, 230, 201, -191, 201 }, + { -77, 20, -98, 49, -93, + 60, 56, 111, 56, 26, + -5, 26, 34, -32, 34 }, + { -360, 164, -376, 164, -1533, + 576, 308, -314, 308, 121, + -73, 121, 91, 124, 91 }, + { -295, 200, -363, 142, -1391, + 826, 151, 301, 151, 151, + 301, 151, 602, -752, 602 }, + { -92, 58, -96, 49, -104, + 44, 17, 35, 17, 12, + 25, 12, 13, 27, 13 }, + { -375, 136, -399, 209, -1479, + 949, 130, 260, 130, 230, + -44, 230, 201, -191, 201 }, + { 0xed9, 0xc8, 0xe95, 0x8e, 0xa91, + 0x33a, 0x97, 0x12d, 0x97, 0x97, + 0x12d, 0x97, 0x25a, 0xd10, 0x25a } +}; + +/* addr0, addr1, bmask, shift */ +const struct nphy_rf_control_override_rev2 tbl_rf_control_override_rev2[] = { + { 0x78, 0x78, 0x0038, 3 }, /* for field == 0x0002 (fls == 2) */ + { 0x7A, 0x7D, 0x0001, 0 }, /* for field == 0x0004 (fls == 3) */ + { 0x7A, 0x7D, 0x0002, 1 }, /* for field == 0x0008 (fls == 4) */ + { 0x7A, 0x7D, 0x0004, 2 }, /* for field == 0x0010 (fls == 5) */ + { 0x7A, 0x7D, 0x0030, 4 }, /* for field == 0x0020 (fls == 6) */ + { 0x7A, 0x7D, 0x00C0, 6 }, /* for field == 0x0040 (fls == 7) */ + { 0x7A, 0x7D, 0x0100, 8 }, /* for field == 0x0080 (fls == 8) */ + { 0x7A, 0x7D, 0x0200, 9 }, /* for field == 0x0100 (fls == 9) */ + { 0x78, 0x78, 0x0004, 2 }, /* for field == 0x0200 (fls == 10) */ + { 0x7B, 0x7E, 0x01FF, 0 }, /* for field == 0x0400 (fls == 11) */ + { 0x7C, 0x7F, 0x01FF, 0 }, /* for field == 0x0800 (fls == 12) */ + { 0x78, 0x78, 0x0100, 8 }, /* for field == 0x1000 (fls == 13) */ + { 0x78, 0x78, 0x0200, 9 }, /* for field == 0x2000 (fls == 14) */ + { 0x78, 0x78, 0xF000, 12 } /* for field == 0x4000 (fls == 15) */ +}; + +/* val_mask, val_shift, en_addr0, val_addr0, en_addr1, val_addr1 */ +const struct nphy_rf_control_override_rev3 tbl_rf_control_override_rev3[] = { + { 0x8000, 15, 0xE5, 0xF9, 0xE6, 0xFB }, /* field == 0x0001 (fls 1) */ + { 0x0001, 0, 0xE7, 0x7A, 0xEC, 0x7D }, /* field == 0x0002 (fls 2) */ + { 0x0002, 1, 0xE7, 0x7A, 0xEC, 0x7D }, /* field == 0x0004 (fls 3) */ + { 0x0004, 2, 0xE7, 0x7A, 0xEC, 0x7D }, /* field == 0x0008 (fls 4) */ + { 0x0010, 4, 0xE7, 0x7A, 0xEC, 0x7D }, /* field == 0x0010 (fls 5) */ + { 0x0020, 5, 0xE7, 0x7A, 0xEC, 0x7D }, /* field == 0x0020 (fls 6) */ + { 0x0040, 6, 0xE7, 0x7A, 0xEC, 0x7D }, /* field == 0x0040 (fls 7) */ + { 0x0080, 7, 0xE7, 0x7A, 0xEC, 0x7D }, /* field == 0x0080 (fls 8) */ + { 0x0100, 8, 0xE7, 0x7A, 0xEC, 0x7D }, /* field == 0x0100 (fls 9) */ + { 0x0007, 0, 0xE7, 0xF8, 0xEC, 0xFA }, /* field == 0x0200 (fls 10) */ + { 0x0070, 4, 0xE7, 0xF8, 0xEC, 0xFA }, /* field == 0x0400 (fls 11) */ + { 0xE000, 13, 0xE7, 0x7A, 0xEC, 0x7D }, /* field == 0x0800 (fls 12) */ + { 0xFFFF, 0, 0xE7, 0x7B, 0xEC, 0x7E }, /* field == 0x1000 (fls 13) */ + { 0xFFFF, 0, 0xE7, 0x7C, 0xEC, 0x7F }, /* field == 0x2000 (fls 14) */ + { 0x00C0, 6, 0xE7, 0xF9, 0xEC, 0xFB } /* field == 0x4000 (fls 15) */ +}; + +/* field, val_addr_core0, val_addr_core1, val_mask, val_shift */ +static const struct nphy_rf_control_override_rev7 + tbl_rf_control_override_rev7_over0[] = { + { 0x0004, 0x07A, 0x07D, 0x0002, 1 }, + { 0x0008, 0x07A, 0x07D, 0x0004, 2 }, + { 0x0010, 0x07A, 0x07D, 0x0010, 4 }, + { 0x0020, 0x07A, 0x07D, 0x0020, 5 }, + { 0x0040, 0x07A, 0x07D, 0x0040, 6 }, + { 0x0080, 0x0F8, 0x0FA, 0x0080, 7 }, + { 0x0400, 0x0F8, 0x0FA, 0x0070, 4 }, + { 0x0800, 0x07B, 0x07E, 0xFFFF, 0 }, + { 0x1000, 0x07C, 0x07F, 0xFFFF, 0 }, + { 0x6000, 0x348, 0x349, 0xFFFF, 0 }, + { 0x2000, 0x348, 0x349, 0x000F, 0 }, +}; + +/* field, val_addr_core0, val_addr_core1, val_mask, val_shift */ +static const struct nphy_rf_control_override_rev7 + tbl_rf_control_override_rev7_over1[] = { + { 0x0002, 0x340, 0x341, 0x0002, 1 }, + { 0x0008, 0x340, 0x341, 0x0008, 3 }, + { 0x0020, 0x340, 0x341, 0x0020, 5 }, + { 0x0010, 0x340, 0x341, 0x0010, 4 }, + { 0x0004, 0x340, 0x341, 0x0004, 2 }, + { 0x0080, 0x340, 0x341, 0x0700, 8 }, + { 0x0800, 0x340, 0x341, 0x4000, 14 }, + { 0x0400, 0x340, 0x341, 0x2000, 13 }, + { 0x0200, 0x340, 0x341, 0x0800, 12 }, + { 0x0100, 0x340, 0x341, 0x0100, 11 }, + { 0x0040, 0x340, 0x341, 0x0040, 6 }, + { 0x0001, 0x340, 0x341, 0x0001, 0 }, +}; + +/* field, val_addr_core0, val_addr_core1, val_mask, val_shift */ +static const struct nphy_rf_control_override_rev7 + tbl_rf_control_override_rev7_over2[] = { + { 0x0008, 0x344, 0x345, 0x0008, 3 }, + { 0x0002, 0x344, 0x345, 0x0002, 1 }, + { 0x0001, 0x344, 0x345, 0x0001, 0 }, + { 0x0004, 0x344, 0x345, 0x0004, 2 }, + { 0x0010, 0x344, 0x345, 0x0010, 4 }, +}; + +static struct nphy_gain_ctl_workaround_entry nphy_gain_ctl_wa_phy6_radio11_ghz2 = { + { 10, 14, 19, 27 }, + { -5, 6, 10, 15 }, + { 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA }, + { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, + 0x427E, + { 0x413F, 0x413F, 0x413F, 0x413F }, + 0x007E, 0x0066, 0x1074, + 0x18, 0x18, 0x18, + 0x01D0, 0x5, +}; +static struct nphy_gain_ctl_workaround_entry nphy_gain_ctl_workaround[2][4] = { + { /* 2GHz */ + { /* PHY rev 3 */ + { 7, 11, 16, 23 }, + { -5, 6, 10, 14 }, + { 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA }, + { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, + 0x627E, + { 0x613F, 0x613F, 0x613F, 0x613F }, + 0x107E, 0x0066, 0x0074, + 0x18, 0x18, 0x18, + 0x020D, 0x5, + }, + { /* PHY rev 4 */ + { 8, 12, 17, 25 }, + { -5, 6, 10, 14 }, + { 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA }, + { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, + 0x527E, + { 0x513F, 0x513F, 0x513F, 0x513F }, + 0x007E, 0x0066, 0x0074, + 0x18, 0x18, 0x18, + 0x01A1, 0x5, + }, + { /* PHY rev 5 */ + { 9, 13, 18, 26 }, + { -3, 7, 11, 16 }, + { 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA }, + { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, + 0x427E, /* invalid for external LNA! */ + { 0x413F, 0x413F, 0x413F, 0x413F }, /* invalid for external LNA! */ + 0x1076, 0x0066, 0x0000, /* low is invalid (the last one) */ + 0x18, 0x18, 0x18, + 0x01D0, 0x9, + }, + { /* PHY rev 6+ */ + { 8, 13, 18, 25 }, + { -5, 6, 10, 14 }, + { 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA }, + { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, + 0x527E, /* invalid for external LNA! */ + { 0x513F, 0x513F, 0x513F, 0x513F }, /* invalid for external LNA! */ + 0x1076, 0x0066, 0x0000, /* low is invalid (the last one) */ + 0x18, 0x18, 0x18, + 0x01D0, 0x5, + }, + }, + { /* 5GHz */ + { /* PHY rev 3 */ + { 7, 11, 17, 23 }, + { -6, 2, 6, 10 }, + { 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13 }, + { 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 }, + 0x52DE, + { 0x516F, 0x516F, 0x516F, 0x516F }, + 0x00DE, 0x00CA, 0x00CC, + 0x1E, 0x1E, 0x1E, + 0x01A1, 25, + }, + { /* PHY rev 4 */ + { 8, 12, 18, 23 }, + { -5, 2, 6, 10 }, + { 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD }, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 }, + 0x629E, + { 0x614F, 0x614F, 0x614F, 0x614F }, + 0x029E, 0x1084, 0x0086, + 0x24, 0x24, 0x24, + 0x0107, 25, + }, + { /* PHY rev 5 */ + { 6, 10, 16, 21 }, + { -7, 0, 4, 8 }, + { 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD }, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 }, + 0x729E, + { 0x714F, 0x714F, 0x714F, 0x714F }, + 0x029E, 0x2084, 0x2086, + 0x24, 0x24, 0x24, + 0x00A9, 25, + }, + { /* PHY rev 6+ */ + { 6, 10, 16, 21 }, + { -7, 0, 4, 8 }, + { 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD }, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 }, + 0x729E, + { 0x714F, 0x714F, 0x714F, 0x714F }, + 0x029E, 0x2084, 0x2086, + 0x24, 0x24, 0x24, /* low is invalid for radio rev 11! */ + 0x00F0, 25, + }, + }, +}; + +static inline void assert_ntab_array_sizes(void) +{ +#undef check +#define check(table, size) \ + BUILD_BUG_ON(ARRAY_SIZE(b43_ntab_##table) != B43_NTAB_##size##_SIZE) + + check(adjustpower0, C0_ADJPLT); + check(adjustpower1, C1_ADJPLT); + check(bdi, BDI); + check(channelest, CHANEST); + check(estimatepowerlt0, C0_ESTPLT); + check(estimatepowerlt1, C1_ESTPLT); + check(framelookup, FRAMELT); + check(framestruct, FRAMESTRUCT); + check(gainctl0, C0_GAINCTL); + check(gainctl1, C1_GAINCTL); + check(intlevel, INTLEVEL); + check(iqlt0, C0_IQLT); + check(iqlt1, C1_IQLT); + check(loftlt0, C0_LOFEEDTH); + check(loftlt1, C1_LOFEEDTH); + check(mcs, MCS); + check(noisevar10, NOISEVAR10); + check(noisevar11, NOISEVAR11); + check(pilot, PILOT); + check(pilotlt, PILOTLT); + check(tdi20a0, TDI20A0); + check(tdi20a1, TDI20A1); + check(tdi40a0, TDI40A0); + check(tdi40a1, TDI40A1); + check(tdtrn, TDTRN); + check(tmap, TMAP); + +#undef check +} + +u32 b43_ntab_read(struct b43_wldev *dev, u32 offset) +{ + u32 type, value; + + type = offset & B43_NTAB_TYPEMASK; + offset &= ~B43_NTAB_TYPEMASK; + B43_WARN_ON(offset > 0xFFFF); + + switch (type) { + case B43_NTAB_8BIT: + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, offset); + value = b43_phy_read(dev, B43_NPHY_TABLE_DATALO) & 0xFF; + break; + case B43_NTAB_16BIT: + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, offset); + value = b43_phy_read(dev, B43_NPHY_TABLE_DATALO); + break; + case B43_NTAB_32BIT: + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, offset); + value = b43_phy_read(dev, B43_NPHY_TABLE_DATALO); + value |= b43_phy_read(dev, B43_NPHY_TABLE_DATAHI) << 16; + break; + default: + B43_WARN_ON(1); + value = 0; + } + + return value; +} + +void b43_ntab_read_bulk(struct b43_wldev *dev, u32 offset, + unsigned int nr_elements, void *_data) +{ + u32 type; + u8 *data = _data; + unsigned int i; + + type = offset & B43_NTAB_TYPEMASK; + offset &= ~B43_NTAB_TYPEMASK; + B43_WARN_ON(offset > 0xFFFF); + + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, offset); + + for (i = 0; i < nr_elements; i++) { + /* Auto increment broken + caching issue on BCM43224? */ + if (dev->dev->chip_id == 43224 && dev->dev->chip_rev == 1) { + b43_phy_read(dev, B43_NPHY_TABLE_DATALO); + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, offset + i); + } + + switch (type) { + case B43_NTAB_8BIT: + *data = b43_phy_read(dev, B43_NPHY_TABLE_DATALO) & 0xFF; + data++; + break; + case B43_NTAB_16BIT: + *((u16 *)data) = b43_phy_read(dev, B43_NPHY_TABLE_DATALO); + data += 2; + break; + case B43_NTAB_32BIT: + *((u32 *)data) = + b43_phy_read(dev, B43_NPHY_TABLE_DATALO); + *((u32 *)data) |= + b43_phy_read(dev, B43_NPHY_TABLE_DATAHI) << 16; + data += 4; + break; + default: + B43_WARN_ON(1); + } + } +} + +void b43_ntab_write(struct b43_wldev *dev, u32 offset, u32 value) +{ + u32 type; + + type = offset & B43_NTAB_TYPEMASK; + offset &= 0xFFFF; + + switch (type) { + case B43_NTAB_8BIT: + B43_WARN_ON(value & ~0xFF); + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, offset); + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, value); + break; + case B43_NTAB_16BIT: + B43_WARN_ON(value & ~0xFFFF); + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, offset); + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, value); + break; + case B43_NTAB_32BIT: + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, offset); + b43_phy_write(dev, B43_NPHY_TABLE_DATAHI, value >> 16); + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, value & 0xFFFF); + break; + default: + B43_WARN_ON(1); + } + + return; + + /* Some compiletime assertions... */ + assert_ntab_array_sizes(); +} + +void b43_ntab_write_bulk(struct b43_wldev *dev, u32 offset, + unsigned int nr_elements, const void *_data) +{ + u32 type, value; + const u8 *data = _data; + unsigned int i; + + type = offset & B43_NTAB_TYPEMASK; + offset &= ~B43_NTAB_TYPEMASK; + B43_WARN_ON(offset > 0xFFFF); + + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, offset); + + for (i = 0; i < nr_elements; i++) { + /* Auto increment broken + caching issue on BCM43224? */ + if ((offset >> 10) == 9 && dev->dev->chip_id == 43224 && + dev->dev->chip_rev == 1) { + b43_phy_read(dev, B43_NPHY_TABLE_DATALO); + b43_phy_write(dev, B43_NPHY_TABLE_ADDR, offset + i); + } + + switch (type) { + case B43_NTAB_8BIT: + value = *data; + data++; + B43_WARN_ON(value & ~0xFF); + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, value); + break; + case B43_NTAB_16BIT: + value = *((u16 *)data); + data += 2; + B43_WARN_ON(value & ~0xFFFF); + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, value); + break; + case B43_NTAB_32BIT: + value = *((u32 *)data); + data += 4; + b43_phy_write(dev, B43_NPHY_TABLE_DATAHI, value >> 16); + b43_phy_write(dev, B43_NPHY_TABLE_DATALO, + value & 0xFFFF); + break; + default: + B43_WARN_ON(1); + } + } +} + +#define ntab_upload(dev, offset, data) do { \ + b43_ntab_write_bulk(dev, offset, ARRAY_SIZE(data), data); \ + } while (0) + +static void b43_nphy_tables_init_rev3(struct b43_wldev *dev) +{ + struct ssb_sprom *sprom = dev->dev->bus_sprom; + u8 antswlut; + + if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) + antswlut = sprom->fem.ghz5.antswlut; + else + antswlut = sprom->fem.ghz2.antswlut; + + /* Static tables */ + ntab_upload(dev, B43_NTAB_FRAMESTRUCT_R3, b43_ntab_framestruct_r3); + ntab_upload(dev, B43_NTAB_PILOT_R3, b43_ntab_pilot_r3); + ntab_upload(dev, B43_NTAB_TMAP_R3, b43_ntab_tmap_r3); + ntab_upload(dev, B43_NTAB_INTLEVEL_R3, b43_ntab_intlevel_r3); + ntab_upload(dev, B43_NTAB_TDTRN_R3, b43_ntab_tdtrn_r3); + ntab_upload(dev, B43_NTAB_NOISEVAR0_R3, b43_ntab_noisevar0_r3); + ntab_upload(dev, B43_NTAB_NOISEVAR1_R3, b43_ntab_noisevar1_r3); + ntab_upload(dev, B43_NTAB_MCS_R3, b43_ntab_mcs_r3); + ntab_upload(dev, B43_NTAB_TDI20A0_R3, b43_ntab_tdi20a0_r3); + ntab_upload(dev, B43_NTAB_TDI20A1_R3, b43_ntab_tdi20a1_r3); + ntab_upload(dev, B43_NTAB_TDI40A0_R3, b43_ntab_tdi40a0_r3); + ntab_upload(dev, B43_NTAB_TDI40A1_R3, b43_ntab_tdi40a1_r3); + ntab_upload(dev, B43_NTAB_PILOTLT_R3, b43_ntab_pilotlt_r3); + ntab_upload(dev, B43_NTAB_CHANEST_R3, b43_ntab_channelest_r3); + ntab_upload(dev, B43_NTAB_FRAMELT_R3, b43_ntab_framelookup_r3); + ntab_upload(dev, B43_NTAB_C0_ESTPLT_R3, b43_ntab_estimatepowerlt0_r3); + ntab_upload(dev, B43_NTAB_C1_ESTPLT_R3, b43_ntab_estimatepowerlt1_r3); + ntab_upload(dev, B43_NTAB_C0_ADJPLT_R3, b43_ntab_adjustpower0_r3); + ntab_upload(dev, B43_NTAB_C1_ADJPLT_R3, b43_ntab_adjustpower1_r3); + ntab_upload(dev, B43_NTAB_C0_GAINCTL_R3, b43_ntab_gainctl0_r3); + ntab_upload(dev, B43_NTAB_C1_GAINCTL_R3, b43_ntab_gainctl1_r3); + ntab_upload(dev, B43_NTAB_C0_IQLT_R3, b43_ntab_iqlt0_r3); + ntab_upload(dev, B43_NTAB_C1_IQLT_R3, b43_ntab_iqlt1_r3); + ntab_upload(dev, B43_NTAB_C0_LOFEEDTH_R3, b43_ntab_loftlt0_r3); + ntab_upload(dev, B43_NTAB_C1_LOFEEDTH_R3, b43_ntab_loftlt1_r3); + + /* Volatile tables */ + if (antswlut < ARRAY_SIZE(b43_ntab_antswctl_r3)) + ntab_upload(dev, B43_NTAB_ANT_SW_CTL_R3, + b43_ntab_antswctl_r3[antswlut]); + else + B43_WARN_ON(1); +} + +static void b43_nphy_tables_init_rev0(struct b43_wldev *dev) +{ + /* Static tables */ + ntab_upload(dev, B43_NTAB_FRAMESTRUCT, b43_ntab_framestruct); + ntab_upload(dev, B43_NTAB_FRAMELT, b43_ntab_framelookup); + ntab_upload(dev, B43_NTAB_TMAP, b43_ntab_tmap); + ntab_upload(dev, B43_NTAB_TDTRN, b43_ntab_tdtrn); + ntab_upload(dev, B43_NTAB_INTLEVEL, b43_ntab_intlevel); + ntab_upload(dev, B43_NTAB_PILOT, b43_ntab_pilot); + ntab_upload(dev, B43_NTAB_TDI20A0, b43_ntab_tdi20a0); + ntab_upload(dev, B43_NTAB_TDI20A1, b43_ntab_tdi20a1); + ntab_upload(dev, B43_NTAB_TDI40A0, b43_ntab_tdi40a0); + ntab_upload(dev, B43_NTAB_TDI40A1, b43_ntab_tdi40a1); + ntab_upload(dev, B43_NTAB_CHANEST, b43_ntab_channelest); + ntab_upload(dev, B43_NTAB_MCS, b43_ntab_mcs); + ntab_upload(dev, B43_NTAB_NOISEVAR10, b43_ntab_noisevar10); + ntab_upload(dev, B43_NTAB_NOISEVAR11, b43_ntab_noisevar11); + + /* Volatile tables */ + ntab_upload(dev, B43_NTAB_BDI, b43_ntab_bdi); + ntab_upload(dev, B43_NTAB_PILOTLT, b43_ntab_pilotlt); + ntab_upload(dev, B43_NTAB_C0_GAINCTL, b43_ntab_gainctl0); + ntab_upload(dev, B43_NTAB_C1_GAINCTL, b43_ntab_gainctl1); + ntab_upload(dev, B43_NTAB_C0_ESTPLT, b43_ntab_estimatepowerlt0); + ntab_upload(dev, B43_NTAB_C1_ESTPLT, b43_ntab_estimatepowerlt1); + ntab_upload(dev, B43_NTAB_C0_ADJPLT, b43_ntab_adjustpower0); + ntab_upload(dev, B43_NTAB_C1_ADJPLT, b43_ntab_adjustpower1); + ntab_upload(dev, B43_NTAB_C0_IQLT, b43_ntab_iqlt0); + ntab_upload(dev, B43_NTAB_C1_IQLT, b43_ntab_iqlt1); + ntab_upload(dev, B43_NTAB_C0_LOFEEDTH, b43_ntab_loftlt0); + ntab_upload(dev, B43_NTAB_C1_LOFEEDTH, b43_ntab_loftlt1); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/InitTables */ +void b43_nphy_tables_init(struct b43_wldev *dev) +{ + if (dev->phy.rev >= 3) + b43_nphy_tables_init_rev3(dev); + else + b43_nphy_tables_init_rev0(dev); +} + +/* http://bcm-v4.sipsolutions.net/802.11/PHY/N/GetIpaGainTbl */ +static const u32 *b43_nphy_get_ipa_gain_table(struct b43_wldev *dev) +{ + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + if (dev->phy.rev >= 6) { + if (dev->dev->chip_id == 47162) + return txpwrctrl_tx_gain_ipa_rev5; + return txpwrctrl_tx_gain_ipa_rev6; + } else if (dev->phy.rev >= 5) { + return txpwrctrl_tx_gain_ipa_rev5; + } else { + return txpwrctrl_tx_gain_ipa; + } + } else { + return txpwrctrl_tx_gain_ipa_5g; + } +} + +const u32 *b43_nphy_get_tx_gain_table(struct b43_wldev *dev) +{ + enum ieee80211_band band = b43_current_band(dev->wl); + struct ssb_sprom *sprom = dev->dev->bus_sprom; + + if (dev->phy.rev < 3) + return b43_ntab_tx_gain_rev0_1_2; + + /* rev 3+ */ + if ((dev->phy.n->ipa2g_on && band == IEEE80211_BAND_2GHZ) || + (dev->phy.n->ipa5g_on && band == IEEE80211_BAND_5GHZ)) { + return b43_nphy_get_ipa_gain_table(dev); + } else if (b43_current_band(dev->wl) == IEEE80211_BAND_5GHZ) { + if (dev->phy.rev == 3) + return b43_ntab_tx_gain_rev3_5ghz; + if (dev->phy.rev == 4) + return sprom->fem.ghz5.extpa_gain == 3 ? + b43_ntab_tx_gain_rev4_5ghz : + b43_ntab_tx_gain_rev4_5ghz; /* FIXME */ + else + return b43_ntab_tx_gain_rev5plus_5ghz; + } else { + if (dev->phy.rev >= 5 && sprom->fem.ghz5.extpa_gain == 3) + return b43_ntab_tx_gain_rev3plus_2ghz; /* FIXME */ + else + return b43_ntab_tx_gain_rev3plus_2ghz; + } +} + +struct nphy_gain_ctl_workaround_entry *b43_nphy_get_gain_ctl_workaround_ent( + struct b43_wldev *dev, bool ghz5, bool ext_lna) +{ + struct nphy_gain_ctl_workaround_entry *e; + u8 phy_idx; + + if (!ghz5 && dev->phy.rev >= 6 && dev->phy.radio_rev == 11) + return &nphy_gain_ctl_wa_phy6_radio11_ghz2; + + B43_WARN_ON(dev->phy.rev < 3); + if (dev->phy.rev >= 6) + phy_idx = 3; + else if (dev->phy.rev == 5) + phy_idx = 2; + else if (dev->phy.rev == 4) + phy_idx = 1; + else + phy_idx = 0; + e = &nphy_gain_ctl_workaround[ghz5][phy_idx]; + + /* Some workarounds to the workarounds... */ + if (ghz5 && dev->phy.rev >= 6) { + if (dev->phy.radio_rev == 11 && + !b43_channel_type_is_40mhz(dev->phy.channel_type)) + e->cliplo_gain = 0x2d; + } else if (!ghz5 && dev->phy.rev >= 5) { + static const int gain_data[] = {0x0062, 0x0064, 0x006a, 0x106a, + 0x106c, 0x1074, 0x107c, 0x207c}; + u8 tr_iso = dev->dev->bus_sprom->fem.ghz2.tr_iso; + + if (ext_lna) { + e->rfseq_init[0] &= ~0x4000; + e->rfseq_init[1] &= ~0x4000; + e->rfseq_init[2] &= ~0x4000; + e->rfseq_init[3] &= ~0x4000; + e->init_gain &= ~0x4000; + } + if (tr_iso > 7) + tr_iso = 3; + e->cliplo_gain = gain_data[tr_iso]; + + } else if (ghz5 && dev->phy.rev == 4 && ext_lna) { + e->rfseq_init[0] &= ~0x4000; + e->rfseq_init[1] &= ~0x4000; + e->rfseq_init[2] &= ~0x4000; + e->rfseq_init[3] &= ~0x4000; + e->init_gain &= ~0x4000; + e->rfseq_init[0] |= 0x1000; + e->rfseq_init[1] |= 0x1000; + e->rfseq_init[2] |= 0x1000; + e->rfseq_init[3] |= 0x1000; + e->init_gain |= 0x1000; + } + + return e; +} + +const struct nphy_rf_control_override_rev7 *b43_nphy_get_rf_ctl_over_rev7( + struct b43_wldev *dev, u16 field, u8 override) +{ + const struct nphy_rf_control_override_rev7 *e; + u8 size, i; + + switch (override) { + case 0: + e = tbl_rf_control_override_rev7_over0; + size = ARRAY_SIZE(tbl_rf_control_override_rev7_over0); + break; + case 1: + e = tbl_rf_control_override_rev7_over1; + size = ARRAY_SIZE(tbl_rf_control_override_rev7_over1); + break; + case 2: + e = tbl_rf_control_override_rev7_over2; + size = ARRAY_SIZE(tbl_rf_control_override_rev7_over2); + break; + default: + b43err(dev->wl, "Invalid override value %d\n", override); + return NULL; + } + + for (i = 0; i < size; i++) { + if (e[i].field == field) + return &e[i]; + } + + return NULL; +} diff --git a/drivers/net/wireless/b43/tables_nphy.h b/drivers/net/wireless/b43/tables_nphy.h new file mode 100644 index 0000000..9ff33ad --- /dev/null +++ b/drivers/net/wireless/b43/tables_nphy.h @@ -0,0 +1,217 @@ +#ifndef B43_TABLES_NPHY_H_ +#define B43_TABLES_NPHY_H_ + +#include + +struct b43_phy_n_sfo_cfg { + u16 phy_bw1a; + u16 phy_bw2; + u16 phy_bw3; + u16 phy_bw4; + u16 phy_bw5; + u16 phy_bw6; +}; + +struct b43_wldev; + +struct nphy_txiqcal_ladder { + u8 percent; + u8 g_env; +}; + +struct nphy_rf_control_override_rev2 { + u8 addr0; + u8 addr1; + u16 bmask; + u8 shift; +}; + +struct nphy_rf_control_override_rev3 { + u16 val_mask; + u8 val_shift; + u8 en_addr0; + u8 val_addr0; + u8 en_addr1; + u8 val_addr1; +}; + +struct nphy_rf_control_override_rev7 { + u16 field; + u16 val_addr_core0; + u16 val_addr_core1; + u16 val_mask; + u8 val_shift; +}; + +struct nphy_gain_ctl_workaround_entry { + s8 lna1_gain[4]; + s8 lna2_gain[4]; + u8 gain_db[10]; + u8 gain_bits[10]; + + u16 init_gain; + u16 rfseq_init[4]; + + u16 cliphi_gain; + u16 clipmd_gain; + u16 cliplo_gain; + + u16 crsmin; + u16 crsminl; + u16 crsminu; + + u16 nbclip; + u16 wlclip; +}; + +/* Get entry with workaround values for gain ctl. Does not return NULL. */ +struct nphy_gain_ctl_workaround_entry *b43_nphy_get_gain_ctl_workaround_ent( + struct b43_wldev *dev, bool ghz5, bool ext_lna); + + +/* The N-PHY tables. */ +#define B43_NTAB_TYPEMASK 0xF0000000 +#define B43_NTAB_8BIT 0x10000000 +#define B43_NTAB_16BIT 0x20000000 +#define B43_NTAB_32BIT 0x30000000 +#define B43_NTAB8(table, offset) (((table) << 10) | (offset) | B43_NTAB_8BIT) +#define B43_NTAB16(table, offset) (((table) << 10) | (offset) | B43_NTAB_16BIT) +#define B43_NTAB32(table, offset) (((table) << 10) | (offset) | B43_NTAB_32BIT) + +/* Static N-PHY tables */ +#define B43_NTAB_FRAMESTRUCT B43_NTAB32(0x0A, 0x000) /* Frame Struct Table */ +#define B43_NTAB_FRAMESTRUCT_SIZE 832 +#define B43_NTAB_FRAMELT B43_NTAB8 (0x18, 0x000) /* Frame Lookup Table */ +#define B43_NTAB_FRAMELT_SIZE 32 +#define B43_NTAB_TMAP B43_NTAB32(0x0C, 0x000) /* T Map Table */ +#define B43_NTAB_TMAP_SIZE 448 +#define B43_NTAB_TDTRN B43_NTAB32(0x0E, 0x000) /* TDTRN Table */ +#define B43_NTAB_TDTRN_SIZE 704 +#define B43_NTAB_INTLEVEL B43_NTAB32(0x0D, 0x000) /* Int Level Table */ +#define B43_NTAB_INTLEVEL_SIZE 7 +#define B43_NTAB_PILOT B43_NTAB16(0x0B, 0x000) /* Pilot Table */ +#define B43_NTAB_PILOT_SIZE 88 +#define B43_NTAB_PILOTLT B43_NTAB32(0x14, 0x000) /* Pilot Lookup Table */ +#define B43_NTAB_PILOTLT_SIZE 6 +#define B43_NTAB_TDI20A0 B43_NTAB32(0x13, 0x080) /* TDI Table 20 Antenna 0 */ +#define B43_NTAB_TDI20A0_SIZE 55 +#define B43_NTAB_TDI20A1 B43_NTAB32(0x13, 0x100) /* TDI Table 20 Antenna 1 */ +#define B43_NTAB_TDI20A1_SIZE 55 +#define B43_NTAB_TDI40A0 B43_NTAB32(0x13, 0x280) /* TDI Table 40 Antenna 0 */ +#define B43_NTAB_TDI40A0_SIZE 110 +#define B43_NTAB_TDI40A1 B43_NTAB32(0x13, 0x300) /* TDI Table 40 Antenna 1 */ +#define B43_NTAB_TDI40A1_SIZE 110 +#define B43_NTAB_BDI B43_NTAB16(0x15, 0x000) /* BDI Table */ +#define B43_NTAB_BDI_SIZE 6 +#define B43_NTAB_CHANEST B43_NTAB32(0x16, 0x000) /* Channel Estimate Table */ +#define B43_NTAB_CHANEST_SIZE 96 +#define B43_NTAB_MCS B43_NTAB8 (0x12, 0x000) /* MCS Table */ +#define B43_NTAB_MCS_SIZE 128 + +/* Volatile N-PHY tables */ +#define B43_NTAB_NOISEVAR10 B43_NTAB32(0x10, 0x000) /* Noise Var Table 10 */ +#define B43_NTAB_NOISEVAR10_SIZE 256 +#define B43_NTAB_NOISEVAR11 B43_NTAB32(0x10, 0x080) /* Noise Var Table 11 */ +#define B43_NTAB_NOISEVAR11_SIZE 256 +#define B43_NTAB_C0_ESTPLT B43_NTAB8 (0x1A, 0x000) /* Estimate Power Lookup Table Core 0 */ +#define B43_NTAB_C0_ESTPLT_SIZE 64 +#define B43_NTAB_C0_ADJPLT B43_NTAB8 (0x1A, 0x040) /* Adjust Power Lookup Table Core 0 */ +#define B43_NTAB_C0_ADJPLT_SIZE 128 +#define B43_NTAB_C0_GAINCTL B43_NTAB32(0x1A, 0x0C0) /* Gain Control Lookup Table Core 0 */ +#define B43_NTAB_C0_GAINCTL_SIZE 128 +#define B43_NTAB_C0_IQLT B43_NTAB32(0x1A, 0x140) /* IQ Lookup Table Core 0 */ +#define B43_NTAB_C0_IQLT_SIZE 128 +#define B43_NTAB_C0_LOFEEDTH B43_NTAB16(0x1A, 0x1C0) /* Local Oscillator Feed Through Lookup Table Core 0 */ +#define B43_NTAB_C0_LOFEEDTH_SIZE 128 +#define B43_NTAB_C1_ESTPLT B43_NTAB8 (0x1B, 0x000) /* Estimate Power Lookup Table Core 1 */ +#define B43_NTAB_C1_ESTPLT_SIZE 64 +#define B43_NTAB_C1_ADJPLT B43_NTAB8 (0x1B, 0x040) /* Adjust Power Lookup Table Core 1 */ +#define B43_NTAB_C1_ADJPLT_SIZE 128 +#define B43_NTAB_C1_GAINCTL B43_NTAB32(0x1B, 0x0C0) /* Gain Control Lookup Table Core 1 */ +#define B43_NTAB_C1_GAINCTL_SIZE 128 +#define B43_NTAB_C1_IQLT B43_NTAB32(0x1B, 0x140) /* IQ Lookup Table Core 1 */ +#define B43_NTAB_C1_IQLT_SIZE 128 +#define B43_NTAB_C1_LOFEEDTH B43_NTAB16(0x1B, 0x1C0) /* Local Oscillator Feed Through Lookup Table Core 1 */ +#define B43_NTAB_C1_LOFEEDTH_SIZE 128 + +/* Volatile N-PHY tables, PHY revision >= 3 */ +#define B43_NTAB_ANT_SW_CTL_R3 B43_NTAB16( 9, 0) /* antenna software control */ + +/* Static N-PHY tables, PHY revision >= 3 */ +#define B43_NTAB_FRAMESTRUCT_R3 B43_NTAB32(10, 0) /* frame struct */ +#define B43_NTAB_PILOT_R3 B43_NTAB16(11, 0) /* pilot */ +#define B43_NTAB_TMAP_R3 B43_NTAB32(12, 0) /* TM AP */ +#define B43_NTAB_INTLEVEL_R3 B43_NTAB32(13, 0) /* INT LV */ +#define B43_NTAB_TDTRN_R3 B43_NTAB32(14, 0) /* TD TRN */ +#define B43_NTAB_NOISEVAR0_R3 B43_NTAB32(16, 0) /* noise variance 0 */ +#define B43_NTAB_NOISEVAR1_R3 B43_NTAB32(16, 128) /* noise variance 1 */ +#define B43_NTAB_MCS_R3 B43_NTAB16(18, 0) /* MCS */ +#define B43_NTAB_TDI20A0_R3 B43_NTAB32(19, 128) /* TDI 20/0 */ +#define B43_NTAB_TDI20A1_R3 B43_NTAB32(19, 256) /* TDI 20/1 */ +#define B43_NTAB_TDI40A0_R3 B43_NTAB32(19, 640) /* TDI 40/0 */ +#define B43_NTAB_TDI40A1_R3 B43_NTAB32(19, 768) /* TDI 40/1 */ +#define B43_NTAB_PILOTLT_R3 B43_NTAB32(20, 0) /* PLT lookup */ +#define B43_NTAB_CHANEST_R3 B43_NTAB32(22, 0) /* channel estimate */ +#define B43_NTAB_FRAMELT_R3 B43_NTAB8(24, 0) /* frame lookup */ +#define B43_NTAB_C0_ESTPLT_R3 B43_NTAB8(26, 0) /* estimated power lookup 0 */ +#define B43_NTAB_C0_ADJPLT_R3 B43_NTAB8(26, 64) /* adjusted power lookup 0 */ +#define B43_NTAB_C0_GAINCTL_R3 B43_NTAB32(26, 192) /* gain control lookup 0 */ +#define B43_NTAB_C0_IQLT_R3 B43_NTAB32(26, 320) /* I/Q lookup 0 */ +#define B43_NTAB_C0_LOFEEDTH_R3 B43_NTAB16(26, 448) /* Local Oscillator Feed Through lookup 0 */ +#define B43_NTAB_C0_PAPD_COMP_R3 B43_NTAB16(26, 576) +#define B43_NTAB_C1_ESTPLT_R3 B43_NTAB8(27, 0) /* estimated power lookup 1 */ +#define B43_NTAB_C1_ADJPLT_R3 B43_NTAB8(27, 64) /* adjusted power lookup 1 */ +#define B43_NTAB_C1_GAINCTL_R3 B43_NTAB32(27, 192) /* gain control lookup 1 */ +#define B43_NTAB_C1_IQLT_R3 B43_NTAB32(27, 320) /* I/Q lookup 1 */ +#define B43_NTAB_C1_LOFEEDTH_R3 B43_NTAB16(27, 448) /* Local Oscillator Feed Through lookup 1 */ +#define B43_NTAB_C1_PAPD_COMP_R3 B43_NTAB16(27, 576) + +#define B43_NTAB_TX_IQLO_CAL_LOFT_LADDER_40_SIZE 18 +#define B43_NTAB_TX_IQLO_CAL_LOFT_LADDER_20_SIZE 18 +#define B43_NTAB_TX_IQLO_CAL_IQIMB_LADDER_40_SIZE 18 +#define B43_NTAB_TX_IQLO_CAL_IQIMB_LADDER_20_SIZE 18 +#define B43_NTAB_TX_IQLO_CAL_STARTCOEFS_REV3 11 +#define B43_NTAB_TX_IQLO_CAL_STARTCOEFS 9 +#define B43_NTAB_TX_IQLO_CAL_CMDS_RECAL_REV3 12 +#define B43_NTAB_TX_IQLO_CAL_CMDS_RECAL 10 +#define B43_NTAB_TX_IQLO_CAL_CMDS_FULLCAL 10 +#define B43_NTAB_TX_IQLO_CAL_CMDS_FULLCAL_REV3 12 + +u32 b43_ntab_read(struct b43_wldev *dev, u32 offset); +void b43_ntab_read_bulk(struct b43_wldev *dev, u32 offset, + unsigned int nr_elements, void *_data); +void b43_ntab_write(struct b43_wldev *dev, u32 offset, u32 value); +void b43_ntab_write_bulk(struct b43_wldev *dev, u32 offset, + unsigned int nr_elements, const void *_data); + +void b43_nphy_tables_init(struct b43_wldev *dev); + +const u32 *b43_nphy_get_tx_gain_table(struct b43_wldev *dev); + +extern const s8 b43_ntab_papd_pga_gain_delta_ipa_2g[]; + +extern const u16 tbl_iqcal_gainparams[2][9][8]; +extern const struct nphy_txiqcal_ladder ladder_lo[]; +extern const struct nphy_txiqcal_ladder ladder_iq[]; +extern const u16 loscale[]; + +extern const u16 tbl_tx_iqlo_cal_loft_ladder_40[]; +extern const u16 tbl_tx_iqlo_cal_loft_ladder_20[]; +extern const u16 tbl_tx_iqlo_cal_iqimb_ladder_40[]; +extern const u16 tbl_tx_iqlo_cal_iqimb_ladder_20[]; +extern const u16 tbl_tx_iqlo_cal_startcoefs_nphyrev3[]; +extern const u16 tbl_tx_iqlo_cal_startcoefs[]; +extern const u16 tbl_tx_iqlo_cal_cmds_recal_nphyrev3[]; +extern const u16 tbl_tx_iqlo_cal_cmds_recal[]; +extern const u16 tbl_tx_iqlo_cal_cmds_fullcal[]; +extern const u16 tbl_tx_iqlo_cal_cmds_fullcal_nphyrev3[]; +extern const s16 tbl_tx_filter_coef_rev4[7][15]; + +extern const struct nphy_rf_control_override_rev2 + tbl_rf_control_override_rev2[]; +extern const struct nphy_rf_control_override_rev3 + tbl_rf_control_override_rev3[]; +const struct nphy_rf_control_override_rev7 *b43_nphy_get_rf_ctl_over_rev7( + struct b43_wldev *dev, u16 field, u8 override); + +#endif /* B43_TABLES_NPHY_H_ */ diff --git a/drivers/net/wireless/b43/tables_phy_ht.c b/drivers/net/wireless/b43/tables_phy_ht.c new file mode 100644 index 0000000..176c49d --- /dev/null +++ b/drivers/net/wireless/b43/tables_phy_ht.c @@ -0,0 +1,836 @@ +/* + + Broadcom B43 wireless driver + IEEE 802.11n HT-PHY data tables + + Copyright (c) 2011 Rafał Miłecki + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" +#include "tables_phy_ht.h" +#include "phy_common.h" +#include "phy_ht.h" + +static const u16 b43_httab_0x12[] = { + 0x0000, 0x0008, 0x000a, 0x0010, 0x0012, 0x0019, + 0x001a, 0x001c, 0x0080, 0x0088, 0x008a, 0x0090, + 0x0092, 0x0099, 0x009a, 0x009c, 0x0100, 0x0108, + 0x010a, 0x0110, 0x0112, 0x0119, 0x011a, 0x011c, + 0x0180, 0x0188, 0x018a, 0x0190, 0x0192, 0x0199, + 0x019a, 0x019c, 0x0000, 0x0098, 0x00a0, 0x00a8, + 0x009a, 0x00a2, 0x00aa, 0x0120, 0x0128, 0x0128, + 0x0130, 0x0138, 0x0138, 0x0140, 0x0122, 0x012a, + 0x012a, 0x0132, 0x013a, 0x013a, 0x0142, 0x01a8, + 0x01b0, 0x01b8, 0x01b0, 0x01b8, 0x01c0, 0x01c8, + 0x01c0, 0x01c8, 0x01d0, 0x01d0, 0x01d8, 0x01aa, + 0x01b2, 0x01ba, 0x01b2, 0x01ba, 0x01c2, 0x01ca, + 0x01c2, 0x01ca, 0x01d2, 0x01d2, 0x01da, 0x0001, + 0x0002, 0x0004, 0x0009, 0x000c, 0x0011, 0x0014, + 0x0018, 0x0020, 0x0021, 0x0022, 0x0024, 0x0081, + 0x0082, 0x0084, 0x0089, 0x008c, 0x0091, 0x0094, + 0x0098, 0x00a0, 0x00a1, 0x00a2, 0x00a4, 0x0007, + 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, + 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, + 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, + 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, 0x0007, + 0x0007, 0x0007, +}; + +static const u16 b43_httab_0x27[] = { + 0x0009, 0x000e, 0x0011, 0x0014, 0x0017, 0x001a, + 0x001d, 0x0020, 0x0009, 0x000e, 0x0011, 0x0014, + 0x0017, 0x001a, 0x001d, 0x0020, 0x0009, 0x000e, + 0x0011, 0x0014, 0x0017, 0x001a, 0x001d, 0x0020, + 0x0009, 0x000e, 0x0011, 0x0014, 0x0017, 0x001a, + 0x001d, 0x0020, +}; + +static const u16 b43_httab_0x26[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, +}; + +static const u32 b43_httab_0x25[] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, +}; + +static const u32 b43_httab_0x2f[] = { + 0x00035700, 0x0002cc9a, 0x00026666, 0x0001581f, + 0x0001581f, 0x0001581f, 0x0001581f, 0x0001581f, + 0x0001581f, 0x0001581f, 0x0001581f, 0x00035700, + 0x0002cc9a, 0x00026666, 0x0001581f, 0x0001581f, + 0x0001581f, 0x0001581f, 0x0001581f, 0x0001581f, + 0x0001581f, 0x0001581f, +}; + +static const u16 b43_httab_0x1a[] = { + 0x0055, 0x0054, 0x0054, 0x0053, 0x0052, 0x0052, + 0x0051, 0x0051, 0x0050, 0x004f, 0x004f, 0x004e, + 0x004e, 0x004d, 0x004c, 0x004c, 0x004b, 0x004a, + 0x0049, 0x0049, 0x0048, 0x0047, 0x0046, 0x0046, + 0x0045, 0x0044, 0x0043, 0x0042, 0x0041, 0x0040, + 0x0040, 0x003f, 0x003e, 0x003d, 0x003c, 0x003a, + 0x0039, 0x0038, 0x0037, 0x0036, 0x0035, 0x0033, + 0x0032, 0x0031, 0x002f, 0x002e, 0x002c, 0x002b, + 0x0029, 0x0027, 0x0025, 0x0023, 0x0021, 0x001f, + 0x001d, 0x001a, 0x0018, 0x0015, 0x0012, 0x000e, + 0x000b, 0x0007, 0x0002, 0x00fd, +}; + +static const u16 b43_httab_0x1b[] = { + 0x0055, 0x0054, 0x0054, 0x0053, 0x0052, 0x0052, + 0x0051, 0x0051, 0x0050, 0x004f, 0x004f, 0x004e, + 0x004e, 0x004d, 0x004c, 0x004c, 0x004b, 0x004a, + 0x0049, 0x0049, 0x0048, 0x0047, 0x0046, 0x0046, + 0x0045, 0x0044, 0x0043, 0x0042, 0x0041, 0x0040, + 0x0040, 0x003f, 0x003e, 0x003d, 0x003c, 0x003a, + 0x0039, 0x0038, 0x0037, 0x0036, 0x0035, 0x0033, + 0x0032, 0x0031, 0x002f, 0x002e, 0x002c, 0x002b, + 0x0029, 0x0027, 0x0025, 0x0023, 0x0021, 0x001f, + 0x001d, 0x001a, 0x0018, 0x0015, 0x0012, 0x000e, + 0x000b, 0x0007, 0x0002, 0x00fd, +}; + +static const u16 b43_httab_0x1c[] = { + 0x0055, 0x0054, 0x0054, 0x0053, 0x0052, 0x0052, + 0x0051, 0x0051, 0x0050, 0x004f, 0x004f, 0x004e, + 0x004e, 0x004d, 0x004c, 0x004c, 0x004b, 0x004a, + 0x0049, 0x0049, 0x0048, 0x0047, 0x0046, 0x0046, + 0x0045, 0x0044, 0x0043, 0x0042, 0x0041, 0x0040, + 0x0040, 0x003f, 0x003e, 0x003d, 0x003c, 0x003a, + 0x0039, 0x0038, 0x0037, 0x0036, 0x0035, 0x0033, + 0x0032, 0x0031, 0x002f, 0x002e, 0x002c, 0x002b, + 0x0029, 0x0027, 0x0025, 0x0023, 0x0021, 0x001f, + 0x001d, 0x001a, 0x0018, 0x0015, 0x0012, 0x000e, + 0x000b, 0x0007, 0x0002, 0x00fd, +}; + +static const u32 b43_httab_0x1a_0xc0[] = { + 0x5bf70044, 0x5bf70042, 0x5bf70040, 0x5bf7003e, + 0x5bf7003c, 0x5bf7003b, 0x5bf70039, 0x5bf70037, + 0x5bf70036, 0x5bf70034, 0x5bf70033, 0x5bf70031, + 0x5bf70030, 0x5ba70044, 0x5ba70042, 0x5ba70040, + 0x5ba7003e, 0x5ba7003c, 0x5ba7003b, 0x5ba70039, + 0x5ba70037, 0x5ba70036, 0x5ba70034, 0x5ba70033, + 0x5b770044, 0x5b770042, 0x5b770040, 0x5b77003e, + 0x5b77003c, 0x5b77003b, 0x5b770039, 0x5b770037, + 0x5b770036, 0x5b770034, 0x5b770033, 0x5b770031, + 0x5b770030, 0x5b77002f, 0x5b77002d, 0x5b77002c, + 0x5b470044, 0x5b470042, 0x5b470040, 0x5b47003e, + 0x5b47003c, 0x5b47003b, 0x5b470039, 0x5b470037, + 0x5b470036, 0x5b470034, 0x5b470033, 0x5b470031, + 0x5b470030, 0x5b47002f, 0x5b47002d, 0x5b47002c, + 0x5b47002b, 0x5b47002a, 0x5b270044, 0x5b270042, + 0x5b270040, 0x5b27003e, 0x5b27003c, 0x5b27003b, + 0x5b270039, 0x5b270037, 0x5b270036, 0x5b270034, + 0x5b270033, 0x5b270031, 0x5b270030, 0x5b27002f, + 0x5b170044, 0x5b170042, 0x5b170040, 0x5b17003e, + 0x5b17003c, 0x5b17003b, 0x5b170039, 0x5b170037, + 0x5b170036, 0x5b170034, 0x5b170033, 0x5b170031, + 0x5b170030, 0x5b17002f, 0x5b17002d, 0x5b17002c, + 0x5b17002b, 0x5b17002a, 0x5b170028, 0x5b170027, + 0x5b170026, 0x5b170025, 0x5b170024, 0x5b170023, + 0x5b070044, 0x5b070042, 0x5b070040, 0x5b07003e, + 0x5b07003c, 0x5b07003b, 0x5b070039, 0x5b070037, + 0x5b070036, 0x5b070034, 0x5b070033, 0x5b070031, + 0x5b070030, 0x5b07002f, 0x5b07002d, 0x5b07002c, + 0x5b07002b, 0x5b07002a, 0x5b070028, 0x5b070027, + 0x5b070026, 0x5b070025, 0x5b070024, 0x5b070023, + 0x5b070022, 0x5b070021, 0x5b070020, 0x5b07001f, + 0x5b07001e, 0x5b07001d, 0x5b07001d, 0x5b07001c, +}; + +static const u32 b43_httab_0x1a_0x140[] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, +}; + +static const u32 b43_httab_0x1b_0x140[] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, +}; + +static const u32 b43_httab_0x1c_0x140[] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, +}; + +static const u16 b43_httab_0x1a_0x1c0[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, +}; + +static const u16 b43_httab_0x1b_0x1c0[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, +}; + +static const u16 b43_httab_0x1c_0x1c0[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, +}; + +static const u16 b43_httab_0x1a_0x240[] = { + 0x0036, 0x0036, 0x0036, 0x0036, 0x0036, 0x0036, + 0x0036, 0x0036, 0x0036, 0x0036, 0x0036, 0x0036, + 0x0036, 0x002a, 0x002a, 0x002a, 0x002a, 0x002a, + 0x002a, 0x002a, 0x002a, 0x002a, 0x002a, 0x002a, + 0x001e, 0x001e, 0x001e, 0x001e, 0x001e, 0x001e, + 0x001e, 0x001e, 0x001e, 0x001e, 0x001e, 0x001e, + 0x001e, 0x001e, 0x001e, 0x001e, 0x000e, 0x000e, + 0x000e, 0x000e, 0x000e, 0x000e, 0x000e, 0x000e, + 0x000e, 0x000e, 0x000e, 0x000e, 0x000e, 0x000e, + 0x000e, 0x000e, 0x000e, 0x000e, 0x01fc, 0x01fc, + 0x01fc, 0x01fc, 0x01fc, 0x01fc, 0x01fc, 0x01fc, + 0x01fc, 0x01fc, 0x01fc, 0x01fc, 0x01fc, 0x01fc, + 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, + 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, + 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, + 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, + 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, + 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, + 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, + 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, + 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, + 0x01d6, 0x01d6, +}; + +static const u16 b43_httab_0x1b_0x240[] = { + 0x0036, 0x0036, 0x0036, 0x0036, 0x0036, 0x0036, + 0x0036, 0x0036, 0x0036, 0x0036, 0x0036, 0x0036, + 0x0036, 0x002a, 0x002a, 0x002a, 0x002a, 0x002a, + 0x002a, 0x002a, 0x002a, 0x002a, 0x002a, 0x002a, + 0x001e, 0x001e, 0x001e, 0x001e, 0x001e, 0x001e, + 0x001e, 0x001e, 0x001e, 0x001e, 0x001e, 0x001e, + 0x001e, 0x001e, 0x001e, 0x001e, 0x000e, 0x000e, + 0x000e, 0x000e, 0x000e, 0x000e, 0x000e, 0x000e, + 0x000e, 0x000e, 0x000e, 0x000e, 0x000e, 0x000e, + 0x000e, 0x000e, 0x000e, 0x000e, 0x01fc, 0x01fc, + 0x01fc, 0x01fc, 0x01fc, 0x01fc, 0x01fc, 0x01fc, + 0x01fc, 0x01fc, 0x01fc, 0x01fc, 0x01fc, 0x01fc, + 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, + 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, + 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, + 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, + 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, + 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, + 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, + 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, + 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, + 0x01d6, 0x01d6, +}; + +static const u16 b43_httab_0x1c_0x240[] = { + 0x0036, 0x0036, 0x0036, 0x0036, 0x0036, 0x0036, + 0x0036, 0x0036, 0x0036, 0x0036, 0x0036, 0x0036, + 0x0036, 0x002a, 0x002a, 0x002a, 0x002a, 0x002a, + 0x002a, 0x002a, 0x002a, 0x002a, 0x002a, 0x002a, + 0x001e, 0x001e, 0x001e, 0x001e, 0x001e, 0x001e, + 0x001e, 0x001e, 0x001e, 0x001e, 0x001e, 0x001e, + 0x001e, 0x001e, 0x001e, 0x001e, 0x000e, 0x000e, + 0x000e, 0x000e, 0x000e, 0x000e, 0x000e, 0x000e, + 0x000e, 0x000e, 0x000e, 0x000e, 0x000e, 0x000e, + 0x000e, 0x000e, 0x000e, 0x000e, 0x01fc, 0x01fc, + 0x01fc, 0x01fc, 0x01fc, 0x01fc, 0x01fc, 0x01fc, + 0x01fc, 0x01fc, 0x01fc, 0x01fc, 0x01fc, 0x01fc, + 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, + 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, + 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, + 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, 0x01ee, + 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, + 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, + 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, + 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, + 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, 0x01d6, + 0x01d6, 0x01d6, +}; + +static const u32 b43_httab_0x1f[] = { + 0x00000000, 0x00000000, 0x00016023, 0x00006028, + 0x00034036, 0x0003402e, 0x0007203c, 0x0006e037, + 0x00070030, 0x0009401f, 0x0009a00f, 0x000b600d, + 0x000c8007, 0x000ce007, 0x00101fff, 0x00121ff9, + 0x0012e004, 0x0014dffc, 0x0016dff6, 0x0018dfe9, + 0x001b3fe5, 0x001c5fd0, 0x001ddfc2, 0x001f1fb6, + 0x00207fa4, 0x00219f8f, 0x0022ff7d, 0x00247f6c, + 0x0024df5b, 0x00267f4b, 0x0027df3b, 0x0029bf3b, + 0x002b5f2f, 0x002d3f2e, 0x002f5f2a, 0x002fff15, + 0x00315f0b, 0x0032defa, 0x0033beeb, 0x0034fed9, + 0x00353ec5, 0x00361eb0, 0x00363e9b, 0x0036be87, + 0x0036be70, 0x0038fe67, 0x0044beb2, 0x00513ef3, + 0x00595f11, 0x00669f3d, 0x0078dfdf, 0x00a143aa, + 0x01642fff, 0x0162afff, 0x01620fff, 0x0160cfff, + 0x015f0fff, 0x015dafff, 0x015bcfff, 0x015bcfff, + 0x015b4fff, 0x015acfff, 0x01590fff, 0x0156cfff, +}; + +static const u32 b43_httab_0x21[] = { + 0x00000000, 0x00000000, 0x00016023, 0x00006028, + 0x00034036, 0x0003402e, 0x0007203c, 0x0006e037, + 0x00070030, 0x0009401f, 0x0009a00f, 0x000b600d, + 0x000c8007, 0x000ce007, 0x00101fff, 0x00121ff9, + 0x0012e004, 0x0014dffc, 0x0016dff6, 0x0018dfe9, + 0x001b3fe5, 0x001c5fd0, 0x001ddfc2, 0x001f1fb6, + 0x00207fa4, 0x00219f8f, 0x0022ff7d, 0x00247f6c, + 0x0024df5b, 0x00267f4b, 0x0027df3b, 0x0029bf3b, + 0x002b5f2f, 0x002d3f2e, 0x002f5f2a, 0x002fff15, + 0x00315f0b, 0x0032defa, 0x0033beeb, 0x0034fed9, + 0x00353ec5, 0x00361eb0, 0x00363e9b, 0x0036be87, + 0x0036be70, 0x0038fe67, 0x0044beb2, 0x00513ef3, + 0x00595f11, 0x00669f3d, 0x0078dfdf, 0x00a143aa, + 0x01642fff, 0x0162afff, 0x01620fff, 0x0160cfff, + 0x015f0fff, 0x015dafff, 0x015bcfff, 0x015bcfff, + 0x015b4fff, 0x015acfff, 0x01590fff, 0x0156cfff, +}; + +static const u32 b43_httab_0x23[] = { + 0x00000000, 0x00000000, 0x00016023, 0x00006028, + 0x00034036, 0x0003402e, 0x0007203c, 0x0006e037, + 0x00070030, 0x0009401f, 0x0009a00f, 0x000b600d, + 0x000c8007, 0x000ce007, 0x00101fff, 0x00121ff9, + 0x0012e004, 0x0014dffc, 0x0016dff6, 0x0018dfe9, + 0x001b3fe5, 0x001c5fd0, 0x001ddfc2, 0x001f1fb6, + 0x00207fa4, 0x00219f8f, 0x0022ff7d, 0x00247f6c, + 0x0024df5b, 0x00267f4b, 0x0027df3b, 0x0029bf3b, + 0x002b5f2f, 0x002d3f2e, 0x002f5f2a, 0x002fff15, + 0x00315f0b, 0x0032defa, 0x0033beeb, 0x0034fed9, + 0x00353ec5, 0x00361eb0, 0x00363e9b, 0x0036be87, + 0x0036be70, 0x0038fe67, 0x0044beb2, 0x00513ef3, + 0x00595f11, 0x00669f3d, 0x0078dfdf, 0x00a143aa, + 0x01642fff, 0x0162afff, 0x01620fff, 0x0160cfff, + 0x015f0fff, 0x015dafff, 0x015bcfff, 0x015bcfff, + 0x015b4fff, 0x015acfff, 0x01590fff, 0x0156cfff, +}; + +static const u32 b43_httab_0x20[] = { + 0x0b5e002d, 0x0ae2002f, 0x0a3b0032, 0x09a70035, + 0x09220038, 0x08ab003b, 0x081f003f, 0x07a20043, + 0x07340047, 0x06d2004b, 0x067a004f, 0x06170054, + 0x05bf0059, 0x0571005e, 0x051e0064, 0x04d3006a, + 0x04910070, 0x044c0077, 0x040f007e, 0x03d90085, + 0x03a1008d, 0x036f0095, 0x033d009e, 0x030b00a8, + 0x02e000b2, 0x02b900bc, 0x029200c7, 0x026d00d3, + 0x024900e0, 0x022900ed, 0x020a00fb, 0x01ec010a, + 0x01d20119, 0x01b7012a, 0x019e013c, 0x0188014e, + 0x01720162, 0x015d0177, 0x0149018e, 0x013701a5, + 0x012601be, 0x011501d8, 0x010601f4, 0x00f70212, + 0x00e90231, 0x00dc0253, 0x00d00276, 0x00c4029b, + 0x00b902c3, 0x00af02ed, 0x00a50319, 0x009c0348, + 0x0093037a, 0x008b03af, 0x008303e6, 0x007c0422, + 0x00750460, 0x006e04a3, 0x006804e9, 0x00620533, + 0x005d0582, 0x005805d6, 0x0053062e, 0x004e068c, +}; + +static const u32 b43_httab_0x22[] = { + 0x0b5e002d, 0x0ae2002f, 0x0a3b0032, 0x09a70035, + 0x09220038, 0x08ab003b, 0x081f003f, 0x07a20043, + 0x07340047, 0x06d2004b, 0x067a004f, 0x06170054, + 0x05bf0059, 0x0571005e, 0x051e0064, 0x04d3006a, + 0x04910070, 0x044c0077, 0x040f007e, 0x03d90085, + 0x03a1008d, 0x036f0095, 0x033d009e, 0x030b00a8, + 0x02e000b2, 0x02b900bc, 0x029200c7, 0x026d00d3, + 0x024900e0, 0x022900ed, 0x020a00fb, 0x01ec010a, + 0x01d20119, 0x01b7012a, 0x019e013c, 0x0188014e, + 0x01720162, 0x015d0177, 0x0149018e, 0x013701a5, + 0x012601be, 0x011501d8, 0x010601f4, 0x00f70212, + 0x00e90231, 0x00dc0253, 0x00d00276, 0x00c4029b, + 0x00b902c3, 0x00af02ed, 0x00a50319, 0x009c0348, + 0x0093037a, 0x008b03af, 0x008303e6, 0x007c0422, + 0x00750460, 0x006e04a3, 0x006804e9, 0x00620533, + 0x005d0582, 0x005805d6, 0x0053062e, 0x004e068c, +}; + +static const u32 b43_httab_0x24[] = { + 0x0b5e002d, 0x0ae2002f, 0x0a3b0032, 0x09a70035, + 0x09220038, 0x08ab003b, 0x081f003f, 0x07a20043, + 0x07340047, 0x06d2004b, 0x067a004f, 0x06170054, + 0x05bf0059, 0x0571005e, 0x051e0064, 0x04d3006a, + 0x04910070, 0x044c0077, 0x040f007e, 0x03d90085, + 0x03a1008d, 0x036f0095, 0x033d009e, 0x030b00a8, + 0x02e000b2, 0x02b900bc, 0x029200c7, 0x026d00d3, + 0x024900e0, 0x022900ed, 0x020a00fb, 0x01ec010a, + 0x01d20119, 0x01b7012a, 0x019e013c, 0x0188014e, + 0x01720162, 0x015d0177, 0x0149018e, 0x013701a5, + 0x012601be, 0x011501d8, 0x010601f4, 0x00f70212, + 0x00e90231, 0x00dc0253, 0x00d00276, 0x00c4029b, + 0x00b902c3, 0x00af02ed, 0x00a50319, 0x009c0348, + 0x0093037a, 0x008b03af, 0x008303e6, 0x007c0422, + 0x00750460, 0x006e04a3, 0x006804e9, 0x00620533, + 0x005d0582, 0x005805d6, 0x0053062e, 0x004e068c, +}; + +/* Some late-init table */ +const u32 b43_httab_0x1a_0xc0_late[] = { + 0x10f90040, 0x10e10040, 0x10e1003c, 0x10c9003d, + 0x10b9003c, 0x10a9003d, 0x10a1003c, 0x1099003b, + 0x1091003b, 0x1089003a, 0x1081003a, 0x10790039, + 0x10710039, 0x1069003a, 0x1061003b, 0x1059003d, + 0x1051003f, 0x10490042, 0x1049003e, 0x1049003b, + 0x1041003e, 0x1041003b, 0x1039003e, 0x1039003b, + 0x10390038, 0x10390035, 0x1031003a, 0x10310036, + 0x10310033, 0x1029003a, 0x10290037, 0x10290034, + 0x10290031, 0x10210039, 0x10210036, 0x10210033, + 0x10210030, 0x1019003c, 0x10190039, 0x10190036, + 0x10190033, 0x10190030, 0x1019002d, 0x1019002b, + 0x10190028, 0x1011003a, 0x10110036, 0x10110033, + 0x10110030, 0x1011002e, 0x1011002b, 0x10110029, + 0x10110027, 0x10110024, 0x10110022, 0x10110020, + 0x1011001f, 0x1011001d, 0x1009003a, 0x10090037, + 0x10090034, 0x10090031, 0x1009002e, 0x1009002c, + 0x10090029, 0x10090027, 0x10090025, 0x10090023, + 0x10090021, 0x1009001f, 0x1009001d, 0x1009001b, + 0x1009001a, 0x10090018, 0x10090017, 0x10090016, + 0x10090015, 0x10090013, 0x10090012, 0x10090011, + 0x10090010, 0x1009000f, 0x1009000f, 0x1009000e, + 0x1009000d, 0x1009000c, 0x1009000c, 0x1009000b, + 0x1009000a, 0x1009000a, 0x10090009, 0x10090009, + 0x10090008, 0x10090008, 0x10090007, 0x10090007, + 0x10090007, 0x10090006, 0x10090006, 0x10090005, + 0x10090005, 0x10090005, 0x10090005, 0x10090004, + 0x10090004, 0x10090004, 0x10090004, 0x10090003, + 0x10090003, 0x10090003, 0x10090003, 0x10090003, + 0x10090003, 0x10090002, 0x10090002, 0x10090002, + 0x10090002, 0x10090002, 0x10090002, 0x10090002, + 0x10090002, 0x10090002, 0x10090001, 0x10090001, + 0x10090001, 0x10090001, 0x10090001, 0x10090001, +}; + +/************************************************** + * R/W ops. + **************************************************/ + +u32 b43_httab_read(struct b43_wldev *dev, u32 offset) +{ + u32 type, value; + + type = offset & B43_HTTAB_TYPEMASK; + offset &= ~B43_HTTAB_TYPEMASK; + B43_WARN_ON(offset > 0xFFFF); + + switch (type) { + case B43_HTTAB_8BIT: + b43_phy_write(dev, B43_PHY_HT_TABLE_ADDR, offset); + value = b43_phy_read(dev, B43_PHY_HT_TABLE_DATALO) & 0xFF; + break; + case B43_HTTAB_16BIT: + b43_phy_write(dev, B43_PHY_HT_TABLE_ADDR, offset); + value = b43_phy_read(dev, B43_PHY_HT_TABLE_DATALO); + break; + case B43_HTTAB_32BIT: + b43_phy_write(dev, B43_PHY_HT_TABLE_ADDR, offset); + value = b43_phy_read(dev, B43_PHY_HT_TABLE_DATAHI); + value <<= 16; + value |= b43_phy_read(dev, B43_PHY_HT_TABLE_DATALO); + break; + default: + B43_WARN_ON(1); + value = 0; + } + + return value; +} + +void b43_httab_read_bulk(struct b43_wldev *dev, u32 offset, + unsigned int nr_elements, void *_data) +{ + u32 type; + u8 *data = _data; + unsigned int i; + + type = offset & B43_HTTAB_TYPEMASK; + offset &= ~B43_HTTAB_TYPEMASK; + B43_WARN_ON(offset > 0xFFFF); + + b43_phy_write(dev, B43_PHY_HT_TABLE_ADDR, offset); + + for (i = 0; i < nr_elements; i++) { + switch (type) { + case B43_HTTAB_8BIT: + *data = b43_phy_read(dev, B43_PHY_HT_TABLE_DATALO) & 0xFF; + data++; + break; + case B43_HTTAB_16BIT: + *((u16 *)data) = b43_phy_read(dev, B43_PHY_HT_TABLE_DATALO); + data += 2; + break; + case B43_HTTAB_32BIT: + *((u32 *)data) = b43_phy_read(dev, B43_PHY_HT_TABLE_DATAHI); + *((u32 *)data) <<= 16; + *((u32 *)data) |= b43_phy_read(dev, B43_PHY_HT_TABLE_DATALO); + data += 4; + break; + default: + B43_WARN_ON(1); + } + } +} + +void b43_httab_write(struct b43_wldev *dev, u32 offset, u32 value) +{ + u32 type; + + type = offset & B43_HTTAB_TYPEMASK; + offset &= 0xFFFF; + + switch (type) { + case B43_HTTAB_8BIT: + B43_WARN_ON(value & ~0xFF); + b43_phy_write(dev, B43_PHY_HT_TABLE_ADDR, offset); + b43_phy_write(dev, B43_PHY_HT_TABLE_DATALO, value); + break; + case B43_HTTAB_16BIT: + B43_WARN_ON(value & ~0xFFFF); + b43_phy_write(dev, B43_PHY_HT_TABLE_ADDR, offset); + b43_phy_write(dev, B43_PHY_HT_TABLE_DATALO, value); + break; + case B43_HTTAB_32BIT: + b43_phy_write(dev, B43_PHY_HT_TABLE_ADDR, offset); + b43_phy_write(dev, B43_PHY_HT_TABLE_DATAHI, value >> 16); + b43_phy_write(dev, B43_PHY_HT_TABLE_DATALO, value & 0xFFFF); + break; + default: + B43_WARN_ON(1); + } + + return; +} + +void b43_httab_write_few(struct b43_wldev *dev, u32 offset, size_t num, ...) +{ + va_list args; + u32 type, value; + unsigned int i; + + type = offset & B43_HTTAB_TYPEMASK; + offset &= 0xFFFF; + + va_start(args, num); + switch (type) { + case B43_HTTAB_8BIT: + b43_phy_write(dev, B43_PHY_HT_TABLE_ADDR, offset); + for (i = 0; i < num; i++) { + value = va_arg(args, int); + B43_WARN_ON(value & ~0xFF); + b43_phy_write(dev, B43_PHY_HT_TABLE_DATALO, value); + } + break; + case B43_HTTAB_16BIT: + b43_phy_write(dev, B43_PHY_HT_TABLE_ADDR, offset); + for (i = 0; i < num; i++) { + value = va_arg(args, int); + B43_WARN_ON(value & ~0xFFFF); + b43_phy_write(dev, B43_PHY_HT_TABLE_DATALO, value); + } + break; + case B43_HTTAB_32BIT: + b43_phy_write(dev, B43_PHY_HT_TABLE_ADDR, offset); + for (i = 0; i < num; i++) { + value = va_arg(args, int); + b43_phy_write(dev, B43_PHY_HT_TABLE_DATAHI, + value >> 16); + b43_phy_write(dev, B43_PHY_HT_TABLE_DATALO, + value & 0xFFFF); + } + break; + default: + B43_WARN_ON(1); + } + va_end(args); + + return; +} + +void b43_httab_write_bulk(struct b43_wldev *dev, u32 offset, + unsigned int nr_elements, const void *_data) +{ + u32 type, value; + const u8 *data = _data; + unsigned int i; + + type = offset & B43_HTTAB_TYPEMASK; + offset &= ~B43_HTTAB_TYPEMASK; + B43_WARN_ON(offset > 0xFFFF); + + b43_phy_write(dev, B43_PHY_HT_TABLE_ADDR, offset); + + for (i = 0; i < nr_elements; i++) { + switch (type) { + case B43_HTTAB_8BIT: + value = *data; + data++; + B43_WARN_ON(value & ~0xFF); + b43_phy_write(dev, B43_PHY_HT_TABLE_DATALO, value); + break; + case B43_HTTAB_16BIT: + value = *((u16 *)data); + data += 2; + B43_WARN_ON(value & ~0xFFFF); + b43_phy_write(dev, B43_PHY_HT_TABLE_DATALO, value); + break; + case B43_HTTAB_32BIT: + value = *((u32 *)data); + data += 4; + b43_phy_write(dev, B43_PHY_HT_TABLE_DATAHI, value >> 16); + b43_phy_write(dev, B43_PHY_HT_TABLE_DATALO, + value & 0xFFFF); + break; + default: + B43_WARN_ON(1); + } + } +} + +/************************************************** + * Tables ops. + **************************************************/ + +#define httab_upload(dev, offset, data) do { \ + b43_httab_write_bulk(dev, offset, ARRAY_SIZE(data), data); \ + } while (0) +void b43_phy_ht_tables_init(struct b43_wldev *dev) +{ + BUILD_BUG_ON(ARRAY_SIZE(b43_httab_0x1a_0xc0_late) != + B43_HTTAB_1A_C0_LATE_SIZE); + + httab_upload(dev, B43_HTTAB16(0x12, 0), b43_httab_0x12); + httab_upload(dev, B43_HTTAB16(0x27, 0), b43_httab_0x27); + httab_upload(dev, B43_HTTAB16(0x26, 0), b43_httab_0x26); + httab_upload(dev, B43_HTTAB32(0x25, 0), b43_httab_0x25); + httab_upload(dev, B43_HTTAB32(0x2f, 0), b43_httab_0x2f); + httab_upload(dev, B43_HTTAB16(0x1a, 0), b43_httab_0x1a); + httab_upload(dev, B43_HTTAB16(0x1b, 0), b43_httab_0x1b); + httab_upload(dev, B43_HTTAB16(0x1c, 0), b43_httab_0x1c); + httab_upload(dev, B43_HTTAB32(0x1a, 0x0c0), b43_httab_0x1a_0xc0); + httab_upload(dev, B43_HTTAB32(0x1a, 0x140), b43_httab_0x1a_0x140); + httab_upload(dev, B43_HTTAB32(0x1b, 0x140), b43_httab_0x1b_0x140); + httab_upload(dev, B43_HTTAB32(0x1c, 0x140), b43_httab_0x1c_0x140); + httab_upload(dev, B43_HTTAB16(0x1a, 0x1c0), b43_httab_0x1a_0x1c0); + httab_upload(dev, B43_HTTAB16(0x1b, 0x1c0), b43_httab_0x1b_0x1c0); + httab_upload(dev, B43_HTTAB16(0x1c, 0x1c0), b43_httab_0x1c_0x1c0); + httab_upload(dev, B43_HTTAB16(0x1a, 0x240), b43_httab_0x1a_0x240); + httab_upload(dev, B43_HTTAB16(0x1b, 0x240), b43_httab_0x1b_0x240); + httab_upload(dev, B43_HTTAB16(0x1c, 0x240), b43_httab_0x1c_0x240); + httab_upload(dev, B43_HTTAB32(0x1f, 0), b43_httab_0x1f); + httab_upload(dev, B43_HTTAB32(0x21, 0), b43_httab_0x21); + httab_upload(dev, B43_HTTAB32(0x23, 0), b43_httab_0x23); + httab_upload(dev, B43_HTTAB32(0x20, 0), b43_httab_0x20); + httab_upload(dev, B43_HTTAB32(0x22, 0), b43_httab_0x22); + httab_upload(dev, B43_HTTAB32(0x24, 0), b43_httab_0x24); +} diff --git a/drivers/net/wireless/b43/tables_phy_ht.h b/drivers/net/wireless/b43/tables_phy_ht.h new file mode 100644 index 0000000..1b5ef2b --- /dev/null +++ b/drivers/net/wireless/b43/tables_phy_ht.h @@ -0,0 +1,26 @@ +#ifndef B43_TABLES_PHY_HT_H_ +#define B43_TABLES_PHY_HT_H_ + +/* The HT-PHY tables. */ +#define B43_HTTAB_TYPEMASK 0xF0000000 +#define B43_HTTAB_8BIT 0x10000000 +#define B43_HTTAB_16BIT 0x20000000 +#define B43_HTTAB_32BIT 0x30000000 +#define B43_HTTAB8(table, offset) (((table) << 10) | (offset) | B43_HTTAB_8BIT) +#define B43_HTTAB16(table, offset) (((table) << 10) | (offset) | B43_HTTAB_16BIT) +#define B43_HTTAB32(table, offset) (((table) << 10) | (offset) | B43_HTTAB_32BIT) + +u32 b43_httab_read(struct b43_wldev *dev, u32 offset); +void b43_httab_read_bulk(struct b43_wldev *dev, u32 offset, + unsigned int nr_elements, void *_data); +void b43_httab_write(struct b43_wldev *dev, u32 offset, u32 value); +void b43_httab_write_few(struct b43_wldev *dev, u32 offset, size_t num, ...); +void b43_httab_write_bulk(struct b43_wldev *dev, u32 offset, + unsigned int nr_elements, const void *_data); + +void b43_phy_ht_tables_init(struct b43_wldev *dev); + +#define B43_HTTAB_1A_C0_LATE_SIZE 128 +extern const u32 b43_httab_0x1a_0xc0_late[]; + +#endif /* B43_TABLES_PHY_HT_H_ */ diff --git a/drivers/net/wireless/b43/tables_phy_lcn.c b/drivers/net/wireless/b43/tables_phy_lcn.c new file mode 100644 index 0000000..e347b8d --- /dev/null +++ b/drivers/net/wireless/b43/tables_phy_lcn.c @@ -0,0 +1,724 @@ +/* + + Broadcom B43 wireless driver + IEEE 802.11n LCN-PHY data tables + + Copyright (c) 2011 Rafał Miłecki + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" +#include "tables_phy_lcn.h" +#include "phy_common.h" +#include "phy_lcn.h" + +struct b43_lcntab_tx_gain_tbl_entry { + u8 gm; + u8 pga; + u8 pad; + u8 dac; + u8 bb_mult; +}; + +/************************************************** + * Static tables. + **************************************************/ + +static const u16 b43_lcntab_0x02[] = { + 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, + 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, + 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, + 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, + 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, + 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, + 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, + 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, + 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, + 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, 0x014d, + 0x014d, 0x014d, 0x014d, 0x014d, +}; + +static const u16 b43_lcntab_0x01[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, +}; + +static const u32 b43_lcntab_0x0b[] = { + 0x000141f8, 0x000021f8, 0x000021fb, 0x000041fb, + 0x0001fedb, 0x0000217b, 0x00002133, 0x000040eb, + 0x0001fea3, 0x0000024b, +}; + +static const u32 b43_lcntab_0x0c[] = { + 0x00100001, 0x00200010, 0x00300001, 0x00400010, + 0x00500022, 0x00600122, 0x00700222, 0x00800322, + 0x00900422, 0x00a00522, 0x00b00622, 0x00c00722, + 0x00d00822, 0x00f00922, 0x00100a22, 0x00200b22, + 0x00300c22, 0x00400d22, 0x00500e22, 0x00600f22, +}; + +static const u32 b43_lcntab_0x0d[] = { + 0x00000000, 0x00000000, 0x10000000, 0x00000000, + 0x20000000, 0x00000000, 0x30000000, 0x00000000, + 0x40000000, 0x00000000, 0x50000000, 0x00000000, + 0x60000000, 0x00000000, 0x70000000, 0x00000000, + 0x80000000, 0x00000000, 0x90000000, 0x00000008, + 0xa0000000, 0x00000008, 0xb0000000, 0x00000008, + 0xc0000000, 0x00000008, 0xd0000000, 0x00000008, + 0xe0000000, 0x00000008, 0xf0000000, 0x00000008, + 0x00000000, 0x00000009, 0x10000000, 0x00000009, + 0x20000000, 0x00000019, 0x30000000, 0x00000019, + 0x40000000, 0x00000019, 0x50000000, 0x00000019, + 0x60000000, 0x00000019, 0x70000000, 0x00000019, + 0x80000000, 0x00000019, 0x90000000, 0x00000019, + 0xa0000000, 0x00000019, 0xb0000000, 0x00000019, + 0xc0000000, 0x00000019, 0xd0000000, 0x00000019, + 0xe0000000, 0x00000019, 0xf0000000, 0x00000019, + 0x00000000, 0x0000001a, 0x10000000, 0x0000001a, + 0x20000000, 0x0000001a, 0x30000000, 0x0000001a, + 0x40000000, 0x0000001a, 0x50000000, 0x00000002, + 0x60000000, 0x00000002, 0x70000000, 0x00000002, + 0x80000000, 0x00000002, 0x90000000, 0x00000002, + 0xa0000000, 0x00000002, 0xb0000000, 0x00000002, + 0xc0000000, 0x0000000a, 0xd0000000, 0x0000000a, + 0xe0000000, 0x0000000a, 0xf0000000, 0x0000000a, + 0x00000000, 0x0000000b, 0x10000000, 0x0000000b, + 0x20000000, 0x0000000b, 0x30000000, 0x0000000b, + 0x40000000, 0x0000000b, 0x50000000, 0x0000001b, + 0x60000000, 0x0000001b, 0x70000000, 0x0000001b, + 0x80000000, 0x0000001b, 0x90000000, 0x0000001b, + 0xa0000000, 0x0000001b, 0xb0000000, 0x0000001b, + 0xc0000000, 0x0000001b, 0xd0000000, 0x0000001b, + 0xe0000000, 0x0000001b, 0xf0000000, 0x0000001b, + 0x00000000, 0x0000001c, 0x10000000, 0x0000001c, + 0x20000000, 0x0000001c, 0x30000000, 0x0000001c, + 0x40000000, 0x0000001c, 0x50000000, 0x0000001c, + 0x60000000, 0x0000001c, 0x70000000, 0x0000001c, + 0x80000000, 0x0000001c, 0x90000000, 0x0000001c, +}; + +static const u16 b43_lcntab_0x0e[] = { + 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, + 0x0407, 0x0408, 0x0409, 0x040a, 0x058b, 0x058c, + 0x058d, 0x058e, 0x058f, 0x0090, 0x0091, 0x0092, + 0x0193, 0x0194, 0x0195, 0x0196, 0x0197, 0x0198, + 0x0199, 0x019a, 0x019b, 0x019c, 0x019d, 0x019e, + 0x019f, 0x01a0, 0x01a1, 0x01a2, 0x01a3, 0x01a4, + 0x01a5, 0x0000, +}; + +static const u16 b43_lcntab_0x0f[] = { + 0x000a, 0x0009, 0x0006, 0x0005, 0x000a, 0x0009, + 0x0006, 0x0005, 0x000a, 0x0009, 0x0006, 0x0005, + 0x000a, 0x0009, 0x0006, 0x0005, 0x000a, 0x0009, + 0x0006, 0x0005, 0x000a, 0x0009, 0x0006, 0x0005, + 0x000a, 0x0009, 0x0006, 0x0005, 0x000a, 0x0009, + 0x0006, 0x0005, 0x000a, 0x0009, 0x0006, 0x0005, + 0x000a, 0x0009, 0x0006, 0x0005, 0x000a, 0x0009, + 0x0006, 0x0005, 0x000a, 0x0009, 0x0006, 0x0005, + 0x000a, 0x0009, 0x0006, 0x0005, 0x000a, 0x0009, + 0x0006, 0x0005, 0x000a, 0x0009, 0x0006, 0x0005, + 0x000a, 0x0009, 0x0006, 0x0005, +}; + +static const u16 b43_lcntab_0x10[] = { + 0x005f, 0x0036, 0x0029, 0x001f, 0x005f, 0x0036, + 0x0029, 0x001f, 0x005f, 0x0036, 0x0029, 0x001f, + 0x005f, 0x0036, 0x0029, 0x001f, +}; + +static const u16 b43_lcntab_0x11[] = { + 0x0009, 0x000f, 0x0014, 0x0018, 0x00fe, 0x0007, + 0x000b, 0x000f, 0x00fb, 0x00fe, 0x0001, 0x0005, + 0x0008, 0x000b, 0x000e, 0x0011, 0x0014, 0x0017, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0003, 0x0006, 0x0009, 0x000c, 0x000f, + 0x0012, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0003, + 0x0006, 0x0009, 0x000c, 0x000f, 0x0012, 0x0015, + 0x0018, 0x001b, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0003, 0x00eb, 0x0000, 0x0000, +}; + +static const u32 b43_lcntab_0x12[] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000004, 0x00000000, 0x00000004, 0x00000008, + 0x00000001, 0x00000005, 0x00000009, 0x0000000d, + 0x0000004d, 0x0000008d, 0x0000000d, 0x0000004d, + 0x0000008d, 0x000000cd, 0x0000004f, 0x0000008f, + 0x000000cf, 0x000000d3, 0x00000113, 0x00000513, + 0x00000913, 0x00000953, 0x00000d53, 0x00001153, + 0x00001193, 0x00005193, 0x00009193, 0x0000d193, + 0x00011193, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000004, + 0x00000000, 0x00000004, 0x00000008, 0x00000001, + 0x00000005, 0x00000009, 0x0000000d, 0x0000004d, + 0x0000008d, 0x0000000d, 0x0000004d, 0x0000008d, + 0x000000cd, 0x0000004f, 0x0000008f, 0x000000cf, + 0x000000d3, 0x00000113, 0x00000513, 0x00000913, + 0x00000953, 0x00000d53, 0x00001153, 0x00005153, + 0x00009153, 0x0000d153, 0x00011153, 0x00015153, + 0x00019153, 0x0001d153, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, +}; + +static const u16 b43_lcntab_0x14[] = { + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0002, 0x0003, 0x0001, 0x0003, 0x0002, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0002, 0x0003, + 0x0001, 0x0003, 0x0002, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, +}; + +static const u16 b43_lcntab_0x17[] = { + 0x001a, 0x0034, 0x004e, 0x0068, 0x009c, 0x00d0, + 0x00ea, 0x0104, 0x0034, 0x0068, 0x009c, 0x00d0, + 0x0138, 0x01a0, 0x01d4, 0x0208, 0x004e, 0x009c, + 0x00ea, 0x0138, 0x01d4, 0x0270, 0x02be, 0x030c, + 0x0068, 0x00d0, 0x0138, 0x01a0, 0x0270, 0x0340, + 0x03a8, 0x0410, 0x0018, 0x009c, 0x00d0, 0x0104, + 0x00ea, 0x0138, 0x0186, 0x00d0, 0x0104, 0x0104, + 0x0138, 0x016c, 0x016c, 0x01a0, 0x0138, 0x0186, + 0x0186, 0x01d4, 0x0222, 0x0222, 0x0270, 0x0104, + 0x0138, 0x016c, 0x0138, 0x016c, 0x01a0, 0x01d4, + 0x01a0, 0x01d4, 0x0208, 0x0208, 0x023c, 0x0186, + 0x01d4, 0x0222, 0x01d4, 0x0222, 0x0270, 0x02be, + 0x0270, 0x02be, 0x030c, 0x030c, 0x035a, 0x0036, + 0x006c, 0x00a2, 0x00d8, 0x0144, 0x01b0, 0x01e6, + 0x021c, 0x006c, 0x00d8, 0x0144, 0x01b0, 0x0288, + 0x0360, 0x03cc, 0x0438, 0x00a2, 0x0144, 0x01e6, + 0x0288, 0x03cc, 0x0510, 0x05b2, 0x0654, 0x00d8, + 0x01b0, 0x0288, 0x0360, 0x0510, 0x06c0, 0x0798, + 0x0870, 0x0018, 0x0144, 0x01b0, 0x021c, 0x01e6, + 0x0288, 0x032a, 0x01b0, 0x021c, 0x021c, 0x0288, + 0x02f4, 0x02f4, 0x0360, 0x0288, 0x032a, 0x032a, + 0x03cc, 0x046e, 0x046e, 0x0510, 0x021c, 0x0288, + 0x02f4, 0x0288, 0x02f4, 0x0360, 0x03cc, 0x0360, + 0x03cc, 0x0438, 0x0438, 0x04a4, 0x032a, 0x03cc, + 0x046e, 0x03cc, 0x046e, 0x0510, 0x05b2, 0x0510, + 0x05b2, 0x0654, 0x0654, 0x06f6, +}; + +static const u16 b43_lcntab_0x00[] = { + 0x0200, 0x0300, 0x0400, 0x0600, 0x0800, 0x0b00, + 0x1000, 0x1001, 0x1002, 0x1003, 0x1004, 0x1005, + 0x1006, 0x1007, 0x1707, 0x2007, 0x2d07, 0x4007, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0200, 0x0300, 0x0400, 0x0600, + 0x0800, 0x0b00, 0x1000, 0x1001, 0x1002, 0x1003, + 0x1004, 0x1005, 0x1006, 0x1007, 0x1707, 0x2007, + 0x2d07, 0x4007, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x4000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, +}; + +static const u32 b43_lcntab_0x18[] = { + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, + 0x00080000, 0x00080000, 0x00080000, 0x00080000, +}; + +/************************************************** + * TX gain. + **************************************************/ + +static const struct b43_lcntab_tx_gain_tbl_entry + b43_lcntab_tx_gain_tbl_2ghz_ext_pa_rev0[B43_LCNTAB_TX_GAIN_SIZE] = { + { 0x03, 0x00, 0x1f, 0x0, 0x48 }, + { 0x03, 0x00, 0x1f, 0x0, 0x46 }, + { 0x03, 0x00, 0x1f, 0x0, 0x44 }, + { 0x03, 0x00, 0x1e, 0x0, 0x43 }, + { 0x03, 0x00, 0x1d, 0x0, 0x44 }, + { 0x03, 0x00, 0x1c, 0x0, 0x44 }, + { 0x03, 0x00, 0x1b, 0x0, 0x45 }, + { 0x03, 0x00, 0x1a, 0x0, 0x46 }, + { 0x03, 0x00, 0x19, 0x0, 0x46 }, + { 0x03, 0x00, 0x18, 0x0, 0x47 }, + { 0x03, 0x00, 0x17, 0x0, 0x48 }, + { 0x03, 0x00, 0x17, 0x0, 0x46 }, + { 0x03, 0x00, 0x16, 0x0, 0x47 }, + { 0x03, 0x00, 0x15, 0x0, 0x48 }, + { 0x03, 0x00, 0x15, 0x0, 0x46 }, + { 0x03, 0x00, 0x15, 0x0, 0x44 }, + { 0x03, 0x00, 0x15, 0x0, 0x42 }, + { 0x03, 0x00, 0x15, 0x0, 0x40 }, + { 0x03, 0x00, 0x15, 0x0, 0x3f }, + { 0x03, 0x00, 0x14, 0x0, 0x40 }, + { 0x03, 0x00, 0x13, 0x0, 0x41 }, + { 0x03, 0x00, 0x13, 0x0, 0x40 }, + { 0x03, 0x00, 0x12, 0x0, 0x41 }, + { 0x03, 0x00, 0x12, 0x0, 0x40 }, + { 0x03, 0x00, 0x11, 0x0, 0x41 }, + { 0x03, 0x00, 0x11, 0x0, 0x40 }, + { 0x03, 0x00, 0x10, 0x0, 0x41 }, + { 0x03, 0x00, 0x10, 0x0, 0x40 }, + { 0x03, 0x00, 0x10, 0x0, 0x3e }, + { 0x03, 0x00, 0x10, 0x0, 0x3c }, + { 0x03, 0x00, 0x10, 0x0, 0x3a }, + { 0x03, 0x00, 0x0f, 0x0, 0x3d }, + { 0x03, 0x00, 0x0f, 0x0, 0x3b }, + { 0x03, 0x00, 0x0e, 0x0, 0x3d }, + { 0x03, 0x00, 0x0e, 0x0, 0x3c }, + { 0x03, 0x00, 0x0e, 0x0, 0x3a }, + { 0x03, 0x00, 0x0d, 0x0, 0x3c }, + { 0x03, 0x00, 0x0d, 0x0, 0x3b }, + { 0x03, 0x00, 0x0c, 0x0, 0x3e }, + { 0x03, 0x00, 0x0c, 0x0, 0x3c }, + { 0x03, 0x00, 0x0c, 0x0, 0x3a }, + { 0x03, 0x00, 0x0b, 0x0, 0x3e }, + { 0x03, 0x00, 0x0b, 0x0, 0x3c }, + { 0x03, 0x00, 0x0b, 0x0, 0x3b }, + { 0x03, 0x00, 0x0b, 0x0, 0x39 }, + { 0x03, 0x00, 0x0a, 0x0, 0x3d }, + { 0x03, 0x00, 0x0a, 0x0, 0x3b }, + { 0x03, 0x00, 0x0a, 0x0, 0x39 }, + { 0x03, 0x00, 0x09, 0x0, 0x3e }, + { 0x03, 0x00, 0x09, 0x0, 0x3c }, + { 0x03, 0x00, 0x09, 0x0, 0x3a }, + { 0x03, 0x00, 0x09, 0x0, 0x39 }, + { 0x03, 0x00, 0x08, 0x0, 0x3e }, + { 0x03, 0x00, 0x08, 0x0, 0x3c }, + { 0x03, 0x00, 0x08, 0x0, 0x3a }, + { 0x03, 0x00, 0x08, 0x0, 0x39 }, + { 0x03, 0x00, 0x08, 0x0, 0x37 }, + { 0x03, 0x00, 0x07, 0x0, 0x3d }, + { 0x03, 0x00, 0x07, 0x0, 0x3c }, + { 0x03, 0x00, 0x07, 0x0, 0x3a }, + { 0x03, 0x00, 0x07, 0x0, 0x38 }, + { 0x03, 0x00, 0x07, 0x0, 0x37 }, + { 0x03, 0x00, 0x06, 0x0, 0x3e }, + { 0x03, 0x00, 0x06, 0x0, 0x3c }, + { 0x03, 0x00, 0x06, 0x0, 0x3a }, + { 0x03, 0x00, 0x06, 0x0, 0x39 }, + { 0x03, 0x00, 0x06, 0x0, 0x37 }, + { 0x03, 0x00, 0x06, 0x0, 0x36 }, + { 0x03, 0x00, 0x06, 0x0, 0x34 }, + { 0x03, 0x00, 0x05, 0x0, 0x3d }, + { 0x03, 0x00, 0x05, 0x0, 0x3b }, + { 0x03, 0x00, 0x05, 0x0, 0x39 }, + { 0x03, 0x00, 0x05, 0x0, 0x38 }, + { 0x03, 0x00, 0x05, 0x0, 0x36 }, + { 0x03, 0x00, 0x05, 0x0, 0x35 }, + { 0x03, 0x00, 0x05, 0x0, 0x33 }, + { 0x03, 0x00, 0x04, 0x0, 0x3e }, + { 0x03, 0x00, 0x04, 0x0, 0x3c }, + { 0x03, 0x00, 0x04, 0x0, 0x3a }, + { 0x03, 0x00, 0x04, 0x0, 0x39 }, + { 0x03, 0x00, 0x04, 0x0, 0x37 }, + { 0x03, 0x00, 0x04, 0x0, 0x36 }, + { 0x03, 0x00, 0x04, 0x0, 0x34 }, + { 0x03, 0x00, 0x04, 0x0, 0x33 }, + { 0x03, 0x00, 0x04, 0x0, 0x31 }, + { 0x03, 0x00, 0x04, 0x0, 0x30 }, + { 0x03, 0x00, 0x04, 0x0, 0x2e }, + { 0x03, 0x00, 0x03, 0x0, 0x3c }, + { 0x03, 0x00, 0x03, 0x0, 0x3a }, + { 0x03, 0x00, 0x03, 0x0, 0x39 }, + { 0x03, 0x00, 0x03, 0x0, 0x37 }, + { 0x03, 0x00, 0x03, 0x0, 0x36 }, + { 0x03, 0x00, 0x03, 0x0, 0x34 }, + { 0x03, 0x00, 0x03, 0x0, 0x33 }, + { 0x03, 0x00, 0x03, 0x0, 0x31 }, + { 0x03, 0x00, 0x03, 0x0, 0x30 }, + { 0x03, 0x00, 0x03, 0x0, 0x2e }, + { 0x03, 0x00, 0x03, 0x0, 0x2d }, + { 0x03, 0x00, 0x03, 0x0, 0x2c }, + { 0x03, 0x00, 0x03, 0x0, 0x2b }, + { 0x03, 0x00, 0x03, 0x0, 0x29 }, + { 0x03, 0x00, 0x02, 0x0, 0x3d }, + { 0x03, 0x00, 0x02, 0x0, 0x3b }, + { 0x03, 0x00, 0x02, 0x0, 0x39 }, + { 0x03, 0x00, 0x02, 0x0, 0x38 }, + { 0x03, 0x00, 0x02, 0x0, 0x36 }, + { 0x03, 0x00, 0x02, 0x0, 0x35 }, + { 0x03, 0x00, 0x02, 0x0, 0x33 }, + { 0x03, 0x00, 0x02, 0x0, 0x32 }, + { 0x03, 0x00, 0x02, 0x0, 0x30 }, + { 0x03, 0x00, 0x02, 0x0, 0x2f }, + { 0x03, 0x00, 0x02, 0x0, 0x2e }, + { 0x03, 0x00, 0x02, 0x0, 0x2c }, + { 0x03, 0x00, 0x02, 0x0, 0x2b }, + { 0x03, 0x00, 0x02, 0x0, 0x2a }, + { 0x03, 0x00, 0x02, 0x0, 0x29 }, + { 0x03, 0x00, 0x02, 0x0, 0x27 }, + { 0x03, 0x00, 0x02, 0x0, 0x26 }, + { 0x03, 0x00, 0x02, 0x0, 0x25 }, + { 0x03, 0x00, 0x02, 0x0, 0x24 }, + { 0x03, 0x00, 0x02, 0x0, 0x23 }, + { 0x03, 0x00, 0x02, 0x0, 0x22 }, + { 0x03, 0x00, 0x02, 0x0, 0x21 }, + { 0x03, 0x00, 0x02, 0x0, 0x20 }, + { 0x03, 0x00, 0x01, 0x0, 0x3f }, + { 0x03, 0x00, 0x01, 0x0, 0x3d }, + { 0x03, 0x00, 0x01, 0x0, 0x3b }, + { 0x03, 0x00, 0x01, 0x0, 0x39 }, +}; + +/************************************************** + * SW control. + **************************************************/ + +static const u16 b43_lcntab_sw_ctl_4313_epa_rev0[] = { + 0x0002, 0x0008, 0x0004, 0x0001, 0x0002, 0x0008, + 0x0004, 0x0001, 0x0002, 0x0008, 0x0004, 0x0001, + 0x0002, 0x0008, 0x0004, 0x0001, 0x0002, 0x0008, + 0x0004, 0x0001, 0x0002, 0x0008, 0x0004, 0x0001, + 0x0002, 0x0008, 0x0004, 0x0001, 0x0002, 0x0008, + 0x0004, 0x0001, 0x0002, 0x0008, 0x0004, 0x0001, + 0x0002, 0x0008, 0x0004, 0x0001, 0x0002, 0x0008, + 0x0004, 0x0001, 0x0002, 0x0008, 0x0004, 0x0001, + 0x0002, 0x0008, 0x0004, 0x0001, 0x0002, 0x0008, + 0x0004, 0x0001, 0x0002, 0x0008, 0x0004, 0x0001, + 0x0002, 0x0008, 0x0004, 0x0001, +}; + +/************************************************** + * R/W ops. + **************************************************/ + +u32 b43_lcntab_read(struct b43_wldev *dev, u32 offset) +{ + u32 type, value; + + type = offset & B43_LCNTAB_TYPEMASK; + offset &= ~B43_LCNTAB_TYPEMASK; + B43_WARN_ON(offset > 0xFFFF); + + switch (type) { + case B43_LCNTAB_8BIT: + b43_phy_write(dev, B43_PHY_LCN_TABLE_ADDR, offset); + value = b43_phy_read(dev, B43_PHY_LCN_TABLE_DATALO) & 0xFF; + break; + case B43_LCNTAB_16BIT: + b43_phy_write(dev, B43_PHY_LCN_TABLE_ADDR, offset); + value = b43_phy_read(dev, B43_PHY_LCN_TABLE_DATALO); + break; + case B43_LCNTAB_32BIT: + b43_phy_write(dev, B43_PHY_LCN_TABLE_ADDR, offset); + value = b43_phy_read(dev, B43_PHY_LCN_TABLE_DATALO); + value |= (b43_phy_read(dev, B43_PHY_LCN_TABLE_DATAHI) << 16); + break; + default: + B43_WARN_ON(1); + value = 0; + } + + return value; +} + +void b43_lcntab_read_bulk(struct b43_wldev *dev, u32 offset, + unsigned int nr_elements, void *_data) +{ + u32 type; + u8 *data = _data; + unsigned int i; + + type = offset & B43_LCNTAB_TYPEMASK; + offset &= ~B43_LCNTAB_TYPEMASK; + B43_WARN_ON(offset > 0xFFFF); + + b43_phy_write(dev, B43_PHY_LCN_TABLE_ADDR, offset); + + for (i = 0; i < nr_elements; i++) { + switch (type) { + case B43_LCNTAB_8BIT: + *data = b43_phy_read(dev, + B43_PHY_LCN_TABLE_DATALO) & 0xFF; + data++; + break; + case B43_LCNTAB_16BIT: + *((u16 *)data) = b43_phy_read(dev, + B43_PHY_LCN_TABLE_DATALO); + data += 2; + break; + case B43_LCNTAB_32BIT: + *((u32 *)data) = b43_phy_read(dev, + B43_PHY_LCN_TABLE_DATALO); + *((u32 *)data) |= (b43_phy_read(dev, + B43_PHY_LCN_TABLE_DATAHI) << 16); + data += 4; + break; + default: + B43_WARN_ON(1); + } + } +} + +void b43_lcntab_write(struct b43_wldev *dev, u32 offset, u32 value) +{ + u32 type; + + type = offset & B43_LCNTAB_TYPEMASK; + offset &= 0xFFFF; + + switch (type) { + case B43_LCNTAB_8BIT: + B43_WARN_ON(value & ~0xFF); + b43_phy_write(dev, B43_PHY_LCN_TABLE_ADDR, offset); + b43_phy_write(dev, B43_PHY_LCN_TABLE_DATALO, value); + break; + case B43_LCNTAB_16BIT: + B43_WARN_ON(value & ~0xFFFF); + b43_phy_write(dev, B43_PHY_LCN_TABLE_ADDR, offset); + b43_phy_write(dev, B43_PHY_LCN_TABLE_DATALO, value); + break; + case B43_LCNTAB_32BIT: + b43_phy_write(dev, B43_PHY_LCN_TABLE_ADDR, offset); + b43_phy_write(dev, B43_PHY_LCN_TABLE_DATAHI, value >> 16); + b43_phy_write(dev, B43_PHY_LCN_TABLE_DATALO, value & 0xFFFF); + break; + default: + B43_WARN_ON(1); + } + + return; +} + +void b43_lcntab_write_bulk(struct b43_wldev *dev, u32 offset, + unsigned int nr_elements, const void *_data) +{ + u32 type, value; + const u8 *data = _data; + unsigned int i; + + type = offset & B43_LCNTAB_TYPEMASK; + offset &= ~B43_LCNTAB_TYPEMASK; + B43_WARN_ON(offset > 0xFFFF); + + b43_phy_write(dev, B43_PHY_LCN_TABLE_ADDR, offset); + + for (i = 0; i < nr_elements; i++) { + switch (type) { + case B43_LCNTAB_8BIT: + value = *data; + data++; + B43_WARN_ON(value & ~0xFF); + b43_phy_write(dev, B43_PHY_LCN_TABLE_DATALO, value); + break; + case B43_LCNTAB_16BIT: + value = *((u16 *)data); + data += 2; + B43_WARN_ON(value & ~0xFFFF); + b43_phy_write(dev, B43_PHY_LCN_TABLE_DATALO, value); + break; + case B43_LCNTAB_32BIT: + value = *((u32 *)data); + data += 4; + b43_phy_write(dev, B43_PHY_LCN_TABLE_DATAHI, + value >> 16); + b43_phy_write(dev, B43_PHY_LCN_TABLE_DATALO, + value & 0xFFFF); + break; + default: + B43_WARN_ON(1); + } + } +} + +/************************************************** + * Tables ops. + **************************************************/ + +#define lcntab_upload(dev, offset, data) do { \ + b43_lcntab_write_bulk(dev, offset, ARRAY_SIZE(data), data); \ + } while (0) +static void b43_phy_lcn_upload_static_tables(struct b43_wldev *dev) +{ + lcntab_upload(dev, B43_LCNTAB16(0x02, 0), b43_lcntab_0x02); + lcntab_upload(dev, B43_LCNTAB16(0x01, 0), b43_lcntab_0x01); + lcntab_upload(dev, B43_LCNTAB32(0x0b, 0), b43_lcntab_0x0b); + lcntab_upload(dev, B43_LCNTAB32(0x0c, 0), b43_lcntab_0x0c); + lcntab_upload(dev, B43_LCNTAB32(0x0d, 0), b43_lcntab_0x0d); + lcntab_upload(dev, B43_LCNTAB16(0x0e, 0), b43_lcntab_0x0e); + lcntab_upload(dev, B43_LCNTAB16(0x0f, 0), b43_lcntab_0x0f); + lcntab_upload(dev, B43_LCNTAB16(0x10, 0), b43_lcntab_0x10); + lcntab_upload(dev, B43_LCNTAB16(0x11, 0), b43_lcntab_0x11); + lcntab_upload(dev, B43_LCNTAB32(0x12, 0), b43_lcntab_0x12); + lcntab_upload(dev, B43_LCNTAB16(0x14, 0), b43_lcntab_0x14); + lcntab_upload(dev, B43_LCNTAB16(0x17, 0), b43_lcntab_0x17); + lcntab_upload(dev, B43_LCNTAB16(0x00, 0), b43_lcntab_0x00); + lcntab_upload(dev, B43_LCNTAB32(0x18, 0), b43_lcntab_0x18); +} + +static void b43_phy_lcn_load_tx_gain_tab(struct b43_wldev *dev, + const struct b43_lcntab_tx_gain_tbl_entry *gain_table) +{ + u32 i; + u32 val; + + u16 pa_gain = 0x70; + if (dev->dev->bus_sprom->boardflags_lo & B43_BFL_FEM) + pa_gain = 0x10; + + for (i = 0; i < B43_LCNTAB_TX_GAIN_SIZE; i++) { + val = ((pa_gain << 24) | + (gain_table[i].pad << 16) | + (gain_table[i].pga << 8) | + gain_table[i].gm); + b43_lcntab_write(dev, B43_LCNTAB32(0x7, 0xc0 + i), val); + + /* brcmsmac doesn't maskset, we follow newer wl here */ + val = b43_lcntab_read(dev, B43_LCNTAB32(0x7, 0x140 + i)); + val &= 0x000fffff; + val |= ((gain_table[i].dac << 28) | + (gain_table[i].bb_mult << 20)); + b43_lcntab_write(dev, B43_LCNTAB32(0x7, 0x140 + i), val); + } +} + +/* wlc_lcnphy_load_rfpower */ +static void b43_phy_lcn_load_rfpower(struct b43_wldev *dev) +{ + u32 bbmult, rfgain; + u8 i; + + for (i = 0; i < 128; i++) { + bbmult = b43_lcntab_read(dev, B43_LCNTAB32(0x7, 0x140 + i)); + bbmult >>= 20; + rfgain = b43_lcntab_read(dev, B43_LCNTAB32(0x7, 0xc0 + i)); + + /* TODO: calculate value for 0x240 + i table offset + * b43_lcntab_write(dev, B43_LCNTAB32(0x7, 0x240 + i), val); + */ + } +} + +/* Not implemented in brcmsmac, noticed in wl in MMIO dump */ +static void b43_phy_lcn_rewrite_rfpower_table(struct b43_wldev *dev) +{ + int i; + u32 tmp; + for (i = 0; i < 128; i++) { + tmp = b43_lcntab_read(dev, B43_LCNTAB32(0x7, 0x240 + i)); + b43_lcntab_write(dev, B43_LCNTAB32(0x7, 0x240 + i), tmp); + } +} + +/* wlc_lcnphy_clear_papd_comptable */ +static void b43_phy_lcn_clean_papd_comp_table(struct b43_wldev *dev) +{ + u8 i; + + for (i = 0; i < 0x80; i++) + b43_lcntab_write(dev, B43_LCNTAB32(0x18, i), 0x80000); +} + +/* wlc_lcnphy_tbl_init */ +void b43_phy_lcn_tables_init(struct b43_wldev *dev) +{ + struct ssb_sprom *sprom = dev->dev->bus_sprom; + + b43_phy_lcn_upload_static_tables(dev); + + if (b43_current_band(dev->wl) == IEEE80211_BAND_2GHZ) { + if (sprom->boardflags_lo & B43_BFL_FEM) + b43_phy_lcn_load_tx_gain_tab(dev, + b43_lcntab_tx_gain_tbl_2ghz_ext_pa_rev0); + else + b43err(dev->wl, + "TX gain table unknown for this card\n"); + } + + if (sprom->boardflags_lo & B43_BFL_FEM && + !(sprom->boardflags_hi & B43_BFH_FEM_BT)) + b43_lcntab_write_bulk(dev, B43_LCNTAB16(0xf, 0), + ARRAY_SIZE(b43_lcntab_sw_ctl_4313_epa_rev0), + b43_lcntab_sw_ctl_4313_epa_rev0); + else + b43err(dev->wl, "SW ctl table is unknown for this card\n"); + + b43_phy_lcn_load_rfpower(dev); + b43_phy_lcn_rewrite_rfpower_table(dev); + b43_phy_lcn_clean_papd_comp_table(dev); +} diff --git a/drivers/net/wireless/b43/tables_phy_lcn.h b/drivers/net/wireless/b43/tables_phy_lcn.h new file mode 100644 index 0000000..caff9db --- /dev/null +++ b/drivers/net/wireless/b43/tables_phy_lcn.h @@ -0,0 +1,24 @@ +#ifndef B43_TABLES_PHY_LCN_H_ +#define B43_TABLES_PHY_LCN_H_ + +/* The LCN-PHY tables. */ +#define B43_LCNTAB_TYPEMASK 0xF0000000 +#define B43_LCNTAB_8BIT 0x10000000 +#define B43_LCNTAB_16BIT 0x20000000 +#define B43_LCNTAB_32BIT 0x30000000 +#define B43_LCNTAB8(table, offset) (((table) << 10) | (offset) | B43_LCNTAB_8BIT) +#define B43_LCNTAB16(table, offset) (((table) << 10) | (offset) | B43_LCNTAB_16BIT) +#define B43_LCNTAB32(table, offset) (((table) << 10) | (offset) | B43_LCNTAB_32BIT) + +#define B43_LCNTAB_TX_GAIN_SIZE 128 + +u32 b43_lcntab_read(struct b43_wldev *dev, u32 offset); +void b43_lcntab_read_bulk(struct b43_wldev *dev, u32 offset, + unsigned int nr_elements, void *_data); +void b43_lcntab_write(struct b43_wldev *dev, u32 offset, u32 value); +void b43_lcntab_write_bulk(struct b43_wldev *dev, u32 offset, + unsigned int nr_elements, const void *_data); + +void b43_phy_lcn_tables_init(struct b43_wldev *dev); + +#endif /* B43_TABLES_PHY_LCN_H_ */ diff --git a/drivers/net/wireless/b43/wa.c b/drivers/net/wireless/b43/wa.c new file mode 100644 index 0000000..9b1a038 --- /dev/null +++ b/drivers/net/wireless/b43/wa.c @@ -0,0 +1,634 @@ +/* + + Broadcom B43 wireless driver + + PHY workarounds. + + Copyright (c) 2005-2007 Stefano Brivio + Copyright (c) 2005-2007 Michael Buesch + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43.h" +#include "main.h" +#include "tables.h" +#include "phy_common.h" +#include "wa.h" + +static void b43_wa_papd(struct b43_wldev *dev) +{ + u16 backup; + + backup = b43_ofdmtab_read16(dev, B43_OFDMTAB_PWRDYN2, 0); + b43_ofdmtab_write16(dev, B43_OFDMTAB_PWRDYN2, 0, 7); + b43_ofdmtab_write16(dev, B43_OFDMTAB_UNKNOWN_APHY, 0, 0); + b43_dummy_transmission(dev, true, true); + b43_ofdmtab_write16(dev, B43_OFDMTAB_PWRDYN2, 0, backup); +} + +static void b43_wa_auxclipthr(struct b43_wldev *dev) +{ + b43_phy_write(dev, B43_PHY_OFDM(0x8E), 0x3800); +} + +static void b43_wa_afcdac(struct b43_wldev *dev) +{ + b43_phy_write(dev, 0x0035, 0x03FF); + b43_phy_write(dev, 0x0036, 0x0400); +} + +static void b43_wa_txdc_offset(struct b43_wldev *dev) +{ + b43_ofdmtab_write16(dev, B43_OFDMTAB_DC, 0, 0x0051); +} + +void b43_wa_initgains(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + + b43_phy_write(dev, B43_PHY_LNAHPFCTL, 0x1FF9); + b43_phy_mask(dev, B43_PHY_LPFGAINCTL, 0xFF0F); + if (phy->rev <= 2) + b43_ofdmtab_write16(dev, B43_OFDMTAB_LPFGAIN, 0, 0x1FBF); + b43_radio_write16(dev, 0x0002, 0x1FBF); + + b43_phy_write(dev, 0x0024, 0x4680); + b43_phy_write(dev, 0x0020, 0x0003); + b43_phy_write(dev, 0x001D, 0x0F40); + b43_phy_write(dev, 0x001F, 0x1C00); + if (phy->rev <= 3) + b43_phy_maskset(dev, 0x002A, 0x00FF, 0x0400); + else if (phy->rev == 5) { + b43_phy_maskset(dev, 0x002A, 0x00FF, 0x1A00); + b43_phy_write(dev, 0x00CC, 0x2121); + } + if (phy->rev >= 3) + b43_phy_write(dev, 0x00BA, 0x3ED5); +} + +static void b43_wa_divider(struct b43_wldev *dev) +{ + b43_phy_mask(dev, 0x002B, ~0x0100); + b43_phy_write(dev, 0x008E, 0x58C1); +} + +static void b43_wa_gt(struct b43_wldev *dev) /* Gain table. */ +{ + if (dev->phy.rev <= 2) { + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN2, 0, 15); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN2, 1, 31); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN2, 2, 42); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN2, 3, 48); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN2, 4, 58); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN0, 0, 19); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN0, 1, 19); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN0, 2, 19); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN0, 3, 19); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN0, 4, 21); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN0, 5, 21); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN0, 6, 25); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN1, 0, 3); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN1, 1, 3); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN1, 2, 7); + } else { + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN0, 0, 19); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN0, 1, 19); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN0, 2, 19); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN0, 3, 19); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN0, 4, 21); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN0, 5, 21); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAIN0, 6, 25); + } +} + +static void b43_wa_rssi_lt(struct b43_wldev *dev) /* RSSI lookup table */ +{ + int i; + + if (0 /* FIXME: For APHY.rev=2 this might be needed */) { + for (i = 0; i < 8; i++) + b43_ofdmtab_write16(dev, B43_OFDMTAB_RSSI, i, i + 8); + for (i = 8; i < 16; i++) + b43_ofdmtab_write16(dev, B43_OFDMTAB_RSSI, i, i - 8); + } else { + for (i = 0; i < 64; i++) + b43_ofdmtab_write16(dev, B43_OFDMTAB_RSSI, i, i); + } +} + +static void b43_wa_analog(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + u16 ofdmrev; + + ofdmrev = b43_phy_read(dev, B43_PHY_VERSION_OFDM) & B43_PHYVER_VERSION; + if (ofdmrev > 2) { + if (phy->type == B43_PHYTYPE_A) + b43_phy_write(dev, B43_PHY_PWRDOWN, 0x1808); + else + b43_phy_write(dev, B43_PHY_PWRDOWN, 0x1000); + } else { + b43_ofdmtab_write16(dev, B43_OFDMTAB_DAC, 3, 0x1044); + b43_ofdmtab_write16(dev, B43_OFDMTAB_DAC, 4, 0x7201); + b43_ofdmtab_write16(dev, B43_OFDMTAB_DAC, 6, 0x0040); + } +} + +static void b43_wa_dac(struct b43_wldev *dev) +{ + if (dev->phy.analog == 1) + b43_ofdmtab_write16(dev, B43_OFDMTAB_DAC, 1, + (b43_ofdmtab_read16(dev, B43_OFDMTAB_DAC, 1) & ~0x0034) | 0x0008); + else + b43_ofdmtab_write16(dev, B43_OFDMTAB_DAC, 1, + (b43_ofdmtab_read16(dev, B43_OFDMTAB_DAC, 1) & ~0x0078) | 0x0010); +} + +static void b43_wa_fft(struct b43_wldev *dev) /* Fine frequency table */ +{ + int i; + + if (dev->phy.type == B43_PHYTYPE_A) + for (i = 0; i < B43_TAB_FINEFREQA_SIZE; i++) + b43_ofdmtab_write16(dev, B43_OFDMTAB_DACRFPABB, i, b43_tab_finefreqa[i]); + else + for (i = 0; i < B43_TAB_FINEFREQG_SIZE; i++) + b43_ofdmtab_write16(dev, B43_OFDMTAB_DACRFPABB, i, b43_tab_finefreqg[i]); +} + +static void b43_wa_nft(struct b43_wldev *dev) /* Noise figure table */ +{ + struct b43_phy *phy = &dev->phy; + int i; + + if (phy->type == B43_PHYTYPE_A) { + if (phy->rev == 2) + for (i = 0; i < B43_TAB_NOISEA2_SIZE; i++) + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC2, i, b43_tab_noisea2[i]); + else + for (i = 0; i < B43_TAB_NOISEA3_SIZE; i++) + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC2, i, b43_tab_noisea3[i]); + } else { + if (phy->rev == 1) + for (i = 0; i < B43_TAB_NOISEG1_SIZE; i++) + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC2, i, b43_tab_noiseg1[i]); + else + for (i = 0; i < B43_TAB_NOISEG2_SIZE; i++) + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC2, i, b43_tab_noiseg2[i]); + } +} + +static void b43_wa_rt(struct b43_wldev *dev) /* Rotor table */ +{ + int i; + + for (i = 0; i < B43_TAB_ROTOR_SIZE; i++) + b43_ofdmtab_write32(dev, B43_OFDMTAB_ROTOR, i, b43_tab_rotor[i]); +} + +static void b43_write_null_nst(struct b43_wldev *dev) +{ + int i; + + for (i = 0; i < B43_TAB_NOISESCALE_SIZE; i++) + b43_ofdmtab_write16(dev, B43_OFDMTAB_NOISESCALE, i, 0); +} + +static void b43_write_nst(struct b43_wldev *dev, const u16 *nst) +{ + int i; + + for (i = 0; i < B43_TAB_NOISESCALE_SIZE; i++) + b43_ofdmtab_write16(dev, B43_OFDMTAB_NOISESCALE, i, nst[i]); +} + +static void b43_wa_nst(struct b43_wldev *dev) /* Noise scale table */ +{ + struct b43_phy *phy = &dev->phy; + + if (phy->type == B43_PHYTYPE_A) { + if (phy->rev <= 1) + b43_write_null_nst(dev); + else if (phy->rev == 2) + b43_write_nst(dev, b43_tab_noisescalea2); + else if (phy->rev == 3) + b43_write_nst(dev, b43_tab_noisescalea3); + else + b43_write_nst(dev, b43_tab_noisescaleg3); + } else { + if (phy->rev >= 6) { + if (b43_phy_read(dev, B43_PHY_ENCORE) & B43_PHY_ENCORE_EN) + b43_write_nst(dev, b43_tab_noisescaleg3); + else + b43_write_nst(dev, b43_tab_noisescaleg2); + } else { + b43_write_nst(dev, b43_tab_noisescaleg1); + } + } +} + +static void b43_wa_art(struct b43_wldev *dev) /* ADV retard table */ +{ + int i; + + for (i = 0; i < B43_TAB_RETARD_SIZE; i++) + b43_ofdmtab_write32(dev, B43_OFDMTAB_ADVRETARD, + i, b43_tab_retard[i]); +} + +static void b43_wa_txlna_gain(struct b43_wldev *dev) +{ + b43_ofdmtab_write16(dev, B43_OFDMTAB_DC, 13, 0x0000); +} + +static void b43_wa_crs_reset(struct b43_wldev *dev) +{ + b43_phy_write(dev, 0x002C, 0x0064); +} + +static void b43_wa_2060txlna_gain(struct b43_wldev *dev) +{ + b43_hf_write(dev, b43_hf_read(dev) | + B43_HF_2060W); +} + +static void b43_wa_lms(struct b43_wldev *dev) +{ + b43_phy_maskset(dev, 0x0055, 0xFFC0, 0x0004); +} + +static void b43_wa_mixedsignal(struct b43_wldev *dev) +{ + b43_ofdmtab_write16(dev, B43_OFDMTAB_DAC, 1, 3); +} + +static void b43_wa_msst(struct b43_wldev *dev) /* Min sigma square table */ +{ + struct b43_phy *phy = &dev->phy; + int i; + const u16 *tab; + + if (phy->type == B43_PHYTYPE_A) { + tab = b43_tab_sigmasqr1; + } else if (phy->type == B43_PHYTYPE_G) { + tab = b43_tab_sigmasqr2; + } else { + B43_WARN_ON(1); + return; + } + + for (i = 0; i < B43_TAB_SIGMASQR_SIZE; i++) { + b43_ofdmtab_write16(dev, B43_OFDMTAB_MINSIGSQ, + i, tab[i]); + } +} + +static void b43_wa_iqadc(struct b43_wldev *dev) +{ + if (dev->phy.analog == 4) + b43_ofdmtab_write16(dev, B43_OFDMTAB_DAC, 0, + b43_ofdmtab_read16(dev, B43_OFDMTAB_DAC, 0) & ~0xF000); +} + +static void b43_wa_crs_ed(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + + if (phy->rev == 1) { + b43_phy_write(dev, B43_PHY_CRSTHRES1_R1, 0x4F19); + } else if (phy->rev == 2) { + b43_phy_write(dev, B43_PHY_CRSTHRES1, 0x1861); + b43_phy_write(dev, B43_PHY_CRSTHRES2, 0x0271); + b43_phy_set(dev, B43_PHY_ANTDWELL, 0x0800); + } else { + b43_phy_write(dev, B43_PHY_CRSTHRES1, 0x0098); + b43_phy_write(dev, B43_PHY_CRSTHRES2, 0x0070); + b43_phy_write(dev, B43_PHY_OFDM(0xC9), 0x0080); + b43_phy_set(dev, B43_PHY_ANTDWELL, 0x0800); + } +} + +static void b43_wa_crs_thr(struct b43_wldev *dev) +{ + b43_phy_maskset(dev, B43_PHY_CRS0, ~0x03C0, 0xD000); +} + +static void b43_wa_crs_blank(struct b43_wldev *dev) +{ + b43_phy_write(dev, B43_PHY_OFDM(0x2C), 0x005A); +} + +static void b43_wa_cck_shiftbits(struct b43_wldev *dev) +{ + b43_phy_write(dev, B43_PHY_CCKSHIFTBITS, 0x0026); +} + +static void b43_wa_wrssi_offset(struct b43_wldev *dev) +{ + int i; + + if (dev->phy.rev == 1) { + for (i = 0; i < 16; i++) { + b43_ofdmtab_write16(dev, B43_OFDMTAB_WRSSI_R1, + i, 0x0020); + } + } else { + for (i = 0; i < 32; i++) { + b43_ofdmtab_write16(dev, B43_OFDMTAB_WRSSI, + i, 0x0820); + } + } +} + +static void b43_wa_txpuoff_rxpuon(struct b43_wldev *dev) +{ + b43_ofdmtab_write16(dev, B43_OFDMTAB_UNKNOWN_0F, 2, 15); + b43_ofdmtab_write16(dev, B43_OFDMTAB_UNKNOWN_0F, 3, 20); +} + +static void b43_wa_altagc(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + + if (phy->rev == 1) { + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC1_R1, 0, 254); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC1_R1, 1, 13); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC1_R1, 2, 19); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC1_R1, 3, 25); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC2, 0, 0x2710); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC2, 1, 0x9B83); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC2, 2, 0x9B83); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC2, 3, 0x0F8D); + b43_phy_write(dev, B43_PHY_LMS, 4); + } else { + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC1, 0, 254); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC1, 1, 13); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC1, 2, 19); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC1, 3, 25); + } + + b43_phy_maskset(dev, B43_PHY_CCKSHIFTBITS_WA, 0x00FF, 0x5700); + b43_phy_maskset(dev, B43_PHY_OFDM(0x1A), ~0x007F, 0x000F); + b43_phy_maskset(dev, B43_PHY_OFDM(0x1A), ~0x3F80, 0x2B80); + b43_phy_maskset(dev, B43_PHY_ANTWRSETT, 0xF0FF, 0x0300); + b43_radio_set(dev, 0x7A, 0x0008); + b43_phy_maskset(dev, B43_PHY_N1P1GAIN, ~0x000F, 0x0008); + b43_phy_maskset(dev, B43_PHY_P1P2GAIN, ~0x0F00, 0x0600); + b43_phy_maskset(dev, B43_PHY_N1N2GAIN, ~0x0F00, 0x0700); + b43_phy_maskset(dev, B43_PHY_N1P1GAIN, ~0x0F00, 0x0100); + if (phy->rev == 1) { + b43_phy_maskset(dev, B43_PHY_N1N2GAIN, ~0x000F, 0x0007); + } + b43_phy_maskset(dev, B43_PHY_OFDM(0x88), ~0x00FF, 0x001C); + b43_phy_maskset(dev, B43_PHY_OFDM(0x88), ~0x3F00, 0x0200); + b43_phy_maskset(dev, B43_PHY_OFDM(0x96), ~0x00FF, 0x001C); + b43_phy_maskset(dev, B43_PHY_OFDM(0x89), ~0x00FF, 0x0020); + b43_phy_maskset(dev, B43_PHY_OFDM(0x89), ~0x3F00, 0x0200); + b43_phy_maskset(dev, B43_PHY_OFDM(0x82), ~0x00FF, 0x002E); + b43_phy_maskset(dev, B43_PHY_OFDM(0x96), 0x00FF, 0x1A00); + b43_phy_maskset(dev, B43_PHY_OFDM(0x81), ~0x00FF, 0x0028); + b43_phy_maskset(dev, B43_PHY_OFDM(0x81), 0x00FF, 0x2C00); + if (phy->rev == 1) { + b43_phy_write(dev, B43_PHY_PEAK_COUNT, 0x092B); + b43_phy_maskset(dev, B43_PHY_OFDM(0x1B), ~0x001E, 0x0002); + } else { + b43_phy_mask(dev, B43_PHY_OFDM(0x1B), ~0x001E); + b43_phy_write(dev, B43_PHY_OFDM(0x1F), 0x287A); + b43_phy_maskset(dev, B43_PHY_LPFGAINCTL, ~0x000F, 0x0004); + if (phy->rev >= 6) { + b43_phy_write(dev, B43_PHY_OFDM(0x22), 0x287A); + b43_phy_maskset(dev, B43_PHY_LPFGAINCTL, 0x0FFF, 0x3000); + } + } + b43_phy_maskset(dev, B43_PHY_DIVSRCHIDX, 0x8080, 0x7874); + b43_phy_write(dev, B43_PHY_OFDM(0x8E), 0x1C00); + if (phy->rev == 1) { + b43_phy_maskset(dev, B43_PHY_DIVP1P2GAIN, ~0x0F00, 0x0600); + b43_phy_write(dev, B43_PHY_OFDM(0x8B), 0x005E); + b43_phy_maskset(dev, B43_PHY_ANTWRSETT, ~0x00FF, 0x001E); + b43_phy_write(dev, B43_PHY_OFDM(0x8D), 0x0002); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC3_R1, 0, 0); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC3_R1, 1, 7); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC3_R1, 2, 16); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC3_R1, 3, 28); + } else { + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC3, 0, 0); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC3, 1, 7); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC3, 2, 16); + b43_ofdmtab_write16(dev, B43_OFDMTAB_AGC3, 3, 28); + } + if (phy->rev >= 6) { + b43_phy_mask(dev, B43_PHY_OFDM(0x26), ~0x0003); + b43_phy_mask(dev, B43_PHY_OFDM(0x26), ~0x1000); + } + b43_phy_read(dev, B43_PHY_VERSION_OFDM); /* Dummy read */ +} + +static void b43_wa_tr_ltov(struct b43_wldev *dev) /* TR Lookup Table Original Values */ +{ + b43_gtab_write(dev, B43_GTAB_ORIGTR, 0, 0xC480); +} + +static void b43_wa_cpll_nonpilot(struct b43_wldev *dev) +{ + b43_ofdmtab_write16(dev, B43_OFDMTAB_UNKNOWN_11, 0, 0); + b43_ofdmtab_write16(dev, B43_OFDMTAB_UNKNOWN_11, 1, 0); +} + +static void b43_wa_rssi_adc(struct b43_wldev *dev) +{ + if (dev->phy.analog == 4) + b43_phy_write(dev, 0x00DC, 0x7454); +} + +static void b43_wa_boards_a(struct b43_wldev *dev) +{ + if (dev->dev->board_vendor == SSB_BOARDVENDOR_BCM && + dev->dev->board_type == SSB_BOARD_BU4306 && + dev->dev->board_rev < 0x30) { + b43_phy_write(dev, 0x0010, 0xE000); + b43_phy_write(dev, 0x0013, 0x0140); + b43_phy_write(dev, 0x0014, 0x0280); + } else { + if (dev->dev->board_type == SSB_BOARD_MP4318 && + dev->dev->board_rev < 0x20) { + b43_phy_write(dev, 0x0013, 0x0210); + b43_phy_write(dev, 0x0014, 0x0840); + } else { + b43_phy_write(dev, 0x0013, 0x0140); + b43_phy_write(dev, 0x0014, 0x0280); + } + if (dev->phy.rev <= 4) + b43_phy_write(dev, 0x0010, 0xE000); + else + b43_phy_write(dev, 0x0010, 0x2000); + b43_ofdmtab_write16(dev, B43_OFDMTAB_DC, 1, 0x0039); + b43_ofdmtab_write16(dev, B43_OFDMTAB_UNKNOWN_APHY, 7, 0x0040); + } +} + +static void b43_wa_boards_g(struct b43_wldev *dev) +{ + struct ssb_sprom *sprom = dev->dev->bus_sprom; + struct b43_phy *phy = &dev->phy; + + if (dev->dev->board_vendor != SSB_BOARDVENDOR_BCM || + dev->dev->board_type != SSB_BOARD_BU4306 || + dev->dev->board_rev != 0x17) { + if (phy->rev < 2) { + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAINX_R1, 1, 0x0002); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAINX_R1, 2, 0x0001); + } else { + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAINX, 1, 0x0002); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAINX, 2, 0x0001); + if ((sprom->boardflags_lo & B43_BFL_EXTLNA) && + (phy->rev >= 7)) { + b43_phy_mask(dev, B43_PHY_EXTG(0x11), 0xF7FF); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAINX, 0x0020, 0x0001); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAINX, 0x0021, 0x0001); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAINX, 0x0022, 0x0001); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAINX, 0x0023, 0x0000); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAINX, 0x0000, 0x0000); + b43_ofdmtab_write16(dev, B43_OFDMTAB_GAINX, 0x0003, 0x0002); + } + } + } + if (sprom->boardflags_lo & B43_BFL_FEM) { + b43_phy_write(dev, B43_PHY_GTABCTL, 0x3120); + b43_phy_write(dev, B43_PHY_GTABDATA, 0xC480); + } +} + +void b43_wa_all(struct b43_wldev *dev) +{ + struct b43_phy *phy = &dev->phy; + + if (phy->type == B43_PHYTYPE_A) { + switch (phy->rev) { + case 2: + b43_wa_papd(dev); + b43_wa_auxclipthr(dev); + b43_wa_afcdac(dev); + b43_wa_txdc_offset(dev); + b43_wa_initgains(dev); + b43_wa_divider(dev); + b43_wa_gt(dev); + b43_wa_rssi_lt(dev); + b43_wa_analog(dev); + b43_wa_dac(dev); + b43_wa_fft(dev); + b43_wa_nft(dev); + b43_wa_rt(dev); + b43_wa_nst(dev); + b43_wa_art(dev); + b43_wa_txlna_gain(dev); + b43_wa_crs_reset(dev); + b43_wa_2060txlna_gain(dev); + b43_wa_lms(dev); + break; + case 3: + b43_wa_papd(dev); + b43_wa_mixedsignal(dev); + b43_wa_rssi_lt(dev); + b43_wa_txdc_offset(dev); + b43_wa_initgains(dev); + b43_wa_dac(dev); + b43_wa_nft(dev); + b43_wa_nst(dev); + b43_wa_msst(dev); + b43_wa_analog(dev); + b43_wa_gt(dev); + b43_wa_txpuoff_rxpuon(dev); + b43_wa_txlna_gain(dev); + break; + case 5: + b43_wa_iqadc(dev); + case 6: + b43_wa_papd(dev); + b43_wa_rssi_lt(dev); + b43_wa_txdc_offset(dev); + b43_wa_initgains(dev); + b43_wa_dac(dev); + b43_wa_nft(dev); + b43_wa_nst(dev); + b43_wa_msst(dev); + b43_wa_analog(dev); + b43_wa_gt(dev); + b43_wa_txpuoff_rxpuon(dev); + b43_wa_txlna_gain(dev); + break; + case 7: + b43_wa_iqadc(dev); + b43_wa_papd(dev); + b43_wa_rssi_lt(dev); + b43_wa_txdc_offset(dev); + b43_wa_initgains(dev); + b43_wa_dac(dev); + b43_wa_nft(dev); + b43_wa_nst(dev); + b43_wa_msst(dev); + b43_wa_analog(dev); + b43_wa_gt(dev); + b43_wa_txpuoff_rxpuon(dev); + b43_wa_txlna_gain(dev); + b43_wa_rssi_adc(dev); + default: + B43_WARN_ON(1); + } + b43_wa_boards_a(dev); + } else if (phy->type == B43_PHYTYPE_G) { + switch (phy->rev) { + case 1://XXX review rev1 + b43_wa_crs_ed(dev); + b43_wa_crs_thr(dev); + b43_wa_crs_blank(dev); + b43_wa_cck_shiftbits(dev); + b43_wa_fft(dev); + b43_wa_nft(dev); + b43_wa_rt(dev); + b43_wa_nst(dev); + b43_wa_art(dev); + b43_wa_wrssi_offset(dev); + b43_wa_altagc(dev); + break; + case 2: + case 6: + case 7: + case 8: + case 9: + b43_wa_tr_ltov(dev); + b43_wa_crs_ed(dev); + b43_wa_rssi_lt(dev); + b43_wa_nft(dev); + b43_wa_nst(dev); + b43_wa_msst(dev); + b43_wa_wrssi_offset(dev); + b43_wa_altagc(dev); + b43_wa_analog(dev); + b43_wa_txpuoff_rxpuon(dev); + break; + default: + B43_WARN_ON(1); + } + b43_wa_boards_g(dev); + } else { /* No N PHY support so far, LP PHY is in phy_lp.c */ + B43_WARN_ON(1); + } + + b43_wa_cpll_nonpilot(dev); +} diff --git a/drivers/net/wireless/b43/wa.h b/drivers/net/wireless/b43/wa.h new file mode 100644 index 0000000..e163c5e --- /dev/null +++ b/drivers/net/wireless/b43/wa.h @@ -0,0 +1,7 @@ +#ifndef B43_WA_H_ +#define B43_WA_H_ + +void b43_wa_initgains(struct b43_wldev *dev); +void b43_wa_all(struct b43_wldev *dev); + +#endif /* B43_WA_H_ */ diff --git a/drivers/net/wireless/b43/xmit.c b/drivers/net/wireless/b43/xmit.c new file mode 100644 index 0000000..8cb206a --- /dev/null +++ b/drivers/net/wireless/b43/xmit.c @@ -0,0 +1,942 @@ +/* + + Broadcom B43 wireless driver + + Transmission (TX/RX) related functions. + + Copyright (C) 2005 Martin Langer + Copyright (C) 2005 Stefano Brivio + Copyright (C) 2005, 2006 Michael Buesch + Copyright (C) 2005 Danny van Dyk + Copyright (C) 2005 Andreas Jaggi + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "xmit.h" +#include "phy_common.h" +#include "dma.h" +#include "pio.h" + +static const struct b43_tx_legacy_rate_phy_ctl_entry b43_tx_legacy_rate_phy_ctl[] = { + { B43_CCK_RATE_1MB, 0x0, 0x0 }, + { B43_CCK_RATE_2MB, 0x0, 0x1 }, + { B43_CCK_RATE_5MB, 0x0, 0x2 }, + { B43_CCK_RATE_11MB, 0x0, 0x3 }, + { B43_OFDM_RATE_6MB, B43_TXH_PHY1_CRATE_1_2, B43_TXH_PHY1_MODUL_BPSK }, + { B43_OFDM_RATE_9MB, B43_TXH_PHY1_CRATE_3_4, B43_TXH_PHY1_MODUL_BPSK }, + { B43_OFDM_RATE_12MB, B43_TXH_PHY1_CRATE_1_2, B43_TXH_PHY1_MODUL_QPSK }, + { B43_OFDM_RATE_18MB, B43_TXH_PHY1_CRATE_3_4, B43_TXH_PHY1_MODUL_QPSK }, + { B43_OFDM_RATE_24MB, B43_TXH_PHY1_CRATE_1_2, B43_TXH_PHY1_MODUL_QAM16 }, + { B43_OFDM_RATE_36MB, B43_TXH_PHY1_CRATE_3_4, B43_TXH_PHY1_MODUL_QAM16 }, + { B43_OFDM_RATE_48MB, B43_TXH_PHY1_CRATE_2_3, B43_TXH_PHY1_MODUL_QAM64 }, + { B43_OFDM_RATE_54MB, B43_TXH_PHY1_CRATE_3_4, B43_TXH_PHY1_MODUL_QAM64 }, +}; + +static const struct b43_tx_legacy_rate_phy_ctl_entry * +b43_tx_legacy_rate_phy_ctl_ent(u8 bitrate) +{ + const struct b43_tx_legacy_rate_phy_ctl_entry *e; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(b43_tx_legacy_rate_phy_ctl); i++) { + e = &(b43_tx_legacy_rate_phy_ctl[i]); + if (e->bitrate == bitrate) + return e; + } + + B43_WARN_ON(1); + return NULL; +} + +/* Extract the bitrate index out of a CCK PLCP header. */ +static int b43_plcp_get_bitrate_idx_cck(struct b43_plcp_hdr6 *plcp) +{ + switch (plcp->raw[0]) { + case 0x0A: + return 0; + case 0x14: + return 1; + case 0x37: + return 2; + case 0x6E: + return 3; + } + return -1; +} + +/* Extract the bitrate index out of an OFDM PLCP header. */ +static int b43_plcp_get_bitrate_idx_ofdm(struct b43_plcp_hdr6 *plcp, bool aphy) +{ + int base = aphy ? 0 : 4; + + switch (plcp->raw[0] & 0xF) { + case 0xB: + return base + 0; + case 0xF: + return base + 1; + case 0xA: + return base + 2; + case 0xE: + return base + 3; + case 0x9: + return base + 4; + case 0xD: + return base + 5; + case 0x8: + return base + 6; + case 0xC: + return base + 7; + } + return -1; +} + +u8 b43_plcp_get_ratecode_cck(const u8 bitrate) +{ + switch (bitrate) { + case B43_CCK_RATE_1MB: + return 0x0A; + case B43_CCK_RATE_2MB: + return 0x14; + case B43_CCK_RATE_5MB: + return 0x37; + case B43_CCK_RATE_11MB: + return 0x6E; + } + B43_WARN_ON(1); + return 0; +} + +u8 b43_plcp_get_ratecode_ofdm(const u8 bitrate) +{ + switch (bitrate) { + case B43_OFDM_RATE_6MB: + return 0xB; + case B43_OFDM_RATE_9MB: + return 0xF; + case B43_OFDM_RATE_12MB: + return 0xA; + case B43_OFDM_RATE_18MB: + return 0xE; + case B43_OFDM_RATE_24MB: + return 0x9; + case B43_OFDM_RATE_36MB: + return 0xD; + case B43_OFDM_RATE_48MB: + return 0x8; + case B43_OFDM_RATE_54MB: + return 0xC; + } + B43_WARN_ON(1); + return 0; +} + +void b43_generate_plcp_hdr(struct b43_plcp_hdr4 *plcp, + const u16 octets, const u8 bitrate) +{ + __u8 *raw = plcp->raw; + + if (b43_is_ofdm_rate(bitrate)) { + u32 d; + + d = b43_plcp_get_ratecode_ofdm(bitrate); + B43_WARN_ON(octets & 0xF000); + d |= (octets << 5); + plcp->data = cpu_to_le32(d); + } else { + u32 plen; + + plen = octets * 16 / bitrate; + if ((octets * 16 % bitrate) > 0) { + plen++; + if ((bitrate == B43_CCK_RATE_11MB) + && ((octets * 8 % 11) < 4)) { + raw[1] = 0x84; + } else + raw[1] = 0x04; + } else + raw[1] = 0x04; + plcp->data |= cpu_to_le32(plen << 16); + raw[0] = b43_plcp_get_ratecode_cck(bitrate); + } +} + +/* TODO: verify if needed for SSLPN or LCN */ +static u16 b43_generate_tx_phy_ctl1(struct b43_wldev *dev, u8 bitrate) +{ + const struct b43_phy *phy = &dev->phy; + const struct b43_tx_legacy_rate_phy_ctl_entry *e; + u16 control = 0; + u16 bw; + + if (phy->type == B43_PHYTYPE_LP) + bw = B43_TXH_PHY1_BW_20; + else /* FIXME */ + bw = B43_TXH_PHY1_BW_20; + + if (0) { /* FIXME: MIMO */ + } else if (b43_is_cck_rate(bitrate) && phy->type != B43_PHYTYPE_LP) { + control = bw; + } else { + control = bw; + e = b43_tx_legacy_rate_phy_ctl_ent(bitrate); + if (e) { + control |= e->coding_rate; + control |= e->modulation; + } + control |= B43_TXH_PHY1_MODE_SISO; + } + + return control; +} + +static u8 b43_calc_fallback_rate(u8 bitrate) +{ + switch (bitrate) { + case B43_CCK_RATE_1MB: + return B43_CCK_RATE_1MB; + case B43_CCK_RATE_2MB: + return B43_CCK_RATE_1MB; + case B43_CCK_RATE_5MB: + return B43_CCK_RATE_2MB; + case B43_CCK_RATE_11MB: + return B43_CCK_RATE_5MB; + case B43_OFDM_RATE_6MB: + return B43_CCK_RATE_5MB; + case B43_OFDM_RATE_9MB: + return B43_OFDM_RATE_6MB; + case B43_OFDM_RATE_12MB: + return B43_OFDM_RATE_9MB; + case B43_OFDM_RATE_18MB: + return B43_OFDM_RATE_12MB; + case B43_OFDM_RATE_24MB: + return B43_OFDM_RATE_18MB; + case B43_OFDM_RATE_36MB: + return B43_OFDM_RATE_24MB; + case B43_OFDM_RATE_48MB: + return B43_OFDM_RATE_36MB; + case B43_OFDM_RATE_54MB: + return B43_OFDM_RATE_48MB; + } + B43_WARN_ON(1); + return 0; +} + +/* Generate a TX data header. */ +int b43_generate_txhdr(struct b43_wldev *dev, + u8 *_txhdr, + struct sk_buff *skb_frag, + struct ieee80211_tx_info *info, + u16 cookie) +{ + const unsigned char *fragment_data = skb_frag->data; + unsigned int fragment_len = skb_frag->len; + struct b43_txhdr *txhdr = (struct b43_txhdr *)_txhdr; + const struct b43_phy *phy = &dev->phy; + const struct ieee80211_hdr *wlhdr = + (const struct ieee80211_hdr *)fragment_data; + int use_encryption = !!info->control.hw_key; + __le16 fctl = wlhdr->frame_control; + struct ieee80211_rate *fbrate; + u8 rate, rate_fb; + int rate_ofdm, rate_fb_ofdm; + unsigned int plcp_fragment_len; + u32 mac_ctl = 0; + u16 phy_ctl = 0; + bool fill_phy_ctl1 = (phy->type == B43_PHYTYPE_LP || + phy->type == B43_PHYTYPE_N || + phy->type == B43_PHYTYPE_HT); + u8 extra_ft = 0; + struct ieee80211_rate *txrate; + struct ieee80211_tx_rate *rates; + + memset(txhdr, 0, sizeof(*txhdr)); + + txrate = ieee80211_get_tx_rate(dev->wl->hw, info); + rate = txrate ? txrate->hw_value : B43_CCK_RATE_1MB; + rate_ofdm = b43_is_ofdm_rate(rate); + fbrate = ieee80211_get_alt_retry_rate(dev->wl->hw, info, 0) ? : txrate; + rate_fb = fbrate->hw_value; + rate_fb_ofdm = b43_is_ofdm_rate(rate_fb); + + if (rate_ofdm) + txhdr->phy_rate = b43_plcp_get_ratecode_ofdm(rate); + else + txhdr->phy_rate = b43_plcp_get_ratecode_cck(rate); + txhdr->mac_frame_ctl = wlhdr->frame_control; + memcpy(txhdr->tx_receiver, wlhdr->addr1, 6); + + /* Calculate duration for fallback rate */ + if ((rate_fb == rate) || + (wlhdr->duration_id & cpu_to_le16(0x8000)) || + (wlhdr->duration_id == cpu_to_le16(0))) { + /* If the fallback rate equals the normal rate or the + * dur_id field contains an AID, CFP magic or 0, + * use the original dur_id field. */ + txhdr->dur_fb = wlhdr->duration_id; + } else { + txhdr->dur_fb = ieee80211_generic_frame_duration( + dev->wl->hw, info->control.vif, info->band, + fragment_len, fbrate); + } + + plcp_fragment_len = fragment_len + FCS_LEN; + if (use_encryption) { + u8 key_idx = info->control.hw_key->hw_key_idx; + struct b43_key *key; + int wlhdr_len; + size_t iv_len; + + B43_WARN_ON(key_idx >= ARRAY_SIZE(dev->key)); + key = &(dev->key[key_idx]); + + if (unlikely(!key->keyconf)) { + /* This key is invalid. This might only happen + * in a short timeframe after machine resume before + * we were able to reconfigure keys. + * Drop this packet completely. Do not transmit it + * unencrypted to avoid leaking information. */ + return -ENOKEY; + } + + /* Hardware appends ICV. */ + plcp_fragment_len += info->control.hw_key->icv_len; + + key_idx = b43_kidx_to_fw(dev, key_idx); + mac_ctl |= (key_idx << B43_TXH_MAC_KEYIDX_SHIFT) & + B43_TXH_MAC_KEYIDX; + mac_ctl |= (key->algorithm << B43_TXH_MAC_KEYALG_SHIFT) & + B43_TXH_MAC_KEYALG; + wlhdr_len = ieee80211_hdrlen(fctl); + if (key->algorithm == B43_SEC_ALGO_TKIP) { + u16 phase1key[5]; + int i; + /* we give the phase1key and iv16 here, the key is stored in + * shm. With that the hardware can do phase 2 and encryption. + */ + ieee80211_get_tkip_p1k(info->control.hw_key, skb_frag, phase1key); + /* phase1key is in host endian. Copy to little-endian txhdr->iv. */ + for (i = 0; i < 5; i++) { + txhdr->iv[i * 2 + 0] = phase1key[i]; + txhdr->iv[i * 2 + 1] = phase1key[i] >> 8; + } + /* iv16 */ + memcpy(txhdr->iv + 10, ((u8 *) wlhdr) + wlhdr_len, 3); + } else { + iv_len = min((size_t) info->control.hw_key->iv_len, + ARRAY_SIZE(txhdr->iv)); + memcpy(txhdr->iv, ((u8 *) wlhdr) + wlhdr_len, iv_len); + } + } + switch (dev->fw.hdr_format) { + case B43_FW_HDR_598: + b43_generate_plcp_hdr((struct b43_plcp_hdr4 *)(&txhdr->format_598.plcp), + plcp_fragment_len, rate); + break; + case B43_FW_HDR_351: + b43_generate_plcp_hdr((struct b43_plcp_hdr4 *)(&txhdr->format_351.plcp), + plcp_fragment_len, rate); + break; + case B43_FW_HDR_410: + b43_generate_plcp_hdr((struct b43_plcp_hdr4 *)(&txhdr->format_410.plcp), + plcp_fragment_len, rate); + break; + } + b43_generate_plcp_hdr((struct b43_plcp_hdr4 *)(&txhdr->plcp_fb), + plcp_fragment_len, rate_fb); + + /* Extra Frame Types */ + if (rate_fb_ofdm) + extra_ft |= B43_TXH_EFT_FB_OFDM; + else + extra_ft |= B43_TXH_EFT_FB_CCK; + + /* Set channel radio code. Note that the micrcode ORs 0x100 to + * this value before comparing it to the value in SHM, if this + * is a 5Ghz packet. + */ + txhdr->chan_radio_code = phy->channel; + + /* PHY TX Control word */ + if (rate_ofdm) + phy_ctl |= B43_TXH_PHY_ENC_OFDM; + else + phy_ctl |= B43_TXH_PHY_ENC_CCK; + if (info->control.rates[0].flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE) + phy_ctl |= B43_TXH_PHY_SHORTPRMBL; + + switch (b43_ieee80211_antenna_sanitize(dev, 0)) { + case 0: /* Default */ + phy_ctl |= B43_TXH_PHY_ANT01AUTO; + break; + case 1: /* Antenna 0 */ + phy_ctl |= B43_TXH_PHY_ANT0; + break; + case 2: /* Antenna 1 */ + phy_ctl |= B43_TXH_PHY_ANT1; + break; + case 3: /* Antenna 2 */ + phy_ctl |= B43_TXH_PHY_ANT2; + break; + case 4: /* Antenna 3 */ + phy_ctl |= B43_TXH_PHY_ANT3; + break; + default: + B43_WARN_ON(1); + } + + rates = info->control.rates; + /* MAC control */ + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) + mac_ctl |= B43_TXH_MAC_ACK; + /* use hardware sequence counter as the non-TID counter */ + if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) + mac_ctl |= B43_TXH_MAC_HWSEQ; + if (info->flags & IEEE80211_TX_CTL_FIRST_FRAGMENT) + mac_ctl |= B43_TXH_MAC_STMSDU; + if (phy->type == B43_PHYTYPE_A) + mac_ctl |= B43_TXH_MAC_5GHZ; + + /* Overwrite rates[0].count to make the retry calculation + * in the tx status easier. need the actual retry limit to + * detect whether the fallback rate was used. + */ + if ((rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS) || + (rates[0].count <= dev->wl->hw->conf.long_frame_max_tx_count)) { + rates[0].count = dev->wl->hw->conf.long_frame_max_tx_count; + mac_ctl |= B43_TXH_MAC_LONGFRAME; + } else { + rates[0].count = dev->wl->hw->conf.short_frame_max_tx_count; + } + + /* Generate the RTS or CTS-to-self frame */ + if ((rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS) || + (rates[0].flags & IEEE80211_TX_RC_USE_CTS_PROTECT)) { + unsigned int len; + struct ieee80211_hdr *uninitialized_var(hdr); + int rts_rate, rts_rate_fb; + int rts_rate_ofdm, rts_rate_fb_ofdm; + struct b43_plcp_hdr6 *uninitialized_var(plcp); + struct ieee80211_rate *rts_cts_rate; + + rts_cts_rate = ieee80211_get_rts_cts_rate(dev->wl->hw, info); + + rts_rate = rts_cts_rate ? rts_cts_rate->hw_value : B43_CCK_RATE_1MB; + rts_rate_ofdm = b43_is_ofdm_rate(rts_rate); + rts_rate_fb = b43_calc_fallback_rate(rts_rate); + rts_rate_fb_ofdm = b43_is_ofdm_rate(rts_rate_fb); + + if (rates[0].flags & IEEE80211_TX_RC_USE_CTS_PROTECT) { + struct ieee80211_cts *uninitialized_var(cts); + + switch (dev->fw.hdr_format) { + case B43_FW_HDR_598: + cts = (struct ieee80211_cts *) + (txhdr->format_598.rts_frame); + break; + case B43_FW_HDR_351: + cts = (struct ieee80211_cts *) + (txhdr->format_351.rts_frame); + break; + case B43_FW_HDR_410: + cts = (struct ieee80211_cts *) + (txhdr->format_410.rts_frame); + break; + } + ieee80211_ctstoself_get(dev->wl->hw, info->control.vif, + fragment_data, fragment_len, + info, cts); + mac_ctl |= B43_TXH_MAC_SENDCTS; + len = sizeof(struct ieee80211_cts); + } else { + struct ieee80211_rts *uninitialized_var(rts); + + switch (dev->fw.hdr_format) { + case B43_FW_HDR_598: + rts = (struct ieee80211_rts *) + (txhdr->format_598.rts_frame); + break; + case B43_FW_HDR_351: + rts = (struct ieee80211_rts *) + (txhdr->format_351.rts_frame); + break; + case B43_FW_HDR_410: + rts = (struct ieee80211_rts *) + (txhdr->format_410.rts_frame); + break; + } + ieee80211_rts_get(dev->wl->hw, info->control.vif, + fragment_data, fragment_len, + info, rts); + mac_ctl |= B43_TXH_MAC_SENDRTS; + len = sizeof(struct ieee80211_rts); + } + len += FCS_LEN; + + /* Generate the PLCP headers for the RTS/CTS frame */ + switch (dev->fw.hdr_format) { + case B43_FW_HDR_598: + plcp = &txhdr->format_598.rts_plcp; + break; + case B43_FW_HDR_351: + plcp = &txhdr->format_351.rts_plcp; + break; + case B43_FW_HDR_410: + plcp = &txhdr->format_410.rts_plcp; + break; + } + b43_generate_plcp_hdr((struct b43_plcp_hdr4 *)plcp, + len, rts_rate); + plcp = &txhdr->rts_plcp_fb; + b43_generate_plcp_hdr((struct b43_plcp_hdr4 *)plcp, + len, rts_rate_fb); + + switch (dev->fw.hdr_format) { + case B43_FW_HDR_598: + hdr = (struct ieee80211_hdr *) + (&txhdr->format_598.rts_frame); + break; + case B43_FW_HDR_351: + hdr = (struct ieee80211_hdr *) + (&txhdr->format_351.rts_frame); + break; + case B43_FW_HDR_410: + hdr = (struct ieee80211_hdr *) + (&txhdr->format_410.rts_frame); + break; + } + txhdr->rts_dur_fb = hdr->duration_id; + + if (rts_rate_ofdm) { + extra_ft |= B43_TXH_EFT_RTS_OFDM; + txhdr->phy_rate_rts = + b43_plcp_get_ratecode_ofdm(rts_rate); + } else { + extra_ft |= B43_TXH_EFT_RTS_CCK; + txhdr->phy_rate_rts = + b43_plcp_get_ratecode_cck(rts_rate); + } + if (rts_rate_fb_ofdm) + extra_ft |= B43_TXH_EFT_RTSFB_OFDM; + else + extra_ft |= B43_TXH_EFT_RTSFB_CCK; + + if (rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS && + fill_phy_ctl1) { + txhdr->phy_ctl1_rts = cpu_to_le16( + b43_generate_tx_phy_ctl1(dev, rts_rate)); + txhdr->phy_ctl1_rts_fb = cpu_to_le16( + b43_generate_tx_phy_ctl1(dev, rts_rate_fb)); + } + } + + /* Magic cookie */ + switch (dev->fw.hdr_format) { + case B43_FW_HDR_598: + txhdr->format_598.cookie = cpu_to_le16(cookie); + break; + case B43_FW_HDR_351: + txhdr->format_351.cookie = cpu_to_le16(cookie); + break; + case B43_FW_HDR_410: + txhdr->format_410.cookie = cpu_to_le16(cookie); + break; + } + + if (fill_phy_ctl1) { + txhdr->phy_ctl1 = + cpu_to_le16(b43_generate_tx_phy_ctl1(dev, rate)); + txhdr->phy_ctl1_fb = + cpu_to_le16(b43_generate_tx_phy_ctl1(dev, rate_fb)); + } + + /* Apply the bitfields */ + txhdr->mac_ctl = cpu_to_le32(mac_ctl); + txhdr->phy_ctl = cpu_to_le16(phy_ctl); + txhdr->extra_ft = extra_ft; + + return 0; +} + +static s8 b43_rssi_postprocess(struct b43_wldev *dev, + u8 in_rssi, int ofdm, + int adjust_2053, int adjust_2050) +{ + struct b43_phy *phy = &dev->phy; + struct b43_phy_g *gphy = phy->g; + s32 tmp; + + switch (phy->radio_ver) { + case 0x2050: + if (ofdm) { + tmp = in_rssi; + if (tmp > 127) + tmp -= 256; + tmp *= 73; + tmp /= 64; + if (adjust_2050) + tmp += 25; + else + tmp -= 3; + } else { + if (dev->dev->bus_sprom-> + boardflags_lo & B43_BFL_RSSI) { + if (in_rssi > 63) + in_rssi = 63; + B43_WARN_ON(phy->type != B43_PHYTYPE_G); + tmp = gphy->nrssi_lt[in_rssi]; + tmp = 31 - tmp; + tmp *= -131; + tmp /= 128; + tmp -= 57; + } else { + tmp = in_rssi; + tmp = 31 - tmp; + tmp *= -149; + tmp /= 128; + tmp -= 68; + } + if (phy->type == B43_PHYTYPE_G && adjust_2050) + tmp += 25; + } + break; + case 0x2060: + if (in_rssi > 127) + tmp = in_rssi - 256; + else + tmp = in_rssi; + break; + default: + tmp = in_rssi; + tmp -= 11; + tmp *= 103; + tmp /= 64; + if (adjust_2053) + tmp -= 109; + else + tmp -= 83; + } + + return (s8) tmp; +} + +//TODO +#if 0 +static s8 b43_rssinoise_postprocess(struct b43_wldev *dev, u8 in_rssi) +{ + struct b43_phy *phy = &dev->phy; + s8 ret; + + if (phy->type == B43_PHYTYPE_A) { + //TODO: Incomplete specs. + ret = 0; + } else + ret = b43_rssi_postprocess(dev, in_rssi, 0, 1, 1); + + return ret; +} +#endif + +void b43_rx(struct b43_wldev *dev, struct sk_buff *skb, const void *_rxhdr) +{ + struct ieee80211_rx_status status; + struct b43_plcp_hdr6 *plcp; + struct ieee80211_hdr *wlhdr; + const struct b43_rxhdr_fw4 *rxhdr = _rxhdr; + __le16 fctl; + u16 phystat0, phystat3; + u16 uninitialized_var(chanstat), uninitialized_var(mactime); + u32 uninitialized_var(macstat); + u16 chanid; + u16 phytype; + int padding, rate_idx; + + memset(&status, 0, sizeof(status)); + + /* Get metadata about the frame from the header. */ + phystat0 = le16_to_cpu(rxhdr->phy_status0); + phystat3 = le16_to_cpu(rxhdr->phy_status3); + switch (dev->fw.hdr_format) { + case B43_FW_HDR_598: + macstat = le32_to_cpu(rxhdr->format_598.mac_status); + mactime = le16_to_cpu(rxhdr->format_598.mac_time); + chanstat = le16_to_cpu(rxhdr->format_598.channel); + break; + case B43_FW_HDR_410: + case B43_FW_HDR_351: + macstat = le32_to_cpu(rxhdr->format_351.mac_status); + mactime = le16_to_cpu(rxhdr->format_351.mac_time); + chanstat = le16_to_cpu(rxhdr->format_351.channel); + break; + } + phytype = chanstat & B43_RX_CHAN_PHYTYPE; + + if (unlikely(macstat & B43_RX_MAC_FCSERR)) { + dev->wl->ieee_stats.dot11FCSErrorCount++; + status.flag |= RX_FLAG_FAILED_FCS_CRC; + } + if (unlikely(phystat0 & (B43_RX_PHYST0_PLCPHCF | B43_RX_PHYST0_PLCPFV))) + status.flag |= RX_FLAG_FAILED_PLCP_CRC; + if (phystat0 & B43_RX_PHYST0_SHORTPRMBL) + status.flag |= RX_FLAG_SHORTPRE; + if (macstat & B43_RX_MAC_DECERR) { + /* Decryption with the given key failed. + * Drop the packet. We also won't be able to decrypt it with + * the key in software. */ + goto drop; + } + + /* Skip PLCP and padding */ + padding = (macstat & B43_RX_MAC_PADDING) ? 2 : 0; + if (unlikely(skb->len < (sizeof(struct b43_plcp_hdr6) + padding))) { + b43dbg(dev->wl, "RX: Packet size underrun (1)\n"); + goto drop; + } + plcp = (struct b43_plcp_hdr6 *)(skb->data + padding); + skb_pull(skb, sizeof(struct b43_plcp_hdr6) + padding); + /* The skb contains the Wireless Header + payload data now */ + if (unlikely(skb->len < (2 + 2 + 6 /*minimum hdr */ + FCS_LEN))) { + b43dbg(dev->wl, "RX: Packet size underrun (2)\n"); + goto drop; + } + wlhdr = (struct ieee80211_hdr *)(skb->data); + fctl = wlhdr->frame_control; + + if (macstat & B43_RX_MAC_DEC) { + unsigned int keyidx; + int wlhdr_len; + + keyidx = ((macstat & B43_RX_MAC_KEYIDX) + >> B43_RX_MAC_KEYIDX_SHIFT); + /* We must adjust the key index here. We want the "physical" + * key index, but the ucode passed it slightly different. + */ + keyidx = b43_kidx_to_raw(dev, keyidx); + B43_WARN_ON(keyidx >= ARRAY_SIZE(dev->key)); + + if (dev->key[keyidx].algorithm != B43_SEC_ALGO_NONE) { + wlhdr_len = ieee80211_hdrlen(fctl); + if (unlikely(skb->len < (wlhdr_len + 3))) { + b43dbg(dev->wl, + "RX: Packet size underrun (3)\n"); + goto drop; + } + status.flag |= RX_FLAG_DECRYPTED; + } + } + + /* Link quality statistics */ + switch (chanstat & B43_RX_CHAN_PHYTYPE) { + case B43_PHYTYPE_HT: + /* TODO: is max the right choice? */ + status.signal = max_t(__s8, + max(rxhdr->phy_ht_power0, rxhdr->phy_ht_power1), + rxhdr->phy_ht_power2); + break; + case B43_PHYTYPE_N: + /* Broadcom has code for min and avg, but always uses max */ + if (rxhdr->power0 == 16 || rxhdr->power0 == 32) + status.signal = max(rxhdr->power1, rxhdr->power2); + else + status.signal = max(rxhdr->power0, rxhdr->power1); + break; + case B43_PHYTYPE_A: + case B43_PHYTYPE_B: + case B43_PHYTYPE_G: + case B43_PHYTYPE_LP: + status.signal = b43_rssi_postprocess(dev, rxhdr->jssi, + (phystat0 & B43_RX_PHYST0_OFDM), + (phystat0 & B43_RX_PHYST0_GAINCTL), + (phystat3 & B43_RX_PHYST3_TRSTATE)); + break; + } + + if (phystat0 & B43_RX_PHYST0_OFDM) + rate_idx = b43_plcp_get_bitrate_idx_ofdm(plcp, + phytype == B43_PHYTYPE_A); + else + rate_idx = b43_plcp_get_bitrate_idx_cck(plcp); + if (unlikely(rate_idx == -1)) { + /* PLCP seems to be corrupted. + * Drop the frame, if we are not interested in corrupted frames. */ + if (!(dev->wl->filter_flags & FIF_PLCPFAIL)) + goto drop; + } + status.rate_idx = rate_idx; + status.antenna = !!(phystat0 & B43_RX_PHYST0_ANT); + + /* + * All frames on monitor interfaces and beacons always need a full + * 64-bit timestamp. Monitor interfaces need it for diagnostic + * purposes and beacons for IBSS merging. + * This code assumes we get to process the packet within 16 bits + * of timestamp, i.e. about 65 milliseconds after the PHY received + * the first symbol. + */ + if (ieee80211_is_beacon(fctl) || dev->wl->radiotap_enabled) { + u16 low_mactime_now; + + b43_tsf_read(dev, &status.mactime); + low_mactime_now = status.mactime; + status.mactime = status.mactime & ~0xFFFFULL; + status.mactime += mactime; + if (low_mactime_now <= mactime) + status.mactime -= 0x10000; + status.flag |= RX_FLAG_MACTIME_START; + } + + chanid = (chanstat & B43_RX_CHAN_ID) >> B43_RX_CHAN_ID_SHIFT; + switch (chanstat & B43_RX_CHAN_PHYTYPE) { + case B43_PHYTYPE_A: + status.band = IEEE80211_BAND_5GHZ; + B43_WARN_ON(1); + /* FIXME: We don't really know which value the "chanid" contains. + * So the following assignment might be wrong. */ + status.freq = b43_channel_to_freq_5ghz(chanid); + break; + case B43_PHYTYPE_G: + status.band = IEEE80211_BAND_2GHZ; + /* chanid is the radio channel cookie value as used + * to tune the radio. */ + status.freq = chanid + 2400; + break; + case B43_PHYTYPE_N: + case B43_PHYTYPE_LP: + case B43_PHYTYPE_HT: + /* chanid is the SHM channel cookie. Which is the plain + * channel number in b43. */ + if (chanstat & B43_RX_CHAN_5GHZ) { + status.band = IEEE80211_BAND_5GHZ; + status.freq = b43_freq_to_channel_5ghz(chanid); + } else { + status.band = IEEE80211_BAND_2GHZ; + status.freq = b43_freq_to_channel_2ghz(chanid); + } + break; + default: + B43_WARN_ON(1); + goto drop; + } + + memcpy(IEEE80211_SKB_RXCB(skb), &status, sizeof(status)); + ieee80211_rx_ni(dev->wl->hw, skb); + +#if B43_DEBUG + dev->rx_count++; +#endif + return; +drop: + dev_kfree_skb_any(skb); +} + +void b43_handle_txstatus(struct b43_wldev *dev, + const struct b43_txstatus *status) +{ + b43_debugfs_log_txstat(dev, status); + + if (status->intermediate) + return; + if (status->for_ampdu) + return; + if (!status->acked) + dev->wl->ieee_stats.dot11ACKFailureCount++; + if (status->rts_count) { + if (status->rts_count == 0xF) //FIXME + dev->wl->ieee_stats.dot11RTSFailureCount++; + else + dev->wl->ieee_stats.dot11RTSSuccessCount++; + } + + if (b43_using_pio_transfers(dev)) + b43_pio_handle_txstatus(dev, status); + else + b43_dma_handle_txstatus(dev, status); + + b43_phy_txpower_check(dev, 0); +} + +/* Fill out the mac80211 TXstatus report based on the b43-specific + * txstatus report data. This returns a boolean whether the frame was + * successfully transmitted. */ +bool b43_fill_txstatus_report(struct b43_wldev *dev, + struct ieee80211_tx_info *report, + const struct b43_txstatus *status) +{ + bool frame_success = true; + int retry_limit; + + /* preserve the confiured retry limit before clearing the status + * The xmit function has overwritten the rc's value with the actual + * retry limit done by the hardware */ + retry_limit = report->status.rates[0].count; + ieee80211_tx_info_clear_status(report); + + if (status->acked) { + /* The frame was ACKed. */ + report->flags |= IEEE80211_TX_STAT_ACK; + } else { + /* The frame was not ACKed... */ + if (!(report->flags & IEEE80211_TX_CTL_NO_ACK)) { + /* ...but we expected an ACK. */ + frame_success = false; + } + } + if (status->frame_count == 0) { + /* The frame was not transmitted at all. */ + report->status.rates[0].count = 0; + } else if (status->rts_count > dev->wl->hw->conf.short_frame_max_tx_count) { + /* + * If the short retries (RTS, not data frame) have exceeded + * the limit, the hw will not have tried the selected rate, + * but will have used the fallback rate instead. + * Don't let the rate control count attempts for the selected + * rate in this case, otherwise the statistics will be off. + */ + report->status.rates[0].count = 0; + report->status.rates[1].count = status->frame_count; + } else { + if (status->frame_count > retry_limit) { + report->status.rates[0].count = retry_limit; + report->status.rates[1].count = status->frame_count - + retry_limit; + + } else { + report->status.rates[0].count = status->frame_count; + report->status.rates[1].idx = -1; + } + } + + return frame_success; +} + +/* Stop any TX operation on the device (suspend the hardware queues) */ +void b43_tx_suspend(struct b43_wldev *dev) +{ + if (b43_using_pio_transfers(dev)) + b43_pio_tx_suspend(dev); + else + b43_dma_tx_suspend(dev); +} + +/* Resume any TX operation on the device (resume the hardware queues) */ +void b43_tx_resume(struct b43_wldev *dev) +{ + if (b43_using_pio_transfers(dev)) + b43_pio_tx_resume(dev); + else + b43_dma_tx_resume(dev); +} diff --git a/drivers/net/wireless/b43/xmit.h b/drivers/net/wireless/b43/xmit.h new file mode 100644 index 0000000..98d9074 --- /dev/null +++ b/drivers/net/wireless/b43/xmit.h @@ -0,0 +1,412 @@ +#ifndef B43_XMIT_H_ +#define B43_XMIT_H_ + +#include "main.h" +#include + + +#define _b43_declare_plcp_hdr(size) \ + struct b43_plcp_hdr##size { \ + union { \ + __le32 data; \ + __u8 raw[size]; \ + } __packed; \ + } __packed + +/* struct b43_plcp_hdr4 */ +_b43_declare_plcp_hdr(4); +/* struct b43_plcp_hdr6 */ +_b43_declare_plcp_hdr(6); + +#undef _b43_declare_plcp_hdr + +/* TX header for v4 firmware */ +struct b43_txhdr { + __le32 mac_ctl; /* MAC TX control */ + __le16 mac_frame_ctl; /* Copy of the FrameControl field */ + __le16 tx_fes_time_norm; /* TX FES Time Normal */ + __le16 phy_ctl; /* PHY TX control */ + __le16 phy_ctl1; /* PHY TX control word 1 */ + __le16 phy_ctl1_fb; /* PHY TX control word 1 for fallback rates */ + __le16 phy_ctl1_rts; /* PHY TX control word 1 RTS */ + __le16 phy_ctl1_rts_fb; /* PHY TX control word 1 RTS for fallback rates */ + __u8 phy_rate; /* PHY rate */ + __u8 phy_rate_rts; /* PHY rate for RTS/CTS */ + __u8 extra_ft; /* Extra Frame Types */ + __u8 chan_radio_code; /* Channel Radio Code */ + __u8 iv[16]; /* Encryption IV */ + __u8 tx_receiver[6]; /* TX Frame Receiver address */ + __le16 tx_fes_time_fb; /* TX FES Time Fallback */ + struct b43_plcp_hdr6 rts_plcp_fb; /* RTS fallback PLCP header */ + __le16 rts_dur_fb; /* RTS fallback duration */ + struct b43_plcp_hdr6 plcp_fb; /* Fallback PLCP header */ + __le16 dur_fb; /* Fallback duration */ + __le16 mimo_modelen; /* MIMO mode length */ + __le16 mimo_ratelen_fb; /* MIMO fallback rate length */ + __le32 timeout; /* Timeout */ + + union { + /* Tested with 598.314, 644.1001 and 666.2 */ + struct { + __le16 mimo_antenna; /* MIMO antenna select */ + __le16 preload_size; /* Preload size */ + PAD_BYTES(2); + __le16 cookie; /* TX frame cookie */ + __le16 tx_status; /* TX status */ + __le16 max_n_mpdus; + __le16 max_a_bytes_mrt; + __le16 max_a_bytes_fbr; + __le16 min_m_bytes; + struct b43_plcp_hdr6 rts_plcp; /* RTS PLCP header */ + __u8 rts_frame[16]; /* The RTS frame (if used) */ + PAD_BYTES(2); + struct b43_plcp_hdr6 plcp; /* Main PLCP header */ + } format_598 __packed; + + /* Tested with 410.2160, 478.104 and 508.* */ + struct { + __le16 mimo_antenna; /* MIMO antenna select */ + __le16 preload_size; /* Preload size */ + PAD_BYTES(2); + __le16 cookie; /* TX frame cookie */ + __le16 tx_status; /* TX status */ + struct b43_plcp_hdr6 rts_plcp; /* RTS PLCP header */ + __u8 rts_frame[16]; /* The RTS frame (if used) */ + PAD_BYTES(2); + struct b43_plcp_hdr6 plcp; /* Main PLCP header */ + } format_410 __packed; + + /* Tested with 351.126 */ + struct { + PAD_BYTES(2); + __le16 cookie; /* TX frame cookie */ + __le16 tx_status; /* TX status */ + struct b43_plcp_hdr6 rts_plcp; /* RTS PLCP header */ + __u8 rts_frame[16]; /* The RTS frame (if used) */ + PAD_BYTES(2); + struct b43_plcp_hdr6 plcp; /* Main PLCP header */ + } format_351 __packed; + + } __packed; +} __packed; + +struct b43_tx_legacy_rate_phy_ctl_entry { + u8 bitrate; + u16 coding_rate; + u16 modulation; +}; + +/* MAC TX control */ +#define B43_TXH_MAC_USEFBR 0x10000000 /* Use fallback rate for this AMPDU */ +#define B43_TXH_MAC_KEYIDX 0x0FF00000 /* Security key index */ +#define B43_TXH_MAC_KEYIDX_SHIFT 20 +#define B43_TXH_MAC_KEYALG 0x00070000 /* Security key algorithm */ +#define B43_TXH_MAC_KEYALG_SHIFT 16 +#define B43_TXH_MAC_AMIC 0x00008000 /* AMIC */ +#define B43_TXH_MAC_RIFS 0x00004000 /* Use RIFS */ +#define B43_TXH_MAC_LIFETIME 0x00002000 /* Lifetime */ +#define B43_TXH_MAC_FRAMEBURST 0x00001000 /* Frameburst */ +#define B43_TXH_MAC_SENDCTS 0x00000800 /* Send CTS-to-self */ +#define B43_TXH_MAC_AMPDU 0x00000600 /* AMPDU status */ +#define B43_TXH_MAC_AMPDU_MPDU 0x00000000 /* Regular MPDU, not an AMPDU */ +#define B43_TXH_MAC_AMPDU_FIRST 0x00000200 /* First MPDU or AMPDU */ +#define B43_TXH_MAC_AMPDU_INTER 0x00000400 /* Intermediate MPDU or AMPDU */ +#define B43_TXH_MAC_AMPDU_LAST 0x00000600 /* Last (or only) MPDU of AMPDU */ +#define B43_TXH_MAC_40MHZ 0x00000100 /* Use 40 MHz bandwidth */ +#define B43_TXH_MAC_5GHZ 0x00000080 /* 5GHz band */ +#define B43_TXH_MAC_DFCS 0x00000040 /* DFCS */ +#define B43_TXH_MAC_IGNPMQ 0x00000020 /* Ignore PMQ */ +#define B43_TXH_MAC_HWSEQ 0x00000010 /* Use Hardware Sequence Number */ +#define B43_TXH_MAC_STMSDU 0x00000008 /* Start MSDU */ +#define B43_TXH_MAC_SENDRTS 0x00000004 /* Send RTS */ +#define B43_TXH_MAC_LONGFRAME 0x00000002 /* Long frame */ +#define B43_TXH_MAC_ACK 0x00000001 /* Immediate ACK */ + +/* Extra Frame Types */ +#define B43_TXH_EFT_FB 0x03 /* Data frame fallback encoding */ +#define B43_TXH_EFT_FB_CCK 0x00 /* CCK */ +#define B43_TXH_EFT_FB_OFDM 0x01 /* OFDM */ +#define B43_TXH_EFT_FB_EWC 0x02 /* EWC */ +#define B43_TXH_EFT_FB_N 0x03 /* N */ +#define B43_TXH_EFT_RTS 0x0C /* RTS/CTS encoding */ +#define B43_TXH_EFT_RTS_CCK 0x00 /* CCK */ +#define B43_TXH_EFT_RTS_OFDM 0x04 /* OFDM */ +#define B43_TXH_EFT_RTS_EWC 0x08 /* EWC */ +#define B43_TXH_EFT_RTS_N 0x0C /* N */ +#define B43_TXH_EFT_RTSFB 0x30 /* RTS/CTS fallback encoding */ +#define B43_TXH_EFT_RTSFB_CCK 0x00 /* CCK */ +#define B43_TXH_EFT_RTSFB_OFDM 0x10 /* OFDM */ +#define B43_TXH_EFT_RTSFB_EWC 0x20 /* EWC */ +#define B43_TXH_EFT_RTSFB_N 0x30 /* N */ + +/* PHY TX control word */ +#define B43_TXH_PHY_ENC 0x0003 /* Data frame encoding */ +#define B43_TXH_PHY_ENC_CCK 0x0000 /* CCK */ +#define B43_TXH_PHY_ENC_OFDM 0x0001 /* OFDM */ +#define B43_TXH_PHY_ENC_EWC 0x0002 /* EWC */ +#define B43_TXH_PHY_ENC_N 0x0003 /* N */ +#define B43_TXH_PHY_SHORTPRMBL 0x0010 /* Use short preamble */ +#define B43_TXH_PHY_ANT 0x03C0 /* Antenna selection */ +#define B43_TXH_PHY_ANT0 0x0000 /* Use antenna 0 */ +#define B43_TXH_PHY_ANT1 0x0040 /* Use antenna 1 */ +#define B43_TXH_PHY_ANT01AUTO 0x00C0 /* Use antenna 0/1 auto */ +#define B43_TXH_PHY_ANT2 0x0100 /* Use antenna 2 */ +#define B43_TXH_PHY_ANT3 0x0200 /* Use antenna 3 */ +#define B43_TXH_PHY_TXPWR 0xFC00 /* TX power */ +#define B43_TXH_PHY_TXPWR_SHIFT 10 + +/* PHY TX control word 1 */ +#define B43_TXH_PHY1_BW 0x0007 /* Bandwidth */ +#define B43_TXH_PHY1_BW_10 0x0000 /* 10 MHz */ +#define B43_TXH_PHY1_BW_10U 0x0001 /* 10 MHz upper */ +#define B43_TXH_PHY1_BW_20 0x0002 /* 20 MHz */ +#define B43_TXH_PHY1_BW_20U 0x0003 /* 20 MHz upper */ +#define B43_TXH_PHY1_BW_40 0x0004 /* 40 MHz */ +#define B43_TXH_PHY1_BW_40DUP 0x0005 /* 50 MHz duplicate */ +#define B43_TXH_PHY1_MODE 0x0038 /* Mode */ +#define B43_TXH_PHY1_MODE_SISO 0x0000 /* SISO */ +#define B43_TXH_PHY1_MODE_CDD 0x0008 /* CDD */ +#define B43_TXH_PHY1_MODE_STBC 0x0010 /* STBC */ +#define B43_TXH_PHY1_MODE_SDM 0x0018 /* SDM */ +#define B43_TXH_PHY1_CRATE 0x0700 /* Coding rate */ +#define B43_TXH_PHY1_CRATE_1_2 0x0000 /* 1/2 */ +#define B43_TXH_PHY1_CRATE_2_3 0x0100 /* 2/3 */ +#define B43_TXH_PHY1_CRATE_3_4 0x0200 /* 3/4 */ +#define B43_TXH_PHY1_CRATE_4_5 0x0300 /* 4/5 */ +#define B43_TXH_PHY1_CRATE_5_6 0x0400 /* 5/6 */ +#define B43_TXH_PHY1_CRATE_7_8 0x0600 /* 7/8 */ +#define B43_TXH_PHY1_MODUL 0x3800 /* Modulation scheme */ +#define B43_TXH_PHY1_MODUL_BPSK 0x0000 /* BPSK */ +#define B43_TXH_PHY1_MODUL_QPSK 0x0800 /* QPSK */ +#define B43_TXH_PHY1_MODUL_QAM16 0x1000 /* QAM16 */ +#define B43_TXH_PHY1_MODUL_QAM64 0x1800 /* QAM64 */ +#define B43_TXH_PHY1_MODUL_QAM256 0x2000 /* QAM256 */ + + +static inline +size_t b43_txhdr_size(struct b43_wldev *dev) +{ + switch (dev->fw.hdr_format) { + case B43_FW_HDR_598: + return 112 + sizeof(struct b43_plcp_hdr6); + case B43_FW_HDR_410: + return 104 + sizeof(struct b43_plcp_hdr6); + case B43_FW_HDR_351: + return 100 + sizeof(struct b43_plcp_hdr6); + } + return 0; +} + + +int b43_generate_txhdr(struct b43_wldev *dev, + u8 * txhdr, + struct sk_buff *skb_frag, + struct ieee80211_tx_info *txctl, u16 cookie); + +/* Transmit Status */ +struct b43_txstatus { + u16 cookie; /* The cookie from the txhdr */ + u16 seq; /* Sequence number */ + u8 phy_stat; /* PHY TX status */ + u8 frame_count; /* Frame transmit count */ + u8 rts_count; /* RTS transmit count */ + u8 supp_reason; /* Suppression reason */ + /* flags */ + u8 pm_indicated; /* PM mode indicated to AP */ + u8 intermediate; /* Intermediate status notification (not final) */ + u8 for_ampdu; /* Status is for an AMPDU (afterburner) */ + u8 acked; /* Wireless ACK received */ +}; + +/* txstatus supp_reason values */ +enum { + B43_TXST_SUPP_NONE, /* Not suppressed */ + B43_TXST_SUPP_PMQ, /* Suppressed due to PMQ entry */ + B43_TXST_SUPP_FLUSH, /* Suppressed due to flush request */ + B43_TXST_SUPP_PREV, /* Previous fragment failed */ + B43_TXST_SUPP_CHAN, /* Channel mismatch */ + B43_TXST_SUPP_LIFE, /* Lifetime expired */ + B43_TXST_SUPP_UNDER, /* Buffer underflow */ + B43_TXST_SUPP_ABNACK, /* Afterburner NACK */ +}; + +/* Receive header for v4 firmware. */ +struct b43_rxhdr_fw4 { + __le16 frame_len; /* Frame length */ + PAD_BYTES(2); + __le16 phy_status0; /* PHY RX Status 0 */ + union { + /* RSSI for A/B/G-PHYs */ + struct { + __u8 jssi; /* PHY RX Status 1: JSSI */ + __u8 sig_qual; /* PHY RX Status 1: Signal Quality */ + } __packed; + + /* RSSI for N-PHYs */ + struct { + __s8 power0; /* PHY RX Status 1: Power 0 */ + __s8 power1; /* PHY RX Status 1: Power 1 */ + } __packed; + } __packed; + union { + /* HT-PHY */ + struct { + PAD_BYTES(1); + __s8 phy_ht_power0; + } __packed; + + /* RSSI for N-PHYs */ + struct { + __s8 power2; + PAD_BYTES(1); + } __packed; + + __le16 phy_status2; /* PHY RX Status 2 */ + } __packed; + union { + /* HT-PHY */ + struct { + __s8 phy_ht_power1; + __s8 phy_ht_power2; + } __packed; + + __le16 phy_status3; /* PHY RX Status 3 */ + } __packed; + union { + /* Tested with 598.314, 644.1001 and 666.2 */ + struct { + __le16 phy_status4; /* PHY RX Status 4 */ + __le16 phy_status5; /* PHY RX Status 5 */ + __le32 mac_status; /* MAC RX status */ + __le16 mac_time; + __le16 channel; + } format_598 __packed; + + /* Tested with 351.126, 410.2160, 478.104 and 508.* */ + struct { + __le32 mac_status; /* MAC RX status */ + __le16 mac_time; + __le16 channel; + } format_351 __packed; + } __packed; +} __packed; + +/* PHY RX Status 0 */ +#define B43_RX_PHYST0_GAINCTL 0x4000 /* Gain Control */ +#define B43_RX_PHYST0_PLCPHCF 0x0200 +#define B43_RX_PHYST0_PLCPFV 0x0100 +#define B43_RX_PHYST0_SHORTPRMBL 0x0080 /* Received with Short Preamble */ +#define B43_RX_PHYST0_LCRS 0x0040 +#define B43_RX_PHYST0_ANT 0x0020 /* Antenna */ +#define B43_RX_PHYST0_UNSRATE 0x0010 +#define B43_RX_PHYST0_CLIP 0x000C +#define B43_RX_PHYST0_CLIP_SHIFT 2 +#define B43_RX_PHYST0_FTYPE 0x0003 /* Frame type */ +#define B43_RX_PHYST0_CCK 0x0000 /* Frame type: CCK */ +#define B43_RX_PHYST0_OFDM 0x0001 /* Frame type: OFDM */ +#define B43_RX_PHYST0_PRE_N 0x0002 /* Pre-standard N-PHY frame */ +#define B43_RX_PHYST0_STD_N 0x0003 /* Standard N-PHY frame */ + +/* PHY RX Status 2 */ +#define B43_RX_PHYST2_LNAG 0xC000 /* LNA Gain */ +#define B43_RX_PHYST2_LNAG_SHIFT 14 +#define B43_RX_PHYST2_PNAG 0x3C00 /* PNA Gain */ +#define B43_RX_PHYST2_PNAG_SHIFT 10 +#define B43_RX_PHYST2_FOFF 0x03FF /* F offset */ + +/* PHY RX Status 3 */ +#define B43_RX_PHYST3_DIGG 0x1800 /* DIG Gain */ +#define B43_RX_PHYST3_DIGG_SHIFT 11 +#define B43_RX_PHYST3_TRSTATE 0x0400 /* TR state */ + +/* MAC RX Status */ +#define B43_RX_MAC_RXST_VALID 0x01000000 /* PHY RXST valid */ +#define B43_RX_MAC_TKIP_MICERR 0x00100000 /* TKIP MIC error */ +#define B43_RX_MAC_TKIP_MICATT 0x00080000 /* TKIP MIC attempted */ +#define B43_RX_MAC_AGGTYPE 0x00060000 /* Aggregation type */ +#define B43_RX_MAC_AGGTYPE_SHIFT 17 +#define B43_RX_MAC_AMSDU 0x00010000 /* A-MSDU mask */ +#define B43_RX_MAC_BEACONSENT 0x00008000 /* Beacon sent flag */ +#define B43_RX_MAC_KEYIDX 0x000007E0 /* Key index */ +#define B43_RX_MAC_KEYIDX_SHIFT 5 +#define B43_RX_MAC_DECERR 0x00000010 /* Decrypt error */ +#define B43_RX_MAC_DEC 0x00000008 /* Decryption attempted */ +#define B43_RX_MAC_PADDING 0x00000004 /* Pad bytes present */ +#define B43_RX_MAC_RESP 0x00000002 /* Response frame transmitted */ +#define B43_RX_MAC_FCSERR 0x00000001 /* FCS error */ + +/* RX channel */ +#define B43_RX_CHAN_40MHZ 0x1000 /* 40 Mhz channel width */ +#define B43_RX_CHAN_5GHZ 0x0800 /* 5 Ghz band */ +#define B43_RX_CHAN_ID 0x07F8 /* Channel ID */ +#define B43_RX_CHAN_ID_SHIFT 3 +#define B43_RX_CHAN_PHYTYPE 0x0007 /* PHY type */ + + +u8 b43_plcp_get_ratecode_cck(const u8 bitrate); +u8 b43_plcp_get_ratecode_ofdm(const u8 bitrate); + +void b43_generate_plcp_hdr(struct b43_plcp_hdr4 *plcp, + const u16 octets, const u8 bitrate); + +void b43_rx(struct b43_wldev *dev, struct sk_buff *skb, const void *_rxhdr); + +void b43_handle_txstatus(struct b43_wldev *dev, + const struct b43_txstatus *status); +bool b43_fill_txstatus_report(struct b43_wldev *dev, + struct ieee80211_tx_info *report, + const struct b43_txstatus *status); + +void b43_tx_suspend(struct b43_wldev *dev); +void b43_tx_resume(struct b43_wldev *dev); + + +/* Helper functions for converting the key-table index from "firmware-format" + * to "raw-format" and back. The firmware API changed for this at some revision. + * We need to account for that here. */ +static inline int b43_new_kidx_api(struct b43_wldev *dev) +{ + /* FIXME: Not sure the change was at rev 351 */ + return (dev->fw.rev >= 351); +} +static inline u8 b43_kidx_to_fw(struct b43_wldev *dev, u8 raw_kidx) +{ + u8 firmware_kidx; + if (b43_new_kidx_api(dev)) { + firmware_kidx = raw_kidx; + } else { + if (raw_kidx >= 4) /* Is per STA key? */ + firmware_kidx = raw_kidx - 4; + else + firmware_kidx = raw_kidx; /* TX default key */ + } + return firmware_kidx; +} +static inline u8 b43_kidx_to_raw(struct b43_wldev *dev, u8 firmware_kidx) +{ + u8 raw_kidx; + if (b43_new_kidx_api(dev)) + raw_kidx = firmware_kidx; + else + raw_kidx = firmware_kidx + 4; /* RX default keys or per STA keys */ + return raw_kidx; +} + +/* struct b43_private_tx_info - TX info private to b43. + * The structure is placed in (struct ieee80211_tx_info *)->rate_driver_data + * + * @bouncebuffer: DMA Bouncebuffer (if used) + */ +struct b43_private_tx_info { + void *bouncebuffer; +}; + +static inline struct b43_private_tx_info * +b43_get_priv_tx_info(struct ieee80211_tx_info *info) +{ + BUILD_BUG_ON(sizeof(struct b43_private_tx_info) > + sizeof(info->rate_driver_data)); + return (struct b43_private_tx_info *)info->rate_driver_data; +} + +#endif /* B43_XMIT_H_ */ diff --git a/drivers/net/wireless/b43legacy/Kconfig b/drivers/net/wireless/b43legacy/Kconfig new file mode 100644 index 0000000..fd247e4 --- /dev/null +++ b/drivers/net/wireless/b43legacy/Kconfig @@ -0,0 +1,105 @@ +config B43LEGACY + tristate "Broadcom 43xx-legacy wireless support (mac80211 stack)" + depends on m + depends on SSB_POSSIBLE && MAC80211 && HAS_DMA + select SSB + select BACKPORT_FW_LOADER + ---help--- + b43legacy is a driver for 802.11b devices from Broadcom (BCM4301 and + BCM4303) and early model 802.11g chips (BCM4306 Ver. 2) used in the + Linksys WPC54G V1 PCMCIA devices. + + Newer 802.11g and 802.11a devices need b43. + + It is safe to include both b43 and b43legacy as the underlying glue + layer will automatically load the correct version for your device. + + This driver uses V3 firmware, which must be installed separately using + b43-fwcutter. + + This driver can be built as a module (recommended) that will be + called "b43legacy". If unsure, say M. + +# Auto-select SSB PCI-HOST support, if possible +config B43LEGACY_PCI_AUTOSELECT + bool + depends on B43LEGACY && SSB_PCIHOST_POSSIBLE + select SSB_PCIHOST + select SSB_B43_PCI_BRIDGE + default y + +# Auto-select SSB PCICORE driver, if possible +config B43LEGACY_PCICORE_AUTOSELECT + bool + depends on B43LEGACY && SSB_DRIVER_PCICORE_POSSIBLE + select SSB_DRIVER_PCICORE + default y + +# LED support +# This config option automatically enables b43legacy LEDS support, +# if it's possible. +config B43LEGACY_LEDS + bool + depends on B43LEGACY && MAC80211_LEDS && (LEDS_CLASS = y || LEDS_CLASS = B43LEGACY) + default y + +# This config option automatically enables b43 HW-RNG support, +# if the HW-RNG core is enabled. +config B43LEGACY_HWRNG + bool + depends on B43LEGACY && (HW_RANDOM = y || HW_RANDOM = B43LEGACY) + default y + +config B43LEGACY_DEBUG + bool "Broadcom 43xx-legacy debugging" + depends on B43LEGACY + default y + ---help--- + Say Y, because this information will help you get the driver running. + This option generates a minimum of log output. + +config B43LEGACY_DMA + bool + depends on B43LEGACY + +config B43LEGACY_PIO + bool + depends on B43LEGACY + +choice + prompt "Broadcom 43xx-legacy data transfer mode" + depends on B43LEGACY + default B43LEGACY_DMA_AND_PIO_MODE + +config B43LEGACY_DMA_AND_PIO_MODE + bool "DMA + PIO" + select B43LEGACY_DMA + select B43LEGACY_PIO + ---help--- + Include both, Direct Memory Access (DMA) and Programmed I/O (PIO) + data transfer modes. The mode actually used is selectable through + the module parameter "pio". With pio=0 as a module parameter, the + default DMA is used, otherwise PIO is used. + + If unsure, choose this option. + +config B43LEGACY_DMA_MODE + bool "DMA (Direct Memory Access) only" + select B43LEGACY_DMA + ---help--- + Only include Direct Memory Access (DMA). + This reduces the size of the driver module, by omitting the PIO code. + +config B43LEGACY_PIO_MODE + bool "PIO (Programmed I/O) only" + select B43LEGACY_PIO + ---help--- + Only include Programmed I/O (PIO). + This reduces the size of the driver module, by omitting the DMA code. + Please note that PIO transfers are slow (compared to DMA). + + Also note that not all devices of the b43legacy series support PIO. + + You should use PIO only if DMA does not work for you. + +endchoice diff --git a/drivers/net/wireless/b43legacy/Makefile b/drivers/net/wireless/b43legacy/Makefile new file mode 100644 index 0000000..cbe179a --- /dev/null +++ b/drivers/net/wireless/b43legacy/Makefile @@ -0,0 +1,19 @@ +# b43legacy core +b43legacy-y += main.o +b43legacy-y += ilt.o +b43legacy-y += phy.o +b43legacy-y += radio.o +b43legacy-y += sysfs.o +b43legacy-y += xmit.o +# b43 RFKILL button support +b43legacy-y += rfkill.o +# b43legacy LED support +b43legacy-$(CPTCFG_B43LEGACY_LEDS) += leds.o +# b43legacy debugging +b43legacy-$(CPTCFG_B43LEGACY_DEBUG) += debugfs.o +# b43legacy DMA and PIO +b43legacy-$(CPTCFG_B43LEGACY_DMA) += dma.o +b43legacy-$(CPTCFG_B43LEGACY_PIO) += pio.o + +obj-$(CPTCFG_B43LEGACY) += b43legacy.o + diff --git a/drivers/net/wireless/b43legacy/b43legacy.h b/drivers/net/wireless/b43legacy/b43legacy.h new file mode 100644 index 0000000..04055cf --- /dev/null +++ b/drivers/net/wireless/b43legacy/b43legacy.h @@ -0,0 +1,858 @@ +#ifndef B43legacy_H_ +#define B43legacy_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "debugfs.h" +#include "leds.h" +#include "rfkill.h" +#include "phy.h" + + +#define B43legacy_IRQWAIT_MAX_RETRIES 20 + +/* MMIO offsets */ +#define B43legacy_MMIO_DMA0_REASON 0x20 +#define B43legacy_MMIO_DMA0_IRQ_MASK 0x24 +#define B43legacy_MMIO_DMA1_REASON 0x28 +#define B43legacy_MMIO_DMA1_IRQ_MASK 0x2C +#define B43legacy_MMIO_DMA2_REASON 0x30 +#define B43legacy_MMIO_DMA2_IRQ_MASK 0x34 +#define B43legacy_MMIO_DMA3_REASON 0x38 +#define B43legacy_MMIO_DMA3_IRQ_MASK 0x3C +#define B43legacy_MMIO_DMA4_REASON 0x40 +#define B43legacy_MMIO_DMA4_IRQ_MASK 0x44 +#define B43legacy_MMIO_DMA5_REASON 0x48 +#define B43legacy_MMIO_DMA5_IRQ_MASK 0x4C +#define B43legacy_MMIO_MACCTL 0x120 /* MAC control */ +#define B43legacy_MMIO_MACCMD 0x124 /* MAC command */ +#define B43legacy_MMIO_GEN_IRQ_REASON 0x128 +#define B43legacy_MMIO_GEN_IRQ_MASK 0x12C +#define B43legacy_MMIO_RAM_CONTROL 0x130 +#define B43legacy_MMIO_RAM_DATA 0x134 +#define B43legacy_MMIO_PS_STATUS 0x140 +#define B43legacy_MMIO_RADIO_HWENABLED_HI 0x158 +#define B43legacy_MMIO_SHM_CONTROL 0x160 +#define B43legacy_MMIO_SHM_DATA 0x164 +#define B43legacy_MMIO_SHM_DATA_UNALIGNED 0x166 +#define B43legacy_MMIO_XMITSTAT_0 0x170 +#define B43legacy_MMIO_XMITSTAT_1 0x174 +#define B43legacy_MMIO_REV3PLUS_TSF_LOW 0x180 /* core rev >= 3 only */ +#define B43legacy_MMIO_REV3PLUS_TSF_HIGH 0x184 /* core rev >= 3 only */ +#define B43legacy_MMIO_TSF_CFP_REP 0x188 +#define B43legacy_MMIO_TSF_CFP_START 0x18C +/* 32-bit DMA */ +#define B43legacy_MMIO_DMA32_BASE0 0x200 +#define B43legacy_MMIO_DMA32_BASE1 0x220 +#define B43legacy_MMIO_DMA32_BASE2 0x240 +#define B43legacy_MMIO_DMA32_BASE3 0x260 +#define B43legacy_MMIO_DMA32_BASE4 0x280 +#define B43legacy_MMIO_DMA32_BASE5 0x2A0 +/* 64-bit DMA */ +#define B43legacy_MMIO_DMA64_BASE0 0x200 +#define B43legacy_MMIO_DMA64_BASE1 0x240 +#define B43legacy_MMIO_DMA64_BASE2 0x280 +#define B43legacy_MMIO_DMA64_BASE3 0x2C0 +#define B43legacy_MMIO_DMA64_BASE4 0x300 +#define B43legacy_MMIO_DMA64_BASE5 0x340 +/* PIO */ +#define B43legacy_MMIO_PIO1_BASE 0x300 +#define B43legacy_MMIO_PIO2_BASE 0x310 +#define B43legacy_MMIO_PIO3_BASE 0x320 +#define B43legacy_MMIO_PIO4_BASE 0x330 + +#define B43legacy_MMIO_PHY_VER 0x3E0 +#define B43legacy_MMIO_PHY_RADIO 0x3E2 +#define B43legacy_MMIO_PHY0 0x3E6 +#define B43legacy_MMIO_ANTENNA 0x3E8 +#define B43legacy_MMIO_CHANNEL 0x3F0 +#define B43legacy_MMIO_CHANNEL_EXT 0x3F4 +#define B43legacy_MMIO_RADIO_CONTROL 0x3F6 +#define B43legacy_MMIO_RADIO_DATA_HIGH 0x3F8 +#define B43legacy_MMIO_RADIO_DATA_LOW 0x3FA +#define B43legacy_MMIO_PHY_CONTROL 0x3FC +#define B43legacy_MMIO_PHY_DATA 0x3FE +#define B43legacy_MMIO_MACFILTER_CONTROL 0x420 +#define B43legacy_MMIO_MACFILTER_DATA 0x422 +#define B43legacy_MMIO_RCMTA_COUNT 0x43C /* Receive Match Transmitter Addr */ +#define B43legacy_MMIO_RADIO_HWENABLED_LO 0x49A +#define B43legacy_MMIO_GPIO_CONTROL 0x49C +#define B43legacy_MMIO_GPIO_MASK 0x49E +#define B43legacy_MMIO_TSF_CFP_PRETBTT 0x612 +#define B43legacy_MMIO_TSF_0 0x632 /* core rev < 3 only */ +#define B43legacy_MMIO_TSF_1 0x634 /* core rev < 3 only */ +#define B43legacy_MMIO_TSF_2 0x636 /* core rev < 3 only */ +#define B43legacy_MMIO_TSF_3 0x638 /* core rev < 3 only */ +#define B43legacy_MMIO_RNG 0x65A +#define B43legacy_MMIO_POWERUP_DELAY 0x6A8 + +/* SPROM boardflags_lo values */ +#define B43legacy_BFL_PACTRL 0x0002 +#define B43legacy_BFL_RSSI 0x0008 +#define B43legacy_BFL_EXTLNA 0x1000 + +/* GPIO register offset, in both ChipCommon and PCI core. */ +#define B43legacy_GPIO_CONTROL 0x6c + +/* SHM Routing */ +#define B43legacy_SHM_SHARED 0x0001 +#define B43legacy_SHM_WIRELESS 0x0002 +#define B43legacy_SHM_HW 0x0004 +#define B43legacy_SHM_UCODE 0x0300 + +/* SHM Routing modifiers */ +#define B43legacy_SHM_AUTOINC_R 0x0200 /* Read Auto-increment */ +#define B43legacy_SHM_AUTOINC_W 0x0100 /* Write Auto-increment */ +#define B43legacy_SHM_AUTOINC_RW (B43legacy_SHM_AUTOINC_R | \ + B43legacy_SHM_AUTOINC_W) + +/* Misc SHM_SHARED offsets */ +#define B43legacy_SHM_SH_WLCOREREV 0x0016 /* 802.11 core revision */ +#define B43legacy_SHM_SH_HOSTFLO 0x005E /* Hostflags ucode opts (low) */ +#define B43legacy_SHM_SH_HOSTFHI 0x0060 /* Hostflags ucode opts (high) */ +/* SHM_SHARED crypto engine */ +#define B43legacy_SHM_SH_KEYIDXBLOCK 0x05D4 /* Key index/algorithm block */ +/* SHM_SHARED beacon/AP variables */ +#define B43legacy_SHM_SH_DTIMP 0x0012 /* DTIM period */ +#define B43legacy_SHM_SH_BTL0 0x0018 /* Beacon template length 0 */ +#define B43legacy_SHM_SH_BTL1 0x001A /* Beacon template length 1 */ +#define B43legacy_SHM_SH_BTSFOFF 0x001C /* Beacon TSF offset */ +#define B43legacy_SHM_SH_TIMPOS 0x001E /* TIM position in beacon */ +#define B43legacy_SHM_SH_BEACPHYCTL 0x0054 /* Beacon PHY TX control word */ +/* SHM_SHARED ACK/CTS control */ +#define B43legacy_SHM_SH_ACKCTSPHYCTL 0x0022 /* ACK/CTS PHY control word */ +/* SHM_SHARED probe response variables */ +#define B43legacy_SHM_SH_PRTLEN 0x004A /* Probe Response template length */ +#define B43legacy_SHM_SH_PRMAXTIME 0x0074 /* Probe Response max time */ +#define B43legacy_SHM_SH_PRPHYCTL 0x0188 /* Probe Resp PHY TX control */ +/* SHM_SHARED rate tables */ +#define B43legacy_SHM_SH_OFDMDIRECT 0x0480 /* Pointer to OFDM direct map */ +#define B43legacy_SHM_SH_OFDMBASIC 0x04A0 /* Pointer to OFDM basic rate map */ +#define B43legacy_SHM_SH_CCKDIRECT 0x04C0 /* Pointer to CCK direct map */ +#define B43legacy_SHM_SH_CCKBASIC 0x04E0 /* Pointer to CCK basic rate map */ +/* SHM_SHARED microcode soft registers */ +#define B43legacy_SHM_SH_UCODEREV 0x0000 /* Microcode revision */ +#define B43legacy_SHM_SH_UCODEPATCH 0x0002 /* Microcode patchlevel */ +#define B43legacy_SHM_SH_UCODEDATE 0x0004 /* Microcode date */ +#define B43legacy_SHM_SH_UCODETIME 0x0006 /* Microcode time */ +#define B43legacy_SHM_SH_SPUWKUP 0x0094 /* pre-wakeup for synth PU in us */ +#define B43legacy_SHM_SH_PRETBTT 0x0096 /* pre-TBTT in us */ + +#define B43legacy_UCODEFLAGS_OFFSET 0x005E + +/* Hardware Radio Enable masks */ +#define B43legacy_MMIO_RADIO_HWENABLED_HI_MASK (1 << 16) +#define B43legacy_MMIO_RADIO_HWENABLED_LO_MASK (1 << 4) + +/* HostFlags. See b43legacy_hf_read/write() */ +#define B43legacy_HF_SYMW 0x00000002 /* G-PHY SYM workaround */ +#define B43legacy_HF_GDCW 0x00000020 /* G-PHY DV cancel filter */ +#define B43legacy_HF_OFDMPABOOST 0x00000040 /* Enable PA boost OFDM */ +#define B43legacy_HF_EDCF 0x00000100 /* on if WME/MAC suspended */ + +/* MacFilter offsets. */ +#define B43legacy_MACFILTER_SELF 0x0000 +#define B43legacy_MACFILTER_BSSID 0x0003 +#define B43legacy_MACFILTER_MAC 0x0010 + +/* PHYVersioning */ +#define B43legacy_PHYTYPE_B 0x01 +#define B43legacy_PHYTYPE_G 0x02 + +/* PHYRegisters */ +#define B43legacy_PHY_G_LO_CONTROL 0x0810 +#define B43legacy_PHY_ILT_G_CTRL 0x0472 +#define B43legacy_PHY_ILT_G_DATA1 0x0473 +#define B43legacy_PHY_ILT_G_DATA2 0x0474 +#define B43legacy_PHY_G_PCTL 0x0029 +#define B43legacy_PHY_RADIO_BITFIELD 0x0401 +#define B43legacy_PHY_G_CRS 0x0429 +#define B43legacy_PHY_NRSSILT_CTRL 0x0803 +#define B43legacy_PHY_NRSSILT_DATA 0x0804 + +/* RadioRegisters */ +#define B43legacy_RADIOCTL_ID 0x01 + +/* MAC Control bitfield */ +#define B43legacy_MACCTL_ENABLED 0x00000001 /* MAC Enabled */ +#define B43legacy_MACCTL_PSM_RUN 0x00000002 /* Run Microcode */ +#define B43legacy_MACCTL_PSM_JMP0 0x00000004 /* Microcode jump to 0 */ +#define B43legacy_MACCTL_SHM_ENABLED 0x00000100 /* SHM Enabled */ +#define B43legacy_MACCTL_IHR_ENABLED 0x00000400 /* IHR Region Enabled */ +#define B43legacy_MACCTL_BE 0x00010000 /* Big Endian mode */ +#define B43legacy_MACCTL_INFRA 0x00020000 /* Infrastructure mode */ +#define B43legacy_MACCTL_AP 0x00040000 /* AccessPoint mode */ +#define B43legacy_MACCTL_RADIOLOCK 0x00080000 /* Radio lock */ +#define B43legacy_MACCTL_BEACPROMISC 0x00100000 /* Beacon Promiscuous */ +#define B43legacy_MACCTL_KEEP_BADPLCP 0x00200000 /* Keep bad PLCP frames */ +#define B43legacy_MACCTL_KEEP_CTL 0x00400000 /* Keep control frames */ +#define B43legacy_MACCTL_KEEP_BAD 0x00800000 /* Keep bad frames (FCS) */ +#define B43legacy_MACCTL_PROMISC 0x01000000 /* Promiscuous mode */ +#define B43legacy_MACCTL_HWPS 0x02000000 /* Hardware Power Saving */ +#define B43legacy_MACCTL_AWAKE 0x04000000 /* Device is awake */ +#define B43legacy_MACCTL_TBTTHOLD 0x10000000 /* TBTT Hold */ +#define B43legacy_MACCTL_GMODE 0x80000000 /* G Mode */ + +/* MAC Command bitfield */ +#define B43legacy_MACCMD_BEACON0_VALID 0x00000001 /* Beacon 0 in template RAM is busy/valid */ +#define B43legacy_MACCMD_BEACON1_VALID 0x00000002 /* Beacon 1 in template RAM is busy/valid */ +#define B43legacy_MACCMD_DFQ_VALID 0x00000004 /* Directed frame queue valid (IBSS PS mode, ATIM) */ +#define B43legacy_MACCMD_CCA 0x00000008 /* Clear channel assessment */ +#define B43legacy_MACCMD_BGNOISE 0x00000010 /* Background noise */ + +/* 802.11 core specific TM State Low flags */ +#define B43legacy_TMSLOW_GMODE 0x20000000 /* G Mode Enable */ +#define B43legacy_TMSLOW_PLLREFSEL 0x00200000 /* PLL Freq Ref Select */ +#define B43legacy_TMSLOW_MACPHYCLKEN 0x00100000 /* MAC PHY Clock Ctrl Enbl */ +#define B43legacy_TMSLOW_PHYRESET 0x00080000 /* PHY Reset */ +#define B43legacy_TMSLOW_PHYCLKEN 0x00040000 /* PHY Clock Enable */ + +/* 802.11 core specific TM State High flags */ +#define B43legacy_TMSHIGH_FCLOCK 0x00040000 /* Fast Clock Available */ +#define B43legacy_TMSHIGH_GPHY 0x00010000 /* G-PHY avail (rev >= 5) */ + +#define B43legacy_UCODEFLAG_AUTODIV 0x0001 + +/* Generic-Interrupt reasons. */ +#define B43legacy_IRQ_MAC_SUSPENDED 0x00000001 +#define B43legacy_IRQ_BEACON 0x00000002 +#define B43legacy_IRQ_TBTT_INDI 0x00000004 /* Target Beacon Transmit Time */ +#define B43legacy_IRQ_BEACON_TX_OK 0x00000008 +#define B43legacy_IRQ_BEACON_CANCEL 0x00000010 +#define B43legacy_IRQ_ATIM_END 0x00000020 +#define B43legacy_IRQ_PMQ 0x00000040 +#define B43legacy_IRQ_PIO_WORKAROUND 0x00000100 +#define B43legacy_IRQ_MAC_TXERR 0x00000200 +#define B43legacy_IRQ_PHY_TXERR 0x00000800 +#define B43legacy_IRQ_PMEVENT 0x00001000 +#define B43legacy_IRQ_TIMER0 0x00002000 +#define B43legacy_IRQ_TIMER1 0x00004000 +#define B43legacy_IRQ_DMA 0x00008000 +#define B43legacy_IRQ_TXFIFO_FLUSH_OK 0x00010000 +#define B43legacy_IRQ_CCA_MEASURE_OK 0x00020000 +#define B43legacy_IRQ_NOISESAMPLE_OK 0x00040000 +#define B43legacy_IRQ_UCODE_DEBUG 0x08000000 +#define B43legacy_IRQ_RFKILL 0x10000000 +#define B43legacy_IRQ_TX_OK 0x20000000 +#define B43legacy_IRQ_PHY_G_CHANGED 0x40000000 +#define B43legacy_IRQ_TIMEOUT 0x80000000 + +#define B43legacy_IRQ_ALL 0xFFFFFFFF +#define B43legacy_IRQ_MASKTEMPLATE (B43legacy_IRQ_MAC_SUSPENDED | \ + B43legacy_IRQ_TBTT_INDI | \ + B43legacy_IRQ_ATIM_END | \ + B43legacy_IRQ_PMQ | \ + B43legacy_IRQ_MAC_TXERR | \ + B43legacy_IRQ_PHY_TXERR | \ + B43legacy_IRQ_DMA | \ + B43legacy_IRQ_TXFIFO_FLUSH_OK | \ + B43legacy_IRQ_NOISESAMPLE_OK | \ + B43legacy_IRQ_UCODE_DEBUG | \ + B43legacy_IRQ_RFKILL | \ + B43legacy_IRQ_TX_OK) + +/* Device specific rate values. + * The actual values defined here are (rate_in_mbps * 2). + * Some code depends on this. Don't change it. */ +#define B43legacy_CCK_RATE_1MB 2 +#define B43legacy_CCK_RATE_2MB 4 +#define B43legacy_CCK_RATE_5MB 11 +#define B43legacy_CCK_RATE_11MB 22 +#define B43legacy_OFDM_RATE_6MB 12 +#define B43legacy_OFDM_RATE_9MB 18 +#define B43legacy_OFDM_RATE_12MB 24 +#define B43legacy_OFDM_RATE_18MB 36 +#define B43legacy_OFDM_RATE_24MB 48 +#define B43legacy_OFDM_RATE_36MB 72 +#define B43legacy_OFDM_RATE_48MB 96 +#define B43legacy_OFDM_RATE_54MB 108 +/* Convert a b43legacy rate value to a rate in 100kbps */ +#define B43legacy_RATE_TO_100KBPS(rate) (((rate) * 10) / 2) + + +#define B43legacy_DEFAULT_SHORT_RETRY_LIMIT 7 +#define B43legacy_DEFAULT_LONG_RETRY_LIMIT 4 + +#define B43legacy_PHY_TX_BADNESS_LIMIT 1000 + +/* Max size of a security key */ +#define B43legacy_SEC_KEYSIZE 16 +/* Security algorithms. */ +enum { + B43legacy_SEC_ALGO_NONE = 0, /* unencrypted, as of TX header. */ + B43legacy_SEC_ALGO_WEP40, + B43legacy_SEC_ALGO_TKIP, + B43legacy_SEC_ALGO_AES, + B43legacy_SEC_ALGO_WEP104, + B43legacy_SEC_ALGO_AES_LEGACY, +}; + +/* Core Information Registers */ +#define B43legacy_CIR_BASE 0xf00 +#define B43legacy_CIR_SBTPSFLAG (B43legacy_CIR_BASE + 0x18) +#define B43legacy_CIR_SBIMSTATE (B43legacy_CIR_BASE + 0x90) +#define B43legacy_CIR_SBINTVEC (B43legacy_CIR_BASE + 0x94) +#define B43legacy_CIR_SBTMSTATELOW (B43legacy_CIR_BASE + 0x98) +#define B43legacy_CIR_SBTMSTATEHIGH (B43legacy_CIR_BASE + 0x9c) +#define B43legacy_CIR_SBIMCONFIGLOW (B43legacy_CIR_BASE + 0xa8) +#define B43legacy_CIR_SB_ID_HI (B43legacy_CIR_BASE + 0xfc) + +/* sbtmstatehigh state flags */ +#define B43legacy_SBTMSTATEHIGH_SERROR 0x00000001 +#define B43legacy_SBTMSTATEHIGH_BUSY 0x00000004 +#define B43legacy_SBTMSTATEHIGH_TIMEOUT 0x00000020 +#define B43legacy_SBTMSTATEHIGH_G_PHY_AVAIL 0x00010000 +#define B43legacy_SBTMSTATEHIGH_COREFLAGS 0x1FFF0000 +#define B43legacy_SBTMSTATEHIGH_DMA64BIT 0x10000000 +#define B43legacy_SBTMSTATEHIGH_GATEDCLK 0x20000000 +#define B43legacy_SBTMSTATEHIGH_BISTFAILED 0x40000000 +#define B43legacy_SBTMSTATEHIGH_BISTCOMPLETE 0x80000000 + +/* sbimstate flags */ +#define B43legacy_SBIMSTATE_IB_ERROR 0x20000 +#define B43legacy_SBIMSTATE_TIMEOUT 0x40000 + +#define PFX KBUILD_MODNAME ": " +#ifdef assert +# undef assert +#endif +#ifdef CPTCFG_B43LEGACY_DEBUG +# define B43legacy_WARN_ON(x) WARN_ON(x) +# define B43legacy_BUG_ON(expr) \ + do { \ + if (unlikely((expr))) { \ + printk(KERN_INFO PFX "Test (%s) failed\n", \ + #expr); \ + BUG_ON(expr); \ + } \ + } while (0) +# define B43legacy_DEBUG 1 +#else +/* This will evaluate the argument even if debugging is disabled. */ +static inline bool __b43legacy_warn_on_dummy(bool x) { return x; } +# define B43legacy_WARN_ON(x) __b43legacy_warn_on_dummy(unlikely(!!(x))) +# define B43legacy_BUG_ON(x) do { /* nothing */ } while (0) +# define B43legacy_DEBUG 0 +#endif + + +struct net_device; +struct pci_dev; +struct b43legacy_dmaring; +struct b43legacy_pioqueue; + +/* The firmware file header */ +#define B43legacy_FW_TYPE_UCODE 'u' +#define B43legacy_FW_TYPE_PCM 'p' +#define B43legacy_FW_TYPE_IV 'i' +struct b43legacy_fw_header { + /* File type */ + u8 type; + /* File format version */ + u8 ver; + u8 __padding[2]; + /* Size of the data. For ucode and PCM this is in bytes. + * For IV this is number-of-ivs. */ + __be32 size; +} __packed; + +/* Initial Value file format */ +#define B43legacy_IV_OFFSET_MASK 0x7FFF +#define B43legacy_IV_32BIT 0x8000 +struct b43legacy_iv { + __be16 offset_size; + union { + __be16 d16; + __be32 d32; + } data __packed; +} __packed; + +#define B43legacy_PHYMODE(phytype) (1 << (phytype)) +#define B43legacy_PHYMODE_B B43legacy_PHYMODE \ + ((B43legacy_PHYTYPE_B)) +#define B43legacy_PHYMODE_G B43legacy_PHYMODE \ + ((B43legacy_PHYTYPE_G)) + +/* Value pair to measure the LocalOscillator. */ +struct b43legacy_lopair { + s8 low; + s8 high; + u8 used:1; +}; +#define B43legacy_LO_COUNT (14*4) + +struct b43legacy_phy { + /* Possible PHYMODEs on this PHY */ + u8 possible_phymodes; + /* GMODE bit enabled in MACCTL? */ + bool gmode; + + /* Analog Type */ + u8 analog; + /* B43legacy_PHYTYPE_ */ + u8 type; + /* PHY revision number. */ + u8 rev; + + u16 antenna_diversity; + u16 savedpctlreg; + /* Radio versioning */ + u16 radio_manuf; /* Radio manufacturer */ + u16 radio_ver; /* Radio version */ + u8 calibrated:1; + u8 radio_rev; /* Radio revision */ + + bool dyn_tssi_tbl; /* tssi2dbm is kmalloc()ed. */ + + /* ACI (adjacent channel interference) flags. */ + bool aci_enable; + bool aci_wlan_automatic; + bool aci_hw_rssi; + + /* Radio switched on/off */ + bool radio_on; + struct { + /* Values saved when turning the radio off. + * They are needed when turning it on again. */ + bool valid; + u16 rfover; + u16 rfoverval; + } radio_off_context; + + u16 minlowsig[2]; + u16 minlowsigpos[2]; + + /* LO Measurement Data. + * Use b43legacy_get_lopair() to get a value. + */ + struct b43legacy_lopair *_lo_pairs; + /* TSSI to dBm table in use */ + const s8 *tssi2dbm; + /* idle TSSI value */ + s8 idle_tssi; + /* Target idle TSSI */ + int tgt_idle_tssi; + /* Current idle TSSI */ + int cur_idle_tssi; + + /* LocalOscillator control values. */ + struct b43legacy_txpower_lo_control *lo_control; + /* Values from b43legacy_calc_loopback_gain() */ + s16 max_lb_gain; /* Maximum Loopback gain in hdB */ + s16 trsw_rx_gain; /* TRSW RX gain in hdB */ + s16 lna_lod_gain; /* LNA lod */ + s16 lna_gain; /* LNA */ + s16 pga_gain; /* PGA */ + + /* Desired TX power level (in dBm). This is set by the user and + * adjusted in b43legacy_phy_xmitpower(). */ + u8 power_level; + + /* Values from b43legacy_calc_loopback_gain() */ + u16 loopback_gain[2]; + + /* TX Power control values. */ + /* B/G PHY */ + struct { + /* Current Radio Attenuation for TXpower recalculation. */ + u16 rfatt; + /* Current Baseband Attenuation for TXpower recalculation. */ + u16 bbatt; + /* Current TXpower control value for TXpower recalculation. */ + u16 txctl1; + u16 txctl2; + }; + /* A PHY */ + struct { + u16 txpwr_offset; + }; + + /* Current Interference Mitigation mode */ + int interfmode; + /* Stack of saved values from the Interference Mitigation code. + * Each value in the stack is laid out as follows: + * bit 0-11: offset + * bit 12-15: register ID + * bit 16-32: value + * register ID is: 0x1 PHY, 0x2 Radio, 0x3 ILT + */ +#define B43legacy_INTERFSTACK_SIZE 26 + u32 interfstack[B43legacy_INTERFSTACK_SIZE]; + + /* Saved values from the NRSSI Slope calculation */ + s16 nrssi[2]; + s32 nrssislope; + /* In memory nrssi lookup table. */ + s8 nrssi_lt[64]; + + /* current channel */ + u8 channel; + + u16 lofcal; + + u16 initval; + + /* PHY TX errors counter. */ + atomic_t txerr_cnt; + +#if B43legacy_DEBUG + /* Manual TX-power control enabled? */ + bool manual_txpower_control; + /* PHY registers locked by b43legacy_phy_lock()? */ + bool phy_locked; +#endif /* B43legacy_DEBUG */ +}; + +/* Data structures for DMA transmission, per 80211 core. */ +struct b43legacy_dma { + struct b43legacy_dmaring *tx_ring0; + struct b43legacy_dmaring *tx_ring1; + struct b43legacy_dmaring *tx_ring2; + struct b43legacy_dmaring *tx_ring3; + struct b43legacy_dmaring *tx_ring4; + struct b43legacy_dmaring *tx_ring5; + + struct b43legacy_dmaring *rx_ring0; + struct b43legacy_dmaring *rx_ring3; /* only on core.rev < 5 */ + + u32 translation; /* Routing bits */ +}; + +/* Data structures for PIO transmission, per 80211 core. */ +struct b43legacy_pio { + struct b43legacy_pioqueue *queue0; + struct b43legacy_pioqueue *queue1; + struct b43legacy_pioqueue *queue2; + struct b43legacy_pioqueue *queue3; +}; + +/* Context information for a noise calculation (Link Quality). */ +struct b43legacy_noise_calculation { + u8 channel_at_start; + bool calculation_running; + u8 nr_samples; + s8 samples[8][4]; +}; + +struct b43legacy_stats { + u8 link_noise; + /* Store the last TX/RX times here for updating the leds. */ + unsigned long last_tx; + unsigned long last_rx; +}; + +struct b43legacy_key { + void *keyconf; + bool enabled; + u8 algorithm; +}; + +#define B43legacy_QOS_QUEUE_NUM 4 + +struct b43legacy_wldev; + +/* QOS parameters for a queue. */ +struct b43legacy_qos_params { + /* The QOS parameters */ + struct ieee80211_tx_queue_params p; +}; + +/* Data structure for the WLAN parts (802.11 cores) of the b43legacy chip. */ +struct b43legacy_wl { + /* Pointer to the active wireless device on this chip */ + struct b43legacy_wldev *current_dev; + /* Pointer to the ieee80211 hardware data structure */ + struct ieee80211_hw *hw; + + spinlock_t irq_lock; /* locks IRQ */ + struct mutex mutex; /* locks wireless core state */ + spinlock_t leds_lock; /* lock for leds */ + + /* firmware loading work */ + struct work_struct firmware_load; + + /* We can only have one operating interface (802.11 core) + * at a time. General information about this interface follows. + */ + + struct ieee80211_vif *vif; + /* MAC address (can be NULL). */ + u8 mac_addr[ETH_ALEN]; + /* Current BSSID (can be NULL). */ + u8 bssid[ETH_ALEN]; + /* Interface type. (IEEE80211_IF_TYPE_XXX) */ + int if_type; + /* Is the card operating in AP, STA or IBSS mode? */ + bool operating; + /* filter flags */ + unsigned int filter_flags; + /* Stats about the wireless interface */ + struct ieee80211_low_level_stats ieee_stats; + +#ifdef CPTCFG_B43LEGACY_HWRNG + struct hwrng rng; + u8 rng_initialized; + char rng_name[30 + 1]; +#endif + + /* List of all wireless devices on this chip */ + struct list_head devlist; + u8 nr_devs; + + bool radiotap_enabled; + bool radio_enabled; + + /* The beacon we are currently using (AP or IBSS mode). + * This beacon stuff is protected by the irq_lock. */ + struct sk_buff *current_beacon; + bool beacon0_uploaded; + bool beacon1_uploaded; + bool beacon_templates_virgin; /* Never wrote the templates? */ + struct work_struct beacon_update_trigger; + /* The current QOS parameters for the 4 queues. */ + struct b43legacy_qos_params qos_params[B43legacy_QOS_QUEUE_NUM]; + + /* Packet transmit work */ + struct work_struct tx_work; + + /* Queue of packets to be transmitted. */ + struct sk_buff_head tx_queue[B43legacy_QOS_QUEUE_NUM]; + + /* Flag that implement the queues stopping. */ + bool tx_queue_stopped[B43legacy_QOS_QUEUE_NUM]; + +}; + +/* Pointers to the firmware data and meta information about it. */ +struct b43legacy_firmware { + /* Microcode */ + const struct firmware *ucode; + /* PCM code */ + const struct firmware *pcm; + /* Initial MMIO values for the firmware */ + const struct firmware *initvals; + /* Initial MMIO values for the firmware, band-specific */ + const struct firmware *initvals_band; + /* Firmware revision */ + u16 rev; + /* Firmware patchlevel */ + u16 patch; +}; + +/* Device (802.11 core) initialization status. */ +enum { + B43legacy_STAT_UNINIT = 0, /* Uninitialized. */ + B43legacy_STAT_INITIALIZED = 1, /* Initialized, not yet started. */ + B43legacy_STAT_STARTED = 2, /* Up and running. */ +}; +#define b43legacy_status(wldev) atomic_read(&(wldev)->__init_status) +#define b43legacy_set_status(wldev, stat) do { \ + atomic_set(&(wldev)->__init_status, (stat)); \ + smp_wmb(); \ + } while (0) + +/* *** --- HOW LOCKING WORKS IN B43legacy --- *** + * + * You should always acquire both, wl->mutex and wl->irq_lock unless: + * - You don't need to acquire wl->irq_lock, if the interface is stopped. + * - You don't need to acquire wl->mutex in the IRQ handler, IRQ tasklet + * and packet TX path (and _ONLY_ there.) + */ + +/* Data structure for one wireless device (802.11 core) */ +struct b43legacy_wldev { + struct ssb_device *dev; + struct b43legacy_wl *wl; + + /* The device initialization status. + * Use b43legacy_status() to query. */ + atomic_t __init_status; + /* Saved init status for handling suspend. */ + int suspend_init_status; + + bool __using_pio; /* Using pio rather than dma. */ + bool bad_frames_preempt;/* Use "Bad Frames Preemption". */ + bool dfq_valid; /* Directed frame queue valid (IBSS PS mode, ATIM). */ + bool short_preamble; /* TRUE if using short preamble. */ + bool radio_hw_enable; /* State of radio hardware enable bit. */ + + /* PHY/Radio device. */ + struct b43legacy_phy phy; + union { + /* DMA engines. */ + struct b43legacy_dma dma; + /* PIO engines. */ + struct b43legacy_pio pio; + }; + + /* Various statistics about the physical device. */ + struct b43legacy_stats stats; + + /* The device LEDs. */ + struct b43legacy_led led_tx; + struct b43legacy_led led_rx; + struct b43legacy_led led_assoc; + struct b43legacy_led led_radio; + + /* Reason code of the last interrupt. */ + u32 irq_reason; + u32 dma_reason[6]; + /* The currently active generic-interrupt mask. */ + u32 irq_mask; + /* Link Quality calculation context. */ + struct b43legacy_noise_calculation noisecalc; + /* if > 0 MAC is suspended. if == 0 MAC is enabled. */ + int mac_suspended; + + /* Interrupt Service Routine tasklet (bottom-half) */ + struct tasklet_struct isr_tasklet; + + /* Periodic tasks */ + struct delayed_work periodic_work; + unsigned int periodic_state; + + struct work_struct restart_work; + + /* encryption/decryption */ + u16 ktp; /* Key table pointer */ + u8 max_nr_keys; + struct b43legacy_key key[58]; + + /* Firmware data */ + struct b43legacy_firmware fw; + const struct firmware *fwp; /* needed to pass fw pointer */ + + /* completion struct for firmware loading */ + struct completion fw_load_complete; + + /* Devicelist in struct b43legacy_wl (all 802.11 cores) */ + struct list_head list; + + /* Debugging stuff follows. */ +#ifdef CPTCFG_B43LEGACY_DEBUG + struct b43legacy_dfsentry *dfsentry; +#endif +}; + + +static inline +struct b43legacy_wl *hw_to_b43legacy_wl(struct ieee80211_hw *hw) +{ + return hw->priv; +} + +/* Helper function, which returns a boolean. + * TRUE, if PIO is used; FALSE, if DMA is used. + */ +#if defined(CPTCFG_B43LEGACY_DMA) && defined(CPTCFG_B43LEGACY_PIO) +static inline +int b43legacy_using_pio(struct b43legacy_wldev *dev) +{ + return dev->__using_pio; +} +#elif defined(CPTCFG_B43LEGACY_DMA) +static inline +int b43legacy_using_pio(struct b43legacy_wldev *dev) +{ + return 0; +} +#elif defined(CPTCFG_B43LEGACY_PIO) +static inline +int b43legacy_using_pio(struct b43legacy_wldev *dev) +{ + return 1; +} +#else +# error "Using neither DMA nor PIO? Confused..." +#endif + + +static inline +struct b43legacy_wldev *dev_to_b43legacy_wldev(struct device *dev) +{ + struct ssb_device *ssb_dev = dev_to_ssb_dev(dev); + return ssb_get_drvdata(ssb_dev); +} + +/* Is the device operating in a specified mode (IEEE80211_IF_TYPE_XXX). */ +static inline +int b43legacy_is_mode(struct b43legacy_wl *wl, int type) +{ + return (wl->operating && + wl->if_type == type); +} + +static inline +bool is_bcm_board_vendor(struct b43legacy_wldev *dev) +{ + return (dev->dev->bus->boardinfo.vendor == PCI_VENDOR_ID_BROADCOM); +} + +static inline +u16 b43legacy_read16(struct b43legacy_wldev *dev, u16 offset) +{ + return ssb_read16(dev->dev, offset); +} + +static inline +void b43legacy_write16(struct b43legacy_wldev *dev, u16 offset, u16 value) +{ + ssb_write16(dev->dev, offset, value); +} + +static inline +u32 b43legacy_read32(struct b43legacy_wldev *dev, u16 offset) +{ + return ssb_read32(dev->dev, offset); +} + +static inline +void b43legacy_write32(struct b43legacy_wldev *dev, u16 offset, u32 value) +{ + ssb_write32(dev->dev, offset, value); +} + +static inline +struct b43legacy_lopair *b43legacy_get_lopair(struct b43legacy_phy *phy, + u16 radio_attenuation, + u16 baseband_attenuation) +{ + return phy->_lo_pairs + (radio_attenuation + + 14 * (baseband_attenuation / 2)); +} + + + +/* Message printing */ +__printf(2, 3) +void b43legacyinfo(struct b43legacy_wl *wl, const char *fmt, ...); +__printf(2, 3) +void b43legacyerr(struct b43legacy_wl *wl, const char *fmt, ...); +__printf(2, 3) +void b43legacywarn(struct b43legacy_wl *wl, const char *fmt, ...); +#if B43legacy_DEBUG +__printf(2, 3) +void b43legacydbg(struct b43legacy_wl *wl, const char *fmt, ...); +#else /* DEBUG */ +# define b43legacydbg(wl, fmt...) do { /* nothing */ } while (0) +#endif /* DEBUG */ + +/* Macros for printing a value in Q5.2 format */ +#define Q52_FMT "%u.%u" +#define Q52_ARG(q52) ((q52) / 4), (((q52) & 3) * 100 / 4) + +#endif /* B43legacy_H_ */ diff --git a/drivers/net/wireless/b43legacy/debugfs.c b/drivers/net/wireless/b43legacy/debugfs.c new file mode 100644 index 0000000..1965edb --- /dev/null +++ b/drivers/net/wireless/b43legacy/debugfs.c @@ -0,0 +1,500 @@ +/* + + Broadcom B43legacy wireless driver + + debugfs driver debugging code + + Copyright (c) 2005-2007 Michael Buesch + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include +#include +#include +#include +#include +#include + +#include "b43legacy.h" +#include "main.h" +#include "debugfs.h" +#include "dma.h" +#include "pio.h" +#include "xmit.h" + + +/* The root directory. */ +static struct dentry *rootdir; + +struct b43legacy_debugfs_fops { + ssize_t (*read)(struct b43legacy_wldev *dev, char *buf, size_t bufsize); + int (*write)(struct b43legacy_wldev *dev, const char *buf, size_t count); + struct file_operations fops; + /* Offset of struct b43legacy_dfs_file in struct b43legacy_dfsentry */ + size_t file_struct_offset; + /* Take wl->irq_lock before calling read/write? */ + bool take_irqlock; +}; + +static inline +struct b43legacy_dfs_file * fops_to_dfs_file(struct b43legacy_wldev *dev, + const struct b43legacy_debugfs_fops *dfops) +{ + void *p; + + p = dev->dfsentry; + p += dfops->file_struct_offset; + + return p; +} + + +#define fappend(fmt, x...) \ + do { \ + if (bufsize - count) \ + count += snprintf(buf + count, \ + bufsize - count, \ + fmt , ##x); \ + else \ + printk(KERN_ERR "b43legacy: fappend overflow\n"); \ + } while (0) + + +/* wl->irq_lock is locked */ +static ssize_t tsf_read_file(struct b43legacy_wldev *dev, char *buf, size_t bufsize) +{ + ssize_t count = 0; + u64 tsf; + + b43legacy_tsf_read(dev, &tsf); + fappend("0x%08x%08x\n", + (unsigned int)((tsf & 0xFFFFFFFF00000000ULL) >> 32), + (unsigned int)(tsf & 0xFFFFFFFFULL)); + + return count; +} + +/* wl->irq_lock is locked */ +static int tsf_write_file(struct b43legacy_wldev *dev, const char *buf, size_t count) +{ + u64 tsf; + + if (sscanf(buf, "%llu", (unsigned long long *)(&tsf)) != 1) + return -EINVAL; + b43legacy_tsf_write(dev, tsf); + + return 0; +} + +/* wl->irq_lock is locked */ +static ssize_t ucode_regs_read_file(struct b43legacy_wldev *dev, char *buf, size_t bufsize) +{ + ssize_t count = 0; + int i; + + for (i = 0; i < 64; i++) { + fappend("r%d = 0x%04x\n", i, + b43legacy_shm_read16(dev, B43legacy_SHM_WIRELESS, i)); + } + + return count; +} + +/* wl->irq_lock is locked */ +static ssize_t shm_read_file(struct b43legacy_wldev *dev, char *buf, size_t bufsize) +{ + ssize_t count = 0; + int i; + u16 tmp; + __le16 *le16buf = (__le16 *)buf; + + for (i = 0; i < 0x1000; i++) { + if (bufsize < sizeof(tmp)) + break; + tmp = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, 2 * i); + le16buf[i] = cpu_to_le16(tmp); + count += sizeof(tmp); + bufsize -= sizeof(tmp); + } + + return count; +} + +static ssize_t txstat_read_file(struct b43legacy_wldev *dev, char *buf, size_t bufsize) +{ + struct b43legacy_txstatus_log *log = &dev->dfsentry->txstatlog; + ssize_t count = 0; + unsigned long flags; + int i, idx; + struct b43legacy_txstatus *stat; + + spin_lock_irqsave(&log->lock, flags); + if (log->end < 0) { + fappend("Nothing transmitted, yet\n"); + goto out_unlock; + } + fappend("b43legacy TX status reports:\n\n" + "index | cookie | seq | phy_stat | frame_count | " + "rts_count | supp_reason | pm_indicated | " + "intermediate | for_ampdu | acked\n" "---\n"); + i = log->end + 1; + idx = 0; + while (1) { + if (i == B43legacy_NR_LOGGED_TXSTATUS) + i = 0; + stat = &(log->log[i]); + if (stat->cookie) { + fappend("%03d | " + "0x%04X | 0x%04X | 0x%02X | " + "0x%X | 0x%X | " + "%u | %u | " + "%u | %u | %u\n", + idx, + stat->cookie, stat->seq, stat->phy_stat, + stat->frame_count, stat->rts_count, + stat->supp_reason, stat->pm_indicated, + stat->intermediate, stat->for_ampdu, + stat->acked); + idx++; + } + if (i == log->end) + break; + i++; + } +out_unlock: + spin_unlock_irqrestore(&log->lock, flags); + + return count; +} + +/* wl->irq_lock is locked */ +static int restart_write_file(struct b43legacy_wldev *dev, const char *buf, size_t count) +{ + int err = 0; + + if (count > 0 && buf[0] == '1') { + b43legacy_controller_restart(dev, "manually restarted"); + } else + err = -EINVAL; + + return err; +} + +#undef fappend + +static ssize_t b43legacy_debugfs_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct b43legacy_wldev *dev; + struct b43legacy_debugfs_fops *dfops; + struct b43legacy_dfs_file *dfile; + ssize_t uninitialized_var(ret); + char *buf; + const size_t bufsize = 1024 * 16; /* 16 KiB buffer */ + const size_t buforder = get_order(bufsize); + int err = 0; + + if (!count) + return 0; + dev = file->private_data; + if (!dev) + return -ENODEV; + + mutex_lock(&dev->wl->mutex); + if (b43legacy_status(dev) < B43legacy_STAT_INITIALIZED) { + err = -ENODEV; + goto out_unlock; + } + + dfops = container_of(file->f_op, struct b43legacy_debugfs_fops, fops); + if (!dfops->read) { + err = -ENOSYS; + goto out_unlock; + } + dfile = fops_to_dfs_file(dev, dfops); + + if (!dfile->buffer) { + buf = (char *)__get_free_pages(GFP_KERNEL, buforder); + if (!buf) { + err = -ENOMEM; + goto out_unlock; + } + memset(buf, 0, bufsize); + if (dfops->take_irqlock) { + spin_lock_irq(&dev->wl->irq_lock); + ret = dfops->read(dev, buf, bufsize); + spin_unlock_irq(&dev->wl->irq_lock); + } else + ret = dfops->read(dev, buf, bufsize); + if (ret <= 0) { + free_pages((unsigned long)buf, buforder); + err = ret; + goto out_unlock; + } + dfile->data_len = ret; + dfile->buffer = buf; + } + + ret = simple_read_from_buffer(userbuf, count, ppos, + dfile->buffer, + dfile->data_len); + if (*ppos >= dfile->data_len) { + free_pages((unsigned long)dfile->buffer, buforder); + dfile->buffer = NULL; + dfile->data_len = 0; + } +out_unlock: + mutex_unlock(&dev->wl->mutex); + + return err ? err : ret; +} + +static ssize_t b43legacy_debugfs_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct b43legacy_wldev *dev; + struct b43legacy_debugfs_fops *dfops; + char *buf; + int err = 0; + + if (!count) + return 0; + if (count > PAGE_SIZE) + return -E2BIG; + dev = file->private_data; + if (!dev) + return -ENODEV; + + mutex_lock(&dev->wl->mutex); + if (b43legacy_status(dev) < B43legacy_STAT_INITIALIZED) { + err = -ENODEV; + goto out_unlock; + } + + dfops = container_of(file->f_op, struct b43legacy_debugfs_fops, fops); + if (!dfops->write) { + err = -ENOSYS; + goto out_unlock; + } + + buf = (char *)get_zeroed_page(GFP_KERNEL); + if (!buf) { + err = -ENOMEM; + goto out_unlock; + } + if (copy_from_user(buf, userbuf, count)) { + err = -EFAULT; + goto out_freepage; + } + if (dfops->take_irqlock) { + spin_lock_irq(&dev->wl->irq_lock); + err = dfops->write(dev, buf, count); + spin_unlock_irq(&dev->wl->irq_lock); + } else + err = dfops->write(dev, buf, count); + if (err) + goto out_freepage; + +out_freepage: + free_page((unsigned long)buf); +out_unlock: + mutex_unlock(&dev->wl->mutex); + + return err ? err : count; +} + + +#define B43legacy_DEBUGFS_FOPS(name, _read, _write, _take_irqlock) \ + static struct b43legacy_debugfs_fops fops_##name = { \ + .read = _read, \ + .write = _write, \ + .fops = { \ + .open = simple_open, \ + .read = b43legacy_debugfs_read, \ + .write = b43legacy_debugfs_write, \ + .llseek = generic_file_llseek, \ + }, \ + .file_struct_offset = offsetof(struct b43legacy_dfsentry, \ + file_##name), \ + .take_irqlock = _take_irqlock, \ + } + +B43legacy_DEBUGFS_FOPS(tsf, tsf_read_file, tsf_write_file, 1); +B43legacy_DEBUGFS_FOPS(ucode_regs, ucode_regs_read_file, NULL, 1); +B43legacy_DEBUGFS_FOPS(shm, shm_read_file, NULL, 1); +B43legacy_DEBUGFS_FOPS(txstat, txstat_read_file, NULL, 0); +B43legacy_DEBUGFS_FOPS(restart, NULL, restart_write_file, 1); + + +int b43legacy_debug(struct b43legacy_wldev *dev, enum b43legacy_dyndbg feature) +{ + return !!(dev->dfsentry && dev->dfsentry->dyn_debug[feature]); +} + +static void b43legacy_remove_dynamic_debug(struct b43legacy_wldev *dev) +{ + struct b43legacy_dfsentry *e = dev->dfsentry; + int i; + + for (i = 0; i < __B43legacy_NR_DYNDBG; i++) + debugfs_remove(e->dyn_debug_dentries[i]); +} + +static void b43legacy_add_dynamic_debug(struct b43legacy_wldev *dev) +{ + struct b43legacy_dfsentry *e = dev->dfsentry; + struct dentry *d; + +#define add_dyn_dbg(name, id, initstate) do { \ + e->dyn_debug[id] = (initstate); \ + d = debugfs_create_bool(name, 0600, e->subdir, \ + &(e->dyn_debug[id])); \ + if (!IS_ERR(d)) \ + e->dyn_debug_dentries[id] = d; \ + } while (0) + + add_dyn_dbg("debug_xmitpower", B43legacy_DBG_XMITPOWER, 0); + add_dyn_dbg("debug_dmaoverflow", B43legacy_DBG_DMAOVERFLOW, 0); + add_dyn_dbg("debug_dmaverbose", B43legacy_DBG_DMAVERBOSE, 0); + add_dyn_dbg("debug_pwork_fast", B43legacy_DBG_PWORK_FAST, 0); + add_dyn_dbg("debug_pwork_stop", B43legacy_DBG_PWORK_STOP, 0); + +#undef add_dyn_dbg +} + +void b43legacy_debugfs_add_device(struct b43legacy_wldev *dev) +{ + struct b43legacy_dfsentry *e; + struct b43legacy_txstatus_log *log; + char devdir[16]; + + B43legacy_WARN_ON(!dev); + e = kzalloc(sizeof(*e), GFP_KERNEL); + if (!e) { + b43legacyerr(dev->wl, "debugfs: add device OOM\n"); + return; + } + e->dev = dev; + log = &e->txstatlog; + log->log = kcalloc(B43legacy_NR_LOGGED_TXSTATUS, + sizeof(struct b43legacy_txstatus), GFP_KERNEL); + if (!log->log) { + b43legacyerr(dev->wl, "debugfs: add device txstatus OOM\n"); + kfree(e); + return; + } + log->end = -1; + spin_lock_init(&log->lock); + + dev->dfsentry = e; + + snprintf(devdir, sizeof(devdir), "%s", wiphy_name(dev->wl->hw->wiphy)); + e->subdir = debugfs_create_dir(devdir, rootdir); + if (!e->subdir || IS_ERR(e->subdir)) { + if (e->subdir == ERR_PTR(-ENODEV)) { + b43legacydbg(dev->wl, "DebugFS (CONFIG_DEBUG_FS) not " + "enabled in kernel config\n"); + } else { + b43legacyerr(dev->wl, "debugfs: cannot create %s directory\n", + devdir); + } + dev->dfsentry = NULL; + kfree(log->log); + kfree(e); + return; + } + +#define ADD_FILE(name, mode) \ + do { \ + struct dentry *d; \ + d = debugfs_create_file(__stringify(name), \ + mode, e->subdir, dev, \ + &fops_##name.fops); \ + e->file_##name.dentry = NULL; \ + if (!IS_ERR(d)) \ + e->file_##name.dentry = d; \ + } while (0) + + + ADD_FILE(tsf, 0600); + ADD_FILE(ucode_regs, 0400); + ADD_FILE(shm, 0400); + ADD_FILE(txstat, 0400); + ADD_FILE(restart, 0200); + +#undef ADD_FILE + + b43legacy_add_dynamic_debug(dev); +} + +void b43legacy_debugfs_remove_device(struct b43legacy_wldev *dev) +{ + struct b43legacy_dfsentry *e; + + if (!dev) + return; + e = dev->dfsentry; + if (!e) + return; + b43legacy_remove_dynamic_debug(dev); + + debugfs_remove(e->file_tsf.dentry); + debugfs_remove(e->file_ucode_regs.dentry); + debugfs_remove(e->file_shm.dentry); + debugfs_remove(e->file_txstat.dentry); + debugfs_remove(e->file_restart.dentry); + + debugfs_remove(e->subdir); + kfree(e->txstatlog.log); + kfree(e); +} + +void b43legacy_debugfs_log_txstat(struct b43legacy_wldev *dev, + const struct b43legacy_txstatus *status) +{ + struct b43legacy_dfsentry *e = dev->dfsentry; + struct b43legacy_txstatus_log *log; + struct b43legacy_txstatus *cur; + int i; + + if (!e) + return; + log = &e->txstatlog; + B43legacy_WARN_ON(!irqs_disabled()); + spin_lock(&log->lock); + i = log->end + 1; + if (i == B43legacy_NR_LOGGED_TXSTATUS) + i = 0; + log->end = i; + cur = &(log->log[i]); + memcpy(cur, status, sizeof(*cur)); + spin_unlock(&log->lock); +} + +void b43legacy_debugfs_init(void) +{ + rootdir = debugfs_create_dir(KBUILD_MODNAME, NULL); + if (IS_ERR(rootdir)) + rootdir = NULL; +} + +void b43legacy_debugfs_exit(void) +{ + debugfs_remove(rootdir); +} diff --git a/drivers/net/wireless/b43legacy/debugfs.h b/drivers/net/wireless/b43legacy/debugfs.h new file mode 100644 index 0000000..26026dd --- /dev/null +++ b/drivers/net/wireless/b43legacy/debugfs.h @@ -0,0 +1,89 @@ +#ifndef B43legacy_DEBUGFS_H_ +#define B43legacy_DEBUGFS_H_ + +struct b43legacy_wldev; +struct b43legacy_txstatus; + +enum b43legacy_dyndbg { /* Dynamic debugging features */ + B43legacy_DBG_XMITPOWER, + B43legacy_DBG_DMAOVERFLOW, + B43legacy_DBG_DMAVERBOSE, + B43legacy_DBG_PWORK_FAST, + B43legacy_DBG_PWORK_STOP, + __B43legacy_NR_DYNDBG, +}; + + +#ifdef CPTCFG_B43LEGACY_DEBUG + +struct dentry; + +#define B43legacy_NR_LOGGED_TXSTATUS 100 + +struct b43legacy_txstatus_log { + struct b43legacy_txstatus *log; + int end; + spinlock_t lock; /* lock for debugging */ +}; + +struct b43legacy_dfs_file { + struct dentry *dentry; + char *buffer; + size_t data_len; +}; + +struct b43legacy_dfsentry { + struct b43legacy_wldev *dev; + struct dentry *subdir; + + struct b43legacy_dfs_file file_tsf; + struct b43legacy_dfs_file file_ucode_regs; + struct b43legacy_dfs_file file_shm; + struct b43legacy_dfs_file file_txstat; + struct b43legacy_dfs_file file_txpower_g; + struct b43legacy_dfs_file file_restart; + struct b43legacy_dfs_file file_loctls; + + struct b43legacy_txstatus_log txstatlog; + + /* Enabled/Disabled list for the dynamic debugging features. */ + u32 dyn_debug[__B43legacy_NR_DYNDBG]; + /* Dentries for the dynamic debugging entries. */ + struct dentry *dyn_debug_dentries[__B43legacy_NR_DYNDBG]; +}; + +int b43legacy_debug(struct b43legacy_wldev *dev, + enum b43legacy_dyndbg feature); + +void b43legacy_debugfs_init(void); +void b43legacy_debugfs_exit(void); +void b43legacy_debugfs_add_device(struct b43legacy_wldev *dev); +void b43legacy_debugfs_remove_device(struct b43legacy_wldev *dev); +void b43legacy_debugfs_log_txstat(struct b43legacy_wldev *dev, + const struct b43legacy_txstatus *status); + +#else /* CPTCFG_B43LEGACY_DEBUG*/ + +static inline +int b43legacy_debug(struct b43legacy_wldev *dev, + enum b43legacy_dyndbg feature) +{ + return 0; +} + +static inline +void b43legacy_debugfs_init(void) { } +static inline +void b43legacy_debugfs_exit(void) { } +static inline +void b43legacy_debugfs_add_device(struct b43legacy_wldev *dev) { } +static inline +void b43legacy_debugfs_remove_device(struct b43legacy_wldev *dev) { } +static inline +void b43legacy_debugfs_log_txstat(struct b43legacy_wldev *dev, + const struct b43legacy_txstatus *status) + { } + +#endif /* CPTCFG_B43LEGACY_DEBUG*/ + +#endif /* B43legacy_DEBUGFS_H_ */ diff --git a/drivers/net/wireless/b43legacy/dma.c b/drivers/net/wireless/b43legacy/dma.c new file mode 100644 index 0000000..9c93c47 --- /dev/null +++ b/drivers/net/wireless/b43legacy/dma.c @@ -0,0 +1,1459 @@ +/* + + Broadcom B43legacy wireless driver + + DMA ringbuffer and descriptor allocation/management + + Copyright (c) 2005, 2006 Michael Buesch + + Some code in this file is derived from the b44.c driver + Copyright (C) 2002 David S. Miller + Copyright (C) Pekka Pietikainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43legacy.h" +#include "dma.h" +#include "main.h" +#include "debugfs.h" +#include "xmit.h" + +#include +#include +#include +#include +#include +#include + +/* 32bit DMA ops. */ +static +struct b43legacy_dmadesc32 *op32_idx2desc(struct b43legacy_dmaring *ring, + int slot, + struct b43legacy_dmadesc_meta **meta) +{ + struct b43legacy_dmadesc32 *desc; + + *meta = &(ring->meta[slot]); + desc = ring->descbase; + desc = &(desc[slot]); + + return desc; +} + +static void op32_fill_descriptor(struct b43legacy_dmaring *ring, + struct b43legacy_dmadesc32 *desc, + dma_addr_t dmaaddr, u16 bufsize, + int start, int end, int irq) +{ + struct b43legacy_dmadesc32 *descbase = ring->descbase; + int slot; + u32 ctl; + u32 addr; + u32 addrext; + + slot = (int)(desc - descbase); + B43legacy_WARN_ON(!(slot >= 0 && slot < ring->nr_slots)); + + addr = (u32)(dmaaddr & ~SSB_DMA_TRANSLATION_MASK); + addrext = (u32)(dmaaddr & SSB_DMA_TRANSLATION_MASK) + >> SSB_DMA_TRANSLATION_SHIFT; + addr |= ring->dev->dma.translation; + ctl = (bufsize - ring->frameoffset) + & B43legacy_DMA32_DCTL_BYTECNT; + if (slot == ring->nr_slots - 1) + ctl |= B43legacy_DMA32_DCTL_DTABLEEND; + if (start) + ctl |= B43legacy_DMA32_DCTL_FRAMESTART; + if (end) + ctl |= B43legacy_DMA32_DCTL_FRAMEEND; + if (irq) + ctl |= B43legacy_DMA32_DCTL_IRQ; + ctl |= (addrext << B43legacy_DMA32_DCTL_ADDREXT_SHIFT) + & B43legacy_DMA32_DCTL_ADDREXT_MASK; + + desc->control = cpu_to_le32(ctl); + desc->address = cpu_to_le32(addr); +} + +static void op32_poke_tx(struct b43legacy_dmaring *ring, int slot) +{ + b43legacy_dma_write(ring, B43legacy_DMA32_TXINDEX, + (u32)(slot * sizeof(struct b43legacy_dmadesc32))); +} + +static void op32_tx_suspend(struct b43legacy_dmaring *ring) +{ + b43legacy_dma_write(ring, B43legacy_DMA32_TXCTL, + b43legacy_dma_read(ring, B43legacy_DMA32_TXCTL) + | B43legacy_DMA32_TXSUSPEND); +} + +static void op32_tx_resume(struct b43legacy_dmaring *ring) +{ + b43legacy_dma_write(ring, B43legacy_DMA32_TXCTL, + b43legacy_dma_read(ring, B43legacy_DMA32_TXCTL) + & ~B43legacy_DMA32_TXSUSPEND); +} + +static int op32_get_current_rxslot(struct b43legacy_dmaring *ring) +{ + u32 val; + + val = b43legacy_dma_read(ring, B43legacy_DMA32_RXSTATUS); + val &= B43legacy_DMA32_RXDPTR; + + return (val / sizeof(struct b43legacy_dmadesc32)); +} + +static void op32_set_current_rxslot(struct b43legacy_dmaring *ring, + int slot) +{ + b43legacy_dma_write(ring, B43legacy_DMA32_RXINDEX, + (u32)(slot * sizeof(struct b43legacy_dmadesc32))); +} + +static inline int free_slots(struct b43legacy_dmaring *ring) +{ + return (ring->nr_slots - ring->used_slots); +} + +static inline int next_slot(struct b43legacy_dmaring *ring, int slot) +{ + B43legacy_WARN_ON(!(slot >= -1 && slot <= ring->nr_slots - 1)); + if (slot == ring->nr_slots - 1) + return 0; + return slot + 1; +} + +static inline int prev_slot(struct b43legacy_dmaring *ring, int slot) +{ + B43legacy_WARN_ON(!(slot >= 0 && slot <= ring->nr_slots - 1)); + if (slot == 0) + return ring->nr_slots - 1; + return slot - 1; +} + +#ifdef CPTCFG_B43LEGACY_DEBUG +static void update_max_used_slots(struct b43legacy_dmaring *ring, + int current_used_slots) +{ + if (current_used_slots <= ring->max_used_slots) + return; + ring->max_used_slots = current_used_slots; + if (b43legacy_debug(ring->dev, B43legacy_DBG_DMAVERBOSE)) + b43legacydbg(ring->dev->wl, + "max_used_slots increased to %d on %s ring %d\n", + ring->max_used_slots, + ring->tx ? "TX" : "RX", + ring->index); +} +#else +static inline +void update_max_used_slots(struct b43legacy_dmaring *ring, + int current_used_slots) +{ } +#endif /* DEBUG */ + +/* Request a slot for usage. */ +static inline +int request_slot(struct b43legacy_dmaring *ring) +{ + int slot; + + B43legacy_WARN_ON(!ring->tx); + B43legacy_WARN_ON(ring->stopped); + B43legacy_WARN_ON(free_slots(ring) == 0); + + slot = next_slot(ring, ring->current_slot); + ring->current_slot = slot; + ring->used_slots++; + + update_max_used_slots(ring, ring->used_slots); + + return slot; +} + +/* Mac80211-queue to b43legacy-ring mapping */ +static struct b43legacy_dmaring *priority_to_txring( + struct b43legacy_wldev *dev, + int queue_priority) +{ + struct b43legacy_dmaring *ring; + +/*FIXME: For now we always run on TX-ring-1 */ +return dev->dma.tx_ring1; + + /* 0 = highest priority */ + switch (queue_priority) { + default: + B43legacy_WARN_ON(1); + /* fallthrough */ + case 0: + ring = dev->dma.tx_ring3; + break; + case 1: + ring = dev->dma.tx_ring2; + break; + case 2: + ring = dev->dma.tx_ring1; + break; + case 3: + ring = dev->dma.tx_ring0; + break; + case 4: + ring = dev->dma.tx_ring4; + break; + case 5: + ring = dev->dma.tx_ring5; + break; + } + + return ring; +} + +/* Bcm4301-ring to mac80211-queue mapping */ +static inline int txring_to_priority(struct b43legacy_dmaring *ring) +{ + static const u8 idx_to_prio[] = + { 3, 2, 1, 0, 4, 5, }; + +/*FIXME: have only one queue, for now */ +return 0; + + return idx_to_prio[ring->index]; +} + + +static u16 b43legacy_dmacontroller_base(enum b43legacy_dmatype type, + int controller_idx) +{ + static const u16 map32[] = { + B43legacy_MMIO_DMA32_BASE0, + B43legacy_MMIO_DMA32_BASE1, + B43legacy_MMIO_DMA32_BASE2, + B43legacy_MMIO_DMA32_BASE3, + B43legacy_MMIO_DMA32_BASE4, + B43legacy_MMIO_DMA32_BASE5, + }; + + B43legacy_WARN_ON(!(controller_idx >= 0 && + controller_idx < ARRAY_SIZE(map32))); + return map32[controller_idx]; +} + +static inline +dma_addr_t map_descbuffer(struct b43legacy_dmaring *ring, + unsigned char *buf, + size_t len, + int tx) +{ + dma_addr_t dmaaddr; + + if (tx) + dmaaddr = dma_map_single(ring->dev->dev->dma_dev, + buf, len, + DMA_TO_DEVICE); + else + dmaaddr = dma_map_single(ring->dev->dev->dma_dev, + buf, len, + DMA_FROM_DEVICE); + + return dmaaddr; +} + +static inline +void unmap_descbuffer(struct b43legacy_dmaring *ring, + dma_addr_t addr, + size_t len, + int tx) +{ + if (tx) + dma_unmap_single(ring->dev->dev->dma_dev, + addr, len, + DMA_TO_DEVICE); + else + dma_unmap_single(ring->dev->dev->dma_dev, + addr, len, + DMA_FROM_DEVICE); +} + +static inline +void sync_descbuffer_for_cpu(struct b43legacy_dmaring *ring, + dma_addr_t addr, + size_t len) +{ + B43legacy_WARN_ON(ring->tx); + + dma_sync_single_for_cpu(ring->dev->dev->dma_dev, + addr, len, DMA_FROM_DEVICE); +} + +static inline +void sync_descbuffer_for_device(struct b43legacy_dmaring *ring, + dma_addr_t addr, + size_t len) +{ + B43legacy_WARN_ON(ring->tx); + + dma_sync_single_for_device(ring->dev->dev->dma_dev, + addr, len, DMA_FROM_DEVICE); +} + +static inline +void free_descriptor_buffer(struct b43legacy_dmaring *ring, + struct b43legacy_dmadesc_meta *meta, + int irq_context) +{ + if (meta->skb) { + if (irq_context) + dev_kfree_skb_irq(meta->skb); + else + dev_kfree_skb(meta->skb); + meta->skb = NULL; + } +} + +static int alloc_ringmemory(struct b43legacy_dmaring *ring) +{ + /* GFP flags must match the flags in free_ringmemory()! */ + ring->descbase = dma_alloc_coherent(ring->dev->dev->dma_dev, + B43legacy_DMA_RINGMEMSIZE, + &(ring->dmabase), + GFP_KERNEL | __GFP_ZERO); + if (!ring->descbase) + return -ENOMEM; + + return 0; +} + +static void free_ringmemory(struct b43legacy_dmaring *ring) +{ + dma_free_coherent(ring->dev->dev->dma_dev, B43legacy_DMA_RINGMEMSIZE, + ring->descbase, ring->dmabase); +} + +/* Reset the RX DMA channel */ +static int b43legacy_dmacontroller_rx_reset(struct b43legacy_wldev *dev, + u16 mmio_base, + enum b43legacy_dmatype type) +{ + int i; + u32 value; + u16 offset; + + might_sleep(); + + offset = B43legacy_DMA32_RXCTL; + b43legacy_write32(dev, mmio_base + offset, 0); + for (i = 0; i < 10; i++) { + offset = B43legacy_DMA32_RXSTATUS; + value = b43legacy_read32(dev, mmio_base + offset); + value &= B43legacy_DMA32_RXSTATE; + if (value == B43legacy_DMA32_RXSTAT_DISABLED) { + i = -1; + break; + } + msleep(1); + } + if (i != -1) { + b43legacyerr(dev->wl, "DMA RX reset timed out\n"); + return -ENODEV; + } + + return 0; +} + +/* Reset the RX DMA channel */ +static int b43legacy_dmacontroller_tx_reset(struct b43legacy_wldev *dev, + u16 mmio_base, + enum b43legacy_dmatype type) +{ + int i; + u32 value; + u16 offset; + + might_sleep(); + + for (i = 0; i < 10; i++) { + offset = B43legacy_DMA32_TXSTATUS; + value = b43legacy_read32(dev, mmio_base + offset); + value &= B43legacy_DMA32_TXSTATE; + if (value == B43legacy_DMA32_TXSTAT_DISABLED || + value == B43legacy_DMA32_TXSTAT_IDLEWAIT || + value == B43legacy_DMA32_TXSTAT_STOPPED) + break; + msleep(1); + } + offset = B43legacy_DMA32_TXCTL; + b43legacy_write32(dev, mmio_base + offset, 0); + for (i = 0; i < 10; i++) { + offset = B43legacy_DMA32_TXSTATUS; + value = b43legacy_read32(dev, mmio_base + offset); + value &= B43legacy_DMA32_TXSTATE; + if (value == B43legacy_DMA32_TXSTAT_DISABLED) { + i = -1; + break; + } + msleep(1); + } + if (i != -1) { + b43legacyerr(dev->wl, "DMA TX reset timed out\n"); + return -ENODEV; + } + /* ensure the reset is completed. */ + msleep(1); + + return 0; +} + +/* Check if a DMA mapping address is invalid. */ +static bool b43legacy_dma_mapping_error(struct b43legacy_dmaring *ring, + dma_addr_t addr, + size_t buffersize, + bool dma_to_device) +{ + if (unlikely(dma_mapping_error(ring->dev->dev->dma_dev, addr))) + return 1; + + switch (ring->type) { + case B43legacy_DMA_30BIT: + if ((u64)addr + buffersize > (1ULL << 30)) + goto address_error; + break; + case B43legacy_DMA_32BIT: + if ((u64)addr + buffersize > (1ULL << 32)) + goto address_error; + break; + } + + /* The address is OK. */ + return 0; + +address_error: + /* We can't support this address. Unmap it again. */ + unmap_descbuffer(ring, addr, buffersize, dma_to_device); + + return 1; +} + +static int setup_rx_descbuffer(struct b43legacy_dmaring *ring, + struct b43legacy_dmadesc32 *desc, + struct b43legacy_dmadesc_meta *meta, + gfp_t gfp_flags) +{ + struct b43legacy_rxhdr_fw3 *rxhdr; + struct b43legacy_hwtxstatus *txstat; + dma_addr_t dmaaddr; + struct sk_buff *skb; + + B43legacy_WARN_ON(ring->tx); + + skb = __dev_alloc_skb(ring->rx_buffersize, gfp_flags); + if (unlikely(!skb)) + return -ENOMEM; + dmaaddr = map_descbuffer(ring, skb->data, + ring->rx_buffersize, 0); + if (b43legacy_dma_mapping_error(ring, dmaaddr, ring->rx_buffersize, 0)) { + /* ugh. try to realloc in zone_dma */ + gfp_flags |= GFP_DMA; + + dev_kfree_skb_any(skb); + + skb = __dev_alloc_skb(ring->rx_buffersize, gfp_flags); + if (unlikely(!skb)) + return -ENOMEM; + dmaaddr = map_descbuffer(ring, skb->data, + ring->rx_buffersize, 0); + } + + if (b43legacy_dma_mapping_error(ring, dmaaddr, ring->rx_buffersize, 0)) { + dev_kfree_skb_any(skb); + return -EIO; + } + + meta->skb = skb; + meta->dmaaddr = dmaaddr; + op32_fill_descriptor(ring, desc, dmaaddr, ring->rx_buffersize, 0, 0, 0); + + rxhdr = (struct b43legacy_rxhdr_fw3 *)(skb->data); + rxhdr->frame_len = 0; + txstat = (struct b43legacy_hwtxstatus *)(skb->data); + txstat->cookie = 0; + + return 0; +} + +/* Allocate the initial descbuffers. + * This is used for an RX ring only. + */ +static int alloc_initial_descbuffers(struct b43legacy_dmaring *ring) +{ + int i; + int err = -ENOMEM; + struct b43legacy_dmadesc32 *desc; + struct b43legacy_dmadesc_meta *meta; + + for (i = 0; i < ring->nr_slots; i++) { + desc = op32_idx2desc(ring, i, &meta); + + err = setup_rx_descbuffer(ring, desc, meta, GFP_KERNEL); + if (err) { + b43legacyerr(ring->dev->wl, + "Failed to allocate initial descbuffers\n"); + goto err_unwind; + } + } + mb(); /* all descbuffer setup before next line */ + ring->used_slots = ring->nr_slots; + err = 0; +out: + return err; + +err_unwind: + for (i--; i >= 0; i--) { + desc = op32_idx2desc(ring, i, &meta); + + unmap_descbuffer(ring, meta->dmaaddr, ring->rx_buffersize, 0); + dev_kfree_skb(meta->skb); + } + goto out; +} + +/* Do initial setup of the DMA controller. + * Reset the controller, write the ring busaddress + * and switch the "enable" bit on. + */ +static int dmacontroller_setup(struct b43legacy_dmaring *ring) +{ + int err = 0; + u32 value; + u32 addrext; + u32 trans = ring->dev->dma.translation; + u32 ringbase = (u32)(ring->dmabase); + + if (ring->tx) { + addrext = (ringbase & SSB_DMA_TRANSLATION_MASK) + >> SSB_DMA_TRANSLATION_SHIFT; + value = B43legacy_DMA32_TXENABLE; + value |= (addrext << B43legacy_DMA32_TXADDREXT_SHIFT) + & B43legacy_DMA32_TXADDREXT_MASK; + b43legacy_dma_write(ring, B43legacy_DMA32_TXCTL, value); + b43legacy_dma_write(ring, B43legacy_DMA32_TXRING, + (ringbase & ~SSB_DMA_TRANSLATION_MASK) + | trans); + } else { + err = alloc_initial_descbuffers(ring); + if (err) + goto out; + + addrext = (ringbase & SSB_DMA_TRANSLATION_MASK) + >> SSB_DMA_TRANSLATION_SHIFT; + value = (ring->frameoffset << + B43legacy_DMA32_RXFROFF_SHIFT); + value |= B43legacy_DMA32_RXENABLE; + value |= (addrext << B43legacy_DMA32_RXADDREXT_SHIFT) + & B43legacy_DMA32_RXADDREXT_MASK; + b43legacy_dma_write(ring, B43legacy_DMA32_RXCTL, value); + b43legacy_dma_write(ring, B43legacy_DMA32_RXRING, + (ringbase & ~SSB_DMA_TRANSLATION_MASK) + | trans); + b43legacy_dma_write(ring, B43legacy_DMA32_RXINDEX, 200); + } + +out: + return err; +} + +/* Shutdown the DMA controller. */ +static void dmacontroller_cleanup(struct b43legacy_dmaring *ring) +{ + if (ring->tx) { + b43legacy_dmacontroller_tx_reset(ring->dev, ring->mmio_base, + ring->type); + b43legacy_dma_write(ring, B43legacy_DMA32_TXRING, 0); + } else { + b43legacy_dmacontroller_rx_reset(ring->dev, ring->mmio_base, + ring->type); + b43legacy_dma_write(ring, B43legacy_DMA32_RXRING, 0); + } +} + +static void free_all_descbuffers(struct b43legacy_dmaring *ring) +{ + struct b43legacy_dmadesc_meta *meta; + int i; + + if (!ring->used_slots) + return; + for (i = 0; i < ring->nr_slots; i++) { + op32_idx2desc(ring, i, &meta); + + if (!meta->skb) { + B43legacy_WARN_ON(!ring->tx); + continue; + } + if (ring->tx) + unmap_descbuffer(ring, meta->dmaaddr, + meta->skb->len, 1); + else + unmap_descbuffer(ring, meta->dmaaddr, + ring->rx_buffersize, 0); + free_descriptor_buffer(ring, meta, 0); + } +} + +static u64 supported_dma_mask(struct b43legacy_wldev *dev) +{ + u32 tmp; + u16 mmio_base; + + mmio_base = b43legacy_dmacontroller_base(0, 0); + b43legacy_write32(dev, + mmio_base + B43legacy_DMA32_TXCTL, + B43legacy_DMA32_TXADDREXT_MASK); + tmp = b43legacy_read32(dev, mmio_base + + B43legacy_DMA32_TXCTL); + if (tmp & B43legacy_DMA32_TXADDREXT_MASK) + return DMA_BIT_MASK(32); + + return DMA_BIT_MASK(30); +} + +static enum b43legacy_dmatype dma_mask_to_engine_type(u64 dmamask) +{ + if (dmamask == DMA_BIT_MASK(30)) + return B43legacy_DMA_30BIT; + if (dmamask == DMA_BIT_MASK(32)) + return B43legacy_DMA_32BIT; + B43legacy_WARN_ON(1); + return B43legacy_DMA_30BIT; +} + +/* Main initialization function. */ +static +struct b43legacy_dmaring *b43legacy_setup_dmaring(struct b43legacy_wldev *dev, + int controller_index, + int for_tx, + enum b43legacy_dmatype type) +{ + struct b43legacy_dmaring *ring; + int err; + int nr_slots; + dma_addr_t dma_test; + + ring = kzalloc(sizeof(*ring), GFP_KERNEL); + if (!ring) + goto out; + ring->type = type; + ring->dev = dev; + + nr_slots = B43legacy_RXRING_SLOTS; + if (for_tx) + nr_slots = B43legacy_TXRING_SLOTS; + + ring->meta = kcalloc(nr_slots, sizeof(struct b43legacy_dmadesc_meta), + GFP_KERNEL); + if (!ring->meta) + goto err_kfree_ring; + if (for_tx) { + ring->txhdr_cache = kcalloc(nr_slots, + sizeof(struct b43legacy_txhdr_fw3), + GFP_KERNEL); + if (!ring->txhdr_cache) + goto err_kfree_meta; + + /* test for ability to dma to txhdr_cache */ + dma_test = dma_map_single(dev->dev->dma_dev, ring->txhdr_cache, + sizeof(struct b43legacy_txhdr_fw3), + DMA_TO_DEVICE); + + if (b43legacy_dma_mapping_error(ring, dma_test, + sizeof(struct b43legacy_txhdr_fw3), 1)) { + /* ugh realloc */ + kfree(ring->txhdr_cache); + ring->txhdr_cache = kcalloc(nr_slots, + sizeof(struct b43legacy_txhdr_fw3), + GFP_KERNEL | GFP_DMA); + if (!ring->txhdr_cache) + goto err_kfree_meta; + + dma_test = dma_map_single(dev->dev->dma_dev, + ring->txhdr_cache, + sizeof(struct b43legacy_txhdr_fw3), + DMA_TO_DEVICE); + + if (b43legacy_dma_mapping_error(ring, dma_test, + sizeof(struct b43legacy_txhdr_fw3), 1)) + goto err_kfree_txhdr_cache; + } + + dma_unmap_single(dev->dev->dma_dev, dma_test, + sizeof(struct b43legacy_txhdr_fw3), + DMA_TO_DEVICE); + } + + ring->nr_slots = nr_slots; + ring->mmio_base = b43legacy_dmacontroller_base(type, controller_index); + ring->index = controller_index; + if (for_tx) { + ring->tx = true; + ring->current_slot = -1; + } else { + if (ring->index == 0) { + ring->rx_buffersize = B43legacy_DMA0_RX_BUFFERSIZE; + ring->frameoffset = B43legacy_DMA0_RX_FRAMEOFFSET; + } else if (ring->index == 3) { + ring->rx_buffersize = B43legacy_DMA3_RX_BUFFERSIZE; + ring->frameoffset = B43legacy_DMA3_RX_FRAMEOFFSET; + } else + B43legacy_WARN_ON(1); + } +#ifdef CPTCFG_B43LEGACY_DEBUG + ring->last_injected_overflow = jiffies; +#endif + + err = alloc_ringmemory(ring); + if (err) + goto err_kfree_txhdr_cache; + err = dmacontroller_setup(ring); + if (err) + goto err_free_ringmemory; + +out: + return ring; + +err_free_ringmemory: + free_ringmemory(ring); +err_kfree_txhdr_cache: + kfree(ring->txhdr_cache); +err_kfree_meta: + kfree(ring->meta); +err_kfree_ring: + kfree(ring); + ring = NULL; + goto out; +} + +/* Main cleanup function. */ +static void b43legacy_destroy_dmaring(struct b43legacy_dmaring *ring) +{ + if (!ring) + return; + + b43legacydbg(ring->dev->wl, "DMA-%u 0x%04X (%s) max used slots:" + " %d/%d\n", (unsigned int)(ring->type), ring->mmio_base, + (ring->tx) ? "TX" : "RX", ring->max_used_slots, + ring->nr_slots); + /* Device IRQs are disabled prior entering this function, + * so no need to take care of concurrency with rx handler stuff. + */ + dmacontroller_cleanup(ring); + free_all_descbuffers(ring); + free_ringmemory(ring); + + kfree(ring->txhdr_cache); + kfree(ring->meta); + kfree(ring); +} + +void b43legacy_dma_free(struct b43legacy_wldev *dev) +{ + struct b43legacy_dma *dma; + + if (b43legacy_using_pio(dev)) + return; + dma = &dev->dma; + + b43legacy_destroy_dmaring(dma->rx_ring3); + dma->rx_ring3 = NULL; + b43legacy_destroy_dmaring(dma->rx_ring0); + dma->rx_ring0 = NULL; + + b43legacy_destroy_dmaring(dma->tx_ring5); + dma->tx_ring5 = NULL; + b43legacy_destroy_dmaring(dma->tx_ring4); + dma->tx_ring4 = NULL; + b43legacy_destroy_dmaring(dma->tx_ring3); + dma->tx_ring3 = NULL; + b43legacy_destroy_dmaring(dma->tx_ring2); + dma->tx_ring2 = NULL; + b43legacy_destroy_dmaring(dma->tx_ring1); + dma->tx_ring1 = NULL; + b43legacy_destroy_dmaring(dma->tx_ring0); + dma->tx_ring0 = NULL; +} + +static int b43legacy_dma_set_mask(struct b43legacy_wldev *dev, u64 mask) +{ + u64 orig_mask = mask; + bool fallback = false; + int err; + + /* Try to set the DMA mask. If it fails, try falling back to a + * lower mask, as we can always also support a lower one. */ + while (1) { + err = dma_set_mask(dev->dev->dma_dev, mask); + if (!err) { + err = dma_set_coherent_mask(dev->dev->dma_dev, mask); + if (!err) + break; + } + if (mask == DMA_BIT_MASK(64)) { + mask = DMA_BIT_MASK(32); + fallback = true; + continue; + } + if (mask == DMA_BIT_MASK(32)) { + mask = DMA_BIT_MASK(30); + fallback = true; + continue; + } + b43legacyerr(dev->wl, "The machine/kernel does not support " + "the required %u-bit DMA mask\n", + (unsigned int)dma_mask_to_engine_type(orig_mask)); + return -EOPNOTSUPP; + } + if (fallback) { + b43legacyinfo(dev->wl, "DMA mask fallback from %u-bit to %u-" + "bit\n", + (unsigned int)dma_mask_to_engine_type(orig_mask), + (unsigned int)dma_mask_to_engine_type(mask)); + } + + return 0; +} + +int b43legacy_dma_init(struct b43legacy_wldev *dev) +{ + struct b43legacy_dma *dma = &dev->dma; + struct b43legacy_dmaring *ring; + int err; + u64 dmamask; + enum b43legacy_dmatype type; + + dmamask = supported_dma_mask(dev); + type = dma_mask_to_engine_type(dmamask); + err = b43legacy_dma_set_mask(dev, dmamask); + if (err) { +#ifdef CPTCFG_B43LEGACY_PIO + b43legacywarn(dev->wl, "DMA for this device not supported. " + "Falling back to PIO\n"); + dev->__using_pio = true; + return -EAGAIN; +#else + b43legacyerr(dev->wl, "DMA for this device not supported and " + "no PIO support compiled in\n"); + return -EOPNOTSUPP; +#endif + } + dma->translation = ssb_dma_translation(dev->dev); + + err = -ENOMEM; + /* setup TX DMA channels. */ + ring = b43legacy_setup_dmaring(dev, 0, 1, type); + if (!ring) + goto out; + dma->tx_ring0 = ring; + + ring = b43legacy_setup_dmaring(dev, 1, 1, type); + if (!ring) + goto err_destroy_tx0; + dma->tx_ring1 = ring; + + ring = b43legacy_setup_dmaring(dev, 2, 1, type); + if (!ring) + goto err_destroy_tx1; + dma->tx_ring2 = ring; + + ring = b43legacy_setup_dmaring(dev, 3, 1, type); + if (!ring) + goto err_destroy_tx2; + dma->tx_ring3 = ring; + + ring = b43legacy_setup_dmaring(dev, 4, 1, type); + if (!ring) + goto err_destroy_tx3; + dma->tx_ring4 = ring; + + ring = b43legacy_setup_dmaring(dev, 5, 1, type); + if (!ring) + goto err_destroy_tx4; + dma->tx_ring5 = ring; + + /* setup RX DMA channels. */ + ring = b43legacy_setup_dmaring(dev, 0, 0, type); + if (!ring) + goto err_destroy_tx5; + dma->rx_ring0 = ring; + + if (dev->dev->id.revision < 5) { + ring = b43legacy_setup_dmaring(dev, 3, 0, type); + if (!ring) + goto err_destroy_rx0; + dma->rx_ring3 = ring; + } + + b43legacydbg(dev->wl, "%u-bit DMA initialized\n", (unsigned int)type); + err = 0; +out: + return err; + +err_destroy_rx0: + b43legacy_destroy_dmaring(dma->rx_ring0); + dma->rx_ring0 = NULL; +err_destroy_tx5: + b43legacy_destroy_dmaring(dma->tx_ring5); + dma->tx_ring5 = NULL; +err_destroy_tx4: + b43legacy_destroy_dmaring(dma->tx_ring4); + dma->tx_ring4 = NULL; +err_destroy_tx3: + b43legacy_destroy_dmaring(dma->tx_ring3); + dma->tx_ring3 = NULL; +err_destroy_tx2: + b43legacy_destroy_dmaring(dma->tx_ring2); + dma->tx_ring2 = NULL; +err_destroy_tx1: + b43legacy_destroy_dmaring(dma->tx_ring1); + dma->tx_ring1 = NULL; +err_destroy_tx0: + b43legacy_destroy_dmaring(dma->tx_ring0); + dma->tx_ring0 = NULL; + goto out; +} + +/* Generate a cookie for the TX header. */ +static u16 generate_cookie(struct b43legacy_dmaring *ring, + int slot) +{ + u16 cookie = 0x1000; + + /* Use the upper 4 bits of the cookie as + * DMA controller ID and store the slot number + * in the lower 12 bits. + * Note that the cookie must never be 0, as this + * is a special value used in RX path. + */ + switch (ring->index) { + case 0: + cookie = 0xA000; + break; + case 1: + cookie = 0xB000; + break; + case 2: + cookie = 0xC000; + break; + case 3: + cookie = 0xD000; + break; + case 4: + cookie = 0xE000; + break; + case 5: + cookie = 0xF000; + break; + } + B43legacy_WARN_ON(!(((u16)slot & 0xF000) == 0x0000)); + cookie |= (u16)slot; + + return cookie; +} + +/* Inspect a cookie and find out to which controller/slot it belongs. */ +static +struct b43legacy_dmaring *parse_cookie(struct b43legacy_wldev *dev, + u16 cookie, int *slot) +{ + struct b43legacy_dma *dma = &dev->dma; + struct b43legacy_dmaring *ring = NULL; + + switch (cookie & 0xF000) { + case 0xA000: + ring = dma->tx_ring0; + break; + case 0xB000: + ring = dma->tx_ring1; + break; + case 0xC000: + ring = dma->tx_ring2; + break; + case 0xD000: + ring = dma->tx_ring3; + break; + case 0xE000: + ring = dma->tx_ring4; + break; + case 0xF000: + ring = dma->tx_ring5; + break; + default: + B43legacy_WARN_ON(1); + } + *slot = (cookie & 0x0FFF); + B43legacy_WARN_ON(!(ring && *slot >= 0 && *slot < ring->nr_slots)); + + return ring; +} + +static int dma_tx_fragment(struct b43legacy_dmaring *ring, + struct sk_buff **in_skb) +{ + struct sk_buff *skb = *in_skb; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + u8 *header; + int slot, old_top_slot, old_used_slots; + int err; + struct b43legacy_dmadesc32 *desc; + struct b43legacy_dmadesc_meta *meta; + struct b43legacy_dmadesc_meta *meta_hdr; + struct sk_buff *bounce_skb; + +#define SLOTS_PER_PACKET 2 + B43legacy_WARN_ON(skb_shinfo(skb)->nr_frags != 0); + + old_top_slot = ring->current_slot; + old_used_slots = ring->used_slots; + + /* Get a slot for the header. */ + slot = request_slot(ring); + desc = op32_idx2desc(ring, slot, &meta_hdr); + memset(meta_hdr, 0, sizeof(*meta_hdr)); + + header = &(ring->txhdr_cache[slot * sizeof( + struct b43legacy_txhdr_fw3)]); + err = b43legacy_generate_txhdr(ring->dev, header, + skb->data, skb->len, info, + generate_cookie(ring, slot)); + if (unlikely(err)) { + ring->current_slot = old_top_slot; + ring->used_slots = old_used_slots; + return err; + } + + meta_hdr->dmaaddr = map_descbuffer(ring, (unsigned char *)header, + sizeof(struct b43legacy_txhdr_fw3), 1); + if (b43legacy_dma_mapping_error(ring, meta_hdr->dmaaddr, + sizeof(struct b43legacy_txhdr_fw3), 1)) { + ring->current_slot = old_top_slot; + ring->used_slots = old_used_slots; + return -EIO; + } + op32_fill_descriptor(ring, desc, meta_hdr->dmaaddr, + sizeof(struct b43legacy_txhdr_fw3), 1, 0, 0); + + /* Get a slot for the payload. */ + slot = request_slot(ring); + desc = op32_idx2desc(ring, slot, &meta); + memset(meta, 0, sizeof(*meta)); + + meta->skb = skb; + meta->is_last_fragment = true; + + meta->dmaaddr = map_descbuffer(ring, skb->data, skb->len, 1); + /* create a bounce buffer in zone_dma on mapping failure. */ + if (b43legacy_dma_mapping_error(ring, meta->dmaaddr, skb->len, 1)) { + bounce_skb = alloc_skb(skb->len, GFP_ATOMIC | GFP_DMA); + if (!bounce_skb) { + ring->current_slot = old_top_slot; + ring->used_slots = old_used_slots; + err = -ENOMEM; + goto out_unmap_hdr; + } + + memcpy(skb_put(bounce_skb, skb->len), skb->data, skb->len); + memcpy(bounce_skb->cb, skb->cb, sizeof(skb->cb)); + bounce_skb->dev = skb->dev; + skb_set_queue_mapping(bounce_skb, skb_get_queue_mapping(skb)); + info = IEEE80211_SKB_CB(bounce_skb); + + dev_kfree_skb_any(skb); + skb = bounce_skb; + *in_skb = bounce_skb; + meta->skb = skb; + meta->dmaaddr = map_descbuffer(ring, skb->data, skb->len, 1); + if (b43legacy_dma_mapping_error(ring, meta->dmaaddr, skb->len, 1)) { + ring->current_slot = old_top_slot; + ring->used_slots = old_used_slots; + err = -EIO; + goto out_free_bounce; + } + } + + op32_fill_descriptor(ring, desc, meta->dmaaddr, + skb->len, 0, 1, 1); + + wmb(); /* previous stuff MUST be done */ + /* Now transfer the whole frame. */ + op32_poke_tx(ring, next_slot(ring, slot)); + return 0; + +out_free_bounce: + dev_kfree_skb_any(skb); +out_unmap_hdr: + unmap_descbuffer(ring, meta_hdr->dmaaddr, + sizeof(struct b43legacy_txhdr_fw3), 1); + return err; +} + +static inline +int should_inject_overflow(struct b43legacy_dmaring *ring) +{ +#ifdef CPTCFG_B43LEGACY_DEBUG + if (unlikely(b43legacy_debug(ring->dev, + B43legacy_DBG_DMAOVERFLOW))) { + /* Check if we should inject another ringbuffer overflow + * to test handling of this situation in the stack. */ + unsigned long next_overflow; + + next_overflow = ring->last_injected_overflow + HZ; + if (time_after(jiffies, next_overflow)) { + ring->last_injected_overflow = jiffies; + b43legacydbg(ring->dev->wl, + "Injecting TX ring overflow on " + "DMA controller %d\n", ring->index); + return 1; + } + } +#endif /* CPTCFG_B43LEGACY_DEBUG */ + return 0; +} + +int b43legacy_dma_tx(struct b43legacy_wldev *dev, + struct sk_buff *skb) +{ + struct b43legacy_dmaring *ring; + int err = 0; + + ring = priority_to_txring(dev, skb_get_queue_mapping(skb)); + B43legacy_WARN_ON(!ring->tx); + + if (unlikely(ring->stopped)) { + /* We get here only because of a bug in mac80211. + * Because of a race, one packet may be queued after + * the queue is stopped, thus we got called when we shouldn't. + * For now, just refuse the transmit. */ + if (b43legacy_debug(dev, B43legacy_DBG_DMAVERBOSE)) + b43legacyerr(dev->wl, "Packet after queue stopped\n"); + return -ENOSPC; + } + + if (unlikely(WARN_ON(free_slots(ring) < SLOTS_PER_PACKET))) { + /* If we get here, we have a real error with the queue + * full, but queues not stopped. */ + b43legacyerr(dev->wl, "DMA queue overflow\n"); + return -ENOSPC; + } + + /* dma_tx_fragment might reallocate the skb, so invalidate pointers pointing + * into the skb data or cb now. */ + err = dma_tx_fragment(ring, &skb); + if (unlikely(err == -ENOKEY)) { + /* Drop this packet, as we don't have the encryption key + * anymore and must not transmit it unencrypted. */ + dev_kfree_skb_any(skb); + return 0; + } + if (unlikely(err)) { + b43legacyerr(dev->wl, "DMA tx mapping failure\n"); + return err; + } + if ((free_slots(ring) < SLOTS_PER_PACKET) || + should_inject_overflow(ring)) { + /* This TX ring is full. */ + unsigned int skb_mapping = skb_get_queue_mapping(skb); + ieee80211_stop_queue(dev->wl->hw, skb_mapping); + dev->wl->tx_queue_stopped[skb_mapping] = 1; + ring->stopped = true; + if (b43legacy_debug(dev, B43legacy_DBG_DMAVERBOSE)) + b43legacydbg(dev->wl, "Stopped TX ring %d\n", + ring->index); + } + return err; +} + +void b43legacy_dma_handle_txstatus(struct b43legacy_wldev *dev, + const struct b43legacy_txstatus *status) +{ + struct b43legacy_dmaring *ring; + struct b43legacy_dmadesc_meta *meta; + int retry_limit; + int slot; + int firstused; + + ring = parse_cookie(dev, status->cookie, &slot); + if (unlikely(!ring)) + return; + B43legacy_WARN_ON(!ring->tx); + + /* Sanity check: TX packets are processed in-order on one ring. + * Check if the slot deduced from the cookie really is the first + * used slot. */ + firstused = ring->current_slot - ring->used_slots + 1; + if (firstused < 0) + firstused = ring->nr_slots + firstused; + if (unlikely(slot != firstused)) { + /* This possibly is a firmware bug and will result in + * malfunction, memory leaks and/or stall of DMA functionality. + */ + b43legacydbg(dev->wl, "Out of order TX status report on DMA " + "ring %d. Expected %d, but got %d\n", + ring->index, firstused, slot); + return; + } + + while (1) { + B43legacy_WARN_ON(!(slot >= 0 && slot < ring->nr_slots)); + op32_idx2desc(ring, slot, &meta); + + if (meta->skb) + unmap_descbuffer(ring, meta->dmaaddr, + meta->skb->len, 1); + else + unmap_descbuffer(ring, meta->dmaaddr, + sizeof(struct b43legacy_txhdr_fw3), + 1); + + if (meta->is_last_fragment) { + struct ieee80211_tx_info *info; + BUG_ON(!meta->skb); + info = IEEE80211_SKB_CB(meta->skb); + + /* preserve the confiured retry limit before clearing the status + * The xmit function has overwritten the rc's value with the actual + * retry limit done by the hardware */ + retry_limit = info->status.rates[0].count; + ieee80211_tx_info_clear_status(info); + + if (status->acked) + info->flags |= IEEE80211_TX_STAT_ACK; + + if (status->rts_count > dev->wl->hw->conf.short_frame_max_tx_count) { + /* + * If the short retries (RTS, not data frame) have exceeded + * the limit, the hw will not have tried the selected rate, + * but will have used the fallback rate instead. + * Don't let the rate control count attempts for the selected + * rate in this case, otherwise the statistics will be off. + */ + info->status.rates[0].count = 0; + info->status.rates[1].count = status->frame_count; + } else { + if (status->frame_count > retry_limit) { + info->status.rates[0].count = retry_limit; + info->status.rates[1].count = status->frame_count - + retry_limit; + + } else { + info->status.rates[0].count = status->frame_count; + info->status.rates[1].idx = -1; + } + } + + /* Call back to inform the ieee80211 subsystem about the + * status of the transmission. + * Some fields of txstat are already filled in dma_tx(). + */ + ieee80211_tx_status_irqsafe(dev->wl->hw, meta->skb); + /* skb is freed by ieee80211_tx_status_irqsafe() */ + meta->skb = NULL; + } else { + /* No need to call free_descriptor_buffer here, as + * this is only the txhdr, which is not allocated. + */ + B43legacy_WARN_ON(meta->skb != NULL); + } + + /* Everything unmapped and free'd. So it's not used anymore. */ + ring->used_slots--; + + if (meta->is_last_fragment) + break; + slot = next_slot(ring, slot); + } + dev->stats.last_tx = jiffies; + if (ring->stopped) { + B43legacy_WARN_ON(free_slots(ring) < SLOTS_PER_PACKET); + ring->stopped = false; + } + + if (dev->wl->tx_queue_stopped[ring->queue_prio]) { + dev->wl->tx_queue_stopped[ring->queue_prio] = 0; + } else { + /* If the driver queue is running wake the corresponding + * mac80211 queue. */ + ieee80211_wake_queue(dev->wl->hw, ring->queue_prio); + if (b43legacy_debug(dev, B43legacy_DBG_DMAVERBOSE)) + b43legacydbg(dev->wl, "Woke up TX ring %d\n", + ring->index); + } + /* Add work to the queue. */ + ieee80211_queue_work(dev->wl->hw, &dev->wl->tx_work); +} + +static void dma_rx(struct b43legacy_dmaring *ring, + int *slot) +{ + struct b43legacy_dmadesc32 *desc; + struct b43legacy_dmadesc_meta *meta; + struct b43legacy_rxhdr_fw3 *rxhdr; + struct sk_buff *skb; + u16 len; + int err; + dma_addr_t dmaaddr; + + desc = op32_idx2desc(ring, *slot, &meta); + + sync_descbuffer_for_cpu(ring, meta->dmaaddr, ring->rx_buffersize); + skb = meta->skb; + + if (ring->index == 3) { + /* We received an xmit status. */ + struct b43legacy_hwtxstatus *hw = + (struct b43legacy_hwtxstatus *)skb->data; + int i = 0; + + while (hw->cookie == 0) { + if (i > 100) + break; + i++; + udelay(2); + barrier(); + } + b43legacy_handle_hwtxstatus(ring->dev, hw); + /* recycle the descriptor buffer. */ + sync_descbuffer_for_device(ring, meta->dmaaddr, + ring->rx_buffersize); + + return; + } + rxhdr = (struct b43legacy_rxhdr_fw3 *)skb->data; + len = le16_to_cpu(rxhdr->frame_len); + if (len == 0) { + int i = 0; + + do { + udelay(2); + barrier(); + len = le16_to_cpu(rxhdr->frame_len); + } while (len == 0 && i++ < 5); + if (unlikely(len == 0)) { + /* recycle the descriptor buffer. */ + sync_descbuffer_for_device(ring, meta->dmaaddr, + ring->rx_buffersize); + goto drop; + } + } + if (unlikely(len > ring->rx_buffersize)) { + /* The data did not fit into one descriptor buffer + * and is split over multiple buffers. + * This should never happen, as we try to allocate buffers + * big enough. So simply ignore this packet. + */ + int cnt = 0; + s32 tmp = len; + + while (1) { + desc = op32_idx2desc(ring, *slot, &meta); + /* recycle the descriptor buffer. */ + sync_descbuffer_for_device(ring, meta->dmaaddr, + ring->rx_buffersize); + *slot = next_slot(ring, *slot); + cnt++; + tmp -= ring->rx_buffersize; + if (tmp <= 0) + break; + } + b43legacyerr(ring->dev->wl, "DMA RX buffer too small " + "(len: %u, buffer: %u, nr-dropped: %d)\n", + len, ring->rx_buffersize, cnt); + goto drop; + } + + dmaaddr = meta->dmaaddr; + err = setup_rx_descbuffer(ring, desc, meta, GFP_ATOMIC); + if (unlikely(err)) { + b43legacydbg(ring->dev->wl, "DMA RX: setup_rx_descbuffer()" + " failed\n"); + sync_descbuffer_for_device(ring, dmaaddr, + ring->rx_buffersize); + goto drop; + } + + unmap_descbuffer(ring, dmaaddr, ring->rx_buffersize, 0); + skb_put(skb, len + ring->frameoffset); + skb_pull(skb, ring->frameoffset); + + b43legacy_rx(ring->dev, skb, rxhdr); +drop: + return; +} + +void b43legacy_dma_rx(struct b43legacy_dmaring *ring) +{ + int slot; + int current_slot; + int used_slots = 0; + + B43legacy_WARN_ON(ring->tx); + current_slot = op32_get_current_rxslot(ring); + B43legacy_WARN_ON(!(current_slot >= 0 && current_slot < + ring->nr_slots)); + + slot = ring->current_slot; + for (; slot != current_slot; slot = next_slot(ring, slot)) { + dma_rx(ring, &slot); + update_max_used_slots(ring, ++used_slots); + } + op32_set_current_rxslot(ring, slot); + ring->current_slot = slot; +} + +static void b43legacy_dma_tx_suspend_ring(struct b43legacy_dmaring *ring) +{ + B43legacy_WARN_ON(!ring->tx); + op32_tx_suspend(ring); +} + +static void b43legacy_dma_tx_resume_ring(struct b43legacy_dmaring *ring) +{ + B43legacy_WARN_ON(!ring->tx); + op32_tx_resume(ring); +} + +void b43legacy_dma_tx_suspend(struct b43legacy_wldev *dev) +{ + b43legacy_power_saving_ctl_bits(dev, -1, 1); + b43legacy_dma_tx_suspend_ring(dev->dma.tx_ring0); + b43legacy_dma_tx_suspend_ring(dev->dma.tx_ring1); + b43legacy_dma_tx_suspend_ring(dev->dma.tx_ring2); + b43legacy_dma_tx_suspend_ring(dev->dma.tx_ring3); + b43legacy_dma_tx_suspend_ring(dev->dma.tx_ring4); + b43legacy_dma_tx_suspend_ring(dev->dma.tx_ring5); +} + +void b43legacy_dma_tx_resume(struct b43legacy_wldev *dev) +{ + b43legacy_dma_tx_resume_ring(dev->dma.tx_ring5); + b43legacy_dma_tx_resume_ring(dev->dma.tx_ring4); + b43legacy_dma_tx_resume_ring(dev->dma.tx_ring3); + b43legacy_dma_tx_resume_ring(dev->dma.tx_ring2); + b43legacy_dma_tx_resume_ring(dev->dma.tx_ring1); + b43legacy_dma_tx_resume_ring(dev->dma.tx_ring0); + b43legacy_power_saving_ctl_bits(dev, -1, -1); +} diff --git a/drivers/net/wireless/b43legacy/dma.h b/drivers/net/wireless/b43legacy/dma.h new file mode 100644 index 0000000..3c5e779 --- /dev/null +++ b/drivers/net/wireless/b43legacy/dma.h @@ -0,0 +1,231 @@ +#ifndef B43legacy_DMA_H_ +#define B43legacy_DMA_H_ + +#include +#include +#include +#include +#include + +#include "b43legacy.h" + + +/* DMA-Interrupt reasons. */ +#define B43legacy_DMAIRQ_FATALMASK ((1 << 10) | (1 << 11) | (1 << 12) \ + | (1 << 14) | (1 << 15)) +#define B43legacy_DMAIRQ_NONFATALMASK (1 << 13) +#define B43legacy_DMAIRQ_RX_DONE (1 << 16) + + +/*** 32-bit DMA Engine. ***/ + +/* 32-bit DMA controller registers. */ +#define B43legacy_DMA32_TXCTL 0x00 +#define B43legacy_DMA32_TXENABLE 0x00000001 +#define B43legacy_DMA32_TXSUSPEND 0x00000002 +#define B43legacy_DMA32_TXLOOPBACK 0x00000004 +#define B43legacy_DMA32_TXFLUSH 0x00000010 +#define B43legacy_DMA32_TXADDREXT_MASK 0x00030000 +#define B43legacy_DMA32_TXADDREXT_SHIFT 16 +#define B43legacy_DMA32_TXRING 0x04 +#define B43legacy_DMA32_TXINDEX 0x08 +#define B43legacy_DMA32_TXSTATUS 0x0C +#define B43legacy_DMA32_TXDPTR 0x00000FFF +#define B43legacy_DMA32_TXSTATE 0x0000F000 +#define B43legacy_DMA32_TXSTAT_DISABLED 0x00000000 +#define B43legacy_DMA32_TXSTAT_ACTIVE 0x00001000 +#define B43legacy_DMA32_TXSTAT_IDLEWAIT 0x00002000 +#define B43legacy_DMA32_TXSTAT_STOPPED 0x00003000 +#define B43legacy_DMA32_TXSTAT_SUSP 0x00004000 +#define B43legacy_DMA32_TXERROR 0x000F0000 +#define B43legacy_DMA32_TXERR_NOERR 0x00000000 +#define B43legacy_DMA32_TXERR_PROT 0x00010000 +#define B43legacy_DMA32_TXERR_UNDERRUN 0x00020000 +#define B43legacy_DMA32_TXERR_BUFREAD 0x00030000 +#define B43legacy_DMA32_TXERR_DESCREAD 0x00040000 +#define B43legacy_DMA32_TXACTIVE 0xFFF00000 +#define B43legacy_DMA32_RXCTL 0x10 +#define B43legacy_DMA32_RXENABLE 0x00000001 +#define B43legacy_DMA32_RXFROFF_MASK 0x000000FE +#define B43legacy_DMA32_RXFROFF_SHIFT 1 +#define B43legacy_DMA32_RXDIRECTFIFO 0x00000100 +#define B43legacy_DMA32_RXADDREXT_MASK 0x00030000 +#define B43legacy_DMA32_RXADDREXT_SHIFT 16 +#define B43legacy_DMA32_RXRING 0x14 +#define B43legacy_DMA32_RXINDEX 0x18 +#define B43legacy_DMA32_RXSTATUS 0x1C +#define B43legacy_DMA32_RXDPTR 0x00000FFF +#define B43legacy_DMA32_RXSTATE 0x0000F000 +#define B43legacy_DMA32_RXSTAT_DISABLED 0x00000000 +#define B43legacy_DMA32_RXSTAT_ACTIVE 0x00001000 +#define B43legacy_DMA32_RXSTAT_IDLEWAIT 0x00002000 +#define B43legacy_DMA32_RXSTAT_STOPPED 0x00003000 +#define B43legacy_DMA32_RXERROR 0x000F0000 +#define B43legacy_DMA32_RXERR_NOERR 0x00000000 +#define B43legacy_DMA32_RXERR_PROT 0x00010000 +#define B43legacy_DMA32_RXERR_OVERFLOW 0x00020000 +#define B43legacy_DMA32_RXERR_BUFWRITE 0x00030000 +#define B43legacy_DMA32_RXERR_DESCREAD 0x00040000 +#define B43legacy_DMA32_RXACTIVE 0xFFF00000 + +/* 32-bit DMA descriptor. */ +struct b43legacy_dmadesc32 { + __le32 control; + __le32 address; +} __packed; +#define B43legacy_DMA32_DCTL_BYTECNT 0x00001FFF +#define B43legacy_DMA32_DCTL_ADDREXT_MASK 0x00030000 +#define B43legacy_DMA32_DCTL_ADDREXT_SHIFT 16 +#define B43legacy_DMA32_DCTL_DTABLEEND 0x10000000 +#define B43legacy_DMA32_DCTL_IRQ 0x20000000 +#define B43legacy_DMA32_DCTL_FRAMEEND 0x40000000 +#define B43legacy_DMA32_DCTL_FRAMESTART 0x80000000 + + +/* Misc DMA constants */ +#define B43legacy_DMA_RINGMEMSIZE PAGE_SIZE +#define B43legacy_DMA0_RX_FRAMEOFFSET 30 +#define B43legacy_DMA3_RX_FRAMEOFFSET 0 + + +/* DMA engine tuning knobs */ +#define B43legacy_TXRING_SLOTS 128 +#define B43legacy_RXRING_SLOTS 64 +#define B43legacy_DMA0_RX_BUFFERSIZE (2304 + 100) +#define B43legacy_DMA3_RX_BUFFERSIZE 16 + + + +#ifdef CPTCFG_B43LEGACY_DMA + + +struct sk_buff; +struct b43legacy_private; +struct b43legacy_txstatus; + + +struct b43legacy_dmadesc_meta { + /* The kernel DMA-able buffer. */ + struct sk_buff *skb; + /* DMA base bus-address of the descriptor buffer. */ + dma_addr_t dmaaddr; + /* ieee80211 TX status. Only used once per 802.11 frag. */ + bool is_last_fragment; +}; + +enum b43legacy_dmatype { + B43legacy_DMA_30BIT = 30, + B43legacy_DMA_32BIT = 32, +}; + +struct b43legacy_dmaring { + /* Kernel virtual base address of the ring memory. */ + void *descbase; + /* Meta data about all descriptors. */ + struct b43legacy_dmadesc_meta *meta; + /* Cache of TX headers for each slot. + * This is to avoid an allocation on each TX. + * This is NULL for an RX ring. + */ + u8 *txhdr_cache; + /* (Unadjusted) DMA base bus-address of the ring memory. */ + dma_addr_t dmabase; + /* Number of descriptor slots in the ring. */ + int nr_slots; + /* Number of used descriptor slots. */ + int used_slots; + /* Currently used slot in the ring. */ + int current_slot; + /* Frameoffset in octets. */ + u32 frameoffset; + /* Descriptor buffer size. */ + u16 rx_buffersize; + /* The MMIO base register of the DMA controller. */ + u16 mmio_base; + /* DMA controller index number (0-5). */ + int index; + /* Boolean. Is this a TX ring? */ + bool tx; + /* The type of DMA engine used. */ + enum b43legacy_dmatype type; + /* Boolean. Is this ring stopped at ieee80211 level? */ + bool stopped; + /* The QOS priority assigned to this ring. Only used for TX rings. + * This is the mac80211 "queue" value. */ + u8 queue_prio; + struct b43legacy_wldev *dev; +#ifdef CPTCFG_B43LEGACY_DEBUG + /* Maximum number of used slots. */ + int max_used_slots; + /* Last time we injected a ring overflow. */ + unsigned long last_injected_overflow; +#endif /* CPTCFG_B43LEGACY_DEBUG*/ +}; + + +static inline +u32 b43legacy_dma_read(struct b43legacy_dmaring *ring, + u16 offset) +{ + return b43legacy_read32(ring->dev, ring->mmio_base + offset); +} + +static inline +void b43legacy_dma_write(struct b43legacy_dmaring *ring, + u16 offset, u32 value) +{ + b43legacy_write32(ring->dev, ring->mmio_base + offset, value); +} + + +int b43legacy_dma_init(struct b43legacy_wldev *dev); +void b43legacy_dma_free(struct b43legacy_wldev *dev); + +void b43legacy_dma_tx_suspend(struct b43legacy_wldev *dev); +void b43legacy_dma_tx_resume(struct b43legacy_wldev *dev); + +int b43legacy_dma_tx(struct b43legacy_wldev *dev, + struct sk_buff *skb); +void b43legacy_dma_handle_txstatus(struct b43legacy_wldev *dev, + const struct b43legacy_txstatus *status); + +void b43legacy_dma_rx(struct b43legacy_dmaring *ring); + +#else /* CPTCFG_B43LEGACY_DMA */ + + +static inline +int b43legacy_dma_init(struct b43legacy_wldev *dev) +{ + return 0; +} +static inline +void b43legacy_dma_free(struct b43legacy_wldev *dev) +{ +} +static inline +int b43legacy_dma_tx(struct b43legacy_wldev *dev, + struct sk_buff *skb) +{ + return 0; +} +static inline +void b43legacy_dma_handle_txstatus(struct b43legacy_wldev *dev, + const struct b43legacy_txstatus *status) +{ +} +static inline +void b43legacy_dma_rx(struct b43legacy_dmaring *ring) +{ +} +static inline +void b43legacy_dma_tx_suspend(struct b43legacy_wldev *dev) +{ +} +static inline +void b43legacy_dma_tx_resume(struct b43legacy_wldev *dev) +{ +} + +#endif /* CPTCFG_B43LEGACY_DMA */ +#endif /* B43legacy_DMA_H_ */ diff --git a/drivers/net/wireless/b43legacy/ilt.c b/drivers/net/wireless/b43legacy/ilt.c new file mode 100644 index 0000000..ee5682e --- /dev/null +++ b/drivers/net/wireless/b43legacy/ilt.c @@ -0,0 +1,336 @@ +/* + + Broadcom B43legacy wireless driver + + Copyright (c) 2005 Martin Langer , + Stefano Brivio + Michael Buesch + Danny van Dyk + Andreas Jaggi + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43legacy.h" +#include "ilt.h" +#include "phy.h" + + +/**** Initial Internal Lookup Tables ****/ + +const u32 b43legacy_ilt_rotor[B43legacy_ILT_ROTOR_SIZE] = { + 0xFEB93FFD, 0xFEC63FFD, /* 0 */ + 0xFED23FFD, 0xFEDF3FFD, + 0xFEEC3FFE, 0xFEF83FFE, + 0xFF053FFE, 0xFF113FFE, + 0xFF1E3FFE, 0xFF2A3FFF, /* 8 */ + 0xFF373FFF, 0xFF443FFF, + 0xFF503FFF, 0xFF5D3FFF, + 0xFF693FFF, 0xFF763FFF, + 0xFF824000, 0xFF8F4000, /* 16 */ + 0xFF9B4000, 0xFFA84000, + 0xFFB54000, 0xFFC14000, + 0xFFCE4000, 0xFFDA4000, + 0xFFE74000, 0xFFF34000, /* 24 */ + 0x00004000, 0x000D4000, + 0x00194000, 0x00264000, + 0x00324000, 0x003F4000, + 0x004B4000, 0x00584000, /* 32 */ + 0x00654000, 0x00714000, + 0x007E4000, 0x008A3FFF, + 0x00973FFF, 0x00A33FFF, + 0x00B03FFF, 0x00BC3FFF, /* 40 */ + 0x00C93FFF, 0x00D63FFF, + 0x00E23FFE, 0x00EF3FFE, + 0x00FB3FFE, 0x01083FFE, + 0x01143FFE, 0x01213FFD, /* 48 */ + 0x012E3FFD, 0x013A3FFD, + 0x01473FFD, +}; + +const u32 b43legacy_ilt_retard[B43legacy_ILT_RETARD_SIZE] = { + 0xDB93CB87, 0xD666CF64, /* 0 */ + 0xD1FDD358, 0xCDA6D826, + 0xCA38DD9F, 0xC729E2B4, + 0xC469E88E, 0xC26AEE2B, + 0xC0DEF46C, 0xC073FA62, /* 8 */ + 0xC01D00D5, 0xC0760743, + 0xC1560D1E, 0xC2E51369, + 0xC4ED18FF, 0xC7AC1ED7, + 0xCB2823B2, 0xCEFA28D9, /* 16 */ + 0xD2F62D3F, 0xD7BB3197, + 0xDCE53568, 0xE1FE3875, + 0xE7D13B35, 0xED663D35, + 0xF39B3EC4, 0xF98E3FA7, /* 24 */ + 0x00004000, 0x06723FA7, + 0x0C653EC4, 0x129A3D35, + 0x182F3B35, 0x1E023875, + 0x231B3568, 0x28453197, /* 32 */ + 0x2D0A2D3F, 0x310628D9, + 0x34D823B2, 0x38541ED7, + 0x3B1318FF, 0x3D1B1369, + 0x3EAA0D1E, 0x3F8A0743, /* 40 */ + 0x3FE300D5, 0x3F8DFA62, + 0x3F22F46C, 0x3D96EE2B, + 0x3B97E88E, 0x38D7E2B4, + 0x35C8DD9F, 0x325AD826, /* 48 */ + 0x2E03D358, 0x299ACF64, + 0x246DCB87, +}; + +const u16 b43legacy_ilt_finefreqa[B43legacy_ILT_FINEFREQA_SIZE] = { + 0x0082, 0x0082, 0x0102, 0x0182, /* 0 */ + 0x0202, 0x0282, 0x0302, 0x0382, + 0x0402, 0x0482, 0x0502, 0x0582, + 0x05E2, 0x0662, 0x06E2, 0x0762, + 0x07E2, 0x0842, 0x08C2, 0x0942, /* 16 */ + 0x09C2, 0x0A22, 0x0AA2, 0x0B02, + 0x0B82, 0x0BE2, 0x0C62, 0x0CC2, + 0x0D42, 0x0DA2, 0x0E02, 0x0E62, + 0x0EE2, 0x0F42, 0x0FA2, 0x1002, /* 32 */ + 0x1062, 0x10C2, 0x1122, 0x1182, + 0x11E2, 0x1242, 0x12A2, 0x12E2, + 0x1342, 0x13A2, 0x1402, 0x1442, + 0x14A2, 0x14E2, 0x1542, 0x1582, /* 48 */ + 0x15E2, 0x1622, 0x1662, 0x16C1, + 0x1701, 0x1741, 0x1781, 0x17E1, + 0x1821, 0x1861, 0x18A1, 0x18E1, + 0x1921, 0x1961, 0x19A1, 0x19E1, /* 64 */ + 0x1A21, 0x1A61, 0x1AA1, 0x1AC1, + 0x1B01, 0x1B41, 0x1B81, 0x1BA1, + 0x1BE1, 0x1C21, 0x1C41, 0x1C81, + 0x1CA1, 0x1CE1, 0x1D01, 0x1D41, /* 80 */ + 0x1D61, 0x1DA1, 0x1DC1, 0x1E01, + 0x1E21, 0x1E61, 0x1E81, 0x1EA1, + 0x1EE1, 0x1F01, 0x1F21, 0x1F41, + 0x1F81, 0x1FA1, 0x1FC1, 0x1FE1, /* 96 */ + 0x2001, 0x2041, 0x2061, 0x2081, + 0x20A1, 0x20C1, 0x20E1, 0x2101, + 0x2121, 0x2141, 0x2161, 0x2181, + 0x21A1, 0x21C1, 0x21E1, 0x2201, /* 112 */ + 0x2221, 0x2241, 0x2261, 0x2281, + 0x22A1, 0x22C1, 0x22C1, 0x22E1, + 0x2301, 0x2321, 0x2341, 0x2361, + 0x2361, 0x2381, 0x23A1, 0x23C1, /* 128 */ + 0x23E1, 0x23E1, 0x2401, 0x2421, + 0x2441, 0x2441, 0x2461, 0x2481, + 0x2481, 0x24A1, 0x24C1, 0x24C1, + 0x24E1, 0x2501, 0x2501, 0x2521, /* 144 */ + 0x2541, 0x2541, 0x2561, 0x2561, + 0x2581, 0x25A1, 0x25A1, 0x25C1, + 0x25C1, 0x25E1, 0x2601, 0x2601, + 0x2621, 0x2621, 0x2641, 0x2641, /* 160 */ + 0x2661, 0x2661, 0x2681, 0x2681, + 0x26A1, 0x26A1, 0x26C1, 0x26C1, + 0x26E1, 0x26E1, 0x2701, 0x2701, + 0x2721, 0x2721, 0x2740, 0x2740, /* 176 */ + 0x2760, 0x2760, 0x2780, 0x2780, + 0x2780, 0x27A0, 0x27A0, 0x27C0, + 0x27C0, 0x27E0, 0x27E0, 0x27E0, + 0x2800, 0x2800, 0x2820, 0x2820, /* 192 */ + 0x2820, 0x2840, 0x2840, 0x2840, + 0x2860, 0x2860, 0x2880, 0x2880, + 0x2880, 0x28A0, 0x28A0, 0x28A0, + 0x28C0, 0x28C0, 0x28C0, 0x28E0, /* 208 */ + 0x28E0, 0x28E0, 0x2900, 0x2900, + 0x2900, 0x2920, 0x2920, 0x2920, + 0x2940, 0x2940, 0x2940, 0x2960, + 0x2960, 0x2960, 0x2960, 0x2980, /* 224 */ + 0x2980, 0x2980, 0x29A0, 0x29A0, + 0x29A0, 0x29A0, 0x29C0, 0x29C0, + 0x29C0, 0x29E0, 0x29E0, 0x29E0, + 0x29E0, 0x2A00, 0x2A00, 0x2A00, /* 240 */ + 0x2A00, 0x2A20, 0x2A20, 0x2A20, + 0x2A20, 0x2A40, 0x2A40, 0x2A40, + 0x2A40, 0x2A60, 0x2A60, 0x2A60, +}; + +const u16 b43legacy_ilt_finefreqg[B43legacy_ILT_FINEFREQG_SIZE] = { + 0x0089, 0x02E9, 0x0409, 0x04E9, /* 0 */ + 0x05A9, 0x0669, 0x0709, 0x0789, + 0x0829, 0x08A9, 0x0929, 0x0989, + 0x0A09, 0x0A69, 0x0AC9, 0x0B29, + 0x0BA9, 0x0BE9, 0x0C49, 0x0CA9, /* 16 */ + 0x0D09, 0x0D69, 0x0DA9, 0x0E09, + 0x0E69, 0x0EA9, 0x0F09, 0x0F49, + 0x0FA9, 0x0FE9, 0x1029, 0x1089, + 0x10C9, 0x1109, 0x1169, 0x11A9, /* 32 */ + 0x11E9, 0x1229, 0x1289, 0x12C9, + 0x1309, 0x1349, 0x1389, 0x13C9, + 0x1409, 0x1449, 0x14A9, 0x14E9, + 0x1529, 0x1569, 0x15A9, 0x15E9, /* 48 */ + 0x1629, 0x1669, 0x16A9, 0x16E8, + 0x1728, 0x1768, 0x17A8, 0x17E8, + 0x1828, 0x1868, 0x18A8, 0x18E8, + 0x1928, 0x1968, 0x19A8, 0x19E8, /* 64 */ + 0x1A28, 0x1A68, 0x1AA8, 0x1AE8, + 0x1B28, 0x1B68, 0x1BA8, 0x1BE8, + 0x1C28, 0x1C68, 0x1CA8, 0x1CE8, + 0x1D28, 0x1D68, 0x1DC8, 0x1E08, /* 80 */ + 0x1E48, 0x1E88, 0x1EC8, 0x1F08, + 0x1F48, 0x1F88, 0x1FE8, 0x2028, + 0x2068, 0x20A8, 0x2108, 0x2148, + 0x2188, 0x21C8, 0x2228, 0x2268, /* 96 */ + 0x22C8, 0x2308, 0x2348, 0x23A8, + 0x23E8, 0x2448, 0x24A8, 0x24E8, + 0x2548, 0x25A8, 0x2608, 0x2668, + 0x26C8, 0x2728, 0x2787, 0x27E7, /* 112 */ + 0x2847, 0x28C7, 0x2947, 0x29A7, + 0x2A27, 0x2AC7, 0x2B47, 0x2BE7, + 0x2CA7, 0x2D67, 0x2E47, 0x2F67, + 0x3247, 0x3526, 0x3646, 0x3726, /* 128 */ + 0x3806, 0x38A6, 0x3946, 0x39E6, + 0x3A66, 0x3AE6, 0x3B66, 0x3BC6, + 0x3C45, 0x3CA5, 0x3D05, 0x3D85, + 0x3DE5, 0x3E45, 0x3EA5, 0x3EE5, /* 144 */ + 0x3F45, 0x3FA5, 0x4005, 0x4045, + 0x40A5, 0x40E5, 0x4145, 0x4185, + 0x41E5, 0x4225, 0x4265, 0x42C5, + 0x4305, 0x4345, 0x43A5, 0x43E5, /* 160 */ + 0x4424, 0x4464, 0x44C4, 0x4504, + 0x4544, 0x4584, 0x45C4, 0x4604, + 0x4644, 0x46A4, 0x46E4, 0x4724, + 0x4764, 0x47A4, 0x47E4, 0x4824, /* 176 */ + 0x4864, 0x48A4, 0x48E4, 0x4924, + 0x4964, 0x49A4, 0x49E4, 0x4A24, + 0x4A64, 0x4AA4, 0x4AE4, 0x4B23, + 0x4B63, 0x4BA3, 0x4BE3, 0x4C23, /* 192 */ + 0x4C63, 0x4CA3, 0x4CE3, 0x4D23, + 0x4D63, 0x4DA3, 0x4DE3, 0x4E23, + 0x4E63, 0x4EA3, 0x4EE3, 0x4F23, + 0x4F63, 0x4FC3, 0x5003, 0x5043, /* 208 */ + 0x5083, 0x50C3, 0x5103, 0x5143, + 0x5183, 0x51E2, 0x5222, 0x5262, + 0x52A2, 0x52E2, 0x5342, 0x5382, + 0x53C2, 0x5402, 0x5462, 0x54A2, /* 224 */ + 0x5502, 0x5542, 0x55A2, 0x55E2, + 0x5642, 0x5682, 0x56E2, 0x5722, + 0x5782, 0x57E1, 0x5841, 0x58A1, + 0x5901, 0x5961, 0x59C1, 0x5A21, /* 240 */ + 0x5AA1, 0x5B01, 0x5B81, 0x5BE1, + 0x5C61, 0x5D01, 0x5D80, 0x5E20, + 0x5EE0, 0x5FA0, 0x6080, 0x61C0, +}; + +const u16 b43legacy_ilt_noisea2[B43legacy_ILT_NOISEA2_SIZE] = { + 0x0001, 0x0001, 0x0001, 0xFFFE, + 0xFFFE, 0x3FFF, 0x1000, 0x0393, +}; + +const u16 b43legacy_ilt_noisea3[B43legacy_ILT_NOISEA3_SIZE] = { + 0x4C4C, 0x4C4C, 0x4C4C, 0x2D36, + 0x4C4C, 0x4C4C, 0x4C4C, 0x2D36, +}; + +const u16 b43legacy_ilt_noiseg1[B43legacy_ILT_NOISEG1_SIZE] = { + 0x013C, 0x01F5, 0x031A, 0x0631, + 0x0001, 0x0001, 0x0001, 0x0001, +}; + +const u16 b43legacy_ilt_noiseg2[B43legacy_ILT_NOISEG2_SIZE] = { + 0x5484, 0x3C40, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, +}; + +const u16 b43legacy_ilt_noisescaleg1[B43legacy_ILT_NOISESCALEG_SIZE] = { + 0x6C77, 0x5162, 0x3B40, 0x3335, /* 0 */ + 0x2F2D, 0x2A2A, 0x2527, 0x1F21, + 0x1A1D, 0x1719, 0x1616, 0x1414, + 0x1414, 0x1400, 0x1414, 0x1614, + 0x1716, 0x1A19, 0x1F1D, 0x2521, /* 16 */ + 0x2A27, 0x2F2A, 0x332D, 0x3B35, + 0x5140, 0x6C62, 0x0077, +}; + +const u16 b43legacy_ilt_noisescaleg2[B43legacy_ILT_NOISESCALEG_SIZE] = { + 0xD8DD, 0xCBD4, 0xBCC0, 0XB6B7, /* 0 */ + 0xB2B0, 0xADAD, 0xA7A9, 0x9FA1, + 0x969B, 0x9195, 0x8F8F, 0x8A8A, + 0x8A8A, 0x8A00, 0x8A8A, 0x8F8A, + 0x918F, 0x9695, 0x9F9B, 0xA7A1, /* 16 */ + 0xADA9, 0xB2AD, 0xB6B0, 0xBCB7, + 0xCBC0, 0xD8D4, 0x00DD, +}; + +const u16 b43legacy_ilt_noisescaleg3[B43legacy_ILT_NOISESCALEG_SIZE] = { + 0xA4A4, 0xA4A4, 0xA4A4, 0xA4A4, /* 0 */ + 0xA4A4, 0xA4A4, 0xA4A4, 0xA4A4, + 0xA4A4, 0xA4A4, 0xA4A4, 0xA4A4, + 0xA4A4, 0xA400, 0xA4A4, 0xA4A4, + 0xA4A4, 0xA4A4, 0xA4A4, 0xA4A4, /* 16 */ + 0xA4A4, 0xA4A4, 0xA4A4, 0xA4A4, + 0xA4A4, 0xA4A4, 0x00A4, +}; + +const u16 b43legacy_ilt_sigmasqr1[B43legacy_ILT_SIGMASQR_SIZE] = { + 0x007A, 0x0075, 0x0071, 0x006C, /* 0 */ + 0x0067, 0x0063, 0x005E, 0x0059, + 0x0054, 0x0050, 0x004B, 0x0046, + 0x0042, 0x003D, 0x003D, 0x003D, + 0x003D, 0x003D, 0x003D, 0x003D, /* 16 */ + 0x003D, 0x003D, 0x003D, 0x003D, + 0x003D, 0x003D, 0x0000, 0x003D, + 0x003D, 0x003D, 0x003D, 0x003D, + 0x003D, 0x003D, 0x003D, 0x003D, /* 32 */ + 0x003D, 0x003D, 0x003D, 0x003D, + 0x0042, 0x0046, 0x004B, 0x0050, + 0x0054, 0x0059, 0x005E, 0x0063, + 0x0067, 0x006C, 0x0071, 0x0075, /* 48 */ + 0x007A, +}; + +const u16 b43legacy_ilt_sigmasqr2[B43legacy_ILT_SIGMASQR_SIZE] = { + 0x00DE, 0x00DC, 0x00DA, 0x00D8, /* 0 */ + 0x00D6, 0x00D4, 0x00D2, 0x00CF, + 0x00CD, 0x00CA, 0x00C7, 0x00C4, + 0x00C1, 0x00BE, 0x00BE, 0x00BE, + 0x00BE, 0x00BE, 0x00BE, 0x00BE, /* 16 */ + 0x00BE, 0x00BE, 0x00BE, 0x00BE, + 0x00BE, 0x00BE, 0x0000, 0x00BE, + 0x00BE, 0x00BE, 0x00BE, 0x00BE, + 0x00BE, 0x00BE, 0x00BE, 0x00BE, /* 32 */ + 0x00BE, 0x00BE, 0x00BE, 0x00BE, + 0x00C1, 0x00C4, 0x00C7, 0x00CA, + 0x00CD, 0x00CF, 0x00D2, 0x00D4, + 0x00D6, 0x00D8, 0x00DA, 0x00DC, /* 48 */ + 0x00DE, +}; + +/**** Helper functions to access the device Internal Lookup Tables ****/ + +void b43legacy_ilt_write(struct b43legacy_wldev *dev, u16 offset, u16 val) +{ + b43legacy_phy_write(dev, B43legacy_PHY_ILT_G_CTRL, offset); + mmiowb(); + b43legacy_phy_write(dev, B43legacy_PHY_ILT_G_DATA1, val); +} + +void b43legacy_ilt_write32(struct b43legacy_wldev *dev, u16 offset, u32 val) +{ + b43legacy_phy_write(dev, B43legacy_PHY_ILT_G_CTRL, offset); + mmiowb(); + b43legacy_phy_write(dev, B43legacy_PHY_ILT_G_DATA2, + (val & 0xFFFF0000) >> 16); + b43legacy_phy_write(dev, B43legacy_PHY_ILT_G_DATA1, + val & 0x0000FFFF); +} + +u16 b43legacy_ilt_read(struct b43legacy_wldev *dev, u16 offset) +{ + b43legacy_phy_write(dev, B43legacy_PHY_ILT_G_CTRL, offset); + return b43legacy_phy_read(dev, B43legacy_PHY_ILT_G_DATA1); +} diff --git a/drivers/net/wireless/b43legacy/ilt.h b/drivers/net/wireless/b43legacy/ilt.h new file mode 100644 index 0000000..48bcf37 --- /dev/null +++ b/drivers/net/wireless/b43legacy/ilt.h @@ -0,0 +1,34 @@ +#ifndef B43legacy_ILT_H_ +#define B43legacy_ILT_H_ + +#define B43legacy_ILT_ROTOR_SIZE 53 +extern const u32 b43legacy_ilt_rotor[B43legacy_ILT_ROTOR_SIZE]; +#define B43legacy_ILT_RETARD_SIZE 53 +extern const u32 b43legacy_ilt_retard[B43legacy_ILT_RETARD_SIZE]; +#define B43legacy_ILT_FINEFREQA_SIZE 256 +extern const u16 b43legacy_ilt_finefreqa[B43legacy_ILT_FINEFREQA_SIZE]; +#define B43legacy_ILT_FINEFREQG_SIZE 256 +extern const u16 b43legacy_ilt_finefreqg[B43legacy_ILT_FINEFREQG_SIZE]; +#define B43legacy_ILT_NOISEA2_SIZE 8 +extern const u16 b43legacy_ilt_noisea2[B43legacy_ILT_NOISEA2_SIZE]; +#define B43legacy_ILT_NOISEA3_SIZE 8 +extern const u16 b43legacy_ilt_noisea3[B43legacy_ILT_NOISEA3_SIZE]; +#define B43legacy_ILT_NOISEG1_SIZE 8 +extern const u16 b43legacy_ilt_noiseg1[B43legacy_ILT_NOISEG1_SIZE]; +#define B43legacy_ILT_NOISEG2_SIZE 8 +extern const u16 b43legacy_ilt_noiseg2[B43legacy_ILT_NOISEG2_SIZE]; +#define B43legacy_ILT_NOISESCALEG_SIZE 27 +extern const u16 b43legacy_ilt_noisescaleg1[B43legacy_ILT_NOISESCALEG_SIZE]; +extern const u16 b43legacy_ilt_noisescaleg2[B43legacy_ILT_NOISESCALEG_SIZE]; +extern const u16 b43legacy_ilt_noisescaleg3[B43legacy_ILT_NOISESCALEG_SIZE]; +#define B43legacy_ILT_SIGMASQR_SIZE 53 +extern const u16 b43legacy_ilt_sigmasqr1[B43legacy_ILT_SIGMASQR_SIZE]; +extern const u16 b43legacy_ilt_sigmasqr2[B43legacy_ILT_SIGMASQR_SIZE]; + + +void b43legacy_ilt_write(struct b43legacy_wldev *dev, u16 offset, u16 val); +void b43legacy_ilt_write32(struct b43legacy_wldev *dev, u16 offset, + u32 val); +u16 b43legacy_ilt_read(struct b43legacy_wldev *dev, u16 offset); + +#endif /* B43legacy_ILT_H_ */ diff --git a/drivers/net/wireless/b43legacy/leds.c b/drivers/net/wireless/b43legacy/leds.c new file mode 100644 index 0000000..fd45653 --- /dev/null +++ b/drivers/net/wireless/b43legacy/leds.c @@ -0,0 +1,243 @@ +/* + + Broadcom B43 wireless driver + LED control + + Copyright (c) 2005 Martin Langer , + Copyright (c) 2005 Stefano Brivio + Copyright (c) 2005-2007 Michael Buesch + Copyright (c) 2005 Danny van Dyk + Copyright (c) 2005 Andreas Jaggi + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43legacy.h" +#include "leds.h" +#include "rfkill.h" + + +static void b43legacy_led_turn_on(struct b43legacy_wldev *dev, u8 led_index, + bool activelow) +{ + struct b43legacy_wl *wl = dev->wl; + unsigned long flags; + u16 ctl; + + spin_lock_irqsave(&wl->leds_lock, flags); + ctl = b43legacy_read16(dev, B43legacy_MMIO_GPIO_CONTROL); + if (activelow) + ctl &= ~(1 << led_index); + else + ctl |= (1 << led_index); + b43legacy_write16(dev, B43legacy_MMIO_GPIO_CONTROL, ctl); + spin_unlock_irqrestore(&wl->leds_lock, flags); +} + +static void b43legacy_led_turn_off(struct b43legacy_wldev *dev, u8 led_index, + bool activelow) +{ + struct b43legacy_wl *wl = dev->wl; + unsigned long flags; + u16 ctl; + + spin_lock_irqsave(&wl->leds_lock, flags); + ctl = b43legacy_read16(dev, B43legacy_MMIO_GPIO_CONTROL); + if (activelow) + ctl |= (1 << led_index); + else + ctl &= ~(1 << led_index); + b43legacy_write16(dev, B43legacy_MMIO_GPIO_CONTROL, ctl); + spin_unlock_irqrestore(&wl->leds_lock, flags); +} + +/* Callback from the LED subsystem. */ +static void b43legacy_led_brightness_set(struct led_classdev *led_dev, + enum led_brightness brightness) +{ + struct b43legacy_led *led = container_of(led_dev, struct b43legacy_led, + led_dev); + struct b43legacy_wldev *dev = led->dev; + bool radio_enabled; + + /* Checking the radio-enabled status here is slightly racy, + * but we want to avoid the locking overhead and we don't care + * whether the LED has the wrong state for a second. */ + radio_enabled = (dev->phy.radio_on && dev->radio_hw_enable); + + if (brightness == LED_OFF || !radio_enabled) + b43legacy_led_turn_off(dev, led->index, led->activelow); + else + b43legacy_led_turn_on(dev, led->index, led->activelow); +} + +static int b43legacy_register_led(struct b43legacy_wldev *dev, + struct b43legacy_led *led, + const char *name, + const char *default_trigger, + u8 led_index, bool activelow) +{ + int err; + + b43legacy_led_turn_off(dev, led_index, activelow); + if (led->dev) + return -EEXIST; + if (!default_trigger) + return -EINVAL; + led->dev = dev; + led->index = led_index; + led->activelow = activelow; + strncpy(led->name, name, sizeof(led->name)); + + led->led_dev.name = led->name; + led->led_dev.default_trigger = default_trigger; + led->led_dev.brightness_set = b43legacy_led_brightness_set; + + err = led_classdev_register(dev->dev->dev, &led->led_dev); + if (err) { + b43legacywarn(dev->wl, "LEDs: Failed to register %s\n", name); + led->dev = NULL; + return err; + } + return 0; +} + +static void b43legacy_unregister_led(struct b43legacy_led *led) +{ + if (!led->dev) + return; + led_classdev_unregister(&led->led_dev); + b43legacy_led_turn_off(led->dev, led->index, led->activelow); + led->dev = NULL; +} + +static void b43legacy_map_led(struct b43legacy_wldev *dev, + u8 led_index, + enum b43legacy_led_behaviour behaviour, + bool activelow) +{ + struct ieee80211_hw *hw = dev->wl->hw; + char name[B43legacy_LED_MAX_NAME_LEN + 1]; + + /* Map the b43 specific LED behaviour value to the + * generic LED triggers. */ + switch (behaviour) { + case B43legacy_LED_INACTIVE: + break; + case B43legacy_LED_OFF: + b43legacy_led_turn_off(dev, led_index, activelow); + break; + case B43legacy_LED_ON: + b43legacy_led_turn_on(dev, led_index, activelow); + break; + case B43legacy_LED_ACTIVITY: + case B43legacy_LED_TRANSFER: + case B43legacy_LED_APTRANSFER: + snprintf(name, sizeof(name), + "b43legacy-%s::tx", wiphy_name(hw->wiphy)); + b43legacy_register_led(dev, &dev->led_tx, name, + ieee80211_get_tx_led_name(hw), + led_index, activelow); + snprintf(name, sizeof(name), + "b43legacy-%s::rx", wiphy_name(hw->wiphy)); + b43legacy_register_led(dev, &dev->led_rx, name, + ieee80211_get_rx_led_name(hw), + led_index, activelow); + break; + case B43legacy_LED_RADIO_ALL: + case B43legacy_LED_RADIO_A: + case B43legacy_LED_RADIO_B: + case B43legacy_LED_MODE_BG: + snprintf(name, sizeof(name), + "b43legacy-%s::radio", wiphy_name(hw->wiphy)); + b43legacy_register_led(dev, &dev->led_radio, name, + ieee80211_get_radio_led_name(hw), + led_index, activelow); + /* Sync the RF-kill LED state with radio and switch states. */ + if (dev->phy.radio_on && b43legacy_is_hw_radio_enabled(dev)) + b43legacy_led_turn_on(dev, led_index, activelow); + break; + case B43legacy_LED_WEIRD: + case B43legacy_LED_ASSOC: + snprintf(name, sizeof(name), + "b43legacy-%s::assoc", wiphy_name(hw->wiphy)); + b43legacy_register_led(dev, &dev->led_assoc, name, + ieee80211_get_assoc_led_name(hw), + led_index, activelow); + break; + default: + b43legacywarn(dev->wl, "LEDs: Unknown behaviour 0x%02X\n", + behaviour); + break; + } +} + +void b43legacy_leds_init(struct b43legacy_wldev *dev) +{ + struct ssb_bus *bus = dev->dev->bus; + u8 sprom[4]; + int i; + enum b43legacy_led_behaviour behaviour; + bool activelow; + + sprom[0] = bus->sprom.gpio0; + sprom[1] = bus->sprom.gpio1; + sprom[2] = bus->sprom.gpio2; + sprom[3] = bus->sprom.gpio3; + + for (i = 0; i < 4; i++) { + if (sprom[i] == 0xFF) { + /* There is no LED information in the SPROM + * for this LED. Hardcode it here. */ + activelow = false; + switch (i) { + case 0: + behaviour = B43legacy_LED_ACTIVITY; + activelow = true; + if (bus->boardinfo.vendor == PCI_VENDOR_ID_COMPAQ) + behaviour = B43legacy_LED_RADIO_ALL; + break; + case 1: + behaviour = B43legacy_LED_RADIO_B; + if (bus->boardinfo.vendor == PCI_VENDOR_ID_ASUSTEK) + behaviour = B43legacy_LED_ASSOC; + break; + case 2: + behaviour = B43legacy_LED_RADIO_A; + break; + case 3: + behaviour = B43legacy_LED_OFF; + break; + default: + B43legacy_WARN_ON(1); + return; + } + } else { + behaviour = sprom[i] & B43legacy_LED_BEHAVIOUR; + activelow = !!(sprom[i] & B43legacy_LED_ACTIVELOW); + } + b43legacy_map_led(dev, i, behaviour, activelow); + } +} + +void b43legacy_leds_exit(struct b43legacy_wldev *dev) +{ + b43legacy_unregister_led(&dev->led_tx); + b43legacy_unregister_led(&dev->led_rx); + b43legacy_unregister_led(&dev->led_assoc); + b43legacy_unregister_led(&dev->led_radio); +} diff --git a/drivers/net/wireless/b43legacy/leds.h b/drivers/net/wireless/b43legacy/leds.h new file mode 100644 index 0000000..12b912e --- /dev/null +++ b/drivers/net/wireless/b43legacy/leds.h @@ -0,0 +1,63 @@ +#ifndef B43legacy_LEDS_H_ +#define B43legacy_LEDS_H_ + +struct b43legacy_wldev; + +#ifdef CPTCFG_B43LEGACY_LEDS + +#include +#include + + +#define B43legacy_LED_MAX_NAME_LEN 31 + +struct b43legacy_led { + struct b43legacy_wldev *dev; + /* The LED class device */ + struct led_classdev led_dev; + /* The index number of the LED. */ + u8 index; + /* If activelow is true, the LED is ON if the + * bit is switched off. */ + bool activelow; + /* The unique name string for this LED device. */ + char name[B43legacy_LED_MAX_NAME_LEN + 1]; +}; + +#define B43legacy_LED_BEHAVIOUR 0x7F +#define B43legacy_LED_ACTIVELOW 0x80 +/* LED behaviour values */ +enum b43legacy_led_behaviour { + B43legacy_LED_OFF, + B43legacy_LED_ON, + B43legacy_LED_ACTIVITY, + B43legacy_LED_RADIO_ALL, + B43legacy_LED_RADIO_A, + B43legacy_LED_RADIO_B, + B43legacy_LED_MODE_BG, + B43legacy_LED_TRANSFER, + B43legacy_LED_APTRANSFER, + B43legacy_LED_WEIRD, + B43legacy_LED_ASSOC, + B43legacy_LED_INACTIVE, +}; + +void b43legacy_leds_init(struct b43legacy_wldev *dev); +void b43legacy_leds_exit(struct b43legacy_wldev *dev); + +#else /* CPTCFG_B43LEGACY_LEDS */ +/* LED support disabled */ + +struct b43legacy_led { + /* empty */ +}; + +static inline void b43legacy_leds_init(struct b43legacy_wldev *dev) +{ +} +static inline void b43legacy_leds_exit(struct b43legacy_wldev *dev) +{ +} +#endif /* CPTCFG_B43LEGACY_LEDS */ + +#endif /* B43legacy_LEDS_H_ */ diff --git a/drivers/net/wireless/b43legacy/main.c b/drivers/net/wireless/b43legacy/main.c new file mode 100644 index 0000000..050a674 --- /dev/null +++ b/drivers/net/wireless/b43legacy/main.c @@ -0,0 +1,4064 @@ +/* + * + * Broadcom B43legacy wireless driver + * + * Copyright (c) 2005 Martin Langer + * Copyright (c) 2005-2008 Stefano Brivio + * Copyright (c) 2005, 2006 Michael Buesch + * Copyright (c) 2005 Danny van Dyk + * Copyright (c) 2005 Andreas Jaggi + * Copyright (c) 2007 Larry Finger + * + * Some parts of the code in this file are derived from the ipw2200 + * driver Copyright(c) 2003 - 2004 Intel Corporation. + + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "b43legacy.h" +#include "main.h" +#include "debugfs.h" +#include "phy.h" +#include "dma.h" +#include "pio.h" +#include "sysfs.h" +#include "xmit.h" +#include "radio.h" + + +MODULE_DESCRIPTION("Broadcom B43legacy wireless driver"); +MODULE_AUTHOR("Martin Langer"); +MODULE_AUTHOR("Stefano Brivio"); +MODULE_AUTHOR("Michael Buesch"); +MODULE_LICENSE("GPL"); + +MODULE_FIRMWARE("b43legacy/ucode2.fw"); +MODULE_FIRMWARE("b43legacy/ucode4.fw"); + +#if defined(CPTCFG_B43LEGACY_DMA) && defined(CPTCFG_B43LEGACY_PIO) +static int modparam_pio; +module_param_named(pio, modparam_pio, int, 0444); +MODULE_PARM_DESC(pio, "enable(1) / disable(0) PIO mode"); +#elif defined(CPTCFG_B43LEGACY_DMA) +# define modparam_pio 0 +#elif defined(CPTCFG_B43LEGACY_PIO) +# define modparam_pio 1 +#endif + +static int modparam_bad_frames_preempt; +module_param_named(bad_frames_preempt, modparam_bad_frames_preempt, int, 0444); +MODULE_PARM_DESC(bad_frames_preempt, "enable(1) / disable(0) Bad Frames" + " Preemption"); + +static char modparam_fwpostfix[16]; +module_param_string(fwpostfix, modparam_fwpostfix, 16, 0444); +MODULE_PARM_DESC(fwpostfix, "Postfix for the firmware files to load."); + +/* The following table supports BCM4301, BCM4303 and BCM4306/2 devices. */ +static const struct ssb_device_id b43legacy_ssb_tbl[] = { + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_80211, 2), + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_80211, 4), + SSB_DEVTABLE_END +}; +MODULE_DEVICE_TABLE(ssb, b43legacy_ssb_tbl); + + +/* Channel and ratetables are shared for all devices. + * They can't be const, because ieee80211 puts some precalculated + * data in there. This data is the same for all devices, so we don't + * get concurrency issues */ +#define RATETAB_ENT(_rateid, _flags) \ + { \ + .bitrate = B43legacy_RATE_TO_100KBPS(_rateid), \ + .hw_value = (_rateid), \ + .flags = (_flags), \ + } +/* + * NOTE: When changing this, sync with xmit.c's + * b43legacy_plcp_get_bitrate_idx_* functions! + */ +static struct ieee80211_rate __b43legacy_ratetable[] = { + RATETAB_ENT(B43legacy_CCK_RATE_1MB, 0), + RATETAB_ENT(B43legacy_CCK_RATE_2MB, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(B43legacy_CCK_RATE_5MB, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(B43legacy_CCK_RATE_11MB, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(B43legacy_OFDM_RATE_6MB, 0), + RATETAB_ENT(B43legacy_OFDM_RATE_9MB, 0), + RATETAB_ENT(B43legacy_OFDM_RATE_12MB, 0), + RATETAB_ENT(B43legacy_OFDM_RATE_18MB, 0), + RATETAB_ENT(B43legacy_OFDM_RATE_24MB, 0), + RATETAB_ENT(B43legacy_OFDM_RATE_36MB, 0), + RATETAB_ENT(B43legacy_OFDM_RATE_48MB, 0), + RATETAB_ENT(B43legacy_OFDM_RATE_54MB, 0), +}; +#define b43legacy_b_ratetable (__b43legacy_ratetable + 0) +#define b43legacy_b_ratetable_size 4 +#define b43legacy_g_ratetable (__b43legacy_ratetable + 0) +#define b43legacy_g_ratetable_size 12 + +#define CHANTAB_ENT(_chanid, _freq) \ + { \ + .center_freq = (_freq), \ + .hw_value = (_chanid), \ + } +static struct ieee80211_channel b43legacy_bg_chantable[] = { + CHANTAB_ENT(1, 2412), + CHANTAB_ENT(2, 2417), + CHANTAB_ENT(3, 2422), + CHANTAB_ENT(4, 2427), + CHANTAB_ENT(5, 2432), + CHANTAB_ENT(6, 2437), + CHANTAB_ENT(7, 2442), + CHANTAB_ENT(8, 2447), + CHANTAB_ENT(9, 2452), + CHANTAB_ENT(10, 2457), + CHANTAB_ENT(11, 2462), + CHANTAB_ENT(12, 2467), + CHANTAB_ENT(13, 2472), + CHANTAB_ENT(14, 2484), +}; + +static struct ieee80211_supported_band b43legacy_band_2GHz_BPHY = { + .channels = b43legacy_bg_chantable, + .n_channels = ARRAY_SIZE(b43legacy_bg_chantable), + .bitrates = b43legacy_b_ratetable, + .n_bitrates = b43legacy_b_ratetable_size, +}; + +static struct ieee80211_supported_band b43legacy_band_2GHz_GPHY = { + .channels = b43legacy_bg_chantable, + .n_channels = ARRAY_SIZE(b43legacy_bg_chantable), + .bitrates = b43legacy_g_ratetable, + .n_bitrates = b43legacy_g_ratetable_size, +}; + +static void b43legacy_wireless_core_exit(struct b43legacy_wldev *dev); +static int b43legacy_wireless_core_init(struct b43legacy_wldev *dev); +static void b43legacy_wireless_core_stop(struct b43legacy_wldev *dev); +static int b43legacy_wireless_core_start(struct b43legacy_wldev *dev); + + +static int b43legacy_ratelimit(struct b43legacy_wl *wl) +{ + if (!wl || !wl->current_dev) + return 1; + if (b43legacy_status(wl->current_dev) < B43legacy_STAT_STARTED) + return 1; + /* We are up and running. + * Ratelimit the messages to avoid DoS over the net. */ + return net_ratelimit(); +} + +void b43legacyinfo(struct b43legacy_wl *wl, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + if (!b43legacy_ratelimit(wl)) + return; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + printk(KERN_INFO "b43legacy-%s: %pV", + (wl && wl->hw) ? wiphy_name(wl->hw->wiphy) : "wlan", &vaf); + + va_end(args); +} + +void b43legacyerr(struct b43legacy_wl *wl, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + if (!b43legacy_ratelimit(wl)) + return; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + printk(KERN_ERR "b43legacy-%s ERROR: %pV", + (wl && wl->hw) ? wiphy_name(wl->hw->wiphy) : "wlan", &vaf); + + va_end(args); +} + +void b43legacywarn(struct b43legacy_wl *wl, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + if (!b43legacy_ratelimit(wl)) + return; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + printk(KERN_WARNING "b43legacy-%s warning: %pV", + (wl && wl->hw) ? wiphy_name(wl->hw->wiphy) : "wlan", &vaf); + + va_end(args); +} + +#if B43legacy_DEBUG +void b43legacydbg(struct b43legacy_wl *wl, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + printk(KERN_DEBUG "b43legacy-%s debug: %pV", + (wl && wl->hw) ? wiphy_name(wl->hw->wiphy) : "wlan", &vaf); + + va_end(args); +} +#endif /* DEBUG */ + +static void b43legacy_ram_write(struct b43legacy_wldev *dev, u16 offset, + u32 val) +{ + u32 status; + + B43legacy_WARN_ON(offset % 4 != 0); + + status = b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + if (status & B43legacy_MACCTL_BE) + val = swab32(val); + + b43legacy_write32(dev, B43legacy_MMIO_RAM_CONTROL, offset); + mmiowb(); + b43legacy_write32(dev, B43legacy_MMIO_RAM_DATA, val); +} + +static inline +void b43legacy_shm_control_word(struct b43legacy_wldev *dev, + u16 routing, u16 offset) +{ + u32 control; + + /* "offset" is the WORD offset. */ + + control = routing; + control <<= 16; + control |= offset; + b43legacy_write32(dev, B43legacy_MMIO_SHM_CONTROL, control); +} + +u32 b43legacy_shm_read32(struct b43legacy_wldev *dev, + u16 routing, u16 offset) +{ + u32 ret; + + if (routing == B43legacy_SHM_SHARED) { + B43legacy_WARN_ON((offset & 0x0001) != 0); + if (offset & 0x0003) { + /* Unaligned access */ + b43legacy_shm_control_word(dev, routing, offset >> 2); + ret = b43legacy_read16(dev, + B43legacy_MMIO_SHM_DATA_UNALIGNED); + ret <<= 16; + b43legacy_shm_control_word(dev, routing, + (offset >> 2) + 1); + ret |= b43legacy_read16(dev, B43legacy_MMIO_SHM_DATA); + + return ret; + } + offset >>= 2; + } + b43legacy_shm_control_word(dev, routing, offset); + ret = b43legacy_read32(dev, B43legacy_MMIO_SHM_DATA); + + return ret; +} + +u16 b43legacy_shm_read16(struct b43legacy_wldev *dev, + u16 routing, u16 offset) +{ + u16 ret; + + if (routing == B43legacy_SHM_SHARED) { + B43legacy_WARN_ON((offset & 0x0001) != 0); + if (offset & 0x0003) { + /* Unaligned access */ + b43legacy_shm_control_word(dev, routing, offset >> 2); + ret = b43legacy_read16(dev, + B43legacy_MMIO_SHM_DATA_UNALIGNED); + + return ret; + } + offset >>= 2; + } + b43legacy_shm_control_word(dev, routing, offset); + ret = b43legacy_read16(dev, B43legacy_MMIO_SHM_DATA); + + return ret; +} + +void b43legacy_shm_write32(struct b43legacy_wldev *dev, + u16 routing, u16 offset, + u32 value) +{ + if (routing == B43legacy_SHM_SHARED) { + B43legacy_WARN_ON((offset & 0x0001) != 0); + if (offset & 0x0003) { + /* Unaligned access */ + b43legacy_shm_control_word(dev, routing, offset >> 2); + mmiowb(); + b43legacy_write16(dev, + B43legacy_MMIO_SHM_DATA_UNALIGNED, + (value >> 16) & 0xffff); + mmiowb(); + b43legacy_shm_control_word(dev, routing, + (offset >> 2) + 1); + mmiowb(); + b43legacy_write16(dev, B43legacy_MMIO_SHM_DATA, + value & 0xffff); + return; + } + offset >>= 2; + } + b43legacy_shm_control_word(dev, routing, offset); + mmiowb(); + b43legacy_write32(dev, B43legacy_MMIO_SHM_DATA, value); +} + +void b43legacy_shm_write16(struct b43legacy_wldev *dev, u16 routing, u16 offset, + u16 value) +{ + if (routing == B43legacy_SHM_SHARED) { + B43legacy_WARN_ON((offset & 0x0001) != 0); + if (offset & 0x0003) { + /* Unaligned access */ + b43legacy_shm_control_word(dev, routing, offset >> 2); + mmiowb(); + b43legacy_write16(dev, + B43legacy_MMIO_SHM_DATA_UNALIGNED, + value); + return; + } + offset >>= 2; + } + b43legacy_shm_control_word(dev, routing, offset); + mmiowb(); + b43legacy_write16(dev, B43legacy_MMIO_SHM_DATA, value); +} + +/* Read HostFlags */ +u32 b43legacy_hf_read(struct b43legacy_wldev *dev) +{ + u32 ret; + + ret = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_HOSTFHI); + ret <<= 16; + ret |= b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_HOSTFLO); + + return ret; +} + +/* Write HostFlags */ +void b43legacy_hf_write(struct b43legacy_wldev *dev, u32 value) +{ + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_HOSTFLO, + (value & 0x0000FFFF)); + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_HOSTFHI, + ((value & 0xFFFF0000) >> 16)); +} + +void b43legacy_tsf_read(struct b43legacy_wldev *dev, u64 *tsf) +{ + /* We need to be careful. As we read the TSF from multiple + * registers, we should take care of register overflows. + * In theory, the whole tsf read process should be atomic. + * We try to be atomic here, by restaring the read process, + * if any of the high registers changed (overflew). + */ + if (dev->dev->id.revision >= 3) { + u32 low; + u32 high; + u32 high2; + + do { + high = b43legacy_read32(dev, + B43legacy_MMIO_REV3PLUS_TSF_HIGH); + low = b43legacy_read32(dev, + B43legacy_MMIO_REV3PLUS_TSF_LOW); + high2 = b43legacy_read32(dev, + B43legacy_MMIO_REV3PLUS_TSF_HIGH); + } while (unlikely(high != high2)); + + *tsf = high; + *tsf <<= 32; + *tsf |= low; + } else { + u64 tmp; + u16 v0; + u16 v1; + u16 v2; + u16 v3; + u16 test1; + u16 test2; + u16 test3; + + do { + v3 = b43legacy_read16(dev, B43legacy_MMIO_TSF_3); + v2 = b43legacy_read16(dev, B43legacy_MMIO_TSF_2); + v1 = b43legacy_read16(dev, B43legacy_MMIO_TSF_1); + v0 = b43legacy_read16(dev, B43legacy_MMIO_TSF_0); + + test3 = b43legacy_read16(dev, B43legacy_MMIO_TSF_3); + test2 = b43legacy_read16(dev, B43legacy_MMIO_TSF_2); + test1 = b43legacy_read16(dev, B43legacy_MMIO_TSF_1); + } while (v3 != test3 || v2 != test2 || v1 != test1); + + *tsf = v3; + *tsf <<= 48; + tmp = v2; + tmp <<= 32; + *tsf |= tmp; + tmp = v1; + tmp <<= 16; + *tsf |= tmp; + *tsf |= v0; + } +} + +static void b43legacy_time_lock(struct b43legacy_wldev *dev) +{ + u32 status; + + status = b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + status |= B43legacy_MACCTL_TBTTHOLD; + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, status); + mmiowb(); +} + +static void b43legacy_time_unlock(struct b43legacy_wldev *dev) +{ + u32 status; + + status = b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + status &= ~B43legacy_MACCTL_TBTTHOLD; + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, status); +} + +static void b43legacy_tsf_write_locked(struct b43legacy_wldev *dev, u64 tsf) +{ + /* Be careful with the in-progress timer. + * First zero out the low register, so we have a full + * register-overflow duration to complete the operation. + */ + if (dev->dev->id.revision >= 3) { + u32 lo = (tsf & 0x00000000FFFFFFFFULL); + u32 hi = (tsf & 0xFFFFFFFF00000000ULL) >> 32; + + b43legacy_write32(dev, B43legacy_MMIO_REV3PLUS_TSF_LOW, 0); + mmiowb(); + b43legacy_write32(dev, B43legacy_MMIO_REV3PLUS_TSF_HIGH, + hi); + mmiowb(); + b43legacy_write32(dev, B43legacy_MMIO_REV3PLUS_TSF_LOW, + lo); + } else { + u16 v0 = (tsf & 0x000000000000FFFFULL); + u16 v1 = (tsf & 0x00000000FFFF0000ULL) >> 16; + u16 v2 = (tsf & 0x0000FFFF00000000ULL) >> 32; + u16 v3 = (tsf & 0xFFFF000000000000ULL) >> 48; + + b43legacy_write16(dev, B43legacy_MMIO_TSF_0, 0); + mmiowb(); + b43legacy_write16(dev, B43legacy_MMIO_TSF_3, v3); + mmiowb(); + b43legacy_write16(dev, B43legacy_MMIO_TSF_2, v2); + mmiowb(); + b43legacy_write16(dev, B43legacy_MMIO_TSF_1, v1); + mmiowb(); + b43legacy_write16(dev, B43legacy_MMIO_TSF_0, v0); + } +} + +void b43legacy_tsf_write(struct b43legacy_wldev *dev, u64 tsf) +{ + b43legacy_time_lock(dev); + b43legacy_tsf_write_locked(dev, tsf); + b43legacy_time_unlock(dev); +} + +static +void b43legacy_macfilter_set(struct b43legacy_wldev *dev, + u16 offset, const u8 *mac) +{ + static const u8 zero_addr[ETH_ALEN] = { 0 }; + u16 data; + + if (!mac) + mac = zero_addr; + + offset |= 0x0020; + b43legacy_write16(dev, B43legacy_MMIO_MACFILTER_CONTROL, offset); + + data = mac[0]; + data |= mac[1] << 8; + b43legacy_write16(dev, B43legacy_MMIO_MACFILTER_DATA, data); + data = mac[2]; + data |= mac[3] << 8; + b43legacy_write16(dev, B43legacy_MMIO_MACFILTER_DATA, data); + data = mac[4]; + data |= mac[5] << 8; + b43legacy_write16(dev, B43legacy_MMIO_MACFILTER_DATA, data); +} + +static void b43legacy_write_mac_bssid_templates(struct b43legacy_wldev *dev) +{ + static const u8 zero_addr[ETH_ALEN] = { 0 }; + const u8 *mac = dev->wl->mac_addr; + const u8 *bssid = dev->wl->bssid; + u8 mac_bssid[ETH_ALEN * 2]; + int i; + u32 tmp; + + if (!bssid) + bssid = zero_addr; + if (!mac) + mac = zero_addr; + + b43legacy_macfilter_set(dev, B43legacy_MACFILTER_BSSID, bssid); + + memcpy(mac_bssid, mac, ETH_ALEN); + memcpy(mac_bssid + ETH_ALEN, bssid, ETH_ALEN); + + /* Write our MAC address and BSSID to template ram */ + for (i = 0; i < ARRAY_SIZE(mac_bssid); i += sizeof(u32)) { + tmp = (u32)(mac_bssid[i + 0]); + tmp |= (u32)(mac_bssid[i + 1]) << 8; + tmp |= (u32)(mac_bssid[i + 2]) << 16; + tmp |= (u32)(mac_bssid[i + 3]) << 24; + b43legacy_ram_write(dev, 0x20 + i, tmp); + b43legacy_ram_write(dev, 0x78 + i, tmp); + b43legacy_ram_write(dev, 0x478 + i, tmp); + } +} + +static void b43legacy_upload_card_macaddress(struct b43legacy_wldev *dev) +{ + b43legacy_write_mac_bssid_templates(dev); + b43legacy_macfilter_set(dev, B43legacy_MACFILTER_SELF, + dev->wl->mac_addr); +} + +static void b43legacy_set_slot_time(struct b43legacy_wldev *dev, + u16 slot_time) +{ + /* slot_time is in usec. */ + if (dev->phy.type != B43legacy_PHYTYPE_G) + return; + b43legacy_write16(dev, 0x684, 510 + slot_time); + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, 0x0010, + slot_time); +} + +static void b43legacy_short_slot_timing_enable(struct b43legacy_wldev *dev) +{ + b43legacy_set_slot_time(dev, 9); +} + +static void b43legacy_short_slot_timing_disable(struct b43legacy_wldev *dev) +{ + b43legacy_set_slot_time(dev, 20); +} + +/* Synchronize IRQ top- and bottom-half. + * IRQs must be masked before calling this. + * This must not be called with the irq_lock held. + */ +static void b43legacy_synchronize_irq(struct b43legacy_wldev *dev) +{ + synchronize_irq(dev->dev->irq); + tasklet_kill(&dev->isr_tasklet); +} + +/* DummyTransmission function, as documented on + * http://bcm-specs.sipsolutions.net/DummyTransmission + */ +void b43legacy_dummy_transmission(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + unsigned int i; + unsigned int max_loop; + u16 value; + u32 buffer[5] = { + 0x00000000, + 0x00D40000, + 0x00000000, + 0x01000000, + 0x00000000, + }; + + switch (phy->type) { + case B43legacy_PHYTYPE_B: + case B43legacy_PHYTYPE_G: + max_loop = 0xFA; + buffer[0] = 0x000B846E; + break; + default: + B43legacy_BUG_ON(1); + return; + } + + for (i = 0; i < 5; i++) + b43legacy_ram_write(dev, i * 4, buffer[i]); + + /* dummy read follows */ + b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + + b43legacy_write16(dev, 0x0568, 0x0000); + b43legacy_write16(dev, 0x07C0, 0x0000); + b43legacy_write16(dev, 0x050C, 0x0000); + b43legacy_write16(dev, 0x0508, 0x0000); + b43legacy_write16(dev, 0x050A, 0x0000); + b43legacy_write16(dev, 0x054C, 0x0000); + b43legacy_write16(dev, 0x056A, 0x0014); + b43legacy_write16(dev, 0x0568, 0x0826); + b43legacy_write16(dev, 0x0500, 0x0000); + b43legacy_write16(dev, 0x0502, 0x0030); + + if (phy->radio_ver == 0x2050 && phy->radio_rev <= 0x5) + b43legacy_radio_write16(dev, 0x0051, 0x0017); + for (i = 0x00; i < max_loop; i++) { + value = b43legacy_read16(dev, 0x050E); + if (value & 0x0080) + break; + udelay(10); + } + for (i = 0x00; i < 0x0A; i++) { + value = b43legacy_read16(dev, 0x050E); + if (value & 0x0400) + break; + udelay(10); + } + for (i = 0x00; i < 0x0A; i++) { + value = b43legacy_read16(dev, 0x0690); + if (!(value & 0x0100)) + break; + udelay(10); + } + if (phy->radio_ver == 0x2050 && phy->radio_rev <= 0x5) + b43legacy_radio_write16(dev, 0x0051, 0x0037); +} + +/* Turn the Analog ON/OFF */ +static void b43legacy_switch_analog(struct b43legacy_wldev *dev, int on) +{ + b43legacy_write16(dev, B43legacy_MMIO_PHY0, on ? 0 : 0xF4); +} + +void b43legacy_wireless_core_reset(struct b43legacy_wldev *dev, u32 flags) +{ + u32 tmslow; + u32 macctl; + + flags |= B43legacy_TMSLOW_PHYCLKEN; + flags |= B43legacy_TMSLOW_PHYRESET; + ssb_device_enable(dev->dev, flags); + msleep(2); /* Wait for the PLL to turn on. */ + + /* Now take the PHY out of Reset again */ + tmslow = ssb_read32(dev->dev, SSB_TMSLOW); + tmslow |= SSB_TMSLOW_FGC; + tmslow &= ~B43legacy_TMSLOW_PHYRESET; + ssb_write32(dev->dev, SSB_TMSLOW, tmslow); + ssb_read32(dev->dev, SSB_TMSLOW); /* flush */ + msleep(1); + tmslow &= ~SSB_TMSLOW_FGC; + ssb_write32(dev->dev, SSB_TMSLOW, tmslow); + ssb_read32(dev->dev, SSB_TMSLOW); /* flush */ + msleep(1); + + /* Turn Analog ON */ + b43legacy_switch_analog(dev, 1); + + macctl = b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + macctl &= ~B43legacy_MACCTL_GMODE; + if (flags & B43legacy_TMSLOW_GMODE) { + macctl |= B43legacy_MACCTL_GMODE; + dev->phy.gmode = true; + } else + dev->phy.gmode = false; + macctl |= B43legacy_MACCTL_IHR_ENABLED; + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, macctl); +} + +static void handle_irq_transmit_status(struct b43legacy_wldev *dev) +{ + u32 v0; + u32 v1; + u16 tmp; + struct b43legacy_txstatus stat; + + while (1) { + v0 = b43legacy_read32(dev, B43legacy_MMIO_XMITSTAT_0); + if (!(v0 & 0x00000001)) + break; + v1 = b43legacy_read32(dev, B43legacy_MMIO_XMITSTAT_1); + + stat.cookie = (v0 >> 16); + stat.seq = (v1 & 0x0000FFFF); + stat.phy_stat = ((v1 & 0x00FF0000) >> 16); + tmp = (v0 & 0x0000FFFF); + stat.frame_count = ((tmp & 0xF000) >> 12); + stat.rts_count = ((tmp & 0x0F00) >> 8); + stat.supp_reason = ((tmp & 0x001C) >> 2); + stat.pm_indicated = !!(tmp & 0x0080); + stat.intermediate = !!(tmp & 0x0040); + stat.for_ampdu = !!(tmp & 0x0020); + stat.acked = !!(tmp & 0x0002); + + b43legacy_handle_txstatus(dev, &stat); + } +} + +static void drain_txstatus_queue(struct b43legacy_wldev *dev) +{ + u32 dummy; + + if (dev->dev->id.revision < 5) + return; + /* Read all entries from the microcode TXstatus FIFO + * and throw them away. + */ + while (1) { + dummy = b43legacy_read32(dev, B43legacy_MMIO_XMITSTAT_0); + if (!(dummy & 0x00000001)) + break; + dummy = b43legacy_read32(dev, B43legacy_MMIO_XMITSTAT_1); + } +} + +static u32 b43legacy_jssi_read(struct b43legacy_wldev *dev) +{ + u32 val = 0; + + val = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, 0x40A); + val <<= 16; + val |= b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, 0x408); + + return val; +} + +static void b43legacy_jssi_write(struct b43legacy_wldev *dev, u32 jssi) +{ + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, 0x408, + (jssi & 0x0000FFFF)); + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, 0x40A, + (jssi & 0xFFFF0000) >> 16); +} + +static void b43legacy_generate_noise_sample(struct b43legacy_wldev *dev) +{ + b43legacy_jssi_write(dev, 0x7F7F7F7F); + b43legacy_write32(dev, B43legacy_MMIO_MACCMD, + b43legacy_read32(dev, B43legacy_MMIO_MACCMD) + | B43legacy_MACCMD_BGNOISE); + B43legacy_WARN_ON(dev->noisecalc.channel_at_start != + dev->phy.channel); +} + +static void b43legacy_calculate_link_quality(struct b43legacy_wldev *dev) +{ + /* Top half of Link Quality calculation. */ + + if (dev->noisecalc.calculation_running) + return; + dev->noisecalc.channel_at_start = dev->phy.channel; + dev->noisecalc.calculation_running = true; + dev->noisecalc.nr_samples = 0; + + b43legacy_generate_noise_sample(dev); +} + +static void handle_irq_noise(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 tmp; + u8 noise[4]; + u8 i; + u8 j; + s32 average; + + /* Bottom half of Link Quality calculation. */ + + B43legacy_WARN_ON(!dev->noisecalc.calculation_running); + if (dev->noisecalc.channel_at_start != phy->channel) + goto drop_calculation; + *((__le32 *)noise) = cpu_to_le32(b43legacy_jssi_read(dev)); + if (noise[0] == 0x7F || noise[1] == 0x7F || + noise[2] == 0x7F || noise[3] == 0x7F) + goto generate_new; + + /* Get the noise samples. */ + B43legacy_WARN_ON(dev->noisecalc.nr_samples >= 8); + i = dev->noisecalc.nr_samples; + noise[0] = clamp_val(noise[0], 0, ARRAY_SIZE(phy->nrssi_lt) - 1); + noise[1] = clamp_val(noise[1], 0, ARRAY_SIZE(phy->nrssi_lt) - 1); + noise[2] = clamp_val(noise[2], 0, ARRAY_SIZE(phy->nrssi_lt) - 1); + noise[3] = clamp_val(noise[3], 0, ARRAY_SIZE(phy->nrssi_lt) - 1); + dev->noisecalc.samples[i][0] = phy->nrssi_lt[noise[0]]; + dev->noisecalc.samples[i][1] = phy->nrssi_lt[noise[1]]; + dev->noisecalc.samples[i][2] = phy->nrssi_lt[noise[2]]; + dev->noisecalc.samples[i][3] = phy->nrssi_lt[noise[3]]; + dev->noisecalc.nr_samples++; + if (dev->noisecalc.nr_samples == 8) { + /* Calculate the Link Quality by the noise samples. */ + average = 0; + for (i = 0; i < 8; i++) { + for (j = 0; j < 4; j++) + average += dev->noisecalc.samples[i][j]; + } + average /= (8 * 4); + average *= 125; + average += 64; + average /= 128; + tmp = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, + 0x40C); + tmp = (tmp / 128) & 0x1F; + if (tmp >= 8) + average += 2; + else + average -= 25; + if (tmp == 8) + average -= 72; + else + average -= 48; + + dev->stats.link_noise = average; +drop_calculation: + dev->noisecalc.calculation_running = false; + return; + } +generate_new: + b43legacy_generate_noise_sample(dev); +} + +static void handle_irq_tbtt_indication(struct b43legacy_wldev *dev) +{ + if (b43legacy_is_mode(dev->wl, NL80211_IFTYPE_AP)) { + /* TODO: PS TBTT */ + } else { + if (1/*FIXME: the last PSpoll frame was sent successfully */) + b43legacy_power_saving_ctl_bits(dev, -1, -1); + } + if (b43legacy_is_mode(dev->wl, NL80211_IFTYPE_ADHOC)) + dev->dfq_valid = true; +} + +static void handle_irq_atim_end(struct b43legacy_wldev *dev) +{ + if (dev->dfq_valid) { + b43legacy_write32(dev, B43legacy_MMIO_MACCMD, + b43legacy_read32(dev, B43legacy_MMIO_MACCMD) + | B43legacy_MACCMD_DFQ_VALID); + dev->dfq_valid = false; + } +} + +static void handle_irq_pmq(struct b43legacy_wldev *dev) +{ + u32 tmp; + + /* TODO: AP mode. */ + + while (1) { + tmp = b43legacy_read32(dev, B43legacy_MMIO_PS_STATUS); + if (!(tmp & 0x00000008)) + break; + } + /* 16bit write is odd, but correct. */ + b43legacy_write16(dev, B43legacy_MMIO_PS_STATUS, 0x0002); +} + +static void b43legacy_write_template_common(struct b43legacy_wldev *dev, + const u8 *data, u16 size, + u16 ram_offset, + u16 shm_size_offset, u8 rate) +{ + u32 i; + u32 tmp; + struct b43legacy_plcp_hdr4 plcp; + + plcp.data = 0; + b43legacy_generate_plcp_hdr(&plcp, size + FCS_LEN, rate); + b43legacy_ram_write(dev, ram_offset, le32_to_cpu(plcp.data)); + ram_offset += sizeof(u32); + /* The PLCP is 6 bytes long, but we only wrote 4 bytes, yet. + * So leave the first two bytes of the next write blank. + */ + tmp = (u32)(data[0]) << 16; + tmp |= (u32)(data[1]) << 24; + b43legacy_ram_write(dev, ram_offset, tmp); + ram_offset += sizeof(u32); + for (i = 2; i < size; i += sizeof(u32)) { + tmp = (u32)(data[i + 0]); + if (i + 1 < size) + tmp |= (u32)(data[i + 1]) << 8; + if (i + 2 < size) + tmp |= (u32)(data[i + 2]) << 16; + if (i + 3 < size) + tmp |= (u32)(data[i + 3]) << 24; + b43legacy_ram_write(dev, ram_offset + i - 2, tmp); + } + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, shm_size_offset, + size + sizeof(struct b43legacy_plcp_hdr6)); +} + +/* Convert a b43legacy antenna number value to the PHY TX control value. */ +static u16 b43legacy_antenna_to_phyctl(int antenna) +{ + switch (antenna) { + case B43legacy_ANTENNA0: + return B43legacy_TX4_PHY_ANT0; + case B43legacy_ANTENNA1: + return B43legacy_TX4_PHY_ANT1; + } + return B43legacy_TX4_PHY_ANTLAST; +} + +static void b43legacy_write_beacon_template(struct b43legacy_wldev *dev, + u16 ram_offset, + u16 shm_size_offset) +{ + + unsigned int i, len, variable_len; + const struct ieee80211_mgmt *bcn; + const u8 *ie; + bool tim_found = false; + unsigned int rate; + u16 ctl; + int antenna; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(dev->wl->current_beacon); + + bcn = (const struct ieee80211_mgmt *)(dev->wl->current_beacon->data); + len = min((size_t)dev->wl->current_beacon->len, + 0x200 - sizeof(struct b43legacy_plcp_hdr6)); + rate = ieee80211_get_tx_rate(dev->wl->hw, info)->hw_value; + + b43legacy_write_template_common(dev, (const u8 *)bcn, len, ram_offset, + shm_size_offset, rate); + + /* Write the PHY TX control parameters. */ + antenna = B43legacy_ANTENNA_DEFAULT; + antenna = b43legacy_antenna_to_phyctl(antenna); + ctl = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_BEACPHYCTL); + /* We can't send beacons with short preamble. Would get PHY errors. */ + ctl &= ~B43legacy_TX4_PHY_SHORTPRMBL; + ctl &= ~B43legacy_TX4_PHY_ANT; + ctl &= ~B43legacy_TX4_PHY_ENC; + ctl |= antenna; + ctl |= B43legacy_TX4_PHY_ENC_CCK; + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_BEACPHYCTL, ctl); + + /* Find the position of the TIM and the DTIM_period value + * and write them to SHM. */ + ie = bcn->u.beacon.variable; + variable_len = len - offsetof(struct ieee80211_mgmt, u.beacon.variable); + for (i = 0; i < variable_len - 2; ) { + uint8_t ie_id, ie_len; + + ie_id = ie[i]; + ie_len = ie[i + 1]; + if (ie_id == 5) { + u16 tim_position; + u16 dtim_period; + /* This is the TIM Information Element */ + + /* Check whether the ie_len is in the beacon data range. */ + if (variable_len < ie_len + 2 + i) + break; + /* A valid TIM is at least 4 bytes long. */ + if (ie_len < 4) + break; + tim_found = true; + + tim_position = sizeof(struct b43legacy_plcp_hdr6); + tim_position += offsetof(struct ieee80211_mgmt, + u.beacon.variable); + tim_position += i; + + dtim_period = ie[i + 3]; + + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_TIMPOS, tim_position); + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_DTIMP, dtim_period); + break; + } + i += ie_len + 2; + } + if (!tim_found) { + b43legacywarn(dev->wl, "Did not find a valid TIM IE in the " + "beacon template packet. AP or IBSS operation " + "may be broken.\n"); + } else + b43legacydbg(dev->wl, "Updated beacon template\n"); +} + +static void b43legacy_write_probe_resp_plcp(struct b43legacy_wldev *dev, + u16 shm_offset, u16 size, + struct ieee80211_rate *rate) +{ + struct b43legacy_plcp_hdr4 plcp; + u32 tmp; + __le16 dur; + + plcp.data = 0; + b43legacy_generate_plcp_hdr(&plcp, size + FCS_LEN, rate->hw_value); + dur = ieee80211_generic_frame_duration(dev->wl->hw, + dev->wl->vif, + IEEE80211_BAND_2GHZ, + size, + rate); + /* Write PLCP in two parts and timing for packet transfer */ + tmp = le32_to_cpu(plcp.data); + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, shm_offset, + tmp & 0xFFFF); + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, shm_offset + 2, + tmp >> 16); + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, shm_offset + 6, + le16_to_cpu(dur)); +} + +/* Instead of using custom probe response template, this function + * just patches custom beacon template by: + * 1) Changing packet type + * 2) Patching duration field + * 3) Stripping TIM + */ +static const u8 *b43legacy_generate_probe_resp(struct b43legacy_wldev *dev, + u16 *dest_size, + struct ieee80211_rate *rate) +{ + const u8 *src_data; + u8 *dest_data; + u16 src_size, elem_size, src_pos, dest_pos; + __le16 dur; + struct ieee80211_hdr *hdr; + size_t ie_start; + + src_size = dev->wl->current_beacon->len; + src_data = (const u8 *)dev->wl->current_beacon->data; + + /* Get the start offset of the variable IEs in the packet. */ + ie_start = offsetof(struct ieee80211_mgmt, u.probe_resp.variable); + B43legacy_WARN_ON(ie_start != offsetof(struct ieee80211_mgmt, + u.beacon.variable)); + + if (B43legacy_WARN_ON(src_size < ie_start)) + return NULL; + + dest_data = kmalloc(src_size, GFP_ATOMIC); + if (unlikely(!dest_data)) + return NULL; + + /* Copy the static data and all Information Elements, except the TIM. */ + memcpy(dest_data, src_data, ie_start); + src_pos = ie_start; + dest_pos = ie_start; + for ( ; src_pos < src_size - 2; src_pos += elem_size) { + elem_size = src_data[src_pos + 1] + 2; + if (src_data[src_pos] == 5) { + /* This is the TIM. */ + continue; + } + memcpy(dest_data + dest_pos, src_data + src_pos, elem_size); + dest_pos += elem_size; + } + *dest_size = dest_pos; + hdr = (struct ieee80211_hdr *)dest_data; + + /* Set the frame control. */ + hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_PROBE_RESP); + dur = ieee80211_generic_frame_duration(dev->wl->hw, + dev->wl->vif, + IEEE80211_BAND_2GHZ, + *dest_size, + rate); + hdr->duration_id = dur; + + return dest_data; +} + +static void b43legacy_write_probe_resp_template(struct b43legacy_wldev *dev, + u16 ram_offset, + u16 shm_size_offset, + struct ieee80211_rate *rate) +{ + const u8 *probe_resp_data; + u16 size; + + size = dev->wl->current_beacon->len; + probe_resp_data = b43legacy_generate_probe_resp(dev, &size, rate); + if (unlikely(!probe_resp_data)) + return; + + /* Looks like PLCP headers plus packet timings are stored for + * all possible basic rates + */ + b43legacy_write_probe_resp_plcp(dev, 0x31A, size, + &b43legacy_b_ratetable[0]); + b43legacy_write_probe_resp_plcp(dev, 0x32C, size, + &b43legacy_b_ratetable[1]); + b43legacy_write_probe_resp_plcp(dev, 0x33E, size, + &b43legacy_b_ratetable[2]); + b43legacy_write_probe_resp_plcp(dev, 0x350, size, + &b43legacy_b_ratetable[3]); + + size = min((size_t)size, + 0x200 - sizeof(struct b43legacy_plcp_hdr6)); + b43legacy_write_template_common(dev, probe_resp_data, + size, ram_offset, + shm_size_offset, rate->hw_value); + kfree(probe_resp_data); +} + +static void b43legacy_upload_beacon0(struct b43legacy_wldev *dev) +{ + struct b43legacy_wl *wl = dev->wl; + + if (wl->beacon0_uploaded) + return; + b43legacy_write_beacon_template(dev, 0x68, 0x18); + /* FIXME: Probe resp upload doesn't really belong here, + * but we don't use that feature anyway. */ + b43legacy_write_probe_resp_template(dev, 0x268, 0x4A, + &__b43legacy_ratetable[3]); + wl->beacon0_uploaded = true; +} + +static void b43legacy_upload_beacon1(struct b43legacy_wldev *dev) +{ + struct b43legacy_wl *wl = dev->wl; + + if (wl->beacon1_uploaded) + return; + b43legacy_write_beacon_template(dev, 0x468, 0x1A); + wl->beacon1_uploaded = true; +} + +static void handle_irq_beacon(struct b43legacy_wldev *dev) +{ + struct b43legacy_wl *wl = dev->wl; + u32 cmd, beacon0_valid, beacon1_valid; + + if (!b43legacy_is_mode(wl, NL80211_IFTYPE_AP)) + return; + + /* This is the bottom half of the asynchronous beacon update. */ + + /* Ignore interrupt in the future. */ + dev->irq_mask &= ~B43legacy_IRQ_BEACON; + + cmd = b43legacy_read32(dev, B43legacy_MMIO_MACCMD); + beacon0_valid = (cmd & B43legacy_MACCMD_BEACON0_VALID); + beacon1_valid = (cmd & B43legacy_MACCMD_BEACON1_VALID); + + /* Schedule interrupt manually, if busy. */ + if (beacon0_valid && beacon1_valid) { + b43legacy_write32(dev, B43legacy_MMIO_GEN_IRQ_REASON, B43legacy_IRQ_BEACON); + dev->irq_mask |= B43legacy_IRQ_BEACON; + return; + } + + if (unlikely(wl->beacon_templates_virgin)) { + /* We never uploaded a beacon before. + * Upload both templates now, but only mark one valid. */ + wl->beacon_templates_virgin = false; + b43legacy_upload_beacon0(dev); + b43legacy_upload_beacon1(dev); + cmd = b43legacy_read32(dev, B43legacy_MMIO_MACCMD); + cmd |= B43legacy_MACCMD_BEACON0_VALID; + b43legacy_write32(dev, B43legacy_MMIO_MACCMD, cmd); + } else { + if (!beacon0_valid) { + b43legacy_upload_beacon0(dev); + cmd = b43legacy_read32(dev, B43legacy_MMIO_MACCMD); + cmd |= B43legacy_MACCMD_BEACON0_VALID; + b43legacy_write32(dev, B43legacy_MMIO_MACCMD, cmd); + } else if (!beacon1_valid) { + b43legacy_upload_beacon1(dev); + cmd = b43legacy_read32(dev, B43legacy_MMIO_MACCMD); + cmd |= B43legacy_MACCMD_BEACON1_VALID; + b43legacy_write32(dev, B43legacy_MMIO_MACCMD, cmd); + } + } +} + +static void b43legacy_beacon_update_trigger_work(struct work_struct *work) +{ + struct b43legacy_wl *wl = container_of(work, struct b43legacy_wl, + beacon_update_trigger); + struct b43legacy_wldev *dev; + + mutex_lock(&wl->mutex); + dev = wl->current_dev; + if (likely(dev && (b43legacy_status(dev) >= B43legacy_STAT_INITIALIZED))) { + spin_lock_irq(&wl->irq_lock); + /* Update beacon right away or defer to IRQ. */ + handle_irq_beacon(dev); + /* The handler might have updated the IRQ mask. */ + b43legacy_write32(dev, B43legacy_MMIO_GEN_IRQ_MASK, + dev->irq_mask); + mmiowb(); + spin_unlock_irq(&wl->irq_lock); + } + mutex_unlock(&wl->mutex); +} + +/* Asynchronously update the packet templates in template RAM. + * Locking: Requires wl->irq_lock to be locked. */ +static void b43legacy_update_templates(struct b43legacy_wl *wl) +{ + struct sk_buff *beacon; + /* This is the top half of the ansynchronous beacon update. The bottom + * half is the beacon IRQ. Beacon update must be asynchronous to avoid + * sending an invalid beacon. This can happen for example, if the + * firmware transmits a beacon while we are updating it. */ + + /* We could modify the existing beacon and set the aid bit in the TIM + * field, but that would probably require resizing and moving of data + * within the beacon template. Simply request a new beacon and let + * mac80211 do the hard work. */ + beacon = ieee80211_beacon_get(wl->hw, wl->vif); + if (unlikely(!beacon)) + return; + + if (wl->current_beacon) + dev_kfree_skb_any(wl->current_beacon); + wl->current_beacon = beacon; + wl->beacon0_uploaded = false; + wl->beacon1_uploaded = false; + ieee80211_queue_work(wl->hw, &wl->beacon_update_trigger); +} + +static void b43legacy_set_beacon_int(struct b43legacy_wldev *dev, + u16 beacon_int) +{ + b43legacy_time_lock(dev); + if (dev->dev->id.revision >= 3) { + b43legacy_write32(dev, B43legacy_MMIO_TSF_CFP_REP, + (beacon_int << 16)); + b43legacy_write32(dev, B43legacy_MMIO_TSF_CFP_START, + (beacon_int << 10)); + } else { + b43legacy_write16(dev, 0x606, (beacon_int >> 6)); + b43legacy_write16(dev, 0x610, beacon_int); + } + b43legacy_time_unlock(dev); + b43legacydbg(dev->wl, "Set beacon interval to %u\n", beacon_int); +} + +static void handle_irq_ucode_debug(struct b43legacy_wldev *dev) +{ +} + +/* Interrupt handler bottom-half */ +static void b43legacy_interrupt_tasklet(struct b43legacy_wldev *dev) +{ + u32 reason; + u32 dma_reason[ARRAY_SIZE(dev->dma_reason)]; + u32 merged_dma_reason = 0; + int i; + unsigned long flags; + + spin_lock_irqsave(&dev->wl->irq_lock, flags); + + B43legacy_WARN_ON(b43legacy_status(dev) < + B43legacy_STAT_INITIALIZED); + + reason = dev->irq_reason; + for (i = 0; i < ARRAY_SIZE(dma_reason); i++) { + dma_reason[i] = dev->dma_reason[i]; + merged_dma_reason |= dma_reason[i]; + } + + if (unlikely(reason & B43legacy_IRQ_MAC_TXERR)) + b43legacyerr(dev->wl, "MAC transmission error\n"); + + if (unlikely(reason & B43legacy_IRQ_PHY_TXERR)) { + b43legacyerr(dev->wl, "PHY transmission error\n"); + rmb(); + if (unlikely(atomic_dec_and_test(&dev->phy.txerr_cnt))) { + b43legacyerr(dev->wl, "Too many PHY TX errors, " + "restarting the controller\n"); + b43legacy_controller_restart(dev, "PHY TX errors"); + } + } + + if (unlikely(merged_dma_reason & (B43legacy_DMAIRQ_FATALMASK | + B43legacy_DMAIRQ_NONFATALMASK))) { + if (merged_dma_reason & B43legacy_DMAIRQ_FATALMASK) { + b43legacyerr(dev->wl, "Fatal DMA error: " + "0x%08X, 0x%08X, 0x%08X, " + "0x%08X, 0x%08X, 0x%08X\n", + dma_reason[0], dma_reason[1], + dma_reason[2], dma_reason[3], + dma_reason[4], dma_reason[5]); + b43legacy_controller_restart(dev, "DMA error"); + mmiowb(); + spin_unlock_irqrestore(&dev->wl->irq_lock, flags); + return; + } + if (merged_dma_reason & B43legacy_DMAIRQ_NONFATALMASK) + b43legacyerr(dev->wl, "DMA error: " + "0x%08X, 0x%08X, 0x%08X, " + "0x%08X, 0x%08X, 0x%08X\n", + dma_reason[0], dma_reason[1], + dma_reason[2], dma_reason[3], + dma_reason[4], dma_reason[5]); + } + + if (unlikely(reason & B43legacy_IRQ_UCODE_DEBUG)) + handle_irq_ucode_debug(dev); + if (reason & B43legacy_IRQ_TBTT_INDI) + handle_irq_tbtt_indication(dev); + if (reason & B43legacy_IRQ_ATIM_END) + handle_irq_atim_end(dev); + if (reason & B43legacy_IRQ_BEACON) + handle_irq_beacon(dev); + if (reason & B43legacy_IRQ_PMQ) + handle_irq_pmq(dev); + if (reason & B43legacy_IRQ_TXFIFO_FLUSH_OK) + ;/*TODO*/ + if (reason & B43legacy_IRQ_NOISESAMPLE_OK) + handle_irq_noise(dev); + + /* Check the DMA reason registers for received data. */ + if (dma_reason[0] & B43legacy_DMAIRQ_RX_DONE) { + if (b43legacy_using_pio(dev)) + b43legacy_pio_rx(dev->pio.queue0); + else + b43legacy_dma_rx(dev->dma.rx_ring0); + } + B43legacy_WARN_ON(dma_reason[1] & B43legacy_DMAIRQ_RX_DONE); + B43legacy_WARN_ON(dma_reason[2] & B43legacy_DMAIRQ_RX_DONE); + if (dma_reason[3] & B43legacy_DMAIRQ_RX_DONE) { + if (b43legacy_using_pio(dev)) + b43legacy_pio_rx(dev->pio.queue3); + else + b43legacy_dma_rx(dev->dma.rx_ring3); + } + B43legacy_WARN_ON(dma_reason[4] & B43legacy_DMAIRQ_RX_DONE); + B43legacy_WARN_ON(dma_reason[5] & B43legacy_DMAIRQ_RX_DONE); + + if (reason & B43legacy_IRQ_TX_OK) + handle_irq_transmit_status(dev); + + b43legacy_write32(dev, B43legacy_MMIO_GEN_IRQ_MASK, dev->irq_mask); + mmiowb(); + spin_unlock_irqrestore(&dev->wl->irq_lock, flags); +} + +static void pio_irq_workaround(struct b43legacy_wldev *dev, + u16 base, int queueidx) +{ + u16 rxctl; + + rxctl = b43legacy_read16(dev, base + B43legacy_PIO_RXCTL); + if (rxctl & B43legacy_PIO_RXCTL_DATAAVAILABLE) + dev->dma_reason[queueidx] |= B43legacy_DMAIRQ_RX_DONE; + else + dev->dma_reason[queueidx] &= ~B43legacy_DMAIRQ_RX_DONE; +} + +static void b43legacy_interrupt_ack(struct b43legacy_wldev *dev, u32 reason) +{ + if (b43legacy_using_pio(dev) && + (dev->dev->id.revision < 3) && + (!(reason & B43legacy_IRQ_PIO_WORKAROUND))) { + /* Apply a PIO specific workaround to the dma_reasons */ + pio_irq_workaround(dev, B43legacy_MMIO_PIO1_BASE, 0); + pio_irq_workaround(dev, B43legacy_MMIO_PIO2_BASE, 1); + pio_irq_workaround(dev, B43legacy_MMIO_PIO3_BASE, 2); + pio_irq_workaround(dev, B43legacy_MMIO_PIO4_BASE, 3); + } + + b43legacy_write32(dev, B43legacy_MMIO_GEN_IRQ_REASON, reason); + + b43legacy_write32(dev, B43legacy_MMIO_DMA0_REASON, + dev->dma_reason[0]); + b43legacy_write32(dev, B43legacy_MMIO_DMA1_REASON, + dev->dma_reason[1]); + b43legacy_write32(dev, B43legacy_MMIO_DMA2_REASON, + dev->dma_reason[2]); + b43legacy_write32(dev, B43legacy_MMIO_DMA3_REASON, + dev->dma_reason[3]); + b43legacy_write32(dev, B43legacy_MMIO_DMA4_REASON, + dev->dma_reason[4]); + b43legacy_write32(dev, B43legacy_MMIO_DMA5_REASON, + dev->dma_reason[5]); +} + +/* Interrupt handler top-half */ +static irqreturn_t b43legacy_interrupt_handler(int irq, void *dev_id) +{ + irqreturn_t ret = IRQ_NONE; + struct b43legacy_wldev *dev = dev_id; + u32 reason; + + B43legacy_WARN_ON(!dev); + + spin_lock(&dev->wl->irq_lock); + + if (unlikely(b43legacy_status(dev) < B43legacy_STAT_STARTED)) + /* This can only happen on shared IRQ lines. */ + goto out; + reason = b43legacy_read32(dev, B43legacy_MMIO_GEN_IRQ_REASON); + if (reason == 0xffffffff) /* shared IRQ */ + goto out; + ret = IRQ_HANDLED; + reason &= dev->irq_mask; + if (!reason) + goto out; + + dev->dma_reason[0] = b43legacy_read32(dev, + B43legacy_MMIO_DMA0_REASON) + & 0x0001DC00; + dev->dma_reason[1] = b43legacy_read32(dev, + B43legacy_MMIO_DMA1_REASON) + & 0x0000DC00; + dev->dma_reason[2] = b43legacy_read32(dev, + B43legacy_MMIO_DMA2_REASON) + & 0x0000DC00; + dev->dma_reason[3] = b43legacy_read32(dev, + B43legacy_MMIO_DMA3_REASON) + & 0x0001DC00; + dev->dma_reason[4] = b43legacy_read32(dev, + B43legacy_MMIO_DMA4_REASON) + & 0x0000DC00; + dev->dma_reason[5] = b43legacy_read32(dev, + B43legacy_MMIO_DMA5_REASON) + & 0x0000DC00; + + b43legacy_interrupt_ack(dev, reason); + /* Disable all IRQs. They are enabled again in the bottom half. */ + b43legacy_write32(dev, B43legacy_MMIO_GEN_IRQ_MASK, 0); + /* Save the reason code and call our bottom half. */ + dev->irq_reason = reason; + tasklet_schedule(&dev->isr_tasklet); +out: + mmiowb(); + spin_unlock(&dev->wl->irq_lock); + + return ret; +} + +static void b43legacy_release_firmware(struct b43legacy_wldev *dev) +{ + release_firmware(dev->fw.ucode); + dev->fw.ucode = NULL; + release_firmware(dev->fw.pcm); + dev->fw.pcm = NULL; + release_firmware(dev->fw.initvals); + dev->fw.initvals = NULL; + release_firmware(dev->fw.initvals_band); + dev->fw.initvals_band = NULL; +} + +static void b43legacy_print_fw_helptext(struct b43legacy_wl *wl) +{ + b43legacyerr(wl, "You must go to http://wireless.kernel.org/en/users/" + "Drivers/b43#devicefirmware " + "and download the correct firmware (version 3).\n"); +} + +static void b43legacy_fw_cb(const struct firmware *firmware, void *context) +{ + struct b43legacy_wldev *dev = context; + + dev->fwp = firmware; + complete(&dev->fw_load_complete); +} + +static int do_request_fw(struct b43legacy_wldev *dev, + const char *name, + const struct firmware **fw, bool async) +{ + char path[sizeof(modparam_fwpostfix) + 32]; + struct b43legacy_fw_header *hdr; + u32 size; + int err; + + if (!name) + return 0; + + snprintf(path, ARRAY_SIZE(path), + "b43legacy%s/%s.fw", + modparam_fwpostfix, name); + b43legacyinfo(dev->wl, "Loading firmware %s\n", path); + if (async) { + init_completion(&dev->fw_load_complete); + err = request_firmware_nowait(THIS_MODULE, 1, path, + dev->dev->dev, GFP_KERNEL, + dev, b43legacy_fw_cb); + if (err) { + b43legacyerr(dev->wl, "Unable to load firmware\n"); + return err; + } + /* stall here until fw ready */ + wait_for_completion(&dev->fw_load_complete); + if (!dev->fwp) + err = -EINVAL; + *fw = dev->fwp; + } else { + err = request_firmware(fw, path, dev->dev->dev); + } + if (err) { + b43legacyerr(dev->wl, "Firmware file \"%s\" not found " + "or load failed.\n", path); + return err; + } + if ((*fw)->size < sizeof(struct b43legacy_fw_header)) + goto err_format; + hdr = (struct b43legacy_fw_header *)((*fw)->data); + switch (hdr->type) { + case B43legacy_FW_TYPE_UCODE: + case B43legacy_FW_TYPE_PCM: + size = be32_to_cpu(hdr->size); + if (size != (*fw)->size - sizeof(struct b43legacy_fw_header)) + goto err_format; + /* fallthrough */ + case B43legacy_FW_TYPE_IV: + if (hdr->ver != 1) + goto err_format; + break; + default: + goto err_format; + } + + return err; + +err_format: + b43legacyerr(dev->wl, "Firmware file \"%s\" format error.\n", path); + return -EPROTO; +} + +static int b43legacy_one_core_attach(struct ssb_device *dev, + struct b43legacy_wl *wl); +static void b43legacy_one_core_detach(struct ssb_device *dev); + +static void b43legacy_request_firmware(struct work_struct *work) +{ + struct b43legacy_wl *wl = container_of(work, + struct b43legacy_wl, firmware_load); + struct b43legacy_wldev *dev = wl->current_dev; + struct b43legacy_firmware *fw = &dev->fw; + const u8 rev = dev->dev->id.revision; + const char *filename; + int err; + + if (!fw->ucode) { + if (rev == 2) + filename = "ucode2"; + else if (rev == 4) + filename = "ucode4"; + else + filename = "ucode5"; + err = do_request_fw(dev, filename, &fw->ucode, true); + if (err) + goto err_load; + } + if (!fw->pcm) { + if (rev < 5) + filename = "pcm4"; + else + filename = "pcm5"; + err = do_request_fw(dev, filename, &fw->pcm, false); + if (err) + goto err_load; + } + if (!fw->initvals) { + switch (dev->phy.type) { + case B43legacy_PHYTYPE_B: + case B43legacy_PHYTYPE_G: + if ((rev >= 5) && (rev <= 10)) + filename = "b0g0initvals5"; + else if (rev == 2 || rev == 4) + filename = "b0g0initvals2"; + else + goto err_no_initvals; + break; + default: + goto err_no_initvals; + } + err = do_request_fw(dev, filename, &fw->initvals, false); + if (err) + goto err_load; + } + if (!fw->initvals_band) { + switch (dev->phy.type) { + case B43legacy_PHYTYPE_B: + case B43legacy_PHYTYPE_G: + if ((rev >= 5) && (rev <= 10)) + filename = "b0g0bsinitvals5"; + else if (rev >= 11) + filename = NULL; + else if (rev == 2 || rev == 4) + filename = NULL; + else + goto err_no_initvals; + break; + default: + goto err_no_initvals; + } + err = do_request_fw(dev, filename, &fw->initvals_band, false); + if (err) + goto err_load; + } + err = ieee80211_register_hw(wl->hw); + if (err) + goto err_one_core_detach; + return; + +err_one_core_detach: + b43legacy_one_core_detach(dev->dev); + goto error; + +err_load: + b43legacy_print_fw_helptext(dev->wl); + goto error; + +err_no_initvals: + err = -ENODEV; + b43legacyerr(dev->wl, "No Initial Values firmware file for PHY %u, " + "core rev %u\n", dev->phy.type, rev); + goto error; + +error: + b43legacy_release_firmware(dev); + return; +} + +static int b43legacy_upload_microcode(struct b43legacy_wldev *dev) +{ + struct wiphy *wiphy = dev->wl->hw->wiphy; + const size_t hdr_len = sizeof(struct b43legacy_fw_header); + const __be32 *data; + unsigned int i; + unsigned int len; + u16 fwrev; + u16 fwpatch; + u16 fwdate; + u16 fwtime; + u32 tmp, macctl; + int err = 0; + + /* Jump the microcode PSM to offset 0 */ + macctl = b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + B43legacy_WARN_ON(macctl & B43legacy_MACCTL_PSM_RUN); + macctl |= B43legacy_MACCTL_PSM_JMP0; + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, macctl); + /* Zero out all microcode PSM registers and shared memory. */ + for (i = 0; i < 64; i++) + b43legacy_shm_write16(dev, B43legacy_SHM_WIRELESS, i, 0); + for (i = 0; i < 4096; i += 2) + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, i, 0); + + /* Upload Microcode. */ + data = (__be32 *) (dev->fw.ucode->data + hdr_len); + len = (dev->fw.ucode->size - hdr_len) / sizeof(__be32); + b43legacy_shm_control_word(dev, + B43legacy_SHM_UCODE | + B43legacy_SHM_AUTOINC_W, + 0x0000); + for (i = 0; i < len; i++) { + b43legacy_write32(dev, B43legacy_MMIO_SHM_DATA, + be32_to_cpu(data[i])); + udelay(10); + } + + if (dev->fw.pcm) { + /* Upload PCM data. */ + data = (__be32 *) (dev->fw.pcm->data + hdr_len); + len = (dev->fw.pcm->size - hdr_len) / sizeof(__be32); + b43legacy_shm_control_word(dev, B43legacy_SHM_HW, 0x01EA); + b43legacy_write32(dev, B43legacy_MMIO_SHM_DATA, 0x00004000); + /* No need for autoinc bit in SHM_HW */ + b43legacy_shm_control_word(dev, B43legacy_SHM_HW, 0x01EB); + for (i = 0; i < len; i++) { + b43legacy_write32(dev, B43legacy_MMIO_SHM_DATA, + be32_to_cpu(data[i])); + udelay(10); + } + } + + b43legacy_write32(dev, B43legacy_MMIO_GEN_IRQ_REASON, + B43legacy_IRQ_ALL); + + /* Start the microcode PSM */ + macctl = b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + macctl &= ~B43legacy_MACCTL_PSM_JMP0; + macctl |= B43legacy_MACCTL_PSM_RUN; + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, macctl); + + /* Wait for the microcode to load and respond */ + i = 0; + while (1) { + tmp = b43legacy_read32(dev, B43legacy_MMIO_GEN_IRQ_REASON); + if (tmp == B43legacy_IRQ_MAC_SUSPENDED) + break; + i++; + if (i >= B43legacy_IRQWAIT_MAX_RETRIES) { + b43legacyerr(dev->wl, "Microcode not responding\n"); + b43legacy_print_fw_helptext(dev->wl); + err = -ENODEV; + goto error; + } + msleep_interruptible(50); + if (signal_pending(current)) { + err = -EINTR; + goto error; + } + } + /* dummy read follows */ + b43legacy_read32(dev, B43legacy_MMIO_GEN_IRQ_REASON); + + /* Get and check the revisions. */ + fwrev = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_UCODEREV); + fwpatch = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_UCODEPATCH); + fwdate = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_UCODEDATE); + fwtime = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_UCODETIME); + + if (fwrev > 0x128) { + b43legacyerr(dev->wl, "YOU ARE TRYING TO LOAD V4 FIRMWARE." + " Only firmware from binary drivers version 3.x" + " is supported. You must change your firmware" + " files.\n"); + b43legacy_print_fw_helptext(dev->wl); + err = -EOPNOTSUPP; + goto error; + } + b43legacyinfo(dev->wl, "Loading firmware version 0x%X, patch level %u " + "(20%.2i-%.2i-%.2i %.2i:%.2i:%.2i)\n", fwrev, fwpatch, + (fwdate >> 12) & 0xF, (fwdate >> 8) & 0xF, fwdate & 0xFF, + (fwtime >> 11) & 0x1F, (fwtime >> 5) & 0x3F, + fwtime & 0x1F); + + dev->fw.rev = fwrev; + dev->fw.patch = fwpatch; + + snprintf(wiphy->fw_version, sizeof(wiphy->fw_version), "%u.%u", + dev->fw.rev, dev->fw.patch); + wiphy->hw_version = dev->dev->id.coreid; + + return 0; + +error: + macctl = b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + macctl &= ~B43legacy_MACCTL_PSM_RUN; + macctl |= B43legacy_MACCTL_PSM_JMP0; + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, macctl); + + return err; +} + +static int b43legacy_write_initvals(struct b43legacy_wldev *dev, + const struct b43legacy_iv *ivals, + size_t count, + size_t array_size) +{ + const struct b43legacy_iv *iv; + u16 offset; + size_t i; + bool bit32; + + BUILD_BUG_ON(sizeof(struct b43legacy_iv) != 6); + iv = ivals; + for (i = 0; i < count; i++) { + if (array_size < sizeof(iv->offset_size)) + goto err_format; + array_size -= sizeof(iv->offset_size); + offset = be16_to_cpu(iv->offset_size); + bit32 = !!(offset & B43legacy_IV_32BIT); + offset &= B43legacy_IV_OFFSET_MASK; + if (offset >= 0x1000) + goto err_format; + if (bit32) { + u32 value; + + if (array_size < sizeof(iv->data.d32)) + goto err_format; + array_size -= sizeof(iv->data.d32); + + value = get_unaligned_be32(&iv->data.d32); + b43legacy_write32(dev, offset, value); + + iv = (const struct b43legacy_iv *)((const uint8_t *)iv + + sizeof(__be16) + + sizeof(__be32)); + } else { + u16 value; + + if (array_size < sizeof(iv->data.d16)) + goto err_format; + array_size -= sizeof(iv->data.d16); + + value = be16_to_cpu(iv->data.d16); + b43legacy_write16(dev, offset, value); + + iv = (const struct b43legacy_iv *)((const uint8_t *)iv + + sizeof(__be16) + + sizeof(__be16)); + } + } + if (array_size) + goto err_format; + + return 0; + +err_format: + b43legacyerr(dev->wl, "Initial Values Firmware file-format error.\n"); + b43legacy_print_fw_helptext(dev->wl); + + return -EPROTO; +} + +static int b43legacy_upload_initvals(struct b43legacy_wldev *dev) +{ + const size_t hdr_len = sizeof(struct b43legacy_fw_header); + const struct b43legacy_fw_header *hdr; + struct b43legacy_firmware *fw = &dev->fw; + const struct b43legacy_iv *ivals; + size_t count; + int err; + + hdr = (const struct b43legacy_fw_header *)(fw->initvals->data); + ivals = (const struct b43legacy_iv *)(fw->initvals->data + hdr_len); + count = be32_to_cpu(hdr->size); + err = b43legacy_write_initvals(dev, ivals, count, + fw->initvals->size - hdr_len); + if (err) + goto out; + if (fw->initvals_band) { + hdr = (const struct b43legacy_fw_header *) + (fw->initvals_band->data); + ivals = (const struct b43legacy_iv *)(fw->initvals_band->data + + hdr_len); + count = be32_to_cpu(hdr->size); + err = b43legacy_write_initvals(dev, ivals, count, + fw->initvals_band->size - hdr_len); + if (err) + goto out; + } +out: + + return err; +} + +/* Initialize the GPIOs + * http://bcm-specs.sipsolutions.net/GPIO + */ +static int b43legacy_gpio_init(struct b43legacy_wldev *dev) +{ + struct ssb_bus *bus = dev->dev->bus; + struct ssb_device *gpiodev, *pcidev = NULL; + u32 mask; + u32 set; + + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, + b43legacy_read32(dev, + B43legacy_MMIO_MACCTL) + & 0xFFFF3FFF); + + b43legacy_write16(dev, B43legacy_MMIO_GPIO_MASK, + b43legacy_read16(dev, + B43legacy_MMIO_GPIO_MASK) + | 0x000F); + + mask = 0x0000001F; + set = 0x0000000F; + if (dev->dev->bus->chip_id == 0x4301) { + mask |= 0x0060; + set |= 0x0060; + } + if (dev->dev->bus->sprom.boardflags_lo & B43legacy_BFL_PACTRL) { + b43legacy_write16(dev, B43legacy_MMIO_GPIO_MASK, + b43legacy_read16(dev, + B43legacy_MMIO_GPIO_MASK) + | 0x0200); + mask |= 0x0200; + set |= 0x0200; + } + if (dev->dev->id.revision >= 2) + mask |= 0x0010; /* FIXME: This is redundant. */ + +#ifdef CPTCFG_SSB_DRIVER_PCICORE + pcidev = bus->pcicore.dev; +#endif + gpiodev = bus->chipco.dev ? : pcidev; + if (!gpiodev) + return 0; + ssb_write32(gpiodev, B43legacy_GPIO_CONTROL, + (ssb_read32(gpiodev, B43legacy_GPIO_CONTROL) + & ~mask) | set); + + return 0; +} + +/* Turn off all GPIO stuff. Call this on module unload, for example. */ +static void b43legacy_gpio_cleanup(struct b43legacy_wldev *dev) +{ + struct ssb_bus *bus = dev->dev->bus; + struct ssb_device *gpiodev, *pcidev = NULL; + +#ifdef CPTCFG_SSB_DRIVER_PCICORE + pcidev = bus->pcicore.dev; +#endif + gpiodev = bus->chipco.dev ? : pcidev; + if (!gpiodev) + return; + ssb_write32(gpiodev, B43legacy_GPIO_CONTROL, 0); +} + +/* http://bcm-specs.sipsolutions.net/EnableMac */ +void b43legacy_mac_enable(struct b43legacy_wldev *dev) +{ + dev->mac_suspended--; + B43legacy_WARN_ON(dev->mac_suspended < 0); + B43legacy_WARN_ON(irqs_disabled()); + if (dev->mac_suspended == 0) { + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, + b43legacy_read32(dev, + B43legacy_MMIO_MACCTL) + | B43legacy_MACCTL_ENABLED); + b43legacy_write32(dev, B43legacy_MMIO_GEN_IRQ_REASON, + B43legacy_IRQ_MAC_SUSPENDED); + /* the next two are dummy reads */ + b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + b43legacy_read32(dev, B43legacy_MMIO_GEN_IRQ_REASON); + b43legacy_power_saving_ctl_bits(dev, -1, -1); + + /* Re-enable IRQs. */ + spin_lock_irq(&dev->wl->irq_lock); + b43legacy_write32(dev, B43legacy_MMIO_GEN_IRQ_MASK, + dev->irq_mask); + spin_unlock_irq(&dev->wl->irq_lock); + } +} + +/* http://bcm-specs.sipsolutions.net/SuspendMAC */ +void b43legacy_mac_suspend(struct b43legacy_wldev *dev) +{ + int i; + u32 tmp; + + might_sleep(); + B43legacy_WARN_ON(irqs_disabled()); + B43legacy_WARN_ON(dev->mac_suspended < 0); + + if (dev->mac_suspended == 0) { + /* Mask IRQs before suspending MAC. Otherwise + * the MAC stays busy and won't suspend. */ + spin_lock_irq(&dev->wl->irq_lock); + b43legacy_write32(dev, B43legacy_MMIO_GEN_IRQ_MASK, 0); + spin_unlock_irq(&dev->wl->irq_lock); + b43legacy_synchronize_irq(dev); + + b43legacy_power_saving_ctl_bits(dev, -1, 1); + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, + b43legacy_read32(dev, + B43legacy_MMIO_MACCTL) + & ~B43legacy_MACCTL_ENABLED); + b43legacy_read32(dev, B43legacy_MMIO_GEN_IRQ_REASON); + for (i = 40; i; i--) { + tmp = b43legacy_read32(dev, + B43legacy_MMIO_GEN_IRQ_REASON); + if (tmp & B43legacy_IRQ_MAC_SUSPENDED) + goto out; + msleep(1); + } + b43legacyerr(dev->wl, "MAC suspend failed\n"); + } +out: + dev->mac_suspended++; +} + +static void b43legacy_adjust_opmode(struct b43legacy_wldev *dev) +{ + struct b43legacy_wl *wl = dev->wl; + u32 ctl; + u16 cfp_pretbtt; + + ctl = b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + /* Reset status to STA infrastructure mode. */ + ctl &= ~B43legacy_MACCTL_AP; + ctl &= ~B43legacy_MACCTL_KEEP_CTL; + ctl &= ~B43legacy_MACCTL_KEEP_BADPLCP; + ctl &= ~B43legacy_MACCTL_KEEP_BAD; + ctl &= ~B43legacy_MACCTL_PROMISC; + ctl &= ~B43legacy_MACCTL_BEACPROMISC; + ctl |= B43legacy_MACCTL_INFRA; + + if (b43legacy_is_mode(wl, NL80211_IFTYPE_AP)) + ctl |= B43legacy_MACCTL_AP; + else if (b43legacy_is_mode(wl, NL80211_IFTYPE_ADHOC)) + ctl &= ~B43legacy_MACCTL_INFRA; + + if (wl->filter_flags & FIF_CONTROL) + ctl |= B43legacy_MACCTL_KEEP_CTL; + if (wl->filter_flags & FIF_FCSFAIL) + ctl |= B43legacy_MACCTL_KEEP_BAD; + if (wl->filter_flags & FIF_PLCPFAIL) + ctl |= B43legacy_MACCTL_KEEP_BADPLCP; + if (wl->filter_flags & FIF_PROMISC_IN_BSS) + ctl |= B43legacy_MACCTL_PROMISC; + if (wl->filter_flags & FIF_BCN_PRBRESP_PROMISC) + ctl |= B43legacy_MACCTL_BEACPROMISC; + + /* Workaround: On old hardware the HW-MAC-address-filter + * doesn't work properly, so always run promisc in filter + * it in software. */ + if (dev->dev->id.revision <= 4) + ctl |= B43legacy_MACCTL_PROMISC; + + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, ctl); + + cfp_pretbtt = 2; + if ((ctl & B43legacy_MACCTL_INFRA) && + !(ctl & B43legacy_MACCTL_AP)) { + if (dev->dev->bus->chip_id == 0x4306 && + dev->dev->bus->chip_rev == 3) + cfp_pretbtt = 100; + else + cfp_pretbtt = 50; + } + b43legacy_write16(dev, 0x612, cfp_pretbtt); +} + +static void b43legacy_rate_memory_write(struct b43legacy_wldev *dev, + u16 rate, + int is_ofdm) +{ + u16 offset; + + if (is_ofdm) { + offset = 0x480; + offset += (b43legacy_plcp_get_ratecode_ofdm(rate) & 0x000F) * 2; + } else { + offset = 0x4C0; + offset += (b43legacy_plcp_get_ratecode_cck(rate) & 0x000F) * 2; + } + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, offset + 0x20, + b43legacy_shm_read16(dev, + B43legacy_SHM_SHARED, offset)); +} + +static void b43legacy_rate_memory_init(struct b43legacy_wldev *dev) +{ + switch (dev->phy.type) { + case B43legacy_PHYTYPE_G: + b43legacy_rate_memory_write(dev, B43legacy_OFDM_RATE_6MB, 1); + b43legacy_rate_memory_write(dev, B43legacy_OFDM_RATE_12MB, 1); + b43legacy_rate_memory_write(dev, B43legacy_OFDM_RATE_18MB, 1); + b43legacy_rate_memory_write(dev, B43legacy_OFDM_RATE_24MB, 1); + b43legacy_rate_memory_write(dev, B43legacy_OFDM_RATE_36MB, 1); + b43legacy_rate_memory_write(dev, B43legacy_OFDM_RATE_48MB, 1); + b43legacy_rate_memory_write(dev, B43legacy_OFDM_RATE_54MB, 1); + /* fallthrough */ + case B43legacy_PHYTYPE_B: + b43legacy_rate_memory_write(dev, B43legacy_CCK_RATE_1MB, 0); + b43legacy_rate_memory_write(dev, B43legacy_CCK_RATE_2MB, 0); + b43legacy_rate_memory_write(dev, B43legacy_CCK_RATE_5MB, 0); + b43legacy_rate_memory_write(dev, B43legacy_CCK_RATE_11MB, 0); + break; + default: + B43legacy_BUG_ON(1); + } +} + +/* Set the TX-Antenna for management frames sent by firmware. */ +static void b43legacy_mgmtframe_txantenna(struct b43legacy_wldev *dev, + int antenna) +{ + u16 ant = 0; + u16 tmp; + + switch (antenna) { + case B43legacy_ANTENNA0: + ant |= B43legacy_TX4_PHY_ANT0; + break; + case B43legacy_ANTENNA1: + ant |= B43legacy_TX4_PHY_ANT1; + break; + case B43legacy_ANTENNA_AUTO: + ant |= B43legacy_TX4_PHY_ANTLAST; + break; + default: + B43legacy_BUG_ON(1); + } + + /* FIXME We also need to set the other flags of the PHY control + * field somewhere. */ + + /* For Beacons */ + tmp = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_BEACPHYCTL); + tmp = (tmp & ~B43legacy_TX4_PHY_ANT) | ant; + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_BEACPHYCTL, tmp); + /* For ACK/CTS */ + tmp = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_ACKCTSPHYCTL); + tmp = (tmp & ~B43legacy_TX4_PHY_ANT) | ant; + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_ACKCTSPHYCTL, tmp); + /* For Probe Resposes */ + tmp = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_PRPHYCTL); + tmp = (tmp & ~B43legacy_TX4_PHY_ANT) | ant; + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_PRPHYCTL, tmp); +} + +/* This is the opposite of b43legacy_chip_init() */ +static void b43legacy_chip_exit(struct b43legacy_wldev *dev) +{ + b43legacy_radio_turn_off(dev, 1); + b43legacy_gpio_cleanup(dev); + /* firmware is released later */ +} + +/* Initialize the chip + * http://bcm-specs.sipsolutions.net/ChipInit + */ +static int b43legacy_chip_init(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + int err; + int tmp; + u32 value32, macctl; + u16 value16; + + /* Initialize the MAC control */ + macctl = B43legacy_MACCTL_IHR_ENABLED | B43legacy_MACCTL_SHM_ENABLED; + if (dev->phy.gmode) + macctl |= B43legacy_MACCTL_GMODE; + macctl |= B43legacy_MACCTL_INFRA; + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, macctl); + + err = b43legacy_upload_microcode(dev); + if (err) + goto out; /* firmware is released later */ + + err = b43legacy_gpio_init(dev); + if (err) + goto out; /* firmware is released later */ + + err = b43legacy_upload_initvals(dev); + if (err) + goto err_gpio_clean; + b43legacy_radio_turn_on(dev); + + b43legacy_write16(dev, 0x03E6, 0x0000); + err = b43legacy_phy_init(dev); + if (err) + goto err_radio_off; + + /* Select initial Interference Mitigation. */ + tmp = phy->interfmode; + phy->interfmode = B43legacy_INTERFMODE_NONE; + b43legacy_radio_set_interference_mitigation(dev, tmp); + + b43legacy_phy_set_antenna_diversity(dev); + b43legacy_mgmtframe_txantenna(dev, B43legacy_ANTENNA_DEFAULT); + + if (phy->type == B43legacy_PHYTYPE_B) { + value16 = b43legacy_read16(dev, 0x005E); + value16 |= 0x0004; + b43legacy_write16(dev, 0x005E, value16); + } + b43legacy_write32(dev, 0x0100, 0x01000000); + if (dev->dev->id.revision < 5) + b43legacy_write32(dev, 0x010C, 0x01000000); + + value32 = b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + value32 &= ~B43legacy_MACCTL_INFRA; + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, value32); + value32 = b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + value32 |= B43legacy_MACCTL_INFRA; + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, value32); + + if (b43legacy_using_pio(dev)) { + b43legacy_write32(dev, 0x0210, 0x00000100); + b43legacy_write32(dev, 0x0230, 0x00000100); + b43legacy_write32(dev, 0x0250, 0x00000100); + b43legacy_write32(dev, 0x0270, 0x00000100); + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, 0x0034, + 0x0000); + } + + /* Probe Response Timeout value */ + /* FIXME: Default to 0, has to be set by ioctl probably... :-/ */ + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, 0x0074, 0x0000); + + /* Initially set the wireless operation mode. */ + b43legacy_adjust_opmode(dev); + + if (dev->dev->id.revision < 3) { + b43legacy_write16(dev, 0x060E, 0x0000); + b43legacy_write16(dev, 0x0610, 0x8000); + b43legacy_write16(dev, 0x0604, 0x0000); + b43legacy_write16(dev, 0x0606, 0x0200); + } else { + b43legacy_write32(dev, 0x0188, 0x80000000); + b43legacy_write32(dev, 0x018C, 0x02000000); + } + b43legacy_write32(dev, B43legacy_MMIO_GEN_IRQ_REASON, 0x00004000); + b43legacy_write32(dev, B43legacy_MMIO_DMA0_IRQ_MASK, 0x0001DC00); + b43legacy_write32(dev, B43legacy_MMIO_DMA1_IRQ_MASK, 0x0000DC00); + b43legacy_write32(dev, B43legacy_MMIO_DMA2_IRQ_MASK, 0x0000DC00); + b43legacy_write32(dev, B43legacy_MMIO_DMA3_IRQ_MASK, 0x0001DC00); + b43legacy_write32(dev, B43legacy_MMIO_DMA4_IRQ_MASK, 0x0000DC00); + b43legacy_write32(dev, B43legacy_MMIO_DMA5_IRQ_MASK, 0x0000DC00); + + value32 = ssb_read32(dev->dev, SSB_TMSLOW); + value32 |= B43legacy_TMSLOW_MACPHYCLKEN; + ssb_write32(dev->dev, SSB_TMSLOW, value32); + + b43legacy_write16(dev, B43legacy_MMIO_POWERUP_DELAY, + dev->dev->bus->chipco.fast_pwrup_delay); + + /* PHY TX errors counter. */ + atomic_set(&phy->txerr_cnt, B43legacy_PHY_TX_BADNESS_LIMIT); + + B43legacy_WARN_ON(err != 0); + b43legacydbg(dev->wl, "Chip initialized\n"); +out: + return err; + +err_radio_off: + b43legacy_radio_turn_off(dev, 1); +err_gpio_clean: + b43legacy_gpio_cleanup(dev); + goto out; +} + +static void b43legacy_periodic_every120sec(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + + if (phy->type != B43legacy_PHYTYPE_G || phy->rev < 2) + return; + + b43legacy_mac_suspend(dev); + b43legacy_phy_lo_g_measure(dev); + b43legacy_mac_enable(dev); +} + +static void b43legacy_periodic_every60sec(struct b43legacy_wldev *dev) +{ + b43legacy_phy_lo_mark_all_unused(dev); + if (dev->dev->bus->sprom.boardflags_lo & B43legacy_BFL_RSSI) { + b43legacy_mac_suspend(dev); + b43legacy_calc_nrssi_slope(dev); + b43legacy_mac_enable(dev); + } +} + +static void b43legacy_periodic_every30sec(struct b43legacy_wldev *dev) +{ + /* Update device statistics. */ + b43legacy_calculate_link_quality(dev); +} + +static void b43legacy_periodic_every15sec(struct b43legacy_wldev *dev) +{ + b43legacy_phy_xmitpower(dev); /* FIXME: unless scanning? */ + + atomic_set(&dev->phy.txerr_cnt, B43legacy_PHY_TX_BADNESS_LIMIT); + wmb(); +} + +static void do_periodic_work(struct b43legacy_wldev *dev) +{ + unsigned int state; + + state = dev->periodic_state; + if (state % 8 == 0) + b43legacy_periodic_every120sec(dev); + if (state % 4 == 0) + b43legacy_periodic_every60sec(dev); + if (state % 2 == 0) + b43legacy_periodic_every30sec(dev); + b43legacy_periodic_every15sec(dev); +} + +/* Periodic work locking policy: + * The whole periodic work handler is protected by + * wl->mutex. If another lock is needed somewhere in the + * pwork callchain, it's acquired in-place, where it's needed. + */ +static void b43legacy_periodic_work_handler(struct work_struct *work) +{ + struct b43legacy_wldev *dev = container_of(work, struct b43legacy_wldev, + periodic_work.work); + struct b43legacy_wl *wl = dev->wl; + unsigned long delay; + + mutex_lock(&wl->mutex); + + if (unlikely(b43legacy_status(dev) != B43legacy_STAT_STARTED)) + goto out; + if (b43legacy_debug(dev, B43legacy_DBG_PWORK_STOP)) + goto out_requeue; + + do_periodic_work(dev); + + dev->periodic_state++; +out_requeue: + if (b43legacy_debug(dev, B43legacy_DBG_PWORK_FAST)) + delay = msecs_to_jiffies(50); + else + delay = round_jiffies_relative(HZ * 15); + ieee80211_queue_delayed_work(wl->hw, &dev->periodic_work, delay); +out: + mutex_unlock(&wl->mutex); +} + +static void b43legacy_periodic_tasks_setup(struct b43legacy_wldev *dev) +{ + struct delayed_work *work = &dev->periodic_work; + + dev->periodic_state = 0; + INIT_DELAYED_WORK(work, b43legacy_periodic_work_handler); + ieee80211_queue_delayed_work(dev->wl->hw, work, 0); +} + +/* Validate access to the chip (SHM) */ +static int b43legacy_validate_chipaccess(struct b43legacy_wldev *dev) +{ + u32 value; + u32 shm_backup; + + shm_backup = b43legacy_shm_read32(dev, B43legacy_SHM_SHARED, 0); + b43legacy_shm_write32(dev, B43legacy_SHM_SHARED, 0, 0xAA5555AA); + if (b43legacy_shm_read32(dev, B43legacy_SHM_SHARED, 0) != + 0xAA5555AA) + goto error; + b43legacy_shm_write32(dev, B43legacy_SHM_SHARED, 0, 0x55AAAA55); + if (b43legacy_shm_read32(dev, B43legacy_SHM_SHARED, 0) != + 0x55AAAA55) + goto error; + b43legacy_shm_write32(dev, B43legacy_SHM_SHARED, 0, shm_backup); + + value = b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + if ((value | B43legacy_MACCTL_GMODE) != + (B43legacy_MACCTL_GMODE | B43legacy_MACCTL_IHR_ENABLED)) + goto error; + + value = b43legacy_read32(dev, B43legacy_MMIO_GEN_IRQ_REASON); + if (value) + goto error; + + return 0; +error: + b43legacyerr(dev->wl, "Failed to validate the chipaccess\n"); + return -ENODEV; +} + +static void b43legacy_security_init(struct b43legacy_wldev *dev) +{ + dev->max_nr_keys = (dev->dev->id.revision >= 5) ? 58 : 20; + B43legacy_WARN_ON(dev->max_nr_keys > ARRAY_SIZE(dev->key)); + dev->ktp = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, + 0x0056); + /* KTP is a word address, but we address SHM bytewise. + * So multiply by two. + */ + dev->ktp *= 2; + if (dev->dev->id.revision >= 5) + /* Number of RCMTA address slots */ + b43legacy_write16(dev, B43legacy_MMIO_RCMTA_COUNT, + dev->max_nr_keys - 8); +} + +#ifdef CPTCFG_B43LEGACY_HWRNG +static int b43legacy_rng_read(struct hwrng *rng, u32 *data) +{ + struct b43legacy_wl *wl = (struct b43legacy_wl *)rng->priv; + unsigned long flags; + + /* Don't take wl->mutex here, as it could deadlock with + * hwrng internal locking. It's not needed to take + * wl->mutex here, anyway. */ + + spin_lock_irqsave(&wl->irq_lock, flags); + *data = b43legacy_read16(wl->current_dev, B43legacy_MMIO_RNG); + spin_unlock_irqrestore(&wl->irq_lock, flags); + + return (sizeof(u16)); +} +#endif + +static void b43legacy_rng_exit(struct b43legacy_wl *wl) +{ +#ifdef CPTCFG_B43LEGACY_HWRNG + if (wl->rng_initialized) + hwrng_unregister(&wl->rng); +#endif +} + +static int b43legacy_rng_init(struct b43legacy_wl *wl) +{ + int err = 0; + +#ifdef CPTCFG_B43LEGACY_HWRNG + snprintf(wl->rng_name, ARRAY_SIZE(wl->rng_name), + "%s_%s", KBUILD_MODNAME, wiphy_name(wl->hw->wiphy)); + wl->rng.name = wl->rng_name; + wl->rng.data_read = b43legacy_rng_read; + wl->rng.priv = (unsigned long)wl; + wl->rng_initialized = 1; + err = hwrng_register(&wl->rng); + if (err) { + wl->rng_initialized = 0; + b43legacyerr(wl, "Failed to register the random " + "number generator (%d)\n", err); + } + +#endif + return err; +} + +static void b43legacy_tx_work(struct work_struct *work) +{ + struct b43legacy_wl *wl = container_of(work, struct b43legacy_wl, + tx_work); + struct b43legacy_wldev *dev; + struct sk_buff *skb; + int queue_num; + int err = 0; + + mutex_lock(&wl->mutex); + dev = wl->current_dev; + if (unlikely(!dev || b43legacy_status(dev) < B43legacy_STAT_STARTED)) { + mutex_unlock(&wl->mutex); + return; + } + + for (queue_num = 0; queue_num < B43legacy_QOS_QUEUE_NUM; queue_num++) { + while (skb_queue_len(&wl->tx_queue[queue_num])) { + skb = skb_dequeue(&wl->tx_queue[queue_num]); + if (b43legacy_using_pio(dev)) + err = b43legacy_pio_tx(dev, skb); + else + err = b43legacy_dma_tx(dev, skb); + if (err == -ENOSPC) { + wl->tx_queue_stopped[queue_num] = 1; + ieee80211_stop_queue(wl->hw, queue_num); + skb_queue_head(&wl->tx_queue[queue_num], skb); + break; + } + if (unlikely(err)) + dev_kfree_skb(skb); /* Drop it */ + err = 0; + } + + if (!err) + wl->tx_queue_stopped[queue_num] = 0; + } + + mutex_unlock(&wl->mutex); +} + +static void b43legacy_op_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct b43legacy_wl *wl = hw_to_b43legacy_wl(hw); + + if (unlikely(skb->len < 2 + 2 + 6)) { + /* Too short, this can't be a valid frame. */ + dev_kfree_skb_any(skb); + return; + } + B43legacy_WARN_ON(skb_shinfo(skb)->nr_frags); + + skb_queue_tail(&wl->tx_queue[skb_get_queue_mapping(skb)], skb); + if (!wl->tx_queue_stopped[skb_get_queue_mapping(skb)]) + ieee80211_queue_work(wl->hw, &wl->tx_work); + else + ieee80211_stop_queue(wl->hw, skb_get_queue_mapping(skb)); +} + +static int b43legacy_op_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + return 0; +} + +static int b43legacy_op_get_stats(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + struct b43legacy_wl *wl = hw_to_b43legacy_wl(hw); + unsigned long flags; + + spin_lock_irqsave(&wl->irq_lock, flags); + memcpy(stats, &wl->ieee_stats, sizeof(*stats)); + spin_unlock_irqrestore(&wl->irq_lock, flags); + + return 0; +} + +static const char *phymode_to_string(unsigned int phymode) +{ + switch (phymode) { + case B43legacy_PHYMODE_B: + return "B"; + case B43legacy_PHYMODE_G: + return "G"; + default: + B43legacy_BUG_ON(1); + } + return ""; +} + +static int find_wldev_for_phymode(struct b43legacy_wl *wl, + unsigned int phymode, + struct b43legacy_wldev **dev, + bool *gmode) +{ + struct b43legacy_wldev *d; + + list_for_each_entry(d, &wl->devlist, list) { + if (d->phy.possible_phymodes & phymode) { + /* Ok, this device supports the PHY-mode. + * Set the gmode bit. */ + *gmode = true; + *dev = d; + + return 0; + } + } + + return -ESRCH; +} + +static void b43legacy_put_phy_into_reset(struct b43legacy_wldev *dev) +{ + struct ssb_device *sdev = dev->dev; + u32 tmslow; + + tmslow = ssb_read32(sdev, SSB_TMSLOW); + tmslow &= ~B43legacy_TMSLOW_GMODE; + tmslow |= B43legacy_TMSLOW_PHYRESET; + tmslow |= SSB_TMSLOW_FGC; + ssb_write32(sdev, SSB_TMSLOW, tmslow); + msleep(1); + + tmslow = ssb_read32(sdev, SSB_TMSLOW); + tmslow &= ~SSB_TMSLOW_FGC; + tmslow |= B43legacy_TMSLOW_PHYRESET; + ssb_write32(sdev, SSB_TMSLOW, tmslow); + msleep(1); +} + +/* Expects wl->mutex locked */ +static int b43legacy_switch_phymode(struct b43legacy_wl *wl, + unsigned int new_mode) +{ + struct b43legacy_wldev *uninitialized_var(up_dev); + struct b43legacy_wldev *down_dev; + int err; + bool gmode = false; + int prev_status; + + err = find_wldev_for_phymode(wl, new_mode, &up_dev, &gmode); + if (err) { + b43legacyerr(wl, "Could not find a device for %s-PHY mode\n", + phymode_to_string(new_mode)); + return err; + } + if ((up_dev == wl->current_dev) && + (!!wl->current_dev->phy.gmode == !!gmode)) + /* This device is already running. */ + return 0; + b43legacydbg(wl, "Reconfiguring PHYmode to %s-PHY\n", + phymode_to_string(new_mode)); + down_dev = wl->current_dev; + + prev_status = b43legacy_status(down_dev); + /* Shutdown the currently running core. */ + if (prev_status >= B43legacy_STAT_STARTED) + b43legacy_wireless_core_stop(down_dev); + if (prev_status >= B43legacy_STAT_INITIALIZED) + b43legacy_wireless_core_exit(down_dev); + + if (down_dev != up_dev) + /* We switch to a different core, so we put PHY into + * RESET on the old core. */ + b43legacy_put_phy_into_reset(down_dev); + + /* Now start the new core. */ + up_dev->phy.gmode = gmode; + if (prev_status >= B43legacy_STAT_INITIALIZED) { + err = b43legacy_wireless_core_init(up_dev); + if (err) { + b43legacyerr(wl, "Fatal: Could not initialize device" + " for newly selected %s-PHY mode\n", + phymode_to_string(new_mode)); + goto init_failure; + } + } + if (prev_status >= B43legacy_STAT_STARTED) { + err = b43legacy_wireless_core_start(up_dev); + if (err) { + b43legacyerr(wl, "Fatal: Could not start device for " + "newly selected %s-PHY mode\n", + phymode_to_string(new_mode)); + b43legacy_wireless_core_exit(up_dev); + goto init_failure; + } + } + B43legacy_WARN_ON(b43legacy_status(up_dev) != prev_status); + + b43legacy_shm_write32(up_dev, B43legacy_SHM_SHARED, 0x003E, 0); + + wl->current_dev = up_dev; + + return 0; +init_failure: + /* Whoops, failed to init the new core. No core is operating now. */ + wl->current_dev = NULL; + return err; +} + +/* Write the short and long frame retry limit values. */ +static void b43legacy_set_retry_limits(struct b43legacy_wldev *dev, + unsigned int short_retry, + unsigned int long_retry) +{ + /* The retry limit is a 4-bit counter. Enforce this to avoid overflowing + * the chip-internal counter. */ + short_retry = min(short_retry, (unsigned int)0xF); + long_retry = min(long_retry, (unsigned int)0xF); + + b43legacy_shm_write16(dev, B43legacy_SHM_WIRELESS, 0x0006, short_retry); + b43legacy_shm_write16(dev, B43legacy_SHM_WIRELESS, 0x0007, long_retry); +} + +static int b43legacy_op_dev_config(struct ieee80211_hw *hw, + u32 changed) +{ + struct b43legacy_wl *wl = hw_to_b43legacy_wl(hw); + struct b43legacy_wldev *dev; + struct b43legacy_phy *phy; + struct ieee80211_conf *conf = &hw->conf; + unsigned long flags; + unsigned int new_phymode = 0xFFFF; + int antenna_tx; + int err = 0; + + antenna_tx = B43legacy_ANTENNA_DEFAULT; + + mutex_lock(&wl->mutex); + dev = wl->current_dev; + phy = &dev->phy; + + if (changed & IEEE80211_CONF_CHANGE_RETRY_LIMITS) + b43legacy_set_retry_limits(dev, + conf->short_frame_max_tx_count, + conf->long_frame_max_tx_count); + changed &= ~IEEE80211_CONF_CHANGE_RETRY_LIMITS; + if (!changed) + goto out_unlock_mutex; + + /* Switch the PHY mode (if necessary). */ + switch (conf->chandef.chan->band) { + case IEEE80211_BAND_2GHZ: + if (phy->type == B43legacy_PHYTYPE_B) + new_phymode = B43legacy_PHYMODE_B; + else + new_phymode = B43legacy_PHYMODE_G; + break; + default: + B43legacy_WARN_ON(1); + } + err = b43legacy_switch_phymode(wl, new_phymode); + if (err) + goto out_unlock_mutex; + + /* Disable IRQs while reconfiguring the device. + * This makes it possible to drop the spinlock throughout + * the reconfiguration process. */ + spin_lock_irqsave(&wl->irq_lock, flags); + if (b43legacy_status(dev) < B43legacy_STAT_STARTED) { + spin_unlock_irqrestore(&wl->irq_lock, flags); + goto out_unlock_mutex; + } + b43legacy_write32(dev, B43legacy_MMIO_GEN_IRQ_MASK, 0); + spin_unlock_irqrestore(&wl->irq_lock, flags); + b43legacy_synchronize_irq(dev); + + /* Switch to the requested channel. + * The firmware takes care of races with the TX handler. */ + if (conf->chandef.chan->hw_value != phy->channel) + b43legacy_radio_selectchannel(dev, conf->chandef.chan->hw_value, + 0); + + dev->wl->radiotap_enabled = !!(conf->flags & IEEE80211_CONF_MONITOR); + + /* Adjust the desired TX power level. */ + if (conf->power_level != 0) { + if (conf->power_level != phy->power_level) { + phy->power_level = conf->power_level; + b43legacy_phy_xmitpower(dev); + } + } + + /* Antennas for RX and management frame TX. */ + b43legacy_mgmtframe_txantenna(dev, antenna_tx); + + if (wl->radio_enabled != phy->radio_on) { + if (wl->radio_enabled) { + b43legacy_radio_turn_on(dev); + b43legacyinfo(dev->wl, "Radio turned on by software\n"); + if (!dev->radio_hw_enable) + b43legacyinfo(dev->wl, "The hardware RF-kill" + " button still turns the radio" + " physically off. Press the" + " button to turn it on.\n"); + } else { + b43legacy_radio_turn_off(dev, 0); + b43legacyinfo(dev->wl, "Radio turned off by" + " software\n"); + } + } + + spin_lock_irqsave(&wl->irq_lock, flags); + b43legacy_write32(dev, B43legacy_MMIO_GEN_IRQ_MASK, dev->irq_mask); + mmiowb(); + spin_unlock_irqrestore(&wl->irq_lock, flags); +out_unlock_mutex: + mutex_unlock(&wl->mutex); + + return err; +} + +static void b43legacy_update_basic_rates(struct b43legacy_wldev *dev, u32 brates) +{ + struct ieee80211_supported_band *sband = + dev->wl->hw->wiphy->bands[IEEE80211_BAND_2GHZ]; + struct ieee80211_rate *rate; + int i; + u16 basic, direct, offset, basic_offset, rateptr; + + for (i = 0; i < sband->n_bitrates; i++) { + rate = &sband->bitrates[i]; + + if (b43legacy_is_cck_rate(rate->hw_value)) { + direct = B43legacy_SHM_SH_CCKDIRECT; + basic = B43legacy_SHM_SH_CCKBASIC; + offset = b43legacy_plcp_get_ratecode_cck(rate->hw_value); + offset &= 0xF; + } else { + direct = B43legacy_SHM_SH_OFDMDIRECT; + basic = B43legacy_SHM_SH_OFDMBASIC; + offset = b43legacy_plcp_get_ratecode_ofdm(rate->hw_value); + offset &= 0xF; + } + + rate = ieee80211_get_response_rate(sband, brates, rate->bitrate); + + if (b43legacy_is_cck_rate(rate->hw_value)) { + basic_offset = b43legacy_plcp_get_ratecode_cck(rate->hw_value); + basic_offset &= 0xF; + } else { + basic_offset = b43legacy_plcp_get_ratecode_ofdm(rate->hw_value); + basic_offset &= 0xF; + } + + /* + * Get the pointer that we need to point to + * from the direct map + */ + rateptr = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, + direct + 2 * basic_offset); + /* and write it to the basic map */ + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, + basic + 2 * offset, rateptr); + } +} + +static void b43legacy_op_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *conf, + u32 changed) +{ + struct b43legacy_wl *wl = hw_to_b43legacy_wl(hw); + struct b43legacy_wldev *dev; + unsigned long flags; + + mutex_lock(&wl->mutex); + B43legacy_WARN_ON(wl->vif != vif); + + dev = wl->current_dev; + + /* Disable IRQs while reconfiguring the device. + * This makes it possible to drop the spinlock throughout + * the reconfiguration process. */ + spin_lock_irqsave(&wl->irq_lock, flags); + if (b43legacy_status(dev) < B43legacy_STAT_STARTED) { + spin_unlock_irqrestore(&wl->irq_lock, flags); + goto out_unlock_mutex; + } + b43legacy_write32(dev, B43legacy_MMIO_GEN_IRQ_MASK, 0); + + if (changed & BSS_CHANGED_BSSID) { + b43legacy_synchronize_irq(dev); + + if (conf->bssid) + memcpy(wl->bssid, conf->bssid, ETH_ALEN); + else + memset(wl->bssid, 0, ETH_ALEN); + } + + if (b43legacy_status(dev) >= B43legacy_STAT_INITIALIZED) { + if (changed & BSS_CHANGED_BEACON && + (b43legacy_is_mode(wl, NL80211_IFTYPE_AP) || + b43legacy_is_mode(wl, NL80211_IFTYPE_ADHOC))) + b43legacy_update_templates(wl); + + if (changed & BSS_CHANGED_BSSID) + b43legacy_write_mac_bssid_templates(dev); + } + spin_unlock_irqrestore(&wl->irq_lock, flags); + + b43legacy_mac_suspend(dev); + + if (changed & BSS_CHANGED_BEACON_INT && + (b43legacy_is_mode(wl, NL80211_IFTYPE_AP) || + b43legacy_is_mode(wl, NL80211_IFTYPE_ADHOC))) + b43legacy_set_beacon_int(dev, conf->beacon_int); + + if (changed & BSS_CHANGED_BASIC_RATES) + b43legacy_update_basic_rates(dev, conf->basic_rates); + + if (changed & BSS_CHANGED_ERP_SLOT) { + if (conf->use_short_slot) + b43legacy_short_slot_timing_enable(dev); + else + b43legacy_short_slot_timing_disable(dev); + } + + b43legacy_mac_enable(dev); + + spin_lock_irqsave(&wl->irq_lock, flags); + b43legacy_write32(dev, B43legacy_MMIO_GEN_IRQ_MASK, dev->irq_mask); + /* XXX: why? */ + mmiowb(); + spin_unlock_irqrestore(&wl->irq_lock, flags); + out_unlock_mutex: + mutex_unlock(&wl->mutex); +} + +static void b43legacy_op_configure_filter(struct ieee80211_hw *hw, + unsigned int changed, + unsigned int *fflags,u64 multicast) +{ + struct b43legacy_wl *wl = hw_to_b43legacy_wl(hw); + struct b43legacy_wldev *dev = wl->current_dev; + unsigned long flags; + + if (!dev) { + *fflags = 0; + return; + } + + spin_lock_irqsave(&wl->irq_lock, flags); + *fflags &= FIF_PROMISC_IN_BSS | + FIF_ALLMULTI | + FIF_FCSFAIL | + FIF_PLCPFAIL | + FIF_CONTROL | + FIF_OTHER_BSS | + FIF_BCN_PRBRESP_PROMISC; + + changed &= FIF_PROMISC_IN_BSS | + FIF_ALLMULTI | + FIF_FCSFAIL | + FIF_PLCPFAIL | + FIF_CONTROL | + FIF_OTHER_BSS | + FIF_BCN_PRBRESP_PROMISC; + + wl->filter_flags = *fflags; + + if (changed && b43legacy_status(dev) >= B43legacy_STAT_INITIALIZED) + b43legacy_adjust_opmode(dev); + spin_unlock_irqrestore(&wl->irq_lock, flags); +} + +/* Locking: wl->mutex */ +static void b43legacy_wireless_core_stop(struct b43legacy_wldev *dev) +{ + struct b43legacy_wl *wl = dev->wl; + unsigned long flags; + int queue_num; + + if (b43legacy_status(dev) < B43legacy_STAT_STARTED) + return; + + /* Disable and sync interrupts. We must do this before than + * setting the status to INITIALIZED, as the interrupt handler + * won't care about IRQs then. */ + spin_lock_irqsave(&wl->irq_lock, flags); + b43legacy_write32(dev, B43legacy_MMIO_GEN_IRQ_MASK, 0); + b43legacy_read32(dev, B43legacy_MMIO_GEN_IRQ_MASK); /* flush */ + spin_unlock_irqrestore(&wl->irq_lock, flags); + b43legacy_synchronize_irq(dev); + + b43legacy_set_status(dev, B43legacy_STAT_INITIALIZED); + + mutex_unlock(&wl->mutex); + /* Must unlock as it would otherwise deadlock. No races here. + * Cancel the possibly running self-rearming periodic work. */ + cancel_delayed_work_sync(&dev->periodic_work); + cancel_work_sync(&wl->tx_work); + mutex_lock(&wl->mutex); + + /* Drain all TX queues. */ + for (queue_num = 0; queue_num < B43legacy_QOS_QUEUE_NUM; queue_num++) { + while (skb_queue_len(&wl->tx_queue[queue_num])) + dev_kfree_skb(skb_dequeue(&wl->tx_queue[queue_num])); + } + +b43legacy_mac_suspend(dev); + free_irq(dev->dev->irq, dev); + b43legacydbg(wl, "Wireless interface stopped\n"); +} + +/* Locking: wl->mutex */ +static int b43legacy_wireless_core_start(struct b43legacy_wldev *dev) +{ + int err; + + B43legacy_WARN_ON(b43legacy_status(dev) != B43legacy_STAT_INITIALIZED); + + drain_txstatus_queue(dev); + err = request_irq(dev->dev->irq, b43legacy_interrupt_handler, + IRQF_SHARED, KBUILD_MODNAME, dev); + if (err) { + b43legacyerr(dev->wl, "Cannot request IRQ-%d\n", + dev->dev->irq); + goto out; + } + /* We are ready to run. */ + ieee80211_wake_queues(dev->wl->hw); + b43legacy_set_status(dev, B43legacy_STAT_STARTED); + + /* Start data flow (TX/RX) */ + b43legacy_mac_enable(dev); + b43legacy_write32(dev, B43legacy_MMIO_GEN_IRQ_MASK, dev->irq_mask); + + /* Start maintenance work */ + b43legacy_periodic_tasks_setup(dev); + + b43legacydbg(dev->wl, "Wireless interface started\n"); +out: + return err; +} + +/* Get PHY and RADIO versioning numbers */ +static int b43legacy_phy_versioning(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u32 tmp; + u8 analog_type; + u8 phy_type; + u8 phy_rev; + u16 radio_manuf; + u16 radio_ver; + u16 radio_rev; + int unsupported = 0; + + /* Get PHY versioning */ + tmp = b43legacy_read16(dev, B43legacy_MMIO_PHY_VER); + analog_type = (tmp & B43legacy_PHYVER_ANALOG) + >> B43legacy_PHYVER_ANALOG_SHIFT; + phy_type = (tmp & B43legacy_PHYVER_TYPE) >> B43legacy_PHYVER_TYPE_SHIFT; + phy_rev = (tmp & B43legacy_PHYVER_VERSION); + switch (phy_type) { + case B43legacy_PHYTYPE_B: + if (phy_rev != 2 && phy_rev != 4 + && phy_rev != 6 && phy_rev != 7) + unsupported = 1; + break; + case B43legacy_PHYTYPE_G: + if (phy_rev > 8) + unsupported = 1; + break; + default: + unsupported = 1; + } + if (unsupported) { + b43legacyerr(dev->wl, "FOUND UNSUPPORTED PHY " + "(Analog %u, Type %u, Revision %u)\n", + analog_type, phy_type, phy_rev); + return -EOPNOTSUPP; + } + b43legacydbg(dev->wl, "Found PHY: Analog %u, Type %u, Revision %u\n", + analog_type, phy_type, phy_rev); + + + /* Get RADIO versioning */ + if (dev->dev->bus->chip_id == 0x4317) { + if (dev->dev->bus->chip_rev == 0) + tmp = 0x3205017F; + else if (dev->dev->bus->chip_rev == 1) + tmp = 0x4205017F; + else + tmp = 0x5205017F; + } else { + b43legacy_write16(dev, B43legacy_MMIO_RADIO_CONTROL, + B43legacy_RADIOCTL_ID); + tmp = b43legacy_read16(dev, B43legacy_MMIO_RADIO_DATA_HIGH); + tmp <<= 16; + b43legacy_write16(dev, B43legacy_MMIO_RADIO_CONTROL, + B43legacy_RADIOCTL_ID); + tmp |= b43legacy_read16(dev, B43legacy_MMIO_RADIO_DATA_LOW); + } + radio_manuf = (tmp & 0x00000FFF); + radio_ver = (tmp & 0x0FFFF000) >> 12; + radio_rev = (tmp & 0xF0000000) >> 28; + switch (phy_type) { + case B43legacy_PHYTYPE_B: + if ((radio_ver & 0xFFF0) != 0x2050) + unsupported = 1; + break; + case B43legacy_PHYTYPE_G: + if (radio_ver != 0x2050) + unsupported = 1; + break; + default: + B43legacy_BUG_ON(1); + } + if (unsupported) { + b43legacyerr(dev->wl, "FOUND UNSUPPORTED RADIO " + "(Manuf 0x%X, Version 0x%X, Revision %u)\n", + radio_manuf, radio_ver, radio_rev); + return -EOPNOTSUPP; + } + b43legacydbg(dev->wl, "Found Radio: Manuf 0x%X, Version 0x%X," + " Revision %u\n", radio_manuf, radio_ver, radio_rev); + + + phy->radio_manuf = radio_manuf; + phy->radio_ver = radio_ver; + phy->radio_rev = radio_rev; + + phy->analog = analog_type; + phy->type = phy_type; + phy->rev = phy_rev; + + return 0; +} + +static void setup_struct_phy_for_init(struct b43legacy_wldev *dev, + struct b43legacy_phy *phy) +{ + struct b43legacy_lopair *lo; + int i; + + memset(phy->minlowsig, 0xFF, sizeof(phy->minlowsig)); + memset(phy->minlowsigpos, 0, sizeof(phy->minlowsigpos)); + + /* Assume the radio is enabled. If it's not enabled, the state will + * immediately get fixed on the first periodic work run. */ + dev->radio_hw_enable = true; + + phy->savedpctlreg = 0xFFFF; + phy->aci_enable = false; + phy->aci_wlan_automatic = false; + phy->aci_hw_rssi = false; + + lo = phy->_lo_pairs; + if (lo) + memset(lo, 0, sizeof(struct b43legacy_lopair) * + B43legacy_LO_COUNT); + phy->max_lb_gain = 0; + phy->trsw_rx_gain = 0; + + /* Set default attenuation values. */ + phy->bbatt = b43legacy_default_baseband_attenuation(dev); + phy->rfatt = b43legacy_default_radio_attenuation(dev); + phy->txctl1 = b43legacy_default_txctl1(dev); + phy->txpwr_offset = 0; + + /* NRSSI */ + phy->nrssislope = 0; + for (i = 0; i < ARRAY_SIZE(phy->nrssi); i++) + phy->nrssi[i] = -1000; + for (i = 0; i < ARRAY_SIZE(phy->nrssi_lt); i++) + phy->nrssi_lt[i] = i; + + phy->lofcal = 0xFFFF; + phy->initval = 0xFFFF; + + phy->interfmode = B43legacy_INTERFMODE_NONE; + phy->channel = 0xFF; +} + +static void setup_struct_wldev_for_init(struct b43legacy_wldev *dev) +{ + /* Flags */ + dev->dfq_valid = false; + + /* Stats */ + memset(&dev->stats, 0, sizeof(dev->stats)); + + setup_struct_phy_for_init(dev, &dev->phy); + + /* IRQ related flags */ + dev->irq_reason = 0; + memset(dev->dma_reason, 0, sizeof(dev->dma_reason)); + dev->irq_mask = B43legacy_IRQ_MASKTEMPLATE; + + dev->mac_suspended = 1; + + /* Noise calculation context */ + memset(&dev->noisecalc, 0, sizeof(dev->noisecalc)); +} + +static void b43legacy_set_synth_pu_delay(struct b43legacy_wldev *dev, + bool idle) { + u16 pu_delay = 1050; + + if (b43legacy_is_mode(dev->wl, NL80211_IFTYPE_ADHOC) || idle) + pu_delay = 500; + if ((dev->phy.radio_ver == 0x2050) && (dev->phy.radio_rev == 8)) + pu_delay = max(pu_delay, (u16)2400); + + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_SPUWKUP, pu_delay); +} + +/* Set the TSF CFP pre-TargetBeaconTransmissionTime. */ +static void b43legacy_set_pretbtt(struct b43legacy_wldev *dev) +{ + u16 pretbtt; + + /* The time value is in microseconds. */ + if (b43legacy_is_mode(dev->wl, NL80211_IFTYPE_ADHOC)) + pretbtt = 2; + else + pretbtt = 250; + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_PRETBTT, pretbtt); + b43legacy_write16(dev, B43legacy_MMIO_TSF_CFP_PRETBTT, pretbtt); +} + +/* Shutdown a wireless core */ +/* Locking: wl->mutex */ +static void b43legacy_wireless_core_exit(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u32 macctl; + + B43legacy_WARN_ON(b43legacy_status(dev) > B43legacy_STAT_INITIALIZED); + if (b43legacy_status(dev) != B43legacy_STAT_INITIALIZED) + return; + b43legacy_set_status(dev, B43legacy_STAT_UNINIT); + + /* Stop the microcode PSM. */ + macctl = b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + macctl &= ~B43legacy_MACCTL_PSM_RUN; + macctl |= B43legacy_MACCTL_PSM_JMP0; + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, macctl); + + b43legacy_leds_exit(dev); + b43legacy_rng_exit(dev->wl); + b43legacy_pio_free(dev); + b43legacy_dma_free(dev); + b43legacy_chip_exit(dev); + b43legacy_radio_turn_off(dev, 1); + b43legacy_switch_analog(dev, 0); + if (phy->dyn_tssi_tbl) + kfree(phy->tssi2dbm); + kfree(phy->lo_control); + phy->lo_control = NULL; + if (dev->wl->current_beacon) { + dev_kfree_skb_any(dev->wl->current_beacon); + dev->wl->current_beacon = NULL; + } + + ssb_device_disable(dev->dev, 0); + ssb_bus_may_powerdown(dev->dev->bus); +} + +static void prepare_phy_data_for_init(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + int i; + + /* Set default attenuation values. */ + phy->bbatt = b43legacy_default_baseband_attenuation(dev); + phy->rfatt = b43legacy_default_radio_attenuation(dev); + phy->txctl1 = b43legacy_default_txctl1(dev); + phy->txctl2 = 0xFFFF; + phy->txpwr_offset = 0; + + /* NRSSI */ + phy->nrssislope = 0; + for (i = 0; i < ARRAY_SIZE(phy->nrssi); i++) + phy->nrssi[i] = -1000; + for (i = 0; i < ARRAY_SIZE(phy->nrssi_lt); i++) + phy->nrssi_lt[i] = i; + + phy->lofcal = 0xFFFF; + phy->initval = 0xFFFF; + + phy->aci_enable = false; + phy->aci_wlan_automatic = false; + phy->aci_hw_rssi = false; + + phy->antenna_diversity = 0xFFFF; + memset(phy->minlowsig, 0xFF, sizeof(phy->minlowsig)); + memset(phy->minlowsigpos, 0, sizeof(phy->minlowsigpos)); + + /* Flags */ + phy->calibrated = 0; + + if (phy->_lo_pairs) + memset(phy->_lo_pairs, 0, + sizeof(struct b43legacy_lopair) * B43legacy_LO_COUNT); + memset(phy->loopback_gain, 0, sizeof(phy->loopback_gain)); +} + +/* Initialize a wireless core */ +static int b43legacy_wireless_core_init(struct b43legacy_wldev *dev) +{ + struct b43legacy_wl *wl = dev->wl; + struct ssb_bus *bus = dev->dev->bus; + struct b43legacy_phy *phy = &dev->phy; + struct ssb_sprom *sprom = &dev->dev->bus->sprom; + int err; + u32 hf; + u32 tmp; + + B43legacy_WARN_ON(b43legacy_status(dev) != B43legacy_STAT_UNINIT); + + err = ssb_bus_powerup(bus, 0); + if (err) + goto out; + if (!ssb_device_is_enabled(dev->dev)) { + tmp = phy->gmode ? B43legacy_TMSLOW_GMODE : 0; + b43legacy_wireless_core_reset(dev, tmp); + } + + if ((phy->type == B43legacy_PHYTYPE_B) || + (phy->type == B43legacy_PHYTYPE_G)) { + phy->_lo_pairs = kzalloc(sizeof(struct b43legacy_lopair) + * B43legacy_LO_COUNT, + GFP_KERNEL); + if (!phy->_lo_pairs) + return -ENOMEM; + } + setup_struct_wldev_for_init(dev); + + err = b43legacy_phy_init_tssi2dbm_table(dev); + if (err) + goto err_kfree_lo_control; + + /* Enable IRQ routing to this device. */ + ssb_pcicore_dev_irqvecs_enable(&bus->pcicore, dev->dev); + + prepare_phy_data_for_init(dev); + b43legacy_phy_calibrate(dev); + err = b43legacy_chip_init(dev); + if (err) + goto err_kfree_tssitbl; + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_WLCOREREV, + dev->dev->id.revision); + hf = b43legacy_hf_read(dev); + if (phy->type == B43legacy_PHYTYPE_G) { + hf |= B43legacy_HF_SYMW; + if (phy->rev == 1) + hf |= B43legacy_HF_GDCW; + if (sprom->boardflags_lo & B43legacy_BFL_PACTRL) + hf |= B43legacy_HF_OFDMPABOOST; + } else if (phy->type == B43legacy_PHYTYPE_B) { + hf |= B43legacy_HF_SYMW; + if (phy->rev >= 2 && phy->radio_ver == 0x2050) + hf &= ~B43legacy_HF_GDCW; + } + b43legacy_hf_write(dev, hf); + + b43legacy_set_retry_limits(dev, + B43legacy_DEFAULT_SHORT_RETRY_LIMIT, + B43legacy_DEFAULT_LONG_RETRY_LIMIT); + + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, + 0x0044, 3); + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, + 0x0046, 2); + + /* Disable sending probe responses from firmware. + * Setting the MaxTime to one usec will always trigger + * a timeout, so we never send any probe resp. + * A timeout of zero is infinite. */ + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, + B43legacy_SHM_SH_PRMAXTIME, 1); + + b43legacy_rate_memory_init(dev); + + /* Minimum Contention Window */ + if (phy->type == B43legacy_PHYTYPE_B) + b43legacy_shm_write16(dev, B43legacy_SHM_WIRELESS, + 0x0003, 31); + else + b43legacy_shm_write16(dev, B43legacy_SHM_WIRELESS, + 0x0003, 15); + /* Maximum Contention Window */ + b43legacy_shm_write16(dev, B43legacy_SHM_WIRELESS, + 0x0004, 1023); + + do { + if (b43legacy_using_pio(dev)) + err = b43legacy_pio_init(dev); + else { + err = b43legacy_dma_init(dev); + if (!err) + b43legacy_qos_init(dev); + } + } while (err == -EAGAIN); + if (err) + goto err_chip_exit; + + b43legacy_set_synth_pu_delay(dev, 1); + + ssb_bus_powerup(bus, 1); /* Enable dynamic PCTL */ + b43legacy_upload_card_macaddress(dev); + b43legacy_security_init(dev); + b43legacy_rng_init(wl); + + ieee80211_wake_queues(dev->wl->hw); + b43legacy_set_status(dev, B43legacy_STAT_INITIALIZED); + + b43legacy_leds_init(dev); +out: + return err; + +err_chip_exit: + b43legacy_chip_exit(dev); +err_kfree_tssitbl: + if (phy->dyn_tssi_tbl) + kfree(phy->tssi2dbm); +err_kfree_lo_control: + kfree(phy->lo_control); + phy->lo_control = NULL; + ssb_bus_may_powerdown(bus); + B43legacy_WARN_ON(b43legacy_status(dev) != B43legacy_STAT_UNINIT); + return err; +} + +static int b43legacy_op_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct b43legacy_wl *wl = hw_to_b43legacy_wl(hw); + struct b43legacy_wldev *dev; + unsigned long flags; + int err = -EOPNOTSUPP; + + /* TODO: allow WDS/AP devices to coexist */ + + if (vif->type != NL80211_IFTYPE_AP && + vif->type != NL80211_IFTYPE_STATION && + vif->type != NL80211_IFTYPE_WDS && + vif->type != NL80211_IFTYPE_ADHOC) + return -EOPNOTSUPP; + + mutex_lock(&wl->mutex); + if (wl->operating) + goto out_mutex_unlock; + + b43legacydbg(wl, "Adding Interface type %d\n", vif->type); + + dev = wl->current_dev; + wl->operating = true; + wl->vif = vif; + wl->if_type = vif->type; + memcpy(wl->mac_addr, vif->addr, ETH_ALEN); + + spin_lock_irqsave(&wl->irq_lock, flags); + b43legacy_adjust_opmode(dev); + b43legacy_set_pretbtt(dev); + b43legacy_set_synth_pu_delay(dev, 0); + b43legacy_upload_card_macaddress(dev); + spin_unlock_irqrestore(&wl->irq_lock, flags); + + err = 0; + out_mutex_unlock: + mutex_unlock(&wl->mutex); + + return err; +} + +static void b43legacy_op_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct b43legacy_wl *wl = hw_to_b43legacy_wl(hw); + struct b43legacy_wldev *dev = wl->current_dev; + unsigned long flags; + + b43legacydbg(wl, "Removing Interface type %d\n", vif->type); + + mutex_lock(&wl->mutex); + + B43legacy_WARN_ON(!wl->operating); + B43legacy_WARN_ON(wl->vif != vif); + wl->vif = NULL; + + wl->operating = false; + + spin_lock_irqsave(&wl->irq_lock, flags); + b43legacy_adjust_opmode(dev); + memset(wl->mac_addr, 0, ETH_ALEN); + b43legacy_upload_card_macaddress(dev); + spin_unlock_irqrestore(&wl->irq_lock, flags); + + mutex_unlock(&wl->mutex); +} + +static int b43legacy_op_start(struct ieee80211_hw *hw) +{ + struct b43legacy_wl *wl = hw_to_b43legacy_wl(hw); + struct b43legacy_wldev *dev = wl->current_dev; + int did_init = 0; + int err = 0; + + /* Kill all old instance specific information to make sure + * the card won't use it in the short timeframe between start + * and mac80211 reconfiguring it. */ + memset(wl->bssid, 0, ETH_ALEN); + memset(wl->mac_addr, 0, ETH_ALEN); + wl->filter_flags = 0; + wl->beacon0_uploaded = false; + wl->beacon1_uploaded = false; + wl->beacon_templates_virgin = true; + wl->radio_enabled = true; + + mutex_lock(&wl->mutex); + + if (b43legacy_status(dev) < B43legacy_STAT_INITIALIZED) { + err = b43legacy_wireless_core_init(dev); + if (err) + goto out_mutex_unlock; + did_init = 1; + } + + if (b43legacy_status(dev) < B43legacy_STAT_STARTED) { + err = b43legacy_wireless_core_start(dev); + if (err) { + if (did_init) + b43legacy_wireless_core_exit(dev); + goto out_mutex_unlock; + } + } + + wiphy_rfkill_start_polling(hw->wiphy); + +out_mutex_unlock: + mutex_unlock(&wl->mutex); + + return err; +} + +static void b43legacy_op_stop(struct ieee80211_hw *hw) +{ + struct b43legacy_wl *wl = hw_to_b43legacy_wl(hw); + struct b43legacy_wldev *dev = wl->current_dev; + + cancel_work_sync(&(wl->beacon_update_trigger)); + + mutex_lock(&wl->mutex); + if (b43legacy_status(dev) >= B43legacy_STAT_STARTED) + b43legacy_wireless_core_stop(dev); + b43legacy_wireless_core_exit(dev); + wl->radio_enabled = false; + mutex_unlock(&wl->mutex); +} + +static int b43legacy_op_beacon_set_tim(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, bool set) +{ + struct b43legacy_wl *wl = hw_to_b43legacy_wl(hw); + unsigned long flags; + + spin_lock_irqsave(&wl->irq_lock, flags); + b43legacy_update_templates(wl); + spin_unlock_irqrestore(&wl->irq_lock, flags); + + return 0; +} + +static int b43legacy_op_get_survey(struct ieee80211_hw *hw, int idx, + struct survey_info *survey) +{ + struct b43legacy_wl *wl = hw_to_b43legacy_wl(hw); + struct b43legacy_wldev *dev = wl->current_dev; + struct ieee80211_conf *conf = &hw->conf; + + if (idx != 0) + return -ENOENT; + + survey->channel = conf->chandef.chan; + survey->filled = SURVEY_INFO_NOISE_DBM; + survey->noise = dev->stats.link_noise; + + return 0; +} + +static const struct ieee80211_ops b43legacy_hw_ops = { + .tx = b43legacy_op_tx, + .conf_tx = b43legacy_op_conf_tx, + .add_interface = b43legacy_op_add_interface, + .remove_interface = b43legacy_op_remove_interface, + .config = b43legacy_op_dev_config, + .bss_info_changed = b43legacy_op_bss_info_changed, + .configure_filter = b43legacy_op_configure_filter, + .get_stats = b43legacy_op_get_stats, + .start = b43legacy_op_start, + .stop = b43legacy_op_stop, + .set_tim = b43legacy_op_beacon_set_tim, + .get_survey = b43legacy_op_get_survey, + .rfkill_poll = b43legacy_rfkill_poll, +}; + +/* Hard-reset the chip. Do not call this directly. + * Use b43legacy_controller_restart() + */ +static void b43legacy_chip_reset(struct work_struct *work) +{ + struct b43legacy_wldev *dev = + container_of(work, struct b43legacy_wldev, restart_work); + struct b43legacy_wl *wl = dev->wl; + int err = 0; + int prev_status; + + mutex_lock(&wl->mutex); + + prev_status = b43legacy_status(dev); + /* Bring the device down... */ + if (prev_status >= B43legacy_STAT_STARTED) + b43legacy_wireless_core_stop(dev); + if (prev_status >= B43legacy_STAT_INITIALIZED) + b43legacy_wireless_core_exit(dev); + + /* ...and up again. */ + if (prev_status >= B43legacy_STAT_INITIALIZED) { + err = b43legacy_wireless_core_init(dev); + if (err) + goto out; + } + if (prev_status >= B43legacy_STAT_STARTED) { + err = b43legacy_wireless_core_start(dev); + if (err) { + b43legacy_wireless_core_exit(dev); + goto out; + } + } +out: + if (err) + wl->current_dev = NULL; /* Failed to init the dev. */ + mutex_unlock(&wl->mutex); + if (err) + b43legacyerr(wl, "Controller restart FAILED\n"); + else + b43legacyinfo(wl, "Controller restarted\n"); +} + +static int b43legacy_setup_modes(struct b43legacy_wldev *dev, + int have_bphy, + int have_gphy) +{ + struct ieee80211_hw *hw = dev->wl->hw; + struct b43legacy_phy *phy = &dev->phy; + + phy->possible_phymodes = 0; + if (have_bphy) { + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = + &b43legacy_band_2GHz_BPHY; + phy->possible_phymodes |= B43legacy_PHYMODE_B; + } + + if (have_gphy) { + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = + &b43legacy_band_2GHz_GPHY; + phy->possible_phymodes |= B43legacy_PHYMODE_G; + } + + return 0; +} + +static void b43legacy_wireless_core_detach(struct b43legacy_wldev *dev) +{ + /* We release firmware that late to not be required to re-request + * is all the time when we reinit the core. */ + b43legacy_release_firmware(dev); +} + +static int b43legacy_wireless_core_attach(struct b43legacy_wldev *dev) +{ + struct b43legacy_wl *wl = dev->wl; + struct ssb_bus *bus = dev->dev->bus; + struct pci_dev *pdev = (bus->bustype == SSB_BUSTYPE_PCI) ? bus->host_pci : NULL; + int err; + int have_bphy = 0; + int have_gphy = 0; + u32 tmp; + + /* Do NOT do any device initialization here. + * Do it in wireless_core_init() instead. + * This function is for gathering basic information about the HW, only. + * Also some structs may be set up here. But most likely you want to + * have that in core_init(), too. + */ + + err = ssb_bus_powerup(bus, 0); + if (err) { + b43legacyerr(wl, "Bus powerup failed\n"); + goto out; + } + /* Get the PHY type. */ + if (dev->dev->id.revision >= 5) { + u32 tmshigh; + + tmshigh = ssb_read32(dev->dev, SSB_TMSHIGH); + have_gphy = !!(tmshigh & B43legacy_TMSHIGH_GPHY); + if (!have_gphy) + have_bphy = 1; + } else if (dev->dev->id.revision == 4) + have_gphy = 1; + else + have_bphy = 1; + + dev->phy.gmode = (have_gphy || have_bphy); + dev->phy.radio_on = true; + tmp = dev->phy.gmode ? B43legacy_TMSLOW_GMODE : 0; + b43legacy_wireless_core_reset(dev, tmp); + + err = b43legacy_phy_versioning(dev); + if (err) + goto err_powerdown; + /* Check if this device supports multiband. */ + if (!pdev || + (pdev->device != 0x4312 && + pdev->device != 0x4319 && + pdev->device != 0x4324)) { + /* No multiband support. */ + have_bphy = 0; + have_gphy = 0; + switch (dev->phy.type) { + case B43legacy_PHYTYPE_B: + have_bphy = 1; + break; + case B43legacy_PHYTYPE_G: + have_gphy = 1; + break; + default: + B43legacy_BUG_ON(1); + } + } + dev->phy.gmode = (have_gphy || have_bphy); + tmp = dev->phy.gmode ? B43legacy_TMSLOW_GMODE : 0; + b43legacy_wireless_core_reset(dev, tmp); + + err = b43legacy_validate_chipaccess(dev); + if (err) + goto err_powerdown; + err = b43legacy_setup_modes(dev, have_bphy, have_gphy); + if (err) + goto err_powerdown; + + /* Now set some default "current_dev" */ + if (!wl->current_dev) + wl->current_dev = dev; + INIT_WORK(&dev->restart_work, b43legacy_chip_reset); + + b43legacy_radio_turn_off(dev, 1); + b43legacy_switch_analog(dev, 0); + ssb_device_disable(dev->dev, 0); + ssb_bus_may_powerdown(bus); + +out: + return err; + +err_powerdown: + ssb_bus_may_powerdown(bus); + return err; +} + +static void b43legacy_one_core_detach(struct ssb_device *dev) +{ + struct b43legacy_wldev *wldev; + struct b43legacy_wl *wl; + + /* Do not cancel ieee80211-workqueue based work here. + * See comment in b43legacy_remove(). */ + + wldev = ssb_get_drvdata(dev); + wl = wldev->wl; + b43legacy_debugfs_remove_device(wldev); + b43legacy_wireless_core_detach(wldev); + list_del(&wldev->list); + wl->nr_devs--; + ssb_set_drvdata(dev, NULL); + kfree(wldev); +} + +static int b43legacy_one_core_attach(struct ssb_device *dev, + struct b43legacy_wl *wl) +{ + struct b43legacy_wldev *wldev; + int err = -ENOMEM; + + wldev = kzalloc(sizeof(*wldev), GFP_KERNEL); + if (!wldev) + goto out; + + wldev->dev = dev; + wldev->wl = wl; + b43legacy_set_status(wldev, B43legacy_STAT_UNINIT); + wldev->bad_frames_preempt = modparam_bad_frames_preempt; + tasklet_init(&wldev->isr_tasklet, + (void (*)(unsigned long))b43legacy_interrupt_tasklet, + (unsigned long)wldev); + if (modparam_pio) + wldev->__using_pio = true; + INIT_LIST_HEAD(&wldev->list); + + err = b43legacy_wireless_core_attach(wldev); + if (err) + goto err_kfree_wldev; + + list_add(&wldev->list, &wl->devlist); + wl->nr_devs++; + ssb_set_drvdata(dev, wldev); + b43legacy_debugfs_add_device(wldev); +out: + return err; + +err_kfree_wldev: + kfree(wldev); + return err; +} + +static void b43legacy_sprom_fixup(struct ssb_bus *bus) +{ + /* boardflags workarounds */ + if (bus->boardinfo.vendor == PCI_VENDOR_ID_APPLE && + bus->boardinfo.type == 0x4E && + bus->sprom.board_rev > 0x40) + bus->sprom.boardflags_lo |= B43legacy_BFL_PACTRL; +} + +static void b43legacy_wireless_exit(struct ssb_device *dev, + struct b43legacy_wl *wl) +{ + struct ieee80211_hw *hw = wl->hw; + + ssb_set_devtypedata(dev, NULL); + ieee80211_free_hw(hw); +} + +static int b43legacy_wireless_init(struct ssb_device *dev) +{ + struct ssb_sprom *sprom = &dev->bus->sprom; + struct ieee80211_hw *hw; + struct b43legacy_wl *wl; + int err = -ENOMEM; + int queue_num; + + b43legacy_sprom_fixup(dev->bus); + + hw = ieee80211_alloc_hw(sizeof(*wl), &b43legacy_hw_ops); + if (!hw) { + b43legacyerr(NULL, "Could not allocate ieee80211 device\n"); + goto out; + } + + /* fill hw info */ + hw->flags = IEEE80211_HW_RX_INCLUDES_FCS | + IEEE80211_HW_SIGNAL_DBM; + hw->wiphy->interface_modes = + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_WDS) | + BIT(NL80211_IFTYPE_ADHOC); + hw->queues = 1; /* FIXME: hardware has more queues */ + hw->max_rates = 2; + SET_IEEE80211_DEV(hw, dev->dev); + if (is_valid_ether_addr(sprom->et1mac)) + SET_IEEE80211_PERM_ADDR(hw, sprom->et1mac); + else + SET_IEEE80211_PERM_ADDR(hw, sprom->il0mac); + + /* Get and initialize struct b43legacy_wl */ + wl = hw_to_b43legacy_wl(hw); + memset(wl, 0, sizeof(*wl)); + wl->hw = hw; + spin_lock_init(&wl->irq_lock); + spin_lock_init(&wl->leds_lock); + mutex_init(&wl->mutex); + INIT_LIST_HEAD(&wl->devlist); + INIT_WORK(&wl->beacon_update_trigger, b43legacy_beacon_update_trigger_work); + INIT_WORK(&wl->tx_work, b43legacy_tx_work); + + /* Initialize queues and flags. */ + for (queue_num = 0; queue_num < B43legacy_QOS_QUEUE_NUM; queue_num++) { + skb_queue_head_init(&wl->tx_queue[queue_num]); + wl->tx_queue_stopped[queue_num] = 0; + } + + ssb_set_devtypedata(dev, wl); + b43legacyinfo(wl, "Broadcom %04X WLAN found (core revision %u)\n", + dev->bus->chip_id, dev->id.revision); + err = 0; +out: + return err; +} + +static int b43legacy_probe(struct ssb_device *dev, + const struct ssb_device_id *id) +{ + struct b43legacy_wl *wl; + int err; + int first = 0; + + wl = ssb_get_devtypedata(dev); + if (!wl) { + /* Probing the first core - setup common struct b43legacy_wl */ + first = 1; + err = b43legacy_wireless_init(dev); + if (err) + goto out; + wl = ssb_get_devtypedata(dev); + B43legacy_WARN_ON(!wl); + } + err = b43legacy_one_core_attach(dev, wl); + if (err) + goto err_wireless_exit; + + /* setup and start work to load firmware */ + INIT_WORK(&wl->firmware_load, b43legacy_request_firmware); + schedule_work(&wl->firmware_load); + +out: + return err; + +err_wireless_exit: + if (first) + b43legacy_wireless_exit(dev, wl); + return err; +} + +static void b43legacy_remove(struct ssb_device *dev) +{ + struct b43legacy_wl *wl = ssb_get_devtypedata(dev); + struct b43legacy_wldev *wldev = ssb_get_drvdata(dev); + + /* We must cancel any work here before unregistering from ieee80211, + * as the ieee80211 unreg will destroy the workqueue. */ + cancel_work_sync(&wldev->restart_work); + cancel_work_sync(&wl->firmware_load); + + B43legacy_WARN_ON(!wl); + if (!wldev->fw.ucode) + return; /* NULL if fw never loaded */ + if (wl->current_dev == wldev) + ieee80211_unregister_hw(wl->hw); + + b43legacy_one_core_detach(dev); + + if (list_empty(&wl->devlist)) + /* Last core on the chip unregistered. + * We can destroy common struct b43legacy_wl. + */ + b43legacy_wireless_exit(dev, wl); +} + +/* Perform a hardware reset. This can be called from any context. */ +void b43legacy_controller_restart(struct b43legacy_wldev *dev, + const char *reason) +{ + /* Must avoid requeueing, if we are in shutdown. */ + if (b43legacy_status(dev) < B43legacy_STAT_INITIALIZED) + return; + b43legacyinfo(dev->wl, "Controller RESET (%s) ...\n", reason); + ieee80211_queue_work(dev->wl->hw, &dev->restart_work); +} + +#ifdef CONFIG_PM + +static int b43legacy_suspend(struct ssb_device *dev, pm_message_t state) +{ + struct b43legacy_wldev *wldev = ssb_get_drvdata(dev); + struct b43legacy_wl *wl = wldev->wl; + + b43legacydbg(wl, "Suspending...\n"); + + mutex_lock(&wl->mutex); + wldev->suspend_init_status = b43legacy_status(wldev); + if (wldev->suspend_init_status >= B43legacy_STAT_STARTED) + b43legacy_wireless_core_stop(wldev); + if (wldev->suspend_init_status >= B43legacy_STAT_INITIALIZED) + b43legacy_wireless_core_exit(wldev); + mutex_unlock(&wl->mutex); + + b43legacydbg(wl, "Device suspended.\n"); + + return 0; +} + +static int b43legacy_resume(struct ssb_device *dev) +{ + struct b43legacy_wldev *wldev = ssb_get_drvdata(dev); + struct b43legacy_wl *wl = wldev->wl; + int err = 0; + + b43legacydbg(wl, "Resuming...\n"); + + mutex_lock(&wl->mutex); + if (wldev->suspend_init_status >= B43legacy_STAT_INITIALIZED) { + err = b43legacy_wireless_core_init(wldev); + if (err) { + b43legacyerr(wl, "Resume failed at core init\n"); + goto out; + } + } + if (wldev->suspend_init_status >= B43legacy_STAT_STARTED) { + err = b43legacy_wireless_core_start(wldev); + if (err) { + b43legacy_wireless_core_exit(wldev); + b43legacyerr(wl, "Resume failed at core start\n"); + goto out; + } + } + + b43legacydbg(wl, "Device resumed.\n"); +out: + mutex_unlock(&wl->mutex); + return err; +} + +#else /* CONFIG_PM */ +# define b43legacy_suspend NULL +# define b43legacy_resume NULL +#endif /* CONFIG_PM */ + +static struct ssb_driver b43legacy_ssb_driver = { + .name = KBUILD_MODNAME, + .id_table = b43legacy_ssb_tbl, + .probe = b43legacy_probe, + .remove = b43legacy_remove, + .suspend = b43legacy_suspend, + .resume = b43legacy_resume, +}; + +static void b43legacy_print_driverinfo(void) +{ + const char *feat_pci = "", *feat_leds = "", + *feat_pio = "", *feat_dma = ""; + +#ifdef CPTCFG_B43LEGACY_PCI_AUTOSELECT + feat_pci = "P"; +#endif +#ifdef CPTCFG_B43LEGACY_LEDS + feat_leds = "L"; +#endif +#ifdef CPTCFG_B43LEGACY_PIO + feat_pio = "I"; +#endif +#ifdef CPTCFG_B43LEGACY_DMA + feat_dma = "D"; +#endif + printk(KERN_INFO "Broadcom 43xx-legacy driver loaded " + "[ Features: %s%s%s%s ]\n", + feat_pci, feat_leds, feat_pio, feat_dma); +} + +static int __init b43legacy_init(void) +{ + int err; + + b43legacy_debugfs_init(); + + err = ssb_driver_register(&b43legacy_ssb_driver); + if (err) + goto err_dfs_exit; + + b43legacy_print_driverinfo(); + + return err; + +err_dfs_exit: + b43legacy_debugfs_exit(); + return err; +} + +static void __exit b43legacy_exit(void) +{ + ssb_driver_unregister(&b43legacy_ssb_driver); + b43legacy_debugfs_exit(); +} + +module_init(b43legacy_init) +module_exit(b43legacy_exit) diff --git a/drivers/net/wireless/b43legacy/main.h b/drivers/net/wireless/b43legacy/main.h new file mode 100644 index 0000000..b74a058 --- /dev/null +++ b/drivers/net/wireless/b43legacy/main.h @@ -0,0 +1,127 @@ +/* + + Broadcom B43legacy wireless driver + + Copyright (c) 2005 Martin Langer , + Copyright (c) 2005 Stefano Brivio + Copyright (c) 2005, 2006 Michael Buesch + Copyright (c) 2005 Danny van Dyk + Copyright (c) 2005 Andreas Jaggi + Copyright (c) 2007 Larry Finger + + Some parts of the code in this file are derived from the ipw2200 + driver Copyright(c) 2003 - 2004 Intel Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#ifndef B43legacy_MAIN_H_ +#define B43legacy_MAIN_H_ + +#include "b43legacy.h" + + +#define P4D_BYT3S(magic, nr_bytes) u8 __p4dding##magic[nr_bytes] +#define P4D_BYTES(line, nr_bytes) P4D_BYT3S(line, nr_bytes) +/* Magic helper macro to pad structures. Ignore those above. It's magic. */ +#define PAD_BYTES(nr_bytes) P4D_BYTES(__LINE__ , (nr_bytes)) + + +/* Lightweight function to convert a frequency (in Mhz) to a channel number. */ +static inline +u8 b43legacy_freq_to_channel_bg(int freq) +{ + u8 channel; + + if (freq == 2484) + channel = 14; + else + channel = (freq - 2407) / 5; + + return channel; +} +static inline +u8 b43legacy_freq_to_channel(struct b43legacy_wldev *dev, + int freq) +{ + return b43legacy_freq_to_channel_bg(freq); +} + +/* Lightweight function to convert a channel number to a frequency (in Mhz). */ +static inline +int b43legacy_channel_to_freq_bg(u8 channel) +{ + int freq; + + if (channel == 14) + freq = 2484; + else + freq = 2407 + (5 * channel); + + return freq; +} + +static inline +int b43legacy_channel_to_freq(struct b43legacy_wldev *dev, + u8 channel) +{ + return b43legacy_channel_to_freq_bg(channel); +} + +static inline +int b43legacy_is_cck_rate(int rate) +{ + return (rate == B43legacy_CCK_RATE_1MB || + rate == B43legacy_CCK_RATE_2MB || + rate == B43legacy_CCK_RATE_5MB || + rate == B43legacy_CCK_RATE_11MB); +} + +static inline +int b43legacy_is_ofdm_rate(int rate) +{ + return !b43legacy_is_cck_rate(rate); +} + +void b43legacy_tsf_read(struct b43legacy_wldev *dev, u64 *tsf); +void b43legacy_tsf_write(struct b43legacy_wldev *dev, u64 tsf); + +u32 b43legacy_shm_read32(struct b43legacy_wldev *dev, + u16 routing, u16 offset); +u16 b43legacy_shm_read16(struct b43legacy_wldev *dev, + u16 routing, u16 offset); +void b43legacy_shm_write32(struct b43legacy_wldev *dev, + u16 routing, u16 offset, + u32 value); +void b43legacy_shm_write16(struct b43legacy_wldev *dev, + u16 routing, u16 offset, + u16 value); + +u32 b43legacy_hf_read(struct b43legacy_wldev *dev); +void b43legacy_hf_write(struct b43legacy_wldev *dev, u32 value); + +void b43legacy_dummy_transmission(struct b43legacy_wldev *dev); + +void b43legacy_wireless_core_reset(struct b43legacy_wldev *dev, u32 flags); + +void b43legacy_mac_suspend(struct b43legacy_wldev *dev); +void b43legacy_mac_enable(struct b43legacy_wldev *dev); + +void b43legacy_controller_restart(struct b43legacy_wldev *dev, + const char *reason); + +#endif /* B43legacy_MAIN_H_ */ diff --git a/drivers/net/wireless/b43legacy/phy.c b/drivers/net/wireless/b43legacy/phy.c new file mode 100644 index 0000000..7aeb6ee --- /dev/null +++ b/drivers/net/wireless/b43legacy/phy.c @@ -0,0 +1,2258 @@ +/* + + Broadcom B43legacy wireless driver + + Copyright (c) 2005 Martin Langer , + Stefano Brivio + Michael Buesch + Danny van Dyk + Andreas Jaggi + Copyright (c) 2007 Larry Finger + + Some parts of the code in this file are derived from the ipw2200 + driver Copyright(c) 2003 - 2004 Intel Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include +#include +#include +#include +#include + +#include "b43legacy.h" +#include "phy.h" +#include "main.h" +#include "radio.h" +#include "ilt.h" + + +static const s8 b43legacy_tssi2dbm_b_table[] = { + 0x4D, 0x4C, 0x4B, 0x4A, + 0x4A, 0x49, 0x48, 0x47, + 0x47, 0x46, 0x45, 0x45, + 0x44, 0x43, 0x42, 0x42, + 0x41, 0x40, 0x3F, 0x3E, + 0x3D, 0x3C, 0x3B, 0x3A, + 0x39, 0x38, 0x37, 0x36, + 0x35, 0x34, 0x32, 0x31, + 0x30, 0x2F, 0x2D, 0x2C, + 0x2B, 0x29, 0x28, 0x26, + 0x25, 0x23, 0x21, 0x1F, + 0x1D, 0x1A, 0x17, 0x14, + 0x10, 0x0C, 0x06, 0x00, + -7, -7, -7, -7, + -7, -7, -7, -7, + -7, -7, -7, -7, +}; + +static const s8 b43legacy_tssi2dbm_g_table[] = { + 77, 77, 77, 76, + 76, 76, 75, 75, + 74, 74, 73, 73, + 73, 72, 72, 71, + 71, 70, 70, 69, + 68, 68, 67, 67, + 66, 65, 65, 64, + 63, 63, 62, 61, + 60, 59, 58, 57, + 56, 55, 54, 53, + 52, 50, 49, 47, + 45, 43, 40, 37, + 33, 28, 22, 14, + 5, -7, -20, -20, + -20, -20, -20, -20, + -20, -20, -20, -20, +}; + +static void b43legacy_phy_initg(struct b43legacy_wldev *dev); + + +static inline +void b43legacy_voluntary_preempt(void) +{ + B43legacy_BUG_ON(!(!in_atomic() && !in_irq() && + !in_interrupt() && !irqs_disabled())); +#ifndef CONFIG_PREEMPT + cond_resched(); +#endif /* CONFIG_PREEMPT */ +} + +/* Lock the PHY registers against concurrent access from the microcode. + * This lock is nonrecursive. */ +void b43legacy_phy_lock(struct b43legacy_wldev *dev) +{ +#if B43legacy_DEBUG + B43legacy_WARN_ON(dev->phy.phy_locked); + dev->phy.phy_locked = 1; +#endif + + if (dev->dev->id.revision < 3) { + b43legacy_mac_suspend(dev); + } else { + if (!b43legacy_is_mode(dev->wl, NL80211_IFTYPE_AP)) + b43legacy_power_saving_ctl_bits(dev, -1, 1); + } +} + +void b43legacy_phy_unlock(struct b43legacy_wldev *dev) +{ +#if B43legacy_DEBUG + B43legacy_WARN_ON(!dev->phy.phy_locked); + dev->phy.phy_locked = 0; +#endif + + if (dev->dev->id.revision < 3) { + b43legacy_mac_enable(dev); + } else { + if (!b43legacy_is_mode(dev->wl, NL80211_IFTYPE_AP)) + b43legacy_power_saving_ctl_bits(dev, -1, -1); + } +} + +u16 b43legacy_phy_read(struct b43legacy_wldev *dev, u16 offset) +{ + b43legacy_write16(dev, B43legacy_MMIO_PHY_CONTROL, offset); + return b43legacy_read16(dev, B43legacy_MMIO_PHY_DATA); +} + +void b43legacy_phy_write(struct b43legacy_wldev *dev, u16 offset, u16 val) +{ + b43legacy_write16(dev, B43legacy_MMIO_PHY_CONTROL, offset); + mmiowb(); + b43legacy_write16(dev, B43legacy_MMIO_PHY_DATA, val); +} + +void b43legacy_phy_calibrate(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + + b43legacy_read32(dev, B43legacy_MMIO_MACCTL); /* Dummy read. */ + if (phy->calibrated) + return; + if (phy->type == B43legacy_PHYTYPE_G && phy->rev == 1) { + b43legacy_wireless_core_reset(dev, 0); + b43legacy_phy_initg(dev); + b43legacy_wireless_core_reset(dev, B43legacy_TMSLOW_GMODE); + } + phy->calibrated = 1; +} + +/* initialize B PHY power control + * as described in http://bcm-specs.sipsolutions.net/InitPowerControl + */ +static void b43legacy_phy_init_pctl(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 saved_batt = 0; + u16 saved_ratt = 0; + u16 saved_txctl1 = 0; + int must_reset_txpower = 0; + + B43legacy_BUG_ON(!(phy->type == B43legacy_PHYTYPE_B || + phy->type == B43legacy_PHYTYPE_G)); + if (is_bcm_board_vendor(dev) && + (dev->dev->bus->boardinfo.type == 0x0416)) + return; + + b43legacy_phy_write(dev, 0x0028, 0x8018); + b43legacy_write16(dev, 0x03E6, b43legacy_read16(dev, 0x03E6) & 0xFFDF); + + if (phy->type == B43legacy_PHYTYPE_G) { + if (!phy->gmode) + return; + b43legacy_phy_write(dev, 0x047A, 0xC111); + } + if (phy->savedpctlreg != 0xFFFF) + return; +#ifdef CPTCFG_B43LEGACY_DEBUG + if (phy->manual_txpower_control) + return; +#endif + + if (phy->type == B43legacy_PHYTYPE_B && + phy->rev >= 2 && + phy->radio_ver == 0x2050) + b43legacy_radio_write16(dev, 0x0076, + b43legacy_radio_read16(dev, 0x0076) + | 0x0084); + else { + saved_batt = phy->bbatt; + saved_ratt = phy->rfatt; + saved_txctl1 = phy->txctl1; + if ((phy->radio_rev >= 6) && (phy->radio_rev <= 8) + && /*FIXME: incomplete specs for 5 < revision < 9 */ 0) + b43legacy_radio_set_txpower_bg(dev, 0xB, 0x1F, 0); + else + b43legacy_radio_set_txpower_bg(dev, 0xB, 9, 0); + must_reset_txpower = 1; + } + b43legacy_dummy_transmission(dev); + + phy->savedpctlreg = b43legacy_phy_read(dev, B43legacy_PHY_G_PCTL); + + if (must_reset_txpower) + b43legacy_radio_set_txpower_bg(dev, saved_batt, saved_ratt, + saved_txctl1); + else + b43legacy_radio_write16(dev, 0x0076, b43legacy_radio_read16(dev, + 0x0076) & 0xFF7B); + b43legacy_radio_clear_tssi(dev); +} + +static void b43legacy_phy_agcsetup(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 offset = 0x0000; + + if (phy->rev == 1) + offset = 0x4C00; + + b43legacy_ilt_write(dev, offset, 0x00FE); + b43legacy_ilt_write(dev, offset + 1, 0x000D); + b43legacy_ilt_write(dev, offset + 2, 0x0013); + b43legacy_ilt_write(dev, offset + 3, 0x0019); + + if (phy->rev == 1) { + b43legacy_ilt_write(dev, 0x1800, 0x2710); + b43legacy_ilt_write(dev, 0x1801, 0x9B83); + b43legacy_ilt_write(dev, 0x1802, 0x9B83); + b43legacy_ilt_write(dev, 0x1803, 0x0F8D); + b43legacy_phy_write(dev, 0x0455, 0x0004); + } + + b43legacy_phy_write(dev, 0x04A5, (b43legacy_phy_read(dev, 0x04A5) + & 0x00FF) | 0x5700); + b43legacy_phy_write(dev, 0x041A, (b43legacy_phy_read(dev, 0x041A) + & 0xFF80) | 0x000F); + b43legacy_phy_write(dev, 0x041A, (b43legacy_phy_read(dev, 0x041A) + & 0xC07F) | 0x2B80); + b43legacy_phy_write(dev, 0x048C, (b43legacy_phy_read(dev, 0x048C) + & 0xF0FF) | 0x0300); + + b43legacy_radio_write16(dev, 0x007A, + b43legacy_radio_read16(dev, 0x007A) + | 0x0008); + + b43legacy_phy_write(dev, 0x04A0, (b43legacy_phy_read(dev, 0x04A0) + & 0xFFF0) | 0x0008); + b43legacy_phy_write(dev, 0x04A1, (b43legacy_phy_read(dev, 0x04A1) + & 0xF0FF) | 0x0600); + b43legacy_phy_write(dev, 0x04A2, (b43legacy_phy_read(dev, 0x04A2) + & 0xF0FF) | 0x0700); + b43legacy_phy_write(dev, 0x04A0, (b43legacy_phy_read(dev, 0x04A0) + & 0xF0FF) | 0x0100); + + if (phy->rev == 1) + b43legacy_phy_write(dev, 0x04A2, + (b43legacy_phy_read(dev, 0x04A2) + & 0xFFF0) | 0x0007); + + b43legacy_phy_write(dev, 0x0488, (b43legacy_phy_read(dev, 0x0488) + & 0xFF00) | 0x001C); + b43legacy_phy_write(dev, 0x0488, (b43legacy_phy_read(dev, 0x0488) + & 0xC0FF) | 0x0200); + b43legacy_phy_write(dev, 0x0496, (b43legacy_phy_read(dev, 0x0496) + & 0xFF00) | 0x001C); + b43legacy_phy_write(dev, 0x0489, (b43legacy_phy_read(dev, 0x0489) + & 0xFF00) | 0x0020); + b43legacy_phy_write(dev, 0x0489, (b43legacy_phy_read(dev, 0x0489) + & 0xC0FF) | 0x0200); + b43legacy_phy_write(dev, 0x0482, (b43legacy_phy_read(dev, 0x0482) + & 0xFF00) | 0x002E); + b43legacy_phy_write(dev, 0x0496, (b43legacy_phy_read(dev, 0x0496) + & 0x00FF) | 0x1A00); + b43legacy_phy_write(dev, 0x0481, (b43legacy_phy_read(dev, 0x0481) + & 0xFF00) | 0x0028); + b43legacy_phy_write(dev, 0x0481, (b43legacy_phy_read(dev, 0x0481) + & 0x00FF) | 0x2C00); + + if (phy->rev == 1) { + b43legacy_phy_write(dev, 0x0430, 0x092B); + b43legacy_phy_write(dev, 0x041B, + (b43legacy_phy_read(dev, 0x041B) + & 0xFFE1) | 0x0002); + } else { + b43legacy_phy_write(dev, 0x041B, + b43legacy_phy_read(dev, 0x041B) & 0xFFE1); + b43legacy_phy_write(dev, 0x041F, 0x287A); + b43legacy_phy_write(dev, 0x0420, + (b43legacy_phy_read(dev, 0x0420) + & 0xFFF0) | 0x0004); + } + + if (phy->rev > 2) { + b43legacy_phy_write(dev, 0x0422, 0x287A); + b43legacy_phy_write(dev, 0x0420, + (b43legacy_phy_read(dev, 0x0420) + & 0x0FFF) | 0x3000); + } + + b43legacy_phy_write(dev, 0x04A8, (b43legacy_phy_read(dev, 0x04A8) + & 0x8080) | 0x7874); + b43legacy_phy_write(dev, 0x048E, 0x1C00); + + if (phy->rev == 1) { + b43legacy_phy_write(dev, 0x04AB, + (b43legacy_phy_read(dev, 0x04AB) + & 0xF0FF) | 0x0600); + b43legacy_phy_write(dev, 0x048B, 0x005E); + b43legacy_phy_write(dev, 0x048C, + (b43legacy_phy_read(dev, 0x048C) & 0xFF00) + | 0x001E); + b43legacy_phy_write(dev, 0x048D, 0x0002); + } + + b43legacy_ilt_write(dev, offset + 0x0800, 0); + b43legacy_ilt_write(dev, offset + 0x0801, 7); + b43legacy_ilt_write(dev, offset + 0x0802, 16); + b43legacy_ilt_write(dev, offset + 0x0803, 28); + + if (phy->rev >= 6) { + b43legacy_phy_write(dev, 0x0426, + (b43legacy_phy_read(dev, 0x0426) & 0xFFFC)); + b43legacy_phy_write(dev, 0x0426, + (b43legacy_phy_read(dev, 0x0426) & 0xEFFF)); + } +} + +static void b43legacy_phy_setupg(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 i; + + B43legacy_BUG_ON(phy->type != B43legacy_PHYTYPE_G); + if (phy->rev == 1) { + b43legacy_phy_write(dev, 0x0406, 0x4F19); + b43legacy_phy_write(dev, B43legacy_PHY_G_CRS, + (b43legacy_phy_read(dev, + B43legacy_PHY_G_CRS) & 0xFC3F) | 0x0340); + b43legacy_phy_write(dev, 0x042C, 0x005A); + b43legacy_phy_write(dev, 0x0427, 0x001A); + + for (i = 0; i < B43legacy_ILT_FINEFREQG_SIZE; i++) + b43legacy_ilt_write(dev, 0x5800 + i, + b43legacy_ilt_finefreqg[i]); + for (i = 0; i < B43legacy_ILT_NOISEG1_SIZE; i++) + b43legacy_ilt_write(dev, 0x1800 + i, + b43legacy_ilt_noiseg1[i]); + for (i = 0; i < B43legacy_ILT_ROTOR_SIZE; i++) + b43legacy_ilt_write32(dev, 0x2000 + i, + b43legacy_ilt_rotor[i]); + } else { + /* nrssi values are signed 6-bit values. Why 0x7654 here? */ + b43legacy_nrssi_hw_write(dev, 0xBA98, (s16)0x7654); + + if (phy->rev == 2) { + b43legacy_phy_write(dev, 0x04C0, 0x1861); + b43legacy_phy_write(dev, 0x04C1, 0x0271); + } else if (phy->rev > 2) { + b43legacy_phy_write(dev, 0x04C0, 0x0098); + b43legacy_phy_write(dev, 0x04C1, 0x0070); + b43legacy_phy_write(dev, 0x04C9, 0x0080); + } + b43legacy_phy_write(dev, 0x042B, b43legacy_phy_read(dev, + 0x042B) | 0x800); + + for (i = 0; i < 64; i++) + b43legacy_ilt_write(dev, 0x4000 + i, i); + for (i = 0; i < B43legacy_ILT_NOISEG2_SIZE; i++) + b43legacy_ilt_write(dev, 0x1800 + i, + b43legacy_ilt_noiseg2[i]); + } + + if (phy->rev <= 2) + for (i = 0; i < B43legacy_ILT_NOISESCALEG_SIZE; i++) + b43legacy_ilt_write(dev, 0x1400 + i, + b43legacy_ilt_noisescaleg1[i]); + else if ((phy->rev >= 7) && (b43legacy_phy_read(dev, 0x0449) & 0x0200)) + for (i = 0; i < B43legacy_ILT_NOISESCALEG_SIZE; i++) + b43legacy_ilt_write(dev, 0x1400 + i, + b43legacy_ilt_noisescaleg3[i]); + else + for (i = 0; i < B43legacy_ILT_NOISESCALEG_SIZE; i++) + b43legacy_ilt_write(dev, 0x1400 + i, + b43legacy_ilt_noisescaleg2[i]); + + if (phy->rev == 2) + for (i = 0; i < B43legacy_ILT_SIGMASQR_SIZE; i++) + b43legacy_ilt_write(dev, 0x5000 + i, + b43legacy_ilt_sigmasqr1[i]); + else if ((phy->rev > 2) && (phy->rev <= 8)) + for (i = 0; i < B43legacy_ILT_SIGMASQR_SIZE; i++) + b43legacy_ilt_write(dev, 0x5000 + i, + b43legacy_ilt_sigmasqr2[i]); + + if (phy->rev == 1) { + for (i = 0; i < B43legacy_ILT_RETARD_SIZE; i++) + b43legacy_ilt_write32(dev, 0x2400 + i, + b43legacy_ilt_retard[i]); + for (i = 4; i < 20; i++) + b43legacy_ilt_write(dev, 0x5400 + i, 0x0020); + b43legacy_phy_agcsetup(dev); + + if (is_bcm_board_vendor(dev) && + (dev->dev->bus->boardinfo.type == 0x0416) && + (dev->dev->bus->sprom.board_rev == 0x0017)) + return; + + b43legacy_ilt_write(dev, 0x5001, 0x0002); + b43legacy_ilt_write(dev, 0x5002, 0x0001); + } else { + for (i = 0; i <= 0x20; i++) + b43legacy_ilt_write(dev, 0x1000 + i, 0x0820); + b43legacy_phy_agcsetup(dev); + b43legacy_phy_read(dev, 0x0400); /* dummy read */ + b43legacy_phy_write(dev, 0x0403, 0x1000); + b43legacy_ilt_write(dev, 0x3C02, 0x000F); + b43legacy_ilt_write(dev, 0x3C03, 0x0014); + + if (is_bcm_board_vendor(dev) && + (dev->dev->bus->boardinfo.type == 0x0416) && + (dev->dev->bus->sprom.board_rev == 0x0017)) + return; + + b43legacy_ilt_write(dev, 0x0401, 0x0002); + b43legacy_ilt_write(dev, 0x0402, 0x0001); + } +} + +/* Initialize the APHY portion of a GPHY. */ +static void b43legacy_phy_inita(struct b43legacy_wldev *dev) +{ + + might_sleep(); + + b43legacy_phy_setupg(dev); + if (dev->dev->bus->sprom.boardflags_lo & B43legacy_BFL_PACTRL) + b43legacy_phy_write(dev, 0x046E, 0x03CF); +} + +static void b43legacy_phy_initb2(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 offset; + int val; + + b43legacy_write16(dev, 0x03EC, 0x3F22); + b43legacy_phy_write(dev, 0x0020, 0x301C); + b43legacy_phy_write(dev, 0x0026, 0x0000); + b43legacy_phy_write(dev, 0x0030, 0x00C6); + b43legacy_phy_write(dev, 0x0088, 0x3E00); + val = 0x3C3D; + for (offset = 0x0089; offset < 0x00A7; offset++) { + b43legacy_phy_write(dev, offset, val); + val -= 0x0202; + } + b43legacy_phy_write(dev, 0x03E4, 0x3000); + b43legacy_radio_selectchannel(dev, phy->channel, 0); + if (phy->radio_ver != 0x2050) { + b43legacy_radio_write16(dev, 0x0075, 0x0080); + b43legacy_radio_write16(dev, 0x0079, 0x0081); + } + b43legacy_radio_write16(dev, 0x0050, 0x0020); + b43legacy_radio_write16(dev, 0x0050, 0x0023); + if (phy->radio_ver == 0x2050) { + b43legacy_radio_write16(dev, 0x0050, 0x0020); + b43legacy_radio_write16(dev, 0x005A, 0x0070); + b43legacy_radio_write16(dev, 0x005B, 0x007B); + b43legacy_radio_write16(dev, 0x005C, 0x00B0); + b43legacy_radio_write16(dev, 0x007A, 0x000F); + b43legacy_phy_write(dev, 0x0038, 0x0677); + b43legacy_radio_init2050(dev); + } + b43legacy_phy_write(dev, 0x0014, 0x0080); + b43legacy_phy_write(dev, 0x0032, 0x00CA); + b43legacy_phy_write(dev, 0x0032, 0x00CC); + b43legacy_phy_write(dev, 0x0035, 0x07C2); + b43legacy_phy_lo_b_measure(dev); + b43legacy_phy_write(dev, 0x0026, 0xCC00); + if (phy->radio_ver != 0x2050) + b43legacy_phy_write(dev, 0x0026, 0xCE00); + b43legacy_write16(dev, B43legacy_MMIO_CHANNEL_EXT, 0x1000); + b43legacy_phy_write(dev, 0x002A, 0x88A3); + if (phy->radio_ver != 0x2050) + b43legacy_phy_write(dev, 0x002A, 0x88C2); + b43legacy_radio_set_txpower_bg(dev, 0xFFFF, 0xFFFF, 0xFFFF); + b43legacy_phy_init_pctl(dev); +} + +static void b43legacy_phy_initb4(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 offset; + u16 val; + + b43legacy_write16(dev, 0x03EC, 0x3F22); + b43legacy_phy_write(dev, 0x0020, 0x301C); + b43legacy_phy_write(dev, 0x0026, 0x0000); + b43legacy_phy_write(dev, 0x0030, 0x00C6); + b43legacy_phy_write(dev, 0x0088, 0x3E00); + val = 0x3C3D; + for (offset = 0x0089; offset < 0x00A7; offset++) { + b43legacy_phy_write(dev, offset, val); + val -= 0x0202; + } + b43legacy_phy_write(dev, 0x03E4, 0x3000); + b43legacy_radio_selectchannel(dev, phy->channel, 0); + if (phy->radio_ver != 0x2050) { + b43legacy_radio_write16(dev, 0x0075, 0x0080); + b43legacy_radio_write16(dev, 0x0079, 0x0081); + } + b43legacy_radio_write16(dev, 0x0050, 0x0020); + b43legacy_radio_write16(dev, 0x0050, 0x0023); + if (phy->radio_ver == 0x2050) { + b43legacy_radio_write16(dev, 0x0050, 0x0020); + b43legacy_radio_write16(dev, 0x005A, 0x0070); + b43legacy_radio_write16(dev, 0x005B, 0x007B); + b43legacy_radio_write16(dev, 0x005C, 0x00B0); + b43legacy_radio_write16(dev, 0x007A, 0x000F); + b43legacy_phy_write(dev, 0x0038, 0x0677); + b43legacy_radio_init2050(dev); + } + b43legacy_phy_write(dev, 0x0014, 0x0080); + b43legacy_phy_write(dev, 0x0032, 0x00CA); + if (phy->radio_ver == 0x2050) + b43legacy_phy_write(dev, 0x0032, 0x00E0); + b43legacy_phy_write(dev, 0x0035, 0x07C2); + + b43legacy_phy_lo_b_measure(dev); + + b43legacy_phy_write(dev, 0x0026, 0xCC00); + if (phy->radio_ver == 0x2050) + b43legacy_phy_write(dev, 0x0026, 0xCE00); + b43legacy_write16(dev, B43legacy_MMIO_CHANNEL_EXT, 0x1100); + b43legacy_phy_write(dev, 0x002A, 0x88A3); + if (phy->radio_ver == 0x2050) + b43legacy_phy_write(dev, 0x002A, 0x88C2); + b43legacy_radio_set_txpower_bg(dev, 0xFFFF, 0xFFFF, 0xFFFF); + if (dev->dev->bus->sprom.boardflags_lo & B43legacy_BFL_RSSI) { + b43legacy_calc_nrssi_slope(dev); + b43legacy_calc_nrssi_threshold(dev); + } + b43legacy_phy_init_pctl(dev); +} + +static void b43legacy_phy_initb5(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 offset; + u16 value; + u8 old_channel; + + if (phy->analog == 1) + b43legacy_radio_write16(dev, 0x007A, + b43legacy_radio_read16(dev, 0x007A) + | 0x0050); + if (!is_bcm_board_vendor(dev) && + (dev->dev->bus->boardinfo.type != 0x0416)) { + value = 0x2120; + for (offset = 0x00A8 ; offset < 0x00C7; offset++) { + b43legacy_phy_write(dev, offset, value); + value += 0x0202; + } + } + b43legacy_phy_write(dev, 0x0035, + (b43legacy_phy_read(dev, 0x0035) & 0xF0FF) + | 0x0700); + if (phy->radio_ver == 0x2050) + b43legacy_phy_write(dev, 0x0038, 0x0667); + + if (phy->gmode) { + if (phy->radio_ver == 0x2050) { + b43legacy_radio_write16(dev, 0x007A, + b43legacy_radio_read16(dev, 0x007A) + | 0x0020); + b43legacy_radio_write16(dev, 0x0051, + b43legacy_radio_read16(dev, 0x0051) + | 0x0004); + } + b43legacy_write16(dev, B43legacy_MMIO_PHY_RADIO, 0x0000); + + b43legacy_phy_write(dev, 0x0802, b43legacy_phy_read(dev, 0x0802) + | 0x0100); + b43legacy_phy_write(dev, 0x042B, b43legacy_phy_read(dev, 0x042B) + | 0x2000); + + b43legacy_phy_write(dev, 0x001C, 0x186A); + + b43legacy_phy_write(dev, 0x0013, (b43legacy_phy_read(dev, + 0x0013) & 0x00FF) | 0x1900); + b43legacy_phy_write(dev, 0x0035, (b43legacy_phy_read(dev, + 0x0035) & 0xFFC0) | 0x0064); + b43legacy_phy_write(dev, 0x005D, (b43legacy_phy_read(dev, + 0x005D) & 0xFF80) | 0x000A); + b43legacy_phy_write(dev, 0x5B, 0x0000); + b43legacy_phy_write(dev, 0x5C, 0x0000); + } + + if (dev->bad_frames_preempt) + b43legacy_phy_write(dev, B43legacy_PHY_RADIO_BITFIELD, + b43legacy_phy_read(dev, + B43legacy_PHY_RADIO_BITFIELD) | (1 << 12)); + + if (phy->analog == 1) { + b43legacy_phy_write(dev, 0x0026, 0xCE00); + b43legacy_phy_write(dev, 0x0021, 0x3763); + b43legacy_phy_write(dev, 0x0022, 0x1BC3); + b43legacy_phy_write(dev, 0x0023, 0x06F9); + b43legacy_phy_write(dev, 0x0024, 0x037E); + } else + b43legacy_phy_write(dev, 0x0026, 0xCC00); + b43legacy_phy_write(dev, 0x0030, 0x00C6); + b43legacy_write16(dev, 0x03EC, 0x3F22); + + if (phy->analog == 1) + b43legacy_phy_write(dev, 0x0020, 0x3E1C); + else + b43legacy_phy_write(dev, 0x0020, 0x301C); + + if (phy->analog == 0) + b43legacy_write16(dev, 0x03E4, 0x3000); + + old_channel = (phy->channel == 0xFF) ? 1 : phy->channel; + /* Force to channel 7, even if not supported. */ + b43legacy_radio_selectchannel(dev, 7, 0); + + if (phy->radio_ver != 0x2050) { + b43legacy_radio_write16(dev, 0x0075, 0x0080); + b43legacy_radio_write16(dev, 0x0079, 0x0081); + } + + b43legacy_radio_write16(dev, 0x0050, 0x0020); + b43legacy_radio_write16(dev, 0x0050, 0x0023); + + if (phy->radio_ver == 0x2050) { + b43legacy_radio_write16(dev, 0x0050, 0x0020); + b43legacy_radio_write16(dev, 0x005A, 0x0070); + } + + b43legacy_radio_write16(dev, 0x005B, 0x007B); + b43legacy_radio_write16(dev, 0x005C, 0x00B0); + + b43legacy_radio_write16(dev, 0x007A, b43legacy_radio_read16(dev, + 0x007A) | 0x0007); + + b43legacy_radio_selectchannel(dev, old_channel, 0); + + b43legacy_phy_write(dev, 0x0014, 0x0080); + b43legacy_phy_write(dev, 0x0032, 0x00CA); + b43legacy_phy_write(dev, 0x002A, 0x88A3); + + b43legacy_radio_set_txpower_bg(dev, 0xFFFF, 0xFFFF, 0xFFFF); + + if (phy->radio_ver == 0x2050) + b43legacy_radio_write16(dev, 0x005D, 0x000D); + + b43legacy_write16(dev, 0x03E4, (b43legacy_read16(dev, 0x03E4) & + 0xFFC0) | 0x0004); +} + +static void b43legacy_phy_initb6(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 offset; + u16 val; + u8 old_channel; + + b43legacy_phy_write(dev, 0x003E, 0x817A); + b43legacy_radio_write16(dev, 0x007A, + (b43legacy_radio_read16(dev, 0x007A) | 0x0058)); + if (phy->radio_rev == 4 || + phy->radio_rev == 5) { + b43legacy_radio_write16(dev, 0x0051, 0x0037); + b43legacy_radio_write16(dev, 0x0052, 0x0070); + b43legacy_radio_write16(dev, 0x0053, 0x00B3); + b43legacy_radio_write16(dev, 0x0054, 0x009B); + b43legacy_radio_write16(dev, 0x005A, 0x0088); + b43legacy_radio_write16(dev, 0x005B, 0x0088); + b43legacy_radio_write16(dev, 0x005D, 0x0088); + b43legacy_radio_write16(dev, 0x005E, 0x0088); + b43legacy_radio_write16(dev, 0x007D, 0x0088); + b43legacy_shm_write32(dev, B43legacy_SHM_SHARED, + B43legacy_UCODEFLAGS_OFFSET, + (b43legacy_shm_read32(dev, + B43legacy_SHM_SHARED, + B43legacy_UCODEFLAGS_OFFSET) + | 0x00000200)); + } + if (phy->radio_rev == 8) { + b43legacy_radio_write16(dev, 0x0051, 0x0000); + b43legacy_radio_write16(dev, 0x0052, 0x0040); + b43legacy_radio_write16(dev, 0x0053, 0x00B7); + b43legacy_radio_write16(dev, 0x0054, 0x0098); + b43legacy_radio_write16(dev, 0x005A, 0x0088); + b43legacy_radio_write16(dev, 0x005B, 0x006B); + b43legacy_radio_write16(dev, 0x005C, 0x000F); + if (dev->dev->bus->sprom.boardflags_lo & 0x8000) { + b43legacy_radio_write16(dev, 0x005D, 0x00FA); + b43legacy_radio_write16(dev, 0x005E, 0x00D8); + } else { + b43legacy_radio_write16(dev, 0x005D, 0x00F5); + b43legacy_radio_write16(dev, 0x005E, 0x00B8); + } + b43legacy_radio_write16(dev, 0x0073, 0x0003); + b43legacy_radio_write16(dev, 0x007D, 0x00A8); + b43legacy_radio_write16(dev, 0x007C, 0x0001); + b43legacy_radio_write16(dev, 0x007E, 0x0008); + } + val = 0x1E1F; + for (offset = 0x0088; offset < 0x0098; offset++) { + b43legacy_phy_write(dev, offset, val); + val -= 0x0202; + } + val = 0x3E3F; + for (offset = 0x0098; offset < 0x00A8; offset++) { + b43legacy_phy_write(dev, offset, val); + val -= 0x0202; + } + val = 0x2120; + for (offset = 0x00A8; offset < 0x00C8; offset++) { + b43legacy_phy_write(dev, offset, (val & 0x3F3F)); + val += 0x0202; + } + if (phy->type == B43legacy_PHYTYPE_G) { + b43legacy_radio_write16(dev, 0x007A, + b43legacy_radio_read16(dev, 0x007A) | + 0x0020); + b43legacy_radio_write16(dev, 0x0051, + b43legacy_radio_read16(dev, 0x0051) | + 0x0004); + b43legacy_phy_write(dev, 0x0802, + b43legacy_phy_read(dev, 0x0802) | 0x0100); + b43legacy_phy_write(dev, 0x042B, + b43legacy_phy_read(dev, 0x042B) | 0x2000); + b43legacy_phy_write(dev, 0x5B, 0x0000); + b43legacy_phy_write(dev, 0x5C, 0x0000); + } + + old_channel = phy->channel; + if (old_channel >= 8) + b43legacy_radio_selectchannel(dev, 1, 0); + else + b43legacy_radio_selectchannel(dev, 13, 0); + + b43legacy_radio_write16(dev, 0x0050, 0x0020); + b43legacy_radio_write16(dev, 0x0050, 0x0023); + udelay(40); + if (phy->radio_rev < 6 || phy->radio_rev == 8) { + b43legacy_radio_write16(dev, 0x007C, + (b43legacy_radio_read16(dev, 0x007C) + | 0x0002)); + b43legacy_radio_write16(dev, 0x0050, 0x0020); + } + if (phy->radio_rev <= 2) { + b43legacy_radio_write16(dev, 0x0050, 0x0020); + b43legacy_radio_write16(dev, 0x005A, 0x0070); + b43legacy_radio_write16(dev, 0x005B, 0x007B); + b43legacy_radio_write16(dev, 0x005C, 0x00B0); + } + b43legacy_radio_write16(dev, 0x007A, + (b43legacy_radio_read16(dev, + 0x007A) & 0x00F8) | 0x0007); + + b43legacy_radio_selectchannel(dev, old_channel, 0); + + b43legacy_phy_write(dev, 0x0014, 0x0200); + if (phy->radio_rev >= 6) + b43legacy_phy_write(dev, 0x002A, 0x88C2); + else + b43legacy_phy_write(dev, 0x002A, 0x8AC0); + b43legacy_phy_write(dev, 0x0038, 0x0668); + b43legacy_radio_set_txpower_bg(dev, 0xFFFF, 0xFFFF, 0xFFFF); + if (phy->radio_rev == 4 || phy->radio_rev == 5) + b43legacy_phy_write(dev, 0x005D, (b43legacy_phy_read(dev, + 0x005D) & 0xFF80) | 0x0003); + if (phy->radio_rev <= 2) + b43legacy_radio_write16(dev, 0x005D, 0x000D); + + if (phy->analog == 4) { + b43legacy_write16(dev, 0x03E4, 0x0009); + b43legacy_phy_write(dev, 0x61, b43legacy_phy_read(dev, 0x61) + & 0xFFF); + } else + b43legacy_phy_write(dev, 0x0002, (b43legacy_phy_read(dev, + 0x0002) & 0xFFC0) | 0x0004); + if (phy->type == B43legacy_PHYTYPE_G) + b43legacy_write16(dev, 0x03E6, 0x0); + if (phy->type == B43legacy_PHYTYPE_B) { + b43legacy_write16(dev, 0x03E6, 0x8140); + b43legacy_phy_write(dev, 0x0016, 0x0410); + b43legacy_phy_write(dev, 0x0017, 0x0820); + b43legacy_phy_write(dev, 0x0062, 0x0007); + b43legacy_radio_init2050(dev); + b43legacy_phy_lo_g_measure(dev); + if (dev->dev->bus->sprom.boardflags_lo & + B43legacy_BFL_RSSI) { + b43legacy_calc_nrssi_slope(dev); + b43legacy_calc_nrssi_threshold(dev); + } + b43legacy_phy_init_pctl(dev); + } +} + +static void b43legacy_calc_loopback_gain(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 backup_phy[15] = {0}; + u16 backup_radio[3]; + u16 backup_bband; + u16 i; + u16 loop1_cnt; + u16 loop1_done; + u16 loop1_omitted; + u16 loop2_done; + + backup_phy[0] = b43legacy_phy_read(dev, 0x0429); + backup_phy[1] = b43legacy_phy_read(dev, 0x0001); + backup_phy[2] = b43legacy_phy_read(dev, 0x0811); + backup_phy[3] = b43legacy_phy_read(dev, 0x0812); + if (phy->rev != 1) { + backup_phy[4] = b43legacy_phy_read(dev, 0x0814); + backup_phy[5] = b43legacy_phy_read(dev, 0x0815); + } + backup_phy[6] = b43legacy_phy_read(dev, 0x005A); + backup_phy[7] = b43legacy_phy_read(dev, 0x0059); + backup_phy[8] = b43legacy_phy_read(dev, 0x0058); + backup_phy[9] = b43legacy_phy_read(dev, 0x000A); + backup_phy[10] = b43legacy_phy_read(dev, 0x0003); + backup_phy[11] = b43legacy_phy_read(dev, 0x080F); + backup_phy[12] = b43legacy_phy_read(dev, 0x0810); + backup_phy[13] = b43legacy_phy_read(dev, 0x002B); + backup_phy[14] = b43legacy_phy_read(dev, 0x0015); + b43legacy_phy_read(dev, 0x002D); /* dummy read */ + backup_bband = phy->bbatt; + backup_radio[0] = b43legacy_radio_read16(dev, 0x0052); + backup_radio[1] = b43legacy_radio_read16(dev, 0x0043); + backup_radio[2] = b43legacy_radio_read16(dev, 0x007A); + + b43legacy_phy_write(dev, 0x0429, + b43legacy_phy_read(dev, 0x0429) & 0x3FFF); + b43legacy_phy_write(dev, 0x0001, + b43legacy_phy_read(dev, 0x0001) & 0x8000); + b43legacy_phy_write(dev, 0x0811, + b43legacy_phy_read(dev, 0x0811) | 0x0002); + b43legacy_phy_write(dev, 0x0812, + b43legacy_phy_read(dev, 0x0812) & 0xFFFD); + b43legacy_phy_write(dev, 0x0811, + b43legacy_phy_read(dev, 0x0811) | 0x0001); + b43legacy_phy_write(dev, 0x0812, + b43legacy_phy_read(dev, 0x0812) & 0xFFFE); + if (phy->rev != 1) { + b43legacy_phy_write(dev, 0x0814, + b43legacy_phy_read(dev, 0x0814) | 0x0001); + b43legacy_phy_write(dev, 0x0815, + b43legacy_phy_read(dev, 0x0815) & 0xFFFE); + b43legacy_phy_write(dev, 0x0814, + b43legacy_phy_read(dev, 0x0814) | 0x0002); + b43legacy_phy_write(dev, 0x0815, + b43legacy_phy_read(dev, 0x0815) & 0xFFFD); + } + b43legacy_phy_write(dev, 0x0811, b43legacy_phy_read(dev, 0x0811) | + 0x000C); + b43legacy_phy_write(dev, 0x0812, b43legacy_phy_read(dev, 0x0812) | + 0x000C); + + b43legacy_phy_write(dev, 0x0811, (b43legacy_phy_read(dev, 0x0811) + & 0xFFCF) | 0x0030); + b43legacy_phy_write(dev, 0x0812, (b43legacy_phy_read(dev, 0x0812) + & 0xFFCF) | 0x0010); + + b43legacy_phy_write(dev, 0x005A, 0x0780); + b43legacy_phy_write(dev, 0x0059, 0xC810); + b43legacy_phy_write(dev, 0x0058, 0x000D); + if (phy->analog == 0) + b43legacy_phy_write(dev, 0x0003, 0x0122); + else + b43legacy_phy_write(dev, 0x000A, + b43legacy_phy_read(dev, 0x000A) + | 0x2000); + if (phy->rev != 1) { + b43legacy_phy_write(dev, 0x0814, + b43legacy_phy_read(dev, 0x0814) | 0x0004); + b43legacy_phy_write(dev, 0x0815, + b43legacy_phy_read(dev, 0x0815) & 0xFFFB); + } + b43legacy_phy_write(dev, 0x0003, + (b43legacy_phy_read(dev, 0x0003) + & 0xFF9F) | 0x0040); + if (phy->radio_ver == 0x2050 && phy->radio_rev == 2) { + b43legacy_radio_write16(dev, 0x0052, 0x0000); + b43legacy_radio_write16(dev, 0x0043, + (b43legacy_radio_read16(dev, 0x0043) + & 0xFFF0) | 0x0009); + loop1_cnt = 9; + } else if (phy->radio_rev == 8) { + b43legacy_radio_write16(dev, 0x0043, 0x000F); + loop1_cnt = 15; + } else + loop1_cnt = 0; + + b43legacy_phy_set_baseband_attenuation(dev, 11); + + if (phy->rev >= 3) + b43legacy_phy_write(dev, 0x080F, 0xC020); + else + b43legacy_phy_write(dev, 0x080F, 0x8020); + b43legacy_phy_write(dev, 0x0810, 0x0000); + + b43legacy_phy_write(dev, 0x002B, + (b43legacy_phy_read(dev, 0x002B) + & 0xFFC0) | 0x0001); + b43legacy_phy_write(dev, 0x002B, + (b43legacy_phy_read(dev, 0x002B) + & 0xC0FF) | 0x0800); + b43legacy_phy_write(dev, 0x0811, + b43legacy_phy_read(dev, 0x0811) | 0x0100); + b43legacy_phy_write(dev, 0x0812, + b43legacy_phy_read(dev, 0x0812) & 0xCFFF); + if (dev->dev->bus->sprom.boardflags_lo & B43legacy_BFL_EXTLNA) { + if (phy->rev >= 7) { + b43legacy_phy_write(dev, 0x0811, + b43legacy_phy_read(dev, 0x0811) + | 0x0800); + b43legacy_phy_write(dev, 0x0812, + b43legacy_phy_read(dev, 0x0812) + | 0x8000); + } + } + b43legacy_radio_write16(dev, 0x007A, + b43legacy_radio_read16(dev, 0x007A) + & 0x00F7); + + for (i = 0; i < loop1_cnt; i++) { + b43legacy_radio_write16(dev, 0x0043, loop1_cnt); + b43legacy_phy_write(dev, 0x0812, + (b43legacy_phy_read(dev, 0x0812) + & 0xF0FF) | (i << 8)); + b43legacy_phy_write(dev, 0x0015, + (b43legacy_phy_read(dev, 0x0015) + & 0x0FFF) | 0xA000); + b43legacy_phy_write(dev, 0x0015, + (b43legacy_phy_read(dev, 0x0015) + & 0x0FFF) | 0xF000); + udelay(20); + if (b43legacy_phy_read(dev, 0x002D) >= 0x0DFC) + break; + } + loop1_done = i; + loop1_omitted = loop1_cnt - loop1_done; + + loop2_done = 0; + if (loop1_done >= 8) { + b43legacy_phy_write(dev, 0x0812, + b43legacy_phy_read(dev, 0x0812) + | 0x0030); + for (i = loop1_done - 8; i < 16; i++) { + b43legacy_phy_write(dev, 0x0812, + (b43legacy_phy_read(dev, 0x0812) + & 0xF0FF) | (i << 8)); + b43legacy_phy_write(dev, 0x0015, + (b43legacy_phy_read(dev, 0x0015) + & 0x0FFF) | 0xA000); + b43legacy_phy_write(dev, 0x0015, + (b43legacy_phy_read(dev, 0x0015) + & 0x0FFF) | 0xF000); + udelay(20); + if (b43legacy_phy_read(dev, 0x002D) >= 0x0DFC) + break; + } + } + + if (phy->rev != 1) { + b43legacy_phy_write(dev, 0x0814, backup_phy[4]); + b43legacy_phy_write(dev, 0x0815, backup_phy[5]); + } + b43legacy_phy_write(dev, 0x005A, backup_phy[6]); + b43legacy_phy_write(dev, 0x0059, backup_phy[7]); + b43legacy_phy_write(dev, 0x0058, backup_phy[8]); + b43legacy_phy_write(dev, 0x000A, backup_phy[9]); + b43legacy_phy_write(dev, 0x0003, backup_phy[10]); + b43legacy_phy_write(dev, 0x080F, backup_phy[11]); + b43legacy_phy_write(dev, 0x0810, backup_phy[12]); + b43legacy_phy_write(dev, 0x002B, backup_phy[13]); + b43legacy_phy_write(dev, 0x0015, backup_phy[14]); + + b43legacy_phy_set_baseband_attenuation(dev, backup_bband); + + b43legacy_radio_write16(dev, 0x0052, backup_radio[0]); + b43legacy_radio_write16(dev, 0x0043, backup_radio[1]); + b43legacy_radio_write16(dev, 0x007A, backup_radio[2]); + + b43legacy_phy_write(dev, 0x0811, backup_phy[2] | 0x0003); + udelay(10); + b43legacy_phy_write(dev, 0x0811, backup_phy[2]); + b43legacy_phy_write(dev, 0x0812, backup_phy[3]); + b43legacy_phy_write(dev, 0x0429, backup_phy[0]); + b43legacy_phy_write(dev, 0x0001, backup_phy[1]); + + phy->loopback_gain[0] = ((loop1_done * 6) - (loop1_omitted * 4)) - 11; + phy->loopback_gain[1] = (24 - (3 * loop2_done)) * 2; +} + +static void b43legacy_phy_initg(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 tmp; + + if (phy->rev == 1) + b43legacy_phy_initb5(dev); + else + b43legacy_phy_initb6(dev); + if (phy->rev >= 2 && phy->gmode) + b43legacy_phy_inita(dev); + + if (phy->rev >= 2) { + b43legacy_phy_write(dev, 0x0814, 0x0000); + b43legacy_phy_write(dev, 0x0815, 0x0000); + } + if (phy->rev == 2) { + b43legacy_phy_write(dev, 0x0811, 0x0000); + b43legacy_phy_write(dev, 0x0015, 0x00C0); + } + if (phy->rev > 5) { + b43legacy_phy_write(dev, 0x0811, 0x0400); + b43legacy_phy_write(dev, 0x0015, 0x00C0); + } + if (phy->gmode) { + tmp = b43legacy_phy_read(dev, 0x0400) & 0xFF; + if (tmp == 3) { + b43legacy_phy_write(dev, 0x04C2, 0x1816); + b43legacy_phy_write(dev, 0x04C3, 0x8606); + } + if (tmp == 4 || tmp == 5) { + b43legacy_phy_write(dev, 0x04C2, 0x1816); + b43legacy_phy_write(dev, 0x04C3, 0x8006); + b43legacy_phy_write(dev, 0x04CC, + (b43legacy_phy_read(dev, + 0x04CC) & 0x00FF) | + 0x1F00); + } + if (phy->rev >= 2) + b43legacy_phy_write(dev, 0x047E, 0x0078); + } + if (phy->radio_rev == 8) { + b43legacy_phy_write(dev, 0x0801, b43legacy_phy_read(dev, 0x0801) + | 0x0080); + b43legacy_phy_write(dev, 0x043E, b43legacy_phy_read(dev, 0x043E) + | 0x0004); + } + if (phy->rev >= 2 && phy->gmode) + b43legacy_calc_loopback_gain(dev); + if (phy->radio_rev != 8) { + if (phy->initval == 0xFFFF) + phy->initval = b43legacy_radio_init2050(dev); + else + b43legacy_radio_write16(dev, 0x0078, phy->initval); + } + if (phy->txctl2 == 0xFFFF) + b43legacy_phy_lo_g_measure(dev); + else { + if (phy->radio_ver == 0x2050 && phy->radio_rev == 8) + b43legacy_radio_write16(dev, 0x0052, + (phy->txctl1 << 4) | + phy->txctl2); + else + b43legacy_radio_write16(dev, 0x0052, + (b43legacy_radio_read16(dev, + 0x0052) & 0xFFF0) | + phy->txctl1); + if (phy->rev >= 6) + b43legacy_phy_write(dev, 0x0036, + (b43legacy_phy_read(dev, 0x0036) + & 0x0FFF) | (phy->txctl2 << 12)); + if (dev->dev->bus->sprom.boardflags_lo & + B43legacy_BFL_PACTRL) + b43legacy_phy_write(dev, 0x002E, 0x8075); + else + b43legacy_phy_write(dev, 0x002E, 0x807F); + if (phy->rev < 2) + b43legacy_phy_write(dev, 0x002F, 0x0101); + else + b43legacy_phy_write(dev, 0x002F, 0x0202); + } + if (phy->gmode) { + b43legacy_phy_lo_adjust(dev, 0); + b43legacy_phy_write(dev, 0x080F, 0x8078); + } + + if (!(dev->dev->bus->sprom.boardflags_lo & B43legacy_BFL_RSSI)) { + /* The specs state to update the NRSSI LT with + * the value 0x7FFFFFFF here. I think that is some weird + * compiler optimization in the original driver. + * Essentially, what we do here is resetting all NRSSI LT + * entries to -32 (see the clamp_val() in nrssi_hw_update()) + */ + b43legacy_nrssi_hw_update(dev, 0xFFFF); + b43legacy_calc_nrssi_threshold(dev); + } else if (phy->gmode || phy->rev >= 2) { + if (phy->nrssi[0] == -1000) { + B43legacy_WARN_ON(phy->nrssi[1] != -1000); + b43legacy_calc_nrssi_slope(dev); + } else { + B43legacy_WARN_ON(phy->nrssi[1] == -1000); + b43legacy_calc_nrssi_threshold(dev); + } + } + if (phy->radio_rev == 8) + b43legacy_phy_write(dev, 0x0805, 0x3230); + b43legacy_phy_init_pctl(dev); + if (dev->dev->bus->chip_id == 0x4306 + && dev->dev->bus->chip_package == 2) { + b43legacy_phy_write(dev, 0x0429, + b43legacy_phy_read(dev, 0x0429) & 0xBFFF); + b43legacy_phy_write(dev, 0x04C3, + b43legacy_phy_read(dev, 0x04C3) & 0x7FFF); + } +} + +static u16 b43legacy_phy_lo_b_r15_loop(struct b43legacy_wldev *dev) +{ + int i; + u16 ret = 0; + unsigned long flags; + + local_irq_save(flags); + for (i = 0; i < 10; i++) { + b43legacy_phy_write(dev, 0x0015, 0xAFA0); + udelay(1); + b43legacy_phy_write(dev, 0x0015, 0xEFA0); + udelay(10); + b43legacy_phy_write(dev, 0x0015, 0xFFA0); + udelay(40); + ret += b43legacy_phy_read(dev, 0x002C); + } + local_irq_restore(flags); + b43legacy_voluntary_preempt(); + + return ret; +} + +void b43legacy_phy_lo_b_measure(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 regstack[12] = { 0 }; + u16 mls; + u16 fval; + int i; + int j; + + regstack[0] = b43legacy_phy_read(dev, 0x0015); + regstack[1] = b43legacy_radio_read16(dev, 0x0052) & 0xFFF0; + + if (phy->radio_ver == 0x2053) { + regstack[2] = b43legacy_phy_read(dev, 0x000A); + regstack[3] = b43legacy_phy_read(dev, 0x002A); + regstack[4] = b43legacy_phy_read(dev, 0x0035); + regstack[5] = b43legacy_phy_read(dev, 0x0003); + regstack[6] = b43legacy_phy_read(dev, 0x0001); + regstack[7] = b43legacy_phy_read(dev, 0x0030); + + regstack[8] = b43legacy_radio_read16(dev, 0x0043); + regstack[9] = b43legacy_radio_read16(dev, 0x007A); + regstack[10] = b43legacy_read16(dev, 0x03EC); + regstack[11] = b43legacy_radio_read16(dev, 0x0052) & 0x00F0; + + b43legacy_phy_write(dev, 0x0030, 0x00FF); + b43legacy_write16(dev, 0x03EC, 0x3F3F); + b43legacy_phy_write(dev, 0x0035, regstack[4] & 0xFF7F); + b43legacy_radio_write16(dev, 0x007A, regstack[9] & 0xFFF0); + } + b43legacy_phy_write(dev, 0x0015, 0xB000); + b43legacy_phy_write(dev, 0x002B, 0x0004); + + if (phy->radio_ver == 0x2053) { + b43legacy_phy_write(dev, 0x002B, 0x0203); + b43legacy_phy_write(dev, 0x002A, 0x08A3); + } + + phy->minlowsig[0] = 0xFFFF; + + for (i = 0; i < 4; i++) { + b43legacy_radio_write16(dev, 0x0052, regstack[1] | i); + b43legacy_phy_lo_b_r15_loop(dev); + } + for (i = 0; i < 10; i++) { + b43legacy_radio_write16(dev, 0x0052, regstack[1] | i); + mls = b43legacy_phy_lo_b_r15_loop(dev) / 10; + if (mls < phy->minlowsig[0]) { + phy->minlowsig[0] = mls; + phy->minlowsigpos[0] = i; + } + } + b43legacy_radio_write16(dev, 0x0052, regstack[1] + | phy->minlowsigpos[0]); + + phy->minlowsig[1] = 0xFFFF; + + for (i = -4; i < 5; i += 2) { + for (j = -4; j < 5; j += 2) { + if (j < 0) + fval = (0x0100 * i) + j + 0x0100; + else + fval = (0x0100 * i) + j; + b43legacy_phy_write(dev, 0x002F, fval); + mls = b43legacy_phy_lo_b_r15_loop(dev) / 10; + if (mls < phy->minlowsig[1]) { + phy->minlowsig[1] = mls; + phy->minlowsigpos[1] = fval; + } + } + } + phy->minlowsigpos[1] += 0x0101; + + b43legacy_phy_write(dev, 0x002F, phy->minlowsigpos[1]); + if (phy->radio_ver == 0x2053) { + b43legacy_phy_write(dev, 0x000A, regstack[2]); + b43legacy_phy_write(dev, 0x002A, regstack[3]); + b43legacy_phy_write(dev, 0x0035, regstack[4]); + b43legacy_phy_write(dev, 0x0003, regstack[5]); + b43legacy_phy_write(dev, 0x0001, regstack[6]); + b43legacy_phy_write(dev, 0x0030, regstack[7]); + + b43legacy_radio_write16(dev, 0x0043, regstack[8]); + b43legacy_radio_write16(dev, 0x007A, regstack[9]); + + b43legacy_radio_write16(dev, 0x0052, + (b43legacy_radio_read16(dev, 0x0052) + & 0x000F) | regstack[11]); + + b43legacy_write16(dev, 0x03EC, regstack[10]); + } + b43legacy_phy_write(dev, 0x0015, regstack[0]); +} + +static inline +u16 b43legacy_phy_lo_g_deviation_subval(struct b43legacy_wldev *dev, + u16 control) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 ret; + unsigned long flags; + + local_irq_save(flags); + if (phy->gmode) { + b43legacy_phy_write(dev, 0x15, 0xE300); + control <<= 8; + b43legacy_phy_write(dev, 0x0812, control | 0x00B0); + udelay(5); + b43legacy_phy_write(dev, 0x0812, control | 0x00B2); + udelay(2); + b43legacy_phy_write(dev, 0x0812, control | 0x00B3); + udelay(4); + b43legacy_phy_write(dev, 0x0015, 0xF300); + udelay(8); + } else { + b43legacy_phy_write(dev, 0x0015, control | 0xEFA0); + udelay(2); + b43legacy_phy_write(dev, 0x0015, control | 0xEFE0); + udelay(4); + b43legacy_phy_write(dev, 0x0015, control | 0xFFE0); + udelay(8); + } + ret = b43legacy_phy_read(dev, 0x002D); + local_irq_restore(flags); + b43legacy_voluntary_preempt(); + + return ret; +} + +static u32 b43legacy_phy_lo_g_singledeviation(struct b43legacy_wldev *dev, + u16 control) +{ + int i; + u32 ret = 0; + + for (i = 0; i < 8; i++) + ret += b43legacy_phy_lo_g_deviation_subval(dev, control); + + return ret; +} + +/* Write the LocalOscillator CONTROL */ +static inline +void b43legacy_lo_write(struct b43legacy_wldev *dev, + struct b43legacy_lopair *pair) +{ + u16 value; + + value = (u8)(pair->low); + value |= ((u8)(pair->high)) << 8; + +#ifdef CPTCFG_B43LEGACY_DEBUG + /* Sanity check. */ + if (pair->low < -8 || pair->low > 8 || + pair->high < -8 || pair->high > 8) { + b43legacydbg(dev->wl, + "WARNING: Writing invalid LOpair " + "(low: %d, high: %d)\n", + pair->low, pair->high); + dump_stack(); + } +#endif + + b43legacy_phy_write(dev, B43legacy_PHY_G_LO_CONTROL, value); +} + +static inline +struct b43legacy_lopair *b43legacy_find_lopair(struct b43legacy_wldev *dev, + u16 bbatt, + u16 rfatt, + u16 tx) +{ + static const u8 dict[10] = { 11, 10, 11, 12, 13, 12, 13, 12, 13, 12 }; + struct b43legacy_phy *phy = &dev->phy; + + if (bbatt > 6) + bbatt = 6; + B43legacy_WARN_ON(rfatt >= 10); + + if (tx == 3) + return b43legacy_get_lopair(phy, rfatt, bbatt); + return b43legacy_get_lopair(phy, dict[rfatt], bbatt); +} + +static inline +struct b43legacy_lopair *b43legacy_current_lopair(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + + return b43legacy_find_lopair(dev, phy->bbatt, + phy->rfatt, phy->txctl1); +} + +/* Adjust B/G LO */ +void b43legacy_phy_lo_adjust(struct b43legacy_wldev *dev, int fixed) +{ + struct b43legacy_lopair *pair; + + if (fixed) { + /* Use fixed values. Only for initialization. */ + pair = b43legacy_find_lopair(dev, 2, 3, 0); + } else + pair = b43legacy_current_lopair(dev); + b43legacy_lo_write(dev, pair); +} + +static void b43legacy_phy_lo_g_measure_txctl2(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 txctl2 = 0; + u16 i; + u32 smallest; + u32 tmp; + + b43legacy_radio_write16(dev, 0x0052, 0x0000); + udelay(10); + smallest = b43legacy_phy_lo_g_singledeviation(dev, 0); + for (i = 0; i < 16; i++) { + b43legacy_radio_write16(dev, 0x0052, i); + udelay(10); + tmp = b43legacy_phy_lo_g_singledeviation(dev, 0); + if (tmp < smallest) { + smallest = tmp; + txctl2 = i; + } + } + phy->txctl2 = txctl2; +} + +static +void b43legacy_phy_lo_g_state(struct b43legacy_wldev *dev, + const struct b43legacy_lopair *in_pair, + struct b43legacy_lopair *out_pair, + u16 r27) +{ + static const struct b43legacy_lopair transitions[8] = { + { .high = 1, .low = 1, }, + { .high = 1, .low = 0, }, + { .high = 1, .low = -1, }, + { .high = 0, .low = -1, }, + { .high = -1, .low = -1, }, + { .high = -1, .low = 0, }, + { .high = -1, .low = 1, }, + { .high = 0, .low = 1, }, + }; + struct b43legacy_lopair lowest_transition = { + .high = in_pair->high, + .low = in_pair->low, + }; + struct b43legacy_lopair tmp_pair; + struct b43legacy_lopair transition; + int i = 12; + int state = 0; + int found_lower; + int j; + int begin; + int end; + u32 lowest_deviation; + u32 tmp; + + /* Note that in_pair and out_pair can point to the same pair. + * Be careful. */ + + b43legacy_lo_write(dev, &lowest_transition); + lowest_deviation = b43legacy_phy_lo_g_singledeviation(dev, r27); + do { + found_lower = 0; + B43legacy_WARN_ON(!(state >= 0 && state <= 8)); + if (state == 0) { + begin = 1; + end = 8; + } else if (state % 2 == 0) { + begin = state - 1; + end = state + 1; + } else { + begin = state - 2; + end = state + 2; + } + if (begin < 1) + begin += 8; + if (end > 8) + end -= 8; + + j = begin; + tmp_pair.high = lowest_transition.high; + tmp_pair.low = lowest_transition.low; + while (1) { + B43legacy_WARN_ON(!(j >= 1 && j <= 8)); + transition.high = tmp_pair.high + + transitions[j - 1].high; + transition.low = tmp_pair.low + transitions[j - 1].low; + if ((abs(transition.low) < 9) + && (abs(transition.high) < 9)) { + b43legacy_lo_write(dev, &transition); + tmp = b43legacy_phy_lo_g_singledeviation(dev, + r27); + if (tmp < lowest_deviation) { + lowest_deviation = tmp; + state = j; + found_lower = 1; + + lowest_transition.high = + transition.high; + lowest_transition.low = transition.low; + } + } + if (j == end) + break; + if (j == 8) + j = 1; + else + j++; + } + } while (i-- && found_lower); + + out_pair->high = lowest_transition.high; + out_pair->low = lowest_transition.low; +} + +/* Set the baseband attenuation value on chip. */ +void b43legacy_phy_set_baseband_attenuation(struct b43legacy_wldev *dev, + u16 bbatt) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 value; + + if (phy->analog == 0) { + value = (b43legacy_read16(dev, 0x03E6) & 0xFFF0); + value |= (bbatt & 0x000F); + b43legacy_write16(dev, 0x03E6, value); + return; + } + + if (phy->analog > 1) { + value = b43legacy_phy_read(dev, 0x0060) & 0xFFC3; + value |= (bbatt << 2) & 0x003C; + } else { + value = b43legacy_phy_read(dev, 0x0060) & 0xFF87; + value |= (bbatt << 3) & 0x0078; + } + b43legacy_phy_write(dev, 0x0060, value); +} + +/* http://bcm-specs.sipsolutions.net/LocalOscillator/Measure */ +void b43legacy_phy_lo_g_measure(struct b43legacy_wldev *dev) +{ + static const u8 pairorder[10] = { 3, 1, 5, 7, 9, 2, 0, 4, 6, 8 }; + const int is_initializing = (b43legacy_status(dev) + < B43legacy_STAT_STARTED); + struct b43legacy_phy *phy = &dev->phy; + u16 h; + u16 i; + u16 oldi = 0; + u16 j; + struct b43legacy_lopair control; + struct b43legacy_lopair *tmp_control; + u16 tmp; + u16 regstack[16] = { 0 }; + u8 oldchannel; + + /* XXX: What are these? */ + u8 r27 = 0; + u16 r31; + + oldchannel = phy->channel; + /* Setup */ + if (phy->gmode) { + regstack[0] = b43legacy_phy_read(dev, B43legacy_PHY_G_CRS); + regstack[1] = b43legacy_phy_read(dev, 0x0802); + b43legacy_phy_write(dev, B43legacy_PHY_G_CRS, regstack[0] + & 0x7FFF); + b43legacy_phy_write(dev, 0x0802, regstack[1] & 0xFFFC); + } + regstack[3] = b43legacy_read16(dev, 0x03E2); + b43legacy_write16(dev, 0x03E2, regstack[3] | 0x8000); + regstack[4] = b43legacy_read16(dev, B43legacy_MMIO_CHANNEL_EXT); + regstack[5] = b43legacy_phy_read(dev, 0x15); + regstack[6] = b43legacy_phy_read(dev, 0x2A); + regstack[7] = b43legacy_phy_read(dev, 0x35); + regstack[8] = b43legacy_phy_read(dev, 0x60); + regstack[9] = b43legacy_radio_read16(dev, 0x43); + regstack[10] = b43legacy_radio_read16(dev, 0x7A); + regstack[11] = b43legacy_radio_read16(dev, 0x52); + if (phy->gmode) { + regstack[12] = b43legacy_phy_read(dev, 0x0811); + regstack[13] = b43legacy_phy_read(dev, 0x0812); + regstack[14] = b43legacy_phy_read(dev, 0x0814); + regstack[15] = b43legacy_phy_read(dev, 0x0815); + } + b43legacy_radio_selectchannel(dev, 6, 0); + if (phy->gmode) { + b43legacy_phy_write(dev, B43legacy_PHY_G_CRS, regstack[0] + & 0x7FFF); + b43legacy_phy_write(dev, 0x0802, regstack[1] & 0xFFFC); + b43legacy_dummy_transmission(dev); + } + b43legacy_radio_write16(dev, 0x0043, 0x0006); + + b43legacy_phy_set_baseband_attenuation(dev, 2); + + b43legacy_write16(dev, B43legacy_MMIO_CHANNEL_EXT, 0x0000); + b43legacy_phy_write(dev, 0x002E, 0x007F); + b43legacy_phy_write(dev, 0x080F, 0x0078); + b43legacy_phy_write(dev, 0x0035, regstack[7] & ~(1 << 7)); + b43legacy_radio_write16(dev, 0x007A, regstack[10] & 0xFFF0); + b43legacy_phy_write(dev, 0x002B, 0x0203); + b43legacy_phy_write(dev, 0x002A, 0x08A3); + if (phy->gmode) { + b43legacy_phy_write(dev, 0x0814, regstack[14] | 0x0003); + b43legacy_phy_write(dev, 0x0815, regstack[15] & 0xFFFC); + b43legacy_phy_write(dev, 0x0811, 0x01B3); + b43legacy_phy_write(dev, 0x0812, 0x00B2); + } + if (is_initializing) + b43legacy_phy_lo_g_measure_txctl2(dev); + b43legacy_phy_write(dev, 0x080F, 0x8078); + + /* Measure */ + control.low = 0; + control.high = 0; + for (h = 0; h < 10; h++) { + /* Loop over each possible RadioAttenuation (0-9) */ + i = pairorder[h]; + if (is_initializing) { + if (i == 3) { + control.low = 0; + control.high = 0; + } else if (((i % 2 == 1) && (oldi % 2 == 1)) || + ((i % 2 == 0) && (oldi % 2 == 0))) { + tmp_control = b43legacy_get_lopair(phy, oldi, + 0); + memcpy(&control, tmp_control, sizeof(control)); + } else { + tmp_control = b43legacy_get_lopair(phy, 3, 0); + memcpy(&control, tmp_control, sizeof(control)); + } + } + /* Loop over each possible BasebandAttenuation/2 */ + for (j = 0; j < 4; j++) { + if (is_initializing) { + tmp = i * 2 + j; + r27 = 0; + r31 = 0; + if (tmp > 14) { + r31 = 1; + if (tmp > 17) + r27 = 1; + if (tmp > 19) + r27 = 2; + } + } else { + tmp_control = b43legacy_get_lopair(phy, i, + j * 2); + if (!tmp_control->used) + continue; + memcpy(&control, tmp_control, sizeof(control)); + r27 = 3; + r31 = 0; + } + b43legacy_radio_write16(dev, 0x43, i); + b43legacy_radio_write16(dev, 0x52, phy->txctl2); + udelay(10); + b43legacy_voluntary_preempt(); + + b43legacy_phy_set_baseband_attenuation(dev, j * 2); + + tmp = (regstack[10] & 0xFFF0); + if (r31) + tmp |= 0x0008; + b43legacy_radio_write16(dev, 0x007A, tmp); + + tmp_control = b43legacy_get_lopair(phy, i, j * 2); + b43legacy_phy_lo_g_state(dev, &control, tmp_control, + r27); + } + oldi = i; + } + /* Loop over each possible RadioAttenuation (10-13) */ + for (i = 10; i < 14; i++) { + /* Loop over each possible BasebandAttenuation/2 */ + for (j = 0; j < 4; j++) { + if (is_initializing) { + tmp_control = b43legacy_get_lopair(phy, i - 9, + j * 2); + memcpy(&control, tmp_control, sizeof(control)); + /* FIXME: The next line is wrong, as the + * following if statement can never trigger. */ + tmp = (i - 9) * 2 + j - 5; + r27 = 0; + r31 = 0; + if (tmp > 14) { + r31 = 1; + if (tmp > 17) + r27 = 1; + if (tmp > 19) + r27 = 2; + } + } else { + tmp_control = b43legacy_get_lopair(phy, i - 9, + j * 2); + if (!tmp_control->used) + continue; + memcpy(&control, tmp_control, sizeof(control)); + r27 = 3; + r31 = 0; + } + b43legacy_radio_write16(dev, 0x43, i - 9); + /* FIXME: shouldn't txctl1 be zero in the next line + * and 3 in the loop above? */ + b43legacy_radio_write16(dev, 0x52, + phy->txctl2 + | (3/*txctl1*/ << 4)); + udelay(10); + b43legacy_voluntary_preempt(); + + b43legacy_phy_set_baseband_attenuation(dev, j * 2); + + tmp = (regstack[10] & 0xFFF0); + if (r31) + tmp |= 0x0008; + b43legacy_radio_write16(dev, 0x7A, tmp); + + tmp_control = b43legacy_get_lopair(phy, i, j * 2); + b43legacy_phy_lo_g_state(dev, &control, tmp_control, + r27); + } + } + + /* Restoration */ + if (phy->gmode) { + b43legacy_phy_write(dev, 0x0015, 0xE300); + b43legacy_phy_write(dev, 0x0812, (r27 << 8) | 0xA0); + udelay(5); + b43legacy_phy_write(dev, 0x0812, (r27 << 8) | 0xA2); + udelay(2); + b43legacy_phy_write(dev, 0x0812, (r27 << 8) | 0xA3); + b43legacy_voluntary_preempt(); + } else + b43legacy_phy_write(dev, 0x0015, r27 | 0xEFA0); + b43legacy_phy_lo_adjust(dev, is_initializing); + b43legacy_phy_write(dev, 0x002E, 0x807F); + if (phy->gmode) + b43legacy_phy_write(dev, 0x002F, 0x0202); + else + b43legacy_phy_write(dev, 0x002F, 0x0101); + b43legacy_write16(dev, B43legacy_MMIO_CHANNEL_EXT, regstack[4]); + b43legacy_phy_write(dev, 0x0015, regstack[5]); + b43legacy_phy_write(dev, 0x002A, regstack[6]); + b43legacy_phy_write(dev, 0x0035, regstack[7]); + b43legacy_phy_write(dev, 0x0060, regstack[8]); + b43legacy_radio_write16(dev, 0x0043, regstack[9]); + b43legacy_radio_write16(dev, 0x007A, regstack[10]); + regstack[11] &= 0x00F0; + regstack[11] |= (b43legacy_radio_read16(dev, 0x52) & 0x000F); + b43legacy_radio_write16(dev, 0x52, regstack[11]); + b43legacy_write16(dev, 0x03E2, regstack[3]); + if (phy->gmode) { + b43legacy_phy_write(dev, 0x0811, regstack[12]); + b43legacy_phy_write(dev, 0x0812, regstack[13]); + b43legacy_phy_write(dev, 0x0814, regstack[14]); + b43legacy_phy_write(dev, 0x0815, regstack[15]); + b43legacy_phy_write(dev, B43legacy_PHY_G_CRS, regstack[0]); + b43legacy_phy_write(dev, 0x0802, regstack[1]); + } + b43legacy_radio_selectchannel(dev, oldchannel, 1); + +#ifdef CPTCFG_B43LEGACY_DEBUG + { + /* Sanity check for all lopairs. */ + for (i = 0; i < B43legacy_LO_COUNT; i++) { + tmp_control = phy->_lo_pairs + i; + if (tmp_control->low < -8 || tmp_control->low > 8 || + tmp_control->high < -8 || tmp_control->high > 8) + b43legacywarn(dev->wl, + "WARNING: Invalid LOpair (low: %d, high:" + " %d, index: %d)\n", + tmp_control->low, tmp_control->high, i); + } + } +#endif /* CPTCFG_B43LEGACY_DEBUG */ +} + +static +void b43legacy_phy_lo_mark_current_used(struct b43legacy_wldev *dev) +{ + struct b43legacy_lopair *pair; + + pair = b43legacy_current_lopair(dev); + pair->used = 1; +} + +void b43legacy_phy_lo_mark_all_unused(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + struct b43legacy_lopair *pair; + int i; + + for (i = 0; i < B43legacy_LO_COUNT; i++) { + pair = phy->_lo_pairs + i; + pair->used = 0; + } +} + +/* http://bcm-specs.sipsolutions.net/EstimatePowerOut + * This function converts a TSSI value to dBm in Q5.2 + */ +static s8 b43legacy_phy_estimate_power_out(struct b43legacy_wldev *dev, s8 tssi) +{ + struct b43legacy_phy *phy = &dev->phy; + s8 dbm = 0; + s32 tmp; + + tmp = phy->idle_tssi; + tmp += tssi; + tmp -= phy->savedpctlreg; + + switch (phy->type) { + case B43legacy_PHYTYPE_B: + case B43legacy_PHYTYPE_G: + tmp = clamp_val(tmp, 0x00, 0x3F); + dbm = phy->tssi2dbm[tmp]; + break; + default: + B43legacy_BUG_ON(1); + } + + return dbm; +} + +/* http://bcm-specs.sipsolutions.net/RecalculateTransmissionPower */ +void b43legacy_phy_xmitpower(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 tmp; + u16 txpower; + s8 v0; + s8 v1; + s8 v2; + s8 v3; + s8 average; + int max_pwr; + s16 desired_pwr; + s16 estimated_pwr; + s16 pwr_adjust; + s16 radio_att_delta; + s16 baseband_att_delta; + s16 radio_attenuation; + s16 baseband_attenuation; + + if (phy->savedpctlreg == 0xFFFF) + return; + if ((dev->dev->bus->boardinfo.type == 0x0416) && + is_bcm_board_vendor(dev)) + return; +#ifdef CPTCFG_B43LEGACY_DEBUG + if (phy->manual_txpower_control) + return; +#endif + + B43legacy_BUG_ON(!(phy->type == B43legacy_PHYTYPE_B || + phy->type == B43legacy_PHYTYPE_G)); + tmp = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, 0x0058); + v0 = (s8)(tmp & 0x00FF); + v1 = (s8)((tmp & 0xFF00) >> 8); + tmp = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, 0x005A); + v2 = (s8)(tmp & 0x00FF); + v3 = (s8)((tmp & 0xFF00) >> 8); + tmp = 0; + + if (v0 == 0x7F || v1 == 0x7F || v2 == 0x7F || v3 == 0x7F) { + tmp = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, + 0x0070); + v0 = (s8)(tmp & 0x00FF); + v1 = (s8)((tmp & 0xFF00) >> 8); + tmp = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, + 0x0072); + v2 = (s8)(tmp & 0x00FF); + v3 = (s8)((tmp & 0xFF00) >> 8); + if (v0 == 0x7F || v1 == 0x7F || v2 == 0x7F || v3 == 0x7F) + return; + v0 = (v0 + 0x20) & 0x3F; + v1 = (v1 + 0x20) & 0x3F; + v2 = (v2 + 0x20) & 0x3F; + v3 = (v3 + 0x20) & 0x3F; + tmp = 1; + } + b43legacy_radio_clear_tssi(dev); + + average = (v0 + v1 + v2 + v3 + 2) / 4; + + if (tmp && (b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, 0x005E) + & 0x8)) + average -= 13; + + estimated_pwr = b43legacy_phy_estimate_power_out(dev, average); + + max_pwr = dev->dev->bus->sprom.maxpwr_bg; + + if ((dev->dev->bus->sprom.boardflags_lo + & B43legacy_BFL_PACTRL) && + (phy->type == B43legacy_PHYTYPE_G)) + max_pwr -= 0x3; + if (unlikely(max_pwr <= 0)) { + b43legacywarn(dev->wl, "Invalid max-TX-power value in SPROM." + "\n"); + max_pwr = 74; /* fake it */ + dev->dev->bus->sprom.maxpwr_bg = max_pwr; + } + + /* Use regulatory information to get the maximum power. + * In the absence of such data from mac80211, we will use 20 dBm, which + * is the value for the EU, US, Canada, and most of the world. + * The regulatory maximum is reduced by the antenna gain (from sprom) + * and 1.5 dBm (a safety factor??). The result is in Q5.2 format + * which accounts for the factor of 4 */ +#define REG_MAX_PWR 20 + max_pwr = min(REG_MAX_PWR * 4 + - dev->dev->bus->sprom.antenna_gain.a0 + - 0x6, max_pwr); + + /* find the desired power in Q5.2 - power_level is in dBm + * and limit it - max_pwr is already in Q5.2 */ + desired_pwr = clamp_val(phy->power_level << 2, 0, max_pwr); + if (b43legacy_debug(dev, B43legacy_DBG_XMITPOWER)) + b43legacydbg(dev->wl, "Current TX power output: " Q52_FMT + " dBm, Desired TX power output: " Q52_FMT + " dBm\n", Q52_ARG(estimated_pwr), + Q52_ARG(desired_pwr)); + /* Check if we need to adjust the current power. The factor of 2 is + * for damping */ + pwr_adjust = (desired_pwr - estimated_pwr) / 2; + /* RF attenuation delta + * The minus sign is because lower attenuation => more power */ + radio_att_delta = -(pwr_adjust + 7) >> 3; + /* Baseband attenuation delta */ + baseband_att_delta = -(pwr_adjust >> 1) - (4 * radio_att_delta); + /* Do we need to adjust anything? */ + if ((radio_att_delta == 0) && (baseband_att_delta == 0)) { + b43legacy_phy_lo_mark_current_used(dev); + return; + } + + /* Calculate the new attenuation values. */ + baseband_attenuation = phy->bbatt; + baseband_attenuation += baseband_att_delta; + radio_attenuation = phy->rfatt; + radio_attenuation += radio_att_delta; + + /* Get baseband and radio attenuation values into permitted ranges. + * baseband 0-11, radio 0-9. + * Radio attenuation affects power level 4 times as much as baseband. + */ + if (radio_attenuation < 0) { + baseband_attenuation -= (4 * -radio_attenuation); + radio_attenuation = 0; + } else if (radio_attenuation > 9) { + baseband_attenuation += (4 * (radio_attenuation - 9)); + radio_attenuation = 9; + } else { + while (baseband_attenuation < 0 && radio_attenuation > 0) { + baseband_attenuation += 4; + radio_attenuation--; + } + while (baseband_attenuation > 11 && radio_attenuation < 9) { + baseband_attenuation -= 4; + radio_attenuation++; + } + } + baseband_attenuation = clamp_val(baseband_attenuation, 0, 11); + + txpower = phy->txctl1; + if ((phy->radio_ver == 0x2050) && (phy->radio_rev == 2)) { + if (radio_attenuation <= 1) { + if (txpower == 0) { + txpower = 3; + radio_attenuation += 2; + baseband_attenuation += 2; + } else if (dev->dev->bus->sprom.boardflags_lo + & B43legacy_BFL_PACTRL) { + baseband_attenuation += 4 * + (radio_attenuation - 2); + radio_attenuation = 2; + } + } else if (radio_attenuation > 4 && txpower != 0) { + txpower = 0; + if (baseband_attenuation < 3) { + radio_attenuation -= 3; + baseband_attenuation += 2; + } else { + radio_attenuation -= 2; + baseband_attenuation -= 2; + } + } + } + /* Save the control values */ + phy->txctl1 = txpower; + baseband_attenuation = clamp_val(baseband_attenuation, 0, 11); + radio_attenuation = clamp_val(radio_attenuation, 0, 9); + phy->rfatt = radio_attenuation; + phy->bbatt = baseband_attenuation; + + /* Adjust the hardware */ + b43legacy_phy_lock(dev); + b43legacy_radio_lock(dev); + b43legacy_radio_set_txpower_bg(dev, baseband_attenuation, + radio_attenuation, txpower); + b43legacy_phy_lo_mark_current_used(dev); + b43legacy_radio_unlock(dev); + b43legacy_phy_unlock(dev); +} + +static inline +s32 b43legacy_tssi2dbm_ad(s32 num, s32 den) +{ + if (num < 0) + return num/den; + else + return (num+den/2)/den; +} + +static inline +s8 b43legacy_tssi2dbm_entry(s8 entry [], u8 index, s16 pab0, s16 pab1, s16 pab2) +{ + s32 m1; + s32 m2; + s32 f = 256; + s32 q; + s32 delta; + s8 i = 0; + + m1 = b43legacy_tssi2dbm_ad(16 * pab0 + index * pab1, 32); + m2 = max(b43legacy_tssi2dbm_ad(32768 + index * pab2, 256), 1); + do { + if (i > 15) + return -EINVAL; + q = b43legacy_tssi2dbm_ad(f * 4096 - + b43legacy_tssi2dbm_ad(m2 * f, 16) * + f, 2048); + delta = abs(q - f); + f = q; + i++; + } while (delta >= 2); + entry[index] = clamp_val(b43legacy_tssi2dbm_ad(m1 * f, 8192), + -127, 128); + return 0; +} + +/* http://bcm-specs.sipsolutions.net/TSSI_to_DBM_Table */ +int b43legacy_phy_init_tssi2dbm_table(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + s16 pab0; + s16 pab1; + s16 pab2; + u8 idx; + s8 *dyn_tssi2dbm; + + B43legacy_WARN_ON(!(phy->type == B43legacy_PHYTYPE_B || + phy->type == B43legacy_PHYTYPE_G)); + pab0 = (s16)(dev->dev->bus->sprom.pa0b0); + pab1 = (s16)(dev->dev->bus->sprom.pa0b1); + pab2 = (s16)(dev->dev->bus->sprom.pa0b2); + + if ((dev->dev->bus->chip_id == 0x4301) && (phy->radio_ver != 0x2050)) { + phy->idle_tssi = 0x34; + phy->tssi2dbm = b43legacy_tssi2dbm_b_table; + return 0; + } + + if (pab0 != 0 && pab1 != 0 && pab2 != 0 && + pab0 != -1 && pab1 != -1 && pab2 != -1) { + /* The pabX values are set in SPROM. Use them. */ + if ((s8)dev->dev->bus->sprom.itssi_bg != 0 && + (s8)dev->dev->bus->sprom.itssi_bg != -1) + phy->idle_tssi = (s8)(dev->dev->bus->sprom. + itssi_bg); + else + phy->idle_tssi = 62; + dyn_tssi2dbm = kmalloc(64, GFP_KERNEL); + if (dyn_tssi2dbm == NULL) { + b43legacyerr(dev->wl, "Could not allocate memory " + "for tssi2dbm table\n"); + return -ENOMEM; + } + for (idx = 0; idx < 64; idx++) + if (b43legacy_tssi2dbm_entry(dyn_tssi2dbm, idx, pab0, + pab1, pab2)) { + phy->tssi2dbm = NULL; + b43legacyerr(dev->wl, "Could not generate " + "tssi2dBm table\n"); + kfree(dyn_tssi2dbm); + return -ENODEV; + } + phy->tssi2dbm = dyn_tssi2dbm; + phy->dyn_tssi_tbl = 1; + } else { + /* pabX values not set in SPROM. */ + switch (phy->type) { + case B43legacy_PHYTYPE_B: + phy->idle_tssi = 0x34; + phy->tssi2dbm = b43legacy_tssi2dbm_b_table; + break; + case B43legacy_PHYTYPE_G: + phy->idle_tssi = 0x34; + phy->tssi2dbm = b43legacy_tssi2dbm_g_table; + break; + } + } + + return 0; +} + +int b43legacy_phy_init(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + int err = -ENODEV; + + switch (phy->type) { + case B43legacy_PHYTYPE_B: + switch (phy->rev) { + case 2: + b43legacy_phy_initb2(dev); + err = 0; + break; + case 4: + b43legacy_phy_initb4(dev); + err = 0; + break; + case 5: + b43legacy_phy_initb5(dev); + err = 0; + break; + case 6: + b43legacy_phy_initb6(dev); + err = 0; + break; + } + break; + case B43legacy_PHYTYPE_G: + b43legacy_phy_initg(dev); + err = 0; + break; + } + if (err) + b43legacyerr(dev->wl, "Unknown PHYTYPE found\n"); + + return err; +} + +void b43legacy_phy_set_antenna_diversity(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 antennadiv; + u16 offset; + u16 value; + u32 ucodeflags; + + antennadiv = phy->antenna_diversity; + + if (antennadiv == 0xFFFF) + antennadiv = 3; + B43legacy_WARN_ON(antennadiv > 3); + + ucodeflags = b43legacy_shm_read32(dev, B43legacy_SHM_SHARED, + B43legacy_UCODEFLAGS_OFFSET); + b43legacy_shm_write32(dev, B43legacy_SHM_SHARED, + B43legacy_UCODEFLAGS_OFFSET, + ucodeflags & ~B43legacy_UCODEFLAG_AUTODIV); + + switch (phy->type) { + case B43legacy_PHYTYPE_G: + offset = 0x0400; + + if (antennadiv == 2) + value = (3/*automatic*/ << 7); + else + value = (antennadiv << 7); + b43legacy_phy_write(dev, offset + 1, + (b43legacy_phy_read(dev, offset + 1) + & 0x7E7F) | value); + + if (antennadiv >= 2) { + if (antennadiv == 2) + value = (antennadiv << 7); + else + value = (0/*force0*/ << 7); + b43legacy_phy_write(dev, offset + 0x2B, + (b43legacy_phy_read(dev, + offset + 0x2B) + & 0xFEFF) | value); + } + + if (phy->type == B43legacy_PHYTYPE_G) { + if (antennadiv >= 2) + b43legacy_phy_write(dev, 0x048C, + b43legacy_phy_read(dev, + 0x048C) | 0x2000); + else + b43legacy_phy_write(dev, 0x048C, + b43legacy_phy_read(dev, + 0x048C) & ~0x2000); + if (phy->rev >= 2) { + b43legacy_phy_write(dev, 0x0461, + b43legacy_phy_read(dev, + 0x0461) | 0x0010); + b43legacy_phy_write(dev, 0x04AD, + (b43legacy_phy_read(dev, + 0x04AD) + & 0x00FF) | 0x0015); + if (phy->rev == 2) + b43legacy_phy_write(dev, 0x0427, + 0x0008); + else + b43legacy_phy_write(dev, 0x0427, + (b43legacy_phy_read(dev, 0x0427) + & 0x00FF) | 0x0008); + } else if (phy->rev >= 6) + b43legacy_phy_write(dev, 0x049B, 0x00DC); + } else { + if (phy->rev < 3) + b43legacy_phy_write(dev, 0x002B, + (b43legacy_phy_read(dev, + 0x002B) & 0x00FF) + | 0x0024); + else { + b43legacy_phy_write(dev, 0x0061, + b43legacy_phy_read(dev, + 0x0061) | 0x0010); + if (phy->rev == 3) { + b43legacy_phy_write(dev, 0x0093, + 0x001D); + b43legacy_phy_write(dev, 0x0027, + 0x0008); + } else { + b43legacy_phy_write(dev, 0x0093, + 0x003A); + b43legacy_phy_write(dev, 0x0027, + (b43legacy_phy_read(dev, 0x0027) + & 0x00FF) | 0x0008); + } + } + } + break; + case B43legacy_PHYTYPE_B: + if (dev->dev->id.revision == 2) + value = (3/*automatic*/ << 7); + else + value = (antennadiv << 7); + b43legacy_phy_write(dev, 0x03E2, + (b43legacy_phy_read(dev, 0x03E2) + & 0xFE7F) | value); + break; + default: + B43legacy_WARN_ON(1); + } + + if (antennadiv >= 2) { + ucodeflags = b43legacy_shm_read32(dev, B43legacy_SHM_SHARED, + B43legacy_UCODEFLAGS_OFFSET); + b43legacy_shm_write32(dev, B43legacy_SHM_SHARED, + B43legacy_UCODEFLAGS_OFFSET, + ucodeflags | B43legacy_UCODEFLAG_AUTODIV); + } + + phy->antenna_diversity = antennadiv; +} + +/* Set the PowerSavingControlBits. + * Bitvalues: + * 0 => unset the bit + * 1 => set the bit + * -1 => calculate the bit + */ +void b43legacy_power_saving_ctl_bits(struct b43legacy_wldev *dev, + int bit25, int bit26) +{ + int i; + u32 status; + +/* FIXME: Force 25 to off and 26 to on for now: */ +bit25 = 0; +bit26 = 1; + + if (bit25 == -1) { + /* TODO: If powersave is not off and FIXME is not set and we + * are not in adhoc and thus is not an AP and we arei + * associated, set bit 25 */ + } + if (bit26 == -1) { + /* TODO: If the device is awake or this is an AP, or we are + * scanning, or FIXME, or we are associated, or FIXME, + * or the latest PS-Poll packet sent was successful, + * set bit26 */ + } + status = b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + if (bit25) + status |= B43legacy_MACCTL_HWPS; + else + status &= ~B43legacy_MACCTL_HWPS; + if (bit26) + status |= B43legacy_MACCTL_AWAKE; + else + status &= ~B43legacy_MACCTL_AWAKE; + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, status); + if (bit26 && dev->dev->id.revision >= 5) { + for (i = 0; i < 100; i++) { + if (b43legacy_shm_read32(dev, B43legacy_SHM_SHARED, + 0x0040) != 4) + break; + udelay(10); + } + } +} diff --git a/drivers/net/wireless/b43legacy/phy.h b/drivers/net/wireless/b43legacy/phy.h new file mode 100644 index 0000000..831a7a4 --- /dev/null +++ b/drivers/net/wireless/b43legacy/phy.h @@ -0,0 +1,209 @@ +/* + + Broadcom B43legacy wireless driver + + Copyright (c) 2005 Martin Langer , + Stefano Brivio + Michael Buesch + Danny van Dyk + Andreas Jaggi + Copyright (c) 2007 Larry Finger + + Some parts of the code in this file are derived from the ipw2200 + driver Copyright(c) 2003 - 2004 Intel Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#ifndef B43legacy_PHY_H_ +#define B43legacy_PHY_H_ + +#include + +enum { + B43legacy_ANTENNA0, /* Antenna 0 */ + B43legacy_ANTENNA1, /* Antenna 0 */ + B43legacy_ANTENNA_AUTO1, /* Automatic, starting with antenna 1 */ + B43legacy_ANTENNA_AUTO0, /* Automatic, starting with antenna 0 */ + + B43legacy_ANTENNA_AUTO = B43legacy_ANTENNA_AUTO0, + B43legacy_ANTENNA_DEFAULT = B43legacy_ANTENNA_AUTO, +}; + +enum { + B43legacy_INTERFMODE_NONE, + B43legacy_INTERFMODE_NONWLAN, + B43legacy_INTERFMODE_MANUALWLAN, + B43legacy_INTERFMODE_AUTOWLAN, +}; + +/*** PHY Registers ***/ + +/* Routing */ +#define B43legacy_PHYROUTE_OFDM_GPHY 0x400 +#define B43legacy_PHYROUTE_EXT_GPHY 0x800 + +/* Base registers. */ +#define B43legacy_PHY_BASE(reg) (reg) +/* OFDM (A) registers of a G-PHY */ +#define B43legacy_PHY_OFDM(reg) ((reg) | B43legacy_PHYROUTE_OFDM_GPHY) +/* Extended G-PHY registers */ +#define B43legacy_PHY_EXTG(reg) ((reg) | B43legacy_PHYROUTE_EXT_GPHY) + + +/* Extended G-PHY Registers */ +#define B43legacy_PHY_CLASSCTL B43legacy_PHY_EXTG(0x02) /* Classify control */ +#define B43legacy_PHY_GTABCTL B43legacy_PHY_EXTG(0x03) /* G-PHY table control (see below) */ +#define B43legacy_PHY_GTABOFF 0x03FF /* G-PHY table offset (see below) */ +#define B43legacy_PHY_GTABNR 0xFC00 /* G-PHY table number (see below) */ +#define B43legacy_PHY_GTABNR_SHIFT 10 +#define B43legacy_PHY_GTABDATA B43legacy_PHY_EXTG(0x04) /* G-PHY table data */ +#define B43legacy_PHY_LO_MASK B43legacy_PHY_EXTG(0x0F) /* Local Oscillator control mask */ +#define B43legacy_PHY_LO_CTL B43legacy_PHY_EXTG(0x10) /* Local Oscillator control */ +#define B43legacy_PHY_RFOVER B43legacy_PHY_EXTG(0x11) /* RF override */ +#define B43legacy_PHY_RFOVERVAL B43legacy_PHY_EXTG(0x12) /* RF override value */ +/*** OFDM table numbers ***/ +#define B43legacy_OFDMTAB(number, offset) \ + (((number) << B43legacy_PHY_OTABLENR_SHIFT) \ + | (offset)) +#define B43legacy_OFDMTAB_AGC1 B43legacy_OFDMTAB(0x00, 0) +#define B43legacy_OFDMTAB_GAIN0 B43legacy_OFDMTAB(0x00, 0) +#define B43legacy_OFDMTAB_GAINX B43legacy_OFDMTAB(0x01, 0) +#define B43legacy_OFDMTAB_GAIN1 B43legacy_OFDMTAB(0x01, 4) +#define B43legacy_OFDMTAB_AGC3 B43legacy_OFDMTAB(0x02, 0) +#define B43legacy_OFDMTAB_GAIN2 B43legacy_OFDMTAB(0x02, 3) +#define B43legacy_OFDMTAB_LNAHPFGAIN1 B43legacy_OFDMTAB(0x03, 0) +#define B43legacy_OFDMTAB_WRSSI B43legacy_OFDMTAB(0x04, 0) +#define B43legacy_OFDMTAB_LNAHPFGAIN2 B43legacy_OFDMTAB(0x04, 0) +#define B43legacy_OFDMTAB_NOISESCALE B43legacy_OFDMTAB(0x05, 0) +#define B43legacy_OFDMTAB_AGC2 B43legacy_OFDMTAB(0x06, 0) +#define B43legacy_OFDMTAB_ROTOR B43legacy_OFDMTAB(0x08, 0) +#define B43legacy_OFDMTAB_ADVRETARD B43legacy_OFDMTAB(0x09, 0) +#define B43legacy_OFDMTAB_DAC B43legacy_OFDMTAB(0x0C, 0) +#define B43legacy_OFDMTAB_DC B43legacy_OFDMTAB(0x0E, 7) +#define B43legacy_OFDMTAB_PWRDYN2 B43legacy_OFDMTAB(0x0E, 12) +#define B43legacy_OFDMTAB_LNAGAIN B43legacy_OFDMTAB(0x0E, 13) + +#define B43legacy_OFDMTAB_LPFGAIN B43legacy_OFDMTAB(0x0F, 12) +#define B43legacy_OFDMTAB_RSSI B43legacy_OFDMTAB(0x10, 0) + +#define B43legacy_OFDMTAB_AGC1_R1 B43legacy_OFDMTAB(0x13, 0) +#define B43legacy_OFDMTAB_GAINX_R1 B43legacy_OFDMTAB(0x14, 0) +#define B43legacy_OFDMTAB_MINSIGSQ B43legacy_OFDMTAB(0x14, 1) +#define B43legacy_OFDMTAB_AGC3_R1 B43legacy_OFDMTAB(0x15, 0) +#define B43legacy_OFDMTAB_WRSSI_R1 B43legacy_OFDMTAB(0x15, 4) +#define B43legacy_OFDMTAB_TSSI B43legacy_OFDMTAB(0x15, 0) +#define B43legacy_OFDMTAB_DACRFPABB B43legacy_OFDMTAB(0x16, 0) +#define B43legacy_OFDMTAB_DACOFF B43legacy_OFDMTAB(0x17, 0) +#define B43legacy_OFDMTAB_DCBIAS B43legacy_OFDMTAB(0x18, 0) + +void b43legacy_put_attenuation_into_ranges(int *_bbatt, int *_rfatt); + +/* OFDM (A) PHY Registers */ +#define B43legacy_PHY_VERSION_OFDM B43legacy_PHY_OFDM(0x00) /* Versioning register for A-PHY */ +#define B43legacy_PHY_BBANDCFG B43legacy_PHY_OFDM(0x01) /* Baseband config */ +#define B43legacy_PHY_BBANDCFG_RXANT 0x180 /* RX Antenna selection */ +#define B43legacy_PHY_BBANDCFG_RXANT_SHIFT 7 +#define B43legacy_PHY_PWRDOWN B43legacy_PHY_OFDM(0x03) /* Powerdown */ +#define B43legacy_PHY_CRSTHRES1 B43legacy_PHY_OFDM(0x06) /* CRS Threshold 1 */ +#define B43legacy_PHY_LNAHPFCTL B43legacy_PHY_OFDM(0x1C) /* LNA/HPF control */ +#define B43legacy_PHY_ADIVRELATED B43legacy_PHY_OFDM(0x27) /* FIXME rename */ +#define B43legacy_PHY_CRS0 B43legacy_PHY_OFDM(0x29) +#define B43legacy_PHY_ANTDWELL B43legacy_PHY_OFDM(0x2B) /* Antenna dwell */ +#define B43legacy_PHY_ANTDWELL_AUTODIV1 0x0100 /* Automatic RX diversity start antenna */ +#define B43legacy_PHY_ENCORE B43legacy_PHY_OFDM(0x49) /* "Encore" (RangeMax / BroadRange) */ +#define B43legacy_PHY_ENCORE_EN 0x0200 /* Encore enable */ +#define B43legacy_PHY_LMS B43legacy_PHY_OFDM(0x55) +#define B43legacy_PHY_OFDM61 B43legacy_PHY_OFDM(0x61) /* FIXME rename */ +#define B43legacy_PHY_OFDM61_10 0x0010 /* FIXME rename */ +#define B43legacy_PHY_IQBAL B43legacy_PHY_OFDM(0x69) /* I/Q balance */ +#define B43legacy_PHY_OTABLECTL B43legacy_PHY_OFDM(0x72) /* OFDM table control (see below) */ +#define B43legacy_PHY_OTABLEOFF 0x03FF /* OFDM table offset (see below) */ +#define B43legacy_PHY_OTABLENR 0xFC00 /* OFDM table number (see below) */ +#define B43legacy_PHY_OTABLENR_SHIFT 10 +#define B43legacy_PHY_OTABLEI B43legacy_PHY_OFDM(0x73) /* OFDM table data I */ +#define B43legacy_PHY_OTABLEQ B43legacy_PHY_OFDM(0x74) /* OFDM table data Q */ +#define B43legacy_PHY_HPWR_TSSICTL B43legacy_PHY_OFDM(0x78) /* Hardware power TSSI control */ +#define B43legacy_PHY_NRSSITHRES B43legacy_PHY_OFDM(0x8A) /* NRSSI threshold */ +#define B43legacy_PHY_ANTWRSETT B43legacy_PHY_OFDM(0x8C) /* Antenna WR settle */ +#define B43legacy_PHY_ANTWRSETT_ARXDIV 0x2000 /* Automatic RX diversity enabled */ +#define B43legacy_PHY_CLIPPWRDOWNT B43legacy_PHY_OFDM(0x93) /* Clip powerdown threshold */ +#define B43legacy_PHY_OFDM9B B43legacy_PHY_OFDM(0x9B) /* FIXME rename */ +#define B43legacy_PHY_N1P1GAIN B43legacy_PHY_OFDM(0xA0) +#define B43legacy_PHY_P1P2GAIN B43legacy_PHY_OFDM(0xA1) +#define B43legacy_PHY_N1N2GAIN B43legacy_PHY_OFDM(0xA2) +#define B43legacy_PHY_CLIPTHRES B43legacy_PHY_OFDM(0xA3) +#define B43legacy_PHY_CLIPN1P2THRES B43legacy_PHY_OFDM(0xA4) +#define B43legacy_PHY_DIVSRCHIDX B43legacy_PHY_OFDM(0xA8) /* Divider search gain/index */ +#define B43legacy_PHY_CLIPP2THRES B43legacy_PHY_OFDM(0xA9) +#define B43legacy_PHY_CLIPP3THRES B43legacy_PHY_OFDM(0xAA) +#define B43legacy_PHY_DIVP1P2GAIN B43legacy_PHY_OFDM(0xAB) +#define B43legacy_PHY_DIVSRCHGAINBACK B43legacy_PHY_OFDM(0xAD) /* Divider search gain back */ +#define B43legacy_PHY_DIVSRCHGAINCHNG B43legacy_PHY_OFDM(0xAE) /* Divider search gain change */ +#define B43legacy_PHY_CRSTHRES1_R1 B43legacy_PHY_OFDM(0xC0) /* CRS Threshold 1 (rev 1 only) */ +#define B43legacy_PHY_CRSTHRES2_R1 B43legacy_PHY_OFDM(0xC1) /* CRS Threshold 2 (rev 1 only) */ +#define B43legacy_PHY_TSSIP_LTBASE B43legacy_PHY_OFDM(0x380) /* TSSI power lookup table base */ +#define B43legacy_PHY_DC_LTBASE B43legacy_PHY_OFDM(0x3A0) /* DC lookup table base */ +#define B43legacy_PHY_GAIN_LTBASE B43legacy_PHY_OFDM(0x3C0) /* Gain lookup table base */ + +void b43legacy_put_attenuation_into_ranges(int *_bbatt, int *_rfatt); + +/* Masks for the different PHY versioning registers. */ +#define B43legacy_PHYVER_ANALOG 0xF000 +#define B43legacy_PHYVER_ANALOG_SHIFT 12 +#define B43legacy_PHYVER_TYPE 0x0F00 +#define B43legacy_PHYVER_TYPE_SHIFT 8 +#define B43legacy_PHYVER_VERSION 0x00FF + +struct b43legacy_wldev; + +void b43legacy_phy_lock(struct b43legacy_wldev *dev); +void b43legacy_phy_unlock(struct b43legacy_wldev *dev); + +/* Card uses the loopback gain stuff */ +#define has_loopback_gain(phy) \ + (((phy)->rev > 1) || ((phy)->gmode)) + +u16 b43legacy_phy_read(struct b43legacy_wldev *dev, u16 offset); +void b43legacy_phy_write(struct b43legacy_wldev *dev, u16 offset, u16 val); + +int b43legacy_phy_init_tssi2dbm_table(struct b43legacy_wldev *dev); +int b43legacy_phy_init(struct b43legacy_wldev *dev); + +void b43legacy_set_rx_antenna(struct b43legacy_wldev *dev, int antenna); + +void b43legacy_phy_set_antenna_diversity(struct b43legacy_wldev *dev); +void b43legacy_phy_calibrate(struct b43legacy_wldev *dev); +int b43legacy_phy_connect(struct b43legacy_wldev *dev, int connect); + +void b43legacy_phy_lo_b_measure(struct b43legacy_wldev *dev); +void b43legacy_phy_lo_g_measure(struct b43legacy_wldev *dev); +void b43legacy_phy_xmitpower(struct b43legacy_wldev *dev); + +/* Adjust the LocalOscillator to the saved values. + * "fixed" is only set to 1 once in initialization. Set to 0 otherwise. + */ +void b43legacy_phy_lo_adjust(struct b43legacy_wldev *dev, int fixed); +void b43legacy_phy_lo_mark_all_unused(struct b43legacy_wldev *dev); + +void b43legacy_phy_set_baseband_attenuation(struct b43legacy_wldev *dev, + u16 baseband_attenuation); + +void b43legacy_power_saving_ctl_bits(struct b43legacy_wldev *dev, + int bit25, int bit26); + +#endif /* B43legacy_PHY_H_ */ diff --git a/drivers/net/wireless/b43legacy/pio.c b/drivers/net/wireless/b43legacy/pio.c new file mode 100644 index 0000000..282eede --- /dev/null +++ b/drivers/net/wireless/b43legacy/pio.c @@ -0,0 +1,695 @@ +/* + + Broadcom B43legacy wireless driver + + PIO Transmission + + Copyright (c) 2005 Michael Buesch + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "b43legacy.h" +#include "pio.h" +#include "main.h" +#include "xmit.h" + +#include +#include + + +static void tx_start(struct b43legacy_pioqueue *queue) +{ + b43legacy_pio_write(queue, B43legacy_PIO_TXCTL, + B43legacy_PIO_TXCTL_INIT); +} + +static void tx_octet(struct b43legacy_pioqueue *queue, + u8 octet) +{ + if (queue->need_workarounds) { + b43legacy_pio_write(queue, B43legacy_PIO_TXDATA, octet); + b43legacy_pio_write(queue, B43legacy_PIO_TXCTL, + B43legacy_PIO_TXCTL_WRITELO); + } else { + b43legacy_pio_write(queue, B43legacy_PIO_TXCTL, + B43legacy_PIO_TXCTL_WRITELO); + b43legacy_pio_write(queue, B43legacy_PIO_TXDATA, octet); + } +} + +static u16 tx_get_next_word(const u8 *txhdr, + const u8 *packet, + size_t txhdr_size, + unsigned int *pos) +{ + const u8 *source; + unsigned int i = *pos; + u16 ret; + + if (i < txhdr_size) + source = txhdr; + else { + source = packet; + i -= txhdr_size; + } + ret = le16_to_cpu(*((__le16 *)(source + i))); + *pos += 2; + + return ret; +} + +static void tx_data(struct b43legacy_pioqueue *queue, + u8 *txhdr, + const u8 *packet, + unsigned int octets) +{ + u16 data; + unsigned int i = 0; + + if (queue->need_workarounds) { + data = tx_get_next_word(txhdr, packet, + sizeof(struct b43legacy_txhdr_fw3), &i); + b43legacy_pio_write(queue, B43legacy_PIO_TXDATA, data); + } + b43legacy_pio_write(queue, B43legacy_PIO_TXCTL, + B43legacy_PIO_TXCTL_WRITELO | + B43legacy_PIO_TXCTL_WRITEHI); + while (i < octets - 1) { + data = tx_get_next_word(txhdr, packet, + sizeof(struct b43legacy_txhdr_fw3), &i); + b43legacy_pio_write(queue, B43legacy_PIO_TXDATA, data); + } + if (octets % 2) + tx_octet(queue, packet[octets - + sizeof(struct b43legacy_txhdr_fw3) - 1]); +} + +static void tx_complete(struct b43legacy_pioqueue *queue, + struct sk_buff *skb) +{ + if (queue->need_workarounds) { + b43legacy_pio_write(queue, B43legacy_PIO_TXDATA, + skb->data[skb->len - 1]); + b43legacy_pio_write(queue, B43legacy_PIO_TXCTL, + B43legacy_PIO_TXCTL_WRITELO | + B43legacy_PIO_TXCTL_COMPLETE); + } else + b43legacy_pio_write(queue, B43legacy_PIO_TXCTL, + B43legacy_PIO_TXCTL_COMPLETE); +} + +static u16 generate_cookie(struct b43legacy_pioqueue *queue, + struct b43legacy_pio_txpacket *packet) +{ + u16 cookie = 0x0000; + int packetindex; + + /* We use the upper 4 bits for the PIO + * controller ID and the lower 12 bits + * for the packet index (in the cache). + */ + switch (queue->mmio_base) { + case B43legacy_MMIO_PIO1_BASE: + break; + case B43legacy_MMIO_PIO2_BASE: + cookie = 0x1000; + break; + case B43legacy_MMIO_PIO3_BASE: + cookie = 0x2000; + break; + case B43legacy_MMIO_PIO4_BASE: + cookie = 0x3000; + break; + default: + B43legacy_WARN_ON(1); + } + packetindex = pio_txpacket_getindex(packet); + B43legacy_WARN_ON(!(((u16)packetindex & 0xF000) == 0x0000)); + cookie |= (u16)packetindex; + + return cookie; +} + +static +struct b43legacy_pioqueue *parse_cookie(struct b43legacy_wldev *dev, + u16 cookie, + struct b43legacy_pio_txpacket **packet) +{ + struct b43legacy_pio *pio = &dev->pio; + struct b43legacy_pioqueue *queue = NULL; + int packetindex; + + switch (cookie & 0xF000) { + case 0x0000: + queue = pio->queue0; + break; + case 0x1000: + queue = pio->queue1; + break; + case 0x2000: + queue = pio->queue2; + break; + case 0x3000: + queue = pio->queue3; + break; + default: + B43legacy_WARN_ON(1); + } + packetindex = (cookie & 0x0FFF); + B43legacy_WARN_ON(!(packetindex >= 0 && packetindex + < B43legacy_PIO_MAXTXPACKETS)); + *packet = &(queue->tx_packets_cache[packetindex]); + + return queue; +} + +union txhdr_union { + struct b43legacy_txhdr_fw3 txhdr_fw3; +}; + +static int pio_tx_write_fragment(struct b43legacy_pioqueue *queue, + struct sk_buff *skb, + struct b43legacy_pio_txpacket *packet, + size_t txhdr_size) +{ + union txhdr_union txhdr_data; + u8 *txhdr = NULL; + unsigned int octets; + int err; + + txhdr = (u8 *)(&txhdr_data.txhdr_fw3); + + B43legacy_WARN_ON(skb_shinfo(skb)->nr_frags != 0); + err = b43legacy_generate_txhdr(queue->dev, + txhdr, skb->data, skb->len, + IEEE80211_SKB_CB(skb), + generate_cookie(queue, packet)); + if (err) + return err; + + tx_start(queue); + octets = skb->len + txhdr_size; + if (queue->need_workarounds) + octets--; + tx_data(queue, txhdr, (u8 *)skb->data, octets); + tx_complete(queue, skb); + + return 0; +} + +static void free_txpacket(struct b43legacy_pio_txpacket *packet, + int irq_context) +{ + struct b43legacy_pioqueue *queue = packet->queue; + + if (packet->skb) { + if (irq_context) + dev_kfree_skb_irq(packet->skb); + else + dev_kfree_skb(packet->skb); + } + list_move(&packet->list, &queue->txfree); + queue->nr_txfree++; +} + +static int pio_tx_packet(struct b43legacy_pio_txpacket *packet) +{ + struct b43legacy_pioqueue *queue = packet->queue; + struct sk_buff *skb = packet->skb; + u16 octets; + int err; + + octets = (u16)skb->len + sizeof(struct b43legacy_txhdr_fw3); + if (queue->tx_devq_size < octets) { + b43legacywarn(queue->dev->wl, "PIO queue too small. " + "Dropping packet.\n"); + /* Drop it silently (return success) */ + free_txpacket(packet, 1); + return 0; + } + B43legacy_WARN_ON(queue->tx_devq_packets > + B43legacy_PIO_MAXTXDEVQPACKETS); + B43legacy_WARN_ON(queue->tx_devq_used > queue->tx_devq_size); + /* Check if there is sufficient free space on the device + * TX queue. If not, return and let the TX tasklet + * retry later. + */ + if (queue->tx_devq_packets == B43legacy_PIO_MAXTXDEVQPACKETS) + return -EBUSY; + if (queue->tx_devq_used + octets > queue->tx_devq_size) + return -EBUSY; + /* Now poke the device. */ + err = pio_tx_write_fragment(queue, skb, packet, + sizeof(struct b43legacy_txhdr_fw3)); + if (unlikely(err == -ENOKEY)) { + /* Drop this packet, as we don't have the encryption key + * anymore and must not transmit it unencrypted. */ + free_txpacket(packet, 1); + return 0; + } + + /* Account for the packet size. + * (We must not overflow the device TX queue) + */ + queue->tx_devq_packets++; + queue->tx_devq_used += octets; + + /* Transmission started, everything ok, move the + * packet to the txrunning list. + */ + list_move_tail(&packet->list, &queue->txrunning); + + return 0; +} + +static void tx_tasklet(unsigned long d) +{ + struct b43legacy_pioqueue *queue = (struct b43legacy_pioqueue *)d; + struct b43legacy_wldev *dev = queue->dev; + unsigned long flags; + struct b43legacy_pio_txpacket *packet, *tmp_packet; + int err; + u16 txctl; + + spin_lock_irqsave(&dev->wl->irq_lock, flags); + if (queue->tx_frozen) + goto out_unlock; + txctl = b43legacy_pio_read(queue, B43legacy_PIO_TXCTL); + if (txctl & B43legacy_PIO_TXCTL_SUSPEND) + goto out_unlock; + + list_for_each_entry_safe(packet, tmp_packet, &queue->txqueue, list) { + /* Try to transmit the packet. This can fail, if + * the device queue is full. In case of failure, the + * packet is left in the txqueue. + * If transmission succeed, the packet is moved to txrunning. + * If it is impossible to transmit the packet, it + * is dropped. + */ + err = pio_tx_packet(packet); + if (err) + break; + } +out_unlock: + spin_unlock_irqrestore(&dev->wl->irq_lock, flags); +} + +static void setup_txqueues(struct b43legacy_pioqueue *queue) +{ + struct b43legacy_pio_txpacket *packet; + int i; + + queue->nr_txfree = B43legacy_PIO_MAXTXPACKETS; + for (i = 0; i < B43legacy_PIO_MAXTXPACKETS; i++) { + packet = &(queue->tx_packets_cache[i]); + + packet->queue = queue; + INIT_LIST_HEAD(&packet->list); + + list_add(&packet->list, &queue->txfree); + } +} + +static +struct b43legacy_pioqueue *b43legacy_setup_pioqueue(struct b43legacy_wldev *dev, + u16 pio_mmio_base) +{ + struct b43legacy_pioqueue *queue; + u32 value; + u16 qsize; + + queue = kzalloc(sizeof(*queue), GFP_KERNEL); + if (!queue) + goto out; + + queue->dev = dev; + queue->mmio_base = pio_mmio_base; + queue->need_workarounds = (dev->dev->id.revision < 3); + + INIT_LIST_HEAD(&queue->txfree); + INIT_LIST_HEAD(&queue->txqueue); + INIT_LIST_HEAD(&queue->txrunning); + tasklet_init(&queue->txtask, tx_tasklet, + (unsigned long)queue); + + value = b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + value &= ~B43legacy_MACCTL_BE; + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, value); + + qsize = b43legacy_read16(dev, queue->mmio_base + + B43legacy_PIO_TXQBUFSIZE); + if (qsize == 0) { + b43legacyerr(dev->wl, "This card does not support PIO " + "operation mode. Please use DMA mode " + "(module parameter pio=0).\n"); + goto err_freequeue; + } + if (qsize <= B43legacy_PIO_TXQADJUST) { + b43legacyerr(dev->wl, "PIO tx device-queue too small (%u)\n", + qsize); + goto err_freequeue; + } + qsize -= B43legacy_PIO_TXQADJUST; + queue->tx_devq_size = qsize; + + setup_txqueues(queue); + +out: + return queue; + +err_freequeue: + kfree(queue); + queue = NULL; + goto out; +} + +static void cancel_transfers(struct b43legacy_pioqueue *queue) +{ + struct b43legacy_pio_txpacket *packet, *tmp_packet; + + tasklet_kill(&queue->txtask); + + list_for_each_entry_safe(packet, tmp_packet, &queue->txrunning, list) + free_txpacket(packet, 0); + list_for_each_entry_safe(packet, tmp_packet, &queue->txqueue, list) + free_txpacket(packet, 0); +} + +static void b43legacy_destroy_pioqueue(struct b43legacy_pioqueue *queue) +{ + if (!queue) + return; + + cancel_transfers(queue); + kfree(queue); +} + +void b43legacy_pio_free(struct b43legacy_wldev *dev) +{ + struct b43legacy_pio *pio; + + if (!b43legacy_using_pio(dev)) + return; + pio = &dev->pio; + + b43legacy_destroy_pioqueue(pio->queue3); + pio->queue3 = NULL; + b43legacy_destroy_pioqueue(pio->queue2); + pio->queue2 = NULL; + b43legacy_destroy_pioqueue(pio->queue1); + pio->queue1 = NULL; + b43legacy_destroy_pioqueue(pio->queue0); + pio->queue0 = NULL; +} + +int b43legacy_pio_init(struct b43legacy_wldev *dev) +{ + struct b43legacy_pio *pio = &dev->pio; + struct b43legacy_pioqueue *queue; + int err = -ENOMEM; + + queue = b43legacy_setup_pioqueue(dev, B43legacy_MMIO_PIO1_BASE); + if (!queue) + goto out; + pio->queue0 = queue; + + queue = b43legacy_setup_pioqueue(dev, B43legacy_MMIO_PIO2_BASE); + if (!queue) + goto err_destroy0; + pio->queue1 = queue; + + queue = b43legacy_setup_pioqueue(dev, B43legacy_MMIO_PIO3_BASE); + if (!queue) + goto err_destroy1; + pio->queue2 = queue; + + queue = b43legacy_setup_pioqueue(dev, B43legacy_MMIO_PIO4_BASE); + if (!queue) + goto err_destroy2; + pio->queue3 = queue; + + if (dev->dev->id.revision < 3) + dev->irq_mask |= B43legacy_IRQ_PIO_WORKAROUND; + + b43legacydbg(dev->wl, "PIO initialized\n"); + err = 0; +out: + return err; + +err_destroy2: + b43legacy_destroy_pioqueue(pio->queue2); + pio->queue2 = NULL; +err_destroy1: + b43legacy_destroy_pioqueue(pio->queue1); + pio->queue1 = NULL; +err_destroy0: + b43legacy_destroy_pioqueue(pio->queue0); + pio->queue0 = NULL; + goto out; +} + +int b43legacy_pio_tx(struct b43legacy_wldev *dev, + struct sk_buff *skb) +{ + struct b43legacy_pioqueue *queue = dev->pio.queue1; + struct b43legacy_pio_txpacket *packet; + + B43legacy_WARN_ON(queue->tx_suspended); + B43legacy_WARN_ON(list_empty(&queue->txfree)); + + packet = list_entry(queue->txfree.next, struct b43legacy_pio_txpacket, + list); + packet->skb = skb; + + list_move_tail(&packet->list, &queue->txqueue); + queue->nr_txfree--; + B43legacy_WARN_ON(queue->nr_txfree >= B43legacy_PIO_MAXTXPACKETS); + + tasklet_schedule(&queue->txtask); + + return 0; +} + +void b43legacy_pio_handle_txstatus(struct b43legacy_wldev *dev, + const struct b43legacy_txstatus *status) +{ + struct b43legacy_pioqueue *queue; + struct b43legacy_pio_txpacket *packet; + struct ieee80211_tx_info *info; + int retry_limit; + + queue = parse_cookie(dev, status->cookie, &packet); + B43legacy_WARN_ON(!queue); + + if (!packet->skb) + return; + + queue->tx_devq_packets--; + queue->tx_devq_used -= (packet->skb->len + + sizeof(struct b43legacy_txhdr_fw3)); + + info = IEEE80211_SKB_CB(packet->skb); + + /* preserve the confiured retry limit before clearing the status + * The xmit function has overwritten the rc's value with the actual + * retry limit done by the hardware */ + retry_limit = info->status.rates[0].count; + ieee80211_tx_info_clear_status(info); + + if (status->acked) + info->flags |= IEEE80211_TX_STAT_ACK; + + if (status->rts_count > dev->wl->hw->conf.short_frame_max_tx_count) { + /* + * If the short retries (RTS, not data frame) have exceeded + * the limit, the hw will not have tried the selected rate, + * but will have used the fallback rate instead. + * Don't let the rate control count attempts for the selected + * rate in this case, otherwise the statistics will be off. + */ + info->status.rates[0].count = 0; + info->status.rates[1].count = status->frame_count; + } else { + if (status->frame_count > retry_limit) { + info->status.rates[0].count = retry_limit; + info->status.rates[1].count = status->frame_count - + retry_limit; + + } else { + info->status.rates[0].count = status->frame_count; + info->status.rates[1].idx = -1; + } + } + ieee80211_tx_status_irqsafe(dev->wl->hw, packet->skb); + packet->skb = NULL; + + free_txpacket(packet, 1); + /* If there are packets on the txqueue, poke the tasklet + * to transmit them. + */ + if (!list_empty(&queue->txqueue)) + tasklet_schedule(&queue->txtask); +} + +static void pio_rx_error(struct b43legacy_pioqueue *queue, + int clear_buffers, + const char *error) +{ + int i; + + b43legacyerr(queue->dev->wl, "PIO RX error: %s\n", error); + b43legacy_pio_write(queue, B43legacy_PIO_RXCTL, + B43legacy_PIO_RXCTL_READY); + if (clear_buffers) { + B43legacy_WARN_ON(queue->mmio_base != B43legacy_MMIO_PIO1_BASE); + for (i = 0; i < 15; i++) { + /* Dummy read. */ + b43legacy_pio_read(queue, B43legacy_PIO_RXDATA); + } + } +} + +void b43legacy_pio_rx(struct b43legacy_pioqueue *queue) +{ + __le16 preamble[21] = { 0 }; + struct b43legacy_rxhdr_fw3 *rxhdr; + u16 tmp; + u16 len; + u16 macstat; + int i; + int preamble_readwords; + struct sk_buff *skb; + + tmp = b43legacy_pio_read(queue, B43legacy_PIO_RXCTL); + if (!(tmp & B43legacy_PIO_RXCTL_DATAAVAILABLE)) + return; + b43legacy_pio_write(queue, B43legacy_PIO_RXCTL, + B43legacy_PIO_RXCTL_DATAAVAILABLE); + + for (i = 0; i < 10; i++) { + tmp = b43legacy_pio_read(queue, B43legacy_PIO_RXCTL); + if (tmp & B43legacy_PIO_RXCTL_READY) + goto data_ready; + udelay(10); + } + b43legacydbg(queue->dev->wl, "PIO RX timed out\n"); + return; +data_ready: + + len = b43legacy_pio_read(queue, B43legacy_PIO_RXDATA); + if (unlikely(len > 0x700)) { + pio_rx_error(queue, 0, "len > 0x700"); + return; + } + if (unlikely(len == 0 && queue->mmio_base != + B43legacy_MMIO_PIO4_BASE)) { + pio_rx_error(queue, 0, "len == 0"); + return; + } + preamble[0] = cpu_to_le16(len); + if (queue->mmio_base == B43legacy_MMIO_PIO4_BASE) + preamble_readwords = 14 / sizeof(u16); + else + preamble_readwords = 18 / sizeof(u16); + for (i = 0; i < preamble_readwords; i++) { + tmp = b43legacy_pio_read(queue, B43legacy_PIO_RXDATA); + preamble[i + 1] = cpu_to_le16(tmp); + } + rxhdr = (struct b43legacy_rxhdr_fw3 *)preamble; + macstat = le16_to_cpu(rxhdr->mac_status); + if (macstat & B43legacy_RX_MAC_FCSERR) { + pio_rx_error(queue, + (queue->mmio_base == B43legacy_MMIO_PIO1_BASE), + "Frame FCS error"); + return; + } + if (queue->mmio_base == B43legacy_MMIO_PIO4_BASE) { + /* We received an xmit status. */ + struct b43legacy_hwtxstatus *hw; + + hw = (struct b43legacy_hwtxstatus *)(preamble + 1); + b43legacy_handle_hwtxstatus(queue->dev, hw); + + return; + } + + skb = dev_alloc_skb(len); + if (unlikely(!skb)) { + pio_rx_error(queue, 1, "OOM"); + return; + } + skb_put(skb, len); + for (i = 0; i < len - 1; i += 2) { + tmp = b43legacy_pio_read(queue, B43legacy_PIO_RXDATA); + *((__le16 *)(skb->data + i)) = cpu_to_le16(tmp); + } + if (len % 2) { + tmp = b43legacy_pio_read(queue, B43legacy_PIO_RXDATA); + skb->data[len - 1] = (tmp & 0x00FF); + } + b43legacy_rx(queue->dev, skb, rxhdr); +} + +void b43legacy_pio_tx_suspend(struct b43legacy_pioqueue *queue) +{ + b43legacy_power_saving_ctl_bits(queue->dev, -1, 1); + b43legacy_pio_write(queue, B43legacy_PIO_TXCTL, + b43legacy_pio_read(queue, B43legacy_PIO_TXCTL) + | B43legacy_PIO_TXCTL_SUSPEND); +} + +void b43legacy_pio_tx_resume(struct b43legacy_pioqueue *queue) +{ + b43legacy_pio_write(queue, B43legacy_PIO_TXCTL, + b43legacy_pio_read(queue, B43legacy_PIO_TXCTL) + & ~B43legacy_PIO_TXCTL_SUSPEND); + b43legacy_power_saving_ctl_bits(queue->dev, -1, -1); + tasklet_schedule(&queue->txtask); +} + +void b43legacy_pio_freeze_txqueues(struct b43legacy_wldev *dev) +{ + struct b43legacy_pio *pio; + + B43legacy_WARN_ON(!b43legacy_using_pio(dev)); + pio = &dev->pio; + pio->queue0->tx_frozen = 1; + pio->queue1->tx_frozen = 1; + pio->queue2->tx_frozen = 1; + pio->queue3->tx_frozen = 1; +} + +void b43legacy_pio_thaw_txqueues(struct b43legacy_wldev *dev) +{ + struct b43legacy_pio *pio; + + B43legacy_WARN_ON(!b43legacy_using_pio(dev)); + pio = &dev->pio; + pio->queue0->tx_frozen = 0; + pio->queue1->tx_frozen = 0; + pio->queue2->tx_frozen = 0; + pio->queue3->tx_frozen = 0; + if (!list_empty(&pio->queue0->txqueue)) + tasklet_schedule(&pio->queue0->txtask); + if (!list_empty(&pio->queue1->txqueue)) + tasklet_schedule(&pio->queue1->txtask); + if (!list_empty(&pio->queue2->txqueue)) + tasklet_schedule(&pio->queue2->txtask); + if (!list_empty(&pio->queue3->txqueue)) + tasklet_schedule(&pio->queue3->txtask); +} diff --git a/drivers/net/wireless/b43legacy/pio.h b/drivers/net/wireless/b43legacy/pio.h new file mode 100644 index 0000000..d47cbc2 --- /dev/null +++ b/drivers/net/wireless/b43legacy/pio.h @@ -0,0 +1,158 @@ +#ifndef B43legacy_PIO_H_ +#define B43legacy_PIO_H_ + +#include "b43legacy.h" + +#include +#include +#include + + +#define B43legacy_PIO_TXCTL 0x00 +#define B43legacy_PIO_TXDATA 0x02 +#define B43legacy_PIO_TXQBUFSIZE 0x04 +#define B43legacy_PIO_RXCTL 0x08 +#define B43legacy_PIO_RXDATA 0x0A + +#define B43legacy_PIO_TXCTL_WRITELO (1 << 0) +#define B43legacy_PIO_TXCTL_WRITEHI (1 << 1) +#define B43legacy_PIO_TXCTL_COMPLETE (1 << 2) +#define B43legacy_PIO_TXCTL_INIT (1 << 3) +#define B43legacy_PIO_TXCTL_SUSPEND (1 << 7) + +#define B43legacy_PIO_RXCTL_DATAAVAILABLE (1 << 0) +#define B43legacy_PIO_RXCTL_READY (1 << 1) + +/* PIO constants */ +#define B43legacy_PIO_MAXTXDEVQPACKETS 31 +#define B43legacy_PIO_TXQADJUST 80 + +/* PIO tuning knobs */ +#define B43legacy_PIO_MAXTXPACKETS 256 + + + +#ifdef CPTCFG_B43LEGACY_PIO + + +struct b43legacy_pioqueue; +struct b43legacy_xmitstatus; + +struct b43legacy_pio_txpacket { + struct b43legacy_pioqueue *queue; + struct sk_buff *skb; + struct list_head list; +}; + +#define pio_txpacket_getindex(packet) ((int)((packet) - \ + (packet)->queue->tx_packets_cache)) + +struct b43legacy_pioqueue { + struct b43legacy_wldev *dev; + u16 mmio_base; + + bool tx_suspended; + bool tx_frozen; + bool need_workarounds; /* Workarounds needed for core.rev < 3 */ + + /* Adjusted size of the device internal TX buffer. */ + u16 tx_devq_size; + /* Used octets of the device internal TX buffer. */ + u16 tx_devq_used; + /* Used packet slots in the device internal TX buffer. */ + u8 tx_devq_packets; + /* Packets from the txfree list can + * be taken on incoming TX requests. + */ + struct list_head txfree; + unsigned int nr_txfree; + /* Packets on the txqueue are queued, + * but not completely written to the chip, yet. + */ + struct list_head txqueue; + /* Packets on the txrunning queue are completely + * posted to the device. We are waiting for the txstatus. + */ + struct list_head txrunning; + struct tasklet_struct txtask; + struct b43legacy_pio_txpacket + tx_packets_cache[B43legacy_PIO_MAXTXPACKETS]; +}; + +static inline +u16 b43legacy_pio_read(struct b43legacy_pioqueue *queue, + u16 offset) +{ + return b43legacy_read16(queue->dev, queue->mmio_base + offset); +} + +static inline +void b43legacy_pio_write(struct b43legacy_pioqueue *queue, + u16 offset, u16 value) +{ + b43legacy_write16(queue->dev, queue->mmio_base + offset, value); + mmiowb(); +} + + +int b43legacy_pio_init(struct b43legacy_wldev *dev); +void b43legacy_pio_free(struct b43legacy_wldev *dev); + +int b43legacy_pio_tx(struct b43legacy_wldev *dev, + struct sk_buff *skb); +void b43legacy_pio_handle_txstatus(struct b43legacy_wldev *dev, + const struct b43legacy_txstatus *status); +void b43legacy_pio_rx(struct b43legacy_pioqueue *queue); + +/* Suspend TX queue in hardware. */ +void b43legacy_pio_tx_suspend(struct b43legacy_pioqueue *queue); +void b43legacy_pio_tx_resume(struct b43legacy_pioqueue *queue); +/* Suspend (freeze) the TX tasklet (software level). */ +void b43legacy_pio_freeze_txqueues(struct b43legacy_wldev *dev); +void b43legacy_pio_thaw_txqueues(struct b43legacy_wldev *dev); + +#else /* CPTCFG_B43LEGACY_PIO */ + +static inline +int b43legacy_pio_init(struct b43legacy_wldev *dev) +{ + return 0; +} +static inline +void b43legacy_pio_free(struct b43legacy_wldev *dev) +{ +} +static inline +int b43legacy_pio_tx(struct b43legacy_wldev *dev, + struct sk_buff *skb) +{ + return 0; +} +static inline +void b43legacy_pio_handle_txstatus(struct b43legacy_wldev *dev, + const struct b43legacy_txstatus *status) +{ +} +static inline +void b43legacy_pio_rx(struct b43legacy_pioqueue *queue) +{ +} +static inline +void b43legacy_pio_tx_suspend(struct b43legacy_pioqueue *queue) +{ +} +static inline +void b43legacy_pio_tx_resume(struct b43legacy_pioqueue *queue) +{ +} +static inline +void b43legacy_pio_freeze_txqueues(struct b43legacy_wldev *dev) +{ +} +static inline +void b43legacy_pio_thaw_txqueues(struct b43legacy_wldev *dev) +{ +} + +#endif /* CPTCFG_B43LEGACY_PIO */ +#endif /* B43legacy_PIO_H_ */ diff --git a/drivers/net/wireless/b43legacy/radio.c b/drivers/net/wireless/b43legacy/radio.c new file mode 100644 index 0000000..8961776 --- /dev/null +++ b/drivers/net/wireless/b43legacy/radio.c @@ -0,0 +1,2162 @@ +/* + + Broadcom B43legacy wireless driver + + Copyright (c) 2005 Martin Langer , + Stefano Brivio + Michael Buesch + Danny van Dyk + Andreas Jaggi + Copyright (c) 2007 Larry Finger + + Some parts of the code in this file are derived from the ipw2200 + driver Copyright(c) 2003 - 2004 Intel Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include + +#include "b43legacy.h" +#include "main.h" +#include "phy.h" +#include "radio.h" +#include "ilt.h" + + +/* Table for b43legacy_radio_calibrationvalue() */ +static const u16 rcc_table[16] = { + 0x0002, 0x0003, 0x0001, 0x000F, + 0x0006, 0x0007, 0x0005, 0x000F, + 0x000A, 0x000B, 0x0009, 0x000F, + 0x000E, 0x000F, 0x000D, 0x000F, +}; + +/* Reverse the bits of a 4bit value. + * Example: 1101 is flipped 1011 + */ +static u16 flip_4bit(u16 value) +{ + u16 flipped = 0x0000; + + B43legacy_BUG_ON(!((value & ~0x000F) == 0x0000)); + + flipped |= (value & 0x0001) << 3; + flipped |= (value & 0x0002) << 1; + flipped |= (value & 0x0004) >> 1; + flipped |= (value & 0x0008) >> 3; + + return flipped; +} + +/* Get the freq, as it has to be written to the device. */ +static inline +u16 channel2freq_bg(u8 channel) +{ + /* Frequencies are given as frequencies_bg[index] + 2.4GHz + * Starting with channel 1 + */ + static const u16 frequencies_bg[14] = { + 12, 17, 22, 27, + 32, 37, 42, 47, + 52, 57, 62, 67, + 72, 84, + }; + + if (unlikely(channel < 1 || channel > 14)) { + printk(KERN_INFO "b43legacy: Channel %d is out of range\n", + channel); + dump_stack(); + return 2412; + } + + return frequencies_bg[channel - 1]; +} + +void b43legacy_radio_lock(struct b43legacy_wldev *dev) +{ + u32 status; + + status = b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + B43legacy_WARN_ON(status & B43legacy_MACCTL_RADIOLOCK); + status |= B43legacy_MACCTL_RADIOLOCK; + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, status); + mmiowb(); + udelay(10); +} + +void b43legacy_radio_unlock(struct b43legacy_wldev *dev) +{ + u32 status; + + b43legacy_read16(dev, B43legacy_MMIO_PHY_VER); /* dummy read */ + status = b43legacy_read32(dev, B43legacy_MMIO_MACCTL); + B43legacy_WARN_ON(!(status & B43legacy_MACCTL_RADIOLOCK)); + status &= ~B43legacy_MACCTL_RADIOLOCK; + b43legacy_write32(dev, B43legacy_MMIO_MACCTL, status); + mmiowb(); +} + +u16 b43legacy_radio_read16(struct b43legacy_wldev *dev, u16 offset) +{ + struct b43legacy_phy *phy = &dev->phy; + + switch (phy->type) { + case B43legacy_PHYTYPE_B: + if (phy->radio_ver == 0x2053) { + if (offset < 0x70) + offset += 0x80; + else if (offset < 0x80) + offset += 0x70; + } else if (phy->radio_ver == 0x2050) + offset |= 0x80; + else + B43legacy_WARN_ON(1); + break; + case B43legacy_PHYTYPE_G: + offset |= 0x80; + break; + default: + B43legacy_BUG_ON(1); + } + + b43legacy_write16(dev, B43legacy_MMIO_RADIO_CONTROL, offset); + return b43legacy_read16(dev, B43legacy_MMIO_RADIO_DATA_LOW); +} + +void b43legacy_radio_write16(struct b43legacy_wldev *dev, u16 offset, u16 val) +{ + b43legacy_write16(dev, B43legacy_MMIO_RADIO_CONTROL, offset); + mmiowb(); + b43legacy_write16(dev, B43legacy_MMIO_RADIO_DATA_LOW, val); +} + +static void b43legacy_set_all_gains(struct b43legacy_wldev *dev, + s16 first, s16 second, s16 third) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 i; + u16 start = 0x08; + u16 end = 0x18; + u16 offset = 0x0400; + u16 tmp; + + if (phy->rev <= 1) { + offset = 0x5000; + start = 0x10; + end = 0x20; + } + + for (i = 0; i < 4; i++) + b43legacy_ilt_write(dev, offset + i, first); + + for (i = start; i < end; i++) + b43legacy_ilt_write(dev, offset + i, second); + + if (third != -1) { + tmp = ((u16)third << 14) | ((u16)third << 6); + b43legacy_phy_write(dev, 0x04A0, + (b43legacy_phy_read(dev, 0x04A0) & 0xBFBF) + | tmp); + b43legacy_phy_write(dev, 0x04A1, + (b43legacy_phy_read(dev, 0x04A1) & 0xBFBF) + | tmp); + b43legacy_phy_write(dev, 0x04A2, + (b43legacy_phy_read(dev, 0x04A2) & 0xBFBF) + | tmp); + } + b43legacy_dummy_transmission(dev); +} + +static void b43legacy_set_original_gains(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 i; + u16 tmp; + u16 offset = 0x0400; + u16 start = 0x0008; + u16 end = 0x0018; + + if (phy->rev <= 1) { + offset = 0x5000; + start = 0x0010; + end = 0x0020; + } + + for (i = 0; i < 4; i++) { + tmp = (i & 0xFFFC); + tmp |= (i & 0x0001) << 1; + tmp |= (i & 0x0002) >> 1; + + b43legacy_ilt_write(dev, offset + i, tmp); + } + + for (i = start; i < end; i++) + b43legacy_ilt_write(dev, offset + i, i - start); + + b43legacy_phy_write(dev, 0x04A0, + (b43legacy_phy_read(dev, 0x04A0) & 0xBFBF) + | 0x4040); + b43legacy_phy_write(dev, 0x04A1, + (b43legacy_phy_read(dev, 0x04A1) & 0xBFBF) + | 0x4040); + b43legacy_phy_write(dev, 0x04A2, + (b43legacy_phy_read(dev, 0x04A2) & 0xBFBF) + | 0x4000); + b43legacy_dummy_transmission(dev); +} + +/* Synthetic PU workaround */ +static void b43legacy_synth_pu_workaround(struct b43legacy_wldev *dev, + u8 channel) +{ + struct b43legacy_phy *phy = &dev->phy; + + might_sleep(); + + if (phy->radio_ver != 0x2050 || phy->radio_rev >= 6) + /* We do not need the workaround. */ + return; + + if (channel <= 10) + b43legacy_write16(dev, B43legacy_MMIO_CHANNEL, + channel2freq_bg(channel + 4)); + else + b43legacy_write16(dev, B43legacy_MMIO_CHANNEL, + channel2freq_bg(channel)); + msleep(1); + b43legacy_write16(dev, B43legacy_MMIO_CHANNEL, + channel2freq_bg(channel)); +} + +u8 b43legacy_radio_aci_detect(struct b43legacy_wldev *dev, u8 channel) +{ + struct b43legacy_phy *phy = &dev->phy; + u8 ret = 0; + u16 saved; + u16 rssi; + u16 temp; + int i; + int j = 0; + + saved = b43legacy_phy_read(dev, 0x0403); + b43legacy_radio_selectchannel(dev, channel, 0); + b43legacy_phy_write(dev, 0x0403, (saved & 0xFFF8) | 5); + if (phy->aci_hw_rssi) + rssi = b43legacy_phy_read(dev, 0x048A) & 0x3F; + else + rssi = saved & 0x3F; + /* clamp temp to signed 5bit */ + if (rssi > 32) + rssi -= 64; + for (i = 0; i < 100; i++) { + temp = (b43legacy_phy_read(dev, 0x047F) >> 8) & 0x3F; + if (temp > 32) + temp -= 64; + if (temp < rssi) + j++; + if (j >= 20) + ret = 1; + } + b43legacy_phy_write(dev, 0x0403, saved); + + return ret; +} + +u8 b43legacy_radio_aci_scan(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u8 ret[13]; + unsigned int channel = phy->channel; + unsigned int i; + unsigned int j; + unsigned int start; + unsigned int end; + + if (!((phy->type == B43legacy_PHYTYPE_G) && (phy->rev > 0))) + return 0; + + b43legacy_phy_lock(dev); + b43legacy_radio_lock(dev); + b43legacy_phy_write(dev, 0x0802, + b43legacy_phy_read(dev, 0x0802) & 0xFFFC); + b43legacy_phy_write(dev, B43legacy_PHY_G_CRS, + b43legacy_phy_read(dev, B43legacy_PHY_G_CRS) + & 0x7FFF); + b43legacy_set_all_gains(dev, 3, 8, 1); + + start = (channel - 5 > 0) ? channel - 5 : 1; + end = (channel + 5 < 14) ? channel + 5 : 13; + + for (i = start; i <= end; i++) { + if (abs(channel - i) > 2) + ret[i-1] = b43legacy_radio_aci_detect(dev, i); + } + b43legacy_radio_selectchannel(dev, channel, 0); + b43legacy_phy_write(dev, 0x0802, + (b43legacy_phy_read(dev, 0x0802) & 0xFFFC) + | 0x0003); + b43legacy_phy_write(dev, 0x0403, + b43legacy_phy_read(dev, 0x0403) & 0xFFF8); + b43legacy_phy_write(dev, B43legacy_PHY_G_CRS, + b43legacy_phy_read(dev, B43legacy_PHY_G_CRS) + | 0x8000); + b43legacy_set_original_gains(dev); + for (i = 0; i < 13; i++) { + if (!ret[i]) + continue; + end = (i + 5 < 13) ? i + 5 : 13; + for (j = i; j < end; j++) + ret[j] = 1; + } + b43legacy_radio_unlock(dev); + b43legacy_phy_unlock(dev); + + return ret[channel - 1]; +} + +/* http://bcm-specs.sipsolutions.net/NRSSILookupTable */ +void b43legacy_nrssi_hw_write(struct b43legacy_wldev *dev, u16 offset, s16 val) +{ + b43legacy_phy_write(dev, B43legacy_PHY_NRSSILT_CTRL, offset); + mmiowb(); + b43legacy_phy_write(dev, B43legacy_PHY_NRSSILT_DATA, (u16)val); +} + +/* http://bcm-specs.sipsolutions.net/NRSSILookupTable */ +s16 b43legacy_nrssi_hw_read(struct b43legacy_wldev *dev, u16 offset) +{ + u16 val; + + b43legacy_phy_write(dev, B43legacy_PHY_NRSSILT_CTRL, offset); + val = b43legacy_phy_read(dev, B43legacy_PHY_NRSSILT_DATA); + + return (s16)val; +} + +/* http://bcm-specs.sipsolutions.net/NRSSILookupTable */ +void b43legacy_nrssi_hw_update(struct b43legacy_wldev *dev, u16 val) +{ + u16 i; + s16 tmp; + + for (i = 0; i < 64; i++) { + tmp = b43legacy_nrssi_hw_read(dev, i); + tmp -= val; + tmp = clamp_val(tmp, -32, 31); + b43legacy_nrssi_hw_write(dev, i, tmp); + } +} + +/* http://bcm-specs.sipsolutions.net/NRSSILookupTable */ +void b43legacy_nrssi_mem_update(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + s16 i; + s16 delta; + s32 tmp; + + delta = 0x1F - phy->nrssi[0]; + for (i = 0; i < 64; i++) { + tmp = (i - delta) * phy->nrssislope; + tmp /= 0x10000; + tmp += 0x3A; + tmp = clamp_val(tmp, 0, 0x3F); + phy->nrssi_lt[i] = tmp; + } +} + +static void b43legacy_calc_nrssi_offset(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 backup[20] = { 0 }; + s16 v47F; + u16 i; + u16 saved = 0xFFFF; + + backup[0] = b43legacy_phy_read(dev, 0x0001); + backup[1] = b43legacy_phy_read(dev, 0x0811); + backup[2] = b43legacy_phy_read(dev, 0x0812); + backup[3] = b43legacy_phy_read(dev, 0x0814); + backup[4] = b43legacy_phy_read(dev, 0x0815); + backup[5] = b43legacy_phy_read(dev, 0x005A); + backup[6] = b43legacy_phy_read(dev, 0x0059); + backup[7] = b43legacy_phy_read(dev, 0x0058); + backup[8] = b43legacy_phy_read(dev, 0x000A); + backup[9] = b43legacy_phy_read(dev, 0x0003); + backup[10] = b43legacy_radio_read16(dev, 0x007A); + backup[11] = b43legacy_radio_read16(dev, 0x0043); + + b43legacy_phy_write(dev, 0x0429, + b43legacy_phy_read(dev, 0x0429) & 0x7FFF); + b43legacy_phy_write(dev, 0x0001, + (b43legacy_phy_read(dev, 0x0001) & 0x3FFF) + | 0x4000); + b43legacy_phy_write(dev, 0x0811, + b43legacy_phy_read(dev, 0x0811) | 0x000C); + b43legacy_phy_write(dev, 0x0812, + (b43legacy_phy_read(dev, 0x0812) & 0xFFF3) + | 0x0004); + b43legacy_phy_write(dev, 0x0802, + b43legacy_phy_read(dev, 0x0802) & ~(0x1 | 0x2)); + if (phy->rev >= 6) { + backup[12] = b43legacy_phy_read(dev, 0x002E); + backup[13] = b43legacy_phy_read(dev, 0x002F); + backup[14] = b43legacy_phy_read(dev, 0x080F); + backup[15] = b43legacy_phy_read(dev, 0x0810); + backup[16] = b43legacy_phy_read(dev, 0x0801); + backup[17] = b43legacy_phy_read(dev, 0x0060); + backup[18] = b43legacy_phy_read(dev, 0x0014); + backup[19] = b43legacy_phy_read(dev, 0x0478); + + b43legacy_phy_write(dev, 0x002E, 0); + b43legacy_phy_write(dev, 0x002F, 0); + b43legacy_phy_write(dev, 0x080F, 0); + b43legacy_phy_write(dev, 0x0810, 0); + b43legacy_phy_write(dev, 0x0478, + b43legacy_phy_read(dev, 0x0478) | 0x0100); + b43legacy_phy_write(dev, 0x0801, + b43legacy_phy_read(dev, 0x0801) | 0x0040); + b43legacy_phy_write(dev, 0x0060, + b43legacy_phy_read(dev, 0x0060) | 0x0040); + b43legacy_phy_write(dev, 0x0014, + b43legacy_phy_read(dev, 0x0014) | 0x0200); + } + b43legacy_radio_write16(dev, 0x007A, + b43legacy_radio_read16(dev, 0x007A) | 0x0070); + b43legacy_radio_write16(dev, 0x007A, + b43legacy_radio_read16(dev, 0x007A) | 0x0080); + udelay(30); + + v47F = (s16)((b43legacy_phy_read(dev, 0x047F) >> 8) & 0x003F); + if (v47F >= 0x20) + v47F -= 0x40; + if (v47F == 31) { + for (i = 7; i >= 4; i--) { + b43legacy_radio_write16(dev, 0x007B, i); + udelay(20); + v47F = (s16)((b43legacy_phy_read(dev, 0x047F) >> 8) + & 0x003F); + if (v47F >= 0x20) + v47F -= 0x40; + if (v47F < 31 && saved == 0xFFFF) + saved = i; + } + if (saved == 0xFFFF) + saved = 4; + } else { + b43legacy_radio_write16(dev, 0x007A, + b43legacy_radio_read16(dev, 0x007A) + & 0x007F); + b43legacy_phy_write(dev, 0x0814, + b43legacy_phy_read(dev, 0x0814) | 0x0001); + b43legacy_phy_write(dev, 0x0815, + b43legacy_phy_read(dev, 0x0815) & 0xFFFE); + b43legacy_phy_write(dev, 0x0811, + b43legacy_phy_read(dev, 0x0811) | 0x000C); + b43legacy_phy_write(dev, 0x0812, + b43legacy_phy_read(dev, 0x0812) | 0x000C); + b43legacy_phy_write(dev, 0x0811, + b43legacy_phy_read(dev, 0x0811) | 0x0030); + b43legacy_phy_write(dev, 0x0812, + b43legacy_phy_read(dev, 0x0812) | 0x0030); + b43legacy_phy_write(dev, 0x005A, 0x0480); + b43legacy_phy_write(dev, 0x0059, 0x0810); + b43legacy_phy_write(dev, 0x0058, 0x000D); + if (phy->analog == 0) + b43legacy_phy_write(dev, 0x0003, 0x0122); + else + b43legacy_phy_write(dev, 0x000A, + b43legacy_phy_read(dev, 0x000A) + | 0x2000); + b43legacy_phy_write(dev, 0x0814, + b43legacy_phy_read(dev, 0x0814) | 0x0004); + b43legacy_phy_write(dev, 0x0815, + b43legacy_phy_read(dev, 0x0815) & 0xFFFB); + b43legacy_phy_write(dev, 0x0003, + (b43legacy_phy_read(dev, 0x0003) & 0xFF9F) + | 0x0040); + b43legacy_radio_write16(dev, 0x007A, + b43legacy_radio_read16(dev, 0x007A) + | 0x000F); + b43legacy_set_all_gains(dev, 3, 0, 1); + b43legacy_radio_write16(dev, 0x0043, + (b43legacy_radio_read16(dev, 0x0043) + & 0x00F0) | 0x000F); + udelay(30); + v47F = (s16)((b43legacy_phy_read(dev, 0x047F) >> 8) & 0x003F); + if (v47F >= 0x20) + v47F -= 0x40; + if (v47F == -32) { + for (i = 0; i < 4; i++) { + b43legacy_radio_write16(dev, 0x007B, i); + udelay(20); + v47F = (s16)((b43legacy_phy_read(dev, 0x047F) >> + 8) & 0x003F); + if (v47F >= 0x20) + v47F -= 0x40; + if (v47F > -31 && saved == 0xFFFF) + saved = i; + } + if (saved == 0xFFFF) + saved = 3; + } else + saved = 0; + } + b43legacy_radio_write16(dev, 0x007B, saved); + + if (phy->rev >= 6) { + b43legacy_phy_write(dev, 0x002E, backup[12]); + b43legacy_phy_write(dev, 0x002F, backup[13]); + b43legacy_phy_write(dev, 0x080F, backup[14]); + b43legacy_phy_write(dev, 0x0810, backup[15]); + } + b43legacy_phy_write(dev, 0x0814, backup[3]); + b43legacy_phy_write(dev, 0x0815, backup[4]); + b43legacy_phy_write(dev, 0x005A, backup[5]); + b43legacy_phy_write(dev, 0x0059, backup[6]); + b43legacy_phy_write(dev, 0x0058, backup[7]); + b43legacy_phy_write(dev, 0x000A, backup[8]); + b43legacy_phy_write(dev, 0x0003, backup[9]); + b43legacy_radio_write16(dev, 0x0043, backup[11]); + b43legacy_radio_write16(dev, 0x007A, backup[10]); + b43legacy_phy_write(dev, 0x0802, + b43legacy_phy_read(dev, 0x0802) | 0x1 | 0x2); + b43legacy_phy_write(dev, 0x0429, + b43legacy_phy_read(dev, 0x0429) | 0x8000); + b43legacy_set_original_gains(dev); + if (phy->rev >= 6) { + b43legacy_phy_write(dev, 0x0801, backup[16]); + b43legacy_phy_write(dev, 0x0060, backup[17]); + b43legacy_phy_write(dev, 0x0014, backup[18]); + b43legacy_phy_write(dev, 0x0478, backup[19]); + } + b43legacy_phy_write(dev, 0x0001, backup[0]); + b43legacy_phy_write(dev, 0x0812, backup[2]); + b43legacy_phy_write(dev, 0x0811, backup[1]); +} + +void b43legacy_calc_nrssi_slope(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 backup[18] = { 0 }; + u16 tmp; + s16 nrssi0; + s16 nrssi1; + + switch (phy->type) { + case B43legacy_PHYTYPE_B: + backup[0] = b43legacy_radio_read16(dev, 0x007A); + backup[1] = b43legacy_radio_read16(dev, 0x0052); + backup[2] = b43legacy_radio_read16(dev, 0x0043); + backup[3] = b43legacy_phy_read(dev, 0x0030); + backup[4] = b43legacy_phy_read(dev, 0x0026); + backup[5] = b43legacy_phy_read(dev, 0x0015); + backup[6] = b43legacy_phy_read(dev, 0x002A); + backup[7] = b43legacy_phy_read(dev, 0x0020); + backup[8] = b43legacy_phy_read(dev, 0x005A); + backup[9] = b43legacy_phy_read(dev, 0x0059); + backup[10] = b43legacy_phy_read(dev, 0x0058); + backup[11] = b43legacy_read16(dev, 0x03E2); + backup[12] = b43legacy_read16(dev, 0x03E6); + backup[13] = b43legacy_read16(dev, B43legacy_MMIO_CHANNEL_EXT); + + tmp = b43legacy_radio_read16(dev, 0x007A); + tmp &= (phy->rev >= 5) ? 0x007F : 0x000F; + b43legacy_radio_write16(dev, 0x007A, tmp); + b43legacy_phy_write(dev, 0x0030, 0x00FF); + b43legacy_write16(dev, 0x03EC, 0x7F7F); + b43legacy_phy_write(dev, 0x0026, 0x0000); + b43legacy_phy_write(dev, 0x0015, + b43legacy_phy_read(dev, 0x0015) | 0x0020); + b43legacy_phy_write(dev, 0x002A, 0x08A3); + b43legacy_radio_write16(dev, 0x007A, + b43legacy_radio_read16(dev, 0x007A) + | 0x0080); + + nrssi0 = (s16)b43legacy_phy_read(dev, 0x0027); + b43legacy_radio_write16(dev, 0x007A, + b43legacy_radio_read16(dev, 0x007A) + & 0x007F); + if (phy->analog >= 2) + b43legacy_write16(dev, 0x03E6, 0x0040); + else if (phy->analog == 0) + b43legacy_write16(dev, 0x03E6, 0x0122); + else + b43legacy_write16(dev, B43legacy_MMIO_CHANNEL_EXT, + b43legacy_read16(dev, + B43legacy_MMIO_CHANNEL_EXT) & 0x2000); + b43legacy_phy_write(dev, 0x0020, 0x3F3F); + b43legacy_phy_write(dev, 0x0015, 0xF330); + b43legacy_radio_write16(dev, 0x005A, 0x0060); + b43legacy_radio_write16(dev, 0x0043, + b43legacy_radio_read16(dev, 0x0043) + & 0x00F0); + b43legacy_phy_write(dev, 0x005A, 0x0480); + b43legacy_phy_write(dev, 0x0059, 0x0810); + b43legacy_phy_write(dev, 0x0058, 0x000D); + udelay(20); + + nrssi1 = (s16)b43legacy_phy_read(dev, 0x0027); + b43legacy_phy_write(dev, 0x0030, backup[3]); + b43legacy_radio_write16(dev, 0x007A, backup[0]); + b43legacy_write16(dev, 0x03E2, backup[11]); + b43legacy_phy_write(dev, 0x0026, backup[4]); + b43legacy_phy_write(dev, 0x0015, backup[5]); + b43legacy_phy_write(dev, 0x002A, backup[6]); + b43legacy_synth_pu_workaround(dev, phy->channel); + if (phy->analog != 0) + b43legacy_write16(dev, 0x03F4, backup[13]); + + b43legacy_phy_write(dev, 0x0020, backup[7]); + b43legacy_phy_write(dev, 0x005A, backup[8]); + b43legacy_phy_write(dev, 0x0059, backup[9]); + b43legacy_phy_write(dev, 0x0058, backup[10]); + b43legacy_radio_write16(dev, 0x0052, backup[1]); + b43legacy_radio_write16(dev, 0x0043, backup[2]); + + if (nrssi0 == nrssi1) + phy->nrssislope = 0x00010000; + else + phy->nrssislope = 0x00400000 / (nrssi0 - nrssi1); + + if (nrssi0 <= -4) { + phy->nrssi[0] = nrssi0; + phy->nrssi[1] = nrssi1; + } + break; + case B43legacy_PHYTYPE_G: + if (phy->radio_rev >= 9) + return; + if (phy->radio_rev == 8) + b43legacy_calc_nrssi_offset(dev); + + b43legacy_phy_write(dev, B43legacy_PHY_G_CRS, + b43legacy_phy_read(dev, B43legacy_PHY_G_CRS) + & 0x7FFF); + b43legacy_phy_write(dev, 0x0802, + b43legacy_phy_read(dev, 0x0802) & 0xFFFC); + backup[7] = b43legacy_read16(dev, 0x03E2); + b43legacy_write16(dev, 0x03E2, + b43legacy_read16(dev, 0x03E2) | 0x8000); + backup[0] = b43legacy_radio_read16(dev, 0x007A); + backup[1] = b43legacy_radio_read16(dev, 0x0052); + backup[2] = b43legacy_radio_read16(dev, 0x0043); + backup[3] = b43legacy_phy_read(dev, 0x0015); + backup[4] = b43legacy_phy_read(dev, 0x005A); + backup[5] = b43legacy_phy_read(dev, 0x0059); + backup[6] = b43legacy_phy_read(dev, 0x0058); + backup[8] = b43legacy_read16(dev, 0x03E6); + backup[9] = b43legacy_read16(dev, B43legacy_MMIO_CHANNEL_EXT); + if (phy->rev >= 3) { + backup[10] = b43legacy_phy_read(dev, 0x002E); + backup[11] = b43legacy_phy_read(dev, 0x002F); + backup[12] = b43legacy_phy_read(dev, 0x080F); + backup[13] = b43legacy_phy_read(dev, + B43legacy_PHY_G_LO_CONTROL); + backup[14] = b43legacy_phy_read(dev, 0x0801); + backup[15] = b43legacy_phy_read(dev, 0x0060); + backup[16] = b43legacy_phy_read(dev, 0x0014); + backup[17] = b43legacy_phy_read(dev, 0x0478); + b43legacy_phy_write(dev, 0x002E, 0); + b43legacy_phy_write(dev, B43legacy_PHY_G_LO_CONTROL, 0); + switch (phy->rev) { + case 4: case 6: case 7: + b43legacy_phy_write(dev, 0x0478, + b43legacy_phy_read(dev, + 0x0478) | 0x0100); + b43legacy_phy_write(dev, 0x0801, + b43legacy_phy_read(dev, + 0x0801) | 0x0040); + break; + case 3: case 5: + b43legacy_phy_write(dev, 0x0801, + b43legacy_phy_read(dev, + 0x0801) & 0xFFBF); + break; + } + b43legacy_phy_write(dev, 0x0060, + b43legacy_phy_read(dev, 0x0060) + | 0x0040); + b43legacy_phy_write(dev, 0x0014, + b43legacy_phy_read(dev, 0x0014) + | 0x0200); + } + b43legacy_radio_write16(dev, 0x007A, + b43legacy_radio_read16(dev, 0x007A) + | 0x0070); + b43legacy_set_all_gains(dev, 0, 8, 0); + b43legacy_radio_write16(dev, 0x007A, + b43legacy_radio_read16(dev, 0x007A) + & 0x00F7); + if (phy->rev >= 2) { + b43legacy_phy_write(dev, 0x0811, + (b43legacy_phy_read(dev, 0x0811) + & 0xFFCF) | 0x0030); + b43legacy_phy_write(dev, 0x0812, + (b43legacy_phy_read(dev, 0x0812) + & 0xFFCF) | 0x0010); + } + b43legacy_radio_write16(dev, 0x007A, + b43legacy_radio_read16(dev, 0x007A) + | 0x0080); + udelay(20); + + nrssi0 = (s16)((b43legacy_phy_read(dev, 0x047F) >> 8) & 0x003F); + if (nrssi0 >= 0x0020) + nrssi0 -= 0x0040; + + b43legacy_radio_write16(dev, 0x007A, + b43legacy_radio_read16(dev, 0x007A) + & 0x007F); + if (phy->analog >= 2) + b43legacy_phy_write(dev, 0x0003, + (b43legacy_phy_read(dev, 0x0003) + & 0xFF9F) | 0x0040); + + b43legacy_write16(dev, B43legacy_MMIO_CHANNEL_EXT, + b43legacy_read16(dev, + B43legacy_MMIO_CHANNEL_EXT) | 0x2000); + b43legacy_radio_write16(dev, 0x007A, + b43legacy_radio_read16(dev, 0x007A) + | 0x000F); + b43legacy_phy_write(dev, 0x0015, 0xF330); + if (phy->rev >= 2) { + b43legacy_phy_write(dev, 0x0812, + (b43legacy_phy_read(dev, 0x0812) + & 0xFFCF) | 0x0020); + b43legacy_phy_write(dev, 0x0811, + (b43legacy_phy_read(dev, 0x0811) + & 0xFFCF) | 0x0020); + } + + b43legacy_set_all_gains(dev, 3, 0, 1); + if (phy->radio_rev == 8) + b43legacy_radio_write16(dev, 0x0043, 0x001F); + else { + tmp = b43legacy_radio_read16(dev, 0x0052) & 0xFF0F; + b43legacy_radio_write16(dev, 0x0052, tmp | 0x0060); + tmp = b43legacy_radio_read16(dev, 0x0043) & 0xFFF0; + b43legacy_radio_write16(dev, 0x0043, tmp | 0x0009); + } + b43legacy_phy_write(dev, 0x005A, 0x0480); + b43legacy_phy_write(dev, 0x0059, 0x0810); + b43legacy_phy_write(dev, 0x0058, 0x000D); + udelay(20); + nrssi1 = (s16)((b43legacy_phy_read(dev, 0x047F) >> 8) & 0x003F); + if (nrssi1 >= 0x0020) + nrssi1 -= 0x0040; + if (nrssi0 == nrssi1) + phy->nrssislope = 0x00010000; + else + phy->nrssislope = 0x00400000 / (nrssi0 - nrssi1); + if (nrssi0 >= -4) { + phy->nrssi[0] = nrssi1; + phy->nrssi[1] = nrssi0; + } + if (phy->rev >= 3) { + b43legacy_phy_write(dev, 0x002E, backup[10]); + b43legacy_phy_write(dev, 0x002F, backup[11]); + b43legacy_phy_write(dev, 0x080F, backup[12]); + b43legacy_phy_write(dev, B43legacy_PHY_G_LO_CONTROL, + backup[13]); + } + if (phy->rev >= 2) { + b43legacy_phy_write(dev, 0x0812, + b43legacy_phy_read(dev, 0x0812) + & 0xFFCF); + b43legacy_phy_write(dev, 0x0811, + b43legacy_phy_read(dev, 0x0811) + & 0xFFCF); + } + + b43legacy_radio_write16(dev, 0x007A, backup[0]); + b43legacy_radio_write16(dev, 0x0052, backup[1]); + b43legacy_radio_write16(dev, 0x0043, backup[2]); + b43legacy_write16(dev, 0x03E2, backup[7]); + b43legacy_write16(dev, 0x03E6, backup[8]); + b43legacy_write16(dev, B43legacy_MMIO_CHANNEL_EXT, backup[9]); + b43legacy_phy_write(dev, 0x0015, backup[3]); + b43legacy_phy_write(dev, 0x005A, backup[4]); + b43legacy_phy_write(dev, 0x0059, backup[5]); + b43legacy_phy_write(dev, 0x0058, backup[6]); + b43legacy_synth_pu_workaround(dev, phy->channel); + b43legacy_phy_write(dev, 0x0802, + b43legacy_phy_read(dev, 0x0802) | 0x0003); + b43legacy_set_original_gains(dev); + b43legacy_phy_write(dev, B43legacy_PHY_G_CRS, + b43legacy_phy_read(dev, B43legacy_PHY_G_CRS) + | 0x8000); + if (phy->rev >= 3) { + b43legacy_phy_write(dev, 0x0801, backup[14]); + b43legacy_phy_write(dev, 0x0060, backup[15]); + b43legacy_phy_write(dev, 0x0014, backup[16]); + b43legacy_phy_write(dev, 0x0478, backup[17]); + } + b43legacy_nrssi_mem_update(dev); + b43legacy_calc_nrssi_threshold(dev); + break; + default: + B43legacy_BUG_ON(1); + } +} + +void b43legacy_calc_nrssi_threshold(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + s32 threshold; + s32 a; + s32 b; + s16 tmp16; + u16 tmp_u16; + + switch (phy->type) { + case B43legacy_PHYTYPE_B: { + if (phy->radio_ver != 0x2050) + return; + if (!(dev->dev->bus->sprom.boardflags_lo & + B43legacy_BFL_RSSI)) + return; + + if (phy->radio_rev >= 6) { + threshold = (phy->nrssi[1] - phy->nrssi[0]) * 32; + threshold += 20 * (phy->nrssi[0] + 1); + threshold /= 40; + } else + threshold = phy->nrssi[1] - 5; + + threshold = clamp_val(threshold, 0, 0x3E); + b43legacy_phy_read(dev, 0x0020); /* dummy read */ + b43legacy_phy_write(dev, 0x0020, (((u16)threshold) << 8) + | 0x001C); + + if (phy->radio_rev >= 6) { + b43legacy_phy_write(dev, 0x0087, 0x0E0D); + b43legacy_phy_write(dev, 0x0086, 0x0C0B); + b43legacy_phy_write(dev, 0x0085, 0x0A09); + b43legacy_phy_write(dev, 0x0084, 0x0808); + b43legacy_phy_write(dev, 0x0083, 0x0808); + b43legacy_phy_write(dev, 0x0082, 0x0604); + b43legacy_phy_write(dev, 0x0081, 0x0302); + b43legacy_phy_write(dev, 0x0080, 0x0100); + } + break; + } + case B43legacy_PHYTYPE_G: + if (!phy->gmode || + !(dev->dev->bus->sprom.boardflags_lo & + B43legacy_BFL_RSSI)) { + tmp16 = b43legacy_nrssi_hw_read(dev, 0x20); + if (tmp16 >= 0x20) + tmp16 -= 0x40; + if (tmp16 < 3) + b43legacy_phy_write(dev, 0x048A, + (b43legacy_phy_read(dev, + 0x048A) & 0xF000) | 0x09EB); + else + b43legacy_phy_write(dev, 0x048A, + (b43legacy_phy_read(dev, + 0x048A) & 0xF000) | 0x0AED); + } else { + if (phy->interfmode == + B43legacy_RADIO_INTERFMODE_NONWLAN) { + a = 0xE; + b = 0xA; + } else if (!phy->aci_wlan_automatic && + phy->aci_enable) { + a = 0x13; + b = 0x12; + } else { + a = 0xE; + b = 0x11; + } + + a = a * (phy->nrssi[1] - phy->nrssi[0]); + a += (phy->nrssi[0] << 6); + if (a < 32) + a += 31; + else + a += 32; + a = a >> 6; + a = clamp_val(a, -31, 31); + + b = b * (phy->nrssi[1] - phy->nrssi[0]); + b += (phy->nrssi[0] << 6); + if (b < 32) + b += 31; + else + b += 32; + b = b >> 6; + b = clamp_val(b, -31, 31); + + tmp_u16 = b43legacy_phy_read(dev, 0x048A) & 0xF000; + tmp_u16 |= ((u32)b & 0x0000003F); + tmp_u16 |= (((u32)a & 0x0000003F) << 6); + b43legacy_phy_write(dev, 0x048A, tmp_u16); + } + break; + default: + B43legacy_BUG_ON(1); + } +} + +/* Stack implementation to save/restore values from the + * interference mitigation code. + * It is save to restore values in random order. + */ +static void _stack_save(u32 *_stackptr, size_t *stackidx, + u8 id, u16 offset, u16 value) +{ + u32 *stackptr = &(_stackptr[*stackidx]); + + B43legacy_WARN_ON(!((offset & 0xE000) == 0x0000)); + B43legacy_WARN_ON(!((id & 0xF8) == 0x00)); + *stackptr = offset; + *stackptr |= ((u32)id) << 13; + *stackptr |= ((u32)value) << 16; + (*stackidx)++; + B43legacy_WARN_ON(!(*stackidx < B43legacy_INTERFSTACK_SIZE)); +} + +static u16 _stack_restore(u32 *stackptr, + u8 id, u16 offset) +{ + size_t i; + + B43legacy_WARN_ON(!((offset & 0xE000) == 0x0000)); + B43legacy_WARN_ON(!((id & 0xF8) == 0x00)); + for (i = 0; i < B43legacy_INTERFSTACK_SIZE; i++, stackptr++) { + if ((*stackptr & 0x00001FFF) != offset) + continue; + if (((*stackptr & 0x00007000) >> 13) != id) + continue; + return ((*stackptr & 0xFFFF0000) >> 16); + } + B43legacy_BUG_ON(1); + + return 0; +} + +#define phy_stacksave(offset) \ + do { \ + _stack_save(stack, &stackidx, 0x1, (offset), \ + b43legacy_phy_read(dev, (offset))); \ + } while (0) +#define phy_stackrestore(offset) \ + do { \ + b43legacy_phy_write(dev, (offset), \ + _stack_restore(stack, 0x1, \ + (offset))); \ + } while (0) +#define radio_stacksave(offset) \ + do { \ + _stack_save(stack, &stackidx, 0x2, (offset), \ + b43legacy_radio_read16(dev, (offset))); \ + } while (0) +#define radio_stackrestore(offset) \ + do { \ + b43legacy_radio_write16(dev, (offset), \ + _stack_restore(stack, 0x2, \ + (offset))); \ + } while (0) +#define ilt_stacksave(offset) \ + do { \ + _stack_save(stack, &stackidx, 0x3, (offset), \ + b43legacy_ilt_read(dev, (offset))); \ + } while (0) +#define ilt_stackrestore(offset) \ + do { \ + b43legacy_ilt_write(dev, (offset), \ + _stack_restore(stack, 0x3, \ + (offset))); \ + } while (0) + +static void +b43legacy_radio_interference_mitigation_enable(struct b43legacy_wldev *dev, + int mode) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 tmp; + u16 flipped; + u32 tmp32; + size_t stackidx = 0; + u32 *stack = phy->interfstack; + + switch (mode) { + case B43legacy_RADIO_INTERFMODE_NONWLAN: + if (phy->rev != 1) { + b43legacy_phy_write(dev, 0x042B, + b43legacy_phy_read(dev, 0x042B) + | 0x0800); + b43legacy_phy_write(dev, B43legacy_PHY_G_CRS, + b43legacy_phy_read(dev, + B43legacy_PHY_G_CRS) & ~0x4000); + break; + } + radio_stacksave(0x0078); + tmp = (b43legacy_radio_read16(dev, 0x0078) & 0x001E); + flipped = flip_4bit(tmp); + if (flipped < 10 && flipped >= 8) + flipped = 7; + else if (flipped >= 10) + flipped -= 3; + flipped = flip_4bit(flipped); + flipped = (flipped << 1) | 0x0020; + b43legacy_radio_write16(dev, 0x0078, flipped); + + b43legacy_calc_nrssi_threshold(dev); + + phy_stacksave(0x0406); + b43legacy_phy_write(dev, 0x0406, 0x7E28); + + b43legacy_phy_write(dev, 0x042B, + b43legacy_phy_read(dev, 0x042B) | 0x0800); + b43legacy_phy_write(dev, B43legacy_PHY_RADIO_BITFIELD, + b43legacy_phy_read(dev, + B43legacy_PHY_RADIO_BITFIELD) | 0x1000); + + phy_stacksave(0x04A0); + b43legacy_phy_write(dev, 0x04A0, + (b43legacy_phy_read(dev, 0x04A0) & 0xC0C0) + | 0x0008); + phy_stacksave(0x04A1); + b43legacy_phy_write(dev, 0x04A1, + (b43legacy_phy_read(dev, 0x04A1) & 0xC0C0) + | 0x0605); + phy_stacksave(0x04A2); + b43legacy_phy_write(dev, 0x04A2, + (b43legacy_phy_read(dev, 0x04A2) & 0xC0C0) + | 0x0204); + phy_stacksave(0x04A8); + b43legacy_phy_write(dev, 0x04A8, + (b43legacy_phy_read(dev, 0x04A8) & 0xC0C0) + | 0x0803); + phy_stacksave(0x04AB); + b43legacy_phy_write(dev, 0x04AB, + (b43legacy_phy_read(dev, 0x04AB) & 0xC0C0) + | 0x0605); + + phy_stacksave(0x04A7); + b43legacy_phy_write(dev, 0x04A7, 0x0002); + phy_stacksave(0x04A3); + b43legacy_phy_write(dev, 0x04A3, 0x287A); + phy_stacksave(0x04A9); + b43legacy_phy_write(dev, 0x04A9, 0x2027); + phy_stacksave(0x0493); + b43legacy_phy_write(dev, 0x0493, 0x32F5); + phy_stacksave(0x04AA); + b43legacy_phy_write(dev, 0x04AA, 0x2027); + phy_stacksave(0x04AC); + b43legacy_phy_write(dev, 0x04AC, 0x32F5); + break; + case B43legacy_RADIO_INTERFMODE_MANUALWLAN: + if (b43legacy_phy_read(dev, 0x0033) & 0x0800) + break; + + phy->aci_enable = true; + + phy_stacksave(B43legacy_PHY_RADIO_BITFIELD); + phy_stacksave(B43legacy_PHY_G_CRS); + if (phy->rev < 2) + phy_stacksave(0x0406); + else { + phy_stacksave(0x04C0); + phy_stacksave(0x04C1); + } + phy_stacksave(0x0033); + phy_stacksave(0x04A7); + phy_stacksave(0x04A3); + phy_stacksave(0x04A9); + phy_stacksave(0x04AA); + phy_stacksave(0x04AC); + phy_stacksave(0x0493); + phy_stacksave(0x04A1); + phy_stacksave(0x04A0); + phy_stacksave(0x04A2); + phy_stacksave(0x048A); + phy_stacksave(0x04A8); + phy_stacksave(0x04AB); + if (phy->rev == 2) { + phy_stacksave(0x04AD); + phy_stacksave(0x04AE); + } else if (phy->rev >= 3) { + phy_stacksave(0x04AD); + phy_stacksave(0x0415); + phy_stacksave(0x0416); + phy_stacksave(0x0417); + ilt_stacksave(0x1A00 + 0x2); + ilt_stacksave(0x1A00 + 0x3); + } + phy_stacksave(0x042B); + phy_stacksave(0x048C); + + b43legacy_phy_write(dev, B43legacy_PHY_RADIO_BITFIELD, + b43legacy_phy_read(dev, + B43legacy_PHY_RADIO_BITFIELD) & ~0x1000); + b43legacy_phy_write(dev, B43legacy_PHY_G_CRS, + (b43legacy_phy_read(dev, + B43legacy_PHY_G_CRS) + & 0xFFFC) | 0x0002); + + b43legacy_phy_write(dev, 0x0033, 0x0800); + b43legacy_phy_write(dev, 0x04A3, 0x2027); + b43legacy_phy_write(dev, 0x04A9, 0x1CA8); + b43legacy_phy_write(dev, 0x0493, 0x287A); + b43legacy_phy_write(dev, 0x04AA, 0x1CA8); + b43legacy_phy_write(dev, 0x04AC, 0x287A); + + b43legacy_phy_write(dev, 0x04A0, + (b43legacy_phy_read(dev, 0x04A0) + & 0xFFC0) | 0x001A); + b43legacy_phy_write(dev, 0x04A7, 0x000D); + + if (phy->rev < 2) + b43legacy_phy_write(dev, 0x0406, 0xFF0D); + else if (phy->rev == 2) { + b43legacy_phy_write(dev, 0x04C0, 0xFFFF); + b43legacy_phy_write(dev, 0x04C1, 0x00A9); + } else { + b43legacy_phy_write(dev, 0x04C0, 0x00C1); + b43legacy_phy_write(dev, 0x04C1, 0x0059); + } + + b43legacy_phy_write(dev, 0x04A1, + (b43legacy_phy_read(dev, 0x04A1) + & 0xC0FF) | 0x1800); + b43legacy_phy_write(dev, 0x04A1, + (b43legacy_phy_read(dev, 0x04A1) + & 0xFFC0) | 0x0015); + b43legacy_phy_write(dev, 0x04A8, + (b43legacy_phy_read(dev, 0x04A8) + & 0xCFFF) | 0x1000); + b43legacy_phy_write(dev, 0x04A8, + (b43legacy_phy_read(dev, 0x04A8) + & 0xF0FF) | 0x0A00); + b43legacy_phy_write(dev, 0x04AB, + (b43legacy_phy_read(dev, 0x04AB) + & 0xCFFF) | 0x1000); + b43legacy_phy_write(dev, 0x04AB, + (b43legacy_phy_read(dev, 0x04AB) + & 0xF0FF) | 0x0800); + b43legacy_phy_write(dev, 0x04AB, + (b43legacy_phy_read(dev, 0x04AB) + & 0xFFCF) | 0x0010); + b43legacy_phy_write(dev, 0x04AB, + (b43legacy_phy_read(dev, 0x04AB) + & 0xFFF0) | 0x0005); + b43legacy_phy_write(dev, 0x04A8, + (b43legacy_phy_read(dev, 0x04A8) + & 0xFFCF) | 0x0010); + b43legacy_phy_write(dev, 0x04A8, + (b43legacy_phy_read(dev, 0x04A8) + & 0xFFF0) | 0x0006); + b43legacy_phy_write(dev, 0x04A2, + (b43legacy_phy_read(dev, 0x04A2) + & 0xF0FF) | 0x0800); + b43legacy_phy_write(dev, 0x04A0, + (b43legacy_phy_read(dev, 0x04A0) + & 0xF0FF) | 0x0500); + b43legacy_phy_write(dev, 0x04A2, + (b43legacy_phy_read(dev, 0x04A2) + & 0xFFF0) | 0x000B); + + if (phy->rev >= 3) { + b43legacy_phy_write(dev, 0x048A, + b43legacy_phy_read(dev, 0x048A) + & ~0x8000); + b43legacy_phy_write(dev, 0x0415, + (b43legacy_phy_read(dev, 0x0415) + & 0x8000) | 0x36D8); + b43legacy_phy_write(dev, 0x0416, + (b43legacy_phy_read(dev, 0x0416) + & 0x8000) | 0x36D8); + b43legacy_phy_write(dev, 0x0417, + (b43legacy_phy_read(dev, 0x0417) + & 0xFE00) | 0x016D); + } else { + b43legacy_phy_write(dev, 0x048A, + b43legacy_phy_read(dev, 0x048A) + | 0x1000); + b43legacy_phy_write(dev, 0x048A, + (b43legacy_phy_read(dev, 0x048A) + & 0x9FFF) | 0x2000); + tmp32 = b43legacy_shm_read32(dev, B43legacy_SHM_SHARED, + B43legacy_UCODEFLAGS_OFFSET); + if (!(tmp32 & 0x800)) { + tmp32 |= 0x800; + b43legacy_shm_write32(dev, B43legacy_SHM_SHARED, + B43legacy_UCODEFLAGS_OFFSET, + tmp32); + } + } + if (phy->rev >= 2) + b43legacy_phy_write(dev, 0x042B, + b43legacy_phy_read(dev, 0x042B) + | 0x0800); + b43legacy_phy_write(dev, 0x048C, + (b43legacy_phy_read(dev, 0x048C) + & 0xF0FF) | 0x0200); + if (phy->rev == 2) { + b43legacy_phy_write(dev, 0x04AE, + (b43legacy_phy_read(dev, 0x04AE) + & 0xFF00) | 0x007F); + b43legacy_phy_write(dev, 0x04AD, + (b43legacy_phy_read(dev, 0x04AD) + & 0x00FF) | 0x1300); + } else if (phy->rev >= 6) { + b43legacy_ilt_write(dev, 0x1A00 + 0x3, 0x007F); + b43legacy_ilt_write(dev, 0x1A00 + 0x2, 0x007F); + b43legacy_phy_write(dev, 0x04AD, + b43legacy_phy_read(dev, 0x04AD) + & 0x00FF); + } + b43legacy_calc_nrssi_slope(dev); + break; + default: + B43legacy_BUG_ON(1); + } +} + +static void +b43legacy_radio_interference_mitigation_disable(struct b43legacy_wldev *dev, + int mode) +{ + struct b43legacy_phy *phy = &dev->phy; + u32 tmp32; + u32 *stack = phy->interfstack; + + switch (mode) { + case B43legacy_RADIO_INTERFMODE_NONWLAN: + if (phy->rev != 1) { + b43legacy_phy_write(dev, 0x042B, + b43legacy_phy_read(dev, 0x042B) + & ~0x0800); + b43legacy_phy_write(dev, B43legacy_PHY_G_CRS, + b43legacy_phy_read(dev, + B43legacy_PHY_G_CRS) | 0x4000); + break; + } + phy_stackrestore(0x0078); + b43legacy_calc_nrssi_threshold(dev); + phy_stackrestore(0x0406); + b43legacy_phy_write(dev, 0x042B, + b43legacy_phy_read(dev, 0x042B) & ~0x0800); + if (!dev->bad_frames_preempt) + b43legacy_phy_write(dev, B43legacy_PHY_RADIO_BITFIELD, + b43legacy_phy_read(dev, + B43legacy_PHY_RADIO_BITFIELD) + & ~(1 << 11)); + b43legacy_phy_write(dev, B43legacy_PHY_G_CRS, + b43legacy_phy_read(dev, B43legacy_PHY_G_CRS) + | 0x4000); + phy_stackrestore(0x04A0); + phy_stackrestore(0x04A1); + phy_stackrestore(0x04A2); + phy_stackrestore(0x04A8); + phy_stackrestore(0x04AB); + phy_stackrestore(0x04A7); + phy_stackrestore(0x04A3); + phy_stackrestore(0x04A9); + phy_stackrestore(0x0493); + phy_stackrestore(0x04AA); + phy_stackrestore(0x04AC); + break; + case B43legacy_RADIO_INTERFMODE_MANUALWLAN: + if (!(b43legacy_phy_read(dev, 0x0033) & 0x0800)) + break; + + phy->aci_enable = false; + + phy_stackrestore(B43legacy_PHY_RADIO_BITFIELD); + phy_stackrestore(B43legacy_PHY_G_CRS); + phy_stackrestore(0x0033); + phy_stackrestore(0x04A3); + phy_stackrestore(0x04A9); + phy_stackrestore(0x0493); + phy_stackrestore(0x04AA); + phy_stackrestore(0x04AC); + phy_stackrestore(0x04A0); + phy_stackrestore(0x04A7); + if (phy->rev >= 2) { + phy_stackrestore(0x04C0); + phy_stackrestore(0x04C1); + } else + phy_stackrestore(0x0406); + phy_stackrestore(0x04A1); + phy_stackrestore(0x04AB); + phy_stackrestore(0x04A8); + if (phy->rev == 2) { + phy_stackrestore(0x04AD); + phy_stackrestore(0x04AE); + } else if (phy->rev >= 3) { + phy_stackrestore(0x04AD); + phy_stackrestore(0x0415); + phy_stackrestore(0x0416); + phy_stackrestore(0x0417); + ilt_stackrestore(0x1A00 + 0x2); + ilt_stackrestore(0x1A00 + 0x3); + } + phy_stackrestore(0x04A2); + phy_stackrestore(0x04A8); + phy_stackrestore(0x042B); + phy_stackrestore(0x048C); + tmp32 = b43legacy_shm_read32(dev, B43legacy_SHM_SHARED, + B43legacy_UCODEFLAGS_OFFSET); + if (tmp32 & 0x800) { + tmp32 &= ~0x800; + b43legacy_shm_write32(dev, B43legacy_SHM_SHARED, + B43legacy_UCODEFLAGS_OFFSET, + tmp32); + } + b43legacy_calc_nrssi_slope(dev); + break; + default: + B43legacy_BUG_ON(1); + } +} + +#undef phy_stacksave +#undef phy_stackrestore +#undef radio_stacksave +#undef radio_stackrestore +#undef ilt_stacksave +#undef ilt_stackrestore + +int b43legacy_radio_set_interference_mitigation(struct b43legacy_wldev *dev, + int mode) +{ + struct b43legacy_phy *phy = &dev->phy; + int currentmode; + + if ((phy->type != B43legacy_PHYTYPE_G) || + (phy->rev == 0) || (!phy->gmode)) + return -ENODEV; + + phy->aci_wlan_automatic = false; + switch (mode) { + case B43legacy_RADIO_INTERFMODE_AUTOWLAN: + phy->aci_wlan_automatic = true; + if (phy->aci_enable) + mode = B43legacy_RADIO_INTERFMODE_MANUALWLAN; + else + mode = B43legacy_RADIO_INTERFMODE_NONE; + break; + case B43legacy_RADIO_INTERFMODE_NONE: + case B43legacy_RADIO_INTERFMODE_NONWLAN: + case B43legacy_RADIO_INTERFMODE_MANUALWLAN: + break; + default: + return -EINVAL; + } + + currentmode = phy->interfmode; + if (currentmode == mode) + return 0; + if (currentmode != B43legacy_RADIO_INTERFMODE_NONE) + b43legacy_radio_interference_mitigation_disable(dev, + currentmode); + + if (mode == B43legacy_RADIO_INTERFMODE_NONE) { + phy->aci_enable = false; + phy->aci_hw_rssi = false; + } else + b43legacy_radio_interference_mitigation_enable(dev, mode); + phy->interfmode = mode; + + return 0; +} + +u16 b43legacy_radio_calibrationvalue(struct b43legacy_wldev *dev) +{ + u16 reg; + u16 index; + u16 ret; + + reg = b43legacy_radio_read16(dev, 0x0060); + index = (reg & 0x001E) >> 1; + ret = rcc_table[index] << 1; + ret |= (reg & 0x0001); + ret |= 0x0020; + + return ret; +} + +#define LPD(L, P, D) (((L) << 2) | ((P) << 1) | ((D) << 0)) +static u16 b43legacy_get_812_value(struct b43legacy_wldev *dev, u8 lpd) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 loop_or = 0; + u16 adj_loopback_gain = phy->loopback_gain[0]; + u8 loop; + u16 extern_lna_control; + + if (!phy->gmode) + return 0; + if (!has_loopback_gain(phy)) { + if (phy->rev < 7 || !(dev->dev->bus->sprom.boardflags_lo + & B43legacy_BFL_EXTLNA)) { + switch (lpd) { + case LPD(0, 1, 1): + return 0x0FB2; + case LPD(0, 0, 1): + return 0x00B2; + case LPD(1, 0, 1): + return 0x30B2; + case LPD(1, 0, 0): + return 0x30B3; + default: + B43legacy_BUG_ON(1); + } + } else { + switch (lpd) { + case LPD(0, 1, 1): + return 0x8FB2; + case LPD(0, 0, 1): + return 0x80B2; + case LPD(1, 0, 1): + return 0x20B2; + case LPD(1, 0, 0): + return 0x20B3; + default: + B43legacy_BUG_ON(1); + } + } + } else { + if (phy->radio_rev == 8) + adj_loopback_gain += 0x003E; + else + adj_loopback_gain += 0x0026; + if (adj_loopback_gain >= 0x46) { + adj_loopback_gain -= 0x46; + extern_lna_control = 0x3000; + } else if (adj_loopback_gain >= 0x3A) { + adj_loopback_gain -= 0x3A; + extern_lna_control = 0x2000; + } else if (adj_loopback_gain >= 0x2E) { + adj_loopback_gain -= 0x2E; + extern_lna_control = 0x1000; + } else { + adj_loopback_gain -= 0x10; + extern_lna_control = 0x0000; + } + for (loop = 0; loop < 16; loop++) { + u16 tmp = adj_loopback_gain - 6 * loop; + if (tmp < 6) + break; + } + + loop_or = (loop << 8) | extern_lna_control; + if (phy->rev >= 7 && dev->dev->bus->sprom.boardflags_lo + & B43legacy_BFL_EXTLNA) { + if (extern_lna_control) + loop_or |= 0x8000; + switch (lpd) { + case LPD(0, 1, 1): + return 0x8F92; + case LPD(0, 0, 1): + return (0x8092 | loop_or); + case LPD(1, 0, 1): + return (0x2092 | loop_or); + case LPD(1, 0, 0): + return (0x2093 | loop_or); + default: + B43legacy_BUG_ON(1); + } + } else { + switch (lpd) { + case LPD(0, 1, 1): + return 0x0F92; + case LPD(0, 0, 1): + case LPD(1, 0, 1): + return (0x0092 | loop_or); + case LPD(1, 0, 0): + return (0x0093 | loop_or); + default: + B43legacy_BUG_ON(1); + } + } + } + return 0; +} + +u16 b43legacy_radio_init2050(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 backup[21] = { 0 }; + u16 ret; + u16 i; + u16 j; + u32 tmp1 = 0; + u32 tmp2 = 0; + + backup[0] = b43legacy_radio_read16(dev, 0x0043); + backup[14] = b43legacy_radio_read16(dev, 0x0051); + backup[15] = b43legacy_radio_read16(dev, 0x0052); + backup[1] = b43legacy_phy_read(dev, 0x0015); + backup[16] = b43legacy_phy_read(dev, 0x005A); + backup[17] = b43legacy_phy_read(dev, 0x0059); + backup[18] = b43legacy_phy_read(dev, 0x0058); + if (phy->type == B43legacy_PHYTYPE_B) { + backup[2] = b43legacy_phy_read(dev, 0x0030); + backup[3] = b43legacy_read16(dev, 0x03EC); + b43legacy_phy_write(dev, 0x0030, 0x00FF); + b43legacy_write16(dev, 0x03EC, 0x3F3F); + } else { + if (phy->gmode) { + backup[4] = b43legacy_phy_read(dev, 0x0811); + backup[5] = b43legacy_phy_read(dev, 0x0812); + backup[6] = b43legacy_phy_read(dev, 0x0814); + backup[7] = b43legacy_phy_read(dev, 0x0815); + backup[8] = b43legacy_phy_read(dev, + B43legacy_PHY_G_CRS); + backup[9] = b43legacy_phy_read(dev, 0x0802); + b43legacy_phy_write(dev, 0x0814, + (b43legacy_phy_read(dev, 0x0814) + | 0x0003)); + b43legacy_phy_write(dev, 0x0815, + (b43legacy_phy_read(dev, 0x0815) + & 0xFFFC)); + b43legacy_phy_write(dev, B43legacy_PHY_G_CRS, + (b43legacy_phy_read(dev, + B43legacy_PHY_G_CRS) & 0x7FFF)); + b43legacy_phy_write(dev, 0x0802, + (b43legacy_phy_read(dev, 0x0802) + & 0xFFFC)); + if (phy->rev > 1) { /* loopback gain enabled */ + backup[19] = b43legacy_phy_read(dev, 0x080F); + backup[20] = b43legacy_phy_read(dev, 0x0810); + if (phy->rev >= 3) + b43legacy_phy_write(dev, 0x080F, + 0xC020); + else + b43legacy_phy_write(dev, 0x080F, + 0x8020); + b43legacy_phy_write(dev, 0x0810, 0x0000); + } + b43legacy_phy_write(dev, 0x0812, + b43legacy_get_812_value(dev, + LPD(0, 1, 1))); + if (phy->rev < 7 || + !(dev->dev->bus->sprom.boardflags_lo + & B43legacy_BFL_EXTLNA)) + b43legacy_phy_write(dev, 0x0811, 0x01B3); + else + b43legacy_phy_write(dev, 0x0811, 0x09B3); + } + } + b43legacy_write16(dev, B43legacy_MMIO_PHY_RADIO, + (b43legacy_read16(dev, B43legacy_MMIO_PHY_RADIO) + | 0x8000)); + backup[10] = b43legacy_phy_read(dev, 0x0035); + b43legacy_phy_write(dev, 0x0035, + (b43legacy_phy_read(dev, 0x0035) & 0xFF7F)); + backup[11] = b43legacy_read16(dev, 0x03E6); + backup[12] = b43legacy_read16(dev, B43legacy_MMIO_CHANNEL_EXT); + + /* Initialization */ + if (phy->analog == 0) + b43legacy_write16(dev, 0x03E6, 0x0122); + else { + if (phy->analog >= 2) + b43legacy_phy_write(dev, 0x0003, + (b43legacy_phy_read(dev, 0x0003) + & 0xFFBF) | 0x0040); + b43legacy_write16(dev, B43legacy_MMIO_CHANNEL_EXT, + (b43legacy_read16(dev, + B43legacy_MMIO_CHANNEL_EXT) | 0x2000)); + } + + ret = b43legacy_radio_calibrationvalue(dev); + + if (phy->type == B43legacy_PHYTYPE_B) + b43legacy_radio_write16(dev, 0x0078, 0x0026); + + if (phy->gmode) + b43legacy_phy_write(dev, 0x0812, + b43legacy_get_812_value(dev, + LPD(0, 1, 1))); + b43legacy_phy_write(dev, 0x0015, 0xBFAF); + b43legacy_phy_write(dev, 0x002B, 0x1403); + if (phy->gmode) + b43legacy_phy_write(dev, 0x0812, + b43legacy_get_812_value(dev, + LPD(0, 0, 1))); + b43legacy_phy_write(dev, 0x0015, 0xBFA0); + b43legacy_radio_write16(dev, 0x0051, + (b43legacy_radio_read16(dev, 0x0051) + | 0x0004)); + if (phy->radio_rev == 8) + b43legacy_radio_write16(dev, 0x0043, 0x001F); + else { + b43legacy_radio_write16(dev, 0x0052, 0x0000); + b43legacy_radio_write16(dev, 0x0043, + (b43legacy_radio_read16(dev, 0x0043) + & 0xFFF0) | 0x0009); + } + b43legacy_phy_write(dev, 0x0058, 0x0000); + + for (i = 0; i < 16; i++) { + b43legacy_phy_write(dev, 0x005A, 0x0480); + b43legacy_phy_write(dev, 0x0059, 0xC810); + b43legacy_phy_write(dev, 0x0058, 0x000D); + if (phy->gmode) + b43legacy_phy_write(dev, 0x0812, + b43legacy_get_812_value(dev, + LPD(1, 0, 1))); + b43legacy_phy_write(dev, 0x0015, 0xAFB0); + udelay(10); + if (phy->gmode) + b43legacy_phy_write(dev, 0x0812, + b43legacy_get_812_value(dev, + LPD(1, 0, 1))); + b43legacy_phy_write(dev, 0x0015, 0xEFB0); + udelay(10); + if (phy->gmode) + b43legacy_phy_write(dev, 0x0812, + b43legacy_get_812_value(dev, + LPD(1, 0, 0))); + b43legacy_phy_write(dev, 0x0015, 0xFFF0); + udelay(20); + tmp1 += b43legacy_phy_read(dev, 0x002D); + b43legacy_phy_write(dev, 0x0058, 0x0000); + if (phy->gmode) + b43legacy_phy_write(dev, 0x0812, + b43legacy_get_812_value(dev, + LPD(1, 0, 1))); + b43legacy_phy_write(dev, 0x0015, 0xAFB0); + } + + tmp1++; + tmp1 >>= 9; + udelay(10); + b43legacy_phy_write(dev, 0x0058, 0x0000); + + for (i = 0; i < 16; i++) { + b43legacy_radio_write16(dev, 0x0078, (flip_4bit(i) << 1) + | 0x0020); + backup[13] = b43legacy_radio_read16(dev, 0x0078); + udelay(10); + for (j = 0; j < 16; j++) { + b43legacy_phy_write(dev, 0x005A, 0x0D80); + b43legacy_phy_write(dev, 0x0059, 0xC810); + b43legacy_phy_write(dev, 0x0058, 0x000D); + if (phy->gmode) + b43legacy_phy_write(dev, 0x0812, + b43legacy_get_812_value(dev, + LPD(1, 0, 1))); + b43legacy_phy_write(dev, 0x0015, 0xAFB0); + udelay(10); + if (phy->gmode) + b43legacy_phy_write(dev, 0x0812, + b43legacy_get_812_value(dev, + LPD(1, 0, 1))); + b43legacy_phy_write(dev, 0x0015, 0xEFB0); + udelay(10); + if (phy->gmode) + b43legacy_phy_write(dev, 0x0812, + b43legacy_get_812_value(dev, + LPD(1, 0, 0))); + b43legacy_phy_write(dev, 0x0015, 0xFFF0); + udelay(10); + tmp2 += b43legacy_phy_read(dev, 0x002D); + b43legacy_phy_write(dev, 0x0058, 0x0000); + if (phy->gmode) + b43legacy_phy_write(dev, 0x0812, + b43legacy_get_812_value(dev, + LPD(1, 0, 1))); + b43legacy_phy_write(dev, 0x0015, 0xAFB0); + } + tmp2++; + tmp2 >>= 8; + if (tmp1 < tmp2) + break; + } + + /* Restore the registers */ + b43legacy_phy_write(dev, 0x0015, backup[1]); + b43legacy_radio_write16(dev, 0x0051, backup[14]); + b43legacy_radio_write16(dev, 0x0052, backup[15]); + b43legacy_radio_write16(dev, 0x0043, backup[0]); + b43legacy_phy_write(dev, 0x005A, backup[16]); + b43legacy_phy_write(dev, 0x0059, backup[17]); + b43legacy_phy_write(dev, 0x0058, backup[18]); + b43legacy_write16(dev, 0x03E6, backup[11]); + if (phy->analog != 0) + b43legacy_write16(dev, B43legacy_MMIO_CHANNEL_EXT, backup[12]); + b43legacy_phy_write(dev, 0x0035, backup[10]); + b43legacy_radio_selectchannel(dev, phy->channel, 1); + if (phy->type == B43legacy_PHYTYPE_B) { + b43legacy_phy_write(dev, 0x0030, backup[2]); + b43legacy_write16(dev, 0x03EC, backup[3]); + } else { + if (phy->gmode) { + b43legacy_write16(dev, B43legacy_MMIO_PHY_RADIO, + (b43legacy_read16(dev, + B43legacy_MMIO_PHY_RADIO) & 0x7FFF)); + b43legacy_phy_write(dev, 0x0811, backup[4]); + b43legacy_phy_write(dev, 0x0812, backup[5]); + b43legacy_phy_write(dev, 0x0814, backup[6]); + b43legacy_phy_write(dev, 0x0815, backup[7]); + b43legacy_phy_write(dev, B43legacy_PHY_G_CRS, + backup[8]); + b43legacy_phy_write(dev, 0x0802, backup[9]); + if (phy->rev > 1) { + b43legacy_phy_write(dev, 0x080F, backup[19]); + b43legacy_phy_write(dev, 0x0810, backup[20]); + } + } + } + if (i >= 15) + ret = backup[13]; + + return ret; +} + +static inline +u16 freq_r3A_value(u16 frequency) +{ + u16 value; + + if (frequency < 5091) + value = 0x0040; + else if (frequency < 5321) + value = 0x0000; + else if (frequency < 5806) + value = 0x0080; + else + value = 0x0040; + + return value; +} + +void b43legacy_radio_set_tx_iq(struct b43legacy_wldev *dev) +{ + static const u8 data_high[5] = { 0x00, 0x40, 0x80, 0x90, 0xD0 }; + static const u8 data_low[5] = { 0x00, 0x01, 0x05, 0x06, 0x0A }; + u16 tmp = b43legacy_radio_read16(dev, 0x001E); + int i; + int j; + + for (i = 0; i < 5; i++) { + for (j = 0; j < 5; j++) { + if (tmp == (data_high[i] | data_low[j])) { + b43legacy_phy_write(dev, 0x0069, (i - j) << 8 | + 0x00C0); + return; + } + } + } +} + +int b43legacy_radio_selectchannel(struct b43legacy_wldev *dev, + u8 channel, + int synthetic_pu_workaround) +{ + struct b43legacy_phy *phy = &dev->phy; + + if (channel == 0xFF) { + switch (phy->type) { + case B43legacy_PHYTYPE_B: + case B43legacy_PHYTYPE_G: + channel = B43legacy_RADIO_DEFAULT_CHANNEL_BG; + break; + default: + B43legacy_WARN_ON(1); + } + } + +/* TODO: Check if channel is valid - return -EINVAL if not */ + if (synthetic_pu_workaround) + b43legacy_synth_pu_workaround(dev, channel); + + b43legacy_write16(dev, B43legacy_MMIO_CHANNEL, + channel2freq_bg(channel)); + + if (channel == 14) { + if (dev->dev->bus->sprom.country_code == 5) /* JAPAN) */ + b43legacy_shm_write32(dev, B43legacy_SHM_SHARED, + B43legacy_UCODEFLAGS_OFFSET, + b43legacy_shm_read32(dev, + B43legacy_SHM_SHARED, + B43legacy_UCODEFLAGS_OFFSET) + & ~(1 << 7)); + else + b43legacy_shm_write32(dev, B43legacy_SHM_SHARED, + B43legacy_UCODEFLAGS_OFFSET, + b43legacy_shm_read32(dev, + B43legacy_SHM_SHARED, + B43legacy_UCODEFLAGS_OFFSET) + | (1 << 7)); + b43legacy_write16(dev, B43legacy_MMIO_CHANNEL_EXT, + b43legacy_read16(dev, + B43legacy_MMIO_CHANNEL_EXT) | (1 << 11)); + } else + b43legacy_write16(dev, B43legacy_MMIO_CHANNEL_EXT, + b43legacy_read16(dev, + B43legacy_MMIO_CHANNEL_EXT) & 0xF7BF); + + phy->channel = channel; + /*XXX: Using the longer of 2 timeouts (8000 vs 2000 usecs). Specs states + * that 2000 usecs might suffice. */ + msleep(8); + + return 0; +} + +void b43legacy_radio_set_txantenna(struct b43legacy_wldev *dev, u32 val) +{ + u16 tmp; + + val <<= 8; + tmp = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, 0x0022) & 0xFCFF; + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, 0x0022, tmp | val); + tmp = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, 0x03A8) & 0xFCFF; + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, 0x03A8, tmp | val); + tmp = b43legacy_shm_read16(dev, B43legacy_SHM_SHARED, 0x0054) & 0xFCFF; + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, 0x0054, tmp | val); +} + +/* http://bcm-specs.sipsolutions.net/TX_Gain_Base_Band */ +static u16 b43legacy_get_txgain_base_band(u16 txpower) +{ + u16 ret; + + B43legacy_WARN_ON(txpower > 63); + + if (txpower >= 54) + ret = 2; + else if (txpower >= 49) + ret = 4; + else if (txpower >= 44) + ret = 5; + else + ret = 6; + + return ret; +} + +/* http://bcm-specs.sipsolutions.net/TX_Gain_Radio_Frequency_Power_Amplifier */ +static u16 b43legacy_get_txgain_freq_power_amp(u16 txpower) +{ + u16 ret; + + B43legacy_WARN_ON(txpower > 63); + + if (txpower >= 32) + ret = 0; + else if (txpower >= 25) + ret = 1; + else if (txpower >= 20) + ret = 2; + else if (txpower >= 12) + ret = 3; + else + ret = 4; + + return ret; +} + +/* http://bcm-specs.sipsolutions.net/TX_Gain_Digital_Analog_Converter */ +static u16 b43legacy_get_txgain_dac(u16 txpower) +{ + u16 ret; + + B43legacy_WARN_ON(txpower > 63); + + if (txpower >= 54) + ret = txpower - 53; + else if (txpower >= 49) + ret = txpower - 42; + else if (txpower >= 44) + ret = txpower - 37; + else if (txpower >= 32) + ret = txpower - 32; + else if (txpower >= 25) + ret = txpower - 20; + else if (txpower >= 20) + ret = txpower - 13; + else if (txpower >= 12) + ret = txpower - 8; + else + ret = txpower; + + return ret; +} + +void b43legacy_radio_set_txpower_a(struct b43legacy_wldev *dev, u16 txpower) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 pamp; + u16 base; + u16 dac; + u16 ilt; + + txpower = clamp_val(txpower, 0, 63); + + pamp = b43legacy_get_txgain_freq_power_amp(txpower); + pamp <<= 5; + pamp &= 0x00E0; + b43legacy_phy_write(dev, 0x0019, pamp); + + base = b43legacy_get_txgain_base_band(txpower); + base &= 0x000F; + b43legacy_phy_write(dev, 0x0017, base | 0x0020); + + ilt = b43legacy_ilt_read(dev, 0x3001); + ilt &= 0x0007; + + dac = b43legacy_get_txgain_dac(txpower); + dac <<= 3; + dac |= ilt; + + b43legacy_ilt_write(dev, 0x3001, dac); + + phy->txpwr_offset = txpower; + + /* TODO: FuncPlaceholder (Adjust BB loft cancel) */ +} + +void b43legacy_radio_set_txpower_bg(struct b43legacy_wldev *dev, + u16 baseband_attenuation, + u16 radio_attenuation, + u16 txpower) +{ + struct b43legacy_phy *phy = &dev->phy; + + if (baseband_attenuation == 0xFFFF) + baseband_attenuation = phy->bbatt; + if (radio_attenuation == 0xFFFF) + radio_attenuation = phy->rfatt; + if (txpower == 0xFFFF) + txpower = phy->txctl1; + phy->bbatt = baseband_attenuation; + phy->rfatt = radio_attenuation; + phy->txctl1 = txpower; + + B43legacy_WARN_ON(baseband_attenuation > 11); + if (phy->radio_rev < 6) + B43legacy_WARN_ON(radio_attenuation > 9); + else + B43legacy_WARN_ON(radio_attenuation > 31); + B43legacy_WARN_ON(txpower > 7); + + b43legacy_phy_set_baseband_attenuation(dev, baseband_attenuation); + b43legacy_radio_write16(dev, 0x0043, radio_attenuation); + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, 0x0064, + radio_attenuation); + if (phy->radio_ver == 0x2050) + b43legacy_radio_write16(dev, 0x0052, + (b43legacy_radio_read16(dev, 0x0052) + & ~0x0070) | ((txpower << 4) & 0x0070)); + /* FIXME: The spec is very weird and unclear here. */ + if (phy->type == B43legacy_PHYTYPE_G) + b43legacy_phy_lo_adjust(dev, 0); +} + +u16 b43legacy_default_baseband_attenuation(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + + if (phy->radio_ver == 0x2050 && phy->radio_rev < 6) + return 0; + return 2; +} + +u16 b43legacy_default_radio_attenuation(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + u16 att = 0xFFFF; + + switch (phy->radio_ver) { + case 0x2053: + switch (phy->radio_rev) { + case 1: + att = 6; + break; + } + break; + case 0x2050: + switch (phy->radio_rev) { + case 0: + att = 5; + break; + case 1: + if (phy->type == B43legacy_PHYTYPE_G) { + if (is_bcm_board_vendor(dev) && + dev->dev->bus->boardinfo.type == 0x421 && + dev->dev->bus->sprom.board_rev >= 30) + att = 3; + else if (is_bcm_board_vendor(dev) && + dev->dev->bus->boardinfo.type == 0x416) + att = 3; + else + att = 1; + } else { + if (is_bcm_board_vendor(dev) && + dev->dev->bus->boardinfo.type == 0x421 && + dev->dev->bus->sprom.board_rev >= 30) + att = 7; + else + att = 6; + } + break; + case 2: + if (phy->type == B43legacy_PHYTYPE_G) { + if (is_bcm_board_vendor(dev) && + dev->dev->bus->boardinfo.type == 0x421 && + dev->dev->bus->sprom.board_rev >= 30) + att = 3; + else if (is_bcm_board_vendor(dev) && + dev->dev->bus->boardinfo.type == + 0x416) + att = 5; + else if (dev->dev->bus->chip_id == 0x4320) + att = 4; + else + att = 3; + } else + att = 6; + break; + case 3: + att = 5; + break; + case 4: + case 5: + att = 1; + break; + case 6: + case 7: + att = 5; + break; + case 8: + att = 0x1A; + break; + case 9: + default: + att = 5; + } + } + if (is_bcm_board_vendor(dev) && + dev->dev->bus->boardinfo.type == 0x421) { + if (dev->dev->bus->sprom.board_rev < 0x43) + att = 2; + else if (dev->dev->bus->sprom.board_rev < 0x51) + att = 3; + } + if (att == 0xFFFF) + att = 5; + + return att; +} + +u16 b43legacy_default_txctl1(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + + if (phy->radio_ver != 0x2050) + return 0; + if (phy->radio_rev == 1) + return 3; + if (phy->radio_rev < 6) + return 2; + if (phy->radio_rev == 8) + return 1; + return 0; +} + +void b43legacy_radio_turn_on(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + int err; + u8 channel; + + might_sleep(); + + if (phy->radio_on) + return; + + switch (phy->type) { + case B43legacy_PHYTYPE_B: + case B43legacy_PHYTYPE_G: + b43legacy_phy_write(dev, 0x0015, 0x8000); + b43legacy_phy_write(dev, 0x0015, 0xCC00); + b43legacy_phy_write(dev, 0x0015, + (phy->gmode ? 0x00C0 : 0x0000)); + if (phy->radio_off_context.valid) { + /* Restore the RFover values. */ + b43legacy_phy_write(dev, B43legacy_PHY_RFOVER, + phy->radio_off_context.rfover); + b43legacy_phy_write(dev, B43legacy_PHY_RFOVERVAL, + phy->radio_off_context.rfoverval); + phy->radio_off_context.valid = false; + } + channel = phy->channel; + err = b43legacy_radio_selectchannel(dev, + B43legacy_RADIO_DEFAULT_CHANNEL_BG, 1); + err |= b43legacy_radio_selectchannel(dev, channel, 0); + B43legacy_WARN_ON(err); + break; + default: + B43legacy_BUG_ON(1); + } + phy->radio_on = true; +} + +void b43legacy_radio_turn_off(struct b43legacy_wldev *dev, bool force) +{ + struct b43legacy_phy *phy = &dev->phy; + + if (!phy->radio_on && !force) + return; + + if (phy->type == B43legacy_PHYTYPE_G && dev->dev->id.revision >= 5) { + u16 rfover, rfoverval; + + rfover = b43legacy_phy_read(dev, B43legacy_PHY_RFOVER); + rfoverval = b43legacy_phy_read(dev, B43legacy_PHY_RFOVERVAL); + if (!force) { + phy->radio_off_context.rfover = rfover; + phy->radio_off_context.rfoverval = rfoverval; + phy->radio_off_context.valid = true; + } + b43legacy_phy_write(dev, B43legacy_PHY_RFOVER, rfover | 0x008C); + b43legacy_phy_write(dev, B43legacy_PHY_RFOVERVAL, + rfoverval & 0xFF73); + } else + b43legacy_phy_write(dev, 0x0015, 0xAA00); + phy->radio_on = false; + b43legacydbg(dev->wl, "Radio initialized\n"); +} + +void b43legacy_radio_clear_tssi(struct b43legacy_wldev *dev) +{ + struct b43legacy_phy *phy = &dev->phy; + + switch (phy->type) { + case B43legacy_PHYTYPE_B: + case B43legacy_PHYTYPE_G: + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, 0x0058, + 0x7F7F); + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, 0x005a, + 0x7F7F); + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, 0x0070, + 0x7F7F); + b43legacy_shm_write16(dev, B43legacy_SHM_SHARED, 0x0072, + 0x7F7F); + break; + } +} diff --git a/drivers/net/wireless/b43legacy/radio.h b/drivers/net/wireless/b43legacy/radio.h new file mode 100644 index 0000000..bccb3d7 --- /dev/null +++ b/drivers/net/wireless/b43legacy/radio.h @@ -0,0 +1,98 @@ +/* + + Broadcom B43legacy wireless driver + + Copyright (c) 2005 Martin Langer , + Stefano Brivio + Michael Buesch + Danny van Dyk + Andreas Jaggi + + Some parts of the code in this file are derived from the ipw2200 + driver Copyright(c) 2003 - 2004 Intel Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#ifndef B43legacy_RADIO_H_ +#define B43legacy_RADIO_H_ + +#include "b43legacy.h" + + +#define B43legacy_RADIO_DEFAULT_CHANNEL_BG 6 + +/* Force antenna 0. */ +#define B43legacy_RADIO_TXANTENNA_0 0 +/* Force antenna 1. */ +#define B43legacy_RADIO_TXANTENNA_1 1 +/* Use the RX antenna, that was selected for the most recently + * received good PLCP header. + */ +#define B43legacy_RADIO_TXANTENNA_LASTPLCP 3 +#define B43legacy_RADIO_TXANTENNA_DEFAULT B43legacy_RADIO_TXANTENNA_LASTPLCP + +#define B43legacy_RADIO_INTERFMODE_NONE 0 +#define B43legacy_RADIO_INTERFMODE_NONWLAN 1 +#define B43legacy_RADIO_INTERFMODE_MANUALWLAN 2 +#define B43legacy_RADIO_INTERFMODE_AUTOWLAN 3 + + +void b43legacy_radio_lock(struct b43legacy_wldev *dev); +void b43legacy_radio_unlock(struct b43legacy_wldev *dev); + +u16 b43legacy_radio_read16(struct b43legacy_wldev *dev, u16 offset); +void b43legacy_radio_write16(struct b43legacy_wldev *dev, u16 offset, u16 val); + +u16 b43legacy_radio_init2050(struct b43legacy_wldev *dev); + +void b43legacy_radio_turn_on(struct b43legacy_wldev *dev); +void b43legacy_radio_turn_off(struct b43legacy_wldev *dev, bool force); + +int b43legacy_radio_selectchannel(struct b43legacy_wldev *dev, u8 channel, + int synthetic_pu_workaround); + +void b43legacy_radio_set_txpower_a(struct b43legacy_wldev *dev, u16 txpower); +void b43legacy_radio_set_txpower_bg(struct b43legacy_wldev *dev, + u16 baseband_attenuation, u16 attenuation, + u16 txpower); + +u16 b43legacy_default_baseband_attenuation(struct b43legacy_wldev *dev); +u16 b43legacy_default_radio_attenuation(struct b43legacy_wldev *dev); +u16 b43legacy_default_txctl1(struct b43legacy_wldev *dev); + +void b43legacy_radio_set_txantenna(struct b43legacy_wldev *dev, u32 val); + +void b43legacy_radio_clear_tssi(struct b43legacy_wldev *dev); + +u8 b43legacy_radio_aci_detect(struct b43legacy_wldev *dev, u8 channel); +u8 b43legacy_radio_aci_scan(struct b43legacy_wldev *dev); + +int b43legacy_radio_set_interference_mitigation(struct b43legacy_wldev *dev, + int mode); + +void b43legacy_calc_nrssi_slope(struct b43legacy_wldev *dev); +void b43legacy_calc_nrssi_threshold(struct b43legacy_wldev *dev); +s16 b43legacy_nrssi_hw_read(struct b43legacy_wldev *dev, u16 offset); +void b43legacy_nrssi_hw_write(struct b43legacy_wldev *dev, u16 offset, s16 val); +void b43legacy_nrssi_hw_update(struct b43legacy_wldev *dev, u16 val); +void b43legacy_nrssi_mem_update(struct b43legacy_wldev *dev); + +void b43legacy_radio_set_tx_iq(struct b43legacy_wldev *dev); +u16 b43legacy_radio_calibrationvalue(struct b43legacy_wldev *dev); + +#endif /* B43legacy_RADIO_H_ */ diff --git a/drivers/net/wireless/b43legacy/rfkill.c b/drivers/net/wireless/b43legacy/rfkill.c new file mode 100644 index 0000000..c4559bc --- /dev/null +++ b/drivers/net/wireless/b43legacy/rfkill.c @@ -0,0 +1,91 @@ +/* + + Broadcom B43 wireless driver + RFKILL support + + Copyright (c) 2007 Michael Buesch + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "radio.h" +#include "b43legacy.h" + + +/* Returns TRUE, if the radio is enabled in hardware. */ +bool b43legacy_is_hw_radio_enabled(struct b43legacy_wldev *dev) +{ + if (dev->dev->id.revision >= 3) { + if (!(b43legacy_read32(dev, B43legacy_MMIO_RADIO_HWENABLED_HI) + & B43legacy_MMIO_RADIO_HWENABLED_HI_MASK)) + return 1; + } else { + /* To prevent CPU fault on PPC, do not read a register + * unless the interface is started; however, on resume + * for hibernation, this routine is entered early. When + * that happens, unconditionally return TRUE. + */ + if (b43legacy_status(dev) < B43legacy_STAT_STARTED) + return 1; + if (b43legacy_read16(dev, B43legacy_MMIO_RADIO_HWENABLED_LO) + & B43legacy_MMIO_RADIO_HWENABLED_LO_MASK) + return 1; + } + return 0; +} + +/* The poll callback for the hardware button. */ +void b43legacy_rfkill_poll(struct ieee80211_hw *hw) +{ + struct b43legacy_wl *wl = hw_to_b43legacy_wl(hw); + struct b43legacy_wldev *dev = wl->current_dev; + struct ssb_bus *bus = dev->dev->bus; + bool enabled; + bool brought_up = false; + + mutex_lock(&wl->mutex); + if (unlikely(b43legacy_status(dev) < B43legacy_STAT_INITIALIZED)) { + if (ssb_bus_powerup(bus, 0)) { + mutex_unlock(&wl->mutex); + return; + } + ssb_device_enable(dev->dev, 0); + brought_up = true; + } + + enabled = b43legacy_is_hw_radio_enabled(dev); + + if (unlikely(enabled != dev->radio_hw_enable)) { + dev->radio_hw_enable = enabled; + b43legacyinfo(wl, "Radio hardware status changed to %s\n", + enabled ? "ENABLED" : "DISABLED"); + wiphy_rfkill_set_hw_state(hw->wiphy, !enabled); + if (enabled != dev->phy.radio_on) { + if (enabled) + b43legacy_radio_turn_on(dev); + else + b43legacy_radio_turn_off(dev, 0); + } + } + + if (brought_up) { + ssb_device_disable(dev->dev, 0); + ssb_bus_may_powerdown(bus); + } + + mutex_unlock(&wl->mutex); +} diff --git a/drivers/net/wireless/b43legacy/rfkill.h b/drivers/net/wireless/b43legacy/rfkill.h new file mode 100644 index 0000000..7558557 --- /dev/null +++ b/drivers/net/wireless/b43legacy/rfkill.h @@ -0,0 +1,11 @@ +#ifndef B43legacy_RFKILL_H_ +#define B43legacy_RFKILL_H_ + +struct ieee80211_hw; +struct b43legacy_wldev; + +void b43legacy_rfkill_poll(struct ieee80211_hw *hw); + +bool b43legacy_is_hw_radio_enabled(struct b43legacy_wldev *dev); + +#endif /* B43legacy_RFKILL_H_ */ diff --git a/drivers/net/wireless/b43legacy/sysfs.c b/drivers/net/wireless/b43legacy/sysfs.c new file mode 100644 index 0000000..57f8b08 --- /dev/null +++ b/drivers/net/wireless/b43legacy/sysfs.c @@ -0,0 +1,238 @@ +/* + + Broadcom B43legacy wireless driver + + SYSFS support routines + + Copyright (c) 2006 Michael Buesch + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include "sysfs.h" +#include "b43legacy.h" +#include "main.h" +#include "phy.h" +#include "radio.h" + +#include + + +#define GENERIC_FILESIZE 64 + + +static int get_integer(const char *buf, size_t count) +{ + char tmp[10 + 1] = { 0 }; + int ret = -EINVAL; + + if (count == 0) + goto out; + count = min(count, (size_t)10); + memcpy(tmp, buf, count); + ret = simple_strtol(tmp, NULL, 10); +out: + return ret; +} + +static int get_boolean(const char *buf, size_t count) +{ + if (count != 0) { + if (buf[0] == '1') + return 1; + if (buf[0] == '0') + return 0; + if (count >= 4 && memcmp(buf, "true", 4) == 0) + return 1; + if (count >= 5 && memcmp(buf, "false", 5) == 0) + return 0; + if (count >= 3 && memcmp(buf, "yes", 3) == 0) + return 1; + if (count >= 2 && memcmp(buf, "no", 2) == 0) + return 0; + if (count >= 2 && memcmp(buf, "on", 2) == 0) + return 1; + if (count >= 3 && memcmp(buf, "off", 3) == 0) + return 0; + } + return -EINVAL; +} + +static ssize_t b43legacy_attr_interfmode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct b43legacy_wldev *wldev = dev_to_b43legacy_wldev(dev); + ssize_t count = 0; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + mutex_lock(&wldev->wl->mutex); + + switch (wldev->phy.interfmode) { + case B43legacy_INTERFMODE_NONE: + count = snprintf(buf, PAGE_SIZE, "0 (No Interference" + " Mitigation)\n"); + break; + case B43legacy_INTERFMODE_NONWLAN: + count = snprintf(buf, PAGE_SIZE, "1 (Non-WLAN Interference" + " Mitigation)\n"); + break; + case B43legacy_INTERFMODE_MANUALWLAN: + count = snprintf(buf, PAGE_SIZE, "2 (WLAN Interference" + " Mitigation)\n"); + break; + default: + B43legacy_WARN_ON(1); + } + + mutex_unlock(&wldev->wl->mutex); + + return count; +} + +static ssize_t b43legacy_attr_interfmode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct b43legacy_wldev *wldev = dev_to_b43legacy_wldev(dev); + unsigned long flags; + int err; + int mode; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + mode = get_integer(buf, count); + switch (mode) { + case 0: + mode = B43legacy_INTERFMODE_NONE; + break; + case 1: + mode = B43legacy_INTERFMODE_NONWLAN; + break; + case 2: + mode = B43legacy_INTERFMODE_MANUALWLAN; + break; + case 3: + mode = B43legacy_INTERFMODE_AUTOWLAN; + break; + default: + return -EINVAL; + } + + mutex_lock(&wldev->wl->mutex); + spin_lock_irqsave(&wldev->wl->irq_lock, flags); + + err = b43legacy_radio_set_interference_mitigation(wldev, mode); + if (err) + b43legacyerr(wldev->wl, "Interference Mitigation not " + "supported by device\n"); + mmiowb(); + spin_unlock_irqrestore(&wldev->wl->irq_lock, flags); + mutex_unlock(&wldev->wl->mutex); + + return err ? err : count; +} + +static DEVICE_ATTR(interference, 0644, + b43legacy_attr_interfmode_show, + b43legacy_attr_interfmode_store); + +static ssize_t b43legacy_attr_preamble_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct b43legacy_wldev *wldev = dev_to_b43legacy_wldev(dev); + ssize_t count; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + mutex_lock(&wldev->wl->mutex); + + if (wldev->short_preamble) + count = snprintf(buf, PAGE_SIZE, "1 (Short Preamble" + " enabled)\n"); + else + count = snprintf(buf, PAGE_SIZE, "0 (Short Preamble" + " disabled)\n"); + + mutex_unlock(&wldev->wl->mutex); + + return count; +} + +static ssize_t b43legacy_attr_preamble_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct b43legacy_wldev *wldev = dev_to_b43legacy_wldev(dev); + unsigned long flags; + int value; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + value = get_boolean(buf, count); + if (value < 0) + return value; + mutex_lock(&wldev->wl->mutex); + spin_lock_irqsave(&wldev->wl->irq_lock, flags); + + wldev->short_preamble = !!value; + + spin_unlock_irqrestore(&wldev->wl->irq_lock, flags); + mutex_unlock(&wldev->wl->mutex); + + return count; +} + +static DEVICE_ATTR(shortpreamble, 0644, + b43legacy_attr_preamble_show, + b43legacy_attr_preamble_store); + +int b43legacy_sysfs_register(struct b43legacy_wldev *wldev) +{ + struct device *dev = wldev->dev->dev; + int err; + + B43legacy_WARN_ON(b43legacy_status(wldev) != + B43legacy_STAT_INITIALIZED); + + err = device_create_file(dev, &dev_attr_interference); + if (err) + goto out; + err = device_create_file(dev, &dev_attr_shortpreamble); + if (err) + goto err_remove_interfmode; + +out: + return err; +err_remove_interfmode: + device_remove_file(dev, &dev_attr_interference); + goto out; +} + +void b43legacy_sysfs_unregister(struct b43legacy_wldev *wldev) +{ + struct device *dev = wldev->dev->dev; + + device_remove_file(dev, &dev_attr_shortpreamble); + device_remove_file(dev, &dev_attr_interference); +} diff --git a/drivers/net/wireless/b43legacy/sysfs.h b/drivers/net/wireless/b43legacy/sysfs.h new file mode 100644 index 0000000..417d509 --- /dev/null +++ b/drivers/net/wireless/b43legacy/sysfs.h @@ -0,0 +1,9 @@ +#ifndef B43legacy_SYSFS_H_ +#define B43legacy_SYSFS_H_ + +struct b43legacy_wldev; + +int b43legacy_sysfs_register(struct b43legacy_wldev *dev); +void b43legacy_sysfs_unregister(struct b43legacy_wldev *dev); + +#endif /* B43legacy_SYSFS_H_ */ diff --git a/drivers/net/wireless/b43legacy/xmit.c b/drivers/net/wireless/b43legacy/xmit.c new file mode 100644 index 0000000..849a28c --- /dev/null +++ b/drivers/net/wireless/b43legacy/xmit.c @@ -0,0 +1,664 @@ +/* + + Broadcom B43legacy wireless driver + + Transmission (TX/RX) related functions. + + Copyright (C) 2005 Martin Langer + Copyright (C) 2005 Stefano Brivio + Copyright (C) 2005, 2006 Michael Buesch + Copyright (C) 2005 Danny van Dyk + Copyright (C) 2005 Andreas Jaggi + Copyright (C) 2007 Larry Finger + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#include + +#include "xmit.h" +#include "phy.h" +#include "dma.h" +#include "pio.h" + + +/* Extract the bitrate out of a CCK PLCP header. */ +static u8 b43legacy_plcp_get_bitrate_idx_cck(struct b43legacy_plcp_hdr6 *plcp) +{ + switch (plcp->raw[0]) { + case 0x0A: + return 0; + case 0x14: + return 1; + case 0x37: + return 2; + case 0x6E: + return 3; + } + B43legacy_BUG_ON(1); + return -1; +} + +/* Extract the bitrate out of an OFDM PLCP header. */ +static u8 b43legacy_plcp_get_bitrate_idx_ofdm(struct b43legacy_plcp_hdr6 *plcp, + bool aphy) +{ + int base = aphy ? 0 : 4; + + switch (plcp->raw[0] & 0xF) { + case 0xB: + return base + 0; + case 0xF: + return base + 1; + case 0xA: + return base + 2; + case 0xE: + return base + 3; + case 0x9: + return base + 4; + case 0xD: + return base + 5; + case 0x8: + return base + 6; + case 0xC: + return base + 7; + } + B43legacy_BUG_ON(1); + return -1; +} + +u8 b43legacy_plcp_get_ratecode_cck(const u8 bitrate) +{ + switch (bitrate) { + case B43legacy_CCK_RATE_1MB: + return 0x0A; + case B43legacy_CCK_RATE_2MB: + return 0x14; + case B43legacy_CCK_RATE_5MB: + return 0x37; + case B43legacy_CCK_RATE_11MB: + return 0x6E; + } + B43legacy_BUG_ON(1); + return 0; +} + +u8 b43legacy_plcp_get_ratecode_ofdm(const u8 bitrate) +{ + switch (bitrate) { + case B43legacy_OFDM_RATE_6MB: + return 0xB; + case B43legacy_OFDM_RATE_9MB: + return 0xF; + case B43legacy_OFDM_RATE_12MB: + return 0xA; + case B43legacy_OFDM_RATE_18MB: + return 0xE; + case B43legacy_OFDM_RATE_24MB: + return 0x9; + case B43legacy_OFDM_RATE_36MB: + return 0xD; + case B43legacy_OFDM_RATE_48MB: + return 0x8; + case B43legacy_OFDM_RATE_54MB: + return 0xC; + } + B43legacy_BUG_ON(1); + return 0; +} + +void b43legacy_generate_plcp_hdr(struct b43legacy_plcp_hdr4 *plcp, + const u16 octets, const u8 bitrate) +{ + __le32 *data = &(plcp->data); + __u8 *raw = plcp->raw; + + if (b43legacy_is_ofdm_rate(bitrate)) { + u16 d; + + d = b43legacy_plcp_get_ratecode_ofdm(bitrate); + B43legacy_WARN_ON(octets & 0xF000); + d |= (octets << 5); + *data = cpu_to_le32(d); + } else { + u32 plen; + + plen = octets * 16 / bitrate; + if ((octets * 16 % bitrate) > 0) { + plen++; + if ((bitrate == B43legacy_CCK_RATE_11MB) + && ((octets * 8 % 11) < 4)) + raw[1] = 0x84; + else + raw[1] = 0x04; + } else + raw[1] = 0x04; + *data |= cpu_to_le32(plen << 16); + raw[0] = b43legacy_plcp_get_ratecode_cck(bitrate); + } +} + +static u8 b43legacy_calc_fallback_rate(u8 bitrate) +{ + switch (bitrate) { + case B43legacy_CCK_RATE_1MB: + return B43legacy_CCK_RATE_1MB; + case B43legacy_CCK_RATE_2MB: + return B43legacy_CCK_RATE_1MB; + case B43legacy_CCK_RATE_5MB: + return B43legacy_CCK_RATE_2MB; + case B43legacy_CCK_RATE_11MB: + return B43legacy_CCK_RATE_5MB; + case B43legacy_OFDM_RATE_6MB: + return B43legacy_CCK_RATE_5MB; + case B43legacy_OFDM_RATE_9MB: + return B43legacy_OFDM_RATE_6MB; + case B43legacy_OFDM_RATE_12MB: + return B43legacy_OFDM_RATE_9MB; + case B43legacy_OFDM_RATE_18MB: + return B43legacy_OFDM_RATE_12MB; + case B43legacy_OFDM_RATE_24MB: + return B43legacy_OFDM_RATE_18MB; + case B43legacy_OFDM_RATE_36MB: + return B43legacy_OFDM_RATE_24MB; + case B43legacy_OFDM_RATE_48MB: + return B43legacy_OFDM_RATE_36MB; + case B43legacy_OFDM_RATE_54MB: + return B43legacy_OFDM_RATE_48MB; + } + B43legacy_BUG_ON(1); + return 0; +} + +static int generate_txhdr_fw3(struct b43legacy_wldev *dev, + struct b43legacy_txhdr_fw3 *txhdr, + const unsigned char *fragment_data, + unsigned int fragment_len, + struct ieee80211_tx_info *info, + u16 cookie) +{ + const struct ieee80211_hdr *wlhdr; + int use_encryption = !!info->control.hw_key; + u8 rate; + struct ieee80211_rate *rate_fb; + int rate_ofdm; + int rate_fb_ofdm; + unsigned int plcp_fragment_len; + u32 mac_ctl = 0; + u16 phy_ctl = 0; + struct ieee80211_rate *tx_rate; + struct ieee80211_tx_rate *rates; + + wlhdr = (const struct ieee80211_hdr *)fragment_data; + + memset(txhdr, 0, sizeof(*txhdr)); + + tx_rate = ieee80211_get_tx_rate(dev->wl->hw, info); + + rate = tx_rate->hw_value; + rate_ofdm = b43legacy_is_ofdm_rate(rate); + rate_fb = ieee80211_get_alt_retry_rate(dev->wl->hw, info, 0) ? : tx_rate; + rate_fb_ofdm = b43legacy_is_ofdm_rate(rate_fb->hw_value); + + txhdr->mac_frame_ctl = wlhdr->frame_control; + memcpy(txhdr->tx_receiver, wlhdr->addr1, 6); + + /* Calculate duration for fallback rate */ + if ((rate_fb->hw_value == rate) || + (wlhdr->duration_id & cpu_to_le16(0x8000)) || + (wlhdr->duration_id == cpu_to_le16(0))) { + /* If the fallback rate equals the normal rate or the + * dur_id field contains an AID, CFP magic or 0, + * use the original dur_id field. */ + txhdr->dur_fb = wlhdr->duration_id; + } else { + txhdr->dur_fb = ieee80211_generic_frame_duration(dev->wl->hw, + info->control.vif, + info->band, + fragment_len, + rate_fb); + } + + plcp_fragment_len = fragment_len + FCS_LEN; + if (use_encryption) { + u8 key_idx = info->control.hw_key->hw_key_idx; + struct b43legacy_key *key; + int wlhdr_len; + size_t iv_len; + + B43legacy_WARN_ON(key_idx >= dev->max_nr_keys); + key = &(dev->key[key_idx]); + + if (key->enabled) { + /* Hardware appends ICV. */ + plcp_fragment_len += info->control.hw_key->icv_len; + + key_idx = b43legacy_kidx_to_fw(dev, key_idx); + mac_ctl |= (key_idx << B43legacy_TX4_MAC_KEYIDX_SHIFT) & + B43legacy_TX4_MAC_KEYIDX; + mac_ctl |= (key->algorithm << + B43legacy_TX4_MAC_KEYALG_SHIFT) & + B43legacy_TX4_MAC_KEYALG; + wlhdr_len = ieee80211_hdrlen(wlhdr->frame_control); + iv_len = min((size_t)info->control.hw_key->iv_len, + ARRAY_SIZE(txhdr->iv)); + memcpy(txhdr->iv, ((u8 *)wlhdr) + wlhdr_len, iv_len); + } else { + /* This key is invalid. This might only happen + * in a short timeframe after machine resume before + * we were able to reconfigure keys. + * Drop this packet completely. Do not transmit it + * unencrypted to avoid leaking information. */ + return -ENOKEY; + } + } + b43legacy_generate_plcp_hdr((struct b43legacy_plcp_hdr4 *) + (&txhdr->plcp), plcp_fragment_len, + rate); + b43legacy_generate_plcp_hdr(&txhdr->plcp_fb, plcp_fragment_len, + rate_fb->hw_value); + + /* PHY TX Control word */ + if (rate_ofdm) + phy_ctl |= B43legacy_TX4_PHY_ENC_OFDM; + if (info->control.rates[0].flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE) + phy_ctl |= B43legacy_TX4_PHY_SHORTPRMBL; + phy_ctl |= B43legacy_TX4_PHY_ANTLAST; + + /* MAC control */ + rates = info->control.rates; + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) + mac_ctl |= B43legacy_TX4_MAC_ACK; + if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) + mac_ctl |= B43legacy_TX4_MAC_HWSEQ; + if (info->flags & IEEE80211_TX_CTL_FIRST_FRAGMENT) + mac_ctl |= B43legacy_TX4_MAC_STMSDU; + if (rate_fb_ofdm) + mac_ctl |= B43legacy_TX4_MAC_FALLBACKOFDM; + + /* Overwrite rates[0].count to make the retry calculation + * in the tx status easier. need the actual retry limit to + * detect whether the fallback rate was used. + */ + if ((rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS) || + (rates[0].count <= dev->wl->hw->conf.long_frame_max_tx_count)) { + rates[0].count = dev->wl->hw->conf.long_frame_max_tx_count; + mac_ctl |= B43legacy_TX4_MAC_LONGFRAME; + } else { + rates[0].count = dev->wl->hw->conf.short_frame_max_tx_count; + } + + /* Generate the RTS or CTS-to-self frame */ + if ((rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS) || + (rates[0].flags & IEEE80211_TX_RC_USE_CTS_PROTECT)) { + unsigned int len; + struct ieee80211_hdr *hdr; + int rts_rate; + int rts_rate_fb; + int rts_rate_fb_ofdm; + + rts_rate = ieee80211_get_rts_cts_rate(dev->wl->hw, info)->hw_value; + rts_rate_fb = b43legacy_calc_fallback_rate(rts_rate); + rts_rate_fb_ofdm = b43legacy_is_ofdm_rate(rts_rate_fb); + if (rts_rate_fb_ofdm) + mac_ctl |= B43legacy_TX4_MAC_CTSFALLBACKOFDM; + + if (rates[0].flags & IEEE80211_TX_RC_USE_CTS_PROTECT) { + ieee80211_ctstoself_get(dev->wl->hw, + info->control.vif, + fragment_data, + fragment_len, info, + (struct ieee80211_cts *) + (txhdr->rts_frame)); + mac_ctl |= B43legacy_TX4_MAC_SENDCTS; + len = sizeof(struct ieee80211_cts); + } else { + ieee80211_rts_get(dev->wl->hw, + info->control.vif, + fragment_data, fragment_len, info, + (struct ieee80211_rts *) + (txhdr->rts_frame)); + mac_ctl |= B43legacy_TX4_MAC_SENDRTS; + len = sizeof(struct ieee80211_rts); + } + len += FCS_LEN; + b43legacy_generate_plcp_hdr((struct b43legacy_plcp_hdr4 *) + (&txhdr->rts_plcp), + len, rts_rate); + b43legacy_generate_plcp_hdr(&txhdr->rts_plcp_fb, + len, rts_rate_fb); + hdr = (struct ieee80211_hdr *)(&txhdr->rts_frame); + txhdr->rts_dur_fb = hdr->duration_id; + } + + /* Magic cookie */ + txhdr->cookie = cpu_to_le16(cookie); + + /* Apply the bitfields */ + txhdr->mac_ctl = cpu_to_le32(mac_ctl); + txhdr->phy_ctl = cpu_to_le16(phy_ctl); + + return 0; +} + +int b43legacy_generate_txhdr(struct b43legacy_wldev *dev, + u8 *txhdr, + const unsigned char *fragment_data, + unsigned int fragment_len, + struct ieee80211_tx_info *info, + u16 cookie) +{ + return generate_txhdr_fw3(dev, (struct b43legacy_txhdr_fw3 *)txhdr, + fragment_data, fragment_len, + info, cookie); +} + +static s8 b43legacy_rssi_postprocess(struct b43legacy_wldev *dev, + u8 in_rssi, int ofdm, + int adjust_2053, int adjust_2050) +{ + struct b43legacy_phy *phy = &dev->phy; + s32 tmp; + + switch (phy->radio_ver) { + case 0x2050: + if (ofdm) { + tmp = in_rssi; + if (tmp > 127) + tmp -= 256; + tmp *= 73; + tmp /= 64; + if (adjust_2050) + tmp += 25; + else + tmp -= 3; + } else { + if (dev->dev->bus->sprom.boardflags_lo + & B43legacy_BFL_RSSI) { + if (in_rssi > 63) + in_rssi = 63; + tmp = phy->nrssi_lt[in_rssi]; + tmp = 31 - tmp; + tmp *= -131; + tmp /= 128; + tmp -= 57; + } else { + tmp = in_rssi; + tmp = 31 - tmp; + tmp *= -149; + tmp /= 128; + tmp -= 68; + } + if (phy->type == B43legacy_PHYTYPE_G && + adjust_2050) + tmp += 25; + } + break; + case 0x2060: + if (in_rssi > 127) + tmp = in_rssi - 256; + else + tmp = in_rssi; + break; + default: + tmp = in_rssi; + tmp -= 11; + tmp *= 103; + tmp /= 64; + if (adjust_2053) + tmp -= 109; + else + tmp -= 83; + } + + return (s8)tmp; +} + +void b43legacy_rx(struct b43legacy_wldev *dev, + struct sk_buff *skb, + const void *_rxhdr) +{ + struct ieee80211_rx_status status; + struct b43legacy_plcp_hdr6 *plcp; + struct ieee80211_hdr *wlhdr; + const struct b43legacy_rxhdr_fw3 *rxhdr = _rxhdr; + __le16 fctl; + u16 phystat0; + u16 phystat3; + u16 chanstat; + u16 mactime; + u32 macstat; + u16 chanid; + u8 jssi; + int padding; + + memset(&status, 0, sizeof(status)); + + /* Get metadata about the frame from the header. */ + phystat0 = le16_to_cpu(rxhdr->phy_status0); + phystat3 = le16_to_cpu(rxhdr->phy_status3); + jssi = rxhdr->jssi; + macstat = le16_to_cpu(rxhdr->mac_status); + mactime = le16_to_cpu(rxhdr->mac_time); + chanstat = le16_to_cpu(rxhdr->channel); + + if (macstat & B43legacy_RX_MAC_FCSERR) + dev->wl->ieee_stats.dot11FCSErrorCount++; + + /* Skip PLCP and padding */ + padding = (macstat & B43legacy_RX_MAC_PADDING) ? 2 : 0; + if (unlikely(skb->len < (sizeof(struct b43legacy_plcp_hdr6) + + padding))) { + b43legacydbg(dev->wl, "RX: Packet size underrun (1)\n"); + goto drop; + } + plcp = (struct b43legacy_plcp_hdr6 *)(skb->data + padding); + skb_pull(skb, sizeof(struct b43legacy_plcp_hdr6) + padding); + /* The skb contains the Wireless Header + payload data now */ + if (unlikely(skb->len < (2+2+6/*minimum hdr*/ + FCS_LEN))) { + b43legacydbg(dev->wl, "RX: Packet size underrun (2)\n"); + goto drop; + } + wlhdr = (struct ieee80211_hdr *)(skb->data); + fctl = wlhdr->frame_control; + + if ((macstat & B43legacy_RX_MAC_DEC) && + !(macstat & B43legacy_RX_MAC_DECERR)) { + unsigned int keyidx; + int wlhdr_len; + int iv_len; + int icv_len; + + keyidx = ((macstat & B43legacy_RX_MAC_KEYIDX) + >> B43legacy_RX_MAC_KEYIDX_SHIFT); + /* We must adjust the key index here. We want the "physical" + * key index, but the ucode passed it slightly different. + */ + keyidx = b43legacy_kidx_to_raw(dev, keyidx); + B43legacy_WARN_ON(keyidx >= dev->max_nr_keys); + + if (dev->key[keyidx].algorithm != B43legacy_SEC_ALGO_NONE) { + /* Remove PROTECTED flag to mark it as decrypted. */ + B43legacy_WARN_ON(!ieee80211_has_protected(fctl)); + fctl &= ~cpu_to_le16(IEEE80211_FCTL_PROTECTED); + wlhdr->frame_control = fctl; + + wlhdr_len = ieee80211_hdrlen(fctl); + if (unlikely(skb->len < (wlhdr_len + 3))) { + b43legacydbg(dev->wl, "RX: Packet size" + " underrun3\n"); + goto drop; + } + if (skb->data[wlhdr_len + 3] & (1 << 5)) { + /* The Ext-IV Bit is set in the "KeyID" + * octet of the IV. + */ + iv_len = 8; + icv_len = 8; + } else { + iv_len = 4; + icv_len = 4; + } + if (unlikely(skb->len < (wlhdr_len + iv_len + + icv_len))) { + b43legacydbg(dev->wl, "RX: Packet size" + " underrun4\n"); + goto drop; + } + /* Remove the IV */ + memmove(skb->data + iv_len, skb->data, wlhdr_len); + skb_pull(skb, iv_len); + /* Remove the ICV */ + skb_trim(skb, skb->len - icv_len); + + status.flag |= RX_FLAG_DECRYPTED; + } + } + + status.signal = b43legacy_rssi_postprocess(dev, jssi, + (phystat0 & B43legacy_RX_PHYST0_OFDM), + (phystat0 & B43legacy_RX_PHYST0_GAINCTL), + (phystat3 & B43legacy_RX_PHYST3_TRSTATE)); + /* change to support A PHY */ + if (phystat0 & B43legacy_RX_PHYST0_OFDM) + status.rate_idx = b43legacy_plcp_get_bitrate_idx_ofdm(plcp, false); + else + status.rate_idx = b43legacy_plcp_get_bitrate_idx_cck(plcp); + status.antenna = !!(phystat0 & B43legacy_RX_PHYST0_ANT); + + /* + * All frames on monitor interfaces and beacons always need a full + * 64-bit timestamp. Monitor interfaces need it for diagnostic + * purposes and beacons for IBSS merging. + * This code assumes we get to process the packet within 16 bits + * of timestamp, i.e. about 65 milliseconds after the PHY received + * the first symbol. + */ + if (ieee80211_is_beacon(fctl) || dev->wl->radiotap_enabled) { + u16 low_mactime_now; + + b43legacy_tsf_read(dev, &status.mactime); + low_mactime_now = status.mactime; + status.mactime = status.mactime & ~0xFFFFULL; + status.mactime += mactime; + if (low_mactime_now <= mactime) + status.mactime -= 0x10000; + status.flag |= RX_FLAG_MACTIME_START; + } + + chanid = (chanstat & B43legacy_RX_CHAN_ID) >> + B43legacy_RX_CHAN_ID_SHIFT; + switch (chanstat & B43legacy_RX_CHAN_PHYTYPE) { + case B43legacy_PHYTYPE_B: + case B43legacy_PHYTYPE_G: + status.band = IEEE80211_BAND_2GHZ; + status.freq = chanid + 2400; + break; + default: + b43legacywarn(dev->wl, "Unexpected value for chanstat (0x%X)\n", + chanstat); + } + + memcpy(IEEE80211_SKB_RXCB(skb), &status, sizeof(status)); + ieee80211_rx_irqsafe(dev->wl->hw, skb); + + return; +drop: + b43legacydbg(dev->wl, "RX: Packet dropped\n"); + dev_kfree_skb_any(skb); +} + +void b43legacy_handle_txstatus(struct b43legacy_wldev *dev, + const struct b43legacy_txstatus *status) +{ + b43legacy_debugfs_log_txstat(dev, status); + + if (status->intermediate) + return; + if (status->for_ampdu) + return; + if (!status->acked) + dev->wl->ieee_stats.dot11ACKFailureCount++; + if (status->rts_count) { + if (status->rts_count == 0xF) /* FIXME */ + dev->wl->ieee_stats.dot11RTSFailureCount++; + else + dev->wl->ieee_stats.dot11RTSSuccessCount++; + } + + if (b43legacy_using_pio(dev)) + b43legacy_pio_handle_txstatus(dev, status); + else + b43legacy_dma_handle_txstatus(dev, status); +} + +/* Handle TX status report as received through DMA/PIO queues */ +void b43legacy_handle_hwtxstatus(struct b43legacy_wldev *dev, + const struct b43legacy_hwtxstatus *hw) +{ + struct b43legacy_txstatus status; + u8 tmp; + + status.cookie = le16_to_cpu(hw->cookie); + status.seq = le16_to_cpu(hw->seq); + status.phy_stat = hw->phy_stat; + tmp = hw->count; + status.frame_count = (tmp >> 4); + status.rts_count = (tmp & 0x0F); + tmp = hw->flags << 1; + status.supp_reason = ((tmp & 0x1C) >> 2); + status.pm_indicated = !!(tmp & 0x80); + status.intermediate = !!(tmp & 0x40); + status.for_ampdu = !!(tmp & 0x20); + status.acked = !!(tmp & 0x02); + + b43legacy_handle_txstatus(dev, &status); +} + +/* Stop any TX operation on the device (suspend the hardware queues) */ +void b43legacy_tx_suspend(struct b43legacy_wldev *dev) +{ + if (b43legacy_using_pio(dev)) + b43legacy_pio_freeze_txqueues(dev); + else + b43legacy_dma_tx_suspend(dev); +} + +/* Resume any TX operation on the device (resume the hardware queues) */ +void b43legacy_tx_resume(struct b43legacy_wldev *dev) +{ + if (b43legacy_using_pio(dev)) + b43legacy_pio_thaw_txqueues(dev); + else + b43legacy_dma_tx_resume(dev); +} + +/* Initialize the QoS parameters */ +void b43legacy_qos_init(struct b43legacy_wldev *dev) +{ + /* FIXME: This function must probably be called from the mac80211 + * config callback. */ +return; + + b43legacy_hf_write(dev, b43legacy_hf_read(dev) | B43legacy_HF_EDCF); + /* FIXME kill magic */ + b43legacy_write16(dev, 0x688, + b43legacy_read16(dev, 0x688) | 0x4); + + + /*TODO: We might need some stack support here to get the values. */ +} diff --git a/drivers/net/wireless/b43legacy/xmit.h b/drivers/net/wireless/b43legacy/xmit.h new file mode 100644 index 0000000..289db00 --- /dev/null +++ b/drivers/net/wireless/b43legacy/xmit.h @@ -0,0 +1,261 @@ +#ifndef B43legacy_XMIT_H_ +#define B43legacy_XMIT_H_ + +#include "main.h" + + +#define _b43legacy_declare_plcp_hdr(size) \ + struct b43legacy_plcp_hdr##size { \ + union { \ + __le32 data; \ + __u8 raw[size]; \ + } __packed; \ + } __packed + +/* struct b43legacy_plcp_hdr4 */ +_b43legacy_declare_plcp_hdr(4); +/* struct b43legacy_plcp_hdr6 */ +_b43legacy_declare_plcp_hdr(6); + +#undef _b43legacy_declare_plcp_hdr + + +/* TX header for v3 firmware */ +struct b43legacy_txhdr_fw3 { + __le32 mac_ctl; /* MAC TX control */ + __le16 mac_frame_ctl; /* Copy of the FrameControl */ + __le16 tx_fes_time_norm; /* TX FES Time Normal */ + __le16 phy_ctl; /* PHY TX control */ + __u8 iv[16]; /* Encryption IV */ + __u8 tx_receiver[6]; /* TX Frame Receiver address */ + __le16 tx_fes_time_fb; /* TX FES Time Fallback */ + struct b43legacy_plcp_hdr4 rts_plcp_fb; /* RTS fallback PLCP */ + __le16 rts_dur_fb; /* RTS fallback duration */ + struct b43legacy_plcp_hdr4 plcp_fb; /* Fallback PLCP */ + __le16 dur_fb; /* Fallback duration */ + PAD_BYTES(2); + __le16 cookie; + __le16 unknown_scb_stuff; + struct b43legacy_plcp_hdr6 rts_plcp; /* RTS PLCP */ + __u8 rts_frame[18]; /* The RTS frame (if used) */ + struct b43legacy_plcp_hdr6 plcp; +} __packed; + +/* MAC TX control */ +#define B43legacy_TX4_MAC_KEYIDX 0x0FF00000 /* Security key index */ +#define B43legacy_TX4_MAC_KEYIDX_SHIFT 20 +#define B43legacy_TX4_MAC_KEYALG 0x00070000 /* Security key algorithm */ +#define B43legacy_TX4_MAC_KEYALG_SHIFT 16 +#define B43legacy_TX4_MAC_LIFETIME 0x00001000 +#define B43legacy_TX4_MAC_FRAMEBURST 0x00000800 +#define B43legacy_TX4_MAC_SENDCTS 0x00000400 +#define B43legacy_TX4_MAC_AMPDU 0x00000300 +#define B43legacy_TX4_MAC_AMPDU_SHIFT 8 +#define B43legacy_TX4_MAC_CTSFALLBACKOFDM 0x00000200 +#define B43legacy_TX4_MAC_FALLBACKOFDM 0x00000100 +#define B43legacy_TX4_MAC_5GHZ 0x00000080 +#define B43legacy_TX4_MAC_IGNPMQ 0x00000020 +#define B43legacy_TX4_MAC_HWSEQ 0x00000010 /* Use Hardware Seq No */ +#define B43legacy_TX4_MAC_STMSDU 0x00000008 /* Start MSDU */ +#define B43legacy_TX4_MAC_SENDRTS 0x00000004 +#define B43legacy_TX4_MAC_LONGFRAME 0x00000002 +#define B43legacy_TX4_MAC_ACK 0x00000001 + +/* Extra Frame Types */ +#define B43legacy_TX4_EFT_FBOFDM 0x0001 /* Data frame fb rate type */ +#define B43legacy_TX4_EFT_RTSOFDM 0x0004 /* RTS/CTS rate type */ +#define B43legacy_TX4_EFT_RTSFBOFDM 0x0010 /* RTS/CTS fallback rate type */ + +/* PHY TX control word */ +#define B43legacy_TX4_PHY_ENC 0x0003 /* Data frame encoding */ +#define B43legacy_TX4_PHY_ENC_CCK 0x0000 /* CCK */ +#define B43legacy_TX4_PHY_ENC_OFDM 0x0001 /* Data frame rate type */ +#define B43legacy_TX4_PHY_SHORTPRMBL 0x0010 /* Use short preamble */ +#define B43legacy_TX4_PHY_ANT 0x03C0 /* Antenna selection */ +#define B43legacy_TX4_PHY_ANT0 0x0000 /* Use antenna 0 */ +#define B43legacy_TX4_PHY_ANT1 0x0100 /* Use antenna 1 */ +#define B43legacy_TX4_PHY_ANTLAST 0x0300 /* Use last used antenna */ + + + +int b43legacy_generate_txhdr(struct b43legacy_wldev *dev, + u8 *txhdr, + const unsigned char *fragment_data, + unsigned int fragment_len, + struct ieee80211_tx_info *info, + u16 cookie); + + +/* Transmit Status */ +struct b43legacy_txstatus { + u16 cookie; /* The cookie from the txhdr */ + u16 seq; /* Sequence number */ + u8 phy_stat; /* PHY TX status */ + u8 frame_count; /* Frame transmit count */ + u8 rts_count; /* RTS transmit count */ + u8 supp_reason; /* Suppression reason */ + /* flags */ + u8 pm_indicated;/* PM mode indicated to AP */ + u8 intermediate;/* Intermediate status notification */ + u8 for_ampdu; /* Status is for an AMPDU (afterburner) */ + u8 acked; /* Wireless ACK received */ +}; + +/* txstatus supp_reason values */ +enum { + B43legacy_TXST_SUPP_NONE, /* Not suppressed */ + B43legacy_TXST_SUPP_PMQ, /* Suppressed due to PMQ entry */ + B43legacy_TXST_SUPP_FLUSH, /* Suppressed due to flush request */ + B43legacy_TXST_SUPP_PREV, /* Previous fragment failed */ + B43legacy_TXST_SUPP_CHAN, /* Channel mismatch */ + B43legacy_TXST_SUPP_LIFE, /* Lifetime expired */ + B43legacy_TXST_SUPP_UNDER, /* Buffer underflow */ + B43legacy_TXST_SUPP_ABNACK, /* Afterburner NACK */ +}; + +/* Transmit Status as received through DMA/PIO on old chips */ +struct b43legacy_hwtxstatus { + PAD_BYTES(4); + __le16 cookie; + u8 flags; + u8 count; + PAD_BYTES(2); + __le16 seq; + u8 phy_stat; + PAD_BYTES(1); +} __packed; + + +/* Receive header for v3 firmware. */ +struct b43legacy_rxhdr_fw3 { + __le16 frame_len; /* Frame length */ + PAD_BYTES(2); + __le16 phy_status0; /* PHY RX Status 0 */ + __u8 jssi; /* PHY RX Status 1: JSSI */ + __u8 sig_qual; /* PHY RX Status 1: Signal Quality */ + PAD_BYTES(2); /* PHY RX Status 2 */ + __le16 phy_status3; /* PHY RX Status 3 */ + __le16 mac_status; /* MAC RX status */ + __le16 mac_time; + __le16 channel; +} __packed; + + +/* PHY RX Status 0 */ +#define B43legacy_RX_PHYST0_GAINCTL 0x4000 /* Gain Control */ +#define B43legacy_RX_PHYST0_PLCPHCF 0x0200 +#define B43legacy_RX_PHYST0_PLCPFV 0x0100 +#define B43legacy_RX_PHYST0_SHORTPRMBL 0x0080 /* Recvd with Short Preamble */ +#define B43legacy_RX_PHYST0_LCRS 0x0040 +#define B43legacy_RX_PHYST0_ANT 0x0020 /* Antenna */ +#define B43legacy_RX_PHYST0_UNSRATE 0x0010 +#define B43legacy_RX_PHYST0_CLIP 0x000C +#define B43legacy_RX_PHYST0_CLIP_SHIFT 2 +#define B43legacy_RX_PHYST0_FTYPE 0x0003 /* Frame type */ +#define B43legacy_RX_PHYST0_CCK 0x0000 /* Frame type: CCK */ +#define B43legacy_RX_PHYST0_OFDM 0x0001 /* Frame type: OFDM */ +#define B43legacy_RX_PHYST0_PRE_N 0x0002 /* Pre-standard N-PHY frame */ +#define B43legacy_RX_PHYST0_STD_N 0x0003 /* Standard N-PHY frame */ + +/* PHY RX Status 2 */ +#define B43legacy_RX_PHYST2_LNAG 0xC000 /* LNA Gain */ +#define B43legacy_RX_PHYST2_LNAG_SHIFT 14 +#define B43legacy_RX_PHYST2_PNAG 0x3C00 /* PNA Gain */ +#define B43legacy_RX_PHYST2_PNAG_SHIFT 10 +#define B43legacy_RX_PHYST2_FOFF 0x03FF /* F offset */ + +/* PHY RX Status 3 */ +#define B43legacy_RX_PHYST3_DIGG 0x1800 /* DIG Gain */ +#define B43legacy_RX_PHYST3_DIGG_SHIFT 11 +#define B43legacy_RX_PHYST3_TRSTATE 0x0400 /* TR state */ + +/* MAC RX Status */ +#define B43legacy_RX_MAC_BEACONSENT 0x00008000 /* Beacon send flag */ +#define B43legacy_RX_MAC_KEYIDX 0x000007E0 /* Key index */ +#define B43legacy_RX_MAC_KEYIDX_SHIFT 5 +#define B43legacy_RX_MAC_DECERR 0x00000010 /* Decrypt error */ +#define B43legacy_RX_MAC_DEC 0x00000008 /* Decryption attempted */ +#define B43legacy_RX_MAC_PADDING 0x00000004 /* Pad bytes present */ +#define B43legacy_RX_MAC_RESP 0x00000002 /* Response frame xmitted */ +#define B43legacy_RX_MAC_FCSERR 0x00000001 /* FCS error */ + +/* RX channel */ +#define B43legacy_RX_CHAN_GAIN 0xFC00 /* Gain */ +#define B43legacy_RX_CHAN_GAIN_SHIFT 10 +#define B43legacy_RX_CHAN_ID 0x03FC /* Channel ID */ +#define B43legacy_RX_CHAN_ID_SHIFT 2 +#define B43legacy_RX_CHAN_PHYTYPE 0x0003 /* PHY type */ + + + +u8 b43legacy_plcp_get_ratecode_cck(const u8 bitrate); +u8 b43legacy_plcp_get_ratecode_ofdm(const u8 bitrate); + +void b43legacy_generate_plcp_hdr(struct b43legacy_plcp_hdr4 *plcp, + const u16 octets, const u8 bitrate); + +void b43legacy_rx(struct b43legacy_wldev *dev, + struct sk_buff *skb, + const void *_rxhdr); + +void b43legacy_handle_txstatus(struct b43legacy_wldev *dev, + const struct b43legacy_txstatus *status); + +void b43legacy_handle_hwtxstatus(struct b43legacy_wldev *dev, + const struct b43legacy_hwtxstatus *hw); + +void b43legacy_tx_suspend(struct b43legacy_wldev *dev); +void b43legacy_tx_resume(struct b43legacy_wldev *dev); + + +#define B43legacy_NR_QOSPARMS 22 +enum { + B43legacy_QOSPARM_TXOP = 0, + B43legacy_QOSPARM_CWMIN, + B43legacy_QOSPARM_CWMAX, + B43legacy_QOSPARM_CWCUR, + B43legacy_QOSPARM_AIFS, + B43legacy_QOSPARM_BSLOTS, + B43legacy_QOSPARM_REGGAP, + B43legacy_QOSPARM_STATUS, +}; + +void b43legacy_qos_init(struct b43legacy_wldev *dev); + + +/* Helper functions for converting the key-table index from "firmware-format" + * to "raw-format" and back. The firmware API changed for this at some revision. + * We need to account for that here. */ +static inline +int b43legacy_new_kidx_api(struct b43legacy_wldev *dev) +{ + /* FIXME: Not sure the change was at rev 351 */ + return (dev->fw.rev >= 351); +} +static inline +u8 b43legacy_kidx_to_fw(struct b43legacy_wldev *dev, u8 raw_kidx) +{ + u8 firmware_kidx; + if (b43legacy_new_kidx_api(dev)) + firmware_kidx = raw_kidx; + else { + if (raw_kidx >= 4) /* Is per STA key? */ + firmware_kidx = raw_kidx - 4; + else + firmware_kidx = raw_kidx; /* TX default key */ + } + return firmware_kidx; +} +static inline +u8 b43legacy_kidx_to_raw(struct b43legacy_wldev *dev, u8 firmware_kidx) +{ + u8 raw_kidx; + if (b43legacy_new_kidx_api(dev)) + raw_kidx = firmware_kidx; + else + /* RX default keys or per STA keys */ + raw_kidx = firmware_kidx + 4; + return raw_kidx; +} + +#endif /* B43legacy_XMIT_H_ */ diff --git a/drivers/net/wireless/brcm80211/Kconfig b/drivers/net/wireless/brcm80211/Kconfig new file mode 100644 index 0000000..bb64a22 --- /dev/null +++ b/drivers/net/wireless/brcm80211/Kconfig @@ -0,0 +1,70 @@ +config BRCMUTIL + tristate + depends on m + +config BRCMSMAC + tristate "Broadcom IEEE802.11n PCIe SoftMAC WLAN driver" + depends on m + depends on MAC80211 + depends on BCMA + select BRCMUTIL + select BACKPORT_FW_LOADER + depends on CRC_CCITT + select BACKPORT_CRC8 + select BACKPORT_CORDIC + ---help--- + This module adds support for PCIe wireless adapters based on Broadcom + IEEE802.11n SoftMAC chipsets. It also has WLAN led support, which will + be available if you select BCMA_DRIVER_GPIO. If you choose to build a + module, the driver will be called brcmsmac.ko. + +config BRCMFMAC + depends on !BACKPORT_KERNEL_2_6_29 + tristate "Broadcom IEEE802.11n embedded FullMAC WLAN driver" + depends on m + depends on CFG80211 + select BRCMUTIL + ---help--- + This module adds support for embedded wireless adapters based on + Broadcom IEEE802.11n FullMAC chipsets. It has to work with at least + one of the bus interface support. If you choose to build a module, + it'll be called brcmfmac.ko. + +config BRCMFMAC_SDIO + bool "SDIO bus interface support for FullMAC driver" + depends on MMC + depends on BRCMFMAC + select BACKPORT_FW_LOADER + default y + ---help--- + This option enables the SDIO bus interface support for Broadcom + IEEE802.11n embedded FullMAC WLAN driver. Say Y if you want to + use the driver for a SDIO wireless card. + +config BRCMFMAC_USB + bool "USB bus interface support for FullMAC driver" + depends on USB + depends on BRCMFMAC + select BACKPORT_FW_LOADER + ---help--- + This option enables the USB bus interface support for Broadcom + IEEE802.11n embedded FullMAC WLAN driver. Say Y if you want to + use the driver for an USB wireless card. + +config BRCM_TRACING + bool "Broadcom device tracing" + depends on BRCMSMAC || BRCMFMAC + ---help--- + If you say Y here, the Broadcom wireless drivers will register + with ftrace to dump event information into the trace ringbuffer. + Tracing can be enabled at runtime to aid in debugging wireless + issues. This option adds a small amount of overhead when tracing + is disabled. If unsure, say Y to allow developers to better help + you when wireless problems occur. + +config BRCMDBG + depends on !BACKPORT_KERNEL_2_6_30 + bool "Broadcom driver debug functions" + depends on BRCMSMAC || BRCMFMAC + ---help--- + Selecting this enables additional code for debug purposes. diff --git a/drivers/net/wireless/brcm80211/Makefile b/drivers/net/wireless/brcm80211/Makefile new file mode 100644 index 0000000..41458ca --- /dev/null +++ b/drivers/net/wireless/brcm80211/Makefile @@ -0,0 +1,23 @@ +# +# Makefile fragment for Broadcom 802.11n Networking Device Driver +# +# Copyright (c) 2010 Broadcom Corporation +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# common flags +subdir-ccflags-$(CPTCFG_BRCMDBG) += -DDEBUG + +obj-$(CPTCFG_BRCMUTIL) += brcmutil/ +obj-$(CPTCFG_BRCMFMAC) += brcmfmac/ +obj-$(CPTCFG_BRCMSMAC) += brcmsmac/ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/Makefile b/drivers/net/wireless/brcm80211/brcmfmac/Makefile new file mode 100644 index 0000000..fec7a01 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/Makefile @@ -0,0 +1,45 @@ +# +# Makefile fragment for Broadcom 802.11n Networking Device Driver +# +# Copyright (c) 2010 Broadcom Corporation +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ccflags-y += \ + -I$(backport_srctree)/drivers/net/wireless/brcm80211/brcmfmac \ + -I$(backport_srctree)/drivers/net/wireless/brcm80211/include + +ccflags-y += -D__CHECK_ENDIAN__ + +obj-$(CPTCFG_BRCMFMAC) += brcmfmac.o +brcmfmac-objs += \ + wl_cfg80211.o \ + fwil.o \ + fweh.o \ + fwsignal.o \ + p2p.o \ + dhd_cdc.o \ + dhd_common.o \ + dhd_linux.o \ + btcoex.o +brcmfmac-$(CPTCFG_BRCMFMAC_SDIO) += \ + dhd_sdio.o \ + bcmsdh.o \ + bcmsdh_sdmmc.o \ + sdio_chip.o +brcmfmac-$(CPTCFG_BRCMFMAC_USB) += \ + usb.o +brcmfmac-$(CPTCFG_BRCMDBG) += \ + dhd_dbg.o +brcmfmac-$(CPTCFG_BRCM_TRACING) += \ + tracepoint.o diff --git a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c new file mode 100644 index 0000000..4891e3d --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh.c @@ -0,0 +1,600 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +/* ****************** SDIO CARD Interface Functions **************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "dhd_bus.h" +#include "dhd_dbg.h" +#include "sdio_host.h" + +#define SDIOH_API_ACCESS_RETRY_LIMIT 2 + + +static irqreturn_t brcmf_sdio_oob_irqhandler(int irq, void *dev_id) +{ + struct brcmf_bus *bus_if = dev_get_drvdata(dev_id); + struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; + + brcmf_dbg(INTR, "OOB intr triggered\n"); + + /* out-of-band interrupt is level-triggered which won't + * be cleared until dpc + */ + if (sdiodev->irq_en) { + disable_irq_nosync(irq); + sdiodev->irq_en = false; + } + + brcmf_sdbrcm_isr(sdiodev->bus); + + return IRQ_HANDLED; +} + +static void brcmf_sdio_ib_irqhandler(struct sdio_func *func) +{ + struct brcmf_bus *bus_if = dev_get_drvdata(&func->dev); + struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; + + brcmf_dbg(INTR, "IB intr triggered\n"); + + brcmf_sdbrcm_isr(sdiodev->bus); +} + +/* dummy handler for SDIO function 2 interrupt */ +static void brcmf_sdio_dummy_irqhandler(struct sdio_func *func) +{ +} + +int brcmf_sdio_intr_register(struct brcmf_sdio_dev *sdiodev) +{ + int ret = 0; + u8 data; + unsigned long flags; + + if ((sdiodev->pdata) && (sdiodev->pdata->oob_irq_supported)) { + brcmf_dbg(SDIO, "Enter, register OOB IRQ %d\n", + sdiodev->pdata->oob_irq_nr); + ret = request_irq(sdiodev->pdata->oob_irq_nr, + brcmf_sdio_oob_irqhandler, + sdiodev->pdata->oob_irq_flags, + "brcmf_oob_intr", + &sdiodev->func[1]->dev); + if (ret != 0) { + brcmf_err("request_irq failed %d\n", ret); + return ret; + } + sdiodev->oob_irq_requested = true; + spin_lock_init(&sdiodev->irq_en_lock); + spin_lock_irqsave(&sdiodev->irq_en_lock, flags); + sdiodev->irq_en = true; + spin_unlock_irqrestore(&sdiodev->irq_en_lock, flags); + + ret = enable_irq_wake(sdiodev->pdata->oob_irq_nr); + if (ret != 0) { + brcmf_err("enable_irq_wake failed %d\n", ret); + return ret; + } + sdiodev->irq_wake = true; + + sdio_claim_host(sdiodev->func[1]); + + /* must configure SDIO_CCCR_IENx to enable irq */ + data = brcmf_sdio_regrb(sdiodev, SDIO_CCCR_IENx, &ret); + data |= 1 << SDIO_FUNC_1 | 1 << SDIO_FUNC_2 | 1; + brcmf_sdio_regwb(sdiodev, SDIO_CCCR_IENx, data, &ret); + + /* redirect, configure and enable io for interrupt signal */ + data = SDIO_SEPINT_MASK | SDIO_SEPINT_OE; + if (sdiodev->pdata->oob_irq_flags & IRQF_TRIGGER_HIGH) + data |= SDIO_SEPINT_ACT_HI; + brcmf_sdio_regwb(sdiodev, SDIO_CCCR_BRCM_SEPINT, data, &ret); + + sdio_release_host(sdiodev->func[1]); + } else { + brcmf_dbg(SDIO, "Entering\n"); + sdio_claim_host(sdiodev->func[1]); + sdio_claim_irq(sdiodev->func[1], brcmf_sdio_ib_irqhandler); + sdio_claim_irq(sdiodev->func[2], brcmf_sdio_dummy_irqhandler); + sdio_release_host(sdiodev->func[1]); + } + + return 0; +} + +int brcmf_sdio_intr_unregister(struct brcmf_sdio_dev *sdiodev) +{ + brcmf_dbg(SDIO, "Entering\n"); + + if ((sdiodev->pdata) && (sdiodev->pdata->oob_irq_supported)) { + sdio_claim_host(sdiodev->func[1]); + brcmf_sdio_regwb(sdiodev, SDIO_CCCR_BRCM_SEPINT, 0, NULL); + brcmf_sdio_regwb(sdiodev, SDIO_CCCR_IENx, 0, NULL); + sdio_release_host(sdiodev->func[1]); + + if (sdiodev->oob_irq_requested) { + sdiodev->oob_irq_requested = false; + if (sdiodev->irq_wake) { + disable_irq_wake(sdiodev->pdata->oob_irq_nr); + sdiodev->irq_wake = false; + } + free_irq(sdiodev->pdata->oob_irq_nr, + &sdiodev->func[1]->dev); + sdiodev->irq_en = false; + } + } else { + sdio_claim_host(sdiodev->func[1]); + sdio_release_irq(sdiodev->func[2]); + sdio_release_irq(sdiodev->func[1]); + sdio_release_host(sdiodev->func[1]); + } + + return 0; +} + +int +brcmf_sdcard_set_sbaddr_window(struct brcmf_sdio_dev *sdiodev, u32 address) +{ + int err = 0, i; + u8 addr[3]; + s32 retry; + + addr[0] = (address >> 8) & SBSDIO_SBADDRLOW_MASK; + addr[1] = (address >> 16) & SBSDIO_SBADDRMID_MASK; + addr[2] = (address >> 24) & SBSDIO_SBADDRHIGH_MASK; + + for (i = 0; i < 3; i++) { + retry = 0; + do { + if (retry) + usleep_range(1000, 2000); + err = brcmf_sdioh_request_byte(sdiodev, SDIOH_WRITE, + SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRLOW + i, + &addr[i]); + } while (err != 0 && retry++ < SDIOH_API_ACCESS_RETRY_LIMIT); + + if (err) { + brcmf_err("failed at addr:0x%0x\n", + SBSDIO_FUNC1_SBADDRLOW + i); + break; + } + } + + return err; +} + +int +brcmf_sdio_regrw_helper(struct brcmf_sdio_dev *sdiodev, u32 addr, + void *data, bool write) +{ + u8 func_num, reg_size; + u32 bar; + s32 retry = 0; + int ret; + + /* + * figure out how to read the register based on address range + * 0x00 ~ 0x7FF: function 0 CCCR and FBR + * 0x10000 ~ 0x1FFFF: function 1 miscellaneous registers + * The rest: function 1 silicon backplane core registers + */ + if ((addr & ~REG_F0_REG_MASK) == 0) { + func_num = SDIO_FUNC_0; + reg_size = 1; + } else if ((addr & ~REG_F1_MISC_MASK) == 0) { + func_num = SDIO_FUNC_1; + reg_size = 1; + } else { + func_num = SDIO_FUNC_1; + reg_size = 4; + + /* Set the window for SB core register */ + bar = addr & ~SBSDIO_SB_OFT_ADDR_MASK; + if (bar != sdiodev->sbwad) { + ret = brcmf_sdcard_set_sbaddr_window(sdiodev, bar); + if (ret != 0) { + memset(data, 0xFF, reg_size); + return ret; + } + sdiodev->sbwad = bar; + } + addr &= SBSDIO_SB_OFT_ADDR_MASK; + addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; + } + + do { + if (!write) + memset(data, 0, reg_size); + if (retry) /* wait for 1 ms till bus get settled down */ + usleep_range(1000, 2000); + if (reg_size == 1) + ret = brcmf_sdioh_request_byte(sdiodev, write, + func_num, addr, data); + else + ret = brcmf_sdioh_request_word(sdiodev, write, + func_num, addr, data, 4); + } while (ret != 0 && retry++ < SDIOH_API_ACCESS_RETRY_LIMIT); + + if (ret != 0) + brcmf_err("failed with %d\n", ret); + + return ret; +} + +u8 brcmf_sdio_regrb(struct brcmf_sdio_dev *sdiodev, u32 addr, int *ret) +{ + u8 data; + int retval; + + brcmf_dbg(SDIO, "addr:0x%08x\n", addr); + retval = brcmf_sdio_regrw_helper(sdiodev, addr, &data, false); + brcmf_dbg(SDIO, "data:0x%02x\n", data); + + if (ret) + *ret = retval; + + return data; +} + +u32 brcmf_sdio_regrl(struct brcmf_sdio_dev *sdiodev, u32 addr, int *ret) +{ + u32 data; + int retval; + + brcmf_dbg(SDIO, "addr:0x%08x\n", addr); + retval = brcmf_sdio_regrw_helper(sdiodev, addr, &data, false); + brcmf_dbg(SDIO, "data:0x%08x\n", data); + + if (ret) + *ret = retval; + + return data; +} + +void brcmf_sdio_regwb(struct brcmf_sdio_dev *sdiodev, u32 addr, + u8 data, int *ret) +{ + int retval; + + brcmf_dbg(SDIO, "addr:0x%08x, data:0x%02x\n", addr, data); + retval = brcmf_sdio_regrw_helper(sdiodev, addr, &data, true); + + if (ret) + *ret = retval; +} + +void brcmf_sdio_regwl(struct brcmf_sdio_dev *sdiodev, u32 addr, + u32 data, int *ret) +{ + int retval; + + brcmf_dbg(SDIO, "addr:0x%08x, data:0x%08x\n", addr, data); + retval = brcmf_sdio_regrw_helper(sdiodev, addr, &data, true); + + if (ret) + *ret = retval; +} + +static int brcmf_sdcard_recv_prepare(struct brcmf_sdio_dev *sdiodev, uint fn, + uint flags, uint width, u32 *addr) +{ + uint bar0 = *addr & ~SBSDIO_SB_OFT_ADDR_MASK; + int err = 0; + + /* Async not implemented yet */ + if (flags & SDIO_REQ_ASYNC) + return -ENOTSUPP; + + if (bar0 != sdiodev->sbwad) { + err = brcmf_sdcard_set_sbaddr_window(sdiodev, bar0); + if (err) + return err; + + sdiodev->sbwad = bar0; + } + + *addr &= SBSDIO_SB_OFT_ADDR_MASK; + + if (width == 4) + *addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; + + return 0; +} + +int +brcmf_sdcard_recv_buf(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, + uint flags, u8 *buf, uint nbytes) +{ + struct sk_buff *mypkt; + int err; + + mypkt = brcmu_pkt_buf_get_skb(nbytes); + if (!mypkt) { + brcmf_err("brcmu_pkt_buf_get_skb failed: len %d\n", + nbytes); + return -EIO; + } + + err = brcmf_sdcard_recv_pkt(sdiodev, addr, fn, flags, mypkt); + if (!err) + memcpy(buf, mypkt->data, nbytes); + + brcmu_pkt_buf_free_skb(mypkt); + return err; +} + +int +brcmf_sdcard_recv_pkt(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, + uint flags, struct sk_buff *pkt) +{ + uint incr_fix; + uint width; + int err = 0; + + brcmf_dbg(SDIO, "fun = %d, addr = 0x%x, size = %d\n", + fn, addr, pkt->len); + + width = (flags & SDIO_REQ_4BYTE) ? 4 : 2; + err = brcmf_sdcard_recv_prepare(sdiodev, fn, flags, width, &addr); + if (err) + goto done; + + incr_fix = (flags & SDIO_REQ_FIXED) ? SDIOH_DATA_FIX : SDIOH_DATA_INC; + err = brcmf_sdioh_request_buffer(sdiodev, incr_fix, SDIOH_READ, + fn, addr, pkt); + +done: + return err; +} + +int brcmf_sdcard_recv_chain(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, + uint flags, struct sk_buff_head *pktq) +{ + uint incr_fix; + uint width; + int err = 0; + + brcmf_dbg(SDIO, "fun = %d, addr = 0x%x, size = %d\n", + fn, addr, pktq->qlen); + + width = (flags & SDIO_REQ_4BYTE) ? 4 : 2; + err = brcmf_sdcard_recv_prepare(sdiodev, fn, flags, width, &addr); + if (err) + goto done; + + incr_fix = (flags & SDIO_REQ_FIXED) ? SDIOH_DATA_FIX : SDIOH_DATA_INC; + err = brcmf_sdioh_request_chain(sdiodev, incr_fix, SDIOH_READ, fn, addr, + pktq); + +done: + return err; +} + +int +brcmf_sdcard_send_buf(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, + uint flags, u8 *buf, uint nbytes) +{ + struct sk_buff *mypkt; + int err; + + mypkt = brcmu_pkt_buf_get_skb(nbytes); + if (!mypkt) { + brcmf_err("brcmu_pkt_buf_get_skb failed: len %d\n", + nbytes); + return -EIO; + } + + memcpy(mypkt->data, buf, nbytes); + err = brcmf_sdcard_send_pkt(sdiodev, addr, fn, flags, mypkt); + + brcmu_pkt_buf_free_skb(mypkt); + return err; + +} + +int +brcmf_sdcard_send_pkt(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, + uint flags, struct sk_buff *pkt) +{ + uint incr_fix; + uint width; + uint bar0 = addr & ~SBSDIO_SB_OFT_ADDR_MASK; + int err = 0; + + brcmf_dbg(SDIO, "fun = %d, addr = 0x%x, size = %d\n", + fn, addr, pkt->len); + + /* Async not implemented yet */ + if (flags & SDIO_REQ_ASYNC) + return -ENOTSUPP; + + if (bar0 != sdiodev->sbwad) { + err = brcmf_sdcard_set_sbaddr_window(sdiodev, bar0); + if (err) + goto done; + + sdiodev->sbwad = bar0; + } + + addr &= SBSDIO_SB_OFT_ADDR_MASK; + + incr_fix = (flags & SDIO_REQ_FIXED) ? SDIOH_DATA_FIX : SDIOH_DATA_INC; + width = (flags & SDIO_REQ_4BYTE) ? 4 : 2; + if (width == 4) + addr |= SBSDIO_SB_ACCESS_2_4B_FLAG; + + err = brcmf_sdioh_request_buffer(sdiodev, incr_fix, SDIOH_WRITE, fn, + addr, pkt); + +done: + return err; +} + +int +brcmf_sdio_ramrw(struct brcmf_sdio_dev *sdiodev, bool write, u32 address, + u8 *data, uint size) +{ + int bcmerror = 0; + struct sk_buff *pkt; + u32 sdaddr; + uint dsize; + + dsize = min_t(uint, SBSDIO_SB_OFT_ADDR_LIMIT, size); + pkt = dev_alloc_skb(dsize); + if (!pkt) { + brcmf_err("dev_alloc_skb failed: len %d\n", dsize); + return -EIO; + } + pkt->priority = 0; + + /* Determine initial transfer parameters */ + sdaddr = address & SBSDIO_SB_OFT_ADDR_MASK; + if ((sdaddr + size) & SBSDIO_SBWINDOW_MASK) + dsize = (SBSDIO_SB_OFT_ADDR_LIMIT - sdaddr); + else + dsize = size; + + sdio_claim_host(sdiodev->func[1]); + + /* Do the transfer(s) */ + while (size) { + /* Set the backplane window to include the start address */ + bcmerror = brcmf_sdcard_set_sbaddr_window(sdiodev, address); + if (bcmerror) + break; + + brcmf_dbg(SDIO, "%s %d bytes at offset 0x%08x in window 0x%08x\n", + write ? "write" : "read", dsize, + sdaddr, address & SBSDIO_SBWINDOW_MASK); + + sdaddr &= SBSDIO_SB_OFT_ADDR_MASK; + sdaddr |= SBSDIO_SB_ACCESS_2_4B_FLAG; + + skb_put(pkt, dsize); + if (write) + memcpy(pkt->data, data, dsize); + bcmerror = brcmf_sdioh_request_buffer(sdiodev, SDIOH_DATA_INC, + write, SDIO_FUNC_1, + sdaddr, pkt); + if (bcmerror) { + brcmf_err("membytes transfer failed\n"); + break; + } + if (!write) + memcpy(data, pkt->data, dsize); + skb_trim(pkt, dsize); + + /* Adjust for next transfer (if any) */ + size -= dsize; + if (size) { + data += dsize; + address += dsize; + sdaddr = 0; + dsize = min_t(uint, SBSDIO_SB_OFT_ADDR_LIMIT, size); + } + } + + dev_kfree_skb(pkt); + + /* Return the window to backplane enumeration space for core access */ + if (brcmf_sdcard_set_sbaddr_window(sdiodev, sdiodev->sbwad)) + brcmf_err("FAILED to set window back to 0x%x\n", + sdiodev->sbwad); + + sdio_release_host(sdiodev->func[1]); + + return bcmerror; +} + +int brcmf_sdcard_abort(struct brcmf_sdio_dev *sdiodev, uint fn) +{ + char t_func = (char)fn; + brcmf_dbg(SDIO, "Enter\n"); + + /* issue abort cmd52 command through F0 */ + brcmf_sdioh_request_byte(sdiodev, SDIOH_WRITE, SDIO_FUNC_0, + SDIO_CCCR_ABORT, &t_func); + + brcmf_dbg(SDIO, "Exit\n"); + return 0; +} + +int brcmf_sdio_probe(struct brcmf_sdio_dev *sdiodev) +{ + u32 regs = 0; + int ret = 0; + + ret = brcmf_sdioh_attach(sdiodev); + if (ret) + goto out; + + regs = SI_ENUM_BASE; + + /* try to attach to the target device */ + sdiodev->bus = brcmf_sdbrcm_probe(regs, sdiodev); + if (!sdiodev->bus) { + brcmf_err("device attach failed\n"); + ret = -ENODEV; + goto out; + } + +out: + if (ret) + brcmf_sdio_remove(sdiodev); + + return ret; +} +EXPORT_SYMBOL(brcmf_sdio_probe); + +int brcmf_sdio_remove(struct brcmf_sdio_dev *sdiodev) +{ + sdiodev->bus_if->state = BRCMF_BUS_DOWN; + + if (sdiodev->bus) { + brcmf_sdbrcm_disconnect(sdiodev->bus); + sdiodev->bus = NULL; + } + + brcmf_sdioh_detach(sdiodev); + + sdiodev->sbwad = 0; + + return 0; +} +EXPORT_SYMBOL(brcmf_sdio_remove); + +void brcmf_sdio_wdtmr_enable(struct brcmf_sdio_dev *sdiodev, bool enable) +{ + if (enable) + brcmf_sdbrcm_wd_timer(sdiodev->bus, BRCMF_WD_POLL_MS); + else + brcmf_sdbrcm_wd_timer(sdiodev->bus, 0); +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c new file mode 100644 index 0000000..e4f3d8f --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/bcmsdh_sdmmc.c @@ -0,0 +1,635 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* request_irq() */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include "sdio_host.h" +#include "dhd_dbg.h" +#include "dhd_bus.h" + +#define SDIO_VENDOR_ID_BROADCOM 0x02d0 + +#define DMA_ALIGN_MASK 0x03 + +#define SDIO_DEVICE_ID_BROADCOM_43143 43143 +#define SDIO_DEVICE_ID_BROADCOM_43241 0x4324 +#define SDIO_DEVICE_ID_BROADCOM_4329 0x4329 +#define SDIO_DEVICE_ID_BROADCOM_4330 0x4330 +#define SDIO_DEVICE_ID_BROADCOM_4334 0x4334 +#define SDIO_DEVICE_ID_BROADCOM_4335 0x4335 + +#define SDIO_FUNC1_BLOCKSIZE 64 +#define SDIO_FUNC2_BLOCKSIZE 512 + +/* devices we support, null terminated */ +static const struct sdio_device_id brcmf_sdmmc_ids[] = { + {SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_43143)}, + {SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_43241)}, + {SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4329)}, + {SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4330)}, + {SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4334)}, + {SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4335)}, + { /* end: all zeroes */ }, +}; +MODULE_DEVICE_TABLE(sdio, brcmf_sdmmc_ids); + +static struct brcmfmac_sdio_platform_data *brcmfmac_sdio_pdata; + + +static bool +brcmf_pm_resume_error(struct brcmf_sdio_dev *sdiodev) +{ + bool is_err = false; +#if defined(CONFIG_PM_SLEEP) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) + is_err = atomic_read(&sdiodev->suspend); +#endif + return is_err; +} + +static void +brcmf_pm_resume_wait(struct brcmf_sdio_dev *sdiodev, wait_queue_head_t *wq) +{ +#if defined(CONFIG_PM_SLEEP) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) + int retry = 0; + while (atomic_read(&sdiodev->suspend) && retry++ != 30) + wait_event_timeout(*wq, false, HZ/100); +#endif +} + +static inline int brcmf_sdioh_f0_write_byte(struct brcmf_sdio_dev *sdiodev, + uint regaddr, u8 *byte) +{ + struct sdio_func *sdfunc = sdiodev->func[0]; + int err_ret; + + /* + * Can only directly write to some F0 registers. + * Handle F2 enable/disable and Abort command + * as a special case. + */ + if (regaddr == SDIO_CCCR_IOEx) { + sdfunc = sdiodev->func[2]; + if (sdfunc) { + if (*byte & SDIO_FUNC_ENABLE_2) { + /* Enable Function 2 */ + err_ret = sdio_enable_func(sdfunc); + if (err_ret) + brcmf_err("enable F2 failed:%d\n", + err_ret); + } else { + /* Disable Function 2 */ + err_ret = sdio_disable_func(sdfunc); + if (err_ret) + brcmf_err("Disable F2 failed:%d\n", + err_ret); + } + } + } else if ((regaddr == SDIO_CCCR_ABORT) || + (regaddr == SDIO_CCCR_IENx)) { + sdfunc = kmemdup(sdiodev->func[0], sizeof(struct sdio_func), + GFP_KERNEL); + if (!sdfunc) + return -ENOMEM; + sdfunc->num = 0; + sdio_writeb(sdfunc, *byte, regaddr, &err_ret); + kfree(sdfunc); + } else if (regaddr < 0xF0) { + brcmf_err("F0 Wr:0x%02x: write disallowed\n", regaddr); + err_ret = -EPERM; + } else { + sdio_f0_writeb(sdfunc, *byte, regaddr, &err_ret); + } + + return err_ret; +} + +int brcmf_sdioh_request_byte(struct brcmf_sdio_dev *sdiodev, uint rw, uint func, + uint regaddr, u8 *byte) +{ + int err_ret; + + brcmf_dbg(SDIO, "rw=%d, func=%d, addr=0x%05x\n", rw, func, regaddr); + + brcmf_pm_resume_wait(sdiodev, &sdiodev->request_byte_wait); + if (brcmf_pm_resume_error(sdiodev)) + return -EIO; + + if (rw && func == 0) { + /* handle F0 separately */ + err_ret = brcmf_sdioh_f0_write_byte(sdiodev, regaddr, byte); + } else { + if (rw) /* CMD52 Write */ + sdio_writeb(sdiodev->func[func], *byte, regaddr, + &err_ret); + else if (func == 0) { + *byte = sdio_f0_readb(sdiodev->func[func], regaddr, + &err_ret); + } else { + *byte = sdio_readb(sdiodev->func[func], regaddr, + &err_ret); + } + } + + if (err_ret) + brcmf_err("Failed to %s byte F%d:@0x%05x=%02x, Err: %d\n", + rw ? "write" : "read", func, regaddr, *byte, err_ret); + + return err_ret; +} + +int brcmf_sdioh_request_word(struct brcmf_sdio_dev *sdiodev, + uint rw, uint func, uint addr, u32 *word, + uint nbytes) +{ + int err_ret = -EIO; + + if (func == 0) { + brcmf_err("Only CMD52 allowed to F0\n"); + return -EINVAL; + } + + brcmf_dbg(SDIO, "rw=%d, func=%d, addr=0x%05x, nbytes=%d\n", + rw, func, addr, nbytes); + + brcmf_pm_resume_wait(sdiodev, &sdiodev->request_word_wait); + if (brcmf_pm_resume_error(sdiodev)) + return -EIO; + + if (rw) { /* CMD52 Write */ + if (nbytes == 4) + sdio_writel(sdiodev->func[func], *word, addr, + &err_ret); + else if (nbytes == 2) + sdio_writew(sdiodev->func[func], (*word & 0xFFFF), + addr, &err_ret); + else + brcmf_err("Invalid nbytes: %d\n", nbytes); + } else { /* CMD52 Read */ + if (nbytes == 4) + *word = sdio_readl(sdiodev->func[func], addr, &err_ret); + else if (nbytes == 2) + *word = sdio_readw(sdiodev->func[func], addr, + &err_ret) & 0xFFFF; + else + brcmf_err("Invalid nbytes: %d\n", nbytes); + } + + if (err_ret) + brcmf_err("Failed to %s word, Err: 0x%08x\n", + rw ? "write" : "read", err_ret); + + return err_ret; +} + +/* precondition: host controller is claimed */ +static int +brcmf_sdioh_request_data(struct brcmf_sdio_dev *sdiodev, uint write, bool fifo, + uint func, uint addr, struct sk_buff *pkt, uint pktlen) +{ + int err_ret = 0; + + if ((write) && (!fifo)) { + err_ret = sdio_memcpy_toio(sdiodev->func[func], addr, + ((u8 *) (pkt->data)), pktlen); + } else if (write) { + err_ret = sdio_memcpy_toio(sdiodev->func[func], addr, + ((u8 *) (pkt->data)), pktlen); + } else if (fifo) { + err_ret = sdio_readsb(sdiodev->func[func], + ((u8 *) (pkt->data)), addr, pktlen); + } else { + err_ret = sdio_memcpy_fromio(sdiodev->func[func], + ((u8 *) (pkt->data)), + addr, pktlen); + } + + return err_ret; +} + +/* + * This function takes a queue of packets. The packets on the queue + * are assumed to be properly aligned by the caller. + */ +int +brcmf_sdioh_request_chain(struct brcmf_sdio_dev *sdiodev, uint fix_inc, + uint write, uint func, uint addr, + struct sk_buff_head *pktq) +{ + bool fifo = (fix_inc == SDIOH_DATA_FIX); + u32 SGCount = 0; + int err_ret = 0; + + struct sk_buff *pkt; + + brcmf_dbg(SDIO, "Enter\n"); + + brcmf_pm_resume_wait(sdiodev, &sdiodev->request_chain_wait); + if (brcmf_pm_resume_error(sdiodev)) + return -EIO; + + skb_queue_walk(pktq, pkt) { + uint pkt_len = pkt->len; + pkt_len += 3; + pkt_len &= 0xFFFFFFFC; + + err_ret = brcmf_sdioh_request_data(sdiodev, write, fifo, func, + addr, pkt, pkt_len); + if (err_ret) { + brcmf_err("%s FAILED %p[%d], addr=0x%05x, pkt_len=%d, ERR=0x%08x\n", + write ? "TX" : "RX", pkt, SGCount, addr, + pkt_len, err_ret); + } else { + brcmf_dbg(SDIO, "%s xfr'd %p[%d], addr=0x%05x, len=%d\n", + write ? "TX" : "RX", pkt, SGCount, addr, + pkt_len); + } + if (!fifo) + addr += pkt_len; + + SGCount++; + } + + brcmf_dbg(SDIO, "Exit\n"); + return err_ret; +} + +/* + * This function takes a single DMA-able packet. + */ +int brcmf_sdioh_request_buffer(struct brcmf_sdio_dev *sdiodev, + uint fix_inc, uint write, uint func, uint addr, + struct sk_buff *pkt) +{ + int status; + uint pkt_len; + bool fifo = (fix_inc == SDIOH_DATA_FIX); + + brcmf_dbg(SDIO, "Enter\n"); + + if (pkt == NULL) + return -EINVAL; + pkt_len = pkt->len; + + brcmf_pm_resume_wait(sdiodev, &sdiodev->request_buffer_wait); + if (brcmf_pm_resume_error(sdiodev)) + return -EIO; + + pkt_len += 3; + pkt_len &= (uint)~3; + + status = brcmf_sdioh_request_data(sdiodev, write, fifo, func, + addr, pkt, pkt_len); + if (status) { + brcmf_err("%s FAILED %p, addr=0x%05x, pkt_len=%d, ERR=0x%08x\n", + write ? "TX" : "RX", pkt, addr, pkt_len, status); + } else { + brcmf_dbg(SDIO, "%s xfr'd %p, addr=0x%05x, len=%d\n", + write ? "TX" : "RX", pkt, addr, pkt_len); + } + + return status; +} + +static int brcmf_sdioh_get_cisaddr(struct brcmf_sdio_dev *sdiodev, u32 regaddr) +{ + /* read 24 bits and return valid 17 bit addr */ + int i, ret; + u32 scratch, regdata; + __le32 scratch_le; + u8 *ptr = (u8 *)&scratch_le; + + for (i = 0; i < 3; i++) { + regdata = brcmf_sdio_regrl(sdiodev, regaddr, &ret); + if (ret != 0) + brcmf_err("Can't read!\n"); + + *ptr++ = (u8) regdata; + regaddr++; + } + + /* Only the lower 17-bits are valid */ + scratch = le32_to_cpu(scratch_le); + scratch &= 0x0001FFFF; + return scratch; +} + +static int brcmf_sdioh_enablefuncs(struct brcmf_sdio_dev *sdiodev) +{ + int err_ret; + u32 fbraddr; + u8 func; + + brcmf_dbg(SDIO, "\n"); + + /* Get the Card's common CIS address */ + sdiodev->func_cis_ptr[0] = brcmf_sdioh_get_cisaddr(sdiodev, + SDIO_CCCR_CIS); + brcmf_dbg(SDIO, "Card's Common CIS Ptr = 0x%x\n", + sdiodev->func_cis_ptr[0]); + + /* Get the Card's function CIS (for each function) */ + for (fbraddr = SDIO_FBR_BASE(1), func = 1; + func <= sdiodev->num_funcs; func++, fbraddr += SDIOD_FBR_SIZE) { + sdiodev->func_cis_ptr[func] = + brcmf_sdioh_get_cisaddr(sdiodev, SDIO_FBR_CIS + fbraddr); + brcmf_dbg(SDIO, "Function %d CIS Ptr = 0x%x\n", + func, sdiodev->func_cis_ptr[func]); + } + + /* Enable Function 1 */ + err_ret = sdio_enable_func(sdiodev->func[1]); + if (err_ret) + brcmf_err("Failed to enable F1 Err: 0x%08x\n", err_ret); + + return false; +} + +/* + * Public entry points & extern's + */ +int brcmf_sdioh_attach(struct brcmf_sdio_dev *sdiodev) +{ + int err_ret = 0; + + brcmf_dbg(SDIO, "\n"); + + sdiodev->num_funcs = 2; + + sdio_claim_host(sdiodev->func[1]); + + err_ret = sdio_set_block_size(sdiodev->func[1], SDIO_FUNC1_BLOCKSIZE); + if (err_ret) { + brcmf_err("Failed to set F1 blocksize\n"); + goto out; + } + + err_ret = sdio_set_block_size(sdiodev->func[2], SDIO_FUNC2_BLOCKSIZE); + if (err_ret) { + brcmf_err("Failed to set F2 blocksize\n"); + goto out; + } + + brcmf_sdioh_enablefuncs(sdiodev); + +out: + sdio_release_host(sdiodev->func[1]); + brcmf_dbg(SDIO, "Done\n"); + return err_ret; +} + +void brcmf_sdioh_detach(struct brcmf_sdio_dev *sdiodev) +{ + brcmf_dbg(SDIO, "\n"); + + /* Disable Function 2 */ + sdio_claim_host(sdiodev->func[2]); + sdio_disable_func(sdiodev->func[2]); + sdio_release_host(sdiodev->func[2]); + + /* Disable Function 1 */ + sdio_claim_host(sdiodev->func[1]); + sdio_disable_func(sdiodev->func[1]); + sdio_release_host(sdiodev->func[1]); + +} + +static int brcmf_ops_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + int err; + struct brcmf_sdio_dev *sdiodev; + struct brcmf_bus *bus_if; + + brcmf_dbg(SDIO, "Enter\n"); + brcmf_dbg(SDIO, "Class=%x\n", func->class); + brcmf_dbg(SDIO, "sdio vendor ID: 0x%04x\n", func->vendor); + brcmf_dbg(SDIO, "sdio device ID: 0x%04x\n", func->device); + brcmf_dbg(SDIO, "Function#: %d\n", func->num); + + /* Consume func num 1 but dont do anything with it. */ + if (func->num == 1) + return 0; + + /* Ignore anything but func 2 */ + if (func->num != 2) + return -ENODEV; + + bus_if = kzalloc(sizeof(struct brcmf_bus), GFP_KERNEL); + if (!bus_if) + return -ENOMEM; + sdiodev = kzalloc(sizeof(struct brcmf_sdio_dev), GFP_KERNEL); + if (!sdiodev) { + kfree(bus_if); + return -ENOMEM; + } + + sdiodev->func[0] = func->card->sdio_func[0]; + sdiodev->func[1] = func->card->sdio_func[0]; + sdiodev->func[2] = func; + + sdiodev->bus_if = bus_if; + bus_if->bus_priv.sdio = sdiodev; + bus_if->align = BRCMF_SDALIGN; + dev_set_drvdata(&func->dev, bus_if); + dev_set_drvdata(&sdiodev->func[1]->dev, bus_if); + sdiodev->dev = &sdiodev->func[1]->dev; + sdiodev->pdata = brcmfmac_sdio_pdata; + + atomic_set(&sdiodev->suspend, false); + init_waitqueue_head(&sdiodev->request_byte_wait); + init_waitqueue_head(&sdiodev->request_word_wait); + init_waitqueue_head(&sdiodev->request_chain_wait); + init_waitqueue_head(&sdiodev->request_buffer_wait); + + brcmf_dbg(SDIO, "F2 found, calling brcmf_sdio_probe...\n"); + err = brcmf_sdio_probe(sdiodev); + if (err) { + brcmf_err("F2 error, probe failed %d...\n", err); + goto fail; + } + brcmf_dbg(SDIO, "F2 init completed...\n"); + return 0; + +fail: + dev_set_drvdata(&func->dev, NULL); + dev_set_drvdata(&sdiodev->func[1]->dev, NULL); + kfree(sdiodev); + kfree(bus_if); + return err; +} + +static void brcmf_ops_sdio_remove(struct sdio_func *func) +{ + struct brcmf_bus *bus_if; + struct brcmf_sdio_dev *sdiodev; + + brcmf_dbg(SDIO, "Enter\n"); + brcmf_dbg(SDIO, "sdio vendor ID: 0x%04x\n", func->vendor); + brcmf_dbg(SDIO, "sdio device ID: 0x%04x\n", func->device); + brcmf_dbg(SDIO, "Function: %d\n", func->num); + + if (func->num != 1 && func->num != 2) + return; + + bus_if = dev_get_drvdata(&func->dev); + if (bus_if) { + sdiodev = bus_if->bus_priv.sdio; + brcmf_sdio_remove(sdiodev); + + dev_set_drvdata(&sdiodev->func[1]->dev, NULL); + dev_set_drvdata(&sdiodev->func[2]->dev, NULL); + + kfree(bus_if); + kfree(sdiodev); + } + + brcmf_dbg(SDIO, "Exit\n"); +} + +#if defined(CONFIG_PM_SLEEP) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) +static int brcmf_sdio_suspend(struct device *dev) +{ + mmc_pm_flag_t sdio_flags; + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; + int ret = 0; + + brcmf_dbg(SDIO, "\n"); + + atomic_set(&sdiodev->suspend, true); + + sdio_flags = sdio_get_host_pm_caps(sdiodev->func[1]); + if (!(sdio_flags & MMC_PM_KEEP_POWER)) { + brcmf_err("Host can't keep power while suspended\n"); + return -EINVAL; + } + + ret = sdio_set_host_pm_flags(sdiodev->func[1], MMC_PM_KEEP_POWER); + if (ret) { + brcmf_err("Failed to set pm_flags\n"); + return ret; + } + + brcmf_sdio_wdtmr_enable(sdiodev, false); + + return ret; +} + +static int brcmf_sdio_resume(struct device *dev) +{ + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; + + brcmf_sdio_wdtmr_enable(sdiodev, true); + atomic_set(&sdiodev->suspend, false); + return 0; +} + +static const struct dev_pm_ops brcmf_sdio_pm_ops = { + .suspend = brcmf_sdio_suspend, + .resume = brcmf_sdio_resume, +}; +#endif /* CONFIG_PM_SLEEP */ + +static struct sdio_driver brcmf_sdmmc_driver = { + .probe = brcmf_ops_sdio_probe, + .remove = brcmf_ops_sdio_remove, + .name = BRCMFMAC_SDIO_PDATA_NAME, + .id_table = brcmf_sdmmc_ids, +#if defined(CONFIG_PM_SLEEP) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) + .drv = { + .pm = &brcmf_sdio_pm_ops, + }, +#endif /* CONFIG_PM_SLEEP */ +}; + +static int brcmf_sdio_pd_probe(struct platform_device *pdev) +{ + brcmf_dbg(SDIO, "Enter\n"); + + brcmfmac_sdio_pdata = pdev->dev.platform_data; + + if (brcmfmac_sdio_pdata->power_on) + brcmfmac_sdio_pdata->power_on(); + + return 0; +} + +static int brcmf_sdio_pd_remove(struct platform_device *pdev) +{ + brcmf_dbg(SDIO, "Enter\n"); + + if (brcmfmac_sdio_pdata->power_off) + brcmfmac_sdio_pdata->power_off(); + + sdio_unregister_driver(&brcmf_sdmmc_driver); + + return 0; +} + +static struct platform_driver brcmf_sdio_pd = { + .remove = brcmf_sdio_pd_remove, + .driver = { + .name = BRCMFMAC_SDIO_PDATA_NAME + } +}; + +void brcmf_sdio_register(void) +{ + int ret; + + ret = sdio_register_driver(&brcmf_sdmmc_driver); + if (ret) + brcmf_err("sdio_register_driver failed: %d\n", ret); +} + +void brcmf_sdio_exit(void) +{ + brcmf_dbg(SDIO, "Enter\n"); + + if (brcmfmac_sdio_pdata) + platform_driver_unregister(&brcmf_sdio_pd); + else + sdio_unregister_driver(&brcmf_sdmmc_driver); +} + +void __init brcmf_sdio_init(void) +{ + int ret; + + brcmf_dbg(SDIO, "Enter\n"); + + ret = platform_driver_probe(&brcmf_sdio_pd, brcmf_sdio_pd_probe); + if (ret == -ENODEV) + brcmf_dbg(SDIO, "No platform data available.\n"); +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/btcoex.c b/drivers/net/wireless/brcm80211/brcmfmac/btcoex.c new file mode 100644 index 0000000..0cb591b --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/btcoex.c @@ -0,0 +1,497 @@ +/* + * Copyright (c) 2013 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include "fwil.h" +#include "fwil_types.h" +#include "btcoex.h" +#include "p2p.h" +#include "wl_cfg80211.h" + +/* T1 start SCO/eSCO priority suppression */ +#define BRCMF_BTCOEX_OPPR_WIN_TIME 2000 + +/* BT registers values during DHCP */ +#define BRCMF_BT_DHCP_REG50 0x8022 +#define BRCMF_BT_DHCP_REG51 0 +#define BRCMF_BT_DHCP_REG64 0 +#define BRCMF_BT_DHCP_REG65 0 +#define BRCMF_BT_DHCP_REG71 0 +#define BRCMF_BT_DHCP_REG66 0x2710 +#define BRCMF_BT_DHCP_REG41 0x33 +#define BRCMF_BT_DHCP_REG68 0x190 + +/* number of samples for SCO detection */ +#define BRCMF_BT_SCO_SAMPLES 12 + +/** +* enum brcmf_btcoex_state - BT coex DHCP state machine states +* @BRCMF_BT_DHCP_IDLE: DCHP is idle +* @BRCMF_BT_DHCP_START: DHCP started, wait before +* boosting wifi priority +* @BRCMF_BT_DHCP_OPPR_WIN: graceful DHCP opportunity ended, +* boost wifi priority +* @BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT: wifi priority boost end, +* restore defaults +*/ +enum brcmf_btcoex_state { + BRCMF_BT_DHCP_IDLE, + BRCMF_BT_DHCP_START, + BRCMF_BT_DHCP_OPPR_WIN, + BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT +}; + +/** + * struct brcmf_btcoex_info - BT coex related information + * @vif: interface for which request was done. + * @timer: timer for DHCP state machine + * @timeout: configured timeout. + * @timer_on: DHCP timer active + * @dhcp_done: DHCP finished before T1/T2 timer expiration + * @bt_state: DHCP state machine state + * @work: DHCP state machine work + * @cfg: driver private data for cfg80211 interface + * @reg66: saved value of btc_params 66 + * @reg41: saved value of btc_params 41 + * @reg68: saved value of btc_params 68 + * @saved_regs_part1: flag indicating regs 66,41,68 + * have been saved + * @reg51: saved value of btc_params 51 + * @reg64: saved value of btc_params 64 + * @reg65: saved value of btc_params 65 + * @reg71: saved value of btc_params 71 + * @saved_regs_part1: flag indicating regs 50,51,64,65,71 + * have been saved + */ +struct brcmf_btcoex_info { + struct brcmf_cfg80211_vif *vif; + struct timer_list timer; + u16 timeout; + bool timer_on; + bool dhcp_done; + enum brcmf_btcoex_state bt_state; + struct work_struct work; + struct brcmf_cfg80211_info *cfg; + u32 reg66; + u32 reg41; + u32 reg68; + bool saved_regs_part1; + u32 reg50; + u32 reg51; + u32 reg64; + u32 reg65; + u32 reg71; + bool saved_regs_part2; +}; + +/** + * brcmf_btcoex_params_write() - write btc_params firmware variable + * @ifp: interface + * @addr: btc_params register number + * @data: data to write + */ +static s32 brcmf_btcoex_params_write(struct brcmf_if *ifp, u32 addr, u32 data) +{ + struct { + __le32 addr; + __le32 data; + } reg_write; + + reg_write.addr = cpu_to_le32(addr); + reg_write.data = cpu_to_le32(data); + return brcmf_fil_iovar_data_set(ifp, "btc_params", + ®_write, sizeof(reg_write)); +} + +/** + * brcmf_btcoex_params_read() - read btc_params firmware variable + * @ifp: interface + * @addr: btc_params register number + * @data: read data + */ +static s32 brcmf_btcoex_params_read(struct brcmf_if *ifp, u32 addr, u32 *data) +{ + *data = addr; + + return brcmf_fil_iovar_int_get(ifp, "btc_params", data); +} + +/** + * brcmf_btcoex_boost_wifi() - control BT SCO/eSCO parameters + * @btci: BT coex info + * @trump_sco: + * true - set SCO/eSCO parameters for compatibility + * during DHCP window + * false - restore saved parameter values + * + * Enhanced BT COEX settings for eSCO compatibility during DHCP window + */ +static void brcmf_btcoex_boost_wifi(struct brcmf_btcoex_info *btci, + bool trump_sco) +{ + struct brcmf_if *ifp = btci->cfg->pub->iflist[0]; + + if (trump_sco && !btci->saved_regs_part2) { + /* this should reduce eSCO agressive + * retransmit w/o breaking it + */ + + /* save current */ + brcmf_dbg(TRACE, "new SCO/eSCO coex algo {save & override}\n"); + brcmf_btcoex_params_read(ifp, 50, &btci->reg50); + brcmf_btcoex_params_read(ifp, 51, &btci->reg51); + brcmf_btcoex_params_read(ifp, 64, &btci->reg64); + brcmf_btcoex_params_read(ifp, 65, &btci->reg65); + brcmf_btcoex_params_read(ifp, 71, &btci->reg71); + + btci->saved_regs_part2 = true; + brcmf_dbg(TRACE, + "saved bt_params[50,51,64,65,71]: 0x%x 0x%x 0x%x 0x%x 0x%x\n", + btci->reg50, btci->reg51, btci->reg64, + btci->reg65, btci->reg71); + + /* pacify the eSco */ + brcmf_btcoex_params_write(ifp, 50, BRCMF_BT_DHCP_REG50); + brcmf_btcoex_params_write(ifp, 51, BRCMF_BT_DHCP_REG51); + brcmf_btcoex_params_write(ifp, 64, BRCMF_BT_DHCP_REG64); + brcmf_btcoex_params_write(ifp, 65, BRCMF_BT_DHCP_REG65); + brcmf_btcoex_params_write(ifp, 71, BRCMF_BT_DHCP_REG71); + + } else if (btci->saved_regs_part2) { + /* restore previously saved bt params */ + brcmf_dbg(TRACE, "Do new SCO/eSCO coex algo {restore}\n"); + brcmf_btcoex_params_write(ifp, 50, btci->reg50); + brcmf_btcoex_params_write(ifp, 51, btci->reg51); + brcmf_btcoex_params_write(ifp, 64, btci->reg64); + brcmf_btcoex_params_write(ifp, 65, btci->reg65); + brcmf_btcoex_params_write(ifp, 71, btci->reg71); + + brcmf_dbg(TRACE, + "restored bt_params[50,51,64,65,71]: 0x%x 0x%x 0x%x 0x%x 0x%x\n", + btci->reg50, btci->reg51, btci->reg64, + btci->reg65, btci->reg71); + + btci->saved_regs_part2 = false; + } else { + brcmf_err("attempted to restore not saved BTCOEX params\n"); + } +} + +/** + * brcmf_btcoex_is_sco_active() - check if SCO/eSCO is active + * @ifp: interface + * + * return: true if SCO/eSCO session is active + */ +static bool brcmf_btcoex_is_sco_active(struct brcmf_if *ifp) +{ + int ioc_res = 0; + bool res = false; + int sco_id_cnt = 0; + u32 param27; + int i; + + for (i = 0; i < BRCMF_BT_SCO_SAMPLES; i++) { + ioc_res = brcmf_btcoex_params_read(ifp, 27, ¶m27); + + if (ioc_res < 0) { + brcmf_err("ioc read btc params error\n"); + break; + } + + brcmf_dbg(TRACE, "sample[%d], btc_params 27:%x\n", i, param27); + + if ((param27 & 0x6) == 2) { /* count both sco & esco */ + sco_id_cnt++; + } + + if (sco_id_cnt > 2) { + brcmf_dbg(TRACE, + "sco/esco detected, pkt id_cnt:%d samples:%d\n", + sco_id_cnt, i); + res = true; + break; + } + } + brcmf_dbg(TRACE, "exit: result=%d\n", res); + return res; +} + +/** + * btcmf_btcoex_save_part1() - save first step parameters. + */ +static void btcmf_btcoex_save_part1(struct brcmf_btcoex_info *btci) +{ + struct brcmf_if *ifp = btci->vif->ifp; + + if (!btci->saved_regs_part1) { + /* Retrieve and save original reg value */ + brcmf_btcoex_params_read(ifp, 66, &btci->reg66); + brcmf_btcoex_params_read(ifp, 41, &btci->reg41); + brcmf_btcoex_params_read(ifp, 68, &btci->reg68); + btci->saved_regs_part1 = true; + brcmf_dbg(TRACE, + "saved btc_params regs (66,41,68) 0x%x 0x%x 0x%x\n", + btci->reg66, btci->reg41, + btci->reg68); + } +} + +/** + * brcmf_btcoex_restore_part1() - restore first step parameters. + */ +static void brcmf_btcoex_restore_part1(struct brcmf_btcoex_info *btci) +{ + struct brcmf_if *ifp; + + if (btci->saved_regs_part1) { + btci->saved_regs_part1 = false; + ifp = btci->vif->ifp; + brcmf_btcoex_params_write(ifp, 66, btci->reg66); + brcmf_btcoex_params_write(ifp, 41, btci->reg41); + brcmf_btcoex_params_write(ifp, 68, btci->reg68); + brcmf_dbg(TRACE, + "restored btc_params regs {66,41,68} 0x%x 0x%x 0x%x\n", + btci->reg66, btci->reg41, + btci->reg68); + } +} + +/** + * brcmf_btcoex_timerfunc() - BT coex timer callback + */ +static void brcmf_btcoex_timerfunc(ulong data) +{ + struct brcmf_btcoex_info *bt_local = (struct brcmf_btcoex_info *)data; + brcmf_dbg(TRACE, "enter\n"); + + bt_local->timer_on = false; + schedule_work(&bt_local->work); +} + +/** + * brcmf_btcoex_handler() - BT coex state machine work handler + * @work: work + */ +static void brcmf_btcoex_handler(struct work_struct *work) +{ + struct brcmf_btcoex_info *btci; + btci = container_of(work, struct brcmf_btcoex_info, work); + if (btci->timer_on) { + btci->timer_on = false; + del_timer_sync(&btci->timer); + } + + switch (btci->bt_state) { + case BRCMF_BT_DHCP_START: + /* DHCP started provide OPPORTUNITY window + to get DHCP address + */ + brcmf_dbg(TRACE, "DHCP started\n"); + btci->bt_state = BRCMF_BT_DHCP_OPPR_WIN; + if (btci->timeout < BRCMF_BTCOEX_OPPR_WIN_TIME) { + mod_timer(&btci->timer, btci->timer.expires); + } else { + btci->timeout -= BRCMF_BTCOEX_OPPR_WIN_TIME; + mod_timer(&btci->timer, + jiffies + + msecs_to_jiffies(BRCMF_BTCOEX_OPPR_WIN_TIME)); + } + btci->timer_on = true; + break; + + case BRCMF_BT_DHCP_OPPR_WIN: + if (btci->dhcp_done) { + brcmf_dbg(TRACE, "DHCP done before T1 expiration\n"); + goto idle; + } + + /* DHCP is not over yet, start lowering BT priority */ + brcmf_dbg(TRACE, "DHCP T1:%d expired\n", + BRCMF_BTCOEX_OPPR_WIN_TIME); + brcmf_btcoex_boost_wifi(btci, true); + + btci->bt_state = BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT; + mod_timer(&btci->timer, + jiffies + msecs_to_jiffies(btci->timeout)); + btci->timer_on = true; + break; + + case BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT: + if (btci->dhcp_done) + brcmf_dbg(TRACE, "DHCP done before T2 expiration\n"); + else + brcmf_dbg(TRACE, "DHCP T2:%d expired\n", + BRCMF_BT_DHCP_FLAG_FORCE_TIMEOUT); + + goto idle; + + default: + brcmf_err("invalid state=%d !!!\n", btci->bt_state); + goto idle; + } + + return; + +idle: + btci->bt_state = BRCMF_BT_DHCP_IDLE; + btci->timer_on = false; + brcmf_btcoex_boost_wifi(btci, false); + cfg80211_crit_proto_stopped(&btci->vif->wdev, GFP_KERNEL); + brcmf_btcoex_restore_part1(btci); + btci->vif = NULL; +} + +/** + * brcmf_btcoex_attach() - initialize BT coex data + * @cfg: driver private cfg80211 data + * + * return: 0 on success + */ +int brcmf_btcoex_attach(struct brcmf_cfg80211_info *cfg) +{ + struct brcmf_btcoex_info *btci = NULL; + brcmf_dbg(TRACE, "enter\n"); + + btci = kmalloc(sizeof(struct brcmf_btcoex_info), GFP_KERNEL); + if (!btci) + return -ENOMEM; + + btci->bt_state = BRCMF_BT_DHCP_IDLE; + + /* Set up timer for BT */ + btci->timer_on = false; + btci->timeout = BRCMF_BTCOEX_OPPR_WIN_TIME; + init_timer(&btci->timer); + btci->timer.data = (ulong)btci; + btci->timer.function = brcmf_btcoex_timerfunc; + btci->cfg = cfg; + btci->saved_regs_part1 = false; + btci->saved_regs_part2 = false; + + INIT_WORK(&btci->work, brcmf_btcoex_handler); + + cfg->btcoex = btci; + return 0; +} + +/** + * brcmf_btcoex_detach - clean BT coex data + * @cfg: driver private cfg80211 data + */ +void brcmf_btcoex_detach(struct brcmf_cfg80211_info *cfg) +{ + brcmf_dbg(TRACE, "enter\n"); + + if (!cfg->btcoex) + return; + + if (cfg->btcoex->timer_on) { + cfg->btcoex->timer_on = false; + del_timer_sync(&cfg->btcoex->timer); + } + + cancel_work_sync(&cfg->btcoex->work); + + brcmf_btcoex_boost_wifi(cfg->btcoex, false); + brcmf_btcoex_restore_part1(cfg->btcoex); + + kfree(cfg->btcoex); + cfg->btcoex = NULL; +} + +static void brcmf_btcoex_dhcp_start(struct brcmf_btcoex_info *btci) +{ + struct brcmf_if *ifp = btci->vif->ifp; + + btcmf_btcoex_save_part1(btci); + /* set new regs values */ + brcmf_btcoex_params_write(ifp, 66, BRCMF_BT_DHCP_REG66); + brcmf_btcoex_params_write(ifp, 41, BRCMF_BT_DHCP_REG41); + brcmf_btcoex_params_write(ifp, 68, BRCMF_BT_DHCP_REG68); + btci->dhcp_done = false; + btci->bt_state = BRCMF_BT_DHCP_START; + schedule_work(&btci->work); + brcmf_dbg(TRACE, "enable BT DHCP Timer\n"); +} + +static void brcmf_btcoex_dhcp_end(struct brcmf_btcoex_info *btci) +{ + /* Stop any bt timer because DHCP session is done */ + btci->dhcp_done = true; + if (btci->timer_on) { + brcmf_dbg(TRACE, "disable BT DHCP Timer\n"); + btci->timer_on = false; + del_timer_sync(&btci->timer); + + /* schedule worker if transition to IDLE is needed */ + if (btci->bt_state != BRCMF_BT_DHCP_IDLE) { + brcmf_dbg(TRACE, "bt_state:%d\n", + btci->bt_state); + schedule_work(&btci->work); + } + } else { + /* Restore original values */ + brcmf_btcoex_restore_part1(btci); + } +} + +/** + * brcmf_btcoex_set_mode - set BT coex mode + * @cfg: driver private cfg80211 data + * @mode: Wifi-Bluetooth coexistence mode + * + * return: 0 on success + */ +int brcmf_btcoex_set_mode(struct brcmf_cfg80211_vif *vif, + enum brcmf_btcoex_mode mode, u16 duration) +{ + struct brcmf_cfg80211_info *cfg = wiphy_priv(vif->wdev.wiphy); + struct brcmf_btcoex_info *btci = cfg->btcoex; + struct brcmf_if *ifp = cfg->pub->iflist[0]; + + switch (mode) { + case BRCMF_BTCOEX_DISABLED: + brcmf_dbg(TRACE, "DHCP session starts\n"); + if (btci->bt_state != BRCMF_BT_DHCP_IDLE) + return -EBUSY; + /* Start BT timer only for SCO connection */ + if (brcmf_btcoex_is_sco_active(ifp)) { + btci->timeout = duration; + btci->vif = vif; + brcmf_btcoex_dhcp_start(btci); + } + break; + + case BRCMF_BTCOEX_ENABLED: + brcmf_dbg(TRACE, "DHCP session ends\n"); + if (btci->bt_state != BRCMF_BT_DHCP_IDLE && + vif == btci->vif) { + brcmf_btcoex_dhcp_end(btci); + } + break; + default: + brcmf_dbg(TRACE, "Unknown mode, ignored\n"); + } + return 0; +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/btcoex.h b/drivers/net/wireless/brcm80211/brcmfmac/btcoex.h new file mode 100644 index 0000000..19647c6 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/btcoex.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2013 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef WL_BTCOEX_H_ +#define WL_BTCOEX_H_ + +enum brcmf_btcoex_mode { + BRCMF_BTCOEX_DISABLED, + BRCMF_BTCOEX_ENABLED +}; + +int brcmf_btcoex_attach(struct brcmf_cfg80211_info *cfg); +void brcmf_btcoex_detach(struct brcmf_cfg80211_info *cfg); +int brcmf_btcoex_set_mode(struct brcmf_cfg80211_vif *vif, + enum brcmf_btcoex_mode mode, u16 duration); + +#endif /* WL_BTCOEX_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd.h b/drivers/net/wireless/brcm80211/brcmfmac/dhd.h new file mode 100644 index 0000000..28db9cf --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd.h @@ -0,0 +1,631 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/**************** + * Common types * + */ + +#ifndef _BRCMF_H_ +#define _BRCMF_H_ + +#define BRCMF_VERSION_STR "4.218.248.5" + +#include "fweh.h" + +/******************************************************************************* + * IO codes that are interpreted by dongle firmware + ******************************************************************************/ +#define BRCMF_C_GET_VERSION 1 +#define BRCMF_C_UP 2 +#define BRCMF_C_DOWN 3 +#define BRCMF_C_SET_PROMISC 10 +#define BRCMF_C_GET_RATE 12 +#define BRCMF_C_GET_INFRA 19 +#define BRCMF_C_SET_INFRA 20 +#define BRCMF_C_GET_AUTH 21 +#define BRCMF_C_SET_AUTH 22 +#define BRCMF_C_GET_BSSID 23 +#define BRCMF_C_GET_SSID 25 +#define BRCMF_C_SET_SSID 26 +#define BRCMF_C_TERMINATED 28 +#define BRCMF_C_GET_CHANNEL 29 +#define BRCMF_C_SET_CHANNEL 30 +#define BRCMF_C_GET_SRL 31 +#define BRCMF_C_SET_SRL 32 +#define BRCMF_C_GET_LRL 33 +#define BRCMF_C_SET_LRL 34 +#define BRCMF_C_GET_RADIO 37 +#define BRCMF_C_SET_RADIO 38 +#define BRCMF_C_GET_PHYTYPE 39 +#define BRCMF_C_SET_KEY 45 +#define BRCMF_C_SET_PASSIVE_SCAN 49 +#define BRCMF_C_SCAN 50 +#define BRCMF_C_SCAN_RESULTS 51 +#define BRCMF_C_DISASSOC 52 +#define BRCMF_C_REASSOC 53 +#define BRCMF_C_SET_ROAM_TRIGGER 55 +#define BRCMF_C_SET_ROAM_DELTA 57 +#define BRCMF_C_GET_BCNPRD 75 +#define BRCMF_C_SET_BCNPRD 76 +#define BRCMF_C_GET_DTIMPRD 77 +#define BRCMF_C_SET_DTIMPRD 78 +#define BRCMF_C_SET_COUNTRY 84 +#define BRCMF_C_GET_PM 85 +#define BRCMF_C_SET_PM 86 +#define BRCMF_C_GET_CURR_RATESET 114 +#define BRCMF_C_GET_AP 117 +#define BRCMF_C_SET_AP 118 +#define BRCMF_C_GET_RSSI 127 +#define BRCMF_C_GET_WSEC 133 +#define BRCMF_C_SET_WSEC 134 +#define BRCMF_C_GET_PHY_NOISE 135 +#define BRCMF_C_GET_BSS_INFO 136 +#define BRCMF_C_GET_BANDLIST 140 +#define BRCMF_C_SET_SCB_TIMEOUT 158 +#define BRCMF_C_GET_PHYLIST 180 +#define BRCMF_C_SET_SCAN_CHANNEL_TIME 185 +#define BRCMF_C_SET_SCAN_UNASSOC_TIME 187 +#define BRCMF_C_SCB_DEAUTHENTICATE_FOR_REASON 201 +#define BRCMF_C_GET_VALID_CHANNELS 217 +#define BRCMF_C_GET_KEY_PRIMARY 235 +#define BRCMF_C_SET_KEY_PRIMARY 236 +#define BRCMF_C_SET_SCAN_PASSIVE_TIME 258 +#define BRCMF_C_GET_VAR 262 +#define BRCMF_C_SET_VAR 263 + +/* phy types (returned by WLC_GET_PHYTPE) */ +#define WLC_PHY_TYPE_A 0 +#define WLC_PHY_TYPE_B 1 +#define WLC_PHY_TYPE_G 2 +#define WLC_PHY_TYPE_N 4 +#define WLC_PHY_TYPE_LP 5 +#define WLC_PHY_TYPE_SSN 6 +#define WLC_PHY_TYPE_HT 7 +#define WLC_PHY_TYPE_LCN 8 +#define WLC_PHY_TYPE_NULL 0xf + +#define BRCMF_EVENTING_MASK_LEN 16 + +#define TOE_TX_CSUM_OL 0x00000001 +#define TOE_RX_CSUM_OL 0x00000002 + +#define BRCMF_BSS_INFO_VERSION 109 /* curr ver of brcmf_bss_info_le struct */ + +/* size of brcmf_scan_params not including variable length array */ +#define BRCMF_SCAN_PARAMS_FIXED_SIZE 64 + +/* masks for channel and ssid count */ +#define BRCMF_SCAN_PARAMS_COUNT_MASK 0x0000ffff +#define BRCMF_SCAN_PARAMS_NSSID_SHIFT 16 + +/* primary (ie tx) key */ +#define BRCMF_PRIMARY_KEY (1 << 1) + +/* For supporting multiple interfaces */ +#define BRCMF_MAX_IFS 16 + +#define DOT11_BSSTYPE_ANY 2 +#define DOT11_MAX_DEFAULT_KEYS 4 + +#define BRCMF_ESCAN_REQ_VERSION 1 + +#define WLC_BSS_RSSI_ON_CHANNEL 0x0002 + +#define BRCMF_MAXRATES_IN_SET 16 /* max # of rates in rateset */ +#define BRCMF_STA_ASSOC 0x10 /* Associated */ + +#define BRCMF_E_STATUS_SUCCESS 0 +#define BRCMF_E_STATUS_FAIL 1 +#define BRCMF_E_STATUS_TIMEOUT 2 +#define BRCMF_E_STATUS_NO_NETWORKS 3 +#define BRCMF_E_STATUS_ABORT 4 +#define BRCMF_E_STATUS_NO_ACK 5 +#define BRCMF_E_STATUS_UNSOLICITED 6 +#define BRCMF_E_STATUS_ATTEMPT 7 +#define BRCMF_E_STATUS_PARTIAL 8 +#define BRCMF_E_STATUS_NEWSCAN 9 +#define BRCMF_E_STATUS_NEWASSOC 10 +#define BRCMF_E_STATUS_11HQUIET 11 +#define BRCMF_E_STATUS_SUPPRESS 12 +#define BRCMF_E_STATUS_NOCHANS 13 +#define BRCMF_E_STATUS_CS_ABORT 15 +#define BRCMF_E_STATUS_ERROR 16 + +#define BRCMF_E_REASON_INITIAL_ASSOC 0 +#define BRCMF_E_REASON_LOW_RSSI 1 +#define BRCMF_E_REASON_DEAUTH 2 +#define BRCMF_E_REASON_DISASSOC 3 +#define BRCMF_E_REASON_BCNS_LOST 4 +#define BRCMF_E_REASON_MINTXRATE 9 +#define BRCMF_E_REASON_TXFAIL 10 + +#define BRCMF_E_REASON_LINK_BSSCFG_DIS 4 +#define BRCMF_E_REASON_FAST_ROAM_FAILED 5 +#define BRCMF_E_REASON_DIRECTED_ROAM 6 +#define BRCMF_E_REASON_TSPEC_REJECTED 7 +#define BRCMF_E_REASON_BETTER_AP 8 + +#define BRCMF_E_PRUNE_ENCR_MISMATCH 1 +#define BRCMF_E_PRUNE_BCAST_BSSID 2 +#define BRCMF_E_PRUNE_MAC_DENY 3 +#define BRCMF_E_PRUNE_MAC_NA 4 +#define BRCMF_E_PRUNE_REG_PASSV 5 +#define BRCMF_E_PRUNE_SPCT_MGMT 6 +#define BRCMF_E_PRUNE_RADAR 7 +#define BRCMF_E_RSN_MISMATCH 8 +#define BRCMF_E_PRUNE_NO_COMMON_RATES 9 +#define BRCMF_E_PRUNE_BASIC_RATES 10 +#define BRCMF_E_PRUNE_CIPHER_NA 12 +#define BRCMF_E_PRUNE_KNOWN_STA 13 +#define BRCMF_E_PRUNE_WDS_PEER 15 +#define BRCMF_E_PRUNE_QBSS_LOAD 16 +#define BRCMF_E_PRUNE_HOME_AP 17 + +#define BRCMF_E_SUP_OTHER 0 +#define BRCMF_E_SUP_DECRYPT_KEY_DATA 1 +#define BRCMF_E_SUP_BAD_UCAST_WEP128 2 +#define BRCMF_E_SUP_BAD_UCAST_WEP40 3 +#define BRCMF_E_SUP_UNSUP_KEY_LEN 4 +#define BRCMF_E_SUP_PW_KEY_CIPHER 5 +#define BRCMF_E_SUP_MSG3_TOO_MANY_IE 6 +#define BRCMF_E_SUP_MSG3_IE_MISMATCH 7 +#define BRCMF_E_SUP_NO_INSTALL_FLAG 8 +#define BRCMF_E_SUP_MSG3_NO_GTK 9 +#define BRCMF_E_SUP_GRP_KEY_CIPHER 10 +#define BRCMF_E_SUP_GRP_MSG1_NO_GTK 11 +#define BRCMF_E_SUP_GTK_DECRYPT_FAIL 12 +#define BRCMF_E_SUP_SEND_FAIL 13 +#define BRCMF_E_SUP_DEAUTH 14 + +#define BRCMF_E_IF_ADD 1 +#define BRCMF_E_IF_DEL 2 +#define BRCMF_E_IF_CHANGE 3 + +#define BRCMF_E_IF_ROLE_STA 0 +#define BRCMF_E_IF_ROLE_AP 1 +#define BRCMF_E_IF_ROLE_WDS 2 + +#define BRCMF_E_LINK_BCN_LOSS 1 +#define BRCMF_E_LINK_DISASSOC 2 +#define BRCMF_E_LINK_ASSOC_REC 3 +#define BRCMF_E_LINK_BSSCFG_DIS 4 + +/* Small, medium and maximum buffer size for dcmd + */ +#define BRCMF_DCMD_SMLEN 256 +#define BRCMF_DCMD_MEDLEN 1536 +#define BRCMF_DCMD_MAXLEN 8192 + +/* Pattern matching filter. Specifies an offset within received packets to + * start matching, the pattern to match, the size of the pattern, and a bitmask + * that indicates which bits within the pattern should be matched. + */ +struct brcmf_pkt_filter_pattern_le { + /* + * Offset within received packet to start pattern matching. + * Offset '0' is the first byte of the ethernet header. + */ + __le32 offset; + /* Size of the pattern. Bitmask must be the same size.*/ + __le32 size_bytes; + /* + * Variable length mask and pattern data. mask starts at offset 0. + * Pattern immediately follows mask. + */ + u8 mask_and_pattern[1]; +}; + +/* IOVAR "pkt_filter_add" parameter. Used to install packet filters. */ +struct brcmf_pkt_filter_le { + __le32 id; /* Unique filter id, specified by app. */ + __le32 type; /* Filter type (WL_PKT_FILTER_TYPE_xxx). */ + __le32 negate_match; /* Negate the result of filter matches */ + union { /* Filter definitions */ + struct brcmf_pkt_filter_pattern_le pattern; /* Filter pattern */ + } u; +}; + +/* IOVAR "pkt_filter_enable" parameter. */ +struct brcmf_pkt_filter_enable_le { + __le32 id; /* Unique filter id */ + __le32 enable; /* Enable/disable bool */ +}; + +/* BSS info structure + * Applications MUST CHECK ie_offset field and length field to access IEs and + * next bss_info structure in a vector (in struct brcmf_scan_results) + */ +struct brcmf_bss_info_le { + __le32 version; /* version field */ + __le32 length; /* byte length of data in this record, + * starting at version and including IEs + */ + u8 BSSID[ETH_ALEN]; + __le16 beacon_period; /* units are Kusec */ + __le16 capability; /* Capability information */ + u8 SSID_len; + u8 SSID[32]; + struct { + __le32 count; /* # rates in this set */ + u8 rates[16]; /* rates in 500kbps units w/hi bit set if basic */ + } rateset; /* supported rates */ + __le16 chanspec; /* chanspec for bss */ + __le16 atim_window; /* units are Kusec */ + u8 dtim_period; /* DTIM period */ + __le16 RSSI; /* receive signal strength (in dBm) */ + s8 phy_noise; /* noise (in dBm) */ + + u8 n_cap; /* BSS is 802.11N Capable */ + /* 802.11N BSS Capabilities (based on HT_CAP_*): */ + __le32 nbss_cap; + u8 ctl_ch; /* 802.11N BSS control channel number */ + __le32 reserved32[1]; /* Reserved for expansion of BSS properties */ + u8 flags; /* flags */ + u8 reserved[3]; /* Reserved for expansion of BSS properties */ + u8 basic_mcs[MCSSET_LEN]; /* 802.11N BSS required MCS set */ + + __le16 ie_offset; /* offset at which IEs start, from beginning */ + __le32 ie_length; /* byte length of Information Elements */ + __le16 SNR; /* average SNR of during frame reception */ + /* Add new fields here */ + /* variable length Information Elements */ +}; + +struct brcm_rateset_le { + /* # rates in this set */ + __le32 count; + /* rates in 500kbps units w/hi bit set if basic */ + u8 rates[BRCMF_MAXRATES_IN_SET]; +}; + +struct brcmf_ssid { + u32 SSID_len; + unsigned char SSID[32]; +}; + +struct brcmf_ssid_le { + __le32 SSID_len; + unsigned char SSID[32]; +}; + +struct brcmf_scan_params_le { + struct brcmf_ssid_le ssid_le; /* default: {0, ""} */ + u8 bssid[ETH_ALEN]; /* default: bcast */ + s8 bss_type; /* default: any, + * DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT + */ + u8 scan_type; /* flags, 0 use default */ + __le32 nprobes; /* -1 use default, number of probes per channel */ + __le32 active_time; /* -1 use default, dwell time per channel for + * active scanning + */ + __le32 passive_time; /* -1 use default, dwell time per channel + * for passive scanning + */ + __le32 home_time; /* -1 use default, dwell time for the + * home channel between channel scans + */ + __le32 channel_num; /* count of channels and ssids that follow + * + * low half is count of channels in + * channel_list, 0 means default (use all + * available channels) + * + * high half is entries in struct brcmf_ssid + * array that follows channel_list, aligned for + * s32 (4 bytes) meaning an odd channel count + * implies a 2-byte pad between end of + * channel_list and first ssid + * + * if ssid count is zero, single ssid in the + * fixed parameter portion is assumed, otherwise + * ssid in the fixed portion is ignored + */ + __le16 channel_list[1]; /* list of chanspecs */ +}; + +struct brcmf_scan_results { + u32 buflen; + u32 version; + u32 count; + struct brcmf_bss_info_le bss_info_le[]; +}; + +struct brcmf_escan_params_le { + __le32 version; + __le16 action; + __le16 sync_id; + struct brcmf_scan_params_le params_le; +}; + +struct brcmf_escan_result_le { + __le32 buflen; + __le32 version; + __le16 sync_id; + __le16 bss_count; + struct brcmf_bss_info_le bss_info_le; +}; + +#define WL_ESCAN_RESULTS_FIXED_SIZE (sizeof(struct brcmf_escan_result_le) - \ + sizeof(struct brcmf_bss_info_le)) + +/* used for association with a specific BSSID and chanspec list */ +struct brcmf_assoc_params_le { + /* 00:00:00:00:00:00: broadcast scan */ + u8 bssid[ETH_ALEN]; + /* 0: all available channels, otherwise count of chanspecs in + * chanspec_list */ + __le32 chanspec_num; + /* list of chanspecs */ + __le16 chanspec_list[1]; +}; + +/* used for join with or without a specific bssid and channel list */ +struct brcmf_join_params { + struct brcmf_ssid_le ssid_le; + struct brcmf_assoc_params_le params_le; +}; + +/* scan params for extended join */ +struct brcmf_join_scan_params_le { + u8 scan_type; /* 0 use default, active or passive scan */ + __le32 nprobes; /* -1 use default, nr of probes per channel */ + __le32 active_time; /* -1 use default, dwell time per channel for + * active scanning + */ + __le32 passive_time; /* -1 use default, dwell time per channel + * for passive scanning + */ + __le32 home_time; /* -1 use default, dwell time for the home + * channel between channel scans + */ +}; + +/* extended join params */ +struct brcmf_ext_join_params_le { + struct brcmf_ssid_le ssid_le; /* {0, ""}: wildcard scan */ + struct brcmf_join_scan_params_le scan_le; + struct brcmf_assoc_params_le assoc_le; +}; + +struct brcmf_wsec_key { + u32 index; /* key index */ + u32 len; /* key length */ + u8 data[WLAN_MAX_KEY_LEN]; /* key data */ + u32 pad_1[18]; + u32 algo; /* CRYPTO_ALGO_AES_CCM, CRYPTO_ALGO_WEP128, etc */ + u32 flags; /* misc flags */ + u32 pad_2[3]; + u32 iv_initialized; /* has IV been initialized already? */ + u32 pad_3; + /* Rx IV */ + struct { + u32 hi; /* upper 32 bits of IV */ + u16 lo; /* lower 16 bits of IV */ + } rxiv; + u32 pad_4[2]; + u8 ea[ETH_ALEN]; /* per station */ +}; + +/* + * dongle requires same struct as above but with fields in little endian order + */ +struct brcmf_wsec_key_le { + __le32 index; /* key index */ + __le32 len; /* key length */ + u8 data[WLAN_MAX_KEY_LEN]; /* key data */ + __le32 pad_1[18]; + __le32 algo; /* CRYPTO_ALGO_AES_CCM, CRYPTO_ALGO_WEP128, etc */ + __le32 flags; /* misc flags */ + __le32 pad_2[3]; + __le32 iv_initialized; /* has IV been initialized already? */ + __le32 pad_3; + /* Rx IV */ + struct { + __le32 hi; /* upper 32 bits of IV */ + __le16 lo; /* lower 16 bits of IV */ + } rxiv; + __le32 pad_4[2]; + u8 ea[ETH_ALEN]; /* per station */ +}; + +/* Used to get specific STA parameters */ +struct brcmf_scb_val_le { + __le32 val; + u8 ea[ETH_ALEN]; +}; + +/* channel encoding */ +struct brcmf_channel_info_le { + __le32 hw_channel; + __le32 target_channel; + __le32 scan_channel; +}; + +struct brcmf_sta_info_le { + __le16 ver; /* version of this struct */ + __le16 len; /* length in bytes of this structure */ + __le16 cap; /* sta's advertised capabilities */ + __le32 flags; /* flags defined below */ + __le32 idle; /* time since data pkt rx'd from sta */ + u8 ea[ETH_ALEN]; /* Station address */ + __le32 count; /* # rates in this set */ + u8 rates[BRCMF_MAXRATES_IN_SET]; /* rates in 500kbps units */ + /* w/hi bit set if basic */ + __le32 in; /* seconds elapsed since associated */ + __le32 listen_interval_inms; /* Min Listen interval in ms for STA */ + __le32 tx_pkts; /* # of packets transmitted */ + __le32 tx_failures; /* # of packets failed */ + __le32 rx_ucast_pkts; /* # of unicast packets received */ + __le32 rx_mcast_pkts; /* # of multicast packets received */ + __le32 tx_rate; /* Rate of last successful tx frame */ + __le32 rx_rate; /* Rate of last successful rx frame */ + __le32 rx_decrypt_succeeds; /* # of packet decrypted successfully */ + __le32 rx_decrypt_failures; /* # of packet decrypted failed */ +}; + +struct brcmf_chanspec_list { + __le32 count; /* # of entries */ + __le32 element[1]; /* variable length uint32 list */ +}; + +/* + * WLC_E_PROBRESP_MSG + * WLC_E_P2P_PROBREQ_MSG + * WLC_E_ACTION_FRAME_RX + */ +struct brcmf_rx_mgmt_data { + __be16 version; + __be16 chanspec; + __be32 rssi; + __be32 mactime; + __be32 rate; +}; + +/* Bus independent dongle command */ +struct brcmf_dcmd { + uint cmd; /* common dongle cmd definition */ + void *buf; /* pointer to user buffer */ + uint len; /* length of user buffer */ + u8 set; /* get or set request (optional) */ + uint used; /* bytes read or written (optional) */ + uint needed; /* bytes needed (optional) */ +}; + +/* Forward decls for struct brcmf_pub (see below) */ +struct brcmf_proto; /* device communication protocol info */ +struct brcmf_cfg80211_dev; /* cfg80211 device info */ +struct brcmf_fws_info; /* firmware signalling info */ + +/* Common structure for module and instance linkage */ +struct brcmf_pub { + /* Linkage ponters */ + struct brcmf_bus *bus_if; + struct brcmf_proto *prot; + struct brcmf_cfg80211_info *config; + + /* Internal brcmf items */ + uint hdrlen; /* Total BRCMF header length (proto + bus) */ + uint rxsz; /* Rx buffer size bus module should use */ + u8 wme_dp; /* wme discard priority */ + + /* Dongle media info */ + unsigned long drv_version; /* Version of dongle-resident driver */ + u8 mac[ETH_ALEN]; /* MAC address obtained from dongle */ + + /* Multicast data packets sent to dongle */ + unsigned long tx_multicast; + + struct brcmf_if *iflist[BRCMF_MAX_IFS]; + + struct mutex proto_block; + unsigned char proto_buf[BRCMF_DCMD_MAXLEN]; + + struct brcmf_fweh_info fweh; + + bool fw_signals; + struct brcmf_fws_info *fws; + spinlock_t fws_spinlock; +#ifdef DEBUG + struct dentry *dbgfs_dir; +#endif +}; + +struct brcmf_if_event { + u8 ifidx; + u8 action; + u8 flags; + u8 bssidx; + u8 role; +}; + +/* forward declarations */ +struct brcmf_cfg80211_vif; +struct brcmf_fws_mac_descriptor; + +/** + * enum brcmf_netif_stop_reason - reason for stopping netif queue. + * + * @BRCMF_NETIF_STOP_REASON_FWS_FC: + * netif stopped due to firmware signalling flow control. + * @BRCMF_NETIF_STOP_REASON_BLOCK_BUS: + * netif stopped due to bus blocking. + */ +enum brcmf_netif_stop_reason { + BRCMF_NETIF_STOP_REASON_FWS_FC = 1, + BRCMF_NETIF_STOP_REASON_BLOCK_BUS = 2 +}; + +/** + * struct brcmf_if - interface control information. + * + * @drvr: points to device related information. + * @vif: points to cfg80211 specific interface information. + * @ndev: associated network device. + * @stats: interface specific network statistics. + * @setmacaddr_work: worker object for setting mac address. + * @multicast_work: worker object for multicast provisioning. + * @fws_desc: interface specific firmware-signalling descriptor. + * @ifidx: interface index in device firmware. + * @bssidx: index of bss associated with this interface. + * @mac_addr: assigned mac address. + * @netif_stop: bitmap indicates reason why netif queues are stopped. + * @pend_8021x_cnt: tracks outstanding number of 802.1x frames. + * @pend_8021x_wait: used for signalling change in count. + */ +struct brcmf_if { + struct brcmf_pub *drvr; + struct brcmf_cfg80211_vif *vif; + struct net_device *ndev; + struct net_device_stats stats; + struct work_struct setmacaddr_work; + struct work_struct multicast_work; + struct brcmf_fws_mac_descriptor *fws_desc; + int ifidx; + s32 bssidx; + u8 mac_addr[ETH_ALEN]; + u8 netif_stop; + atomic_t pend_8021x_cnt; + wait_queue_head_t pend_8021x_wait; +}; + + +extern int brcmf_netdev_wait_pend8021x(struct net_device *ndev); + +/* Return pointer to interface name */ +extern char *brcmf_ifname(struct brcmf_pub *drvr, int idx); + +/* Query dongle */ +extern int brcmf_proto_cdc_query_dcmd(struct brcmf_pub *drvr, int ifidx, + uint cmd, void *buf, uint len); +extern int brcmf_proto_cdc_set_dcmd(struct brcmf_pub *drvr, int ifidx, uint cmd, + void *buf, uint len); + +/* Remove any protocol-specific data header. */ +extern int brcmf_proto_hdrpull(struct brcmf_pub *drvr, bool do_fws, u8 *ifidx, + struct sk_buff *rxp); + +extern int brcmf_net_attach(struct brcmf_if *ifp, bool rtnl_locked); +extern struct brcmf_if *brcmf_add_if(struct brcmf_pub *drvr, s32 bssidx, + s32 ifidx, char *name, u8 *mac_addr); +extern void brcmf_del_if(struct brcmf_pub *drvr, s32 bssidx); +void brcmf_txflowblock_if(struct brcmf_if *ifp, + enum brcmf_netif_stop_reason reason, bool state); +extern u32 brcmf_get_chip_info(struct brcmf_if *ifp); +extern void brcmf_txfinalize(struct brcmf_pub *drvr, struct sk_buff *txp, + bool success); + +#endif /* _BRCMF_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_bus.h b/drivers/net/wireless/brcm80211/brcmfmac/dhd_bus.h new file mode 100644 index 0000000..14b1a05 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_bus.h @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCMF_BUS_H_ +#define _BRCMF_BUS_H_ + +/* The level of bus communication with the dongle */ +enum brcmf_bus_state { + BRCMF_BUS_DOWN, /* Not ready for frame transfers */ + BRCMF_BUS_LOAD, /* Download access only (CPU reset) */ + BRCMF_BUS_DATA /* Ready for frame transfers */ +}; + +struct brcmf_bus_dcmd { + char *name; + char *param; + int param_len; + struct list_head list; +}; + +/** + * struct brcmf_bus_ops - bus callback operations. + * + * @init: prepare for communication with dongle. + * @stop: clear pending frames, disable data flow. + * @txdata: send a data frame to the dongle (callee disposes skb). + * @txctl: transmit a control request message to dongle. + * @rxctl: receive a control response message from dongle. + * @gettxq: obtain a reference of bus transmit queue (optional). + * + * This structure provides an abstract interface towards the + * bus specific driver. For control messages to common driver + * will assure there is only one active transaction. Unless + * indicated otherwise these callbacks are mandatory. + */ +struct brcmf_bus_ops { + int (*init)(struct device *dev); + void (*stop)(struct device *dev); + int (*txdata)(struct device *dev, struct sk_buff *skb); + int (*txctl)(struct device *dev, unsigned char *msg, uint len); + int (*rxctl)(struct device *dev, unsigned char *msg, uint len); + struct pktq * (*gettxq)(struct device *dev); +}; + +/** + * struct brcmf_bus - interface structure between common and bus layer + * + * @bus_priv: pointer to private bus device. + * @dev: device pointer of bus device. + * @drvr: public driver information. + * @state: operational state of the bus interface. + * @maxctl: maximum size for rxctl request message. + * @tx_realloc: number of tx packets realloced for headroom. + * @dstats: dongle-based statistical data. + * @align: alignment requirement for the bus. + * @dcmd_list: bus/device specific dongle initialization commands. + * @chip: device identifier of the dongle chip. + * @chiprev: revision of the dongle chip. + */ +struct brcmf_bus { + union { + struct brcmf_sdio_dev *sdio; + struct brcmf_usbdev *usb; + } bus_priv; + struct device *dev; + struct brcmf_pub *drvr; + enum brcmf_bus_state state; + uint maxctl; + unsigned long tx_realloc; + u8 align; + u32 chip; + u32 chiprev; + struct list_head dcmd_list; + + struct brcmf_bus_ops *ops; +}; + +/* + * callback wrappers + */ +static inline int brcmf_bus_init(struct brcmf_bus *bus) +{ + return bus->ops->init(bus->dev); +} + +static inline void brcmf_bus_stop(struct brcmf_bus *bus) +{ + bus->ops->stop(bus->dev); +} + +static inline int brcmf_bus_txdata(struct brcmf_bus *bus, struct sk_buff *skb) +{ + return bus->ops->txdata(bus->dev, skb); +} + +static inline +int brcmf_bus_txctl(struct brcmf_bus *bus, unsigned char *msg, uint len) +{ + return bus->ops->txctl(bus->dev, msg, len); +} + +static inline +int brcmf_bus_rxctl(struct brcmf_bus *bus, unsigned char *msg, uint len) +{ + return bus->ops->rxctl(bus->dev, msg, len); +} + +static inline +struct pktq *brcmf_bus_gettxq(struct brcmf_bus *bus) +{ + if (!bus->ops->gettxq) + return ERR_PTR(-ENOENT); + + return bus->ops->gettxq(bus->dev); +} +/* + * interface functions from common layer + */ + +extern bool brcmf_c_prec_enq(struct device *dev, struct pktq *q, + struct sk_buff *pkt, int prec); + +/* Receive frame for delivery to OS. Callee disposes of rxp. */ +extern void brcmf_rx_frames(struct device *dev, struct sk_buff_head *rxlist); + +/* Indication from bus module regarding presence/insertion of dongle. */ +extern int brcmf_attach(uint bus_hdrlen, struct device *dev); +/* Indication from bus module regarding removal/absence of dongle */ +extern void brcmf_detach(struct device *dev); +/* Indication from bus module that dongle should be reset */ +extern void brcmf_dev_reset(struct device *dev); +/* Indication from bus module to change flow-control state */ +extern void brcmf_txflowblock(struct device *dev, bool state); + +/* Notify the bus has transferred the tx packet to firmware */ +extern void brcmf_txcomplete(struct device *dev, struct sk_buff *txp, + bool success); + +extern int brcmf_bus_start(struct device *dev); + +#ifdef CPTCFG_BRCMFMAC_SDIO +extern void brcmf_sdio_exit(void); +extern void brcmf_sdio_init(void); +extern void brcmf_sdio_register(void); +#endif +#ifdef CPTCFG_BRCMFMAC_USB +extern void brcmf_usb_exit(void); +extern void brcmf_usb_register(void); +#endif + +#endif /* _BRCMF_BUS_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_cdc.c b/drivers/net/wireless/brcm80211/brcmfmac/dhd_cdc.c new file mode 100644 index 0000000..59c77aa --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_cdc.c @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/******************************************************************************* + * Communicates with the dongle by using dcmd codes. + * For certain dcmd codes, the dongle interprets string data from the host. + ******************************************************************************/ + +#include +#include + +#include +#include + +#include "dhd.h" +#include "dhd_proto.h" +#include "dhd_bus.h" +#include "fwsignal.h" +#include "dhd_dbg.h" + +struct brcmf_proto_cdc_dcmd { + __le32 cmd; /* dongle command value */ + __le32 len; /* lower 16: output buflen; + * upper 16: input buflen (excludes header) */ + __le32 flags; /* flag defns given below */ + __le32 status; /* status code returned from the device */ +}; + +/* Max valid buffer size that can be sent to the dongle */ +#define CDC_MAX_MSG_SIZE (ETH_FRAME_LEN+ETH_FCS_LEN) + +/* CDC flag definitions */ +#define CDC_DCMD_ERROR 0x01 /* 1=cmd failed */ +#define CDC_DCMD_SET 0x02 /* 0=get, 1=set cmd */ +#define CDC_DCMD_IF_MASK 0xF000 /* I/F index */ +#define CDC_DCMD_IF_SHIFT 12 +#define CDC_DCMD_ID_MASK 0xFFFF0000 /* id an cmd pairing */ +#define CDC_DCMD_ID_SHIFT 16 /* ID Mask shift bits */ +#define CDC_DCMD_ID(flags) \ + (((flags) & CDC_DCMD_ID_MASK) >> CDC_DCMD_ID_SHIFT) + +/* + * BDC header - Broadcom specific extension of CDC. + * Used on data packets to convey priority across USB. + */ +#define BDC_HEADER_LEN 4 +#define BDC_PROTO_VER 2 /* Protocol version */ +#define BDC_FLAG_VER_MASK 0xf0 /* Protocol version mask */ +#define BDC_FLAG_VER_SHIFT 4 /* Protocol version shift */ +#define BDC_FLAG_SUM_GOOD 0x04 /* Good RX checksums */ +#define BDC_FLAG_SUM_NEEDED 0x08 /* Dongle needs to do TX checksums */ +#define BDC_PRIORITY_MASK 0x7 +#define BDC_FLAG2_IF_MASK 0x0f /* packet rx interface in APSTA */ +#define BDC_FLAG2_IF_SHIFT 0 + +#define BDC_GET_IF_IDX(hdr) \ + ((int)((((hdr)->flags2) & BDC_FLAG2_IF_MASK) >> BDC_FLAG2_IF_SHIFT)) +#define BDC_SET_IF_IDX(hdr, idx) \ + ((hdr)->flags2 = (((hdr)->flags2 & ~BDC_FLAG2_IF_MASK) | \ + ((idx) << BDC_FLAG2_IF_SHIFT))) + +/** + * struct brcmf_proto_bdc_header - BDC header format + * + * @flags: flags contain protocol and checksum info. + * @priority: 802.1d priority and USB flow control info (bit 4:7). + * @flags2: additional flags containing dongle interface index. + * @data_offset: start of packet data. header is following by firmware signals. + */ +struct brcmf_proto_bdc_header { + u8 flags; + u8 priority; + u8 flags2; + u8 data_offset; +}; + +/* + * maximum length of firmware signal data between + * the BDC header and packet data in the tx path. + */ +#define BRCMF_PROT_FW_SIGNAL_MAX_TXBYTES 12 + +#define RETRIES 2 /* # of retries to retrieve matching dcmd response */ +#define BUS_HEADER_LEN (16+64) /* Must be atleast SDPCM_RESERVE + * (amount of header tha might be added) + * plus any space that might be needed + * for bus alignment padding. + */ +#define ROUND_UP_MARGIN 2048 /* Biggest bus block size possible for + * round off at the end of buffer + * Currently is SDIO + */ + +struct brcmf_proto { + u16 reqid; + u8 bus_header[BUS_HEADER_LEN]; + struct brcmf_proto_cdc_dcmd msg; + unsigned char buf[BRCMF_DCMD_MAXLEN + ROUND_UP_MARGIN]; +}; + +static int brcmf_proto_cdc_msg(struct brcmf_pub *drvr) +{ + struct brcmf_proto *prot = drvr->prot; + int len = le32_to_cpu(prot->msg.len) + + sizeof(struct brcmf_proto_cdc_dcmd); + + brcmf_dbg(CDC, "Enter\n"); + + /* NOTE : cdc->msg.len holds the desired length of the buffer to be + * returned. Only up to CDC_MAX_MSG_SIZE of this buffer area + * is actually sent to the dongle + */ + if (len > CDC_MAX_MSG_SIZE) + len = CDC_MAX_MSG_SIZE; + + /* Send request */ + return brcmf_bus_txctl(drvr->bus_if, (unsigned char *)&prot->msg, len); +} + +static int brcmf_proto_cdc_cmplt(struct brcmf_pub *drvr, u32 id, u32 len) +{ + int ret; + struct brcmf_proto *prot = drvr->prot; + + brcmf_dbg(CDC, "Enter\n"); + len += sizeof(struct brcmf_proto_cdc_dcmd); + do { + ret = brcmf_bus_rxctl(drvr->bus_if, (unsigned char *)&prot->msg, + len); + if (ret < 0) + break; + } while (CDC_DCMD_ID(le32_to_cpu(prot->msg.flags)) != id); + + return ret; +} + +int +brcmf_proto_cdc_query_dcmd(struct brcmf_pub *drvr, int ifidx, uint cmd, + void *buf, uint len) +{ + struct brcmf_proto *prot = drvr->prot; + struct brcmf_proto_cdc_dcmd *msg = &prot->msg; + void *info; + int ret = 0, retries = 0; + u32 id, flags; + + brcmf_dbg(CDC, "Enter, cmd %d len %d\n", cmd, len); + + memset(msg, 0, sizeof(struct brcmf_proto_cdc_dcmd)); + + msg->cmd = cpu_to_le32(cmd); + msg->len = cpu_to_le32(len); + flags = (++prot->reqid << CDC_DCMD_ID_SHIFT); + flags = (flags & ~CDC_DCMD_IF_MASK) | + (ifidx << CDC_DCMD_IF_SHIFT); + msg->flags = cpu_to_le32(flags); + + if (buf) + memcpy(prot->buf, buf, len); + + ret = brcmf_proto_cdc_msg(drvr); + if (ret < 0) { + brcmf_err("brcmf_proto_cdc_msg failed w/status %d\n", + ret); + goto done; + } + +retry: + /* wait for interrupt and get first fragment */ + ret = brcmf_proto_cdc_cmplt(drvr, prot->reqid, len); + if (ret < 0) + goto done; + + flags = le32_to_cpu(msg->flags); + id = (flags & CDC_DCMD_ID_MASK) >> CDC_DCMD_ID_SHIFT; + + if ((id < prot->reqid) && (++retries < RETRIES)) + goto retry; + if (id != prot->reqid) { + brcmf_err("%s: unexpected request id %d (expected %d)\n", + brcmf_ifname(drvr, ifidx), id, prot->reqid); + ret = -EINVAL; + goto done; + } + + /* Check info buffer */ + info = (void *)&msg[1]; + + /* Copy info buffer */ + if (buf) { + if (ret < (int)len) + len = ret; + memcpy(buf, info, len); + } + + /* Check the ERROR flag */ + if (flags & CDC_DCMD_ERROR) + ret = le32_to_cpu(msg->status); + +done: + return ret; +} + +int brcmf_proto_cdc_set_dcmd(struct brcmf_pub *drvr, int ifidx, uint cmd, + void *buf, uint len) +{ + struct brcmf_proto *prot = drvr->prot; + struct brcmf_proto_cdc_dcmd *msg = &prot->msg; + int ret = 0; + u32 flags, id; + + brcmf_dbg(CDC, "Enter, cmd %d len %d\n", cmd, len); + + memset(msg, 0, sizeof(struct brcmf_proto_cdc_dcmd)); + + msg->cmd = cpu_to_le32(cmd); + msg->len = cpu_to_le32(len); + flags = (++prot->reqid << CDC_DCMD_ID_SHIFT) | CDC_DCMD_SET; + flags = (flags & ~CDC_DCMD_IF_MASK) | + (ifidx << CDC_DCMD_IF_SHIFT); + msg->flags = cpu_to_le32(flags); + + if (buf) + memcpy(prot->buf, buf, len); + + ret = brcmf_proto_cdc_msg(drvr); + if (ret < 0) + goto done; + + ret = brcmf_proto_cdc_cmplt(drvr, prot->reqid, len); + if (ret < 0) + goto done; + + flags = le32_to_cpu(msg->flags); + id = (flags & CDC_DCMD_ID_MASK) >> CDC_DCMD_ID_SHIFT; + + if (id != prot->reqid) { + brcmf_err("%s: unexpected request id %d (expected %d)\n", + brcmf_ifname(drvr, ifidx), id, prot->reqid); + ret = -EINVAL; + goto done; + } + + /* Check the ERROR flag */ + if (flags & CDC_DCMD_ERROR) + ret = le32_to_cpu(msg->status); + +done: + return ret; +} + +static bool pkt_sum_needed(struct sk_buff *skb) +{ + return skb->ip_summed == CHECKSUM_PARTIAL; +} + +static void pkt_set_sum_good(struct sk_buff *skb, bool x) +{ + skb->ip_summed = (x ? CHECKSUM_UNNECESSARY : CHECKSUM_NONE); +} + +void brcmf_proto_hdrpush(struct brcmf_pub *drvr, int ifidx, u8 offset, + struct sk_buff *pktbuf) +{ + struct brcmf_proto_bdc_header *h; + + brcmf_dbg(CDC, "Enter\n"); + + /* Push BDC header used to convey priority for buses that don't */ + skb_push(pktbuf, BDC_HEADER_LEN); + + h = (struct brcmf_proto_bdc_header *)(pktbuf->data); + + h->flags = (BDC_PROTO_VER << BDC_FLAG_VER_SHIFT); + if (pkt_sum_needed(pktbuf)) + h->flags |= BDC_FLAG_SUM_NEEDED; + + h->priority = (pktbuf->priority & BDC_PRIORITY_MASK); + h->flags2 = 0; + h->data_offset = offset; + BDC_SET_IF_IDX(h, ifidx); +} + +int brcmf_proto_hdrpull(struct brcmf_pub *drvr, bool do_fws, u8 *ifidx, + struct sk_buff *pktbuf) +{ + struct brcmf_proto_bdc_header *h; + + brcmf_dbg(CDC, "Enter\n"); + + /* Pop BDC header used to convey priority for buses that don't */ + + if (pktbuf->len <= BDC_HEADER_LEN) { + brcmf_dbg(INFO, "rx data too short (%d <= %d)\n", + pktbuf->len, BDC_HEADER_LEN); + return -EBADE; + } + + h = (struct brcmf_proto_bdc_header *)(pktbuf->data); + + *ifidx = BDC_GET_IF_IDX(h); + if (*ifidx >= BRCMF_MAX_IFS) { + brcmf_err("rx data ifnum out of range (%d)\n", *ifidx); + return -EBADE; + } + /* The ifidx is the idx to map to matching netdev/ifp. When receiving + * events this is easy because it contains the bssidx which maps + * 1-on-1 to the netdev/ifp. But for data frames the ifidx is rcvd. + * bssidx 1 is used for p2p0 and no data can be received or + * transmitted on it. Therefor bssidx is ifidx + 1 if ifidx > 0 + */ + if (*ifidx) + (*ifidx)++; + + if (((h->flags & BDC_FLAG_VER_MASK) >> BDC_FLAG_VER_SHIFT) != + BDC_PROTO_VER) { + brcmf_err("%s: non-BDC packet received, flags 0x%x\n", + brcmf_ifname(drvr, *ifidx), h->flags); + return -EBADE; + } + + if (h->flags & BDC_FLAG_SUM_GOOD) { + brcmf_dbg(CDC, "%s: BDC rcv, good checksum, flags 0x%x\n", + brcmf_ifname(drvr, *ifidx), h->flags); + pkt_set_sum_good(pktbuf, true); + } + + pktbuf->priority = h->priority & BDC_PRIORITY_MASK; + + skb_pull(pktbuf, BDC_HEADER_LEN); + if (do_fws) + brcmf_fws_hdrpull(drvr, *ifidx, h->data_offset << 2, pktbuf); + else + skb_pull(pktbuf, h->data_offset << 2); + + if (pktbuf->len == 0) + return -ENODATA; + return 0; +} + +int brcmf_proto_attach(struct brcmf_pub *drvr) +{ + struct brcmf_proto *cdc; + + cdc = kzalloc(sizeof(struct brcmf_proto), GFP_ATOMIC); + if (!cdc) + goto fail; + + /* ensure that the msg buf directly follows the cdc msg struct */ + if ((unsigned long)(&cdc->msg + 1) != (unsigned long)cdc->buf) { + brcmf_err("struct brcmf_proto is not correctly defined\n"); + goto fail; + } + + drvr->prot = cdc; + drvr->hdrlen += BDC_HEADER_LEN + BRCMF_PROT_FW_SIGNAL_MAX_TXBYTES; + drvr->bus_if->maxctl = BRCMF_DCMD_MAXLEN + + sizeof(struct brcmf_proto_cdc_dcmd) + ROUND_UP_MARGIN; + return 0; + +fail: + kfree(cdc); + return -ENOMEM; +} + +/* ~NOTE~ What if another thread is waiting on the semaphore? Holding it? */ +void brcmf_proto_detach(struct brcmf_pub *drvr) +{ + kfree(drvr->prot); + drvr->prot = NULL; +} + +void brcmf_proto_stop(struct brcmf_pub *drvr) +{ + /* Nothing to do for CDC */ +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_common.c b/drivers/net/wireless/brcm80211/brcmfmac/dhd_common.c new file mode 100644 index 0000000..bd1ba4c --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_common.c @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include "dhd.h" +#include "dhd_bus.h" +#include "dhd_proto.h" +#include "dhd_dbg.h" +#include "fwil.h" +#include "tracepoint.h" + +#define PKTFILTER_BUF_SIZE 128 +#define BRCMF_DEFAULT_BCN_TIMEOUT 3 +#define BRCMF_DEFAULT_SCAN_CHANNEL_TIME 40 +#define BRCMF_DEFAULT_SCAN_UNASSOC_TIME 40 +#define BRCMF_DEFAULT_PACKET_FILTER "100 0 0 0 0x01 0x00" + +#ifdef DEBUG +static const char brcmf_version[] = + "Dongle Host Driver, version " BRCMF_VERSION_STR "\nCompiled on " + __DATE__ " at " __TIME__; +#else +static const char brcmf_version[] = + "Dongle Host Driver, version " BRCMF_VERSION_STR; +#endif + + +bool brcmf_c_prec_enq(struct device *dev, struct pktq *q, + struct sk_buff *pkt, int prec) +{ + struct sk_buff *p; + int eprec = -1; /* precedence to evict from */ + bool discard_oldest; + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_pub *drvr = bus_if->drvr; + + /* Fast case, precedence queue is not full and we are also not + * exceeding total queue length + */ + if (!pktq_pfull(q, prec) && !pktq_full(q)) { + brcmu_pktq_penq(q, prec, pkt); + return true; + } + + /* Determine precedence from which to evict packet, if any */ + if (pktq_pfull(q, prec)) + eprec = prec; + else if (pktq_full(q)) { + p = brcmu_pktq_peek_tail(q, &eprec); + if (eprec > prec) + return false; + } + + /* Evict if needed */ + if (eprec >= 0) { + /* Detect queueing to unconfigured precedence */ + discard_oldest = ac_bitmap_tst(drvr->wme_dp, eprec); + if (eprec == prec && !discard_oldest) + return false; /* refuse newer (incoming) packet */ + /* Evict packet according to discard policy */ + p = discard_oldest ? brcmu_pktq_pdeq(q, eprec) : + brcmu_pktq_pdeq_tail(q, eprec); + if (p == NULL) + brcmf_err("brcmu_pktq_penq() failed, oldest %d\n", + discard_oldest); + + brcmu_pkt_buf_free_skb(p); + } + + /* Enqueue */ + p = brcmu_pktq_penq(q, prec, pkt); + if (p == NULL) + brcmf_err("brcmu_pktq_penq() failed\n"); + + return p != NULL; +} + +/* Convert user's input in hex pattern to byte-size mask */ +static int brcmf_c_pattern_atoh(char *src, char *dst) +{ + int i; + if (strncmp(src, "0x", 2) != 0 && strncmp(src, "0X", 2) != 0) { + brcmf_err("Mask invalid format. Needs to start with 0x\n"); + return -EINVAL; + } + src = src + 2; /* Skip past 0x */ + if (strlen(src) % 2 != 0) { + brcmf_err("Mask invalid format. Length must be even.\n"); + return -EINVAL; + } + for (i = 0; *src != '\0'; i++) { + unsigned long res; + char num[3]; + strncpy(num, src, 2); + num[2] = '\0'; + if (kstrtoul(num, 16, &res)) + return -EINVAL; + dst[i] = (u8)res; + src += 2; + } + return i; +} + +static void +brcmf_c_pktfilter_offload_enable(struct brcmf_if *ifp, char *arg, int enable, + int master_mode) +{ + unsigned long res; + char *argv; + char *arg_save = NULL, *arg_org = NULL; + s32 err; + struct brcmf_pkt_filter_enable_le enable_parm; + + arg_save = kstrdup(arg, GFP_ATOMIC); + if (!arg_save) + goto fail; + + arg_org = arg_save; + + argv = strsep(&arg_save, " "); + + if (argv == NULL) { + brcmf_err("No args provided\n"); + goto fail; + } + + /* Parse packet filter id. */ + enable_parm.id = 0; + if (!kstrtoul(argv, 0, &res)) + enable_parm.id = cpu_to_le32((u32)res); + + /* Enable/disable the specified filter. */ + enable_parm.enable = cpu_to_le32(enable); + + err = brcmf_fil_iovar_data_set(ifp, "pkt_filter_enable", &enable_parm, + sizeof(enable_parm)); + if (err) + brcmf_err("Set pkt_filter_enable error (%d)\n", err); + + /* Control the master mode */ + err = brcmf_fil_iovar_int_set(ifp, "pkt_filter_mode", master_mode); + if (err) + brcmf_err("Set pkt_filter_mode error (%d)\n", err); + +fail: + kfree(arg_org); +} + +static void brcmf_c_pktfilter_offload_set(struct brcmf_if *ifp, char *arg) +{ + struct brcmf_pkt_filter_le *pkt_filter; + unsigned long res; + int buf_len; + s32 err; + u32 mask_size; + u32 pattern_size; + char *argv[8], *buf = NULL; + int i = 0; + char *arg_save = NULL, *arg_org = NULL; + + arg_save = kstrdup(arg, GFP_ATOMIC); + if (!arg_save) + goto fail; + + arg_org = arg_save; + + buf = kmalloc(PKTFILTER_BUF_SIZE, GFP_ATOMIC); + if (!buf) + goto fail; + + argv[i] = strsep(&arg_save, " "); + while (argv[i]) { + i++; + if (i >= 8) { + brcmf_err("Too many parameters\n"); + goto fail; + } + argv[i] = strsep(&arg_save, " "); + } + + if (i != 6) { + brcmf_err("Not enough args provided %d\n", i); + goto fail; + } + + pkt_filter = (struct brcmf_pkt_filter_le *)buf; + + /* Parse packet filter id. */ + pkt_filter->id = 0; + if (!kstrtoul(argv[0], 0, &res)) + pkt_filter->id = cpu_to_le32((u32)res); + + /* Parse filter polarity. */ + pkt_filter->negate_match = 0; + if (!kstrtoul(argv[1], 0, &res)) + pkt_filter->negate_match = cpu_to_le32((u32)res); + + /* Parse filter type. */ + pkt_filter->type = 0; + if (!kstrtoul(argv[2], 0, &res)) + pkt_filter->type = cpu_to_le32((u32)res); + + /* Parse pattern filter offset. */ + pkt_filter->u.pattern.offset = 0; + if (!kstrtoul(argv[3], 0, &res)) + pkt_filter->u.pattern.offset = cpu_to_le32((u32)res); + + /* Parse pattern filter mask. */ + mask_size = brcmf_c_pattern_atoh(argv[4], + (char *)pkt_filter->u.pattern.mask_and_pattern); + + /* Parse pattern filter pattern. */ + pattern_size = brcmf_c_pattern_atoh(argv[5], + (char *)&pkt_filter->u.pattern.mask_and_pattern[mask_size]); + + if (mask_size != pattern_size) { + brcmf_err("Mask and pattern not the same size\n"); + goto fail; + } + + pkt_filter->u.pattern.size_bytes = cpu_to_le32(mask_size); + buf_len = offsetof(struct brcmf_pkt_filter_le, + u.pattern.mask_and_pattern); + buf_len += mask_size + pattern_size; + + err = brcmf_fil_iovar_data_set(ifp, "pkt_filter_add", pkt_filter, + buf_len); + if (err) + brcmf_err("Set pkt_filter_add error (%d)\n", err); + +fail: + kfree(arg_org); + + kfree(buf); +} + +int brcmf_c_preinit_dcmds(struct brcmf_if *ifp) +{ + s8 eventmask[BRCMF_EVENTING_MASK_LEN]; + u8 buf[BRCMF_DCMD_SMLEN]; + char *ptr; + s32 err; + struct brcmf_bus_dcmd *cmdlst; + struct list_head *cur, *q; + + /* retreive mac address */ + err = brcmf_fil_iovar_data_get(ifp, "cur_etheraddr", ifp->mac_addr, + sizeof(ifp->mac_addr)); + if (err < 0) { + brcmf_err("Retreiving cur_etheraddr failed, %d\n", + err); + goto done; + } + memcpy(ifp->drvr->mac, ifp->mac_addr, sizeof(ifp->drvr->mac)); + + /* query for 'ver' to get version info from firmware */ + memset(buf, 0, sizeof(buf)); + strcpy(buf, "ver"); + err = brcmf_fil_iovar_data_get(ifp, "ver", buf, sizeof(buf)); + if (err < 0) { + brcmf_err("Retreiving version information failed, %d\n", + err); + goto done; + } + ptr = (char *)buf; + strsep(&ptr, "\n"); + /* Print fw version info */ + brcmf_err("Firmware version = %s\n", buf); + + /* + * Setup timeout if Beacons are lost and roam is off to report + * link down + */ + err = brcmf_fil_iovar_int_set(ifp, "bcn_timeout", + BRCMF_DEFAULT_BCN_TIMEOUT); + if (err) { + brcmf_err("bcn_timeout error (%d)\n", err); + goto done; + } + + /* Enable/Disable build-in roaming to allowed ext supplicant to take + * of romaing + */ + err = brcmf_fil_iovar_int_set(ifp, "roam_off", 1); + if (err) { + brcmf_err("roam_off error (%d)\n", err); + goto done; + } + + /* Setup event_msgs, enable E_IF */ + err = brcmf_fil_iovar_data_get(ifp, "event_msgs", eventmask, + BRCMF_EVENTING_MASK_LEN); + if (err) { + brcmf_err("Get event_msgs error (%d)\n", err); + goto done; + } + setbit(eventmask, BRCMF_E_IF); + err = brcmf_fil_iovar_data_set(ifp, "event_msgs", eventmask, + BRCMF_EVENTING_MASK_LEN); + if (err) { + brcmf_err("Set event_msgs error (%d)\n", err); + goto done; + } + + /* Setup default scan channel time */ + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCAN_CHANNEL_TIME, + BRCMF_DEFAULT_SCAN_CHANNEL_TIME); + if (err) { + brcmf_err("BRCMF_C_SET_SCAN_CHANNEL_TIME error (%d)\n", + err); + goto done; + } + + /* Setup default scan unassoc time */ + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCAN_UNASSOC_TIME, + BRCMF_DEFAULT_SCAN_UNASSOC_TIME); + if (err) { + brcmf_err("BRCMF_C_SET_SCAN_UNASSOC_TIME error (%d)\n", + err); + goto done; + } + + /* Setup packet filter */ + brcmf_c_pktfilter_offload_set(ifp, BRCMF_DEFAULT_PACKET_FILTER); + brcmf_c_pktfilter_offload_enable(ifp, BRCMF_DEFAULT_PACKET_FILTER, + 0, true); + + /* set bus specific command if there is any */ + list_for_each_safe(cur, q, &ifp->drvr->bus_if->dcmd_list) { + cmdlst = list_entry(cur, struct brcmf_bus_dcmd, list); + if (cmdlst->name && cmdlst->param && cmdlst->param_len) { + brcmf_fil_iovar_data_set(ifp, cmdlst->name, + cmdlst->param, + cmdlst->param_len); + } + list_del(cur); + kfree(cmdlst); + } +done: + return err; +} + +#ifdef CPTCFG_BRCM_TRACING +void __brcmf_err(const char *func, const char *fmt, ...) +{ + struct va_format vaf = { + .fmt = fmt, + }; + va_list args; + + va_start(args, fmt); + vaf.va = &args; + pr_err("%s: %pV", func, &vaf); + trace_brcmf_err(func, &vaf); + va_end(args); +} +#endif +#if defined(CPTCFG_BRCM_TRACING) || defined(CPTCFG_BRCMDBG) +void __brcmf_dbg(u32 level, const char *func, const char *fmt, ...) +{ + struct va_format vaf = { + .fmt = fmt, + }; + va_list args; + + va_start(args, fmt); + vaf.va = &args; + if (brcmf_msg_level & level) + pr_debug("%s %pV", func, &vaf); + trace_brcmf_dbg(level, func, &vaf); + va_end(args); +} +#endif diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_dbg.c b/drivers/net/wireless/brcm80211/brcmfmac/dhd_dbg.c new file mode 100644 index 0000000..202869c --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_dbg.c @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2012 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include + +#include +#include +#include "dhd.h" +#include "dhd_bus.h" +#include "dhd_dbg.h" +#include "tracepoint.h" + +static struct dentry *root_folder; + +void brcmf_debugfs_init(void) +{ + root_folder = debugfs_create_dir(KBUILD_MODNAME, NULL); + if (IS_ERR(root_folder)) + root_folder = NULL; +} + +void brcmf_debugfs_exit(void) +{ + if (!root_folder) + return; + + debugfs_remove_recursive(root_folder); + root_folder = NULL; +} + +int brcmf_debugfs_attach(struct brcmf_pub *drvr) +{ + struct device *dev = drvr->bus_if->dev; + + if (!root_folder) + return -ENODEV; + + drvr->dbgfs_dir = debugfs_create_dir(dev_name(dev), root_folder); + return PTR_RET(drvr->dbgfs_dir); +} + +void brcmf_debugfs_detach(struct brcmf_pub *drvr) +{ + if (!IS_ERR_OR_NULL(drvr->dbgfs_dir)) + debugfs_remove_recursive(drvr->dbgfs_dir); +} + +struct dentry *brcmf_debugfs_get_devdir(struct brcmf_pub *drvr) +{ + return drvr->dbgfs_dir; +} + +static +ssize_t brcmf_debugfs_sdio_counter_read(struct file *f, char __user *data, + size_t count, loff_t *ppos) +{ + struct brcmf_sdio_count *sdcnt = f->private_data; + char buf[750]; + int res; + + /* only allow read from start */ + if (*ppos > 0) + return 0; + + res = scnprintf(buf, sizeof(buf), + "intrcount: %u\nlastintrs: %u\n" + "pollcnt: %u\nregfails: %u\n" + "tx_sderrs: %u\nfcqueued: %u\n" + "rxrtx: %u\nrx_toolong: %u\n" + "rxc_errors: %u\nrx_hdrfail: %u\n" + "rx_badhdr: %u\nrx_badseq: %u\n" + "fc_rcvd: %u\nfc_xoff: %u\n" + "fc_xon: %u\nrxglomfail: %u\n" + "rxglomframes: %u\nrxglompkts: %u\n" + "f2rxhdrs: %u\nf2rxdata: %u\n" + "f2txdata: %u\nf1regdata: %u\n" + "tickcnt: %u\ntx_ctlerrs: %lu\n" + "tx_ctlpkts: %lu\nrx_ctlerrs: %lu\n" + "rx_ctlpkts: %lu\nrx_readahead: %lu\n", + sdcnt->intrcount, sdcnt->lastintrs, + sdcnt->pollcnt, sdcnt->regfails, + sdcnt->tx_sderrs, sdcnt->fcqueued, + sdcnt->rxrtx, sdcnt->rx_toolong, + sdcnt->rxc_errors, sdcnt->rx_hdrfail, + sdcnt->rx_badhdr, sdcnt->rx_badseq, + sdcnt->fc_rcvd, sdcnt->fc_xoff, + sdcnt->fc_xon, sdcnt->rxglomfail, + sdcnt->rxglomframes, sdcnt->rxglompkts, + sdcnt->f2rxhdrs, sdcnt->f2rxdata, + sdcnt->f2txdata, sdcnt->f1regdata, + sdcnt->tickcnt, sdcnt->tx_ctlerrs, + sdcnt->tx_ctlpkts, sdcnt->rx_ctlerrs, + sdcnt->rx_ctlpkts, sdcnt->rx_readahead_cnt); + + return simple_read_from_buffer(data, count, ppos, buf, res); +} + +static const struct file_operations brcmf_debugfs_sdio_counter_ops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = brcmf_debugfs_sdio_counter_read +}; + +void brcmf_debugfs_create_sdio_count(struct brcmf_pub *drvr, + struct brcmf_sdio_count *sdcnt) +{ + struct dentry *dentry = drvr->dbgfs_dir; + + if (!IS_ERR_OR_NULL(dentry)) + debugfs_create_file("counters", S_IRUGO, dentry, + sdcnt, &brcmf_debugfs_sdio_counter_ops); +} + +static +ssize_t brcmf_debugfs_fws_stats_read(struct file *f, char __user *data, + size_t count, loff_t *ppos) +{ + struct brcmf_fws_stats *fwstats = f->private_data; + char buf[650]; + int res; + + /* only allow read from start */ + if (*ppos > 0) + return 0; + + res = scnprintf(buf, sizeof(buf), + "header_pulls: %u\n" + "header_only_pkt: %u\n" + "tlv_parse_failed: %u\n" + "tlv_invalid_type: %u\n" + "mac_update_fails: %u\n" + "ps_update_fails: %u\n" + "if_update_fails: %u\n" + "pkt2bus: %u\n" + "generic_error: %u\n" + "rollback_success: %u\n" + "rollback_failed: %u\n" + "delayq_full: %u\n" + "supprq_full: %u\n" + "txs_indicate: %u\n" + "txs_discard: %u\n" + "txs_suppr_core: %u\n" + "txs_suppr_ps: %u\n" + "txs_tossed: %u\n" + "send_pkts: BK:%u BE:%u VO:%u VI:%u BCMC:%u\n" + "fifo_credits_sent: BK:%u BE:%u VO:%u VI:%u BCMC:%u\n", + fwstats->header_pulls, + fwstats->header_only_pkt, + fwstats->tlv_parse_failed, + fwstats->tlv_invalid_type, + fwstats->mac_update_failed, + fwstats->mac_ps_update_failed, + fwstats->if_update_failed, + fwstats->pkt2bus, + fwstats->generic_error, + fwstats->rollback_success, + fwstats->rollback_failed, + fwstats->delayq_full_error, + fwstats->supprq_full_error, + fwstats->txs_indicate, + fwstats->txs_discard, + fwstats->txs_supp_core, + fwstats->txs_supp_ps, + fwstats->txs_tossed, + fwstats->send_pkts[0], fwstats->send_pkts[1], + fwstats->send_pkts[2], fwstats->send_pkts[3], + fwstats->send_pkts[4], + fwstats->fifo_credits_sent[0], + fwstats->fifo_credits_sent[1], + fwstats->fifo_credits_sent[2], + fwstats->fifo_credits_sent[3], + fwstats->fifo_credits_sent[4]); + + return simple_read_from_buffer(data, count, ppos, buf, res); +} + +static const struct file_operations brcmf_debugfs_fws_stats_ops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = brcmf_debugfs_fws_stats_read +}; + +void brcmf_debugfs_create_fws_stats(struct brcmf_pub *drvr, + struct brcmf_fws_stats *stats) +{ + struct dentry *dentry = drvr->dbgfs_dir; + + if (!IS_ERR_OR_NULL(dentry)) + debugfs_create_file("fws_stats", S_IRUGO, dentry, + stats, &brcmf_debugfs_fws_stats_ops); +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_dbg.h b/drivers/net/wireless/brcm80211/brcmfmac/dhd_dbg.h new file mode 100644 index 0000000..81e1b4c --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_dbg.h @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCMF_DBG_H_ +#define _BRCMF_DBG_H_ + +/* message levels */ +#define BRCMF_TRACE_VAL 0x00000002 +#define BRCMF_INFO_VAL 0x00000004 +#define BRCMF_DATA_VAL 0x00000008 +#define BRCMF_CTL_VAL 0x00000010 +#define BRCMF_TIMER_VAL 0x00000020 +#define BRCMF_HDRS_VAL 0x00000040 +#define BRCMF_BYTES_VAL 0x00000080 +#define BRCMF_INTR_VAL 0x00000100 +#define BRCMF_GLOM_VAL 0x00000200 +#define BRCMF_EVENT_VAL 0x00000400 +#define BRCMF_BTA_VAL 0x00000800 +#define BRCMF_FIL_VAL 0x00001000 +#define BRCMF_USB_VAL 0x00002000 +#define BRCMF_SCAN_VAL 0x00004000 +#define BRCMF_CONN_VAL 0x00008000 +#define BRCMF_CDC_VAL 0x00010000 +#define BRCMF_SDIO_VAL 0x00020000 + +/* set default print format */ +#undef pr_fmt +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +/* Macro for error messages. net_ratelimit() is used when driver + * debugging is not selected. When debugging the driver error + * messages are as important as other tracing or even more so. + */ +#ifndef CPTCFG_BRCM_TRACING +#ifdef CPTCFG_BRCMDBG +#define brcmf_err(fmt, ...) pr_err("%s: " fmt, __func__, ##__VA_ARGS__) +#else +#define brcmf_err(fmt, ...) \ + do { \ + if (net_ratelimit()) \ + pr_err("%s: " fmt, __func__, ##__VA_ARGS__); \ + } while (0) +#endif +#else +__printf(2, 3) +void __brcmf_err(const char *func, const char *fmt, ...); +#define brcmf_err(fmt, ...) \ + __brcmf_err(__func__, fmt, ##__VA_ARGS__) +#endif + +#if defined(DEBUG) || defined(CPTCFG_BRCM_TRACING) +__printf(3, 4) +void __brcmf_dbg(u32 level, const char *func, const char *fmt, ...); +#define brcmf_dbg(level, fmt, ...) \ +do { \ + __brcmf_dbg(BRCMF_##level##_VAL, __func__, \ + fmt, ##__VA_ARGS__); \ +} while (0) +#define BRCMF_DATA_ON() (brcmf_msg_level & BRCMF_DATA_VAL) +#define BRCMF_CTL_ON() (brcmf_msg_level & BRCMF_CTL_VAL) +#define BRCMF_HDRS_ON() (brcmf_msg_level & BRCMF_HDRS_VAL) +#define BRCMF_BYTES_ON() (brcmf_msg_level & BRCMF_BYTES_VAL) +#define BRCMF_GLOM_ON() (brcmf_msg_level & BRCMF_GLOM_VAL) +#define BRCMF_EVENT_ON() (brcmf_msg_level & BRCMF_EVENT_VAL) +#define BRCMF_FIL_ON() (brcmf_msg_level & BRCMF_FIL_VAL) + +#else /* defined(DEBUG) || defined(CPTCFG_BRCM_TRACING) */ + +#define brcmf_dbg(level, fmt, ...) no_printk(fmt, ##__VA_ARGS__) + +#define BRCMF_DATA_ON() 0 +#define BRCMF_CTL_ON() 0 +#define BRCMF_HDRS_ON() 0 +#define BRCMF_BYTES_ON() 0 +#define BRCMF_GLOM_ON() 0 +#define BRCMF_EVENT_ON() 0 +#define BRCMF_FIL_ON() 0 + +#endif /* defined(DEBUG) || defined(CPTCFG_BRCM_TRACING) */ + +#define brcmf_dbg_hex_dump(test, data, len, fmt, ...) \ +do { \ + trace_brcmf_hexdump((void *)data, len); \ + if (test) \ + brcmu_dbg_hex_dump(data, len, fmt, ##__VA_ARGS__); \ +} while (0) + +extern int brcmf_msg_level; + +/* + * hold counter variables used in brcmfmac sdio driver. + */ +struct brcmf_sdio_count { + uint intrcount; /* Count of device interrupt callbacks */ + uint lastintrs; /* Count as of last watchdog timer */ + uint pollcnt; /* Count of active polls */ + uint regfails; /* Count of R_REG failures */ + uint tx_sderrs; /* Count of tx attempts with sd errors */ + uint fcqueued; /* Tx packets that got queued */ + uint rxrtx; /* Count of rtx requests (NAK to dongle) */ + uint rx_toolong; /* Receive frames too long to receive */ + uint rxc_errors; /* SDIO errors when reading control frames */ + uint rx_hdrfail; /* SDIO errors on header reads */ + uint rx_badhdr; /* Bad received headers (roosync?) */ + uint rx_badseq; /* Mismatched rx sequence number */ + uint fc_rcvd; /* Number of flow-control events received */ + uint fc_xoff; /* Number which turned on flow-control */ + uint fc_xon; /* Number which turned off flow-control */ + uint rxglomfail; /* Failed deglom attempts */ + uint rxglomframes; /* Number of glom frames (superframes) */ + uint rxglompkts; /* Number of packets from glom frames */ + uint f2rxhdrs; /* Number of header reads */ + uint f2rxdata; /* Number of frame data reads */ + uint f2txdata; /* Number of f2 frame writes */ + uint f1regdata; /* Number of f1 register accesses */ + uint tickcnt; /* Number of watchdog been schedule */ + ulong tx_ctlerrs; /* Err of sending ctrl frames */ + ulong tx_ctlpkts; /* Ctrl frames sent to dongle */ + ulong rx_ctlerrs; /* Err of processing rx ctrl frames */ + ulong rx_ctlpkts; /* Ctrl frames processed from dongle */ + ulong rx_readahead_cnt; /* packets where header read-ahead was used */ +}; + +struct brcmf_fws_stats { + u32 tlv_parse_failed; + u32 tlv_invalid_type; + u32 header_only_pkt; + u32 header_pulls; + u32 pkt2bus; + u32 send_pkts[5]; + u32 fifo_credits_sent[5]; + u32 fifo_credits_back[6]; + u32 generic_error; + u32 mac_update_failed; + u32 mac_ps_update_failed; + u32 if_update_failed; + u32 packet_request_failed; + u32 credit_request_failed; + u32 rollback_success; + u32 rollback_failed; + u32 delayq_full_error; + u32 supprq_full_error; + u32 txs_indicate; + u32 txs_discard; + u32 txs_supp_core; + u32 txs_supp_ps; + u32 txs_tossed; +}; + +struct brcmf_pub; +#ifdef DEBUG +void brcmf_debugfs_init(void); +void brcmf_debugfs_exit(void); +int brcmf_debugfs_attach(struct brcmf_pub *drvr); +void brcmf_debugfs_detach(struct brcmf_pub *drvr); +struct dentry *brcmf_debugfs_get_devdir(struct brcmf_pub *drvr); +void brcmf_debugfs_create_sdio_count(struct brcmf_pub *drvr, + struct brcmf_sdio_count *sdcnt); +void brcmf_debugfs_create_fws_stats(struct brcmf_pub *drvr, + struct brcmf_fws_stats *stats); +#else +static inline void brcmf_debugfs_init(void) +{ +} +static inline void brcmf_debugfs_exit(void) +{ +} +static inline int brcmf_debugfs_attach(struct brcmf_pub *drvr) +{ + return 0; +} +static inline void brcmf_debugfs_detach(struct brcmf_pub *drvr) +{ +} +static inline void brcmf_debugfs_create_fws_stats(struct brcmf_pub *drvr, + struct brcmf_fws_stats *stats) +{ +} +#endif + +#endif /* _BRCMF_DBG_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_linux.c b/drivers/net/wireless/brcm80211/brcmfmac/dhd_linux.c new file mode 100644 index 0000000..ffb581e --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_linux.c @@ -0,0 +1,1078 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "dhd.h" +#include "dhd_bus.h" +#include "dhd_proto.h" +#include "dhd_dbg.h" +#include "fwil_types.h" +#include "p2p.h" +#include "wl_cfg80211.h" +#include "fwil.h" +#include "fwsignal.h" + +MODULE_AUTHOR("Broadcom Corporation"); +MODULE_DESCRIPTION("Broadcom 802.11 wireless LAN fullmac driver."); +MODULE_LICENSE("Dual BSD/GPL"); + +#define MAX_WAIT_FOR_8021X_TX 50 /* msecs */ + +/* Error bits */ +int brcmf_msg_level; +module_param_named(debug, brcmf_msg_level, int, S_IRUSR | S_IWUSR); +MODULE_PARM_DESC(debug, "level of debug output"); + +/* P2P0 enable */ +static int brcmf_p2p_enable; +#ifdef CPTCFG_BRCMDBG +module_param_named(p2pon, brcmf_p2p_enable, int, 0); +MODULE_PARM_DESC(p2pon, "enable p2p management functionality"); +#endif + +char *brcmf_ifname(struct brcmf_pub *drvr, int ifidx) +{ + if (ifidx < 0 || ifidx >= BRCMF_MAX_IFS) { + brcmf_err("ifidx %d out of range\n", ifidx); + return ""; + } + + if (drvr->iflist[ifidx] == NULL) { + brcmf_err("null i/f %d\n", ifidx); + return ""; + } + + if (drvr->iflist[ifidx]->ndev) + return drvr->iflist[ifidx]->ndev->name; + + return ""; +} + +static void _brcmf_set_multicast_list(struct work_struct *work) +{ + struct brcmf_if *ifp; + struct net_device *ndev; + struct netdev_hw_addr *ha; + u32 cmd_value, cnt; + __le32 cnt_le; + char *buf, *bufp; + u32 buflen; + s32 err; + + ifp = container_of(work, struct brcmf_if, multicast_work); + + brcmf_dbg(TRACE, "Enter, idx=%d\n", ifp->bssidx); + + ndev = ifp->ndev; + + /* Determine initial value of allmulti flag */ + cmd_value = (ndev->flags & IFF_ALLMULTI) ? true : false; + + /* Send down the multicast list first. */ + cnt = netdev_mc_count(ndev); + buflen = sizeof(cnt) + (cnt * ETH_ALEN); + buf = kmalloc(buflen, GFP_ATOMIC); + if (!buf) + return; + bufp = buf; + + cnt_le = cpu_to_le32(cnt); + memcpy(bufp, &cnt_le, sizeof(cnt_le)); + bufp += sizeof(cnt_le); + + netdev_for_each_mc_addr(ha, ndev) { + if (!cnt) + break; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + memcpy(bufp, ha->addr, ETH_ALEN); +#else + memcpy(bufp, ha->dmi_addr, ETH_ALEN); +#endif + bufp += ETH_ALEN; + cnt--; + } + + err = brcmf_fil_iovar_data_set(ifp, "mcast_list", buf, buflen); + if (err < 0) { + brcmf_err("Setting mcast_list failed, %d\n", err); + cmd_value = cnt ? true : cmd_value; + } + + kfree(buf); + + /* + * Now send the allmulti setting. This is based on the setting in the + * net_device flags, but might be modified above to be turned on if we + * were trying to set some addresses and dongle rejected it... + */ + err = brcmf_fil_iovar_int_set(ifp, "allmulti", cmd_value); + if (err < 0) + brcmf_err("Setting allmulti failed, %d\n", err); + + /*Finally, pick up the PROMISC flag */ + cmd_value = (ndev->flags & IFF_PROMISC) ? true : false; + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PROMISC, cmd_value); + if (err < 0) + brcmf_err("Setting BRCMF_C_SET_PROMISC failed, %d\n", + err); +} + +static void +_brcmf_set_mac_address(struct work_struct *work) +{ + struct brcmf_if *ifp; + s32 err; + + ifp = container_of(work, struct brcmf_if, setmacaddr_work); + + brcmf_dbg(TRACE, "Enter, idx=%d\n", ifp->bssidx); + + err = brcmf_fil_iovar_data_set(ifp, "cur_etheraddr", ifp->mac_addr, + ETH_ALEN); + if (err < 0) { + brcmf_err("Setting cur_etheraddr failed, %d\n", err); + } else { + brcmf_dbg(TRACE, "MAC address updated to %pM\n", + ifp->mac_addr); + memcpy(ifp->ndev->dev_addr, ifp->mac_addr, ETH_ALEN); + } +} + +static int brcmf_netdev_set_mac_address(struct net_device *ndev, void *addr) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + struct sockaddr *sa = (struct sockaddr *)addr; + + memcpy(&ifp->mac_addr, sa->sa_data, ETH_ALEN); + schedule_work(&ifp->setmacaddr_work); + return 0; +} + +static void brcmf_netdev_set_multicast_list(struct net_device *ndev) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + + schedule_work(&ifp->multicast_work); +} + +static netdev_tx_t brcmf_netdev_start_xmit(struct sk_buff *skb, + struct net_device *ndev) +{ + int ret; + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_pub *drvr = ifp->drvr; + struct ethhdr *eh; + + brcmf_dbg(TRACE, "Enter, idx=%d\n", ifp->bssidx); + + /* Can the device send data? */ + if (drvr->bus_if->state != BRCMF_BUS_DATA) { + brcmf_err("xmit rejected state=%d\n", drvr->bus_if->state); + netif_stop_queue(ndev); + dev_kfree_skb(skb); + ret = -ENODEV; + goto done; + } + + if (!drvr->iflist[ifp->bssidx]) { + brcmf_err("bad ifidx %d\n", ifp->bssidx); + netif_stop_queue(ndev); + dev_kfree_skb(skb); + ret = -ENODEV; + goto done; + } + + /* Make sure there's enough room for any header */ + if (skb_headroom(skb) < drvr->hdrlen) { + struct sk_buff *skb2; + + brcmf_dbg(INFO, "%s: insufficient headroom\n", + brcmf_ifname(drvr, ifp->bssidx)); + drvr->bus_if->tx_realloc++; + skb2 = skb_realloc_headroom(skb, drvr->hdrlen); + dev_kfree_skb(skb); + skb = skb2; + if (skb == NULL) { + brcmf_err("%s: skb_realloc_headroom failed\n", + brcmf_ifname(drvr, ifp->bssidx)); + ret = -ENOMEM; + goto done; + } + } + + /* validate length for ether packet */ + if (skb->len < sizeof(*eh)) { + ret = -EINVAL; + dev_kfree_skb(skb); + goto done; + } + + ret = brcmf_fws_process_skb(ifp, skb); + +done: + if (ret) { + ifp->stats.tx_dropped++; + } else { + ifp->stats.tx_packets++; + ifp->stats.tx_bytes += skb->len; + } + + /* Return ok: we always eat the packet */ + return NETDEV_TX_OK; +} + +void brcmf_txflowblock_if(struct brcmf_if *ifp, + enum brcmf_netif_stop_reason reason, bool state) +{ + if (!ifp) + return; + + brcmf_dbg(TRACE, "enter: idx=%d stop=0x%X reason=%d state=%d\n", + ifp->bssidx, ifp->netif_stop, reason, state); + if (state) { + if (!ifp->netif_stop) + netif_stop_queue(ifp->ndev); + ifp->netif_stop |= reason; + } else { + ifp->netif_stop &= ~reason; + if (!ifp->netif_stop) + netif_wake_queue(ifp->ndev); + } +} + +void brcmf_txflowblock(struct device *dev, bool state) +{ + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_pub *drvr = bus_if->drvr; + int i; + + brcmf_dbg(TRACE, "Enter\n"); + + for (i = 0; i < BRCMF_MAX_IFS; i++) + brcmf_txflowblock_if(drvr->iflist[i], + BRCMF_NETIF_STOP_REASON_BLOCK_BUS, state); +} + +void brcmf_rx_frames(struct device *dev, struct sk_buff_head *skb_list) +{ + unsigned char *eth; + uint len; + struct sk_buff *skb, *pnext; + struct brcmf_if *ifp; + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_pub *drvr = bus_if->drvr; + u8 ifidx; + int ret; + + brcmf_dbg(TRACE, "Enter\n"); + + skb_queue_walk_safe(skb_list, skb, pnext) { + skb_unlink(skb, skb_list); + + /* process and remove protocol-specific header */ + ret = brcmf_proto_hdrpull(drvr, drvr->fw_signals, &ifidx, skb); + ifp = drvr->iflist[ifidx]; + + if (ret || !ifp || !ifp->ndev) { + if ((ret != -ENODATA) && ifp) + ifp->stats.rx_errors++; + brcmu_pkt_buf_free_skb(skb); + continue; + } + + /* Get the protocol, maintain skb around eth_type_trans() + * The main reason for this hack is for the limitation of + * Linux 2.4 where 'eth_type_trans' uses the + * 'net->hard_header_len' + * to perform skb_pull inside vs ETH_HLEN. Since to avoid + * coping of the packet coming from the network stack to add + * BDC, Hardware header etc, during network interface + * registration + * we set the 'net->hard_header_len' to ETH_HLEN + extra space + * required + * for BDC, Hardware header etc. and not just the ETH_HLEN + */ + eth = skb->data; + len = skb->len; + + skb->dev = ifp->ndev; + skb->protocol = eth_type_trans(skb, skb->dev); + + if (skb->pkt_type == PACKET_MULTICAST) + ifp->stats.multicast++; + + skb->data = eth; + skb->len = len; + + /* Strip header, count, deliver upward */ + skb_pull(skb, ETH_HLEN); + + /* Process special event packets */ + brcmf_fweh_process_skb(drvr, skb); + + if (!(ifp->ndev->flags & IFF_UP)) { + brcmu_pkt_buf_free_skb(skb); + continue; + } + + ifp->stats.rx_bytes += skb->len; + ifp->stats.rx_packets++; + + if (in_interrupt()) + netif_rx(skb); + else + /* If the receive is not processed inside an ISR, + * the softirqd must be woken explicitly to service + * the NET_RX_SOFTIRQ. In 2.6 kernels, this is handled + * by netif_rx_ni(), but in earlier kernels, we need + * to do it manually. + */ + netif_rx_ni(skb); + } +} + +void brcmf_txfinalize(struct brcmf_pub *drvr, struct sk_buff *txp, + bool success) +{ + struct brcmf_if *ifp; + struct ethhdr *eh; + u8 ifidx; + u16 type; + int res; + + res = brcmf_proto_hdrpull(drvr, false, &ifidx, txp); + + ifp = drvr->iflist[ifidx]; + if (!ifp) + goto done; + + if (res == 0) { + eh = (struct ethhdr *)(txp->data); + type = ntohs(eh->h_proto); + + if (type == ETH_P_PAE) { + atomic_dec(&ifp->pend_8021x_cnt); + if (waitqueue_active(&ifp->pend_8021x_wait)) + wake_up(&ifp->pend_8021x_wait); + } + } + if (!success) + ifp->stats.tx_errors++; +done: + brcmu_pkt_buf_free_skb(txp); +} + +void brcmf_txcomplete(struct device *dev, struct sk_buff *txp, bool success) +{ + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_pub *drvr = bus_if->drvr; + + /* await txstatus signal for firmware if active */ + if (brcmf_fws_fc_active(drvr->fws)) { + if (!success) + brcmf_fws_bustxfail(drvr->fws, txp); + } else { + brcmf_txfinalize(drvr, txp, success); + } +} + +static struct net_device_stats *brcmf_netdev_get_stats(struct net_device *ndev) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + + brcmf_dbg(TRACE, "Enter, idx=%d\n", ifp->bssidx); + + return &ifp->stats; +} + +/* + * Set current toe component enables in toe_ol iovar, + * and set toe global enable iovar + */ +static int brcmf_toe_set(struct brcmf_if *ifp, u32 toe_ol) +{ + s32 err; + + err = brcmf_fil_iovar_int_set(ifp, "toe_ol", toe_ol); + if (err < 0) { + brcmf_err("Setting toe_ol failed, %d\n", err); + return err; + } + + err = brcmf_fil_iovar_int_set(ifp, "toe", (toe_ol != 0)); + if (err < 0) + brcmf_err("Setting toe failed, %d\n", err); + + return err; + +} + +static void brcmf_ethtool_get_drvinfo(struct net_device *ndev, + struct ethtool_drvinfo *info) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_pub *drvr = ifp->drvr; + + strlcpy(info->driver, KBUILD_MODNAME, sizeof(info->driver)); + snprintf(info->version, sizeof(info->version), "%lu", + drvr->drv_version); + strlcpy(info->bus_info, dev_name(drvr->bus_if->dev), + sizeof(info->bus_info)); +} + +static const struct ethtool_ops brcmf_ethtool_ops = { + .get_drvinfo = brcmf_ethtool_get_drvinfo, +}; + +static int brcmf_ethtool(struct brcmf_if *ifp, void __user *uaddr) +{ + struct brcmf_pub *drvr = ifp->drvr; + struct ethtool_drvinfo info; + char drvname[sizeof(info.driver)]; + u32 cmd; + struct ethtool_value edata; + u32 toe_cmpnt, csum_dir; + int ret; + + brcmf_dbg(TRACE, "Enter, idx=%d\n", ifp->bssidx); + + /* all ethtool calls start with a cmd word */ + if (copy_from_user(&cmd, uaddr, sizeof(u32))) + return -EFAULT; + + switch (cmd) { + case ETHTOOL_GDRVINFO: + /* Copy out any request driver name */ + if (copy_from_user(&info, uaddr, sizeof(info))) + return -EFAULT; + strncpy(drvname, info.driver, sizeof(info.driver)); + drvname[sizeof(info.driver) - 1] = '\0'; + + /* clear struct for return */ + memset(&info, 0, sizeof(info)); + info.cmd = cmd; + + /* if requested, identify ourselves */ + if (strcmp(drvname, "?dhd") == 0) { + sprintf(info.driver, "dhd"); + strcpy(info.version, BRCMF_VERSION_STR); + } + /* report dongle driver type */ + else + sprintf(info.driver, "wl"); + + sprintf(info.version, "%lu", drvr->drv_version); + if (copy_to_user(uaddr, &info, sizeof(info))) + return -EFAULT; + brcmf_dbg(TRACE, "given %*s, returning %s\n", + (int)sizeof(drvname), drvname, info.driver); + break; + + /* Get toe offload components from dongle */ + case ETHTOOL_GRXCSUM: + case ETHTOOL_GTXCSUM: + ret = brcmf_fil_iovar_int_get(ifp, "toe_ol", &toe_cmpnt); + if (ret < 0) + return ret; + + csum_dir = + (cmd == ETHTOOL_GTXCSUM) ? TOE_TX_CSUM_OL : TOE_RX_CSUM_OL; + + edata.cmd = cmd; + edata.data = (toe_cmpnt & csum_dir) ? 1 : 0; + + if (copy_to_user(uaddr, &edata, sizeof(edata))) + return -EFAULT; + break; + + /* Set toe offload components in dongle */ + case ETHTOOL_SRXCSUM: + case ETHTOOL_STXCSUM: + if (copy_from_user(&edata, uaddr, sizeof(edata))) + return -EFAULT; + + /* Read the current settings, update and write back */ + ret = brcmf_fil_iovar_int_get(ifp, "toe_ol", &toe_cmpnt); + if (ret < 0) + return ret; + + csum_dir = + (cmd == ETHTOOL_STXCSUM) ? TOE_TX_CSUM_OL : TOE_RX_CSUM_OL; + + if (edata.data != 0) + toe_cmpnt |= csum_dir; + else + toe_cmpnt &= ~csum_dir; + + ret = brcmf_toe_set(ifp, toe_cmpnt); + if (ret < 0) + return ret; + + /* If setting TX checksum mode, tell Linux the new mode */ + if (cmd == ETHTOOL_STXCSUM) { + if (edata.data) + ifp->ndev->features |= NETIF_F_IP_CSUM; + else + ifp->ndev->features &= ~NETIF_F_IP_CSUM; + } + + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int brcmf_netdev_ioctl_entry(struct net_device *ndev, struct ifreq *ifr, + int cmd) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_pub *drvr = ifp->drvr; + + brcmf_dbg(TRACE, "Enter, idx=%d, cmd=0x%04x\n", ifp->bssidx, cmd); + + if (!drvr->iflist[ifp->bssidx]) + return -1; + + if (cmd == SIOCETHTOOL) + return brcmf_ethtool(ifp, ifr->ifr_data); + + return -EOPNOTSUPP; +} + +static int brcmf_netdev_stop(struct net_device *ndev) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + + brcmf_dbg(TRACE, "Enter, idx=%d\n", ifp->bssidx); + + brcmf_cfg80211_down(ndev); + + /* Set state and stop OS transmissions */ + netif_stop_queue(ndev); + + return 0; +} + +static int brcmf_netdev_open(struct net_device *ndev) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_pub *drvr = ifp->drvr; + struct brcmf_bus *bus_if = drvr->bus_if; + u32 toe_ol; + s32 ret = 0; + + brcmf_dbg(TRACE, "Enter, idx=%d\n", ifp->bssidx); + + /* If bus is not ready, can't continue */ + if (bus_if->state != BRCMF_BUS_DATA) { + brcmf_err("failed bus is not ready\n"); + return -EAGAIN; + } + + atomic_set(&ifp->pend_8021x_cnt, 0); + + /* Get current TOE mode from dongle */ + if (brcmf_fil_iovar_int_get(ifp, "toe_ol", &toe_ol) >= 0 + && (toe_ol & TOE_TX_CSUM_OL) != 0) + ndev->features |= NETIF_F_IP_CSUM; + else + ndev->features &= ~NETIF_F_IP_CSUM; + + /* Allow transmit calls */ + netif_start_queue(ndev); + if (brcmf_cfg80211_up(ndev)) { + brcmf_err("failed to bring up cfg80211\n"); + return -1; + } + + return ret; +} + +static const struct net_device_ops brcmf_netdev_ops_pri = { + .ndo_open = brcmf_netdev_open, + .ndo_stop = brcmf_netdev_stop, + .ndo_get_stats = brcmf_netdev_get_stats, + .ndo_do_ioctl = brcmf_netdev_ioctl_entry, + .ndo_start_xmit = brcmf_netdev_start_xmit, + .ndo_set_mac_address = brcmf_netdev_set_mac_address, + .ndo_set_rx_mode = brcmf_netdev_set_multicast_list +}; + +int brcmf_net_attach(struct brcmf_if *ifp, bool rtnl_locked) +{ + struct brcmf_pub *drvr = ifp->drvr; + struct net_device *ndev; + s32 err; + + brcmf_dbg(TRACE, "Enter, idx=%d mac=%pM\n", ifp->bssidx, + ifp->mac_addr); + ndev = ifp->ndev; + + /* set appropriate operations */ + ndev->netdev_ops = &brcmf_netdev_ops_pri; + + ndev->hard_header_len = ETH_HLEN + drvr->hdrlen; + ndev->ethtool_ops = &brcmf_ethtool_ops; + + drvr->rxsz = ndev->mtu + ndev->hard_header_len + + drvr->hdrlen; + + /* set the mac address */ + memcpy(ndev->dev_addr, ifp->mac_addr, ETH_ALEN); + + INIT_WORK(&ifp->setmacaddr_work, _brcmf_set_mac_address); + INIT_WORK(&ifp->multicast_work, _brcmf_set_multicast_list); + + if (rtnl_locked) + err = register_netdevice(ndev); + else + err = register_netdev(ndev); + if (err != 0) { + brcmf_err("couldn't register the net device\n"); + goto fail; + } + + brcmf_dbg(INFO, "%s: Broadcom Dongle Host Driver\n", ndev->name); + + ndev->destructor = free_netdev; + return 0; + +fail: + drvr->iflist[ifp->bssidx] = NULL; + ndev->netdev_ops = NULL; + free_netdev(ndev); + return -EBADE; +} + +static int brcmf_net_p2p_open(struct net_device *ndev) +{ + brcmf_dbg(TRACE, "Enter\n"); + + return brcmf_cfg80211_up(ndev); +} + +static int brcmf_net_p2p_stop(struct net_device *ndev) +{ + brcmf_dbg(TRACE, "Enter\n"); + + return brcmf_cfg80211_down(ndev); +} + +static int brcmf_net_p2p_do_ioctl(struct net_device *ndev, + struct ifreq *ifr, int cmd) +{ + brcmf_dbg(TRACE, "Enter\n"); + return 0; +} + +static netdev_tx_t brcmf_net_p2p_start_xmit(struct sk_buff *skb, + struct net_device *ndev) +{ + if (skb) + dev_kfree_skb_any(skb); + + return NETDEV_TX_OK; +} + +static const struct net_device_ops brcmf_netdev_ops_p2p = { + .ndo_open = brcmf_net_p2p_open, + .ndo_stop = brcmf_net_p2p_stop, + .ndo_do_ioctl = brcmf_net_p2p_do_ioctl, + .ndo_start_xmit = brcmf_net_p2p_start_xmit +}; + +static int brcmf_net_p2p_attach(struct brcmf_if *ifp) +{ + struct net_device *ndev; + + brcmf_dbg(TRACE, "Enter, idx=%d mac=%pM\n", ifp->bssidx, + ifp->mac_addr); + ndev = ifp->ndev; + + ndev->netdev_ops = &brcmf_netdev_ops_p2p; + + /* set the mac address */ + memcpy(ndev->dev_addr, ifp->mac_addr, ETH_ALEN); + + if (register_netdev(ndev) != 0) { + brcmf_err("couldn't register the p2p net device\n"); + goto fail; + } + + brcmf_dbg(INFO, "%s: Broadcom Dongle Host Driver\n", ndev->name); + + return 0; + +fail: + ifp->drvr->iflist[ifp->bssidx] = NULL; + ndev->netdev_ops = NULL; + free_netdev(ndev); + return -EBADE; +} + +struct brcmf_if *brcmf_add_if(struct brcmf_pub *drvr, s32 bssidx, s32 ifidx, + char *name, u8 *mac_addr) +{ + struct brcmf_if *ifp; + struct net_device *ndev; + + brcmf_dbg(TRACE, "Enter, idx=%d, ifidx=%d\n", bssidx, ifidx); + + ifp = drvr->iflist[bssidx]; + /* + * Delete the existing interface before overwriting it + * in case we missed the BRCMF_E_IF_DEL event. + */ + if (ifp) { + brcmf_err("ERROR: netdev:%s already exists\n", + ifp->ndev->name); + if (ifidx) { + netif_stop_queue(ifp->ndev); + unregister_netdev(ifp->ndev); + free_netdev(ifp->ndev); + drvr->iflist[bssidx] = NULL; + } else { + brcmf_err("ignore IF event\n"); + return ERR_PTR(-EINVAL); + } + } + + if (!brcmf_p2p_enable && bssidx == 1) { + /* this is P2P_DEVICE interface */ + brcmf_dbg(INFO, "allocate non-netdev interface\n"); + ifp = kzalloc(sizeof(*ifp), GFP_KERNEL); + if (!ifp) + return ERR_PTR(-ENOMEM); + } else { + brcmf_dbg(INFO, "allocate netdev interface\n"); + /* Allocate netdev, including space for private structure */ + ndev = alloc_netdev(sizeof(*ifp), name, ether_setup); + if (!ndev) + return ERR_PTR(-ENOMEM); + + ifp = netdev_priv(ndev); + ifp->ndev = ndev; + } + + ifp->drvr = drvr; + drvr->iflist[bssidx] = ifp; + ifp->ifidx = ifidx; + ifp->bssidx = bssidx; + + init_waitqueue_head(&ifp->pend_8021x_wait); + + if (mac_addr != NULL) + memcpy(ifp->mac_addr, mac_addr, ETH_ALEN); + + brcmf_dbg(TRACE, " ==== pid:%x, if:%s (%pM) created ===\n", + current->pid, name, ifp->mac_addr); + + return ifp; +} + +void brcmf_del_if(struct brcmf_pub *drvr, s32 bssidx) +{ + struct brcmf_if *ifp; + + ifp = drvr->iflist[bssidx]; + drvr->iflist[bssidx] = NULL; + if (!ifp) { + brcmf_err("Null interface, idx=%d\n", bssidx); + return; + } + brcmf_dbg(TRACE, "Enter, idx=%d, ifidx=%d\n", bssidx, ifp->ifidx); + if (ifp->ndev) { + if (bssidx == 0) { + if (ifp->ndev->netdev_ops == &brcmf_netdev_ops_pri) { + rtnl_lock(); + brcmf_netdev_stop(ifp->ndev); + rtnl_unlock(); + } + } else { + netif_stop_queue(ifp->ndev); + } + + if (ifp->ndev->netdev_ops == &brcmf_netdev_ops_pri) { + cancel_work_sync(&ifp->setmacaddr_work); + cancel_work_sync(&ifp->multicast_work); + } + /* unregister will take care of freeing it */ + unregister_netdev(ifp->ndev); + if (bssidx == 0) + brcmf_cfg80211_detach(drvr->config); + } else { + kfree(ifp); + } +} + +int brcmf_attach(uint bus_hdrlen, struct device *dev) +{ + struct brcmf_pub *drvr = NULL; + int ret = 0; + + brcmf_dbg(TRACE, "Enter\n"); + + /* Allocate primary brcmf_info */ + drvr = kzalloc(sizeof(struct brcmf_pub), GFP_ATOMIC); + if (!drvr) + return -ENOMEM; + + mutex_init(&drvr->proto_block); + + /* Link to bus module */ + drvr->hdrlen = bus_hdrlen; + drvr->bus_if = dev_get_drvdata(dev); + drvr->bus_if->drvr = drvr; + + /* create device debugfs folder */ + brcmf_debugfs_attach(drvr); + + /* Attach and link in the protocol */ + ret = brcmf_proto_attach(drvr); + if (ret != 0) { + brcmf_err("brcmf_prot_attach failed\n"); + goto fail; + } + + /* attach firmware event handler */ + brcmf_fweh_attach(drvr); + + INIT_LIST_HEAD(&drvr->bus_if->dcmd_list); + + return ret; + +fail: + brcmf_detach(dev); + + return ret; +} + +int brcmf_bus_start(struct device *dev) +{ + int ret = -1; + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_pub *drvr = bus_if->drvr; + struct brcmf_if *ifp; + struct brcmf_if *p2p_ifp; + + brcmf_dbg(TRACE, "\n"); + + /* Bring up the bus */ + ret = brcmf_bus_init(bus_if); + if (ret != 0) { + brcmf_err("brcmf_sdbrcm_bus_init failed %d\n", ret); + return ret; + } + + /* add primary networking interface */ + ifp = brcmf_add_if(drvr, 0, 0, "wlan%d", NULL); + if (IS_ERR(ifp)) + return PTR_ERR(ifp); + + if (brcmf_p2p_enable) + p2p_ifp = brcmf_add_if(drvr, 1, 0, "p2p%d", NULL); + else + p2p_ifp = NULL; + if (IS_ERR(p2p_ifp)) + p2p_ifp = NULL; + + /* signal bus ready */ + bus_if->state = BRCMF_BUS_DATA; + + /* Bus is ready, do any initialization */ + ret = brcmf_c_preinit_dcmds(ifp); + if (ret < 0) + goto fail; + + drvr->fw_signals = true; + ret = brcmf_fws_init(drvr); + if (ret < 0) + goto fail; + + brcmf_fws_add_interface(ifp); + + drvr->config = brcmf_cfg80211_attach(drvr, bus_if->dev); + if (drvr->config == NULL) { + ret = -ENOMEM; + goto fail; + } + + ret = brcmf_fweh_activate_events(ifp); + if (ret < 0) + goto fail; + + ret = brcmf_net_attach(ifp, false); +fail: + if (ret < 0) { + brcmf_err("failed: %d\n", ret); + if (drvr->config) + brcmf_cfg80211_detach(drvr->config); + if (drvr->fws) { + brcmf_fws_del_interface(ifp); + brcmf_fws_deinit(drvr); + } + if (drvr->iflist[0]) { + free_netdev(ifp->ndev); + drvr->iflist[0] = NULL; + } + if (p2p_ifp) { + free_netdev(p2p_ifp->ndev); + drvr->iflist[1] = NULL; + } + return ret; + } + if ((brcmf_p2p_enable) && (p2p_ifp)) + if (brcmf_net_p2p_attach(p2p_ifp) < 0) + brcmf_p2p_enable = 0; + + return 0; +} + +static void brcmf_bus_detach(struct brcmf_pub *drvr) +{ + brcmf_dbg(TRACE, "Enter\n"); + + if (drvr) { + /* Stop the protocol module */ + brcmf_proto_stop(drvr); + + /* Stop the bus module */ + brcmf_bus_stop(drvr->bus_if); + } +} + +void brcmf_dev_reset(struct device *dev) +{ + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_pub *drvr = bus_if->drvr; + + if (drvr == NULL) + return; + + if (drvr->iflist[0]) + brcmf_fil_cmd_int_set(drvr->iflist[0], BRCMF_C_TERMINATED, 1); +} + +void brcmf_detach(struct device *dev) +{ + s32 i; + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_pub *drvr = bus_if->drvr; + + brcmf_dbg(TRACE, "Enter\n"); + + if (drvr == NULL) + return; + + /* stop firmware event handling */ + brcmf_fweh_detach(drvr); + + /* make sure primary interface removed last */ + for (i = BRCMF_MAX_IFS-1; i > -1; i--) + if (drvr->iflist[i]) { + brcmf_fws_del_interface(drvr->iflist[i]); + brcmf_del_if(drvr, i); + } + + brcmf_bus_detach(drvr); + + if (drvr->prot) + brcmf_proto_detach(drvr); + + brcmf_fws_deinit(drvr); + + brcmf_debugfs_detach(drvr); + bus_if->drvr = NULL; + kfree(drvr); +} + +static int brcmf_get_pend_8021x_cnt(struct brcmf_if *ifp) +{ + return atomic_read(&ifp->pend_8021x_cnt); +} + +int brcmf_netdev_wait_pend8021x(struct net_device *ndev) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + int err; + + err = wait_event_timeout(ifp->pend_8021x_wait, + !brcmf_get_pend_8021x_cnt(ifp), + msecs_to_jiffies(MAX_WAIT_FOR_8021X_TX)); + + WARN_ON(!err); + + return !err; +} + +/* + * return chip id and rev of the device encoded in u32. + */ +u32 brcmf_get_chip_info(struct brcmf_if *ifp) +{ + struct brcmf_bus *bus = ifp->drvr->bus_if; + + return bus->chip << 4 | bus->chiprev; +} + +static void brcmf_driver_register(struct work_struct *work) +{ +#ifdef CPTCFG_BRCMFMAC_SDIO + brcmf_sdio_register(); +#endif +#ifdef CPTCFG_BRCMFMAC_USB + brcmf_usb_register(); +#endif +} +static DECLARE_WORK(brcmf_driver_work, brcmf_driver_register); + +static int __init brcmfmac_module_init(void) +{ + brcmf_debugfs_init(); +#ifdef CPTCFG_BRCMFMAC_SDIO + brcmf_sdio_init(); +#endif + if (!schedule_work(&brcmf_driver_work)) + return -EBUSY; + + return 0; +} + +static void __exit brcmfmac_module_exit(void) +{ + cancel_work_sync(&brcmf_driver_work); + +#ifdef CPTCFG_BRCMFMAC_SDIO + brcmf_sdio_exit(); +#endif +#ifdef CPTCFG_BRCMFMAC_USB + brcmf_usb_exit(); +#endif + brcmf_debugfs_exit(); +} + +module_init(brcmfmac_module_init); +module_exit(brcmfmac_module_exit); diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_proto.h b/drivers/net/wireless/brcm80211/brcmfmac/dhd_proto.h new file mode 100644 index 0000000..ef91798 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_proto.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCMF_PROTO_H_ +#define _BRCMF_PROTO_H_ + +/* + * Exported from the brcmf protocol module (brcmf_cdc) + */ + +/* Linkage, sets prot link and updates hdrlen in pub */ +extern int brcmf_proto_attach(struct brcmf_pub *drvr); + +/* Unlink, frees allocated protocol memory (including brcmf_proto) */ +extern void brcmf_proto_detach(struct brcmf_pub *drvr); + +/* Stop protocol: sync w/dongle state. */ +extern void brcmf_proto_stop(struct brcmf_pub *drvr); + +/* Add any protocol-specific data header. + * Caller must reserve prot_hdrlen prepend space. + */ +extern void brcmf_proto_hdrpush(struct brcmf_pub *, int ifidx, u8 offset, + struct sk_buff *txp); + +/* Sets dongle media info (drv_version, mac address). */ +extern int brcmf_c_preinit_dcmds(struct brcmf_if *ifp); + +#endif /* _BRCMF_PROTO_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c new file mode 100644 index 0000000..d248751 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/dhd_sdio.c @@ -0,0 +1,4043 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sdio_host.h" +#include "sdio_chip.h" + +#define DCMD_RESP_TIMEOUT 2000 /* In milli second */ + +#ifdef DEBUG + +#define BRCMF_TRAP_INFO_SIZE 80 + +#define CBUF_LEN (128) + +/* Device console log buffer state */ +#define CONSOLE_BUFFER_MAX 2024 + +struct rte_log_le { + __le32 buf; /* Can't be pointer on (64-bit) hosts */ + __le32 buf_size; + __le32 idx; + char *_buf_compat; /* Redundant pointer for backward compat. */ +}; + +struct rte_console { + /* Virtual UART + * When there is no UART (e.g. Quickturn), + * the host should write a complete + * input line directly into cbuf and then write + * the length into vcons_in. + * This may also be used when there is a real UART + * (at risk of conflicting with + * the real UART). vcons_out is currently unused. + */ + uint vcons_in; + uint vcons_out; + + /* Output (logging) buffer + * Console output is written to a ring buffer log_buf at index log_idx. + * The host may read the output when it sees log_idx advance. + * Output will be lost if the output wraps around faster than the host + * polls. + */ + struct rte_log_le log_le; + + /* Console input line buffer + * Characters are read one at a time into cbuf + * until is received, then + * the buffer is processed as a command line. + * Also used for virtual UART. + */ + uint cbuf_idx; + char cbuf[CBUF_LEN]; +}; + +#endif /* DEBUG */ +#include + +#include "dhd_bus.h" +#include "dhd_dbg.h" +#include "tracepoint.h" + +#define TXQLEN 2048 /* bulk tx queue length */ +#define TXHI (TXQLEN - 256) /* turn on flow control above TXHI */ +#define TXLOW (TXHI - 256) /* turn off flow control below TXLOW */ +#define PRIOMASK 7 + +#define TXRETRIES 2 /* # of retries for tx frames */ + +#define BRCMF_RXBOUND 50 /* Default for max rx frames in + one scheduling */ + +#define BRCMF_TXBOUND 20 /* Default for max tx frames in + one scheduling */ + +#define BRCMF_TXMINMAX 1 /* Max tx frames if rx still pending */ + +#define MEMBLOCK 2048 /* Block size used for downloading + of dongle image */ +#define MAX_DATA_BUF (32 * 1024) /* Must be large enough to hold + biggest possible glom */ + +#define BRCMF_FIRSTREAD (1 << 6) + + +/* SBSDIO_DEVICE_CTL */ + +/* 1: device will assert busy signal when receiving CMD53 */ +#define SBSDIO_DEVCTL_SETBUSY 0x01 +/* 1: assertion of sdio interrupt is synchronous to the sdio clock */ +#define SBSDIO_DEVCTL_SPI_INTR_SYNC 0x02 +/* 1: mask all interrupts to host except the chipActive (rev 8) */ +#define SBSDIO_DEVCTL_CA_INT_ONLY 0x04 +/* 1: isolate internal sdio signals, put external pads in tri-state; requires + * sdio bus power cycle to clear (rev 9) */ +#define SBSDIO_DEVCTL_PADS_ISO 0x08 +/* Force SD->SB reset mapping (rev 11) */ +#define SBSDIO_DEVCTL_SB_RST_CTL 0x30 +/* Determined by CoreControl bit */ +#define SBSDIO_DEVCTL_RST_CORECTL 0x00 +/* Force backplane reset */ +#define SBSDIO_DEVCTL_RST_BPRESET 0x10 +/* Force no backplane reset */ +#define SBSDIO_DEVCTL_RST_NOBPRESET 0x20 + +/* direct(mapped) cis space */ + +/* MAPPED common CIS address */ +#define SBSDIO_CIS_BASE_COMMON 0x1000 +/* maximum bytes in one CIS */ +#define SBSDIO_CIS_SIZE_LIMIT 0x200 +/* cis offset addr is < 17 bits */ +#define SBSDIO_CIS_OFT_ADDR_MASK 0x1FFFF + +/* manfid tuple length, include tuple, link bytes */ +#define SBSDIO_CIS_MANFID_TUPLE_LEN 6 + +/* intstatus */ +#define I_SMB_SW0 (1 << 0) /* To SB Mail S/W interrupt 0 */ +#define I_SMB_SW1 (1 << 1) /* To SB Mail S/W interrupt 1 */ +#define I_SMB_SW2 (1 << 2) /* To SB Mail S/W interrupt 2 */ +#define I_SMB_SW3 (1 << 3) /* To SB Mail S/W interrupt 3 */ +#define I_SMB_SW_MASK 0x0000000f /* To SB Mail S/W interrupts mask */ +#define I_SMB_SW_SHIFT 0 /* To SB Mail S/W interrupts shift */ +#define I_HMB_SW0 (1 << 4) /* To Host Mail S/W interrupt 0 */ +#define I_HMB_SW1 (1 << 5) /* To Host Mail S/W interrupt 1 */ +#define I_HMB_SW2 (1 << 6) /* To Host Mail S/W interrupt 2 */ +#define I_HMB_SW3 (1 << 7) /* To Host Mail S/W interrupt 3 */ +#define I_HMB_SW_MASK 0x000000f0 /* To Host Mail S/W interrupts mask */ +#define I_HMB_SW_SHIFT 4 /* To Host Mail S/W interrupts shift */ +#define I_WR_OOSYNC (1 << 8) /* Write Frame Out Of Sync */ +#define I_RD_OOSYNC (1 << 9) /* Read Frame Out Of Sync */ +#define I_PC (1 << 10) /* descriptor error */ +#define I_PD (1 << 11) /* data error */ +#define I_DE (1 << 12) /* Descriptor protocol Error */ +#define I_RU (1 << 13) /* Receive descriptor Underflow */ +#define I_RO (1 << 14) /* Receive fifo Overflow */ +#define I_XU (1 << 15) /* Transmit fifo Underflow */ +#define I_RI (1 << 16) /* Receive Interrupt */ +#define I_BUSPWR (1 << 17) /* SDIO Bus Power Change (rev 9) */ +#define I_XMTDATA_AVAIL (1 << 23) /* bits in fifo */ +#define I_XI (1 << 24) /* Transmit Interrupt */ +#define I_RF_TERM (1 << 25) /* Read Frame Terminate */ +#define I_WF_TERM (1 << 26) /* Write Frame Terminate */ +#define I_PCMCIA_XU (1 << 27) /* PCMCIA Transmit FIFO Underflow */ +#define I_SBINT (1 << 28) /* sbintstatus Interrupt */ +#define I_CHIPACTIVE (1 << 29) /* chip from doze to active state */ +#define I_SRESET (1 << 30) /* CCCR RES interrupt */ +#define I_IOE2 (1U << 31) /* CCCR IOE2 Bit Changed */ +#define I_ERRORS (I_PC | I_PD | I_DE | I_RU | I_RO | I_XU) +#define I_DMA (I_RI | I_XI | I_ERRORS) + +/* corecontrol */ +#define CC_CISRDY (1 << 0) /* CIS Ready */ +#define CC_BPRESEN (1 << 1) /* CCCR RES signal */ +#define CC_F2RDY (1 << 2) /* set CCCR IOR2 bit */ +#define CC_CLRPADSISO (1 << 3) /* clear SDIO pads isolation */ +#define CC_XMTDATAAVAIL_MODE (1 << 4) +#define CC_XMTDATAAVAIL_CTRL (1 << 5) + +/* SDA_FRAMECTRL */ +#define SFC_RF_TERM (1 << 0) /* Read Frame Terminate */ +#define SFC_WF_TERM (1 << 1) /* Write Frame Terminate */ +#define SFC_CRC4WOOS (1 << 2) /* CRC error for write out of sync */ +#define SFC_ABORTALL (1 << 3) /* Abort all in-progress frames */ + +/* HW frame tag */ +#define SDPCM_FRAMETAG_LEN 4 /* 2 bytes len, 2 bytes check val */ + +/* Total length of frame header for dongle protocol */ +#define SDPCM_HDRLEN (SDPCM_FRAMETAG_LEN + SDPCM_SWHEADER_LEN) +#define SDPCM_RESERVE (SDPCM_HDRLEN + BRCMF_SDALIGN) + +/* + * Software allocation of To SB Mailbox resources + */ + +/* tosbmailbox bits corresponding to intstatus bits */ +#define SMB_NAK (1 << 0) /* Frame NAK */ +#define SMB_INT_ACK (1 << 1) /* Host Interrupt ACK */ +#define SMB_USE_OOB (1 << 2) /* Use OOB Wakeup */ +#define SMB_DEV_INT (1 << 3) /* Miscellaneous Interrupt */ + +/* tosbmailboxdata */ +#define SMB_DATA_VERSION_SHIFT 16 /* host protocol version */ + +/* + * Software allocation of To Host Mailbox resources + */ + +/* intstatus bits */ +#define I_HMB_FC_STATE I_HMB_SW0 /* Flow Control State */ +#define I_HMB_FC_CHANGE I_HMB_SW1 /* Flow Control State Changed */ +#define I_HMB_FRAME_IND I_HMB_SW2 /* Frame Indication */ +#define I_HMB_HOST_INT I_HMB_SW3 /* Miscellaneous Interrupt */ + +/* tohostmailboxdata */ +#define HMB_DATA_NAKHANDLED 1 /* retransmit NAK'd frame */ +#define HMB_DATA_DEVREADY 2 /* talk to host after enable */ +#define HMB_DATA_FC 4 /* per prio flowcontrol update flag */ +#define HMB_DATA_FWREADY 8 /* fw ready for protocol activity */ + +#define HMB_DATA_FCDATA_MASK 0xff000000 +#define HMB_DATA_FCDATA_SHIFT 24 + +#define HMB_DATA_VERSION_MASK 0x00ff0000 +#define HMB_DATA_VERSION_SHIFT 16 + +/* + * Software-defined protocol header + */ + +/* Current protocol version */ +#define SDPCM_PROT_VERSION 4 + +/* SW frame header */ +#define SDPCM_PACKET_SEQUENCE(p) (((u8 *)p)[0] & 0xff) + +#define SDPCM_CHANNEL_MASK 0x00000f00 +#define SDPCM_CHANNEL_SHIFT 8 +#define SDPCM_PACKET_CHANNEL(p) (((u8 *)p)[1] & 0x0f) + +#define SDPCM_NEXTLEN_OFFSET 2 + +/* Data Offset from SOF (HW Tag, SW Tag, Pad) */ +#define SDPCM_DOFFSET_OFFSET 3 /* Data Offset */ +#define SDPCM_DOFFSET_VALUE(p) (((u8 *)p)[SDPCM_DOFFSET_OFFSET] & 0xff) +#define SDPCM_DOFFSET_MASK 0xff000000 +#define SDPCM_DOFFSET_SHIFT 24 +#define SDPCM_FCMASK_OFFSET 4 /* Flow control */ +#define SDPCM_FCMASK_VALUE(p) (((u8 *)p)[SDPCM_FCMASK_OFFSET] & 0xff) +#define SDPCM_WINDOW_OFFSET 5 /* Credit based fc */ +#define SDPCM_WINDOW_VALUE(p) (((u8 *)p)[SDPCM_WINDOW_OFFSET] & 0xff) + +#define SDPCM_SWHEADER_LEN 8 /* SW header is 64 bits */ + +/* logical channel numbers */ +#define SDPCM_CONTROL_CHANNEL 0 /* Control channel Id */ +#define SDPCM_EVENT_CHANNEL 1 /* Asyc Event Indication Channel Id */ +#define SDPCM_DATA_CHANNEL 2 /* Data Xmit/Recv Channel Id */ +#define SDPCM_GLOM_CHANNEL 3 /* For coalesced packets */ +#define SDPCM_TEST_CHANNEL 15 /* Reserved for test/debug packets */ + +#define SDPCM_SEQUENCE_WRAP 256 /* wrap-around val for 8bit frame seq */ + +#define SDPCM_GLOMDESC(p) (((u8 *)p)[1] & 0x80) + +/* + * Shared structure between dongle and the host. + * The structure contains pointers to trap or assert information. + */ +#define SDPCM_SHARED_VERSION 0x0003 +#define SDPCM_SHARED_VERSION_MASK 0x00FF +#define SDPCM_SHARED_ASSERT_BUILT 0x0100 +#define SDPCM_SHARED_ASSERT 0x0200 +#define SDPCM_SHARED_TRAP 0x0400 + +/* Space for header read, limit for data packets */ +#define MAX_HDR_READ (1 << 6) +#define MAX_RX_DATASZ 2048 + +/* Maximum milliseconds to wait for F2 to come up */ +#define BRCMF_WAIT_F2RDY 3000 + +/* Bump up limit on waiting for HT to account for first startup; + * if the image is doing a CRC calculation before programming the PMU + * for HT availability, it could take a couple hundred ms more, so + * max out at a 1 second (1000000us). + */ +#undef PMU_MAX_TRANSITION_DLY +#define PMU_MAX_TRANSITION_DLY 1000000 + +/* Value for ChipClockCSR during initial setup */ +#define BRCMF_INIT_CLKCTL1 (SBSDIO_FORCE_HW_CLKREQ_OFF | \ + SBSDIO_ALP_AVAIL_REQ) + +/* Flags for SDH calls */ +#define F2SYNC (SDIO_REQ_4BYTE | SDIO_REQ_FIXED) + +#define BRCMF_SDIO_FW_NAME "brcm/brcmfmac-sdio.bin" +#define BRCMF_SDIO_NV_NAME "brcm/brcmfmac-sdio.txt" +MODULE_FIRMWARE(BRCMF_SDIO_FW_NAME); +MODULE_FIRMWARE(BRCMF_SDIO_NV_NAME); + +#define BRCMF_IDLE_IMMEDIATE (-1) /* Enter idle immediately */ +#define BRCMF_IDLE_ACTIVE 0 /* Do not request any SD clock change + * when idle + */ +#define BRCMF_IDLE_INTERVAL 1 + +#define KSO_WAIT_US 50 +#define MAX_KSO_ATTEMPTS (PMU_MAX_TRANSITION_DLY/KSO_WAIT_US) + +/* + * Conversion of 802.1D priority to precedence level + */ +static uint prio2prec(u32 prio) +{ + return (prio == PRIO_8021D_NONE || prio == PRIO_8021D_BE) ? + (prio^2) : prio; +} + +#ifdef DEBUG +/* Device console log buffer state */ +struct brcmf_console { + uint count; /* Poll interval msec counter */ + uint log_addr; /* Log struct address (fixed) */ + struct rte_log_le log_le; /* Log struct (host copy) */ + uint bufsize; /* Size of log buffer */ + u8 *buf; /* Log buffer (host copy) */ + uint last; /* Last buffer read index */ +}; + +struct brcmf_trap_info { + __le32 type; + __le32 epc; + __le32 cpsr; + __le32 spsr; + __le32 r0; /* a1 */ + __le32 r1; /* a2 */ + __le32 r2; /* a3 */ + __le32 r3; /* a4 */ + __le32 r4; /* v1 */ + __le32 r5; /* v2 */ + __le32 r6; /* v3 */ + __le32 r7; /* v4 */ + __le32 r8; /* v5 */ + __le32 r9; /* sb/v6 */ + __le32 r10; /* sl/v7 */ + __le32 r11; /* fp/v8 */ + __le32 r12; /* ip */ + __le32 r13; /* sp */ + __le32 r14; /* lr */ + __le32 pc; /* r15 */ +}; +#endif /* DEBUG */ + +struct sdpcm_shared { + u32 flags; + u32 trap_addr; + u32 assert_exp_addr; + u32 assert_file_addr; + u32 assert_line; + u32 console_addr; /* Address of struct rte_console */ + u32 msgtrace_addr; + u8 tag[32]; + u32 brpt_addr; +}; + +struct sdpcm_shared_le { + __le32 flags; + __le32 trap_addr; + __le32 assert_exp_addr; + __le32 assert_file_addr; + __le32 assert_line; + __le32 console_addr; /* Address of struct rte_console */ + __le32 msgtrace_addr; + u8 tag[32]; + __le32 brpt_addr; +}; + +/* SDIO read frame info */ +struct brcmf_sdio_read { + u8 seq_num; + u8 channel; + u16 len; + u16 len_left; + u16 len_nxtfrm; + u8 dat_offset; +}; + +/* misc chip info needed by some of the routines */ +/* Private data for SDIO bus interaction */ +struct brcmf_sdio { + struct brcmf_sdio_dev *sdiodev; /* sdio device handler */ + struct chip_info *ci; /* Chip info struct */ + char *vars; /* Variables (from CIS and/or other) */ + uint varsz; /* Size of variables buffer */ + + u32 ramsize; /* Size of RAM in SOCRAM (bytes) */ + + u32 hostintmask; /* Copy of Host Interrupt Mask */ + atomic_t intstatus; /* Intstatus bits (events) pending */ + atomic_t fcstate; /* State of dongle flow-control */ + + uint blocksize; /* Block size of SDIO transfers */ + uint roundup; /* Max roundup limit */ + + struct pktq txq; /* Queue length used for flow-control */ + u8 flowcontrol; /* per prio flow control bitmask */ + u8 tx_seq; /* Transmit sequence number (next) */ + u8 tx_max; /* Maximum transmit sequence allowed */ + + u8 hdrbuf[MAX_HDR_READ + BRCMF_SDALIGN]; + u8 *rxhdr; /* Header of current rx frame (in hdrbuf) */ + u8 rx_seq; /* Receive sequence number (expected) */ + struct brcmf_sdio_read cur_read; + /* info of current read frame */ + bool rxskip; /* Skip receive (awaiting NAK ACK) */ + bool rxpending; /* Data frame pending in dongle */ + + uint rxbound; /* Rx frames to read before resched */ + uint txbound; /* Tx frames to send before resched */ + uint txminmax; + + struct sk_buff *glomd; /* Packet containing glomming descriptor */ + struct sk_buff_head glom; /* Packet list for glommed superframe */ + uint glomerr; /* Glom packet read errors */ + + u8 *rxbuf; /* Buffer for receiving control packets */ + uint rxblen; /* Allocated length of rxbuf */ + u8 *rxctl; /* Aligned pointer into rxbuf */ + u8 *rxctl_orig; /* pointer for freeing rxctl */ + u8 *databuf; /* Buffer for receiving big glom packet */ + u8 *dataptr; /* Aligned pointer into databuf */ + uint rxlen; /* Length of valid data in buffer */ + spinlock_t rxctl_lock; /* protection lock for ctrl frame resources */ + + u8 sdpcm_ver; /* Bus protocol reported by dongle */ + + bool intr; /* Use interrupts */ + bool poll; /* Use polling */ + atomic_t ipend; /* Device interrupt is pending */ + uint spurious; /* Count of spurious interrupts */ + uint pollrate; /* Ticks between device polls */ + uint polltick; /* Tick counter */ + +#ifdef DEBUG + uint console_interval; + struct brcmf_console console; /* Console output polling support */ + uint console_addr; /* Console address from shared struct */ +#endif /* DEBUG */ + + uint clkstate; /* State of sd and backplane clock(s) */ + bool activity; /* Activity flag for clock down */ + s32 idletime; /* Control for activity timeout */ + s32 idlecount; /* Activity timeout counter */ + s32 idleclock; /* How to set bus driver when idle */ + s32 sd_rxchain; + bool use_rxchain; /* If brcmf should use PKT chains */ + bool rxflow_mode; /* Rx flow control mode */ + bool rxflow; /* Is rx flow control on */ + bool alp_only; /* Don't use HT clock (ALP only) */ + + u8 *ctrl_frame_buf; + u32 ctrl_frame_len; + bool ctrl_frame_stat; + + spinlock_t txqlock; + wait_queue_head_t ctrl_wait; + wait_queue_head_t dcmd_resp_wait; + + struct timer_list timer; + struct completion watchdog_wait; + struct task_struct *watchdog_tsk; + bool wd_timer_valid; + uint save_ms; + + struct workqueue_struct *brcmf_wq; + struct work_struct datawork; + struct list_head dpc_tsklst; + spinlock_t dpc_tl_lock; + + const struct firmware *firmware; + u32 fw_ptr; + + bool txoff; /* Transmit flow-controlled */ + struct brcmf_sdio_count sdcnt; + bool sr_enabled; /* SaveRestore enabled */ + bool sleeping; /* SDIO bus sleeping */ +}; + +/* clkstate */ +#define CLK_NONE 0 +#define CLK_SDONLY 1 +#define CLK_PENDING 2 +#define CLK_AVAIL 3 + +#ifdef DEBUG +static int qcount[NUMPRIO]; +static int tx_packets[NUMPRIO]; +#endif /* DEBUG */ + +#define DEFAULT_SDIO_DRIVE_STRENGTH 6 /* in milliamps */ + +#define RETRYCHAN(chan) ((chan) == SDPCM_EVENT_CHANNEL) + +/* Retry count for register access failures */ +static const uint retry_limit = 2; + +/* Limit on rounding up frames */ +static const uint max_roundup = 512; + +#define ALIGNMENT 4 + +enum brcmf_sdio_frmtype { + BRCMF_SDIO_FT_NORMAL, + BRCMF_SDIO_FT_SUPER, + BRCMF_SDIO_FT_SUB, +}; + +static void pkt_align(struct sk_buff *p, int len, int align) +{ + uint datalign; + datalign = (unsigned long)(p->data); + datalign = roundup(datalign, (align)) - datalign; + if (datalign) + skb_pull(p, datalign); + __skb_trim(p, len); +} + +/* To check if there's window offered */ +static bool data_ok(struct brcmf_sdio *bus) +{ + return (u8)(bus->tx_max - bus->tx_seq) != 0 && + ((u8)(bus->tx_max - bus->tx_seq) & 0x80) == 0; +} + +/* + * Reads a register in the SDIO hardware block. This block occupies a series of + * adresses on the 32 bit backplane bus. + */ +static int +r_sdreg32(struct brcmf_sdio *bus, u32 *regvar, u32 offset) +{ + u8 idx = brcmf_sdio_chip_getinfidx(bus->ci, BCMA_CORE_SDIO_DEV); + int ret; + + *regvar = brcmf_sdio_regrl(bus->sdiodev, + bus->ci->c_inf[idx].base + offset, &ret); + + return ret; +} + +static int +w_sdreg32(struct brcmf_sdio *bus, u32 regval, u32 reg_offset) +{ + u8 idx = brcmf_sdio_chip_getinfidx(bus->ci, BCMA_CORE_SDIO_DEV); + int ret; + + brcmf_sdio_regwl(bus->sdiodev, + bus->ci->c_inf[idx].base + reg_offset, + regval, &ret); + + return ret; +} + +static int +brcmf_sdbrcm_kso_control(struct brcmf_sdio *bus, bool on) +{ + u8 wr_val = 0, rd_val, cmp_val, bmask; + int err = 0; + int try_cnt = 0; + + brcmf_dbg(TRACE, "Enter\n"); + + wr_val = (on << SBSDIO_FUNC1_SLEEPCSR_KSO_SHIFT); + /* 1st KSO write goes to AOS wake up core if device is asleep */ + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_FUNC1_SLEEPCSR, + wr_val, &err); + if (err) { + brcmf_err("SDIO_AOS KSO write error: %d\n", err); + return err; + } + + if (on) { + /* device WAKEUP through KSO: + * write bit 0 & read back until + * both bits 0 (kso bit) & 1 (dev on status) are set + */ + cmp_val = SBSDIO_FUNC1_SLEEPCSR_KSO_MASK | + SBSDIO_FUNC1_SLEEPCSR_DEVON_MASK; + bmask = cmp_val; + usleep_range(2000, 3000); + } else { + /* Put device to sleep, turn off KSO */ + cmp_val = 0; + /* only check for bit0, bit1(dev on status) may not + * get cleared right away + */ + bmask = SBSDIO_FUNC1_SLEEPCSR_KSO_MASK; + } + + do { + /* reliable KSO bit set/clr: + * the sdiod sleep write access is synced to PMU 32khz clk + * just one write attempt may fail, + * read it back until it matches written value + */ + rd_val = brcmf_sdio_regrb(bus->sdiodev, SBSDIO_FUNC1_SLEEPCSR, + &err); + if (((rd_val & bmask) == cmp_val) && !err) + break; + brcmf_dbg(SDIO, "KSO wr/rd retry:%d (max: %d) ERR:%x\n", + try_cnt, MAX_KSO_ATTEMPTS, err); + udelay(KSO_WAIT_US); + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_FUNC1_SLEEPCSR, + wr_val, &err); + } while (try_cnt++ < MAX_KSO_ATTEMPTS); + + return err; +} + +#define PKT_AVAILABLE() (intstatus & I_HMB_FRAME_IND) + +#define HOSTINTMASK (I_HMB_SW_MASK | I_CHIPACTIVE) + +/* Turn backplane clock on or off */ +static int brcmf_sdbrcm_htclk(struct brcmf_sdio *bus, bool on, bool pendok) +{ + int err; + u8 clkctl, clkreq, devctl; + unsigned long timeout; + + brcmf_dbg(SDIO, "Enter\n"); + + clkctl = 0; + + if (bus->sr_enabled) { + bus->clkstate = (on ? CLK_AVAIL : CLK_SDONLY); + return 0; + } + + if (on) { + /* Request HT Avail */ + clkreq = + bus->alp_only ? SBSDIO_ALP_AVAIL_REQ : SBSDIO_HT_AVAIL_REQ; + + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, + clkreq, &err); + if (err) { + brcmf_err("HT Avail request error: %d\n", err); + return -EBADE; + } + + /* Check current status */ + clkctl = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_FUNC1_CHIPCLKCSR, &err); + if (err) { + brcmf_err("HT Avail read error: %d\n", err); + return -EBADE; + } + + /* Go to pending and await interrupt if appropriate */ + if (!SBSDIO_CLKAV(clkctl, bus->alp_only) && pendok) { + /* Allow only clock-available interrupt */ + devctl = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_DEVICE_CTL, &err); + if (err) { + brcmf_err("Devctl error setting CA: %d\n", + err); + return -EBADE; + } + + devctl |= SBSDIO_DEVCTL_CA_INT_ONLY; + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_DEVICE_CTL, + devctl, &err); + brcmf_dbg(SDIO, "CLKCTL: set PENDING\n"); + bus->clkstate = CLK_PENDING; + + return 0; + } else if (bus->clkstate == CLK_PENDING) { + /* Cancel CA-only interrupt filter */ + devctl = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_DEVICE_CTL, &err); + devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY; + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_DEVICE_CTL, + devctl, &err); + } + + /* Otherwise, wait here (polling) for HT Avail */ + timeout = jiffies + + msecs_to_jiffies(PMU_MAX_TRANSITION_DLY/1000); + while (!SBSDIO_CLKAV(clkctl, bus->alp_only)) { + clkctl = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_FUNC1_CHIPCLKCSR, + &err); + if (time_after(jiffies, timeout)) + break; + else + usleep_range(5000, 10000); + } + if (err) { + brcmf_err("HT Avail request error: %d\n", err); + return -EBADE; + } + if (!SBSDIO_CLKAV(clkctl, bus->alp_only)) { + brcmf_err("HT Avail timeout (%d): clkctl 0x%02x\n", + PMU_MAX_TRANSITION_DLY, clkctl); + return -EBADE; + } + + /* Mark clock available */ + bus->clkstate = CLK_AVAIL; + brcmf_dbg(SDIO, "CLKCTL: turned ON\n"); + +#if defined(DEBUG) + if (!bus->alp_only) { + if (SBSDIO_ALPONLY(clkctl)) + brcmf_err("HT Clock should be on\n"); + } +#endif /* defined (DEBUG) */ + + bus->activity = true; + } else { + clkreq = 0; + + if (bus->clkstate == CLK_PENDING) { + /* Cancel CA-only interrupt filter */ + devctl = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_DEVICE_CTL, &err); + devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY; + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_DEVICE_CTL, + devctl, &err); + } + + bus->clkstate = CLK_SDONLY; + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, + clkreq, &err); + brcmf_dbg(SDIO, "CLKCTL: turned OFF\n"); + if (err) { + brcmf_err("Failed access turning clock off: %d\n", + err); + return -EBADE; + } + } + return 0; +} + +/* Change idle/active SD state */ +static int brcmf_sdbrcm_sdclk(struct brcmf_sdio *bus, bool on) +{ + brcmf_dbg(SDIO, "Enter\n"); + + if (on) + bus->clkstate = CLK_SDONLY; + else + bus->clkstate = CLK_NONE; + + return 0; +} + +/* Transition SD and backplane clock readiness */ +static int brcmf_sdbrcm_clkctl(struct brcmf_sdio *bus, uint target, bool pendok) +{ +#ifdef DEBUG + uint oldstate = bus->clkstate; +#endif /* DEBUG */ + + brcmf_dbg(SDIO, "Enter\n"); + + /* Early exit if we're already there */ + if (bus->clkstate == target) { + if (target == CLK_AVAIL) { + brcmf_sdbrcm_wd_timer(bus, BRCMF_WD_POLL_MS); + bus->activity = true; + } + return 0; + } + + switch (target) { + case CLK_AVAIL: + /* Make sure SD clock is available */ + if (bus->clkstate == CLK_NONE) + brcmf_sdbrcm_sdclk(bus, true); + /* Now request HT Avail on the backplane */ + brcmf_sdbrcm_htclk(bus, true, pendok); + brcmf_sdbrcm_wd_timer(bus, BRCMF_WD_POLL_MS); + bus->activity = true; + break; + + case CLK_SDONLY: + /* Remove HT request, or bring up SD clock */ + if (bus->clkstate == CLK_NONE) + brcmf_sdbrcm_sdclk(bus, true); + else if (bus->clkstate == CLK_AVAIL) + brcmf_sdbrcm_htclk(bus, false, false); + else + brcmf_err("request for %d -> %d\n", + bus->clkstate, target); + brcmf_sdbrcm_wd_timer(bus, BRCMF_WD_POLL_MS); + break; + + case CLK_NONE: + /* Make sure to remove HT request */ + if (bus->clkstate == CLK_AVAIL) + brcmf_sdbrcm_htclk(bus, false, false); + /* Now remove the SD clock */ + brcmf_sdbrcm_sdclk(bus, false); + brcmf_sdbrcm_wd_timer(bus, 0); + break; + } +#ifdef DEBUG + brcmf_dbg(SDIO, "%d -> %d\n", oldstate, bus->clkstate); +#endif /* DEBUG */ + + return 0; +} + +static int +brcmf_sdbrcm_bus_sleep(struct brcmf_sdio *bus, bool sleep, bool pendok) +{ + int err = 0; + brcmf_dbg(TRACE, "Enter\n"); + brcmf_dbg(SDIO, "request %s currently %s\n", + (sleep ? "SLEEP" : "WAKE"), + (bus->sleeping ? "SLEEP" : "WAKE")); + + /* If SR is enabled control bus state with KSO */ + if (bus->sr_enabled) { + /* Done if we're already in the requested state */ + if (sleep == bus->sleeping) + goto end; + + /* Going to sleep */ + if (sleep) { + /* Don't sleep if something is pending */ + if (atomic_read(&bus->intstatus) || + atomic_read(&bus->ipend) > 0 || + (!atomic_read(&bus->fcstate) && + brcmu_pktq_mlen(&bus->txq, ~bus->flowcontrol) && + data_ok(bus))) + return -EBUSY; + err = brcmf_sdbrcm_kso_control(bus, false); + /* disable watchdog */ + if (!err) + brcmf_sdbrcm_wd_timer(bus, 0); + } else { + bus->idlecount = 0; + err = brcmf_sdbrcm_kso_control(bus, true); + } + if (!err) { + /* Change state */ + bus->sleeping = sleep; + brcmf_dbg(SDIO, "new state %s\n", + (sleep ? "SLEEP" : "WAKE")); + } else { + brcmf_err("error while changing bus sleep state %d\n", + err); + return err; + } + } + +end: + /* control clocks */ + if (sleep) { + if (!bus->sr_enabled) + brcmf_sdbrcm_clkctl(bus, CLK_NONE, pendok); + } else { + brcmf_sdbrcm_clkctl(bus, CLK_AVAIL, pendok); + } + + return err; + +} + +static u32 brcmf_sdbrcm_hostmail(struct brcmf_sdio *bus) +{ + u32 intstatus = 0; + u32 hmb_data; + u8 fcbits; + int ret; + + brcmf_dbg(SDIO, "Enter\n"); + + /* Read mailbox data and ack that we did so */ + ret = r_sdreg32(bus, &hmb_data, + offsetof(struct sdpcmd_regs, tohostmailboxdata)); + + if (ret == 0) + w_sdreg32(bus, SMB_INT_ACK, + offsetof(struct sdpcmd_regs, tosbmailbox)); + bus->sdcnt.f1regdata += 2; + + /* Dongle recomposed rx frames, accept them again */ + if (hmb_data & HMB_DATA_NAKHANDLED) { + brcmf_dbg(SDIO, "Dongle reports NAK handled, expect rtx of %d\n", + bus->rx_seq); + if (!bus->rxskip) + brcmf_err("unexpected NAKHANDLED!\n"); + + bus->rxskip = false; + intstatus |= I_HMB_FRAME_IND; + } + + /* + * DEVREADY does not occur with gSPI. + */ + if (hmb_data & (HMB_DATA_DEVREADY | HMB_DATA_FWREADY)) { + bus->sdpcm_ver = + (hmb_data & HMB_DATA_VERSION_MASK) >> + HMB_DATA_VERSION_SHIFT; + if (bus->sdpcm_ver != SDPCM_PROT_VERSION) + brcmf_err("Version mismatch, dongle reports %d, " + "expecting %d\n", + bus->sdpcm_ver, SDPCM_PROT_VERSION); + else + brcmf_dbg(SDIO, "Dongle ready, protocol version %d\n", + bus->sdpcm_ver); + } + + /* + * Flow Control has been moved into the RX headers and this out of band + * method isn't used any more. + * remaining backward compatible with older dongles. + */ + if (hmb_data & HMB_DATA_FC) { + fcbits = (hmb_data & HMB_DATA_FCDATA_MASK) >> + HMB_DATA_FCDATA_SHIFT; + + if (fcbits & ~bus->flowcontrol) + bus->sdcnt.fc_xoff++; + + if (bus->flowcontrol & ~fcbits) + bus->sdcnt.fc_xon++; + + bus->sdcnt.fc_rcvd++; + bus->flowcontrol = fcbits; + } + + /* Shouldn't be any others */ + if (hmb_data & ~(HMB_DATA_DEVREADY | + HMB_DATA_NAKHANDLED | + HMB_DATA_FC | + HMB_DATA_FWREADY | + HMB_DATA_FCDATA_MASK | HMB_DATA_VERSION_MASK)) + brcmf_err("Unknown mailbox data content: 0x%02x\n", + hmb_data); + + return intstatus; +} + +static void brcmf_sdbrcm_rxfail(struct brcmf_sdio *bus, bool abort, bool rtx) +{ + uint retries = 0; + u16 lastrbc; + u8 hi, lo; + int err; + + brcmf_err("%sterminate frame%s\n", + abort ? "abort command, " : "", + rtx ? ", send NAK" : ""); + + if (abort) + brcmf_sdcard_abort(bus->sdiodev, SDIO_FUNC_2); + + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_FUNC1_FRAMECTRL, + SFC_RF_TERM, &err); + bus->sdcnt.f1regdata++; + + /* Wait until the packet has been flushed (device/FIFO stable) */ + for (lastrbc = retries = 0xffff; retries > 0; retries--) { + hi = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_FUNC1_RFRAMEBCHI, &err); + lo = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_FUNC1_RFRAMEBCLO, &err); + bus->sdcnt.f1regdata += 2; + + if ((hi == 0) && (lo == 0)) + break; + + if ((hi > (lastrbc >> 8)) && (lo > (lastrbc & 0x00ff))) { + brcmf_err("count growing: last 0x%04x now 0x%04x\n", + lastrbc, (hi << 8) + lo); + } + lastrbc = (hi << 8) + lo; + } + + if (!retries) + brcmf_err("count never zeroed: last 0x%04x\n", lastrbc); + else + brcmf_dbg(SDIO, "flush took %d iterations\n", 0xffff - retries); + + if (rtx) { + bus->sdcnt.rxrtx++; + err = w_sdreg32(bus, SMB_NAK, + offsetof(struct sdpcmd_regs, tosbmailbox)); + + bus->sdcnt.f1regdata++; + if (err == 0) + bus->rxskip = true; + } + + /* Clear partial in any case */ + bus->cur_read.len = 0; + + /* If we can't reach the device, signal failure */ + if (err) + bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN; +} + +/* copy a buffer into a pkt buffer chain */ +static uint brcmf_sdbrcm_glom_from_buf(struct brcmf_sdio *bus, uint len) +{ + uint n, ret = 0; + struct sk_buff *p; + u8 *buf; + + buf = bus->dataptr; + + /* copy the data */ + skb_queue_walk(&bus->glom, p) { + n = min_t(uint, p->len, len); + memcpy(p->data, buf, n); + buf += n; + len -= n; + ret += n; + if (!len) + break; + } + + return ret; +} + +/* return total length of buffer chain */ +static uint brcmf_sdbrcm_glom_len(struct brcmf_sdio *bus) +{ + struct sk_buff *p; + uint total; + + total = 0; + skb_queue_walk(&bus->glom, p) + total += p->len; + return total; +} + +static void brcmf_sdbrcm_free_glom(struct brcmf_sdio *bus) +{ + struct sk_buff *cur, *next; + + skb_queue_walk_safe(&bus->glom, cur, next) { + skb_unlink(cur, &bus->glom); + brcmu_pkt_buf_free_skb(cur); + } +} + +static int brcmf_sdio_hdparser(struct brcmf_sdio *bus, u8 *header, + struct brcmf_sdio_read *rd, + enum brcmf_sdio_frmtype type) +{ + u16 len, checksum; + u8 rx_seq, fc, tx_seq_max; + + /* + * 4 bytes hardware header (frame tag) + * Byte 0~1: Frame length + * Byte 2~3: Checksum, bit-wise inverse of frame length + */ + len = get_unaligned_le16(header); + checksum = get_unaligned_le16(header + sizeof(u16)); + /* All zero means no more to read */ + if (!(len | checksum)) { + bus->rxpending = false; + return -ENODATA; + } + if ((u16)(~(len ^ checksum))) { + brcmf_err("HW header checksum error\n"); + bus->sdcnt.rx_badhdr++; + brcmf_sdbrcm_rxfail(bus, false, false); + return -EIO; + } + if (len < SDPCM_HDRLEN) { + brcmf_err("HW header length error\n"); + return -EPROTO; + } + if (type == BRCMF_SDIO_FT_SUPER && + (roundup(len, bus->blocksize) != rd->len)) { + brcmf_err("HW superframe header length error\n"); + return -EPROTO; + } + if (type == BRCMF_SDIO_FT_SUB && len > rd->len) { + brcmf_err("HW subframe header length error\n"); + return -EPROTO; + } + rd->len = len; + + /* + * 8 bytes hardware header + * Byte 0: Rx sequence number + * Byte 1: 4 MSB Channel number, 4 LSB arbitrary flag + * Byte 2: Length of next data frame + * Byte 3: Data offset + * Byte 4: Flow control bits + * Byte 5: Maximum Sequence number allow for Tx + * Byte 6~7: Reserved + */ + if (type == BRCMF_SDIO_FT_SUPER && + SDPCM_GLOMDESC(&header[SDPCM_FRAMETAG_LEN])) { + brcmf_err("Glom descriptor found in superframe head\n"); + rd->len = 0; + return -EINVAL; + } + rx_seq = SDPCM_PACKET_SEQUENCE(&header[SDPCM_FRAMETAG_LEN]); + rd->channel = SDPCM_PACKET_CHANNEL(&header[SDPCM_FRAMETAG_LEN]); + if (len > MAX_RX_DATASZ && rd->channel != SDPCM_CONTROL_CHANNEL && + type != BRCMF_SDIO_FT_SUPER) { + brcmf_err("HW header length too long\n"); + bus->sdcnt.rx_toolong++; + brcmf_sdbrcm_rxfail(bus, false, false); + rd->len = 0; + return -EPROTO; + } + if (type == BRCMF_SDIO_FT_SUPER && rd->channel != SDPCM_GLOM_CHANNEL) { + brcmf_err("Wrong channel for superframe\n"); + rd->len = 0; + return -EINVAL; + } + if (type == BRCMF_SDIO_FT_SUB && rd->channel != SDPCM_DATA_CHANNEL && + rd->channel != SDPCM_EVENT_CHANNEL) { + brcmf_err("Wrong channel for subframe\n"); + rd->len = 0; + return -EINVAL; + } + rd->dat_offset = SDPCM_DOFFSET_VALUE(&header[SDPCM_FRAMETAG_LEN]); + if (rd->dat_offset < SDPCM_HDRLEN || rd->dat_offset > rd->len) { + brcmf_err("seq %d: bad data offset\n", rx_seq); + bus->sdcnt.rx_badhdr++; + brcmf_sdbrcm_rxfail(bus, false, false); + rd->len = 0; + return -ENXIO; + } + if (rd->seq_num != rx_seq) { + brcmf_err("seq %d: sequence number error, expect %d\n", + rx_seq, rd->seq_num); + bus->sdcnt.rx_badseq++; + rd->seq_num = rx_seq; + } + /* no need to check the reset for subframe */ + if (type == BRCMF_SDIO_FT_SUB) + return 0; + rd->len_nxtfrm = header[SDPCM_FRAMETAG_LEN + SDPCM_NEXTLEN_OFFSET]; + if (rd->len_nxtfrm << 4 > MAX_RX_DATASZ) { + /* only warm for NON glom packet */ + if (rd->channel != SDPCM_GLOM_CHANNEL) + brcmf_err("seq %d: next length error\n", rx_seq); + rd->len_nxtfrm = 0; + } + fc = SDPCM_FCMASK_VALUE(&header[SDPCM_FRAMETAG_LEN]); + if (bus->flowcontrol != fc) { + if (~bus->flowcontrol & fc) + bus->sdcnt.fc_xoff++; + if (bus->flowcontrol & ~fc) + bus->sdcnt.fc_xon++; + bus->sdcnt.fc_rcvd++; + bus->flowcontrol = fc; + } + tx_seq_max = SDPCM_WINDOW_VALUE(&header[SDPCM_FRAMETAG_LEN]); + if ((u8)(tx_seq_max - bus->tx_seq) > 0x40) { + brcmf_err("seq %d: max tx seq number error\n", rx_seq); + tx_seq_max = bus->tx_seq + 2; + } + bus->tx_max = tx_seq_max; + + return 0; +} + +static u8 brcmf_sdbrcm_rxglom(struct brcmf_sdio *bus, u8 rxseq) +{ + u16 dlen, totlen; + u8 *dptr, num = 0; + + u16 sublen; + struct sk_buff *pfirst, *pnext; + + int errcode; + u8 doff, sfdoff; + + bool usechain = bus->use_rxchain; + + struct brcmf_sdio_read rd_new; + + /* If packets, issue read(s) and send up packet chain */ + /* Return sequence numbers consumed? */ + + brcmf_dbg(SDIO, "start: glomd %p glom %p\n", + bus->glomd, skb_peek(&bus->glom)); + + /* If there's a descriptor, generate the packet chain */ + if (bus->glomd) { + pfirst = pnext = NULL; + dlen = (u16) (bus->glomd->len); + dptr = bus->glomd->data; + if (!dlen || (dlen & 1)) { + brcmf_err("bad glomd len(%d), ignore descriptor\n", + dlen); + dlen = 0; + } + + for (totlen = num = 0; dlen; num++) { + /* Get (and move past) next length */ + sublen = get_unaligned_le16(dptr); + dlen -= sizeof(u16); + dptr += sizeof(u16); + if ((sublen < SDPCM_HDRLEN) || + ((num == 0) && (sublen < (2 * SDPCM_HDRLEN)))) { + brcmf_err("descriptor len %d bad: %d\n", + num, sublen); + pnext = NULL; + break; + } + if (sublen % BRCMF_SDALIGN) { + brcmf_err("sublen %d not multiple of %d\n", + sublen, BRCMF_SDALIGN); + usechain = false; + } + totlen += sublen; + + /* For last frame, adjust read len so total + is a block multiple */ + if (!dlen) { + sublen += + (roundup(totlen, bus->blocksize) - totlen); + totlen = roundup(totlen, bus->blocksize); + } + + /* Allocate/chain packet for next subframe */ + pnext = brcmu_pkt_buf_get_skb(sublen + BRCMF_SDALIGN); + if (pnext == NULL) { + brcmf_err("bcm_pkt_buf_get_skb failed, num %d len %d\n", + num, sublen); + break; + } + skb_queue_tail(&bus->glom, pnext); + + /* Adhere to start alignment requirements */ + pkt_align(pnext, sublen, BRCMF_SDALIGN); + } + + /* If all allocations succeeded, save packet chain + in bus structure */ + if (pnext) { + brcmf_dbg(GLOM, "allocated %d-byte packet chain for %d subframes\n", + totlen, num); + if (BRCMF_GLOM_ON() && bus->cur_read.len && + totlen != bus->cur_read.len) { + brcmf_dbg(GLOM, "glomdesc mismatch: nextlen %d glomdesc %d rxseq %d\n", + bus->cur_read.len, totlen, rxseq); + } + pfirst = pnext = NULL; + } else { + brcmf_sdbrcm_free_glom(bus); + num = 0; + } + + /* Done with descriptor packet */ + brcmu_pkt_buf_free_skb(bus->glomd); + bus->glomd = NULL; + bus->cur_read.len = 0; + } + + /* Ok -- either we just generated a packet chain, + or had one from before */ + if (!skb_queue_empty(&bus->glom)) { + if (BRCMF_GLOM_ON()) { + brcmf_dbg(GLOM, "try superframe read, packet chain:\n"); + skb_queue_walk(&bus->glom, pnext) { + brcmf_dbg(GLOM, " %p: %p len 0x%04x (%d)\n", + pnext, (u8 *) (pnext->data), + pnext->len, pnext->len); + } + } + + pfirst = skb_peek(&bus->glom); + dlen = (u16) brcmf_sdbrcm_glom_len(bus); + + /* Do an SDIO read for the superframe. Configurable iovar to + * read directly into the chained packet, or allocate a large + * packet and and copy into the chain. + */ + sdio_claim_host(bus->sdiodev->func[1]); + if (usechain) { + errcode = brcmf_sdcard_recv_chain(bus->sdiodev, + bus->sdiodev->sbwad, + SDIO_FUNC_2, F2SYNC, &bus->glom); + } else if (bus->dataptr) { + errcode = brcmf_sdcard_recv_buf(bus->sdiodev, + bus->sdiodev->sbwad, + SDIO_FUNC_2, F2SYNC, + bus->dataptr, dlen); + sublen = (u16) brcmf_sdbrcm_glom_from_buf(bus, dlen); + if (sublen != dlen) { + brcmf_err("FAILED TO COPY, dlen %d sublen %d\n", + dlen, sublen); + errcode = -1; + } + pnext = NULL; + } else { + brcmf_err("COULDN'T ALLOC %d-BYTE GLOM, FORCE FAILURE\n", + dlen); + errcode = -1; + } + sdio_release_host(bus->sdiodev->func[1]); + bus->sdcnt.f2rxdata++; + + /* On failure, kill the superframe, allow a couple retries */ + if (errcode < 0) { + brcmf_err("glom read of %d bytes failed: %d\n", + dlen, errcode); + + sdio_claim_host(bus->sdiodev->func[1]); + if (bus->glomerr++ < 3) { + brcmf_sdbrcm_rxfail(bus, true, true); + } else { + bus->glomerr = 0; + brcmf_sdbrcm_rxfail(bus, true, false); + bus->sdcnt.rxglomfail++; + brcmf_sdbrcm_free_glom(bus); + } + sdio_release_host(bus->sdiodev->func[1]); + return 0; + } + + brcmf_dbg_hex_dump(BRCMF_GLOM_ON(), + pfirst->data, min_t(int, pfirst->len, 48), + "SUPERFRAME:\n"); + + rd_new.seq_num = rxseq; + rd_new.len = dlen; + sdio_claim_host(bus->sdiodev->func[1]); + errcode = brcmf_sdio_hdparser(bus, pfirst->data, &rd_new, + BRCMF_SDIO_FT_SUPER); + sdio_release_host(bus->sdiodev->func[1]); + bus->cur_read.len = rd_new.len_nxtfrm << 4; + + /* Remove superframe header, remember offset */ + skb_pull(pfirst, rd_new.dat_offset); + sfdoff = rd_new.dat_offset; + num = 0; + + /* Validate all the subframe headers */ + skb_queue_walk(&bus->glom, pnext) { + /* leave when invalid subframe is found */ + if (errcode) + break; + + rd_new.len = pnext->len; + rd_new.seq_num = rxseq++; + sdio_claim_host(bus->sdiodev->func[1]); + errcode = brcmf_sdio_hdparser(bus, pnext->data, &rd_new, + BRCMF_SDIO_FT_SUB); + sdio_release_host(bus->sdiodev->func[1]); + brcmf_dbg_hex_dump(BRCMF_GLOM_ON(), + pnext->data, 32, "subframe:\n"); + + num++; + } + + if (errcode) { + /* Terminate frame on error, request + a couple retries */ + sdio_claim_host(bus->sdiodev->func[1]); + if (bus->glomerr++ < 3) { + /* Restore superframe header space */ + skb_push(pfirst, sfdoff); + brcmf_sdbrcm_rxfail(bus, true, true); + } else { + bus->glomerr = 0; + brcmf_sdbrcm_rxfail(bus, true, false); + bus->sdcnt.rxglomfail++; + brcmf_sdbrcm_free_glom(bus); + } + sdio_release_host(bus->sdiodev->func[1]); + bus->cur_read.len = 0; + return 0; + } + + /* Basic SD framing looks ok - process each packet (header) */ + + skb_queue_walk_safe(&bus->glom, pfirst, pnext) { + dptr = (u8 *) (pfirst->data); + sublen = get_unaligned_le16(dptr); + doff = SDPCM_DOFFSET_VALUE(&dptr[SDPCM_FRAMETAG_LEN]); + + brcmf_dbg_hex_dump(BRCMF_BYTES_ON() && BRCMF_DATA_ON(), + dptr, pfirst->len, + "Rx Subframe Data:\n"); + + __skb_trim(pfirst, sublen); + skb_pull(pfirst, doff); + + if (pfirst->len == 0) { + skb_unlink(pfirst, &bus->glom); + brcmu_pkt_buf_free_skb(pfirst); + continue; + } + + brcmf_dbg_hex_dump(BRCMF_GLOM_ON(), + pfirst->data, + min_t(int, pfirst->len, 32), + "subframe %d to stack, %p (%p/%d) nxt/lnk %p/%p\n", + bus->glom.qlen, pfirst, pfirst->data, + pfirst->len, pfirst->next, + pfirst->prev); + } + /* sent any remaining packets up */ + if (bus->glom.qlen) + brcmf_rx_frames(bus->sdiodev->dev, &bus->glom); + + bus->sdcnt.rxglomframes++; + bus->sdcnt.rxglompkts += bus->glom.qlen; + } + return num; +} + +static int brcmf_sdbrcm_dcmd_resp_wait(struct brcmf_sdio *bus, uint *condition, + bool *pending) +{ + DECLARE_WAITQUEUE(wait, current); + int timeout = msecs_to_jiffies(DCMD_RESP_TIMEOUT); + + /* Wait until control frame is available */ + add_wait_queue(&bus->dcmd_resp_wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + + while (!(*condition) && (!signal_pending(current) && timeout)) + timeout = schedule_timeout(timeout); + + if (signal_pending(current)) + *pending = true; + + set_current_state(TASK_RUNNING); + remove_wait_queue(&bus->dcmd_resp_wait, &wait); + + return timeout; +} + +static int brcmf_sdbrcm_dcmd_resp_wake(struct brcmf_sdio *bus) +{ + if (waitqueue_active(&bus->dcmd_resp_wait)) + wake_up_interruptible(&bus->dcmd_resp_wait); + + return 0; +} +static void +brcmf_sdbrcm_read_control(struct brcmf_sdio *bus, u8 *hdr, uint len, uint doff) +{ + uint rdlen, pad; + u8 *buf = NULL, *rbuf; + int sdret; + + brcmf_dbg(TRACE, "Enter\n"); + + if (bus->rxblen) + buf = vzalloc(bus->rxblen); + if (!buf) + goto done; + + rbuf = bus->rxbuf; + pad = ((unsigned long)rbuf % BRCMF_SDALIGN); + if (pad) + rbuf += (BRCMF_SDALIGN - pad); + + /* Copy the already-read portion over */ + memcpy(buf, hdr, BRCMF_FIRSTREAD); + if (len <= BRCMF_FIRSTREAD) + goto gotpkt; + + /* Raise rdlen to next SDIO block to avoid tail command */ + rdlen = len - BRCMF_FIRSTREAD; + if (bus->roundup && bus->blocksize && (rdlen > bus->blocksize)) { + pad = bus->blocksize - (rdlen % bus->blocksize); + if ((pad <= bus->roundup) && (pad < bus->blocksize) && + ((len + pad) < bus->sdiodev->bus_if->maxctl)) + rdlen += pad; + } else if (rdlen % BRCMF_SDALIGN) { + rdlen += BRCMF_SDALIGN - (rdlen % BRCMF_SDALIGN); + } + + /* Satisfy length-alignment requirements */ + if (rdlen & (ALIGNMENT - 1)) + rdlen = roundup(rdlen, ALIGNMENT); + + /* Drop if the read is too big or it exceeds our maximum */ + if ((rdlen + BRCMF_FIRSTREAD) > bus->sdiodev->bus_if->maxctl) { + brcmf_err("%d-byte control read exceeds %d-byte buffer\n", + rdlen, bus->sdiodev->bus_if->maxctl); + brcmf_sdbrcm_rxfail(bus, false, false); + goto done; + } + + if ((len - doff) > bus->sdiodev->bus_if->maxctl) { + brcmf_err("%d-byte ctl frame (%d-byte ctl data) exceeds %d-byte limit\n", + len, len - doff, bus->sdiodev->bus_if->maxctl); + bus->sdcnt.rx_toolong++; + brcmf_sdbrcm_rxfail(bus, false, false); + goto done; + } + + /* Read remain of frame body */ + sdret = brcmf_sdcard_recv_buf(bus->sdiodev, + bus->sdiodev->sbwad, + SDIO_FUNC_2, + F2SYNC, rbuf, rdlen); + bus->sdcnt.f2rxdata++; + + /* Control frame failures need retransmission */ + if (sdret < 0) { + brcmf_err("read %d control bytes failed: %d\n", + rdlen, sdret); + bus->sdcnt.rxc_errors++; + brcmf_sdbrcm_rxfail(bus, true, true); + goto done; + } else + memcpy(buf + BRCMF_FIRSTREAD, rbuf, rdlen); + +gotpkt: + + brcmf_dbg_hex_dump(BRCMF_BYTES_ON() && BRCMF_CTL_ON(), + buf, len, "RxCtrl:\n"); + + /* Point to valid data and indicate its length */ + spin_lock_bh(&bus->rxctl_lock); + if (bus->rxctl) { + brcmf_err("last control frame is being processed.\n"); + spin_unlock_bh(&bus->rxctl_lock); + vfree(buf); + goto done; + } + bus->rxctl = buf + doff; + bus->rxctl_orig = buf; + bus->rxlen = len - doff; + spin_unlock_bh(&bus->rxctl_lock); + +done: + /* Awake any waiters */ + brcmf_sdbrcm_dcmd_resp_wake(bus); +} + +/* Pad read to blocksize for efficiency */ +static void brcmf_pad(struct brcmf_sdio *bus, u16 *pad, u16 *rdlen) +{ + if (bus->roundup && bus->blocksize && *rdlen > bus->blocksize) { + *pad = bus->blocksize - (*rdlen % bus->blocksize); + if (*pad <= bus->roundup && *pad < bus->blocksize && + *rdlen + *pad + BRCMF_FIRSTREAD < MAX_RX_DATASZ) + *rdlen += *pad; + } else if (*rdlen % BRCMF_SDALIGN) { + *rdlen += BRCMF_SDALIGN - (*rdlen % BRCMF_SDALIGN); + } +} + +static uint brcmf_sdio_readframes(struct brcmf_sdio *bus, uint maxframes) +{ + struct sk_buff *pkt; /* Packet for event or data frames */ + struct sk_buff_head pktlist; /* needed for bus interface */ + u16 pad; /* Number of pad bytes to read */ + uint rxleft = 0; /* Remaining number of frames allowed */ + int ret; /* Return code from calls */ + uint rxcount = 0; /* Total frames read */ + struct brcmf_sdio_read *rd = &bus->cur_read, rd_new; + u8 head_read = 0; + + brcmf_dbg(TRACE, "Enter\n"); + + /* Not finished unless we encounter no more frames indication */ + bus->rxpending = true; + + for (rd->seq_num = bus->rx_seq, rxleft = maxframes; + !bus->rxskip && rxleft && + bus->sdiodev->bus_if->state != BRCMF_BUS_DOWN; + rd->seq_num++, rxleft--) { + + /* Handle glomming separately */ + if (bus->glomd || !skb_queue_empty(&bus->glom)) { + u8 cnt; + brcmf_dbg(GLOM, "calling rxglom: glomd %p, glom %p\n", + bus->glomd, skb_peek(&bus->glom)); + cnt = brcmf_sdbrcm_rxglom(bus, rd->seq_num); + brcmf_dbg(GLOM, "rxglom returned %d\n", cnt); + rd->seq_num += cnt - 1; + rxleft = (rxleft > cnt) ? (rxleft - cnt) : 1; + continue; + } + + rd->len_left = rd->len; + /* read header first for unknow frame length */ + sdio_claim_host(bus->sdiodev->func[1]); + if (!rd->len) { + ret = brcmf_sdcard_recv_buf(bus->sdiodev, + bus->sdiodev->sbwad, + SDIO_FUNC_2, F2SYNC, + bus->rxhdr, + BRCMF_FIRSTREAD); + bus->sdcnt.f2rxhdrs++; + if (ret < 0) { + brcmf_err("RXHEADER FAILED: %d\n", + ret); + bus->sdcnt.rx_hdrfail++; + brcmf_sdbrcm_rxfail(bus, true, true); + sdio_release_host(bus->sdiodev->func[1]); + continue; + } + + brcmf_dbg_hex_dump(BRCMF_BYTES_ON() || BRCMF_HDRS_ON(), + bus->rxhdr, SDPCM_HDRLEN, + "RxHdr:\n"); + + if (brcmf_sdio_hdparser(bus, bus->rxhdr, rd, + BRCMF_SDIO_FT_NORMAL)) { + sdio_release_host(bus->sdiodev->func[1]); + if (!bus->rxpending) + break; + else + continue; + } + + if (rd->channel == SDPCM_CONTROL_CHANNEL) { + brcmf_sdbrcm_read_control(bus, bus->rxhdr, + rd->len, + rd->dat_offset); + /* prepare the descriptor for the next read */ + rd->len = rd->len_nxtfrm << 4; + rd->len_nxtfrm = 0; + /* treat all packet as event if we don't know */ + rd->channel = SDPCM_EVENT_CHANNEL; + sdio_release_host(bus->sdiodev->func[1]); + continue; + } + rd->len_left = rd->len > BRCMF_FIRSTREAD ? + rd->len - BRCMF_FIRSTREAD : 0; + head_read = BRCMF_FIRSTREAD; + } + + brcmf_pad(bus, &pad, &rd->len_left); + + pkt = brcmu_pkt_buf_get_skb(rd->len_left + head_read + + BRCMF_SDALIGN); + if (!pkt) { + /* Give up on data, request rtx of events */ + brcmf_err("brcmu_pkt_buf_get_skb failed\n"); + brcmf_sdbrcm_rxfail(bus, false, + RETRYCHAN(rd->channel)); + sdio_release_host(bus->sdiodev->func[1]); + continue; + } + skb_pull(pkt, head_read); + pkt_align(pkt, rd->len_left, BRCMF_SDALIGN); + + ret = brcmf_sdcard_recv_pkt(bus->sdiodev, bus->sdiodev->sbwad, + SDIO_FUNC_2, F2SYNC, pkt); + bus->sdcnt.f2rxdata++; + sdio_release_host(bus->sdiodev->func[1]); + + if (ret < 0) { + brcmf_err("read %d bytes from channel %d failed: %d\n", + rd->len, rd->channel, ret); + brcmu_pkt_buf_free_skb(pkt); + sdio_claim_host(bus->sdiodev->func[1]); + brcmf_sdbrcm_rxfail(bus, true, + RETRYCHAN(rd->channel)); + sdio_release_host(bus->sdiodev->func[1]); + continue; + } + + if (head_read) { + skb_push(pkt, head_read); + memcpy(pkt->data, bus->rxhdr, head_read); + head_read = 0; + } else { + memcpy(bus->rxhdr, pkt->data, SDPCM_HDRLEN); + rd_new.seq_num = rd->seq_num; + sdio_claim_host(bus->sdiodev->func[1]); + if (brcmf_sdio_hdparser(bus, bus->rxhdr, &rd_new, + BRCMF_SDIO_FT_NORMAL)) { + rd->len = 0; + brcmu_pkt_buf_free_skb(pkt); + } + bus->sdcnt.rx_readahead_cnt++; + if (rd->len != roundup(rd_new.len, 16)) { + brcmf_err("frame length mismatch:read %d, should be %d\n", + rd->len, + roundup(rd_new.len, 16) >> 4); + rd->len = 0; + brcmf_sdbrcm_rxfail(bus, true, true); + sdio_release_host(bus->sdiodev->func[1]); + brcmu_pkt_buf_free_skb(pkt); + continue; + } + sdio_release_host(bus->sdiodev->func[1]); + rd->len_nxtfrm = rd_new.len_nxtfrm; + rd->channel = rd_new.channel; + rd->dat_offset = rd_new.dat_offset; + + brcmf_dbg_hex_dump(!(BRCMF_BYTES_ON() && + BRCMF_DATA_ON()) && + BRCMF_HDRS_ON(), + bus->rxhdr, SDPCM_HDRLEN, + "RxHdr:\n"); + + if (rd_new.channel == SDPCM_CONTROL_CHANNEL) { + brcmf_err("readahead on control packet %d?\n", + rd_new.seq_num); + /* Force retry w/normal header read */ + rd->len = 0; + sdio_claim_host(bus->sdiodev->func[1]); + brcmf_sdbrcm_rxfail(bus, false, true); + sdio_release_host(bus->sdiodev->func[1]); + brcmu_pkt_buf_free_skb(pkt); + continue; + } + } + + brcmf_dbg_hex_dump(BRCMF_BYTES_ON() && BRCMF_DATA_ON(), + pkt->data, rd->len, "Rx Data:\n"); + + /* Save superframe descriptor and allocate packet frame */ + if (rd->channel == SDPCM_GLOM_CHANNEL) { + if (SDPCM_GLOMDESC(&bus->rxhdr[SDPCM_FRAMETAG_LEN])) { + brcmf_dbg(GLOM, "glom descriptor, %d bytes:\n", + rd->len); + brcmf_dbg_hex_dump(BRCMF_GLOM_ON(), + pkt->data, rd->len, + "Glom Data:\n"); + __skb_trim(pkt, rd->len); + skb_pull(pkt, SDPCM_HDRLEN); + bus->glomd = pkt; + } else { + brcmf_err("%s: glom superframe w/o " + "descriptor!\n", __func__); + sdio_claim_host(bus->sdiodev->func[1]); + brcmf_sdbrcm_rxfail(bus, false, false); + sdio_release_host(bus->sdiodev->func[1]); + } + /* prepare the descriptor for the next read */ + rd->len = rd->len_nxtfrm << 4; + rd->len_nxtfrm = 0; + /* treat all packet as event if we don't know */ + rd->channel = SDPCM_EVENT_CHANNEL; + continue; + } + + /* Fill in packet len and prio, deliver upward */ + __skb_trim(pkt, rd->len); + skb_pull(pkt, rd->dat_offset); + + /* prepare the descriptor for the next read */ + rd->len = rd->len_nxtfrm << 4; + rd->len_nxtfrm = 0; + /* treat all packet as event if we don't know */ + rd->channel = SDPCM_EVENT_CHANNEL; + + if (pkt->len == 0) { + brcmu_pkt_buf_free_skb(pkt); + continue; + } + + skb_queue_head_init(&pktlist); + skb_queue_tail(&pktlist, pkt); + brcmf_rx_frames(bus->sdiodev->dev, &pktlist); + } + + rxcount = maxframes - rxleft; + /* Message if we hit the limit */ + if (!rxleft) + brcmf_dbg(DATA, "hit rx limit of %d frames\n", maxframes); + else + brcmf_dbg(DATA, "processed %d frames\n", rxcount); + /* Back off rxseq if awaiting rtx, update rx_seq */ + if (bus->rxskip) + rd->seq_num--; + bus->rx_seq = rd->seq_num; + + return rxcount; +} + +static void +brcmf_sdbrcm_wait_event_wakeup(struct brcmf_sdio *bus) +{ + if (waitqueue_active(&bus->ctrl_wait)) + wake_up_interruptible(&bus->ctrl_wait); + return; +} + +/* Writes a HW/SW header into the packet and sends it. */ +/* Assumes: (a) header space already there, (b) caller holds lock */ +static int brcmf_sdbrcm_txpkt(struct brcmf_sdio *bus, struct sk_buff *pkt, + uint chan) +{ + int ret; + u8 *frame; + u16 len, pad = 0; + u32 swheader; + int i; + + brcmf_dbg(TRACE, "Enter\n"); + + frame = (u8 *) (pkt->data); + + /* Add alignment padding, allocate new packet if needed */ + pad = ((unsigned long)frame % BRCMF_SDALIGN); + if (pad) { + if (skb_headroom(pkt) < pad) { + brcmf_dbg(INFO, "insufficient headroom %d for %d pad\n", + skb_headroom(pkt), pad); + bus->sdiodev->bus_if->tx_realloc++; + ret = skb_cow(pkt, BRCMF_SDALIGN); + if (ret) + goto done; + pad = ((unsigned long)frame % BRCMF_SDALIGN); + } + skb_push(pkt, pad); + frame = (u8 *) (pkt->data); + memset(frame, 0, pad + SDPCM_HDRLEN); + } + /* precondition: pad < BRCMF_SDALIGN */ + + /* Hardware tag: 2 byte len followed by 2 byte ~len check (all LE) */ + len = (u16) (pkt->len); + *(__le16 *) frame = cpu_to_le16(len); + *(((__le16 *) frame) + 1) = cpu_to_le16(~len); + + /* Software tag: channel, sequence number, data offset */ + swheader = + ((chan << SDPCM_CHANNEL_SHIFT) & SDPCM_CHANNEL_MASK) | bus->tx_seq | + (((pad + + SDPCM_HDRLEN) << SDPCM_DOFFSET_SHIFT) & SDPCM_DOFFSET_MASK); + + *(((__le32 *) frame) + 1) = cpu_to_le32(swheader); + *(((__le32 *) frame) + 2) = 0; + +#ifdef DEBUG + tx_packets[pkt->priority]++; +#endif + + brcmf_dbg_hex_dump(BRCMF_BYTES_ON() && + ((BRCMF_CTL_ON() && chan == SDPCM_CONTROL_CHANNEL) || + (BRCMF_DATA_ON() && chan != SDPCM_CONTROL_CHANNEL)), + frame, len, "Tx Frame:\n"); + brcmf_dbg_hex_dump(!(BRCMF_BYTES_ON() && + ((BRCMF_CTL_ON() && + chan == SDPCM_CONTROL_CHANNEL) || + (BRCMF_DATA_ON() && + chan != SDPCM_CONTROL_CHANNEL))) && + BRCMF_HDRS_ON(), + frame, min_t(u16, len, 16), "TxHdr:\n"); + + /* Raise len to next SDIO block to eliminate tail command */ + if (bus->roundup && bus->blocksize && (len > bus->blocksize)) { + u16 pad = bus->blocksize - (len % bus->blocksize); + if ((pad <= bus->roundup) && (pad < bus->blocksize)) + len += pad; + } else if (len % BRCMF_SDALIGN) { + len += BRCMF_SDALIGN - (len % BRCMF_SDALIGN); + } + + /* Some controllers have trouble with odd bytes -- round to even */ + if (len & (ALIGNMENT - 1)) + len = roundup(len, ALIGNMENT); + + sdio_claim_host(bus->sdiodev->func[1]); + ret = brcmf_sdcard_send_pkt(bus->sdiodev, bus->sdiodev->sbwad, + SDIO_FUNC_2, F2SYNC, pkt); + bus->sdcnt.f2txdata++; + + if (ret < 0) { + /* On failure, abort the command and terminate the frame */ + brcmf_dbg(INFO, "sdio error %d, abort command and terminate frame\n", + ret); + bus->sdcnt.tx_sderrs++; + + brcmf_sdcard_abort(bus->sdiodev, SDIO_FUNC_2); + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_FUNC1_FRAMECTRL, + SFC_WF_TERM, NULL); + bus->sdcnt.f1regdata++; + + for (i = 0; i < 3; i++) { + u8 hi, lo; + hi = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_FUNC1_WFRAMEBCHI, NULL); + lo = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_FUNC1_WFRAMEBCLO, NULL); + bus->sdcnt.f1regdata += 2; + if ((hi == 0) && (lo == 0)) + break; + } + + } + sdio_release_host(bus->sdiodev->func[1]); + if (ret == 0) + bus->tx_seq = (bus->tx_seq + 1) % SDPCM_SEQUENCE_WRAP; + +done: + /* restore pkt buffer pointer before calling tx complete routine */ + skb_pull(pkt, SDPCM_HDRLEN + pad); + brcmf_txcomplete(bus->sdiodev->dev, pkt, ret == 0); + return ret; +} + +static uint brcmf_sdbrcm_sendfromq(struct brcmf_sdio *bus, uint maxframes) +{ + struct sk_buff *pkt; + u32 intstatus = 0; + int ret = 0, prec_out; + uint cnt = 0; + uint datalen; + u8 tx_prec_map; + + brcmf_dbg(TRACE, "Enter\n"); + + tx_prec_map = ~bus->flowcontrol; + + /* Send frames until the limit or some other event */ + for (cnt = 0; (cnt < maxframes) && data_ok(bus); cnt++) { + spin_lock_bh(&bus->txqlock); + pkt = brcmu_pktq_mdeq(&bus->txq, tx_prec_map, &prec_out); + if (pkt == NULL) { + spin_unlock_bh(&bus->txqlock); + break; + } + spin_unlock_bh(&bus->txqlock); + datalen = pkt->len - SDPCM_HDRLEN; + + ret = brcmf_sdbrcm_txpkt(bus, pkt, SDPCM_DATA_CHANNEL); + + /* In poll mode, need to check for other events */ + if (!bus->intr && cnt) { + /* Check device status, signal pending interrupt */ + sdio_claim_host(bus->sdiodev->func[1]); + ret = r_sdreg32(bus, &intstatus, + offsetof(struct sdpcmd_regs, + intstatus)); + sdio_release_host(bus->sdiodev->func[1]); + bus->sdcnt.f2txdata++; + if (ret != 0) + break; + if (intstatus & bus->hostintmask) + atomic_set(&bus->ipend, 1); + } + } + + /* Deflow-control stack if needed */ + if ((bus->sdiodev->bus_if->state == BRCMF_BUS_DATA) && + bus->txoff && (pktq_len(&bus->txq) < TXLOW)) { + bus->txoff = false; + brcmf_txflowblock(bus->sdiodev->dev, false); + } + + return cnt; +} + +static void brcmf_sdbrcm_bus_stop(struct device *dev) +{ + u32 local_hostintmask; + u8 saveclk; + int err; + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; + struct brcmf_sdio *bus = sdiodev->bus; + + brcmf_dbg(TRACE, "Enter\n"); + + if (bus->watchdog_tsk) { + send_sig(SIGTERM, bus->watchdog_tsk, 1); + kthread_stop(bus->watchdog_tsk); + bus->watchdog_tsk = NULL; + } + + sdio_claim_host(bus->sdiodev->func[1]); + + /* Enable clock for device interrupts */ + brcmf_sdbrcm_bus_sleep(bus, false, false); + + /* Disable and clear interrupts at the chip level also */ + w_sdreg32(bus, 0, offsetof(struct sdpcmd_regs, hostintmask)); + local_hostintmask = bus->hostintmask; + bus->hostintmask = 0; + + /* Change our idea of bus state */ + bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN; + + /* Force clocks on backplane to be sure F2 interrupt propagates */ + saveclk = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_FUNC1_CHIPCLKCSR, &err); + if (!err) { + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, + (saveclk | SBSDIO_FORCE_HT), &err); + } + if (err) + brcmf_err("Failed to force clock for F2: err %d\n", err); + + /* Turn off the bus (F2), free any pending packets */ + brcmf_dbg(INTR, "disable SDIO interrupts\n"); + brcmf_sdio_regwb(bus->sdiodev, SDIO_CCCR_IOEx, SDIO_FUNC_ENABLE_1, + NULL); + + /* Clear any pending interrupts now that F2 is disabled */ + w_sdreg32(bus, local_hostintmask, + offsetof(struct sdpcmd_regs, intstatus)); + + /* Turn off the backplane clock (only) */ + brcmf_sdbrcm_clkctl(bus, CLK_SDONLY, false); + sdio_release_host(bus->sdiodev->func[1]); + + /* Clear the data packet queues */ + brcmu_pktq_flush(&bus->txq, true, NULL, NULL); + + /* Clear any held glomming stuff */ + if (bus->glomd) + brcmu_pkt_buf_free_skb(bus->glomd); + brcmf_sdbrcm_free_glom(bus); + + /* Clear rx control and wake any waiters */ + spin_lock_bh(&bus->rxctl_lock); + bus->rxlen = 0; + spin_unlock_bh(&bus->rxctl_lock); + brcmf_sdbrcm_dcmd_resp_wake(bus); + + /* Reset some F2 state stuff */ + bus->rxskip = false; + bus->tx_seq = bus->rx_seq = 0; +} + +static inline void brcmf_sdbrcm_clrintr(struct brcmf_sdio *bus) +{ + unsigned long flags; + + if (bus->sdiodev->oob_irq_requested) { + spin_lock_irqsave(&bus->sdiodev->irq_en_lock, flags); + if (!bus->sdiodev->irq_en && !atomic_read(&bus->ipend)) { + enable_irq(bus->sdiodev->pdata->oob_irq_nr); + bus->sdiodev->irq_en = true; + } + spin_unlock_irqrestore(&bus->sdiodev->irq_en_lock, flags); + } +} + +static inline void brcmf_sdbrcm_adddpctsk(struct brcmf_sdio *bus) +{ + struct list_head *new_hd; + unsigned long flags; + + if (in_interrupt()) + new_hd = kzalloc(sizeof(struct list_head), GFP_ATOMIC); + else + new_hd = kzalloc(sizeof(struct list_head), GFP_KERNEL); + if (new_hd == NULL) + return; + + spin_lock_irqsave(&bus->dpc_tl_lock, flags); + list_add_tail(new_hd, &bus->dpc_tsklst); + spin_unlock_irqrestore(&bus->dpc_tl_lock, flags); +} + +static int brcmf_sdio_intr_rstatus(struct brcmf_sdio *bus) +{ + u8 idx; + u32 addr; + unsigned long val; + int n, ret; + + idx = brcmf_sdio_chip_getinfidx(bus->ci, BCMA_CORE_SDIO_DEV); + addr = bus->ci->c_inf[idx].base + + offsetof(struct sdpcmd_regs, intstatus); + + ret = brcmf_sdio_regrw_helper(bus->sdiodev, addr, &val, false); + bus->sdcnt.f1regdata++; + if (ret != 0) + val = 0; + + val &= bus->hostintmask; + atomic_set(&bus->fcstate, !!(val & I_HMB_FC_STATE)); + + /* Clear interrupts */ + if (val) { + ret = brcmf_sdio_regrw_helper(bus->sdiodev, addr, &val, true); + bus->sdcnt.f1regdata++; + } + + if (ret) { + atomic_set(&bus->intstatus, 0); + } else if (val) { + for_each_set_bit(n, &val, 32) + set_bit(n, (unsigned long *)&bus->intstatus.counter); + } + + return ret; +} + +static void brcmf_sdbrcm_dpc(struct brcmf_sdio *bus) +{ + u32 newstatus = 0; + unsigned long intstatus; + uint rxlimit = bus->rxbound; /* Rx frames to read before resched */ + uint txlimit = bus->txbound; /* Tx frames to send before resched */ + uint framecnt = 0; /* Temporary counter of tx/rx frames */ + int err = 0, n; + + brcmf_dbg(TRACE, "Enter\n"); + + sdio_claim_host(bus->sdiodev->func[1]); + + /* If waiting for HTAVAIL, check status */ + if (!bus->sr_enabled && bus->clkstate == CLK_PENDING) { + u8 clkctl, devctl = 0; + +#ifdef DEBUG + /* Check for inconsistent device control */ + devctl = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_DEVICE_CTL, &err); + if (err) { + brcmf_err("error reading DEVCTL: %d\n", err); + bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN; + } +#endif /* DEBUG */ + + /* Read CSR, if clock on switch to AVAIL, else ignore */ + clkctl = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_FUNC1_CHIPCLKCSR, &err); + if (err) { + brcmf_err("error reading CSR: %d\n", + err); + bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN; + } + + brcmf_dbg(SDIO, "DPC: PENDING, devctl 0x%02x clkctl 0x%02x\n", + devctl, clkctl); + + if (SBSDIO_HTAV(clkctl)) { + devctl = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_DEVICE_CTL, &err); + if (err) { + brcmf_err("error reading DEVCTL: %d\n", + err); + bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN; + } + devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY; + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_DEVICE_CTL, + devctl, &err); + if (err) { + brcmf_err("error writing DEVCTL: %d\n", + err); + bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN; + } + bus->clkstate = CLK_AVAIL; + } + } + + /* Make sure backplane clock is on */ + brcmf_sdbrcm_bus_sleep(bus, false, true); + + /* Pending interrupt indicates new device status */ + if (atomic_read(&bus->ipend) > 0) { + atomic_set(&bus->ipend, 0); + err = brcmf_sdio_intr_rstatus(bus); + } + + /* Start with leftover status bits */ + intstatus = atomic_xchg(&bus->intstatus, 0); + + /* Handle flow-control change: read new state in case our ack + * crossed another change interrupt. If change still set, assume + * FC ON for safety, let next loop through do the debounce. + */ + if (intstatus & I_HMB_FC_CHANGE) { + intstatus &= ~I_HMB_FC_CHANGE; + err = w_sdreg32(bus, I_HMB_FC_CHANGE, + offsetof(struct sdpcmd_regs, intstatus)); + + err = r_sdreg32(bus, &newstatus, + offsetof(struct sdpcmd_regs, intstatus)); + bus->sdcnt.f1regdata += 2; + atomic_set(&bus->fcstate, + !!(newstatus & (I_HMB_FC_STATE | I_HMB_FC_CHANGE))); + intstatus |= (newstatus & bus->hostintmask); + } + + /* Handle host mailbox indication */ + if (intstatus & I_HMB_HOST_INT) { + intstatus &= ~I_HMB_HOST_INT; + intstatus |= brcmf_sdbrcm_hostmail(bus); + } + + sdio_release_host(bus->sdiodev->func[1]); + + /* Generally don't ask for these, can get CRC errors... */ + if (intstatus & I_WR_OOSYNC) { + brcmf_err("Dongle reports WR_OOSYNC\n"); + intstatus &= ~I_WR_OOSYNC; + } + + if (intstatus & I_RD_OOSYNC) { + brcmf_err("Dongle reports RD_OOSYNC\n"); + intstatus &= ~I_RD_OOSYNC; + } + + if (intstatus & I_SBINT) { + brcmf_err("Dongle reports SBINT\n"); + intstatus &= ~I_SBINT; + } + + /* Would be active due to wake-wlan in gSPI */ + if (intstatus & I_CHIPACTIVE) { + brcmf_dbg(INFO, "Dongle reports CHIPACTIVE\n"); + intstatus &= ~I_CHIPACTIVE; + } + + /* Ignore frame indications if rxskip is set */ + if (bus->rxskip) + intstatus &= ~I_HMB_FRAME_IND; + + /* On frame indication, read available frames */ + if (PKT_AVAILABLE() && bus->clkstate == CLK_AVAIL) { + framecnt = brcmf_sdio_readframes(bus, rxlimit); + if (!bus->rxpending) + intstatus &= ~I_HMB_FRAME_IND; + rxlimit -= min(framecnt, rxlimit); + } + + /* Keep still-pending events for next scheduling */ + if (intstatus) { + for_each_set_bit(n, &intstatus, 32) + set_bit(n, (unsigned long *)&bus->intstatus.counter); + } + + brcmf_sdbrcm_clrintr(bus); + + if (data_ok(bus) && bus->ctrl_frame_stat && + (bus->clkstate == CLK_AVAIL)) { + int i; + + sdio_claim_host(bus->sdiodev->func[1]); + err = brcmf_sdcard_send_buf(bus->sdiodev, bus->sdiodev->sbwad, + SDIO_FUNC_2, F2SYNC, bus->ctrl_frame_buf, + (u32) bus->ctrl_frame_len); + + if (err < 0) { + /* On failure, abort the command and + terminate the frame */ + brcmf_dbg(INFO, "sdio error %d, abort command and terminate frame\n", + err); + bus->sdcnt.tx_sderrs++; + + brcmf_sdcard_abort(bus->sdiodev, SDIO_FUNC_2); + + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_FUNC1_FRAMECTRL, + SFC_WF_TERM, &err); + bus->sdcnt.f1regdata++; + + for (i = 0; i < 3; i++) { + u8 hi, lo; + hi = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_FUNC1_WFRAMEBCHI, + &err); + lo = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_FUNC1_WFRAMEBCLO, + &err); + bus->sdcnt.f1regdata += 2; + if ((hi == 0) && (lo == 0)) + break; + } + + } else { + bus->tx_seq = (bus->tx_seq + 1) % SDPCM_SEQUENCE_WRAP; + } + sdio_release_host(bus->sdiodev->func[1]); + bus->ctrl_frame_stat = false; + brcmf_sdbrcm_wait_event_wakeup(bus); + } + /* Send queued frames (limit 1 if rx may still be pending) */ + else if ((bus->clkstate == CLK_AVAIL) && !atomic_read(&bus->fcstate) && + brcmu_pktq_mlen(&bus->txq, ~bus->flowcontrol) && txlimit + && data_ok(bus)) { + framecnt = bus->rxpending ? min(txlimit, bus->txminmax) : + txlimit; + framecnt = brcmf_sdbrcm_sendfromq(bus, framecnt); + txlimit -= framecnt; + } + + if ((bus->sdiodev->bus_if->state == BRCMF_BUS_DOWN) || (err != 0)) { + brcmf_err("failed backplane access over SDIO, halting operation\n"); + bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN; + atomic_set(&bus->intstatus, 0); + } else if (atomic_read(&bus->intstatus) || + atomic_read(&bus->ipend) > 0 || + (!atomic_read(&bus->fcstate) && + brcmu_pktq_mlen(&bus->txq, ~bus->flowcontrol) && + data_ok(bus)) || PKT_AVAILABLE()) { + brcmf_sdbrcm_adddpctsk(bus); + } + + /* If we're done for now, turn off clock request. */ + if ((bus->clkstate != CLK_PENDING) + && bus->idletime == BRCMF_IDLE_IMMEDIATE) { + bus->activity = false; + brcmf_dbg(SDIO, "idle state\n"); + sdio_claim_host(bus->sdiodev->func[1]); + brcmf_sdbrcm_bus_sleep(bus, true, false); + sdio_release_host(bus->sdiodev->func[1]); + } +} + +static struct pktq *brcmf_sdbrcm_bus_gettxq(struct device *dev) +{ + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; + struct brcmf_sdio *bus = sdiodev->bus; + + return &bus->txq; +} + +static int brcmf_sdbrcm_bus_txdata(struct device *dev, struct sk_buff *pkt) +{ + int ret = -EBADE; + uint datalen, prec; + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; + struct brcmf_sdio *bus = sdiodev->bus; + unsigned long flags; + + brcmf_dbg(TRACE, "Enter\n"); + + datalen = pkt->len; + + /* Add space for the header */ + skb_push(pkt, SDPCM_HDRLEN); + /* precondition: IS_ALIGNED((unsigned long)(pkt->data), 2) */ + + prec = prio2prec((pkt->priority & PRIOMASK)); + + /* Check for existing queue, current flow-control, + pending event, or pending clock */ + brcmf_dbg(TRACE, "deferring pktq len %d\n", pktq_len(&bus->txq)); + bus->sdcnt.fcqueued++; + + /* Priority based enq */ + spin_lock_bh(&bus->txqlock); + if (!brcmf_c_prec_enq(bus->sdiodev->dev, &bus->txq, pkt, prec)) { + skb_pull(pkt, SDPCM_HDRLEN); + brcmf_txcomplete(bus->sdiodev->dev, pkt, false); + brcmf_err("out of bus->txq !!!\n"); + ret = -ENOSR; + } else { + ret = 0; + } + spin_unlock_bh(&bus->txqlock); + + if (pktq_len(&bus->txq) >= TXHI) { + bus->txoff = true; + brcmf_txflowblock(bus->sdiodev->dev, true); + } + +#ifdef DEBUG + if (pktq_plen(&bus->txq, prec) > qcount[prec]) + qcount[prec] = pktq_plen(&bus->txq, prec); +#endif + + spin_lock_irqsave(&bus->dpc_tl_lock, flags); + if (list_empty(&bus->dpc_tsklst)) { + spin_unlock_irqrestore(&bus->dpc_tl_lock, flags); + + brcmf_sdbrcm_adddpctsk(bus); + queue_work(bus->brcmf_wq, &bus->datawork); + } else { + spin_unlock_irqrestore(&bus->dpc_tl_lock, flags); + } + + return ret; +} + +#ifdef DEBUG +#define CONSOLE_LINE_MAX 192 + +static int brcmf_sdbrcm_readconsole(struct brcmf_sdio *bus) +{ + struct brcmf_console *c = &bus->console; + u8 line[CONSOLE_LINE_MAX], ch; + u32 n, idx, addr; + int rv; + + /* Don't do anything until FWREADY updates console address */ + if (bus->console_addr == 0) + return 0; + + /* Read console log struct */ + addr = bus->console_addr + offsetof(struct rte_console, log_le); + rv = brcmf_sdio_ramrw(bus->sdiodev, false, addr, (u8 *)&c->log_le, + sizeof(c->log_le)); + if (rv < 0) + return rv; + + /* Allocate console buffer (one time only) */ + if (c->buf == NULL) { + c->bufsize = le32_to_cpu(c->log_le.buf_size); + c->buf = kmalloc(c->bufsize, GFP_ATOMIC); + if (c->buf == NULL) + return -ENOMEM; + } + + idx = le32_to_cpu(c->log_le.idx); + + /* Protect against corrupt value */ + if (idx > c->bufsize) + return -EBADE; + + /* Skip reading the console buffer if the index pointer + has not moved */ + if (idx == c->last) + return 0; + + /* Read the console buffer */ + addr = le32_to_cpu(c->log_le.buf); + rv = brcmf_sdio_ramrw(bus->sdiodev, false, addr, c->buf, c->bufsize); + if (rv < 0) + return rv; + + while (c->last != idx) { + for (n = 0; n < CONSOLE_LINE_MAX - 2; n++) { + if (c->last == idx) { + /* This would output a partial line. + * Instead, back up + * the buffer pointer and output this + * line next time around. + */ + if (c->last >= n) + c->last -= n; + else + c->last = c->bufsize - n; + goto break2; + } + ch = c->buf[c->last]; + c->last = (c->last + 1) % c->bufsize; + if (ch == '\n') + break; + line[n] = ch; + } + + if (n > 0) { + if (line[n - 1] == '\r') + n--; + line[n] = 0; + pr_debug("CONSOLE: %s\n", line); + } + } +break2: + + return 0; +} +#endif /* DEBUG */ + +static int brcmf_tx_frame(struct brcmf_sdio *bus, u8 *frame, u16 len) +{ + int i; + int ret; + + bus->ctrl_frame_stat = false; + ret = brcmf_sdcard_send_buf(bus->sdiodev, bus->sdiodev->sbwad, + SDIO_FUNC_2, F2SYNC, frame, len); + + if (ret < 0) { + /* On failure, abort the command and terminate the frame */ + brcmf_dbg(INFO, "sdio error %d, abort command and terminate frame\n", + ret); + bus->sdcnt.tx_sderrs++; + + brcmf_sdcard_abort(bus->sdiodev, SDIO_FUNC_2); + + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_FUNC1_FRAMECTRL, + SFC_WF_TERM, NULL); + bus->sdcnt.f1regdata++; + + for (i = 0; i < 3; i++) { + u8 hi, lo; + hi = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_FUNC1_WFRAMEBCHI, NULL); + lo = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_FUNC1_WFRAMEBCLO, NULL); + bus->sdcnt.f1regdata += 2; + if (hi == 0 && lo == 0) + break; + } + return ret; + } + + bus->tx_seq = (bus->tx_seq + 1) % SDPCM_SEQUENCE_WRAP; + + return ret; +} + +static int +brcmf_sdbrcm_bus_txctl(struct device *dev, unsigned char *msg, uint msglen) +{ + u8 *frame; + u16 len; + u32 swheader; + uint retries = 0; + u8 doff = 0; + int ret = -1; + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; + struct brcmf_sdio *bus = sdiodev->bus; + unsigned long flags; + + brcmf_dbg(TRACE, "Enter\n"); + + /* Back the pointer to make a room for bus header */ + frame = msg - SDPCM_HDRLEN; + len = (msglen += SDPCM_HDRLEN); + + /* Add alignment padding (optional for ctl frames) */ + doff = ((unsigned long)frame % BRCMF_SDALIGN); + if (doff) { + frame -= doff; + len += doff; + msglen += doff; + memset(frame, 0, doff + SDPCM_HDRLEN); + } + /* precondition: doff < BRCMF_SDALIGN */ + doff += SDPCM_HDRLEN; + + /* Round send length to next SDIO block */ + if (bus->roundup && bus->blocksize && (len > bus->blocksize)) { + u16 pad = bus->blocksize - (len % bus->blocksize); + if ((pad <= bus->roundup) && (pad < bus->blocksize)) + len += pad; + } else if (len % BRCMF_SDALIGN) { + len += BRCMF_SDALIGN - (len % BRCMF_SDALIGN); + } + + /* Satisfy length-alignment requirements */ + if (len & (ALIGNMENT - 1)) + len = roundup(len, ALIGNMENT); + + /* precondition: IS_ALIGNED((unsigned long)frame, 2) */ + + /* Make sure backplane clock is on */ + sdio_claim_host(bus->sdiodev->func[1]); + brcmf_sdbrcm_bus_sleep(bus, false, false); + sdio_release_host(bus->sdiodev->func[1]); + + /* Hardware tag: 2 byte len followed by 2 byte ~len check (all LE) */ + *(__le16 *) frame = cpu_to_le16((u16) msglen); + *(((__le16 *) frame) + 1) = cpu_to_le16(~msglen); + + /* Software tag: channel, sequence number, data offset */ + swheader = + ((SDPCM_CONTROL_CHANNEL << SDPCM_CHANNEL_SHIFT) & + SDPCM_CHANNEL_MASK) + | bus->tx_seq | ((doff << SDPCM_DOFFSET_SHIFT) & + SDPCM_DOFFSET_MASK); + put_unaligned_le32(swheader, frame + SDPCM_FRAMETAG_LEN); + put_unaligned_le32(0, frame + SDPCM_FRAMETAG_LEN + sizeof(swheader)); + + if (!data_ok(bus)) { + brcmf_dbg(INFO, "No bus credit bus->tx_max %d, bus->tx_seq %d\n", + bus->tx_max, bus->tx_seq); + bus->ctrl_frame_stat = true; + /* Send from dpc */ + bus->ctrl_frame_buf = frame; + bus->ctrl_frame_len = len; + + wait_event_interruptible_timeout(bus->ctrl_wait, + !bus->ctrl_frame_stat, + msecs_to_jiffies(2000)); + + if (!bus->ctrl_frame_stat) { + brcmf_dbg(SDIO, "ctrl_frame_stat == false\n"); + ret = 0; + } else { + brcmf_dbg(SDIO, "ctrl_frame_stat == true\n"); + ret = -1; + } + } + + if (ret == -1) { + brcmf_dbg_hex_dump(BRCMF_BYTES_ON() && BRCMF_CTL_ON(), + frame, len, "Tx Frame:\n"); + brcmf_dbg_hex_dump(!(BRCMF_BYTES_ON() && BRCMF_CTL_ON()) && + BRCMF_HDRS_ON(), + frame, min_t(u16, len, 16), "TxHdr:\n"); + + do { + sdio_claim_host(bus->sdiodev->func[1]); + ret = brcmf_tx_frame(bus, frame, len); + sdio_release_host(bus->sdiodev->func[1]); + } while (ret < 0 && retries++ < TXRETRIES); + } + + spin_lock_irqsave(&bus->dpc_tl_lock, flags); + if ((bus->idletime == BRCMF_IDLE_IMMEDIATE) && + list_empty(&bus->dpc_tsklst)) { + spin_unlock_irqrestore(&bus->dpc_tl_lock, flags); + + bus->activity = false; + sdio_claim_host(bus->sdiodev->func[1]); + brcmf_dbg(INFO, "idle\n"); + brcmf_sdbrcm_clkctl(bus, CLK_NONE, true); + sdio_release_host(bus->sdiodev->func[1]); + } else { + spin_unlock_irqrestore(&bus->dpc_tl_lock, flags); + } + + if (ret) + bus->sdcnt.tx_ctlerrs++; + else + bus->sdcnt.tx_ctlpkts++; + + return ret ? -EIO : 0; +} + +#ifdef DEBUG +static inline bool brcmf_sdio_valid_shared_address(u32 addr) +{ + return !(addr == 0 || ((~addr >> 16) & 0xffff) == (addr & 0xffff)); +} + +static int brcmf_sdio_readshared(struct brcmf_sdio *bus, + struct sdpcm_shared *sh) +{ + u32 addr; + int rv; + u32 shaddr = 0; + struct sdpcm_shared_le sh_le; + __le32 addr_le; + + shaddr = bus->ci->rambase + bus->ramsize - 4; + + /* + * Read last word in socram to determine + * address of sdpcm_shared structure + */ + sdio_claim_host(bus->sdiodev->func[1]); + brcmf_sdbrcm_bus_sleep(bus, false, false); + rv = brcmf_sdio_ramrw(bus->sdiodev, false, shaddr, (u8 *)&addr_le, 4); + sdio_release_host(bus->sdiodev->func[1]); + if (rv < 0) + return rv; + + addr = le32_to_cpu(addr_le); + + brcmf_dbg(SDIO, "sdpcm_shared address 0x%08X\n", addr); + + /* + * Check if addr is valid. + * NVRAM length at the end of memory should have been overwritten. + */ + if (!brcmf_sdio_valid_shared_address(addr)) { + brcmf_err("invalid sdpcm_shared address 0x%08X\n", + addr); + return -EINVAL; + } + + /* Read hndrte_shared structure */ + rv = brcmf_sdio_ramrw(bus->sdiodev, false, addr, (u8 *)&sh_le, + sizeof(struct sdpcm_shared_le)); + if (rv < 0) + return rv; + + /* Endianness */ + sh->flags = le32_to_cpu(sh_le.flags); + sh->trap_addr = le32_to_cpu(sh_le.trap_addr); + sh->assert_exp_addr = le32_to_cpu(sh_le.assert_exp_addr); + sh->assert_file_addr = le32_to_cpu(sh_le.assert_file_addr); + sh->assert_line = le32_to_cpu(sh_le.assert_line); + sh->console_addr = le32_to_cpu(sh_le.console_addr); + sh->msgtrace_addr = le32_to_cpu(sh_le.msgtrace_addr); + + if ((sh->flags & SDPCM_SHARED_VERSION_MASK) > SDPCM_SHARED_VERSION) { + brcmf_err("sdpcm shared version unsupported: dhd %d dongle %d\n", + SDPCM_SHARED_VERSION, + sh->flags & SDPCM_SHARED_VERSION_MASK); + return -EPROTO; + } + + return 0; +} + +static int brcmf_sdio_dump_console(struct brcmf_sdio *bus, + struct sdpcm_shared *sh, char __user *data, + size_t count) +{ + u32 addr, console_ptr, console_size, console_index; + char *conbuf = NULL; + __le32 sh_val; + int rv; + loff_t pos = 0; + int nbytes = 0; + + /* obtain console information from device memory */ + addr = sh->console_addr + offsetof(struct rte_console, log_le); + rv = brcmf_sdio_ramrw(bus->sdiodev, false, addr, + (u8 *)&sh_val, sizeof(u32)); + if (rv < 0) + return rv; + console_ptr = le32_to_cpu(sh_val); + + addr = sh->console_addr + offsetof(struct rte_console, log_le.buf_size); + rv = brcmf_sdio_ramrw(bus->sdiodev, false, addr, + (u8 *)&sh_val, sizeof(u32)); + if (rv < 0) + return rv; + console_size = le32_to_cpu(sh_val); + + addr = sh->console_addr + offsetof(struct rte_console, log_le.idx); + rv = brcmf_sdio_ramrw(bus->sdiodev, false, addr, + (u8 *)&sh_val, sizeof(u32)); + if (rv < 0) + return rv; + console_index = le32_to_cpu(sh_val); + + /* allocate buffer for console data */ + if (console_size <= CONSOLE_BUFFER_MAX) + conbuf = vzalloc(console_size+1); + + if (!conbuf) + return -ENOMEM; + + /* obtain the console data from device */ + conbuf[console_size] = '\0'; + rv = brcmf_sdio_ramrw(bus->sdiodev, false, console_ptr, (u8 *)conbuf, + console_size); + if (rv < 0) + goto done; + + rv = simple_read_from_buffer(data, count, &pos, + conbuf + console_index, + console_size - console_index); + if (rv < 0) + goto done; + + nbytes = rv; + if (console_index > 0) { + pos = 0; + rv = simple_read_from_buffer(data+nbytes, count, &pos, + conbuf, console_index - 1); + if (rv < 0) + goto done; + rv += nbytes; + } +done: + vfree(conbuf); + return rv; +} + +static int brcmf_sdio_trap_info(struct brcmf_sdio *bus, struct sdpcm_shared *sh, + char __user *data, size_t count) +{ + int error, res; + char buf[350]; + struct brcmf_trap_info tr; + loff_t pos = 0; + + if ((sh->flags & SDPCM_SHARED_TRAP) == 0) { + brcmf_dbg(INFO, "no trap in firmware\n"); + return 0; + } + + error = brcmf_sdio_ramrw(bus->sdiodev, false, sh->trap_addr, (u8 *)&tr, + sizeof(struct brcmf_trap_info)); + if (error < 0) + return error; + + res = scnprintf(buf, sizeof(buf), + "dongle trap info: type 0x%x @ epc 0x%08x\n" + " cpsr 0x%08x spsr 0x%08x sp 0x%08x\n" + " lr 0x%08x pc 0x%08x offset 0x%x\n" + " r0 0x%08x r1 0x%08x r2 0x%08x r3 0x%08x\n" + " r4 0x%08x r5 0x%08x r6 0x%08x r7 0x%08x\n", + le32_to_cpu(tr.type), le32_to_cpu(tr.epc), + le32_to_cpu(tr.cpsr), le32_to_cpu(tr.spsr), + le32_to_cpu(tr.r13), le32_to_cpu(tr.r14), + le32_to_cpu(tr.pc), sh->trap_addr, + le32_to_cpu(tr.r0), le32_to_cpu(tr.r1), + le32_to_cpu(tr.r2), le32_to_cpu(tr.r3), + le32_to_cpu(tr.r4), le32_to_cpu(tr.r5), + le32_to_cpu(tr.r6), le32_to_cpu(tr.r7)); + + return simple_read_from_buffer(data, count, &pos, buf, res); +} + +static int brcmf_sdio_assert_info(struct brcmf_sdio *bus, + struct sdpcm_shared *sh, char __user *data, + size_t count) +{ + int error = 0; + char buf[200]; + char file[80] = "?"; + char expr[80] = ""; + int res; + loff_t pos = 0; + + if ((sh->flags & SDPCM_SHARED_ASSERT_BUILT) == 0) { + brcmf_dbg(INFO, "firmware not built with -assert\n"); + return 0; + } else if ((sh->flags & SDPCM_SHARED_ASSERT) == 0) { + brcmf_dbg(INFO, "no assert in dongle\n"); + return 0; + } + + sdio_claim_host(bus->sdiodev->func[1]); + if (sh->assert_file_addr != 0) { + error = brcmf_sdio_ramrw(bus->sdiodev, false, + sh->assert_file_addr, (u8 *)file, 80); + if (error < 0) + return error; + } + if (sh->assert_exp_addr != 0) { + error = brcmf_sdio_ramrw(bus->sdiodev, false, + sh->assert_exp_addr, (u8 *)expr, 80); + if (error < 0) + return error; + } + sdio_release_host(bus->sdiodev->func[1]); + + res = scnprintf(buf, sizeof(buf), + "dongle assert: %s:%d: assert(%s)\n", + file, sh->assert_line, expr); + return simple_read_from_buffer(data, count, &pos, buf, res); +} + +static int brcmf_sdbrcm_checkdied(struct brcmf_sdio *bus) +{ + int error; + struct sdpcm_shared sh; + + error = brcmf_sdio_readshared(bus, &sh); + + if (error < 0) + return error; + + if ((sh.flags & SDPCM_SHARED_ASSERT_BUILT) == 0) + brcmf_dbg(INFO, "firmware not built with -assert\n"); + else if (sh.flags & SDPCM_SHARED_ASSERT) + brcmf_err("assertion in dongle\n"); + + if (sh.flags & SDPCM_SHARED_TRAP) + brcmf_err("firmware trap in dongle\n"); + + return 0; +} + +static int brcmf_sdbrcm_died_dump(struct brcmf_sdio *bus, char __user *data, + size_t count, loff_t *ppos) +{ + int error = 0; + struct sdpcm_shared sh; + int nbytes = 0; + loff_t pos = *ppos; + + if (pos != 0) + return 0; + + error = brcmf_sdio_readshared(bus, &sh); + if (error < 0) + goto done; + + error = brcmf_sdio_assert_info(bus, &sh, data, count); + if (error < 0) + goto done; + nbytes = error; + + error = brcmf_sdio_trap_info(bus, &sh, data+nbytes, count); + if (error < 0) + goto done; + nbytes += error; + + error = brcmf_sdio_dump_console(bus, &sh, data+nbytes, count); + if (error < 0) + goto done; + nbytes += error; + + error = nbytes; + *ppos += nbytes; +done: + return error; +} + +static ssize_t brcmf_sdio_forensic_read(struct file *f, char __user *data, + size_t count, loff_t *ppos) +{ + struct brcmf_sdio *bus = f->private_data; + int res; + + res = brcmf_sdbrcm_died_dump(bus, data, count, ppos); + if (res > 0) + *ppos += res; + return (ssize_t)res; +} + +static const struct file_operations brcmf_sdio_forensic_ops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = brcmf_sdio_forensic_read +}; + +static void brcmf_sdio_debugfs_create(struct brcmf_sdio *bus) +{ + struct brcmf_pub *drvr = bus->sdiodev->bus_if->drvr; + struct dentry *dentry = brcmf_debugfs_get_devdir(drvr); + + if (IS_ERR_OR_NULL(dentry)) + return; + + debugfs_create_file("forensics", S_IRUGO, dentry, bus, + &brcmf_sdio_forensic_ops); + brcmf_debugfs_create_sdio_count(drvr, &bus->sdcnt); +} +#else +static int brcmf_sdbrcm_checkdied(struct brcmf_sdio *bus) +{ + return 0; +} + +static void brcmf_sdio_debugfs_create(struct brcmf_sdio *bus) +{ +} +#endif /* DEBUG */ + +static int +brcmf_sdbrcm_bus_rxctl(struct device *dev, unsigned char *msg, uint msglen) +{ + int timeleft; + uint rxlen = 0; + bool pending; + u8 *buf; + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; + struct brcmf_sdio *bus = sdiodev->bus; + + brcmf_dbg(TRACE, "Enter\n"); + + /* Wait until control frame is available */ + timeleft = brcmf_sdbrcm_dcmd_resp_wait(bus, &bus->rxlen, &pending); + + spin_lock_bh(&bus->rxctl_lock); + rxlen = bus->rxlen; + memcpy(msg, bus->rxctl, min(msglen, rxlen)); + bus->rxctl = NULL; + buf = bus->rxctl_orig; + bus->rxctl_orig = NULL; + bus->rxlen = 0; + spin_unlock_bh(&bus->rxctl_lock); + vfree(buf); + + if (rxlen) { + brcmf_dbg(CTL, "resumed on rxctl frame, got %d expected %d\n", + rxlen, msglen); + } else if (timeleft == 0) { + brcmf_err("resumed on timeout\n"); + brcmf_sdbrcm_checkdied(bus); + } else if (pending) { + brcmf_dbg(CTL, "cancelled\n"); + return -ERESTARTSYS; + } else { + brcmf_dbg(CTL, "resumed for unknown reason?\n"); + brcmf_sdbrcm_checkdied(bus); + } + + if (rxlen) + bus->sdcnt.rx_ctlpkts++; + else + bus->sdcnt.rx_ctlerrs++; + + return rxlen ? (int)rxlen : -ETIMEDOUT; +} + +static bool brcmf_sdbrcm_download_state(struct brcmf_sdio *bus, bool enter) +{ + struct chip_info *ci = bus->ci; + + /* To enter download state, disable ARM and reset SOCRAM. + * To exit download state, simply reset ARM (default is RAM boot). + */ + if (enter) { + bus->alp_only = true; + + brcmf_sdio_chip_enter_download(bus->sdiodev, ci); + } else { + if (!brcmf_sdio_chip_exit_download(bus->sdiodev, ci, bus->vars, + bus->varsz)) + return false; + + /* Allow HT Clock now that the ARM is running. */ + bus->alp_only = false; + + bus->sdiodev->bus_if->state = BRCMF_BUS_LOAD; + } + + return true; +} + +static int brcmf_sdbrcm_get_image(char *buf, int len, struct brcmf_sdio *bus) +{ + if (bus->firmware->size < bus->fw_ptr + len) + len = bus->firmware->size - bus->fw_ptr; + + memcpy(buf, &bus->firmware->data[bus->fw_ptr], len); + bus->fw_ptr += len; + return len; +} + +static int brcmf_sdbrcm_download_code_file(struct brcmf_sdio *bus) +{ + int offset; + uint len; + u8 *memblock = NULL, *memptr; + int ret; + u8 idx; + + brcmf_dbg(INFO, "Enter\n"); + + ret = request_firmware(&bus->firmware, BRCMF_SDIO_FW_NAME, + &bus->sdiodev->func[2]->dev); + if (ret) { + brcmf_err("Fail to request firmware %d\n", ret); + return ret; + } + bus->fw_ptr = 0; + + memptr = memblock = kmalloc(MEMBLOCK + BRCMF_SDALIGN, GFP_ATOMIC); + if (memblock == NULL) { + ret = -ENOMEM; + goto err; + } + if ((u32)(unsigned long)memblock % BRCMF_SDALIGN) + memptr += (BRCMF_SDALIGN - + ((u32)(unsigned long)memblock % BRCMF_SDALIGN)); + + offset = bus->ci->rambase; + + /* Download image */ + len = brcmf_sdbrcm_get_image((char *)memptr, MEMBLOCK, bus); + idx = brcmf_sdio_chip_getinfidx(bus->ci, BCMA_CORE_ARM_CR4); + if (BRCMF_MAX_CORENUM != idx) + memcpy(&bus->ci->rst_vec, memptr, sizeof(bus->ci->rst_vec)); + while (len) { + ret = brcmf_sdio_ramrw(bus->sdiodev, true, offset, memptr, len); + if (ret) { + brcmf_err("error %d on writing %d membytes at 0x%08x\n", + ret, MEMBLOCK, offset); + goto err; + } + + offset += MEMBLOCK; + len = brcmf_sdbrcm_get_image((char *)memptr, MEMBLOCK, bus); + } + +err: + kfree(memblock); + + release_firmware(bus->firmware); + bus->fw_ptr = 0; + + return ret; +} + +/* + * ProcessVars:Takes a buffer of "=\n" lines read from a file + * and ending in a NUL. + * Removes carriage returns, empty lines, comment lines, and converts + * newlines to NULs. + * Shortens buffer as needed and pads with NULs. End of buffer is marked + * by two NULs. +*/ + +static int brcmf_process_nvram_vars(struct brcmf_sdio *bus) +{ + char *varbuf; + char *dp; + bool findNewline; + int column; + int ret = 0; + uint buf_len, n, len; + + len = bus->firmware->size; + varbuf = vmalloc(len); + if (!varbuf) + return -ENOMEM; + + memcpy(varbuf, bus->firmware->data, len); + dp = varbuf; + + findNewline = false; + column = 0; + + for (n = 0; n < len; n++) { + if (varbuf[n] == 0) + break; + if (varbuf[n] == '\r') + continue; + if (findNewline && varbuf[n] != '\n') + continue; + findNewline = false; + if (varbuf[n] == '#') { + findNewline = true; + continue; + } + if (varbuf[n] == '\n') { + if (column == 0) + continue; + *dp++ = 0; + column = 0; + continue; + } + *dp++ = varbuf[n]; + column++; + } + buf_len = dp - varbuf; + while (dp < varbuf + n) + *dp++ = 0; + + kfree(bus->vars); + /* roundup needed for download to device */ + bus->varsz = roundup(buf_len + 1, 4); + bus->vars = kmalloc(bus->varsz, GFP_KERNEL); + if (bus->vars == NULL) { + bus->varsz = 0; + ret = -ENOMEM; + goto err; + } + + /* copy the processed variables and add null termination */ + memcpy(bus->vars, varbuf, buf_len); + bus->vars[buf_len] = 0; +err: + vfree(varbuf); + return ret; +} + +static int brcmf_sdbrcm_download_nvram(struct brcmf_sdio *bus) +{ + int ret; + + ret = request_firmware(&bus->firmware, BRCMF_SDIO_NV_NAME, + &bus->sdiodev->func[2]->dev); + if (ret) { + brcmf_err("Fail to request nvram %d\n", ret); + return ret; + } + + ret = brcmf_process_nvram_vars(bus); + + release_firmware(bus->firmware); + + return ret; +} + +static int _brcmf_sdbrcm_download_firmware(struct brcmf_sdio *bus) +{ + int bcmerror = -1; + + /* Keep arm in reset */ + if (!brcmf_sdbrcm_download_state(bus, true)) { + brcmf_err("error placing ARM core in reset\n"); + goto err; + } + + if (brcmf_sdbrcm_download_code_file(bus)) { + brcmf_err("dongle image file download failed\n"); + goto err; + } + + if (brcmf_sdbrcm_download_nvram(bus)) { + brcmf_err("dongle nvram file download failed\n"); + goto err; + } + + /* Take arm out of reset */ + if (!brcmf_sdbrcm_download_state(bus, false)) { + brcmf_err("error getting out of ARM core reset\n"); + goto err; + } + + bcmerror = 0; + +err: + return bcmerror; +} + +static bool brcmf_sdbrcm_sr_capable(struct brcmf_sdio *bus) +{ + u32 addr, reg; + + brcmf_dbg(TRACE, "Enter\n"); + + /* old chips with PMU version less than 17 don't support save restore */ + if (bus->ci->pmurev < 17) + return false; + + /* read PMU chipcontrol register 3*/ + addr = CORE_CC_REG(bus->ci->c_inf[0].base, chipcontrol_addr); + brcmf_sdio_regwl(bus->sdiodev, addr, 3, NULL); + addr = CORE_CC_REG(bus->ci->c_inf[0].base, chipcontrol_data); + reg = brcmf_sdio_regrl(bus->sdiodev, addr, NULL); + + return (bool)reg; +} + +static void brcmf_sdbrcm_sr_init(struct brcmf_sdio *bus) +{ + int err = 0; + u8 val; + + brcmf_dbg(TRACE, "Enter\n"); + + val = brcmf_sdio_regrb(bus->sdiodev, SBSDIO_FUNC1_WAKEUPCTRL, + &err); + if (err) { + brcmf_err("error reading SBSDIO_FUNC1_WAKEUPCTRL\n"); + return; + } + + val |= 1 << SBSDIO_FUNC1_WCTRL_HTWAIT_SHIFT; + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_FUNC1_WAKEUPCTRL, + val, &err); + if (err) { + brcmf_err("error writing SBSDIO_FUNC1_WAKEUPCTRL\n"); + return; + } + + /* Add CMD14 Support */ + brcmf_sdio_regwb(bus->sdiodev, SDIO_CCCR_BRCM_CARDCAP, + (SDIO_CCCR_BRCM_CARDCAP_CMD14_SUPPORT | + SDIO_CCCR_BRCM_CARDCAP_CMD14_EXT), + &err); + if (err) { + brcmf_err("error writing SDIO_CCCR_BRCM_CARDCAP\n"); + return; + } + + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, + SBSDIO_FORCE_HT, &err); + if (err) { + brcmf_err("error writing SBSDIO_FUNC1_CHIPCLKCSR\n"); + return; + } + + /* set flag */ + bus->sr_enabled = true; + brcmf_dbg(INFO, "SR enabled\n"); +} + +/* enable KSO bit */ +static int brcmf_sdbrcm_kso_init(struct brcmf_sdio *bus) +{ + u8 val; + int err = 0; + + brcmf_dbg(TRACE, "Enter\n"); + + /* KSO bit added in SDIO core rev 12 */ + if (bus->ci->c_inf[1].rev < 12) + return 0; + + val = brcmf_sdio_regrb(bus->sdiodev, SBSDIO_FUNC1_SLEEPCSR, + &err); + if (err) { + brcmf_err("error reading SBSDIO_FUNC1_SLEEPCSR\n"); + return err; + } + + if (!(val & SBSDIO_FUNC1_SLEEPCSR_KSO_MASK)) { + val |= (SBSDIO_FUNC1_SLEEPCSR_KSO_EN << + SBSDIO_FUNC1_SLEEPCSR_KSO_SHIFT); + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_FUNC1_SLEEPCSR, + val, &err); + if (err) { + brcmf_err("error writing SBSDIO_FUNC1_SLEEPCSR\n"); + return err; + } + } + + return 0; +} + + +static bool +brcmf_sdbrcm_download_firmware(struct brcmf_sdio *bus) +{ + bool ret; + + sdio_claim_host(bus->sdiodev->func[1]); + + brcmf_sdbrcm_clkctl(bus, CLK_AVAIL, false); + + ret = _brcmf_sdbrcm_download_firmware(bus) == 0; + + brcmf_sdbrcm_clkctl(bus, CLK_SDONLY, false); + + sdio_release_host(bus->sdiodev->func[1]); + + return ret; +} + +static int brcmf_sdbrcm_bus_init(struct device *dev) +{ + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio; + struct brcmf_sdio *bus = sdiodev->bus; + unsigned long timeout; + u8 ready, enable; + int err, ret = 0; + u8 saveclk; + + brcmf_dbg(TRACE, "Enter\n"); + + /* try to download image and nvram to the dongle */ + if (bus_if->state == BRCMF_BUS_DOWN) { + if (!(brcmf_sdbrcm_download_firmware(bus))) + return -1; + } + + if (!bus->sdiodev->bus_if->drvr) + return 0; + + /* Start the watchdog timer */ + bus->sdcnt.tickcnt = 0; + brcmf_sdbrcm_wd_timer(bus, BRCMF_WD_POLL_MS); + + sdio_claim_host(bus->sdiodev->func[1]); + + /* Make sure backplane clock is on, needed to generate F2 interrupt */ + brcmf_sdbrcm_clkctl(bus, CLK_AVAIL, false); + if (bus->clkstate != CLK_AVAIL) + goto exit; + + /* Force clocks on backplane to be sure F2 interrupt propagates */ + saveclk = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_FUNC1_CHIPCLKCSR, &err); + if (!err) { + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, + (saveclk | SBSDIO_FORCE_HT), &err); + } + if (err) { + brcmf_err("Failed to force clock for F2: err %d\n", err); + goto exit; + } + + /* Enable function 2 (frame transfers) */ + w_sdreg32(bus, SDPCM_PROT_VERSION << SMB_DATA_VERSION_SHIFT, + offsetof(struct sdpcmd_regs, tosbmailboxdata)); + enable = (SDIO_FUNC_ENABLE_1 | SDIO_FUNC_ENABLE_2); + + brcmf_sdio_regwb(bus->sdiodev, SDIO_CCCR_IOEx, enable, NULL); + + timeout = jiffies + msecs_to_jiffies(BRCMF_WAIT_F2RDY); + ready = 0; + while (enable != ready) { + ready = brcmf_sdio_regrb(bus->sdiodev, + SDIO_CCCR_IORx, NULL); + if (time_after(jiffies, timeout)) + break; + else if (time_after(jiffies, timeout - BRCMF_WAIT_F2RDY + 50)) + /* prevent busy waiting if it takes too long */ + msleep_interruptible(20); + } + + brcmf_dbg(INFO, "enable 0x%02x, ready 0x%02x\n", enable, ready); + + /* If F2 successfully enabled, set core and enable interrupts */ + if (ready == enable) { + /* Set up the interrupt mask and enable interrupts */ + bus->hostintmask = HOSTINTMASK; + w_sdreg32(bus, bus->hostintmask, + offsetof(struct sdpcmd_regs, hostintmask)); + + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_WATERMARK, 8, &err); + } else { + /* Disable F2 again */ + enable = SDIO_FUNC_ENABLE_1; + brcmf_sdio_regwb(bus->sdiodev, SDIO_CCCR_IOEx, enable, NULL); + ret = -ENODEV; + } + + if (brcmf_sdbrcm_sr_capable(bus)) { + brcmf_sdbrcm_sr_init(bus); + } else { + /* Restore previous clock setting */ + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, + saveclk, &err); + } + + if (ret == 0) { + ret = brcmf_sdio_intr_register(bus->sdiodev); + if (ret != 0) + brcmf_err("intr register failed:%d\n", ret); + } + + /* If we didn't come up, turn off backplane clock */ + if (bus_if->state != BRCMF_BUS_DATA) + brcmf_sdbrcm_clkctl(bus, CLK_NONE, false); + +exit: + sdio_release_host(bus->sdiodev->func[1]); + + return ret; +} + +void brcmf_sdbrcm_isr(void *arg) +{ + struct brcmf_sdio *bus = (struct brcmf_sdio *) arg; + + brcmf_dbg(TRACE, "Enter\n"); + + if (!bus) { + brcmf_err("bus is null pointer, exiting\n"); + return; + } + + if (bus->sdiodev->bus_if->state == BRCMF_BUS_DOWN) { + brcmf_err("bus is down. we have nothing to do\n"); + return; + } + /* Count the interrupt call */ + bus->sdcnt.intrcount++; + if (in_interrupt()) + atomic_set(&bus->ipend, 1); + else + if (brcmf_sdio_intr_rstatus(bus)) { + brcmf_err("failed backplane access\n"); + bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN; + } + + /* Disable additional interrupts (is this needed now)? */ + if (!bus->intr) + brcmf_err("isr w/o interrupt configured!\n"); + + brcmf_sdbrcm_adddpctsk(bus); + queue_work(bus->brcmf_wq, &bus->datawork); +} + +static bool brcmf_sdbrcm_bus_watchdog(struct brcmf_sdio *bus) +{ +#ifdef DEBUG + struct brcmf_bus *bus_if = dev_get_drvdata(bus->sdiodev->dev); +#endif /* DEBUG */ + unsigned long flags; + + brcmf_dbg(TIMER, "Enter\n"); + + /* Poll period: check device if appropriate. */ + if (!bus->sr_enabled && + bus->poll && (++bus->polltick >= bus->pollrate)) { + u32 intstatus = 0; + + /* Reset poll tick */ + bus->polltick = 0; + + /* Check device if no interrupts */ + if (!bus->intr || + (bus->sdcnt.intrcount == bus->sdcnt.lastintrs)) { + + spin_lock_irqsave(&bus->dpc_tl_lock, flags); + if (list_empty(&bus->dpc_tsklst)) { + u8 devpend; + spin_unlock_irqrestore(&bus->dpc_tl_lock, + flags); + sdio_claim_host(bus->sdiodev->func[1]); + devpend = brcmf_sdio_regrb(bus->sdiodev, + SDIO_CCCR_INTx, + NULL); + sdio_release_host(bus->sdiodev->func[1]); + intstatus = + devpend & (INTR_STATUS_FUNC1 | + INTR_STATUS_FUNC2); + } else { + spin_unlock_irqrestore(&bus->dpc_tl_lock, + flags); + } + + /* If there is something, make like the ISR and + schedule the DPC */ + if (intstatus) { + bus->sdcnt.pollcnt++; + atomic_set(&bus->ipend, 1); + + brcmf_sdbrcm_adddpctsk(bus); + queue_work(bus->brcmf_wq, &bus->datawork); + } + } + + /* Update interrupt tracking */ + bus->sdcnt.lastintrs = bus->sdcnt.intrcount; + } +#ifdef DEBUG + /* Poll for console output periodically */ + if (bus_if && bus_if->state == BRCMF_BUS_DATA && + bus->console_interval != 0) { + bus->console.count += BRCMF_WD_POLL_MS; + if (bus->console.count >= bus->console_interval) { + bus->console.count -= bus->console_interval; + sdio_claim_host(bus->sdiodev->func[1]); + /* Make sure backplane clock is on */ + brcmf_sdbrcm_bus_sleep(bus, false, false); + if (brcmf_sdbrcm_readconsole(bus) < 0) + /* stop on error */ + bus->console_interval = 0; + sdio_release_host(bus->sdiodev->func[1]); + } + } +#endif /* DEBUG */ + + /* On idle timeout clear activity flag and/or turn off clock */ + if ((bus->idletime > 0) && (bus->clkstate == CLK_AVAIL)) { + if (++bus->idlecount >= bus->idletime) { + bus->idlecount = 0; + if (bus->activity) { + bus->activity = false; + brcmf_sdbrcm_wd_timer(bus, BRCMF_WD_POLL_MS); + } else { + brcmf_dbg(SDIO, "idle\n"); + sdio_claim_host(bus->sdiodev->func[1]); + brcmf_sdbrcm_bus_sleep(bus, true, false); + sdio_release_host(bus->sdiodev->func[1]); + } + } + } + + return (atomic_read(&bus->ipend) > 0); +} + +static bool brcmf_sdbrcm_chipmatch(u16 chipid) +{ + if (chipid == BCM43143_CHIP_ID) + return true; + if (chipid == BCM43241_CHIP_ID) + return true; + if (chipid == BCM4329_CHIP_ID) + return true; + if (chipid == BCM4330_CHIP_ID) + return true; + if (chipid == BCM4334_CHIP_ID) + return true; + if (chipid == BCM4335_CHIP_ID) + return true; + return false; +} + +static void brcmf_sdio_dataworker(struct work_struct *work) +{ + struct brcmf_sdio *bus = container_of(work, struct brcmf_sdio, + datawork); + struct list_head *cur_hd, *tmp_hd; + unsigned long flags; + + spin_lock_irqsave(&bus->dpc_tl_lock, flags); + list_for_each_safe(cur_hd, tmp_hd, &bus->dpc_tsklst) { + spin_unlock_irqrestore(&bus->dpc_tl_lock, flags); + + brcmf_sdbrcm_dpc(bus); + + spin_lock_irqsave(&bus->dpc_tl_lock, flags); + list_del(cur_hd); + kfree(cur_hd); + } + spin_unlock_irqrestore(&bus->dpc_tl_lock, flags); +} + +static void brcmf_sdbrcm_release_malloc(struct brcmf_sdio *bus) +{ + brcmf_dbg(TRACE, "Enter\n"); + + kfree(bus->rxbuf); + bus->rxctl = bus->rxbuf = NULL; + bus->rxlen = 0; + + kfree(bus->databuf); + bus->databuf = NULL; +} + +static bool brcmf_sdbrcm_probe_malloc(struct brcmf_sdio *bus) +{ + brcmf_dbg(TRACE, "Enter\n"); + + if (bus->sdiodev->bus_if->maxctl) { + bus->rxblen = + roundup((bus->sdiodev->bus_if->maxctl + SDPCM_HDRLEN), + ALIGNMENT) + BRCMF_SDALIGN; + bus->rxbuf = kmalloc(bus->rxblen, GFP_ATOMIC); + if (!(bus->rxbuf)) + goto fail; + } + + /* Allocate buffer to receive glomed packet */ + bus->databuf = kmalloc(MAX_DATA_BUF, GFP_ATOMIC); + if (!(bus->databuf)) { + /* release rxbuf which was already located as above */ + if (!bus->rxblen) + kfree(bus->rxbuf); + goto fail; + } + + /* Align the buffer */ + if ((unsigned long)bus->databuf % BRCMF_SDALIGN) + bus->dataptr = bus->databuf + (BRCMF_SDALIGN - + ((unsigned long)bus->databuf % BRCMF_SDALIGN)); + else + bus->dataptr = bus->databuf; + + return true; + +fail: + return false; +} + +static bool +brcmf_sdbrcm_probe_attach(struct brcmf_sdio *bus, u32 regsva) +{ + u8 clkctl = 0; + int err = 0; + int reg_addr; + u32 reg_val; + u32 drivestrength; + + bus->alp_only = true; + + sdio_claim_host(bus->sdiodev->func[1]); + + pr_debug("F1 signature read @0x18000000=0x%4x\n", + brcmf_sdio_regrl(bus->sdiodev, SI_ENUM_BASE, NULL)); + + /* + * Force PLL off until brcmf_sdio_chip_attach() + * programs PLL control regs + */ + + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, + BRCMF_INIT_CLKCTL1, &err); + if (!err) + clkctl = brcmf_sdio_regrb(bus->sdiodev, + SBSDIO_FUNC1_CHIPCLKCSR, &err); + + if (err || ((clkctl & ~SBSDIO_AVBITS) != BRCMF_INIT_CLKCTL1)) { + brcmf_err("ChipClkCSR access: err %d wrote 0x%02x read 0x%02x\n", + err, BRCMF_INIT_CLKCTL1, clkctl); + goto fail; + } + + if (brcmf_sdio_chip_attach(bus->sdiodev, &bus->ci, regsva)) { + brcmf_err("brcmf_sdio_chip_attach failed!\n"); + goto fail; + } + + if (!brcmf_sdbrcm_chipmatch((u16) bus->ci->chip)) { + brcmf_err("unsupported chip: 0x%04x\n", bus->ci->chip); + goto fail; + } + + if (brcmf_sdbrcm_kso_init(bus)) { + brcmf_err("error enabling KSO\n"); + goto fail; + } + + if ((bus->sdiodev->pdata) && (bus->sdiodev->pdata->drive_strength)) + drivestrength = bus->sdiodev->pdata->drive_strength; + else + drivestrength = DEFAULT_SDIO_DRIVE_STRENGTH; + brcmf_sdio_chip_drivestrengthinit(bus->sdiodev, bus->ci, drivestrength); + + /* Get info on the SOCRAM cores... */ + bus->ramsize = bus->ci->ramsize; + if (!(bus->ramsize)) { + brcmf_err("failed to find SOCRAM memory!\n"); + goto fail; + } + + /* Set card control so an SDIO card reset does a WLAN backplane reset */ + reg_val = brcmf_sdio_regrb(bus->sdiodev, + SDIO_CCCR_BRCM_CARDCTRL, &err); + if (err) + goto fail; + + reg_val |= SDIO_CCCR_BRCM_CARDCTRL_WLANRESET; + + brcmf_sdio_regwb(bus->sdiodev, + SDIO_CCCR_BRCM_CARDCTRL, reg_val, &err); + if (err) + goto fail; + + /* set PMUControl so a backplane reset does PMU state reload */ + reg_addr = CORE_CC_REG(bus->ci->c_inf[0].base, + pmucontrol); + reg_val = brcmf_sdio_regrl(bus->sdiodev, + reg_addr, + &err); + if (err) + goto fail; + + reg_val |= (BCMA_CC_PMU_CTL_RES_RELOAD << BCMA_CC_PMU_CTL_RES_SHIFT); + + brcmf_sdio_regwl(bus->sdiodev, + reg_addr, + reg_val, + &err); + if (err) + goto fail; + + + sdio_release_host(bus->sdiodev->func[1]); + + brcmu_pktq_init(&bus->txq, (PRIOMASK + 1), TXQLEN); + + /* Locate an appropriately-aligned portion of hdrbuf */ + bus->rxhdr = (u8 *) roundup((unsigned long)&bus->hdrbuf[0], + BRCMF_SDALIGN); + + /* Set the poll and/or interrupt flags */ + bus->intr = true; + bus->poll = false; + if (bus->poll) + bus->pollrate = 1; + + return true; + +fail: + sdio_release_host(bus->sdiodev->func[1]); + return false; +} + +static bool brcmf_sdbrcm_probe_init(struct brcmf_sdio *bus) +{ + brcmf_dbg(TRACE, "Enter\n"); + + sdio_claim_host(bus->sdiodev->func[1]); + + /* Disable F2 to clear any intermediate frame state on the dongle */ + brcmf_sdio_regwb(bus->sdiodev, SDIO_CCCR_IOEx, + SDIO_FUNC_ENABLE_1, NULL); + + bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN; + bus->rxflow = false; + + /* Done with backplane-dependent accesses, can drop clock... */ + brcmf_sdio_regwb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, 0, NULL); + + sdio_release_host(bus->sdiodev->func[1]); + + /* ...and initialize clock/power states */ + bus->clkstate = CLK_SDONLY; + bus->idletime = BRCMF_IDLE_INTERVAL; + bus->idleclock = BRCMF_IDLE_ACTIVE; + + /* Query the F2 block size, set roundup accordingly */ + bus->blocksize = bus->sdiodev->func[2]->cur_blksize; + bus->roundup = min(max_roundup, bus->blocksize); + + /* bus module does not support packet chaining */ + bus->use_rxchain = false; + bus->sd_rxchain = false; + + /* SR state */ + bus->sleeping = false; + bus->sr_enabled = false; + + return true; +} + +static int +brcmf_sdbrcm_watchdog_thread(void *data) +{ + struct brcmf_sdio *bus = (struct brcmf_sdio *)data; + + allow_signal(SIGTERM); + /* Run until signal received */ + while (1) { + if (kthread_should_stop()) + break; + if (!wait_for_completion_interruptible(&bus->watchdog_wait)) { + brcmf_sdbrcm_bus_watchdog(bus); + /* Count the tick for reference */ + bus->sdcnt.tickcnt++; + } else + break; + } + return 0; +} + +static void +brcmf_sdbrcm_watchdog(unsigned long data) +{ + struct brcmf_sdio *bus = (struct brcmf_sdio *)data; + + if (bus->watchdog_tsk) { + complete(&bus->watchdog_wait); + /* Reschedule the watchdog */ + if (bus->wd_timer_valid) + mod_timer(&bus->timer, + jiffies + BRCMF_WD_POLL_MS * HZ / 1000); + } +} + +static void brcmf_sdbrcm_release_dongle(struct brcmf_sdio *bus) +{ + brcmf_dbg(TRACE, "Enter\n"); + + if (bus->ci) { + sdio_claim_host(bus->sdiodev->func[1]); + brcmf_sdbrcm_clkctl(bus, CLK_AVAIL, false); + brcmf_sdbrcm_clkctl(bus, CLK_NONE, false); + sdio_release_host(bus->sdiodev->func[1]); + brcmf_sdio_chip_detach(&bus->ci); + if (bus->vars && bus->varsz) + kfree(bus->vars); + bus->vars = NULL; + } + + brcmf_dbg(TRACE, "Disconnected\n"); +} + +/* Detach and free everything */ +static void brcmf_sdbrcm_release(struct brcmf_sdio *bus) +{ + brcmf_dbg(TRACE, "Enter\n"); + + if (bus) { + /* De-register interrupt handler */ + brcmf_sdio_intr_unregister(bus->sdiodev); + + cancel_work_sync(&bus->datawork); + if (bus->brcmf_wq) + destroy_workqueue(bus->brcmf_wq); + + if (bus->sdiodev->bus_if->drvr) { + brcmf_detach(bus->sdiodev->dev); + brcmf_sdbrcm_release_dongle(bus); + } + + brcmf_sdbrcm_release_malloc(bus); + + kfree(bus); + } + + brcmf_dbg(TRACE, "Disconnected\n"); +} + +static struct brcmf_bus_ops brcmf_sdio_bus_ops = { + .stop = brcmf_sdbrcm_bus_stop, + .init = brcmf_sdbrcm_bus_init, + .txdata = brcmf_sdbrcm_bus_txdata, + .txctl = brcmf_sdbrcm_bus_txctl, + .rxctl = brcmf_sdbrcm_bus_rxctl, + .gettxq = brcmf_sdbrcm_bus_gettxq, +}; + +void *brcmf_sdbrcm_probe(u32 regsva, struct brcmf_sdio_dev *sdiodev) +{ + int ret; + struct brcmf_sdio *bus; + struct brcmf_bus_dcmd *dlst; + u32 dngl_txglom; + u32 dngl_txglomalign; + u8 idx; + + brcmf_dbg(TRACE, "Enter\n"); + + /* We make an assumption about address window mappings: + * regsva == SI_ENUM_BASE*/ + + /* Allocate private bus interface state */ + bus = kzalloc(sizeof(struct brcmf_sdio), GFP_ATOMIC); + if (!bus) + goto fail; + + bus->sdiodev = sdiodev; + sdiodev->bus = bus; + skb_queue_head_init(&bus->glom); + bus->txbound = BRCMF_TXBOUND; + bus->rxbound = BRCMF_RXBOUND; + bus->txminmax = BRCMF_TXMINMAX; + bus->tx_seq = SDPCM_SEQUENCE_WRAP - 1; + + INIT_WORK(&bus->datawork, brcmf_sdio_dataworker); + bus->brcmf_wq = create_singlethread_workqueue("brcmf_wq"); + if (bus->brcmf_wq == NULL) { + brcmf_err("insufficient memory to create txworkqueue\n"); + goto fail; + } + + /* attempt to attach to the dongle */ + if (!(brcmf_sdbrcm_probe_attach(bus, regsva))) { + brcmf_err("brcmf_sdbrcm_probe_attach failed\n"); + goto fail; + } + + spin_lock_init(&bus->rxctl_lock); + spin_lock_init(&bus->txqlock); + init_waitqueue_head(&bus->ctrl_wait); + init_waitqueue_head(&bus->dcmd_resp_wait); + + /* Set up the watchdog timer */ + init_timer(&bus->timer); + bus->timer.data = (unsigned long)bus; + bus->timer.function = brcmf_sdbrcm_watchdog; + + /* Initialize watchdog thread */ + init_completion(&bus->watchdog_wait); + bus->watchdog_tsk = kthread_run(brcmf_sdbrcm_watchdog_thread, + bus, "brcmf_watchdog"); + if (IS_ERR(bus->watchdog_tsk)) { + pr_warn("brcmf_watchdog thread failed to start\n"); + bus->watchdog_tsk = NULL; + } + /* Initialize DPC thread */ + INIT_LIST_HEAD(&bus->dpc_tsklst); + spin_lock_init(&bus->dpc_tl_lock); + + /* Assign bus interface call back */ + bus->sdiodev->bus_if->dev = bus->sdiodev->dev; + bus->sdiodev->bus_if->ops = &brcmf_sdio_bus_ops; + bus->sdiodev->bus_if->chip = bus->ci->chip; + bus->sdiodev->bus_if->chiprev = bus->ci->chiprev; + + /* Attach to the brcmf/OS/network interface */ + ret = brcmf_attach(SDPCM_RESERVE, bus->sdiodev->dev); + if (ret != 0) { + brcmf_err("brcmf_attach failed\n"); + goto fail; + } + + /* Allocate buffers */ + if (!(brcmf_sdbrcm_probe_malloc(bus))) { + brcmf_err("brcmf_sdbrcm_probe_malloc failed\n"); + goto fail; + } + + if (!(brcmf_sdbrcm_probe_init(bus))) { + brcmf_err("brcmf_sdbrcm_probe_init failed\n"); + goto fail; + } + + brcmf_sdio_debugfs_create(bus); + brcmf_dbg(INFO, "completed!!\n"); + + /* sdio bus core specific dcmd */ + idx = brcmf_sdio_chip_getinfidx(bus->ci, BCMA_CORE_SDIO_DEV); + dlst = kzalloc(sizeof(struct brcmf_bus_dcmd), GFP_KERNEL); + if (dlst) { + if (bus->ci->c_inf[idx].rev < 12) { + /* for sdio core rev < 12, disable txgloming */ + dngl_txglom = 0; + dlst->name = "bus:txglom"; + dlst->param = (char *)&dngl_txglom; + dlst->param_len = sizeof(u32); + } else { + /* otherwise, set txglomalign */ + dngl_txglomalign = bus->sdiodev->bus_if->align; + dlst->name = "bus:txglomalign"; + dlst->param = (char *)&dngl_txglomalign; + dlst->param_len = sizeof(u32); + } + list_add(&dlst->list, &bus->sdiodev->bus_if->dcmd_list); + } + + /* if firmware path present try to download and bring up bus */ + ret = brcmf_bus_start(bus->sdiodev->dev); + if (ret != 0) { + brcmf_err("dongle is not responding\n"); + goto fail; + } + + return bus; + +fail: + brcmf_sdbrcm_release(bus); + return NULL; +} + +void brcmf_sdbrcm_disconnect(void *ptr) +{ + struct brcmf_sdio *bus = (struct brcmf_sdio *)ptr; + + brcmf_dbg(TRACE, "Enter\n"); + + if (bus) + brcmf_sdbrcm_release(bus); + + brcmf_dbg(TRACE, "Disconnected\n"); +} + +void +brcmf_sdbrcm_wd_timer(struct brcmf_sdio *bus, uint wdtick) +{ + /* Totally stop the timer */ + if (!wdtick && bus->wd_timer_valid) { + del_timer_sync(&bus->timer); + bus->wd_timer_valid = false; + bus->save_ms = wdtick; + return; + } + + /* don't start the wd until fw is loaded */ + if (bus->sdiodev->bus_if->state == BRCMF_BUS_DOWN) + return; + + if (wdtick) { + if (bus->save_ms != BRCMF_WD_POLL_MS) { + if (bus->wd_timer_valid) + /* Stop timer and restart at new value */ + del_timer_sync(&bus->timer); + + /* Create timer again when watchdog period is + dynamically changed or in the first instance + */ + bus->timer.expires = + jiffies + BRCMF_WD_POLL_MS * HZ / 1000; + add_timer(&bus->timer); + + } else { + /* Re arm the timer, at last watchdog period */ + mod_timer(&bus->timer, + jiffies + BRCMF_WD_POLL_MS * HZ / 1000); + } + + bus->wd_timer_valid = true; + bus->save_ms = wdtick; + } +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/fweh.c b/drivers/net/wireless/brcm80211/brcmfmac/fweh.c new file mode 100644 index 0000000..83ee53a --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/fweh.c @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2012 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include "brcmu_wifi.h" +#include "brcmu_utils.h" + +#include "dhd.h" +#include "dhd_dbg.h" +#include "tracepoint.h" +#include "fwsignal.h" +#include "fweh.h" +#include "fwil.h" + +/** + * struct brcm_ethhdr - broadcom specific ether header. + * + * @subtype: subtype for this packet. + * @length: TODO: length of appended data. + * @version: version indication. + * @oui: OUI of this packet. + * @usr_subtype: subtype for this OUI. + */ +struct brcm_ethhdr { + __be16 subtype; + __be16 length; + u8 version; + u8 oui[3]; + __be16 usr_subtype; +} __packed; + +struct brcmf_event_msg_be { + __be16 version; + __be16 flags; + __be32 event_type; + __be32 status; + __be32 reason; + __be32 auth_type; + __be32 datalen; + u8 addr[ETH_ALEN]; + char ifname[IFNAMSIZ]; + u8 ifidx; + u8 bsscfgidx; +} __packed; + +/** + * struct brcmf_event - contents of broadcom event packet. + * + * @eth: standard ether header. + * @hdr: broadcom specific ether header. + * @msg: common part of the actual event message. + */ +struct brcmf_event { + struct ethhdr eth; + struct brcm_ethhdr hdr; + struct brcmf_event_msg_be msg; +} __packed; + +/** + * struct brcmf_fweh_queue_item - event item on event queue. + * + * @q: list element for queuing. + * @code: event code. + * @ifidx: interface index related to this event. + * @ifaddr: ethernet address for interface. + * @emsg: common parameters of the firmware event message. + * @data: event specific data part of the firmware event. + */ +struct brcmf_fweh_queue_item { + struct list_head q; + enum brcmf_fweh_event_code code; + u8 ifidx; + u8 ifaddr[ETH_ALEN]; + struct brcmf_event_msg_be emsg; + u8 data[0]; +}; + +/** + * struct brcmf_fweh_event_name - code, name mapping entry. + */ +struct brcmf_fweh_event_name { + enum brcmf_fweh_event_code code; + const char *name; +}; + +#ifdef DEBUG +#define BRCMF_ENUM_DEF(id, val) \ + { val, #id }, + +/* array for mapping code to event name */ +static struct brcmf_fweh_event_name fweh_event_names[] = { + BRCMF_FWEH_EVENT_ENUM_DEFLIST +}; +#undef BRCMF_ENUM_DEF + +/** + * brcmf_fweh_event_name() - returns name for given event code. + * + * @code: code to lookup. + */ +static const char *brcmf_fweh_event_name(enum brcmf_fweh_event_code code) +{ + int i; + for (i = 0; i < ARRAY_SIZE(fweh_event_names); i++) { + if (fweh_event_names[i].code == code) + return fweh_event_names[i].name; + } + return "unknown"; +} +#else +static const char *brcmf_fweh_event_name(enum brcmf_fweh_event_code code) +{ + return "nodebug"; +} +#endif + +/** + * brcmf_fweh_queue_event() - create and queue event. + * + * @fweh: firmware event handling info. + * @event: event queue entry. + */ +static void brcmf_fweh_queue_event(struct brcmf_fweh_info *fweh, + struct brcmf_fweh_queue_item *event) +{ + ulong flags; + + spin_lock_irqsave(&fweh->evt_q_lock, flags); + list_add_tail(&event->q, &fweh->event_q); + spin_unlock_irqrestore(&fweh->evt_q_lock, flags); + schedule_work(&fweh->event_work); +} + +static int brcmf_fweh_call_event_handler(struct brcmf_if *ifp, + enum brcmf_fweh_event_code code, + struct brcmf_event_msg *emsg, + void *data) +{ + struct brcmf_fweh_info *fweh; + int err = -EINVAL; + + if (ifp) { + fweh = &ifp->drvr->fweh; + + /* handle the event if valid interface and handler */ + if (fweh->evt_handler[code]) + err = fweh->evt_handler[code](ifp, emsg, data); + else + brcmf_err("unhandled event %d ignored\n", code); + } else { + brcmf_err("no interface object\n"); + } + return err; +} + +/** + * brcmf_fweh_handle_if_event() - handle IF event. + * + * @drvr: driver information object. + * @item: queue entry. + * @ifpp: interface object (may change upon ADD action). + */ +static void brcmf_fweh_handle_if_event(struct brcmf_pub *drvr, + struct brcmf_event_msg *emsg, + void *data) +{ + struct brcmf_if_event *ifevent = data; + struct brcmf_if *ifp; + int err = 0; + + brcmf_dbg(EVENT, "action: %u idx: %u bsscfg: %u flags: %u role: %u\n", + ifevent->action, ifevent->ifidx, ifevent->bssidx, + ifevent->flags, ifevent->role); + + if (ifevent->ifidx >= BRCMF_MAX_IFS) { + brcmf_err("invalid interface index: %u\n", + ifevent->ifidx); + return; + } + + ifp = drvr->iflist[ifevent->bssidx]; + + if (ifevent->action == BRCMF_E_IF_ADD) { + brcmf_dbg(EVENT, "adding %s (%pM)\n", emsg->ifname, + emsg->addr); + ifp = brcmf_add_if(drvr, ifevent->bssidx, ifevent->ifidx, + emsg->ifname, emsg->addr); + if (IS_ERR(ifp)) + return; + brcmf_fws_add_interface(ifp); + if (!drvr->fweh.evt_handler[BRCMF_E_IF]) + if (brcmf_net_attach(ifp, false) < 0) + return; + } + + if (ifevent->action == BRCMF_E_IF_CHANGE) + brcmf_fws_reset_interface(ifp); + + err = brcmf_fweh_call_event_handler(ifp, emsg->event_code, emsg, data); + + if (ifevent->action == BRCMF_E_IF_DEL) { + brcmf_fws_del_interface(ifp); + brcmf_del_if(drvr, ifevent->bssidx); + } +} + +/** + * brcmf_fweh_dequeue_event() - get event from the queue. + * + * @fweh: firmware event handling info. + */ +static struct brcmf_fweh_queue_item * +brcmf_fweh_dequeue_event(struct brcmf_fweh_info *fweh) +{ + struct brcmf_fweh_queue_item *event = NULL; + ulong flags; + + spin_lock_irqsave(&fweh->evt_q_lock, flags); + if (!list_empty(&fweh->event_q)) { + event = list_first_entry(&fweh->event_q, + struct brcmf_fweh_queue_item, q); + list_del(&event->q); + } + spin_unlock_irqrestore(&fweh->evt_q_lock, flags); + + return event; +} + +/** + * brcmf_fweh_event_worker() - firmware event worker. + * + * @work: worker object. + */ +static void brcmf_fweh_event_worker(struct work_struct *work) +{ + struct brcmf_pub *drvr; + struct brcmf_if *ifp; + struct brcmf_fweh_info *fweh; + struct brcmf_fweh_queue_item *event; + int err = 0; + struct brcmf_event_msg_be *emsg_be; + struct brcmf_event_msg emsg; + + fweh = container_of(work, struct brcmf_fweh_info, event_work); + drvr = container_of(fweh, struct brcmf_pub, fweh); + + while ((event = brcmf_fweh_dequeue_event(fweh))) { + brcmf_dbg(EVENT, "event %s (%u) ifidx %u bsscfg %u addr %pM\n", + brcmf_fweh_event_name(event->code), event->code, + event->emsg.ifidx, event->emsg.bsscfgidx, + event->emsg.addr); + + /* convert event message */ + emsg_be = &event->emsg; + emsg.version = be16_to_cpu(emsg_be->version); + emsg.flags = be16_to_cpu(emsg_be->flags); + emsg.event_code = event->code; + emsg.status = be32_to_cpu(emsg_be->status); + emsg.reason = be32_to_cpu(emsg_be->reason); + emsg.auth_type = be32_to_cpu(emsg_be->auth_type); + emsg.datalen = be32_to_cpu(emsg_be->datalen); + memcpy(emsg.addr, emsg_be->addr, ETH_ALEN); + memcpy(emsg.ifname, emsg_be->ifname, sizeof(emsg.ifname)); + emsg.ifidx = emsg_be->ifidx; + emsg.bsscfgidx = emsg_be->bsscfgidx; + + brcmf_dbg(EVENT, " version %u flags %u status %u reason %u\n", + emsg.version, emsg.flags, emsg.status, emsg.reason); + brcmf_dbg_hex_dump(BRCMF_EVENT_ON(), event->data, + min_t(u32, emsg.datalen, 64), + "event payload, len=%d\n", emsg.datalen); + + /* special handling of interface event */ + if (event->code == BRCMF_E_IF) { + brcmf_fweh_handle_if_event(drvr, &emsg, event->data); + goto event_free; + } + + ifp = drvr->iflist[emsg.bsscfgidx]; + err = brcmf_fweh_call_event_handler(ifp, event->code, &emsg, + event->data); + if (err) { + brcmf_err("event handler failed (%d)\n", + event->code); + err = 0; + } +event_free: + kfree(event); + } +} + +/** + * brcmf_fweh_attach() - initialize firmware event handling. + * + * @drvr: driver information object. + */ +void brcmf_fweh_attach(struct brcmf_pub *drvr) +{ + struct brcmf_fweh_info *fweh = &drvr->fweh; + INIT_WORK(&fweh->event_work, brcmf_fweh_event_worker); + spin_lock_init(&fweh->evt_q_lock); + INIT_LIST_HEAD(&fweh->event_q); +} + +/** + * brcmf_fweh_detach() - cleanup firmware event handling. + * + * @drvr: driver information object. + */ +void brcmf_fweh_detach(struct brcmf_pub *drvr) +{ + struct brcmf_fweh_info *fweh = &drvr->fweh; + struct brcmf_if *ifp = drvr->iflist[0]; + s8 eventmask[BRCMF_EVENTING_MASK_LEN]; + + if (ifp) { + /* clear all events */ + memset(eventmask, 0, BRCMF_EVENTING_MASK_LEN); + (void)brcmf_fil_iovar_data_set(ifp, "event_msgs", + eventmask, + BRCMF_EVENTING_MASK_LEN); + } + /* cancel the worker */ + cancel_work_sync(&fweh->event_work); + WARN_ON(!list_empty(&fweh->event_q)); + memset(fweh->evt_handler, 0, sizeof(fweh->evt_handler)); +} + +/** + * brcmf_fweh_register() - register handler for given event code. + * + * @drvr: driver information object. + * @code: event code. + * @handler: handler for the given event code. + */ +int brcmf_fweh_register(struct brcmf_pub *drvr, enum brcmf_fweh_event_code code, + brcmf_fweh_handler_t handler) +{ + if (drvr->fweh.evt_handler[code]) { + brcmf_err("event code %d already registered\n", code); + return -ENOSPC; + } + drvr->fweh.evt_handler[code] = handler; + brcmf_dbg(TRACE, "event handler registered for %s\n", + brcmf_fweh_event_name(code)); + return 0; +} + +/** + * brcmf_fweh_unregister() - remove handler for given code. + * + * @drvr: driver information object. + * @code: event code. + */ +void brcmf_fweh_unregister(struct brcmf_pub *drvr, + enum brcmf_fweh_event_code code) +{ + brcmf_dbg(TRACE, "event handler cleared for %s\n", + brcmf_fweh_event_name(code)); + drvr->fweh.evt_handler[code] = NULL; +} + +/** + * brcmf_fweh_activate_events() - enables firmware events registered. + * + * @ifp: primary interface object. + */ +int brcmf_fweh_activate_events(struct brcmf_if *ifp) +{ + int i, err; + s8 eventmask[BRCMF_EVENTING_MASK_LEN]; + + for (i = 0; i < BRCMF_E_LAST; i++) { + if (ifp->drvr->fweh.evt_handler[i]) { + brcmf_dbg(EVENT, "enable event %s\n", + brcmf_fweh_event_name(i)); + setbit(eventmask, i); + } + } + + /* want to handle IF event as well */ + brcmf_dbg(EVENT, "enable event IF\n"); + setbit(eventmask, BRCMF_E_IF); + + err = brcmf_fil_iovar_data_set(ifp, "event_msgs", + eventmask, BRCMF_EVENTING_MASK_LEN); + if (err) + brcmf_err("Set event_msgs error (%d)\n", err); + + return err; +} + +/** + * brcmf_fweh_process_event() - process skb as firmware event. + * + * @drvr: driver information object. + * @event_packet: event packet to process. + * + * If the packet buffer contains a firmware event message it will + * dispatch the event to a registered handler (using worker). + */ +void brcmf_fweh_process_event(struct brcmf_pub *drvr, + struct brcmf_event *event_packet) +{ + enum brcmf_fweh_event_code code; + struct brcmf_fweh_info *fweh = &drvr->fweh; + struct brcmf_fweh_queue_item *event; + gfp_t alloc_flag = GFP_KERNEL; + void *data; + u32 datalen; + + /* get event info */ + code = get_unaligned_be32(&event_packet->msg.event_type); + datalen = get_unaligned_be32(&event_packet->msg.datalen); + data = &event_packet[1]; + + if (code >= BRCMF_E_LAST) + return; + + if (code != BRCMF_E_IF && !fweh->evt_handler[code]) + return; + + if (in_interrupt()) + alloc_flag = GFP_ATOMIC; + + event = kzalloc(sizeof(*event) + datalen, alloc_flag); + if (!event) + return; + + event->code = code; + event->ifidx = event_packet->msg.ifidx; + + /* use memcpy to get aligned event message */ + memcpy(&event->emsg, &event_packet->msg, sizeof(event->emsg)); + memcpy(event->data, data, datalen); + memcpy(event->ifaddr, event_packet->eth.h_dest, ETH_ALEN); + + brcmf_fweh_queue_event(fweh, event); +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/fweh.h b/drivers/net/wireless/brcm80211/brcmfmac/fweh.h new file mode 100644 index 0000000..6ec5db9 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/fweh.h @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2012 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +#ifndef FWEH_H_ +#define FWEH_H_ + +#include +#include +#include +#include + +/* formward declarations */ +struct brcmf_pub; +struct brcmf_if; +struct brcmf_cfg80211_info; +struct brcmf_event; + +/* list of firmware events */ +#define BRCMF_FWEH_EVENT_ENUM_DEFLIST \ + BRCMF_ENUM_DEF(SET_SSID, 0) \ + BRCMF_ENUM_DEF(JOIN, 1) \ + BRCMF_ENUM_DEF(START, 2) \ + BRCMF_ENUM_DEF(AUTH, 3) \ + BRCMF_ENUM_DEF(AUTH_IND, 4) \ + BRCMF_ENUM_DEF(DEAUTH, 5) \ + BRCMF_ENUM_DEF(DEAUTH_IND, 6) \ + BRCMF_ENUM_DEF(ASSOC, 7) \ + BRCMF_ENUM_DEF(ASSOC_IND, 8) \ + BRCMF_ENUM_DEF(REASSOC, 9) \ + BRCMF_ENUM_DEF(REASSOC_IND, 10) \ + BRCMF_ENUM_DEF(DISASSOC, 11) \ + BRCMF_ENUM_DEF(DISASSOC_IND, 12) \ + BRCMF_ENUM_DEF(QUIET_START, 13) \ + BRCMF_ENUM_DEF(QUIET_END, 14) \ + BRCMF_ENUM_DEF(BEACON_RX, 15) \ + BRCMF_ENUM_DEF(LINK, 16) \ + BRCMF_ENUM_DEF(MIC_ERROR, 17) \ + BRCMF_ENUM_DEF(NDIS_LINK, 18) \ + BRCMF_ENUM_DEF(ROAM, 19) \ + BRCMF_ENUM_DEF(TXFAIL, 20) \ + BRCMF_ENUM_DEF(PMKID_CACHE, 21) \ + BRCMF_ENUM_DEF(RETROGRADE_TSF, 22) \ + BRCMF_ENUM_DEF(PRUNE, 23) \ + BRCMF_ENUM_DEF(AUTOAUTH, 24) \ + BRCMF_ENUM_DEF(EAPOL_MSG, 25) \ + BRCMF_ENUM_DEF(SCAN_COMPLETE, 26) \ + BRCMF_ENUM_DEF(ADDTS_IND, 27) \ + BRCMF_ENUM_DEF(DELTS_IND, 28) \ + BRCMF_ENUM_DEF(BCNSENT_IND, 29) \ + BRCMF_ENUM_DEF(BCNRX_MSG, 30) \ + BRCMF_ENUM_DEF(BCNLOST_MSG, 31) \ + BRCMF_ENUM_DEF(ROAM_PREP, 32) \ + BRCMF_ENUM_DEF(PFN_NET_FOUND, 33) \ + BRCMF_ENUM_DEF(PFN_NET_LOST, 34) \ + BRCMF_ENUM_DEF(RESET_COMPLETE, 35) \ + BRCMF_ENUM_DEF(JOIN_START, 36) \ + BRCMF_ENUM_DEF(ROAM_START, 37) \ + BRCMF_ENUM_DEF(ASSOC_START, 38) \ + BRCMF_ENUM_DEF(IBSS_ASSOC, 39) \ + BRCMF_ENUM_DEF(RADIO, 40) \ + BRCMF_ENUM_DEF(PSM_WATCHDOG, 41) \ + BRCMF_ENUM_DEF(PROBREQ_MSG, 44) \ + BRCMF_ENUM_DEF(SCAN_CONFIRM_IND, 45) \ + BRCMF_ENUM_DEF(PSK_SUP, 46) \ + BRCMF_ENUM_DEF(COUNTRY_CODE_CHANGED, 47) \ + BRCMF_ENUM_DEF(EXCEEDED_MEDIUM_TIME, 48) \ + BRCMF_ENUM_DEF(ICV_ERROR, 49) \ + BRCMF_ENUM_DEF(UNICAST_DECODE_ERROR, 50) \ + BRCMF_ENUM_DEF(MULTICAST_DECODE_ERROR, 51) \ + BRCMF_ENUM_DEF(TRACE, 52) \ + BRCMF_ENUM_DEF(IF, 54) \ + BRCMF_ENUM_DEF(P2P_DISC_LISTEN_COMPLETE, 55) \ + BRCMF_ENUM_DEF(RSSI, 56) \ + BRCMF_ENUM_DEF(PFN_SCAN_COMPLETE, 57) \ + BRCMF_ENUM_DEF(EXTLOG_MSG, 58) \ + BRCMF_ENUM_DEF(ACTION_FRAME, 59) \ + BRCMF_ENUM_DEF(ACTION_FRAME_COMPLETE, 60) \ + BRCMF_ENUM_DEF(PRE_ASSOC_IND, 61) \ + BRCMF_ENUM_DEF(PRE_REASSOC_IND, 62) \ + BRCMF_ENUM_DEF(CHANNEL_ADOPTED, 63) \ + BRCMF_ENUM_DEF(AP_STARTED, 64) \ + BRCMF_ENUM_DEF(DFS_AP_STOP, 65) \ + BRCMF_ENUM_DEF(DFS_AP_RESUME, 66) \ + BRCMF_ENUM_DEF(ESCAN_RESULT, 69) \ + BRCMF_ENUM_DEF(ACTION_FRAME_OFF_CHAN_COMPLETE, 70) \ + BRCMF_ENUM_DEF(PROBERESP_MSG, 71) \ + BRCMF_ENUM_DEF(P2P_PROBEREQ_MSG, 72) \ + BRCMF_ENUM_DEF(DCS_REQUEST, 73) \ + BRCMF_ENUM_DEF(FIFO_CREDIT_MAP, 74) \ + BRCMF_ENUM_DEF(ACTION_FRAME_RX, 75) + +#define BRCMF_ENUM_DEF(id, val) \ + BRCMF_E_##id = (val), + +/* firmware event codes sent by the dongle */ +enum brcmf_fweh_event_code { + BRCMF_FWEH_EVENT_ENUM_DEFLIST + BRCMF_E_LAST +}; +#undef BRCMF_ENUM_DEF + +/* flags field values in struct brcmf_event_msg */ +#define BRCMF_EVENT_MSG_LINK 0x01 +#define BRCMF_EVENT_MSG_FLUSHTXQ 0x02 +#define BRCMF_EVENT_MSG_GROUP 0x04 + +/** + * definitions for event packet validation. + */ +#define BRCMF_EVENT_OUI_OFFSET 19 +#define BRCM_OUI "\x00\x10\x18" +#define DOT11_OUI_LEN 3 +#define BCMILCP_BCM_SUBTYPE_EVENT 1 + + +/** + * struct brcmf_event_msg - firmware event message. + * + * @version: version information. + * @flags: event flags. + * @event_code: firmware event code. + * @status: status information. + * @reason: reason code. + * @auth_type: authentication type. + * @datalen: lenght of event data buffer. + * @addr: ether address. + * @ifname: interface name. + * @ifidx: interface index. + * @bsscfgidx: bsscfg index. + */ +struct brcmf_event_msg { + u16 version; + u16 flags; + u32 event_code; + u32 status; + u32 reason; + s32 auth_type; + u32 datalen; + u8 addr[ETH_ALEN]; + char ifname[IFNAMSIZ]; + u8 ifidx; + u8 bsscfgidx; +}; + +typedef int (*brcmf_fweh_handler_t)(struct brcmf_if *ifp, + const struct brcmf_event_msg *evtmsg, + void *data); + +/** + * struct brcmf_fweh_info - firmware event handling information. + * + * @event_work: event worker. + * @evt_q_lock: lock for event queue protection. + * @event_q: event queue. + * @evt_handler: registered event handlers. + */ +struct brcmf_fweh_info { + struct work_struct event_work; + spinlock_t evt_q_lock; + struct list_head event_q; + int (*evt_handler[BRCMF_E_LAST])(struct brcmf_if *ifp, + const struct brcmf_event_msg *evtmsg, + void *data); +}; + +void brcmf_fweh_attach(struct brcmf_pub *drvr); +void brcmf_fweh_detach(struct brcmf_pub *drvr); +int brcmf_fweh_register(struct brcmf_pub *drvr, enum brcmf_fweh_event_code code, + int (*handler)(struct brcmf_if *ifp, + const struct brcmf_event_msg *evtmsg, + void *data)); +void brcmf_fweh_unregister(struct brcmf_pub *drvr, + enum brcmf_fweh_event_code code); +int brcmf_fweh_activate_events(struct brcmf_if *ifp); +void brcmf_fweh_process_event(struct brcmf_pub *drvr, + struct brcmf_event *event_packet); + +static inline void brcmf_fweh_process_skb(struct brcmf_pub *drvr, + struct sk_buff *skb) +{ + struct brcmf_event *event_packet; + u8 *data; + u16 usr_stype; + + /* only process events when protocol matches */ + if (skb->protocol != cpu_to_be16(ETH_P_LINK_CTL)) + return; + + /* check for BRCM oui match */ + event_packet = (struct brcmf_event *)skb_mac_header(skb); + data = (u8 *)event_packet; + data += BRCMF_EVENT_OUI_OFFSET; + if (memcmp(BRCM_OUI, data, DOT11_OUI_LEN)) + return; + + /* final match on usr_subtype */ + data += DOT11_OUI_LEN; + usr_stype = get_unaligned_be16(data); + if (usr_stype != BCMILCP_BCM_SUBTYPE_EVENT) + return; + + brcmf_fweh_process_event(drvr, event_packet); +} + +#endif /* FWEH_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/fwil.c b/drivers/net/wireless/brcm80211/brcmfmac/fwil.c new file mode 100644 index 0000000..04f3959 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/fwil.c @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2012 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* FWIL is the Firmware Interface Layer. In this module the support functions + * are located to set and get variables to and from the firmware. + */ + +#include +#include +#include +#include +#include "dhd.h" +#include "dhd_bus.h" +#include "dhd_dbg.h" +#include "tracepoint.h" +#include "fwil.h" + + +#define MAX_HEX_DUMP_LEN 64 + + +static s32 +brcmf_fil_cmd_data(struct brcmf_if *ifp, u32 cmd, void *data, u32 len, bool set) +{ + struct brcmf_pub *drvr = ifp->drvr; + s32 err; + + if (drvr->bus_if->state != BRCMF_BUS_DATA) { + brcmf_err("bus is down. we have nothing to do.\n"); + return -EIO; + } + + if (data != NULL) + len = min_t(uint, len, BRCMF_DCMD_MAXLEN); + if (set) + err = brcmf_proto_cdc_set_dcmd(drvr, ifp->ifidx, cmd, data, + len); + else + err = brcmf_proto_cdc_query_dcmd(drvr, ifp->ifidx, cmd, data, + len); + + if (err >= 0) + err = 0; + else + brcmf_err("Failed err=%d\n", err); + + return err; +} + +s32 +brcmf_fil_cmd_data_set(struct brcmf_if *ifp, u32 cmd, void *data, u32 len) +{ + s32 err; + + mutex_lock(&ifp->drvr->proto_block); + + brcmf_dbg(FIL, "cmd=%d, len=%d\n", cmd, len); + brcmf_dbg_hex_dump(BRCMF_FIL_ON(), data, + min_t(uint, len, MAX_HEX_DUMP_LEN), "data"); + + err = brcmf_fil_cmd_data(ifp, cmd, data, len, true); + mutex_unlock(&ifp->drvr->proto_block); + + return err; +} + +s32 +brcmf_fil_cmd_data_get(struct brcmf_if *ifp, u32 cmd, void *data, u32 len) +{ + s32 err; + + mutex_lock(&ifp->drvr->proto_block); + err = brcmf_fil_cmd_data(ifp, cmd, data, len, false); + + brcmf_dbg(FIL, "cmd=%d, len=%d\n", cmd, len); + brcmf_dbg_hex_dump(BRCMF_FIL_ON(), data, + min_t(uint, len, MAX_HEX_DUMP_LEN), "data"); + + mutex_unlock(&ifp->drvr->proto_block); + + return err; +} + + +s32 +brcmf_fil_cmd_int_set(struct brcmf_if *ifp, u32 cmd, u32 data) +{ + s32 err; + __le32 data_le = cpu_to_le32(data); + + mutex_lock(&ifp->drvr->proto_block); + brcmf_dbg(FIL, "cmd=%d, value=%d\n", cmd, data); + err = brcmf_fil_cmd_data(ifp, cmd, &data_le, sizeof(data_le), true); + mutex_unlock(&ifp->drvr->proto_block); + + return err; +} + +s32 +brcmf_fil_cmd_int_get(struct brcmf_if *ifp, u32 cmd, u32 *data) +{ + s32 err; + __le32 data_le = cpu_to_le32(*data); + + mutex_lock(&ifp->drvr->proto_block); + err = brcmf_fil_cmd_data(ifp, cmd, &data_le, sizeof(data_le), false); + mutex_unlock(&ifp->drvr->proto_block); + *data = le32_to_cpu(data_le); + brcmf_dbg(FIL, "cmd=%d, value=%d\n", cmd, *data); + + return err; +} + +static u32 +brcmf_create_iovar(char *name, char *data, u32 datalen, char *buf, u32 buflen) +{ + u32 len; + + len = strlen(name) + 1; + + if ((len + datalen) > buflen) + return 0; + + memcpy(buf, name, len); + + /* append data onto the end of the name string */ + if (data && datalen) + memcpy(&buf[len], data, datalen); + + return len + datalen; +} + + +s32 +brcmf_fil_iovar_data_set(struct brcmf_if *ifp, char *name, void *data, + u32 len) +{ + struct brcmf_pub *drvr = ifp->drvr; + s32 err; + u32 buflen; + + mutex_lock(&drvr->proto_block); + + brcmf_dbg(FIL, "name=%s, len=%d\n", name, len); + brcmf_dbg_hex_dump(BRCMF_FIL_ON(), data, + min_t(uint, len, MAX_HEX_DUMP_LEN), "data"); + + buflen = brcmf_create_iovar(name, data, len, drvr->proto_buf, + sizeof(drvr->proto_buf)); + if (buflen) { + err = brcmf_fil_cmd_data(ifp, BRCMF_C_SET_VAR, drvr->proto_buf, + buflen, true); + } else { + err = -EPERM; + brcmf_err("Creating iovar failed\n"); + } + + mutex_unlock(&drvr->proto_block); + return err; +} + +s32 +brcmf_fil_iovar_data_get(struct brcmf_if *ifp, char *name, void *data, + u32 len) +{ + struct brcmf_pub *drvr = ifp->drvr; + s32 err; + u32 buflen; + + mutex_lock(&drvr->proto_block); + + buflen = brcmf_create_iovar(name, data, len, drvr->proto_buf, + sizeof(drvr->proto_buf)); + if (buflen) { + err = brcmf_fil_cmd_data(ifp, BRCMF_C_GET_VAR, drvr->proto_buf, + buflen, false); + if (err == 0) + memcpy(data, drvr->proto_buf, len); + } else { + err = -EPERM; + brcmf_err("Creating iovar failed\n"); + } + + brcmf_dbg(FIL, "name=%s, len=%d\n", name, len); + brcmf_dbg_hex_dump(BRCMF_FIL_ON(), data, + min_t(uint, len, MAX_HEX_DUMP_LEN), "data"); + + mutex_unlock(&drvr->proto_block); + return err; +} + +s32 +brcmf_fil_iovar_int_set(struct brcmf_if *ifp, char *name, u32 data) +{ + __le32 data_le = cpu_to_le32(data); + + return brcmf_fil_iovar_data_set(ifp, name, &data_le, sizeof(data_le)); +} + +s32 +brcmf_fil_iovar_int_get(struct brcmf_if *ifp, char *name, u32 *data) +{ + __le32 data_le = cpu_to_le32(*data); + s32 err; + + err = brcmf_fil_iovar_data_get(ifp, name, &data_le, sizeof(data_le)); + if (err == 0) + *data = le32_to_cpu(data_le); + return err; +} + +static u32 +brcmf_create_bsscfg(s32 bssidx, char *name, char *data, u32 datalen, char *buf, + u32 buflen) +{ + const s8 *prefix = "bsscfg:"; + s8 *p; + u32 prefixlen; + u32 namelen; + u32 iolen; + __le32 bssidx_le; + + if (bssidx == 0) + return brcmf_create_iovar(name, data, datalen, buf, buflen); + + prefixlen = strlen(prefix); + namelen = strlen(name) + 1; /* lengh of iovar name + null */ + iolen = prefixlen + namelen + sizeof(bssidx_le) + datalen; + + if (buflen < iolen) { + brcmf_err("buffer is too short\n"); + return 0; + } + + p = buf; + + /* copy prefix, no null */ + memcpy(p, prefix, prefixlen); + p += prefixlen; + + /* copy iovar name including null */ + memcpy(p, name, namelen); + p += namelen; + + /* bss config index as first data */ + bssidx_le = cpu_to_le32(bssidx); + memcpy(p, &bssidx_le, sizeof(bssidx_le)); + p += sizeof(bssidx_le); + + /* parameter buffer follows */ + if (datalen) + memcpy(p, data, datalen); + + return iolen; +} + +s32 +brcmf_fil_bsscfg_data_set(struct brcmf_if *ifp, char *name, + void *data, u32 len) +{ + struct brcmf_pub *drvr = ifp->drvr; + s32 err; + u32 buflen; + + mutex_lock(&drvr->proto_block); + + brcmf_dbg(FIL, "bssidx=%d, name=%s, len=%d\n", ifp->bssidx, name, len); + brcmf_dbg_hex_dump(BRCMF_FIL_ON(), data, + min_t(uint, len, MAX_HEX_DUMP_LEN), "data"); + + buflen = brcmf_create_bsscfg(ifp->bssidx, name, data, len, + drvr->proto_buf, sizeof(drvr->proto_buf)); + if (buflen) { + err = brcmf_fil_cmd_data(ifp, BRCMF_C_SET_VAR, drvr->proto_buf, + buflen, true); + } else { + err = -EPERM; + brcmf_err("Creating bsscfg failed\n"); + } + + mutex_unlock(&drvr->proto_block); + return err; +} + +s32 +brcmf_fil_bsscfg_data_get(struct brcmf_if *ifp, char *name, + void *data, u32 len) +{ + struct brcmf_pub *drvr = ifp->drvr; + s32 err; + u32 buflen; + + mutex_lock(&drvr->proto_block); + + buflen = brcmf_create_bsscfg(ifp->bssidx, name, data, len, + drvr->proto_buf, sizeof(drvr->proto_buf)); + if (buflen) { + err = brcmf_fil_cmd_data(ifp, BRCMF_C_GET_VAR, drvr->proto_buf, + buflen, false); + if (err == 0) + memcpy(data, drvr->proto_buf, len); + } else { + err = -EPERM; + brcmf_err("Creating bsscfg failed\n"); + } + brcmf_dbg(FIL, "bssidx=%d, name=%s, len=%d\n", ifp->bssidx, name, len); + brcmf_dbg_hex_dump(BRCMF_FIL_ON(), data, + min_t(uint, len, MAX_HEX_DUMP_LEN), "data"); + + mutex_unlock(&drvr->proto_block); + return err; + +} + +s32 +brcmf_fil_bsscfg_int_set(struct brcmf_if *ifp, char *name, u32 data) +{ + __le32 data_le = cpu_to_le32(data); + + return brcmf_fil_bsscfg_data_set(ifp, name, &data_le, + sizeof(data_le)); +} + +s32 +brcmf_fil_bsscfg_int_get(struct brcmf_if *ifp, char *name, u32 *data) +{ + __le32 data_le = cpu_to_le32(*data); + s32 err; + + err = brcmf_fil_bsscfg_data_get(ifp, name, &data_le, + sizeof(data_le)); + if (err == 0) + *data = le32_to_cpu(data_le); + return err; +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/fwil.h b/drivers/net/wireless/brcm80211/brcmfmac/fwil.h new file mode 100644 index 0000000..16eb820 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/fwil.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2012 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _fwil_h_ +#define _fwil_h_ + +s32 brcmf_fil_cmd_data_set(struct brcmf_if *ifp, u32 cmd, void *data, u32 len); +s32 brcmf_fil_cmd_data_get(struct brcmf_if *ifp, u32 cmd, void *data, u32 len); +s32 brcmf_fil_cmd_int_set(struct brcmf_if *ifp, u32 cmd, u32 data); +s32 brcmf_fil_cmd_int_get(struct brcmf_if *ifp, u32 cmd, u32 *data); + +s32 brcmf_fil_iovar_data_set(struct brcmf_if *ifp, char *name, void *data, + u32 len); +s32 brcmf_fil_iovar_data_get(struct brcmf_if *ifp, char *name, void *data, + u32 len); +s32 brcmf_fil_iovar_int_set(struct brcmf_if *ifp, char *name, u32 data); +s32 brcmf_fil_iovar_int_get(struct brcmf_if *ifp, char *name, u32 *data); + +s32 brcmf_fil_bsscfg_data_set(struct brcmf_if *ifp, char *name, void *data, + u32 len); +s32 brcmf_fil_bsscfg_data_get(struct brcmf_if *ifp, char *name, void *data, + u32 len); +s32 brcmf_fil_bsscfg_int_set(struct brcmf_if *ifp, char *name, u32 data); +s32 brcmf_fil_bsscfg_int_get(struct brcmf_if *ifp, char *name, u32 *data); + +#endif /* _fwil_h_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/brcm80211/brcmfmac/fwil_types.h new file mode 100644 index 0000000..665ef69 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/fwil_types.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2012 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +#ifndef FWIL_TYPES_H_ +#define FWIL_TYPES_H_ + +#include + + +#define BRCMF_FIL_ACTION_FRAME_SIZE 1800 + +/* ARP Offload feature flags for arp_ol iovar */ +#define BRCMF_ARP_OL_AGENT 0x00000001 +#define BRCMF_ARP_OL_SNOOP 0x00000002 +#define BRCMF_ARP_OL_HOST_AUTO_REPLY 0x00000004 +#define BRCMF_ARP_OL_PEER_AUTO_REPLY 0x00000008 + + +enum brcmf_fil_p2p_if_types { + BRCMF_FIL_P2P_IF_CLIENT, + BRCMF_FIL_P2P_IF_GO, + BRCMF_FIL_P2P_IF_DYNBCN_GO, + BRCMF_FIL_P2P_IF_DEV, +}; + +struct brcmf_fil_p2p_if_le { + u8 addr[ETH_ALEN]; + __le16 type; + __le16 chspec; +}; + +struct brcmf_fil_chan_info_le { + __le32 hw_channel; + __le32 target_channel; + __le32 scan_channel; +}; + +struct brcmf_fil_action_frame_le { + u8 da[ETH_ALEN]; + __le16 len; + __le32 packet_id; + u8 data[BRCMF_FIL_ACTION_FRAME_SIZE]; +}; + +struct brcmf_fil_af_params_le { + __le32 channel; + __le32 dwell_time; + u8 bssid[ETH_ALEN]; + u8 pad[2]; + struct brcmf_fil_action_frame_le action_frame; +}; + +struct brcmf_fil_bss_enable_le { + __le32 bsscfg_idx; + __le32 enable; +}; + +#endif /* FWIL_TYPES_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/fwsignal.c b/drivers/net/wireless/brcm80211/brcmfmac/fwsignal.c new file mode 100644 index 0000000..5352dc1 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/fwsignal.c @@ -0,0 +1,2067 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "dhd.h" +#include "dhd_proto.h" +#include "dhd_dbg.h" +#include "dhd_bus.h" +#include "fwil.h" +#include "fwil_types.h" +#include "fweh.h" +#include "fwsignal.h" +#include "p2p.h" +#include "wl_cfg80211.h" + +/** + * DOC: Firmware Signalling + * + * Firmware can send signals to host and vice versa, which are passed in the + * data packets using TLV based header. This signalling layer is on top of the + * BDC bus protocol layer. + */ + +/* + * single definition for firmware-driver flow control tlv's. + * + * each tlv is specified by BRCMF_FWS_TLV_DEF(name, ID, length). + * A length value 0 indicates variable length tlv. + */ +#define BRCMF_FWS_TLV_DEFLIST \ + BRCMF_FWS_TLV_DEF(MAC_OPEN, 1, 1) \ + BRCMF_FWS_TLV_DEF(MAC_CLOSE, 2, 1) \ + BRCMF_FWS_TLV_DEF(MAC_REQUEST_CREDIT, 3, 2) \ + BRCMF_FWS_TLV_DEF(TXSTATUS, 4, 4) \ + BRCMF_FWS_TLV_DEF(PKTTAG, 5, 4) \ + BRCMF_FWS_TLV_DEF(MACDESC_ADD, 6, 8) \ + BRCMF_FWS_TLV_DEF(MACDESC_DEL, 7, 8) \ + BRCMF_FWS_TLV_DEF(RSSI, 8, 1) \ + BRCMF_FWS_TLV_DEF(INTERFACE_OPEN, 9, 1) \ + BRCMF_FWS_TLV_DEF(INTERFACE_CLOSE, 10, 1) \ + BRCMF_FWS_TLV_DEF(FIFO_CREDITBACK, 11, 6) \ + BRCMF_FWS_TLV_DEF(PENDING_TRAFFIC_BMP, 12, 2) \ + BRCMF_FWS_TLV_DEF(MAC_REQUEST_PACKET, 13, 3) \ + BRCMF_FWS_TLV_DEF(HOST_REORDER_RXPKTS, 14, 10) \ + BRCMF_FWS_TLV_DEF(TRANS_ID, 18, 6) \ + BRCMF_FWS_TLV_DEF(COMP_TXSTATUS, 19, 1) \ + BRCMF_FWS_TLV_DEF(FILLER, 255, 0) + +/* + * enum brcmf_fws_tlv_type - definition of tlv identifiers. + */ +#define BRCMF_FWS_TLV_DEF(name, id, len) \ + BRCMF_FWS_TYPE_ ## name = id, +enum brcmf_fws_tlv_type { + BRCMF_FWS_TLV_DEFLIST + BRCMF_FWS_TYPE_INVALID +}; +#undef BRCMF_FWS_TLV_DEF + +/* + * enum brcmf_fws_tlv_len - definition of tlv lengths. + */ +#define BRCMF_FWS_TLV_DEF(name, id, len) \ + BRCMF_FWS_TYPE_ ## name ## _LEN = (len), +enum brcmf_fws_tlv_len { + BRCMF_FWS_TLV_DEFLIST +}; +#undef BRCMF_FWS_TLV_DEF + +#ifdef DEBUG +/* + * brcmf_fws_tlv_names - array of tlv names. + */ +#define BRCMF_FWS_TLV_DEF(name, id, len) \ + { id, #name }, +static struct { + enum brcmf_fws_tlv_type id; + const char *name; +} brcmf_fws_tlv_names[] = { + BRCMF_FWS_TLV_DEFLIST +}; +#undef BRCMF_FWS_TLV_DEF + +static const char *brcmf_fws_get_tlv_name(enum brcmf_fws_tlv_type id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(brcmf_fws_tlv_names); i++) + if (brcmf_fws_tlv_names[i].id == id) + return brcmf_fws_tlv_names[i].name; + + return "INVALID"; +} +#else +static const char *brcmf_fws_get_tlv_name(enum brcmf_fws_tlv_type id) +{ + return "NODEBUG"; +} +#endif /* DEBUG */ + +/* + * flags used to enable tlv signalling from firmware. + */ +#define BRCMF_FWS_FLAGS_RSSI_SIGNALS 0x0001 +#define BRCMF_FWS_FLAGS_XONXOFF_SIGNALS 0x0002 +#define BRCMF_FWS_FLAGS_CREDIT_STATUS_SIGNALS 0x0004 +#define BRCMF_FWS_FLAGS_HOST_PROPTXSTATUS_ACTIVE 0x0008 +#define BRCMF_FWS_FLAGS_PSQ_GENERATIONFSM_ENABLE 0x0010 +#define BRCMF_FWS_FLAGS_PSQ_ZERO_BUFFER_ENABLE 0x0020 +#define BRCMF_FWS_FLAGS_HOST_RXREORDER_ACTIVE 0x0040 + +#define BRCMF_FWS_MAC_DESC_TABLE_SIZE 32 +#define BRCMF_FWS_MAC_DESC_ID_INVALID 0xff + +#define BRCMF_FWS_HOSTIF_FLOWSTATE_OFF 0 +#define BRCMF_FWS_HOSTIF_FLOWSTATE_ON 1 +#define BRCMF_FWS_FLOWCONTROL_HIWATER 128 +#define BRCMF_FWS_FLOWCONTROL_LOWATER 64 + +#define BRCMF_FWS_PSQ_PREC_COUNT ((NL80211_NUM_ACS + 1) * 2) +#define BRCMF_FWS_PSQ_LEN 256 + +#define BRCMF_FWS_HTOD_FLAG_PKTFROMHOST 0x01 +#define BRCMF_FWS_HTOD_FLAG_PKT_REQUESTED 0x02 + +#define BRCMF_FWS_RET_OK_NOSCHEDULE 0 +#define BRCMF_FWS_RET_OK_SCHEDULE 1 + +/** + * enum brcmf_fws_skb_state - indicates processing state of skb. + * + * @BRCMF_FWS_SKBSTATE_NEW: sk_buff is newly arrived in the driver. + * @BRCMF_FWS_SKBSTATE_DELAYED: sk_buff had to wait on queue. + * @BRCMF_FWS_SKBSTATE_SUPPRESSED: sk_buff has been suppressed by firmware. + */ +enum brcmf_fws_skb_state { + BRCMF_FWS_SKBSTATE_NEW, + BRCMF_FWS_SKBSTATE_DELAYED, + BRCMF_FWS_SKBSTATE_SUPPRESSED +}; + +/** + * struct brcmf_skbuff_cb - control buffer associated with skbuff. + * + * @if_flags: holds interface index and packet related flags. + * @htod: host to device packet identifier (used in PKTTAG tlv). + * @state: transmit state of the packet. + * @mac: descriptor related to destination for this packet. + * + * This information is stored in control buffer struct sk_buff::cb, which + * provides 48 bytes of storage so this structure should not exceed that. + */ +struct brcmf_skbuff_cb { + u16 if_flags; + u32 htod; + enum brcmf_fws_skb_state state; + struct brcmf_fws_mac_descriptor *mac; +}; + +/* + * macro casting skbuff control buffer to struct brcmf_skbuff_cb. + */ +#define brcmf_skbcb(skb) ((struct brcmf_skbuff_cb *)((skb)->cb)) + +/* + * sk_buff control if flags + * + * b[11] - packet sent upon firmware request. + * b[10] - packet only contains signalling data. + * b[9] - packet is a tx packet. + * b[8] - packet uses FIFO credit (non-pspoll). + * b[7] - interface in AP mode. + * b[6:4] - AC FIFO number. + * b[3:0] - interface index. + */ +#define BRCMF_SKB_IF_FLAGS_REQUESTED_MASK 0x0800 +#define BRCMF_SKB_IF_FLAGS_REQUESTED_SHIFT 11 +#define BRCMF_SKB_IF_FLAGS_SIGNAL_ONLY_MASK 0x0400 +#define BRCMF_SKB_IF_FLAGS_SIGNAL_ONLY_SHIFT 10 +#define BRCMF_SKB_IF_FLAGS_TRANSMIT_MASK 0x0200 +#define BRCMF_SKB_IF_FLAGS_TRANSMIT_SHIFT 9 +#define BRCMF_SKB_IF_FLAGS_CREDITCHECK_MASK 0x0100 +#define BRCMF_SKB_IF_FLAGS_CREDITCHECK_SHIFT 8 +#define BRCMF_SKB_IF_FLAGS_IF_AP_MASK 0x0080 +#define BRCMF_SKB_IF_FLAGS_IF_AP_SHIFT 7 +#define BRCMF_SKB_IF_FLAGS_FIFO_MASK 0x0070 +#define BRCMF_SKB_IF_FLAGS_FIFO_SHIFT 4 +#define BRCMF_SKB_IF_FLAGS_INDEX_MASK 0x000f +#define BRCMF_SKB_IF_FLAGS_INDEX_SHIFT 0 + +#define brcmf_skb_if_flags_set_field(skb, field, value) \ + brcmu_maskset16(&(brcmf_skbcb(skb)->if_flags), \ + BRCMF_SKB_IF_FLAGS_ ## field ## _MASK, \ + BRCMF_SKB_IF_FLAGS_ ## field ## _SHIFT, (value)) +#define brcmf_skb_if_flags_get_field(skb, field) \ + brcmu_maskget16(brcmf_skbcb(skb)->if_flags, \ + BRCMF_SKB_IF_FLAGS_ ## field ## _MASK, \ + BRCMF_SKB_IF_FLAGS_ ## field ## _SHIFT) + +/* + * sk_buff control packet identifier + * + * 32-bit packet identifier used in PKTTAG tlv from host to dongle. + * + * - Generated at the host (e.g. dhd) + * - Seen as a generic sequence number by firmware except for the flags field. + * + * Generation : b[31] => generation number for this packet [host->fw] + * OR, current generation number [fw->host] + * Flags : b[30:27] => command, status flags + * FIFO-AC : b[26:24] => AC-FIFO id + * h-slot : b[23:8] => hanger-slot + * freerun : b[7:0] => A free running counter + */ +#define BRCMF_SKB_HTOD_TAG_GENERATION_MASK 0x80000000 +#define BRCMF_SKB_HTOD_TAG_GENERATION_SHIFT 31 +#define BRCMF_SKB_HTOD_TAG_FLAGS_MASK 0x78000000 +#define BRCMF_SKB_HTOD_TAG_FLAGS_SHIFT 27 +#define BRCMF_SKB_HTOD_TAG_FIFO_MASK 0x07000000 +#define BRCMF_SKB_HTOD_TAG_FIFO_SHIFT 24 +#define BRCMF_SKB_HTOD_TAG_HSLOT_MASK 0x00ffff00 +#define BRCMF_SKB_HTOD_TAG_HSLOT_SHIFT 8 +#define BRCMF_SKB_HTOD_TAG_FREERUN_MASK 0x000000ff +#define BRCMF_SKB_HTOD_TAG_FREERUN_SHIFT 0 + +#define brcmf_skb_htod_tag_set_field(skb, field, value) \ + brcmu_maskset32(&(brcmf_skbcb(skb)->htod), \ + BRCMF_SKB_HTOD_TAG_ ## field ## _MASK, \ + BRCMF_SKB_HTOD_TAG_ ## field ## _SHIFT, (value)) +#define brcmf_skb_htod_tag_get_field(skb, field) \ + brcmu_maskget32(brcmf_skbcb(skb)->htod, \ + BRCMF_SKB_HTOD_TAG_ ## field ## _MASK, \ + BRCMF_SKB_HTOD_TAG_ ## field ## _SHIFT) + +#define BRCMF_FWS_TXSTAT_GENERATION_MASK 0x80000000 +#define BRCMF_FWS_TXSTAT_GENERATION_SHIFT 31 +#define BRCMF_FWS_TXSTAT_FLAGS_MASK 0x78000000 +#define BRCMF_FWS_TXSTAT_FLAGS_SHIFT 27 +#define BRCMF_FWS_TXSTAT_FIFO_MASK 0x07000000 +#define BRCMF_FWS_TXSTAT_FIFO_SHIFT 24 +#define BRCMF_FWS_TXSTAT_HSLOT_MASK 0x00FFFF00 +#define BRCMF_FWS_TXSTAT_HSLOT_SHIFT 8 +#define BRCMF_FWS_TXSTAT_PKTID_MASK 0x00FFFFFF +#define BRCMF_FWS_TXSTAT_PKTID_SHIFT 0 + +#define brcmf_txstatus_get_field(txs, field) \ + brcmu_maskget32(txs, BRCMF_FWS_TXSTAT_ ## field ## _MASK, \ + BRCMF_FWS_TXSTAT_ ## field ## _SHIFT) + +/* How long to defer borrowing in jiffies */ +#define BRCMF_FWS_BORROW_DEFER_PERIOD (HZ / 10) + +/** + * enum brcmf_fws_fifo - fifo indices used by dongle firmware. + * + * @BRCMF_FWS_FIFO_AC_BK: fifo for background traffic. + * @BRCMF_FWS_FIFO_AC_BE: fifo for best-effort traffic. + * @BRCMF_FWS_FIFO_AC_VI: fifo for video traffic. + * @BRCMF_FWS_FIFO_AC_VO: fifo for voice traffic. + * @BRCMF_FWS_FIFO_BCMC: fifo for broadcast/multicast (AP only). + * @BRCMF_FWS_FIFO_ATIM: fifo for ATIM (AP only). + * @BRCMF_FWS_FIFO_COUNT: number of fifos. + */ +enum brcmf_fws_fifo { + BRCMF_FWS_FIFO_AC_BK, + BRCMF_FWS_FIFO_AC_BE, + BRCMF_FWS_FIFO_AC_VI, + BRCMF_FWS_FIFO_AC_VO, + BRCMF_FWS_FIFO_BCMC, + BRCMF_FWS_FIFO_ATIM, + BRCMF_FWS_FIFO_COUNT +}; + +/** + * enum brcmf_fws_txstatus - txstatus flag values. + * + * @BRCMF_FWS_TXSTATUS_DISCARD: + * host is free to discard the packet. + * @BRCMF_FWS_TXSTATUS_CORE_SUPPRESS: + * 802.11 core suppressed the packet. + * @BRCMF_FWS_TXSTATUS_FW_PS_SUPPRESS: + * firmware suppress the packet as device is already in PS mode. + * @BRCMF_FWS_TXSTATUS_FW_TOSSED: + * firmware tossed the packet. + */ +enum brcmf_fws_txstatus { + BRCMF_FWS_TXSTATUS_DISCARD, + BRCMF_FWS_TXSTATUS_CORE_SUPPRESS, + BRCMF_FWS_TXSTATUS_FW_PS_SUPPRESS, + BRCMF_FWS_TXSTATUS_FW_TOSSED +}; + +enum brcmf_fws_fcmode { + BRCMF_FWS_FCMODE_NONE, + BRCMF_FWS_FCMODE_IMPLIED_CREDIT, + BRCMF_FWS_FCMODE_EXPLICIT_CREDIT +}; + +enum brcmf_fws_mac_desc_state { + BRCMF_FWS_STATE_OPEN = 1, + BRCMF_FWS_STATE_CLOSE +}; + +/** + * struct brcmf_fws_mac_descriptor - firmware signalling data per node/interface + * + * @occupied: slot is in use. + * @mac_handle: handle for mac entry determined by firmware. + * @interface_id: interface index. + * @state: current state. + * @suppressed: mac entry is suppressed. + * @generation: generation bit. + * @ac_bitmap: ac queue bitmap. + * @requested_credit: credits requested by firmware. + * @ea: ethernet address. + * @seq: per-node free-running sequence. + * @psq: power-save queue. + * @transit_count: packet in transit to firmware. + */ +struct brcmf_fws_mac_descriptor { + u8 occupied; + u8 mac_handle; + u8 interface_id; + u8 state; + bool suppressed; + u8 generation; + u8 ac_bitmap; + u8 requested_credit; + u8 requested_packet; + u8 ea[ETH_ALEN]; + u8 seq[BRCMF_FWS_FIFO_COUNT]; + struct pktq psq; + int transit_count; + int suppress_count; + int suppr_transit_count; + bool send_tim_signal; + u8 traffic_pending_bmp; + u8 traffic_lastreported_bmp; +}; + +#define BRCMF_FWS_HANGER_MAXITEMS 1024 + +/** + * enum brcmf_fws_hanger_item_state - state of hanger item. + * + * @BRCMF_FWS_HANGER_ITEM_STATE_FREE: item is free for use. + * @BRCMF_FWS_HANGER_ITEM_STATE_INUSE: item is in use. + * @BRCMF_FWS_HANGER_ITEM_STATE_INUSE_SUPPRESSED: item was suppressed. + */ +enum brcmf_fws_hanger_item_state { + BRCMF_FWS_HANGER_ITEM_STATE_FREE = 1, + BRCMF_FWS_HANGER_ITEM_STATE_INUSE, + BRCMF_FWS_HANGER_ITEM_STATE_INUSE_SUPPRESSED +}; + + +/** + * struct brcmf_fws_hanger_item - single entry for tx pending packet. + * + * @state: entry is either free or occupied. + * @gen: generation. + * @pkt: packet itself. + */ +struct brcmf_fws_hanger_item { + enum brcmf_fws_hanger_item_state state; + u8 gen; + struct sk_buff *pkt; +}; + +/** + * struct brcmf_fws_hanger - holds packets awaiting firmware txstatus. + * + * @pushed: packets pushed to await txstatus. + * @popped: packets popped upon handling txstatus. + * @failed_to_push: packets that could not be pushed. + * @failed_to_pop: packets that could not be popped. + * @failed_slotfind: packets for which failed to find an entry. + * @slot_pos: last returned item index for a free entry. + * @items: array of hanger items. + */ +struct brcmf_fws_hanger { + u32 pushed; + u32 popped; + u32 failed_to_push; + u32 failed_to_pop; + u32 failed_slotfind; + u32 slot_pos; + struct brcmf_fws_hanger_item items[BRCMF_FWS_HANGER_MAXITEMS]; +}; + +struct brcmf_fws_macdesc_table { + struct brcmf_fws_mac_descriptor nodes[BRCMF_FWS_MAC_DESC_TABLE_SIZE]; + struct brcmf_fws_mac_descriptor iface[BRCMF_MAX_IFS]; + struct brcmf_fws_mac_descriptor other; +}; + +struct brcmf_fws_info { + struct brcmf_pub *drvr; + struct brcmf_fws_stats stats; + struct brcmf_fws_hanger hanger; + enum brcmf_fws_fcmode fcmode; + struct brcmf_fws_macdesc_table desc; + struct workqueue_struct *fws_wq; + struct work_struct fws_dequeue_work; + u32 fifo_enqpkt[BRCMF_FWS_FIFO_COUNT]; + int fifo_credit[BRCMF_FWS_FIFO_COUNT]; + int credits_borrowed[BRCMF_FWS_FIFO_AC_VO + 1]; + int deq_node_pos[BRCMF_FWS_FIFO_COUNT]; + u32 fifo_credit_map; + u32 fifo_delay_map; + unsigned long borrow_defer_timestamp; +}; + +/* + * brcmf_fws_prio2fifo - mapping from 802.1d priority to firmware fifo index. + */ +static const int brcmf_fws_prio2fifo[] = { + BRCMF_FWS_FIFO_AC_BE, + BRCMF_FWS_FIFO_AC_BK, + BRCMF_FWS_FIFO_AC_BK, + BRCMF_FWS_FIFO_AC_BE, + BRCMF_FWS_FIFO_AC_VI, + BRCMF_FWS_FIFO_AC_VI, + BRCMF_FWS_FIFO_AC_VO, + BRCMF_FWS_FIFO_AC_VO +}; + +static int fcmode; +module_param(fcmode, int, S_IRUSR); +MODULE_PARM_DESC(fcmode, "mode of firmware signalled flow control"); + +#define BRCMF_FWS_TLV_DEF(name, id, len) \ + case BRCMF_FWS_TYPE_ ## name: \ + return len; + +/** + * brcmf_fws_get_tlv_len() - returns defined length for given tlv id. + * + * @fws: firmware-signalling information. + * @id: identifier of the TLV. + * + * Return: the specified length for the given TLV; Otherwise -EINVAL. + */ +static int brcmf_fws_get_tlv_len(struct brcmf_fws_info *fws, + enum brcmf_fws_tlv_type id) +{ + switch (id) { + BRCMF_FWS_TLV_DEFLIST + default: + fws->stats.tlv_invalid_type++; + break; + } + return -EINVAL; +} +#undef BRCMF_FWS_TLV_DEF + +static bool brcmf_fws_ifidx_match(struct sk_buff *skb, void *arg) +{ + u32 ifidx = brcmf_skb_if_flags_get_field(skb, INDEX); + return ifidx == *(int *)arg; +} + +static void brcmf_fws_psq_flush(struct brcmf_fws_info *fws, struct pktq *q, + int ifidx) +{ + bool (*matchfn)(struct sk_buff *, void *) = NULL; + struct sk_buff *skb; + int prec; + + if (ifidx != -1) + matchfn = brcmf_fws_ifidx_match; + for (prec = 0; prec < q->num_prec; prec++) { + skb = brcmu_pktq_pdeq_match(q, prec, matchfn, &ifidx); + while (skb) { + brcmu_pkt_buf_free_skb(skb); + skb = brcmu_pktq_pdeq_match(q, prec, matchfn, &ifidx); + } + } +} + +static void brcmf_fws_hanger_init(struct brcmf_fws_hanger *hanger) +{ + int i; + + brcmf_dbg(TRACE, "enter\n"); + memset(hanger, 0, sizeof(*hanger)); + for (i = 0; i < ARRAY_SIZE(hanger->items); i++) + hanger->items[i].state = BRCMF_FWS_HANGER_ITEM_STATE_FREE; +} + +static u32 brcmf_fws_hanger_get_free_slot(struct brcmf_fws_hanger *h) +{ + u32 i; + + brcmf_dbg(TRACE, "enter\n"); + i = (h->slot_pos + 1) % BRCMF_FWS_HANGER_MAXITEMS; + + while (i != h->slot_pos) { + if (h->items[i].state == BRCMF_FWS_HANGER_ITEM_STATE_FREE) { + h->slot_pos = i; + goto done; + } + i++; + if (i == BRCMF_FWS_HANGER_MAXITEMS) + i = 0; + } + brcmf_err("all slots occupied\n"); + h->failed_slotfind++; + i = BRCMF_FWS_HANGER_MAXITEMS; +done: + brcmf_dbg(TRACE, "exit: %d\n", i); + return i; +} + +static int brcmf_fws_hanger_pushpkt(struct brcmf_fws_hanger *h, + struct sk_buff *pkt, u32 slot_id) +{ + brcmf_dbg(TRACE, "enter\n"); + if (slot_id >= BRCMF_FWS_HANGER_MAXITEMS) + return -ENOENT; + + if (h->items[slot_id].state != BRCMF_FWS_HANGER_ITEM_STATE_FREE) { + brcmf_err("slot is not free\n"); + h->failed_to_push++; + return -EINVAL; + } + + h->items[slot_id].state = BRCMF_FWS_HANGER_ITEM_STATE_INUSE; + h->items[slot_id].pkt = pkt; + h->pushed++; + return 0; +} + +static int brcmf_fws_hanger_poppkt(struct brcmf_fws_hanger *h, + u32 slot_id, struct sk_buff **pktout, + bool remove_item) +{ + brcmf_dbg(TRACE, "enter\n"); + if (slot_id >= BRCMF_FWS_HANGER_MAXITEMS) + return -ENOENT; + + if (h->items[slot_id].state == BRCMF_FWS_HANGER_ITEM_STATE_FREE) { + brcmf_err("entry not in use\n"); + h->failed_to_pop++; + return -EINVAL; + } + + *pktout = h->items[slot_id].pkt; + if (remove_item) { + h->items[slot_id].state = BRCMF_FWS_HANGER_ITEM_STATE_FREE; + h->items[slot_id].pkt = NULL; + h->items[slot_id].gen = 0xff; + h->popped++; + } + return 0; +} + +static int brcmf_fws_hanger_mark_suppressed(struct brcmf_fws_hanger *h, + u32 slot_id, u8 gen) +{ + brcmf_dbg(TRACE, "enter\n"); + + if (slot_id >= BRCMF_FWS_HANGER_MAXITEMS) + return -ENOENT; + + h->items[slot_id].gen = gen; + + if (h->items[slot_id].state != BRCMF_FWS_HANGER_ITEM_STATE_INUSE) { + brcmf_err("entry not in use\n"); + return -EINVAL; + } + + h->items[slot_id].state = BRCMF_FWS_HANGER_ITEM_STATE_INUSE_SUPPRESSED; + return 0; +} + +static int brcmf_fws_hanger_get_genbit(struct brcmf_fws_hanger *hanger, + struct sk_buff *pkt, u32 slot_id, + int *gen) +{ + brcmf_dbg(TRACE, "enter\n"); + *gen = 0xff; + + if (slot_id >= BRCMF_FWS_HANGER_MAXITEMS) + return -ENOENT; + + if (hanger->items[slot_id].state == BRCMF_FWS_HANGER_ITEM_STATE_FREE) { + brcmf_err("slot not in use\n"); + return -EINVAL; + } + + *gen = hanger->items[slot_id].gen; + return 0; +} + +static void brcmf_fws_hanger_cleanup(struct brcmf_fws_info *fws, + bool (*fn)(struct sk_buff *, void *), + int ifidx) +{ + struct brcmf_fws_hanger *h = &fws->hanger; + struct sk_buff *skb; + int i; + enum brcmf_fws_hanger_item_state s; + + brcmf_dbg(TRACE, "enter: ifidx=%d\n", ifidx); + for (i = 0; i < ARRAY_SIZE(h->items); i++) { + s = h->items[i].state; + if (s == BRCMF_FWS_HANGER_ITEM_STATE_INUSE || + s == BRCMF_FWS_HANGER_ITEM_STATE_INUSE_SUPPRESSED) { + skb = h->items[i].pkt; + if (fn == NULL || fn(skb, &ifidx)) { + /* suppress packets freed from psq */ + if (s == BRCMF_FWS_HANGER_ITEM_STATE_INUSE) + brcmu_pkt_buf_free_skb(skb); + h->items[i].state = + BRCMF_FWS_HANGER_ITEM_STATE_FREE; + } + } + } +} + +static void brcmf_fws_init_mac_descriptor(struct brcmf_fws_mac_descriptor *desc, + u8 *addr, u8 ifidx) +{ + brcmf_dbg(TRACE, + "enter: desc %p ea=%pM, ifidx=%u\n", desc, addr, ifidx); + desc->occupied = 1; + desc->state = BRCMF_FWS_STATE_OPEN; + desc->requested_credit = 0; + /* depending on use may need ifp->bssidx instead */ + desc->interface_id = ifidx; + desc->ac_bitmap = 0xff; /* update this when handling APSD */ + if (addr) + memcpy(&desc->ea[0], addr, ETH_ALEN); +} + +static +void brcmf_fws_clear_mac_descriptor(struct brcmf_fws_mac_descriptor *desc) +{ + brcmf_dbg(TRACE, + "enter: ea=%pM, ifidx=%u\n", desc->ea, desc->interface_id); + desc->occupied = 0; + desc->state = BRCMF_FWS_STATE_CLOSE; + desc->requested_credit = 0; +} + +static struct brcmf_fws_mac_descriptor * +brcmf_fws_mac_descriptor_lookup(struct brcmf_fws_info *fws, u8 *ea) +{ + struct brcmf_fws_mac_descriptor *entry; + int i; + + brcmf_dbg(TRACE, "enter: ea=%pM\n", ea); + if (ea == NULL) + return ERR_PTR(-EINVAL); + + entry = &fws->desc.nodes[0]; + for (i = 0; i < ARRAY_SIZE(fws->desc.nodes); i++) { + if (entry->occupied && !memcmp(entry->ea, ea, ETH_ALEN)) + return entry; + entry++; + } + + return ERR_PTR(-ENOENT); +} + +static struct brcmf_fws_mac_descriptor* +brcmf_fws_find_mac_desc(struct brcmf_fws_info *fws, struct brcmf_if *ifp, + u8 *da) +{ + struct brcmf_fws_mac_descriptor *entry = &fws->desc.other; + bool multicast; + enum nl80211_iftype iftype; + + brcmf_dbg(TRACE, "enter: idx=%d\n", ifp->bssidx); + + multicast = is_multicast_ether_addr(da); + iftype = brcmf_cfg80211_get_iftype(ifp); + + /* Multicast destination and P2P clients get the interface entry. + * STA gets the interface entry if there is no exact match. For + * example, TDLS destinations have their own entry. + */ + entry = NULL; + if ((multicast || iftype == NL80211_IFTYPE_STATION || + iftype == NL80211_IFTYPE_P2P_CLIENT) && ifp->fws_desc) + entry = ifp->fws_desc; + + if (entry != NULL && iftype != NL80211_IFTYPE_STATION) + goto done; + + entry = brcmf_fws_mac_descriptor_lookup(fws, da); + if (IS_ERR(entry)) + entry = &fws->desc.other; + +done: + brcmf_dbg(TRACE, "exit: entry=%p\n", entry); + return entry; +} + +static bool brcmf_fws_mac_desc_closed(struct brcmf_fws_info *fws, + struct brcmf_fws_mac_descriptor *entry, + int fifo) +{ + struct brcmf_fws_mac_descriptor *if_entry; + bool closed; + + /* for unique destination entries the related interface + * may be closed. + */ + if (entry->mac_handle) { + if_entry = &fws->desc.iface[entry->interface_id]; + if (if_entry->state == BRCMF_FWS_STATE_CLOSE) + return true; + } + /* an entry is closed when the state is closed and + * the firmware did not request anything. + */ + closed = entry->state == BRCMF_FWS_STATE_CLOSE && + !entry->requested_credit && !entry->requested_packet; + + /* Or firmware does not allow traffic for given fifo */ + return closed || !(entry->ac_bitmap & BIT(fifo)); +} + +static void brcmf_fws_mac_desc_cleanup(struct brcmf_fws_info *fws, + struct brcmf_fws_mac_descriptor *entry, + int ifidx) +{ + brcmf_dbg(TRACE, "enter: entry=(ea=%pM, ifid=%d), ifidx=%d\n", + entry->ea, entry->interface_id, ifidx); + if (entry->occupied && (ifidx == -1 || ifidx == entry->interface_id)) { + brcmf_dbg(TRACE, "flush psq: ifidx=%d, qlen=%d\n", + ifidx, entry->psq.len); + brcmf_fws_psq_flush(fws, &entry->psq, ifidx); + entry->occupied = !!(entry->psq.len); + } +} + +static void brcmf_fws_bus_txq_cleanup(struct brcmf_fws_info *fws, + bool (*fn)(struct sk_buff *, void *), + int ifidx) +{ + struct brcmf_fws_hanger_item *hi; + struct pktq *txq; + struct sk_buff *skb; + int prec; + u32 hslot; + + brcmf_dbg(TRACE, "enter: ifidx=%d\n", ifidx); + txq = brcmf_bus_gettxq(fws->drvr->bus_if); + if (IS_ERR(txq)) { + brcmf_dbg(TRACE, "no txq to clean up\n"); + return; + } + + for (prec = 0; prec < txq->num_prec; prec++) { + skb = brcmu_pktq_pdeq_match(txq, prec, fn, &ifidx); + while (skb) { + hslot = brcmf_skb_htod_tag_get_field(skb, HSLOT); + hi = &fws->hanger.items[hslot]; + WARN_ON(skb != hi->pkt); + hi->state = BRCMF_FWS_HANGER_ITEM_STATE_FREE; + brcmu_pkt_buf_free_skb(skb); + skb = brcmu_pktq_pdeq_match(txq, prec, fn, &ifidx); + } + } +} + +static void brcmf_fws_cleanup(struct brcmf_fws_info *fws, int ifidx) +{ + int i; + struct brcmf_fws_mac_descriptor *table; + bool (*matchfn)(struct sk_buff *, void *) = NULL; + + brcmf_dbg(TRACE, "enter: ifidx=%d\n", ifidx); + if (fws == NULL) + return; + + if (ifidx != -1) + matchfn = brcmf_fws_ifidx_match; + + /* cleanup individual nodes */ + table = &fws->desc.nodes[0]; + for (i = 0; i < ARRAY_SIZE(fws->desc.nodes); i++) + brcmf_fws_mac_desc_cleanup(fws, &table[i], ifidx); + + brcmf_fws_mac_desc_cleanup(fws, &fws->desc.other, ifidx); + brcmf_fws_bus_txq_cleanup(fws, matchfn, ifidx); + brcmf_fws_hanger_cleanup(fws, matchfn, ifidx); +} + +static void brcmf_fws_tim_update(struct brcmf_fws_info *ctx, + struct brcmf_fws_mac_descriptor *entry, + int prec) +{ + brcmf_dbg(TRACE, "enter: ea=%pM\n", entry->ea); + if (entry->state == BRCMF_FWS_STATE_CLOSE) { + /* check delayedQ and suppressQ in one call using bitmap */ + if (brcmu_pktq_mlen(&entry->psq, 3 << (prec * 2)) == 0) + entry->traffic_pending_bmp = + entry->traffic_pending_bmp & ~NBITVAL(prec); + else + entry->traffic_pending_bmp = + entry->traffic_pending_bmp | NBITVAL(prec); + } + /* request a TIM update to firmware at the next piggyback opportunity */ + if (entry->traffic_lastreported_bmp != entry->traffic_pending_bmp) + entry->send_tim_signal = true; +} + +static void +brcmf_fws_flow_control_check(struct brcmf_fws_info *fws, struct pktq *pq, + u8 if_id) +{ + struct brcmf_if *ifp = fws->drvr->iflist[if_id]; + + if (WARN_ON(!ifp)) + return; + + brcmf_dbg(TRACE, + "enter: bssidx=%d, ifidx=%d\n", ifp->bssidx, ifp->ifidx); + + if ((ifp->netif_stop & BRCMF_NETIF_STOP_REASON_FWS_FC) && + pq->len <= BRCMF_FWS_FLOWCONTROL_LOWATER) + brcmf_txflowblock_if(ifp, + BRCMF_NETIF_STOP_REASON_FWS_FC, false); + if (!(ifp->netif_stop & BRCMF_NETIF_STOP_REASON_FWS_FC) && + pq->len >= BRCMF_FWS_FLOWCONTROL_HIWATER) + brcmf_txflowblock_if(ifp, BRCMF_NETIF_STOP_REASON_FWS_FC, true); + return; +} + +static int brcmf_fws_rssi_indicate(struct brcmf_fws_info *fws, s8 rssi) +{ + brcmf_dbg(CTL, "rssi %d\n", rssi); + return 0; +} + +static +int brcmf_fws_macdesc_indicate(struct brcmf_fws_info *fws, u8 type, u8 *data) +{ + struct brcmf_fws_mac_descriptor *entry, *existing; + u8 mac_handle; + u8 ifidx; + u8 *addr; + + mac_handle = *data++; + ifidx = *data++; + addr = data; + + entry = &fws->desc.nodes[mac_handle & 0x1F]; + if (type == BRCMF_FWS_TYPE_MACDESC_DEL) { + brcmf_dbg(TRACE, "deleting mac %pM idx %d\n", addr, ifidx); + if (entry->occupied) { + brcmf_fws_mac_desc_cleanup(fws, entry, -1); + brcmf_fws_clear_mac_descriptor(entry); + } else + fws->stats.mac_update_failed++; + return 0; + } + + brcmf_dbg(TRACE, + "add mac %pM handle %u idx %d\n", addr, mac_handle, ifidx); + existing = brcmf_fws_mac_descriptor_lookup(fws, addr); + if (IS_ERR(existing)) { + if (!entry->occupied) { + entry->mac_handle = mac_handle; + brcmf_fws_init_mac_descriptor(entry, addr, ifidx); + brcmu_pktq_init(&entry->psq, BRCMF_FWS_PSQ_PREC_COUNT, + BRCMF_FWS_PSQ_LEN); + } else { + fws->stats.mac_update_failed++; + } + } else { + if (entry != existing) { + brcmf_dbg(TRACE, "relocate mac\n"); + memcpy(entry, existing, + offsetof(struct brcmf_fws_mac_descriptor, psq)); + entry->mac_handle = mac_handle; + brcmf_fws_clear_mac_descriptor(existing); + } else { + brcmf_dbg(TRACE, "use existing\n"); + WARN_ON(entry->mac_handle != mac_handle); + /* TODO: what should we do here: continue, reinit, .. */ + } + } + return 0; +} + +static int brcmf_fws_macdesc_state_indicate(struct brcmf_fws_info *fws, + u8 type, u8 *data) +{ + struct brcmf_fws_mac_descriptor *entry; + u8 mac_handle; + int i; + + mac_handle = data[0]; + entry = &fws->desc.nodes[mac_handle & 0x1F]; + if (!entry->occupied) { + fws->stats.mac_ps_update_failed++; + return -ESRCH; + } + + /* a state update should wipe old credits? */ + entry->requested_credit = 0; + if (type == BRCMF_FWS_TYPE_MAC_OPEN) { + entry->state = BRCMF_FWS_STATE_OPEN; + return BRCMF_FWS_RET_OK_SCHEDULE; + } else { + entry->state = BRCMF_FWS_STATE_CLOSE; + for (i = BRCMF_FWS_FIFO_AC_BE; i < NL80211_NUM_ACS; i++) + brcmf_fws_tim_update(fws, entry, i); + } + return BRCMF_FWS_RET_OK_NOSCHEDULE; +} + +static int brcmf_fws_interface_state_indicate(struct brcmf_fws_info *fws, + u8 type, u8 *data) +{ + struct brcmf_fws_mac_descriptor *entry; + u8 ifidx; + int ret; + + ifidx = data[0]; + + brcmf_dbg(TRACE, "enter: ifidx=%d\n", ifidx); + if (ifidx >= BRCMF_MAX_IFS) { + ret = -ERANGE; + goto fail; + } + + entry = &fws->desc.iface[ifidx]; + if (!entry->occupied) { + ret = -ESRCH; + goto fail; + } + + switch (type) { + case BRCMF_FWS_TYPE_INTERFACE_OPEN: + entry->state = BRCMF_FWS_STATE_OPEN; + return BRCMF_FWS_RET_OK_SCHEDULE; + case BRCMF_FWS_TYPE_INTERFACE_CLOSE: + entry->state = BRCMF_FWS_STATE_CLOSE; + return BRCMF_FWS_RET_OK_NOSCHEDULE; + default: + ret = -EINVAL; + break; + } +fail: + fws->stats.if_update_failed++; + return ret; +} + +static int brcmf_fws_request_indicate(struct brcmf_fws_info *fws, u8 type, + u8 *data) +{ + struct brcmf_fws_mac_descriptor *entry; + + entry = &fws->desc.nodes[data[1] & 0x1F]; + if (!entry->occupied) { + if (type == BRCMF_FWS_TYPE_MAC_REQUEST_CREDIT) + fws->stats.credit_request_failed++; + else + fws->stats.packet_request_failed++; + return -ESRCH; + } + + if (type == BRCMF_FWS_TYPE_MAC_REQUEST_CREDIT) + entry->requested_credit = data[0]; + else + entry->requested_packet = data[0]; + + entry->ac_bitmap = data[2]; + return BRCMF_FWS_RET_OK_SCHEDULE; +} + +static void brcmf_fws_return_credits(struct brcmf_fws_info *fws, + u8 fifo, u8 credits) +{ + int lender_ac; + int *borrowed; + int *fifo_credit; + + if (!credits) + return; + + if ((fifo == BRCMF_FWS_FIFO_AC_BE) && + (fws->credits_borrowed[0])) { + for (lender_ac = BRCMF_FWS_FIFO_AC_VO; lender_ac >= 0; + lender_ac--) { + borrowed = &fws->credits_borrowed[lender_ac]; + if (*borrowed) { + fws->fifo_credit_map |= (1 << lender_ac); + fifo_credit = &fws->fifo_credit[lender_ac]; + if (*borrowed >= credits) { + *borrowed -= credits; + *fifo_credit += credits; + return; + } else { + credits -= *borrowed; + *fifo_credit += *borrowed; + *borrowed = 0; + } + } + } + } + + fws->fifo_credit_map |= 1 << fifo; + fws->fifo_credit[fifo] += credits; +} + +static void brcmf_fws_schedule_deq(struct brcmf_fws_info *fws) +{ + /* only schedule dequeue when there are credits for delayed traffic */ + if (fws->fifo_credit_map & fws->fifo_delay_map) + queue_work(fws->fws_wq, &fws->fws_dequeue_work); +} + +static void brcmf_skb_pick_up_credit(struct brcmf_fws_info *fws, int fifo, + struct sk_buff *p) +{ + struct brcmf_fws_mac_descriptor *entry = brcmf_skbcb(p)->mac; + + if (brcmf_skbcb(p)->if_flags & BRCMF_SKB_IF_FLAGS_CREDITCHECK_MASK) { + if (fws->fcmode != BRCMF_FWS_FCMODE_IMPLIED_CREDIT) + return; + brcmf_fws_return_credits(fws, fifo, 1); + } else { + /* + * if this packet did not count against FIFO credit, it + * must have taken a requested_credit from the destination + * entry (for pspoll etc.) + */ + if (!brcmf_skb_if_flags_get_field(p, REQUESTED)) + entry->requested_credit++; + } + brcmf_fws_schedule_deq(fws); +} + +static int brcmf_fws_enq(struct brcmf_fws_info *fws, + enum brcmf_fws_skb_state state, int fifo, + struct sk_buff *p) +{ + int prec = 2 * fifo; + u32 *qfull_stat = &fws->stats.delayq_full_error; + + struct brcmf_fws_mac_descriptor *entry; + + entry = brcmf_skbcb(p)->mac; + if (entry == NULL) { + brcmf_err("no mac descriptor found for skb %p\n", p); + return -ENOENT; + } + + brcmf_dbg(TRACE, "enter: ea=%pM, qlen=%d\n", entry->ea, entry->psq.len); + if (state == BRCMF_FWS_SKBSTATE_SUPPRESSED) { + prec += 1; + qfull_stat = &fws->stats.supprq_full_error; + } + + if (brcmu_pktq_penq(&entry->psq, prec, p) == NULL) { + *qfull_stat += 1; + return -ENFILE; + } + + /* increment total enqueued packet count */ + fws->fifo_delay_map |= 1 << fifo; + fws->fifo_enqpkt[fifo]++; + + /* update the sk_buff state */ + brcmf_skbcb(p)->state = state; + if (state == BRCMF_FWS_SKBSTATE_SUPPRESSED) + entry->suppress_count++; + + /* + * A packet has been pushed so update traffic + * availability bitmap, if applicable + */ + brcmf_fws_tim_update(fws, entry, fifo); + brcmf_fws_flow_control_check(fws, &entry->psq, + brcmf_skb_if_flags_get_field(p, INDEX)); + return 0; +} + +static struct sk_buff *brcmf_fws_deq(struct brcmf_fws_info *fws, int fifo) +{ + struct brcmf_fws_mac_descriptor *table; + struct brcmf_fws_mac_descriptor *entry; + struct sk_buff *p; + int use_credit = 1; + int num_nodes; + int node_pos; + int prec_out; + int pmsk; + int i; + + table = (struct brcmf_fws_mac_descriptor *)&fws->desc; + num_nodes = sizeof(fws->desc) / sizeof(struct brcmf_fws_mac_descriptor); + node_pos = fws->deq_node_pos[fifo]; + + for (i = 0; i < num_nodes; i++) { + entry = &table[(node_pos + i) % num_nodes]; + if (!entry->occupied || + brcmf_fws_mac_desc_closed(fws, entry, fifo)) + continue; + + if (entry->suppressed) + pmsk = 2; + else + pmsk = 3; + p = brcmu_pktq_mdeq(&entry->psq, pmsk << (fifo * 2), &prec_out); + if (p == NULL) { + if (entry->suppressed) { + if (entry->suppr_transit_count > + entry->suppress_count) + return NULL; + entry->suppressed = false; + p = brcmu_pktq_mdeq(&entry->psq, + 1 << (fifo * 2), &prec_out); + } + } + if (p == NULL) + continue; + + /* did the packet come from suppress sub-queue? */ + if (entry->requested_credit > 0) { + entry->requested_credit--; + /* + * if the packet was pulled out while destination is in + * closed state but had a non-zero packets requested, + * then this should not count against the FIFO credit. + * That is due to the fact that the firmware will + * most likely hold onto this packet until a suitable + * time later to push it to the appropriate AC FIFO. + */ + if (entry->state == BRCMF_FWS_STATE_CLOSE) + use_credit = 0; + } else if (entry->requested_packet > 0) { + entry->requested_packet--; + brcmf_skb_if_flags_set_field(p, REQUESTED, 1); + if (entry->state == BRCMF_FWS_STATE_CLOSE) + use_credit = 0; + } + brcmf_skb_if_flags_set_field(p, CREDITCHECK, use_credit); + + /* move dequeue position to ensure fair round-robin */ + fws->deq_node_pos[fifo] = (node_pos + i + 1) % num_nodes; + brcmf_fws_flow_control_check(fws, &entry->psq, + brcmf_skb_if_flags_get_field(p, + INDEX) + ); + /* + * A packet has been picked up, update traffic + * availability bitmap, if applicable + */ + brcmf_fws_tim_update(fws, entry, fifo); + + /* + * decrement total enqueued fifo packets and + * clear delay bitmap if done. + */ + fws->fifo_enqpkt[fifo]--; + if (fws->fifo_enqpkt[fifo] == 0) + fws->fifo_delay_map &= ~(1 << fifo); + goto done; + } + p = NULL; +done: + brcmf_dbg(TRACE, "exit: fifo %d skb %p\n", fifo, p); + return p; +} + +static int brcmf_fws_txstatus_suppressed(struct brcmf_fws_info *fws, int fifo, + struct sk_buff *skb, u32 genbit) +{ + struct brcmf_fws_mac_descriptor *entry = brcmf_skbcb(skb)->mac; + u32 hslot; + int ret; + + hslot = brcmf_skb_htod_tag_get_field(skb, HSLOT); + + /* this packet was suppressed */ + if (!entry->suppressed || entry->generation != genbit) { + entry->suppressed = true; + entry->suppress_count = brcmu_pktq_mlen(&entry->psq, + 1 << (fifo * 2 + 1)); + entry->suppr_transit_count = entry->transit_count; + } + + entry->generation = genbit; + + ret = brcmf_fws_enq(fws, BRCMF_FWS_SKBSTATE_SUPPRESSED, fifo, skb); + if (ret != 0) { + /* suppress q is full, drop this packet */ + brcmf_fws_hanger_poppkt(&fws->hanger, hslot, &skb, + true); + } else { + /* + * Mark suppressed to avoid a double free during + * wlfc cleanup + */ + brcmf_fws_hanger_mark_suppressed(&fws->hanger, hslot, + genbit); + entry->suppress_count++; + } + + return ret; +} + +static int +brcmf_fws_txstatus_process(struct brcmf_fws_info *fws, u8 flags, u32 hslot, + u32 genbit) +{ + u32 fifo; + int ret; + bool remove_from_hanger = true; + struct sk_buff *skb; + struct brcmf_fws_mac_descriptor *entry = NULL; + + brcmf_dbg(TRACE, "status: flags=0x%X, hslot=%d\n", + flags, hslot); + + if (flags == BRCMF_FWS_TXSTATUS_DISCARD) + fws->stats.txs_discard++; + else if (flags == BRCMF_FWS_TXSTATUS_CORE_SUPPRESS) { + fws->stats.txs_supp_core++; + remove_from_hanger = false; + } else if (flags == BRCMF_FWS_TXSTATUS_FW_PS_SUPPRESS) { + fws->stats.txs_supp_ps++; + remove_from_hanger = false; + } else if (flags == BRCMF_FWS_TXSTATUS_FW_TOSSED) + fws->stats.txs_tossed++; + else + brcmf_err("unexpected txstatus\n"); + + ret = brcmf_fws_hanger_poppkt(&fws->hanger, hslot, &skb, + remove_from_hanger); + if (ret != 0) { + brcmf_err("no packet in hanger slot: hslot=%d\n", hslot); + return ret; + } + + entry = brcmf_skbcb(skb)->mac; + if (WARN_ON(!entry)) { + brcmu_pkt_buf_free_skb(skb); + return -EINVAL; + } + + /* pick up the implicit credit from this packet */ + fifo = brcmf_skb_htod_tag_get_field(skb, FIFO); + brcmf_skb_pick_up_credit(fws, fifo, skb); + + if (!remove_from_hanger) + ret = brcmf_fws_txstatus_suppressed(fws, fifo, skb, genbit); + + if (remove_from_hanger || ret) { + entry->transit_count--; + if (entry->suppressed) + entry->suppr_transit_count--; + + brcmf_txfinalize(fws->drvr, skb, true); + } + return 0; +} + +static int brcmf_fws_fifocreditback_indicate(struct brcmf_fws_info *fws, + u8 *data) +{ + int i; + + if (fws->fcmode != BRCMF_FWS_FCMODE_EXPLICIT_CREDIT) { + brcmf_dbg(INFO, "ignored\n"); + return BRCMF_FWS_RET_OK_NOSCHEDULE; + } + + brcmf_dbg(TRACE, "enter: data %pM\n", data); + for (i = 0; i < BRCMF_FWS_FIFO_COUNT; i++) + brcmf_fws_return_credits(fws, i, data[i]); + + brcmf_dbg(INFO, "map: credit %x delay %x\n", fws->fifo_credit_map, + fws->fifo_delay_map); + return BRCMF_FWS_RET_OK_SCHEDULE; +} + +static int brcmf_fws_txstatus_indicate(struct brcmf_fws_info *fws, u8 *data) +{ + __le32 status_le; + u32 status; + u32 hslot; + u32 genbit; + u8 flags; + + fws->stats.txs_indicate++; + memcpy(&status_le, data, sizeof(status_le)); + status = le32_to_cpu(status_le); + flags = brcmf_txstatus_get_field(status, FLAGS); + hslot = brcmf_txstatus_get_field(status, HSLOT); + genbit = brcmf_txstatus_get_field(status, GENERATION); + + return brcmf_fws_txstatus_process(fws, flags, hslot, genbit); +} + +static int brcmf_fws_dbg_seqnum_check(struct brcmf_fws_info *fws, u8 *data) +{ + __le32 timestamp; + + memcpy(×tamp, &data[2], sizeof(timestamp)); + brcmf_dbg(INFO, "received: seq %d, timestamp %d\n", data[1], + le32_to_cpu(timestamp)); + return 0; +} + +/* using macro so sparse checking does not complain + * about locking imbalance. + */ +#define brcmf_fws_lock(drvr, flags) \ +do { \ + flags = 0; \ + spin_lock_irqsave(&((drvr)->fws_spinlock), (flags)); \ +} while (0) + +/* using macro so sparse checking does not complain + * about locking imbalance. + */ +#define brcmf_fws_unlock(drvr, flags) \ + spin_unlock_irqrestore(&((drvr)->fws_spinlock), (flags)) + +static int brcmf_fws_notify_credit_map(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, + void *data) +{ + struct brcmf_fws_info *fws = ifp->drvr->fws; + int i; + ulong flags; + u8 *credits = data; + + if (e->datalen < BRCMF_FWS_FIFO_COUNT) { + brcmf_err("event payload too small (%d)\n", e->datalen); + return -EINVAL; + } + + brcmf_dbg(TRACE, "enter: credits %pM\n", credits); + brcmf_fws_lock(ifp->drvr, flags); + for (i = 0; i < ARRAY_SIZE(fws->fifo_credit); i++) { + if (*credits) + fws->fifo_credit_map |= 1 << i; + else + fws->fifo_credit_map &= ~(1 << i); + fws->fifo_credit[i] = *credits++; + } + brcmf_fws_schedule_deq(fws); + brcmf_fws_unlock(ifp->drvr, flags); + return 0; +} + +int brcmf_fws_hdrpull(struct brcmf_pub *drvr, int ifidx, s16 signal_len, + struct sk_buff *skb) +{ + struct brcmf_fws_info *fws = drvr->fws; + ulong flags; + u8 *signal_data; + s16 data_len; + u8 type; + u8 len; + u8 *data; + s32 status; + s32 err; + + brcmf_dbg(TRACE, "enter: ifidx %d, skblen %u, sig %d\n", + ifidx, skb->len, signal_len); + + WARN_ON(signal_len > skb->len); + + /* if flow control disabled, skip to packet data and leave */ + if (!signal_len || !drvr->fw_signals) { + skb_pull(skb, signal_len); + return 0; + } + + /* lock during tlv parsing */ + brcmf_fws_lock(drvr, flags); + + fws->stats.header_pulls++; + data_len = signal_len; + signal_data = skb->data; + + status = BRCMF_FWS_RET_OK_NOSCHEDULE; + while (data_len > 0) { + /* extract tlv info */ + type = signal_data[0]; + + /* FILLER type is actually not a TLV, but + * a single byte that can be skipped. + */ + if (type == BRCMF_FWS_TYPE_FILLER) { + signal_data += 1; + data_len -= 1; + continue; + } + len = signal_data[1]; + data = signal_data + 2; + + brcmf_dbg(INFO, "tlv type=%d (%s), len=%d, data[0]=%d\n", type, + brcmf_fws_get_tlv_name(type), len, *data); + + /* abort parsing when length invalid */ + if (data_len < len + 2) + break; + + if (len != brcmf_fws_get_tlv_len(fws, type)) + break; + + err = BRCMF_FWS_RET_OK_NOSCHEDULE; + switch (type) { + case BRCMF_FWS_TYPE_HOST_REORDER_RXPKTS: + case BRCMF_FWS_TYPE_COMP_TXSTATUS: + break; + case BRCMF_FWS_TYPE_MACDESC_ADD: + case BRCMF_FWS_TYPE_MACDESC_DEL: + brcmf_fws_macdesc_indicate(fws, type, data); + break; + case BRCMF_FWS_TYPE_MAC_OPEN: + case BRCMF_FWS_TYPE_MAC_CLOSE: + err = brcmf_fws_macdesc_state_indicate(fws, type, data); + break; + case BRCMF_FWS_TYPE_INTERFACE_OPEN: + case BRCMF_FWS_TYPE_INTERFACE_CLOSE: + err = brcmf_fws_interface_state_indicate(fws, type, + data); + break; + case BRCMF_FWS_TYPE_MAC_REQUEST_CREDIT: + case BRCMF_FWS_TYPE_MAC_REQUEST_PACKET: + err = brcmf_fws_request_indicate(fws, type, data); + break; + case BRCMF_FWS_TYPE_TXSTATUS: + brcmf_fws_txstatus_indicate(fws, data); + break; + case BRCMF_FWS_TYPE_FIFO_CREDITBACK: + err = brcmf_fws_fifocreditback_indicate(fws, data); + break; + case BRCMF_FWS_TYPE_RSSI: + brcmf_fws_rssi_indicate(fws, *data); + break; + case BRCMF_FWS_TYPE_TRANS_ID: + brcmf_fws_dbg_seqnum_check(fws, data); + break; + case BRCMF_FWS_TYPE_PKTTAG: + case BRCMF_FWS_TYPE_PENDING_TRAFFIC_BMP: + default: + fws->stats.tlv_invalid_type++; + break; + } + if (err == BRCMF_FWS_RET_OK_SCHEDULE) + status = BRCMF_FWS_RET_OK_SCHEDULE; + signal_data += len + 2; + data_len -= len + 2; + } + + if (data_len != 0) + fws->stats.tlv_parse_failed++; + + if (status == BRCMF_FWS_RET_OK_SCHEDULE) + brcmf_fws_schedule_deq(fws); + + /* signalling processing result does + * not affect the actual ethernet packet. + */ + skb_pull(skb, signal_len); + + /* this may be a signal-only packet + */ + if (skb->len == 0) + fws->stats.header_only_pkt++; + + brcmf_fws_unlock(drvr, flags); + return 0; +} + +static int brcmf_fws_hdrpush(struct brcmf_fws_info *fws, struct sk_buff *skb) +{ + struct brcmf_fws_mac_descriptor *entry = brcmf_skbcb(skb)->mac; + u8 *wlh; + u16 data_offset = 0; + u8 fillers; + __le32 pkttag = cpu_to_le32(brcmf_skbcb(skb)->htod); + + brcmf_dbg(TRACE, "enter: ea=%pM, ifidx=%u, pkttag=0x%08X\n", + entry->ea, entry->interface_id, le32_to_cpu(pkttag)); + if (entry->send_tim_signal) + data_offset += 2 + BRCMF_FWS_TYPE_PENDING_TRAFFIC_BMP_LEN; + + /* +2 is for Type[1] and Len[1] in TLV, plus TIM signal */ + data_offset += 2 + BRCMF_FWS_TYPE_PKTTAG_LEN; + fillers = round_up(data_offset, 4) - data_offset; + data_offset += fillers; + + skb_push(skb, data_offset); + wlh = skb->data; + + wlh[0] = BRCMF_FWS_TYPE_PKTTAG; + wlh[1] = BRCMF_FWS_TYPE_PKTTAG_LEN; + memcpy(&wlh[2], &pkttag, sizeof(pkttag)); + wlh += BRCMF_FWS_TYPE_PKTTAG_LEN + 2; + + if (entry->send_tim_signal) { + entry->send_tim_signal = 0; + wlh[0] = BRCMF_FWS_TYPE_PENDING_TRAFFIC_BMP; + wlh[1] = BRCMF_FWS_TYPE_PENDING_TRAFFIC_BMP_LEN; + wlh[2] = entry->mac_handle; + wlh[3] = entry->traffic_pending_bmp; + wlh += BRCMF_FWS_TYPE_PENDING_TRAFFIC_BMP_LEN + 2; + entry->traffic_lastreported_bmp = entry->traffic_pending_bmp; + } + if (fillers) + memset(wlh, BRCMF_FWS_TYPE_FILLER, fillers); + + brcmf_proto_hdrpush(fws->drvr, brcmf_skb_if_flags_get_field(skb, INDEX), + data_offset >> 2, skb); + return 0; +} + +static int brcmf_fws_precommit_skb(struct brcmf_fws_info *fws, int fifo, + struct sk_buff *p) +{ + struct brcmf_skbuff_cb *skcb = brcmf_skbcb(p); + struct brcmf_fws_mac_descriptor *entry = skcb->mac; + int rc = 0; + bool header_needed; + int hslot = BRCMF_FWS_HANGER_MAXITEMS; + u8 free_ctr; + u8 ifidx; + u8 flags; + + header_needed = skcb->state != BRCMF_FWS_SKBSTATE_SUPPRESSED; + + if (header_needed) { + /* obtaining free slot may fail, but that will be caught + * by the hanger push. This assures the packet has a BDC + * header upon return. + */ + hslot = brcmf_fws_hanger_get_free_slot(&fws->hanger); + free_ctr = entry->seq[fifo]; + brcmf_skb_htod_tag_set_field(p, HSLOT, hslot); + brcmf_skb_htod_tag_set_field(p, FREERUN, free_ctr); + brcmf_skb_htod_tag_set_field(p, GENERATION, 1); + entry->transit_count++; + } + brcmf_skb_if_flags_set_field(p, TRANSMIT, 1); + brcmf_skb_htod_tag_set_field(p, FIFO, fifo); + + flags = BRCMF_FWS_HTOD_FLAG_PKTFROMHOST; + if (!(skcb->if_flags & BRCMF_SKB_IF_FLAGS_CREDITCHECK_MASK)) { + /* + Indicate that this packet is being sent in response to an + explicit request from the firmware side. + */ + flags |= BRCMF_FWS_HTOD_FLAG_PKT_REQUESTED; + } + brcmf_skb_htod_tag_set_field(p, FLAGS, flags); + if (header_needed) { + brcmf_fws_hdrpush(fws, p); + rc = brcmf_fws_hanger_pushpkt(&fws->hanger, p, hslot); + if (rc) + brcmf_err("hanger push failed: rc=%d\n", rc); + } else { + int gen; + + /* remove old header */ + rc = brcmf_proto_hdrpull(fws->drvr, false, &ifidx, p); + if (rc == 0) { + hslot = brcmf_skb_htod_tag_get_field(p, HSLOT); + brcmf_fws_hanger_get_genbit(&fws->hanger, p, + hslot, &gen); + brcmf_skb_htod_tag_set_field(p, GENERATION, gen); + + /* push new header */ + brcmf_fws_hdrpush(fws, p); + } + } + + return rc; +} + +static void +brcmf_fws_rollback_toq(struct brcmf_fws_info *fws, struct sk_buff *skb) +{ + /* + put the packet back to the head of queue + + - suppressed packet goes back to suppress sub-queue + - pull out the header, if new or delayed packet + + Note: hslot is used only when header removal is done. + */ + struct brcmf_fws_mac_descriptor *entry; + enum brcmf_fws_skb_state state; + struct sk_buff *pktout; + int rc = 0; + int fifo; + int hslot; + u8 ifidx; + + fifo = brcmf_skb_if_flags_get_field(skb, FIFO); + state = brcmf_skbcb(skb)->state; + entry = brcmf_skbcb(skb)->mac; + + if (entry != NULL) { + if (state == BRCMF_FWS_SKBSTATE_SUPPRESSED) { + /* wl-header is saved for suppressed packets */ + pktout = brcmu_pktq_penq_head(&entry->psq, 2 * fifo + 1, + skb); + if (pktout == NULL) { + brcmf_err("suppress queue full\n"); + rc = -ENOSPC; + } + } else { + hslot = brcmf_skb_htod_tag_get_field(skb, HSLOT); + + /* remove header first */ + rc = brcmf_proto_hdrpull(fws->drvr, false, &ifidx, skb); + if (rc) { + brcmf_err("header removal failed\n"); + /* free the hanger slot */ + brcmf_fws_hanger_poppkt(&fws->hanger, hslot, + &pktout, true); + rc = -EINVAL; + goto fail; + } + + /* delay-q packets are going to delay-q */ + pktout = brcmu_pktq_penq_head(&entry->psq, + 2 * fifo, skb); + if (pktout == NULL) { + brcmf_err("delay queue full\n"); + rc = -ENOSPC; + } + + /* free the hanger slot */ + brcmf_fws_hanger_poppkt(&fws->hanger, hslot, &pktout, + true); + + /* decrement sequence count */ + entry->seq[fifo]--; + } + /* + if this packet did not count against FIFO credit, it must have + taken a requested_credit from the firmware (for pspoll etc.) + */ + if (!(brcmf_skbcb(skb)->if_flags & + BRCMF_SKB_IF_FLAGS_CREDITCHECK_MASK)) + entry->requested_credit++; + } else { + brcmf_err("no mac entry linked\n"); + rc = -ENOENT; + } + + +fail: + if (rc) { + brcmf_txfinalize(fws->drvr, skb, false); + fws->stats.rollback_failed++; + } else + fws->stats.rollback_success++; +} + +static int brcmf_fws_borrow_credit(struct brcmf_fws_info *fws) +{ + int lender_ac; + + if (time_after(fws->borrow_defer_timestamp, jiffies)) + return -ENAVAIL; + + for (lender_ac = 0; lender_ac <= BRCMF_FWS_FIFO_AC_VO; lender_ac++) { + if (fws->fifo_credit[lender_ac]) { + fws->credits_borrowed[lender_ac]++; + fws->fifo_credit[lender_ac]--; + if (fws->fifo_credit[lender_ac] == 0) + fws->fifo_credit_map &= ~(1 << lender_ac); + brcmf_dbg(TRACE, "borrow credit from: %d\n", lender_ac); + return 0; + } + } + return -ENAVAIL; +} + +static int brcmf_fws_consume_credit(struct brcmf_fws_info *fws, int fifo, + struct sk_buff *skb) +{ + struct brcmf_fws_mac_descriptor *entry = brcmf_skbcb(skb)->mac; + int *credit = &fws->fifo_credit[fifo]; + int use_credit = 1; + + brcmf_dbg(TRACE, "enter: ac=%d, credits=%d\n", fifo, *credit); + + if (entry->requested_credit > 0) { + /* + * if the packet was pulled out while destination is in + * closed state but had a non-zero packets requested, + * then this should not count against the FIFO credit. + * That is due to the fact that the firmware will + * most likely hold onto this packet until a suitable + * time later to push it to the appropriate AC FIFO. + */ + entry->requested_credit--; + if (entry->state == BRCMF_FWS_STATE_CLOSE) + use_credit = 0; + } else if (entry->requested_packet > 0) { + entry->requested_packet--; + brcmf_skb_if_flags_set_field(skb, REQUESTED, 1); + if (entry->state == BRCMF_FWS_STATE_CLOSE) + use_credit = 0; + } + brcmf_skb_if_flags_set_field(skb, CREDITCHECK, use_credit); + if (!use_credit) { + brcmf_dbg(TRACE, "exit: no creditcheck set\n"); + return 0; + } + + if (fifo != BRCMF_FWS_FIFO_AC_BE) + fws->borrow_defer_timestamp = jiffies + + BRCMF_FWS_BORROW_DEFER_PERIOD; + + if (!(*credit)) { + /* Try to borrow a credit from other queue */ + if (fifo == BRCMF_FWS_FIFO_AC_BE && + brcmf_fws_borrow_credit(fws) == 0) + return 0; + + brcmf_dbg(TRACE, "exit: ac=%d, credits depleted\n", fifo); + return -ENAVAIL; + } + (*credit)--; + if (!(*credit)) + fws->fifo_credit_map &= ~(1 << fifo); + brcmf_dbg(TRACE, "exit: ac=%d, credits=%d\n", fifo, *credit); + return 0; +} + +static int brcmf_fws_commit_skb(struct brcmf_fws_info *fws, int fifo, + struct sk_buff *skb) +{ + struct brcmf_skbuff_cb *skcb = brcmf_skbcb(skb); + struct brcmf_fws_mac_descriptor *entry; + struct brcmf_bus *bus = fws->drvr->bus_if; + int rc; + + entry = skcb->mac; + if (IS_ERR(entry)) + return PTR_ERR(entry); + + rc = brcmf_fws_precommit_skb(fws, fifo, skb); + if (rc < 0) { + fws->stats.generic_error++; + goto rollback; + } + + rc = brcmf_bus_txdata(bus, skb); + if (rc < 0) + goto rollback; + + entry->seq[fifo]++; + fws->stats.pkt2bus++; + if (brcmf_skbcb(skb)->if_flags & BRCMF_SKB_IF_FLAGS_CREDITCHECK_MASK) { + fws->stats.send_pkts[fifo]++; + fws->stats.fifo_credits_sent[fifo]++; + } + + return rc; + +rollback: + brcmf_fws_rollback_toq(fws, skb); + return rc; +} + +int brcmf_fws_process_skb(struct brcmf_if *ifp, struct sk_buff *skb) +{ + struct brcmf_pub *drvr = ifp->drvr; + struct brcmf_fws_info *fws = drvr->fws; + struct brcmf_skbuff_cb *skcb = brcmf_skbcb(skb); + struct ethhdr *eh = (struct ethhdr *)(skb->data); + ulong flags; + int fifo = BRCMF_FWS_FIFO_BCMC; + bool multicast = is_multicast_ether_addr(eh->h_dest); + + /* determine the priority */ + if (!skb->priority) + skb->priority = cfg80211_classify8021d(skb); + + drvr->tx_multicast += !!multicast; + if (ntohs(eh->h_proto) == ETH_P_PAE) + atomic_inc(&ifp->pend_8021x_cnt); + + if (!brcmf_fws_fc_active(fws)) { + /* If the protocol uses a data header, apply it */ + brcmf_proto_hdrpush(drvr, ifp->ifidx, 0, skb); + + /* Use bus module to send data frame */ + return brcmf_bus_txdata(drvr->bus_if, skb); + } + + /* set control buffer information */ + skcb->if_flags = 0; + skcb->mac = brcmf_fws_find_mac_desc(fws, ifp, eh->h_dest); + skcb->state = BRCMF_FWS_SKBSTATE_NEW; + brcmf_skb_if_flags_set_field(skb, INDEX, ifp->ifidx); + if (!multicast) + fifo = brcmf_fws_prio2fifo[skb->priority]; + brcmf_skb_if_flags_set_field(skb, FIFO, fifo); + + brcmf_dbg(TRACE, "ea=%pM, multi=%d, fifo=%d\n", eh->h_dest, + multicast, fifo); + + brcmf_fws_lock(drvr, flags); + if (skcb->mac->suppressed || + brcmf_fws_mac_desc_closed(fws, skcb->mac, fifo) || + brcmu_pktq_mlen(&skcb->mac->psq, 3 << (fifo * 2)) || + (!multicast && + brcmf_fws_consume_credit(fws, fifo, skb) < 0)) { + /* enqueue the packet in delayQ */ + drvr->fws->fifo_delay_map |= 1 << fifo; + brcmf_fws_enq(fws, BRCMF_FWS_SKBSTATE_DELAYED, fifo, skb); + } else { + if (brcmf_fws_commit_skb(fws, fifo, skb)) + if (!multicast) + brcmf_skb_pick_up_credit(fws, fifo, skb); + } + brcmf_fws_unlock(drvr, flags); + return 0; +} + +void brcmf_fws_reset_interface(struct brcmf_if *ifp) +{ + struct brcmf_fws_mac_descriptor *entry = ifp->fws_desc; + + brcmf_dbg(TRACE, "enter: idx=%d\n", ifp->bssidx); + if (!entry) + return; + + brcmf_fws_init_mac_descriptor(entry, ifp->mac_addr, ifp->ifidx); +} + +void brcmf_fws_add_interface(struct brcmf_if *ifp) +{ + struct brcmf_fws_info *fws = ifp->drvr->fws; + struct brcmf_fws_mac_descriptor *entry; + + brcmf_dbg(TRACE, "enter: idx=%d, mac=%pM\n", + ifp->bssidx, ifp->mac_addr); + if (!ifp->ndev || !ifp->drvr->fw_signals) + return; + + entry = &fws->desc.iface[ifp->ifidx]; + ifp->fws_desc = entry; + brcmf_fws_init_mac_descriptor(entry, ifp->mac_addr, ifp->ifidx); + brcmu_pktq_init(&entry->psq, BRCMF_FWS_PSQ_PREC_COUNT, + BRCMF_FWS_PSQ_LEN); +} + +void brcmf_fws_del_interface(struct brcmf_if *ifp) +{ + struct brcmf_fws_mac_descriptor *entry = ifp->fws_desc; + ulong flags; + + brcmf_dbg(TRACE, "enter: idx=%d\n", ifp->bssidx); + if (!entry) + return; + + brcmf_fws_lock(ifp->drvr, flags); + ifp->fws_desc = NULL; + brcmf_fws_clear_mac_descriptor(entry); + brcmf_fws_cleanup(ifp->drvr->fws, ifp->ifidx); + brcmf_fws_unlock(ifp->drvr, flags); +} + +static void brcmf_fws_dequeue_worker(struct work_struct *worker) +{ + struct brcmf_fws_info *fws; + struct sk_buff *skb; + ulong flags; + int fifo; + int credit; + + fws = container_of(worker, struct brcmf_fws_info, fws_dequeue_work); + + brcmf_dbg(TRACE, "enter: fws=%p\n", fws); + brcmf_fws_lock(fws->drvr, flags); + for (fifo = NL80211_NUM_ACS; fifo >= 0; fifo--) { + brcmf_dbg(TRACE, "fifo %d credit %d\n", fifo, + fws->fifo_credit[fifo]); + for (credit = 0; credit < fws->fifo_credit[fifo]; /* nop */) { + skb = brcmf_fws_deq(fws, fifo); + if (!skb || brcmf_fws_commit_skb(fws, fifo, skb)) + break; + if (brcmf_skbcb(skb)->if_flags & + BRCMF_SKB_IF_FLAGS_CREDITCHECK_MASK) + credit++; + } + if ((fifo == BRCMF_FWS_FIFO_AC_BE) && + (credit == fws->fifo_credit[fifo])) { + fws->fifo_credit[fifo] -= credit; + while (brcmf_fws_borrow_credit(fws) == 0) { + skb = brcmf_fws_deq(fws, fifo); + if (!skb) { + brcmf_fws_return_credits(fws, fifo, 1); + break; + } + if (brcmf_fws_commit_skb(fws, fifo, skb)) { + brcmf_fws_return_credits(fws, fifo, 1); + break; + } + } + } else { + fws->fifo_credit[fifo] -= credit; + } + } + brcmf_fws_unlock(fws->drvr, flags); +} + +int brcmf_fws_init(struct brcmf_pub *drvr) +{ + u32 tlv = BRCMF_FWS_FLAGS_RSSI_SIGNALS; + int rc; + + if (!drvr->fw_signals) + return 0; + + spin_lock_init(&drvr->fws_spinlock); + + drvr->fws = kzalloc(sizeof(*(drvr->fws)), GFP_KERNEL); + if (!drvr->fws) { + rc = -ENOMEM; + goto fail; + } + + /* set linkage back */ + drvr->fws->drvr = drvr; + drvr->fws->fcmode = fcmode; + + drvr->fws->fws_wq = create_singlethread_workqueue("brcmf_fws_wq"); + if (drvr->fws->fws_wq == NULL) { + brcmf_err("workqueue creation failed\n"); + rc = -EBADF; + goto fail; + } + INIT_WORK(&drvr->fws->fws_dequeue_work, brcmf_fws_dequeue_worker); + + /* enable firmware signalling if fcmode active */ + if (drvr->fws->fcmode != BRCMF_FWS_FCMODE_NONE) + tlv |= BRCMF_FWS_FLAGS_XONXOFF_SIGNALS | + BRCMF_FWS_FLAGS_CREDIT_STATUS_SIGNALS | + BRCMF_FWS_FLAGS_HOST_PROPTXSTATUS_ACTIVE; + + rc = brcmf_fweh_register(drvr, BRCMF_E_FIFO_CREDIT_MAP, + brcmf_fws_notify_credit_map); + if (rc < 0) { + brcmf_err("register credit map handler failed\n"); + goto fail; + } + + /* setting the iovar may fail if feature is unsupported + * so leave the rc as is so driver initialization can + * continue. + */ + if (brcmf_fil_iovar_int_set(drvr->iflist[0], "tlv", tlv)) { + brcmf_err("failed to set bdcv2 tlv signaling\n"); + goto fail_event; + } + + brcmf_fws_hanger_init(&drvr->fws->hanger); + brcmf_fws_init_mac_descriptor(&drvr->fws->desc.other, NULL, 0); + brcmu_pktq_init(&drvr->fws->desc.other.psq, BRCMF_FWS_PSQ_PREC_COUNT, + BRCMF_FWS_PSQ_LEN); + + /* create debugfs file for statistics */ + brcmf_debugfs_create_fws_stats(drvr, &drvr->fws->stats); + + /* TODO: remove upon feature delivery */ + brcmf_err("%s bdcv2 tlv signaling [%x]\n", + drvr->fw_signals ? "enabled" : "disabled", tlv); + return 0; + +fail_event: + brcmf_fweh_unregister(drvr, BRCMF_E_FIFO_CREDIT_MAP); +fail: + brcmf_fws_deinit(drvr); + return rc; +} + +void brcmf_fws_deinit(struct brcmf_pub *drvr) +{ + struct brcmf_fws_info *fws = drvr->fws; + ulong flags; + + if (!fws) + return; + + /* disable firmware signalling entirely + * to avoid using the workqueue. + */ + drvr->fw_signals = false; + + if (drvr->fws->fws_wq) + destroy_workqueue(drvr->fws->fws_wq); + + /* cleanup */ + brcmf_fws_lock(drvr, flags); + brcmf_fws_cleanup(fws, -1); + drvr->fws = NULL; + brcmf_fws_unlock(drvr, flags); + + /* free top structure */ + kfree(fws); +} + +bool brcmf_fws_fc_active(struct brcmf_fws_info *fws) +{ + if (!fws) + return false; + + brcmf_dbg(TRACE, "enter: mode=%d\n", fws->fcmode); + return fws->fcmode != BRCMF_FWS_FCMODE_NONE; +} + +void brcmf_fws_bustxfail(struct brcmf_fws_info *fws, struct sk_buff *skb) +{ + ulong flags; + + brcmf_fws_lock(fws->drvr, flags); + brcmf_fws_txstatus_process(fws, BRCMF_FWS_TXSTATUS_FW_TOSSED, + brcmf_skb_htod_tag_get_field(skb, HSLOT), 0); + /* the packet never reached firmware so reclaim credit */ + if (fws->fcmode == BRCMF_FWS_FCMODE_EXPLICIT_CREDIT && + brcmf_skbcb(skb)->if_flags & BRCMF_SKB_IF_FLAGS_CREDITCHECK_MASK) { + brcmf_fws_return_credits(fws, + brcmf_skb_htod_tag_get_field(skb, + FIFO), + 1); + brcmf_fws_schedule_deq(fws); + } + brcmf_fws_unlock(fws->drvr, flags); +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/fwsignal.h b/drivers/net/wireless/brcm80211/brcmfmac/fwsignal.h new file mode 100644 index 0000000..fbe483d --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/fwsignal.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2012 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +#ifndef FWSIGNAL_H_ +#define FWSIGNAL_H_ + +int brcmf_fws_init(struct brcmf_pub *drvr); +void brcmf_fws_deinit(struct brcmf_pub *drvr); +bool brcmf_fws_fc_active(struct brcmf_fws_info *fws); +int brcmf_fws_hdrpull(struct brcmf_pub *drvr, int ifidx, s16 signal_len, + struct sk_buff *skb); +int brcmf_fws_process_skb(struct brcmf_if *ifp, struct sk_buff *skb); + +void brcmf_fws_reset_interface(struct brcmf_if *ifp); +void brcmf_fws_add_interface(struct brcmf_if *ifp); +void brcmf_fws_del_interface(struct brcmf_if *ifp); +void brcmf_fws_bustxfail(struct brcmf_fws_info *fws, struct sk_buff *skb); + +#endif /* FWSIGNAL_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/p2p.c b/drivers/net/wireless/brcm80211/brcmfmac/p2p.c new file mode 100644 index 0000000..79555f0 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/p2p.c @@ -0,0 +1,2449 @@ +/* + * Copyright (c) 2012 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "fwil.h" +#include "fwil_types.h" +#include "p2p.h" +#include "wl_cfg80211.h" + +/* parameters used for p2p escan */ +#define P2PAPI_SCAN_NPROBES 1 +#define P2PAPI_SCAN_DWELL_TIME_MS 80 +#define P2PAPI_SCAN_SOCIAL_DWELL_TIME_MS 40 +#define P2PAPI_SCAN_HOME_TIME_MS 60 +#define P2PAPI_SCAN_NPROBS_TIME_MS 30 +#define P2PAPI_SCAN_AF_SEARCH_DWELL_TIME_MS 100 +#define WL_SCAN_CONNECT_DWELL_TIME_MS 200 +#define WL_SCAN_JOIN_PROBE_INTERVAL_MS 20 + +#define BRCMF_P2P_WILDCARD_SSID "DIRECT-" +#define BRCMF_P2P_WILDCARD_SSID_LEN (sizeof(BRCMF_P2P_WILDCARD_SSID) - 1) + +#define SOCIAL_CHAN_1 1 +#define SOCIAL_CHAN_2 6 +#define SOCIAL_CHAN_3 11 +#define IS_P2P_SOCIAL_CHANNEL(channel) ((channel == SOCIAL_CHAN_1) || \ + (channel == SOCIAL_CHAN_2) || \ + (channel == SOCIAL_CHAN_3)) +#define BRCMF_P2P_TEMP_CHAN SOCIAL_CHAN_3 +#define SOCIAL_CHAN_CNT 3 +#define AF_PEER_SEARCH_CNT 2 + +#define BRCMF_SCB_TIMEOUT_VALUE 20 + +#define P2P_VER 9 /* P2P version: 9=WiFi P2P v1.0 */ +#define P2P_PUB_AF_CATEGORY 0x04 +#define P2P_PUB_AF_ACTION 0x09 +#define P2P_AF_CATEGORY 0x7f +#define P2P_OUI "\x50\x6F\x9A" /* P2P OUI */ +#define P2P_OUI_LEN 3 /* P2P OUI length */ + +/* Action Frame Constants */ +#define DOT11_ACTION_HDR_LEN 2 /* action frame category + action */ +#define DOT11_ACTION_CAT_OFF 0 /* category offset */ +#define DOT11_ACTION_ACT_OFF 1 /* action offset */ + +#define P2P_AF_DWELL_TIME 200 +#define P2P_AF_MIN_DWELL_TIME 100 +#define P2P_AF_MED_DWELL_TIME 400 +#define P2P_AF_LONG_DWELL_TIME 1000 +#define P2P_AF_TX_MAX_RETRY 1 +#define P2P_AF_MAX_WAIT_TIME 2000 +#define P2P_INVALID_CHANNEL -1 +#define P2P_CHANNEL_SYNC_RETRY 5 +#define P2P_AF_FRM_SCAN_MAX_WAIT 1500 +#define P2P_DEFAULT_SLEEP_TIME_VSDB 200 + +/* WiFi P2P Public Action Frame OUI Subtypes */ +#define P2P_PAF_GON_REQ 0 /* Group Owner Negotiation Req */ +#define P2P_PAF_GON_RSP 1 /* Group Owner Negotiation Rsp */ +#define P2P_PAF_GON_CONF 2 /* Group Owner Negotiation Confirm */ +#define P2P_PAF_INVITE_REQ 3 /* P2P Invitation Request */ +#define P2P_PAF_INVITE_RSP 4 /* P2P Invitation Response */ +#define P2P_PAF_DEVDIS_REQ 5 /* Device Discoverability Request */ +#define P2P_PAF_DEVDIS_RSP 6 /* Device Discoverability Response */ +#define P2P_PAF_PROVDIS_REQ 7 /* Provision Discovery Request */ +#define P2P_PAF_PROVDIS_RSP 8 /* Provision Discovery Response */ +#define P2P_PAF_SUBTYPE_INVALID 255 /* Invalid Subtype */ + +/* WiFi P2P Action Frame OUI Subtypes */ +#define P2P_AF_NOTICE_OF_ABSENCE 0 /* Notice of Absence */ +#define P2P_AF_PRESENCE_REQ 1 /* P2P Presence Request */ +#define P2P_AF_PRESENCE_RSP 2 /* P2P Presence Response */ +#define P2P_AF_GO_DISC_REQ 3 /* GO Discoverability Request */ + +/* P2P Service Discovery related */ +#define P2PSD_ACTION_CATEGORY 0x04 /* Public action frame */ +#define P2PSD_ACTION_ID_GAS_IREQ 0x0a /* GAS Initial Request AF */ +#define P2PSD_ACTION_ID_GAS_IRESP 0x0b /* GAS Initial Response AF */ +#define P2PSD_ACTION_ID_GAS_CREQ 0x0c /* GAS Comback Request AF */ +#define P2PSD_ACTION_ID_GAS_CRESP 0x0d /* GAS Comback Response AF */ + +/** + * struct brcmf_p2p_disc_st_le - set discovery state in firmware. + * + * @state: requested discovery state (see enum brcmf_p2p_disc_state). + * @chspec: channel parameter for %WL_P2P_DISC_ST_LISTEN state. + * @dwell: dwell time in ms for %WL_P2P_DISC_ST_LISTEN state. + */ +struct brcmf_p2p_disc_st_le { + u8 state; + __le16 chspec; + __le16 dwell; +}; + +/** + * enum brcmf_p2p_disc_state - P2P discovery state values + * + * @WL_P2P_DISC_ST_SCAN: P2P discovery with wildcard SSID and P2P IE. + * @WL_P2P_DISC_ST_LISTEN: P2P discovery off-channel for specified time. + * @WL_P2P_DISC_ST_SEARCH: P2P discovery with P2P wildcard SSID and P2P IE. + */ +enum brcmf_p2p_disc_state { + WL_P2P_DISC_ST_SCAN, + WL_P2P_DISC_ST_LISTEN, + WL_P2P_DISC_ST_SEARCH +}; + +/** + * struct brcmf_p2p_scan_le - P2P specific scan request. + * + * @type: type of scan method requested (values: 'E' or 'S'). + * @reserved: reserved (ignored). + * @eparams: parameters used for type 'E'. + * @sparams: parameters used for type 'S'. + */ +struct brcmf_p2p_scan_le { + u8 type; + u8 reserved[3]; + union { + struct brcmf_escan_params_le eparams; + struct brcmf_scan_params_le sparams; + }; +}; + +/** + * struct brcmf_p2p_pub_act_frame - WiFi P2P Public Action Frame + * + * @category: P2P_PUB_AF_CATEGORY + * @action: P2P_PUB_AF_ACTION + * @oui[3]: P2P_OUI + * @oui_type: OUI type - P2P_VER + * @subtype: OUI subtype - P2P_TYPE_* + * @dialog_token: nonzero, identifies req/rsp transaction + * @elts[1]: Variable length information elements. + */ +struct brcmf_p2p_pub_act_frame { + u8 category; + u8 action; + u8 oui[3]; + u8 oui_type; + u8 subtype; + u8 dialog_token; + u8 elts[1]; +}; + +/** + * struct brcmf_p2p_action_frame - WiFi P2P Action Frame + * + * @category: P2P_AF_CATEGORY + * @OUI[3]: OUI - P2P_OUI + * @type: OUI Type - P2P_VER + * @subtype: OUI Subtype - P2P_AF_* + * @dialog_token: nonzero, identifies req/resp tranaction + * @elts[1]: Variable length information elements. + */ +struct brcmf_p2p_action_frame { + u8 category; + u8 oui[3]; + u8 type; + u8 subtype; + u8 dialog_token; + u8 elts[1]; +}; + +/** + * struct brcmf_p2psd_gas_pub_act_frame - Wi-Fi GAS Public Action Frame + * + * @category: 0x04 Public Action Frame + * @action: 0x6c Advertisement Protocol + * @dialog_token: nonzero, identifies req/rsp transaction + * @query_data[1]: Query Data. SD gas ireq SD gas iresp + */ +struct brcmf_p2psd_gas_pub_act_frame { + u8 category; + u8 action; + u8 dialog_token; + u8 query_data[1]; +}; + +/** + * struct brcmf_config_af_params - Action Frame Parameters for tx. + * + * @mpc_onoff: To make sure to send successfully action frame, we have to + * turn off mpc 0: off, 1: on, (-1): do nothing + * @search_channel: 1: search peer's channel to send af + * extra_listen: keep the dwell time to get af response frame. + */ +struct brcmf_config_af_params { + s32 mpc_onoff; + bool search_channel; + bool extra_listen; +}; + +/** + * brcmf_p2p_is_pub_action() - true if p2p public type frame. + * + * @frame: action frame data. + * @frame_len: length of action frame data. + * + * Determine if action frame is p2p public action type + */ +static bool brcmf_p2p_is_pub_action(void *frame, u32 frame_len) +{ + struct brcmf_p2p_pub_act_frame *pact_frm; + + if (frame == NULL) + return false; + + pact_frm = (struct brcmf_p2p_pub_act_frame *)frame; + if (frame_len < sizeof(struct brcmf_p2p_pub_act_frame) - 1) + return false; + + if (pact_frm->category == P2P_PUB_AF_CATEGORY && + pact_frm->action == P2P_PUB_AF_ACTION && + pact_frm->oui_type == P2P_VER && + memcmp(pact_frm->oui, P2P_OUI, P2P_OUI_LEN) == 0) + return true; + + return false; +} + +/** + * brcmf_p2p_is_p2p_action() - true if p2p action type frame. + * + * @frame: action frame data. + * @frame_len: length of action frame data. + * + * Determine if action frame is p2p action type + */ +static bool brcmf_p2p_is_p2p_action(void *frame, u32 frame_len) +{ + struct brcmf_p2p_action_frame *act_frm; + + if (frame == NULL) + return false; + + act_frm = (struct brcmf_p2p_action_frame *)frame; + if (frame_len < sizeof(struct brcmf_p2p_action_frame) - 1) + return false; + + if (act_frm->category == P2P_AF_CATEGORY && + act_frm->type == P2P_VER && + memcmp(act_frm->oui, P2P_OUI, P2P_OUI_LEN) == 0) + return true; + + return false; +} + +/** + * brcmf_p2p_is_gas_action() - true if p2p gas action type frame. + * + * @frame: action frame data. + * @frame_len: length of action frame data. + * + * Determine if action frame is p2p gas action type + */ +static bool brcmf_p2p_is_gas_action(void *frame, u32 frame_len) +{ + struct brcmf_p2psd_gas_pub_act_frame *sd_act_frm; + + if (frame == NULL) + return false; + + sd_act_frm = (struct brcmf_p2psd_gas_pub_act_frame *)frame; + if (frame_len < sizeof(struct brcmf_p2psd_gas_pub_act_frame) - 1) + return false; + + if (sd_act_frm->category != P2PSD_ACTION_CATEGORY) + return false; + + if (sd_act_frm->action == P2PSD_ACTION_ID_GAS_IREQ || + sd_act_frm->action == P2PSD_ACTION_ID_GAS_IRESP || + sd_act_frm->action == P2PSD_ACTION_ID_GAS_CREQ || + sd_act_frm->action == P2PSD_ACTION_ID_GAS_CRESP) + return true; + + return false; +} + +/** + * brcmf_p2p_print_actframe() - debug print routine. + * + * @tx: Received or to be transmitted + * @frame: action frame data. + * @frame_len: length of action frame data. + * + * Print information about the p2p action frame + */ + +#ifdef DEBUG + +static void brcmf_p2p_print_actframe(bool tx, void *frame, u32 frame_len) +{ + struct brcmf_p2p_pub_act_frame *pact_frm; + struct brcmf_p2p_action_frame *act_frm; + struct brcmf_p2psd_gas_pub_act_frame *sd_act_frm; + + if (!frame || frame_len <= 2) + return; + + if (brcmf_p2p_is_pub_action(frame, frame_len)) { + pact_frm = (struct brcmf_p2p_pub_act_frame *)frame; + switch (pact_frm->subtype) { + case P2P_PAF_GON_REQ: + brcmf_dbg(TRACE, "%s P2P Group Owner Negotiation Req Frame\n", + (tx) ? "TX" : "RX"); + break; + case P2P_PAF_GON_RSP: + brcmf_dbg(TRACE, "%s P2P Group Owner Negotiation Rsp Frame\n", + (tx) ? "TX" : "RX"); + break; + case P2P_PAF_GON_CONF: + brcmf_dbg(TRACE, "%s P2P Group Owner Negotiation Confirm Frame\n", + (tx) ? "TX" : "RX"); + break; + case P2P_PAF_INVITE_REQ: + brcmf_dbg(TRACE, "%s P2P Invitation Request Frame\n", + (tx) ? "TX" : "RX"); + break; + case P2P_PAF_INVITE_RSP: + brcmf_dbg(TRACE, "%s P2P Invitation Response Frame\n", + (tx) ? "TX" : "RX"); + break; + case P2P_PAF_DEVDIS_REQ: + brcmf_dbg(TRACE, "%s P2P Device Discoverability Request Frame\n", + (tx) ? "TX" : "RX"); + break; + case P2P_PAF_DEVDIS_RSP: + brcmf_dbg(TRACE, "%s P2P Device Discoverability Response Frame\n", + (tx) ? "TX" : "RX"); + break; + case P2P_PAF_PROVDIS_REQ: + brcmf_dbg(TRACE, "%s P2P Provision Discovery Request Frame\n", + (tx) ? "TX" : "RX"); + break; + case P2P_PAF_PROVDIS_RSP: + brcmf_dbg(TRACE, "%s P2P Provision Discovery Response Frame\n", + (tx) ? "TX" : "RX"); + break; + default: + brcmf_dbg(TRACE, "%s Unknown P2P Public Action Frame\n", + (tx) ? "TX" : "RX"); + break; + } + } else if (brcmf_p2p_is_p2p_action(frame, frame_len)) { + act_frm = (struct brcmf_p2p_action_frame *)frame; + switch (act_frm->subtype) { + case P2P_AF_NOTICE_OF_ABSENCE: + brcmf_dbg(TRACE, "%s P2P Notice of Absence Frame\n", + (tx) ? "TX" : "RX"); + break; + case P2P_AF_PRESENCE_REQ: + brcmf_dbg(TRACE, "%s P2P Presence Request Frame\n", + (tx) ? "TX" : "RX"); + break; + case P2P_AF_PRESENCE_RSP: + brcmf_dbg(TRACE, "%s P2P Presence Response Frame\n", + (tx) ? "TX" : "RX"); + break; + case P2P_AF_GO_DISC_REQ: + brcmf_dbg(TRACE, "%s P2P Discoverability Request Frame\n", + (tx) ? "TX" : "RX"); + break; + default: + brcmf_dbg(TRACE, "%s Unknown P2P Action Frame\n", + (tx) ? "TX" : "RX"); + } + + } else if (brcmf_p2p_is_gas_action(frame, frame_len)) { + sd_act_frm = (struct brcmf_p2psd_gas_pub_act_frame *)frame; + switch (sd_act_frm->action) { + case P2PSD_ACTION_ID_GAS_IREQ: + brcmf_dbg(TRACE, "%s P2P GAS Initial Request\n", + (tx) ? "TX" : "RX"); + break; + case P2PSD_ACTION_ID_GAS_IRESP: + brcmf_dbg(TRACE, "%s P2P GAS Initial Response\n", + (tx) ? "TX" : "RX"); + break; + case P2PSD_ACTION_ID_GAS_CREQ: + brcmf_dbg(TRACE, "%s P2P GAS Comback Request\n", + (tx) ? "TX" : "RX"); + break; + case P2PSD_ACTION_ID_GAS_CRESP: + brcmf_dbg(TRACE, "%s P2P GAS Comback Response\n", + (tx) ? "TX" : "RX"); + break; + default: + brcmf_dbg(TRACE, "%s Unknown P2P GAS Frame\n", + (tx) ? "TX" : "RX"); + break; + } + } +} + +#else + +static void brcmf_p2p_print_actframe(bool tx, void *frame, u32 frame_len) +{ +} + +#endif + + +/** + * brcmf_p2p_set_firmware() - prepare firmware for peer-to-peer operation. + * + * @ifp: ifp to use for iovars (primary). + * @p2p_mac: mac address to configure for p2p_da_override + */ +static int brcmf_p2p_set_firmware(struct brcmf_if *ifp, u8 *p2p_mac) +{ + s32 ret = 0; + + brcmf_fil_cmd_int_set(ifp, BRCMF_C_DOWN, 1); + brcmf_fil_iovar_int_set(ifp, "apsta", 1); + brcmf_fil_cmd_int_set(ifp, BRCMF_C_UP, 1); + + /* In case of COB type, firmware has default mac address + * After Initializing firmware, we have to set current mac address to + * firmware for P2P device address + */ + ret = brcmf_fil_iovar_data_set(ifp, "p2p_da_override", p2p_mac, + ETH_ALEN); + if (ret) + brcmf_err("failed to update device address ret %d\n", ret); + + return ret; +} + +/** + * brcmf_p2p_generate_bss_mac() - derive mac addresses for P2P. + * + * @p2p: P2P specific data. + * @dev_addr: optional device address. + * + * P2P needs mac addresses for P2P device and interface. If no device + * address it specified, these are derived from the primary net device, ie. + * the permanent ethernet address of the device. + */ +static void brcmf_p2p_generate_bss_mac(struct brcmf_p2p_info *p2p, u8 *dev_addr) +{ + struct brcmf_if *pri_ifp = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif->ifp; + bool local_admin = false; + + if (!dev_addr || is_zero_ether_addr(dev_addr)) { + dev_addr = pri_ifp->mac_addr; + local_admin = true; + } + + /* Generate the P2P Device Address. This consists of the device's + * primary MAC address with the locally administered bit set. + */ + memcpy(p2p->dev_addr, dev_addr, ETH_ALEN); + if (local_admin) + p2p->dev_addr[0] |= 0x02; + + /* Generate the P2P Interface Address. If the discovery and connection + * BSSCFGs need to simultaneously co-exist, then this address must be + * different from the P2P Device Address, but also locally administered. + */ + memcpy(p2p->int_addr, p2p->dev_addr, ETH_ALEN); + p2p->int_addr[0] |= 0x02; + p2p->int_addr[4] ^= 0x80; +} + +/** + * brcmf_p2p_scan_is_p2p_request() - is cfg80211 scan request a P2P scan. + * + * @request: the scan request as received from cfg80211. + * + * returns true if one of the ssids in the request matches the + * P2P wildcard ssid; otherwise returns false. + */ +static bool brcmf_p2p_scan_is_p2p_request(struct cfg80211_scan_request *request) +{ + struct cfg80211_ssid *ssids = request->ssids; + int i; + + for (i = 0; i < request->n_ssids; i++) { + if (ssids[i].ssid_len != BRCMF_P2P_WILDCARD_SSID_LEN) + continue; + + brcmf_dbg(INFO, "comparing ssid \"%s\"", ssids[i].ssid); + if (!memcmp(BRCMF_P2P_WILDCARD_SSID, ssids[i].ssid, + BRCMF_P2P_WILDCARD_SSID_LEN)) + return true; + } + return false; +} + +/** + * brcmf_p2p_set_discover_state - set discover state in firmware. + * + * @ifp: low-level interface object. + * @state: discover state to set. + * @chanspec: channel parameters (for state @WL_P2P_DISC_ST_LISTEN only). + * @listen_ms: duration to listen (for state @WL_P2P_DISC_ST_LISTEN only). + */ +static s32 brcmf_p2p_set_discover_state(struct brcmf_if *ifp, u8 state, + u16 chanspec, u16 listen_ms) +{ + struct brcmf_p2p_disc_st_le discover_state; + s32 ret = 0; + brcmf_dbg(TRACE, "enter\n"); + + discover_state.state = state; + discover_state.chspec = cpu_to_le16(chanspec); + discover_state.dwell = cpu_to_le16(listen_ms); + ret = brcmf_fil_bsscfg_data_set(ifp, "p2p_state", &discover_state, + sizeof(discover_state)); + return ret; +} + +/** + * brcmf_p2p_deinit_discovery() - disable P2P device discovery. + * + * @p2p: P2P specific data. + * + * Resets the discovery state and disables it in firmware. + */ +static s32 brcmf_p2p_deinit_discovery(struct brcmf_p2p_info *p2p) +{ + struct brcmf_cfg80211_vif *vif; + + brcmf_dbg(TRACE, "enter\n"); + + /* Set the discovery state to SCAN */ + vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; + (void)brcmf_p2p_set_discover_state(vif->ifp, WL_P2P_DISC_ST_SCAN, 0, 0); + + /* Disable P2P discovery in the firmware */ + vif = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif; + (void)brcmf_fil_iovar_int_set(vif->ifp, "p2p_disc", 0); + + return 0; +} + +/** + * brcmf_p2p_enable_discovery() - initialize and configure discovery. + * + * @p2p: P2P specific data. + * + * Initializes the discovery device and configure the virtual interface. + */ +static int brcmf_p2p_enable_discovery(struct brcmf_p2p_info *p2p) +{ + struct brcmf_cfg80211_vif *vif; + s32 ret = 0; + + brcmf_dbg(TRACE, "enter\n"); + vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; + if (!vif) { + brcmf_err("P2P config device not available\n"); + ret = -EPERM; + goto exit; + } + + if (test_bit(BRCMF_P2P_STATUS_ENABLED, &p2p->status)) { + brcmf_dbg(INFO, "P2P config device already configured\n"); + goto exit; + } + + /* Re-initialize P2P Discovery in the firmware */ + vif = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif; + ret = brcmf_fil_iovar_int_set(vif->ifp, "p2p_disc", 1); + if (ret < 0) { + brcmf_err("set p2p_disc error\n"); + goto exit; + } + vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; + ret = brcmf_p2p_set_discover_state(vif->ifp, WL_P2P_DISC_ST_SCAN, 0, 0); + if (ret < 0) { + brcmf_err("unable to set WL_P2P_DISC_ST_SCAN\n"); + goto exit; + } + + /* + * Set wsec to any non-zero value in the discovery bsscfg + * to ensure our P2P probe responses have the privacy bit + * set in the 802.11 WPA IE. Some peer devices may not + * initiate WPS with us if this bit is not set. + */ + ret = brcmf_fil_bsscfg_int_set(vif->ifp, "wsec", AES_ENABLED); + if (ret < 0) { + brcmf_err("wsec error %d\n", ret); + goto exit; + } + + set_bit(BRCMF_P2P_STATUS_ENABLED, &p2p->status); +exit: + return ret; +} + +/** + * brcmf_p2p_escan() - initiate a P2P scan. + * + * @p2p: P2P specific data. + * @num_chans: number of channels to scan. + * @chanspecs: channel parameters for @num_chans channels. + * @search_state: P2P discover state to use. + * @action: scan action to pass to firmware. + * @bss_type: type of P2P bss. + */ +static s32 brcmf_p2p_escan(struct brcmf_p2p_info *p2p, u32 num_chans, + u16 chanspecs[], s32 search_state, u16 action, + enum p2p_bss_type bss_type) +{ + s32 ret = 0; + s32 memsize = offsetof(struct brcmf_p2p_scan_le, + eparams.params_le.channel_list); + s32 nprobes; + s32 active; + u32 i; + u8 *memblk; + struct brcmf_cfg80211_vif *vif; + struct brcmf_p2p_scan_le *p2p_params; + struct brcmf_scan_params_le *sparams; + struct brcmf_ssid ssid; + + memsize += num_chans * sizeof(__le16); + memblk = kzalloc(memsize, GFP_KERNEL); + if (!memblk) + return -ENOMEM; + + vif = p2p->bss_idx[bss_type].vif; + if (vif == NULL) { + brcmf_err("no vif for bss type %d\n", bss_type); + ret = -EINVAL; + goto exit; + } + + switch (search_state) { + case WL_P2P_DISC_ST_SEARCH: + /* + * If we in SEARCH STATE, we don't need to set SSID explictly + * because dongle use P2P WILDCARD internally by default + */ + /* use null ssid */ + ssid.SSID_len = 0; + memset(ssid.SSID, 0, sizeof(ssid.SSID)); + break; + case WL_P2P_DISC_ST_SCAN: + /* + * wpa_supplicant has p2p_find command with type social or + * progressive. For progressive, we need to set the ssid to + * P2P WILDCARD because we just do broadcast scan unless + * setting SSID. + */ + ssid.SSID_len = BRCMF_P2P_WILDCARD_SSID_LEN; + memcpy(ssid.SSID, BRCMF_P2P_WILDCARD_SSID, ssid.SSID_len); + break; + default: + brcmf_err(" invalid search state %d\n", search_state); + ret = -EINVAL; + goto exit; + } + + brcmf_p2p_set_discover_state(vif->ifp, search_state, 0, 0); + + /* + * set p2p scan parameters. + */ + p2p_params = (struct brcmf_p2p_scan_le *)memblk; + p2p_params->type = 'E'; + + /* determine the scan engine parameters */ + sparams = &p2p_params->eparams.params_le; + sparams->bss_type = DOT11_BSSTYPE_ANY; + if (p2p->cfg->active_scan) + sparams->scan_type = 0; + else + sparams->scan_type = 1; + + memset(&sparams->bssid, 0xFF, ETH_ALEN); + if (ssid.SSID_len) + memcpy(sparams->ssid_le.SSID, ssid.SSID, ssid.SSID_len); + sparams->ssid_le.SSID_len = cpu_to_le32(ssid.SSID_len); + sparams->home_time = cpu_to_le32(P2PAPI_SCAN_HOME_TIME_MS); + + /* + * SOCIAL_CHAN_CNT + 1 takes care of the Progressive scan + * supported by the supplicant. + */ + if (num_chans == SOCIAL_CHAN_CNT || num_chans == (SOCIAL_CHAN_CNT + 1)) + active = P2PAPI_SCAN_SOCIAL_DWELL_TIME_MS; + else if (num_chans == AF_PEER_SEARCH_CNT) + active = P2PAPI_SCAN_AF_SEARCH_DWELL_TIME_MS; + else if (wl_get_vif_state_all(p2p->cfg, BRCMF_VIF_STATUS_CONNECTED)) + active = -1; + else + active = P2PAPI_SCAN_DWELL_TIME_MS; + + /* Override scan params to find a peer for a connection */ + if (num_chans == 1) { + active = WL_SCAN_CONNECT_DWELL_TIME_MS; + /* WAR to sync with presence period of VSDB GO. + * send probe request more frequently + */ + nprobes = active / WL_SCAN_JOIN_PROBE_INTERVAL_MS; + } else { + nprobes = active / P2PAPI_SCAN_NPROBS_TIME_MS; + } + + if (nprobes <= 0) + nprobes = 1; + + brcmf_dbg(INFO, "nprobes # %d, active_time %d\n", nprobes, active); + sparams->active_time = cpu_to_le32(active); + sparams->nprobes = cpu_to_le32(nprobes); + sparams->passive_time = cpu_to_le32(-1); + sparams->channel_num = cpu_to_le32(num_chans & + BRCMF_SCAN_PARAMS_COUNT_MASK); + for (i = 0; i < num_chans; i++) + sparams->channel_list[i] = cpu_to_le16(chanspecs[i]); + + /* set the escan specific parameters */ + p2p_params->eparams.version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION); + p2p_params->eparams.action = cpu_to_le16(action); + p2p_params->eparams.sync_id = cpu_to_le16(0x1234); + /* perform p2p scan on primary device */ + ret = brcmf_fil_bsscfg_data_set(vif->ifp, "p2p_scan", memblk, memsize); + if (!ret) + set_bit(BRCMF_SCAN_STATUS_BUSY, &p2p->cfg->scan_status); +exit: + kfree(memblk); + return ret; +} + +/** + * brcmf_p2p_run_escan() - escan callback for peer-to-peer. + * + * @cfg: driver private data for cfg80211 interface. + * @ndev: net device for which scan is requested. + * @request: scan request from cfg80211. + * @action: scan action. + * + * Determines the P2P discovery state based to scan request parameters and + * validates the channels in the request. + */ +static s32 brcmf_p2p_run_escan(struct brcmf_cfg80211_info *cfg, + struct brcmf_if *ifp, + struct cfg80211_scan_request *request, + u16 action) +{ + struct brcmf_p2p_info *p2p = &cfg->p2p; + s32 err = 0; + s32 search_state = WL_P2P_DISC_ST_SCAN; + struct brcmf_cfg80211_vif *vif; + struct net_device *dev = NULL; + int i, num_nodfs = 0; + u16 *chanspecs; + + brcmf_dbg(TRACE, "enter\n"); + + if (!request) { + err = -EINVAL; + goto exit; + } + + if (request->n_channels) { + chanspecs = kcalloc(request->n_channels, sizeof(*chanspecs), + GFP_KERNEL); + if (!chanspecs) { + err = -ENOMEM; + goto exit; + } + vif = p2p->bss_idx[P2PAPI_BSSCFG_CONNECTION].vif; + if (vif) + dev = vif->wdev.netdev; + if (request->n_channels == 3 && + request->channels[0]->hw_value == SOCIAL_CHAN_1 && + request->channels[1]->hw_value == SOCIAL_CHAN_2 && + request->channels[2]->hw_value == SOCIAL_CHAN_3) { + /* SOCIAL CHANNELS 1, 6, 11 */ + search_state = WL_P2P_DISC_ST_SEARCH; + brcmf_dbg(INFO, "P2P SEARCH PHASE START\n"); + } else if (dev != NULL && vif->mode == WL_MODE_AP) { + /* If you are already a GO, then do SEARCH only */ + brcmf_dbg(INFO, "Already a GO. Do SEARCH Only\n"); + search_state = WL_P2P_DISC_ST_SEARCH; + } else { + brcmf_dbg(INFO, "P2P SCAN STATE START\n"); + } + + /* + * no P2P scanning on passive or DFS channels. + */ + for (i = 0; i < request->n_channels; i++) { + struct ieee80211_channel *chan = request->channels[i]; + + if (chan->flags & (IEEE80211_CHAN_RADAR | + IEEE80211_CHAN_PASSIVE_SCAN)) + continue; + + chanspecs[i] = channel_to_chanspec(&p2p->cfg->d11inf, + chan); + brcmf_dbg(INFO, "%d: chan=%d, channel spec=%x\n", + num_nodfs, chan->hw_value, chanspecs[i]); + num_nodfs++; + } + err = brcmf_p2p_escan(p2p, num_nodfs, chanspecs, search_state, + action, P2PAPI_BSSCFG_DEVICE); + } +exit: + if (err) + brcmf_err("error (%d)\n", err); + return err; +} + + +/** + * brcmf_p2p_find_listen_channel() - find listen channel in ie string. + * + * @ie: string of information elements. + * @ie_len: length of string. + * + * Scan ie for p2p ie and look for attribute 6 channel. If available determine + * channel and return it. + */ +static s32 brcmf_p2p_find_listen_channel(const u8 *ie, u32 ie_len) +{ + u8 channel_ie[5]; + s32 listen_channel; + s32 err; + + err = cfg80211_get_p2p_attr(ie, ie_len, + IEEE80211_P2P_ATTR_LISTEN_CHANNEL, + channel_ie, sizeof(channel_ie)); + if (err < 0) + return err; + + /* listen channel subel length format: */ + /* 3(country) + 1(op. class) + 1(chan num) */ + listen_channel = (s32)channel_ie[3 + 1]; + + if (listen_channel == SOCIAL_CHAN_1 || + listen_channel == SOCIAL_CHAN_2 || + listen_channel == SOCIAL_CHAN_3) { + brcmf_dbg(INFO, "Found my Listen Channel %d\n", listen_channel); + return listen_channel; + } + + return -EPERM; +} + + +/** + * brcmf_p2p_scan_prep() - prepare scan based on request. + * + * @wiphy: wiphy device. + * @request: scan request from cfg80211. + * @vif: vif on which scan request is to be executed. + * + * Prepare the scan appropriately for type of scan requested. Overrides the + * escan .run() callback for peer-to-peer scanning. + */ +int brcmf_p2p_scan_prep(struct wiphy *wiphy, + struct cfg80211_scan_request *request, + struct brcmf_cfg80211_vif *vif) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_p2p_info *p2p = &cfg->p2p; + int err = 0; + + if (brcmf_p2p_scan_is_p2p_request(request)) { + /* find my listen channel */ + err = brcmf_p2p_find_listen_channel(request->ie, + request->ie_len); + if (err < 0) + return err; + + p2p->afx_hdl.my_listen_chan = err; + + clear_bit(BRCMF_P2P_STATUS_GO_NEG_PHASE, &p2p->status); + brcmf_dbg(INFO, "P2P: GO_NEG_PHASE status cleared\n"); + + err = brcmf_p2p_enable_discovery(p2p); + if (err) + return err; + + vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; + + /* override .run_escan() callback. */ + cfg->escan_info.run = brcmf_p2p_run_escan; + } + err = brcmf_vif_set_mgmt_ie(vif, BRCMF_VNDR_IE_PRBREQ_FLAG, + request->ie, request->ie_len); + return err; +} + + +/** + * brcmf_p2p_discover_listen() - set firmware to discover listen state. + * + * @p2p: p2p device. + * @channel: channel nr for discover listen. + * @duration: time in ms to stay on channel. + * + */ +static s32 +brcmf_p2p_discover_listen(struct brcmf_p2p_info *p2p, u16 channel, u32 duration) +{ + struct brcmf_cfg80211_vif *vif; + struct brcmu_chan ch; + s32 err = 0; + + vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; + if (!vif) { + brcmf_err("Discovery is not set, so we have nothing to do\n"); + err = -EPERM; + goto exit; + } + + if (test_bit(BRCMF_P2P_STATUS_DISCOVER_LISTEN, &p2p->status)) { + brcmf_err("Previous LISTEN is not completed yet\n"); + /* WAR: prevent cookie mismatch in wpa_supplicant return OK */ + goto exit; + } + + ch.chnum = channel; + ch.bw = BRCMU_CHAN_BW_20; + p2p->cfg->d11inf.encchspec(&ch); + err = brcmf_p2p_set_discover_state(vif->ifp, WL_P2P_DISC_ST_LISTEN, + ch.chspec, (u16)duration); + if (!err) { + set_bit(BRCMF_P2P_STATUS_DISCOVER_LISTEN, &p2p->status); + p2p->remain_on_channel_cookie++; + } +exit: + return err; +} + + +/** + * brcmf_p2p_remain_on_channel() - put device on channel and stay there. + * + * @wiphy: wiphy device. + * @channel: channel to stay on. + * @duration: time in ms to remain on channel. + * + */ +int brcmf_p2p_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, + struct ieee80211_channel *channel, + unsigned int duration, u64 *cookie) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_p2p_info *p2p = &cfg->p2p; + s32 err; + u16 channel_nr; + + channel_nr = ieee80211_frequency_to_channel(channel->center_freq); + brcmf_dbg(TRACE, "Enter, channel: %d, duration ms (%d)\n", channel_nr, + duration); + + err = brcmf_p2p_enable_discovery(p2p); + if (err) + goto exit; + err = brcmf_p2p_discover_listen(p2p, channel_nr, duration); + if (err) + goto exit; + + memcpy(&p2p->remain_on_channel, channel, sizeof(*channel)); + *cookie = p2p->remain_on_channel_cookie; + cfg80211_ready_on_channel(wdev, *cookie, channel, duration, GFP_KERNEL); + +exit: + return err; +} + + +/** + * brcmf_p2p_notify_listen_complete() - p2p listen has completed. + * + * @ifp: interfac control. + * @e: event message. Not used, to make it usable for fweh event dispatcher. + * @data: payload of message. Not used. + * + */ +int brcmf_p2p_notify_listen_complete(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, + void *data) +{ + struct brcmf_cfg80211_info *cfg = ifp->drvr->config; + struct brcmf_p2p_info *p2p = &cfg->p2p; + + brcmf_dbg(TRACE, "Enter\n"); + if (test_and_clear_bit(BRCMF_P2P_STATUS_DISCOVER_LISTEN, + &p2p->status)) { + if (test_and_clear_bit(BRCMF_P2P_STATUS_WAITING_NEXT_AF_LISTEN, + &p2p->status)) { + clear_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, + &p2p->status); + brcmf_dbg(INFO, "Listen DONE, wake up wait_next_af\n"); + complete(&p2p->wait_next_af); + } + + cfg80211_remain_on_channel_expired(&ifp->vif->wdev, + p2p->remain_on_channel_cookie, + &p2p->remain_on_channel, + GFP_KERNEL); + } + return 0; +} + + +/** + * brcmf_p2p_cancel_remain_on_channel() - cancel p2p listen state. + * + * @ifp: interfac control. + * + */ +void brcmf_p2p_cancel_remain_on_channel(struct brcmf_if *ifp) +{ + if (!ifp) + return; + brcmf_p2p_set_discover_state(ifp, WL_P2P_DISC_ST_SCAN, 0, 0); + brcmf_p2p_notify_listen_complete(ifp, NULL, NULL); +} + + +/** + * brcmf_p2p_act_frm_search() - search function for action frame. + * + * @p2p: p2p device. + * channel: channel on which action frame is to be trasmitted. + * + * search function to reach at common channel to send action frame. When + * channel is 0 then all social channels will be used to send af + */ +static s32 brcmf_p2p_act_frm_search(struct brcmf_p2p_info *p2p, u16 channel) +{ + s32 err; + u32 channel_cnt; + u16 *default_chan_list; + u32 i; + struct brcmu_chan ch; + + brcmf_dbg(TRACE, "Enter\n"); + + if (channel) + channel_cnt = AF_PEER_SEARCH_CNT; + else + channel_cnt = SOCIAL_CHAN_CNT; + default_chan_list = kzalloc(channel_cnt * sizeof(*default_chan_list), + GFP_KERNEL); + if (default_chan_list == NULL) { + brcmf_err("channel list allocation failed\n"); + err = -ENOMEM; + goto exit; + } + ch.bw = BRCMU_CHAN_BW_20; + if (channel) { + ch.chnum = channel; + p2p->cfg->d11inf.encchspec(&ch); + /* insert same channel to the chan_list */ + for (i = 0; i < channel_cnt; i++) + default_chan_list[i] = ch.chspec; + } else { + ch.chnum = SOCIAL_CHAN_1; + p2p->cfg->d11inf.encchspec(&ch); + default_chan_list[0] = ch.chspec; + ch.chnum = SOCIAL_CHAN_2; + p2p->cfg->d11inf.encchspec(&ch); + default_chan_list[1] = ch.chspec; + ch.chnum = SOCIAL_CHAN_3; + p2p->cfg->d11inf.encchspec(&ch); + default_chan_list[2] = ch.chspec; + } + err = brcmf_p2p_escan(p2p, channel_cnt, default_chan_list, + WL_P2P_DISC_ST_SEARCH, WL_ESCAN_ACTION_START, + P2PAPI_BSSCFG_DEVICE); + kfree(default_chan_list); +exit: + return err; +} + + +/** + * brcmf_p2p_afx_handler() - afx worker thread. + * + * @work: + * + */ +static void brcmf_p2p_afx_handler(struct work_struct *work) +{ + struct afx_hdl *afx_hdl = container_of(work, struct afx_hdl, afx_work); + struct brcmf_p2p_info *p2p = container_of(afx_hdl, + struct brcmf_p2p_info, + afx_hdl); + s32 err; + + if (!afx_hdl->is_active) + return; + + if (afx_hdl->is_listen && afx_hdl->my_listen_chan) + /* 100ms ~ 300ms */ + err = brcmf_p2p_discover_listen(p2p, afx_hdl->my_listen_chan, + 100 * (1 + prandom_u32() % 3)); + else + err = brcmf_p2p_act_frm_search(p2p, afx_hdl->peer_listen_chan); + + if (err) { + brcmf_err("ERROR occurred! value is (%d)\n", err); + if (test_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, + &p2p->status)) + complete(&afx_hdl->act_frm_scan); + } +} + + +/** + * brcmf_p2p_af_searching_channel() - search channel. + * + * @p2p: p2p device info struct. + * + */ +static s32 brcmf_p2p_af_searching_channel(struct brcmf_p2p_info *p2p) +{ + struct afx_hdl *afx_hdl = &p2p->afx_hdl; + struct brcmf_cfg80211_vif *pri_vif; + unsigned long duration; + s32 retry; + + brcmf_dbg(TRACE, "Enter\n"); + + pri_vif = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif; + + INIT_COMPLETION(afx_hdl->act_frm_scan); + set_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status); + afx_hdl->is_active = true; + afx_hdl->peer_chan = P2P_INVALID_CHANNEL; + + /* Loop to wait until we find a peer's channel or the + * pending action frame tx is cancelled. + */ + retry = 0; + duration = msecs_to_jiffies(P2P_AF_FRM_SCAN_MAX_WAIT); + while ((retry < P2P_CHANNEL_SYNC_RETRY) && + (afx_hdl->peer_chan == P2P_INVALID_CHANNEL)) { + afx_hdl->is_listen = false; + brcmf_dbg(TRACE, "Scheduling action frame for sending.. (%d)\n", + retry); + /* search peer on peer's listen channel */ + schedule_work(&afx_hdl->afx_work); + wait_for_completion_timeout(&afx_hdl->act_frm_scan, duration); + if ((afx_hdl->peer_chan != P2P_INVALID_CHANNEL) || + (!test_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, + &p2p->status))) + break; + + if (afx_hdl->my_listen_chan) { + brcmf_dbg(TRACE, "Scheduling listen peer, channel=%d\n", + afx_hdl->my_listen_chan); + /* listen on my listen channel */ + afx_hdl->is_listen = true; + schedule_work(&afx_hdl->afx_work); + wait_for_completion_timeout(&afx_hdl->act_frm_scan, + duration); + } + if ((afx_hdl->peer_chan != P2P_INVALID_CHANNEL) || + (!test_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, + &p2p->status))) + break; + retry++; + + /* if sta is connected or connecting, sleep for a while before + * retry af tx or finding a peer + */ + if (test_bit(BRCMF_VIF_STATUS_CONNECTED, &pri_vif->sme_state) || + test_bit(BRCMF_VIF_STATUS_CONNECTING, &pri_vif->sme_state)) + msleep(P2P_DEFAULT_SLEEP_TIME_VSDB); + } + + brcmf_dbg(TRACE, "Completed search/listen peer_chan=%d\n", + afx_hdl->peer_chan); + afx_hdl->is_active = false; + + clear_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status); + + return afx_hdl->peer_chan; +} + + +/** + * brcmf_p2p_scan_finding_common_channel() - was escan used for finding channel + * + * @cfg: common configuration struct. + * @bi: bss info struct, result from scan. + * + */ +bool brcmf_p2p_scan_finding_common_channel(struct brcmf_cfg80211_info *cfg, + struct brcmf_bss_info_le *bi) + +{ + struct brcmf_p2p_info *p2p = &cfg->p2p; + struct afx_hdl *afx_hdl = &p2p->afx_hdl; + struct brcmu_chan ch; + u8 *ie; + s32 err; + u8 p2p_dev_addr[ETH_ALEN]; + + if (!test_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status)) + return false; + + if (bi == NULL) { + brcmf_dbg(TRACE, "ACTION FRAME SCAN Done\n"); + if (afx_hdl->peer_chan == P2P_INVALID_CHANNEL) + complete(&afx_hdl->act_frm_scan); + return true; + } + + ie = ((u8 *)bi) + le16_to_cpu(bi->ie_offset); + memset(p2p_dev_addr, 0, sizeof(p2p_dev_addr)); + err = cfg80211_get_p2p_attr(ie, le32_to_cpu(bi->ie_length), + IEEE80211_P2P_ATTR_DEVICE_INFO, + p2p_dev_addr, sizeof(p2p_dev_addr)); + if (err < 0) + err = cfg80211_get_p2p_attr(ie, le32_to_cpu(bi->ie_length), + IEEE80211_P2P_ATTR_DEVICE_ID, + p2p_dev_addr, sizeof(p2p_dev_addr)); + if ((err >= 0) && + (!memcmp(p2p_dev_addr, afx_hdl->tx_dst_addr, ETH_ALEN))) { + if (!bi->ctl_ch) { + ch.chspec = le16_to_cpu(bi->chanspec); + cfg->d11inf.decchspec(&ch); + bi->ctl_ch = ch.chnum; + } + afx_hdl->peer_chan = bi->ctl_ch; + brcmf_dbg(TRACE, "ACTION FRAME SCAN : Peer %pM found, channel : %d\n", + afx_hdl->tx_dst_addr, afx_hdl->peer_chan); + complete(&afx_hdl->act_frm_scan); + } + return true; +} + +/** + * brcmf_p2p_stop_wait_next_action_frame() - finish scan if af tx complete. + * + * @cfg: common configuration struct. + * + */ +static void +brcmf_p2p_stop_wait_next_action_frame(struct brcmf_cfg80211_info *cfg) +{ + struct brcmf_p2p_info *p2p = &cfg->p2p; + struct brcmf_if *ifp = cfg->escan_info.ifp; + + if (test_bit(BRCMF_P2P_STATUS_SENDING_ACT_FRAME, &p2p->status) && + (test_bit(BRCMF_P2P_STATUS_ACTION_TX_COMPLETED, &p2p->status) || + test_bit(BRCMF_P2P_STATUS_ACTION_TX_NOACK, &p2p->status))) { + brcmf_dbg(TRACE, "*** Wake UP ** abort actframe iovar\n"); + /* if channel is not zero, "actfame" uses off channel scan. + * So abort scan for off channel completion. + */ + if (p2p->af_sent_channel) + brcmf_notify_escan_complete(cfg, ifp, true, true); + } else if (test_bit(BRCMF_P2P_STATUS_WAITING_NEXT_AF_LISTEN, + &p2p->status)) { + brcmf_dbg(TRACE, "*** Wake UP ** abort listen for next af frame\n"); + /* So abort scan to cancel listen */ + brcmf_notify_escan_complete(cfg, ifp, true, true); + } +} + + +/** + * brcmf_p2p_gon_req_collision() - Check if go negotiaton collission + * + * @p2p: p2p device info struct. + * + * return true if recevied action frame is to be dropped. + */ +static bool +brcmf_p2p_gon_req_collision(struct brcmf_p2p_info *p2p, u8 *mac) +{ + struct brcmf_cfg80211_info *cfg = p2p->cfg; + struct brcmf_if *ifp; + + brcmf_dbg(TRACE, "Enter\n"); + + if (!test_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, &p2p->status) || + !p2p->gon_req_action) + return false; + + brcmf_dbg(TRACE, "GO Negotiation Request COLLISION !!!\n"); + /* if sa(peer) addr is less than da(my) addr, then this device + * process peer's gon request and block to send gon req. + * if not (sa addr > da addr), + * this device will process gon request and drop gon req of peer. + */ + ifp = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif->ifp; + if (memcmp(mac, ifp->mac_addr, ETH_ALEN) < 0) { + brcmf_dbg(INFO, "Block transmit gon req !!!\n"); + p2p->block_gon_req_tx = true; + /* if we are finding a common channel for sending af, + * do not scan more to block to send current gon req + */ + if (test_and_clear_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, + &p2p->status)) + complete(&p2p->afx_hdl.act_frm_scan); + if (test_and_clear_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, + &p2p->status)) + brcmf_p2p_stop_wait_next_action_frame(cfg); + return false; + } + + /* drop gon request of peer to process gon request by this device. */ + brcmf_dbg(INFO, "Drop received gon req !!!\n"); + + return true; +} + + +/** + * brcmf_p2p_notify_action_frame_rx() - received action frame. + * + * @ifp: interfac control. + * @e: event message. Not used, to make it usable for fweh event dispatcher. + * @data: payload of message, containing action frame data. + * + */ +int brcmf_p2p_notify_action_frame_rx(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, + void *data) +{ + struct brcmf_cfg80211_info *cfg = ifp->drvr->config; + struct brcmf_p2p_info *p2p = &cfg->p2p; + struct afx_hdl *afx_hdl = &p2p->afx_hdl; + struct wireless_dev *wdev; + u32 mgmt_frame_len = e->datalen - sizeof(struct brcmf_rx_mgmt_data); + struct brcmf_rx_mgmt_data *rxframe = (struct brcmf_rx_mgmt_data *)data; + u8 *frame = (u8 *)(rxframe + 1); + struct brcmf_p2p_pub_act_frame *act_frm; + struct brcmf_p2psd_gas_pub_act_frame *sd_act_frm; + struct brcmu_chan ch; + struct ieee80211_mgmt *mgmt_frame; + s32 freq; + u16 mgmt_type; + u8 action; + + ch.chspec = be16_to_cpu(rxframe->chanspec); + cfg->d11inf.decchspec(&ch); + /* Check if wpa_supplicant has registered for this frame */ + brcmf_dbg(INFO, "ifp->vif->mgmt_rx_reg %04x\n", ifp->vif->mgmt_rx_reg); + mgmt_type = (IEEE80211_STYPE_ACTION & IEEE80211_FCTL_STYPE) >> 4; + if ((ifp->vif->mgmt_rx_reg & BIT(mgmt_type)) == 0) + return 0; + + brcmf_p2p_print_actframe(false, frame, mgmt_frame_len); + + action = P2P_PAF_SUBTYPE_INVALID; + if (brcmf_p2p_is_pub_action(frame, mgmt_frame_len)) { + act_frm = (struct brcmf_p2p_pub_act_frame *)frame; + action = act_frm->subtype; + if ((action == P2P_PAF_GON_REQ) && + (brcmf_p2p_gon_req_collision(p2p, (u8 *)e->addr))) { + if (test_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, + &p2p->status) && + (memcmp(afx_hdl->tx_dst_addr, e->addr, + ETH_ALEN) == 0)) { + afx_hdl->peer_chan = ch.chnum; + brcmf_dbg(INFO, "GON request: Peer found, channel=%d\n", + afx_hdl->peer_chan); + complete(&afx_hdl->act_frm_scan); + } + return 0; + } + /* After complete GO Negotiation, roll back to mpc mode */ + if ((action == P2P_PAF_GON_CONF) || + (action == P2P_PAF_PROVDIS_RSP)) + brcmf_set_mpc(ifp, 1); + if (action == P2P_PAF_GON_CONF) { + brcmf_dbg(TRACE, "P2P: GO_NEG_PHASE status cleared\n"); + clear_bit(BRCMF_P2P_STATUS_GO_NEG_PHASE, &p2p->status); + } + } else if (brcmf_p2p_is_gas_action(frame, mgmt_frame_len)) { + sd_act_frm = (struct brcmf_p2psd_gas_pub_act_frame *)frame; + action = sd_act_frm->action; + } + + if (test_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, &p2p->status) && + (p2p->next_af_subtype == action)) { + brcmf_dbg(TRACE, "We got a right next frame! (%d)\n", action); + clear_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, + &p2p->status); + /* Stop waiting for next AF. */ + brcmf_p2p_stop_wait_next_action_frame(cfg); + } + + mgmt_frame = kzalloc(offsetof(struct ieee80211_mgmt, u) + + mgmt_frame_len, GFP_KERNEL); + if (!mgmt_frame) { + brcmf_err("No memory available for action frame\n"); + return -ENOMEM; + } + memcpy(mgmt_frame->da, ifp->mac_addr, ETH_ALEN); + brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BSSID, mgmt_frame->bssid, + ETH_ALEN); + memcpy(mgmt_frame->sa, e->addr, ETH_ALEN); + mgmt_frame->frame_control = cpu_to_le16(IEEE80211_STYPE_ACTION); + memcpy(&mgmt_frame->u, frame, mgmt_frame_len); + mgmt_frame_len += offsetof(struct ieee80211_mgmt, u); + + freq = ieee80211_channel_to_frequency(ch.chnum, + ch.band == BRCMU_CHAN_BAND_2G ? + IEEE80211_BAND_2GHZ : + IEEE80211_BAND_5GHZ); + + wdev = &ifp->vif->wdev; + cfg80211_rx_mgmt(wdev, freq, 0, (u8 *)mgmt_frame, mgmt_frame_len, + GFP_ATOMIC); + + kfree(mgmt_frame); + return 0; +} + + +/** + * brcmf_p2p_notify_action_tx_complete() - transmit action frame complete + * + * @ifp: interfac control. + * @e: event message. Not used, to make it usable for fweh event dispatcher. + * @data: not used. + * + */ +int brcmf_p2p_notify_action_tx_complete(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, + void *data) +{ + struct brcmf_cfg80211_info *cfg = ifp->drvr->config; + struct brcmf_p2p_info *p2p = &cfg->p2p; + + brcmf_dbg(INFO, "Enter: event %s, status=%d\n", + e->event_code == BRCMF_E_ACTION_FRAME_OFF_CHAN_COMPLETE ? + "ACTION_FRAME_OFF_CHAN_COMPLETE" : "ACTION_FRAME_COMPLETE", + e->status); + + if (!test_bit(BRCMF_P2P_STATUS_SENDING_ACT_FRAME, &p2p->status)) + return 0; + + if (e->event_code == BRCMF_E_ACTION_FRAME_COMPLETE) { + if (e->status == BRCMF_E_STATUS_SUCCESS) + set_bit(BRCMF_P2P_STATUS_ACTION_TX_COMPLETED, + &p2p->status); + else { + set_bit(BRCMF_P2P_STATUS_ACTION_TX_NOACK, &p2p->status); + /* If there is no ack, we don't need to wait for + * WLC_E_ACTION_FRAME_OFFCHAN_COMPLETE event + */ + brcmf_p2p_stop_wait_next_action_frame(cfg); + } + + } else { + complete(&p2p->send_af_done); + } + return 0; +} + + +/** + * brcmf_p2p_tx_action_frame() - send action frame over fil. + * + * @p2p: p2p info struct for vif. + * @af_params: action frame data/info. + * + * Send an action frame immediately without doing channel synchronization. + * + * This function waits for a completion event before returning. + * The WLC_E_ACTION_FRAME_COMPLETE event will be received when the action + * frame is transmitted. + */ +static s32 brcmf_p2p_tx_action_frame(struct brcmf_p2p_info *p2p, + struct brcmf_fil_af_params_le *af_params) +{ + struct brcmf_cfg80211_vif *vif; + s32 err = 0; + s32 timeout = 0; + + brcmf_dbg(TRACE, "Enter\n"); + + INIT_COMPLETION(p2p->send_af_done); + clear_bit(BRCMF_P2P_STATUS_ACTION_TX_COMPLETED, &p2p->status); + clear_bit(BRCMF_P2P_STATUS_ACTION_TX_NOACK, &p2p->status); + + vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; + err = brcmf_fil_bsscfg_data_set(vif->ifp, "actframe", af_params, + sizeof(*af_params)); + if (err) { + brcmf_err(" sending action frame has failed\n"); + goto exit; + } + + p2p->af_sent_channel = le32_to_cpu(af_params->channel); + p2p->af_tx_sent_jiffies = jiffies; + + timeout = wait_for_completion_timeout(&p2p->send_af_done, + msecs_to_jiffies(P2P_AF_MAX_WAIT_TIME)); + + if (test_bit(BRCMF_P2P_STATUS_ACTION_TX_COMPLETED, &p2p->status)) { + brcmf_dbg(TRACE, "TX action frame operation is success\n"); + } else { + err = -EIO; + brcmf_dbg(TRACE, "TX action frame operation has failed\n"); + } + /* clear status bit for action tx */ + clear_bit(BRCMF_P2P_STATUS_ACTION_TX_COMPLETED, &p2p->status); + clear_bit(BRCMF_P2P_STATUS_ACTION_TX_NOACK, &p2p->status); + +exit: + return err; +} + + +/** + * brcmf_p2p_pub_af_tx() - public action frame tx routine. + * + * @cfg: driver private data for cfg80211 interface. + * @af_params: action frame data/info. + * @config_af_params: configuration data for action frame. + * + * routine which transmits ation frame public type. + */ +static s32 brcmf_p2p_pub_af_tx(struct brcmf_cfg80211_info *cfg, + struct brcmf_fil_af_params_le *af_params, + struct brcmf_config_af_params *config_af_params) +{ + struct brcmf_p2p_info *p2p = &cfg->p2p; + struct brcmf_fil_action_frame_le *action_frame; + struct brcmf_p2p_pub_act_frame *act_frm; + s32 err = 0; + u16 ie_len; + + action_frame = &af_params->action_frame; + act_frm = (struct brcmf_p2p_pub_act_frame *)(action_frame->data); + + config_af_params->extra_listen = true; + + switch (act_frm->subtype) { + case P2P_PAF_GON_REQ: + brcmf_dbg(TRACE, "P2P: GO_NEG_PHASE status set\n"); + set_bit(BRCMF_P2P_STATUS_GO_NEG_PHASE, &p2p->status); + config_af_params->mpc_onoff = 0; + config_af_params->search_channel = true; + p2p->next_af_subtype = act_frm->subtype + 1; + p2p->gon_req_action = true; + /* increase dwell time to wait for RESP frame */ + af_params->dwell_time = cpu_to_le32(P2P_AF_MED_DWELL_TIME); + break; + case P2P_PAF_GON_RSP: + p2p->next_af_subtype = act_frm->subtype + 1; + /* increase dwell time to wait for CONF frame */ + af_params->dwell_time = cpu_to_le32(P2P_AF_MED_DWELL_TIME); + break; + case P2P_PAF_GON_CONF: + /* If we reached till GO Neg confirmation reset the filter */ + brcmf_dbg(TRACE, "P2P: GO_NEG_PHASE status cleared\n"); + clear_bit(BRCMF_P2P_STATUS_GO_NEG_PHASE, &p2p->status); + /* turn on mpc again if go nego is done */ + config_af_params->mpc_onoff = 1; + /* minimize dwell time */ + af_params->dwell_time = cpu_to_le32(P2P_AF_MIN_DWELL_TIME); + config_af_params->extra_listen = false; + break; + case P2P_PAF_INVITE_REQ: + config_af_params->search_channel = true; + p2p->next_af_subtype = act_frm->subtype + 1; + /* increase dwell time */ + af_params->dwell_time = cpu_to_le32(P2P_AF_MED_DWELL_TIME); + break; + case P2P_PAF_INVITE_RSP: + /* minimize dwell time */ + af_params->dwell_time = cpu_to_le32(P2P_AF_MIN_DWELL_TIME); + config_af_params->extra_listen = false; + break; + case P2P_PAF_DEVDIS_REQ: + config_af_params->search_channel = true; + p2p->next_af_subtype = act_frm->subtype + 1; + /* maximize dwell time to wait for RESP frame */ + af_params->dwell_time = cpu_to_le32(P2P_AF_LONG_DWELL_TIME); + break; + case P2P_PAF_DEVDIS_RSP: + /* minimize dwell time */ + af_params->dwell_time = cpu_to_le32(P2P_AF_MIN_DWELL_TIME); + config_af_params->extra_listen = false; + break; + case P2P_PAF_PROVDIS_REQ: + ie_len = le16_to_cpu(action_frame->len) - + offsetof(struct brcmf_p2p_pub_act_frame, elts); + if (cfg80211_get_p2p_attr(&act_frm->elts[0], ie_len, + IEEE80211_P2P_ATTR_GROUP_ID, + NULL, 0) < 0) + config_af_params->search_channel = true; + config_af_params->mpc_onoff = 0; + p2p->next_af_subtype = act_frm->subtype + 1; + /* increase dwell time to wait for RESP frame */ + af_params->dwell_time = cpu_to_le32(P2P_AF_MED_DWELL_TIME); + break; + case P2P_PAF_PROVDIS_RSP: + /* wpa_supplicant send go nego req right after prov disc */ + p2p->next_af_subtype = P2P_PAF_GON_REQ; + /* increase dwell time to MED level */ + af_params->dwell_time = cpu_to_le32(P2P_AF_MED_DWELL_TIME); + config_af_params->extra_listen = false; + break; + default: + brcmf_err("Unknown p2p pub act frame subtype: %d\n", + act_frm->subtype); + err = -EINVAL; + } + return err; +} + +/** + * brcmf_p2p_send_action_frame() - send action frame . + * + * @cfg: driver private data for cfg80211 interface. + * @ndev: net device to transmit on. + * @af_params: configuration data for action frame. + */ +bool brcmf_p2p_send_action_frame(struct brcmf_cfg80211_info *cfg, + struct net_device *ndev, + struct brcmf_fil_af_params_le *af_params) +{ + struct brcmf_p2p_info *p2p = &cfg->p2p; + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_fil_action_frame_le *action_frame; + struct brcmf_config_af_params config_af_params; + struct afx_hdl *afx_hdl = &p2p->afx_hdl; + u16 action_frame_len; + bool ack = false; + u8 category; + u8 action; + s32 tx_retry; + s32 extra_listen_time; + uint delta_ms; + + action_frame = &af_params->action_frame; + action_frame_len = le16_to_cpu(action_frame->len); + + brcmf_p2p_print_actframe(true, action_frame->data, action_frame_len); + + /* Add the default dwell time. Dwell time to stay off-channel */ + /* to wait for a response action frame after transmitting an */ + /* GO Negotiation action frame */ + af_params->dwell_time = cpu_to_le32(P2P_AF_DWELL_TIME); + + category = action_frame->data[DOT11_ACTION_CAT_OFF]; + action = action_frame->data[DOT11_ACTION_ACT_OFF]; + + /* initialize variables */ + p2p->next_af_subtype = P2P_PAF_SUBTYPE_INVALID; + p2p->gon_req_action = false; + + /* config parameters */ + config_af_params.mpc_onoff = -1; + config_af_params.search_channel = false; + config_af_params.extra_listen = false; + + if (brcmf_p2p_is_pub_action(action_frame->data, action_frame_len)) { + /* p2p public action frame process */ + if (brcmf_p2p_pub_af_tx(cfg, af_params, &config_af_params)) { + /* Just send unknown subtype frame with */ + /* default parameters. */ + brcmf_err("P2P Public action frame, unknown subtype.\n"); + } + } else if (brcmf_p2p_is_gas_action(action_frame->data, + action_frame_len)) { + /* service discovery process */ + if (action == P2PSD_ACTION_ID_GAS_IREQ || + action == P2PSD_ACTION_ID_GAS_CREQ) { + /* configure service discovery query frame */ + config_af_params.search_channel = true; + + /* save next af suptype to cancel */ + /* remaining dwell time */ + p2p->next_af_subtype = action + 1; + + af_params->dwell_time = + cpu_to_le32(P2P_AF_MED_DWELL_TIME); + } else if (action == P2PSD_ACTION_ID_GAS_IRESP || + action == P2PSD_ACTION_ID_GAS_CRESP) { + /* configure service discovery response frame */ + af_params->dwell_time = + cpu_to_le32(P2P_AF_MIN_DWELL_TIME); + } else { + brcmf_err("Unknown action type: %d\n", action); + goto exit; + } + } else if (brcmf_p2p_is_p2p_action(action_frame->data, + action_frame_len)) { + /* do not configure anything. it will be */ + /* sent with a default configuration */ + } else { + brcmf_err("Unknown Frame: category 0x%x, action 0x%x\n", + category, action); + return false; + } + + /* if connecting on primary iface, sleep for a while before sending + * af tx for VSDB + */ + if (test_bit(BRCMF_VIF_STATUS_CONNECTING, + &p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif->sme_state)) + msleep(50); + + /* if scan is ongoing, abort current scan. */ + if (test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status)) + brcmf_abort_scanning(cfg); + + memcpy(afx_hdl->tx_dst_addr, action_frame->da, ETH_ALEN); + + /* To make sure to send successfully action frame, turn off mpc */ + if (config_af_params.mpc_onoff == 0) + brcmf_set_mpc(ifp, 0); + + /* set status and destination address before sending af */ + if (p2p->next_af_subtype != P2P_PAF_SUBTYPE_INVALID) { + /* set status to cancel the remained dwell time in rx process */ + set_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, &p2p->status); + } + + p2p->af_sent_channel = 0; + set_bit(BRCMF_P2P_STATUS_SENDING_ACT_FRAME, &p2p->status); + /* validate channel and p2p ies */ + if (config_af_params.search_channel && + IS_P2P_SOCIAL_CHANNEL(le32_to_cpu(af_params->channel)) && + p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif->saved_ie.probe_req_ie_len) { + afx_hdl = &p2p->afx_hdl; + afx_hdl->peer_listen_chan = le32_to_cpu(af_params->channel); + + if (brcmf_p2p_af_searching_channel(p2p) == + P2P_INVALID_CHANNEL) { + brcmf_err("Couldn't find peer's channel.\n"); + goto exit; + } + + /* Abort scan even for VSDB scenarios. Scan gets aborted in + * firmware but after the check of piggyback algorithm. To take + * care of current piggback algo, lets abort the scan here + * itself. + */ + brcmf_notify_escan_complete(cfg, ifp, true, true); + + /* update channel */ + af_params->channel = cpu_to_le32(afx_hdl->peer_chan); + } + + tx_retry = 0; + while (!p2p->block_gon_req_tx && + (ack == false) && (tx_retry < P2P_AF_TX_MAX_RETRY)) { + ack = !brcmf_p2p_tx_action_frame(p2p, af_params); + tx_retry++; + } + if (ack == false) { + brcmf_err("Failed to send Action Frame(retry %d)\n", tx_retry); + clear_bit(BRCMF_P2P_STATUS_GO_NEG_PHASE, &p2p->status); + } + +exit: + clear_bit(BRCMF_P2P_STATUS_SENDING_ACT_FRAME, &p2p->status); + + /* WAR: sometimes dongle does not keep the dwell time of 'actframe'. + * if we coundn't get the next action response frame and dongle does + * not keep the dwell time, go to listen state again to get next action + * response frame. + */ + if (ack && config_af_params.extra_listen && !p2p->block_gon_req_tx && + test_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, &p2p->status) && + p2p->af_sent_channel == afx_hdl->my_listen_chan) { + delta_ms = jiffies_to_msecs(jiffies - p2p->af_tx_sent_jiffies); + if (le32_to_cpu(af_params->dwell_time) > delta_ms) + extra_listen_time = le32_to_cpu(af_params->dwell_time) - + delta_ms; + else + extra_listen_time = 0; + if (extra_listen_time > 50) { + set_bit(BRCMF_P2P_STATUS_WAITING_NEXT_AF_LISTEN, + &p2p->status); + brcmf_dbg(INFO, "Wait more time! actual af time:%d, calculated extra listen:%d\n", + le32_to_cpu(af_params->dwell_time), + extra_listen_time); + extra_listen_time += 100; + if (!brcmf_p2p_discover_listen(p2p, + p2p->af_sent_channel, + extra_listen_time)) { + unsigned long duration; + + extra_listen_time += 100; + duration = msecs_to_jiffies(extra_listen_time); + wait_for_completion_timeout(&p2p->wait_next_af, + duration); + } + clear_bit(BRCMF_P2P_STATUS_WAITING_NEXT_AF_LISTEN, + &p2p->status); + } + } + + if (p2p->block_gon_req_tx) { + /* if ack is true, supplicant will wait more time(100ms). + * so we will return it as a success to get more time . + */ + p2p->block_gon_req_tx = false; + ack = true; + } + + clear_bit(BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, &p2p->status); + /* if all done, turn mpc on again */ + if (config_af_params.mpc_onoff == 1) + brcmf_set_mpc(ifp, 1); + + return ack; +} + +/** + * brcmf_p2p_notify_rx_mgmt_p2p_probereq() - Event handler for p2p probe req. + * + * @ifp: interface pointer for which event was received. + * @e: even message. + * @data: payload of event message (probe request). + */ +s32 brcmf_p2p_notify_rx_mgmt_p2p_probereq(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, + void *data) +{ + struct brcmf_cfg80211_info *cfg = ifp->drvr->config; + struct brcmf_p2p_info *p2p = &cfg->p2p; + struct afx_hdl *afx_hdl = &p2p->afx_hdl; + struct brcmf_cfg80211_vif *vif = ifp->vif; + struct brcmf_rx_mgmt_data *rxframe = (struct brcmf_rx_mgmt_data *)data; + u16 chanspec = be16_to_cpu(rxframe->chanspec); + struct brcmu_chan ch; + u8 *mgmt_frame; + u32 mgmt_frame_len; + s32 freq; + u16 mgmt_type; + + brcmf_dbg(INFO, "Enter: event %d reason %d\n", e->event_code, + e->reason); + + ch.chspec = be16_to_cpu(rxframe->chanspec); + cfg->d11inf.decchspec(&ch); + + if (test_bit(BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL, &p2p->status) && + (memcmp(afx_hdl->tx_dst_addr, e->addr, ETH_ALEN) == 0)) { + afx_hdl->peer_chan = ch.chnum; + brcmf_dbg(INFO, "PROBE REQUEST: Peer found, channel=%d\n", + afx_hdl->peer_chan); + complete(&afx_hdl->act_frm_scan); + } + + /* Firmware sends us two proberesponses for each idx one. At the */ + /* moment anything but bsscfgidx 0 is passed up to supplicant */ + if (e->bsscfgidx == 0) + return 0; + + /* Filter any P2P probe reqs arriving during the GO-NEG Phase */ + if (test_bit(BRCMF_P2P_STATUS_GO_NEG_PHASE, &p2p->status)) { + brcmf_dbg(INFO, "Filtering P2P probe_req in GO-NEG phase\n"); + return 0; + } + + /* Check if wpa_supplicant has registered for this frame */ + brcmf_dbg(INFO, "vif->mgmt_rx_reg %04x\n", vif->mgmt_rx_reg); + mgmt_type = (IEEE80211_STYPE_PROBE_REQ & IEEE80211_FCTL_STYPE) >> 4; + if ((vif->mgmt_rx_reg & BIT(mgmt_type)) == 0) + return 0; + + mgmt_frame = (u8 *)(rxframe + 1); + mgmt_frame_len = e->datalen - sizeof(*rxframe); + freq = ieee80211_channel_to_frequency(ch.chnum, + ch.band == BRCMU_CHAN_BAND_2G ? + IEEE80211_BAND_2GHZ : + IEEE80211_BAND_5GHZ); + + cfg80211_rx_mgmt(&vif->wdev, freq, 0, mgmt_frame, mgmt_frame_len, + GFP_ATOMIC); + + brcmf_dbg(INFO, "mgmt_frame_len (%d) , e->datalen (%d), chanspec (%04x), freq (%d)\n", + mgmt_frame_len, e->datalen, chanspec, freq); + + return 0; +} + + +/** + * brcmf_p2p_attach() - attach for P2P. + * + * @cfg: driver private data for cfg80211 interface. + */ +s32 brcmf_p2p_attach(struct brcmf_cfg80211_info *cfg) +{ + struct brcmf_if *pri_ifp; + struct brcmf_if *p2p_ifp; + struct brcmf_cfg80211_vif *p2p_vif; + struct brcmf_p2p_info *p2p; + struct brcmf_pub *drvr; + s32 bssidx; + s32 err = 0; + + p2p = &cfg->p2p; + p2p->cfg = cfg; + + drvr = cfg->pub; + + pri_ifp = drvr->iflist[0]; + p2p_ifp = drvr->iflist[1]; + + p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif = pri_ifp->vif; + + if (p2p_ifp) { + p2p_vif = brcmf_alloc_vif(cfg, NL80211_IFTYPE_P2P_DEVICE, + false); + if (IS_ERR(p2p_vif)) { + brcmf_err("could not create discovery vif\n"); + err = -ENOMEM; + goto exit; + } + + p2p_vif->ifp = p2p_ifp; + p2p_ifp->vif = p2p_vif; + p2p_vif->wdev.netdev = p2p_ifp->ndev; + p2p_ifp->ndev->ieee80211_ptr = &p2p_vif->wdev; + SET_NETDEV_DEV(p2p_ifp->ndev, wiphy_dev(cfg->wiphy)); + + p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif = p2p_vif; + + brcmf_p2p_generate_bss_mac(p2p, NULL); + memcpy(p2p_ifp->mac_addr, p2p->dev_addr, ETH_ALEN); + brcmf_p2p_set_firmware(pri_ifp, p2p->dev_addr); + + /* Initialize P2P Discovery in the firmware */ + err = brcmf_fil_iovar_int_set(pri_ifp, "p2p_disc", 1); + if (err < 0) { + brcmf_err("set p2p_disc error\n"); + brcmf_free_vif(cfg, p2p_vif); + goto exit; + } + /* obtain bsscfg index for P2P discovery */ + err = brcmf_fil_iovar_int_get(pri_ifp, "p2p_dev", &bssidx); + if (err < 0) { + brcmf_err("retrieving discover bsscfg index failed\n"); + brcmf_free_vif(cfg, p2p_vif); + goto exit; + } + /* Verify that firmware uses same bssidx as driver !! */ + if (p2p_ifp->bssidx != bssidx) { + brcmf_err("Incorrect bssidx=%d, compared to p2p_ifp->bssidx=%d\n", + bssidx, p2p_ifp->bssidx); + brcmf_free_vif(cfg, p2p_vif); + goto exit; + } + + init_completion(&p2p->send_af_done); + INIT_WORK(&p2p->afx_hdl.afx_work, brcmf_p2p_afx_handler); + init_completion(&p2p->afx_hdl.act_frm_scan); + init_completion(&p2p->wait_next_af); + } +exit: + return err; +} + + +/** + * brcmf_p2p_detach() - detach P2P. + * + * @p2p: P2P specific data. + */ +void brcmf_p2p_detach(struct brcmf_p2p_info *p2p) +{ + struct brcmf_cfg80211_vif *vif; + + vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif; + if (vif != NULL) { + brcmf_p2p_cancel_remain_on_channel(vif->ifp); + brcmf_p2p_deinit_discovery(p2p); + /* remove discovery interface */ + brcmf_free_vif(p2p->cfg, vif); + p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif = NULL; + } + /* just set it all to zero */ + memset(p2p, 0, sizeof(*p2p)); +} + +/** + * brcmf_p2p_get_current_chanspec() - Get current operation channel. + * + * @p2p: P2P specific data. + * @chanspec: chanspec to be returned. + */ +static void brcmf_p2p_get_current_chanspec(struct brcmf_p2p_info *p2p, + u16 *chanspec) +{ + struct brcmf_if *ifp; + u8 mac_addr[ETH_ALEN]; + struct brcmu_chan ch; + struct brcmf_bss_info_le *bi; + u8 *buf; + + ifp = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif->ifp; + + if (brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BSSID, mac_addr, + ETH_ALEN) == 0) { + buf = kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL); + if (buf != NULL) { + *(__le32 *)buf = cpu_to_le32(WL_BSS_INFO_MAX); + if (brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BSS_INFO, + buf, WL_BSS_INFO_MAX) == 0) { + bi = (struct brcmf_bss_info_le *)(buf + 4); + *chanspec = le16_to_cpu(bi->chanspec); + kfree(buf); + return; + } + kfree(buf); + } + } + /* Use default channel for P2P */ + ch.chnum = BRCMF_P2P_TEMP_CHAN; + ch.bw = BRCMU_CHAN_BW_20; + p2p->cfg->d11inf.encchspec(&ch); + *chanspec = ch.chspec; +} + +/** + * Change a P2P Role. + * Parameters: + * @mac: MAC address of the BSS to change a role + * Returns 0 if success. + */ +int brcmf_p2p_ifchange(struct brcmf_cfg80211_info *cfg, + enum brcmf_fil_p2p_if_types if_type) +{ + struct brcmf_p2p_info *p2p = &cfg->p2p; + struct brcmf_cfg80211_vif *vif; + struct brcmf_fil_p2p_if_le if_request; + s32 err; + u16 chanspec; + + brcmf_dbg(TRACE, "Enter\n"); + + vif = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif; + if (!vif) { + brcmf_err("vif for P2PAPI_BSSCFG_PRIMARY does not exist\n"); + return -EPERM; + } + brcmf_notify_escan_complete(cfg, vif->ifp, true, true); + vif = p2p->bss_idx[P2PAPI_BSSCFG_CONNECTION].vif; + if (!vif) { + brcmf_err("vif for P2PAPI_BSSCFG_CONNECTION does not exist\n"); + return -EPERM; + } + brcmf_set_mpc(vif->ifp, 0); + + /* In concurrency case, STA may be already associated in a particular */ + /* channel. so retrieve the current channel of primary interface and */ + /* then start the virtual interface on that. */ + brcmf_p2p_get_current_chanspec(p2p, &chanspec); + + if_request.type = cpu_to_le16((u16)if_type); + if_request.chspec = cpu_to_le16(chanspec); + memcpy(if_request.addr, p2p->int_addr, sizeof(if_request.addr)); + + brcmf_cfg80211_arm_vif_event(cfg, vif); + err = brcmf_fil_iovar_data_set(vif->ifp, "p2p_ifupd", &if_request, + sizeof(if_request)); + if (err) { + brcmf_err("p2p_ifupd FAILED, err=%d\n", err); + brcmf_cfg80211_arm_vif_event(cfg, NULL); + return err; + } + err = brcmf_cfg80211_wait_vif_event_timeout(cfg, BRCMF_E_IF_CHANGE, + msecs_to_jiffies(1500)); + brcmf_cfg80211_arm_vif_event(cfg, NULL); + if (!err) { + brcmf_err("No BRCMF_E_IF_CHANGE event received\n"); + return -EIO; + } + + err = brcmf_fil_cmd_int_set(vif->ifp, BRCMF_C_SET_SCB_TIMEOUT, + BRCMF_SCB_TIMEOUT_VALUE); + + return err; +} + +static int brcmf_p2p_request_p2p_if(struct brcmf_p2p_info *p2p, + struct brcmf_if *ifp, u8 ea[ETH_ALEN], + enum brcmf_fil_p2p_if_types iftype) +{ + struct brcmf_fil_p2p_if_le if_request; + int err; + u16 chanspec; + + /* we need a default channel */ + brcmf_p2p_get_current_chanspec(p2p, &chanspec); + + /* fill the firmware request */ + memcpy(if_request.addr, ea, ETH_ALEN); + if_request.type = cpu_to_le16((u16)iftype); + if_request.chspec = cpu_to_le16(chanspec); + + err = brcmf_fil_iovar_data_set(ifp, "p2p_ifadd", &if_request, + sizeof(if_request)); + if (err) + return err; + + return err; +} + +static int brcmf_p2p_disable_p2p_if(struct brcmf_cfg80211_vif *vif) +{ + struct brcmf_cfg80211_info *cfg = wdev_to_cfg(&vif->wdev); + struct net_device *pri_ndev = cfg_to_ndev(cfg); + struct brcmf_if *ifp = netdev_priv(pri_ndev); + u8 *addr = vif->wdev.netdev->dev_addr; + + return brcmf_fil_iovar_data_set(ifp, "p2p_ifdis", addr, ETH_ALEN); +} + +static int brcmf_p2p_release_p2p_if(struct brcmf_cfg80211_vif *vif) +{ + struct brcmf_cfg80211_info *cfg = wdev_to_cfg(&vif->wdev); + struct net_device *pri_ndev = cfg_to_ndev(cfg); + struct brcmf_if *ifp = netdev_priv(pri_ndev); + u8 *addr = vif->wdev.netdev->dev_addr; + + return brcmf_fil_iovar_data_set(ifp, "p2p_ifdel", addr, ETH_ALEN); +} + +/** + * brcmf_p2p_create_p2pdev() - create a P2P_DEVICE virtual interface. + * + * @p2p: P2P specific data. + * @wiphy: wiphy device of new interface. + * @addr: mac address for this new interface. + */ +static struct wireless_dev *brcmf_p2p_create_p2pdev(struct brcmf_p2p_info *p2p, + struct wiphy *wiphy, + u8 *addr) +{ + struct brcmf_cfg80211_vif *p2p_vif; + struct brcmf_if *p2p_ifp; + struct brcmf_if *pri_ifp; + int err; + u32 bssidx; + + if (p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif) + return ERR_PTR(-ENOSPC); + + p2p_vif = brcmf_alloc_vif(p2p->cfg, NL80211_IFTYPE_P2P_DEVICE, + false); + if (IS_ERR(p2p_vif)) { + brcmf_err("could not create discovery vif\n"); + return (struct wireless_dev *)p2p_vif; + } + + pri_ifp = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif->ifp; + brcmf_p2p_generate_bss_mac(p2p, addr); + brcmf_p2p_set_firmware(pri_ifp, p2p->dev_addr); + + brcmf_cfg80211_arm_vif_event(p2p->cfg, p2p_vif); + + /* Initialize P2P Discovery in the firmware */ + err = brcmf_fil_iovar_int_set(pri_ifp, "p2p_disc", 1); + if (err < 0) { + brcmf_err("set p2p_disc error\n"); + brcmf_cfg80211_arm_vif_event(p2p->cfg, NULL); + goto fail; + } + + /* wait for firmware event */ + err = brcmf_cfg80211_wait_vif_event_timeout(p2p->cfg, BRCMF_E_IF_ADD, + msecs_to_jiffies(1500)); + brcmf_cfg80211_arm_vif_event(p2p->cfg, NULL); + if (!err) { + brcmf_err("timeout occurred\n"); + err = -EIO; + goto fail; + } + + /* discovery interface created */ + p2p_ifp = p2p_vif->ifp; + p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif = p2p_vif; + memcpy(p2p_ifp->mac_addr, p2p->dev_addr, ETH_ALEN); + memcpy(&p2p_vif->wdev.address, p2p->dev_addr, sizeof(p2p->dev_addr)); + + /* verify bsscfg index for P2P discovery */ + err = brcmf_fil_iovar_int_get(pri_ifp, "p2p_dev", &bssidx); + if (err < 0) { + brcmf_err("retrieving discover bsscfg index failed\n"); + goto fail; + } + + WARN_ON(p2p_ifp->bssidx != bssidx); + + init_completion(&p2p->send_af_done); + INIT_WORK(&p2p->afx_hdl.afx_work, brcmf_p2p_afx_handler); + init_completion(&p2p->afx_hdl.act_frm_scan); + init_completion(&p2p->wait_next_af); + + return &p2p_vif->wdev; + +fail: + brcmf_free_vif(p2p->cfg, p2p_vif); + return ERR_PTR(err); +} + +/** + * brcmf_p2p_delete_p2pdev() - delete P2P_DEVICE virtual interface. + * + * @vif: virtual interface object to delete. + */ +static void brcmf_p2p_delete_p2pdev(struct brcmf_cfg80211_info *cfg, + struct brcmf_cfg80211_vif *vif) +{ + cfg80211_unregister_wdev(&vif->wdev); + cfg->p2p.bss_idx[P2PAPI_BSSCFG_DEVICE].vif = NULL; + brcmf_free_vif(cfg, vif); +} + +/** + * brcmf_p2p_free_p2p_if() - free up net device related data. + * + * @ndev: net device that needs to be freed. + */ +static void brcmf_p2p_free_p2p_if(struct net_device *ndev) +{ + struct brcmf_cfg80211_info *cfg; + struct brcmf_cfg80211_vif *vif; + struct brcmf_if *ifp; + + ifp = netdev_priv(ndev); + cfg = ifp->drvr->config; + vif = ifp->vif; + + brcmf_free_vif(cfg, vif); + free_netdev(ifp->ndev); +} + +/** + * brcmf_p2p_add_vif() - create a new P2P virtual interface. + * + * @wiphy: wiphy device of new interface. + * @name: name of the new interface. + * @type: nl80211 interface type. + * @flags: not used. + * @params: contains mac address for P2P device. + */ +struct wireless_dev *brcmf_p2p_add_vif(struct wiphy *wiphy, const char *name, + enum nl80211_iftype type, u32 *flags, + struct vif_params *params) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg)); + struct brcmf_cfg80211_vif *vif; + enum brcmf_fil_p2p_if_types iftype; + enum wl_mode mode; + int err; + + if (brcmf_cfg80211_vif_event_armed(cfg)) + return ERR_PTR(-EBUSY); + + brcmf_dbg(INFO, "adding vif \"%s\" (type=%d)\n", name, type); + + switch (type) { + case NL80211_IFTYPE_P2P_CLIENT: + iftype = BRCMF_FIL_P2P_IF_CLIENT; + mode = WL_MODE_BSS; + break; + case NL80211_IFTYPE_P2P_GO: + iftype = BRCMF_FIL_P2P_IF_GO; + mode = WL_MODE_AP; + break; + case NL80211_IFTYPE_P2P_DEVICE: + return brcmf_p2p_create_p2pdev(&cfg->p2p, wiphy, + params->macaddr); + default: + return ERR_PTR(-EOPNOTSUPP); + } + + vif = brcmf_alloc_vif(cfg, type, false); + if (IS_ERR(vif)) + return (struct wireless_dev *)vif; + brcmf_cfg80211_arm_vif_event(cfg, vif); + + err = brcmf_p2p_request_p2p_if(&cfg->p2p, ifp, cfg->p2p.int_addr, + iftype); + if (err) { + brcmf_cfg80211_arm_vif_event(cfg, NULL); + goto fail; + } + + /* wait for firmware event */ + err = brcmf_cfg80211_wait_vif_event_timeout(cfg, BRCMF_E_IF_ADD, + msecs_to_jiffies(1500)); + brcmf_cfg80211_arm_vif_event(cfg, NULL); + if (!err) { + brcmf_err("timeout occurred\n"); + err = -EIO; + goto fail; + } + + /* interface created in firmware */ + ifp = vif->ifp; + if (!ifp) { + brcmf_err("no if pointer provided\n"); + err = -ENOENT; + goto fail; + } + + strncpy(ifp->ndev->name, name, sizeof(ifp->ndev->name) - 1); + err = brcmf_net_attach(ifp, true); + if (err) { + brcmf_err("Registering netdevice failed\n"); + goto fail; + } + /* override destructor */ + ifp->ndev->destructor = brcmf_p2p_free_p2p_if; + + cfg->p2p.bss_idx[P2PAPI_BSSCFG_CONNECTION].vif = vif; + /* Disable firmware roaming for P2P interface */ + brcmf_fil_iovar_int_set(ifp, "roam_off", 1); + if (iftype == BRCMF_FIL_P2P_IF_GO) { + /* set station timeout for p2p */ + brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCB_TIMEOUT, + BRCMF_SCB_TIMEOUT_VALUE); + } + return &ifp->vif->wdev; + +fail: + brcmf_free_vif(cfg, vif); + return ERR_PTR(err); +} + +/** + * brcmf_p2p_del_vif() - delete a P2P virtual interface. + * + * @wiphy: wiphy device of interface. + * @wdev: wireless device of interface. + * + * TODO: not yet supported. + */ +int brcmf_p2p_del_vif(struct wiphy *wiphy, struct wireless_dev *wdev) +{ + struct brcmf_cfg80211_info *cfg = wiphy_priv(wiphy); + struct brcmf_p2p_info *p2p = &cfg->p2p; + struct brcmf_cfg80211_vif *vif; + unsigned long jiffie_timeout = msecs_to_jiffies(1500); + bool wait_for_disable = false; + int err; + + brcmf_dbg(TRACE, "delete P2P vif\n"); + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + + switch (vif->wdev.iftype) { + case NL80211_IFTYPE_P2P_CLIENT: + if (test_bit(BRCMF_VIF_STATUS_DISCONNECTING, &vif->sme_state)) + wait_for_disable = true; + break; + + case NL80211_IFTYPE_P2P_GO: + if (!brcmf_p2p_disable_p2p_if(vif)) + wait_for_disable = true; + break; + + case NL80211_IFTYPE_P2P_DEVICE: + brcmf_p2p_delete_p2pdev(cfg, vif); + return 0; + default: + return -ENOTSUPP; + break; + } + + clear_bit(BRCMF_P2P_STATUS_GO_NEG_PHASE, &p2p->status); + brcmf_dbg(INFO, "P2P: GO_NEG_PHASE status cleared\n"); + + if (wait_for_disable) + wait_for_completion_timeout(&cfg->vif_disabled, + msecs_to_jiffies(500)); + + brcmf_vif_clear_mgmt_ies(vif); + + brcmf_cfg80211_arm_vif_event(cfg, vif); + err = brcmf_p2p_release_p2p_if(vif); + if (!err) { + /* wait for firmware event */ + err = brcmf_cfg80211_wait_vif_event_timeout(cfg, BRCMF_E_IF_DEL, + jiffie_timeout); + if (!err) + err = -EIO; + else + err = 0; + } + brcmf_cfg80211_arm_vif_event(cfg, NULL); + p2p->bss_idx[P2PAPI_BSSCFG_CONNECTION].vif = NULL; + + return err; +} + +int brcmf_p2p_start_device(struct wiphy *wiphy, struct wireless_dev *wdev) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_p2p_info *p2p = &cfg->p2p; + struct brcmf_cfg80211_vif *vif; + int err; + + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + mutex_lock(&cfg->usr_sync); + err = brcmf_p2p_enable_discovery(p2p); + if (!err) + set_bit(BRCMF_VIF_STATUS_READY, &vif->sme_state); + mutex_unlock(&cfg->usr_sync); + return err; +} + +void brcmf_p2p_stop_device(struct wiphy *wiphy, struct wireless_dev *wdev) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_p2p_info *p2p = &cfg->p2p; + struct brcmf_cfg80211_vif *vif; + + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + mutex_lock(&cfg->usr_sync); + (void)brcmf_p2p_deinit_discovery(p2p); + brcmf_abort_scanning(cfg); + clear_bit(BRCMF_VIF_STATUS_READY, &vif->sme_state); + mutex_unlock(&cfg->usr_sync); +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/p2p.h b/drivers/net/wireless/brcm80211/brcmfmac/p2p.h new file mode 100644 index 0000000..6821b26 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/p2p.h @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2012 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef WL_CFGP2P_H_ +#define WL_CFGP2P_H_ + +#include + +struct brcmf_cfg80211_info; + +/** + * enum p2p_bss_type - different type of BSS configurations. + * + * @P2PAPI_BSSCFG_PRIMARY: maps to driver's primary bsscfg. + * @P2PAPI_BSSCFG_DEVICE: maps to driver's P2P device discovery bsscfg. + * @P2PAPI_BSSCFG_CONNECTION: maps to driver's P2P connection bsscfg. + * @P2PAPI_BSSCFG_MAX: used for range checking. + */ +enum p2p_bss_type { + P2PAPI_BSSCFG_PRIMARY, /* maps to driver's primary bsscfg */ + P2PAPI_BSSCFG_DEVICE, /* maps to driver's P2P device discovery bsscfg */ + P2PAPI_BSSCFG_CONNECTION, /* maps to driver's P2P connection bsscfg */ + P2PAPI_BSSCFG_MAX +}; + +/** + * struct p2p_bss - peer-to-peer bss related information. + * + * @vif: virtual interface of this P2P bss. + * @private_data: TBD + */ +struct p2p_bss { + struct brcmf_cfg80211_vif *vif; + void *private_data; +}; + +/** + * enum brcmf_p2p_status - P2P specific dongle status. + * + * @BRCMF_P2P_STATUS_IF_ADD: peer-to-peer vif add sent to dongle. + * @BRCMF_P2P_STATUS_IF_DEL: NOT-USED? + * @BRCMF_P2P_STATUS_IF_DELETING: peer-to-peer vif delete sent to dongle. + * @BRCMF_P2P_STATUS_IF_CHANGING: peer-to-peer vif change sent to dongle. + * @BRCMF_P2P_STATUS_IF_CHANGED: peer-to-peer vif change completed on dongle. + * @BRCMF_P2P_STATUS_ACTION_TX_COMPLETED: action frame tx completed. + * @BRCMF_P2P_STATUS_ACTION_TX_NOACK: action frame tx not acked. + * @BRCMF_P2P_STATUS_GO_NEG_PHASE: P2P GO negotiation ongoing. + * @BRCMF_P2P_STATUS_DISCOVER_LISTEN: P2P listen, remaining on channel. + * @BRCMF_P2P_STATUS_SENDING_ACT_FRAME: In the process of sending action frame. + * @BRCMF_P2P_STATUS_WAITING_NEXT_AF_LISTEN: extra listen time for af tx. + * @BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME: waiting for action frame response. + * @BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL: search channel for AF active. + */ +enum brcmf_p2p_status { + BRCMF_P2P_STATUS_ENABLED, + BRCMF_P2P_STATUS_IF_ADD, + BRCMF_P2P_STATUS_IF_DEL, + BRCMF_P2P_STATUS_IF_DELETING, + BRCMF_P2P_STATUS_IF_CHANGING, + BRCMF_P2P_STATUS_IF_CHANGED, + BRCMF_P2P_STATUS_ACTION_TX_COMPLETED, + BRCMF_P2P_STATUS_ACTION_TX_NOACK, + BRCMF_P2P_STATUS_GO_NEG_PHASE, + BRCMF_P2P_STATUS_DISCOVER_LISTEN, + BRCMF_P2P_STATUS_SENDING_ACT_FRAME, + BRCMF_P2P_STATUS_WAITING_NEXT_AF_LISTEN, + BRCMF_P2P_STATUS_WAITING_NEXT_ACT_FRAME, + BRCMF_P2P_STATUS_FINDING_COMMON_CHANNEL +}; + +/** + * struct afx_hdl - action frame off channel storage. + * + * @afx_work: worker thread for searching channel + * @act_frm_scan: thread synchronizing struct. + * @is_active: channel searching active. + * @peer_chan: current channel. + * @is_listen: sets mode for afx worker. + * @my_listen_chan: this peers listen channel. + * @peer_listen_chan: remote peers listen channel. + * @tx_dst_addr: mac address where tx af should be sent to. + */ +struct afx_hdl { + struct work_struct afx_work; + struct completion act_frm_scan; + bool is_active; + s32 peer_chan; + bool is_listen; + u16 my_listen_chan; + u16 peer_listen_chan; + u8 tx_dst_addr[ETH_ALEN]; +}; + +/** + * struct brcmf_p2p_info - p2p specific driver information. + * + * @cfg: driver private data for cfg80211 interface. + * @status: status of P2P (see enum brcmf_p2p_status). + * @dev_addr: P2P device address. + * @int_addr: P2P interface address. + * @bss_idx: informate for P2P bss types. + * @listen_timer: timer for @WL_P2P_DISC_ST_LISTEN discover state. + * @ssid: ssid for P2P GO. + * @listen_channel: channel for @WL_P2P_DISC_ST_LISTEN discover state. + * @remain_on_channel: contains copy of struct used by cfg80211. + * @remain_on_channel_cookie: cookie counter for remain on channel cmd + * @next_af_subtype: expected action frame subtype. + * @send_af_done: indication that action frame tx is complete. + * @afx_hdl: action frame search handler info. + * @af_sent_channel: channel action frame is sent. + * @af_tx_sent_jiffies: jiffies time when af tx was transmitted. + * @wait_next_af: thread synchronizing struct. + * @gon_req_action: about to send go negotiation requets frame. + * @block_gon_req_tx: drop tx go negotiation requets frame. + */ +struct brcmf_p2p_info { + struct brcmf_cfg80211_info *cfg; + unsigned long status; + u8 dev_addr[ETH_ALEN]; + u8 int_addr[ETH_ALEN]; + struct p2p_bss bss_idx[P2PAPI_BSSCFG_MAX]; + struct timer_list listen_timer; + struct brcmf_ssid ssid; + u8 listen_channel; + struct ieee80211_channel remain_on_channel; + u32 remain_on_channel_cookie; + u8 next_af_subtype; + struct completion send_af_done; + struct afx_hdl afx_hdl; + u32 af_sent_channel; + unsigned long af_tx_sent_jiffies; + struct completion wait_next_af; + bool gon_req_action; + bool block_gon_req_tx; +}; + +s32 brcmf_p2p_attach(struct brcmf_cfg80211_info *cfg); +void brcmf_p2p_detach(struct brcmf_p2p_info *p2p); +struct wireless_dev *brcmf_p2p_add_vif(struct wiphy *wiphy, const char *name, + enum nl80211_iftype type, u32 *flags, + struct vif_params *params); +int brcmf_p2p_del_vif(struct wiphy *wiphy, struct wireless_dev *wdev); +int brcmf_p2p_ifchange(struct brcmf_cfg80211_info *cfg, + enum brcmf_fil_p2p_if_types if_type); +int brcmf_p2p_start_device(struct wiphy *wiphy, struct wireless_dev *wdev); +void brcmf_p2p_stop_device(struct wiphy *wiphy, struct wireless_dev *wdev); +int brcmf_p2p_scan_prep(struct wiphy *wiphy, + struct cfg80211_scan_request *request, + struct brcmf_cfg80211_vif *vif); +int brcmf_p2p_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, + struct ieee80211_channel *channel, + unsigned int duration, u64 *cookie); +int brcmf_p2p_notify_listen_complete(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, + void *data); +void brcmf_p2p_cancel_remain_on_channel(struct brcmf_if *ifp); +int brcmf_p2p_notify_action_frame_rx(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, + void *data); +int brcmf_p2p_notify_action_tx_complete(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, + void *data); +bool brcmf_p2p_send_action_frame(struct brcmf_cfg80211_info *cfg, + struct net_device *ndev, + struct brcmf_fil_af_params_le *af_params); +bool brcmf_p2p_scan_finding_common_channel(struct brcmf_cfg80211_info *cfg, + struct brcmf_bss_info_le *bi); +s32 brcmf_p2p_notify_rx_mgmt_p2p_probereq(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, + void *data); +#endif /* WL_CFGP2P_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/sdio_chip.c b/drivers/net/wireless/brcm80211/brcmfmac/sdio_chip.c new file mode 100644 index 0000000..ca72177 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/sdio_chip.c @@ -0,0 +1,955 @@ +/* + * Copyright (c) 2011 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +/* ***** SDIO interface chip backplane handle functions ***** */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "dhd_dbg.h" +#include "sdio_host.h" +#include "sdio_chip.h" + +/* chip core base & ramsize */ +/* bcm4329 */ +/* SDIO device core, ID 0x829 */ +#define BCM4329_CORE_BUS_BASE 0x18011000 +/* internal memory core, ID 0x80e */ +#define BCM4329_CORE_SOCRAM_BASE 0x18003000 +/* ARM Cortex M3 core, ID 0x82a */ +#define BCM4329_CORE_ARM_BASE 0x18002000 +#define BCM4329_RAMSIZE 0x48000 + +/* bcm43143 */ +/* SDIO device core */ +#define BCM43143_CORE_BUS_BASE 0x18002000 +/* internal memory core */ +#define BCM43143_CORE_SOCRAM_BASE 0x18004000 +/* ARM Cortex M3 core, ID 0x82a */ +#define BCM43143_CORE_ARM_BASE 0x18003000 +#define BCM43143_RAMSIZE 0x70000 + +#define SBCOREREV(sbidh) \ + ((((sbidh) & SSB_IDHIGH_RCHI) >> SSB_IDHIGH_RCHI_SHIFT) | \ + ((sbidh) & SSB_IDHIGH_RCLO)) + +/* SOC Interconnect types (aka chip types) */ +#define SOCI_SB 0 +#define SOCI_AI 1 + +/* EROM CompIdentB */ +#define CIB_REV_MASK 0xff000000 +#define CIB_REV_SHIFT 24 + +/* ARM CR4 core specific control flag bits */ +#define ARMCR4_BCMA_IOCTL_CPUHALT 0x0020 + +#define SDIOD_DRVSTR_KEY(chip, pmu) (((chip) << 16) | (pmu)) +/* SDIO Pad drive strength to select value mappings */ +struct sdiod_drive_str { + u8 strength; /* Pad Drive Strength in mA */ + u8 sel; /* Chip-specific select value */ +}; +/* SDIO Drive Strength to sel value table for PMU Rev 11 (1.8V) */ +static const struct sdiod_drive_str sdiod_drvstr_tab1_1v8[] = { + {32, 0x6}, + {26, 0x7}, + {22, 0x4}, + {16, 0x5}, + {12, 0x2}, + {8, 0x3}, + {4, 0x0}, + {0, 0x1} +}; + +/* SDIO Drive Strength to sel value table for 43143 PMU Rev 17 (3.3V) */ +static const struct sdiod_drive_str sdiod_drvstr_tab2_3v3[] = { + {16, 0x7}, + {12, 0x5}, + {8, 0x3}, + {4, 0x1} +}; + +u8 +brcmf_sdio_chip_getinfidx(struct chip_info *ci, u16 coreid) +{ + u8 idx; + + for (idx = 0; idx < BRCMF_MAX_CORENUM; idx++) + if (coreid == ci->c_inf[idx].id) + return idx; + + return BRCMF_MAX_CORENUM; +} + +static u32 +brcmf_sdio_sb_corerev(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, u16 coreid) +{ + u32 regdata; + u8 idx; + + idx = brcmf_sdio_chip_getinfidx(ci, coreid); + + regdata = brcmf_sdio_regrl(sdiodev, + CORE_SB(ci->c_inf[idx].base, sbidhigh), + NULL); + return SBCOREREV(regdata); +} + +static u32 +brcmf_sdio_ai_corerev(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, u16 coreid) +{ + u8 idx; + + idx = brcmf_sdio_chip_getinfidx(ci, coreid); + + return (ci->c_inf[idx].cib & CIB_REV_MASK) >> CIB_REV_SHIFT; +} + +static bool +brcmf_sdio_sb_iscoreup(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, u16 coreid) +{ + u32 regdata; + u8 idx; + + idx = brcmf_sdio_chip_getinfidx(ci, coreid); + + regdata = brcmf_sdio_regrl(sdiodev, + CORE_SB(ci->c_inf[idx].base, sbtmstatelow), + NULL); + regdata &= (SSB_TMSLOW_RESET | SSB_TMSLOW_REJECT | + SSB_IMSTATE_REJECT | SSB_TMSLOW_CLOCK); + return (SSB_TMSLOW_CLOCK == regdata); +} + +static bool +brcmf_sdio_ai_iscoreup(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, u16 coreid) +{ + u32 regdata; + u8 idx; + bool ret; + + idx = brcmf_sdio_chip_getinfidx(ci, coreid); + + regdata = brcmf_sdio_regrl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_IOCTL, + NULL); + ret = (regdata & (BCMA_IOCTL_FGC | BCMA_IOCTL_CLK)) == BCMA_IOCTL_CLK; + + regdata = brcmf_sdio_regrl(sdiodev, + ci->c_inf[idx].wrapbase+BCMA_RESET_CTL, + NULL); + ret = ret && ((regdata & BCMA_RESET_CTL_RESET) == 0); + + return ret; +} + +static void +brcmf_sdio_sb_coredisable(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, u16 coreid, u32 core_bits) +{ + u32 regdata, base; + u8 idx; + + idx = brcmf_sdio_chip_getinfidx(ci, coreid); + base = ci->c_inf[idx].base; + + regdata = brcmf_sdio_regrl(sdiodev, CORE_SB(base, sbtmstatelow), NULL); + if (regdata & SSB_TMSLOW_RESET) + return; + + regdata = brcmf_sdio_regrl(sdiodev, CORE_SB(base, sbtmstatelow), NULL); + if ((regdata & SSB_TMSLOW_CLOCK) != 0) { + /* + * set target reject and spin until busy is clear + * (preserve core-specific bits) + */ + regdata = brcmf_sdio_regrl(sdiodev, CORE_SB(base, sbtmstatelow), + NULL); + brcmf_sdio_regwl(sdiodev, CORE_SB(base, sbtmstatelow), + regdata | SSB_TMSLOW_REJECT, NULL); + + regdata = brcmf_sdio_regrl(sdiodev, CORE_SB(base, sbtmstatelow), + NULL); + udelay(1); + SPINWAIT((brcmf_sdio_regrl(sdiodev, + CORE_SB(base, sbtmstatehigh), + NULL) & + SSB_TMSHIGH_BUSY), 100000); + + regdata = brcmf_sdio_regrl(sdiodev, + CORE_SB(base, sbtmstatehigh), + NULL); + if (regdata & SSB_TMSHIGH_BUSY) + brcmf_err("core state still busy\n"); + + regdata = brcmf_sdio_regrl(sdiodev, CORE_SB(base, sbidlow), + NULL); + if (regdata & SSB_IDLOW_INITIATOR) { + regdata = brcmf_sdio_regrl(sdiodev, + CORE_SB(base, sbimstate), + NULL); + regdata |= SSB_IMSTATE_REJECT; + brcmf_sdio_regwl(sdiodev, CORE_SB(base, sbimstate), + regdata, NULL); + regdata = brcmf_sdio_regrl(sdiodev, + CORE_SB(base, sbimstate), + NULL); + udelay(1); + SPINWAIT((brcmf_sdio_regrl(sdiodev, + CORE_SB(base, sbimstate), + NULL) & + SSB_IMSTATE_BUSY), 100000); + } + + /* set reset and reject while enabling the clocks */ + regdata = SSB_TMSLOW_FGC | SSB_TMSLOW_CLOCK | + SSB_TMSLOW_REJECT | SSB_TMSLOW_RESET; + brcmf_sdio_regwl(sdiodev, CORE_SB(base, sbtmstatelow), + regdata, NULL); + regdata = brcmf_sdio_regrl(sdiodev, CORE_SB(base, sbtmstatelow), + NULL); + udelay(10); + + /* clear the initiator reject bit */ + regdata = brcmf_sdio_regrl(sdiodev, CORE_SB(base, sbidlow), + NULL); + if (regdata & SSB_IDLOW_INITIATOR) { + regdata = brcmf_sdio_regrl(sdiodev, + CORE_SB(base, sbimstate), + NULL); + regdata &= ~SSB_IMSTATE_REJECT; + brcmf_sdio_regwl(sdiodev, CORE_SB(base, sbimstate), + regdata, NULL); + } + } + + /* leave reset and reject asserted */ + brcmf_sdio_regwl(sdiodev, CORE_SB(base, sbtmstatelow), + (SSB_TMSLOW_REJECT | SSB_TMSLOW_RESET), NULL); + udelay(1); +} + +static void +brcmf_sdio_ai_coredisable(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, u16 coreid, u32 core_bits) +{ + u8 idx; + u32 regdata; + + idx = brcmf_sdio_chip_getinfidx(ci, coreid); + + /* if core is already in reset, just return */ + regdata = brcmf_sdio_regrl(sdiodev, + ci->c_inf[idx].wrapbase+BCMA_RESET_CTL, + NULL); + if ((regdata & BCMA_RESET_CTL_RESET) != 0) + return; + + /* ensure no pending backplane operation + * 300uc should be sufficient for backplane ops to be finish + * extra 10ms is taken into account for firmware load stage + * after 10300us carry on disabling the core anyway + */ + SPINWAIT(brcmf_sdio_regrl(sdiodev, + ci->c_inf[idx].wrapbase+BCMA_RESET_ST, + NULL), 10300); + regdata = brcmf_sdio_regrl(sdiodev, + ci->c_inf[idx].wrapbase+BCMA_RESET_ST, + NULL); + if (regdata) + brcmf_err("disabling core 0x%x with reset status %x\n", + coreid, regdata); + + brcmf_sdio_regwl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_RESET_CTL, + BCMA_RESET_CTL_RESET, NULL); + udelay(1); + + brcmf_sdio_regwl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_IOCTL, + core_bits, NULL); + regdata = brcmf_sdio_regrl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_IOCTL, + NULL); + usleep_range(10, 20); + +} + +static void +brcmf_sdio_sb_resetcore(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, u16 coreid, u32 core_bits) +{ + u32 regdata; + u8 idx; + + idx = brcmf_sdio_chip_getinfidx(ci, coreid); + + /* + * Must do the disable sequence first to work for + * arbitrary current core state. + */ + brcmf_sdio_sb_coredisable(sdiodev, ci, coreid, 0); + + /* + * Now do the initialization sequence. + * set reset while enabling the clock and + * forcing them on throughout the core + */ + brcmf_sdio_regwl(sdiodev, + CORE_SB(ci->c_inf[idx].base, sbtmstatelow), + SSB_TMSLOW_FGC | SSB_TMSLOW_CLOCK | SSB_TMSLOW_RESET, + NULL); + regdata = brcmf_sdio_regrl(sdiodev, + CORE_SB(ci->c_inf[idx].base, sbtmstatelow), + NULL); + udelay(1); + + /* clear any serror */ + regdata = brcmf_sdio_regrl(sdiodev, + CORE_SB(ci->c_inf[idx].base, sbtmstatehigh), + NULL); + if (regdata & SSB_TMSHIGH_SERR) + brcmf_sdio_regwl(sdiodev, + CORE_SB(ci->c_inf[idx].base, sbtmstatehigh), + 0, NULL); + + regdata = brcmf_sdio_regrl(sdiodev, + CORE_SB(ci->c_inf[idx].base, sbimstate), + NULL); + if (regdata & (SSB_IMSTATE_IBE | SSB_IMSTATE_TO)) + brcmf_sdio_regwl(sdiodev, + CORE_SB(ci->c_inf[idx].base, sbimstate), + regdata & ~(SSB_IMSTATE_IBE | SSB_IMSTATE_TO), + NULL); + + /* clear reset and allow it to propagate throughout the core */ + brcmf_sdio_regwl(sdiodev, CORE_SB(ci->c_inf[idx].base, sbtmstatelow), + SSB_TMSLOW_FGC | SSB_TMSLOW_CLOCK, NULL); + regdata = brcmf_sdio_regrl(sdiodev, + CORE_SB(ci->c_inf[idx].base, sbtmstatelow), + NULL); + udelay(1); + + /* leave clock enabled */ + brcmf_sdio_regwl(sdiodev, CORE_SB(ci->c_inf[idx].base, sbtmstatelow), + SSB_TMSLOW_CLOCK, NULL); + regdata = brcmf_sdio_regrl(sdiodev, + CORE_SB(ci->c_inf[idx].base, sbtmstatelow), + NULL); + udelay(1); +} + +static void +brcmf_sdio_ai_resetcore(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, u16 coreid, u32 core_bits) +{ + u8 idx; + u32 regdata; + + idx = brcmf_sdio_chip_getinfidx(ci, coreid); + + /* must disable first to work for arbitrary current core state */ + brcmf_sdio_ai_coredisable(sdiodev, ci, coreid, core_bits); + + /* now do initialization sequence */ + brcmf_sdio_regwl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_IOCTL, + core_bits | BCMA_IOCTL_FGC | BCMA_IOCTL_CLK, NULL); + regdata = brcmf_sdio_regrl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_IOCTL, + NULL); + brcmf_sdio_regwl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_RESET_CTL, + 0, NULL); + regdata = brcmf_sdio_regrl(sdiodev, + ci->c_inf[idx].wrapbase+BCMA_RESET_CTL, + NULL); + udelay(1); + + brcmf_sdio_regwl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_IOCTL, + core_bits | BCMA_IOCTL_CLK, NULL); + regdata = brcmf_sdio_regrl(sdiodev, ci->c_inf[idx].wrapbase+BCMA_IOCTL, + NULL); + udelay(1); +} + +#ifdef DEBUG +/* safety check for chipinfo */ +static int brcmf_sdio_chip_cichk(struct chip_info *ci) +{ + u8 core_idx; + + /* check RAM core presence for ARM CM3 core */ + core_idx = brcmf_sdio_chip_getinfidx(ci, BCMA_CORE_ARM_CM3); + if (BRCMF_MAX_CORENUM != core_idx) { + core_idx = brcmf_sdio_chip_getinfidx(ci, + BCMA_CORE_INTERNAL_MEM); + if (BRCMF_MAX_CORENUM == core_idx) { + brcmf_err("RAM core not provided with ARM CM3 core\n"); + return -ENODEV; + } + } + + /* check RAM base for ARM CR4 core */ + core_idx = brcmf_sdio_chip_getinfidx(ci, BCMA_CORE_ARM_CR4); + if (BRCMF_MAX_CORENUM != core_idx) { + if (ci->rambase == 0) { + brcmf_err("RAM base not provided with ARM CR4 core\n"); + return -ENOMEM; + } + } + + return 0; +} +#else /* DEBUG */ +static inline int brcmf_sdio_chip_cichk(struct chip_info *ci) +{ + return 0; +} +#endif + +static int brcmf_sdio_chip_recognition(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, u32 regs) +{ + u32 regdata; + int ret; + + /* Get CC core rev + * Chipid is assume to be at offset 0 from regs arg + * For different chiptypes or old sdio hosts w/o chipcommon, + * other ways of recognition should be added here. + */ + ci->c_inf[0].id = BCMA_CORE_CHIPCOMMON; + ci->c_inf[0].base = regs; + regdata = brcmf_sdio_regrl(sdiodev, + CORE_CC_REG(ci->c_inf[0].base, chipid), + NULL); + ci->chip = regdata & CID_ID_MASK; + ci->chiprev = (regdata & CID_REV_MASK) >> CID_REV_SHIFT; + ci->socitype = (regdata & CID_TYPE_MASK) >> CID_TYPE_SHIFT; + + brcmf_dbg(INFO, "chipid=0x%x chiprev=%d\n", ci->chip, ci->chiprev); + + /* Address of cores for new chips should be added here */ + switch (ci->chip) { + case BCM43143_CHIP_ID: + ci->c_inf[0].wrapbase = ci->c_inf[0].base + 0x00100000; + ci->c_inf[0].cib = 0x2b000000; + ci->c_inf[1].id = BCMA_CORE_SDIO_DEV; + ci->c_inf[1].base = BCM43143_CORE_BUS_BASE; + ci->c_inf[1].wrapbase = ci->c_inf[1].base + 0x00100000; + ci->c_inf[1].cib = 0x18000000; + ci->c_inf[2].id = BCMA_CORE_INTERNAL_MEM; + ci->c_inf[2].base = BCM43143_CORE_SOCRAM_BASE; + ci->c_inf[2].wrapbase = ci->c_inf[2].base + 0x00100000; + ci->c_inf[2].cib = 0x14000000; + ci->c_inf[3].id = BCMA_CORE_ARM_CM3; + ci->c_inf[3].base = BCM43143_CORE_ARM_BASE; + ci->c_inf[3].wrapbase = ci->c_inf[3].base + 0x00100000; + ci->c_inf[3].cib = 0x07000000; + ci->ramsize = BCM43143_RAMSIZE; + break; + case BCM43241_CHIP_ID: + ci->c_inf[0].wrapbase = 0x18100000; + ci->c_inf[0].cib = 0x2a084411; + ci->c_inf[1].id = BCMA_CORE_SDIO_DEV; + ci->c_inf[1].base = 0x18002000; + ci->c_inf[1].wrapbase = 0x18102000; + ci->c_inf[1].cib = 0x0e004211; + ci->c_inf[2].id = BCMA_CORE_INTERNAL_MEM; + ci->c_inf[2].base = 0x18004000; + ci->c_inf[2].wrapbase = 0x18104000; + ci->c_inf[2].cib = 0x14080401; + ci->c_inf[3].id = BCMA_CORE_ARM_CM3; + ci->c_inf[3].base = 0x18003000; + ci->c_inf[3].wrapbase = 0x18103000; + ci->c_inf[3].cib = 0x07004211; + ci->ramsize = 0x90000; + break; + case BCM4329_CHIP_ID: + ci->c_inf[1].id = BCMA_CORE_SDIO_DEV; + ci->c_inf[1].base = BCM4329_CORE_BUS_BASE; + ci->c_inf[2].id = BCMA_CORE_INTERNAL_MEM; + ci->c_inf[2].base = BCM4329_CORE_SOCRAM_BASE; + ci->c_inf[3].id = BCMA_CORE_ARM_CM3; + ci->c_inf[3].base = BCM4329_CORE_ARM_BASE; + ci->ramsize = BCM4329_RAMSIZE; + break; + case BCM4330_CHIP_ID: + ci->c_inf[0].wrapbase = 0x18100000; + ci->c_inf[0].cib = 0x27004211; + ci->c_inf[1].id = BCMA_CORE_SDIO_DEV; + ci->c_inf[1].base = 0x18002000; + ci->c_inf[1].wrapbase = 0x18102000; + ci->c_inf[1].cib = 0x07004211; + ci->c_inf[2].id = BCMA_CORE_INTERNAL_MEM; + ci->c_inf[2].base = 0x18004000; + ci->c_inf[2].wrapbase = 0x18104000; + ci->c_inf[2].cib = 0x0d080401; + ci->c_inf[3].id = BCMA_CORE_ARM_CM3; + ci->c_inf[3].base = 0x18003000; + ci->c_inf[3].wrapbase = 0x18103000; + ci->c_inf[3].cib = 0x03004211; + ci->ramsize = 0x48000; + break; + case BCM4334_CHIP_ID: + ci->c_inf[0].wrapbase = 0x18100000; + ci->c_inf[0].cib = 0x29004211; + ci->c_inf[1].id = BCMA_CORE_SDIO_DEV; + ci->c_inf[1].base = 0x18002000; + ci->c_inf[1].wrapbase = 0x18102000; + ci->c_inf[1].cib = 0x0d004211; + ci->c_inf[2].id = BCMA_CORE_INTERNAL_MEM; + ci->c_inf[2].base = 0x18004000; + ci->c_inf[2].wrapbase = 0x18104000; + ci->c_inf[2].cib = 0x13080401; + ci->c_inf[3].id = BCMA_CORE_ARM_CM3; + ci->c_inf[3].base = 0x18003000; + ci->c_inf[3].wrapbase = 0x18103000; + ci->c_inf[3].cib = 0x07004211; + ci->ramsize = 0x80000; + break; + case BCM4335_CHIP_ID: + ci->c_inf[0].wrapbase = 0x18100000; + ci->c_inf[0].cib = 0x2b084411; + ci->c_inf[1].id = BCMA_CORE_SDIO_DEV; + ci->c_inf[1].base = 0x18005000; + ci->c_inf[1].wrapbase = 0x18105000; + ci->c_inf[1].cib = 0x0f004211; + ci->c_inf[2].id = BCMA_CORE_ARM_CR4; + ci->c_inf[2].base = 0x18002000; + ci->c_inf[2].wrapbase = 0x18102000; + ci->c_inf[2].cib = 0x01084411; + ci->ramsize = 0xc0000; + ci->rambase = 0x180000; + break; + default: + brcmf_err("chipid 0x%x is not supported\n", ci->chip); + return -ENODEV; + } + + ret = brcmf_sdio_chip_cichk(ci); + if (ret) + return ret; + + switch (ci->socitype) { + case SOCI_SB: + ci->iscoreup = brcmf_sdio_sb_iscoreup; + ci->corerev = brcmf_sdio_sb_corerev; + ci->coredisable = brcmf_sdio_sb_coredisable; + ci->resetcore = brcmf_sdio_sb_resetcore; + break; + case SOCI_AI: + ci->iscoreup = brcmf_sdio_ai_iscoreup; + ci->corerev = brcmf_sdio_ai_corerev; + ci->coredisable = brcmf_sdio_ai_coredisable; + ci->resetcore = brcmf_sdio_ai_resetcore; + break; + default: + brcmf_err("socitype %u not supported\n", ci->socitype); + return -ENODEV; + } + + return 0; +} + +static int +brcmf_sdio_chip_buscoreprep(struct brcmf_sdio_dev *sdiodev) +{ + int err = 0; + u8 clkval, clkset; + + /* Try forcing SDIO core to do ALPAvail request only */ + clkset = SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_ALP_AVAIL_REQ; + brcmf_sdio_regwb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, clkset, &err); + if (err) { + brcmf_err("error writing for HT off\n"); + return err; + } + + /* If register supported, wait for ALPAvail and then force ALP */ + /* This may take up to 15 milliseconds */ + clkval = brcmf_sdio_regrb(sdiodev, + SBSDIO_FUNC1_CHIPCLKCSR, NULL); + + if ((clkval & ~SBSDIO_AVBITS) != clkset) { + brcmf_err("ChipClkCSR access: wrote 0x%02x read 0x%02x\n", + clkset, clkval); + return -EACCES; + } + + SPINWAIT(((clkval = brcmf_sdio_regrb(sdiodev, + SBSDIO_FUNC1_CHIPCLKCSR, NULL)), + !SBSDIO_ALPAV(clkval)), + PMU_MAX_TRANSITION_DLY); + if (!SBSDIO_ALPAV(clkval)) { + brcmf_err("timeout on ALPAV wait, clkval 0x%02x\n", + clkval); + return -EBUSY; + } + + clkset = SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_FORCE_ALP; + brcmf_sdio_regwb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, clkset, &err); + udelay(65); + + /* Also, disable the extra SDIO pull-ups */ + brcmf_sdio_regwb(sdiodev, SBSDIO_FUNC1_SDIOPULLUP, 0, NULL); + + return 0; +} + +static void +brcmf_sdio_chip_buscoresetup(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci) +{ + u32 base = ci->c_inf[0].base; + + /* get chipcommon rev */ + ci->c_inf[0].rev = ci->corerev(sdiodev, ci, ci->c_inf[0].id); + + /* get chipcommon capabilites */ + ci->c_inf[0].caps = brcmf_sdio_regrl(sdiodev, + CORE_CC_REG(base, capabilities), + NULL); + + /* get pmu caps & rev */ + if (ci->c_inf[0].caps & CC_CAP_PMU) { + ci->pmucaps = + brcmf_sdio_regrl(sdiodev, + CORE_CC_REG(base, pmucapabilities), + NULL); + ci->pmurev = ci->pmucaps & PCAP_REV_MASK; + } + + ci->c_inf[1].rev = ci->corerev(sdiodev, ci, ci->c_inf[1].id); + + brcmf_dbg(INFO, "ccrev=%d, pmurev=%d, buscore rev/type=%d/0x%x\n", + ci->c_inf[0].rev, ci->pmurev, + ci->c_inf[1].rev, ci->c_inf[1].id); + + /* + * Make sure any on-chip ARM is off (in case strapping is wrong), + * or downloaded code was already running. + */ + ci->coredisable(sdiodev, ci, BCMA_CORE_ARM_CM3, 0); +} + +int brcmf_sdio_chip_attach(struct brcmf_sdio_dev *sdiodev, + struct chip_info **ci_ptr, u32 regs) +{ + int ret; + struct chip_info *ci; + + brcmf_dbg(TRACE, "Enter\n"); + + /* alloc chip_info_t */ + ci = kzalloc(sizeof(struct chip_info), GFP_ATOMIC); + if (!ci) + return -ENOMEM; + + ret = brcmf_sdio_chip_buscoreprep(sdiodev); + if (ret != 0) + goto err; + + ret = brcmf_sdio_chip_recognition(sdiodev, ci, regs); + if (ret != 0) + goto err; + + brcmf_sdio_chip_buscoresetup(sdiodev, ci); + + brcmf_sdio_regwl(sdiodev, CORE_CC_REG(ci->c_inf[0].base, gpiopullup), + 0, NULL); + brcmf_sdio_regwl(sdiodev, CORE_CC_REG(ci->c_inf[0].base, gpiopulldown), + 0, NULL); + + *ci_ptr = ci; + return 0; + +err: + kfree(ci); + return ret; +} + +void +brcmf_sdio_chip_detach(struct chip_info **ci_ptr) +{ + brcmf_dbg(TRACE, "Enter\n"); + + kfree(*ci_ptr); + *ci_ptr = NULL; +} + +static char *brcmf_sdio_chip_name(uint chipid, char *buf, uint len) +{ + const char *fmt; + + fmt = ((chipid > 0xa000) || (chipid < 0x4000)) ? "%d" : "%x"; + snprintf(buf, len, fmt, chipid); + return buf; +} + +void +brcmf_sdio_chip_drivestrengthinit(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, u32 drivestrength) +{ + const struct sdiod_drive_str *str_tab = NULL; + u32 str_mask; + u32 str_shift; + char chn[8]; + u32 base = ci->c_inf[0].base; + u32 i; + u32 drivestrength_sel = 0; + u32 cc_data_temp; + u32 addr; + + if (!(ci->c_inf[0].caps & CC_CAP_PMU)) + return; + + switch (SDIOD_DRVSTR_KEY(ci->chip, ci->pmurev)) { + case SDIOD_DRVSTR_KEY(BCM4330_CHIP_ID, 12): + str_tab = sdiod_drvstr_tab1_1v8; + str_mask = 0x00003800; + str_shift = 11; + break; + case SDIOD_DRVSTR_KEY(BCM43143_CHIP_ID, 17): + /* note: 43143 does not support tristate */ + i = ARRAY_SIZE(sdiod_drvstr_tab2_3v3) - 1; + if (drivestrength >= sdiod_drvstr_tab2_3v3[i].strength) { + str_tab = sdiod_drvstr_tab2_3v3; + str_mask = 0x00000007; + str_shift = 0; + } else + brcmf_err("Invalid SDIO Drive strength for chip %s, strength=%d\n", + brcmf_sdio_chip_name(ci->chip, chn, 8), + drivestrength); + break; + default: + brcmf_err("No SDIO Drive strength init done for chip %s rev %d pmurev %d\n", + brcmf_sdio_chip_name(ci->chip, chn, 8), + ci->chiprev, ci->pmurev); + break; + } + + if (str_tab != NULL) { + for (i = 0; str_tab[i].strength != 0; i++) { + if (drivestrength >= str_tab[i].strength) { + drivestrength_sel = str_tab[i].sel; + break; + } + } + addr = CORE_CC_REG(base, chipcontrol_addr); + brcmf_sdio_regwl(sdiodev, addr, 1, NULL); + cc_data_temp = brcmf_sdio_regrl(sdiodev, addr, NULL); + cc_data_temp &= ~str_mask; + drivestrength_sel <<= str_shift; + cc_data_temp |= drivestrength_sel; + brcmf_sdio_regwl(sdiodev, addr, cc_data_temp, NULL); + + brcmf_dbg(INFO, "SDIO: %d mA (req=%d mA) drive strength selected, set to 0x%08x\n", + str_tab[i].strength, drivestrength, cc_data_temp); + } +} + +#ifdef DEBUG +static bool +brcmf_sdio_chip_verifynvram(struct brcmf_sdio_dev *sdiodev, u32 nvram_addr, + char *nvram_dat, uint nvram_sz) +{ + char *nvram_ularray; + int err; + bool ret = true; + + /* read back and verify */ + brcmf_dbg(INFO, "Compare NVRAM dl & ul; size=%d\n", nvram_sz); + nvram_ularray = kmalloc(nvram_sz, GFP_KERNEL); + /* do not proceed while no memory but */ + if (!nvram_ularray) + return true; + + /* Upload image to verify downloaded contents. */ + memset(nvram_ularray, 0xaa, nvram_sz); + + /* Read the vars list to temp buffer for comparison */ + err = brcmf_sdio_ramrw(sdiodev, false, nvram_addr, nvram_ularray, + nvram_sz); + if (err) { + brcmf_err("error %d on reading %d nvram bytes at 0x%08x\n", + err, nvram_sz, nvram_addr); + } else if (memcmp(nvram_dat, nvram_ularray, nvram_sz)) { + brcmf_err("Downloaded NVRAM image is corrupted\n"); + ret = false; + } + kfree(nvram_ularray); + + return ret; +} +#else /* DEBUG */ +static inline bool +brcmf_sdio_chip_verifynvram(struct brcmf_sdio_dev *sdiodev, u32 nvram_addr, + char *nvram_dat, uint nvram_sz) +{ + return true; +} +#endif /* DEBUG */ + +static bool brcmf_sdio_chip_writenvram(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, + char *nvram_dat, uint nvram_sz) +{ + int err; + u32 nvram_addr; + u32 token; + __le32 token_le; + + nvram_addr = (ci->ramsize - 4) - nvram_sz + ci->rambase; + + /* Write the vars list */ + err = brcmf_sdio_ramrw(sdiodev, true, nvram_addr, nvram_dat, nvram_sz); + if (err) { + brcmf_err("error %d on writing %d nvram bytes at 0x%08x\n", + err, nvram_sz, nvram_addr); + return false; + } + + if (!brcmf_sdio_chip_verifynvram(sdiodev, nvram_addr, + nvram_dat, nvram_sz)) + return false; + + /* generate token: + * nvram size, converted to words, in lower 16-bits, checksum + * in upper 16-bits. + */ + token = nvram_sz / 4; + token = (~token << 16) | (token & 0x0000FFFF); + token_le = cpu_to_le32(token); + + brcmf_dbg(INFO, "RAM size: %d\n", ci->ramsize); + brcmf_dbg(INFO, "nvram is placed at %d, size %d, token=0x%08x\n", + nvram_addr, nvram_sz, token); + + /* Write the length token to the last word */ + if (brcmf_sdio_ramrw(sdiodev, true, (ci->ramsize - 4 + ci->rambase), + (u8 *)&token_le, 4)) + return false; + + return true; +} + +static void +brcmf_sdio_chip_cm3_enterdl(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci) +{ + u32 zeros = 0; + + ci->coredisable(sdiodev, ci, BCMA_CORE_ARM_CM3, 0); + ci->resetcore(sdiodev, ci, BCMA_CORE_INTERNAL_MEM, 0); + + /* clear length token */ + brcmf_sdio_ramrw(sdiodev, true, ci->ramsize - 4, (u8 *)&zeros, 4); +} + +static bool +brcmf_sdio_chip_cm3_exitdl(struct brcmf_sdio_dev *sdiodev, struct chip_info *ci, + char *nvram_dat, uint nvram_sz) +{ + u8 core_idx; + u32 reg_addr; + + if (!ci->iscoreup(sdiodev, ci, BCMA_CORE_INTERNAL_MEM)) { + brcmf_err("SOCRAM core is down after reset?\n"); + return false; + } + + if (!brcmf_sdio_chip_writenvram(sdiodev, ci, nvram_dat, nvram_sz)) + return false; + + /* clear all interrupts */ + core_idx = brcmf_sdio_chip_getinfidx(ci, BCMA_CORE_SDIO_DEV); + reg_addr = ci->c_inf[core_idx].base; + reg_addr += offsetof(struct sdpcmd_regs, intstatus); + brcmf_sdio_regwl(sdiodev, reg_addr, 0xFFFFFFFF, NULL); + + ci->resetcore(sdiodev, ci, BCMA_CORE_ARM_CM3, 0); + + return true; +} + +static inline void +brcmf_sdio_chip_cr4_enterdl(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci) +{ + ci->resetcore(sdiodev, ci, BCMA_CORE_ARM_CR4, + ARMCR4_BCMA_IOCTL_CPUHALT); +} + +static bool +brcmf_sdio_chip_cr4_exitdl(struct brcmf_sdio_dev *sdiodev, struct chip_info *ci, + char *nvram_dat, uint nvram_sz) +{ + u8 core_idx; + u32 reg_addr; + + if (!brcmf_sdio_chip_writenvram(sdiodev, ci, nvram_dat, nvram_sz)) + return false; + + /* clear all interrupts */ + core_idx = brcmf_sdio_chip_getinfidx(ci, BCMA_CORE_SDIO_DEV); + reg_addr = ci->c_inf[core_idx].base; + reg_addr += offsetof(struct sdpcmd_regs, intstatus); + brcmf_sdio_regwl(sdiodev, reg_addr, 0xFFFFFFFF, NULL); + + /* Write reset vector to address 0 */ + brcmf_sdio_ramrw(sdiodev, true, 0, (void *)&ci->rst_vec, + sizeof(ci->rst_vec)); + + /* restore ARM */ + ci->resetcore(sdiodev, ci, BCMA_CORE_ARM_CR4, 0); + + return true; +} + +void brcmf_sdio_chip_enter_download(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci) +{ + u8 arm_core_idx; + + arm_core_idx = brcmf_sdio_chip_getinfidx(ci, BCMA_CORE_ARM_CM3); + if (BRCMF_MAX_CORENUM != arm_core_idx) { + brcmf_sdio_chip_cm3_enterdl(sdiodev, ci); + return; + } + + brcmf_sdio_chip_cr4_enterdl(sdiodev, ci); +} + +bool brcmf_sdio_chip_exit_download(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, char *nvram_dat, + uint nvram_sz) +{ + u8 arm_core_idx; + + arm_core_idx = brcmf_sdio_chip_getinfidx(ci, BCMA_CORE_ARM_CM3); + if (BRCMF_MAX_CORENUM != arm_core_idx) + return brcmf_sdio_chip_cm3_exitdl(sdiodev, ci, nvram_dat, + nvram_sz); + + return brcmf_sdio_chip_cr4_exitdl(sdiodev, ci, nvram_dat, nvram_sz); +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/sdio_chip.h b/drivers/net/wireless/brcm80211/brcmfmac/sdio_chip.h new file mode 100644 index 0000000..83c041f --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/sdio_chip.h @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2011 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCMFMAC_SDIO_CHIP_H_ +#define _BRCMFMAC_SDIO_CHIP_H_ + +/* + * Core reg address translation. + * Both macro's returns a 32 bits byte address on the backplane bus. + */ +#define CORE_CC_REG(base, field) \ + (base + offsetof(struct chipcregs, field)) +#define CORE_BUS_REG(base, field) \ + (base + offsetof(struct sdpcmd_regs, field)) +#define CORE_SB(base, field) \ + (base + SBCONFIGOFF + offsetof(struct sbconfig, field)) + +/* SDIO function 1 register CHIPCLKCSR */ +/* Force ALP request to backplane */ +#define SBSDIO_FORCE_ALP 0x01 +/* Force HT request to backplane */ +#define SBSDIO_FORCE_HT 0x02 +/* Force ILP request to backplane */ +#define SBSDIO_FORCE_ILP 0x04 +/* Make ALP ready (power up xtal) */ +#define SBSDIO_ALP_AVAIL_REQ 0x08 +/* Make HT ready (power up PLL) */ +#define SBSDIO_HT_AVAIL_REQ 0x10 +/* Squelch clock requests from HW */ +#define SBSDIO_FORCE_HW_CLKREQ_OFF 0x20 +/* Status: ALP is ready */ +#define SBSDIO_ALP_AVAIL 0x40 +/* Status: HT is ready */ +#define SBSDIO_HT_AVAIL 0x80 +#define SBSDIO_AVBITS (SBSDIO_HT_AVAIL | SBSDIO_ALP_AVAIL) +#define SBSDIO_ALPAV(regval) ((regval) & SBSDIO_AVBITS) +#define SBSDIO_HTAV(regval) (((regval) & SBSDIO_AVBITS) == SBSDIO_AVBITS) +#define SBSDIO_ALPONLY(regval) (SBSDIO_ALPAV(regval) && !SBSDIO_HTAV(regval)) +#define SBSDIO_CLKAV(regval, alponly) \ + (SBSDIO_ALPAV(regval) && (alponly ? 1 : SBSDIO_HTAV(regval))) + +#define BRCMF_MAX_CORENUM 6 + +struct chip_core_info { + u16 id; + u16 rev; + u32 base; + u32 wrapbase; + u32 caps; + u32 cib; +}; + +struct chip_info { + u32 chip; + u32 chiprev; + u32 socitype; + /* core info */ + /* always put chipcommon core at 0, bus core at 1 */ + struct chip_core_info c_inf[BRCMF_MAX_CORENUM]; + u32 pmurev; + u32 pmucaps; + u32 ramsize; + u32 rambase; + u32 rst_vec; /* reset vertor for ARM CR4 core */ + + bool (*iscoreup)(struct brcmf_sdio_dev *sdiodev, struct chip_info *ci, + u16 coreid); + u32 (*corerev)(struct brcmf_sdio_dev *sdiodev, struct chip_info *ci, + u16 coreid); + void (*coredisable)(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, u16 coreid, u32 core_bits); + void (*resetcore)(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, u16 coreid, u32 core_bits); +}; + +struct sbconfig { + u32 PAD[2]; + u32 sbipsflag; /* initiator port ocp slave flag */ + u32 PAD[3]; + u32 sbtpsflag; /* target port ocp slave flag */ + u32 PAD[11]; + u32 sbtmerrloga; /* (sonics >= 2.3) */ + u32 PAD; + u32 sbtmerrlog; /* (sonics >= 2.3) */ + u32 PAD[3]; + u32 sbadmatch3; /* address match3 */ + u32 PAD; + u32 sbadmatch2; /* address match2 */ + u32 PAD; + u32 sbadmatch1; /* address match1 */ + u32 PAD[7]; + u32 sbimstate; /* initiator agent state */ + u32 sbintvec; /* interrupt mask */ + u32 sbtmstatelow; /* target state */ + u32 sbtmstatehigh; /* target state */ + u32 sbbwa0; /* bandwidth allocation table0 */ + u32 PAD; + u32 sbimconfiglow; /* initiator configuration */ + u32 sbimconfighigh; /* initiator configuration */ + u32 sbadmatch0; /* address match0 */ + u32 PAD; + u32 sbtmconfiglow; /* target configuration */ + u32 sbtmconfighigh; /* target configuration */ + u32 sbbconfig; /* broadcast configuration */ + u32 PAD; + u32 sbbstate; /* broadcast state */ + u32 PAD[3]; + u32 sbactcnfg; /* activate configuration */ + u32 PAD[3]; + u32 sbflagst; /* current sbflags */ + u32 PAD[3]; + u32 sbidlow; /* identification */ + u32 sbidhigh; /* identification */ +}; + +/* sdio core registers */ +struct sdpcmd_regs { + u32 corecontrol; /* 0x00, rev8 */ + u32 corestatus; /* rev8 */ + u32 PAD[1]; + u32 biststatus; /* rev8 */ + + /* PCMCIA access */ + u16 pcmciamesportaladdr; /* 0x010, rev8 */ + u16 PAD[1]; + u16 pcmciamesportalmask; /* rev8 */ + u16 PAD[1]; + u16 pcmciawrframebc; /* rev8 */ + u16 PAD[1]; + u16 pcmciaunderflowtimer; /* rev8 */ + u16 PAD[1]; + + /* interrupt */ + u32 intstatus; /* 0x020, rev8 */ + u32 hostintmask; /* rev8 */ + u32 intmask; /* rev8 */ + u32 sbintstatus; /* rev8 */ + u32 sbintmask; /* rev8 */ + u32 funcintmask; /* rev4 */ + u32 PAD[2]; + u32 tosbmailbox; /* 0x040, rev8 */ + u32 tohostmailbox; /* rev8 */ + u32 tosbmailboxdata; /* rev8 */ + u32 tohostmailboxdata; /* rev8 */ + + /* synchronized access to registers in SDIO clock domain */ + u32 sdioaccess; /* 0x050, rev8 */ + u32 PAD[3]; + + /* PCMCIA frame control */ + u8 pcmciaframectrl; /* 0x060, rev8 */ + u8 PAD[3]; + u8 pcmciawatermark; /* rev8 */ + u8 PAD[155]; + + /* interrupt batching control */ + u32 intrcvlazy; /* 0x100, rev8 */ + u32 PAD[3]; + + /* counters */ + u32 cmd52rd; /* 0x110, rev8 */ + u32 cmd52wr; /* rev8 */ + u32 cmd53rd; /* rev8 */ + u32 cmd53wr; /* rev8 */ + u32 abort; /* rev8 */ + u32 datacrcerror; /* rev8 */ + u32 rdoutofsync; /* rev8 */ + u32 wroutofsync; /* rev8 */ + u32 writebusy; /* rev8 */ + u32 readwait; /* rev8 */ + u32 readterm; /* rev8 */ + u32 writeterm; /* rev8 */ + u32 PAD[40]; + u32 clockctlstatus; /* rev8 */ + u32 PAD[7]; + + u32 PAD[128]; /* DMA engines */ + + /* SDIO/PCMCIA CIS region */ + char cis[512]; /* 0x400-0x5ff, rev6 */ + + /* PCMCIA function control registers */ + char pcmciafcr[256]; /* 0x600-6ff, rev6 */ + u16 PAD[55]; + + /* PCMCIA backplane access */ + u16 backplanecsr; /* 0x76E, rev6 */ + u16 backplaneaddr0; /* rev6 */ + u16 backplaneaddr1; /* rev6 */ + u16 backplaneaddr2; /* rev6 */ + u16 backplaneaddr3; /* rev6 */ + u16 backplanedata0; /* rev6 */ + u16 backplanedata1; /* rev6 */ + u16 backplanedata2; /* rev6 */ + u16 backplanedata3; /* rev6 */ + u16 PAD[31]; + + /* sprom "size" & "blank" info */ + u16 spromstatus; /* 0x7BE, rev2 */ + u32 PAD[464]; + + u16 PAD[0x80]; +}; + +extern int brcmf_sdio_chip_attach(struct brcmf_sdio_dev *sdiodev, + struct chip_info **ci_ptr, u32 regs); +extern void brcmf_sdio_chip_detach(struct chip_info **ci_ptr); +extern void brcmf_sdio_chip_drivestrengthinit(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, + u32 drivestrength); +extern u8 brcmf_sdio_chip_getinfidx(struct chip_info *ci, u16 coreid); +extern void brcmf_sdio_chip_enter_download(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci); +extern bool brcmf_sdio_chip_exit_download(struct brcmf_sdio_dev *sdiodev, + struct chip_info *ci, char *nvram_dat, + uint nvram_sz); + +#endif /* _BRCMFMAC_SDIO_CHIP_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h b/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h new file mode 100644 index 0000000..7c1b633 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/sdio_host.h @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_SDH_H_ +#define _BRCM_SDH_H_ + +#include + +#define SDIO_FUNC_0 0 +#define SDIO_FUNC_1 1 +#define SDIO_FUNC_2 2 + +#define SDIOD_FBR_SIZE 0x100 + +/* io_en */ +#define SDIO_FUNC_ENABLE_1 0x02 +#define SDIO_FUNC_ENABLE_2 0x04 + +/* io_rdys */ +#define SDIO_FUNC_READY_1 0x02 +#define SDIO_FUNC_READY_2 0x04 + +/* intr_status */ +#define INTR_STATUS_FUNC1 0x2 +#define INTR_STATUS_FUNC2 0x4 + +/* Maximum number of I/O funcs */ +#define SDIOD_MAX_IOFUNCS 7 + +/* mask of register map */ +#define REG_F0_REG_MASK 0x7FF +#define REG_F1_MISC_MASK 0x1FFFF + +/* as of sdiod rev 0, supports 3 functions */ +#define SBSDIO_NUM_FUNCTION 3 + +/* function 0 vendor specific CCCR registers */ +#define SDIO_CCCR_BRCM_CARDCAP 0xf0 +#define SDIO_CCCR_BRCM_CARDCAP_CMD14_SUPPORT 0x02 +#define SDIO_CCCR_BRCM_CARDCAP_CMD14_EXT 0x04 +#define SDIO_CCCR_BRCM_CARDCAP_CMD_NODEC 0x08 +#define SDIO_CCCR_BRCM_CARDCTRL 0xf1 +#define SDIO_CCCR_BRCM_CARDCTRL_WLANRESET 0x02 +#define SDIO_CCCR_BRCM_SEPINT 0xf2 + +#define SDIO_SEPINT_MASK 0x01 +#define SDIO_SEPINT_OE 0x02 +#define SDIO_SEPINT_ACT_HI 0x04 + +/* function 1 miscellaneous registers */ + +/* sprom command and status */ +#define SBSDIO_SPROM_CS 0x10000 +/* sprom info register */ +#define SBSDIO_SPROM_INFO 0x10001 +/* sprom indirect access data byte 0 */ +#define SBSDIO_SPROM_DATA_LOW 0x10002 +/* sprom indirect access data byte 1 */ +#define SBSDIO_SPROM_DATA_HIGH 0x10003 +/* sprom indirect access addr byte 0 */ +#define SBSDIO_SPROM_ADDR_LOW 0x10004 +/* sprom indirect access addr byte 0 */ +#define SBSDIO_SPROM_ADDR_HIGH 0x10005 +/* xtal_pu (gpio) output */ +#define SBSDIO_CHIP_CTRL_DATA 0x10006 +/* xtal_pu (gpio) enable */ +#define SBSDIO_CHIP_CTRL_EN 0x10007 +/* rev < 7, watermark for sdio device */ +#define SBSDIO_WATERMARK 0x10008 +/* control busy signal generation */ +#define SBSDIO_DEVICE_CTL 0x10009 + +/* SB Address Window Low (b15) */ +#define SBSDIO_FUNC1_SBADDRLOW 0x1000A +/* SB Address Window Mid (b23:b16) */ +#define SBSDIO_FUNC1_SBADDRMID 0x1000B +/* SB Address Window High (b31:b24) */ +#define SBSDIO_FUNC1_SBADDRHIGH 0x1000C +/* Frame Control (frame term/abort) */ +#define SBSDIO_FUNC1_FRAMECTRL 0x1000D +/* ChipClockCSR (ALP/HT ctl/status) */ +#define SBSDIO_FUNC1_CHIPCLKCSR 0x1000E +/* SdioPullUp (on cmd, d0-d2) */ +#define SBSDIO_FUNC1_SDIOPULLUP 0x1000F +/* Write Frame Byte Count Low */ +#define SBSDIO_FUNC1_WFRAMEBCLO 0x10019 +/* Write Frame Byte Count High */ +#define SBSDIO_FUNC1_WFRAMEBCHI 0x1001A +/* Read Frame Byte Count Low */ +#define SBSDIO_FUNC1_RFRAMEBCLO 0x1001B +/* Read Frame Byte Count High */ +#define SBSDIO_FUNC1_RFRAMEBCHI 0x1001C +/* MesBusyCtl (rev 11) */ +#define SBSDIO_FUNC1_MESBUSYCTRL 0x1001D +/* Sdio Core Rev 12 */ +#define SBSDIO_FUNC1_WAKEUPCTRL 0x1001E +#define SBSDIO_FUNC1_WCTRL_ALPWAIT_MASK 0x1 +#define SBSDIO_FUNC1_WCTRL_ALPWAIT_SHIFT 0 +#define SBSDIO_FUNC1_WCTRL_HTWAIT_MASK 0x2 +#define SBSDIO_FUNC1_WCTRL_HTWAIT_SHIFT 1 +#define SBSDIO_FUNC1_SLEEPCSR 0x1001F +#define SBSDIO_FUNC1_SLEEPCSR_KSO_MASK 0x1 +#define SBSDIO_FUNC1_SLEEPCSR_KSO_SHIFT 0 +#define SBSDIO_FUNC1_SLEEPCSR_KSO_EN 1 +#define SBSDIO_FUNC1_SLEEPCSR_DEVON_MASK 0x2 +#define SBSDIO_FUNC1_SLEEPCSR_DEVON_SHIFT 1 + +#define SBSDIO_FUNC1_MISC_REG_START 0x10000 /* f1 misc register start */ +#define SBSDIO_FUNC1_MISC_REG_LIMIT 0x1001F /* f1 misc register end */ + +/* function 1 OCP space */ + +/* sb offset addr is <= 15 bits, 32k */ +#define SBSDIO_SB_OFT_ADDR_MASK 0x07FFF +#define SBSDIO_SB_OFT_ADDR_LIMIT 0x08000 +/* with b15, maps to 32-bit SB access */ +#define SBSDIO_SB_ACCESS_2_4B_FLAG 0x08000 + +/* valid bits in SBSDIO_FUNC1_SBADDRxxx regs */ + +#define SBSDIO_SBADDRLOW_MASK 0x80 /* Valid bits in SBADDRLOW */ +#define SBSDIO_SBADDRMID_MASK 0xff /* Valid bits in SBADDRMID */ +#define SBSDIO_SBADDRHIGH_MASK 0xffU /* Valid bits in SBADDRHIGH */ +/* Address bits from SBADDR regs */ +#define SBSDIO_SBWINDOW_MASK 0xffff8000 + +#define SDIOH_READ 0 /* Read request */ +#define SDIOH_WRITE 1 /* Write request */ + +#define SDIOH_DATA_FIX 0 /* Fixed addressing */ +#define SDIOH_DATA_INC 1 /* Incremental addressing */ + +/* internal return code */ +#define SUCCESS 0 +#define ERROR 1 + +/* Packet alignment for most efficient SDIO (can change based on platform) */ +#define BRCMF_SDALIGN (1 << 6) + +/* watchdog polling interval in ms */ +#define BRCMF_WD_POLL_MS 10 + +struct brcmf_sdreg { + int func; + int offset; + int value; +}; + +struct brcmf_sdio; + +struct brcmf_sdio_dev { + struct sdio_func *func[SDIO_MAX_FUNCS]; + u8 num_funcs; /* Supported funcs on client */ + u32 func_cis_ptr[SDIOD_MAX_IOFUNCS]; + u32 sbwad; /* Save backplane window address */ + void *bus; + atomic_t suspend; /* suspend flag */ + wait_queue_head_t request_byte_wait; + wait_queue_head_t request_word_wait; + wait_queue_head_t request_chain_wait; + wait_queue_head_t request_buffer_wait; + struct device *dev; + struct brcmf_bus *bus_if; + struct brcmfmac_sdio_platform_data *pdata; + bool oob_irq_requested; + bool irq_en; /* irq enable flags */ + spinlock_t irq_en_lock; + bool irq_wake; /* irq wake enable flags */ +}; + +/* Register/deregister interrupt handler. */ +extern int brcmf_sdio_intr_register(struct brcmf_sdio_dev *sdiodev); +extern int brcmf_sdio_intr_unregister(struct brcmf_sdio_dev *sdiodev); + +/* sdio device register access interface */ +extern u8 brcmf_sdio_regrb(struct brcmf_sdio_dev *sdiodev, u32 addr, int *ret); +extern u32 brcmf_sdio_regrl(struct brcmf_sdio_dev *sdiodev, u32 addr, int *ret); +extern void brcmf_sdio_regwb(struct brcmf_sdio_dev *sdiodev, u32 addr, + u8 data, int *ret); +extern void brcmf_sdio_regwl(struct brcmf_sdio_dev *sdiodev, u32 addr, + u32 data, int *ret); +extern int brcmf_sdio_regrw_helper(struct brcmf_sdio_dev *sdiodev, u32 addr, + void *data, bool write); + +/* Buffer transfer to/from device (client) core via cmd53. + * fn: function number + * addr: backplane address (i.e. >= regsva from attach) + * flags: backplane width, address increment, sync/async + * buf: pointer to memory data buffer + * nbytes: number of bytes to transfer to/from buf + * pkt: pointer to packet associated with buf (if any) + * complete: callback function for command completion (async only) + * handle: handle for completion callback (first arg in callback) + * Returns 0 or error code. + * NOTE: Async operation is not currently supported. + */ +extern int +brcmf_sdcard_send_pkt(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, + uint flags, struct sk_buff *pkt); +extern int +brcmf_sdcard_send_buf(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, + uint flags, u8 *buf, uint nbytes); + +extern int +brcmf_sdcard_recv_pkt(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, + uint flags, struct sk_buff *pkt); +extern int +brcmf_sdcard_recv_buf(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, + uint flags, u8 *buf, uint nbytes); +extern int +brcmf_sdcard_recv_chain(struct brcmf_sdio_dev *sdiodev, u32 addr, uint fn, + uint flags, struct sk_buff_head *pktq); + +/* Flags bits */ + +/* Four-byte target (backplane) width (vs. two-byte) */ +#define SDIO_REQ_4BYTE 0x1 +/* Fixed address (FIFO) (vs. incrementing address) */ +#define SDIO_REQ_FIXED 0x2 +/* Async request (vs. sync request) */ +#define SDIO_REQ_ASYNC 0x4 + +/* Read/write to memory block (F1, no FIFO) via CMD53 (sync only). + * rw: read or write (0/1) + * addr: direct SDIO address + * buf: pointer to memory data buffer + * nbytes: number of bytes to transfer to/from buf + * Returns 0 or error code. + */ +extern int brcmf_sdcard_rwdata(struct brcmf_sdio_dev *sdiodev, uint rw, + u32 addr, u8 *buf, uint nbytes); +extern int brcmf_sdio_ramrw(struct brcmf_sdio_dev *sdiodev, bool write, + u32 address, u8 *data, uint size); + +/* Issue an abort to the specified function */ +extern int brcmf_sdcard_abort(struct brcmf_sdio_dev *sdiodev, uint fn); + +/* platform specific/high level functions */ +extern int brcmf_sdio_probe(struct brcmf_sdio_dev *sdiodev); +extern int brcmf_sdio_remove(struct brcmf_sdio_dev *sdiodev); + +extern int brcmf_sdcard_set_sbaddr_window(struct brcmf_sdio_dev *sdiodev, + u32 address); + +/* attach, return handler on success, NULL if failed. + * The handler shall be provided by all subsequent calls. No local cache + * cfghdl points to the starting address of pci device mapped memory + */ +extern int brcmf_sdioh_attach(struct brcmf_sdio_dev *sdiodev); +extern void brcmf_sdioh_detach(struct brcmf_sdio_dev *sdiodev); + +/* read or write one byte using cmd52 */ +extern int brcmf_sdioh_request_byte(struct brcmf_sdio_dev *sdiodev, uint rw, + uint fnc, uint addr, u8 *byte); + +/* read or write 2/4 bytes using cmd53 */ +extern int +brcmf_sdioh_request_word(struct brcmf_sdio_dev *sdiodev, + uint rw, uint fnc, uint addr, + u32 *word, uint nbyte); + +/* read or write any buffer using cmd53 */ +extern int +brcmf_sdioh_request_buffer(struct brcmf_sdio_dev *sdiodev, + uint fix_inc, uint rw, uint fnc_num, u32 addr, + struct sk_buff *pkt); +extern int +brcmf_sdioh_request_chain(struct brcmf_sdio_dev *sdiodev, uint fix_inc, + uint write, uint func, uint addr, + struct sk_buff_head *pktq); + +/* Watchdog timer interface for pm ops */ +extern void brcmf_sdio_wdtmr_enable(struct brcmf_sdio_dev *sdiodev, + bool enable); + +extern void *brcmf_sdbrcm_probe(u32 regsva, struct brcmf_sdio_dev *sdiodev); +extern void brcmf_sdbrcm_disconnect(void *ptr); +extern void brcmf_sdbrcm_isr(void *arg); + +extern void brcmf_sdbrcm_wd_timer(struct brcmf_sdio *bus, uint wdtick); +#endif /* _BRCM_SDH_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/tracepoint.c b/drivers/net/wireless/brcm80211/brcmfmac/tracepoint.c new file mode 100644 index 0000000..b505db4 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/tracepoint.c @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2012 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include /* bug in tracepoint.h, it should include this */ + +#ifndef __CHECKER__ +#define CREATE_TRACE_POINTS +#include "tracepoint.h" +#endif diff --git a/drivers/net/wireless/brcm80211/brcmfmac/tracepoint.h b/drivers/net/wireless/brcm80211/brcmfmac/tracepoint.h new file mode 100644 index 0000000..4c8f25f --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/tracepoint.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2013 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#if !defined(BRCMF_TRACEPOINT_H_) || defined(TRACE_HEADER_MULTI_READ) +#define BRCMF_TRACEPOINT_H_ + +#include +#include + +#ifndef CPTCFG_BRCM_TRACING + +#undef TRACE_EVENT +#define TRACE_EVENT(name, proto, ...) \ +static inline void trace_ ## name(proto) {} + +#undef DECLARE_EVENT_CLASS +#define DECLARE_EVENT_CLASS(...) + +#undef DEFINE_EVENT +#define DEFINE_EVENT(evt_class, name, proto, ...) \ +static inline void trace_ ## name(proto) {} + +#endif /* CPTCFG_BRCM_TRACING */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM brcmfmac + +#define MAX_MSG_LEN 100 + +TRACE_EVENT(brcmf_err, + TP_PROTO(const char *func, struct va_format *vaf), + TP_ARGS(func, vaf), + TP_STRUCT__entry( + __string(func, func) + __dynamic_array(char, msg, MAX_MSG_LEN) + ), + TP_fast_assign( + __assign_str(func, func); + WARN_ON_ONCE(vsnprintf(__get_dynamic_array(msg), + MAX_MSG_LEN, vaf->fmt, + *vaf->va) >= MAX_MSG_LEN); + ), + TP_printk("%s: %s", __get_str(func), __get_str(msg)) +); + +TRACE_EVENT(brcmf_dbg, + TP_PROTO(u32 level, const char *func, struct va_format *vaf), + TP_ARGS(level, func, vaf), + TP_STRUCT__entry( + __field(u32, level) + __string(func, func) + __dynamic_array(char, msg, MAX_MSG_LEN) + ), + TP_fast_assign( + __entry->level = level; + __assign_str(func, func); + WARN_ON_ONCE(vsnprintf(__get_dynamic_array(msg), + MAX_MSG_LEN, vaf->fmt, + *vaf->va) >= MAX_MSG_LEN); + ), + TP_printk("%s: %s", __get_str(func), __get_str(msg)) +); + +TRACE_EVENT(brcmf_hexdump, + TP_PROTO(void *data, size_t len), + TP_ARGS(data, len), + TP_STRUCT__entry( + __field(unsigned long, len) + __dynamic_array(u8, hdata, len) + ), + TP_fast_assign( + __entry->len = len; + memcpy(__get_dynamic_array(hdata), data, len); + ), + TP_printk("hexdump [length=%lu]", __entry->len) +); + +#ifdef CPTCFG_BRCM_TRACING + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE tracepoint + +#include + +#endif /* CPTCFG_BRCM_TRACING */ + +#endif /* BRCMF_TRACEPOINT_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/usb.c b/drivers/net/wireless/brcm80211/brcmfmac/usb.c new file mode 100644 index 0000000..1dc0a6b --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/usb.c @@ -0,0 +1,1542 @@ +/* + * Copyright (c) 2011 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "usb_rdl.h" +#include "usb.h" + +#define IOCTL_RESP_TIMEOUT 2000 + +#define BRCMF_USB_RESET_GETVER_SPINWAIT 100 /* in unit of ms */ +#define BRCMF_USB_RESET_GETVER_LOOP_CNT 10 + +#define BRCMF_POSTBOOT_ID 0xA123 /* ID to detect if dongle + has boot up */ +#define BRCMF_USB_NRXQ 50 +#define BRCMF_USB_NTXQ 50 + +#define CONFIGDESC(usb) (&((usb)->actconfig)->desc) +#define IFPTR(usb, idx) ((usb)->actconfig->interface[(idx)]) +#define IFALTS(usb, idx) (IFPTR((usb), (idx))->altsetting[0]) +#define IFDESC(usb, idx) IFALTS((usb), (idx)).desc +#define IFEPDESC(usb, idx, ep) (IFALTS((usb), (idx)).endpoint[(ep)]).desc + +#define CONTROL_IF 0 +#define BULK_IF 0 + +#define BRCMF_USB_CBCTL_WRITE 0 +#define BRCMF_USB_CBCTL_READ 1 +#define BRCMF_USB_MAX_PKT_SIZE 1600 + +#define BRCMF_USB_43143_FW_NAME "brcm/brcmfmac43143.bin" +#define BRCMF_USB_43236_FW_NAME "brcm/brcmfmac43236b.bin" +#define BRCMF_USB_43242_FW_NAME "brcm/brcmfmac43242a.bin" + +struct brcmf_usb_image { + struct list_head list; + s8 *fwname; + u8 *image; + int image_len; +}; +static struct list_head fw_image_list; + +struct intr_transfer_buf { + u32 notification; + u32 reserved; +}; + +struct brcmf_usbdev_info { + struct brcmf_usbdev bus_pub; /* MUST BE FIRST */ + spinlock_t qlock; + struct list_head rx_freeq; + struct list_head rx_postq; + struct list_head tx_freeq; + struct list_head tx_postq; + uint rx_pipe, tx_pipe, intr_pipe, rx_pipe2; + + int rx_low_watermark; + int tx_low_watermark; + int tx_high_watermark; + int tx_freecount; + bool tx_flowblock; + + struct brcmf_usbreq *tx_reqs; + struct brcmf_usbreq *rx_reqs; + + u8 *image; /* buffer for combine fw and nvram */ + int image_len; + + struct usb_device *usbdev; + struct device *dev; + + int ctl_in_pipe, ctl_out_pipe; + struct urb *ctl_urb; /* URB for control endpoint */ + struct usb_ctrlrequest ctl_write; + struct usb_ctrlrequest ctl_read; + u32 ctl_urb_actual_length; + int ctl_urb_status; + int ctl_completed; + wait_queue_head_t ioctl_resp_wait; + ulong ctl_op; + + struct urb *bulk_urb; /* used for FW download */ + struct urb *intr_urb; /* URB for interrupt endpoint */ + int intr_size; /* Size of interrupt message */ + int interval; /* Interrupt polling interval */ + struct intr_transfer_buf intr; /* Data buffer for interrupt endpoint */ +}; + +static void brcmf_usb_rx_refill(struct brcmf_usbdev_info *devinfo, + struct brcmf_usbreq *req); + +static struct brcmf_usbdev *brcmf_usb_get_buspub(struct device *dev) +{ + struct brcmf_bus *bus_if = dev_get_drvdata(dev); + return bus_if->bus_priv.usb; +} + +static struct brcmf_usbdev_info *brcmf_usb_get_businfo(struct device *dev) +{ + return brcmf_usb_get_buspub(dev)->devinfo; +} + +static int brcmf_usb_ioctl_resp_wait(struct brcmf_usbdev_info *devinfo) +{ + return wait_event_timeout(devinfo->ioctl_resp_wait, + devinfo->ctl_completed, + msecs_to_jiffies(IOCTL_RESP_TIMEOUT)); +} + +static void brcmf_usb_ioctl_resp_wake(struct brcmf_usbdev_info *devinfo) +{ + if (waitqueue_active(&devinfo->ioctl_resp_wait)) + wake_up(&devinfo->ioctl_resp_wait); +} + +static void +brcmf_usb_ctl_complete(struct brcmf_usbdev_info *devinfo, int type, int status) +{ + brcmf_dbg(USB, "Enter, status=%d\n", status); + + if (unlikely(devinfo == NULL)) + return; + + if (type == BRCMF_USB_CBCTL_READ) { + if (status == 0) + devinfo->bus_pub.stats.rx_ctlpkts++; + else + devinfo->bus_pub.stats.rx_ctlerrs++; + } else if (type == BRCMF_USB_CBCTL_WRITE) { + if (status == 0) + devinfo->bus_pub.stats.tx_ctlpkts++; + else + devinfo->bus_pub.stats.tx_ctlerrs++; + } + + devinfo->ctl_urb_status = status; + devinfo->ctl_completed = true; + brcmf_usb_ioctl_resp_wake(devinfo); +} + +static void +brcmf_usb_ctlread_complete(struct urb *urb) +{ + struct brcmf_usbdev_info *devinfo = + (struct brcmf_usbdev_info *)urb->context; + + brcmf_dbg(USB, "Enter\n"); + devinfo->ctl_urb_actual_length = urb->actual_length; + brcmf_usb_ctl_complete(devinfo, BRCMF_USB_CBCTL_READ, + urb->status); +} + +static void +brcmf_usb_ctlwrite_complete(struct urb *urb) +{ + struct brcmf_usbdev_info *devinfo = + (struct brcmf_usbdev_info *)urb->context; + + brcmf_dbg(USB, "Enter\n"); + brcmf_usb_ctl_complete(devinfo, BRCMF_USB_CBCTL_WRITE, + urb->status); +} + +static int +brcmf_usb_send_ctl(struct brcmf_usbdev_info *devinfo, u8 *buf, int len) +{ + int ret; + u16 size; + + brcmf_dbg(USB, "Enter\n"); + if (devinfo == NULL || buf == NULL || + len == 0 || devinfo->ctl_urb == NULL) + return -EINVAL; + + size = len; + devinfo->ctl_write.wLength = cpu_to_le16p(&size); + devinfo->ctl_urb->transfer_buffer_length = size; + devinfo->ctl_urb_status = 0; + devinfo->ctl_urb_actual_length = 0; + + usb_fill_control_urb(devinfo->ctl_urb, + devinfo->usbdev, + devinfo->ctl_out_pipe, + (unsigned char *) &devinfo->ctl_write, + buf, size, + (usb_complete_t)brcmf_usb_ctlwrite_complete, + devinfo); + + ret = usb_submit_urb(devinfo->ctl_urb, GFP_ATOMIC); + if (ret < 0) + brcmf_err("usb_submit_urb failed %d\n", ret); + + return ret; +} + +static int +brcmf_usb_recv_ctl(struct brcmf_usbdev_info *devinfo, u8 *buf, int len) +{ + int ret; + u16 size; + + brcmf_dbg(USB, "Enter\n"); + if ((devinfo == NULL) || (buf == NULL) || (len == 0) + || (devinfo->ctl_urb == NULL)) + return -EINVAL; + + size = len; + devinfo->ctl_read.wLength = cpu_to_le16p(&size); + devinfo->ctl_urb->transfer_buffer_length = size; + + devinfo->ctl_read.bRequestType = USB_DIR_IN + | USB_TYPE_CLASS | USB_RECIP_INTERFACE; + devinfo->ctl_read.bRequest = 1; + + usb_fill_control_urb(devinfo->ctl_urb, + devinfo->usbdev, + devinfo->ctl_in_pipe, + (unsigned char *) &devinfo->ctl_read, + buf, size, + (usb_complete_t)brcmf_usb_ctlread_complete, + devinfo); + + ret = usb_submit_urb(devinfo->ctl_urb, GFP_ATOMIC); + if (ret < 0) + brcmf_err("usb_submit_urb failed %d\n", ret); + + return ret; +} + +static int brcmf_usb_tx_ctlpkt(struct device *dev, u8 *buf, u32 len) +{ + int err = 0; + int timeout = 0; + struct brcmf_usbdev_info *devinfo = brcmf_usb_get_businfo(dev); + + brcmf_dbg(USB, "Enter\n"); + if (devinfo->bus_pub.state != BRCMFMAC_USB_STATE_UP) + return -EIO; + + if (test_and_set_bit(0, &devinfo->ctl_op)) + return -EIO; + + devinfo->ctl_completed = false; + err = brcmf_usb_send_ctl(devinfo, buf, len); + if (err) { + brcmf_err("fail %d bytes: %d\n", err, len); + clear_bit(0, &devinfo->ctl_op); + return err; + } + timeout = brcmf_usb_ioctl_resp_wait(devinfo); + clear_bit(0, &devinfo->ctl_op); + if (!timeout) { + brcmf_err("Txctl wait timed out\n"); + err = -EIO; + } + return err; +} + +static int brcmf_usb_rx_ctlpkt(struct device *dev, u8 *buf, u32 len) +{ + int err = 0; + int timeout = 0; + struct brcmf_usbdev_info *devinfo = brcmf_usb_get_businfo(dev); + + brcmf_dbg(USB, "Enter\n"); + if (devinfo->bus_pub.state != BRCMFMAC_USB_STATE_UP) + return -EIO; + + if (test_and_set_bit(0, &devinfo->ctl_op)) + return -EIO; + + devinfo->ctl_completed = false; + err = brcmf_usb_recv_ctl(devinfo, buf, len); + if (err) { + brcmf_err("fail %d bytes: %d\n", err, len); + clear_bit(0, &devinfo->ctl_op); + return err; + } + timeout = brcmf_usb_ioctl_resp_wait(devinfo); + err = devinfo->ctl_urb_status; + clear_bit(0, &devinfo->ctl_op); + if (!timeout) { + brcmf_err("rxctl wait timed out\n"); + err = -EIO; + } + if (!err) + return devinfo->ctl_urb_actual_length; + else + return err; +} + +static struct brcmf_usbreq *brcmf_usb_deq(struct brcmf_usbdev_info *devinfo, + struct list_head *q, int *counter) +{ + unsigned long flags; + struct brcmf_usbreq *req; + spin_lock_irqsave(&devinfo->qlock, flags); + if (list_empty(q)) { + spin_unlock_irqrestore(&devinfo->qlock, flags); + return NULL; + } + req = list_entry(q->next, struct brcmf_usbreq, list); + list_del_init(q->next); + if (counter) + (*counter)--; + spin_unlock_irqrestore(&devinfo->qlock, flags); + return req; + +} + +static void brcmf_usb_enq(struct brcmf_usbdev_info *devinfo, + struct list_head *q, struct brcmf_usbreq *req, + int *counter) +{ + unsigned long flags; + spin_lock_irqsave(&devinfo->qlock, flags); + list_add_tail(&req->list, q); + if (counter) + (*counter)++; + spin_unlock_irqrestore(&devinfo->qlock, flags); +} + +static struct brcmf_usbreq * +brcmf_usbdev_qinit(struct list_head *q, int qsize) +{ + int i; + struct brcmf_usbreq *req, *reqs; + + reqs = kcalloc(qsize, sizeof(struct brcmf_usbreq), GFP_ATOMIC); + if (reqs == NULL) + return NULL; + + req = reqs; + + for (i = 0; i < qsize; i++) { + req->urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!req->urb) + goto fail; + + INIT_LIST_HEAD(&req->list); + list_add_tail(&req->list, q); + req++; + } + return reqs; +fail: + brcmf_err("fail!\n"); + while (!list_empty(q)) { + req = list_entry(q->next, struct brcmf_usbreq, list); + if (req && req->urb) + usb_free_urb(req->urb); + list_del(q->next); + } + return NULL; + +} + +static void brcmf_usb_free_q(struct list_head *q, bool pending) +{ + struct brcmf_usbreq *req, *next; + int i = 0; + list_for_each_entry_safe(req, next, q, list) { + if (!req->urb) { + brcmf_err("bad req\n"); + break; + } + i++; + if (pending) { + usb_kill_urb(req->urb); + } else { + usb_free_urb(req->urb); + list_del_init(&req->list); + } + } +} + +static void brcmf_usb_del_fromq(struct brcmf_usbdev_info *devinfo, + struct brcmf_usbreq *req) +{ + unsigned long flags; + + spin_lock_irqsave(&devinfo->qlock, flags); + list_del_init(&req->list); + spin_unlock_irqrestore(&devinfo->qlock, flags); +} + + +static void brcmf_usb_tx_complete(struct urb *urb) +{ + struct brcmf_usbreq *req = (struct brcmf_usbreq *)urb->context; + struct brcmf_usbdev_info *devinfo = req->devinfo; + + brcmf_dbg(USB, "Enter, urb->status=%d, skb=%p\n", urb->status, + req->skb); + brcmf_usb_del_fromq(devinfo, req); + + brcmf_txcomplete(devinfo->dev, req->skb, urb->status == 0); + req->skb = NULL; + brcmf_usb_enq(devinfo, &devinfo->tx_freeq, req, &devinfo->tx_freecount); + if (devinfo->tx_freecount > devinfo->tx_high_watermark && + devinfo->tx_flowblock) { + brcmf_txflowblock(devinfo->dev, false); + devinfo->tx_flowblock = false; + } +} + +static void brcmf_usb_rx_complete(struct urb *urb) +{ + struct brcmf_usbreq *req = (struct brcmf_usbreq *)urb->context; + struct brcmf_usbdev_info *devinfo = req->devinfo; + struct sk_buff *skb; + struct sk_buff_head skbq; + + brcmf_dbg(USB, "Enter, urb->status=%d\n", urb->status); + brcmf_usb_del_fromq(devinfo, req); + skb = req->skb; + req->skb = NULL; + + /* zero lenght packets indicate usb "failure". Do not refill */ + if (urb->status != 0 || !urb->actual_length) { + brcmu_pkt_buf_free_skb(skb); + brcmf_usb_enq(devinfo, &devinfo->rx_freeq, req, NULL); + return; + } + + if (devinfo->bus_pub.state == BRCMFMAC_USB_STATE_UP) { + skb_queue_head_init(&skbq); + skb_queue_tail(&skbq, skb); + skb_put(skb, urb->actual_length); + brcmf_rx_frames(devinfo->dev, &skbq); + brcmf_usb_rx_refill(devinfo, req); + } else { + brcmu_pkt_buf_free_skb(skb); + brcmf_usb_enq(devinfo, &devinfo->rx_freeq, req, NULL); + } + return; + +} + +static void brcmf_usb_rx_refill(struct brcmf_usbdev_info *devinfo, + struct brcmf_usbreq *req) +{ + struct sk_buff *skb; + int ret; + + if (!req || !devinfo) + return; + + skb = dev_alloc_skb(devinfo->bus_pub.bus_mtu); + if (!skb) { + brcmf_usb_enq(devinfo, &devinfo->rx_freeq, req, NULL); + return; + } + req->skb = skb; + + usb_fill_bulk_urb(req->urb, devinfo->usbdev, devinfo->rx_pipe, + skb->data, skb_tailroom(skb), brcmf_usb_rx_complete, + req); + req->devinfo = devinfo; + brcmf_usb_enq(devinfo, &devinfo->rx_postq, req, NULL); + + ret = usb_submit_urb(req->urb, GFP_ATOMIC); + if (ret) { + brcmf_usb_del_fromq(devinfo, req); + brcmu_pkt_buf_free_skb(req->skb); + req->skb = NULL; + brcmf_usb_enq(devinfo, &devinfo->rx_freeq, req, NULL); + } + return; +} + +static void brcmf_usb_rx_fill_all(struct brcmf_usbdev_info *devinfo) +{ + struct brcmf_usbreq *req; + + if (devinfo->bus_pub.state != BRCMFMAC_USB_STATE_UP) { + brcmf_err("bus is not up=%d\n", devinfo->bus_pub.state); + return; + } + while ((req = brcmf_usb_deq(devinfo, &devinfo->rx_freeq, NULL)) != NULL) + brcmf_usb_rx_refill(devinfo, req); +} + +static void +brcmf_usb_state_change(struct brcmf_usbdev_info *devinfo, int state) +{ + struct brcmf_bus *bcmf_bus = devinfo->bus_pub.bus; + int old_state; + + brcmf_dbg(USB, "Enter, current state=%d, new state=%d\n", + devinfo->bus_pub.state, state); + + if (devinfo->bus_pub.state == state) + return; + + old_state = devinfo->bus_pub.state; + devinfo->bus_pub.state = state; + + /* update state of upper layer */ + if (state == BRCMFMAC_USB_STATE_DOWN) { + brcmf_dbg(USB, "DBUS is down\n"); + bcmf_bus->state = BRCMF_BUS_DOWN; + } else if (state == BRCMFMAC_USB_STATE_UP) { + brcmf_dbg(USB, "DBUS is up\n"); + bcmf_bus->state = BRCMF_BUS_DATA; + } else { + brcmf_dbg(USB, "DBUS current state=%d\n", state); + } +} + +static void +brcmf_usb_intr_complete(struct urb *urb) +{ + struct brcmf_usbdev_info *devinfo = + (struct brcmf_usbdev_info *)urb->context; + int err; + + brcmf_dbg(USB, "Enter, urb->status=%d\n", urb->status); + + if (devinfo == NULL) + return; + + if (unlikely(urb->status)) { + if (urb->status == -ENOENT || + urb->status == -ESHUTDOWN || + urb->status == -ENODEV) { + brcmf_usb_state_change(devinfo, + BRCMFMAC_USB_STATE_DOWN); + } + } + + if (devinfo->bus_pub.state == BRCMFMAC_USB_STATE_DOWN) { + brcmf_err("intr cb when DBUS down, ignoring\n"); + return; + } + + if (devinfo->bus_pub.state == BRCMFMAC_USB_STATE_UP) { + err = usb_submit_urb(devinfo->intr_urb, GFP_ATOMIC); + if (err) + brcmf_err("usb_submit_urb, err=%d\n", err); + } +} + +static int brcmf_usb_tx(struct device *dev, struct sk_buff *skb) +{ + struct brcmf_usbdev_info *devinfo = brcmf_usb_get_businfo(dev); + struct brcmf_usbreq *req; + int ret; + + brcmf_dbg(USB, "Enter, skb=%p\n", skb); + if (devinfo->bus_pub.state != BRCMFMAC_USB_STATE_UP) { + ret = -EIO; + goto fail; + } + + req = brcmf_usb_deq(devinfo, &devinfo->tx_freeq, + &devinfo->tx_freecount); + if (!req) { + brcmf_err("no req to send\n"); + ret = -ENOMEM; + goto fail; + } + + req->skb = skb; + req->devinfo = devinfo; + usb_fill_bulk_urb(req->urb, devinfo->usbdev, devinfo->tx_pipe, + skb->data, skb->len, brcmf_usb_tx_complete, req); + req->urb->transfer_flags |= URB_ZERO_PACKET; + brcmf_usb_enq(devinfo, &devinfo->tx_postq, req, NULL); + ret = usb_submit_urb(req->urb, GFP_ATOMIC); + if (ret) { + brcmf_err("brcmf_usb_tx usb_submit_urb FAILED\n"); + brcmf_usb_del_fromq(devinfo, req); + req->skb = NULL; + brcmf_usb_enq(devinfo, &devinfo->tx_freeq, req, + &devinfo->tx_freecount); + goto fail; + } + + if (devinfo->tx_freecount < devinfo->tx_low_watermark && + !devinfo->tx_flowblock) { + brcmf_txflowblock(dev, true); + devinfo->tx_flowblock = true; + } + return 0; + +fail: + brcmf_txcomplete(dev, skb, false); + return ret; +} + + +static int brcmf_usb_up(struct device *dev) +{ + struct brcmf_usbdev_info *devinfo = brcmf_usb_get_businfo(dev); + u16 ifnum; + int ret; + + brcmf_dbg(USB, "Enter\n"); + if (devinfo->bus_pub.state == BRCMFMAC_USB_STATE_UP) + return 0; + + /* Success, indicate devinfo is fully up */ + brcmf_usb_state_change(devinfo, BRCMFMAC_USB_STATE_UP); + + if (devinfo->intr_urb) { + usb_fill_int_urb(devinfo->intr_urb, devinfo->usbdev, + devinfo->intr_pipe, + &devinfo->intr, + devinfo->intr_size, + (usb_complete_t)brcmf_usb_intr_complete, + devinfo, + devinfo->interval); + + ret = usb_submit_urb(devinfo->intr_urb, GFP_ATOMIC); + if (ret) { + brcmf_err("USB_SUBMIT_URB failed with status %d\n", + ret); + return -EINVAL; + } + } + + if (devinfo->ctl_urb) { + devinfo->ctl_in_pipe = usb_rcvctrlpipe(devinfo->usbdev, 0); + devinfo->ctl_out_pipe = usb_sndctrlpipe(devinfo->usbdev, 0); + + ifnum = IFDESC(devinfo->usbdev, CONTROL_IF).bInterfaceNumber; + + /* CTL Write */ + devinfo->ctl_write.bRequestType = + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE; + devinfo->ctl_write.bRequest = 0; + devinfo->ctl_write.wValue = cpu_to_le16(0); + devinfo->ctl_write.wIndex = cpu_to_le16p(&ifnum); + + /* CTL Read */ + devinfo->ctl_read.bRequestType = + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE; + devinfo->ctl_read.bRequest = 1; + devinfo->ctl_read.wValue = cpu_to_le16(0); + devinfo->ctl_read.wIndex = cpu_to_le16p(&ifnum); + } + brcmf_usb_rx_fill_all(devinfo); + return 0; +} + +static void brcmf_usb_down(struct device *dev) +{ + struct brcmf_usbdev_info *devinfo = brcmf_usb_get_businfo(dev); + + brcmf_dbg(USB, "Enter\n"); + if (devinfo == NULL) + return; + + if (devinfo->bus_pub.state == BRCMFMAC_USB_STATE_DOWN) + return; + + brcmf_usb_state_change(devinfo, BRCMFMAC_USB_STATE_DOWN); + if (devinfo->intr_urb) + usb_kill_urb(devinfo->intr_urb); + + if (devinfo->ctl_urb) + usb_kill_urb(devinfo->ctl_urb); + + if (devinfo->bulk_urb) + usb_kill_urb(devinfo->bulk_urb); + brcmf_usb_free_q(&devinfo->tx_postq, true); + + brcmf_usb_free_q(&devinfo->rx_postq, true); +} + +static void +brcmf_usb_sync_complete(struct urb *urb) +{ + struct brcmf_usbdev_info *devinfo = + (struct brcmf_usbdev_info *)urb->context; + + devinfo->ctl_completed = true; + brcmf_usb_ioctl_resp_wake(devinfo); +} + +static bool brcmf_usb_dl_cmd(struct brcmf_usbdev_info *devinfo, u8 cmd, + void *buffer, int buflen) +{ + int ret = 0; + char *tmpbuf; + u16 size; + + if ((!devinfo) || (devinfo->ctl_urb == NULL)) + return false; + + tmpbuf = kmalloc(buflen, GFP_ATOMIC); + if (!tmpbuf) + return false; + + size = buflen; + devinfo->ctl_urb->transfer_buffer_length = size; + + devinfo->ctl_read.wLength = cpu_to_le16p(&size); + devinfo->ctl_read.bRequestType = USB_DIR_IN | USB_TYPE_VENDOR | + USB_RECIP_INTERFACE; + devinfo->ctl_read.bRequest = cmd; + + usb_fill_control_urb(devinfo->ctl_urb, + devinfo->usbdev, + usb_rcvctrlpipe(devinfo->usbdev, 0), + (unsigned char *) &devinfo->ctl_read, + (void *) tmpbuf, size, + (usb_complete_t)brcmf_usb_sync_complete, devinfo); + + devinfo->ctl_completed = false; + ret = usb_submit_urb(devinfo->ctl_urb, GFP_ATOMIC); + if (ret < 0) { + brcmf_err("usb_submit_urb failed %d\n", ret); + kfree(tmpbuf); + return false; + } + + ret = brcmf_usb_ioctl_resp_wait(devinfo); + memcpy(buffer, tmpbuf, buflen); + kfree(tmpbuf); + + return ret; +} + +static bool +brcmf_usb_dlneeded(struct brcmf_usbdev_info *devinfo) +{ + struct bootrom_id_le id; + u32 chipid, chiprev; + + brcmf_dbg(USB, "Enter\n"); + + if (devinfo == NULL) + return false; + + /* Check if firmware downloaded already by querying runtime ID */ + id.chip = cpu_to_le32(0xDEAD); + brcmf_usb_dl_cmd(devinfo, DL_GETVER, &id, sizeof(id)); + + chipid = le32_to_cpu(id.chip); + chiprev = le32_to_cpu(id.chiprev); + + if ((chipid & 0x4300) == 0x4300) + brcmf_dbg(USB, "chip %x rev 0x%x\n", chipid, chiprev); + else + brcmf_dbg(USB, "chip %d rev 0x%x\n", chipid, chiprev); + if (chipid == BRCMF_POSTBOOT_ID) { + brcmf_dbg(USB, "firmware already downloaded\n"); + brcmf_usb_dl_cmd(devinfo, DL_RESETCFG, &id, sizeof(id)); + return false; + } else { + devinfo->bus_pub.devid = chipid; + devinfo->bus_pub.chiprev = chiprev; + } + return true; +} + +static int +brcmf_usb_resetcfg(struct brcmf_usbdev_info *devinfo) +{ + struct bootrom_id_le id; + u32 loop_cnt; + + brcmf_dbg(USB, "Enter\n"); + + loop_cnt = 0; + do { + mdelay(BRCMF_USB_RESET_GETVER_SPINWAIT); + loop_cnt++; + id.chip = cpu_to_le32(0xDEAD); /* Get the ID */ + brcmf_usb_dl_cmd(devinfo, DL_GETVER, &id, sizeof(id)); + if (id.chip == cpu_to_le32(BRCMF_POSTBOOT_ID)) + break; + } while (loop_cnt < BRCMF_USB_RESET_GETVER_LOOP_CNT); + + if (id.chip == cpu_to_le32(BRCMF_POSTBOOT_ID)) { + brcmf_dbg(USB, "postboot chip 0x%x/rev 0x%x\n", + le32_to_cpu(id.chip), le32_to_cpu(id.chiprev)); + + brcmf_usb_dl_cmd(devinfo, DL_RESETCFG, &id, sizeof(id)); + return 0; + } else { + brcmf_err("Cannot talk to Dongle. Firmware is not UP, %d ms\n", + BRCMF_USB_RESET_GETVER_SPINWAIT * loop_cnt); + return -EINVAL; + } +} + + +static int +brcmf_usb_dl_send_bulk(struct brcmf_usbdev_info *devinfo, void *buffer, int len) +{ + int ret; + + if ((devinfo == NULL) || (devinfo->bulk_urb == NULL)) + return -EINVAL; + + /* Prepare the URB */ + usb_fill_bulk_urb(devinfo->bulk_urb, devinfo->usbdev, + devinfo->tx_pipe, buffer, len, + (usb_complete_t)brcmf_usb_sync_complete, devinfo); + + devinfo->bulk_urb->transfer_flags |= URB_ZERO_PACKET; + + devinfo->ctl_completed = false; + ret = usb_submit_urb(devinfo->bulk_urb, GFP_ATOMIC); + if (ret) { + brcmf_err("usb_submit_urb failed %d\n", ret); + return ret; + } + ret = brcmf_usb_ioctl_resp_wait(devinfo); + return (ret == 0); +} + +static int +brcmf_usb_dl_writeimage(struct brcmf_usbdev_info *devinfo, u8 *fw, int fwlen) +{ + unsigned int sendlen, sent, dllen; + char *bulkchunk = NULL, *dlpos; + struct rdl_state_le state; + u32 rdlstate, rdlbytes; + int err = 0; + + brcmf_dbg(USB, "Enter, fw %p, len %d\n", fw, fwlen); + + bulkchunk = kmalloc(RDL_CHUNK, GFP_ATOMIC); + if (bulkchunk == NULL) { + err = -ENOMEM; + goto fail; + } + + /* 1) Prepare USB boot loader for runtime image */ + brcmf_usb_dl_cmd(devinfo, DL_START, &state, + sizeof(struct rdl_state_le)); + + rdlstate = le32_to_cpu(state.state); + rdlbytes = le32_to_cpu(state.bytes); + + /* 2) Check we are in the Waiting state */ + if (rdlstate != DL_WAITING) { + brcmf_err("Failed to DL_START\n"); + err = -EINVAL; + goto fail; + } + sent = 0; + dlpos = fw; + dllen = fwlen; + + /* Get chip id and rev */ + while (rdlbytes != dllen) { + /* Wait until the usb device reports it received all + * the bytes we sent */ + if ((rdlbytes == sent) && (rdlbytes != dllen)) { + if ((dllen-sent) < RDL_CHUNK) + sendlen = dllen-sent; + else + sendlen = RDL_CHUNK; + + /* simply avoid having to send a ZLP by ensuring we + * never have an even + * multiple of 64 + */ + if (!(sendlen % 64)) + sendlen -= 4; + + /* send data */ + memcpy(bulkchunk, dlpos, sendlen); + if (brcmf_usb_dl_send_bulk(devinfo, bulkchunk, + sendlen)) { + brcmf_err("send_bulk failed\n"); + err = -EINVAL; + goto fail; + } + + dlpos += sendlen; + sent += sendlen; + } + if (!brcmf_usb_dl_cmd(devinfo, DL_GETSTATE, &state, + sizeof(struct rdl_state_le))) { + brcmf_err("DL_GETSTATE Failed xxxx\n"); + err = -EINVAL; + goto fail; + } + + rdlstate = le32_to_cpu(state.state); + rdlbytes = le32_to_cpu(state.bytes); + + /* restart if an error is reported */ + if (rdlstate == DL_BAD_HDR || rdlstate == DL_BAD_CRC) { + brcmf_err("Bad Hdr or Bad CRC state %d\n", + rdlstate); + err = -EINVAL; + goto fail; + } + } + +fail: + kfree(bulkchunk); + brcmf_dbg(USB, "Exit, err=%d\n", err); + return err; +} + +static int brcmf_usb_dlstart(struct brcmf_usbdev_info *devinfo, u8 *fw, int len) +{ + int err; + + brcmf_dbg(USB, "Enter\n"); + + if (devinfo == NULL) + return -EINVAL; + + if (devinfo->bus_pub.devid == 0xDEAD) + return -EINVAL; + + err = brcmf_usb_dl_writeimage(devinfo, fw, len); + if (err == 0) + devinfo->bus_pub.state = BRCMFMAC_USB_STATE_DL_DONE; + else + devinfo->bus_pub.state = BRCMFMAC_USB_STATE_DL_FAIL; + brcmf_dbg(USB, "Exit, err=%d\n", err); + + return err; +} + +static int brcmf_usb_dlrun(struct brcmf_usbdev_info *devinfo) +{ + struct rdl_state_le state; + + brcmf_dbg(USB, "Enter\n"); + if (!devinfo) + return -EINVAL; + + if (devinfo->bus_pub.devid == 0xDEAD) + return -EINVAL; + + /* Check we are runnable */ + brcmf_usb_dl_cmd(devinfo, DL_GETSTATE, &state, + sizeof(struct rdl_state_le)); + + /* Start the image */ + if (state.state == cpu_to_le32(DL_RUNNABLE)) { + if (!brcmf_usb_dl_cmd(devinfo, DL_GO, &state, + sizeof(struct rdl_state_le))) + return -ENODEV; + if (brcmf_usb_resetcfg(devinfo)) + return -ENODEV; + /* The Dongle may go for re-enumeration. */ + } else { + brcmf_err("Dongle not runnable\n"); + return -EINVAL; + } + brcmf_dbg(USB, "Exit\n"); + return 0; +} + +static bool brcmf_usb_chip_support(int chipid, int chiprev) +{ + switch(chipid) { + case 43143: + return true; + case 43235: + case 43236: + case 43238: + return (chiprev == 3); + case 43242: + return true; + default: + break; + } + return false; +} + +static int +brcmf_usb_fw_download(struct brcmf_usbdev_info *devinfo) +{ + int devid, chiprev; + int err; + + brcmf_dbg(USB, "Enter\n"); + if (devinfo == NULL) + return -ENODEV; + + devid = devinfo->bus_pub.devid; + chiprev = devinfo->bus_pub.chiprev; + + if (!brcmf_usb_chip_support(devid, chiprev)) { + brcmf_err("unsupported chip %d rev %d\n", + devid, chiprev); + return -EINVAL; + } + + if (!devinfo->image) { + brcmf_err("No firmware!\n"); + return -ENOENT; + } + + err = brcmf_usb_dlstart(devinfo, + devinfo->image, devinfo->image_len); + if (err == 0) + err = brcmf_usb_dlrun(devinfo); + return err; +} + + +static void brcmf_usb_detach(struct brcmf_usbdev_info *devinfo) +{ + brcmf_dbg(USB, "Enter, devinfo %p\n", devinfo); + + /* free the URBS */ + brcmf_usb_free_q(&devinfo->rx_freeq, false); + brcmf_usb_free_q(&devinfo->tx_freeq, false); + + usb_free_urb(devinfo->intr_urb); + usb_free_urb(devinfo->ctl_urb); + usb_free_urb(devinfo->bulk_urb); + + kfree(devinfo->tx_reqs); + kfree(devinfo->rx_reqs); +} + +#define TRX_MAGIC 0x30524448 /* "HDR0" */ +#define TRX_VERSION 1 /* Version 1 */ +#define TRX_MAX_LEN 0x3B0000 /* Max length */ +#define TRX_NO_HEADER 1 /* Do not write TRX header */ +#define TRX_MAX_OFFSET 3 /* Max number of individual files */ +#define TRX_UNCOMP_IMAGE 0x20 /* Trx contains uncompressed image */ + +struct trx_header_le { + __le32 magic; /* "HDR0" */ + __le32 len; /* Length of file including header */ + __le32 crc32; /* CRC from flag_version to end of file */ + __le32 flag_version; /* 0:15 flags, 16:31 version */ + __le32 offsets[TRX_MAX_OFFSET]; /* Offsets of partitions from start of + * header */ +}; + +static int check_file(const u8 *headers) +{ + struct trx_header_le *trx; + int actual_len = -1; + + brcmf_dbg(USB, "Enter\n"); + /* Extract trx header */ + trx = (struct trx_header_le *) headers; + if (trx->magic != cpu_to_le32(TRX_MAGIC)) + return -1; + + headers += sizeof(struct trx_header_le); + + if (le32_to_cpu(trx->flag_version) & TRX_UNCOMP_IMAGE) { + actual_len = le32_to_cpu(trx->offsets[TRX_OFFSETS_DLFWLEN_IDX]); + return actual_len + sizeof(struct trx_header_le); + } + return -1; +} + +static int brcmf_usb_get_fw(struct brcmf_usbdev_info *devinfo) +{ + s8 *fwname; + const struct firmware *fw; + struct brcmf_usb_image *fw_image; + int err; + + brcmf_dbg(USB, "Enter\n"); + switch (devinfo->bus_pub.devid) { + case 43143: + fwname = BRCMF_USB_43143_FW_NAME; + break; + case 43235: + case 43236: + case 43238: + fwname = BRCMF_USB_43236_FW_NAME; + break; + case 43242: + fwname = BRCMF_USB_43242_FW_NAME; + break; + default: + return -EINVAL; + break; + } + brcmf_dbg(USB, "Loading FW %s\n", fwname); + list_for_each_entry(fw_image, &fw_image_list, list) { + if (fw_image->fwname == fwname) { + devinfo->image = fw_image->image; + devinfo->image_len = fw_image->image_len; + return 0; + } + } + /* fw image not yet loaded. Load it now and add to list */ + err = request_firmware(&fw, fwname, devinfo->dev); + if (!fw) { + brcmf_err("fail to request firmware %s\n", fwname); + return err; + } + if (check_file(fw->data) < 0) { + brcmf_err("invalid firmware %s\n", fwname); + return -EINVAL; + } + + fw_image = kzalloc(sizeof(*fw_image), GFP_ATOMIC); + if (!fw_image) + return -ENOMEM; + INIT_LIST_HEAD(&fw_image->list); + list_add_tail(&fw_image->list, &fw_image_list); + fw_image->fwname = fwname; + fw_image->image = vmalloc(fw->size); + if (!fw_image->image) + return -ENOMEM; + + memcpy(fw_image->image, fw->data, fw->size); + fw_image->image_len = fw->size; + + release_firmware(fw); + + devinfo->image = fw_image->image; + devinfo->image_len = fw_image->image_len; + + return 0; +} + + +static +struct brcmf_usbdev *brcmf_usb_attach(struct brcmf_usbdev_info *devinfo, + int nrxq, int ntxq) +{ + brcmf_dbg(USB, "Enter\n"); + + devinfo->bus_pub.nrxq = nrxq; + devinfo->rx_low_watermark = nrxq / 2; + devinfo->bus_pub.devinfo = devinfo; + devinfo->bus_pub.ntxq = ntxq; + devinfo->bus_pub.state = BRCMFMAC_USB_STATE_DOWN; + + /* flow control when too many tx urbs posted */ + devinfo->tx_low_watermark = ntxq / 4; + devinfo->tx_high_watermark = devinfo->tx_low_watermark * 3; + devinfo->bus_pub.bus_mtu = BRCMF_USB_MAX_PKT_SIZE; + + /* Initialize other structure content */ + init_waitqueue_head(&devinfo->ioctl_resp_wait); + + /* Initialize the spinlocks */ + spin_lock_init(&devinfo->qlock); + + INIT_LIST_HEAD(&devinfo->rx_freeq); + INIT_LIST_HEAD(&devinfo->rx_postq); + + INIT_LIST_HEAD(&devinfo->tx_freeq); + INIT_LIST_HEAD(&devinfo->tx_postq); + + devinfo->tx_flowblock = false; + + devinfo->rx_reqs = brcmf_usbdev_qinit(&devinfo->rx_freeq, nrxq); + if (!devinfo->rx_reqs) + goto error; + + devinfo->tx_reqs = brcmf_usbdev_qinit(&devinfo->tx_freeq, ntxq); + if (!devinfo->tx_reqs) + goto error; + devinfo->tx_freecount = ntxq; + + devinfo->intr_urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!devinfo->intr_urb) { + brcmf_err("usb_alloc_urb (intr) failed\n"); + goto error; + } + devinfo->ctl_urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!devinfo->ctl_urb) { + brcmf_err("usb_alloc_urb (ctl) failed\n"); + goto error; + } + devinfo->bulk_urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!devinfo->bulk_urb) { + brcmf_err("usb_alloc_urb (bulk) failed\n"); + goto error; + } + + if (!brcmf_usb_dlneeded(devinfo)) + return &devinfo->bus_pub; + + brcmf_dbg(USB, "Start fw downloading\n"); + if (brcmf_usb_get_fw(devinfo)) + goto error; + + if (brcmf_usb_fw_download(devinfo)) + goto error; + + return &devinfo->bus_pub; + +error: + brcmf_err("failed!\n"); + brcmf_usb_detach(devinfo); + return NULL; +} + +static struct brcmf_bus_ops brcmf_usb_bus_ops = { + .txdata = brcmf_usb_tx, + .init = brcmf_usb_up, + .stop = brcmf_usb_down, + .txctl = brcmf_usb_tx_ctlpkt, + .rxctl = brcmf_usb_rx_ctlpkt, +}; + +static int brcmf_usb_probe_cb(struct brcmf_usbdev_info *devinfo) +{ + struct brcmf_bus *bus = NULL; + struct brcmf_usbdev *bus_pub = NULL; + int ret; + struct device *dev = devinfo->dev; + + brcmf_dbg(USB, "Enter\n"); + bus_pub = brcmf_usb_attach(devinfo, BRCMF_USB_NRXQ, BRCMF_USB_NTXQ); + if (!bus_pub) + return -ENODEV; + + bus = kzalloc(sizeof(struct brcmf_bus), GFP_ATOMIC); + if (!bus) { + ret = -ENOMEM; + goto fail; + } + + bus->dev = dev; + bus_pub->bus = bus; + bus->bus_priv.usb = bus_pub; + dev_set_drvdata(dev, bus); + bus->ops = &brcmf_usb_bus_ops; + bus->chip = bus_pub->devid; + bus->chiprev = bus_pub->chiprev; + + /* Attach to the common driver interface */ + ret = brcmf_attach(0, dev); + if (ret) { + brcmf_err("brcmf_attach failed\n"); + goto fail; + } + + ret = brcmf_bus_start(dev); + if (ret) { + brcmf_err("dongle is not responding\n"); + brcmf_detach(dev); + goto fail; + } + + return 0; +fail: + /* Release resources in reverse order */ + kfree(bus); + brcmf_usb_detach(devinfo); + return ret; +} + +static void +brcmf_usb_disconnect_cb(struct brcmf_usbdev_info *devinfo) +{ + if (!devinfo) + return; + brcmf_dbg(USB, "Enter, bus_pub %p\n", devinfo); + + brcmf_detach(devinfo->dev); + kfree(devinfo->bus_pub.bus); + brcmf_usb_detach(devinfo); +} + +static int +brcmf_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + int ep; + struct usb_endpoint_descriptor *endpoint; + int ret = 0; + struct usb_device *usb = interface_to_usbdev(intf); + int num_of_eps; + u8 endpoint_num; + struct brcmf_usbdev_info *devinfo; + + brcmf_dbg(USB, "Enter\n"); + + devinfo = kzalloc(sizeof(*devinfo), GFP_ATOMIC); + if (devinfo == NULL) + return -ENOMEM; + + devinfo->usbdev = usb; + devinfo->dev = &usb->dev; + + usb_set_intfdata(intf, devinfo); + + /* Check that the device supports only one configuration */ + if (usb->descriptor.bNumConfigurations != 1) { + ret = -1; + goto fail; + } + + if (usb->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC) { + ret = -1; + goto fail; + } + + /* + * Only the BDC interface configuration is supported: + * Device class: USB_CLASS_VENDOR_SPEC + * if0 class: USB_CLASS_VENDOR_SPEC + * if0/ep0: control + * if0/ep1: bulk in + * if0/ep2: bulk out (ok if swapped with bulk in) + */ + if (CONFIGDESC(usb)->bNumInterfaces != 1) { + ret = -1; + goto fail; + } + + /* Check interface */ + if (IFDESC(usb, CONTROL_IF).bInterfaceClass != USB_CLASS_VENDOR_SPEC || + IFDESC(usb, CONTROL_IF).bInterfaceSubClass != 2 || + IFDESC(usb, CONTROL_IF).bInterfaceProtocol != 0xff) { + brcmf_err("invalid control interface: class %d, subclass %d, proto %d\n", + IFDESC(usb, CONTROL_IF).bInterfaceClass, + IFDESC(usb, CONTROL_IF).bInterfaceSubClass, + IFDESC(usb, CONTROL_IF).bInterfaceProtocol); + ret = -1; + goto fail; + } + + /* Check control endpoint */ + endpoint = &IFEPDESC(usb, CONTROL_IF, 0); + if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + != USB_ENDPOINT_XFER_INT) { + brcmf_err("invalid control endpoint %d\n", + endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK); + ret = -1; + goto fail; + } + + endpoint_num = endpoint->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + devinfo->intr_pipe = usb_rcvintpipe(usb, endpoint_num); + + devinfo->rx_pipe = 0; + devinfo->rx_pipe2 = 0; + devinfo->tx_pipe = 0; + num_of_eps = IFDESC(usb, BULK_IF).bNumEndpoints - 1; + + /* Check data endpoints and get pipes */ + for (ep = 1; ep <= num_of_eps; ep++) { + endpoint = &IFEPDESC(usb, BULK_IF, ep); + if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != + USB_ENDPOINT_XFER_BULK) { + brcmf_err("invalid data endpoint %d\n", ep); + ret = -1; + goto fail; + } + + endpoint_num = endpoint->bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK; + if ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) + == USB_DIR_IN) { + if (!devinfo->rx_pipe) { + devinfo->rx_pipe = + usb_rcvbulkpipe(usb, endpoint_num); + } else { + devinfo->rx_pipe2 = + usb_rcvbulkpipe(usb, endpoint_num); + } + } else { + devinfo->tx_pipe = usb_sndbulkpipe(usb, endpoint_num); + } + } + + /* Allocate interrupt URB and data buffer */ + /* RNDIS says 8-byte intr, our old drivers used 4-byte */ + if (IFEPDESC(usb, CONTROL_IF, 0).wMaxPacketSize == cpu_to_le16(16)) + devinfo->intr_size = 8; + else + devinfo->intr_size = 4; + + devinfo->interval = IFEPDESC(usb, CONTROL_IF, 0).bInterval; + + if (usb->speed == USB_SPEED_HIGH) + brcmf_dbg(USB, "Broadcom high speed USB wireless device detected\n"); + else + brcmf_dbg(USB, "Broadcom full speed USB wireless device detected\n"); + + ret = brcmf_usb_probe_cb(devinfo); + if (ret) + goto fail; + + /* Success */ + return 0; + +fail: + brcmf_err("failed with errno %d\n", ret); + kfree(devinfo); + usb_set_intfdata(intf, NULL); + return ret; + +} + +static void +brcmf_usb_disconnect(struct usb_interface *intf) +{ + struct brcmf_usbdev_info *devinfo; + + brcmf_dbg(USB, "Enter\n"); + devinfo = (struct brcmf_usbdev_info *)usb_get_intfdata(intf); + brcmf_usb_disconnect_cb(devinfo); + kfree(devinfo); + brcmf_dbg(USB, "Exit\n"); +} + +/* + * only need to signal the bus being down and update the state. + */ +static int brcmf_usb_suspend(struct usb_interface *intf, pm_message_t state) +{ + struct usb_device *usb = interface_to_usbdev(intf); + struct brcmf_usbdev_info *devinfo = brcmf_usb_get_businfo(&usb->dev); + + brcmf_dbg(USB, "Enter\n"); + devinfo->bus_pub.state = BRCMFMAC_USB_STATE_SLEEP; + brcmf_detach(&usb->dev); + return 0; +} + +/* + * (re-) start the bus. + */ +static int brcmf_usb_resume(struct usb_interface *intf) +{ + struct usb_device *usb = interface_to_usbdev(intf); + struct brcmf_usbdev_info *devinfo = brcmf_usb_get_businfo(&usb->dev); + + brcmf_dbg(USB, "Enter\n"); + if (!brcmf_attach(0, devinfo->dev)) + return brcmf_bus_start(&usb->dev); + + return 0; +} + +static int brcmf_usb_reset_resume(struct usb_interface *intf) +{ + struct usb_device *usb = interface_to_usbdev(intf); + struct brcmf_usbdev_info *devinfo = brcmf_usb_get_businfo(&usb->dev); + + brcmf_dbg(USB, "Enter\n"); + + if (!brcmf_usb_fw_download(devinfo)) + return brcmf_usb_resume(intf); + + return -EIO; +} + +#define BRCMF_USB_VENDOR_ID_BROADCOM 0x0a5c +#define BRCMF_USB_DEVICE_ID_43143 0xbd1e +#define BRCMF_USB_DEVICE_ID_43236 0xbd17 +#define BRCMF_USB_DEVICE_ID_43242 0xbd1f +#define BRCMF_USB_DEVICE_ID_BCMFW 0x0bdc + +static struct usb_device_id brcmf_usb_devid_table[] = { + { USB_DEVICE(BRCMF_USB_VENDOR_ID_BROADCOM, BRCMF_USB_DEVICE_ID_43143) }, + { USB_DEVICE(BRCMF_USB_VENDOR_ID_BROADCOM, BRCMF_USB_DEVICE_ID_43236) }, + { USB_DEVICE(BRCMF_USB_VENDOR_ID_BROADCOM, BRCMF_USB_DEVICE_ID_43242) }, + /* special entry for device with firmware loaded and running */ + { USB_DEVICE(BRCMF_USB_VENDOR_ID_BROADCOM, BRCMF_USB_DEVICE_ID_BCMFW) }, + { } +}; + +MODULE_DEVICE_TABLE(usb, brcmf_usb_devid_table); +MODULE_FIRMWARE(BRCMF_USB_43143_FW_NAME); +MODULE_FIRMWARE(BRCMF_USB_43236_FW_NAME); +MODULE_FIRMWARE(BRCMF_USB_43242_FW_NAME); + +static struct usb_driver brcmf_usbdrvr = { + .name = KBUILD_MODNAME, + .probe = brcmf_usb_probe, + .disconnect = brcmf_usb_disconnect, + .id_table = brcmf_usb_devid_table, + .suspend = brcmf_usb_suspend, + .resume = brcmf_usb_resume, + .reset_resume = brcmf_usb_reset_resume, + .supports_autosuspend = 1, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +static void brcmf_release_fw(struct list_head *q) +{ + struct brcmf_usb_image *fw_image, *next; + + list_for_each_entry_safe(fw_image, next, q, list) { + vfree(fw_image->image); + list_del_init(&fw_image->list); + } +} + +static int brcmf_usb_reset_device(struct device *dev, void *notused) +{ + /* device past is the usb interface so we + * need to use parent here. + */ + brcmf_dev_reset(dev->parent); + return 0; +} + +void brcmf_usb_exit(void) +{ + struct device_driver *drv = &brcmf_usbdrvr.drvwrap.driver; + int ret; + + brcmf_dbg(USB, "Enter\n"); + ret = driver_for_each_device(drv, NULL, NULL, + brcmf_usb_reset_device); + usb_deregister(&brcmf_usbdrvr); + brcmf_release_fw(&fw_image_list); +} + +void brcmf_usb_register(void) +{ + brcmf_dbg(USB, "Enter\n"); + INIT_LIST_HEAD(&fw_image_list); + usb_register(&brcmf_usbdrvr); +} diff --git a/drivers/net/wireless/brcm80211/brcmfmac/usb.h b/drivers/net/wireless/brcm80211/brcmfmac/usb.h new file mode 100644 index 0000000..f483a8c --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/usb.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2011 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef BRCMFMAC_USB_H +#define BRCMFMAC_USB_H + +enum brcmf_usb_state { + BRCMFMAC_USB_STATE_DOWN, + BRCMFMAC_USB_STATE_DL_FAIL, + BRCMFMAC_USB_STATE_DL_DONE, + BRCMFMAC_USB_STATE_UP, + BRCMFMAC_USB_STATE_SLEEP +}; + +struct brcmf_stats { + u32 tx_ctlpkts; + u32 tx_ctlerrs; + u32 rx_ctlpkts; + u32 rx_ctlerrs; +}; + +struct brcmf_usbdev { + struct brcmf_bus *bus; + struct brcmf_usbdev_info *devinfo; + enum brcmf_usb_state state; + struct brcmf_stats stats; + int ntxq, nrxq, rxsize; + u32 bus_mtu; + int devid; + int chiprev; /* chip revsion number */ +}; + +/* IO Request Block (IRB) */ +struct brcmf_usbreq { + struct list_head list; + struct brcmf_usbdev_info *devinfo; + struct urb *urb; + struct sk_buff *skb; +}; + +#endif /* BRCMFMAC_USB_H */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/usb_rdl.h b/drivers/net/wireless/brcm80211/brcmfmac/usb_rdl.h new file mode 100644 index 0000000..0a35c51 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/usb_rdl.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2011 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _USB_RDL_H +#define _USB_RDL_H + +/* Control messages: bRequest values */ +#define DL_GETSTATE 0 /* returns the rdl_state_t struct */ +#define DL_CHECK_CRC 1 /* currently unused */ +#define DL_GO 2 /* execute downloaded image */ +#define DL_START 3 /* initialize dl state */ +#define DL_REBOOT 4 /* reboot the device in 2 seconds */ +#define DL_GETVER 5 /* returns the bootrom_id_t struct */ +#define DL_GO_PROTECTED 6 /* execute the downloaded code and set reset + * event to occur in 2 seconds. It is the + * responsibility of the downloaded code to + * clear this event + */ +#define DL_EXEC 7 /* jump to a supplied address */ +#define DL_RESETCFG 8 /* To support single enum on dongle + * - Not used by bootloader + */ +#define DL_DEFER_RESP_OK 9 /* Potentially defer the response to setup + * if resp unavailable + */ + +/* states */ +#define DL_WAITING 0 /* waiting to rx first pkt */ +#define DL_READY 1 /* hdr was good, waiting for more of the + * compressed image */ +#define DL_BAD_HDR 2 /* hdr was corrupted */ +#define DL_BAD_CRC 3 /* compressed image was corrupted */ +#define DL_RUNNABLE 4 /* download was successful,waiting for go cmd */ +#define DL_START_FAIL 5 /* failed to initialize correctly */ +#define DL_NVRAM_TOOBIG 6 /* host specified nvram data exceeds DL_NVRAM + * value */ +#define DL_IMAGE_TOOBIG 7 /* download image too big (exceeds DATA_START + * for rdl) */ + +struct rdl_state_le { + __le32 state; + __le32 bytes; +}; + +struct bootrom_id_le { + __le32 chip; /* Chip id */ + __le32 chiprev; /* Chip rev */ + __le32 ramsize; /* Size of RAM */ + __le32 remapbase; /* Current remap base address */ + __le32 boardtype; /* Type of board */ + __le32 boardrev; /* Board revision */ +}; + +#define RDL_CHUNK 1500 /* size of each dl transfer */ + +#define TRX_OFFSETS_DLFWLEN_IDX 0 +#define TRX_OFFSETS_JUMPTO_IDX 1 +#define TRX_OFFSETS_NVM_LEN_IDX 2 + +#define TRX_OFFSETS_DLBASE_IDX 0 + +#endif /* _USB_RDL_H */ diff --git a/drivers/net/wireless/brcm80211/brcmfmac/wl_cfg80211.c b/drivers/net/wireless/brcm80211/brcmfmac/wl_cfg80211.c new file mode 100644 index 0000000..f61a8f0 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/wl_cfg80211.c @@ -0,0 +1,5417 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* Toplevel file. Relies on dhd_linux.c to send commands to the dongle. */ + +#include +#include +#include +#include + +#include +#include +#include +#include "dhd.h" +#include "dhd_dbg.h" +#include "tracepoint.h" +#include "fwil_types.h" +#include "p2p.h" +#include "btcoex.h" +#include "wl_cfg80211.h" +#include "fwil.h" + +#define BRCMF_SCAN_IE_LEN_MAX 2048 +#define BRCMF_PNO_VERSION 2 +#define BRCMF_PNO_TIME 30 +#define BRCMF_PNO_REPEAT 4 +#define BRCMF_PNO_FREQ_EXPO_MAX 3 +#define BRCMF_PNO_MAX_PFN_COUNT 16 +#define BRCMF_PNO_ENABLE_ADAPTSCAN_BIT 6 +#define BRCMF_PNO_HIDDEN_BIT 2 +#define BRCMF_PNO_WPA_AUTH_ANY 0xFFFFFFFF +#define BRCMF_PNO_SCAN_COMPLETE 1 +#define BRCMF_PNO_SCAN_INCOMPLETE 0 + +#define BRCMF_IFACE_MAX_CNT 3 + +#define WPA_OUI "\x00\x50\xF2" /* WPA OUI */ +#define WPA_OUI_TYPE 1 +#define RSN_OUI "\x00\x0F\xAC" /* RSN OUI */ +#define WME_OUI_TYPE 2 +#define WPS_OUI_TYPE 4 + +#define VS_IE_FIXED_HDR_LEN 6 +#define WPA_IE_VERSION_LEN 2 +#define WPA_IE_MIN_OUI_LEN 4 +#define WPA_IE_SUITE_COUNT_LEN 2 + +#define WPA_CIPHER_NONE 0 /* None */ +#define WPA_CIPHER_WEP_40 1 /* WEP (40-bit) */ +#define WPA_CIPHER_TKIP 2 /* TKIP: default for WPA */ +#define WPA_CIPHER_AES_CCM 4 /* AES (CCM) */ +#define WPA_CIPHER_WEP_104 5 /* WEP (104-bit) */ + +#define RSN_AKM_NONE 0 /* None (IBSS) */ +#define RSN_AKM_UNSPECIFIED 1 /* Over 802.1x */ +#define RSN_AKM_PSK 2 /* Pre-shared Key */ +#define RSN_CAP_LEN 2 /* Length of RSN capabilities */ +#define RSN_CAP_PTK_REPLAY_CNTR_MASK 0x000C + +#define VNDR_IE_CMD_LEN 4 /* length of the set command + * string :"add", "del" (+ NUL) + */ +#define VNDR_IE_COUNT_OFFSET 4 +#define VNDR_IE_PKTFLAG_OFFSET 8 +#define VNDR_IE_VSIE_OFFSET 12 +#define VNDR_IE_HDR_SIZE 12 +#define VNDR_IE_PARSE_LIMIT 5 + +#define DOT11_MGMT_HDR_LEN 24 /* d11 management header len */ +#define DOT11_BCN_PRB_FIXED_LEN 12 /* beacon/probe fixed length */ + +#define BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS 320 +#define BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS 400 +#define BRCMF_SCAN_JOIN_PROBE_INTERVAL_MS 20 + +#define BRCMF_ASSOC_PARAMS_FIXED_SIZE \ + (sizeof(struct brcmf_assoc_params_le) - sizeof(u16)) + +static bool check_vif_up(struct brcmf_cfg80211_vif *vif) +{ + if (!test_bit(BRCMF_VIF_STATUS_READY, &vif->sme_state)) { + brcmf_dbg(INFO, "device is not ready : status (%lu)\n", + vif->sme_state); + return false; + } + return true; +} + +#define CHAN2G(_channel, _freq, _flags) { \ + .band = IEEE80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_channel), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +#define CHAN5G(_channel, _flags) { \ + .band = IEEE80211_BAND_5GHZ, \ + .center_freq = 5000 + (5 * (_channel)), \ + .hw_value = (_channel), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +#define RATE_TO_BASE100KBPS(rate) (((rate) * 10) / 2) +#define RATETAB_ENT(_rateid, _flags) \ + { \ + .bitrate = RATE_TO_BASE100KBPS(_rateid), \ + .hw_value = (_rateid), \ + .flags = (_flags), \ + } + +static struct ieee80211_rate __wl_rates[] = { + RATETAB_ENT(BRCM_RATE_1M, 0), + RATETAB_ENT(BRCM_RATE_2M, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(BRCM_RATE_5M5, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(BRCM_RATE_11M, IEEE80211_RATE_SHORT_PREAMBLE), + RATETAB_ENT(BRCM_RATE_6M, 0), + RATETAB_ENT(BRCM_RATE_9M, 0), + RATETAB_ENT(BRCM_RATE_12M, 0), + RATETAB_ENT(BRCM_RATE_18M, 0), + RATETAB_ENT(BRCM_RATE_24M, 0), + RATETAB_ENT(BRCM_RATE_36M, 0), + RATETAB_ENT(BRCM_RATE_48M, 0), + RATETAB_ENT(BRCM_RATE_54M, 0), +}; + +#define wl_a_rates (__wl_rates + 4) +#define wl_a_rates_size 8 +#define wl_g_rates (__wl_rates + 0) +#define wl_g_rates_size 12 + +static struct ieee80211_channel __wl_2ghz_channels[] = { + CHAN2G(1, 2412, 0), + CHAN2G(2, 2417, 0), + CHAN2G(3, 2422, 0), + CHAN2G(4, 2427, 0), + CHAN2G(5, 2432, 0), + CHAN2G(6, 2437, 0), + CHAN2G(7, 2442, 0), + CHAN2G(8, 2447, 0), + CHAN2G(9, 2452, 0), + CHAN2G(10, 2457, 0), + CHAN2G(11, 2462, 0), + CHAN2G(12, 2467, 0), + CHAN2G(13, 2472, 0), + CHAN2G(14, 2484, 0), +}; + +static struct ieee80211_channel __wl_5ghz_a_channels[] = { + CHAN5G(34, 0), CHAN5G(36, 0), + CHAN5G(38, 0), CHAN5G(40, 0), + CHAN5G(42, 0), CHAN5G(44, 0), + CHAN5G(46, 0), CHAN5G(48, 0), + CHAN5G(52, 0), CHAN5G(56, 0), + CHAN5G(60, 0), CHAN5G(64, 0), + CHAN5G(100, 0), CHAN5G(104, 0), + CHAN5G(108, 0), CHAN5G(112, 0), + CHAN5G(116, 0), CHAN5G(120, 0), + CHAN5G(124, 0), CHAN5G(128, 0), + CHAN5G(132, 0), CHAN5G(136, 0), + CHAN5G(140, 0), CHAN5G(149, 0), + CHAN5G(153, 0), CHAN5G(157, 0), + CHAN5G(161, 0), CHAN5G(165, 0), + CHAN5G(184, 0), CHAN5G(188, 0), + CHAN5G(192, 0), CHAN5G(196, 0), + CHAN5G(200, 0), CHAN5G(204, 0), + CHAN5G(208, 0), CHAN5G(212, 0), + CHAN5G(216, 0), +}; + +static struct ieee80211_supported_band __wl_band_2ghz = { + .band = IEEE80211_BAND_2GHZ, + .channels = __wl_2ghz_channels, + .n_channels = ARRAY_SIZE(__wl_2ghz_channels), + .bitrates = wl_g_rates, + .n_bitrates = wl_g_rates_size, +}; + +static struct ieee80211_supported_band __wl_band_5ghz_a = { + .band = IEEE80211_BAND_5GHZ, + .channels = __wl_5ghz_a_channels, + .n_channels = ARRAY_SIZE(__wl_5ghz_a_channels), + .bitrates = wl_a_rates, + .n_bitrates = wl_a_rates_size, +}; + +/* This is to override regulatory domains defined in cfg80211 module (reg.c) + * By default world regulatory domain defined in reg.c puts the flags + * NL80211_RRF_PASSIVE_SCAN and NL80211_RRF_NO_IBSS for 5GHz channels (for + * 36..48 and 149..165). With respect to these flags, wpa_supplicant doesn't + * start p2p operations on 5GHz channels. All the changes in world regulatory + * domain are to be done here. + */ +static const struct ieee80211_regdomain brcmf_regdom = { + .n_reg_rules = 4, + .alpha2 = "99", + .reg_rules = { + /* IEEE 802.11b/g, channels 1..11 */ + REG_RULE(2412-10, 2472+10, 40, 6, 20, 0), + /* If any */ + /* IEEE 802.11 channel 14 - Only JP enables + * this and for 802.11b only + */ + REG_RULE(2484-10, 2484+10, 20, 6, 20, 0), + /* IEEE 802.11a, channel 36..64 */ + REG_RULE(5150-10, 5350+10, 40, 6, 20, 0), + /* IEEE 802.11a, channel 100..165 */ + REG_RULE(5470-10, 5850+10, 40, 6, 20, 0), } +}; + +static const u32 __wl_cipher_suites[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, + WLAN_CIPHER_SUITE_AES_CMAC, +}; + +/* Vendor specific ie. id = 221, oui and type defines exact ie */ +struct brcmf_vs_tlv { + u8 id; + u8 len; + u8 oui[3]; + u8 oui_type; +}; + +struct parsed_vndr_ie_info { + u8 *ie_ptr; + u32 ie_len; /* total length including id & length field */ + struct brcmf_vs_tlv vndrie; +}; + +struct parsed_vndr_ies { + u32 count; + struct parsed_vndr_ie_info ie_info[VNDR_IE_PARSE_LIMIT]; +}; + +/* Quarter dBm units to mW + * Table starts at QDBM_OFFSET, so the first entry is mW for qdBm=153 + * Table is offset so the last entry is largest mW value that fits in + * a u16. + */ + +#define QDBM_OFFSET 153 /* Offset for first entry */ +#define QDBM_TABLE_LEN 40 /* Table size */ + +/* Smallest mW value that will round up to the first table entry, QDBM_OFFSET. + * Value is ( mW(QDBM_OFFSET - 1) + mW(QDBM_OFFSET) ) / 2 + */ +#define QDBM_TABLE_LOW_BOUND 6493 /* Low bound */ + +/* Largest mW value that will round down to the last table entry, + * QDBM_OFFSET + QDBM_TABLE_LEN-1. + * Value is ( mW(QDBM_OFFSET + QDBM_TABLE_LEN - 1) + + * mW(QDBM_OFFSET + QDBM_TABLE_LEN) ) / 2. + */ +#define QDBM_TABLE_HIGH_BOUND 64938 /* High bound */ + +static const u16 nqdBm_to_mW_map[QDBM_TABLE_LEN] = { +/* qdBm: +0 +1 +2 +3 +4 +5 +6 +7 */ +/* 153: */ 6683, 7079, 7499, 7943, 8414, 8913, 9441, 10000, +/* 161: */ 10593, 11220, 11885, 12589, 13335, 14125, 14962, 15849, +/* 169: */ 16788, 17783, 18836, 19953, 21135, 22387, 23714, 25119, +/* 177: */ 26607, 28184, 29854, 31623, 33497, 35481, 37584, 39811, +/* 185: */ 42170, 44668, 47315, 50119, 53088, 56234, 59566, 63096 +}; + +static u16 brcmf_qdbm_to_mw(u8 qdbm) +{ + uint factor = 1; + int idx = qdbm - QDBM_OFFSET; + + if (idx >= QDBM_TABLE_LEN) + /* clamp to max u16 mW value */ + return 0xFFFF; + + /* scale the qdBm index up to the range of the table 0-40 + * where an offset of 40 qdBm equals a factor of 10 mW. + */ + while (idx < 0) { + idx += 40; + factor *= 10; + } + + /* return the mW value scaled down to the correct factor of 10, + * adding in factor/2 to get proper rounding. + */ + return (nqdBm_to_mW_map[idx] + factor / 2) / factor; +} + +static u8 brcmf_mw_to_qdbm(u16 mw) +{ + u8 qdbm; + int offset; + uint mw_uint = mw; + uint boundary; + + /* handle boundary case */ + if (mw_uint <= 1) + return 0; + + offset = QDBM_OFFSET; + + /* move mw into the range of the table */ + while (mw_uint < QDBM_TABLE_LOW_BOUND) { + mw_uint *= 10; + offset -= 40; + } + + for (qdbm = 0; qdbm < QDBM_TABLE_LEN - 1; qdbm++) { + boundary = nqdBm_to_mW_map[qdbm] + (nqdBm_to_mW_map[qdbm + 1] - + nqdBm_to_mW_map[qdbm]) / 2; + if (mw_uint < boundary) + break; + } + + qdbm += (u8) offset; + + return qdbm; +} + +u16 channel_to_chanspec(struct brcmu_d11inf *d11inf, + struct ieee80211_channel *ch) +{ + struct brcmu_chan ch_inf; + + ch_inf.chnum = ieee80211_frequency_to_channel(ch->center_freq); + ch_inf.bw = BRCMU_CHAN_BW_20; + d11inf->encchspec(&ch_inf); + + return ch_inf.chspec; +} + +/* Traverse a string of 1-byte tag/1-byte length/variable-length value + * triples, returning a pointer to the substring whose first element + * matches tag + */ +struct brcmf_tlv *brcmf_parse_tlvs(void *buf, int buflen, uint key) +{ + struct brcmf_tlv *elt; + int totlen; + + elt = (struct brcmf_tlv *)buf; + totlen = buflen; + + /* find tagged parameter */ + while (totlen >= TLV_HDR_LEN) { + int len = elt->len; + + /* validate remaining totlen */ + if ((elt->id == key) && (totlen >= (len + TLV_HDR_LEN))) + return elt; + + elt = (struct brcmf_tlv *)((u8 *)elt + (len + TLV_HDR_LEN)); + totlen -= (len + TLV_HDR_LEN); + } + + return NULL; +} + +/* Is any of the tlvs the expected entry? If + * not update the tlvs buffer pointer/length. + */ +static bool +brcmf_tlv_has_ie(u8 *ie, u8 **tlvs, u32 *tlvs_len, + u8 *oui, u32 oui_len, u8 type) +{ + /* If the contents match the OUI and the type */ + if (ie[TLV_LEN_OFF] >= oui_len + 1 && + !memcmp(&ie[TLV_BODY_OFF], oui, oui_len) && + type == ie[TLV_BODY_OFF + oui_len]) { + return true; + } + + if (tlvs == NULL) + return false; + /* point to the next ie */ + ie += ie[TLV_LEN_OFF] + TLV_HDR_LEN; + /* calculate the length of the rest of the buffer */ + *tlvs_len -= (int)(ie - *tlvs); + /* update the pointer to the start of the buffer */ + *tlvs = ie; + + return false; +} + +static struct brcmf_vs_tlv * +brcmf_find_wpaie(u8 *parse, u32 len) +{ + struct brcmf_tlv *ie; + + while ((ie = brcmf_parse_tlvs(parse, len, WLAN_EID_VENDOR_SPECIFIC))) { + if (brcmf_tlv_has_ie((u8 *)ie, &parse, &len, + WPA_OUI, TLV_OUI_LEN, WPA_OUI_TYPE)) + return (struct brcmf_vs_tlv *)ie; + } + return NULL; +} + +static struct brcmf_vs_tlv * +brcmf_find_wpsie(u8 *parse, u32 len) +{ + struct brcmf_tlv *ie; + + while ((ie = brcmf_parse_tlvs(parse, len, WLAN_EID_VENDOR_SPECIFIC))) { + if (brcmf_tlv_has_ie((u8 *)ie, &parse, &len, + WPA_OUI, TLV_OUI_LEN, WPS_OUI_TYPE)) + return (struct brcmf_vs_tlv *)ie; + } + return NULL; +} + + +static void convert_key_from_CPU(struct brcmf_wsec_key *key, + struct brcmf_wsec_key_le *key_le) +{ + key_le->index = cpu_to_le32(key->index); + key_le->len = cpu_to_le32(key->len); + key_le->algo = cpu_to_le32(key->algo); + key_le->flags = cpu_to_le32(key->flags); + key_le->rxiv.hi = cpu_to_le32(key->rxiv.hi); + key_le->rxiv.lo = cpu_to_le16(key->rxiv.lo); + key_le->iv_initialized = cpu_to_le32(key->iv_initialized); + memcpy(key_le->data, key->data, sizeof(key->data)); + memcpy(key_le->ea, key->ea, sizeof(key->ea)); +} + +static int +send_key_to_dongle(struct net_device *ndev, struct brcmf_wsec_key *key) +{ + int err; + struct brcmf_wsec_key_le key_le; + + convert_key_from_CPU(key, &key_le); + + brcmf_netdev_wait_pend8021x(ndev); + + err = brcmf_fil_bsscfg_data_set(netdev_priv(ndev), "wsec_key", &key_le, + sizeof(key_le)); + + if (err) + brcmf_err("wsec_key error (%d)\n", err); + return err; +} + +static s32 +brcmf_configure_arp_offload(struct brcmf_if *ifp, bool enable) +{ + s32 err; + u32 mode; + + if (enable) + mode = BRCMF_ARP_OL_AGENT | BRCMF_ARP_OL_PEER_AUTO_REPLY; + else + mode = 0; + + /* Try to set and enable ARP offload feature, this may fail, then it */ + /* is simply not supported and err 0 will be returned */ + err = brcmf_fil_iovar_int_set(ifp, "arp_ol", mode); + if (err) { + brcmf_dbg(TRACE, "failed to set ARP offload mode to 0x%x, err = %d\n", + mode, err); + err = 0; + } else { + err = brcmf_fil_iovar_int_set(ifp, "arpoe", enable); + if (err) { + brcmf_dbg(TRACE, "failed to configure (%d) ARP offload err = %d\n", + enable, err); + err = 0; + } else + brcmf_dbg(TRACE, "successfully configured (%d) ARP offload to 0x%x\n", + enable, mode); + } + + return err; +} + +static struct wireless_dev *brcmf_cfg80211_add_iface(struct wiphy *wiphy, + const char *name, + enum nl80211_iftype type, + u32 *flags, + struct vif_params *params) +{ + brcmf_dbg(TRACE, "enter: %s type %d\n", name, type); + switch (type) { + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_AP_VLAN: + case NL80211_IFTYPE_WDS: + case NL80211_IFTYPE_MONITOR: + case NL80211_IFTYPE_MESH_POINT: + return ERR_PTR(-EOPNOTSUPP); + case NL80211_IFTYPE_P2P_CLIENT: + case NL80211_IFTYPE_P2P_GO: + case NL80211_IFTYPE_P2P_DEVICE: + return brcmf_p2p_add_vif(wiphy, name, type, flags, params); + case NL80211_IFTYPE_UNSPECIFIED: + default: + return ERR_PTR(-EINVAL); + } +} + +void brcmf_set_mpc(struct brcmf_if *ifp, int mpc) +{ + s32 err = 0; + + if (check_vif_up(ifp->vif)) { + err = brcmf_fil_iovar_int_set(ifp, "mpc", mpc); + if (err) { + brcmf_err("fail to set mpc\n"); + return; + } + brcmf_dbg(INFO, "MPC : %d\n", mpc); + } +} + +s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg, + struct brcmf_if *ifp, bool aborted, + bool fw_abort) +{ + struct brcmf_scan_params_le params_le; + struct cfg80211_scan_request *scan_request; + s32 err = 0; + + brcmf_dbg(SCAN, "Enter\n"); + + /* clear scan request, because the FW abort can cause a second call */ + /* to this functon and might cause a double cfg80211_scan_done */ + scan_request = cfg->scan_request; + cfg->scan_request = NULL; + + if (timer_pending(&cfg->escan_timeout)) + del_timer_sync(&cfg->escan_timeout); + + if (fw_abort) { + /* Do a scan abort to stop the driver's scan engine */ + brcmf_dbg(SCAN, "ABORT scan in firmware\n"); + memset(¶ms_le, 0, sizeof(params_le)); + memset(params_le.bssid, 0xFF, ETH_ALEN); + params_le.bss_type = DOT11_BSSTYPE_ANY; + params_le.scan_type = 0; + params_le.channel_num = cpu_to_le32(1); + params_le.nprobes = cpu_to_le32(1); + params_le.active_time = cpu_to_le32(-1); + params_le.passive_time = cpu_to_le32(-1); + params_le.home_time = cpu_to_le32(-1); + /* Scan is aborted by setting channel_list[0] to -1 */ + params_le.channel_list[0] = cpu_to_le16(-1); + /* E-Scan (or anyother type) can be aborted by SCAN */ + err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCAN, + ¶ms_le, sizeof(params_le)); + if (err) + brcmf_err("Scan abort failed\n"); + } + /* + * e-scan can be initiated by scheduled scan + * which takes precedence. + */ + if (cfg->sched_escan) { + brcmf_dbg(SCAN, "scheduled scan completed\n"); + cfg->sched_escan = false; + if (!aborted) + cfg80211_sched_scan_results(cfg_to_wiphy(cfg)); + brcmf_set_mpc(ifp, 1); + } else if (scan_request) { + brcmf_dbg(SCAN, "ESCAN Completed scan: %s\n", + aborted ? "Aborted" : "Done"); + cfg80211_scan_done(scan_request, aborted); + brcmf_set_mpc(ifp, 1); + } + if (!test_and_clear_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status)) + brcmf_dbg(SCAN, "Scan complete, probably P2P scan\n"); + + return err; +} + +static +int brcmf_cfg80211_del_iface(struct wiphy *wiphy, struct wireless_dev *wdev) +{ + struct brcmf_cfg80211_info *cfg = wiphy_priv(wiphy); + struct net_device *ndev = wdev->netdev; + + /* vif event pending in firmware */ + if (brcmf_cfg80211_vif_event_armed(cfg)) + return -EBUSY; + + if (ndev) { + if (test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status) && + cfg->escan_info.ifp == netdev_priv(ndev)) + brcmf_notify_escan_complete(cfg, netdev_priv(ndev), + true, true); + + brcmf_fil_iovar_int_set(netdev_priv(ndev), "mpc", 1); + } + + switch (wdev->iftype) { + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_AP_VLAN: + case NL80211_IFTYPE_WDS: + case NL80211_IFTYPE_MONITOR: + case NL80211_IFTYPE_MESH_POINT: + return -EOPNOTSUPP; + case NL80211_IFTYPE_P2P_CLIENT: + case NL80211_IFTYPE_P2P_GO: + case NL80211_IFTYPE_P2P_DEVICE: + return brcmf_p2p_del_vif(wiphy, wdev); + case NL80211_IFTYPE_UNSPECIFIED: + default: + return -EINVAL; + } + return -EOPNOTSUPP; +} + +static s32 +brcmf_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *ndev, + enum nl80211_iftype type, u32 *flags, + struct vif_params *params) +{ + struct brcmf_cfg80211_info *cfg = wiphy_priv(wiphy); + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_cfg80211_vif *vif = ifp->vif; + s32 infra = 0; + s32 ap = 0; + s32 err = 0; + + brcmf_dbg(TRACE, "Enter, ndev=%p, type=%d\n", ndev, type); + + switch (type) { + case NL80211_IFTYPE_MONITOR: + case NL80211_IFTYPE_WDS: + brcmf_err("type (%d) : currently we do not support this type\n", + type); + return -EOPNOTSUPP; + case NL80211_IFTYPE_ADHOC: + vif->mode = WL_MODE_IBSS; + infra = 0; + break; + case NL80211_IFTYPE_STATION: + /* Ignore change for p2p IF. Unclear why supplicant does this */ + if ((vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) || + (vif->wdev.iftype == NL80211_IFTYPE_P2P_GO)) { + brcmf_dbg(TRACE, "Ignoring cmd for p2p if\n"); + /* WAR: It is unexpected to get a change of VIF for P2P + * IF, but it happens. The request can not be handled + * but returning EPERM causes a crash. Returning 0 + * without setting ieee80211_ptr->iftype causes trace + * (WARN_ON) but it works with wpa_supplicant + */ + return 0; + } + vif->mode = WL_MODE_BSS; + infra = 1; + break; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + vif->mode = WL_MODE_AP; + ap = 1; + break; + default: + err = -EINVAL; + goto done; + } + + if (ap) { + if (type == NL80211_IFTYPE_P2P_GO) { + brcmf_dbg(INFO, "IF Type = P2P GO\n"); + err = brcmf_p2p_ifchange(cfg, BRCMF_FIL_P2P_IF_GO); + } + if (!err) { + set_bit(BRCMF_VIF_STATUS_AP_CREATING, &vif->sme_state); + brcmf_dbg(INFO, "IF Type = AP\n"); + } + } else { + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_INFRA, infra); + if (err) { + brcmf_err("WLC_SET_INFRA error (%d)\n", err); + err = -EAGAIN; + goto done; + } + brcmf_dbg(INFO, "IF Type = %s\n", (vif->mode == WL_MODE_IBSS) ? + "Adhoc" : "Infra"); + } + ndev->ieee80211_ptr->iftype = type; + +done: + brcmf_dbg(TRACE, "Exit\n"); + + return err; +} + +static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg, + struct brcmf_scan_params_le *params_le, + struct cfg80211_scan_request *request) +{ + u32 n_ssids; + u32 n_channels; + s32 i; + s32 offset; + u16 chanspec; + char *ptr; + struct brcmf_ssid_le ssid_le; + + memset(params_le->bssid, 0xFF, ETH_ALEN); + params_le->bss_type = DOT11_BSSTYPE_ANY; + params_le->scan_type = 0; + params_le->channel_num = 0; + params_le->nprobes = cpu_to_le32(-1); + params_le->active_time = cpu_to_le32(-1); + params_le->passive_time = cpu_to_le32(-1); + params_le->home_time = cpu_to_le32(-1); + memset(¶ms_le->ssid_le, 0, sizeof(params_le->ssid_le)); + + /* if request is null exit so it will be all channel broadcast scan */ + if (!request) + return; + + n_ssids = request->n_ssids; + n_channels = request->n_channels; + /* Copy channel array if applicable */ + brcmf_dbg(SCAN, "### List of channelspecs to scan ### %d\n", + n_channels); + if (n_channels > 0) { + for (i = 0; i < n_channels; i++) { + chanspec = channel_to_chanspec(&cfg->d11inf, + request->channels[i]); + brcmf_dbg(SCAN, "Chan : %d, Channel spec: %x\n", + request->channels[i]->hw_value, chanspec); + params_le->channel_list[i] = cpu_to_le16(chanspec); + } + } else { + brcmf_dbg(SCAN, "Scanning all channels\n"); + } + /* Copy ssid array if applicable */ + brcmf_dbg(SCAN, "### List of SSIDs to scan ### %d\n", n_ssids); + if (n_ssids > 0) { + offset = offsetof(struct brcmf_scan_params_le, channel_list) + + n_channels * sizeof(u16); + offset = roundup(offset, sizeof(u32)); + ptr = (char *)params_le + offset; + for (i = 0; i < n_ssids; i++) { + memset(&ssid_le, 0, sizeof(ssid_le)); + ssid_le.SSID_len = + cpu_to_le32(request->ssids[i].ssid_len); + memcpy(ssid_le.SSID, request->ssids[i].ssid, + request->ssids[i].ssid_len); + if (!ssid_le.SSID_len) + brcmf_dbg(SCAN, "%d: Broadcast scan\n", i); + else + brcmf_dbg(SCAN, "%d: scan for %s size =%d\n", + i, ssid_le.SSID, ssid_le.SSID_len); + memcpy(ptr, &ssid_le, sizeof(ssid_le)); + ptr += sizeof(ssid_le); + } + } else { + brcmf_dbg(SCAN, "Broadcast scan %p\n", request->ssids); + if ((request->ssids) && request->ssids->ssid_len) { + brcmf_dbg(SCAN, "SSID %s len=%d\n", + params_le->ssid_le.SSID, + request->ssids->ssid_len); + params_le->ssid_le.SSID_len = + cpu_to_le32(request->ssids->ssid_len); + memcpy(¶ms_le->ssid_le.SSID, request->ssids->ssid, + request->ssids->ssid_len); + } + } + /* Adding mask to channel numbers */ + params_le->channel_num = + cpu_to_le32((n_ssids << BRCMF_SCAN_PARAMS_NSSID_SHIFT) | + (n_channels & BRCMF_SCAN_PARAMS_COUNT_MASK)); +} + +static s32 +brcmf_run_escan(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, + struct cfg80211_scan_request *request, u16 action) +{ + s32 params_size = BRCMF_SCAN_PARAMS_FIXED_SIZE + + offsetof(struct brcmf_escan_params_le, params_le); + struct brcmf_escan_params_le *params; + s32 err = 0; + + brcmf_dbg(SCAN, "E-SCAN START\n"); + + if (request != NULL) { + /* Allocate space for populating ssids in struct */ + params_size += sizeof(u32) * ((request->n_channels + 1) / 2); + + /* Allocate space for populating ssids in struct */ + params_size += sizeof(struct brcmf_ssid) * request->n_ssids; + } + + params = kzalloc(params_size, GFP_KERNEL); + if (!params) { + err = -ENOMEM; + goto exit; + } + BUG_ON(params_size + sizeof("escan") >= BRCMF_DCMD_MEDLEN); + brcmf_escan_prep(cfg, ¶ms->params_le, request); + params->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION); + params->action = cpu_to_le16(action); + params->sync_id = cpu_to_le16(0x1234); + + err = brcmf_fil_iovar_data_set(ifp, "escan", params, params_size); + if (err) { + if (err == -EBUSY) + brcmf_dbg(INFO, "system busy : escan canceled\n"); + else + brcmf_err("error (%d)\n", err); + } + + kfree(params); +exit: + return err; +} + +static s32 +brcmf_do_escan(struct brcmf_cfg80211_info *cfg, struct wiphy *wiphy, + struct brcmf_if *ifp, struct cfg80211_scan_request *request) +{ + s32 err; + u32 passive_scan; + struct brcmf_scan_results *results; + struct escan_info *escan = &cfg->escan_info; + + brcmf_dbg(SCAN, "Enter\n"); + escan->ifp = ifp; + escan->wiphy = wiphy; + escan->escan_state = WL_ESCAN_STATE_SCANNING; + passive_scan = cfg->active_scan ? 0 : 1; + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PASSIVE_SCAN, + passive_scan); + if (err) { + brcmf_err("error (%d)\n", err); + return err; + } + brcmf_set_mpc(ifp, 0); + results = (struct brcmf_scan_results *)cfg->escan_info.escan_buf; + results->version = 0; + results->count = 0; + results->buflen = WL_ESCAN_RESULTS_FIXED_SIZE; + + err = escan->run(cfg, ifp, request, WL_ESCAN_ACTION_START); + if (err) + brcmf_set_mpc(ifp, 1); + return err; +} + +static s32 +brcmf_cfg80211_escan(struct wiphy *wiphy, struct brcmf_cfg80211_vif *vif, + struct cfg80211_scan_request *request, + struct cfg80211_ssid *this_ssid) +{ + struct brcmf_if *ifp = vif->ifp; + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct cfg80211_ssid *ssids; + struct brcmf_cfg80211_scan_req *sr = &cfg->scan_req_int; + u32 passive_scan; + bool escan_req; + bool spec_scan; + s32 err; + u32 SSID_len; + + brcmf_dbg(SCAN, "START ESCAN\n"); + + if (test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status)) { + brcmf_err("Scanning already: status (%lu)\n", cfg->scan_status); + return -EAGAIN; + } + if (test_bit(BRCMF_SCAN_STATUS_ABORT, &cfg->scan_status)) { + brcmf_err("Scanning being aborted: status (%lu)\n", + cfg->scan_status); + return -EAGAIN; + } + if (test_bit(BRCMF_SCAN_STATUS_SUPPRESS, &cfg->scan_status)) { + brcmf_err("Scanning suppressed: status (%lu)\n", + cfg->scan_status); + return -EAGAIN; + } + if (test_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state)) { + brcmf_err("Connecting: status (%lu)\n", ifp->vif->sme_state); + return -EAGAIN; + } + + /* If scan req comes for p2p0, send it over primary I/F */ + if (vif == cfg->p2p.bss_idx[P2PAPI_BSSCFG_DEVICE].vif) + vif = cfg->p2p.bss_idx[P2PAPI_BSSCFG_PRIMARY].vif; + + /* Arm scan timeout timer */ + mod_timer(&cfg->escan_timeout, jiffies + + WL_ESCAN_TIMER_INTERVAL_MS * HZ / 1000); + + escan_req = false; + if (request) { + /* scan bss */ + ssids = request->ssids; + escan_req = true; + } else { + /* scan in ibss */ + /* we don't do escan in ibss */ + ssids = this_ssid; + } + + cfg->scan_request = request; + set_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status); + if (escan_req) { + cfg->escan_info.run = brcmf_run_escan; + err = brcmf_p2p_scan_prep(wiphy, request, vif); + if (err) + goto scan_out; + + err = brcmf_do_escan(cfg, wiphy, vif->ifp, request); + if (err) + goto scan_out; + } else { + brcmf_dbg(SCAN, "ssid \"%s\", ssid_len (%d)\n", + ssids->ssid, ssids->ssid_len); + memset(&sr->ssid_le, 0, sizeof(sr->ssid_le)); + SSID_len = min_t(u8, sizeof(sr->ssid_le.SSID), ssids->ssid_len); + sr->ssid_le.SSID_len = cpu_to_le32(0); + spec_scan = false; + if (SSID_len) { + memcpy(sr->ssid_le.SSID, ssids->ssid, SSID_len); + sr->ssid_le.SSID_len = cpu_to_le32(SSID_len); + spec_scan = true; + } else + brcmf_dbg(SCAN, "Broadcast scan\n"); + + passive_scan = cfg->active_scan ? 0 : 1; + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PASSIVE_SCAN, + passive_scan); + if (err) { + brcmf_err("WLC_SET_PASSIVE_SCAN error (%d)\n", err); + goto scan_out; + } + brcmf_set_mpc(ifp, 0); + err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCAN, + &sr->ssid_le, sizeof(sr->ssid_le)); + if (err) { + if (err == -EBUSY) + brcmf_dbg(INFO, "BUSY: scan for \"%s\" canceled\n", + sr->ssid_le.SSID); + else + brcmf_err("WLC_SCAN error (%d)\n", err); + + brcmf_set_mpc(ifp, 1); + goto scan_out; + } + } + + return 0; + +scan_out: + clear_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status); + if (timer_pending(&cfg->escan_timeout)) + del_timer_sync(&cfg->escan_timeout); + cfg->scan_request = NULL; + return err; +} + +static s32 +brcmf_cfg80211_scan(struct wiphy *wiphy, struct cfg80211_scan_request *request) +{ + struct brcmf_cfg80211_vif *vif; + s32 err = 0; + + brcmf_dbg(TRACE, "Enter\n"); + vif = container_of(request->wdev, struct brcmf_cfg80211_vif, wdev); + if (!check_vif_up(vif)) + return -EIO; + + err = brcmf_cfg80211_escan(wiphy, vif, request, NULL); + + if (err) + brcmf_err("scan error (%d)\n", err); + + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 brcmf_set_rts(struct net_device *ndev, u32 rts_threshold) +{ + s32 err = 0; + + err = brcmf_fil_iovar_int_set(netdev_priv(ndev), "rtsthresh", + rts_threshold); + if (err) + brcmf_err("Error (%d)\n", err); + + return err; +} + +static s32 brcmf_set_frag(struct net_device *ndev, u32 frag_threshold) +{ + s32 err = 0; + + err = brcmf_fil_iovar_int_set(netdev_priv(ndev), "fragthresh", + frag_threshold); + if (err) + brcmf_err("Error (%d)\n", err); + + return err; +} + +static s32 brcmf_set_retry(struct net_device *ndev, u32 retry, bool l) +{ + s32 err = 0; + u32 cmd = (l ? BRCMF_C_SET_LRL : BRCMF_C_SET_SRL); + + err = brcmf_fil_cmd_int_set(netdev_priv(ndev), cmd, retry); + if (err) { + brcmf_err("cmd (%d) , error (%d)\n", cmd, err); + return err; + } + return err; +} + +static s32 brcmf_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct net_device *ndev = cfg_to_ndev(cfg); + struct brcmf_if *ifp = netdev_priv(ndev); + s32 err = 0; + + brcmf_dbg(TRACE, "Enter\n"); + if (!check_vif_up(ifp->vif)) + return -EIO; + + if (changed & WIPHY_PARAM_RTS_THRESHOLD && + (cfg->conf->rts_threshold != wiphy->rts_threshold)) { + cfg->conf->rts_threshold = wiphy->rts_threshold; + err = brcmf_set_rts(ndev, cfg->conf->rts_threshold); + if (!err) + goto done; + } + if (changed & WIPHY_PARAM_FRAG_THRESHOLD && + (cfg->conf->frag_threshold != wiphy->frag_threshold)) { + cfg->conf->frag_threshold = wiphy->frag_threshold; + err = brcmf_set_frag(ndev, cfg->conf->frag_threshold); + if (!err) + goto done; + } + if (changed & WIPHY_PARAM_RETRY_LONG + && (cfg->conf->retry_long != wiphy->retry_long)) { + cfg->conf->retry_long = wiphy->retry_long; + err = brcmf_set_retry(ndev, cfg->conf->retry_long, true); + if (!err) + goto done; + } + if (changed & WIPHY_PARAM_RETRY_SHORT + && (cfg->conf->retry_short != wiphy->retry_short)) { + cfg->conf->retry_short = wiphy->retry_short; + err = brcmf_set_retry(ndev, cfg->conf->retry_short, false); + if (!err) + goto done; + } + +done: + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + +static void brcmf_init_prof(struct brcmf_cfg80211_profile *prof) +{ + memset(prof, 0, sizeof(*prof)); +} + +static void brcmf_link_down(struct brcmf_cfg80211_vif *vif) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(vif->wdev.wiphy); + s32 err = 0; + + brcmf_dbg(TRACE, "Enter\n"); + + if (test_bit(BRCMF_VIF_STATUS_CONNECTED, &vif->sme_state)) { + brcmf_dbg(INFO, "Call WLC_DISASSOC to stop excess roaming\n "); + err = brcmf_fil_cmd_data_set(vif->ifp, + BRCMF_C_DISASSOC, NULL, 0); + if (err) + brcmf_err("WLC_DISASSOC failed (%d)\n", err); + clear_bit(BRCMF_VIF_STATUS_CONNECTED, &vif->sme_state); + } + clear_bit(BRCMF_VIF_STATUS_CONNECTING, &vif->sme_state); + clear_bit(BRCMF_SCAN_STATUS_SUPPRESS, &cfg->scan_status); + brcmf_btcoex_set_mode(vif, BRCMF_BTCOEX_ENABLED, 0); + brcmf_dbg(TRACE, "Exit\n"); +} + +static s32 +brcmf_cfg80211_join_ibss(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_ibss_params *params) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; + struct brcmf_join_params join_params; + size_t join_params_size = 0; + s32 err = 0; + s32 wsec = 0; + s32 bcnprd; + u16 chanspec; + + brcmf_dbg(TRACE, "Enter\n"); + if (!check_vif_up(ifp->vif)) + return -EIO; + + if (params->ssid) + brcmf_dbg(CONN, "SSID: %s\n", params->ssid); + else { + brcmf_dbg(CONN, "SSID: NULL, Not supported\n"); + return -EOPNOTSUPP; + } + + set_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); + + if (params->bssid) + brcmf_dbg(CONN, "BSSID: %pM\n", params->bssid); + else + brcmf_dbg(CONN, "No BSSID specified\n"); + + if (params->chandef.chan) + brcmf_dbg(CONN, "channel: %d\n", + params->chandef.chan->center_freq); + else + brcmf_dbg(CONN, "no channel specified\n"); + + if (params->channel_fixed) + brcmf_dbg(CONN, "fixed channel required\n"); + else + brcmf_dbg(CONN, "no fixed channel required\n"); + + if (params->ie && params->ie_len) + brcmf_dbg(CONN, "ie len: %d\n", params->ie_len); + else + brcmf_dbg(CONN, "no ie specified\n"); + + if (params->beacon_interval) + brcmf_dbg(CONN, "beacon interval: %d\n", + params->beacon_interval); + else + brcmf_dbg(CONN, "no beacon interval specified\n"); + + if (params->basic_rates) + brcmf_dbg(CONN, "basic rates: %08X\n", params->basic_rates); + else + brcmf_dbg(CONN, "no basic rates specified\n"); + + if (params->privacy) + brcmf_dbg(CONN, "privacy required\n"); + else + brcmf_dbg(CONN, "no privacy required\n"); + + /* Configure Privacy for starter */ + if (params->privacy) + wsec |= WEP_ENABLED; + + err = brcmf_fil_iovar_int_set(ifp, "wsec", wsec); + if (err) { + brcmf_err("wsec failed (%d)\n", err); + goto done; + } + + /* Configure Beacon Interval for starter */ + if (params->beacon_interval) + bcnprd = params->beacon_interval; + else + bcnprd = 100; + + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_BCNPRD, bcnprd); + if (err) { + brcmf_err("WLC_SET_BCNPRD failed (%d)\n", err); + goto done; + } + + /* Configure required join parameter */ + memset(&join_params, 0, sizeof(struct brcmf_join_params)); + + /* SSID */ + profile->ssid.SSID_len = min_t(u32, params->ssid_len, 32); + memcpy(profile->ssid.SSID, params->ssid, profile->ssid.SSID_len); + memcpy(join_params.ssid_le.SSID, params->ssid, profile->ssid.SSID_len); + join_params.ssid_le.SSID_len = cpu_to_le32(profile->ssid.SSID_len); + join_params_size = sizeof(join_params.ssid_le); + + /* BSSID */ + if (params->bssid) { + memcpy(join_params.params_le.bssid, params->bssid, ETH_ALEN); + join_params_size = sizeof(join_params.ssid_le) + + BRCMF_ASSOC_PARAMS_FIXED_SIZE; + memcpy(profile->bssid, params->bssid, ETH_ALEN); + } else { + memset(join_params.params_le.bssid, 0xFF, ETH_ALEN); + memset(profile->bssid, 0, ETH_ALEN); + } + + /* Channel */ + if (params->chandef.chan) { + u32 target_channel; + + cfg->channel = + ieee80211_frequency_to_channel( + params->chandef.chan->center_freq); + if (params->channel_fixed) { + /* adding chanspec */ + chanspec = channel_to_chanspec(&cfg->d11inf, + params->chandef.chan); + join_params.params_le.chanspec_list[0] = + cpu_to_le16(chanspec); + join_params.params_le.chanspec_num = cpu_to_le32(1); + join_params_size += sizeof(join_params.params_le); + } + + /* set channel for starter */ + target_channel = cfg->channel; + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_CHANNEL, + target_channel); + if (err) { + brcmf_err("WLC_SET_CHANNEL failed (%d)\n", err); + goto done; + } + } else + cfg->channel = 0; + + cfg->ibss_starter = false; + + + err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID, + &join_params, join_params_size); + if (err) { + brcmf_err("WLC_SET_SSID failed (%d)\n", err); + goto done; + } + +done: + if (err) + clear_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_leave_ibss(struct wiphy *wiphy, struct net_device *ndev) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + s32 err = 0; + + brcmf_dbg(TRACE, "Enter\n"); + if (!check_vif_up(ifp->vif)) + return -EIO; + + brcmf_link_down(ifp->vif); + + brcmf_dbg(TRACE, "Exit\n"); + + return err; +} + +static s32 brcmf_set_wpa_version(struct net_device *ndev, + struct cfg80211_connect_params *sme) +{ + struct brcmf_cfg80211_profile *profile = ndev_to_prof(ndev); + struct brcmf_cfg80211_security *sec; + s32 val = 0; + s32 err = 0; + + if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_1) + val = WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED; + else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_2) + val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED; + else + val = WPA_AUTH_DISABLED; + brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val); + err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth", val); + if (err) { + brcmf_err("set wpa_auth failed (%d)\n", err); + return err; + } + sec = &profile->sec; + sec->wpa_versions = sme->crypto.wpa_versions; + return err; +} + +static s32 brcmf_set_auth_type(struct net_device *ndev, + struct cfg80211_connect_params *sme) +{ + struct brcmf_cfg80211_profile *profile = ndev_to_prof(ndev); + struct brcmf_cfg80211_security *sec; + s32 val = 0; + s32 err = 0; + + switch (sme->auth_type) { + case NL80211_AUTHTYPE_OPEN_SYSTEM: + val = 0; + brcmf_dbg(CONN, "open system\n"); + break; + case NL80211_AUTHTYPE_SHARED_KEY: + val = 1; + brcmf_dbg(CONN, "shared key\n"); + break; + case NL80211_AUTHTYPE_AUTOMATIC: + val = 2; + brcmf_dbg(CONN, "automatic\n"); + break; + case NL80211_AUTHTYPE_NETWORK_EAP: + brcmf_dbg(CONN, "network eap\n"); + default: + val = 2; + brcmf_err("invalid auth type (%d)\n", sme->auth_type); + break; + } + + err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "auth", val); + if (err) { + brcmf_err("set auth failed (%d)\n", err); + return err; + } + sec = &profile->sec; + sec->auth_type = sme->auth_type; + return err; +} + +static s32 +brcmf_set_set_cipher(struct net_device *ndev, + struct cfg80211_connect_params *sme) +{ + struct brcmf_cfg80211_profile *profile = ndev_to_prof(ndev); + struct brcmf_cfg80211_security *sec; + s32 pval = 0; + s32 gval = 0; + s32 err = 0; + + if (sme->crypto.n_ciphers_pairwise) { + switch (sme->crypto.ciphers_pairwise[0]) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + pval = WEP_ENABLED; + break; + case WLAN_CIPHER_SUITE_TKIP: + pval = TKIP_ENABLED; + break; + case WLAN_CIPHER_SUITE_CCMP: + pval = AES_ENABLED; + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + pval = AES_ENABLED; + break; + default: + brcmf_err("invalid cipher pairwise (%d)\n", + sme->crypto.ciphers_pairwise[0]); + return -EINVAL; + } + } + if (sme->crypto.cipher_group) { + switch (sme->crypto.cipher_group) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + gval = WEP_ENABLED; + break; + case WLAN_CIPHER_SUITE_TKIP: + gval = TKIP_ENABLED; + break; + case WLAN_CIPHER_SUITE_CCMP: + gval = AES_ENABLED; + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + gval = AES_ENABLED; + break; + default: + brcmf_err("invalid cipher group (%d)\n", + sme->crypto.cipher_group); + return -EINVAL; + } + } + + brcmf_dbg(CONN, "pval (%d) gval (%d)\n", pval, gval); + /* In case of privacy, but no security and WPS then simulate */ + /* setting AES. WPS-2.0 allows no security */ + if (brcmf_find_wpsie(sme->ie, sme->ie_len) && !pval && !gval && + sme->privacy) + pval = AES_ENABLED; + err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wsec", pval | gval); + if (err) { + brcmf_err("error (%d)\n", err); + return err; + } + + sec = &profile->sec; + sec->cipher_pairwise = sme->crypto.ciphers_pairwise[0]; + sec->cipher_group = sme->crypto.cipher_group; + + return err; +} + +static s32 +brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) +{ + struct brcmf_cfg80211_profile *profile = ndev_to_prof(ndev); + struct brcmf_cfg80211_security *sec; + s32 val = 0; + s32 err = 0; + + if (sme->crypto.n_akm_suites) { + err = brcmf_fil_bsscfg_int_get(netdev_priv(ndev), + "wpa_auth", &val); + if (err) { + brcmf_err("could not get wpa_auth (%d)\n", err); + return err; + } + if (val & (WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED)) { + switch (sme->crypto.akm_suites[0]) { + case WLAN_AKM_SUITE_8021X: + val = WPA_AUTH_UNSPECIFIED; + break; + case WLAN_AKM_SUITE_PSK: + val = WPA_AUTH_PSK; + break; + default: + brcmf_err("invalid cipher group (%d)\n", + sme->crypto.cipher_group); + return -EINVAL; + } + } else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED)) { + switch (sme->crypto.akm_suites[0]) { + case WLAN_AKM_SUITE_8021X: + val = WPA2_AUTH_UNSPECIFIED; + break; + case WLAN_AKM_SUITE_PSK: + val = WPA2_AUTH_PSK; + break; + default: + brcmf_err("invalid cipher group (%d)\n", + sme->crypto.cipher_group); + return -EINVAL; + } + } + + brcmf_dbg(CONN, "setting wpa_auth to %d\n", val); + err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), + "wpa_auth", val); + if (err) { + brcmf_err("could not set wpa_auth (%d)\n", err); + return err; + } + } + sec = &profile->sec; + sec->wpa_auth = sme->crypto.akm_suites[0]; + + return err; +} + +static s32 +brcmf_set_sharedkey(struct net_device *ndev, + struct cfg80211_connect_params *sme) +{ + struct brcmf_cfg80211_profile *profile = ndev_to_prof(ndev); + struct brcmf_cfg80211_security *sec; + struct brcmf_wsec_key key; + s32 val; + s32 err = 0; + + brcmf_dbg(CONN, "key len (%d)\n", sme->key_len); + + if (sme->key_len == 0) + return 0; + + sec = &profile->sec; + brcmf_dbg(CONN, "wpa_versions 0x%x cipher_pairwise 0x%x\n", + sec->wpa_versions, sec->cipher_pairwise); + + if (sec->wpa_versions & (NL80211_WPA_VERSION_1 | NL80211_WPA_VERSION_2)) + return 0; + + if (!(sec->cipher_pairwise & + (WLAN_CIPHER_SUITE_WEP40 | WLAN_CIPHER_SUITE_WEP104))) + return 0; + + memset(&key, 0, sizeof(key)); + key.len = (u32) sme->key_len; + key.index = (u32) sme->key_idx; + if (key.len > sizeof(key.data)) { + brcmf_err("Too long key length (%u)\n", key.len); + return -EINVAL; + } + memcpy(key.data, sme->key, key.len); + key.flags = BRCMF_PRIMARY_KEY; + switch (sec->cipher_pairwise) { + case WLAN_CIPHER_SUITE_WEP40: + key.algo = CRYPTO_ALGO_WEP1; + break; + case WLAN_CIPHER_SUITE_WEP104: + key.algo = CRYPTO_ALGO_WEP128; + break; + default: + brcmf_err("Invalid algorithm (%d)\n", + sme->crypto.ciphers_pairwise[0]); + return -EINVAL; + } + /* Set the new key/index */ + brcmf_dbg(CONN, "key length (%d) key index (%d) algo (%d)\n", + key.len, key.index, key.algo); + brcmf_dbg(CONN, "key \"%s\"\n", key.data); + err = send_key_to_dongle(ndev, &key); + if (err) + return err; + + if (sec->auth_type == NL80211_AUTHTYPE_SHARED_KEY) { + brcmf_dbg(CONN, "set auth_type to shared key\n"); + val = WL_AUTH_SHARED_KEY; /* shared key */ + err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "auth", val); + if (err) + brcmf_err("set auth failed (%d)\n", err); + } + return err; +} + +static +enum nl80211_auth_type brcmf_war_auth_type(struct brcmf_if *ifp, + enum nl80211_auth_type type) +{ + u32 ci; + if (type == NL80211_AUTHTYPE_AUTOMATIC) { + /* shift to ignore chip revision */ + ci = brcmf_get_chip_info(ifp) >> 4; + switch (ci) { + case 43236: + brcmf_dbg(CONN, "43236 WAR: use OPEN instead of AUTO\n"); + return NL80211_AUTHTYPE_OPEN_SYSTEM; + default: + break; + } + } + return type; +} + +static s32 +brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_connect_params *sme) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; + struct ieee80211_channel *chan = sme->channel; + struct brcmf_join_params join_params; + size_t join_params_size; + struct brcmf_tlv *rsn_ie; + struct brcmf_vs_tlv *wpa_ie; + void *ie; + u32 ie_len; + struct brcmf_ext_join_params_le *ext_join_params; + u16 chanspec; + + s32 err = 0; + + brcmf_dbg(TRACE, "Enter\n"); + if (!check_vif_up(ifp->vif)) + return -EIO; + + if (!sme->ssid) { + brcmf_err("Invalid ssid\n"); + return -EOPNOTSUPP; + } + + if (ifp->vif == cfg->p2p.bss_idx[P2PAPI_BSSCFG_PRIMARY].vif) { + /* A normal (non P2P) connection request setup. */ + ie = NULL; + ie_len = 0; + /* find the WPA_IE */ + wpa_ie = brcmf_find_wpaie((u8 *)sme->ie, sme->ie_len); + if (wpa_ie) { + ie = wpa_ie; + ie_len = wpa_ie->len + TLV_HDR_LEN; + } else { + /* find the RSN_IE */ + rsn_ie = brcmf_parse_tlvs((u8 *)sme->ie, sme->ie_len, + WLAN_EID_RSN); + if (rsn_ie) { + ie = rsn_ie; + ie_len = rsn_ie->len + TLV_HDR_LEN; + } + } + brcmf_fil_iovar_data_set(ifp, "wpaie", ie, ie_len); + } + + err = brcmf_vif_set_mgmt_ie(ifp->vif, BRCMF_VNDR_IE_ASSOCREQ_FLAG, + sme->ie, sme->ie_len); + if (err) + brcmf_err("Set Assoc REQ IE Failed\n"); + else + brcmf_dbg(TRACE, "Applied Vndr IEs for Assoc request\n"); + + set_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); + + if (chan) { + cfg->channel = + ieee80211_frequency_to_channel(chan->center_freq); + chanspec = channel_to_chanspec(&cfg->d11inf, chan); + brcmf_dbg(CONN, "channel=%d, center_req=%d, chanspec=0x%04x\n", + cfg->channel, chan->center_freq, chanspec); + } else { + cfg->channel = 0; + chanspec = 0; + } + + brcmf_dbg(INFO, "ie (%p), ie_len (%zd)\n", sme->ie, sme->ie_len); + + err = brcmf_set_wpa_version(ndev, sme); + if (err) { + brcmf_err("wl_set_wpa_version failed (%d)\n", err); + goto done; + } + + sme->auth_type = brcmf_war_auth_type(ifp, sme->auth_type); + err = brcmf_set_auth_type(ndev, sme); + if (err) { + brcmf_err("wl_set_auth_type failed (%d)\n", err); + goto done; + } + + err = brcmf_set_set_cipher(ndev, sme); + if (err) { + brcmf_err("wl_set_set_cipher failed (%d)\n", err); + goto done; + } + + err = brcmf_set_key_mgmt(ndev, sme); + if (err) { + brcmf_err("wl_set_key_mgmt failed (%d)\n", err); + goto done; + } + + err = brcmf_set_sharedkey(ndev, sme); + if (err) { + brcmf_err("brcmf_set_sharedkey failed (%d)\n", err); + goto done; + } + + profile->ssid.SSID_len = min_t(u32, (u32)sizeof(profile->ssid.SSID), + (u32)sme->ssid_len); + memcpy(&profile->ssid.SSID, sme->ssid, profile->ssid.SSID_len); + if (profile->ssid.SSID_len < IEEE80211_MAX_SSID_LEN) { + profile->ssid.SSID[profile->ssid.SSID_len] = 0; + brcmf_dbg(CONN, "SSID \"%s\", len (%d)\n", profile->ssid.SSID, + profile->ssid.SSID_len); + } + + /* Join with specific BSSID and cached SSID + * If SSID is zero join based on BSSID only + */ + join_params_size = offsetof(struct brcmf_ext_join_params_le, assoc_le) + + offsetof(struct brcmf_assoc_params_le, chanspec_list); + if (cfg->channel) + join_params_size += sizeof(u16); + ext_join_params = kzalloc(join_params_size, GFP_KERNEL); + if (ext_join_params == NULL) { + err = -ENOMEM; + goto done; + } + ext_join_params->ssid_le.SSID_len = cpu_to_le32(profile->ssid.SSID_len); + memcpy(&ext_join_params->ssid_le.SSID, sme->ssid, + profile->ssid.SSID_len); + /*increase dwell time to receive probe response or detect Beacon + * from target AP at a noisy air only during connect command + */ + ext_join_params->scan_le.active_time = + cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS); + ext_join_params->scan_le.passive_time = + cpu_to_le32(BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS); + /* Set up join scan parameters */ + ext_join_params->scan_le.scan_type = -1; + /* to sync with presence period of VSDB GO. + * Send probe request more frequently. Probe request will be stopped + * when it gets probe response from target AP/GO. + */ + ext_join_params->scan_le.nprobes = + cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS / + BRCMF_SCAN_JOIN_PROBE_INTERVAL_MS); + ext_join_params->scan_le.home_time = cpu_to_le32(-1); + + if (sme->bssid) + memcpy(&ext_join_params->assoc_le.bssid, sme->bssid, ETH_ALEN); + else + memset(&ext_join_params->assoc_le.bssid, 0xFF, ETH_ALEN); + + if (cfg->channel) { + ext_join_params->assoc_le.chanspec_num = cpu_to_le32(1); + + ext_join_params->assoc_le.chanspec_list[0] = + cpu_to_le16(chanspec); + } + + err = brcmf_fil_bsscfg_data_set(ifp, "join", ext_join_params, + join_params_size); + kfree(ext_join_params); + if (!err) + /* This is it. join command worked, we are done */ + goto done; + + /* join command failed, fallback to set ssid */ + memset(&join_params, 0, sizeof(join_params)); + join_params_size = sizeof(join_params.ssid_le); + + memcpy(&join_params.ssid_le.SSID, sme->ssid, profile->ssid.SSID_len); + join_params.ssid_le.SSID_len = cpu_to_le32(profile->ssid.SSID_len); + + if (sme->bssid) + memcpy(join_params.params_le.bssid, sme->bssid, ETH_ALEN); + else + memset(join_params.params_le.bssid, 0xFF, ETH_ALEN); + + if (cfg->channel) { + join_params.params_le.chanspec_list[0] = cpu_to_le16(chanspec); + join_params.params_le.chanspec_num = cpu_to_le32(1); + join_params_size += sizeof(join_params.params_le); + } + err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID, + &join_params, join_params_size); + if (err) + brcmf_err("BRCMF_C_SET_SSID failed (%d)\n", err); + +done: + if (err) + clear_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state); + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *ndev, + u16 reason_code) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; + struct brcmf_scb_val_le scbval; + s32 err = 0; + + brcmf_dbg(TRACE, "Enter. Reason code = %d\n", reason_code); + if (!check_vif_up(ifp->vif)) + return -EIO; + + clear_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state); + + memcpy(&scbval.ea, &profile->bssid, ETH_ALEN); + scbval.val = cpu_to_le32(reason_code); + err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_DISASSOC, + &scbval, sizeof(scbval)); + if (err) + brcmf_err("error (%d)\n", err); + + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, + enum nl80211_tx_power_setting type, s32 mbm) +{ + + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct net_device *ndev = cfg_to_ndev(cfg); + struct brcmf_if *ifp = netdev_priv(ndev); + u16 txpwrmw; + s32 err = 0; + s32 disable = 0; + s32 dbm = MBM_TO_DBM(mbm); + + brcmf_dbg(TRACE, "Enter\n"); + if (!check_vif_up(ifp->vif)) + return -EIO; + + switch (type) { + case NL80211_TX_POWER_AUTOMATIC: + break; + case NL80211_TX_POWER_LIMITED: + case NL80211_TX_POWER_FIXED: + if (dbm < 0) { + brcmf_err("TX_POWER_FIXED - dbm is negative\n"); + err = -EINVAL; + goto done; + } + break; + } + /* Make sure radio is off or on as far as software is concerned */ + disable = WL_RADIO_SW_DISABLE << 16; + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_RADIO, disable); + if (err) + brcmf_err("WLC_SET_RADIO error (%d)\n", err); + + if (dbm > 0xffff) + txpwrmw = 0xffff; + else + txpwrmw = (u16) dbm; + err = brcmf_fil_iovar_int_set(ifp, "qtxpower", + (s32)brcmf_mw_to_qdbm(txpwrmw)); + if (err) + brcmf_err("qtxpower error (%d)\n", err); + cfg->conf->tx_power = dbm; + +done: + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 brcmf_cfg80211_get_tx_power(struct wiphy *wiphy, + struct wireless_dev *wdev, + s32 *dbm) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg)); + s32 txpwrdbm; + u8 result; + s32 err = 0; + + brcmf_dbg(TRACE, "Enter\n"); + if (!check_vif_up(ifp->vif)) + return -EIO; + + err = brcmf_fil_iovar_int_get(ifp, "qtxpower", &txpwrdbm); + if (err) { + brcmf_err("error (%d)\n", err); + goto done; + } + + result = (u8) (txpwrdbm & ~WL_TXPWR_OVERRIDE); + *dbm = (s32) brcmf_qdbm_to_mw(result); + +done: + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_config_default_key(struct wiphy *wiphy, struct net_device *ndev, + u8 key_idx, bool unicast, bool multicast) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + u32 index; + u32 wsec; + s32 err = 0; + + brcmf_dbg(TRACE, "Enter\n"); + brcmf_dbg(CONN, "key index (%d)\n", key_idx); + if (!check_vif_up(ifp->vif)) + return -EIO; + + err = brcmf_fil_bsscfg_int_get(ifp, "wsec", &wsec); + if (err) { + brcmf_err("WLC_GET_WSEC error (%d)\n", err); + goto done; + } + + if (wsec & WEP_ENABLED) { + /* Just select a new current key */ + index = key_idx; + err = brcmf_fil_cmd_int_set(ifp, + BRCMF_C_SET_KEY_PRIMARY, index); + if (err) + brcmf_err("error (%d)\n", err); + } +done: + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +brcmf_add_keyext(struct wiphy *wiphy, struct net_device *ndev, + u8 key_idx, const u8 *mac_addr, struct key_params *params) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_wsec_key key; + s32 err = 0; + u8 keybuf[8]; + + memset(&key, 0, sizeof(key)); + key.index = (u32) key_idx; + /* Instead of bcast for ea address for default wep keys, + driver needs it to be Null */ + if (!is_multicast_ether_addr(mac_addr)) + memcpy((char *)&key.ea, (void *)mac_addr, ETH_ALEN); + key.len = (u32) params->key_len; + /* check for key index change */ + if (key.len == 0) { + /* key delete */ + err = send_key_to_dongle(ndev, &key); + if (err) + brcmf_err("key delete error (%d)\n", err); + } else { + if (key.len > sizeof(key.data)) { + brcmf_err("Invalid key length (%d)\n", key.len); + return -EINVAL; + } + + brcmf_dbg(CONN, "Setting the key index %d\n", key.index); + memcpy(key.data, params->key, key.len); + + if ((ifp->vif->mode != WL_MODE_AP) && + (params->cipher == WLAN_CIPHER_SUITE_TKIP)) { + brcmf_dbg(CONN, "Swapping RX/TX MIC key\n"); + memcpy(keybuf, &key.data[24], sizeof(keybuf)); + memcpy(&key.data[24], &key.data[16], sizeof(keybuf)); + memcpy(&key.data[16], keybuf, sizeof(keybuf)); + } + + /* if IW_ENCODE_EXT_RX_SEQ_VALID set */ + if (params->seq && params->seq_len == 6) { + /* rx iv */ + u8 *ivptr; + ivptr = (u8 *) params->seq; + key.rxiv.hi = (ivptr[5] << 24) | (ivptr[4] << 16) | + (ivptr[3] << 8) | ivptr[2]; + key.rxiv.lo = (ivptr[1] << 8) | ivptr[0]; + key.iv_initialized = true; + } + + switch (params->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + key.algo = CRYPTO_ALGO_WEP1; + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_WEP40\n"); + break; + case WLAN_CIPHER_SUITE_WEP104: + key.algo = CRYPTO_ALGO_WEP128; + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_WEP104\n"); + break; + case WLAN_CIPHER_SUITE_TKIP: + key.algo = CRYPTO_ALGO_TKIP; + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_TKIP\n"); + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + key.algo = CRYPTO_ALGO_AES_CCM; + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_AES_CMAC\n"); + break; + case WLAN_CIPHER_SUITE_CCMP: + key.algo = CRYPTO_ALGO_AES_CCM; + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_CCMP\n"); + break; + default: + brcmf_err("Invalid cipher (0x%x)\n", params->cipher); + return -EINVAL; + } + err = send_key_to_dongle(ndev, &key); + if (err) + brcmf_err("wsec_key error (%d)\n", err); + } + return err; +} + +static s32 +brcmf_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev, + u8 key_idx, bool pairwise, const u8 *mac_addr, + struct key_params *params) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_wsec_key key; + s32 val; + s32 wsec; + s32 err = 0; + u8 keybuf[8]; + + brcmf_dbg(TRACE, "Enter\n"); + brcmf_dbg(CONN, "key index (%d)\n", key_idx); + if (!check_vif_up(ifp->vif)) + return -EIO; + + if (mac_addr) { + brcmf_dbg(TRACE, "Exit"); + return brcmf_add_keyext(wiphy, ndev, key_idx, mac_addr, params); + } + memset(&key, 0, sizeof(key)); + + key.len = (u32) params->key_len; + key.index = (u32) key_idx; + + if (key.len > sizeof(key.data)) { + brcmf_err("Too long key length (%u)\n", key.len); + err = -EINVAL; + goto done; + } + memcpy(key.data, params->key, key.len); + + key.flags = BRCMF_PRIMARY_KEY; + switch (params->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + key.algo = CRYPTO_ALGO_WEP1; + val = WEP_ENABLED; + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_WEP40\n"); + break; + case WLAN_CIPHER_SUITE_WEP104: + key.algo = CRYPTO_ALGO_WEP128; + val = WEP_ENABLED; + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_WEP104\n"); + break; + case WLAN_CIPHER_SUITE_TKIP: + if (ifp->vif->mode != WL_MODE_AP) { + brcmf_dbg(CONN, "Swapping RX/TX MIC key\n"); + memcpy(keybuf, &key.data[24], sizeof(keybuf)); + memcpy(&key.data[24], &key.data[16], sizeof(keybuf)); + memcpy(&key.data[16], keybuf, sizeof(keybuf)); + } + key.algo = CRYPTO_ALGO_TKIP; + val = TKIP_ENABLED; + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_TKIP\n"); + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + key.algo = CRYPTO_ALGO_AES_CCM; + val = AES_ENABLED; + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_AES_CMAC\n"); + break; + case WLAN_CIPHER_SUITE_CCMP: + key.algo = CRYPTO_ALGO_AES_CCM; + val = AES_ENABLED; + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_CCMP\n"); + break; + default: + brcmf_err("Invalid cipher (0x%x)\n", params->cipher); + err = -EINVAL; + goto done; + } + + err = send_key_to_dongle(ndev, &key); + if (err) + goto done; + + err = brcmf_fil_bsscfg_int_get(ifp, "wsec", &wsec); + if (err) { + brcmf_err("get wsec error (%d)\n", err); + goto done; + } + wsec |= val; + err = brcmf_fil_bsscfg_int_set(ifp, "wsec", wsec); + if (err) { + brcmf_err("set wsec error (%d)\n", err); + goto done; + } + +done: + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_del_key(struct wiphy *wiphy, struct net_device *ndev, + u8 key_idx, bool pairwise, const u8 *mac_addr) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_wsec_key key; + s32 err = 0; + + brcmf_dbg(TRACE, "Enter\n"); + if (!check_vif_up(ifp->vif)) + return -EIO; + + if (key_idx >= DOT11_MAX_DEFAULT_KEYS) { + /* we ignore this key index in this case */ + brcmf_err("invalid key index (%d)\n", key_idx); + return -EINVAL; + } + + memset(&key, 0, sizeof(key)); + + key.index = (u32) key_idx; + key.flags = BRCMF_PRIMARY_KEY; + key.algo = CRYPTO_ALGO_OFF; + + brcmf_dbg(CONN, "key index (%d)\n", key_idx); + + /* Set the new key/index */ + err = send_key_to_dongle(ndev, &key); + + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_get_key(struct wiphy *wiphy, struct net_device *ndev, + u8 key_idx, bool pairwise, const u8 *mac_addr, void *cookie, + void (*callback) (void *cookie, struct key_params * params)) +{ + struct key_params params; + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; + struct brcmf_cfg80211_security *sec; + s32 wsec; + s32 err = 0; + + brcmf_dbg(TRACE, "Enter\n"); + brcmf_dbg(CONN, "key index (%d)\n", key_idx); + if (!check_vif_up(ifp->vif)) + return -EIO; + + memset(¶ms, 0, sizeof(params)); + + err = brcmf_fil_bsscfg_int_get(ifp, "wsec", &wsec); + if (err) { + brcmf_err("WLC_GET_WSEC error (%d)\n", err); + /* Ignore this error, may happen during DISASSOC */ + err = -EAGAIN; + goto done; + } + if (wsec & WEP_ENABLED) { + sec = &profile->sec; + if (sec->cipher_pairwise & WLAN_CIPHER_SUITE_WEP40) { + params.cipher = WLAN_CIPHER_SUITE_WEP40; + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_WEP40\n"); + } else if (sec->cipher_pairwise & WLAN_CIPHER_SUITE_WEP104) { + params.cipher = WLAN_CIPHER_SUITE_WEP104; + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_WEP104\n"); + } + } else if (wsec & TKIP_ENABLED) { + params.cipher = WLAN_CIPHER_SUITE_TKIP; + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_TKIP\n"); + } else if (wsec & AES_ENABLED) { + params.cipher = WLAN_CIPHER_SUITE_AES_CMAC; + brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_AES_CMAC\n"); + } else { + brcmf_err("Invalid algo (0x%x)\n", wsec); + err = -EINVAL; + goto done; + } + callback(cookie, ¶ms); + +done: + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_config_default_mgmt_key(struct wiphy *wiphy, + struct net_device *ndev, u8 key_idx) +{ + brcmf_dbg(INFO, "Not supported\n"); + + return -EOPNOTSUPP; +} + +static s32 +brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev, + u8 *mac, struct station_info *sinfo) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; + struct brcmf_scb_val_le scb_val; + int rssi; + s32 rate; + s32 err = 0; + u8 *bssid = profile->bssid; + struct brcmf_sta_info_le sta_info_le; + + brcmf_dbg(TRACE, "Enter, MAC %pM\n", mac); + if (!check_vif_up(ifp->vif)) + return -EIO; + + if (ifp->vif->mode == WL_MODE_AP) { + memcpy(&sta_info_le, mac, ETH_ALEN); + err = brcmf_fil_iovar_data_get(ifp, "sta_info", + &sta_info_le, + sizeof(sta_info_le)); + if (err < 0) { + brcmf_err("GET STA INFO failed, %d\n", err); + goto done; + } + sinfo->filled = STATION_INFO_INACTIVE_TIME; + sinfo->inactive_time = le32_to_cpu(sta_info_le.idle) * 1000; + if (le32_to_cpu(sta_info_le.flags) & BRCMF_STA_ASSOC) { + sinfo->filled |= STATION_INFO_CONNECTED_TIME; + sinfo->connected_time = le32_to_cpu(sta_info_le.in); + } + brcmf_dbg(TRACE, "STA idle time : %d ms, connected time :%d sec\n", + sinfo->inactive_time, sinfo->connected_time); + } else if (ifp->vif->mode == WL_MODE_BSS) { + if (memcmp(mac, bssid, ETH_ALEN)) { + brcmf_err("Wrong Mac address cfg_mac-%pM wl_bssid-%pM\n", + mac, bssid); + err = -ENOENT; + goto done; + } + /* Report the current tx rate */ + err = brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_RATE, &rate); + if (err) { + brcmf_err("Could not get rate (%d)\n", err); + goto done; + } else { + sinfo->filled |= STATION_INFO_TX_BITRATE; + sinfo->txrate.legacy = rate * 5; + brcmf_dbg(CONN, "Rate %d Mbps\n", rate / 2); + } + + if (test_bit(BRCMF_VIF_STATUS_CONNECTED, + &ifp->vif->sme_state)) { + memset(&scb_val, 0, sizeof(scb_val)); + err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_RSSI, + &scb_val, sizeof(scb_val)); + if (err) { + brcmf_err("Could not get rssi (%d)\n", err); + goto done; + } else { + rssi = le32_to_cpu(scb_val.val); + sinfo->filled |= STATION_INFO_SIGNAL; + sinfo->signal = rssi; + brcmf_dbg(CONN, "RSSI %d dBm\n", rssi); + } + } + } else + err = -EPERM; +done: + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *ndev, + bool enabled, s32 timeout) +{ + s32 pm; + s32 err = 0; + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_if *ifp = netdev_priv(ndev); + + brcmf_dbg(TRACE, "Enter\n"); + + /* + * Powersave enable/disable request is coming from the + * cfg80211 even before the interface is up. In that + * scenario, driver will be storing the power save + * preference in cfg struct to apply this to + * FW later while initializing the dongle + */ + cfg->pwr_save = enabled; + if (!check_vif_up(ifp->vif)) { + + brcmf_dbg(INFO, "Device is not ready, storing the value in cfg_info struct\n"); + goto done; + } + + pm = enabled ? PM_FAST : PM_OFF; + /* Do not enable the power save after assoc if it is a p2p interface */ + if (ifp->vif->wdev.iftype == NL80211_IFTYPE_P2P_CLIENT) { + brcmf_dbg(INFO, "Do not enable power save for P2P clients\n"); + pm = PM_OFF; + } + brcmf_dbg(INFO, "power save %s\n", (pm ? "enabled" : "disabled")); + + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PM, pm); + if (err) { + if (err == -ENODEV) + brcmf_err("net_device is not ready yet\n"); + else + brcmf_err("error (%d)\n", err); + } +done: + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 brcmf_inform_single_bss(struct brcmf_cfg80211_info *cfg, + struct brcmf_bss_info_le *bi) +{ + struct wiphy *wiphy = cfg_to_wiphy(cfg); + struct ieee80211_channel *notify_channel; + struct cfg80211_bss *bss; + struct ieee80211_supported_band *band; + struct brcmu_chan ch; + s32 err = 0; + u16 channel; + u32 freq; + u16 notify_capability; + u16 notify_interval; + u8 *notify_ie; + size_t notify_ielen; + s32 notify_signal; + + if (le32_to_cpu(bi->length) > WL_BSS_INFO_MAX) { + brcmf_err("Bss info is larger than buffer. Discarding\n"); + return 0; + } + + if (!bi->ctl_ch) { + ch.chspec = le16_to_cpu(bi->chanspec); + cfg->d11inf.decchspec(&ch); + bi->ctl_ch = ch.chnum; + } + channel = bi->ctl_ch; + + if (channel <= CH_MAX_2G_CHANNEL) + band = wiphy->bands[IEEE80211_BAND_2GHZ]; + else + band = wiphy->bands[IEEE80211_BAND_5GHZ]; + + freq = ieee80211_channel_to_frequency(channel, band->band); + notify_channel = ieee80211_get_channel(wiphy, freq); + + notify_capability = le16_to_cpu(bi->capability); + notify_interval = le16_to_cpu(bi->beacon_period); + notify_ie = (u8 *)bi + le16_to_cpu(bi->ie_offset); + notify_ielen = le32_to_cpu(bi->ie_length); + notify_signal = (s16)le16_to_cpu(bi->RSSI) * 100; + + brcmf_dbg(CONN, "bssid: %pM\n", bi->BSSID); + brcmf_dbg(CONN, "Channel: %d(%d)\n", channel, freq); + brcmf_dbg(CONN, "Capability: %X\n", notify_capability); + brcmf_dbg(CONN, "Beacon interval: %d\n", notify_interval); + brcmf_dbg(CONN, "Signal: %d\n", notify_signal); + + bss = cfg80211_inform_bss(wiphy, notify_channel, (const u8 *)bi->BSSID, + 0, notify_capability, notify_interval, notify_ie, + notify_ielen, notify_signal, GFP_KERNEL); + + if (!bss) + return -ENOMEM; + + cfg80211_put_bss(wiphy, bss); + + return err; +} + +static struct brcmf_bss_info_le * +next_bss_le(struct brcmf_scan_results *list, struct brcmf_bss_info_le *bss) +{ + if (bss == NULL) + return list->bss_info_le; + return (struct brcmf_bss_info_le *)((unsigned long)bss + + le32_to_cpu(bss->length)); +} + +static s32 brcmf_inform_bss(struct brcmf_cfg80211_info *cfg) +{ + struct brcmf_scan_results *bss_list; + struct brcmf_bss_info_le *bi = NULL; /* must be initialized */ + s32 err = 0; + int i; + + bss_list = cfg->bss_list; + if (bss_list->count != 0 && + bss_list->version != BRCMF_BSS_INFO_VERSION) { + brcmf_err("Version %d != WL_BSS_INFO_VERSION\n", + bss_list->version); + return -EOPNOTSUPP; + } + brcmf_dbg(SCAN, "scanned AP count (%d)\n", bss_list->count); + for (i = 0; i < bss_list->count; i++) { + bi = next_bss_le(bss_list, bi); + err = brcmf_inform_single_bss(cfg, bi); + if (err) + break; + } + return err; +} + +static s32 wl_inform_ibss(struct brcmf_cfg80211_info *cfg, + struct net_device *ndev, const u8 *bssid) +{ + struct wiphy *wiphy = cfg_to_wiphy(cfg); + struct ieee80211_channel *notify_channel; + struct brcmf_bss_info_le *bi = NULL; + struct ieee80211_supported_band *band; + struct cfg80211_bss *bss; + struct brcmu_chan ch; + u8 *buf = NULL; + s32 err = 0; + u32 freq; + u16 notify_capability; + u16 notify_interval; + u8 *notify_ie; + size_t notify_ielen; + s32 notify_signal; + + brcmf_dbg(TRACE, "Enter\n"); + + buf = kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL); + if (buf == NULL) { + err = -ENOMEM; + goto CleanUp; + } + + *(__le32 *)buf = cpu_to_le32(WL_BSS_INFO_MAX); + + err = brcmf_fil_cmd_data_get(netdev_priv(ndev), BRCMF_C_GET_BSS_INFO, + buf, WL_BSS_INFO_MAX); + if (err) { + brcmf_err("WLC_GET_BSS_INFO failed: %d\n", err); + goto CleanUp; + } + + bi = (struct brcmf_bss_info_le *)(buf + 4); + + ch.chspec = le16_to_cpu(bi->chanspec); + cfg->d11inf.decchspec(&ch); + + if (ch.band == BRCMU_CHAN_BAND_2G) + band = wiphy->bands[IEEE80211_BAND_2GHZ]; + else + band = wiphy->bands[IEEE80211_BAND_5GHZ]; + + freq = ieee80211_channel_to_frequency(ch.chnum, band->band); + notify_channel = ieee80211_get_channel(wiphy, freq); + + notify_capability = le16_to_cpu(bi->capability); + notify_interval = le16_to_cpu(bi->beacon_period); + notify_ie = (u8 *)bi + le16_to_cpu(bi->ie_offset); + notify_ielen = le32_to_cpu(bi->ie_length); + notify_signal = (s16)le16_to_cpu(bi->RSSI) * 100; + + brcmf_dbg(CONN, "channel: %d(%d)\n", ch.chnum, freq); + brcmf_dbg(CONN, "capability: %X\n", notify_capability); + brcmf_dbg(CONN, "beacon interval: %d\n", notify_interval); + brcmf_dbg(CONN, "signal: %d\n", notify_signal); + + bss = cfg80211_inform_bss(wiphy, notify_channel, bssid, + 0, notify_capability, notify_interval, + notify_ie, notify_ielen, notify_signal, GFP_KERNEL); + + if (!bss) { + err = -ENOMEM; + goto CleanUp; + } + + cfg80211_put_bss(wiphy, bss); + +CleanUp: + + kfree(buf); + + brcmf_dbg(TRACE, "Exit\n"); + + return err; +} + +static bool brcmf_is_ibssmode(struct brcmf_cfg80211_vif *vif) +{ + return vif->mode == WL_MODE_IBSS; +} + +static s32 brcmf_update_bss_info(struct brcmf_cfg80211_info *cfg, + struct brcmf_if *ifp) +{ + struct brcmf_cfg80211_profile *profile = ndev_to_prof(ifp->ndev); + struct brcmf_bss_info_le *bi; + struct brcmf_ssid *ssid; + struct brcmf_tlv *tim; + u16 beacon_interval; + u8 dtim_period; + size_t ie_len; + u8 *ie; + s32 err = 0; + + brcmf_dbg(TRACE, "Enter\n"); + if (brcmf_is_ibssmode(ifp->vif)) + return err; + + ssid = &profile->ssid; + + *(__le32 *)cfg->extra_buf = cpu_to_le32(WL_EXTRA_BUF_MAX); + err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BSS_INFO, + cfg->extra_buf, WL_EXTRA_BUF_MAX); + if (err) { + brcmf_err("Could not get bss info %d\n", err); + goto update_bss_info_out; + } + + bi = (struct brcmf_bss_info_le *)(cfg->extra_buf + 4); + err = brcmf_inform_single_bss(cfg, bi); + if (err) + goto update_bss_info_out; + + ie = ((u8 *)bi) + le16_to_cpu(bi->ie_offset); + ie_len = le32_to_cpu(bi->ie_length); + beacon_interval = le16_to_cpu(bi->beacon_period); + + tim = brcmf_parse_tlvs(ie, ie_len, WLAN_EID_TIM); + if (tim) + dtim_period = tim->data[1]; + else { + /* + * active scan was done so we could not get dtim + * information out of probe response. + * so we speficially query dtim information to dongle. + */ + u32 var; + err = brcmf_fil_iovar_int_get(ifp, "dtim_assoc", &var); + if (err) { + brcmf_err("wl dtim_assoc failed (%d)\n", err); + goto update_bss_info_out; + } + dtim_period = (u8)var; + } + +update_bss_info_out: + brcmf_dbg(TRACE, "Exit"); + return err; +} + +void brcmf_abort_scanning(struct brcmf_cfg80211_info *cfg) +{ + struct escan_info *escan = &cfg->escan_info; + + set_bit(BRCMF_SCAN_STATUS_ABORT, &cfg->scan_status); + if (cfg->scan_request) { + escan->escan_state = WL_ESCAN_STATE_IDLE; + brcmf_notify_escan_complete(cfg, escan->ifp, true, true); + } + clear_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status); + clear_bit(BRCMF_SCAN_STATUS_ABORT, &cfg->scan_status); +} + +static void brcmf_cfg80211_escan_timeout_worker(struct work_struct *work) +{ + struct brcmf_cfg80211_info *cfg = + container_of(work, struct brcmf_cfg80211_info, + escan_timeout_work); + + brcmf_notify_escan_complete(cfg, cfg->escan_info.ifp, true, true); +} + +static void brcmf_escan_timeout(unsigned long data) +{ + struct brcmf_cfg80211_info *cfg = + (struct brcmf_cfg80211_info *)data; + + if (cfg->scan_request) { + brcmf_err("timer expired\n"); + schedule_work(&cfg->escan_timeout_work); + } +} + +static s32 +brcmf_compare_update_same_bss(struct brcmf_cfg80211_info *cfg, + struct brcmf_bss_info_le *bss, + struct brcmf_bss_info_le *bss_info_le) +{ + struct brcmu_chan ch_bss, ch_bss_info_le; + + ch_bss.chspec = le16_to_cpu(bss->chanspec); + cfg->d11inf.decchspec(&ch_bss); + ch_bss_info_le.chspec = le16_to_cpu(bss_info_le->chanspec); + cfg->d11inf.decchspec(&ch_bss_info_le); + + if (!memcmp(&bss_info_le->BSSID, &bss->BSSID, ETH_ALEN) && + ch_bss.band == ch_bss_info_le.band && + bss_info_le->SSID_len == bss->SSID_len && + !memcmp(bss_info_le->SSID, bss->SSID, bss_info_le->SSID_len)) { + if ((bss->flags & WLC_BSS_RSSI_ON_CHANNEL) == + (bss_info_le->flags & WLC_BSS_RSSI_ON_CHANNEL)) { + s16 bss_rssi = le16_to_cpu(bss->RSSI); + s16 bss_info_rssi = le16_to_cpu(bss_info_le->RSSI); + + /* preserve max RSSI if the measurements are + * both on-channel or both off-channel + */ + if (bss_info_rssi > bss_rssi) + bss->RSSI = bss_info_le->RSSI; + } else if ((bss->flags & WLC_BSS_RSSI_ON_CHANNEL) && + (bss_info_le->flags & WLC_BSS_RSSI_ON_CHANNEL) == 0) { + /* preserve the on-channel rssi measurement + * if the new measurement is off channel + */ + bss->RSSI = bss_info_le->RSSI; + bss->flags |= WLC_BSS_RSSI_ON_CHANNEL; + } + return 1; + } + return 0; +} + +static s32 +brcmf_cfg80211_escan_handler(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, void *data) +{ + struct brcmf_cfg80211_info *cfg = ifp->drvr->config; + s32 status; + s32 err = 0; + struct brcmf_escan_result_le *escan_result_le; + struct brcmf_bss_info_le *bss_info_le; + struct brcmf_bss_info_le *bss = NULL; + u32 bi_length; + struct brcmf_scan_results *list; + u32 i; + bool aborted; + + status = e->status; + + if (!test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status)) { + brcmf_err("scan not ready, bssidx=%d\n", ifp->bssidx); + return -EPERM; + } + + if (status == BRCMF_E_STATUS_PARTIAL) { + brcmf_dbg(SCAN, "ESCAN Partial result\n"); + escan_result_le = (struct brcmf_escan_result_le *) data; + if (!escan_result_le) { + brcmf_err("Invalid escan result (NULL pointer)\n"); + goto exit; + } + if (le16_to_cpu(escan_result_le->bss_count) != 1) { + brcmf_err("Invalid bss_count %d: ignoring\n", + escan_result_le->bss_count); + goto exit; + } + bss_info_le = &escan_result_le->bss_info_le; + + if (brcmf_p2p_scan_finding_common_channel(cfg, bss_info_le)) + goto exit; + + if (!cfg->scan_request) { + brcmf_dbg(SCAN, "result without cfg80211 request\n"); + goto exit; + } + + bi_length = le32_to_cpu(bss_info_le->length); + if (bi_length != (le32_to_cpu(escan_result_le->buflen) - + WL_ESCAN_RESULTS_FIXED_SIZE)) { + brcmf_err("Invalid bss_info length %d: ignoring\n", + bi_length); + goto exit; + } + + if (!(cfg_to_wiphy(cfg)->interface_modes & + BIT(NL80211_IFTYPE_ADHOC))) { + if (le16_to_cpu(bss_info_le->capability) & + WLAN_CAPABILITY_IBSS) { + brcmf_err("Ignoring IBSS result\n"); + goto exit; + } + } + + list = (struct brcmf_scan_results *) + cfg->escan_info.escan_buf; + if (bi_length > WL_ESCAN_BUF_SIZE - list->buflen) { + brcmf_err("Buffer is too small: ignoring\n"); + goto exit; + } + + for (i = 0; i < list->count; i++) { + bss = bss ? (struct brcmf_bss_info_le *) + ((unsigned char *)bss + + le32_to_cpu(bss->length)) : list->bss_info_le; + if (brcmf_compare_update_same_bss(cfg, bss, + bss_info_le)) + goto exit; + } + memcpy(&(cfg->escan_info.escan_buf[list->buflen]), + bss_info_le, bi_length); + list->version = le32_to_cpu(bss_info_le->version); + list->buflen += bi_length; + list->count++; + } else { + cfg->escan_info.escan_state = WL_ESCAN_STATE_IDLE; + if (brcmf_p2p_scan_finding_common_channel(cfg, NULL)) + goto exit; + if (cfg->scan_request) { + cfg->bss_list = (struct brcmf_scan_results *) + cfg->escan_info.escan_buf; + brcmf_inform_bss(cfg); + aborted = status != BRCMF_E_STATUS_SUCCESS; + brcmf_notify_escan_complete(cfg, ifp, aborted, + false); + } else + brcmf_dbg(SCAN, "Ignored scan complete result 0x%x\n", + status); + } +exit: + return err; +} + +static void brcmf_init_escan(struct brcmf_cfg80211_info *cfg) +{ + brcmf_fweh_register(cfg->pub, BRCMF_E_ESCAN_RESULT, + brcmf_cfg80211_escan_handler); + cfg->escan_info.escan_state = WL_ESCAN_STATE_IDLE; + /* Init scan_timeout timer */ + init_timer(&cfg->escan_timeout); + cfg->escan_timeout.data = (unsigned long) cfg; + cfg->escan_timeout.function = brcmf_escan_timeout; + INIT_WORK(&cfg->escan_timeout_work, + brcmf_cfg80211_escan_timeout_worker); +} + +static __always_inline void brcmf_delay(u32 ms) +{ + if (ms < 1000 / HZ) { + cond_resched(); + mdelay(ms); + } else { + msleep(ms); + } +} + +static s32 brcmf_cfg80211_resume(struct wiphy *wiphy) +{ + brcmf_dbg(TRACE, "Enter\n"); + + return 0; +} + +static s32 brcmf_cfg80211_suspend(struct wiphy *wiphy, + struct cfg80211_wowlan *wow) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct net_device *ndev = cfg_to_ndev(cfg); + struct brcmf_cfg80211_vif *vif; + + brcmf_dbg(TRACE, "Enter\n"); + + /* + * if the primary net_device is not READY there is nothing + * we can do but pray resume goes smoothly. + */ + vif = ((struct brcmf_if *)netdev_priv(ndev))->vif; + if (!check_vif_up(vif)) + goto exit; + + list_for_each_entry(vif, &cfg->vif_list, list) { + if (!test_bit(BRCMF_VIF_STATUS_READY, &vif->sme_state)) + continue; + /* + * While going to suspend if associated with AP disassociate + * from AP to save power while system is in suspended state + */ + brcmf_link_down(vif); + + /* Make sure WPA_Supplicant receives all the event + * generated due to DISASSOC call to the fw to keep + * the state fw and WPA_Supplicant state consistent + */ + brcmf_delay(500); + } + + /* end any scanning */ + if (test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status)) + brcmf_abort_scanning(cfg); + + /* Turn off watchdog timer */ + brcmf_set_mpc(netdev_priv(ndev), 1); + +exit: + brcmf_dbg(TRACE, "Exit\n"); + /* clear any scanning activity */ + cfg->scan_status = 0; + return 0; +} + +static __used s32 +brcmf_update_pmklist(struct net_device *ndev, + struct brcmf_cfg80211_pmk_list *pmk_list, s32 err) +{ + int i, j; + int pmkid_len; + + pmkid_len = le32_to_cpu(pmk_list->pmkids.npmkid); + + brcmf_dbg(CONN, "No of elements %d\n", pmkid_len); + for (i = 0; i < pmkid_len; i++) { + brcmf_dbg(CONN, "PMKID[%d]: %pM =\n", i, + &pmk_list->pmkids.pmkid[i].BSSID); + for (j = 0; j < WLAN_PMKID_LEN; j++) + brcmf_dbg(CONN, "%02x\n", + pmk_list->pmkids.pmkid[i].PMKID[j]); + } + + if (!err) + brcmf_fil_iovar_data_set(netdev_priv(ndev), "pmkid_info", + (char *)pmk_list, sizeof(*pmk_list)); + + return err; +} + +static s32 +brcmf_cfg80211_set_pmksa(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_pmksa *pmksa) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_if *ifp = netdev_priv(ndev); + struct pmkid_list *pmkids = &cfg->pmk_list->pmkids; + s32 err = 0; + int i; + int pmkid_len; + + brcmf_dbg(TRACE, "Enter\n"); + if (!check_vif_up(ifp->vif)) + return -EIO; + + pmkid_len = le32_to_cpu(pmkids->npmkid); + for (i = 0; i < pmkid_len; i++) + if (!memcmp(pmksa->bssid, pmkids->pmkid[i].BSSID, ETH_ALEN)) + break; + if (i < WL_NUM_PMKIDS_MAX) { + memcpy(pmkids->pmkid[i].BSSID, pmksa->bssid, ETH_ALEN); + memcpy(pmkids->pmkid[i].PMKID, pmksa->pmkid, WLAN_PMKID_LEN); + if (i == pmkid_len) { + pmkid_len++; + pmkids->npmkid = cpu_to_le32(pmkid_len); + } + } else + err = -EINVAL; + + brcmf_dbg(CONN, "set_pmksa,IW_PMKSA_ADD - PMKID: %pM =\n", + pmkids->pmkid[pmkid_len].BSSID); + for (i = 0; i < WLAN_PMKID_LEN; i++) + brcmf_dbg(CONN, "%02x\n", pmkids->pmkid[pmkid_len].PMKID[i]); + + err = brcmf_update_pmklist(ndev, cfg->pmk_list, err); + + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +brcmf_cfg80211_del_pmksa(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_pmksa *pmksa) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_if *ifp = netdev_priv(ndev); + struct pmkid_list pmkid; + s32 err = 0; + int i, pmkid_len; + + brcmf_dbg(TRACE, "Enter\n"); + if (!check_vif_up(ifp->vif)) + return -EIO; + + memcpy(&pmkid.pmkid[0].BSSID, pmksa->bssid, ETH_ALEN); + memcpy(&pmkid.pmkid[0].PMKID, pmksa->pmkid, WLAN_PMKID_LEN); + + brcmf_dbg(CONN, "del_pmksa,IW_PMKSA_REMOVE - PMKID: %pM =\n", + &pmkid.pmkid[0].BSSID); + for (i = 0; i < WLAN_PMKID_LEN; i++) + brcmf_dbg(CONN, "%02x\n", pmkid.pmkid[0].PMKID[i]); + + pmkid_len = le32_to_cpu(cfg->pmk_list->pmkids.npmkid); + for (i = 0; i < pmkid_len; i++) + if (!memcmp + (pmksa->bssid, &cfg->pmk_list->pmkids.pmkid[i].BSSID, + ETH_ALEN)) + break; + + if ((pmkid_len > 0) + && (i < pmkid_len)) { + memset(&cfg->pmk_list->pmkids.pmkid[i], 0, + sizeof(struct pmkid)); + for (; i < (pmkid_len - 1); i++) { + memcpy(&cfg->pmk_list->pmkids.pmkid[i].BSSID, + &cfg->pmk_list->pmkids.pmkid[i + 1].BSSID, + ETH_ALEN); + memcpy(&cfg->pmk_list->pmkids.pmkid[i].PMKID, + &cfg->pmk_list->pmkids.pmkid[i + 1].PMKID, + WLAN_PMKID_LEN); + } + cfg->pmk_list->pmkids.npmkid = cpu_to_le32(pmkid_len - 1); + } else + err = -EINVAL; + + err = brcmf_update_pmklist(ndev, cfg->pmk_list, err); + + brcmf_dbg(TRACE, "Exit\n"); + return err; + +} + +static s32 +brcmf_cfg80211_flush_pmksa(struct wiphy *wiphy, struct net_device *ndev) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_if *ifp = netdev_priv(ndev); + s32 err = 0; + + brcmf_dbg(TRACE, "Enter\n"); + if (!check_vif_up(ifp->vif)) + return -EIO; + + memset(cfg->pmk_list, 0, sizeof(*cfg->pmk_list)); + err = brcmf_update_pmklist(ndev, cfg->pmk_list, err); + + brcmf_dbg(TRACE, "Exit\n"); + return err; + +} + +/* + * PFN result doesn't have all the info which are + * required by the supplicant + * (For e.g IEs) Do a target Escan so that sched scan results are reported + * via wl_inform_single_bss in the required format. Escan does require the + * scan request in the form of cfg80211_scan_request. For timebeing, create + * cfg80211_scan_request one out of the received PNO event. + */ +static s32 +brcmf_notify_sched_scan_results(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, void *data) +{ + struct brcmf_cfg80211_info *cfg = ifp->drvr->config; + struct brcmf_pno_net_info_le *netinfo, *netinfo_start; + struct cfg80211_scan_request *request = NULL; + struct cfg80211_ssid *ssid = NULL; + struct ieee80211_channel *channel = NULL; + struct wiphy *wiphy = cfg_to_wiphy(cfg); + int err = 0; + int channel_req = 0; + int band = 0; + struct brcmf_pno_scanresults_le *pfn_result; + u32 result_count; + u32 status; + + brcmf_dbg(SCAN, "Enter\n"); + + if (e->event_code == BRCMF_E_PFN_NET_LOST) { + brcmf_dbg(SCAN, "PFN NET LOST event. Do Nothing\n"); + return 0; + } + + pfn_result = (struct brcmf_pno_scanresults_le *)data; + result_count = le32_to_cpu(pfn_result->count); + status = le32_to_cpu(pfn_result->status); + + /* + * PFN event is limited to fit 512 bytes so we may get + * multiple NET_FOUND events. For now place a warning here. + */ + WARN_ON(status != BRCMF_PNO_SCAN_COMPLETE); + brcmf_dbg(SCAN, "PFN NET FOUND event. count: %d\n", result_count); + if (result_count > 0) { + int i; + + request = kzalloc(sizeof(*request), GFP_KERNEL); + ssid = kcalloc(result_count, sizeof(*ssid), GFP_KERNEL); + channel = kcalloc(result_count, sizeof(*channel), GFP_KERNEL); + if (!request || !ssid || !channel) { + err = -ENOMEM; + goto out_err; + } + + request->wiphy = wiphy; + data += sizeof(struct brcmf_pno_scanresults_le); + netinfo_start = (struct brcmf_pno_net_info_le *)data; + + for (i = 0; i < result_count; i++) { + netinfo = &netinfo_start[i]; + if (!netinfo) { + brcmf_err("Invalid netinfo ptr. index: %d\n", + i); + err = -EINVAL; + goto out_err; + } + + brcmf_dbg(SCAN, "SSID:%s Channel:%d\n", + netinfo->SSID, netinfo->channel); + memcpy(ssid[i].ssid, netinfo->SSID, netinfo->SSID_len); + ssid[i].ssid_len = netinfo->SSID_len; + request->n_ssids++; + + channel_req = netinfo->channel; + if (channel_req <= CH_MAX_2G_CHANNEL) + band = NL80211_BAND_2GHZ; + else + band = NL80211_BAND_5GHZ; + channel[i].center_freq = + ieee80211_channel_to_frequency(channel_req, + band); + channel[i].band = band; + channel[i].flags |= IEEE80211_CHAN_NO_HT40; + request->channels[i] = &channel[i]; + request->n_channels++; + } + + /* assign parsed ssid array */ + if (request->n_ssids) + request->ssids = &ssid[0]; + + if (test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status)) { + /* Abort any on-going scan */ + brcmf_abort_scanning(cfg); + } + + set_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status); + err = brcmf_do_escan(cfg, wiphy, ifp, request); + if (err) { + clear_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status); + goto out_err; + } + cfg->sched_escan = true; + cfg->scan_request = request; + } else { + brcmf_err("FALSE PNO Event. (pfn_count == 0)\n"); + goto out_err; + } + + kfree(ssid); + kfree(channel); + kfree(request); + return 0; + +out_err: + kfree(ssid); + kfree(channel); + kfree(request); + cfg80211_sched_scan_stopped(wiphy); + return err; +} + +static int brcmf_dev_pno_clean(struct net_device *ndev) +{ + int ret; + + /* Disable pfn */ + ret = brcmf_fil_iovar_int_set(netdev_priv(ndev), "pfn", 0); + if (ret == 0) { + /* clear pfn */ + ret = brcmf_fil_iovar_data_set(netdev_priv(ndev), "pfnclear", + NULL, 0); + } + if (ret < 0) + brcmf_err("failed code %d\n", ret); + + return ret; +} + +static int brcmf_dev_pno_config(struct net_device *ndev) +{ + struct brcmf_pno_param_le pfn_param; + + memset(&pfn_param, 0, sizeof(pfn_param)); + pfn_param.version = cpu_to_le32(BRCMF_PNO_VERSION); + + /* set extra pno params */ + pfn_param.flags = cpu_to_le16(1 << BRCMF_PNO_ENABLE_ADAPTSCAN_BIT); + pfn_param.repeat = BRCMF_PNO_REPEAT; + pfn_param.exp = BRCMF_PNO_FREQ_EXPO_MAX; + + /* set up pno scan fr */ + pfn_param.scan_freq = cpu_to_le32(BRCMF_PNO_TIME); + + return brcmf_fil_iovar_data_set(netdev_priv(ndev), "pfn_set", + &pfn_param, sizeof(pfn_param)); +} + +static int +brcmf_cfg80211_sched_scan_start(struct wiphy *wiphy, + struct net_device *ndev, + struct cfg80211_sched_scan_request *request) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_cfg80211_info *cfg = wiphy_priv(wiphy); + struct brcmf_pno_net_param_le pfn; + int i; + int ret = 0; + + brcmf_dbg(SCAN, "Enter n_match_sets:%d n_ssids:%d\n", + request->n_match_sets, request->n_ssids); + if (test_bit(BRCMF_SCAN_STATUS_BUSY, &cfg->scan_status)) { + brcmf_err("Scanning already: status (%lu)\n", cfg->scan_status); + return -EAGAIN; + } + if (test_bit(BRCMF_SCAN_STATUS_SUPPRESS, &cfg->scan_status)) { + brcmf_err("Scanning suppressed: status (%lu)\n", + cfg->scan_status); + return -EAGAIN; + } + + if (!request->n_ssids || !request->n_match_sets) { + brcmf_err("Invalid sched scan req!! n_ssids:%d\n", + request->n_ssids); + return -EINVAL; + } + + if (request->n_ssids > 0) { + for (i = 0; i < request->n_ssids; i++) { + /* Active scan req for ssids */ + brcmf_dbg(SCAN, ">>> Active scan req for ssid (%s)\n", + request->ssids[i].ssid); + + /* + * match_set ssids is a supert set of n_ssid list, + * so we need not add these set seperately. + */ + } + } + + if (request->n_match_sets > 0) { + /* clean up everything */ + ret = brcmf_dev_pno_clean(ndev); + if (ret < 0) { + brcmf_err("failed error=%d\n", ret); + return ret; + } + + /* configure pno */ + ret = brcmf_dev_pno_config(ndev); + if (ret < 0) { + brcmf_err("PNO setup failed!! ret=%d\n", ret); + return -EINVAL; + } + + /* configure each match set */ + for (i = 0; i < request->n_match_sets; i++) { + struct cfg80211_ssid *ssid; + u32 ssid_len; + + ssid = &request->match_sets[i].ssid; + ssid_len = ssid->ssid_len; + + if (!ssid_len) { + brcmf_err("skip broadcast ssid\n"); + continue; + } + pfn.auth = cpu_to_le32(WLAN_AUTH_OPEN); + pfn.wpa_auth = cpu_to_le32(BRCMF_PNO_WPA_AUTH_ANY); + pfn.wsec = cpu_to_le32(0); + pfn.infra = cpu_to_le32(1); + pfn.flags = cpu_to_le32(1 << BRCMF_PNO_HIDDEN_BIT); + pfn.ssid.SSID_len = cpu_to_le32(ssid_len); + memcpy(pfn.ssid.SSID, ssid->ssid, ssid_len); + ret = brcmf_fil_iovar_data_set(ifp, "pfn_add", &pfn, + sizeof(pfn)); + brcmf_dbg(SCAN, ">>> PNO filter %s for ssid (%s)\n", + ret == 0 ? "set" : "failed", ssid->ssid); + } + /* Enable the PNO */ + if (brcmf_fil_iovar_int_set(ifp, "pfn", 1) < 0) { + brcmf_err("PNO enable failed!! ret=%d\n", ret); + return -EINVAL; + } + } else { + return -EINVAL; + } + + return 0; +} + +static int brcmf_cfg80211_sched_scan_stop(struct wiphy *wiphy, + struct net_device *ndev) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + + brcmf_dbg(SCAN, "enter\n"); + brcmf_dev_pno_clean(ndev); + if (cfg->sched_escan) + brcmf_notify_escan_complete(cfg, netdev_priv(ndev), true, true); + return 0; +} + +#ifdef CPTCFG_NL80211_TESTMODE +static int brcmf_cfg80211_testmode(struct wiphy *wiphy, void *data, int len) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct net_device *ndev = cfg_to_ndev(cfg); + struct brcmf_dcmd *dcmd = data; + struct sk_buff *reply; + int ret; + + brcmf_dbg(TRACE, "cmd %x set %d buf %p len %d\n", dcmd->cmd, dcmd->set, + dcmd->buf, dcmd->len); + + if (dcmd->set) + ret = brcmf_fil_cmd_data_set(netdev_priv(ndev), dcmd->cmd, + dcmd->buf, dcmd->len); + else + ret = brcmf_fil_cmd_data_get(netdev_priv(ndev), dcmd->cmd, + dcmd->buf, dcmd->len); + if (ret == 0) { + reply = cfg80211_testmode_alloc_reply_skb(wiphy, sizeof(*dcmd)); + nla_put(reply, NL80211_ATTR_TESTDATA, sizeof(*dcmd), dcmd); + ret = cfg80211_testmode_reply(reply); + } + return ret; +} +#endif + +static s32 brcmf_configure_opensecurity(struct brcmf_if *ifp) +{ + s32 err; + + /* set auth */ + err = brcmf_fil_bsscfg_int_set(ifp, "auth", 0); + if (err < 0) { + brcmf_err("auth error %d\n", err); + return err; + } + /* set wsec */ + err = brcmf_fil_bsscfg_int_set(ifp, "wsec", 0); + if (err < 0) { + brcmf_err("wsec error %d\n", err); + return err; + } + /* set upper-layer auth */ + err = brcmf_fil_bsscfg_int_set(ifp, "wpa_auth", WPA_AUTH_NONE); + if (err < 0) { + brcmf_err("wpa_auth error %d\n", err); + return err; + } + + return 0; +} + +static bool brcmf_valid_wpa_oui(u8 *oui, bool is_rsn_ie) +{ + if (is_rsn_ie) + return (memcmp(oui, RSN_OUI, TLV_OUI_LEN) == 0); + + return (memcmp(oui, WPA_OUI, TLV_OUI_LEN) == 0); +} + +static s32 +brcmf_configure_wpaie(struct net_device *ndev, struct brcmf_vs_tlv *wpa_ie, + bool is_rsn_ie) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + u32 auth = 0; /* d11 open authentication */ + u16 count; + s32 err = 0; + s32 len = 0; + u32 i; + u32 wsec; + u32 pval = 0; + u32 gval = 0; + u32 wpa_auth = 0; + u32 offset; + u8 *data; + u16 rsn_cap; + u32 wme_bss_disable; + + brcmf_dbg(TRACE, "Enter\n"); + if (wpa_ie == NULL) + goto exit; + + len = wpa_ie->len + TLV_HDR_LEN; + data = (u8 *)wpa_ie; + offset = TLV_HDR_LEN; + if (!is_rsn_ie) + offset += VS_IE_FIXED_HDR_LEN; + else + offset += WPA_IE_VERSION_LEN; + + /* check for multicast cipher suite */ + if (offset + WPA_IE_MIN_OUI_LEN > len) { + err = -EINVAL; + brcmf_err("no multicast cipher suite\n"); + goto exit; + } + + if (!brcmf_valid_wpa_oui(&data[offset], is_rsn_ie)) { + err = -EINVAL; + brcmf_err("ivalid OUI\n"); + goto exit; + } + offset += TLV_OUI_LEN; + + /* pick up multicast cipher */ + switch (data[offset]) { + case WPA_CIPHER_NONE: + gval = 0; + break; + case WPA_CIPHER_WEP_40: + case WPA_CIPHER_WEP_104: + gval = WEP_ENABLED; + break; + case WPA_CIPHER_TKIP: + gval = TKIP_ENABLED; + break; + case WPA_CIPHER_AES_CCM: + gval = AES_ENABLED; + break; + default: + err = -EINVAL; + brcmf_err("Invalid multi cast cipher info\n"); + goto exit; + } + + offset++; + /* walk thru unicast cipher list and pick up what we recognize */ + count = data[offset] + (data[offset + 1] << 8); + offset += WPA_IE_SUITE_COUNT_LEN; + /* Check for unicast suite(s) */ + if (offset + (WPA_IE_MIN_OUI_LEN * count) > len) { + err = -EINVAL; + brcmf_err("no unicast cipher suite\n"); + goto exit; + } + for (i = 0; i < count; i++) { + if (!brcmf_valid_wpa_oui(&data[offset], is_rsn_ie)) { + err = -EINVAL; + brcmf_err("ivalid OUI\n"); + goto exit; + } + offset += TLV_OUI_LEN; + switch (data[offset]) { + case WPA_CIPHER_NONE: + break; + case WPA_CIPHER_WEP_40: + case WPA_CIPHER_WEP_104: + pval |= WEP_ENABLED; + break; + case WPA_CIPHER_TKIP: + pval |= TKIP_ENABLED; + break; + case WPA_CIPHER_AES_CCM: + pval |= AES_ENABLED; + break; + default: + brcmf_err("Ivalid unicast security info\n"); + } + offset++; + } + /* walk thru auth management suite list and pick up what we recognize */ + count = data[offset] + (data[offset + 1] << 8); + offset += WPA_IE_SUITE_COUNT_LEN; + /* Check for auth key management suite(s) */ + if (offset + (WPA_IE_MIN_OUI_LEN * count) > len) { + err = -EINVAL; + brcmf_err("no auth key mgmt suite\n"); + goto exit; + } + for (i = 0; i < count; i++) { + if (!brcmf_valid_wpa_oui(&data[offset], is_rsn_ie)) { + err = -EINVAL; + brcmf_err("ivalid OUI\n"); + goto exit; + } + offset += TLV_OUI_LEN; + switch (data[offset]) { + case RSN_AKM_NONE: + brcmf_dbg(TRACE, "RSN_AKM_NONE\n"); + wpa_auth |= WPA_AUTH_NONE; + break; + case RSN_AKM_UNSPECIFIED: + brcmf_dbg(TRACE, "RSN_AKM_UNSPECIFIED\n"); + is_rsn_ie ? (wpa_auth |= WPA2_AUTH_UNSPECIFIED) : + (wpa_auth |= WPA_AUTH_UNSPECIFIED); + break; + case RSN_AKM_PSK: + brcmf_dbg(TRACE, "RSN_AKM_PSK\n"); + is_rsn_ie ? (wpa_auth |= WPA2_AUTH_PSK) : + (wpa_auth |= WPA_AUTH_PSK); + break; + default: + brcmf_err("Ivalid key mgmt info\n"); + } + offset++; + } + + if (is_rsn_ie) { + wme_bss_disable = 1; + if ((offset + RSN_CAP_LEN) <= len) { + rsn_cap = data[offset] + (data[offset + 1] << 8); + if (rsn_cap & RSN_CAP_PTK_REPLAY_CNTR_MASK) + wme_bss_disable = 0; + } + /* set wme_bss_disable to sync RSN Capabilities */ + err = brcmf_fil_bsscfg_int_set(ifp, "wme_bss_disable", + wme_bss_disable); + if (err < 0) { + brcmf_err("wme_bss_disable error %d\n", err); + goto exit; + } + } + /* FOR WPS , set SES_OW_ENABLED */ + wsec = (pval | gval | SES_OW_ENABLED); + + /* set auth */ + err = brcmf_fil_bsscfg_int_set(ifp, "auth", auth); + if (err < 0) { + brcmf_err("auth error %d\n", err); + goto exit; + } + /* set wsec */ + err = brcmf_fil_bsscfg_int_set(ifp, "wsec", wsec); + if (err < 0) { + brcmf_err("wsec error %d\n", err); + goto exit; + } + /* set upper-layer auth */ + err = brcmf_fil_bsscfg_int_set(ifp, "wpa_auth", wpa_auth); + if (err < 0) { + brcmf_err("wpa_auth error %d\n", err); + goto exit; + } + +exit: + return err; +} + +static s32 +brcmf_parse_vndr_ies(const u8 *vndr_ie_buf, u32 vndr_ie_len, + struct parsed_vndr_ies *vndr_ies) +{ + s32 err = 0; + struct brcmf_vs_tlv *vndrie; + struct brcmf_tlv *ie; + struct parsed_vndr_ie_info *parsed_info; + s32 remaining_len; + + remaining_len = (s32)vndr_ie_len; + memset(vndr_ies, 0, sizeof(*vndr_ies)); + + ie = (struct brcmf_tlv *)vndr_ie_buf; + while (ie) { + if (ie->id != WLAN_EID_VENDOR_SPECIFIC) + goto next; + vndrie = (struct brcmf_vs_tlv *)ie; + /* len should be bigger than OUI length + one */ + if (vndrie->len < (VS_IE_FIXED_HDR_LEN - TLV_HDR_LEN + 1)) { + brcmf_err("invalid vndr ie. length is too small %d\n", + vndrie->len); + goto next; + } + /* if wpa or wme ie, do not add ie */ + if (!memcmp(vndrie->oui, (u8 *)WPA_OUI, TLV_OUI_LEN) && + ((vndrie->oui_type == WPA_OUI_TYPE) || + (vndrie->oui_type == WME_OUI_TYPE))) { + brcmf_dbg(TRACE, "Found WPA/WME oui. Do not add it\n"); + goto next; + } + + parsed_info = &vndr_ies->ie_info[vndr_ies->count]; + + /* save vndr ie information */ + parsed_info->ie_ptr = (char *)vndrie; + parsed_info->ie_len = vndrie->len + TLV_HDR_LEN; + memcpy(&parsed_info->vndrie, vndrie, sizeof(*vndrie)); + + vndr_ies->count++; + + brcmf_dbg(TRACE, "** OUI %02x %02x %02x, type 0x%02x\n", + parsed_info->vndrie.oui[0], + parsed_info->vndrie.oui[1], + parsed_info->vndrie.oui[2], + parsed_info->vndrie.oui_type); + + if (vndr_ies->count >= VNDR_IE_PARSE_LIMIT) + break; +next: + remaining_len -= (ie->len + TLV_HDR_LEN); + if (remaining_len <= TLV_HDR_LEN) + ie = NULL; + else + ie = (struct brcmf_tlv *)(((u8 *)ie) + ie->len + + TLV_HDR_LEN); + } + return err; +} + +static u32 +brcmf_vndr_ie(u8 *iebuf, s32 pktflag, u8 *ie_ptr, u32 ie_len, s8 *add_del_cmd) +{ + + __le32 iecount_le; + __le32 pktflag_le; + + strncpy(iebuf, add_del_cmd, VNDR_IE_CMD_LEN - 1); + iebuf[VNDR_IE_CMD_LEN - 1] = '\0'; + + iecount_le = cpu_to_le32(1); + memcpy(&iebuf[VNDR_IE_COUNT_OFFSET], &iecount_le, sizeof(iecount_le)); + + pktflag_le = cpu_to_le32(pktflag); + memcpy(&iebuf[VNDR_IE_PKTFLAG_OFFSET], &pktflag_le, sizeof(pktflag_le)); + + memcpy(&iebuf[VNDR_IE_VSIE_OFFSET], ie_ptr, ie_len); + + return ie_len + VNDR_IE_HDR_SIZE; +} + +s32 brcmf_vif_set_mgmt_ie(struct brcmf_cfg80211_vif *vif, s32 pktflag, + const u8 *vndr_ie_buf, u32 vndr_ie_len) +{ + struct brcmf_if *ifp; + struct vif_saved_ie *saved_ie; + s32 err = 0; + u8 *iovar_ie_buf; + u8 *curr_ie_buf; + u8 *mgmt_ie_buf = NULL; + int mgmt_ie_buf_len; + u32 *mgmt_ie_len; + u32 del_add_ie_buf_len = 0; + u32 total_ie_buf_len = 0; + u32 parsed_ie_buf_len = 0; + struct parsed_vndr_ies old_vndr_ies; + struct parsed_vndr_ies new_vndr_ies; + struct parsed_vndr_ie_info *vndrie_info; + s32 i; + u8 *ptr; + int remained_buf_len; + + if (!vif) + return -ENODEV; + ifp = vif->ifp; + saved_ie = &vif->saved_ie; + + brcmf_dbg(TRACE, "bssidx %d, pktflag : 0x%02X\n", ifp->bssidx, pktflag); + iovar_ie_buf = kzalloc(WL_EXTRA_BUF_MAX, GFP_KERNEL); + if (!iovar_ie_buf) + return -ENOMEM; + curr_ie_buf = iovar_ie_buf; + switch (pktflag) { + case BRCMF_VNDR_IE_PRBREQ_FLAG: + mgmt_ie_buf = saved_ie->probe_req_ie; + mgmt_ie_len = &saved_ie->probe_req_ie_len; + mgmt_ie_buf_len = sizeof(saved_ie->probe_req_ie); + break; + case BRCMF_VNDR_IE_PRBRSP_FLAG: + mgmt_ie_buf = saved_ie->probe_res_ie; + mgmt_ie_len = &saved_ie->probe_res_ie_len; + mgmt_ie_buf_len = sizeof(saved_ie->probe_res_ie); + break; + case BRCMF_VNDR_IE_BEACON_FLAG: + mgmt_ie_buf = saved_ie->beacon_ie; + mgmt_ie_len = &saved_ie->beacon_ie_len; + mgmt_ie_buf_len = sizeof(saved_ie->beacon_ie); + break; + case BRCMF_VNDR_IE_ASSOCREQ_FLAG: + mgmt_ie_buf = saved_ie->assoc_req_ie; + mgmt_ie_len = &saved_ie->assoc_req_ie_len; + mgmt_ie_buf_len = sizeof(saved_ie->assoc_req_ie); + break; + default: + err = -EPERM; + brcmf_err("not suitable type\n"); + goto exit; + } + + if (vndr_ie_len > mgmt_ie_buf_len) { + err = -ENOMEM; + brcmf_err("extra IE size too big\n"); + goto exit; + } + + /* parse and save new vndr_ie in curr_ie_buff before comparing it */ + if (vndr_ie_buf && vndr_ie_len && curr_ie_buf) { + ptr = curr_ie_buf; + brcmf_parse_vndr_ies(vndr_ie_buf, vndr_ie_len, &new_vndr_ies); + for (i = 0; i < new_vndr_ies.count; i++) { + vndrie_info = &new_vndr_ies.ie_info[i]; + memcpy(ptr + parsed_ie_buf_len, vndrie_info->ie_ptr, + vndrie_info->ie_len); + parsed_ie_buf_len += vndrie_info->ie_len; + } + } + + if (mgmt_ie_buf && *mgmt_ie_len) { + if (parsed_ie_buf_len && (parsed_ie_buf_len == *mgmt_ie_len) && + (memcmp(mgmt_ie_buf, curr_ie_buf, + parsed_ie_buf_len) == 0)) { + brcmf_dbg(TRACE, "Previous mgmt IE equals to current IE\n"); + goto exit; + } + + /* parse old vndr_ie */ + brcmf_parse_vndr_ies(mgmt_ie_buf, *mgmt_ie_len, &old_vndr_ies); + + /* make a command to delete old ie */ + for (i = 0; i < old_vndr_ies.count; i++) { + vndrie_info = &old_vndr_ies.ie_info[i]; + + brcmf_dbg(TRACE, "DEL ID : %d, Len: %d , OUI:%02x:%02x:%02x\n", + vndrie_info->vndrie.id, + vndrie_info->vndrie.len, + vndrie_info->vndrie.oui[0], + vndrie_info->vndrie.oui[1], + vndrie_info->vndrie.oui[2]); + + del_add_ie_buf_len = brcmf_vndr_ie(curr_ie_buf, pktflag, + vndrie_info->ie_ptr, + vndrie_info->ie_len, + "del"); + curr_ie_buf += del_add_ie_buf_len; + total_ie_buf_len += del_add_ie_buf_len; + } + } + + *mgmt_ie_len = 0; + /* Add if there is any extra IE */ + if (mgmt_ie_buf && parsed_ie_buf_len) { + ptr = mgmt_ie_buf; + + remained_buf_len = mgmt_ie_buf_len; + + /* make a command to add new ie */ + for (i = 0; i < new_vndr_ies.count; i++) { + vndrie_info = &new_vndr_ies.ie_info[i]; + + /* verify remained buf size before copy data */ + if (remained_buf_len < (vndrie_info->vndrie.len + + VNDR_IE_VSIE_OFFSET)) { + brcmf_err("no space in mgmt_ie_buf: len left %d", + remained_buf_len); + break; + } + remained_buf_len -= (vndrie_info->ie_len + + VNDR_IE_VSIE_OFFSET); + + brcmf_dbg(TRACE, "ADDED ID : %d, Len: %d, OUI:%02x:%02x:%02x\n", + vndrie_info->vndrie.id, + vndrie_info->vndrie.len, + vndrie_info->vndrie.oui[0], + vndrie_info->vndrie.oui[1], + vndrie_info->vndrie.oui[2]); + + del_add_ie_buf_len = brcmf_vndr_ie(curr_ie_buf, pktflag, + vndrie_info->ie_ptr, + vndrie_info->ie_len, + "add"); + + /* save the parsed IE in wl struct */ + memcpy(ptr + (*mgmt_ie_len), vndrie_info->ie_ptr, + vndrie_info->ie_len); + *mgmt_ie_len += vndrie_info->ie_len; + + curr_ie_buf += del_add_ie_buf_len; + total_ie_buf_len += del_add_ie_buf_len; + } + } + if (total_ie_buf_len) { + err = brcmf_fil_bsscfg_data_set(ifp, "vndr_ie", iovar_ie_buf, + total_ie_buf_len); + if (err) + brcmf_err("vndr ie set error : %d\n", err); + } + +exit: + kfree(iovar_ie_buf); + return err; +} + +s32 brcmf_vif_clear_mgmt_ies(struct brcmf_cfg80211_vif *vif) +{ + s32 pktflags[] = { + BRCMF_VNDR_IE_PRBREQ_FLAG, + BRCMF_VNDR_IE_PRBRSP_FLAG, + BRCMF_VNDR_IE_BEACON_FLAG + }; + int i; + + for (i = 0; i < ARRAY_SIZE(pktflags); i++) + brcmf_vif_set_mgmt_ie(vif, pktflags[i], NULL, 0); + + memset(&vif->saved_ie, 0, sizeof(vif->saved_ie)); + return 0; +} + +static s32 +brcmf_config_ap_mgmt_ie(struct brcmf_cfg80211_vif *vif, + struct cfg80211_beacon_data *beacon) +{ + s32 err; + + /* Set Beacon IEs to FW */ + err = brcmf_vif_set_mgmt_ie(vif, BRCMF_VNDR_IE_BEACON_FLAG, + beacon->tail, beacon->tail_len); + if (err) { + brcmf_err("Set Beacon IE Failed\n"); + return err; + } + brcmf_dbg(TRACE, "Applied Vndr IEs for Beacon\n"); + + /* Set Probe Response IEs to FW */ + err = brcmf_vif_set_mgmt_ie(vif, BRCMF_VNDR_IE_PRBRSP_FLAG, + beacon->proberesp_ies, + beacon->proberesp_ies_len); + if (err) + brcmf_err("Set Probe Resp IE Failed\n"); + else + brcmf_dbg(TRACE, "Applied Vndr IEs for Probe Resp\n"); + + return err; +} + +static s32 +brcmf_cfg80211_set_channel(struct brcmf_cfg80211_info *cfg, + struct brcmf_if *ifp, + struct ieee80211_channel *channel) +{ + u16 chanspec; + s32 err; + + brcmf_dbg(TRACE, "band=%d, center_freq=%d\n", channel->band, + channel->center_freq); + + chanspec = channel_to_chanspec(&cfg->d11inf, channel); + err = brcmf_fil_iovar_int_set(ifp, "chanspec", chanspec); + + return err; +} + +static s32 +brcmf_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_ap_settings *settings) +{ + s32 ie_offset; + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_tlv *ssid_ie; + struct brcmf_ssid_le ssid_le; + s32 err = -EPERM; + struct brcmf_tlv *rsn_ie; + struct brcmf_vs_tlv *wpa_ie; + struct brcmf_join_params join_params; + enum nl80211_iftype dev_role; + struct brcmf_fil_bss_enable_le bss_enable; + + brcmf_dbg(TRACE, "channel_type=%d, beacon_interval=%d, dtim_period=%d,\n", + cfg80211_get_chandef_type(&settings->chandef), + settings->beacon_interval, + settings->dtim_period); + brcmf_dbg(TRACE, "ssid=%s(%zu), auth_type=%d, inactivity_timeout=%d\n", + settings->ssid, settings->ssid_len, settings->auth_type, + settings->inactivity_timeout); + + dev_role = ifp->vif->wdev.iftype; + + memset(&ssid_le, 0, sizeof(ssid_le)); + if (settings->ssid == NULL || settings->ssid_len == 0) { + ie_offset = DOT11_MGMT_HDR_LEN + DOT11_BCN_PRB_FIXED_LEN; + ssid_ie = brcmf_parse_tlvs( + (u8 *)&settings->beacon.head[ie_offset], + settings->beacon.head_len - ie_offset, + WLAN_EID_SSID); + if (!ssid_ie) + return -EINVAL; + + memcpy(ssid_le.SSID, ssid_ie->data, ssid_ie->len); + ssid_le.SSID_len = cpu_to_le32(ssid_ie->len); + brcmf_dbg(TRACE, "SSID is (%s) in Head\n", ssid_le.SSID); + } else { + memcpy(ssid_le.SSID, settings->ssid, settings->ssid_len); + ssid_le.SSID_len = cpu_to_le32((u32)settings->ssid_len); + } + + brcmf_set_mpc(ifp, 0); + brcmf_configure_arp_offload(ifp, false); + + /* find the RSN_IE */ + rsn_ie = brcmf_parse_tlvs((u8 *)settings->beacon.tail, + settings->beacon.tail_len, WLAN_EID_RSN); + + /* find the WPA_IE */ + wpa_ie = brcmf_find_wpaie((u8 *)settings->beacon.tail, + settings->beacon.tail_len); + + if ((wpa_ie != NULL || rsn_ie != NULL)) { + brcmf_dbg(TRACE, "WPA(2) IE is found\n"); + if (wpa_ie != NULL) { + /* WPA IE */ + err = brcmf_configure_wpaie(ndev, wpa_ie, false); + if (err < 0) + goto exit; + } else { + /* RSN IE */ + err = brcmf_configure_wpaie(ndev, + (struct brcmf_vs_tlv *)rsn_ie, true); + if (err < 0) + goto exit; + } + } else { + brcmf_dbg(TRACE, "No WPA(2) IEs found\n"); + brcmf_configure_opensecurity(ifp); + } + + brcmf_config_ap_mgmt_ie(ifp->vif, &settings->beacon); + + err = brcmf_cfg80211_set_channel(cfg, ifp, settings->chandef.chan); + if (err < 0) { + brcmf_err("Set Channel failed, %d\n", err); + goto exit; + } + + if (settings->beacon_interval) { + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_BCNPRD, + settings->beacon_interval); + if (err < 0) { + brcmf_err("Beacon Interval Set Error, %d\n", err); + goto exit; + } + } + if (settings->dtim_period) { + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_DTIMPRD, + settings->dtim_period); + if (err < 0) { + brcmf_err("DTIM Interval Set Error, %d\n", err); + goto exit; + } + } + + if (dev_role == NL80211_IFTYPE_AP) { + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_DOWN, 1); + if (err < 0) { + brcmf_err("BRCMF_C_DOWN error %d\n", err); + goto exit; + } + brcmf_fil_iovar_int_set(ifp, "apsta", 0); + } + + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_INFRA, 1); + if (err < 0) { + brcmf_err("SET INFRA error %d\n", err); + goto exit; + } + if (dev_role == NL80211_IFTYPE_AP) { + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_AP, 1); + if (err < 0) { + brcmf_err("setting AP mode failed %d\n", err); + goto exit; + } + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_UP, 1); + if (err < 0) { + brcmf_err("BRCMF_C_UP error (%d)\n", err); + goto exit; + } + + memset(&join_params, 0, sizeof(join_params)); + /* join parameters starts with ssid */ + memcpy(&join_params.ssid_le, &ssid_le, sizeof(ssid_le)); + /* create softap */ + err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID, + &join_params, sizeof(join_params)); + if (err < 0) { + brcmf_err("SET SSID error (%d)\n", err); + goto exit; + } + brcmf_dbg(TRACE, "AP mode configuration complete\n"); + } else { + err = brcmf_fil_bsscfg_data_set(ifp, "ssid", &ssid_le, + sizeof(ssid_le)); + if (err < 0) { + brcmf_err("setting ssid failed %d\n", err); + goto exit; + } + bss_enable.bsscfg_idx = cpu_to_le32(ifp->bssidx); + bss_enable.enable = cpu_to_le32(1); + err = brcmf_fil_iovar_data_set(ifp, "bss", &bss_enable, + sizeof(bss_enable)); + if (err < 0) { + brcmf_err("bss_enable config failed %d\n", err); + goto exit; + } + + brcmf_dbg(TRACE, "GO mode configuration complete\n"); + } + clear_bit(BRCMF_VIF_STATUS_AP_CREATING, &ifp->vif->sme_state); + set_bit(BRCMF_VIF_STATUS_AP_CREATED, &ifp->vif->sme_state); + +exit: + if (err) { + brcmf_set_mpc(ifp, 1); + brcmf_configure_arp_offload(ifp, true); + } + return err; +} + +static int brcmf_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *ndev) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + s32 err; + struct brcmf_fil_bss_enable_le bss_enable; + struct brcmf_join_params join_params; + + brcmf_dbg(TRACE, "Enter\n"); + + if (ifp->vif->wdev.iftype == NL80211_IFTYPE_AP) { + /* Due to most likely deauths outstanding we sleep */ + /* first to make sure they get processed by fw. */ + msleep(400); + + memset(&join_params, 0, sizeof(join_params)); + err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID, + &join_params, sizeof(join_params)); + if (err < 0) + brcmf_err("SET SSID error (%d)\n", err); + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_UP, 0); + if (err < 0) + brcmf_err("BRCMF_C_UP error %d\n", err); + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_AP, 0); + if (err < 0) + brcmf_err("setting AP mode failed %d\n", err); + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_INFRA, 0); + if (err < 0) + brcmf_err("setting INFRA mode failed %d\n", err); + } else { + bss_enable.bsscfg_idx = cpu_to_le32(ifp->bssidx); + bss_enable.enable = cpu_to_le32(0); + err = brcmf_fil_iovar_data_set(ifp, "bss", &bss_enable, + sizeof(bss_enable)); + if (err < 0) + brcmf_err("bss_enable config failed %d\n", err); + } + brcmf_set_mpc(ifp, 1); + brcmf_configure_arp_offload(ifp, true); + set_bit(BRCMF_VIF_STATUS_AP_CREATING, &ifp->vif->sme_state); + clear_bit(BRCMF_VIF_STATUS_AP_CREATED, &ifp->vif->sme_state); + + return err; +} + +static s32 +brcmf_cfg80211_change_beacon(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_beacon_data *info) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + s32 err; + + brcmf_dbg(TRACE, "Enter\n"); + + err = brcmf_config_ap_mgmt_ie(ifp->vif, info); + + return err; +} + +static int +brcmf_cfg80211_del_station(struct wiphy *wiphy, struct net_device *ndev, + u8 *mac) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_scb_val_le scbval; + struct brcmf_if *ifp = netdev_priv(ndev); + s32 err; + + if (!mac) + return -EFAULT; + + brcmf_dbg(TRACE, "Enter %pM\n", mac); + + if (ifp->vif == cfg->p2p.bss_idx[P2PAPI_BSSCFG_DEVICE].vif) + ifp = cfg->p2p.bss_idx[P2PAPI_BSSCFG_PRIMARY].vif->ifp; + if (!check_vif_up(ifp->vif)) + return -EIO; + + memcpy(&scbval.ea, mac, ETH_ALEN); + scbval.val = cpu_to_le32(WLAN_REASON_DEAUTH_LEAVING); + err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCB_DEAUTHENTICATE_FOR_REASON, + &scbval, sizeof(scbval)); + if (err) + brcmf_err("SCB_DEAUTHENTICATE_FOR_REASON failed %d\n", err); + + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + + +static void +brcmf_cfg80211_mgmt_frame_register(struct wiphy *wiphy, + struct wireless_dev *wdev, + u16 frame_type, bool reg) +{ + struct brcmf_cfg80211_vif *vif; + u16 mgmt_type; + + brcmf_dbg(TRACE, "Enter, frame_type %04x, reg=%d\n", frame_type, reg); + + mgmt_type = (frame_type & IEEE80211_FCTL_STYPE) >> 4; + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + if (reg) + vif->mgmt_rx_reg |= BIT(mgmt_type); + else + vif->mgmt_rx_reg &= ~BIT(mgmt_type); +} + + +static int +brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, + struct ieee80211_channel *chan, bool offchan, + unsigned int wait, const u8 *buf, size_t len, + bool no_cck, bool dont_wait_for_ack, u64 *cookie) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + const struct ieee80211_mgmt *mgmt; + struct brcmf_cfg80211_vif *vif; + s32 err = 0; + s32 ie_offset; + s32 ie_len; + struct brcmf_fil_action_frame_le *action_frame; + struct brcmf_fil_af_params_le *af_params; + bool ack; + s32 chan_nr; + + brcmf_dbg(TRACE, "Enter\n"); + + *cookie = 0; + + mgmt = (const struct ieee80211_mgmt *)buf; + + if (!ieee80211_is_mgmt(mgmt->frame_control)) { + brcmf_err("Driver only allows MGMT packet type\n"); + return -EPERM; + } + + if (ieee80211_is_probe_resp(mgmt->frame_control)) { + /* Right now the only reason to get a probe response */ + /* is for p2p listen response or for p2p GO from */ + /* wpa_supplicant. Unfortunately the probe is send */ + /* on primary ndev, while dongle wants it on the p2p */ + /* vif. Since this is only reason for a probe */ + /* response to be sent, the vif is taken from cfg. */ + /* If ever desired to send proberesp for non p2p */ + /* response then data should be checked for */ + /* "DIRECT-". Note in future supplicant will take */ + /* dedicated p2p wdev to do this and then this 'hack'*/ + /* is not needed anymore. */ + ie_offset = DOT11_MGMT_HDR_LEN + + DOT11_BCN_PRB_FIXED_LEN; + ie_len = len - ie_offset; + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + if (vif == cfg->p2p.bss_idx[P2PAPI_BSSCFG_PRIMARY].vif) + vif = cfg->p2p.bss_idx[P2PAPI_BSSCFG_DEVICE].vif; + err = brcmf_vif_set_mgmt_ie(vif, + BRCMF_VNDR_IE_PRBRSP_FLAG, + &buf[ie_offset], + ie_len); + cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, true, + GFP_KERNEL); + } else if (ieee80211_is_action(mgmt->frame_control)) { + af_params = kzalloc(sizeof(*af_params), GFP_KERNEL); + if (af_params == NULL) { + brcmf_err("unable to allocate frame\n"); + err = -ENOMEM; + goto exit; + } + action_frame = &af_params->action_frame; + /* Add the packet Id */ + action_frame->packet_id = cpu_to_le32(*cookie); + /* Add BSSID */ + memcpy(&action_frame->da[0], &mgmt->da[0], ETH_ALEN); + memcpy(&af_params->bssid[0], &mgmt->bssid[0], ETH_ALEN); + /* Add the length exepted for 802.11 header */ + action_frame->len = cpu_to_le16(len - DOT11_MGMT_HDR_LEN); + /* Add the channel */ + chan_nr = ieee80211_frequency_to_channel(chan->center_freq); + af_params->channel = cpu_to_le32(chan_nr); + + memcpy(action_frame->data, &buf[DOT11_MGMT_HDR_LEN], + le16_to_cpu(action_frame->len)); + + brcmf_dbg(TRACE, "Action frame, cookie=%lld, len=%d, freq=%d\n", + *cookie, le16_to_cpu(action_frame->len), + chan->center_freq); + + ack = brcmf_p2p_send_action_frame(cfg, cfg_to_ndev(cfg), + af_params); + + cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, ack, + GFP_KERNEL); + kfree(af_params); + } else { + brcmf_dbg(TRACE, "Unhandled, fc=%04x!!\n", mgmt->frame_control); + brcmf_dbg_hex_dump(true, buf, len, "payload, len=%Zu\n", len); + } + +exit: + return err; +} + + +static int +brcmf_cfg80211_cancel_remain_on_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + u64 cookie) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_cfg80211_vif *vif; + int err = 0; + + brcmf_dbg(TRACE, "Enter p2p listen cancel\n"); + + vif = cfg->p2p.bss_idx[P2PAPI_BSSCFG_DEVICE].vif; + if (vif == NULL) { + brcmf_err("No p2p device available for probe response\n"); + err = -ENODEV; + goto exit; + } + brcmf_p2p_cancel_remain_on_channel(vif->ifp); +exit: + return err; +} + +static int brcmf_cfg80211_crit_proto_start(struct wiphy *wiphy, + struct wireless_dev *wdev, + enum nl80211_crit_proto_id proto, + u16 duration) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_cfg80211_vif *vif; + + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + + /* only DHCP support for now */ + if (proto != NL80211_CRIT_PROTO_DHCP) + return -EINVAL; + + /* suppress and abort scanning */ + set_bit(BRCMF_SCAN_STATUS_SUPPRESS, &cfg->scan_status); + brcmf_abort_scanning(cfg); + + return brcmf_btcoex_set_mode(vif, BRCMF_BTCOEX_DISABLED, duration); +} + +static void brcmf_cfg80211_crit_proto_stop(struct wiphy *wiphy, + struct wireless_dev *wdev) +{ + struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); + struct brcmf_cfg80211_vif *vif; + + vif = container_of(wdev, struct brcmf_cfg80211_vif, wdev); + + brcmf_btcoex_set_mode(vif, BRCMF_BTCOEX_ENABLED, 0); + clear_bit(BRCMF_SCAN_STATUS_SUPPRESS, &cfg->scan_status); +} + +static struct cfg80211_ops wl_cfg80211_ops = { + .add_virtual_intf = brcmf_cfg80211_add_iface, + .del_virtual_intf = brcmf_cfg80211_del_iface, + .change_virtual_intf = brcmf_cfg80211_change_iface, + .scan = brcmf_cfg80211_scan, + .set_wiphy_params = brcmf_cfg80211_set_wiphy_params, + .join_ibss = brcmf_cfg80211_join_ibss, + .leave_ibss = brcmf_cfg80211_leave_ibss, + .get_station = brcmf_cfg80211_get_station, + .set_tx_power = brcmf_cfg80211_set_tx_power, + .get_tx_power = brcmf_cfg80211_get_tx_power, + .add_key = brcmf_cfg80211_add_key, + .del_key = brcmf_cfg80211_del_key, + .get_key = brcmf_cfg80211_get_key, + .set_default_key = brcmf_cfg80211_config_default_key, + .set_default_mgmt_key = brcmf_cfg80211_config_default_mgmt_key, + .set_power_mgmt = brcmf_cfg80211_set_power_mgmt, + .connect = brcmf_cfg80211_connect, + .disconnect = brcmf_cfg80211_disconnect, + .suspend = brcmf_cfg80211_suspend, + .resume = brcmf_cfg80211_resume, + .set_pmksa = brcmf_cfg80211_set_pmksa, + .del_pmksa = brcmf_cfg80211_del_pmksa, + .flush_pmksa = brcmf_cfg80211_flush_pmksa, + .start_ap = brcmf_cfg80211_start_ap, + .stop_ap = brcmf_cfg80211_stop_ap, + .change_beacon = brcmf_cfg80211_change_beacon, + .del_station = brcmf_cfg80211_del_station, + .sched_scan_start = brcmf_cfg80211_sched_scan_start, + .sched_scan_stop = brcmf_cfg80211_sched_scan_stop, + .mgmt_frame_register = brcmf_cfg80211_mgmt_frame_register, + .mgmt_tx = brcmf_cfg80211_mgmt_tx, + .remain_on_channel = brcmf_p2p_remain_on_channel, + .cancel_remain_on_channel = brcmf_cfg80211_cancel_remain_on_channel, + .start_p2p_device = brcmf_p2p_start_device, + .stop_p2p_device = brcmf_p2p_stop_device, + .crit_proto_start = brcmf_cfg80211_crit_proto_start, + .crit_proto_stop = brcmf_cfg80211_crit_proto_stop, +#ifdef CPTCFG_NL80211_TESTMODE + .testmode_cmd = brcmf_cfg80211_testmode +#endif +}; + +static s32 brcmf_nl80211_iftype_to_mode(enum nl80211_iftype type) +{ + switch (type) { + case NL80211_IFTYPE_AP_VLAN: + case NL80211_IFTYPE_WDS: + case NL80211_IFTYPE_MONITOR: + case NL80211_IFTYPE_MESH_POINT: + return -ENOTSUPP; + case NL80211_IFTYPE_ADHOC: + return WL_MODE_IBSS; + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + return WL_MODE_BSS; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + return WL_MODE_AP; + case NL80211_IFTYPE_P2P_DEVICE: + return WL_MODE_P2P; + case NL80211_IFTYPE_UNSPECIFIED: + default: + break; + } + + return -EINVAL; +} + +static void brcmf_wiphy_pno_params(struct wiphy *wiphy) +{ + /* scheduled scan settings */ + wiphy->max_sched_scan_ssids = BRCMF_PNO_MAX_PFN_COUNT; + wiphy->max_match_sets = BRCMF_PNO_MAX_PFN_COUNT; + wiphy->max_sched_scan_ie_len = BRCMF_SCAN_IE_LEN_MAX; + wiphy->flags |= WIPHY_FLAG_SUPPORTS_SCHED_SCAN; +} + +static const struct ieee80211_iface_limit brcmf_iface_limits[] = { + { + .max = 2, + .types = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_AP) + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO) + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_P2P_DEVICE) + } +}; +static const struct ieee80211_iface_combination brcmf_iface_combos[] = { + { + .max_interfaces = BRCMF_IFACE_MAX_CNT, + .num_different_channels = 2, + .n_limits = ARRAY_SIZE(brcmf_iface_limits), + .limits = brcmf_iface_limits + } +}; + +static const struct ieee80211_txrx_stypes +brcmf_txrx_stypes[NUM_NL80211_IFTYPES] = { + [NL80211_IFTYPE_STATION] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + }, + [NL80211_IFTYPE_P2P_CLIENT] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + }, + [NL80211_IFTYPE_P2P_GO] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | + BIT(IEEE80211_STYPE_DISASSOC >> 4) | + BIT(IEEE80211_STYPE_AUTH >> 4) | + BIT(IEEE80211_STYPE_DEAUTH >> 4) | + BIT(IEEE80211_STYPE_ACTION >> 4) + }, + [NL80211_IFTYPE_P2P_DEVICE] = { + .tx = 0xffff, + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4) + } +}; + +static struct wiphy *brcmf_setup_wiphy(struct device *phydev) +{ + struct wiphy *wiphy; + s32 err = 0; + + wiphy = wiphy_new(&wl_cfg80211_ops, sizeof(struct brcmf_cfg80211_info)); + if (!wiphy) { + brcmf_err("Could not allocate wiphy device\n"); + return ERR_PTR(-ENOMEM); + } + set_wiphy_dev(wiphy, phydev); + wiphy->max_scan_ssids = WL_NUM_SCAN_MAX; + wiphy->max_scan_ie_len = BRCMF_SCAN_IE_LEN_MAX; + wiphy->max_num_pmkids = WL_NUM_PMKIDS_MAX; + wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_P2P_DEVICE); + wiphy->iface_combinations = brcmf_iface_combos; + wiphy->n_iface_combinations = ARRAY_SIZE(brcmf_iface_combos); + wiphy->bands[IEEE80211_BAND_2GHZ] = &__wl_band_2ghz; + wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + wiphy->cipher_suites = __wl_cipher_suites; + wiphy->n_cipher_suites = ARRAY_SIZE(__wl_cipher_suites); + wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT | + WIPHY_FLAG_OFFCHAN_TX | + WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; + wiphy->mgmt_stypes = brcmf_txrx_stypes; + wiphy->max_remain_on_channel_duration = 5000; + brcmf_wiphy_pno_params(wiphy); + brcmf_dbg(INFO, "Registering custom regulatory\n"); + wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY; + wiphy_apply_custom_regulatory(wiphy, &brcmf_regdom); + err = wiphy_register(wiphy); + if (err < 0) { + brcmf_err("Could not register wiphy device (%d)\n", err); + wiphy_free(wiphy); + return ERR_PTR(err); + } + return wiphy; +} + +struct brcmf_cfg80211_vif *brcmf_alloc_vif(struct brcmf_cfg80211_info *cfg, + enum nl80211_iftype type, + bool pm_block) +{ + struct brcmf_cfg80211_vif *vif; + + if (cfg->vif_cnt == BRCMF_IFACE_MAX_CNT) + return ERR_PTR(-ENOSPC); + + brcmf_dbg(TRACE, "allocating virtual interface (size=%zu)\n", + sizeof(*vif)); + vif = kzalloc(sizeof(*vif), GFP_KERNEL); + if (!vif) + return ERR_PTR(-ENOMEM); + + vif->wdev.wiphy = cfg->wiphy; + vif->wdev.iftype = type; + + vif->mode = brcmf_nl80211_iftype_to_mode(type); + vif->pm_block = pm_block; + vif->roam_off = -1; + + brcmf_init_prof(&vif->profile); + + list_add_tail(&vif->list, &cfg->vif_list); + cfg->vif_cnt++; + return vif; +} + +void brcmf_free_vif(struct brcmf_cfg80211_info *cfg, + struct brcmf_cfg80211_vif *vif) +{ + list_del(&vif->list); + cfg->vif_cnt--; + + kfree(vif); + if (!cfg->vif_cnt) { + wiphy_unregister(cfg->wiphy); + wiphy_free(cfg->wiphy); + } +} + +static bool brcmf_is_linkup(const struct brcmf_event_msg *e) +{ + u32 event = e->event_code; + u32 status = e->status; + + if (event == BRCMF_E_SET_SSID && status == BRCMF_E_STATUS_SUCCESS) { + brcmf_dbg(CONN, "Processing set ssid\n"); + return true; + } + + return false; +} + +static bool brcmf_is_linkdown(const struct brcmf_event_msg *e) +{ + u32 event = e->event_code; + u16 flags = e->flags; + + if (event == BRCMF_E_LINK && (!(flags & BRCMF_EVENT_MSG_LINK))) { + brcmf_dbg(CONN, "Processing link down\n"); + return true; + } + return false; +} + +static bool brcmf_is_nonetwork(struct brcmf_cfg80211_info *cfg, + const struct brcmf_event_msg *e) +{ + u32 event = e->event_code; + u32 status = e->status; + + if (event == BRCMF_E_LINK && status == BRCMF_E_STATUS_NO_NETWORKS) { + brcmf_dbg(CONN, "Processing Link %s & no network found\n", + e->flags & BRCMF_EVENT_MSG_LINK ? "up" : "down"); + return true; + } + + if (event == BRCMF_E_SET_SSID && status != BRCMF_E_STATUS_SUCCESS) { + brcmf_dbg(CONN, "Processing connecting & no network found\n"); + return true; + } + + return false; +} + +static void brcmf_clear_assoc_ies(struct brcmf_cfg80211_info *cfg) +{ + struct brcmf_cfg80211_connect_info *conn_info = cfg_to_conn(cfg); + + kfree(conn_info->req_ie); + conn_info->req_ie = NULL; + conn_info->req_ie_len = 0; + kfree(conn_info->resp_ie); + conn_info->resp_ie = NULL; + conn_info->resp_ie_len = 0; +} + +static s32 brcmf_get_assoc_ies(struct brcmf_cfg80211_info *cfg, + struct brcmf_if *ifp) +{ + struct brcmf_cfg80211_assoc_ielen_le *assoc_info; + struct brcmf_cfg80211_connect_info *conn_info = cfg_to_conn(cfg); + u32 req_len; + u32 resp_len; + s32 err = 0; + + brcmf_clear_assoc_ies(cfg); + + err = brcmf_fil_iovar_data_get(ifp, "assoc_info", + cfg->extra_buf, WL_ASSOC_INFO_MAX); + if (err) { + brcmf_err("could not get assoc info (%d)\n", err); + return err; + } + assoc_info = + (struct brcmf_cfg80211_assoc_ielen_le *)cfg->extra_buf; + req_len = le32_to_cpu(assoc_info->req_len); + resp_len = le32_to_cpu(assoc_info->resp_len); + if (req_len) { + err = brcmf_fil_iovar_data_get(ifp, "assoc_req_ies", + cfg->extra_buf, + WL_ASSOC_INFO_MAX); + if (err) { + brcmf_err("could not get assoc req (%d)\n", err); + return err; + } + conn_info->req_ie_len = req_len; + conn_info->req_ie = + kmemdup(cfg->extra_buf, conn_info->req_ie_len, + GFP_KERNEL); + } else { + conn_info->req_ie_len = 0; + conn_info->req_ie = NULL; + } + if (resp_len) { + err = brcmf_fil_iovar_data_get(ifp, "assoc_resp_ies", + cfg->extra_buf, + WL_ASSOC_INFO_MAX); + if (err) { + brcmf_err("could not get assoc resp (%d)\n", err); + return err; + } + conn_info->resp_ie_len = resp_len; + conn_info->resp_ie = + kmemdup(cfg->extra_buf, conn_info->resp_ie_len, + GFP_KERNEL); + } else { + conn_info->resp_ie_len = 0; + conn_info->resp_ie = NULL; + } + brcmf_dbg(CONN, "req len (%d) resp len (%d)\n", + conn_info->req_ie_len, conn_info->resp_ie_len); + + return err; +} + +static s32 +brcmf_bss_roaming_done(struct brcmf_cfg80211_info *cfg, + struct net_device *ndev, + const struct brcmf_event_msg *e) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; + struct brcmf_cfg80211_connect_info *conn_info = cfg_to_conn(cfg); + struct wiphy *wiphy = cfg_to_wiphy(cfg); + struct ieee80211_channel *notify_channel = NULL; + struct ieee80211_supported_band *band; + struct brcmf_bss_info_le *bi; + struct brcmu_chan ch; + u32 freq; + s32 err = 0; + u8 *buf; + + brcmf_dbg(TRACE, "Enter\n"); + + brcmf_get_assoc_ies(cfg, ifp); + memcpy(profile->bssid, e->addr, ETH_ALEN); + brcmf_update_bss_info(cfg, ifp); + + buf = kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL); + if (buf == NULL) { + err = -ENOMEM; + goto done; + } + + /* data sent to dongle has to be little endian */ + *(__le32 *)buf = cpu_to_le32(WL_BSS_INFO_MAX); + err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BSS_INFO, + buf, WL_BSS_INFO_MAX); + + if (err) + goto done; + + bi = (struct brcmf_bss_info_le *)(buf + 4); + ch.chspec = le16_to_cpu(bi->chanspec); + cfg->d11inf.decchspec(&ch); + + if (ch.band == BRCMU_CHAN_BAND_2G) + band = wiphy->bands[IEEE80211_BAND_2GHZ]; + else + band = wiphy->bands[IEEE80211_BAND_5GHZ]; + + freq = ieee80211_channel_to_frequency(ch.chnum, band->band); + notify_channel = ieee80211_get_channel(wiphy, freq); + +done: + kfree(buf); + cfg80211_roamed(ndev, notify_channel, (u8 *)profile->bssid, + conn_info->req_ie, conn_info->req_ie_len, + conn_info->resp_ie, conn_info->resp_ie_len, GFP_KERNEL); + brcmf_dbg(CONN, "Report roaming result\n"); + + set_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state); + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +brcmf_bss_connect_done(struct brcmf_cfg80211_info *cfg, + struct net_device *ndev, const struct brcmf_event_msg *e, + bool completed) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; + struct brcmf_cfg80211_connect_info *conn_info = cfg_to_conn(cfg); + s32 err = 0; + + brcmf_dbg(TRACE, "Enter\n"); + + if (test_and_clear_bit(BRCMF_VIF_STATUS_CONNECTING, + &ifp->vif->sme_state)) { + if (completed) { + brcmf_get_assoc_ies(cfg, ifp); + memcpy(profile->bssid, e->addr, ETH_ALEN); + brcmf_update_bss_info(cfg, ifp); + set_bit(BRCMF_VIF_STATUS_CONNECTED, + &ifp->vif->sme_state); + } + cfg80211_connect_result(ndev, + (u8 *)profile->bssid, + conn_info->req_ie, + conn_info->req_ie_len, + conn_info->resp_ie, + conn_info->resp_ie_len, + completed ? WLAN_STATUS_SUCCESS : + WLAN_STATUS_AUTH_TIMEOUT, + GFP_KERNEL); + brcmf_dbg(CONN, "Report connect result - connection %s\n", + completed ? "succeeded" : "failed"); + } + brcmf_dbg(TRACE, "Exit\n"); + return err; +} + +static s32 +brcmf_notify_connect_status_ap(struct brcmf_cfg80211_info *cfg, + struct net_device *ndev, + const struct brcmf_event_msg *e, void *data) +{ + static int generation; + u32 event = e->event_code; + u32 reason = e->reason; + struct station_info sinfo; + + brcmf_dbg(CONN, "event %d, reason %d\n", event, reason); + if (event == BRCMF_E_LINK && reason == BRCMF_E_REASON_LINK_BSSCFG_DIS && + ndev != cfg_to_ndev(cfg)) { + brcmf_dbg(CONN, "AP mode link down\n"); + complete(&cfg->vif_disabled); + return 0; + } + + if (((event == BRCMF_E_ASSOC_IND) || (event == BRCMF_E_REASSOC_IND)) && + (reason == BRCMF_E_STATUS_SUCCESS)) { + memset(&sinfo, 0, sizeof(sinfo)); + sinfo.filled = STATION_INFO_ASSOC_REQ_IES; + if (!data) { + brcmf_err("No IEs present in ASSOC/REASSOC_IND"); + return -EINVAL; + } + sinfo.assoc_req_ies = data; + sinfo.assoc_req_ies_len = e->datalen; + generation++; + sinfo.generation = generation; + cfg80211_new_sta(ndev, e->addr, &sinfo, GFP_KERNEL); + } else if ((event == BRCMF_E_DISASSOC_IND) || + (event == BRCMF_E_DEAUTH_IND) || + (event == BRCMF_E_DEAUTH)) { + cfg80211_del_sta(ndev, e->addr, GFP_KERNEL); + } + return 0; +} + +static s32 +brcmf_notify_connect_status(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, void *data) +{ + struct brcmf_cfg80211_info *cfg = ifp->drvr->config; + struct net_device *ndev = ifp->ndev; + struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; + s32 err = 0; + + if (ifp->vif->mode == WL_MODE_AP) { + err = brcmf_notify_connect_status_ap(cfg, ndev, e, data); + } else if (brcmf_is_linkup(e)) { + brcmf_dbg(CONN, "Linkup\n"); + if (brcmf_is_ibssmode(ifp->vif)) { + memcpy(profile->bssid, e->addr, ETH_ALEN); + wl_inform_ibss(cfg, ndev, e->addr); + cfg80211_ibss_joined(ndev, e->addr, GFP_KERNEL); + clear_bit(BRCMF_VIF_STATUS_CONNECTING, + &ifp->vif->sme_state); + set_bit(BRCMF_VIF_STATUS_CONNECTED, + &ifp->vif->sme_state); + } else + brcmf_bss_connect_done(cfg, ndev, e, true); + } else if (brcmf_is_linkdown(e)) { + brcmf_dbg(CONN, "Linkdown\n"); + if (!brcmf_is_ibssmode(ifp->vif)) { + brcmf_bss_connect_done(cfg, ndev, e, false); + if (test_and_clear_bit(BRCMF_VIF_STATUS_CONNECTED, + &ifp->vif->sme_state)) + cfg80211_disconnected(ndev, 0, NULL, 0, + GFP_KERNEL); + } + brcmf_link_down(ifp->vif); + brcmf_init_prof(ndev_to_prof(ndev)); + if (ndev != cfg_to_ndev(cfg)) + complete(&cfg->vif_disabled); + } else if (brcmf_is_nonetwork(cfg, e)) { + if (brcmf_is_ibssmode(ifp->vif)) + clear_bit(BRCMF_VIF_STATUS_CONNECTING, + &ifp->vif->sme_state); + else + brcmf_bss_connect_done(cfg, ndev, e, false); + } + + return err; +} + +static s32 +brcmf_notify_roaming_status(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, void *data) +{ + struct brcmf_cfg80211_info *cfg = ifp->drvr->config; + s32 err = 0; + u32 event = e->event_code; + u32 status = e->status; + + if (event == BRCMF_E_ROAM && status == BRCMF_E_STATUS_SUCCESS) { + if (test_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state)) + brcmf_bss_roaming_done(cfg, ifp->ndev, e); + else + brcmf_bss_connect_done(cfg, ifp->ndev, e, true); + } + + return err; +} + +static s32 +brcmf_notify_mic_status(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, void *data) +{ + u16 flags = e->flags; + enum nl80211_key_type key_type; + + if (flags & BRCMF_EVENT_MSG_GROUP) + key_type = NL80211_KEYTYPE_GROUP; + else + key_type = NL80211_KEYTYPE_PAIRWISE; + + cfg80211_michael_mic_failure(ifp->ndev, (u8 *)&e->addr, key_type, -1, + NULL, GFP_KERNEL); + + return 0; +} + +static s32 brcmf_notify_vif_event(struct brcmf_if *ifp, + const struct brcmf_event_msg *e, void *data) +{ + struct brcmf_cfg80211_info *cfg = ifp->drvr->config; + struct brcmf_if_event *ifevent = (struct brcmf_if_event *)data; + struct brcmf_cfg80211_vif_event *event = &cfg->vif_event; + struct brcmf_cfg80211_vif *vif; + + brcmf_dbg(TRACE, "Enter: action %u flags %u ifidx %u bsscfg %u\n", + ifevent->action, ifevent->flags, ifevent->ifidx, + ifevent->bssidx); + + mutex_lock(&event->vif_event_lock); + event->action = ifevent->action; + vif = event->vif; + + switch (ifevent->action) { + case BRCMF_E_IF_ADD: + /* waiting process may have timed out */ + if (!cfg->vif_event.vif) { + mutex_unlock(&event->vif_event_lock); + return -EBADF; + } + + ifp->vif = vif; + vif->ifp = ifp; + if (ifp->ndev) { + vif->wdev.netdev = ifp->ndev; + ifp->ndev->ieee80211_ptr = &vif->wdev; + SET_NETDEV_DEV(ifp->ndev, wiphy_dev(cfg->wiphy)); + } + mutex_unlock(&event->vif_event_lock); + wake_up(&event->vif_wq); + return 0; + + case BRCMF_E_IF_DEL: + mutex_unlock(&event->vif_event_lock); + /* event may not be upon user request */ + if (brcmf_cfg80211_vif_event_armed(cfg)) + wake_up(&event->vif_wq); + return 0; + + case BRCMF_E_IF_CHANGE: + mutex_unlock(&event->vif_event_lock); + wake_up(&event->vif_wq); + return 0; + + default: + mutex_unlock(&event->vif_event_lock); + break; + } + return -EINVAL; +} + +static void brcmf_init_conf(struct brcmf_cfg80211_conf *conf) +{ + conf->frag_threshold = (u32)-1; + conf->rts_threshold = (u32)-1; + conf->retry_short = (u32)-1; + conf->retry_long = (u32)-1; + conf->tx_power = -1; +} + +static void brcmf_register_event_handlers(struct brcmf_cfg80211_info *cfg) +{ + brcmf_fweh_register(cfg->pub, BRCMF_E_LINK, + brcmf_notify_connect_status); + brcmf_fweh_register(cfg->pub, BRCMF_E_DEAUTH_IND, + brcmf_notify_connect_status); + brcmf_fweh_register(cfg->pub, BRCMF_E_DEAUTH, + brcmf_notify_connect_status); + brcmf_fweh_register(cfg->pub, BRCMF_E_DISASSOC_IND, + brcmf_notify_connect_status); + brcmf_fweh_register(cfg->pub, BRCMF_E_ASSOC_IND, + brcmf_notify_connect_status); + brcmf_fweh_register(cfg->pub, BRCMF_E_REASSOC_IND, + brcmf_notify_connect_status); + brcmf_fweh_register(cfg->pub, BRCMF_E_ROAM, + brcmf_notify_roaming_status); + brcmf_fweh_register(cfg->pub, BRCMF_E_MIC_ERROR, + brcmf_notify_mic_status); + brcmf_fweh_register(cfg->pub, BRCMF_E_SET_SSID, + brcmf_notify_connect_status); + brcmf_fweh_register(cfg->pub, BRCMF_E_PFN_NET_FOUND, + brcmf_notify_sched_scan_results); + brcmf_fweh_register(cfg->pub, BRCMF_E_IF, + brcmf_notify_vif_event); + brcmf_fweh_register(cfg->pub, BRCMF_E_P2P_PROBEREQ_MSG, + brcmf_p2p_notify_rx_mgmt_p2p_probereq); + brcmf_fweh_register(cfg->pub, BRCMF_E_P2P_DISC_LISTEN_COMPLETE, + brcmf_p2p_notify_listen_complete); + brcmf_fweh_register(cfg->pub, BRCMF_E_ACTION_FRAME_RX, + brcmf_p2p_notify_action_frame_rx); + brcmf_fweh_register(cfg->pub, BRCMF_E_ACTION_FRAME_COMPLETE, + brcmf_p2p_notify_action_tx_complete); + brcmf_fweh_register(cfg->pub, BRCMF_E_ACTION_FRAME_OFF_CHAN_COMPLETE, + brcmf_p2p_notify_action_tx_complete); +} + +static void brcmf_deinit_priv_mem(struct brcmf_cfg80211_info *cfg) +{ + kfree(cfg->conf); + cfg->conf = NULL; + kfree(cfg->escan_ioctl_buf); + cfg->escan_ioctl_buf = NULL; + kfree(cfg->extra_buf); + cfg->extra_buf = NULL; + kfree(cfg->pmk_list); + cfg->pmk_list = NULL; +} + +static s32 brcmf_init_priv_mem(struct brcmf_cfg80211_info *cfg) +{ + cfg->conf = kzalloc(sizeof(*cfg->conf), GFP_KERNEL); + if (!cfg->conf) + goto init_priv_mem_out; + cfg->escan_ioctl_buf = kzalloc(BRCMF_DCMD_MEDLEN, GFP_KERNEL); + if (!cfg->escan_ioctl_buf) + goto init_priv_mem_out; + cfg->extra_buf = kzalloc(WL_EXTRA_BUF_MAX, GFP_KERNEL); + if (!cfg->extra_buf) + goto init_priv_mem_out; + cfg->pmk_list = kzalloc(sizeof(*cfg->pmk_list), GFP_KERNEL); + if (!cfg->pmk_list) + goto init_priv_mem_out; + + return 0; + +init_priv_mem_out: + brcmf_deinit_priv_mem(cfg); + + return -ENOMEM; +} + +static s32 wl_init_priv(struct brcmf_cfg80211_info *cfg) +{ + s32 err = 0; + + cfg->scan_request = NULL; + cfg->pwr_save = true; + cfg->roam_on = true; /* roam on & off switch. + we enable roam per default */ + cfg->active_scan = true; /* we do active scan for + specific scan per default */ + cfg->dongle_up = false; /* dongle is not up yet */ + err = brcmf_init_priv_mem(cfg); + if (err) + return err; + brcmf_register_event_handlers(cfg); + mutex_init(&cfg->usr_sync); + brcmf_init_escan(cfg); + brcmf_init_conf(cfg->conf); + init_completion(&cfg->vif_disabled); + return err; +} + +static void wl_deinit_priv(struct brcmf_cfg80211_info *cfg) +{ + cfg->dongle_up = false; /* dongle down */ + brcmf_abort_scanning(cfg); + brcmf_deinit_priv_mem(cfg); +} + +static void init_vif_event(struct brcmf_cfg80211_vif_event *event) +{ + init_waitqueue_head(&event->vif_wq); + mutex_init(&event->vif_event_lock); +} + +struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr, + struct device *busdev) +{ + struct net_device *ndev = drvr->iflist[0]->ndev; + struct brcmf_cfg80211_info *cfg; + struct wiphy *wiphy; + struct brcmf_cfg80211_vif *vif; + struct brcmf_if *ifp; + s32 err = 0; + s32 io_type; + + if (!ndev) { + brcmf_err("ndev is invalid\n"); + return NULL; + } + + ifp = netdev_priv(ndev); + wiphy = brcmf_setup_wiphy(busdev); + if (IS_ERR(wiphy)) + return NULL; + + cfg = wiphy_priv(wiphy); + cfg->wiphy = wiphy; + cfg->pub = drvr; + init_vif_event(&cfg->vif_event); + INIT_LIST_HEAD(&cfg->vif_list); + + vif = brcmf_alloc_vif(cfg, NL80211_IFTYPE_STATION, false); + if (IS_ERR(vif)) { + wiphy_free(wiphy); + return NULL; + } + + vif->ifp = ifp; + vif->wdev.netdev = ndev; + ndev->ieee80211_ptr = &vif->wdev; + SET_NETDEV_DEV(ndev, wiphy_dev(cfg->wiphy)); + + err = wl_init_priv(cfg); + if (err) { + brcmf_err("Failed to init iwm_priv (%d)\n", err); + goto cfg80211_attach_out; + } + ifp->vif = vif; + + err = brcmf_p2p_attach(cfg); + if (err) { + brcmf_err("P2P initilisation failed (%d)\n", err); + goto cfg80211_p2p_attach_out; + } + err = brcmf_btcoex_attach(cfg); + if (err) { + brcmf_err("BT-coex initialisation failed (%d)\n", err); + brcmf_p2p_detach(&cfg->p2p); + goto cfg80211_p2p_attach_out; + } + + err = brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_VERSION, + &io_type); + if (err) { + brcmf_err("Failed to get D11 version (%d)\n", err); + goto cfg80211_p2p_attach_out; + } + cfg->d11inf.io_type = (u8)io_type; + brcmu_d11_attach(&cfg->d11inf); + + return cfg; + +cfg80211_p2p_attach_out: + wl_deinit_priv(cfg); + +cfg80211_attach_out: + brcmf_free_vif(cfg, vif); + return NULL; +} + +void brcmf_cfg80211_detach(struct brcmf_cfg80211_info *cfg) +{ + struct brcmf_cfg80211_vif *vif; + struct brcmf_cfg80211_vif *tmp; + + wl_deinit_priv(cfg); + brcmf_btcoex_detach(cfg); + list_for_each_entry_safe(vif, tmp, &cfg->vif_list, list) { + brcmf_free_vif(cfg, vif); + } +} + +static s32 +brcmf_dongle_roam(struct brcmf_if *ifp, u32 roamvar, u32 bcn_timeout) +{ + s32 err = 0; + __le32 roamtrigger[2]; + __le32 roam_delta[2]; + + /* + * Setup timeout if Beacons are lost and roam is + * off to report link down + */ + if (roamvar) { + err = brcmf_fil_iovar_int_set(ifp, "bcn_timeout", bcn_timeout); + if (err) { + brcmf_err("bcn_timeout error (%d)\n", err); + goto dongle_rom_out; + } + } + + /* + * Enable/Disable built-in roaming to allow supplicant + * to take care of roaming + */ + brcmf_dbg(INFO, "Internal Roaming = %s\n", roamvar ? "Off" : "On"); + err = brcmf_fil_iovar_int_set(ifp, "roam_off", roamvar); + if (err) { + brcmf_err("roam_off error (%d)\n", err); + goto dongle_rom_out; + } + + roamtrigger[0] = cpu_to_le32(WL_ROAM_TRIGGER_LEVEL); + roamtrigger[1] = cpu_to_le32(BRCM_BAND_ALL); + err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_ROAM_TRIGGER, + (void *)roamtrigger, sizeof(roamtrigger)); + if (err) { + brcmf_err("WLC_SET_ROAM_TRIGGER error (%d)\n", err); + goto dongle_rom_out; + } + + roam_delta[0] = cpu_to_le32(WL_ROAM_DELTA); + roam_delta[1] = cpu_to_le32(BRCM_BAND_ALL); + err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_ROAM_DELTA, + (void *)roam_delta, sizeof(roam_delta)); + if (err) { + brcmf_err("WLC_SET_ROAM_DELTA error (%d)\n", err); + goto dongle_rom_out; + } + +dongle_rom_out: + return err; +} + +static s32 +brcmf_dongle_scantime(struct brcmf_if *ifp, s32 scan_assoc_time, + s32 scan_unassoc_time, s32 scan_passive_time) +{ + s32 err = 0; + + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCAN_CHANNEL_TIME, + scan_assoc_time); + if (err) { + if (err == -EOPNOTSUPP) + brcmf_dbg(INFO, "Scan assoc time is not supported\n"); + else + brcmf_err("Scan assoc time error (%d)\n", err); + goto dongle_scantime_out; + } + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCAN_UNASSOC_TIME, + scan_unassoc_time); + if (err) { + if (err == -EOPNOTSUPP) + brcmf_dbg(INFO, "Scan unassoc time is not supported\n"); + else + brcmf_err("Scan unassoc time error (%d)\n", err); + goto dongle_scantime_out; + } + + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCAN_PASSIVE_TIME, + scan_passive_time); + if (err) { + if (err == -EOPNOTSUPP) + brcmf_dbg(INFO, "Scan passive time is not supported\n"); + else + brcmf_err("Scan passive time error (%d)\n", err); + goto dongle_scantime_out; + } + +dongle_scantime_out: + return err; +} + + +static s32 brcmf_construct_reginfo(struct brcmf_cfg80211_info *cfg, u32 bw_cap) +{ + struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg)); + struct ieee80211_channel *band_chan_arr; + struct brcmf_chanspec_list *list; + struct brcmu_chan ch; + s32 err; + u8 *pbuf; + u32 i, j; + u32 total; + enum ieee80211_band band; + u32 channel; + u32 *n_cnt; + bool ht40_allowed; + u32 index; + u32 ht40_flag; + bool update; + u32 array_size; + + pbuf = kzalloc(BRCMF_DCMD_MEDLEN, GFP_KERNEL); + + if (pbuf == NULL) + return -ENOMEM; + + list = (struct brcmf_chanspec_list *)pbuf; + + err = brcmf_fil_iovar_data_get(ifp, "chanspecs", pbuf, + BRCMF_DCMD_MEDLEN); + if (err) { + brcmf_err("get chanspecs error (%d)\n", err); + goto exit; + } + + __wl_band_2ghz.n_channels = 0; + __wl_band_5ghz_a.n_channels = 0; + + total = le32_to_cpu(list->count); + for (i = 0; i < total; i++) { + ch.chspec = (u16)le32_to_cpu(list->element[i]); + cfg->d11inf.decchspec(&ch); + + if (ch.band == BRCMU_CHAN_BAND_2G) { + band_chan_arr = __wl_2ghz_channels; + array_size = ARRAY_SIZE(__wl_2ghz_channels); + n_cnt = &__wl_band_2ghz.n_channels; + band = IEEE80211_BAND_2GHZ; + ht40_allowed = (bw_cap == WLC_N_BW_40ALL); + } else if (ch.band == BRCMU_CHAN_BAND_5G) { + band_chan_arr = __wl_5ghz_a_channels; + array_size = ARRAY_SIZE(__wl_5ghz_a_channels); + n_cnt = &__wl_band_5ghz_a.n_channels; + band = IEEE80211_BAND_5GHZ; + ht40_allowed = !(bw_cap == WLC_N_BW_20ALL); + } else { + brcmf_err("Invalid channel Sepc. 0x%x.\n", ch.chspec); + continue; + } + if (!ht40_allowed && ch.bw == BRCMU_CHAN_BW_40) + continue; + update = false; + for (j = 0; (j < *n_cnt && (*n_cnt < array_size)); j++) { + if (band_chan_arr[j].hw_value == ch.chnum) { + update = true; + break; + } + } + if (update) + index = j; + else + index = *n_cnt; + if (index < array_size) { + band_chan_arr[index].center_freq = + ieee80211_channel_to_frequency(ch.chnum, band); + band_chan_arr[index].hw_value = ch.chnum; + + if (ch.bw == BRCMU_CHAN_BW_40 && ht40_allowed) { + /* assuming the order is HT20, HT40 Upper, + * HT40 lower from chanspecs + */ + ht40_flag = band_chan_arr[index].flags & + IEEE80211_CHAN_NO_HT40; + if (ch.sb == BRCMU_CHAN_SB_U) { + if (ht40_flag == IEEE80211_CHAN_NO_HT40) + band_chan_arr[index].flags &= + ~IEEE80211_CHAN_NO_HT40; + band_chan_arr[index].flags |= + IEEE80211_CHAN_NO_HT40PLUS; + } else { + /* It should be one of + * IEEE80211_CHAN_NO_HT40 or + * IEEE80211_CHAN_NO_HT40PLUS + */ + band_chan_arr[index].flags &= + ~IEEE80211_CHAN_NO_HT40; + if (ht40_flag == IEEE80211_CHAN_NO_HT40) + band_chan_arr[index].flags |= + IEEE80211_CHAN_NO_HT40MINUS; + } + } else { + band_chan_arr[index].flags = + IEEE80211_CHAN_NO_HT40; + ch.bw = BRCMU_CHAN_BW_20; + cfg->d11inf.encchspec(&ch); + channel = ch.chspec; + err = brcmf_fil_bsscfg_int_get(ifp, + "per_chan_info", + &channel); + if (!err) { + if (channel & WL_CHAN_RADAR) + band_chan_arr[index].flags |= + (IEEE80211_CHAN_RADAR | + IEEE80211_CHAN_NO_IBSS); + if (channel & WL_CHAN_PASSIVE) + band_chan_arr[index].flags |= + IEEE80211_CHAN_PASSIVE_SCAN; + } + } + if (!update) + (*n_cnt)++; + } + } +exit: + kfree(pbuf); + return err; +} + + +static s32 brcmf_update_wiphybands(struct brcmf_cfg80211_info *cfg) +{ + struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg)); + struct wiphy *wiphy; + s32 phy_list; + u32 band_list[3]; + u32 nmode; + u32 bw_cap = 0; + s8 phy; + s32 err; + u32 nband; + s32 i; + struct ieee80211_supported_band *bands[IEEE80211_NUM_BANDS]; + s32 index; + + err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_PHYLIST, + &phy_list, sizeof(phy_list)); + if (err) { + brcmf_err("BRCMF_C_GET_PHYLIST error (%d)\n", err); + return err; + } + + phy = ((char *)&phy_list)[0]; + brcmf_dbg(INFO, "BRCMF_C_GET_PHYLIST reported: %c phy\n", phy); + + + err = brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BANDLIST, + &band_list, sizeof(band_list)); + if (err) { + brcmf_err("BRCMF_C_GET_BANDLIST error (%d)\n", err); + return err; + } + brcmf_dbg(INFO, "BRCMF_C_GET_BANDLIST reported: 0x%08x 0x%08x 0x%08x phy\n", + band_list[0], band_list[1], band_list[2]); + + err = brcmf_fil_iovar_int_get(ifp, "nmode", &nmode); + if (err) { + brcmf_err("nmode error (%d)\n", err); + } else { + err = brcmf_fil_iovar_int_get(ifp, "mimo_bw_cap", &bw_cap); + if (err) + brcmf_err("mimo_bw_cap error (%d)\n", err); + } + brcmf_dbg(INFO, "nmode=%d, mimo_bw_cap=%d\n", nmode, bw_cap); + + err = brcmf_construct_reginfo(cfg, bw_cap); + if (err) { + brcmf_err("brcmf_construct_reginfo failed (%d)\n", err); + return err; + } + + nband = band_list[0]; + memset(bands, 0, sizeof(bands)); + + for (i = 1; i <= nband && i < ARRAY_SIZE(band_list); i++) { + index = -1; + if ((band_list[i] == WLC_BAND_5G) && + (__wl_band_5ghz_a.n_channels > 0)) { + index = IEEE80211_BAND_5GHZ; + bands[index] = &__wl_band_5ghz_a; + if ((bw_cap == WLC_N_BW_40ALL) || + (bw_cap == WLC_N_BW_20IN2G_40IN5G)) + bands[index]->ht_cap.cap |= + IEEE80211_HT_CAP_SGI_40; + } else if ((band_list[i] == WLC_BAND_2G) && + (__wl_band_2ghz.n_channels > 0)) { + index = IEEE80211_BAND_2GHZ; + bands[index] = &__wl_band_2ghz; + if (bw_cap == WLC_N_BW_40ALL) + bands[index]->ht_cap.cap |= + IEEE80211_HT_CAP_SGI_40; + } + + if ((index >= 0) && nmode) { + bands[index]->ht_cap.cap |= IEEE80211_HT_CAP_SGI_20; + bands[index]->ht_cap.cap |= IEEE80211_HT_CAP_DSSSCCK40; + bands[index]->ht_cap.ht_supported = true; + bands[index]->ht_cap.ampdu_factor = + IEEE80211_HT_MAX_AMPDU_64K; + bands[index]->ht_cap.ampdu_density = + IEEE80211_HT_MPDU_DENSITY_16; + /* An HT shall support all EQM rates for one spatial + * stream + */ + bands[index]->ht_cap.mcs.rx_mask[0] = 0xff; + } + } + + wiphy = cfg_to_wiphy(cfg); + wiphy->bands[IEEE80211_BAND_2GHZ] = bands[IEEE80211_BAND_2GHZ]; + wiphy->bands[IEEE80211_BAND_5GHZ] = bands[IEEE80211_BAND_5GHZ]; + wiphy_apply_custom_regulatory(wiphy, &brcmf_regdom); + + return err; +} + + +static s32 brcmf_dongle_probecap(struct brcmf_cfg80211_info *cfg) +{ + return brcmf_update_wiphybands(cfg); +} + +static s32 brcmf_config_dongle(struct brcmf_cfg80211_info *cfg) +{ + struct net_device *ndev; + struct wireless_dev *wdev; + struct brcmf_if *ifp; + s32 power_mode; + s32 err = 0; + + if (cfg->dongle_up) + return err; + + ndev = cfg_to_ndev(cfg); + wdev = ndev->ieee80211_ptr; + ifp = netdev_priv(ndev); + + /* make sure RF is ready for work */ + brcmf_fil_cmd_int_set(ifp, BRCMF_C_UP, 0); + + brcmf_dongle_scantime(ifp, WL_SCAN_CHANNEL_TIME, + WL_SCAN_UNASSOC_TIME, WL_SCAN_PASSIVE_TIME); + + power_mode = cfg->pwr_save ? PM_FAST : PM_OFF; + err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PM, power_mode); + if (err) + goto default_conf_out; + brcmf_dbg(INFO, "power save set to %s\n", + (power_mode ? "enabled" : "disabled")); + + err = brcmf_dongle_roam(ifp, (cfg->roam_on ? 0 : 1), WL_BEACON_TIMEOUT); + if (err) + goto default_conf_out; + err = brcmf_cfg80211_change_iface(wdev->wiphy, ndev, wdev->iftype, + NULL, NULL); + if (err) + goto default_conf_out; + err = brcmf_dongle_probecap(cfg); + if (err) + goto default_conf_out; + + brcmf_configure_arp_offload(ifp, true); + + cfg->dongle_up = true; +default_conf_out: + + return err; + +} + +static s32 __brcmf_cfg80211_up(struct brcmf_if *ifp) +{ + set_bit(BRCMF_VIF_STATUS_READY, &ifp->vif->sme_state); + + return brcmf_config_dongle(ifp->drvr->config); +} + +static s32 __brcmf_cfg80211_down(struct brcmf_if *ifp) +{ + struct brcmf_cfg80211_info *cfg = ifp->drvr->config; + + /* + * While going down, if associated with AP disassociate + * from AP to save power + */ + if (check_vif_up(ifp->vif)) { + brcmf_link_down(ifp->vif); + + /* Make sure WPA_Supplicant receives all the event + generated due to DISASSOC call to the fw to keep + the state fw and WPA_Supplicant state consistent + */ + brcmf_delay(500); + } + + brcmf_abort_scanning(cfg); + clear_bit(BRCMF_VIF_STATUS_READY, &ifp->vif->sme_state); + + return 0; +} + +s32 brcmf_cfg80211_up(struct net_device *ndev) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_cfg80211_info *cfg = ifp->drvr->config; + s32 err = 0; + + mutex_lock(&cfg->usr_sync); + err = __brcmf_cfg80211_up(ifp); + mutex_unlock(&cfg->usr_sync); + + return err; +} + +s32 brcmf_cfg80211_down(struct net_device *ndev) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + struct brcmf_cfg80211_info *cfg = ifp->drvr->config; + s32 err = 0; + + mutex_lock(&cfg->usr_sync); + err = __brcmf_cfg80211_down(ifp); + mutex_unlock(&cfg->usr_sync); + + return err; +} + +enum nl80211_iftype brcmf_cfg80211_get_iftype(struct brcmf_if *ifp) +{ + struct wireless_dev *wdev = &ifp->vif->wdev; + + return wdev->iftype; +} + +u32 wl_get_vif_state_all(struct brcmf_cfg80211_info *cfg, unsigned long state) +{ + struct brcmf_cfg80211_vif *vif; + bool result = 0; + + list_for_each_entry(vif, &cfg->vif_list, list) { + if (test_bit(state, &vif->sme_state)) + result++; + } + return result; +} + +static inline bool vif_event_equals(struct brcmf_cfg80211_vif_event *event, + u8 action) +{ + u8 evt_action; + + mutex_lock(&event->vif_event_lock); + evt_action = event->action; + mutex_unlock(&event->vif_event_lock); + return evt_action == action; +} + +void brcmf_cfg80211_arm_vif_event(struct brcmf_cfg80211_info *cfg, + struct brcmf_cfg80211_vif *vif) +{ + struct brcmf_cfg80211_vif_event *event = &cfg->vif_event; + + mutex_lock(&event->vif_event_lock); + event->vif = vif; + event->action = 0; + mutex_unlock(&event->vif_event_lock); +} + +bool brcmf_cfg80211_vif_event_armed(struct brcmf_cfg80211_info *cfg) +{ + struct brcmf_cfg80211_vif_event *event = &cfg->vif_event; + bool armed; + + mutex_lock(&event->vif_event_lock); + armed = event->vif != NULL; + mutex_unlock(&event->vif_event_lock); + + return armed; +} +int brcmf_cfg80211_wait_vif_event_timeout(struct brcmf_cfg80211_info *cfg, + u8 action, ulong timeout) +{ + struct brcmf_cfg80211_vif_event *event = &cfg->vif_event; + + return wait_event_timeout(event->vif_wq, + vif_event_equals(event, action), timeout); +} + diff --git a/drivers/net/wireless/brcm80211/brcmfmac/wl_cfg80211.h b/drivers/net/wireless/brcm80211/brcmfmac/wl_cfg80211.h new file mode 100644 index 0000000..d9bdaf9 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmfmac/wl_cfg80211.h @@ -0,0 +1,511 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _wl_cfg80211_h_ +#define _wl_cfg80211_h_ + +/* for brcmu_d11inf */ +#include + +#define WL_NUM_SCAN_MAX 10 +#define WL_NUM_PMKIDS_MAX MAXPMKID +#define WL_TLV_INFO_MAX 1024 +#define WL_BSS_INFO_MAX 2048 +#define WL_ASSOC_INFO_MAX 512 /* assoc related fil max buf */ +#define WL_EXTRA_BUF_MAX 2048 +#define WL_ROAM_TRIGGER_LEVEL -75 +#define WL_ROAM_DELTA 20 +#define WL_BEACON_TIMEOUT 3 + +#define WL_SCAN_CHANNEL_TIME 40 +#define WL_SCAN_UNASSOC_TIME 40 +#define WL_SCAN_PASSIVE_TIME 120 + +#define WL_ESCAN_BUF_SIZE (1024 * 64) +#define WL_ESCAN_TIMER_INTERVAL_MS 8000 /* E-Scan timeout */ + +#define WL_ESCAN_ACTION_START 1 +#define WL_ESCAN_ACTION_CONTINUE 2 +#define WL_ESCAN_ACTION_ABORT 3 + +#define WL_AUTH_SHARED_KEY 1 /* d11 shared authentication */ +#define IE_MAX_LEN 512 + +/* IE TLV processing */ +#define TLV_LEN_OFF 1 /* length offset */ +#define TLV_HDR_LEN 2 /* header length */ +#define TLV_BODY_OFF 2 /* body offset */ +#define TLV_OUI_LEN 3 /* oui id length */ + +/* 802.11 Mgmt Packet flags */ +#define BRCMF_VNDR_IE_BEACON_FLAG 0x1 +#define BRCMF_VNDR_IE_PRBRSP_FLAG 0x2 +#define BRCMF_VNDR_IE_ASSOCRSP_FLAG 0x4 +#define BRCMF_VNDR_IE_AUTHRSP_FLAG 0x8 +#define BRCMF_VNDR_IE_PRBREQ_FLAG 0x10 +#define BRCMF_VNDR_IE_ASSOCREQ_FLAG 0x20 +/* vendor IE in IW advertisement protocol ID field */ +#define BRCMF_VNDR_IE_IWAPID_FLAG 0x40 +/* allow custom IE id */ +#define BRCMF_VNDR_IE_CUSTOM_FLAG 0x100 + +/* P2P Action Frames flags (spec ordered) */ +#define BRCMF_VNDR_IE_GONREQ_FLAG 0x001000 +#define BRCMF_VNDR_IE_GONRSP_FLAG 0x002000 +#define BRCMF_VNDR_IE_GONCFM_FLAG 0x004000 +#define BRCMF_VNDR_IE_INVREQ_FLAG 0x008000 +#define BRCMF_VNDR_IE_INVRSP_FLAG 0x010000 +#define BRCMF_VNDR_IE_DISREQ_FLAG 0x020000 +#define BRCMF_VNDR_IE_DISRSP_FLAG 0x040000 +#define BRCMF_VNDR_IE_PRDREQ_FLAG 0x080000 +#define BRCMF_VNDR_IE_PRDRSP_FLAG 0x100000 + +#define BRCMF_VNDR_IE_P2PAF_SHIFT 12 + + +/** + * enum brcmf_scan_status - scan engine status + * + * @BRCMF_SCAN_STATUS_BUSY: scanning in progress on dongle. + * @BRCMF_SCAN_STATUS_ABORT: scan being aborted on dongle. + * @BRCMF_SCAN_STATUS_SUPPRESS: scanning is suppressed in driver. + */ +enum brcmf_scan_status { + BRCMF_SCAN_STATUS_BUSY, + BRCMF_SCAN_STATUS_ABORT, + BRCMF_SCAN_STATUS_SUPPRESS, +}; + +/** + * enum wl_mode - driver mode of virtual interface. + * + * @WL_MODE_BSS: connects to BSS. + * @WL_MODE_IBSS: operate as ad-hoc. + * @WL_MODE_AP: operate as access-point. + * @WL_MODE_P2P: provide P2P discovery. + */ +enum wl_mode { + WL_MODE_BSS, + WL_MODE_IBSS, + WL_MODE_AP, + WL_MODE_P2P +}; + +/* dongle configuration */ +struct brcmf_cfg80211_conf { + u32 frag_threshold; + u32 rts_threshold; + u32 retry_short; + u32 retry_long; + s32 tx_power; + struct ieee80211_channel channel; +}; + +/* basic structure of scan request */ +struct brcmf_cfg80211_scan_req { + struct brcmf_ssid_le ssid_le; +}; + +/* basic structure of information element */ +struct brcmf_cfg80211_ie { + u16 offset; + u8 buf[WL_TLV_INFO_MAX]; +}; + +/* security information with currently associated ap */ +struct brcmf_cfg80211_security { + u32 wpa_versions; + u32 auth_type; + u32 cipher_pairwise; + u32 cipher_group; + u32 wpa_auth; +}; + +/** + * struct brcmf_cfg80211_profile - profile information. + * + * @ssid: ssid of associated/associating ap. + * @bssid: bssid of joined/joining ibss. + * @sec: security information. + */ +struct brcmf_cfg80211_profile { + struct brcmf_ssid ssid; + u8 bssid[ETH_ALEN]; + struct brcmf_cfg80211_security sec; +}; + +/** + * enum brcmf_vif_status - bit indices for vif status. + * + * @BRCMF_VIF_STATUS_READY: ready for operation. + * @BRCMF_VIF_STATUS_CONNECTING: connect/join in progress. + * @BRCMF_VIF_STATUS_CONNECTED: connected/joined succesfully. + * @BRCMF_VIF_STATUS_DISCONNECTING: disconnect/disable in progress. + * @BRCMF_VIF_STATUS_AP_CREATING: interface configured for AP operation. + * @BRCMF_VIF_STATUS_AP_CREATED: AP operation started. + */ +enum brcmf_vif_status { + BRCMF_VIF_STATUS_READY, + BRCMF_VIF_STATUS_CONNECTING, + BRCMF_VIF_STATUS_CONNECTED, + BRCMF_VIF_STATUS_DISCONNECTING, + BRCMF_VIF_STATUS_AP_CREATING, + BRCMF_VIF_STATUS_AP_CREATED +}; + +/** + * struct vif_saved_ie - holds saved IEs for a virtual interface. + * + * @probe_req_ie: IE info for probe request. + * @probe_res_ie: IE info for probe response. + * @beacon_ie: IE info for beacon frame. + * @probe_req_ie_len: IE info length for probe request. + * @probe_res_ie_len: IE info length for probe response. + * @beacon_ie_len: IE info length for beacon frame. + */ +struct vif_saved_ie { + u8 probe_req_ie[IE_MAX_LEN]; + u8 probe_res_ie[IE_MAX_LEN]; + u8 beacon_ie[IE_MAX_LEN]; + u8 assoc_req_ie[IE_MAX_LEN]; + u32 probe_req_ie_len; + u32 probe_res_ie_len; + u32 beacon_ie_len; + u32 assoc_req_ie_len; +}; + +/** + * struct brcmf_cfg80211_vif - virtual interface specific information. + * + * @ifp: lower layer interface pointer + * @wdev: wireless device. + * @profile: profile information. + * @mode: operating mode. + * @roam_off: roaming state. + * @sme_state: SME state using enum brcmf_vif_status bits. + * @pm_block: power-management blocked. + * @list: linked list. + * @mgmt_rx_reg: registered rx mgmt frame types. + */ +struct brcmf_cfg80211_vif { + struct brcmf_if *ifp; + struct wireless_dev wdev; + struct brcmf_cfg80211_profile profile; + s32 mode; + s32 roam_off; + unsigned long sme_state; + bool pm_block; + struct vif_saved_ie saved_ie; + struct list_head list; + u16 mgmt_rx_reg; +}; + +/* association inform */ +struct brcmf_cfg80211_connect_info { + u8 *req_ie; + s32 req_ie_len; + u8 *resp_ie; + s32 resp_ie_len; +}; + +/* assoc ie length */ +struct brcmf_cfg80211_assoc_ielen_le { + __le32 req_len; + __le32 resp_len; +}; + +/* wpa2 pmk list */ +struct brcmf_cfg80211_pmk_list { + struct pmkid_list pmkids; + struct pmkid foo[MAXPMKID - 1]; +}; + +/* dongle escan state */ +enum wl_escan_state { + WL_ESCAN_STATE_IDLE, + WL_ESCAN_STATE_SCANNING +}; + +struct escan_info { + u32 escan_state; + u8 escan_buf[WL_ESCAN_BUF_SIZE]; + struct wiphy *wiphy; + struct brcmf_if *ifp; + s32 (*run)(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp, + struct cfg80211_scan_request *request, u16 action); +}; + +/** + * struct brcmf_pno_param_le - PNO scan configuration parameters + * + * @version: PNO parameters version. + * @scan_freq: scan frequency. + * @lost_network_timeout: #sec. to declare discovered network as lost. + * @flags: Bit field to control features of PFN such as sort criteria auto + * enable switch and background scan. + * @rssi_margin: Margin to avoid jitter for choosing a PFN based on RSSI sort + * criteria. + * @bestn: number of best networks in each scan. + * @mscan: number of scans recorded. + * @repeat: minimum number of scan intervals before scan frequency changes + * in adaptive scan. + * @exp: exponent of 2 for maximum scan interval. + * @slow_freq: slow scan period. + */ +struct brcmf_pno_param_le { + __le32 version; + __le32 scan_freq; + __le32 lost_network_timeout; + __le16 flags; + __le16 rssi_margin; + u8 bestn; + u8 mscan; + u8 repeat; + u8 exp; + __le32 slow_freq; +}; + +/** + * struct brcmf_pno_net_param_le - scan parameters per preferred network. + * + * @ssid: ssid name and its length. + * @flags: bit2: hidden. + * @infra: BSS vs IBSS. + * @auth: Open vs Closed. + * @wpa_auth: WPA type. + * @wsec: wsec value. + */ +struct brcmf_pno_net_param_le { + struct brcmf_ssid_le ssid; + __le32 flags; + __le32 infra; + __le32 auth; + __le32 wpa_auth; + __le32 wsec; +}; + +/** + * struct brcmf_pno_net_info_le - information per found network. + * + * @bssid: BSS network identifier. + * @channel: channel number only. + * @SSID_len: length of ssid. + * @SSID: ssid characters. + * @RSSI: receive signal strength (in dBm). + * @timestamp: age in seconds. + */ +struct brcmf_pno_net_info_le { + u8 bssid[ETH_ALEN]; + u8 channel; + u8 SSID_len; + u8 SSID[32]; + __le16 RSSI; + __le16 timestamp; +}; + +/** + * struct brcmf_pno_scanresults_le - result returned in PNO NET FOUND event. + * + * @version: PNO version identifier. + * @status: indicates completion status of PNO scan. + * @count: amount of brcmf_pno_net_info_le entries appended. + */ +struct brcmf_pno_scanresults_le { + __le32 version; + __le32 status; + __le32 count; +}; + +/** + * struct brcmf_cfg80211_vif_event - virtual interface event information. + * + * @vif_wq: waitqueue awaiting interface event from firmware. + * @vif_event_lock: protects other members in this structure. + * @vif_complete: completion for net attach. + * @action: either add, change, or delete. + * @vif: virtual interface object related to the event. + */ +struct brcmf_cfg80211_vif_event { + wait_queue_head_t vif_wq; + struct mutex vif_event_lock; + u8 action; + struct brcmf_cfg80211_vif *vif; +}; + +/** + * struct brcmf_cfg80211_info - dongle private data of cfg80211 interface + * + * @wiphy: wiphy object for cfg80211 interface. + * @conf: dongle configuration. + * @p2p: peer-to-peer specific information. + * @btcoex: Bluetooth coexistence information. + * @scan_request: cfg80211 scan request object. + * @usr_sync: mainly for dongle up/down synchronization. + * @bss_list: bss_list holding scanned ap information. + * @scan_req_int: internal scan request object. + * @bss_info: bss information for cfg80211 layer. + * @ie: information element object for internal purpose. + * @conn_info: association info. + * @pmk_list: wpa2 pmk list. + * @scan_status: scan activity on the dongle. + * @pub: common driver information. + * @channel: current channel. + * @active_scan: current scan mode. + * @sched_escan: e-scan for scheduled scan support running. + * @ibss_starter: indicates this sta is ibss starter. + * @pwr_save: indicate whether dongle to support power save mode. + * @dongle_up: indicate whether dongle up or not. + * @roam_on: on/off switch for dongle self-roaming. + * @scan_tried: indicates if first scan attempted. + * @dcmd_buf: dcmd buffer. + * @extra_buf: mainly to grab assoc information. + * @debugfsdir: debugfs folder for this device. + * @escan_info: escan information. + * @escan_timeout: Timer for catch scan timeout. + * @escan_timeout_work: scan timeout worker. + * @escan_ioctl_buf: dongle command buffer for escan commands. + * @vif_list: linked list of vif instances. + * @vif_cnt: number of vif instances. + * @vif_event: vif event signalling. + */ +struct brcmf_cfg80211_info { + struct wiphy *wiphy; + struct brcmf_cfg80211_conf *conf; + struct brcmf_p2p_info p2p; + struct brcmf_btcoex_info *btcoex; + struct cfg80211_scan_request *scan_request; + struct mutex usr_sync; + struct brcmf_scan_results *bss_list; + struct brcmf_cfg80211_scan_req scan_req_int; + struct wl_cfg80211_bss_info *bss_info; + struct brcmf_cfg80211_ie ie; + struct brcmf_cfg80211_connect_info conn_info; + struct brcmf_cfg80211_pmk_list *pmk_list; + unsigned long scan_status; + struct brcmf_pub *pub; + u32 channel; + bool active_scan; + bool sched_escan; + bool ibss_starter; + bool pwr_save; + bool dongle_up; + bool roam_on; + bool scan_tried; + u8 *dcmd_buf; + u8 *extra_buf; + struct dentry *debugfsdir; + struct escan_info escan_info; + struct timer_list escan_timeout; + struct work_struct escan_timeout_work; + u8 *escan_ioctl_buf; + struct list_head vif_list; + u8 vif_cnt; + struct brcmf_cfg80211_vif_event vif_event; + struct completion vif_disabled; + struct brcmu_d11inf d11inf; +}; + +/** + * struct brcmf_tlv - tag_ID/length/value_buffer tuple. + * + * @id: tag identifier. + * @len: number of bytes in value buffer. + * @data: value buffer. + */ +struct brcmf_tlv { + u8 id; + u8 len; + u8 data[1]; +}; + +static inline struct wiphy *cfg_to_wiphy(struct brcmf_cfg80211_info *cfg) +{ + return cfg->wiphy; +} + +static inline struct brcmf_cfg80211_info *wiphy_to_cfg(struct wiphy *w) +{ + return (struct brcmf_cfg80211_info *)(wiphy_priv(w)); +} + +static inline struct brcmf_cfg80211_info *wdev_to_cfg(struct wireless_dev *wd) +{ + return (struct brcmf_cfg80211_info *)(wdev_priv(wd)); +} + +static inline +struct net_device *cfg_to_ndev(struct brcmf_cfg80211_info *cfg) +{ + struct brcmf_cfg80211_vif *vif; + vif = list_first_entry(&cfg->vif_list, struct brcmf_cfg80211_vif, list); + return vif->wdev.netdev; +} + +static inline struct brcmf_cfg80211_info *ndev_to_cfg(struct net_device *ndev) +{ + return wdev_to_cfg(ndev->ieee80211_ptr); +} + +static inline struct brcmf_cfg80211_profile *ndev_to_prof(struct net_device *nd) +{ + struct brcmf_if *ifp = netdev_priv(nd); + return &ifp->vif->profile; +} + +static inline struct brcmf_cfg80211_vif *ndev_to_vif(struct net_device *ndev) +{ + struct brcmf_if *ifp = netdev_priv(ndev); + return ifp->vif; +} + +static inline struct +brcmf_cfg80211_connect_info *cfg_to_conn(struct brcmf_cfg80211_info *cfg) +{ + return &cfg->conn_info; +} + +struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr, + struct device *busdev); +void brcmf_cfg80211_detach(struct brcmf_cfg80211_info *cfg); +s32 brcmf_cfg80211_up(struct net_device *ndev); +s32 brcmf_cfg80211_down(struct net_device *ndev); +enum nl80211_iftype brcmf_cfg80211_get_iftype(struct brcmf_if *ifp); + +struct brcmf_cfg80211_vif *brcmf_alloc_vif(struct brcmf_cfg80211_info *cfg, + enum nl80211_iftype type, + bool pm_block); +void brcmf_free_vif(struct brcmf_cfg80211_info *cfg, + struct brcmf_cfg80211_vif *vif); + +s32 brcmf_vif_set_mgmt_ie(struct brcmf_cfg80211_vif *vif, s32 pktflag, + const u8 *vndr_ie_buf, u32 vndr_ie_len); +s32 brcmf_vif_clear_mgmt_ies(struct brcmf_cfg80211_vif *vif); +struct brcmf_tlv *brcmf_parse_tlvs(void *buf, int buflen, uint key); +u16 channel_to_chanspec(struct brcmu_d11inf *d11inf, + struct ieee80211_channel *ch); +u32 wl_get_vif_state_all(struct brcmf_cfg80211_info *cfg, unsigned long state); +void brcmf_cfg80211_arm_vif_event(struct brcmf_cfg80211_info *cfg, + struct brcmf_cfg80211_vif *vif); +bool brcmf_cfg80211_vif_event_armed(struct brcmf_cfg80211_info *cfg); +int brcmf_cfg80211_wait_vif_event_timeout(struct brcmf_cfg80211_info *cfg, + u8 action, ulong timeout); +s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg, + struct brcmf_if *ifp, bool aborted, + bool fw_abort); +void brcmf_set_mpc(struct brcmf_if *ndev, int mpc); +void brcmf_abort_scanning(struct brcmf_cfg80211_info *cfg); + +#endif /* _wl_cfg80211_h_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/Makefile b/drivers/net/wireless/brcm80211/brcmsmac/Makefile new file mode 100644 index 0000000..4e16c03 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/Makefile @@ -0,0 +1,48 @@ +# +# Makefile fragment for Broadcom 802.11n Networking Device Driver +# +# Copyright (c) 2010 Broadcom Corporation +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ccflags-y := \ + -D__CHECK_ENDIAN__ \ + -I$(backport_srctree)/drivers/net/wireless/brcm80211/brcmsmac \ + -I$(backport_srctree)/drivers/net/wireless/brcm80211/brcmsmac/phy \ + -I$(backport_srctree)/drivers/net/wireless/brcm80211/include + +brcmsmac-y := \ + mac80211_if.o \ + ucode_loader.o \ + ampdu.o \ + antsel.o \ + channel.o \ + main.o \ + phy_shim.o \ + pmu.o \ + rate.o \ + stf.o \ + aiutils.o \ + phy/phy_cmn.o \ + phy/phy_lcn.o \ + phy/phy_n.o \ + phy/phytbl_lcn.o \ + phy/phytbl_n.o \ + phy/phy_qmath.o \ + dma.o \ + brcms_trace_events.o \ + debug.o + +brcmsmac-$(CPTCFG_BCMA_DRIVER_GPIO) += led.o + +obj-$(CPTCFG_BRCMSMAC) += brcmsmac.o diff --git a/drivers/net/wireless/brcm80211/brcmsmac/aiutils.c b/drivers/net/wireless/brcm80211/brcmsmac/aiutils.c new file mode 100644 index 0000000..e4fd1ee --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/aiutils.c @@ -0,0 +1,731 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * File contents: support functions for PCI/PCIe + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include + +#include +#include +#include +#include +#include +#include "types.h" +#include "pub.h" +#include "pmu.h" +#include "aiutils.h" + +/* slow_clk_ctl */ + /* slow clock source mask */ +#define SCC_SS_MASK 0x00000007 + /* source of slow clock is LPO */ +#define SCC_SS_LPO 0x00000000 + /* source of slow clock is crystal */ +#define SCC_SS_XTAL 0x00000001 + /* source of slow clock is PCI */ +#define SCC_SS_PCI 0x00000002 + /* LPOFreqSel, 1: 160Khz, 0: 32KHz */ +#define SCC_LF 0x00000200 + /* LPOPowerDown, 1: LPO is disabled, 0: LPO is enabled */ +#define SCC_LP 0x00000400 + /* ForceSlowClk, 1: sb/cores running on slow clock, 0: power logic control */ +#define SCC_FS 0x00000800 + /* IgnorePllOffReq, 1/0: + * power logic ignores/honors PLL clock disable requests from core + */ +#define SCC_IP 0x00001000 + /* XtalControlEn, 1/0: + * power logic does/doesn't disable crystal when appropriate + */ +#define SCC_XC 0x00002000 + /* XtalPU (RO), 1/0: crystal running/disabled */ +#define SCC_XP 0x00004000 + /* ClockDivider (SlowClk = 1/(4+divisor)) */ +#define SCC_CD_MASK 0xffff0000 +#define SCC_CD_SHIFT 16 + +/* system_clk_ctl */ + /* ILPen: Enable Idle Low Power */ +#define SYCC_IE 0x00000001 + /* ALPen: Enable Active Low Power */ +#define SYCC_AE 0x00000002 + /* ForcePLLOn */ +#define SYCC_FP 0x00000004 + /* Force ALP (or HT if ALPen is not set */ +#define SYCC_AR 0x00000008 + /* Force HT */ +#define SYCC_HR 0x00000010 + /* ClkDiv (ILP = 1/(4 * (divisor + 1)) */ +#define SYCC_CD_MASK 0xffff0000 +#define SYCC_CD_SHIFT 16 + +#define CST4329_SPROM_OTP_SEL_MASK 0x00000003 + /* OTP is powered up, use def. CIS, no SPROM */ +#define CST4329_DEFCIS_SEL 0 + /* OTP is powered up, SPROM is present */ +#define CST4329_SPROM_SEL 1 + /* OTP is powered up, no SPROM */ +#define CST4329_OTP_SEL 2 + /* OTP is powered down, SPROM is present */ +#define CST4329_OTP_PWRDN 3 + +#define CST4329_SPI_SDIO_MODE_MASK 0x00000004 +#define CST4329_SPI_SDIO_MODE_SHIFT 2 + +/* 43224 chip-specific ChipControl register bits */ +#define CCTRL43224_GPIO_TOGGLE 0x8000 + /* 12 mA drive strength */ +#define CCTRL_43224A0_12MA_LED_DRIVE 0x00F000F0 + /* 12 mA drive strength for later 43224s */ +#define CCTRL_43224B0_12MA_LED_DRIVE 0xF0 + +/* 43236 Chip specific ChipStatus register bits */ +#define CST43236_SFLASH_MASK 0x00000040 +#define CST43236_OTP_MASK 0x00000080 +#define CST43236_HSIC_MASK 0x00000100 /* USB/HSIC */ +#define CST43236_BP_CLK 0x00000200 /* 120/96Mbps */ +#define CST43236_BOOT_MASK 0x00001800 +#define CST43236_BOOT_SHIFT 11 +#define CST43236_BOOT_FROM_SRAM 0 /* boot from SRAM, ARM in reset */ +#define CST43236_BOOT_FROM_ROM 1 /* boot from ROM */ +#define CST43236_BOOT_FROM_FLASH 2 /* boot from FLASH */ +#define CST43236_BOOT_FROM_INVALID 3 + +/* 4331 chip-specific ChipControl register bits */ + /* 0 disable */ +#define CCTRL4331_BT_COEXIST (1<<0) + /* 0 SECI is disabled (JTAG functional) */ +#define CCTRL4331_SECI (1<<1) + /* 0 disable */ +#define CCTRL4331_EXT_LNA (1<<2) + /* sprom/gpio13-15 mux */ +#define CCTRL4331_SPROM_GPIO13_15 (1<<3) + /* 0 ext pa disable, 1 ext pa enabled */ +#define CCTRL4331_EXTPA_EN (1<<4) + /* set drive out GPIO_CLK on sprom_cs pin */ +#define CCTRL4331_GPIOCLK_ON_SPROMCS (1<<5) + /* use sprom_cs pin as PCIE mdio interface */ +#define CCTRL4331_PCIE_MDIO_ON_SPROMCS (1<<6) + /* aband extpa will be at gpio2/5 and sprom_dout */ +#define CCTRL4331_EXTPA_ON_GPIO2_5 (1<<7) + /* override core control on pipe_AuxClkEnable */ +#define CCTRL4331_OVR_PIPEAUXCLKEN (1<<8) + /* override core control on pipe_AuxPowerDown */ +#define CCTRL4331_OVR_PIPEAUXPWRDOWN (1<<9) + /* pcie_auxclkenable */ +#define CCTRL4331_PCIE_AUXCLKEN (1<<10) + /* pcie_pipe_pllpowerdown */ +#define CCTRL4331_PCIE_PIPE_PLLDOWN (1<<11) + /* enable bt_shd0 at gpio4 */ +#define CCTRL4331_BT_SHD0_ON_GPIO4 (1<<16) + /* enable bt_shd1 at gpio5 */ +#define CCTRL4331_BT_SHD1_ON_GPIO5 (1<<17) + +/* 4331 Chip specific ChipStatus register bits */ + /* crystal frequency 20/40Mhz */ +#define CST4331_XTAL_FREQ 0x00000001 +#define CST4331_SPROM_PRESENT 0x00000002 +#define CST4331_OTP_PRESENT 0x00000004 +#define CST4331_LDO_RF 0x00000008 +#define CST4331_LDO_PAR 0x00000010 + +/* 4319 chip-specific ChipStatus register bits */ +#define CST4319_SPI_CPULESSUSB 0x00000001 +#define CST4319_SPI_CLK_POL 0x00000002 +#define CST4319_SPI_CLK_PH 0x00000008 + /* gpio [7:6], SDIO CIS selection */ +#define CST4319_SPROM_OTP_SEL_MASK 0x000000c0 +#define CST4319_SPROM_OTP_SEL_SHIFT 6 + /* use default CIS, OTP is powered up */ +#define CST4319_DEFCIS_SEL 0x00000000 + /* use SPROM, OTP is powered up */ +#define CST4319_SPROM_SEL 0x00000040 + /* use OTP, OTP is powered up */ +#define CST4319_OTP_SEL 0x00000080 + /* use SPROM, OTP is powered down */ +#define CST4319_OTP_PWRDN 0x000000c0 + /* gpio [8], sdio/usb mode */ +#define CST4319_SDIO_USB_MODE 0x00000100 +#define CST4319_REMAP_SEL_MASK 0x00000600 +#define CST4319_ILPDIV_EN 0x00000800 +#define CST4319_XTAL_PD_POL 0x00001000 +#define CST4319_LPO_SEL 0x00002000 +#define CST4319_RES_INIT_MODE 0x0000c000 + /* PALDO is configured with external PNP */ +#define CST4319_PALDO_EXTPNP 0x00010000 +#define CST4319_CBUCK_MODE_MASK 0x00060000 +#define CST4319_CBUCK_MODE_BURST 0x00020000 +#define CST4319_CBUCK_MODE_LPBURST 0x00060000 +#define CST4319_RCAL_VALID 0x01000000 +#define CST4319_RCAL_VALUE_MASK 0x3e000000 +#define CST4319_RCAL_VALUE_SHIFT 25 + +/* 4336 chip-specific ChipStatus register bits */ +#define CST4336_SPI_MODE_MASK 0x00000001 +#define CST4336_SPROM_PRESENT 0x00000002 +#define CST4336_OTP_PRESENT 0x00000004 +#define CST4336_ARMREMAP_0 0x00000008 +#define CST4336_ILPDIV_EN_MASK 0x00000010 +#define CST4336_ILPDIV_EN_SHIFT 4 +#define CST4336_XTAL_PD_POL_MASK 0x00000020 +#define CST4336_XTAL_PD_POL_SHIFT 5 +#define CST4336_LPO_SEL_MASK 0x00000040 +#define CST4336_LPO_SEL_SHIFT 6 +#define CST4336_RES_INIT_MODE_MASK 0x00000180 +#define CST4336_RES_INIT_MODE_SHIFT 7 +#define CST4336_CBUCK_MODE_MASK 0x00000600 +#define CST4336_CBUCK_MODE_SHIFT 9 + +/* 4313 chip-specific ChipStatus register bits */ +#define CST4313_SPROM_PRESENT 1 +#define CST4313_OTP_PRESENT 2 +#define CST4313_SPROM_OTP_SEL_MASK 0x00000002 +#define CST4313_SPROM_OTP_SEL_SHIFT 0 + +/* 4313 Chip specific ChipControl register bits */ + /* 12 mA drive strengh for later 4313 */ +#define CCTRL_4313_12MA_LED_DRIVE 0x00000007 + +/* Manufacturer Ids */ +#define MFGID_ARM 0x43b +#define MFGID_BRCM 0x4bf +#define MFGID_MIPS 0x4a7 + +/* Enumeration ROM registers */ +#define ER_EROMENTRY 0x000 +#define ER_REMAPCONTROL 0xe00 +#define ER_REMAPSELECT 0xe04 +#define ER_MASTERSELECT 0xe10 +#define ER_ITCR 0xf00 +#define ER_ITIP 0xf04 + +/* Erom entries */ +#define ER_TAG 0xe +#define ER_TAG1 0x6 +#define ER_VALID 1 +#define ER_CI 0 +#define ER_MP 2 +#define ER_ADD 4 +#define ER_END 0xe +#define ER_BAD 0xffffffff + +/* EROM CompIdentA */ +#define CIA_MFG_MASK 0xfff00000 +#define CIA_MFG_SHIFT 20 +#define CIA_CID_MASK 0x000fff00 +#define CIA_CID_SHIFT 8 +#define CIA_CCL_MASK 0x000000f0 +#define CIA_CCL_SHIFT 4 + +/* EROM CompIdentB */ +#define CIB_REV_MASK 0xff000000 +#define CIB_REV_SHIFT 24 +#define CIB_NSW_MASK 0x00f80000 +#define CIB_NSW_SHIFT 19 +#define CIB_NMW_MASK 0x0007c000 +#define CIB_NMW_SHIFT 14 +#define CIB_NSP_MASK 0x00003e00 +#define CIB_NSP_SHIFT 9 +#define CIB_NMP_MASK 0x000001f0 +#define CIB_NMP_SHIFT 4 + +/* EROM AddrDesc */ +#define AD_ADDR_MASK 0xfffff000 +#define AD_SP_MASK 0x00000f00 +#define AD_SP_SHIFT 8 +#define AD_ST_MASK 0x000000c0 +#define AD_ST_SHIFT 6 +#define AD_ST_SLAVE 0x00000000 +#define AD_ST_BRIDGE 0x00000040 +#define AD_ST_SWRAP 0x00000080 +#define AD_ST_MWRAP 0x000000c0 +#define AD_SZ_MASK 0x00000030 +#define AD_SZ_SHIFT 4 +#define AD_SZ_4K 0x00000000 +#define AD_SZ_8K 0x00000010 +#define AD_SZ_16K 0x00000020 +#define AD_SZ_SZD 0x00000030 +#define AD_AG32 0x00000008 +#define AD_ADDR_ALIGN 0x00000fff +#define AD_SZ_BASE 0x00001000 /* 4KB */ + +/* EROM SizeDesc */ +#define SD_SZ_MASK 0xfffff000 +#define SD_SG32 0x00000008 +#define SD_SZ_ALIGN 0x00000fff + +/* PCI config space bit 4 for 4306c0 slow clock source */ +#define PCI_CFG_GPIO_SCS 0x10 +/* PCI config space GPIO 14 for Xtal power-up */ +#define PCI_CFG_GPIO_XTAL 0x40 +/* PCI config space GPIO 15 for PLL power-down */ +#define PCI_CFG_GPIO_PLL 0x80 + +/* power control defines */ +#define PLL_DELAY 150 /* us pll on delay */ +#define FREF_DELAY 200 /* us fref change delay */ +#define XTAL_ON_DELAY 1000 /* us crystal power-on delay */ + +/* resetctrl */ +#define AIRC_RESET 1 + +#define NOREV -1 /* Invalid rev */ + +/* GPIO Based LED powersave defines */ +#define DEFAULT_GPIO_ONTIME 10 /* Default: 10% on */ +#define DEFAULT_GPIO_OFFTIME 90 /* Default: 10% on */ + +/* When Srom support present, fields in sromcontrol */ +#define SRC_START 0x80000000 +#define SRC_BUSY 0x80000000 +#define SRC_OPCODE 0x60000000 +#define SRC_OP_READ 0x00000000 +#define SRC_OP_WRITE 0x20000000 +#define SRC_OP_WRDIS 0x40000000 +#define SRC_OP_WREN 0x60000000 +#define SRC_OTPSEL 0x00000010 +#define SRC_LOCK 0x00000008 +#define SRC_SIZE_MASK 0x00000006 +#define SRC_SIZE_1K 0x00000000 +#define SRC_SIZE_4K 0x00000002 +#define SRC_SIZE_16K 0x00000004 +#define SRC_SIZE_SHIFT 1 +#define SRC_PRESENT 0x00000001 + +/* External PA enable mask */ +#define GPIO_CTRL_EPA_EN_MASK 0x40 + +#define DEFAULT_GPIOTIMERVAL \ + ((DEFAULT_GPIO_ONTIME << GPIO_ONTIME_SHIFT) | DEFAULT_GPIO_OFFTIME) + +#define BADIDX (SI_MAXCORES + 1) + +#define IS_SIM(chippkg) \ + ((chippkg == HDLSIM_PKG_ID) || (chippkg == HWSIM_PKG_ID)) + +#define GOODCOREADDR(x, b) \ + (((x) >= (b)) && ((x) < ((b) + SI_MAXCORES * SI_CORE_SIZE)) && \ + IS_ALIGNED((x), SI_CORE_SIZE)) + +struct aidmp { + u32 oobselina30; /* 0x000 */ + u32 oobselina74; /* 0x004 */ + u32 PAD[6]; + u32 oobselinb30; /* 0x020 */ + u32 oobselinb74; /* 0x024 */ + u32 PAD[6]; + u32 oobselinc30; /* 0x040 */ + u32 oobselinc74; /* 0x044 */ + u32 PAD[6]; + u32 oobselind30; /* 0x060 */ + u32 oobselind74; /* 0x064 */ + u32 PAD[38]; + u32 oobselouta30; /* 0x100 */ + u32 oobselouta74; /* 0x104 */ + u32 PAD[6]; + u32 oobseloutb30; /* 0x120 */ + u32 oobseloutb74; /* 0x124 */ + u32 PAD[6]; + u32 oobseloutc30; /* 0x140 */ + u32 oobseloutc74; /* 0x144 */ + u32 PAD[6]; + u32 oobseloutd30; /* 0x160 */ + u32 oobseloutd74; /* 0x164 */ + u32 PAD[38]; + u32 oobsynca; /* 0x200 */ + u32 oobseloutaen; /* 0x204 */ + u32 PAD[6]; + u32 oobsyncb; /* 0x220 */ + u32 oobseloutben; /* 0x224 */ + u32 PAD[6]; + u32 oobsyncc; /* 0x240 */ + u32 oobseloutcen; /* 0x244 */ + u32 PAD[6]; + u32 oobsyncd; /* 0x260 */ + u32 oobseloutden; /* 0x264 */ + u32 PAD[38]; + u32 oobaextwidth; /* 0x300 */ + u32 oobainwidth; /* 0x304 */ + u32 oobaoutwidth; /* 0x308 */ + u32 PAD[5]; + u32 oobbextwidth; /* 0x320 */ + u32 oobbinwidth; /* 0x324 */ + u32 oobboutwidth; /* 0x328 */ + u32 PAD[5]; + u32 oobcextwidth; /* 0x340 */ + u32 oobcinwidth; /* 0x344 */ + u32 oobcoutwidth; /* 0x348 */ + u32 PAD[5]; + u32 oobdextwidth; /* 0x360 */ + u32 oobdinwidth; /* 0x364 */ + u32 oobdoutwidth; /* 0x368 */ + u32 PAD[37]; + u32 ioctrlset; /* 0x400 */ + u32 ioctrlclear; /* 0x404 */ + u32 ioctrl; /* 0x408 */ + u32 PAD[61]; + u32 iostatus; /* 0x500 */ + u32 PAD[127]; + u32 ioctrlwidth; /* 0x700 */ + u32 iostatuswidth; /* 0x704 */ + u32 PAD[62]; + u32 resetctrl; /* 0x800 */ + u32 resetstatus; /* 0x804 */ + u32 resetreadid; /* 0x808 */ + u32 resetwriteid; /* 0x80c */ + u32 PAD[60]; + u32 errlogctrl; /* 0x900 */ + u32 errlogdone; /* 0x904 */ + u32 errlogstatus; /* 0x908 */ + u32 errlogaddrlo; /* 0x90c */ + u32 errlogaddrhi; /* 0x910 */ + u32 errlogid; /* 0x914 */ + u32 errloguser; /* 0x918 */ + u32 errlogflags; /* 0x91c */ + u32 PAD[56]; + u32 intstatus; /* 0xa00 */ + u32 PAD[127]; + u32 config; /* 0xe00 */ + u32 PAD[63]; + u32 itcr; /* 0xf00 */ + u32 PAD[3]; + u32 itipooba; /* 0xf10 */ + u32 itipoobb; /* 0xf14 */ + u32 itipoobc; /* 0xf18 */ + u32 itipoobd; /* 0xf1c */ + u32 PAD[4]; + u32 itipoobaout; /* 0xf30 */ + u32 itipoobbout; /* 0xf34 */ + u32 itipoobcout; /* 0xf38 */ + u32 itipoobdout; /* 0xf3c */ + u32 PAD[4]; + u32 itopooba; /* 0xf50 */ + u32 itopoobb; /* 0xf54 */ + u32 itopoobc; /* 0xf58 */ + u32 itopoobd; /* 0xf5c */ + u32 PAD[4]; + u32 itopoobain; /* 0xf70 */ + u32 itopoobbin; /* 0xf74 */ + u32 itopoobcin; /* 0xf78 */ + u32 itopoobdin; /* 0xf7c */ + u32 PAD[4]; + u32 itopreset; /* 0xf90 */ + u32 PAD[15]; + u32 peripherialid4; /* 0xfd0 */ + u32 peripherialid5; /* 0xfd4 */ + u32 peripherialid6; /* 0xfd8 */ + u32 peripherialid7; /* 0xfdc */ + u32 peripherialid0; /* 0xfe0 */ + u32 peripherialid1; /* 0xfe4 */ + u32 peripherialid2; /* 0xfe8 */ + u32 peripherialid3; /* 0xfec */ + u32 componentid0; /* 0xff0 */ + u32 componentid1; /* 0xff4 */ + u32 componentid2; /* 0xff8 */ + u32 componentid3; /* 0xffc */ +}; + +static bool +ai_buscore_setup(struct si_info *sii, struct bcma_device *cc) +{ + /* no cores found, bail out */ + if (cc->bus->nr_cores == 0) + return false; + + /* get chipcommon rev */ + sii->pub.ccrev = cc->id.rev; + + /* get chipcommon chipstatus */ + sii->chipst = bcma_read32(cc, CHIPCREGOFFS(chipstatus)); + + /* get chipcommon capabilites */ + sii->pub.cccaps = bcma_read32(cc, CHIPCREGOFFS(capabilities)); + + /* get pmu rev and caps */ + if (ai_get_cccaps(&sii->pub) & CC_CAP_PMU) { + sii->pub.pmucaps = bcma_read32(cc, + CHIPCREGOFFS(pmucapabilities)); + sii->pub.pmurev = sii->pub.pmucaps & PCAP_REV_MASK; + } + + return true; +} + +static struct si_info *ai_doattach(struct si_info *sii, + struct bcma_bus *pbus) +{ + struct si_pub *sih = &sii->pub; + struct bcma_device *cc; + + sii->icbus = pbus; + sii->pcibus = pbus->host_pci; + + /* switch to Chipcommon core */ + cc = pbus->drv_cc.core; + + sih->chip = pbus->chipinfo.id; + sih->chiprev = pbus->chipinfo.rev; + sih->chippkg = pbus->chipinfo.pkg; + sih->boardvendor = pbus->boardinfo.vendor; + sih->boardtype = pbus->boardinfo.type; + + if (!ai_buscore_setup(sii, cc)) + goto exit; + + /* === NVRAM, clock is ready === */ + bcma_write32(cc, CHIPCREGOFFS(gpiopullup), 0); + bcma_write32(cc, CHIPCREGOFFS(gpiopulldown), 0); + + /* PMU specific initializations */ + if (ai_get_cccaps(sih) & CC_CAP_PMU) { + (void)si_pmu_measure_alpclk(sih); + } + + return sii; + + exit: + + return NULL; +} + +/* + * Allocate a si handle and do the attach. + */ +struct si_pub * +ai_attach(struct bcma_bus *pbus) +{ + struct si_info *sii; + + /* alloc struct si_info */ + sii = kzalloc(sizeof(struct si_info), GFP_ATOMIC); + if (sii == NULL) + return NULL; + + if (ai_doattach(sii, pbus) == NULL) { + kfree(sii); + return NULL; + } + + return (struct si_pub *) sii; +} + +/* may be called with core in reset */ +void ai_detach(struct si_pub *sih) +{ + struct si_info *sii; + + sii = container_of(sih, struct si_info, pub); + + if (sii == NULL) + return; + + kfree(sii); +} + +/* + * read/modify chipcommon core register. + */ +uint ai_cc_reg(struct si_pub *sih, uint regoff, u32 mask, u32 val) +{ + struct bcma_device *cc; + u32 w; + struct si_info *sii; + + sii = container_of(sih, struct si_info, pub); + cc = sii->icbus->drv_cc.core; + + /* mask and set */ + if (mask || val) + bcma_maskset32(cc, regoff, ~mask, val); + + /* readback */ + w = bcma_read32(cc, regoff); + + return w; +} + +/* return the slow clock source - LPO, XTAL, or PCI */ +static uint ai_slowclk_src(struct si_pub *sih, struct bcma_device *cc) +{ + return SCC_SS_XTAL; +} + +/* +* return the ILP (slowclock) min or max frequency +* precondition: we've established the chip has dynamic clk control +*/ +static uint ai_slowclk_freq(struct si_pub *sih, bool max_freq, + struct bcma_device *cc) +{ + uint div; + + /* Chipc rev 10 is InstaClock */ + div = bcma_read32(cc, CHIPCREGOFFS(system_clk_ctl)); + div = 4 * ((div >> SYCC_CD_SHIFT) + 1); + return max_freq ? XTALMAXFREQ : (XTALMINFREQ / div); +} + +static void +ai_clkctl_setdelay(struct si_pub *sih, struct bcma_device *cc) +{ + uint slowmaxfreq, pll_delay, slowclk; + uint pll_on_delay, fref_sel_delay; + + pll_delay = PLL_DELAY; + + /* + * If the slow clock is not sourced by the xtal then + * add the xtal_on_delay since the xtal will also be + * powered down by dynamic clk control logic. + */ + + slowclk = ai_slowclk_src(sih, cc); + if (slowclk != SCC_SS_XTAL) + pll_delay += XTAL_ON_DELAY; + + /* Starting with 4318 it is ILP that is used for the delays */ + slowmaxfreq = + ai_slowclk_freq(sih, false, cc); + + pll_on_delay = ((slowmaxfreq * pll_delay) + 999999) / 1000000; + fref_sel_delay = ((slowmaxfreq * FREF_DELAY) + 999999) / 1000000; + + bcma_write32(cc, CHIPCREGOFFS(pll_on_delay), pll_on_delay); + bcma_write32(cc, CHIPCREGOFFS(fref_sel_delay), fref_sel_delay); +} + +/* initialize power control delay registers */ +void ai_clkctl_init(struct si_pub *sih) +{ + struct si_info *sii = container_of(sih, struct si_info, pub); + struct bcma_device *cc; + + if (!(ai_get_cccaps(sih) & CC_CAP_PWR_CTL)) + return; + + cc = sii->icbus->drv_cc.core; + if (cc == NULL) + return; + + /* set all Instaclk chip ILP to 1 MHz */ + bcma_maskset32(cc, CHIPCREGOFFS(system_clk_ctl), SYCC_CD_MASK, + (ILP_DIV_1MHZ << SYCC_CD_SHIFT)); + + ai_clkctl_setdelay(sih, cc); +} + +/* + * return the value suitable for writing to the + * dot11 core FAST_PWRUP_DELAY register + */ +u16 ai_clkctl_fast_pwrup_delay(struct si_pub *sih) +{ + struct si_info *sii; + struct bcma_device *cc; + uint slowminfreq; + u16 fpdelay; + + sii = container_of(sih, struct si_info, pub); + if (ai_get_cccaps(sih) & CC_CAP_PMU) { + fpdelay = si_pmu_fast_pwrup_delay(sih); + return fpdelay; + } + + if (!(ai_get_cccaps(sih) & CC_CAP_PWR_CTL)) + return 0; + + fpdelay = 0; + cc = sii->icbus->drv_cc.core; + if (cc) { + slowminfreq = ai_slowclk_freq(sih, false, cc); + fpdelay = (((bcma_read32(cc, CHIPCREGOFFS(pll_on_delay)) + 2) + * 1000000) + (slowminfreq - 1)) / slowminfreq; + } + return fpdelay; +} + +/* + * clock control policy function throught chipcommon + * + * set dynamic clk control mode (forceslow, forcefast, dynamic) + * returns true if we are forcing fast clock + * this is a wrapper over the next internal function + * to allow flexible policy settings for outside caller + */ +bool ai_clkctl_cc(struct si_pub *sih, enum bcma_clkmode mode) +{ + struct si_info *sii; + struct bcma_device *cc; + + sii = container_of(sih, struct si_info, pub); + + cc = sii->icbus->drv_cc.core; + bcma_core_set_clockmode(cc, mode); + return mode == BCMA_CLKMODE_FAST; +} + +void ai_pci_up(struct si_pub *sih) +{ + struct si_info *sii; + + sii = container_of(sih, struct si_info, pub); + + if (sii->icbus->hosttype == BCMA_HOSTTYPE_PCI) + bcma_core_pci_extend_L1timer(&sii->icbus->drv_pci[0], true); +} + +/* Unconfigure and/or apply various WARs when going down */ +void ai_pci_down(struct si_pub *sih) +{ + struct si_info *sii; + + sii = container_of(sih, struct si_info, pub); + + if (sii->icbus->hosttype == BCMA_HOSTTYPE_PCI) + bcma_core_pci_extend_L1timer(&sii->icbus->drv_pci[0], false); +} + +/* Enable BT-COEX & Ex-PA for 4313 */ +void ai_epa_4313war(struct si_pub *sih) +{ + struct si_info *sii = container_of(sih, struct si_info, pub); + struct bcma_device *cc; + + cc = sii->icbus->drv_cc.core; + + /* EPA Fix */ + bcma_set32(cc, CHIPCREGOFFS(gpiocontrol), GPIO_CTRL_EPA_EN_MASK); +} + +/* check if the device is removed */ +bool ai_deviceremoved(struct si_pub *sih) +{ + u32 w = 0; + struct si_info *sii; + + sii = container_of(sih, struct si_info, pub); + + if (sii->icbus->hosttype != BCMA_HOSTTYPE_PCI) + return false; + + pci_read_config_dword(sii->pcibus, PCI_VENDOR_ID, &w); + if ((w & 0xFFFF) != PCI_VENDOR_ID_BROADCOM) + return true; + + return false; +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/aiutils.h b/drivers/net/wireless/brcm80211/brcmsmac/aiutils.h new file mode 100644 index 0000000..89562c1 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/aiutils.h @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2011 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_AIUTILS_H_ +#define _BRCM_AIUTILS_H_ + +#include + +#include "types.h" + +/* + * SOC Interconnect Address Map. + * All regions may not exist on all chips. + */ +/* each core gets 4Kbytes for registers */ +#define SI_CORE_SIZE 0x1000 +/* + * Max cores (this is arbitrary, for software + * convenience and could be changed if we + * make any larger chips + */ +#define SI_MAXCORES 16 + +/* Client Mode sb2pcitranslation2 size in bytes */ +#define SI_PCI_DMA_SZ 0x40000000 + +/* PCIE Client Mode sb2pcitranslation2 (2 ZettaBytes), high 32 bits */ +#define SI_PCIE_DMA_H32 0x80000000 + +/* chipcommon being the first core: */ +#define SI_CC_IDX 0 + +/* SOC Interconnect types (aka chip types) */ +#define SOCI_AI 1 + +/* A register that is common to all cores to + * communicate w/PMU regarding clock control. + */ +#define SI_CLK_CTL_ST 0x1e0 /* clock control and status */ + +/* clk_ctl_st register */ +#define CCS_FORCEALP 0x00000001 /* force ALP request */ +#define CCS_FORCEHT 0x00000002 /* force HT request */ +#define CCS_FORCEILP 0x00000004 /* force ILP request */ +#define CCS_ALPAREQ 0x00000008 /* ALP Avail Request */ +#define CCS_HTAREQ 0x00000010 /* HT Avail Request */ +#define CCS_FORCEHWREQOFF 0x00000020 /* Force HW Clock Request Off */ +#define CCS_ERSRC_REQ_MASK 0x00000700 /* external resource requests */ +#define CCS_ERSRC_REQ_SHIFT 8 +#define CCS_ALPAVAIL 0x00010000 /* ALP is available */ +#define CCS_HTAVAIL 0x00020000 /* HT is available */ +#define CCS_BP_ON_APL 0x00040000 /* RO: running on ALP clock */ +#define CCS_BP_ON_HT 0x00080000 /* RO: running on HT clock */ +#define CCS_ERSRC_STS_MASK 0x07000000 /* external resource status */ +#define CCS_ERSRC_STS_SHIFT 24 + +/* HT avail in chipc and pcmcia on 4328a0 */ +#define CCS0_HTAVAIL 0x00010000 +/* ALP avail in chipc and pcmcia on 4328a0 */ +#define CCS0_ALPAVAIL 0x00020000 + +/* Not really related to SOC Interconnect, but a couple of software + * conventions for the use the flash space: + */ + +/* Minumum amount of flash we support */ +#define FLASH_MIN 0x00020000 /* Minimum flash size */ + +#define CC_SROM_OTP 0x800 /* SROM/OTP address space */ + +/* gpiotimerval */ +#define GPIO_ONTIME_SHIFT 16 + +/* Fields in clkdiv */ +#define CLKD_OTP 0x000f0000 +#define CLKD_OTP_SHIFT 16 + +/* dynamic clock control defines */ +#define LPOMINFREQ 25000 /* low power oscillator min */ +#define LPOMAXFREQ 43000 /* low power oscillator max */ +#define XTALMINFREQ 19800000 /* 20 MHz - 1% */ +#define XTALMAXFREQ 20200000 /* 20 MHz + 1% */ +#define PCIMINFREQ 25000000 /* 25 MHz */ +#define PCIMAXFREQ 34000000 /* 33 MHz + fudge */ + +#define ILP_DIV_5MHZ 0 /* ILP = 5 MHz */ +#define ILP_DIV_1MHZ 4 /* ILP = 1 MHz */ + +/* clkctl xtal what flags */ +#define XTAL 0x1 /* primary crystal oscillator (2050) */ +#define PLL 0x2 /* main chip pll */ + +/* GPIO usage priorities */ +#define GPIO_DRV_PRIORITY 0 /* Driver */ +#define GPIO_APP_PRIORITY 1 /* Application */ +#define GPIO_HI_PRIORITY 2 /* Highest priority. Ignore GPIO + * reservation + */ + +/* GPIO pull up/down */ +#define GPIO_PULLUP 0 +#define GPIO_PULLDN 1 + +/* GPIO event regtype */ +#define GPIO_REGEVT 0 /* GPIO register event */ +#define GPIO_REGEVT_INTMSK 1 /* GPIO register event int mask */ +#define GPIO_REGEVT_INTPOL 2 /* GPIO register event int polarity */ + +/* device path */ +#define SI_DEVPATH_BUFSZ 16 /* min buffer size in bytes */ + +/* SI routine enumeration: to be used by update function with multiple hooks */ +#define SI_DOATTACH 1 +#define SI_PCIDOWN 2 +#define SI_PCIUP 3 + +/* + * Data structure to export all chip specific common variables + * public (read-only) portion of aiutils handle returned by si_attach() + */ +struct si_pub { + int ccrev; /* chip common core rev */ + u32 cccaps; /* chip common capabilities */ + int pmurev; /* pmu core rev */ + u32 pmucaps; /* pmu capabilities */ + uint boardtype; /* board type */ + uint boardvendor; /* board vendor */ + uint chip; /* chip number */ + uint chiprev; /* chip revision */ + uint chippkg; /* chip package option */ +}; + +struct pci_dev; + +struct gpioh_item { + void *arg; + bool level; + void (*handler) (u32 stat, void *arg); + u32 event; + struct gpioh_item *next; +}; + +/* misc si info needed by some of the routines */ +struct si_info { + struct si_pub pub; /* back plane public state (must be first) */ + struct bcma_bus *icbus; /* handle to soc interconnect bus */ + struct pci_dev *pcibus; /* handle to pci bus */ + + u32 chipst; /* chip status */ +}; + +/* + * Many of the routines below take an 'sih' handle as their first arg. + * Allocate this by calling si_attach(). Free it by calling si_detach(). + * At any one time, the sih is logically focused on one particular si core + * (the "current core"). + * Use si_setcore() or si_setcoreidx() to change the association to another core + */ + + +/* AMBA Interconnect exported externs */ +extern u32 ai_core_cflags(struct bcma_device *core, u32 mask, u32 val); + +/* === exported functions === */ +extern struct si_pub *ai_attach(struct bcma_bus *pbus); +extern void ai_detach(struct si_pub *sih); +extern uint ai_cc_reg(struct si_pub *sih, uint regoff, u32 mask, u32 val); +extern void ai_clkctl_init(struct si_pub *sih); +extern u16 ai_clkctl_fast_pwrup_delay(struct si_pub *sih); +extern bool ai_clkctl_cc(struct si_pub *sih, enum bcma_clkmode mode); +extern bool ai_deviceremoved(struct si_pub *sih); + +extern void ai_pci_down(struct si_pub *sih); +extern void ai_pci_up(struct si_pub *sih); + +/* Enable Ex-PA for 4313 */ +extern void ai_epa_4313war(struct si_pub *sih); + +static inline u32 ai_get_cccaps(struct si_pub *sih) +{ + return sih->cccaps; +} + +static inline int ai_get_pmurev(struct si_pub *sih) +{ + return sih->pmurev; +} + +static inline u32 ai_get_pmucaps(struct si_pub *sih) +{ + return sih->pmucaps; +} + +static inline uint ai_get_boardtype(struct si_pub *sih) +{ + return sih->boardtype; +} + +static inline uint ai_get_boardvendor(struct si_pub *sih) +{ + return sih->boardvendor; +} + +static inline uint ai_get_chip_id(struct si_pub *sih) +{ + return sih->chip; +} + +static inline uint ai_get_chiprev(struct si_pub *sih) +{ + return sih->chiprev; +} + +static inline uint ai_get_chippkg(struct si_pub *sih) +{ + return sih->chippkg; +} + +#endif /* _BRCM_AIUTILS_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/ampdu.c b/drivers/net/wireless/brcm80211/brcmsmac/ampdu.c new file mode 100644 index 0000000..1585cc5 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/ampdu.c @@ -0,0 +1,1144 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include + +#include "rate.h" +#include "scb.h" +#include "phy/phy_hal.h" +#include "antsel.h" +#include "main.h" +#include "ampdu.h" +#include "debug.h" +#include "brcms_trace_events.h" + +/* max number of mpdus in an ampdu */ +#define AMPDU_MAX_MPDU 32 +/* max number of mpdus in an ampdu to a legacy */ +#define AMPDU_NUM_MPDU_LEGACY 16 +/* max Tx ba window size (in pdu) */ +#define AMPDU_TX_BA_MAX_WSIZE 64 +/* default Tx ba window size (in pdu) */ +#define AMPDU_TX_BA_DEF_WSIZE 64 +/* default Rx ba window size (in pdu) */ +#define AMPDU_RX_BA_DEF_WSIZE 64 +/* max Rx ba window size (in pdu) */ +#define AMPDU_RX_BA_MAX_WSIZE 64 +/* max dur of tx ampdu (in msec) */ +#define AMPDU_MAX_DUR 5 +/* default tx retry limit */ +#define AMPDU_DEF_RETRY_LIMIT 5 +/* default tx retry limit at reg rate */ +#define AMPDU_DEF_RR_RETRY_LIMIT 2 +/* default ffpld reserved bytes */ +#define AMPDU_DEF_FFPLD_RSVD 2048 +/* # of inis to be freed on detach */ +#define AMPDU_INI_FREE 10 +/* max # of mpdus released at a time */ +#define AMPDU_SCB_MAX_RELEASE 20 + +#define NUM_FFPLD_FIFO 4 /* number of fifo concerned by pre-loading */ +#define FFPLD_TX_MAX_UNFL 200 /* default value of the average number of ampdu + * without underflows + */ +#define FFPLD_MPDU_SIZE 1800 /* estimate of maximum mpdu size */ +#define FFPLD_MAX_MCS 23 /* we don't deal with mcs 32 */ +#define FFPLD_PLD_INCR 1000 /* increments in bytes */ +#define FFPLD_MAX_AMPDU_CNT 5000 /* maximum number of ampdu we + * accumulate between resets. + */ + +#define AMPDU_DELIMITER_LEN 4 + +/* max allowed number of mpdus in an ampdu (2 streams) */ +#define AMPDU_NUM_MPDU 16 + +#define TX_SEQ_TO_INDEX(seq) ((seq) % AMPDU_TX_BA_MAX_WSIZE) + +/* max possible overhead per mpdu in the ampdu; 3 is for roundup if needed */ +#define AMPDU_MAX_MPDU_OVERHEAD (FCS_LEN + DOT11_ICV_AES_LEN +\ + AMPDU_DELIMITER_LEN + 3\ + + DOT11_A4_HDR_LEN + DOT11_QOS_LEN + DOT11_IV_MAX_LEN) + +/* modulo add/sub, bound = 2^k */ +#define MODADD_POW2(x, y, bound) (((x) + (y)) & ((bound) - 1)) +#define MODSUB_POW2(x, y, bound) (((x) - (y)) & ((bound) - 1)) + +/* structure to hold tx fifo information and pre-loading state + * counters specific to tx underflows of ampdus + * some counters might be redundant with the ones in wlc or ampdu structures. + * This allows to maintain a specific state independently of + * how often and/or when the wlc counters are updated. + * + * ampdu_pld_size: number of bytes to be pre-loaded + * mcs2ampdu_table: per-mcs max # of mpdus in an ampdu + * prev_txfunfl: num of underflows last read from the HW macstats counter + * accum_txfunfl: num of underflows since we modified pld params + * accum_txampdu: num of tx ampdu since we modified pld params + * prev_txampdu: previous reading of tx ampdu + * dmaxferrate: estimated dma avg xfer rate in kbits/sec + */ +struct brcms_fifo_info { + u16 ampdu_pld_size; + u8 mcs2ampdu_table[FFPLD_MAX_MCS + 1]; + u16 prev_txfunfl; + u32 accum_txfunfl; + u32 accum_txampdu; + u32 prev_txampdu; + u32 dmaxferrate; +}; + +/* AMPDU module specific state + * + * wlc: pointer to main wlc structure + * scb_handle: scb cubby handle to retrieve data from scb + * ini_enable: per-tid initiator enable/disable of ampdu + * ba_tx_wsize: Tx ba window size (in pdu) + * ba_rx_wsize: Rx ba window size (in pdu) + * retry_limit: mpdu transmit retry limit + * rr_retry_limit: mpdu transmit retry limit at regular rate + * retry_limit_tid: per-tid mpdu transmit retry limit + * rr_retry_limit_tid: per-tid mpdu transmit retry limit at regular rate + * mpdu_density: min mpdu spacing (0-7) ==> 2^(x-1)/8 usec + * max_pdu: max pdus allowed in ampdu + * dur: max duration of an ampdu (in msec) + * rx_factor: maximum rx ampdu factor (0-3) ==> 2^(13+x) bytes + * ffpld_rsvd: number of bytes to reserve for preload + * max_txlen: max size of ampdu per mcs, bw and sgi + * mfbr: enable multiple fallback rate + * tx_max_funl: underflows should be kept such that + * (tx_max_funfl*underflows) < tx frames + * fifo_tb: table of fifo infos + */ +struct ampdu_info { + struct brcms_c_info *wlc; + int scb_handle; + u8 ini_enable[AMPDU_MAX_SCB_TID]; + u8 ba_tx_wsize; + u8 ba_rx_wsize; + u8 retry_limit; + u8 rr_retry_limit; + u8 retry_limit_tid[AMPDU_MAX_SCB_TID]; + u8 rr_retry_limit_tid[AMPDU_MAX_SCB_TID]; + u8 mpdu_density; + s8 max_pdu; + u8 dur; + u8 rx_factor; + u32 ffpld_rsvd; + u32 max_txlen[MCS_TABLE_SIZE][2][2]; + bool mfbr; + u32 tx_max_funl; + struct brcms_fifo_info fifo_tb[NUM_FFPLD_FIFO]; +}; + +/* used for flushing ampdu packets */ +struct cb_del_ampdu_pars { + struct ieee80211_sta *sta; + u16 tid; +}; + +static void brcms_c_scb_ampdu_update_max_txlen(struct ampdu_info *ampdu, u8 dur) +{ + u32 rate, mcs; + + for (mcs = 0; mcs < MCS_TABLE_SIZE; mcs++) { + /* rate is in Kbps; dur is in msec ==> len = (rate * dur) / 8 */ + /* 20MHz, No SGI */ + rate = mcs_2_rate(mcs, false, false); + ampdu->max_txlen[mcs][0][0] = (rate * dur) >> 3; + /* 40 MHz, No SGI */ + rate = mcs_2_rate(mcs, true, false); + ampdu->max_txlen[mcs][1][0] = (rate * dur) >> 3; + /* 20MHz, SGI */ + rate = mcs_2_rate(mcs, false, true); + ampdu->max_txlen[mcs][0][1] = (rate * dur) >> 3; + /* 40 MHz, SGI */ + rate = mcs_2_rate(mcs, true, true); + ampdu->max_txlen[mcs][1][1] = (rate * dur) >> 3; + } +} + +static bool brcms_c_ampdu_cap(struct ampdu_info *ampdu) +{ + if (BRCMS_PHY_11N_CAP(ampdu->wlc->band)) + return true; + else + return false; +} + +static int brcms_c_ampdu_set(struct ampdu_info *ampdu, bool on) +{ + struct brcms_c_info *wlc = ampdu->wlc; + struct bcma_device *core = wlc->hw->d11core; + + wlc->pub->_ampdu = false; + + if (on) { + if (!(wlc->pub->_n_enab & SUPPORT_11N)) { + brcms_err(core, "wl%d: driver not nmode enabled\n", + wlc->pub->unit); + return -ENOTSUPP; + } + if (!brcms_c_ampdu_cap(ampdu)) { + brcms_err(core, "wl%d: device not ampdu capable\n", + wlc->pub->unit); + return -ENOTSUPP; + } + wlc->pub->_ampdu = on; + } + + return 0; +} + +static void brcms_c_ffpld_init(struct ampdu_info *ampdu) +{ + int i, j; + struct brcms_fifo_info *fifo; + + for (j = 0; j < NUM_FFPLD_FIFO; j++) { + fifo = (ampdu->fifo_tb + j); + fifo->ampdu_pld_size = 0; + for (i = 0; i <= FFPLD_MAX_MCS; i++) + fifo->mcs2ampdu_table[i] = 255; + fifo->dmaxferrate = 0; + fifo->accum_txampdu = 0; + fifo->prev_txfunfl = 0; + fifo->accum_txfunfl = 0; + + } +} + +struct ampdu_info *brcms_c_ampdu_attach(struct brcms_c_info *wlc) +{ + struct ampdu_info *ampdu; + int i; + + ampdu = kzalloc(sizeof(struct ampdu_info), GFP_ATOMIC); + if (!ampdu) + return NULL; + + ampdu->wlc = wlc; + + for (i = 0; i < AMPDU_MAX_SCB_TID; i++) + ampdu->ini_enable[i] = true; + /* Disable ampdu for VO by default */ + ampdu->ini_enable[PRIO_8021D_VO] = false; + ampdu->ini_enable[PRIO_8021D_NC] = false; + + /* Disable ampdu for BK by default since not enough fifo space */ + ampdu->ini_enable[PRIO_8021D_NONE] = false; + ampdu->ini_enable[PRIO_8021D_BK] = false; + + ampdu->ba_tx_wsize = AMPDU_TX_BA_DEF_WSIZE; + ampdu->ba_rx_wsize = AMPDU_RX_BA_DEF_WSIZE; + ampdu->mpdu_density = AMPDU_DEF_MPDU_DENSITY; + ampdu->max_pdu = AUTO; + ampdu->dur = AMPDU_MAX_DUR; + + ampdu->ffpld_rsvd = AMPDU_DEF_FFPLD_RSVD; + /* + * bump max ampdu rcv size to 64k for all 11n + * devices except 4321A0 and 4321A1 + */ + if (BRCMS_ISNPHY(wlc->band) && NREV_LT(wlc->band->phyrev, 2)) + ampdu->rx_factor = IEEE80211_HT_MAX_AMPDU_32K; + else + ampdu->rx_factor = IEEE80211_HT_MAX_AMPDU_64K; + ampdu->retry_limit = AMPDU_DEF_RETRY_LIMIT; + ampdu->rr_retry_limit = AMPDU_DEF_RR_RETRY_LIMIT; + + for (i = 0; i < AMPDU_MAX_SCB_TID; i++) { + ampdu->retry_limit_tid[i] = ampdu->retry_limit; + ampdu->rr_retry_limit_tid[i] = ampdu->rr_retry_limit; + } + + brcms_c_scb_ampdu_update_max_txlen(ampdu, ampdu->dur); + ampdu->mfbr = false; + /* try to set ampdu to the default value */ + brcms_c_ampdu_set(ampdu, wlc->pub->_ampdu); + + ampdu->tx_max_funl = FFPLD_TX_MAX_UNFL; + brcms_c_ffpld_init(ampdu); + + return ampdu; +} + +void brcms_c_ampdu_detach(struct ampdu_info *ampdu) +{ + kfree(ampdu); +} + +static void brcms_c_scb_ampdu_update_config(struct ampdu_info *ampdu, + struct scb *scb) +{ + struct scb_ampdu *scb_ampdu = &scb->scb_ampdu; + int i; + + scb_ampdu->max_pdu = AMPDU_NUM_MPDU; + + /* go back to legacy size if some preloading is occurring */ + for (i = 0; i < NUM_FFPLD_FIFO; i++) { + if (ampdu->fifo_tb[i].ampdu_pld_size > FFPLD_PLD_INCR) + scb_ampdu->max_pdu = AMPDU_NUM_MPDU_LEGACY; + } + + /* apply user override */ + if (ampdu->max_pdu != AUTO) + scb_ampdu->max_pdu = (u8) ampdu->max_pdu; + + scb_ampdu->release = min_t(u8, scb_ampdu->max_pdu, + AMPDU_SCB_MAX_RELEASE); + + if (scb_ampdu->max_rx_ampdu_bytes) + scb_ampdu->release = min_t(u8, scb_ampdu->release, + scb_ampdu->max_rx_ampdu_bytes / 1600); + + scb_ampdu->release = min(scb_ampdu->release, + ampdu->fifo_tb[TX_AC_BE_FIFO]. + mcs2ampdu_table[FFPLD_MAX_MCS]); +} + +static void brcms_c_scb_ampdu_update_config_all(struct ampdu_info *ampdu) +{ + brcms_c_scb_ampdu_update_config(ampdu, &du->wlc->pri_scb); +} + +static void brcms_c_ffpld_calc_mcs2ampdu_table(struct ampdu_info *ampdu, int f) +{ + int i; + u32 phy_rate, dma_rate, tmp; + u8 max_mpdu; + struct brcms_fifo_info *fifo = (ampdu->fifo_tb + f); + + /* recompute the dma rate */ + /* note : we divide/multiply by 100 to avoid integer overflows */ + max_mpdu = min_t(u8, fifo->mcs2ampdu_table[FFPLD_MAX_MCS], + AMPDU_NUM_MPDU_LEGACY); + phy_rate = mcs_2_rate(FFPLD_MAX_MCS, true, false); + dma_rate = + (((phy_rate / 100) * + (max_mpdu * FFPLD_MPDU_SIZE - fifo->ampdu_pld_size)) + / (max_mpdu * FFPLD_MPDU_SIZE)) * 100; + fifo->dmaxferrate = dma_rate; + + /* fill up the mcs2ampdu table; do not recalc the last mcs */ + dma_rate = dma_rate >> 7; + for (i = 0; i < FFPLD_MAX_MCS; i++) { + /* shifting to keep it within integer range */ + phy_rate = mcs_2_rate(i, true, false) >> 7; + if (phy_rate > dma_rate) { + tmp = ((fifo->ampdu_pld_size * phy_rate) / + ((phy_rate - dma_rate) * FFPLD_MPDU_SIZE)) + 1; + tmp = min_t(u32, tmp, 255); + fifo->mcs2ampdu_table[i] = (u8) tmp; + } + } +} + +/* evaluate the dma transfer rate using the tx underflows as feedback. + * If necessary, increase tx fifo preloading. If not enough, + * decrease maximum ampdu size for each mcs till underflows stop + * Return 1 if pre-loading not active, -1 if not an underflow event, + * 0 if pre-loading module took care of the event. + */ +static int brcms_c_ffpld_check_txfunfl(struct brcms_c_info *wlc, int fid) +{ + struct ampdu_info *ampdu = wlc->ampdu; + u32 phy_rate = mcs_2_rate(FFPLD_MAX_MCS, true, false); + u32 txunfl_ratio; + u8 max_mpdu; + u32 current_ampdu_cnt = 0; + u16 max_pld_size; + u32 new_txunfl; + struct brcms_fifo_info *fifo = (ampdu->fifo_tb + fid); + uint xmtfifo_sz; + u16 cur_txunfl; + + /* return if we got here for a different reason than underflows */ + cur_txunfl = brcms_b_read_shm(wlc->hw, + M_UCODE_MACSTAT + + offsetof(struct macstat, txfunfl[fid])); + new_txunfl = (u16) (cur_txunfl - fifo->prev_txfunfl); + if (new_txunfl == 0) { + brcms_dbg_ht(wlc->hw->d11core, + "TX status FRAG set but no tx underflows\n"); + return -1; + } + fifo->prev_txfunfl = cur_txunfl; + + if (!ampdu->tx_max_funl) + return 1; + + /* check if fifo is big enough */ + if (brcms_b_xmtfifo_sz_get(wlc->hw, fid, &xmtfifo_sz)) + return -1; + + if ((TXFIFO_SIZE_UNIT * (u32) xmtfifo_sz) <= ampdu->ffpld_rsvd) + return 1; + + max_pld_size = TXFIFO_SIZE_UNIT * xmtfifo_sz - ampdu->ffpld_rsvd; + fifo->accum_txfunfl += new_txunfl; + + /* we need to wait for at least 10 underflows */ + if (fifo->accum_txfunfl < 10) + return 0; + + brcms_dbg_ht(wlc->hw->d11core, "ampdu_count %d tx_underflows %d\n", + current_ampdu_cnt, fifo->accum_txfunfl); + + /* + compute the current ratio of tx unfl per ampdu. + When the current ampdu count becomes too + big while the ratio remains small, we reset + the current count in order to not + introduce too big of a latency in detecting a + large amount of tx underflows later. + */ + + txunfl_ratio = current_ampdu_cnt / fifo->accum_txfunfl; + + if (txunfl_ratio > ampdu->tx_max_funl) { + if (current_ampdu_cnt >= FFPLD_MAX_AMPDU_CNT) + fifo->accum_txfunfl = 0; + + return 0; + } + max_mpdu = min_t(u8, fifo->mcs2ampdu_table[FFPLD_MAX_MCS], + AMPDU_NUM_MPDU_LEGACY); + + /* In case max value max_pdu is already lower than + the fifo depth, there is nothing more we can do. + */ + + if (fifo->ampdu_pld_size >= max_mpdu * FFPLD_MPDU_SIZE) { + fifo->accum_txfunfl = 0; + return 0; + } + + if (fifo->ampdu_pld_size < max_pld_size) { + + /* increment by TX_FIFO_PLD_INC bytes */ + fifo->ampdu_pld_size += FFPLD_PLD_INCR; + if (fifo->ampdu_pld_size > max_pld_size) + fifo->ampdu_pld_size = max_pld_size; + + /* update scb release size */ + brcms_c_scb_ampdu_update_config_all(ampdu); + + /* + * compute a new dma xfer rate for max_mpdu @ max mcs. + * This is the minimum dma rate that can achieve no + * underflow condition for the current mpdu size. + * + * note : we divide/multiply by 100 to avoid integer overflows + */ + fifo->dmaxferrate = + (((phy_rate / 100) * + (max_mpdu * FFPLD_MPDU_SIZE - fifo->ampdu_pld_size)) + / (max_mpdu * FFPLD_MPDU_SIZE)) * 100; + + brcms_dbg_ht(wlc->hw->d11core, + "DMA estimated transfer rate %d; " + "pre-load size %d\n", + fifo->dmaxferrate, fifo->ampdu_pld_size); + } else { + + /* decrease ampdu size */ + if (fifo->mcs2ampdu_table[FFPLD_MAX_MCS] > 1) { + if (fifo->mcs2ampdu_table[FFPLD_MAX_MCS] == 255) + fifo->mcs2ampdu_table[FFPLD_MAX_MCS] = + AMPDU_NUM_MPDU_LEGACY - 1; + else + fifo->mcs2ampdu_table[FFPLD_MAX_MCS] -= 1; + + /* recompute the table */ + brcms_c_ffpld_calc_mcs2ampdu_table(ampdu, fid); + + /* update scb release size */ + brcms_c_scb_ampdu_update_config_all(ampdu); + } + } + fifo->accum_txfunfl = 0; + return 0; +} + +void +brcms_c_ampdu_tx_operational(struct brcms_c_info *wlc, u8 tid, + u8 ba_wsize, /* negotiated ba window size (in pdu) */ + uint max_rx_ampdu_bytes) /* from ht_cap in beacon */ +{ + struct scb_ampdu *scb_ampdu; + struct scb_ampdu_tid_ini *ini; + struct ampdu_info *ampdu = wlc->ampdu; + struct scb *scb = &wlc->pri_scb; + scb_ampdu = &scb->scb_ampdu; + + if (!ampdu->ini_enable[tid]) { + brcms_err(wlc->hw->d11core, "%s: Rejecting tid %d\n", + __func__, tid); + return; + } + + ini = &scb_ampdu->ini[tid]; + ini->tid = tid; + ini->scb = scb_ampdu->scb; + ini->ba_wsize = ba_wsize; + scb_ampdu->max_rx_ampdu_bytes = max_rx_ampdu_bytes; +} + +void brcms_c_ampdu_reset_session(struct brcms_ampdu_session *session, + struct brcms_c_info *wlc) +{ + session->wlc = wlc; + skb_queue_head_init(&session->skb_list); + session->max_ampdu_len = 0; /* determined from first MPDU */ + session->max_ampdu_frames = 0; /* determined from first MPDU */ + session->ampdu_len = 0; + session->dma_len = 0; +} + +/* + * Preps the given packet for AMPDU based on the session data. If the + * frame cannot be accomodated in the current session, -ENOSPC is + * returned. + */ +int brcms_c_ampdu_add_frame(struct brcms_ampdu_session *session, + struct sk_buff *p) +{ + struct brcms_c_info *wlc = session->wlc; + struct ampdu_info *ampdu = wlc->ampdu; + struct scb *scb = &wlc->pri_scb; + struct scb_ampdu *scb_ampdu = &scb->scb_ampdu; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(p); + struct ieee80211_tx_rate *txrate = tx_info->status.rates; + struct d11txh *txh = (struct d11txh *)p->data; + unsigned ampdu_frames; + u8 ndelim, tid; + u8 *plcp; + uint len; + u16 mcl; + bool fbr_iscck; + bool rr; + + ndelim = txh->RTSPLCPFallback[AMPDU_FBR_NULL_DELIM]; + plcp = (u8 *)(txh + 1); + fbr_iscck = !(le16_to_cpu(txh->XtraFrameTypes) & 0x03); + len = fbr_iscck ? BRCMS_GET_CCK_PLCP_LEN(txh->FragPLCPFallback) : + BRCMS_GET_MIMO_PLCP_LEN(txh->FragPLCPFallback); + len = roundup(len, 4) + (ndelim + 1) * AMPDU_DELIMITER_LEN; + + ampdu_frames = skb_queue_len(&session->skb_list); + if (ampdu_frames != 0) { + struct sk_buff *first; + + if (ampdu_frames + 1 > session->max_ampdu_frames || + session->ampdu_len + len > session->max_ampdu_len) + return -ENOSPC; + + /* + * We aren't really out of space if the new frame is of + * a different priority, but we want the same behaviour + * so return -ENOSPC anyway. + * + * XXX: The old AMPDU code did this, but is it really + * necessary? + */ + first = skb_peek(&session->skb_list); + if (p->priority != first->priority) + return -ENOSPC; + } + + /* + * Now that we're sure this frame can be accomodated, update the + * session information. + */ + session->ampdu_len += len; + session->dma_len += p->len; + + tid = (u8)p->priority; + + /* Handle retry limits */ + if (txrate[0].count <= ampdu->rr_retry_limit_tid[tid]) { + txrate[0].count++; + rr = true; + } else { + txrate[1].count++; + rr = false; + } + + if (ampdu_frames == 0) { + u8 plcp0, plcp3, is40, sgi, mcs; + uint fifo = le16_to_cpu(txh->TxFrameID) & TXFID_QUEUE_MASK; + struct brcms_fifo_info *f = &du->fifo_tb[fifo]; + + if (rr) { + plcp0 = plcp[0]; + plcp3 = plcp[3]; + } else { + plcp0 = txh->FragPLCPFallback[0]; + plcp3 = txh->FragPLCPFallback[3]; + + } + + /* Limit AMPDU size based on MCS */ + is40 = (plcp0 & MIMO_PLCP_40MHZ) ? 1 : 0; + sgi = plcp3_issgi(plcp3) ? 1 : 0; + mcs = plcp0 & ~MIMO_PLCP_40MHZ; + session->max_ampdu_len = min(scb_ampdu->max_rx_ampdu_bytes, + ampdu->max_txlen[mcs][is40][sgi]); + + session->max_ampdu_frames = scb_ampdu->max_pdu; + if (mcs_2_rate(mcs, true, false) >= f->dmaxferrate) { + session->max_ampdu_frames = + min_t(u16, f->mcs2ampdu_table[mcs], + session->max_ampdu_frames); + } + } + + /* + * Treat all frames as "middle" frames of AMPDU here. First and + * last frames must be fixed up after all MPDUs have been prepped. + */ + mcl = le16_to_cpu(txh->MacTxControlLow); + mcl &= ~TXC_AMPDU_MASK; + mcl |= (TXC_AMPDU_MIDDLE << TXC_AMPDU_SHIFT); + mcl &= ~(TXC_STARTMSDU | TXC_SENDRTS | TXC_SENDCTS); + txh->MacTxControlLow = cpu_to_le16(mcl); + txh->PreloadSize = 0; /* always default to 0 */ + + skb_queue_tail(&session->skb_list, p); + + return 0; +} + +void brcms_c_ampdu_finalize(struct brcms_ampdu_session *session) +{ + struct brcms_c_info *wlc = session->wlc; + struct ampdu_info *ampdu = wlc->ampdu; + struct sk_buff *first, *last; + struct d11txh *txh; + struct ieee80211_tx_info *tx_info; + struct ieee80211_tx_rate *txrate; + u8 ndelim; + u8 *plcp; + uint len; + uint fifo; + struct brcms_fifo_info *f; + u16 mcl; + bool fbr; + bool fbr_iscck; + struct ieee80211_rts *rts; + bool use_rts = false, use_cts = false; + u16 dma_len = session->dma_len; + u16 mimo_ctlchbw = PHY_TXC1_BW_20MHZ; + u32 rspec = 0, rspec_fallback = 0; + u32 rts_rspec = 0, rts_rspec_fallback = 0; + u8 plcp0, plcp3, is40, sgi, mcs; + u16 mch; + u8 preamble_type = BRCMS_GF_PREAMBLE; + u8 fbr_preamble_type = BRCMS_GF_PREAMBLE; + u8 rts_preamble_type = BRCMS_LONG_PREAMBLE; + u8 rts_fbr_preamble_type = BRCMS_LONG_PREAMBLE; + + if (skb_queue_empty(&session->skb_list)) + return; + + first = skb_peek(&session->skb_list); + last = skb_peek_tail(&session->skb_list); + + /* Need to fix up last MPDU first to adjust AMPDU length */ + txh = (struct d11txh *)last->data; + fifo = le16_to_cpu(txh->TxFrameID) & TXFID_QUEUE_MASK; + f = &du->fifo_tb[fifo]; + + mcl = le16_to_cpu(txh->MacTxControlLow); + mcl &= ~TXC_AMPDU_MASK; + mcl |= (TXC_AMPDU_LAST << TXC_AMPDU_SHIFT); + txh->MacTxControlLow = cpu_to_le16(mcl); + + /* remove the null delimiter after last mpdu */ + ndelim = txh->RTSPLCPFallback[AMPDU_FBR_NULL_DELIM]; + txh->RTSPLCPFallback[AMPDU_FBR_NULL_DELIM] = 0; + session->ampdu_len -= ndelim * AMPDU_DELIMITER_LEN; + + /* remove the pad len from last mpdu */ + fbr_iscck = ((le16_to_cpu(txh->XtraFrameTypes) & 0x3) == 0); + len = fbr_iscck ? BRCMS_GET_CCK_PLCP_LEN(txh->FragPLCPFallback) : + BRCMS_GET_MIMO_PLCP_LEN(txh->FragPLCPFallback); + session->ampdu_len -= roundup(len, 4) - len; + + /* Now fix up the first MPDU */ + tx_info = IEEE80211_SKB_CB(first); + txrate = tx_info->status.rates; + txh = (struct d11txh *)first->data; + plcp = (u8 *)(txh + 1); + rts = (struct ieee80211_rts *)&txh->rts_frame; + + mcl = le16_to_cpu(txh->MacTxControlLow); + /* If only one MPDU leave it marked as last */ + if (first != last) { + mcl &= ~TXC_AMPDU_MASK; + mcl |= (TXC_AMPDU_FIRST << TXC_AMPDU_SHIFT); + } + mcl |= TXC_STARTMSDU; + if (ieee80211_is_rts(rts->frame_control)) { + mcl |= TXC_SENDRTS; + use_rts = true; + } + if (ieee80211_is_cts(rts->frame_control)) { + mcl |= TXC_SENDCTS; + use_cts = true; + } + txh->MacTxControlLow = cpu_to_le16(mcl); + + fbr = txrate[1].count > 0; + if (!fbr) { + plcp0 = plcp[0]; + plcp3 = plcp[3]; + } else { + plcp0 = txh->FragPLCPFallback[0]; + plcp3 = txh->FragPLCPFallback[3]; + } + is40 = (plcp0 & MIMO_PLCP_40MHZ) ? 1 : 0; + sgi = plcp3_issgi(plcp3) ? 1 : 0; + mcs = plcp0 & ~MIMO_PLCP_40MHZ; + + if (is40) { + if (CHSPEC_SB_UPPER(wlc_phy_chanspec_get(wlc->band->pi))) + mimo_ctlchbw = PHY_TXC1_BW_20MHZ_UP; + else + mimo_ctlchbw = PHY_TXC1_BW_20MHZ; + } + + /* rebuild the rspec and rspec_fallback */ + rspec = RSPEC_MIMORATE; + rspec |= plcp[0] & ~MIMO_PLCP_40MHZ; + if (plcp[0] & MIMO_PLCP_40MHZ) + rspec |= (PHY_TXC1_BW_40MHZ << RSPEC_BW_SHIFT); + + fbr_iscck = !(le16_to_cpu(txh->XtraFrameTypes) & 0x03); + if (fbr_iscck) { + rspec_fallback = + cck_rspec(cck_phy2mac_rate(txh->FragPLCPFallback[0])); + } else { + rspec_fallback = RSPEC_MIMORATE; + rspec_fallback |= txh->FragPLCPFallback[0] & ~MIMO_PLCP_40MHZ; + if (txh->FragPLCPFallback[0] & MIMO_PLCP_40MHZ) + rspec_fallback |= PHY_TXC1_BW_40MHZ << RSPEC_BW_SHIFT; + } + + if (use_rts || use_cts) { + rts_rspec = + brcms_c_rspec_to_rts_rspec(wlc, rspec, + false, mimo_ctlchbw); + rts_rspec_fallback = + brcms_c_rspec_to_rts_rspec(wlc, rspec_fallback, + false, mimo_ctlchbw); + } + + BRCMS_SET_MIMO_PLCP_LEN(plcp, session->ampdu_len); + /* mark plcp to indicate ampdu */ + BRCMS_SET_MIMO_PLCP_AMPDU(plcp); + + /* reset the mixed mode header durations */ + if (txh->MModeLen) { + u16 mmodelen = brcms_c_calc_lsig_len(wlc, rspec, + session->ampdu_len); + txh->MModeLen = cpu_to_le16(mmodelen); + preamble_type = BRCMS_MM_PREAMBLE; + } + if (txh->MModeFbrLen) { + u16 mmfbrlen = brcms_c_calc_lsig_len(wlc, rspec_fallback, + session->ampdu_len); + txh->MModeFbrLen = cpu_to_le16(mmfbrlen); + fbr_preamble_type = BRCMS_MM_PREAMBLE; + } + + /* set the preload length */ + if (mcs_2_rate(mcs, true, false) >= f->dmaxferrate) { + dma_len = min(dma_len, f->ampdu_pld_size); + txh->PreloadSize = cpu_to_le16(dma_len); + } else { + txh->PreloadSize = 0; + } + + mch = le16_to_cpu(txh->MacTxControlHigh); + + /* update RTS dur fields */ + if (use_rts || use_cts) { + u16 durid; + if ((mch & TXC_PREAMBLE_RTS_MAIN_SHORT) == + TXC_PREAMBLE_RTS_MAIN_SHORT) + rts_preamble_type = BRCMS_SHORT_PREAMBLE; + + if ((mch & TXC_PREAMBLE_RTS_FB_SHORT) == + TXC_PREAMBLE_RTS_FB_SHORT) + rts_fbr_preamble_type = BRCMS_SHORT_PREAMBLE; + + durid = brcms_c_compute_rtscts_dur(wlc, use_cts, rts_rspec, + rspec, rts_preamble_type, + preamble_type, + session->ampdu_len, true); + rts->duration = cpu_to_le16(durid); + durid = brcms_c_compute_rtscts_dur(wlc, use_cts, + rts_rspec_fallback, + rspec_fallback, + rts_fbr_preamble_type, + fbr_preamble_type, + session->ampdu_len, true); + txh->RTSDurFallback = cpu_to_le16(durid); + /* set TxFesTimeNormal */ + txh->TxFesTimeNormal = rts->duration; + /* set fallback rate version of TxFesTimeNormal */ + txh->TxFesTimeFallback = txh->RTSDurFallback; + } + + /* set flag and plcp for fallback rate */ + if (fbr) { + mch |= TXC_AMPDU_FBR; + txh->MacTxControlHigh = cpu_to_le16(mch); + BRCMS_SET_MIMO_PLCP_AMPDU(plcp); + BRCMS_SET_MIMO_PLCP_AMPDU(txh->FragPLCPFallback); + } + + brcms_dbg_ht(wlc->hw->d11core, "wl%d: count %d ampdu_len %d\n", + wlc->pub->unit, skb_queue_len(&session->skb_list), + session->ampdu_len); +} + +static void +brcms_c_ampdu_rate_status(struct brcms_c_info *wlc, + struct ieee80211_tx_info *tx_info, + struct tx_status *txs, u8 mcs) +{ + struct ieee80211_tx_rate *txrate = tx_info->status.rates; + int i; + + /* clear the rest of the rates */ + for (i = 2; i < IEEE80211_TX_MAX_RATES; i++) { + txrate[i].idx = -1; + txrate[i].count = 0; + } +} + +static void +brcms_c_ampdu_dotxstatus_complete(struct ampdu_info *ampdu, struct scb *scb, + struct sk_buff *p, struct tx_status *txs, + u32 s1, u32 s2) +{ + struct scb_ampdu *scb_ampdu; + struct brcms_c_info *wlc = ampdu->wlc; + struct scb_ampdu_tid_ini *ini; + u8 bitmap[8], queue, tid; + struct d11txh *txh; + u8 *plcp; + struct ieee80211_hdr *h; + u16 seq, start_seq = 0, bindex, index, mcl; + u8 mcs = 0; + bool ba_recd = false, ack_recd = false; + u8 suc_mpdu = 0, tot_mpdu = 0; + uint supr_status; + bool update_rate = true, retry = true, tx_error = false; + u16 mimoantsel = 0; + u8 antselid = 0; + u8 retry_limit, rr_retry_limit; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(p); + +#ifdef DEBUG + u8 hole[AMPDU_MAX_MPDU]; + memset(hole, 0, sizeof(hole)); +#endif + + scb_ampdu = &scb->scb_ampdu; + tid = (u8) (p->priority); + + ini = &scb_ampdu->ini[tid]; + retry_limit = ampdu->retry_limit_tid[tid]; + rr_retry_limit = ampdu->rr_retry_limit_tid[tid]; + memset(bitmap, 0, sizeof(bitmap)); + queue = txs->frameid & TXFID_QUEUE_MASK; + supr_status = txs->status & TX_STATUS_SUPR_MASK; + + if (txs->status & TX_STATUS_ACK_RCV) { + if (TX_STATUS_SUPR_UF == supr_status) + update_rate = false; + + WARN_ON(!(txs->status & TX_STATUS_INTERMEDIATE)); + start_seq = txs->sequence >> SEQNUM_SHIFT; + bitmap[0] = (txs->status & TX_STATUS_BA_BMAP03_MASK) >> + TX_STATUS_BA_BMAP03_SHIFT; + + WARN_ON(s1 & TX_STATUS_INTERMEDIATE); + WARN_ON(!(s1 & TX_STATUS_AMPDU)); + + bitmap[0] |= + (s1 & TX_STATUS_BA_BMAP47_MASK) << + TX_STATUS_BA_BMAP47_SHIFT; + bitmap[1] = (s1 >> 8) & 0xff; + bitmap[2] = (s1 >> 16) & 0xff; + bitmap[3] = (s1 >> 24) & 0xff; + + bitmap[4] = s2 & 0xff; + bitmap[5] = (s2 >> 8) & 0xff; + bitmap[6] = (s2 >> 16) & 0xff; + bitmap[7] = (s2 >> 24) & 0xff; + + ba_recd = true; + } else { + if (supr_status) { + update_rate = false; + if (supr_status == TX_STATUS_SUPR_BADCH) { + brcms_err(wlc->hw->d11core, + "%s: Pkt tx suppressed, illegal channel possibly %d\n", + __func__, CHSPEC_CHANNEL( + wlc->default_bss->chanspec)); + } else { + if (supr_status != TX_STATUS_SUPR_FRAG) + brcms_err(wlc->hw->d11core, + "%s: supr_status 0x%x\n", + __func__, supr_status); + } + /* no need to retry for badch; will fail again */ + if (supr_status == TX_STATUS_SUPR_BADCH || + supr_status == TX_STATUS_SUPR_EXPTIME) { + retry = false; + } else if (supr_status == TX_STATUS_SUPR_EXPTIME) { + /* TX underflow: + * try tuning pre-loading or ampdu size + */ + } else if (supr_status == TX_STATUS_SUPR_FRAG) { + /* + * if there were underflows, but pre-loading + * is not active, notify rate adaptation. + */ + if (brcms_c_ffpld_check_txfunfl(wlc, queue) > 0) + tx_error = true; + } + } else if (txs->phyerr) { + update_rate = false; + brcms_err(wlc->hw->d11core, + "%s: ampdu tx phy error (0x%x)\n", + __func__, txs->phyerr); + } + } + + /* loop through all pkts and retry if not acked */ + while (p) { + tx_info = IEEE80211_SKB_CB(p); + txh = (struct d11txh *) p->data; + mcl = le16_to_cpu(txh->MacTxControlLow); + plcp = (u8 *) (txh + 1); + h = (struct ieee80211_hdr *)(plcp + D11_PHY_HDR_LEN); + seq = le16_to_cpu(h->seq_ctrl) >> SEQNUM_SHIFT; + + trace_brcms_txdesc(&wlc->hw->d11core->dev, txh, sizeof(*txh)); + + if (tot_mpdu == 0) { + mcs = plcp[0] & MIMO_PLCP_MCS_MASK; + mimoantsel = le16_to_cpu(txh->ABI_MimoAntSel); + } + + index = TX_SEQ_TO_INDEX(seq); + ack_recd = false; + if (ba_recd) { + bindex = MODSUB_POW2(seq, start_seq, SEQNUM_MAX); + brcms_dbg_ht(wlc->hw->d11core, + "tid %d seq %d, start_seq %d, bindex %d set %d, index %d\n", + tid, seq, start_seq, bindex, + isset(bitmap, bindex), index); + /* if acked then clear bit and free packet */ + if ((bindex < AMPDU_TX_BA_MAX_WSIZE) + && isset(bitmap, bindex)) { + ini->txretry[index] = 0; + + /* + * ampdu_ack_len: + * number of acked aggregated frames + */ + /* ampdu_len: number of aggregated frames */ + brcms_c_ampdu_rate_status(wlc, tx_info, txs, + mcs); + tx_info->flags |= IEEE80211_TX_STAT_ACK; + tx_info->flags |= IEEE80211_TX_STAT_AMPDU; + tx_info->status.ampdu_ack_len = + tx_info->status.ampdu_len = 1; + + skb_pull(p, D11_PHY_HDR_LEN); + skb_pull(p, D11_TXH_LEN); + + ieee80211_tx_status_irqsafe(wlc->pub->ieee_hw, + p); + ack_recd = true; + suc_mpdu++; + } + } + /* either retransmit or send bar if ack not recd */ + if (!ack_recd) { + if (retry && (ini->txretry[index] < (int)retry_limit)) { + int ret; + ini->txretry[index]++; + ret = brcms_c_txfifo(wlc, queue, p); + /* + * We shouldn't be out of space in the DMA + * ring here since we're reinserting a frame + * that was just pulled out. + */ + WARN_ONCE(ret, "queue %d out of txds\n", queue); + } else { + /* Retry timeout */ + ieee80211_tx_info_clear_status(tx_info); + tx_info->status.ampdu_ack_len = 0; + tx_info->status.ampdu_len = 1; + tx_info->flags |= + IEEE80211_TX_STAT_AMPDU_NO_BACK; + skb_pull(p, D11_PHY_HDR_LEN); + skb_pull(p, D11_TXH_LEN); + brcms_dbg_ht(wlc->hw->d11core, + "BA Timeout, seq %d\n", + seq); + ieee80211_tx_status_irqsafe(wlc->pub->ieee_hw, + p); + } + } + tot_mpdu++; + + /* break out if last packet of ampdu */ + if (((mcl & TXC_AMPDU_MASK) >> TXC_AMPDU_SHIFT) == + TXC_AMPDU_LAST) + break; + + p = dma_getnexttxp(wlc->hw->di[queue], DMA_RANGE_TRANSMITTED); + } + + /* update rate state */ + antselid = brcms_c_antsel_antsel2id(wlc->asi, mimoantsel); +} + +void +brcms_c_ampdu_dotxstatus(struct ampdu_info *ampdu, struct scb *scb, + struct sk_buff *p, struct tx_status *txs) +{ + struct scb_ampdu *scb_ampdu; + struct brcms_c_info *wlc = ampdu->wlc; + struct scb_ampdu_tid_ini *ini; + u32 s1 = 0, s2 = 0; + struct ieee80211_tx_info *tx_info; + + tx_info = IEEE80211_SKB_CB(p); + + /* BMAC_NOTE: For the split driver, second level txstatus comes later + * So if the ACK was received then wait for the second level else just + * call the first one + */ + if (txs->status & TX_STATUS_ACK_RCV) { + u8 status_delay = 0; + + /* wait till the next 8 bytes of txstatus is available */ + s1 = bcma_read32(wlc->hw->d11core, D11REGOFFS(frmtxstatus)); + while ((s1 & TXS_V) == 0) { + udelay(1); + status_delay++; + if (status_delay > 10) + return; /* error condition */ + s1 = bcma_read32(wlc->hw->d11core, + D11REGOFFS(frmtxstatus)); + } + + s2 = bcma_read32(wlc->hw->d11core, D11REGOFFS(frmtxstatus2)); + } + + if (scb) { + scb_ampdu = &scb->scb_ampdu; + ini = &scb_ampdu->ini[p->priority]; + brcms_c_ampdu_dotxstatus_complete(ampdu, scb, p, txs, s1, s2); + } else { + /* loop through all pkts and free */ + u8 queue = txs->frameid & TXFID_QUEUE_MASK; + struct d11txh *txh; + u16 mcl; + while (p) { + tx_info = IEEE80211_SKB_CB(p); + txh = (struct d11txh *) p->data; + trace_brcms_txdesc(&wlc->hw->d11core->dev, txh, + sizeof(*txh)); + mcl = le16_to_cpu(txh->MacTxControlLow); + brcmu_pkt_buf_free_skb(p); + /* break out if last packet of ampdu */ + if (((mcl & TXC_AMPDU_MASK) >> TXC_AMPDU_SHIFT) == + TXC_AMPDU_LAST) + break; + p = dma_getnexttxp(wlc->hw->di[queue], + DMA_RANGE_TRANSMITTED); + } + } +} + +void brcms_c_ampdu_macaddr_upd(struct brcms_c_info *wlc) +{ + char template[T_RAM_ACCESS_SZ * 2]; + + /* driver needs to write the ta in the template; ta is at offset 16 */ + memset(template, 0, sizeof(template)); + memcpy(template, wlc->pub->cur_etheraddr, ETH_ALEN); + brcms_b_write_template_ram(wlc->hw, (T_BA_TPL_BASE + 16), + (T_RAM_ACCESS_SZ * 2), + template); +} + +bool brcms_c_aggregatable(struct brcms_c_info *wlc, u8 tid) +{ + return wlc->ampdu->ini_enable[tid]; +} + +void brcms_c_ampdu_shm_upd(struct ampdu_info *ampdu) +{ + struct brcms_c_info *wlc = ampdu->wlc; + + /* + * Extend ucode internal watchdog timer to + * match larger received frames + */ + if ((ampdu->rx_factor & IEEE80211_HT_AMPDU_PARM_FACTOR) == + IEEE80211_HT_MAX_AMPDU_64K) { + brcms_b_write_shm(wlc->hw, M_MIMO_MAXSYM, MIMO_MAXSYM_MAX); + brcms_b_write_shm(wlc->hw, M_WATCHDOG_8TU, WATCHDOG_8TU_MAX); + } else { + brcms_b_write_shm(wlc->hw, M_MIMO_MAXSYM, MIMO_MAXSYM_DEF); + brcms_b_write_shm(wlc->hw, M_WATCHDOG_8TU, WATCHDOG_8TU_DEF); + } +} + +/* + * callback function that helps invalidating ampdu packets in a DMA queue + */ +static void dma_cb_fn_ampdu(void *txi, void *arg_a) +{ + struct ieee80211_sta *sta = arg_a; + struct ieee80211_tx_info *tx_info = (struct ieee80211_tx_info *)txi; + + if ((tx_info->flags & IEEE80211_TX_CTL_AMPDU) && + (tx_info->rate_driver_data[0] == sta || sta == NULL)) + tx_info->rate_driver_data[0] = NULL; +} + +/* + * When a remote party is no longer available for ampdu communication, any + * pending tx ampdu packets in the driver have to be flushed. + */ +void brcms_c_ampdu_flush(struct brcms_c_info *wlc, + struct ieee80211_sta *sta, u16 tid) +{ + brcms_c_inval_dma_pkts(wlc->hw, sta, dma_cb_fn_ampdu); +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/ampdu.h b/drivers/net/wireless/brcm80211/brcmsmac/ampdu.h new file mode 100644 index 0000000..73d01e5 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/ampdu.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_AMPDU_H_ +#define _BRCM_AMPDU_H_ + +/* + * Data structure representing an in-progress session for accumulating + * frames for AMPDU. + * + * wlc: pointer to common driver data + * skb_list: queue of skb's for AMPDU + * max_ampdu_len: maximum length for this AMPDU + * max_ampdu_frames: maximum number of frames for this AMPDU + * ampdu_len: total number of bytes accumulated for this AMPDU + * dma_len: DMA length of this AMPDU + */ +struct brcms_ampdu_session { + struct brcms_c_info *wlc; + struct sk_buff_head skb_list; + unsigned max_ampdu_len; + u16 max_ampdu_frames; + u16 ampdu_len; + u16 dma_len; +}; + +extern void brcms_c_ampdu_reset_session(struct brcms_ampdu_session *session, + struct brcms_c_info *wlc); +extern int brcms_c_ampdu_add_frame(struct brcms_ampdu_session *session, + struct sk_buff *p); +extern void brcms_c_ampdu_finalize(struct brcms_ampdu_session *session); + +extern struct ampdu_info *brcms_c_ampdu_attach(struct brcms_c_info *wlc); +extern void brcms_c_ampdu_detach(struct ampdu_info *ampdu); +extern void brcms_c_ampdu_dotxstatus(struct ampdu_info *ampdu, struct scb *scb, + struct sk_buff *p, struct tx_status *txs); +extern void brcms_c_ampdu_macaddr_upd(struct brcms_c_info *wlc); +extern void brcms_c_ampdu_shm_upd(struct ampdu_info *ampdu); + +#endif /* _BRCM_AMPDU_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/antsel.c b/drivers/net/wireless/brcm80211/brcmsmac/antsel.c new file mode 100644 index 0000000..54c6169 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/antsel.c @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "types.h" +#include "main.h" +#include "phy_shim.h" +#include "antsel.h" +#include "debug.h" + +#define ANT_SELCFG_AUTO 0x80 /* bit indicates antenna sel AUTO */ +#define ANT_SELCFG_MASK 0x33 /* antenna configuration mask */ +#define ANT_SELCFG_TX_UNICAST 0 /* unicast tx antenna configuration */ +#define ANT_SELCFG_RX_UNICAST 1 /* unicast rx antenna configuration */ +#define ANT_SELCFG_TX_DEF 2 /* default tx antenna configuration */ +#define ANT_SELCFG_RX_DEF 3 /* default rx antenna configuration */ + +/* useful macros */ +#define BRCMS_ANTSEL_11N_0(ant) ((((ant) & ANT_SELCFG_MASK) >> 4) & 0xf) +#define BRCMS_ANTSEL_11N_1(ant) (((ant) & ANT_SELCFG_MASK) & 0xf) +#define BRCMS_ANTIDX_11N(ant) (((BRCMS_ANTSEL_11N_0(ant)) << 2) +\ + (BRCMS_ANTSEL_11N_1(ant))) +#define BRCMS_ANT_ISAUTO_11N(ant) (((ant) & ANT_SELCFG_AUTO) == ANT_SELCFG_AUTO) +#define BRCMS_ANTSEL_11N(ant) ((ant) & ANT_SELCFG_MASK) + +/* antenna switch */ +/* defines for no boardlevel antenna diversity */ +#define ANT_SELCFG_DEF_2x2 0x01 /* default antenna configuration */ + +/* 2x3 antdiv defines and tables for GPIO communication */ +#define ANT_SELCFG_NUM_2x3 3 +#define ANT_SELCFG_DEF_2x3 0x01 /* default antenna configuration */ + +/* 2x4 antdiv rev4 defines and tables for GPIO communication */ +#define ANT_SELCFG_NUM_2x4 4 +#define ANT_SELCFG_DEF_2x4 0x02 /* default antenna configuration */ + +static const u16 mimo_2x4_div_antselpat_tbl[] = { + 0, 0, 0x9, 0xa, /* ant0: 0 ant1: 2,3 */ + 0, 0, 0x5, 0x6, /* ant0: 1 ant1: 2,3 */ + 0, 0, 0, 0, /* n.a. */ + 0, 0, 0, 0 /* n.a. */ +}; + +static const u8 mimo_2x4_div_antselid_tbl[16] = { + 0, 0, 0, 0, 0, 2, 3, 0, + 0, 0, 1, 0, 0, 0, 0, 0 /* pat to antselid */ +}; + +static const u16 mimo_2x3_div_antselpat_tbl[] = { + 16, 0, 1, 16, /* ant0: 0 ant1: 1,2 */ + 16, 16, 16, 16, /* n.a. */ + 16, 2, 16, 16, /* ant0: 2 ant1: 1 */ + 16, 16, 16, 16 /* n.a. */ +}; + +static const u8 mimo_2x3_div_antselid_tbl[16] = { + 0, 1, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 /* pat to antselid */ +}; + +/* boardlevel antenna selection: init antenna selection structure */ +static void +brcms_c_antsel_init_cfg(struct antsel_info *asi, struct brcms_antselcfg *antsel, + bool auto_sel) +{ + if (asi->antsel_type == ANTSEL_2x3) { + u8 antcfg_def = ANT_SELCFG_DEF_2x3 | + ((asi->antsel_avail && auto_sel) ? ANT_SELCFG_AUTO : 0); + antsel->ant_config[ANT_SELCFG_TX_DEF] = antcfg_def; + antsel->ant_config[ANT_SELCFG_TX_UNICAST] = antcfg_def; + antsel->ant_config[ANT_SELCFG_RX_DEF] = antcfg_def; + antsel->ant_config[ANT_SELCFG_RX_UNICAST] = antcfg_def; + antsel->num_antcfg = ANT_SELCFG_NUM_2x3; + + } else if (asi->antsel_type == ANTSEL_2x4) { + + antsel->ant_config[ANT_SELCFG_TX_DEF] = ANT_SELCFG_DEF_2x4; + antsel->ant_config[ANT_SELCFG_TX_UNICAST] = ANT_SELCFG_DEF_2x4; + antsel->ant_config[ANT_SELCFG_RX_DEF] = ANT_SELCFG_DEF_2x4; + antsel->ant_config[ANT_SELCFG_RX_UNICAST] = ANT_SELCFG_DEF_2x4; + antsel->num_antcfg = ANT_SELCFG_NUM_2x4; + + } else { /* no antenna selection available */ + + antsel->ant_config[ANT_SELCFG_TX_DEF] = ANT_SELCFG_DEF_2x2; + antsel->ant_config[ANT_SELCFG_TX_UNICAST] = ANT_SELCFG_DEF_2x2; + antsel->ant_config[ANT_SELCFG_RX_DEF] = ANT_SELCFG_DEF_2x2; + antsel->ant_config[ANT_SELCFG_RX_UNICAST] = ANT_SELCFG_DEF_2x2; + antsel->num_antcfg = 0; + } +} + +struct antsel_info *brcms_c_antsel_attach(struct brcms_c_info *wlc) +{ + struct antsel_info *asi; + struct ssb_sprom *sprom = &wlc->hw->d11core->bus->sprom; + + asi = kzalloc(sizeof(struct antsel_info), GFP_ATOMIC); + if (!asi) + return NULL; + + asi->wlc = wlc; + asi->pub = wlc->pub; + asi->antsel_type = ANTSEL_NA; + asi->antsel_avail = false; + asi->antsel_antswitch = sprom->antswitch; + + if ((asi->pub->sromrev >= 4) && (asi->antsel_antswitch != 0)) { + switch (asi->antsel_antswitch) { + case ANTSWITCH_TYPE_1: + case ANTSWITCH_TYPE_2: + case ANTSWITCH_TYPE_3: + /* 4321/2 board with 2x3 switch logic */ + asi->antsel_type = ANTSEL_2x3; + /* Antenna selection availability */ + if ((sprom->ant_available_bg == 7) || + (sprom->ant_available_a == 7)) { + asi->antsel_avail = true; + } else if ( + sprom->ant_available_bg == 3 || + sprom->ant_available_a == 3) { + asi->antsel_avail = false; + } else { + asi->antsel_avail = false; + brcms_err(wlc->hw->d11core, + "antsel_attach: 2o3 " + "board cfg invalid\n"); + } + + break; + default: + break; + } + } else if ((asi->pub->sromrev == 4) && + (sprom->ant_available_bg == 7) && + (sprom->ant_available_a == 0)) { + /* hack to match old 4321CB2 cards with 2of3 antenna switch */ + asi->antsel_type = ANTSEL_2x3; + asi->antsel_avail = true; + } else if (asi->pub->boardflags2 & BFL2_2X4_DIV) { + asi->antsel_type = ANTSEL_2x4; + asi->antsel_avail = true; + } + + /* Set the antenna selection type for the low driver */ + brcms_b_antsel_type_set(wlc->hw, asi->antsel_type); + + /* Init (auto/manual) antenna selection */ + brcms_c_antsel_init_cfg(asi, &asi->antcfg_11n, true); + brcms_c_antsel_init_cfg(asi, &asi->antcfg_cur, true); + + return asi; +} + +void brcms_c_antsel_detach(struct antsel_info *asi) +{ + kfree(asi); +} + +/* + * boardlevel antenna selection: + * convert ant_cfg to mimo_antsel (ucode interface) + */ +static u16 brcms_c_antsel_antcfg2antsel(struct antsel_info *asi, u8 ant_cfg) +{ + u8 idx = BRCMS_ANTIDX_11N(BRCMS_ANTSEL_11N(ant_cfg)); + u16 mimo_antsel = 0; + + if (asi->antsel_type == ANTSEL_2x4) { + /* 2x4 antenna diversity board, 4 cfgs: 0-2 0-3 1-2 1-3 */ + mimo_antsel = (mimo_2x4_div_antselpat_tbl[idx] & 0xf); + return mimo_antsel; + + } else if (asi->antsel_type == ANTSEL_2x3) { + /* 2x3 antenna selection, 3 cfgs: 0-1 0-2 2-1 */ + mimo_antsel = (mimo_2x3_div_antselpat_tbl[idx] & 0xf); + return mimo_antsel; + } + + return mimo_antsel; +} + +/* boardlevel antenna selection: ucode interface control */ +static int brcms_c_antsel_cfgupd(struct antsel_info *asi, + struct brcms_antselcfg *antsel) +{ + struct brcms_c_info *wlc = asi->wlc; + u8 ant_cfg; + u16 mimo_antsel; + + /* 1) Update TX antconfig for all frames that are not unicast data + * (aka default TX) + */ + ant_cfg = antsel->ant_config[ANT_SELCFG_TX_DEF]; + mimo_antsel = brcms_c_antsel_antcfg2antsel(asi, ant_cfg); + brcms_b_write_shm(wlc->hw, M_MIMO_ANTSEL_TXDFLT, mimo_antsel); + /* + * Update driver stats for currently selected + * default tx/rx antenna config + */ + asi->antcfg_cur.ant_config[ANT_SELCFG_TX_DEF] = ant_cfg; + + /* 2) Update RX antconfig for all frames that are not unicast data + * (aka default RX) + */ + ant_cfg = antsel->ant_config[ANT_SELCFG_RX_DEF]; + mimo_antsel = brcms_c_antsel_antcfg2antsel(asi, ant_cfg); + brcms_b_write_shm(wlc->hw, M_MIMO_ANTSEL_RXDFLT, mimo_antsel); + /* + * Update driver stats for currently selected + * default tx/rx antenna config + */ + asi->antcfg_cur.ant_config[ANT_SELCFG_RX_DEF] = ant_cfg; + + return 0; +} + +void brcms_c_antsel_init(struct antsel_info *asi) +{ + if ((asi->antsel_type == ANTSEL_2x3) || + (asi->antsel_type == ANTSEL_2x4)) + brcms_c_antsel_cfgupd(asi, &asi->antcfg_11n); +} + +/* boardlevel antenna selection: convert id to ant_cfg */ +static u8 brcms_c_antsel_id2antcfg(struct antsel_info *asi, u8 id) +{ + u8 antcfg = ANT_SELCFG_DEF_2x2; + + if (asi->antsel_type == ANTSEL_2x4) { + /* 2x4 antenna diversity board, 4 cfgs: 0-2 0-3 1-2 1-3 */ + antcfg = (((id & 0x2) << 3) | ((id & 0x1) + 2)); + return antcfg; + + } else if (asi->antsel_type == ANTSEL_2x3) { + /* 2x3 antenna selection, 3 cfgs: 0-1 0-2 2-1 */ + antcfg = (((id & 0x02) << 4) | ((id & 0x1) + 1)); + return antcfg; + } + + return antcfg; +} + +void +brcms_c_antsel_antcfg_get(struct antsel_info *asi, bool usedef, bool sel, + u8 antselid, u8 fbantselid, u8 *antcfg, + u8 *fbantcfg) +{ + u8 ant; + + /* if use default, assign it and return */ + if (usedef) { + *antcfg = asi->antcfg_11n.ant_config[ANT_SELCFG_TX_DEF]; + *fbantcfg = *antcfg; + return; + } + + if (!sel) { + *antcfg = asi->antcfg_11n.ant_config[ANT_SELCFG_TX_UNICAST]; + *fbantcfg = *antcfg; + + } else { + ant = asi->antcfg_11n.ant_config[ANT_SELCFG_TX_UNICAST]; + if ((ant & ANT_SELCFG_AUTO) == ANT_SELCFG_AUTO) { + *antcfg = brcms_c_antsel_id2antcfg(asi, antselid); + *fbantcfg = brcms_c_antsel_id2antcfg(asi, fbantselid); + } else { + *antcfg = + asi->antcfg_11n.ant_config[ANT_SELCFG_TX_UNICAST]; + *fbantcfg = *antcfg; + } + } + return; +} + +/* boardlevel antenna selection: convert mimo_antsel (ucode interface) to id */ +u8 brcms_c_antsel_antsel2id(struct antsel_info *asi, u16 antsel) +{ + u8 antselid = 0; + + if (asi->antsel_type == ANTSEL_2x4) { + /* 2x4 antenna diversity board, 4 cfgs: 0-2 0-3 1-2 1-3 */ + antselid = mimo_2x4_div_antselid_tbl[(antsel & 0xf)]; + return antselid; + + } else if (asi->antsel_type == ANTSEL_2x3) { + /* 2x3 antenna selection, 3 cfgs: 0-1 0-2 2-1 */ + antselid = mimo_2x3_div_antselid_tbl[(antsel & 0xf)]; + return antselid; + } + + return antselid; +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/antsel.h b/drivers/net/wireless/brcm80211/brcmsmac/antsel.h new file mode 100644 index 0000000..97ea388 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/antsel.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_ANTSEL_H_ +#define _BRCM_ANTSEL_H_ + +extern struct antsel_info *brcms_c_antsel_attach(struct brcms_c_info *wlc); +extern void brcms_c_antsel_detach(struct antsel_info *asi); +extern void brcms_c_antsel_init(struct antsel_info *asi); +extern void brcms_c_antsel_antcfg_get(struct antsel_info *asi, bool usedef, + bool sel, + u8 id, u8 fbid, u8 *antcfg, + u8 *fbantcfg); +extern u8 brcms_c_antsel_antsel2id(struct antsel_info *asi, u16 antsel); + +#endif /* _BRCM_ANTSEL_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/brcms_trace_events.c b/drivers/net/wireless/brcm80211/brcmsmac/brcms_trace_events.c new file mode 100644 index 0000000..52fc9ee --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/brcms_trace_events.c @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2011 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include /* bug in tracepoint.h, it should include this */ + +#ifndef __CHECKER__ +#include "mac80211_if.h" +#define CREATE_TRACE_POINTS +#include "brcms_trace_events.h" +#endif diff --git a/drivers/net/wireless/brcm80211/brcmsmac/brcms_trace_events.h b/drivers/net/wireless/brcm80211/brcmsmac/brcms_trace_events.h new file mode 100644 index 0000000..18dda96 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/brcms_trace_events.h @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2011 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#if !defined(__TRACE_BRCMSMAC_H) || defined(TRACE_HEADER_MULTI_READ) + +#define __TRACE_BRCMSMAC_H + +#include +#include +#include +#include "mac80211_if.h" + +#ifndef CPTCFG_BRCM_TRACING +#undef TRACE_EVENT +#define TRACE_EVENT(name, proto, ...) \ +static inline void trace_ ## name(proto) {} +#undef DECLARE_EVENT_CLASS +#define DECLARE_EVENT_CLASS(...) +#undef DEFINE_EVENT +#define DEFINE_EVENT(evt_class, name, proto, ...) \ +static inline void trace_ ## name(proto) {} +#endif + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM brcmsmac + +/* + * We define a tracepoint, its arguments, its printk format and its + * 'fast binary record' layout. + */ +TRACE_EVENT(brcms_timer, + /* TPPROTO is the prototype of the function called by this tracepoint */ + TP_PROTO(struct brcms_timer *t), + /* + * TPARGS(firstarg, p) are the parameters names, same as found in the + * prototype. + */ + TP_ARGS(t), + /* + * Fast binary tracing: define the trace record via TP_STRUCT__entry(). + * You can think about it like a regular C structure local variable + * definition. + */ + TP_STRUCT__entry( + __field(uint, ms) + __field(uint, set) + __field(uint, periodic) + ), + TP_fast_assign( + __entry->ms = t->ms; + __entry->set = t->set; + __entry->periodic = t->periodic; + ), + TP_printk( + "ms=%u set=%u periodic=%u", + __entry->ms, __entry->set, __entry->periodic + ) +); + +TRACE_EVENT(brcms_dpc, + TP_PROTO(unsigned long data), + TP_ARGS(data), + TP_STRUCT__entry( + __field(unsigned long, data) + ), + TP_fast_assign( + __entry->data = data; + ), + TP_printk( + "data=%p", + (void *)__entry->data + ) +); + +TRACE_EVENT(brcms_macintstatus, + TP_PROTO(const struct device *dev, int in_isr, u32 macintstatus, + u32 mask), + TP_ARGS(dev, in_isr, macintstatus, mask), + TP_STRUCT__entry( + __string(dev, dev_name(dev)) + __field(int, in_isr) + __field(u32, macintstatus) + __field(u32, mask) + ), + TP_fast_assign( + __assign_str(dev, dev_name(dev)); + __entry->in_isr = in_isr; + __entry->macintstatus = macintstatus; + __entry->mask = mask; + ), + TP_printk("[%s] in_isr=%d macintstatus=%#x mask=%#x", __get_str(dev), + __entry->in_isr, __entry->macintstatus, __entry->mask) +); + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM brcmsmac_tx + +TRACE_EVENT(brcms_txdesc, + TP_PROTO(const struct device *dev, + void *txh, size_t txh_len), + TP_ARGS(dev, txh, txh_len), + TP_STRUCT__entry( + __string(dev, dev_name(dev)) + __dynamic_array(u8, txh, txh_len) + ), + TP_fast_assign( + __assign_str(dev, dev_name(dev)); + memcpy(__get_dynamic_array(txh), txh, txh_len); + ), + TP_printk("[%s] txdesc", __get_str(dev)) +); + +TRACE_EVENT(brcms_txstatus, + TP_PROTO(const struct device *dev, u16 framelen, u16 frameid, + u16 status, u16 lasttxtime, u16 sequence, u16 phyerr, + u16 ackphyrxsh), + TP_ARGS(dev, framelen, frameid, status, lasttxtime, sequence, phyerr, + ackphyrxsh), + TP_STRUCT__entry( + __string(dev, dev_name(dev)) + __field(u16, framelen) + __field(u16, frameid) + __field(u16, status) + __field(u16, lasttxtime) + __field(u16, sequence) + __field(u16, phyerr) + __field(u16, ackphyrxsh) + ), + TP_fast_assign( + __assign_str(dev, dev_name(dev)); + __entry->framelen = framelen; + __entry->frameid = frameid; + __entry->status = status; + __entry->lasttxtime = lasttxtime; + __entry->sequence = sequence; + __entry->phyerr = phyerr; + __entry->ackphyrxsh = ackphyrxsh; + ), + TP_printk("[%s] FrameId %#04x TxStatus %#04x LastTxTime %#04x " + "Seq %#04x PHYTxStatus %#04x RxAck %#04x", + __get_str(dev), __entry->frameid, __entry->status, + __entry->lasttxtime, __entry->sequence, __entry->phyerr, + __entry->ackphyrxsh) +); + +TRACE_EVENT(brcms_ampdu_session, + TP_PROTO(const struct device *dev, unsigned max_ampdu_len, + u16 max_ampdu_frames, u16 ampdu_len, u16 ampdu_frames, + u16 dma_len), + TP_ARGS(dev, max_ampdu_len, max_ampdu_frames, ampdu_len, ampdu_frames, + dma_len), + TP_STRUCT__entry( + __string(dev, dev_name(dev)) + __field(unsigned, max_ampdu_len) + __field(u16, max_ampdu_frames) + __field(u16, ampdu_len) + __field(u16, ampdu_frames) + __field(u16, dma_len) + ), + TP_fast_assign( + __assign_str(dev, dev_name(dev)); + __entry->max_ampdu_len = max_ampdu_len; + __entry->max_ampdu_frames = max_ampdu_frames; + __entry->ampdu_len = ampdu_len; + __entry->ampdu_frames = ampdu_frames; + __entry->dma_len = dma_len; + ), + TP_printk("[%s] ampdu session max_len=%u max_frames=%u len=%u frames=%u dma_len=%u", + __get_str(dev), __entry->max_ampdu_len, + __entry->max_ampdu_frames, __entry->ampdu_len, + __entry->ampdu_frames, __entry->dma_len) +); + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM brcmsmac_msg + +#define MAX_MSG_LEN 100 + +DECLARE_EVENT_CLASS(brcms_msg_event, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf), + TP_STRUCT__entry( + __dynamic_array(char, msg, MAX_MSG_LEN) + ), + TP_fast_assign( + WARN_ON_ONCE(vsnprintf(__get_dynamic_array(msg), + MAX_MSG_LEN, vaf->fmt, + *vaf->va) >= MAX_MSG_LEN); + ), + TP_printk("%s", __get_str(msg)) +); + +DEFINE_EVENT(brcms_msg_event, brcms_info, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(brcms_msg_event, brcms_warn, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(brcms_msg_event, brcms_err, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(brcms_msg_event, brcms_crit, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +TRACE_EVENT(brcms_dbg, + TP_PROTO(u32 level, const char *func, struct va_format *vaf), + TP_ARGS(level, func, vaf), + TP_STRUCT__entry( + __field(u32, level) + __string(func, func) + __dynamic_array(char, msg, MAX_MSG_LEN) + ), + TP_fast_assign( + __entry->level = level; + __assign_str(func, func); + WARN_ON_ONCE(vsnprintf(__get_dynamic_array(msg), + MAX_MSG_LEN, vaf->fmt, + *vaf->va) >= MAX_MSG_LEN); + ), + TP_printk("%s: %s", __get_str(func), __get_str(msg)) +); + +#endif /* __TRACE_BRCMSMAC_H */ + +#ifdef CPTCFG_BRCM_TRACING + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE brcms_trace_events + +#include + +#endif /* CPTCFG_BRCM_TRACING */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/channel.c b/drivers/net/wireless/brcm80211/brcmsmac/channel.c new file mode 100644 index 0000000..cc87926 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/channel.c @@ -0,0 +1,782 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include +#include "pub.h" +#include "phy/phy_hal.h" +#include "main.h" +#include "stf.h" +#include "channel.h" +#include "mac80211_if.h" +#include "debug.h" + +/* QDB() macro takes a dB value and converts to a quarter dB value */ +#define QDB(n) ((n) * BRCMS_TXPWR_DB_FACTOR) + +#define LOCALE_MIMO_IDX_bn 0 +#define LOCALE_MIMO_IDX_11n 0 + +/* max of BAND_5G_PWR_LVLS and 14 for 2.4 GHz */ +#define BRCMS_MAXPWR_MIMO_TBL_SIZE 14 + +/* maxpwr mapping to 5GHz band channels: + * maxpwr[0] - channels [34-48] + * maxpwr[1] - channels [52-60] + * maxpwr[2] - channels [62-64] + * maxpwr[3] - channels [100-140] + * maxpwr[4] - channels [149-165] + */ +#define BAND_5G_PWR_LVLS 5 /* 5 power levels for 5G */ + +#define LC(id) LOCALE_MIMO_IDX_ ## id + +#define LOCALES(mimo2, mimo5) \ + {LC(mimo2), LC(mimo5)} + +/* macro to get 5 GHz channel group index for tx power */ +#define CHANNEL_POWER_IDX_5G(c) (((c) < 52) ? 0 : \ + (((c) < 62) ? 1 : \ + (((c) < 100) ? 2 : \ + (((c) < 149) ? 3 : 4)))) + +#define BRCM_2GHZ_2412_2462 REG_RULE(2412-10, 2462+10, 40, 0, 19, 0) +#define BRCM_2GHZ_2467_2472 REG_RULE(2467-10, 2472+10, 20, 0, 19, \ + NL80211_RRF_PASSIVE_SCAN | \ + NL80211_RRF_NO_IBSS) + +#define BRCM_5GHZ_5180_5240 REG_RULE(5180-10, 5240+10, 40, 0, 21, \ + NL80211_RRF_PASSIVE_SCAN | \ + NL80211_RRF_NO_IBSS) +#define BRCM_5GHZ_5260_5320 REG_RULE(5260-10, 5320+10, 40, 0, 21, \ + NL80211_RRF_PASSIVE_SCAN | \ + NL80211_RRF_DFS | \ + NL80211_RRF_NO_IBSS) +#define BRCM_5GHZ_5500_5700 REG_RULE(5500-10, 5700+10, 40, 0, 21, \ + NL80211_RRF_PASSIVE_SCAN | \ + NL80211_RRF_DFS | \ + NL80211_RRF_NO_IBSS) +#define BRCM_5GHZ_5745_5825 REG_RULE(5745-10, 5825+10, 40, 0, 21, \ + NL80211_RRF_PASSIVE_SCAN | \ + NL80211_RRF_NO_IBSS) + +static const struct ieee80211_regdomain brcms_regdom_x2 = { + .n_reg_rules = 6, + .alpha2 = "X2", + .reg_rules = { + BRCM_2GHZ_2412_2462, + BRCM_2GHZ_2467_2472, + BRCM_5GHZ_5180_5240, + BRCM_5GHZ_5260_5320, + BRCM_5GHZ_5500_5700, + BRCM_5GHZ_5745_5825, + } +}; + + /* locale per-channel tx power limits for MIMO frames + * maxpwr arrays are index by channel for 2.4 GHz limits, and + * by sub-band for 5 GHz limits using CHANNEL_POWER_IDX_5G(channel) + */ +struct locale_mimo_info { + /* tx 20 MHz power limits, qdBm units */ + s8 maxpwr20[BRCMS_MAXPWR_MIMO_TBL_SIZE]; + /* tx 40 MHz power limits, qdBm units */ + s8 maxpwr40[BRCMS_MAXPWR_MIMO_TBL_SIZE]; +}; + +/* Country names and abbreviations with locale defined from ISO 3166 */ +struct country_info { + const u8 locale_mimo_2G; /* 2.4G mimo info */ + const u8 locale_mimo_5G; /* 5G mimo info */ +}; + +struct brcms_regd { + struct country_info country; + const struct ieee80211_regdomain *regdomain; +}; + +struct brcms_cm_info { + struct brcms_pub *pub; + struct brcms_c_info *wlc; + const struct brcms_regd *world_regd; +}; + +/* + * MIMO Locale Definitions - 2.4 GHz + */ +static const struct locale_mimo_info locale_bn = { + {QDB(13), QDB(13), QDB(13), QDB(13), QDB(13), + QDB(13), QDB(13), QDB(13), QDB(13), QDB(13), + QDB(13), QDB(13), QDB(13)}, + {0, 0, QDB(13), QDB(13), QDB(13), + QDB(13), QDB(13), QDB(13), QDB(13), QDB(13), + QDB(13), 0, 0}, +}; + +static const struct locale_mimo_info *g_mimo_2g_table[] = { + &locale_bn +}; + +/* + * MIMO Locale Definitions - 5 GHz + */ +static const struct locale_mimo_info locale_11n = { + { /* 12.5 dBm */ 50, 50, 50, QDB(15), QDB(15)}, + {QDB(14), QDB(15), QDB(15), QDB(15), QDB(15)}, +}; + +static const struct locale_mimo_info *g_mimo_5g_table[] = { + &locale_11n +}; + +static const struct brcms_regd cntry_locales[] = { + /* Worldwide RoW 2, must always be at index 0 */ + { + .country = LOCALES(bn, 11n), + .regdomain = &brcms_regdom_x2, + }, +}; + +static const struct locale_mimo_info *brcms_c_get_mimo_2g(u8 locale_idx) +{ + if (locale_idx >= ARRAY_SIZE(g_mimo_2g_table)) + return NULL; + + return g_mimo_2g_table[locale_idx]; +} + +static const struct locale_mimo_info *brcms_c_get_mimo_5g(u8 locale_idx) +{ + if (locale_idx >= ARRAY_SIZE(g_mimo_5g_table)) + return NULL; + + return g_mimo_5g_table[locale_idx]; +} + +/* + * Indicates whether the country provided is valid to pass + * to cfg80211 or not. + * + * returns true if valid; false if not. + */ +static bool brcms_c_country_valid(const char *ccode) +{ + /* + * only allow ascii alpha uppercase for the first 2 + * chars. + */ + if (!((0x80 & ccode[0]) == 0 && ccode[0] >= 0x41 && ccode[0] <= 0x5A && + (0x80 & ccode[1]) == 0 && ccode[1] >= 0x41 && ccode[1] <= 0x5A)) + return false; + + /* + * do not match ISO 3166-1 user assigned country codes + * that may be in the driver table + */ + if (!strcmp("AA", ccode) || /* AA */ + !strcmp("ZZ", ccode) || /* ZZ */ + ccode[0] == 'X' || /* XA - XZ */ + (ccode[0] == 'Q' && /* QM - QZ */ + (ccode[1] >= 'M' && ccode[1] <= 'Z'))) + return false; + + if (!strcmp("NA", ccode)) + return false; + + return true; +} + +static const struct brcms_regd *brcms_world_regd(const char *regdom, int len) +{ + const struct brcms_regd *regd = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(cntry_locales); i++) { + if (!strncmp(regdom, cntry_locales[i].regdomain->alpha2, len)) { + regd = &cntry_locales[i]; + break; + } + } + + return regd; +} + +static const struct brcms_regd *brcms_default_world_regd(void) +{ + return &cntry_locales[0]; +} + +/* JP, J1 - J10 are Japan ccodes */ +static bool brcms_c_japan_ccode(const char *ccode) +{ + return (ccode[0] == 'J' && + (ccode[1] == 'P' || (ccode[1] >= '1' && ccode[1] <= '9'))); +} + +static void +brcms_c_channel_min_txpower_limits_with_local_constraint( + struct brcms_cm_info *wlc_cm, struct txpwr_limits *txpwr, + u8 local_constraint_qdbm) +{ + int j; + + /* CCK Rates */ + for (j = 0; j < WL_TX_POWER_CCK_NUM; j++) + txpwr->cck[j] = min(txpwr->cck[j], local_constraint_qdbm); + + /* 20 MHz Legacy OFDM SISO */ + for (j = 0; j < WL_TX_POWER_OFDM_NUM; j++) + txpwr->ofdm[j] = min(txpwr->ofdm[j], local_constraint_qdbm); + + /* 20 MHz Legacy OFDM CDD */ + for (j = 0; j < BRCMS_NUM_RATES_OFDM; j++) + txpwr->ofdm_cdd[j] = + min(txpwr->ofdm_cdd[j], local_constraint_qdbm); + + /* 40 MHz Legacy OFDM SISO */ + for (j = 0; j < BRCMS_NUM_RATES_OFDM; j++) + txpwr->ofdm_40_siso[j] = + min(txpwr->ofdm_40_siso[j], local_constraint_qdbm); + + /* 40 MHz Legacy OFDM CDD */ + for (j = 0; j < BRCMS_NUM_RATES_OFDM; j++) + txpwr->ofdm_40_cdd[j] = + min(txpwr->ofdm_40_cdd[j], local_constraint_qdbm); + + /* 20MHz MCS 0-7 SISO */ + for (j = 0; j < BRCMS_NUM_RATES_MCS_1_STREAM; j++) + txpwr->mcs_20_siso[j] = + min(txpwr->mcs_20_siso[j], local_constraint_qdbm); + + /* 20MHz MCS 0-7 CDD */ + for (j = 0; j < BRCMS_NUM_RATES_MCS_1_STREAM; j++) + txpwr->mcs_20_cdd[j] = + min(txpwr->mcs_20_cdd[j], local_constraint_qdbm); + + /* 20MHz MCS 0-7 STBC */ + for (j = 0; j < BRCMS_NUM_RATES_MCS_1_STREAM; j++) + txpwr->mcs_20_stbc[j] = + min(txpwr->mcs_20_stbc[j], local_constraint_qdbm); + + /* 20MHz MCS 8-15 MIMO */ + for (j = 0; j < BRCMS_NUM_RATES_MCS_2_STREAM; j++) + txpwr->mcs_20_mimo[j] = + min(txpwr->mcs_20_mimo[j], local_constraint_qdbm); + + /* 40MHz MCS 0-7 SISO */ + for (j = 0; j < BRCMS_NUM_RATES_MCS_1_STREAM; j++) + txpwr->mcs_40_siso[j] = + min(txpwr->mcs_40_siso[j], local_constraint_qdbm); + + /* 40MHz MCS 0-7 CDD */ + for (j = 0; j < BRCMS_NUM_RATES_MCS_1_STREAM; j++) + txpwr->mcs_40_cdd[j] = + min(txpwr->mcs_40_cdd[j], local_constraint_qdbm); + + /* 40MHz MCS 0-7 STBC */ + for (j = 0; j < BRCMS_NUM_RATES_MCS_1_STREAM; j++) + txpwr->mcs_40_stbc[j] = + min(txpwr->mcs_40_stbc[j], local_constraint_qdbm); + + /* 40MHz MCS 8-15 MIMO */ + for (j = 0; j < BRCMS_NUM_RATES_MCS_2_STREAM; j++) + txpwr->mcs_40_mimo[j] = + min(txpwr->mcs_40_mimo[j], local_constraint_qdbm); + + /* 40MHz MCS 32 */ + txpwr->mcs32 = min(txpwr->mcs32, local_constraint_qdbm); + +} + +/* + * set the driver's current country and regulatory information + * using a country code as the source. Look up built in country + * information found with the country code. + */ +static void +brcms_c_set_country(struct brcms_cm_info *wlc_cm, + const struct brcms_regd *regd) +{ + struct brcms_c_info *wlc = wlc_cm->wlc; + + if ((wlc->pub->_n_enab & SUPPORT_11N) != + wlc->protection->nmode_user) + brcms_c_set_nmode(wlc); + + brcms_c_stf_ss_update(wlc, wlc->bandstate[BAND_2G_INDEX]); + brcms_c_stf_ss_update(wlc, wlc->bandstate[BAND_5G_INDEX]); + + brcms_c_set_gmode(wlc, wlc->protection->gmode_user, false); + + return; +} + +struct brcms_cm_info *brcms_c_channel_mgr_attach(struct brcms_c_info *wlc) +{ + struct brcms_cm_info *wlc_cm; + struct brcms_pub *pub = wlc->pub; + struct ssb_sprom *sprom = &wlc->hw->d11core->bus->sprom; + const char *ccode = sprom->alpha2; + int ccode_len = sizeof(sprom->alpha2); + + wlc_cm = kzalloc(sizeof(struct brcms_cm_info), GFP_ATOMIC); + if (wlc_cm == NULL) + return NULL; + wlc_cm->pub = pub; + wlc_cm->wlc = wlc; + wlc->cmi = wlc_cm; + + /* store the country code for passing up as a regulatory hint */ + wlc_cm->world_regd = brcms_world_regd(ccode, ccode_len); + if (brcms_c_country_valid(ccode)) + strncpy(wlc->pub->srom_ccode, ccode, ccode_len); + + /* + * If no custom world domain is found in the SROM, use the + * default "X2" domain. + */ + if (!wlc_cm->world_regd) { + wlc_cm->world_regd = brcms_default_world_regd(); + ccode = wlc_cm->world_regd->regdomain->alpha2; + ccode_len = BRCM_CNTRY_BUF_SZ - 1; + } + + /* save default country for exiting 11d regulatory mode */ + strncpy(wlc->country_default, ccode, ccode_len); + + /* initialize autocountry_default to driver default */ + strncpy(wlc->autocountry_default, ccode, ccode_len); + + brcms_c_set_country(wlc_cm, wlc_cm->world_regd); + + return wlc_cm; +} + +void brcms_c_channel_mgr_detach(struct brcms_cm_info *wlc_cm) +{ + kfree(wlc_cm); +} + +void +brcms_c_channel_set_chanspec(struct brcms_cm_info *wlc_cm, u16 chanspec, + u8 local_constraint_qdbm) +{ + struct brcms_c_info *wlc = wlc_cm->wlc; + struct ieee80211_channel *ch = wlc->pub->ieee_hw->conf.chandef.chan; + struct txpwr_limits txpwr; + + brcms_c_channel_reg_limits(wlc_cm, chanspec, &txpwr); + + brcms_c_channel_min_txpower_limits_with_local_constraint( + wlc_cm, &txpwr, local_constraint_qdbm + ); + + /* set or restore gmode as required by regulatory */ + if (ch->flags & IEEE80211_CHAN_NO_OFDM) + brcms_c_set_gmode(wlc, GMODE_LEGACY_B, false); + else + brcms_c_set_gmode(wlc, wlc->protection->gmode_user, false); + + brcms_b_set_chanspec(wlc->hw, chanspec, + !!(ch->flags & IEEE80211_CHAN_PASSIVE_SCAN), + &txpwr); +} + +void +brcms_c_channel_reg_limits(struct brcms_cm_info *wlc_cm, u16 chanspec, + struct txpwr_limits *txpwr) +{ + struct brcms_c_info *wlc = wlc_cm->wlc; + struct ieee80211_channel *ch = wlc->pub->ieee_hw->conf.chandef.chan; + uint i; + uint chan; + int maxpwr; + int delta; + const struct country_info *country; + struct brcms_band *band; + int conducted_max = BRCMS_TXPWR_MAX; + const struct locale_mimo_info *li_mimo; + int maxpwr20, maxpwr40; + int maxpwr_idx; + uint j; + + memset(txpwr, 0, sizeof(struct txpwr_limits)); + + if (WARN_ON(!ch)) + return; + + country = &wlc_cm->world_regd->country; + + chan = CHSPEC_CHANNEL(chanspec); + band = wlc->bandstate[chspec_bandunit(chanspec)]; + li_mimo = (band->bandtype == BRCM_BAND_5G) ? + brcms_c_get_mimo_5g(country->locale_mimo_5G) : + brcms_c_get_mimo_2g(country->locale_mimo_2G); + + delta = band->antgain; + + if (band->bandtype == BRCM_BAND_2G) + conducted_max = QDB(22); + + maxpwr = QDB(ch->max_power) - delta; + maxpwr = max(maxpwr, 0); + maxpwr = min(maxpwr, conducted_max); + + /* CCK txpwr limits for 2.4G band */ + if (band->bandtype == BRCM_BAND_2G) { + for (i = 0; i < BRCMS_NUM_RATES_CCK; i++) + txpwr->cck[i] = (u8) maxpwr; + } + + for (i = 0; i < BRCMS_NUM_RATES_OFDM; i++) { + txpwr->ofdm[i] = (u8) maxpwr; + + /* + * OFDM 40 MHz SISO has the same power as the corresponding + * MCS0-7 rate unless overriden by the locale specific code. + * We set this value to 0 as a flag (presumably 0 dBm isn't + * a possibility) and then copy the MCS0-7 value to the 40 MHz + * value if it wasn't explicitly set. + */ + txpwr->ofdm_40_siso[i] = 0; + + txpwr->ofdm_cdd[i] = (u8) maxpwr; + + txpwr->ofdm_40_cdd[i] = 0; + } + + delta = 0; + if (band->antgain > QDB(6)) + delta = band->antgain - QDB(6); /* Excess over 6 dB */ + + if (band->bandtype == BRCM_BAND_2G) + maxpwr_idx = (chan - 1); + else + maxpwr_idx = CHANNEL_POWER_IDX_5G(chan); + + maxpwr20 = li_mimo->maxpwr20[maxpwr_idx]; + maxpwr40 = li_mimo->maxpwr40[maxpwr_idx]; + + maxpwr20 = maxpwr20 - delta; + maxpwr20 = max(maxpwr20, 0); + maxpwr40 = maxpwr40 - delta; + maxpwr40 = max(maxpwr40, 0); + + /* Fill in the MCS 0-7 (SISO) rates */ + for (i = 0; i < BRCMS_NUM_RATES_MCS_1_STREAM; i++) { + + /* + * 20 MHz has the same power as the corresponding OFDM rate + * unless overriden by the locale specific code. + */ + txpwr->mcs_20_siso[i] = txpwr->ofdm[i]; + txpwr->mcs_40_siso[i] = 0; + } + + /* Fill in the MCS 0-7 CDD rates */ + for (i = 0; i < BRCMS_NUM_RATES_MCS_1_STREAM; i++) { + txpwr->mcs_20_cdd[i] = (u8) maxpwr20; + txpwr->mcs_40_cdd[i] = (u8) maxpwr40; + } + + /* + * These locales have SISO expressed in the + * table and override CDD later + */ + if (li_mimo == &locale_bn) { + if (li_mimo == &locale_bn) { + maxpwr20 = QDB(16); + maxpwr40 = 0; + + if (chan >= 3 && chan <= 11) + maxpwr40 = QDB(16); + } + + for (i = 0; i < BRCMS_NUM_RATES_MCS_1_STREAM; i++) { + txpwr->mcs_20_siso[i] = (u8) maxpwr20; + txpwr->mcs_40_siso[i] = (u8) maxpwr40; + } + } + + /* Fill in the MCS 0-7 STBC rates */ + for (i = 0; i < BRCMS_NUM_RATES_MCS_1_STREAM; i++) { + txpwr->mcs_20_stbc[i] = 0; + txpwr->mcs_40_stbc[i] = 0; + } + + /* Fill in the MCS 8-15 SDM rates */ + for (i = 0; i < BRCMS_NUM_RATES_MCS_2_STREAM; i++) { + txpwr->mcs_20_mimo[i] = (u8) maxpwr20; + txpwr->mcs_40_mimo[i] = (u8) maxpwr40; + } + + /* Fill in MCS32 */ + txpwr->mcs32 = (u8) maxpwr40; + + for (i = 0, j = 0; i < BRCMS_NUM_RATES_OFDM; i++, j++) { + if (txpwr->ofdm_40_cdd[i] == 0) + txpwr->ofdm_40_cdd[i] = txpwr->mcs_40_cdd[j]; + if (i == 0) { + i = i + 1; + if (txpwr->ofdm_40_cdd[i] == 0) + txpwr->ofdm_40_cdd[i] = txpwr->mcs_40_cdd[j]; + } + } + + /* + * Copy the 40 MHZ MCS 0-7 CDD value to the 40 MHZ MCS 0-7 SISO + * value if it wasn't provided explicitly. + */ + for (i = 0; i < BRCMS_NUM_RATES_MCS_1_STREAM; i++) { + if (txpwr->mcs_40_siso[i] == 0) + txpwr->mcs_40_siso[i] = txpwr->mcs_40_cdd[i]; + } + + for (i = 0, j = 0; i < BRCMS_NUM_RATES_OFDM; i++, j++) { + if (txpwr->ofdm_40_siso[i] == 0) + txpwr->ofdm_40_siso[i] = txpwr->mcs_40_siso[j]; + if (i == 0) { + i = i + 1; + if (txpwr->ofdm_40_siso[i] == 0) + txpwr->ofdm_40_siso[i] = txpwr->mcs_40_siso[j]; + } + } + + /* + * Copy the 20 and 40 MHz MCS0-7 CDD values to the corresponding + * STBC values if they weren't provided explicitly. + */ + for (i = 0; i < BRCMS_NUM_RATES_MCS_1_STREAM; i++) { + if (txpwr->mcs_20_stbc[i] == 0) + txpwr->mcs_20_stbc[i] = txpwr->mcs_20_cdd[i]; + + if (txpwr->mcs_40_stbc[i] == 0) + txpwr->mcs_40_stbc[i] = txpwr->mcs_40_cdd[i]; + } + + return; +} + +/* + * Verify the chanspec is using a legal set of parameters, i.e. that the + * chanspec specified a band, bw, ctl_sb and channel and that the + * combination could be legal given any set of circumstances. + * RETURNS: true is the chanspec is malformed, false if it looks good. + */ +static bool brcms_c_chspec_malformed(u16 chanspec) +{ + /* must be 2G or 5G band */ + if (!CHSPEC_IS5G(chanspec) && !CHSPEC_IS2G(chanspec)) + return true; + /* must be 20 or 40 bandwidth */ + if (!CHSPEC_IS40(chanspec) && !CHSPEC_IS20(chanspec)) + return true; + + /* 20MHZ b/w must have no ctl sb, 40 must have a ctl sb */ + if (CHSPEC_IS20(chanspec)) { + if (!CHSPEC_SB_NONE(chanspec)) + return true; + } else if (!CHSPEC_SB_UPPER(chanspec) && !CHSPEC_SB_LOWER(chanspec)) { + return true; + } + + return false; +} + +/* + * Validate the chanspec for this locale, for 40MHZ we need to also + * check that the sidebands are valid 20MZH channels in this locale + * and they are also a legal HT combination + */ +static bool +brcms_c_valid_chanspec_ext(struct brcms_cm_info *wlc_cm, u16 chspec) +{ + struct brcms_c_info *wlc = wlc_cm->wlc; + u8 channel = CHSPEC_CHANNEL(chspec); + + /* check the chanspec */ + if (brcms_c_chspec_malformed(chspec)) { + brcms_err(wlc->hw->d11core, "wl%d: malformed chanspec 0x%x\n", + wlc->pub->unit, chspec); + return false; + } + + if (CHANNEL_BANDUNIT(wlc_cm->wlc, channel) != + chspec_bandunit(chspec)) + return false; + + return true; +} + +bool brcms_c_valid_chanspec_db(struct brcms_cm_info *wlc_cm, u16 chspec) +{ + return brcms_c_valid_chanspec_ext(wlc_cm, chspec); +} + +static bool brcms_is_radar_freq(u16 center_freq) +{ + return center_freq >= 5260 && center_freq <= 5700; +} + +static void brcms_reg_apply_radar_flags(struct wiphy *wiphy) +{ + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + int i; + + sband = wiphy->bands[IEEE80211_BAND_5GHZ]; + if (!sband) + return; + + for (i = 0; i < sband->n_channels; i++) { + ch = &sband->channels[i]; + + if (!brcms_is_radar_freq(ch->center_freq)) + continue; + + /* + * All channels in this range should be passive and have + * DFS enabled. + */ + if (!(ch->flags & IEEE80211_CHAN_DISABLED)) + ch->flags |= IEEE80211_CHAN_RADAR | + IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN; + } +} + +static void +brcms_reg_apply_beaconing_flags(struct wiphy *wiphy, + enum nl80211_reg_initiator initiator) +{ + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + const struct ieee80211_reg_rule *rule; + int band, i; + + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { + sband = wiphy->bands[band]; + if (!sband) + continue; + + for (i = 0; i < sband->n_channels; i++) { + ch = &sband->channels[i]; + + if (ch->flags & + (IEEE80211_CHAN_DISABLED | IEEE80211_CHAN_RADAR)) + continue; + + if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) { + rule = freq_reg_info(wiphy, ch->center_freq); + if (IS_ERR(rule)) + continue; + + if (!(rule->flags & NL80211_RRF_NO_IBSS)) + ch->flags &= ~IEEE80211_CHAN_NO_IBSS; + if (!(rule->flags & NL80211_RRF_PASSIVE_SCAN)) + ch->flags &= + ~IEEE80211_CHAN_PASSIVE_SCAN; + } else if (ch->beacon_found) { + ch->flags &= ~(IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN); + } + } + } +} + +static void brcms_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct brcms_info *wl = hw->priv; + struct brcms_c_info *wlc = wl->wlc; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + int band, i; + bool ch_found = false; + + brcms_reg_apply_radar_flags(wiphy); + + if (request->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) + brcms_reg_apply_beaconing_flags(wiphy, request->initiator); + + /* Disable radio if all channels disallowed by regulatory */ + for (band = 0; !ch_found && band < IEEE80211_NUM_BANDS; band++) { + sband = wiphy->bands[band]; + if (!sband) + continue; + + for (i = 0; !ch_found && i < sband->n_channels; i++) { + ch = &sband->channels[i]; + + if (!(ch->flags & IEEE80211_CHAN_DISABLED)) + ch_found = true; + } + } + + if (ch_found) { + mboolclr(wlc->pub->radio_disabled, WL_RADIO_COUNTRY_DISABLE); + } else { + mboolset(wlc->pub->radio_disabled, WL_RADIO_COUNTRY_DISABLE); + brcms_err(wlc->hw->d11core, + "wl%d: %s: no valid channel for \"%s\"\n", + wlc->pub->unit, __func__, request->alpha2); + } + + if (wlc->pub->_nbands > 1 || wlc->band->bandtype == BRCM_BAND_2G) + wlc_phy_chanspec_ch14_widefilter_set(wlc->band->pi, + brcms_c_japan_ccode(request->alpha2)); +} + +void brcms_c_regd_init(struct brcms_c_info *wlc) +{ + struct wiphy *wiphy = wlc->wiphy; + const struct brcms_regd *regd = wlc->cmi->world_regd; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + struct brcms_chanvec sup_chan; + struct brcms_band *band; + int band_idx, i; + + /* Disable any channels not supported by the phy */ + for (band_idx = 0; band_idx < wlc->pub->_nbands; band_idx++) { + band = wlc->bandstate[band_idx]; + + wlc_phy_chanspec_band_validch(band->pi, band->bandtype, + &sup_chan); + + if (band_idx == BAND_2G_INDEX) + sband = wiphy->bands[IEEE80211_BAND_2GHZ]; + else + sband = wiphy->bands[IEEE80211_BAND_5GHZ]; + + for (i = 0; i < sband->n_channels; i++) { + ch = &sband->channels[i]; + if (!isset(sup_chan.vec, ch->hw_value)) + ch->flags |= IEEE80211_CHAN_DISABLED; + } + } + + wlc->wiphy->reg_notifier = brcms_reg_notifier; + wlc->wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY | + WIPHY_FLAG_STRICT_REGULATORY; + wiphy_apply_custom_regulatory(wlc->wiphy, regd->regdomain); + brcms_reg_apply_beaconing_flags(wiphy, NL80211_REGDOM_SET_BY_DRIVER); +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/channel.h b/drivers/net/wireless/brcm80211/brcmsmac/channel.h new file mode 100644 index 0000000..006483a --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/channel.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_CHANNEL_H_ +#define _BRCM_CHANNEL_H_ + +/* conversion for phy txpwr calculations that use .25 dB units */ +#define BRCMS_TXPWR_DB_FACTOR 4 + +/* bits for locale_info flags */ +#define BRCMS_PEAK_CONDUCTED 0x00 /* Peak for locals */ +#define BRCMS_EIRP 0x01 /* Flag for EIRP */ +#define BRCMS_DFS_TPC 0x02 /* Flag for DFS TPC */ +#define BRCMS_NO_OFDM 0x04 /* Flag for No OFDM */ +#define BRCMS_NO_40MHZ 0x08 /* Flag for No MIMO 40MHz */ +#define BRCMS_NO_MIMO 0x10 /* Flag for No MIMO, 20 or 40 MHz */ +#define BRCMS_RADAR_TYPE_EU 0x20 /* Flag for EU */ +#define BRCMS_DFS_FCC BRCMS_DFS_TPC /* Flag for DFS FCC */ + +#define BRCMS_DFS_EU (BRCMS_DFS_TPC | BRCMS_RADAR_TYPE_EU) /* Flag for DFS EU */ + +extern struct brcms_cm_info * +brcms_c_channel_mgr_attach(struct brcms_c_info *wlc); + +extern void brcms_c_channel_mgr_detach(struct brcms_cm_info *wlc_cm); + +extern bool brcms_c_valid_chanspec_db(struct brcms_cm_info *wlc_cm, + u16 chspec); + +extern void brcms_c_channel_reg_limits(struct brcms_cm_info *wlc_cm, + u16 chanspec, + struct txpwr_limits *txpwr); +extern void brcms_c_channel_set_chanspec(struct brcms_cm_info *wlc_cm, + u16 chanspec, + u8 local_constraint_qdbm); +extern void brcms_c_regd_init(struct brcms_c_info *wlc); + +#endif /* _WLC_CHANNEL_H */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/d11.h b/drivers/net/wireless/brcm80211/brcmsmac/d11.h new file mode 100644 index 0000000..9035cc4 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/d11.h @@ -0,0 +1,1902 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_D11_H_ +#define _BRCM_D11_H_ + +#include + +#include +#include "pub.h" +#include "dma.h" + +/* RX FIFO numbers */ +#define RX_FIFO 0 /* data and ctl frames */ +#define RX_TXSTATUS_FIFO 3 /* RX fifo for tx status packages */ + +/* TX FIFO numbers using WME Access Category */ +#define TX_AC_BK_FIFO 0 /* Background TX FIFO */ +#define TX_AC_BE_FIFO 1 /* Best-Effort TX FIFO */ +#define TX_AC_VI_FIFO 2 /* Video TX FIFO */ +#define TX_AC_VO_FIFO 3 /* Voice TX FIFO */ +#define TX_BCMC_FIFO 4 /* Broadcast/Multicast TX FIFO */ +#define TX_ATIM_FIFO 5 /* TX fifo for ATIM window info */ + +/* Addr is byte address used by SW; offset is word offset used by uCode */ + +/* Per AC TX limit settings */ +#define M_AC_TXLMT_BASE_ADDR (0x180 * 2) +#define M_AC_TXLMT_ADDR(_ac) (M_AC_TXLMT_BASE_ADDR + (2 * (_ac))) + +/* Legacy TX FIFO numbers */ +#define TX_DATA_FIFO TX_AC_BE_FIFO +#define TX_CTL_FIFO TX_AC_VO_FIFO + +#define WL_RSSI_ANT_MAX 4 /* max possible rx antennas */ + +struct intctrlregs { + u32 intstatus; + u32 intmask; +}; + +/* PIO structure, + * support two PIO format: 2 bytes access and 4 bytes access + * basic FIFO register set is per channel(transmit or receive) + * a pair of channels is defined for convenience + */ +/* 2byte-wide pio register set per channel(xmt or rcv) */ +struct pio2regs { + u16 fifocontrol; + u16 fifodata; + u16 fifofree; /* only valid in xmt channel, not in rcv channel */ + u16 PAD; +}; + +/* a pair of pio channels(tx and rx) */ +struct pio2regp { + struct pio2regs tx; + struct pio2regs rx; +}; + +/* 4byte-wide pio register set per channel(xmt or rcv) */ +struct pio4regs { + u32 fifocontrol; + u32 fifodata; +}; + +/* a pair of pio channels(tx and rx) */ +struct pio4regp { + struct pio4regs tx; + struct pio4regs rx; +}; + +/* read: 32-bit register that can be read as 32-bit or as 2 16-bit + * write: only low 16b-it half can be written + */ +union pmqreg { + u32 pmqhostdata; /* read only! */ + struct { + u16 pmqctrlstatus; /* read/write */ + u16 PAD; + } w; +}; + +struct fifo64 { + struct dma64regs dmaxmt; /* dma tx */ + struct pio4regs piotx; /* pio tx */ + struct dma64regs dmarcv; /* dma rx */ + struct pio4regs piorx; /* pio rx */ +}; + +/* + * Host Interface Registers + */ +struct d11regs { + /* Device Control ("semi-standard host registers") */ + u32 PAD[3]; /* 0x0 - 0x8 */ + u32 biststatus; /* 0xC */ + u32 biststatus2; /* 0x10 */ + u32 PAD; /* 0x14 */ + u32 gptimer; /* 0x18 */ + u32 usectimer; /* 0x1c *//* for corerev >= 26 */ + + /* Interrupt Control *//* 0x20 */ + struct intctrlregs intctrlregs[8]; + + u32 PAD[40]; /* 0x60 - 0xFC */ + + u32 intrcvlazy[4]; /* 0x100 - 0x10C */ + + u32 PAD[4]; /* 0x110 - 0x11c */ + + u32 maccontrol; /* 0x120 */ + u32 maccommand; /* 0x124 */ + u32 macintstatus; /* 0x128 */ + u32 macintmask; /* 0x12C */ + + /* Transmit Template Access */ + u32 tplatewrptr; /* 0x130 */ + u32 tplatewrdata; /* 0x134 */ + u32 PAD[2]; /* 0x138 - 0x13C */ + + /* PMQ registers */ + union pmqreg pmqreg; /* 0x140 */ + u32 pmqpatl; /* 0x144 */ + u32 pmqpath; /* 0x148 */ + u32 PAD; /* 0x14C */ + + u32 chnstatus; /* 0x150 */ + u32 psmdebug; /* 0x154 */ + u32 phydebug; /* 0x158 */ + u32 machwcap; /* 0x15C */ + + /* Extended Internal Objects */ + u32 objaddr; /* 0x160 */ + u32 objdata; /* 0x164 */ + u32 PAD[2]; /* 0x168 - 0x16c */ + + u32 frmtxstatus; /* 0x170 */ + u32 frmtxstatus2; /* 0x174 */ + u32 PAD[2]; /* 0x178 - 0x17c */ + + /* TSF host access */ + u32 tsf_timerlow; /* 0x180 */ + u32 tsf_timerhigh; /* 0x184 */ + u32 tsf_cfprep; /* 0x188 */ + u32 tsf_cfpstart; /* 0x18c */ + u32 tsf_cfpmaxdur32; /* 0x190 */ + u32 PAD[3]; /* 0x194 - 0x19c */ + + u32 maccontrol1; /* 0x1a0 */ + u32 machwcap1; /* 0x1a4 */ + u32 PAD[14]; /* 0x1a8 - 0x1dc */ + + /* Clock control and hardware workarounds*/ + u32 clk_ctl_st; /* 0x1e0 */ + u32 hw_war; + u32 d11_phypllctl; /* the phypll request/avail bits are + * moved to clk_ctl_st + */ + u32 PAD[5]; /* 0x1ec - 0x1fc */ + + /* 0x200-0x37F dma/pio registers */ + struct fifo64 fifo64regs[6]; + + /* FIFO diagnostic port access */ + struct dma32diag dmafifo; /* 0x380 - 0x38C */ + + u32 aggfifocnt; /* 0x390 */ + u32 aggfifodata; /* 0x394 */ + u32 PAD[16]; /* 0x398 - 0x3d4 */ + u16 radioregaddr; /* 0x3d8 */ + u16 radioregdata; /* 0x3da */ + + /* + * time delay between the change on rf disable input and + * radio shutdown + */ + u32 rfdisabledly; /* 0x3DC */ + + /* PHY register access */ + u16 phyversion; /* 0x3e0 - 0x0 */ + u16 phybbconfig; /* 0x3e2 - 0x1 */ + u16 phyadcbias; /* 0x3e4 - 0x2 Bphy only */ + u16 phyanacore; /* 0x3e6 - 0x3 pwwrdwn on aphy */ + u16 phyrxstatus0; /* 0x3e8 - 0x4 */ + u16 phyrxstatus1; /* 0x3ea - 0x5 */ + u16 phycrsth; /* 0x3ec - 0x6 */ + u16 phytxerror; /* 0x3ee - 0x7 */ + u16 phychannel; /* 0x3f0 - 0x8 */ + u16 PAD[1]; /* 0x3f2 - 0x9 */ + u16 phytest; /* 0x3f4 - 0xa */ + u16 phy4waddr; /* 0x3f6 - 0xb */ + u16 phy4wdatahi; /* 0x3f8 - 0xc */ + u16 phy4wdatalo; /* 0x3fa - 0xd */ + u16 phyregaddr; /* 0x3fc - 0xe */ + u16 phyregdata; /* 0x3fe - 0xf */ + + /* IHR *//* 0x400 - 0x7FE */ + + /* RXE Block */ + u16 PAD[3]; /* 0x400 - 0x406 */ + u16 rcv_fifo_ctl; /* 0x406 */ + u16 PAD; /* 0x408 - 0x40a */ + u16 rcv_frm_cnt; /* 0x40a */ + u16 PAD[4]; /* 0x40a - 0x414 */ + u16 rssi; /* 0x414 */ + u16 PAD[5]; /* 0x414 - 0x420 */ + u16 rcm_ctl; /* 0x420 */ + u16 rcm_mat_data; /* 0x422 */ + u16 rcm_mat_mask; /* 0x424 */ + u16 rcm_mat_dly; /* 0x426 */ + u16 rcm_cond_mask_l; /* 0x428 */ + u16 rcm_cond_mask_h; /* 0x42A */ + u16 rcm_cond_dly; /* 0x42C */ + u16 PAD[1]; /* 0x42E */ + u16 ext_ihr_addr; /* 0x430 */ + u16 ext_ihr_data; /* 0x432 */ + u16 rxe_phyrs_2; /* 0x434 */ + u16 rxe_phyrs_3; /* 0x436 */ + u16 phy_mode; /* 0x438 */ + u16 rcmta_ctl; /* 0x43a */ + u16 rcmta_size; /* 0x43c */ + u16 rcmta_addr0; /* 0x43e */ + u16 rcmta_addr1; /* 0x440 */ + u16 rcmta_addr2; /* 0x442 */ + u16 PAD[30]; /* 0x444 - 0x480 */ + + /* PSM Block *//* 0x480 - 0x500 */ + + u16 PAD; /* 0x480 */ + u16 psm_maccontrol_h; /* 0x482 */ + u16 psm_macintstatus_l; /* 0x484 */ + u16 psm_macintstatus_h; /* 0x486 */ + u16 psm_macintmask_l; /* 0x488 */ + u16 psm_macintmask_h; /* 0x48A */ + u16 PAD; /* 0x48C */ + u16 psm_maccommand; /* 0x48E */ + u16 psm_brc; /* 0x490 */ + u16 psm_phy_hdr_param; /* 0x492 */ + u16 psm_postcard; /* 0x494 */ + u16 psm_pcard_loc_l; /* 0x496 */ + u16 psm_pcard_loc_h; /* 0x498 */ + u16 psm_gpio_in; /* 0x49A */ + u16 psm_gpio_out; /* 0x49C */ + u16 psm_gpio_oe; /* 0x49E */ + + u16 psm_bred_0; /* 0x4A0 */ + u16 psm_bred_1; /* 0x4A2 */ + u16 psm_bred_2; /* 0x4A4 */ + u16 psm_bred_3; /* 0x4A6 */ + u16 psm_brcl_0; /* 0x4A8 */ + u16 psm_brcl_1; /* 0x4AA */ + u16 psm_brcl_2; /* 0x4AC */ + u16 psm_brcl_3; /* 0x4AE */ + u16 psm_brpo_0; /* 0x4B0 */ + u16 psm_brpo_1; /* 0x4B2 */ + u16 psm_brpo_2; /* 0x4B4 */ + u16 psm_brpo_3; /* 0x4B6 */ + u16 psm_brwk_0; /* 0x4B8 */ + u16 psm_brwk_1; /* 0x4BA */ + u16 psm_brwk_2; /* 0x4BC */ + u16 psm_brwk_3; /* 0x4BE */ + + u16 psm_base_0; /* 0x4C0 */ + u16 psm_base_1; /* 0x4C2 */ + u16 psm_base_2; /* 0x4C4 */ + u16 psm_base_3; /* 0x4C6 */ + u16 psm_base_4; /* 0x4C8 */ + u16 psm_base_5; /* 0x4CA */ + u16 psm_base_6; /* 0x4CC */ + u16 psm_pc_reg_0; /* 0x4CE */ + u16 psm_pc_reg_1; /* 0x4D0 */ + u16 psm_pc_reg_2; /* 0x4D2 */ + u16 psm_pc_reg_3; /* 0x4D4 */ + u16 PAD[0xD]; /* 0x4D6 - 0x4DE */ + u16 psm_corectlsts; /* 0x4f0 *//* Corerev >= 13 */ + u16 PAD[0x7]; /* 0x4f2 - 0x4fE */ + + /* TXE0 Block *//* 0x500 - 0x580 */ + u16 txe_ctl; /* 0x500 */ + u16 txe_aux; /* 0x502 */ + u16 txe_ts_loc; /* 0x504 */ + u16 txe_time_out; /* 0x506 */ + u16 txe_wm_0; /* 0x508 */ + u16 txe_wm_1; /* 0x50A */ + u16 txe_phyctl; /* 0x50C */ + u16 txe_status; /* 0x50E */ + u16 txe_mmplcp0; /* 0x510 */ + u16 txe_mmplcp1; /* 0x512 */ + u16 txe_phyctl1; /* 0x514 */ + + u16 PAD[0x05]; /* 0x510 - 0x51E */ + + /* Transmit control */ + u16 xmtfifodef; /* 0x520 */ + u16 xmtfifo_frame_cnt; /* 0x522 *//* Corerev >= 16 */ + u16 xmtfifo_byte_cnt; /* 0x524 *//* Corerev >= 16 */ + u16 xmtfifo_head; /* 0x526 *//* Corerev >= 16 */ + u16 xmtfifo_rd_ptr; /* 0x528 *//* Corerev >= 16 */ + u16 xmtfifo_wr_ptr; /* 0x52A *//* Corerev >= 16 */ + u16 xmtfifodef1; /* 0x52C *//* Corerev >= 16 */ + + u16 PAD[0x09]; /* 0x52E - 0x53E */ + + u16 xmtfifocmd; /* 0x540 */ + u16 xmtfifoflush; /* 0x542 */ + u16 xmtfifothresh; /* 0x544 */ + u16 xmtfifordy; /* 0x546 */ + u16 xmtfifoprirdy; /* 0x548 */ + u16 xmtfiforqpri; /* 0x54A */ + u16 xmttplatetxptr; /* 0x54C */ + u16 PAD; /* 0x54E */ + u16 xmttplateptr; /* 0x550 */ + u16 smpl_clct_strptr; /* 0x552 *//* Corerev >= 22 */ + u16 smpl_clct_stpptr; /* 0x554 *//* Corerev >= 22 */ + u16 smpl_clct_curptr; /* 0x556 *//* Corerev >= 22 */ + u16 PAD[0x04]; /* 0x558 - 0x55E */ + u16 xmttplatedatalo; /* 0x560 */ + u16 xmttplatedatahi; /* 0x562 */ + + u16 PAD[2]; /* 0x564 - 0x566 */ + + u16 xmtsel; /* 0x568 */ + u16 xmttxcnt; /* 0x56A */ + u16 xmttxshmaddr; /* 0x56C */ + + u16 PAD[0x09]; /* 0x56E - 0x57E */ + + /* TXE1 Block */ + u16 PAD[0x40]; /* 0x580 - 0x5FE */ + + /* TSF Block */ + u16 PAD[0X02]; /* 0x600 - 0x602 */ + u16 tsf_cfpstrt_l; /* 0x604 */ + u16 tsf_cfpstrt_h; /* 0x606 */ + u16 PAD[0X05]; /* 0x608 - 0x610 */ + u16 tsf_cfppretbtt; /* 0x612 */ + u16 PAD[0XD]; /* 0x614 - 0x62C */ + u16 tsf_clk_frac_l; /* 0x62E */ + u16 tsf_clk_frac_h; /* 0x630 */ + u16 PAD[0X14]; /* 0x632 - 0x658 */ + u16 tsf_random; /* 0x65A */ + u16 PAD[0x05]; /* 0x65C - 0x664 */ + /* GPTimer 2 registers */ + u16 tsf_gpt2_stat; /* 0x666 */ + u16 tsf_gpt2_ctr_l; /* 0x668 */ + u16 tsf_gpt2_ctr_h; /* 0x66A */ + u16 tsf_gpt2_val_l; /* 0x66C */ + u16 tsf_gpt2_val_h; /* 0x66E */ + u16 tsf_gptall_stat; /* 0x670 */ + u16 PAD[0x07]; /* 0x672 - 0x67E */ + + /* IFS Block */ + u16 ifs_sifs_rx_tx_tx; /* 0x680 */ + u16 ifs_sifs_nav_tx; /* 0x682 */ + u16 ifs_slot; /* 0x684 */ + u16 PAD; /* 0x686 */ + u16 ifs_ctl; /* 0x688 */ + u16 PAD[0x3]; /* 0x68a - 0x68F */ + u16 ifsstat; /* 0x690 */ + u16 ifsmedbusyctl; /* 0x692 */ + u16 iftxdur; /* 0x694 */ + u16 PAD[0x3]; /* 0x696 - 0x69b */ + /* EDCF support in dot11macs */ + u16 ifs_aifsn; /* 0x69c */ + u16 ifs_ctl1; /* 0x69e */ + + /* slow clock registers */ + u16 scc_ctl; /* 0x6a0 */ + u16 scc_timer_l; /* 0x6a2 */ + u16 scc_timer_h; /* 0x6a4 */ + u16 scc_frac; /* 0x6a6 */ + u16 scc_fastpwrup_dly; /* 0x6a8 */ + u16 scc_per; /* 0x6aa */ + u16 scc_per_frac; /* 0x6ac */ + u16 scc_cal_timer_l; /* 0x6ae */ + u16 scc_cal_timer_h; /* 0x6b0 */ + u16 PAD; /* 0x6b2 */ + + u16 PAD[0x26]; + + /* NAV Block */ + u16 nav_ctl; /* 0x700 */ + u16 navstat; /* 0x702 */ + u16 PAD[0x3e]; /* 0x702 - 0x77E */ + + /* WEP/PMQ Block *//* 0x780 - 0x7FE */ + u16 PAD[0x20]; /* 0x780 - 0x7BE */ + + u16 wepctl; /* 0x7C0 */ + u16 wepivloc; /* 0x7C2 */ + u16 wepivkey; /* 0x7C4 */ + u16 wepwkey; /* 0x7C6 */ + + u16 PAD[4]; /* 0x7C8 - 0x7CE */ + u16 pcmctl; /* 0X7D0 */ + u16 pcmstat; /* 0X7D2 */ + u16 PAD[6]; /* 0x7D4 - 0x7DE */ + + u16 pmqctl; /* 0x7E0 */ + u16 pmqstatus; /* 0x7E2 */ + u16 pmqpat0; /* 0x7E4 */ + u16 pmqpat1; /* 0x7E6 */ + u16 pmqpat2; /* 0x7E8 */ + + u16 pmqdat; /* 0x7EA */ + u16 pmqdator; /* 0x7EC */ + u16 pmqhst; /* 0x7EE */ + u16 pmqpath0; /* 0x7F0 */ + u16 pmqpath1; /* 0x7F2 */ + u16 pmqpath2; /* 0x7F4 */ + u16 pmqdath; /* 0x7F6 */ + + u16 PAD[0x04]; /* 0x7F8 - 0x7FE */ + + /* SHM *//* 0x800 - 0xEFE */ + u16 PAD[0x380]; /* 0x800 - 0xEFE */ +}; + +/* d11 register field offset */ +#define D11REGOFFS(field) offsetof(struct d11regs, field) + +#define PIHR_BASE 0x0400 /* byte address of packed IHR region */ + +/* biststatus */ +#define BT_DONE (1U << 31) /* bist done */ +#define BT_B2S (1 << 30) /* bist2 ram summary bit */ + +/* intstatus and intmask */ +#define I_PC (1 << 10) /* pci descriptor error */ +#define I_PD (1 << 11) /* pci data error */ +#define I_DE (1 << 12) /* descriptor protocol error */ +#define I_RU (1 << 13) /* receive descriptor underflow */ +#define I_RO (1 << 14) /* receive fifo overflow */ +#define I_XU (1 << 15) /* transmit fifo underflow */ +#define I_RI (1 << 16) /* receive interrupt */ +#define I_XI (1 << 24) /* transmit interrupt */ + +/* interrupt receive lazy */ +#define IRL_TO_MASK 0x00ffffff /* timeout */ +#define IRL_FC_MASK 0xff000000 /* frame count */ +#define IRL_FC_SHIFT 24 /* frame count */ + +/*== maccontrol register ==*/ +#define MCTL_GMODE (1U << 31) +#define MCTL_DISCARD_PMQ (1 << 30) +#define MCTL_TBTTHOLD (1 << 28) +#define MCTL_WAKE (1 << 26) +#define MCTL_HPS (1 << 25) +#define MCTL_PROMISC (1 << 24) +#define MCTL_KEEPBADFCS (1 << 23) +#define MCTL_KEEPCONTROL (1 << 22) +#define MCTL_PHYLOCK (1 << 21) +#define MCTL_BCNS_PROMISC (1 << 20) +#define MCTL_LOCK_RADIO (1 << 19) +#define MCTL_AP (1 << 18) +#define MCTL_INFRA (1 << 17) +#define MCTL_BIGEND (1 << 16) +#define MCTL_GPOUT_SEL_MASK (3 << 14) +#define MCTL_GPOUT_SEL_SHIFT 14 +#define MCTL_EN_PSMDBG (1 << 13) +#define MCTL_IHR_EN (1 << 10) +#define MCTL_SHM_UPPER (1 << 9) +#define MCTL_SHM_EN (1 << 8) +#define MCTL_PSM_JMP_0 (1 << 2) +#define MCTL_PSM_RUN (1 << 1) +#define MCTL_EN_MAC (1 << 0) + +/*== maccommand register ==*/ +#define MCMD_BCN0VLD (1 << 0) +#define MCMD_BCN1VLD (1 << 1) +#define MCMD_DIRFRMQVAL (1 << 2) +#define MCMD_CCA (1 << 3) +#define MCMD_BG_NOISE (1 << 4) +#define MCMD_SKIP_SHMINIT (1 << 5) /* only used for simulation */ +#define MCMD_SAMPLECOLL MCMD_SKIP_SHMINIT /* reuse for sample collect */ + +/*== macintstatus/macintmask ==*/ +/* gracefully suspended */ +#define MI_MACSSPNDD (1 << 0) +/* beacon template available */ +#define MI_BCNTPL (1 << 1) +/* TBTT indication */ +#define MI_TBTT (1 << 2) +/* beacon successfully tx'd */ +#define MI_BCNSUCCESS (1 << 3) +/* beacon canceled (IBSS) */ +#define MI_BCNCANCLD (1 << 4) +/* end of ATIM-window (IBSS) */ +#define MI_ATIMWINEND (1 << 5) +/* PMQ entries available */ +#define MI_PMQ (1 << 6) +/* non-specific gen-stat bits that are set by PSM */ +#define MI_NSPECGEN_0 (1 << 7) +/* non-specific gen-stat bits that are set by PSM */ +#define MI_NSPECGEN_1 (1 << 8) +/* MAC level Tx error */ +#define MI_MACTXERR (1 << 9) +/* non-specific gen-stat bits that are set by PSM */ +#define MI_NSPECGEN_3 (1 << 10) +/* PHY Tx error */ +#define MI_PHYTXERR (1 << 11) +/* Power Management Event */ +#define MI_PME (1 << 12) +/* General-purpose timer0 */ +#define MI_GP0 (1 << 13) +/* General-purpose timer1 */ +#define MI_GP1 (1 << 14) +/* (ORed) DMA-interrupts */ +#define MI_DMAINT (1 << 15) +/* MAC has completed a TX FIFO Suspend/Flush */ +#define MI_TXSTOP (1 << 16) +/* MAC has completed a CCA measurement */ +#define MI_CCA (1 << 17) +/* MAC has collected background noise samples */ +#define MI_BG_NOISE (1 << 18) +/* MBSS DTIM TBTT indication */ +#define MI_DTIM_TBTT (1 << 19) +/* Probe response queue needs attention */ +#define MI_PRQ (1 << 20) +/* Radio/PHY has been powered back up. */ +#define MI_PWRUP (1 << 21) +#define MI_RESERVED3 (1 << 22) +#define MI_RESERVED2 (1 << 23) +#define MI_RESERVED1 (1 << 25) +/* MAC detected change on RF Disable input*/ +#define MI_RFDISABLE (1 << 28) +/* MAC has completed a TX */ +#define MI_TFS (1 << 29) +/* A phy status change wrt G mode */ +#define MI_PHYCHANGED (1 << 30) +/* general purpose timeout */ +#define MI_TO (1U << 31) + +/* Mac capabilities registers */ +/*== machwcap ==*/ +#define MCAP_TKIPMIC 0x80000000 /* TKIP MIC hardware present */ + +/*== pmqhost data ==*/ +/* data entry of head pmq entry */ +#define PMQH_DATA_MASK 0xffff0000 +/* PM entry for BSS config */ +#define PMQH_BSSCFG 0x00100000 +/* PM Mode OFF: power save off */ +#define PMQH_PMOFF 0x00010000 +/* PM Mode ON: power save on */ +#define PMQH_PMON 0x00020000 +/* Dis-associated or De-authenticated */ +#define PMQH_DASAT 0x00040000 +/* ATIM not acknowledged */ +#define PMQH_ATIMFAIL 0x00080000 +/* delete head entry */ +#define PMQH_DEL_ENTRY 0x00000001 +/* delete head entry to cur read pointer -1 */ +#define PMQH_DEL_MULT 0x00000002 +/* pmq overflow indication */ +#define PMQH_OFLO 0x00000004 +/* entries are present in pmq */ +#define PMQH_NOT_EMPTY 0x00000008 + +/*== phydebug ==*/ +/* phy is asserting carrier sense */ +#define PDBG_CRS (1 << 0) +/* phy is taking xmit byte from mac this cycle */ +#define PDBG_TXA (1 << 1) +/* mac is instructing the phy to transmit a frame */ +#define PDBG_TXF (1 << 2) +/* phy is signalling a transmit Error to the mac */ +#define PDBG_TXE (1 << 3) +/* phy detected the end of a valid frame preamble */ +#define PDBG_RXF (1 << 4) +/* phy detected the end of a valid PLCP header */ +#define PDBG_RXS (1 << 5) +/* rx start not asserted */ +#define PDBG_RXFRG (1 << 6) +/* mac is taking receive byte from phy this cycle */ +#define PDBG_RXV (1 << 7) +/* RF portion of the radio is disabled */ +#define PDBG_RFD (1 << 16) + +/*== objaddr register ==*/ +#define OBJADDR_SEL_MASK 0x000F0000 +#define OBJADDR_UCM_SEL 0x00000000 +#define OBJADDR_SHM_SEL 0x00010000 +#define OBJADDR_SCR_SEL 0x00020000 +#define OBJADDR_IHR_SEL 0x00030000 +#define OBJADDR_RCMTA_SEL 0x00040000 +#define OBJADDR_SRCHM_SEL 0x00060000 +#define OBJADDR_WINC 0x01000000 +#define OBJADDR_RINC 0x02000000 +#define OBJADDR_AUTO_INC 0x03000000 + +#define WEP_PCMADDR 0x07d4 +#define WEP_PCMDATA 0x07d6 + +/*== frmtxstatus ==*/ +#define TXS_V (1 << 0) /* valid bit */ +#define TXS_STATUS_MASK 0xffff +#define TXS_FID_MASK 0xffff0000 +#define TXS_FID_SHIFT 16 + +/*== frmtxstatus2 ==*/ +#define TXS_SEQ_MASK 0xffff +#define TXS_PTX_MASK 0xff0000 +#define TXS_PTX_SHIFT 16 +#define TXS_MU_MASK 0x01000000 +#define TXS_MU_SHIFT 24 + +/*== clk_ctl_st ==*/ +#define CCS_ERSRC_REQ_D11PLL 0x00000100 /* d11 core pll request */ +#define CCS_ERSRC_REQ_PHYPLL 0x00000200 /* PHY pll request */ +#define CCS_ERSRC_AVAIL_D11PLL 0x01000000 /* d11 core pll available */ +#define CCS_ERSRC_AVAIL_PHYPLL 0x02000000 /* PHY pll available */ + +/* HT Cloclk Ctrl and Clock Avail for 4313 */ +#define CCS_ERSRC_REQ_HT 0x00000010 /* HT avail request */ +#define CCS_ERSRC_AVAIL_HT 0x00020000 /* HT clock available */ + +/* tsf_cfprep register */ +#define CFPREP_CBI_MASK 0xffffffc0 +#define CFPREP_CBI_SHIFT 6 +#define CFPREP_CFPP 0x00000001 + +/* tx fifo sizes values are in terms of 256 byte blocks */ +#define TXFIFOCMD_RESET_MASK (1 << 15) /* reset */ +#define TXFIFOCMD_FIFOSEL_SHIFT 8 /* fifo */ +#define TXFIFO_FIFOTOP_SHIFT 8 /* fifo start */ + +#define TXFIFO_START_BLK16 65 /* Base address + 32 * 512 B/P */ +#define TXFIFO_START_BLK 6 /* Base address + 6 * 256 B */ +#define TXFIFO_SIZE_UNIT 256 /* one unit corresponds to 256 bytes */ +#define MBSS16_TEMPLMEM_MINBLKS 65 /* one unit corresponds to 256 bytes */ + +/*== phy versions (PhyVersion:Revision field) ==*/ +/* analog block version */ +#define PV_AV_MASK 0xf000 +/* analog block version bitfield offset */ +#define PV_AV_SHIFT 12 +/* phy type */ +#define PV_PT_MASK 0x0f00 +/* phy type bitfield offset */ +#define PV_PT_SHIFT 8 +/* phy version */ +#define PV_PV_MASK 0x000f +#define PHY_TYPE(v) ((v & PV_PT_MASK) >> PV_PT_SHIFT) + +/*== phy types (PhyVersion:PhyType field) ==*/ +#define PHY_TYPE_N 4 /* N-Phy value */ +#define PHY_TYPE_SSN 6 /* SSLPN-Phy value */ +#define PHY_TYPE_LCN 8 /* LCN-Phy value */ +#define PHY_TYPE_LCNXN 9 /* LCNXN-Phy value */ +#define PHY_TYPE_NULL 0xf /* Invalid Phy value */ + +/*== analog types (PhyVersion:AnalogType field) ==*/ +#define ANA_11N_013 5 + +/* 802.11a PLCP header def */ +struct ofdm_phy_hdr { + u8 rlpt[3]; /* rate, length, parity, tail */ + u16 service; + u8 pad; +} __packed; + +#define D11A_PHY_HDR_GRATE(phdr) ((phdr)->rlpt[0] & 0x0f) +#define D11A_PHY_HDR_GRES(phdr) (((phdr)->rlpt[0] >> 4) & 0x01) +#define D11A_PHY_HDR_GLENGTH(phdr) (((u32 *)((phdr)->rlpt) >> 5) & 0x0fff) +#define D11A_PHY_HDR_GPARITY(phdr) (((phdr)->rlpt[3] >> 1) & 0x01) +#define D11A_PHY_HDR_GTAIL(phdr) (((phdr)->rlpt[3] >> 2) & 0x3f) + +/* rate encoded per 802.11a-1999 sec 17.3.4.1 */ +#define D11A_PHY_HDR_SRATE(phdr, rate) \ + ((phdr)->rlpt[0] = ((phdr)->rlpt[0] & 0xf0) | ((rate) & 0xf)) +/* set reserved field to zero */ +#define D11A_PHY_HDR_SRES(phdr) ((phdr)->rlpt[0] &= 0xef) +/* length is number of octets in PSDU */ +#define D11A_PHY_HDR_SLENGTH(phdr, length) \ + (*(u32 *)((phdr)->rlpt) = *(u32 *)((phdr)->rlpt) | \ + (((length) & 0x0fff) << 5)) +/* set the tail to all zeros */ +#define D11A_PHY_HDR_STAIL(phdr) ((phdr)->rlpt[3] &= 0x03) + +#define D11A_PHY_HDR_LEN_L 3 /* low-rate part of PLCP header */ +#define D11A_PHY_HDR_LEN_R 2 /* high-rate part of PLCP header */ + +#define D11A_PHY_TX_DELAY (2) /* 2.1 usec */ + +#define D11A_PHY_HDR_TIME (4) /* low-rate part of PLCP header */ +#define D11A_PHY_PRE_TIME (16) +#define D11A_PHY_PREHDR_TIME (D11A_PHY_PRE_TIME + D11A_PHY_HDR_TIME) + +/* 802.11b PLCP header def */ +struct cck_phy_hdr { + u8 signal; + u8 service; + u16 length; + u16 crc; +} __packed; + +#define D11B_PHY_HDR_LEN 6 + +#define D11B_PHY_TX_DELAY (3) /* 3.4 usec */ + +#define D11B_PHY_LHDR_TIME (D11B_PHY_HDR_LEN << 3) +#define D11B_PHY_LPRE_TIME (144) +#define D11B_PHY_LPREHDR_TIME (D11B_PHY_LPRE_TIME + D11B_PHY_LHDR_TIME) + +#define D11B_PHY_SHDR_TIME (D11B_PHY_LHDR_TIME >> 1) +#define D11B_PHY_SPRE_TIME (D11B_PHY_LPRE_TIME >> 1) +#define D11B_PHY_SPREHDR_TIME (D11B_PHY_SPRE_TIME + D11B_PHY_SHDR_TIME) + +#define D11B_PLCP_SIGNAL_LOCKED (1 << 2) +#define D11B_PLCP_SIGNAL_LE (1 << 7) + +#define MIMO_PLCP_MCS_MASK 0x7f /* mcs index */ +#define MIMO_PLCP_40MHZ 0x80 /* 40 Hz frame */ +#define MIMO_PLCP_AMPDU 0x08 /* ampdu */ + +#define BRCMS_GET_CCK_PLCP_LEN(plcp) (plcp[4] + (plcp[5] << 8)) +#define BRCMS_GET_MIMO_PLCP_LEN(plcp) (plcp[1] + (plcp[2] << 8)) +#define BRCMS_SET_MIMO_PLCP_LEN(plcp, len) \ + do { \ + plcp[1] = len & 0xff; \ + plcp[2] = ((len >> 8) & 0xff); \ + } while (0) + +#define BRCMS_SET_MIMO_PLCP_AMPDU(plcp) (plcp[3] |= MIMO_PLCP_AMPDU) +#define BRCMS_CLR_MIMO_PLCP_AMPDU(plcp) (plcp[3] &= ~MIMO_PLCP_AMPDU) +#define BRCMS_IS_MIMO_PLCP_AMPDU(plcp) (plcp[3] & MIMO_PLCP_AMPDU) + +/* + * The dot11a PLCP header is 5 bytes. To simplify the software (so that we + * don't need e.g. different tx DMA headers for 11a and 11b), the PLCP header + * has padding added in the ucode. + */ +#define D11_PHY_HDR_LEN 6 + +/* TX DMA buffer header */ +struct d11txh { + __le16 MacTxControlLow; /* 0x0 */ + __le16 MacTxControlHigh; /* 0x1 */ + __le16 MacFrameControl; /* 0x2 */ + __le16 TxFesTimeNormal; /* 0x3 */ + __le16 PhyTxControlWord; /* 0x4 */ + __le16 PhyTxControlWord_1; /* 0x5 */ + __le16 PhyTxControlWord_1_Fbr; /* 0x6 */ + __le16 PhyTxControlWord_1_Rts; /* 0x7 */ + __le16 PhyTxControlWord_1_FbrRts; /* 0x8 */ + __le16 MainRates; /* 0x9 */ + __le16 XtraFrameTypes; /* 0xa */ + u8 IV[16]; /* 0x0b - 0x12 */ + u8 TxFrameRA[6]; /* 0x13 - 0x15 */ + __le16 TxFesTimeFallback; /* 0x16 */ + u8 RTSPLCPFallback[6]; /* 0x17 - 0x19 */ + __le16 RTSDurFallback; /* 0x1a */ + u8 FragPLCPFallback[6]; /* 0x1b - 1d */ + __le16 FragDurFallback; /* 0x1e */ + __le16 MModeLen; /* 0x1f */ + __le16 MModeFbrLen; /* 0x20 */ + __le16 TstampLow; /* 0x21 */ + __le16 TstampHigh; /* 0x22 */ + __le16 ABI_MimoAntSel; /* 0x23 */ + __le16 PreloadSize; /* 0x24 */ + __le16 AmpduSeqCtl; /* 0x25 */ + __le16 TxFrameID; /* 0x26 */ + __le16 TxStatus; /* 0x27 */ + __le16 MaxNMpdus; /* 0x28 */ + __le16 MaxABytes_MRT; /* 0x29 */ + __le16 MaxABytes_FBR; /* 0x2a */ + __le16 MinMBytes; /* 0x2b */ + u8 RTSPhyHeader[D11_PHY_HDR_LEN]; /* 0x2c - 0x2e */ + struct ieee80211_rts rts_frame; /* 0x2f - 0x36 */ + u16 PAD; /* 0x37 */ +} __packed; + +#define D11_TXH_LEN 112 /* bytes */ + +/* Frame Types */ +#define FT_CCK 0 +#define FT_OFDM 1 +#define FT_HT 2 +#define FT_N 3 + +/* + * Position of MPDU inside A-MPDU; indicated with bits 10:9 + * of MacTxControlLow + */ +#define TXC_AMPDU_SHIFT 9 /* shift for ampdu settings */ +#define TXC_AMPDU_NONE 0 /* Regular MPDU, not an A-MPDU */ +#define TXC_AMPDU_FIRST 1 /* first MPDU of an A-MPDU */ +#define TXC_AMPDU_MIDDLE 2 /* intermediate MPDU of an A-MPDU */ +#define TXC_AMPDU_LAST 3 /* last (or single) MPDU of an A-MPDU */ + +/*== MacTxControlLow ==*/ +#define TXC_AMIC 0x8000 +#define TXC_SENDCTS 0x0800 +#define TXC_AMPDU_MASK 0x0600 +#define TXC_BW_40 0x0100 +#define TXC_FREQBAND_5G 0x0080 +#define TXC_DFCS 0x0040 +#define TXC_IGNOREPMQ 0x0020 +#define TXC_HWSEQ 0x0010 +#define TXC_STARTMSDU 0x0008 +#define TXC_SENDRTS 0x0004 +#define TXC_LONGFRAME 0x0002 +#define TXC_IMMEDACK 0x0001 + +/*== MacTxControlHigh ==*/ +/* RTS fallback preamble type 1 = SHORT 0 = LONG */ +#define TXC_PREAMBLE_RTS_FB_SHORT 0x8000 +/* RTS main rate preamble type 1 = SHORT 0 = LONG */ +#define TXC_PREAMBLE_RTS_MAIN_SHORT 0x4000 +/* + * Main fallback rate preamble type + * 1 = SHORT for OFDM/GF for MIMO + * 0 = LONG for CCK/MM for MIMO + */ +#define TXC_PREAMBLE_DATA_FB_SHORT 0x2000 + +/* TXC_PREAMBLE_DATA_MAIN is in PhyTxControl bit 5 */ +/* use fallback rate for this AMPDU */ +#define TXC_AMPDU_FBR 0x1000 +#define TXC_SECKEY_MASK 0x0FF0 +#define TXC_SECKEY_SHIFT 4 +/* Use alternate txpwr defined at loc. M_ALT_TXPWR_IDX */ +#define TXC_ALT_TXPWR 0x0008 +#define TXC_SECTYPE_MASK 0x0007 +#define TXC_SECTYPE_SHIFT 0 + +/* Null delimiter for Fallback rate */ +#define AMPDU_FBR_NULL_DELIM 5 /* Location of Null delimiter count for AMPDU */ + +/* PhyTxControl for Mimophy */ +#define PHY_TXC_PWR_MASK 0xFC00 +#define PHY_TXC_PWR_SHIFT 10 +#define PHY_TXC_ANT_MASK 0x03C0 /* bit 6, 7, 8, 9 */ +#define PHY_TXC_ANT_SHIFT 6 +#define PHY_TXC_ANT_0_1 0x00C0 /* auto, last rx */ +#define PHY_TXC_LCNPHY_ANT_LAST 0x0000 +#define PHY_TXC_ANT_3 0x0200 /* virtual antenna 3 */ +#define PHY_TXC_ANT_2 0x0100 /* virtual antenna 2 */ +#define PHY_TXC_ANT_1 0x0080 /* virtual antenna 1 */ +#define PHY_TXC_ANT_0 0x0040 /* virtual antenna 0 */ +#define PHY_TXC_SHORT_HDR 0x0010 + +#define PHY_TXC_OLD_ANT_0 0x0000 +#define PHY_TXC_OLD_ANT_1 0x0100 +#define PHY_TXC_OLD_ANT_LAST 0x0300 + +/* PhyTxControl_1 for Mimophy */ +#define PHY_TXC1_BW_MASK 0x0007 +#define PHY_TXC1_BW_10MHZ 0 +#define PHY_TXC1_BW_10MHZ_UP 1 +#define PHY_TXC1_BW_20MHZ 2 +#define PHY_TXC1_BW_20MHZ_UP 3 +#define PHY_TXC1_BW_40MHZ 4 +#define PHY_TXC1_BW_40MHZ_DUP 5 +#define PHY_TXC1_MODE_SHIFT 3 +#define PHY_TXC1_MODE_MASK 0x0038 +#define PHY_TXC1_MODE_SISO 0 +#define PHY_TXC1_MODE_CDD 1 +#define PHY_TXC1_MODE_STBC 2 +#define PHY_TXC1_MODE_SDM 3 + +/* PhyTxControl for HTphy that are different from Mimophy */ +#define PHY_TXC_HTANT_MASK 0x3fC0 /* bits 6-13 */ + +/* XtraFrameTypes */ +#define XFTS_RTS_FT_SHIFT 2 +#define XFTS_FBRRTS_FT_SHIFT 4 +#define XFTS_CHANNEL_SHIFT 8 + +/* Antenna diversity bit in ant_wr_settle */ +#define PHY_AWS_ANTDIV 0x2000 + +/* IFS ctl */ +#define IFS_USEEDCF (1 << 2) + +/* IFS ctl1 */ +#define IFS_CTL1_EDCRS (1 << 3) +#define IFS_CTL1_EDCRS_20L (1 << 4) +#define IFS_CTL1_EDCRS_40 (1 << 5) + +/* ABI_MimoAntSel */ +#define ABI_MAS_ADDR_BMP_IDX_MASK 0x0f00 +#define ABI_MAS_ADDR_BMP_IDX_SHIFT 8 +#define ABI_MAS_FBR_ANT_PTN_MASK 0x00f0 +#define ABI_MAS_FBR_ANT_PTN_SHIFT 4 +#define ABI_MAS_MRT_ANT_PTN_MASK 0x000f + +/* tx status packet */ +struct tx_status { + u16 framelen; + u16 PAD; + u16 frameid; + u16 status; + u16 lasttxtime; + u16 sequence; + u16 phyerr; + u16 ackphyrxsh; +} __packed; + +#define TXSTATUS_LEN 16 + +/* status field bit definitions */ +#define TX_STATUS_FRM_RTX_MASK 0xF000 +#define TX_STATUS_FRM_RTX_SHIFT 12 +#define TX_STATUS_RTS_RTX_MASK 0x0F00 +#define TX_STATUS_RTS_RTX_SHIFT 8 +#define TX_STATUS_MASK 0x00FE +#define TX_STATUS_PMINDCTD (1 << 7) /* PM mode indicated to AP */ +#define TX_STATUS_INTERMEDIATE (1 << 6) /* intermediate or 1st ampdu pkg */ +#define TX_STATUS_AMPDU (1 << 5) /* AMPDU status */ +#define TX_STATUS_SUPR_MASK 0x1C /* suppress status bits (4:2) */ +#define TX_STATUS_SUPR_SHIFT 2 +#define TX_STATUS_ACK_RCV (1 << 1) /* ACK received */ +#define TX_STATUS_VALID (1 << 0) /* Tx status valid */ +#define TX_STATUS_NO_ACK 0 + +/* suppress status reason codes */ +#define TX_STATUS_SUPR_PMQ (1 << 2) /* PMQ entry */ +#define TX_STATUS_SUPR_FLUSH (2 << 2) /* flush request */ +#define TX_STATUS_SUPR_FRAG (3 << 2) /* previous frag failure */ +#define TX_STATUS_SUPR_TBTT (3 << 2) /* SHARED: Probe resp supr for TBTT */ +#define TX_STATUS_SUPR_BADCH (4 << 2) /* channel mismatch */ +#define TX_STATUS_SUPR_EXPTIME (5 << 2) /* lifetime expiry */ +#define TX_STATUS_SUPR_UF (6 << 2) /* underflow */ + +/* Unexpected tx status for rate update */ +#define TX_STATUS_UNEXP(status) \ + ((((status) & TX_STATUS_INTERMEDIATE) != 0) && \ + TX_STATUS_UNEXP_AMPDU(status)) + +/* Unexpected tx status for A-MPDU rate update */ +#define TX_STATUS_UNEXP_AMPDU(status) \ + ((((status) & TX_STATUS_SUPR_MASK) != 0) && \ + (((status) & TX_STATUS_SUPR_MASK) != TX_STATUS_SUPR_EXPTIME)) + +#define TX_STATUS_BA_BMAP03_MASK 0xF000 /* ba bitmap 0:3 in 1st pkg */ +#define TX_STATUS_BA_BMAP03_SHIFT 12 /* ba bitmap 0:3 in 1st pkg */ +#define TX_STATUS_BA_BMAP47_MASK 0x001E /* ba bitmap 4:7 in 2nd pkg */ +#define TX_STATUS_BA_BMAP47_SHIFT 3 /* ba bitmap 4:7 in 2nd pkg */ + +/* RXE (Receive Engine) */ + +/* RCM_CTL */ +#define RCM_INC_MASK_H 0x0080 +#define RCM_INC_MASK_L 0x0040 +#define RCM_INC_DATA 0x0020 +#define RCM_INDEX_MASK 0x001F +#define RCM_SIZE 15 + +#define RCM_MAC_OFFSET 0 /* current MAC address */ +#define RCM_BSSID_OFFSET 3 /* current BSSID address */ +#define RCM_F_BSSID_0_OFFSET 6 /* foreign BSS CFP tracking */ +#define RCM_F_BSSID_1_OFFSET 9 /* foreign BSS CFP tracking */ +#define RCM_F_BSSID_2_OFFSET 12 /* foreign BSS CFP tracking */ + +#define RCM_WEP_TA0_OFFSET 16 +#define RCM_WEP_TA1_OFFSET 19 +#define RCM_WEP_TA2_OFFSET 22 +#define RCM_WEP_TA3_OFFSET 25 + +/* PSM Block */ + +/* psm_phy_hdr_param bits */ +#define MAC_PHY_RESET 1 +#define MAC_PHY_CLOCK_EN 2 +#define MAC_PHY_FORCE_CLK 4 + +/* WEP Block */ + +/* WEP_WKEY */ +#define WKEY_START (1 << 8) +#define WKEY_SEL_MASK 0x1F + +/* WEP data formats */ + +/* the number of RCMTA entries */ +#define RCMTA_SIZE 50 + +#define M_ADDR_BMP_BLK (0x37e * 2) +#define M_ADDR_BMP_BLK_SZ 12 + +#define ADDR_BMP_RA (1 << 0) /* Receiver Address (RA) */ +#define ADDR_BMP_TA (1 << 1) /* Transmitter Address (TA) */ +#define ADDR_BMP_BSSID (1 << 2) /* BSSID */ +#define ADDR_BMP_AP (1 << 3) /* Infra-BSS Access Point */ +#define ADDR_BMP_STA (1 << 4) /* Infra-BSS Station */ +#define ADDR_BMP_RESERVED1 (1 << 5) +#define ADDR_BMP_RESERVED2 (1 << 6) +#define ADDR_BMP_RESERVED3 (1 << 7) +#define ADDR_BMP_BSS_IDX_MASK (3 << 8) /* BSS control block index */ +#define ADDR_BMP_BSS_IDX_SHIFT 8 + +#define WSEC_MAX_RCMTA_KEYS 54 + +/* max keys in M_TKMICKEYS_BLK */ +#define WSEC_MAX_TKMIC_ENGINE_KEYS 12 /* 8 + 4 default */ + +/* max RXE match registers */ +#define WSEC_MAX_RXE_KEYS 4 + +/* SECKINDXALGO (Security Key Index & Algorithm Block) word format */ +/* SKL (Security Key Lookup) */ +#define SKL_ALGO_MASK 0x0007 +#define SKL_ALGO_SHIFT 0 +#define SKL_KEYID_MASK 0x0008 +#define SKL_KEYID_SHIFT 3 +#define SKL_INDEX_MASK 0x03F0 +#define SKL_INDEX_SHIFT 4 +#define SKL_GRP_ALGO_MASK 0x1c00 +#define SKL_GRP_ALGO_SHIFT 10 + +/* additional bits defined for IBSS group key support */ +#define SKL_IBSS_INDEX_MASK 0x01F0 +#define SKL_IBSS_INDEX_SHIFT 4 +#define SKL_IBSS_KEYID1_MASK 0x0600 +#define SKL_IBSS_KEYID1_SHIFT 9 +#define SKL_IBSS_KEYID2_MASK 0x1800 +#define SKL_IBSS_KEYID2_SHIFT 11 +#define SKL_IBSS_KEYALGO_MASK 0xE000 +#define SKL_IBSS_KEYALGO_SHIFT 13 + +#define WSEC_MODE_OFF 0 +#define WSEC_MODE_HW 1 +#define WSEC_MODE_SW 2 + +#define WSEC_ALGO_OFF 0 +#define WSEC_ALGO_WEP1 1 +#define WSEC_ALGO_TKIP 2 +#define WSEC_ALGO_AES 3 +#define WSEC_ALGO_WEP128 4 +#define WSEC_ALGO_AES_LEGACY 5 +#define WSEC_ALGO_NALG 6 + +#define AES_MODE_NONE 0 +#define AES_MODE_CCM 1 + +/* WEP_CTL (Rev 0) */ +#define WECR0_KEYREG_SHIFT 0 +#define WECR0_KEYREG_MASK 0x7 +#define WECR0_DECRYPT (1 << 3) +#define WECR0_IVINLINE (1 << 4) +#define WECR0_WEPALG_SHIFT 5 +#define WECR0_WEPALG_MASK (0x7 << 5) +#define WECR0_WKEYSEL_SHIFT 8 +#define WECR0_WKEYSEL_MASK (0x7 << 8) +#define WECR0_WKEYSTART (1 << 11) +#define WECR0_WEPINIT (1 << 14) +#define WECR0_ICVERR (1 << 15) + +/* Frame template map byte offsets */ +#define T_ACTS_TPL_BASE (0) +#define T_NULL_TPL_BASE (0xc * 2) +#define T_QNULL_TPL_BASE (0x1c * 2) +#define T_RR_TPL_BASE (0x2c * 2) +#define T_BCN0_TPL_BASE (0x34 * 2) +#define T_PRS_TPL_BASE (0x134 * 2) +#define T_BCN1_TPL_BASE (0x234 * 2) +#define T_TX_FIFO_TXRAM_BASE (T_ACTS_TPL_BASE + \ + (TXFIFO_START_BLK * TXFIFO_SIZE_UNIT)) + +#define T_BA_TPL_BASE T_QNULL_TPL_BASE /* template area for BA */ + +#define T_RAM_ACCESS_SZ 4 /* template ram is 4 byte access only */ + +/* Shared Mem byte offsets */ + +/* Location where the ucode expects the corerev */ +#define M_MACHW_VER (0x00b * 2) + +/* Location where the ucode expects the MAC capabilities */ +#define M_MACHW_CAP_L (0x060 * 2) +#define M_MACHW_CAP_H (0x061 * 2) + +/* WME shared memory */ +#define M_EDCF_STATUS_OFF (0x007 * 2) +#define M_TXF_CUR_INDEX (0x018 * 2) +#define M_EDCF_QINFO (0x120 * 2) + +/* PS-mode related parameters */ +#define M_DOT11_SLOT (0x008 * 2) +#define M_DOT11_DTIMPERIOD (0x009 * 2) +#define M_NOSLPZNATDTIM (0x026 * 2) + +/* Beacon-related parameters */ +#define M_BCN0_FRM_BYTESZ (0x00c * 2) /* Bcn 0 template length */ +#define M_BCN1_FRM_BYTESZ (0x00d * 2) /* Bcn 1 template length */ +#define M_BCN_TXTSF_OFFSET (0x00e * 2) +#define M_TIMBPOS_INBEACON (0x00f * 2) +#define M_SFRMTXCNTFBRTHSD (0x022 * 2) +#define M_LFRMTXCNTFBRTHSD (0x023 * 2) +#define M_BCN_PCTLWD (0x02a * 2) +#define M_BCN_LI (0x05b * 2) /* beacon listen interval */ + +/* MAX Rx Frame len */ +#define M_MAXRXFRM_LEN (0x010 * 2) + +/* ACK/CTS related params */ +#define M_RSP_PCTLWD (0x011 * 2) + +/* Hardware Power Control */ +#define M_TXPWR_N (0x012 * 2) +#define M_TXPWR_TARGET (0x013 * 2) +#define M_TXPWR_MAX (0x014 * 2) +#define M_TXPWR_CUR (0x019 * 2) + +/* Rx-related parameters */ +#define M_RX_PAD_DATA_OFFSET (0x01a * 2) + +/* WEP Shared mem data */ +#define M_SEC_DEFIVLOC (0x01e * 2) +#define M_SEC_VALNUMSOFTMCHTA (0x01f * 2) +#define M_PHYVER (0x028 * 2) +#define M_PHYTYPE (0x029 * 2) +#define M_SECRXKEYS_PTR (0x02b * 2) +#define M_TKMICKEYS_PTR (0x059 * 2) +#define M_SECKINDXALGO_BLK (0x2ea * 2) +#define M_SECKINDXALGO_BLK_SZ 54 +#define M_SECPSMRXTAMCH_BLK (0x2fa * 2) +#define M_TKIP_TSC_TTAK (0x18c * 2) +#define D11_MAX_KEY_SIZE 16 + +#define M_MAX_ANTCNT (0x02e * 2) /* antenna swap threshold */ + +/* Probe response related parameters */ +#define M_SSIDLEN (0x024 * 2) +#define M_PRB_RESP_FRM_LEN (0x025 * 2) +#define M_PRS_MAXTIME (0x03a * 2) +#define M_SSID (0xb0 * 2) +#define M_CTXPRS_BLK (0xc0 * 2) +#define C_CTX_PCTLWD_POS (0x4 * 2) + +/* Delta between OFDM and CCK power in CCK power boost mode */ +#define M_OFDM_OFFSET (0x027 * 2) + +/* TSSI for last 4 11b/g CCK packets transmitted */ +#define M_B_TSSI_0 (0x02c * 2) +#define M_B_TSSI_1 (0x02d * 2) + +/* Host flags to turn on ucode options */ +#define M_HOST_FLAGS1 (0x02f * 2) +#define M_HOST_FLAGS2 (0x030 * 2) +#define M_HOST_FLAGS3 (0x031 * 2) +#define M_HOST_FLAGS4 (0x03c * 2) +#define M_HOST_FLAGS5 (0x06a * 2) +#define M_HOST_FLAGS_SZ 16 + +#define M_RADAR_REG (0x033 * 2) + +/* TSSI for last 4 11a OFDM packets transmitted */ +#define M_A_TSSI_0 (0x034 * 2) +#define M_A_TSSI_1 (0x035 * 2) + +/* noise interference measurement */ +#define M_NOISE_IF_COUNT (0x034 * 2) +#define M_NOISE_IF_TIMEOUT (0x035 * 2) + +#define M_RF_RX_SP_REG1 (0x036 * 2) + +/* TSSI for last 4 11g OFDM packets transmitted */ +#define M_G_TSSI_0 (0x038 * 2) +#define M_G_TSSI_1 (0x039 * 2) + +/* Background noise measure */ +#define M_JSSI_0 (0x44 * 2) +#define M_JSSI_1 (0x45 * 2) +#define M_JSSI_AUX (0x46 * 2) + +#define M_CUR_2050_RADIOCODE (0x47 * 2) + +/* TX fifo sizes */ +#define M_FIFOSIZE0 (0x4c * 2) +#define M_FIFOSIZE1 (0x4d * 2) +#define M_FIFOSIZE2 (0x4e * 2) +#define M_FIFOSIZE3 (0x4f * 2) +#define D11_MAX_TX_FRMS 32 /* max frames allowed in tx fifo */ + +/* Current channel number plus upper bits */ +#define M_CURCHANNEL (0x50 * 2) +#define D11_CURCHANNEL_5G 0x0100; +#define D11_CURCHANNEL_40 0x0200; +#define D11_CURCHANNEL_MAX 0x00FF; + +/* last posted frameid on the bcmc fifo */ +#define M_BCMC_FID (0x54 * 2) +#define INVALIDFID 0xffff + +/* extended beacon phyctl bytes for 11N */ +#define M_BCN_PCTL1WD (0x058 * 2) + +/* idle busy ratio to duty_cycle requirement */ +#define M_TX_IDLE_BUSY_RATIO_X_16_CCK (0x52 * 2) +#define M_TX_IDLE_BUSY_RATIO_X_16_OFDM (0x5A * 2) + +/* CW RSSI for LCNPHY */ +#define M_LCN_RSSI_0 0x1332 +#define M_LCN_RSSI_1 0x1338 +#define M_LCN_RSSI_2 0x133e +#define M_LCN_RSSI_3 0x1344 + +/* SNR for LCNPHY */ +#define M_LCN_SNR_A_0 0x1334 +#define M_LCN_SNR_B_0 0x1336 + +#define M_LCN_SNR_A_1 0x133a +#define M_LCN_SNR_B_1 0x133c + +#define M_LCN_SNR_A_2 0x1340 +#define M_LCN_SNR_B_2 0x1342 + +#define M_LCN_SNR_A_3 0x1346 +#define M_LCN_SNR_B_3 0x1348 + +#define M_LCN_LAST_RESET (81*2) +#define M_LCN_LAST_LOC (63*2) +#define M_LCNPHY_RESET_STATUS (4902) +#define M_LCNPHY_DSC_TIME (0x98d*2) +#define M_LCNPHY_RESET_CNT_DSC (0x98b*2) +#define M_LCNPHY_RESET_CNT (0x98c*2) + +/* Rate table offsets */ +#define M_RT_DIRMAP_A (0xe0 * 2) +#define M_RT_BBRSMAP_A (0xf0 * 2) +#define M_RT_DIRMAP_B (0x100 * 2) +#define M_RT_BBRSMAP_B (0x110 * 2) + +/* Rate table entry offsets */ +#define M_RT_PRS_PLCP_POS 10 +#define M_RT_PRS_DUR_POS 16 +#define M_RT_OFDM_PCTL1_POS 18 + +#define M_20IN40_IQ (0x380 * 2) + +/* SHM locations where ucode stores the current power index */ +#define M_CURR_IDX1 (0x384 * 2) +#define M_CURR_IDX2 (0x387 * 2) + +#define M_BSCALE_ANT0 (0x5e * 2) +#define M_BSCALE_ANT1 (0x5f * 2) + +/* Antenna Diversity Testing */ +#define M_MIMO_ANTSEL_RXDFLT (0x63 * 2) +#define M_ANTSEL_CLKDIV (0x61 * 2) +#define M_MIMO_ANTSEL_TXDFLT (0x64 * 2) + +#define M_MIMO_MAXSYM (0x5d * 2) +#define MIMO_MAXSYM_DEF 0x8000 /* 32k */ +#define MIMO_MAXSYM_MAX 0xffff /* 64k */ + +#define M_WATCHDOG_8TU (0x1e * 2) +#define WATCHDOG_8TU_DEF 5 +#define WATCHDOG_8TU_MAX 10 + +/* Manufacturing Test Variables */ +/* PER test mode */ +#define M_PKTENG_CTRL (0x6c * 2) +/* IFS for TX mode */ +#define M_PKTENG_IFS (0x6d * 2) +/* Lower word of tx frmcnt/rx lostcnt */ +#define M_PKTENG_FRMCNT_LO (0x6e * 2) +/* Upper word of tx frmcnt/rx lostcnt */ +#define M_PKTENG_FRMCNT_HI (0x6f * 2) + +/* Index variation in vbat ripple */ +#define M_LCN_PWR_IDX_MAX (0x67 * 2) /* highest index read by ucode */ +#define M_LCN_PWR_IDX_MIN (0x66 * 2) /* lowest index read by ucode */ + +/* M_PKTENG_CTRL bit definitions */ +#define M_PKTENG_MODE_TX 0x0001 +#define M_PKTENG_MODE_TX_RIFS 0x0004 +#define M_PKTENG_MODE_TX_CTS 0x0008 +#define M_PKTENG_MODE_RX 0x0002 +#define M_PKTENG_MODE_RX_WITH_ACK 0x0402 +#define M_PKTENG_MODE_MASK 0x0003 +/* TX frames indicated in the frmcnt reg */ +#define M_PKTENG_FRMCNT_VLD 0x0100 + +/* Sample Collect parameters (bitmap and type) */ +/* Trigger bitmap for sample collect */ +#define M_SMPL_COL_BMP (0x37d * 2) +/* Sample collect type */ +#define M_SMPL_COL_CTL (0x3b2 * 2) + +#define ANTSEL_CLKDIV_4MHZ 6 +#define MIMO_ANTSEL_BUSY 0x4000 /* bit 14 (busy) */ +#define MIMO_ANTSEL_SEL 0x8000 /* bit 15 write the value */ +#define MIMO_ANTSEL_WAIT 50 /* 50us wait */ +#define MIMO_ANTSEL_OVERRIDE 0x8000 /* flag */ + +struct shm_acparams { + u16 txop; + u16 cwmin; + u16 cwmax; + u16 cwcur; + u16 aifs; + u16 bslots; + u16 reggap; + u16 status; + u16 rsvd[8]; +} __packed; +#define M_EDCF_QLEN (16 * 2) + +#define WME_STATUS_NEWAC (1 << 8) + +/* M_HOST_FLAGS */ +#define MHFMAX 5 /* Number of valid hostflag half-word (u16) */ +#define MHF1 0 /* Hostflag 1 index */ +#define MHF2 1 /* Hostflag 2 index */ +#define MHF3 2 /* Hostflag 3 index */ +#define MHF4 3 /* Hostflag 4 index */ +#define MHF5 4 /* Hostflag 5 index */ + +/* Flags in M_HOST_FLAGS */ +/* Enable ucode antenna diversity help */ +#define MHF1_ANTDIV 0x0001 +/* Enable EDCF access control */ +#define MHF1_EDCF 0x0100 +#define MHF1_IQSWAP_WAR 0x0200 +/* Disable Slow clock request, for corerev < 11 */ +#define MHF1_FORCEFASTCLK 0x0400 + +/* Flags in M_HOST_FLAGS2 */ + +/* Flush BCMC FIFO immediately */ +#define MHF2_TXBCMC_NOW 0x0040 +/* Enable ucode/hw power control */ +#define MHF2_HWPWRCTL 0x0080 +#define MHF2_NPHY40MHZ_WAR 0x0800 + +/* Flags in M_HOST_FLAGS3 */ +/* enabled mimo antenna selection */ +#define MHF3_ANTSEL_EN 0x0001 +/* antenna selection mode: 0: 2x3, 1: 2x4 */ +#define MHF3_ANTSEL_MODE 0x0002 +#define MHF3_RESERVED1 0x0004 +#define MHF3_RESERVED2 0x0008 +#define MHF3_NPHY_MLADV_WAR 0x0010 + +/* Flags in M_HOST_FLAGS4 */ +/* force bphy Tx on core 0 (board level WAR) */ +#define MHF4_BPHY_TXCORE0 0x0080 +/* for 4313A0 FEM boards */ +#define MHF4_EXTPA_ENABLE 0x4000 + +/* Flags in M_HOST_FLAGS5 */ +#define MHF5_4313_GPIOCTRL 0x0001 +#define MHF5_RESERVED1 0x0002 +#define MHF5_RESERVED2 0x0004 +/* Radio power setting for ucode */ +#define M_RADIO_PWR (0x32 * 2) + +/* phy noise recorded by ucode right after tx */ +#define M_PHY_NOISE (0x037 * 2) +#define PHY_NOISE_MASK 0x00ff + +/* + * Receive Frame Data Header for 802.11b DCF-only frames + * + * RxFrameSize: Actual byte length of the frame data received + * PAD: padding (not used) + * PhyRxStatus_0: PhyRxStatus 15:0 + * PhyRxStatus_1: PhyRxStatus 31:16 + * PhyRxStatus_2: PhyRxStatus 47:32 + * PhyRxStatus_3: PhyRxStatus 63:48 + * PhyRxStatus_4: PhyRxStatus 79:64 + * PhyRxStatus_5: PhyRxStatus 95:80 + * RxStatus1: MAC Rx Status + * RxStatus2: extended MAC Rx status + * RxTSFTime: RxTSFTime time of first MAC symbol + M_PHY_PLCPRX_DLY + * RxChan: gain code, channel radio code, and phy type + */ +struct d11rxhdr_le { + __le16 RxFrameSize; + u16 PAD; + __le16 PhyRxStatus_0; + __le16 PhyRxStatus_1; + __le16 PhyRxStatus_2; + __le16 PhyRxStatus_3; + __le16 PhyRxStatus_4; + __le16 PhyRxStatus_5; + __le16 RxStatus1; + __le16 RxStatus2; + __le16 RxTSFTime; + __le16 RxChan; +} __packed; + +struct d11rxhdr { + u16 RxFrameSize; + u16 PAD; + u16 PhyRxStatus_0; + u16 PhyRxStatus_1; + u16 PhyRxStatus_2; + u16 PhyRxStatus_3; + u16 PhyRxStatus_4; + u16 PhyRxStatus_5; + u16 RxStatus1; + u16 RxStatus2; + u16 RxTSFTime; + u16 RxChan; +} __packed; + +/* PhyRxStatus_0: */ +/* NPHY only: CCK, OFDM, preN, N */ +#define PRXS0_FT_MASK 0x0003 +/* NPHY only: clip count adjustment steps by AGC */ +#define PRXS0_CLIP_MASK 0x000C +#define PRXS0_CLIP_SHIFT 2 +/* PHY received a frame with unsupported rate */ +#define PRXS0_UNSRATE 0x0010 +/* GPHY: rx ant, NPHY: upper sideband */ +#define PRXS0_RXANT_UPSUBBAND 0x0020 +/* CCK frame only: lost crs during cck frame reception */ +#define PRXS0_LCRS 0x0040 +/* Short Preamble */ +#define PRXS0_SHORTH 0x0080 +/* PLCP violation */ +#define PRXS0_PLCPFV 0x0100 +/* PLCP header integrity check failed */ +#define PRXS0_PLCPHCF 0x0200 +/* legacy PHY gain control */ +#define PRXS0_GAIN_CTL 0x4000 +/* NPHY: Antennas used for received frame, bitmask */ +#define PRXS0_ANTSEL_MASK 0xF000 +#define PRXS0_ANTSEL_SHIFT 0x12 + +/* subfield PRXS0_FT_MASK */ +#define PRXS0_CCK 0x0000 +/* valid only for G phy, use rxh->RxChan for A phy */ +#define PRXS0_OFDM 0x0001 +#define PRXS0_PREN 0x0002 +#define PRXS0_STDN 0x0003 + +/* subfield PRXS0_ANTSEL_MASK */ +#define PRXS0_ANTSEL_0 0x0 /* antenna 0 is used */ +#define PRXS0_ANTSEL_1 0x2 /* antenna 1 is used */ +#define PRXS0_ANTSEL_2 0x4 /* antenna 2 is used */ +#define PRXS0_ANTSEL_3 0x8 /* antenna 3 is used */ + +/* PhyRxStatus_1: */ +#define PRXS1_JSSI_MASK 0x00FF +#define PRXS1_JSSI_SHIFT 0 +#define PRXS1_SQ_MASK 0xFF00 +#define PRXS1_SQ_SHIFT 8 + +/* nphy PhyRxStatus_1: */ +#define PRXS1_nphy_PWR0_MASK 0x00FF +#define PRXS1_nphy_PWR1_MASK 0xFF00 + +/* HTPHY Rx Status defines */ +/* htphy PhyRxStatus_0: those bit are overlapped with PhyRxStatus_0 */ +#define PRXS0_BAND 0x0400 /* 0 = 2.4G, 1 = 5G */ +#define PRXS0_RSVD 0x0800 /* reserved; set to 0 */ +#define PRXS0_UNUSED 0xF000 /* unused and not defined; set to 0 */ + +/* htphy PhyRxStatus_1: */ +/* core enables for {3..0}, 0=disabled, 1=enabled */ +#define PRXS1_HTPHY_CORE_MASK 0x000F +/* antenna configation */ +#define PRXS1_HTPHY_ANTCFG_MASK 0x00F0 +/* Mixmode PLCP Length low byte mask */ +#define PRXS1_HTPHY_MMPLCPLenL_MASK 0xFF00 + +/* htphy PhyRxStatus_2: */ +/* Mixmode PLCP Length high byte maskw */ +#define PRXS2_HTPHY_MMPLCPLenH_MASK 0x000F +/* Mixmode PLCP rate mask */ +#define PRXS2_HTPHY_MMPLCH_RATE_MASK 0x00F0 +/* Rx power on core 0 */ +#define PRXS2_HTPHY_RXPWR_ANT0 0xFF00 + +/* htphy PhyRxStatus_3: */ +/* Rx power on core 1 */ +#define PRXS3_HTPHY_RXPWR_ANT1 0x00FF +/* Rx power on core 2 */ +#define PRXS3_HTPHY_RXPWR_ANT2 0xFF00 + +/* htphy PhyRxStatus_4: */ +/* Rx power on core 3 */ +#define PRXS4_HTPHY_RXPWR_ANT3 0x00FF +/* Coarse frequency offset */ +#define PRXS4_HTPHY_CFO 0xFF00 + +/* htphy PhyRxStatus_5: */ +/* Fine frequency offset */ +#define PRXS5_HTPHY_FFO 0x00FF +/* Advance Retard */ +#define PRXS5_HTPHY_AR 0xFF00 + +#define HTPHY_MMPLCPLen(rxs) \ + ((((rxs)->PhyRxStatus_1 & PRXS1_HTPHY_MMPLCPLenL_MASK) >> 8) | \ + (((rxs)->PhyRxStatus_2 & PRXS2_HTPHY_MMPLCPLenH_MASK) << 8)) +/* Get Rx power on core 0 */ +#define HTPHY_RXPWR_ANT0(rxs) \ + ((((rxs)->PhyRxStatus_2) & PRXS2_HTPHY_RXPWR_ANT0) >> 8) +/* Get Rx power on core 1 */ +#define HTPHY_RXPWR_ANT1(rxs) \ + (((rxs)->PhyRxStatus_3) & PRXS3_HTPHY_RXPWR_ANT1) +/* Get Rx power on core 2 */ +#define HTPHY_RXPWR_ANT2(rxs) \ + ((((rxs)->PhyRxStatus_3) & PRXS3_HTPHY_RXPWR_ANT2) >> 8) + +/* ucode RxStatus1: */ +#define RXS_BCNSENT 0x8000 +#define RXS_SECKINDX_MASK 0x07e0 +#define RXS_SECKINDX_SHIFT 5 +#define RXS_DECERR (1 << 4) +#define RXS_DECATMPT (1 << 3) +/* PAD bytes to make IP data 4 bytes aligned */ +#define RXS_PBPRES (1 << 2) +#define RXS_RESPFRAMETX (1 << 1) +#define RXS_FCSERR (1 << 0) + +/* ucode RxStatus2: */ +#define RXS_AMSDU_MASK 1 +#define RXS_AGGTYPE_MASK 0x6 +#define RXS_AGGTYPE_SHIFT 1 +#define RXS_PHYRXST_VALID (1 << 8) +#define RXS_RXANT_MASK 0x3 +#define RXS_RXANT_SHIFT 12 + +/* RxChan */ +#define RXS_CHAN_40 0x1000 +#define RXS_CHAN_5G 0x0800 +#define RXS_CHAN_ID_MASK 0x07f8 +#define RXS_CHAN_ID_SHIFT 3 +#define RXS_CHAN_PHYTYPE_MASK 0x0007 +#define RXS_CHAN_PHYTYPE_SHIFT 0 + +/* Index of attenuations used during ucode power control. */ +#define M_PWRIND_BLKS (0x184 * 2) +#define M_PWRIND_MAP0 (M_PWRIND_BLKS + 0x0) +#define M_PWRIND_MAP1 (M_PWRIND_BLKS + 0x2) +#define M_PWRIND_MAP2 (M_PWRIND_BLKS + 0x4) +#define M_PWRIND_MAP3 (M_PWRIND_BLKS + 0x6) +/* M_PWRIND_MAP(core) macro */ +#define M_PWRIND_MAP(core) (M_PWRIND_BLKS + ((core)<<1)) + +/* PSM SHM variable offsets */ +#define M_PSM_SOFT_REGS 0x0 +#define M_BOM_REV_MAJOR (M_PSM_SOFT_REGS + 0x0) +#define M_BOM_REV_MINOR (M_PSM_SOFT_REGS + 0x2) +#define M_UCODE_DBGST (M_PSM_SOFT_REGS + 0x40) /* ucode debug status code */ +#define M_UCODE_MACSTAT (M_PSM_SOFT_REGS + 0xE0) /* macstat counters */ + +#define M_AGING_THRSH (0x3e * 2) /* max time waiting for medium before tx */ +#define M_MBURST_SIZE (0x40 * 2) /* max frames in a frameburst */ +#define M_MBURST_TXOP (0x41 * 2) /* max frameburst TXOP in unit of us */ +#define M_SYNTHPU_DLY (0x4a * 2) /* pre-wakeup for synthpu, default: 500 */ +#define M_PRETBTT (0x4b * 2) + +/* offset to the target txpwr */ +#define M_ALT_TXPWR_IDX (M_PSM_SOFT_REGS + (0x3b * 2)) +#define M_PHY_TX_FLT_PTR (M_PSM_SOFT_REGS + (0x3d * 2)) +#define M_CTS_DURATION (M_PSM_SOFT_REGS + (0x5c * 2)) +#define M_LP_RCCAL_OVR (M_PSM_SOFT_REGS + (0x6b * 2)) + +/* PKTENG Rx Stats Block */ +#define M_RXSTATS_BLK_PTR (M_PSM_SOFT_REGS + (0x65 * 2)) + +/* ucode debug status codes */ +/* not valid really */ +#define DBGST_INACTIVE 0 +/* after zeroing SHM, before suspending at init */ +#define DBGST_INIT 1 +/* "normal" state */ +#define DBGST_ACTIVE 2 +/* suspended */ +#define DBGST_SUSPENDED 3 +/* asleep (PS mode) */ +#define DBGST_ASLEEP 4 + +/* Scratch Reg defs */ +enum _ePsmScratchPadRegDefinitions { + S_RSV0 = 0, + S_RSV1, + S_RSV2, + + /* offset 0x03: scratch registers for Dot11-contants */ + S_DOT11_CWMIN, /* CW-minimum */ + S_DOT11_CWMAX, /* CW-maximum */ + S_DOT11_CWCUR, /* CW-current */ + S_DOT11_SRC_LMT, /* short retry count limit */ + S_DOT11_LRC_LMT, /* long retry count limit */ + S_DOT11_DTIMCOUNT, /* DTIM-count */ + + /* offset 0x09: Tx-side scratch registers */ + S_SEQ_NUM, /* hardware sequence number reg */ + S_SEQ_NUM_FRAG, /* seq num for frags (at the start of MSDU) */ + S_FRMRETX_CNT, /* frame retx count */ + S_SSRC, /* Station short retry count */ + S_SLRC, /* Station long retry count */ + S_EXP_RSP, /* Expected response frame */ + S_OLD_BREM, /* Remaining backoff ctr */ + S_OLD_CWWIN, /* saved-off CW-cur */ + S_TXECTL, /* TXE-Ctl word constructed in scr-pad */ + S_CTXTST, /* frm type-subtype as read from Tx-descr */ + + /* offset 0x13: Rx-side scratch registers */ + S_RXTST, /* Type and subtype in Rxframe */ + + /* Global state register */ + S_STREG, /* state storage actual bit maps below */ + + S_TXPWR_SUM, /* Tx power control: accumulator */ + S_TXPWR_ITER, /* Tx power control: iteration */ + S_RX_FRMTYPE, /* Rate and PHY type for frames */ + S_THIS_AGG, /* Size of this AGG (A-MSDU) */ + + S_KEYINDX, + S_RXFRMLEN, /* Receive MPDU length in bytes */ + + /* offset 0x1B: Receive TSF time stored in SCR */ + S_RXTSFTMRVAL_WD3, /* TSF value at the start of rx */ + S_RXTSFTMRVAL_WD2, /* TSF value at the start of rx */ + S_RXTSFTMRVAL_WD1, /* TSF value at the start of rx */ + S_RXTSFTMRVAL_WD0, /* TSF value at the start of rx */ + S_RXSSN, /* Received start seq number for A-MPDU BA */ + S_RXQOSFLD, /* Rx-QoS field (if present) */ + + /* offset 0x21: Scratch pad regs used in microcode as temp storage */ + S_TMP0, /* stmp0 */ + S_TMP1, /* stmp1 */ + S_TMP2, /* stmp2 */ + S_TMP3, /* stmp3 */ + S_TMP4, /* stmp4 */ + S_TMP5, /* stmp5 */ + S_PRQPENALTY_CTR, /* Probe response queue penalty counter */ + S_ANTCNT, /* unsuccessful attempts on current ant. */ + S_SYMBOL, /* flag for possible symbol ctl frames */ + S_RXTP, /* rx frame type */ + S_STREG2, /* extra state storage */ + S_STREG3, /* even more extra state storage */ + S_STREG4, /* ... */ + S_STREG5, /* remember to initialize it to zero */ + + S_ADJPWR_IDX, + S_CUR_PTR, /* Temp pointer for A-MPDU re-Tx SHM table */ + S_REVID4, /* 0x33 */ + S_INDX, /* 0x34 */ + S_ADDR0, /* 0x35 */ + S_ADDR1, /* 0x36 */ + S_ADDR2, /* 0x37 */ + S_ADDR3, /* 0x38 */ + S_ADDR4, /* 0x39 */ + S_ADDR5, /* 0x3A */ + S_TMP6, /* 0x3B */ + S_KEYINDX_BU, /* Backup for Key index */ + S_MFGTEST_TMP0, /* Temp regs used for RX test calculations */ + S_RXESN, /* Received end sequence number for A-MPDU BA */ + S_STREG6, /* 0x3F */ +}; + +#define S_BEACON_INDX S_OLD_BREM +#define S_PRS_INDX S_OLD_CWWIN +#define S_PHYTYPE S_SSRC +#define S_PHYVER S_SLRC + +/* IHR SLOW_CTRL values */ +#define SLOW_CTRL_PDE (1 << 0) +#define SLOW_CTRL_FD (1 << 8) + +/* ucode mac statistic counters in shared memory */ +struct macstat { + u16 txallfrm; /* 0x80 */ + u16 txrtsfrm; /* 0x82 */ + u16 txctsfrm; /* 0x84 */ + u16 txackfrm; /* 0x86 */ + u16 txdnlfrm; /* 0x88 */ + u16 txbcnfrm; /* 0x8a */ + u16 txfunfl[8]; /* 0x8c - 0x9b */ + u16 txtplunfl; /* 0x9c */ + u16 txphyerr; /* 0x9e */ + u16 pktengrxducast; /* 0xa0 */ + u16 pktengrxdmcast; /* 0xa2 */ + u16 rxfrmtoolong; /* 0xa4 */ + u16 rxfrmtooshrt; /* 0xa6 */ + u16 rxinvmachdr; /* 0xa8 */ + u16 rxbadfcs; /* 0xaa */ + u16 rxbadplcp; /* 0xac */ + u16 rxcrsglitch; /* 0xae */ + u16 rxstrt; /* 0xb0 */ + u16 rxdfrmucastmbss; /* 0xb2 */ + u16 rxmfrmucastmbss; /* 0xb4 */ + u16 rxcfrmucast; /* 0xb6 */ + u16 rxrtsucast; /* 0xb8 */ + u16 rxctsucast; /* 0xba */ + u16 rxackucast; /* 0xbc */ + u16 rxdfrmocast; /* 0xbe */ + u16 rxmfrmocast; /* 0xc0 */ + u16 rxcfrmocast; /* 0xc2 */ + u16 rxrtsocast; /* 0xc4 */ + u16 rxctsocast; /* 0xc6 */ + u16 rxdfrmmcast; /* 0xc8 */ + u16 rxmfrmmcast; /* 0xca */ + u16 rxcfrmmcast; /* 0xcc */ + u16 rxbeaconmbss; /* 0xce */ + u16 rxdfrmucastobss; /* 0xd0 */ + u16 rxbeaconobss; /* 0xd2 */ + u16 rxrsptmout; /* 0xd4 */ + u16 bcntxcancl; /* 0xd6 */ + u16 PAD; + u16 rxf0ovfl; /* 0xda */ + u16 rxf1ovfl; /* 0xdc */ + u16 rxf2ovfl; /* 0xde */ + u16 txsfovfl; /* 0xe0 */ + u16 pmqovfl; /* 0xe2 */ + u16 rxcgprqfrm; /* 0xe4 */ + u16 rxcgprsqovfl; /* 0xe6 */ + u16 txcgprsfail; /* 0xe8 */ + u16 txcgprssuc; /* 0xea */ + u16 prs_timeout; /* 0xec */ + u16 rxnack; + u16 frmscons; + u16 txnack; + u16 txglitch_nack; + u16 txburst; /* 0xf6 # tx bursts */ + u16 bphy_rxcrsglitch; /* bphy rx crs glitch */ + u16 phywatchdog; /* 0xfa # of phy watchdog events */ + u16 PAD; + u16 bphy_badplcp; /* bphy bad plcp */ +}; + +/* dot11 core-specific control flags */ +#define SICF_PCLKE 0x0004 /* PHY clock enable */ +#define SICF_PRST 0x0008 /* PHY reset */ +#define SICF_MPCLKE 0x0010 /* MAC PHY clockcontrol enable */ +#define SICF_FREF 0x0020 /* PLL FreqRefSelect */ +/* NOTE: the following bw bits only apply when the core is attached + * to a NPHY + */ +#define SICF_BWMASK 0x00c0 /* phy clock mask (b6 & b7) */ +#define SICF_BW40 0x0080 /* 40MHz BW (160MHz phyclk) */ +#define SICF_BW20 0x0040 /* 20MHz BW (80MHz phyclk) */ +#define SICF_BW10 0x0000 /* 10MHz BW (40MHz phyclk) */ +#define SICF_GMODE 0x2000 /* gmode enable */ + +/* dot11 core-specific status flags */ +#define SISF_2G_PHY 0x0001 /* 2.4G capable phy */ +#define SISF_5G_PHY 0x0002 /* 5G capable phy */ +#define SISF_FCLKA 0x0004 /* FastClkAvailable */ +#define SISF_DB_PHY 0x0008 /* Dualband phy */ + +/* === End of MAC reg, Beginning of PHY(b/a/g/n) reg === */ +/* radio and LPPHY regs are separated */ + +#define BPHY_REG_OFT_BASE 0x0 +/* offsets for indirect access to bphy registers */ +#define BPHY_BB_CONFIG 0x01 +#define BPHY_ADCBIAS 0x02 +#define BPHY_ANACORE 0x03 +#define BPHY_PHYCRSTH 0x06 +#define BPHY_TEST 0x0a +#define BPHY_PA_TX_TO 0x10 +#define BPHY_SYNTH_DC_TO 0x11 +#define BPHY_PA_TX_TIME_UP 0x12 +#define BPHY_RX_FLTR_TIME_UP 0x13 +#define BPHY_TX_POWER_OVERRIDE 0x14 +#define BPHY_RF_OVERRIDE 0x15 +#define BPHY_RF_TR_LOOKUP1 0x16 +#define BPHY_RF_TR_LOOKUP2 0x17 +#define BPHY_COEFFS 0x18 +#define BPHY_PLL_OUT 0x19 +#define BPHY_REFRESH_MAIN 0x1a +#define BPHY_REFRESH_TO0 0x1b +#define BPHY_REFRESH_TO1 0x1c +#define BPHY_RSSI_TRESH 0x20 +#define BPHY_IQ_TRESH_HH 0x21 +#define BPHY_IQ_TRESH_H 0x22 +#define BPHY_IQ_TRESH_L 0x23 +#define BPHY_IQ_TRESH_LL 0x24 +#define BPHY_GAIN 0x25 +#define BPHY_LNA_GAIN_RANGE 0x26 +#define BPHY_JSSI 0x27 +#define BPHY_TSSI_CTL 0x28 +#define BPHY_TSSI 0x29 +#define BPHY_TR_LOSS_CTL 0x2a +#define BPHY_LO_LEAKAGE 0x2b +#define BPHY_LO_RSSI_ACC 0x2c +#define BPHY_LO_IQMAG_ACC 0x2d +#define BPHY_TX_DC_OFF1 0x2e +#define BPHY_TX_DC_OFF2 0x2f +#define BPHY_PEAK_CNT_THRESH 0x30 +#define BPHY_FREQ_OFFSET 0x31 +#define BPHY_DIVERSITY_CTL 0x32 +#define BPHY_PEAK_ENERGY_LO 0x33 +#define BPHY_PEAK_ENERGY_HI 0x34 +#define BPHY_SYNC_CTL 0x35 +#define BPHY_TX_PWR_CTRL 0x36 +#define BPHY_TX_EST_PWR 0x37 +#define BPHY_STEP 0x38 +#define BPHY_WARMUP 0x39 +#define BPHY_LMS_CFF_READ 0x3a +#define BPHY_LMS_COEFF_I 0x3b +#define BPHY_LMS_COEFF_Q 0x3c +#define BPHY_SIG_POW 0x3d +#define BPHY_RFDC_CANCEL_CTL 0x3e +#define BPHY_HDR_TYPE 0x40 +#define BPHY_SFD_TO 0x41 +#define BPHY_SFD_CTL 0x42 +#define BPHY_DEBUG 0x43 +#define BPHY_RX_DELAY_COMP 0x44 +#define BPHY_CRS_DROP_TO 0x45 +#define BPHY_SHORT_SFD_NZEROS 0x46 +#define BPHY_DSSS_COEFF1 0x48 +#define BPHY_DSSS_COEFF2 0x49 +#define BPHY_CCK_COEFF1 0x4a +#define BPHY_CCK_COEFF2 0x4b +#define BPHY_TR_CORR 0x4c +#define BPHY_ANGLE_SCALE 0x4d +#define BPHY_TX_PWR_BASE_IDX 0x4e +#define BPHY_OPTIONAL_MODES2 0x4f +#define BPHY_CCK_LMS_STEP 0x50 +#define BPHY_BYPASS 0x51 +#define BPHY_CCK_DELAY_LONG 0x52 +#define BPHY_CCK_DELAY_SHORT 0x53 +#define BPHY_PPROC_CHAN_DELAY 0x54 +#define BPHY_DDFS_ENABLE 0x58 +#define BPHY_PHASE_SCALE 0x59 +#define BPHY_FREQ_CONTROL 0x5a +#define BPHY_LNA_GAIN_RANGE_10 0x5b +#define BPHY_LNA_GAIN_RANGE_32 0x5c +#define BPHY_OPTIONAL_MODES 0x5d +#define BPHY_RX_STATUS2 0x5e +#define BPHY_RX_STATUS3 0x5f +#define BPHY_DAC_CONTROL 0x60 +#define BPHY_ANA11G_FILT_CTRL 0x62 +#define BPHY_REFRESH_CTRL 0x64 +#define BPHY_RF_OVERRIDE2 0x65 +#define BPHY_SPUR_CANCEL_CTRL 0x66 +#define BPHY_FINE_DIGIGAIN_CTRL 0x67 +#define BPHY_RSSI_LUT 0x88 +#define BPHY_RSSI_LUT_END 0xa7 +#define BPHY_TSSI_LUT 0xa8 +#define BPHY_TSSI_LUT_END 0xc7 +#define BPHY_TSSI2PWR_LUT 0x380 +#define BPHY_TSSI2PWR_LUT_END 0x39f +#define BPHY_LOCOMP_LUT 0x3a0 +#define BPHY_LOCOMP_LUT_END 0x3bf +#define BPHY_TXGAIN_LUT 0x3c0 +#define BPHY_TXGAIN_LUT_END 0x3ff + +/* Bits in BB_CONFIG: */ +#define PHY_BBC_ANT_MASK 0x0180 +#define PHY_BBC_ANT_SHIFT 7 +#define BB_DARWIN 0x1000 +#define BBCFG_RESETCCA 0x4000 +#define BBCFG_RESETRX 0x8000 + +/* Bits in phytest(0x0a): */ +#define TST_DDFS 0x2000 +#define TST_TXFILT1 0x0800 +#define TST_UNSCRAM 0x0400 +#define TST_CARR_SUPP 0x0200 +#define TST_DC_COMP_LOOP 0x0100 +#define TST_LOOPBACK 0x0080 +#define TST_TXFILT0 0x0040 +#define TST_TXTEST_ENABLE 0x0020 +#define TST_TXTEST_RATE 0x0018 +#define TST_TXTEST_PHASE 0x0007 + +/* phytest txTestRate values */ +#define TST_TXTEST_RATE_1MBPS 0 +#define TST_TXTEST_RATE_2MBPS 1 +#define TST_TXTEST_RATE_5_5MBPS 2 +#define TST_TXTEST_RATE_11MBPS 3 +#define TST_TXTEST_RATE_SHIFT 3 + +#define SHM_BYT_CNT 0x2 /* IHR location */ +#define MAX_BYT_CNT 0x600 /* Maximum frame len */ + +struct d11cnt { + u32 txfrag; + u32 txmulti; + u32 txfail; + u32 txretry; + u32 txretrie; + u32 rxdup; + u32 txrts; + u32 txnocts; + u32 txnoack; + u32 rxfrag; + u32 rxmulti; + u32 rxcrc; + u32 txfrmsnt; + u32 rxundec; +}; + +#endif /* _BRCM_D11_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/debug.c b/drivers/net/wireless/brcm80211/brcmsmac/debug.c new file mode 100644 index 0000000..ec95187 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/debug.c @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2012 Broadcom Corporation + * Copyright (c) 2012 Canonical Ltd. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "types.h" +#include "main.h" +#include "debug.h" +#include "brcms_trace_events.h" + +static struct dentry *root_folder; + +void brcms_debugfs_init(void) +{ + root_folder = debugfs_create_dir(KBUILD_MODNAME, NULL); + if (IS_ERR(root_folder)) + root_folder = NULL; +} + +void brcms_debugfs_exit(void) +{ + if (!root_folder) + return; + + debugfs_remove_recursive(root_folder); + root_folder = NULL; +} + +int brcms_debugfs_attach(struct brcms_pub *drvr) +{ + if (!root_folder) + return -ENODEV; + + drvr->dbgfs_dir = debugfs_create_dir( + dev_name(&drvr->wlc->hw->d11core->dev), root_folder); + return PTR_RET(drvr->dbgfs_dir); +} + +void brcms_debugfs_detach(struct brcms_pub *drvr) +{ + if (!IS_ERR_OR_NULL(drvr->dbgfs_dir)) + debugfs_remove_recursive(drvr->dbgfs_dir); +} + +struct dentry *brcms_debugfs_get_devdir(struct brcms_pub *drvr) +{ + return drvr->dbgfs_dir; +} + +static +ssize_t brcms_debugfs_hardware_read(struct file *f, char __user *data, + size_t count, loff_t *ppos) +{ + char buf[128]; + int res; + struct brcms_pub *drvr = f->private_data; + + /* only allow read from start */ + if (*ppos > 0) + return 0; + + res = scnprintf(buf, sizeof(buf), + "board vendor: %x\n" + "board type: %x\n" + "board revision: %x\n" + "board flags: %x\n" + "board flags2: %x\n" + "firmware revision: %x\n", + drvr->wlc->hw->d11core->bus->boardinfo.vendor, + drvr->wlc->hw->d11core->bus->boardinfo.type, + drvr->wlc->hw->boardrev, + drvr->wlc->hw->boardflags, + drvr->wlc->hw->boardflags2, + drvr->wlc->ucode_rev + ); + + return simple_read_from_buffer(data, count, ppos, buf, res); +} + +static const struct file_operations brcms_debugfs_hardware_ops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = brcms_debugfs_hardware_read +}; + +void brcms_debugfs_create_files(struct brcms_pub *drvr) +{ + struct dentry *dentry = drvr->dbgfs_dir; + + if (!IS_ERR_OR_NULL(dentry)) + debugfs_create_file("hardware", S_IRUGO, dentry, + drvr, &brcms_debugfs_hardware_ops); +} + +#define __brcms_fn(fn) \ +void __brcms_ ##fn(struct device *dev, const char *fmt, ...) \ +{ \ + struct va_format vaf = { \ + .fmt = fmt, \ + }; \ + va_list args; \ + \ + va_start(args, fmt); \ + vaf.va = &args; \ + dev_ ##fn(dev, "%pV", &vaf); \ + trace_brcms_ ##fn(&vaf); \ + va_end(args); \ +} + +__brcms_fn(info) +__brcms_fn(warn) +__brcms_fn(err) +__brcms_fn(crit) + +#if defined(CPTCFG_BRCMDBG) || defined(CPTCFG_BRCM_TRACING) +void __brcms_dbg(struct device *dev, u32 level, const char *func, + const char *fmt, ...) +{ + struct va_format vaf = { + .fmt = fmt, + }; + va_list args; + + va_start(args, fmt); + vaf.va = &args; +#ifdef CPTCFG_BRCMDBG + if ((brcm_msg_level & level) && net_ratelimit()) + dev_err(dev, "%s %pV", func, &vaf); +#endif + trace_brcms_dbg(level, func, &vaf); + va_end(args); +} +#endif diff --git a/drivers/net/wireless/brcm80211/brcmsmac/debug.h b/drivers/net/wireless/brcm80211/brcmsmac/debug.h new file mode 100644 index 0000000..ba39fac --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/debug.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2012 Broadcom Corporation + * Copyright (c) 2012 Canonical Ltd. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef _BRCMS_DEBUG_H_ +#define _BRCMS_DEBUG_H_ + +#include +#include +#include +#include +#include "main.h" +#include "mac80211_if.h" + +__printf(2, 3) +void __brcms_info(struct device *dev, const char *fmt, ...); +__printf(2, 3) +void __brcms_warn(struct device *dev, const char *fmt, ...); +__printf(2, 3) +void __brcms_err(struct device *dev, const char *fmt, ...); +__printf(2, 3) +void __brcms_crit(struct device *dev, const char *fmt, ...); + +#if defined(CPTCFG_BRCMDBG) || defined(CPTCFG_BRCM_TRACING) +__printf(4, 5) +void __brcms_dbg(struct device *dev, u32 level, const char *func, + const char *fmt, ...); +#else +static inline __printf(4, 5) +void __brcms_dbg(struct device *dev, u32 level, const char *func, + const char *fmt, ...) +{ +} +#endif + +/* + * Debug macros cannot be used when wlc is uninitialized. Generally + * this means any code that could run before brcms_c_attach() has + * returned successfully probably shouldn't use the following macros. + */ + +#define brcms_dbg(core, l, f, a...) __brcms_dbg(&(core)->dev, l, __func__, f, ##a) +#define brcms_info(core, f, a...) __brcms_info(&(core)->dev, f, ##a) +#define brcms_warn(core, f, a...) __brcms_warn(&(core)->dev, f, ##a) +#define brcms_err(core, f, a...) __brcms_err(&(core)->dev, f, ##a) +#define brcms_crit(core, f, a...) __brcms_crit(&(core)->dev, f, ##a) + +#define brcms_dbg_info(core, f, a...) brcms_dbg(core, BRCM_DL_INFO, f, ##a) +#define brcms_dbg_mac80211(core, f, a...) brcms_dbg(core, BRCM_DL_MAC80211, f, ##a) +#define brcms_dbg_rx(core, f, a...) brcms_dbg(core, BRCM_DL_RX, f, ##a) +#define brcms_dbg_tx(core, f, a...) brcms_dbg(core, BRCM_DL_TX, f, ##a) +#define brcms_dbg_int(core, f, a...) brcms_dbg(core, BRCM_DL_INT, f, ##a) +#define brcms_dbg_dma(core, f, a...) brcms_dbg(core, BRCM_DL_DMA, f, ##a) +#define brcms_dbg_ht(core, f, a...) brcms_dbg(core, BRCM_DL_HT, f, ##a) + +struct brcms_pub; +void brcms_debugfs_init(void); +void brcms_debugfs_exit(void); +int brcms_debugfs_attach(struct brcms_pub *drvr); +void brcms_debugfs_detach(struct brcms_pub *drvr); +struct dentry *brcms_debugfs_get_devdir(struct brcms_pub *drvr); +void brcms_debugfs_create_files(struct brcms_pub *drvr); + +#endif /* _BRCMS_DEBUG_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/dma.c b/drivers/net/wireless/brcm80211/brcmsmac/dma.c new file mode 100644 index 0000000..4fb9635 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/dma.c @@ -0,0 +1,1564 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include "types.h" +#include "main.h" +#include "dma.h" +#include "soc.h" +#include "scb.h" +#include "ampdu.h" +#include "debug.h" +#include "brcms_trace_events.h" + +/* + * dma register field offset calculation + */ +#define DMA64REGOFFS(field) offsetof(struct dma64regs, field) +#define DMA64TXREGOFFS(di, field) (di->d64txregbase + DMA64REGOFFS(field)) +#define DMA64RXREGOFFS(di, field) (di->d64rxregbase + DMA64REGOFFS(field)) + +/* + * DMA hardware requires each descriptor ring to be 8kB aligned, and fit within + * a contiguous 8kB physical address. + */ +#define D64RINGALIGN_BITS 13 +#define D64MAXRINGSZ (1 << D64RINGALIGN_BITS) +#define D64RINGALIGN (1 << D64RINGALIGN_BITS) + +#define D64MAXDD (D64MAXRINGSZ / sizeof(struct dma64desc)) + +/* transmit channel control */ +#define D64_XC_XE 0x00000001 /* transmit enable */ +#define D64_XC_SE 0x00000002 /* transmit suspend request */ +#define D64_XC_LE 0x00000004 /* loopback enable */ +#define D64_XC_FL 0x00000010 /* flush request */ +#define D64_XC_PD 0x00000800 /* parity check disable */ +#define D64_XC_AE 0x00030000 /* address extension bits */ +#define D64_XC_AE_SHIFT 16 + +/* transmit descriptor table pointer */ +#define D64_XP_LD_MASK 0x00000fff /* last valid descriptor */ + +/* transmit channel status */ +#define D64_XS0_CD_MASK 0x00001fff /* current descriptor pointer */ +#define D64_XS0_XS_MASK 0xf0000000 /* transmit state */ +#define D64_XS0_XS_SHIFT 28 +#define D64_XS0_XS_DISABLED 0x00000000 /* disabled */ +#define D64_XS0_XS_ACTIVE 0x10000000 /* active */ +#define D64_XS0_XS_IDLE 0x20000000 /* idle wait */ +#define D64_XS0_XS_STOPPED 0x30000000 /* stopped */ +#define D64_XS0_XS_SUSP 0x40000000 /* suspend pending */ + +#define D64_XS1_AD_MASK 0x00001fff /* active descriptor */ +#define D64_XS1_XE_MASK 0xf0000000 /* transmit errors */ +#define D64_XS1_XE_SHIFT 28 +#define D64_XS1_XE_NOERR 0x00000000 /* no error */ +#define D64_XS1_XE_DPE 0x10000000 /* descriptor protocol error */ +#define D64_XS1_XE_DFU 0x20000000 /* data fifo underrun */ +#define D64_XS1_XE_DTE 0x30000000 /* data transfer error */ +#define D64_XS1_XE_DESRE 0x40000000 /* descriptor read error */ +#define D64_XS1_XE_COREE 0x50000000 /* core error */ + +/* receive channel control */ +/* receive enable */ +#define D64_RC_RE 0x00000001 +/* receive frame offset */ +#define D64_RC_RO_MASK 0x000000fe +#define D64_RC_RO_SHIFT 1 +/* direct fifo receive (pio) mode */ +#define D64_RC_FM 0x00000100 +/* separate rx header descriptor enable */ +#define D64_RC_SH 0x00000200 +/* overflow continue */ +#define D64_RC_OC 0x00000400 +/* parity check disable */ +#define D64_RC_PD 0x00000800 +/* address extension bits */ +#define D64_RC_AE 0x00030000 +#define D64_RC_AE_SHIFT 16 + +/* flags for dma controller */ +/* partity enable */ +#define DMA_CTRL_PEN (1 << 0) +/* rx overflow continue */ +#define DMA_CTRL_ROC (1 << 1) +/* allow rx scatter to multiple descriptors */ +#define DMA_CTRL_RXMULTI (1 << 2) +/* Unframed Rx/Tx data */ +#define DMA_CTRL_UNFRAMED (1 << 3) + +/* receive descriptor table pointer */ +#define D64_RP_LD_MASK 0x00000fff /* last valid descriptor */ + +/* receive channel status */ +#define D64_RS0_CD_MASK 0x00001fff /* current descriptor pointer */ +#define D64_RS0_RS_MASK 0xf0000000 /* receive state */ +#define D64_RS0_RS_SHIFT 28 +#define D64_RS0_RS_DISABLED 0x00000000 /* disabled */ +#define D64_RS0_RS_ACTIVE 0x10000000 /* active */ +#define D64_RS0_RS_IDLE 0x20000000 /* idle wait */ +#define D64_RS0_RS_STOPPED 0x30000000 /* stopped */ +#define D64_RS0_RS_SUSP 0x40000000 /* suspend pending */ + +#define D64_RS1_AD_MASK 0x0001ffff /* active descriptor */ +#define D64_RS1_RE_MASK 0xf0000000 /* receive errors */ +#define D64_RS1_RE_SHIFT 28 +#define D64_RS1_RE_NOERR 0x00000000 /* no error */ +#define D64_RS1_RE_DPO 0x10000000 /* descriptor protocol error */ +#define D64_RS1_RE_DFU 0x20000000 /* data fifo overflow */ +#define D64_RS1_RE_DTE 0x30000000 /* data transfer error */ +#define D64_RS1_RE_DESRE 0x40000000 /* descriptor read error */ +#define D64_RS1_RE_COREE 0x50000000 /* core error */ + +/* fifoaddr */ +#define D64_FA_OFF_MASK 0xffff /* offset */ +#define D64_FA_SEL_MASK 0xf0000 /* select */ +#define D64_FA_SEL_SHIFT 16 +#define D64_FA_SEL_XDD 0x00000 /* transmit dma data */ +#define D64_FA_SEL_XDP 0x10000 /* transmit dma pointers */ +#define D64_FA_SEL_RDD 0x40000 /* receive dma data */ +#define D64_FA_SEL_RDP 0x50000 /* receive dma pointers */ +#define D64_FA_SEL_XFD 0x80000 /* transmit fifo data */ +#define D64_FA_SEL_XFP 0x90000 /* transmit fifo pointers */ +#define D64_FA_SEL_RFD 0xc0000 /* receive fifo data */ +#define D64_FA_SEL_RFP 0xd0000 /* receive fifo pointers */ +#define D64_FA_SEL_RSD 0xe0000 /* receive frame status data */ +#define D64_FA_SEL_RSP 0xf0000 /* receive frame status pointers */ + +/* descriptor control flags 1 */ +#define D64_CTRL_COREFLAGS 0x0ff00000 /* core specific flags */ +#define D64_CTRL1_EOT ((u32)1 << 28) /* end of descriptor table */ +#define D64_CTRL1_IOC ((u32)1 << 29) /* interrupt on completion */ +#define D64_CTRL1_EOF ((u32)1 << 30) /* end of frame */ +#define D64_CTRL1_SOF ((u32)1 << 31) /* start of frame */ + +/* descriptor control flags 2 */ +/* buffer byte count. real data len must <= 16KB */ +#define D64_CTRL2_BC_MASK 0x00007fff +/* address extension bits */ +#define D64_CTRL2_AE 0x00030000 +#define D64_CTRL2_AE_SHIFT 16 +/* parity bit */ +#define D64_CTRL2_PARITY 0x00040000 + +/* control flags in the range [27:20] are core-specific and not defined here */ +#define D64_CTRL_CORE_MASK 0x0ff00000 + +#define D64_RX_FRM_STS_LEN 0x0000ffff /* frame length mask */ +#define D64_RX_FRM_STS_OVFL 0x00800000 /* RxOverFlow */ +#define D64_RX_FRM_STS_DSCRCNT 0x0f000000 /* no. of descriptors used - 1 */ +#define D64_RX_FRM_STS_DATATYPE 0xf0000000 /* core-dependent data type */ + +/* + * packet headroom necessary to accommodate the largest header + * in the system, (i.e TXOFF). By doing, we avoid the need to + * allocate an extra buffer for the header when bridging to WL. + * There is a compile time check in wlc.c which ensure that this + * value is at least as big as TXOFF. This value is used in + * dma_rxfill(). + */ + +#define BCMEXTRAHDROOM 172 + +#define MAXNAMEL 8 /* 8 char names */ + +/* macros to convert between byte offsets and indexes */ +#define B2I(bytes, type) ((bytes) / sizeof(type)) +#define I2B(index, type) ((index) * sizeof(type)) + +#define PCI32ADDR_HIGH 0xc0000000 /* address[31:30] */ +#define PCI32ADDR_HIGH_SHIFT 30 /* address[31:30] */ + +#define PCI64ADDR_HIGH 0x80000000 /* address[63] */ +#define PCI64ADDR_HIGH_SHIFT 31 /* address[63] */ + +/* + * DMA Descriptor + * Descriptors are only read by the hardware, never written back. + */ +struct dma64desc { + __le32 ctrl1; /* misc control bits & bufcount */ + __le32 ctrl2; /* buffer count and address extension */ + __le32 addrlow; /* memory address of the date buffer, bits 31:0 */ + __le32 addrhigh; /* memory address of the date buffer, bits 63:32 */ +}; + +/* dma engine software state */ +struct dma_info { + struct dma_pub dma; /* exported structure */ + char name[MAXNAMEL]; /* callers name for diag msgs */ + + struct bcma_device *core; + struct device *dmadev; + + /* session information for AMPDU */ + struct brcms_ampdu_session ampdu_session; + + bool dma64; /* this dma engine is operating in 64-bit mode */ + bool addrext; /* this dma engine supports DmaExtendedAddrChanges */ + + /* 64-bit dma tx engine registers */ + uint d64txregbase; + /* 64-bit dma rx engine registers */ + uint d64rxregbase; + /* pointer to dma64 tx descriptor ring */ + struct dma64desc *txd64; + /* pointer to dma64 rx descriptor ring */ + struct dma64desc *rxd64; + + u16 dmadesc_align; /* alignment requirement for dma descriptors */ + + u16 ntxd; /* # tx descriptors tunable */ + u16 txin; /* index of next descriptor to reclaim */ + u16 txout; /* index of next descriptor to post */ + /* pointer to parallel array of pointers to packets */ + struct sk_buff **txp; + /* Aligned physical address of descriptor ring */ + dma_addr_t txdpa; + /* Original physical address of descriptor ring */ + dma_addr_t txdpaorig; + u16 txdalign; /* #bytes added to alloc'd mem to align txd */ + u32 txdalloc; /* #bytes allocated for the ring */ + u32 xmtptrbase; /* When using unaligned descriptors, the ptr register + * is not just an index, it needs all 13 bits to be + * an offset from the addr register. + */ + + u16 nrxd; /* # rx descriptors tunable */ + u16 rxin; /* index of next descriptor to reclaim */ + u16 rxout; /* index of next descriptor to post */ + /* pointer to parallel array of pointers to packets */ + struct sk_buff **rxp; + /* Aligned physical address of descriptor ring */ + dma_addr_t rxdpa; + /* Original physical address of descriptor ring */ + dma_addr_t rxdpaorig; + u16 rxdalign; /* #bytes added to alloc'd mem to align rxd */ + u32 rxdalloc; /* #bytes allocated for the ring */ + u32 rcvptrbase; /* Base for ptr reg when using unaligned descriptors */ + + /* tunables */ + unsigned int rxbufsize; /* rx buffer size in bytes, not including + * the extra headroom + */ + uint rxextrahdrroom; /* extra rx headroom, reverseved to assist upper + * stack, e.g. some rx pkt buffers will be + * bridged to tx side without byte copying. + * The extra headroom needs to be large enough + * to fit txheader needs. Some dongle driver may + * not need it. + */ + uint nrxpost; /* # rx buffers to keep posted */ + unsigned int rxoffset; /* rxcontrol offset */ + /* add to get dma address of descriptor ring, low 32 bits */ + uint ddoffsetlow; + /* high 32 bits */ + uint ddoffsethigh; + /* add to get dma address of data buffer, low 32 bits */ + uint dataoffsetlow; + /* high 32 bits */ + uint dataoffsethigh; + /* descriptor base need to be aligned or not */ + bool aligndesc_4k; +}; + +/* Check for odd number of 1's */ +static u32 parity32(__le32 data) +{ + /* no swap needed for counting 1's */ + u32 par_data = *(u32 *)&data; + + par_data ^= par_data >> 16; + par_data ^= par_data >> 8; + par_data ^= par_data >> 4; + par_data ^= par_data >> 2; + par_data ^= par_data >> 1; + + return par_data & 1; +} + +static bool dma64_dd_parity(struct dma64desc *dd) +{ + return parity32(dd->addrlow ^ dd->addrhigh ^ dd->ctrl1 ^ dd->ctrl2); +} + +/* descriptor bumping functions */ + +static uint xxd(uint x, uint n) +{ + return x & (n - 1); /* faster than %, but n must be power of 2 */ +} + +static uint txd(struct dma_info *di, uint x) +{ + return xxd(x, di->ntxd); +} + +static uint rxd(struct dma_info *di, uint x) +{ + return xxd(x, di->nrxd); +} + +static uint nexttxd(struct dma_info *di, uint i) +{ + return txd(di, i + 1); +} + +static uint prevtxd(struct dma_info *di, uint i) +{ + return txd(di, i - 1); +} + +static uint nextrxd(struct dma_info *di, uint i) +{ + return rxd(di, i + 1); +} + +static uint ntxdactive(struct dma_info *di, uint h, uint t) +{ + return txd(di, t-h); +} + +static uint nrxdactive(struct dma_info *di, uint h, uint t) +{ + return rxd(di, t-h); +} + +static uint _dma_ctrlflags(struct dma_info *di, uint mask, uint flags) +{ + uint dmactrlflags; + + if (di == NULL) + return 0; + + dmactrlflags = di->dma.dmactrlflags; + dmactrlflags &= ~mask; + dmactrlflags |= flags; + + /* If trying to enable parity, check if parity is actually supported */ + if (dmactrlflags & DMA_CTRL_PEN) { + u32 control; + + control = bcma_read32(di->core, DMA64TXREGOFFS(di, control)); + bcma_write32(di->core, DMA64TXREGOFFS(di, control), + control | D64_XC_PD); + if (bcma_read32(di->core, DMA64TXREGOFFS(di, control)) & + D64_XC_PD) + /* We *can* disable it so it is supported, + * restore control register + */ + bcma_write32(di->core, DMA64TXREGOFFS(di, control), + control); + else + /* Not supported, don't allow it to be enabled */ + dmactrlflags &= ~DMA_CTRL_PEN; + } + + di->dma.dmactrlflags = dmactrlflags; + + return dmactrlflags; +} + +static bool _dma64_addrext(struct dma_info *di, uint ctrl_offset) +{ + u32 w; + bcma_set32(di->core, ctrl_offset, D64_XC_AE); + w = bcma_read32(di->core, ctrl_offset); + bcma_mask32(di->core, ctrl_offset, ~D64_XC_AE); + return (w & D64_XC_AE) == D64_XC_AE; +} + +/* + * return true if this dma engine supports DmaExtendedAddrChanges, + * otherwise false + */ +static bool _dma_isaddrext(struct dma_info *di) +{ + /* DMA64 supports full 32- or 64-bit operation. AE is always valid */ + + /* not all tx or rx channel are available */ + if (di->d64txregbase != 0) { + if (!_dma64_addrext(di, DMA64TXREGOFFS(di, control))) + brcms_dbg_dma(di->core, + "%s: DMA64 tx doesn't have AE set\n", + di->name); + return true; + } else if (di->d64rxregbase != 0) { + if (!_dma64_addrext(di, DMA64RXREGOFFS(di, control))) + brcms_dbg_dma(di->core, + "%s: DMA64 rx doesn't have AE set\n", + di->name); + return true; + } + + return false; +} + +static bool _dma_descriptor_align(struct dma_info *di) +{ + u32 addrl; + + /* Check to see if the descriptors need to be aligned on 4K/8K or not */ + if (di->d64txregbase != 0) { + bcma_write32(di->core, DMA64TXREGOFFS(di, addrlow), 0xff0); + addrl = bcma_read32(di->core, DMA64TXREGOFFS(di, addrlow)); + if (addrl != 0) + return false; + } else if (di->d64rxregbase != 0) { + bcma_write32(di->core, DMA64RXREGOFFS(di, addrlow), 0xff0); + addrl = bcma_read32(di->core, DMA64RXREGOFFS(di, addrlow)); + if (addrl != 0) + return false; + } + return true; +} + +/* + * Descriptor table must start at the DMA hardware dictated alignment, so + * allocated memory must be large enough to support this requirement. + */ +static void *dma_alloc_consistent(struct dma_info *di, uint size, + u16 align_bits, uint *alloced, + dma_addr_t *pap) +{ + if (align_bits) { + u16 align = (1 << align_bits); + if (!IS_ALIGNED(PAGE_SIZE, align)) + size += align; + *alloced = size; + } + return dma_alloc_coherent(di->dmadev, size, pap, GFP_ATOMIC); +} + +static +u8 dma_align_sizetobits(uint size) +{ + u8 bitpos = 0; + while (size >>= 1) + bitpos++; + return bitpos; +} + +/* This function ensures that the DMA descriptor ring will not get allocated + * across Page boundary. If the allocation is done across the page boundary + * at the first time, then it is freed and the allocation is done at + * descriptor ring size aligned location. This will ensure that the ring will + * not cross page boundary + */ +static void *dma_ringalloc(struct dma_info *di, u32 boundary, uint size, + u16 *alignbits, uint *alloced, + dma_addr_t *descpa) +{ + void *va; + u32 desc_strtaddr; + u32 alignbytes = 1 << *alignbits; + + va = dma_alloc_consistent(di, size, *alignbits, alloced, descpa); + + if (NULL == va) + return NULL; + + desc_strtaddr = (u32) roundup((unsigned long)va, alignbytes); + if (((desc_strtaddr + size - 1) & boundary) != (desc_strtaddr + & boundary)) { + *alignbits = dma_align_sizetobits(size); + dma_free_coherent(di->dmadev, size, va, *descpa); + va = dma_alloc_consistent(di, size, *alignbits, + alloced, descpa); + } + return va; +} + +static bool dma64_alloc(struct dma_info *di, uint direction) +{ + u16 size; + uint ddlen; + void *va; + uint alloced = 0; + u16 align; + u16 align_bits; + + ddlen = sizeof(struct dma64desc); + + size = (direction == DMA_TX) ? (di->ntxd * ddlen) : (di->nrxd * ddlen); + align_bits = di->dmadesc_align; + align = (1 << align_bits); + + if (direction == DMA_TX) { + va = dma_ringalloc(di, D64RINGALIGN, size, &align_bits, + &alloced, &di->txdpaorig); + if (va == NULL) { + brcms_dbg_dma(di->core, + "%s: DMA_ALLOC_CONSISTENT(ntxd) failed\n", + di->name); + return false; + } + align = (1 << align_bits); + di->txd64 = (struct dma64desc *) + roundup((unsigned long)va, align); + di->txdalign = (uint) ((s8 *)di->txd64 - (s8 *) va); + di->txdpa = di->txdpaorig + di->txdalign; + di->txdalloc = alloced; + } else { + va = dma_ringalloc(di, D64RINGALIGN, size, &align_bits, + &alloced, &di->rxdpaorig); + if (va == NULL) { + brcms_dbg_dma(di->core, + "%s: DMA_ALLOC_CONSISTENT(nrxd) failed\n", + di->name); + return false; + } + align = (1 << align_bits); + di->rxd64 = (struct dma64desc *) + roundup((unsigned long)va, align); + di->rxdalign = (uint) ((s8 *)di->rxd64 - (s8 *) va); + di->rxdpa = di->rxdpaorig + di->rxdalign; + di->rxdalloc = alloced; + } + + return true; +} + +static bool _dma_alloc(struct dma_info *di, uint direction) +{ + return dma64_alloc(di, direction); +} + +struct dma_pub *dma_attach(char *name, struct brcms_c_info *wlc, + uint txregbase, uint rxregbase, uint ntxd, uint nrxd, + uint rxbufsize, int rxextheadroom, + uint nrxpost, uint rxoffset) +{ + struct si_pub *sih = wlc->hw->sih; + struct bcma_device *core = wlc->hw->d11core; + struct dma_info *di; + u8 rev = core->id.rev; + uint size; + struct si_info *sii = container_of(sih, struct si_info, pub); + + /* allocate private info structure */ + di = kzalloc(sizeof(struct dma_info), GFP_ATOMIC); + if (di == NULL) + return NULL; + + di->dma64 = + ((bcma_aread32(core, BCMA_IOST) & SISF_DMA64) == SISF_DMA64); + + /* init dma reg info */ + di->core = core; + di->d64txregbase = txregbase; + di->d64rxregbase = rxregbase; + + /* + * Default flags (which can be changed by the driver calling + * dma_ctrlflags before enable): For backwards compatibility + * both Rx Overflow Continue and Parity are DISABLED. + */ + _dma_ctrlflags(di, DMA_CTRL_ROC | DMA_CTRL_PEN, 0); + + brcms_dbg_dma(di->core, "%s: %s flags 0x%x ntxd %d nrxd %d " + "rxbufsize %d rxextheadroom %d nrxpost %d rxoffset %d " + "txregbase %u rxregbase %u\n", name, "DMA64", + di->dma.dmactrlflags, ntxd, nrxd, rxbufsize, + rxextheadroom, nrxpost, rxoffset, txregbase, rxregbase); + + /* make a private copy of our callers name */ + strncpy(di->name, name, MAXNAMEL); + di->name[MAXNAMEL - 1] = '\0'; + + di->dmadev = core->dma_dev; + + /* save tunables */ + di->ntxd = (u16) ntxd; + di->nrxd = (u16) nrxd; + + /* the actual dma size doesn't include the extra headroom */ + di->rxextrahdrroom = + (rxextheadroom == -1) ? BCMEXTRAHDROOM : rxextheadroom; + if (rxbufsize > BCMEXTRAHDROOM) + di->rxbufsize = (u16) (rxbufsize - di->rxextrahdrroom); + else + di->rxbufsize = (u16) rxbufsize; + + di->nrxpost = (u16) nrxpost; + di->rxoffset = (u8) rxoffset; + + /* + * figure out the DMA physical address offset for dd and data + * PCI/PCIE: they map silicon backplace address to zero + * based memory, need offset + * Other bus: use zero SI_BUS BIGENDIAN kludge: use sdram + * swapped region for data buffer, not descriptor + */ + di->ddoffsetlow = 0; + di->dataoffsetlow = 0; + /* for pci bus, add offset */ + if (sii->icbus->hosttype == BCMA_HOSTTYPE_PCI) { + /* add offset for pcie with DMA64 bus */ + di->ddoffsetlow = 0; + di->ddoffsethigh = SI_PCIE_DMA_H32; + } + di->dataoffsetlow = di->ddoffsetlow; + di->dataoffsethigh = di->ddoffsethigh; + + /* WAR64450 : DMACtl.Addr ext fields are not supported in SDIOD core. */ + if ((core->id.id == BCMA_CORE_SDIO_DEV) + && ((rev > 0) && (rev <= 2))) + di->addrext = false; + else if ((core->id.id == BCMA_CORE_I2S) && + ((rev == 0) || (rev == 1))) + di->addrext = false; + else + di->addrext = _dma_isaddrext(di); + + /* does the descriptor need to be aligned and if yes, on 4K/8K or not */ + di->aligndesc_4k = _dma_descriptor_align(di); + if (di->aligndesc_4k) { + di->dmadesc_align = D64RINGALIGN_BITS; + if ((ntxd < D64MAXDD / 2) && (nrxd < D64MAXDD / 2)) + /* for smaller dd table, HW relax alignment reqmnt */ + di->dmadesc_align = D64RINGALIGN_BITS - 1; + } else { + di->dmadesc_align = 4; /* 16 byte alignment */ + } + + brcms_dbg_dma(di->core, "DMA descriptor align_needed %d, align %d\n", + di->aligndesc_4k, di->dmadesc_align); + + /* allocate tx packet pointer vector */ + if (ntxd) { + size = ntxd * sizeof(void *); + di->txp = kzalloc(size, GFP_ATOMIC); + if (di->txp == NULL) + goto fail; + } + + /* allocate rx packet pointer vector */ + if (nrxd) { + size = nrxd * sizeof(void *); + di->rxp = kzalloc(size, GFP_ATOMIC); + if (di->rxp == NULL) + goto fail; + } + + /* + * allocate transmit descriptor ring, only need ntxd descriptors + * but it must be aligned + */ + if (ntxd) { + if (!_dma_alloc(di, DMA_TX)) + goto fail; + } + + /* + * allocate receive descriptor ring, only need nrxd descriptors + * but it must be aligned + */ + if (nrxd) { + if (!_dma_alloc(di, DMA_RX)) + goto fail; + } + + if ((di->ddoffsetlow != 0) && !di->addrext) { + if (di->txdpa > SI_PCI_DMA_SZ) { + brcms_dbg_dma(di->core, + "%s: txdpa 0x%x: addrext not supported\n", + di->name, (u32)di->txdpa); + goto fail; + } + if (di->rxdpa > SI_PCI_DMA_SZ) { + brcms_dbg_dma(di->core, + "%s: rxdpa 0x%x: addrext not supported\n", + di->name, (u32)di->rxdpa); + goto fail; + } + } + + /* Initialize AMPDU session */ + brcms_c_ampdu_reset_session(&di->ampdu_session, wlc); + + brcms_dbg_dma(di->core, + "ddoffsetlow 0x%x ddoffsethigh 0x%x dataoffsetlow 0x%x dataoffsethigh 0x%x addrext %d\n", + di->ddoffsetlow, di->ddoffsethigh, + di->dataoffsetlow, di->dataoffsethigh, + di->addrext); + + return (struct dma_pub *) di; + + fail: + dma_detach((struct dma_pub *)di); + return NULL; +} + +static inline void +dma64_dd_upd(struct dma_info *di, struct dma64desc *ddring, + dma_addr_t pa, uint outidx, u32 *flags, u32 bufcount) +{ + u32 ctrl2 = bufcount & D64_CTRL2_BC_MASK; + + /* PCI bus with big(>1G) physical address, use address extension */ + if ((di->dataoffsetlow == 0) || !(pa & PCI32ADDR_HIGH)) { + ddring[outidx].addrlow = cpu_to_le32(pa + di->dataoffsetlow); + ddring[outidx].addrhigh = cpu_to_le32(di->dataoffsethigh); + ddring[outidx].ctrl1 = cpu_to_le32(*flags); + ddring[outidx].ctrl2 = cpu_to_le32(ctrl2); + } else { + /* address extension for 32-bit PCI */ + u32 ae; + + ae = (pa & PCI32ADDR_HIGH) >> PCI32ADDR_HIGH_SHIFT; + pa &= ~PCI32ADDR_HIGH; + + ctrl2 |= (ae << D64_CTRL2_AE_SHIFT) & D64_CTRL2_AE; + ddring[outidx].addrlow = cpu_to_le32(pa + di->dataoffsetlow); + ddring[outidx].addrhigh = cpu_to_le32(di->dataoffsethigh); + ddring[outidx].ctrl1 = cpu_to_le32(*flags); + ddring[outidx].ctrl2 = cpu_to_le32(ctrl2); + } + if (di->dma.dmactrlflags & DMA_CTRL_PEN) { + if (dma64_dd_parity(&ddring[outidx])) + ddring[outidx].ctrl2 = + cpu_to_le32(ctrl2 | D64_CTRL2_PARITY); + } +} + +/* !! may be called with core in reset */ +void dma_detach(struct dma_pub *pub) +{ + struct dma_info *di = (struct dma_info *)pub; + + brcms_dbg_dma(di->core, "%s:\n", di->name); + + /* free dma descriptor rings */ + if (di->txd64) + dma_free_coherent(di->dmadev, di->txdalloc, + ((s8 *)di->txd64 - di->txdalign), + (di->txdpaorig)); + if (di->rxd64) + dma_free_coherent(di->dmadev, di->rxdalloc, + ((s8 *)di->rxd64 - di->rxdalign), + (di->rxdpaorig)); + + /* free packet pointer vectors */ + kfree(di->txp); + kfree(di->rxp); + + /* free our private info structure */ + kfree(di); + +} + +/* initialize descriptor table base address */ +static void +_dma_ddtable_init(struct dma_info *di, uint direction, dma_addr_t pa) +{ + if (!di->aligndesc_4k) { + if (direction == DMA_TX) + di->xmtptrbase = pa; + else + di->rcvptrbase = pa; + } + + if ((di->ddoffsetlow == 0) + || !(pa & PCI32ADDR_HIGH)) { + if (direction == DMA_TX) { + bcma_write32(di->core, DMA64TXREGOFFS(di, addrlow), + pa + di->ddoffsetlow); + bcma_write32(di->core, DMA64TXREGOFFS(di, addrhigh), + di->ddoffsethigh); + } else { + bcma_write32(di->core, DMA64RXREGOFFS(di, addrlow), + pa + di->ddoffsetlow); + bcma_write32(di->core, DMA64RXREGOFFS(di, addrhigh), + di->ddoffsethigh); + } + } else { + /* DMA64 32bits address extension */ + u32 ae; + + /* shift the high bit(s) from pa to ae */ + ae = (pa & PCI32ADDR_HIGH) >> PCI32ADDR_HIGH_SHIFT; + pa &= ~PCI32ADDR_HIGH; + + if (direction == DMA_TX) { + bcma_write32(di->core, DMA64TXREGOFFS(di, addrlow), + pa + di->ddoffsetlow); + bcma_write32(di->core, DMA64TXREGOFFS(di, addrhigh), + di->ddoffsethigh); + bcma_maskset32(di->core, DMA64TXREGOFFS(di, control), + D64_XC_AE, (ae << D64_XC_AE_SHIFT)); + } else { + bcma_write32(di->core, DMA64RXREGOFFS(di, addrlow), + pa + di->ddoffsetlow); + bcma_write32(di->core, DMA64RXREGOFFS(di, addrhigh), + di->ddoffsethigh); + bcma_maskset32(di->core, DMA64RXREGOFFS(di, control), + D64_RC_AE, (ae << D64_RC_AE_SHIFT)); + } + } +} + +static void _dma_rxenable(struct dma_info *di) +{ + uint dmactrlflags = di->dma.dmactrlflags; + u32 control; + + brcms_dbg_dma(di->core, "%s:\n", di->name); + + control = D64_RC_RE | (bcma_read32(di->core, + DMA64RXREGOFFS(di, control)) & + D64_RC_AE); + + if ((dmactrlflags & DMA_CTRL_PEN) == 0) + control |= D64_RC_PD; + + if (dmactrlflags & DMA_CTRL_ROC) + control |= D64_RC_OC; + + bcma_write32(di->core, DMA64RXREGOFFS(di, control), + ((di->rxoffset << D64_RC_RO_SHIFT) | control)); +} + +void dma_rxinit(struct dma_pub *pub) +{ + struct dma_info *di = (struct dma_info *)pub; + + brcms_dbg_dma(di->core, "%s:\n", di->name); + + if (di->nrxd == 0) + return; + + di->rxin = di->rxout = 0; + + /* clear rx descriptor ring */ + memset(di->rxd64, '\0', di->nrxd * sizeof(struct dma64desc)); + + /* DMA engine with out alignment requirement requires table to be inited + * before enabling the engine + */ + if (!di->aligndesc_4k) + _dma_ddtable_init(di, DMA_RX, di->rxdpa); + + _dma_rxenable(di); + + if (di->aligndesc_4k) + _dma_ddtable_init(di, DMA_RX, di->rxdpa); +} + +static struct sk_buff *dma64_getnextrxp(struct dma_info *di, bool forceall) +{ + uint i, curr; + struct sk_buff *rxp; + dma_addr_t pa; + + i = di->rxin; + + /* return if no packets posted */ + if (i == di->rxout) + return NULL; + + curr = + B2I(((bcma_read32(di->core, + DMA64RXREGOFFS(di, status0)) & D64_RS0_CD_MASK) - + di->rcvptrbase) & D64_RS0_CD_MASK, struct dma64desc); + + /* ignore curr if forceall */ + if (!forceall && (i == curr)) + return NULL; + + /* get the packet pointer that corresponds to the rx descriptor */ + rxp = di->rxp[i]; + di->rxp[i] = NULL; + + pa = le32_to_cpu(di->rxd64[i].addrlow) - di->dataoffsetlow; + + /* clear this packet from the descriptor ring */ + dma_unmap_single(di->dmadev, pa, di->rxbufsize, DMA_FROM_DEVICE); + + di->rxd64[i].addrlow = cpu_to_le32(0xdeadbeef); + di->rxd64[i].addrhigh = cpu_to_le32(0xdeadbeef); + + di->rxin = nextrxd(di, i); + + return rxp; +} + +static struct sk_buff *_dma_getnextrxp(struct dma_info *di, bool forceall) +{ + if (di->nrxd == 0) + return NULL; + + return dma64_getnextrxp(di, forceall); +} + +/* + * !! rx entry routine + * returns the number packages in the next frame, or 0 if there are no more + * if DMA_CTRL_RXMULTI is defined, DMA scattering(multiple buffers) is + * supported with pkts chain + * otherwise, it's treated as giant pkt and will be tossed. + * The DMA scattering starts with normal DMA header, followed by first + * buffer data. After it reaches the max size of buffer, the data continues + * in next DMA descriptor buffer WITHOUT DMA header + */ +int dma_rx(struct dma_pub *pub, struct sk_buff_head *skb_list) +{ + struct dma_info *di = (struct dma_info *)pub; + struct sk_buff_head dma_frames; + struct sk_buff *p, *next; + uint len; + uint pkt_len; + int resid = 0; + int pktcnt = 1; + + skb_queue_head_init(&dma_frames); + next_frame: + p = _dma_getnextrxp(di, false); + if (p == NULL) + return 0; + + len = le16_to_cpu(*(__le16 *) (p->data)); + brcms_dbg_dma(di->core, "%s: dma_rx len %d\n", di->name, len); + dma_spin_for_len(len, p); + + /* set actual length */ + pkt_len = min((di->rxoffset + len), di->rxbufsize); + __skb_trim(p, pkt_len); + skb_queue_tail(&dma_frames, p); + resid = len - (di->rxbufsize - di->rxoffset); + + /* check for single or multi-buffer rx */ + if (resid > 0) { + while ((resid > 0) && (p = _dma_getnextrxp(di, false))) { + pkt_len = min_t(uint, resid, di->rxbufsize); + __skb_trim(p, pkt_len); + skb_queue_tail(&dma_frames, p); + resid -= di->rxbufsize; + pktcnt++; + } + +#ifdef DEBUG + if (resid > 0) { + uint cur; + cur = + B2I(((bcma_read32(di->core, + DMA64RXREGOFFS(di, status0)) & + D64_RS0_CD_MASK) - di->rcvptrbase) & + D64_RS0_CD_MASK, struct dma64desc); + brcms_dbg_dma(di->core, + "rxin %d rxout %d, hw_curr %d\n", + di->rxin, di->rxout, cur); + } +#endif /* DEBUG */ + + if ((di->dma.dmactrlflags & DMA_CTRL_RXMULTI) == 0) { + brcms_dbg_dma(di->core, "%s: bad frame length (%d)\n", + di->name, len); + skb_queue_walk_safe(&dma_frames, p, next) { + skb_unlink(p, &dma_frames); + brcmu_pkt_buf_free_skb(p); + } + di->dma.rxgiants++; + pktcnt = 1; + goto next_frame; + } + } + + skb_queue_splice_tail(&dma_frames, skb_list); + return pktcnt; +} + +static bool dma64_rxidle(struct dma_info *di) +{ + brcms_dbg_dma(di->core, "%s:\n", di->name); + + if (di->nrxd == 0) + return true; + + return ((bcma_read32(di->core, + DMA64RXREGOFFS(di, status0)) & D64_RS0_CD_MASK) == + (bcma_read32(di->core, DMA64RXREGOFFS(di, ptr)) & + D64_RS0_CD_MASK)); +} + +static bool dma64_txidle(struct dma_info *di) +{ + if (di->ntxd == 0) + return true; + + return ((bcma_read32(di->core, + DMA64TXREGOFFS(di, status0)) & D64_XS0_CD_MASK) == + (bcma_read32(di->core, DMA64TXREGOFFS(di, ptr)) & + D64_XS0_CD_MASK)); +} + +/* + * post receive buffers + * Return false if refill failed completely or dma mapping failed. The ring + * is empty, which will stall the rx dma and user might want to call rxfill + * again asap. This is unlikely to happen on a memory-rich NIC, but often on + * memory-constrained dongle. + */ +bool dma_rxfill(struct dma_pub *pub) +{ + struct dma_info *di = (struct dma_info *)pub; + struct sk_buff *p; + u16 rxin, rxout; + u32 flags = 0; + uint n; + uint i; + dma_addr_t pa; + uint extra_offset = 0; + bool ring_empty; + + ring_empty = false; + + /* + * Determine how many receive buffers we're lacking + * from the full complement, allocate, initialize, + * and post them, then update the chip rx lastdscr. + */ + + rxin = di->rxin; + rxout = di->rxout; + + n = di->nrxpost - nrxdactive(di, rxin, rxout); + + brcms_dbg_dma(di->core, "%s: post %d\n", di->name, n); + + if (di->rxbufsize > BCMEXTRAHDROOM) + extra_offset = di->rxextrahdrroom; + + for (i = 0; i < n; i++) { + /* + * the di->rxbufsize doesn't include the extra headroom, + * we need to add it to the size to be allocated + */ + p = brcmu_pkt_buf_get_skb(di->rxbufsize + extra_offset); + + if (p == NULL) { + brcms_dbg_dma(di->core, "%s: out of rxbufs\n", + di->name); + if (i == 0 && dma64_rxidle(di)) { + brcms_dbg_dma(di->core, "%s: ring is empty !\n", + di->name); + ring_empty = true; + } + di->dma.rxnobuf++; + break; + } + /* reserve an extra headroom, if applicable */ + if (extra_offset) + skb_pull(p, extra_offset); + + /* Do a cached write instead of uncached write since DMA_MAP + * will flush the cache. + */ + *(u32 *) (p->data) = 0; + + pa = dma_map_single(di->dmadev, p->data, di->rxbufsize, + DMA_FROM_DEVICE); + if (dma_mapping_error(di->dmadev, pa)) + return false; + + /* save the free packet pointer */ + di->rxp[rxout] = p; + + /* reset flags for each descriptor */ + flags = 0; + if (rxout == (di->nrxd - 1)) + flags = D64_CTRL1_EOT; + + dma64_dd_upd(di, di->rxd64, pa, rxout, &flags, + di->rxbufsize); + rxout = nextrxd(di, rxout); + } + + di->rxout = rxout; + + /* update the chip lastdscr pointer */ + bcma_write32(di->core, DMA64RXREGOFFS(di, ptr), + di->rcvptrbase + I2B(rxout, struct dma64desc)); + + return ring_empty; +} + +void dma_rxreclaim(struct dma_pub *pub) +{ + struct dma_info *di = (struct dma_info *)pub; + struct sk_buff *p; + + brcms_dbg_dma(di->core, "%s:\n", di->name); + + while ((p = _dma_getnextrxp(di, true))) + brcmu_pkt_buf_free_skb(p); +} + +void dma_counterreset(struct dma_pub *pub) +{ + /* reset all software counters */ + pub->rxgiants = 0; + pub->rxnobuf = 0; + pub->txnobuf = 0; +} + +/* get the address of the var in order to change later */ +unsigned long dma_getvar(struct dma_pub *pub, const char *name) +{ + struct dma_info *di = (struct dma_info *)pub; + + if (!strcmp(name, "&txavail")) + return (unsigned long)&(di->dma.txavail); + return 0; +} + +/* 64-bit DMA functions */ + +void dma_txinit(struct dma_pub *pub) +{ + struct dma_info *di = (struct dma_info *)pub; + u32 control = D64_XC_XE; + + brcms_dbg_dma(di->core, "%s:\n", di->name); + + if (di->ntxd == 0) + return; + + di->txin = di->txout = 0; + di->dma.txavail = di->ntxd - 1; + + /* clear tx descriptor ring */ + memset(di->txd64, '\0', (di->ntxd * sizeof(struct dma64desc))); + + /* DMA engine with out alignment requirement requires table to be inited + * before enabling the engine + */ + if (!di->aligndesc_4k) + _dma_ddtable_init(di, DMA_TX, di->txdpa); + + if ((di->dma.dmactrlflags & DMA_CTRL_PEN) == 0) + control |= D64_XC_PD; + bcma_set32(di->core, DMA64TXREGOFFS(di, control), control); + + /* DMA engine with alignment requirement requires table to be inited + * before enabling the engine + */ + if (di->aligndesc_4k) + _dma_ddtable_init(di, DMA_TX, di->txdpa); +} + +void dma_txsuspend(struct dma_pub *pub) +{ + struct dma_info *di = (struct dma_info *)pub; + + brcms_dbg_dma(di->core, "%s:\n", di->name); + + if (di->ntxd == 0) + return; + + bcma_set32(di->core, DMA64TXREGOFFS(di, control), D64_XC_SE); +} + +void dma_txresume(struct dma_pub *pub) +{ + struct dma_info *di = (struct dma_info *)pub; + + brcms_dbg_dma(di->core, "%s:\n", di->name); + + if (di->ntxd == 0) + return; + + bcma_mask32(di->core, DMA64TXREGOFFS(di, control), ~D64_XC_SE); +} + +bool dma_txsuspended(struct dma_pub *pub) +{ + struct dma_info *di = (struct dma_info *)pub; + + return (di->ntxd == 0) || + ((bcma_read32(di->core, + DMA64TXREGOFFS(di, control)) & D64_XC_SE) == + D64_XC_SE); +} + +void dma_txreclaim(struct dma_pub *pub, enum txd_range range) +{ + struct dma_info *di = (struct dma_info *)pub; + struct sk_buff *p; + + brcms_dbg_dma(di->core, "%s: %s\n", + di->name, + range == DMA_RANGE_ALL ? "all" : + range == DMA_RANGE_TRANSMITTED ? "transmitted" : + "transferred"); + + if (di->txin == di->txout) + return; + + while ((p = dma_getnexttxp(pub, range))) { + /* For unframed data, we don't have any packets to free */ + if (!(di->dma.dmactrlflags & DMA_CTRL_UNFRAMED)) + brcmu_pkt_buf_free_skb(p); + } +} + +bool dma_txreset(struct dma_pub *pub) +{ + struct dma_info *di = (struct dma_info *)pub; + u32 status; + + if (di->ntxd == 0) + return true; + + /* suspend tx DMA first */ + bcma_write32(di->core, DMA64TXREGOFFS(di, control), D64_XC_SE); + SPINWAIT(((status = + (bcma_read32(di->core, DMA64TXREGOFFS(di, status0)) & + D64_XS0_XS_MASK)) != D64_XS0_XS_DISABLED) && + (status != D64_XS0_XS_IDLE) && (status != D64_XS0_XS_STOPPED), + 10000); + + bcma_write32(di->core, DMA64TXREGOFFS(di, control), 0); + SPINWAIT(((status = + (bcma_read32(di->core, DMA64TXREGOFFS(di, status0)) & + D64_XS0_XS_MASK)) != D64_XS0_XS_DISABLED), 10000); + + /* wait for the last transaction to complete */ + udelay(300); + + return status == D64_XS0_XS_DISABLED; +} + +bool dma_rxreset(struct dma_pub *pub) +{ + struct dma_info *di = (struct dma_info *)pub; + u32 status; + + if (di->nrxd == 0) + return true; + + bcma_write32(di->core, DMA64RXREGOFFS(di, control), 0); + SPINWAIT(((status = + (bcma_read32(di->core, DMA64RXREGOFFS(di, status0)) & + D64_RS0_RS_MASK)) != D64_RS0_RS_DISABLED), 10000); + + return status == D64_RS0_RS_DISABLED; +} + +static void dma_txenq(struct dma_info *di, struct sk_buff *p) +{ + unsigned char *data; + uint len; + u16 txout; + u32 flags = 0; + dma_addr_t pa; + + txout = di->txout; + + if (WARN_ON(nexttxd(di, txout) == di->txin)) + return; + + /* + * obtain and initialize transmit descriptor entry. + */ + data = p->data; + len = p->len; + + /* get physical address of buffer start */ + pa = dma_map_single(di->dmadev, data, len, DMA_TO_DEVICE); + /* if mapping failed, free skb */ + if (dma_mapping_error(di->dmadev, pa)) { + brcmu_pkt_buf_free_skb(p); + return; + } + /* With a DMA segment list, Descriptor table is filled + * using the segment list instead of looping over + * buffers in multi-chain DMA. Therefore, EOF for SGLIST + * is when end of segment list is reached. + */ + flags = D64_CTRL1_SOF | D64_CTRL1_IOC | D64_CTRL1_EOF; + if (txout == (di->ntxd - 1)) + flags |= D64_CTRL1_EOT; + + dma64_dd_upd(di, di->txd64, pa, txout, &flags, len); + + txout = nexttxd(di, txout); + + /* save the packet */ + di->txp[prevtxd(di, txout)] = p; + + /* bump the tx descriptor index */ + di->txout = txout; +} + +static void ampdu_finalize(struct dma_info *di) +{ + struct brcms_ampdu_session *session = &di->ampdu_session; + struct sk_buff *p; + + trace_brcms_ampdu_session(&session->wlc->hw->d11core->dev, + session->max_ampdu_len, + session->max_ampdu_frames, + session->ampdu_len, + skb_queue_len(&session->skb_list), + session->dma_len); + + if (WARN_ON(skb_queue_empty(&session->skb_list))) + return; + + brcms_c_ampdu_finalize(session); + + while (!skb_queue_empty(&session->skb_list)) { + p = skb_dequeue(&session->skb_list); + dma_txenq(di, p); + } + + bcma_write32(di->core, DMA64TXREGOFFS(di, ptr), + di->xmtptrbase + I2B(di->txout, struct dma64desc)); + brcms_c_ampdu_reset_session(session, session->wlc); +} + +static void prep_ampdu_frame(struct dma_info *di, struct sk_buff *p) +{ + struct brcms_ampdu_session *session = &di->ampdu_session; + int ret; + + ret = brcms_c_ampdu_add_frame(session, p); + if (ret == -ENOSPC) { + /* + * AMPDU cannot accomodate this frame. Close out the in- + * progress AMPDU session and start a new one. + */ + ampdu_finalize(di); + ret = brcms_c_ampdu_add_frame(session, p); + } + + WARN_ON(ret); +} + +/* Update count of available tx descriptors based on current DMA state */ +static void dma_update_txavail(struct dma_info *di) +{ + /* + * Available space is number of descriptors less the number of + * active descriptors and the number of queued AMPDU frames. + */ + di->dma.txavail = di->ntxd - ntxdactive(di, di->txin, di->txout) - + skb_queue_len(&di->ampdu_session.skb_list) - 1; +} + +/* + * !! tx entry routine + * WARNING: call must check the return value for error. + * the error(toss frames) could be fatal and cause many subsequent hard + * to debug problems + */ +int dma_txfast(struct brcms_c_info *wlc, struct dma_pub *pub, + struct sk_buff *p) +{ + struct dma_info *di = (struct dma_info *)pub; + struct brcms_ampdu_session *session = &di->ampdu_session; + struct ieee80211_tx_info *tx_info; + bool is_ampdu; + + /* no use to transmit a zero length packet */ + if (p->len == 0) + return 0; + + /* return nonzero if out of tx descriptors */ + if (di->dma.txavail == 0 || nexttxd(di, di->txout) == di->txin) + goto outoftxd; + + tx_info = IEEE80211_SKB_CB(p); + is_ampdu = tx_info->flags & IEEE80211_TX_CTL_AMPDU; + if (is_ampdu) + prep_ampdu_frame(di, p); + else + dma_txenq(di, p); + + /* tx flow control */ + dma_update_txavail(di); + + /* kick the chip */ + if (is_ampdu) { + /* + * Start sending data if we've got a full AMPDU, there's + * no more space in the DMA ring, or the ring isn't + * currently transmitting. + */ + if (skb_queue_len(&session->skb_list) == session->max_ampdu_frames || + di->dma.txavail == 0 || dma64_txidle(di)) + ampdu_finalize(di); + } else { + bcma_write32(di->core, DMA64TXREGOFFS(di, ptr), + di->xmtptrbase + I2B(di->txout, struct dma64desc)); + } + + return 0; + + outoftxd: + brcms_dbg_dma(di->core, "%s: out of txds !!!\n", di->name); + brcmu_pkt_buf_free_skb(p); + di->dma.txavail = 0; + di->dma.txnobuf++; + return -ENOSPC; +} + +void dma_txflush(struct dma_pub *pub) +{ + struct dma_info *di = (struct dma_info *)pub; + struct brcms_ampdu_session *session = &di->ampdu_session; + + if (!skb_queue_empty(&session->skb_list)) + ampdu_finalize(di); +} + +int dma_txpending(struct dma_pub *pub) +{ + struct dma_info *di = (struct dma_info *)pub; + return ntxdactive(di, di->txin, di->txout); +} + +/* + * If we have an active AMPDU session and are not transmitting, + * this function will force tx to start. + */ +void dma_kick_tx(struct dma_pub *pub) +{ + struct dma_info *di = (struct dma_info *)pub; + struct brcms_ampdu_session *session = &di->ampdu_session; + + if (!skb_queue_empty(&session->skb_list) && dma64_txidle(di)) + ampdu_finalize(di); +} + +/* + * Reclaim next completed txd (txds if using chained buffers) in the range + * specified and return associated packet. + * If range is DMA_RANGE_TRANSMITTED, reclaim descriptors that have be + * transmitted as noted by the hardware "CurrDescr" pointer. + * If range is DMA_RANGE_TRANSFERED, reclaim descriptors that have be + * transferred by the DMA as noted by the hardware "ActiveDescr" pointer. + * If range is DMA_RANGE_ALL, reclaim all txd(s) posted to the ring and + * return associated packet regardless of the value of hardware pointers. + */ +struct sk_buff *dma_getnexttxp(struct dma_pub *pub, enum txd_range range) +{ + struct dma_info *di = (struct dma_info *)pub; + u16 start, end, i; + u16 active_desc; + struct sk_buff *txp; + + brcms_dbg_dma(di->core, "%s: %s\n", + di->name, + range == DMA_RANGE_ALL ? "all" : + range == DMA_RANGE_TRANSMITTED ? "transmitted" : + "transferred"); + + if (di->ntxd == 0) + return NULL; + + txp = NULL; + + start = di->txin; + if (range == DMA_RANGE_ALL) + end = di->txout; + else { + end = (u16) (B2I(((bcma_read32(di->core, + DMA64TXREGOFFS(di, status0)) & + D64_XS0_CD_MASK) - di->xmtptrbase) & + D64_XS0_CD_MASK, struct dma64desc)); + + if (range == DMA_RANGE_TRANSFERED) { + active_desc = + (u16)(bcma_read32(di->core, + DMA64TXREGOFFS(di, status1)) & + D64_XS1_AD_MASK); + active_desc = + (active_desc - di->xmtptrbase) & D64_XS0_CD_MASK; + active_desc = B2I(active_desc, struct dma64desc); + if (end != active_desc) + end = prevtxd(di, active_desc); + } + } + + if ((start == 0) && (end > di->txout)) + goto bogus; + + for (i = start; i != end && !txp; i = nexttxd(di, i)) { + dma_addr_t pa; + uint size; + + pa = le32_to_cpu(di->txd64[i].addrlow) - di->dataoffsetlow; + + size = + (le32_to_cpu(di->txd64[i].ctrl2) & + D64_CTRL2_BC_MASK); + + di->txd64[i].addrlow = cpu_to_le32(0xdeadbeef); + di->txd64[i].addrhigh = cpu_to_le32(0xdeadbeef); + + txp = di->txp[i]; + di->txp[i] = NULL; + + dma_unmap_single(di->dmadev, pa, size, DMA_TO_DEVICE); + } + + di->txin = i; + + /* tx flow control */ + dma_update_txavail(di); + + return txp; + + bogus: + brcms_dbg_dma(di->core, "bogus curr: start %d end %d txout %d\n", + start, end, di->txout); + return NULL; +} + +/* + * Mac80211 initiated actions sometimes require packets in the DMA queue to be + * modified. The modified portion of the packet is not under control of the DMA + * engine. This function calls a caller-supplied function for each packet in + * the caller specified dma chain. + */ +void dma_walk_packets(struct dma_pub *dmah, void (*callback_fnc) + (void *pkt, void *arg_a), void *arg_a) +{ + struct dma_info *di = (struct dma_info *) dmah; + uint i = di->txin; + uint end = di->txout; + struct sk_buff *skb; + struct ieee80211_tx_info *tx_info; + + while (i != end) { + skb = di->txp[i]; + if (skb != NULL) { + tx_info = (struct ieee80211_tx_info *)skb->cb; + (callback_fnc)(tx_info, arg_a); + } + i = nexttxd(di, i); + } +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/dma.h b/drivers/net/wireless/brcm80211/brcmsmac/dma.h new file mode 100644 index 0000000..ff5b80b --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/dma.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_DMA_H_ +#define _BRCM_DMA_H_ + +#include +#include +#include "types.h" /* forward structure declarations */ + +/* map/unmap direction */ +#define DMA_TX 1 /* TX direction for DMA */ +#define DMA_RX 2 /* RX direction for DMA */ + +/* DMA structure: + * support two DMA engines: 32 bits address or 64 bit addressing + * basic DMA register set is per channel(transmit or receive) + * a pair of channels is defined for convenience + */ + +/* 32 bits addressing */ + +struct dma32diag { /* diag access */ + u32 fifoaddr; /* diag address */ + u32 fifodatalow; /* low 32bits of data */ + u32 fifodatahigh; /* high 32bits of data */ + u32 pad; /* reserved */ +}; + +/* 64 bits addressing */ + +/* dma registers per channel(xmt or rcv) */ +struct dma64regs { + u32 control; /* enable, et al */ + u32 ptr; /* last descriptor posted to chip */ + u32 addrlow; /* desc ring base address low 32-bits (8K aligned) */ + u32 addrhigh; /* desc ring base address bits 63:32 (8K aligned) */ + u32 status0; /* current descriptor, xmt state */ + u32 status1; /* active descriptor, xmt error */ +}; + +/* range param for dma_getnexttxp() and dma_txreclaim */ +enum txd_range { + DMA_RANGE_ALL = 1, + DMA_RANGE_TRANSMITTED, + DMA_RANGE_TRANSFERED +}; + +/* + * Exported data structure (read-only) + */ +/* export structure */ +struct dma_pub { + uint txavail; /* # free tx descriptors */ + uint dmactrlflags; /* dma control flags */ + + /* rx error counters */ + uint rxgiants; /* rx giant frames */ + uint rxnobuf; /* rx out of dma descriptors */ + /* tx error counters */ + uint txnobuf; /* tx out of dma descriptors */ +}; + +extern struct dma_pub *dma_attach(char *name, struct brcms_c_info *wlc, + uint txregbase, uint rxregbase, + uint ntxd, uint nrxd, + uint rxbufsize, int rxextheadroom, + uint nrxpost, uint rxoffset); + +void dma_rxinit(struct dma_pub *pub); +int dma_rx(struct dma_pub *pub, struct sk_buff_head *skb_list); +bool dma_rxfill(struct dma_pub *pub); +bool dma_rxreset(struct dma_pub *pub); +bool dma_txreset(struct dma_pub *pub); +void dma_txinit(struct dma_pub *pub); +int dma_txfast(struct brcms_c_info *wlc, struct dma_pub *pub, + struct sk_buff *p0); +void dma_txflush(struct dma_pub *pub); +int dma_txpending(struct dma_pub *pub); +void dma_kick_tx(struct dma_pub *pub); +void dma_txsuspend(struct dma_pub *pub); +bool dma_txsuspended(struct dma_pub *pub); +void dma_txresume(struct dma_pub *pub); +void dma_txreclaim(struct dma_pub *pub, enum txd_range range); +void dma_rxreclaim(struct dma_pub *pub); +void dma_detach(struct dma_pub *pub); +unsigned long dma_getvar(struct dma_pub *pub, const char *name); +struct sk_buff *dma_getnexttxp(struct dma_pub *pub, enum txd_range range); +void dma_counterreset(struct dma_pub *pub); + +void dma_walk_packets(struct dma_pub *dmah, void (*callback_fnc) + (void *pkt, void *arg_a), void *arg_a); + +/* + * DMA(Bug) on bcm47xx chips seems to declare that the packet is ready, but + * the packet length is not updated yet (by DMA) on the expected time. + * Workaround is to hold processor till DMA updates the length, and stay off + * the bus to allow DMA update the length in buffer + */ +static inline void dma_spin_for_len(uint len, struct sk_buff *head) +{ +#if defined(CONFIG_BCM47XX) + if (!len) { + while (!(len = *(u16 *) KSEG1ADDR(head->data))) + udelay(1); + + *(u16 *) (head->data) = cpu_to_le16((u16) len); + } +#endif /* defined(CONFIG_BCM47XX) */ +} + +#endif /* _BRCM_DMA_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/led.c b/drivers/net/wireless/brcm80211/brcmsmac/led.c new file mode 100644 index 0000000..74b17ce --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/led.c @@ -0,0 +1,126 @@ +#include +#include +#include + +#include "mac80211_if.h" +#include "pub.h" +#include "main.h" +#include "led.h" + + /* number of leds */ +#define BRCMS_LED_NO 4 + /* behavior mask */ +#define BRCMS_LED_BEH_MASK 0x7f + /* activelow (polarity) bit */ +#define BRCMS_LED_AL_MASK 0x80 + /* radio enabled */ +#define BRCMS_LED_RADIO 3 + +static void brcms_radio_led_ctrl(struct brcms_info *wl, bool state) +{ + if (wl->radio_led.gpio == -1) + return; + + if (wl->radio_led.active_low) + state = !state; + + if (state) + gpio_set_value(wl->radio_led.gpio, 1); + else + gpio_set_value(wl->radio_led.gpio, 0); +} + + +/* Callback from the LED subsystem. */ +static void brcms_led_brightness_set(struct led_classdev *led_dev, + enum led_brightness brightness) +{ + struct brcms_info *wl = container_of(led_dev, + struct brcms_info, led_dev); + brcms_radio_led_ctrl(wl, brightness); +} + +void brcms_led_unregister(struct brcms_info *wl) +{ + if (wl->led_dev.dev) + led_classdev_unregister(&wl->led_dev); + if (wl->radio_led.gpio != -1) + gpio_free(wl->radio_led.gpio); +} + +int brcms_led_register(struct brcms_info *wl) +{ + int i, err; + struct brcms_led *radio_led = &wl->radio_led; + /* get CC core */ + struct bcma_drv_cc *cc_drv = &wl->wlc->hw->d11core->bus->drv_cc; + struct gpio_chip *bcma_gpio = &cc_drv->gpio; + struct ssb_sprom *sprom = &wl->wlc->hw->d11core->bus->sprom; + u8 *leds[] = { &sprom->gpio0, + &sprom->gpio1, + &sprom->gpio2, + &sprom->gpio3 }; + unsigned gpio = -1; + bool active_low = false; + + /* none by default */ + radio_led->gpio = -1; + radio_led->active_low = false; + + if (!bcma_gpio || !gpio_is_valid(bcma_gpio->base)) + return -ENODEV; + + /* find radio enabled LED */ + for (i = 0; i < BRCMS_LED_NO; i++) { + u8 led = *leds[i]; + if ((led & BRCMS_LED_BEH_MASK) == BRCMS_LED_RADIO) { + gpio = bcma_gpio->base + i; + if (led & BRCMS_LED_AL_MASK) + active_low = true; + break; + } + } + + if (gpio == -1 || !gpio_is_valid(gpio)) + return -ENODEV; + + /* request and configure LED gpio */ + err = gpio_request_one(gpio, + active_low ? GPIOF_OUT_INIT_HIGH + : GPIOF_OUT_INIT_LOW, + "radio on"); + if (err) { + wiphy_err(wl->wiphy, "requesting led gpio %d failed (err: %d)\n", + gpio, err); + return err; + } + err = gpio_direction_output(gpio, 1); + if (err) { + wiphy_err(wl->wiphy, "cannot set led gpio %d to output (err: %d)\n", + gpio, err); + return err; + } + + snprintf(wl->radio_led.name, sizeof(wl->radio_led.name), + "brcmsmac-%s:radio", wiphy_name(wl->wiphy)); + + wl->led_dev.name = wl->radio_led.name; + wl->led_dev.default_trigger = + ieee80211_get_radio_led_name(wl->pub->ieee_hw); + wl->led_dev.brightness_set = brcms_led_brightness_set; + err = led_classdev_register(wiphy_dev(wl->wiphy), &wl->led_dev); + + if (err) { + wiphy_err(wl->wiphy, "cannot register led device: %s (err: %d)\n", + wl->radio_led.name, err); + return err; + } + + wiphy_info(wl->wiphy, "registered radio enabled led device: %s gpio: %d\n", + wl->radio_led.name, + gpio); + radio_led->gpio = gpio; + radio_led->active_low = active_low; + + return 0; +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/led.h b/drivers/net/wireless/brcm80211/brcmsmac/led.h new file mode 100644 index 0000000..09a5bc0 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/led.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_LED_H_ +#define _BRCM_LED_H_ +struct brcms_led { + char name[32]; + unsigned gpio; + bool active_low; +}; + +#ifdef CPTCFG_BCMA_DRIVER_GPIO +void brcms_led_unregister(struct brcms_info *wl); +int brcms_led_register(struct brcms_info *wl); +#else +static inline void brcms_led_unregister(struct brcms_info *wl) {}; +static inline int brcms_led_register(struct brcms_info *wl) +{ + return -ENOTSUPP; +}; +#endif + +#endif /* _BRCM_LED_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/mac80211_if.c b/drivers/net/wireless/brcm80211/brcmsmac/mac80211_if.c new file mode 100644 index 0000000..5024ad8 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/mac80211_if.c @@ -0,0 +1,1707 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * Copyright (c) 2013 Hauke Mehrtens + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define __UNDEF_NO_VERSION__ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include "phy/phy_int.h" +#include "d11.h" +#include "channel.h" +#include "scb.h" +#include "pub.h" +#include "ucode_loader.h" +#include "mac80211_if.h" +#include "main.h" +#include "debug.h" +#include "led.h" + +#define N_TX_QUEUES 4 /* #tx queues on mac80211<->driver interface */ +#define BRCMS_FLUSH_TIMEOUT 500 /* msec */ + +/* Flags we support */ +#define MAC_FILTERS (FIF_PROMISC_IN_BSS | \ + FIF_ALLMULTI | \ + FIF_FCSFAIL | \ + FIF_CONTROL | \ + FIF_OTHER_BSS | \ + FIF_BCN_PRBRESP_PROMISC | \ + FIF_PSPOLL) + +#define CHAN2GHZ(channel, freqency, chflags) { \ + .band = IEEE80211_BAND_2GHZ, \ + .center_freq = (freqency), \ + .hw_value = (channel), \ + .flags = chflags, \ + .max_antenna_gain = 0, \ + .max_power = 19, \ +} + +#define CHAN5GHZ(channel, chflags) { \ + .band = IEEE80211_BAND_5GHZ, \ + .center_freq = 5000 + 5*(channel), \ + .hw_value = (channel), \ + .flags = chflags, \ + .max_antenna_gain = 0, \ + .max_power = 21, \ +} + +#define RATE(rate100m, _flags) { \ + .bitrate = (rate100m), \ + .flags = (_flags), \ + .hw_value = (rate100m / 5), \ +} + +struct firmware_hdr { + __le32 offset; + __le32 len; + __le32 idx; +}; + +static const char * const brcms_firmwares[MAX_FW_IMAGES] = { + "brcm/bcm43xx", + NULL +}; + +static int n_adapters_found; + +MODULE_AUTHOR("Broadcom Corporation"); +MODULE_DESCRIPTION("Broadcom 802.11n wireless LAN driver."); +MODULE_SUPPORTED_DEVICE("Broadcom 802.11n WLAN cards"); +MODULE_LICENSE("Dual BSD/GPL"); +/* This needs to be adjusted when brcms_firmwares changes */ +MODULE_FIRMWARE("brcm/bcm43xx-0.fw"); +MODULE_FIRMWARE("brcm/bcm43xx_hdr-0.fw"); + +/* recognized BCMA Core IDs */ +static struct bcma_device_id brcms_coreid_table[] = { + BCMA_CORE(BCMA_MANUF_BCM, BCMA_CORE_80211, 17, BCMA_ANY_CLASS), + BCMA_CORE(BCMA_MANUF_BCM, BCMA_CORE_80211, 23, BCMA_ANY_CLASS), + BCMA_CORE(BCMA_MANUF_BCM, BCMA_CORE_80211, 24, BCMA_ANY_CLASS), + BCMA_CORETABLE_END +}; +MODULE_DEVICE_TABLE(bcma, brcms_coreid_table); + +#if defined(CPTCFG_BRCMDBG) +/* + * Module parameter for setting the debug message level. Available + * flags are specified by the BRCM_DL_* macros in + * drivers/net/wireless/brcm80211/include/defs.h. + */ +module_param_named(debug, brcm_msg_level, uint, S_IRUGO | S_IWUSR); +#endif + +static struct ieee80211_channel brcms_2ghz_chantable[] = { + CHAN2GHZ(1, 2412, IEEE80211_CHAN_NO_HT40MINUS), + CHAN2GHZ(2, 2417, IEEE80211_CHAN_NO_HT40MINUS), + CHAN2GHZ(3, 2422, IEEE80211_CHAN_NO_HT40MINUS), + CHAN2GHZ(4, 2427, IEEE80211_CHAN_NO_HT40MINUS), + CHAN2GHZ(5, 2432, 0), + CHAN2GHZ(6, 2437, 0), + CHAN2GHZ(7, 2442, 0), + CHAN2GHZ(8, 2447, IEEE80211_CHAN_NO_HT40PLUS), + CHAN2GHZ(9, 2452, IEEE80211_CHAN_NO_HT40PLUS), + CHAN2GHZ(10, 2457, IEEE80211_CHAN_NO_HT40PLUS), + CHAN2GHZ(11, 2462, IEEE80211_CHAN_NO_HT40PLUS), + CHAN2GHZ(12, 2467, + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_NO_HT40PLUS), + CHAN2GHZ(13, 2472, + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_NO_HT40PLUS), + CHAN2GHZ(14, 2484, + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_NO_HT40PLUS | IEEE80211_CHAN_NO_HT40MINUS | + IEEE80211_CHAN_NO_OFDM) +}; + +static struct ieee80211_channel brcms_5ghz_nphy_chantable[] = { + /* UNII-1 */ + CHAN5GHZ(36, IEEE80211_CHAN_NO_HT40MINUS), + CHAN5GHZ(40, IEEE80211_CHAN_NO_HT40PLUS), + CHAN5GHZ(44, IEEE80211_CHAN_NO_HT40MINUS), + CHAN5GHZ(48, IEEE80211_CHAN_NO_HT40PLUS), + /* UNII-2 */ + CHAN5GHZ(52, + IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_HT40MINUS), + CHAN5GHZ(56, + IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_HT40PLUS), + CHAN5GHZ(60, + IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_HT40MINUS), + CHAN5GHZ(64, + IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_HT40PLUS), + /* MID */ + CHAN5GHZ(100, + IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_HT40MINUS), + CHAN5GHZ(104, + IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_HT40PLUS), + CHAN5GHZ(108, + IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_HT40MINUS), + CHAN5GHZ(112, + IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_HT40PLUS), + CHAN5GHZ(116, + IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_HT40MINUS), + CHAN5GHZ(120, + IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_HT40PLUS), + CHAN5GHZ(124, + IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_HT40MINUS), + CHAN5GHZ(128, + IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_HT40PLUS), + CHAN5GHZ(132, + IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_HT40MINUS), + CHAN5GHZ(136, + IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_HT40PLUS), + CHAN5GHZ(140, + IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN | IEEE80211_CHAN_NO_HT40PLUS | + IEEE80211_CHAN_NO_HT40MINUS), + /* UNII-3 */ + CHAN5GHZ(149, IEEE80211_CHAN_NO_HT40MINUS), + CHAN5GHZ(153, IEEE80211_CHAN_NO_HT40PLUS), + CHAN5GHZ(157, IEEE80211_CHAN_NO_HT40MINUS), + CHAN5GHZ(161, IEEE80211_CHAN_NO_HT40PLUS), + CHAN5GHZ(165, IEEE80211_CHAN_NO_HT40PLUS | IEEE80211_CHAN_NO_HT40MINUS) +}; + +/* + * The rate table is used for both 2.4G and 5G rates. The + * latter being a subset as it does not support CCK rates. + */ +static struct ieee80211_rate legacy_ratetable[] = { + RATE(10, 0), + RATE(20, IEEE80211_RATE_SHORT_PREAMBLE), + RATE(55, IEEE80211_RATE_SHORT_PREAMBLE), + RATE(110, IEEE80211_RATE_SHORT_PREAMBLE), + RATE(60, 0), + RATE(90, 0), + RATE(120, 0), + RATE(180, 0), + RATE(240, 0), + RATE(360, 0), + RATE(480, 0), + RATE(540, 0), +}; + +static const struct ieee80211_supported_band brcms_band_2GHz_nphy_template = { + .band = IEEE80211_BAND_2GHZ, + .channels = brcms_2ghz_chantable, + .n_channels = ARRAY_SIZE(brcms_2ghz_chantable), + .bitrates = legacy_ratetable, + .n_bitrates = ARRAY_SIZE(legacy_ratetable), + .ht_cap = { + /* from include/linux/ieee80211.h */ + .cap = IEEE80211_HT_CAP_GRN_FLD | + IEEE80211_HT_CAP_SGI_20 | IEEE80211_HT_CAP_SGI_40, + .ht_supported = true, + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K, + .ampdu_density = AMPDU_DEF_MPDU_DENSITY, + .mcs = { + /* placeholders for now */ + .rx_mask = {0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0}, + .rx_highest = cpu_to_le16(500), + .tx_params = IEEE80211_HT_MCS_TX_DEFINED} + } +}; + +static const struct ieee80211_supported_band brcms_band_5GHz_nphy_template = { + .band = IEEE80211_BAND_5GHZ, + .channels = brcms_5ghz_nphy_chantable, + .n_channels = ARRAY_SIZE(brcms_5ghz_nphy_chantable), + .bitrates = legacy_ratetable + BRCMS_LEGACY_5G_RATE_OFFSET, + .n_bitrates = ARRAY_SIZE(legacy_ratetable) - + BRCMS_LEGACY_5G_RATE_OFFSET, + .ht_cap = { + .cap = IEEE80211_HT_CAP_GRN_FLD | IEEE80211_HT_CAP_SGI_20 | + IEEE80211_HT_CAP_SGI_40, + .ht_supported = true, + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K, + .ampdu_density = AMPDU_DEF_MPDU_DENSITY, + .mcs = { + /* placeholders for now */ + .rx_mask = {0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0}, + .rx_highest = cpu_to_le16(500), + .tx_params = IEEE80211_HT_MCS_TX_DEFINED} + } +}; + +/* flags the given rate in rateset as requested */ +static void brcms_set_basic_rate(struct brcm_rateset *rs, u16 rate, bool is_br) +{ + u32 i; + + for (i = 0; i < rs->count; i++) { + if (rate != (rs->rates[i] & 0x7f)) + continue; + + if (is_br) + rs->rates[i] |= BRCMS_RATE_FLAG; + else + rs->rates[i] &= BRCMS_RATE_MASK; + return; + } +} + +/** + * This function frees the WL per-device resources. + * + * This function frees resources owned by the WL device pointed to + * by the wl parameter. + * + * precondition: can both be called locked and unlocked + * + */ +static void brcms_free(struct brcms_info *wl) +{ + struct brcms_timer *t, *next; + + /* free ucode data */ + if (wl->fw.fw_cnt) + brcms_ucode_data_free(&wl->ucode); + if (wl->irq) + free_irq(wl->irq, wl); + + /* kill dpc */ + tasklet_kill(&wl->tasklet); + + if (wl->pub) { + brcms_debugfs_detach(wl->pub); + brcms_c_module_unregister(wl->pub, "linux", wl); + } + + /* free common resources */ + if (wl->wlc) { + brcms_c_detach(wl->wlc); + wl->wlc = NULL; + wl->pub = NULL; + } + + /* virtual interface deletion is deferred so we cannot spinwait */ + + /* wait for all pending callbacks to complete */ + while (atomic_read(&wl->callbacks) > 0) + schedule(); + + /* free timers */ + for (t = wl->timers; t; t = next) { + next = t->next; +#ifdef DEBUG + kfree(t->name); +#endif + kfree(t); + } +} + +/* +* called from both kernel as from this kernel module (error flow on attach) +* precondition: perimeter lock is not acquired. +*/ +static void brcms_remove(struct bcma_device *pdev) +{ + struct ieee80211_hw *hw = bcma_get_drvdata(pdev); + struct brcms_info *wl = hw->priv; + + if (wl->wlc) { + brcms_led_unregister(wl); + wiphy_rfkill_set_hw_state(wl->pub->ieee_hw->wiphy, false); + wiphy_rfkill_stop_polling(wl->pub->ieee_hw->wiphy); + ieee80211_unregister_hw(hw); + } + + brcms_free(wl); + + bcma_set_drvdata(pdev, NULL); + ieee80211_free_hw(hw); +} + +/* + * Precondition: Since this function is called in brcms_pci_probe() context, + * no locking is required. + */ +static void brcms_release_fw(struct brcms_info *wl) +{ + int i; + for (i = 0; i < MAX_FW_IMAGES; i++) { + release_firmware(wl->fw.fw_bin[i]); + release_firmware(wl->fw.fw_hdr[i]); + } +} + +/* + * Precondition: Since this function is called in brcms_pci_probe() context, + * no locking is required. + */ +static int brcms_request_fw(struct brcms_info *wl, struct bcma_device *pdev) +{ + int status; + struct device *device = &pdev->dev; + char fw_name[100]; + int i; + + memset(&wl->fw, 0, sizeof(struct brcms_firmware)); + for (i = 0; i < MAX_FW_IMAGES; i++) { + if (brcms_firmwares[i] == NULL) + break; + sprintf(fw_name, "%s-%d.fw", brcms_firmwares[i], + UCODE_LOADER_API_VER); + status = request_firmware(&wl->fw.fw_bin[i], fw_name, device); + if (status) { + wiphy_err(wl->wiphy, "%s: fail to load firmware %s\n", + KBUILD_MODNAME, fw_name); + return status; + } + sprintf(fw_name, "%s_hdr-%d.fw", brcms_firmwares[i], + UCODE_LOADER_API_VER); + status = request_firmware(&wl->fw.fw_hdr[i], fw_name, device); + if (status) { + wiphy_err(wl->wiphy, "%s: fail to load firmware %s\n", + KBUILD_MODNAME, fw_name); + return status; + } + wl->fw.hdr_num_entries[i] = + wl->fw.fw_hdr[i]->size / (sizeof(struct firmware_hdr)); + } + wl->fw.fw_cnt = i; + status = brcms_ucode_data_init(wl, &wl->ucode); + brcms_release_fw(wl); + return status; +} + +static void brcms_ops_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct brcms_info *wl = hw->priv; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + + spin_lock_bh(&wl->lock); + if (!wl->pub->up) { + brcms_err(wl->wlc->hw->d11core, "ops->tx called while down\n"); + kfree_skb(skb); + goto done; + } + if (brcms_c_sendpkt_mac80211(wl->wlc, skb, hw)) + tx_info->rate_driver_data[0] = control->sta; + done: + spin_unlock_bh(&wl->lock); +} + +static int brcms_ops_start(struct ieee80211_hw *hw) +{ + struct brcms_info *wl = hw->priv; + bool blocked; + int err; + + ieee80211_wake_queues(hw); + spin_lock_bh(&wl->lock); + blocked = brcms_rfkill_set_hw_state(wl); + spin_unlock_bh(&wl->lock); + if (!blocked) + wiphy_rfkill_stop_polling(wl->pub->ieee_hw->wiphy); + + if (!wl->ucode.bcm43xx_bomminor) { + err = brcms_request_fw(wl, wl->wlc->hw->d11core); + if (err) { + brcms_remove(wl->wlc->hw->d11core); + return -ENOENT; + } + } + + spin_lock_bh(&wl->lock); + /* avoid acknowledging frames before a non-monitor device is added */ + wl->mute_tx = true; + + if (!wl->pub->up) + if (!blocked) + err = brcms_up(wl); + else + err = -ERFKILL; + else + err = -ENODEV; + spin_unlock_bh(&wl->lock); + + if (err != 0) + brcms_err(wl->wlc->hw->d11core, "%s: brcms_up() returned %d\n", + __func__, err); + return err; +} + +static void brcms_ops_stop(struct ieee80211_hw *hw) +{ + struct brcms_info *wl = hw->priv; + int status; + + ieee80211_stop_queues(hw); + + if (wl->wlc == NULL) + return; + + spin_lock_bh(&wl->lock); + status = brcms_c_chipmatch(wl->wlc->hw->d11core); + spin_unlock_bh(&wl->lock); + if (!status) { + brcms_err(wl->wlc->hw->d11core, + "wl: brcms_ops_stop: chipmatch failed\n"); + return; + } + + /* put driver in down state */ + spin_lock_bh(&wl->lock); + brcms_down(wl); + spin_unlock_bh(&wl->lock); +} + +static int +brcms_ops_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct brcms_info *wl = hw->priv; + + /* Just STA, AP and ADHOC for now */ + if (vif->type != NL80211_IFTYPE_STATION && + vif->type != NL80211_IFTYPE_AP && + vif->type != NL80211_IFTYPE_ADHOC) { + brcms_err(wl->wlc->hw->d11core, + "%s: Attempt to add type %d, only STA, AP and AdHoc for now\n", + __func__, vif->type); + return -EOPNOTSUPP; + } + + spin_lock_bh(&wl->lock); + wl->mute_tx = false; + brcms_c_mute(wl->wlc, false); + if (vif->type == NL80211_IFTYPE_STATION) + brcms_c_start_station(wl->wlc, vif->addr); + else if (vif->type == NL80211_IFTYPE_AP) + brcms_c_start_ap(wl->wlc, vif->addr, vif->bss_conf.bssid, + vif->bss_conf.ssid, vif->bss_conf.ssid_len); + else if (vif->type == NL80211_IFTYPE_ADHOC) + brcms_c_start_adhoc(wl->wlc, vif->addr); + spin_unlock_bh(&wl->lock); + + return 0; +} + +static void +brcms_ops_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ +} + +static int brcms_ops_config(struct ieee80211_hw *hw, u32 changed) +{ + struct ieee80211_conf *conf = &hw->conf; + struct brcms_info *wl = hw->priv; + struct bcma_device *core = wl->wlc->hw->d11core; + int err = 0; + int new_int; + + spin_lock_bh(&wl->lock); + if (changed & IEEE80211_CONF_CHANGE_LISTEN_INTERVAL) { + brcms_c_set_beacon_listen_interval(wl->wlc, + conf->listen_interval); + } + if (changed & IEEE80211_CONF_CHANGE_MONITOR) + brcms_dbg_info(core, "%s: change monitor mode: %s\n", + __func__, conf->flags & IEEE80211_CONF_MONITOR ? + "true" : "false"); + if (changed & IEEE80211_CONF_CHANGE_PS) + brcms_err(core, "%s: change power-save mode: %s (implement)\n", + __func__, conf->flags & IEEE80211_CONF_PS ? + "true" : "false"); + + if (changed & IEEE80211_CONF_CHANGE_POWER) { + err = brcms_c_set_tx_power(wl->wlc, conf->power_level); + if (err < 0) { + brcms_err(core, "%s: Error setting power_level\n", + __func__); + goto config_out; + } + new_int = brcms_c_get_tx_power(wl->wlc); + if (new_int != conf->power_level) + brcms_err(core, + "%s: Power level req != actual, %d %d\n", + __func__, conf->power_level, + new_int); + } + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + if (conf->chandef.width == NL80211_CHAN_WIDTH_20 || + conf->chandef.width == NL80211_CHAN_WIDTH_20_NOHT) + err = brcms_c_set_channel(wl->wlc, + conf->chandef.chan->hw_value); + else + err = -ENOTSUPP; + } + if (changed & IEEE80211_CONF_CHANGE_RETRY_LIMITS) + err = brcms_c_set_rate_limit(wl->wlc, + conf->short_frame_max_tx_count, + conf->long_frame_max_tx_count); + + config_out: + spin_unlock_bh(&wl->lock); + return err; +} + +static void +brcms_ops_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, u32 changed) +{ + struct brcms_info *wl = hw->priv; + struct bcma_device *core = wl->wlc->hw->d11core; + + if (changed & BSS_CHANGED_ASSOC) { + /* association status changed (associated/disassociated) + * also implies a change in the AID. + */ + brcms_err(core, "%s: %s: %sassociated\n", KBUILD_MODNAME, + __func__, info->assoc ? "" : "dis"); + spin_lock_bh(&wl->lock); + brcms_c_associate_upd(wl->wlc, info->assoc); + spin_unlock_bh(&wl->lock); + } + if (changed & BSS_CHANGED_ERP_SLOT) { + s8 val; + + /* slot timing changed */ + if (info->use_short_slot) + val = 1; + else + val = 0; + spin_lock_bh(&wl->lock); + brcms_c_set_shortslot_override(wl->wlc, val); + spin_unlock_bh(&wl->lock); + } + + if (changed & BSS_CHANGED_HT) { + /* 802.11n parameters changed */ + u16 mode = info->ht_operation_mode; + + spin_lock_bh(&wl->lock); + brcms_c_protection_upd(wl->wlc, BRCMS_PROT_N_CFG, + mode & IEEE80211_HT_OP_MODE_PROTECTION); + brcms_c_protection_upd(wl->wlc, BRCMS_PROT_N_NONGF, + mode & IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT); + brcms_c_protection_upd(wl->wlc, BRCMS_PROT_N_OBSS, + mode & IEEE80211_HT_OP_MODE_NON_HT_STA_PRSNT); + spin_unlock_bh(&wl->lock); + } + if (changed & BSS_CHANGED_BASIC_RATES) { + struct ieee80211_supported_band *bi; + u32 br_mask, i; + u16 rate; + struct brcm_rateset rs; + int error; + + /* retrieve the current rates */ + spin_lock_bh(&wl->lock); + brcms_c_get_current_rateset(wl->wlc, &rs); + spin_unlock_bh(&wl->lock); + + br_mask = info->basic_rates; + bi = hw->wiphy->bands[brcms_c_get_curband(wl->wlc)]; + for (i = 0; i < bi->n_bitrates; i++) { + /* convert to internal rate value */ + rate = (bi->bitrates[i].bitrate << 1) / 10; + + /* set/clear basic rate flag */ + brcms_set_basic_rate(&rs, rate, br_mask & 1); + br_mask >>= 1; + } + + /* update the rate set */ + spin_lock_bh(&wl->lock); + error = brcms_c_set_rateset(wl->wlc, &rs); + spin_unlock_bh(&wl->lock); + if (error) + brcms_err(core, "changing basic rates failed: %d\n", + error); + } + if (changed & BSS_CHANGED_BEACON_INT) { + /* Beacon interval changed */ + spin_lock_bh(&wl->lock); + brcms_c_set_beacon_period(wl->wlc, info->beacon_int); + spin_unlock_bh(&wl->lock); + } + if (changed & BSS_CHANGED_BSSID) { + /* BSSID changed, for whatever reason (IBSS and managed mode) */ + spin_lock_bh(&wl->lock); + brcms_c_set_addrmatch(wl->wlc, RCM_BSSID_OFFSET, info->bssid); + spin_unlock_bh(&wl->lock); + } + if (changed & BSS_CHANGED_SSID) { + /* BSSID changed, for whatever reason (IBSS and managed mode) */ + spin_lock_bh(&wl->lock); + brcms_c_set_ssid(wl->wlc, info->ssid, info->ssid_len); + spin_unlock_bh(&wl->lock); + } + if (changed & BSS_CHANGED_BEACON) { + /* Beacon data changed, retrieve new beacon (beaconing modes) */ + struct sk_buff *beacon; + u16 tim_offset = 0; + + spin_lock_bh(&wl->lock); + beacon = ieee80211_beacon_get_tim(hw, vif, &tim_offset, NULL); + brcms_c_set_new_beacon(wl->wlc, beacon, tim_offset, + info->dtim_period); + spin_unlock_bh(&wl->lock); + } + + if (changed & BSS_CHANGED_AP_PROBE_RESP) { + struct sk_buff *probe_resp; + + spin_lock_bh(&wl->lock); + probe_resp = ieee80211_proberesp_get(hw, vif); + brcms_c_set_new_probe_resp(wl->wlc, probe_resp); + spin_unlock_bh(&wl->lock); + } + + if (changed & BSS_CHANGED_BEACON_ENABLED) { + /* Beaconing should be enabled/disabled (beaconing modes) */ + brcms_err(core, "%s: Beacon enabled: %s\n", __func__, + info->enable_beacon ? "true" : "false"); + if (info->enable_beacon && + hw->wiphy->flags & WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD) { + brcms_c_enable_probe_resp(wl->wlc, true); + } else { + brcms_c_enable_probe_resp(wl->wlc, false); + } + } + + if (changed & BSS_CHANGED_CQM) { + /* Connection quality monitor config changed */ + brcms_err(core, "%s: cqm change: threshold %d, hys %d " + " (implement)\n", __func__, info->cqm_rssi_thold, + info->cqm_rssi_hyst); + } + + if (changed & BSS_CHANGED_IBSS) { + /* IBSS join status changed */ + brcms_err(core, "%s: IBSS joined: %s (implement)\n", + __func__, info->ibss_joined ? "true" : "false"); + } + + if (changed & BSS_CHANGED_ARP_FILTER) { + /* Hardware ARP filter address list or state changed */ + brcms_err(core, "%s: arp filtering: %d addresses" + " (implement)\n", __func__, info->arp_addr_cnt); + } + + if (changed & BSS_CHANGED_QOS) { + /* + * QoS for this association was enabled/disabled. + * Note that it is only ever disabled for station mode. + */ + brcms_err(core, "%s: qos enabled: %s (implement)\n", + __func__, info->qos ? "true" : "false"); + } + return; +} + +static void +brcms_ops_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, u64 multicast) +{ + struct brcms_info *wl = hw->priv; + struct bcma_device *core = wl->wlc->hw->d11core; + + changed_flags &= MAC_FILTERS; + *total_flags &= MAC_FILTERS; + + if (changed_flags & FIF_PROMISC_IN_BSS) + brcms_dbg_info(core, "FIF_PROMISC_IN_BSS\n"); + if (changed_flags & FIF_ALLMULTI) + brcms_dbg_info(core, "FIF_ALLMULTI\n"); + if (changed_flags & FIF_FCSFAIL) + brcms_dbg_info(core, "FIF_FCSFAIL\n"); + if (changed_flags & FIF_CONTROL) + brcms_dbg_info(core, "FIF_CONTROL\n"); + if (changed_flags & FIF_OTHER_BSS) + brcms_dbg_info(core, "FIF_OTHER_BSS\n"); + if (changed_flags & FIF_PSPOLL) + brcms_dbg_info(core, "FIF_PSPOLL\n"); + if (changed_flags & FIF_BCN_PRBRESP_PROMISC) + brcms_dbg_info(core, "FIF_BCN_PRBRESP_PROMISC\n"); + + spin_lock_bh(&wl->lock); + brcms_c_mac_promisc(wl->wlc, *total_flags); + spin_unlock_bh(&wl->lock); + return; +} + +static void brcms_ops_sw_scan_start(struct ieee80211_hw *hw) +{ + struct brcms_info *wl = hw->priv; + spin_lock_bh(&wl->lock); + brcms_c_scan_start(wl->wlc); + spin_unlock_bh(&wl->lock); + return; +} + +static void brcms_ops_sw_scan_complete(struct ieee80211_hw *hw) +{ + struct brcms_info *wl = hw->priv; + spin_lock_bh(&wl->lock); + brcms_c_scan_stop(wl->wlc); + spin_unlock_bh(&wl->lock); + return; +} + +static int +brcms_ops_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct brcms_info *wl = hw->priv; + + spin_lock_bh(&wl->lock); + brcms_c_wme_setparams(wl->wlc, queue, params, true); + spin_unlock_bh(&wl->lock); + + return 0; +} + +static int +brcms_ops_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct brcms_info *wl = hw->priv; + struct scb *scb = &wl->wlc->pri_scb; + + brcms_c_init_scb(scb); + + wl->pub->global_ampdu = &(scb->scb_ampdu); + wl->pub->global_ampdu->scb = scb; + wl->pub->global_ampdu->max_pdu = 16; + + /* + * minstrel_ht initiates addBA on our behalf by calling + * ieee80211_start_tx_ba_session() + */ + return 0; +} + +static int +brcms_ops_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, u16 *ssn, + u8 buf_size) +{ + struct brcms_info *wl = hw->priv; + struct scb *scb = &wl->wlc->pri_scb; + int status; + + if (WARN_ON(scb->magic != SCB_MAGIC)) + return -EIDRM; + switch (action) { + case IEEE80211_AMPDU_RX_START: + break; + case IEEE80211_AMPDU_RX_STOP: + break; + case IEEE80211_AMPDU_TX_START: + spin_lock_bh(&wl->lock); + status = brcms_c_aggregatable(wl->wlc, tid); + spin_unlock_bh(&wl->lock); + if (!status) { + brcms_err(wl->wlc->hw->d11core, + "START: tid %d is not agg\'able\n", tid); + return -EINVAL; + } + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + + case IEEE80211_AMPDU_TX_STOP_CONT: + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + spin_lock_bh(&wl->lock); + brcms_c_ampdu_flush(wl->wlc, sta, tid); + spin_unlock_bh(&wl->lock); + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + /* + * BA window size from ADDBA response ('buf_size') defines how + * many outstanding MPDUs are allowed for the BA stream by + * recipient and traffic class. 'ampdu_factor' gives maximum + * AMPDU size. + */ + spin_lock_bh(&wl->lock); + brcms_c_ampdu_tx_operational(wl->wlc, tid, buf_size, + (1 << (IEEE80211_HT_MAX_AMPDU_FACTOR + + sta->ht_cap.ampdu_factor)) - 1); + spin_unlock_bh(&wl->lock); + /* Power save wakeup */ + break; + default: + brcms_err(wl->wlc->hw->d11core, + "%s: Invalid command, ignoring\n", __func__); + } + + return 0; +} + +static void brcms_ops_rfkill_poll(struct ieee80211_hw *hw) +{ + struct brcms_info *wl = hw->priv; + bool blocked; + + spin_lock_bh(&wl->lock); + blocked = brcms_c_check_radio_disabled(wl->wlc); + spin_unlock_bh(&wl->lock); + + wiphy_rfkill_set_hw_state(wl->pub->ieee_hw->wiphy, blocked); +} + +static bool brcms_tx_flush_completed(struct brcms_info *wl) +{ + bool result; + + spin_lock_bh(&wl->lock); + result = brcms_c_tx_flush_completed(wl->wlc); + spin_unlock_bh(&wl->lock); + return result; +} + +static void brcms_ops_flush(struct ieee80211_hw *hw, u32 queues, bool drop) +{ + struct brcms_info *wl = hw->priv; + int ret; + + no_printk("%s: drop = %s\n", __func__, drop ? "true" : "false"); + + ret = wait_event_timeout(wl->tx_flush_wq, + brcms_tx_flush_completed(wl), + msecs_to_jiffies(BRCMS_FLUSH_TIMEOUT)); + + brcms_dbg_mac80211(wl->wlc->hw->d11core, + "ret=%d\n", jiffies_to_msecs(ret)); +} + +static u64 brcms_ops_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct brcms_info *wl = hw->priv; + u64 tsf; + + spin_lock_bh(&wl->lock); + tsf = brcms_c_tsf_get(wl->wlc); + spin_unlock_bh(&wl->lock); + + return tsf; +} + +static void brcms_ops_set_tsf(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u64 tsf) +{ + struct brcms_info *wl = hw->priv; + + spin_lock_bh(&wl->lock); + brcms_c_tsf_set(wl->wlc, tsf); + spin_unlock_bh(&wl->lock); +} + +static const struct ieee80211_ops brcms_ops = { + .tx = brcms_ops_tx, + .start = brcms_ops_start, + .stop = brcms_ops_stop, + .add_interface = brcms_ops_add_interface, + .remove_interface = brcms_ops_remove_interface, + .config = brcms_ops_config, + .bss_info_changed = brcms_ops_bss_info_changed, + .configure_filter = brcms_ops_configure_filter, + .sw_scan_start = brcms_ops_sw_scan_start, + .sw_scan_complete = brcms_ops_sw_scan_complete, + .conf_tx = brcms_ops_conf_tx, + .sta_add = brcms_ops_sta_add, + .ampdu_action = brcms_ops_ampdu_action, + .rfkill_poll = brcms_ops_rfkill_poll, + .flush = brcms_ops_flush, + .get_tsf = brcms_ops_get_tsf, + .set_tsf = brcms_ops_set_tsf, +}; + +void brcms_dpc(unsigned long data) +{ + struct brcms_info *wl; + + wl = (struct brcms_info *) data; + + spin_lock_bh(&wl->lock); + + /* call the common second level interrupt handler */ + if (wl->pub->up) { + if (wl->resched) { + unsigned long flags; + + spin_lock_irqsave(&wl->isr_lock, flags); + brcms_c_intrsupd(wl->wlc); + spin_unlock_irqrestore(&wl->isr_lock, flags); + } + + wl->resched = brcms_c_dpc(wl->wlc, true); + } + + /* brcms_c_dpc() may bring the driver down */ + if (!wl->pub->up) + goto done; + + /* re-schedule dpc */ + if (wl->resched) + tasklet_schedule(&wl->tasklet); + else + /* re-enable interrupts */ + brcms_intrson(wl); + + done: + spin_unlock_bh(&wl->lock); + wake_up(&wl->tx_flush_wq); +} + +static irqreturn_t brcms_isr(int irq, void *dev_id) +{ + struct brcms_info *wl; + irqreturn_t ret = IRQ_NONE; + + wl = (struct brcms_info *) dev_id; + + spin_lock(&wl->isr_lock); + + /* call common first level interrupt handler */ + if (brcms_c_isr(wl->wlc)) { + /* schedule second level handler */ + tasklet_schedule(&wl->tasklet); + ret = IRQ_HANDLED; + } + + spin_unlock(&wl->isr_lock); + + return ret; +} + +/* + * is called in brcms_pci_probe() context, therefore no locking required. + */ +static int ieee_hw_rate_init(struct ieee80211_hw *hw) +{ + struct brcms_info *wl = hw->priv; + struct brcms_c_info *wlc = wl->wlc; + struct ieee80211_supported_band *band; + int has_5g = 0; + u16 phy_type; + + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = NULL; + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = NULL; + + phy_type = brcms_c_get_phy_type(wl->wlc, 0); + if (phy_type == PHY_TYPE_N || phy_type == PHY_TYPE_LCN) { + band = &wlc->bandstate[BAND_2G_INDEX]->band; + *band = brcms_band_2GHz_nphy_template; + if (phy_type == PHY_TYPE_LCN) { + /* Single stream */ + band->ht_cap.mcs.rx_mask[1] = 0; + band->ht_cap.mcs.rx_highest = cpu_to_le16(72); + } + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = band; + } else { + return -EPERM; + } + + /* Assume all bands use the same phy. True for 11n devices. */ + if (wl->pub->_nbands > 1) { + has_5g++; + if (phy_type == PHY_TYPE_N || phy_type == PHY_TYPE_LCN) { + band = &wlc->bandstate[BAND_5G_INDEX]->band; + *band = brcms_band_5GHz_nphy_template; + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = band; + } else { + return -EPERM; + } + } + return 0; +} + +/* + * is called in brcms_pci_probe() context, therefore no locking required. + */ +static int ieee_hw_init(struct ieee80211_hw *hw) +{ + hw->flags = IEEE80211_HW_SIGNAL_DBM + /* | IEEE80211_HW_CONNECTION_MONITOR What is this? */ + | IEEE80211_HW_REPORTS_TX_ACK_STATUS + | IEEE80211_HW_AMPDU_AGGREGATION; + + hw->extra_tx_headroom = brcms_c_get_header_len(); + hw->queues = N_TX_QUEUES; + hw->max_rates = 2; /* Primary rate and 1 fallback rate */ + + /* channel change time is dependent on chip and band */ + hw->channel_change_time = 7 * 1000; + hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_ADHOC); + + /* + * deactivate sending probe responses by ucude, because this will + * cause problems when WPS is used. + * + * hw->wiphy->flags |= WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD; + */ + + hw->rate_control_algorithm = "minstrel_ht"; + + hw->sta_data_size = 0; + return ieee_hw_rate_init(hw); +} + +/** + * attach to the WL device. + * + * Attach to the WL device identified by vendor and device parameters. + * regs is a host accessible memory address pointing to WL device registers. + * + * brcms_attach is not defined as static because in the case where no bus + * is defined, wl_attach will never be called, and thus, gcc will issue + * a warning that this function is defined but not used if we declare + * it as static. + * + * + * is called in brcms_bcma_probe() context, therefore no locking required. + */ +static struct brcms_info *brcms_attach(struct bcma_device *pdev) +{ + struct brcms_info *wl = NULL; + int unit, err; + struct ieee80211_hw *hw; + u8 perm[ETH_ALEN]; + + unit = n_adapters_found; + err = 0; + + if (unit < 0) + return NULL; + + /* allocate private info */ + hw = bcma_get_drvdata(pdev); + if (hw != NULL) + wl = hw->priv; + if (WARN_ON(hw == NULL) || WARN_ON(wl == NULL)) + return NULL; + wl->wiphy = hw->wiphy; + + atomic_set(&wl->callbacks, 0); + + init_waitqueue_head(&wl->tx_flush_wq); + + /* setup the bottom half handler */ + tasklet_init(&wl->tasklet, brcms_dpc, (unsigned long) wl); + + spin_lock_init(&wl->lock); + spin_lock_init(&wl->isr_lock); + + /* common load-time initialization */ + wl->wlc = brcms_c_attach((void *)wl, pdev, unit, false, &err); + if (!wl->wlc) { + wiphy_err(wl->wiphy, "%s: attach() failed with code %d\n", + KBUILD_MODNAME, err); + goto fail; + } + wl->pub = brcms_c_pub(wl->wlc); + + wl->pub->ieee_hw = hw; + + /* register our interrupt handler */ + if (request_irq(pdev->irq, brcms_isr, + IRQF_SHARED, KBUILD_MODNAME, wl)) { + wiphy_err(wl->wiphy, "wl%d: request_irq() failed\n", unit); + goto fail; + } + wl->irq = pdev->irq; + + /* register module */ + brcms_c_module_register(wl->pub, "linux", wl, NULL); + + if (ieee_hw_init(hw)) { + wiphy_err(wl->wiphy, "wl%d: %s: ieee_hw_init failed!\n", unit, + __func__); + goto fail; + } + + brcms_c_regd_init(wl->wlc); + + memcpy(perm, &wl->pub->cur_etheraddr, ETH_ALEN); + if (WARN_ON(!is_valid_ether_addr(perm))) + goto fail; + SET_IEEE80211_PERM_ADDR(hw, perm); + + err = ieee80211_register_hw(hw); + if (err) + wiphy_err(wl->wiphy, "%s: ieee80211_register_hw failed, status" + "%d\n", __func__, err); + + if (wl->pub->srom_ccode[0] && + regulatory_hint(wl->wiphy, wl->pub->srom_ccode)) + wiphy_err(wl->wiphy, "%s: regulatory hint failed\n", __func__); + + brcms_debugfs_attach(wl->pub); + brcms_debugfs_create_files(wl->pub); + n_adapters_found++; + return wl; + +fail: + brcms_free(wl); + return NULL; +} + + + +/** + * determines if a device is a WL device, and if so, attaches it. + * + * This function determines if a device pointed to by pdev is a WL device, + * and if so, performs a brcms_attach() on it. + * + * Perimeter lock is initialized in the course of this function. + */ +static int brcms_bcma_probe(struct bcma_device *pdev) +{ + struct brcms_info *wl; + struct ieee80211_hw *hw; + + dev_info(&pdev->dev, "mfg %x core %x rev %d class %d irq %d\n", + pdev->id.manuf, pdev->id.id, pdev->id.rev, pdev->id.class, + pdev->irq); + + if ((pdev->id.manuf != BCMA_MANUF_BCM) || + (pdev->id.id != BCMA_CORE_80211)) + return -ENODEV; + + hw = ieee80211_alloc_hw(sizeof(struct brcms_info), &brcms_ops); + if (!hw) { + pr_err("%s: ieee80211_alloc_hw failed\n", __func__); + return -ENOMEM; + } + + SET_IEEE80211_DEV(hw, &pdev->dev); + + bcma_set_drvdata(pdev, hw); + + memset(hw->priv, 0, sizeof(*wl)); + + wl = brcms_attach(pdev); + if (!wl) { + pr_err("%s: brcms_attach failed!\n", __func__); + return -ENODEV; + } + brcms_led_register(wl); + + return 0; +} + +static int brcms_suspend(struct bcma_device *pdev) +{ + struct brcms_info *wl; + struct ieee80211_hw *hw; + + hw = bcma_get_drvdata(pdev); + wl = hw->priv; + if (!wl) { + pr_err("%s: %s: no driver private struct!\n", KBUILD_MODNAME, + __func__); + return -ENODEV; + } + + /* only need to flag hw is down for proper resume */ + spin_lock_bh(&wl->lock); + wl->pub->hw_up = false; + spin_unlock_bh(&wl->lock); + + brcms_dbg_info(wl->wlc->hw->d11core, "brcms_suspend ok\n"); + + return 0; +} + +static int brcms_resume(struct bcma_device *pdev) +{ + return 0; +} + +static struct bcma_driver brcms_bcma_driver = { + .name = KBUILD_MODNAME, + .probe = brcms_bcma_probe, + .suspend = brcms_suspend, + .resume = brcms_resume, + .remove = brcms_remove, + .id_table = brcms_coreid_table, +}; + +/** + * This is the main entry point for the brcmsmac driver. + * + * This function is scheduled upon module initialization and + * does the driver registration, which result in brcms_bcma_probe() + * call resulting in the driver bringup. + */ +static void brcms_driver_init(struct work_struct *work) +{ + int error; + + error = bcma_driver_register(&brcms_bcma_driver); + if (error) + pr_err("%s: register returned %d\n", __func__, error); +} + +static DECLARE_WORK(brcms_driver_work, brcms_driver_init); + +static int __init brcms_module_init(void) +{ + brcms_debugfs_init(); + if (!schedule_work(&brcms_driver_work)) + return -EBUSY; + + return 0; +} + +/** + * This function unloads the brcmsmac driver from the system. + * + * This function unconditionally unloads the brcmsmac driver module from the + * system. + * + */ +static void __exit brcms_module_exit(void) +{ + cancel_work_sync(&brcms_driver_work); + bcma_driver_unregister(&brcms_bcma_driver); + brcms_debugfs_exit(); +} + +module_init(brcms_module_init); +module_exit(brcms_module_exit); + +/* + * precondition: perimeter lock has been acquired + */ +void brcms_txflowcontrol(struct brcms_info *wl, struct brcms_if *wlif, + bool state, int prio) +{ + brcms_err(wl->wlc->hw->d11core, "Shouldn't be here %s\n", __func__); +} + +/* + * precondition: perimeter lock has been acquired + */ +void brcms_init(struct brcms_info *wl) +{ + brcms_dbg_info(wl->wlc->hw->d11core, "Initializing wl%d\n", + wl->pub->unit); + brcms_reset(wl); + brcms_c_init(wl->wlc, wl->mute_tx); +} + +/* + * precondition: perimeter lock has been acquired + */ +uint brcms_reset(struct brcms_info *wl) +{ + brcms_dbg_info(wl->wlc->hw->d11core, "Resetting wl%d\n", wl->pub->unit); + brcms_c_reset(wl->wlc); + + /* dpc will not be rescheduled */ + wl->resched = false; + + /* inform publicly that interface is down */ + wl->pub->up = false; + + return 0; +} + +void brcms_fatal_error(struct brcms_info *wl) +{ + brcms_err(wl->wlc->hw->d11core, "wl%d: fatal error, reinitializing\n", + wl->wlc->pub->unit); + brcms_reset(wl); + ieee80211_restart_hw(wl->pub->ieee_hw); +} + +/* + * These are interrupt on/off entry points. Disable interrupts + * during interrupt state transition. + */ +void brcms_intrson(struct brcms_info *wl) +{ + unsigned long flags; + + spin_lock_irqsave(&wl->isr_lock, flags); + brcms_c_intrson(wl->wlc); + spin_unlock_irqrestore(&wl->isr_lock, flags); +} + +u32 brcms_intrsoff(struct brcms_info *wl) +{ + unsigned long flags; + u32 status; + + spin_lock_irqsave(&wl->isr_lock, flags); + status = brcms_c_intrsoff(wl->wlc); + spin_unlock_irqrestore(&wl->isr_lock, flags); + return status; +} + +void brcms_intrsrestore(struct brcms_info *wl, u32 macintmask) +{ + unsigned long flags; + + spin_lock_irqsave(&wl->isr_lock, flags); + brcms_c_intrsrestore(wl->wlc, macintmask); + spin_unlock_irqrestore(&wl->isr_lock, flags); +} + +/* + * precondition: perimeter lock has been acquired + */ +int brcms_up(struct brcms_info *wl) +{ + int error = 0; + + if (wl->pub->up) + return 0; + + error = brcms_c_up(wl->wlc); + + return error; +} + +/* + * precondition: perimeter lock has been acquired + */ +void brcms_down(struct brcms_info *wl) +{ + uint callbacks, ret_val = 0; + + /* call common down function */ + ret_val = brcms_c_down(wl->wlc); + callbacks = atomic_read(&wl->callbacks) - ret_val; + + /* wait for down callbacks to complete */ + spin_unlock_bh(&wl->lock); + + /* For HIGH_only driver, it's important to actually schedule other work, + * not just spin wait since everything runs at schedule level + */ + SPINWAIT((atomic_read(&wl->callbacks) > callbacks), 100 * 1000); + + spin_lock_bh(&wl->lock); +} + +/* +* precondition: perimeter lock is not acquired + */ +static void _brcms_timer(struct work_struct *work) +{ + struct brcms_timer *t = container_of(work, struct brcms_timer, + dly_wrk.work); + + spin_lock_bh(&t->wl->lock); + + if (t->set) { + if (t->periodic) { + atomic_inc(&t->wl->callbacks); + ieee80211_queue_delayed_work(t->wl->pub->ieee_hw, + &t->dly_wrk, + msecs_to_jiffies(t->ms)); + } else { + t->set = false; + } + + t->fn(t->arg); + } + + atomic_dec(&t->wl->callbacks); + + spin_unlock_bh(&t->wl->lock); +} + +/* + * Adds a timer to the list. Caller supplies a timer function. + * Is called from wlc. + * + * precondition: perimeter lock has been acquired + */ +struct brcms_timer *brcms_init_timer(struct brcms_info *wl, + void (*fn) (void *arg), + void *arg, const char *name) +{ + struct brcms_timer *t; + + t = kzalloc(sizeof(struct brcms_timer), GFP_ATOMIC); + if (!t) + return NULL; + + INIT_DELAYED_WORK(&t->dly_wrk, _brcms_timer); + t->wl = wl; + t->fn = fn; + t->arg = arg; + t->next = wl->timers; + wl->timers = t; + +#ifdef DEBUG + t->name = kmalloc(strlen(name) + 1, GFP_ATOMIC); + if (t->name) + strcpy(t->name, name); +#endif + + return t; +} + +/* + * adds only the kernel timer since it's going to be more accurate + * as well as it's easier to make it periodic + * + * precondition: perimeter lock has been acquired + */ +void brcms_add_timer(struct brcms_timer *t, uint ms, int periodic) +{ + struct ieee80211_hw *hw = t->wl->pub->ieee_hw; + +#ifdef DEBUG + if (t->set) + brcms_dbg_info(t->wl->wlc->hw->d11core, + "%s: Already set. Name: %s, per %d\n", + __func__, t->name, periodic); +#endif + t->ms = ms; + t->periodic = (bool) periodic; + if (!t->set) { + t->set = true; + atomic_inc(&t->wl->callbacks); + } + + ieee80211_queue_delayed_work(hw, &t->dly_wrk, msecs_to_jiffies(ms)); +} + +/* + * return true if timer successfully deleted, false if still pending + * + * precondition: perimeter lock has been acquired + */ +bool brcms_del_timer(struct brcms_timer *t) +{ + if (t->set) { + t->set = false; + if (!cancel_delayed_work(&t->dly_wrk)) + return false; + + atomic_dec(&t->wl->callbacks); + } + + return true; +} + +/* + * precondition: perimeter lock has been acquired + */ +void brcms_free_timer(struct brcms_timer *t) +{ + struct brcms_info *wl = t->wl; + struct brcms_timer *tmp; + + /* delete the timer in case it is active */ + brcms_del_timer(t); + + if (wl->timers == t) { + wl->timers = wl->timers->next; +#ifdef DEBUG + kfree(t->name); +#endif + kfree(t); + return; + + } + + tmp = wl->timers; + while (tmp) { + if (tmp->next == t) { + tmp->next = t->next; +#ifdef DEBUG + kfree(t->name); +#endif + kfree(t); + return; + } + tmp = tmp->next; + } + +} + +/* + * precondition: perimeter lock has been acquired + */ +int brcms_ucode_init_buf(struct brcms_info *wl, void **pbuf, u32 idx) +{ + int i, entry; + const u8 *pdata; + struct firmware_hdr *hdr; + for (i = 0; i < wl->fw.fw_cnt; i++) { + hdr = (struct firmware_hdr *)wl->fw.fw_hdr[i]->data; + for (entry = 0; entry < wl->fw.hdr_num_entries[i]; + entry++, hdr++) { + u32 len = le32_to_cpu(hdr->len); + if (le32_to_cpu(hdr->idx) == idx) { + pdata = wl->fw.fw_bin[i]->data + + le32_to_cpu(hdr->offset); + *pbuf = kmemdup(pdata, len, GFP_ATOMIC); + if (*pbuf == NULL) + goto fail; + + return 0; + } + } + } + brcms_err(wl->wlc->hw->d11core, + "ERROR: ucode buf tag:%d can not be found!\n", idx); + *pbuf = NULL; +fail: + return -ENODATA; +} + +/* + * Precondition: Since this function is called in brcms_bcma_probe() context, + * no locking is required. + */ +int brcms_ucode_init_uint(struct brcms_info *wl, size_t *n_bytes, u32 idx) +{ + int i, entry; + const u8 *pdata; + struct firmware_hdr *hdr; + for (i = 0; i < wl->fw.fw_cnt; i++) { + hdr = (struct firmware_hdr *)wl->fw.fw_hdr[i]->data; + for (entry = 0; entry < wl->fw.hdr_num_entries[i]; + entry++, hdr++) { + if (le32_to_cpu(hdr->idx) == idx) { + pdata = wl->fw.fw_bin[i]->data + + le32_to_cpu(hdr->offset); + if (le32_to_cpu(hdr->len) != 4) { + brcms_err(wl->wlc->hw->d11core, + "ERROR: fw hdr len\n"); + return -ENOMSG; + } + *n_bytes = le32_to_cpu(*((__le32 *) pdata)); + return 0; + } + } + } + brcms_err(wl->wlc->hw->d11core, + "ERROR: ucode tag:%d can not be found!\n", idx); + return -ENOMSG; +} + +/* + * precondition: can both be called locked and unlocked + */ +void brcms_ucode_free_buf(void *p) +{ + kfree(p); +} + +/* + * checks validity of all firmware images loaded from user space + * + * Precondition: Since this function is called in brcms_bcma_probe() context, + * no locking is required. + */ +int brcms_check_firmwares(struct brcms_info *wl) +{ + int i; + int entry; + int rc = 0; + const struct firmware *fw; + const struct firmware *fw_hdr; + struct firmware_hdr *ucode_hdr; + for (i = 0; i < MAX_FW_IMAGES && rc == 0; i++) { + fw = wl->fw.fw_bin[i]; + fw_hdr = wl->fw.fw_hdr[i]; + if (fw == NULL && fw_hdr == NULL) { + break; + } else if (fw == NULL || fw_hdr == NULL) { + wiphy_err(wl->wiphy, "%s: invalid bin/hdr fw\n", + __func__); + rc = -EBADF; + } else if (fw_hdr->size % sizeof(struct firmware_hdr)) { + wiphy_err(wl->wiphy, "%s: non integral fw hdr file " + "size %zu/%zu\n", __func__, fw_hdr->size, + sizeof(struct firmware_hdr)); + rc = -EBADF; + } else if (fw->size < MIN_FW_SIZE || fw->size > MAX_FW_SIZE) { + wiphy_err(wl->wiphy, "%s: out of bounds fw file size %zu\n", + __func__, fw->size); + rc = -EBADF; + } else { + /* check if ucode section overruns firmware image */ + ucode_hdr = (struct firmware_hdr *)fw_hdr->data; + for (entry = 0; entry < wl->fw.hdr_num_entries[i] && + !rc; entry++, ucode_hdr++) { + if (le32_to_cpu(ucode_hdr->offset) + + le32_to_cpu(ucode_hdr->len) > + fw->size) { + wiphy_err(wl->wiphy, + "%s: conflicting bin/hdr\n", + __func__); + rc = -EBADF; + } + } + } + } + if (rc == 0 && wl->fw.fw_cnt != i) { + wiphy_err(wl->wiphy, "%s: invalid fw_cnt=%d\n", __func__, + wl->fw.fw_cnt); + rc = -EBADF; + } + return rc; +} + +/* + * precondition: perimeter lock has been acquired + */ +bool brcms_rfkill_set_hw_state(struct brcms_info *wl) +{ + bool blocked = brcms_c_check_radio_disabled(wl->wlc); + + spin_unlock_bh(&wl->lock); + wiphy_rfkill_set_hw_state(wl->pub->ieee_hw->wiphy, blocked); + if (blocked) + wiphy_rfkill_start_polling(wl->pub->ieee_hw->wiphy); + spin_lock_bh(&wl->lock); + return blocked; +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/mac80211_if.h b/drivers/net/wireless/brcm80211/brcmsmac/mac80211_if.h new file mode 100644 index 0000000..4090032 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/mac80211_if.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_MAC80211_IF_H_ +#define _BRCM_MAC80211_IF_H_ + +#include +#include +#include +#include + +#include "ucode_loader.h" +#include "led.h" +/* + * Starting index for 5G rates in the + * legacy rate table. + */ +#define BRCMS_LEGACY_5G_RATE_OFFSET 4 + +/* softmac ioctl definitions */ +#define BRCMS_SET_SHORTSLOT_OVERRIDE 146 + +struct brcms_timer { + struct delayed_work dly_wrk; + struct brcms_info *wl; + void (*fn) (void *); /* function called upon expiration */ + void *arg; /* fixed argument provided to called function */ + uint ms; + bool periodic; + bool set; /* indicates if timer is active */ + struct brcms_timer *next; /* for freeing on unload */ +#ifdef DEBUG + char *name; /* Description of the timer */ +#endif +}; + +struct brcms_if { + uint subunit; /* WDS/BSS unit */ + struct pci_dev *pci_dev; +}; + +#define MAX_FW_IMAGES 4 +struct brcms_firmware { + u32 fw_cnt; + const struct firmware *fw_bin[MAX_FW_IMAGES]; + const struct firmware *fw_hdr[MAX_FW_IMAGES]; + u32 hdr_num_entries[MAX_FW_IMAGES]; +}; + +struct brcms_info { + struct brcms_pub *pub; /* pointer to public wlc state */ + struct brcms_c_info *wlc; /* pointer to private common data */ + u32 magic; + + int irq; + + spinlock_t lock; /* per-device perimeter lock */ + spinlock_t isr_lock; /* per-device ISR synchronization lock */ + + /* tx flush */ + wait_queue_head_t tx_flush_wq; + + /* timer related fields */ + atomic_t callbacks; /* # outstanding callback functions */ + struct brcms_timer *timers; /* timer cleanup queue */ + + struct tasklet_struct tasklet; /* dpc tasklet */ + bool resched; /* dpc needs to be and is rescheduled */ + struct brcms_firmware fw; + struct wiphy *wiphy; + struct brcms_ucode ucode; + bool mute_tx; + struct brcms_led radio_led; + struct led_classdev led_dev; +}; + +/* misc callbacks */ +extern void brcms_init(struct brcms_info *wl); +extern uint brcms_reset(struct brcms_info *wl); +extern void brcms_intrson(struct brcms_info *wl); +extern u32 brcms_intrsoff(struct brcms_info *wl); +extern void brcms_intrsrestore(struct brcms_info *wl, u32 macintmask); +extern int brcms_up(struct brcms_info *wl); +extern void brcms_down(struct brcms_info *wl); +extern void brcms_txflowcontrol(struct brcms_info *wl, struct brcms_if *wlif, + bool state, int prio); +extern bool brcms_rfkill_set_hw_state(struct brcms_info *wl); + +/* timer functions */ +extern struct brcms_timer *brcms_init_timer(struct brcms_info *wl, + void (*fn) (void *arg), void *arg, + const char *name); +extern void brcms_free_timer(struct brcms_timer *timer); +extern void brcms_add_timer(struct brcms_timer *timer, uint ms, int periodic); +extern bool brcms_del_timer(struct brcms_timer *timer); +extern void brcms_dpc(unsigned long data); +extern void brcms_timer(struct brcms_timer *t); +extern void brcms_fatal_error(struct brcms_info *wl); + +#endif /* _BRCM_MAC80211_IF_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/main.c b/drivers/net/wireless/brcm80211/brcmsmac/main.c new file mode 100644 index 0000000..9fd6f2f --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/main.c @@ -0,0 +1,8182 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * Copyright (c) 2013 Hauke Mehrtens + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include "rate.h" +#include "scb.h" +#include "phy/phy_hal.h" +#include "channel.h" +#include "antsel.h" +#include "stf.h" +#include "ampdu.h" +#include "mac80211_if.h" +#include "ucode_loader.h" +#include "main.h" +#include "soc.h" +#include "dma.h" +#include "debug.h" +#include "brcms_trace_events.h" + +/* watchdog timer, in unit of ms */ +#define TIMER_INTERVAL_WATCHDOG 1000 +/* radio monitor timer, in unit of ms */ +#define TIMER_INTERVAL_RADIOCHK 800 + +/* beacon interval, in unit of 1024TU */ +#define BEACON_INTERVAL_DEFAULT 100 + +/* n-mode support capability */ +/* 2x2 includes both 1x1 & 2x2 devices + * reserved #define 2 for future when we want to separate 1x1 & 2x2 and + * control it independently + */ +#define WL_11N_2x2 1 +#define WL_11N_3x3 3 +#define WL_11N_4x4 4 + +#define EDCF_ACI_MASK 0x60 +#define EDCF_ACI_SHIFT 5 +#define EDCF_ECWMIN_MASK 0x0f +#define EDCF_ECWMAX_SHIFT 4 +#define EDCF_AIFSN_MASK 0x0f +#define EDCF_AIFSN_MAX 15 +#define EDCF_ECWMAX_MASK 0xf0 + +#define EDCF_AC_BE_TXOP_STA 0x0000 +#define EDCF_AC_BK_TXOP_STA 0x0000 +#define EDCF_AC_VO_ACI_STA 0x62 +#define EDCF_AC_VO_ECW_STA 0x32 +#define EDCF_AC_VI_ACI_STA 0x42 +#define EDCF_AC_VI_ECW_STA 0x43 +#define EDCF_AC_BK_ECW_STA 0xA4 +#define EDCF_AC_VI_TXOP_STA 0x005e +#define EDCF_AC_VO_TXOP_STA 0x002f +#define EDCF_AC_BE_ACI_STA 0x03 +#define EDCF_AC_BE_ECW_STA 0xA4 +#define EDCF_AC_BK_ACI_STA 0x27 +#define EDCF_AC_VO_TXOP_AP 0x002f + +#define EDCF_TXOP2USEC(txop) ((txop) << 5) +#define EDCF_ECW2CW(exp) ((1 << (exp)) - 1) + +#define APHY_SYMBOL_TIME 4 +#define APHY_PREAMBLE_TIME 16 +#define APHY_SIGNAL_TIME 4 +#define APHY_SIFS_TIME 16 +#define APHY_SERVICE_NBITS 16 +#define APHY_TAIL_NBITS 6 +#define BPHY_SIFS_TIME 10 +#define BPHY_PLCP_SHORT_TIME 96 + +#define PREN_PREAMBLE 24 +#define PREN_MM_EXT 12 +#define PREN_PREAMBLE_EXT 4 + +#define DOT11_MAC_HDR_LEN 24 +#define DOT11_ACK_LEN 10 +#define DOT11_BA_LEN 4 +#define DOT11_OFDM_SIGNAL_EXTENSION 6 +#define DOT11_MIN_FRAG_LEN 256 +#define DOT11_RTS_LEN 16 +#define DOT11_CTS_LEN 10 +#define DOT11_BA_BITMAP_LEN 128 +#define DOT11_MAXNUMFRAGS 16 +#define DOT11_MAX_FRAG_LEN 2346 + +#define BPHY_PLCP_TIME 192 +#define RIFS_11N_TIME 2 + +/* length of the BCN template area */ +#define BCN_TMPL_LEN 512 + +/* brcms_bss_info flag bit values */ +#define BRCMS_BSS_HT 0x0020 /* BSS is HT (MIMO) capable */ + +/* chip rx buffer offset */ +#define BRCMS_HWRXOFF 38 + +/* rfdisable delay timer 500 ms, runs of ALP clock */ +#define RFDISABLE_DEFAULT 10000000 + +#define BRCMS_TEMPSENSE_PERIOD 10 /* 10 second timeout */ + +/* synthpu_dly times in us */ +#define SYNTHPU_DLY_APHY_US 3700 +#define SYNTHPU_DLY_BPHY_US 1050 +#define SYNTHPU_DLY_NPHY_US 2048 +#define SYNTHPU_DLY_LPPHY_US 300 + +#define ANTCNT 10 /* vanilla M_MAX_ANTCNT val */ + +/* Per-AC retry limit register definitions; uses defs.h bitfield macros */ +#define EDCF_SHORT_S 0 +#define EDCF_SFB_S 4 +#define EDCF_LONG_S 8 +#define EDCF_LFB_S 12 +#define EDCF_SHORT_M BITFIELD_MASK(4) +#define EDCF_SFB_M BITFIELD_MASK(4) +#define EDCF_LONG_M BITFIELD_MASK(4) +#define EDCF_LFB_M BITFIELD_MASK(4) + +#define RETRY_SHORT_DEF 7 /* Default Short retry Limit */ +#define RETRY_SHORT_MAX 255 /* Maximum Short retry Limit */ +#define RETRY_LONG_DEF 4 /* Default Long retry count */ +#define RETRY_SHORT_FB 3 /* Short count for fb rate */ +#define RETRY_LONG_FB 2 /* Long count for fb rate */ + +#define APHY_CWMIN 15 +#define PHY_CWMAX 1023 + +#define EDCF_AIFSN_MIN 1 + +#define FRAGNUM_MASK 0xF + +#define APHY_SLOT_TIME 9 +#define BPHY_SLOT_TIME 20 + +#define WL_SPURAVOID_OFF 0 +#define WL_SPURAVOID_ON1 1 +#define WL_SPURAVOID_ON2 2 + +/* invalid core flags, use the saved coreflags */ +#define BRCMS_USE_COREFLAGS 0xffffffff + +/* values for PLCPHdr_override */ +#define BRCMS_PLCP_AUTO -1 +#define BRCMS_PLCP_SHORT 0 +#define BRCMS_PLCP_LONG 1 + +/* values for g_protection_override and n_protection_override */ +#define BRCMS_PROTECTION_AUTO -1 +#define BRCMS_PROTECTION_OFF 0 +#define BRCMS_PROTECTION_ON 1 +#define BRCMS_PROTECTION_MMHDR_ONLY 2 +#define BRCMS_PROTECTION_CTS_ONLY 3 + +/* values for g_protection_control and n_protection_control */ +#define BRCMS_PROTECTION_CTL_OFF 0 +#define BRCMS_PROTECTION_CTL_LOCAL 1 +#define BRCMS_PROTECTION_CTL_OVERLAP 2 + +/* values for n_protection */ +#define BRCMS_N_PROTECTION_OFF 0 +#define BRCMS_N_PROTECTION_OPTIONAL 1 +#define BRCMS_N_PROTECTION_20IN40 2 +#define BRCMS_N_PROTECTION_MIXEDMODE 3 + +/* values for band specific 40MHz capabilities */ +#define BRCMS_N_BW_20ALL 0 +#define BRCMS_N_BW_40ALL 1 +#define BRCMS_N_BW_20IN2G_40IN5G 2 + +/* bitflags for SGI support (sgi_rx iovar) */ +#define BRCMS_N_SGI_20 0x01 +#define BRCMS_N_SGI_40 0x02 + +/* defines used by the nrate iovar */ +/* MSC in use,indicates b0-6 holds an mcs */ +#define NRATE_MCS_INUSE 0x00000080 +/* rate/mcs value */ +#define NRATE_RATE_MASK 0x0000007f +/* stf mode mask: siso, cdd, stbc, sdm */ +#define NRATE_STF_MASK 0x0000ff00 +/* stf mode shift */ +#define NRATE_STF_SHIFT 8 +/* bit indicate to override mcs only */ +#define NRATE_OVERRIDE_MCS_ONLY 0x40000000 +#define NRATE_SGI_MASK 0x00800000 /* sgi mode */ +#define NRATE_SGI_SHIFT 23 /* sgi mode */ +#define NRATE_LDPC_CODING 0x00400000 /* adv coding in use */ +#define NRATE_LDPC_SHIFT 22 /* ldpc shift */ + +#define NRATE_STF_SISO 0 /* stf mode SISO */ +#define NRATE_STF_CDD 1 /* stf mode CDD */ +#define NRATE_STF_STBC 2 /* stf mode STBC */ +#define NRATE_STF_SDM 3 /* stf mode SDM */ + +#define MAX_DMA_SEGS 4 + +/* # of entries in Tx FIFO */ +#define NTXD 64 +/* Max # of entries in Rx FIFO based on 4kb page size */ +#define NRXD 256 + +/* Amount of headroom to leave in Tx FIFO */ +#define TX_HEADROOM 4 + +/* try to keep this # rbufs posted to the chip */ +#define NRXBUFPOST 32 + +/* max # frames to process in brcms_c_recv() */ +#define RXBND 8 +/* max # tx status to process in wlc_txstatus() */ +#define TXSBND 8 + +/* brcmu_format_flags() bit description structure */ +struct brcms_c_bit_desc { + u32 bit; + const char *name; +}; + +/* + * The following table lists the buffer memory allocated to xmt fifos in HW. + * the size is in units of 256bytes(one block), total size is HW dependent + * ucode has default fifo partition, sw can overwrite if necessary + * + * This is documented in twiki under the topic UcodeTxFifo. Please ensure + * the twiki is updated before making changes. + */ + +/* Starting corerev for the fifo size table */ +#define XMTFIFOTBL_STARTREV 17 + +struct d11init { + __le16 addr; + __le16 size; + __le32 value; +}; + +struct edcf_acparam { + u8 ACI; + u8 ECW; + u16 TXOP; +} __packed; + +/* debug/trace */ +uint brcm_msg_level; + +/* TX FIFO number to WME/802.1E Access Category */ +static const u8 wme_fifo2ac[] = { + IEEE80211_AC_BK, + IEEE80211_AC_BE, + IEEE80211_AC_VI, + IEEE80211_AC_VO, + IEEE80211_AC_BE, + IEEE80211_AC_BE +}; + +/* ieee80211 Access Category to TX FIFO number */ +static const u8 wme_ac2fifo[] = { + TX_AC_VO_FIFO, + TX_AC_VI_FIFO, + TX_AC_BE_FIFO, + TX_AC_BK_FIFO +}; + +static const u16 xmtfifo_sz[][NFIFO] = { + /* corerev 17: 5120, 49152, 49152, 5376, 4352, 1280 */ + {20, 192, 192, 21, 17, 5}, + /* corerev 18: */ + {0, 0, 0, 0, 0, 0}, + /* corerev 19: */ + {0, 0, 0, 0, 0, 0}, + /* corerev 20: 5120, 49152, 49152, 5376, 4352, 1280 */ + {20, 192, 192, 21, 17, 5}, + /* corerev 21: 2304, 14848, 5632, 3584, 3584, 1280 */ + {9, 58, 22, 14, 14, 5}, + /* corerev 22: 5120, 49152, 49152, 5376, 4352, 1280 */ + {20, 192, 192, 21, 17, 5}, + /* corerev 23: 5120, 49152, 49152, 5376, 4352, 1280 */ + {20, 192, 192, 21, 17, 5}, + /* corerev 24: 2304, 14848, 5632, 3584, 3584, 1280 */ + {9, 58, 22, 14, 14, 5}, + /* corerev 25: */ + {0, 0, 0, 0, 0, 0}, + /* corerev 26: */ + {0, 0, 0, 0, 0, 0}, + /* corerev 27: */ + {0, 0, 0, 0, 0, 0}, + /* corerev 28: 2304, 14848, 5632, 3584, 3584, 1280 */ + {9, 58, 22, 14, 14, 5}, +}; + +#ifdef DEBUG +static const char * const fifo_names[] = { + "AC_BK", "AC_BE", "AC_VI", "AC_VO", "BCMC", "ATIM" }; +#else +static const char fifo_names[6][0]; +#endif + +#ifdef DEBUG +/* pointer to most recently allocated wl/wlc */ +static struct brcms_c_info *wlc_info_dbg = (struct brcms_c_info *) (NULL); +#endif + +/* Mapping of ieee80211 AC numbers to tx fifos */ +static const u8 ac_to_fifo_mapping[IEEE80211_NUM_ACS] = { + [IEEE80211_AC_VO] = TX_AC_VO_FIFO, + [IEEE80211_AC_VI] = TX_AC_VI_FIFO, + [IEEE80211_AC_BE] = TX_AC_BE_FIFO, + [IEEE80211_AC_BK] = TX_AC_BK_FIFO, +}; + +/* Mapping of tx fifos to ieee80211 AC numbers */ +static const u8 fifo_to_ac_mapping[IEEE80211_NUM_ACS] = { + [TX_AC_BK_FIFO] = IEEE80211_AC_BK, + [TX_AC_BE_FIFO] = IEEE80211_AC_BE, + [TX_AC_VI_FIFO] = IEEE80211_AC_VI, + [TX_AC_VO_FIFO] = IEEE80211_AC_VO, +}; + +static u8 brcms_ac_to_fifo(u8 ac) +{ + if (ac >= ARRAY_SIZE(ac_to_fifo_mapping)) + return TX_AC_BE_FIFO; + return ac_to_fifo_mapping[ac]; +} + +static u8 brcms_fifo_to_ac(u8 fifo) +{ + if (fifo >= ARRAY_SIZE(fifo_to_ac_mapping)) + return IEEE80211_AC_BE; + return fifo_to_ac_mapping[fifo]; +} + +/* Find basic rate for a given rate */ +static u8 brcms_basic_rate(struct brcms_c_info *wlc, u32 rspec) +{ + if (is_mcs_rate(rspec)) + return wlc->band->basic_rate[mcs_table[rspec & RSPEC_RATE_MASK] + .leg_ofdm]; + return wlc->band->basic_rate[rspec & RSPEC_RATE_MASK]; +} + +static u16 frametype(u32 rspec, u8 mimoframe) +{ + if (is_mcs_rate(rspec)) + return mimoframe; + return is_cck_rate(rspec) ? FT_CCK : FT_OFDM; +} + +/* currently the best mechanism for determining SIFS is the band in use */ +static u16 get_sifs(struct brcms_band *band) +{ + return band->bandtype == BRCM_BAND_5G ? APHY_SIFS_TIME : + BPHY_SIFS_TIME; +} + +/* + * Detect Card removed. + * Even checking an sbconfig register read will not false trigger when the core + * is in reset it breaks CF address mechanism. Accessing gphy phyversion will + * cause SB error if aphy is in reset on 4306B0-DB. Need a simple accessible + * reg with fixed 0/1 pattern (some platforms return all 0). + * If clocks are present, call the sb routine which will figure out if the + * device is removed. + */ +static bool brcms_deviceremoved(struct brcms_c_info *wlc) +{ + u32 macctrl; + + if (!wlc->hw->clk) + return ai_deviceremoved(wlc->hw->sih); + macctrl = bcma_read32(wlc->hw->d11core, + D11REGOFFS(maccontrol)); + return (macctrl & (MCTL_PSM_JMP_0 | MCTL_IHR_EN)) != MCTL_IHR_EN; +} + +/* sum the individual fifo tx pending packet counts */ +static int brcms_txpktpendtot(struct brcms_c_info *wlc) +{ + int i; + int pending = 0; + + for (i = 0; i < ARRAY_SIZE(wlc->hw->di); i++) + if (wlc->hw->di[i]) + pending += dma_txpending(wlc->hw->di[i]); + return pending; +} + +static bool brcms_is_mband_unlocked(struct brcms_c_info *wlc) +{ + return wlc->pub->_nbands > 1 && !wlc->bandlocked; +} + +static int brcms_chspec_bw(u16 chanspec) +{ + if (CHSPEC_IS40(chanspec)) + return BRCMS_40_MHZ; + if (CHSPEC_IS20(chanspec)) + return BRCMS_20_MHZ; + + return BRCMS_10_MHZ; +} + +static void brcms_c_bsscfg_mfree(struct brcms_bss_cfg *cfg) +{ + if (cfg == NULL) + return; + + kfree(cfg->current_bss); + kfree(cfg); +} + +static void brcms_c_detach_mfree(struct brcms_c_info *wlc) +{ + if (wlc == NULL) + return; + + brcms_c_bsscfg_mfree(wlc->bsscfg); + kfree(wlc->pub); + kfree(wlc->modulecb); + kfree(wlc->default_bss); + kfree(wlc->protection); + kfree(wlc->stf); + kfree(wlc->bandstate[0]); + kfree(wlc->corestate->macstat_snapshot); + kfree(wlc->corestate); + kfree(wlc->hw->bandstate[0]); + kfree(wlc->hw); + if (wlc->beacon) + dev_kfree_skb_any(wlc->beacon); + if (wlc->probe_resp) + dev_kfree_skb_any(wlc->probe_resp); + + /* free the wlc */ + kfree(wlc); + wlc = NULL; +} + +static struct brcms_bss_cfg *brcms_c_bsscfg_malloc(uint unit) +{ + struct brcms_bss_cfg *cfg; + + cfg = kzalloc(sizeof(struct brcms_bss_cfg), GFP_ATOMIC); + if (cfg == NULL) + goto fail; + + cfg->current_bss = kzalloc(sizeof(struct brcms_bss_info), GFP_ATOMIC); + if (cfg->current_bss == NULL) + goto fail; + + return cfg; + + fail: + brcms_c_bsscfg_mfree(cfg); + return NULL; +} + +static struct brcms_c_info * +brcms_c_attach_malloc(uint unit, uint *err, uint devid) +{ + struct brcms_c_info *wlc; + + wlc = kzalloc(sizeof(struct brcms_c_info), GFP_ATOMIC); + if (wlc == NULL) { + *err = 1002; + goto fail; + } + + /* allocate struct brcms_c_pub state structure */ + wlc->pub = kzalloc(sizeof(struct brcms_pub), GFP_ATOMIC); + if (wlc->pub == NULL) { + *err = 1003; + goto fail; + } + wlc->pub->wlc = wlc; + + /* allocate struct brcms_hardware state structure */ + + wlc->hw = kzalloc(sizeof(struct brcms_hardware), GFP_ATOMIC); + if (wlc->hw == NULL) { + *err = 1005; + goto fail; + } + wlc->hw->wlc = wlc; + + wlc->hw->bandstate[0] = + kzalloc(sizeof(struct brcms_hw_band) * MAXBANDS, GFP_ATOMIC); + if (wlc->hw->bandstate[0] == NULL) { + *err = 1006; + goto fail; + } else { + int i; + + for (i = 1; i < MAXBANDS; i++) + wlc->hw->bandstate[i] = (struct brcms_hw_band *) + ((unsigned long)wlc->hw->bandstate[0] + + (sizeof(struct brcms_hw_band) * i)); + } + + wlc->modulecb = + kzalloc(sizeof(struct modulecb) * BRCMS_MAXMODULES, GFP_ATOMIC); + if (wlc->modulecb == NULL) { + *err = 1009; + goto fail; + } + + wlc->default_bss = kzalloc(sizeof(struct brcms_bss_info), GFP_ATOMIC); + if (wlc->default_bss == NULL) { + *err = 1010; + goto fail; + } + + wlc->bsscfg = brcms_c_bsscfg_malloc(unit); + if (wlc->bsscfg == NULL) { + *err = 1011; + goto fail; + } + + wlc->protection = kzalloc(sizeof(struct brcms_protection), + GFP_ATOMIC); + if (wlc->protection == NULL) { + *err = 1016; + goto fail; + } + + wlc->stf = kzalloc(sizeof(struct brcms_stf), GFP_ATOMIC); + if (wlc->stf == NULL) { + *err = 1017; + goto fail; + } + + wlc->bandstate[0] = + kzalloc(sizeof(struct brcms_band)*MAXBANDS, GFP_ATOMIC); + if (wlc->bandstate[0] == NULL) { + *err = 1025; + goto fail; + } else { + int i; + + for (i = 1; i < MAXBANDS; i++) + wlc->bandstate[i] = (struct brcms_band *) + ((unsigned long)wlc->bandstate[0] + + (sizeof(struct brcms_band)*i)); + } + + wlc->corestate = kzalloc(sizeof(struct brcms_core), GFP_ATOMIC); + if (wlc->corestate == NULL) { + *err = 1026; + goto fail; + } + + wlc->corestate->macstat_snapshot = + kzalloc(sizeof(struct macstat), GFP_ATOMIC); + if (wlc->corestate->macstat_snapshot == NULL) { + *err = 1027; + goto fail; + } + + return wlc; + + fail: + brcms_c_detach_mfree(wlc); + return NULL; +} + +/* + * Update the slot timing for standard 11b/g (20us slots) + * or shortslot 11g (9us slots) + * The PSM needs to be suspended for this call. + */ +static void brcms_b_update_slot_timing(struct brcms_hardware *wlc_hw, + bool shortslot) +{ + struct bcma_device *core = wlc_hw->d11core; + + if (shortslot) { + /* 11g short slot: 11a timing */ + bcma_write16(core, D11REGOFFS(ifs_slot), 0x0207); + brcms_b_write_shm(wlc_hw, M_DOT11_SLOT, APHY_SLOT_TIME); + } else { + /* 11g long slot: 11b timing */ + bcma_write16(core, D11REGOFFS(ifs_slot), 0x0212); + brcms_b_write_shm(wlc_hw, M_DOT11_SLOT, BPHY_SLOT_TIME); + } +} + +/* + * calculate frame duration of a given rate and length, return + * time in usec unit + */ +static uint brcms_c_calc_frame_time(struct brcms_c_info *wlc, u32 ratespec, + u8 preamble_type, uint mac_len) +{ + uint nsyms, dur = 0, Ndps, kNdps; + uint rate = rspec2rate(ratespec); + + if (rate == 0) { + brcms_err(wlc->hw->d11core, "wl%d: WAR: using rate of 1 mbps\n", + wlc->pub->unit); + rate = BRCM_RATE_1M; + } + + if (is_mcs_rate(ratespec)) { + uint mcs = ratespec & RSPEC_RATE_MASK; + int tot_streams = mcs_2_txstreams(mcs) + rspec_stc(ratespec); + + dur = PREN_PREAMBLE + (tot_streams * PREN_PREAMBLE_EXT); + if (preamble_type == BRCMS_MM_PREAMBLE) + dur += PREN_MM_EXT; + /* 1000Ndbps = kbps * 4 */ + kNdps = mcs_2_rate(mcs, rspec_is40mhz(ratespec), + rspec_issgi(ratespec)) * 4; + + if (rspec_stc(ratespec) == 0) + nsyms = + CEIL((APHY_SERVICE_NBITS + 8 * mac_len + + APHY_TAIL_NBITS) * 1000, kNdps); + else + /* STBC needs to have even number of symbols */ + nsyms = + 2 * + CEIL((APHY_SERVICE_NBITS + 8 * mac_len + + APHY_TAIL_NBITS) * 1000, 2 * kNdps); + + dur += APHY_SYMBOL_TIME * nsyms; + if (wlc->band->bandtype == BRCM_BAND_2G) + dur += DOT11_OFDM_SIGNAL_EXTENSION; + } else if (is_ofdm_rate(rate)) { + dur = APHY_PREAMBLE_TIME; + dur += APHY_SIGNAL_TIME; + /* Ndbps = Mbps * 4 = rate(500Kbps) * 2 */ + Ndps = rate * 2; + /* NSyms = CEILING((SERVICE + 8*NBytes + TAIL) / Ndbps) */ + nsyms = + CEIL((APHY_SERVICE_NBITS + 8 * mac_len + APHY_TAIL_NBITS), + Ndps); + dur += APHY_SYMBOL_TIME * nsyms; + if (wlc->band->bandtype == BRCM_BAND_2G) + dur += DOT11_OFDM_SIGNAL_EXTENSION; + } else { + /* + * calc # bits * 2 so factor of 2 in rate (1/2 mbps) + * will divide out + */ + mac_len = mac_len * 8 * 2; + /* calc ceiling of bits/rate = microseconds of air time */ + dur = (mac_len + rate - 1) / rate; + if (preamble_type & BRCMS_SHORT_PREAMBLE) + dur += BPHY_PLCP_SHORT_TIME; + else + dur += BPHY_PLCP_TIME; + } + return dur; +} + +static void brcms_c_write_inits(struct brcms_hardware *wlc_hw, + const struct d11init *inits) +{ + struct bcma_device *core = wlc_hw->d11core; + int i; + uint offset; + u16 size; + u32 value; + + brcms_dbg_info(wlc_hw->d11core, "wl%d\n", wlc_hw->unit); + + for (i = 0; inits[i].addr != cpu_to_le16(0xffff); i++) { + size = le16_to_cpu(inits[i].size); + offset = le16_to_cpu(inits[i].addr); + value = le32_to_cpu(inits[i].value); + if (size == 2) + bcma_write16(core, offset, value); + else if (size == 4) + bcma_write32(core, offset, value); + else + break; + } +} + +static void brcms_c_write_mhf(struct brcms_hardware *wlc_hw, u16 *mhfs) +{ + u8 idx; + u16 addr[] = { + M_HOST_FLAGS1, M_HOST_FLAGS2, M_HOST_FLAGS3, M_HOST_FLAGS4, + M_HOST_FLAGS5 + }; + + for (idx = 0; idx < MHFMAX; idx++) + brcms_b_write_shm(wlc_hw, addr[idx], mhfs[idx]); +} + +static void brcms_c_ucode_bsinit(struct brcms_hardware *wlc_hw) +{ + struct brcms_ucode *ucode = &wlc_hw->wlc->wl->ucode; + + /* init microcode host flags */ + brcms_c_write_mhf(wlc_hw, wlc_hw->band->mhfs); + + /* do band-specific ucode IHR, SHM, and SCR inits */ + if (D11REV_IS(wlc_hw->corerev, 17) || D11REV_IS(wlc_hw->corerev, 23)) { + if (BRCMS_ISNPHY(wlc_hw->band)) + brcms_c_write_inits(wlc_hw, ucode->d11n0bsinitvals16); + else + brcms_err(wlc_hw->d11core, + "%s: wl%d: unsupported phy in corerev %d\n", + __func__, wlc_hw->unit, + wlc_hw->corerev); + } else { + if (D11REV_IS(wlc_hw->corerev, 24)) { + if (BRCMS_ISLCNPHY(wlc_hw->band)) + brcms_c_write_inits(wlc_hw, + ucode->d11lcn0bsinitvals24); + else + brcms_err(wlc_hw->d11core, + "%s: wl%d: unsupported phy in core rev %d\n", + __func__, wlc_hw->unit, + wlc_hw->corerev); + } else { + brcms_err(wlc_hw->d11core, + "%s: wl%d: unsupported corerev %d\n", + __func__, wlc_hw->unit, wlc_hw->corerev); + } + } +} + +static void brcms_b_core_ioctl(struct brcms_hardware *wlc_hw, u32 m, u32 v) +{ + struct bcma_device *core = wlc_hw->d11core; + u32 ioctl = bcma_aread32(core, BCMA_IOCTL) & ~m; + + bcma_awrite32(core, BCMA_IOCTL, ioctl | v); +} + +static void brcms_b_core_phy_clk(struct brcms_hardware *wlc_hw, bool clk) +{ + brcms_dbg_info(wlc_hw->d11core, "wl%d: clk %d\n", wlc_hw->unit, clk); + + wlc_hw->phyclk = clk; + + if (OFF == clk) { /* clear gmode bit, put phy into reset */ + + brcms_b_core_ioctl(wlc_hw, (SICF_PRST | SICF_FGC | SICF_GMODE), + (SICF_PRST | SICF_FGC)); + udelay(1); + brcms_b_core_ioctl(wlc_hw, (SICF_PRST | SICF_FGC), SICF_PRST); + udelay(1); + + } else { /* take phy out of reset */ + + brcms_b_core_ioctl(wlc_hw, (SICF_PRST | SICF_FGC), SICF_FGC); + udelay(1); + brcms_b_core_ioctl(wlc_hw, SICF_FGC, 0); + udelay(1); + + } +} + +/* low-level band switch utility routine */ +static void brcms_c_setxband(struct brcms_hardware *wlc_hw, uint bandunit) +{ + brcms_dbg_mac80211(wlc_hw->d11core, "wl%d: bandunit %d\n", wlc_hw->unit, + bandunit); + + wlc_hw->band = wlc_hw->bandstate[bandunit]; + + /* + * BMAC_NOTE: + * until we eliminate need for wlc->band refs in low level code + */ + wlc_hw->wlc->band = wlc_hw->wlc->bandstate[bandunit]; + + /* set gmode core flag */ + if (wlc_hw->sbclk && !wlc_hw->noreset) { + u32 gmode = 0; + + if (bandunit == 0) + gmode = SICF_GMODE; + + brcms_b_core_ioctl(wlc_hw, SICF_GMODE, gmode); + } +} + +/* switch to new band but leave it inactive */ +static u32 brcms_c_setband_inact(struct brcms_c_info *wlc, uint bandunit) +{ + struct brcms_hardware *wlc_hw = wlc->hw; + u32 macintmask; + u32 macctrl; + + brcms_dbg_mac80211(wlc_hw->d11core, "wl%d\n", wlc_hw->unit); + macctrl = bcma_read32(wlc_hw->d11core, + D11REGOFFS(maccontrol)); + WARN_ON((macctrl & MCTL_EN_MAC) != 0); + + /* disable interrupts */ + macintmask = brcms_intrsoff(wlc->wl); + + /* radio off */ + wlc_phy_switch_radio(wlc_hw->band->pi, OFF); + + brcms_b_core_phy_clk(wlc_hw, OFF); + + brcms_c_setxband(wlc_hw, bandunit); + + return macintmask; +} + +/* process an individual struct tx_status */ +static bool +brcms_c_dotxstatus(struct brcms_c_info *wlc, struct tx_status *txs) +{ + struct sk_buff *p = NULL; + uint queue = NFIFO; + struct dma_pub *dma = NULL; + struct d11txh *txh = NULL; + struct scb *scb = NULL; + bool free_pdu; + int tx_rts, tx_frame_count, tx_rts_count; + uint totlen, supr_status; + bool lastframe; + struct ieee80211_hdr *h; + u16 mcl; + struct ieee80211_tx_info *tx_info; + struct ieee80211_tx_rate *txrate; + int i; + bool fatal = true; + + trace_brcms_txstatus(&wlc->hw->d11core->dev, txs->framelen, + txs->frameid, txs->status, txs->lasttxtime, + txs->sequence, txs->phyerr, txs->ackphyrxsh); + + /* discard intermediate indications for ucode with one legitimate case: + * e.g. if "useRTS" is set. ucode did a successful rts/cts exchange, + * but the subsequent tx of DATA failed. so it will start rts/cts + * from the beginning (resetting the rts transmission count) + */ + if (!(txs->status & TX_STATUS_AMPDU) + && (txs->status & TX_STATUS_INTERMEDIATE)) { + brcms_dbg_tx(wlc->hw->d11core, "INTERMEDIATE but not AMPDU\n"); + fatal = false; + goto out; + } + + queue = txs->frameid & TXFID_QUEUE_MASK; + if (queue >= NFIFO) { + brcms_err(wlc->hw->d11core, "queue %u >= NFIFO\n", queue); + goto out; + } + + dma = wlc->hw->di[queue]; + + p = dma_getnexttxp(wlc->hw->di[queue], DMA_RANGE_TRANSMITTED); + if (p == NULL) { + brcms_err(wlc->hw->d11core, "dma_getnexttxp returned null!\n"); + goto out; + } + + txh = (struct d11txh *) (p->data); + mcl = le16_to_cpu(txh->MacTxControlLow); + + if (txs->phyerr) + brcms_err(wlc->hw->d11core, "phyerr 0x%x, rate 0x%x\n", + txs->phyerr, txh->MainRates); + + if (txs->frameid != le16_to_cpu(txh->TxFrameID)) { + brcms_err(wlc->hw->d11core, "frameid != txh->TxFrameID\n"); + goto out; + } + tx_info = IEEE80211_SKB_CB(p); + h = (struct ieee80211_hdr *)((u8 *) (txh + 1) + D11_PHY_HDR_LEN); + + if (tx_info->rate_driver_data[0]) + scb = &wlc->pri_scb; + + if (tx_info->flags & IEEE80211_TX_CTL_AMPDU) { + brcms_c_ampdu_dotxstatus(wlc->ampdu, scb, p, txs); + fatal = false; + goto out; + } + + /* + * brcms_c_ampdu_dotxstatus() will trace tx descriptors for AMPDU + * frames; this traces them for the rest. + */ + trace_brcms_txdesc(&wlc->hw->d11core->dev, txh, sizeof(*txh)); + + supr_status = txs->status & TX_STATUS_SUPR_MASK; + if (supr_status == TX_STATUS_SUPR_BADCH) { + unsigned xfts = le16_to_cpu(txh->XtraFrameTypes); + brcms_dbg_tx(wlc->hw->d11core, + "Pkt tx suppressed, dest chan %u, current %d\n", + (xfts >> XFTS_CHANNEL_SHIFT) & 0xff, + CHSPEC_CHANNEL(wlc->default_bss->chanspec)); + } + + tx_rts = le16_to_cpu(txh->MacTxControlLow) & TXC_SENDRTS; + tx_frame_count = + (txs->status & TX_STATUS_FRM_RTX_MASK) >> TX_STATUS_FRM_RTX_SHIFT; + tx_rts_count = + (txs->status & TX_STATUS_RTS_RTX_MASK) >> TX_STATUS_RTS_RTX_SHIFT; + + lastframe = !ieee80211_has_morefrags(h->frame_control); + + if (!lastframe) { + brcms_err(wlc->hw->d11core, "Not last frame!\n"); + } else { + /* + * Set information to be consumed by Minstrel ht. + * + * The "fallback limit" is the number of tx attempts a given + * MPDU is sent at the "primary" rate. Tx attempts beyond that + * limit are sent at the "secondary" rate. + * A 'short frame' does not exceed RTS treshold. + */ + u16 sfbl, /* Short Frame Rate Fallback Limit */ + lfbl, /* Long Frame Rate Fallback Limit */ + fbl; + + if (queue < IEEE80211_NUM_ACS) { + sfbl = GFIELD(wlc->wme_retries[wme_fifo2ac[queue]], + EDCF_SFB); + lfbl = GFIELD(wlc->wme_retries[wme_fifo2ac[queue]], + EDCF_LFB); + } else { + sfbl = wlc->SFBL; + lfbl = wlc->LFBL; + } + + txrate = tx_info->status.rates; + if (txrate[0].flags & IEEE80211_TX_RC_USE_RTS_CTS) + fbl = lfbl; + else + fbl = sfbl; + + ieee80211_tx_info_clear_status(tx_info); + + if ((tx_frame_count > fbl) && (txrate[1].idx >= 0)) { + /* + * rate selection requested a fallback rate + * and we used it + */ + txrate[0].count = fbl; + txrate[1].count = tx_frame_count - fbl; + } else { + /* + * rate selection did not request fallback rate, or + * we didn't need it + */ + txrate[0].count = tx_frame_count; + /* + * rc80211_minstrel.c:minstrel_tx_status() expects + * unused rates to be marked with idx = -1 + */ + txrate[1].idx = -1; + txrate[1].count = 0; + } + + /* clear the rest of the rates */ + for (i = 2; i < IEEE80211_TX_MAX_RATES; i++) { + txrate[i].idx = -1; + txrate[i].count = 0; + } + + if (txs->status & TX_STATUS_ACK_RCV) + tx_info->flags |= IEEE80211_TX_STAT_ACK; + } + + totlen = p->len; + free_pdu = true; + + if (lastframe) { + /* remove PLCP & Broadcom tx descriptor header */ + skb_pull(p, D11_PHY_HDR_LEN); + skb_pull(p, D11_TXH_LEN); + ieee80211_tx_status_irqsafe(wlc->pub->ieee_hw, p); + } else { + brcms_err(wlc->hw->d11core, + "%s: Not last frame => not calling tx_status\n", + __func__); + } + + fatal = false; + + out: + if (fatal) { + if (txh) + trace_brcms_txdesc(&wlc->hw->d11core->dev, txh, + sizeof(*txh)); + if (p) + brcmu_pkt_buf_free_skb(p); + } + + if (dma && queue < NFIFO) { + u16 ac_queue = brcms_fifo_to_ac(queue); + if (dma->txavail > TX_HEADROOM && queue < TX_BCMC_FIFO && + ieee80211_queue_stopped(wlc->pub->ieee_hw, ac_queue)) + ieee80211_wake_queue(wlc->pub->ieee_hw, ac_queue); + dma_kick_tx(dma); + } + + return fatal; +} + +/* process tx completion events in BMAC + * Return true if more tx status need to be processed. false otherwise. + */ +static bool +brcms_b_txstatus(struct brcms_hardware *wlc_hw, bool bound, bool *fatal) +{ + struct bcma_device *core; + struct tx_status txstatus, *txs; + u32 s1, s2; + uint n = 0; + /* + * Param 'max_tx_num' indicates max. # tx status to process before + * break out. + */ + uint max_tx_num = bound ? TXSBND : -1; + + txs = &txstatus; + core = wlc_hw->d11core; + *fatal = false; + + while (n < max_tx_num) { + s1 = bcma_read32(core, D11REGOFFS(frmtxstatus)); + if (s1 == 0xffffffff) { + brcms_err(core, "wl%d: %s: dead chip\n", wlc_hw->unit, + __func__); + *fatal = true; + return false; + } + /* only process when valid */ + if (!(s1 & TXS_V)) + break; + + s2 = bcma_read32(core, D11REGOFFS(frmtxstatus2)); + txs->status = s1 & TXS_STATUS_MASK; + txs->frameid = (s1 & TXS_FID_MASK) >> TXS_FID_SHIFT; + txs->sequence = s2 & TXS_SEQ_MASK; + txs->phyerr = (s2 & TXS_PTX_MASK) >> TXS_PTX_SHIFT; + txs->lasttxtime = 0; + + *fatal = brcms_c_dotxstatus(wlc_hw->wlc, txs); + if (*fatal == true) + return false; + n++; + } + + return n >= max_tx_num; +} + +static void brcms_c_tbtt(struct brcms_c_info *wlc) +{ + if (wlc->bsscfg->type == BRCMS_TYPE_ADHOC) + /* + * DirFrmQ is now valid...defer setting until end + * of ATIM window + */ + wlc->qvalid |= MCMD_DIRFRMQVAL; +} + +/* set initial host flags value */ +static void +brcms_c_mhfdef(struct brcms_c_info *wlc, u16 *mhfs, u16 mhf2_init) +{ + struct brcms_hardware *wlc_hw = wlc->hw; + + memset(mhfs, 0, MHFMAX * sizeof(u16)); + + mhfs[MHF2] |= mhf2_init; + + /* prohibit use of slowclock on multifunction boards */ + if (wlc_hw->boardflags & BFL_NOPLLDOWN) + mhfs[MHF1] |= MHF1_FORCEFASTCLK; + + if (BRCMS_ISNPHY(wlc_hw->band) && NREV_LT(wlc_hw->band->phyrev, 2)) { + mhfs[MHF2] |= MHF2_NPHY40MHZ_WAR; + mhfs[MHF1] |= MHF1_IQSWAP_WAR; + } +} + +static uint +dmareg(uint direction, uint fifonum) +{ + if (direction == DMA_TX) + return offsetof(struct d11regs, fifo64regs[fifonum].dmaxmt); + return offsetof(struct d11regs, fifo64regs[fifonum].dmarcv); +} + +static bool brcms_b_attach_dmapio(struct brcms_c_info *wlc, uint j, bool wme) +{ + uint i; + char name[8]; + /* + * ucode host flag 2 needed for pio mode, independent of band and fifo + */ + u16 pio_mhf2 = 0; + struct brcms_hardware *wlc_hw = wlc->hw; + uint unit = wlc_hw->unit; + + /* name and offsets for dma_attach */ + snprintf(name, sizeof(name), "wl%d", unit); + + if (wlc_hw->di[0] == NULL) { /* Init FIFOs */ + int dma_attach_err = 0; + + /* + * FIFO 0 + * TX: TX_AC_BK_FIFO (TX AC Background data packets) + * RX: RX_FIFO (RX data packets) + */ + wlc_hw->di[0] = dma_attach(name, wlc, + (wme ? dmareg(DMA_TX, 0) : 0), + dmareg(DMA_RX, 0), + (wme ? NTXD : 0), NRXD, + RXBUFSZ, -1, NRXBUFPOST, + BRCMS_HWRXOFF); + dma_attach_err |= (NULL == wlc_hw->di[0]); + + /* + * FIFO 1 + * TX: TX_AC_BE_FIFO (TX AC Best-Effort data packets) + * (legacy) TX_DATA_FIFO (TX data packets) + * RX: UNUSED + */ + wlc_hw->di[1] = dma_attach(name, wlc, + dmareg(DMA_TX, 1), 0, + NTXD, 0, 0, -1, 0, 0); + dma_attach_err |= (NULL == wlc_hw->di[1]); + + /* + * FIFO 2 + * TX: TX_AC_VI_FIFO (TX AC Video data packets) + * RX: UNUSED + */ + wlc_hw->di[2] = dma_attach(name, wlc, + dmareg(DMA_TX, 2), 0, + NTXD, 0, 0, -1, 0, 0); + dma_attach_err |= (NULL == wlc_hw->di[2]); + /* + * FIFO 3 + * TX: TX_AC_VO_FIFO (TX AC Voice data packets) + * (legacy) TX_CTL_FIFO (TX control & mgmt packets) + */ + wlc_hw->di[3] = dma_attach(name, wlc, + dmareg(DMA_TX, 3), + 0, NTXD, 0, 0, -1, + 0, 0); + dma_attach_err |= (NULL == wlc_hw->di[3]); +/* Cleaner to leave this as if with AP defined */ + + if (dma_attach_err) { + brcms_err(wlc_hw->d11core, + "wl%d: wlc_attach: dma_attach failed\n", + unit); + return false; + } + + /* get pointer to dma engine tx flow control variable */ + for (i = 0; i < NFIFO; i++) + if (wlc_hw->di[i]) + wlc_hw->txavail[i] = + (uint *) dma_getvar(wlc_hw->di[i], + "&txavail"); + } + + /* initial ucode host flags */ + brcms_c_mhfdef(wlc, wlc_hw->band->mhfs, pio_mhf2); + + return true; +} + +static void brcms_b_detach_dmapio(struct brcms_hardware *wlc_hw) +{ + uint j; + + for (j = 0; j < NFIFO; j++) { + if (wlc_hw->di[j]) { + dma_detach(wlc_hw->di[j]); + wlc_hw->di[j] = NULL; + } + } +} + +/* + * Initialize brcms_c_info default values ... + * may get overrides later in this function + * BMAC_NOTES, move low out and resolve the dangling ones + */ +static void brcms_b_info_init(struct brcms_hardware *wlc_hw) +{ + struct brcms_c_info *wlc = wlc_hw->wlc; + + /* set default sw macintmask value */ + wlc->defmacintmask = DEF_MACINTMASK; + + /* various 802.11g modes */ + wlc_hw->shortslot = false; + + wlc_hw->SFBL = RETRY_SHORT_FB; + wlc_hw->LFBL = RETRY_LONG_FB; + + /* default mac retry limits */ + wlc_hw->SRL = RETRY_SHORT_DEF; + wlc_hw->LRL = RETRY_LONG_DEF; + wlc_hw->chanspec = ch20mhz_chspec(1); +} + +static void brcms_b_wait_for_wake(struct brcms_hardware *wlc_hw) +{ + /* delay before first read of ucode state */ + udelay(40); + + /* wait until ucode is no longer asleep */ + SPINWAIT((brcms_b_read_shm(wlc_hw, M_UCODE_DBGST) == + DBGST_ASLEEP), wlc_hw->wlc->fastpwrup_dly); +} + +/* control chip clock to save power, enable dynamic clock or force fast clock */ +static void brcms_b_clkctl_clk(struct brcms_hardware *wlc_hw, enum bcma_clkmode mode) +{ + if (ai_get_cccaps(wlc_hw->sih) & CC_CAP_PMU) { + /* new chips with PMU, CCS_FORCEHT will distribute the HT clock + * on backplane, but mac core will still run on ALP(not HT) when + * it enters powersave mode, which means the FCA bit may not be + * set. Should wakeup mac if driver wants it to run on HT. + */ + + if (wlc_hw->clk) { + if (mode == BCMA_CLKMODE_FAST) { + bcma_set32(wlc_hw->d11core, + D11REGOFFS(clk_ctl_st), + CCS_FORCEHT); + + udelay(64); + + SPINWAIT( + ((bcma_read32(wlc_hw->d11core, + D11REGOFFS(clk_ctl_st)) & + CCS_HTAVAIL) == 0), + PMU_MAX_TRANSITION_DLY); + WARN_ON(!(bcma_read32(wlc_hw->d11core, + D11REGOFFS(clk_ctl_st)) & + CCS_HTAVAIL)); + } else { + if ((ai_get_pmurev(wlc_hw->sih) == 0) && + (bcma_read32(wlc_hw->d11core, + D11REGOFFS(clk_ctl_st)) & + (CCS_FORCEHT | CCS_HTAREQ))) + SPINWAIT( + ((bcma_read32(wlc_hw->d11core, + offsetof(struct d11regs, + clk_ctl_st)) & + CCS_HTAVAIL) == 0), + PMU_MAX_TRANSITION_DLY); + bcma_mask32(wlc_hw->d11core, + D11REGOFFS(clk_ctl_st), + ~CCS_FORCEHT); + } + } + wlc_hw->forcefastclk = (mode == BCMA_CLKMODE_FAST); + } else { + + /* old chips w/o PMU, force HT through cc, + * then use FCA to verify mac is running fast clock + */ + + wlc_hw->forcefastclk = ai_clkctl_cc(wlc_hw->sih, mode); + + /* check fast clock is available (if core is not in reset) */ + if (wlc_hw->forcefastclk && wlc_hw->clk) + WARN_ON(!(bcma_aread32(wlc_hw->d11core, BCMA_IOST) & + SISF_FCLKA)); + + /* + * keep the ucode wake bit on if forcefastclk is on since we + * do not want ucode to put us back to slow clock when it dozes + * for PM mode. Code below matches the wake override bit with + * current forcefastclk state. Only setting bit in wake_override + * instead of waking ucode immediately since old code had this + * behavior. Older code set wlc->forcefastclk but only had the + * wake happen if the wakup_ucode work (protected by an up + * check) was executed just below. + */ + if (wlc_hw->forcefastclk) + mboolset(wlc_hw->wake_override, + BRCMS_WAKE_OVERRIDE_FORCEFAST); + else + mboolclr(wlc_hw->wake_override, + BRCMS_WAKE_OVERRIDE_FORCEFAST); + } +} + +/* set or clear ucode host flag bits + * it has an optimization for no-change write + * it only writes through shared memory when the core has clock; + * pre-CLK changes should use wlc_write_mhf to get around the optimization + * + * + * bands values are: BRCM_BAND_AUTO <--- Current band only + * BRCM_BAND_5G <--- 5G band only + * BRCM_BAND_2G <--- 2G band only + * BRCM_BAND_ALL <--- All bands + */ +void +brcms_b_mhf(struct brcms_hardware *wlc_hw, u8 idx, u16 mask, u16 val, + int bands) +{ + u16 save; + u16 addr[MHFMAX] = { + M_HOST_FLAGS1, M_HOST_FLAGS2, M_HOST_FLAGS3, M_HOST_FLAGS4, + M_HOST_FLAGS5 + }; + struct brcms_hw_band *band; + + if ((val & ~mask) || idx >= MHFMAX) + return; /* error condition */ + + switch (bands) { + /* Current band only or all bands, + * then set the band to current band + */ + case BRCM_BAND_AUTO: + case BRCM_BAND_ALL: + band = wlc_hw->band; + break; + case BRCM_BAND_5G: + band = wlc_hw->bandstate[BAND_5G_INDEX]; + break; + case BRCM_BAND_2G: + band = wlc_hw->bandstate[BAND_2G_INDEX]; + break; + default: + band = NULL; /* error condition */ + } + + if (band) { + save = band->mhfs[idx]; + band->mhfs[idx] = (band->mhfs[idx] & ~mask) | val; + + /* optimization: only write through if changed, and + * changed band is the current band + */ + if (wlc_hw->clk && (band->mhfs[idx] != save) + && (band == wlc_hw->band)) + brcms_b_write_shm(wlc_hw, addr[idx], + (u16) band->mhfs[idx]); + } + + if (bands == BRCM_BAND_ALL) { + wlc_hw->bandstate[0]->mhfs[idx] = + (wlc_hw->bandstate[0]->mhfs[idx] & ~mask) | val; + wlc_hw->bandstate[1]->mhfs[idx] = + (wlc_hw->bandstate[1]->mhfs[idx] & ~mask) | val; + } +} + +/* set the maccontrol register to desired reset state and + * initialize the sw cache of the register + */ +static void brcms_c_mctrl_reset(struct brcms_hardware *wlc_hw) +{ + /* IHR accesses are always enabled, PSM disabled, HPS off and WAKE on */ + wlc_hw->maccontrol = 0; + wlc_hw->suspended_fifos = 0; + wlc_hw->wake_override = 0; + wlc_hw->mute_override = 0; + brcms_b_mctrl(wlc_hw, ~0, MCTL_IHR_EN | MCTL_WAKE); +} + +/* + * write the software state of maccontrol and + * overrides to the maccontrol register + */ +static void brcms_c_mctrl_write(struct brcms_hardware *wlc_hw) +{ + u32 maccontrol = wlc_hw->maccontrol; + + /* OR in the wake bit if overridden */ + if (wlc_hw->wake_override) + maccontrol |= MCTL_WAKE; + + /* set AP and INFRA bits for mute if needed */ + if (wlc_hw->mute_override) { + maccontrol &= ~(MCTL_AP); + maccontrol |= MCTL_INFRA; + } + + bcma_write32(wlc_hw->d11core, D11REGOFFS(maccontrol), + maccontrol); +} + +/* set or clear maccontrol bits */ +void brcms_b_mctrl(struct brcms_hardware *wlc_hw, u32 mask, u32 val) +{ + u32 maccontrol; + u32 new_maccontrol; + + if (val & ~mask) + return; /* error condition */ + maccontrol = wlc_hw->maccontrol; + new_maccontrol = (maccontrol & ~mask) | val; + + /* if the new maccontrol value is the same as the old, nothing to do */ + if (new_maccontrol == maccontrol) + return; + + /* something changed, cache the new value */ + wlc_hw->maccontrol = new_maccontrol; + + /* write the new values with overrides applied */ + brcms_c_mctrl_write(wlc_hw); +} + +void brcms_c_ucode_wake_override_set(struct brcms_hardware *wlc_hw, + u32 override_bit) +{ + if (wlc_hw->wake_override || (wlc_hw->maccontrol & MCTL_WAKE)) { + mboolset(wlc_hw->wake_override, override_bit); + return; + } + + mboolset(wlc_hw->wake_override, override_bit); + + brcms_c_mctrl_write(wlc_hw); + brcms_b_wait_for_wake(wlc_hw); +} + +void brcms_c_ucode_wake_override_clear(struct brcms_hardware *wlc_hw, + u32 override_bit) +{ + mboolclr(wlc_hw->wake_override, override_bit); + + if (wlc_hw->wake_override || (wlc_hw->maccontrol & MCTL_WAKE)) + return; + + brcms_c_mctrl_write(wlc_hw); +} + +/* When driver needs ucode to stop beaconing, it has to make sure that + * MCTL_AP is clear and MCTL_INFRA is set + * Mode MCTL_AP MCTL_INFRA + * AP 1 1 + * STA 0 1 <--- This will ensure no beacons + * IBSS 0 0 + */ +static void brcms_c_ucode_mute_override_set(struct brcms_hardware *wlc_hw) +{ + wlc_hw->mute_override = 1; + + /* if maccontrol already has AP == 0 and INFRA == 1 without this + * override, then there is no change to write + */ + if ((wlc_hw->maccontrol & (MCTL_AP | MCTL_INFRA)) == MCTL_INFRA) + return; + + brcms_c_mctrl_write(wlc_hw); +} + +/* Clear the override on AP and INFRA bits */ +static void brcms_c_ucode_mute_override_clear(struct brcms_hardware *wlc_hw) +{ + if (wlc_hw->mute_override == 0) + return; + + wlc_hw->mute_override = 0; + + /* if maccontrol already has AP == 0 and INFRA == 1 without this + * override, then there is no change to write + */ + if ((wlc_hw->maccontrol & (MCTL_AP | MCTL_INFRA)) == MCTL_INFRA) + return; + + brcms_c_mctrl_write(wlc_hw); +} + +/* + * Write a MAC address to the given match reg offset in the RXE match engine. + */ +static void +brcms_b_set_addrmatch(struct brcms_hardware *wlc_hw, int match_reg_offset, + const u8 *addr) +{ + struct bcma_device *core = wlc_hw->d11core; + u16 mac_l; + u16 mac_m; + u16 mac_h; + + brcms_dbg_rx(core, "wl%d: brcms_b_set_addrmatch\n", wlc_hw->unit); + + mac_l = addr[0] | (addr[1] << 8); + mac_m = addr[2] | (addr[3] << 8); + mac_h = addr[4] | (addr[5] << 8); + + /* enter the MAC addr into the RXE match registers */ + bcma_write16(core, D11REGOFFS(rcm_ctl), + RCM_INC_DATA | match_reg_offset); + bcma_write16(core, D11REGOFFS(rcm_mat_data), mac_l); + bcma_write16(core, D11REGOFFS(rcm_mat_data), mac_m); + bcma_write16(core, D11REGOFFS(rcm_mat_data), mac_h); +} + +void +brcms_b_write_template_ram(struct brcms_hardware *wlc_hw, int offset, int len, + void *buf) +{ + struct bcma_device *core = wlc_hw->d11core; + u32 word; + __le32 word_le; + __be32 word_be; + bool be_bit; + brcms_dbg_info(core, "wl%d\n", wlc_hw->unit); + + bcma_write32(core, D11REGOFFS(tplatewrptr), offset); + + /* if MCTL_BIGEND bit set in mac control register, + * the chip swaps data in fifo, as well as data in + * template ram + */ + be_bit = (bcma_read32(core, D11REGOFFS(maccontrol)) & MCTL_BIGEND) != 0; + + while (len > 0) { + memcpy(&word, buf, sizeof(u32)); + + if (be_bit) { + word_be = cpu_to_be32(word); + word = *(u32 *)&word_be; + } else { + word_le = cpu_to_le32(word); + word = *(u32 *)&word_le; + } + + bcma_write32(core, D11REGOFFS(tplatewrdata), word); + + buf = (u8 *) buf + sizeof(u32); + len -= sizeof(u32); + } +} + +static void brcms_b_set_cwmin(struct brcms_hardware *wlc_hw, u16 newmin) +{ + wlc_hw->band->CWmin = newmin; + + bcma_write32(wlc_hw->d11core, D11REGOFFS(objaddr), + OBJADDR_SCR_SEL | S_DOT11_CWMIN); + (void)bcma_read32(wlc_hw->d11core, D11REGOFFS(objaddr)); + bcma_write32(wlc_hw->d11core, D11REGOFFS(objdata), newmin); +} + +static void brcms_b_set_cwmax(struct brcms_hardware *wlc_hw, u16 newmax) +{ + wlc_hw->band->CWmax = newmax; + + bcma_write32(wlc_hw->d11core, D11REGOFFS(objaddr), + OBJADDR_SCR_SEL | S_DOT11_CWMAX); + (void)bcma_read32(wlc_hw->d11core, D11REGOFFS(objaddr)); + bcma_write32(wlc_hw->d11core, D11REGOFFS(objdata), newmax); +} + +void brcms_b_bw_set(struct brcms_hardware *wlc_hw, u16 bw) +{ + bool fastclk; + + /* request FAST clock if not on */ + fastclk = wlc_hw->forcefastclk; + if (!fastclk) + brcms_b_clkctl_clk(wlc_hw, BCMA_CLKMODE_FAST); + + wlc_phy_bw_state_set(wlc_hw->band->pi, bw); + + brcms_b_phy_reset(wlc_hw); + wlc_phy_init(wlc_hw->band->pi, wlc_phy_chanspec_get(wlc_hw->band->pi)); + + /* restore the clk */ + if (!fastclk) + brcms_b_clkctl_clk(wlc_hw, BCMA_CLKMODE_DYNAMIC); +} + +static void brcms_b_upd_synthpu(struct brcms_hardware *wlc_hw) +{ + u16 v; + struct brcms_c_info *wlc = wlc_hw->wlc; + /* update SYNTHPU_DLY */ + + if (BRCMS_ISLCNPHY(wlc->band)) + v = SYNTHPU_DLY_LPPHY_US; + else if (BRCMS_ISNPHY(wlc->band) && (NREV_GE(wlc->band->phyrev, 3))) + v = SYNTHPU_DLY_NPHY_US; + else + v = SYNTHPU_DLY_BPHY_US; + + brcms_b_write_shm(wlc_hw, M_SYNTHPU_DLY, v); +} + +static void brcms_c_ucode_txant_set(struct brcms_hardware *wlc_hw) +{ + u16 phyctl; + u16 phytxant = wlc_hw->bmac_phytxant; + u16 mask = PHY_TXC_ANT_MASK; + + /* set the Probe Response frame phy control word */ + phyctl = brcms_b_read_shm(wlc_hw, M_CTXPRS_BLK + C_CTX_PCTLWD_POS); + phyctl = (phyctl & ~mask) | phytxant; + brcms_b_write_shm(wlc_hw, M_CTXPRS_BLK + C_CTX_PCTLWD_POS, phyctl); + + /* set the Response (ACK/CTS) frame phy control word */ + phyctl = brcms_b_read_shm(wlc_hw, M_RSP_PCTLWD); + phyctl = (phyctl & ~mask) | phytxant; + brcms_b_write_shm(wlc_hw, M_RSP_PCTLWD, phyctl); +} + +static u16 brcms_b_ofdm_ratetable_offset(struct brcms_hardware *wlc_hw, + u8 rate) +{ + uint i; + u8 plcp_rate = 0; + struct plcp_signal_rate_lookup { + u8 rate; + u8 signal_rate; + }; + /* OFDM RATE sub-field of PLCP SIGNAL field, per 802.11 sec 17.3.4.1 */ + const struct plcp_signal_rate_lookup rate_lookup[] = { + {BRCM_RATE_6M, 0xB}, + {BRCM_RATE_9M, 0xF}, + {BRCM_RATE_12M, 0xA}, + {BRCM_RATE_18M, 0xE}, + {BRCM_RATE_24M, 0x9}, + {BRCM_RATE_36M, 0xD}, + {BRCM_RATE_48M, 0x8}, + {BRCM_RATE_54M, 0xC} + }; + + for (i = 0; i < ARRAY_SIZE(rate_lookup); i++) { + if (rate == rate_lookup[i].rate) { + plcp_rate = rate_lookup[i].signal_rate; + break; + } + } + + /* Find the SHM pointer to the rate table entry by looking in the + * Direct-map Table + */ + return 2 * brcms_b_read_shm(wlc_hw, M_RT_DIRMAP_A + (plcp_rate * 2)); +} + +static void brcms_upd_ofdm_pctl1_table(struct brcms_hardware *wlc_hw) +{ + u8 rate; + u8 rates[8] = { + BRCM_RATE_6M, BRCM_RATE_9M, BRCM_RATE_12M, BRCM_RATE_18M, + BRCM_RATE_24M, BRCM_RATE_36M, BRCM_RATE_48M, BRCM_RATE_54M + }; + u16 entry_ptr; + u16 pctl1; + uint i; + + if (!BRCMS_PHY_11N_CAP(wlc_hw->band)) + return; + + /* walk the phy rate table and update the entries */ + for (i = 0; i < ARRAY_SIZE(rates); i++) { + rate = rates[i]; + + entry_ptr = brcms_b_ofdm_ratetable_offset(wlc_hw, rate); + + /* read the SHM Rate Table entry OFDM PCTL1 values */ + pctl1 = + brcms_b_read_shm(wlc_hw, entry_ptr + M_RT_OFDM_PCTL1_POS); + + /* modify the value */ + pctl1 &= ~PHY_TXC1_MODE_MASK; + pctl1 |= (wlc_hw->hw_stf_ss_opmode << PHY_TXC1_MODE_SHIFT); + + /* Update the SHM Rate Table entry OFDM PCTL1 values */ + brcms_b_write_shm(wlc_hw, entry_ptr + M_RT_OFDM_PCTL1_POS, + pctl1); + } +} + +/* band-specific init */ +static void brcms_b_bsinit(struct brcms_c_info *wlc, u16 chanspec) +{ + struct brcms_hardware *wlc_hw = wlc->hw; + + brcms_dbg_mac80211(wlc_hw->d11core, "wl%d: bandunit %d\n", wlc_hw->unit, + wlc_hw->band->bandunit); + + brcms_c_ucode_bsinit(wlc_hw); + + wlc_phy_init(wlc_hw->band->pi, chanspec); + + brcms_c_ucode_txant_set(wlc_hw); + + /* + * cwmin is band-specific, update hardware + * with value for current band + */ + brcms_b_set_cwmin(wlc_hw, wlc_hw->band->CWmin); + brcms_b_set_cwmax(wlc_hw, wlc_hw->band->CWmax); + + brcms_b_update_slot_timing(wlc_hw, + wlc_hw->band->bandtype == BRCM_BAND_5G ? + true : wlc_hw->shortslot); + + /* write phytype and phyvers */ + brcms_b_write_shm(wlc_hw, M_PHYTYPE, (u16) wlc_hw->band->phytype); + brcms_b_write_shm(wlc_hw, M_PHYVER, (u16) wlc_hw->band->phyrev); + + /* + * initialize the txphyctl1 rate table since + * shmem is shared between bands + */ + brcms_upd_ofdm_pctl1_table(wlc_hw); + + brcms_b_upd_synthpu(wlc_hw); +} + +/* Perform a soft reset of the PHY PLL */ +void brcms_b_core_phypll_reset(struct brcms_hardware *wlc_hw) +{ + ai_cc_reg(wlc_hw->sih, offsetof(struct chipcregs, chipcontrol_addr), + ~0, 0); + udelay(1); + ai_cc_reg(wlc_hw->sih, offsetof(struct chipcregs, chipcontrol_data), + 0x4, 0); + udelay(1); + ai_cc_reg(wlc_hw->sih, offsetof(struct chipcregs, chipcontrol_data), + 0x4, 4); + udelay(1); + ai_cc_reg(wlc_hw->sih, offsetof(struct chipcregs, chipcontrol_data), + 0x4, 0); + udelay(1); +} + +/* light way to turn on phy clock without reset for NPHY only + * refer to brcms_b_core_phy_clk for full version + */ +void brcms_b_phyclk_fgc(struct brcms_hardware *wlc_hw, bool clk) +{ + /* support(necessary for NPHY and HYPHY) only */ + if (!BRCMS_ISNPHY(wlc_hw->band)) + return; + + if (ON == clk) + brcms_b_core_ioctl(wlc_hw, SICF_FGC, SICF_FGC); + else + brcms_b_core_ioctl(wlc_hw, SICF_FGC, 0); + +} + +void brcms_b_macphyclk_set(struct brcms_hardware *wlc_hw, bool clk) +{ + if (ON == clk) + brcms_b_core_ioctl(wlc_hw, SICF_MPCLKE, SICF_MPCLKE); + else + brcms_b_core_ioctl(wlc_hw, SICF_MPCLKE, 0); +} + +void brcms_b_phy_reset(struct brcms_hardware *wlc_hw) +{ + struct brcms_phy_pub *pih = wlc_hw->band->pi; + u32 phy_bw_clkbits; + bool phy_in_reset = false; + + brcms_dbg_info(wlc_hw->d11core, "wl%d: reset phy\n", wlc_hw->unit); + + if (pih == NULL) + return; + + phy_bw_clkbits = wlc_phy_clk_bwbits(wlc_hw->band->pi); + + /* Specific reset sequence required for NPHY rev 3 and 4 */ + if (BRCMS_ISNPHY(wlc_hw->band) && NREV_GE(wlc_hw->band->phyrev, 3) && + NREV_LE(wlc_hw->band->phyrev, 4)) { + /* Set the PHY bandwidth */ + brcms_b_core_ioctl(wlc_hw, SICF_BWMASK, phy_bw_clkbits); + + udelay(1); + + /* Perform a soft reset of the PHY PLL */ + brcms_b_core_phypll_reset(wlc_hw); + + /* reset the PHY */ + brcms_b_core_ioctl(wlc_hw, (SICF_PRST | SICF_PCLKE), + (SICF_PRST | SICF_PCLKE)); + phy_in_reset = true; + } else { + brcms_b_core_ioctl(wlc_hw, + (SICF_PRST | SICF_PCLKE | SICF_BWMASK), + (SICF_PRST | SICF_PCLKE | phy_bw_clkbits)); + } + + udelay(2); + brcms_b_core_phy_clk(wlc_hw, ON); + + if (pih) + wlc_phy_anacore(pih, ON); +} + +/* switch to and initialize new band */ +static void brcms_b_setband(struct brcms_hardware *wlc_hw, uint bandunit, + u16 chanspec) { + struct brcms_c_info *wlc = wlc_hw->wlc; + u32 macintmask; + + /* Enable the d11 core before accessing it */ + if (!bcma_core_is_enabled(wlc_hw->d11core)) { + bcma_core_enable(wlc_hw->d11core, 0); + brcms_c_mctrl_reset(wlc_hw); + } + + macintmask = brcms_c_setband_inact(wlc, bandunit); + + if (!wlc_hw->up) + return; + + brcms_b_core_phy_clk(wlc_hw, ON); + + /* band-specific initializations */ + brcms_b_bsinit(wlc, chanspec); + + /* + * If there are any pending software interrupt bits, + * then replace these with a harmless nonzero value + * so brcms_c_dpc() will re-enable interrupts when done. + */ + if (wlc->macintstatus) + wlc->macintstatus = MI_DMAINT; + + /* restore macintmask */ + brcms_intrsrestore(wlc->wl, macintmask); + + /* ucode should still be suspended.. */ + WARN_ON((bcma_read32(wlc_hw->d11core, D11REGOFFS(maccontrol)) & + MCTL_EN_MAC) != 0); +} + +static bool brcms_c_isgoodchip(struct brcms_hardware *wlc_hw) +{ + + /* reject unsupported corerev */ + if (!CONF_HAS(D11CONF, wlc_hw->corerev)) { + wiphy_err(wlc_hw->wlc->wiphy, "unsupported core rev %d\n", + wlc_hw->corerev); + return false; + } + + return true; +} + +/* Validate some board info parameters */ +static bool brcms_c_validboardtype(struct brcms_hardware *wlc_hw) +{ + uint boardrev = wlc_hw->boardrev; + + /* 4 bits each for board type, major, minor, and tiny version */ + uint brt = (boardrev & 0xf000) >> 12; + uint b0 = (boardrev & 0xf00) >> 8; + uint b1 = (boardrev & 0xf0) >> 4; + uint b2 = boardrev & 0xf; + + /* voards from other vendors are always considered valid */ + if (ai_get_boardvendor(wlc_hw->sih) != PCI_VENDOR_ID_BROADCOM) + return true; + + /* do some boardrev sanity checks when boardvendor is Broadcom */ + if (boardrev == 0) + return false; + + if (boardrev <= 0xff) + return true; + + if ((brt > 2) || (brt == 0) || (b0 > 9) || (b0 == 0) || (b1 > 9) + || (b2 > 9)) + return false; + + return true; +} + +static void brcms_c_get_macaddr(struct brcms_hardware *wlc_hw, u8 etheraddr[ETH_ALEN]) +{ + struct ssb_sprom *sprom = &wlc_hw->d11core->bus->sprom; + + /* If macaddr exists, use it (Sromrev4, CIS, ...). */ + if (!is_zero_ether_addr(sprom->il0mac)) { + memcpy(etheraddr, sprom->il0mac, 6); + return; + } + + if (wlc_hw->_nbands > 1) + memcpy(etheraddr, sprom->et1mac, 6); + else + memcpy(etheraddr, sprom->il0mac, 6); +} + +/* power both the pll and external oscillator on/off */ +static void brcms_b_xtal(struct brcms_hardware *wlc_hw, bool want) +{ + brcms_dbg_info(wlc_hw->d11core, "wl%d: want %d\n", wlc_hw->unit, want); + + /* + * dont power down if plldown is false or + * we must poll hw radio disable + */ + if (!want && wlc_hw->pllreq) + return; + + wlc_hw->sbclk = want; + if (!wlc_hw->sbclk) { + wlc_hw->clk = false; + if (wlc_hw->band && wlc_hw->band->pi) + wlc_phy_hw_clk_state_upd(wlc_hw->band->pi, false); + } +} + +/* + * Return true if radio is disabled, otherwise false. + * hw radio disable signal is an external pin, users activate it asynchronously + * this function could be called when driver is down and w/o clock + * it operates on different registers depending on corerev and boardflag. + */ +static bool brcms_b_radio_read_hwdisabled(struct brcms_hardware *wlc_hw) +{ + bool v, clk, xtal; + u32 flags = 0; + + xtal = wlc_hw->sbclk; + if (!xtal) + brcms_b_xtal(wlc_hw, ON); + + /* may need to take core out of reset first */ + clk = wlc_hw->clk; + if (!clk) { + /* + * mac no longer enables phyclk automatically when driver + * accesses phyreg throughput mac. This can be skipped since + * only mac reg is accessed below + */ + if (D11REV_GE(wlc_hw->corerev, 18)) + flags |= SICF_PCLKE; + + /* + * TODO: test suspend/resume + * + * AI chip doesn't restore bar0win2 on + * hibernation/resume, need sw fixup + */ + + bcma_core_enable(wlc_hw->d11core, flags); + brcms_c_mctrl_reset(wlc_hw); + } + + v = ((bcma_read32(wlc_hw->d11core, + D11REGOFFS(phydebug)) & PDBG_RFD) != 0); + + /* put core back into reset */ + if (!clk) + bcma_core_disable(wlc_hw->d11core, 0); + + if (!xtal) + brcms_b_xtal(wlc_hw, OFF); + + return v; +} + +static bool wlc_dma_rxreset(struct brcms_hardware *wlc_hw, uint fifo) +{ + struct dma_pub *di = wlc_hw->di[fifo]; + return dma_rxreset(di); +} + +/* d11 core reset + * ensure fask clock during reset + * reset dma + * reset d11(out of reset) + * reset phy(out of reset) + * clear software macintstatus for fresh new start + * one testing hack wlc_hw->noreset will bypass the d11/phy reset + */ +void brcms_b_corereset(struct brcms_hardware *wlc_hw, u32 flags) +{ + uint i; + bool fastclk; + + if (flags == BRCMS_USE_COREFLAGS) + flags = (wlc_hw->band->pi ? wlc_hw->band->core_flags : 0); + + brcms_dbg_info(wlc_hw->d11core, "wl%d: core reset\n", wlc_hw->unit); + + /* request FAST clock if not on */ + fastclk = wlc_hw->forcefastclk; + if (!fastclk) + brcms_b_clkctl_clk(wlc_hw, BCMA_CLKMODE_FAST); + + /* reset the dma engines except first time thru */ + if (bcma_core_is_enabled(wlc_hw->d11core)) { + for (i = 0; i < NFIFO; i++) + if ((wlc_hw->di[i]) && (!dma_txreset(wlc_hw->di[i]))) + brcms_err(wlc_hw->d11core, "wl%d: %s: " + "dma_txreset[%d]: cannot stop dma\n", + wlc_hw->unit, __func__, i); + + if ((wlc_hw->di[RX_FIFO]) + && (!wlc_dma_rxreset(wlc_hw, RX_FIFO))) + brcms_err(wlc_hw->d11core, "wl%d: %s: dma_rxreset" + "[%d]: cannot stop dma\n", + wlc_hw->unit, __func__, RX_FIFO); + } + /* if noreset, just stop the psm and return */ + if (wlc_hw->noreset) { + wlc_hw->wlc->macintstatus = 0; /* skip wl_dpc after down */ + brcms_b_mctrl(wlc_hw, MCTL_PSM_RUN | MCTL_EN_MAC, 0); + return; + } + + /* + * mac no longer enables phyclk automatically when driver accesses + * phyreg throughput mac, AND phy_reset is skipped at early stage when + * band->pi is invalid. need to enable PHY CLK + */ + if (D11REV_GE(wlc_hw->corerev, 18)) + flags |= SICF_PCLKE; + + /* + * reset the core + * In chips with PMU, the fastclk request goes through d11 core + * reg 0x1e0, which is cleared by the core_reset. have to re-request it. + * + * This adds some delay and we can optimize it by also requesting + * fastclk through chipcommon during this period if necessary. But + * that has to work coordinate with other driver like mips/arm since + * they may touch chipcommon as well. + */ + wlc_hw->clk = false; + bcma_core_enable(wlc_hw->d11core, flags); + wlc_hw->clk = true; + if (wlc_hw->band && wlc_hw->band->pi) + wlc_phy_hw_clk_state_upd(wlc_hw->band->pi, true); + + brcms_c_mctrl_reset(wlc_hw); + + if (ai_get_cccaps(wlc_hw->sih) & CC_CAP_PMU) + brcms_b_clkctl_clk(wlc_hw, BCMA_CLKMODE_FAST); + + brcms_b_phy_reset(wlc_hw); + + /* turn on PHY_PLL */ + brcms_b_core_phypll_ctl(wlc_hw, true); + + /* clear sw intstatus */ + wlc_hw->wlc->macintstatus = 0; + + /* restore the clk setting */ + if (!fastclk) + brcms_b_clkctl_clk(wlc_hw, BCMA_CLKMODE_DYNAMIC); +} + +/* txfifo sizes needs to be modified(increased) since the newer cores + * have more memory. + */ +static void brcms_b_corerev_fifofixup(struct brcms_hardware *wlc_hw) +{ + struct bcma_device *core = wlc_hw->d11core; + u16 fifo_nu; + u16 txfifo_startblk = TXFIFO_START_BLK, txfifo_endblk; + u16 txfifo_def, txfifo_def1; + u16 txfifo_cmd; + + /* tx fifos start at TXFIFO_START_BLK from the Base address */ + txfifo_startblk = TXFIFO_START_BLK; + + /* sequence of operations: reset fifo, set fifo size, reset fifo */ + for (fifo_nu = 0; fifo_nu < NFIFO; fifo_nu++) { + + txfifo_endblk = txfifo_startblk + wlc_hw->xmtfifo_sz[fifo_nu]; + txfifo_def = (txfifo_startblk & 0xff) | + (((txfifo_endblk - 1) & 0xff) << TXFIFO_FIFOTOP_SHIFT); + txfifo_def1 = ((txfifo_startblk >> 8) & 0x1) | + ((((txfifo_endblk - + 1) >> 8) & 0x1) << TXFIFO_FIFOTOP_SHIFT); + txfifo_cmd = + TXFIFOCMD_RESET_MASK | (fifo_nu << TXFIFOCMD_FIFOSEL_SHIFT); + + bcma_write16(core, D11REGOFFS(xmtfifocmd), txfifo_cmd); + bcma_write16(core, D11REGOFFS(xmtfifodef), txfifo_def); + bcma_write16(core, D11REGOFFS(xmtfifodef1), txfifo_def1); + + bcma_write16(core, D11REGOFFS(xmtfifocmd), txfifo_cmd); + + txfifo_startblk += wlc_hw->xmtfifo_sz[fifo_nu]; + } + /* + * need to propagate to shm location to be in sync since ucode/hw won't + * do this + */ + brcms_b_write_shm(wlc_hw, M_FIFOSIZE0, + wlc_hw->xmtfifo_sz[TX_AC_BE_FIFO]); + brcms_b_write_shm(wlc_hw, M_FIFOSIZE1, + wlc_hw->xmtfifo_sz[TX_AC_VI_FIFO]); + brcms_b_write_shm(wlc_hw, M_FIFOSIZE2, + ((wlc_hw->xmtfifo_sz[TX_AC_VO_FIFO] << 8) | wlc_hw-> + xmtfifo_sz[TX_AC_BK_FIFO])); + brcms_b_write_shm(wlc_hw, M_FIFOSIZE3, + ((wlc_hw->xmtfifo_sz[TX_ATIM_FIFO] << 8) | wlc_hw-> + xmtfifo_sz[TX_BCMC_FIFO])); +} + +/* This function is used for changing the tsf frac register + * If spur avoidance mode is off, the mac freq will be 80/120/160Mhz + * If spur avoidance mode is on1, the mac freq will be 82/123/164Mhz + * If spur avoidance mode is on2, the mac freq will be 84/126/168Mhz + * HTPHY Formula is 2^26/freq(MHz) e.g. + * For spuron2 - 126MHz -> 2^26/126 = 532610.0 + * - 532610 = 0x82082 => tsf_clk_frac_h = 0x8, tsf_clk_frac_l = 0x2082 + * For spuron: 123MHz -> 2^26/123 = 545600.5 + * - 545601 = 0x85341 => tsf_clk_frac_h = 0x8, tsf_clk_frac_l = 0x5341 + * For spur off: 120MHz -> 2^26/120 = 559240.5 + * - 559241 = 0x88889 => tsf_clk_frac_h = 0x8, tsf_clk_frac_l = 0x8889 + */ + +void brcms_b_switch_macfreq(struct brcms_hardware *wlc_hw, u8 spurmode) +{ + struct bcma_device *core = wlc_hw->d11core; + + if ((ai_get_chip_id(wlc_hw->sih) == BCMA_CHIP_ID_BCM43224) || + (ai_get_chip_id(wlc_hw->sih) == BCMA_CHIP_ID_BCM43225)) { + if (spurmode == WL_SPURAVOID_ON2) { /* 126Mhz */ + bcma_write16(core, D11REGOFFS(tsf_clk_frac_l), 0x2082); + bcma_write16(core, D11REGOFFS(tsf_clk_frac_h), 0x8); + } else if (spurmode == WL_SPURAVOID_ON1) { /* 123Mhz */ + bcma_write16(core, D11REGOFFS(tsf_clk_frac_l), 0x5341); + bcma_write16(core, D11REGOFFS(tsf_clk_frac_h), 0x8); + } else { /* 120Mhz */ + bcma_write16(core, D11REGOFFS(tsf_clk_frac_l), 0x8889); + bcma_write16(core, D11REGOFFS(tsf_clk_frac_h), 0x8); + } + } else if (BRCMS_ISLCNPHY(wlc_hw->band)) { + if (spurmode == WL_SPURAVOID_ON1) { /* 82Mhz */ + bcma_write16(core, D11REGOFFS(tsf_clk_frac_l), 0x7CE0); + bcma_write16(core, D11REGOFFS(tsf_clk_frac_h), 0xC); + } else { /* 80Mhz */ + bcma_write16(core, D11REGOFFS(tsf_clk_frac_l), 0xCCCD); + bcma_write16(core, D11REGOFFS(tsf_clk_frac_h), 0xC); + } + } +} + +void brcms_c_start_station(struct brcms_c_info *wlc, u8 *addr) +{ + memcpy(wlc->pub->cur_etheraddr, addr, sizeof(wlc->pub->cur_etheraddr)); + wlc->bsscfg->type = BRCMS_TYPE_STATION; +} + +void brcms_c_start_ap(struct brcms_c_info *wlc, u8 *addr, const u8 *bssid, + u8 *ssid, size_t ssid_len) +{ + brcms_c_set_ssid(wlc, ssid, ssid_len); + + memcpy(wlc->pub->cur_etheraddr, addr, sizeof(wlc->pub->cur_etheraddr)); + memcpy(wlc->bsscfg->BSSID, bssid, sizeof(wlc->bsscfg->BSSID)); + wlc->bsscfg->type = BRCMS_TYPE_AP; + + brcms_b_mctrl(wlc->hw, MCTL_AP | MCTL_INFRA, MCTL_AP | MCTL_INFRA); +} + +void brcms_c_start_adhoc(struct brcms_c_info *wlc, u8 *addr) +{ + memcpy(wlc->pub->cur_etheraddr, addr, sizeof(wlc->pub->cur_etheraddr)); + wlc->bsscfg->type = BRCMS_TYPE_ADHOC; + + brcms_b_mctrl(wlc->hw, MCTL_AP | MCTL_INFRA, 0); +} + +/* Initialize GPIOs that are controlled by D11 core */ +static void brcms_c_gpio_init(struct brcms_c_info *wlc) +{ + struct brcms_hardware *wlc_hw = wlc->hw; + u32 gc, gm; + + /* use GPIO select 0 to get all gpio signals from the gpio out reg */ + brcms_b_mctrl(wlc_hw, MCTL_GPOUT_SEL_MASK, 0); + + /* + * Common GPIO setup: + * G0 = LED 0 = WLAN Activity + * G1 = LED 1 = WLAN 2.4 GHz Radio State + * G2 = LED 2 = WLAN 5 GHz Radio State + * G4 = radio disable input (HI enabled, LO disabled) + */ + + gc = gm = 0; + + /* Allocate GPIOs for mimo antenna diversity feature */ + if (wlc_hw->antsel_type == ANTSEL_2x3) { + /* Enable antenna diversity, use 2x3 mode */ + brcms_b_mhf(wlc_hw, MHF3, MHF3_ANTSEL_EN, + MHF3_ANTSEL_EN, BRCM_BAND_ALL); + brcms_b_mhf(wlc_hw, MHF3, MHF3_ANTSEL_MODE, + MHF3_ANTSEL_MODE, BRCM_BAND_ALL); + + /* init superswitch control */ + wlc_phy_antsel_init(wlc_hw->band->pi, false); + + } else if (wlc_hw->antsel_type == ANTSEL_2x4) { + gm |= gc |= (BOARD_GPIO_12 | BOARD_GPIO_13); + /* + * The board itself is powered by these GPIOs + * (when not sending pattern) so set them high + */ + bcma_set16(wlc_hw->d11core, D11REGOFFS(psm_gpio_oe), + (BOARD_GPIO_12 | BOARD_GPIO_13)); + bcma_set16(wlc_hw->d11core, D11REGOFFS(psm_gpio_out), + (BOARD_GPIO_12 | BOARD_GPIO_13)); + + /* Enable antenna diversity, use 2x4 mode */ + brcms_b_mhf(wlc_hw, MHF3, MHF3_ANTSEL_EN, + MHF3_ANTSEL_EN, BRCM_BAND_ALL); + brcms_b_mhf(wlc_hw, MHF3, MHF3_ANTSEL_MODE, 0, + BRCM_BAND_ALL); + + /* Configure the desired clock to be 4Mhz */ + brcms_b_write_shm(wlc_hw, M_ANTSEL_CLKDIV, + ANTSEL_CLKDIV_4MHZ); + } + + /* + * gpio 9 controls the PA. ucode is responsible + * for wiggling out and oe + */ + if (wlc_hw->boardflags & BFL_PACTRL) + gm |= gc |= BOARD_GPIO_PACTRL; + + /* apply to gpiocontrol register */ + bcma_chipco_gpio_control(&wlc_hw->d11core->bus->drv_cc, gm, gc); +} + +static void brcms_ucode_write(struct brcms_hardware *wlc_hw, + const __le32 ucode[], const size_t nbytes) +{ + struct bcma_device *core = wlc_hw->d11core; + uint i; + uint count; + + brcms_dbg_info(wlc_hw->d11core, "wl%d\n", wlc_hw->unit); + + count = (nbytes / sizeof(u32)); + + bcma_write32(core, D11REGOFFS(objaddr), + OBJADDR_AUTO_INC | OBJADDR_UCM_SEL); + (void)bcma_read32(core, D11REGOFFS(objaddr)); + for (i = 0; i < count; i++) + bcma_write32(core, D11REGOFFS(objdata), le32_to_cpu(ucode[i])); + +} + +static void brcms_ucode_download(struct brcms_hardware *wlc_hw) +{ + struct brcms_c_info *wlc; + struct brcms_ucode *ucode = &wlc_hw->wlc->wl->ucode; + + wlc = wlc_hw->wlc; + + if (wlc_hw->ucode_loaded) + return; + + if (D11REV_IS(wlc_hw->corerev, 17) || D11REV_IS(wlc_hw->corerev, 23)) { + if (BRCMS_ISNPHY(wlc_hw->band)) { + brcms_ucode_write(wlc_hw, ucode->bcm43xx_16_mimo, + ucode->bcm43xx_16_mimosz); + wlc_hw->ucode_loaded = true; + } else + brcms_err(wlc_hw->d11core, + "%s: wl%d: unsupported phy in corerev %d\n", + __func__, wlc_hw->unit, wlc_hw->corerev); + } else if (D11REV_IS(wlc_hw->corerev, 24)) { + if (BRCMS_ISLCNPHY(wlc_hw->band)) { + brcms_ucode_write(wlc_hw, ucode->bcm43xx_24_lcn, + ucode->bcm43xx_24_lcnsz); + wlc_hw->ucode_loaded = true; + } else { + brcms_err(wlc_hw->d11core, + "%s: wl%d: unsupported phy in corerev %d\n", + __func__, wlc_hw->unit, wlc_hw->corerev); + } + } +} + +void brcms_b_txant_set(struct brcms_hardware *wlc_hw, u16 phytxant) +{ + /* update sw state */ + wlc_hw->bmac_phytxant = phytxant; + + /* push to ucode if up */ + if (!wlc_hw->up) + return; + brcms_c_ucode_txant_set(wlc_hw); + +} + +u16 brcms_b_get_txant(struct brcms_hardware *wlc_hw) +{ + return (u16) wlc_hw->wlc->stf->txant; +} + +void brcms_b_antsel_type_set(struct brcms_hardware *wlc_hw, u8 antsel_type) +{ + wlc_hw->antsel_type = antsel_type; + + /* Update the antsel type for phy module to use */ + wlc_phy_antsel_type_set(wlc_hw->band->pi, antsel_type); +} + +static void brcms_b_fifoerrors(struct brcms_hardware *wlc_hw) +{ + bool fatal = false; + uint unit; + uint intstatus, idx; + struct bcma_device *core = wlc_hw->d11core; + + unit = wlc_hw->unit; + + for (idx = 0; idx < NFIFO; idx++) { + /* read intstatus register and ignore any non-error bits */ + intstatus = + bcma_read32(core, + D11REGOFFS(intctrlregs[idx].intstatus)) & + I_ERRORS; + if (!intstatus) + continue; + + brcms_dbg_int(core, "wl%d: intstatus%d 0x%x\n", + unit, idx, intstatus); + + if (intstatus & I_RO) { + brcms_err(core, "wl%d: fifo %d: receive fifo " + "overflow\n", unit, idx); + fatal = true; + } + + if (intstatus & I_PC) { + brcms_err(core, "wl%d: fifo %d: descriptor error\n", + unit, idx); + fatal = true; + } + + if (intstatus & I_PD) { + brcms_err(core, "wl%d: fifo %d: data error\n", unit, + idx); + fatal = true; + } + + if (intstatus & I_DE) { + brcms_err(core, "wl%d: fifo %d: descriptor protocol " + "error\n", unit, idx); + fatal = true; + } + + if (intstatus & I_RU) + brcms_err(core, "wl%d: fifo %d: receive descriptor " + "underflow\n", idx, unit); + + if (intstatus & I_XU) { + brcms_err(core, "wl%d: fifo %d: transmit fifo " + "underflow\n", idx, unit); + fatal = true; + } + + if (fatal) { + brcms_fatal_error(wlc_hw->wlc->wl); /* big hammer */ + break; + } else + bcma_write32(core, + D11REGOFFS(intctrlregs[idx].intstatus), + intstatus); + } +} + +void brcms_c_intrson(struct brcms_c_info *wlc) +{ + struct brcms_hardware *wlc_hw = wlc->hw; + wlc->macintmask = wlc->defmacintmask; + bcma_write32(wlc_hw->d11core, D11REGOFFS(macintmask), wlc->macintmask); +} + +u32 brcms_c_intrsoff(struct brcms_c_info *wlc) +{ + struct brcms_hardware *wlc_hw = wlc->hw; + u32 macintmask; + + if (!wlc_hw->clk) + return 0; + + macintmask = wlc->macintmask; /* isr can still happen */ + + bcma_write32(wlc_hw->d11core, D11REGOFFS(macintmask), 0); + (void)bcma_read32(wlc_hw->d11core, D11REGOFFS(macintmask)); + udelay(1); /* ensure int line is no longer driven */ + wlc->macintmask = 0; + + /* return previous macintmask; resolve race between us and our isr */ + return wlc->macintstatus ? 0 : macintmask; +} + +void brcms_c_intrsrestore(struct brcms_c_info *wlc, u32 macintmask) +{ + struct brcms_hardware *wlc_hw = wlc->hw; + if (!wlc_hw->clk) + return; + + wlc->macintmask = macintmask; + bcma_write32(wlc_hw->d11core, D11REGOFFS(macintmask), wlc->macintmask); +} + +/* assumes that the d11 MAC is enabled */ +static void brcms_b_tx_fifo_suspend(struct brcms_hardware *wlc_hw, + uint tx_fifo) +{ + u8 fifo = 1 << tx_fifo; + + /* Two clients of this code, 11h Quiet period and scanning. */ + + /* only suspend if not already suspended */ + if ((wlc_hw->suspended_fifos & fifo) == fifo) + return; + + /* force the core awake only if not already */ + if (wlc_hw->suspended_fifos == 0) + brcms_c_ucode_wake_override_set(wlc_hw, + BRCMS_WAKE_OVERRIDE_TXFIFO); + + wlc_hw->suspended_fifos |= fifo; + + if (wlc_hw->di[tx_fifo]) { + /* + * Suspending AMPDU transmissions in the middle can cause + * underflow which may result in mismatch between ucode and + * driver so suspend the mac before suspending the FIFO + */ + if (BRCMS_PHY_11N_CAP(wlc_hw->band)) + brcms_c_suspend_mac_and_wait(wlc_hw->wlc); + + dma_txsuspend(wlc_hw->di[tx_fifo]); + + if (BRCMS_PHY_11N_CAP(wlc_hw->band)) + brcms_c_enable_mac(wlc_hw->wlc); + } +} + +static void brcms_b_tx_fifo_resume(struct brcms_hardware *wlc_hw, + uint tx_fifo) +{ + /* BMAC_NOTE: BRCMS_TX_FIFO_ENAB is done in brcms_c_dpc() for DMA case + * but need to be done here for PIO otherwise the watchdog will catch + * the inconsistency and fire + */ + /* Two clients of this code, 11h Quiet period and scanning. */ + if (wlc_hw->di[tx_fifo]) + dma_txresume(wlc_hw->di[tx_fifo]); + + /* allow core to sleep again */ + if (wlc_hw->suspended_fifos == 0) + return; + else { + wlc_hw->suspended_fifos &= ~(1 << tx_fifo); + if (wlc_hw->suspended_fifos == 0) + brcms_c_ucode_wake_override_clear(wlc_hw, + BRCMS_WAKE_OVERRIDE_TXFIFO); + } +} + +/* precondition: requires the mac core to be enabled */ +static void brcms_b_mute(struct brcms_hardware *wlc_hw, bool mute_tx) +{ + static const u8 null_ether_addr[ETH_ALEN] = {0, 0, 0, 0, 0, 0}; + u8 *ethaddr = wlc_hw->wlc->pub->cur_etheraddr; + + if (mute_tx) { + /* suspend tx fifos */ + brcms_b_tx_fifo_suspend(wlc_hw, TX_DATA_FIFO); + brcms_b_tx_fifo_suspend(wlc_hw, TX_CTL_FIFO); + brcms_b_tx_fifo_suspend(wlc_hw, TX_AC_BK_FIFO); + brcms_b_tx_fifo_suspend(wlc_hw, TX_AC_VI_FIFO); + + /* zero the address match register so we do not send ACKs */ + brcms_b_set_addrmatch(wlc_hw, RCM_MAC_OFFSET, null_ether_addr); + } else { + /* resume tx fifos */ + brcms_b_tx_fifo_resume(wlc_hw, TX_DATA_FIFO); + brcms_b_tx_fifo_resume(wlc_hw, TX_CTL_FIFO); + brcms_b_tx_fifo_resume(wlc_hw, TX_AC_BK_FIFO); + brcms_b_tx_fifo_resume(wlc_hw, TX_AC_VI_FIFO); + + /* Restore address */ + brcms_b_set_addrmatch(wlc_hw, RCM_MAC_OFFSET, ethaddr); + } + + wlc_phy_mute_upd(wlc_hw->band->pi, mute_tx, 0); + + if (mute_tx) + brcms_c_ucode_mute_override_set(wlc_hw); + else + brcms_c_ucode_mute_override_clear(wlc_hw); +} + +void +brcms_c_mute(struct brcms_c_info *wlc, bool mute_tx) +{ + brcms_b_mute(wlc->hw, mute_tx); +} + +/* + * Read and clear macintmask and macintstatus and intstatus registers. + * This routine should be called with interrupts off + * Return: + * -1 if brcms_deviceremoved(wlc) evaluates to true; + * 0 if the interrupt is not for us, or we are in some special cases; + * device interrupt status bits otherwise. + */ +static inline u32 wlc_intstatus(struct brcms_c_info *wlc, bool in_isr) +{ + struct brcms_hardware *wlc_hw = wlc->hw; + struct bcma_device *core = wlc_hw->d11core; + u32 macintstatus, mask; + + /* macintstatus includes a DMA interrupt summary bit */ + macintstatus = bcma_read32(core, D11REGOFFS(macintstatus)); + mask = in_isr ? wlc->macintmask : wlc->defmacintmask; + + trace_brcms_macintstatus(&core->dev, in_isr, macintstatus, mask); + + /* detect cardbus removed, in power down(suspend) and in reset */ + if (brcms_deviceremoved(wlc)) + return -1; + + /* brcms_deviceremoved() succeeds even when the core is still resetting, + * handle that case here. + */ + if (macintstatus == 0xffffffff) + return 0; + + /* defer unsolicited interrupts */ + macintstatus &= mask; + + /* if not for us */ + if (macintstatus == 0) + return 0; + + /* turn off the interrupts */ + bcma_write32(core, D11REGOFFS(macintmask), 0); + (void)bcma_read32(core, D11REGOFFS(macintmask)); + wlc->macintmask = 0; + + /* clear device interrupts */ + bcma_write32(core, D11REGOFFS(macintstatus), macintstatus); + + /* MI_DMAINT is indication of non-zero intstatus */ + if (macintstatus & MI_DMAINT) + /* + * only fifo interrupt enabled is I_RI in + * RX_FIFO. If MI_DMAINT is set, assume it + * is set and clear the interrupt. + */ + bcma_write32(core, D11REGOFFS(intctrlregs[RX_FIFO].intstatus), + DEF_RXINTMASK); + + return macintstatus; +} + +/* Update wlc->macintstatus and wlc->intstatus[]. */ +/* Return true if they are updated successfully. false otherwise */ +bool brcms_c_intrsupd(struct brcms_c_info *wlc) +{ + u32 macintstatus; + + /* read and clear macintstatus and intstatus registers */ + macintstatus = wlc_intstatus(wlc, false); + + /* device is removed */ + if (macintstatus == 0xffffffff) + return false; + + /* update interrupt status in software */ + wlc->macintstatus |= macintstatus; + + return true; +} + +/* + * First-level interrupt processing. + * Return true if this was our interrupt + * and if further brcms_c_dpc() processing is required, + * false otherwise. + */ +bool brcms_c_isr(struct brcms_c_info *wlc) +{ + struct brcms_hardware *wlc_hw = wlc->hw; + u32 macintstatus; + + if (!wlc_hw->up || !wlc->macintmask) + return false; + + /* read and clear macintstatus and intstatus registers */ + macintstatus = wlc_intstatus(wlc, true); + + if (macintstatus == 0xffffffff) { + brcms_err(wlc_hw->d11core, + "DEVICEREMOVED detected in the ISR code path\n"); + return false; + } + + /* it is not for us */ + if (macintstatus == 0) + return false; + + /* save interrupt status bits */ + wlc->macintstatus = macintstatus; + + return true; + +} + +void brcms_c_suspend_mac_and_wait(struct brcms_c_info *wlc) +{ + struct brcms_hardware *wlc_hw = wlc->hw; + struct bcma_device *core = wlc_hw->d11core; + u32 mc, mi; + + brcms_dbg_mac80211(core, "wl%d: bandunit %d\n", wlc_hw->unit, + wlc_hw->band->bandunit); + + /* + * Track overlapping suspend requests + */ + wlc_hw->mac_suspend_depth++; + if (wlc_hw->mac_suspend_depth > 1) + return; + + /* force the core awake */ + brcms_c_ucode_wake_override_set(wlc_hw, BRCMS_WAKE_OVERRIDE_MACSUSPEND); + + mc = bcma_read32(core, D11REGOFFS(maccontrol)); + + if (mc == 0xffffffff) { + brcms_err(core, "wl%d: %s: dead chip\n", wlc_hw->unit, + __func__); + brcms_down(wlc->wl); + return; + } + WARN_ON(mc & MCTL_PSM_JMP_0); + WARN_ON(!(mc & MCTL_PSM_RUN)); + WARN_ON(!(mc & MCTL_EN_MAC)); + + mi = bcma_read32(core, D11REGOFFS(macintstatus)); + if (mi == 0xffffffff) { + brcms_err(core, "wl%d: %s: dead chip\n", wlc_hw->unit, + __func__); + brcms_down(wlc->wl); + return; + } + WARN_ON(mi & MI_MACSSPNDD); + + brcms_b_mctrl(wlc_hw, MCTL_EN_MAC, 0); + + SPINWAIT(!(bcma_read32(core, D11REGOFFS(macintstatus)) & MI_MACSSPNDD), + BRCMS_MAX_MAC_SUSPEND); + + if (!(bcma_read32(core, D11REGOFFS(macintstatus)) & MI_MACSSPNDD)) { + brcms_err(core, "wl%d: wlc_suspend_mac_and_wait: waited %d uS" + " and MI_MACSSPNDD is still not on.\n", + wlc_hw->unit, BRCMS_MAX_MAC_SUSPEND); + brcms_err(core, "wl%d: psmdebug 0x%08x, phydebug 0x%08x, " + "psm_brc 0x%04x\n", wlc_hw->unit, + bcma_read32(core, D11REGOFFS(psmdebug)), + bcma_read32(core, D11REGOFFS(phydebug)), + bcma_read16(core, D11REGOFFS(psm_brc))); + } + + mc = bcma_read32(core, D11REGOFFS(maccontrol)); + if (mc == 0xffffffff) { + brcms_err(core, "wl%d: %s: dead chip\n", wlc_hw->unit, + __func__); + brcms_down(wlc->wl); + return; + } + WARN_ON(mc & MCTL_PSM_JMP_0); + WARN_ON(!(mc & MCTL_PSM_RUN)); + WARN_ON(mc & MCTL_EN_MAC); +} + +void brcms_c_enable_mac(struct brcms_c_info *wlc) +{ + struct brcms_hardware *wlc_hw = wlc->hw; + struct bcma_device *core = wlc_hw->d11core; + u32 mc, mi; + + brcms_dbg_mac80211(core, "wl%d: bandunit %d\n", wlc_hw->unit, + wlc->band->bandunit); + + /* + * Track overlapping suspend requests + */ + wlc_hw->mac_suspend_depth--; + if (wlc_hw->mac_suspend_depth > 0) + return; + + mc = bcma_read32(core, D11REGOFFS(maccontrol)); + WARN_ON(mc & MCTL_PSM_JMP_0); + WARN_ON(mc & MCTL_EN_MAC); + WARN_ON(!(mc & MCTL_PSM_RUN)); + + brcms_b_mctrl(wlc_hw, MCTL_EN_MAC, MCTL_EN_MAC); + bcma_write32(core, D11REGOFFS(macintstatus), MI_MACSSPNDD); + + mc = bcma_read32(core, D11REGOFFS(maccontrol)); + WARN_ON(mc & MCTL_PSM_JMP_0); + WARN_ON(!(mc & MCTL_EN_MAC)); + WARN_ON(!(mc & MCTL_PSM_RUN)); + + mi = bcma_read32(core, D11REGOFFS(macintstatus)); + WARN_ON(mi & MI_MACSSPNDD); + + brcms_c_ucode_wake_override_clear(wlc_hw, + BRCMS_WAKE_OVERRIDE_MACSUSPEND); +} + +void brcms_b_band_stf_ss_set(struct brcms_hardware *wlc_hw, u8 stf_mode) +{ + wlc_hw->hw_stf_ss_opmode = stf_mode; + + if (wlc_hw->clk) + brcms_upd_ofdm_pctl1_table(wlc_hw); +} + +static bool brcms_b_validate_chip_access(struct brcms_hardware *wlc_hw) +{ + struct bcma_device *core = wlc_hw->d11core; + u32 w, val; + struct wiphy *wiphy = wlc_hw->wlc->wiphy; + + /* Validate dchip register access */ + + bcma_write32(core, D11REGOFFS(objaddr), OBJADDR_SHM_SEL | 0); + (void)bcma_read32(core, D11REGOFFS(objaddr)); + w = bcma_read32(core, D11REGOFFS(objdata)); + + /* Can we write and read back a 32bit register? */ + bcma_write32(core, D11REGOFFS(objaddr), OBJADDR_SHM_SEL | 0); + (void)bcma_read32(core, D11REGOFFS(objaddr)); + bcma_write32(core, D11REGOFFS(objdata), (u32) 0xaa5555aa); + + bcma_write32(core, D11REGOFFS(objaddr), OBJADDR_SHM_SEL | 0); + (void)bcma_read32(core, D11REGOFFS(objaddr)); + val = bcma_read32(core, D11REGOFFS(objdata)); + if (val != (u32) 0xaa5555aa) { + wiphy_err(wiphy, "wl%d: validate_chip_access: SHM = 0x%x, " + "expected 0xaa5555aa\n", wlc_hw->unit, val); + return false; + } + + bcma_write32(core, D11REGOFFS(objaddr), OBJADDR_SHM_SEL | 0); + (void)bcma_read32(core, D11REGOFFS(objaddr)); + bcma_write32(core, D11REGOFFS(objdata), (u32) 0x55aaaa55); + + bcma_write32(core, D11REGOFFS(objaddr), OBJADDR_SHM_SEL | 0); + (void)bcma_read32(core, D11REGOFFS(objaddr)); + val = bcma_read32(core, D11REGOFFS(objdata)); + if (val != (u32) 0x55aaaa55) { + wiphy_err(wiphy, "wl%d: validate_chip_access: SHM = 0x%x, " + "expected 0x55aaaa55\n", wlc_hw->unit, val); + return false; + } + + bcma_write32(core, D11REGOFFS(objaddr), OBJADDR_SHM_SEL | 0); + (void)bcma_read32(core, D11REGOFFS(objaddr)); + bcma_write32(core, D11REGOFFS(objdata), w); + + /* clear CFPStart */ + bcma_write32(core, D11REGOFFS(tsf_cfpstart), 0); + + w = bcma_read32(core, D11REGOFFS(maccontrol)); + if ((w != (MCTL_IHR_EN | MCTL_WAKE)) && + (w != (MCTL_IHR_EN | MCTL_GMODE | MCTL_WAKE))) { + wiphy_err(wiphy, "wl%d: validate_chip_access: maccontrol = " + "0x%x, expected 0x%x or 0x%x\n", wlc_hw->unit, w, + (MCTL_IHR_EN | MCTL_WAKE), + (MCTL_IHR_EN | MCTL_GMODE | MCTL_WAKE)); + return false; + } + + return true; +} + +#define PHYPLL_WAIT_US 100000 + +void brcms_b_core_phypll_ctl(struct brcms_hardware *wlc_hw, bool on) +{ + struct bcma_device *core = wlc_hw->d11core; + u32 tmp; + + brcms_dbg_info(core, "wl%d\n", wlc_hw->unit); + + tmp = 0; + + if (on) { + if ((ai_get_chip_id(wlc_hw->sih) == BCMA_CHIP_ID_BCM4313)) { + bcma_set32(core, D11REGOFFS(clk_ctl_st), + CCS_ERSRC_REQ_HT | + CCS_ERSRC_REQ_D11PLL | + CCS_ERSRC_REQ_PHYPLL); + SPINWAIT((bcma_read32(core, D11REGOFFS(clk_ctl_st)) & + CCS_ERSRC_AVAIL_HT) != CCS_ERSRC_AVAIL_HT, + PHYPLL_WAIT_US); + + tmp = bcma_read32(core, D11REGOFFS(clk_ctl_st)); + if ((tmp & CCS_ERSRC_AVAIL_HT) != CCS_ERSRC_AVAIL_HT) + brcms_err(core, "%s: turn on PHY PLL failed\n", + __func__); + } else { + bcma_set32(core, D11REGOFFS(clk_ctl_st), + tmp | CCS_ERSRC_REQ_D11PLL | + CCS_ERSRC_REQ_PHYPLL); + SPINWAIT((bcma_read32(core, D11REGOFFS(clk_ctl_st)) & + (CCS_ERSRC_AVAIL_D11PLL | + CCS_ERSRC_AVAIL_PHYPLL)) != + (CCS_ERSRC_AVAIL_D11PLL | + CCS_ERSRC_AVAIL_PHYPLL), PHYPLL_WAIT_US); + + tmp = bcma_read32(core, D11REGOFFS(clk_ctl_st)); + if ((tmp & + (CCS_ERSRC_AVAIL_D11PLL | CCS_ERSRC_AVAIL_PHYPLL)) + != + (CCS_ERSRC_AVAIL_D11PLL | CCS_ERSRC_AVAIL_PHYPLL)) + brcms_err(core, "%s: turn on PHY PLL failed\n", + __func__); + } + } else { + /* + * Since the PLL may be shared, other cores can still + * be requesting it; so we'll deassert the request but + * not wait for status to comply. + */ + bcma_mask32(core, D11REGOFFS(clk_ctl_st), + ~CCS_ERSRC_REQ_PHYPLL); + (void)bcma_read32(core, D11REGOFFS(clk_ctl_st)); + } +} + +static void brcms_c_coredisable(struct brcms_hardware *wlc_hw) +{ + bool dev_gone; + + brcms_dbg_info(wlc_hw->d11core, "wl%d: disable core\n", wlc_hw->unit); + + dev_gone = brcms_deviceremoved(wlc_hw->wlc); + + if (dev_gone) + return; + + if (wlc_hw->noreset) + return; + + /* radio off */ + wlc_phy_switch_radio(wlc_hw->band->pi, OFF); + + /* turn off analog core */ + wlc_phy_anacore(wlc_hw->band->pi, OFF); + + /* turn off PHYPLL to save power */ + brcms_b_core_phypll_ctl(wlc_hw, false); + + wlc_hw->clk = false; + bcma_core_disable(wlc_hw->d11core, 0); + wlc_phy_hw_clk_state_upd(wlc_hw->band->pi, false); +} + +static void brcms_c_flushqueues(struct brcms_c_info *wlc) +{ + struct brcms_hardware *wlc_hw = wlc->hw; + uint i; + + /* free any posted tx packets */ + for (i = 0; i < NFIFO; i++) { + if (wlc_hw->di[i]) { + dma_txreclaim(wlc_hw->di[i], DMA_RANGE_ALL); + if (i < TX_BCMC_FIFO) + ieee80211_wake_queue(wlc->pub->ieee_hw, + brcms_fifo_to_ac(i)); + } + } + + /* free any posted rx packets */ + dma_rxreclaim(wlc_hw->di[RX_FIFO]); +} + +static u16 +brcms_b_read_objmem(struct brcms_hardware *wlc_hw, uint offset, u32 sel) +{ + struct bcma_device *core = wlc_hw->d11core; + u16 objoff = D11REGOFFS(objdata); + + bcma_write32(core, D11REGOFFS(objaddr), sel | (offset >> 2)); + (void)bcma_read32(core, D11REGOFFS(objaddr)); + if (offset & 2) + objoff += 2; + + return bcma_read16(core, objoff); +} + +static void +brcms_b_write_objmem(struct brcms_hardware *wlc_hw, uint offset, u16 v, + u32 sel) +{ + struct bcma_device *core = wlc_hw->d11core; + u16 objoff = D11REGOFFS(objdata); + + bcma_write32(core, D11REGOFFS(objaddr), sel | (offset >> 2)); + (void)bcma_read32(core, D11REGOFFS(objaddr)); + if (offset & 2) + objoff += 2; + + bcma_wflush16(core, objoff, v); +} + +/* + * Read a single u16 from shared memory. + * SHM 'offset' needs to be an even address + */ +u16 brcms_b_read_shm(struct brcms_hardware *wlc_hw, uint offset) +{ + return brcms_b_read_objmem(wlc_hw, offset, OBJADDR_SHM_SEL); +} + +/* + * Write a single u16 to shared memory. + * SHM 'offset' needs to be an even address + */ +void brcms_b_write_shm(struct brcms_hardware *wlc_hw, uint offset, u16 v) +{ + brcms_b_write_objmem(wlc_hw, offset, v, OBJADDR_SHM_SEL); +} + +/* + * Copy a buffer to shared memory of specified type . + * SHM 'offset' needs to be an even address and + * Buffer length 'len' must be an even number of bytes + * 'sel' selects the type of memory + */ +void +brcms_b_copyto_objmem(struct brcms_hardware *wlc_hw, uint offset, + const void *buf, int len, u32 sel) +{ + u16 v; + const u8 *p = (const u8 *)buf; + int i; + + if (len <= 0 || (offset & 1) || (len & 1)) + return; + + for (i = 0; i < len; i += 2) { + v = p[i] | (p[i + 1] << 8); + brcms_b_write_objmem(wlc_hw, offset + i, v, sel); + } +} + +/* + * Copy a piece of shared memory of specified type to a buffer . + * SHM 'offset' needs to be an even address and + * Buffer length 'len' must be an even number of bytes + * 'sel' selects the type of memory + */ +void +brcms_b_copyfrom_objmem(struct brcms_hardware *wlc_hw, uint offset, void *buf, + int len, u32 sel) +{ + u16 v; + u8 *p = (u8 *) buf; + int i; + + if (len <= 0 || (offset & 1) || (len & 1)) + return; + + for (i = 0; i < len; i += 2) { + v = brcms_b_read_objmem(wlc_hw, offset + i, sel); + p[i] = v & 0xFF; + p[i + 1] = (v >> 8) & 0xFF; + } +} + +/* Copy a buffer to shared memory. + * SHM 'offset' needs to be an even address and + * Buffer length 'len' must be an even number of bytes + */ +static void brcms_c_copyto_shm(struct brcms_c_info *wlc, uint offset, + const void *buf, int len) +{ + brcms_b_copyto_objmem(wlc->hw, offset, buf, len, OBJADDR_SHM_SEL); +} + +static void brcms_b_retrylimit_upd(struct brcms_hardware *wlc_hw, + u16 SRL, u16 LRL) +{ + wlc_hw->SRL = SRL; + wlc_hw->LRL = LRL; + + /* write retry limit to SCR, shouldn't need to suspend */ + if (wlc_hw->up) { + bcma_write32(wlc_hw->d11core, D11REGOFFS(objaddr), + OBJADDR_SCR_SEL | S_DOT11_SRC_LMT); + (void)bcma_read32(wlc_hw->d11core, D11REGOFFS(objaddr)); + bcma_write32(wlc_hw->d11core, D11REGOFFS(objdata), wlc_hw->SRL); + bcma_write32(wlc_hw->d11core, D11REGOFFS(objaddr), + OBJADDR_SCR_SEL | S_DOT11_LRC_LMT); + (void)bcma_read32(wlc_hw->d11core, D11REGOFFS(objaddr)); + bcma_write32(wlc_hw->d11core, D11REGOFFS(objdata), wlc_hw->LRL); + } +} + +static void brcms_b_pllreq(struct brcms_hardware *wlc_hw, bool set, u32 req_bit) +{ + if (set) { + if (mboolisset(wlc_hw->pllreq, req_bit)) + return; + + mboolset(wlc_hw->pllreq, req_bit); + + if (mboolisset(wlc_hw->pllreq, BRCMS_PLLREQ_FLIP)) { + if (!wlc_hw->sbclk) + brcms_b_xtal(wlc_hw, ON); + } + } else { + if (!mboolisset(wlc_hw->pllreq, req_bit)) + return; + + mboolclr(wlc_hw->pllreq, req_bit); + + if (mboolisset(wlc_hw->pllreq, BRCMS_PLLREQ_FLIP)) { + if (wlc_hw->sbclk) + brcms_b_xtal(wlc_hw, OFF); + } + } +} + +static void brcms_b_antsel_set(struct brcms_hardware *wlc_hw, u32 antsel_avail) +{ + wlc_hw->antsel_avail = antsel_avail; +} + +/* + * conditions under which the PM bit should be set in outgoing frames + * and STAY_AWAKE is meaningful + */ +static bool brcms_c_ps_allowed(struct brcms_c_info *wlc) +{ + /* not supporting PS so always return false for now */ + return false; +} + +static void brcms_c_statsupd(struct brcms_c_info *wlc) +{ + int i; + struct macstat macstats; +#ifdef DEBUG + u16 delta; + u16 rxf0ovfl; + u16 txfunfl[NFIFO]; +#endif /* DEBUG */ + + /* if driver down, make no sense to update stats */ + if (!wlc->pub->up) + return; + +#ifdef DEBUG + /* save last rx fifo 0 overflow count */ + rxf0ovfl = wlc->core->macstat_snapshot->rxf0ovfl; + + /* save last tx fifo underflow count */ + for (i = 0; i < NFIFO; i++) + txfunfl[i] = wlc->core->macstat_snapshot->txfunfl[i]; +#endif /* DEBUG */ + + /* Read mac stats from contiguous shared memory */ + brcms_b_copyfrom_objmem(wlc->hw, M_UCODE_MACSTAT, &macstats, + sizeof(struct macstat), OBJADDR_SHM_SEL); + +#ifdef DEBUG + /* check for rx fifo 0 overflow */ + delta = (u16) (wlc->core->macstat_snapshot->rxf0ovfl - rxf0ovfl); + if (delta) + brcms_err(wlc->hw->d11core, "wl%d: %u rx fifo 0 overflows!\n", + wlc->pub->unit, delta); + + /* check for tx fifo underflows */ + for (i = 0; i < NFIFO; i++) { + delta = + (u16) (wlc->core->macstat_snapshot->txfunfl[i] - + txfunfl[i]); + if (delta) + brcms_err(wlc->hw->d11core, + "wl%d: %u tx fifo %d underflows!\n", + wlc->pub->unit, delta, i); + } +#endif /* DEBUG */ + + /* merge counters from dma module */ + for (i = 0; i < NFIFO; i++) { + if (wlc->hw->di[i]) + dma_counterreset(wlc->hw->di[i]); + } +} + +static void brcms_b_reset(struct brcms_hardware *wlc_hw) +{ + /* reset the core */ + if (!brcms_deviceremoved(wlc_hw->wlc)) + brcms_b_corereset(wlc_hw, BRCMS_USE_COREFLAGS); + + /* purge the dma rings */ + brcms_c_flushqueues(wlc_hw->wlc); +} + +void brcms_c_reset(struct brcms_c_info *wlc) +{ + brcms_dbg_info(wlc->hw->d11core, "wl%d\n", wlc->pub->unit); + + /* slurp up hw mac counters before core reset */ + brcms_c_statsupd(wlc); + + /* reset our snapshot of macstat counters */ + memset(wlc->core->macstat_snapshot, 0, sizeof(struct macstat)); + + brcms_b_reset(wlc->hw); +} + +void brcms_c_init_scb(struct scb *scb) +{ + int i; + + memset(scb, 0, sizeof(struct scb)); + scb->flags = SCB_WMECAP | SCB_HTCAP; + for (i = 0; i < NUMPRIO; i++) { + scb->seqnum[i] = 0; + scb->seqctl[i] = 0xFFFF; + } + + scb->seqctl_nonqos = 0xFFFF; + scb->magic = SCB_MAGIC; +} + +/* d11 core init + * reset PSM + * download ucode/PCM + * let ucode run to suspended + * download ucode inits + * config other core registers + * init dma + */ +static void brcms_b_coreinit(struct brcms_c_info *wlc) +{ + struct brcms_hardware *wlc_hw = wlc->hw; + struct bcma_device *core = wlc_hw->d11core; + u32 sflags; + u32 bcnint_us; + uint i = 0; + bool fifosz_fixup = false; + int err = 0; + u16 buf[NFIFO]; + struct brcms_ucode *ucode = &wlc_hw->wlc->wl->ucode; + + brcms_dbg_info(core, "wl%d: core init\n", wlc_hw->unit); + + /* reset PSM */ + brcms_b_mctrl(wlc_hw, ~0, (MCTL_IHR_EN | MCTL_PSM_JMP_0 | MCTL_WAKE)); + + brcms_ucode_download(wlc_hw); + /* + * FIFOSZ fixup. driver wants to controls the fifo allocation. + */ + fifosz_fixup = true; + + /* let the PSM run to the suspended state, set mode to BSS STA */ + bcma_write32(core, D11REGOFFS(macintstatus), -1); + brcms_b_mctrl(wlc_hw, ~0, + (MCTL_IHR_EN | MCTL_INFRA | MCTL_PSM_RUN | MCTL_WAKE)); + + /* wait for ucode to self-suspend after auto-init */ + SPINWAIT(((bcma_read32(core, D11REGOFFS(macintstatus)) & + MI_MACSSPNDD) == 0), 1000 * 1000); + if ((bcma_read32(core, D11REGOFFS(macintstatus)) & MI_MACSSPNDD) == 0) + brcms_err(core, "wl%d: wlc_coreinit: ucode did not self-" + "suspend!\n", wlc_hw->unit); + + brcms_c_gpio_init(wlc); + + sflags = bcma_aread32(core, BCMA_IOST); + + if (D11REV_IS(wlc_hw->corerev, 17) || D11REV_IS(wlc_hw->corerev, 23)) { + if (BRCMS_ISNPHY(wlc_hw->band)) + brcms_c_write_inits(wlc_hw, ucode->d11n0initvals16); + else + brcms_err(core, "%s: wl%d: unsupported phy in corerev" + " %d\n", __func__, wlc_hw->unit, + wlc_hw->corerev); + } else if (D11REV_IS(wlc_hw->corerev, 24)) { + if (BRCMS_ISLCNPHY(wlc_hw->band)) + brcms_c_write_inits(wlc_hw, ucode->d11lcn0initvals24); + else + brcms_err(core, "%s: wl%d: unsupported phy in corerev" + " %d\n", __func__, wlc_hw->unit, + wlc_hw->corerev); + } else { + brcms_err(core, "%s: wl%d: unsupported corerev %d\n", + __func__, wlc_hw->unit, wlc_hw->corerev); + } + + /* For old ucode, txfifo sizes needs to be modified(increased) */ + if (fifosz_fixup) + brcms_b_corerev_fifofixup(wlc_hw); + + /* check txfifo allocations match between ucode and driver */ + buf[TX_AC_BE_FIFO] = brcms_b_read_shm(wlc_hw, M_FIFOSIZE0); + if (buf[TX_AC_BE_FIFO] != wlc_hw->xmtfifo_sz[TX_AC_BE_FIFO]) { + i = TX_AC_BE_FIFO; + err = -1; + } + buf[TX_AC_VI_FIFO] = brcms_b_read_shm(wlc_hw, M_FIFOSIZE1); + if (buf[TX_AC_VI_FIFO] != wlc_hw->xmtfifo_sz[TX_AC_VI_FIFO]) { + i = TX_AC_VI_FIFO; + err = -1; + } + buf[TX_AC_BK_FIFO] = brcms_b_read_shm(wlc_hw, M_FIFOSIZE2); + buf[TX_AC_VO_FIFO] = (buf[TX_AC_BK_FIFO] >> 8) & 0xff; + buf[TX_AC_BK_FIFO] &= 0xff; + if (buf[TX_AC_BK_FIFO] != wlc_hw->xmtfifo_sz[TX_AC_BK_FIFO]) { + i = TX_AC_BK_FIFO; + err = -1; + } + if (buf[TX_AC_VO_FIFO] != wlc_hw->xmtfifo_sz[TX_AC_VO_FIFO]) { + i = TX_AC_VO_FIFO; + err = -1; + } + buf[TX_BCMC_FIFO] = brcms_b_read_shm(wlc_hw, M_FIFOSIZE3); + buf[TX_ATIM_FIFO] = (buf[TX_BCMC_FIFO] >> 8) & 0xff; + buf[TX_BCMC_FIFO] &= 0xff; + if (buf[TX_BCMC_FIFO] != wlc_hw->xmtfifo_sz[TX_BCMC_FIFO]) { + i = TX_BCMC_FIFO; + err = -1; + } + if (buf[TX_ATIM_FIFO] != wlc_hw->xmtfifo_sz[TX_ATIM_FIFO]) { + i = TX_ATIM_FIFO; + err = -1; + } + if (err != 0) + brcms_err(core, "wlc_coreinit: txfifo mismatch: ucode size %d" + " driver size %d index %d\n", buf[i], + wlc_hw->xmtfifo_sz[i], i); + + /* make sure we can still talk to the mac */ + WARN_ON(bcma_read32(core, D11REGOFFS(maccontrol)) == 0xffffffff); + + /* band-specific inits done by wlc_bsinit() */ + + /* Set up frame burst size and antenna swap threshold init values */ + brcms_b_write_shm(wlc_hw, M_MBURST_SIZE, MAXTXFRAMEBURST); + brcms_b_write_shm(wlc_hw, M_MAX_ANTCNT, ANTCNT); + + /* enable one rx interrupt per received frame */ + bcma_write32(core, D11REGOFFS(intrcvlazy[0]), (1 << IRL_FC_SHIFT)); + + /* set the station mode (BSS STA) */ + brcms_b_mctrl(wlc_hw, + (MCTL_INFRA | MCTL_DISCARD_PMQ | MCTL_AP), + (MCTL_INFRA | MCTL_DISCARD_PMQ)); + + /* set up Beacon interval */ + bcnint_us = 0x8000 << 10; + bcma_write32(core, D11REGOFFS(tsf_cfprep), + (bcnint_us << CFPREP_CBI_SHIFT)); + bcma_write32(core, D11REGOFFS(tsf_cfpstart), bcnint_us); + bcma_write32(core, D11REGOFFS(macintstatus), MI_GP1); + + /* write interrupt mask */ + bcma_write32(core, D11REGOFFS(intctrlregs[RX_FIFO].intmask), + DEF_RXINTMASK); + + /* allow the MAC to control the PHY clock (dynamic on/off) */ + brcms_b_macphyclk_set(wlc_hw, ON); + + /* program dynamic clock control fast powerup delay register */ + wlc->fastpwrup_dly = ai_clkctl_fast_pwrup_delay(wlc_hw->sih); + bcma_write16(core, D11REGOFFS(scc_fastpwrup_dly), wlc->fastpwrup_dly); + + /* tell the ucode the corerev */ + brcms_b_write_shm(wlc_hw, M_MACHW_VER, (u16) wlc_hw->corerev); + + /* tell the ucode MAC capabilities */ + brcms_b_write_shm(wlc_hw, M_MACHW_CAP_L, + (u16) (wlc_hw->machwcap & 0xffff)); + brcms_b_write_shm(wlc_hw, M_MACHW_CAP_H, + (u16) ((wlc_hw-> + machwcap >> 16) & 0xffff)); + + /* write retry limits to SCR, this done after PSM init */ + bcma_write32(core, D11REGOFFS(objaddr), + OBJADDR_SCR_SEL | S_DOT11_SRC_LMT); + (void)bcma_read32(core, D11REGOFFS(objaddr)); + bcma_write32(core, D11REGOFFS(objdata), wlc_hw->SRL); + bcma_write32(core, D11REGOFFS(objaddr), + OBJADDR_SCR_SEL | S_DOT11_LRC_LMT); + (void)bcma_read32(core, D11REGOFFS(objaddr)); + bcma_write32(core, D11REGOFFS(objdata), wlc_hw->LRL); + + /* write rate fallback retry limits */ + brcms_b_write_shm(wlc_hw, M_SFRMTXCNTFBRTHSD, wlc_hw->SFBL); + brcms_b_write_shm(wlc_hw, M_LFRMTXCNTFBRTHSD, wlc_hw->LFBL); + + bcma_mask16(core, D11REGOFFS(ifs_ctl), 0x0FFF); + bcma_write16(core, D11REGOFFS(ifs_aifsn), EDCF_AIFSN_MIN); + + /* init the tx dma engines */ + for (i = 0; i < NFIFO; i++) { + if (wlc_hw->di[i]) + dma_txinit(wlc_hw->di[i]); + } + + /* init the rx dma engine(s) and post receive buffers */ + dma_rxinit(wlc_hw->di[RX_FIFO]); + dma_rxfill(wlc_hw->di[RX_FIFO]); +} + +void +static brcms_b_init(struct brcms_hardware *wlc_hw, u16 chanspec) { + u32 macintmask; + bool fastclk; + struct brcms_c_info *wlc = wlc_hw->wlc; + + /* request FAST clock if not on */ + fastclk = wlc_hw->forcefastclk; + if (!fastclk) + brcms_b_clkctl_clk(wlc_hw, BCMA_CLKMODE_FAST); + + /* disable interrupts */ + macintmask = brcms_intrsoff(wlc->wl); + + /* set up the specified band and chanspec */ + brcms_c_setxband(wlc_hw, chspec_bandunit(chanspec)); + wlc_phy_chanspec_radio_set(wlc_hw->band->pi, chanspec); + + /* do one-time phy inits and calibration */ + wlc_phy_cal_init(wlc_hw->band->pi); + + /* core-specific initialization */ + brcms_b_coreinit(wlc); + + /* band-specific inits */ + brcms_b_bsinit(wlc, chanspec); + + /* restore macintmask */ + brcms_intrsrestore(wlc->wl, macintmask); + + /* seed wake_override with BRCMS_WAKE_OVERRIDE_MACSUSPEND since the mac + * is suspended and brcms_c_enable_mac() will clear this override bit. + */ + mboolset(wlc_hw->wake_override, BRCMS_WAKE_OVERRIDE_MACSUSPEND); + + /* + * initialize mac_suspend_depth to 1 to match ucode + * initial suspended state + */ + wlc_hw->mac_suspend_depth = 1; + + /* restore the clk */ + if (!fastclk) + brcms_b_clkctl_clk(wlc_hw, BCMA_CLKMODE_DYNAMIC); +} + +static void brcms_c_set_phy_chanspec(struct brcms_c_info *wlc, + u16 chanspec) +{ + /* Save our copy of the chanspec */ + wlc->chanspec = chanspec; + + /* Set the chanspec and power limits for this locale */ + brcms_c_channel_set_chanspec(wlc->cmi, chanspec, BRCMS_TXPWR_MAX); + + if (wlc->stf->ss_algosel_auto) + brcms_c_stf_ss_algo_channel_get(wlc, &wlc->stf->ss_algo_channel, + chanspec); + + brcms_c_stf_ss_update(wlc, wlc->band); +} + +static void +brcms_default_rateset(struct brcms_c_info *wlc, struct brcms_c_rateset *rs) +{ + brcms_c_rateset_default(rs, NULL, wlc->band->phytype, + wlc->band->bandtype, false, BRCMS_RATE_MASK_FULL, + (bool) (wlc->pub->_n_enab & SUPPORT_11N), + brcms_chspec_bw(wlc->default_bss->chanspec), + wlc->stf->txstreams); +} + +/* derive wlc->band->basic_rate[] table from 'rateset' */ +static void brcms_c_rate_lookup_init(struct brcms_c_info *wlc, + struct brcms_c_rateset *rateset) +{ + u8 rate; + u8 mandatory; + u8 cck_basic = 0; + u8 ofdm_basic = 0; + u8 *br = wlc->band->basic_rate; + uint i; + + /* incoming rates are in 500kbps units as in 802.11 Supported Rates */ + memset(br, 0, BRCM_MAXRATE + 1); + + /* For each basic rate in the rates list, make an entry in the + * best basic lookup. + */ + for (i = 0; i < rateset->count; i++) { + /* only make an entry for a basic rate */ + if (!(rateset->rates[i] & BRCMS_RATE_FLAG)) + continue; + + /* mask off basic bit */ + rate = (rateset->rates[i] & BRCMS_RATE_MASK); + + if (rate > BRCM_MAXRATE) { + brcms_err(wlc->hw->d11core, "brcms_c_rate_lookup_init: " + "invalid rate 0x%X in rate set\n", + rateset->rates[i]); + continue; + } + + br[rate] = rate; + } + + /* The rate lookup table now has non-zero entries for each + * basic rate, equal to the basic rate: br[basicN] = basicN + * + * To look up the best basic rate corresponding to any + * particular rate, code can use the basic_rate table + * like this + * + * basic_rate = wlc->band->basic_rate[tx_rate] + * + * Make sure there is a best basic rate entry for + * every rate by walking up the table from low rates + * to high, filling in holes in the lookup table + */ + + for (i = 0; i < wlc->band->hw_rateset.count; i++) { + rate = wlc->band->hw_rateset.rates[i]; + + if (br[rate] != 0) { + /* This rate is a basic rate. + * Keep track of the best basic rate so far by + * modulation type. + */ + if (is_ofdm_rate(rate)) + ofdm_basic = rate; + else + cck_basic = rate; + + continue; + } + + /* This rate is not a basic rate so figure out the + * best basic rate less than this rate and fill in + * the hole in the table + */ + + br[rate] = is_ofdm_rate(rate) ? ofdm_basic : cck_basic; + + if (br[rate] != 0) + continue; + + if (is_ofdm_rate(rate)) { + /* + * In 11g and 11a, the OFDM mandatory rates + * are 6, 12, and 24 Mbps + */ + if (rate >= BRCM_RATE_24M) + mandatory = BRCM_RATE_24M; + else if (rate >= BRCM_RATE_12M) + mandatory = BRCM_RATE_12M; + else + mandatory = BRCM_RATE_6M; + } else { + /* In 11b, all CCK rates are mandatory 1 - 11 Mbps */ + mandatory = rate; + } + + br[rate] = mandatory; + } +} + +static void brcms_c_bandinit_ordered(struct brcms_c_info *wlc, + u16 chanspec) +{ + struct brcms_c_rateset default_rateset; + uint parkband; + uint i, band_order[2]; + + /* + * We might have been bandlocked during down and the chip + * power-cycled (hibernate). Figure out the right band to park on + */ + if (wlc->bandlocked || wlc->pub->_nbands == 1) { + /* updated in brcms_c_bandlock() */ + parkband = wlc->band->bandunit; + band_order[0] = band_order[1] = parkband; + } else { + /* park on the band of the specified chanspec */ + parkband = chspec_bandunit(chanspec); + + /* order so that parkband initialize last */ + band_order[0] = parkband ^ 1; + band_order[1] = parkband; + } + + /* make each band operational, software state init */ + for (i = 0; i < wlc->pub->_nbands; i++) { + uint j = band_order[i]; + + wlc->band = wlc->bandstate[j]; + + brcms_default_rateset(wlc, &default_rateset); + + /* fill in hw_rate */ + brcms_c_rateset_filter(&default_rateset, &wlc->band->hw_rateset, + false, BRCMS_RATES_CCK_OFDM, BRCMS_RATE_MASK, + (bool) (wlc->pub->_n_enab & SUPPORT_11N)); + + /* init basic rate lookup */ + brcms_c_rate_lookup_init(wlc, &default_rateset); + } + + /* sync up phy/radio chanspec */ + brcms_c_set_phy_chanspec(wlc, chanspec); +} + +/* + * Set or clear filtering related maccontrol bits based on + * specified filter flags + */ +void brcms_c_mac_promisc(struct brcms_c_info *wlc, uint filter_flags) +{ + u32 promisc_bits = 0; + + wlc->filter_flags = filter_flags; + + if (filter_flags & (FIF_PROMISC_IN_BSS | FIF_OTHER_BSS)) + promisc_bits |= MCTL_PROMISC; + + if (filter_flags & FIF_BCN_PRBRESP_PROMISC) + promisc_bits |= MCTL_BCNS_PROMISC; + + if (filter_flags & FIF_FCSFAIL) + promisc_bits |= MCTL_KEEPBADFCS; + + if (filter_flags & (FIF_CONTROL | FIF_PSPOLL)) + promisc_bits |= MCTL_KEEPCONTROL; + + brcms_b_mctrl(wlc->hw, + MCTL_PROMISC | MCTL_BCNS_PROMISC | + MCTL_KEEPCONTROL | MCTL_KEEPBADFCS, + promisc_bits); +} + +/* + * ucode, hwmac update + * Channel dependent updates for ucode and hw + */ +static void brcms_c_ucode_mac_upd(struct brcms_c_info *wlc) +{ + /* enable or disable any active IBSSs depending on whether or not + * we are on the home channel + */ + if (wlc->home_chanspec == wlc_phy_chanspec_get(wlc->band->pi)) { + if (wlc->pub->associated) { + /* + * BMAC_NOTE: This is something that should be fixed + * in ucode inits. I think that the ucode inits set + * up the bcn templates and shm values with a bogus + * beacon. This should not be done in the inits. If + * ucode needs to set up a beacon for testing, the + * test routines should write it down, not expect the + * inits to populate a bogus beacon. + */ + if (BRCMS_PHY_11N_CAP(wlc->band)) + brcms_b_write_shm(wlc->hw, + M_BCN_TXTSF_OFFSET, 0); + } + } else { + /* disable an active IBSS if we are not on the home channel */ + } +} + +static void brcms_c_write_rate_shm(struct brcms_c_info *wlc, u8 rate, + u8 basic_rate) +{ + u8 phy_rate, index; + u8 basic_phy_rate, basic_index; + u16 dir_table, basic_table; + u16 basic_ptr; + + /* Shared memory address for the table we are reading */ + dir_table = is_ofdm_rate(basic_rate) ? M_RT_DIRMAP_A : M_RT_DIRMAP_B; + + /* Shared memory address for the table we are writing */ + basic_table = is_ofdm_rate(rate) ? M_RT_BBRSMAP_A : M_RT_BBRSMAP_B; + + /* + * for a given rate, the LS-nibble of the PLCP SIGNAL field is + * the index into the rate table. + */ + phy_rate = rate_info[rate] & BRCMS_RATE_MASK; + basic_phy_rate = rate_info[basic_rate] & BRCMS_RATE_MASK; + index = phy_rate & 0xf; + basic_index = basic_phy_rate & 0xf; + + /* Find the SHM pointer to the ACK rate entry by looking in the + * Direct-map Table + */ + basic_ptr = brcms_b_read_shm(wlc->hw, (dir_table + basic_index * 2)); + + /* Update the SHM BSS-basic-rate-set mapping table with the pointer + * to the correct basic rate for the given incoming rate + */ + brcms_b_write_shm(wlc->hw, (basic_table + index * 2), basic_ptr); +} + +static const struct brcms_c_rateset * +brcms_c_rateset_get_hwrs(struct brcms_c_info *wlc) +{ + const struct brcms_c_rateset *rs_dflt; + + if (BRCMS_PHY_11N_CAP(wlc->band)) { + if (wlc->band->bandtype == BRCM_BAND_5G) + rs_dflt = &ofdm_mimo_rates; + else + rs_dflt = &cck_ofdm_mimo_rates; + } else if (wlc->band->gmode) + rs_dflt = &cck_ofdm_rates; + else + rs_dflt = &cck_rates; + + return rs_dflt; +} + +static void brcms_c_set_ratetable(struct brcms_c_info *wlc) +{ + const struct brcms_c_rateset *rs_dflt; + struct brcms_c_rateset rs; + u8 rate, basic_rate; + uint i; + + rs_dflt = brcms_c_rateset_get_hwrs(wlc); + + brcms_c_rateset_copy(rs_dflt, &rs); + brcms_c_rateset_mcs_upd(&rs, wlc->stf->txstreams); + + /* walk the phy rate table and update SHM basic rate lookup table */ + for (i = 0; i < rs.count; i++) { + rate = rs.rates[i] & BRCMS_RATE_MASK; + + /* for a given rate brcms_basic_rate returns the rate at + * which a response ACK/CTS should be sent. + */ + basic_rate = brcms_basic_rate(wlc, rate); + if (basic_rate == 0) + /* This should only happen if we are using a + * restricted rateset. + */ + basic_rate = rs.rates[0] & BRCMS_RATE_MASK; + + brcms_c_write_rate_shm(wlc, rate, basic_rate); + } +} + +/* band-specific init */ +static void brcms_c_bsinit(struct brcms_c_info *wlc) +{ + brcms_dbg_info(wlc->hw->d11core, "wl%d: bandunit %d\n", + wlc->pub->unit, wlc->band->bandunit); + + /* write ucode ACK/CTS rate table */ + brcms_c_set_ratetable(wlc); + + /* update some band specific mac configuration */ + brcms_c_ucode_mac_upd(wlc); + + /* init antenna selection */ + brcms_c_antsel_init(wlc->asi); + +} + +/* formula: IDLE_BUSY_RATIO_X_16 = (100-duty_cycle)/duty_cycle*16 */ +static int +brcms_c_duty_cycle_set(struct brcms_c_info *wlc, int duty_cycle, bool isOFDM, + bool writeToShm) +{ + int idle_busy_ratio_x_16 = 0; + uint offset = + isOFDM ? M_TX_IDLE_BUSY_RATIO_X_16_OFDM : + M_TX_IDLE_BUSY_RATIO_X_16_CCK; + if (duty_cycle > 100 || duty_cycle < 0) { + brcms_err(wlc->hw->d11core, + "wl%d: duty cycle value off limit\n", + wlc->pub->unit); + return -EINVAL; + } + if (duty_cycle) + idle_busy_ratio_x_16 = (100 - duty_cycle) * 16 / duty_cycle; + /* Only write to shared memory when wl is up */ + if (writeToShm) + brcms_b_write_shm(wlc->hw, offset, (u16) idle_busy_ratio_x_16); + + if (isOFDM) + wlc->tx_duty_cycle_ofdm = (u16) duty_cycle; + else + wlc->tx_duty_cycle_cck = (u16) duty_cycle; + + return 0; +} + +/* push sw hps and wake state through hardware */ +static void brcms_c_set_ps_ctrl(struct brcms_c_info *wlc) +{ + u32 v1, v2; + bool hps; + bool awake_before; + + hps = brcms_c_ps_allowed(wlc); + + brcms_dbg_mac80211(wlc->hw->d11core, "wl%d: hps %d\n", wlc->pub->unit, + hps); + + v1 = bcma_read32(wlc->hw->d11core, D11REGOFFS(maccontrol)); + v2 = MCTL_WAKE; + if (hps) + v2 |= MCTL_HPS; + + brcms_b_mctrl(wlc->hw, MCTL_WAKE | MCTL_HPS, v2); + + awake_before = ((v1 & MCTL_WAKE) || ((v1 & MCTL_HPS) == 0)); + + if (!awake_before) + brcms_b_wait_for_wake(wlc->hw); +} + +/* + * Write this BSS config's MAC address to core. + * Updates RXE match engine. + */ +static int brcms_c_set_mac(struct brcms_bss_cfg *bsscfg) +{ + int err = 0; + struct brcms_c_info *wlc = bsscfg->wlc; + + /* enter the MAC addr into the RXE match registers */ + brcms_c_set_addrmatch(wlc, RCM_MAC_OFFSET, wlc->pub->cur_etheraddr); + + brcms_c_ampdu_macaddr_upd(wlc); + + return err; +} + +/* Write the BSS config's BSSID address to core (set_bssid in d11procs.tcl). + * Updates RXE match engine. + */ +static void brcms_c_set_bssid(struct brcms_bss_cfg *bsscfg) +{ + /* we need to update BSSID in RXE match registers */ + brcms_c_set_addrmatch(bsscfg->wlc, RCM_BSSID_OFFSET, bsscfg->BSSID); +} + +void brcms_c_set_ssid(struct brcms_c_info *wlc, u8 *ssid, size_t ssid_len) +{ + u8 len = min_t(u8, sizeof(wlc->bsscfg->SSID), ssid_len); + memset(wlc->bsscfg->SSID, 0, sizeof(wlc->bsscfg->SSID)); + + memcpy(wlc->bsscfg->SSID, ssid, len); + wlc->bsscfg->SSID_len = len; +} + +static void brcms_b_set_shortslot(struct brcms_hardware *wlc_hw, bool shortslot) +{ + wlc_hw->shortslot = shortslot; + + if (wlc_hw->band->bandtype == BRCM_BAND_2G && wlc_hw->up) { + brcms_c_suspend_mac_and_wait(wlc_hw->wlc); + brcms_b_update_slot_timing(wlc_hw, shortslot); + brcms_c_enable_mac(wlc_hw->wlc); + } +} + +/* + * Suspend the the MAC and update the slot timing + * for standard 11b/g (20us slots) or shortslot 11g (9us slots). + */ +static void brcms_c_switch_shortslot(struct brcms_c_info *wlc, bool shortslot) +{ + /* use the override if it is set */ + if (wlc->shortslot_override != BRCMS_SHORTSLOT_AUTO) + shortslot = (wlc->shortslot_override == BRCMS_SHORTSLOT_ON); + + if (wlc->shortslot == shortslot) + return; + + wlc->shortslot = shortslot; + + brcms_b_set_shortslot(wlc->hw, shortslot); +} + +static void brcms_c_set_home_chanspec(struct brcms_c_info *wlc, u16 chanspec) +{ + if (wlc->home_chanspec != chanspec) { + wlc->home_chanspec = chanspec; + + if (wlc->pub->associated) + wlc->bsscfg->current_bss->chanspec = chanspec; + } +} + +void +brcms_b_set_chanspec(struct brcms_hardware *wlc_hw, u16 chanspec, + bool mute_tx, struct txpwr_limits *txpwr) +{ + uint bandunit; + + brcms_dbg_mac80211(wlc_hw->d11core, "wl%d: 0x%x\n", wlc_hw->unit, + chanspec); + + wlc_hw->chanspec = chanspec; + + /* Switch bands if necessary */ + if (wlc_hw->_nbands > 1) { + bandunit = chspec_bandunit(chanspec); + if (wlc_hw->band->bandunit != bandunit) { + /* brcms_b_setband disables other bandunit, + * use light band switch if not up yet + */ + if (wlc_hw->up) { + wlc_phy_chanspec_radio_set(wlc_hw-> + bandstate[bandunit]-> + pi, chanspec); + brcms_b_setband(wlc_hw, bandunit, chanspec); + } else { + brcms_c_setxband(wlc_hw, bandunit); + } + } + } + + wlc_phy_initcal_enable(wlc_hw->band->pi, !mute_tx); + + if (!wlc_hw->up) { + if (wlc_hw->clk) + wlc_phy_txpower_limit_set(wlc_hw->band->pi, txpwr, + chanspec); + wlc_phy_chanspec_radio_set(wlc_hw->band->pi, chanspec); + } else { + wlc_phy_chanspec_set(wlc_hw->band->pi, chanspec); + wlc_phy_txpower_limit_set(wlc_hw->band->pi, txpwr, chanspec); + + /* Update muting of the channel */ + brcms_b_mute(wlc_hw, mute_tx); + } +} + +/* switch to and initialize new band */ +static void brcms_c_setband(struct brcms_c_info *wlc, + uint bandunit) +{ + wlc->band = wlc->bandstate[bandunit]; + + if (!wlc->pub->up) + return; + + /* wait for at least one beacon before entering sleeping state */ + brcms_c_set_ps_ctrl(wlc); + + /* band-specific initializations */ + brcms_c_bsinit(wlc); +} + +static void brcms_c_set_chanspec(struct brcms_c_info *wlc, u16 chanspec) +{ + uint bandunit; + bool switchband = false; + u16 old_chanspec = wlc->chanspec; + + if (!brcms_c_valid_chanspec_db(wlc->cmi, chanspec)) { + brcms_err(wlc->hw->d11core, "wl%d: %s: Bad channel %d\n", + wlc->pub->unit, __func__, CHSPEC_CHANNEL(chanspec)); + return; + } + + /* Switch bands if necessary */ + if (wlc->pub->_nbands > 1) { + bandunit = chspec_bandunit(chanspec); + if (wlc->band->bandunit != bandunit || wlc->bandinit_pending) { + switchband = true; + if (wlc->bandlocked) { + brcms_err(wlc->hw->d11core, + "wl%d: %s: chspec %d band is locked!\n", + wlc->pub->unit, __func__, + CHSPEC_CHANNEL(chanspec)); + return; + } + /* + * should the setband call come after the + * brcms_b_chanspec() ? if the setband updates + * (brcms_c_bsinit) use low level calls to inspect and + * set state, the state inspected may be from the wrong + * band, or the following brcms_b_set_chanspec() may + * undo the work. + */ + brcms_c_setband(wlc, bandunit); + } + } + + /* sync up phy/radio chanspec */ + brcms_c_set_phy_chanspec(wlc, chanspec); + + /* init antenna selection */ + if (brcms_chspec_bw(old_chanspec) != brcms_chspec_bw(chanspec)) { + brcms_c_antsel_init(wlc->asi); + + /* Fix the hardware rateset based on bw. + * Mainly add MCS32 for 40Mhz, remove MCS 32 for 20Mhz + */ + brcms_c_rateset_bw_mcs_filter(&wlc->band->hw_rateset, + wlc->band->mimo_cap_40 ? brcms_chspec_bw(chanspec) : 0); + } + + /* update some mac configuration since chanspec changed */ + brcms_c_ucode_mac_upd(wlc); +} + +/* + * This function changes the phytxctl for beacon based on current + * beacon ratespec AND txant setting as per this table: + * ratespec CCK ant = wlc->stf->txant + * OFDM ant = 3 + */ +void brcms_c_beacon_phytxctl_txant_upd(struct brcms_c_info *wlc, + u32 bcn_rspec) +{ + u16 phyctl; + u16 phytxant = wlc->stf->phytxant; + u16 mask = PHY_TXC_ANT_MASK; + + /* for non-siso rates or default setting, use the available chains */ + if (BRCMS_PHY_11N_CAP(wlc->band)) + phytxant = brcms_c_stf_phytxchain_sel(wlc, bcn_rspec); + + phyctl = brcms_b_read_shm(wlc->hw, M_BCN_PCTLWD); + phyctl = (phyctl & ~mask) | phytxant; + brcms_b_write_shm(wlc->hw, M_BCN_PCTLWD, phyctl); +} + +/* + * centralized protection config change function to simplify debugging, no + * consistency checking this should be called only on changes to avoid overhead + * in periodic function + */ +void brcms_c_protection_upd(struct brcms_c_info *wlc, uint idx, int val) +{ + /* + * Cannot use brcms_dbg_* here because this function is called + * before wlc is sufficiently initialized. + */ + BCMMSG(wlc->wiphy, "idx %d, val %d\n", idx, val); + + switch (idx) { + case BRCMS_PROT_G_SPEC: + wlc->protection->_g = (bool) val; + break; + case BRCMS_PROT_G_OVR: + wlc->protection->g_override = (s8) val; + break; + case BRCMS_PROT_G_USER: + wlc->protection->gmode_user = (u8) val; + break; + case BRCMS_PROT_OVERLAP: + wlc->protection->overlap = (s8) val; + break; + case BRCMS_PROT_N_USER: + wlc->protection->nmode_user = (s8) val; + break; + case BRCMS_PROT_N_CFG: + wlc->protection->n_cfg = (s8) val; + break; + case BRCMS_PROT_N_CFG_OVR: + wlc->protection->n_cfg_override = (s8) val; + break; + case BRCMS_PROT_N_NONGF: + wlc->protection->nongf = (bool) val; + break; + case BRCMS_PROT_N_NONGF_OVR: + wlc->protection->nongf_override = (s8) val; + break; + case BRCMS_PROT_N_PAM_OVR: + wlc->protection->n_pam_override = (s8) val; + break; + case BRCMS_PROT_N_OBSS: + wlc->protection->n_obss = (bool) val; + break; + + default: + break; + } + +} + +static void brcms_c_ht_update_sgi_rx(struct brcms_c_info *wlc, int val) +{ + if (wlc->pub->up) { + brcms_c_update_beacon(wlc); + brcms_c_update_probe_resp(wlc, true); + } +} + +static void brcms_c_ht_update_ldpc(struct brcms_c_info *wlc, s8 val) +{ + wlc->stf->ldpc = val; + + if (wlc->pub->up) { + brcms_c_update_beacon(wlc); + brcms_c_update_probe_resp(wlc, true); + wlc_phy_ldpc_override_set(wlc->band->pi, (val ? true : false)); + } +} + +void brcms_c_wme_setparams(struct brcms_c_info *wlc, u16 aci, + const struct ieee80211_tx_queue_params *params, + bool suspend) +{ + int i; + struct shm_acparams acp_shm; + u16 *shm_entry; + + /* Only apply params if the core is out of reset and has clocks */ + if (!wlc->clk) { + brcms_err(wlc->hw->d11core, "wl%d: %s : no-clock\n", + wlc->pub->unit, __func__); + return; + } + + memset(&acp_shm, 0, sizeof(struct shm_acparams)); + /* fill in shm ac params struct */ + acp_shm.txop = params->txop; + /* convert from units of 32us to us for ucode */ + wlc->edcf_txop[aci & 0x3] = acp_shm.txop = + EDCF_TXOP2USEC(acp_shm.txop); + acp_shm.aifs = (params->aifs & EDCF_AIFSN_MASK); + + if (aci == IEEE80211_AC_VI && acp_shm.txop == 0 + && acp_shm.aifs < EDCF_AIFSN_MAX) + acp_shm.aifs++; + + if (acp_shm.aifs < EDCF_AIFSN_MIN + || acp_shm.aifs > EDCF_AIFSN_MAX) { + brcms_err(wlc->hw->d11core, "wl%d: edcf_setparams: bad " + "aifs %d\n", wlc->pub->unit, acp_shm.aifs); + } else { + acp_shm.cwmin = params->cw_min; + acp_shm.cwmax = params->cw_max; + acp_shm.cwcur = acp_shm.cwmin; + acp_shm.bslots = + bcma_read16(wlc->hw->d11core, D11REGOFFS(tsf_random)) & + acp_shm.cwcur; + acp_shm.reggap = acp_shm.bslots + acp_shm.aifs; + /* Indicate the new params to the ucode */ + acp_shm.status = brcms_b_read_shm(wlc->hw, (M_EDCF_QINFO + + wme_ac2fifo[aci] * + M_EDCF_QLEN + + M_EDCF_STATUS_OFF)); + acp_shm.status |= WME_STATUS_NEWAC; + + /* Fill in shm acparam table */ + shm_entry = (u16 *) &acp_shm; + for (i = 0; i < (int)sizeof(struct shm_acparams); i += 2) + brcms_b_write_shm(wlc->hw, + M_EDCF_QINFO + + wme_ac2fifo[aci] * M_EDCF_QLEN + i, + *shm_entry++); + } + + if (suspend) + brcms_c_suspend_mac_and_wait(wlc); + + brcms_c_update_beacon(wlc); + brcms_c_update_probe_resp(wlc, false); + + if (suspend) + brcms_c_enable_mac(wlc); +} + +static void brcms_c_edcf_setparams(struct brcms_c_info *wlc, bool suspend) +{ + u16 aci; + int i_ac; + struct ieee80211_tx_queue_params txq_pars; + static const struct edcf_acparam default_edcf_acparams[] = { + {EDCF_AC_BE_ACI_STA, EDCF_AC_BE_ECW_STA, EDCF_AC_BE_TXOP_STA}, + {EDCF_AC_BK_ACI_STA, EDCF_AC_BK_ECW_STA, EDCF_AC_BK_TXOP_STA}, + {EDCF_AC_VI_ACI_STA, EDCF_AC_VI_ECW_STA, EDCF_AC_VI_TXOP_STA}, + {EDCF_AC_VO_ACI_STA, EDCF_AC_VO_ECW_STA, EDCF_AC_VO_TXOP_STA} + }; /* ucode needs these parameters during its initialization */ + const struct edcf_acparam *edcf_acp = &default_edcf_acparams[0]; + + for (i_ac = 0; i_ac < IEEE80211_NUM_ACS; i_ac++, edcf_acp++) { + /* find out which ac this set of params applies to */ + aci = (edcf_acp->ACI & EDCF_ACI_MASK) >> EDCF_ACI_SHIFT; + + /* fill in shm ac params struct */ + txq_pars.txop = edcf_acp->TXOP; + txq_pars.aifs = edcf_acp->ACI; + + /* CWmin = 2^(ECWmin) - 1 */ + txq_pars.cw_min = EDCF_ECW2CW(edcf_acp->ECW & EDCF_ECWMIN_MASK); + /* CWmax = 2^(ECWmax) - 1 */ + txq_pars.cw_max = EDCF_ECW2CW((edcf_acp->ECW & EDCF_ECWMAX_MASK) + >> EDCF_ECWMAX_SHIFT); + brcms_c_wme_setparams(wlc, aci, &txq_pars, suspend); + } + + if (suspend) { + brcms_c_suspend_mac_and_wait(wlc); + brcms_c_enable_mac(wlc); + } +} + +static void brcms_c_radio_monitor_start(struct brcms_c_info *wlc) +{ + /* Don't start the timer if HWRADIO feature is disabled */ + if (wlc->radio_monitor) + return; + + wlc->radio_monitor = true; + brcms_b_pllreq(wlc->hw, true, BRCMS_PLLREQ_RADIO_MON); + brcms_add_timer(wlc->radio_timer, TIMER_INTERVAL_RADIOCHK, true); +} + +static bool brcms_c_radio_monitor_stop(struct brcms_c_info *wlc) +{ + if (!wlc->radio_monitor) + return true; + + wlc->radio_monitor = false; + brcms_b_pllreq(wlc->hw, false, BRCMS_PLLREQ_RADIO_MON); + return brcms_del_timer(wlc->radio_timer); +} + +/* read hwdisable state and propagate to wlc flag */ +static void brcms_c_radio_hwdisable_upd(struct brcms_c_info *wlc) +{ + if (wlc->pub->hw_off) + return; + + if (brcms_b_radio_read_hwdisabled(wlc->hw)) + mboolset(wlc->pub->radio_disabled, WL_RADIO_HW_DISABLE); + else + mboolclr(wlc->pub->radio_disabled, WL_RADIO_HW_DISABLE); +} + +/* update hwradio status and return it */ +bool brcms_c_check_radio_disabled(struct brcms_c_info *wlc) +{ + brcms_c_radio_hwdisable_upd(wlc); + + return mboolisset(wlc->pub->radio_disabled, WL_RADIO_HW_DISABLE) ? + true : false; +} + +/* periodical query hw radio button while driver is "down" */ +static void brcms_c_radio_timer(void *arg) +{ + struct brcms_c_info *wlc = (struct brcms_c_info *) arg; + + if (brcms_deviceremoved(wlc)) { + brcms_err(wlc->hw->d11core, "wl%d: %s: dead chip\n", + wlc->pub->unit, __func__); + brcms_down(wlc->wl); + return; + } + + brcms_c_radio_hwdisable_upd(wlc); +} + +/* common low-level watchdog code */ +static void brcms_b_watchdog(struct brcms_c_info *wlc) +{ + struct brcms_hardware *wlc_hw = wlc->hw; + + if (!wlc_hw->up) + return; + + /* increment second count */ + wlc_hw->now++; + + /* Check for FIFO error interrupts */ + brcms_b_fifoerrors(wlc_hw); + + /* make sure RX dma has buffers */ + dma_rxfill(wlc->hw->di[RX_FIFO]); + + wlc_phy_watchdog(wlc_hw->band->pi); +} + +/* common watchdog code */ +static void brcms_c_watchdog(struct brcms_c_info *wlc) +{ + brcms_dbg_info(wlc->hw->d11core, "wl%d\n", wlc->pub->unit); + + if (!wlc->pub->up) + return; + + if (brcms_deviceremoved(wlc)) { + brcms_err(wlc->hw->d11core, "wl%d: %s: dead chip\n", + wlc->pub->unit, __func__); + brcms_down(wlc->wl); + return; + } + + /* increment second count */ + wlc->pub->now++; + + brcms_c_radio_hwdisable_upd(wlc); + /* if radio is disable, driver may be down, quit here */ + if (wlc->pub->radio_disabled) + return; + + brcms_b_watchdog(wlc); + + /* + * occasionally sample mac stat counters to + * detect 16-bit counter wrap + */ + if ((wlc->pub->now % SW_TIMER_MAC_STAT_UPD) == 0) + brcms_c_statsupd(wlc); + + if (BRCMS_ISNPHY(wlc->band) && + ((wlc->pub->now - wlc->tempsense_lasttime) >= + BRCMS_TEMPSENSE_PERIOD)) { + wlc->tempsense_lasttime = wlc->pub->now; + brcms_c_tempsense_upd(wlc); + } +} + +static void brcms_c_watchdog_by_timer(void *arg) +{ + struct brcms_c_info *wlc = (struct brcms_c_info *) arg; + + brcms_c_watchdog(wlc); +} + +static bool brcms_c_timers_init(struct brcms_c_info *wlc, int unit) +{ + wlc->wdtimer = brcms_init_timer(wlc->wl, brcms_c_watchdog_by_timer, + wlc, "watchdog"); + if (!wlc->wdtimer) { + wiphy_err(wlc->wiphy, "wl%d: wl_init_timer for wdtimer " + "failed\n", unit); + goto fail; + } + + wlc->radio_timer = brcms_init_timer(wlc->wl, brcms_c_radio_timer, + wlc, "radio"); + if (!wlc->radio_timer) { + wiphy_err(wlc->wiphy, "wl%d: wl_init_timer for radio_timer " + "failed\n", unit); + goto fail; + } + + return true; + + fail: + return false; +} + +/* + * Initialize brcms_c_info default values ... + * may get overrides later in this function + */ +static void brcms_c_info_init(struct brcms_c_info *wlc, int unit) +{ + int i; + + /* Save our copy of the chanspec */ + wlc->chanspec = ch20mhz_chspec(1); + + /* various 802.11g modes */ + wlc->shortslot = false; + wlc->shortslot_override = BRCMS_SHORTSLOT_AUTO; + + brcms_c_protection_upd(wlc, BRCMS_PROT_G_OVR, BRCMS_PROTECTION_AUTO); + brcms_c_protection_upd(wlc, BRCMS_PROT_G_SPEC, false); + + brcms_c_protection_upd(wlc, BRCMS_PROT_N_CFG_OVR, + BRCMS_PROTECTION_AUTO); + brcms_c_protection_upd(wlc, BRCMS_PROT_N_CFG, BRCMS_N_PROTECTION_OFF); + brcms_c_protection_upd(wlc, BRCMS_PROT_N_NONGF_OVR, + BRCMS_PROTECTION_AUTO); + brcms_c_protection_upd(wlc, BRCMS_PROT_N_NONGF, false); + brcms_c_protection_upd(wlc, BRCMS_PROT_N_PAM_OVR, AUTO); + + brcms_c_protection_upd(wlc, BRCMS_PROT_OVERLAP, + BRCMS_PROTECTION_CTL_OVERLAP); + + /* 802.11g draft 4.0 NonERP elt advertisement */ + wlc->include_legacy_erp = true; + + wlc->stf->ant_rx_ovr = ANT_RX_DIV_DEF; + wlc->stf->txant = ANT_TX_DEF; + + wlc->prb_resp_timeout = BRCMS_PRB_RESP_TIMEOUT; + + wlc->usr_fragthresh = DOT11_DEFAULT_FRAG_LEN; + for (i = 0; i < NFIFO; i++) + wlc->fragthresh[i] = DOT11_DEFAULT_FRAG_LEN; + wlc->RTSThresh = DOT11_DEFAULT_RTS_LEN; + + /* default rate fallback retry limits */ + wlc->SFBL = RETRY_SHORT_FB; + wlc->LFBL = RETRY_LONG_FB; + + /* default mac retry limits */ + wlc->SRL = RETRY_SHORT_DEF; + wlc->LRL = RETRY_LONG_DEF; + + /* WME QoS mode is Auto by default */ + wlc->pub->_ampdu = AMPDU_AGG_HOST; +} + +static uint brcms_c_attach_module(struct brcms_c_info *wlc) +{ + uint err = 0; + uint unit; + unit = wlc->pub->unit; + + wlc->asi = brcms_c_antsel_attach(wlc); + if (wlc->asi == NULL) { + wiphy_err(wlc->wiphy, "wl%d: attach: antsel_attach " + "failed\n", unit); + err = 44; + goto fail; + } + + wlc->ampdu = brcms_c_ampdu_attach(wlc); + if (wlc->ampdu == NULL) { + wiphy_err(wlc->wiphy, "wl%d: attach: ampdu_attach " + "failed\n", unit); + err = 50; + goto fail; + } + + if ((brcms_c_stf_attach(wlc) != 0)) { + wiphy_err(wlc->wiphy, "wl%d: attach: stf_attach " + "failed\n", unit); + err = 68; + goto fail; + } + fail: + return err; +} + +struct brcms_pub *brcms_c_pub(struct brcms_c_info *wlc) +{ + return wlc->pub; +} + +/* low level attach + * run backplane attach, init nvram + * run phy attach + * initialize software state for each core and band + * put the whole chip in reset(driver down state), no clock + */ +static int brcms_b_attach(struct brcms_c_info *wlc, struct bcma_device *core, + uint unit, bool piomode) +{ + struct brcms_hardware *wlc_hw; + uint err = 0; + uint j; + bool wme = false; + struct shared_phy_params sha_params; + struct wiphy *wiphy = wlc->wiphy; + struct pci_dev *pcidev = core->bus->host_pci; + struct ssb_sprom *sprom = &core->bus->sprom; + + if (core->bus->hosttype == BCMA_HOSTTYPE_PCI) + brcms_dbg_info(core, "wl%d: vendor 0x%x device 0x%x\n", unit, + pcidev->vendor, + pcidev->device); + else + brcms_dbg_info(core, "wl%d: vendor 0x%x device 0x%x\n", unit, + core->bus->boardinfo.vendor, + core->bus->boardinfo.type); + + wme = true; + + wlc_hw = wlc->hw; + wlc_hw->wlc = wlc; + wlc_hw->unit = unit; + wlc_hw->band = wlc_hw->bandstate[0]; + wlc_hw->_piomode = piomode; + + /* populate struct brcms_hardware with default values */ + brcms_b_info_init(wlc_hw); + + /* + * Do the hardware portion of the attach. Also initialize software + * state that depends on the particular hardware we are running. + */ + wlc_hw->sih = ai_attach(core->bus); + if (wlc_hw->sih == NULL) { + wiphy_err(wiphy, "wl%d: brcms_b_attach: si_attach failed\n", + unit); + err = 11; + goto fail; + } + + /* verify again the device is supported */ + if (!brcms_c_chipmatch(core)) { + wiphy_err(wiphy, "wl%d: brcms_b_attach: Unsupported device\n", + unit); + err = 12; + goto fail; + } + + if (core->bus->hosttype == BCMA_HOSTTYPE_PCI) { + wlc_hw->vendorid = pcidev->vendor; + wlc_hw->deviceid = pcidev->device; + } else { + wlc_hw->vendorid = core->bus->boardinfo.vendor; + wlc_hw->deviceid = core->bus->boardinfo.type; + } + + wlc_hw->d11core = core; + wlc_hw->corerev = core->id.rev; + + /* validate chip, chiprev and corerev */ + if (!brcms_c_isgoodchip(wlc_hw)) { + err = 13; + goto fail; + } + + /* initialize power control registers */ + ai_clkctl_init(wlc_hw->sih); + + /* request fastclock and force fastclock for the rest of attach + * bring the d11 core out of reset. + * For PMU chips, the first wlc_clkctl_clk is no-op since core-clk + * is still false; But it will be called again inside wlc_corereset, + * after d11 is out of reset. + */ + brcms_b_clkctl_clk(wlc_hw, BCMA_CLKMODE_FAST); + brcms_b_corereset(wlc_hw, BRCMS_USE_COREFLAGS); + + if (!brcms_b_validate_chip_access(wlc_hw)) { + wiphy_err(wiphy, "wl%d: brcms_b_attach: validate_chip_access " + "failed\n", unit); + err = 14; + goto fail; + } + + /* get the board rev, used just below */ + j = sprom->board_rev; + /* promote srom boardrev of 0xFF to 1 */ + if (j == BOARDREV_PROMOTABLE) + j = BOARDREV_PROMOTED; + wlc_hw->boardrev = (u16) j; + if (!brcms_c_validboardtype(wlc_hw)) { + wiphy_err(wiphy, "wl%d: brcms_b_attach: Unsupported Broadcom " + "board type (0x%x)" " or revision level (0x%x)\n", + unit, ai_get_boardtype(wlc_hw->sih), + wlc_hw->boardrev); + err = 15; + goto fail; + } + wlc_hw->sromrev = sprom->revision; + wlc_hw->boardflags = sprom->boardflags_lo + (sprom->boardflags_hi << 16); + wlc_hw->boardflags2 = sprom->boardflags2_lo + (sprom->boardflags2_hi << 16); + + if (wlc_hw->boardflags & BFL_NOPLLDOWN) + brcms_b_pllreq(wlc_hw, true, BRCMS_PLLREQ_SHARED); + + /* check device id(srom, nvram etc.) to set bands */ + if (wlc_hw->deviceid == BCM43224_D11N_ID || + wlc_hw->deviceid == BCM43224_D11N_ID_VEN1 || + wlc_hw->deviceid == BCM43224_CHIP_ID) + /* Dualband boards */ + wlc_hw->_nbands = 2; + else + wlc_hw->_nbands = 1; + + if ((ai_get_chip_id(wlc_hw->sih) == BCMA_CHIP_ID_BCM43225)) + wlc_hw->_nbands = 1; + + /* BMAC_NOTE: remove init of pub values when brcms_c_attach() + * unconditionally does the init of these values + */ + wlc->vendorid = wlc_hw->vendorid; + wlc->deviceid = wlc_hw->deviceid; + wlc->pub->sih = wlc_hw->sih; + wlc->pub->corerev = wlc_hw->corerev; + wlc->pub->sromrev = wlc_hw->sromrev; + wlc->pub->boardrev = wlc_hw->boardrev; + wlc->pub->boardflags = wlc_hw->boardflags; + wlc->pub->boardflags2 = wlc_hw->boardflags2; + wlc->pub->_nbands = wlc_hw->_nbands; + + wlc_hw->physhim = wlc_phy_shim_attach(wlc_hw, wlc->wl, wlc); + + if (wlc_hw->physhim == NULL) { + wiphy_err(wiphy, "wl%d: brcms_b_attach: wlc_phy_shim_attach " + "failed\n", unit); + err = 25; + goto fail; + } + + /* pass all the parameters to wlc_phy_shared_attach in one struct */ + sha_params.sih = wlc_hw->sih; + sha_params.physhim = wlc_hw->physhim; + sha_params.unit = unit; + sha_params.corerev = wlc_hw->corerev; + sha_params.vid = wlc_hw->vendorid; + sha_params.did = wlc_hw->deviceid; + sha_params.chip = ai_get_chip_id(wlc_hw->sih); + sha_params.chiprev = ai_get_chiprev(wlc_hw->sih); + sha_params.chippkg = ai_get_chippkg(wlc_hw->sih); + sha_params.sromrev = wlc_hw->sromrev; + sha_params.boardtype = ai_get_boardtype(wlc_hw->sih); + sha_params.boardrev = wlc_hw->boardrev; + sha_params.boardflags = wlc_hw->boardflags; + sha_params.boardflags2 = wlc_hw->boardflags2; + + /* alloc and save pointer to shared phy state area */ + wlc_hw->phy_sh = wlc_phy_shared_attach(&sha_params); + if (!wlc_hw->phy_sh) { + err = 16; + goto fail; + } + + /* initialize software state for each core and band */ + for (j = 0; j < wlc_hw->_nbands; j++) { + /* + * band0 is always 2.4Ghz + * band1, if present, is 5Ghz + */ + + brcms_c_setxband(wlc_hw, j); + + wlc_hw->band->bandunit = j; + wlc_hw->band->bandtype = j ? BRCM_BAND_5G : BRCM_BAND_2G; + wlc->band->bandunit = j; + wlc->band->bandtype = j ? BRCM_BAND_5G : BRCM_BAND_2G; + wlc->core->coreidx = core->core_index; + + wlc_hw->machwcap = bcma_read32(core, D11REGOFFS(machwcap)); + wlc_hw->machwcap_backup = wlc_hw->machwcap; + + /* init tx fifo size */ + WARN_ON((wlc_hw->corerev - XMTFIFOTBL_STARTREV) < 0 || + (wlc_hw->corerev - XMTFIFOTBL_STARTREV) > + ARRAY_SIZE(xmtfifo_sz)); + wlc_hw->xmtfifo_sz = + xmtfifo_sz[(wlc_hw->corerev - XMTFIFOTBL_STARTREV)]; + WARN_ON(!wlc_hw->xmtfifo_sz[0]); + + /* Get a phy for this band */ + wlc_hw->band->pi = + wlc_phy_attach(wlc_hw->phy_sh, core, + wlc_hw->band->bandtype, + wlc->wiphy); + if (wlc_hw->band->pi == NULL) { + wiphy_err(wiphy, "wl%d: brcms_b_attach: wlc_phy_" + "attach failed\n", unit); + err = 17; + goto fail; + } + + wlc_phy_machwcap_set(wlc_hw->band->pi, wlc_hw->machwcap); + + wlc_phy_get_phyversion(wlc_hw->band->pi, &wlc_hw->band->phytype, + &wlc_hw->band->phyrev, + &wlc_hw->band->radioid, + &wlc_hw->band->radiorev); + wlc_hw->band->abgphy_encore = + wlc_phy_get_encore(wlc_hw->band->pi); + wlc->band->abgphy_encore = wlc_phy_get_encore(wlc_hw->band->pi); + wlc_hw->band->core_flags = + wlc_phy_get_coreflags(wlc_hw->band->pi); + + /* verify good phy_type & supported phy revision */ + if (BRCMS_ISNPHY(wlc_hw->band)) { + if (NCONF_HAS(wlc_hw->band->phyrev)) + goto good_phy; + else + goto bad_phy; + } else if (BRCMS_ISLCNPHY(wlc_hw->band)) { + if (LCNCONF_HAS(wlc_hw->band->phyrev)) + goto good_phy; + else + goto bad_phy; + } else { + bad_phy: + wiphy_err(wiphy, "wl%d: brcms_b_attach: unsupported " + "phy type/rev (%d/%d)\n", unit, + wlc_hw->band->phytype, wlc_hw->band->phyrev); + err = 18; + goto fail; + } + + good_phy: + /* + * BMAC_NOTE: wlc->band->pi should not be set below and should + * be done in the high level attach. However we can not make + * that change until all low level access is changed to + * wlc_hw->band->pi. Instead do the wlc->band->pi init below, + * keeping wlc_hw->band->pi as well for incremental update of + * low level fns, and cut over low only init when all fns + * updated. + */ + wlc->band->pi = wlc_hw->band->pi; + wlc->band->phytype = wlc_hw->band->phytype; + wlc->band->phyrev = wlc_hw->band->phyrev; + wlc->band->radioid = wlc_hw->band->radioid; + wlc->band->radiorev = wlc_hw->band->radiorev; + + /* default contention windows size limits */ + wlc_hw->band->CWmin = APHY_CWMIN; + wlc_hw->band->CWmax = PHY_CWMAX; + + if (!brcms_b_attach_dmapio(wlc, j, wme)) { + err = 19; + goto fail; + } + } + + /* disable core to match driver "down" state */ + brcms_c_coredisable(wlc_hw); + + /* Match driver "down" state */ + ai_pci_down(wlc_hw->sih); + + /* turn off pll and xtal to match driver "down" state */ + brcms_b_xtal(wlc_hw, OFF); + + /* ******************************************************************* + * The hardware is in the DOWN state at this point. D11 core + * or cores are in reset with clocks off, and the board PLLs + * are off if possible. + * + * Beyond this point, wlc->sbclk == false and chip registers + * should not be touched. + ********************************************************************* + */ + + /* init etheraddr state variables */ + brcms_c_get_macaddr(wlc_hw, wlc_hw->etheraddr); + + if (is_broadcast_ether_addr(wlc_hw->etheraddr) || + is_zero_ether_addr(wlc_hw->etheraddr)) { + wiphy_err(wiphy, "wl%d: brcms_b_attach: bad macaddr\n", + unit); + err = 22; + goto fail; + } + + brcms_dbg_info(wlc_hw->d11core, "deviceid 0x%x nbands %d board 0x%x\n", + wlc_hw->deviceid, wlc_hw->_nbands, + ai_get_boardtype(wlc_hw->sih)); + + return err; + + fail: + wiphy_err(wiphy, "wl%d: brcms_b_attach: failed with err %d\n", unit, + err); + return err; +} + +static void brcms_c_attach_antgain_init(struct brcms_c_info *wlc) +{ + uint unit; + unit = wlc->pub->unit; + + if ((wlc->band->antgain == -1) && (wlc->pub->sromrev == 1)) { + /* default antenna gain for srom rev 1 is 2 dBm (8 qdbm) */ + wlc->band->antgain = 8; + } else if (wlc->band->antgain == -1) { + wiphy_err(wlc->wiphy, "wl%d: %s: Invalid antennas available in" + " srom, using 2dB\n", unit, __func__); + wlc->band->antgain = 8; + } else { + s8 gain, fract; + /* Older sroms specified gain in whole dbm only. In order + * be able to specify qdbm granularity and remain backward + * compatible the whole dbms are now encoded in only + * low 6 bits and remaining qdbms are encoded in the hi 2 bits. + * 6 bit signed number ranges from -32 - 31. + * + * Examples: + * 0x1 = 1 db, + * 0xc1 = 1.75 db (1 + 3 quarters), + * 0x3f = -1 (-1 + 0 quarters), + * 0x7f = -.75 (-1 + 1 quarters) = -3 qdbm. + * 0xbf = -.50 (-1 + 2 quarters) = -2 qdbm. + */ + gain = wlc->band->antgain & 0x3f; + gain <<= 2; /* Sign extend */ + gain >>= 2; + fract = (wlc->band->antgain & 0xc0) >> 6; + wlc->band->antgain = 4 * gain + fract; + } +} + +static bool brcms_c_attach_stf_ant_init(struct brcms_c_info *wlc) +{ + int aa; + uint unit; + int bandtype; + struct ssb_sprom *sprom = &wlc->hw->d11core->bus->sprom; + + unit = wlc->pub->unit; + bandtype = wlc->band->bandtype; + + /* get antennas available */ + if (bandtype == BRCM_BAND_5G) + aa = sprom->ant_available_a; + else + aa = sprom->ant_available_bg; + + if ((aa < 1) || (aa > 15)) { + wiphy_err(wlc->wiphy, "wl%d: %s: Invalid antennas available in" + " srom (0x%x), using 3\n", unit, __func__, aa); + aa = 3; + } + + /* reset the defaults if we have a single antenna */ + if (aa == 1) { + wlc->stf->ant_rx_ovr = ANT_RX_DIV_FORCE_0; + wlc->stf->txant = ANT_TX_FORCE_0; + } else if (aa == 2) { + wlc->stf->ant_rx_ovr = ANT_RX_DIV_FORCE_1; + wlc->stf->txant = ANT_TX_FORCE_1; + } else { + } + + /* Compute Antenna Gain */ + if (bandtype == BRCM_BAND_5G) + wlc->band->antgain = sprom->antenna_gain.a1; + else + wlc->band->antgain = sprom->antenna_gain.a0; + + brcms_c_attach_antgain_init(wlc); + + return true; +} + +static void brcms_c_bss_default_init(struct brcms_c_info *wlc) +{ + u16 chanspec; + struct brcms_band *band; + struct brcms_bss_info *bi = wlc->default_bss; + + /* init default and target BSS with some sane initial values */ + memset(bi, 0, sizeof(*bi)); + bi->beacon_period = BEACON_INTERVAL_DEFAULT; + + /* fill the default channel as the first valid channel + * starting from the 2G channels + */ + chanspec = ch20mhz_chspec(1); + wlc->home_chanspec = bi->chanspec = chanspec; + + /* find the band of our default channel */ + band = wlc->band; + if (wlc->pub->_nbands > 1 && + band->bandunit != chspec_bandunit(chanspec)) + band = wlc->bandstate[OTHERBANDUNIT(wlc)]; + + /* init bss rates to the band specific default rate set */ + brcms_c_rateset_default(&bi->rateset, NULL, band->phytype, + band->bandtype, false, BRCMS_RATE_MASK_FULL, + (bool) (wlc->pub->_n_enab & SUPPORT_11N), + brcms_chspec_bw(chanspec), wlc->stf->txstreams); + + if (wlc->pub->_n_enab & SUPPORT_11N) + bi->flags |= BRCMS_BSS_HT; +} + +static void brcms_c_update_mimo_band_bwcap(struct brcms_c_info *wlc, u8 bwcap) +{ + uint i; + struct brcms_band *band; + + for (i = 0; i < wlc->pub->_nbands; i++) { + band = wlc->bandstate[i]; + if (band->bandtype == BRCM_BAND_5G) { + if ((bwcap == BRCMS_N_BW_40ALL) + || (bwcap == BRCMS_N_BW_20IN2G_40IN5G)) + band->mimo_cap_40 = true; + else + band->mimo_cap_40 = false; + } else { + if (bwcap == BRCMS_N_BW_40ALL) + band->mimo_cap_40 = true; + else + band->mimo_cap_40 = false; + } + } +} + +static void brcms_c_timers_deinit(struct brcms_c_info *wlc) +{ + /* free timer state */ + if (wlc->wdtimer) { + brcms_free_timer(wlc->wdtimer); + wlc->wdtimer = NULL; + } + if (wlc->radio_timer) { + brcms_free_timer(wlc->radio_timer); + wlc->radio_timer = NULL; + } +} + +static void brcms_c_detach_module(struct brcms_c_info *wlc) +{ + if (wlc->asi) { + brcms_c_antsel_detach(wlc->asi); + wlc->asi = NULL; + } + + if (wlc->ampdu) { + brcms_c_ampdu_detach(wlc->ampdu); + wlc->ampdu = NULL; + } + + brcms_c_stf_detach(wlc); +} + +/* + * low level detach + */ +static int brcms_b_detach(struct brcms_c_info *wlc) +{ + uint i; + struct brcms_hw_band *band; + struct brcms_hardware *wlc_hw = wlc->hw; + int callbacks; + + callbacks = 0; + + brcms_b_detach_dmapio(wlc_hw); + + band = wlc_hw->band; + for (i = 0; i < wlc_hw->_nbands; i++) { + if (band->pi) { + /* Detach this band's phy */ + wlc_phy_detach(band->pi); + band->pi = NULL; + } + band = wlc_hw->bandstate[OTHERBANDUNIT(wlc)]; + } + + /* Free shared phy state */ + kfree(wlc_hw->phy_sh); + + wlc_phy_shim_detach(wlc_hw->physhim); + + if (wlc_hw->sih) { + ai_detach(wlc_hw->sih); + wlc_hw->sih = NULL; + } + + return callbacks; + +} + +/* + * Return a count of the number of driver callbacks still pending. + * + * General policy is that brcms_c_detach can only dealloc/free software states. + * It can NOT touch hardware registers since the d11core may be in reset and + * clock may not be available. + * One exception is sb register access, which is possible if crystal is turned + * on after "down" state, driver should avoid software timer with the exception + * of radio_monitor. + */ +uint brcms_c_detach(struct brcms_c_info *wlc) +{ + uint callbacks = 0; + + if (wlc == NULL) + return 0; + + callbacks += brcms_b_detach(wlc); + + /* delete software timers */ + if (!brcms_c_radio_monitor_stop(wlc)) + callbacks++; + + brcms_c_channel_mgr_detach(wlc->cmi); + + brcms_c_timers_deinit(wlc); + + brcms_c_detach_module(wlc); + + brcms_c_detach_mfree(wlc); + return callbacks; +} + +/* update state that depends on the current value of "ap" */ +static void brcms_c_ap_upd(struct brcms_c_info *wlc) +{ + /* STA-BSS; short capable */ + wlc->PLCPHdr_override = BRCMS_PLCP_SHORT; +} + +/* Initialize just the hardware when coming out of POR or S3/S5 system states */ +static void brcms_b_hw_up(struct brcms_hardware *wlc_hw) +{ + if (wlc_hw->wlc->pub->hw_up) + return; + + brcms_dbg_info(wlc_hw->d11core, "wl%d\n", wlc_hw->unit); + + /* + * Enable pll and xtal, initialize the power control registers, + * and force fastclock for the remainder of brcms_c_up(). + */ + brcms_b_xtal(wlc_hw, ON); + ai_clkctl_init(wlc_hw->sih); + brcms_b_clkctl_clk(wlc_hw, BCMA_CLKMODE_FAST); + + /* + * TODO: test suspend/resume + * + * AI chip doesn't restore bar0win2 on + * hibernation/resume, need sw fixup + */ + + /* + * Inform phy that a POR reset has occurred so + * it does a complete phy init + */ + wlc_phy_por_inform(wlc_hw->band->pi); + + wlc_hw->ucode_loaded = false; + wlc_hw->wlc->pub->hw_up = true; + + if ((wlc_hw->boardflags & BFL_FEM) + && (ai_get_chip_id(wlc_hw->sih) == BCMA_CHIP_ID_BCM4313)) { + if (! + (wlc_hw->boardrev >= 0x1250 + && (wlc_hw->boardflags & BFL_FEM_BT))) + ai_epa_4313war(wlc_hw->sih); + } +} + +static int brcms_b_up_prep(struct brcms_hardware *wlc_hw) +{ + brcms_dbg_info(wlc_hw->d11core, "wl%d\n", wlc_hw->unit); + + /* + * Enable pll and xtal, initialize the power control registers, + * and force fastclock for the remainder of brcms_c_up(). + */ + brcms_b_xtal(wlc_hw, ON); + ai_clkctl_init(wlc_hw->sih); + brcms_b_clkctl_clk(wlc_hw, BCMA_CLKMODE_FAST); + + /* + * Configure pci/pcmcia here instead of in brcms_c_attach() + * to allow mfg hotswap: down, hotswap (chip power cycle), up. + */ + bcma_core_pci_irq_ctl(&wlc_hw->d11core->bus->drv_pci[0], wlc_hw->d11core, + true); + + /* + * Need to read the hwradio status here to cover the case where the + * system is loaded with the hw radio disabled. We do not want to + * bring the driver up in this case. + */ + if (brcms_b_radio_read_hwdisabled(wlc_hw)) { + /* put SB PCI in down state again */ + ai_pci_down(wlc_hw->sih); + brcms_b_xtal(wlc_hw, OFF); + return -ENOMEDIUM; + } + + ai_pci_up(wlc_hw->sih); + + /* reset the d11 core */ + brcms_b_corereset(wlc_hw, BRCMS_USE_COREFLAGS); + + return 0; +} + +static int brcms_b_up_finish(struct brcms_hardware *wlc_hw) +{ + wlc_hw->up = true; + wlc_phy_hw_state_upd(wlc_hw->band->pi, true); + + /* FULLY enable dynamic power control and d11 core interrupt */ + brcms_b_clkctl_clk(wlc_hw, BCMA_CLKMODE_DYNAMIC); + brcms_intrson(wlc_hw->wlc->wl); + return 0; +} + +/* + * Write WME tunable parameters for retransmit/max rate + * from wlc struct to ucode + */ +static void brcms_c_wme_retries_write(struct brcms_c_info *wlc) +{ + int ac; + + /* Need clock to do this */ + if (!wlc->clk) + return; + + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) + brcms_b_write_shm(wlc->hw, M_AC_TXLMT_ADDR(ac), + wlc->wme_retries[ac]); +} + +/* make interface operational */ +int brcms_c_up(struct brcms_c_info *wlc) +{ + struct ieee80211_channel *ch; + + brcms_dbg_info(wlc->hw->d11core, "wl%d\n", wlc->pub->unit); + + /* HW is turned off so don't try to access it */ + if (wlc->pub->hw_off || brcms_deviceremoved(wlc)) + return -ENOMEDIUM; + + if (!wlc->pub->hw_up) { + brcms_b_hw_up(wlc->hw); + wlc->pub->hw_up = true; + } + + if ((wlc->pub->boardflags & BFL_FEM) + && (ai_get_chip_id(wlc->hw->sih) == BCMA_CHIP_ID_BCM4313)) { + if (wlc->pub->boardrev >= 0x1250 + && (wlc->pub->boardflags & BFL_FEM_BT)) + brcms_b_mhf(wlc->hw, MHF5, MHF5_4313_GPIOCTRL, + MHF5_4313_GPIOCTRL, BRCM_BAND_ALL); + else + brcms_b_mhf(wlc->hw, MHF4, MHF4_EXTPA_ENABLE, + MHF4_EXTPA_ENABLE, BRCM_BAND_ALL); + } + + /* + * Need to read the hwradio status here to cover the case where the + * system is loaded with the hw radio disabled. We do not want to bring + * the driver up in this case. If radio is disabled, abort up, lower + * power, start radio timer and return 0(for NDIS) don't call + * radio_update to avoid looping brcms_c_up. + * + * brcms_b_up_prep() returns either 0 or -BCME_RADIOOFF only + */ + if (!wlc->pub->radio_disabled) { + int status = brcms_b_up_prep(wlc->hw); + if (status == -ENOMEDIUM) { + if (!mboolisset + (wlc->pub->radio_disabled, WL_RADIO_HW_DISABLE)) { + struct brcms_bss_cfg *bsscfg = wlc->bsscfg; + mboolset(wlc->pub->radio_disabled, + WL_RADIO_HW_DISABLE); + if (bsscfg->type == BRCMS_TYPE_STATION || + bsscfg->type == BRCMS_TYPE_ADHOC) + brcms_err(wlc->hw->d11core, + "wl%d: up: rfdisable -> " + "bsscfg_disable()\n", + wlc->pub->unit); + } + } + } + + if (wlc->pub->radio_disabled) { + brcms_c_radio_monitor_start(wlc); + return 0; + } + + /* brcms_b_up_prep has done brcms_c_corereset(). so clk is on, set it */ + wlc->clk = true; + + brcms_c_radio_monitor_stop(wlc); + + /* Set EDCF hostflags */ + brcms_b_mhf(wlc->hw, MHF1, MHF1_EDCF, MHF1_EDCF, BRCM_BAND_ALL); + + brcms_init(wlc->wl); + wlc->pub->up = true; + + if (wlc->bandinit_pending) { + ch = wlc->pub->ieee_hw->conf.chandef.chan; + brcms_c_suspend_mac_and_wait(wlc); + brcms_c_set_chanspec(wlc, ch20mhz_chspec(ch->hw_value)); + wlc->bandinit_pending = false; + brcms_c_enable_mac(wlc); + } + + brcms_b_up_finish(wlc->hw); + + /* Program the TX wme params with the current settings */ + brcms_c_wme_retries_write(wlc); + + /* start one second watchdog timer */ + brcms_add_timer(wlc->wdtimer, TIMER_INTERVAL_WATCHDOG, true); + wlc->WDarmed = true; + + /* ensure antenna config is up to date */ + brcms_c_stf_phy_txant_upd(wlc); + /* ensure LDPC config is in sync */ + brcms_c_ht_update_ldpc(wlc, wlc->stf->ldpc); + + return 0; +} + +static uint brcms_c_down_del_timer(struct brcms_c_info *wlc) +{ + uint callbacks = 0; + + return callbacks; +} + +static int brcms_b_bmac_down_prep(struct brcms_hardware *wlc_hw) +{ + bool dev_gone; + uint callbacks = 0; + + if (!wlc_hw->up) + return callbacks; + + dev_gone = brcms_deviceremoved(wlc_hw->wlc); + + /* disable interrupts */ + if (dev_gone) + wlc_hw->wlc->macintmask = 0; + else { + /* now disable interrupts */ + brcms_intrsoff(wlc_hw->wlc->wl); + + /* ensure we're running on the pll clock again */ + brcms_b_clkctl_clk(wlc_hw, BCMA_CLKMODE_FAST); + } + /* down phy at the last of this stage */ + callbacks += wlc_phy_down(wlc_hw->band->pi); + + return callbacks; +} + +static int brcms_b_down_finish(struct brcms_hardware *wlc_hw) +{ + uint callbacks = 0; + bool dev_gone; + + if (!wlc_hw->up) + return callbacks; + + wlc_hw->up = false; + wlc_phy_hw_state_upd(wlc_hw->band->pi, false); + + dev_gone = brcms_deviceremoved(wlc_hw->wlc); + + if (dev_gone) { + wlc_hw->sbclk = false; + wlc_hw->clk = false; + wlc_phy_hw_clk_state_upd(wlc_hw->band->pi, false); + + /* reclaim any posted packets */ + brcms_c_flushqueues(wlc_hw->wlc); + } else { + + /* Reset and disable the core */ + if (bcma_core_is_enabled(wlc_hw->d11core)) { + if (bcma_read32(wlc_hw->d11core, + D11REGOFFS(maccontrol)) & MCTL_EN_MAC) + brcms_c_suspend_mac_and_wait(wlc_hw->wlc); + callbacks += brcms_reset(wlc_hw->wlc->wl); + brcms_c_coredisable(wlc_hw); + } + + /* turn off primary xtal and pll */ + if (!wlc_hw->noreset) { + ai_pci_down(wlc_hw->sih); + brcms_b_xtal(wlc_hw, OFF); + } + } + + return callbacks; +} + +/* + * Mark the interface nonoperational, stop the software mechanisms, + * disable the hardware, free any transient buffer state. + * Return a count of the number of driver callbacks still pending. + */ +uint brcms_c_down(struct brcms_c_info *wlc) +{ + + uint callbacks = 0; + int i; + bool dev_gone = false; + + brcms_dbg_info(wlc->hw->d11core, "wl%d\n", wlc->pub->unit); + + /* check if we are already in the going down path */ + if (wlc->going_down) { + brcms_err(wlc->hw->d11core, + "wl%d: %s: Driver going down so return\n", + wlc->pub->unit, __func__); + return 0; + } + if (!wlc->pub->up) + return callbacks; + + wlc->going_down = true; + + callbacks += brcms_b_bmac_down_prep(wlc->hw); + + dev_gone = brcms_deviceremoved(wlc); + + /* Call any registered down handlers */ + for (i = 0; i < BRCMS_MAXMODULES; i++) { + if (wlc->modulecb[i].down_fn) + callbacks += + wlc->modulecb[i].down_fn(wlc->modulecb[i].hdl); + } + + /* cancel the watchdog timer */ + if (wlc->WDarmed) { + if (!brcms_del_timer(wlc->wdtimer)) + callbacks++; + wlc->WDarmed = false; + } + /* cancel all other timers */ + callbacks += brcms_c_down_del_timer(wlc); + + wlc->pub->up = false; + + wlc_phy_mute_upd(wlc->band->pi, false, PHY_MUTE_ALL); + + callbacks += brcms_b_down_finish(wlc->hw); + + /* brcms_b_down_finish has done brcms_c_coredisable(). so clk is off */ + wlc->clk = false; + + wlc->going_down = false; + return callbacks; +} + +/* Set the current gmode configuration */ +int brcms_c_set_gmode(struct brcms_c_info *wlc, u8 gmode, bool config) +{ + int ret = 0; + uint i; + struct brcms_c_rateset rs; + /* Default to 54g Auto */ + /* Advertise and use shortslot (-1/0/1 Auto/Off/On) */ + s8 shortslot = BRCMS_SHORTSLOT_AUTO; + bool shortslot_restrict = false; /* Restrict association to stations + * that support shortslot + */ + bool ofdm_basic = false; /* Make 6, 12, and 24 basic rates */ + /* Advertise and use short preambles (-1/0/1 Auto/Off/On) */ + int preamble = BRCMS_PLCP_LONG; + bool preamble_restrict = false; /* Restrict association to stations + * that support short preambles + */ + struct brcms_band *band; + + /* if N-support is enabled, allow Gmode set as long as requested + * Gmode is not GMODE_LEGACY_B + */ + if ((wlc->pub->_n_enab & SUPPORT_11N) && gmode == GMODE_LEGACY_B) + return -ENOTSUPP; + + /* verify that we are dealing with 2G band and grab the band pointer */ + if (wlc->band->bandtype == BRCM_BAND_2G) + band = wlc->band; + else if ((wlc->pub->_nbands > 1) && + (wlc->bandstate[OTHERBANDUNIT(wlc)]->bandtype == BRCM_BAND_2G)) + band = wlc->bandstate[OTHERBANDUNIT(wlc)]; + else + return -EINVAL; + + /* update configuration value */ + if (config) + brcms_c_protection_upd(wlc, BRCMS_PROT_G_USER, gmode); + + /* Clear rateset override */ + memset(&rs, 0, sizeof(rs)); + + switch (gmode) { + case GMODE_LEGACY_B: + shortslot = BRCMS_SHORTSLOT_OFF; + brcms_c_rateset_copy(&gphy_legacy_rates, &rs); + + break; + + case GMODE_LRS: + break; + + case GMODE_AUTO: + /* Accept defaults */ + break; + + case GMODE_ONLY: + ofdm_basic = true; + preamble = BRCMS_PLCP_SHORT; + preamble_restrict = true; + break; + + case GMODE_PERFORMANCE: + shortslot = BRCMS_SHORTSLOT_ON; + shortslot_restrict = true; + ofdm_basic = true; + preamble = BRCMS_PLCP_SHORT; + preamble_restrict = true; + break; + + default: + /* Error */ + brcms_err(wlc->hw->d11core, "wl%d: %s: invalid gmode %d\n", + wlc->pub->unit, __func__, gmode); + return -ENOTSUPP; + } + + band->gmode = gmode; + + wlc->shortslot_override = shortslot; + + /* Use the default 11g rateset */ + if (!rs.count) + brcms_c_rateset_copy(&cck_ofdm_rates, &rs); + + if (ofdm_basic) { + for (i = 0; i < rs.count; i++) { + if (rs.rates[i] == BRCM_RATE_6M + || rs.rates[i] == BRCM_RATE_12M + || rs.rates[i] == BRCM_RATE_24M) + rs.rates[i] |= BRCMS_RATE_FLAG; + } + } + + /* Set default bss rateset */ + wlc->default_bss->rateset.count = rs.count; + memcpy(wlc->default_bss->rateset.rates, rs.rates, + sizeof(wlc->default_bss->rateset.rates)); + + return ret; +} + +int brcms_c_set_nmode(struct brcms_c_info *wlc) +{ + uint i; + s32 nmode = AUTO; + + if (wlc->stf->txstreams == WL_11N_3x3) + nmode = WL_11N_3x3; + else + nmode = WL_11N_2x2; + + /* force GMODE_AUTO if NMODE is ON */ + brcms_c_set_gmode(wlc, GMODE_AUTO, true); + if (nmode == WL_11N_3x3) + wlc->pub->_n_enab = SUPPORT_HT; + else + wlc->pub->_n_enab = SUPPORT_11N; + wlc->default_bss->flags |= BRCMS_BSS_HT; + /* add the mcs rates to the default and hw ratesets */ + brcms_c_rateset_mcs_build(&wlc->default_bss->rateset, + wlc->stf->txstreams); + for (i = 0; i < wlc->pub->_nbands; i++) + memcpy(wlc->bandstate[i]->hw_rateset.mcs, + wlc->default_bss->rateset.mcs, MCSSET_LEN); + + return 0; +} + +static int +brcms_c_set_internal_rateset(struct brcms_c_info *wlc, + struct brcms_c_rateset *rs_arg) +{ + struct brcms_c_rateset rs, new; + uint bandunit; + + memcpy(&rs, rs_arg, sizeof(struct brcms_c_rateset)); + + /* check for bad count value */ + if ((rs.count == 0) || (rs.count > BRCMS_NUMRATES)) + return -EINVAL; + + /* try the current band */ + bandunit = wlc->band->bandunit; + memcpy(&new, &rs, sizeof(struct brcms_c_rateset)); + if (brcms_c_rate_hwrs_filter_sort_validate + (&new, &wlc->bandstate[bandunit]->hw_rateset, true, + wlc->stf->txstreams)) + goto good; + + /* try the other band */ + if (brcms_is_mband_unlocked(wlc)) { + bandunit = OTHERBANDUNIT(wlc); + memcpy(&new, &rs, sizeof(struct brcms_c_rateset)); + if (brcms_c_rate_hwrs_filter_sort_validate(&new, + &wlc-> + bandstate[bandunit]-> + hw_rateset, true, + wlc->stf->txstreams)) + goto good; + } + + return -EBADE; + + good: + /* apply new rateset */ + memcpy(&wlc->default_bss->rateset, &new, + sizeof(struct brcms_c_rateset)); + memcpy(&wlc->bandstate[bandunit]->defrateset, &new, + sizeof(struct brcms_c_rateset)); + return 0; +} + +static void brcms_c_ofdm_rateset_war(struct brcms_c_info *wlc) +{ + u8 r; + bool war = false; + + if (wlc->pub->associated) + r = wlc->bsscfg->current_bss->rateset.rates[0]; + else + r = wlc->default_bss->rateset.rates[0]; + + wlc_phy_ofdm_rateset_war(wlc->band->pi, war); +} + +int brcms_c_set_channel(struct brcms_c_info *wlc, u16 channel) +{ + u16 chspec = ch20mhz_chspec(channel); + + if (channel < 0 || channel > MAXCHANNEL) + return -EINVAL; + + if (!brcms_c_valid_chanspec_db(wlc->cmi, chspec)) + return -EINVAL; + + + if (!wlc->pub->up && brcms_is_mband_unlocked(wlc)) { + if (wlc->band->bandunit != chspec_bandunit(chspec)) + wlc->bandinit_pending = true; + else + wlc->bandinit_pending = false; + } + + wlc->default_bss->chanspec = chspec; + /* brcms_c_BSSinit() will sanitize the rateset before + * using it.. */ + if (wlc->pub->up && (wlc_phy_chanspec_get(wlc->band->pi) != chspec)) { + brcms_c_set_home_chanspec(wlc, chspec); + brcms_c_suspend_mac_and_wait(wlc); + brcms_c_set_chanspec(wlc, chspec); + brcms_c_enable_mac(wlc); + } + return 0; +} + +int brcms_c_set_rate_limit(struct brcms_c_info *wlc, u16 srl, u16 lrl) +{ + int ac; + + if (srl < 1 || srl > RETRY_SHORT_MAX || + lrl < 1 || lrl > RETRY_SHORT_MAX) + return -EINVAL; + + wlc->SRL = srl; + wlc->LRL = lrl; + + brcms_b_retrylimit_upd(wlc->hw, wlc->SRL, wlc->LRL); + + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { + wlc->wme_retries[ac] = SFIELD(wlc->wme_retries[ac], + EDCF_SHORT, wlc->SRL); + wlc->wme_retries[ac] = SFIELD(wlc->wme_retries[ac], + EDCF_LONG, wlc->LRL); + } + brcms_c_wme_retries_write(wlc); + + return 0; +} + +void brcms_c_get_current_rateset(struct brcms_c_info *wlc, + struct brcm_rateset *currs) +{ + struct brcms_c_rateset *rs; + + if (wlc->pub->associated) + rs = &wlc->bsscfg->current_bss->rateset; + else + rs = &wlc->default_bss->rateset; + + /* Copy only legacy rateset section */ + currs->count = rs->count; + memcpy(&currs->rates, &rs->rates, rs->count); +} + +int brcms_c_set_rateset(struct brcms_c_info *wlc, struct brcm_rateset *rs) +{ + struct brcms_c_rateset internal_rs; + int bcmerror; + + if (rs->count > BRCMS_NUMRATES) + return -ENOBUFS; + + memset(&internal_rs, 0, sizeof(internal_rs)); + + /* Copy only legacy rateset section */ + internal_rs.count = rs->count; + memcpy(&internal_rs.rates, &rs->rates, internal_rs.count); + + /* merge rateset coming in with the current mcsset */ + if (wlc->pub->_n_enab & SUPPORT_11N) { + struct brcms_bss_info *mcsset_bss; + if (wlc->pub->associated) + mcsset_bss = wlc->bsscfg->current_bss; + else + mcsset_bss = wlc->default_bss; + memcpy(internal_rs.mcs, &mcsset_bss->rateset.mcs[0], + MCSSET_LEN); + } + + bcmerror = brcms_c_set_internal_rateset(wlc, &internal_rs); + if (!bcmerror) + brcms_c_ofdm_rateset_war(wlc); + + return bcmerror; +} + +static void brcms_c_time_lock(struct brcms_c_info *wlc) +{ + bcma_set32(wlc->hw->d11core, D11REGOFFS(maccontrol), MCTL_TBTTHOLD); + /* Commit the write */ + bcma_read32(wlc->hw->d11core, D11REGOFFS(maccontrol)); +} + +static void brcms_c_time_unlock(struct brcms_c_info *wlc) +{ + bcma_mask32(wlc->hw->d11core, D11REGOFFS(maccontrol), ~MCTL_TBTTHOLD); + /* Commit the write */ + bcma_read32(wlc->hw->d11core, D11REGOFFS(maccontrol)); +} + +int brcms_c_set_beacon_period(struct brcms_c_info *wlc, u16 period) +{ + u32 bcnint_us; + + if (period == 0) + return -EINVAL; + + wlc->default_bss->beacon_period = period; + + bcnint_us = period << 10; + brcms_c_time_lock(wlc); + bcma_write32(wlc->hw->d11core, D11REGOFFS(tsf_cfprep), + (bcnint_us << CFPREP_CBI_SHIFT)); + bcma_write32(wlc->hw->d11core, D11REGOFFS(tsf_cfpstart), bcnint_us); + brcms_c_time_unlock(wlc); + + return 0; +} + +u16 brcms_c_get_phy_type(struct brcms_c_info *wlc, int phyidx) +{ + return wlc->band->phytype; +} + +void brcms_c_set_shortslot_override(struct brcms_c_info *wlc, s8 sslot_override) +{ + wlc->shortslot_override = sslot_override; + + /* + * shortslot is an 11g feature, so no more work if we are + * currently on the 5G band + */ + if (wlc->band->bandtype == BRCM_BAND_5G) + return; + + if (wlc->pub->up && wlc->pub->associated) { + /* let watchdog or beacon processing update shortslot */ + } else if (wlc->pub->up) { + /* unassociated shortslot is off */ + brcms_c_switch_shortslot(wlc, false); + } else { + /* driver is down, so just update the brcms_c_info + * value */ + if (wlc->shortslot_override == BRCMS_SHORTSLOT_AUTO) + wlc->shortslot = false; + else + wlc->shortslot = + (wlc->shortslot_override == + BRCMS_SHORTSLOT_ON); + } +} + +/* + * register watchdog and down handlers. + */ +int brcms_c_module_register(struct brcms_pub *pub, + const char *name, struct brcms_info *hdl, + int (*d_fn)(void *handle)) +{ + struct brcms_c_info *wlc = (struct brcms_c_info *) pub->wlc; + int i; + + /* find an empty entry and just add, no duplication check! */ + for (i = 0; i < BRCMS_MAXMODULES; i++) { + if (wlc->modulecb[i].name[0] == '\0') { + strncpy(wlc->modulecb[i].name, name, + sizeof(wlc->modulecb[i].name) - 1); + wlc->modulecb[i].hdl = hdl; + wlc->modulecb[i].down_fn = d_fn; + return 0; + } + } + + return -ENOSR; +} + +/* unregister module callbacks */ +int brcms_c_module_unregister(struct brcms_pub *pub, const char *name, + struct brcms_info *hdl) +{ + struct brcms_c_info *wlc = (struct brcms_c_info *) pub->wlc; + int i; + + if (wlc == NULL) + return -ENODATA; + + for (i = 0; i < BRCMS_MAXMODULES; i++) { + if (!strcmp(wlc->modulecb[i].name, name) && + (wlc->modulecb[i].hdl == hdl)) { + memset(&wlc->modulecb[i], 0, sizeof(wlc->modulecb[i])); + return 0; + } + } + + /* table not found! */ + return -ENODATA; +} + +static bool brcms_c_chipmatch_pci(struct bcma_device *core) +{ + struct pci_dev *pcidev = core->bus->host_pci; + u16 vendor = pcidev->vendor; + u16 device = pcidev->device; + + if (vendor != PCI_VENDOR_ID_BROADCOM) { + pr_err("unknown vendor id %04x\n", vendor); + return false; + } + + if (device == BCM43224_D11N_ID_VEN1 || device == BCM43224_CHIP_ID) + return true; + if ((device == BCM43224_D11N_ID) || (device == BCM43225_D11N2G_ID)) + return true; + if (device == BCM4313_D11N2G_ID) + return true; + if ((device == BCM43236_D11N_ID) || (device == BCM43236_D11N2G_ID)) + return true; + + pr_err("unknown device id %04x\n", device); + return false; +} + +static bool brcms_c_chipmatch_soc(struct bcma_device *core) +{ + struct bcma_chipinfo *chipinfo = &core->bus->chipinfo; + + if (chipinfo->id == BCMA_CHIP_ID_BCM4716) + return true; + + pr_err("unknown chip id %04x\n", chipinfo->id); + return false; +} + +bool brcms_c_chipmatch(struct bcma_device *core) +{ + switch (core->bus->hosttype) { + case BCMA_HOSTTYPE_PCI: + return brcms_c_chipmatch_pci(core); + case BCMA_HOSTTYPE_SOC: + return brcms_c_chipmatch_soc(core); + default: + pr_err("unknown host type: %i\n", core->bus->hosttype); + return false; + } +} + +u16 brcms_b_rate_shm_offset(struct brcms_hardware *wlc_hw, u8 rate) +{ + u16 table_ptr; + u8 phy_rate, index; + + /* get the phy specific rate encoding for the PLCP SIGNAL field */ + if (is_ofdm_rate(rate)) + table_ptr = M_RT_DIRMAP_A; + else + table_ptr = M_RT_DIRMAP_B; + + /* for a given rate, the LS-nibble of the PLCP SIGNAL field is + * the index into the rate table. + */ + phy_rate = rate_info[rate] & BRCMS_RATE_MASK; + index = phy_rate & 0xf; + + /* Find the SHM pointer to the rate table entry by looking in the + * Direct-map Table + */ + return 2 * brcms_b_read_shm(wlc_hw, table_ptr + (index * 2)); +} + +/* + * bcmc_fid_generate: + * Generate frame ID for a BCMC packet. The frag field is not used + * for MC frames so is used as part of the sequence number. + */ +static inline u16 +bcmc_fid_generate(struct brcms_c_info *wlc, struct brcms_bss_cfg *bsscfg, + struct d11txh *txh) +{ + u16 frameid; + + frameid = le16_to_cpu(txh->TxFrameID) & ~(TXFID_SEQ_MASK | + TXFID_QUEUE_MASK); + frameid |= + (((wlc-> + mc_fid_counter++) << TXFID_SEQ_SHIFT) & TXFID_SEQ_MASK) | + TX_BCMC_FIFO; + + return frameid; +} + +static uint +brcms_c_calc_ack_time(struct brcms_c_info *wlc, u32 rspec, + u8 preamble_type) +{ + uint dur = 0; + + /* + * Spec 9.6: ack rate is the highest rate in BSSBasicRateSet that + * is less than or equal to the rate of the immediately previous + * frame in the FES + */ + rspec = brcms_basic_rate(wlc, rspec); + /* ACK frame len == 14 == 2(fc) + 2(dur) + 6(ra) + 4(fcs) */ + dur = + brcms_c_calc_frame_time(wlc, rspec, preamble_type, + (DOT11_ACK_LEN + FCS_LEN)); + return dur; +} + +static uint +brcms_c_calc_cts_time(struct brcms_c_info *wlc, u32 rspec, + u8 preamble_type) +{ + return brcms_c_calc_ack_time(wlc, rspec, preamble_type); +} + +static uint +brcms_c_calc_ba_time(struct brcms_c_info *wlc, u32 rspec, + u8 preamble_type) +{ + /* + * Spec 9.6: ack rate is the highest rate in BSSBasicRateSet that + * is less than or equal to the rate of the immediately previous + * frame in the FES + */ + rspec = brcms_basic_rate(wlc, rspec); + /* BA len == 32 == 16(ctl hdr) + 4(ba len) + 8(bitmap) + 4(fcs) */ + return brcms_c_calc_frame_time(wlc, rspec, preamble_type, + (DOT11_BA_LEN + DOT11_BA_BITMAP_LEN + + FCS_LEN)); +} + +/* brcms_c_compute_frame_dur() + * + * Calculate the 802.11 MAC header DUR field for MPDU + * DUR for a single frame = 1 SIFS + 1 ACK + * DUR for a frame with following frags = 3 SIFS + 2 ACK + next frag time + * + * rate MPDU rate in unit of 500kbps + * next_frag_len next MPDU length in bytes + * preamble_type use short/GF or long/MM PLCP header + */ +static u16 +brcms_c_compute_frame_dur(struct brcms_c_info *wlc, u32 rate, + u8 preamble_type, uint next_frag_len) +{ + u16 dur, sifs; + + sifs = get_sifs(wlc->band); + + dur = sifs; + dur += (u16) brcms_c_calc_ack_time(wlc, rate, preamble_type); + + if (next_frag_len) { + /* Double the current DUR to get 2 SIFS + 2 ACKs */ + dur *= 2; + /* add another SIFS and the frag time */ + dur += sifs; + dur += + (u16) brcms_c_calc_frame_time(wlc, rate, preamble_type, + next_frag_len); + } + return dur; +} + +/* The opposite of brcms_c_calc_frame_time */ +static uint +brcms_c_calc_frame_len(struct brcms_c_info *wlc, u32 ratespec, + u8 preamble_type, uint dur) +{ + uint nsyms, mac_len, Ndps, kNdps; + uint rate = rspec2rate(ratespec); + + if (is_mcs_rate(ratespec)) { + uint mcs = ratespec & RSPEC_RATE_MASK; + int tot_streams = mcs_2_txstreams(mcs) + rspec_stc(ratespec); + dur -= PREN_PREAMBLE + (tot_streams * PREN_PREAMBLE_EXT); + /* payload calculation matches that of regular ofdm */ + if (wlc->band->bandtype == BRCM_BAND_2G) + dur -= DOT11_OFDM_SIGNAL_EXTENSION; + /* kNdbps = kbps * 4 */ + kNdps = mcs_2_rate(mcs, rspec_is40mhz(ratespec), + rspec_issgi(ratespec)) * 4; + nsyms = dur / APHY_SYMBOL_TIME; + mac_len = + ((nsyms * kNdps) - + ((APHY_SERVICE_NBITS + APHY_TAIL_NBITS) * 1000)) / 8000; + } else if (is_ofdm_rate(ratespec)) { + dur -= APHY_PREAMBLE_TIME; + dur -= APHY_SIGNAL_TIME; + /* Ndbps = Mbps * 4 = rate(500Kbps) * 2 */ + Ndps = rate * 2; + nsyms = dur / APHY_SYMBOL_TIME; + mac_len = + ((nsyms * Ndps) - + (APHY_SERVICE_NBITS + APHY_TAIL_NBITS)) / 8; + } else { + if (preamble_type & BRCMS_SHORT_PREAMBLE) + dur -= BPHY_PLCP_SHORT_TIME; + else + dur -= BPHY_PLCP_TIME; + mac_len = dur * rate; + /* divide out factor of 2 in rate (1/2 mbps) */ + mac_len = mac_len / 8 / 2; + } + return mac_len; +} + +/* + * Return true if the specified rate is supported by the specified band. + * BRCM_BAND_AUTO indicates the current band. + */ +static bool brcms_c_valid_rate(struct brcms_c_info *wlc, u32 rspec, int band, + bool verbose) +{ + struct brcms_c_rateset *hw_rateset; + uint i; + + if ((band == BRCM_BAND_AUTO) || (band == wlc->band->bandtype)) + hw_rateset = &wlc->band->hw_rateset; + else if (wlc->pub->_nbands > 1) + hw_rateset = &wlc->bandstate[OTHERBANDUNIT(wlc)]->hw_rateset; + else + /* other band specified and we are a single band device */ + return false; + + /* check if this is a mimo rate */ + if (is_mcs_rate(rspec)) { + if ((rspec & RSPEC_RATE_MASK) >= MCS_TABLE_SIZE) + goto error; + + return isset(hw_rateset->mcs, (rspec & RSPEC_RATE_MASK)); + } + + for (i = 0; i < hw_rateset->count; i++) + if (hw_rateset->rates[i] == rspec2rate(rspec)) + return true; + error: + if (verbose) + brcms_err(wlc->hw->d11core, "wl%d: valid_rate: rate spec 0x%x " + "not in hw_rateset\n", wlc->pub->unit, rspec); + + return false; +} + +static u32 +mac80211_wlc_set_nrate(struct brcms_c_info *wlc, struct brcms_band *cur_band, + u32 int_val) +{ + struct bcma_device *core = wlc->hw->d11core; + u8 stf = (int_val & NRATE_STF_MASK) >> NRATE_STF_SHIFT; + u8 rate = int_val & NRATE_RATE_MASK; + u32 rspec; + bool ismcs = ((int_val & NRATE_MCS_INUSE) == NRATE_MCS_INUSE); + bool issgi = ((int_val & NRATE_SGI_MASK) >> NRATE_SGI_SHIFT); + bool override_mcs_only = ((int_val & NRATE_OVERRIDE_MCS_ONLY) + == NRATE_OVERRIDE_MCS_ONLY); + int bcmerror = 0; + + if (!ismcs) + return (u32) rate; + + /* validate the combination of rate/mcs/stf is allowed */ + if ((wlc->pub->_n_enab & SUPPORT_11N) && ismcs) { + /* mcs only allowed when nmode */ + if (stf > PHY_TXC1_MODE_SDM) { + brcms_err(core, "wl%d: %s: Invalid stf\n", + wlc->pub->unit, __func__); + bcmerror = -EINVAL; + goto done; + } + + /* mcs 32 is a special case, DUP mode 40 only */ + if (rate == 32) { + if (!CHSPEC_IS40(wlc->home_chanspec) || + ((stf != PHY_TXC1_MODE_SISO) + && (stf != PHY_TXC1_MODE_CDD))) { + brcms_err(core, "wl%d: %s: Invalid mcs 32\n", + wlc->pub->unit, __func__); + bcmerror = -EINVAL; + goto done; + } + /* mcs > 7 must use stf SDM */ + } else if (rate > HIGHEST_SINGLE_STREAM_MCS) { + /* mcs > 7 must use stf SDM */ + if (stf != PHY_TXC1_MODE_SDM) { + brcms_dbg_mac80211(core, "wl%d: enabling " + "SDM mode for mcs %d\n", + wlc->pub->unit, rate); + stf = PHY_TXC1_MODE_SDM; + } + } else { + /* + * MCS 0-7 may use SISO, CDD, and for + * phy_rev >= 3 STBC + */ + if ((stf > PHY_TXC1_MODE_STBC) || + (!BRCMS_STBC_CAP_PHY(wlc) + && (stf == PHY_TXC1_MODE_STBC))) { + brcms_err(core, "wl%d: %s: Invalid STBC\n", + wlc->pub->unit, __func__); + bcmerror = -EINVAL; + goto done; + } + } + } else if (is_ofdm_rate(rate)) { + if ((stf != PHY_TXC1_MODE_CDD) && (stf != PHY_TXC1_MODE_SISO)) { + brcms_err(core, "wl%d: %s: Invalid OFDM\n", + wlc->pub->unit, __func__); + bcmerror = -EINVAL; + goto done; + } + } else if (is_cck_rate(rate)) { + if ((cur_band->bandtype != BRCM_BAND_2G) + || (stf != PHY_TXC1_MODE_SISO)) { + brcms_err(core, "wl%d: %s: Invalid CCK\n", + wlc->pub->unit, __func__); + bcmerror = -EINVAL; + goto done; + } + } else { + brcms_err(core, "wl%d: %s: Unknown rate type\n", + wlc->pub->unit, __func__); + bcmerror = -EINVAL; + goto done; + } + /* make sure multiple antennae are available for non-siso rates */ + if ((stf != PHY_TXC1_MODE_SISO) && (wlc->stf->txstreams == 1)) { + brcms_err(core, "wl%d: %s: SISO antenna but !SISO " + "request\n", wlc->pub->unit, __func__); + bcmerror = -EINVAL; + goto done; + } + + rspec = rate; + if (ismcs) { + rspec |= RSPEC_MIMORATE; + /* For STBC populate the STC field of the ratespec */ + if (stf == PHY_TXC1_MODE_STBC) { + u8 stc; + stc = 1; /* Nss for single stream is always 1 */ + rspec |= (stc << RSPEC_STC_SHIFT); + } + } + + rspec |= (stf << RSPEC_STF_SHIFT); + + if (override_mcs_only) + rspec |= RSPEC_OVERRIDE_MCS_ONLY; + + if (issgi) + rspec |= RSPEC_SHORT_GI; + + if ((rate != 0) + && !brcms_c_valid_rate(wlc, rspec, cur_band->bandtype, true)) + return rate; + + return rspec; +done: + return rate; +} + +/* + * Compute PLCP, but only requires actual rate and length of pkt. + * Rate is given in the driver standard multiple of 500 kbps. + * le is set for 11 Mbps rate if necessary. + * Broken out for PRQ. + */ + +static void brcms_c_cck_plcp_set(struct brcms_c_info *wlc, int rate_500, + uint length, u8 *plcp) +{ + u16 usec = 0; + u8 le = 0; + + switch (rate_500) { + case BRCM_RATE_1M: + usec = length << 3; + break; + case BRCM_RATE_2M: + usec = length << 2; + break; + case BRCM_RATE_5M5: + usec = (length << 4) / 11; + if ((length << 4) - (usec * 11) > 0) + usec++; + break; + case BRCM_RATE_11M: + usec = (length << 3) / 11; + if ((length << 3) - (usec * 11) > 0) { + usec++; + if ((usec * 11) - (length << 3) >= 8) + le = D11B_PLCP_SIGNAL_LE; + } + break; + + default: + brcms_err(wlc->hw->d11core, + "brcms_c_cck_plcp_set: unsupported rate %d\n", + rate_500); + rate_500 = BRCM_RATE_1M; + usec = length << 3; + break; + } + /* PLCP signal byte */ + plcp[0] = rate_500 * 5; /* r (500kbps) * 5 == r (100kbps) */ + /* PLCP service byte */ + plcp[1] = (u8) (le | D11B_PLCP_SIGNAL_LOCKED); + /* PLCP length u16, little endian */ + plcp[2] = usec & 0xff; + plcp[3] = (usec >> 8) & 0xff; + /* PLCP CRC16 */ + plcp[4] = 0; + plcp[5] = 0; +} + +/* Rate: 802.11 rate code, length: PSDU length in octets */ +static void brcms_c_compute_mimo_plcp(u32 rspec, uint length, u8 *plcp) +{ + u8 mcs = (u8) (rspec & RSPEC_RATE_MASK); + plcp[0] = mcs; + if (rspec_is40mhz(rspec) || (mcs == 32)) + plcp[0] |= MIMO_PLCP_40MHZ; + BRCMS_SET_MIMO_PLCP_LEN(plcp, length); + plcp[3] = rspec_mimoplcp3(rspec); /* rspec already holds this byte */ + plcp[3] |= 0x7; /* set smoothing, not sounding ppdu & reserved */ + plcp[4] = 0; /* number of extension spatial streams bit 0 & 1 */ + plcp[5] = 0; +} + +/* Rate: 802.11 rate code, length: PSDU length in octets */ +static void +brcms_c_compute_ofdm_plcp(u32 rspec, u32 length, u8 *plcp) +{ + u8 rate_signal; + u32 tmp = 0; + int rate = rspec2rate(rspec); + + /* + * encode rate per 802.11a-1999 sec 17.3.4.1, with lsb + * transmitted first + */ + rate_signal = rate_info[rate] & BRCMS_RATE_MASK; + memset(plcp, 0, D11_PHY_HDR_LEN); + D11A_PHY_HDR_SRATE((struct ofdm_phy_hdr *) plcp, rate_signal); + + tmp = (length & 0xfff) << 5; + plcp[2] |= (tmp >> 16) & 0xff; + plcp[1] |= (tmp >> 8) & 0xff; + plcp[0] |= tmp & 0xff; +} + +/* Rate: 802.11 rate code, length: PSDU length in octets */ +static void brcms_c_compute_cck_plcp(struct brcms_c_info *wlc, u32 rspec, + uint length, u8 *plcp) +{ + int rate = rspec2rate(rspec); + + brcms_c_cck_plcp_set(wlc, rate, length, plcp); +} + +static void +brcms_c_compute_plcp(struct brcms_c_info *wlc, u32 rspec, + uint length, u8 *plcp) +{ + if (is_mcs_rate(rspec)) + brcms_c_compute_mimo_plcp(rspec, length, plcp); + else if (is_ofdm_rate(rspec)) + brcms_c_compute_ofdm_plcp(rspec, length, plcp); + else + brcms_c_compute_cck_plcp(wlc, rspec, length, plcp); +} + +/* brcms_c_compute_rtscts_dur() + * + * Calculate the 802.11 MAC header DUR field for an RTS or CTS frame + * DUR for normal RTS/CTS w/ frame = 3 SIFS + 1 CTS + next frame time + 1 ACK + * DUR for CTS-TO-SELF w/ frame = 2 SIFS + next frame time + 1 ACK + * + * cts cts-to-self or rts/cts + * rts_rate rts or cts rate in unit of 500kbps + * rate next MPDU rate in unit of 500kbps + * frame_len next MPDU frame length in bytes + */ +u16 +brcms_c_compute_rtscts_dur(struct brcms_c_info *wlc, bool cts_only, + u32 rts_rate, + u32 frame_rate, u8 rts_preamble_type, + u8 frame_preamble_type, uint frame_len, bool ba) +{ + u16 dur, sifs; + + sifs = get_sifs(wlc->band); + + if (!cts_only) { + /* RTS/CTS */ + dur = 3 * sifs; + dur += + (u16) brcms_c_calc_cts_time(wlc, rts_rate, + rts_preamble_type); + } else { + /* CTS-TO-SELF */ + dur = 2 * sifs; + } + + dur += + (u16) brcms_c_calc_frame_time(wlc, frame_rate, frame_preamble_type, + frame_len); + if (ba) + dur += + (u16) brcms_c_calc_ba_time(wlc, frame_rate, + BRCMS_SHORT_PREAMBLE); + else + dur += + (u16) brcms_c_calc_ack_time(wlc, frame_rate, + frame_preamble_type); + return dur; +} + +static u16 brcms_c_phytxctl1_calc(struct brcms_c_info *wlc, u32 rspec) +{ + u16 phyctl1 = 0; + u16 bw; + + if (BRCMS_ISLCNPHY(wlc->band)) { + bw = PHY_TXC1_BW_20MHZ; + } else { + bw = rspec_get_bw(rspec); + /* 10Mhz is not supported yet */ + if (bw < PHY_TXC1_BW_20MHZ) { + brcms_err(wlc->hw->d11core, "phytxctl1_calc: bw %d is " + "not supported yet, set to 20L\n", bw); + bw = PHY_TXC1_BW_20MHZ; + } + } + + if (is_mcs_rate(rspec)) { + uint mcs = rspec & RSPEC_RATE_MASK; + + /* bw, stf, coding-type is part of rspec_phytxbyte2 returns */ + phyctl1 = rspec_phytxbyte2(rspec); + /* set the upper byte of phyctl1 */ + phyctl1 |= (mcs_table[mcs].tx_phy_ctl3 << 8); + } else if (is_cck_rate(rspec) && !BRCMS_ISLCNPHY(wlc->band) + && !BRCMS_ISSSLPNPHY(wlc->band)) { + /* + * In CCK mode LPPHY overloads OFDM Modulation bits with CCK + * Data Rate. Eventually MIMOPHY would also be converted to + * this format + */ + /* 0 = 1Mbps; 1 = 2Mbps; 2 = 5.5Mbps; 3 = 11Mbps */ + phyctl1 = (bw | (rspec_stf(rspec) << PHY_TXC1_MODE_SHIFT)); + } else { /* legacy OFDM/CCK */ + s16 phycfg; + /* get the phyctl byte from rate phycfg table */ + phycfg = brcms_c_rate_legacy_phyctl(rspec2rate(rspec)); + if (phycfg == -1) { + brcms_err(wlc->hw->d11core, "phytxctl1_calc: wrong " + "legacy OFDM/CCK rate\n"); + phycfg = 0; + } + /* set the upper byte of phyctl1 */ + phyctl1 = + (bw | (phycfg << 8) | + (rspec_stf(rspec) << PHY_TXC1_MODE_SHIFT)); + } + return phyctl1; +} + +/* + * Add struct d11txh, struct cck_phy_hdr. + * + * 'p' data must start with 802.11 MAC header + * 'p' must allow enough bytes of local headers to be "pushed" onto the packet + * + * headroom == D11_PHY_HDR_LEN + D11_TXH_LEN (D11_TXH_LEN is now 104 bytes) + * + */ +static u16 +brcms_c_d11hdrs_mac80211(struct brcms_c_info *wlc, struct ieee80211_hw *hw, + struct sk_buff *p, struct scb *scb, uint frag, + uint nfrags, uint queue, uint next_frag_len) +{ + struct ieee80211_hdr *h; + struct d11txh *txh; + u8 *plcp, plcp_fallback[D11_PHY_HDR_LEN]; + int len, phylen, rts_phylen; + u16 mch, phyctl, xfts, mainrates; + u16 seq = 0, mcl = 0, status = 0, frameid = 0; + u32 rspec[2] = { BRCM_RATE_1M, BRCM_RATE_1M }; + u32 rts_rspec[2] = { BRCM_RATE_1M, BRCM_RATE_1M }; + bool use_rts = false; + bool use_cts = false; + bool use_rifs = false; + bool short_preamble[2] = { false, false }; + u8 preamble_type[2] = { BRCMS_LONG_PREAMBLE, BRCMS_LONG_PREAMBLE }; + u8 rts_preamble_type[2] = { BRCMS_LONG_PREAMBLE, BRCMS_LONG_PREAMBLE }; + u8 *rts_plcp, rts_plcp_fallback[D11_PHY_HDR_LEN]; + struct ieee80211_rts *rts = NULL; + bool qos; + uint ac; + bool hwtkmic = false; + u16 mimo_ctlchbw = PHY_TXC1_BW_20MHZ; +#define ANTCFG_NONE 0xFF + u8 antcfg = ANTCFG_NONE; + u8 fbantcfg = ANTCFG_NONE; + uint phyctl1_stf = 0; + u16 durid = 0; + struct ieee80211_tx_rate *txrate[2]; + int k; + struct ieee80211_tx_info *tx_info; + bool is_mcs; + u16 mimo_txbw; + u8 mimo_preamble_type; + + /* locate 802.11 MAC header */ + h = (struct ieee80211_hdr *)(p->data); + qos = ieee80211_is_data_qos(h->frame_control); + + /* compute length of frame in bytes for use in PLCP computations */ + len = p->len; + phylen = len + FCS_LEN; + + /* Get tx_info */ + tx_info = IEEE80211_SKB_CB(p); + + /* add PLCP */ + plcp = skb_push(p, D11_PHY_HDR_LEN); + + /* add Broadcom tx descriptor header */ + txh = (struct d11txh *) skb_push(p, D11_TXH_LEN); + memset(txh, 0, D11_TXH_LEN); + + /* setup frameid */ + if (tx_info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) { + /* non-AP STA should never use BCMC queue */ + if (queue == TX_BCMC_FIFO) { + brcms_err(wlc->hw->d11core, + "wl%d: %s: ASSERT queue == TX_BCMC!\n", + wlc->pub->unit, __func__); + frameid = bcmc_fid_generate(wlc, NULL, txh); + } else { + /* Increment the counter for first fragment */ + if (tx_info->flags & IEEE80211_TX_CTL_FIRST_FRAGMENT) + scb->seqnum[p->priority]++; + + /* extract fragment number from frame first */ + seq = le16_to_cpu(h->seq_ctrl) & FRAGNUM_MASK; + seq |= (scb->seqnum[p->priority] << SEQNUM_SHIFT); + h->seq_ctrl = cpu_to_le16(seq); + + frameid = ((seq << TXFID_SEQ_SHIFT) & TXFID_SEQ_MASK) | + (queue & TXFID_QUEUE_MASK); + } + } + frameid |= queue & TXFID_QUEUE_MASK; + + /* set the ignpmq bit for all pkts tx'd in PS mode and for beacons */ + if (ieee80211_is_beacon(h->frame_control)) + mcl |= TXC_IGNOREPMQ; + + txrate[0] = tx_info->control.rates; + txrate[1] = txrate[0] + 1; + + /* + * if rate control algorithm didn't give us a fallback + * rate, use the primary rate + */ + if (txrate[1]->idx < 0) + txrate[1] = txrate[0]; + + for (k = 0; k < hw->max_rates; k++) { + is_mcs = txrate[k]->flags & IEEE80211_TX_RC_MCS ? true : false; + if (!is_mcs) { + if ((txrate[k]->idx >= 0) + && (txrate[k]->idx < + hw->wiphy->bands[tx_info->band]->n_bitrates)) { + rspec[k] = + hw->wiphy->bands[tx_info->band]-> + bitrates[txrate[k]->idx].hw_value; + short_preamble[k] = + txrate[k]-> + flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE ? + true : false; + } else { + rspec[k] = BRCM_RATE_1M; + } + } else { + rspec[k] = mac80211_wlc_set_nrate(wlc, wlc->band, + NRATE_MCS_INUSE | txrate[k]->idx); + } + + /* + * Currently only support same setting for primay and + * fallback rates. Unify flags for each rate into a + * single value for the frame + */ + use_rts |= + txrate[k]-> + flags & IEEE80211_TX_RC_USE_RTS_CTS ? true : false; + use_cts |= + txrate[k]-> + flags & IEEE80211_TX_RC_USE_CTS_PROTECT ? true : false; + + + /* + * (1) RATE: + * determine and validate primary rate + * and fallback rates + */ + if (!rspec_active(rspec[k])) { + rspec[k] = BRCM_RATE_1M; + } else { + if (!is_multicast_ether_addr(h->addr1)) { + /* set tx antenna config */ + brcms_c_antsel_antcfg_get(wlc->asi, false, + false, 0, 0, &antcfg, &fbantcfg); + } + } + } + + phyctl1_stf = wlc->stf->ss_opmode; + + if (wlc->pub->_n_enab & SUPPORT_11N) { + for (k = 0; k < hw->max_rates; k++) { + /* + * apply siso/cdd to single stream mcs's or ofdm + * if rspec is auto selected + */ + if (((is_mcs_rate(rspec[k]) && + is_single_stream(rspec[k] & RSPEC_RATE_MASK)) || + is_ofdm_rate(rspec[k])) + && ((rspec[k] & RSPEC_OVERRIDE_MCS_ONLY) + || !(rspec[k] & RSPEC_OVERRIDE))) { + rspec[k] &= ~(RSPEC_STF_MASK | RSPEC_STC_MASK); + + /* For SISO MCS use STBC if possible */ + if (is_mcs_rate(rspec[k]) + && BRCMS_STF_SS_STBC_TX(wlc, scb)) { + u8 stc; + + /* Nss for single stream is always 1 */ + stc = 1; + rspec[k] |= (PHY_TXC1_MODE_STBC << + RSPEC_STF_SHIFT) | + (stc << RSPEC_STC_SHIFT); + } else + rspec[k] |= + (phyctl1_stf << RSPEC_STF_SHIFT); + } + + /* + * Is the phy configured to use 40MHZ frames? If + * so then pick the desired txbw + */ + if (brcms_chspec_bw(wlc->chanspec) == BRCMS_40_MHZ) { + /* default txbw is 20in40 SB */ + mimo_ctlchbw = mimo_txbw = + CHSPEC_SB_UPPER(wlc_phy_chanspec_get( + wlc->band->pi)) + ? PHY_TXC1_BW_20MHZ_UP : PHY_TXC1_BW_20MHZ; + + if (is_mcs_rate(rspec[k])) { + /* mcs 32 must be 40b/w DUP */ + if ((rspec[k] & RSPEC_RATE_MASK) + == 32) { + mimo_txbw = + PHY_TXC1_BW_40MHZ_DUP; + /* use override */ + } else if (wlc->mimo_40txbw != AUTO) + mimo_txbw = wlc->mimo_40txbw; + /* else check if dst is using 40 Mhz */ + else if (scb->flags & SCB_IS40) + mimo_txbw = PHY_TXC1_BW_40MHZ; + } else if (is_ofdm_rate(rspec[k])) { + if (wlc->ofdm_40txbw != AUTO) + mimo_txbw = wlc->ofdm_40txbw; + } else if (wlc->cck_40txbw != AUTO) { + mimo_txbw = wlc->cck_40txbw; + } + } else { + /* + * mcs32 is 40 b/w only. + * This is possible for probe packets on + * a STA during SCAN + */ + if ((rspec[k] & RSPEC_RATE_MASK) == 32) + /* mcs 0 */ + rspec[k] = RSPEC_MIMORATE; + + mimo_txbw = PHY_TXC1_BW_20MHZ; + } + + /* Set channel width */ + rspec[k] &= ~RSPEC_BW_MASK; + if ((k == 0) || ((k > 0) && is_mcs_rate(rspec[k]))) + rspec[k] |= (mimo_txbw << RSPEC_BW_SHIFT); + else + rspec[k] |= (mimo_ctlchbw << RSPEC_BW_SHIFT); + + /* Disable short GI, not supported yet */ + rspec[k] &= ~RSPEC_SHORT_GI; + + mimo_preamble_type = BRCMS_MM_PREAMBLE; + if (txrate[k]->flags & IEEE80211_TX_RC_GREEN_FIELD) + mimo_preamble_type = BRCMS_GF_PREAMBLE; + + if ((txrate[k]->flags & IEEE80211_TX_RC_MCS) + && (!is_mcs_rate(rspec[k]))) { + brcms_warn(wlc->hw->d11core, + "wl%d: %s: IEEE80211_TX_RC_MCS != is_mcs_rate(rspec)\n", + wlc->pub->unit, __func__); + } + + if (is_mcs_rate(rspec[k])) { + preamble_type[k] = mimo_preamble_type; + + /* + * if SGI is selected, then forced mm + * for single stream + */ + if ((rspec[k] & RSPEC_SHORT_GI) + && is_single_stream(rspec[k] & + RSPEC_RATE_MASK)) + preamble_type[k] = BRCMS_MM_PREAMBLE; + } + + /* should be better conditionalized */ + if (!is_mcs_rate(rspec[0]) + && (tx_info->control.rates[0]. + flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE)) + preamble_type[k] = BRCMS_SHORT_PREAMBLE; + } + } else { + for (k = 0; k < hw->max_rates; k++) { + /* Set ctrlchbw as 20Mhz */ + rspec[k] &= ~RSPEC_BW_MASK; + rspec[k] |= (PHY_TXC1_BW_20MHZ << RSPEC_BW_SHIFT); + + /* for nphy, stf of ofdm frames must follow policies */ + if (BRCMS_ISNPHY(wlc->band) && is_ofdm_rate(rspec[k])) { + rspec[k] &= ~RSPEC_STF_MASK; + rspec[k] |= phyctl1_stf << RSPEC_STF_SHIFT; + } + } + } + + /* Reset these for use with AMPDU's */ + txrate[0]->count = 0; + txrate[1]->count = 0; + + /* (2) PROTECTION, may change rspec */ + if ((ieee80211_is_data(h->frame_control) || + ieee80211_is_mgmt(h->frame_control)) && + (phylen > wlc->RTSThresh) && !is_multicast_ether_addr(h->addr1)) + use_rts = true; + + /* (3) PLCP: determine PLCP header and MAC duration, + * fill struct d11txh */ + brcms_c_compute_plcp(wlc, rspec[0], phylen, plcp); + brcms_c_compute_plcp(wlc, rspec[1], phylen, plcp_fallback); + memcpy(&txh->FragPLCPFallback, + plcp_fallback, sizeof(txh->FragPLCPFallback)); + + /* Length field now put in CCK FBR CRC field */ + if (is_cck_rate(rspec[1])) { + txh->FragPLCPFallback[4] = phylen & 0xff; + txh->FragPLCPFallback[5] = (phylen & 0xff00) >> 8; + } + + /* MIMO-RATE: need validation ?? */ + mainrates = is_ofdm_rate(rspec[0]) ? + D11A_PHY_HDR_GRATE((struct ofdm_phy_hdr *) plcp) : + plcp[0]; + + /* DUR field for main rate */ + if (!ieee80211_is_pspoll(h->frame_control) && + !is_multicast_ether_addr(h->addr1) && !use_rifs) { + durid = + brcms_c_compute_frame_dur(wlc, rspec[0], preamble_type[0], + next_frag_len); + h->duration_id = cpu_to_le16(durid); + } else if (use_rifs) { + /* NAV protect to end of next max packet size */ + durid = + (u16) brcms_c_calc_frame_time(wlc, rspec[0], + preamble_type[0], + DOT11_MAX_FRAG_LEN); + durid += RIFS_11N_TIME; + h->duration_id = cpu_to_le16(durid); + } + + /* DUR field for fallback rate */ + if (ieee80211_is_pspoll(h->frame_control)) + txh->FragDurFallback = h->duration_id; + else if (is_multicast_ether_addr(h->addr1) || use_rifs) + txh->FragDurFallback = 0; + else { + durid = brcms_c_compute_frame_dur(wlc, rspec[1], + preamble_type[1], next_frag_len); + txh->FragDurFallback = cpu_to_le16(durid); + } + + /* (4) MAC-HDR: MacTxControlLow */ + if (frag == 0) + mcl |= TXC_STARTMSDU; + + if (!is_multicast_ether_addr(h->addr1)) + mcl |= TXC_IMMEDACK; + + if (wlc->band->bandtype == BRCM_BAND_5G) + mcl |= TXC_FREQBAND_5G; + + if (CHSPEC_IS40(wlc_phy_chanspec_get(wlc->band->pi))) + mcl |= TXC_BW_40; + + /* set AMIC bit if using hardware TKIP MIC */ + if (hwtkmic) + mcl |= TXC_AMIC; + + txh->MacTxControlLow = cpu_to_le16(mcl); + + /* MacTxControlHigh */ + mch = 0; + + /* Set fallback rate preamble type */ + if ((preamble_type[1] == BRCMS_SHORT_PREAMBLE) || + (preamble_type[1] == BRCMS_GF_PREAMBLE)) { + if (rspec2rate(rspec[1]) != BRCM_RATE_1M) + mch |= TXC_PREAMBLE_DATA_FB_SHORT; + } + + /* MacFrameControl */ + memcpy(&txh->MacFrameControl, &h->frame_control, sizeof(u16)); + txh->TxFesTimeNormal = cpu_to_le16(0); + + txh->TxFesTimeFallback = cpu_to_le16(0); + + /* TxFrameRA */ + memcpy(&txh->TxFrameRA, &h->addr1, ETH_ALEN); + + /* TxFrameID */ + txh->TxFrameID = cpu_to_le16(frameid); + + /* + * TxStatus, Note the case of recreating the first frag of a suppressed + * frame then we may need to reset the retry cnt's via the status reg + */ + txh->TxStatus = cpu_to_le16(status); + + /* + * extra fields for ucode AMPDU aggregation, the new fields are added to + * the END of previous structure so that it's compatible in driver. + */ + txh->MaxNMpdus = cpu_to_le16(0); + txh->MaxABytes_MRT = cpu_to_le16(0); + txh->MaxABytes_FBR = cpu_to_le16(0); + txh->MinMBytes = cpu_to_le16(0); + + /* (5) RTS/CTS: determine RTS/CTS PLCP header and MAC duration, + * furnish struct d11txh */ + /* RTS PLCP header and RTS frame */ + if (use_rts || use_cts) { + if (use_rts && use_cts) + use_cts = false; + + for (k = 0; k < 2; k++) { + rts_rspec[k] = brcms_c_rspec_to_rts_rspec(wlc, rspec[k], + false, + mimo_ctlchbw); + } + + if (!is_ofdm_rate(rts_rspec[0]) && + !((rspec2rate(rts_rspec[0]) == BRCM_RATE_1M) || + (wlc->PLCPHdr_override == BRCMS_PLCP_LONG))) { + rts_preamble_type[0] = BRCMS_SHORT_PREAMBLE; + mch |= TXC_PREAMBLE_RTS_MAIN_SHORT; + } + + if (!is_ofdm_rate(rts_rspec[1]) && + !((rspec2rate(rts_rspec[1]) == BRCM_RATE_1M) || + (wlc->PLCPHdr_override == BRCMS_PLCP_LONG))) { + rts_preamble_type[1] = BRCMS_SHORT_PREAMBLE; + mch |= TXC_PREAMBLE_RTS_FB_SHORT; + } + + /* RTS/CTS additions to MacTxControlLow */ + if (use_cts) { + txh->MacTxControlLow |= cpu_to_le16(TXC_SENDCTS); + } else { + txh->MacTxControlLow |= cpu_to_le16(TXC_SENDRTS); + txh->MacTxControlLow |= cpu_to_le16(TXC_LONGFRAME); + } + + /* RTS PLCP header */ + rts_plcp = txh->RTSPhyHeader; + if (use_cts) + rts_phylen = DOT11_CTS_LEN + FCS_LEN; + else + rts_phylen = DOT11_RTS_LEN + FCS_LEN; + + brcms_c_compute_plcp(wlc, rts_rspec[0], rts_phylen, rts_plcp); + + /* fallback rate version of RTS PLCP header */ + brcms_c_compute_plcp(wlc, rts_rspec[1], rts_phylen, + rts_plcp_fallback); + memcpy(&txh->RTSPLCPFallback, rts_plcp_fallback, + sizeof(txh->RTSPLCPFallback)); + + /* RTS frame fields... */ + rts = (struct ieee80211_rts *)&txh->rts_frame; + + durid = brcms_c_compute_rtscts_dur(wlc, use_cts, rts_rspec[0], + rspec[0], rts_preamble_type[0], + preamble_type[0], phylen, false); + rts->duration = cpu_to_le16(durid); + /* fallback rate version of RTS DUR field */ + durid = brcms_c_compute_rtscts_dur(wlc, use_cts, + rts_rspec[1], rspec[1], + rts_preamble_type[1], + preamble_type[1], phylen, false); + txh->RTSDurFallback = cpu_to_le16(durid); + + if (use_cts) { + rts->frame_control = cpu_to_le16(IEEE80211_FTYPE_CTL | + IEEE80211_STYPE_CTS); + + memcpy(&rts->ra, &h->addr2, ETH_ALEN); + } else { + rts->frame_control = cpu_to_le16(IEEE80211_FTYPE_CTL | + IEEE80211_STYPE_RTS); + + memcpy(&rts->ra, &h->addr1, 2 * ETH_ALEN); + } + + /* mainrate + * low 8 bits: main frag rate/mcs, + * high 8 bits: rts/cts rate/mcs + */ + mainrates |= (is_ofdm_rate(rts_rspec[0]) ? + D11A_PHY_HDR_GRATE( + (struct ofdm_phy_hdr *) rts_plcp) : + rts_plcp[0]) << 8; + } else { + memset(txh->RTSPhyHeader, 0, D11_PHY_HDR_LEN); + memset(&txh->rts_frame, 0, sizeof(struct ieee80211_rts)); + memset(txh->RTSPLCPFallback, 0, sizeof(txh->RTSPLCPFallback)); + txh->RTSDurFallback = 0; + } + +#ifdef SUPPORT_40MHZ + /* add null delimiter count */ + if ((tx_info->flags & IEEE80211_TX_CTL_AMPDU) && is_mcs_rate(rspec)) + txh->RTSPLCPFallback[AMPDU_FBR_NULL_DELIM] = + brcm_c_ampdu_null_delim_cnt(wlc->ampdu, scb, rspec, phylen); + +#endif + + /* + * Now that RTS/RTS FB preamble types are updated, write + * the final value + */ + txh->MacTxControlHigh = cpu_to_le16(mch); + + /* + * MainRates (both the rts and frag plcp rates have + * been calculated now) + */ + txh->MainRates = cpu_to_le16(mainrates); + + /* XtraFrameTypes */ + xfts = frametype(rspec[1], wlc->mimoft); + xfts |= (frametype(rts_rspec[0], wlc->mimoft) << XFTS_RTS_FT_SHIFT); + xfts |= (frametype(rts_rspec[1], wlc->mimoft) << XFTS_FBRRTS_FT_SHIFT); + xfts |= CHSPEC_CHANNEL(wlc_phy_chanspec_get(wlc->band->pi)) << + XFTS_CHANNEL_SHIFT; + txh->XtraFrameTypes = cpu_to_le16(xfts); + + /* PhyTxControlWord */ + phyctl = frametype(rspec[0], wlc->mimoft); + if ((preamble_type[0] == BRCMS_SHORT_PREAMBLE) || + (preamble_type[0] == BRCMS_GF_PREAMBLE)) { + if (rspec2rate(rspec[0]) != BRCM_RATE_1M) + phyctl |= PHY_TXC_SHORT_HDR; + } + + /* phytxant is properly bit shifted */ + phyctl |= brcms_c_stf_d11hdrs_phyctl_txant(wlc, rspec[0]); + txh->PhyTxControlWord = cpu_to_le16(phyctl); + + /* PhyTxControlWord_1 */ + if (BRCMS_PHY_11N_CAP(wlc->band)) { + u16 phyctl1 = 0; + + phyctl1 = brcms_c_phytxctl1_calc(wlc, rspec[0]); + txh->PhyTxControlWord_1 = cpu_to_le16(phyctl1); + phyctl1 = brcms_c_phytxctl1_calc(wlc, rspec[1]); + txh->PhyTxControlWord_1_Fbr = cpu_to_le16(phyctl1); + + if (use_rts || use_cts) { + phyctl1 = brcms_c_phytxctl1_calc(wlc, rts_rspec[0]); + txh->PhyTxControlWord_1_Rts = cpu_to_le16(phyctl1); + phyctl1 = brcms_c_phytxctl1_calc(wlc, rts_rspec[1]); + txh->PhyTxControlWord_1_FbrRts = cpu_to_le16(phyctl1); + } + + /* + * For mcs frames, if mixedmode(overloaded with long preamble) + * is going to be set, fill in non-zero MModeLen and/or + * MModeFbrLen it will be unnecessary if they are separated + */ + if (is_mcs_rate(rspec[0]) && + (preamble_type[0] == BRCMS_MM_PREAMBLE)) { + u16 mmodelen = + brcms_c_calc_lsig_len(wlc, rspec[0], phylen); + txh->MModeLen = cpu_to_le16(mmodelen); + } + + if (is_mcs_rate(rspec[1]) && + (preamble_type[1] == BRCMS_MM_PREAMBLE)) { + u16 mmodefbrlen = + brcms_c_calc_lsig_len(wlc, rspec[1], phylen); + txh->MModeFbrLen = cpu_to_le16(mmodefbrlen); + } + } + + ac = skb_get_queue_mapping(p); + if ((scb->flags & SCB_WMECAP) && qos && wlc->edcf_txop[ac]) { + uint frag_dur, dur, dur_fallback; + + /* WME: Update TXOP threshold */ + if (!(tx_info->flags & IEEE80211_TX_CTL_AMPDU) && frag == 0) { + frag_dur = + brcms_c_calc_frame_time(wlc, rspec[0], + preamble_type[0], phylen); + + if (rts) { + /* 1 RTS or CTS-to-self frame */ + dur = + brcms_c_calc_cts_time(wlc, rts_rspec[0], + rts_preamble_type[0]); + dur_fallback = + brcms_c_calc_cts_time(wlc, rts_rspec[1], + rts_preamble_type[1]); + /* (SIFS + CTS) + SIFS + frame + SIFS + ACK */ + dur += le16_to_cpu(rts->duration); + dur_fallback += + le16_to_cpu(txh->RTSDurFallback); + } else if (use_rifs) { + dur = frag_dur; + dur_fallback = 0; + } else { + /* frame + SIFS + ACK */ + dur = frag_dur; + dur += + brcms_c_compute_frame_dur(wlc, rspec[0], + preamble_type[0], 0); + + dur_fallback = + brcms_c_calc_frame_time(wlc, rspec[1], + preamble_type[1], + phylen); + dur_fallback += + brcms_c_compute_frame_dur(wlc, rspec[1], + preamble_type[1], 0); + } + /* NEED to set TxFesTimeNormal (hard) */ + txh->TxFesTimeNormal = cpu_to_le16((u16) dur); + /* + * NEED to set fallback rate version of + * TxFesTimeNormal (hard) + */ + txh->TxFesTimeFallback = + cpu_to_le16((u16) dur_fallback); + + /* + * update txop byte threshold (txop minus intraframe + * overhead) + */ + if (wlc->edcf_txop[ac] >= (dur - frag_dur)) { + uint newfragthresh; + + newfragthresh = + brcms_c_calc_frame_len(wlc, + rspec[0], preamble_type[0], + (wlc->edcf_txop[ac] - + (dur - frag_dur))); + /* range bound the fragthreshold */ + if (newfragthresh < DOT11_MIN_FRAG_LEN) + newfragthresh = + DOT11_MIN_FRAG_LEN; + else if (newfragthresh > + wlc->usr_fragthresh) + newfragthresh = + wlc->usr_fragthresh; + /* update the fragthresh and do txc update */ + if (wlc->fragthresh[queue] != + (u16) newfragthresh) + wlc->fragthresh[queue] = + (u16) newfragthresh; + } else { + brcms_warn(wlc->hw->d11core, + "wl%d: %s txop invalid for rate %d\n", + wlc->pub->unit, fifo_names[queue], + rspec2rate(rspec[0])); + } + + if (dur > wlc->edcf_txop[ac]) + brcms_warn(wlc->hw->d11core, + "wl%d: %s: %s txop exceeded phylen %d/%d dur %d/%d\n", + wlc->pub->unit, __func__, + fifo_names[queue], + phylen, wlc->fragthresh[queue], + dur, wlc->edcf_txop[ac]); + } + } + + return 0; +} + +static int brcms_c_tx(struct brcms_c_info *wlc, struct sk_buff *skb) +{ + struct dma_pub *dma; + int fifo, ret = -ENOSPC; + struct d11txh *txh; + u16 frameid = INVALIDFID; + + fifo = brcms_ac_to_fifo(skb_get_queue_mapping(skb)); + dma = wlc->hw->di[fifo]; + txh = (struct d11txh *)(skb->data); + + if (dma->txavail == 0) { + /* + * We sometimes get a frame from mac80211 after stopping + * the queues. This only ever seems to be a single frame + * and is seems likely to be a race. TX_HEADROOM should + * ensure that we have enough space to handle these stray + * packets, so warn if there isn't. If we're out of space + * in the tx ring and the tx queue isn't stopped then + * we've really got a bug; warn loudly if that happens. + */ + brcms_warn(wlc->hw->d11core, + "Received frame for tx with no space in DMA ring\n"); + WARN_ON(!ieee80211_queue_stopped(wlc->pub->ieee_hw, + skb_get_queue_mapping(skb))); + return -ENOSPC; + } + + /* When a BC/MC frame is being committed to the BCMC fifo + * via DMA (NOT PIO), update ucode or BSS info as appropriate. + */ + if (fifo == TX_BCMC_FIFO) + frameid = le16_to_cpu(txh->TxFrameID); + + /* Commit BCMC sequence number in the SHM frame ID location */ + if (frameid != INVALIDFID) { + /* + * To inform the ucode of the last mcast frame posted + * so that it can clear moredata bit + */ + brcms_b_write_shm(wlc->hw, M_BCMC_FID, frameid); + } + + ret = brcms_c_txfifo(wlc, fifo, skb); + /* + * The only reason for brcms_c_txfifo to fail is because + * there weren't any DMA descriptors, but we've already + * checked for that. So if it does fail yell loudly. + */ + WARN_ON_ONCE(ret); + + return ret; +} + +bool brcms_c_sendpkt_mac80211(struct brcms_c_info *wlc, struct sk_buff *sdu, + struct ieee80211_hw *hw) +{ + uint fifo; + struct scb *scb = &wlc->pri_scb; + + fifo = brcms_ac_to_fifo(skb_get_queue_mapping(sdu)); + brcms_c_d11hdrs_mac80211(wlc, hw, sdu, scb, 0, 1, fifo, 0); + if (!brcms_c_tx(wlc, sdu)) + return true; + + /* packet discarded */ + dev_kfree_skb_any(sdu); + return false; +} + +int +brcms_c_txfifo(struct brcms_c_info *wlc, uint fifo, struct sk_buff *p) +{ + struct dma_pub *dma = wlc->hw->di[fifo]; + int ret; + u16 queue; + + ret = dma_txfast(wlc, dma, p); + if (ret < 0) + wiphy_err(wlc->wiphy, "txfifo: fatal, toss frames !!!\n"); + + /* + * Stop queue if DMA ring is full. Reserve some free descriptors, + * as we sometimes receive a frame from mac80211 after the queues + * are stopped. + */ + queue = skb_get_queue_mapping(p); + if (dma->txavail <= TX_HEADROOM && fifo < TX_BCMC_FIFO && + !ieee80211_queue_stopped(wlc->pub->ieee_hw, queue)) + ieee80211_stop_queue(wlc->pub->ieee_hw, queue); + + return ret; +} + +u32 +brcms_c_rspec_to_rts_rspec(struct brcms_c_info *wlc, u32 rspec, + bool use_rspec, u16 mimo_ctlchbw) +{ + u32 rts_rspec = 0; + + if (use_rspec) + /* use frame rate as rts rate */ + rts_rspec = rspec; + else if (wlc->band->gmode && wlc->protection->_g && !is_cck_rate(rspec)) + /* Use 11Mbps as the g protection RTS target rate and fallback. + * Use the brcms_basic_rate() lookup to find the best basic rate + * under the target in case 11 Mbps is not Basic. + * 6 and 9 Mbps are not usually selected by rate selection, but + * even if the OFDM rate we are protecting is 6 or 9 Mbps, 11 + * is more robust. + */ + rts_rspec = brcms_basic_rate(wlc, BRCM_RATE_11M); + else + /* calculate RTS rate and fallback rate based on the frame rate + * RTS must be sent at a basic rate since it is a + * control frame, sec 9.6 of 802.11 spec + */ + rts_rspec = brcms_basic_rate(wlc, rspec); + + if (BRCMS_PHY_11N_CAP(wlc->band)) { + /* set rts txbw to correct side band */ + rts_rspec &= ~RSPEC_BW_MASK; + + /* + * if rspec/rspec_fallback is 40MHz, then send RTS on both + * 20MHz channel (DUP), otherwise send RTS on control channel + */ + if (rspec_is40mhz(rspec) && !is_cck_rate(rts_rspec)) + rts_rspec |= (PHY_TXC1_BW_40MHZ_DUP << RSPEC_BW_SHIFT); + else + rts_rspec |= (mimo_ctlchbw << RSPEC_BW_SHIFT); + + /* pick siso/cdd as default for ofdm */ + if (is_ofdm_rate(rts_rspec)) { + rts_rspec &= ~RSPEC_STF_MASK; + rts_rspec |= (wlc->stf->ss_opmode << RSPEC_STF_SHIFT); + } + } + return rts_rspec; +} + +/* Update beacon listen interval in shared memory */ +static void brcms_c_bcn_li_upd(struct brcms_c_info *wlc) +{ + /* wake up every DTIM is the default */ + if (wlc->bcn_li_dtim == 1) + brcms_b_write_shm(wlc->hw, M_BCN_LI, 0); + else + brcms_b_write_shm(wlc->hw, M_BCN_LI, + (wlc->bcn_li_dtim << 8) | wlc->bcn_li_bcn); +} + +static void +brcms_b_read_tsf(struct brcms_hardware *wlc_hw, u32 *tsf_l_ptr, + u32 *tsf_h_ptr) +{ + struct bcma_device *core = wlc_hw->d11core; + + /* read the tsf timer low, then high to get an atomic read */ + *tsf_l_ptr = bcma_read32(core, D11REGOFFS(tsf_timerlow)); + *tsf_h_ptr = bcma_read32(core, D11REGOFFS(tsf_timerhigh)); +} + +/* + * recover 64bit TSF value from the 16bit TSF value in the rx header + * given the assumption that the TSF passed in header is within 65ms + * of the current tsf. + * + * 6 5 4 4 3 2 1 + * 3.......6.......8.......0.......2.......4.......6.......8......0 + * |<---------- tsf_h ----------->||<--- tsf_l -->||<-RxTSFTime ->| + * + * The RxTSFTime are the lowest 16 bits and provided by the ucode. The + * tsf_l is filled in by brcms_b_recv, which is done earlier in the + * receive call sequence after rx interrupt. Only the higher 16 bits + * are used. Finally, the tsf_h is read from the tsf register. + */ +static u64 brcms_c_recover_tsf64(struct brcms_c_info *wlc, + struct d11rxhdr *rxh) +{ + u32 tsf_h, tsf_l; + u16 rx_tsf_0_15, rx_tsf_16_31; + + brcms_b_read_tsf(wlc->hw, &tsf_l, &tsf_h); + + rx_tsf_16_31 = (u16)(tsf_l >> 16); + rx_tsf_0_15 = rxh->RxTSFTime; + + /* + * a greater tsf time indicates the low 16 bits of + * tsf_l wrapped, so decrement the high 16 bits. + */ + if ((u16)tsf_l < rx_tsf_0_15) { + rx_tsf_16_31 -= 1; + if (rx_tsf_16_31 == 0xffff) + tsf_h -= 1; + } + + return ((u64)tsf_h << 32) | (((u32)rx_tsf_16_31 << 16) + rx_tsf_0_15); +} + +static void +prep_mac80211_status(struct brcms_c_info *wlc, struct d11rxhdr *rxh, + struct sk_buff *p, + struct ieee80211_rx_status *rx_status) +{ + int preamble; + int channel; + u32 rspec; + unsigned char *plcp; + + /* fill in TSF and flag its presence */ + rx_status->mactime = brcms_c_recover_tsf64(wlc, rxh); + rx_status->flag |= RX_FLAG_MACTIME_START; + + channel = BRCMS_CHAN_CHANNEL(rxh->RxChan); + + rx_status->band = + channel > 14 ? IEEE80211_BAND_5GHZ : IEEE80211_BAND_2GHZ; + rx_status->freq = + ieee80211_channel_to_frequency(channel, rx_status->band); + + rx_status->signal = wlc_phy_rssi_compute(wlc->hw->band->pi, rxh); + + /* noise */ + /* qual */ + rx_status->antenna = + (rxh->PhyRxStatus_0 & PRXS0_RXANT_UPSUBBAND) ? 1 : 0; + + plcp = p->data; + + rspec = brcms_c_compute_rspec(rxh, plcp); + if (is_mcs_rate(rspec)) { + rx_status->rate_idx = rspec & RSPEC_RATE_MASK; + rx_status->flag |= RX_FLAG_HT; + if (rspec_is40mhz(rspec)) + rx_status->flag |= RX_FLAG_40MHZ; + } else { + switch (rspec2rate(rspec)) { + case BRCM_RATE_1M: + rx_status->rate_idx = 0; + break; + case BRCM_RATE_2M: + rx_status->rate_idx = 1; + break; + case BRCM_RATE_5M5: + rx_status->rate_idx = 2; + break; + case BRCM_RATE_11M: + rx_status->rate_idx = 3; + break; + case BRCM_RATE_6M: + rx_status->rate_idx = 4; + break; + case BRCM_RATE_9M: + rx_status->rate_idx = 5; + break; + case BRCM_RATE_12M: + rx_status->rate_idx = 6; + break; + case BRCM_RATE_18M: + rx_status->rate_idx = 7; + break; + case BRCM_RATE_24M: + rx_status->rate_idx = 8; + break; + case BRCM_RATE_36M: + rx_status->rate_idx = 9; + break; + case BRCM_RATE_48M: + rx_status->rate_idx = 10; + break; + case BRCM_RATE_54M: + rx_status->rate_idx = 11; + break; + default: + brcms_err(wlc->hw->d11core, + "%s: Unknown rate\n", __func__); + } + + /* + * For 5GHz, we should decrease the index as it is + * a subset of the 2.4G rates. See bitrates field + * of brcms_band_5GHz_nphy (in mac80211_if.c). + */ + if (rx_status->band == IEEE80211_BAND_5GHZ) + rx_status->rate_idx -= BRCMS_LEGACY_5G_RATE_OFFSET; + + /* Determine short preamble and rate_idx */ + preamble = 0; + if (is_cck_rate(rspec)) { + if (rxh->PhyRxStatus_0 & PRXS0_SHORTH) + rx_status->flag |= RX_FLAG_SHORTPRE; + } else if (is_ofdm_rate(rspec)) { + rx_status->flag |= RX_FLAG_SHORTPRE; + } else { + brcms_err(wlc->hw->d11core, "%s: Unknown modulation\n", + __func__); + } + } + + if (plcp3_issgi(plcp[3])) + rx_status->flag |= RX_FLAG_SHORT_GI; + + if (rxh->RxStatus1 & RXS_DECERR) { + rx_status->flag |= RX_FLAG_FAILED_PLCP_CRC; + brcms_err(wlc->hw->d11core, "%s: RX_FLAG_FAILED_PLCP_CRC\n", + __func__); + } + if (rxh->RxStatus1 & RXS_FCSERR) { + rx_status->flag |= RX_FLAG_FAILED_FCS_CRC; + brcms_err(wlc->hw->d11core, "%s: RX_FLAG_FAILED_FCS_CRC\n", + __func__); + } +} + +static void +brcms_c_recvctl(struct brcms_c_info *wlc, struct d11rxhdr *rxh, + struct sk_buff *p) +{ + int len_mpdu; + struct ieee80211_rx_status rx_status; + struct ieee80211_hdr *hdr; + + memset(&rx_status, 0, sizeof(rx_status)); + prep_mac80211_status(wlc, rxh, p, &rx_status); + + /* mac header+body length, exclude CRC and plcp header */ + len_mpdu = p->len - D11_PHY_HDR_LEN - FCS_LEN; + skb_pull(p, D11_PHY_HDR_LEN); + __skb_trim(p, len_mpdu); + + /* unmute transmit */ + if (wlc->hw->suspended_fifos) { + hdr = (struct ieee80211_hdr *)p->data; + if (ieee80211_is_beacon(hdr->frame_control)) + brcms_b_mute(wlc->hw, false); + } + + memcpy(IEEE80211_SKB_RXCB(p), &rx_status, sizeof(rx_status)); + ieee80211_rx_irqsafe(wlc->pub->ieee_hw, p); +} + +/* calculate frame duration for Mixed-mode L-SIG spoofing, return + * number of bytes goes in the length field + * + * Formula given by HT PHY Spec v 1.13 + * len = 3(nsyms + nstream + 3) - 3 + */ +u16 +brcms_c_calc_lsig_len(struct brcms_c_info *wlc, u32 ratespec, + uint mac_len) +{ + uint nsyms, len = 0, kNdps; + + if (is_mcs_rate(ratespec)) { + uint mcs = ratespec & RSPEC_RATE_MASK; + int tot_streams = (mcs_2_txstreams(mcs) + 1) + + rspec_stc(ratespec); + + /* + * the payload duration calculation matches that + * of regular ofdm + */ + /* 1000Ndbps = kbps * 4 */ + kNdps = mcs_2_rate(mcs, rspec_is40mhz(ratespec), + rspec_issgi(ratespec)) * 4; + + if (rspec_stc(ratespec) == 0) + nsyms = + CEIL((APHY_SERVICE_NBITS + 8 * mac_len + + APHY_TAIL_NBITS) * 1000, kNdps); + else + /* STBC needs to have even number of symbols */ + nsyms = + 2 * + CEIL((APHY_SERVICE_NBITS + 8 * mac_len + + APHY_TAIL_NBITS) * 1000, 2 * kNdps); + + /* (+3) account for HT-SIG(2) and HT-STF(1) */ + nsyms += (tot_streams + 3); + /* + * 3 bytes/symbol @ legacy 6Mbps rate + * (-3) excluding service bits and tail bits + */ + len = (3 * nsyms) - 3; + } + + return (u16) len; +} + +static void +brcms_c_mod_prb_rsp_rate_table(struct brcms_c_info *wlc, uint frame_len) +{ + const struct brcms_c_rateset *rs_dflt; + struct brcms_c_rateset rs; + u8 rate; + u16 entry_ptr; + u8 plcp[D11_PHY_HDR_LEN]; + u16 dur, sifs; + uint i; + + sifs = get_sifs(wlc->band); + + rs_dflt = brcms_c_rateset_get_hwrs(wlc); + + brcms_c_rateset_copy(rs_dflt, &rs); + brcms_c_rateset_mcs_upd(&rs, wlc->stf->txstreams); + + /* + * walk the phy rate table and update MAC core SHM + * basic rate table entries + */ + for (i = 0; i < rs.count; i++) { + rate = rs.rates[i] & BRCMS_RATE_MASK; + + entry_ptr = brcms_b_rate_shm_offset(wlc->hw, rate); + + /* Calculate the Probe Response PLCP for the given rate */ + brcms_c_compute_plcp(wlc, rate, frame_len, plcp); + + /* + * Calculate the duration of the Probe Response + * frame plus SIFS for the MAC + */ + dur = (u16) brcms_c_calc_frame_time(wlc, rate, + BRCMS_LONG_PREAMBLE, frame_len); + dur += sifs; + + /* Update the SHM Rate Table entry Probe Response values */ + brcms_b_write_shm(wlc->hw, entry_ptr + M_RT_PRS_PLCP_POS, + (u16) (plcp[0] + (plcp[1] << 8))); + brcms_b_write_shm(wlc->hw, entry_ptr + M_RT_PRS_PLCP_POS + 2, + (u16) (plcp[2] + (plcp[3] << 8))); + brcms_b_write_shm(wlc->hw, entry_ptr + M_RT_PRS_DUR_POS, dur); + } +} + +int brcms_c_get_header_len(void) +{ + return TXOFF; +} + +static void brcms_c_beacon_write(struct brcms_c_info *wlc, + struct sk_buff *beacon, u16 tim_offset, + u16 dtim_period, bool bcn0, bool bcn1) +{ + size_t len; + struct ieee80211_tx_info *tx_info; + struct brcms_hardware *wlc_hw = wlc->hw; + struct ieee80211_hw *ieee_hw = brcms_c_pub(wlc)->ieee_hw; + + /* Get tx_info */ + tx_info = IEEE80211_SKB_CB(beacon); + + len = min_t(size_t, beacon->len, BCN_TMPL_LEN); + wlc->bcn_rspec = ieee80211_get_tx_rate(ieee_hw, tx_info)->hw_value; + + brcms_c_compute_plcp(wlc, wlc->bcn_rspec, + len + FCS_LEN - D11_PHY_HDR_LEN, beacon->data); + + /* "Regular" and 16 MBSS but not for 4 MBSS */ + /* Update the phytxctl for the beacon based on the rspec */ + brcms_c_beacon_phytxctl_txant_upd(wlc, wlc->bcn_rspec); + + if (bcn0) { + /* write the probe response into the template region */ + brcms_b_write_template_ram(wlc_hw, T_BCN0_TPL_BASE, + (len + 3) & ~3, beacon->data); + + /* write beacon length to SCR */ + brcms_b_write_shm(wlc_hw, M_BCN0_FRM_BYTESZ, (u16) len); + } + if (bcn1) { + /* write the probe response into the template region */ + brcms_b_write_template_ram(wlc_hw, T_BCN1_TPL_BASE, + (len + 3) & ~3, beacon->data); + + /* write beacon length to SCR */ + brcms_b_write_shm(wlc_hw, M_BCN1_FRM_BYTESZ, (u16) len); + } + + if (tim_offset != 0) { + brcms_b_write_shm(wlc_hw, M_TIMBPOS_INBEACON, + tim_offset + D11B_PHY_HDR_LEN); + brcms_b_write_shm(wlc_hw, M_DOT11_DTIMPERIOD, dtim_period); + } else { + brcms_b_write_shm(wlc_hw, M_TIMBPOS_INBEACON, + len + D11B_PHY_HDR_LEN); + brcms_b_write_shm(wlc_hw, M_DOT11_DTIMPERIOD, 0); + } +} + +static void brcms_c_update_beacon_hw(struct brcms_c_info *wlc, + struct sk_buff *beacon, u16 tim_offset, + u16 dtim_period) +{ + struct brcms_hardware *wlc_hw = wlc->hw; + struct bcma_device *core = wlc_hw->d11core; + + /* Hardware beaconing for this config */ + u32 both_valid = MCMD_BCN0VLD | MCMD_BCN1VLD; + + /* Check if both templates are in use, if so sched. an interrupt + * that will call back into this routine + */ + if ((bcma_read32(core, D11REGOFFS(maccommand)) & both_valid) == both_valid) + /* clear any previous status */ + bcma_write32(core, D11REGOFFS(macintstatus), MI_BCNTPL); + + if (wlc->beacon_template_virgin) { + wlc->beacon_template_virgin = false; + brcms_c_beacon_write(wlc, beacon, tim_offset, dtim_period, true, + true); + /* mark beacon0 valid */ + bcma_set32(core, D11REGOFFS(maccommand), MCMD_BCN0VLD); + return; + } + + /* Check that after scheduling the interrupt both of the + * templates are still busy. if not clear the int. & remask + */ + if ((bcma_read32(core, D11REGOFFS(maccommand)) & both_valid) == both_valid) { + wlc->defmacintmask |= MI_BCNTPL; + return; + } + + if (!(bcma_read32(core, D11REGOFFS(maccommand)) & MCMD_BCN0VLD)) { + brcms_c_beacon_write(wlc, beacon, tim_offset, dtim_period, true, + false); + /* mark beacon0 valid */ + bcma_set32(core, D11REGOFFS(maccommand), MCMD_BCN0VLD); + return; + } + if (!(bcma_read32(core, D11REGOFFS(maccommand)) & MCMD_BCN1VLD)) { + brcms_c_beacon_write(wlc, beacon, tim_offset, dtim_period, + false, true); + /* mark beacon0 valid */ + bcma_set32(core, D11REGOFFS(maccommand), MCMD_BCN1VLD); + return; + } + return; +} + +/* + * Update all beacons for the system. + */ +void brcms_c_update_beacon(struct brcms_c_info *wlc) +{ + struct brcms_bss_cfg *bsscfg = wlc->bsscfg; + + if (wlc->pub->up && (bsscfg->type == BRCMS_TYPE_AP || + bsscfg->type == BRCMS_TYPE_ADHOC)) { + /* Clear the soft intmask */ + wlc->defmacintmask &= ~MI_BCNTPL; + if (!wlc->beacon) + return; + brcms_c_update_beacon_hw(wlc, wlc->beacon, + wlc->beacon_tim_offset, + wlc->beacon_dtim_period); + } +} + +void brcms_c_set_new_beacon(struct brcms_c_info *wlc, struct sk_buff *beacon, + u16 tim_offset, u16 dtim_period) +{ + if (!beacon) + return; + if (wlc->beacon) + dev_kfree_skb_any(wlc->beacon); + wlc->beacon = beacon; + + /* add PLCP */ + skb_push(wlc->beacon, D11_PHY_HDR_LEN); + wlc->beacon_tim_offset = tim_offset; + wlc->beacon_dtim_period = dtim_period; + brcms_c_update_beacon(wlc); +} + +void brcms_c_set_new_probe_resp(struct brcms_c_info *wlc, + struct sk_buff *probe_resp) +{ + if (!probe_resp) + return; + if (wlc->probe_resp) + dev_kfree_skb_any(wlc->probe_resp); + wlc->probe_resp = probe_resp; + + /* add PLCP */ + skb_push(wlc->probe_resp, D11_PHY_HDR_LEN); + brcms_c_update_probe_resp(wlc, false); +} + +void brcms_c_enable_probe_resp(struct brcms_c_info *wlc, bool enable) +{ + /* + * prevent ucode from sending probe responses by setting the timeout + * to 1, it can not send it in that time frame. + */ + wlc->prb_resp_timeout = enable ? BRCMS_PRB_RESP_TIMEOUT : 1; + brcms_b_write_shm(wlc->hw, M_PRS_MAXTIME, wlc->prb_resp_timeout); + /* TODO: if (enable) => also deactivate receiving of probe request */ +} + +/* Write ssid into shared memory */ +static void +brcms_c_shm_ssid_upd(struct brcms_c_info *wlc, struct brcms_bss_cfg *cfg) +{ + u8 *ssidptr = cfg->SSID; + u16 base = M_SSID; + u8 ssidbuf[IEEE80211_MAX_SSID_LEN]; + + /* padding the ssid with zero and copy it into shm */ + memset(ssidbuf, 0, IEEE80211_MAX_SSID_LEN); + memcpy(ssidbuf, ssidptr, cfg->SSID_len); + + brcms_c_copyto_shm(wlc, base, ssidbuf, IEEE80211_MAX_SSID_LEN); + brcms_b_write_shm(wlc->hw, M_SSIDLEN, (u16) cfg->SSID_len); +} + +static void +brcms_c_bss_update_probe_resp(struct brcms_c_info *wlc, + struct brcms_bss_cfg *cfg, + struct sk_buff *probe_resp, + bool suspend) +{ + int len; + + len = min_t(size_t, probe_resp->len, BCN_TMPL_LEN); + + if (suspend) + brcms_c_suspend_mac_and_wait(wlc); + + /* write the probe response into the template region */ + brcms_b_write_template_ram(wlc->hw, T_PRS_TPL_BASE, + (len + 3) & ~3, probe_resp->data); + + /* write the length of the probe response frame (+PLCP/-FCS) */ + brcms_b_write_shm(wlc->hw, M_PRB_RESP_FRM_LEN, (u16) len); + + /* write the SSID and SSID length */ + brcms_c_shm_ssid_upd(wlc, cfg); + + /* + * Write PLCP headers and durations for probe response frames + * at all rates. Use the actual frame length covered by the + * PLCP header for the call to brcms_c_mod_prb_rsp_rate_table() + * by subtracting the PLCP len and adding the FCS. + */ + brcms_c_mod_prb_rsp_rate_table(wlc, + (u16)len + FCS_LEN - D11_PHY_HDR_LEN); + + if (suspend) + brcms_c_enable_mac(wlc); +} + +void brcms_c_update_probe_resp(struct brcms_c_info *wlc, bool suspend) +{ + struct brcms_bss_cfg *bsscfg = wlc->bsscfg; + + /* update AP or IBSS probe responses */ + if (wlc->pub->up && (bsscfg->type == BRCMS_TYPE_AP || + bsscfg->type == BRCMS_TYPE_ADHOC)) { + if (!wlc->probe_resp) + return; + brcms_c_bss_update_probe_resp(wlc, bsscfg, wlc->probe_resp, + suspend); + } +} + +int brcms_b_xmtfifo_sz_get(struct brcms_hardware *wlc_hw, uint fifo, + uint *blocks) +{ + if (fifo >= NFIFO) + return -EINVAL; + + *blocks = wlc_hw->xmtfifo_sz[fifo]; + + return 0; +} + +void +brcms_c_set_addrmatch(struct brcms_c_info *wlc, int match_reg_offset, + const u8 *addr) +{ + brcms_b_set_addrmatch(wlc->hw, match_reg_offset, addr); + if (match_reg_offset == RCM_BSSID_OFFSET) + memcpy(wlc->bsscfg->BSSID, addr, ETH_ALEN); +} + +/* + * Flag 'scan in progress' to withhold dynamic phy calibration + */ +void brcms_c_scan_start(struct brcms_c_info *wlc) +{ + wlc_phy_hold_upd(wlc->band->pi, PHY_HOLD_FOR_SCAN, true); +} + +void brcms_c_scan_stop(struct brcms_c_info *wlc) +{ + wlc_phy_hold_upd(wlc->band->pi, PHY_HOLD_FOR_SCAN, false); +} + +void brcms_c_associate_upd(struct brcms_c_info *wlc, bool state) +{ + wlc->pub->associated = state; +} + +/* + * When a remote STA/AP is removed by Mac80211, or when it can no longer accept + * AMPDU traffic, packets pending in hardware have to be invalidated so that + * when later on hardware releases them, they can be handled appropriately. + */ +void brcms_c_inval_dma_pkts(struct brcms_hardware *hw, + struct ieee80211_sta *sta, + void (*dma_callback_fn)) +{ + struct dma_pub *dmah; + int i; + for (i = 0; i < NFIFO; i++) { + dmah = hw->di[i]; + if (dmah != NULL) + dma_walk_packets(dmah, dma_callback_fn, sta); + } +} + +int brcms_c_get_curband(struct brcms_c_info *wlc) +{ + return wlc->band->bandunit; +} + +bool brcms_c_tx_flush_completed(struct brcms_c_info *wlc) +{ + int i; + + /* Kick DMA to send any pending AMPDU */ + for (i = 0; i < ARRAY_SIZE(wlc->hw->di); i++) + if (wlc->hw->di[i]) + dma_kick_tx(wlc->hw->di[i]); + + return !brcms_txpktpendtot(wlc); +} + +void brcms_c_set_beacon_listen_interval(struct brcms_c_info *wlc, u8 interval) +{ + wlc->bcn_li_bcn = interval; + if (wlc->pub->up) + brcms_c_bcn_li_upd(wlc); +} + +u64 brcms_c_tsf_get(struct brcms_c_info *wlc) +{ + u32 tsf_h, tsf_l; + u64 tsf; + + brcms_b_read_tsf(wlc->hw, &tsf_l, &tsf_h); + + tsf = tsf_h; + tsf <<= 32; + tsf |= tsf_l; + + return tsf; +} + +void brcms_c_tsf_set(struct brcms_c_info *wlc, u64 tsf) +{ + u32 tsf_h, tsf_l; + + brcms_c_time_lock(wlc); + + tsf_l = tsf; + tsf_h = (tsf >> 32); + + /* read the tsf timer low, then high to get an atomic read */ + bcma_write32(wlc->hw->d11core, D11REGOFFS(tsf_timerlow), tsf_l); + bcma_write32(wlc->hw->d11core, D11REGOFFS(tsf_timerhigh), tsf_h); + + brcms_c_time_unlock(wlc); +} + +int brcms_c_set_tx_power(struct brcms_c_info *wlc, int txpwr) +{ + uint qdbm; + + /* Remove override bit and clip to max qdbm value */ + qdbm = min_t(uint, txpwr * BRCMS_TXPWR_DB_FACTOR, 0xff); + return wlc_phy_txpower_set(wlc->band->pi, qdbm, false); +} + +int brcms_c_get_tx_power(struct brcms_c_info *wlc) +{ + uint qdbm; + bool override; + + wlc_phy_txpower_get(wlc->band->pi, &qdbm, &override); + + /* Return qdbm units */ + return (int)(qdbm / BRCMS_TXPWR_DB_FACTOR); +} + +/* Process received frames */ +/* + * Return true if more frames need to be processed. false otherwise. + * Param 'bound' indicates max. # frames to process before break out. + */ +static void brcms_c_recv(struct brcms_c_info *wlc, struct sk_buff *p) +{ + struct d11rxhdr *rxh; + struct ieee80211_hdr *h; + uint len; + bool is_amsdu; + + /* frame starts with rxhdr */ + rxh = (struct d11rxhdr *) (p->data); + + /* strip off rxhdr */ + skb_pull(p, BRCMS_HWRXOFF); + + /* MAC inserts 2 pad bytes for a4 headers or QoS or A-MSDU subframes */ + if (rxh->RxStatus1 & RXS_PBPRES) { + if (p->len < 2) { + brcms_err(wlc->hw->d11core, + "wl%d: recv: rcvd runt of len %d\n", + wlc->pub->unit, p->len); + goto toss; + } + skb_pull(p, 2); + } + + h = (struct ieee80211_hdr *)(p->data + D11_PHY_HDR_LEN); + len = p->len; + + if (rxh->RxStatus1 & RXS_FCSERR) { + if (!(wlc->filter_flags & FIF_FCSFAIL)) + goto toss; + } + + /* check received pkt has at least frame control field */ + if (len < D11_PHY_HDR_LEN + sizeof(h->frame_control)) + goto toss; + + /* not supporting A-MSDU */ + is_amsdu = rxh->RxStatus2 & RXS_AMSDU_MASK; + if (is_amsdu) + goto toss; + + brcms_c_recvctl(wlc, rxh, p); + return; + + toss: + brcmu_pkt_buf_free_skb(p); +} + +/* Process received frames */ +/* + * Return true if more frames need to be processed. false otherwise. + * Param 'bound' indicates max. # frames to process before break out. + */ +static bool +brcms_b_recv(struct brcms_hardware *wlc_hw, uint fifo, bool bound) +{ + struct sk_buff *p; + struct sk_buff *next = NULL; + struct sk_buff_head recv_frames; + + uint n = 0; + uint bound_limit = bound ? RXBND : -1; + bool morepending = false; + + skb_queue_head_init(&recv_frames); + + /* gather received frames */ + do { + /* !give others some time to run! */ + if (n >= bound_limit) + break; + + morepending = dma_rx(wlc_hw->di[fifo], &recv_frames); + n++; + } while (morepending); + + /* post more rbufs */ + dma_rxfill(wlc_hw->di[fifo]); + + /* process each frame */ + skb_queue_walk_safe(&recv_frames, p, next) { + struct d11rxhdr_le *rxh_le; + struct d11rxhdr *rxh; + + skb_unlink(p, &recv_frames); + rxh_le = (struct d11rxhdr_le *)p->data; + rxh = (struct d11rxhdr *)p->data; + + /* fixup rx header endianness */ + rxh->RxFrameSize = le16_to_cpu(rxh_le->RxFrameSize); + rxh->PhyRxStatus_0 = le16_to_cpu(rxh_le->PhyRxStatus_0); + rxh->PhyRxStatus_1 = le16_to_cpu(rxh_le->PhyRxStatus_1); + rxh->PhyRxStatus_2 = le16_to_cpu(rxh_le->PhyRxStatus_2); + rxh->PhyRxStatus_3 = le16_to_cpu(rxh_le->PhyRxStatus_3); + rxh->PhyRxStatus_4 = le16_to_cpu(rxh_le->PhyRxStatus_4); + rxh->PhyRxStatus_5 = le16_to_cpu(rxh_le->PhyRxStatus_5); + rxh->RxStatus1 = le16_to_cpu(rxh_le->RxStatus1); + rxh->RxStatus2 = le16_to_cpu(rxh_le->RxStatus2); + rxh->RxTSFTime = le16_to_cpu(rxh_le->RxTSFTime); + rxh->RxChan = le16_to_cpu(rxh_le->RxChan); + + brcms_c_recv(wlc_hw->wlc, p); + } + + return morepending; +} + +/* second-level interrupt processing + * Return true if another dpc needs to be re-scheduled. false otherwise. + * Param 'bounded' indicates if applicable loops should be bounded. + */ +bool brcms_c_dpc(struct brcms_c_info *wlc, bool bounded) +{ + u32 macintstatus; + struct brcms_hardware *wlc_hw = wlc->hw; + struct bcma_device *core = wlc_hw->d11core; + + if (brcms_deviceremoved(wlc)) { + brcms_err(core, "wl%d: %s: dead chip\n", wlc_hw->unit, + __func__); + brcms_down(wlc->wl); + return false; + } + + /* grab and clear the saved software intstatus bits */ + macintstatus = wlc->macintstatus; + wlc->macintstatus = 0; + + brcms_dbg_int(core, "wl%d: macintstatus 0x%x\n", + wlc_hw->unit, macintstatus); + + WARN_ON(macintstatus & MI_PRQ); /* PRQ Interrupt in non-MBSS */ + + /* tx status */ + if (macintstatus & MI_TFS) { + bool fatal; + if (brcms_b_txstatus(wlc->hw, bounded, &fatal)) + wlc->macintstatus |= MI_TFS; + if (fatal) { + brcms_err(core, "MI_TFS: fatal\n"); + goto fatal; + } + } + + if (macintstatus & (MI_TBTT | MI_DTIM_TBTT)) + brcms_c_tbtt(wlc); + + /* ATIM window end */ + if (macintstatus & MI_ATIMWINEND) { + brcms_dbg_info(core, "end of ATIM window\n"); + bcma_set32(core, D11REGOFFS(maccommand), wlc->qvalid); + wlc->qvalid = 0; + } + + /* + * received data or control frame, MI_DMAINT is + * indication of RX_FIFO interrupt + */ + if (macintstatus & MI_DMAINT) + if (brcms_b_recv(wlc_hw, RX_FIFO, bounded)) + wlc->macintstatus |= MI_DMAINT; + + /* noise sample collected */ + if (macintstatus & MI_BG_NOISE) + wlc_phy_noise_sample_intr(wlc_hw->band->pi); + + if (macintstatus & MI_GP0) { + brcms_err(core, "wl%d: PSM microcode watchdog fired at %d " + "(seconds). Resetting.\n", wlc_hw->unit, wlc_hw->now); + + printk_once("%s : PSM Watchdog, chipid 0x%x, chiprev 0x%x\n", + __func__, ai_get_chip_id(wlc_hw->sih), + ai_get_chiprev(wlc_hw->sih)); + brcms_fatal_error(wlc_hw->wlc->wl); + } + + /* gptimer timeout */ + if (macintstatus & MI_TO) + bcma_write32(core, D11REGOFFS(gptimer), 0); + + if (macintstatus & MI_RFDISABLE) { + brcms_dbg_info(core, "wl%d: BMAC Detected a change on the" + " RF Disable Input\n", wlc_hw->unit); + brcms_rfkill_set_hw_state(wlc->wl); + } + + /* BCN template is available */ + if (macintstatus & MI_BCNTPL) + brcms_c_update_beacon(wlc); + + /* it isn't done and needs to be resched if macintstatus is non-zero */ + return wlc->macintstatus != 0; + + fatal: + brcms_fatal_error(wlc_hw->wlc->wl); + return wlc->macintstatus != 0; +} + +void brcms_c_init(struct brcms_c_info *wlc, bool mute_tx) +{ + struct bcma_device *core = wlc->hw->d11core; + struct ieee80211_channel *ch = wlc->pub->ieee_hw->conf.chandef.chan; + u16 chanspec; + + brcms_dbg_info(core, "wl%d\n", wlc->pub->unit); + + chanspec = ch20mhz_chspec(ch->hw_value); + + brcms_b_init(wlc->hw, chanspec); + + /* update beacon listen interval */ + brcms_c_bcn_li_upd(wlc); + + /* write ethernet address to core */ + brcms_c_set_mac(wlc->bsscfg); + brcms_c_set_bssid(wlc->bsscfg); + + /* Update tsf_cfprep if associated and up */ + if (wlc->pub->associated && wlc->pub->up) { + u32 bi; + + /* get beacon period and convert to uS */ + bi = wlc->bsscfg->current_bss->beacon_period << 10; + /* + * update since init path would reset + * to default value + */ + bcma_write32(core, D11REGOFFS(tsf_cfprep), + bi << CFPREP_CBI_SHIFT); + + /* Update maccontrol PM related bits */ + brcms_c_set_ps_ctrl(wlc); + } + + brcms_c_bandinit_ordered(wlc, chanspec); + + /* init probe response timeout */ + brcms_b_write_shm(wlc->hw, M_PRS_MAXTIME, wlc->prb_resp_timeout); + + /* init max burst txop (framebursting) */ + brcms_b_write_shm(wlc->hw, M_MBURST_TXOP, + (wlc-> + _rifs ? (EDCF_AC_VO_TXOP_AP << 5) : MAXFRAMEBURST_TXOP)); + + /* initialize maximum allowed duty cycle */ + brcms_c_duty_cycle_set(wlc, wlc->tx_duty_cycle_ofdm, true, true); + brcms_c_duty_cycle_set(wlc, wlc->tx_duty_cycle_cck, false, true); + + /* + * Update some shared memory locations related to + * max AMPDU size allowed to received + */ + brcms_c_ampdu_shm_upd(wlc->ampdu); + + /* band-specific inits */ + brcms_c_bsinit(wlc); + + /* Enable EDCF mode (while the MAC is suspended) */ + bcma_set16(core, D11REGOFFS(ifs_ctl), IFS_USEEDCF); + brcms_c_edcf_setparams(wlc, false); + + /* read the ucode version if we have not yet done so */ + if (wlc->ucode_rev == 0) { + u16 rev; + u16 patch; + + rev = brcms_b_read_shm(wlc->hw, M_BOM_REV_MAJOR); + patch = brcms_b_read_shm(wlc->hw, M_BOM_REV_MINOR); + wlc->ucode_rev = (rev << NBITS(u16)) | patch; + snprintf(wlc->wiphy->fw_version, + sizeof(wlc->wiphy->fw_version), "%u.%u", rev, patch); + } + + /* ..now really unleash hell (allow the MAC out of suspend) */ + brcms_c_enable_mac(wlc); + + /* suspend the tx fifos and mute the phy for preism cac time */ + if (mute_tx) + brcms_b_mute(wlc->hw, true); + + /* enable the RF Disable Delay timer */ + bcma_write32(core, D11REGOFFS(rfdisabledly), RFDISABLE_DEFAULT); + + /* + * Initialize WME parameters; if they haven't been set by some other + * mechanism (IOVar, etc) then read them from the hardware. + */ + if (GFIELD(wlc->wme_retries[0], EDCF_SHORT) == 0) { + /* Uninitialized; read from HW */ + int ac; + + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) + wlc->wme_retries[ac] = + brcms_b_read_shm(wlc->hw, M_AC_TXLMT_ADDR(ac)); + } +} + +/* + * The common driver entry routine. Error codes should be unique + */ +struct brcms_c_info * +brcms_c_attach(struct brcms_info *wl, struct bcma_device *core, uint unit, + bool piomode, uint *perr) +{ + struct brcms_c_info *wlc; + uint err = 0; + uint i, j; + struct brcms_pub *pub; + + /* allocate struct brcms_c_info state and its substructures */ + wlc = brcms_c_attach_malloc(unit, &err, 0); + if (wlc == NULL) + goto fail; + wlc->wiphy = wl->wiphy; + pub = wlc->pub; + +#if defined(DEBUG) + wlc_info_dbg = wlc; +#endif + + wlc->band = wlc->bandstate[0]; + wlc->core = wlc->corestate; + wlc->wl = wl; + pub->unit = unit; + pub->_piomode = piomode; + wlc->bandinit_pending = false; + wlc->beacon_template_virgin = true; + + /* populate struct brcms_c_info with default values */ + brcms_c_info_init(wlc, unit); + + /* update sta/ap related parameters */ + brcms_c_ap_upd(wlc); + + /* + * low level attach steps(all hw accesses go + * inside, no more in rest of the attach) + */ + err = brcms_b_attach(wlc, core, unit, piomode); + if (err) + goto fail; + + brcms_c_protection_upd(wlc, BRCMS_PROT_N_PAM_OVR, OFF); + + pub->phy_11ncapable = BRCMS_PHY_11N_CAP(wlc->band); + + /* disable allowed duty cycle */ + wlc->tx_duty_cycle_ofdm = 0; + wlc->tx_duty_cycle_cck = 0; + + brcms_c_stf_phy_chain_calc(wlc); + + /* txchain 1: txant 0, txchain 2: txant 1 */ + if (BRCMS_ISNPHY(wlc->band) && (wlc->stf->txstreams == 1)) + wlc->stf->txant = wlc->stf->hw_txchain - 1; + + /* push to BMAC driver */ + wlc_phy_stf_chain_init(wlc->band->pi, wlc->stf->hw_txchain, + wlc->stf->hw_rxchain); + + /* pull up some info resulting from the low attach */ + for (i = 0; i < NFIFO; i++) + wlc->core->txavail[i] = wlc->hw->txavail[i]; + + memcpy(&wlc->perm_etheraddr, &wlc->hw->etheraddr, ETH_ALEN); + memcpy(&pub->cur_etheraddr, &wlc->hw->etheraddr, ETH_ALEN); + + for (j = 0; j < wlc->pub->_nbands; j++) { + wlc->band = wlc->bandstate[j]; + + if (!brcms_c_attach_stf_ant_init(wlc)) { + err = 24; + goto fail; + } + + /* default contention windows size limits */ + wlc->band->CWmin = APHY_CWMIN; + wlc->band->CWmax = PHY_CWMAX; + + /* init gmode value */ + if (wlc->band->bandtype == BRCM_BAND_2G) { + wlc->band->gmode = GMODE_AUTO; + brcms_c_protection_upd(wlc, BRCMS_PROT_G_USER, + wlc->band->gmode); + } + + /* init _n_enab supported mode */ + if (BRCMS_PHY_11N_CAP(wlc->band)) { + pub->_n_enab = SUPPORT_11N; + brcms_c_protection_upd(wlc, BRCMS_PROT_N_USER, + ((pub->_n_enab == + SUPPORT_11N) ? WL_11N_2x2 : + WL_11N_3x3)); + } + + /* init per-band default rateset, depend on band->gmode */ + brcms_default_rateset(wlc, &wlc->band->defrateset); + + /* fill in hw_rateset */ + brcms_c_rateset_filter(&wlc->band->defrateset, + &wlc->band->hw_rateset, false, + BRCMS_RATES_CCK_OFDM, BRCMS_RATE_MASK, + (bool) (wlc->pub->_n_enab & SUPPORT_11N)); + } + + /* + * update antenna config due to + * wlc->stf->txant/txchain/ant_rx_ovr change + */ + brcms_c_stf_phy_txant_upd(wlc); + + /* attach each modules */ + err = brcms_c_attach_module(wlc); + if (err != 0) + goto fail; + + if (!brcms_c_timers_init(wlc, unit)) { + wiphy_err(wl->wiphy, "wl%d: %s: init_timer failed\n", unit, + __func__); + err = 32; + goto fail; + } + + /* depend on rateset, gmode */ + wlc->cmi = brcms_c_channel_mgr_attach(wlc); + if (!wlc->cmi) { + wiphy_err(wl->wiphy, "wl%d: %s: channel_mgr_attach failed" + "\n", unit, __func__); + err = 33; + goto fail; + } + + /* init default when all parameters are ready, i.e. ->rateset */ + brcms_c_bss_default_init(wlc); + + /* + * Complete the wlc default state initializations.. + */ + + wlc->bsscfg->wlc = wlc; + + wlc->mimoft = FT_HT; + wlc->mimo_40txbw = AUTO; + wlc->ofdm_40txbw = AUTO; + wlc->cck_40txbw = AUTO; + brcms_c_update_mimo_band_bwcap(wlc, BRCMS_N_BW_20IN2G_40IN5G); + + /* Set default values of SGI */ + if (BRCMS_SGI_CAP_PHY(wlc)) { + brcms_c_ht_update_sgi_rx(wlc, (BRCMS_N_SGI_20 | + BRCMS_N_SGI_40)); + } else if (BRCMS_ISSSLPNPHY(wlc->band)) { + brcms_c_ht_update_sgi_rx(wlc, (BRCMS_N_SGI_20 | + BRCMS_N_SGI_40)); + } else { + brcms_c_ht_update_sgi_rx(wlc, 0); + } + + brcms_b_antsel_set(wlc->hw, wlc->asi->antsel_avail); + + if (perr) + *perr = 0; + + return wlc; + + fail: + wiphy_err(wl->wiphy, "wl%d: %s: failed with err %d\n", + unit, __func__, err); + if (wlc) + brcms_c_detach(wlc); + + if (perr) + *perr = err; + return NULL; +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/main.h b/drivers/net/wireless/brcm80211/brcmsmac/main.h new file mode 100644 index 0000000..b5d7a38 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/main.h @@ -0,0 +1,681 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_MAIN_H_ +#define _BRCM_MAIN_H_ + +#include + +#include +#include "types.h" +#include "d11.h" +#include "scb.h" + +#define INVCHANNEL 255 /* invalid channel */ + +/* max # brcms_c_module_register() calls */ +#define BRCMS_MAXMODULES 22 + +#define SEQNUM_SHIFT 4 +#define SEQNUM_MAX 0x1000 + +#define NTXRATE 64 /* # tx MPDUs rate is reported for */ + +/* Maximum wait time for a MAC suspend */ +/* uS: 83mS is max packet time (64KB ampdu @ 6Mbps) */ +#define BRCMS_MAX_MAC_SUSPEND 83000 + +/* responses for probe requests older that this are tossed, zero to disable */ +#define BRCMS_PRB_RESP_TIMEOUT 0 /* Disable probe response timeout */ + +/* transmit buffer max headroom for protocol headers */ +#define TXOFF (D11_TXH_LEN + D11_PHY_HDR_LEN) + +/* Macros for doing definition and get/set of bitfields + * Usage example, e.g. a three-bit field (bits 4-6): + * #define _M BITFIELD_MASK(3) + * #define _S 4 + * ... + * regval = R_REG(osh, ®s->regfoo); + * field = GFIELD(regval, ); + * regval = SFIELD(regval, , 1); + * W_REG(osh, ®s->regfoo, regval); + */ +#define BITFIELD_MASK(width) \ + (((unsigned)1 << (width)) - 1) +#define GFIELD(val, field) \ + (((val) >> field ## _S) & field ## _M) +#define SFIELD(val, field, bits) \ + (((val) & (~(field ## _M << field ## _S))) | \ + ((unsigned)(bits) << field ## _S)) + +#define SW_TIMER_MAC_STAT_UPD 30 /* periodic MAC stats update */ + +/* max # supported core revisions (0 .. MAXCOREREV - 1) */ +#define MAXCOREREV 28 + +/* Double check that unsupported cores are not enabled */ +#if CONF_MSK(D11CONF, 0x4f) || CONF_GE(D11CONF, MAXCOREREV) +#error "Configuration for D11CONF includes unsupported versions." +#endif /* Bad versions */ + +/* values for shortslot_override */ +#define BRCMS_SHORTSLOT_AUTO -1 /* Driver will manage Shortslot setting */ +#define BRCMS_SHORTSLOT_OFF 0 /* Turn off short slot */ +#define BRCMS_SHORTSLOT_ON 1 /* Turn on short slot */ + +/* value for short/long and mixmode/greenfield preamble */ +#define BRCMS_LONG_PREAMBLE (0) +#define BRCMS_SHORT_PREAMBLE (1 << 0) +#define BRCMS_GF_PREAMBLE (1 << 1) +#define BRCMS_MM_PREAMBLE (1 << 2) +#define BRCMS_IS_MIMO_PREAMBLE(_pre) (((_pre) == BRCMS_GF_PREAMBLE) || \ + ((_pre) == BRCMS_MM_PREAMBLE)) + +/* TxFrameID */ +/* seq and frag bits: SEQNUM_SHIFT, FRAGNUM_MASK (802.11.h) */ +/* rate epoch bits: TXFID_RATE_SHIFT, TXFID_RATE_MASK ((wlc_rate.c) */ +#define TXFID_QUEUE_MASK 0x0007 /* Bits 0-2 */ +#define TXFID_SEQ_MASK 0x7FE0 /* Bits 5-15 */ +#define TXFID_SEQ_SHIFT 5 /* Number of bit shifts */ +#define TXFID_RATE_PROBE_MASK 0x8000 /* Bit 15 for rate probe */ +#define TXFID_RATE_MASK 0x0018 /* Mask for bits 3 and 4 */ +#define TXFID_RATE_SHIFT 3 /* Shift 3 bits for rate mask */ + +/* promote boardrev */ +#define BOARDREV_PROMOTABLE 0xFF /* from */ +#define BOARDREV_PROMOTED 1 /* to */ + +#define DATA_BLOCK_TX_SUPR (1 << 4) + +/* Ucode MCTL_WAKE override bits */ +#define BRCMS_WAKE_OVERRIDE_CLKCTL 0x01 +#define BRCMS_WAKE_OVERRIDE_PHYREG 0x02 +#define BRCMS_WAKE_OVERRIDE_MACSUSPEND 0x04 +#define BRCMS_WAKE_OVERRIDE_TXFIFO 0x08 +#define BRCMS_WAKE_OVERRIDE_FORCEFAST 0x10 + +/* stuff pulled in from wlc.c */ + +/* Interrupt bit error summary. Don't include I_RU: we refill DMA at other + * times; and if we run out, constant I_RU interrupts may cause lockup. We + * will still get error counts from rx0ovfl. + */ +#define I_ERRORS (I_PC | I_PD | I_DE | I_RO | I_XU) +/* default software intmasks */ +#define DEF_RXINTMASK (I_RI) /* enable rx int on rxfifo only */ +#define DEF_MACINTMASK (MI_TXSTOP | MI_TBTT | MI_ATIMWINEND | MI_PMQ | \ + MI_PHYTXERR | MI_DMAINT | MI_TFS | MI_BG_NOISE | \ + MI_CCA | MI_TO | MI_GP0 | MI_RFDISABLE | MI_PWRUP) + +#define MAXTXPKTS 6 /* max # pkts pending */ + +/* frameburst */ +#define MAXTXFRAMEBURST 8 /* vanilla xpress mode: max frames/burst */ +#define MAXFRAMEBURST_TXOP 10000 /* Frameburst TXOP in usec */ + +#define NFIFO 6 /* # tx/rx fifopairs */ + +/* PLL requests */ + +/* pll is shared on old chips */ +#define BRCMS_PLLREQ_SHARED 0x1 +/* hold pll for radio monitor register checking */ +#define BRCMS_PLLREQ_RADIO_MON 0x2 +/* hold/release pll for some short operation */ +#define BRCMS_PLLREQ_FLIP 0x4 + +#define CHANNEL_BANDUNIT(wlc, ch) \ + (((ch) <= CH_MAX_2G_CHANNEL) ? BAND_2G_INDEX : BAND_5G_INDEX) + +#define OTHERBANDUNIT(wlc) \ + ((uint)((wlc)->band->bandunit ? BAND_2G_INDEX : BAND_5G_INDEX)) + +/* + * 802.11 protection information + * + * _g: use g spec protection, driver internal. + * g_override: override for use of g spec protection. + * gmode_user: user config gmode, operating band->gmode is different. + * overlap: Overlap BSS/IBSS protection for both 11g and 11n. + * nmode_user: user config nmode, operating pub->nmode is different. + * n_cfg: use OFDM protection on MIMO frames. + * n_cfg_override: override for use of N protection. + * nongf: non-GF present protection. + * nongf_override: override for use of GF protection. + * n_pam_override: override for preamble: MM or GF. + * n_obss: indicated OBSS Non-HT STA present. +*/ +struct brcms_protection { + bool _g; + s8 g_override; + u8 gmode_user; + s8 overlap; + s8 nmode_user; + s8 n_cfg; + s8 n_cfg_override; + bool nongf; + s8 nongf_override; + s8 n_pam_override; + bool n_obss; +}; + +/* + * anything affecting the single/dual streams/antenna operation + * + * hw_txchain: HW txchain bitmap cfg. + * txchain: txchain bitmap being used. + * txstreams: number of txchains being used. + * hw_rxchain: HW rxchain bitmap cfg. + * rxchain: rxchain bitmap being used. + * rxstreams: number of rxchains being used. + * ant_rx_ovr: rx antenna override. + * txant: userTx antenna setting. + * phytxant: phyTx antenna setting in txheader. + * ss_opmode: singlestream Operational mode, 0:siso; 1:cdd. + * ss_algosel_auto: if true, use wlc->stf->ss_algo_channel; + * else use wlc->band->stf->ss_mode_band. + * ss_algo_channel: ss based on per-channel algo: 0: SISO, 1: CDD 2: STBC. + * rxchain_restore_delay: delay time to restore default rxchain. + * ldpc: AUTO/ON/OFF ldpc cap supported. + * txcore[MAX_STREAMS_SUPPORTED + 1]: bitmap of selected core for each Nsts. + * spatial_policy: + */ +struct brcms_stf { + u8 hw_txchain; + u8 txchain; + u8 txstreams; + u8 hw_rxchain; + u8 rxchain; + u8 rxstreams; + u8 ant_rx_ovr; + s8 txant; + u16 phytxant; + u8 ss_opmode; + bool ss_algosel_auto; + u16 ss_algo_channel; + u8 rxchain_restore_delay; + s8 ldpc; + u8 txcore[MAX_STREAMS_SUPPORTED + 1]; + s8 spatial_policy; +}; + +#define BRCMS_STF_SS_STBC_TX(wlc, scb) \ + (((wlc)->stf->txstreams > 1) && (((wlc)->band->band_stf_stbc_tx == ON) \ + || (((scb)->flags & SCB_STBCCAP) && \ + (wlc)->band->band_stf_stbc_tx == AUTO && \ + isset(&((wlc)->stf->ss_algo_channel), PHY_TXC1_MODE_STBC)))) + +#define BRCMS_STBC_CAP_PHY(wlc) (BRCMS_ISNPHY(wlc->band) && \ + NREV_GE(wlc->band->phyrev, 3)) + +#define BRCMS_SGI_CAP_PHY(wlc) ((BRCMS_ISNPHY(wlc->band) && \ + NREV_GE(wlc->band->phyrev, 3)) || \ + BRCMS_ISLCNPHY(wlc->band)) + +#define BRCMS_CHAN_PHYTYPE(x) (((x) & RXS_CHAN_PHYTYPE_MASK) \ + >> RXS_CHAN_PHYTYPE_SHIFT) +#define BRCMS_CHAN_CHANNEL(x) (((x) & RXS_CHAN_ID_MASK) \ + >> RXS_CHAN_ID_SHIFT) + +/* + * core state (mac) + */ +struct brcms_core { + uint coreidx; /* # sb enumerated core */ + + /* fifo */ + uint *txavail[NFIFO]; /* # tx descriptors available */ + + struct macstat *macstat_snapshot; /* mac hw prev read values */ +}; + +/* + * band state (phy+ana+radio) + */ +struct brcms_band { + int bandtype; /* BRCM_BAND_2G, BRCM_BAND_5G */ + uint bandunit; /* bandstate[] index */ + + u16 phytype; /* phytype */ + u16 phyrev; + u16 radioid; + u16 radiorev; + struct brcms_phy_pub *pi; /* pointer to phy specific information */ + bool abgphy_encore; + + u8 gmode; /* currently active gmode */ + + struct scb *hwrs_scb; /* permanent scb for hw rateset */ + + /* band-specific copy of default_bss.rateset */ + struct brcms_c_rateset defrateset; + + u8 band_stf_ss_mode; /* Configured STF type, 0:siso; 1:cdd */ + s8 band_stf_stbc_tx; /* STBC TX 0:off; 1:force on; -1:auto */ + /* rates supported by chip (phy-specific) */ + struct brcms_c_rateset hw_rateset; + u8 basic_rate[BRCM_MAXRATE + 1]; /* basic rates indexed by rate */ + bool mimo_cap_40; /* 40 MHz cap enabled on this band */ + s8 antgain; /* antenna gain from srom */ + + u16 CWmin; /* minimum size of contention window, in unit of aSlotTime */ + u16 CWmax; /* maximum size of contention window, in unit of aSlotTime */ + struct ieee80211_supported_band band; +}; + +/* module control blocks */ +struct modulecb { + /* module name : NULL indicates empty array member */ + char name[32]; + /* handle passed when handler 'doiovar' is called */ + struct brcms_info *hdl; + + int (*down_fn)(void *handle); /* down handler. Note: the int returned + * by the down function is a count of the + * number of timers that could not be + * freed. + */ + +}; + +struct brcms_hw_band { + int bandtype; /* BRCM_BAND_2G, BRCM_BAND_5G */ + uint bandunit; /* bandstate[] index */ + u16 mhfs[MHFMAX]; /* MHF array shadow */ + u8 bandhw_stf_ss_mode; /* HW configured STF type, 0:siso; 1:cdd */ + u16 CWmin; + u16 CWmax; + u32 core_flags; + + u16 phytype; /* phytype */ + u16 phyrev; + u16 radioid; + u16 radiorev; + struct brcms_phy_pub *pi; /* pointer to phy specific information */ + bool abgphy_encore; +}; + +struct brcms_hardware { + bool _piomode; /* true if pio mode */ + struct brcms_c_info *wlc; + + /* fifo */ + struct dma_pub *di[NFIFO]; /* dma handles, per fifo */ + + uint unit; /* device instance number */ + + /* version info */ + u16 vendorid; /* PCI vendor id */ + u16 deviceid; /* PCI device id */ + uint corerev; /* core revision */ + u8 sromrev; /* version # of the srom */ + u16 boardrev; /* version # of particular board */ + u32 boardflags; /* Board specific flags from srom */ + u32 boardflags2; /* More board flags if sromrev >= 4 */ + u32 machwcap; /* MAC capabilities */ + u32 machwcap_backup; /* backup of machwcap */ + + struct si_pub *sih; /* SI handle (cookie for siutils calls) */ + struct bcma_device *d11core; /* pointer to 802.11 core */ + struct phy_shim_info *physhim; /* phy shim layer handler */ + struct shared_phy *phy_sh; /* pointer to shared phy state */ + struct brcms_hw_band *band;/* pointer to active per-band state */ + /* band state per phy/radio */ + struct brcms_hw_band *bandstate[MAXBANDS]; + u16 bmac_phytxant; /* cache of high phytxant state */ + bool shortslot; /* currently using 11g ShortSlot timing */ + u16 SRL; /* 802.11 dot11ShortRetryLimit */ + u16 LRL; /* 802.11 dot11LongRetryLimit */ + u16 SFBL; /* Short Frame Rate Fallback Limit */ + u16 LFBL; /* Long Frame Rate Fallback Limit */ + + bool up; /* d11 hardware up and running */ + uint now; /* # elapsed seconds */ + uint _nbands; /* # bands supported */ + u16 chanspec; /* bmac chanspec shadow */ + + uint *txavail[NFIFO]; /* # tx descriptors available */ + const u16 *xmtfifo_sz; /* fifo size in 256B for each xmt fifo */ + + u32 pllreq; /* pll requests to keep PLL on */ + + u8 suspended_fifos; /* Which TX fifo to remain awake for */ + u32 maccontrol; /* Cached value of maccontrol */ + uint mac_suspend_depth; /* current depth of mac_suspend levels */ + u32 wake_override; /* bit flags to force MAC to WAKE mode */ + u32 mute_override; /* Prevent ucode from sending beacons */ + u8 etheraddr[ETH_ALEN]; /* currently configured ethernet address */ + bool noreset; /* true= do not reset hw, used by WLC_OUT */ + bool forcefastclk; /* true if h/w is forcing to use fast clk */ + bool clk; /* core is out of reset and has clock */ + bool sbclk; /* sb has clock */ + bool phyclk; /* phy is out of reset and has clock */ + + bool ucode_loaded; /* true after ucode downloaded */ + + + u8 hw_stf_ss_opmode; /* STF single stream operation mode */ + u8 antsel_type; /* Type of boardlevel mimo antenna switch-logic + * 0 = N/A, 1 = 2x4 board, 2 = 2x3 CB2 board + */ + u32 antsel_avail; /* + * put struct antsel_info here if more info is + * needed + */ +}; + +/* + * Principal common driver data structure. + * + * pub: pointer to driver public state. + * wl: pointer to specific private state. + * hw: HW related state. + * clkreq_override: setting for clkreq for PCIE : Auto, 0, 1. + * fastpwrup_dly: time in us needed to bring up d11 fast clock. + * macintstatus: bit channel between isr and dpc. + * macintmask: sw runtime master macintmask value. + * defmacintmask: default "on" macintmask value. + * clk: core is out of reset and has clock. + * core: pointer to active io core. + * band: pointer to active per-band state. + * corestate: per-core state (one per hw core). + * bandstate: per-band state (one per phy/radio). + * qvalid: DirFrmQValid and BcMcFrmQValid. + * ampdu: ampdu module handler. + * asi: antsel module handler. + * cmi: channel manager module handler. + * vendorid: PCI vendor id. + * deviceid: PCI device id. + * ucode_rev: microcode revision. + * machwcap: MAC capabilities, BMAC shadow. + * perm_etheraddr: original sprom local ethernet address. + * bandlocked: disable auto multi-band switching. + * bandinit_pending: track band init in auto band. + * radio_monitor: radio timer is running. + * going_down: down path intermediate variable. + * wdtimer: timer for watchdog routine. + * radio_timer: timer for hw radio button monitor routine. + * monitor: monitor (MPDU sniffing) mode. + * bcnmisc_monitor: bcns promisc mode override for monitor. + * _rifs: enable per-packet rifs. + * bcn_li_bcn: beacon listen interval in # beacons. + * bcn_li_dtim: beacon listen interval in # dtims. + * WDarmed: watchdog timer is armed. + * WDlast: last time wlc_watchdog() was called. + * edcf_txop[IEEE80211_NUM_ACS]: current txop for each ac. + * wme_retries: per-AC retry limits. + * bsscfg: set of BSS configurations, idx 0 is default and always valid. + * cfg: the primary bsscfg (can be AP or STA). + * modulecb: + * mimoft: SIGN or 11N. + * cck_40txbw: 11N, cck tx b/w override when in 40MHZ mode. + * ofdm_40txbw: 11N, ofdm tx b/w override when in 40MHZ mode. + * mimo_40txbw: 11N, mimo tx b/w override when in 40MHZ mode. + * default_bss: configured BSS parameters. + * mc_fid_counter: BC/MC FIFO frame ID counter. + * country_default: saved country for leaving 802.11d auto-country mode. + * autocountry_default: initial country for 802.11d auto-country mode. + * prb_resp_timeout: do not send prb resp if request older + * than this, 0 = disable. + * home_chanspec: shared home chanspec. + * chanspec: target operational channel. + * usr_fragthresh: user configured fragmentation threshold. + * fragthresh[NFIFO]: per-fifo fragmentation thresholds. + * RTSThresh: 802.11 dot11RTSThreshold. + * SRL: 802.11 dot11ShortRetryLimit. + * LRL: 802.11 dot11LongRetryLimit. + * SFBL: Short Frame Rate Fallback Limit. + * LFBL: Long Frame Rate Fallback Limit. + * shortslot: currently using 11g ShortSlot timing. + * shortslot_override: 11g ShortSlot override. + * include_legacy_erp: include Legacy ERP info elt ID 47 as well as g ID 42. + * PLCPHdr_override: 802.11b Preamble Type override. + * stf: + * bcn_rspec: save bcn ratespec purpose. + * tempsense_lasttime; + * tx_duty_cycle_ofdm: maximum allowed duty cycle for OFDM. + * tx_duty_cycle_cck: maximum allowed duty cycle for CCK. + * wiphy: + * pri_scb: primary Station Control Block + */ +struct brcms_c_info { + struct brcms_pub *pub; + struct brcms_info *wl; + struct brcms_hardware *hw; + + /* clock */ + u16 fastpwrup_dly; + + /* interrupt */ + u32 macintstatus; + u32 macintmask; + u32 defmacintmask; + + bool clk; + + /* multiband */ + struct brcms_core *core; + struct brcms_band *band; + struct brcms_core *corestate; + struct brcms_band *bandstate[MAXBANDS]; + + /* packet queue */ + uint qvalid; + + struct ampdu_info *ampdu; + struct antsel_info *asi; + struct brcms_cm_info *cmi; + + u16 vendorid; + u16 deviceid; + uint ucode_rev; + + u8 perm_etheraddr[ETH_ALEN]; + + bool bandlocked; + bool bandinit_pending; + + bool radio_monitor; + bool going_down; + + bool beacon_template_virgin; + + struct brcms_timer *wdtimer; + struct brcms_timer *radio_timer; + + /* promiscuous */ + uint filter_flags; + + /* driver feature */ + bool _rifs; + + /* AP-STA synchronization, power save */ + u8 bcn_li_bcn; + u8 bcn_li_dtim; + + bool WDarmed; + u32 WDlast; + + /* WME */ + u16 edcf_txop[IEEE80211_NUM_ACS]; + + u16 wme_retries[IEEE80211_NUM_ACS]; + + struct brcms_bss_cfg *bsscfg; + + struct modulecb *modulecb; + + u8 mimoft; + s8 cck_40txbw; + s8 ofdm_40txbw; + s8 mimo_40txbw; + + struct brcms_bss_info *default_bss; + + u16 mc_fid_counter; + + char country_default[BRCM_CNTRY_BUF_SZ]; + char autocountry_default[BRCM_CNTRY_BUF_SZ]; + u16 prb_resp_timeout; + + u16 home_chanspec; + + /* PHY parameters */ + u16 chanspec; + u16 usr_fragthresh; + u16 fragthresh[NFIFO]; + u16 RTSThresh; + u16 SRL; + u16 LRL; + u16 SFBL; + u16 LFBL; + + /* network config */ + bool shortslot; + s8 shortslot_override; + bool include_legacy_erp; + + struct brcms_protection *protection; + s8 PLCPHdr_override; + + struct brcms_stf *stf; + + u32 bcn_rspec; + + uint tempsense_lasttime; + + u16 tx_duty_cycle_ofdm; + u16 tx_duty_cycle_cck; + + struct wiphy *wiphy; + struct scb pri_scb; + + struct sk_buff *beacon; + u16 beacon_tim_offset; + u16 beacon_dtim_period; + struct sk_buff *probe_resp; +}; + +/* antsel module specific state */ +struct antsel_info { + struct brcms_c_info *wlc; /* pointer to main wlc structure */ + struct brcms_pub *pub; /* pointer to public fn */ + u8 antsel_type; /* Type of boardlevel mimo antenna switch-logic + * 0 = N/A, 1 = 2x4 board, 2 = 2x3 CB2 board + */ + u8 antsel_antswitch; /* board level antenna switch type */ + bool antsel_avail; /* Ant selection availability (SROM based) */ + struct brcms_antselcfg antcfg_11n; /* antenna configuration */ + struct brcms_antselcfg antcfg_cur; /* current antenna config (auto) */ +}; + +enum brcms_bss_type { + BRCMS_TYPE_STATION, + BRCMS_TYPE_AP, + BRCMS_TYPE_ADHOC, +}; + +/* + * BSS configuration state + * + * wlc: wlc to which this bsscfg belongs to. + * type: interface type + * SSID_len: the length of SSID + * SSID: SSID string + * + * + * BSSID: BSSID (associated) + * cur_etheraddr: h/w address + * flags: BSSCFG flags; see below + * + * current_bss: BSS parms in ASSOCIATED state + * + * + * ID: 'unique' ID of this bsscfg, assigned at bsscfg allocation + */ +struct brcms_bss_cfg { + struct brcms_c_info *wlc; + enum brcms_bss_type type; + u8 SSID_len; + u8 SSID[IEEE80211_MAX_SSID_LEN]; + u8 BSSID[ETH_ALEN]; + struct brcms_bss_info *current_bss; +}; + +extern int brcms_c_txfifo(struct brcms_c_info *wlc, uint fifo, + struct sk_buff *p); +extern int brcms_b_xmtfifo_sz_get(struct brcms_hardware *wlc_hw, uint fifo, + uint *blocks); + +extern int brcms_c_set_gmode(struct brcms_c_info *wlc, u8 gmode, bool config); +extern void brcms_c_mac_promisc(struct brcms_c_info *wlc, uint filter_flags); +extern u16 brcms_c_calc_lsig_len(struct brcms_c_info *wlc, u32 ratespec, + uint mac_len); +extern u32 brcms_c_rspec_to_rts_rspec(struct brcms_c_info *wlc, + u32 rspec, + bool use_rspec, u16 mimo_ctlchbw); +extern u16 brcms_c_compute_rtscts_dur(struct brcms_c_info *wlc, bool cts_only, + u32 rts_rate, + u32 frame_rate, + u8 rts_preamble_type, + u8 frame_preamble_type, uint frame_len, + bool ba); +extern void brcms_c_inval_dma_pkts(struct brcms_hardware *hw, + struct ieee80211_sta *sta, + void (*dma_callback_fn)); +extern void brcms_c_update_probe_resp(struct brcms_c_info *wlc, bool suspend); +extern int brcms_c_set_nmode(struct brcms_c_info *wlc); +extern void brcms_c_beacon_phytxctl_txant_upd(struct brcms_c_info *wlc, + u32 bcn_rate); +extern void brcms_b_antsel_type_set(struct brcms_hardware *wlc_hw, + u8 antsel_type); +extern void brcms_b_set_chanspec(struct brcms_hardware *wlc_hw, + u16 chanspec, + bool mute, struct txpwr_limits *txpwr); +extern void brcms_b_write_shm(struct brcms_hardware *wlc_hw, uint offset, + u16 v); +extern u16 brcms_b_read_shm(struct brcms_hardware *wlc_hw, uint offset); +extern void brcms_b_mhf(struct brcms_hardware *wlc_hw, u8 idx, u16 mask, + u16 val, int bands); +extern void brcms_b_corereset(struct brcms_hardware *wlc_hw, u32 flags); +extern void brcms_b_mctrl(struct brcms_hardware *wlc_hw, u32 mask, u32 val); +extern void brcms_b_phy_reset(struct brcms_hardware *wlc_hw); +extern void brcms_b_bw_set(struct brcms_hardware *wlc_hw, u16 bw); +extern void brcms_b_core_phypll_reset(struct brcms_hardware *wlc_hw); +extern void brcms_c_ucode_wake_override_set(struct brcms_hardware *wlc_hw, + u32 override_bit); +extern void brcms_c_ucode_wake_override_clear(struct brcms_hardware *wlc_hw, + u32 override_bit); +extern void brcms_b_write_template_ram(struct brcms_hardware *wlc_hw, + int offset, int len, void *buf); +extern u16 brcms_b_rate_shm_offset(struct brcms_hardware *wlc_hw, u8 rate); +extern void brcms_b_copyto_objmem(struct brcms_hardware *wlc_hw, + uint offset, const void *buf, int len, + u32 sel); +extern void brcms_b_copyfrom_objmem(struct brcms_hardware *wlc_hw, uint offset, + void *buf, int len, u32 sel); +extern void brcms_b_switch_macfreq(struct brcms_hardware *wlc_hw, u8 spurmode); +extern u16 brcms_b_get_txant(struct brcms_hardware *wlc_hw); +extern void brcms_b_phyclk_fgc(struct brcms_hardware *wlc_hw, bool clk); +extern void brcms_b_macphyclk_set(struct brcms_hardware *wlc_hw, bool clk); +extern void brcms_b_core_phypll_ctl(struct brcms_hardware *wlc_hw, bool on); +extern void brcms_b_txant_set(struct brcms_hardware *wlc_hw, u16 phytxant); +extern void brcms_b_band_stf_ss_set(struct brcms_hardware *wlc_hw, + u8 stf_mode); +extern void brcms_c_init_scb(struct scb *scb); + +#endif /* _BRCM_MAIN_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_cmn.c b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_cmn.c new file mode 100644 index 0000000..b0fd807 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_cmn.c @@ -0,0 +1,2957 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include "phy_hal.h" +#include "phy_int.h" +#include "phy_radio.h" +#include "phy_lcn.h" +#include "phyreg_n.h" + +#define VALID_N_RADIO(radioid) ((radioid == BCM2055_ID) || \ + (radioid == BCM2056_ID) || \ + (radioid == BCM2057_ID)) + +#define VALID_LCN_RADIO(radioid) (radioid == BCM2064_ID) + +#define VALID_RADIO(pi, radioid) ( \ + (ISNPHY(pi) ? VALID_N_RADIO(radioid) : false) || \ + (ISLCNPHY(pi) ? VALID_LCN_RADIO(radioid) : false)) + +/* basic mux operation - can be optimized on several architectures */ +#define MUX(pred, true, false) ((pred) ? (true) : (false)) + +/* modulo inc/dec - assumes x E [0, bound - 1] */ +#define MODINC(x, bound) MUX((x) == (bound) - 1, 0, (x) + 1) + +/* modulo inc/dec, bound = 2^k */ +#define MODDEC_POW2(x, bound) (((x) - 1) & ((bound) - 1)) +#define MODINC_POW2(x, bound) (((x) + 1) & ((bound) - 1)) + +struct chan_info_basic { + u16 chan; + u16 freq; +}; + +static const struct chan_info_basic chan_info_all[] = { + {1, 2412}, + {2, 2417}, + {3, 2422}, + {4, 2427}, + {5, 2432}, + {6, 2437}, + {7, 2442}, + {8, 2447}, + {9, 2452}, + {10, 2457}, + {11, 2462}, + {12, 2467}, + {13, 2472}, + {14, 2484}, + + {34, 5170}, + {38, 5190}, + {42, 5210}, + {46, 5230}, + + {36, 5180}, + {40, 5200}, + {44, 5220}, + {48, 5240}, + {52, 5260}, + {56, 5280}, + {60, 5300}, + {64, 5320}, + + {100, 5500}, + {104, 5520}, + {108, 5540}, + {112, 5560}, + {116, 5580}, + {120, 5600}, + {124, 5620}, + {128, 5640}, + {132, 5660}, + {136, 5680}, + {140, 5700}, + + {149, 5745}, + {153, 5765}, + {157, 5785}, + {161, 5805}, + {165, 5825}, + + {184, 4920}, + {188, 4940}, + {192, 4960}, + {196, 4980}, + {200, 5000}, + {204, 5020}, + {208, 5040}, + {212, 5060}, + {216, 5080} +}; + +static const u8 ofdm_rate_lookup[] = { + + BRCM_RATE_48M, + BRCM_RATE_24M, + BRCM_RATE_12M, + BRCM_RATE_6M, + BRCM_RATE_54M, + BRCM_RATE_36M, + BRCM_RATE_18M, + BRCM_RATE_9M +}; + +#define PHY_WREG_LIMIT 24 + +void wlc_phyreg_enter(struct brcms_phy_pub *pih) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + wlapi_bmac_ucode_wake_override_phyreg_set(pi->sh->physhim); +} + +void wlc_phyreg_exit(struct brcms_phy_pub *pih) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + wlapi_bmac_ucode_wake_override_phyreg_clear(pi->sh->physhim); +} + +void wlc_radioreg_enter(struct brcms_phy_pub *pih) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + wlapi_bmac_mctrl(pi->sh->physhim, MCTL_LOCK_RADIO, MCTL_LOCK_RADIO); + + udelay(10); +} + +void wlc_radioreg_exit(struct brcms_phy_pub *pih) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + + (void)bcma_read16(pi->d11core, D11REGOFFS(phyversion)); + pi->phy_wreg = 0; + wlapi_bmac_mctrl(pi->sh->physhim, MCTL_LOCK_RADIO, 0); +} + +u16 read_radio_reg(struct brcms_phy *pi, u16 addr) +{ + u16 data; + + if ((addr == RADIO_IDCODE)) + return 0xffff; + + switch (pi->pubpi.phy_type) { + case PHY_TYPE_N: + if (!CONF_HAS(PHYTYPE, PHY_TYPE_N)) + break; + if (NREV_GE(pi->pubpi.phy_rev, 7)) + addr |= RADIO_2057_READ_OFF; + else + addr |= RADIO_2055_READ_OFF; + break; + + case PHY_TYPE_LCN: + if (!CONF_HAS(PHYTYPE, PHY_TYPE_LCN)) + break; + addr |= RADIO_2064_READ_OFF; + break; + + default: + break; + } + + if ((D11REV_GE(pi->sh->corerev, 24)) || + (D11REV_IS(pi->sh->corerev, 22) + && (pi->pubpi.phy_type != PHY_TYPE_SSN))) { + bcma_wflush16(pi->d11core, D11REGOFFS(radioregaddr), addr); + data = bcma_read16(pi->d11core, D11REGOFFS(radioregdata)); + } else { + bcma_wflush16(pi->d11core, D11REGOFFS(phy4waddr), addr); + data = bcma_read16(pi->d11core, D11REGOFFS(phy4wdatalo)); + } + pi->phy_wreg = 0; + + return data; +} + +void write_radio_reg(struct brcms_phy *pi, u16 addr, u16 val) +{ + if ((D11REV_GE(pi->sh->corerev, 24)) || + (D11REV_IS(pi->sh->corerev, 22) + && (pi->pubpi.phy_type != PHY_TYPE_SSN))) { + + bcma_wflush16(pi->d11core, D11REGOFFS(radioregaddr), addr); + bcma_write16(pi->d11core, D11REGOFFS(radioregdata), val); + } else { + bcma_wflush16(pi->d11core, D11REGOFFS(phy4waddr), addr); + bcma_write16(pi->d11core, D11REGOFFS(phy4wdatalo), val); + } + + if ((pi->d11core->bus->hosttype == BCMA_HOSTTYPE_PCI) && + (++pi->phy_wreg >= pi->phy_wreg_limit)) { + (void)bcma_read32(pi->d11core, D11REGOFFS(maccontrol)); + pi->phy_wreg = 0; + } +} + +static u32 read_radio_id(struct brcms_phy *pi) +{ + u32 id; + + if (D11REV_GE(pi->sh->corerev, 24)) { + u32 b0, b1, b2; + + bcma_wflush16(pi->d11core, D11REGOFFS(radioregaddr), 0); + b0 = (u32) bcma_read16(pi->d11core, D11REGOFFS(radioregdata)); + bcma_wflush16(pi->d11core, D11REGOFFS(radioregaddr), 1); + b1 = (u32) bcma_read16(pi->d11core, D11REGOFFS(radioregdata)); + bcma_wflush16(pi->d11core, D11REGOFFS(radioregaddr), 2); + b2 = (u32) bcma_read16(pi->d11core, D11REGOFFS(radioregdata)); + + id = ((b0 & 0xf) << 28) | (((b2 << 8) | b1) << 12) | ((b0 >> 4) + & 0xf); + } else { + bcma_wflush16(pi->d11core, D11REGOFFS(phy4waddr), RADIO_IDCODE); + id = (u32) bcma_read16(pi->d11core, D11REGOFFS(phy4wdatalo)); + id |= (u32) bcma_read16(pi->d11core, + D11REGOFFS(phy4wdatahi)) << 16; + } + pi->phy_wreg = 0; + return id; +} + +void and_radio_reg(struct brcms_phy *pi, u16 addr, u16 val) +{ + u16 rval; + + rval = read_radio_reg(pi, addr); + write_radio_reg(pi, addr, (rval & val)); +} + +void or_radio_reg(struct brcms_phy *pi, u16 addr, u16 val) +{ + u16 rval; + + rval = read_radio_reg(pi, addr); + write_radio_reg(pi, addr, (rval | val)); +} + +void xor_radio_reg(struct brcms_phy *pi, u16 addr, u16 mask) +{ + u16 rval; + + rval = read_radio_reg(pi, addr); + write_radio_reg(pi, addr, (rval ^ mask)); +} + +void mod_radio_reg(struct brcms_phy *pi, u16 addr, u16 mask, u16 val) +{ + u16 rval; + + rval = read_radio_reg(pi, addr); + write_radio_reg(pi, addr, (rval & ~mask) | (val & mask)); +} + +void write_phy_channel_reg(struct brcms_phy *pi, uint val) +{ + bcma_write16(pi->d11core, D11REGOFFS(phychannel), val); +} + +u16 read_phy_reg(struct brcms_phy *pi, u16 addr) +{ + bcma_wflush16(pi->d11core, D11REGOFFS(phyregaddr), addr); + + pi->phy_wreg = 0; + return bcma_read16(pi->d11core, D11REGOFFS(phyregdata)); +} + +void write_phy_reg(struct brcms_phy *pi, u16 addr, u16 val) +{ +#ifdef CONFIG_BCM47XX + bcma_wflush16(pi->d11core, D11REGOFFS(phyregaddr), addr); + bcma_write16(pi->d11core, D11REGOFFS(phyregdata), val); + if (addr == 0x72) + (void)bcma_read16(pi->d11core, D11REGOFFS(phyregdata)); +#else + bcma_write32(pi->d11core, D11REGOFFS(phyregaddr), addr | (val << 16)); + if ((pi->d11core->bus->hosttype == BCMA_HOSTTYPE_PCI) && + (++pi->phy_wreg >= pi->phy_wreg_limit)) { + pi->phy_wreg = 0; + (void)bcma_read16(pi->d11core, D11REGOFFS(phyversion)); + } +#endif +} + +void and_phy_reg(struct brcms_phy *pi, u16 addr, u16 val) +{ + bcma_wflush16(pi->d11core, D11REGOFFS(phyregaddr), addr); + bcma_mask16(pi->d11core, D11REGOFFS(phyregdata), val); + pi->phy_wreg = 0; +} + +void or_phy_reg(struct brcms_phy *pi, u16 addr, u16 val) +{ + bcma_wflush16(pi->d11core, D11REGOFFS(phyregaddr), addr); + bcma_set16(pi->d11core, D11REGOFFS(phyregdata), val); + pi->phy_wreg = 0; +} + +void mod_phy_reg(struct brcms_phy *pi, u16 addr, u16 mask, u16 val) +{ + val &= mask; + bcma_wflush16(pi->d11core, D11REGOFFS(phyregaddr), addr); + bcma_maskset16(pi->d11core, D11REGOFFS(phyregdata), ~mask, val); + pi->phy_wreg = 0; +} + +static void wlc_set_phy_uninitted(struct brcms_phy *pi) +{ + int i, j; + + pi->initialized = false; + + pi->tx_vos = 0xffff; + pi->nrssi_table_delta = 0x7fffffff; + pi->rc_cal = 0xffff; + pi->mintxbias = 0xffff; + pi->txpwridx = -1; + if (ISNPHY(pi)) { + pi->phy_spuravoid = SPURAVOID_DISABLE; + + if (NREV_GE(pi->pubpi.phy_rev, 3) + && NREV_LT(pi->pubpi.phy_rev, 7)) + pi->phy_spuravoid = SPURAVOID_AUTO; + + pi->nphy_papd_skip = 0; + pi->nphy_papd_epsilon_offset[0] = 0xf588; + pi->nphy_papd_epsilon_offset[1] = 0xf588; + pi->nphy_txpwr_idx[0] = 128; + pi->nphy_txpwr_idx[1] = 128; + pi->nphy_txpwrindex[0].index_internal = 40; + pi->nphy_txpwrindex[1].index_internal = 40; + pi->phy_pabias = 0; + } else { + pi->phy_spuravoid = SPURAVOID_AUTO; + } + pi->radiopwr = 0xffff; + for (i = 0; i < STATIC_NUM_RF; i++) { + for (j = 0; j < STATIC_NUM_BB; j++) + pi->stats_11b_txpower[i][j] = -1; + } +} + +struct shared_phy *wlc_phy_shared_attach(struct shared_phy_params *shp) +{ + struct shared_phy *sh; + + sh = kzalloc(sizeof(struct shared_phy), GFP_ATOMIC); + if (sh == NULL) + return NULL; + + sh->physhim = shp->physhim; + sh->unit = shp->unit; + sh->corerev = shp->corerev; + + sh->vid = shp->vid; + sh->did = shp->did; + sh->chip = shp->chip; + sh->chiprev = shp->chiprev; + sh->chippkg = shp->chippkg; + sh->sromrev = shp->sromrev; + sh->boardtype = shp->boardtype; + sh->boardrev = shp->boardrev; + sh->boardflags = shp->boardflags; + sh->boardflags2 = shp->boardflags2; + + sh->fast_timer = PHY_SW_TIMER_FAST; + sh->slow_timer = PHY_SW_TIMER_SLOW; + sh->glacial_timer = PHY_SW_TIMER_GLACIAL; + + sh->rssi_mode = RSSI_ANT_MERGE_MAX; + + return sh; +} + +static void wlc_phy_timercb_phycal(struct brcms_phy *pi) +{ + uint delay = 5; + + if (PHY_PERICAL_MPHASE_PENDING(pi)) { + if (!pi->sh->up) { + wlc_phy_cal_perical_mphase_reset(pi); + return; + } + + if (SCAN_RM_IN_PROGRESS(pi) || PLT_INPROG_PHY(pi)) { + + delay = 1000; + wlc_phy_cal_perical_mphase_restart(pi); + } else + wlc_phy_cal_perical_nphy_run(pi, PHY_PERICAL_AUTO); + wlapi_add_timer(pi->phycal_timer, delay, 0); + return; + } + +} + +static u32 wlc_phy_get_radio_ver(struct brcms_phy *pi) +{ + u32 ver; + + ver = read_radio_id(pi); + + return ver; +} + +struct brcms_phy_pub * +wlc_phy_attach(struct shared_phy *sh, struct bcma_device *d11core, + int bandtype, struct wiphy *wiphy) +{ + struct brcms_phy *pi; + u32 sflags = 0; + uint phyversion; + u32 idcode; + int i; + + if (D11REV_IS(sh->corerev, 4)) + sflags = SISF_2G_PHY | SISF_5G_PHY; + else + sflags = bcma_aread32(d11core, BCMA_IOST); + + if (bandtype == BRCM_BAND_5G) { + if ((sflags & (SISF_5G_PHY | SISF_DB_PHY)) == 0) + return NULL; + } + + pi = sh->phy_head; + if ((sflags & SISF_DB_PHY) && pi) { + wlapi_bmac_corereset(pi->sh->physhim, pi->pubpi.coreflags); + pi->refcnt++; + return &pi->pubpi_ro; + } + + pi = kzalloc(sizeof(struct brcms_phy), GFP_ATOMIC); + if (pi == NULL) + return NULL; + pi->wiphy = wiphy; + pi->d11core = d11core; + pi->sh = sh; + pi->phy_init_por = true; + pi->phy_wreg_limit = PHY_WREG_LIMIT; + + pi->txpwr_percent = 100; + + pi->do_initcal = true; + + pi->phycal_tempdelta = 0; + + if (bandtype == BRCM_BAND_2G && (sflags & SISF_2G_PHY)) + pi->pubpi.coreflags = SICF_GMODE; + + wlapi_bmac_corereset(pi->sh->physhim, pi->pubpi.coreflags); + phyversion = bcma_read16(pi->d11core, D11REGOFFS(phyversion)); + + pi->pubpi.phy_type = PHY_TYPE(phyversion); + pi->pubpi.phy_rev = phyversion & PV_PV_MASK; + + if (pi->pubpi.phy_type == PHY_TYPE_LCNXN) { + pi->pubpi.phy_type = PHY_TYPE_N; + pi->pubpi.phy_rev += LCNXN_BASEREV; + } + pi->pubpi.phy_corenum = PHY_CORE_NUM_2; + pi->pubpi.ana_rev = (phyversion & PV_AV_MASK) >> PV_AV_SHIFT; + + if (pi->pubpi.phy_type != PHY_TYPE_N && + pi->pubpi.phy_type != PHY_TYPE_LCN) + goto err; + + if (bandtype == BRCM_BAND_5G) { + if (!ISNPHY(pi)) + goto err; + } else if (!ISNPHY(pi) && !ISLCNPHY(pi)) { + goto err; + } + + wlc_phy_anacore((struct brcms_phy_pub *) pi, ON); + + idcode = wlc_phy_get_radio_ver(pi); + pi->pubpi.radioid = + (idcode & IDCODE_ID_MASK) >> IDCODE_ID_SHIFT; + pi->pubpi.radiorev = + (idcode & IDCODE_REV_MASK) >> IDCODE_REV_SHIFT; + pi->pubpi.radiover = + (idcode & IDCODE_VER_MASK) >> IDCODE_VER_SHIFT; + if (!VALID_RADIO(pi, pi->pubpi.radioid)) + goto err; + + wlc_phy_switch_radio((struct brcms_phy_pub *) pi, OFF); + + wlc_set_phy_uninitted(pi); + + pi->bw = WL_CHANSPEC_BW_20; + pi->radio_chanspec = (bandtype == BRCM_BAND_2G) ? + ch20mhz_chspec(1) : ch20mhz_chspec(36); + + pi->rxiq_samps = PHY_NOISE_SAMPLE_LOG_NUM_NPHY; + pi->rxiq_antsel = ANT_RX_DIV_DEF; + + pi->watchdog_override = true; + + pi->cal_type_override = PHY_PERICAL_AUTO; + + pi->nphy_saved_noisevars.bufcount = 0; + + if (ISNPHY(pi)) + pi->min_txpower = PHY_TXPWR_MIN_NPHY; + else + pi->min_txpower = PHY_TXPWR_MIN; + + pi->sh->phyrxchain = 0x3; + + pi->rx2tx_biasentry = -1; + + pi->phy_txcore_disable_temp = PHY_CHAIN_TX_DISABLE_TEMP; + pi->phy_txcore_enable_temp = + PHY_CHAIN_TX_DISABLE_TEMP - PHY_HYSTERESIS_DELTATEMP; + pi->phy_tempsense_offset = 0; + pi->phy_txcore_heatedup = false; + + pi->nphy_lastcal_temp = -50; + + pi->phynoise_polling = true; + if (ISNPHY(pi) || ISLCNPHY(pi)) + pi->phynoise_polling = false; + + for (i = 0; i < TXP_NUM_RATES; i++) { + pi->txpwr_limit[i] = BRCMS_TXPWR_MAX; + pi->txpwr_env_limit[i] = BRCMS_TXPWR_MAX; + pi->tx_user_target[i] = BRCMS_TXPWR_MAX; + } + + pi->radiopwr_override = RADIOPWR_OVERRIDE_DEF; + + pi->user_txpwr_at_rfport = false; + + if (ISNPHY(pi)) { + + pi->phycal_timer = wlapi_init_timer(pi->sh->physhim, + wlc_phy_timercb_phycal, + pi, "phycal"); + if (!pi->phycal_timer) + goto err; + + if (!wlc_phy_attach_nphy(pi)) + goto err; + + } else if (ISLCNPHY(pi)) { + if (!wlc_phy_attach_lcnphy(pi)) + goto err; + + } + + pi->refcnt++; + pi->next = pi->sh->phy_head; + sh->phy_head = pi; + + memcpy(&pi->pubpi_ro, &pi->pubpi, sizeof(struct brcms_phy_pub)); + + return &pi->pubpi_ro; + +err: + kfree(pi); + return NULL; +} + +void wlc_phy_detach(struct brcms_phy_pub *pih) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + + if (pih) { + if (--pi->refcnt) + return; + + if (pi->phycal_timer) { + wlapi_free_timer(pi->phycal_timer); + pi->phycal_timer = NULL; + } + + if (pi->sh->phy_head == pi) + pi->sh->phy_head = pi->next; + else if (pi->sh->phy_head->next == pi) + pi->sh->phy_head->next = NULL; + + if (pi->pi_fptr.detach) + (pi->pi_fptr.detach)(pi); + + kfree(pi); + } +} + +bool +wlc_phy_get_phyversion(struct brcms_phy_pub *pih, u16 *phytype, u16 *phyrev, + u16 *radioid, u16 *radiover) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + *phytype = (u16) pi->pubpi.phy_type; + *phyrev = (u16) pi->pubpi.phy_rev; + *radioid = pi->pubpi.radioid; + *radiover = pi->pubpi.radiorev; + + return true; +} + +bool wlc_phy_get_encore(struct brcms_phy_pub *pih) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + return pi->pubpi.abgphy_encore; +} + +u32 wlc_phy_get_coreflags(struct brcms_phy_pub *pih) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + return pi->pubpi.coreflags; +} + +void wlc_phy_anacore(struct brcms_phy_pub *pih, bool on) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + + if (ISNPHY(pi)) { + if (on) { + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + write_phy_reg(pi, 0xa6, 0x0d); + write_phy_reg(pi, 0x8f, 0x0); + write_phy_reg(pi, 0xa7, 0x0d); + write_phy_reg(pi, 0xa5, 0x0); + } else { + write_phy_reg(pi, 0xa5, 0x0); + } + } else { + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + write_phy_reg(pi, 0x8f, 0x07ff); + write_phy_reg(pi, 0xa6, 0x0fd); + write_phy_reg(pi, 0xa5, 0x07ff); + write_phy_reg(pi, 0xa7, 0x0fd); + } else { + write_phy_reg(pi, 0xa5, 0x7fff); + } + } + } else if (ISLCNPHY(pi)) { + if (on) { + and_phy_reg(pi, 0x43b, + ~((0x1 << 0) | (0x1 << 1) | (0x1 << 2))); + } else { + or_phy_reg(pi, 0x43c, + (0x1 << 0) | (0x1 << 1) | (0x1 << 2)); + or_phy_reg(pi, 0x43b, + (0x1 << 0) | (0x1 << 1) | (0x1 << 2)); + } + } +} + +u32 wlc_phy_clk_bwbits(struct brcms_phy_pub *pih) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + + u32 phy_bw_clkbits = 0; + + if (pi && (ISNPHY(pi) || ISLCNPHY(pi))) { + switch (pi->bw) { + case WL_CHANSPEC_BW_10: + phy_bw_clkbits = SICF_BW10; + break; + case WL_CHANSPEC_BW_20: + phy_bw_clkbits = SICF_BW20; + break; + case WL_CHANSPEC_BW_40: + phy_bw_clkbits = SICF_BW40; + break; + default: + break; + } + } + + return phy_bw_clkbits; +} + +void wlc_phy_por_inform(struct brcms_phy_pub *ppi) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + pi->phy_init_por = true; +} + +void wlc_phy_edcrs_lock(struct brcms_phy_pub *pih, bool lock) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + + pi->edcrs_threshold_lock = lock; + + write_phy_reg(pi, 0x22c, 0x46b); + write_phy_reg(pi, 0x22d, 0x46b); + write_phy_reg(pi, 0x22e, 0x3c0); + write_phy_reg(pi, 0x22f, 0x3c0); +} + +void wlc_phy_initcal_enable(struct brcms_phy_pub *pih, bool initcal) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + + pi->do_initcal = initcal; +} + +void wlc_phy_hw_clk_state_upd(struct brcms_phy_pub *pih, bool newstate) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + + if (!pi || !pi->sh) + return; + + pi->sh->clk = newstate; +} + +void wlc_phy_hw_state_upd(struct brcms_phy_pub *pih, bool newstate) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + + if (!pi || !pi->sh) + return; + + pi->sh->up = newstate; +} + +void wlc_phy_init(struct brcms_phy_pub *pih, u16 chanspec) +{ + u32 mc; + void (*phy_init)(struct brcms_phy *) = NULL; + struct brcms_phy *pi = (struct brcms_phy *) pih; + + if (pi->init_in_progress) + return; + + pi->init_in_progress = true; + + pi->radio_chanspec = chanspec; + + mc = bcma_read32(pi->d11core, D11REGOFFS(maccontrol)); + if (WARN(mc & MCTL_EN_MAC, "HW error MAC running on init")) + return; + + if (!(pi->measure_hold & PHY_HOLD_FOR_SCAN)) + pi->measure_hold |= PHY_HOLD_FOR_NOT_ASSOC; + + if (WARN(!(bcma_aread32(pi->d11core, BCMA_IOST) & SISF_FCLKA), + "HW error SISF_FCLKA\n")) + return; + + phy_init = pi->pi_fptr.init; + + if (phy_init == NULL) + return; + + wlc_phy_anacore(pih, ON); + + if (CHSPEC_BW(pi->radio_chanspec) != pi->bw) + wlapi_bmac_bw_set(pi->sh->physhim, + CHSPEC_BW(pi->radio_chanspec)); + + pi->nphy_gain_boost = true; + + wlc_phy_switch_radio((struct brcms_phy_pub *) pi, ON); + + (*phy_init)(pi); + + pi->phy_init_por = false; + + if (D11REV_IS(pi->sh->corerev, 11) || D11REV_IS(pi->sh->corerev, 12)) + wlc_phy_do_dummy_tx(pi, true, OFF); + + if (!(ISNPHY(pi))) + wlc_phy_txpower_update_shm(pi); + + wlc_phy_ant_rxdiv_set((struct brcms_phy_pub *) pi, pi->sh->rx_antdiv); + + pi->init_in_progress = false; +} + +void wlc_phy_cal_init(struct brcms_phy_pub *pih) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + void (*cal_init)(struct brcms_phy *) = NULL; + + if (WARN((bcma_read32(pi->d11core, D11REGOFFS(maccontrol)) & + MCTL_EN_MAC) != 0, "HW error: MAC enabled during phy cal\n")) + return; + + if (!pi->initialized) { + cal_init = pi->pi_fptr.calinit; + if (cal_init) + (*cal_init)(pi); + + pi->initialized = true; + } +} + +int wlc_phy_down(struct brcms_phy_pub *pih) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + int callbacks = 0; + + if (pi->phycal_timer + && !wlapi_del_timer(pi->phycal_timer)) + callbacks++; + + pi->nphy_iqcal_chanspec_2G = 0; + pi->nphy_iqcal_chanspec_5G = 0; + + return callbacks; +} + +void +wlc_phy_table_addr(struct brcms_phy *pi, uint tbl_id, uint tbl_offset, + u16 tblAddr, u16 tblDataHi, u16 tblDataLo) +{ + write_phy_reg(pi, tblAddr, (tbl_id << 10) | tbl_offset); + + pi->tbl_data_hi = tblDataHi; + pi->tbl_data_lo = tblDataLo; + + if (pi->sh->chip == BCMA_CHIP_ID_BCM43224 && + pi->sh->chiprev == 1) { + pi->tbl_addr = tblAddr; + pi->tbl_save_id = tbl_id; + pi->tbl_save_offset = tbl_offset; + } +} + +void wlc_phy_table_data_write(struct brcms_phy *pi, uint width, u32 val) +{ + if ((pi->sh->chip == BCMA_CHIP_ID_BCM43224) && + (pi->sh->chiprev == 1) && + (pi->tbl_save_id == NPHY_TBL_ID_ANTSWCTRLLUT)) { + read_phy_reg(pi, pi->tbl_data_lo); + + write_phy_reg(pi, pi->tbl_addr, + (pi->tbl_save_id << 10) | pi->tbl_save_offset); + pi->tbl_save_offset++; + } + + if (width == 32) { + write_phy_reg(pi, pi->tbl_data_hi, (u16) (val >> 16)); + write_phy_reg(pi, pi->tbl_data_lo, (u16) val); + } else { + write_phy_reg(pi, pi->tbl_data_lo, (u16) val); + } +} + +void +wlc_phy_write_table(struct brcms_phy *pi, const struct phytbl_info *ptbl_info, + u16 tblAddr, u16 tblDataHi, u16 tblDataLo) +{ + uint idx; + uint tbl_id = ptbl_info->tbl_id; + uint tbl_offset = ptbl_info->tbl_offset; + uint tbl_width = ptbl_info->tbl_width; + const u8 *ptbl_8b = (const u8 *)ptbl_info->tbl_ptr; + const u16 *ptbl_16b = (const u16 *)ptbl_info->tbl_ptr; + const u32 *ptbl_32b = (const u32 *)ptbl_info->tbl_ptr; + + write_phy_reg(pi, tblAddr, (tbl_id << 10) | tbl_offset); + + for (idx = 0; idx < ptbl_info->tbl_len; idx++) { + + if ((pi->sh->chip == BCMA_CHIP_ID_BCM43224) && + (pi->sh->chiprev == 1) && + (tbl_id == NPHY_TBL_ID_ANTSWCTRLLUT)) { + read_phy_reg(pi, tblDataLo); + + write_phy_reg(pi, tblAddr, + (tbl_id << 10) | (tbl_offset + idx)); + } + + if (tbl_width == 32) { + write_phy_reg(pi, tblDataHi, + (u16) (ptbl_32b[idx] >> 16)); + write_phy_reg(pi, tblDataLo, (u16) ptbl_32b[idx]); + } else if (tbl_width == 16) { + write_phy_reg(pi, tblDataLo, ptbl_16b[idx]); + } else { + write_phy_reg(pi, tblDataLo, ptbl_8b[idx]); + } + } +} + +void +wlc_phy_read_table(struct brcms_phy *pi, const struct phytbl_info *ptbl_info, + u16 tblAddr, u16 tblDataHi, u16 tblDataLo) +{ + uint idx; + uint tbl_id = ptbl_info->tbl_id; + uint tbl_offset = ptbl_info->tbl_offset; + uint tbl_width = ptbl_info->tbl_width; + u8 *ptbl_8b = (u8 *)ptbl_info->tbl_ptr; + u16 *ptbl_16b = (u16 *)ptbl_info->tbl_ptr; + u32 *ptbl_32b = (u32 *)ptbl_info->tbl_ptr; + + write_phy_reg(pi, tblAddr, (tbl_id << 10) | tbl_offset); + + for (idx = 0; idx < ptbl_info->tbl_len; idx++) { + + if ((pi->sh->chip == BCMA_CHIP_ID_BCM43224) && + (pi->sh->chiprev == 1)) { + (void)read_phy_reg(pi, tblDataLo); + + write_phy_reg(pi, tblAddr, + (tbl_id << 10) | (tbl_offset + idx)); + } + + if (tbl_width == 32) { + ptbl_32b[idx] = read_phy_reg(pi, tblDataLo); + ptbl_32b[idx] |= (read_phy_reg(pi, tblDataHi) << 16); + } else if (tbl_width == 16) { + ptbl_16b[idx] = read_phy_reg(pi, tblDataLo); + } else { + ptbl_8b[idx] = (u8) read_phy_reg(pi, tblDataLo); + } + } +} + +uint +wlc_phy_init_radio_regs_allbands(struct brcms_phy *pi, + struct radio_20xx_regs *radioregs) +{ + uint i = 0; + + do { + if (radioregs[i].do_init) + write_radio_reg(pi, radioregs[i].address, + (u16) radioregs[i].init); + + i++; + } while (radioregs[i].address != 0xffff); + + return i; +} + +uint +wlc_phy_init_radio_regs(struct brcms_phy *pi, + const struct radio_regs *radioregs, + u16 core_offset) +{ + uint i = 0; + uint count = 0; + + do { + if (CHSPEC_IS5G(pi->radio_chanspec)) { + if (radioregs[i].do_init_a) { + write_radio_reg(pi, + radioregs[i]. + address | core_offset, + (u16) radioregs[i].init_a); + if (ISNPHY(pi) && (++count % 4 == 0)) + BRCMS_PHY_WAR_PR51571(pi); + } + } else { + if (radioregs[i].do_init_g) { + write_radio_reg(pi, + radioregs[i]. + address | core_offset, + (u16) radioregs[i].init_g); + if (ISNPHY(pi) && (++count % 4 == 0)) + BRCMS_PHY_WAR_PR51571(pi); + } + } + + i++; + } while (radioregs[i].address != 0xffff); + + return i; +} + +void wlc_phy_do_dummy_tx(struct brcms_phy *pi, bool ofdm, bool pa_on) +{ +#define DUMMY_PKT_LEN 20 + struct bcma_device *core = pi->d11core; + int i, count; + u8 ofdmpkt[DUMMY_PKT_LEN] = { + 0xcc, 0x01, 0x02, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 + }; + u8 cckpkt[DUMMY_PKT_LEN] = { + 0x6e, 0x84, 0x0b, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 + }; + u32 *dummypkt; + + dummypkt = (u32 *) (ofdm ? ofdmpkt : cckpkt); + wlapi_bmac_write_template_ram(pi->sh->physhim, 0, DUMMY_PKT_LEN, + dummypkt); + + bcma_write16(core, D11REGOFFS(xmtsel), 0); + + if (D11REV_GE(pi->sh->corerev, 11)) + bcma_write16(core, D11REGOFFS(wepctl), 0x100); + else + bcma_write16(core, D11REGOFFS(wepctl), 0); + + bcma_write16(core, D11REGOFFS(txe_phyctl), + (ofdm ? 1 : 0) | PHY_TXC_ANT_0); + if (ISNPHY(pi) || ISLCNPHY(pi)) + bcma_write16(core, D11REGOFFS(txe_phyctl1), 0x1A02); + + bcma_write16(core, D11REGOFFS(txe_wm_0), 0); + bcma_write16(core, D11REGOFFS(txe_wm_1), 0); + + bcma_write16(core, D11REGOFFS(xmttplatetxptr), 0); + bcma_write16(core, D11REGOFFS(xmttxcnt), DUMMY_PKT_LEN); + + bcma_write16(core, D11REGOFFS(xmtsel), + ((8 << 8) | (1 << 5) | (1 << 2) | 2)); + + bcma_write16(core, D11REGOFFS(txe_ctl), 0); + + if (!pa_on) { + if (ISNPHY(pi)) + wlc_phy_pa_override_nphy(pi, OFF); + } + + if (ISNPHY(pi) || ISLCNPHY(pi)) + bcma_write16(core, D11REGOFFS(txe_aux), 0xD0); + else + bcma_write16(core, D11REGOFFS(txe_aux), ((1 << 5) | (1 << 4))); + + (void)bcma_read16(core, D11REGOFFS(txe_aux)); + + i = 0; + count = ofdm ? 30 : 250; + while ((i++ < count) + && (bcma_read16(core, D11REGOFFS(txe_status)) & (1 << 7))) + udelay(10); + + i = 0; + + while ((i++ < 10) && + ((bcma_read16(core, D11REGOFFS(txe_status)) & (1 << 10)) == 0)) + udelay(10); + + i = 0; + + while ((i++ < 10) && + ((bcma_read16(core, D11REGOFFS(ifsstat)) & (1 << 8)))) + udelay(10); + + if (!pa_on) { + if (ISNPHY(pi)) + wlc_phy_pa_override_nphy(pi, ON); + } +} + +void wlc_phy_hold_upd(struct brcms_phy_pub *pih, u32 id, bool set) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + + if (set) + mboolset(pi->measure_hold, id); + else + mboolclr(pi->measure_hold, id); + + return; +} + +void wlc_phy_mute_upd(struct brcms_phy_pub *pih, bool mute, u32 flags) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + + if (mute) + mboolset(pi->measure_hold, PHY_HOLD_FOR_MUTE); + else + mboolclr(pi->measure_hold, PHY_HOLD_FOR_MUTE); + + if (!mute && (flags & PHY_MUTE_FOR_PREISM)) + pi->nphy_perical_last = pi->sh->now - pi->sh->glacial_timer; + return; +} + +void wlc_phy_clear_tssi(struct brcms_phy_pub *pih) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + + if (ISNPHY(pi)) { + return; + } else { + wlapi_bmac_write_shm(pi->sh->physhim, M_B_TSSI_0, NULL_TSSI_W); + wlapi_bmac_write_shm(pi->sh->physhim, M_B_TSSI_1, NULL_TSSI_W); + wlapi_bmac_write_shm(pi->sh->physhim, M_G_TSSI_0, NULL_TSSI_W); + wlapi_bmac_write_shm(pi->sh->physhim, M_G_TSSI_1, NULL_TSSI_W); + } +} + +static bool wlc_phy_cal_txpower_recalc_sw(struct brcms_phy *pi) +{ + return false; +} + +void wlc_phy_switch_radio(struct brcms_phy_pub *pih, bool on) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + (void)bcma_read32(pi->d11core, D11REGOFFS(maccontrol)); + + if (ISNPHY(pi)) { + wlc_phy_switch_radio_nphy(pi, on); + } else if (ISLCNPHY(pi)) { + if (on) { + and_phy_reg(pi, 0x44c, + ~((0x1 << 8) | + (0x1 << 9) | + (0x1 << 10) | (0x1 << 11) | (0x1 << 12))); + and_phy_reg(pi, 0x4b0, ~((0x1 << 3) | (0x1 << 11))); + and_phy_reg(pi, 0x4f9, ~(0x1 << 3)); + } else { + and_phy_reg(pi, 0x44d, + ~((0x1 << 10) | + (0x1 << 11) | + (0x1 << 12) | (0x1 << 13) | (0x1 << 14))); + or_phy_reg(pi, 0x44c, + (0x1 << 8) | + (0x1 << 9) | + (0x1 << 10) | (0x1 << 11) | (0x1 << 12)); + + and_phy_reg(pi, 0x4b7, ~((0x7f << 8))); + and_phy_reg(pi, 0x4b1, ~((0x1 << 13))); + or_phy_reg(pi, 0x4b0, (0x1 << 3) | (0x1 << 11)); + and_phy_reg(pi, 0x4fa, ~((0x1 << 3))); + or_phy_reg(pi, 0x4f9, (0x1 << 3)); + } + } +} + +u16 wlc_phy_bw_state_get(struct brcms_phy_pub *ppi) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + return pi->bw; +} + +void wlc_phy_bw_state_set(struct brcms_phy_pub *ppi, u16 bw) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + pi->bw = bw; +} + +void wlc_phy_chanspec_radio_set(struct brcms_phy_pub *ppi, u16 newch) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + pi->radio_chanspec = newch; + +} + +u16 wlc_phy_chanspec_get(struct brcms_phy_pub *ppi) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + return pi->radio_chanspec; +} + +void wlc_phy_chanspec_set(struct brcms_phy_pub *ppi, u16 chanspec) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + u16 m_cur_channel; + void (*chanspec_set)(struct brcms_phy *, u16) = NULL; + m_cur_channel = CHSPEC_CHANNEL(chanspec); + if (CHSPEC_IS5G(chanspec)) + m_cur_channel |= D11_CURCHANNEL_5G; + if (CHSPEC_IS40(chanspec)) + m_cur_channel |= D11_CURCHANNEL_40; + wlapi_bmac_write_shm(pi->sh->physhim, M_CURCHANNEL, m_cur_channel); + + chanspec_set = pi->pi_fptr.chanset; + if (chanspec_set) + (*chanspec_set)(pi, chanspec); + +} + +int wlc_phy_chanspec_freq2bandrange_lpssn(uint freq) +{ + int range = -1; + + if (freq < 2500) + range = WL_CHAN_FREQ_RANGE_2G; + else if (freq <= 5320) + range = WL_CHAN_FREQ_RANGE_5GL; + else if (freq <= 5700) + range = WL_CHAN_FREQ_RANGE_5GM; + else + range = WL_CHAN_FREQ_RANGE_5GH; + + return range; +} + +int wlc_phy_chanspec_bandrange_get(struct brcms_phy *pi, u16 chanspec) +{ + int range = -1; + uint channel = CHSPEC_CHANNEL(chanspec); + uint freq = wlc_phy_channel2freq(channel); + + if (ISNPHY(pi)) + range = wlc_phy_get_chan_freq_range_nphy(pi, channel); + else if (ISLCNPHY(pi)) + range = wlc_phy_chanspec_freq2bandrange_lpssn(freq); + + return range; +} + +void wlc_phy_chanspec_ch14_widefilter_set(struct brcms_phy_pub *ppi, + bool wide_filter) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + pi->channel_14_wide_filter = wide_filter; + +} + +int wlc_phy_channel2freq(uint channel) +{ + uint i; + + for (i = 0; i < ARRAY_SIZE(chan_info_all); i++) + if (chan_info_all[i].chan == channel) + return chan_info_all[i].freq; + return 0; +} + +void +wlc_phy_chanspec_band_validch(struct brcms_phy_pub *ppi, uint band, + struct brcms_chanvec *channels) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + uint i; + uint channel; + + memset(channels, 0, sizeof(struct brcms_chanvec)); + + for (i = 0; i < ARRAY_SIZE(chan_info_all); i++) { + channel = chan_info_all[i].chan; + + if ((pi->a_band_high_disable) && (channel >= FIRST_REF5_CHANNUM) + && (channel <= LAST_REF5_CHANNUM)) + continue; + + if ((band == BRCM_BAND_2G && channel <= CH_MAX_2G_CHANNEL) || + (band == BRCM_BAND_5G && channel > CH_MAX_2G_CHANNEL)) + setbit(channels->vec, channel); + } +} + +u16 wlc_phy_chanspec_band_firstch(struct brcms_phy_pub *ppi, uint band) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + uint i; + uint channel; + u16 chspec; + + for (i = 0; i < ARRAY_SIZE(chan_info_all); i++) { + channel = chan_info_all[i].chan; + + if (ISNPHY(pi) && pi->bw == WL_CHANSPEC_BW_40) { + uint j; + + for (j = 0; j < ARRAY_SIZE(chan_info_all); j++) { + if (chan_info_all[j].chan == + channel + CH_10MHZ_APART) + break; + } + + if (j == ARRAY_SIZE(chan_info_all)) + continue; + + channel = upper_20_sb(channel); + chspec = channel | WL_CHANSPEC_BW_40 | + WL_CHANSPEC_CTL_SB_LOWER; + if (band == BRCM_BAND_2G) + chspec |= WL_CHANSPEC_BAND_2G; + else + chspec |= WL_CHANSPEC_BAND_5G; + } else + chspec = ch20mhz_chspec(channel); + + if ((pi->a_band_high_disable) && (channel >= FIRST_REF5_CHANNUM) + && (channel <= LAST_REF5_CHANNUM)) + continue; + + if ((band == BRCM_BAND_2G && channel <= CH_MAX_2G_CHANNEL) || + (band == BRCM_BAND_5G && channel > CH_MAX_2G_CHANNEL)) + return chspec; + } + + return (u16) INVCHANSPEC; +} + +int wlc_phy_txpower_get(struct brcms_phy_pub *ppi, uint *qdbm, bool *override) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + *qdbm = pi->tx_user_target[0]; + if (override != NULL) + *override = pi->txpwroverride; + return 0; +} + +void wlc_phy_txpower_target_set(struct brcms_phy_pub *ppi, + struct txpwr_limits *txpwr) +{ + bool mac_enabled = false; + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + memcpy(&pi->tx_user_target[TXP_FIRST_CCK], + &txpwr->cck[0], BRCMS_NUM_RATES_CCK); + + memcpy(&pi->tx_user_target[TXP_FIRST_OFDM], + &txpwr->ofdm[0], BRCMS_NUM_RATES_OFDM); + memcpy(&pi->tx_user_target[TXP_FIRST_OFDM_20_CDD], + &txpwr->ofdm_cdd[0], BRCMS_NUM_RATES_OFDM); + + memcpy(&pi->tx_user_target[TXP_FIRST_OFDM_40_SISO], + &txpwr->ofdm_40_siso[0], BRCMS_NUM_RATES_OFDM); + memcpy(&pi->tx_user_target[TXP_FIRST_OFDM_40_CDD], + &txpwr->ofdm_40_cdd[0], BRCMS_NUM_RATES_OFDM); + + memcpy(&pi->tx_user_target[TXP_FIRST_MCS_20_SISO], + &txpwr->mcs_20_siso[0], BRCMS_NUM_RATES_MCS_1_STREAM); + memcpy(&pi->tx_user_target[TXP_FIRST_MCS_20_CDD], + &txpwr->mcs_20_cdd[0], BRCMS_NUM_RATES_MCS_1_STREAM); + memcpy(&pi->tx_user_target[TXP_FIRST_MCS_20_STBC], + &txpwr->mcs_20_stbc[0], BRCMS_NUM_RATES_MCS_1_STREAM); + memcpy(&pi->tx_user_target[TXP_FIRST_MCS_20_SDM], + &txpwr->mcs_20_mimo[0], BRCMS_NUM_RATES_MCS_2_STREAM); + + memcpy(&pi->tx_user_target[TXP_FIRST_MCS_40_SISO], + &txpwr->mcs_40_siso[0], BRCMS_NUM_RATES_MCS_1_STREAM); + memcpy(&pi->tx_user_target[TXP_FIRST_MCS_40_CDD], + &txpwr->mcs_40_cdd[0], BRCMS_NUM_RATES_MCS_1_STREAM); + memcpy(&pi->tx_user_target[TXP_FIRST_MCS_40_STBC], + &txpwr->mcs_40_stbc[0], BRCMS_NUM_RATES_MCS_1_STREAM); + memcpy(&pi->tx_user_target[TXP_FIRST_MCS_40_SDM], + &txpwr->mcs_40_mimo[0], BRCMS_NUM_RATES_MCS_2_STREAM); + + if (bcma_read32(pi->d11core, D11REGOFFS(maccontrol)) & MCTL_EN_MAC) + mac_enabled = true; + + if (mac_enabled) + wlapi_suspend_mac_and_wait(pi->sh->physhim); + + wlc_phy_txpower_recalc_target(pi); + wlc_phy_cal_txpower_recalc_sw(pi); + + if (mac_enabled) + wlapi_enable_mac(pi->sh->physhim); +} + +int wlc_phy_txpower_set(struct brcms_phy_pub *ppi, uint qdbm, bool override) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + int i; + + if (qdbm > 127) + return -EINVAL; + + for (i = 0; i < TXP_NUM_RATES; i++) + pi->tx_user_target[i] = (u8) qdbm; + + pi->txpwroverride = false; + + if (pi->sh->up) { + if (!SCAN_INPROG_PHY(pi)) { + bool suspend; + + suspend = (0 == (bcma_read32(pi->d11core, + D11REGOFFS(maccontrol)) & + MCTL_EN_MAC)); + + if (!suspend) + wlapi_suspend_mac_and_wait(pi->sh->physhim); + + wlc_phy_txpower_recalc_target(pi); + wlc_phy_cal_txpower_recalc_sw(pi); + + if (!suspend) + wlapi_enable_mac(pi->sh->physhim); + } + } + return 0; +} + +void +wlc_phy_txpower_sromlimit(struct brcms_phy_pub *ppi, uint channel, u8 *min_pwr, + u8 *max_pwr, int txp_rate_idx) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + uint i; + + *min_pwr = pi->min_txpower * BRCMS_TXPWR_DB_FACTOR; + + if (ISNPHY(pi)) { + if (txp_rate_idx < 0) + txp_rate_idx = TXP_FIRST_CCK; + wlc_phy_txpower_sromlimit_get_nphy(pi, channel, max_pwr, + (u8) txp_rate_idx); + + } else if ((channel <= CH_MAX_2G_CHANNEL)) { + if (txp_rate_idx < 0) + txp_rate_idx = TXP_FIRST_CCK; + *max_pwr = pi->tx_srom_max_rate_2g[txp_rate_idx]; + } else { + + *max_pwr = BRCMS_TXPWR_MAX; + + if (txp_rate_idx < 0) + txp_rate_idx = TXP_FIRST_OFDM; + + for (i = 0; i < ARRAY_SIZE(chan_info_all); i++) { + if (channel == chan_info_all[i].chan) + break; + } + + if (pi->hwtxpwr) { + *max_pwr = pi->hwtxpwr[i]; + } else { + + if ((i >= FIRST_MID_5G_CHAN) && (i <= LAST_MID_5G_CHAN)) + *max_pwr = + pi->tx_srom_max_rate_5g_mid[txp_rate_idx]; + if ((i >= FIRST_HIGH_5G_CHAN) + && (i <= LAST_HIGH_5G_CHAN)) + *max_pwr = + pi->tx_srom_max_rate_5g_hi[txp_rate_idx]; + if ((i >= FIRST_LOW_5G_CHAN) && (i <= LAST_LOW_5G_CHAN)) + *max_pwr = + pi->tx_srom_max_rate_5g_low[txp_rate_idx]; + } + } +} + +void +wlc_phy_txpower_sromlimit_max_get(struct brcms_phy_pub *ppi, uint chan, + u8 *max_txpwr, u8 *min_txpwr) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + u8 tx_pwr_max = 0; + u8 tx_pwr_min = 255; + u8 max_num_rate; + u8 maxtxpwr, mintxpwr, rate, pactrl; + + pactrl = 0; + + max_num_rate = ISNPHY(pi) ? TXP_NUM_RATES : + ISLCNPHY(pi) ? (TXP_LAST_SISO_MCS_20 + + 1) : (TXP_LAST_OFDM + 1); + + for (rate = 0; rate < max_num_rate; rate++) { + + wlc_phy_txpower_sromlimit(ppi, chan, &mintxpwr, &maxtxpwr, + rate); + + maxtxpwr = (maxtxpwr > pactrl) ? (maxtxpwr - pactrl) : 0; + + maxtxpwr = (maxtxpwr > 6) ? (maxtxpwr - 6) : 0; + + tx_pwr_max = max(tx_pwr_max, maxtxpwr); + tx_pwr_min = min(tx_pwr_min, maxtxpwr); + } + *max_txpwr = tx_pwr_max; + *min_txpwr = tx_pwr_min; +} + +void +wlc_phy_txpower_boardlimit_band(struct brcms_phy_pub *ppi, uint bandunit, + s32 *max_pwr, s32 *min_pwr, u32 *step_pwr) +{ + return; +} + +u8 wlc_phy_txpower_get_target_min(struct brcms_phy_pub *ppi) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + return pi->tx_power_min; +} + +u8 wlc_phy_txpower_get_target_max(struct brcms_phy_pub *ppi) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + return pi->tx_power_max; +} + +static s8 wlc_phy_env_measure_vbat(struct brcms_phy *pi) +{ + if (ISLCNPHY(pi)) + return wlc_lcnphy_vbatsense(pi, 0); + else + return 0; +} + +static s8 wlc_phy_env_measure_temperature(struct brcms_phy *pi) +{ + if (ISLCNPHY(pi)) + return wlc_lcnphy_tempsense_degree(pi, 0); + else + return 0; +} + +static void wlc_phy_upd_env_txpwr_rate_limits(struct brcms_phy *pi, u32 band) +{ + u8 i; + s8 temp, vbat; + + for (i = 0; i < TXP_NUM_RATES; i++) + pi->txpwr_env_limit[i] = BRCMS_TXPWR_MAX; + + vbat = wlc_phy_env_measure_vbat(pi); + temp = wlc_phy_env_measure_temperature(pi); + +} + +static s8 +wlc_user_txpwr_antport_to_rfport(struct brcms_phy *pi, uint chan, u32 band, + u8 rate) +{ + s8 offset = 0; + + if (!pi->user_txpwr_at_rfport) + return offset; + return offset; +} + +void wlc_phy_txpower_recalc_target(struct brcms_phy *pi) +{ + u8 maxtxpwr, mintxpwr, rate, pactrl; + uint target_chan; + u8 tx_pwr_target[TXP_NUM_RATES]; + u8 tx_pwr_max = 0; + u8 tx_pwr_min = 255; + u8 tx_pwr_max_rate_ind = 0; + u8 max_num_rate; + u8 start_rate = 0; + u16 chspec; + u32 band = CHSPEC2BAND(pi->radio_chanspec); + void (*txpwr_recalc_fn)(struct brcms_phy *) = NULL; + + chspec = pi->radio_chanspec; + if (CHSPEC_CTL_SB(chspec) == WL_CHANSPEC_CTL_SB_NONE) + target_chan = CHSPEC_CHANNEL(chspec); + else if (CHSPEC_CTL_SB(chspec) == WL_CHANSPEC_CTL_SB_UPPER) + target_chan = upper_20_sb(CHSPEC_CHANNEL(chspec)); + else + target_chan = lower_20_sb(CHSPEC_CHANNEL(chspec)); + + pactrl = 0; + if (ISLCNPHY(pi)) { + u32 offset_mcs, i; + + if (CHSPEC_IS40(pi->radio_chanspec)) { + offset_mcs = pi->mcs40_po; + for (i = TXP_FIRST_SISO_MCS_20; + i <= TXP_LAST_SISO_MCS_20; i++) { + pi->tx_srom_max_rate_2g[i - 8] = + pi->tx_srom_max_2g - + ((offset_mcs & 0xf) * 2); + offset_mcs >>= 4; + } + } else { + offset_mcs = pi->mcs20_po; + for (i = TXP_FIRST_SISO_MCS_20; + i <= TXP_LAST_SISO_MCS_20; i++) { + pi->tx_srom_max_rate_2g[i - 8] = + pi->tx_srom_max_2g - + ((offset_mcs & 0xf) * 2); + offset_mcs >>= 4; + } + } + } + + max_num_rate = ((ISNPHY(pi)) ? (TXP_NUM_RATES) : + ((ISLCNPHY(pi)) ? + (TXP_LAST_SISO_MCS_20 + 1) : (TXP_LAST_OFDM + 1))); + + wlc_phy_upd_env_txpwr_rate_limits(pi, band); + + for (rate = start_rate; rate < max_num_rate; rate++) { + + tx_pwr_target[rate] = pi->tx_user_target[rate]; + + if (pi->user_txpwr_at_rfport) + tx_pwr_target[rate] += + wlc_user_txpwr_antport_to_rfport(pi, + target_chan, + band, + rate); + + wlc_phy_txpower_sromlimit((struct brcms_phy_pub *) pi, + target_chan, + &mintxpwr, &maxtxpwr, rate); + + maxtxpwr = min(maxtxpwr, pi->txpwr_limit[rate]); + + maxtxpwr = (maxtxpwr > pactrl) ? (maxtxpwr - pactrl) : 0; + + maxtxpwr = (maxtxpwr > 6) ? (maxtxpwr - 6) : 0; + + maxtxpwr = min(maxtxpwr, tx_pwr_target[rate]); + + if (pi->txpwr_percent <= 100) + maxtxpwr = (maxtxpwr * pi->txpwr_percent) / 100; + + tx_pwr_target[rate] = max(maxtxpwr, mintxpwr); + + tx_pwr_target[rate] = + min(tx_pwr_target[rate], pi->txpwr_env_limit[rate]); + + if (tx_pwr_target[rate] > tx_pwr_max) + tx_pwr_max_rate_ind = rate; + + tx_pwr_max = max(tx_pwr_max, tx_pwr_target[rate]); + tx_pwr_min = min(tx_pwr_min, tx_pwr_target[rate]); + } + + memset(pi->tx_power_offset, 0, sizeof(pi->tx_power_offset)); + pi->tx_power_max = tx_pwr_max; + pi->tx_power_min = tx_pwr_min; + pi->tx_power_max_rate_ind = tx_pwr_max_rate_ind; + for (rate = 0; rate < max_num_rate; rate++) { + + pi->tx_power_target[rate] = tx_pwr_target[rate]; + + if (!pi->hwpwrctrl || ISNPHY(pi)) + pi->tx_power_offset[rate] = + pi->tx_power_max - pi->tx_power_target[rate]; + else + pi->tx_power_offset[rate] = + pi->tx_power_target[rate] - pi->tx_power_min; + } + + txpwr_recalc_fn = pi->pi_fptr.txpwrrecalc; + if (txpwr_recalc_fn) + (*txpwr_recalc_fn)(pi); +} + +static void +wlc_phy_txpower_reg_limit_calc(struct brcms_phy *pi, struct txpwr_limits *txpwr, + u16 chanspec) +{ + u8 tmp_txpwr_limit[2 * BRCMS_NUM_RATES_OFDM]; + u8 *txpwr_ptr1 = NULL, *txpwr_ptr2 = NULL; + int rate_start_index = 0, rate1, rate2, k; + + for (rate1 = WL_TX_POWER_CCK_FIRST, rate2 = 0; + rate2 < WL_TX_POWER_CCK_NUM; rate1++, rate2++) + pi->txpwr_limit[rate1] = txpwr->cck[rate2]; + + for (rate1 = WL_TX_POWER_OFDM_FIRST, rate2 = 0; + rate2 < WL_TX_POWER_OFDM_NUM; rate1++, rate2++) + pi->txpwr_limit[rate1] = txpwr->ofdm[rate2]; + + if (ISNPHY(pi)) { + + for (k = 0; k < 4; k++) { + switch (k) { + case 0: + + txpwr_ptr1 = txpwr->mcs_20_siso; + txpwr_ptr2 = txpwr->ofdm; + rate_start_index = WL_TX_POWER_OFDM_FIRST; + break; + case 1: + + txpwr_ptr1 = txpwr->mcs_20_cdd; + txpwr_ptr2 = txpwr->ofdm_cdd; + rate_start_index = WL_TX_POWER_OFDM20_CDD_FIRST; + break; + case 2: + + txpwr_ptr1 = txpwr->mcs_40_siso; + txpwr_ptr2 = txpwr->ofdm_40_siso; + rate_start_index = + WL_TX_POWER_OFDM40_SISO_FIRST; + break; + case 3: + + txpwr_ptr1 = txpwr->mcs_40_cdd; + txpwr_ptr2 = txpwr->ofdm_40_cdd; + rate_start_index = WL_TX_POWER_OFDM40_CDD_FIRST; + break; + } + + for (rate2 = 0; rate2 < BRCMS_NUM_RATES_OFDM; + rate2++) { + tmp_txpwr_limit[rate2] = 0; + tmp_txpwr_limit[BRCMS_NUM_RATES_OFDM + rate2] = + txpwr_ptr1[rate2]; + } + wlc_phy_mcs_to_ofdm_powers_nphy( + tmp_txpwr_limit, 0, + BRCMS_NUM_RATES_OFDM - + 1, BRCMS_NUM_RATES_OFDM); + for (rate1 = rate_start_index, rate2 = 0; + rate2 < BRCMS_NUM_RATES_OFDM; rate1++, rate2++) + pi->txpwr_limit[rate1] = + min(txpwr_ptr2[rate2], + tmp_txpwr_limit[rate2]); + } + + for (k = 0; k < 4; k++) { + switch (k) { + case 0: + + txpwr_ptr1 = txpwr->ofdm; + txpwr_ptr2 = txpwr->mcs_20_siso; + rate_start_index = WL_TX_POWER_MCS20_SISO_FIRST; + break; + case 1: + + txpwr_ptr1 = txpwr->ofdm_cdd; + txpwr_ptr2 = txpwr->mcs_20_cdd; + rate_start_index = WL_TX_POWER_MCS20_CDD_FIRST; + break; + case 2: + + txpwr_ptr1 = txpwr->ofdm_40_siso; + txpwr_ptr2 = txpwr->mcs_40_siso; + rate_start_index = WL_TX_POWER_MCS40_SISO_FIRST; + break; + case 3: + + txpwr_ptr1 = txpwr->ofdm_40_cdd; + txpwr_ptr2 = txpwr->mcs_40_cdd; + rate_start_index = WL_TX_POWER_MCS40_CDD_FIRST; + break; + } + for (rate2 = 0; rate2 < BRCMS_NUM_RATES_OFDM; + rate2++) { + tmp_txpwr_limit[rate2] = 0; + tmp_txpwr_limit[BRCMS_NUM_RATES_OFDM + rate2] = + txpwr_ptr1[rate2]; + } + wlc_phy_ofdm_to_mcs_powers_nphy( + tmp_txpwr_limit, 0, + BRCMS_NUM_RATES_OFDM - + 1, BRCMS_NUM_RATES_OFDM); + for (rate1 = rate_start_index, rate2 = 0; + rate2 < BRCMS_NUM_RATES_MCS_1_STREAM; + rate1++, rate2++) + pi->txpwr_limit[rate1] = + min(txpwr_ptr2[rate2], + tmp_txpwr_limit[rate2]); + } + + for (k = 0; k < 2; k++) { + switch (k) { + case 0: + + rate_start_index = WL_TX_POWER_MCS20_STBC_FIRST; + txpwr_ptr1 = txpwr->mcs_20_stbc; + break; + case 1: + + rate_start_index = WL_TX_POWER_MCS40_STBC_FIRST; + txpwr_ptr1 = txpwr->mcs_40_stbc; + break; + } + for (rate1 = rate_start_index, rate2 = 0; + rate2 < BRCMS_NUM_RATES_MCS_1_STREAM; + rate1++, rate2++) + pi->txpwr_limit[rate1] = txpwr_ptr1[rate2]; + } + + for (k = 0; k < 2; k++) { + switch (k) { + case 0: + + rate_start_index = WL_TX_POWER_MCS20_SDM_FIRST; + txpwr_ptr1 = txpwr->mcs_20_mimo; + break; + case 1: + + rate_start_index = WL_TX_POWER_MCS40_SDM_FIRST; + txpwr_ptr1 = txpwr->mcs_40_mimo; + break; + } + for (rate1 = rate_start_index, rate2 = 0; + rate2 < BRCMS_NUM_RATES_MCS_2_STREAM; + rate1++, rate2++) + pi->txpwr_limit[rate1] = txpwr_ptr1[rate2]; + } + + pi->txpwr_limit[WL_TX_POWER_MCS_32] = txpwr->mcs32; + + pi->txpwr_limit[WL_TX_POWER_MCS40_CDD_FIRST] = + min(pi->txpwr_limit[WL_TX_POWER_MCS40_CDD_FIRST], + pi->txpwr_limit[WL_TX_POWER_MCS_32]); + pi->txpwr_limit[WL_TX_POWER_MCS_32] = + pi->txpwr_limit[WL_TX_POWER_MCS40_CDD_FIRST]; + } +} + +void wlc_phy_txpwr_percent_set(struct brcms_phy_pub *ppi, u8 txpwr_percent) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + pi->txpwr_percent = txpwr_percent; +} + +void wlc_phy_machwcap_set(struct brcms_phy_pub *ppi, u32 machwcap) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + pi->sh->machwcap = machwcap; +} + +void wlc_phy_runbist_config(struct brcms_phy_pub *ppi, bool start_end) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + u16 rxc; + rxc = 0; + + if (start_end == ON) { + if (!ISNPHY(pi)) + return; + + if (NREV_IS(pi->pubpi.phy_rev, 3) + || NREV_IS(pi->pubpi.phy_rev, 4)) { + bcma_wflush16(pi->d11core, D11REGOFFS(phyregaddr), + 0xa0); + bcma_set16(pi->d11core, D11REGOFFS(phyregdata), + 0x1 << 15); + } + } else { + if (NREV_IS(pi->pubpi.phy_rev, 3) + || NREV_IS(pi->pubpi.phy_rev, 4)) { + bcma_wflush16(pi->d11core, D11REGOFFS(phyregaddr), + 0xa0); + bcma_write16(pi->d11core, D11REGOFFS(phyregdata), rxc); + } + + wlc_phy_por_inform(ppi); + } +} + +void +wlc_phy_txpower_limit_set(struct brcms_phy_pub *ppi, struct txpwr_limits *txpwr, + u16 chanspec) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + wlc_phy_txpower_reg_limit_calc(pi, txpwr, chanspec); + + if (ISLCNPHY(pi)) { + int i, j; + for (i = TXP_FIRST_OFDM_20_CDD, j = 0; + j < BRCMS_NUM_RATES_MCS_1_STREAM; i++, j++) { + if (txpwr->mcs_20_siso[j]) + pi->txpwr_limit[i] = txpwr->mcs_20_siso[j]; + else + pi->txpwr_limit[i] = txpwr->ofdm[j]; + } + } + + wlapi_suspend_mac_and_wait(pi->sh->physhim); + + wlc_phy_txpower_recalc_target(pi); + wlc_phy_cal_txpower_recalc_sw(pi); + wlapi_enable_mac(pi->sh->physhim); +} + +void wlc_phy_ofdm_rateset_war(struct brcms_phy_pub *pih, bool war) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + + pi->ofdm_rateset_war = war; +} + +void wlc_phy_bf_preempt_enable(struct brcms_phy_pub *pih, bool bf_preempt) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + + pi->bf_preempt_4306 = bf_preempt; +} + +void wlc_phy_txpower_update_shm(struct brcms_phy *pi) +{ + int j; + if (ISNPHY(pi)) + return; + + if (!pi->sh->clk) + return; + + if (pi->hwpwrctrl) { + u16 offset; + + wlapi_bmac_write_shm(pi->sh->physhim, M_TXPWR_MAX, 63); + wlapi_bmac_write_shm(pi->sh->physhim, M_TXPWR_N, + 1 << NUM_TSSI_FRAMES); + + wlapi_bmac_write_shm(pi->sh->physhim, M_TXPWR_TARGET, + pi->tx_power_min << NUM_TSSI_FRAMES); + + wlapi_bmac_write_shm(pi->sh->physhim, M_TXPWR_CUR, + pi->hwpwr_txcur); + + for (j = TXP_FIRST_OFDM; j <= TXP_LAST_OFDM; j++) { + const u8 ucode_ofdm_rates[] = { + 0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c + }; + offset = wlapi_bmac_rate_shm_offset( + pi->sh->physhim, + ucode_ofdm_rates[j - TXP_FIRST_OFDM]); + wlapi_bmac_write_shm(pi->sh->physhim, offset + 6, + pi->tx_power_offset[j]); + wlapi_bmac_write_shm(pi->sh->physhim, offset + 14, + -(pi->tx_power_offset[j] / 2)); + } + + wlapi_bmac_mhf(pi->sh->physhim, MHF2, MHF2_HWPWRCTL, + MHF2_HWPWRCTL, BRCM_BAND_ALL); + } else { + int i; + + for (i = TXP_FIRST_OFDM; i <= TXP_LAST_OFDM; i++) + pi->tx_power_offset[i] = + (u8) roundup(pi->tx_power_offset[i], 8); + wlapi_bmac_write_shm(pi->sh->physhim, M_OFDM_OFFSET, + (u16) + ((pi->tx_power_offset[TXP_FIRST_OFDM] + + 7) >> 3)); + } +} + +bool wlc_phy_txpower_hw_ctrl_get(struct brcms_phy_pub *ppi) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + if (ISNPHY(pi)) + return pi->nphy_txpwrctrl; + else + return pi->hwpwrctrl; +} + +void wlc_phy_txpower_hw_ctrl_set(struct brcms_phy_pub *ppi, bool hwpwrctrl) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + bool suspend; + + if (!pi->hwpwrctrl_capable) + return; + + pi->hwpwrctrl = hwpwrctrl; + pi->nphy_txpwrctrl = hwpwrctrl; + pi->txpwrctrl = hwpwrctrl; + + if (ISNPHY(pi)) { + suspend = (0 == (bcma_read32(pi->d11core, + D11REGOFFS(maccontrol)) & + MCTL_EN_MAC)); + if (!suspend) + wlapi_suspend_mac_and_wait(pi->sh->physhim); + + wlc_phy_txpwrctrl_enable_nphy(pi, pi->nphy_txpwrctrl); + if (pi->nphy_txpwrctrl == PHY_TPC_HW_OFF) + wlc_phy_txpwr_fixpower_nphy(pi); + else + mod_phy_reg(pi, 0x1e7, (0x7f << 0), + pi->saved_txpwr_idx); + + if (!suspend) + wlapi_enable_mac(pi->sh->physhim); + } +} + +void wlc_phy_txpower_ipa_upd(struct brcms_phy *pi) +{ + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + pi->ipa2g_on = (pi->srom_fem2g.extpagain == 2); + pi->ipa5g_on = (pi->srom_fem5g.extpagain == 2); + } else { + pi->ipa2g_on = false; + pi->ipa5g_on = false; + } +} + +static u32 wlc_phy_txpower_est_power_nphy(struct brcms_phy *pi) +{ + s16 tx0_status, tx1_status; + u16 estPower1, estPower2; + u8 pwr0, pwr1, adj_pwr0, adj_pwr1; + u32 est_pwr; + + estPower1 = read_phy_reg(pi, 0x118); + estPower2 = read_phy_reg(pi, 0x119); + + if ((estPower1 & (0x1 << 8)) == (0x1 << 8)) + pwr0 = (u8) (estPower1 & (0xff << 0)) >> 0; + else + pwr0 = 0x80; + + if ((estPower2 & (0x1 << 8)) == (0x1 << 8)) + pwr1 = (u8) (estPower2 & (0xff << 0)) >> 0; + else + pwr1 = 0x80; + + tx0_status = read_phy_reg(pi, 0x1ed); + tx1_status = read_phy_reg(pi, 0x1ee); + + if ((tx0_status & (0x1 << 15)) == (0x1 << 15)) + adj_pwr0 = (u8) (tx0_status & (0xff << 0)) >> 0; + else + adj_pwr0 = 0x80; + if ((tx1_status & (0x1 << 15)) == (0x1 << 15)) + adj_pwr1 = (u8) (tx1_status & (0xff << 0)) >> 0; + else + adj_pwr1 = 0x80; + + est_pwr = (u32) ((pwr0 << 24) | (pwr1 << 16) | (adj_pwr0 << 8) | + adj_pwr1); + + return est_pwr; +} + +void +wlc_phy_txpower_get_current(struct brcms_phy_pub *ppi, struct tx_power *power, + uint channel) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + uint rate, num_rates; + u8 min_pwr, max_pwr; + +#if WL_TX_POWER_RATES != TXP_NUM_RATES +#error "struct tx_power out of sync with this fn" +#endif + + if (ISNPHY(pi)) { + power->rf_cores = 2; + power->flags |= (WL_TX_POWER_F_MIMO); + if (pi->nphy_txpwrctrl == PHY_TPC_HW_ON) + power->flags |= + (WL_TX_POWER_F_ENABLED | WL_TX_POWER_F_HW); + } else if (ISLCNPHY(pi)) { + power->rf_cores = 1; + power->flags |= (WL_TX_POWER_F_SISO); + if (pi->radiopwr_override == RADIOPWR_OVERRIDE_DEF) + power->flags |= WL_TX_POWER_F_ENABLED; + if (pi->hwpwrctrl) + power->flags |= WL_TX_POWER_F_HW; + } + + num_rates = ((ISNPHY(pi)) ? (TXP_NUM_RATES) : + ((ISLCNPHY(pi)) ? + (TXP_LAST_OFDM_20_CDD + 1) : (TXP_LAST_OFDM + 1))); + + for (rate = 0; rate < num_rates; rate++) { + power->user_limit[rate] = pi->tx_user_target[rate]; + wlc_phy_txpower_sromlimit(ppi, channel, &min_pwr, &max_pwr, + rate); + power->board_limit[rate] = (u8) max_pwr; + power->target[rate] = pi->tx_power_target[rate]; + } + + if (ISNPHY(pi)) { + u32 est_pout; + + wlapi_suspend_mac_and_wait(pi->sh->physhim); + wlc_phyreg_enter((struct brcms_phy_pub *) pi); + est_pout = wlc_phy_txpower_est_power_nphy(pi); + wlc_phyreg_exit((struct brcms_phy_pub *) pi); + wlapi_enable_mac(pi->sh->physhim); + + power->est_Pout[0] = (est_pout >> 8) & 0xff; + power->est_Pout[1] = est_pout & 0xff; + + power->est_Pout_act[0] = est_pout >> 24; + power->est_Pout_act[1] = (est_pout >> 16) & 0xff; + + if (power->est_Pout[0] == 0x80) + power->est_Pout[0] = 0; + if (power->est_Pout[1] == 0x80) + power->est_Pout[1] = 0; + + if (power->est_Pout_act[0] == 0x80) + power->est_Pout_act[0] = 0; + if (power->est_Pout_act[1] == 0x80) + power->est_Pout_act[1] = 0; + + power->est_Pout_cck = 0; + + power->tx_power_max[0] = pi->tx_power_max; + power->tx_power_max[1] = pi->tx_power_max; + + power->tx_power_max_rate_ind[0] = pi->tx_power_max_rate_ind; + power->tx_power_max_rate_ind[1] = pi->tx_power_max_rate_ind; + } else if (pi->hwpwrctrl && pi->sh->up) { + + wlc_phyreg_enter(ppi); + if (ISLCNPHY(pi)) { + + power->tx_power_max[0] = pi->tx_power_max; + power->tx_power_max[1] = pi->tx_power_max; + + power->tx_power_max_rate_ind[0] = + pi->tx_power_max_rate_ind; + power->tx_power_max_rate_ind[1] = + pi->tx_power_max_rate_ind; + + if (wlc_phy_tpc_isenabled_lcnphy(pi)) + power->flags |= + (WL_TX_POWER_F_HW | + WL_TX_POWER_F_ENABLED); + else + power->flags &= + ~(WL_TX_POWER_F_HW | + WL_TX_POWER_F_ENABLED); + + wlc_lcnphy_get_tssi(pi, (s8 *) &power->est_Pout[0], + (s8 *) &power->est_Pout_cck); + } + wlc_phyreg_exit(ppi); + } +} + +void wlc_phy_antsel_type_set(struct brcms_phy_pub *ppi, u8 antsel_type) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + pi->antsel_type = antsel_type; +} + +bool wlc_phy_test_ison(struct brcms_phy_pub *ppi) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + return pi->phytest_on; +} + +void wlc_phy_ant_rxdiv_set(struct brcms_phy_pub *ppi, u8 val) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + bool suspend; + + pi->sh->rx_antdiv = val; + + if (!(ISNPHY(pi) && D11REV_IS(pi->sh->corerev, 16))) { + if (val > ANT_RX_DIV_FORCE_1) + wlapi_bmac_mhf(pi->sh->physhim, MHF1, MHF1_ANTDIV, + MHF1_ANTDIV, BRCM_BAND_ALL); + else + wlapi_bmac_mhf(pi->sh->physhim, MHF1, MHF1_ANTDIV, 0, + BRCM_BAND_ALL); + } + + if (ISNPHY(pi)) + return; + + if (!pi->sh->clk) + return; + + suspend = (0 == (bcma_read32(pi->d11core, D11REGOFFS(maccontrol)) & + MCTL_EN_MAC)); + if (!suspend) + wlapi_suspend_mac_and_wait(pi->sh->physhim); + + if (ISLCNPHY(pi)) { + if (val > ANT_RX_DIV_FORCE_1) { + mod_phy_reg(pi, 0x410, (0x1 << 1), 0x01 << 1); + mod_phy_reg(pi, 0x410, + (0x1 << 0), + ((ANT_RX_DIV_START_1 == val) ? 1 : 0) << 0); + } else { + mod_phy_reg(pi, 0x410, (0x1 << 1), 0x00 << 1); + mod_phy_reg(pi, 0x410, (0x1 << 0), (u16) val << 0); + } + } + + if (!suspend) + wlapi_enable_mac(pi->sh->physhim); + + return; +} + +static bool +wlc_phy_noise_calc_phy(struct brcms_phy *pi, u32 *cmplx_pwr, s8 *pwr_ant) +{ + s8 cmplx_pwr_dbm[PHY_CORE_MAX]; + u8 i; + + memset((u8 *) cmplx_pwr_dbm, 0, sizeof(cmplx_pwr_dbm)); + wlc_phy_compute_dB(cmplx_pwr, cmplx_pwr_dbm, pi->pubpi.phy_corenum); + + for (i = 0; i < pi->pubpi.phy_corenum; i++) { + if (NREV_GE(pi->pubpi.phy_rev, 3)) + cmplx_pwr_dbm[i] += (s8) PHY_NOISE_OFFSETFACT_4322; + else + + cmplx_pwr_dbm[i] += (s8) (16 - (15) * 3 - 70); + } + + for (i = 0; i < pi->pubpi.phy_corenum; i++) { + pi->nphy_noise_win[i][pi->nphy_noise_index] = cmplx_pwr_dbm[i]; + pwr_ant[i] = cmplx_pwr_dbm[i]; + } + pi->nphy_noise_index = + MODINC_POW2(pi->nphy_noise_index, PHY_NOISE_WINDOW_SZ); + return true; +} + +static void wlc_phy_noise_cb(struct brcms_phy *pi, u8 channel, s8 noise_dbm) +{ + if (!pi->phynoise_state) + return; + + if (pi->phynoise_state & PHY_NOISE_STATE_MON) { + if (pi->phynoise_chan_watchdog == channel) { + pi->sh->phy_noise_window[pi->sh->phy_noise_index] = + noise_dbm; + pi->sh->phy_noise_index = + MODINC(pi->sh->phy_noise_index, MA_WINDOW_SZ); + } + pi->phynoise_state &= ~PHY_NOISE_STATE_MON; + } + + if (pi->phynoise_state & PHY_NOISE_STATE_EXTERNAL) + pi->phynoise_state &= ~PHY_NOISE_STATE_EXTERNAL; + +} + +static s8 wlc_phy_noise_read_shmem(struct brcms_phy *pi) +{ + u32 cmplx_pwr[PHY_CORE_MAX]; + s8 noise_dbm_ant[PHY_CORE_MAX]; + u16 lo, hi; + u32 cmplx_pwr_tot = 0; + s8 noise_dbm = PHY_NOISE_FIXED_VAL_NPHY; + u8 idx, core; + + memset((u8 *) cmplx_pwr, 0, sizeof(cmplx_pwr)); + memset((u8 *) noise_dbm_ant, 0, sizeof(noise_dbm_ant)); + + for (idx = 0, core = 0; core < pi->pubpi.phy_corenum; idx += 2, + core++) { + lo = wlapi_bmac_read_shm(pi->sh->physhim, M_PWRIND_MAP(idx)); + hi = wlapi_bmac_read_shm(pi->sh->physhim, + M_PWRIND_MAP(idx + 1)); + cmplx_pwr[core] = (hi << 16) + lo; + cmplx_pwr_tot += cmplx_pwr[core]; + if (cmplx_pwr[core] == 0) + noise_dbm_ant[core] = PHY_NOISE_FIXED_VAL_NPHY; + else + cmplx_pwr[core] >>= PHY_NOISE_SAMPLE_LOG_NUM_UCODE; + } + + if (cmplx_pwr_tot != 0) + wlc_phy_noise_calc_phy(pi, cmplx_pwr, noise_dbm_ant); + + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + pi->nphy_noise_win[core][pi->nphy_noise_index] = + noise_dbm_ant[core]; + + if (noise_dbm_ant[core] > noise_dbm) + noise_dbm = noise_dbm_ant[core]; + } + pi->nphy_noise_index = + MODINC_POW2(pi->nphy_noise_index, PHY_NOISE_WINDOW_SZ); + + return noise_dbm; + +} + +void wlc_phy_noise_sample_intr(struct brcms_phy_pub *pih) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + u16 jssi_aux; + u8 channel = 0; + s8 noise_dbm = PHY_NOISE_FIXED_VAL_NPHY; + + if (ISLCNPHY(pi)) { + u32 cmplx_pwr, cmplx_pwr0, cmplx_pwr1; + u16 lo, hi; + s32 pwr_offset_dB, gain_dB; + u16 status_0, status_1; + + jssi_aux = wlapi_bmac_read_shm(pi->sh->physhim, M_JSSI_AUX); + channel = jssi_aux & D11_CURCHANNEL_MAX; + + lo = wlapi_bmac_read_shm(pi->sh->physhim, M_PWRIND_MAP0); + hi = wlapi_bmac_read_shm(pi->sh->physhim, M_PWRIND_MAP1); + cmplx_pwr0 = (hi << 16) + lo; + + lo = wlapi_bmac_read_shm(pi->sh->physhim, M_PWRIND_MAP2); + hi = wlapi_bmac_read_shm(pi->sh->physhim, M_PWRIND_MAP3); + cmplx_pwr1 = (hi << 16) + lo; + cmplx_pwr = (cmplx_pwr0 + cmplx_pwr1) >> 6; + + status_0 = 0x44; + status_1 = wlapi_bmac_read_shm(pi->sh->physhim, M_JSSI_0); + if ((cmplx_pwr > 0 && cmplx_pwr < 500) + && ((status_1 & 0xc000) == 0x4000)) { + + wlc_phy_compute_dB(&cmplx_pwr, &noise_dbm, + pi->pubpi.phy_corenum); + pwr_offset_dB = (read_phy_reg(pi, 0x434) & 0xFF); + if (pwr_offset_dB > 127) + pwr_offset_dB -= 256; + + noise_dbm += (s8) (pwr_offset_dB - 30); + + gain_dB = (status_0 & 0x1ff); + noise_dbm -= (s8) (gain_dB); + } else { + noise_dbm = PHY_NOISE_FIXED_VAL_LCNPHY; + } + } else if (ISNPHY(pi)) { + + jssi_aux = wlapi_bmac_read_shm(pi->sh->physhim, M_JSSI_AUX); + channel = jssi_aux & D11_CURCHANNEL_MAX; + + noise_dbm = wlc_phy_noise_read_shmem(pi); + } + + wlc_phy_noise_cb(pi, channel, noise_dbm); + +} + +static void +wlc_phy_noise_sample_request(struct brcms_phy_pub *pih, u8 reason, u8 ch) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + s8 noise_dbm = PHY_NOISE_FIXED_VAL_NPHY; + bool sampling_in_progress = (pi->phynoise_state != 0); + bool wait_for_intr = true; + + switch (reason) { + case PHY_NOISE_SAMPLE_MON: + pi->phynoise_chan_watchdog = ch; + pi->phynoise_state |= PHY_NOISE_STATE_MON; + break; + + case PHY_NOISE_SAMPLE_EXTERNAL: + pi->phynoise_state |= PHY_NOISE_STATE_EXTERNAL; + break; + + default: + break; + } + + if (sampling_in_progress) + return; + + pi->phynoise_now = pi->sh->now; + + if (pi->phy_fixed_noise) { + if (ISNPHY(pi)) { + pi->nphy_noise_win[WL_ANT_IDX_1][pi->nphy_noise_index] = + PHY_NOISE_FIXED_VAL_NPHY; + pi->nphy_noise_win[WL_ANT_IDX_2][pi->nphy_noise_index] = + PHY_NOISE_FIXED_VAL_NPHY; + pi->nphy_noise_index = MODINC_POW2(pi->nphy_noise_index, + PHY_NOISE_WINDOW_SZ); + noise_dbm = PHY_NOISE_FIXED_VAL_NPHY; + } else { + noise_dbm = PHY_NOISE_FIXED_VAL; + } + + wait_for_intr = false; + goto done; + } + + if (ISLCNPHY(pi)) { + if (!pi->phynoise_polling + || (reason == PHY_NOISE_SAMPLE_EXTERNAL)) { + wlapi_bmac_write_shm(pi->sh->physhim, M_JSSI_0, 0); + wlapi_bmac_write_shm(pi->sh->physhim, M_PWRIND_MAP0, 0); + wlapi_bmac_write_shm(pi->sh->physhim, M_PWRIND_MAP1, 0); + wlapi_bmac_write_shm(pi->sh->physhim, M_PWRIND_MAP2, 0); + wlapi_bmac_write_shm(pi->sh->physhim, M_PWRIND_MAP3, 0); + + bcma_set32(pi->d11core, D11REGOFFS(maccommand), + MCMD_BG_NOISE); + } else { + wlapi_suspend_mac_and_wait(pi->sh->physhim); + wlc_lcnphy_deaf_mode(pi, (bool) 0); + noise_dbm = (s8) wlc_lcnphy_rx_signal_power(pi, 20); + wlc_lcnphy_deaf_mode(pi, (bool) 1); + wlapi_enable_mac(pi->sh->physhim); + wait_for_intr = false; + } + } else if (ISNPHY(pi)) { + if (!pi->phynoise_polling + || (reason == PHY_NOISE_SAMPLE_EXTERNAL)) { + + wlapi_bmac_write_shm(pi->sh->physhim, M_PWRIND_MAP0, 0); + wlapi_bmac_write_shm(pi->sh->physhim, M_PWRIND_MAP1, 0); + wlapi_bmac_write_shm(pi->sh->physhim, M_PWRIND_MAP2, 0); + wlapi_bmac_write_shm(pi->sh->physhim, M_PWRIND_MAP3, 0); + + bcma_set32(pi->d11core, D11REGOFFS(maccommand), + MCMD_BG_NOISE); + } else { + struct phy_iq_est est[PHY_CORE_MAX]; + u32 cmplx_pwr[PHY_CORE_MAX]; + s8 noise_dbm_ant[PHY_CORE_MAX]; + u16 log_num_samps, num_samps, classif_state = 0; + u8 wait_time = 32; + u8 wait_crs = 0; + u8 i; + + memset((u8 *) est, 0, sizeof(est)); + memset((u8 *) cmplx_pwr, 0, sizeof(cmplx_pwr)); + memset((u8 *) noise_dbm_ant, 0, sizeof(noise_dbm_ant)); + + log_num_samps = PHY_NOISE_SAMPLE_LOG_NUM_NPHY; + num_samps = 1 << log_num_samps; + + wlapi_suspend_mac_and_wait(pi->sh->physhim); + classif_state = wlc_phy_classifier_nphy(pi, 0, 0); + wlc_phy_classifier_nphy(pi, 3, 0); + wlc_phy_rx_iq_est_nphy(pi, est, num_samps, wait_time, + wait_crs); + wlc_phy_classifier_nphy(pi, (0x7 << 0), classif_state); + wlapi_enable_mac(pi->sh->physhim); + + for (i = 0; i < pi->pubpi.phy_corenum; i++) + cmplx_pwr[i] = (est[i].i_pwr + est[i].q_pwr) >> + log_num_samps; + + wlc_phy_noise_calc_phy(pi, cmplx_pwr, noise_dbm_ant); + + for (i = 0; i < pi->pubpi.phy_corenum; i++) { + pi->nphy_noise_win[i][pi->nphy_noise_index] = + noise_dbm_ant[i]; + + if (noise_dbm_ant[i] > noise_dbm) + noise_dbm = noise_dbm_ant[i]; + } + pi->nphy_noise_index = MODINC_POW2(pi->nphy_noise_index, + PHY_NOISE_WINDOW_SZ); + + wait_for_intr = false; + } + } + +done: + + if (!wait_for_intr) + wlc_phy_noise_cb(pi, ch, noise_dbm); + +} + +void wlc_phy_noise_sample_request_external(struct brcms_phy_pub *pih) +{ + u8 channel; + + channel = CHSPEC_CHANNEL(wlc_phy_chanspec_get(pih)); + + wlc_phy_noise_sample_request(pih, PHY_NOISE_SAMPLE_EXTERNAL, channel); +} + +static const s8 lcnphy_gain_index_offset_for_pkt_rssi[] = { + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 9, + 10, + 8, + 8, + 7, + 7, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 1, + 1, + 0, + 0, + 0, + 0 +}; + +void wlc_phy_compute_dB(u32 *cmplx_pwr, s8 *p_cmplx_pwr_dB, u8 core) +{ + u8 msb, secondmsb, i; + u32 tmp; + + for (i = 0; i < core; i++) { + secondmsb = 0; + tmp = cmplx_pwr[i]; + msb = fls(tmp); + if (msb) + secondmsb = (u8) ((tmp >> (--msb - 1)) & 1); + p_cmplx_pwr_dB[i] = (s8) (3 * msb + 2 * secondmsb); + } +} + +int wlc_phy_rssi_compute(struct brcms_phy_pub *pih, + struct d11rxhdr *rxh) +{ + int rssi = rxh->PhyRxStatus_1 & PRXS1_JSSI_MASK; + uint radioid = pih->radioid; + struct brcms_phy *pi = (struct brcms_phy *) pih; + + if ((pi->sh->corerev >= 11) + && !(rxh->RxStatus2 & RXS_PHYRXST_VALID)) { + rssi = BRCMS_RSSI_INVALID; + goto end; + } + + if (ISLCNPHY(pi)) { + u8 gidx = (rxh->PhyRxStatus_2 & 0xFC00) >> 10; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + if (rssi > 127) + rssi -= 256; + + rssi = rssi + lcnphy_gain_index_offset_for_pkt_rssi[gidx]; + if ((rssi > -46) && (gidx > 18)) + rssi = rssi + 7; + + rssi = rssi + pi_lcn->lcnphy_pkteng_rssi_slope; + + rssi = rssi + 2; + + } + + if (ISLCNPHY(pi)) { + if (rssi > 127) + rssi -= 256; + } else if (radioid == BCM2055_ID || radioid == BCM2056_ID + || radioid == BCM2057_ID) { + rssi = wlc_phy_rssi_compute_nphy(pi, rxh); + } + +end: + return rssi; +} + +void wlc_phy_freqtrack_start(struct brcms_phy_pub *pih) +{ + return; +} + +void wlc_phy_freqtrack_end(struct brcms_phy_pub *pih) +{ + return; +} + +void wlc_phy_set_deaf(struct brcms_phy_pub *ppi, bool user_flag) +{ + struct brcms_phy *pi; + pi = (struct brcms_phy *) ppi; + + if (ISLCNPHY(pi)) + wlc_lcnphy_deaf_mode(pi, true); + else if (ISNPHY(pi)) + wlc_nphy_deaf_mode(pi, true); +} + +void wlc_phy_watchdog(struct brcms_phy_pub *pih) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + bool delay_phy_cal = false; + pi->sh->now++; + + if (!pi->watchdog_override) + return; + + if (!(SCAN_RM_IN_PROGRESS(pi) || PLT_INPROG_PHY(pi))) + wlc_phy_noise_sample_request((struct brcms_phy_pub *) pi, + PHY_NOISE_SAMPLE_MON, + CHSPEC_CHANNEL(pi-> + radio_chanspec)); + + if (pi->phynoise_state && (pi->sh->now - pi->phynoise_now) > 5) + pi->phynoise_state = 0; + + if ((!pi->phycal_txpower) || + ((pi->sh->now - pi->phycal_txpower) >= pi->sh->fast_timer)) { + + if (!SCAN_INPROG_PHY(pi) && wlc_phy_cal_txpower_recalc_sw(pi)) + pi->phycal_txpower = pi->sh->now; + } + + if ((SCAN_RM_IN_PROGRESS(pi) || PLT_INPROG_PHY(pi) + || ASSOC_INPROG_PHY(pi))) + return; + + if (ISNPHY(pi) && !pi->disable_percal && !delay_phy_cal) { + + if ((pi->nphy_perical != PHY_PERICAL_DISABLE) && + (pi->nphy_perical != PHY_PERICAL_MANUAL) && + ((pi->sh->now - pi->nphy_perical_last) >= + pi->sh->glacial_timer)) + wlc_phy_cal_perical((struct brcms_phy_pub *) pi, + PHY_PERICAL_WATCHDOG); + + wlc_phy_txpwr_papd_cal_nphy(pi); + } + + if (ISLCNPHY(pi)) { + if (pi->phy_forcecal || + ((pi->sh->now - pi->phy_lastcal) >= + pi->sh->glacial_timer)) { + if (!(SCAN_RM_IN_PROGRESS(pi) || ASSOC_INPROG_PHY(pi))) + wlc_lcnphy_calib_modes( + pi, + LCNPHY_PERICAL_TEMPBASED_TXPWRCTRL); + if (! + (SCAN_RM_IN_PROGRESS(pi) || PLT_INPROG_PHY(pi) + || ASSOC_INPROG_PHY(pi) + || pi->carrier_suppr_disable + || pi->disable_percal)) + wlc_lcnphy_calib_modes(pi, + PHY_PERICAL_WATCHDOG); + } + } +} + +void wlc_phy_BSSinit(struct brcms_phy_pub *pih, bool bonlyap, int rssi) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + uint i; + uint k; + + for (i = 0; i < MA_WINDOW_SZ; i++) + pi->sh->phy_noise_window[i] = (s8) (rssi & 0xff); + if (ISLCNPHY(pi)) { + for (i = 0; i < MA_WINDOW_SZ; i++) + pi->sh->phy_noise_window[i] = + PHY_NOISE_FIXED_VAL_LCNPHY; + } + pi->sh->phy_noise_index = 0; + + for (i = 0; i < PHY_NOISE_WINDOW_SZ; i++) { + for (k = WL_ANT_IDX_1; k < WL_ANT_RX_MAX; k++) + pi->nphy_noise_win[k][i] = PHY_NOISE_FIXED_VAL_NPHY; + } + pi->nphy_noise_index = 0; +} + +void +wlc_phy_papd_decode_epsilon(u32 epsilon, s32 *eps_real, s32 *eps_imag) +{ + *eps_imag = (epsilon >> 13); + if (*eps_imag > 0xfff) + *eps_imag -= 0x2000; + + *eps_real = (epsilon & 0x1fff); + if (*eps_real > 0xfff) + *eps_real -= 0x2000; +} + +void wlc_phy_cal_perical_mphase_reset(struct brcms_phy *pi) +{ + wlapi_del_timer(pi->phycal_timer); + + pi->cal_type_override = PHY_PERICAL_AUTO; + pi->mphase_cal_phase_id = MPHASE_CAL_STATE_IDLE; + pi->mphase_txcal_cmdidx = 0; +} + +static void +wlc_phy_cal_perical_mphase_schedule(struct brcms_phy *pi, uint delay) +{ + + if ((pi->nphy_perical != PHY_PERICAL_MPHASE) && + (pi->nphy_perical != PHY_PERICAL_MANUAL)) + return; + + wlapi_del_timer(pi->phycal_timer); + + pi->mphase_cal_phase_id = MPHASE_CAL_STATE_INIT; + wlapi_add_timer(pi->phycal_timer, delay, 0); +} + +void wlc_phy_cal_perical(struct brcms_phy_pub *pih, u8 reason) +{ + s16 nphy_currtemp = 0; + s16 delta_temp = 0; + bool do_periodic_cal = true; + struct brcms_phy *pi = (struct brcms_phy *) pih; + + if (!ISNPHY(pi)) + return; + + if ((pi->nphy_perical == PHY_PERICAL_DISABLE) || + (pi->nphy_perical == PHY_PERICAL_MANUAL)) + return; + + switch (reason) { + case PHY_PERICAL_DRIVERUP: + break; + + case PHY_PERICAL_PHYINIT: + if (pi->nphy_perical == PHY_PERICAL_MPHASE) { + if (PHY_PERICAL_MPHASE_PENDING(pi)) + wlc_phy_cal_perical_mphase_reset(pi); + + wlc_phy_cal_perical_mphase_schedule( + pi, + PHY_PERICAL_INIT_DELAY); + } + break; + + case PHY_PERICAL_JOIN_BSS: + case PHY_PERICAL_START_IBSS: + case PHY_PERICAL_UP_BSS: + if ((pi->nphy_perical == PHY_PERICAL_MPHASE) && + PHY_PERICAL_MPHASE_PENDING(pi)) + wlc_phy_cal_perical_mphase_reset(pi); + + pi->first_cal_after_assoc = true; + + pi->cal_type_override = PHY_PERICAL_FULL; + + if (pi->phycal_tempdelta) + pi->nphy_lastcal_temp = wlc_phy_tempsense_nphy(pi); + + wlc_phy_cal_perical_nphy_run(pi, PHY_PERICAL_FULL); + break; + + case PHY_PERICAL_WATCHDOG: + if (pi->phycal_tempdelta) { + nphy_currtemp = wlc_phy_tempsense_nphy(pi); + delta_temp = + (nphy_currtemp > pi->nphy_lastcal_temp) ? + nphy_currtemp - pi->nphy_lastcal_temp : + pi->nphy_lastcal_temp - nphy_currtemp; + + if ((delta_temp < (s16) pi->phycal_tempdelta) && + (pi->nphy_txiqlocal_chanspec == + pi->radio_chanspec)) + do_periodic_cal = false; + else + pi->nphy_lastcal_temp = nphy_currtemp; + } + + if (do_periodic_cal) { + if (pi->nphy_perical == PHY_PERICAL_MPHASE) { + if (!PHY_PERICAL_MPHASE_PENDING(pi)) + wlc_phy_cal_perical_mphase_schedule( + pi, + PHY_PERICAL_WDOG_DELAY); + } else if (pi->nphy_perical == PHY_PERICAL_SPHASE) + wlc_phy_cal_perical_nphy_run(pi, + PHY_PERICAL_AUTO); + } + break; + default: + break; + } +} + +void wlc_phy_cal_perical_mphase_restart(struct brcms_phy *pi) +{ + pi->mphase_cal_phase_id = MPHASE_CAL_STATE_INIT; + pi->mphase_txcal_cmdidx = 0; +} + +u8 wlc_phy_nbits(s32 value) +{ + s32 abs_val; + u8 nbits = 0; + + abs_val = abs(value); + while ((abs_val >> nbits) > 0) + nbits++; + + return nbits; +} + +void wlc_phy_stf_chain_init(struct brcms_phy_pub *pih, u8 txchain, u8 rxchain) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + + pi->sh->hw_phytxchain = txchain; + pi->sh->hw_phyrxchain = rxchain; + pi->sh->phytxchain = txchain; + pi->sh->phyrxchain = rxchain; + pi->pubpi.phy_corenum = (u8)hweight8(pi->sh->phyrxchain); +} + +void wlc_phy_stf_chain_set(struct brcms_phy_pub *pih, u8 txchain, u8 rxchain) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + + pi->sh->phytxchain = txchain; + + if (ISNPHY(pi)) + wlc_phy_rxcore_setstate_nphy(pih, rxchain); + + pi->pubpi.phy_corenum = (u8)hweight8(pi->sh->phyrxchain); +} + +void wlc_phy_stf_chain_get(struct brcms_phy_pub *pih, u8 *txchain, u8 *rxchain) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + + *txchain = pi->sh->phytxchain; + *rxchain = pi->sh->phyrxchain; +} + +u8 wlc_phy_stf_chain_active_get(struct brcms_phy_pub *pih) +{ + s16 nphy_currtemp; + u8 active_bitmap; + struct brcms_phy *pi = (struct brcms_phy *) pih; + + active_bitmap = (pi->phy_txcore_heatedup) ? 0x31 : 0x33; + + if (!pi->watchdog_override) + return active_bitmap; + + if (NREV_GE(pi->pubpi.phy_rev, 6)) { + wlapi_suspend_mac_and_wait(pi->sh->physhim); + nphy_currtemp = wlc_phy_tempsense_nphy(pi); + wlapi_enable_mac(pi->sh->physhim); + + if (!pi->phy_txcore_heatedup) { + if (nphy_currtemp >= pi->phy_txcore_disable_temp) { + active_bitmap &= 0xFD; + pi->phy_txcore_heatedup = true; + } + } else { + if (nphy_currtemp <= pi->phy_txcore_enable_temp) { + active_bitmap |= 0x2; + pi->phy_txcore_heatedup = false; + } + } + } + + return active_bitmap; +} + +s8 wlc_phy_stf_ssmode_get(struct brcms_phy_pub *pih, u16 chanspec) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + u8 siso_mcs_id, cdd_mcs_id; + + siso_mcs_id = + (CHSPEC_IS40(chanspec)) ? TXP_FIRST_MCS_40_SISO : + TXP_FIRST_MCS_20_SISO; + cdd_mcs_id = + (CHSPEC_IS40(chanspec)) ? TXP_FIRST_MCS_40_CDD : + TXP_FIRST_MCS_20_CDD; + + if (pi->tx_power_target[siso_mcs_id] > + (pi->tx_power_target[cdd_mcs_id] + 12)) + return PHY_TXC1_MODE_SISO; + else + return PHY_TXC1_MODE_CDD; +} + +const u8 *wlc_phy_get_ofdm_rate_lookup(void) +{ + return ofdm_rate_lookup; +} + +void wlc_lcnphy_epa_switch(struct brcms_phy *pi, bool mode) +{ + if ((pi->sh->chip == BCMA_CHIP_ID_BCM4313) && + (pi->sh->boardflags & BFL_FEM)) { + if (mode) { + u16 txant = 0; + txant = wlapi_bmac_get_txant(pi->sh->physhim); + if (txant == 1) { + mod_phy_reg(pi, 0x44d, (0x1 << 2), (1) << 2); + + mod_phy_reg(pi, 0x44c, (0x1 << 2), (1) << 2); + + } + + bcma_chipco_gpio_control(&pi->d11core->bus->drv_cc, + 0x0, 0x0); + bcma_chipco_gpio_out(&pi->d11core->bus->drv_cc, + ~0x40, 0x40); + bcma_chipco_gpio_outen(&pi->d11core->bus->drv_cc, + ~0x40, 0x40); + } else { + mod_phy_reg(pi, 0x44c, (0x1 << 2), (0) << 2); + + mod_phy_reg(pi, 0x44d, (0x1 << 2), (0) << 2); + + bcma_chipco_gpio_out(&pi->d11core->bus->drv_cc, + ~0x40, 0x00); + bcma_chipco_gpio_outen(&pi->d11core->bus->drv_cc, + ~0x40, 0x00); + bcma_chipco_gpio_control(&pi->d11core->bus->drv_cc, + 0x0, 0x40); + } + } +} + +void wlc_phy_ldpc_override_set(struct brcms_phy_pub *ppi, bool ldpc) +{ + return; +} + +void +wlc_phy_get_pwrdet_offsets(struct brcms_phy *pi, s8 *cckoffset, s8 *ofdmoffset) +{ + *cckoffset = 0; + *ofdmoffset = 0; +} + +s8 wlc_phy_upd_rssi_offset(struct brcms_phy *pi, s8 rssi, u16 chanspec) +{ + + return rssi; +} + +bool wlc_phy_txpower_ipa_ison(struct brcms_phy_pub *ppi) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + if (ISNPHY(pi)) + return wlc_phy_n_txpower_ipa_ison(pi); + else + return 0; +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_hal.h b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_hal.h new file mode 100644 index 0000000..e34a71e --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_hal.h @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * phy_hal.h: functionality exported from the phy to higher layers + */ + +#ifndef _BRCM_PHY_HAL_H_ +#define _BRCM_PHY_HAL_H_ + +#include +#include +#include + +#define IDCODE_VER_MASK 0x0000000f +#define IDCODE_VER_SHIFT 0 +#define IDCODE_MFG_MASK 0x00000fff +#define IDCODE_MFG_SHIFT 0 +#define IDCODE_ID_MASK 0x0ffff000 +#define IDCODE_ID_SHIFT 12 +#define IDCODE_REV_MASK 0xf0000000 +#define IDCODE_REV_SHIFT 28 + +#define NORADIO_ID 0xe4f5 +#define NORADIO_IDCODE 0x4e4f5246 + +#define BCM2055_ID 0x2055 +#define BCM2055_IDCODE 0x02055000 +#define BCM2055A0_IDCODE 0x1205517f + +#define BCM2056_ID 0x2056 +#define BCM2056_IDCODE 0x02056000 +#define BCM2056A0_IDCODE 0x1205617f + +#define BCM2057_ID 0x2057 +#define BCM2057_IDCODE 0x02057000 +#define BCM2057A0_IDCODE 0x1205717f + +#define BCM2064_ID 0x2064 +#define BCM2064_IDCODE 0x02064000 +#define BCM2064A0_IDCODE 0x0206417f + +#define PHY_TPC_HW_OFF false +#define PHY_TPC_HW_ON true + +#define PHY_PERICAL_DRIVERUP 1 +#define PHY_PERICAL_WATCHDOG 2 +#define PHY_PERICAL_PHYINIT 3 +#define PHY_PERICAL_JOIN_BSS 4 +#define PHY_PERICAL_START_IBSS 5 +#define PHY_PERICAL_UP_BSS 6 +#define PHY_PERICAL_CHAN 7 +#define PHY_FULLCAL 8 + +#define PHY_PERICAL_DISABLE 0 +#define PHY_PERICAL_SPHASE 1 +#define PHY_PERICAL_MPHASE 2 +#define PHY_PERICAL_MANUAL 3 + +#define PHY_HOLD_FOR_ASSOC 1 +#define PHY_HOLD_FOR_SCAN 2 +#define PHY_HOLD_FOR_RM 4 +#define PHY_HOLD_FOR_PLT 8 +#define PHY_HOLD_FOR_MUTE 16 +#define PHY_HOLD_FOR_NOT_ASSOC 0x20 + +#define PHY_MUTE_FOR_PREISM 1 +#define PHY_MUTE_ALL 0xffffffff + +#define PHY_NOISE_FIXED_VAL (-95) +#define PHY_NOISE_FIXED_VAL_NPHY (-92) +#define PHY_NOISE_FIXED_VAL_LCNPHY (-92) + +#define PHY_MODE_CAL 0x0002 +#define PHY_MODE_NOISEM 0x0004 + +#define BRCMS_TXPWR_DB_FACTOR 4 + +/* a large TX Power as an init value to factor out of min() calculations, + * keep low enough to fit in an s8, units are .25 dBm + */ +#define BRCMS_TXPWR_MAX (127) /* ~32 dBm = 1,500 mW */ + +#define BRCMS_NUM_RATES_CCK 4 +#define BRCMS_NUM_RATES_OFDM 8 +#define BRCMS_NUM_RATES_MCS_1_STREAM 8 +#define BRCMS_NUM_RATES_MCS_2_STREAM 8 +#define BRCMS_NUM_RATES_MCS_3_STREAM 8 +#define BRCMS_NUM_RATES_MCS_4_STREAM 8 + +#define BRCMS_RSSI_INVALID 0 /* invalid RSSI value */ + +struct d11regs; +struct phy_shim_info; + +struct txpwr_limits { + u8 cck[BRCMS_NUM_RATES_CCK]; + u8 ofdm[BRCMS_NUM_RATES_OFDM]; + + u8 ofdm_cdd[BRCMS_NUM_RATES_OFDM]; + + u8 ofdm_40_siso[BRCMS_NUM_RATES_OFDM]; + u8 ofdm_40_cdd[BRCMS_NUM_RATES_OFDM]; + + u8 mcs_20_siso[BRCMS_NUM_RATES_MCS_1_STREAM]; + u8 mcs_20_cdd[BRCMS_NUM_RATES_MCS_1_STREAM]; + u8 mcs_20_stbc[BRCMS_NUM_RATES_MCS_1_STREAM]; + u8 mcs_20_mimo[BRCMS_NUM_RATES_MCS_2_STREAM]; + + u8 mcs_40_siso[BRCMS_NUM_RATES_MCS_1_STREAM]; + u8 mcs_40_cdd[BRCMS_NUM_RATES_MCS_1_STREAM]; + u8 mcs_40_stbc[BRCMS_NUM_RATES_MCS_1_STREAM]; + u8 mcs_40_mimo[BRCMS_NUM_RATES_MCS_2_STREAM]; + u8 mcs32; +}; + +struct tx_power { + u32 flags; + u16 chanspec; /* txpwr report for this channel */ + u16 local_chanspec; /* channel on which we are associated */ + u8 local_max; /* local max according to the AP */ + u8 local_constraint; /* local constraint according to the AP */ + s8 antgain[2]; /* Ant gain for each band - from SROM */ + u8 rf_cores; /* count of RF Cores being reported */ + u8 est_Pout[4]; /* Latest tx power out estimate per RF chain */ + u8 est_Pout_act[4]; /* Latest tx power out estimate per RF chain + * without adjustment */ + u8 est_Pout_cck; /* Latest CCK tx power out estimate */ + u8 tx_power_max[4]; /* Maximum target power among all rates */ + /* Index of the rate with the max target power */ + u8 tx_power_max_rate_ind[4]; + /* User limit */ + u8 user_limit[WL_TX_POWER_RATES]; + /* Regulatory power limit */ + u8 reg_limit[WL_TX_POWER_RATES]; + /* Max power board can support (SROM) */ + u8 board_limit[WL_TX_POWER_RATES]; + /* Latest target power */ + u8 target[WL_TX_POWER_RATES]; +}; + +struct tx_inst_power { + u8 txpwr_est_Pout[2]; /* Latest estimate for 2.4 and 5 Ghz */ + u8 txpwr_est_Pout_gofdm; /* Pwr estimate for 2.4 OFDM */ +}; + +struct brcms_chanvec { + u8 vec[MAXCHANNEL / NBBY]; +}; + +struct shared_phy_params { + struct si_pub *sih; + struct phy_shim_info *physhim; + uint unit; + uint corerev; + u16 vid; + u16 did; + uint chip; + uint chiprev; + uint chippkg; + uint sromrev; + uint boardtype; + uint boardrev; + u32 boardflags; + u32 boardflags2; +}; + + +extern struct shared_phy *wlc_phy_shared_attach(struct shared_phy_params *shp); +extern struct brcms_phy_pub *wlc_phy_attach(struct shared_phy *sh, + struct bcma_device *d11core, + int bandtype, struct wiphy *wiphy); +extern void wlc_phy_detach(struct brcms_phy_pub *ppi); + +extern bool wlc_phy_get_phyversion(struct brcms_phy_pub *pih, u16 *phytype, + u16 *phyrev, u16 *radioid, + u16 *radiover); +extern bool wlc_phy_get_encore(struct brcms_phy_pub *pih); +extern u32 wlc_phy_get_coreflags(struct brcms_phy_pub *pih); + +extern void wlc_phy_hw_clk_state_upd(struct brcms_phy_pub *ppi, bool newstate); +extern void wlc_phy_hw_state_upd(struct brcms_phy_pub *ppi, bool newstate); +extern void wlc_phy_init(struct brcms_phy_pub *ppi, u16 chanspec); +extern void wlc_phy_watchdog(struct brcms_phy_pub *ppi); +extern int wlc_phy_down(struct brcms_phy_pub *ppi); +extern u32 wlc_phy_clk_bwbits(struct brcms_phy_pub *pih); +extern void wlc_phy_cal_init(struct brcms_phy_pub *ppi); +extern void wlc_phy_antsel_init(struct brcms_phy_pub *ppi, bool lut_init); + +extern void wlc_phy_chanspec_set(struct brcms_phy_pub *ppi, + u16 chanspec); +extern u16 wlc_phy_chanspec_get(struct brcms_phy_pub *ppi); +extern void wlc_phy_chanspec_radio_set(struct brcms_phy_pub *ppi, + u16 newch); +extern u16 wlc_phy_bw_state_get(struct brcms_phy_pub *ppi); +extern void wlc_phy_bw_state_set(struct brcms_phy_pub *ppi, u16 bw); + +extern int wlc_phy_rssi_compute(struct brcms_phy_pub *pih, + struct d11rxhdr *rxh); +extern void wlc_phy_por_inform(struct brcms_phy_pub *ppi); +extern void wlc_phy_noise_sample_intr(struct brcms_phy_pub *ppi); +extern bool wlc_phy_bist_check_phy(struct brcms_phy_pub *ppi); + +extern void wlc_phy_set_deaf(struct brcms_phy_pub *ppi, bool user_flag); + +extern void wlc_phy_switch_radio(struct brcms_phy_pub *ppi, bool on); +extern void wlc_phy_anacore(struct brcms_phy_pub *ppi, bool on); + + +extern void wlc_phy_BSSinit(struct brcms_phy_pub *ppi, bool bonlyap, int rssi); + +extern void wlc_phy_chanspec_ch14_widefilter_set(struct brcms_phy_pub *ppi, + bool wide_filter); +extern void wlc_phy_chanspec_band_validch(struct brcms_phy_pub *ppi, uint band, + struct brcms_chanvec *channels); +extern u16 wlc_phy_chanspec_band_firstch(struct brcms_phy_pub *ppi, + uint band); + +extern void wlc_phy_txpower_sromlimit(struct brcms_phy_pub *ppi, uint chan, + u8 *_min_, u8 *_max_, int rate); +extern void wlc_phy_txpower_sromlimit_max_get(struct brcms_phy_pub *ppi, + uint chan, u8 *_max_, u8 *_min_); +extern void wlc_phy_txpower_boardlimit_band(struct brcms_phy_pub *ppi, + uint band, s32 *, s32 *, u32 *); +extern void wlc_phy_txpower_limit_set(struct brcms_phy_pub *ppi, + struct txpwr_limits *, + u16 chanspec); +extern int wlc_phy_txpower_get(struct brcms_phy_pub *ppi, uint *qdbm, + bool *override); +extern int wlc_phy_txpower_set(struct brcms_phy_pub *ppi, uint qdbm, + bool override); +extern void wlc_phy_txpower_target_set(struct brcms_phy_pub *ppi, + struct txpwr_limits *); +extern bool wlc_phy_txpower_hw_ctrl_get(struct brcms_phy_pub *ppi); +extern void wlc_phy_txpower_hw_ctrl_set(struct brcms_phy_pub *ppi, + bool hwpwrctrl); +extern u8 wlc_phy_txpower_get_target_min(struct brcms_phy_pub *ppi); +extern u8 wlc_phy_txpower_get_target_max(struct brcms_phy_pub *ppi); +extern bool wlc_phy_txpower_ipa_ison(struct brcms_phy_pub *pih); + +extern void wlc_phy_stf_chain_init(struct brcms_phy_pub *pih, u8 txchain, + u8 rxchain); +extern void wlc_phy_stf_chain_set(struct brcms_phy_pub *pih, u8 txchain, + u8 rxchain); +extern void wlc_phy_stf_chain_get(struct brcms_phy_pub *pih, u8 *txchain, + u8 *rxchain); +extern u8 wlc_phy_stf_chain_active_get(struct brcms_phy_pub *pih); +extern s8 wlc_phy_stf_ssmode_get(struct brcms_phy_pub *pih, + u16 chanspec); +extern void wlc_phy_ldpc_override_set(struct brcms_phy_pub *ppi, bool val); + +extern void wlc_phy_cal_perical(struct brcms_phy_pub *ppi, u8 reason); +extern void wlc_phy_noise_sample_request_external(struct brcms_phy_pub *ppi); +extern void wlc_phy_edcrs_lock(struct brcms_phy_pub *pih, bool lock); +extern void wlc_phy_cal_papd_recal(struct brcms_phy_pub *ppi); + +extern void wlc_phy_ant_rxdiv_set(struct brcms_phy_pub *ppi, u8 val); +extern void wlc_phy_clear_tssi(struct brcms_phy_pub *ppi); +extern void wlc_phy_hold_upd(struct brcms_phy_pub *ppi, u32 id, bool val); +extern void wlc_phy_mute_upd(struct brcms_phy_pub *ppi, bool val, u32 flags); + +extern void wlc_phy_antsel_type_set(struct brcms_phy_pub *ppi, u8 antsel_type); + +extern void wlc_phy_txpower_get_current(struct brcms_phy_pub *ppi, + struct tx_power *power, uint channel); + +extern void wlc_phy_initcal_enable(struct brcms_phy_pub *pih, bool initcal); +extern bool wlc_phy_test_ison(struct brcms_phy_pub *ppi); +extern void wlc_phy_txpwr_percent_set(struct brcms_phy_pub *ppi, + u8 txpwr_percent); +extern void wlc_phy_ofdm_rateset_war(struct brcms_phy_pub *pih, bool war); +extern void wlc_phy_bf_preempt_enable(struct brcms_phy_pub *pih, + bool bf_preempt); +extern void wlc_phy_machwcap_set(struct brcms_phy_pub *ppi, u32 machwcap); + +extern void wlc_phy_runbist_config(struct brcms_phy_pub *ppi, bool start_end); + +extern void wlc_phy_freqtrack_start(struct brcms_phy_pub *ppi); +extern void wlc_phy_freqtrack_end(struct brcms_phy_pub *ppi); + +extern const u8 *wlc_phy_get_ofdm_rate_lookup(void); + +extern s8 wlc_phy_get_tx_power_offset_by_mcs(struct brcms_phy_pub *ppi, + u8 mcs_offset); +extern s8 wlc_phy_get_tx_power_offset(struct brcms_phy_pub *ppi, u8 tbl_offset); +#endif /* _BRCM_PHY_HAL_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_int.h b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_int.h new file mode 100644 index 0000000..1dc767c --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_int.h @@ -0,0 +1,1161 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_PHY_INT_H_ +#define _BRCM_PHY_INT_H_ + +#include +#include +#include + +#define PHY_VERSION { 1, 82, 8, 0 } + +#define LCNXN_BASEREV 16 + +struct phy_shim_info; + +struct brcms_phy_srom_fem { + /* TSSI positive slope, 1: positive, 0: negative */ + u8 tssipos; + /* Ext PA gain-type: full-gain: 0, pa-lite: 1, no_pa: 2 */ + u8 extpagain; + /* support 32 combinations of different Pdet dynamic ranges */ + u8 pdetrange; + /* TR switch isolation */ + u8 triso; + /* antswctrl lookup table configuration: 32 possible choices */ + u8 antswctrllut; +}; + +#define ISNPHY(pi) PHYTYPE_IS((pi)->pubpi.phy_type, PHY_TYPE_N) +#define ISLCNPHY(pi) PHYTYPE_IS((pi)->pubpi.phy_type, PHY_TYPE_LCN) + +#define PHY_GET_RFATTN(rfgain) ((rfgain) & 0x0f) +#define PHY_GET_PADMIX(rfgain) (((rfgain) & 0x10) >> 4) +#define PHY_GET_RFGAINID(rfattn, padmix, width) ((rfattn) + ((padmix)*(width))) +#define PHY_SAT(x, n) ((x) > ((1<<((n)-1))-1) ? ((1<<((n)-1))-1) : \ + ((x) < -(1<<((n)-1)) ? -(1<<((n)-1)) : (x))) +#define PHY_SHIFT_ROUND(x, n) ((x) >= 0 ? ((x)+(1<<((n)-1)))>>(n) : (x)>>(n)) +#define PHY_HW_ROUND(x, s) ((x >> s) + ((x >> (s-1)) & (s != 0))) + +#define CH_5G_GROUP 3 +#define A_LOW_CHANS 0 +#define A_MID_CHANS 1 +#define A_HIGH_CHANS 2 +#define CH_2G_GROUP 1 +#define G_ALL_CHANS 0 + +#define FIRST_REF5_CHANNUM 149 +#define LAST_REF5_CHANNUM 165 +#define FIRST_5G_CHAN 14 +#define LAST_5G_CHAN 50 +#define FIRST_MID_5G_CHAN 14 +#define LAST_MID_5G_CHAN 35 +#define FIRST_HIGH_5G_CHAN 36 +#define LAST_HIGH_5G_CHAN 41 +#define FIRST_LOW_5G_CHAN 42 +#define LAST_LOW_5G_CHAN 50 + +#define BASE_LOW_5G_CHAN 4900 +#define BASE_MID_5G_CHAN 5100 +#define BASE_HIGH_5G_CHAN 5500 + +#define CHAN5G_FREQ(chan) (5000 + chan*5) +#define CHAN2G_FREQ(chan) (2407 + chan*5) + +#define TXP_FIRST_CCK 0 +#define TXP_LAST_CCK 3 +#define TXP_FIRST_OFDM 4 +#define TXP_LAST_OFDM 11 +#define TXP_FIRST_OFDM_20_CDD 12 +#define TXP_LAST_OFDM_20_CDD 19 +#define TXP_FIRST_MCS_20_SISO 20 +#define TXP_LAST_MCS_20_SISO 27 +#define TXP_FIRST_MCS_20_CDD 28 +#define TXP_LAST_MCS_20_CDD 35 +#define TXP_FIRST_MCS_20_STBC 36 +#define TXP_LAST_MCS_20_STBC 43 +#define TXP_FIRST_MCS_20_SDM 44 +#define TXP_LAST_MCS_20_SDM 51 +#define TXP_FIRST_OFDM_40_SISO 52 +#define TXP_LAST_OFDM_40_SISO 59 +#define TXP_FIRST_OFDM_40_CDD 60 +#define TXP_LAST_OFDM_40_CDD 67 +#define TXP_FIRST_MCS_40_SISO 68 +#define TXP_LAST_MCS_40_SISO 75 +#define TXP_FIRST_MCS_40_CDD 76 +#define TXP_LAST_MCS_40_CDD 83 +#define TXP_FIRST_MCS_40_STBC 84 +#define TXP_LAST_MCS_40_STBC 91 +#define TXP_FIRST_MCS_40_SDM 92 +#define TXP_LAST_MCS_40_SDM 99 +#define TXP_MCS_32 100 +#define TXP_NUM_RATES 101 +#define ADJ_PWR_TBL_LEN 84 + +#define TXP_FIRST_SISO_MCS_20 20 +#define TXP_LAST_SISO_MCS_20 27 + +#define PHY_CORE_NUM_1 1 +#define PHY_CORE_NUM_2 2 +#define PHY_CORE_NUM_3 3 +#define PHY_CORE_NUM_4 4 +#define PHY_CORE_MAX PHY_CORE_NUM_4 +#define PHY_CORE_0 0 +#define PHY_CORE_1 1 +#define PHY_CORE_2 2 +#define PHY_CORE_3 3 + +#define MA_WINDOW_SZ 8 + +#define PHY_NOISE_SAMPLE_MON 1 +#define PHY_NOISE_SAMPLE_EXTERNAL 2 +#define PHY_NOISE_WINDOW_SZ 16 +#define PHY_NOISE_GLITCH_INIT_MA 10 +#define PHY_NOISE_GLITCH_INIT_MA_BADPlCP 10 +#define PHY_NOISE_STATE_MON 0x1 +#define PHY_NOISE_STATE_EXTERNAL 0x2 +#define PHY_NOISE_SAMPLE_LOG_NUM_NPHY 10 +#define PHY_NOISE_SAMPLE_LOG_NUM_UCODE 9 + +#define PHY_NOISE_OFFSETFACT_4322 (-103) +#define PHY_NOISE_MA_WINDOW_SZ 2 + +#define PHY_RSSI_TABLE_SIZE 64 +#define RSSI_ANT_MERGE_MAX 0 +#define RSSI_ANT_MERGE_MIN 1 +#define RSSI_ANT_MERGE_AVG 2 + +#define PHY_TSSI_TABLE_SIZE 64 +#define APHY_TSSI_TABLE_SIZE 256 +#define TX_GAIN_TABLE_LENGTH 64 +#define DEFAULT_11A_TXP_IDX 24 +#define NUM_TSSI_FRAMES 4 +#define NULL_TSSI 0x7f +#define NULL_TSSI_W 0x7f7f + +#define PHY_PAPD_EPS_TBL_SIZE_LCNPHY 64 + +#define LCNPHY_PERICAL_TEMPBASED_TXPWRCTRL 9 + +#define PHY_TXPWR_MIN 10 +#define PHY_TXPWR_MIN_NPHY 8 +#define RADIOPWR_OVERRIDE_DEF (-1) + +#define PWRTBL_NUM_COEFF 3 + +#define SPURAVOID_DISABLE 0 +#define SPURAVOID_AUTO 1 +#define SPURAVOID_FORCEON 2 +#define SPURAVOID_FORCEON2 3 + +#define PHY_SW_TIMER_FAST 15 +#define PHY_SW_TIMER_SLOW 60 +#define PHY_SW_TIMER_GLACIAL 120 + +#define PHY_PERICAL_AUTO 0 +#define PHY_PERICAL_FULL 1 +#define PHY_PERICAL_PARTIAL 2 + +#define PHY_PERICAL_NODELAY 0 +#define PHY_PERICAL_INIT_DELAY 5 +#define PHY_PERICAL_ASSOC_DELAY 5 +#define PHY_PERICAL_WDOG_DELAY 5 + +#define MPHASE_TXCAL_NUMCMDS 2 + +#define PHY_PERICAL_MPHASE_PENDING(pi) \ + (pi->mphase_cal_phase_id > MPHASE_CAL_STATE_IDLE) + +enum { + MPHASE_CAL_STATE_IDLE = 0, + MPHASE_CAL_STATE_INIT = 1, + MPHASE_CAL_STATE_TXPHASE0, + MPHASE_CAL_STATE_TXPHASE1, + MPHASE_CAL_STATE_TXPHASE2, + MPHASE_CAL_STATE_TXPHASE3, + MPHASE_CAL_STATE_TXPHASE4, + MPHASE_CAL_STATE_TXPHASE5, + MPHASE_CAL_STATE_PAPDCAL, + MPHASE_CAL_STATE_RXCAL, + MPHASE_CAL_STATE_RSSICAL, + MPHASE_CAL_STATE_IDLETSSI +}; + +enum phy_cal_mode { + CAL_FULL, + CAL_RECAL, + CAL_CURRECAL, + CAL_DIGCAL, + CAL_GCTRL, + CAL_SOFT, + CAL_DIGLO +}; + +#define RDR_NTIERS 1 +#define RDR_TIER_SIZE 64 +#define RDR_LIST_SIZE (512/3) +#define RDR_EPOCH_SIZE 40 +#define RDR_NANTENNAS 2 +#define RDR_NTIER_SIZE RDR_LIST_SIZE +#define RDR_LP_BUFFER_SIZE 64 +#define LP_LEN_HIS_SIZE 10 + +#define STATIC_NUM_RF 32 +#define STATIC_NUM_BB 9 + +#define BB_MULT_MASK 0x0000ffff +#define BB_MULT_VALID_MASK 0x80000000 + +#define CORDIC_AG 39797 +#define CORDIC_NI 18 +#define FIXED(X) ((s32)((X) << 16)) + +#define FLOAT(X) \ + (((X) >= 0) ? ((((X) >> 15) + 1) >> 1) : -((((-(X)) >> 15) + 1) >> 1)) + +#define PHY_CHAIN_TX_DISABLE_TEMP 115 +#define PHY_HYSTERESIS_DELTATEMP 5 + +#define SCAN_INPROG_PHY(pi) \ + (mboolisset(pi->measure_hold, PHY_HOLD_FOR_SCAN)) + +#define PLT_INPROG_PHY(pi) (mboolisset(pi->measure_hold, PHY_HOLD_FOR_PLT)) + +#define ASSOC_INPROG_PHY(pi) \ + (mboolisset(pi->measure_hold, PHY_HOLD_FOR_ASSOC)) + +#define SCAN_RM_IN_PROGRESS(pi) \ + (mboolisset(pi->measure_hold, PHY_HOLD_FOR_SCAN | PHY_HOLD_FOR_RM)) + +#define PHY_MUTED(pi) \ + (mboolisset(pi->measure_hold, PHY_HOLD_FOR_MUTE)) + +#define PUB_NOT_ASSOC(pi) \ + (mboolisset(pi->measure_hold, PHY_HOLD_FOR_NOT_ASSOC)) + +struct phy_table_info { + uint table; + int q; + uint max; +}; + +struct phytbl_info { + const void *tbl_ptr; + u32 tbl_len; + u32 tbl_id; + u32 tbl_offset; + u32 tbl_width; +}; + +struct interference_info { + u8 curr_home_channel; + u16 crsminpwrthld_40_stored; + u16 crsminpwrthld_20L_stored; + u16 crsminpwrthld_20U_stored; + u16 init_gain_code_core1_stored; + u16 init_gain_code_core2_stored; + u16 init_gain_codeb_core1_stored; + u16 init_gain_codeb_core2_stored; + u16 init_gain_table_stored[4]; + + u16 clip1_hi_gain_code_core1_stored; + u16 clip1_hi_gain_code_core2_stored; + u16 clip1_hi_gain_codeb_core1_stored; + u16 clip1_hi_gain_codeb_core2_stored; + u16 nb_clip_thresh_core1_stored; + u16 nb_clip_thresh_core2_stored; + u16 init_ofdmlna2gainchange_stored[4]; + u16 init_ccklna2gainchange_stored[4]; + u16 clip1_lo_gain_code_core1_stored; + u16 clip1_lo_gain_code_core2_stored; + u16 clip1_lo_gain_codeb_core1_stored; + u16 clip1_lo_gain_codeb_core2_stored; + u16 w1_clip_thresh_core1_stored; + u16 w1_clip_thresh_core2_stored; + u16 radio_2056_core1_rssi_gain_stored; + u16 radio_2056_core2_rssi_gain_stored; + u16 energy_drop_timeout_len_stored; + + u16 ed_crs40_assertthld0_stored; + u16 ed_crs40_assertthld1_stored; + u16 ed_crs40_deassertthld0_stored; + u16 ed_crs40_deassertthld1_stored; + u16 ed_crs20L_assertthld0_stored; + u16 ed_crs20L_assertthld1_stored; + u16 ed_crs20L_deassertthld0_stored; + u16 ed_crs20L_deassertthld1_stored; + u16 ed_crs20U_assertthld0_stored; + u16 ed_crs20U_assertthld1_stored; + u16 ed_crs20U_deassertthld0_stored; + u16 ed_crs20U_deassertthld1_stored; + + u16 badplcp_ma; + u16 badplcp_ma_previous; + u16 badplcp_ma_total; + u16 badplcp_ma_list[MA_WINDOW_SZ]; + int badplcp_ma_index; + s16 pre_badplcp_cnt; + s16 bphy_pre_badplcp_cnt; + + u16 init_gain_core1; + u16 init_gain_core2; + u16 init_gainb_core1; + u16 init_gainb_core2; + u16 init_gain_rfseq[4]; + + u16 crsminpwr0; + u16 crsminpwrl0; + u16 crsminpwru0; + + s16 crsminpwr_index; + + u16 radio_2057_core1_rssi_wb1a_gc_stored; + u16 radio_2057_core2_rssi_wb1a_gc_stored; + u16 radio_2057_core1_rssi_wb1g_gc_stored; + u16 radio_2057_core2_rssi_wb1g_gc_stored; + u16 radio_2057_core1_rssi_wb2_gc_stored; + u16 radio_2057_core2_rssi_wb2_gc_stored; + u16 radio_2057_core1_rssi_nb_gc_stored; + u16 radio_2057_core2_rssi_nb_gc_stored; +}; + +struct aci_save_gphy { + u16 rc_cal_ovr; + u16 phycrsth1; + u16 phycrsth2; + u16 init_n1p1_gain; + u16 p1_p2_gain; + u16 n1_n2_gain; + u16 n1_p1_gain; + u16 div_search_gain; + u16 div_p1_p2_gain; + u16 div_search_gn_change; + u16 table_7_2; + u16 table_7_3; + u16 cckshbits_gnref; + u16 clip_thresh; + u16 clip2_thresh; + u16 clip3_thresh; + u16 clip_p2_thresh; + u16 clip_pwdn_thresh; + u16 clip_n1p1_thresh; + u16 clip_n1_pwdn_thresh; + u16 bbconfig; + u16 cthr_sthr_shdin; + u16 energy; + u16 clip_p1_p2_thresh; + u16 threshold; + u16 reg15; + u16 reg16; + u16 reg17; + u16 div_srch_idx; + u16 div_srch_p1_p2; + u16 div_srch_gn_back; + u16 ant_dwell; + u16 ant_wr_settle; +}; + +struct lo_complex_abgphy_info { + s8 i; + s8 q; +}; + +struct nphy_iq_comp { + s16 a0; + s16 b0; + s16 a1; + s16 b1; +}; + +struct nphy_txpwrindex { + s8 index; + s8 index_internal; + s8 index_internal_save; + u16 AfectrlOverride; + u16 AfeCtrlDacGain; + u16 rad_gain; + u8 bbmult; + u16 iqcomp_a; + u16 iqcomp_b; + u16 locomp; +}; + +struct txiqcal_cache { + + u16 txcal_coeffs_2G[8]; + u16 txcal_radio_regs_2G[8]; + struct nphy_iq_comp rxcal_coeffs_2G; + + u16 txcal_coeffs_5G[8]; + u16 txcal_radio_regs_5G[8]; + struct nphy_iq_comp rxcal_coeffs_5G; +}; + +struct nphy_pwrctrl { + s8 max_pwr_2g; + s8 idle_targ_2g; + s16 pwrdet_2g_a1; + s16 pwrdet_2g_b0; + s16 pwrdet_2g_b1; + s8 max_pwr_5gm; + s8 idle_targ_5gm; + s8 max_pwr_5gh; + s8 max_pwr_5gl; + s16 pwrdet_5gm_a1; + s16 pwrdet_5gm_b0; + s16 pwrdet_5gm_b1; + s16 pwrdet_5gl_a1; + s16 pwrdet_5gl_b0; + s16 pwrdet_5gl_b1; + s16 pwrdet_5gh_a1; + s16 pwrdet_5gh_b0; + s16 pwrdet_5gh_b1; + s8 idle_targ_5gl; + s8 idle_targ_5gh; + s8 idle_tssi_2g; + s8 idle_tssi_5g; + s8 idle_tssi; + s16 a1; + s16 b0; + s16 b1; +}; + +struct nphy_txgains { + u16 txlpf[2]; + u16 txgm[2]; + u16 pga[2]; + u16 pad[2]; + u16 ipa[2]; +}; + +#define PHY_NOISEVAR_BUFSIZE 10 + +struct nphy_noisevar_buf { + int bufcount; + int tone_id[PHY_NOISEVAR_BUFSIZE]; + u32 noise_vars[PHY_NOISEVAR_BUFSIZE]; + u32 min_noise_vars[PHY_NOISEVAR_BUFSIZE]; +}; + +struct rssical_cache { + u16 rssical_radio_regs_2G[2]; + u16 rssical_phyregs_2G[12]; + + u16 rssical_radio_regs_5G[2]; + u16 rssical_phyregs_5G[12]; +}; + +struct lcnphy_cal_results { + + u16 txiqlocal_a; + u16 txiqlocal_b; + u16 txiqlocal_didq; + u8 txiqlocal_ei0; + u8 txiqlocal_eq0; + u8 txiqlocal_fi0; + u8 txiqlocal_fq0; + + u16 txiqlocal_bestcoeffs[11]; + u16 txiqlocal_bestcoeffs_valid; + + u32 papd_eps_tbl[PHY_PAPD_EPS_TBL_SIZE_LCNPHY]; + u16 analog_gain_ref; + u16 lut_begin; + u16 lut_end; + u16 lut_step; + u16 rxcompdbm; + u16 papdctrl; + u16 sslpnCalibClkEnCtrl; + + u16 rxiqcal_coeff_a0; + u16 rxiqcal_coeff_b0; +}; + +struct shared_phy { + struct brcms_phy *phy_head; + uint unit; + struct phy_shim_info *physhim; + uint corerev; + u32 machwcap; + bool up; + bool clk; + uint now; + u16 vid; + u16 did; + uint chip; + uint chiprev; + uint chippkg; + uint sromrev; + uint boardtype; + uint boardrev; + u32 boardflags; + u32 boardflags2; + uint fast_timer; + uint slow_timer; + uint glacial_timer; + u8 rx_antdiv; + s8 phy_noise_window[MA_WINDOW_SZ]; + uint phy_noise_index; + u8 hw_phytxchain; + u8 hw_phyrxchain; + u8 phytxchain; + u8 phyrxchain; + u8 rssi_mode; + bool _rifs_phy; +}; + +struct brcms_phy_pub { + uint phy_type; + uint phy_rev; + u8 phy_corenum; + u16 radioid; + u8 radiorev; + u8 radiover; + + uint coreflags; + uint ana_rev; + bool abgphy_encore; +}; + +struct phy_func_ptr { + void (*init)(struct brcms_phy *); + void (*calinit)(struct brcms_phy *); + void (*chanset)(struct brcms_phy *, u16 chanspec); + void (*txpwrrecalc)(struct brcms_phy *); + int (*longtrn)(struct brcms_phy *, int); + void (*txiqccget)(struct brcms_phy *, u16 *, u16 *); + void (*txiqccset)(struct brcms_phy *, u16, u16); + u16 (*txloccget)(struct brcms_phy *); + void (*radioloftget)(struct brcms_phy *, u8 *, u8 *, u8 *, u8 *); + void (*carrsuppr)(struct brcms_phy *); + s32 (*rxsigpwr)(struct brcms_phy *, s32); + void (*detach)(struct brcms_phy *); +}; + +struct brcms_phy { + struct brcms_phy_pub pubpi_ro; + struct shared_phy *sh; + struct phy_func_ptr pi_fptr; + + union { + struct brcms_phy_lcnphy *pi_lcnphy; + } u; + bool user_txpwr_at_rfport; + + struct bcma_device *d11core; + struct brcms_phy *next; + struct brcms_phy_pub pubpi; + + bool do_initcal; + bool phytest_on; + bool ofdm_rateset_war; + bool bf_preempt_4306; + u16 radio_chanspec; + u8 antsel_type; + u16 bw; + u8 txpwr_percent; + bool phy_init_por; + + bool init_in_progress; + bool initialized; + bool sbtml_gm; + uint refcnt; + bool watchdog_override; + u8 phynoise_state; + uint phynoise_now; + int phynoise_chan_watchdog; + bool phynoise_polling; + bool disable_percal; + u32 measure_hold; + + s16 txpa_2g[PWRTBL_NUM_COEFF]; + s16 txpa_2g_low_temp[PWRTBL_NUM_COEFF]; + s16 txpa_2g_high_temp[PWRTBL_NUM_COEFF]; + s16 txpa_5g_low[PWRTBL_NUM_COEFF]; + s16 txpa_5g_mid[PWRTBL_NUM_COEFF]; + s16 txpa_5g_hi[PWRTBL_NUM_COEFF]; + + u8 tx_srom_max_2g; + u8 tx_srom_max_5g_low; + u8 tx_srom_max_5g_mid; + u8 tx_srom_max_5g_hi; + u8 tx_srom_max_rate_2g[TXP_NUM_RATES]; + u8 tx_srom_max_rate_5g_low[TXP_NUM_RATES]; + u8 tx_srom_max_rate_5g_mid[TXP_NUM_RATES]; + u8 tx_srom_max_rate_5g_hi[TXP_NUM_RATES]; + u8 tx_user_target[TXP_NUM_RATES]; + s8 tx_power_offset[TXP_NUM_RATES]; + u8 tx_power_target[TXP_NUM_RATES]; + + struct brcms_phy_srom_fem srom_fem2g; + struct brcms_phy_srom_fem srom_fem5g; + + u8 tx_power_max; + u8 tx_power_max_rate_ind; + bool hwpwrctrl; + u8 nphy_txpwrctrl; + s8 nphy_txrx_chain; + bool phy_5g_pwrgain; + + u16 phy_wreg; + u16 phy_wreg_limit; + + s8 n_preamble_override; + u8 antswitch; + u8 aa2g, aa5g; + + s8 idle_tssi[CH_5G_GROUP]; + s8 target_idle_tssi; + s8 txpwr_est_Pout; + u8 tx_power_min; + u8 txpwr_limit[TXP_NUM_RATES]; + u8 txpwr_env_limit[TXP_NUM_RATES]; + u8 adj_pwr_tbl_nphy[ADJ_PWR_TBL_LEN]; + + bool channel_14_wide_filter; + + bool txpwroverride; + bool txpwridx_override_aphy; + s16 radiopwr_override; + u16 hwpwr_txcur; + u8 saved_txpwr_idx; + + bool edcrs_threshold_lock; + + u32 tr_R_gain_val; + u32 tr_T_gain_val; + + s16 ofdm_analog_filt_bw_override; + s16 cck_analog_filt_bw_override; + s16 ofdm_rccal_override; + s16 cck_rccal_override; + u16 extlna_type; + + uint interference_mode_crs_time; + u16 crsglitch_prev; + bool interference_mode_crs; + + u32 phy_tx_tone_freq; + uint phy_lastcal; + bool phy_forcecal; + bool phy_fixed_noise; + u32 xtalfreq; + u8 pdiv; + s8 carrier_suppr_disable; + + bool phy_bphy_evm; + bool phy_bphy_rfcs; + s8 phy_scraminit; + u8 phy_gpiosel; + + s16 phy_txcore_disable_temp; + s16 phy_txcore_enable_temp; + s8 phy_tempsense_offset; + bool phy_txcore_heatedup; + + u16 radiopwr; + u16 bb_atten; + u16 txctl1; + + u16 mintxbias; + u16 mintxmag; + struct lo_complex_abgphy_info gphy_locomp_iq + [STATIC_NUM_RF][STATIC_NUM_BB]; + s8 stats_11b_txpower[STATIC_NUM_RF][STATIC_NUM_BB]; + u16 gain_table[TX_GAIN_TABLE_LENGTH]; + bool loopback_gain; + s16 max_lpback_gain_hdB; + s16 trsw_rx_gain_hdB; + u8 power_vec[8]; + + u16 rc_cal; + int nrssi_table_delta; + int nrssi_slope_scale; + int nrssi_slope_offset; + int min_rssi; + int max_rssi; + + s8 txpwridx; + u8 min_txpower; + + u8 a_band_high_disable; + + u16 tx_vos; + u16 global_tx_bb_dc_bias_loft; + + int rf_max; + int bb_max; + int rf_list_size; + int bb_list_size; + u16 *rf_attn_list; + u16 *bb_attn_list; + u16 padmix_mask; + u16 padmix_reg; + u16 *txmag_list; + uint txmag_len; + bool txmag_enable; + + s8 *a_tssi_to_dbm; + s8 *m_tssi_to_dbm; + s8 *l_tssi_to_dbm; + s8 *h_tssi_to_dbm; + u8 *hwtxpwr; + + u16 freqtrack_saved_regs[2]; + int cur_interference_mode; + bool hwpwrctrl_capable; + bool temppwrctrl_capable; + + uint phycal_nslope; + uint phycal_noffset; + uint phycal_mlo; + uint phycal_txpower; + + u8 phy_aa2g; + + bool nphy_tableloaded; + s8 nphy_rssisel; + u32 nphy_bb_mult_save; + u16 nphy_txiqlocal_bestc[11]; + bool nphy_txiqlocal_coeffsvalid; + struct nphy_txpwrindex nphy_txpwrindex[PHY_CORE_NUM_2]; + struct nphy_pwrctrl nphy_pwrctrl_info[PHY_CORE_NUM_2]; + u16 cck2gpo; + u32 ofdm2gpo; + u32 ofdm5gpo; + u32 ofdm5glpo; + u32 ofdm5ghpo; + u8 bw402gpo; + u8 bw405gpo; + u8 bw405glpo; + u8 bw405ghpo; + u8 cdd2gpo; + u8 cdd5gpo; + u8 cdd5glpo; + u8 cdd5ghpo; + u8 stbc2gpo; + u8 stbc5gpo; + u8 stbc5glpo; + u8 stbc5ghpo; + u8 bwdup2gpo; + u8 bwdup5gpo; + u8 bwdup5glpo; + u8 bwdup5ghpo; + u16 mcs2gpo[8]; + u16 mcs5gpo[8]; + u16 mcs5glpo[8]; + u16 mcs5ghpo[8]; + u32 nphy_rxcalparams; + + u8 phy_spuravoid; + bool phy_isspuravoid; + + u8 phy_pabias; + u8 nphy_papd_skip; + u8 nphy_tssi_slope; + + s16 nphy_noise_win[PHY_CORE_MAX][PHY_NOISE_WINDOW_SZ]; + u8 nphy_noise_index; + + bool nphy_gain_boost; + bool nphy_elna_gain_config; + u16 old_bphy_test; + u16 old_bphy_testcontrol; + + bool phyhang_avoid; + + bool rssical_nphy; + u8 nphy_perical; + uint nphy_perical_last; + u8 cal_type_override; + u8 mphase_cal_phase_id; + u8 mphase_txcal_cmdidx; + u8 mphase_txcal_numcmds; + u16 mphase_txcal_bestcoeffs[11]; + u16 nphy_txiqlocal_chanspec; + u16 nphy_iqcal_chanspec_2G; + u16 nphy_iqcal_chanspec_5G; + u16 nphy_rssical_chanspec_2G; + u16 nphy_rssical_chanspec_5G; + struct wlapi_timer *phycal_timer; + bool use_int_tx_iqlo_cal_nphy; + bool internal_tx_iqlo_cal_tapoff_intpa_nphy; + s16 nphy_lastcal_temp; + + struct txiqcal_cache calibration_cache; + struct rssical_cache rssical_cache; + + u8 nphy_txpwr_idx[2]; + u8 nphy_papd_cal_type; + uint nphy_papd_last_cal; + u16 nphy_papd_tx_gain_at_last_cal[2]; + u8 nphy_papd_cal_gain_index[2]; + s16 nphy_papd_epsilon_offset[2]; + bool nphy_papd_recal_enable; + u32 nphy_papd_recal_counter; + bool nphy_force_papd_cal; + bool nphy_papdcomp; + bool ipa2g_on; + bool ipa5g_on; + + u16 classifier_state; + u16 clip_state[2]; + uint nphy_deaf_count; + u8 rxiq_samps; + u8 rxiq_antsel; + + u16 rfctrlIntc1_save; + u16 rfctrlIntc2_save; + bool first_cal_after_assoc; + u16 tx_rx_cal_radio_saveregs[22]; + u16 tx_rx_cal_phy_saveregs[15]; + + u8 nphy_cal_orig_pwr_idx[2]; + u8 nphy_txcal_pwr_idx[2]; + u8 nphy_rxcal_pwr_idx[2]; + u16 nphy_cal_orig_tx_gain[2]; + struct nphy_txgains nphy_cal_target_gain; + u16 nphy_txcal_bbmult; + u16 nphy_gmval; + + u16 nphy_saved_bbconf; + + bool nphy_gband_spurwar_en; + bool nphy_gband_spurwar2_en; + bool nphy_aband_spurwar_en; + u16 nphy_rccal_value; + u16 nphy_crsminpwr[3]; + struct nphy_noisevar_buf nphy_saved_noisevars; + bool nphy_anarxlpf_adjusted; + bool nphy_crsminpwr_adjusted; + bool nphy_noisevars_adjusted; + + bool nphy_rxcal_active; + u16 radar_percal_mask; + bool dfs_lp_buffer_nphy; + + u16 nphy_fineclockgatecontrol; + + s8 rx2tx_biasentry; + + u16 crsminpwr0; + u16 crsminpwrl0; + u16 crsminpwru0; + s16 noise_crsminpwr_index; + u16 init_gain_core1; + u16 init_gain_core2; + u16 init_gainb_core1; + u16 init_gainb_core2; + u8 aci_noise_curr_channel; + u16 init_gain_rfseq[4]; + + bool radio_is_on; + + bool nphy_sample_play_lpf_bw_ctl_ovr; + + u16 tbl_data_hi; + u16 tbl_data_lo; + u16 tbl_addr; + + uint tbl_save_id; + uint tbl_save_offset; + + u8 txpwrctrl; + s8 txpwrindex[PHY_CORE_MAX]; + + u8 phycal_tempdelta; + u32 mcs20_po; + u32 mcs40_po; + struct wiphy *wiphy; +}; + +struct cs32 { + s32 q; + s32 i; +}; + +struct radio_regs { + u16 address; + u32 init_a; + u32 init_g; + u8 do_init_a; + u8 do_init_g; +}; + +struct radio_20xx_regs { + u16 address; + u8 init; + u8 do_init; +}; + +struct lcnphy_radio_regs { + u16 address; + u8 init_a; + u8 init_g; + u8 do_init_a; + u8 do_init_g; +}; + +extern u16 read_phy_reg(struct brcms_phy *pi, u16 addr); +extern void write_phy_reg(struct brcms_phy *pi, u16 addr, u16 val); +extern void and_phy_reg(struct brcms_phy *pi, u16 addr, u16 val); +extern void or_phy_reg(struct brcms_phy *pi, u16 addr, u16 val); +extern void mod_phy_reg(struct brcms_phy *pi, u16 addr, u16 mask, u16 val); + +extern u16 read_radio_reg(struct brcms_phy *pi, u16 addr); +extern void or_radio_reg(struct brcms_phy *pi, u16 addr, u16 val); +extern void and_radio_reg(struct brcms_phy *pi, u16 addr, u16 val); +extern void mod_radio_reg(struct brcms_phy *pi, u16 addr, u16 mask, + u16 val); +extern void xor_radio_reg(struct brcms_phy *pi, u16 addr, u16 mask); + +extern void write_radio_reg(struct brcms_phy *pi, u16 addr, u16 val); + +extern void wlc_phyreg_enter(struct brcms_phy_pub *pih); +extern void wlc_phyreg_exit(struct brcms_phy_pub *pih); +extern void wlc_radioreg_enter(struct brcms_phy_pub *pih); +extern void wlc_radioreg_exit(struct brcms_phy_pub *pih); + +extern void wlc_phy_read_table(struct brcms_phy *pi, + const struct phytbl_info *ptbl_info, + u16 tblAddr, u16 tblDataHi, + u16 tblDatalo); +extern void wlc_phy_write_table(struct brcms_phy *pi, + const struct phytbl_info *ptbl_info, + u16 tblAddr, u16 tblDataHi, u16 tblDatalo); +extern void wlc_phy_table_addr(struct brcms_phy *pi, uint tbl_id, + uint tbl_offset, u16 tblAddr, u16 tblDataHi, + u16 tblDataLo); +extern void wlc_phy_table_data_write(struct brcms_phy *pi, uint width, u32 val); + +extern void write_phy_channel_reg(struct brcms_phy *pi, uint val); +extern void wlc_phy_txpower_update_shm(struct brcms_phy *pi); + +extern u8 wlc_phy_nbits(s32 value); +extern void wlc_phy_compute_dB(u32 *cmplx_pwr, s8 *p_dB, u8 core); + +extern uint wlc_phy_init_radio_regs_allbands(struct brcms_phy *pi, + struct radio_20xx_regs *radioregs); +extern uint wlc_phy_init_radio_regs(struct brcms_phy *pi, + const struct radio_regs *radioregs, + u16 core_offset); + +extern void wlc_phy_txpower_ipa_upd(struct brcms_phy *pi); + +extern void wlc_phy_do_dummy_tx(struct brcms_phy *pi, bool ofdm, bool pa_on); +extern void wlc_phy_papd_decode_epsilon(u32 epsilon, s32 *eps_real, + s32 *eps_imag); + +extern void wlc_phy_cal_perical_mphase_reset(struct brcms_phy *pi); +extern void wlc_phy_cal_perical_mphase_restart(struct brcms_phy *pi); + +extern bool wlc_phy_attach_nphy(struct brcms_phy *pi); +extern bool wlc_phy_attach_lcnphy(struct brcms_phy *pi); + +extern void wlc_phy_detach_lcnphy(struct brcms_phy *pi); + +extern void wlc_phy_init_nphy(struct brcms_phy *pi); +extern void wlc_phy_init_lcnphy(struct brcms_phy *pi); + +extern void wlc_phy_cal_init_nphy(struct brcms_phy *pi); +extern void wlc_phy_cal_init_lcnphy(struct brcms_phy *pi); + +extern void wlc_phy_chanspec_set_nphy(struct brcms_phy *pi, + u16 chanspec); +extern void wlc_phy_chanspec_set_lcnphy(struct brcms_phy *pi, + u16 chanspec); +extern void wlc_phy_chanspec_set_fixup_lcnphy(struct brcms_phy *pi, + u16 chanspec); +extern int wlc_phy_channel2freq(uint channel); +extern int wlc_phy_chanspec_freq2bandrange_lpssn(uint); +extern int wlc_phy_chanspec_bandrange_get(struct brcms_phy *, u16 chanspec); + +extern void wlc_lcnphy_set_tx_pwr_ctrl(struct brcms_phy *pi, u16 mode); +extern s8 wlc_lcnphy_get_current_tx_pwr_idx(struct brcms_phy *pi); + +extern void wlc_phy_txpower_recalc_target_nphy(struct brcms_phy *pi); +extern void wlc_lcnphy_txpower_recalc_target(struct brcms_phy *pi); +extern void wlc_phy_txpower_recalc_target_lcnphy(struct brcms_phy *pi); + +extern void wlc_lcnphy_set_tx_pwr_by_index(struct brcms_phy *pi, int index); +extern void wlc_lcnphy_tx_pu(struct brcms_phy *pi, bool bEnable); +extern void wlc_lcnphy_stop_tx_tone(struct brcms_phy *pi); +extern void wlc_lcnphy_start_tx_tone(struct brcms_phy *pi, s32 f_kHz, + u16 max_val, bool iqcalmode); + +extern void wlc_phy_txpower_sromlimit_get_nphy(struct brcms_phy *pi, uint chan, + u8 *max_pwr, u8 rate_id); +extern void wlc_phy_ofdm_to_mcs_powers_nphy(u8 *power, u8 rate_mcs_start, + u8 rate_mcs_end, + u8 rate_ofdm_start); +extern void wlc_phy_mcs_to_ofdm_powers_nphy(u8 *power, + u8 rate_ofdm_start, + u8 rate_ofdm_end, + u8 rate_mcs_start); + +extern u16 wlc_lcnphy_tempsense(struct brcms_phy *pi, bool mode); +extern s16 wlc_lcnphy_tempsense_new(struct brcms_phy *pi, bool mode); +extern s8 wlc_lcnphy_tempsense_degree(struct brcms_phy *pi, bool mode); +extern s8 wlc_lcnphy_vbatsense(struct brcms_phy *pi, bool mode); +extern void wlc_phy_carrier_suppress_lcnphy(struct brcms_phy *pi); +extern void wlc_lcnphy_crsuprs(struct brcms_phy *pi, int channel); +extern void wlc_lcnphy_epa_switch(struct brcms_phy *pi, bool mode); +extern void wlc_2064_vco_cal(struct brcms_phy *pi); + +extern void wlc_phy_txpower_recalc_target(struct brcms_phy *pi); + +#define LCNPHY_TBL_ID_PAPDCOMPDELTATBL 0x18 +#define LCNPHY_TX_POWER_TABLE_SIZE 128 +#define LCNPHY_MAX_TX_POWER_INDEX (LCNPHY_TX_POWER_TABLE_SIZE - 1) +#define LCNPHY_TBL_ID_TXPWRCTL 0x07 +#define LCNPHY_TX_PWR_CTRL_OFF 0 +#define LCNPHY_TX_PWR_CTRL_SW (0x1 << 15) +#define LCNPHY_TX_PWR_CTRL_HW ((0x1 << 15) | \ + (0x1 << 14) | \ + (0x1 << 13)) + +#define LCNPHY_TX_PWR_CTRL_TEMPBASED 0xE001 + +extern void wlc_lcnphy_write_table(struct brcms_phy *pi, + const struct phytbl_info *pti); +extern void wlc_lcnphy_read_table(struct brcms_phy *pi, + struct phytbl_info *pti); +extern void wlc_lcnphy_set_tx_iqcc(struct brcms_phy *pi, u16 a, u16 b); +extern void wlc_lcnphy_set_tx_locc(struct brcms_phy *pi, u16 didq); +extern void wlc_lcnphy_get_tx_iqcc(struct brcms_phy *pi, u16 *a, u16 *b); +extern u16 wlc_lcnphy_get_tx_locc(struct brcms_phy *pi); +extern void wlc_lcnphy_get_radio_loft(struct brcms_phy *pi, u8 *ei0, + u8 *eq0, u8 *fi0, u8 *fq0); +extern void wlc_lcnphy_calib_modes(struct brcms_phy *pi, uint mode); +extern void wlc_lcnphy_deaf_mode(struct brcms_phy *pi, bool mode); +extern bool wlc_phy_tpc_isenabled_lcnphy(struct brcms_phy *pi); +extern void wlc_lcnphy_tx_pwr_update_npt(struct brcms_phy *pi); +extern s32 wlc_lcnphy_tssi2dbm(s32 tssi, s32 a1, s32 b0, s32 b1); +extern void wlc_lcnphy_get_tssi(struct brcms_phy *pi, s8 *ofdm_pwr, + s8 *cck_pwr); +extern void wlc_lcnphy_tx_power_adjustment(struct brcms_phy_pub *ppi); + +extern s32 wlc_lcnphy_rx_signal_power(struct brcms_phy *pi, s32 gain_index); + +#define NPHY_MAX_HPVGA1_INDEX 10 +#define NPHY_DEF_HPVGA1_INDEXLIMIT 7 + +struct phy_iq_est { + s32 iq_prod; + u32 i_pwr; + u32 q_pwr; +}; + +extern void wlc_phy_stay_in_carriersearch_nphy(struct brcms_phy *pi, + bool enable); +extern void wlc_nphy_deaf_mode(struct brcms_phy *pi, bool mode); + +#define wlc_phy_write_table_nphy(pi, pti) \ + wlc_phy_write_table(pi, pti, 0x72, 0x74, 0x73) + +#define wlc_phy_read_table_nphy(pi, pti) \ + wlc_phy_read_table(pi, pti, 0x72, 0x74, 0x73) + +#define wlc_nphy_table_addr(pi, id, off) \ + wlc_phy_table_addr((pi), (id), (off), 0x72, 0x74, 0x73) + +#define wlc_nphy_table_data_write(pi, w, v) \ + wlc_phy_table_data_write((pi), (w), (v)) + +extern void wlc_phy_table_read_nphy(struct brcms_phy *pi, u32, u32 l, u32 o, + u32 w, void *d); +extern void wlc_phy_table_write_nphy(struct brcms_phy *pi, u32, u32, u32, + u32, const void *); + +#define PHY_IPA(pi) \ + ((pi->ipa2g_on && CHSPEC_IS2G(pi->radio_chanspec)) || \ + (pi->ipa5g_on && CHSPEC_IS5G(pi->radio_chanspec))) + +#define BRCMS_PHY_WAR_PR51571(pi) \ + if (NREV_LT((pi)->pubpi.phy_rev, 3)) \ + (void)bcma_read32(pi->d11core, D11REGOFFS(maccontrol)) + +extern void wlc_phy_cal_perical_nphy_run(struct brcms_phy *pi, u8 caltype); +extern void wlc_phy_aci_reset_nphy(struct brcms_phy *pi); +extern void wlc_phy_pa_override_nphy(struct brcms_phy *pi, bool en); + +extern u8 wlc_phy_get_chan_freq_range_nphy(struct brcms_phy *pi, uint chan); +extern void wlc_phy_switch_radio_nphy(struct brcms_phy *pi, bool on); + +extern void wlc_phy_stf_chain_upd_nphy(struct brcms_phy *pi); + +extern void wlc_phy_force_rfseq_nphy(struct brcms_phy *pi, u8 cmd); +extern s16 wlc_phy_tempsense_nphy(struct brcms_phy *pi); + +extern u16 wlc_phy_classifier_nphy(struct brcms_phy *pi, u16 mask, u16 val); + +extern void wlc_phy_rx_iq_est_nphy(struct brcms_phy *pi, struct phy_iq_est *est, + u16 num_samps, u8 wait_time, + u8 wait_for_crs); + +extern void wlc_phy_rx_iq_coeffs_nphy(struct brcms_phy *pi, u8 write, + struct nphy_iq_comp *comp); +extern void wlc_phy_aci_and_noise_reduction_nphy(struct brcms_phy *pi); + +extern void wlc_phy_rxcore_setstate_nphy(struct brcms_phy_pub *pih, + u8 rxcore_bitmask); +extern u8 wlc_phy_rxcore_getstate_nphy(struct brcms_phy_pub *pih); + +extern void wlc_phy_txpwrctrl_enable_nphy(struct brcms_phy *pi, u8 ctrl_type); +extern void wlc_phy_txpwr_fixpower_nphy(struct brcms_phy *pi); +extern void wlc_phy_txpwr_apply_nphy(struct brcms_phy *pi); +extern void wlc_phy_txpwr_papd_cal_nphy(struct brcms_phy *pi); +extern u16 wlc_phy_txpwr_idx_get_nphy(struct brcms_phy *pi); + +extern struct nphy_txgains wlc_phy_get_tx_gain_nphy(struct brcms_phy *pi); +extern int wlc_phy_cal_txiqlo_nphy(struct brcms_phy *pi, + struct nphy_txgains target_gain, + bool full, bool m); +extern int wlc_phy_cal_rxiq_nphy(struct brcms_phy *pi, + struct nphy_txgains target_gain, + u8 type, bool d); +extern void wlc_phy_txpwr_index_nphy(struct brcms_phy *pi, u8 core_mask, + s8 txpwrindex, bool res); +extern void wlc_phy_rssisel_nphy(struct brcms_phy *pi, u8 core, u8 rssi_type); +extern int wlc_phy_poll_rssi_nphy(struct brcms_phy *pi, u8 rssi_type, + s32 *rssi_buf, u8 nsamps); +extern void wlc_phy_rssi_cal_nphy(struct brcms_phy *pi); +extern int wlc_phy_aci_scan_nphy(struct brcms_phy *pi); +extern void wlc_phy_cal_txgainctrl_nphy(struct brcms_phy *pi, + s32 dBm_targetpower, bool debug); +extern int wlc_phy_tx_tone_nphy(struct brcms_phy *pi, u32 f_kHz, u16 max_val, + u8 mode, u8, bool); +extern void wlc_phy_stopplayback_nphy(struct brcms_phy *pi); +extern void wlc_phy_est_tonepwr_nphy(struct brcms_phy *pi, s32 *qdBm_pwrbuf, + u8 num_samps); +extern void wlc_phy_radio205x_vcocal_nphy(struct brcms_phy *pi); + +extern int wlc_phy_rssi_compute_nphy(struct brcms_phy *pi, + struct d11rxhdr *rxh); + +#define NPHY_TESTPATTERN_BPHY_EVM 0 +#define NPHY_TESTPATTERN_BPHY_RFCS 1 + +extern void wlc_phy_nphy_tkip_rifs_war(struct brcms_phy *pi, u8 rifs); + +void wlc_phy_get_pwrdet_offsets(struct brcms_phy *pi, s8 *cckoffset, + s8 *ofdmoffset); +extern s8 wlc_phy_upd_rssi_offset(struct brcms_phy *pi, s8 rssi, + u16 chanspec); + +extern bool wlc_phy_n_txpower_ipa_ison(struct brcms_phy *pih); +#endif /* _BRCM_PHY_INT_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_lcn.c b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_lcn.c new file mode 100644 index 0000000..3d6b16c --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_lcn.c @@ -0,0 +1,5148 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include +#include +#include "phy_qmath.h" +#include "phy_hal.h" +#include "phy_radio.h" +#include "phytbl_lcn.h" +#include "phy_lcn.h" + +#define PLL_2064_NDIV 90 +#define PLL_2064_LOW_END_VCO 3000 +#define PLL_2064_LOW_END_KVCO 27 +#define PLL_2064_HIGH_END_VCO 4200 +#define PLL_2064_HIGH_END_KVCO 68 +#define PLL_2064_LOOP_BW_DOUBLER 200 +#define PLL_2064_D30_DOUBLER 10500 +#define PLL_2064_LOOP_BW 260 +#define PLL_2064_D30 8000 +#define PLL_2064_CAL_REF_TO 8 +#define PLL_2064_MHZ 1000000 +#define PLL_2064_OPEN_LOOP_DELAY 5 + +#define TEMPSENSE 1 +#define VBATSENSE 2 + +#define NOISE_IF_UPD_CHK_INTERVAL 1 +#define NOISE_IF_UPD_RST_INTERVAL 60 +#define NOISE_IF_UPD_THRESHOLD_CNT 1 +#define NOISE_IF_UPD_TRHRESHOLD 50 +#define NOISE_IF_UPD_TIMEOUT 1000 +#define NOISE_IF_OFF 0 +#define NOISE_IF_CHK 1 +#define NOISE_IF_ON 2 + +#define PAPD_BLANKING_PROFILE 3 +#define PAPD2LUT 0 +#define PAPD_CORR_NORM 0 +#define PAPD_BLANKING_THRESHOLD 0 +#define PAPD_STOP_AFTER_LAST_UPDATE 0 + +#define LCN_TARGET_PWR 60 + +#define LCN_VBAT_OFFSET_433X 34649679 +#define LCN_VBAT_SLOPE_433X 8258032 + +#define LCN_VBAT_SCALE_NOM 53 +#define LCN_VBAT_SCALE_DEN 432 + +#define LCN_TEMPSENSE_OFFSET 80812 +#define LCN_TEMPSENSE_DEN 2647 + +#define LCN_BW_LMT 200 +#define LCN_CUR_LMT 1250 +#define LCN_MULT 1 +#define LCN_VCO_DIV 30 +#define LCN_OFFSET 680 +#define LCN_FACT 490 +#define LCN_CUR_DIV 2640 + +#define LCNPHY_txgainctrlovrval1_pagain_ovr_val1_SHIFT \ + (0 + 8) +#define LCNPHY_txgainctrlovrval1_pagain_ovr_val1_MASK \ + (0x7f << LCNPHY_txgainctrlovrval1_pagain_ovr_val1_SHIFT) + +#define LCNPHY_stxtxgainctrlovrval1_pagain_ovr_val1_SHIFT \ + (0 + 8) +#define LCNPHY_stxtxgainctrlovrval1_pagain_ovr_val1_MASK \ + (0x7f << LCNPHY_stxtxgainctrlovrval1_pagain_ovr_val1_SHIFT) + +#define wlc_lcnphy_enable_tx_gain_override(pi) \ + wlc_lcnphy_set_tx_gain_override(pi, true) +#define wlc_lcnphy_disable_tx_gain_override(pi) \ + wlc_lcnphy_set_tx_gain_override(pi, false) + +#define wlc_lcnphy_iqcal_active(pi) \ + (read_phy_reg((pi), 0x451) & \ + ((0x1 << 15) | (0x1 << 14))) + +#define txpwrctrl_off(pi) (0x7 != ((read_phy_reg(pi, 0x4a4) & 0xE000) >> 13)) +#define wlc_lcnphy_tempsense_based_pwr_ctrl_enabled(pi) \ + (pi->temppwrctrl_capable) +#define wlc_lcnphy_tssi_based_pwr_ctrl_enabled(pi) \ + (pi->hwpwrctrl_capable) + +#define SWCTRL_BT_TX 0x18 +#define SWCTRL_OVR_DISABLE 0x40 + +#define AFE_CLK_INIT_MODE_TXRX2X 1 +#define AFE_CLK_INIT_MODE_PAPD 0 + +#define LCNPHY_TBL_ID_IQLOCAL 0x00 + +#define LCNPHY_TBL_ID_RFSEQ 0x08 +#define LCNPHY_TBL_ID_GAIN_IDX 0x0d +#define LCNPHY_TBL_ID_SW_CTRL 0x0f +#define LCNPHY_TBL_ID_GAIN_TBL 0x12 +#define LCNPHY_TBL_ID_SPUR 0x14 +#define LCNPHY_TBL_ID_SAMPLEPLAY 0x15 +#define LCNPHY_TBL_ID_SAMPLEPLAY1 0x16 + +#define LCNPHY_TX_PWR_CTRL_RATE_OFFSET 832 +#define LCNPHY_TX_PWR_CTRL_MAC_OFFSET 128 +#define LCNPHY_TX_PWR_CTRL_GAIN_OFFSET 192 +#define LCNPHY_TX_PWR_CTRL_IQ_OFFSET 320 +#define LCNPHY_TX_PWR_CTRL_LO_OFFSET 448 +#define LCNPHY_TX_PWR_CTRL_PWR_OFFSET 576 + +#define LCNPHY_TX_PWR_CTRL_START_INDEX_2G_4313 140 + +#define LCNPHY_TX_PWR_CTRL_START_NPT 1 +#define LCNPHY_TX_PWR_CTRL_MAX_NPT 7 + +#define LCNPHY_NOISE_SAMPLES_DEFAULT 5000 + +#define LCNPHY_ACI_DETECT_START 1 +#define LCNPHY_ACI_DETECT_PROGRESS 2 +#define LCNPHY_ACI_DETECT_STOP 3 + +#define LCNPHY_ACI_CRSHIFRMLO_TRSH 100 +#define LCNPHY_ACI_GLITCH_TRSH 2000 +#define LCNPHY_ACI_TMOUT 250 +#define LCNPHY_ACI_DETECT_TIMEOUT 2 +#define LCNPHY_ACI_START_DELAY 0 + +#define wlc_lcnphy_tx_gain_override_enabled(pi) \ + (0 != (read_phy_reg((pi), 0x43b) & (0x1 << 6))) + +#define wlc_lcnphy_total_tx_frames(pi) \ + wlapi_bmac_read_shm((pi)->sh->physhim, M_UCODE_MACSTAT + \ + offsetof(struct macstat, txallfrm)) + +struct lcnphy_txgains { + u16 gm_gain; + u16 pga_gain; + u16 pad_gain; + u16 dac_gain; +}; + +enum lcnphy_cal_mode { + LCNPHY_CAL_FULL, + LCNPHY_CAL_RECAL, + LCNPHY_CAL_CURRECAL, + LCNPHY_CAL_DIGCAL, + LCNPHY_CAL_GCTRL +}; + +struct lcnphy_rx_iqcomp { + u8 chan; + s16 a; + s16 b; +}; + +struct lcnphy_spb_tone { + s16 re; + s16 im; +}; + +struct lcnphy_unsign16_struct { + u16 re; + u16 im; +}; + +struct lcnphy_iq_est { + u32 iq_prod; + u32 i_pwr; + u32 q_pwr; +}; + +struct lcnphy_sfo_cfg { + u16 ptcentreTs20; + u16 ptcentreFactor; +}; + +enum lcnphy_papd_cal_type { + LCNPHY_PAPD_CAL_CW, + LCNPHY_PAPD_CAL_OFDM +}; + +typedef u16 iqcal_gain_params_lcnphy[9]; + +static const iqcal_gain_params_lcnphy tbl_iqcal_gainparams_lcnphy_2G[] = { + {0, 0, 0, 0, 0, 0, 0, 0, 0}, +}; + +static const iqcal_gain_params_lcnphy *tbl_iqcal_gainparams_lcnphy[1] = { + tbl_iqcal_gainparams_lcnphy_2G, +}; + +static const u16 iqcal_gainparams_numgains_lcnphy[1] = { + ARRAY_SIZE(tbl_iqcal_gainparams_lcnphy_2G), +}; + +static const struct lcnphy_sfo_cfg lcnphy_sfo_cfg[] = { + {965, 1087}, + {967, 1085}, + {969, 1082}, + {971, 1080}, + {973, 1078}, + {975, 1076}, + {977, 1073}, + {979, 1071}, + {981, 1069}, + {983, 1067}, + {985, 1065}, + {987, 1063}, + {989, 1060}, + {994, 1055} +}; + +static const +u16 lcnphy_iqcal_loft_gainladder[] = { + ((2 << 8) | 0), + ((3 << 8) | 0), + ((4 << 8) | 0), + ((6 << 8) | 0), + ((8 << 8) | 0), + ((11 << 8) | 0), + ((16 << 8) | 0), + ((16 << 8) | 1), + ((16 << 8) | 2), + ((16 << 8) | 3), + ((16 << 8) | 4), + ((16 << 8) | 5), + ((16 << 8) | 6), + ((16 << 8) | 7), + ((23 << 8) | 7), + ((32 << 8) | 7), + ((45 << 8) | 7), + ((64 << 8) | 7), + ((91 << 8) | 7), + ((128 << 8) | 7) +}; + +static const +u16 lcnphy_iqcal_ir_gainladder[] = { + ((1 << 8) | 0), + ((2 << 8) | 0), + ((4 << 8) | 0), + ((6 << 8) | 0), + ((8 << 8) | 0), + ((11 << 8) | 0), + ((16 << 8) | 0), + ((23 << 8) | 0), + ((32 << 8) | 0), + ((45 << 8) | 0), + ((64 << 8) | 0), + ((64 << 8) | 1), + ((64 << 8) | 2), + ((64 << 8) | 3), + ((64 << 8) | 4), + ((64 << 8) | 5), + ((64 << 8) | 6), + ((64 << 8) | 7), + ((91 << 8) | 7), + ((128 << 8) | 7) +}; + +static const +struct lcnphy_spb_tone lcnphy_spb_tone_3750[] = { + {88, 0}, + {73, 49}, + {34, 81}, + {-17, 86}, + {-62, 62}, + {-86, 17}, + {-81, -34}, + {-49, -73}, + {0, -88}, + {49, -73}, + {81, -34}, + {86, 17}, + {62, 62}, + {17, 86}, + {-34, 81}, + {-73, 49}, + {-88, 0}, + {-73, -49}, + {-34, -81}, + {17, -86}, + {62, -62}, + {86, -17}, + {81, 34}, + {49, 73}, + {0, 88}, + {-49, 73}, + {-81, 34}, + {-86, -17}, + {-62, -62}, + {-17, -86}, + {34, -81}, + {73, -49}, +}; + +static const +u16 iqlo_loopback_rf_regs[20] = { + RADIO_2064_REG036, + RADIO_2064_REG11A, + RADIO_2064_REG03A, + RADIO_2064_REG025, + RADIO_2064_REG028, + RADIO_2064_REG005, + RADIO_2064_REG112, + RADIO_2064_REG0FF, + RADIO_2064_REG11F, + RADIO_2064_REG00B, + RADIO_2064_REG113, + RADIO_2064_REG007, + RADIO_2064_REG0FC, + RADIO_2064_REG0FD, + RADIO_2064_REG012, + RADIO_2064_REG057, + RADIO_2064_REG059, + RADIO_2064_REG05C, + RADIO_2064_REG078, + RADIO_2064_REG092, +}; + +static const +u16 tempsense_phy_regs[14] = { + 0x503, + 0x4a4, + 0x4d0, + 0x4d9, + 0x4da, + 0x4a6, + 0x938, + 0x939, + 0x4d8, + 0x4d0, + 0x4d7, + 0x4a5, + 0x40d, + 0x4a2, +}; + +static const +u16 rxiq_cal_rf_reg[11] = { + RADIO_2064_REG098, + RADIO_2064_REG116, + RADIO_2064_REG12C, + RADIO_2064_REG06A, + RADIO_2064_REG00B, + RADIO_2064_REG01B, + RADIO_2064_REG113, + RADIO_2064_REG01D, + RADIO_2064_REG114, + RADIO_2064_REG02E, + RADIO_2064_REG12A, +}; + +static const +struct lcnphy_rx_iqcomp lcnphy_rx_iqcomp_table_rev0[] = { + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {4, 0, 0}, + {5, 0, 0}, + {6, 0, 0}, + {7, 0, 0}, + {8, 0, 0}, + {9, 0, 0}, + {10, 0, 0}, + {11, 0, 0}, + {12, 0, 0}, + {13, 0, 0}, + {14, 0, 0}, + {34, 0, 0}, + {38, 0, 0}, + {42, 0, 0}, + {46, 0, 0}, + {36, 0, 0}, + {40, 0, 0}, + {44, 0, 0}, + {48, 0, 0}, + {52, 0, 0}, + {56, 0, 0}, + {60, 0, 0}, + {64, 0, 0}, + {100, 0, 0}, + {104, 0, 0}, + {108, 0, 0}, + {112, 0, 0}, + {116, 0, 0}, + {120, 0, 0}, + {124, 0, 0}, + {128, 0, 0}, + {132, 0, 0}, + {136, 0, 0}, + {140, 0, 0}, + {149, 0, 0}, + {153, 0, 0}, + {157, 0, 0}, + {161, 0, 0}, + {165, 0, 0}, + {184, 0, 0}, + {188, 0, 0}, + {192, 0, 0}, + {196, 0, 0}, + {200, 0, 0}, + {204, 0, 0}, + {208, 0, 0}, + {212, 0, 0}, + {216, 0, 0}, +}; + +static const u32 lcnphy_23bitgaincode_table[] = { + 0x200100, + 0x200200, + 0x200004, + 0x200014, + 0x200024, + 0x200034, + 0x200134, + 0x200234, + 0x200334, + 0x200434, + 0x200037, + 0x200137, + 0x200237, + 0x200337, + 0x200437, + 0x000035, + 0x000135, + 0x000235, + 0x000037, + 0x000137, + 0x000237, + 0x000337, + 0x00013f, + 0x00023f, + 0x00033f, + 0x00034f, + 0x00044f, + 0x00144f, + 0x00244f, + 0x00254f, + 0x00354f, + 0x00454f, + 0x00464f, + 0x01464f, + 0x02464f, + 0x03464f, + 0x04464f, +}; + +static const s8 lcnphy_gain_table[] = { + -16, + -13, + 10, + 7, + 4, + 0, + 3, + 6, + 9, + 12, + 15, + 18, + 21, + 24, + 27, + 30, + 33, + 36, + 39, + 42, + 45, + 48, + 50, + 53, + 56, + 59, + 62, + 65, + 68, + 71, + 74, + 77, + 80, + 83, + 86, + 89, + 92, +}; + +static const s8 lcnphy_gain_index_offset_for_rssi[] = { + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 8, + 7, + 7, + 6, + 7, + 7, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 3, + 3, + 3, + 3, + 3, + 3, + 4, + 2, + 2, + 2, + 2, + 2, + 2, + -1, + -2, + -2, + -2 +}; + +struct chan_info_2064_lcnphy { + uint chan; + uint freq; + u8 logen_buftune; + u8 logen_rccr_tx; + u8 txrf_mix_tune_ctrl; + u8 pa_input_tune_g; + u8 logen_rccr_rx; + u8 pa_rxrf_lna1_freq_tune; + u8 pa_rxrf_lna2_freq_tune; + u8 rxrf_rxrf_spare1; +}; + +static const struct chan_info_2064_lcnphy chan_info_2064_lcnphy[] = { + {1, 2412, 0x0B, 0x0A, 0x00, 0x07, 0x0A, 0x88, 0x88, 0x80}, + {2, 2417, 0x0B, 0x0A, 0x00, 0x07, 0x0A, 0x88, 0x88, 0x80}, + {3, 2422, 0x0B, 0x0A, 0x00, 0x07, 0x0A, 0x88, 0x88, 0x80}, + {4, 2427, 0x0B, 0x0A, 0x00, 0x07, 0x0A, 0x88, 0x88, 0x80}, + {5, 2432, 0x0B, 0x0A, 0x00, 0x07, 0x0A, 0x88, 0x88, 0x80}, + {6, 2437, 0x0B, 0x0A, 0x00, 0x07, 0x0A, 0x88, 0x88, 0x80}, + {7, 2442, 0x0B, 0x0A, 0x00, 0x07, 0x0A, 0x88, 0x88, 0x80}, + {8, 2447, 0x0B, 0x0A, 0x00, 0x07, 0x0A, 0x88, 0x88, 0x80}, + {9, 2452, 0x0B, 0x0A, 0x00, 0x07, 0x0A, 0x88, 0x88, 0x80}, + {10, 2457, 0x0B, 0x0A, 0x00, 0x07, 0x0A, 0x88, 0x88, 0x80}, + {11, 2462, 0x0B, 0x0A, 0x00, 0x07, 0x0A, 0x88, 0x88, 0x80}, + {12, 2467, 0x0B, 0x0A, 0x00, 0x07, 0x0A, 0x88, 0x88, 0x80}, + {13, 2472, 0x0B, 0x0A, 0x00, 0x07, 0x0A, 0x88, 0x88, 0x80}, + {14, 2484, 0x0B, 0x0A, 0x00, 0x07, 0x0A, 0x88, 0x88, 0x80}, +}; + +static const struct lcnphy_radio_regs lcnphy_radio_regs_2064[] = { + {0x00, 0, 0, 0, 0}, + {0x01, 0x64, 0x64, 0, 0}, + {0x02, 0x20, 0x20, 0, 0}, + {0x03, 0x66, 0x66, 0, 0}, + {0x04, 0xf8, 0xf8, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0x10, 0x10, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0, 0, 0, 0}, + {0x0A, 0x37, 0x37, 0, 0}, + {0x0B, 0x6, 0x6, 0, 0}, + {0x0C, 0x55, 0x55, 0, 0}, + {0x0D, 0x8b, 0x8b, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0x5, 0x5, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0xe, 0xe, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0xb, 0xb, 0, 0}, + {0x14, 0x2, 0x2, 0, 0}, + {0x15, 0x12, 0x12, 0, 0}, + {0x16, 0x12, 0x12, 0, 0}, + {0x17, 0xc, 0xc, 0, 0}, + {0x18, 0xc, 0xc, 0, 0}, + {0x19, 0xc, 0xc, 0, 0}, + {0x1A, 0x8, 0x8, 0, 0}, + {0x1B, 0x2, 0x2, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0x1, 0x1, 0, 0}, + {0x1E, 0x12, 0x12, 0, 0}, + {0x1F, 0x6e, 0x6e, 0, 0}, + {0x20, 0x2, 0x2, 0, 0}, + {0x21, 0x23, 0x23, 0, 0}, + {0x22, 0x8, 0x8, 0, 0}, + {0x23, 0, 0, 0, 0}, + {0x24, 0, 0, 0, 0}, + {0x25, 0xc, 0xc, 0, 0}, + {0x26, 0x33, 0x33, 0, 0}, + {0x27, 0x55, 0x55, 0, 0}, + {0x28, 0, 0, 0, 0}, + {0x29, 0x30, 0x30, 0, 0}, + {0x2A, 0xb, 0xb, 0, 0}, + {0x2B, 0x1b, 0x1b, 0, 0}, + {0x2C, 0x3, 0x3, 0, 0}, + {0x2D, 0x1b, 0x1b, 0, 0}, + {0x2E, 0, 0, 0, 0}, + {0x2F, 0x20, 0x20, 0, 0}, + {0x30, 0xa, 0xa, 0, 0}, + {0x31, 0, 0, 0, 0}, + {0x32, 0x62, 0x62, 0, 0}, + {0x33, 0x19, 0x19, 0, 0}, + {0x34, 0x33, 0x33, 0, 0}, + {0x35, 0x77, 0x77, 0, 0}, + {0x36, 0, 0, 0, 0}, + {0x37, 0x70, 0x70, 0, 0}, + {0x38, 0x3, 0x3, 0, 0}, + {0x39, 0xf, 0xf, 0, 0}, + {0x3A, 0x6, 0x6, 0, 0}, + {0x3B, 0xcf, 0xcf, 0, 0}, + {0x3C, 0x1a, 0x1a, 0, 0}, + {0x3D, 0x6, 0x6, 0, 0}, + {0x3E, 0x42, 0x42, 0, 0}, + {0x3F, 0, 0, 0, 0}, + {0x40, 0xfb, 0xfb, 0, 0}, + {0x41, 0x9a, 0x9a, 0, 0}, + {0x42, 0x7a, 0x7a, 0, 0}, + {0x43, 0x29, 0x29, 0, 0}, + {0x44, 0, 0, 0, 0}, + {0x45, 0x8, 0x8, 0, 0}, + {0x46, 0xce, 0xce, 0, 0}, + {0x47, 0x27, 0x27, 0, 0}, + {0x48, 0x62, 0x62, 0, 0}, + {0x49, 0x6, 0x6, 0, 0}, + {0x4A, 0x58, 0x58, 0, 0}, + {0x4B, 0xf7, 0xf7, 0, 0}, + {0x4C, 0, 0, 0, 0}, + {0x4D, 0xb3, 0xb3, 0, 0}, + {0x4E, 0, 0, 0, 0}, + {0x4F, 0x2, 0x2, 0, 0}, + {0x50, 0, 0, 0, 0}, + {0x51, 0x9, 0x9, 0, 0}, + {0x52, 0x5, 0x5, 0, 0}, + {0x53, 0x17, 0x17, 0, 0}, + {0x54, 0x38, 0x38, 0, 0}, + {0x55, 0, 0, 0, 0}, + {0x56, 0, 0, 0, 0}, + {0x57, 0xb, 0xb, 0, 0}, + {0x58, 0, 0, 0, 0}, + {0x59, 0, 0, 0, 0}, + {0x5A, 0, 0, 0, 0}, + {0x5B, 0, 0, 0, 0}, + {0x5C, 0, 0, 0, 0}, + {0x5D, 0, 0, 0, 0}, + {0x5E, 0x88, 0x88, 0, 0}, + {0x5F, 0xcc, 0xcc, 0, 0}, + {0x60, 0x74, 0x74, 0, 0}, + {0x61, 0x74, 0x74, 0, 0}, + {0x62, 0x74, 0x74, 0, 0}, + {0x63, 0x44, 0x44, 0, 0}, + {0x64, 0x77, 0x77, 0, 0}, + {0x65, 0x44, 0x44, 0, 0}, + {0x66, 0x77, 0x77, 0, 0}, + {0x67, 0x55, 0x55, 0, 0}, + {0x68, 0x77, 0x77, 0, 0}, + {0x69, 0x77, 0x77, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0x7f, 0x7f, 0, 0}, + {0x6C, 0x8, 0x8, 0, 0}, + {0x6D, 0, 0, 0, 0}, + {0x6E, 0x88, 0x88, 0, 0}, + {0x6F, 0x66, 0x66, 0, 0}, + {0x70, 0x66, 0x66, 0, 0}, + {0x71, 0x28, 0x28, 0, 0}, + {0x72, 0x55, 0x55, 0, 0}, + {0x73, 0x4, 0x4, 0, 0}, + {0x74, 0, 0, 0, 0}, + {0x75, 0, 0, 0, 0}, + {0x76, 0, 0, 0, 0}, + {0x77, 0x1, 0x1, 0, 0}, + {0x78, 0xd6, 0xd6, 0, 0}, + {0x79, 0, 0, 0, 0}, + {0x7A, 0, 0, 0, 0}, + {0x7B, 0, 0, 0, 0}, + {0x7C, 0, 0, 0, 0}, + {0x7D, 0, 0, 0, 0}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0xb4, 0xb4, 0, 0}, + {0x84, 0x1, 0x1, 0, 0}, + {0x85, 0x20, 0x20, 0, 0}, + {0x86, 0x5, 0x5, 0, 0}, + {0x87, 0xff, 0xff, 0, 0}, + {0x88, 0x7, 0x7, 0, 0}, + {0x89, 0x77, 0x77, 0, 0}, + {0x8A, 0x77, 0x77, 0, 0}, + {0x8B, 0x77, 0x77, 0, 0}, + {0x8C, 0x77, 0x77, 0, 0}, + {0x8D, 0x8, 0x8, 0, 0}, + {0x8E, 0xa, 0xa, 0, 0}, + {0x8F, 0x8, 0x8, 0, 0}, + {0x90, 0x18, 0x18, 0, 0}, + {0x91, 0x5, 0x5, 0, 0}, + {0x92, 0x1f, 0x1f, 0, 0}, + {0x93, 0x10, 0x10, 0, 0}, + {0x94, 0x3, 0x3, 0, 0}, + {0x95, 0, 0, 0, 0}, + {0x96, 0, 0, 0, 0}, + {0x97, 0xaa, 0xaa, 0, 0}, + {0x98, 0, 0, 0, 0}, + {0x99, 0x23, 0x23, 0, 0}, + {0x9A, 0x7, 0x7, 0, 0}, + {0x9B, 0xf, 0xf, 0, 0}, + {0x9C, 0x10, 0x10, 0, 0}, + {0x9D, 0x3, 0x3, 0, 0}, + {0x9E, 0x4, 0x4, 0, 0}, + {0x9F, 0x20, 0x20, 0, 0}, + {0xA0, 0, 0, 0, 0}, + {0xA1, 0, 0, 0, 0}, + {0xA2, 0, 0, 0, 0}, + {0xA3, 0, 0, 0, 0}, + {0xA4, 0x1, 0x1, 0, 0}, + {0xA5, 0x77, 0x77, 0, 0}, + {0xA6, 0x77, 0x77, 0, 0}, + {0xA7, 0x77, 0x77, 0, 0}, + {0xA8, 0x77, 0x77, 0, 0}, + {0xA9, 0x8c, 0x8c, 0, 0}, + {0xAA, 0x88, 0x88, 0, 0}, + {0xAB, 0x78, 0x78, 0, 0}, + {0xAC, 0x57, 0x57, 0, 0}, + {0xAD, 0x88, 0x88, 0, 0}, + {0xAE, 0, 0, 0, 0}, + {0xAF, 0x8, 0x8, 0, 0}, + {0xB0, 0x88, 0x88, 0, 0}, + {0xB1, 0, 0, 0, 0}, + {0xB2, 0x1b, 0x1b, 0, 0}, + {0xB3, 0x3, 0x3, 0, 0}, + {0xB4, 0x24, 0x24, 0, 0}, + {0xB5, 0x3, 0x3, 0, 0}, + {0xB6, 0x1b, 0x1b, 0, 0}, + {0xB7, 0x24, 0x24, 0, 0}, + {0xB8, 0x3, 0x3, 0, 0}, + {0xB9, 0, 0, 0, 0}, + {0xBA, 0xaa, 0xaa, 0, 0}, + {0xBB, 0, 0, 0, 0}, + {0xBC, 0x4, 0x4, 0, 0}, + {0xBD, 0, 0, 0, 0}, + {0xBE, 0x8, 0x8, 0, 0}, + {0xBF, 0x11, 0x11, 0, 0}, + {0xC0, 0, 0, 0, 0}, + {0xC1, 0, 0, 0, 0}, + {0xC2, 0x62, 0x62, 0, 0}, + {0xC3, 0x1e, 0x1e, 0, 0}, + {0xC4, 0x33, 0x33, 0, 0}, + {0xC5, 0x37, 0x37, 0, 0}, + {0xC6, 0, 0, 0, 0}, + {0xC7, 0x70, 0x70, 0, 0}, + {0xC8, 0x1e, 0x1e, 0, 0}, + {0xC9, 0x6, 0x6, 0, 0}, + {0xCA, 0x4, 0x4, 0, 0}, + {0xCB, 0x2f, 0x2f, 0, 0}, + {0xCC, 0xf, 0xf, 0, 0}, + {0xCD, 0, 0, 0, 0}, + {0xCE, 0xff, 0xff, 0, 0}, + {0xCF, 0x8, 0x8, 0, 0}, + {0xD0, 0x3f, 0x3f, 0, 0}, + {0xD1, 0x3f, 0x3f, 0, 0}, + {0xD2, 0x3f, 0x3f, 0, 0}, + {0xD3, 0, 0, 0, 0}, + {0xD4, 0, 0, 0, 0}, + {0xD5, 0, 0, 0, 0}, + {0xD6, 0xcc, 0xcc, 0, 0}, + {0xD7, 0, 0, 0, 0}, + {0xD8, 0x8, 0x8, 0, 0}, + {0xD9, 0x8, 0x8, 0, 0}, + {0xDA, 0x8, 0x8, 0, 0}, + {0xDB, 0x11, 0x11, 0, 0}, + {0xDC, 0, 0, 0, 0}, + {0xDD, 0x87, 0x87, 0, 0}, + {0xDE, 0x88, 0x88, 0, 0}, + {0xDF, 0x8, 0x8, 0, 0}, + {0xE0, 0x8, 0x8, 0, 0}, + {0xE1, 0x8, 0x8, 0, 0}, + {0xE2, 0, 0, 0, 0}, + {0xE3, 0, 0, 0, 0}, + {0xE4, 0, 0, 0, 0}, + {0xE5, 0xf5, 0xf5, 0, 0}, + {0xE6, 0x30, 0x30, 0, 0}, + {0xE7, 0x1, 0x1, 0, 0}, + {0xE8, 0, 0, 0, 0}, + {0xE9, 0xff, 0xff, 0, 0}, + {0xEA, 0, 0, 0, 0}, + {0xEB, 0, 0, 0, 0}, + {0xEC, 0x22, 0x22, 0, 0}, + {0xED, 0, 0, 0, 0}, + {0xEE, 0, 0, 0, 0}, + {0xEF, 0, 0, 0, 0}, + {0xF0, 0x3, 0x3, 0, 0}, + {0xF1, 0x1, 0x1, 0, 0}, + {0xF2, 0, 0, 0, 0}, + {0xF3, 0, 0, 0, 0}, + {0xF4, 0, 0, 0, 0}, + {0xF5, 0, 0, 0, 0}, + {0xF6, 0, 0, 0, 0}, + {0xF7, 0x6, 0x6, 0, 0}, + {0xF8, 0, 0, 0, 0}, + {0xF9, 0, 0, 0, 0}, + {0xFA, 0x40, 0x40, 0, 0}, + {0xFB, 0, 0, 0, 0}, + {0xFC, 0x1, 0x1, 0, 0}, + {0xFD, 0x80, 0x80, 0, 0}, + {0xFE, 0x2, 0x2, 0, 0}, + {0xFF, 0x10, 0x10, 0, 0}, + {0x100, 0x2, 0x2, 0, 0}, + {0x101, 0x1e, 0x1e, 0, 0}, + {0x102, 0x1e, 0x1e, 0, 0}, + {0x103, 0, 0, 0, 0}, + {0x104, 0x1f, 0x1f, 0, 0}, + {0x105, 0, 0x8, 0, 1}, + {0x106, 0x2a, 0x2a, 0, 0}, + {0x107, 0xf, 0xf, 0, 0}, + {0x108, 0, 0, 0, 0}, + {0x109, 0, 0, 0, 0}, + {0x10A, 0, 0, 0, 0}, + {0x10B, 0, 0, 0, 0}, + {0x10C, 0, 0, 0, 0}, + {0x10D, 0, 0, 0, 0}, + {0x10E, 0, 0, 0, 0}, + {0x10F, 0, 0, 0, 0}, + {0x110, 0, 0, 0, 0}, + {0x111, 0, 0, 0, 0}, + {0x112, 0, 0, 0, 0}, + {0x113, 0, 0, 0, 0}, + {0x114, 0, 0, 0, 0}, + {0x115, 0, 0, 0, 0}, + {0x116, 0, 0, 0, 0}, + {0x117, 0, 0, 0, 0}, + {0x118, 0, 0, 0, 0}, + {0x119, 0, 0, 0, 0}, + {0x11A, 0, 0, 0, 0}, + {0x11B, 0, 0, 0, 0}, + {0x11C, 0x1, 0x1, 0, 0}, + {0x11D, 0, 0, 0, 0}, + {0x11E, 0, 0, 0, 0}, + {0x11F, 0, 0, 0, 0}, + {0x120, 0, 0, 0, 0}, + {0x121, 0, 0, 0, 0}, + {0x122, 0x80, 0x80, 0, 0}, + {0x123, 0, 0, 0, 0}, + {0x124, 0xf8, 0xf8, 0, 0}, + {0x125, 0, 0, 0, 0}, + {0x126, 0, 0, 0, 0}, + {0x127, 0, 0, 0, 0}, + {0x128, 0, 0, 0, 0}, + {0x129, 0, 0, 0, 0}, + {0x12A, 0, 0, 0, 0}, + {0x12B, 0, 0, 0, 0}, + {0x12C, 0, 0, 0, 0}, + {0x12D, 0, 0, 0, 0}, + {0x12E, 0, 0, 0, 0}, + {0x12F, 0, 0, 0, 0}, + {0x130, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0} +}; + +#define LCNPHY_NUM_DIG_FILT_COEFFS 16 +#define LCNPHY_NUM_TX_DIG_FILTERS_CCK 13 + +static const u16 LCNPHY_txdigfiltcoeffs_cck[LCNPHY_NUM_TX_DIG_FILTERS_CCK] + [LCNPHY_NUM_DIG_FILT_COEFFS + 1] = { + {0, 1, 415, 1874, 64, 128, 64, 792, 1656, 64, 128, 64, 778, 1582, 64, + 128, 64,}, + {1, 1, 402, 1847, 259, 59, 259, 671, 1794, 68, 54, 68, 608, 1863, 93, + 167, 93,}, + {2, 1, 415, 1874, 64, 128, 64, 792, 1656, 192, 384, 192, 778, 1582, 64, + 128, 64,}, + {3, 1, 302, 1841, 129, 258, 129, 658, 1720, 205, 410, 205, 754, 1760, + 170, 340, 170,}, + {20, 1, 360, 1884, 242, 1734, 242, 752, 1720, 205, 1845, 205, 767, 1760, + 256, 185, 256,}, + {21, 1, 360, 1884, 149, 1874, 149, 752, 1720, 205, 1883, 205, 767, 1760, + 256, 273, 256,}, + {22, 1, 360, 1884, 98, 1948, 98, 752, 1720, 205, 1924, 205, 767, 1760, + 256, 352, 256,}, + {23, 1, 350, 1884, 116, 1966, 116, 752, 1720, 205, 2008, 205, 767, 1760, + 128, 233, 128,}, + {24, 1, 325, 1884, 32, 40, 32, 756, 1720, 256, 471, 256, 766, 1760, 256, + 1881, 256,}, + {25, 1, 299, 1884, 51, 64, 51, 736, 1720, 256, 471, 256, 765, 1760, 256, + 1881, 256,}, + {26, 1, 277, 1943, 39, 117, 88, 637, 1838, 64, 192, 144, 614, 1864, 128, + 384, 288,}, + {27, 1, 245, 1943, 49, 147, 110, 626, 1838, 256, 768, 576, 613, 1864, + 128, 384, 288,}, + {30, 1, 302, 1841, 61, 122, 61, 658, 1720, 205, 410, 205, 754, 1760, + 170, 340, 170,}, +}; + +#define LCNPHY_NUM_TX_DIG_FILTERS_OFDM 3 +static const u16 LCNPHY_txdigfiltcoeffs_ofdm[LCNPHY_NUM_TX_DIG_FILTERS_OFDM] + [LCNPHY_NUM_DIG_FILT_COEFFS + 1] = { + {0, 0, 0xa2, 0x0, 0x100, 0x100, 0x0, 0x0, 0x0, 0x100, 0x0, 0x0, + 0x278, 0xfea0, 0x80, 0x100, 0x80,}, + {1, 0, 374, 0xFF79, 16, 32, 16, 799, 0xFE74, 50, 32, 50, + 750, 0xFE2B, 212, 0xFFCE, 212,}, + {2, 0, 375, 0xFF16, 37, 76, 37, 799, 0xFE74, 32, 20, 32, 748, + 0xFEF2, 128, 0xFFE2, 128} +}; + +#define wlc_lcnphy_set_start_tx_pwr_idx(pi, idx) \ + mod_phy_reg(pi, 0x4a4, \ + (0x1ff << 0), \ + (u16)(idx) << 0) + +#define wlc_lcnphy_set_tx_pwr_npt(pi, npt) \ + mod_phy_reg(pi, 0x4a5, \ + (0x7 << 8), \ + (u16)(npt) << 8) + +#define wlc_lcnphy_get_tx_pwr_ctrl(pi) \ + (read_phy_reg((pi), 0x4a4) & \ + ((0x1 << 15) | \ + (0x1 << 14) | \ + (0x1 << 13))) + +#define wlc_lcnphy_get_tx_pwr_npt(pi) \ + ((read_phy_reg(pi, 0x4a5) & \ + (0x7 << 8)) >> \ + 8) + +#define wlc_lcnphy_get_current_tx_pwr_idx_if_pwrctrl_on(pi) \ + (read_phy_reg(pi, 0x473) & 0x1ff) + +#define wlc_lcnphy_get_target_tx_pwr(pi) \ + ((read_phy_reg(pi, 0x4a7) & \ + (0xff << 0)) >> \ + 0) + +#define wlc_lcnphy_set_target_tx_pwr(pi, target) \ + mod_phy_reg(pi, 0x4a7, \ + (0xff << 0), \ + (u16)(target) << 0) + +#define wlc_radio_2064_rcal_done(pi) \ + (0 != (read_radio_reg(pi, RADIO_2064_REG05C) & 0x20)) + +#define tempsense_done(pi) \ + (0x8000 == (read_phy_reg(pi, 0x476) & 0x8000)) + +#define LCNPHY_IQLOCC_READ(val) \ + ((u8)(-(s8)(((val) & 0xf0) >> 4) + (s8)((val) & 0x0f))) + +#define FIXED_TXPWR 78 +#define LCNPHY_TEMPSENSE(val) ((s16)((val > 255) ? (val - 512) : val)) + +void wlc_lcnphy_write_table(struct brcms_phy *pi, const struct phytbl_info *pti) +{ + wlc_phy_write_table(pi, pti, 0x455, 0x457, 0x456); +} + +void wlc_lcnphy_read_table(struct brcms_phy *pi, struct phytbl_info *pti) +{ + wlc_phy_read_table(pi, pti, 0x455, 0x457, 0x456); +} + +static void +wlc_lcnphy_common_read_table(struct brcms_phy *pi, u32 tbl_id, + const u16 *tbl_ptr, u32 tbl_len, + u32 tbl_width, u32 tbl_offset) +{ + struct phytbl_info tab; + tab.tbl_id = tbl_id; + tab.tbl_ptr = tbl_ptr; + tab.tbl_len = tbl_len; + tab.tbl_width = tbl_width; + tab.tbl_offset = tbl_offset; + wlc_lcnphy_read_table(pi, &tab); +} + +static void +wlc_lcnphy_common_write_table(struct brcms_phy *pi, u32 tbl_id, + const u16 *tbl_ptr, u32 tbl_len, + u32 tbl_width, u32 tbl_offset) +{ + + struct phytbl_info tab; + tab.tbl_id = tbl_id; + tab.tbl_ptr = tbl_ptr; + tab.tbl_len = tbl_len; + tab.tbl_width = tbl_width; + tab.tbl_offset = tbl_offset; + wlc_lcnphy_write_table(pi, &tab); +} + +static u32 +wlc_lcnphy_qdiv_roundup(u32 dividend, u32 divisor, u8 precision) +{ + u32 quotient, remainder, roundup, rbit; + + quotient = dividend / divisor; + remainder = dividend % divisor; + rbit = divisor & 1; + roundup = (divisor >> 1) + rbit; + + while (precision--) { + quotient <<= 1; + if (remainder >= roundup) { + quotient++; + remainder = ((remainder - roundup) << 1) + rbit; + } else { + remainder <<= 1; + } + } + + if (remainder >= roundup) + quotient++; + + return quotient; +} + +static int wlc_lcnphy_calc_floor(s16 coeff_x, int type) +{ + int k; + k = 0; + if (type == 0) { + if (coeff_x < 0) + k = (coeff_x - 1) / 2; + else + k = coeff_x / 2; + } + + if (type == 1) { + if ((coeff_x + 1) < 0) + k = (coeff_x) / 2; + else + k = (coeff_x + 1) / 2; + } + return k; +} + +static void +wlc_lcnphy_get_tx_gain(struct brcms_phy *pi, struct lcnphy_txgains *gains) +{ + u16 dac_gain, rfgain0, rfgain1; + + dac_gain = read_phy_reg(pi, 0x439) >> 0; + gains->dac_gain = (dac_gain & 0x380) >> 7; + + rfgain0 = (read_phy_reg(pi, 0x4b5) & (0xffff << 0)) >> 0; + rfgain1 = (read_phy_reg(pi, 0x4fb) & (0x7fff << 0)) >> 0; + + gains->gm_gain = rfgain0 & 0xff; + gains->pga_gain = (rfgain0 >> 8) & 0xff; + gains->pad_gain = rfgain1 & 0xff; +} + + +static void wlc_lcnphy_set_dac_gain(struct brcms_phy *pi, u16 dac_gain) +{ + u16 dac_ctrl; + + dac_ctrl = (read_phy_reg(pi, 0x439) >> 0); + dac_ctrl = dac_ctrl & 0xc7f; + dac_ctrl = dac_ctrl | (dac_gain << 7); + mod_phy_reg(pi, 0x439, (0xfff << 0), (dac_ctrl) << 0); + +} + +static void wlc_lcnphy_set_tx_gain_override(struct brcms_phy *pi, bool bEnable) +{ + u16 bit = bEnable ? 1 : 0; + + mod_phy_reg(pi, 0x4b0, (0x1 << 7), bit << 7); + + mod_phy_reg(pi, 0x4b0, (0x1 << 14), bit << 14); + + mod_phy_reg(pi, 0x43b, (0x1 << 6), bit << 6); +} + +static void +wlc_lcnphy_rx_gain_override_enable(struct brcms_phy *pi, bool enable) +{ + u16 ebit = enable ? 1 : 0; + + mod_phy_reg(pi, 0x4b0, (0x1 << 8), ebit << 8); + + mod_phy_reg(pi, 0x44c, (0x1 << 0), ebit << 0); + + if (LCNREV_LT(pi->pubpi.phy_rev, 2)) { + mod_phy_reg(pi, 0x44c, (0x1 << 4), ebit << 4); + mod_phy_reg(pi, 0x44c, (0x1 << 6), ebit << 6); + mod_phy_reg(pi, 0x4b0, (0x1 << 5), ebit << 5); + mod_phy_reg(pi, 0x4b0, (0x1 << 6), ebit << 6); + } else { + mod_phy_reg(pi, 0x4b0, (0x1 << 12), ebit << 12); + mod_phy_reg(pi, 0x4b0, (0x1 << 13), ebit << 13); + mod_phy_reg(pi, 0x4b0, (0x1 << 5), ebit << 5); + } + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + mod_phy_reg(pi, 0x4b0, (0x1 << 10), ebit << 10); + mod_phy_reg(pi, 0x4e5, (0x1 << 3), ebit << 3); + } +} + +static void +wlc_lcnphy_set_rx_gain_by_distribution(struct brcms_phy *pi, + u16 trsw, + u16 ext_lna, + u16 biq2, + u16 biq1, + u16 tia, u16 lna2, u16 lna1) +{ + u16 gain0_15, gain16_19; + + gain16_19 = biq2 & 0xf; + gain0_15 = ((biq1 & 0xf) << 12) | + ((tia & 0xf) << 8) | + ((lna2 & 0x3) << 6) | + ((lna2 & + 0x3) << 4) | ((lna1 & 0x3) << 2) | ((lna1 & 0x3) << 0); + + mod_phy_reg(pi, 0x4b6, (0xffff << 0), gain0_15 << 0); + mod_phy_reg(pi, 0x4b7, (0xf << 0), gain16_19 << 0); + mod_phy_reg(pi, 0x4b1, (0x3 << 11), lna1 << 11); + + if (LCNREV_LT(pi->pubpi.phy_rev, 2)) { + mod_phy_reg(pi, 0x4b1, (0x1 << 9), ext_lna << 9); + mod_phy_reg(pi, 0x4b1, (0x1 << 10), ext_lna << 10); + } else { + mod_phy_reg(pi, 0x4b1, (0x1 << 10), 0 << 10); + + mod_phy_reg(pi, 0x4b1, (0x1 << 15), 0 << 15); + + mod_phy_reg(pi, 0x4b1, (0x1 << 9), ext_lna << 9); + } + + mod_phy_reg(pi, 0x44d, (0x1 << 0), (!trsw) << 0); + +} + +static void wlc_lcnphy_set_trsw_override(struct brcms_phy *pi, bool tx, bool rx) +{ + + mod_phy_reg(pi, 0x44d, + (0x1 << 1) | + (0x1 << 0), (tx ? (0x1 << 1) : 0) | (rx ? (0x1 << 0) : 0)); + + or_phy_reg(pi, 0x44c, (0x1 << 1) | (0x1 << 0)); +} + +static void wlc_lcnphy_clear_trsw_override(struct brcms_phy *pi) +{ + + and_phy_reg(pi, 0x44c, (u16) ~((0x1 << 1) | (0x1 << 0))); +} + +static void wlc_lcnphy_set_rx_iq_comp(struct brcms_phy *pi, u16 a, u16 b) +{ + mod_phy_reg(pi, 0x645, (0x3ff << 0), (a) << 0); + + mod_phy_reg(pi, 0x646, (0x3ff << 0), (b) << 0); + + mod_phy_reg(pi, 0x647, (0x3ff << 0), (a) << 0); + + mod_phy_reg(pi, 0x648, (0x3ff << 0), (b) << 0); + + mod_phy_reg(pi, 0x649, (0x3ff << 0), (a) << 0); + + mod_phy_reg(pi, 0x64a, (0x3ff << 0), (b) << 0); + +} + +static bool +wlc_lcnphy_rx_iq_est(struct brcms_phy *pi, + u16 num_samps, + u8 wait_time, struct lcnphy_iq_est *iq_est) +{ + int wait_count = 0; + bool result = true; + u8 phybw40; + phybw40 = CHSPEC_IS40(pi->radio_chanspec); + + mod_phy_reg(pi, 0x6da, (0x1 << 5), (1) << 5); + + mod_phy_reg(pi, 0x410, (0x1 << 3), (0) << 3); + + mod_phy_reg(pi, 0x482, (0xffff << 0), (num_samps) << 0); + + mod_phy_reg(pi, 0x481, (0xff << 0), ((u16) wait_time) << 0); + + mod_phy_reg(pi, 0x481, (0x1 << 8), (0) << 8); + + mod_phy_reg(pi, 0x481, (0x1 << 9), (1) << 9); + + while (read_phy_reg(pi, 0x481) & (0x1 << 9)) { + + if (wait_count > (10 * 500)) { + result = false; + goto cleanup; + } + udelay(100); + wait_count++; + } + + iq_est->iq_prod = ((u32) read_phy_reg(pi, 0x483) << 16) | + (u32) read_phy_reg(pi, 0x484); + iq_est->i_pwr = ((u32) read_phy_reg(pi, 0x485) << 16) | + (u32) read_phy_reg(pi, 0x486); + iq_est->q_pwr = ((u32) read_phy_reg(pi, 0x487) << 16) | + (u32) read_phy_reg(pi, 0x488); + +cleanup: + mod_phy_reg(pi, 0x410, (0x1 << 3), (1) << 3); + + mod_phy_reg(pi, 0x6da, (0x1 << 5), (0) << 5); + + return result; +} + +static bool wlc_lcnphy_calc_rx_iq_comp(struct brcms_phy *pi, u16 num_samps) +{ +#define LCNPHY_MIN_RXIQ_PWR 2 + bool result; + u16 a0_new, b0_new; + struct lcnphy_iq_est iq_est = { 0, 0, 0 }; + s32 a, b, temp; + s16 iq_nbits, qq_nbits, arsh, brsh; + s32 iq; + u32 ii, qq; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + a0_new = ((read_phy_reg(pi, 0x645) & (0x3ff << 0)) >> 0); + b0_new = ((read_phy_reg(pi, 0x646) & (0x3ff << 0)) >> 0); + mod_phy_reg(pi, 0x6d1, (0x1 << 2), (0) << 2); + + mod_phy_reg(pi, 0x64b, (0x1 << 6), (1) << 6); + + wlc_lcnphy_set_rx_iq_comp(pi, 0, 0); + + result = wlc_lcnphy_rx_iq_est(pi, num_samps, 32, &iq_est); + if (!result) + goto cleanup; + + iq = (s32) iq_est.iq_prod; + ii = iq_est.i_pwr; + qq = iq_est.q_pwr; + + if ((ii + qq) < LCNPHY_MIN_RXIQ_PWR) { + result = false; + goto cleanup; + } + + iq_nbits = wlc_phy_nbits(iq); + qq_nbits = wlc_phy_nbits(qq); + + arsh = 10 - (30 - iq_nbits); + if (arsh >= 0) { + a = (-(iq << (30 - iq_nbits)) + (ii >> (1 + arsh))); + temp = (s32) (ii >> arsh); + if (temp == 0) + return false; + } else { + a = (-(iq << (30 - iq_nbits)) + (ii << (-1 - arsh))); + temp = (s32) (ii << -arsh); + if (temp == 0) + return false; + } + a /= temp; + brsh = qq_nbits - 31 + 20; + if (brsh >= 0) { + b = (qq << (31 - qq_nbits)); + temp = (s32) (ii >> brsh); + if (temp == 0) + return false; + } else { + b = (qq << (31 - qq_nbits)); + temp = (s32) (ii << -brsh); + if (temp == 0) + return false; + } + b /= temp; + b -= a * a; + b = (s32) int_sqrt((unsigned long) b); + b -= (1 << 10); + a0_new = (u16) (a & 0x3ff); + b0_new = (u16) (b & 0x3ff); +cleanup: + + wlc_lcnphy_set_rx_iq_comp(pi, a0_new, b0_new); + + mod_phy_reg(pi, 0x64b, (0x1 << 0), (1) << 0); + + mod_phy_reg(pi, 0x64b, (0x1 << 3), (1) << 3); + + pi_lcn->lcnphy_cal_results.rxiqcal_coeff_a0 = a0_new; + pi_lcn->lcnphy_cal_results.rxiqcal_coeff_b0 = b0_new; + + return result; +} + +static u32 wlc_lcnphy_measure_digital_power(struct brcms_phy *pi, u16 nsamples) +{ + struct lcnphy_iq_est iq_est = { 0, 0, 0 }; + + if (!wlc_lcnphy_rx_iq_est(pi, nsamples, 32, &iq_est)) + return 0; + return (iq_est.i_pwr + iq_est.q_pwr) / nsamples; +} + +static bool +wlc_lcnphy_rx_iq_cal(struct brcms_phy *pi, + const struct lcnphy_rx_iqcomp *iqcomp, + int iqcomp_sz, bool tx_switch, bool rx_switch, int module, + int tx_gain_idx) +{ + struct lcnphy_txgains old_gains; + u16 tx_pwr_ctrl; + u8 tx_gain_index_old = 0; + bool result = false, tx_gain_override_old = false; + u16 i, Core1TxControl_old, RFOverride0_old, + RFOverrideVal0_old, rfoverride2_old, rfoverride2val_old, + rfoverride3_old, rfoverride3val_old, rfoverride4_old, + rfoverride4val_old, afectrlovr_old, afectrlovrval_old; + int tia_gain; + u32 received_power, rx_pwr_threshold; + u16 old_sslpnCalibClkEnCtrl, old_sslpnRxFeClkEnCtrl; + u16 values_to_save[11]; + s16 *ptr; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + ptr = kmalloc(sizeof(s16) * 131, GFP_ATOMIC); + if (NULL == ptr) + return false; + if (module == 2) { + while (iqcomp_sz--) { + if (iqcomp[iqcomp_sz].chan == + CHSPEC_CHANNEL(pi->radio_chanspec)) { + wlc_lcnphy_set_rx_iq_comp(pi, + (u16) + iqcomp[iqcomp_sz].a, + (u16) + iqcomp[iqcomp_sz].b); + result = true; + break; + } + } + goto cal_done; + } + + if (module == 1) { + + tx_pwr_ctrl = wlc_lcnphy_get_tx_pwr_ctrl(pi); + wlc_lcnphy_set_tx_pwr_ctrl(pi, LCNPHY_TX_PWR_CTRL_OFF); + + for (i = 0; i < 11; i++) + values_to_save[i] = + read_radio_reg(pi, rxiq_cal_rf_reg[i]); + Core1TxControl_old = read_phy_reg(pi, 0x631); + + or_phy_reg(pi, 0x631, 0x0015); + + RFOverride0_old = read_phy_reg(pi, 0x44c); + RFOverrideVal0_old = read_phy_reg(pi, 0x44d); + rfoverride2_old = read_phy_reg(pi, 0x4b0); + rfoverride2val_old = read_phy_reg(pi, 0x4b1); + rfoverride3_old = read_phy_reg(pi, 0x4f9); + rfoverride3val_old = read_phy_reg(pi, 0x4fa); + rfoverride4_old = read_phy_reg(pi, 0x938); + rfoverride4val_old = read_phy_reg(pi, 0x939); + afectrlovr_old = read_phy_reg(pi, 0x43b); + afectrlovrval_old = read_phy_reg(pi, 0x43c); + old_sslpnCalibClkEnCtrl = read_phy_reg(pi, 0x6da); + old_sslpnRxFeClkEnCtrl = read_phy_reg(pi, 0x6db); + + tx_gain_override_old = wlc_lcnphy_tx_gain_override_enabled(pi); + if (tx_gain_override_old) { + wlc_lcnphy_get_tx_gain(pi, &old_gains); + tx_gain_index_old = pi_lcn->lcnphy_current_index; + } + + wlc_lcnphy_set_tx_pwr_by_index(pi, tx_gain_idx); + + mod_phy_reg(pi, 0x4f9, (0x1 << 0), 1 << 0); + mod_phy_reg(pi, 0x4fa, (0x1 << 0), 0 << 0); + + mod_phy_reg(pi, 0x43b, (0x1 << 1), 1 << 1); + mod_phy_reg(pi, 0x43c, (0x1 << 1), 0 << 1); + + write_radio_reg(pi, RADIO_2064_REG116, 0x06); + write_radio_reg(pi, RADIO_2064_REG12C, 0x07); + write_radio_reg(pi, RADIO_2064_REG06A, 0xd3); + write_radio_reg(pi, RADIO_2064_REG098, 0x03); + write_radio_reg(pi, RADIO_2064_REG00B, 0x7); + mod_radio_reg(pi, RADIO_2064_REG113, 1 << 4, 1 << 4); + write_radio_reg(pi, RADIO_2064_REG01D, 0x01); + write_radio_reg(pi, RADIO_2064_REG114, 0x01); + write_radio_reg(pi, RADIO_2064_REG02E, 0x10); + write_radio_reg(pi, RADIO_2064_REG12A, 0x08); + + mod_phy_reg(pi, 0x938, (0x1 << 0), 1 << 0); + mod_phy_reg(pi, 0x939, (0x1 << 0), 0 << 0); + mod_phy_reg(pi, 0x938, (0x1 << 1), 1 << 1); + mod_phy_reg(pi, 0x939, (0x1 << 1), 1 << 1); + mod_phy_reg(pi, 0x938, (0x1 << 2), 1 << 2); + mod_phy_reg(pi, 0x939, (0x1 << 2), 1 << 2); + mod_phy_reg(pi, 0x938, (0x1 << 3), 1 << 3); + mod_phy_reg(pi, 0x939, (0x1 << 3), 1 << 3); + mod_phy_reg(pi, 0x938, (0x1 << 5), 1 << 5); + mod_phy_reg(pi, 0x939, (0x1 << 5), 0 << 5); + + mod_phy_reg(pi, 0x43b, (0x1 << 0), 1 << 0); + mod_phy_reg(pi, 0x43c, (0x1 << 0), 0 << 0); + + wlc_lcnphy_start_tx_tone(pi, 2000, 120, 0); + write_phy_reg(pi, 0x6da, 0xffff); + or_phy_reg(pi, 0x6db, 0x3); + wlc_lcnphy_set_trsw_override(pi, tx_switch, rx_switch); + wlc_lcnphy_rx_gain_override_enable(pi, true); + + tia_gain = 8; + rx_pwr_threshold = 950; + while (tia_gain > 0) { + tia_gain -= 1; + wlc_lcnphy_set_rx_gain_by_distribution(pi, + 0, 0, 2, 2, + (u16) + tia_gain, 1, 0); + udelay(500); + + received_power = + wlc_lcnphy_measure_digital_power(pi, 2000); + if (received_power < rx_pwr_threshold) + break; + } + result = wlc_lcnphy_calc_rx_iq_comp(pi, 0xffff); + + wlc_lcnphy_stop_tx_tone(pi); + + write_phy_reg(pi, 0x631, Core1TxControl_old); + + write_phy_reg(pi, 0x44c, RFOverrideVal0_old); + write_phy_reg(pi, 0x44d, RFOverrideVal0_old); + write_phy_reg(pi, 0x4b0, rfoverride2_old); + write_phy_reg(pi, 0x4b1, rfoverride2val_old); + write_phy_reg(pi, 0x4f9, rfoverride3_old); + write_phy_reg(pi, 0x4fa, rfoverride3val_old); + write_phy_reg(pi, 0x938, rfoverride4_old); + write_phy_reg(pi, 0x939, rfoverride4val_old); + write_phy_reg(pi, 0x43b, afectrlovr_old); + write_phy_reg(pi, 0x43c, afectrlovrval_old); + write_phy_reg(pi, 0x6da, old_sslpnCalibClkEnCtrl); + write_phy_reg(pi, 0x6db, old_sslpnRxFeClkEnCtrl); + + wlc_lcnphy_clear_trsw_override(pi); + + mod_phy_reg(pi, 0x44c, (0x1 << 2), 0 << 2); + + for (i = 0; i < 11; i++) + write_radio_reg(pi, rxiq_cal_rf_reg[i], + values_to_save[i]); + + if (tx_gain_override_old) + wlc_lcnphy_set_tx_pwr_by_index(pi, tx_gain_index_old); + else + wlc_lcnphy_disable_tx_gain_override(pi); + + wlc_lcnphy_set_tx_pwr_ctrl(pi, tx_pwr_ctrl); + wlc_lcnphy_rx_gain_override_enable(pi, false); + } + +cal_done: + kfree(ptr); + return result; +} + +s8 wlc_lcnphy_get_current_tx_pwr_idx(struct brcms_phy *pi) +{ + s8 index; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + if (txpwrctrl_off(pi)) + index = pi_lcn->lcnphy_current_index; + else if (wlc_lcnphy_tssi_based_pwr_ctrl_enabled(pi)) + index = (s8) (wlc_lcnphy_get_current_tx_pwr_idx_if_pwrctrl_on( + pi) / 2); + else + index = pi_lcn->lcnphy_current_index; + return index; +} + +void wlc_lcnphy_crsuprs(struct brcms_phy *pi, int channel) +{ + u16 afectrlovr, afectrlovrval; + afectrlovr = read_phy_reg(pi, 0x43b); + afectrlovrval = read_phy_reg(pi, 0x43c); + if (channel != 0) { + mod_phy_reg(pi, 0x43b, (0x1 << 1), (1) << 1); + + mod_phy_reg(pi, 0x43c, (0x1 << 1), (0) << 1); + + mod_phy_reg(pi, 0x43b, (0x1 << 4), (1) << 4); + + mod_phy_reg(pi, 0x43c, (0x1 << 6), (0) << 6); + + write_phy_reg(pi, 0x44b, 0xffff); + wlc_lcnphy_tx_pu(pi, 1); + + mod_phy_reg(pi, 0x634, (0xff << 8), (0) << 8); + + or_phy_reg(pi, 0x6da, 0x0080); + + or_phy_reg(pi, 0x00a, 0x228); + } else { + and_phy_reg(pi, 0x00a, ~(0x228)); + + and_phy_reg(pi, 0x6da, 0xFF7F); + write_phy_reg(pi, 0x43b, afectrlovr); + write_phy_reg(pi, 0x43c, afectrlovrval); + } +} + +static void wlc_lcnphy_toggle_afe_pwdn(struct brcms_phy *pi) +{ + u16 save_AfeCtrlOvrVal, save_AfeCtrlOvr; + + save_AfeCtrlOvrVal = read_phy_reg(pi, 0x43c); + save_AfeCtrlOvr = read_phy_reg(pi, 0x43b); + + write_phy_reg(pi, 0x43c, save_AfeCtrlOvrVal | 0x1); + write_phy_reg(pi, 0x43b, save_AfeCtrlOvr | 0x1); + + write_phy_reg(pi, 0x43c, save_AfeCtrlOvrVal & 0xfffe); + write_phy_reg(pi, 0x43b, save_AfeCtrlOvr & 0xfffe); + + write_phy_reg(pi, 0x43c, save_AfeCtrlOvrVal); + write_phy_reg(pi, 0x43b, save_AfeCtrlOvr); +} + +static void +wlc_lcnphy_txrx_spur_avoidance_mode(struct brcms_phy *pi, bool enable) +{ + if (enable) { + write_phy_reg(pi, 0x942, 0x7); + write_phy_reg(pi, 0x93b, ((1 << 13) + 23)); + write_phy_reg(pi, 0x93c, ((1 << 13) + 1989)); + + write_phy_reg(pi, 0x44a, 0x084); + write_phy_reg(pi, 0x44a, 0x080); + write_phy_reg(pi, 0x6d3, 0x2222); + write_phy_reg(pi, 0x6d3, 0x2220); + } else { + write_phy_reg(pi, 0x942, 0x0); + write_phy_reg(pi, 0x93b, ((0 << 13) + 23)); + write_phy_reg(pi, 0x93c, ((0 << 13) + 1989)); + } + wlapi_switch_macfreq(pi->sh->physhim, enable); +} + +static void +wlc_lcnphy_set_chanspec_tweaks(struct brcms_phy *pi, u16 chanspec) +{ + u8 channel = CHSPEC_CHANNEL(chanspec); + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + if (channel == 14) + mod_phy_reg(pi, 0x448, (0x3 << 8), (2) << 8); + else + mod_phy_reg(pi, 0x448, (0x3 << 8), (1) << 8); + + pi_lcn->lcnphy_bandedge_corr = 2; + if (channel == 1) + pi_lcn->lcnphy_bandedge_corr = 4; + + if (channel == 1 || channel == 2 || channel == 3 || + channel == 4 || channel == 9 || + channel == 10 || channel == 11 || channel == 12) { + bcma_chipco_pll_write(&pi->d11core->bus->drv_cc, 0x2, + 0x03000c04); + bcma_chipco_pll_maskset(&pi->d11core->bus->drv_cc, 0x3, + ~0x00ffffff, 0x0); + bcma_chipco_pll_write(&pi->d11core->bus->drv_cc, 0x4, + 0x200005c0); + + bcma_cc_set32(&pi->d11core->bus->drv_cc, BCMA_CC_PMU_CTL, + BCMA_CC_PMU_CTL_PLL_UPD); + write_phy_reg(pi, 0x942, 0); + wlc_lcnphy_txrx_spur_avoidance_mode(pi, false); + pi_lcn->lcnphy_spurmod = false; + mod_phy_reg(pi, 0x424, (0xff << 8), (0x1b) << 8); + + write_phy_reg(pi, 0x425, 0x5907); + } else { + bcma_chipco_pll_write(&pi->d11core->bus->drv_cc, 0x2, + 0x03140c04); + bcma_chipco_pll_maskset(&pi->d11core->bus->drv_cc, 0x3, + ~0x00ffffff, 0x333333); + bcma_chipco_pll_write(&pi->d11core->bus->drv_cc, 0x4, + 0x202c2820); + + bcma_cc_set32(&pi->d11core->bus->drv_cc, BCMA_CC_PMU_CTL, + BCMA_CC_PMU_CTL_PLL_UPD); + write_phy_reg(pi, 0x942, 0); + wlc_lcnphy_txrx_spur_avoidance_mode(pi, true); + + pi_lcn->lcnphy_spurmod = false; + mod_phy_reg(pi, 0x424, (0xff << 8), (0x1f) << 8); + + write_phy_reg(pi, 0x425, 0x590a); + } + + or_phy_reg(pi, 0x44a, 0x44); + write_phy_reg(pi, 0x44a, 0x80); +} + +static void +wlc_lcnphy_radio_2064_channel_tune_4313(struct brcms_phy *pi, u8 channel) +{ + uint i; + const struct chan_info_2064_lcnphy *ci; + u8 rfpll_doubler = 0; + u8 pll_pwrup, pll_pwrup_ovr; + s32 qFxtal, qFref, qFvco, qFcal; + u8 d15, d16, f16, e44, e45; + u32 div_int, div_frac, fvco3, fpfd, fref3, fcal_div; + u16 loop_bw, d30, setCount; + + u8 h29, h28_ten, e30, h30_ten, cp_current; + u16 g30, d28; + + ci = &chan_info_2064_lcnphy[0]; + rfpll_doubler = 1; + + mod_radio_reg(pi, RADIO_2064_REG09D, 0x4, 0x1 << 2); + + write_radio_reg(pi, RADIO_2064_REG09E, 0xf); + if (!rfpll_doubler) { + loop_bw = PLL_2064_LOOP_BW; + d30 = PLL_2064_D30; + } else { + loop_bw = PLL_2064_LOOP_BW_DOUBLER; + d30 = PLL_2064_D30_DOUBLER; + } + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + for (i = 0; i < ARRAY_SIZE(chan_info_2064_lcnphy); i++) + if (chan_info_2064_lcnphy[i].chan == channel) + break; + + if (i >= ARRAY_SIZE(chan_info_2064_lcnphy)) + return; + + ci = &chan_info_2064_lcnphy[i]; + } + + write_radio_reg(pi, RADIO_2064_REG02A, ci->logen_buftune); + + mod_radio_reg(pi, RADIO_2064_REG030, 0x3, ci->logen_rccr_tx); + + mod_radio_reg(pi, RADIO_2064_REG091, 0x3, ci->txrf_mix_tune_ctrl); + + mod_radio_reg(pi, RADIO_2064_REG038, 0xf, ci->pa_input_tune_g); + + mod_radio_reg(pi, RADIO_2064_REG030, 0x3 << 2, + (ci->logen_rccr_rx) << 2); + + mod_radio_reg(pi, RADIO_2064_REG05E, 0xf, ci->pa_rxrf_lna1_freq_tune); + + mod_radio_reg(pi, RADIO_2064_REG05E, (0xf) << 4, + (ci->pa_rxrf_lna2_freq_tune) << 4); + + write_radio_reg(pi, RADIO_2064_REG06C, ci->rxrf_rxrf_spare1); + + pll_pwrup = (u8) read_radio_reg(pi, RADIO_2064_REG044); + pll_pwrup_ovr = (u8) read_radio_reg(pi, RADIO_2064_REG12B); + + or_radio_reg(pi, RADIO_2064_REG044, 0x07); + + or_radio_reg(pi, RADIO_2064_REG12B, (0x07) << 1); + e44 = 0; + e45 = 0; + + fpfd = rfpll_doubler ? (pi->xtalfreq << 1) : (pi->xtalfreq); + if (pi->xtalfreq > 26000000) + e44 = 1; + if (pi->xtalfreq > 52000000) + e45 = 1; + if (e44 == 0) + fcal_div = 1; + else if (e45 == 0) + fcal_div = 2; + else + fcal_div = 4; + fvco3 = (ci->freq * 3); + fref3 = 2 * fpfd; + + qFxtal = wlc_lcnphy_qdiv_roundup(pi->xtalfreq, PLL_2064_MHZ, 16); + qFref = wlc_lcnphy_qdiv_roundup(fpfd, PLL_2064_MHZ, 16); + qFcal = pi->xtalfreq * fcal_div / PLL_2064_MHZ; + qFvco = wlc_lcnphy_qdiv_roundup(fvco3, 2, 16); + + write_radio_reg(pi, RADIO_2064_REG04F, 0x02); + + d15 = (pi->xtalfreq * fcal_div * 4 / 5) / PLL_2064_MHZ - 1; + write_radio_reg(pi, RADIO_2064_REG052, (0x07 & (d15 >> 2))); + write_radio_reg(pi, RADIO_2064_REG053, (d15 & 0x3) << 5); + + d16 = (qFcal * 8 / (d15 + 1)) - 1; + write_radio_reg(pi, RADIO_2064_REG051, d16); + + f16 = ((d16 + 1) * (d15 + 1)) / qFcal; + setCount = f16 * 3 * (ci->freq) / 32 - 1; + mod_radio_reg(pi, RADIO_2064_REG053, (0x0f << 0), + (u8) (setCount >> 8)); + + or_radio_reg(pi, RADIO_2064_REG053, 0x10); + write_radio_reg(pi, RADIO_2064_REG054, (u8) (setCount & 0xff)); + + div_int = ((fvco3 * (PLL_2064_MHZ >> 4)) / fref3) << 4; + + div_frac = ((fvco3 * (PLL_2064_MHZ >> 4)) % fref3) << 4; + while (div_frac >= fref3) { + div_int++; + div_frac -= fref3; + } + div_frac = wlc_lcnphy_qdiv_roundup(div_frac, fref3, 20); + + mod_radio_reg(pi, RADIO_2064_REG045, (0x1f << 0), + (u8) (div_int >> 4)); + mod_radio_reg(pi, RADIO_2064_REG046, (0x1f << 4), + (u8) (div_int << 4)); + mod_radio_reg(pi, RADIO_2064_REG046, (0x0f << 0), + (u8) (div_frac >> 16)); + write_radio_reg(pi, RADIO_2064_REG047, (u8) (div_frac >> 8) & 0xff); + write_radio_reg(pi, RADIO_2064_REG048, (u8) div_frac & 0xff); + + write_radio_reg(pi, RADIO_2064_REG040, 0xfb); + + write_radio_reg(pi, RADIO_2064_REG041, 0x9A); + write_radio_reg(pi, RADIO_2064_REG042, 0xA3); + write_radio_reg(pi, RADIO_2064_REG043, 0x0C); + + h29 = LCN_BW_LMT / loop_bw; + d28 = (((PLL_2064_HIGH_END_KVCO - PLL_2064_LOW_END_KVCO) * + (fvco3 / 2 - PLL_2064_LOW_END_VCO)) / + (PLL_2064_HIGH_END_VCO - PLL_2064_LOW_END_VCO)) + + PLL_2064_LOW_END_KVCO; + h28_ten = (d28 * 10) / LCN_VCO_DIV; + e30 = (d30 - LCN_OFFSET) / LCN_FACT; + g30 = LCN_OFFSET + (e30 * LCN_FACT); + h30_ten = (g30 * 10) / LCN_CUR_DIV; + cp_current = ((LCN_CUR_LMT * h29 * LCN_MULT * 100) / h28_ten) / h30_ten; + mod_radio_reg(pi, RADIO_2064_REG03C, 0x3f, cp_current); + + if (channel >= 1 && channel <= 5) + write_radio_reg(pi, RADIO_2064_REG03C, 0x8); + else + write_radio_reg(pi, RADIO_2064_REG03C, 0x7); + write_radio_reg(pi, RADIO_2064_REG03D, 0x3); + + mod_radio_reg(pi, RADIO_2064_REG044, 0x0c, 0x0c); + udelay(1); + + wlc_2064_vco_cal(pi); + + write_radio_reg(pi, RADIO_2064_REG044, pll_pwrup); + write_radio_reg(pi, RADIO_2064_REG12B, pll_pwrup_ovr); + if (LCNREV_IS(pi->pubpi.phy_rev, 1)) { + write_radio_reg(pi, RADIO_2064_REG038, 3); + write_radio_reg(pi, RADIO_2064_REG091, 7); + } +} + +static int +wlc_lcnphy_load_tx_iir_filter(struct brcms_phy *pi, bool is_ofdm, s16 filt_type) +{ + s16 filt_index = -1; + int j; + + u16 addr[] = { + 0x910, + 0x91e, + 0x91f, + 0x924, + 0x925, + 0x926, + 0x920, + 0x921, + 0x927, + 0x928, + 0x929, + 0x922, + 0x923, + 0x930, + 0x931, + 0x932 + }; + + u16 addr_ofdm[] = { + 0x90f, + 0x900, + 0x901, + 0x906, + 0x907, + 0x908, + 0x902, + 0x903, + 0x909, + 0x90a, + 0x90b, + 0x904, + 0x905, + 0x90c, + 0x90d, + 0x90e + }; + + if (!is_ofdm) { + for (j = 0; j < LCNPHY_NUM_TX_DIG_FILTERS_CCK; j++) { + if (filt_type == LCNPHY_txdigfiltcoeffs_cck[j][0]) { + filt_index = (s16) j; + break; + } + } + + if (filt_index != -1) { + for (j = 0; j < LCNPHY_NUM_DIG_FILT_COEFFS; j++) + write_phy_reg(pi, addr[j], + LCNPHY_txdigfiltcoeffs_cck + [filt_index][j + 1]); + } + } else { + for (j = 0; j < LCNPHY_NUM_TX_DIG_FILTERS_OFDM; j++) { + if (filt_type == LCNPHY_txdigfiltcoeffs_ofdm[j][0]) { + filt_index = (s16) j; + break; + } + } + + if (filt_index != -1) { + for (j = 0; j < LCNPHY_NUM_DIG_FILT_COEFFS; j++) + write_phy_reg(pi, addr_ofdm[j], + LCNPHY_txdigfiltcoeffs_ofdm + [filt_index][j + 1]); + } + } + + return (filt_index != -1) ? 0 : -1; +} + +static u16 wlc_lcnphy_get_pa_gain(struct brcms_phy *pi) +{ + u16 pa_gain; + + pa_gain = (read_phy_reg(pi, 0x4fb) & + LCNPHY_txgainctrlovrval1_pagain_ovr_val1_MASK) >> + LCNPHY_txgainctrlovrval1_pagain_ovr_val1_SHIFT; + + return pa_gain; +} + +static void wlc_lcnphy_set_tx_gain(struct brcms_phy *pi, + struct lcnphy_txgains *target_gains) +{ + u16 pa_gain = wlc_lcnphy_get_pa_gain(pi); + + mod_phy_reg( + pi, 0x4b5, + (0xffff << 0), + ((target_gains->gm_gain) | + (target_gains->pga_gain << 8)) << + 0); + mod_phy_reg(pi, 0x4fb, + (0x7fff << 0), + ((target_gains->pad_gain) | (pa_gain << 8)) << 0); + + mod_phy_reg( + pi, 0x4fc, + (0xffff << 0), + ((target_gains->gm_gain) | + (target_gains->pga_gain << 8)) << + 0); + mod_phy_reg(pi, 0x4fd, + (0x7fff << 0), + ((target_gains->pad_gain) | (pa_gain << 8)) << 0); + + wlc_lcnphy_set_dac_gain(pi, target_gains->dac_gain); + + wlc_lcnphy_enable_tx_gain_override(pi); +} + +static u8 wlc_lcnphy_get_bbmult(struct brcms_phy *pi) +{ + u16 m0m1; + struct phytbl_info tab; + + tab.tbl_ptr = &m0m1; + tab.tbl_len = 1; + tab.tbl_id = LCNPHY_TBL_ID_IQLOCAL; + tab.tbl_offset = 87; + tab.tbl_width = 16; + wlc_lcnphy_read_table(pi, &tab); + + return (u8) ((m0m1 & 0xff00) >> 8); +} + +static void wlc_lcnphy_set_bbmult(struct brcms_phy *pi, u8 m0) +{ + u16 m0m1 = (u16) m0 << 8; + struct phytbl_info tab; + + tab.tbl_ptr = &m0m1; + tab.tbl_len = 1; + tab.tbl_id = LCNPHY_TBL_ID_IQLOCAL; + tab.tbl_offset = 87; + tab.tbl_width = 16; + wlc_lcnphy_write_table(pi, &tab); +} + +static void wlc_lcnphy_clear_tx_power_offsets(struct brcms_phy *pi) +{ + u32 data_buf[64]; + struct phytbl_info tab; + + memset(data_buf, 0, sizeof(data_buf)); + + tab.tbl_id = LCNPHY_TBL_ID_TXPWRCTL; + tab.tbl_width = 32; + tab.tbl_ptr = data_buf; + + if (!wlc_lcnphy_tempsense_based_pwr_ctrl_enabled(pi)) { + + tab.tbl_len = 30; + tab.tbl_offset = LCNPHY_TX_PWR_CTRL_RATE_OFFSET; + wlc_lcnphy_write_table(pi, &tab); + } + + tab.tbl_len = 64; + tab.tbl_offset = LCNPHY_TX_PWR_CTRL_MAC_OFFSET; + wlc_lcnphy_write_table(pi, &tab); +} + +enum lcnphy_tssi_mode { + LCNPHY_TSSI_PRE_PA, + LCNPHY_TSSI_POST_PA, + LCNPHY_TSSI_EXT +}; + +static void +wlc_lcnphy_set_tssi_mux(struct brcms_phy *pi, enum lcnphy_tssi_mode pos) +{ + mod_phy_reg(pi, 0x4d7, (0x1 << 0), (0x1) << 0); + + mod_phy_reg(pi, 0x4d7, (0x1 << 6), (1) << 6); + + if (LCNPHY_TSSI_POST_PA == pos) { + mod_phy_reg(pi, 0x4d9, (0x1 << 2), (0) << 2); + + mod_phy_reg(pi, 0x4d9, (0x1 << 3), (1) << 3); + + if (LCNREV_IS(pi->pubpi.phy_rev, 2)) { + mod_radio_reg(pi, RADIO_2064_REG086, 0x4, 0x4); + } else { + mod_radio_reg(pi, RADIO_2064_REG03A, 1, 0x1); + mod_radio_reg(pi, RADIO_2064_REG11A, 0x8, 0x8); + } + } else { + mod_phy_reg(pi, 0x4d9, (0x1 << 2), (0x1) << 2); + + mod_phy_reg(pi, 0x4d9, (0x1 << 3), (0) << 3); + + if (LCNREV_IS(pi->pubpi.phy_rev, 2)) { + mod_radio_reg(pi, RADIO_2064_REG086, 0x4, 0x4); + } else { + mod_radio_reg(pi, RADIO_2064_REG03A, 1, 0); + mod_radio_reg(pi, RADIO_2064_REG11A, 0x8, 0x8); + } + } + mod_phy_reg(pi, 0x637, (0x3 << 14), (0) << 14); + + if (LCNPHY_TSSI_EXT == pos) { + write_radio_reg(pi, RADIO_2064_REG07F, 1); + mod_radio_reg(pi, RADIO_2064_REG005, 0x7, 0x2); + mod_radio_reg(pi, RADIO_2064_REG112, 0x80, 0x1 << 7); + mod_radio_reg(pi, RADIO_2064_REG028, 0x1f, 0x3); + } +} + +static u16 wlc_lcnphy_rfseq_tbl_adc_pwrup(struct brcms_phy *pi) +{ + u16 N1, N2, N3, N4, N5, N6, N; + N1 = ((read_phy_reg(pi, 0x4a5) & (0xff << 0)) + >> 0); + N2 = 1 << ((read_phy_reg(pi, 0x4a5) & (0x7 << 12)) + >> 12); + N3 = ((read_phy_reg(pi, 0x40d) & (0xff << 0)) + >> 0); + N4 = 1 << ((read_phy_reg(pi, 0x40d) & (0x7 << 8)) + >> 8); + N5 = ((read_phy_reg(pi, 0x4a2) & (0xff << 0)) + >> 0); + N6 = 1 << ((read_phy_reg(pi, 0x4a2) & (0x7 << 8)) + >> 8); + N = 2 * (N1 + N2 + N3 + N4 + 2 * (N5 + N6)) + 80; + if (N < 1600) + N = 1600; + return N; +} + +static void wlc_lcnphy_pwrctrl_rssiparams(struct brcms_phy *pi) +{ + u16 auxpga_vmid, auxpga_vmid_temp, auxpga_gain_temp; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + auxpga_vmid = (2 << 8) | + (pi_lcn->lcnphy_rssi_vc << 4) | pi_lcn->lcnphy_rssi_vf; + auxpga_vmid_temp = (2 << 8) | (8 << 4) | 4; + auxpga_gain_temp = 2; + + mod_phy_reg(pi, 0x4d8, (0x1 << 0), (0) << 0); + + mod_phy_reg(pi, 0x4d8, (0x1 << 1), (0) << 1); + + mod_phy_reg(pi, 0x4d7, (0x1 << 3), (0) << 3); + + mod_phy_reg(pi, 0x4db, + (0x3ff << 0) | + (0x7 << 12), + (auxpga_vmid << 0) | (pi_lcn->lcnphy_rssi_gs << 12)); + + mod_phy_reg(pi, 0x4dc, + (0x3ff << 0) | + (0x7 << 12), + (auxpga_vmid << 0) | (pi_lcn->lcnphy_rssi_gs << 12)); + + mod_phy_reg(pi, 0x40a, + (0x3ff << 0) | + (0x7 << 12), + (auxpga_vmid << 0) | (pi_lcn->lcnphy_rssi_gs << 12)); + + mod_phy_reg(pi, 0x40b, + (0x3ff << 0) | + (0x7 << 12), + (auxpga_vmid_temp << 0) | (auxpga_gain_temp << 12)); + + mod_phy_reg(pi, 0x40c, + (0x3ff << 0) | + (0x7 << 12), + (auxpga_vmid_temp << 0) | (auxpga_gain_temp << 12)); + + mod_radio_reg(pi, RADIO_2064_REG082, (1 << 5), (1 << 5)); +} + +static void wlc_lcnphy_tssi_setup(struct brcms_phy *pi) +{ + struct phytbl_info tab; + u32 rfseq, ind; + + tab.tbl_id = LCNPHY_TBL_ID_TXPWRCTL; + tab.tbl_width = 32; + tab.tbl_ptr = &ind; + tab.tbl_len = 1; + tab.tbl_offset = 0; + for (ind = 0; ind < 128; ind++) { + wlc_lcnphy_write_table(pi, &tab); + tab.tbl_offset++; + } + tab.tbl_offset = 704; + for (ind = 0; ind < 128; ind++) { + wlc_lcnphy_write_table(pi, &tab); + tab.tbl_offset++; + } + mod_phy_reg(pi, 0x503, (0x1 << 0), (0) << 0); + + mod_phy_reg(pi, 0x503, (0x1 << 2), (0) << 2); + + mod_phy_reg(pi, 0x503, (0x1 << 4), (1) << 4); + + wlc_lcnphy_set_tssi_mux(pi, LCNPHY_TSSI_EXT); + mod_phy_reg(pi, 0x4a4, (0x1 << 14), (0) << 14); + + mod_phy_reg(pi, 0x4a4, (0x1 << 15), (1) << 15); + + mod_phy_reg(pi, 0x4d0, (0x1 << 5), (0) << 5); + + mod_phy_reg(pi, 0x4a4, (0x1ff << 0), (0) << 0); + + mod_phy_reg(pi, 0x4a5, (0xff << 0), (255) << 0); + + mod_phy_reg(pi, 0x4a5, (0x7 << 12), (5) << 12); + + mod_phy_reg(pi, 0x4a5, (0x7 << 8), (0) << 8); + + mod_phy_reg(pi, 0x40d, (0xff << 0), (64) << 0); + + mod_phy_reg(pi, 0x40d, (0x7 << 8), (4) << 8); + + mod_phy_reg(pi, 0x4a2, (0xff << 0), (64) << 0); + + mod_phy_reg(pi, 0x4a2, (0x7 << 8), (4) << 8); + + mod_phy_reg(pi, 0x4d0, (0x1ff << 6), (0) << 6); + + mod_phy_reg(pi, 0x4a8, (0xff << 0), (0x1) << 0); + + wlc_lcnphy_clear_tx_power_offsets(pi); + + mod_phy_reg(pi, 0x4a6, (0x1 << 15), (1) << 15); + + mod_phy_reg(pi, 0x4a6, (0x1ff << 0), (0xff) << 0); + + mod_phy_reg(pi, 0x49a, (0x1ff << 0), (0xff) << 0); + + if (LCNREV_IS(pi->pubpi.phy_rev, 2)) { + mod_radio_reg(pi, RADIO_2064_REG028, 0xf, 0xe); + mod_radio_reg(pi, RADIO_2064_REG086, 0x4, 0x4); + } else { + mod_radio_reg(pi, RADIO_2064_REG03A, 0x1, 1); + mod_radio_reg(pi, RADIO_2064_REG11A, 0x8, 1 << 3); + } + + write_radio_reg(pi, RADIO_2064_REG025, 0xc); + + if (LCNREV_IS(pi->pubpi.phy_rev, 2)) { + mod_radio_reg(pi, RADIO_2064_REG03A, 0x1, 1); + } else { + if (CHSPEC_IS2G(pi->radio_chanspec)) + mod_radio_reg(pi, RADIO_2064_REG03A, 0x2, 1 << 1); + else + mod_radio_reg(pi, RADIO_2064_REG03A, 0x2, 0 << 1); + } + + if (LCNREV_IS(pi->pubpi.phy_rev, 2)) + mod_radio_reg(pi, RADIO_2064_REG03A, 0x2, 1 << 1); + else + mod_radio_reg(pi, RADIO_2064_REG03A, 0x4, 1 << 2); + + mod_radio_reg(pi, RADIO_2064_REG11A, 0x1, 1 << 0); + + mod_radio_reg(pi, RADIO_2064_REG005, 0x8, 1 << 3); + + if (!wlc_lcnphy_tempsense_based_pwr_ctrl_enabled(pi)) + mod_phy_reg(pi, 0x4d7, + (0x1 << 3) | (0x7 << 12), 0 << 3 | 2 << 12); + + rfseq = wlc_lcnphy_rfseq_tbl_adc_pwrup(pi); + tab.tbl_id = LCNPHY_TBL_ID_RFSEQ; + tab.tbl_width = 16; + tab.tbl_ptr = &rfseq; + tab.tbl_len = 1; + tab.tbl_offset = 6; + wlc_lcnphy_write_table(pi, &tab); + + mod_phy_reg(pi, 0x938, (0x1 << 2), (1) << 2); + + mod_phy_reg(pi, 0x939, (0x1 << 2), (1) << 2); + + mod_phy_reg(pi, 0x4a4, (0x1 << 12), (1) << 12); + + mod_phy_reg(pi, 0x4d7, (0x1 << 2), (1) << 2); + + mod_phy_reg(pi, 0x4d7, (0xf << 8), (0) << 8); + + wlc_lcnphy_pwrctrl_rssiparams(pi); +} + +void wlc_lcnphy_tx_pwr_update_npt(struct brcms_phy *pi) +{ + u16 tx_cnt, tx_total, npt; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + tx_total = wlc_lcnphy_total_tx_frames(pi); + tx_cnt = tx_total - pi_lcn->lcnphy_tssi_tx_cnt; + npt = wlc_lcnphy_get_tx_pwr_npt(pi); + + if (tx_cnt > (1 << npt)) { + + pi_lcn->lcnphy_tssi_tx_cnt = tx_total; + + pi_lcn->lcnphy_tssi_idx = wlc_lcnphy_get_current_tx_pwr_idx(pi); + pi_lcn->lcnphy_tssi_npt = npt; + + } +} + +s32 wlc_lcnphy_tssi2dbm(s32 tssi, s32 a1, s32 b0, s32 b1) +{ + s32 a, b, p; + + a = 32768 + (a1 * tssi); + b = (1024 * b0) + (64 * b1 * tssi); + p = ((2 * b) + a) / (2 * a); + + return p; +} + +static void wlc_lcnphy_txpower_reset_npt(struct brcms_phy *pi) +{ + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + if (wlc_lcnphy_tempsense_based_pwr_ctrl_enabled(pi)) + return; + + pi_lcn->lcnphy_tssi_idx = LCNPHY_TX_PWR_CTRL_START_INDEX_2G_4313; + pi_lcn->lcnphy_tssi_npt = LCNPHY_TX_PWR_CTRL_START_NPT; +} + +void wlc_lcnphy_txpower_recalc_target(struct brcms_phy *pi) +{ + struct phytbl_info tab; + u32 rate_table[BRCMS_NUM_RATES_CCK + BRCMS_NUM_RATES_OFDM + + BRCMS_NUM_RATES_MCS_1_STREAM]; + uint i, j; + if (wlc_lcnphy_tempsense_based_pwr_ctrl_enabled(pi)) + return; + + for (i = 0, j = 0; i < ARRAY_SIZE(rate_table); i++, j++) { + + if (i == BRCMS_NUM_RATES_CCK + BRCMS_NUM_RATES_OFDM) + j = TXP_FIRST_MCS_20_SISO; + + rate_table[i] = (u32) ((s32) (-pi->tx_power_offset[j])); + } + + tab.tbl_id = LCNPHY_TBL_ID_TXPWRCTL; + tab.tbl_width = 32; + tab.tbl_len = ARRAY_SIZE(rate_table); + tab.tbl_ptr = rate_table; + tab.tbl_offset = LCNPHY_TX_PWR_CTRL_RATE_OFFSET; + wlc_lcnphy_write_table(pi, &tab); + + if (wlc_lcnphy_get_target_tx_pwr(pi) != pi->tx_power_min) { + wlc_lcnphy_set_target_tx_pwr(pi, pi->tx_power_min); + + wlc_lcnphy_txpower_reset_npt(pi); + } +} + +static void wlc_lcnphy_set_tx_pwr_soft_ctrl(struct brcms_phy *pi, s8 index) +{ + u32 cck_offset[4] = { 22, 22, 22, 22 }; + u32 ofdm_offset, reg_offset_cck; + int i; + u16 index2; + struct phytbl_info tab; + + if (wlc_lcnphy_tssi_based_pwr_ctrl_enabled(pi)) + return; + + mod_phy_reg(pi, 0x4a4, (0x1 << 14), (0x1) << 14); + + mod_phy_reg(pi, 0x4a4, (0x1 << 14), (0x0) << 14); + + or_phy_reg(pi, 0x6da, 0x0040); + + reg_offset_cck = 0; + for (i = 0; i < 4; i++) + cck_offset[i] -= reg_offset_cck; + tab.tbl_id = LCNPHY_TBL_ID_TXPWRCTL; + tab.tbl_width = 32; + tab.tbl_len = 4; + tab.tbl_ptr = cck_offset; + tab.tbl_offset = LCNPHY_TX_PWR_CTRL_RATE_OFFSET; + wlc_lcnphy_write_table(pi, &tab); + ofdm_offset = 0; + tab.tbl_len = 1; + tab.tbl_ptr = &ofdm_offset; + for (i = 836; i < 862; i++) { + tab.tbl_offset = i; + wlc_lcnphy_write_table(pi, &tab); + } + + mod_phy_reg(pi, 0x4a4, (0x1 << 15), (0x1) << 15); + + mod_phy_reg(pi, 0x4a4, (0x1 << 14), (0x1) << 14); + + mod_phy_reg(pi, 0x4a4, (0x1 << 13), (0x1) << 13); + + mod_phy_reg(pi, 0x4b0, (0x1 << 7), (0) << 7); + + mod_phy_reg(pi, 0x43b, (0x1 << 6), (0) << 6); + + mod_phy_reg(pi, 0x4a9, (0x1 << 15), (1) << 15); + + index2 = (u16) (index * 2); + mod_phy_reg(pi, 0x4a9, (0x1ff << 0), (index2) << 0); + + mod_phy_reg(pi, 0x6a3, (0x1 << 4), (0) << 4); + +} + +static s8 wlc_lcnphy_tempcompensated_txpwrctrl(struct brcms_phy *pi) +{ + s8 index, delta_brd, delta_temp, new_index, tempcorrx; + s16 manp, meas_temp, temp_diff; + bool neg = false; + u16 temp; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + if (wlc_lcnphy_tssi_based_pwr_ctrl_enabled(pi)) + return pi_lcn->lcnphy_current_index; + + index = FIXED_TXPWR; + + if (pi_lcn->lcnphy_tempsense_slope == 0) + return index; + + temp = (u16) wlc_lcnphy_tempsense(pi, 0); + meas_temp = LCNPHY_TEMPSENSE(temp); + + if (pi->tx_power_min != 0) + delta_brd = (pi_lcn->lcnphy_measPower - pi->tx_power_min); + else + delta_brd = 0; + + manp = LCNPHY_TEMPSENSE(pi_lcn->lcnphy_rawtempsense); + temp_diff = manp - meas_temp; + if (temp_diff < 0) { + neg = true; + temp_diff = -temp_diff; + } + + delta_temp = (s8) wlc_lcnphy_qdiv_roundup((u32) (temp_diff * 192), + (u32) (pi_lcn-> + lcnphy_tempsense_slope + * 10), 0); + if (neg) + delta_temp = -delta_temp; + + if (pi_lcn->lcnphy_tempsense_option == 3 + && LCNREV_IS(pi->pubpi.phy_rev, 0)) + delta_temp = 0; + if (pi_lcn->lcnphy_tempcorrx > 31) + tempcorrx = (s8) (pi_lcn->lcnphy_tempcorrx - 64); + else + tempcorrx = (s8) pi_lcn->lcnphy_tempcorrx; + if (LCNREV_IS(pi->pubpi.phy_rev, 1)) + tempcorrx = 4; + new_index = + index + delta_brd + delta_temp - pi_lcn->lcnphy_bandedge_corr; + new_index += tempcorrx; + + if (LCNREV_IS(pi->pubpi.phy_rev, 1)) + index = 127; + + if (new_index < 0 || new_index > 126) + return index; + + return new_index; +} + +static u16 wlc_lcnphy_set_tx_pwr_ctrl_mode(struct brcms_phy *pi, u16 mode) +{ + + u16 current_mode = mode; + if (wlc_lcnphy_tempsense_based_pwr_ctrl_enabled(pi) && + mode == LCNPHY_TX_PWR_CTRL_HW) + current_mode = LCNPHY_TX_PWR_CTRL_TEMPBASED; + if (wlc_lcnphy_tssi_based_pwr_ctrl_enabled(pi) && + mode == LCNPHY_TX_PWR_CTRL_TEMPBASED) + current_mode = LCNPHY_TX_PWR_CTRL_HW; + return current_mode; +} + +void wlc_lcnphy_set_tx_pwr_ctrl(struct brcms_phy *pi, u16 mode) +{ + u16 old_mode = wlc_lcnphy_get_tx_pwr_ctrl(pi); + s8 index; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + mode = wlc_lcnphy_set_tx_pwr_ctrl_mode(pi, mode); + old_mode = wlc_lcnphy_set_tx_pwr_ctrl_mode(pi, old_mode); + + mod_phy_reg(pi, 0x6da, (0x1 << 6), + ((LCNPHY_TX_PWR_CTRL_HW == mode) ? 1 : 0) << 6); + + mod_phy_reg(pi, 0x6a3, (0x1 << 4), + ((LCNPHY_TX_PWR_CTRL_HW == mode) ? 0 : 1) << 4); + + if (old_mode != mode) { + if (LCNPHY_TX_PWR_CTRL_HW == old_mode) { + + wlc_lcnphy_tx_pwr_update_npt(pi); + + wlc_lcnphy_clear_tx_power_offsets(pi); + } + if (LCNPHY_TX_PWR_CTRL_HW == mode) { + + wlc_lcnphy_txpower_recalc_target(pi); + + wlc_lcnphy_set_start_tx_pwr_idx(pi, + pi_lcn-> + lcnphy_tssi_idx); + wlc_lcnphy_set_tx_pwr_npt(pi, pi_lcn->lcnphy_tssi_npt); + mod_radio_reg(pi, RADIO_2064_REG11F, 0x4, 0); + + pi_lcn->lcnphy_tssi_tx_cnt = + wlc_lcnphy_total_tx_frames(pi); + + wlc_lcnphy_disable_tx_gain_override(pi); + pi_lcn->lcnphy_tx_power_idx_override = -1; + } else + wlc_lcnphy_enable_tx_gain_override(pi); + + mod_phy_reg(pi, 0x4a4, + ((0x1 << 15) | (0x1 << 14) | (0x1 << 13)), mode); + if (mode == LCNPHY_TX_PWR_CTRL_TEMPBASED) { + index = wlc_lcnphy_tempcompensated_txpwrctrl(pi); + wlc_lcnphy_set_tx_pwr_soft_ctrl(pi, index); + pi_lcn->lcnphy_current_index = (s8) + ((read_phy_reg(pi, + 0x4a9) & + 0xFF) / 2); + } + } +} + +static void +wlc_lcnphy_tx_iqlo_loopback(struct brcms_phy *pi, u16 *values_to_save) +{ + u16 vmid; + int i; + for (i = 0; i < 20; i++) + values_to_save[i] = + read_radio_reg(pi, iqlo_loopback_rf_regs[i]); + + mod_phy_reg(pi, 0x44c, (0x1 << 12), 1 << 12); + mod_phy_reg(pi, 0x44d, (0x1 << 14), 1 << 14); + + mod_phy_reg(pi, 0x44c, (0x1 << 11), 1 << 11); + mod_phy_reg(pi, 0x44d, (0x1 << 13), 0 << 13); + + mod_phy_reg(pi, 0x43b, (0x1 << 1), 1 << 1); + mod_phy_reg(pi, 0x43c, (0x1 << 1), 0 << 1); + + mod_phy_reg(pi, 0x43b, (0x1 << 0), 1 << 0); + mod_phy_reg(pi, 0x43c, (0x1 << 0), 0 << 0); + + if (LCNREV_IS(pi->pubpi.phy_rev, 2)) + and_radio_reg(pi, RADIO_2064_REG03A, 0xFD); + else + and_radio_reg(pi, RADIO_2064_REG03A, 0xF9); + or_radio_reg(pi, RADIO_2064_REG11A, 0x1); + + or_radio_reg(pi, RADIO_2064_REG036, 0x01); + or_radio_reg(pi, RADIO_2064_REG11A, 0x18); + udelay(20); + + if (LCNREV_IS(pi->pubpi.phy_rev, 2)) { + if (CHSPEC_IS5G(pi->radio_chanspec)) + mod_radio_reg(pi, RADIO_2064_REG03A, 1, 0); + else + or_radio_reg(pi, RADIO_2064_REG03A, 1); + } else { + if (CHSPEC_IS5G(pi->radio_chanspec)) + mod_radio_reg(pi, RADIO_2064_REG03A, 3, 1); + else + or_radio_reg(pi, RADIO_2064_REG03A, 0x3); + } + + udelay(20); + + write_radio_reg(pi, RADIO_2064_REG025, 0xF); + if (LCNREV_IS(pi->pubpi.phy_rev, 2)) { + if (CHSPEC_IS5G(pi->radio_chanspec)) + mod_radio_reg(pi, RADIO_2064_REG028, 0xF, 0x4); + else + mod_radio_reg(pi, RADIO_2064_REG028, 0xF, 0x6); + } else { + if (CHSPEC_IS5G(pi->radio_chanspec)) + mod_radio_reg(pi, RADIO_2064_REG028, 0x1e, 0x4 << 1); + else + mod_radio_reg(pi, RADIO_2064_REG028, 0x1e, 0x6 << 1); + } + + udelay(20); + + write_radio_reg(pi, RADIO_2064_REG005, 0x8); + or_radio_reg(pi, RADIO_2064_REG112, 0x80); + udelay(20); + + or_radio_reg(pi, RADIO_2064_REG0FF, 0x10); + or_radio_reg(pi, RADIO_2064_REG11F, 0x44); + udelay(20); + + or_radio_reg(pi, RADIO_2064_REG00B, 0x7); + or_radio_reg(pi, RADIO_2064_REG113, 0x10); + udelay(20); + + write_radio_reg(pi, RADIO_2064_REG007, 0x1); + udelay(20); + + vmid = 0x2A6; + mod_radio_reg(pi, RADIO_2064_REG0FC, 0x3 << 0, (vmid >> 8) & 0x3); + write_radio_reg(pi, RADIO_2064_REG0FD, (vmid & 0xff)); + or_radio_reg(pi, RADIO_2064_REG11F, 0x44); + udelay(20); + + or_radio_reg(pi, RADIO_2064_REG0FF, 0x10); + udelay(20); + write_radio_reg(pi, RADIO_2064_REG012, 0x02); + or_radio_reg(pi, RADIO_2064_REG112, 0x06); + write_radio_reg(pi, RADIO_2064_REG036, 0x11); + write_radio_reg(pi, RADIO_2064_REG059, 0xcc); + write_radio_reg(pi, RADIO_2064_REG05C, 0x2e); + write_radio_reg(pi, RADIO_2064_REG078, 0xd7); + write_radio_reg(pi, RADIO_2064_REG092, 0x15); +} + +static bool wlc_lcnphy_iqcal_wait(struct brcms_phy *pi) +{ + uint delay_count = 0; + + while (wlc_lcnphy_iqcal_active(pi)) { + udelay(100); + delay_count++; + + if (delay_count > (10 * 500)) + break; + } + + return (0 == wlc_lcnphy_iqcal_active(pi)); +} + +static void +wlc_lcnphy_tx_iqlo_loopback_cleanup(struct brcms_phy *pi, u16 *values_to_save) +{ + int i; + + and_phy_reg(pi, 0x44c, 0x0 >> 11); + + and_phy_reg(pi, 0x43b, 0xC); + + for (i = 0; i < 20; i++) + write_radio_reg(pi, iqlo_loopback_rf_regs[i], + values_to_save[i]); +} + +static void +wlc_lcnphy_tx_iqlo_cal(struct brcms_phy *pi, + struct lcnphy_txgains *target_gains, + enum lcnphy_cal_mode cal_mode, bool keep_tone) +{ + + struct lcnphy_txgains cal_gains, temp_gains; + u16 hash; + u8 band_idx; + int j; + u16 ncorr_override[5]; + u16 syst_coeffs[] = { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}; + + u16 commands_fullcal[] = { + 0x8434, 0x8334, 0x8084, 0x8267, 0x8056, 0x8234 + }; + + u16 commands_recal[] = { + 0x8434, 0x8334, 0x8084, 0x8267, 0x8056, 0x8234 + }; + + u16 command_nums_fullcal[] = { + 0x7a97, 0x7a97, 0x7a97, 0x7a87, 0x7a87, 0x7b97 + }; + + u16 command_nums_recal[] = { + 0x7a97, 0x7a97, 0x7a97, 0x7a87, 0x7a87, 0x7b97 + }; + u16 *command_nums = command_nums_fullcal; + + u16 *start_coeffs = NULL, *cal_cmds = NULL, cal_type, diq_start; + u16 tx_pwr_ctrl_old, save_txpwrctrlrfctrl2; + u16 save_sslpnCalibClkEnCtrl, save_sslpnRxFeClkEnCtrl; + bool tx_gain_override_old; + struct lcnphy_txgains old_gains; + uint i, n_cal_cmds = 0, n_cal_start = 0; + u16 *values_to_save; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + values_to_save = kmalloc(sizeof(u16) * 20, GFP_ATOMIC); + if (NULL == values_to_save) + return; + + save_sslpnRxFeClkEnCtrl = read_phy_reg(pi, 0x6db); + save_sslpnCalibClkEnCtrl = read_phy_reg(pi, 0x6da); + + or_phy_reg(pi, 0x6da, 0x40); + or_phy_reg(pi, 0x6db, 0x3); + + switch (cal_mode) { + case LCNPHY_CAL_FULL: + start_coeffs = syst_coeffs; + cal_cmds = commands_fullcal; + n_cal_cmds = ARRAY_SIZE(commands_fullcal); + break; + + case LCNPHY_CAL_RECAL: + start_coeffs = syst_coeffs; + cal_cmds = commands_recal; + n_cal_cmds = ARRAY_SIZE(commands_recal); + command_nums = command_nums_recal; + break; + + default: + break; + } + + wlc_lcnphy_common_write_table(pi, LCNPHY_TBL_ID_IQLOCAL, + start_coeffs, 11, 16, 64); + + write_phy_reg(pi, 0x6da, 0xffff); + mod_phy_reg(pi, 0x503, (0x1 << 3), (1) << 3); + + tx_pwr_ctrl_old = wlc_lcnphy_get_tx_pwr_ctrl(pi); + + mod_phy_reg(pi, 0x4a4, (0x1 << 12), (1) << 12); + + wlc_lcnphy_set_tx_pwr_ctrl(pi, LCNPHY_TX_PWR_CTRL_OFF); + + save_txpwrctrlrfctrl2 = read_phy_reg(pi, 0x4db); + + mod_phy_reg(pi, 0x4db, (0x3ff << 0), (0x2a6) << 0); + + mod_phy_reg(pi, 0x4db, (0x7 << 12), (2) << 12); + + wlc_lcnphy_tx_iqlo_loopback(pi, values_to_save); + + tx_gain_override_old = wlc_lcnphy_tx_gain_override_enabled(pi); + if (tx_gain_override_old) + wlc_lcnphy_get_tx_gain(pi, &old_gains); + + if (!target_gains) { + if (!tx_gain_override_old) + wlc_lcnphy_set_tx_pwr_by_index(pi, + pi_lcn->lcnphy_tssi_idx); + wlc_lcnphy_get_tx_gain(pi, &temp_gains); + target_gains = &temp_gains; + } + + hash = (target_gains->gm_gain << 8) | + (target_gains->pga_gain << 4) | (target_gains->pad_gain); + + band_idx = (CHSPEC_IS5G(pi->radio_chanspec) ? 1 : 0); + + cal_gains = *target_gains; + memset(ncorr_override, 0, sizeof(ncorr_override)); + for (j = 0; j < iqcal_gainparams_numgains_lcnphy[band_idx]; j++) { + if (hash == tbl_iqcal_gainparams_lcnphy[band_idx][j][0]) { + cal_gains.gm_gain = + tbl_iqcal_gainparams_lcnphy[band_idx][j][1]; + cal_gains.pga_gain = + tbl_iqcal_gainparams_lcnphy[band_idx][j][2]; + cal_gains.pad_gain = + tbl_iqcal_gainparams_lcnphy[band_idx][j][3]; + memcpy(ncorr_override, + &tbl_iqcal_gainparams_lcnphy[band_idx][j][3], + sizeof(ncorr_override)); + break; + } + } + + wlc_lcnphy_set_tx_gain(pi, &cal_gains); + + write_phy_reg(pi, 0x453, 0xaa9); + write_phy_reg(pi, 0x93d, 0xc0); + + wlc_lcnphy_common_write_table(pi, LCNPHY_TBL_ID_IQLOCAL, + lcnphy_iqcal_loft_gainladder, + ARRAY_SIZE(lcnphy_iqcal_loft_gainladder), + 16, 0); + + wlc_lcnphy_common_write_table(pi, LCNPHY_TBL_ID_IQLOCAL, + lcnphy_iqcal_ir_gainladder, + ARRAY_SIZE( + lcnphy_iqcal_ir_gainladder), 16, + 32); + + if (pi->phy_tx_tone_freq) { + + wlc_lcnphy_stop_tx_tone(pi); + udelay(5); + wlc_lcnphy_start_tx_tone(pi, 3750, 88, 1); + } else { + wlc_lcnphy_start_tx_tone(pi, 3750, 88, 1); + } + + write_phy_reg(pi, 0x6da, 0xffff); + + for (i = n_cal_start; i < n_cal_cmds; i++) { + u16 zero_diq = 0; + u16 best_coeffs[11]; + u16 command_num; + + cal_type = (cal_cmds[i] & 0x0f00) >> 8; + + command_num = command_nums[i]; + if (ncorr_override[cal_type]) + command_num = + ncorr_override[cal_type] << 8 | (command_num & + 0xff); + + write_phy_reg(pi, 0x452, command_num); + + if ((cal_type == 3) || (cal_type == 4)) { + wlc_lcnphy_common_read_table(pi, LCNPHY_TBL_ID_IQLOCAL, + &diq_start, 1, 16, 69); + + wlc_lcnphy_common_write_table(pi, LCNPHY_TBL_ID_IQLOCAL, + &zero_diq, 1, 16, 69); + } + + write_phy_reg(pi, 0x451, cal_cmds[i]); + + if (!wlc_lcnphy_iqcal_wait(pi)) + goto cleanup; + + wlc_lcnphy_common_read_table(pi, LCNPHY_TBL_ID_IQLOCAL, + best_coeffs, + ARRAY_SIZE(best_coeffs), 16, 96); + wlc_lcnphy_common_write_table(pi, LCNPHY_TBL_ID_IQLOCAL, + best_coeffs, + ARRAY_SIZE(best_coeffs), 16, 64); + + if ((cal_type == 3) || (cal_type == 4)) + wlc_lcnphy_common_write_table(pi, LCNPHY_TBL_ID_IQLOCAL, + &diq_start, 1, 16, 69); + wlc_lcnphy_common_read_table(pi, LCNPHY_TBL_ID_IQLOCAL, + pi_lcn->lcnphy_cal_results. + txiqlocal_bestcoeffs, + ARRAY_SIZE(pi_lcn-> + lcnphy_cal_results. + txiqlocal_bestcoeffs), + 16, 96); + } + + wlc_lcnphy_common_read_table(pi, LCNPHY_TBL_ID_IQLOCAL, + pi_lcn->lcnphy_cal_results. + txiqlocal_bestcoeffs, + ARRAY_SIZE(pi_lcn->lcnphy_cal_results. + txiqlocal_bestcoeffs), 16, 96); + pi_lcn->lcnphy_cal_results.txiqlocal_bestcoeffs_valid = true; + + wlc_lcnphy_common_write_table(pi, LCNPHY_TBL_ID_IQLOCAL, + &pi_lcn->lcnphy_cal_results. + txiqlocal_bestcoeffs[0], 4, 16, 80); + + wlc_lcnphy_common_write_table(pi, LCNPHY_TBL_ID_IQLOCAL, + &pi_lcn->lcnphy_cal_results. + txiqlocal_bestcoeffs[5], 2, 16, 85); + +cleanup: + wlc_lcnphy_tx_iqlo_loopback_cleanup(pi, values_to_save); + kfree(values_to_save); + + if (!keep_tone) + wlc_lcnphy_stop_tx_tone(pi); + + write_phy_reg(pi, 0x4db, save_txpwrctrlrfctrl2); + + write_phy_reg(pi, 0x453, 0); + + if (tx_gain_override_old) + wlc_lcnphy_set_tx_gain(pi, &old_gains); + wlc_lcnphy_set_tx_pwr_ctrl(pi, tx_pwr_ctrl_old); + + write_phy_reg(pi, 0x6da, save_sslpnCalibClkEnCtrl); + write_phy_reg(pi, 0x6db, save_sslpnRxFeClkEnCtrl); + +} + +static void wlc_lcnphy_idle_tssi_est(struct brcms_phy_pub *ppi) +{ + bool suspend, tx_gain_override_old; + struct lcnphy_txgains old_gains; + struct brcms_phy *pi = (struct brcms_phy *) ppi; + u16 idleTssi, idleTssi0_2C, idleTssi0_OB, idleTssi0_regvalue_OB, + idleTssi0_regvalue_2C; + u16 SAVE_txpwrctrl = wlc_lcnphy_get_tx_pwr_ctrl(pi); + u16 SAVE_lpfgain = read_radio_reg(pi, RADIO_2064_REG112); + u16 SAVE_jtag_bb_afe_switch = + read_radio_reg(pi, RADIO_2064_REG007) & 1; + u16 SAVE_jtag_auxpga = read_radio_reg(pi, RADIO_2064_REG0FF) & 0x10; + u16 SAVE_iqadc_aux_en = read_radio_reg(pi, RADIO_2064_REG11F) & 4; + idleTssi = read_phy_reg(pi, 0x4ab); + suspend = (0 == (bcma_read32(pi->d11core, D11REGOFFS(maccontrol)) & + MCTL_EN_MAC)); + if (!suspend) + wlapi_suspend_mac_and_wait(pi->sh->physhim); + wlc_lcnphy_set_tx_pwr_ctrl(pi, LCNPHY_TX_PWR_CTRL_OFF); + + tx_gain_override_old = wlc_lcnphy_tx_gain_override_enabled(pi); + wlc_lcnphy_get_tx_gain(pi, &old_gains); + + wlc_lcnphy_enable_tx_gain_override(pi); + wlc_lcnphy_set_tx_pwr_by_index(pi, 127); + write_radio_reg(pi, RADIO_2064_REG112, 0x6); + mod_radio_reg(pi, RADIO_2064_REG007, 0x1, 1); + mod_radio_reg(pi, RADIO_2064_REG0FF, 0x10, 1 << 4); + mod_radio_reg(pi, RADIO_2064_REG11F, 0x4, 1 << 2); + wlc_lcnphy_tssi_setup(pi); + wlc_phy_do_dummy_tx(pi, true, OFF); + idleTssi = ((read_phy_reg(pi, 0x4ab) & (0x1ff << 0)) + >> 0); + + idleTssi0_2C = ((read_phy_reg(pi, 0x63e) & (0x1ff << 0)) + >> 0); + + if (idleTssi0_2C >= 256) + idleTssi0_OB = idleTssi0_2C - 256; + else + idleTssi0_OB = idleTssi0_2C + 256; + + idleTssi0_regvalue_OB = idleTssi0_OB; + if (idleTssi0_regvalue_OB >= 256) + idleTssi0_regvalue_2C = idleTssi0_regvalue_OB - 256; + else + idleTssi0_regvalue_2C = idleTssi0_regvalue_OB + 256; + mod_phy_reg(pi, 0x4a6, (0x1ff << 0), (idleTssi0_regvalue_2C) << 0); + + mod_phy_reg(pi, 0x44c, (0x1 << 12), (0) << 12); + + wlc_lcnphy_set_tx_gain_override(pi, tx_gain_override_old); + wlc_lcnphy_set_tx_gain(pi, &old_gains); + wlc_lcnphy_set_tx_pwr_ctrl(pi, SAVE_txpwrctrl); + + write_radio_reg(pi, RADIO_2064_REG112, SAVE_lpfgain); + mod_radio_reg(pi, RADIO_2064_REG007, 0x1, SAVE_jtag_bb_afe_switch); + mod_radio_reg(pi, RADIO_2064_REG0FF, 0x10, SAVE_jtag_auxpga); + mod_radio_reg(pi, RADIO_2064_REG11F, 0x4, SAVE_iqadc_aux_en); + mod_radio_reg(pi, RADIO_2064_REG112, 0x80, 1 << 7); + if (!suspend) + wlapi_enable_mac(pi->sh->physhim); +} + +static void wlc_lcnphy_vbat_temp_sense_setup(struct brcms_phy *pi, u8 mode) +{ + bool suspend; + u16 save_txpwrCtrlEn; + u8 auxpga_vmidcourse, auxpga_vmidfine, auxpga_gain; + u16 auxpga_vmid; + struct phytbl_info tab; + u32 val; + u8 save_reg007, save_reg0FF, save_reg11F, save_reg005, save_reg025, + save_reg112; + u16 values_to_save[14]; + s8 index; + int i; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + udelay(999); + + save_reg007 = (u8) read_radio_reg(pi, RADIO_2064_REG007); + save_reg0FF = (u8) read_radio_reg(pi, RADIO_2064_REG0FF); + save_reg11F = (u8) read_radio_reg(pi, RADIO_2064_REG11F); + save_reg005 = (u8) read_radio_reg(pi, RADIO_2064_REG005); + save_reg025 = (u8) read_radio_reg(pi, RADIO_2064_REG025); + save_reg112 = (u8) read_radio_reg(pi, RADIO_2064_REG112); + + for (i = 0; i < 14; i++) + values_to_save[i] = read_phy_reg(pi, tempsense_phy_regs[i]); + suspend = (0 == (bcma_read32(pi->d11core, D11REGOFFS(maccontrol)) & + MCTL_EN_MAC)); + if (!suspend) + wlapi_suspend_mac_and_wait(pi->sh->physhim); + save_txpwrCtrlEn = read_radio_reg(pi, 0x4a4); + + wlc_lcnphy_set_tx_pwr_ctrl(pi, LCNPHY_TX_PWR_CTRL_OFF); + index = pi_lcn->lcnphy_current_index; + wlc_lcnphy_set_tx_pwr_by_index(pi, 127); + mod_radio_reg(pi, RADIO_2064_REG007, 0x1, 0x1); + mod_radio_reg(pi, RADIO_2064_REG0FF, 0x10, 0x1 << 4); + mod_radio_reg(pi, RADIO_2064_REG11F, 0x4, 0x1 << 2); + mod_phy_reg(pi, 0x503, (0x1 << 0), (0) << 0); + + mod_phy_reg(pi, 0x503, (0x1 << 2), (0) << 2); + + mod_phy_reg(pi, 0x4a4, (0x1 << 14), (0) << 14); + + mod_phy_reg(pi, 0x4a4, (0x1 << 15), (0) << 15); + + mod_phy_reg(pi, 0x4d0, (0x1 << 5), (0) << 5); + + mod_phy_reg(pi, 0x4a5, (0xff << 0), (255) << 0); + + mod_phy_reg(pi, 0x4a5, (0x7 << 12), (5) << 12); + + mod_phy_reg(pi, 0x4a5, (0x7 << 8), (0) << 8); + + mod_phy_reg(pi, 0x40d, (0xff << 0), (64) << 0); + + mod_phy_reg(pi, 0x40d, (0x7 << 8), (6) << 8); + + mod_phy_reg(pi, 0x4a2, (0xff << 0), (64) << 0); + + mod_phy_reg(pi, 0x4a2, (0x7 << 8), (6) << 8); + + mod_phy_reg(pi, 0x4d9, (0x7 << 4), (2) << 4); + + mod_phy_reg(pi, 0x4d9, (0x7 << 8), (3) << 8); + + mod_phy_reg(pi, 0x4d9, (0x7 << 12), (1) << 12); + + mod_phy_reg(pi, 0x4da, (0x1 << 12), (0) << 12); + + mod_phy_reg(pi, 0x4da, (0x1 << 13), (1) << 13); + + mod_phy_reg(pi, 0x4a6, (0x1 << 15), (1) << 15); + + write_radio_reg(pi, RADIO_2064_REG025, 0xC); + + mod_radio_reg(pi, RADIO_2064_REG005, 0x8, 0x1 << 3); + + mod_phy_reg(pi, 0x938, (0x1 << 2), (1) << 2); + + mod_phy_reg(pi, 0x939, (0x1 << 2), (1) << 2); + + mod_phy_reg(pi, 0x4a4, (0x1 << 12), (1) << 12); + + val = wlc_lcnphy_rfseq_tbl_adc_pwrup(pi); + tab.tbl_id = LCNPHY_TBL_ID_RFSEQ; + tab.tbl_width = 16; + tab.tbl_len = 1; + tab.tbl_ptr = &val; + tab.tbl_offset = 6; + wlc_lcnphy_write_table(pi, &tab); + if (mode == TEMPSENSE) { + mod_phy_reg(pi, 0x4d7, (0x1 << 3), (1) << 3); + + mod_phy_reg(pi, 0x4d7, (0x7 << 12), (1) << 12); + + auxpga_vmidcourse = 8; + auxpga_vmidfine = 0x4; + auxpga_gain = 2; + mod_radio_reg(pi, RADIO_2064_REG082, 0x20, 1 << 5); + } else { + mod_phy_reg(pi, 0x4d7, (0x1 << 3), (1) << 3); + + mod_phy_reg(pi, 0x4d7, (0x7 << 12), (3) << 12); + + auxpga_vmidcourse = 7; + auxpga_vmidfine = 0xa; + auxpga_gain = 2; + } + auxpga_vmid = + (u16) ((2 << 8) | (auxpga_vmidcourse << 4) | auxpga_vmidfine); + mod_phy_reg(pi, 0x4d8, (0x1 << 0), (1) << 0); + + mod_phy_reg(pi, 0x4d8, (0x3ff << 2), (auxpga_vmid) << 2); + + mod_phy_reg(pi, 0x4d8, (0x1 << 1), (1) << 1); + + mod_phy_reg(pi, 0x4d8, (0x7 << 12), (auxpga_gain) << 12); + + mod_phy_reg(pi, 0x4d0, (0x1 << 5), (1) << 5); + + write_radio_reg(pi, RADIO_2064_REG112, 0x6); + + wlc_phy_do_dummy_tx(pi, true, OFF); + if (!tempsense_done(pi)) + udelay(10); + + write_radio_reg(pi, RADIO_2064_REG007, (u16) save_reg007); + write_radio_reg(pi, RADIO_2064_REG0FF, (u16) save_reg0FF); + write_radio_reg(pi, RADIO_2064_REG11F, (u16) save_reg11F); + write_radio_reg(pi, RADIO_2064_REG005, (u16) save_reg005); + write_radio_reg(pi, RADIO_2064_REG025, (u16) save_reg025); + write_radio_reg(pi, RADIO_2064_REG112, (u16) save_reg112); + for (i = 0; i < 14; i++) + write_phy_reg(pi, tempsense_phy_regs[i], values_to_save[i]); + wlc_lcnphy_set_tx_pwr_by_index(pi, (int)index); + + write_radio_reg(pi, 0x4a4, save_txpwrCtrlEn); + if (!suspend) + wlapi_enable_mac(pi->sh->physhim); + udelay(999); +} + +static void wlc_lcnphy_tx_pwr_ctrl_init(struct brcms_phy_pub *ppi) +{ + struct lcnphy_txgains tx_gains; + u8 bbmult; + struct phytbl_info tab; + s32 a1, b0, b1; + s32 tssi, pwr, maxtargetpwr, mintargetpwr; + bool suspend; + struct brcms_phy *pi = (struct brcms_phy *) ppi; + + suspend = (0 == (bcma_read32(pi->d11core, D11REGOFFS(maccontrol)) & + MCTL_EN_MAC)); + if (!suspend) + wlapi_suspend_mac_and_wait(pi->sh->physhim); + + if (!pi->hwpwrctrl_capable) { + if (CHSPEC_IS2G(pi->radio_chanspec)) { + tx_gains.gm_gain = 4; + tx_gains.pga_gain = 12; + tx_gains.pad_gain = 12; + tx_gains.dac_gain = 0; + + bbmult = 150; + } else { + tx_gains.gm_gain = 7; + tx_gains.pga_gain = 15; + tx_gains.pad_gain = 14; + tx_gains.dac_gain = 0; + + bbmult = 150; + } + wlc_lcnphy_set_tx_gain(pi, &tx_gains); + wlc_lcnphy_set_bbmult(pi, bbmult); + wlc_lcnphy_vbat_temp_sense_setup(pi, TEMPSENSE); + } else { + + wlc_lcnphy_idle_tssi_est(ppi); + + wlc_lcnphy_clear_tx_power_offsets(pi); + + b0 = pi->txpa_2g[0]; + b1 = pi->txpa_2g[1]; + a1 = pi->txpa_2g[2]; + maxtargetpwr = wlc_lcnphy_tssi2dbm(10, a1, b0, b1); + mintargetpwr = wlc_lcnphy_tssi2dbm(125, a1, b0, b1); + + tab.tbl_id = LCNPHY_TBL_ID_TXPWRCTL; + tab.tbl_width = 32; + tab.tbl_ptr = &pwr; + tab.tbl_len = 1; + tab.tbl_offset = 0; + for (tssi = 0; tssi < 128; tssi++) { + pwr = wlc_lcnphy_tssi2dbm(tssi, a1, b0, b1); + + pwr = (pwr < mintargetpwr) ? mintargetpwr : pwr; + wlc_lcnphy_write_table(pi, &tab); + tab.tbl_offset++; + } + + mod_phy_reg(pi, 0x410, (0x1 << 7), (0) << 7); + + write_phy_reg(pi, 0x4a8, 10); + + wlc_lcnphy_set_target_tx_pwr(pi, LCN_TARGET_PWR); + + wlc_lcnphy_set_tx_pwr_ctrl(pi, LCNPHY_TX_PWR_CTRL_HW); + } + if (!suspend) + wlapi_enable_mac(pi->sh->physhim); +} + +static void wlc_lcnphy_set_pa_gain(struct brcms_phy *pi, u16 gain) +{ + mod_phy_reg(pi, 0x4fb, + LCNPHY_txgainctrlovrval1_pagain_ovr_val1_MASK, + gain << LCNPHY_txgainctrlovrval1_pagain_ovr_val1_SHIFT); + mod_phy_reg(pi, 0x4fd, + LCNPHY_stxtxgainctrlovrval1_pagain_ovr_val1_MASK, + gain << LCNPHY_stxtxgainctrlovrval1_pagain_ovr_val1_SHIFT); +} + +void +wlc_lcnphy_get_radio_loft(struct brcms_phy *pi, + u8 *ei0, u8 *eq0, u8 *fi0, u8 *fq0) +{ + *ei0 = LCNPHY_IQLOCC_READ(read_radio_reg(pi, RADIO_2064_REG089)); + *eq0 = LCNPHY_IQLOCC_READ(read_radio_reg(pi, RADIO_2064_REG08A)); + *fi0 = LCNPHY_IQLOCC_READ(read_radio_reg(pi, RADIO_2064_REG08B)); + *fq0 = LCNPHY_IQLOCC_READ(read_radio_reg(pi, RADIO_2064_REG08C)); +} + +void wlc_lcnphy_set_tx_iqcc(struct brcms_phy *pi, u16 a, u16 b) +{ + struct phytbl_info tab; + u16 iqcc[2]; + + iqcc[0] = a; + iqcc[1] = b; + + tab.tbl_id = LCNPHY_TBL_ID_IQLOCAL; + tab.tbl_width = 16; + tab.tbl_ptr = iqcc; + tab.tbl_len = 2; + tab.tbl_offset = 80; + wlc_lcnphy_write_table(pi, &tab); +} + +void wlc_lcnphy_set_tx_locc(struct brcms_phy *pi, u16 didq) +{ + struct phytbl_info tab; + + tab.tbl_id = LCNPHY_TBL_ID_IQLOCAL; + tab.tbl_width = 16; + tab.tbl_ptr = &didq; + tab.tbl_len = 1; + tab.tbl_offset = 85; + wlc_lcnphy_write_table(pi, &tab); +} + +void wlc_lcnphy_set_tx_pwr_by_index(struct brcms_phy *pi, int index) +{ + struct phytbl_info tab; + u16 a, b; + u8 bb_mult; + u32 bbmultiqcomp, txgain, locoeffs, rfpower; + struct lcnphy_txgains gains; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + pi_lcn->lcnphy_tx_power_idx_override = (s8) index; + pi_lcn->lcnphy_current_index = (u8) index; + + tab.tbl_id = LCNPHY_TBL_ID_TXPWRCTL; + tab.tbl_width = 32; + tab.tbl_len = 1; + + wlc_lcnphy_set_tx_pwr_ctrl(pi, LCNPHY_TX_PWR_CTRL_OFF); + + tab.tbl_offset = LCNPHY_TX_PWR_CTRL_IQ_OFFSET + index; + tab.tbl_ptr = &bbmultiqcomp; + wlc_lcnphy_read_table(pi, &tab); + + tab.tbl_offset = LCNPHY_TX_PWR_CTRL_GAIN_OFFSET + index; + tab.tbl_width = 32; + tab.tbl_ptr = &txgain; + wlc_lcnphy_read_table(pi, &tab); + + gains.gm_gain = (u16) (txgain & 0xff); + gains.pga_gain = (u16) (txgain >> 8) & 0xff; + gains.pad_gain = (u16) (txgain >> 16) & 0xff; + gains.dac_gain = (u16) (bbmultiqcomp >> 28) & 0x07; + wlc_lcnphy_set_tx_gain(pi, &gains); + wlc_lcnphy_set_pa_gain(pi, (u16) (txgain >> 24) & 0x7f); + + bb_mult = (u8) ((bbmultiqcomp >> 20) & 0xff); + wlc_lcnphy_set_bbmult(pi, bb_mult); + + wlc_lcnphy_enable_tx_gain_override(pi); + + if (!wlc_lcnphy_tempsense_based_pwr_ctrl_enabled(pi)) { + + a = (u16) ((bbmultiqcomp >> 10) & 0x3ff); + b = (u16) (bbmultiqcomp & 0x3ff); + wlc_lcnphy_set_tx_iqcc(pi, a, b); + + tab.tbl_offset = LCNPHY_TX_PWR_CTRL_LO_OFFSET + index; + tab.tbl_ptr = &locoeffs; + wlc_lcnphy_read_table(pi, &tab); + + wlc_lcnphy_set_tx_locc(pi, (u16) locoeffs); + + tab.tbl_offset = LCNPHY_TX_PWR_CTRL_PWR_OFFSET + index; + tab.tbl_ptr = &rfpower; + wlc_lcnphy_read_table(pi, &tab); + mod_phy_reg(pi, 0x6a6, (0x1fff << 0), (rfpower * 8) << 0); + + } +} + +static void wlc_lcnphy_clear_papd_comptable(struct brcms_phy *pi) +{ + u32 j; + struct phytbl_info tab; + u32 temp_offset[128]; + tab.tbl_ptr = temp_offset; + tab.tbl_len = 128; + tab.tbl_id = LCNPHY_TBL_ID_PAPDCOMPDELTATBL; + tab.tbl_width = 32; + tab.tbl_offset = 0; + + memset(temp_offset, 0, sizeof(temp_offset)); + for (j = 1; j < 128; j += 2) + temp_offset[j] = 0x80000; + + wlc_lcnphy_write_table(pi, &tab); + return; +} + +void wlc_lcnphy_tx_pu(struct brcms_phy *pi, bool bEnable) +{ + if (!bEnable) { + + and_phy_reg(pi, 0x43b, ~(u16) ((0x1 << 1) | (0x1 << 4))); + + mod_phy_reg(pi, 0x43c, (0x1 << 1), 1 << 1); + + and_phy_reg(pi, 0x44c, + ~(u16) ((0x1 << 3) | + (0x1 << 5) | + (0x1 << 12) | + (0x1 << 0) | (0x1 << 1) | (0x1 << 2))); + + and_phy_reg(pi, 0x44d, + ~(u16) ((0x1 << 3) | (0x1 << 5) | (0x1 << 14))); + mod_phy_reg(pi, 0x44d, (0x1 << 2), 1 << 2); + + mod_phy_reg(pi, 0x44d, (0x1 << 1) | (0x1 << 0), (0x1 << 0)); + + and_phy_reg(pi, 0x4f9, + ~(u16) ((0x1 << 0) | (0x1 << 1) | (0x1 << 2))); + + and_phy_reg(pi, 0x4fa, + ~(u16) ((0x1 << 0) | (0x1 << 1) | (0x1 << 2))); + } else { + + mod_phy_reg(pi, 0x43b, (0x1 << 1), 1 << 1); + mod_phy_reg(pi, 0x43c, (0x1 << 1), 0 << 1); + + mod_phy_reg(pi, 0x43b, (0x1 << 4), 1 << 4); + mod_phy_reg(pi, 0x43c, (0x1 << 6), 0 << 6); + + mod_phy_reg(pi, 0x44c, (0x1 << 12), 1 << 12); + mod_phy_reg(pi, 0x44d, (0x1 << 14), 1 << 14); + + wlc_lcnphy_set_trsw_override(pi, true, false); + + mod_phy_reg(pi, 0x44d, (0x1 << 2), 0 << 2); + mod_phy_reg(pi, 0x44c, (0x1 << 2), 1 << 2); + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + + mod_phy_reg(pi, 0x44c, (0x1 << 3), 1 << 3); + mod_phy_reg(pi, 0x44d, (0x1 << 3), 1 << 3); + + mod_phy_reg(pi, 0x44c, (0x1 << 5), 1 << 5); + mod_phy_reg(pi, 0x44d, (0x1 << 5), 0 << 5); + + mod_phy_reg(pi, 0x4f9, (0x1 << 1), 1 << 1); + mod_phy_reg(pi, 0x4fa, (0x1 << 1), 1 << 1); + + mod_phy_reg(pi, 0x4f9, (0x1 << 2), 1 << 2); + mod_phy_reg(pi, 0x4fa, (0x1 << 2), 1 << 2); + + mod_phy_reg(pi, 0x4f9, (0x1 << 0), 1 << 0); + mod_phy_reg(pi, 0x4fa, (0x1 << 0), 1 << 0); + } else { + + mod_phy_reg(pi, 0x44c, (0x1 << 3), 1 << 3); + mod_phy_reg(pi, 0x44d, (0x1 << 3), 0 << 3); + + mod_phy_reg(pi, 0x44c, (0x1 << 5), 1 << 5); + mod_phy_reg(pi, 0x44d, (0x1 << 5), 1 << 5); + + mod_phy_reg(pi, 0x4f9, (0x1 << 1), 1 << 1); + mod_phy_reg(pi, 0x4fa, (0x1 << 1), 0 << 1); + + mod_phy_reg(pi, 0x4f9, (0x1 << 2), 1 << 2); + mod_phy_reg(pi, 0x4fa, (0x1 << 2), 0 << 2); + + mod_phy_reg(pi, 0x4f9, (0x1 << 0), 1 << 0); + mod_phy_reg(pi, 0x4fa, (0x1 << 0), 0 << 0); + } + } +} + +static void +wlc_lcnphy_run_samples(struct brcms_phy *pi, + u16 num_samps, + u16 num_loops, u16 wait, bool iqcalmode) +{ + + or_phy_reg(pi, 0x6da, 0x8080); + + mod_phy_reg(pi, 0x642, (0x7f << 0), (num_samps - 1) << 0); + if (num_loops != 0xffff) + num_loops--; + mod_phy_reg(pi, 0x640, (0xffff << 0), num_loops << 0); + + mod_phy_reg(pi, 0x641, (0xffff << 0), wait << 0); + + if (iqcalmode) { + + and_phy_reg(pi, 0x453, (u16) ~(0x1 << 15)); + or_phy_reg(pi, 0x453, (0x1 << 15)); + } else { + write_phy_reg(pi, 0x63f, 1); + wlc_lcnphy_tx_pu(pi, 1); + } + + or_radio_reg(pi, RADIO_2064_REG112, 0x6); +} + +void wlc_lcnphy_deaf_mode(struct brcms_phy *pi, bool mode) +{ + + u8 phybw40; + phybw40 = CHSPEC_IS40(pi->radio_chanspec); + + if (LCNREV_LT(pi->pubpi.phy_rev, 2)) { + mod_phy_reg(pi, 0x4b0, (0x1 << 5), (mode) << 5); + mod_phy_reg(pi, 0x4b1, (0x1 << 9), 0 << 9); + } else { + mod_phy_reg(pi, 0x4b0, (0x1 << 5), (mode) << 5); + mod_phy_reg(pi, 0x4b1, (0x1 << 9), 0 << 9); + } + + if (phybw40 == 0) { + mod_phy_reg((pi), 0x410, + (0x1 << 6) | + (0x1 << 5), + ((CHSPEC_IS2G( + pi->radio_chanspec)) ? (!mode) : 0) << + 6 | (!mode) << 5); + mod_phy_reg(pi, 0x410, (0x1 << 7), (mode) << 7); + } +} + +void +wlc_lcnphy_start_tx_tone(struct brcms_phy *pi, s32 f_kHz, u16 max_val, + bool iqcalmode) +{ + u8 phy_bw; + u16 num_samps, t, k; + u32 bw; + s32 theta = 0, rot = 0; + struct cordic_iq tone_samp; + u32 data_buf[64]; + u16 i_samp, q_samp; + struct phytbl_info tab; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + pi->phy_tx_tone_freq = f_kHz; + + wlc_lcnphy_deaf_mode(pi, true); + + phy_bw = 40; + if (pi_lcn->lcnphy_spurmod) { + write_phy_reg(pi, 0x942, 0x2); + write_phy_reg(pi, 0x93b, 0x0); + write_phy_reg(pi, 0x93c, 0x0); + wlc_lcnphy_txrx_spur_avoidance_mode(pi, false); + } + + if (f_kHz) { + k = 1; + do { + bw = phy_bw * 1000 * k; + num_samps = bw / abs(f_kHz); + k++; + } while ((num_samps * (u32) (abs(f_kHz))) != bw); + } else + num_samps = 2; + + rot = ((f_kHz * 36) / phy_bw) / 100; + theta = 0; + + for (t = 0; t < num_samps; t++) { + + tone_samp = cordic_calc_iq(theta); + + theta += rot; + + i_samp = (u16) (FLOAT(tone_samp.i * max_val) & 0x3ff); + q_samp = (u16) (FLOAT(tone_samp.q * max_val) & 0x3ff); + data_buf[t] = (i_samp << 10) | q_samp; + } + + mod_phy_reg(pi, 0x6d6, (0x3 << 0), 0 << 0); + + mod_phy_reg(pi, 0x6da, (0x1 << 3), 1 << 3); + + tab.tbl_ptr = data_buf; + tab.tbl_len = num_samps; + tab.tbl_id = LCNPHY_TBL_ID_SAMPLEPLAY; + tab.tbl_offset = 0; + tab.tbl_width = 32; + wlc_lcnphy_write_table(pi, &tab); + + wlc_lcnphy_run_samples(pi, num_samps, 0xffff, 0, iqcalmode); +} + +void wlc_lcnphy_stop_tx_tone(struct brcms_phy *pi) +{ + s16 playback_status; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + pi->phy_tx_tone_freq = 0; + if (pi_lcn->lcnphy_spurmod) { + write_phy_reg(pi, 0x942, 0x7); + write_phy_reg(pi, 0x93b, 0x2017); + write_phy_reg(pi, 0x93c, 0x27c5); + wlc_lcnphy_txrx_spur_avoidance_mode(pi, true); + } + + playback_status = read_phy_reg(pi, 0x644); + if (playback_status & (0x1 << 0)) { + wlc_lcnphy_tx_pu(pi, 0); + mod_phy_reg(pi, 0x63f, (0x1 << 1), 1 << 1); + } else if (playback_status & (0x1 << 1)) + mod_phy_reg(pi, 0x453, (0x1 << 15), 0 << 15); + + mod_phy_reg(pi, 0x6d6, (0x3 << 0), 1 << 0); + + mod_phy_reg(pi, 0x6da, (0x1 << 3), 0 << 3); + + mod_phy_reg(pi, 0x6da, (0x1 << 7), 0 << 7); + + and_radio_reg(pi, RADIO_2064_REG112, 0xFFF9); + + wlc_lcnphy_deaf_mode(pi, false); +} + +static void +wlc_lcnphy_set_cc(struct brcms_phy *pi, int cal_type, s16 coeff_x, s16 coeff_y) +{ + u16 di0dq0; + u16 x, y, data_rf; + int k; + switch (cal_type) { + case 0: + wlc_lcnphy_set_tx_iqcc(pi, coeff_x, coeff_y); + break; + case 2: + di0dq0 = (coeff_x & 0xff) << 8 | (coeff_y & 0xff); + wlc_lcnphy_set_tx_locc(pi, di0dq0); + break; + case 3: + k = wlc_lcnphy_calc_floor(coeff_x, 0); + y = 8 + k; + k = wlc_lcnphy_calc_floor(coeff_x, 1); + x = 8 - k; + data_rf = (x * 16 + y); + write_radio_reg(pi, RADIO_2064_REG089, data_rf); + k = wlc_lcnphy_calc_floor(coeff_y, 0); + y = 8 + k; + k = wlc_lcnphy_calc_floor(coeff_y, 1); + x = 8 - k; + data_rf = (x * 16 + y); + write_radio_reg(pi, RADIO_2064_REG08A, data_rf); + break; + case 4: + k = wlc_lcnphy_calc_floor(coeff_x, 0); + y = 8 + k; + k = wlc_lcnphy_calc_floor(coeff_x, 1); + x = 8 - k; + data_rf = (x * 16 + y); + write_radio_reg(pi, RADIO_2064_REG08B, data_rf); + k = wlc_lcnphy_calc_floor(coeff_y, 0); + y = 8 + k; + k = wlc_lcnphy_calc_floor(coeff_y, 1); + x = 8 - k; + data_rf = (x * 16 + y); + write_radio_reg(pi, RADIO_2064_REG08C, data_rf); + break; + } +} + +static struct lcnphy_unsign16_struct +wlc_lcnphy_get_cc(struct brcms_phy *pi, int cal_type) +{ + u16 a, b, didq; + u8 di0, dq0, ei, eq, fi, fq; + struct lcnphy_unsign16_struct cc; + cc.re = 0; + cc.im = 0; + switch (cal_type) { + case 0: + wlc_lcnphy_get_tx_iqcc(pi, &a, &b); + cc.re = a; + cc.im = b; + break; + case 2: + didq = wlc_lcnphy_get_tx_locc(pi); + di0 = (((didq & 0xff00) << 16) >> 24); + dq0 = (((didq & 0x00ff) << 24) >> 24); + cc.re = (u16) di0; + cc.im = (u16) dq0; + break; + case 3: + wlc_lcnphy_get_radio_loft(pi, &ei, &eq, &fi, &fq); + cc.re = (u16) ei; + cc.im = (u16) eq; + break; + case 4: + wlc_lcnphy_get_radio_loft(pi, &ei, &eq, &fi, &fq); + cc.re = (u16) fi; + cc.im = (u16) fq; + break; + } + return cc; +} + +static void +wlc_lcnphy_samp_cap(struct brcms_phy *pi, int clip_detect_algo, u16 thresh, + s16 *ptr, int mode) +{ + u32 curval1, curval2, stpptr, curptr, strptr, val; + u16 sslpnCalibClkEnCtrl, timer; + u16 old_sslpnCalibClkEnCtrl; + s16 imag, real; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + timer = 0; + old_sslpnCalibClkEnCtrl = read_phy_reg(pi, 0x6da); + + curval1 = bcma_read16(pi->d11core, D11REGOFFS(psm_corectlsts)); + ptr[130] = 0; + bcma_write16(pi->d11core, D11REGOFFS(psm_corectlsts), + ((1 << 6) | curval1)); + + bcma_write16(pi->d11core, D11REGOFFS(smpl_clct_strptr), 0x7E00); + bcma_write16(pi->d11core, D11REGOFFS(smpl_clct_stpptr), 0x8000); + udelay(20); + curval2 = bcma_read16(pi->d11core, D11REGOFFS(psm_phy_hdr_param)); + bcma_write16(pi->d11core, D11REGOFFS(psm_phy_hdr_param), + curval2 | 0x30); + + write_phy_reg(pi, 0x555, 0x0); + write_phy_reg(pi, 0x5a6, 0x5); + + write_phy_reg(pi, 0x5a2, (u16) (mode | mode << 6)); + write_phy_reg(pi, 0x5cf, 3); + write_phy_reg(pi, 0x5a5, 0x3); + write_phy_reg(pi, 0x583, 0x0); + write_phy_reg(pi, 0x584, 0x0); + write_phy_reg(pi, 0x585, 0x0fff); + write_phy_reg(pi, 0x586, 0x0000); + + write_phy_reg(pi, 0x580, 0x4501); + + sslpnCalibClkEnCtrl = read_phy_reg(pi, 0x6da); + write_phy_reg(pi, 0x6da, (u32) (sslpnCalibClkEnCtrl | 0x2008)); + stpptr = bcma_read16(pi->d11core, D11REGOFFS(smpl_clct_stpptr)); + curptr = bcma_read16(pi->d11core, D11REGOFFS(smpl_clct_curptr)); + do { + udelay(10); + curptr = bcma_read16(pi->d11core, D11REGOFFS(smpl_clct_curptr)); + timer++; + } while ((curptr != stpptr) && (timer < 500)); + + bcma_write16(pi->d11core, D11REGOFFS(psm_phy_hdr_param), 0x2); + strptr = 0x7E00; + bcma_write32(pi->d11core, D11REGOFFS(tplatewrptr), strptr); + while (strptr < 0x8000) { + val = bcma_read32(pi->d11core, D11REGOFFS(tplatewrdata)); + imag = ((val >> 16) & 0x3ff); + real = ((val) & 0x3ff); + if (imag > 511) + imag -= 1024; + + if (real > 511) + real -= 1024; + + if (pi_lcn->lcnphy_iqcal_swp_dis) + ptr[(strptr - 0x7E00) / 4] = real; + else + ptr[(strptr - 0x7E00) / 4] = imag; + + if (clip_detect_algo) { + if (imag > thresh || imag < -thresh) { + strptr = 0x8000; + ptr[130] = 1; + } + } + + strptr += 4; + } + + write_phy_reg(pi, 0x6da, old_sslpnCalibClkEnCtrl); + bcma_write16(pi->d11core, D11REGOFFS(psm_phy_hdr_param), curval2); + bcma_write16(pi->d11core, D11REGOFFS(psm_corectlsts), curval1); +} + +static void +wlc_lcnphy_a1(struct brcms_phy *pi, int cal_type, int num_levels, + int step_size_lg2) +{ + const struct lcnphy_spb_tone *phy_c1; + struct lcnphy_spb_tone phy_c2; + struct lcnphy_unsign16_struct phy_c3; + int phy_c4, phy_c5, k, l, j, phy_c6; + u16 phy_c7, phy_c8, phy_c9; + s16 phy_c10, phy_c11, phy_c12, phy_c13, phy_c14, phy_c15, phy_c16; + s16 *ptr, phy_c17; + s32 phy_c18, phy_c19; + u32 phy_c20, phy_c21; + bool phy_c22, phy_c23, phy_c24, phy_c25; + u16 phy_c26, phy_c27; + u16 phy_c28, phy_c29, phy_c30; + u16 phy_c31; + u16 *phy_c32; + phy_c21 = 0; + phy_c10 = phy_c13 = phy_c14 = phy_c8 = 0; + ptr = kmalloc(sizeof(s16) * 131, GFP_ATOMIC); + if (NULL == ptr) + return; + + phy_c32 = kmalloc(sizeof(u16) * 20, GFP_ATOMIC); + if (NULL == phy_c32) { + kfree(ptr); + return; + } + phy_c26 = read_phy_reg(pi, 0x6da); + phy_c27 = read_phy_reg(pi, 0x6db); + phy_c31 = read_radio_reg(pi, RADIO_2064_REG026); + write_phy_reg(pi, 0x93d, 0xC0); + + wlc_lcnphy_start_tx_tone(pi, 3750, 88, 0); + write_phy_reg(pi, 0x6da, 0xffff); + or_phy_reg(pi, 0x6db, 0x3); + + wlc_lcnphy_tx_iqlo_loopback(pi, phy_c32); + udelay(500); + phy_c28 = read_phy_reg(pi, 0x938); + phy_c29 = read_phy_reg(pi, 0x4d7); + phy_c30 = read_phy_reg(pi, 0x4d8); + or_phy_reg(pi, 0x938, 0x1 << 2); + or_phy_reg(pi, 0x4d7, 0x1 << 2); + or_phy_reg(pi, 0x4d7, 0x1 << 3); + mod_phy_reg(pi, 0x4d7, (0x7 << 12), 0x2 << 12); + or_phy_reg(pi, 0x4d8, 1 << 0); + or_phy_reg(pi, 0x4d8, 1 << 1); + mod_phy_reg(pi, 0x4d8, (0x3ff << 2), 0x23A << 2); + mod_phy_reg(pi, 0x4d8, (0x7 << 12), 0x7 << 12); + phy_c1 = &lcnphy_spb_tone_3750[0]; + phy_c4 = 32; + + if (num_levels == 0) { + if (cal_type != 0) + num_levels = 4; + else + num_levels = 9; + } + if (step_size_lg2 == 0) { + if (cal_type != 0) + step_size_lg2 = 3; + else + step_size_lg2 = 8; + } + + phy_c7 = (1 << step_size_lg2); + phy_c3 = wlc_lcnphy_get_cc(pi, cal_type); + phy_c15 = (s16) phy_c3.re; + phy_c16 = (s16) phy_c3.im; + if (cal_type == 2) { + if (phy_c3.re > 127) + phy_c15 = phy_c3.re - 256; + if (phy_c3.im > 127) + phy_c16 = phy_c3.im - 256; + } + wlc_lcnphy_set_cc(pi, cal_type, phy_c15, phy_c16); + udelay(20); + for (phy_c8 = 0; phy_c7 != 0 && phy_c8 < num_levels; phy_c8++) { + phy_c23 = true; + phy_c22 = false; + switch (cal_type) { + case 0: + phy_c10 = 511; + break; + case 2: + phy_c10 = 127; + break; + case 3: + phy_c10 = 15; + break; + case 4: + phy_c10 = 15; + break; + } + + phy_c9 = read_phy_reg(pi, 0x93d); + phy_c9 = 2 * phy_c9; + phy_c24 = false; + phy_c5 = 7; + phy_c25 = true; + while (1) { + write_radio_reg(pi, RADIO_2064_REG026, + (phy_c5 & 0x7) | ((phy_c5 & 0x7) << 4)); + udelay(50); + phy_c22 = false; + ptr[130] = 0; + wlc_lcnphy_samp_cap(pi, 1, phy_c9, &ptr[0], 2); + if (ptr[130] == 1) + phy_c22 = true; + if (phy_c22) + phy_c5 -= 1; + if ((phy_c22 != phy_c24) && (!phy_c25)) + break; + if (!phy_c22) + phy_c5 += 1; + if (phy_c5 <= 0 || phy_c5 >= 7) + break; + phy_c24 = phy_c22; + phy_c25 = false; + } + + if (phy_c5 < 0) + phy_c5 = 0; + else if (phy_c5 > 7) + phy_c5 = 7; + + for (k = -phy_c7; k <= phy_c7; k += phy_c7) { + for (l = -phy_c7; l <= phy_c7; l += phy_c7) { + phy_c11 = phy_c15 + k; + phy_c12 = phy_c16 + l; + + if (phy_c11 < -phy_c10) + phy_c11 = -phy_c10; + else if (phy_c11 > phy_c10) + phy_c11 = phy_c10; + if (phy_c12 < -phy_c10) + phy_c12 = -phy_c10; + else if (phy_c12 > phy_c10) + phy_c12 = phy_c10; + wlc_lcnphy_set_cc(pi, cal_type, phy_c11, + phy_c12); + udelay(20); + wlc_lcnphy_samp_cap(pi, 0, 0, ptr, 2); + + phy_c18 = 0; + phy_c19 = 0; + for (j = 0; j < 128; j++) { + if (cal_type != 0) + phy_c6 = j % phy_c4; + else + phy_c6 = (2 * j) % phy_c4; + + phy_c2.re = phy_c1[phy_c6].re; + phy_c2.im = phy_c1[phy_c6].im; + phy_c17 = ptr[j]; + phy_c18 = phy_c18 + phy_c17 * phy_c2.re; + phy_c19 = phy_c19 + phy_c17 * phy_c2.im; + } + + phy_c18 = phy_c18 >> 10; + phy_c19 = phy_c19 >> 10; + phy_c20 = ((phy_c18 * phy_c18) + + (phy_c19 * phy_c19)); + + if (phy_c23 || phy_c20 < phy_c21) { + phy_c21 = phy_c20; + phy_c13 = phy_c11; + phy_c14 = phy_c12; + } + phy_c23 = false; + } + } + phy_c23 = true; + phy_c15 = phy_c13; + phy_c16 = phy_c14; + phy_c7 = phy_c7 >> 1; + wlc_lcnphy_set_cc(pi, cal_type, phy_c15, phy_c16); + udelay(20); + } + goto cleanup; +cleanup: + wlc_lcnphy_tx_iqlo_loopback_cleanup(pi, phy_c32); + wlc_lcnphy_stop_tx_tone(pi); + write_phy_reg(pi, 0x6da, phy_c26); + write_phy_reg(pi, 0x6db, phy_c27); + write_phy_reg(pi, 0x938, phy_c28); + write_phy_reg(pi, 0x4d7, phy_c29); + write_phy_reg(pi, 0x4d8, phy_c30); + write_radio_reg(pi, RADIO_2064_REG026, phy_c31); + + kfree(phy_c32); + kfree(ptr); +} + +void wlc_lcnphy_get_tx_iqcc(struct brcms_phy *pi, u16 *a, u16 *b) +{ + u16 iqcc[2]; + struct phytbl_info tab; + + tab.tbl_ptr = iqcc; + tab.tbl_len = 2; + tab.tbl_id = 0; + tab.tbl_offset = 80; + tab.tbl_width = 16; + wlc_lcnphy_read_table(pi, &tab); + + *a = iqcc[0]; + *b = iqcc[1]; +} + +static void wlc_lcnphy_tx_iqlo_soft_cal_full(struct brcms_phy *pi) +{ + struct lcnphy_unsign16_struct iqcc0, locc2, locc3, locc4; + + wlc_lcnphy_set_cc(pi, 0, 0, 0); + wlc_lcnphy_set_cc(pi, 2, 0, 0); + wlc_lcnphy_set_cc(pi, 3, 0, 0); + wlc_lcnphy_set_cc(pi, 4, 0, 0); + + wlc_lcnphy_a1(pi, 4, 0, 0); + wlc_lcnphy_a1(pi, 3, 0, 0); + wlc_lcnphy_a1(pi, 2, 3, 2); + wlc_lcnphy_a1(pi, 0, 5, 8); + wlc_lcnphy_a1(pi, 2, 2, 1); + wlc_lcnphy_a1(pi, 0, 4, 3); + + iqcc0 = wlc_lcnphy_get_cc(pi, 0); + locc2 = wlc_lcnphy_get_cc(pi, 2); + locc3 = wlc_lcnphy_get_cc(pi, 3); + locc4 = wlc_lcnphy_get_cc(pi, 4); +} + +u16 wlc_lcnphy_get_tx_locc(struct brcms_phy *pi) +{ + struct phytbl_info tab; + u16 didq; + + tab.tbl_id = 0; + tab.tbl_width = 16; + tab.tbl_ptr = &didq; + tab.tbl_len = 1; + tab.tbl_offset = 85; + wlc_lcnphy_read_table(pi, &tab); + + return didq; +} + +static void wlc_lcnphy_txpwrtbl_iqlo_cal(struct brcms_phy *pi) +{ + + struct lcnphy_txgains target_gains, old_gains; + u8 save_bb_mult; + u16 a, b, didq, save_pa_gain = 0; + uint idx, SAVE_txpwrindex = 0xFF; + u32 val; + u16 SAVE_txpwrctrl = wlc_lcnphy_get_tx_pwr_ctrl(pi); + struct phytbl_info tab; + u8 ei0, eq0, fi0, fq0; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + wlc_lcnphy_get_tx_gain(pi, &old_gains); + save_pa_gain = wlc_lcnphy_get_pa_gain(pi); + + save_bb_mult = wlc_lcnphy_get_bbmult(pi); + + if (SAVE_txpwrctrl == LCNPHY_TX_PWR_CTRL_OFF) + SAVE_txpwrindex = wlc_lcnphy_get_current_tx_pwr_idx(pi); + + wlc_lcnphy_set_tx_pwr_ctrl(pi, LCNPHY_TX_PWR_CTRL_OFF); + + target_gains.gm_gain = 7; + target_gains.pga_gain = 0; + target_gains.pad_gain = 21; + target_gains.dac_gain = 0; + wlc_lcnphy_set_tx_gain(pi, &target_gains); + wlc_lcnphy_set_tx_pwr_by_index(pi, 16); + + if (LCNREV_IS(pi->pubpi.phy_rev, 1) || pi_lcn->lcnphy_hw_iqcal_en) { + + wlc_lcnphy_set_tx_pwr_by_index(pi, 30); + + wlc_lcnphy_tx_iqlo_cal(pi, &target_gains, + (pi_lcn-> + lcnphy_recal ? LCNPHY_CAL_RECAL : + LCNPHY_CAL_FULL), false); + } else { + wlc_lcnphy_tx_iqlo_soft_cal_full(pi); + } + + wlc_lcnphy_get_radio_loft(pi, &ei0, &eq0, &fi0, &fq0); + if ((abs((s8) fi0) == 15) && (abs((s8) fq0) == 15)) { + if (CHSPEC_IS5G(pi->radio_chanspec)) { + target_gains.gm_gain = 255; + target_gains.pga_gain = 255; + target_gains.pad_gain = 0xf0; + target_gains.dac_gain = 0; + } else { + target_gains.gm_gain = 7; + target_gains.pga_gain = 45; + target_gains.pad_gain = 186; + target_gains.dac_gain = 0; + } + + if (LCNREV_IS(pi->pubpi.phy_rev, 1) + || pi_lcn->lcnphy_hw_iqcal_en) { + + target_gains.pga_gain = 0; + target_gains.pad_gain = 30; + wlc_lcnphy_set_tx_pwr_by_index(pi, 16); + wlc_lcnphy_tx_iqlo_cal(pi, &target_gains, + LCNPHY_CAL_FULL, false); + } else { + wlc_lcnphy_tx_iqlo_soft_cal_full(pi); + } + } + + wlc_lcnphy_get_tx_iqcc(pi, &a, &b); + + didq = wlc_lcnphy_get_tx_locc(pi); + + tab.tbl_id = LCNPHY_TBL_ID_TXPWRCTL; + tab.tbl_width = 32; + tab.tbl_ptr = &val; + + tab.tbl_len = 1; + tab.tbl_offset = LCNPHY_TX_PWR_CTRL_RATE_OFFSET; + + for (idx = 0; idx < 128; idx++) { + tab.tbl_offset = LCNPHY_TX_PWR_CTRL_IQ_OFFSET + idx; + + wlc_lcnphy_read_table(pi, &tab); + val = (val & 0xfff00000) | + ((u32) (a & 0x3FF) << 10) | (b & 0x3ff); + wlc_lcnphy_write_table(pi, &tab); + + val = didq; + tab.tbl_offset = LCNPHY_TX_PWR_CTRL_LO_OFFSET + idx; + wlc_lcnphy_write_table(pi, &tab); + } + + pi_lcn->lcnphy_cal_results.txiqlocal_a = a; + pi_lcn->lcnphy_cal_results.txiqlocal_b = b; + pi_lcn->lcnphy_cal_results.txiqlocal_didq = didq; + pi_lcn->lcnphy_cal_results.txiqlocal_ei0 = ei0; + pi_lcn->lcnphy_cal_results.txiqlocal_eq0 = eq0; + pi_lcn->lcnphy_cal_results.txiqlocal_fi0 = fi0; + pi_lcn->lcnphy_cal_results.txiqlocal_fq0 = fq0; + + wlc_lcnphy_set_bbmult(pi, save_bb_mult); + wlc_lcnphy_set_pa_gain(pi, save_pa_gain); + wlc_lcnphy_set_tx_gain(pi, &old_gains); + + if (SAVE_txpwrctrl != LCNPHY_TX_PWR_CTRL_OFF) + wlc_lcnphy_set_tx_pwr_ctrl(pi, SAVE_txpwrctrl); + else + wlc_lcnphy_set_tx_pwr_by_index(pi, SAVE_txpwrindex); +} + +s16 wlc_lcnphy_tempsense_new(struct brcms_phy *pi, bool mode) +{ + u16 tempsenseval1, tempsenseval2; + s16 avg = 0; + bool suspend = false; + + if (mode == 1) { + suspend = (0 == (bcma_read32(pi->d11core, + D11REGOFFS(maccontrol)) & + MCTL_EN_MAC)); + if (!suspend) + wlapi_suspend_mac_and_wait(pi->sh->physhim); + wlc_lcnphy_vbat_temp_sense_setup(pi, TEMPSENSE); + } + tempsenseval1 = read_phy_reg(pi, 0x476) & 0x1FF; + tempsenseval2 = read_phy_reg(pi, 0x477) & 0x1FF; + + if (tempsenseval1 > 255) + avg = (s16) (tempsenseval1 - 512); + else + avg = (s16) tempsenseval1; + + if (tempsenseval2 > 255) + avg += (s16) (tempsenseval2 - 512); + else + avg += (s16) tempsenseval2; + + avg /= 2; + + if (mode == 1) { + + mod_phy_reg(pi, 0x448, (0x1 << 14), (1) << 14); + + udelay(100); + mod_phy_reg(pi, 0x448, (0x1 << 14), (0) << 14); + + if (!suspend) + wlapi_enable_mac(pi->sh->physhim); + } + return avg; +} + +u16 wlc_lcnphy_tempsense(struct brcms_phy *pi, bool mode) +{ + u16 tempsenseval1, tempsenseval2; + s32 avg = 0; + bool suspend = false; + u16 SAVE_txpwrctrl = wlc_lcnphy_get_tx_pwr_ctrl(pi); + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + if (mode == 1) { + suspend = (0 == (bcma_read32(pi->d11core, + D11REGOFFS(maccontrol)) & + MCTL_EN_MAC)); + if (!suspend) + wlapi_suspend_mac_and_wait(pi->sh->physhim); + wlc_lcnphy_vbat_temp_sense_setup(pi, TEMPSENSE); + } + tempsenseval1 = read_phy_reg(pi, 0x476) & 0x1FF; + tempsenseval2 = read_phy_reg(pi, 0x477) & 0x1FF; + + if (tempsenseval1 > 255) + avg = (int)(tempsenseval1 - 512); + else + avg = (int)tempsenseval1; + + if (pi_lcn->lcnphy_tempsense_option == 1 || pi->hwpwrctrl_capable) { + if (tempsenseval2 > 255) + avg = (int)(avg - tempsenseval2 + 512); + else + avg = (int)(avg - tempsenseval2); + } else { + if (tempsenseval2 > 255) + avg = (int)(avg + tempsenseval2 - 512); + else + avg = (int)(avg + tempsenseval2); + avg = avg / 2; + } + if (avg < 0) + avg = avg + 512; + + if (pi_lcn->lcnphy_tempsense_option == 2) + avg = tempsenseval1; + + if (mode) + wlc_lcnphy_set_tx_pwr_ctrl(pi, SAVE_txpwrctrl); + + if (mode == 1) { + + mod_phy_reg(pi, 0x448, (0x1 << 14), (1) << 14); + + udelay(100); + mod_phy_reg(pi, 0x448, (0x1 << 14), (0) << 14); + + if (!suspend) + wlapi_enable_mac(pi->sh->physhim); + } + return (u16) avg; +} + +s8 wlc_lcnphy_tempsense_degree(struct brcms_phy *pi, bool mode) +{ + s32 degree = wlc_lcnphy_tempsense_new(pi, mode); + degree = + ((degree << + 10) + LCN_TEMPSENSE_OFFSET + (LCN_TEMPSENSE_DEN >> 1)) + / LCN_TEMPSENSE_DEN; + return (s8) degree; +} + +s8 wlc_lcnphy_vbatsense(struct brcms_phy *pi, bool mode) +{ + u16 vbatsenseval; + s32 avg = 0; + bool suspend = false; + + if (mode == 1) { + suspend = (0 == (bcma_read32(pi->d11core, + D11REGOFFS(maccontrol)) & + MCTL_EN_MAC)); + if (!suspend) + wlapi_suspend_mac_and_wait(pi->sh->physhim); + wlc_lcnphy_vbat_temp_sense_setup(pi, VBATSENSE); + } + + vbatsenseval = read_phy_reg(pi, 0x475) & 0x1FF; + + if (vbatsenseval > 255) + avg = (s32) (vbatsenseval - 512); + else + avg = (s32) vbatsenseval; + + avg = (avg * LCN_VBAT_SCALE_NOM + + (LCN_VBAT_SCALE_DEN >> 1)) / LCN_VBAT_SCALE_DEN; + + if (mode == 1) { + if (!suspend) + wlapi_enable_mac(pi->sh->physhim); + } + return (s8) avg; +} + +static void wlc_lcnphy_afe_clk_init(struct brcms_phy *pi, u8 mode) +{ + u8 phybw40; + phybw40 = CHSPEC_IS40(pi->radio_chanspec); + + mod_phy_reg(pi, 0x6d1, (0x1 << 7), (1) << 7); + + if (((mode == AFE_CLK_INIT_MODE_PAPD) && (phybw40 == 0)) || + (mode == AFE_CLK_INIT_MODE_TXRX2X)) + write_phy_reg(pi, 0x6d0, 0x7); + + wlc_lcnphy_toggle_afe_pwdn(pi); +} + +static void wlc_lcnphy_temp_adj(struct brcms_phy *pi) +{ +} + +static void wlc_lcnphy_glacial_timer_based_cal(struct brcms_phy *pi) +{ + bool suspend; + s8 index; + u16 SAVE_pwrctrl = wlc_lcnphy_get_tx_pwr_ctrl(pi); + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + suspend = (0 == (bcma_read32(pi->d11core, D11REGOFFS(maccontrol)) & + MCTL_EN_MAC)); + if (!suspend) + wlapi_suspend_mac_and_wait(pi->sh->physhim); + wlc_lcnphy_deaf_mode(pi, true); + pi->phy_lastcal = pi->sh->now; + pi->phy_forcecal = false; + index = pi_lcn->lcnphy_current_index; + + wlc_lcnphy_txpwrtbl_iqlo_cal(pi); + + wlc_lcnphy_set_tx_pwr_by_index(pi, index); + wlc_lcnphy_set_tx_pwr_ctrl(pi, SAVE_pwrctrl); + wlc_lcnphy_deaf_mode(pi, false); + if (!suspend) + wlapi_enable_mac(pi->sh->physhim); + +} + +static void wlc_lcnphy_periodic_cal(struct brcms_phy *pi) +{ + bool suspend, full_cal; + const struct lcnphy_rx_iqcomp *rx_iqcomp; + int rx_iqcomp_sz; + u16 SAVE_pwrctrl = wlc_lcnphy_get_tx_pwr_ctrl(pi); + s8 index; + struct phytbl_info tab; + s32 a1, b0, b1; + s32 tssi, pwr, maxtargetpwr, mintargetpwr; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + pi->phy_lastcal = pi->sh->now; + pi->phy_forcecal = false; + full_cal = + (pi_lcn->lcnphy_full_cal_channel != + CHSPEC_CHANNEL(pi->radio_chanspec)); + pi_lcn->lcnphy_full_cal_channel = CHSPEC_CHANNEL(pi->radio_chanspec); + index = pi_lcn->lcnphy_current_index; + + suspend = (0 == (bcma_read32(pi->d11core, D11REGOFFS(maccontrol)) & + MCTL_EN_MAC)); + if (!suspend) { + wlapi_bmac_write_shm(pi->sh->physhim, M_CTS_DURATION, 10000); + wlapi_suspend_mac_and_wait(pi->sh->physhim); + } + + wlc_lcnphy_deaf_mode(pi, true); + + wlc_lcnphy_txpwrtbl_iqlo_cal(pi); + + rx_iqcomp = lcnphy_rx_iqcomp_table_rev0; + rx_iqcomp_sz = ARRAY_SIZE(lcnphy_rx_iqcomp_table_rev0); + + if (LCNREV_IS(pi->pubpi.phy_rev, 1)) + wlc_lcnphy_rx_iq_cal(pi, NULL, 0, true, false, 1, 40); + else + wlc_lcnphy_rx_iq_cal(pi, NULL, 0, true, false, 1, 127); + + if (wlc_lcnphy_tssi_based_pwr_ctrl_enabled(pi)) { + + wlc_lcnphy_idle_tssi_est((struct brcms_phy_pub *) pi); + + b0 = pi->txpa_2g[0]; + b1 = pi->txpa_2g[1]; + a1 = pi->txpa_2g[2]; + maxtargetpwr = wlc_lcnphy_tssi2dbm(10, a1, b0, b1); + mintargetpwr = wlc_lcnphy_tssi2dbm(125, a1, b0, b1); + + tab.tbl_id = LCNPHY_TBL_ID_TXPWRCTL; + tab.tbl_width = 32; + tab.tbl_ptr = &pwr; + tab.tbl_len = 1; + tab.tbl_offset = 0; + for (tssi = 0; tssi < 128; tssi++) { + pwr = wlc_lcnphy_tssi2dbm(tssi, a1, b0, b1); + pwr = (pwr < mintargetpwr) ? mintargetpwr : pwr; + wlc_lcnphy_write_table(pi, &tab); + tab.tbl_offset++; + } + } + + wlc_lcnphy_set_tx_pwr_by_index(pi, index); + wlc_lcnphy_set_tx_pwr_ctrl(pi, SAVE_pwrctrl); + wlc_lcnphy_deaf_mode(pi, false); + if (!suspend) + wlapi_enable_mac(pi->sh->physhim); +} + +void wlc_lcnphy_calib_modes(struct brcms_phy *pi, uint mode) +{ + u16 temp_new; + int temp1, temp2, temp_diff; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + switch (mode) { + case PHY_PERICAL_CHAN: + break; + case PHY_FULLCAL: + wlc_lcnphy_periodic_cal(pi); + break; + case PHY_PERICAL_PHYINIT: + wlc_lcnphy_periodic_cal(pi); + break; + case PHY_PERICAL_WATCHDOG: + if (wlc_lcnphy_tempsense_based_pwr_ctrl_enabled(pi)) { + temp_new = wlc_lcnphy_tempsense(pi, 0); + temp1 = LCNPHY_TEMPSENSE(temp_new); + temp2 = LCNPHY_TEMPSENSE(pi_lcn->lcnphy_cal_temper); + temp_diff = temp1 - temp2; + if ((pi_lcn->lcnphy_cal_counter > 90) || + (temp_diff > 60) || (temp_diff < -60)) { + wlc_lcnphy_glacial_timer_based_cal(pi); + wlc_2064_vco_cal(pi); + pi_lcn->lcnphy_cal_temper = temp_new; + pi_lcn->lcnphy_cal_counter = 0; + } else + pi_lcn->lcnphy_cal_counter++; + } + break; + case LCNPHY_PERICAL_TEMPBASED_TXPWRCTRL: + if (wlc_lcnphy_tempsense_based_pwr_ctrl_enabled(pi)) + wlc_lcnphy_tx_power_adjustment( + (struct brcms_phy_pub *) pi); + break; + } +} + +void wlc_lcnphy_get_tssi(struct brcms_phy *pi, s8 *ofdm_pwr, s8 *cck_pwr) +{ + s8 cck_offset; + u16 status; + status = (read_phy_reg(pi, 0x4ab)); + if (wlc_lcnphy_tssi_based_pwr_ctrl_enabled(pi) && + (status & (0x1 << 15))) { + *ofdm_pwr = (s8) (((read_phy_reg(pi, 0x4ab) & (0x1ff << 0)) + >> 0) >> 1); + + if (wlc_phy_tpc_isenabled_lcnphy(pi)) + cck_offset = pi->tx_power_offset[TXP_FIRST_CCK]; + else + cck_offset = 0; + + *cck_pwr = *ofdm_pwr + cck_offset; + } else { + *cck_pwr = 0; + *ofdm_pwr = 0; + } +} + +void wlc_phy_cal_init_lcnphy(struct brcms_phy *pi) +{ + return; + +} + +void wlc_lcnphy_tx_power_adjustment(struct brcms_phy_pub *ppi) +{ + s8 index; + u16 index2; + struct brcms_phy *pi = (struct brcms_phy *) ppi; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + u16 SAVE_txpwrctrl = wlc_lcnphy_get_tx_pwr_ctrl(pi); + if (wlc_lcnphy_tempsense_based_pwr_ctrl_enabled(pi) && + SAVE_txpwrctrl) { + index = wlc_lcnphy_tempcompensated_txpwrctrl(pi); + index2 = (u16) (index * 2); + mod_phy_reg(pi, 0x4a9, (0x1ff << 0), (index2) << 0); + + pi_lcn->lcnphy_current_index = + (s8)((read_phy_reg(pi, 0x4a9) & 0xFF) / 2); + } +} + +static void +wlc_lcnphy_load_tx_gain_table(struct brcms_phy *pi, + const struct lcnphy_tx_gain_tbl_entry *gain_table) +{ + u32 j; + struct phytbl_info tab; + u32 val; + u16 pa_gain; + u16 gm_gain; + + if (CHSPEC_IS5G(pi->radio_chanspec)) + pa_gain = 0x70; + else + pa_gain = 0x70; + + if (pi->sh->boardflags & BFL_FEM) + pa_gain = 0x10; + tab.tbl_id = LCNPHY_TBL_ID_TXPWRCTL; + tab.tbl_width = 32; + tab.tbl_len = 1; + tab.tbl_ptr = &val; + + for (j = 0; j < 128; j++) { + gm_gain = gain_table[j].gm; + val = (((u32) pa_gain << 24) | + (gain_table[j].pad << 16) | + (gain_table[j].pga << 8) | gm_gain); + + tab.tbl_offset = LCNPHY_TX_PWR_CTRL_GAIN_OFFSET + j; + wlc_lcnphy_write_table(pi, &tab); + + val = (gain_table[j].dac << 28) | (gain_table[j].bb_mult << 20); + tab.tbl_offset = LCNPHY_TX_PWR_CTRL_IQ_OFFSET + j; + wlc_lcnphy_write_table(pi, &tab); + } +} + +static void wlc_lcnphy_load_rfpower(struct brcms_phy *pi) +{ + struct phytbl_info tab; + u32 val, bbmult, rfgain; + u8 index; + u8 scale_factor = 1; + s16 temp, temp1, temp2, qQ, qQ1, qQ2, shift; + + tab.tbl_id = LCNPHY_TBL_ID_TXPWRCTL; + tab.tbl_width = 32; + tab.tbl_len = 1; + + for (index = 0; index < 128; index++) { + tab.tbl_ptr = &bbmult; + tab.tbl_offset = LCNPHY_TX_PWR_CTRL_IQ_OFFSET + index; + wlc_lcnphy_read_table(pi, &tab); + bbmult = bbmult >> 20; + + tab.tbl_ptr = &rfgain; + tab.tbl_offset = LCNPHY_TX_PWR_CTRL_GAIN_OFFSET + index; + wlc_lcnphy_read_table(pi, &tab); + + qm_log10((s32) (bbmult), 0, &temp1, &qQ1); + qm_log10((s32) (1 << 6), 0, &temp2, &qQ2); + + if (qQ1 < qQ2) { + temp2 = qm_shr16(temp2, qQ2 - qQ1); + qQ = qQ1; + } else { + temp1 = qm_shr16(temp1, qQ1 - qQ2); + qQ = qQ2; + } + temp = qm_sub16(temp1, temp2); + + if (qQ >= 4) + shift = qQ - 4; + else + shift = 4 - qQ; + + val = (((index << shift) + (5 * temp) + + (1 << (scale_factor + shift - 3))) >> (scale_factor + + shift - 2)); + + tab.tbl_ptr = &val; + tab.tbl_offset = LCNPHY_TX_PWR_CTRL_PWR_OFFSET + index; + wlc_lcnphy_write_table(pi, &tab); + } +} + +static void wlc_lcnphy_bu_tweaks(struct brcms_phy *pi) +{ + or_phy_reg(pi, 0x805, 0x1); + + mod_phy_reg(pi, 0x42f, (0x7 << 0), (0x3) << 0); + + mod_phy_reg(pi, 0x030, (0x7 << 0), (0x3) << 0); + + write_phy_reg(pi, 0x414, 0x1e10); + write_phy_reg(pi, 0x415, 0x0640); + + mod_phy_reg(pi, 0x4df, (0xff << 8), -9 << 8); + + or_phy_reg(pi, 0x44a, 0x44); + write_phy_reg(pi, 0x44a, 0x80); + mod_phy_reg(pi, 0x434, (0xff << 0), (0xFD) << 0); + + mod_phy_reg(pi, 0x420, (0xff << 0), (16) << 0); + + if (!(pi->sh->boardrev < 0x1204)) + mod_radio_reg(pi, RADIO_2064_REG09B, 0xF0, 0xF0); + + write_phy_reg(pi, 0x7d6, 0x0902); + mod_phy_reg(pi, 0x429, (0xf << 0), (0x9) << 0); + + mod_phy_reg(pi, 0x429, (0x3f << 4), (0xe) << 4); + + if (LCNREV_IS(pi->pubpi.phy_rev, 1)) { + mod_phy_reg(pi, 0x423, (0xff << 0), (0x46) << 0); + + mod_phy_reg(pi, 0x411, (0xff << 0), (1) << 0); + + mod_phy_reg(pi, 0x434, (0xff << 0), (0xFF) << 0); + + mod_phy_reg(pi, 0x656, (0xf << 0), (2) << 0); + + mod_phy_reg(pi, 0x44d, (0x1 << 2), (1) << 2); + + mod_radio_reg(pi, RADIO_2064_REG0F7, 0x4, 0x4); + mod_radio_reg(pi, RADIO_2064_REG0F1, 0x3, 0); + mod_radio_reg(pi, RADIO_2064_REG0F2, 0xF8, 0x90); + mod_radio_reg(pi, RADIO_2064_REG0F3, 0x3, 0x2); + mod_radio_reg(pi, RADIO_2064_REG0F3, 0xf0, 0xa0); + + mod_radio_reg(pi, RADIO_2064_REG11F, 0x2, 0x2); + + wlc_lcnphy_clear_tx_power_offsets(pi); + mod_phy_reg(pi, 0x4d0, (0x1ff << 6), (10) << 6); + + } +} + +static void wlc_lcnphy_rcal(struct brcms_phy *pi) +{ + u8 rcal_value; + + and_radio_reg(pi, RADIO_2064_REG05B, 0xfD); + + or_radio_reg(pi, RADIO_2064_REG004, 0x40); + or_radio_reg(pi, RADIO_2064_REG120, 0x10); + + or_radio_reg(pi, RADIO_2064_REG078, 0x80); + or_radio_reg(pi, RADIO_2064_REG129, 0x02); + + or_radio_reg(pi, RADIO_2064_REG057, 0x01); + + or_radio_reg(pi, RADIO_2064_REG05B, 0x02); + mdelay(5); + SPINWAIT(!wlc_radio_2064_rcal_done(pi), 10 * 1000 * 1000); + + if (wlc_radio_2064_rcal_done(pi)) { + rcal_value = (u8) read_radio_reg(pi, RADIO_2064_REG05C); + rcal_value = rcal_value & 0x1f; + } + + and_radio_reg(pi, RADIO_2064_REG05B, 0xfD); + + and_radio_reg(pi, RADIO_2064_REG057, 0xFE); +} + +static void wlc_lcnphy_rc_cal(struct brcms_phy *pi) +{ + u8 dflt_rc_cal_val; + u16 flt_val; + + dflt_rc_cal_val = 7; + if (LCNREV_IS(pi->pubpi.phy_rev, 1)) + dflt_rc_cal_val = 11; + flt_val = + (dflt_rc_cal_val << 10) | (dflt_rc_cal_val << 5) | + (dflt_rc_cal_val); + write_phy_reg(pi, 0x933, flt_val); + write_phy_reg(pi, 0x934, flt_val); + write_phy_reg(pi, 0x935, flt_val); + write_phy_reg(pi, 0x936, flt_val); + write_phy_reg(pi, 0x937, (flt_val & 0x1FF)); + + return; +} + +static void wlc_radio_2064_init(struct brcms_phy *pi) +{ + u32 i; + const struct lcnphy_radio_regs *lcnphyregs = NULL; + + lcnphyregs = lcnphy_radio_regs_2064; + + for (i = 0; lcnphyregs[i].address != 0xffff; i++) + if (CHSPEC_IS5G(pi->radio_chanspec) && lcnphyregs[i].do_init_a) + write_radio_reg(pi, + ((lcnphyregs[i].address & 0x3fff) | + RADIO_DEFAULT_CORE), + (u16) lcnphyregs[i].init_a); + else if (lcnphyregs[i].do_init_g) + write_radio_reg(pi, + ((lcnphyregs[i].address & 0x3fff) | + RADIO_DEFAULT_CORE), + (u16) lcnphyregs[i].init_g); + + write_radio_reg(pi, RADIO_2064_REG032, 0x62); + write_radio_reg(pi, RADIO_2064_REG033, 0x19); + + write_radio_reg(pi, RADIO_2064_REG090, 0x10); + + write_radio_reg(pi, RADIO_2064_REG010, 0x00); + + if (LCNREV_IS(pi->pubpi.phy_rev, 1)) { + + write_radio_reg(pi, RADIO_2064_REG060, 0x7f); + write_radio_reg(pi, RADIO_2064_REG061, 0x72); + write_radio_reg(pi, RADIO_2064_REG062, 0x7f); + } + + write_radio_reg(pi, RADIO_2064_REG01D, 0x02); + write_radio_reg(pi, RADIO_2064_REG01E, 0x06); + + mod_phy_reg(pi, 0x4ea, (0x7 << 0), 0 << 0); + + mod_phy_reg(pi, 0x4ea, (0x7 << 3), 1 << 3); + + mod_phy_reg(pi, 0x4ea, (0x7 << 6), 2 << 6); + + mod_phy_reg(pi, 0x4ea, (0x7 << 9), 3 << 9); + + mod_phy_reg(pi, 0x4ea, (0x7 << 12), 4 << 12); + + write_phy_reg(pi, 0x4ea, 0x4688); + + mod_phy_reg(pi, 0x4eb, (0x7 << 0), 2 << 0); + + mod_phy_reg(pi, 0x4eb, (0x7 << 6), 0 << 6); + + mod_phy_reg(pi, 0x46a, (0xffff << 0), 25 << 0); + + wlc_lcnphy_set_tx_locc(pi, 0); + + wlc_lcnphy_rcal(pi); + + wlc_lcnphy_rc_cal(pi); +} + +static void wlc_lcnphy_radio_init(struct brcms_phy *pi) +{ + wlc_radio_2064_init(pi); +} + +static void wlc_lcnphy_tbl_init(struct brcms_phy *pi) +{ + uint idx; + u8 phybw40; + struct phytbl_info tab; + u32 val; + + phybw40 = CHSPEC_IS40(pi->radio_chanspec); + + for (idx = 0; idx < dot11lcnphytbl_info_sz_rev0; idx++) + wlc_lcnphy_write_table(pi, &dot11lcnphytbl_info_rev0[idx]); + + if (pi->sh->boardflags & BFL_FEM_BT) { + tab.tbl_id = LCNPHY_TBL_ID_RFSEQ; + tab.tbl_width = 16; + tab.tbl_ptr = &val; + tab.tbl_len = 1; + val = 100; + tab.tbl_offset = 4; + wlc_lcnphy_write_table(pi, &tab); + } + + tab.tbl_id = LCNPHY_TBL_ID_RFSEQ; + tab.tbl_width = 16; + tab.tbl_ptr = &val; + tab.tbl_len = 1; + + val = 114; + tab.tbl_offset = 0; + wlc_lcnphy_write_table(pi, &tab); + + val = 130; + tab.tbl_offset = 1; + wlc_lcnphy_write_table(pi, &tab); + + val = 6; + tab.tbl_offset = 8; + wlc_lcnphy_write_table(pi, &tab); + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if (pi->sh->boardflags & BFL_FEM) + wlc_lcnphy_load_tx_gain_table( + pi, + dot11lcnphy_2GHz_extPA_gaintable_rev0); + else + wlc_lcnphy_load_tx_gain_table( + pi, + dot11lcnphy_2GHz_gaintable_rev0); + } + + if (LCNREV_IS(pi->pubpi.phy_rev, 2)) { + const struct phytbl_info *tb; + int l; + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + l = dot11lcnphytbl_rx_gain_info_2G_rev2_sz; + if (pi->sh->boardflags & BFL_EXTLNA) + tb = dot11lcnphytbl_rx_gain_info_extlna_2G_rev2; + else + tb = dot11lcnphytbl_rx_gain_info_2G_rev2; + } else { + l = dot11lcnphytbl_rx_gain_info_5G_rev2_sz; + if (pi->sh->boardflags & BFL_EXTLNA_5GHz) + tb = dot11lcnphytbl_rx_gain_info_extlna_5G_rev2; + else + tb = dot11lcnphytbl_rx_gain_info_5G_rev2; + } + + for (idx = 0; idx < l; idx++) + wlc_lcnphy_write_table(pi, &tb[idx]); + } + + if ((pi->sh->boardflags & BFL_FEM) + && !(pi->sh->boardflags & BFL_FEM_BT)) + wlc_lcnphy_write_table(pi, &dot11lcn_sw_ctrl_tbl_info_4313_epa); + else if (pi->sh->boardflags & BFL_FEM_BT) { + if (pi->sh->boardrev < 0x1250) + wlc_lcnphy_write_table( + pi, + &dot11lcn_sw_ctrl_tbl_info_4313_bt_epa); + else + wlc_lcnphy_write_table( + pi, + &dot11lcn_sw_ctrl_tbl_info_4313_bt_epa_p250); + } else + wlc_lcnphy_write_table(pi, &dot11lcn_sw_ctrl_tbl_info_4313); + + wlc_lcnphy_load_rfpower(pi); + + wlc_lcnphy_clear_papd_comptable(pi); +} + +static void wlc_lcnphy_rev0_baseband_init(struct brcms_phy *pi) +{ + u16 afectrl1; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + write_radio_reg(pi, RADIO_2064_REG11C, 0x0); + + write_phy_reg(pi, 0x43b, 0x0); + write_phy_reg(pi, 0x43c, 0x0); + write_phy_reg(pi, 0x44c, 0x0); + write_phy_reg(pi, 0x4e6, 0x0); + write_phy_reg(pi, 0x4f9, 0x0); + write_phy_reg(pi, 0x4b0, 0x0); + write_phy_reg(pi, 0x938, 0x0); + write_phy_reg(pi, 0x4b0, 0x0); + write_phy_reg(pi, 0x44e, 0); + + or_phy_reg(pi, 0x567, 0x03); + + or_phy_reg(pi, 0x44a, 0x44); + write_phy_reg(pi, 0x44a, 0x80); + + if (!(pi->sh->boardflags & BFL_FEM)) + wlc_lcnphy_set_tx_pwr_by_index(pi, 52); + + if (0) { + afectrl1 = 0; + afectrl1 = (u16) ((pi_lcn->lcnphy_rssi_vf) | + (pi_lcn->lcnphy_rssi_vc << 4) | + (pi_lcn->lcnphy_rssi_gs << 10)); + write_phy_reg(pi, 0x43e, afectrl1); + } + + mod_phy_reg(pi, 0x634, (0xff << 0), 0xC << 0); + if (pi->sh->boardflags & BFL_FEM) { + mod_phy_reg(pi, 0x634, (0xff << 0), 0xA << 0); + + write_phy_reg(pi, 0x910, 0x1); + } + + mod_phy_reg(pi, 0x448, (0x3 << 8), 1 << 8); + mod_phy_reg(pi, 0x608, (0xff << 0), 0x17 << 0); + mod_phy_reg(pi, 0x604, (0x7ff << 0), 0x3EA << 0); + +} + +static void wlc_lcnphy_rev2_baseband_init(struct brcms_phy *pi) +{ + if (CHSPEC_IS5G(pi->radio_chanspec)) { + mod_phy_reg(pi, 0x416, (0xff << 0), 80 << 0); + mod_phy_reg(pi, 0x416, (0xff << 8), 80 << 8); + } +} + +static void wlc_lcnphy_agc_temp_init(struct brcms_phy *pi) +{ + s16 temp; + struct phytbl_info tab; + u32 tableBuffer[2]; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + temp = (s16) read_phy_reg(pi, 0x4df); + pi_lcn->lcnphy_ofdmgainidxtableoffset = (temp & (0xff << 0)) >> 0; + + if (pi_lcn->lcnphy_ofdmgainidxtableoffset > 127) + pi_lcn->lcnphy_ofdmgainidxtableoffset -= 256; + + pi_lcn->lcnphy_dsssgainidxtableoffset = (temp & (0xff << 8)) >> 8; + + if (pi_lcn->lcnphy_dsssgainidxtableoffset > 127) + pi_lcn->lcnphy_dsssgainidxtableoffset -= 256; + + tab.tbl_ptr = tableBuffer; + tab.tbl_len = 2; + tab.tbl_id = 17; + tab.tbl_offset = 59; + tab.tbl_width = 32; + wlc_lcnphy_read_table(pi, &tab); + + if (tableBuffer[0] > 63) + tableBuffer[0] -= 128; + pi_lcn->lcnphy_tr_R_gain_val = tableBuffer[0]; + + if (tableBuffer[1] > 63) + tableBuffer[1] -= 128; + pi_lcn->lcnphy_tr_T_gain_val = tableBuffer[1]; + + temp = (s16) (read_phy_reg(pi, 0x434) & (0xff << 0)); + if (temp > 127) + temp -= 256; + pi_lcn->lcnphy_input_pwr_offset_db = (s8) temp; + + pi_lcn->lcnphy_Med_Low_Gain_db = + (read_phy_reg(pi, 0x424) & (0xff << 8)) >> 8; + pi_lcn->lcnphy_Very_Low_Gain_db = + (read_phy_reg(pi, 0x425) & (0xff << 0)) >> 0; + + tab.tbl_ptr = tableBuffer; + tab.tbl_len = 2; + tab.tbl_id = LCNPHY_TBL_ID_GAIN_IDX; + tab.tbl_offset = 28; + tab.tbl_width = 32; + wlc_lcnphy_read_table(pi, &tab); + + pi_lcn->lcnphy_gain_idx_14_lowword = tableBuffer[0]; + pi_lcn->lcnphy_gain_idx_14_hiword = tableBuffer[1]; + +} + +static void wlc_lcnphy_baseband_init(struct brcms_phy *pi) +{ + + wlc_lcnphy_tbl_init(pi); + wlc_lcnphy_rev0_baseband_init(pi); + if (LCNREV_IS(pi->pubpi.phy_rev, 2)) + wlc_lcnphy_rev2_baseband_init(pi); + wlc_lcnphy_bu_tweaks(pi); +} + +void wlc_phy_init_lcnphy(struct brcms_phy *pi) +{ + u8 phybw40; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + phybw40 = CHSPEC_IS40(pi->radio_chanspec); + + pi_lcn->lcnphy_cal_counter = 0; + pi_lcn->lcnphy_cal_temper = pi_lcn->lcnphy_rawtempsense; + + or_phy_reg(pi, 0x44a, 0x80); + and_phy_reg(pi, 0x44a, 0x7f); + + wlc_lcnphy_afe_clk_init(pi, AFE_CLK_INIT_MODE_TXRX2X); + + write_phy_reg(pi, 0x60a, 160); + + write_phy_reg(pi, 0x46a, 25); + + wlc_lcnphy_baseband_init(pi); + + wlc_lcnphy_radio_init(pi); + + if (CHSPEC_IS2G(pi->radio_chanspec)) + wlc_lcnphy_tx_pwr_ctrl_init((struct brcms_phy_pub *) pi); + + wlc_phy_chanspec_set((struct brcms_phy_pub *) pi, pi->radio_chanspec); + + bcma_chipco_regctl_maskset(&pi->d11core->bus->drv_cc, 0, ~0xf, 0x9); + + bcma_chipco_chipctl_maskset(&pi->d11core->bus->drv_cc, 0, 0x0, + 0x03CDDDDD); + + if ((pi->sh->boardflags & BFL_FEM) + && wlc_lcnphy_tempsense_based_pwr_ctrl_enabled(pi)) + wlc_lcnphy_set_tx_pwr_by_index(pi, FIXED_TXPWR); + + wlc_lcnphy_agc_temp_init(pi); + + wlc_lcnphy_temp_adj(pi); + + mod_phy_reg(pi, 0x448, (0x1 << 14), (1) << 14); + + udelay(100); + mod_phy_reg(pi, 0x448, (0x1 << 14), (0) << 14); + + wlc_lcnphy_set_tx_pwr_ctrl(pi, LCNPHY_TX_PWR_CTRL_HW); + pi_lcn->lcnphy_noise_samples = LCNPHY_NOISE_SAMPLES_DEFAULT; + wlc_lcnphy_calib_modes(pi, PHY_PERICAL_PHYINIT); +} + +static bool wlc_phy_txpwr_srom_read_lcnphy(struct brcms_phy *pi) +{ + s8 txpwr = 0; + int i; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + struct ssb_sprom *sprom = &pi->d11core->bus->sprom; + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + u16 cckpo = 0; + u32 offset_ofdm, offset_mcs; + + pi_lcn->lcnphy_tr_isolation_mid = sprom->fem.ghz2.tr_iso; + + pi_lcn->lcnphy_rx_power_offset = sprom->rxpo2g; + + pi->txpa_2g[0] = sprom->pa0b0; + pi->txpa_2g[1] = sprom->pa0b1; + pi->txpa_2g[2] = sprom->pa0b2; + + pi_lcn->lcnphy_rssi_vf = sprom->rssismf2g; + pi_lcn->lcnphy_rssi_vc = sprom->rssismc2g; + pi_lcn->lcnphy_rssi_gs = sprom->rssisav2g; + + pi_lcn->lcnphy_rssi_vf_lowtemp = pi_lcn->lcnphy_rssi_vf; + pi_lcn->lcnphy_rssi_vc_lowtemp = pi_lcn->lcnphy_rssi_vc; + pi_lcn->lcnphy_rssi_gs_lowtemp = pi_lcn->lcnphy_rssi_gs; + + pi_lcn->lcnphy_rssi_vf_hightemp = pi_lcn->lcnphy_rssi_vf; + pi_lcn->lcnphy_rssi_vc_hightemp = pi_lcn->lcnphy_rssi_vc; + pi_lcn->lcnphy_rssi_gs_hightemp = pi_lcn->lcnphy_rssi_gs; + + txpwr = sprom->core_pwr_info[0].maxpwr_2g; + pi->tx_srom_max_2g = txpwr; + + for (i = 0; i < PWRTBL_NUM_COEFF; i++) { + pi->txpa_2g_low_temp[i] = pi->txpa_2g[i]; + pi->txpa_2g_high_temp[i] = pi->txpa_2g[i]; + } + + cckpo = sprom->cck2gpo; + offset_ofdm = sprom->ofdm2gpo; + if (cckpo) { + uint max_pwr_chan = txpwr; + + for (i = TXP_FIRST_CCK; i <= TXP_LAST_CCK; i++) { + pi->tx_srom_max_rate_2g[i] = + max_pwr_chan - ((cckpo & 0xf) * 2); + cckpo >>= 4; + } + + for (i = TXP_FIRST_OFDM; i <= TXP_LAST_OFDM; i++) { + pi->tx_srom_max_rate_2g[i] = + max_pwr_chan - + ((offset_ofdm & 0xf) * 2); + offset_ofdm >>= 4; + } + } else { + u8 opo = 0; + + opo = sprom->opo; + + for (i = TXP_FIRST_CCK; i <= TXP_LAST_CCK; i++) + pi->tx_srom_max_rate_2g[i] = txpwr; + + for (i = TXP_FIRST_OFDM; i <= TXP_LAST_OFDM; i++) { + pi->tx_srom_max_rate_2g[i] = txpwr - + ((offset_ofdm & 0xf) * 2); + offset_ofdm >>= 4; + } + offset_mcs = sprom->mcs2gpo[1] << 16; + offset_mcs |= sprom->mcs2gpo[0]; + pi_lcn->lcnphy_mcs20_po = offset_mcs; + for (i = TXP_FIRST_SISO_MCS_20; + i <= TXP_LAST_SISO_MCS_20; i++) { + pi->tx_srom_max_rate_2g[i] = + txpwr - ((offset_mcs & 0xf) * 2); + offset_mcs >>= 4; + } + } + + pi_lcn->lcnphy_rawtempsense = sprom->rawtempsense; + pi_lcn->lcnphy_measPower = sprom->measpower; + pi_lcn->lcnphy_tempsense_slope = sprom->tempsense_slope; + pi_lcn->lcnphy_hw_iqcal_en = sprom->hw_iqcal_en; + pi_lcn->lcnphy_iqcal_swp_dis = sprom->iqcal_swp_dis; + pi_lcn->lcnphy_tempcorrx = sprom->tempcorrx; + pi_lcn->lcnphy_tempsense_option = sprom->tempsense_option; + pi_lcn->lcnphy_freqoffset_corr = sprom->freqoffset_corr; + if (sprom->ant_available_bg > 1) + wlc_phy_ant_rxdiv_set((struct brcms_phy_pub *) pi, + sprom->ant_available_bg); + } + pi_lcn->lcnphy_cck_dig_filt_type = -1; + + return true; +} + +void wlc_2064_vco_cal(struct brcms_phy *pi) +{ + u8 calnrst; + + mod_radio_reg(pi, RADIO_2064_REG057, 1 << 3, 1 << 3); + calnrst = (u8) read_radio_reg(pi, RADIO_2064_REG056) & 0xf8; + write_radio_reg(pi, RADIO_2064_REG056, calnrst); + udelay(1); + write_radio_reg(pi, RADIO_2064_REG056, calnrst | 0x03); + udelay(1); + write_radio_reg(pi, RADIO_2064_REG056, calnrst | 0x07); + udelay(300); + mod_radio_reg(pi, RADIO_2064_REG057, 1 << 3, 0); +} + +bool wlc_phy_tpc_isenabled_lcnphy(struct brcms_phy *pi) +{ + if (wlc_lcnphy_tempsense_based_pwr_ctrl_enabled(pi)) + return 0; + else + return (LCNPHY_TX_PWR_CTRL_HW == + wlc_lcnphy_get_tx_pwr_ctrl((pi))); +} + +void wlc_phy_txpower_recalc_target_lcnphy(struct brcms_phy *pi) +{ + u16 pwr_ctrl; + if (wlc_lcnphy_tempsense_based_pwr_ctrl_enabled(pi)) { + wlc_lcnphy_calib_modes(pi, LCNPHY_PERICAL_TEMPBASED_TXPWRCTRL); + } else if (wlc_lcnphy_tssi_based_pwr_ctrl_enabled(pi)) { + pwr_ctrl = wlc_lcnphy_get_tx_pwr_ctrl(pi); + wlc_lcnphy_set_tx_pwr_ctrl(pi, LCNPHY_TX_PWR_CTRL_OFF); + wlc_lcnphy_txpower_recalc_target(pi); + wlc_lcnphy_set_tx_pwr_ctrl(pi, pwr_ctrl); + } +} + +void wlc_phy_chanspec_set_lcnphy(struct brcms_phy *pi, u16 chanspec) +{ + u8 channel = CHSPEC_CHANNEL(chanspec); + + wlc_phy_chanspec_radio_set((struct brcms_phy_pub *)pi, chanspec); + + wlc_lcnphy_set_chanspec_tweaks(pi, pi->radio_chanspec); + + or_phy_reg(pi, 0x44a, 0x44); + write_phy_reg(pi, 0x44a, 0x80); + + wlc_lcnphy_radio_2064_channel_tune_4313(pi, channel); + udelay(1000); + + wlc_lcnphy_toggle_afe_pwdn(pi); + + write_phy_reg(pi, 0x657, lcnphy_sfo_cfg[channel - 1].ptcentreTs20); + write_phy_reg(pi, 0x658, lcnphy_sfo_cfg[channel - 1].ptcentreFactor); + + if (CHSPEC_CHANNEL(pi->radio_chanspec) == 14) { + mod_phy_reg(pi, 0x448, (0x3 << 8), (2) << 8); + + wlc_lcnphy_load_tx_iir_filter(pi, false, 3); + } else { + mod_phy_reg(pi, 0x448, (0x3 << 8), (1) << 8); + + wlc_lcnphy_load_tx_iir_filter(pi, false, 2); + } + + if (pi->sh->boardflags & BFL_FEM) + wlc_lcnphy_load_tx_iir_filter(pi, true, 0); + else + wlc_lcnphy_load_tx_iir_filter(pi, true, 3); + + mod_phy_reg(pi, 0x4eb, (0x7 << 3), (1) << 3); +} + +void wlc_phy_detach_lcnphy(struct brcms_phy *pi) +{ + kfree(pi->u.pi_lcnphy); +} + +bool wlc_phy_attach_lcnphy(struct brcms_phy *pi) +{ + struct brcms_phy_lcnphy *pi_lcn; + + pi->u.pi_lcnphy = kzalloc(sizeof(struct brcms_phy_lcnphy), GFP_ATOMIC); + if (pi->u.pi_lcnphy == NULL) + return false; + + pi_lcn = pi->u.pi_lcnphy; + + if (0 == (pi->sh->boardflags & BFL_NOPA)) { + pi->hwpwrctrl = true; + pi->hwpwrctrl_capable = true; + } + + pi->xtalfreq = bcma_chipco_get_alp_clock(&pi->d11core->bus->drv_cc); + pi_lcn->lcnphy_papd_rxGnCtrl_init = 0; + + pi->pi_fptr.init = wlc_phy_init_lcnphy; + pi->pi_fptr.calinit = wlc_phy_cal_init_lcnphy; + pi->pi_fptr.chanset = wlc_phy_chanspec_set_lcnphy; + pi->pi_fptr.txpwrrecalc = wlc_phy_txpower_recalc_target_lcnphy; + pi->pi_fptr.txiqccget = wlc_lcnphy_get_tx_iqcc; + pi->pi_fptr.txiqccset = wlc_lcnphy_set_tx_iqcc; + pi->pi_fptr.txloccget = wlc_lcnphy_get_tx_locc; + pi->pi_fptr.radioloftget = wlc_lcnphy_get_radio_loft; + pi->pi_fptr.detach = wlc_phy_detach_lcnphy; + + if (!wlc_phy_txpwr_srom_read_lcnphy(pi)) + return false; + + if ((pi->sh->boardflags & BFL_FEM) && + (LCNREV_IS(pi->pubpi.phy_rev, 1))) { + if (pi_lcn->lcnphy_tempsense_option == 3) { + pi->hwpwrctrl = true; + pi->hwpwrctrl_capable = true; + pi->temppwrctrl_capable = false; + } else { + pi->hwpwrctrl = false; + pi->hwpwrctrl_capable = false; + pi->temppwrctrl_capable = true; + } + } + + return true; +} + +static void wlc_lcnphy_set_rx_gain(struct brcms_phy *pi, u32 gain) +{ + u16 trsw, ext_lna, lna1, lna2, tia, biq0, biq1, gain0_15, gain16_19; + + trsw = (gain & ((u32) 1 << 28)) ? 0 : 1; + ext_lna = (u16) (gain >> 29) & 0x01; + lna1 = (u16) (gain >> 0) & 0x0f; + lna2 = (u16) (gain >> 4) & 0x0f; + tia = (u16) (gain >> 8) & 0xf; + biq0 = (u16) (gain >> 12) & 0xf; + biq1 = (u16) (gain >> 16) & 0xf; + + gain0_15 = (u16) ((lna1 & 0x3) | ((lna1 & 0x3) << 2) | + ((lna2 & 0x3) << 4) | ((lna2 & 0x3) << 6) | + ((tia & 0xf) << 8) | ((biq0 & 0xf) << 12)); + gain16_19 = biq1; + + mod_phy_reg(pi, 0x44d, (0x1 << 0), trsw << 0); + mod_phy_reg(pi, 0x4b1, (0x1 << 9), ext_lna << 9); + mod_phy_reg(pi, 0x4b1, (0x1 << 10), ext_lna << 10); + mod_phy_reg(pi, 0x4b6, (0xffff << 0), gain0_15 << 0); + mod_phy_reg(pi, 0x4b7, (0xf << 0), gain16_19 << 0); + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + mod_phy_reg(pi, 0x4b1, (0x3 << 11), lna1 << 11); + mod_phy_reg(pi, 0x4e6, (0x3 << 3), lna1 << 3); + } + wlc_lcnphy_rx_gain_override_enable(pi, true); +} + +static u32 wlc_lcnphy_get_receive_power(struct brcms_phy *pi, s32 *gain_index) +{ + u32 received_power = 0; + s32 max_index = 0; + u32 gain_code = 0; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + max_index = 36; + if (*gain_index >= 0) + gain_code = lcnphy_23bitgaincode_table[*gain_index]; + + if (-1 == *gain_index) { + *gain_index = 0; + while ((*gain_index <= (s32) max_index) + && (received_power < 700)) { + wlc_lcnphy_set_rx_gain(pi, + lcnphy_23bitgaincode_table + [*gain_index]); + received_power = + wlc_lcnphy_measure_digital_power( + pi, + pi_lcn-> + lcnphy_noise_samples); + (*gain_index)++; + } + (*gain_index)--; + } else { + wlc_lcnphy_set_rx_gain(pi, gain_code); + received_power = + wlc_lcnphy_measure_digital_power(pi, + pi_lcn-> + lcnphy_noise_samples); + } + + return received_power; +} + +s32 wlc_lcnphy_rx_signal_power(struct brcms_phy *pi, s32 gain_index) +{ + s32 gain = 0; + s32 nominal_power_db; + s32 log_val, gain_mismatch, desired_gain, input_power_offset_db, + input_power_db; + s32 received_power, temperature; + u32 power; + u32 msb1, msb2, val1, val2, diff1, diff2; + uint freq; + struct brcms_phy_lcnphy *pi_lcn = pi->u.pi_lcnphy; + + received_power = wlc_lcnphy_get_receive_power(pi, &gain_index); + + gain = lcnphy_gain_table[gain_index]; + + nominal_power_db = read_phy_reg(pi, 0x425) >> 8; + + power = (received_power * 16); + msb1 = ffs(power) - 1; + msb2 = msb1 + 1; + val1 = 1 << msb1; + val2 = 1 << msb2; + diff1 = (power - val1); + diff2 = (val2 - power); + if (diff1 < diff2) + log_val = msb1; + else + log_val = msb2; + + log_val = log_val * 3; + + gain_mismatch = (nominal_power_db / 2) - (log_val); + + desired_gain = gain + gain_mismatch; + + input_power_offset_db = read_phy_reg(pi, 0x434) & 0xFF; + + if (input_power_offset_db > 127) + input_power_offset_db -= 256; + + input_power_db = input_power_offset_db - desired_gain; + + input_power_db = + input_power_db + lcnphy_gain_index_offset_for_rssi[gain_index]; + + freq = wlc_phy_channel2freq(CHSPEC_CHANNEL(pi->radio_chanspec)); + if ((freq > 2427) && (freq <= 2467)) + input_power_db = input_power_db - 1; + + temperature = pi_lcn->lcnphy_lastsensed_temperature; + + if ((temperature - 15) < -30) + input_power_db = + input_power_db + + (((temperature - 10 - 25) * 286) >> 12) - + 7; + else if ((temperature - 15) < 4) + input_power_db = + input_power_db + + (((temperature - 10 - 25) * 286) >> 12) - + 3; + else + input_power_db = input_power_db + + (((temperature - 10 - 25) * 286) >> 12); + + wlc_lcnphy_rx_gain_override_enable(pi, 0); + + return input_power_db; +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_lcn.h b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_lcn.h new file mode 100644 index 0000000..f4a8ab0 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_lcn.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_PHY_LCN_H_ +#define _BRCM_PHY_LCN_H_ + +#include + +struct brcms_phy_lcnphy { + int lcnphy_txrf_sp_9_override; + u8 lcnphy_full_cal_channel; + u8 lcnphy_cal_counter; + u16 lcnphy_cal_temper; + bool lcnphy_recal; + + u8 lcnphy_rc_cap; + u32 lcnphy_mcs20_po; + + u8 lcnphy_tr_isolation_mid; + u8 lcnphy_tr_isolation_low; + u8 lcnphy_tr_isolation_hi; + + u8 lcnphy_bx_arch; + u8 lcnphy_rx_power_offset; + u8 lcnphy_rssi_vf; + u8 lcnphy_rssi_vc; + u8 lcnphy_rssi_gs; + u8 lcnphy_tssi_val; + u8 lcnphy_rssi_vf_lowtemp; + u8 lcnphy_rssi_vc_lowtemp; + u8 lcnphy_rssi_gs_lowtemp; + + u8 lcnphy_rssi_vf_hightemp; + u8 lcnphy_rssi_vc_hightemp; + u8 lcnphy_rssi_gs_hightemp; + + s16 lcnphy_pa0b0; + s16 lcnphy_pa0b1; + s16 lcnphy_pa0b2; + + u16 lcnphy_rawtempsense; + u8 lcnphy_measPower; + u8 lcnphy_tempsense_slope; + u8 lcnphy_freqoffset_corr; + u8 lcnphy_tempsense_option; + u8 lcnphy_tempcorrx; + bool lcnphy_iqcal_swp_dis; + bool lcnphy_hw_iqcal_en; + uint lcnphy_bandedge_corr; + bool lcnphy_spurmod; + u16 lcnphy_tssi_tx_cnt; + u16 lcnphy_tssi_idx; + u16 lcnphy_tssi_npt; + + u16 lcnphy_target_tx_freq; + s8 lcnphy_tx_power_idx_override; + u16 lcnphy_noise_samples; + + u32 lcnphy_papdRxGnIdx; + u32 lcnphy_papd_rxGnCtrl_init; + + u32 lcnphy_gain_idx_14_lowword; + u32 lcnphy_gain_idx_14_hiword; + u32 lcnphy_gain_idx_27_lowword; + u32 lcnphy_gain_idx_27_hiword; + s16 lcnphy_ofdmgainidxtableoffset; + s16 lcnphy_dsssgainidxtableoffset; + u32 lcnphy_tr_R_gain_val; + u32 lcnphy_tr_T_gain_val; + s8 lcnphy_input_pwr_offset_db; + u16 lcnphy_Med_Low_Gain_db; + u16 lcnphy_Very_Low_Gain_db; + s8 lcnphy_lastsensed_temperature; + s8 lcnphy_pkteng_rssi_slope; + u8 lcnphy_saved_tx_user_target[TXP_NUM_RATES]; + u8 lcnphy_volt_winner; + u8 lcnphy_volt_low; + u8 lcnphy_54_48_36_24mbps_backoff; + u8 lcnphy_11n_backoff; + u8 lcnphy_lowerofdm; + u8 lcnphy_cck; + u8 lcnphy_psat_2pt3_detected; + s32 lcnphy_lowest_Re_div_Im; + s8 lcnphy_final_papd_cal_idx; + u16 lcnphy_extstxctrl4; + u16 lcnphy_extstxctrl0; + u16 lcnphy_extstxctrl1; + s16 lcnphy_cck_dig_filt_type; + s16 lcnphy_ofdm_dig_filt_type; + struct lcnphy_cal_results lcnphy_cal_results; + + u8 lcnphy_psat_pwr; + u8 lcnphy_psat_indx; + s32 lcnphy_min_phase; + u8 lcnphy_final_idx; + u8 lcnphy_start_idx; + u8 lcnphy_current_index; + u16 lcnphy_logen_buf_1; + u16 lcnphy_local_ovr_2; + u16 lcnphy_local_oval_6; + u16 lcnphy_local_oval_5; + u16 lcnphy_logen_mixer_1; + + u8 lcnphy_aci_stat; + uint lcnphy_aci_start_time; + s8 lcnphy_tx_power_offset[TXP_NUM_RATES]; +}; +#endif /* _BRCM_PHY_LCN_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_n.c b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_n.c new file mode 100644 index 0000000..3e9f5b2 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_n.c @@ -0,0 +1,28729 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "phy_int.h" +#include "phy_hal.h" +#include "phy_radio.h" +#include "phyreg_n.h" +#include "phytbl_n.h" +#include "soc.h" + +#define READ_RADIO_REG2(pi, radio_type, jspace, core, reg_name) \ + read_radio_reg(pi, radio_type##_##jspace##_##reg_name | \ + ((core == PHY_CORE_0) ? \ + radio_type##_##jspace##0 : \ + radio_type##_##jspace##1)) + +#define WRITE_RADIO_REG2(pi, radio_type, jspace, core, reg_name, value) \ + write_radio_reg(pi, radio_type##_##jspace##_##reg_name | \ + ((core == PHY_CORE_0) ? \ + radio_type##_##jspace##0 : \ + radio_type##_##jspace##1), value) + +#define WRITE_RADIO_SYN(pi, radio_type, reg_name, value) \ + write_radio_reg(pi, radio_type##_##SYN##_##reg_name, value) + +#define READ_RADIO_REG3(pi, radio_type, jspace, core, reg_name) \ + read_radio_reg(pi, ((core == PHY_CORE_0) ? \ + radio_type##_##jspace##0##_##reg_name : \ + radio_type##_##jspace##1##_##reg_name)) + +#define WRITE_RADIO_REG3(pi, radio_type, jspace, core, reg_name, value) \ + write_radio_reg(pi, ((core == PHY_CORE_0) ? \ + radio_type##_##jspace##0##_##reg_name : \ + radio_type##_##jspace##1##_##reg_name), \ + value) + +#define READ_RADIO_REG4(pi, radio_type, jspace, core, reg_name) \ + read_radio_reg(pi, ((core == PHY_CORE_0) ? \ + radio_type##_##reg_name##_##jspace##0 : \ + radio_type##_##reg_name##_##jspace##1)) + +#define WRITE_RADIO_REG4(pi, radio_type, jspace, core, reg_name, value) \ + write_radio_reg(pi, ((core == PHY_CORE_0) ? \ + radio_type##_##reg_name##_##jspace##0 : \ + radio_type##_##reg_name##_##jspace##1), \ + value) + +#define NPHY_ACI_MAX_UNDETECT_WINDOW_SZ 40 +#define NPHY_ACI_CHANNEL_DELTA 5 +#define NPHY_ACI_CHANNEL_SKIP 4 +#define NPHY_ACI_40MHZ_CHANNEL_DELTA 6 +#define NPHY_ACI_40MHZ_CHANNEL_SKIP 5 +#define NPHY_ACI_40MHZ_CHANNEL_DELTA_GE_REV3 6 +#define NPHY_ACI_40MHZ_CHANNEL_SKIP_GE_REV3 5 +#define NPHY_ACI_CHANNEL_DELTA_GE_REV3 4 +#define NPHY_ACI_CHANNEL_SKIP_GE_REV3 3 + +#define NPHY_NOISE_NOASSOC_GLITCH_TH_UP 2 + +#define NPHY_NOISE_NOASSOC_GLITCH_TH_DN 8 + +#define NPHY_NOISE_ASSOC_GLITCH_TH_UP 2 + +#define NPHY_NOISE_ASSOC_GLITCH_TH_DN 8 + +#define NPHY_NOISE_ASSOC_ACI_GLITCH_TH_UP 2 + +#define NPHY_NOISE_ASSOC_ACI_GLITCH_TH_DN 8 + +#define NPHY_NOISE_NOASSOC_ENTER_TH 400 + +#define NPHY_NOISE_ASSOC_ENTER_TH 400 + +#define NPHY_NOISE_ASSOC_RX_GLITCH_BADPLCP_ENTER_TH 400 + +#define NPHY_NOISE_CRSMINPWR_ARRAY_MAX_INDEX 44 +#define NPHY_NOISE_CRSMINPWR_ARRAY_MAX_INDEX_REV_7 56 + +#define NPHY_NOISE_NOASSOC_CRSIDX_INCR 16 + +#define NPHY_NOISE_ASSOC_CRSIDX_INCR 8 + +#define NPHY_IS_SROM_REINTERPRET NREV_GE(pi->pubpi.phy_rev, 5) + +#define NPHY_RSSICAL_MAXREAD 31 + +#define NPHY_RSSICAL_NPOLL 8 +#define NPHY_RSSICAL_MAXD (1<<20) +#define NPHY_MIN_RXIQ_PWR 2 + +#define NPHY_RSSICAL_W1_TARGET 25 +#define NPHY_RSSICAL_W2_TARGET NPHY_RSSICAL_W1_TARGET +#define NPHY_RSSICAL_NB_TARGET 0 + +#define NPHY_RSSICAL_W1_TARGET_REV3 29 +#define NPHY_RSSICAL_W2_TARGET_REV3 NPHY_RSSICAL_W1_TARGET_REV3 + +#define NPHY_CALSANITY_RSSI_NB_MAX_POS 9 +#define NPHY_CALSANITY_RSSI_NB_MAX_NEG -9 +#define NPHY_CALSANITY_RSSI_W1_MAX_POS 12 +#define NPHY_CALSANITY_RSSI_W1_MAX_NEG (NPHY_RSSICAL_W1_TARGET - \ + NPHY_RSSICAL_MAXREAD) +#define NPHY_CALSANITY_RSSI_W2_MAX_POS NPHY_CALSANITY_RSSI_W1_MAX_POS +#define NPHY_CALSANITY_RSSI_W2_MAX_NEG (NPHY_RSSICAL_W2_TARGET - \ + NPHY_RSSICAL_MAXREAD) +#define NPHY_RSSI_SXT(x) ((s8) (-((x) & 0x20) + ((x) & 0x1f))) +#define NPHY_RSSI_NB_VIOL(x) (((x) > NPHY_CALSANITY_RSSI_NB_MAX_POS) || \ + ((x) < NPHY_CALSANITY_RSSI_NB_MAX_NEG)) +#define NPHY_RSSI_W1_VIOL(x) (((x) > NPHY_CALSANITY_RSSI_W1_MAX_POS) || \ + ((x) < NPHY_CALSANITY_RSSI_W1_MAX_NEG)) +#define NPHY_RSSI_W2_VIOL(x) (((x) > NPHY_CALSANITY_RSSI_W2_MAX_POS) || \ + ((x) < NPHY_CALSANITY_RSSI_W2_MAX_NEG)) + +#define NPHY_IQCAL_NUMGAINS 9 +#define NPHY_N_GCTL 0x66 + +#define NPHY_PAPD_EPS_TBL_SIZE 64 +#define NPHY_PAPD_SCL_TBL_SIZE 64 +#define NPHY_NUM_DIG_FILT_COEFFS 15 + +#define NPHY_PAPD_COMP_OFF 0 +#define NPHY_PAPD_COMP_ON 1 + +#define NPHY_SROM_TEMPSHIFT 32 +#define NPHY_SROM_MAXTEMPOFFSET 16 +#define NPHY_SROM_MINTEMPOFFSET -16 + +#define NPHY_CAL_MAXTEMPDELTA 64 + +#define NPHY_NOISEVAR_TBLLEN40 256 +#define NPHY_NOISEVAR_TBLLEN20 128 + +#define NPHY_ANARXLPFBW_REDUCTIONFACT 7 + +#define NPHY_ADJUSTED_MINCRSPOWER 0x1e + +/* 5357 Chip specific ChipControl register bits */ +#define CCTRL5357_EXTPA (1<<14) /* extPA in ChipControl 1, bit 14 */ +#define CCTRL5357_ANT_MUX_2o3 (1<<15) /* 2o3 in ChipControl 1, bit 15 */ + +#define NPHY_CAL_TSSISAMPS 64 +#define NPHY_TEST_TONE_FREQ_40MHz 4000 +#define NPHY_TEST_TONE_FREQ_20MHz 2500 + +#define MAX_205x_RCAL_WAITLOOPS 10000 + +#define NPHY_RXCAL_TONEAMP 181 +#define NPHY_RXCAL_TONEFREQ_40MHz 4000 +#define NPHY_RXCAL_TONEFREQ_20MHz 2000 + +#define TXFILT_SHAPING_OFDM20 0 +#define TXFILT_SHAPING_OFDM40 1 +#define TXFILT_SHAPING_CCK 2 +#define TXFILT_DEFAULT_OFDM20 3 +#define TXFILT_DEFAULT_OFDM40 4 + +struct nphy_iqcal_params { + u16 txlpf; + u16 txgm; + u16 pga; + u16 pad; + u16 ipa; + u16 cal_gain; + u16 ncorr[5]; +}; + +struct nphy_txiqcal_ladder { + u8 percent; + u8 g_env; +}; + +struct nphy_ipa_txcalgains { + struct nphy_txgains gains; + bool useindex; + u8 index; +}; + +struct nphy_papd_restore_state { + u16 fbmix[2]; + u16 vga_master[2]; + u16 intpa_master[2]; + u16 afectrl[2]; + u16 afeoverride[2]; + u16 pwrup[2]; + u16 atten[2]; + u16 mm; +}; + +struct nphy_ipa_txrxgain { + u16 hpvga; + u16 lpf_biq1; + u16 lpf_biq0; + u16 lna2; + u16 lna1; + s8 txpwrindex; +}; + +#define NPHY_IPA_RXCAL_MAXGAININDEX (6 - 1) + +static const struct nphy_ipa_txrxgain nphy_ipa_rxcal_gaintbl_5GHz[] = { + {0, 0, 0, 0, 0, 100}, + {0, 0, 0, 0, 0, 50}, + {0, 0, 0, 0, 0, -1}, + {0, 0, 0, 3, 0, -1}, + {0, 0, 3, 3, 0, -1}, + {0, 2, 3, 3, 0, -1} +}; + +static const struct nphy_ipa_txrxgain nphy_ipa_rxcal_gaintbl_2GHz[] = { + {0, 0, 0, 0, 0, 128}, + {0, 0, 0, 0, 0, 70}, + {0, 0, 0, 0, 0, 20}, + {0, 0, 0, 3, 0, 20}, + {0, 0, 3, 3, 0, 20}, + {0, 2, 3, 3, 0, 20} +}; + +static const struct nphy_ipa_txrxgain nphy_ipa_rxcal_gaintbl_5GHz_rev7[] = { + {0, 0, 0, 0, 0, 100}, + {0, 0, 0, 0, 0, 50}, + {0, 0, 0, 0, 0, -1}, + {0, 0, 0, 3, 0, -1}, + {0, 0, 3, 3, 0, -1}, + {0, 0, 5, 3, 0, -1} +}; + +static const struct nphy_ipa_txrxgain nphy_ipa_rxcal_gaintbl_2GHz_rev7[] = { + {0, 0, 0, 0, 0, 10}, + {0, 0, 0, 1, 0, 10}, + {0, 0, 1, 2, 0, 10}, + {0, 0, 1, 3, 0, 10}, + {0, 0, 4, 3, 0, 10}, + {0, 0, 6, 3, 0, 10} +}; + +enum { + NPHY_RXCAL_GAIN_INIT = 0, + NPHY_RXCAL_GAIN_UP, + NPHY_RXCAL_GAIN_DOWN +}; + +#define wlc_phy_get_papd_nphy(pi) \ + (read_phy_reg((pi), 0x1e7) & \ + ((0x1 << 15) | \ + (0x1 << 14) | \ + (0x1 << 13))) + +static const u16 NPHY_IPA_REV4_txdigi_filtcoeffs[][NPHY_NUM_DIG_FILT_COEFFS] = { + {-377, 137, -407, 208, -1527, 956, 93, 186, 93, + 230, -44, 230, 201, -191, 201}, + {-77, 20, -98, 49, -93, 60, 56, 111, 56, 26, -5, + 26, 34, -32, 34}, + {-360, 164, -376, 164, -1533, 576, 308, -314, 308, + 121, -73, 121, 91, 124, 91}, + {-295, 200, -363, 142, -1391, 826, 151, 301, 151, + 151, 301, 151, 602, -752, 602}, + {-92, 58, -96, 49, -104, 44, 17, 35, 17, + 12, 25, 12, 13, 27, 13}, + {-375, 136, -399, 209, -1479, 949, 130, 260, 130, + 230, -44, 230, 201, -191, 201}, + {0xed9, 0xc8, 0xe95, 0x8e, 0xa91, 0x33a, 0x97, 0x12d, 0x97, + 0x97, 0x12d, 0x97, 0x25a, 0xd10, 0x25a} +}; + +struct chan_info_nphy_2055 { + u16 chan; + u16 freq; + uint unknown; + u8 RF_pll_ref; + u8 RF_rf_pll_mod1; + u8 RF_rf_pll_mod0; + u8 RF_vco_cap_tail; + u8 RF_vco_cal1; + u8 RF_vco_cal2; + u8 RF_pll_lf_c1; + u8 RF_pll_lf_r1; + u8 RF_pll_lf_c2; + u8 RF_lgbuf_cen_buf; + u8 RF_lgen_tune1; + u8 RF_lgen_tune2; + u8 RF_core1_lgbuf_a_tune; + u8 RF_core1_lgbuf_g_tune; + u8 RF_core1_rxrf_reg1; + u8 RF_core1_tx_pga_pad_tn; + u8 RF_core1_tx_mx_bgtrim; + u8 RF_core2_lgbuf_a_tune; + u8 RF_core2_lgbuf_g_tune; + u8 RF_core2_rxrf_reg1; + u8 RF_core2_tx_pga_pad_tn; + u8 RF_core2_tx_mx_bgtrim; + u16 PHY_BW1a; + u16 PHY_BW2; + u16 PHY_BW3; + u16 PHY_BW4; + u16 PHY_BW5; + u16 PHY_BW6; +}; + +struct chan_info_nphy_radio205x { + u16 chan; + u16 freq; + u8 RF_SYN_pll_vcocal1; + u8 RF_SYN_pll_vcocal2; + u8 RF_SYN_pll_refdiv; + u8 RF_SYN_pll_mmd2; + u8 RF_SYN_pll_mmd1; + u8 RF_SYN_pll_loopfilter1; + u8 RF_SYN_pll_loopfilter2; + u8 RF_SYN_pll_loopfilter3; + u8 RF_SYN_pll_loopfilter4; + u8 RF_SYN_pll_loopfilter5; + u8 RF_SYN_reserved_addr27; + u8 RF_SYN_reserved_addr28; + u8 RF_SYN_reserved_addr29; + u8 RF_SYN_logen_VCOBUF1; + u8 RF_SYN_logen_MIXER2; + u8 RF_SYN_logen_BUF3; + u8 RF_SYN_logen_BUF4; + u8 RF_RX0_lnaa_tune; + u8 RF_RX0_lnag_tune; + u8 RF_TX0_intpaa_boost_tune; + u8 RF_TX0_intpag_boost_tune; + u8 RF_TX0_pada_boost_tune; + u8 RF_TX0_padg_boost_tune; + u8 RF_TX0_pgaa_boost_tune; + u8 RF_TX0_pgag_boost_tune; + u8 RF_TX0_mixa_boost_tune; + u8 RF_TX0_mixg_boost_tune; + u8 RF_RX1_lnaa_tune; + u8 RF_RX1_lnag_tune; + u8 RF_TX1_intpaa_boost_tune; + u8 RF_TX1_intpag_boost_tune; + u8 RF_TX1_pada_boost_tune; + u8 RF_TX1_padg_boost_tune; + u8 RF_TX1_pgaa_boost_tune; + u8 RF_TX1_pgag_boost_tune; + u8 RF_TX1_mixa_boost_tune; + u8 RF_TX1_mixg_boost_tune; + u16 PHY_BW1a; + u16 PHY_BW2; + u16 PHY_BW3; + u16 PHY_BW4; + u16 PHY_BW5; + u16 PHY_BW6; +}; + +struct chan_info_nphy_radio2057 { + u16 chan; + u16 freq; + u8 RF_vcocal_countval0; + u8 RF_vcocal_countval1; + u8 RF_rfpll_refmaster_sparextalsize; + u8 RF_rfpll_loopfilter_r1; + u8 RF_rfpll_loopfilter_c2; + u8 RF_rfpll_loopfilter_c1; + u8 RF_cp_kpd_idac; + u8 RF_rfpll_mmd0; + u8 RF_rfpll_mmd1; + u8 RF_vcobuf_tune; + u8 RF_logen_mx2g_tune; + u8 RF_logen_mx5g_tune; + u8 RF_logen_indbuf2g_tune; + u8 RF_logen_indbuf5g_tune; + u8 RF_txmix2g_tune_boost_pu_core0; + u8 RF_pad2g_tune_pus_core0; + u8 RF_pga_boost_tune_core0; + u8 RF_txmix5g_boost_tune_core0; + u8 RF_pad5g_tune_misc_pus_core0; + u8 RF_lna2g_tune_core0; + u8 RF_lna5g_tune_core0; + u8 RF_txmix2g_tune_boost_pu_core1; + u8 RF_pad2g_tune_pus_core1; + u8 RF_pga_boost_tune_core1; + u8 RF_txmix5g_boost_tune_core1; + u8 RF_pad5g_tune_misc_pus_core1; + u8 RF_lna2g_tune_core1; + u8 RF_lna5g_tune_core1; + u16 PHY_BW1a; + u16 PHY_BW2; + u16 PHY_BW3; + u16 PHY_BW4; + u16 PHY_BW5; + u16 PHY_BW6; +}; + +struct chan_info_nphy_radio2057_rev5 { + u16 chan; + u16 freq; + u8 RF_vcocal_countval0; + u8 RF_vcocal_countval1; + u8 RF_rfpll_refmaster_sparextalsize; + u8 RF_rfpll_loopfilter_r1; + u8 RF_rfpll_loopfilter_c2; + u8 RF_rfpll_loopfilter_c1; + u8 RF_cp_kpd_idac; + u8 RF_rfpll_mmd0; + u8 RF_rfpll_mmd1; + u8 RF_vcobuf_tune; + u8 RF_logen_mx2g_tune; + u8 RF_logen_indbuf2g_tune; + u8 RF_txmix2g_tune_boost_pu_core0; + u8 RF_pad2g_tune_pus_core0; + u8 RF_lna2g_tune_core0; + u8 RF_txmix2g_tune_boost_pu_core1; + u8 RF_pad2g_tune_pus_core1; + u8 RF_lna2g_tune_core1; + u16 PHY_BW1a; + u16 PHY_BW2; + u16 PHY_BW3; + u16 PHY_BW4; + u16 PHY_BW5; + u16 PHY_BW6; +}; + +struct nphy_sfo_cfg { + u16 PHY_BW1a; + u16 PHY_BW2; + u16 PHY_BW3; + u16 PHY_BW4; + u16 PHY_BW5; + u16 PHY_BW6; +}; + +static const struct chan_info_nphy_2055 chan_info_nphy_2055[] = { + { + 184, 4920, 3280, 0x71, 0x01, 0xEC, 0x0F, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0x0F, 0x8F, 0xFF, 0x00, 0x0F, + 0x0F, 0x8F, 0x7B4, 0x7B0, 0x7AC, 0x214, 0x215, 0x216}, + { + 186, 4930, 3287, 0x71, 0x01, 0xED, 0x0F, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0x0F, 0x8F, 0xFF, 0x00, 0x0F, + 0x0F, 0x8F, 0x7B8, 0x7B4, 0x7B0, 0x213, 0x214, 0x215}, + { + 188, 4940, 3293, 0x71, 0x01, 0xEE, 0x0F, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xEE, 0xEE, 0xFF, 0x00, 0x0F, 0x0F, 0x8F, 0xFF, 0x00, 0x0F, + 0x0F, 0x8F, 0x7BC, 0x7B8, 0x7B4, 0x212, 0x213, 0x214}, + { + 190, 4950, 3300, 0x71, 0x01, 0xEF, 0x0F, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xEE, 0xEE, 0xFF, 0x00, 0x0F, 0x0F, 0x8F, 0xFF, 0x00, 0x0F, + 0x0F, 0x8F, 0x7C0, 0x7BC, 0x7B8, 0x211, 0x212, 0x213}, + { + 192, 4960, 3307, 0x71, 0x01, 0xF0, 0x0F, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xEE, 0xEE, 0xFF, 0x00, 0x0F, 0x0F, 0x8F, 0xFF, 0x00, 0x0F, + 0x0F, 0x8F, 0x7C4, 0x7C0, 0x7BC, 0x20F, 0x211, 0x212}, + { + 194, 4970, 3313, 0x71, 0x01, 0xF1, 0x0F, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xEE, 0xEE, 0xFF, 0x00, 0x0F, 0x0F, 0x8F, 0xFF, 0x00, 0x0F, + 0x0F, 0x8F, 0x7C8, 0x7C4, 0x7C0, 0x20E, 0x20F, 0x211}, + { + 196, 4980, 3320, 0x71, 0x01, 0xF2, 0x0E, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xDD, 0xDD, 0xFF, 0x00, 0x0F, 0x0F, 0x8F, 0xFF, 0x00, 0x0F, + 0x0F, 0x8F, 0x7CC, 0x7C8, 0x7C4, 0x20D, 0x20E, 0x20F}, + { + 198, 4990, 3327, 0x71, 0x01, 0xF3, 0x0E, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xDD, 0xDD, 0xFF, 0x00, 0x0F, 0x0F, 0x8F, 0xFF, 0x00, 0x0F, + 0x0F, 0x8F, 0x7D0, 0x7CC, 0x7C8, 0x20C, 0x20D, 0x20E}, + { + 200, 5000, 3333, 0x71, 0x01, 0xF4, 0x0E, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xDD, 0xDD, 0xFF, 0x00, 0x0F, 0x0F, 0x8F, 0xFF, 0x00, 0x0F, + 0x0F, 0x8F, 0x7D4, 0x7D0, 0x7CC, 0x20B, 0x20C, 0x20D}, + { + 202, 5010, 3340, 0x71, 0x01, 0xF5, 0x0E, 0xFF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xDD, 0xDD, 0xFF, 0x00, 0x0F, 0x0F, 0x8F, 0xFF, 0x00, 0x0F, + 0x0F, 0x8F, 0x7D8, 0x7D4, 0x7D0, 0x20A, 0x20B, 0x20C}, + { + 204, 5020, 3347, 0x71, 0x01, 0xF6, 0x0E, 0xF7, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xCC, 0xCC, 0xFF, 0x00, 0x0F, 0x0F, 0x8F, 0xFF, 0x00, 0x0F, + 0x0F, 0x8F, 0x7DC, 0x7D8, 0x7D4, 0x209, 0x20A, 0x20B}, + { + 206, 5030, 3353, 0x71, 0x01, 0xF7, 0x0E, 0xF7, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xCC, 0xCC, 0xFF, 0x00, 0x0F, 0x0F, 0x8F, 0xFF, 0x00, 0x0F, + 0x0F, 0x8F, 0x7E0, 0x7DC, 0x7D8, 0x208, 0x209, 0x20A}, + { + 208, 5040, 3360, 0x71, 0x01, 0xF8, 0x0D, 0xEF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xCC, 0xCC, 0xFF, 0x00, 0x0F, 0x0F, 0x8F, 0xFF, 0x00, 0x0F, + 0x0F, 0x8F, 0x7E4, 0x7E0, 0x7DC, 0x207, 0x208, 0x209}, + { + 210, 5050, 3367, 0x71, 0x01, 0xF9, 0x0D, 0xEF, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xCC, 0xCC, 0xFF, 0x00, 0x0F, 0x0F, 0x8F, 0xFF, 0x00, 0x0F, + 0x0F, 0x8F, 0x7E8, 0x7E4, 0x7E0, 0x206, 0x207, 0x208}, + { + 212, 5060, 3373, 0x71, 0x01, 0xFA, 0x0D, 0xE6, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xBB, 0xBB, 0xFF, 0x00, 0x0E, 0x0F, 0x8E, 0xFF, 0x00, 0x0E, + 0x0F, 0x8E, 0x7EC, 0x7E8, 0x7E4, 0x205, 0x206, 0x207}, + { + 214, 5070, 3380, 0x71, 0x01, 0xFB, 0x0D, 0xE6, 0x01, 0x04, 0x0A, + 0x00, 0x8F, 0xBB, 0xBB, 0xFF, 0x00, 0x0E, 0x0F, 0x8E, 0xFF, 0x00, 0x0E, + 0x0F, 0x8E, 0x7F0, 0x7EC, 0x7E8, 0x204, 0x205, 0x206}, + { + 216, 5080, 3387, 0x71, 0x01, 0xFC, 0x0D, 0xDE, 0x01, 0x04, 0x0A, + 0x00, 0x8E, 0xBB, 0xBB, 0xEE, 0x00, 0x0E, 0x0F, 0x8D, 0xEE, 0x00, 0x0E, + 0x0F, 0x8D, 0x7F4, 0x7F0, 0x7EC, 0x203, 0x204, 0x205}, + { + 218, 5090, 3393, 0x71, 0x01, 0xFD, 0x0D, 0xDE, 0x01, 0x04, 0x0A, + 0x00, 0x8E, 0xBB, 0xBB, 0xEE, 0x00, 0x0E, 0x0F, 0x8D, 0xEE, 0x00, 0x0E, + 0x0F, 0x8D, 0x7F8, 0x7F4, 0x7F0, 0x202, 0x203, 0x204}, + { + 220, 5100, 3400, 0x71, 0x01, 0xFE, 0x0C, 0xD6, 0x01, 0x04, 0x0A, + 0x00, 0x8E, 0xAA, 0xAA, 0xEE, 0x00, 0x0D, 0x0F, 0x8D, 0xEE, 0x00, 0x0D, + 0x0F, 0x8D, 0x7FC, 0x7F8, 0x7F4, 0x201, 0x202, 0x203}, + { + 222, 5110, 3407, 0x71, 0x01, 0xFF, 0x0C, 0xD6, 0x01, 0x04, 0x0A, + 0x00, 0x8E, 0xAA, 0xAA, 0xEE, 0x00, 0x0D, 0x0F, 0x8D, 0xEE, 0x00, 0x0D, + 0x0F, 0x8D, 0x800, 0x7FC, 0x7F8, 0x200, 0x201, 0x202}, + { + 224, 5120, 3413, 0x71, 0x02, 0x00, 0x0C, 0xCE, 0x01, 0x04, 0x0A, + 0x00, 0x8D, 0xAA, 0xAA, 0xDD, 0x00, 0x0D, 0x0F, 0x8C, 0xDD, 0x00, 0x0D, + 0x0F, 0x8C, 0x804, 0x800, 0x7FC, 0x1FF, 0x200, 0x201}, + { + 226, 5130, 3420, 0x71, 0x02, 0x01, 0x0C, 0xCE, 0x01, 0x04, 0x0A, + 0x00, 0x8D, 0xAA, 0xAA, 0xDD, 0x00, 0x0D, 0x0F, 0x8C, 0xDD, 0x00, 0x0D, + 0x0F, 0x8C, 0x808, 0x804, 0x800, 0x1FE, 0x1FF, 0x200}, + { + 228, 5140, 3427, 0x71, 0x02, 0x02, 0x0C, 0xC6, 0x01, 0x04, 0x0A, + 0x00, 0x8D, 0x99, 0x99, 0xDD, 0x00, 0x0C, 0x0E, 0x8B, 0xDD, 0x00, 0x0C, + 0x0E, 0x8B, 0x80C, 0x808, 0x804, 0x1FD, 0x1FE, 0x1FF}, + { + 32, 5160, 3440, 0x71, 0x02, 0x04, 0x0B, 0xBE, 0x01, 0x04, 0x0A, + 0x00, 0x8C, 0x99, 0x99, 0xCC, 0x00, 0x0B, 0x0D, 0x8A, 0xCC, 0x00, 0x0B, + 0x0D, 0x8A, 0x814, 0x810, 0x80C, 0x1FB, 0x1FC, 0x1FD}, + { + 34, 5170, 3447, 0x71, 0x02, 0x05, 0x0B, 0xBE, 0x01, 0x04, 0x0A, + 0x00, 0x8C, 0x99, 0x99, 0xCC, 0x00, 0x0B, 0x0D, 0x8A, 0xCC, 0x00, 0x0B, + 0x0D, 0x8A, 0x818, 0x814, 0x810, 0x1FA, 0x1FB, 0x1FC}, + { + 36, 5180, 3453, 0x71, 0x02, 0x06, 0x0B, 0xB6, 0x01, 0x04, 0x0A, + 0x00, 0x8C, 0x88, 0x88, 0xCC, 0x00, 0x0B, 0x0C, 0x89, 0xCC, 0x00, 0x0B, + 0x0C, 0x89, 0x81C, 0x818, 0x814, 0x1F9, 0x1FA, 0x1FB}, + { + 38, 5190, 3460, 0x71, 0x02, 0x07, 0x0B, 0xB6, 0x01, 0x04, 0x0A, + 0x00, 0x8C, 0x88, 0x88, 0xCC, 0x00, 0x0B, 0x0C, 0x89, 0xCC, 0x00, 0x0B, + 0x0C, 0x89, 0x820, 0x81C, 0x818, 0x1F8, 0x1F9, 0x1FA}, + { + 40, 5200, 3467, 0x71, 0x02, 0x08, 0x0B, 0xAF, 0x01, 0x04, 0x0A, + 0x00, 0x8B, 0x88, 0x88, 0xBB, 0x00, 0x0A, 0x0B, 0x89, 0xBB, 0x00, 0x0A, + 0x0B, 0x89, 0x824, 0x820, 0x81C, 0x1F7, 0x1F8, 0x1F9}, + { + 42, 5210, 3473, 0x71, 0x02, 0x09, 0x0B, 0xAF, 0x01, 0x04, 0x0A, + 0x00, 0x8B, 0x88, 0x88, 0xBB, 0x00, 0x0A, 0x0B, 0x89, 0xBB, 0x00, 0x0A, + 0x0B, 0x89, 0x828, 0x824, 0x820, 0x1F6, 0x1F7, 0x1F8}, + { + 44, 5220, 3480, 0x71, 0x02, 0x0A, 0x0A, 0xA7, 0x01, 0x04, 0x0A, + 0x00, 0x8B, 0x77, 0x77, 0xBB, 0x00, 0x09, 0x0A, 0x88, 0xBB, 0x00, 0x09, + 0x0A, 0x88, 0x82C, 0x828, 0x824, 0x1F5, 0x1F6, 0x1F7}, + { + 46, 5230, 3487, 0x71, 0x02, 0x0B, 0x0A, 0xA7, 0x01, 0x04, 0x0A, + 0x00, 0x8B, 0x77, 0x77, 0xBB, 0x00, 0x09, 0x0A, 0x88, 0xBB, 0x00, 0x09, + 0x0A, 0x88, 0x830, 0x82C, 0x828, 0x1F4, 0x1F5, 0x1F6}, + { + 48, 5240, 3493, 0x71, 0x02, 0x0C, 0x0A, 0xA0, 0x01, 0x04, 0x0A, + 0x00, 0x8A, 0x77, 0x77, 0xAA, 0x00, 0x09, 0x0A, 0x87, 0xAA, 0x00, 0x09, + 0x0A, 0x87, 0x834, 0x830, 0x82C, 0x1F3, 0x1F4, 0x1F5}, + { + 50, 5250, 3500, 0x71, 0x02, 0x0D, 0x0A, 0xA0, 0x01, 0x04, 0x0A, + 0x00, 0x8A, 0x77, 0x77, 0xAA, 0x00, 0x09, 0x0A, 0x87, 0xAA, 0x00, 0x09, + 0x0A, 0x87, 0x838, 0x834, 0x830, 0x1F2, 0x1F3, 0x1F4}, + { + 52, 5260, 3507, 0x71, 0x02, 0x0E, 0x0A, 0x98, 0x01, 0x04, 0x0A, + 0x00, 0x8A, 0x66, 0x66, 0xAA, 0x00, 0x08, 0x09, 0x87, 0xAA, 0x00, 0x08, + 0x09, 0x87, 0x83C, 0x838, 0x834, 0x1F1, 0x1F2, 0x1F3}, + { + 54, 5270, 3513, 0x71, 0x02, 0x0F, 0x0A, 0x98, 0x01, 0x04, 0x0A, + 0x00, 0x8A, 0x66, 0x66, 0xAA, 0x00, 0x08, 0x09, 0x87, 0xAA, 0x00, 0x08, + 0x09, 0x87, 0x840, 0x83C, 0x838, 0x1F0, 0x1F1, 0x1F2}, + { + 56, 5280, 3520, 0x71, 0x02, 0x10, 0x09, 0x91, 0x01, 0x04, 0x0A, + 0x00, 0x89, 0x66, 0x66, 0x99, 0x00, 0x08, 0x08, 0x86, 0x99, 0x00, 0x08, + 0x08, 0x86, 0x844, 0x840, 0x83C, 0x1F0, 0x1F0, 0x1F1}, + { + 58, 5290, 3527, 0x71, 0x02, 0x11, 0x09, 0x91, 0x01, 0x04, 0x0A, + 0x00, 0x89, 0x66, 0x66, 0x99, 0x00, 0x08, 0x08, 0x86, 0x99, 0x00, 0x08, + 0x08, 0x86, 0x848, 0x844, 0x840, 0x1EF, 0x1F0, 0x1F0}, + { + 60, 5300, 3533, 0x71, 0x02, 0x12, 0x09, 0x8A, 0x01, 0x04, 0x0A, + 0x00, 0x89, 0x55, 0x55, 0x99, 0x00, 0x08, 0x07, 0x85, 0x99, 0x00, 0x08, + 0x07, 0x85, 0x84C, 0x848, 0x844, 0x1EE, 0x1EF, 0x1F0}, + { + 62, 5310, 3540, 0x71, 0x02, 0x13, 0x09, 0x8A, 0x01, 0x04, 0x0A, + 0x00, 0x89, 0x55, 0x55, 0x99, 0x00, 0x08, 0x07, 0x85, 0x99, 0x00, 0x08, + 0x07, 0x85, 0x850, 0x84C, 0x848, 0x1ED, 0x1EE, 0x1EF}, + { + 64, 5320, 3547, 0x71, 0x02, 0x14, 0x09, 0x83, 0x01, 0x04, 0x0A, + 0x00, 0x88, 0x55, 0x55, 0x88, 0x00, 0x07, 0x07, 0x84, 0x88, 0x00, 0x07, + 0x07, 0x84, 0x854, 0x850, 0x84C, 0x1EC, 0x1ED, 0x1EE}, + { + 66, 5330, 3553, 0x71, 0x02, 0x15, 0x09, 0x83, 0x01, 0x04, 0x0A, + 0x00, 0x88, 0x55, 0x55, 0x88, 0x00, 0x07, 0x07, 0x84, 0x88, 0x00, 0x07, + 0x07, 0x84, 0x858, 0x854, 0x850, 0x1EB, 0x1EC, 0x1ED}, + { + 68, 5340, 3560, 0x71, 0x02, 0x16, 0x08, 0x7C, 0x01, 0x04, 0x0A, + 0x00, 0x88, 0x44, 0x44, 0x88, 0x00, 0x07, 0x06, 0x84, 0x88, 0x00, 0x07, + 0x06, 0x84, 0x85C, 0x858, 0x854, 0x1EA, 0x1EB, 0x1EC}, + { + 70, 5350, 3567, 0x71, 0x02, 0x17, 0x08, 0x7C, 0x01, 0x04, 0x0A, + 0x00, 0x88, 0x44, 0x44, 0x88, 0x00, 0x07, 0x06, 0x84, 0x88, 0x00, 0x07, + 0x06, 0x84, 0x860, 0x85C, 0x858, 0x1E9, 0x1EA, 0x1EB}, + { + 72, 5360, 3573, 0x71, 0x02, 0x18, 0x08, 0x75, 0x01, 0x04, 0x0A, + 0x00, 0x87, 0x44, 0x44, 0x77, 0x00, 0x06, 0x05, 0x83, 0x77, 0x00, 0x06, + 0x05, 0x83, 0x864, 0x860, 0x85C, 0x1E8, 0x1E9, 0x1EA}, + { + 74, 5370, 3580, 0x71, 0x02, 0x19, 0x08, 0x75, 0x01, 0x04, 0x0A, + 0x00, 0x87, 0x44, 0x44, 0x77, 0x00, 0x06, 0x05, 0x83, 0x77, 0x00, 0x06, + 0x05, 0x83, 0x868, 0x864, 0x860, 0x1E7, 0x1E8, 0x1E9}, + { + 76, 5380, 3587, 0x71, 0x02, 0x1A, 0x08, 0x6E, 0x01, 0x04, 0x0A, + 0x00, 0x87, 0x33, 0x33, 0x77, 0x00, 0x06, 0x04, 0x82, 0x77, 0x00, 0x06, + 0x04, 0x82, 0x86C, 0x868, 0x864, 0x1E6, 0x1E7, 0x1E8}, + { + 78, 5390, 3593, 0x71, 0x02, 0x1B, 0x08, 0x6E, 0x01, 0x04, 0x0A, + 0x00, 0x87, 0x33, 0x33, 0x77, 0x00, 0x06, 0x04, 0x82, 0x77, 0x00, 0x06, + 0x04, 0x82, 0x870, 0x86C, 0x868, 0x1E5, 0x1E6, 0x1E7}, + { + 80, 5400, 3600, 0x71, 0x02, 0x1C, 0x07, 0x67, 0x01, 0x04, 0x0A, + 0x00, 0x86, 0x33, 0x33, 0x66, 0x00, 0x05, 0x04, 0x81, 0x66, 0x00, 0x05, + 0x04, 0x81, 0x874, 0x870, 0x86C, 0x1E5, 0x1E5, 0x1E6}, + { + 82, 5410, 3607, 0x71, 0x02, 0x1D, 0x07, 0x67, 0x01, 0x04, 0x0A, + 0x00, 0x86, 0x33, 0x33, 0x66, 0x00, 0x05, 0x04, 0x81, 0x66, 0x00, 0x05, + 0x04, 0x81, 0x878, 0x874, 0x870, 0x1E4, 0x1E5, 0x1E5}, + { + 84, 5420, 3613, 0x71, 0x02, 0x1E, 0x07, 0x61, 0x01, 0x04, 0x0A, + 0x00, 0x86, 0x22, 0x22, 0x66, 0x00, 0x05, 0x03, 0x80, 0x66, 0x00, 0x05, + 0x03, 0x80, 0x87C, 0x878, 0x874, 0x1E3, 0x1E4, 0x1E5}, + { + 86, 5430, 3620, 0x71, 0x02, 0x1F, 0x07, 0x61, 0x01, 0x04, 0x0A, + 0x00, 0x86, 0x22, 0x22, 0x66, 0x00, 0x05, 0x03, 0x80, 0x66, 0x00, 0x05, + 0x03, 0x80, 0x880, 0x87C, 0x878, 0x1E2, 0x1E3, 0x1E4}, + { + 88, 5440, 3627, 0x71, 0x02, 0x20, 0x07, 0x5A, 0x01, 0x04, 0x0A, + 0x00, 0x85, 0x22, 0x22, 0x55, 0x00, 0x04, 0x02, 0x80, 0x55, 0x00, 0x04, + 0x02, 0x80, 0x884, 0x880, 0x87C, 0x1E1, 0x1E2, 0x1E3}, + { + 90, 5450, 3633, 0x71, 0x02, 0x21, 0x07, 0x5A, 0x01, 0x04, 0x0A, + 0x00, 0x85, 0x22, 0x22, 0x55, 0x00, 0x04, 0x02, 0x80, 0x55, 0x00, 0x04, + 0x02, 0x80, 0x888, 0x884, 0x880, 0x1E0, 0x1E1, 0x1E2}, + { + 92, 5460, 3640, 0x71, 0x02, 0x22, 0x06, 0x53, 0x01, 0x04, 0x0A, + 0x00, 0x85, 0x11, 0x11, 0x55, 0x00, 0x04, 0x01, 0x80, 0x55, 0x00, 0x04, + 0x01, 0x80, 0x88C, 0x888, 0x884, 0x1DF, 0x1E0, 0x1E1}, + { + 94, 5470, 3647, 0x71, 0x02, 0x23, 0x06, 0x53, 0x01, 0x04, 0x0A, + 0x00, 0x85, 0x11, 0x11, 0x55, 0x00, 0x04, 0x01, 0x80, 0x55, 0x00, 0x04, + 0x01, 0x80, 0x890, 0x88C, 0x888, 0x1DE, 0x1DF, 0x1E0}, + { + 96, 5480, 3653, 0x71, 0x02, 0x24, 0x06, 0x4D, 0x01, 0x04, 0x0A, + 0x00, 0x84, 0x11, 0x11, 0x44, 0x00, 0x03, 0x00, 0x80, 0x44, 0x00, 0x03, + 0x00, 0x80, 0x894, 0x890, 0x88C, 0x1DD, 0x1DE, 0x1DF}, + { + 98, 5490, 3660, 0x71, 0x02, 0x25, 0x06, 0x4D, 0x01, 0x04, 0x0A, + 0x00, 0x84, 0x11, 0x11, 0x44, 0x00, 0x03, 0x00, 0x80, 0x44, 0x00, 0x03, + 0x00, 0x80, 0x898, 0x894, 0x890, 0x1DD, 0x1DD, 0x1DE}, + { + 100, 5500, 3667, 0x71, 0x02, 0x26, 0x06, 0x47, 0x01, 0x04, 0x0A, + 0x00, 0x84, 0x00, 0x00, 0x44, 0x00, 0x03, 0x00, 0x80, 0x44, 0x00, 0x03, + 0x00, 0x80, 0x89C, 0x898, 0x894, 0x1DC, 0x1DD, 0x1DD}, + { + 102, 5510, 3673, 0x71, 0x02, 0x27, 0x06, 0x47, 0x01, 0x04, 0x0A, + 0x00, 0x84, 0x00, 0x00, 0x44, 0x00, 0x03, 0x00, 0x80, 0x44, 0x00, 0x03, + 0x00, 0x80, 0x8A0, 0x89C, 0x898, 0x1DB, 0x1DC, 0x1DD}, + { + 104, 5520, 3680, 0x71, 0x02, 0x28, 0x05, 0x40, 0x01, 0x04, 0x0A, + 0x00, 0x83, 0x00, 0x00, 0x33, 0x00, 0x02, 0x00, 0x80, 0x33, 0x00, 0x02, + 0x00, 0x80, 0x8A4, 0x8A0, 0x89C, 0x1DA, 0x1DB, 0x1DC}, + { + 106, 5530, 3687, 0x71, 0x02, 0x29, 0x05, 0x40, 0x01, 0x04, 0x0A, + 0x00, 0x83, 0x00, 0x00, 0x33, 0x00, 0x02, 0x00, 0x80, 0x33, 0x00, 0x02, + 0x00, 0x80, 0x8A8, 0x8A4, 0x8A0, 0x1D9, 0x1DA, 0x1DB}, + { + 108, 5540, 3693, 0x71, 0x02, 0x2A, 0x05, 0x3A, 0x01, 0x04, 0x0A, + 0x00, 0x83, 0x00, 0x00, 0x33, 0x00, 0x02, 0x00, 0x80, 0x33, 0x00, 0x02, + 0x00, 0x80, 0x8AC, 0x8A8, 0x8A4, 0x1D8, 0x1D9, 0x1DA}, + { + 110, 5550, 3700, 0x71, 0x02, 0x2B, 0x05, 0x3A, 0x01, 0x04, 0x0A, + 0x00, 0x83, 0x00, 0x00, 0x33, 0x00, 0x02, 0x00, 0x80, 0x33, 0x00, 0x02, + 0x00, 0x80, 0x8B0, 0x8AC, 0x8A8, 0x1D7, 0x1D8, 0x1D9}, + { + 112, 5560, 3707, 0x71, 0x02, 0x2C, 0x05, 0x34, 0x01, 0x04, 0x0A, + 0x00, 0x82, 0x00, 0x00, 0x22, 0x00, 0x01, 0x00, 0x80, 0x22, 0x00, 0x01, + 0x00, 0x80, 0x8B4, 0x8B0, 0x8AC, 0x1D7, 0x1D7, 0x1D8}, + { + 114, 5570, 3713, 0x71, 0x02, 0x2D, 0x05, 0x34, 0x01, 0x04, 0x0A, + 0x00, 0x82, 0x00, 0x00, 0x22, 0x00, 0x01, 0x00, 0x80, 0x22, 0x00, 0x01, + 0x00, 0x80, 0x8B8, 0x8B4, 0x8B0, 0x1D6, 0x1D7, 0x1D7}, + { + 116, 5580, 3720, 0x71, 0x02, 0x2E, 0x04, 0x2E, 0x01, 0x04, 0x0A, + 0x00, 0x82, 0x00, 0x00, 0x22, 0x00, 0x01, 0x00, 0x80, 0x22, 0x00, 0x01, + 0x00, 0x80, 0x8BC, 0x8B8, 0x8B4, 0x1D5, 0x1D6, 0x1D7}, + { + 118, 5590, 3727, 0x71, 0x02, 0x2F, 0x04, 0x2E, 0x01, 0x04, 0x0A, + 0x00, 0x82, 0x00, 0x00, 0x22, 0x00, 0x01, 0x00, 0x80, 0x22, 0x00, 0x01, + 0x00, 0x80, 0x8C0, 0x8BC, 0x8B8, 0x1D4, 0x1D5, 0x1D6}, + { + 120, 5600, 3733, 0x71, 0x02, 0x30, 0x04, 0x28, 0x01, 0x04, 0x0A, + 0x00, 0x81, 0x00, 0x00, 0x11, 0x00, 0x01, 0x00, 0x80, 0x11, 0x00, 0x01, + 0x00, 0x80, 0x8C4, 0x8C0, 0x8BC, 0x1D3, 0x1D4, 0x1D5}, + { + 122, 5610, 3740, 0x71, 0x02, 0x31, 0x04, 0x28, 0x01, 0x04, 0x0A, + 0x00, 0x81, 0x00, 0x00, 0x11, 0x00, 0x01, 0x00, 0x80, 0x11, 0x00, 0x01, + 0x00, 0x80, 0x8C8, 0x8C4, 0x8C0, 0x1D2, 0x1D3, 0x1D4}, + { + 124, 5620, 3747, 0x71, 0x02, 0x32, 0x04, 0x21, 0x01, 0x04, 0x0A, + 0x00, 0x81, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x80, 0x11, 0x00, 0x00, + 0x00, 0x80, 0x8CC, 0x8C8, 0x8C4, 0x1D2, 0x1D2, 0x1D3}, + { + 126, 5630, 3753, 0x71, 0x02, 0x33, 0x04, 0x21, 0x01, 0x04, 0x0A, + 0x00, 0x81, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x80, 0x11, 0x00, 0x00, + 0x00, 0x80, 0x8D0, 0x8CC, 0x8C8, 0x1D1, 0x1D2, 0x1D2}, + { + 128, 5640, 3760, 0x71, 0x02, 0x34, 0x03, 0x1C, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x8D4, 0x8D0, 0x8CC, 0x1D0, 0x1D1, 0x1D2}, + { + 130, 5650, 3767, 0x71, 0x02, 0x35, 0x03, 0x1C, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x8D8, 0x8D4, 0x8D0, 0x1CF, 0x1D0, 0x1D1}, + { + 132, 5660, 3773, 0x71, 0x02, 0x36, 0x03, 0x16, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x8DC, 0x8D8, 0x8D4, 0x1CE, 0x1CF, 0x1D0}, + { + 134, 5670, 3780, 0x71, 0x02, 0x37, 0x03, 0x16, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x8E0, 0x8DC, 0x8D8, 0x1CE, 0x1CE, 0x1CF}, + { + 136, 5680, 3787, 0x71, 0x02, 0x38, 0x03, 0x10, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x8E4, 0x8E0, 0x8DC, 0x1CD, 0x1CE, 0x1CE}, + { + 138, 5690, 3793, 0x71, 0x02, 0x39, 0x03, 0x10, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x8E8, 0x8E4, 0x8E0, 0x1CC, 0x1CD, 0x1CE}, + { + 140, 5700, 3800, 0x71, 0x02, 0x3A, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x8EC, 0x8E8, 0x8E4, 0x1CB, 0x1CC, 0x1CD}, + { + 142, 5710, 3807, 0x71, 0x02, 0x3B, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x8F0, 0x8EC, 0x8E8, 0x1CA, 0x1CB, 0x1CC}, + { + 144, 5720, 3813, 0x71, 0x02, 0x3C, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x8F4, 0x8F0, 0x8EC, 0x1C9, 0x1CA, 0x1CB}, + { + 145, 5725, 3817, 0x72, 0x04, 0x79, 0x02, 0x03, 0x01, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x8F6, 0x8F2, 0x8EE, 0x1C9, 0x1CA, 0x1CB}, + { + 146, 5730, 3820, 0x71, 0x02, 0x3D, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x8F8, 0x8F4, 0x8F0, 0x1C9, 0x1C9, 0x1CA}, + { + 147, 5735, 3823, 0x72, 0x04, 0x7B, 0x02, 0x03, 0x01, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x8FA, 0x8F6, 0x8F2, 0x1C8, 0x1C9, 0x1CA}, + { + 148, 5740, 3827, 0x71, 0x02, 0x3E, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x8FC, 0x8F8, 0x8F4, 0x1C8, 0x1C9, 0x1C9}, + { + 149, 5745, 3830, 0x72, 0x04, 0x7D, 0x02, 0xFE, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x8FE, 0x8FA, 0x8F6, 0x1C8, 0x1C8, 0x1C9}, + { + 150, 5750, 3833, 0x71, 0x02, 0x3F, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x900, 0x8FC, 0x8F8, 0x1C7, 0x1C8, 0x1C9}, + { + 151, 5755, 3837, 0x72, 0x04, 0x7F, 0x02, 0xFE, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x902, 0x8FE, 0x8FA, 0x1C7, 0x1C8, 0x1C8}, + { + 152, 5760, 3840, 0x71, 0x02, 0x40, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x904, 0x900, 0x8FC, 0x1C6, 0x1C7, 0x1C8}, + { + 153, 5765, 3843, 0x72, 0x04, 0x81, 0x02, 0xF8, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x906, 0x902, 0x8FE, 0x1C6, 0x1C7, 0x1C8}, + { + 154, 5770, 3847, 0x71, 0x02, 0x41, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x908, 0x904, 0x900, 0x1C6, 0x1C6, 0x1C7}, + { + 155, 5775, 3850, 0x72, 0x04, 0x83, 0x02, 0xF8, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x90A, 0x906, 0x902, 0x1C5, 0x1C6, 0x1C7}, + { + 156, 5780, 3853, 0x71, 0x02, 0x42, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x90C, 0x908, 0x904, 0x1C5, 0x1C6, 0x1C6}, + { + 157, 5785, 3857, 0x72, 0x04, 0x85, 0x02, 0xF2, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x90E, 0x90A, 0x906, 0x1C4, 0x1C5, 0x1C6}, + { + 158, 5790, 3860, 0x71, 0x02, 0x43, 0x02, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x910, 0x90C, 0x908, 0x1C4, 0x1C5, 0x1C6}, + { + 159, 5795, 3863, 0x72, 0x04, 0x87, 0x02, 0xF2, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x912, 0x90E, 0x90A, 0x1C4, 0x1C4, 0x1C5}, + { + 160, 5800, 3867, 0x71, 0x02, 0x44, 0x01, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x914, 0x910, 0x90C, 0x1C3, 0x1C4, 0x1C5}, + { + 161, 5805, 3870, 0x72, 0x04, 0x89, 0x01, 0xED, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x916, 0x912, 0x90E, 0x1C3, 0x1C4, 0x1C4}, + { + 162, 5810, 3873, 0x71, 0x02, 0x45, 0x01, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x918, 0x914, 0x910, 0x1C2, 0x1C3, 0x1C4}, + { + 163, 5815, 3877, 0x72, 0x04, 0x8B, 0x01, 0xED, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x91A, 0x916, 0x912, 0x1C2, 0x1C3, 0x1C4}, + { + 164, 5820, 3880, 0x71, 0x02, 0x46, 0x01, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x91C, 0x918, 0x914, 0x1C2, 0x1C2, 0x1C3}, + { + 165, 5825, 3883, 0x72, 0x04, 0x8D, 0x01, 0xED, 0x00, 0x03, 0x14, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x91E, 0x91A, 0x916, 0x1C1, 0x1C2, 0x1C3}, + { + 166, 5830, 3887, 0x71, 0x02, 0x47, 0x01, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x920, 0x91C, 0x918, 0x1C1, 0x1C2, 0x1C2}, + { + 168, 5840, 3893, 0x71, 0x02, 0x48, 0x01, 0x0A, 0x01, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x924, 0x920, 0x91C, 0x1C0, 0x1C1, 0x1C2}, + { + 170, 5850, 3900, 0x71, 0x02, 0x49, 0x01, 0xE0, 0x00, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x928, 0x924, 0x920, 0x1BF, 0x1C0, 0x1C1}, + { + 172, 5860, 3907, 0x71, 0x02, 0x4A, 0x01, 0xDE, 0x00, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x92C, 0x928, 0x924, 0x1BF, 0x1BF, 0x1C0}, + { + 174, 5870, 3913, 0x71, 0x02, 0x4B, 0x00, 0xDB, 0x00, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x930, 0x92C, 0x928, 0x1BE, 0x1BF, 0x1BF}, + { + 176, 5880, 3920, 0x71, 0x02, 0x4C, 0x00, 0xD8, 0x00, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x934, 0x930, 0x92C, 0x1BD, 0x1BE, 0x1BF}, + { + 178, 5890, 3927, 0x71, 0x02, 0x4D, 0x00, 0xD6, 0x00, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x938, 0x934, 0x930, 0x1BC, 0x1BD, 0x1BE}, + { + 180, 5900, 3933, 0x71, 0x02, 0x4E, 0x00, 0xD3, 0x00, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x93C, 0x938, 0x934, 0x1BC, 0x1BC, 0x1BD}, + { + 182, 5910, 3940, 0x71, 0x02, 0x4F, 0x00, 0xD6, 0x00, 0x04, 0x0A, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x940, 0x93C, 0x938, 0x1BB, 0x1BC, 0x1BC}, + { + 1, 2412, 3216, 0x73, 0x09, 0x6C, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x0D, 0x0C, 0x80, 0xFF, 0x88, 0x0D, + 0x0C, 0x80, 0x3C9, 0x3C5, 0x3C1, 0x43A, 0x43F, 0x443}, + { + 2, 2417, 3223, 0x73, 0x09, 0x71, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x0C, 0x0B, 0x80, 0xFF, 0x88, 0x0C, + 0x0B, 0x80, 0x3CB, 0x3C7, 0x3C3, 0x438, 0x43D, 0x441}, + { + 3, 2422, 3229, 0x73, 0x09, 0x76, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x0C, 0x0A, 0x80, 0xFF, 0x88, 0x0C, + 0x0A, 0x80, 0x3CD, 0x3C9, 0x3C5, 0x436, 0x43A, 0x43F}, + { + 4, 2427, 3236, 0x73, 0x09, 0x7B, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x0C, 0x0A, 0x80, 0xFF, 0x88, 0x0C, + 0x0A, 0x80, 0x3CF, 0x3CB, 0x3C7, 0x434, 0x438, 0x43D}, + { + 5, 2432, 3243, 0x73, 0x09, 0x80, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x0C, 0x09, 0x80, 0xFF, 0x88, 0x0C, + 0x09, 0x80, 0x3D1, 0x3CD, 0x3C9, 0x431, 0x436, 0x43A}, + { + 6, 2437, 3249, 0x73, 0x09, 0x85, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x0B, 0x08, 0x80, 0xFF, 0x88, 0x0B, + 0x08, 0x80, 0x3D3, 0x3CF, 0x3CB, 0x42F, 0x434, 0x438}, + { + 7, 2442, 3256, 0x73, 0x09, 0x8A, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x0A, 0x07, 0x80, 0xFF, 0x88, 0x0A, + 0x07, 0x80, 0x3D5, 0x3D1, 0x3CD, 0x42D, 0x431, 0x436}, + { + 8, 2447, 3263, 0x73, 0x09, 0x8F, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x0A, 0x06, 0x80, 0xFF, 0x88, 0x0A, + 0x06, 0x80, 0x3D7, 0x3D3, 0x3CF, 0x42B, 0x42F, 0x434}, + { + 9, 2452, 3269, 0x73, 0x09, 0x94, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x09, 0x06, 0x80, 0xFF, 0x88, 0x09, + 0x06, 0x80, 0x3D9, 0x3D5, 0x3D1, 0x429, 0x42D, 0x431}, + { + 10, 2457, 3276, 0x73, 0x09, 0x99, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x08, 0x05, 0x80, 0xFF, 0x88, 0x08, + 0x05, 0x80, 0x3DB, 0x3D7, 0x3D3, 0x427, 0x42B, 0x42F}, + { + 11, 2462, 3283, 0x73, 0x09, 0x9E, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x08, 0x04, 0x80, 0xFF, 0x88, 0x08, + 0x04, 0x80, 0x3DD, 0x3D9, 0x3D5, 0x424, 0x429, 0x42D}, + { + 12, 2467, 3289, 0x73, 0x09, 0xA3, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x08, 0x03, 0x80, 0xFF, 0x88, 0x08, + 0x03, 0x80, 0x3DF, 0x3DB, 0x3D7, 0x422, 0x427, 0x42B}, + { + 13, 2472, 3296, 0x73, 0x09, 0xA8, 0x0F, 0x00, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x07, 0x03, 0x80, 0xFF, 0x88, 0x07, + 0x03, 0x80, 0x3E1, 0x3DD, 0x3D9, 0x420, 0x424, 0x429}, + { + 14, 2484, 3312, 0x73, 0x09, 0xB4, 0x0F, 0xFF, 0x01, 0x07, 0x15, + 0x01, 0x8F, 0xFF, 0xFF, 0xFF, 0x88, 0x07, 0x01, 0x80, 0xFF, 0x88, 0x07, + 0x01, 0x80, 0x3E6, 0x3E2, 0x3DE, 0x41B, 0x41F, 0x424} +}; + +static const struct chan_info_nphy_radio205x chan_info_nphyrev3_2056[] = { + { + 184, 4920, 0xff, 0x01, 0x01, 0x01, 0xec, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07b4, 0x07b0, 0x07ac, 0x0214, 0x0215, 0x0216}, + { + 186, 4930, 0xff, 0x01, 0x01, 0x01, 0xed, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07b8, 0x07b4, 0x07b0, 0x0213, 0x0214, 0x0215}, + { + 188, 4940, 0xff, 0x01, 0x01, 0x01, 0xee, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07bc, 0x07b8, 0x07b4, 0x0212, 0x0213, 0x0214}, + { + 190, 4950, 0xff, 0x01, 0x01, 0x01, 0xef, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07c0, 0x07bc, 0x07b8, 0x0211, 0x0212, 0x0213}, + { + 192, 4960, 0xff, 0x01, 0x01, 0x01, 0xf0, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07c4, 0x07c0, 0x07bc, 0x020f, 0x0211, 0x0212}, + { + 194, 4970, 0xff, 0x01, 0x01, 0x01, 0xf1, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07c8, 0x07c4, 0x07c0, 0x020e, 0x020f, 0x0211}, + { + 196, 4980, 0xff, 0x01, 0x01, 0x01, 0xf2, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07cc, 0x07c8, 0x07c4, 0x020d, 0x020e, 0x020f}, + { + 198, 4990, 0xff, 0x01, 0x01, 0x01, 0xf3, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07d0, 0x07cc, 0x07c8, 0x020c, 0x020d, 0x020e}, + { + 200, 5000, 0xff, 0x01, 0x01, 0x01, 0xf4, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07d4, 0x07d0, 0x07cc, 0x020b, 0x020c, 0x020d}, + { + 202, 5010, 0xff, 0x01, 0x01, 0x01, 0xf5, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07d8, 0x07d4, 0x07d0, 0x020a, 0x020b, 0x020c}, + { + 204, 5020, 0xf7, 0x01, 0x01, 0x01, 0xf6, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07dc, 0x07d8, 0x07d4, 0x0209, 0x020a, 0x020b}, + { + 206, 5030, 0xf7, 0x01, 0x01, 0x01, 0xf7, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07e0, 0x07dc, 0x07d8, 0x0208, 0x0209, 0x020a}, + { + 208, 5040, 0xef, 0x01, 0x01, 0x01, 0xf8, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07e4, 0x07e0, 0x07dc, 0x0207, 0x0208, 0x0209}, + { + 210, 5050, 0xef, 0x01, 0x01, 0x01, 0xf9, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07e8, 0x07e4, 0x07e0, 0x0206, 0x0207, 0x0208}, + { + 212, 5060, 0xe6, 0x01, 0x01, 0x01, 0xfa, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07ec, 0x07e8, 0x07e4, 0x0205, 0x0206, 0x0207}, + { + 214, 5070, 0xe6, 0x01, 0x01, 0x01, 0xfb, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07f0, 0x07ec, 0x07e8, 0x0204, 0x0205, 0x0206}, + { + 216, 5080, 0xde, 0x01, 0x01, 0x01, 0xfc, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07f4, 0x07f0, 0x07ec, 0x0203, 0x0204, 0x0205}, + { + 218, 5090, 0xde, 0x01, 0x01, 0x01, 0xfd, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07f8, 0x07f4, 0x07f0, 0x0202, 0x0203, 0x0204}, + { + 220, 5100, 0xd6, 0x01, 0x01, 0x01, 0xfe, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xff, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xff, 0x00, 0x07fc, 0x07f8, 0x07f4, 0x0201, 0x0202, 0x0203}, + { + 222, 5110, 0xd6, 0x01, 0x01, 0x01, 0xff, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xfc, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xfc, 0x00, 0x0800, 0x07fc, 0x07f8, 0x0200, 0x0201, 0x0202}, + { + 224, 5120, 0xce, 0x01, 0x01, 0x02, 0x00, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xfc, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xfc, 0x00, 0x0804, 0x0800, 0x07fc, 0x01ff, 0x0200, 0x0201}, + { + 226, 5130, 0xce, 0x01, 0x01, 0x02, 0x01, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xfc, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xfc, 0x00, 0x0808, 0x0804, 0x0800, 0x01fe, 0x01ff, 0x0200}, + { + 228, 5140, 0xc6, 0x01, 0x01, 0x02, 0x02, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xfc, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xfc, 0x00, 0x080c, 0x0808, 0x0804, 0x01fd, 0x01fe, 0x01ff}, + { + 32, 5160, 0xbe, 0x01, 0x01, 0x02, 0x04, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xfc, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xfc, 0x00, 0x0814, 0x0810, 0x080c, 0x01fb, 0x01fc, 0x01fd}, + { + 34, 5170, 0xbe, 0x01, 0x01, 0x02, 0x05, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xfc, 0x00, 0xff, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xfc, 0x00, 0x0818, 0x0814, 0x0810, 0x01fa, 0x01fb, 0x01fc}, + { + 36, 5180, 0xb6, 0x01, 0x01, 0x02, 0x06, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xef, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xfc, 0x00, 0xef, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xfc, 0x00, 0x081c, 0x0818, 0x0814, 0x01f9, 0x01fa, 0x01fb}, + { + 38, 5190, 0xb6, 0x01, 0x01, 0x02, 0x07, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xef, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xfc, 0x00, 0xef, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xfc, 0x00, 0x0820, 0x081c, 0x0818, 0x01f8, 0x01f9, 0x01fa}, + { + 40, 5200, 0xaf, 0x01, 0x01, 0x02, 0x08, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xef, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00, 0xef, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0x0824, 0x0820, 0x081c, 0x01f7, 0x01f8, 0x01f9}, + { + 42, 5210, 0xaf, 0x01, 0x01, 0x02, 0x09, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xdf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00, 0xdf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0x0828, 0x0824, 0x0820, 0x01f6, 0x01f7, 0x01f8}, + { + 44, 5220, 0xa7, 0x01, 0x01, 0x02, 0x0a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xdf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00, 0xdf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0x082c, 0x0828, 0x0824, 0x01f5, 0x01f6, 0x01f7}, + { + 46, 5230, 0xa7, 0x01, 0x01, 0x02, 0x0b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xdf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00, 0xdf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0x0830, 0x082c, 0x0828, 0x01f4, 0x01f5, 0x01f6}, + { + 48, 5240, 0xa0, 0x01, 0x01, 0x02, 0x0c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xcf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00, 0xcf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0x0834, 0x0830, 0x082c, 0x01f3, 0x01f4, 0x01f5}, + { + 50, 5250, 0xa0, 0x01, 0x01, 0x02, 0x0d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xcf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00, 0xcf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0x0838, 0x0834, 0x0830, 0x01f2, 0x01f3, 0x01f4}, + { + 52, 5260, 0x98, 0x01, 0x01, 0x02, 0x0e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xcf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00, 0xcf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0x083c, 0x0838, 0x0834, 0x01f1, 0x01f2, 0x01f3}, + { + 54, 5270, 0x98, 0x01, 0x01, 0x02, 0x0f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, 0xff, 0xcf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00, 0xcf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0x0840, 0x083c, 0x0838, 0x01f0, 0x01f1, 0x01f2}, + { + 56, 5280, 0x91, 0x01, 0x01, 0x02, 0x10, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, 0xff, 0xbf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00, 0xbf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0x0844, 0x0840, 0x083c, 0x01f0, 0x01f0, 0x01f1}, + { + 58, 5290, 0x91, 0x01, 0x01, 0x02, 0x11, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, 0xff, 0xbf, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xfc, 0x00, 0xbf, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xfc, 0x00, 0x0848, 0x0844, 0x0840, 0x01ef, 0x01f0, 0x01f0}, + { + 60, 5300, 0x8a, 0x01, 0x01, 0x02, 0x12, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xbf, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfc, 0x00, 0xbf, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfc, 0x00, 0x084c, 0x0848, 0x0844, 0x01ee, 0x01ef, 0x01f0}, + { + 62, 5310, 0x8a, 0x01, 0x01, 0x02, 0x13, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xbf, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00, 0xbf, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0x0850, 0x084c, 0x0848, 0x01ed, 0x01ee, 0x01ef}, + { + 64, 5320, 0x83, 0x01, 0x01, 0x02, 0x14, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xbf, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00, 0xbf, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0x0854, 0x0850, 0x084c, 0x01ec, 0x01ed, 0x01ee}, + { + 66, 5330, 0x83, 0x01, 0x01, 0x02, 0x15, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xaf, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00, 0xaf, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0x0858, 0x0854, 0x0850, 0x01eb, 0x01ec, 0x01ed}, + { + 68, 5340, 0x7c, 0x01, 0x01, 0x02, 0x16, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xaf, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00, 0xaf, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0x085c, 0x0858, 0x0854, 0x01ea, 0x01eb, 0x01ec}, + { + 70, 5350, 0x7c, 0x01, 0x01, 0x02, 0x17, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0x9f, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00, 0x9f, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0x0860, 0x085c, 0x0858, 0x01e9, 0x01ea, 0x01eb}, + { + 72, 5360, 0x75, 0x01, 0x01, 0x02, 0x18, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0x9f, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00, 0x9f, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0x0864, 0x0860, 0x085c, 0x01e8, 0x01e9, 0x01ea}, + { + 74, 5370, 0x75, 0x01, 0x01, 0x02, 0x19, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0x9f, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00, 0x9f, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0x0868, 0x0864, 0x0860, 0x01e7, 0x01e8, 0x01e9}, + { + 76, 5380, 0x6e, 0x01, 0x01, 0x02, 0x1a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0x9f, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00, 0x9f, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0x086c, 0x0868, 0x0864, 0x01e6, 0x01e7, 0x01e8}, + { + 78, 5390, 0x6e, 0x01, 0x01, 0x02, 0x1b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0x8f, 0x00, 0x05, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xfa, 0x00, 0x8f, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xfa, 0x00, 0x0870, 0x086c, 0x0868, 0x01e5, 0x01e6, 0x01e7}, + { + 80, 5400, 0x67, 0x01, 0x01, 0x02, 0x1c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xc8, 0x8f, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xfa, 0x00, 0x8f, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xfa, 0x00, 0x0874, 0x0870, 0x086c, 0x01e5, 0x01e5, 0x01e6}, + { + 82, 5410, 0x67, 0x01, 0x01, 0x02, 0x1d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xc8, 0x8f, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xfa, 0x00, 0x8f, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xfa, 0x00, 0x0878, 0x0874, 0x0870, 0x01e4, 0x01e5, 0x01e5}, + { + 84, 5420, 0x61, 0x01, 0x01, 0x02, 0x1e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xc8, 0x8e, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xfa, 0x00, 0x8e, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xfa, 0x00, 0x087c, 0x0878, 0x0874, 0x01e3, 0x01e4, 0x01e5}, + { + 86, 5430, 0x61, 0x01, 0x01, 0x02, 0x1f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xc8, 0x8e, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xfa, 0x00, 0x8e, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xfa, 0x00, 0x0880, 0x087c, 0x0878, 0x01e2, 0x01e3, 0x01e4}, + { + 88, 5440, 0x5a, 0x01, 0x01, 0x02, 0x20, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x7e, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xfa, 0x00, 0x7e, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xfa, 0x00, 0x0884, 0x0880, 0x087c, 0x01e1, 0x01e2, 0x01e3}, + { + 90, 5450, 0x5a, 0x01, 0x01, 0x02, 0x21, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x7d, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xfa, 0x00, 0x7d, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xfa, 0x00, 0x0888, 0x0884, 0x0880, 0x01e0, 0x01e1, 0x01e2}, + { + 92, 5460, 0x53, 0x01, 0x01, 0x02, 0x22, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x6d, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xf8, 0x00, 0x6d, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xf8, 0x00, 0x088c, 0x0888, 0x0884, 0x01df, 0x01e0, 0x01e1}, + { + 94, 5470, 0x53, 0x01, 0x01, 0x02, 0x23, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x6d, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xf8, 0x00, 0x6d, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xf8, 0x00, 0x0890, 0x088c, 0x0888, 0x01de, 0x01df, 0x01e0}, + { + 96, 5480, 0x4d, 0x01, 0x01, 0x02, 0x24, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x5d, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xf8, 0x00, 0x5d, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xf8, 0x00, 0x0894, 0x0890, 0x088c, 0x01dd, 0x01de, 0x01df}, + { + 98, 5490, 0x4d, 0x01, 0x01, 0x02, 0x25, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x5c, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x08, 0x00, 0xf8, 0x00, 0x5c, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x08, + 0x00, 0xf8, 0x00, 0x0898, 0x0894, 0x0890, 0x01dd, 0x01dd, 0x01de}, + { + 100, 5500, 0x47, 0x01, 0x01, 0x02, 0x26, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x5c, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00, 0x5c, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x089c, 0x0898, 0x0894, 0x01dc, 0x01dd, 0x01dd}, + { + 102, 5510, 0x47, 0x01, 0x01, 0x02, 0x27, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x4c, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00, 0x4c, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x08a0, 0x089c, 0x0898, 0x01db, 0x01dc, 0x01dd}, + { + 104, 5520, 0x40, 0x01, 0x01, 0x02, 0x28, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x4c, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00, 0x4c, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x08a4, 0x08a0, 0x089c, 0x01da, 0x01db, 0x01dc}, + { + 106, 5530, 0x40, 0x01, 0x01, 0x02, 0x29, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x3b, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00, 0x3b, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x08a8, 0x08a4, 0x08a0, 0x01d9, 0x01da, 0x01db}, + { + 108, 5540, 0x3a, 0x01, 0x01, 0x02, 0x2a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x3b, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00, 0x3b, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x08ac, 0x08a8, 0x08a4, 0x01d8, 0x01d9, 0x01da}, + { + 110, 5550, 0x3a, 0x01, 0x01, 0x02, 0x2b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x3b, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00, 0x3b, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x08b0, 0x08ac, 0x08a8, 0x01d7, 0x01d8, 0x01d9}, + { + 112, 5560, 0x34, 0x01, 0x01, 0x02, 0x2c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x2b, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00, 0x2b, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x08b4, 0x08b0, 0x08ac, 0x01d7, 0x01d7, 0x01d8}, + { + 114, 5570, 0x34, 0x01, 0x01, 0x02, 0x2d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x2a, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00, 0x2a, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x08b8, 0x08b4, 0x08b0, 0x01d6, 0x01d7, 0x01d7}, + { + 116, 5580, 0x2e, 0x01, 0x01, 0x02, 0x2e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x1a, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00, 0x1a, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x08bc, 0x08b8, 0x08b4, 0x01d5, 0x01d6, 0x01d7}, + { + 118, 5590, 0x2e, 0x01, 0x01, 0x02, 0x2f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x1a, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00, 0x1a, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x08c0, 0x08bc, 0x08b8, 0x01d4, 0x01d5, 0x01d6}, + { + 120, 5600, 0x28, 0x01, 0x01, 0x02, 0x30, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x1a, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00, 0x1a, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x08c4, 0x08c0, 0x08bc, 0x01d3, 0x01d4, 0x01d5}, + { + 122, 5610, 0x28, 0x01, 0x01, 0x02, 0x31, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x19, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00, 0x19, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x08c8, 0x08c4, 0x08c0, 0x01d2, 0x01d3, 0x01d4}, + { + 124, 5620, 0x21, 0x01, 0x01, 0x02, 0x32, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x19, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00, 0x19, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x08cc, 0x08c8, 0x08c4, 0x01d2, 0x01d2, 0x01d3}, + { + 126, 5630, 0x21, 0x01, 0x01, 0x02, 0x33, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x09, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00, 0x09, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x08d0, 0x08cc, 0x08c8, 0x01d1, 0x01d2, 0x01d2}, + { + 128, 5640, 0x1c, 0x01, 0x01, 0x02, 0x34, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x09, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00, 0x09, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x08d4, 0x08d0, 0x08cc, 0x01d0, 0x01d1, 0x01d2}, + { + 130, 5650, 0x1c, 0x01, 0x01, 0x02, 0x35, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x08, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf8, 0x00, 0x08, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf8, 0x00, 0x08d8, 0x08d4, 0x08d0, 0x01cf, 0x01d0, 0x01d1}, + { + 132, 5660, 0x16, 0x01, 0x01, 0x02, 0x36, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x08, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf6, 0x00, 0x08, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf6, 0x00, 0x08dc, 0x08d8, 0x08d4, 0x01ce, 0x01cf, 0x01d0}, + { + 134, 5670, 0x16, 0x01, 0x01, 0x02, 0x37, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x08, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf6, 0x00, 0x08, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf6, 0x00, 0x08e0, 0x08dc, 0x08d8, 0x01ce, 0x01ce, 0x01cf}, + { + 136, 5680, 0x10, 0x01, 0x01, 0x02, 0x38, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x08, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf6, 0x00, 0x08, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf6, 0x00, 0x08e4, 0x08e0, 0x08dc, 0x01cd, 0x01ce, 0x01ce}, + { + 138, 5690, 0x10, 0x01, 0x01, 0x02, 0x39, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x07, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf6, 0x00, 0x07, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf6, 0x00, 0x08e8, 0x08e4, 0x08e0, 0x01cc, 0x01cd, 0x01ce}, + { + 140, 5700, 0x0a, 0x01, 0x01, 0x02, 0x3a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x07, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf6, 0x00, 0x07, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf6, 0x00, 0x08ec, 0x08e8, 0x08e4, 0x01cb, 0x01cc, 0x01cd}, + { + 142, 5710, 0x0a, 0x01, 0x01, 0x02, 0x3b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x07, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x07, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x08f0, 0x08ec, 0x08e8, 0x01ca, 0x01cb, 0x01cc}, + { + 144, 5720, 0x0a, 0x01, 0x01, 0x02, 0x3c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x07, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x07, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x08f4, 0x08f0, 0x08ec, 0x01c9, 0x01ca, 0x01cb}, + { + 145, 5725, 0x03, 0x01, 0x02, 0x04, 0x79, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x06, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x06, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x08f6, 0x08f2, 0x08ee, 0x01c9, 0x01ca, 0x01cb}, + { + 146, 5730, 0x0a, 0x01, 0x01, 0x02, 0x3d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x06, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x06, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x08f8, 0x08f4, 0x08f0, 0x01c9, 0x01c9, 0x01ca}, + { + 147, 5735, 0x03, 0x01, 0x02, 0x04, 0x7b, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x06, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x06, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x08fa, 0x08f6, 0x08f2, 0x01c8, 0x01c9, 0x01ca}, + { + 148, 5740, 0x0a, 0x01, 0x01, 0x02, 0x3e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x06, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x06, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x08fc, 0x08f8, 0x08f4, 0x01c8, 0x01c9, 0x01c9}, + { + 149, 5745, 0xfe, 0x00, 0x02, 0x04, 0x7d, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x06, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x06, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x08fe, 0x08fa, 0x08f6, 0x01c8, 0x01c8, 0x01c9}, + { + 150, 5750, 0x0a, 0x01, 0x01, 0x02, 0x3f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x06, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x06, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x0900, 0x08fc, 0x08f8, 0x01c7, 0x01c8, 0x01c9}, + { + 151, 5755, 0xfe, 0x00, 0x02, 0x04, 0x7f, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x05, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x05, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x0902, 0x08fe, 0x08fa, 0x01c7, 0x01c8, 0x01c8}, + { + 152, 5760, 0x0a, 0x01, 0x01, 0x02, 0x40, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x05, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x05, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x0904, 0x0900, 0x08fc, 0x01c6, 0x01c7, 0x01c8}, + { + 153, 5765, 0xf8, 0x00, 0x02, 0x04, 0x81, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x05, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x05, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x0906, 0x0902, 0x08fe, 0x01c6, 0x01c7, 0x01c8}, + { + 154, 5770, 0x0a, 0x01, 0x01, 0x02, 0x41, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x05, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x05, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x0908, 0x0904, 0x0900, 0x01c6, 0x01c6, 0x01c7}, + { + 155, 5775, 0xf8, 0x00, 0x02, 0x04, 0x83, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x05, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x05, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x090a, 0x0906, 0x0902, 0x01c5, 0x01c6, 0x01c7}, + { + 156, 5780, 0x0a, 0x01, 0x01, 0x02, 0x42, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x05, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x05, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x090c, 0x0908, 0x0904, 0x01c5, 0x01c6, 0x01c6}, + { + 157, 5785, 0xf2, 0x00, 0x02, 0x04, 0x85, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, 0x40, 0x04, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x04, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x090e, 0x090a, 0x0906, 0x01c4, 0x01c5, 0x01c6}, + { + 158, 5790, 0x0a, 0x01, 0x01, 0x02, 0x43, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, 0x40, 0x04, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x04, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x0910, 0x090c, 0x0908, 0x01c4, 0x01c5, 0x01c6}, + { + 159, 5795, 0xf2, 0x00, 0x02, 0x04, 0x87, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, 0x40, 0x04, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x04, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x0912, 0x090e, 0x090a, 0x01c4, 0x01c4, 0x01c5}, + { + 160, 5800, 0x0a, 0x01, 0x01, 0x02, 0x44, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x04, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x0914, 0x0910, 0x090c, 0x01c3, 0x01c4, 0x01c5}, + { + 161, 5805, 0xed, 0x00, 0x02, 0x04, 0x89, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x04, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x0916, 0x0912, 0x090e, 0x01c3, 0x01c4, 0x01c4}, + { + 162, 5810, 0x0a, 0x01, 0x01, 0x02, 0x45, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x04, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x0918, 0x0914, 0x0910, 0x01c2, 0x01c3, 0x01c4}, + { + 163, 5815, 0xed, 0x00, 0x02, 0x04, 0x8b, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x04, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x091a, 0x0916, 0x0912, 0x01c2, 0x01c3, 0x01c4}, + { + 164, 5820, 0x0a, 0x01, 0x01, 0x02, 0x46, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x03, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x091c, 0x0918, 0x0914, 0x01c2, 0x01c2, 0x01c3}, + { + 165, 5825, 0xed, 0x00, 0x02, 0x04, 0x8d, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x03, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x091e, 0x091a, 0x0916, 0x01c1, 0x01c2, 0x01c3}, + { + 166, 5830, 0x0a, 0x01, 0x01, 0x02, 0x47, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x03, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x0920, 0x091c, 0x0918, 0x01c1, 0x01c2, 0x01c2}, + { + 168, 5840, 0x0a, 0x01, 0x01, 0x02, 0x48, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x03, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x0924, 0x0920, 0x091c, 0x01c0, 0x01c1, 0x01c2}, + { + 170, 5850, 0xe0, 0x00, 0x01, 0x02, 0x49, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf4, 0x00, 0x03, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf4, 0x00, 0x0928, 0x0924, 0x0920, 0x01bf, 0x01c0, 0x01c1}, + { + 172, 5860, 0xde, 0x00, 0x01, 0x02, 0x4a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf2, 0x00, 0x03, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf2, 0x00, 0x092c, 0x0928, 0x0924, 0x01bf, 0x01bf, 0x01c0}, + { + 174, 5870, 0xdb, 0x00, 0x01, 0x02, 0x4b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x02, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf2, 0x00, 0x02, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf2, 0x00, 0x0930, 0x092c, 0x0928, 0x01be, 0x01bf, 0x01bf}, + { + 176, 5880, 0xd8, 0x00, 0x01, 0x02, 0x4c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x02, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf2, 0x00, 0x02, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf2, 0x00, 0x0934, 0x0930, 0x092c, 0x01bd, 0x01be, 0x01bf}, + { + 178, 5890, 0xd6, 0x00, 0x01, 0x02, 0x4d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x02, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x06, 0x00, 0xf2, 0x00, 0x02, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x06, + 0x00, 0xf2, 0x00, 0x0938, 0x0934, 0x0930, 0x01bc, 0x01bd, 0x01be}, + { + 180, 5900, 0xd3, 0x00, 0x01, 0x02, 0x4e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x87, 0x03, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x05, 0x00, 0xf2, 0x00, 0x02, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x05, + 0x00, 0xf2, 0x00, 0x093c, 0x0938, 0x0934, 0x01bc, 0x01bc, 0x01bd}, + { + 182, 5910, 0xd6, 0x00, 0x01, 0x02, 0x4f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x87, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x05, 0x00, 0xf2, 0x00, 0x01, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x05, + 0x00, 0xf2, 0x00, 0x0940, 0x093c, 0x0938, 0x01bb, 0x01bc, 0x01bc}, + { + 1, 2412, 0x00, 0x01, 0x03, 0x09, 0x6c, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x04, 0x04, 0x04, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xff, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f, 0x00, 0xff, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x03c9, 0x03c5, 0x03c1, 0x043a, 0x043f, 0x0443}, + { + 2, 2417, 0x00, 0x01, 0x03, 0x09, 0x71, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xff, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f, 0x00, 0xff, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x03cb, 0x03c7, 0x03c3, 0x0438, 0x043d, 0x0441}, + { + 3, 2422, 0x00, 0x01, 0x03, 0x09, 0x76, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xff, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f, 0x00, 0xff, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x03cd, 0x03c9, 0x03c5, 0x0436, 0x043a, 0x043f}, + { + 4, 2427, 0x00, 0x01, 0x03, 0x09, 0x7b, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f, 0x00, 0xfd, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x03cf, 0x03cb, 0x03c7, 0x0434, 0x0438, 0x043d}, + { + 5, 2432, 0x00, 0x01, 0x03, 0x09, 0x80, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xfb, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f, 0x00, 0xfb, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x03d1, 0x03cd, 0x03c9, 0x0431, 0x0436, 0x043a}, + { + 6, 2437, 0x00, 0x01, 0x03, 0x09, 0x85, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f, 0x00, 0xfa, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x03d3, 0x03cf, 0x03cb, 0x042f, 0x0434, 0x0438}, + { + 7, 2442, 0x00, 0x01, 0x03, 0x09, 0x8a, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f, 0x00, 0xf8, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x03d5, 0x03d1, 0x03cd, 0x042d, 0x0431, 0x0436}, + { + 8, 2447, 0x00, 0x01, 0x03, 0x09, 0x8f, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f, 0x00, 0xf7, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x03d7, 0x03d3, 0x03cf, 0x042b, 0x042f, 0x0434}, + { + 9, 2452, 0x00, 0x01, 0x03, 0x09, 0x94, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xf6, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0f, 0x00, 0xf6, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0f, 0x03d9, 0x03d5, 0x03d1, 0x0429, 0x042d, 0x0431}, + { + 10, 2457, 0x00, 0x01, 0x03, 0x09, 0x99, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xf5, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0d, 0x00, 0xf5, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0d, 0x03db, 0x03d7, 0x03d3, 0x0427, 0x042b, 0x042f}, + { + 11, 2462, 0x00, 0x01, 0x03, 0x09, 0x9e, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0d, 0x00, 0xf4, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0d, 0x03dd, 0x03d9, 0x03d5, 0x0424, 0x0429, 0x042d}, + { + 12, 2467, 0x00, 0x01, 0x03, 0x09, 0xa3, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xf3, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0d, 0x00, 0xf3, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0d, 0x03df, 0x03db, 0x03d7, 0x0422, 0x0427, 0x042b}, + { + 13, 2472, 0x00, 0x01, 0x03, 0x09, 0xa8, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xf2, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0d, 0x00, 0xf2, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0d, 0x03e1, 0x03dd, 0x03d9, 0x0420, 0x0424, 0x0429}, + { + 14, 2484, 0xff, 0x01, 0x03, 0x09, 0xb4, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x05, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0d, 0x00, 0xf0, 0x00, 0x05, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0d, 0x03e6, 0x03e2, 0x03de, 0x041b, 0x041f, 0x0424} +}; + +static const struct chan_info_nphy_radio205x chan_info_nphyrev4_2056_A1[] = { + { + 184, 4920, 0xff, 0x01, 0x01, 0x01, 0xec, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0e, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0e, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07b4, 0x07b0, 0x07ac, 0x0214, 0x0215, 0x0216}, + { + 186, 4930, 0xff, 0x01, 0x01, 0x01, 0xed, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0e, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0e, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07b8, 0x07b4, 0x07b0, 0x0213, 0x0214, 0x0215}, + { + 188, 4940, 0xff, 0x01, 0x01, 0x01, 0xee, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0e, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0e, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07bc, 0x07b8, 0x07b4, 0x0212, 0x0213, 0x0214}, + { + 190, 4950, 0xff, 0x01, 0x01, 0x01, 0xef, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0e, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0e, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07c0, 0x07bc, 0x07b8, 0x0211, 0x0212, 0x0213}, + { + 192, 4960, 0xff, 0x01, 0x01, 0x01, 0xf0, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0e, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0e, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07c4, 0x07c0, 0x07bc, 0x020f, 0x0211, 0x0212}, + { + 194, 4970, 0xff, 0x01, 0x01, 0x01, 0xf1, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0e, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0e, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07c8, 0x07c4, 0x07c0, 0x020e, 0x020f, 0x0211}, + { + 196, 4980, 0xff, 0x01, 0x01, 0x01, 0xf2, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0e, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0e, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07cc, 0x07c8, 0x07c4, 0x020d, 0x020e, 0x020f}, + { + 198, 4990, 0xff, 0x01, 0x01, 0x01, 0xf3, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0e, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0e, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07d0, 0x07cc, 0x07c8, 0x020c, 0x020d, 0x020e}, + { + 200, 5000, 0xff, 0x01, 0x01, 0x01, 0xf4, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07d4, 0x07d0, 0x07cc, 0x020b, 0x020c, 0x020d}, + { + 202, 5010, 0xff, 0x01, 0x01, 0x01, 0xf5, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07d8, 0x07d4, 0x07d0, 0x020a, 0x020b, 0x020c}, + { + 204, 5020, 0xf7, 0x01, 0x01, 0x01, 0xf6, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07dc, 0x07d8, 0x07d4, 0x0209, 0x020a, 0x020b}, + { + 206, 5030, 0xf7, 0x01, 0x01, 0x01, 0xf7, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07e0, 0x07dc, 0x07d8, 0x0208, 0x0209, 0x020a}, + { + 208, 5040, 0xef, 0x01, 0x01, 0x01, 0xf8, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07e4, 0x07e0, 0x07dc, 0x0207, 0x0208, 0x0209}, + { + 210, 5050, 0xef, 0x01, 0x01, 0x01, 0xf9, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07e8, 0x07e4, 0x07e0, 0x0206, 0x0207, 0x0208}, + { + 212, 5060, 0xe6, 0x01, 0x01, 0x01, 0xfa, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07ec, 0x07e8, 0x07e4, 0x0205, 0x0206, 0x0207}, + { + 214, 5070, 0xe6, 0x01, 0x01, 0x01, 0xfb, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07f0, 0x07ec, 0x07e8, 0x0204, 0x0205, 0x0206}, + { + 216, 5080, 0xde, 0x01, 0x01, 0x01, 0xfc, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07f4, 0x07f0, 0x07ec, 0x0203, 0x0204, 0x0205}, + { + 218, 5090, 0xde, 0x01, 0x01, 0x01, 0xfd, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0d, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xff, 0x00, 0x0d, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xff, 0x00, 0x07f8, 0x07f4, 0x07f0, 0x0202, 0x0203, 0x0204}, + { + 220, 5100, 0xd6, 0x01, 0x01, 0x01, 0xfe, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00, 0xff, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0x07fc, 0x07f8, 0x07f4, 0x0201, 0x0202, 0x0203}, + { + 222, 5110, 0xd6, 0x01, 0x01, 0x01, 0xff, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00, 0xff, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0x0800, 0x07fc, 0x07f8, 0x0200, 0x0201, 0x0202}, + { + 224, 5120, 0xce, 0x01, 0x01, 0x02, 0x00, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00, 0xff, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0x0804, 0x0800, 0x07fc, 0x01ff, 0x0200, 0x0201}, + { + 226, 5130, 0xce, 0x01, 0x01, 0x02, 0x01, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00, 0xff, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0x0808, 0x0804, 0x0800, 0x01fe, 0x01ff, 0x0200}, + { + 228, 5140, 0xc6, 0x01, 0x01, 0x02, 0x02, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00, 0xff, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0x080c, 0x0808, 0x0804, 0x01fd, 0x01fe, 0x01ff}, + { + 32, 5160, 0xbe, 0x01, 0x01, 0x02, 0x04, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00, 0xff, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0x0814, 0x0810, 0x080c, 0x01fb, 0x01fc, 0x01fd}, + { + 34, 5170, 0xbe, 0x01, 0x01, 0x02, 0x05, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00, 0xff, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0x0818, 0x0814, 0x0810, 0x01fa, 0x01fb, 0x01fc}, + { + 36, 5180, 0xb6, 0x01, 0x01, 0x02, 0x06, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xef, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00, 0xef, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0x081c, 0x0818, 0x0814, 0x01f9, 0x01fa, 0x01fb}, + { + 38, 5190, 0xb6, 0x01, 0x01, 0x02, 0x07, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xef, 0x00, 0x0c, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfe, 0x00, 0xef, 0x00, 0x0c, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfe, 0x00, 0x0820, 0x081c, 0x0818, 0x01f8, 0x01f9, 0x01fa}, + { + 40, 5200, 0xaf, 0x01, 0x01, 0x02, 0x08, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xef, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00, 0xef, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0x0824, 0x0820, 0x081c, 0x01f7, 0x01f8, 0x01f9}, + { + 42, 5210, 0xaf, 0x01, 0x01, 0x02, 0x09, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xdf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00, 0xdf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0x0828, 0x0824, 0x0820, 0x01f6, 0x01f7, 0x01f8}, + { + 44, 5220, 0xa7, 0x01, 0x01, 0x02, 0x0a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xdf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00, 0xdf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0x082c, 0x0828, 0x0824, 0x01f5, 0x01f6, 0x01f7}, + { + 46, 5230, 0xa7, 0x01, 0x01, 0x02, 0x0b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xdf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00, 0xdf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0x0830, 0x082c, 0x0828, 0x01f4, 0x01f5, 0x01f6}, + { + 48, 5240, 0xa0, 0x01, 0x01, 0x02, 0x0c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xcf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00, 0xcf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0x0834, 0x0830, 0x082c, 0x01f3, 0x01f4, 0x01f5}, + { + 50, 5250, 0xa0, 0x01, 0x01, 0x02, 0x0d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xcf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00, 0xcf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0x0838, 0x0834, 0x0830, 0x01f2, 0x01f3, 0x01f4}, + { + 52, 5260, 0x98, 0x01, 0x01, 0x02, 0x0e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xcf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00, 0xcf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0x083c, 0x0838, 0x0834, 0x01f1, 0x01f2, 0x01f3}, + { + 54, 5270, 0x98, 0x01, 0x01, 0x02, 0x0f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, 0xff, 0xcf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00, 0xcf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0x0840, 0x083c, 0x0838, 0x01f0, 0x01f1, 0x01f2}, + { + 56, 5280, 0x91, 0x01, 0x01, 0x02, 0x10, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, 0xff, 0xbf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00, 0xbf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0x0844, 0x0840, 0x083c, 0x01f0, 0x01f0, 0x01f1}, + { + 58, 5290, 0x91, 0x01, 0x01, 0x02, 0x11, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, 0xff, 0xbf, 0x00, 0x0a, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfc, 0x00, 0xbf, 0x00, 0x0a, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfc, 0x00, 0x0848, 0x0844, 0x0840, 0x01ef, 0x01f0, 0x01f0}, + { + 60, 5300, 0x8a, 0x01, 0x01, 0x02, 0x12, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xbf, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00, 0xbf, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0x084c, 0x0848, 0x0844, 0x01ee, 0x01ef, 0x01f0}, + { + 62, 5310, 0x8a, 0x01, 0x01, 0x02, 0x13, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xbf, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00, 0xbf, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0x0850, 0x084c, 0x0848, 0x01ed, 0x01ee, 0x01ef}, + { + 64, 5320, 0x83, 0x01, 0x01, 0x02, 0x14, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xbf, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00, 0xbf, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0x0854, 0x0850, 0x084c, 0x01ec, 0x01ed, 0x01ee}, + { + 66, 5330, 0x83, 0x01, 0x01, 0x02, 0x15, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xaf, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00, 0xaf, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0x0858, 0x0854, 0x0850, 0x01eb, 0x01ec, 0x01ed}, + { + 68, 5340, 0x7c, 0x01, 0x01, 0x02, 0x16, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xaf, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00, 0xaf, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0x085c, 0x0858, 0x0854, 0x01ea, 0x01eb, 0x01ec}, + { + 70, 5350, 0x7c, 0x01, 0x01, 0x02, 0x17, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0x9f, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00, 0x9f, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0x0860, 0x085c, 0x0858, 0x01e9, 0x01ea, 0x01eb}, + { + 72, 5360, 0x75, 0x01, 0x01, 0x02, 0x18, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0x9f, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00, 0x9f, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0x0864, 0x0860, 0x085c, 0x01e8, 0x01e9, 0x01ea}, + { + 74, 5370, 0x75, 0x01, 0x01, 0x02, 0x19, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0x9f, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00, 0x9f, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0x0868, 0x0864, 0x0860, 0x01e7, 0x01e8, 0x01e9}, + { + 76, 5380, 0x6e, 0x01, 0x01, 0x02, 0x1a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0x9f, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00, 0x9f, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0x086c, 0x0868, 0x0864, 0x01e6, 0x01e7, 0x01e8}, + { + 78, 5390, 0x6e, 0x01, 0x01, 0x02, 0x1b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0x8f, 0x00, 0x08, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xfa, 0x00, 0x8f, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xfa, 0x00, 0x0870, 0x086c, 0x0868, 0x01e5, 0x01e6, 0x01e7}, + { + 80, 5400, 0x67, 0x01, 0x01, 0x02, 0x1c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xc8, 0x8f, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00, 0x8f, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x0874, 0x0870, 0x086c, 0x01e5, 0x01e5, 0x01e6}, + { + 82, 5410, 0x67, 0x01, 0x01, 0x02, 0x1d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xc8, 0x8f, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00, 0x8f, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x0878, 0x0874, 0x0870, 0x01e4, 0x01e5, 0x01e5}, + { + 84, 5420, 0x61, 0x01, 0x01, 0x02, 0x1e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xc8, 0x8e, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00, 0x8e, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x087c, 0x0878, 0x0874, 0x01e3, 0x01e4, 0x01e5}, + { + 86, 5430, 0x61, 0x01, 0x01, 0x02, 0x1f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xc8, 0x8e, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00, 0x8e, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x0880, 0x087c, 0x0878, 0x01e2, 0x01e3, 0x01e4}, + { + 88, 5440, 0x5a, 0x01, 0x01, 0x02, 0x20, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x7e, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00, 0x7e, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x0884, 0x0880, 0x087c, 0x01e1, 0x01e2, 0x01e3}, + { + 90, 5450, 0x5a, 0x01, 0x01, 0x02, 0x21, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x7d, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00, 0x7d, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x0888, 0x0884, 0x0880, 0x01e0, 0x01e1, 0x01e2}, + { + 92, 5460, 0x53, 0x01, 0x01, 0x02, 0x22, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x6d, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00, 0x6d, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x088c, 0x0888, 0x0884, 0x01df, 0x01e0, 0x01e1}, + { + 94, 5470, 0x53, 0x01, 0x01, 0x02, 0x23, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x6d, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00, 0x6d, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x0890, 0x088c, 0x0888, 0x01de, 0x01df, 0x01e0}, + { + 96, 5480, 0x4d, 0x01, 0x01, 0x02, 0x24, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x5d, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00, 0x5d, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x0894, 0x0890, 0x088c, 0x01dd, 0x01de, 0x01df}, + { + 98, 5490, 0x4d, 0x01, 0x01, 0x02, 0x25, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x5c, 0x00, 0x07, 0x00, 0x7f, + 0x00, 0x0f, 0x00, 0xf8, 0x00, 0x5c, 0x00, 0x07, 0x00, 0x7f, 0x00, 0x0f, + 0x00, 0xf8, 0x00, 0x0898, 0x0894, 0x0890, 0x01dd, 0x01dd, 0x01de}, + { + 100, 5500, 0x47, 0x01, 0x01, 0x02, 0x26, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x5c, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00, 0x5c, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x089c, 0x0898, 0x0894, 0x01dc, 0x01dd, 0x01dd}, + { + 102, 5510, 0x47, 0x01, 0x01, 0x02, 0x27, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x4c, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00, 0x4c, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x08a0, 0x089c, 0x0898, 0x01db, 0x01dc, 0x01dd}, + { + 104, 5520, 0x40, 0x01, 0x01, 0x02, 0x28, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x4c, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00, 0x4c, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x08a4, 0x08a0, 0x089c, 0x01da, 0x01db, 0x01dc}, + { + 106, 5530, 0x40, 0x01, 0x01, 0x02, 0x29, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x3b, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00, 0x3b, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x08a8, 0x08a4, 0x08a0, 0x01d9, 0x01da, 0x01db}, + { + 108, 5540, 0x3a, 0x01, 0x01, 0x02, 0x2a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x3b, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00, 0x3b, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x08ac, 0x08a8, 0x08a4, 0x01d8, 0x01d9, 0x01da}, + { + 110, 5550, 0x3a, 0x01, 0x01, 0x02, 0x2b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x3b, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00, 0x3b, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x08b0, 0x08ac, 0x08a8, 0x01d7, 0x01d8, 0x01d9}, + { + 112, 5560, 0x34, 0x01, 0x01, 0x02, 0x2c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x2b, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00, 0x2b, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x08b4, 0x08b0, 0x08ac, 0x01d7, 0x01d7, 0x01d8}, + { + 114, 5570, 0x34, 0x01, 0x01, 0x02, 0x2d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x2a, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00, 0x2a, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x08b8, 0x08b4, 0x08b0, 0x01d6, 0x01d7, 0x01d7}, + { + 116, 5580, 0x2e, 0x01, 0x01, 0x02, 0x2e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x1a, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00, 0x1a, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x08bc, 0x08b8, 0x08b4, 0x01d5, 0x01d6, 0x01d7}, + { + 118, 5590, 0x2e, 0x01, 0x01, 0x02, 0x2f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x1a, 0x00, 0x06, 0x00, 0x7f, + 0x00, 0x0d, 0x00, 0xf6, 0x00, 0x1a, 0x00, 0x06, 0x00, 0x7f, 0x00, 0x0d, + 0x00, 0xf6, 0x00, 0x08c0, 0x08bc, 0x08b8, 0x01d4, 0x01d5, 0x01d6}, + { + 120, 5600, 0x28, 0x01, 0x01, 0x02, 0x30, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x1a, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00, 0x1a, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x08c4, 0x08c0, 0x08bc, 0x01d3, 0x01d4, 0x01d5}, + { + 122, 5610, 0x28, 0x01, 0x01, 0x02, 0x31, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x19, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00, 0x19, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x08c8, 0x08c4, 0x08c0, 0x01d2, 0x01d3, 0x01d4}, + { + 124, 5620, 0x21, 0x01, 0x01, 0x02, 0x32, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x19, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00, 0x19, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x08cc, 0x08c8, 0x08c4, 0x01d2, 0x01d2, 0x01d3}, + { + 126, 5630, 0x21, 0x01, 0x01, 0x02, 0x33, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x09, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00, 0x09, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x08d0, 0x08cc, 0x08c8, 0x01d1, 0x01d2, 0x01d2}, + { + 128, 5640, 0x1c, 0x01, 0x01, 0x02, 0x34, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x09, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00, 0x09, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x08d4, 0x08d0, 0x08cc, 0x01d0, 0x01d1, 0x01d2}, + { + 130, 5650, 0x1c, 0x01, 0x01, 0x02, 0x35, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x08, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00, 0x08, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x08d8, 0x08d4, 0x08d0, 0x01cf, 0x01d0, 0x01d1}, + { + 132, 5660, 0x16, 0x01, 0x01, 0x02, 0x36, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x08, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00, 0x08, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x08dc, 0x08d8, 0x08d4, 0x01ce, 0x01cf, 0x01d0}, + { + 134, 5670, 0x16, 0x01, 0x01, 0x02, 0x37, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x08, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00, 0x08, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x08e0, 0x08dc, 0x08d8, 0x01ce, 0x01ce, 0x01cf}, + { + 136, 5680, 0x10, 0x01, 0x01, 0x02, 0x38, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x08, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00, 0x08, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x08e4, 0x08e0, 0x08dc, 0x01cd, 0x01ce, 0x01ce}, + { + 138, 5690, 0x10, 0x01, 0x01, 0x02, 0x39, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x07, 0x00, 0x04, 0x00, 0x7f, + 0x00, 0x0b, 0x00, 0xf4, 0x00, 0x07, 0x00, 0x04, 0x00, 0x7f, 0x00, 0x0b, + 0x00, 0xf4, 0x00, 0x08e8, 0x08e4, 0x08e0, 0x01cc, 0x01cd, 0x01ce}, + { + 140, 5700, 0x0a, 0x01, 0x01, 0x02, 0x3a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x07, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x07, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x08ec, 0x08e8, 0x08e4, 0x01cb, 0x01cc, 0x01cd}, + { + 142, 5710, 0x0a, 0x01, 0x01, 0x02, 0x3b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x07, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x07, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x08f0, 0x08ec, 0x08e8, 0x01ca, 0x01cb, 0x01cc}, + { + 144, 5720, 0x0a, 0x01, 0x01, 0x02, 0x3c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x07, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x07, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x08f4, 0x08f0, 0x08ec, 0x01c9, 0x01ca, 0x01cb}, + { + 145, 5725, 0x03, 0x01, 0x02, 0x04, 0x79, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x06, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x06, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x08f6, 0x08f2, 0x08ee, 0x01c9, 0x01ca, 0x01cb}, + { + 146, 5730, 0x0a, 0x01, 0x01, 0x02, 0x3d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x06, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x06, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x08f8, 0x08f4, 0x08f0, 0x01c9, 0x01c9, 0x01ca}, + { + 147, 5735, 0x03, 0x01, 0x02, 0x04, 0x7b, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x06, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x06, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x08fa, 0x08f6, 0x08f2, 0x01c8, 0x01c9, 0x01ca}, + { + 148, 5740, 0x0a, 0x01, 0x01, 0x02, 0x3e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x06, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x06, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x08fc, 0x08f8, 0x08f4, 0x01c8, 0x01c9, 0x01c9}, + { + 149, 5745, 0xfe, 0x00, 0x02, 0x04, 0x7d, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x06, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x06, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x08fe, 0x08fa, 0x08f6, 0x01c8, 0x01c8, 0x01c9}, + { + 150, 5750, 0x0a, 0x01, 0x01, 0x02, 0x3f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x06, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x06, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x0900, 0x08fc, 0x08f8, 0x01c7, 0x01c8, 0x01c9}, + { + 151, 5755, 0xfe, 0x00, 0x02, 0x04, 0x7f, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x05, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x05, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x0902, 0x08fe, 0x08fa, 0x01c7, 0x01c8, 0x01c8}, + { + 152, 5760, 0x0a, 0x01, 0x01, 0x02, 0x40, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x05, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x05, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x0904, 0x0900, 0x08fc, 0x01c6, 0x01c7, 0x01c8}, + { + 153, 5765, 0xf8, 0x00, 0x02, 0x04, 0x81, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x05, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x05, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x0906, 0x0902, 0x08fe, 0x01c6, 0x01c7, 0x01c8}, + { + 154, 5770, 0x0a, 0x01, 0x01, 0x02, 0x41, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x05, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x05, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x0908, 0x0904, 0x0900, 0x01c6, 0x01c6, 0x01c7}, + { + 155, 5775, 0xf8, 0x00, 0x02, 0x04, 0x83, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x05, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x05, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x090a, 0x0906, 0x0902, 0x01c5, 0x01c6, 0x01c7}, + { + 156, 5780, 0x0a, 0x01, 0x01, 0x02, 0x42, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x05, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x05, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x090c, 0x0908, 0x0904, 0x01c5, 0x01c6, 0x01c6}, + { + 157, 5785, 0xf2, 0x00, 0x02, 0x04, 0x85, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, 0x40, 0x04, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x04, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x090e, 0x090a, 0x0906, 0x01c4, 0x01c5, 0x01c6}, + { + 158, 5790, 0x0a, 0x01, 0x01, 0x02, 0x43, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, 0x40, 0x04, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x04, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x0910, 0x090c, 0x0908, 0x01c4, 0x01c5, 0x01c6}, + { + 159, 5795, 0xf2, 0x00, 0x02, 0x04, 0x87, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, 0x40, 0x04, 0x00, 0x03, 0x00, 0x7f, + 0x00, 0x0a, 0x00, 0xf2, 0x00, 0x04, 0x00, 0x03, 0x00, 0x7f, 0x00, 0x0a, + 0x00, 0xf2, 0x00, 0x0912, 0x090e, 0x090a, 0x01c4, 0x01c4, 0x01c5}, + { + 160, 5800, 0x0a, 0x01, 0x01, 0x02, 0x44, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x04, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00, 0x04, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x0914, 0x0910, 0x090c, 0x01c3, 0x01c4, 0x01c5}, + { + 161, 5805, 0xed, 0x00, 0x02, 0x04, 0x89, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x04, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00, 0x04, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x0916, 0x0912, 0x090e, 0x01c3, 0x01c4, 0x01c4}, + { + 162, 5810, 0x0a, 0x01, 0x01, 0x02, 0x45, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x04, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00, 0x04, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x0918, 0x0914, 0x0910, 0x01c2, 0x01c3, 0x01c4}, + { + 163, 5815, 0xed, 0x00, 0x02, 0x04, 0x8b, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x04, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00, 0x04, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x091a, 0x0916, 0x0912, 0x01c2, 0x01c3, 0x01c4}, + { + 164, 5820, 0x0a, 0x01, 0x01, 0x02, 0x46, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x03, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00, 0x03, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x091c, 0x0918, 0x0914, 0x01c2, 0x01c2, 0x01c3}, + { + 165, 5825, 0xed, 0x00, 0x02, 0x04, 0x8d, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x03, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00, 0x03, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x091e, 0x091a, 0x0916, 0x01c1, 0x01c2, 0x01c3}, + { + 166, 5830, 0x0a, 0x01, 0x01, 0x02, 0x47, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x03, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00, 0x03, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x0920, 0x091c, 0x0918, 0x01c1, 0x01c2, 0x01c2}, + { + 168, 5840, 0x0a, 0x01, 0x01, 0x02, 0x48, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x03, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00, 0x03, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x0924, 0x0920, 0x091c, 0x01c0, 0x01c1, 0x01c2}, + { + 170, 5850, 0xe0, 0x00, 0x01, 0x02, 0x49, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x03, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00, 0x03, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x0928, 0x0924, 0x0920, 0x01bf, 0x01c0, 0x01c1}, + { + 172, 5860, 0xde, 0x00, 0x01, 0x02, 0x4a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x03, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00, 0x03, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x092c, 0x0928, 0x0924, 0x01bf, 0x01bf, 0x01c0}, + { + 174, 5870, 0xdb, 0x00, 0x01, 0x02, 0x4b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x02, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00, 0x02, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x0930, 0x092c, 0x0928, 0x01be, 0x01bf, 0x01bf}, + { + 176, 5880, 0xd8, 0x00, 0x01, 0x02, 0x4c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x02, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00, 0x02, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x0934, 0x0930, 0x092c, 0x01bd, 0x01be, 0x01bf}, + { + 178, 5890, 0xd6, 0x00, 0x01, 0x02, 0x4d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x02, 0x00, 0x02, 0x00, 0x7f, + 0x00, 0x09, 0x00, 0xf0, 0x00, 0x02, 0x00, 0x02, 0x00, 0x7f, 0x00, 0x09, + 0x00, 0xf0, 0x00, 0x0938, 0x0934, 0x0930, 0x01bc, 0x01bd, 0x01be}, + { + 180, 5900, 0xd3, 0x00, 0x01, 0x02, 0x4e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x87, 0x03, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf0, 0x00, 0x02, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf0, 0x00, 0x093c, 0x0938, 0x0934, 0x01bc, 0x01bc, 0x01bd}, + { + 182, 5910, 0xd6, 0x00, 0x01, 0x02, 0x4f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x87, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x07, 0x00, 0xf0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x07, + 0x00, 0xf0, 0x00, 0x0940, 0x093c, 0x0938, 0x01bb, 0x01bc, 0x01bc}, + { + 1, 2412, 0x00, 0x01, 0x03, 0x09, 0x6c, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x04, 0x04, 0x04, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xff, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e, 0x00, 0xff, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x03c9, 0x03c5, 0x03c1, 0x043a, 0x043f, 0x0443}, + { + 2, 2417, 0x00, 0x01, 0x03, 0x09, 0x71, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xff, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e, 0x00, 0xff, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x03cb, 0x03c7, 0x03c3, 0x0438, 0x043d, 0x0441}, + { + 3, 2422, 0x00, 0x01, 0x03, 0x09, 0x76, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xff, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e, 0x00, 0xff, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x03cd, 0x03c9, 0x03c5, 0x0436, 0x043a, 0x043f}, + { + 4, 2427, 0x00, 0x01, 0x03, 0x09, 0x7b, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e, 0x00, 0xfd, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x03cf, 0x03cb, 0x03c7, 0x0434, 0x0438, 0x043d}, + { + 5, 2432, 0x00, 0x01, 0x03, 0x09, 0x80, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xfb, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e, 0x00, 0xfb, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x03d1, 0x03cd, 0x03c9, 0x0431, 0x0436, 0x043a}, + { + 6, 2437, 0x00, 0x01, 0x03, 0x09, 0x85, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x03d3, 0x03cf, 0x03cb, 0x042f, 0x0434, 0x0438}, + { + 7, 2442, 0x00, 0x01, 0x03, 0x09, 0x8a, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e, 0x00, 0xf8, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x03d5, 0x03d1, 0x03cd, 0x042d, 0x0431, 0x0436}, + { + 8, 2447, 0x00, 0x01, 0x03, 0x09, 0x8f, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e, 0x00, 0xf7, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x03d7, 0x03d3, 0x03cf, 0x042b, 0x042f, 0x0434}, + { + 9, 2452, 0x00, 0x01, 0x03, 0x09, 0x94, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xf6, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e, 0x00, 0xf6, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x03d9, 0x03d5, 0x03d1, 0x0429, 0x042d, 0x0431}, + { + 10, 2457, 0x00, 0x01, 0x03, 0x09, 0x99, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xf5, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e, 0x00, 0xf5, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x03db, 0x03d7, 0x03d3, 0x0427, 0x042b, 0x042f}, + { + 11, 2462, 0x00, 0x01, 0x03, 0x09, 0x9e, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e, 0x00, 0xf4, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x03dd, 0x03d9, 0x03d5, 0x0424, 0x0429, 0x042d}, + { + 12, 2467, 0x00, 0x01, 0x03, 0x09, 0xa3, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xf3, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e, 0x00, 0xf3, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x03df, 0x03db, 0x03d7, 0x0422, 0x0427, 0x042b}, + { + 13, 2472, 0x00, 0x01, 0x03, 0x09, 0xa8, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xf2, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e, 0x00, 0xf2, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x03e1, 0x03dd, 0x03d9, 0x0420, 0x0424, 0x0429}, + { + 14, 2484, 0xff, 0x01, 0x03, 0x09, 0xb4, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x04, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0e, 0x00, 0xf0, 0x00, 0x04, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0e, 0x03e6, 0x03e2, 0x03de, 0x041b, 0x041f, 0x0424} +}; + +static const struct chan_info_nphy_radio205x chan_info_nphyrev5_2056v5[] = { + { + 184, 4920, 0xff, 0x01, 0x01, 0x01, 0xec, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0b, 0x00, 0x70, + 0x00, 0x0f, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0b, 0x00, 0x70, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07b4, 0x07b0, 0x07ac, 0x0214, 0x0215, 0x0216}, + { + 186, 4930, 0xff, 0x01, 0x01, 0x01, 0xed, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0b, 0x00, 0x70, + 0x00, 0x0e, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0b, 0x00, 0x70, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x07b8, 0x07b4, 0x07b0, 0x0213, 0x0214, 0x0215}, + { + 188, 4940, 0xff, 0x01, 0x01, 0x01, 0xee, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0b, 0x00, 0x70, + 0x00, 0x0e, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0b, 0x00, 0x70, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x07bc, 0x07b8, 0x07b4, 0x0212, 0x0213, 0x0214}, + { + 190, 4950, 0xff, 0x01, 0x01, 0x01, 0xef, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0b, 0x00, 0x70, + 0x00, 0x0e, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0b, 0x00, 0x70, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x07c0, 0x07bc, 0x07b8, 0x0211, 0x0212, 0x0213}, + { + 192, 4960, 0xff, 0x01, 0x01, 0x01, 0xf0, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0e, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x07c4, 0x07c0, 0x07bc, 0x020f, 0x0211, 0x0212}, + { + 194, 4970, 0xff, 0x01, 0x01, 0x01, 0xf1, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x07c8, 0x07c4, 0x07c0, 0x020e, 0x020f, 0x0211}, + { + 196, 4980, 0xff, 0x01, 0x01, 0x01, 0xf2, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x07cc, 0x07c8, 0x07c4, 0x020d, 0x020e, 0x020f}, + { + 198, 4990, 0xff, 0x01, 0x01, 0x01, 0xf3, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x07d0, 0x07cc, 0x07c8, 0x020c, 0x020d, 0x020e}, + { + 200, 5000, 0xff, 0x01, 0x01, 0x01, 0xf4, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x07d4, 0x07d0, 0x07cc, 0x020b, 0x020c, 0x020d}, + { + 202, 5010, 0xff, 0x01, 0x01, 0x01, 0xf5, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x07d8, 0x07d4, 0x07d0, 0x020a, 0x020b, 0x020c}, + { + 204, 5020, 0xf7, 0x01, 0x01, 0x01, 0xf6, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x07dc, 0x07d8, 0x07d4, 0x0209, 0x020a, 0x020b}, + { + 206, 5030, 0xf7, 0x01, 0x01, 0x01, 0xf7, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0c, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x07e0, 0x07dc, 0x07d8, 0x0208, 0x0209, 0x020a}, + { + 208, 5040, 0xef, 0x01, 0x01, 0x01, 0xf8, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0c, 0x00, 0x9f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x07e4, 0x07e0, 0x07dc, 0x0207, 0x0208, 0x0209}, + { + 210, 5050, 0xef, 0x01, 0x01, 0x01, 0xf9, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0c, 0x00, 0x9f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x07e8, 0x07e4, 0x07e0, 0x0206, 0x0207, 0x0208}, + { + 212, 5060, 0xe6, 0x01, 0x01, 0x01, 0xfa, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfd, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0c, 0x00, 0x9f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x07ec, 0x07e8, 0x07e4, 0x0205, 0x0206, 0x0207}, + { + 214, 5070, 0xe6, 0x01, 0x01, 0x01, 0xfb, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfd, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x9f, 0x00, 0xfd, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x07f0, 0x07ec, 0x07e8, 0x0204, 0x0205, 0x0206}, + { + 216, 5080, 0xde, 0x01, 0x01, 0x01, 0xfc, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x07f4, 0x07f0, 0x07ec, 0x0203, 0x0204, 0x0205}, + { + 218, 5090, 0xde, 0x01, 0x01, 0x01, 0xfd, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x07f8, 0x07f4, 0x07f0, 0x0202, 0x0203, 0x0204}, + { + 220, 5100, 0xd6, 0x01, 0x01, 0x01, 0xfe, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x07fc, 0x07f8, 0x07f4, 0x0201, 0x0202, 0x0203}, + { + 222, 5110, 0xd6, 0x01, 0x01, 0x01, 0xff, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x0800, 0x07fc, 0x07f8, 0x0200, 0x0201, 0x0202}, + { + 224, 5120, 0xce, 0x01, 0x01, 0x02, 0x00, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x0804, 0x0800, 0x07fc, 0x01ff, 0x0200, 0x0201}, + { + 226, 5130, 0xce, 0x01, 0x01, 0x02, 0x01, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfb, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0a, 0x00, 0x9f, 0x00, 0xfb, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0808, 0x0804, 0x0800, 0x01fe, 0x01ff, 0x0200}, + { + 228, 5140, 0xc6, 0x01, 0x01, 0x02, 0x02, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfb, 0x00, 0x07, 0x00, 0x70, + 0x00, 0x0a, 0x00, 0x9f, 0x00, 0xfb, 0x00, 0x07, 0x00, 0x70, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x080c, 0x0808, 0x0804, 0x01fd, 0x01fe, 0x01ff}, + { + 32, 5160, 0xbe, 0x01, 0x01, 0x02, 0x04, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfb, 0x00, 0x07, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x9e, 0x00, 0xfb, 0x00, 0x07, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x6e, 0x00, 0x0814, 0x0810, 0x080c, 0x01fb, 0x01fc, 0x01fd}, + { + 34, 5170, 0xbe, 0x01, 0x01, 0x02, 0x05, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfb, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x9e, 0x00, 0xfb, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x6e, 0x00, 0x0818, 0x0814, 0x0810, 0x01fa, 0x01fb, 0x01fc}, + { + 36, 5180, 0xb6, 0x01, 0x01, 0x02, 0x06, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x6e, 0x00, 0x081c, 0x0818, 0x0814, 0x01f9, 0x01fa, 0x01fb}, + { + 38, 5190, 0xb6, 0x01, 0x01, 0x02, 0x07, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x6e, 0x00, 0x0820, 0x081c, 0x0818, 0x01f8, 0x01f9, 0x01fa}, + { + 40, 5200, 0xaf, 0x01, 0x01, 0x02, 0x08, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x6e, 0x00, 0x0824, 0x0820, 0x081c, 0x01f7, 0x01f8, 0x01f9}, + { + 42, 5210, 0xaf, 0x01, 0x01, 0x02, 0x09, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x6e, 0x00, 0x0828, 0x0824, 0x0820, 0x01f6, 0x01f7, 0x01f8}, + { + 44, 5220, 0xa7, 0x01, 0x01, 0x02, 0x0a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x6e, 0x00, 0x082c, 0x0828, 0x0824, 0x01f5, 0x01f6, 0x01f7}, + { + 46, 5230, 0xa7, 0x01, 0x01, 0x02, 0x0b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xea, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x08, 0x00, 0x9e, 0x00, 0xea, 0x00, 0x06, 0x00, 0x70, 0x00, 0x08, + 0x00, 0x6e, 0x00, 0x0830, 0x082c, 0x0828, 0x01f4, 0x01f5, 0x01f6}, + { + 48, 5240, 0xa0, 0x01, 0x01, 0x02, 0x0c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xe9, 0x00, 0x05, 0x00, 0x70, + 0x00, 0x08, 0x00, 0x9d, 0x00, 0xe9, 0x00, 0x05, 0x00, 0x70, 0x00, 0x08, + 0x00, 0x6d, 0x00, 0x0834, 0x0830, 0x082c, 0x01f3, 0x01f4, 0x01f5}, + { + 50, 5250, 0xa0, 0x01, 0x01, 0x02, 0x0d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xe9, 0x00, 0x05, 0x00, 0x70, + 0x00, 0x08, 0x00, 0x9d, 0x00, 0xe9, 0x00, 0x05, 0x00, 0x70, 0x00, 0x08, + 0x00, 0x6d, 0x00, 0x0838, 0x0834, 0x0830, 0x01f2, 0x01f3, 0x01f4}, + { + 52, 5260, 0x98, 0x01, 0x01, 0x02, 0x0e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xd9, 0x00, 0x05, 0x00, 0x70, + 0x00, 0x08, 0x00, 0x9d, 0x00, 0xd9, 0x00, 0x05, 0x00, 0x70, 0x00, 0x08, + 0x00, 0x6d, 0x00, 0x083c, 0x0838, 0x0834, 0x01f1, 0x01f2, 0x01f3}, + { + 54, 5270, 0x98, 0x01, 0x01, 0x02, 0x0f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, 0xff, 0xd8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x9c, 0x00, 0xd8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x6c, 0x00, 0x0840, 0x083c, 0x0838, 0x01f0, 0x01f1, 0x01f2}, + { + 56, 5280, 0x91, 0x01, 0x01, 0x02, 0x10, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, 0xff, 0xc8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x9c, 0x00, 0xc8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x6c, 0x00, 0x0844, 0x0840, 0x083c, 0x01f0, 0x01f0, 0x01f1}, + { + 58, 5290, 0x91, 0x01, 0x01, 0x02, 0x11, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0f, 0x00, 0xff, 0xc8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x9c, 0x00, 0xc8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x6c, 0x00, 0x0848, 0x0844, 0x0840, 0x01ef, 0x01f0, 0x01f0}, + { + 60, 5300, 0x8a, 0x01, 0x01, 0x02, 0x12, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xc8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x9c, 0x00, 0xc8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x6c, 0x00, 0x084c, 0x0848, 0x0844, 0x01ee, 0x01ef, 0x01f0}, + { + 62, 5310, 0x8a, 0x01, 0x01, 0x02, 0x13, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xc8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x9c, 0x00, 0xc8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x6c, 0x00, 0x0850, 0x084c, 0x0848, 0x01ed, 0x01ee, 0x01ef}, + { + 64, 5320, 0x83, 0x01, 0x01, 0x02, 0x14, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xb8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x9c, 0x00, 0xb8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x6c, 0x00, 0x0854, 0x0850, 0x084c, 0x01ec, 0x01ed, 0x01ee}, + { + 66, 5330, 0x83, 0x01, 0x01, 0x02, 0x15, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xb7, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x9b, 0x00, 0xb7, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x6b, 0x00, 0x0858, 0x0854, 0x0850, 0x01eb, 0x01ec, 0x01ed}, + { + 68, 5340, 0x7c, 0x01, 0x01, 0x02, 0x16, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xb7, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x9b, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x6b, 0x00, 0x085c, 0x0858, 0x0854, 0x01ea, 0x01eb, 0x01ec}, + { + 70, 5350, 0x7c, 0x01, 0x01, 0x02, 0x17, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xa7, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x9b, 0x00, 0xa7, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x6b, 0x00, 0x0860, 0x085c, 0x0858, 0x01e9, 0x01ea, 0x01eb}, + { + 72, 5360, 0x75, 0x01, 0x01, 0x02, 0x18, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xa6, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x9b, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x6b, 0x00, 0x0864, 0x0860, 0x085c, 0x01e8, 0x01e9, 0x01ea}, + { + 74, 5370, 0x75, 0x01, 0x01, 0x02, 0x19, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0xa6, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x9b, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x5b, 0x00, 0x0868, 0x0864, 0x0860, 0x01e7, 0x01e8, 0x01e9}, + { + 76, 5380, 0x6e, 0x01, 0x01, 0x02, 0x1a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0x96, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x9a, 0x00, 0x96, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x5a, 0x00, 0x086c, 0x0868, 0x0864, 0x01e6, 0x01e7, 0x01e8}, + { + 78, 5390, 0x6e, 0x01, 0x01, 0x02, 0x1b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8f, 0x0e, 0x00, 0xff, 0x95, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x9a, 0x00, 0x95, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x5a, 0x00, 0x0870, 0x086c, 0x0868, 0x01e5, 0x01e6, 0x01e7}, + { + 80, 5400, 0x67, 0x01, 0x01, 0x02, 0x1c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xc8, 0x95, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x9a, 0x00, 0x95, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x5a, 0x00, 0x0874, 0x0870, 0x086c, 0x01e5, 0x01e5, 0x01e6}, + { + 82, 5410, 0x67, 0x01, 0x01, 0x02, 0x1d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xc8, 0x95, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x9a, 0x00, 0x95, 0x00, 0x03, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x5a, 0x00, 0x0878, 0x0874, 0x0870, 0x01e4, 0x01e5, 0x01e5}, + { + 84, 5420, 0x61, 0x01, 0x01, 0x02, 0x1e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xc8, 0x95, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x9a, 0x00, 0x95, 0x00, 0x03, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x5a, 0x00, 0x087c, 0x0878, 0x0874, 0x01e3, 0x01e4, 0x01e5}, + { + 86, 5430, 0x61, 0x01, 0x01, 0x02, 0x1f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xc8, 0x85, 0x00, 0x02, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x99, 0x00, 0x85, 0x00, 0x02, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x59, 0x00, 0x0880, 0x087c, 0x0878, 0x01e2, 0x01e3, 0x01e4}, + { + 88, 5440, 0x5a, 0x01, 0x01, 0x02, 0x20, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x84, 0x00, 0x02, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x99, 0x00, 0x84, 0x00, 0x02, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x59, 0x00, 0x0884, 0x0880, 0x087c, 0x01e1, 0x01e2, 0x01e3}, + { + 90, 5450, 0x5a, 0x01, 0x01, 0x02, 0x21, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x84, 0x00, 0x02, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x99, 0x00, 0x84, 0x00, 0x02, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x59, 0x00, 0x0888, 0x0884, 0x0880, 0x01e0, 0x01e1, 0x01e2}, + { + 92, 5460, 0x53, 0x01, 0x01, 0x02, 0x22, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x84, 0x00, 0x02, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x99, 0x00, 0x84, 0x00, 0x02, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x088c, 0x0888, 0x0884, 0x01df, 0x01e0, 0x01e1}, + { + 94, 5470, 0x53, 0x01, 0x01, 0x02, 0x23, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x74, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x99, 0x00, 0x74, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x0890, 0x088c, 0x0888, 0x01de, 0x01df, 0x01e0}, + { + 96, 5480, 0x4d, 0x01, 0x01, 0x02, 0x24, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x0894, 0x0890, 0x088c, 0x01dd, 0x01de, 0x01df}, + { + 98, 5490, 0x4d, 0x01, 0x01, 0x02, 0x25, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0d, 0x00, 0xc8, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x0898, 0x0894, 0x0890, 0x01dd, 0x01dd, 0x01de}, + { + 100, 5500, 0x47, 0x01, 0x01, 0x02, 0x26, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x78, 0x00, 0x089c, 0x0898, 0x0894, 0x01dc, 0x01dd, 0x01dd}, + { + 102, 5510, 0x47, 0x01, 0x01, 0x02, 0x27, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x78, 0x00, 0x08a0, 0x089c, 0x0898, 0x01db, 0x01dc, 0x01dd}, + { + 104, 5520, 0x40, 0x01, 0x01, 0x02, 0x28, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x78, 0x00, 0x08a4, 0x08a0, 0x089c, 0x01da, 0x01db, 0x01dc}, + { + 106, 5530, 0x40, 0x01, 0x01, 0x02, 0x29, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x63, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x03, 0x00, 0x98, 0x00, 0x63, 0x00, 0x01, 0x00, 0x70, 0x00, 0x03, + 0x00, 0x78, 0x00, 0x08a8, 0x08a4, 0x08a0, 0x01d9, 0x01da, 0x01db}, + { + 108, 5540, 0x3a, 0x01, 0x01, 0x02, 0x2a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x62, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x03, 0x00, 0x97, 0x00, 0x62, 0x00, 0x00, 0x00, 0x70, 0x00, 0x03, + 0x00, 0x77, 0x00, 0x08ac, 0x08a8, 0x08a4, 0x01d8, 0x01d9, 0x01da}, + { + 110, 5550, 0x3a, 0x01, 0x01, 0x02, 0x2b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x62, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x03, 0x00, 0x97, 0x00, 0x62, 0x00, 0x00, 0x00, 0x70, 0x00, 0x03, + 0x00, 0x77, 0x00, 0x08b0, 0x08ac, 0x08a8, 0x01d7, 0x01d8, 0x01d9}, + { + 112, 5560, 0x34, 0x01, 0x01, 0x02, 0x2c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x62, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x03, 0x00, 0x97, 0x00, 0x62, 0x00, 0x00, 0x00, 0x70, 0x00, 0x03, + 0x00, 0x77, 0x00, 0x08b4, 0x08b0, 0x08ac, 0x01d7, 0x01d7, 0x01d8}, + { + 114, 5570, 0x34, 0x01, 0x01, 0x02, 0x2d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x52, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x96, 0x00, 0x52, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x76, 0x00, 0x08b8, 0x08b4, 0x08b0, 0x01d6, 0x01d7, 0x01d7}, + { + 116, 5580, 0x2e, 0x01, 0x01, 0x02, 0x2e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x52, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x96, 0x00, 0x52, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x76, 0x00, 0x08bc, 0x08b8, 0x08b4, 0x01d5, 0x01d6, 0x01d7}, + { + 118, 5590, 0x2e, 0x01, 0x01, 0x02, 0x2f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8d, 0x0b, 0x00, 0x84, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x76, 0x00, 0x08c0, 0x08bc, 0x08b8, 0x01d4, 0x01d5, 0x01d6}, + { + 120, 5600, 0x28, 0x01, 0x01, 0x02, 0x30, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x76, 0x00, 0x08c4, 0x08c0, 0x08bc, 0x01d3, 0x01d4, 0x01d5}, + { + 122, 5610, 0x28, 0x01, 0x01, 0x02, 0x31, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x76, 0x00, 0x08c8, 0x08c4, 0x08c0, 0x01d2, 0x01d3, 0x01d4}, + { + 124, 5620, 0x21, 0x01, 0x01, 0x02, 0x32, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x76, 0x00, 0x08cc, 0x08c8, 0x08c4, 0x01d2, 0x01d2, 0x01d3}, + { + 126, 5630, 0x21, 0x01, 0x01, 0x02, 0x33, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x76, 0x00, 0x08d0, 0x08cc, 0x08c8, 0x01d1, 0x01d2, 0x01d2}, + { + 128, 5640, 0x1c, 0x01, 0x01, 0x02, 0x34, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x95, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x75, 0x00, 0x08d4, 0x08d0, 0x08cc, 0x01d0, 0x01d1, 0x01d2}, + { + 130, 5650, 0x1c, 0x01, 0x01, 0x02, 0x35, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x50, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x95, 0x00, 0x50, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x75, 0x00, 0x08d8, 0x08d4, 0x08d0, 0x01cf, 0x01d0, 0x01d1}, + { + 132, 5660, 0x16, 0x01, 0x01, 0x02, 0x36, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x50, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x95, 0x00, 0x50, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x75, 0x00, 0x08dc, 0x08d8, 0x08d4, 0x01ce, 0x01cf, 0x01d0}, + { + 134, 5670, 0x16, 0x01, 0x01, 0x02, 0x37, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x74, 0x00, 0x08e0, 0x08dc, 0x08d8, 0x01ce, 0x01ce, 0x01cf}, + { + 136, 5680, 0x10, 0x01, 0x01, 0x02, 0x38, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x74, 0x00, 0x08e4, 0x08e0, 0x08dc, 0x01cd, 0x01ce, 0x01ce}, + { + 138, 5690, 0x10, 0x01, 0x01, 0x02, 0x39, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8b, 0x09, 0x00, 0x70, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x74, 0x00, 0x08e8, 0x08e4, 0x08e0, 0x01cc, 0x01cd, 0x01ce}, + { + 140, 5700, 0x0a, 0x01, 0x01, 0x02, 0x3a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x74, 0x00, 0x08ec, 0x08e8, 0x08e4, 0x01cb, 0x01cc, 0x01cd}, + { + 142, 5710, 0x0a, 0x01, 0x01, 0x02, 0x3b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x74, 0x00, 0x08f0, 0x08ec, 0x08e8, 0x01ca, 0x01cb, 0x01cc}, + { + 144, 5720, 0x0a, 0x01, 0x01, 0x02, 0x3c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x74, 0x00, 0x08f4, 0x08f0, 0x08ec, 0x01c9, 0x01ca, 0x01cb}, + { + 145, 5725, 0x03, 0x01, 0x02, 0x04, 0x79, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x74, 0x00, 0x08f6, 0x08f2, 0x08ee, 0x01c9, 0x01ca, 0x01cb}, + { + 146, 5730, 0x0a, 0x01, 0x01, 0x02, 0x3d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x84, 0x00, 0x08f8, 0x08f4, 0x08f0, 0x01c9, 0x01c9, 0x01ca}, + { + 147, 5735, 0x03, 0x01, 0x02, 0x04, 0x7b, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x83, 0x00, 0x08fa, 0x08f6, 0x08f2, 0x01c8, 0x01c9, 0x01ca}, + { + 148, 5740, 0x0a, 0x01, 0x01, 0x02, 0x3e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x83, 0x00, 0x08fc, 0x08f8, 0x08f4, 0x01c8, 0x01c9, 0x01c9}, + { + 149, 5745, 0xfe, 0x00, 0x02, 0x04, 0x7d, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x83, 0x00, 0x08fe, 0x08fa, 0x08f6, 0x01c8, 0x01c8, 0x01c9}, + { + 150, 5750, 0x0a, 0x01, 0x01, 0x02, 0x3f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x83, 0x00, 0x0900, 0x08fc, 0x08f8, 0x01c7, 0x01c8, 0x01c9}, + { + 151, 5755, 0xfe, 0x00, 0x02, 0x04, 0x7f, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x83, 0x00, 0x0902, 0x08fe, 0x08fa, 0x01c7, 0x01c8, 0x01c8}, + { + 152, 5760, 0x0a, 0x01, 0x01, 0x02, 0x40, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x20, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x83, 0x00, 0x0904, 0x0900, 0x08fc, 0x01c6, 0x01c7, 0x01c8}, + { + 153, 5765, 0xf8, 0x00, 0x02, 0x04, 0x81, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x20, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x82, 0x00, 0x0906, 0x0902, 0x08fe, 0x01c6, 0x01c7, 0x01c8}, + { + 154, 5770, 0x0a, 0x01, 0x01, 0x02, 0x41, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x20, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x82, 0x00, 0x0908, 0x0904, 0x0900, 0x01c6, 0x01c6, 0x01c7}, + { + 155, 5775, 0xf8, 0x00, 0x02, 0x04, 0x83, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x20, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x82, 0x00, 0x090a, 0x0906, 0x0902, 0x01c5, 0x01c6, 0x01c7}, + { + 156, 5780, 0x0a, 0x01, 0x01, 0x02, 0x42, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x8a, 0x06, 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x82, 0x00, 0x090c, 0x0908, 0x0904, 0x01c5, 0x01c6, 0x01c6}, + { + 157, 5785, 0xf2, 0x00, 0x02, 0x04, 0x85, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x82, 0x00, 0x090e, 0x090a, 0x0906, 0x01c4, 0x01c5, 0x01c6}, + { + 158, 5790, 0x0a, 0x01, 0x01, 0x02, 0x43, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x82, 0x00, 0x0910, 0x090c, 0x0908, 0x01c4, 0x01c5, 0x01c6}, + { + 159, 5795, 0xf2, 0x00, 0x02, 0x04, 0x87, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x8a, 0x06, 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x82, 0x00, 0x0912, 0x090e, 0x090a, 0x01c4, 0x01c4, 0x01c5}, + { + 160, 5800, 0x0a, 0x01, 0x01, 0x02, 0x44, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x82, 0x00, 0x0914, 0x0910, 0x090c, 0x01c3, 0x01c4, 0x01c5}, + { + 161, 5805, 0xed, 0x00, 0x02, 0x04, 0x89, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x82, 0x00, 0x0916, 0x0912, 0x090e, 0x01c3, 0x01c4, 0x01c4}, + { + 162, 5810, 0x0a, 0x01, 0x01, 0x02, 0x45, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x82, 0x00, 0x0918, 0x0914, 0x0910, 0x01c2, 0x01c3, 0x01c4}, + { + 163, 5815, 0xed, 0x00, 0x02, 0x04, 0x8b, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x82, 0x00, 0x091a, 0x0916, 0x0912, 0x01c2, 0x01c3, 0x01c4}, + { + 164, 5820, 0x0a, 0x01, 0x01, 0x02, 0x46, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x82, 0x00, 0x091c, 0x0918, 0x0914, 0x01c2, 0x01c2, 0x01c3}, + { + 165, 5825, 0xed, 0x00, 0x02, 0x04, 0x8d, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x82, 0x00, 0x091e, 0x091a, 0x0916, 0x01c1, 0x01c2, 0x01c3}, + { + 166, 5830, 0x0a, 0x01, 0x01, 0x02, 0x47, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x72, 0x00, 0x0920, 0x091c, 0x0918, 0x01c1, 0x01c2, 0x01c2}, + { + 168, 5840, 0x0a, 0x01, 0x01, 0x02, 0x48, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x72, 0x00, 0x0924, 0x0920, 0x091c, 0x01c0, 0x01c1, 0x01c2}, + { + 170, 5850, 0xe0, 0x00, 0x01, 0x02, 0x49, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x72, 0x00, 0x0928, 0x0924, 0x0920, 0x01bf, 0x01c0, 0x01c1}, + { + 172, 5860, 0xde, 0x00, 0x01, 0x02, 0x4a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x72, 0x00, 0x092c, 0x0928, 0x0924, 0x01bf, 0x01bf, 0x01c0}, + { + 174, 5870, 0xdb, 0x00, 0x01, 0x02, 0x4b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x71, 0x00, 0x0930, 0x092c, 0x0928, 0x01be, 0x01bf, 0x01bf}, + { + 176, 5880, 0xd8, 0x00, 0x01, 0x02, 0x4c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x71, 0x00, 0x0934, 0x0930, 0x092c, 0x01bd, 0x01be, 0x01bf}, + { + 178, 5890, 0xd6, 0x00, 0x01, 0x02, 0x4d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x88, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x71, 0x00, 0x0938, 0x0934, 0x0930, 0x01bc, 0x01bd, 0x01be}, + { + 180, 5900, 0xd3, 0x00, 0x01, 0x02, 0x4e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x87, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x71, 0x00, 0x093c, 0x0938, 0x0934, 0x01bc, 0x01bc, 0x01bd}, + { + 182, 5910, 0xd6, 0x00, 0x01, 0x02, 0x4f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x87, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x71, 0x00, 0x0940, 0x093c, 0x0938, 0x01bb, 0x01bc, 0x01bc}, + { + 1, 2412, 0x00, 0x01, 0x03, 0x09, 0x6c, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x04, 0x04, 0x04, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0b, 0x00, 0x1f, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0b, 0x03c9, 0x03c5, 0x03c1, 0x043a, 0x043f, 0x0443}, + { + 2, 2417, 0x00, 0x01, 0x03, 0x09, 0x71, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0a, 0x00, 0x1f, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0a, 0x03cb, 0x03c7, 0x03c3, 0x0438, 0x043d, 0x0441}, + { + 3, 2422, 0x00, 0x01, 0x03, 0x09, 0x76, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0a, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0a, 0x03cd, 0x03c9, 0x03c5, 0x0436, 0x043a, 0x043f}, + { + 4, 2427, 0x00, 0x01, 0x03, 0x09, 0x7b, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x0a, 0x00, 0x0d, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x0a, 0x03cf, 0x03cb, 0x03c7, 0x0434, 0x0438, 0x043d}, + { + 5, 2432, 0x00, 0x01, 0x03, 0x09, 0x80, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x0a, 0x03d1, 0x03cd, 0x03c9, 0x0431, 0x0436, 0x043a}, + { + 6, 2437, 0x00, 0x01, 0x03, 0x09, 0x85, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x0a, 0x00, 0x0b, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x0a, 0x03d3, 0x03cf, 0x03cb, 0x042f, 0x0434, 0x0438}, + { + 7, 2442, 0x00, 0x01, 0x03, 0x09, 0x8a, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x09, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x0a, 0x03d5, 0x03d1, 0x03cd, 0x042d, 0x0431, 0x0436}, + { + 8, 2447, 0x00, 0x01, 0x03, 0x09, 0x8f, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x09, 0x00, 0x08, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x09, 0x03d7, 0x03d3, 0x03cf, 0x042b, 0x042f, 0x0434}, + { + 9, 2452, 0x00, 0x01, 0x03, 0x09, 0x94, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x07, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x09, 0x00, 0x07, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x09, 0x03d9, 0x03d5, 0x03d1, 0x0429, 0x042d, 0x0431}, + { + 10, 2457, 0x00, 0x01, 0x03, 0x09, 0x99, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x09, 0x00, 0x06, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x09, 0x03db, 0x03d7, 0x03d3, 0x0427, 0x042b, 0x042f}, + { + 11, 2462, 0x00, 0x01, 0x03, 0x09, 0x9e, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x05, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x09, 0x00, 0x05, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x09, 0x03dd, 0x03d9, 0x03d5, 0x0424, 0x0429, 0x042d}, + { + 12, 2467, 0x00, 0x01, 0x03, 0x09, 0xa3, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x08, 0x03df, 0x03db, 0x03d7, 0x0422, 0x0427, 0x042b}, + { + 13, 2472, 0x00, 0x01, 0x03, 0x09, 0xa8, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x08, 0x00, 0x03, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x08, 0x03e1, 0x03dd, 0x03d9, 0x0420, 0x0424, 0x0429}, + { + 14, 2484, 0xff, 0x01, 0x03, 0x09, 0xb4, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x08, 0x03e6, 0x03e2, 0x03de, 0x041b, 0x041f, 0x0424} +}; + +static const struct chan_info_nphy_radio205x chan_info_nphyrev6_2056v6[] = { + { + 184, 4920, 0xff, 0x01, 0x01, 0x01, 0xec, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07b4, 0x07b0, 0x07ac, 0x0214, 0x0215, 0x0216}, + { + 186, 4930, 0xff, 0x01, 0x01, 0x01, 0xed, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07b8, 0x07b4, 0x07b0, 0x0213, 0x0214, 0x0215}, + { + 188, 4940, 0xff, 0x01, 0x01, 0x01, 0xee, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07bc, 0x07b8, 0x07b4, 0x0212, 0x0213, 0x0214}, + { + 190, 4950, 0xff, 0x01, 0x01, 0x01, 0xef, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07c0, 0x07bc, 0x07b8, 0x0211, 0x0212, 0x0213}, + { + 192, 4960, 0xff, 0x01, 0x01, 0x01, 0xf0, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07c4, 0x07c0, 0x07bc, 0x020f, 0x0211, 0x0212}, + { + 194, 4970, 0xff, 0x01, 0x01, 0x01, 0xf1, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07c8, 0x07c4, 0x07c0, 0x020e, 0x020f, 0x0211}, + { + 196, 4980, 0xff, 0x01, 0x01, 0x01, 0xf2, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07cc, 0x07c8, 0x07c4, 0x020d, 0x020e, 0x020f}, + { + 198, 4990, 0xff, 0x01, 0x01, 0x01, 0xf3, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07d0, 0x07cc, 0x07c8, 0x020c, 0x020d, 0x020e}, + { + 200, 5000, 0xff, 0x01, 0x01, 0x01, 0xf4, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07d4, 0x07d0, 0x07cc, 0x020b, 0x020c, 0x020d}, + { + 202, 5010, 0xff, 0x01, 0x01, 0x01, 0xf5, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07d8, 0x07d4, 0x07d0, 0x020a, 0x020b, 0x020c}, + { + 204, 5020, 0xf7, 0x01, 0x01, 0x01, 0xf6, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07dc, 0x07d8, 0x07d4, 0x0209, 0x020a, 0x020b}, + { + 206, 5030, 0xf7, 0x01, 0x01, 0x01, 0xf7, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07e0, 0x07dc, 0x07d8, 0x0208, 0x0209, 0x020a}, + { + 208, 5040, 0xef, 0x01, 0x01, 0x01, 0xf8, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07e4, 0x07e0, 0x07dc, 0x0207, 0x0208, 0x0209}, + { + 210, 5050, 0xef, 0x01, 0x01, 0x01, 0xf9, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07e8, 0x07e4, 0x07e0, 0x0206, 0x0207, 0x0208}, + { + 212, 5060, 0xe6, 0x01, 0x01, 0x01, 0xfa, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07ec, 0x07e8, 0x07e4, 0x0205, 0x0206, 0x0207}, + { + 214, 5070, 0xe6, 0x01, 0x01, 0x01, 0xfb, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfd, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07f0, 0x07ec, 0x07e8, 0x0204, 0x0205, 0x0206}, + { + 216, 5080, 0xde, 0x01, 0x01, 0x01, 0xfc, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfd, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07f4, 0x07f0, 0x07ec, 0x0203, 0x0204, 0x0205}, + { + 218, 5090, 0xde, 0x01, 0x01, 0x01, 0xfd, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfd, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07f8, 0x07f4, 0x07f0, 0x0202, 0x0203, 0x0204}, + { + 220, 5100, 0xd6, 0x01, 0x01, 0x01, 0xfe, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfd, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07fc, 0x07f8, 0x07f4, 0x0201, 0x0202, 0x0203}, + { + 222, 5110, 0xd6, 0x01, 0x01, 0x01, 0xff, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x0800, 0x07fc, 0x07f8, 0x0200, 0x0201, 0x0202}, + { + 224, 5120, 0xce, 0x01, 0x01, 0x02, 0x00, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x0804, 0x0800, 0x07fc, 0x01ff, 0x0200, 0x0201}, + { + 226, 5130, 0xce, 0x01, 0x01, 0x02, 0x01, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x0808, 0x0804, 0x0800, 0x01fe, 0x01ff, 0x0200}, + { + 228, 5140, 0xc6, 0x01, 0x01, 0x02, 0x02, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfb, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfb, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x080c, 0x0808, 0x0804, 0x01fd, 0x01fe, 0x01ff}, + { + 32, 5160, 0xbe, 0x01, 0x01, 0x02, 0x04, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfa, 0x00, 0x07, 0x00, 0x77, + 0x00, 0x0e, 0x00, 0x6f, 0x00, 0xfa, 0x00, 0x07, 0x00, 0x77, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x0814, 0x0810, 0x080c, 0x01fb, 0x01fc, 0x01fd}, + { + 34, 5170, 0xbe, 0x01, 0x01, 0x02, 0x05, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfa, 0x00, 0x07, 0x00, 0x77, + 0x00, 0x0e, 0x00, 0x6f, 0x00, 0xfa, 0x00, 0x07, 0x00, 0x77, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x0818, 0x0814, 0x0810, 0x01fa, 0x01fb, 0x01fc}, + { + 36, 5180, 0xb6, 0x01, 0x01, 0x02, 0x06, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xf9, 0x00, 0x06, 0x00, 0x77, + 0x00, 0x0e, 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x06, 0x00, 0x77, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x081c, 0x0818, 0x0814, 0x01f9, 0x01fa, 0x01fb}, + { + 38, 5190, 0xb6, 0x01, 0x01, 0x02, 0x07, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xf9, 0x00, 0x06, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x06, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0820, 0x081c, 0x0818, 0x01f8, 0x01f9, 0x01fa}, + { + 40, 5200, 0xaf, 0x01, 0x01, 0x02, 0x08, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xf9, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0824, 0x0820, 0x081c, 0x01f7, 0x01f8, 0x01f9}, + { + 42, 5210, 0xaf, 0x01, 0x01, 0x02, 0x09, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xf9, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0828, 0x0824, 0x0820, 0x01f6, 0x01f7, 0x01f8}, + { + 44, 5220, 0xa7, 0x01, 0x01, 0x02, 0x0a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, 0xfe, 0xd8, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xd8, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x082c, 0x0828, 0x0824, 0x01f5, 0x01f6, 0x01f7}, + { + 46, 5230, 0xa7, 0x01, 0x01, 0x02, 0x0b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, 0xee, 0xd8, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xd8, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0830, 0x082c, 0x0828, 0x01f4, 0x01f5, 0x01f6}, + { + 48, 5240, 0xa0, 0x01, 0x01, 0x02, 0x0c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, 0xee, 0xc8, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xc8, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0834, 0x0830, 0x082c, 0x01f3, 0x01f4, 0x01f5}, + { + 50, 5250, 0xa0, 0x01, 0x01, 0x02, 0x0d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, 0xed, 0xc7, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xc7, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0838, 0x0834, 0x0830, 0x01f2, 0x01f3, 0x01f4}, + { + 52, 5260, 0x98, 0x01, 0x01, 0x02, 0x0e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0e, 0x00, 0xed, 0xc7, 0x00, 0x04, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xc7, 0x00, 0x04, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x083c, 0x0838, 0x0834, 0x01f1, 0x01f2, 0x01f3}, + { + 54, 5270, 0x98, 0x01, 0x01, 0x02, 0x0f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8e, 0x0e, 0x00, 0xed, 0xc7, 0x00, 0x04, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xc7, 0x00, 0x04, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x0840, 0x083c, 0x0838, 0x01f0, 0x01f1, 0x01f2}, + { + 56, 5280, 0x91, 0x01, 0x01, 0x02, 0x10, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x0844, 0x0840, 0x083c, 0x01f0, 0x01f0, 0x01f1}, + { + 58, 5290, 0x91, 0x01, 0x01, 0x02, 0x11, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x0848, 0x0844, 0x0840, 0x01ef, 0x01f0, 0x01f0}, + { + 60, 5300, 0x8a, 0x01, 0x01, 0x02, 0x12, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x084c, 0x0848, 0x0844, 0x01ee, 0x01ef, 0x01f0}, + { + 62, 5310, 0x8a, 0x01, 0x01, 0x02, 0x13, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x0850, 0x084c, 0x0848, 0x01ed, 0x01ee, 0x01ef}, + { + 64, 5320, 0x83, 0x01, 0x01, 0x02, 0x14, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdb, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x0854, 0x0850, 0x084c, 0x01ec, 0x01ed, 0x01ee}, + { + 66, 5330, 0x83, 0x01, 0x01, 0x02, 0x15, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xcb, 0xa6, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0b, 0x00, 0x6f, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x0858, 0x0854, 0x0850, 0x01eb, 0x01ec, 0x01ed}, + { + 68, 5340, 0x7c, 0x01, 0x01, 0x02, 0x16, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xca, 0xa6, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0b, 0x00, 0x6f, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x085c, 0x0858, 0x0854, 0x01ea, 0x01eb, 0x01ec}, + { + 70, 5350, 0x7c, 0x01, 0x01, 0x02, 0x17, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, 0xca, 0xa6, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0b, 0x00, 0x6f, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x0860, 0x085c, 0x0858, 0x01e9, 0x01ea, 0x01eb}, + { + 72, 5360, 0x75, 0x01, 0x01, 0x02, 0x18, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, 0xc9, 0x95, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x95, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0864, 0x0860, 0x085c, 0x01e8, 0x01e9, 0x01ea}, + { + 74, 5370, 0x75, 0x01, 0x01, 0x02, 0x19, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, 0xc9, 0x95, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x95, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0868, 0x0864, 0x0860, 0x01e7, 0x01e8, 0x01e9}, + { + 76, 5380, 0x6e, 0x01, 0x01, 0x02, 0x1a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xb8, 0x95, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x95, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x086c, 0x0868, 0x0864, 0x01e6, 0x01e7, 0x01e8}, + { + 78, 5390, 0x6e, 0x01, 0x01, 0x02, 0x1b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xb8, 0x84, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0870, 0x086c, 0x0868, 0x01e5, 0x01e6, 0x01e7}, + { + 80, 5400, 0x67, 0x01, 0x01, 0x02, 0x1c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xb8, 0x84, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0874, 0x0870, 0x086c, 0x01e5, 0x01e5, 0x01e6}, + { + 82, 5410, 0x67, 0x01, 0x01, 0x02, 0x1d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xb7, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0878, 0x0874, 0x0870, 0x01e4, 0x01e5, 0x01e5}, + { + 84, 5420, 0x61, 0x01, 0x01, 0x02, 0x1e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xa7, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x087c, 0x0878, 0x0874, 0x01e3, 0x01e4, 0x01e5}, + { + 86, 5430, 0x61, 0x01, 0x01, 0x02, 0x1f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0b, 0x00, 0xa6, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0880, 0x087c, 0x0878, 0x01e2, 0x01e3, 0x01e4}, + { + 88, 5440, 0x5a, 0x01, 0x01, 0x02, 0x20, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, 0xa6, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x0884, 0x0880, 0x087c, 0x01e1, 0x01e2, 0x01e3}, + { + 90, 5450, 0x5a, 0x01, 0x01, 0x02, 0x21, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, 0x95, 0x84, 0x00, 0x01, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x01, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x0888, 0x0884, 0x0880, 0x01e0, 0x01e1, 0x01e2}, + { + 92, 5460, 0x53, 0x01, 0x01, 0x02, 0x22, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, 0x95, 0x84, 0x00, 0x01, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x01, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x088c, 0x0888, 0x0884, 0x01df, 0x01e0, 0x01e1}, + { + 94, 5470, 0x53, 0x01, 0x01, 0x02, 0x23, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, 0x94, 0x73, 0x00, 0x01, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x01, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x0890, 0x088c, 0x0888, 0x01de, 0x01df, 0x01e0}, + { + 96, 5480, 0x4d, 0x01, 0x01, 0x02, 0x24, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x84, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x0894, 0x0890, 0x088c, 0x01dd, 0x01de, 0x01df}, + { + 98, 5490, 0x4d, 0x01, 0x01, 0x02, 0x25, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x83, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x0898, 0x0894, 0x0890, 0x01dd, 0x01dd, 0x01de}, + { + 100, 5500, 0x47, 0x01, 0x01, 0x02, 0x26, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x82, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x089c, 0x0898, 0x0894, 0x01dc, 0x01dd, 0x01dd}, + { + 102, 5510, 0x47, 0x01, 0x01, 0x02, 0x27, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x82, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08a0, 0x089c, 0x0898, 0x01db, 0x01dc, 0x01dd}, + { + 104, 5520, 0x40, 0x01, 0x01, 0x02, 0x28, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x72, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08a4, 0x08a0, 0x089c, 0x01da, 0x01db, 0x01dc}, + { + 106, 5530, 0x40, 0x01, 0x01, 0x02, 0x29, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x09, 0x00, 0x72, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08a8, 0x08a4, 0x08a0, 0x01d9, 0x01da, 0x01db}, + { + 108, 5540, 0x3a, 0x01, 0x01, 0x02, 0x2a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x09, 0x00, 0x71, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08ac, 0x08a8, 0x08a4, 0x01d8, 0x01d9, 0x01da}, + { + 110, 5550, 0x3a, 0x01, 0x01, 0x02, 0x2b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08b0, 0x08ac, 0x08a8, 0x01d7, 0x01d8, 0x01d9}, + { + 112, 5560, 0x34, 0x01, 0x01, 0x02, 0x2c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08b4, 0x08b0, 0x08ac, 0x01d7, 0x01d7, 0x01d8}, + { + 114, 5570, 0x34, 0x01, 0x01, 0x02, 0x2d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, 0x61, 0x62, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08b8, 0x08b4, 0x08b0, 0x01d6, 0x01d7, 0x01d7}, + { + 116, 5580, 0x2e, 0x01, 0x01, 0x02, 0x2e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x08, 0x00, 0x60, 0x62, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x08bc, 0x08b8, 0x08b4, 0x01d5, 0x01d6, 0x01d7}, + { + 118, 5590, 0x2e, 0x01, 0x01, 0x02, 0x2f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x08, 0x00, 0x50, 0x61, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00, 0x61, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x08c0, 0x08bc, 0x08b8, 0x01d4, 0x01d5, 0x01d6}, + { + 120, 5600, 0x28, 0x01, 0x01, 0x02, 0x30, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, 0x50, 0x51, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00, 0x51, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x08c4, 0x08c0, 0x08bc, 0x01d3, 0x01d4, 0x01d5}, + { + 122, 5610, 0x28, 0x01, 0x01, 0x02, 0x31, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, 0x50, 0x51, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00, 0x51, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x08c8, 0x08c4, 0x08c0, 0x01d2, 0x01d3, 0x01d4}, + { + 124, 5620, 0x21, 0x01, 0x01, 0x02, 0x32, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, 0x50, 0x50, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00, 0x50, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x08cc, 0x08c8, 0x08c4, 0x01d2, 0x01d2, 0x01d3}, + { + 126, 5630, 0x21, 0x01, 0x01, 0x02, 0x33, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x50, 0x50, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00, 0x50, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x08d0, 0x08cc, 0x08c8, 0x01d1, 0x01d2, 0x01d2}, + { + 128, 5640, 0x1c, 0x01, 0x01, 0x02, 0x34, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x40, 0x50, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00, 0x50, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x08d4, 0x08d0, 0x08cc, 0x01d0, 0x01d1, 0x01d2}, + { + 130, 5650, 0x1c, 0x01, 0x01, 0x02, 0x35, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00, 0x40, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x08d8, 0x08d4, 0x08d0, 0x01cf, 0x01d0, 0x01d1}, + { + 132, 5660, 0x16, 0x01, 0x01, 0x02, 0x36, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00, 0x40, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x08dc, 0x08d8, 0x08d4, 0x01ce, 0x01cf, 0x01d0}, + { + 134, 5670, 0x16, 0x01, 0x01, 0x02, 0x37, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x40, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x08e0, 0x08dc, 0x08d8, 0x01ce, 0x01ce, 0x01cf}, + { + 136, 5680, 0x10, 0x01, 0x01, 0x02, 0x38, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x08e4, 0x08e0, 0x08dc, 0x01cd, 0x01ce, 0x01ce}, + { + 138, 5690, 0x10, 0x01, 0x01, 0x02, 0x39, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x08e8, 0x08e4, 0x08e0, 0x01cc, 0x01cd, 0x01ce}, + { + 140, 5700, 0x0a, 0x01, 0x01, 0x02, 0x3a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x08ec, 0x08e8, 0x08e4, 0x01cb, 0x01cc, 0x01cd}, + { + 142, 5710, 0x0a, 0x01, 0x01, 0x02, 0x3b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x08f0, 0x08ec, 0x08e8, 0x01ca, 0x01cb, 0x01cc}, + { + 144, 5720, 0x0a, 0x01, 0x01, 0x02, 0x3c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x08f4, 0x08f0, 0x08ec, 0x01c9, 0x01ca, 0x01cb}, + { + 145, 5725, 0x03, 0x01, 0x02, 0x04, 0x79, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x08f6, 0x08f2, 0x08ee, 0x01c9, 0x01ca, 0x01cb}, + { + 146, 5730, 0x0a, 0x01, 0x01, 0x02, 0x3d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x08f8, 0x08f4, 0x08f0, 0x01c9, 0x01c9, 0x01ca}, + { + 147, 5735, 0x03, 0x01, 0x02, 0x04, 0x7b, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6d, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6d, 0x00, 0x08fa, 0x08f6, 0x08f2, 0x01c8, 0x01c9, 0x01ca}, + { + 148, 5740, 0x0a, 0x01, 0x01, 0x02, 0x3e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6d, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6d, 0x00, 0x08fc, 0x08f8, 0x08f4, 0x01c8, 0x01c9, 0x01c9}, + { + 149, 5745, 0xfe, 0x00, 0x02, 0x04, 0x7d, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6d, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6d, 0x00, 0x08fe, 0x08fa, 0x08f6, 0x01c8, 0x01c8, 0x01c9}, + { + 150, 5750, 0x0a, 0x01, 0x01, 0x02, 0x3f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6d, 0x00, 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6d, 0x00, 0x0900, 0x08fc, 0x08f8, 0x01c7, 0x01c8, 0x01c9}, + { + 151, 5755, 0xfe, 0x00, 0x02, 0x04, 0x7f, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6c, 0x00, 0x0902, 0x08fe, 0x08fa, 0x01c7, 0x01c8, 0x01c8}, + { + 152, 5760, 0x0a, 0x01, 0x01, 0x02, 0x40, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x05, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6c, 0x00, 0x0904, 0x0900, 0x08fc, 0x01c6, 0x01c7, 0x01c8}, + { + 153, 5765, 0xf8, 0x00, 0x02, 0x04, 0x81, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x05, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6c, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6c, 0x00, 0x0906, 0x0902, 0x08fe, 0x01c6, 0x01c7, 0x01c8}, + { + 154, 5770, 0x0a, 0x01, 0x01, 0x02, 0x41, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x0908, 0x0904, 0x0900, 0x01c6, 0x01c6, 0x01c7}, + { + 155, 5775, 0xf8, 0x00, 0x02, 0x04, 0x83, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x090a, 0x0906, 0x0902, 0x01c5, 0x01c6, 0x01c7}, + { + 156, 5780, 0x0a, 0x01, 0x01, 0x02, 0x42, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x090c, 0x0908, 0x0904, 0x01c5, 0x01c6, 0x01c6}, + { + 157, 5785, 0xf2, 0x00, 0x02, 0x04, 0x85, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x090e, 0x090a, 0x0906, 0x01c4, 0x01c5, 0x01c6}, + { + 158, 5790, 0x0a, 0x01, 0x01, 0x02, 0x43, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x0910, 0x090c, 0x0908, 0x01c4, 0x01c5, 0x01c6}, + { + 159, 5795, 0xf2, 0x00, 0x02, 0x04, 0x87, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x0912, 0x090e, 0x090a, 0x01c4, 0x01c4, 0x01c5}, + { + 160, 5800, 0x0a, 0x01, 0x01, 0x02, 0x44, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x0914, 0x0910, 0x090c, 0x01c3, 0x01c4, 0x01c5}, + { + 161, 5805, 0xed, 0x00, 0x02, 0x04, 0x89, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x0916, 0x0912, 0x090e, 0x01c3, 0x01c4, 0x01c4}, + { + 162, 5810, 0x0a, 0x01, 0x01, 0x02, 0x45, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x0918, 0x0914, 0x0910, 0x01c2, 0x01c3, 0x01c4}, + { + 163, 5815, 0xed, 0x00, 0x02, 0x04, 0x8b, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x091a, 0x0916, 0x0912, 0x01c2, 0x01c3, 0x01c4}, + { + 164, 5820, 0x0a, 0x01, 0x01, 0x02, 0x46, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x091c, 0x0918, 0x0914, 0x01c2, 0x01c2, 0x01c3}, + { + 165, 5825, 0xed, 0x00, 0x02, 0x04, 0x8d, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x69, 0x00, 0x091e, 0x091a, 0x0916, 0x01c1, 0x01c2, 0x01c3}, + { + 166, 5830, 0x0a, 0x01, 0x01, 0x02, 0x47, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x69, 0x00, 0x0920, 0x091c, 0x0918, 0x01c1, 0x01c2, 0x01c2}, + { + 168, 5840, 0x0a, 0x01, 0x01, 0x02, 0x48, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x0924, 0x0920, 0x091c, 0x01c0, 0x01c1, 0x01c2}, + { + 170, 5850, 0xe0, 0x00, 0x01, 0x02, 0x49, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x0928, 0x0924, 0x0920, 0x01bf, 0x01c0, 0x01c1}, + { + 172, 5860, 0xde, 0x00, 0x01, 0x02, 0x4a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x092c, 0x0928, 0x0924, 0x01bf, 0x01bf, 0x01c0}, + { + 174, 5870, 0xdb, 0x00, 0x01, 0x02, 0x4b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x0930, 0x092c, 0x0928, 0x01be, 0x01bf, 0x01bf}, + { + 176, 5880, 0xd8, 0x00, 0x01, 0x02, 0x4c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x0934, 0x0930, 0x092c, 0x01bd, 0x01be, 0x01bf}, + { + 178, 5890, 0xd6, 0x00, 0x01, 0x02, 0x4d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x0938, 0x0934, 0x0930, 0x01bc, 0x01bd, 0x01be}, + { + 180, 5900, 0xd3, 0x00, 0x01, 0x02, 0x4e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x093c, 0x0938, 0x0934, 0x01bc, 0x01bc, 0x01bd}, + { + 182, 5910, 0xd6, 0x00, 0x01, 0x02, 0x4f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x0940, 0x093c, 0x0938, 0x01bb, 0x01bc, 0x01bc}, + { + 1, 2412, 0x00, 0x01, 0x03, 0x09, 0x6c, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x04, 0x04, 0x04, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x78, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0b, 0x00, 0x0a, 0x00, 0x78, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0b, 0x00, 0x0a, 0x03c9, 0x03c5, 0x03c1, 0x043a, 0x043f, 0x0443}, + { + 2, 2417, 0x00, 0x01, 0x03, 0x09, 0x71, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x78, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0b, 0x00, 0x0a, 0x00, 0x78, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0b, 0x00, 0x0a, 0x03cb, 0x03c7, 0x03c3, 0x0438, 0x043d, 0x0441}, + { + 3, 2422, 0x00, 0x01, 0x03, 0x09, 0x76, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x67, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0b, 0x00, 0x0a, 0x00, 0x67, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0b, 0x00, 0x0a, 0x03cd, 0x03c9, 0x03c5, 0x0436, 0x043a, 0x043f}, + { + 4, 2427, 0x00, 0x01, 0x03, 0x09, 0x7b, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x57, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x57, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x03cf, 0x03cb, 0x03c7, 0x0434, 0x0438, 0x043d}, + { + 5, 2432, 0x00, 0x01, 0x03, 0x09, 0x80, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x56, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x56, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x03d1, 0x03cd, 0x03c9, 0x0431, 0x0436, 0x043a}, + { + 6, 2437, 0x00, 0x01, 0x03, 0x09, 0x85, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x46, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x46, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x03d3, 0x03cf, 0x03cb, 0x042f, 0x0434, 0x0438}, + { + 7, 2442, 0x00, 0x01, 0x03, 0x09, 0x8a, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x45, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x45, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x03d5, 0x03d1, 0x03cd, 0x042d, 0x0431, 0x0436}, + { + 8, 2447, 0x00, 0x01, 0x03, 0x09, 0x8f, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x34, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x34, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x09, 0x03d7, 0x03d3, 0x03cf, 0x042b, 0x042f, 0x0434}, + { + 9, 2452, 0x00, 0x01, 0x03, 0x09, 0x94, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x23, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x23, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x09, 0x03d9, 0x03d5, 0x03d1, 0x0429, 0x042d, 0x0431}, + { + 10, 2457, 0x00, 0x01, 0x03, 0x09, 0x99, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x12, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x12, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x09, 0x03db, 0x03d7, 0x03d3, 0x0427, 0x042b, 0x042f}, + { + 11, 2462, 0x00, 0x01, 0x03, 0x09, 0x9e, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09, 0x00, 0x02, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x03dd, 0x03d9, 0x03d5, 0x0424, 0x0429, 0x042d}, + { + 12, 2467, 0x00, 0x01, 0x03, 0x09, 0xa3, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09, 0x00, 0x01, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x03df, 0x03db, 0x03d7, 0x0422, 0x0427, 0x042b}, + { + 13, 2472, 0x00, 0x01, 0x03, 0x09, 0xa8, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09, 0x00, 0x01, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x03e1, 0x03dd, 0x03d9, 0x0420, 0x0424, 0x0429}, + { + 14, 2484, 0xff, 0x01, 0x03, 0x09, 0xb4, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x07, 0x07, 0x07, 0x8f, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x03e6, 0x03e2, 0x03de, 0x041b, 0x041f, 0x0424} +}; + +static const struct chan_info_nphy_radio205x chan_info_nphyrev5n6_2056v7[] = { + { + 184, 4920, 0xff, 0x01, 0x01, 0x01, 0xec, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0b, 0x00, 0x70, + 0x00, 0x0f, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0b, 0x00, 0x70, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07b4, 0x07b0, 0x07ac, 0x0214, 0x0215, 0x0216}, + { + 186, 4930, 0xff, 0x01, 0x01, 0x01, 0xed, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0b, 0x00, 0x70, + 0x00, 0x0e, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0b, 0x00, 0x70, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x07b8, 0x07b4, 0x07b0, 0x0213, 0x0214, 0x0215}, + { + 188, 4940, 0xff, 0x01, 0x01, 0x01, 0xee, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0b, 0x00, 0x70, + 0x00, 0x0e, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0b, 0x00, 0x70, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x07bc, 0x07b8, 0x07b4, 0x0212, 0x0213, 0x0214}, + { + 190, 4950, 0xff, 0x01, 0x01, 0x01, 0xef, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0b, 0x00, 0x70, + 0x00, 0x0e, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0b, 0x00, 0x70, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x07c0, 0x07bc, 0x07b8, 0x0211, 0x0212, 0x0213}, + { + 192, 4960, 0xff, 0x01, 0x01, 0x01, 0xf0, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0e, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x07c4, 0x07c0, 0x07bc, 0x020f, 0x0211, 0x0212}, + { + 194, 4970, 0xff, 0x01, 0x01, 0x01, 0xf1, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x07c8, 0x07c4, 0x07c0, 0x020e, 0x020f, 0x0211}, + { + 196, 4980, 0xff, 0x01, 0x01, 0x01, 0xf2, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x07cc, 0x07c8, 0x07c4, 0x020d, 0x020e, 0x020f}, + { + 198, 4990, 0xff, 0x01, 0x01, 0x01, 0xf3, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x07d0, 0x07cc, 0x07c8, 0x020c, 0x020d, 0x020e}, + { + 200, 5000, 0xff, 0x01, 0x01, 0x01, 0xf4, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x07d4, 0x07d0, 0x07cc, 0x020b, 0x020c, 0x020d}, + { + 202, 5010, 0xff, 0x01, 0x01, 0x01, 0xf5, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x0a, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x0a, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x07d8, 0x07d4, 0x07d0, 0x020a, 0x020b, 0x020c}, + { + 204, 5020, 0xf7, 0x01, 0x01, 0x01, 0xf6, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0d, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x07dc, 0x07d8, 0x07d4, 0x0209, 0x020a, 0x020b}, + { + 206, 5030, 0xf7, 0x01, 0x01, 0x01, 0xf7, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0c, 0x00, 0x9f, 0x00, 0xff, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x07e0, 0x07dc, 0x07d8, 0x0208, 0x0209, 0x020a}, + { + 208, 5040, 0xef, 0x01, 0x01, 0x01, 0xf8, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0c, 0x00, 0x9f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x07e4, 0x07e0, 0x07dc, 0x0207, 0x0208, 0x0209}, + { + 210, 5050, 0xef, 0x01, 0x01, 0x01, 0xf9, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0c, 0x00, 0x9f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x07e8, 0x07e4, 0x07e0, 0x0206, 0x0207, 0x0208}, + { + 212, 5060, 0xe6, 0x01, 0x01, 0x01, 0xfa, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfd, 0x00, 0x09, 0x00, 0x70, + 0x00, 0x0c, 0x00, 0x9f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x70, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x07ec, 0x07e8, 0x07e4, 0x0205, 0x0206, 0x0207}, + { + 214, 5070, 0xe6, 0x01, 0x01, 0x01, 0xfb, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfd, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x9f, 0x00, 0xfd, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x07f0, 0x07ec, 0x07e8, 0x0204, 0x0205, 0x0206}, + { + 216, 5080, 0xde, 0x01, 0x01, 0x01, 0xfc, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x07f4, 0x07f0, 0x07ec, 0x0203, 0x0204, 0x0205}, + { + 218, 5090, 0xde, 0x01, 0x01, 0x01, 0xfd, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x07f8, 0x07f4, 0x07f0, 0x0202, 0x0203, 0x0204}, + { + 220, 5100, 0xd6, 0x01, 0x01, 0x01, 0xfe, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x07fc, 0x07f8, 0x07f4, 0x0201, 0x0202, 0x0203}, + { + 222, 5110, 0xd6, 0x01, 0x01, 0x01, 0xff, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x0800, 0x07fc, 0x07f8, 0x0200, 0x0201, 0x0202}, + { + 224, 5120, 0xce, 0x01, 0x01, 0x02, 0x00, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0b, 0x00, 0x9f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x0804, 0x0800, 0x07fc, 0x01ff, 0x0200, 0x0201}, + { + 226, 5130, 0xce, 0x01, 0x01, 0x02, 0x01, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfb, 0x00, 0x08, 0x00, 0x70, + 0x00, 0x0a, 0x00, 0x9f, 0x00, 0xfb, 0x00, 0x08, 0x00, 0x70, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0808, 0x0804, 0x0800, 0x01fe, 0x01ff, 0x0200}, + { + 228, 5140, 0xc6, 0x01, 0x01, 0x02, 0x02, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfb, 0x00, 0x07, 0x00, 0x70, + 0x00, 0x0a, 0x00, 0x9f, 0x00, 0xfb, 0x00, 0x07, 0x00, 0x70, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x080c, 0x0808, 0x0804, 0x01fd, 0x01fe, 0x01ff}, + { + 32, 5160, 0xbe, 0x01, 0x01, 0x02, 0x04, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfb, 0x00, 0x07, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x9e, 0x00, 0xfb, 0x00, 0x07, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x6e, 0x00, 0x0814, 0x0810, 0x080c, 0x01fb, 0x01fc, 0x01fd}, + { + 34, 5170, 0xbe, 0x01, 0x01, 0x02, 0x05, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfb, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x9e, 0x00, 0xfb, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x6e, 0x00, 0x0818, 0x0814, 0x0810, 0x01fa, 0x01fb, 0x01fc}, + { + 36, 5180, 0xb6, 0x01, 0x01, 0x02, 0x06, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x6e, 0x00, 0x081c, 0x0818, 0x0814, 0x01f9, 0x01fa, 0x01fb}, + { + 38, 5190, 0xb6, 0x01, 0x01, 0x02, 0x07, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x6e, 0x00, 0x0820, 0x081c, 0x0818, 0x01f8, 0x01f9, 0x01fa}, + { + 40, 5200, 0xaf, 0x01, 0x01, 0x02, 0x08, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x6e, 0x00, 0x0824, 0x0820, 0x081c, 0x01f7, 0x01f8, 0x01f9}, + { + 42, 5210, 0xaf, 0x01, 0x01, 0x02, 0x09, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x6e, 0x00, 0x0828, 0x0824, 0x0820, 0x01f6, 0x01f7, 0x01f8}, + { + 44, 5220, 0xa7, 0x01, 0x01, 0x02, 0x0a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, 0xfe, 0xfa, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x09, 0x00, 0x9e, 0x00, 0xfa, 0x00, 0x06, 0x00, 0x70, 0x00, 0x09, + 0x00, 0x6e, 0x00, 0x082c, 0x0828, 0x0824, 0x01f5, 0x01f6, 0x01f7}, + { + 46, 5230, 0xa7, 0x01, 0x01, 0x02, 0x0b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, 0xee, 0xea, 0x00, 0x06, 0x00, 0x70, + 0x00, 0x08, 0x00, 0x9e, 0x00, 0xea, 0x00, 0x06, 0x00, 0x70, 0x00, 0x08, + 0x00, 0x6e, 0x00, 0x0830, 0x082c, 0x0828, 0x01f4, 0x01f5, 0x01f6}, + { + 48, 5240, 0xa0, 0x01, 0x01, 0x02, 0x0c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, 0xee, 0xe9, 0x00, 0x05, 0x00, 0x70, + 0x00, 0x08, 0x00, 0x9d, 0x00, 0xe9, 0x00, 0x05, 0x00, 0x70, 0x00, 0x08, + 0x00, 0x6d, 0x00, 0x0834, 0x0830, 0x082c, 0x01f3, 0x01f4, 0x01f5}, + { + 50, 5250, 0xa0, 0x01, 0x01, 0x02, 0x0d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, 0xed, 0xe9, 0x00, 0x05, 0x00, 0x70, + 0x00, 0x08, 0x00, 0x9d, 0x00, 0xe9, 0x00, 0x05, 0x00, 0x70, 0x00, 0x08, + 0x00, 0x6d, 0x00, 0x0838, 0x0834, 0x0830, 0x01f2, 0x01f3, 0x01f4}, + { + 52, 5260, 0x98, 0x01, 0x01, 0x02, 0x0e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0e, 0x00, 0xed, 0xd9, 0x00, 0x05, 0x00, 0x70, + 0x00, 0x08, 0x00, 0x9d, 0x00, 0xd9, 0x00, 0x05, 0x00, 0x70, 0x00, 0x08, + 0x00, 0x6d, 0x00, 0x083c, 0x0838, 0x0834, 0x01f1, 0x01f2, 0x01f3}, + { + 54, 5270, 0x98, 0x01, 0x01, 0x02, 0x0f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8e, 0x0e, 0x00, 0xed, 0xd8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x9c, 0x00, 0xd8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x6c, 0x00, 0x0840, 0x083c, 0x0838, 0x01f0, 0x01f1, 0x01f2}, + { + 56, 5280, 0x91, 0x01, 0x01, 0x02, 0x10, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdc, 0xc8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x9c, 0x00, 0xc8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x6c, 0x00, 0x0844, 0x0840, 0x083c, 0x01f0, 0x01f0, 0x01f1}, + { + 58, 5290, 0x91, 0x01, 0x01, 0x02, 0x11, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdc, 0xc8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x9c, 0x00, 0xc8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x6c, 0x00, 0x0848, 0x0844, 0x0840, 0x01ef, 0x01f0, 0x01f0}, + { + 60, 5300, 0x8a, 0x01, 0x01, 0x02, 0x12, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdc, 0xc8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x9c, 0x00, 0xc8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x6c, 0x00, 0x084c, 0x0848, 0x0844, 0x01ee, 0x01ef, 0x01f0}, + { + 62, 5310, 0x8a, 0x01, 0x01, 0x02, 0x13, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdc, 0xc8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x9c, 0x00, 0xc8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x6c, 0x00, 0x0850, 0x084c, 0x0848, 0x01ed, 0x01ee, 0x01ef}, + { + 64, 5320, 0x83, 0x01, 0x01, 0x02, 0x14, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdb, 0xb8, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x9c, 0x00, 0xb8, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x6c, 0x00, 0x0854, 0x0850, 0x084c, 0x01ec, 0x01ed, 0x01ee}, + { + 66, 5330, 0x83, 0x01, 0x01, 0x02, 0x15, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xcb, 0xb7, 0x00, 0x04, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x9b, 0x00, 0xb7, 0x00, 0x04, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x6b, 0x00, 0x0858, 0x0854, 0x0850, 0x01eb, 0x01ec, 0x01ed}, + { + 68, 5340, 0x7c, 0x01, 0x01, 0x02, 0x16, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xca, 0xb7, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x07, 0x00, 0x9b, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x70, 0x00, 0x07, + 0x00, 0x6b, 0x00, 0x085c, 0x0858, 0x0854, 0x01ea, 0x01eb, 0x01ec}, + { + 70, 5350, 0x7c, 0x01, 0x01, 0x02, 0x17, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, 0xca, 0xa7, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x9b, 0x00, 0xa7, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x6b, 0x00, 0x0860, 0x085c, 0x0858, 0x01e9, 0x01ea, 0x01eb}, + { + 72, 5360, 0x75, 0x01, 0x01, 0x02, 0x18, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, 0xc9, 0xa6, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x9b, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x6b, 0x00, 0x0864, 0x0860, 0x085c, 0x01e8, 0x01e9, 0x01ea}, + { + 74, 5370, 0x75, 0x01, 0x01, 0x02, 0x19, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, 0xc9, 0xa6, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x9b, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x7b, 0x00, 0x0868, 0x0864, 0x0860, 0x01e7, 0x01e8, 0x01e9}, + { + 76, 5380, 0x6e, 0x01, 0x01, 0x02, 0x1a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xb8, 0x96, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x9a, 0x00, 0x96, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x7a, 0x00, 0x086c, 0x0868, 0x0864, 0x01e6, 0x01e7, 0x01e8}, + { + 78, 5390, 0x6e, 0x01, 0x01, 0x02, 0x1b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xb8, 0x95, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x9a, 0x00, 0x95, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x7a, 0x00, 0x0870, 0x086c, 0x0868, 0x01e5, 0x01e6, 0x01e7}, + { + 80, 5400, 0x67, 0x01, 0x01, 0x02, 0x1c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xb8, 0x95, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x06, 0x00, 0x9a, 0x00, 0x95, 0x00, 0x03, 0x00, 0x70, 0x00, 0x06, + 0x00, 0x7a, 0x00, 0x0874, 0x0870, 0x086c, 0x01e5, 0x01e5, 0x01e6}, + { + 82, 5410, 0x67, 0x01, 0x01, 0x02, 0x1d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xb7, 0x95, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x9a, 0x00, 0x95, 0x00, 0x03, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x7a, 0x00, 0x0878, 0x0874, 0x0870, 0x01e4, 0x01e5, 0x01e5}, + { + 84, 5420, 0x61, 0x01, 0x01, 0x02, 0x1e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xa7, 0x95, 0x00, 0x03, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x9a, 0x00, 0x95, 0x00, 0x03, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x7a, 0x00, 0x087c, 0x0878, 0x0874, 0x01e3, 0x01e4, 0x01e5}, + { + 86, 5430, 0x61, 0x01, 0x01, 0x02, 0x1f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0b, 0x00, 0xa6, 0x85, 0x00, 0x02, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x99, 0x00, 0x85, 0x00, 0x02, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x79, 0x00, 0x0880, 0x087c, 0x0878, 0x01e2, 0x01e3, 0x01e4}, + { + 88, 5440, 0x5a, 0x01, 0x01, 0x02, 0x20, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, 0xa6, 0x84, 0x00, 0x02, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x99, 0x00, 0x84, 0x00, 0x02, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x79, 0x00, 0x0884, 0x0880, 0x087c, 0x01e1, 0x01e2, 0x01e3}, + { + 90, 5450, 0x5a, 0x01, 0x01, 0x02, 0x21, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, 0x95, 0x84, 0x00, 0x02, 0x00, 0x70, + 0x00, 0x05, 0x00, 0x99, 0x00, 0x84, 0x00, 0x02, 0x00, 0x70, 0x00, 0x05, + 0x00, 0x79, 0x00, 0x0888, 0x0884, 0x0880, 0x01e0, 0x01e1, 0x01e2}, + { + 92, 5460, 0x53, 0x01, 0x01, 0x02, 0x22, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, 0x95, 0x84, 0x00, 0x02, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x99, 0x00, 0x84, 0x00, 0x02, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x79, 0x00, 0x088c, 0x0888, 0x0884, 0x01df, 0x01e0, 0x01e1}, + { + 94, 5470, 0x53, 0x01, 0x01, 0x02, 0x23, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, 0x94, 0x74, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x99, 0x00, 0x74, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x79, 0x00, 0x0890, 0x088c, 0x0888, 0x01de, 0x01df, 0x01e0}, + { + 96, 5480, 0x4d, 0x01, 0x01, 0x02, 0x24, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x84, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x78, 0x00, 0x0894, 0x0890, 0x088c, 0x01dd, 0x01de, 0x01df}, + { + 98, 5490, 0x4d, 0x01, 0x01, 0x02, 0x25, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x83, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x78, 0x00, 0x0898, 0x0894, 0x0890, 0x01dd, 0x01dd, 0x01de}, + { + 100, 5500, 0x47, 0x01, 0x01, 0x02, 0x26, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x82, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x78, 0x00, 0x089c, 0x0898, 0x0894, 0x01dc, 0x01dd, 0x01dd}, + { + 102, 5510, 0x47, 0x01, 0x01, 0x02, 0x27, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x82, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x78, 0x00, 0x08a0, 0x089c, 0x0898, 0x01db, 0x01dc, 0x01dd}, + { + 104, 5520, 0x40, 0x01, 0x01, 0x02, 0x28, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x72, 0x73, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x04, 0x00, 0x98, 0x00, 0x73, 0x00, 0x01, 0x00, 0x70, 0x00, 0x04, + 0x00, 0x78, 0x00, 0x08a4, 0x08a0, 0x089c, 0x01da, 0x01db, 0x01dc}, + { + 106, 5530, 0x40, 0x01, 0x01, 0x02, 0x29, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x09, 0x00, 0x72, 0x63, 0x00, 0x01, 0x00, 0x70, + 0x00, 0x03, 0x00, 0x98, 0x00, 0x63, 0x00, 0x01, 0x00, 0x70, 0x00, 0x03, + 0x00, 0x78, 0x00, 0x08a8, 0x08a4, 0x08a0, 0x01d9, 0x01da, 0x01db}, + { + 108, 5540, 0x3a, 0x01, 0x01, 0x02, 0x2a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x09, 0x00, 0x71, 0x62, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x03, 0x00, 0x97, 0x00, 0x62, 0x00, 0x00, 0x00, 0x70, 0x00, 0x03, + 0x00, 0x77, 0x00, 0x08ac, 0x08a8, 0x08a4, 0x01d8, 0x01d9, 0x01da}, + { + 110, 5550, 0x3a, 0x01, 0x01, 0x02, 0x2b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, 0x61, 0x62, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x03, 0x00, 0x97, 0x00, 0x62, 0x00, 0x00, 0x00, 0x70, 0x00, 0x03, + 0x00, 0x77, 0x00, 0x08b0, 0x08ac, 0x08a8, 0x01d7, 0x01d8, 0x01d9}, + { + 112, 5560, 0x34, 0x01, 0x01, 0x02, 0x2c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, 0x61, 0x62, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x03, 0x00, 0x97, 0x00, 0x62, 0x00, 0x00, 0x00, 0x70, 0x00, 0x03, + 0x00, 0x77, 0x00, 0x08b4, 0x08b0, 0x08ac, 0x01d7, 0x01d7, 0x01d8}, + { + 114, 5570, 0x34, 0x01, 0x01, 0x02, 0x2d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, 0x61, 0x52, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x96, 0x00, 0x52, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x76, 0x00, 0x08b8, 0x08b4, 0x08b0, 0x01d6, 0x01d7, 0x01d7}, + { + 116, 5580, 0x2e, 0x01, 0x01, 0x02, 0x2e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x08, 0x00, 0x60, 0x52, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x96, 0x00, 0x52, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x86, 0x00, 0x08bc, 0x08b8, 0x08b4, 0x01d5, 0x01d6, 0x01d7}, + { + 118, 5590, 0x2e, 0x01, 0x01, 0x02, 0x2f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x08, 0x00, 0x50, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x86, 0x00, 0x08c0, 0x08bc, 0x08b8, 0x01d4, 0x01d5, 0x01d6}, + { + 120, 5600, 0x28, 0x01, 0x01, 0x02, 0x30, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, 0x50, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x86, 0x00, 0x08c4, 0x08c0, 0x08bc, 0x01d3, 0x01d4, 0x01d5}, + { + 122, 5610, 0x28, 0x01, 0x01, 0x02, 0x31, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, 0x50, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x86, 0x00, 0x08c8, 0x08c4, 0x08c0, 0x01d2, 0x01d3, 0x01d4}, + { + 124, 5620, 0x21, 0x01, 0x01, 0x02, 0x32, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, 0x50, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x86, 0x00, 0x08cc, 0x08c8, 0x08c4, 0x01d2, 0x01d2, 0x01d3}, + { + 126, 5630, 0x21, 0x01, 0x01, 0x02, 0x33, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x50, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x96, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x86, 0x00, 0x08d0, 0x08cc, 0x08c8, 0x01d1, 0x01d2, 0x01d2}, + { + 128, 5640, 0x1c, 0x01, 0x01, 0x02, 0x34, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x40, 0x51, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x02, 0x00, 0x95, 0x00, 0x51, 0x00, 0x00, 0x00, 0x70, 0x00, 0x02, + 0x00, 0x85, 0x00, 0x08d4, 0x08d0, 0x08cc, 0x01d0, 0x01d1, 0x01d2}, + { + 130, 5650, 0x1c, 0x01, 0x01, 0x02, 0x35, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x40, 0x50, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x95, 0x00, 0x50, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x85, 0x00, 0x08d8, 0x08d4, 0x08d0, 0x01cf, 0x01d0, 0x01d1}, + { + 132, 5660, 0x16, 0x01, 0x01, 0x02, 0x36, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x40, 0x50, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x95, 0x00, 0x50, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x85, 0x00, 0x08dc, 0x08d8, 0x08d4, 0x01ce, 0x01cf, 0x01d0}, + { + 134, 5670, 0x16, 0x01, 0x01, 0x02, 0x37, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x84, 0x00, 0x08e0, 0x08dc, 0x08d8, 0x01ce, 0x01ce, 0x01cf}, + { + 136, 5680, 0x10, 0x01, 0x01, 0x02, 0x38, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x84, 0x00, 0x08e4, 0x08e0, 0x08dc, 0x01cd, 0x01ce, 0x01ce}, + { + 138, 5690, 0x10, 0x01, 0x01, 0x02, 0x39, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x08e8, 0x08e4, 0x08e0, 0x01cc, 0x01cd, 0x01ce}, + { + 140, 5700, 0x0a, 0x01, 0x01, 0x02, 0x3a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x08ec, 0x08e8, 0x08e4, 0x01cb, 0x01cc, 0x01cd}, + { + 142, 5710, 0x0a, 0x01, 0x01, 0x02, 0x3b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x08f0, 0x08ec, 0x08e8, 0x01ca, 0x01cb, 0x01cc}, + { + 144, 5720, 0x0a, 0x01, 0x01, 0x02, 0x3c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x08f4, 0x08f0, 0x08ec, 0x01c9, 0x01ca, 0x01cb}, + { + 145, 5725, 0x03, 0x01, 0x02, 0x04, 0x79, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x40, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x40, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x08f6, 0x08f2, 0x08ee, 0x01c9, 0x01ca, 0x01cb}, + { + 146, 5730, 0x0a, 0x01, 0x01, 0x02, 0x3d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x01, 0x00, 0x94, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x01, + 0x00, 0x94, 0x00, 0x08f8, 0x08f4, 0x08f0, 0x01c9, 0x01c9, 0x01ca}, + { + 147, 5735, 0x03, 0x01, 0x02, 0x04, 0x7b, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x08fa, 0x08f6, 0x08f2, 0x01c8, 0x01c9, 0x01ca}, + { + 148, 5740, 0x0a, 0x01, 0x01, 0x02, 0x3e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x08fc, 0x08f8, 0x08f4, 0x01c8, 0x01c9, 0x01c9}, + { + 149, 5745, 0xfe, 0x00, 0x02, 0x04, 0x7d, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x08fe, 0x08fa, 0x08f6, 0x01c8, 0x01c8, 0x01c9}, + { + 150, 5750, 0x0a, 0x01, 0x01, 0x02, 0x3f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x0900, 0x08fc, 0x08f8, 0x01c7, 0x01c8, 0x01c9}, + { + 151, 5755, 0xfe, 0x00, 0x02, 0x04, 0x7f, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x10, 0x30, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00, 0x30, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x0902, 0x08fe, 0x08fa, 0x01c7, 0x01c8, 0x01c8}, + { + 152, 5760, 0x0a, 0x01, 0x01, 0x02, 0x40, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x05, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x93, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x93, 0x00, 0x0904, 0x0900, 0x08fc, 0x01c6, 0x01c7, 0x01c8}, + { + 153, 5765, 0xf8, 0x00, 0x02, 0x04, 0x81, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x05, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x0906, 0x0902, 0x08fe, 0x01c6, 0x01c7, 0x01c8}, + { + 154, 5770, 0x0a, 0x01, 0x01, 0x02, 0x41, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x0908, 0x0904, 0x0900, 0x01c6, 0x01c6, 0x01c7}, + { + 155, 5775, 0xf8, 0x00, 0x02, 0x04, 0x83, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x20, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x090a, 0x0906, 0x0902, 0x01c5, 0x01c6, 0x01c7}, + { + 156, 5780, 0x0a, 0x01, 0x01, 0x02, 0x42, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x090c, 0x0908, 0x0904, 0x01c5, 0x01c6, 0x01c6}, + { + 157, 5785, 0xf2, 0x00, 0x02, 0x04, 0x85, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x090e, 0x090a, 0x0906, 0x01c4, 0x01c5, 0x01c6}, + { + 158, 5790, 0x0a, 0x01, 0x01, 0x02, 0x43, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x0910, 0x090c, 0x0908, 0x01c4, 0x01c5, 0x01c6}, + { + 159, 5795, 0xf2, 0x00, 0x02, 0x04, 0x87, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x0912, 0x090e, 0x090a, 0x01c4, 0x01c4, 0x01c5}, + { + 160, 5800, 0x0a, 0x01, 0x01, 0x02, 0x44, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x0914, 0x0910, 0x090c, 0x01c3, 0x01c4, 0x01c5}, + { + 161, 5805, 0xed, 0x00, 0x02, 0x04, 0x89, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x0916, 0x0912, 0x090e, 0x01c3, 0x01c4, 0x01c4}, + { + 162, 5810, 0x0a, 0x01, 0x01, 0x02, 0x45, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x0918, 0x0914, 0x0910, 0x01c2, 0x01c3, 0x01c4}, + { + 163, 5815, 0xed, 0x00, 0x02, 0x04, 0x8b, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x091a, 0x0916, 0x0912, 0x01c2, 0x01c3, 0x01c4}, + { + 164, 5820, 0x0a, 0x01, 0x01, 0x02, 0x46, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x091c, 0x0918, 0x0914, 0x01c2, 0x01c2, 0x01c3}, + { + 165, 5825, 0xed, 0x00, 0x02, 0x04, 0x8d, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x091e, 0x091a, 0x0916, 0x01c1, 0x01c2, 0x01c3}, + { + 166, 5830, 0x0a, 0x01, 0x01, 0x02, 0x47, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x0920, 0x091c, 0x0918, 0x01c1, 0x01c2, 0x01c2}, + { + 168, 5840, 0x0a, 0x01, 0x01, 0x02, 0x48, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x0924, 0x0920, 0x091c, 0x01c0, 0x01c1, 0x01c2}, + { + 170, 5850, 0xe0, 0x00, 0x01, 0x02, 0x49, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x0928, 0x0924, 0x0920, 0x01bf, 0x01c0, 0x01c1}, + { + 172, 5860, 0xde, 0x00, 0x01, 0x02, 0x4a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x92, 0x00, 0x092c, 0x0928, 0x0924, 0x01bf, 0x01bf, 0x01c0}, + { + 174, 5870, 0xdb, 0x00, 0x01, 0x02, 0x4b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x91, 0x00, 0x0930, 0x092c, 0x0928, 0x01be, 0x01bf, 0x01bf}, + { + 176, 5880, 0xd8, 0x00, 0x01, 0x02, 0x4c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x91, 0x00, 0x0934, 0x0930, 0x092c, 0x01bd, 0x01be, 0x01bf}, + { + 178, 5890, 0xd6, 0x00, 0x01, 0x02, 0x4d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x91, 0x00, 0x0938, 0x0934, 0x0930, 0x01bc, 0x01bd, 0x01be}, + { + 180, 5900, 0xd3, 0x00, 0x01, 0x02, 0x4e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x91, 0x00, 0x093c, 0x0938, 0x0934, 0x01bc, 0x01bc, 0x01bd}, + { + 182, 5910, 0xd6, 0x00, 0x01, 0x02, 0x4f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, + 0x00, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x91, 0x00, 0x0940, 0x093c, 0x0938, 0x01bb, 0x01bc, 0x01bc}, + { + 1, 2412, 0x00, 0x01, 0x03, 0x09, 0x6c, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x04, 0x04, 0x04, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x89, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0b, 0x00, 0x89, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0b, 0x03c9, 0x03c5, 0x03c1, 0x043a, 0x043f, 0x0443}, + { + 2, 2417, 0x00, 0x01, 0x03, 0x09, 0x71, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x89, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0a, 0x00, 0x89, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0a, 0x03cb, 0x03c7, 0x03c3, 0x0438, 0x043d, 0x0441}, + { + 3, 2422, 0x00, 0x01, 0x03, 0x09, 0x76, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x89, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0f, 0x00, 0x0a, 0x00, 0x89, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0f, 0x00, 0x0a, 0x03cd, 0x03c9, 0x03c5, 0x0436, 0x043a, 0x043f}, + { + 4, 2427, 0x00, 0x01, 0x03, 0x09, 0x7b, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x78, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x0a, 0x00, 0x78, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x0a, 0x03cf, 0x03cb, 0x03c7, 0x0434, 0x0438, 0x043d}, + { + 5, 2432, 0x00, 0x01, 0x03, 0x09, 0x80, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x0a, 0x00, 0x77, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x0a, 0x03d1, 0x03cd, 0x03c9, 0x0431, 0x0436, 0x043a}, + { + 6, 2437, 0x00, 0x01, 0x03, 0x09, 0x85, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x76, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x0a, 0x00, 0x76, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x0a, 0x03d3, 0x03cf, 0x03cb, 0x042f, 0x0434, 0x0438}, + { + 7, 2442, 0x00, 0x01, 0x03, 0x09, 0x8a, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x66, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x0a, 0x00, 0x66, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x0a, 0x03d5, 0x03d1, 0x03cd, 0x042d, 0x0431, 0x0436}, + { + 8, 2447, 0x00, 0x01, 0x03, 0x09, 0x8f, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x55, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x09, 0x00, 0x55, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x09, 0x03d7, 0x03d3, 0x03cf, 0x042b, 0x042f, 0x0434}, + { + 9, 2452, 0x00, 0x01, 0x03, 0x09, 0x94, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x45, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0e, 0x00, 0x09, 0x00, 0x45, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0e, 0x00, 0x09, 0x03d9, 0x03d5, 0x03d1, 0x0429, 0x042d, 0x0431}, + { + 10, 2457, 0x00, 0x01, 0x03, 0x09, 0x99, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x34, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x09, 0x00, 0x34, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x09, 0x03db, 0x03d7, 0x03d3, 0x0427, 0x042b, 0x042f}, + { + 11, 2462, 0x00, 0x01, 0x03, 0x09, 0x9e, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x33, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x09, 0x00, 0x33, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x09, 0x03dd, 0x03d9, 0x03d5, 0x0424, 0x0429, 0x042d}, + { + 12, 2467, 0x00, 0x01, 0x03, 0x09, 0xa3, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x22, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x08, 0x00, 0x22, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x08, 0x03df, 0x03db, 0x03d7, 0x0422, 0x0427, 0x042b}, + { + 13, 2472, 0x00, 0x01, 0x03, 0x09, 0xa8, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x08, 0x00, 0x11, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x08, 0x03e1, 0x03dd, 0x03d9, 0x0420, 0x0424, 0x0429}, + { + 14, 2484, 0xff, 0x01, 0x03, 0x09, 0xb4, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x07, 0x07, 0x07, 0x8f, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0d, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0d, 0x00, 0x08, 0x03e6, 0x03e2, 0x03de, 0x041b, 0x041f, 0x0424} +}; + +static const struct chan_info_nphy_radio205x chan_info_nphyrev6_2056v8[] = { + { + 184, 4920, 0xff, 0x01, 0x01, 0x01, 0xec, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07b4, 0x07b0, 0x07ac, 0x0214, 0x0215, 0x0216}, + { + 186, 4930, 0xff, 0x01, 0x01, 0x01, 0xed, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07b8, 0x07b4, 0x07b0, 0x0213, 0x0214, 0x0215}, + { + 188, 4940, 0xff, 0x01, 0x01, 0x01, 0xee, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07bc, 0x07b8, 0x07b4, 0x0212, 0x0213, 0x0214}, + { + 190, 4950, 0xff, 0x01, 0x01, 0x01, 0xef, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07c0, 0x07bc, 0x07b8, 0x0211, 0x0212, 0x0213}, + { + 192, 4960, 0xff, 0x01, 0x01, 0x01, 0xf0, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07c4, 0x07c0, 0x07bc, 0x020f, 0x0211, 0x0212}, + { + 194, 4970, 0xff, 0x01, 0x01, 0x01, 0xf1, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07c8, 0x07c4, 0x07c0, 0x020e, 0x020f, 0x0211}, + { + 196, 4980, 0xff, 0x01, 0x01, 0x01, 0xf2, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07cc, 0x07c8, 0x07c4, 0x020d, 0x020e, 0x020f}, + { + 198, 4990, 0xff, 0x01, 0x01, 0x01, 0xf3, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07d0, 0x07cc, 0x07c8, 0x020c, 0x020d, 0x020e}, + { + 200, 5000, 0xff, 0x01, 0x01, 0x01, 0xf4, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07d4, 0x07d0, 0x07cc, 0x020b, 0x020c, 0x020d}, + { + 202, 5010, 0xff, 0x01, 0x01, 0x01, 0xf5, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07d8, 0x07d4, 0x07d0, 0x020a, 0x020b, 0x020c}, + { + 204, 5020, 0xf7, 0x01, 0x01, 0x01, 0xf6, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07dc, 0x07d8, 0x07d4, 0x0209, 0x020a, 0x020b}, + { + 206, 5030, 0xf7, 0x01, 0x01, 0x01, 0xf7, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07e0, 0x07dc, 0x07d8, 0x0208, 0x0209, 0x020a}, + { + 208, 5040, 0xef, 0x01, 0x01, 0x01, 0xf8, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07e4, 0x07e0, 0x07dc, 0x0207, 0x0208, 0x0209}, + { + 210, 5050, 0xef, 0x01, 0x01, 0x01, 0xf9, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07e8, 0x07e4, 0x07e0, 0x0206, 0x0207, 0x0208}, + { + 212, 5060, 0xe6, 0x01, 0x01, 0x01, 0xfa, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07ec, 0x07e8, 0x07e4, 0x0205, 0x0206, 0x0207}, + { + 214, 5070, 0xe6, 0x01, 0x01, 0x01, 0xfb, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfd, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07f0, 0x07ec, 0x07e8, 0x0204, 0x0205, 0x0206}, + { + 216, 5080, 0xde, 0x01, 0x01, 0x01, 0xfc, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfd, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07f4, 0x07f0, 0x07ec, 0x0203, 0x0204, 0x0205}, + { + 218, 5090, 0xde, 0x01, 0x01, 0x01, 0xfd, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfd, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07f8, 0x07f4, 0x07f0, 0x0202, 0x0203, 0x0204}, + { + 220, 5100, 0xd6, 0x01, 0x01, 0x01, 0xfe, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfd, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07fc, 0x07f8, 0x07f4, 0x0201, 0x0202, 0x0203}, + { + 222, 5110, 0xd6, 0x01, 0x01, 0x01, 0xff, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x0800, 0x07fc, 0x07f8, 0x0200, 0x0201, 0x0202}, + { + 224, 5120, 0xce, 0x01, 0x01, 0x02, 0x00, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x0804, 0x0800, 0x07fc, 0x01ff, 0x0200, 0x0201}, + { + 226, 5130, 0xce, 0x01, 0x01, 0x02, 0x01, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x0808, 0x0804, 0x0800, 0x01fe, 0x01ff, 0x0200}, + { + 228, 5140, 0xc6, 0x01, 0x01, 0x02, 0x02, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfb, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfb, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x080c, 0x0808, 0x0804, 0x01fd, 0x01fe, 0x01ff}, + { + 32, 5160, 0xbe, 0x01, 0x01, 0x02, 0x04, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfa, 0x00, 0x07, 0x00, 0x77, + 0x00, 0x0e, 0x00, 0x6f, 0x00, 0xfa, 0x00, 0x07, 0x00, 0x77, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x0814, 0x0810, 0x080c, 0x01fb, 0x01fc, 0x01fd}, + { + 34, 5170, 0xbe, 0x01, 0x01, 0x02, 0x05, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfa, 0x00, 0x07, 0x00, 0x77, + 0x00, 0x0e, 0x00, 0x6f, 0x00, 0xfa, 0x00, 0x07, 0x00, 0x77, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x0818, 0x0814, 0x0810, 0x01fa, 0x01fb, 0x01fc}, + { + 36, 5180, 0xb6, 0x01, 0x01, 0x02, 0x06, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xf9, 0x00, 0x06, 0x00, 0x77, + 0x00, 0x0e, 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x06, 0x00, 0x77, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x081c, 0x0818, 0x0814, 0x01f9, 0x01fa, 0x01fb}, + { + 38, 5190, 0xb6, 0x01, 0x01, 0x02, 0x07, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xf9, 0x00, 0x06, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x06, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0820, 0x081c, 0x0818, 0x01f8, 0x01f9, 0x01fa}, + { + 40, 5200, 0xaf, 0x01, 0x01, 0x02, 0x08, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xf9, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0824, 0x0820, 0x081c, 0x01f7, 0x01f8, 0x01f9}, + { + 42, 5210, 0xaf, 0x01, 0x01, 0x02, 0x09, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xf9, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0828, 0x0824, 0x0820, 0x01f6, 0x01f7, 0x01f8}, + { + 44, 5220, 0xa7, 0x01, 0x01, 0x02, 0x0a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, 0xfe, 0xd8, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xd8, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x082c, 0x0828, 0x0824, 0x01f5, 0x01f6, 0x01f7}, + { + 46, 5230, 0xa7, 0x01, 0x01, 0x02, 0x0b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, 0xee, 0xd8, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xd8, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0830, 0x082c, 0x0828, 0x01f4, 0x01f5, 0x01f6}, + { + 48, 5240, 0xa0, 0x01, 0x01, 0x02, 0x0c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, 0xee, 0xc8, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xc8, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0834, 0x0830, 0x082c, 0x01f3, 0x01f4, 0x01f5}, + { + 50, 5250, 0xa0, 0x01, 0x01, 0x02, 0x0d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, 0xed, 0xc7, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xc7, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0838, 0x0834, 0x0830, 0x01f2, 0x01f3, 0x01f4}, + { + 52, 5260, 0x98, 0x01, 0x01, 0x02, 0x0e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0e, 0x00, 0xed, 0xc7, 0x00, 0x04, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xc7, 0x00, 0x04, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x083c, 0x0838, 0x0834, 0x01f1, 0x01f2, 0x01f3}, + { + 54, 5270, 0x98, 0x01, 0x01, 0x02, 0x0f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8e, 0x0e, 0x00, 0xed, 0xc7, 0x00, 0x04, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xc7, 0x00, 0x04, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x0840, 0x083c, 0x0838, 0x01f0, 0x01f1, 0x01f2}, + { + 56, 5280, 0x91, 0x01, 0x01, 0x02, 0x10, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x0844, 0x0840, 0x083c, 0x01f0, 0x01f0, 0x01f1}, + { + 58, 5290, 0x91, 0x01, 0x01, 0x02, 0x11, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x0848, 0x0844, 0x0840, 0x01ef, 0x01f0, 0x01f0}, + { + 60, 5300, 0x8a, 0x01, 0x01, 0x02, 0x12, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x084c, 0x0848, 0x0844, 0x01ee, 0x01ef, 0x01f0}, + { + 62, 5310, 0x8a, 0x01, 0x01, 0x02, 0x13, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x0850, 0x084c, 0x0848, 0x01ed, 0x01ee, 0x01ef}, + { + 64, 5320, 0x83, 0x01, 0x01, 0x02, 0x14, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdb, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x0854, 0x0850, 0x084c, 0x01ec, 0x01ed, 0x01ee}, + { + 66, 5330, 0x83, 0x01, 0x01, 0x02, 0x15, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xcb, 0xa6, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0b, 0x00, 0x6f, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x0858, 0x0854, 0x0850, 0x01eb, 0x01ec, 0x01ed}, + { + 68, 5340, 0x7c, 0x01, 0x01, 0x02, 0x16, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xca, 0xa6, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0b, 0x00, 0x6f, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x085c, 0x0858, 0x0854, 0x01ea, 0x01eb, 0x01ec}, + { + 70, 5350, 0x7c, 0x01, 0x01, 0x02, 0x17, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, 0xca, 0xa6, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0b, 0x00, 0x6f, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x0860, 0x085c, 0x0858, 0x01e9, 0x01ea, 0x01eb}, + { + 72, 5360, 0x75, 0x01, 0x01, 0x02, 0x18, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, 0xc9, 0x95, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x95, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0864, 0x0860, 0x085c, 0x01e8, 0x01e9, 0x01ea}, + { + 74, 5370, 0x75, 0x01, 0x01, 0x02, 0x19, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, 0xc9, 0x95, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x95, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0868, 0x0864, 0x0860, 0x01e7, 0x01e8, 0x01e9}, + { + 76, 5380, 0x6e, 0x01, 0x01, 0x02, 0x1a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xb8, 0x95, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x95, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x086c, 0x0868, 0x0864, 0x01e6, 0x01e7, 0x01e8}, + { + 78, 5390, 0x6e, 0x01, 0x01, 0x02, 0x1b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xb8, 0x84, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0870, 0x086c, 0x0868, 0x01e5, 0x01e6, 0x01e7}, + { + 80, 5400, 0x67, 0x01, 0x01, 0x02, 0x1c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xb8, 0x84, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0874, 0x0870, 0x086c, 0x01e5, 0x01e5, 0x01e6}, + { + 82, 5410, 0x67, 0x01, 0x01, 0x02, 0x1d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xb7, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0878, 0x0874, 0x0870, 0x01e4, 0x01e5, 0x01e5}, + { + 84, 5420, 0x61, 0x01, 0x01, 0x02, 0x1e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xa7, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x087c, 0x0878, 0x0874, 0x01e3, 0x01e4, 0x01e5}, + { + 86, 5430, 0x61, 0x01, 0x01, 0x02, 0x1f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0b, 0x00, 0xa6, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0880, 0x087c, 0x0878, 0x01e2, 0x01e3, 0x01e4}, + { + 88, 5440, 0x5a, 0x01, 0x01, 0x02, 0x20, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, 0xa6, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x0884, 0x0880, 0x087c, 0x01e1, 0x01e2, 0x01e3}, + { + 90, 5450, 0x5a, 0x01, 0x01, 0x02, 0x21, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, 0x95, 0x84, 0x00, 0x01, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x01, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x0888, 0x0884, 0x0880, 0x01e0, 0x01e1, 0x01e2}, + { + 92, 5460, 0x53, 0x01, 0x01, 0x02, 0x22, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, 0x95, 0x84, 0x00, 0x01, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x01, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x088c, 0x0888, 0x0884, 0x01df, 0x01e0, 0x01e1}, + { + 94, 5470, 0x53, 0x01, 0x01, 0x02, 0x23, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, 0x94, 0x73, 0x00, 0x01, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x01, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x0890, 0x088c, 0x0888, 0x01de, 0x01df, 0x01e0}, + { + 96, 5480, 0x4d, 0x01, 0x01, 0x02, 0x24, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x84, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x0894, 0x0890, 0x088c, 0x01dd, 0x01de, 0x01df}, + { + 98, 5490, 0x4d, 0x01, 0x01, 0x02, 0x25, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x83, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x0898, 0x0894, 0x0890, 0x01dd, 0x01dd, 0x01de}, + { + 100, 5500, 0x47, 0x01, 0x01, 0x02, 0x26, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x82, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x089c, 0x0898, 0x0894, 0x01dc, 0x01dd, 0x01dd}, + { + 102, 5510, 0x47, 0x01, 0x01, 0x02, 0x27, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x82, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08a0, 0x089c, 0x0898, 0x01db, 0x01dc, 0x01dd}, + { + 104, 5520, 0x40, 0x01, 0x01, 0x02, 0x28, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x72, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08a4, 0x08a0, 0x089c, 0x01da, 0x01db, 0x01dc}, + { + 106, 5530, 0x40, 0x01, 0x01, 0x02, 0x29, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x09, 0x00, 0x72, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08a8, 0x08a4, 0x08a0, 0x01d9, 0x01da, 0x01db}, + { + 108, 5540, 0x3a, 0x01, 0x01, 0x02, 0x2a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x09, 0x00, 0x71, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08ac, 0x08a8, 0x08a4, 0x01d8, 0x01d9, 0x01da}, + { + 110, 5550, 0x3a, 0x01, 0x01, 0x02, 0x2b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08b0, 0x08ac, 0x08a8, 0x01d7, 0x01d8, 0x01d9}, + { + 112, 5560, 0x34, 0x01, 0x01, 0x02, 0x2c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08b4, 0x08b0, 0x08ac, 0x01d7, 0x01d7, 0x01d8}, + { + 114, 5570, 0x34, 0x01, 0x01, 0x02, 0x2d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, 0x61, 0x62, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08b8, 0x08b4, 0x08b0, 0x01d6, 0x01d7, 0x01d7}, + { + 116, 5580, 0x2e, 0x01, 0x01, 0x02, 0x2e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x08, 0x00, 0x60, 0x62, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x08bc, 0x08b8, 0x08b4, 0x01d5, 0x01d6, 0x01d7}, + { + 118, 5590, 0x2e, 0x01, 0x01, 0x02, 0x2f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x08, 0x00, 0x50, 0x61, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00, 0x61, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x08c0, 0x08bc, 0x08b8, 0x01d4, 0x01d5, 0x01d6}, + { + 120, 5600, 0x28, 0x01, 0x01, 0x02, 0x30, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, 0x50, 0x51, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00, 0x51, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x08c4, 0x08c0, 0x08bc, 0x01d3, 0x01d4, 0x01d5}, + { + 122, 5610, 0x28, 0x01, 0x01, 0x02, 0x31, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, 0x50, 0x51, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00, 0x51, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x08c8, 0x08c4, 0x08c0, 0x01d2, 0x01d3, 0x01d4}, + { + 124, 5620, 0x21, 0x01, 0x01, 0x02, 0x32, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, 0x50, 0x50, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00, 0x50, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x08cc, 0x08c8, 0x08c4, 0x01d2, 0x01d2, 0x01d3}, + { + 126, 5630, 0x21, 0x01, 0x01, 0x02, 0x33, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x50, 0x50, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00, 0x50, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x08d0, 0x08cc, 0x08c8, 0x01d1, 0x01d2, 0x01d2}, + { + 128, 5640, 0x1c, 0x01, 0x01, 0x02, 0x34, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x40, 0x50, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00, 0x50, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x08d4, 0x08d0, 0x08cc, 0x01d0, 0x01d1, 0x01d2}, + { + 130, 5650, 0x1c, 0x01, 0x01, 0x02, 0x35, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00, 0x40, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x08d8, 0x08d4, 0x08d0, 0x01cf, 0x01d0, 0x01d1}, + { + 132, 5660, 0x16, 0x01, 0x01, 0x02, 0x36, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00, 0x40, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x08dc, 0x08d8, 0x08d4, 0x01ce, 0x01cf, 0x01d0}, + { + 134, 5670, 0x16, 0x01, 0x01, 0x02, 0x37, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x40, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x08e0, 0x08dc, 0x08d8, 0x01ce, 0x01ce, 0x01cf}, + { + 136, 5680, 0x10, 0x01, 0x01, 0x02, 0x38, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x08e4, 0x08e0, 0x08dc, 0x01cd, 0x01ce, 0x01ce}, + { + 138, 5690, 0x10, 0x01, 0x01, 0x02, 0x39, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x08e8, 0x08e4, 0x08e0, 0x01cc, 0x01cd, 0x01ce}, + { + 140, 5700, 0x0a, 0x01, 0x01, 0x02, 0x3a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x08ec, 0x08e8, 0x08e4, 0x01cb, 0x01cc, 0x01cd}, + { + 142, 5710, 0x0a, 0x01, 0x01, 0x02, 0x3b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x08f0, 0x08ec, 0x08e8, 0x01ca, 0x01cb, 0x01cc}, + { + 144, 5720, 0x0a, 0x01, 0x01, 0x02, 0x3c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x08f4, 0x08f0, 0x08ec, 0x01c9, 0x01ca, 0x01cb}, + { + 145, 5725, 0x03, 0x01, 0x02, 0x04, 0x79, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x08f6, 0x08f2, 0x08ee, 0x01c9, 0x01ca, 0x01cb}, + { + 146, 5730, 0x0a, 0x01, 0x01, 0x02, 0x3d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x08f8, 0x08f4, 0x08f0, 0x01c9, 0x01c9, 0x01ca}, + { + 147, 5735, 0x03, 0x01, 0x02, 0x04, 0x7b, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6d, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6d, 0x00, 0x08fa, 0x08f6, 0x08f2, 0x01c8, 0x01c9, 0x01ca}, + { + 148, 5740, 0x0a, 0x01, 0x01, 0x02, 0x3e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6d, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6d, 0x00, 0x08fc, 0x08f8, 0x08f4, 0x01c8, 0x01c9, 0x01c9}, + { + 149, 5745, 0xfe, 0x00, 0x02, 0x04, 0x7d, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6d, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6d, 0x00, 0x08fe, 0x08fa, 0x08f6, 0x01c8, 0x01c8, 0x01c9}, + { + 150, 5750, 0x0a, 0x01, 0x01, 0x02, 0x3f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6d, 0x00, 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6d, 0x00, 0x0900, 0x08fc, 0x08f8, 0x01c7, 0x01c8, 0x01c9}, + { + 151, 5755, 0xfe, 0x00, 0x02, 0x04, 0x7f, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6c, 0x00, 0x0902, 0x08fe, 0x08fa, 0x01c7, 0x01c8, 0x01c8}, + { + 152, 5760, 0x0a, 0x01, 0x01, 0x02, 0x40, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x05, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6c, 0x00, 0x0904, 0x0900, 0x08fc, 0x01c6, 0x01c7, 0x01c8}, + { + 153, 5765, 0xf8, 0x00, 0x02, 0x04, 0x81, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x05, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6c, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6c, 0x00, 0x0906, 0x0902, 0x08fe, 0x01c6, 0x01c7, 0x01c8}, + { + 154, 5770, 0x0a, 0x01, 0x01, 0x02, 0x41, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x0908, 0x0904, 0x0900, 0x01c6, 0x01c6, 0x01c7}, + { + 155, 5775, 0xf8, 0x00, 0x02, 0x04, 0x83, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x090a, 0x0906, 0x0902, 0x01c5, 0x01c6, 0x01c7}, + { + 156, 5780, 0x0a, 0x01, 0x01, 0x02, 0x42, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x090c, 0x0908, 0x0904, 0x01c5, 0x01c6, 0x01c6}, + { + 157, 5785, 0xf2, 0x00, 0x02, 0x04, 0x85, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x090e, 0x090a, 0x0906, 0x01c4, 0x01c5, 0x01c6}, + { + 158, 5790, 0x0a, 0x01, 0x01, 0x02, 0x43, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x0910, 0x090c, 0x0908, 0x01c4, 0x01c5, 0x01c6}, + { + 159, 5795, 0xf2, 0x00, 0x02, 0x04, 0x87, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x0912, 0x090e, 0x090a, 0x01c4, 0x01c4, 0x01c5}, + { + 160, 5800, 0x0a, 0x01, 0x01, 0x02, 0x44, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x0914, 0x0910, 0x090c, 0x01c3, 0x01c4, 0x01c5}, + { + 161, 5805, 0xed, 0x00, 0x02, 0x04, 0x89, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x0916, 0x0912, 0x090e, 0x01c3, 0x01c4, 0x01c4}, + { + 162, 5810, 0x0a, 0x01, 0x01, 0x02, 0x45, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x0918, 0x0914, 0x0910, 0x01c2, 0x01c3, 0x01c4}, + { + 163, 5815, 0xed, 0x00, 0x02, 0x04, 0x8b, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x091a, 0x0916, 0x0912, 0x01c2, 0x01c3, 0x01c4}, + { + 164, 5820, 0x0a, 0x01, 0x01, 0x02, 0x46, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x091c, 0x0918, 0x0914, 0x01c2, 0x01c2, 0x01c3}, + { + 165, 5825, 0xed, 0x00, 0x02, 0x04, 0x8d, 0x07, 0x07, 0x04, 0x10, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x69, 0x00, 0x091e, 0x091a, 0x0916, 0x01c1, 0x01c2, 0x01c3}, + { + 166, 5830, 0x0a, 0x01, 0x01, 0x02, 0x47, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x69, 0x00, 0x0920, 0x091c, 0x0918, 0x01c1, 0x01c2, 0x01c2}, + { + 168, 5840, 0x0a, 0x01, 0x01, 0x02, 0x48, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x0924, 0x0920, 0x091c, 0x01c0, 0x01c1, 0x01c2}, + { + 170, 5850, 0xe0, 0x00, 0x01, 0x02, 0x49, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x0928, 0x0924, 0x0920, 0x01bf, 0x01c0, 0x01c1}, + { + 172, 5860, 0xde, 0x00, 0x01, 0x02, 0x4a, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x092c, 0x0928, 0x0924, 0x01bf, 0x01bf, 0x01c0}, + { + 174, 5870, 0xdb, 0x00, 0x01, 0x02, 0x4b, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x0930, 0x092c, 0x0928, 0x01be, 0x01bf, 0x01bf}, + { + 176, 5880, 0xd8, 0x00, 0x01, 0x02, 0x4c, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x0934, 0x0930, 0x092c, 0x01bd, 0x01be, 0x01bf}, + { + 178, 5890, 0xd6, 0x00, 0x01, 0x02, 0x4d, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x0938, 0x0934, 0x0930, 0x01bc, 0x01bd, 0x01be}, + { + 180, 5900, 0xd3, 0x00, 0x01, 0x02, 0x4e, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x093c, 0x0938, 0x0934, 0x01bc, 0x01bc, 0x01bd}, + { + 182, 5910, 0xd6, 0x00, 0x01, 0x02, 0x4f, 0x05, 0x05, 0x04, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x0940, 0x093c, 0x0938, 0x01bb, 0x01bc, 0x01bc}, + { + 1, 2412, 0x00, 0x01, 0x03, 0x09, 0x6c, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x04, 0x04, 0x04, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x78, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0b, 0x00, 0x0a, 0x00, 0x89, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0b, 0x00, 0x0a, 0x03c9, 0x03c5, 0x03c1, 0x043a, 0x043f, 0x0443}, + { + 2, 2417, 0x00, 0x01, 0x03, 0x09, 0x71, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x78, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0b, 0x00, 0x0a, 0x00, 0x89, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0b, 0x00, 0x0a, 0x03cb, 0x03c7, 0x03c3, 0x0438, 0x043d, 0x0441}, + { + 3, 2422, 0x00, 0x01, 0x03, 0x09, 0x76, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x67, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0b, 0x00, 0x0a, 0x00, 0x89, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0b, 0x00, 0x0a, 0x03cd, 0x03c9, 0x03c5, 0x0436, 0x043a, 0x043f}, + { + 4, 2427, 0x00, 0x01, 0x03, 0x09, 0x7b, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x57, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x78, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x03cf, 0x03cb, 0x03c7, 0x0434, 0x0438, 0x043d}, + { + 5, 2432, 0x00, 0x01, 0x03, 0x09, 0x80, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x56, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x77, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x03d1, 0x03cd, 0x03c9, 0x0431, 0x0436, 0x043a}, + { + 6, 2437, 0x00, 0x01, 0x03, 0x09, 0x85, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x46, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x76, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x03d3, 0x03cf, 0x03cb, 0x042f, 0x0434, 0x0438}, + { + 7, 2442, 0x00, 0x01, 0x03, 0x09, 0x8a, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x45, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x66, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x03d5, 0x03d1, 0x03cd, 0x042d, 0x0431, 0x0436}, + { + 8, 2447, 0x00, 0x01, 0x03, 0x09, 0x8f, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x34, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x55, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x09, 0x03d7, 0x03d3, 0x03cf, 0x042b, 0x042f, 0x0434}, + { + 9, 2452, 0x00, 0x01, 0x03, 0x09, 0x94, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x23, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x45, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x09, 0x03d9, 0x03d5, 0x03d1, 0x0429, 0x042d, 0x0431}, + { + 10, 2457, 0x00, 0x01, 0x03, 0x09, 0x99, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x12, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x34, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x09, 0x03db, 0x03d7, 0x03d3, 0x0427, 0x042b, 0x042f}, + { + 11, 2462, 0x00, 0x01, 0x03, 0x09, 0x9e, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09, 0x00, 0x33, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x03dd, 0x03d9, 0x03d5, 0x0424, 0x0429, 0x042d}, + { + 12, 2467, 0x00, 0x01, 0x03, 0x09, 0xa3, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09, 0x00, 0x22, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x03df, 0x03db, 0x03d7, 0x0422, 0x0427, 0x042b}, + { + 13, 2472, 0x00, 0x01, 0x03, 0x09, 0xa8, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09, 0x00, 0x11, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x03e1, 0x03dd, 0x03d9, 0x0420, 0x0424, 0x0429}, + { + 14, 2484, 0xff, 0x01, 0x03, 0x09, 0xb4, 0x08, 0x08, 0x04, 0x16, 0x01, + 0x07, 0x07, 0x07, 0x8f, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x03e6, 0x03e2, 0x03de, 0x041b, 0x041f, 0x0424} +}; + +static const struct chan_info_nphy_radio205x chan_info_nphyrev6_2056v11[] = { + { + 184, 4920, 0xff, 0x01, 0x01, 0x01, 0xec, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07b4, 0x07b0, 0x07ac, 0x0214, 0x0215, 0x0216}, + { + 186, 4930, 0xff, 0x01, 0x01, 0x01, 0xed, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07b8, 0x07b4, 0x07b0, 0x0213, 0x0214, 0x0215}, + { + 188, 4940, 0xff, 0x01, 0x01, 0x01, 0xee, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07bc, 0x07b8, 0x07b4, 0x0212, 0x0213, 0x0214}, + { + 190, 4950, 0xff, 0x01, 0x01, 0x01, 0xef, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x00, 0x00, 0x00, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07c0, 0x07bc, 0x07b8, 0x0211, 0x0212, 0x0213}, + { + 192, 4960, 0xff, 0x01, 0x01, 0x01, 0xf0, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07c4, 0x07c0, 0x07bc, 0x020f, 0x0211, 0x0212}, + { + 194, 4970, 0xff, 0x01, 0x01, 0x01, 0xf1, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07c8, 0x07c4, 0x07c0, 0x020e, 0x020f, 0x0211}, + { + 196, 4980, 0xff, 0x01, 0x01, 0x01, 0xf2, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07cc, 0x07c8, 0x07c4, 0x020d, 0x020e, 0x020f}, + { + 198, 4990, 0xff, 0x01, 0x01, 0x01, 0xf3, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07d0, 0x07cc, 0x07c8, 0x020c, 0x020d, 0x020e}, + { + 200, 5000, 0xff, 0x01, 0x01, 0x01, 0xf4, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07d4, 0x07d0, 0x07cc, 0x020b, 0x020c, 0x020d}, + { + 202, 5010, 0xff, 0x01, 0x01, 0x01, 0xf5, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07d8, 0x07d4, 0x07d0, 0x020a, 0x020b, 0x020c}, + { + 204, 5020, 0xf7, 0x01, 0x01, 0x01, 0xf6, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07dc, 0x07d8, 0x07d4, 0x0209, 0x020a, 0x020b}, + { + 206, 5030, 0xf7, 0x01, 0x01, 0x01, 0xf7, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07e0, 0x07dc, 0x07d8, 0x0208, 0x0209, 0x020a}, + { + 208, 5040, 0xef, 0x01, 0x01, 0x01, 0xf8, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07e4, 0x07e0, 0x07dc, 0x0207, 0x0208, 0x0209}, + { + 210, 5050, 0xef, 0x01, 0x01, 0x01, 0xf9, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07e8, 0x07e4, 0x07e0, 0x0206, 0x0207, 0x0208}, + { + 212, 5060, 0xe6, 0x01, 0x01, 0x01, 0xfa, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfe, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfe, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07ec, 0x07e8, 0x07e4, 0x0205, 0x0206, 0x0207}, + { + 214, 5070, 0xe6, 0x01, 0x01, 0x01, 0xfb, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfd, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07f0, 0x07ec, 0x07e8, 0x0204, 0x0205, 0x0206}, + { + 216, 5080, 0xde, 0x01, 0x01, 0x01, 0xfc, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfd, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07f4, 0x07f0, 0x07ec, 0x0203, 0x0204, 0x0205}, + { + 218, 5090, 0xde, 0x01, 0x01, 0x01, 0xfd, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x01, 0x01, 0x01, 0x8f, 0x0f, 0x00, 0xff, 0xfd, 0x00, 0x09, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x09, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07f8, 0x07f4, 0x07f0, 0x0202, 0x0203, 0x0204}, + { + 220, 5100, 0xd6, 0x01, 0x01, 0x01, 0xfe, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfd, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfd, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x07fc, 0x07f8, 0x07f4, 0x0201, 0x0202, 0x0203}, + { + 222, 5110, 0xd6, 0x01, 0x01, 0x01, 0xff, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x0800, 0x07fc, 0x07f8, 0x0200, 0x0201, 0x0202}, + { + 224, 5120, 0xce, 0x01, 0x01, 0x02, 0x00, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x0804, 0x0800, 0x07fc, 0x01ff, 0x0200, 0x0201}, + { + 226, 5130, 0xce, 0x01, 0x01, 0x02, 0x01, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfc, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfc, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x0808, 0x0804, 0x0800, 0x01fe, 0x01ff, 0x0200}, + { + 228, 5140, 0xc6, 0x01, 0x01, 0x02, 0x02, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfb, 0x00, 0x08, 0x00, 0x77, + 0x00, 0x0f, 0x00, 0x6f, 0x00, 0xfb, 0x00, 0x08, 0x00, 0x77, 0x00, 0x0f, + 0x00, 0x6f, 0x00, 0x080c, 0x0808, 0x0804, 0x01fd, 0x01fe, 0x01ff}, + { + 32, 5160, 0xbe, 0x01, 0x01, 0x02, 0x04, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfa, 0x00, 0x07, 0x00, 0x77, + 0x00, 0x0e, 0x00, 0x6f, 0x00, 0xfa, 0x00, 0x07, 0x00, 0x77, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x0814, 0x0810, 0x080c, 0x01fb, 0x01fc, 0x01fd}, + { + 34, 5170, 0xbe, 0x01, 0x01, 0x02, 0x05, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xfa, 0x00, 0x07, 0x00, 0x77, + 0x00, 0x0e, 0x00, 0x6f, 0x00, 0xfa, 0x00, 0x07, 0x00, 0x77, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x0818, 0x0814, 0x0810, 0x01fa, 0x01fb, 0x01fc}, + { + 36, 5180, 0xb6, 0x01, 0x01, 0x02, 0x06, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xf9, 0x00, 0x06, 0x00, 0x77, + 0x00, 0x0e, 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x06, 0x00, 0x77, 0x00, 0x0e, + 0x00, 0x6f, 0x00, 0x081c, 0x0818, 0x0814, 0x01f9, 0x01fa, 0x01fb}, + { + 38, 5190, 0xb6, 0x01, 0x01, 0x02, 0x07, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xf9, 0x00, 0x06, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x06, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0820, 0x081c, 0x0818, 0x01f8, 0x01f9, 0x01fa}, + { + 40, 5200, 0xaf, 0x01, 0x01, 0x02, 0x08, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xf9, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0824, 0x0820, 0x081c, 0x01f7, 0x01f8, 0x01f9}, + { + 42, 5210, 0xaf, 0x01, 0x01, 0x02, 0x09, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8f, 0x0f, 0x00, 0xff, 0xf9, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xf9, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0828, 0x0824, 0x0820, 0x01f6, 0x01f7, 0x01f8}, + { + 44, 5220, 0xa7, 0x01, 0x01, 0x02, 0x0a, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, 0xfe, 0xd8, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xd8, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x082c, 0x0828, 0x0824, 0x01f5, 0x01f6, 0x01f7}, + { + 46, 5230, 0xa7, 0x01, 0x01, 0x02, 0x0b, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, 0xee, 0xd8, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xd8, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0830, 0x082c, 0x0828, 0x01f4, 0x01f5, 0x01f6}, + { + 48, 5240, 0xa0, 0x01, 0x01, 0x02, 0x0c, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, 0xee, 0xc8, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xc8, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0834, 0x0830, 0x082c, 0x01f3, 0x01f4, 0x01f5}, + { + 50, 5250, 0xa0, 0x01, 0x01, 0x02, 0x0d, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0f, 0x00, 0xed, 0xc7, 0x00, 0x05, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xc7, 0x00, 0x05, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x0838, 0x0834, 0x0830, 0x01f2, 0x01f3, 0x01f4}, + { + 52, 5260, 0x98, 0x01, 0x01, 0x02, 0x0e, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x02, 0x02, 0x02, 0x8e, 0x0e, 0x00, 0xed, 0xc7, 0x00, 0x04, 0x00, 0x77, + 0x00, 0x0d, 0x00, 0x6f, 0x00, 0xc7, 0x00, 0x04, 0x00, 0x77, 0x00, 0x0d, + 0x00, 0x6f, 0x00, 0x083c, 0x0838, 0x0834, 0x01f1, 0x01f2, 0x01f3}, + { + 54, 5270, 0x98, 0x01, 0x01, 0x02, 0x0f, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8e, 0x0e, 0x00, 0xed, 0xc7, 0x00, 0x04, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xc7, 0x00, 0x04, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x0840, 0x083c, 0x0838, 0x01f0, 0x01f1, 0x01f2}, + { + 56, 5280, 0x91, 0x01, 0x01, 0x02, 0x10, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x0844, 0x0840, 0x083c, 0x01f0, 0x01f0, 0x01f1}, + { + 58, 5290, 0x91, 0x01, 0x01, 0x02, 0x11, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x0848, 0x0844, 0x0840, 0x01ef, 0x01f0, 0x01f0}, + { + 60, 5300, 0x8a, 0x01, 0x01, 0x02, 0x12, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x084c, 0x0848, 0x0844, 0x01ee, 0x01ef, 0x01f0}, + { + 62, 5310, 0x8a, 0x01, 0x01, 0x02, 0x13, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdc, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x0850, 0x084c, 0x0848, 0x01ed, 0x01ee, 0x01ef}, + { + 64, 5320, 0x83, 0x01, 0x01, 0x02, 0x14, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0e, 0x00, 0xdb, 0xb7, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0c, 0x00, 0x6f, 0x00, 0xb7, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0c, + 0x00, 0x6f, 0x00, 0x0854, 0x0850, 0x084c, 0x01ec, 0x01ed, 0x01ee}, + { + 66, 5330, 0x83, 0x01, 0x01, 0x02, 0x15, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xcb, 0xa6, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0b, 0x00, 0x6f, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x0858, 0x0854, 0x0850, 0x01eb, 0x01ec, 0x01ed}, + { + 68, 5340, 0x7c, 0x01, 0x01, 0x02, 0x16, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8d, 0x0d, 0x00, 0xca, 0xa6, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0b, 0x00, 0x6f, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x085c, 0x0858, 0x0854, 0x01ea, 0x01eb, 0x01ec}, + { + 70, 5350, 0x7c, 0x01, 0x01, 0x02, 0x17, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, 0xca, 0xa6, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0b, 0x00, 0x6f, 0x00, 0xa6, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0b, + 0x00, 0x6f, 0x00, 0x0860, 0x085c, 0x0858, 0x01e9, 0x01ea, 0x01eb}, + { + 72, 5360, 0x75, 0x01, 0x01, 0x02, 0x18, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, 0xc9, 0x95, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x95, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0864, 0x0860, 0x085c, 0x01e8, 0x01e9, 0x01ea}, + { + 74, 5370, 0x75, 0x01, 0x01, 0x02, 0x19, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0d, 0x00, 0xc9, 0x95, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x95, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0868, 0x0864, 0x0860, 0x01e7, 0x01e8, 0x01e9}, + { + 76, 5380, 0x6e, 0x01, 0x01, 0x02, 0x1a, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xb8, 0x95, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x95, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x086c, 0x0868, 0x0864, 0x01e6, 0x01e7, 0x01e8}, + { + 78, 5390, 0x6e, 0x01, 0x01, 0x02, 0x1b, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xb8, 0x84, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0870, 0x086c, 0x0868, 0x01e5, 0x01e6, 0x01e7}, + { + 80, 5400, 0x67, 0x01, 0x01, 0x02, 0x1c, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xb8, 0x84, 0x00, 0x03, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x03, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0874, 0x0870, 0x086c, 0x01e5, 0x01e5, 0x01e6}, + { + 82, 5410, 0x67, 0x01, 0x01, 0x02, 0x1d, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xb7, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0878, 0x0874, 0x0870, 0x01e4, 0x01e5, 0x01e5}, + { + 84, 5420, 0x61, 0x01, 0x01, 0x02, 0x1e, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0c, 0x00, 0xa7, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x087c, 0x0878, 0x0874, 0x01e3, 0x01e4, 0x01e5}, + { + 86, 5430, 0x61, 0x01, 0x01, 0x02, 0x1f, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x03, 0x03, 0x03, 0x8c, 0x0b, 0x00, 0xa6, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x0a, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x0a, + 0x00, 0x6f, 0x00, 0x0880, 0x087c, 0x0878, 0x01e2, 0x01e3, 0x01e4}, + { + 88, 5440, 0x5a, 0x01, 0x01, 0x02, 0x20, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, 0xa6, 0x84, 0x00, 0x02, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x02, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x0884, 0x0880, 0x087c, 0x01e1, 0x01e2, 0x01e3}, + { + 90, 5450, 0x5a, 0x01, 0x01, 0x02, 0x21, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, 0x95, 0x84, 0x00, 0x01, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x01, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x0888, 0x0884, 0x0880, 0x01e0, 0x01e1, 0x01e2}, + { + 92, 5460, 0x53, 0x01, 0x01, 0x02, 0x22, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, 0x95, 0x84, 0x00, 0x01, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x84, 0x00, 0x01, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x088c, 0x0888, 0x0884, 0x01df, 0x01e0, 0x01e1}, + { + 94, 5470, 0x53, 0x01, 0x01, 0x02, 0x23, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8b, 0x0b, 0x00, 0x94, 0x73, 0x00, 0x01, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x01, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x0890, 0x088c, 0x0888, 0x01de, 0x01df, 0x01e0}, + { + 96, 5480, 0x4d, 0x01, 0x01, 0x02, 0x24, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x84, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x0894, 0x0890, 0x088c, 0x01dd, 0x01de, 0x01df}, + { + 98, 5490, 0x4d, 0x01, 0x01, 0x02, 0x25, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x83, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x0898, 0x0894, 0x0890, 0x01dd, 0x01dd, 0x01de}, + { + 100, 5500, 0x47, 0x01, 0x01, 0x02, 0x26, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x82, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x089c, 0x0898, 0x0894, 0x01dc, 0x01dd, 0x01dd}, + { + 102, 5510, 0x47, 0x01, 0x01, 0x02, 0x27, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x82, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08a0, 0x089c, 0x0898, 0x01db, 0x01dc, 0x01dd}, + { + 104, 5520, 0x40, 0x01, 0x01, 0x02, 0x28, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x0a, 0x00, 0x72, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08a4, 0x08a0, 0x089c, 0x01da, 0x01db, 0x01dc}, + { + 106, 5530, 0x40, 0x01, 0x01, 0x02, 0x29, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x09, 0x00, 0x72, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08a8, 0x08a4, 0x08a0, 0x01d9, 0x01da, 0x01db}, + { + 108, 5540, 0x3a, 0x01, 0x01, 0x02, 0x2a, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x8a, 0x09, 0x00, 0x71, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08ac, 0x08a8, 0x08a4, 0x01d8, 0x01d9, 0x01da}, + { + 110, 5550, 0x3a, 0x01, 0x01, 0x02, 0x2b, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08b0, 0x08ac, 0x08a8, 0x01d7, 0x01d8, 0x01d9}, + { + 112, 5560, 0x34, 0x01, 0x01, 0x02, 0x2c, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08b4, 0x08b0, 0x08ac, 0x01d7, 0x01d7, 0x01d8}, + { + 114, 5570, 0x34, 0x01, 0x01, 0x02, 0x2d, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x09, 0x00, 0x61, 0x62, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x09, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x00, 0x00, 0x77, 0x00, 0x09, + 0x00, 0x6f, 0x00, 0x08b8, 0x08b4, 0x08b0, 0x01d6, 0x01d7, 0x01d7}, + { + 116, 5580, 0x2e, 0x01, 0x01, 0x02, 0x2e, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x08, 0x00, 0x60, 0x62, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x08bc, 0x08b8, 0x08b4, 0x01d5, 0x01d6, 0x01d7}, + { + 118, 5590, 0x2e, 0x01, 0x01, 0x02, 0x2f, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x04, 0x04, 0x04, 0x89, 0x08, 0x00, 0x50, 0x61, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00, 0x61, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x08c0, 0x08bc, 0x08b8, 0x01d4, 0x01d5, 0x01d6}, + { + 120, 5600, 0x28, 0x01, 0x01, 0x02, 0x30, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, 0x50, 0x51, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00, 0x51, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x08c4, 0x08c0, 0x08bc, 0x01d3, 0x01d4, 0x01d5}, + { + 122, 5610, 0x28, 0x01, 0x01, 0x02, 0x31, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, 0x50, 0x51, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x08, 0x00, 0x6f, 0x00, 0x51, 0x00, 0x00, 0x00, 0x77, 0x00, 0x08, + 0x00, 0x6f, 0x00, 0x08c8, 0x08c4, 0x08c0, 0x01d2, 0x01d3, 0x01d4}, + { + 124, 5620, 0x21, 0x01, 0x01, 0x02, 0x32, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x89, 0x08, 0x00, 0x50, 0x50, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00, 0x50, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x08cc, 0x08c8, 0x08c4, 0x01d2, 0x01d2, 0x01d3}, + { + 126, 5630, 0x21, 0x01, 0x01, 0x02, 0x33, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x50, 0x50, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00, 0x50, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x08d0, 0x08cc, 0x08c8, 0x01d1, 0x01d2, 0x01d2}, + { + 128, 5640, 0x1c, 0x01, 0x01, 0x02, 0x34, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x40, 0x50, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00, 0x50, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x08d4, 0x08d0, 0x08cc, 0x01d0, 0x01d1, 0x01d2}, + { + 130, 5650, 0x1c, 0x01, 0x01, 0x02, 0x35, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x07, 0x00, 0x6f, 0x00, 0x40, 0x00, 0x00, 0x00, 0x77, 0x00, 0x07, + 0x00, 0x6f, 0x00, 0x08d8, 0x08d4, 0x08d0, 0x01cf, 0x01d0, 0x01d1}, + { + 132, 5660, 0x16, 0x01, 0x01, 0x02, 0x36, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00, 0x40, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x08dc, 0x08d8, 0x08d4, 0x01ce, 0x01cf, 0x01d0}, + { + 134, 5670, 0x16, 0x01, 0x01, 0x02, 0x37, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x88, 0x07, 0x00, 0x40, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x08e0, 0x08dc, 0x08d8, 0x01ce, 0x01ce, 0x01cf}, + { + 136, 5680, 0x10, 0x01, 0x01, 0x02, 0x38, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x08e4, 0x08e0, 0x08dc, 0x01cd, 0x01ce, 0x01ce}, + { + 138, 5690, 0x10, 0x01, 0x01, 0x02, 0x39, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6f, 0x00, 0x08e8, 0x08e4, 0x08e0, 0x01cc, 0x01cd, 0x01ce}, + { + 140, 5700, 0x0a, 0x01, 0x01, 0x02, 0x3a, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x08ec, 0x08e8, 0x08e4, 0x01cb, 0x01cc, 0x01cd}, + { + 142, 5710, 0x0a, 0x01, 0x01, 0x02, 0x3b, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x08f0, 0x08ec, 0x08e8, 0x01ca, 0x01cb, 0x01cc}, + { + 144, 5720, 0x0a, 0x01, 0x01, 0x02, 0x3c, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x08f4, 0x08f0, 0x08ec, 0x01c9, 0x01ca, 0x01cb}, + { + 145, 5725, 0x03, 0x01, 0x02, 0x04, 0x79, 0x05, 0x05, 0x02, 0x15, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x06, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x08f6, 0x08f2, 0x08ee, 0x01c9, 0x01ca, 0x01cb}, + { + 146, 5730, 0x0a, 0x01, 0x01, 0x02, 0x3d, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6e, 0x00, 0x08f8, 0x08f4, 0x08f0, 0x01c9, 0x01c9, 0x01ca}, + { + 147, 5735, 0x03, 0x01, 0x02, 0x04, 0x7b, 0x05, 0x05, 0x02, 0x15, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6d, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6d, 0x00, 0x08fa, 0x08f6, 0x08f2, 0x01c8, 0x01c9, 0x01ca}, + { + 148, 5740, 0x0a, 0x01, 0x01, 0x02, 0x3e, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6d, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6d, 0x00, 0x08fc, 0x08f8, 0x08f4, 0x01c8, 0x01c9, 0x01c9}, + { + 149, 5745, 0xfe, 0x00, 0x02, 0x04, 0x7d, 0x05, 0x05, 0x02, 0x15, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x06, 0x00, 0x6d, 0x00, 0x30, 0x00, 0x00, 0x00, 0x77, 0x00, 0x06, + 0x00, 0x6d, 0x00, 0x08fe, 0x08fa, 0x08f6, 0x01c8, 0x01c8, 0x01c9}, + { + 150, 5750, 0x0a, 0x01, 0x01, 0x02, 0x3f, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x20, 0x20, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6d, 0x00, 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6d, 0x00, 0x0900, 0x08fc, 0x08f8, 0x01c7, 0x01c8, 0x01c9}, + { + 151, 5755, 0xfe, 0x00, 0x02, 0x04, 0x7f, 0x05, 0x05, 0x02, 0x15, 0x01, + 0x05, 0x05, 0x05, 0x87, 0x05, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6c, 0x00, 0x0902, 0x08fe, 0x08fa, 0x01c7, 0x01c8, 0x01c8}, + { + 152, 5760, 0x0a, 0x01, 0x01, 0x02, 0x40, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x05, 0x00, 0x10, 0x20, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6c, 0x00, 0x0904, 0x0900, 0x08fc, 0x01c6, 0x01c7, 0x01c8}, + { + 153, 5765, 0xf8, 0x00, 0x02, 0x04, 0x81, 0x05, 0x05, 0x02, 0x15, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x05, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6c, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6c, 0x00, 0x0906, 0x0902, 0x08fe, 0x01c6, 0x01c7, 0x01c8}, + { + 154, 5770, 0x0a, 0x01, 0x01, 0x02, 0x41, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x0908, 0x0904, 0x0900, 0x01c6, 0x01c6, 0x01c7}, + { + 155, 5775, 0xf8, 0x00, 0x02, 0x04, 0x83, 0x05, 0x05, 0x02, 0x15, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x090a, 0x0906, 0x0902, 0x01c5, 0x01c6, 0x01c7}, + { + 156, 5780, 0x0a, 0x01, 0x01, 0x02, 0x42, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x05, 0x05, 0x05, 0x86, 0x04, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x090c, 0x0908, 0x0904, 0x01c5, 0x01c6, 0x01c6}, + { + 157, 5785, 0xf2, 0x00, 0x02, 0x04, 0x85, 0x05, 0x05, 0x02, 0x15, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x090e, 0x090a, 0x0906, 0x01c4, 0x01c5, 0x01c6}, + { + 158, 5790, 0x0a, 0x01, 0x01, 0x02, 0x43, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x10, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x0910, 0x090c, 0x0908, 0x01c4, 0x01c5, 0x01c6}, + { + 159, 5795, 0xf2, 0x00, 0x02, 0x04, 0x87, 0x05, 0x05, 0x02, 0x15, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x0912, 0x090e, 0x090a, 0x01c4, 0x01c4, 0x01c5}, + { + 160, 5800, 0x0a, 0x01, 0x01, 0x02, 0x44, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6b, 0x00, 0x0914, 0x0910, 0x090c, 0x01c3, 0x01c4, 0x01c5}, + { + 161, 5805, 0xed, 0x00, 0x02, 0x04, 0x89, 0x05, 0x05, 0x02, 0x15, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x0916, 0x0912, 0x090e, 0x01c3, 0x01c4, 0x01c4}, + { + 162, 5810, 0x0a, 0x01, 0x01, 0x02, 0x45, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x0918, 0x0914, 0x0910, 0x01c2, 0x01c3, 0x01c4}, + { + 163, 5815, 0xed, 0x00, 0x02, 0x04, 0x8b, 0x05, 0x05, 0x02, 0x15, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x091a, 0x0916, 0x0912, 0x01c2, 0x01c3, 0x01c4}, + { + 164, 5820, 0x0a, 0x01, 0x01, 0x02, 0x46, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x6a, 0x00, 0x091c, 0x0918, 0x0914, 0x01c2, 0x01c2, 0x01c3}, + { + 165, 5825, 0xed, 0x00, 0x02, 0x04, 0x8d, 0x05, 0x05, 0x02, 0x15, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x69, 0x00, 0x091e, 0x091a, 0x0916, 0x01c1, 0x01c2, 0x01c3}, + { + 166, 5830, 0x0a, 0x01, 0x01, 0x02, 0x47, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x05, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, + 0x00, 0x69, 0x00, 0x0920, 0x091c, 0x0918, 0x01c1, 0x01c2, 0x01c2}, + { + 168, 5840, 0x0a, 0x01, 0x01, 0x02, 0x48, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x86, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x0924, 0x0920, 0x091c, 0x01c0, 0x01c1, 0x01c2}, + { + 170, 5850, 0xe0, 0x00, 0x01, 0x02, 0x49, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x0928, 0x0924, 0x0920, 0x01bf, 0x01c0, 0x01c1}, + { + 172, 5860, 0xde, 0x00, 0x01, 0x02, 0x4a, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x69, 0x00, 0x092c, 0x0928, 0x0924, 0x01bf, 0x01bf, 0x01c0}, + { + 174, 5870, 0xdb, 0x00, 0x01, 0x02, 0x4b, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x0930, 0x092c, 0x0928, 0x01be, 0x01bf, 0x01bf}, + { + 176, 5880, 0xd8, 0x00, 0x01, 0x02, 0x4c, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x0934, 0x0930, 0x092c, 0x01bd, 0x01be, 0x01bf}, + { + 178, 5890, 0xd6, 0x00, 0x01, 0x02, 0x4d, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x0938, 0x0934, 0x0930, 0x01bc, 0x01bd, 0x01be}, + { + 180, 5900, 0xd3, 0x00, 0x01, 0x02, 0x4e, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x093c, 0x0938, 0x0934, 0x01bc, 0x01bc, 0x01bd}, + { + 182, 5910, 0xd6, 0x00, 0x01, 0x02, 0x4f, 0x05, 0x05, 0x02, 0x0c, 0x01, + 0x06, 0x06, 0x06, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x04, + 0x00, 0x68, 0x00, 0x0940, 0x093c, 0x0938, 0x01bb, 0x01bc, 0x01bc}, + { + 1, 2412, 0x00, 0x01, 0x03, 0x09, 0x6c, 0x06, 0x06, 0x04, 0x2b, 0x01, + 0x04, 0x04, 0x04, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x78, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0b, 0x00, 0x0a, 0x00, 0x89, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0b, 0x00, 0x0a, 0x03c9, 0x03c5, 0x03c1, 0x043a, 0x043f, 0x0443}, + { + 2, 2417, 0x00, 0x01, 0x03, 0x09, 0x71, 0x06, 0x06, 0x04, 0x2b, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x78, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0b, 0x00, 0x0a, 0x00, 0x89, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0b, 0x00, 0x0a, 0x03cb, 0x03c7, 0x03c3, 0x0438, 0x043d, 0x0441}, + { + 3, 2422, 0x00, 0x01, 0x03, 0x09, 0x76, 0x06, 0x06, 0x04, 0x2b, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x67, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0b, 0x00, 0x0a, 0x00, 0x89, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0b, 0x00, 0x0a, 0x03cd, 0x03c9, 0x03c5, 0x0436, 0x043a, 0x043f}, + { + 4, 2427, 0x00, 0x01, 0x03, 0x09, 0x7b, 0x06, 0x06, 0x04, 0x2b, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x57, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x78, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x03cf, 0x03cb, 0x03c7, 0x0434, 0x0438, 0x043d}, + { + 5, 2432, 0x00, 0x01, 0x03, 0x09, 0x80, 0x06, 0x06, 0x04, 0x2b, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x56, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x77, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x03d1, 0x03cd, 0x03c9, 0x0431, 0x0436, 0x043a}, + { + 6, 2437, 0x00, 0x01, 0x03, 0x09, 0x85, 0x06, 0x06, 0x04, 0x2b, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x46, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x76, 0x00, 0x03, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x03d3, 0x03cf, 0x03cb, 0x042f, 0x0434, 0x0438}, + { + 7, 2442, 0x00, 0x01, 0x03, 0x09, 0x8a, 0x06, 0x06, 0x04, 0x2b, 0x01, + 0x05, 0x05, 0x05, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x45, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x66, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x0a, 0x03d5, 0x03d1, 0x03cd, 0x042d, 0x0431, 0x0436}, + { + 8, 2447, 0x00, 0x01, 0x03, 0x09, 0x8f, 0x06, 0x06, 0x04, 0x2b, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x34, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x55, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x09, 0x03d7, 0x03d3, 0x03cf, 0x042b, 0x042f, 0x0434}, + { + 9, 2452, 0x00, 0x01, 0x03, 0x09, 0x94, 0x06, 0x06, 0x04, 0x2b, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x23, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x45, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x09, 0x03d9, 0x03d5, 0x03d1, 0x0429, 0x042d, 0x0431}, + { + 10, 2457, 0x00, 0x01, 0x03, 0x09, 0x99, 0x06, 0x06, 0x04, 0x2b, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x12, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x34, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x0a, 0x00, 0x09, 0x03db, 0x03d7, 0x03d3, 0x0427, 0x042b, 0x042f}, + { + 11, 2462, 0x00, 0x01, 0x03, 0x09, 0x9e, 0x06, 0x06, 0x04, 0x2b, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09, 0x00, 0x33, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x03dd, 0x03d9, 0x03d5, 0x0424, 0x0429, 0x042d}, + { + 12, 2467, 0x00, 0x01, 0x03, 0x09, 0xa3, 0x06, 0x06, 0x04, 0x2b, 0x01, + 0x06, 0x06, 0x06, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09, 0x00, 0x22, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x03df, 0x03db, 0x03d7, 0x0422, 0x0427, 0x042b}, + { + 13, 2472, 0x00, 0x01, 0x03, 0x09, 0xa8, 0x06, 0x06, 0x04, 0x2b, 0x01, + 0x07, 0x07, 0x07, 0x8f, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09, 0x00, 0x11, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x03e1, 0x03dd, 0x03d9, 0x0420, 0x0424, 0x0429}, + { + 14, 2484, 0xff, 0x01, 0x03, 0x09, 0xb4, 0x06, 0x06, 0x04, 0x2b, 0x01, + 0x07, 0x07, 0x07, 0x8f, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x70, 0x00, 0x09, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00, 0x70, 0x00, + 0x09, 0x00, 0x09, 0x03e6, 0x03e2, 0x03de, 0x041b, 0x041f, 0x0424} +}; + +static const struct chan_info_nphy_radio2057 chan_info_nphyrev7_2057_rev4[] = { + { + 184, 4920, 0x68, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xec, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x00, + 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x07b4, 0x07b0, 0x07ac, 0x0214, + 0x0215, + 0x0216, + }, + { + 186, 4930, 0x6b, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xed, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x00, + 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x07b8, 0x07b4, 0x07b0, 0x0213, + 0x0214, + 0x0215, + }, + { + 188, 4940, 0x6e, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xee, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x00, + 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x07bc, 0x07b8, 0x07b4, 0x0212, + 0x0213, + 0x0214, + }, + { + 190, 4950, 0x72, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xef, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x00, + 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x07c0, 0x07bc, 0x07b8, 0x0211, + 0x0212, + 0x0213, + }, + { + 192, 4960, 0x75, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf0, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x00, + 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x07c4, 0x07c0, 0x07bc, 0x020f, + 0x0211, + 0x0212, + }, + { + 194, 4970, 0x78, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf1, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x00, + 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x07c8, 0x07c4, 0x07c0, 0x020e, + 0x020f, + 0x0211, + }, + { + 196, 4980, 0x7c, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf2, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x00, + 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x07cc, 0x07c8, 0x07c4, 0x020d, + 0x020e, + 0x020f, + }, + { + 198, 4990, 0x7f, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf3, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x00, + 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x07d0, 0x07cc, 0x07c8, 0x020c, + 0x020d, + 0x020e, + }, + { + 200, 5000, 0x82, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf4, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x00, + 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x07d4, 0x07d0, 0x07cc, 0x020b, + 0x020c, + 0x020d, + }, + { + 202, 5010, 0x86, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf5, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x00, + 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x07d8, 0x07d4, 0x07d0, 0x020a, + 0x020b, + 0x020c, + }, + { + 204, 5020, 0x89, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf6, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x00, + 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x07dc, 0x07d8, 0x07d4, 0x0209, + 0x020a, + 0x020b, + }, + { + 206, 5030, 0x8c, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf7, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x00, + 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x07e0, 0x07dc, 0x07d8, 0x0208, + 0x0209, + 0x020a, + }, + { + 208, 5040, 0x90, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf8, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x00, + 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x07e4, 0x07e0, 0x07dc, 0x0207, + 0x0208, + 0x0209, + }, + { + 210, 5050, 0x93, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf9, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x00, + 0x00, 0x0f, 0x0f, 0xf3, 0x00, 0xef, 0x07e8, 0x07e4, 0x07e0, 0x0206, + 0x0207, + 0x0208, + }, + { + 212, 5060, 0x96, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xfa, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xe3, 0x00, 0xef, 0x00, + 0x00, 0x0f, 0x0f, 0xe3, 0x00, 0xef, 0x07ec, 0x07e8, 0x07e4, 0x0205, + 0x0206, + 0x0207, + }, + { + 214, 5070, 0x9a, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xfb, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0e, 0x0f, 0xe3, 0x00, 0xef, 0x00, + 0x00, 0x0e, 0x0f, 0xe3, 0x00, 0xef, 0x07f0, 0x07ec, 0x07e8, 0x0204, + 0x0205, + 0x0206, + }, + { + 216, 5080, 0x9d, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xfc, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0e, 0x0f, 0xe3, 0x00, 0xef, 0x00, + 0x00, 0x0e, 0x0f, 0xe3, 0x00, 0xef, 0x07f4, 0x07f0, 0x07ec, 0x0203, + 0x0204, + 0x0205, + }, + { + 218, 5090, 0xa0, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xfd, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0e, 0x0f, 0xe3, 0x00, 0xd6, 0x00, + 0x00, 0x0e, 0x0f, 0xe3, 0x00, 0xd6, 0x07f8, 0x07f4, 0x07f0, 0x0202, + 0x0203, + 0x0204, + }, + { + 220, 5100, 0xa4, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xfe, 0x01, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0e, 0x0f, 0xe3, 0x00, 0xd6, 0x00, + 0x00, 0x0e, 0x0f, 0xe3, 0x00, 0xd6, 0x07fc, 0x07f8, 0x07f4, 0x0201, + 0x0202, + 0x0203, + }, + { + 222, 5110, 0xa7, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xff, 0x01, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0e, 0x0f, 0xe3, 0x00, 0xd6, 0x00, + 0x00, 0x0e, 0x0f, 0xe3, 0x00, 0xd6, 0x0800, 0x07fc, 0x07f8, 0x0200, + 0x0201, + 0x0202, + }, + { + 224, 5120, 0xaa, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x00, 0x02, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0e, 0x0f, 0xe3, 0x00, 0xd6, 0x00, + 0x00, 0x0e, 0x0f, 0xe3, 0x00, 0xd6, 0x0804, 0x0800, 0x07fc, 0x01ff, + 0x0200, + 0x0201, + }, + { + 226, 5130, 0xae, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x01, 0x02, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0e, 0x0f, 0xe3, 0x00, 0xd6, 0x00, + 0x00, 0x0e, 0x0f, 0xe3, 0x00, 0xd6, 0x0808, 0x0804, 0x0800, 0x01fe, + 0x01ff, + 0x0200, + }, + { + 228, 5140, 0xb1, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x02, 0x02, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0e, 0x0e, 0xe3, 0x00, 0xd6, 0x00, + 0x00, 0x0e, 0x0e, 0xe3, 0x00, 0xd6, 0x080c, 0x0808, 0x0804, 0x01fd, + 0x01fe, + 0x01ff, + }, + { + 32, 5160, 0xb8, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x04, 0x02, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0d, 0x0e, 0xe3, 0x00, 0xd6, 0x00, + 0x00, 0x0d, 0x0e, 0xe3, 0x00, 0xd6, 0x0814, 0x0810, 0x080c, 0x01fb, + 0x01fc, + 0x01fd, + }, + { + 34, 5170, 0xbb, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x05, 0x02, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0d, 0x0e, 0xe3, 0x00, 0xd6, 0x00, + 0x00, 0x0d, 0x0e, 0xe3, 0x00, 0xd6, 0x0818, 0x0814, 0x0810, 0x01fa, + 0x01fb, + 0x01fc, + }, + { + 36, 5180, 0xbe, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x06, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0d, 0x0e, 0xd3, 0x00, 0xd6, 0x00, + 0x00, 0x0d, 0x0e, 0xd3, 0x00, 0xd6, 0x081c, 0x0818, 0x0814, 0x01f9, + 0x01fa, + 0x01fb, + }, + { + 38, 5190, 0xc2, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x07, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0d, 0x0e, 0xd3, 0x00, 0xd6, 0x00, + 0x00, 0x0d, 0x0e, 0xd3, 0x00, 0xd6, 0x0820, 0x081c, 0x0818, 0x01f8, + 0x01f9, + 0x01fa, + }, + { + 40, 5200, 0xc5, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x08, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0d, 0x0e, 0xd3, 0x00, 0xd6, 0x00, + 0x00, 0x0d, 0x0e, 0xd3, 0x00, 0xd6, 0x0824, 0x0820, 0x081c, 0x01f7, + 0x01f8, + 0x01f9, + }, + { + 42, 5210, 0xc8, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x09, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0d, 0x0e, 0xd3, 0x00, 0xd6, 0x00, + 0x00, 0x0d, 0x0e, 0xd3, 0x00, 0xd6, 0x0828, 0x0824, 0x0820, 0x01f6, + 0x01f7, + 0x01f8, + }, + { + 44, 5220, 0xcc, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0a, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0c, 0x0e, 0xd3, 0x00, 0xd6, 0x00, + 0x00, 0x0c, 0x0e, 0xd3, 0x00, 0xd6, 0x082c, 0x0828, 0x0824, 0x01f5, + 0x01f6, + 0x01f7, + }, + { + 46, 5230, 0xcf, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0b, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0c, 0x0e, 0xd3, 0x00, 0xd6, 0x00, + 0x00, 0x0c, 0x0e, 0xd3, 0x00, 0xd6, 0x0830, 0x082c, 0x0828, 0x01f4, + 0x01f5, + 0x01f6, + }, + { + 48, 5240, 0xd2, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0c, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0c, 0x0e, 0xd3, 0x00, 0xd6, 0x00, + 0x00, 0x0c, 0x0e, 0xd3, 0x00, 0xd6, 0x0834, 0x0830, 0x082c, 0x01f3, + 0x01f4, + 0x01f5, + }, + { + 50, 5250, 0xd6, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0d, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0c, 0x0e, 0xd3, 0x00, 0xd6, 0x00, + 0x00, 0x0c, 0x0e, 0xd3, 0x00, 0xd6, 0x0838, 0x0834, 0x0830, 0x01f2, + 0x01f3, + 0x01f4, + }, + { + 52, 5260, 0xd9, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0e, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0c, 0x0d, 0xd3, 0x00, 0xd6, 0x00, + 0x00, 0x0c, 0x0d, 0xd3, 0x00, 0xd6, 0x083c, 0x0838, 0x0834, 0x01f1, + 0x01f2, + 0x01f3, + }, + { + 54, 5270, 0xdc, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0f, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0c, 0x0d, 0xd3, 0x00, 0xd6, 0x00, + 0x00, 0x0c, 0x0d, 0xd3, 0x00, 0xd6, 0x0840, 0x083c, 0x0838, 0x01f0, + 0x01f1, + 0x01f2, + }, + { + 56, 5280, 0xe0, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x10, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0c, 0x0c, 0xc3, 0x00, 0xd4, 0x00, + 0x00, 0x0c, 0x0c, 0xc3, 0x00, 0xd4, 0x0844, 0x0840, 0x083c, 0x01f0, + 0x01f0, + 0x01f1, + }, + { + 58, 5290, 0xe3, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x11, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0c, 0x0c, 0xc3, 0x00, 0xd4, 0x00, + 0x00, 0x0c, 0x0c, 0xc3, 0x00, 0xd4, 0x0848, 0x0844, 0x0840, 0x01ef, + 0x01f0, + 0x01f0, + }, + { + 60, 5300, 0xe6, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x12, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0c, 0x0c, 0xc3, 0x00, 0xd4, 0x00, + 0x00, 0x0c, 0x0c, 0xc3, 0x00, 0xd4, 0x084c, 0x0848, 0x0844, 0x01ee, + 0x01ef, + 0x01f0, + }, + { + 62, 5310, 0xea, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x13, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0b, 0x0c, 0xc3, 0x00, 0xd4, 0x00, + 0x00, 0x0b, 0x0c, 0xc3, 0x00, 0xd4, 0x0850, 0x084c, 0x0848, 0x01ed, + 0x01ee, + 0x01ef, + }, + { + 64, 5320, 0xed, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x14, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0b, 0x0c, 0xc3, 0x00, 0xd4, 0x00, + 0x00, 0x0b, 0x0c, 0xc3, 0x00, 0xd4, 0x0854, 0x0850, 0x084c, 0x01ec, + 0x01ed, + 0x01ee, + }, + { + 66, 5330, 0xf0, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x15, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0b, 0x0c, 0xc3, 0x00, 0xd4, 0x00, + 0x00, 0x0b, 0x0c, 0xc3, 0x00, 0xd4, 0x0858, 0x0854, 0x0850, 0x01eb, + 0x01ec, + 0x01ed, + }, + { + 68, 5340, 0xf4, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x16, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0a, 0x0c, 0xc3, 0x00, 0xa1, 0x00, + 0x00, 0x0a, 0x0c, 0xc3, 0x00, 0xa1, 0x085c, 0x0858, 0x0854, 0x01ea, + 0x01eb, + 0x01ec, + }, + { + 70, 5350, 0xf7, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x17, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0a, 0x0b, 0xb3, 0x00, 0xa1, 0x00, + 0x00, 0x0a, 0x0b, 0xb3, 0x00, 0xa1, 0x0860, 0x085c, 0x0858, 0x01e9, + 0x01ea, + 0x01eb, + }, + { + 72, 5360, 0xfa, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x18, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0a, 0x0b, 0xb3, 0x00, 0xa1, 0x00, + 0x00, 0x0a, 0x0b, 0xb3, 0x00, 0xa1, 0x0864, 0x0860, 0x085c, 0x01e8, + 0x01e9, + 0x01ea, + }, + { + 74, 5370, 0xfe, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x19, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0a, 0x0b, 0xb3, 0x00, 0xa1, 0x00, + 0x00, 0x0a, 0x0b, 0xb3, 0x00, 0xa1, 0x0868, 0x0864, 0x0860, 0x01e7, + 0x01e8, + 0x01e9, + }, + { + 76, 5380, 0x01, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1a, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0a, 0x0b, 0xb3, 0x00, 0xa1, 0x00, + 0x00, 0x0a, 0x0b, 0xb3, 0x00, 0xa1, 0x086c, 0x0868, 0x0864, 0x01e6, + 0x01e7, + 0x01e8, + }, + { + 78, 5390, 0x04, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1b, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0a, 0x0a, 0xa3, 0x00, 0xa1, 0x00, + 0x00, 0x0a, 0x0a, 0xa3, 0x00, 0xa1, 0x0870, 0x086c, 0x0868, 0x01e5, + 0x01e6, + 0x01e7, + }, + { + 80, 5400, 0x08, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1c, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x09, 0x0a, 0xa3, 0x00, 0x90, 0x00, + 0x00, 0x09, 0x0a, 0xa3, 0x00, 0x90, 0x0874, 0x0870, 0x086c, 0x01e5, + 0x01e5, + 0x01e6, + }, + { + 82, 5410, 0x0b, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1d, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x09, 0x0a, 0xa3, 0x00, 0x90, 0x00, + 0x00, 0x09, 0x0a, 0xa3, 0x00, 0x90, 0x0878, 0x0874, 0x0870, 0x01e4, + 0x01e5, + 0x01e5, + }, + { + 84, 5420, 0x0e, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1e, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x09, 0x09, 0xa3, 0x00, 0x90, 0x00, + 0x00, 0x09, 0x09, 0xa3, 0x00, 0x90, 0x087c, 0x0878, 0x0874, 0x01e3, + 0x01e4, + 0x01e5, + }, + { + 86, 5430, 0x12, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1f, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x09, 0x09, 0x93, 0x00, 0x90, 0x00, + 0x00, 0x09, 0x09, 0x93, 0x00, 0x90, 0x0880, 0x087c, 0x0878, 0x01e2, + 0x01e3, + 0x01e4, + }, + { + 88, 5440, 0x15, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x20, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x09, 0x09, 0x93, 0x00, 0x90, 0x00, + 0x00, 0x09, 0x09, 0x93, 0x00, 0x90, 0x0884, 0x0880, 0x087c, 0x01e1, + 0x01e2, + 0x01e3, + }, + { + 90, 5450, 0x18, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x21, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x09, 0x09, 0x93, 0x00, 0x90, 0x00, + 0x00, 0x09, 0x09, 0x93, 0x00, 0x90, 0x0888, 0x0884, 0x0880, 0x01e0, + 0x01e1, + 0x01e2, + }, + { + 92, 5460, 0x1c, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x22, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x08, 0x08, 0x93, 0x00, 0x90, 0x00, + 0x00, 0x08, 0x08, 0x93, 0x00, 0x90, 0x088c, 0x0888, 0x0884, 0x01df, + 0x01e0, + 0x01e1, + }, + { + 94, 5470, 0x1f, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x23, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x08, 0x08, 0x93, 0x00, 0x60, 0x00, + 0x00, 0x08, 0x08, 0x93, 0x00, 0x60, 0x0890, 0x088c, 0x0888, 0x01de, + 0x01df, + 0x01e0, + }, + { + 96, 5480, 0x22, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x24, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x08, 0x07, 0x93, 0x00, 0x60, 0x00, + 0x00, 0x08, 0x07, 0x93, 0x00, 0x60, 0x0894, 0x0890, 0x088c, 0x01dd, + 0x01de, + 0x01df, + }, + { + 98, 5490, 0x26, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x25, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x08, 0x07, 0x93, 0x00, 0x60, 0x00, + 0x00, 0x08, 0x07, 0x93, 0x00, 0x60, 0x0898, 0x0894, 0x0890, 0x01dd, + 0x01dd, + 0x01de, + }, + { + 100, 5500, 0x29, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x26, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x08, 0x07, 0x93, 0x00, 0x60, 0x00, + 0x00, 0x08, 0x07, 0x93, 0x00, 0x60, 0x089c, 0x0898, 0x0894, 0x01dc, + 0x01dd, + 0x01dd, + }, + { + 102, 5510, 0x2c, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x27, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x08, 0x07, 0x93, 0x00, 0x60, 0x00, + 0x00, 0x08, 0x07, 0x93, 0x00, 0x60, 0x08a0, 0x089c, 0x0898, 0x01db, + 0x01dc, + 0x01dd, + }, + { + 104, 5520, 0x30, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x28, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x08, 0x06, 0x93, 0x00, 0x60, 0x00, + 0x00, 0x08, 0x06, 0x93, 0x00, 0x60, 0x08a4, 0x08a0, 0x089c, 0x01da, + 0x01db, + 0x01dc, + }, + { + 106, 5530, 0x33, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x29, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x08, 0x06, 0x93, 0x00, 0x60, 0x00, + 0x00, 0x08, 0x06, 0x93, 0x00, 0x60, 0x08a8, 0x08a4, 0x08a0, 0x01d9, + 0x01da, + 0x01db, + }, + { + 108, 5540, 0x36, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2a, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x08, 0x06, 0x93, 0x00, 0x60, 0x00, + 0x00, 0x08, 0x06, 0x93, 0x00, 0x60, 0x08ac, 0x08a8, 0x08a4, 0x01d8, + 0x01d9, + 0x01da, + }, + { + 110, 5550, 0x3a, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2b, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x08, 0x05, 0x83, 0x00, 0x60, 0x00, + 0x00, 0x08, 0x05, 0x83, 0x00, 0x60, 0x08b0, 0x08ac, 0x08a8, 0x01d7, + 0x01d8, + 0x01d9, + }, + { + 112, 5560, 0x3d, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2c, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x08, 0x05, 0x83, 0x00, 0x60, 0x00, + 0x00, 0x08, 0x05, 0x83, 0x00, 0x60, 0x08b4, 0x08b0, 0x08ac, 0x01d7, + 0x01d7, + 0x01d8, + }, + { + 114, 5570, 0x40, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2d, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x08, 0x05, 0x83, 0x00, 0x60, 0x00, + 0x00, 0x08, 0x05, 0x83, 0x00, 0x60, 0x08b8, 0x08b4, 0x08b0, 0x01d6, + 0x01d7, + 0x01d7, + }, + { + 116, 5580, 0x44, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2e, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x07, 0x05, 0x83, 0x00, 0x60, 0x00, + 0x00, 0x07, 0x05, 0x83, 0x00, 0x60, 0x08bc, 0x08b8, 0x08b4, 0x01d5, + 0x01d6, + 0x01d7, + }, + { + 118, 5590, 0x47, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2f, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x07, 0x04, 0x83, 0x00, 0x60, 0x00, + 0x00, 0x07, 0x04, 0x83, 0x00, 0x60, 0x08c0, 0x08bc, 0x08b8, 0x01d4, + 0x01d5, + 0x01d6, + }, + { + 120, 5600, 0x4a, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x30, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x07, 0x04, 0x73, 0x00, 0x30, 0x00, + 0x00, 0x07, 0x04, 0x73, 0x00, 0x30, 0x08c4, 0x08c0, 0x08bc, 0x01d3, + 0x01d4, + 0x01d5, + }, + { + 122, 5610, 0x4e, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x31, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x06, 0x04, 0x73, 0x00, 0x30, 0x00, + 0x00, 0x06, 0x04, 0x73, 0x00, 0x30, 0x08c8, 0x08c4, 0x08c0, 0x01d2, + 0x01d3, + 0x01d4, + }, + { + 124, 5620, 0x51, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x32, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x06, 0x04, 0x73, 0x00, 0x30, 0x00, + 0x00, 0x06, 0x04, 0x73, 0x00, 0x30, 0x08cc, 0x08c8, 0x08c4, 0x01d2, + 0x01d2, + 0x01d3, + }, + { + 126, 5630, 0x54, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x33, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x06, 0x04, 0x73, 0x00, 0x30, 0x00, + 0x00, 0x06, 0x04, 0x73, 0x00, 0x30, 0x08d0, 0x08cc, 0x08c8, 0x01d1, + 0x01d2, + 0x01d2, + }, + { + 128, 5640, 0x58, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x34, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x06, 0x04, 0x73, 0x00, 0x30, 0x00, + 0x00, 0x06, 0x04, 0x73, 0x00, 0x30, 0x08d4, 0x08d0, 0x08cc, 0x01d0, + 0x01d1, + 0x01d2, + }, + { + 130, 5650, 0x5b, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x35, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x06, 0x03, 0x63, 0x00, 0x30, 0x00, + 0x00, 0x06, 0x03, 0x63, 0x00, 0x30, 0x08d8, 0x08d4, 0x08d0, 0x01cf, + 0x01d0, + 0x01d1, + }, + { + 132, 5660, 0x5e, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x36, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x06, 0x03, 0x63, 0x00, 0x30, 0x00, + 0x00, 0x06, 0x03, 0x63, 0x00, 0x30, 0x08dc, 0x08d8, 0x08d4, 0x01ce, + 0x01cf, + 0x01d0, + }, + { + 134, 5670, 0x62, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x37, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x05, 0x03, 0x63, 0x00, 0x00, 0x00, + 0x00, 0x05, 0x03, 0x63, 0x00, 0x00, 0x08e0, 0x08dc, 0x08d8, 0x01ce, + 0x01ce, + 0x01cf, + }, + { + 136, 5680, 0x65, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x38, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x05, 0x02, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x05, 0x02, 0x53, 0x00, 0x00, 0x08e4, 0x08e0, 0x08dc, 0x01cd, + 0x01ce, + 0x01ce, + }, + { + 138, 5690, 0x68, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x39, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x05, 0x02, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x05, 0x02, 0x53, 0x00, 0x00, 0x08e8, 0x08e4, 0x08e0, 0x01cc, + 0x01cd, + 0x01ce, + }, + { + 140, 5700, 0x6c, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3a, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x05, 0x02, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x05, 0x02, 0x53, 0x00, 0x00, 0x08ec, 0x08e8, 0x08e4, 0x01cb, + 0x01cc, + 0x01cd, + }, + { + 142, 5710, 0x6f, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3b, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x05, 0x02, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x05, 0x02, 0x53, 0x00, 0x00, 0x08f0, 0x08ec, 0x08e8, 0x01ca, + 0x01cb, + 0x01cc, + }, + { + 144, 5720, 0x72, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3c, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x05, 0x02, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x05, 0x02, 0x53, 0x00, 0x00, 0x08f4, 0x08f0, 0x08ec, 0x01c9, + 0x01ca, + 0x01cb, + }, + { + 145, 5725, 0x74, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x79, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x05, 0x01, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x05, 0x01, 0x53, 0x00, 0x00, 0x08f6, 0x08f2, 0x08ee, 0x01c9, + 0x01ca, + 0x01cb, + }, + { + 146, 5730, 0x76, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3d, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x04, 0x01, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x01, 0x53, 0x00, 0x00, 0x08f8, 0x08f4, 0x08f0, 0x01c9, + 0x01c9, + 0x01ca, + }, + { + 147, 5735, 0x77, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x7b, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x04, 0x01, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x01, 0x53, 0x00, 0x00, 0x08fa, 0x08f6, 0x08f2, 0x01c8, + 0x01c9, + 0x01ca, + }, + { + 148, 5740, 0x79, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3e, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x04, 0x01, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x01, 0x53, 0x00, 0x00, 0x08fc, 0x08f8, 0x08f4, 0x01c8, + 0x01c9, + 0x01c9, + }, + { + 149, 5745, 0x7b, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x7d, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x04, 0x01, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x01, 0x53, 0x00, 0x00, 0x08fe, 0x08fa, 0x08f6, 0x01c8, + 0x01c8, + 0x01c9, + }, + { + 150, 5750, 0x7c, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3f, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x04, 0x01, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x01, 0x53, 0x00, 0x00, 0x0900, 0x08fc, 0x08f8, 0x01c7, + 0x01c8, + 0x01c9, + }, + { + 151, 5755, 0x7e, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x7f, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x04, 0x01, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x01, 0x53, 0x00, 0x00, 0x0902, 0x08fe, 0x08fa, 0x01c7, + 0x01c8, + 0x01c8, + }, + { + 152, 5760, 0x80, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x40, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x04, 0x01, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x01, 0x43, 0x00, 0x00, 0x0904, 0x0900, 0x08fc, 0x01c6, + 0x01c7, + 0x01c8, + }, + { + 153, 5765, 0x81, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x81, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x04, 0x01, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x01, 0x43, 0x00, 0x00, 0x0906, 0x0902, 0x08fe, 0x01c6, + 0x01c7, + 0x01c8, + }, + { + 154, 5770, 0x83, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x41, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x04, 0x01, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x01, 0x43, 0x00, 0x00, 0x0908, 0x0904, 0x0900, 0x01c6, + 0x01c6, + 0x01c7, + }, + { + 155, 5775, 0x85, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x83, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x04, 0x01, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x01, 0x43, 0x00, 0x00, 0x090a, 0x0906, 0x0902, 0x01c5, + 0x01c6, + 0x01c7, + }, + { + 156, 5780, 0x86, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x42, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x03, 0x01, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x01, 0x43, 0x00, 0x00, 0x090c, 0x0908, 0x0904, 0x01c5, + 0x01c6, + 0x01c6, + }, + { + 157, 5785, 0x88, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x85, 0x04, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x090e, 0x090a, 0x0906, 0x01c4, + 0x01c5, + 0x01c6, + }, + { + 158, 5790, 0x8a, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x43, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x0910, 0x090c, 0x0908, 0x01c4, + 0x01c5, + 0x01c6, + }, + { + 159, 5795, 0x8b, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x87, 0x04, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x0912, 0x090e, 0x090a, 0x01c4, + 0x01c4, + 0x01c5, + }, + { + 160, 5800, 0x8d, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x44, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x0914, 0x0910, 0x090c, 0x01c3, + 0x01c4, + 0x01c5, + }, + { + 161, 5805, 0x8f, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x89, 0x04, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x0916, 0x0912, 0x090e, 0x01c3, + 0x01c4, + 0x01c4, + }, + { + 162, 5810, 0x90, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x45, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x0918, 0x0914, 0x0910, 0x01c2, + 0x01c3, + 0x01c4, + }, + { + 163, 5815, 0x92, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x8b, 0x04, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x091a, 0x0916, 0x0912, 0x01c2, + 0x01c3, + 0x01c4, + }, + { + 164, 5820, 0x94, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x46, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x091c, 0x0918, 0x0914, 0x01c2, + 0x01c2, + 0x01c3, + }, + { + 165, 5825, 0x95, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x8d, 0x04, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x091e, 0x091a, 0x0916, 0x01c1, + 0x01c2, + 0x01c3, + }, + { + 166, 5830, 0x97, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x47, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x0920, 0x091c, 0x0918, 0x01c1, + 0x01c2, + 0x01c2, + }, + { + 168, 5840, 0x9a, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x48, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x0924, 0x0920, 0x091c, 0x01c0, + 0x01c1, + 0x01c2, + }, + { + 170, 5850, 0x9e, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x49, 0x02, 0x04, + 0x00, 0x04, 0x00, 0x44, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x0928, 0x0924, 0x0920, 0x01bf, + 0x01c0, + 0x01c1, + }, + { + 172, 5860, 0xa1, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x4a, 0x02, 0x04, + 0x00, 0x04, 0x00, 0x44, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x092c, 0x0928, 0x0924, 0x01bf, + 0x01bf, + 0x01c0, + }, + { + 174, 5870, 0xa4, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x4b, 0x02, 0x04, + 0x00, 0x04, 0x00, 0x44, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x0930, 0x092c, 0x0928, 0x01be, + 0x01bf, + 0x01bf, + }, + { + 176, 5880, 0xa8, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x4c, 0x02, 0x03, + 0x00, 0x03, 0x00, 0x33, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x0934, 0x0930, 0x092c, 0x01bd, + 0x01be, + 0x01bf, + }, + { + 178, 5890, 0xab, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x4d, 0x02, 0x03, + 0x00, 0x03, 0x00, 0x33, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x0938, 0x0934, 0x0930, 0x01bc, + 0x01bd, + 0x01be, + }, + { + 180, 5900, 0xae, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x4e, 0x02, 0x03, + 0x00, 0x03, 0x00, 0x33, 0x00, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x093c, 0x0938, 0x0934, 0x01bc, + 0x01bc, + 0x01bd, + }, + { + 1, 2412, 0x48, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x6c, 0x09, 0x0f, + 0x0a, 0x00, 0x0a, 0x00, 0x71, 0xa3, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x71, + 0xa3, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03c9, 0x03c5, 0x03c1, 0x043a, + 0x043f, + 0x0443, + }, + { + 2, 2417, 0x4b, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x71, 0x09, 0x0f, + 0x0a, 0x00, 0x0a, 0x00, 0x71, 0xa3, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x71, + 0xa3, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03cb, 0x03c7, 0x03c3, 0x0438, + 0x043d, + 0x0441, + }, + { + 3, 2422, 0x4e, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x76, 0x09, 0x0f, + 0x09, 0x00, 0x09, 0x00, 0x71, 0x93, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x71, + 0x93, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03cd, 0x03c9, 0x03c5, 0x0436, + 0x043a, + 0x043f, + }, + { + 4, 2427, 0x52, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x7b, 0x09, 0x0f, + 0x09, 0x00, 0x09, 0x00, 0x71, 0x93, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x71, + 0x93, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03cf, 0x03cb, 0x03c7, 0x0434, + 0x0438, + 0x043d, + }, + { + 5, 2432, 0x55, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x80, 0x09, 0x0f, + 0x08, 0x00, 0x08, 0x00, 0x51, 0x83, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x51, + 0x83, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03d1, 0x03cd, 0x03c9, 0x0431, + 0x0436, + 0x043a, + }, + { + 6, 2437, 0x58, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x85, 0x09, 0x0f, + 0x08, 0x00, 0x08, 0x00, 0x51, 0x83, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x51, + 0x83, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03d3, 0x03cf, 0x03cb, 0x042f, + 0x0434, + 0x0438, + }, + { + 7, 2442, 0x5c, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x8a, 0x09, 0x0f, + 0x07, 0x00, 0x07, 0x00, 0x51, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x51, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03d5, 0x03d1, 0x03cd, 0x042d, + 0x0431, + 0x0436, + }, + { + 8, 2447, 0x5f, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x8f, 0x09, 0x0f, + 0x07, 0x00, 0x07, 0x00, 0x31, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x31, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03d7, 0x03d3, 0x03cf, 0x042b, + 0x042f, + 0x0434, + }, + { + 9, 2452, 0x62, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x94, 0x09, 0x0f, + 0x07, 0x00, 0x07, 0x00, 0x31, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x31, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03d9, 0x03d5, 0x03d1, 0x0429, + 0x042d, + 0x0431, + }, + { + 10, 2457, 0x66, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x99, 0x09, 0x0f, + 0x06, 0x00, 0x06, 0x00, 0x31, 0x63, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x31, + 0x63, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03db, 0x03d7, 0x03d3, 0x0427, + 0x042b, + 0x042f, + }, + { + 11, 2462, 0x69, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x9e, 0x09, 0x0f, + 0x06, 0x00, 0x06, 0x00, 0x31, 0x63, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x31, + 0x63, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03dd, 0x03d9, 0x03d5, 0x0424, + 0x0429, + 0x042d, + }, + { + 12, 2467, 0x6c, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xa3, 0x09, 0x0f, + 0x05, 0x00, 0x05, 0x00, 0x11, 0x53, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x11, + 0x53, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03df, 0x03db, 0x03d7, 0x0422, + 0x0427, + 0x042b, + }, + { + 13, 2472, 0x70, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xa8, 0x09, 0x0f, + 0x05, 0x00, 0x05, 0x00, 0x11, 0x53, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x11, + 0x53, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03e1, 0x03dd, 0x03d9, 0x0420, + 0x0424, + 0x0429, + }, + { + 14, 2484, 0x78, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xb4, 0x09, 0x0f, + 0x04, 0x00, 0x04, 0x00, 0x11, 0x43, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x11, + 0x43, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x03e6, 0x03e2, 0x03de, 0x041b, + 0x041f, + 0x0424} +}; + +static const struct chan_info_nphy_radio2057_rev5 +chan_info_nphyrev8_2057_rev5[] = { + { + 1, 2412, 0x48, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x6c, 0x09, 0x0d, + 0x08, 0x0e, 0x61, 0x03, 0xff, 0x61, 0x03, 0xff, 0x03c9, 0x03c5, 0x03c1, + 0x043a, 0x043f, 0x0443}, + { + 2, 2417, 0x4b, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x71, 0x09, 0x0d, + 0x08, 0x0e, 0x61, 0x03, 0xff, 0x61, 0x03, 0xff, 0x03cb, 0x03c7, 0x03c3, + 0x0438, 0x043d, 0x0441}, + { + 3, 2422, 0x4e, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x76, 0x09, 0x0d, + 0x08, 0x0e, 0x61, 0x03, 0xef, 0x61, 0x03, 0xef, 0x03cd, 0x03c9, 0x03c5, + 0x0436, 0x043a, 0x043f}, + { + 4, 2427, 0x52, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x7b, 0x09, 0x0c, + 0x08, 0x0e, 0x61, 0x03, 0xdf, 0x61, 0x03, 0xdf, 0x03cf, 0x03cb, 0x03c7, + 0x0434, 0x0438, 0x043d}, + { + 5, 2432, 0x55, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x80, 0x09, 0x0c, + 0x07, 0x0d, 0x61, 0x03, 0xcf, 0x61, 0x03, 0xcf, 0x03d1, 0x03cd, 0x03c9, + 0x0431, 0x0436, 0x043a}, + { + 6, 2437, 0x58, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x85, 0x09, 0x0c, + 0x07, 0x0d, 0x61, 0x03, 0xbf, 0x61, 0x03, 0xbf, 0x03d3, 0x03cf, 0x03cb, + 0x042f, 0x0434, 0x0438}, + { + 7, 2442, 0x5c, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x8a, 0x09, 0x0b, + 0x07, 0x0d, 0x61, 0x03, 0xaf, 0x61, 0x03, 0xaf, 0x03d5, 0x03d1, 0x03cd, + 0x042d, 0x0431, 0x0436}, + { + 8, 2447, 0x5f, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x8f, 0x09, 0x0b, + 0x07, 0x0d, 0x61, 0x03, 0x9f, 0x61, 0x03, 0x9f, 0x03d7, 0x03d3, 0x03cf, + 0x042b, 0x042f, 0x0434}, + { + 9, 2452, 0x62, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x94, 0x09, 0x0b, + 0x07, 0x0d, 0x61, 0x03, 0x8f, 0x61, 0x03, 0x8f, 0x03d9, 0x03d5, 0x03d1, + 0x0429, 0x042d, 0x0431}, + { + 10, 2457, 0x66, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x99, 0x09, 0x0b, + 0x07, 0x0c, 0x61, 0x03, 0x7f, 0x61, 0x03, 0x7f, 0x03db, 0x03d7, 0x03d3, + 0x0427, 0x042b, 0x042f}, + { + 11, 2462, 0x69, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x9e, 0x09, 0x0b, + 0x07, 0x0c, 0x61, 0x03, 0x6f, 0x61, 0x03, 0x6f, 0x03dd, 0x03d9, 0x03d5, + 0x0424, 0x0429, 0x042d}, + { + 12, 2467, 0x6c, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xa3, 0x09, 0x0b, + 0x06, 0x0c, 0x61, 0x03, 0x5f, 0x61, 0x03, 0x5f, 0x03df, 0x03db, 0x03d7, + 0x0422, 0x0427, 0x042b}, + { + 13, 2472, 0x70, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xa8, 0x09, 0x0a, + 0x06, 0x0b, 0x61, 0x03, 0x4f, 0x61, 0x03, 0x4f, 0x03e1, 0x03dd, 0x03d9, + 0x0420, 0x0424, 0x0429}, + { + 14, 2484, 0x78, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xb4, 0x09, 0x0a, + 0x06, 0x0b, 0x61, 0x03, 0x3f, 0x61, 0x03, 0x3f, 0x03e6, 0x03e2, 0x03de, + 0x041b, 0x041f, 0x0424} +}; + +static const struct chan_info_nphy_radio2057_rev5 +chan_info_nphyrev9_2057_rev5v1[] = { + { + 1, 2412, 0x48, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x6c, 0x09, 0x0d, + 0x08, 0x0e, 0x61, 0x03, 0xff, 0x61, 0x03, 0xff, 0x03c9, 0x03c5, 0x03c1, + 0x043a, 0x043f, 0x0443}, + { + 2, 2417, 0x4b, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x71, 0x09, 0x0d, + 0x08, 0x0e, 0x61, 0x03, 0xff, 0x61, 0x03, 0xff, 0x03cb, 0x03c7, 0x03c3, + 0x0438, 0x043d, 0x0441}, + { + 3, 2422, 0x4e, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x76, 0x09, 0x0d, + 0x08, 0x0e, 0x61, 0x03, 0xef, 0x61, 0x03, 0xef, 0x03cd, 0x03c9, 0x03c5, + 0x0436, 0x043a, 0x043f}, + { + 4, 2427, 0x52, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x7b, 0x09, 0x0c, + 0x08, 0x0e, 0x61, 0x03, 0xdf, 0x61, 0x03, 0xdf, 0x03cf, 0x03cb, 0x03c7, + 0x0434, 0x0438, 0x043d}, + { + 5, 2432, 0x55, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x80, 0x09, 0x0c, + 0x07, 0x0d, 0x61, 0x03, 0xcf, 0x61, 0x03, 0xcf, 0x03d1, 0x03cd, 0x03c9, + 0x0431, 0x0436, 0x043a}, + { + 6, 2437, 0x58, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x85, 0x09, 0x0c, + 0x07, 0x0d, 0x61, 0x03, 0xbf, 0x61, 0x03, 0xbf, 0x03d3, 0x03cf, 0x03cb, + 0x042f, 0x0434, 0x0438}, + { + 7, 2442, 0x5c, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x8a, 0x09, 0x0b, + 0x07, 0x0d, 0x61, 0x03, 0xaf, 0x61, 0x03, 0xaf, 0x03d5, 0x03d1, 0x03cd, + 0x042d, 0x0431, 0x0436}, + { + 8, 2447, 0x5f, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x8f, 0x09, 0x0b, + 0x07, 0x0d, 0x61, 0x03, 0x9f, 0x61, 0x03, 0x9f, 0x03d7, 0x03d3, 0x03cf, + 0x042b, 0x042f, 0x0434}, + { + 9, 2452, 0x62, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x94, 0x09, 0x0b, + 0x07, 0x0d, 0x61, 0x03, 0x8f, 0x61, 0x03, 0x8f, 0x03d9, 0x03d5, 0x03d1, + 0x0429, 0x042d, 0x0431}, + { + 10, 2457, 0x66, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x99, 0x09, 0x0b, + 0x07, 0x0c, 0x61, 0x03, 0x7f, 0x61, 0x03, 0x7f, 0x03db, 0x03d7, 0x03d3, + 0x0427, 0x042b, 0x042f}, + { + 11, 2462, 0x69, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x9e, 0x09, 0x0b, + 0x07, 0x0c, 0x61, 0x03, 0x6f, 0x61, 0x03, 0x6f, 0x03dd, 0x03d9, 0x03d5, + 0x0424, 0x0429, 0x042d}, + { + 12, 2467, 0x6c, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xa3, 0x09, 0x0b, + 0x06, 0x0c, 0x61, 0x03, 0x5f, 0x61, 0x03, 0x5f, 0x03df, 0x03db, 0x03d7, + 0x0422, 0x0427, 0x042b}, + { + 13, 2472, 0x70, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xa8, 0x09, 0x0a, + 0x06, 0x0b, 0x61, 0x03, 0x4f, 0x61, 0x03, 0x4f, 0x03e1, 0x03dd, 0x03d9, + 0x0420, 0x0424, 0x0429}, + { + 14, 2484, 0x78, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xb4, 0x09, 0x0a, + 0x06, 0x0b, 0x61, 0x03, 0x3f, 0x61, 0x03, 0x3f, 0x03e6, 0x03e2, 0x03de, + 0x041b, 0x041f, 0x0424} +}; + +static const struct chan_info_nphy_radio2057 chan_info_nphyrev8_2057_rev7[] = { + { + 184, 4920, 0x68, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xec, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xd3, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x07b4, 0x07b0, 0x07ac, 0x0214, + 0x0215, + 0x0216}, + { + 186, 4930, 0x6b, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xed, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xd3, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x07b8, 0x07b4, 0x07b0, 0x0213, + 0x0214, + 0x0215}, + { + 188, 4940, 0x6e, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xee, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xd3, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x07bc, 0x07b8, 0x07b4, 0x0212, + 0x0213, + 0x0214}, + { + 190, 4950, 0x72, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xef, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x07c0, 0x07bc, 0x07b8, 0x0211, + 0x0212, + 0x0213}, + { + 192, 4960, 0x75, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf0, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x07c4, 0x07c0, 0x07bc, 0x020f, + 0x0211, + 0x0212}, + { + 194, 4970, 0x78, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf1, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x07c8, 0x07c4, 0x07c0, 0x020e, + 0x020f, + 0x0211}, + { + 196, 4980, 0x7c, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf2, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x07cc, 0x07c8, 0x07c4, 0x020d, + 0x020e, + 0x020f}, + { + 198, 4990, 0x7f, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf3, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x07d0, 0x07cc, 0x07c8, 0x020c, + 0x020d, + 0x020e}, + { + 200, 5000, 0x82, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf4, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07d4, 0x07d0, 0x07cc, 0x020b, + 0x020c, + 0x020d}, + { + 202, 5010, 0x86, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf5, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07d8, 0x07d4, 0x07d0, 0x020a, + 0x020b, + 0x020c}, + { + 204, 5020, 0x89, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf6, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07dc, 0x07d8, 0x07d4, 0x0209, + 0x020a, + 0x020b}, + { + 206, 5030, 0x8c, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf7, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07e0, 0x07dc, 0x07d8, 0x0208, + 0x0209, + 0x020a}, + { + 208, 5040, 0x90, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf8, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07e4, 0x07e0, 0x07dc, 0x0207, + 0x0208, + 0x0209}, + { + 210, 5050, 0x93, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf9, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07e8, 0x07e4, 0x07e0, 0x0206, + 0x0207, + 0x0208}, + { + 212, 5060, 0x96, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xfa, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07ec, 0x07e8, 0x07e4, 0x0205, + 0x0206, + 0x0207}, + { + 214, 5070, 0x9a, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xfb, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07f0, 0x07ec, 0x07e8, 0x0204, + 0x0205, + 0x0206}, + { + 216, 5080, 0x9d, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xfc, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07f4, 0x07f0, 0x07ec, 0x0203, + 0x0204, + 0x0205}, + { + 218, 5090, 0xa0, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xfd, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07f8, 0x07f4, 0x07f0, 0x0202, + 0x0203, + 0x0204}, + { + 220, 5100, 0xa4, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xfe, 0x01, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x07fc, 0x07f8, 0x07f4, 0x0201, + 0x0202, + 0x0203}, + { + 222, 5110, 0xa7, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xff, 0x01, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x0800, 0x07fc, 0x07f8, 0x0200, + 0x0201, + 0x0202}, + { + 224, 5120, 0xaa, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x00, 0x02, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x0804, 0x0800, 0x07fc, 0x01ff, + 0x0200, + 0x0201}, + { + 226, 5130, 0xae, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x01, 0x02, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x0808, 0x0804, 0x0800, 0x01fe, + 0x01ff, + 0x0200}, + { + 228, 5140, 0xb1, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x02, 0x02, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x080c, 0x0808, 0x0804, 0x01fd, + 0x01fe, + 0x01ff}, + { + 32, 5160, 0xb8, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x04, 0x02, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x0814, 0x0810, 0x080c, 0x01fb, + 0x01fc, + 0x01fd}, + { + 34, 5170, 0xbb, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x05, 0x02, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x0818, 0x0814, 0x0810, 0x01fa, + 0x01fb, + 0x01fc}, + { + 36, 5180, 0xbe, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x06, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x081c, 0x0818, 0x0814, 0x01f9, + 0x01fa, + 0x01fb}, + { + 38, 5190, 0xc2, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x07, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x0820, 0x081c, 0x0818, 0x01f8, + 0x01f9, + 0x01fa}, + { + 40, 5200, 0xc5, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x08, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x0824, 0x0820, 0x081c, 0x01f7, + 0x01f8, + 0x01f9}, + { + 42, 5210, 0xc8, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x09, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x0828, 0x0824, 0x0820, 0x01f6, + 0x01f7, + 0x01f8}, + { + 44, 5220, 0xcc, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0a, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x082c, 0x0828, 0x0824, 0x01f5, + 0x01f6, + 0x01f7}, + { + 46, 5230, 0xcf, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0b, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x0830, 0x082c, 0x0828, 0x01f4, + 0x01f5, + 0x01f6}, + { + 48, 5240, 0xd2, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0c, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x0834, 0x0830, 0x082c, 0x01f3, + 0x01f4, + 0x01f5}, + { + 50, 5250, 0xd6, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0d, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x0838, 0x0834, 0x0830, 0x01f2, + 0x01f3, + 0x01f4}, + { + 52, 5260, 0xd9, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0e, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x083c, 0x0838, 0x0834, 0x01f1, + 0x01f2, + 0x01f3}, + { + 54, 5270, 0xdc, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0f, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x0840, 0x083c, 0x0838, 0x01f0, + 0x01f1, + 0x01f2}, + { + 56, 5280, 0xe0, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x10, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x0844, 0x0840, 0x083c, 0x01f0, + 0x01f0, + 0x01f1}, + { + 58, 5290, 0xe3, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x11, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x0848, 0x0844, 0x0840, 0x01ef, + 0x01f0, + 0x01f0}, + { + 60, 5300, 0xe6, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x12, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x084c, 0x0848, 0x0844, 0x01ee, + 0x01ef, + 0x01f0}, + { + 62, 5310, 0xea, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x13, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x0850, 0x084c, 0x0848, 0x01ed, + 0x01ee, + 0x01ef}, + { + 64, 5320, 0xed, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x14, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x0854, 0x0850, 0x084c, 0x01ec, + 0x01ed, + 0x01ee}, + { + 66, 5330, 0xf0, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x15, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x0858, 0x0854, 0x0850, 0x01eb, + 0x01ec, + 0x01ed}, + { + 68, 5340, 0xf4, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x16, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x085c, 0x0858, 0x0854, 0x01ea, + 0x01eb, + 0x01ec}, + { + 70, 5350, 0xf7, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x17, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x0860, 0x085c, 0x0858, 0x01e9, + 0x01ea, + 0x01eb}, + { + 72, 5360, 0xfa, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x18, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x0864, 0x0860, 0x085c, 0x01e8, + 0x01e9, + 0x01ea}, + { + 74, 5370, 0xfe, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x19, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x0868, 0x0864, 0x0860, 0x01e7, + 0x01e8, + 0x01e9}, + { + 76, 5380, 0x01, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1a, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x086c, 0x0868, 0x0864, 0x01e6, + 0x01e7, + 0x01e8}, + { + 78, 5390, 0x04, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1b, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x0870, 0x086c, 0x0868, 0x01e5, + 0x01e6, + 0x01e7}, + { + 80, 5400, 0x08, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1c, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x0874, 0x0870, 0x086c, 0x01e5, + 0x01e5, + 0x01e6}, + { + 82, 5410, 0x0b, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1d, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x0878, 0x0874, 0x0870, 0x01e4, + 0x01e5, + 0x01e5}, + { + 84, 5420, 0x0e, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1e, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x087c, 0x0878, 0x0874, 0x01e3, + 0x01e4, + 0x01e5}, + { + 86, 5430, 0x12, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1f, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x0880, 0x087c, 0x0878, 0x01e2, + 0x01e3, + 0x01e4}, + { + 88, 5440, 0x15, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x20, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x0884, 0x0880, 0x087c, 0x01e1, + 0x01e2, + 0x01e3}, + { + 90, 5450, 0x18, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x21, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x0888, 0x0884, 0x0880, 0x01e0, + 0x01e1, + 0x01e2}, + { + 92, 5460, 0x1c, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x22, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x088c, 0x0888, 0x0884, 0x01df, + 0x01e0, + 0x01e1}, + { + 94, 5470, 0x1f, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x23, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x0890, 0x088c, 0x0888, 0x01de, + 0x01df, + 0x01e0}, + { + 96, 5480, 0x22, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x24, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x0894, 0x0890, 0x088c, 0x01dd, + 0x01de, + 0x01df}, + { + 98, 5490, 0x26, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x25, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x0898, 0x0894, 0x0890, 0x01dd, + 0x01dd, + 0x01de}, + { + 100, 5500, 0x29, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x26, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x089c, 0x0898, 0x0894, 0x01dc, + 0x01dd, + 0x01dd}, + { + 102, 5510, 0x2c, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x27, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08a0, 0x089c, 0x0898, 0x01db, + 0x01dc, + 0x01dd}, + { + 104, 5520, 0x30, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x28, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08a4, 0x08a0, 0x089c, 0x01da, + 0x01db, + 0x01dc}, + { + 106, 5530, 0x33, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x29, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08a8, 0x08a4, 0x08a0, 0x01d9, + 0x01da, + 0x01db}, + { + 108, 5540, 0x36, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2a, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08ac, 0x08a8, 0x08a4, 0x01d8, + 0x01d9, + 0x01da}, + { + 110, 5550, 0x3a, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2b, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08b0, 0x08ac, 0x08a8, 0x01d7, + 0x01d8, + 0x01d9}, + { + 112, 5560, 0x3d, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2c, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08b4, 0x08b0, 0x08ac, 0x01d7, + 0x01d7, + 0x01d8}, + { + 114, 5570, 0x40, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2d, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08b8, 0x08b4, 0x08b0, 0x01d6, + 0x01d7, + 0x01d7}, + { + 116, 5580, 0x44, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2e, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08bc, 0x08b8, 0x08b4, 0x01d5, + 0x01d6, + 0x01d7}, + { + 118, 5590, 0x47, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2f, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08c0, 0x08bc, 0x08b8, 0x01d4, + 0x01d5, + 0x01d6}, + { + 120, 5600, 0x4a, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x30, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x08c4, 0x08c0, 0x08bc, 0x01d3, + 0x01d4, + 0x01d5}, + { + 122, 5610, 0x4e, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x31, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x08c8, 0x08c4, 0x08c0, 0x01d2, + 0x01d3, + 0x01d4}, + { + 124, 5620, 0x51, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x32, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x08cc, 0x08c8, 0x08c4, 0x01d2, + 0x01d2, + 0x01d3}, + { + 126, 5630, 0x54, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x33, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x08d0, 0x08cc, 0x08c8, 0x01d1, + 0x01d2, + 0x01d2}, + { + 128, 5640, 0x58, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x34, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x08d4, 0x08d0, 0x08cc, 0x01d0, + 0x01d1, + 0x01d2}, + { + 130, 5650, 0x5b, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x35, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x09, 0x03, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x03, 0x23, 0x00, 0x60, 0x08d8, 0x08d4, 0x08d0, 0x01cf, + 0x01d0, + 0x01d1}, + { + 132, 5660, 0x5e, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x36, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x09, 0x03, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x03, 0x23, 0x00, 0x60, 0x08dc, 0x08d8, 0x08d4, 0x01ce, + 0x01cf, + 0x01d0}, + { + 134, 5670, 0x62, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x37, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x09, 0x03, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x03, 0x23, 0x00, 0x60, 0x08e0, 0x08dc, 0x08d8, 0x01ce, + 0x01ce, + 0x01cf}, + { + 136, 5680, 0x65, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x38, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x09, 0x02, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x02, 0x23, 0x00, 0x60, 0x08e4, 0x08e0, 0x08dc, 0x01cd, + 0x01ce, + 0x01ce}, + { + 138, 5690, 0x68, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x39, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x09, 0x02, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x02, 0x23, 0x00, 0x60, 0x08e8, 0x08e4, 0x08e0, 0x01cc, + 0x01cd, + 0x01ce}, + { + 140, 5700, 0x6c, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3a, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x08ec, 0x08e8, 0x08e4, 0x01cb, + 0x01cc, + 0x01cd}, + { + 142, 5710, 0x6f, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3b, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x08f0, 0x08ec, 0x08e8, 0x01ca, + 0x01cb, + 0x01cc}, + { + 144, 5720, 0x72, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3c, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x08f4, 0x08f0, 0x08ec, 0x01c9, + 0x01ca, + 0x01cb}, + { + 145, 5725, 0x74, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x79, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x08f6, 0x08f2, 0x08ee, 0x01c9, + 0x01ca, + 0x01cb}, + { + 146, 5730, 0x76, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3d, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x08f8, 0x08f4, 0x08f0, 0x01c9, + 0x01c9, + 0x01ca}, + { + 147, 5735, 0x77, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x7b, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x08fa, 0x08f6, 0x08f2, 0x01c8, + 0x01c9, + 0x01ca}, + { + 148, 5740, 0x79, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3e, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x08fc, 0x08f8, 0x08f4, 0x01c8, + 0x01c9, + 0x01c9}, + { + 149, 5745, 0x7b, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x7d, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x08fe, 0x08fa, 0x08f6, 0x01c8, + 0x01c8, + 0x01c9}, + { + 150, 5750, 0x7c, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3f, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x0900, 0x08fc, 0x08f8, 0x01c7, + 0x01c8, + 0x01c9}, + { + 151, 5755, 0x7e, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x7f, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x0902, 0x08fe, 0x08fa, 0x01c7, + 0x01c8, + 0x01c8}, + { + 152, 5760, 0x80, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x40, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x0904, 0x0900, 0x08fc, 0x01c6, + 0x01c7, + 0x01c8}, + { + 153, 5765, 0x81, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x81, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x0906, 0x0902, 0x08fe, 0x01c6, + 0x01c7, + 0x01c8}, + { + 154, 5770, 0x83, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x41, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x0908, 0x0904, 0x0900, 0x01c6, + 0x01c6, + 0x01c7}, + { + 155, 5775, 0x85, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x83, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x090a, 0x0906, 0x0902, 0x01c5, + 0x01c6, + 0x01c7}, + { + 156, 5780, 0x86, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x42, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x090c, 0x0908, 0x0904, 0x01c5, + 0x01c6, + 0x01c6}, + { + 157, 5785, 0x88, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x85, 0x04, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x090e, 0x090a, 0x0906, 0x01c4, + 0x01c5, + 0x01c6}, + { + 158, 5790, 0x8a, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x43, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x0910, 0x090c, 0x0908, 0x01c4, + 0x01c5, + 0x01c6}, + { + 159, 5795, 0x8b, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x87, 0x04, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x0912, 0x090e, 0x090a, 0x01c4, + 0x01c4, + 0x01c5}, + { + 160, 5800, 0x8d, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x44, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x08, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x01, 0x03, 0x00, 0x00, 0x0914, 0x0910, 0x090c, 0x01c3, + 0x01c4, + 0x01c5}, + { + 161, 5805, 0x8f, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x89, 0x04, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x0916, 0x0912, 0x090e, 0x01c3, + 0x01c4, + 0x01c4}, + { + 162, 5810, 0x90, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x45, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x0918, 0x0914, 0x0910, 0x01c2, + 0x01c3, + 0x01c4}, + { + 163, 5815, 0x92, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x8b, 0x04, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x091a, 0x0916, 0x0912, 0x01c2, + 0x01c3, + 0x01c4}, + { + 164, 5820, 0x94, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x46, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x091c, 0x0918, 0x0914, 0x01c2, + 0x01c2, + 0x01c3}, + { + 165, 5825, 0x95, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x8d, 0x04, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x091e, 0x091a, 0x0916, 0x01c1, + 0x01c2, + 0x01c3}, + { + 166, 5830, 0x97, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x47, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x0920, 0x091c, 0x0918, 0x01c1, + 0x01c2, + 0x01c2}, + { + 168, 5840, 0x9a, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x48, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x0924, 0x0920, 0x091c, 0x01c0, + 0x01c1, + 0x01c2}, + { + 170, 5850, 0x9e, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x49, 0x02, 0x04, + 0x00, 0x04, 0x00, 0x44, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x0928, 0x0924, 0x0920, 0x01bf, + 0x01c0, + 0x01c1}, + { + 172, 5860, 0xa1, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x4a, 0x02, 0x04, + 0x00, 0x04, 0x00, 0x44, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x092c, 0x0928, 0x0924, 0x01bf, + 0x01bf, + 0x01c0}, + { + 174, 5870, 0xa4, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x4b, 0x02, 0x04, + 0x00, 0x04, 0x00, 0x44, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x0930, 0x092c, 0x0928, 0x01be, + 0x01bf, + 0x01bf}, + { + 176, 5880, 0xa8, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x4c, 0x02, 0x03, + 0x00, 0x03, 0x00, 0x33, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x0934, 0x0930, 0x092c, 0x01bd, + 0x01be, + 0x01bf}, + { + 178, 5890, 0xab, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x4d, 0x02, 0x03, + 0x00, 0x03, 0x00, 0x33, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x0938, 0x0934, 0x0930, 0x01bc, + 0x01bd, + 0x01be}, + { + 180, 5900, 0xae, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x4e, 0x02, 0x03, + 0x00, 0x03, 0x00, 0x33, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x093c, 0x0938, 0x0934, 0x01bc, + 0x01bc, + 0x01bd}, + { + 1, 2412, 0x48, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x6c, 0x09, 0x0f, + 0x0a, 0x00, 0x0a, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03c9, 0x03c5, 0x03c1, 0x043a, + 0x043f, + 0x0443}, + { + 2, 2417, 0x4b, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x71, 0x09, 0x0f, + 0x0a, 0x00, 0x0a, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03cb, 0x03c7, 0x03c3, 0x0438, + 0x043d, + 0x0441}, + { + 3, 2422, 0x4e, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x76, 0x09, 0x0f, + 0x09, 0x00, 0x09, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03cd, 0x03c9, 0x03c5, 0x0436, + 0x043a, + 0x043f}, + { + 4, 2427, 0x52, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x7b, 0x09, 0x0f, + 0x09, 0x00, 0x09, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03cf, 0x03cb, 0x03c7, 0x0434, + 0x0438, + 0x043d}, + { + 5, 2432, 0x55, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x80, 0x09, 0x0f, + 0x08, 0x00, 0x08, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03d1, 0x03cd, 0x03c9, 0x0431, + 0x0436, + 0x043a}, + { + 6, 2437, 0x58, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x85, 0x09, 0x0f, + 0x08, 0x00, 0x08, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03d3, 0x03cf, 0x03cb, 0x042f, + 0x0434, + 0x0438}, + { + 7, 2442, 0x5c, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x8a, 0x09, 0x0f, + 0x07, 0x00, 0x07, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03d5, 0x03d1, 0x03cd, 0x042d, + 0x0431, + 0x0436}, + { + 8, 2447, 0x5f, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x8f, 0x09, 0x0f, + 0x07, 0x00, 0x07, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03d7, 0x03d3, 0x03cf, 0x042b, + 0x042f, + 0x0434}, + { + 9, 2452, 0x62, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x94, 0x09, 0x0f, + 0x07, 0x00, 0x07, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03d9, 0x03d5, 0x03d1, 0x0429, + 0x042d, + 0x0431}, + { + 10, 2457, 0x66, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x99, 0x09, 0x0f, + 0x06, 0x00, 0x06, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03db, 0x03d7, 0x03d3, 0x0427, + 0x042b, + 0x042f}, + { + 11, 2462, 0x69, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x9e, 0x09, 0x0f, + 0x06, 0x00, 0x06, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03dd, 0x03d9, 0x03d5, 0x0424, + 0x0429, + 0x042d}, + { + 12, 2467, 0x6c, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xa3, 0x09, 0x0f, + 0x05, 0x00, 0x05, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03df, 0x03db, 0x03d7, 0x0422, + 0x0427, + 0x042b}, + { + 13, 2472, 0x70, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xa8, 0x09, 0x0f, + 0x05, 0x00, 0x05, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03e1, 0x03dd, 0x03d9, 0x0420, + 0x0424, + 0x0429}, + { + 14, 2484, 0x78, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xb4, 0x09, 0x0f, + 0x04, 0x00, 0x04, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x03e6, 0x03e2, 0x03de, 0x041b, + 0x041f, + 0x0424} +}; + +static const struct chan_info_nphy_radio2057 chan_info_nphyrev8_2057_rev8[] = { + { + 186, 4930, 0x6b, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xed, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xd3, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x07b8, 0x07b4, 0x07b0, 0x0213, + 0x0214, + 0x0215}, + { + 188, 4940, 0x6e, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xee, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0xd3, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x07bc, 0x07b8, 0x07b4, 0x0212, + 0x0213, + 0x0214}, + { + 190, 4950, 0x72, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xef, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x07c0, 0x07bc, 0x07b8, 0x0211, + 0x0212, + 0x0213}, + { + 192, 4960, 0x75, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf0, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x07c4, 0x07c0, 0x07bc, 0x020f, + 0x0211, + 0x0212}, + { + 194, 4970, 0x78, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf1, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x07c8, 0x07c4, 0x07c0, 0x020e, + 0x020f, + 0x0211}, + { + 196, 4980, 0x7c, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf2, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x07cc, 0x07c8, 0x07c4, 0x020d, + 0x020e, + 0x020f}, + { + 198, 4990, 0x7f, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf3, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xd3, 0x00, 0xff, 0x07d0, 0x07cc, 0x07c8, 0x020c, + 0x020d, + 0x020e}, + { + 200, 5000, 0x82, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf4, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07d4, 0x07d0, 0x07cc, 0x020b, + 0x020c, + 0x020d}, + { + 202, 5010, 0x86, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf5, 0x01, 0x0f, + 0x00, 0x0f, 0x00, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07d8, 0x07d4, 0x07d0, 0x020a, + 0x020b, + 0x020c}, + { + 204, 5020, 0x89, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf6, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07dc, 0x07d8, 0x07d4, 0x0209, + 0x020a, + 0x020b}, + { + 206, 5030, 0x8c, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf7, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07e0, 0x07dc, 0x07d8, 0x0208, + 0x0209, + 0x020a}, + { + 208, 5040, 0x90, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf8, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07e4, 0x07e0, 0x07dc, 0x0207, + 0x0208, + 0x0209}, + { + 210, 5050, 0x93, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xf9, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07e8, 0x07e4, 0x07e0, 0x0206, + 0x0207, + 0x0208}, + { + 212, 5060, 0x96, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xfa, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07ec, 0x07e8, 0x07e4, 0x0205, + 0x0206, + 0x0207}, + { + 214, 5070, 0x9a, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xfb, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07f0, 0x07ec, 0x07e8, 0x0204, + 0x0205, + 0x0206}, + { + 216, 5080, 0x9d, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xfc, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07f4, 0x07f0, 0x07ec, 0x0203, + 0x0204, + 0x0205}, + { + 218, 5090, 0xa0, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xfd, 0x01, 0x0e, + 0x00, 0x0e, 0x00, 0xee, 0x00, 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x00, + 0x00, 0x0f, 0x0f, 0xb3, 0x00, 0xff, 0x07f8, 0x07f4, 0x07f0, 0x0202, + 0x0203, + 0x0204}, + { + 220, 5100, 0xa4, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xfe, 0x01, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x07fc, 0x07f8, 0x07f4, 0x0201, + 0x0202, + 0x0203}, + { + 222, 5110, 0xa7, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0xff, 0x01, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x0800, 0x07fc, 0x07f8, 0x0200, + 0x0201, + 0x0202}, + { + 224, 5120, 0xaa, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x00, 0x02, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x0804, 0x0800, 0x07fc, 0x01ff, + 0x0200, + 0x0201}, + { + 226, 5130, 0xae, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x01, 0x02, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x0808, 0x0804, 0x0800, 0x01fe, + 0x01ff, + 0x0200}, + { + 228, 5140, 0xb1, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x02, 0x02, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x080c, 0x0808, 0x0804, 0x01fd, + 0x01fe, + 0x01ff}, + { + 32, 5160, 0xb8, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x04, 0x02, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x0814, 0x0810, 0x080c, 0x01fb, + 0x01fc, + 0x01fd}, + { + 34, 5170, 0xbb, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x05, 0x02, 0x0d, + 0x00, 0x0d, 0x00, 0xdd, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x0818, 0x0814, 0x0810, 0x01fa, + 0x01fb, + 0x01fc}, + { + 36, 5180, 0xbe, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x06, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x081c, 0x0818, 0x0814, 0x01f9, + 0x01fa, + 0x01fb}, + { + 38, 5190, 0xc2, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x07, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x00, + 0x00, 0x0f, 0x0f, 0xa3, 0x00, 0xfc, 0x0820, 0x081c, 0x0818, 0x01f8, + 0x01f9, + 0x01fa}, + { + 40, 5200, 0xc5, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x08, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x0824, 0x0820, 0x081c, 0x01f7, + 0x01f8, + 0x01f9}, + { + 42, 5210, 0xc8, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x09, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x0828, 0x0824, 0x0820, 0x01f6, + 0x01f7, + 0x01f8}, + { + 44, 5220, 0xcc, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0a, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x082c, 0x0828, 0x0824, 0x01f5, + 0x01f6, + 0x01f7}, + { + 46, 5230, 0xcf, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0b, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x0830, 0x082c, 0x0828, 0x01f4, + 0x01f5, + 0x01f6}, + { + 48, 5240, 0xd2, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0c, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x0834, 0x0830, 0x082c, 0x01f3, + 0x01f4, + 0x01f5}, + { + 50, 5250, 0xd6, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0d, 0x02, 0x0c, + 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x0838, 0x0834, 0x0830, 0x01f2, + 0x01f3, + 0x01f4}, + { + 52, 5260, 0xd9, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0e, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x083c, 0x0838, 0x0834, 0x01f1, + 0x01f2, + 0x01f3}, + { + 54, 5270, 0xdc, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x0f, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x0840, 0x083c, 0x0838, 0x01f0, + 0x01f1, + 0x01f2}, + { + 56, 5280, 0xe0, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x10, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x0844, 0x0840, 0x083c, 0x01f0, + 0x01f0, + 0x01f1}, + { + 58, 5290, 0xe3, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x11, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x00, + 0x00, 0x0f, 0x0f, 0x93, 0x00, 0xf8, 0x0848, 0x0844, 0x0840, 0x01ef, + 0x01f0, + 0x01f0}, + { + 60, 5300, 0xe6, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x12, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x084c, 0x0848, 0x0844, 0x01ee, + 0x01ef, + 0x01f0}, + { + 62, 5310, 0xea, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x13, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x0850, 0x084c, 0x0848, 0x01ed, + 0x01ee, + 0x01ef}, + { + 64, 5320, 0xed, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x14, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x0854, 0x0850, 0x084c, 0x01ec, + 0x01ed, + 0x01ee}, + { + 66, 5330, 0xf0, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x15, 0x02, 0x0b, + 0x00, 0x0b, 0x00, 0xbb, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x0858, 0x0854, 0x0850, 0x01eb, + 0x01ec, + 0x01ed}, + { + 68, 5340, 0xf4, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x16, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x085c, 0x0858, 0x0854, 0x01ea, + 0x01eb, + 0x01ec}, + { + 70, 5350, 0xf7, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x17, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x0860, 0x085c, 0x0858, 0x01e9, + 0x01ea, + 0x01eb}, + { + 72, 5360, 0xfa, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x18, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x0864, 0x0860, 0x085c, 0x01e8, + 0x01e9, + 0x01ea}, + { + 74, 5370, 0xfe, 0x16, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x19, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x0868, 0x0864, 0x0860, 0x01e7, + 0x01e8, + 0x01e9}, + { + 76, 5380, 0x01, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1a, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x086c, 0x0868, 0x0864, 0x01e6, + 0x01e7, + 0x01e8}, + { + 78, 5390, 0x04, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1b, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x00, + 0x00, 0x0f, 0x0c, 0x83, 0x00, 0xf5, 0x0870, 0x086c, 0x0868, 0x01e5, + 0x01e6, + 0x01e7}, + { + 80, 5400, 0x08, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1c, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x0874, 0x0870, 0x086c, 0x01e5, + 0x01e5, + 0x01e6}, + { + 82, 5410, 0x0b, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1d, 0x02, 0x0a, + 0x00, 0x0a, 0x00, 0xaa, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x0878, 0x0874, 0x0870, 0x01e4, + 0x01e5, + 0x01e5}, + { + 84, 5420, 0x0e, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1e, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x087c, 0x0878, 0x0874, 0x01e3, + 0x01e4, + 0x01e5}, + { + 86, 5430, 0x12, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x1f, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x0880, 0x087c, 0x0878, 0x01e2, + 0x01e3, + 0x01e4}, + { + 88, 5440, 0x15, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x20, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x0884, 0x0880, 0x087c, 0x01e1, + 0x01e2, + 0x01e3}, + { + 90, 5450, 0x18, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x21, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x0888, 0x0884, 0x0880, 0x01e0, + 0x01e1, + 0x01e2}, + { + 92, 5460, 0x1c, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x22, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x088c, 0x0888, 0x0884, 0x01df, + 0x01e0, + 0x01e1}, + { + 94, 5470, 0x1f, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x23, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x0890, 0x088c, 0x0888, 0x01de, + 0x01df, + 0x01e0}, + { + 96, 5480, 0x22, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x24, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x0894, 0x0890, 0x088c, 0x01dd, + 0x01de, + 0x01df}, + { + 98, 5490, 0x26, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x25, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x00, + 0x00, 0x0d, 0x09, 0x53, 0x00, 0xb1, 0x0898, 0x0894, 0x0890, 0x01dd, + 0x01dd, + 0x01de}, + { + 100, 5500, 0x29, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x26, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x089c, 0x0898, 0x0894, 0x01dc, + 0x01dd, + 0x01dd}, + { + 102, 5510, 0x2c, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x27, 0x02, 0x09, + 0x00, 0x09, 0x00, 0x99, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08a0, 0x089c, 0x0898, 0x01db, + 0x01dc, + 0x01dd}, + { + 104, 5520, 0x30, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x28, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08a4, 0x08a0, 0x089c, 0x01da, + 0x01db, + 0x01dc}, + { + 106, 5530, 0x33, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x29, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08a8, 0x08a4, 0x08a0, 0x01d9, + 0x01da, + 0x01db}, + { + 108, 5540, 0x36, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2a, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08ac, 0x08a8, 0x08a4, 0x01d8, + 0x01d9, + 0x01da}, + { + 110, 5550, 0x3a, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2b, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08b0, 0x08ac, 0x08a8, 0x01d7, + 0x01d8, + 0x01d9}, + { + 112, 5560, 0x3d, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2c, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08b4, 0x08b0, 0x08ac, 0x01d7, + 0x01d7, + 0x01d8}, + { + 114, 5570, 0x40, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2d, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08b8, 0x08b4, 0x08b0, 0x01d6, + 0x01d7, + 0x01d7}, + { + 116, 5580, 0x44, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2e, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08bc, 0x08b8, 0x08b4, 0x01d5, + 0x01d6, + 0x01d7}, + { + 118, 5590, 0x47, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x2f, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x00, + 0x00, 0x0a, 0x06, 0x43, 0x00, 0x80, 0x08c0, 0x08bc, 0x08b8, 0x01d4, + 0x01d5, + 0x01d6}, + { + 120, 5600, 0x4a, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x30, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x08c4, 0x08c0, 0x08bc, 0x01d3, + 0x01d4, + 0x01d5}, + { + 122, 5610, 0x4e, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x31, 0x02, 0x08, + 0x00, 0x08, 0x00, 0x88, 0x00, 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x08c8, 0x08c4, 0x08c0, 0x01d2, + 0x01d3, + 0x01d4}, + { + 124, 5620, 0x51, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x32, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x08cc, 0x08c8, 0x08c4, 0x01d2, + 0x01d2, + 0x01d3}, + { + 126, 5630, 0x54, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x33, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x08d0, 0x08cc, 0x08c8, 0x01d1, + 0x01d2, + 0x01d2}, + { + 128, 5640, 0x58, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x34, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x04, 0x23, 0x00, 0x60, 0x08d4, 0x08d0, 0x08cc, 0x01d0, + 0x01d1, + 0x01d2}, + { + 130, 5650, 0x5b, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x35, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x09, 0x03, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x03, 0x23, 0x00, 0x60, 0x08d8, 0x08d4, 0x08d0, 0x01cf, + 0x01d0, + 0x01d1}, + { + 132, 5660, 0x5e, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x36, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x09, 0x03, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x03, 0x23, 0x00, 0x60, 0x08dc, 0x08d8, 0x08d4, 0x01ce, + 0x01cf, + 0x01d0}, + { + 134, 5670, 0x62, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x37, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x09, 0x03, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x03, 0x23, 0x00, 0x60, 0x08e0, 0x08dc, 0x08d8, 0x01ce, + 0x01ce, + 0x01cf}, + { + 136, 5680, 0x65, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x38, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x09, 0x02, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x02, 0x23, 0x00, 0x60, 0x08e4, 0x08e0, 0x08dc, 0x01cd, + 0x01ce, + 0x01ce}, + { + 138, 5690, 0x68, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x39, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x09, 0x02, 0x23, 0x00, 0x60, 0x00, + 0x00, 0x09, 0x02, 0x23, 0x00, 0x60, 0x08e8, 0x08e4, 0x08e0, 0x01cc, + 0x01cd, + 0x01ce}, + { + 140, 5700, 0x6c, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3a, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x08ec, 0x08e8, 0x08e4, 0x01cb, + 0x01cc, + 0x01cd}, + { + 142, 5710, 0x6f, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3b, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x08f0, 0x08ec, 0x08e8, 0x01ca, + 0x01cb, + 0x01cc}, + { + 144, 5720, 0x72, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3c, 0x02, 0x07, + 0x00, 0x07, 0x00, 0x77, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x08f4, 0x08f0, 0x08ec, 0x01c9, + 0x01ca, + 0x01cb}, + { + 145, 5725, 0x74, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x79, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x08f6, 0x08f2, 0x08ee, 0x01c9, + 0x01ca, + 0x01cb}, + { + 146, 5730, 0x76, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3d, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x08f8, 0x08f4, 0x08f0, 0x01c9, + 0x01c9, + 0x01ca}, + { + 147, 5735, 0x77, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x7b, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x08fa, 0x08f6, 0x08f2, 0x01c8, + 0x01c9, + 0x01ca}, + { + 148, 5740, 0x79, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3e, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x08fc, 0x08f8, 0x08f4, 0x01c8, + 0x01c9, + 0x01c9}, + { + 149, 5745, 0x7b, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x7d, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x30, 0x08fe, 0x08fa, 0x08f6, 0x01c8, + 0x01c8, + 0x01c9}, + { + 150, 5750, 0x7c, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x3f, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x0900, 0x08fc, 0x08f8, 0x01c7, + 0x01c8, + 0x01c9}, + { + 151, 5755, 0x7e, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x7f, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x0902, 0x08fe, 0x08fa, 0x01c7, + 0x01c8, + 0x01c8}, + { + 152, 5760, 0x80, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x40, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x0904, 0x0900, 0x08fc, 0x01c6, + 0x01c7, + 0x01c8}, + { + 153, 5765, 0x81, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x81, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x0906, 0x0902, 0x08fe, 0x01c6, + 0x01c7, + 0x01c8}, + { + 154, 5770, 0x83, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x41, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x0908, 0x0904, 0x0900, 0x01c6, + 0x01c6, + 0x01c7}, + { + 155, 5775, 0x85, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x83, 0x04, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x090a, 0x0906, 0x0902, 0x01c5, + 0x01c6, + 0x01c7}, + { + 156, 5780, 0x86, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x42, 0x02, 0x06, + 0x00, 0x06, 0x00, 0x66, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x090c, 0x0908, 0x0904, 0x01c5, + 0x01c6, + 0x01c6}, + { + 157, 5785, 0x88, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x85, 0x04, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x090e, 0x090a, 0x0906, 0x01c4, + 0x01c5, + 0x01c6}, + { + 158, 5790, 0x8a, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x43, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x0910, 0x090c, 0x0908, 0x01c4, + 0x01c5, + 0x01c6}, + { + 159, 5795, 0x8b, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x87, 0x04, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x02, 0x13, 0x00, 0x00, 0x0912, 0x090e, 0x090a, 0x01c4, + 0x01c4, + 0x01c5}, + { + 160, 5800, 0x8d, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x44, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x08, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x01, 0x03, 0x00, 0x00, 0x0914, 0x0910, 0x090c, 0x01c3, + 0x01c4, + 0x01c5}, + { + 161, 5805, 0x8f, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x89, 0x04, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x0916, 0x0912, 0x090e, 0x01c3, + 0x01c4, + 0x01c4}, + { + 162, 5810, 0x90, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x45, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x0918, 0x0914, 0x0910, 0x01c2, + 0x01c3, + 0x01c4}, + { + 163, 5815, 0x92, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x8b, 0x04, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x091a, 0x0916, 0x0912, 0x01c2, + 0x01c3, + 0x01c4}, + { + 164, 5820, 0x94, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x46, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x091c, 0x0918, 0x0914, 0x01c2, + 0x01c2, + 0x01c3}, + { + 165, 5825, 0x95, 0x17, 0x20, 0x14, 0x08, 0x08, 0x30, 0x8d, 0x04, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x091e, 0x091a, 0x0916, 0x01c1, + 0x01c2, + 0x01c3}, + { + 166, 5830, 0x97, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x47, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x0920, 0x091c, 0x0918, 0x01c1, + 0x01c2, + 0x01c2}, + { + 168, 5840, 0x9a, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x48, 0x02, 0x05, + 0x00, 0x05, 0x00, 0x55, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x0924, 0x0920, 0x091c, 0x01c0, + 0x01c1, + 0x01c2}, + { + 170, 5850, 0x9e, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x49, 0x02, 0x04, + 0x00, 0x04, 0x00, 0x44, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x0928, 0x0924, 0x0920, 0x01bf, + 0x01c0, + 0x01c1}, + { + 172, 5860, 0xa1, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x4a, 0x02, 0x04, + 0x00, 0x04, 0x00, 0x44, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x092c, 0x0928, 0x0924, 0x01bf, + 0x01bf, + 0x01c0}, + { + 174, 5870, 0xa4, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x4b, 0x02, 0x04, + 0x00, 0x04, 0x00, 0x44, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x0930, 0x092c, 0x0928, 0x01be, + 0x01bf, + 0x01bf}, + { + 176, 5880, 0xa8, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x4c, 0x02, 0x03, + 0x00, 0x03, 0x00, 0x33, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x0934, 0x0930, 0x092c, 0x01bd, + 0x01be, + 0x01bf}, + { + 178, 5890, 0xab, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x4d, 0x02, 0x03, + 0x00, 0x03, 0x00, 0x33, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x0938, 0x0934, 0x0930, 0x01bc, + 0x01bd, + 0x01be}, + { + 180, 5900, 0xae, 0x17, 0x10, 0x0c, 0x0c, 0x0c, 0x30, 0x4e, 0x02, 0x03, + 0x00, 0x03, 0x00, 0x33, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x093c, 0x0938, 0x0934, 0x01bc, + 0x01bc, + 0x01bd}, + { + 1, 2412, 0x48, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x6c, 0x09, 0x0f, + 0x0a, 0x00, 0x0a, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03c9, 0x03c5, 0x03c1, 0x043a, + 0x043f, + 0x0443}, + { + 2, 2417, 0x4b, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x71, 0x09, 0x0f, + 0x0a, 0x00, 0x0a, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03cb, 0x03c7, 0x03c3, 0x0438, + 0x043d, + 0x0441}, + { + 3, 2422, 0x4e, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x76, 0x09, 0x0f, + 0x09, 0x00, 0x09, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03cd, 0x03c9, 0x03c5, 0x0436, + 0x043a, + 0x043f}, + { + 4, 2427, 0x52, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x7b, 0x09, 0x0f, + 0x09, 0x00, 0x09, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03cf, 0x03cb, 0x03c7, 0x0434, + 0x0438, + 0x043d}, + { + 5, 2432, 0x55, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x80, 0x09, 0x0f, + 0x08, 0x00, 0x08, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03d1, 0x03cd, 0x03c9, 0x0431, + 0x0436, + 0x043a}, + { + 6, 2437, 0x58, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x85, 0x09, 0x0f, + 0x08, 0x00, 0x08, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03d3, 0x03cf, 0x03cb, 0x042f, + 0x0434, + 0x0438}, + { + 7, 2442, 0x5c, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x8a, 0x09, 0x0f, + 0x07, 0x00, 0x07, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03d5, 0x03d1, 0x03cd, 0x042d, + 0x0431, + 0x0436}, + { + 8, 2447, 0x5f, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x8f, 0x09, 0x0f, + 0x07, 0x00, 0x07, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03d7, 0x03d3, 0x03cf, 0x042b, + 0x042f, + 0x0434}, + { + 9, 2452, 0x62, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x94, 0x09, 0x0f, + 0x07, 0x00, 0x07, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03d9, 0x03d5, 0x03d1, 0x0429, + 0x042d, + 0x0431}, + { + 10, 2457, 0x66, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x99, 0x09, 0x0f, + 0x06, 0x00, 0x06, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03db, 0x03d7, 0x03d3, 0x0427, + 0x042b, + 0x042f}, + { + 11, 2462, 0x69, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0x9e, 0x09, 0x0f, + 0x06, 0x00, 0x06, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03dd, 0x03d9, 0x03d5, 0x0424, + 0x0429, + 0x042d}, + { + 12, 2467, 0x6c, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xa3, 0x09, 0x0f, + 0x05, 0x00, 0x05, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03df, 0x03db, 0x03d7, 0x0422, + 0x0427, + 0x042b}, + { + 13, 2472, 0x70, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xa8, 0x09, 0x0f, + 0x05, 0x00, 0x05, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x03e1, 0x03dd, 0x03d9, 0x0420, + 0x0424, + 0x0429}, + { + 14, 2484, 0x78, 0x16, 0x30, 0x1b, 0x0a, 0x0a, 0x30, 0xb4, 0x09, 0x0f, + 0x04, 0x00, 0x04, 0x00, 0x61, 0x73, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x61, + 0x73, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x03e6, 0x03e2, 0x03de, 0x041b, + 0x041f, + 0x0424} +}; + +static struct radio_regs regs_2055[] = { + {0x02, 0x80, 0x80, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0x27, 0x27, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0x27, 0x27, 0, 0}, + {0x07, 0x7f, 0x7f, 1, 1}, + {0x08, 0x7, 0x7, 1, 1}, + {0x09, 0x7f, 0x7f, 1, 1}, + {0x0A, 0x7, 0x7, 1, 1}, + {0x0B, 0x15, 0x15, 0, 0}, + {0x0C, 0x15, 0x15, 0, 0}, + {0x0D, 0x4f, 0x4f, 1, 1}, + {0x0E, 0x5, 0x5, 1, 1}, + {0x0F, 0x4f, 0x4f, 1, 1}, + {0x10, 0x5, 0x5, 1, 1}, + {0x11, 0xd0, 0xd0, 0, 0}, + {0x12, 0x2, 0x2, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0x40, 0x40, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0xc0, 0xc0, 0, 0}, + {0x1E, 0xff, 0xff, 0, 0}, + {0x1F, 0xc0, 0xc0, 0, 0}, + {0x20, 0xff, 0xff, 0, 0}, + {0x21, 0xc0, 0xc0, 0, 0}, + {0x22, 0, 0, 0, 0}, + {0x23, 0x2c, 0x2c, 0, 0}, + {0x24, 0, 0, 0, 0}, + {0x25, 0, 0, 0, 0}, + {0x26, 0, 0, 0, 0}, + {0x27, 0, 0, 0, 0}, + {0x28, 0, 0, 0, 0}, + {0x29, 0, 0, 0, 0}, + {0x2A, 0, 0, 0, 0}, + {0x2B, 0, 0, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0xa4, 0xa4, 0, 0}, + {0x2E, 0x38, 0x38, 0, 0}, + {0x2F, 0, 0, 0, 0}, + {0x30, 0x4, 0x4, 1, 1}, + {0x31, 0, 0, 0, 0}, + {0x32, 0xa, 0xa, 0, 0}, + {0x33, 0x87, 0x87, 0, 0}, + {0x34, 0x9, 0x9, 0, 0}, + {0x35, 0x70, 0x70, 0, 0}, + {0x36, 0x11, 0x11, 0, 0}, + {0x37, 0x18, 0x18, 1, 1}, + {0x38, 0x6, 0x6, 0, 0}, + {0x39, 0x4, 0x4, 1, 1}, + {0x3A, 0x6, 0x6, 0, 0}, + {0x3B, 0x9e, 0x9e, 0, 0}, + {0x3C, 0x9, 0x9, 0, 0}, + {0x3D, 0xc8, 0xc8, 1, 1}, + {0x3E, 0x88, 0x88, 0, 0}, + {0x3F, 0, 0, 0, 0}, + {0x40, 0, 0, 0, 0}, + {0x41, 0, 0, 0, 0}, + {0x42, 0x1, 0x1, 0, 0}, + {0x43, 0x2, 0x2, 0, 0}, + {0x44, 0x96, 0x96, 0, 0}, + {0x45, 0x3e, 0x3e, 0, 0}, + {0x46, 0x3e, 0x3e, 0, 0}, + {0x47, 0x13, 0x13, 0, 0}, + {0x48, 0x2, 0x2, 0, 0}, + {0x49, 0x15, 0x15, 0, 0}, + {0x4A, 0x7, 0x7, 0, 0}, + {0x4B, 0, 0, 0, 0}, + {0x4C, 0, 0, 0, 0}, + {0x4D, 0, 0, 0, 0}, + {0x4E, 0, 0, 0, 0}, + {0x4F, 0, 0, 0, 0}, + {0x50, 0x8, 0x8, 0, 0}, + {0x51, 0x8, 0x8, 0, 0}, + {0x52, 0x6, 0x6, 0, 0}, + {0x53, 0x84, 0x84, 1, 1}, + {0x54, 0xc3, 0xc3, 0, 0}, + {0x55, 0x8f, 0x8f, 0, 0}, + {0x56, 0xff, 0xff, 0, 0}, + {0x57, 0xff, 0xff, 0, 0}, + {0x58, 0x88, 0x88, 0, 0}, + {0x59, 0x88, 0x88, 0, 0}, + {0x5A, 0, 0, 0, 0}, + {0x5B, 0xcc, 0xcc, 0, 0}, + {0x5C, 0x6, 0x6, 0, 0}, + {0x5D, 0x80, 0x80, 0, 0}, + {0x5E, 0x80, 0x80, 0, 0}, + {0x5F, 0xf8, 0xf8, 0, 0}, + {0x60, 0x88, 0x88, 0, 0}, + {0x61, 0x88, 0x88, 0, 0}, + {0x62, 0x88, 0x8, 1, 1}, + {0x63, 0x88, 0x88, 0, 0}, + {0x64, 0, 0, 0, 0}, + {0x65, 0x1, 0x1, 1, 1}, + {0x66, 0x8a, 0x8a, 0, 0}, + {0x67, 0x8, 0x8, 0, 0}, + {0x68, 0x83, 0x83, 0, 0}, + {0x69, 0x6, 0x6, 0, 0}, + {0x6A, 0xa0, 0xa0, 0, 0}, + {0x6B, 0xa, 0xa, 0, 0}, + {0x6C, 0x87, 0x87, 1, 1}, + {0x6D, 0x2a, 0x2a, 0, 0}, + {0x6E, 0x2a, 0x2a, 0, 0}, + {0x6F, 0x2a, 0x2a, 0, 0}, + {0x70, 0x2a, 0x2a, 0, 0}, + {0x71, 0x18, 0x18, 0, 0}, + {0x72, 0x6a, 0x6a, 1, 1}, + {0x73, 0xab, 0xab, 1, 1}, + {0x74, 0x13, 0x13, 1, 1}, + {0x75, 0xc1, 0xc1, 1, 1}, + {0x76, 0xaa, 0xaa, 1, 1}, + {0x77, 0x87, 0x87, 1, 1}, + {0x78, 0, 0, 0, 0}, + {0x79, 0x6, 0x6, 0, 0}, + {0x7A, 0x7, 0x7, 0, 0}, + {0x7B, 0x7, 0x7, 0, 0}, + {0x7C, 0x15, 0x15, 0, 0}, + {0x7D, 0x55, 0x55, 0, 0}, + {0x7E, 0x97, 0x97, 1, 1}, + {0x7F, 0x8, 0x8, 0, 0}, + {0x80, 0x14, 0x14, 1, 1}, + {0x81, 0x33, 0x33, 0, 0}, + {0x82, 0x88, 0x88, 0, 0}, + {0x83, 0x6, 0x6, 0, 0}, + {0x84, 0x3, 0x3, 1, 1}, + {0x85, 0xa, 0xa, 0, 0}, + {0x86, 0x3, 0x3, 1, 1}, + {0x87, 0x2a, 0x2a, 0, 0}, + {0x88, 0xa4, 0xa4, 0, 0}, + {0x89, 0x18, 0x18, 0, 0}, + {0x8A, 0x28, 0x28, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0x4a, 0x4a, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0xf8, 0xf8, 0, 0}, + {0x8F, 0x88, 0x88, 0, 0}, + {0x90, 0x88, 0x88, 0, 0}, + {0x91, 0x88, 0x8, 1, 1}, + {0x92, 0x88, 0x88, 0, 0}, + {0x93, 0, 0, 0, 0}, + {0x94, 0x1, 0x1, 1, 1}, + {0x95, 0x8a, 0x8a, 0, 0}, + {0x96, 0x8, 0x8, 0, 0}, + {0x97, 0x83, 0x83, 0, 0}, + {0x98, 0x6, 0x6, 0, 0}, + {0x99, 0xa0, 0xa0, 0, 0}, + {0x9A, 0xa, 0xa, 0, 0}, + {0x9B, 0x87, 0x87, 1, 1}, + {0x9C, 0x2a, 0x2a, 0, 0}, + {0x9D, 0x2a, 0x2a, 0, 0}, + {0x9E, 0x2a, 0x2a, 0, 0}, + {0x9F, 0x2a, 0x2a, 0, 0}, + {0xA0, 0x18, 0x18, 0, 0}, + {0xA1, 0x6a, 0x6a, 1, 1}, + {0xA2, 0xab, 0xab, 1, 1}, + {0xA3, 0x13, 0x13, 1, 1}, + {0xA4, 0xc1, 0xc1, 1, 1}, + {0xA5, 0xaa, 0xaa, 1, 1}, + {0xA6, 0x87, 0x87, 1, 1}, + {0xA7, 0, 0, 0, 0}, + {0xA8, 0x6, 0x6, 0, 0}, + {0xA9, 0x7, 0x7, 0, 0}, + {0xAA, 0x7, 0x7, 0, 0}, + {0xAB, 0x15, 0x15, 0, 0}, + {0xAC, 0x55, 0x55, 0, 0}, + {0xAD, 0x97, 0x97, 1, 1}, + {0xAE, 0x8, 0x8, 0, 0}, + {0xAF, 0x14, 0x14, 1, 1}, + {0xB0, 0x33, 0x33, 0, 0}, + {0xB1, 0x88, 0x88, 0, 0}, + {0xB2, 0x6, 0x6, 0, 0}, + {0xB3, 0x3, 0x3, 1, 1}, + {0xB4, 0xa, 0xa, 0, 0}, + {0xB5, 0x3, 0x3, 1, 1}, + {0xB6, 0x2a, 0x2a, 0, 0}, + {0xB7, 0xa4, 0xa4, 0, 0}, + {0xB8, 0x18, 0x18, 0, 0}, + {0xB9, 0x28, 0x28, 0, 0}, + {0xBA, 0, 0, 0, 0}, + {0xBB, 0x4a, 0x4a, 0, 0}, + {0xBC, 0, 0, 0, 0}, + {0xBD, 0x71, 0x71, 0, 0}, + {0xBE, 0x72, 0x72, 0, 0}, + {0xBF, 0x73, 0x73, 0, 0}, + {0xC0, 0x74, 0x74, 0, 0}, + {0xC1, 0x75, 0x75, 0, 0}, + {0xC2, 0x76, 0x76, 0, 0}, + {0xC3, 0x77, 0x77, 0, 0}, + {0xC4, 0x78, 0x78, 0, 0}, + {0xC5, 0x79, 0x79, 0, 0}, + {0xC6, 0x7a, 0x7a, 0, 0}, + {0xC7, 0, 0, 0, 0}, + {0xC8, 0, 0, 0, 0}, + {0xC9, 0, 0, 0, 0}, + {0xCA, 0, 0, 0, 0}, + {0xCB, 0, 0, 0, 0}, + {0xCC, 0, 0, 0, 0}, + {0xCD, 0, 0, 0, 0}, + {0xCE, 0x6, 0x6, 0, 0}, + {0xCF, 0, 0, 0, 0}, + {0xD0, 0, 0, 0, 0}, + {0xD1, 0x18, 0x18, 0, 0}, + {0xD2, 0x88, 0x88, 0, 0}, + {0xD3, 0, 0, 0, 0}, + {0xD4, 0, 0, 0, 0}, + {0xD5, 0, 0, 0, 0}, + {0xD6, 0, 0, 0, 0}, + {0xD7, 0, 0, 0, 0}, + {0xD8, 0, 0, 0, 0}, + {0xD9, 0, 0, 0, 0}, + {0xDA, 0x6, 0x6, 0, 0}, + {0xDB, 0, 0, 0, 0}, + {0xDC, 0, 0, 0, 0}, + {0xDD, 0x18, 0x18, 0, 0}, + {0xDE, 0x88, 0x88, 0, 0}, + {0xDF, 0, 0, 0, 0}, + {0xE0, 0, 0, 0, 0}, + {0xE1, 0, 0, 0, 0}, + {0xE2, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0}, +}; + +static struct radio_regs regs_SYN_2056[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0x1, 0x1, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0, 0, 0, 0}, + {0x21, 0, 0, 0, 0}, + {0x22, 0x60, 0x60, 0, 0}, + {0x23, 0x6, 0x6, 0, 0}, + {0x24, 0xc, 0xc, 0, 0}, + {0x25, 0, 0, 0, 0}, + {0x26, 0, 0, 0, 0}, + {0x27, 0, 0, 0, 0}, + {0x28, 0x1, 0x1, 0, 0}, + {0x29, 0, 0, 0, 0}, + {0x2A, 0, 0, 0, 0}, + {0x2B, 0, 0, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0, 0, 0, 0}, + {0x2E, 0xd, 0xd, 0, 0}, + {0x2F, 0x1f, 0x1f, 0, 0}, + {0x30, 0x15, 0x15, 0, 0}, + {0x31, 0xf, 0xf, 0, 0}, + {0x32, 0, 0, 0, 0}, + {0x33, 0, 0, 0, 0}, + {0x34, 0, 0, 0, 0}, + {0x35, 0, 0, 0, 0}, + {0x36, 0, 0, 0, 0}, + {0x37, 0, 0, 0, 0}, + {0x38, 0, 0, 0, 0}, + {0x39, 0, 0, 0, 0}, + {0x3A, 0, 0, 0, 0}, + {0x3B, 0, 0, 0, 0}, + {0x3C, 0x13, 0x13, 0, 0}, + {0x3D, 0xf, 0xf, 0, 0}, + {0x3E, 0x18, 0x18, 0, 0}, + {0x3F, 0, 0, 0, 0}, + {0x40, 0, 0, 0, 0}, + {0x41, 0x20, 0x20, 0, 0}, + {0x42, 0x20, 0x20, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x77, 0x77, 0, 0}, + {0x45, 0x7, 0x7, 0, 0}, + {0x46, 0x1, 0x1, 0, 0}, + {0x47, 0x4, 0x4, 0, 0}, + {0x48, 0xf, 0xf, 0, 0}, + {0x49, 0x30, 0x30, 0, 0}, + {0x4A, 0x32, 0x32, 0, 0}, + {0x4B, 0xd, 0xd, 0, 0}, + {0x4C, 0xd, 0xd, 0, 0}, + {0x4D, 0x4, 0x4, 0, 0}, + {0x4E, 0x6, 0x6, 0, 0}, + {0x4F, 0x1, 0x1, 0, 0}, + {0x50, 0x1c, 0x1c, 0, 0}, + {0x51, 0x2, 0x2, 0, 0}, + {0x52, 0x2, 0x2, 0, 0}, + {0x53, 0xf7, 0xf7, 1, 1}, + {0x54, 0xb4, 0xb4, 0, 0}, + {0x55, 0xd2, 0xd2, 0, 0}, + {0x56, 0, 0, 0, 0}, + {0x57, 0, 0, 0, 0}, + {0x58, 0x4, 0x4, 0, 0}, + {0x59, 0x96, 0x96, 0, 0}, + {0x5A, 0x3e, 0x3e, 0, 0}, + {0x5B, 0x3e, 0x3e, 0, 0}, + {0x5C, 0x13, 0x13, 0, 0}, + {0x5D, 0x2, 0x2, 0, 0}, + {0x5E, 0, 0, 0, 0}, + {0x5F, 0x7, 0x7, 0, 0}, + {0x60, 0x7, 0x7, 1, 1}, + {0x61, 0x8, 0x8, 0, 0}, + {0x62, 0x3, 0x3, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0, 0, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 0, 0}, + {0x68, 0x40, 0x40, 0, 0}, + {0x69, 0, 0, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0, 0, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0x1, 0x1, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0x60, 0x60, 0, 0}, + {0x71, 0x66, 0x66, 0, 0}, + {0x72, 0xc, 0xc, 0, 0}, + {0x73, 0x66, 0x66, 0, 0}, + {0x74, 0x8f, 0x8f, 1, 1}, + {0x75, 0, 0, 0, 0}, + {0x76, 0xcc, 0xcc, 0, 0}, + {0x77, 0x1, 0x1, 0, 0}, + {0x78, 0x66, 0x66, 0, 0}, + {0x79, 0x66, 0x66, 0, 0}, + {0x7A, 0, 0, 0, 0}, + {0x7B, 0, 0, 0, 0}, + {0x7C, 0, 0, 0, 0}, + {0x7D, 0, 0, 0, 0}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0xff, 0xff, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0, 0, 0, 0}, + {0x94, 0, 0, 0, 0}, + {0x95, 0, 0, 0, 0}, + {0x96, 0, 0, 0, 0}, + {0x97, 0, 0, 0, 0}, + {0x98, 0, 0, 0, 0}, + {0x99, 0, 0, 0, 0}, + {0x9A, 0, 0, 0, 0}, + {0x9B, 0, 0, 0, 0}, + {0x9C, 0, 0, 0, 0}, + {0x9D, 0, 0, 0, 0}, + {0x9E, 0, 0, 0, 0}, + {0x9F, 0x6, 0x6, 0, 0}, + {0xA0, 0x66, 0x66, 0, 0}, + {0xA1, 0x66, 0x66, 0, 0}, + {0xA2, 0x66, 0x66, 0, 0}, + {0xA3, 0x66, 0x66, 0, 0}, + {0xA4, 0x66, 0x66, 0, 0}, + {0xA5, 0x66, 0x66, 0, 0}, + {0xA6, 0x66, 0x66, 0, 0}, + {0xA7, 0x66, 0x66, 0, 0}, + {0xA8, 0x66, 0x66, 0, 0}, + {0xA9, 0x66, 0x66, 0, 0}, + {0xAA, 0x66, 0x66, 0, 0}, + {0xAB, 0x66, 0x66, 0, 0}, + {0xAC, 0x66, 0x66, 0, 0}, + {0xAD, 0x66, 0x66, 0, 0}, + {0xAE, 0x66, 0x66, 0, 0}, + {0xAF, 0x66, 0x66, 0, 0}, + {0xB0, 0x66, 0x66, 0, 0}, + {0xB1, 0x66, 0x66, 0, 0}, + {0xB2, 0x66, 0x66, 0, 0}, + {0xB3, 0xa, 0xa, 0, 0}, + {0xB4, 0, 0, 0, 0}, + {0xB5, 0, 0, 0, 0}, + {0xB6, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0} +}; + +static struct radio_regs regs_TX_2056[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0, 0, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0, 0, 0, 0}, + {0x21, 0x88, 0x88, 0, 0}, + {0x22, 0x88, 0x88, 0, 0}, + {0x23, 0x88, 0x88, 0, 0}, + {0x24, 0x88, 0x88, 0, 0}, + {0x25, 0xc, 0xc, 0, 0}, + {0x26, 0, 0, 0, 0}, + {0x27, 0x3, 0x3, 0, 0}, + {0x28, 0, 0, 0, 0}, + {0x29, 0x3, 0x3, 0, 0}, + {0x2A, 0x37, 0x37, 0, 0}, + {0x2B, 0x3, 0x3, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0, 0, 0, 0}, + {0x2E, 0x1, 0x1, 0, 0}, + {0x2F, 0x1, 0x1, 0, 0}, + {0x30, 0, 0, 0, 0}, + {0x31, 0, 0, 0, 0}, + {0x32, 0, 0, 0, 0}, + {0x33, 0x11, 0x11, 0, 0}, + {0x34, 0x11, 0x11, 0, 0}, + {0x35, 0, 0, 0, 0}, + {0x36, 0, 0, 0, 0}, + {0x37, 0x3, 0x3, 0, 0}, + {0x38, 0xf, 0xf, 0, 0}, + {0x39, 0, 0, 0, 0}, + {0x3A, 0x2d, 0x2d, 0, 0}, + {0x3B, 0, 0, 0, 0}, + {0x3C, 0x6e, 0x6e, 0, 0}, + {0x3D, 0xf0, 0xf0, 1, 1}, + {0x3E, 0, 0, 0, 0}, + {0x3F, 0, 0, 0, 0}, + {0x40, 0, 0, 0, 0}, + {0x41, 0x3, 0x3, 0, 0}, + {0x42, 0x3, 0x3, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x1e, 0x1e, 0, 0}, + {0x45, 0, 0, 0, 0}, + {0x46, 0x6e, 0x6e, 0, 0}, + {0x47, 0xf0, 0xf0, 1, 1}, + {0x48, 0, 0, 0, 0}, + {0x49, 0x2, 0x2, 0, 0}, + {0x4A, 0xff, 0xff, 1, 1}, + {0x4B, 0xc, 0xc, 0, 0}, + {0x4C, 0, 0, 0, 0}, + {0x4D, 0x38, 0x38, 0, 0}, + {0x4E, 0x70, 0x70, 1, 1}, + {0x4F, 0x2, 0x2, 0, 0}, + {0x50, 0x88, 0x88, 0, 0}, + {0x51, 0xc, 0xc, 0, 0}, + {0x52, 0, 0, 0, 0}, + {0x53, 0x8, 0x8, 0, 0}, + {0x54, 0x70, 0x70, 1, 1}, + {0x55, 0x2, 0x2, 0, 0}, + {0x56, 0xff, 0xff, 1, 1}, + {0x57, 0, 0, 0, 0}, + {0x58, 0x83, 0x83, 0, 0}, + {0x59, 0x77, 0x77, 1, 1}, + {0x5A, 0, 0, 0, 0}, + {0x5B, 0x2, 0x2, 0, 0}, + {0x5C, 0x88, 0x88, 0, 0}, + {0x5D, 0, 0, 0, 0}, + {0x5E, 0x8, 0x8, 0, 0}, + {0x5F, 0x77, 0x77, 1, 1}, + {0x60, 0x1, 0x1, 0, 0}, + {0x61, 0, 0, 0, 0}, + {0x62, 0x7, 0x7, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0x7, 0x7, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0x74, 0x74, 1, 1}, + {0x68, 0, 0, 0, 0}, + {0x69, 0xa, 0xa, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0, 0, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0, 0, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0, 0, 0, 0}, + {0x71, 0x2, 0x2, 0, 0}, + {0x72, 0, 0, 0, 0}, + {0x73, 0, 0, 0, 0}, + {0x74, 0xe, 0xe, 0, 0}, + {0x75, 0xe, 0xe, 0, 0}, + {0x76, 0xe, 0xe, 0, 0}, + {0x77, 0x13, 0x13, 0, 0}, + {0x78, 0x13, 0x13, 0, 0}, + {0x79, 0x1b, 0x1b, 0, 0}, + {0x7A, 0x1b, 0x1b, 0, 0}, + {0x7B, 0x55, 0x55, 0, 0}, + {0x7C, 0x5b, 0x5b, 0, 0}, + {0x7D, 0, 0, 0, 0}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0, 0, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0} +}; + +static struct radio_regs regs_RX_2056[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0, 0, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0x3, 0x3, 0, 0}, + {0x21, 0, 0, 0, 0}, + {0x22, 0, 0, 0, 0}, + {0x23, 0x90, 0x90, 0, 0}, + {0x24, 0x55, 0x55, 0, 0}, + {0x25, 0x15, 0x15, 0, 0}, + {0x26, 0x5, 0x5, 0, 0}, + {0x27, 0x15, 0x15, 0, 0}, + {0x28, 0x5, 0x5, 0, 0}, + {0x29, 0x20, 0x20, 0, 0}, + {0x2A, 0x11, 0x11, 0, 0}, + {0x2B, 0x90, 0x90, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0x88, 0x88, 0, 0}, + {0x2E, 0x32, 0x32, 0, 0}, + {0x2F, 0x77, 0x77, 0, 0}, + {0x30, 0x17, 0x17, 1, 1}, + {0x31, 0xff, 0xff, 1, 1}, + {0x32, 0x20, 0x20, 0, 0}, + {0x33, 0, 0, 0, 0}, + {0x34, 0x88, 0x88, 0, 0}, + {0x35, 0x32, 0x32, 0, 0}, + {0x36, 0x77, 0x77, 0, 0}, + {0x37, 0x17, 0x17, 1, 1}, + {0x38, 0xf0, 0xf0, 1, 1}, + {0x39, 0x20, 0x20, 0, 0}, + {0x3A, 0x8, 0x8, 0, 0}, + {0x3B, 0x99, 0x99, 0, 0}, + {0x3C, 0, 0, 0, 0}, + {0x3D, 0x44, 0x44, 1, 1}, + {0x3E, 0, 0, 0, 0}, + {0x3F, 0x44, 0x44, 0, 0}, + {0x40, 0xf, 0xf, 1, 1}, + {0x41, 0x6, 0x6, 0, 0}, + {0x42, 0x4, 0x4, 0, 0}, + {0x43, 0x50, 0x50, 1, 1}, + {0x44, 0x8, 0x8, 0, 0}, + {0x45, 0x99, 0x99, 0, 0}, + {0x46, 0, 0, 0, 0}, + {0x47, 0x11, 0x11, 0, 0}, + {0x48, 0, 0, 0, 0}, + {0x49, 0x44, 0x44, 0, 0}, + {0x4A, 0x7, 0x7, 0, 0}, + {0x4B, 0x6, 0x6, 0, 0}, + {0x4C, 0x4, 0x4, 0, 0}, + {0x4D, 0, 0, 0, 0}, + {0x4E, 0, 0, 0, 0}, + {0x4F, 0x66, 0x66, 0, 0}, + {0x50, 0x66, 0x66, 0, 0}, + {0x51, 0x57, 0x57, 0, 0}, + {0x52, 0x57, 0x57, 0, 0}, + {0x53, 0x44, 0x44, 0, 0}, + {0x54, 0, 0, 0, 0}, + {0x55, 0, 0, 0, 0}, + {0x56, 0x8, 0x8, 0, 0}, + {0x57, 0x8, 0x8, 0, 0}, + {0x58, 0x7, 0x7, 0, 0}, + {0x59, 0x22, 0x22, 0, 0}, + {0x5A, 0x22, 0x22, 0, 0}, + {0x5B, 0x2, 0x2, 0, 0}, + {0x5C, 0x23, 0x23, 0, 0}, + {0x5D, 0x7, 0x7, 0, 0}, + {0x5E, 0x55, 0x55, 0, 0}, + {0x5F, 0x23, 0x23, 0, 0}, + {0x60, 0x41, 0x41, 0, 0}, + {0x61, 0x1, 0x1, 0, 0}, + {0x62, 0xa, 0xa, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0, 0, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 0, 0}, + {0x68, 0, 0, 0, 0}, + {0x69, 0, 0, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0xc, 0xc, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0, 0, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0, 0, 0, 0}, + {0x71, 0, 0, 0, 0}, + {0x72, 0x22, 0x22, 0, 0}, + {0x73, 0x22, 0x22, 0, 0}, + {0x74, 0x2, 0x2, 0, 0}, + {0x75, 0xa, 0xa, 0, 0}, + {0x76, 0x1, 0x1, 0, 0}, + {0x77, 0x22, 0x22, 0, 0}, + {0x78, 0x30, 0x30, 0, 0}, + {0x79, 0, 0, 0, 0}, + {0x7A, 0, 0, 0, 0}, + {0x7B, 0, 0, 0, 0}, + {0x7C, 0, 0, 0, 0}, + {0x7D, 0, 0, 0, 0}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0, 0, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0, 0, 0, 0}, + {0x94, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0} +}; + +static struct radio_regs regs_SYN_2056_A1[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0x1, 0x1, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0, 0, 0, 0}, + {0x21, 0, 0, 0, 0}, + {0x22, 0x60, 0x60, 0, 0}, + {0x23, 0x6, 0x6, 0, 0}, + {0x24, 0xc, 0xc, 0, 0}, + {0x25, 0, 0, 0, 0}, + {0x26, 0, 0, 0, 0}, + {0x27, 0, 0, 0, 0}, + {0x28, 0x1, 0x1, 0, 0}, + {0x29, 0, 0, 0, 0}, + {0x2A, 0, 0, 0, 0}, + {0x2B, 0, 0, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0, 0, 0, 0}, + {0x2E, 0xd, 0xd, 0, 0}, + {0x2F, 0x1f, 0x1f, 0, 0}, + {0x30, 0x15, 0x15, 0, 0}, + {0x31, 0xf, 0xf, 0, 0}, + {0x32, 0, 0, 0, 0}, + {0x33, 0, 0, 0, 0}, + {0x34, 0, 0, 0, 0}, + {0x35, 0, 0, 0, 0}, + {0x36, 0, 0, 0, 0}, + {0x37, 0, 0, 0, 0}, + {0x38, 0, 0, 0, 0}, + {0x39, 0, 0, 0, 0}, + {0x3A, 0, 0, 0, 0}, + {0x3B, 0, 0, 0, 0}, + {0x3C, 0x13, 0x13, 0, 0}, + {0x3D, 0xf, 0xf, 0, 0}, + {0x3E, 0x18, 0x18, 0, 0}, + {0x3F, 0, 0, 0, 0}, + {0x40, 0, 0, 0, 0}, + {0x41, 0x20, 0x20, 0, 0}, + {0x42, 0x20, 0x20, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x77, 0x77, 0, 0}, + {0x45, 0x7, 0x7, 0, 0}, + {0x46, 0x1, 0x1, 0, 0}, + {0x47, 0x4, 0x4, 0, 0}, + {0x48, 0xf, 0xf, 0, 0}, + {0x49, 0x30, 0x30, 0, 0}, + {0x4A, 0x32, 0x32, 0, 0}, + {0x4B, 0xd, 0xd, 0, 0}, + {0x4C, 0xd, 0xd, 0, 0}, + {0x4D, 0x4, 0x4, 0, 0}, + {0x4E, 0x6, 0x6, 0, 0}, + {0x4F, 0x1, 0x1, 0, 0}, + {0x50, 0x1c, 0x1c, 0, 0}, + {0x51, 0x2, 0x2, 0, 0}, + {0x52, 0x2, 0x2, 0, 0}, + {0x53, 0xf7, 0xf7, 1, 1}, + {0x54, 0xb4, 0xb4, 0, 0}, + {0x55, 0xd2, 0xd2, 0, 0}, + {0x56, 0, 0, 0, 0}, + {0x57, 0, 0, 0, 0}, + {0x58, 0x4, 0x4, 0, 0}, + {0x59, 0x96, 0x96, 0, 0}, + {0x5A, 0x3e, 0x3e, 0, 0}, + {0x5B, 0x3e, 0x3e, 0, 0}, + {0x5C, 0x13, 0x13, 0, 0}, + {0x5D, 0x2, 0x2, 0, 0}, + {0x5E, 0, 0, 0, 0}, + {0x5F, 0x7, 0x7, 0, 0}, + {0x60, 0x7, 0x7, 1, 1}, + {0x61, 0x8, 0x8, 0, 0}, + {0x62, 0x3, 0x3, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0, 0, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 0, 0}, + {0x68, 0x40, 0x40, 0, 0}, + {0x69, 0, 0, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0, 0, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0x1, 0x1, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0x60, 0x60, 0, 0}, + {0x71, 0x66, 0x66, 0, 0}, + {0x72, 0xc, 0xc, 0, 0}, + {0x73, 0x66, 0x66, 0, 0}, + {0x74, 0x8f, 0x8f, 1, 1}, + {0x75, 0, 0, 0, 0}, + {0x76, 0xcc, 0xcc, 0, 0}, + {0x77, 0x1, 0x1, 0, 0}, + {0x78, 0x66, 0x66, 0, 0}, + {0x79, 0x66, 0x66, 0, 0}, + {0x7A, 0, 0, 0, 0}, + {0x7B, 0, 0, 0, 0}, + {0x7C, 0, 0, 0, 0}, + {0x7D, 0, 0, 0, 0}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0xff, 0xff, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0, 0, 0, 0}, + {0x94, 0, 0, 0, 0}, + {0x95, 0, 0, 0, 0}, + {0x96, 0, 0, 0, 0}, + {0x97, 0, 0, 0, 0}, + {0x98, 0, 0, 0, 0}, + {0x99, 0, 0, 0, 0}, + {0x9A, 0, 0, 0, 0}, + {0x9B, 0, 0, 0, 0}, + {0x9C, 0, 0, 0, 0}, + {0x9D, 0, 0, 0, 0}, + {0x9E, 0, 0, 0, 0}, + {0x9F, 0x6, 0x6, 0, 0}, + {0xA0, 0x66, 0x66, 0, 0}, + {0xA1, 0x66, 0x66, 0, 0}, + {0xA2, 0x66, 0x66, 0, 0}, + {0xA3, 0x66, 0x66, 0, 0}, + {0xA4, 0x66, 0x66, 0, 0}, + {0xA5, 0x66, 0x66, 0, 0}, + {0xA6, 0x66, 0x66, 0, 0}, + {0xA7, 0x66, 0x66, 0, 0}, + {0xA8, 0x66, 0x66, 0, 0}, + {0xA9, 0x66, 0x66, 0, 0}, + {0xAA, 0x66, 0x66, 0, 0}, + {0xAB, 0x66, 0x66, 0, 0}, + {0xAC, 0x66, 0x66, 0, 0}, + {0xAD, 0x66, 0x66, 0, 0}, + {0xAE, 0x66, 0x66, 0, 0}, + {0xAF, 0x66, 0x66, 0, 0}, + {0xB0, 0x66, 0x66, 0, 0}, + {0xB1, 0x66, 0x66, 0, 0}, + {0xB2, 0x66, 0x66, 0, 0}, + {0xB3, 0xa, 0xa, 0, 0}, + {0xB4, 0, 0, 0, 0}, + {0xB5, 0, 0, 0, 0}, + {0xB6, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0} +}; + +static struct radio_regs regs_TX_2056_A1[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0, 0, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0, 0, 0, 0}, + {0x21, 0x88, 0x88, 0, 0}, + {0x22, 0x88, 0x88, 0, 0}, + {0x23, 0x88, 0x88, 0, 0}, + {0x24, 0x88, 0x88, 0, 0}, + {0x25, 0xc, 0xc, 0, 0}, + {0x26, 0, 0, 0, 0}, + {0x27, 0x3, 0x3, 0, 0}, + {0x28, 0, 0, 0, 0}, + {0x29, 0x3, 0x3, 0, 0}, + {0x2A, 0x37, 0x37, 0, 0}, + {0x2B, 0x3, 0x3, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0, 0, 0, 0}, + {0x2E, 0x1, 0x1, 0, 0}, + {0x2F, 0x1, 0x1, 0, 0}, + {0x30, 0, 0, 0, 0}, + {0x31, 0, 0, 0, 0}, + {0x32, 0, 0, 0, 0}, + {0x33, 0x11, 0x11, 0, 0}, + {0x34, 0x11, 0x11, 0, 0}, + {0x35, 0, 0, 0, 0}, + {0x36, 0, 0, 0, 0}, + {0x37, 0x3, 0x3, 0, 0}, + {0x38, 0xf, 0xf, 0, 0}, + {0x39, 0, 0, 0, 0}, + {0x3A, 0x2d, 0x2d, 0, 0}, + {0x3B, 0, 0, 0, 0}, + {0x3C, 0x6e, 0x6e, 0, 0}, + {0x3D, 0xf0, 0xf0, 1, 1}, + {0x3E, 0, 0, 0, 0}, + {0x3F, 0, 0, 0, 0}, + {0x40, 0, 0, 0, 0}, + {0x41, 0x3, 0x3, 0, 0}, + {0x42, 0x3, 0x3, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x1e, 0x1e, 0, 0}, + {0x45, 0, 0, 0, 0}, + {0x46, 0x6e, 0x6e, 0, 0}, + {0x47, 0xf0, 0xf0, 1, 1}, + {0x48, 0, 0, 0, 0}, + {0x49, 0x2, 0x2, 0, 0}, + {0x4A, 0xff, 0xff, 1, 1}, + {0x4B, 0xc, 0xc, 0, 0}, + {0x4C, 0, 0, 0, 0}, + {0x4D, 0x38, 0x38, 0, 0}, + {0x4E, 0x70, 0x70, 1, 1}, + {0x4F, 0x2, 0x2, 0, 0}, + {0x50, 0x88, 0x88, 0, 0}, + {0x51, 0xc, 0xc, 0, 0}, + {0x52, 0, 0, 0, 0}, + {0x53, 0x8, 0x8, 0, 0}, + {0x54, 0x70, 0x70, 1, 1}, + {0x55, 0x2, 0x2, 0, 0}, + {0x56, 0xff, 0xff, 1, 1}, + {0x57, 0, 0, 0, 0}, + {0x58, 0x83, 0x83, 0, 0}, + {0x59, 0x77, 0x77, 1, 1}, + {0x5A, 0, 0, 0, 0}, + {0x5B, 0x2, 0x2, 0, 0}, + {0x5C, 0x88, 0x88, 0, 0}, + {0x5D, 0, 0, 0, 0}, + {0x5E, 0x8, 0x8, 0, 0}, + {0x5F, 0x77, 0x77, 1, 1}, + {0x60, 0x1, 0x1, 0, 0}, + {0x61, 0, 0, 0, 0}, + {0x62, 0x7, 0x7, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0x7, 0x7, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0x72, 0x72, 1, 1}, + {0x68, 0, 0, 0, 0}, + {0x69, 0xa, 0xa, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0, 0, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0, 0, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0, 0, 0, 0}, + {0x71, 0x2, 0x2, 0, 0}, + {0x72, 0, 0, 0, 0}, + {0x73, 0, 0, 0, 0}, + {0x74, 0xe, 0xe, 0, 0}, + {0x75, 0xe, 0xe, 0, 0}, + {0x76, 0xe, 0xe, 0, 0}, + {0x77, 0x13, 0x13, 0, 0}, + {0x78, 0x13, 0x13, 0, 0}, + {0x79, 0x1b, 0x1b, 0, 0}, + {0x7A, 0x1b, 0x1b, 0, 0}, + {0x7B, 0x55, 0x55, 0, 0}, + {0x7C, 0x5b, 0x5b, 0, 0}, + {0x7D, 0, 0, 0, 0}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0, 0, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0} +}; + +static struct radio_regs regs_RX_2056_A1[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0, 0, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0x3, 0x3, 0, 0}, + {0x21, 0, 0, 0, 0}, + {0x22, 0, 0, 0, 0}, + {0x23, 0x90, 0x90, 0, 0}, + {0x24, 0x55, 0x55, 0, 0}, + {0x25, 0x15, 0x15, 0, 0}, + {0x26, 0x5, 0x5, 0, 0}, + {0x27, 0x15, 0x15, 0, 0}, + {0x28, 0x5, 0x5, 0, 0}, + {0x29, 0x20, 0x20, 0, 0}, + {0x2A, 0x11, 0x11, 0, 0}, + {0x2B, 0x90, 0x90, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0x88, 0x88, 0, 0}, + {0x2E, 0x32, 0x32, 0, 0}, + {0x2F, 0x77, 0x77, 0, 0}, + {0x30, 0x17, 0x17, 1, 1}, + {0x31, 0xff, 0xff, 1, 1}, + {0x32, 0x20, 0x20, 0, 0}, + {0x33, 0, 0, 0, 0}, + {0x34, 0x88, 0x88, 0, 0}, + {0x35, 0x32, 0x32, 0, 0}, + {0x36, 0x77, 0x77, 0, 0}, + {0x37, 0x17, 0x17, 1, 1}, + {0x38, 0xf0, 0xf0, 1, 1}, + {0x39, 0x20, 0x20, 0, 0}, + {0x3A, 0x8, 0x8, 0, 0}, + {0x3B, 0x55, 0x55, 1, 1}, + {0x3C, 0, 0, 0, 0}, + {0x3D, 0x44, 0x44, 1, 1}, + {0x3E, 0, 0, 0, 0}, + {0x3F, 0x44, 0x44, 0, 0}, + {0x40, 0xf, 0xf, 1, 1}, + {0x41, 0x6, 0x6, 0, 0}, + {0x42, 0x4, 0x4, 0, 0}, + {0x43, 0x50, 0x50, 1, 1}, + {0x44, 0x8, 0x8, 0, 0}, + {0x45, 0x55, 0x55, 1, 1}, + {0x46, 0, 0, 0, 0}, + {0x47, 0x11, 0x11, 0, 0}, + {0x48, 0, 0, 0, 0}, + {0x49, 0x44, 0x44, 0, 0}, + {0x4A, 0x7, 0x7, 0, 0}, + {0x4B, 0x6, 0x6, 0, 0}, + {0x4C, 0x4, 0x4, 0, 0}, + {0x4D, 0, 0, 0, 0}, + {0x4E, 0, 0, 0, 0}, + {0x4F, 0x26, 0x26, 1, 1}, + {0x50, 0x26, 0x26, 1, 1}, + {0x51, 0xf, 0xf, 1, 1}, + {0x52, 0xf, 0xf, 1, 1}, + {0x53, 0x44, 0x44, 0, 0}, + {0x54, 0, 0, 0, 0}, + {0x55, 0, 0, 0, 0}, + {0x56, 0x8, 0x8, 0, 0}, + {0x57, 0x8, 0x8, 0, 0}, + {0x58, 0x7, 0x7, 0, 0}, + {0x59, 0x22, 0x22, 0, 0}, + {0x5A, 0x22, 0x22, 0, 0}, + {0x5B, 0x2, 0x2, 0, 0}, + {0x5C, 0x2f, 0x2f, 1, 1}, + {0x5D, 0x7, 0x7, 0, 0}, + {0x5E, 0x55, 0x55, 0, 0}, + {0x5F, 0x23, 0x23, 0, 0}, + {0x60, 0x41, 0x41, 0, 0}, + {0x61, 0x1, 0x1, 0, 0}, + {0x62, 0xa, 0xa, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0, 0, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 0, 0}, + {0x68, 0, 0, 0, 0}, + {0x69, 0, 0, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0xc, 0xc, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0, 0, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0, 0, 0, 0}, + {0x71, 0, 0, 0, 0}, + {0x72, 0x22, 0x22, 0, 0}, + {0x73, 0x22, 0x22, 0, 0}, + {0x74, 0, 0, 1, 1}, + {0x75, 0xa, 0xa, 0, 0}, + {0x76, 0x1, 0x1, 0, 0}, + {0x77, 0x22, 0x22, 0, 0}, + {0x78, 0x30, 0x30, 0, 0}, + {0x79, 0, 0, 0, 0}, + {0x7A, 0, 0, 0, 0}, + {0x7B, 0, 0, 0, 0}, + {0x7C, 0, 0, 0, 0}, + {0x7D, 0, 0, 0, 0}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0, 0, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0, 0, 0, 0}, + {0x94, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0} +}; + +static struct radio_regs regs_SYN_2056_rev5[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0x1, 0x1, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0, 0, 0, 0}, + {0x21, 0, 0, 0, 0}, + {0x22, 0x60, 0x60, 0, 0}, + {0x23, 0x6, 0x6, 0, 0}, + {0x24, 0xc, 0xc, 0, 0}, + {0x25, 0, 0, 0, 0}, + {0x26, 0, 0, 0, 0}, + {0x27, 0, 0, 0, 0}, + {0x28, 0x1, 0x1, 0, 0}, + {0x29, 0, 0, 0, 0}, + {0x2A, 0, 0, 0, 0}, + {0x2B, 0, 0, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0, 0, 0, 0}, + {0x2E, 0, 0, 0, 0}, + {0x2F, 0x1f, 0x1f, 0, 0}, + {0x30, 0x15, 0x15, 0, 0}, + {0x31, 0xf, 0xf, 0, 0}, + {0x32, 0, 0, 0, 0}, + {0x33, 0, 0, 0, 0}, + {0x34, 0, 0, 0, 0}, + {0x35, 0, 0, 0, 0}, + {0x36, 0, 0, 0, 0}, + {0x37, 0, 0, 0, 0}, + {0x38, 0, 0, 0, 0}, + {0x39, 0, 0, 0, 0}, + {0x3A, 0, 0, 0, 0}, + {0x3B, 0, 0, 0, 0}, + {0x3C, 0x13, 0x13, 0, 0}, + {0x3D, 0xf, 0xf, 0, 0}, + {0x3E, 0x18, 0x18, 0, 0}, + {0x3F, 0, 0, 0, 0}, + {0x40, 0, 0, 0, 0}, + {0x41, 0x20, 0x20, 0, 0}, + {0x42, 0x20, 0x20, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x77, 0x77, 0, 0}, + {0x45, 0x7, 0x7, 0, 0}, + {0x46, 0x1, 0x1, 0, 0}, + {0x47, 0x4, 0x4, 0, 0}, + {0x48, 0xf, 0xf, 0, 0}, + {0x49, 0x30, 0x30, 0, 0}, + {0x4A, 0x32, 0x32, 0, 0}, + {0x4B, 0xd, 0xd, 0, 0}, + {0x4C, 0xd, 0xd, 0, 0}, + {0x4D, 0x4, 0x4, 0, 0}, + {0x4E, 0x6, 0x6, 0, 0}, + {0x4F, 0x1, 0x1, 0, 0}, + {0x50, 0x1c, 0x1c, 0, 0}, + {0x51, 0x2, 0x2, 0, 0}, + {0x52, 0x2, 0x2, 0, 0}, + {0x53, 0xf7, 0xf7, 1, 1}, + {0x54, 0xb4, 0xb4, 0, 0}, + {0x55, 0xd2, 0xd2, 0, 0}, + {0x56, 0, 0, 0, 0}, + {0x57, 0, 0, 0, 0}, + {0x58, 0x4, 0x4, 0, 0}, + {0x59, 0x96, 0x96, 0, 0}, + {0x5A, 0x3e, 0x3e, 0, 0}, + {0x5B, 0x3e, 0x3e, 0, 0}, + {0x5C, 0x13, 0x13, 0, 0}, + {0x5D, 0x2, 0x2, 0, 0}, + {0x5E, 0, 0, 0, 0}, + {0x5F, 0x7, 0x7, 0, 0}, + {0x60, 0x7, 0x7, 1, 1}, + {0x61, 0x8, 0x8, 0, 0}, + {0x62, 0x3, 0x3, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0, 0, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 0, 0}, + {0x68, 0x40, 0x40, 0, 0}, + {0x69, 0, 0, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0, 0, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0x1, 0x1, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0x60, 0x60, 0, 0}, + {0x71, 0x66, 0x66, 0, 0}, + {0x72, 0xc, 0xc, 0, 0}, + {0x73, 0x66, 0x66, 0, 0}, + {0x74, 0x8f, 0x8f, 1, 1}, + {0x75, 0, 0, 0, 0}, + {0x76, 0xcc, 0xcc, 0, 0}, + {0x77, 0x1, 0x1, 0, 0}, + {0x78, 0x66, 0x66, 0, 0}, + {0x79, 0x66, 0x66, 0, 0}, + {0x7A, 0, 0, 0, 0}, + {0x7B, 0, 0, 0, 0}, + {0x7C, 0, 0, 0, 0}, + {0x7D, 0, 0, 0, 0}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0xff, 0xff, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0, 0, 0, 0}, + {0x94, 0, 0, 0, 0}, + {0x95, 0, 0, 0, 0}, + {0x96, 0, 0, 0, 0}, + {0x97, 0, 0, 0, 0}, + {0x98, 0, 0, 0, 0}, + {0x99, 0, 0, 0, 0}, + {0x9A, 0, 0, 0, 0}, + {0x9B, 0, 0, 0, 0}, + {0x9C, 0, 0, 0, 0}, + {0x9D, 0, 0, 0, 0}, + {0x9E, 0, 0, 0, 0}, + {0x9F, 0x6, 0x6, 0, 0}, + {0xA0, 0x66, 0x66, 0, 0}, + {0xA1, 0x66, 0x66, 0, 0}, + {0xA2, 0x66, 0x66, 0, 0}, + {0xA3, 0x66, 0x66, 0, 0}, + {0xA4, 0x66, 0x66, 0, 0}, + {0xA5, 0x66, 0x66, 0, 0}, + {0xA6, 0x66, 0x66, 0, 0}, + {0xA7, 0x66, 0x66, 0, 0}, + {0xA8, 0x66, 0x66, 0, 0}, + {0xA9, 0x66, 0x66, 0, 0}, + {0xAA, 0x66, 0x66, 0, 0}, + {0xAB, 0x66, 0x66, 0, 0}, + {0xAC, 0x66, 0x66, 0, 0}, + {0xAD, 0x66, 0x66, 0, 0}, + {0xAE, 0x66, 0x66, 0, 0}, + {0xAF, 0x66, 0x66, 0, 0}, + {0xB0, 0x66, 0x66, 0, 0}, + {0xB1, 0x66, 0x66, 0, 0}, + {0xB2, 0x66, 0x66, 0, 0}, + {0xB3, 0xa, 0xa, 0, 0}, + {0xB4, 0, 0, 0, 0}, + {0xB5, 0, 0, 0, 0}, + {0xB6, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0} +}; + +static struct radio_regs regs_TX_2056_rev5[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0, 0, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0, 0, 0, 0}, + {0x21, 0x88, 0x88, 0, 0}, + {0x22, 0x88, 0x88, 0, 0}, + {0x23, 0x88, 0x88, 0, 0}, + {0x24, 0x88, 0x88, 0, 0}, + {0x25, 0xc, 0xc, 0, 0}, + {0x26, 0, 0, 0, 0}, + {0x27, 0x3, 0x3, 0, 0}, + {0x28, 0, 0, 0, 0}, + {0x29, 0x3, 0x3, 0, 0}, + {0x2A, 0x37, 0x37, 0, 0}, + {0x2B, 0x3, 0x3, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0, 0, 0, 0}, + {0x2E, 0x1, 0x1, 0, 0}, + {0x2F, 0x1, 0x1, 0, 0}, + {0x30, 0, 0, 0, 0}, + {0x31, 0, 0, 0, 0}, + {0x32, 0, 0, 0, 0}, + {0x33, 0x11, 0x11, 0, 0}, + {0x34, 0x11, 0x11, 0, 0}, + {0x35, 0, 0, 0, 0}, + {0x36, 0, 0, 0, 0}, + {0x37, 0x3, 0x3, 0, 0}, + {0x38, 0xf, 0xf, 0, 0}, + {0x39, 0, 0, 0, 0}, + {0x3A, 0x2d, 0x2d, 0, 0}, + {0x3B, 0, 0, 0, 0}, + {0x3C, 0x6e, 0x6e, 0, 0}, + {0x3D, 0xf0, 0xf0, 1, 1}, + {0x3E, 0, 0, 0, 0}, + {0x3F, 0, 0, 0, 0}, + {0x40, 0, 0, 0, 0}, + {0x41, 0x3, 0x3, 0, 0}, + {0x42, 0x3, 0x3, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x1e, 0x1e, 0, 0}, + {0x45, 0, 0, 0, 0}, + {0x46, 0x6e, 0x6e, 0, 0}, + {0x47, 0xf0, 0xf0, 1, 1}, + {0x48, 0, 0, 0, 0}, + {0x49, 0x2, 0x2, 0, 0}, + {0x4A, 0xff, 0xff, 1, 1}, + {0x4B, 0xc, 0xc, 0, 0}, + {0x4C, 0, 0, 0, 0}, + {0x4D, 0x38, 0x38, 0, 0}, + {0x4E, 0x70, 0x70, 1, 1}, + {0x4F, 0x2, 0x2, 0, 0}, + {0x50, 0x88, 0x88, 0, 0}, + {0x51, 0xc, 0xc, 0, 0}, + {0x52, 0, 0, 0, 0}, + {0x53, 0x8, 0x8, 0, 0}, + {0x54, 0x70, 0x70, 1, 1}, + {0x55, 0x2, 0x2, 0, 0}, + {0x56, 0xff, 0xff, 1, 1}, + {0x57, 0, 0, 0, 0}, + {0x58, 0x83, 0x83, 0, 0}, + {0x59, 0x77, 0x77, 1, 1}, + {0x5A, 0, 0, 0, 0}, + {0x5B, 0x2, 0x2, 0, 0}, + {0x5C, 0x88, 0x88, 0, 0}, + {0x5D, 0, 0, 0, 0}, + {0x5E, 0x8, 0x8, 0, 0}, + {0x5F, 0x77, 0x77, 1, 1}, + {0x60, 0x1, 0x1, 0, 0}, + {0x61, 0, 0, 0, 0}, + {0x62, 0x7, 0x7, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0x7, 0x7, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 1, 1}, + {0x68, 0, 0, 0, 0}, + {0x69, 0xa, 0xa, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0, 0, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0, 0, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0, 0, 0, 0}, + {0x71, 0x2, 0x2, 0, 0}, + {0x72, 0, 0, 0, 0}, + {0x73, 0, 0, 0, 0}, + {0x74, 0xe, 0xe, 0, 0}, + {0x75, 0xe, 0xe, 0, 0}, + {0x76, 0xe, 0xe, 0, 0}, + {0x77, 0x13, 0x13, 0, 0}, + {0x78, 0x13, 0x13, 0, 0}, + {0x79, 0x1b, 0x1b, 0, 0}, + {0x7A, 0x1b, 0x1b, 0, 0}, + {0x7B, 0x55, 0x55, 0, 0}, + {0x7C, 0x5b, 0x5b, 0, 0}, + {0x7D, 0, 0, 0, 0}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0, 0, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0x70, 0x70, 0, 0}, + {0x94, 0x70, 0x70, 0, 0}, + {0x95, 0x71, 0x71, 1, 1}, + {0x96, 0x71, 0x71, 1, 1}, + {0x97, 0x72, 0x72, 1, 1}, + {0x98, 0x73, 0x73, 1, 1}, + {0x99, 0x74, 0x74, 1, 1}, + {0x9A, 0x75, 0x75, 1, 1}, + {0xFFFF, 0, 0, 0, 0} +}; + +static struct radio_regs regs_RX_2056_rev5[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0, 0, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0x3, 0x3, 0, 0}, + {0x21, 0, 0, 0, 0}, + {0x22, 0, 0, 0, 0}, + {0x23, 0x90, 0x90, 0, 0}, + {0x24, 0x55, 0x55, 0, 0}, + {0x25, 0x15, 0x15, 0, 0}, + {0x26, 0x5, 0x5, 0, 0}, + {0x27, 0x15, 0x15, 0, 0}, + {0x28, 0x5, 0x5, 0, 0}, + {0x29, 0x20, 0x20, 0, 0}, + {0x2A, 0x11, 0x11, 0, 0}, + {0x2B, 0x90, 0x90, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0x88, 0x88, 0, 0}, + {0x2E, 0x32, 0x32, 0, 0}, + {0x2F, 0x77, 0x77, 0, 0}, + {0x30, 0x17, 0x17, 1, 1}, + {0x31, 0xff, 0xff, 1, 1}, + {0x32, 0x20, 0x20, 0, 0}, + {0x33, 0, 0, 0, 0}, + {0x34, 0x88, 0x88, 0, 0}, + {0x35, 0x32, 0x32, 0, 0}, + {0x36, 0x77, 0x77, 0, 0}, + {0x37, 0x17, 0x17, 1, 1}, + {0x38, 0xf0, 0xf0, 1, 1}, + {0x39, 0x20, 0x20, 0, 0}, + {0x3A, 0x8, 0x8, 0, 0}, + {0x3B, 0x55, 0x55, 1, 1}, + {0x3C, 0, 0, 0, 0}, + {0x3D, 0x88, 0x88, 1, 1}, + {0x3E, 0, 0, 0, 0}, + {0x3F, 0, 0, 1, 1}, + {0x40, 0x7, 0x7, 1, 1}, + {0x41, 0x6, 0x6, 0, 0}, + {0x42, 0x4, 0x4, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x8, 0x8, 0, 0}, + {0x45, 0x55, 0x55, 1, 1}, + {0x46, 0, 0, 0, 0}, + {0x47, 0x11, 0x11, 0, 0}, + {0x48, 0, 0, 0, 0}, + {0x49, 0, 0, 1, 1}, + {0x4A, 0x7, 0x7, 0, 0}, + {0x4B, 0x6, 0x6, 0, 0}, + {0x4C, 0x4, 0x4, 0, 0}, + {0x4D, 0, 0, 0, 0}, + {0x4E, 0, 0, 0, 0}, + {0x4F, 0x26, 0x26, 1, 1}, + {0x50, 0x26, 0x26, 1, 1}, + {0x51, 0xf, 0xf, 1, 1}, + {0x52, 0xf, 0xf, 1, 1}, + {0x53, 0x44, 0x44, 0, 0}, + {0x54, 0, 0, 0, 0}, + {0x55, 0, 0, 0, 0}, + {0x56, 0x8, 0x8, 0, 0}, + {0x57, 0x8, 0x8, 0, 0}, + {0x58, 0x7, 0x7, 0, 0}, + {0x59, 0x22, 0x22, 0, 0}, + {0x5A, 0x22, 0x22, 0, 0}, + {0x5B, 0x2, 0x2, 0, 0}, + {0x5C, 0x4, 0x4, 1, 1}, + {0x5D, 0x7, 0x7, 0, 0}, + {0x5E, 0x55, 0x55, 0, 0}, + {0x5F, 0x23, 0x23, 0, 0}, + {0x60, 0x41, 0x41, 0, 0}, + {0x61, 0x1, 0x1, 0, 0}, + {0x62, 0xa, 0xa, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0, 0, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 0, 0}, + {0x68, 0, 0, 0, 0}, + {0x69, 0, 0, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0xc, 0xc, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0, 0, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0, 0, 0, 0}, + {0x71, 0, 0, 0, 0}, + {0x72, 0x22, 0x22, 0, 0}, + {0x73, 0x22, 0x22, 0, 0}, + {0x74, 0, 0, 1, 1}, + {0x75, 0xa, 0xa, 0, 0}, + {0x76, 0x1, 0x1, 0, 0}, + {0x77, 0x22, 0x22, 0, 0}, + {0x78, 0x30, 0x30, 0, 0}, + {0x79, 0, 0, 0, 0}, + {0x7A, 0, 0, 0, 0}, + {0x7B, 0, 0, 0, 0}, + {0x7C, 0, 0, 0, 0}, + {0x7D, 0, 0, 0, 0}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0, 0, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0, 0, 0, 0}, + {0x94, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0} +}; + +static struct radio_regs regs_SYN_2056_rev6[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0x1, 0x1, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0, 0, 0, 0}, + {0x21, 0, 0, 0, 0}, + {0x22, 0x60, 0x60, 0, 0}, + {0x23, 0x6, 0x6, 0, 0}, + {0x24, 0xc, 0xc, 0, 0}, + {0x25, 0, 0, 0, 0}, + {0x26, 0, 0, 0, 0}, + {0x27, 0, 0, 0, 0}, + {0x28, 0x1, 0x1, 0, 0}, + {0x29, 0, 0, 0, 0}, + {0x2A, 0, 0, 0, 0}, + {0x2B, 0, 0, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0, 0, 0, 0}, + {0x2E, 0, 0, 0, 0}, + {0x2F, 0x1f, 0x1f, 0, 0}, + {0x30, 0x15, 0x15, 0, 0}, + {0x31, 0xf, 0xf, 0, 0}, + {0x32, 0, 0, 0, 0}, + {0x33, 0, 0, 0, 0}, + {0x34, 0, 0, 0, 0}, + {0x35, 0, 0, 0, 0}, + {0x36, 0, 0, 0, 0}, + {0x37, 0, 0, 0, 0}, + {0x38, 0, 0, 0, 0}, + {0x39, 0, 0, 0, 0}, + {0x3A, 0, 0, 0, 0}, + {0x3B, 0, 0, 0, 0}, + {0x3C, 0x13, 0x13, 0, 0}, + {0x3D, 0xf, 0xf, 0, 0}, + {0x3E, 0x18, 0x18, 0, 0}, + {0x3F, 0, 0, 0, 0}, + {0x40, 0, 0, 0, 0}, + {0x41, 0x20, 0x20, 0, 0}, + {0x42, 0x20, 0x20, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x77, 0x77, 0, 0}, + {0x45, 0x7, 0x7, 0, 0}, + {0x46, 0x1, 0x1, 0, 0}, + {0x47, 0x4, 0x4, 0, 0}, + {0x48, 0xf, 0xf, 0, 0}, + {0x49, 0x30, 0x30, 0, 0}, + {0x4A, 0x32, 0x32, 0, 0}, + {0x4B, 0xd, 0xd, 0, 0}, + {0x4C, 0xd, 0xd, 0, 0}, + {0x4D, 0x4, 0x4, 0, 0}, + {0x4E, 0x6, 0x6, 0, 0}, + {0x4F, 0x1, 0x1, 0, 0}, + {0x50, 0x1c, 0x1c, 0, 0}, + {0x51, 0x2, 0x2, 0, 0}, + {0x52, 0x2, 0x2, 0, 0}, + {0x53, 0xf7, 0xf7, 1, 1}, + {0x54, 0xb4, 0xb4, 0, 0}, + {0x55, 0xd2, 0xd2, 0, 0}, + {0x56, 0, 0, 0, 0}, + {0x57, 0, 0, 0, 0}, + {0x58, 0x4, 0x4, 0, 0}, + {0x59, 0x96, 0x96, 0, 0}, + {0x5A, 0x3e, 0x3e, 0, 0}, + {0x5B, 0x3e, 0x3e, 0, 0}, + {0x5C, 0x13, 0x13, 0, 0}, + {0x5D, 0x2, 0x2, 0, 0}, + {0x5E, 0, 0, 0, 0}, + {0x5F, 0x7, 0x7, 0, 0}, + {0x60, 0x7, 0x7, 1, 1}, + {0x61, 0x8, 0x8, 0, 0}, + {0x62, 0x3, 0x3, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0, 0, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 0, 0}, + {0x68, 0x40, 0x40, 0, 0}, + {0x69, 0, 0, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0, 0, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0x1, 0x1, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0x60, 0x60, 0, 0}, + {0x71, 0x66, 0x66, 0, 0}, + {0x72, 0xc, 0xc, 0, 0}, + {0x73, 0x66, 0x66, 0, 0}, + {0x74, 0x8f, 0x8f, 1, 1}, + {0x75, 0, 0, 0, 0}, + {0x76, 0xcc, 0xcc, 0, 0}, + {0x77, 0x1, 0x1, 0, 0}, + {0x78, 0x66, 0x66, 0, 0}, + {0x79, 0x66, 0x66, 0, 0}, + {0x7A, 0, 0, 0, 0}, + {0x7B, 0, 0, 0, 0}, + {0x7C, 0, 0, 0, 0}, + {0x7D, 0, 0, 0, 0}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0xff, 0xff, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0, 0, 0, 0}, + {0x94, 0, 0, 0, 0}, + {0x95, 0, 0, 0, 0}, + {0x96, 0, 0, 0, 0}, + {0x97, 0, 0, 0, 0}, + {0x98, 0, 0, 0, 0}, + {0x99, 0, 0, 0, 0}, + {0x9A, 0, 0, 0, 0}, + {0x9B, 0, 0, 0, 0}, + {0x9C, 0, 0, 0, 0}, + {0x9D, 0, 0, 0, 0}, + {0x9E, 0, 0, 0, 0}, + {0x9F, 0x6, 0x6, 0, 0}, + {0xA0, 0x66, 0x66, 0, 0}, + {0xA1, 0x66, 0x66, 0, 0}, + {0xA2, 0x66, 0x66, 0, 0}, + {0xA3, 0x66, 0x66, 0, 0}, + {0xA4, 0x66, 0x66, 0, 0}, + {0xA5, 0x66, 0x66, 0, 0}, + {0xA6, 0x66, 0x66, 0, 0}, + {0xA7, 0x66, 0x66, 0, 0}, + {0xA8, 0x66, 0x66, 0, 0}, + {0xA9, 0x66, 0x66, 0, 0}, + {0xAA, 0x66, 0x66, 0, 0}, + {0xAB, 0x66, 0x66, 0, 0}, + {0xAC, 0x66, 0x66, 0, 0}, + {0xAD, 0x66, 0x66, 0, 0}, + {0xAE, 0x66, 0x66, 0, 0}, + {0xAF, 0x66, 0x66, 0, 0}, + {0xB0, 0x66, 0x66, 0, 0}, + {0xB1, 0x66, 0x66, 0, 0}, + {0xB2, 0x66, 0x66, 0, 0}, + {0xB3, 0xa, 0xa, 0, 0}, + {0xB4, 0, 0, 0, 0}, + {0xB5, 0, 0, 0, 0}, + {0xB6, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0} +}; + +static struct radio_regs regs_TX_2056_rev6[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0, 0, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0, 0, 0, 0}, + {0x21, 0x88, 0x88, 0, 0}, + {0x22, 0x88, 0x88, 0, 0}, + {0x23, 0x88, 0x88, 0, 0}, + {0x24, 0x88, 0x88, 0, 0}, + {0x25, 0xc, 0xc, 0, 0}, + {0x26, 0, 0, 0, 0}, + {0x27, 0x3, 0x3, 0, 0}, + {0x28, 0, 0, 0, 0}, + {0x29, 0x3, 0x3, 0, 0}, + {0x2A, 0x37, 0x37, 0, 0}, + {0x2B, 0x3, 0x3, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0, 0, 0, 0}, + {0x2E, 0x1, 0x1, 0, 0}, + {0x2F, 0x1, 0x1, 0, 0}, + {0x30, 0, 0, 0, 0}, + {0x31, 0, 0, 0, 0}, + {0x32, 0, 0, 0, 0}, + {0x33, 0x11, 0x11, 0, 0}, + {0x34, 0xee, 0xee, 1, 1}, + {0x35, 0, 0, 0, 0}, + {0x36, 0, 0, 0, 0}, + {0x37, 0x3, 0x3, 0, 0}, + {0x38, 0x50, 0x50, 1, 1}, + {0x39, 0, 0, 0, 0}, + {0x3A, 0x50, 0x50, 1, 1}, + {0x3B, 0, 0, 0, 0}, + {0x3C, 0x6e, 0x6e, 0, 0}, + {0x3D, 0xf0, 0xf0, 1, 1}, + {0x3E, 0, 0, 0, 0}, + {0x3F, 0, 0, 0, 0}, + {0x40, 0, 0, 0, 0}, + {0x41, 0x3, 0x3, 0, 0}, + {0x42, 0x3, 0x3, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x1e, 0x1e, 0, 0}, + {0x45, 0, 0, 0, 0}, + {0x46, 0x6e, 0x6e, 0, 0}, + {0x47, 0xf0, 0xf0, 1, 1}, + {0x48, 0, 0, 0, 0}, + {0x49, 0x2, 0x2, 0, 0}, + {0x4A, 0xff, 0xff, 1, 1}, + {0x4B, 0xc, 0xc, 0, 0}, + {0x4C, 0, 0, 0, 0}, + {0x4D, 0x38, 0x38, 0, 0}, + {0x4E, 0x70, 0x70, 1, 1}, + {0x4F, 0x2, 0x2, 0, 0}, + {0x50, 0x88, 0x88, 0, 0}, + {0x51, 0xc, 0xc, 0, 0}, + {0x52, 0, 0, 0, 0}, + {0x53, 0x8, 0x8, 0, 0}, + {0x54, 0x70, 0x70, 1, 1}, + {0x55, 0x2, 0x2, 0, 0}, + {0x56, 0xff, 0xff, 1, 1}, + {0x57, 0, 0, 0, 0}, + {0x58, 0x83, 0x83, 0, 0}, + {0x59, 0x77, 0x77, 1, 1}, + {0x5A, 0, 0, 0, 0}, + {0x5B, 0x2, 0x2, 0, 0}, + {0x5C, 0x88, 0x88, 0, 0}, + {0x5D, 0, 0, 0, 0}, + {0x5E, 0x8, 0x8, 0, 0}, + {0x5F, 0x77, 0x77, 1, 1}, + {0x60, 0x1, 0x1, 0, 0}, + {0x61, 0, 0, 0, 0}, + {0x62, 0x7, 0x7, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0x7, 0x7, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 1, 1}, + {0x68, 0, 0, 0, 0}, + {0x69, 0xa, 0xa, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0, 0, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0, 0, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0, 0, 0, 0}, + {0x71, 0x2, 0x2, 0, 0}, + {0x72, 0, 0, 0, 0}, + {0x73, 0, 0, 0, 0}, + {0x74, 0xe, 0xe, 0, 0}, + {0x75, 0xe, 0xe, 0, 0}, + {0x76, 0xe, 0xe, 0, 0}, + {0x77, 0x13, 0x13, 0, 0}, + {0x78, 0x13, 0x13, 0, 0}, + {0x79, 0x1b, 0x1b, 0, 0}, + {0x7A, 0x1b, 0x1b, 0, 0}, + {0x7B, 0x55, 0x55, 0, 0}, + {0x7C, 0x5b, 0x5b, 0, 0}, + {0x7D, 0x30, 0x30, 1, 1}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0, 0, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0x70, 0x70, 0, 0}, + {0x94, 0x70, 0x70, 0, 0}, + {0x95, 0x70, 0x70, 0, 0}, + {0x96, 0x70, 0x70, 0, 0}, + {0x97, 0x70, 0x70, 0, 0}, + {0x98, 0x70, 0x70, 0, 0}, + {0x99, 0x70, 0x70, 0, 0}, + {0x9A, 0x70, 0x70, 0, 0}, + {0xFFFF, 0, 0, 0, 0} +}; + +static struct radio_regs regs_RX_2056_rev6[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0, 0, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0x3, 0x3, 0, 0}, + {0x21, 0, 0, 0, 0}, + {0x22, 0, 0, 0, 0}, + {0x23, 0x90, 0x90, 0, 0}, + {0x24, 0x55, 0x55, 0, 0}, + {0x25, 0x15, 0x15, 0, 0}, + {0x26, 0x5, 0x5, 0, 0}, + {0x27, 0x15, 0x15, 0, 0}, + {0x28, 0x5, 0x5, 0, 0}, + {0x29, 0x20, 0x20, 0, 0}, + {0x2A, 0x11, 0x11, 0, 0}, + {0x2B, 0x90, 0x90, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0x88, 0x88, 0, 0}, + {0x2E, 0x32, 0x32, 0, 0}, + {0x2F, 0x77, 0x77, 0, 0}, + {0x30, 0x17, 0x17, 1, 1}, + {0x31, 0xff, 0xff, 1, 1}, + {0x32, 0x20, 0x20, 0, 0}, + {0x33, 0, 0, 0, 0}, + {0x34, 0x88, 0x88, 0, 0}, + {0x35, 0x32, 0x32, 0, 0}, + {0x36, 0x77, 0x77, 0, 0}, + {0x37, 0x17, 0x17, 1, 1}, + {0x38, 0xf0, 0xf0, 1, 1}, + {0x39, 0x20, 0x20, 0, 0}, + {0x3A, 0x8, 0x8, 0, 0}, + {0x3B, 0x55, 0x55, 1, 1}, + {0x3C, 0, 0, 0, 0}, + {0x3D, 0x88, 0x88, 1, 1}, + {0x3E, 0, 0, 0, 0}, + {0x3F, 0x44, 0x44, 0, 0}, + {0x40, 0x7, 0x7, 1, 1}, + {0x41, 0x6, 0x6, 0, 0}, + {0x42, 0x4, 0x4, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x8, 0x8, 0, 0}, + {0x45, 0x55, 0x55, 1, 1}, + {0x46, 0, 0, 0, 0}, + {0x47, 0x11, 0x11, 0, 0}, + {0x48, 0, 0, 0, 0}, + {0x49, 0x44, 0x44, 0, 0}, + {0x4A, 0x7, 0x7, 0, 0}, + {0x4B, 0x6, 0x6, 0, 0}, + {0x4C, 0x4, 0x4, 0, 0}, + {0x4D, 0, 0, 0, 0}, + {0x4E, 0, 0, 0, 0}, + {0x4F, 0x26, 0x26, 1, 1}, + {0x50, 0x26, 0x26, 1, 1}, + {0x51, 0xf, 0xf, 1, 1}, + {0x52, 0xf, 0xf, 1, 1}, + {0x53, 0x44, 0x44, 0, 0}, + {0x54, 0, 0, 0, 0}, + {0x55, 0, 0, 0, 0}, + {0x56, 0x8, 0x8, 0, 0}, + {0x57, 0x8, 0x8, 0, 0}, + {0x58, 0x7, 0x7, 0, 0}, + {0x59, 0x22, 0x22, 0, 0}, + {0x5A, 0x22, 0x22, 0, 0}, + {0x5B, 0x2, 0x2, 0, 0}, + {0x5C, 0x4, 0x4, 1, 1}, + {0x5D, 0x7, 0x7, 0, 0}, + {0x5E, 0x55, 0x55, 0, 0}, + {0x5F, 0x23, 0x23, 0, 0}, + {0x60, 0x41, 0x41, 0, 0}, + {0x61, 0x1, 0x1, 0, 0}, + {0x62, 0xa, 0xa, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0, 0, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 0, 0}, + {0x68, 0, 0, 0, 0}, + {0x69, 0, 0, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0xc, 0xc, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0, 0, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0, 0, 0, 0}, + {0x71, 0, 0, 0, 0}, + {0x72, 0x22, 0x22, 0, 0}, + {0x73, 0x22, 0x22, 0, 0}, + {0x74, 0, 0, 1, 1}, + {0x75, 0xa, 0xa, 0, 0}, + {0x76, 0x1, 0x1, 0, 0}, + {0x77, 0x22, 0x22, 0, 0}, + {0x78, 0x30, 0x30, 0, 0}, + {0x79, 0, 0, 0, 0}, + {0x7A, 0, 0, 0, 0}, + {0x7B, 0, 0, 0, 0}, + {0x7C, 0, 0, 0, 0}, + {0x7D, 0x5, 0x5, 1, 1}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0, 0, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0, 0, 0, 0}, + {0x94, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0} +}; + +static struct radio_regs regs_SYN_2056_rev7[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0x1, 0x1, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0, 0, 0, 0}, + {0x21, 0, 0, 0, 0}, + {0x22, 0x60, 0x60, 0, 0}, + {0x23, 0x6, 0x6, 0, 0}, + {0x24, 0xc, 0xc, 0, 0}, + {0x25, 0, 0, 0, 0}, + {0x26, 0, 0, 0, 0}, + {0x27, 0, 0, 0, 0}, + {0x28, 0x1, 0x1, 0, 0}, + {0x29, 0, 0, 0, 0}, + {0x2A, 0, 0, 0, 0}, + {0x2B, 0, 0, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0, 0, 0, 0}, + {0x2E, 0, 0, 0, 0}, + {0x2F, 0x1f, 0x1f, 0, 0}, + {0x30, 0x15, 0x15, 0, 0}, + {0x31, 0xf, 0xf, 0, 0}, + {0x32, 0, 0, 0, 0}, + {0x33, 0, 0, 0, 0}, + {0x34, 0, 0, 0, 0}, + {0x35, 0, 0, 0, 0}, + {0x36, 0, 0, 0, 0}, + {0x37, 0, 0, 0, 0}, + {0x38, 0, 0, 0, 0}, + {0x39, 0, 0, 0, 0}, + {0x3A, 0, 0, 0, 0}, + {0x3B, 0, 0, 0, 0}, + {0x3C, 0x13, 0x13, 0, 0}, + {0x3D, 0xf, 0xf, 0, 0}, + {0x3E, 0x18, 0x18, 0, 0}, + {0x3F, 0, 0, 0, 0}, + {0x40, 0, 0, 0, 0}, + {0x41, 0x20, 0x20, 0, 0}, + {0x42, 0x20, 0x20, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x77, 0x77, 0, 0}, + {0x45, 0x7, 0x7, 0, 0}, + {0x46, 0x1, 0x1, 0, 0}, + {0x47, 0x4, 0x4, 0, 0}, + {0x48, 0xf, 0xf, 0, 0}, + {0x49, 0x30, 0x30, 0, 0}, + {0x4A, 0x32, 0x32, 0, 0}, + {0x4B, 0xd, 0xd, 0, 0}, + {0x4C, 0xd, 0xd, 0, 0}, + {0x4D, 0x4, 0x4, 0, 0}, + {0x4E, 0x6, 0x6, 0, 0}, + {0x4F, 0x1, 0x1, 0, 0}, + {0x50, 0x1c, 0x1c, 0, 0}, + {0x51, 0x2, 0x2, 0, 0}, + {0x52, 0x2, 0x2, 0, 0}, + {0x53, 0xf7, 0xf7, 1, 1}, + {0x54, 0xb4, 0xb4, 0, 0}, + {0x55, 0xd2, 0xd2, 0, 0}, + {0x56, 0, 0, 0, 0}, + {0x57, 0, 0, 0, 0}, + {0x58, 0x4, 0x4, 0, 0}, + {0x59, 0x96, 0x96, 0, 0}, + {0x5A, 0x3e, 0x3e, 0, 0}, + {0x5B, 0x3e, 0x3e, 0, 0}, + {0x5C, 0x13, 0x13, 0, 0}, + {0x5D, 0x2, 0x2, 0, 0}, + {0x5E, 0, 0, 0, 0}, + {0x5F, 0x7, 0x7, 0, 0}, + {0x60, 0x7, 0x7, 1, 1}, + {0x61, 0x8, 0x8, 0, 0}, + {0x62, 0x3, 0x3, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0, 0, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 0, 0}, + {0x68, 0x40, 0x40, 0, 0}, + {0x69, 0, 0, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0, 0, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0x1, 0x1, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0x60, 0x60, 0, 0}, + {0x71, 0x66, 0x66, 0, 0}, + {0x72, 0xc, 0xc, 0, 0}, + {0x73, 0x66, 0x66, 0, 0}, + {0x74, 0x8f, 0x8f, 1, 1}, + {0x75, 0, 0, 0, 0}, + {0x76, 0xcc, 0xcc, 0, 0}, + {0x77, 0x1, 0x1, 0, 0}, + {0x78, 0x66, 0x66, 0, 0}, + {0x79, 0x66, 0x66, 0, 0}, + {0x7A, 0, 0, 0, 0}, + {0x7B, 0, 0, 0, 0}, + {0x7C, 0, 0, 0, 0}, + {0x7D, 0, 0, 0, 0}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0xff, 0xff, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0, 0, 0, 0}, + {0x94, 0, 0, 0, 0}, + {0x95, 0, 0, 0, 0}, + {0x96, 0, 0, 0, 0}, + {0x97, 0, 0, 0, 0}, + {0x98, 0, 0, 0, 0}, + {0x99, 0, 0, 0, 0}, + {0x9A, 0, 0, 0, 0}, + {0x9B, 0, 0, 0, 0}, + {0x9C, 0, 0, 0, 0}, + {0x9D, 0, 0, 0, 0}, + {0x9E, 0, 0, 0, 0}, + {0x9F, 0x6, 0x6, 0, 0}, + {0xA0, 0x66, 0x66, 0, 0}, + {0xA1, 0x66, 0x66, 0, 0}, + {0xA2, 0x66, 0x66, 0, 0}, + {0xA3, 0x66, 0x66, 0, 0}, + {0xA4, 0x66, 0x66, 0, 0}, + {0xA5, 0x66, 0x66, 0, 0}, + {0xA6, 0x66, 0x66, 0, 0}, + {0xA7, 0x66, 0x66, 0, 0}, + {0xA8, 0x66, 0x66, 0, 0}, + {0xA9, 0x66, 0x66, 0, 0}, + {0xAA, 0x66, 0x66, 0, 0}, + {0xAB, 0x66, 0x66, 0, 0}, + {0xAC, 0x66, 0x66, 0, 0}, + {0xAD, 0x66, 0x66, 0, 0}, + {0xAE, 0x66, 0x66, 0, 0}, + {0xAF, 0x66, 0x66, 0, 0}, + {0xB0, 0x66, 0x66, 0, 0}, + {0xB1, 0x66, 0x66, 0, 0}, + {0xB2, 0x66, 0x66, 0, 0}, + {0xB3, 0xa, 0xa, 0, 0}, + {0xB4, 0, 0, 0, 0}, + {0xB5, 0, 0, 0, 0}, + {0xB6, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0}, +}; + +static struct radio_regs regs_TX_2056_rev7[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0, 0, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0, 0, 0, 0}, + {0x21, 0x88, 0x88, 0, 0}, + {0x22, 0x88, 0x88, 0, 0}, + {0x23, 0x88, 0x88, 0, 0}, + {0x24, 0x88, 0x88, 0, 0}, + {0x25, 0xc, 0xc, 0, 0}, + {0x26, 0, 0, 0, 0}, + {0x27, 0x3, 0x3, 0, 0}, + {0x28, 0, 0, 0, 0}, + {0x29, 0x3, 0x3, 0, 0}, + {0x2A, 0x37, 0x37, 0, 0}, + {0x2B, 0x3, 0x3, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0, 0, 0, 0}, + {0x2E, 0x1, 0x1, 0, 0}, + {0x2F, 0x1, 0x1, 0, 0}, + {0x30, 0, 0, 0, 0}, + {0x31, 0, 0, 0, 0}, + {0x32, 0, 0, 0, 0}, + {0x33, 0x11, 0x11, 0, 0}, + {0x34, 0xee, 0xee, 1, 1}, + {0x35, 0, 0, 0, 0}, + {0x36, 0, 0, 0, 0}, + {0x37, 0x3, 0x3, 0, 0}, + {0x38, 0x50, 0x50, 1, 1}, + {0x39, 0, 0, 0, 0}, + {0x3A, 0x50, 0x50, 1, 1}, + {0x3B, 0, 0, 0, 0}, + {0x3C, 0x6e, 0x6e, 0, 0}, + {0x3D, 0xf0, 0xf0, 1, 1}, + {0x3E, 0, 0, 0, 0}, + {0x3F, 0, 0, 0, 0}, + {0x40, 0, 0, 0, 0}, + {0x41, 0x3, 0x3, 0, 0}, + {0x42, 0x3, 0x3, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x1e, 0x1e, 0, 0}, + {0x45, 0, 0, 0, 0}, + {0x46, 0x6e, 0x6e, 0, 0}, + {0x47, 0xf0, 0xf0, 1, 1}, + {0x48, 0, 0, 0, 0}, + {0x49, 0x2, 0x2, 0, 0}, + {0x4A, 0xff, 0xff, 1, 1}, + {0x4B, 0xc, 0xc, 0, 0}, + {0x4C, 0, 0, 0, 0}, + {0x4D, 0x38, 0x38, 0, 0}, + {0x4E, 0x70, 0x70, 1, 1}, + {0x4F, 0x2, 0x2, 0, 0}, + {0x50, 0x88, 0x88, 0, 0}, + {0x51, 0xc, 0xc, 0, 0}, + {0x52, 0, 0, 0, 0}, + {0x53, 0x8, 0x8, 0, 0}, + {0x54, 0x70, 0x70, 1, 1}, + {0x55, 0x2, 0x2, 0, 0}, + {0x56, 0xff, 0xff, 1, 1}, + {0x57, 0, 0, 0, 0}, + {0x58, 0x83, 0x83, 0, 0}, + {0x59, 0x77, 0x77, 1, 1}, + {0x5A, 0, 0, 0, 0}, + {0x5B, 0x2, 0x2, 0, 0}, + {0x5C, 0x88, 0x88, 0, 0}, + {0x5D, 0, 0, 0, 0}, + {0x5E, 0x8, 0x8, 0, 0}, + {0x5F, 0x77, 0x77, 1, 1}, + {0x60, 0x1, 0x1, 0, 0}, + {0x61, 0, 0, 0, 0}, + {0x62, 0x7, 0x7, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0x7, 0x7, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 1, 1}, + {0x68, 0, 0, 0, 0}, + {0x69, 0xa, 0xa, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0, 0, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0, 0, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0, 0, 0, 0}, + {0x71, 0x2, 0x2, 0, 0}, + {0x72, 0, 0, 0, 0}, + {0x73, 0, 0, 0, 0}, + {0x74, 0xe, 0xe, 0, 0}, + {0x75, 0xe, 0xe, 0, 0}, + {0x76, 0xe, 0xe, 0, 0}, + {0x77, 0x13, 0x13, 0, 0}, + {0x78, 0x13, 0x13, 0, 0}, + {0x79, 0x1b, 0x1b, 0, 0}, + {0x7A, 0x1b, 0x1b, 0, 0}, + {0x7B, 0x55, 0x55, 0, 0}, + {0x7C, 0x5b, 0x5b, 0, 0}, + {0x7D, 0x30, 0x30, 1, 1}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0, 0, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0x70, 0x70, 0, 0}, + {0x94, 0x70, 0x70, 0, 0}, + {0x95, 0x71, 0x71, 1, 1}, + {0x96, 0x71, 0x71, 1, 1}, + {0x97, 0x72, 0x72, 1, 1}, + {0x98, 0x73, 0x73, 1, 1}, + {0x99, 0x74, 0x74, 1, 1}, + {0x9A, 0x75, 0x75, 1, 1}, + {0xFFFF, 0, 0, 0, 0}, +}; + +static struct radio_regs regs_RX_2056_rev7[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0, 0, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0x3, 0x3, 0, 0}, + {0x21, 0, 0, 0, 0}, + {0x22, 0, 0, 0, 0}, + {0x23, 0x90, 0x90, 0, 0}, + {0x24, 0x55, 0x55, 0, 0}, + {0x25, 0x15, 0x15, 0, 0}, + {0x26, 0x5, 0x5, 0, 0}, + {0x27, 0x15, 0x15, 0, 0}, + {0x28, 0x5, 0x5, 0, 0}, + {0x29, 0x20, 0x20, 0, 0}, + {0x2A, 0x11, 0x11, 0, 0}, + {0x2B, 0x90, 0x90, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0x88, 0x88, 0, 0}, + {0x2E, 0x32, 0x32, 0, 0}, + {0x2F, 0x77, 0x77, 0, 0}, + {0x30, 0x17, 0x17, 1, 1}, + {0x31, 0xff, 0xff, 1, 1}, + {0x32, 0x20, 0x20, 0, 0}, + {0x33, 0, 0, 0, 0}, + {0x34, 0x88, 0x88, 0, 0}, + {0x35, 0x32, 0x32, 0, 0}, + {0x36, 0x77, 0x77, 0, 0}, + {0x37, 0x17, 0x17, 1, 1}, + {0x38, 0xf0, 0xf0, 1, 1}, + {0x39, 0x20, 0x20, 0, 0}, + {0x3A, 0x8, 0x8, 0, 0}, + {0x3B, 0x55, 0x55, 1, 1}, + {0x3C, 0, 0, 0, 0}, + {0x3D, 0x88, 0x88, 1, 1}, + {0x3E, 0, 0, 0, 0}, + {0x3F, 0, 0, 1, 1}, + {0x40, 0x7, 0x7, 1, 1}, + {0x41, 0x6, 0x6, 0, 0}, + {0x42, 0x4, 0x4, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x8, 0x8, 0, 0}, + {0x45, 0x55, 0x55, 1, 1}, + {0x46, 0, 0, 0, 0}, + {0x47, 0x11, 0x11, 0, 0}, + {0x48, 0, 0, 0, 0}, + {0x49, 0, 0, 1, 1}, + {0x4A, 0x7, 0x7, 0, 0}, + {0x4B, 0x6, 0x6, 0, 0}, + {0x4C, 0x4, 0x4, 0, 0}, + {0x4D, 0, 0, 0, 0}, + {0x4E, 0, 0, 0, 0}, + {0x4F, 0x26, 0x26, 1, 1}, + {0x50, 0x26, 0x26, 1, 1}, + {0x51, 0xf, 0xf, 1, 1}, + {0x52, 0xf, 0xf, 1, 1}, + {0x53, 0x44, 0x44, 0, 0}, + {0x54, 0, 0, 0, 0}, + {0x55, 0, 0, 0, 0}, + {0x56, 0x8, 0x8, 0, 0}, + {0x57, 0x8, 0x8, 0, 0}, + {0x58, 0x7, 0x7, 0, 0}, + {0x59, 0x22, 0x22, 0, 0}, + {0x5A, 0x22, 0x22, 0, 0}, + {0x5B, 0x2, 0x2, 0, 0}, + {0x5C, 0x4, 0x4, 1, 1}, + {0x5D, 0x7, 0x7, 0, 0}, + {0x5E, 0x55, 0x55, 0, 0}, + {0x5F, 0x23, 0x23, 0, 0}, + {0x60, 0x41, 0x41, 0, 0}, + {0x61, 0x1, 0x1, 0, 0}, + {0x62, 0xa, 0xa, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0, 0, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 0, 0}, + {0x68, 0, 0, 0, 0}, + {0x69, 0, 0, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0xc, 0xc, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0, 0, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0, 0, 0, 0}, + {0x71, 0, 0, 0, 0}, + {0x72, 0x22, 0x22, 0, 0}, + {0x73, 0x22, 0x22, 0, 0}, + {0x74, 0, 0, 1, 1}, + {0x75, 0xa, 0xa, 0, 0}, + {0x76, 0x1, 0x1, 0, 0}, + {0x77, 0x22, 0x22, 0, 0}, + {0x78, 0x30, 0x30, 0, 0}, + {0x79, 0, 0, 0, 0}, + {0x7A, 0, 0, 0, 0}, + {0x7B, 0, 0, 0, 0}, + {0x7C, 0, 0, 0, 0}, + {0x7D, 0, 0, 0, 0}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0, 0, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0, 0, 0, 0}, + {0x94, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0}, +}; + +static struct radio_regs regs_SYN_2056_rev8[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0x1, 0x1, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0, 0, 0, 0}, + {0x21, 0, 0, 0, 0}, + {0x22, 0x60, 0x60, 0, 0}, + {0x23, 0x6, 0x6, 0, 0}, + {0x24, 0xc, 0xc, 0, 0}, + {0x25, 0, 0, 0, 0}, + {0x26, 0, 0, 0, 0}, + {0x27, 0, 0, 0, 0}, + {0x28, 0x1, 0x1, 0, 0}, + {0x29, 0, 0, 0, 0}, + {0x2A, 0, 0, 0, 0}, + {0x2B, 0, 0, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0, 0, 0, 0}, + {0x2E, 0, 0, 0, 0}, + {0x2F, 0x1f, 0x1f, 0, 0}, + {0x30, 0x15, 0x15, 0, 0}, + {0x31, 0xf, 0xf, 0, 0}, + {0x32, 0, 0, 0, 0}, + {0x33, 0, 0, 0, 0}, + {0x34, 0, 0, 0, 0}, + {0x35, 0, 0, 0, 0}, + {0x36, 0, 0, 0, 0}, + {0x37, 0, 0, 0, 0}, + {0x38, 0, 0, 0, 0}, + {0x39, 0, 0, 0, 0}, + {0x3A, 0, 0, 0, 0}, + {0x3B, 0, 0, 0, 0}, + {0x3C, 0x13, 0x13, 0, 0}, + {0x3D, 0xf, 0xf, 0, 0}, + {0x3E, 0x18, 0x18, 0, 0}, + {0x3F, 0, 0, 0, 0}, + {0x40, 0, 0, 0, 0}, + {0x41, 0x20, 0x20, 0, 0}, + {0x42, 0x20, 0x20, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x77, 0x77, 0, 0}, + {0x45, 0x7, 0x7, 0, 0}, + {0x46, 0x1, 0x1, 0, 0}, + {0x47, 0x4, 0x4, 0, 0}, + {0x48, 0xf, 0xf, 0, 0}, + {0x49, 0x30, 0x30, 0, 0}, + {0x4A, 0x32, 0x32, 0, 0}, + {0x4B, 0xd, 0xd, 0, 0}, + {0x4C, 0xd, 0xd, 0, 0}, + {0x4D, 0x4, 0x4, 0, 0}, + {0x4E, 0x6, 0x6, 0, 0}, + {0x4F, 0x1, 0x1, 0, 0}, + {0x50, 0x1c, 0x1c, 0, 0}, + {0x51, 0x2, 0x2, 0, 0}, + {0x52, 0x2, 0x2, 0, 0}, + {0x53, 0xf7, 0xf7, 1, 1}, + {0x54, 0xb4, 0xb4, 0, 0}, + {0x55, 0xd2, 0xd2, 0, 0}, + {0x56, 0, 0, 0, 0}, + {0x57, 0, 0, 0, 0}, + {0x58, 0x4, 0x4, 0, 0}, + {0x59, 0x96, 0x96, 0, 0}, + {0x5A, 0x3e, 0x3e, 0, 0}, + {0x5B, 0x3e, 0x3e, 0, 0}, + {0x5C, 0x13, 0x13, 0, 0}, + {0x5D, 0x2, 0x2, 0, 0}, + {0x5E, 0, 0, 0, 0}, + {0x5F, 0x7, 0x7, 0, 0}, + {0x60, 0x7, 0x7, 1, 1}, + {0x61, 0x8, 0x8, 0, 0}, + {0x62, 0x3, 0x3, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0, 0, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 0, 0}, + {0x68, 0x40, 0x40, 0, 0}, + {0x69, 0, 0, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0, 0, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0x1, 0x1, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0x60, 0x60, 0, 0}, + {0x71, 0x66, 0x66, 0, 0}, + {0x72, 0xc, 0xc, 0, 0}, + {0x73, 0x66, 0x66, 0, 0}, + {0x74, 0x8f, 0x8f, 1, 1}, + {0x75, 0, 0, 0, 0}, + {0x76, 0xcc, 0xcc, 0, 0}, + {0x77, 0x1, 0x1, 0, 0}, + {0x78, 0x66, 0x66, 0, 0}, + {0x79, 0x66, 0x66, 0, 0}, + {0x7A, 0, 0, 0, 0}, + {0x7B, 0, 0, 0, 0}, + {0x7C, 0, 0, 0, 0}, + {0x7D, 0, 0, 0, 0}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0xff, 0xff, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0, 0, 0, 0}, + {0x94, 0, 0, 0, 0}, + {0x95, 0, 0, 0, 0}, + {0x96, 0, 0, 0, 0}, + {0x97, 0, 0, 0, 0}, + {0x98, 0, 0, 0, 0}, + {0x99, 0, 0, 0, 0}, + {0x9A, 0, 0, 0, 0}, + {0x9B, 0, 0, 0, 0}, + {0x9C, 0, 0, 0, 0}, + {0x9D, 0, 0, 0, 0}, + {0x9E, 0, 0, 0, 0}, + {0x9F, 0x6, 0x6, 0, 0}, + {0xA0, 0x66, 0x66, 0, 0}, + {0xA1, 0x66, 0x66, 0, 0}, + {0xA2, 0x66, 0x66, 0, 0}, + {0xA3, 0x66, 0x66, 0, 0}, + {0xA4, 0x66, 0x66, 0, 0}, + {0xA5, 0x66, 0x66, 0, 0}, + {0xA6, 0x66, 0x66, 0, 0}, + {0xA7, 0x66, 0x66, 0, 0}, + {0xA8, 0x66, 0x66, 0, 0}, + {0xA9, 0x66, 0x66, 0, 0}, + {0xAA, 0x66, 0x66, 0, 0}, + {0xAB, 0x66, 0x66, 0, 0}, + {0xAC, 0x66, 0x66, 0, 0}, + {0xAD, 0x66, 0x66, 0, 0}, + {0xAE, 0x66, 0x66, 0, 0}, + {0xAF, 0x66, 0x66, 0, 0}, + {0xB0, 0x66, 0x66, 0, 0}, + {0xB1, 0x66, 0x66, 0, 0}, + {0xB2, 0x66, 0x66, 0, 0}, + {0xB3, 0xa, 0xa, 0, 0}, + {0xB4, 0, 0, 0, 0}, + {0xB5, 0, 0, 0, 0}, + {0xB6, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0}, +}; + +static struct radio_regs regs_TX_2056_rev8[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0, 0, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0, 0, 0, 0}, + {0x21, 0x88, 0x88, 0, 0}, + {0x22, 0x88, 0x88, 0, 0}, + {0x23, 0x88, 0x88, 0, 0}, + {0x24, 0x88, 0x88, 0, 0}, + {0x25, 0xc, 0xc, 0, 0}, + {0x26, 0, 0, 0, 0}, + {0x27, 0x3, 0x3, 0, 0}, + {0x28, 0, 0, 0, 0}, + {0x29, 0x3, 0x3, 0, 0}, + {0x2A, 0x37, 0x37, 0, 0}, + {0x2B, 0x3, 0x3, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0, 0, 0, 0}, + {0x2E, 0x1, 0x1, 0, 0}, + {0x2F, 0x1, 0x1, 0, 0}, + {0x30, 0, 0, 0, 0}, + {0x31, 0, 0, 0, 0}, + {0x32, 0, 0, 0, 0}, + {0x33, 0x11, 0x11, 0, 0}, + {0x34, 0xee, 0xee, 1, 1}, + {0x35, 0, 0, 0, 0}, + {0x36, 0, 0, 0, 0}, + {0x37, 0x3, 0x3, 0, 0}, + {0x38, 0x50, 0x50, 1, 1}, + {0x39, 0, 0, 0, 0}, + {0x3A, 0x50, 0x50, 1, 1}, + {0x3B, 0, 0, 0, 0}, + {0x3C, 0x6e, 0x6e, 0, 0}, + {0x3D, 0xf0, 0xf0, 1, 1}, + {0x3E, 0, 0, 0, 0}, + {0x3F, 0, 0, 0, 0}, + {0x40, 0, 0, 0, 0}, + {0x41, 0x3, 0x3, 0, 0}, + {0x42, 0x3, 0x3, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x1e, 0x1e, 0, 0}, + {0x45, 0, 0, 0, 0}, + {0x46, 0x6e, 0x6e, 0, 0}, + {0x47, 0xf0, 0xf0, 1, 1}, + {0x48, 0, 0, 0, 0}, + {0x49, 0x2, 0x2, 0, 0}, + {0x4A, 0xff, 0xff, 1, 1}, + {0x4B, 0xc, 0xc, 0, 0}, + {0x4C, 0, 0, 0, 0}, + {0x4D, 0x38, 0x38, 0, 0}, + {0x4E, 0x70, 0x70, 1, 1}, + {0x4F, 0x2, 0x2, 0, 0}, + {0x50, 0x88, 0x88, 0, 0}, + {0x51, 0xc, 0xc, 0, 0}, + {0x52, 0, 0, 0, 0}, + {0x53, 0x8, 0x8, 0, 0}, + {0x54, 0x70, 0x70, 1, 1}, + {0x55, 0x2, 0x2, 0, 0}, + {0x56, 0xff, 0xff, 1, 1}, + {0x57, 0, 0, 0, 0}, + {0x58, 0x83, 0x83, 0, 0}, + {0x59, 0x77, 0x77, 1, 1}, + {0x5A, 0, 0, 0, 0}, + {0x5B, 0x2, 0x2, 0, 0}, + {0x5C, 0x88, 0x88, 0, 0}, + {0x5D, 0, 0, 0, 0}, + {0x5E, 0x8, 0x8, 0, 0}, + {0x5F, 0x77, 0x77, 1, 1}, + {0x60, 0x1, 0x1, 0, 0}, + {0x61, 0, 0, 0, 0}, + {0x62, 0x7, 0x7, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0x7, 0x7, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 1, 1}, + {0x68, 0, 0, 0, 0}, + {0x69, 0xa, 0xa, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0, 0, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0, 0, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0, 0, 0, 0}, + {0x71, 0x2, 0x2, 0, 0}, + {0x72, 0, 0, 0, 0}, + {0x73, 0, 0, 0, 0}, + {0x74, 0xe, 0xe, 0, 0}, + {0x75, 0xe, 0xe, 0, 0}, + {0x76, 0xe, 0xe, 0, 0}, + {0x77, 0x13, 0x13, 0, 0}, + {0x78, 0x13, 0x13, 0, 0}, + {0x79, 0x1b, 0x1b, 0, 0}, + {0x7A, 0x1b, 0x1b, 0, 0}, + {0x7B, 0x55, 0x55, 0, 0}, + {0x7C, 0x5b, 0x5b, 0, 0}, + {0x7D, 0x30, 0x30, 1, 1}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0, 0, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0x70, 0x70, 0, 0}, + {0x94, 0x70, 0x70, 0, 0}, + {0x95, 0x70, 0x70, 0, 0}, + {0x96, 0x70, 0x70, 0, 0}, + {0x97, 0x70, 0x70, 0, 0}, + {0x98, 0x70, 0x70, 0, 0}, + {0x99, 0x70, 0x70, 0, 0}, + {0x9A, 0x70, 0x70, 0, 0}, + {0xFFFF, 0, 0, 0, 0}, +}; + +static struct radio_regs regs_RX_2056_rev8[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0, 0, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0x3, 0x3, 0, 0}, + {0x21, 0, 0, 0, 0}, + {0x22, 0, 0, 0, 0}, + {0x23, 0x90, 0x90, 0, 0}, + {0x24, 0x55, 0x55, 0, 0}, + {0x25, 0x15, 0x15, 0, 0}, + {0x26, 0x5, 0x5, 0, 0}, + {0x27, 0x15, 0x15, 0, 0}, + {0x28, 0x5, 0x5, 0, 0}, + {0x29, 0x20, 0x20, 0, 0}, + {0x2A, 0x11, 0x11, 0, 0}, + {0x2B, 0x90, 0x90, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0x88, 0x88, 0, 0}, + {0x2E, 0x32, 0x32, 0, 0}, + {0x2F, 0x77, 0x77, 0, 0}, + {0x30, 0x17, 0x17, 1, 1}, + {0x31, 0xff, 0xff, 1, 1}, + {0x32, 0x20, 0x20, 0, 0}, + {0x33, 0, 0, 0, 0}, + {0x34, 0x88, 0x88, 0, 0}, + {0x35, 0x32, 0x32, 0, 0}, + {0x36, 0x77, 0x77, 0, 0}, + {0x37, 0x17, 0x17, 1, 1}, + {0x38, 0xf0, 0xf0, 1, 1}, + {0x39, 0x20, 0x20, 0, 0}, + {0x3A, 0x8, 0x8, 0, 0}, + {0x3B, 0x55, 0x55, 1, 1}, + {0x3C, 0, 0, 0, 0}, + {0x3D, 0x88, 0x88, 1, 1}, + {0x3E, 0, 0, 0, 0}, + {0x3F, 0x44, 0x44, 0, 0}, + {0x40, 0x7, 0x7, 1, 1}, + {0x41, 0x6, 0x6, 0, 0}, + {0x42, 0x4, 0x4, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x8, 0x8, 0, 0}, + {0x45, 0x55, 0x55, 1, 1}, + {0x46, 0, 0, 0, 0}, + {0x47, 0x11, 0x11, 0, 0}, + {0x48, 0, 0, 0, 0}, + {0x49, 0x44, 0x44, 0, 0}, + {0x4A, 0x7, 0x7, 0, 0}, + {0x4B, 0x6, 0x6, 0, 0}, + {0x4C, 0x4, 0x4, 0, 0}, + {0x4D, 0, 0, 0, 0}, + {0x4E, 0, 0, 0, 0}, + {0x4F, 0x26, 0x26, 1, 1}, + {0x50, 0x26, 0x26, 1, 1}, + {0x51, 0xf, 0xf, 1, 1}, + {0x52, 0xf, 0xf, 1, 1}, + {0x53, 0x44, 0x44, 0, 0}, + {0x54, 0, 0, 0, 0}, + {0x55, 0, 0, 0, 0}, + {0x56, 0x8, 0x8, 0, 0}, + {0x57, 0x8, 0x8, 0, 0}, + {0x58, 0x7, 0x7, 0, 0}, + {0x59, 0x22, 0x22, 0, 0}, + {0x5A, 0x22, 0x22, 0, 0}, + {0x5B, 0x2, 0x2, 0, 0}, + {0x5C, 0x4, 0x4, 1, 1}, + {0x5D, 0x7, 0x7, 0, 0}, + {0x5E, 0x55, 0x55, 0, 0}, + {0x5F, 0x23, 0x23, 0, 0}, + {0x60, 0x41, 0x41, 0, 0}, + {0x61, 0x1, 0x1, 0, 0}, + {0x62, 0xa, 0xa, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0, 0, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 0, 0}, + {0x68, 0, 0, 0, 0}, + {0x69, 0, 0, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0xc, 0xc, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0, 0, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0, 0, 0, 0}, + {0x71, 0, 0, 0, 0}, + {0x72, 0x22, 0x22, 0, 0}, + {0x73, 0x22, 0x22, 0, 0}, + {0x74, 0, 0, 1, 1}, + {0x75, 0xa, 0xa, 0, 0}, + {0x76, 0x1, 0x1, 0, 0}, + {0x77, 0x22, 0x22, 0, 0}, + {0x78, 0x30, 0x30, 0, 0}, + {0x79, 0, 0, 0, 0}, + {0x7A, 0, 0, 0, 0}, + {0x7B, 0, 0, 0, 0}, + {0x7C, 0, 0, 0, 0}, + {0x7D, 0x5, 0x5, 1, 1}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0, 0, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0, 0, 0, 0}, + {0x94, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0}, +}; + +static const struct radio_regs regs_SYN_2056_rev11[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0x1, 0x1, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0, 0, 0, 0}, + {0x21, 0, 0, 0, 0}, + {0x22, 0x60, 0x60, 0, 0}, + {0x23, 0x6, 0x6, 0, 0}, + {0x24, 0xc, 0xc, 0, 0}, + {0x25, 0, 0, 0, 0}, + {0x26, 0, 0, 0, 0}, + {0x27, 0, 0, 0, 0}, + {0x28, 0x1, 0x1, 0, 0}, + {0x29, 0, 0, 0, 0}, + {0x2A, 0, 0, 0, 0}, + {0x2B, 0, 0, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0, 0, 0, 0}, + {0x2E, 0, 0, 0, 0}, + {0x2F, 0x1f, 0x1f, 0, 0}, + {0x30, 0x15, 0x15, 0, 0}, + {0x31, 0xf, 0xf, 0, 0}, + {0x32, 0, 0, 0, 0}, + {0x33, 0, 0, 0, 0}, + {0x34, 0, 0, 0, 0}, + {0x35, 0, 0, 0, 0}, + {0x36, 0, 0, 0, 0}, + {0x37, 0, 0, 0, 0}, + {0x38, 0, 0, 0, 0}, + {0x39, 0, 0, 0, 0}, + {0x3A, 0, 0, 0, 0}, + {0x3B, 0, 0, 0, 0}, + {0x3C, 0x13, 0x13, 0, 0}, + {0x3D, 0xf, 0xf, 0, 0}, + {0x3E, 0x18, 0x18, 0, 0}, + {0x3F, 0, 0, 0, 0}, + {0x40, 0, 0, 0, 0}, + {0x41, 0x20, 0x20, 0, 0}, + {0x42, 0x20, 0x20, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x77, 0x77, 0, 0}, + {0x45, 0x7, 0x7, 0, 0}, + {0x46, 0x1, 0x1, 0, 0}, + {0x47, 0x6, 0x6, 1, 1}, + {0x48, 0xf, 0xf, 0, 0}, + {0x49, 0x3f, 0x3f, 1, 1}, + {0x4A, 0x32, 0x32, 0, 0}, + {0x4B, 0x6, 0x6, 1, 1}, + {0x4C, 0x6, 0x6, 1, 1}, + {0x4D, 0x4, 0x4, 0, 0}, + {0x4E, 0x2b, 0x2b, 1, 1}, + {0x4F, 0x1, 0x1, 0, 0}, + {0x50, 0x1c, 0x1c, 0, 0}, + {0x51, 0x2, 0x2, 0, 0}, + {0x52, 0x2, 0x2, 0, 0}, + {0x53, 0xf7, 0xf7, 1, 1}, + {0x54, 0xb4, 0xb4, 0, 0}, + {0x55, 0xd2, 0xd2, 0, 0}, + {0x56, 0, 0, 0, 0}, + {0x57, 0, 0, 0, 0}, + {0x58, 0x4, 0x4, 0, 0}, + {0x59, 0x96, 0x96, 0, 0}, + {0x5A, 0x3e, 0x3e, 0, 0}, + {0x5B, 0x3e, 0x3e, 0, 0}, + {0x5C, 0x13, 0x13, 0, 0}, + {0x5D, 0x2, 0x2, 0, 0}, + {0x5E, 0, 0, 0, 0}, + {0x5F, 0x7, 0x7, 0, 0}, + {0x60, 0x7, 0x7, 1, 1}, + {0x61, 0x8, 0x8, 0, 0}, + {0x62, 0x3, 0x3, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0, 0, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 0, 0}, + {0x68, 0x40, 0x40, 0, 0}, + {0x69, 0, 0, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0, 0, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0x1, 0x1, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0x60, 0x60, 0, 0}, + {0x71, 0x66, 0x66, 0, 0}, + {0x72, 0xc, 0xc, 0, 0}, + {0x73, 0x66, 0x66, 0, 0}, + {0x74, 0x8f, 0x8f, 1, 1}, + {0x75, 0, 0, 0, 0}, + {0x76, 0xcc, 0xcc, 0, 0}, + {0x77, 0x1, 0x1, 0, 0}, + {0x78, 0x66, 0x66, 0, 0}, + {0x79, 0x66, 0x66, 0, 0}, + {0x7A, 0, 0, 0, 0}, + {0x7B, 0, 0, 0, 0}, + {0x7C, 0, 0, 0, 0}, + {0x7D, 0, 0, 0, 0}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0xff, 0xff, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0, 0, 0, 0}, + {0x94, 0, 0, 0, 0}, + {0x95, 0, 0, 0, 0}, + {0x96, 0, 0, 0, 0}, + {0x97, 0, 0, 0, 0}, + {0x98, 0, 0, 0, 0}, + {0x99, 0, 0, 0, 0}, + {0x9A, 0, 0, 0, 0}, + {0x9B, 0, 0, 0, 0}, + {0x9C, 0, 0, 0, 0}, + {0x9D, 0, 0, 0, 0}, + {0x9E, 0, 0, 0, 0}, + {0x9F, 0x6, 0x6, 0, 0}, + {0xA0, 0x66, 0x66, 0, 0}, + {0xA1, 0x66, 0x66, 0, 0}, + {0xA2, 0x66, 0x66, 0, 0}, + {0xA3, 0x66, 0x66, 0, 0}, + {0xA4, 0x66, 0x66, 0, 0}, + {0xA5, 0x66, 0x66, 0, 0}, + {0xA6, 0x66, 0x66, 0, 0}, + {0xA7, 0x66, 0x66, 0, 0}, + {0xA8, 0x66, 0x66, 0, 0}, + {0xA9, 0x66, 0x66, 0, 0}, + {0xAA, 0x66, 0x66, 0, 0}, + {0xAB, 0x66, 0x66, 0, 0}, + {0xAC, 0x66, 0x66, 0, 0}, + {0xAD, 0x66, 0x66, 0, 0}, + {0xAE, 0x66, 0x66, 0, 0}, + {0xAF, 0x66, 0x66, 0, 0}, + {0xB0, 0x66, 0x66, 0, 0}, + {0xB1, 0x66, 0x66, 0, 0}, + {0xB2, 0x66, 0x66, 0, 0}, + {0xB3, 0xa, 0xa, 0, 0}, + {0xB4, 0, 0, 0, 0}, + {0xB5, 0, 0, 0, 0}, + {0xB6, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0}, +}; + +static const struct radio_regs regs_TX_2056_rev11[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0, 0, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0, 0, 0, 0}, + {0x21, 0x88, 0x88, 0, 0}, + {0x22, 0x88, 0x88, 0, 0}, + {0x23, 0x88, 0x88, 0, 0}, + {0x24, 0x88, 0x88, 0, 0}, + {0x25, 0xc, 0xc, 0, 0}, + {0x26, 0, 0, 0, 0}, + {0x27, 0x3, 0x3, 0, 0}, + {0x28, 0, 0, 0, 0}, + {0x29, 0x3, 0x3, 0, 0}, + {0x2A, 0x37, 0x37, 0, 0}, + {0x2B, 0x3, 0x3, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0, 0, 0, 0}, + {0x2E, 0x1, 0x1, 0, 0}, + {0x2F, 0x1, 0x1, 0, 0}, + {0x30, 0, 0, 0, 0}, + {0x31, 0, 0, 0, 0}, + {0x32, 0, 0, 0, 0}, + {0x33, 0x11, 0x11, 0, 0}, + {0x34, 0xee, 0xee, 1, 1}, + {0x35, 0, 0, 0, 0}, + {0x36, 0, 0, 0, 0}, + {0x37, 0x3, 0x3, 0, 0}, + {0x38, 0x50, 0x50, 1, 1}, + {0x39, 0, 0, 0, 0}, + {0x3A, 0x50, 0x50, 1, 1}, + {0x3B, 0, 0, 0, 0}, + {0x3C, 0x6e, 0x6e, 0, 0}, + {0x3D, 0xf0, 0xf0, 1, 1}, + {0x3E, 0, 0, 0, 0}, + {0x3F, 0, 0, 0, 0}, + {0x40, 0, 0, 0, 0}, + {0x41, 0x3, 0x3, 0, 0}, + {0x42, 0x3, 0x3, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x1e, 0x1e, 0, 0}, + {0x45, 0, 0, 0, 0}, + {0x46, 0x6e, 0x6e, 0, 0}, + {0x47, 0xf0, 0xf0, 1, 1}, + {0x48, 0, 0, 0, 0}, + {0x49, 0x2, 0x2, 0, 0}, + {0x4A, 0xff, 0xff, 1, 1}, + {0x4B, 0xc, 0xc, 0, 0}, + {0x4C, 0, 0, 0, 0}, + {0x4D, 0x38, 0x38, 0, 0}, + {0x4E, 0x70, 0x70, 1, 1}, + {0x4F, 0x2, 0x2, 0, 0}, + {0x50, 0x88, 0x88, 0, 0}, + {0x51, 0xc, 0xc, 0, 0}, + {0x52, 0, 0, 0, 0}, + {0x53, 0x8, 0x8, 0, 0}, + {0x54, 0x70, 0x70, 1, 1}, + {0x55, 0x2, 0x2, 0, 0}, + {0x56, 0xff, 0xff, 1, 1}, + {0x57, 0, 0, 0, 0}, + {0x58, 0x83, 0x83, 0, 0}, + {0x59, 0x77, 0x77, 1, 1}, + {0x5A, 0, 0, 0, 0}, + {0x5B, 0x2, 0x2, 0, 0}, + {0x5C, 0x88, 0x88, 0, 0}, + {0x5D, 0, 0, 0, 0}, + {0x5E, 0x8, 0x8, 0, 0}, + {0x5F, 0x77, 0x77, 1, 1}, + {0x60, 0x1, 0x1, 0, 0}, + {0x61, 0, 0, 0, 0}, + {0x62, 0x7, 0x7, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0x7, 0x7, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 1, 1}, + {0x68, 0, 0, 0, 0}, + {0x69, 0xa, 0xa, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0, 0, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0, 0, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0, 0, 0, 0}, + {0x71, 0x2, 0x2, 0, 0}, + {0x72, 0, 0, 0, 0}, + {0x73, 0, 0, 0, 0}, + {0x74, 0xe, 0xe, 0, 0}, + {0x75, 0xe, 0xe, 0, 0}, + {0x76, 0xe, 0xe, 0, 0}, + {0x77, 0x13, 0x13, 0, 0}, + {0x78, 0x13, 0x13, 0, 0}, + {0x79, 0x1b, 0x1b, 0, 0}, + {0x7A, 0x1b, 0x1b, 0, 0}, + {0x7B, 0x55, 0x55, 0, 0}, + {0x7C, 0x5b, 0x5b, 0, 0}, + {0x7D, 0x30, 0x30, 1, 1}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0, 0, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0x70, 0x70, 0, 0}, + {0x94, 0x70, 0x70, 0, 0}, + {0x95, 0x70, 0x70, 0, 0}, + {0x96, 0x70, 0x70, 0, 0}, + {0x97, 0x70, 0x70, 0, 0}, + {0x98, 0x70, 0x70, 0, 0}, + {0x99, 0x70, 0x70, 0, 0}, + {0x9A, 0x70, 0x70, 0, 0}, + {0xFFFF, 0, 0, 0, 0}, +}; + +static const struct radio_regs regs_RX_2056_rev11[] = { + {0x02, 0, 0, 0, 0}, + {0x03, 0, 0, 0, 0}, + {0x04, 0, 0, 0, 0}, + {0x05, 0, 0, 0, 0}, + {0x06, 0, 0, 0, 0}, + {0x07, 0, 0, 0, 0}, + {0x08, 0, 0, 0, 0}, + {0x09, 0, 0, 0, 0}, + {0x0A, 0, 0, 0, 0}, + {0x0B, 0, 0, 0, 0}, + {0x0C, 0, 0, 0, 0}, + {0x0D, 0, 0, 0, 0}, + {0x0E, 0, 0, 0, 0}, + {0x0F, 0, 0, 0, 0}, + {0x10, 0, 0, 0, 0}, + {0x11, 0, 0, 0, 0}, + {0x12, 0, 0, 0, 0}, + {0x13, 0, 0, 0, 0}, + {0x14, 0, 0, 0, 0}, + {0x15, 0, 0, 0, 0}, + {0x16, 0, 0, 0, 0}, + {0x17, 0, 0, 0, 0}, + {0x18, 0, 0, 0, 0}, + {0x19, 0, 0, 0, 0}, + {0x1A, 0, 0, 0, 0}, + {0x1B, 0, 0, 0, 0}, + {0x1C, 0, 0, 0, 0}, + {0x1D, 0, 0, 0, 0}, + {0x1E, 0, 0, 0, 0}, + {0x1F, 0, 0, 0, 0}, + {0x20, 0x3, 0x3, 0, 0}, + {0x21, 0, 0, 0, 0}, + {0x22, 0, 0, 0, 0}, + {0x23, 0x90, 0x90, 0, 0}, + {0x24, 0x55, 0x55, 0, 0}, + {0x25, 0x15, 0x15, 0, 0}, + {0x26, 0x5, 0x5, 0, 0}, + {0x27, 0x15, 0x15, 0, 0}, + {0x28, 0x5, 0x5, 0, 0}, + {0x29, 0x20, 0x20, 0, 0}, + {0x2A, 0x11, 0x11, 0, 0}, + {0x2B, 0x90, 0x90, 0, 0}, + {0x2C, 0, 0, 0, 0}, + {0x2D, 0x88, 0x88, 0, 0}, + {0x2E, 0x32, 0x32, 0, 0}, + {0x2F, 0x77, 0x77, 0, 0}, + {0x30, 0x17, 0x17, 1, 1}, + {0x31, 0xff, 0xff, 1, 1}, + {0x32, 0x20, 0x20, 0, 0}, + {0x33, 0, 0, 0, 0}, + {0x34, 0x88, 0x88, 0, 0}, + {0x35, 0x32, 0x32, 0, 0}, + {0x36, 0x77, 0x77, 0, 0}, + {0x37, 0x17, 0x17, 1, 1}, + {0x38, 0xf0, 0xf0, 1, 1}, + {0x39, 0x20, 0x20, 0, 0}, + {0x3A, 0x8, 0x8, 0, 0}, + {0x3B, 0x55, 0x55, 1, 1}, + {0x3C, 0, 0, 0, 0}, + {0x3D, 0x88, 0x88, 1, 1}, + {0x3E, 0, 0, 0, 0}, + {0x3F, 0x44, 0x44, 0, 0}, + {0x40, 0x7, 0x7, 1, 1}, + {0x41, 0x6, 0x6, 0, 0}, + {0x42, 0x4, 0x4, 0, 0}, + {0x43, 0, 0, 0, 0}, + {0x44, 0x8, 0x8, 0, 0}, + {0x45, 0x55, 0x55, 1, 1}, + {0x46, 0, 0, 0, 0}, + {0x47, 0x11, 0x11, 0, 0}, + {0x48, 0, 0, 0, 0}, + {0x49, 0x44, 0x44, 0, 0}, + {0x4A, 0x7, 0x7, 0, 0}, + {0x4B, 0x6, 0x6, 0, 0}, + {0x4C, 0x4, 0x4, 0, 0}, + {0x4D, 0, 0, 0, 0}, + {0x4E, 0, 0, 0, 0}, + {0x4F, 0x26, 0x26, 1, 1}, + {0x50, 0x26, 0x26, 1, 1}, + {0x51, 0xf, 0xf, 1, 1}, + {0x52, 0xf, 0xf, 1, 1}, + {0x53, 0x44, 0x44, 0, 0}, + {0x54, 0, 0, 0, 0}, + {0x55, 0, 0, 0, 0}, + {0x56, 0x8, 0x8, 0, 0}, + {0x57, 0x8, 0x8, 0, 0}, + {0x58, 0x7, 0x7, 0, 0}, + {0x59, 0x22, 0x22, 0, 0}, + {0x5A, 0x22, 0x22, 0, 0}, + {0x5B, 0x2, 0x2, 0, 0}, + {0x5C, 0x4, 0x4, 1, 1}, + {0x5D, 0x7, 0x7, 0, 0}, + {0x5E, 0x55, 0x55, 0, 0}, + {0x5F, 0x23, 0x23, 0, 0}, + {0x60, 0x41, 0x41, 0, 0}, + {0x61, 0x1, 0x1, 0, 0}, + {0x62, 0xa, 0xa, 0, 0}, + {0x63, 0, 0, 0, 0}, + {0x64, 0, 0, 0, 0}, + {0x65, 0, 0, 0, 0}, + {0x66, 0, 0, 0, 0}, + {0x67, 0, 0, 0, 0}, + {0x68, 0, 0, 0, 0}, + {0x69, 0, 0, 0, 0}, + {0x6A, 0, 0, 0, 0}, + {0x6B, 0xc, 0xc, 0, 0}, + {0x6C, 0, 0, 0, 0}, + {0x6D, 0, 0, 0, 0}, + {0x6E, 0, 0, 0, 0}, + {0x6F, 0, 0, 0, 0}, + {0x70, 0, 0, 0, 0}, + {0x71, 0, 0, 0, 0}, + {0x72, 0x22, 0x22, 0, 0}, + {0x73, 0x22, 0x22, 0, 0}, + {0x74, 0, 0, 1, 1}, + {0x75, 0xa, 0xa, 0, 0}, + {0x76, 0x1, 0x1, 0, 0}, + {0x77, 0x22, 0x22, 0, 0}, + {0x78, 0x30, 0x30, 0, 0}, + {0x79, 0, 0, 0, 0}, + {0x7A, 0, 0, 0, 0}, + {0x7B, 0, 0, 0, 0}, + {0x7C, 0, 0, 0, 0}, + {0x7D, 0x5, 0x5, 1, 1}, + {0x7E, 0, 0, 0, 0}, + {0x7F, 0, 0, 0, 0}, + {0x80, 0, 0, 0, 0}, + {0x81, 0, 0, 0, 0}, + {0x82, 0, 0, 0, 0}, + {0x83, 0, 0, 0, 0}, + {0x84, 0, 0, 0, 0}, + {0x85, 0, 0, 0, 0}, + {0x86, 0, 0, 0, 0}, + {0x87, 0, 0, 0, 0}, + {0x88, 0, 0, 0, 0}, + {0x89, 0, 0, 0, 0}, + {0x8A, 0, 0, 0, 0}, + {0x8B, 0, 0, 0, 0}, + {0x8C, 0, 0, 0, 0}, + {0x8D, 0, 0, 0, 0}, + {0x8E, 0, 0, 0, 0}, + {0x8F, 0, 0, 0, 0}, + {0x90, 0, 0, 0, 0}, + {0x91, 0, 0, 0, 0}, + {0x92, 0, 0, 0, 0}, + {0x93, 0, 0, 0, 0}, + {0x94, 0, 0, 0, 0}, + {0xFFFF, 0, 0, 0, 0}, +}; + +static struct radio_20xx_regs regs_2057_rev4[] = { + {0x00, 0x84, 0}, + {0x01, 0, 0}, + {0x02, 0x60, 0}, + {0x03, 0x1f, 0}, + {0x04, 0x4, 0}, + {0x05, 0x2, 0}, + {0x06, 0x1, 0}, + {0x07, 0x1, 0}, + {0x08, 0x1, 0}, + {0x09, 0x69, 0}, + {0x0A, 0x66, 0}, + {0x0B, 0x6, 0}, + {0x0C, 0x18, 0}, + {0x0D, 0x3, 0}, + {0x0E, 0x20, 1}, + {0x0F, 0x20, 0}, + {0x10, 0, 0}, + {0x11, 0x7c, 0}, + {0x12, 0x42, 0}, + {0x13, 0xbd, 0}, + {0x14, 0x7, 0}, + {0x15, 0xf7, 0}, + {0x16, 0x8, 0}, + {0x17, 0x17, 0}, + {0x18, 0x7, 0}, + {0x19, 0, 0}, + {0x1A, 0x2, 0}, + {0x1B, 0x13, 0}, + {0x1C, 0x3e, 0}, + {0x1D, 0x3e, 0}, + {0x1E, 0x96, 0}, + {0x1F, 0x4, 0}, + {0x20, 0, 0}, + {0x21, 0, 0}, + {0x22, 0x17, 0}, + {0x23, 0x4, 0}, + {0x24, 0x1, 0}, + {0x25, 0x6, 0}, + {0x26, 0x4, 0}, + {0x27, 0xd, 0}, + {0x28, 0xd, 0}, + {0x29, 0x30, 0}, + {0x2A, 0x32, 0}, + {0x2B, 0x8, 0}, + {0x2C, 0x1c, 0}, + {0x2D, 0x2, 0}, + {0x2E, 0x4, 0}, + {0x2F, 0x7f, 0}, + {0x30, 0x27, 0}, + {0x31, 0, 1}, + {0x32, 0, 1}, + {0x33, 0, 1}, + {0x34, 0, 0}, + {0x35, 0x26, 1}, + {0x36, 0x18, 0}, + {0x37, 0x7, 0}, + {0x38, 0x66, 0}, + {0x39, 0x66, 0}, + {0x3A, 0x66, 0}, + {0x3B, 0x66, 0}, + {0x3C, 0xff, 1}, + {0x3D, 0xff, 1}, + {0x3E, 0xff, 1}, + {0x3F, 0xff, 1}, + {0x40, 0x16, 0}, + {0x41, 0x7, 0}, + {0x42, 0x19, 0}, + {0x43, 0x7, 0}, + {0x44, 0x6, 0}, + {0x45, 0x3, 0}, + {0x46, 0x1, 0}, + {0x47, 0x7, 0}, + {0x48, 0x33, 0}, + {0x49, 0x5, 0}, + {0x4A, 0x77, 0}, + {0x4B, 0x66, 0}, + {0x4C, 0x66, 0}, + {0x4D, 0, 0}, + {0x4E, 0x4, 0}, + {0x4F, 0xc, 0}, + {0x50, 0, 0}, + {0x51, 0x75, 0}, + {0x56, 0x7, 0}, + {0x57, 0, 0}, + {0x58, 0, 0}, + {0x59, 0xa8, 0}, + {0x5A, 0, 0}, + {0x5B, 0x1f, 0}, + {0x5C, 0x30, 0}, + {0x5D, 0x1, 0}, + {0x5E, 0x30, 0}, + {0x5F, 0x70, 0}, + {0x60, 0, 0}, + {0x61, 0, 0}, + {0x62, 0x33, 1}, + {0x63, 0x19, 0}, + {0x64, 0x62, 0}, + {0x65, 0, 0}, + {0x66, 0x11, 0}, + {0x69, 0, 0}, + {0x6A, 0x7e, 0}, + {0x6B, 0x3f, 0}, + {0x6C, 0x7f, 0}, + {0x6D, 0x78, 0}, + {0x6E, 0xc8, 0}, + {0x6F, 0x88, 0}, + {0x70, 0x8, 0}, + {0x71, 0xf, 0}, + {0x72, 0xbc, 0}, + {0x73, 0x8, 0}, + {0x74, 0x60, 0}, + {0x75, 0x1e, 0}, + {0x76, 0x70, 0}, + {0x77, 0, 0}, + {0x78, 0, 0}, + {0x79, 0, 0}, + {0x7A, 0x33, 0}, + {0x7B, 0x1e, 0}, + {0x7C, 0x62, 0}, + {0x7D, 0x11, 0}, + {0x80, 0x3c, 0}, + {0x81, 0x9c, 0}, + {0x82, 0xa, 0}, + {0x83, 0x9d, 0}, + {0x84, 0xa, 0}, + {0x85, 0, 0}, + {0x86, 0x40, 0}, + {0x87, 0x40, 0}, + {0x88, 0x88, 0}, + {0x89, 0x10, 0}, + {0x8A, 0xf0, 1}, + {0x8B, 0x10, 1}, + {0x8C, 0xf0, 1}, + {0x8D, 0, 0}, + {0x8E, 0, 0}, + {0x8F, 0x10, 0}, + {0x90, 0x55, 0}, + {0x91, 0x3f, 1}, + {0x92, 0x36, 1}, + {0x93, 0, 0}, + {0x94, 0, 0}, + {0x95, 0, 0}, + {0x96, 0x87, 0}, + {0x97, 0x11, 0}, + {0x98, 0, 0}, + {0x99, 0x33, 0}, + {0x9A, 0x88, 0}, + {0x9B, 0, 0}, + {0x9C, 0x87, 0}, + {0x9D, 0x11, 0}, + {0x9E, 0, 0}, + {0x9F, 0x33, 0}, + {0xA0, 0x88, 0}, + {0xA1, 0xe1, 0}, + {0xA2, 0x3f, 0}, + {0xA3, 0x44, 0}, + {0xA4, 0x8c, 1}, + {0xA5, 0x6d, 0}, + {0xA6, 0x22, 0}, + {0xA7, 0xbe, 0}, + {0xA8, 0x55, 1}, + {0xA9, 0xc, 0}, + {0xAA, 0xc, 0}, + {0xAB, 0xaa, 0}, + {0xAC, 0x2, 0}, + {0xAD, 0, 0}, + {0xAE, 0x10, 0}, + {0xAF, 0x1, 1}, + {0xB0, 0, 0}, + {0xB1, 0, 0}, + {0xB2, 0x80, 0}, + {0xB3, 0x60, 0}, + {0xB4, 0x44, 0}, + {0xB5, 0x55, 0}, + {0xB6, 0x1, 0}, + {0xB7, 0x55, 0}, + {0xB8, 0x1, 0}, + {0xB9, 0x5, 0}, + {0xBA, 0x55, 0}, + {0xBB, 0x55, 0}, + {0xC1, 0, 0}, + {0xC2, 0, 0}, + {0xC3, 0, 0}, + {0xC4, 0, 0}, + {0xC5, 0, 0}, + {0xC6, 0, 0}, + {0xC7, 0, 0}, + {0xC8, 0, 0}, + {0xC9, 0, 0}, + {0xCA, 0, 0}, + {0xCB, 0, 0}, + {0xCC, 0, 0}, + {0xCD, 0, 0}, + {0xCE, 0x5e, 0}, + {0xCF, 0xc, 0}, + {0xD0, 0xc, 0}, + {0xD1, 0xc, 0}, + {0xD2, 0, 0}, + {0xD3, 0x2b, 0}, + {0xD4, 0xc, 0}, + {0xD5, 0, 0}, + {0xD6, 0x75, 0}, + {0xDB, 0x7, 0}, + {0xDC, 0, 0}, + {0xDD, 0, 0}, + {0xDE, 0xa8, 0}, + {0xDF, 0, 0}, + {0xE0, 0x1f, 0}, + {0xE1, 0x30, 0}, + {0xE2, 0x1, 0}, + {0xE3, 0x30, 0}, + {0xE4, 0x70, 0}, + {0xE5, 0, 0}, + {0xE6, 0, 0}, + {0xE7, 0x33, 0}, + {0xE8, 0x19, 0}, + {0xE9, 0x62, 0}, + {0xEA, 0, 0}, + {0xEB, 0x11, 0}, + {0xEE, 0, 0}, + {0xEF, 0x7e, 0}, + {0xF0, 0x3f, 0}, + {0xF1, 0x7f, 0}, + {0xF2, 0x78, 0}, + {0xF3, 0xc8, 0}, + {0xF4, 0x88, 0}, + {0xF5, 0x8, 0}, + {0xF6, 0xf, 0}, + {0xF7, 0xbc, 0}, + {0xF8, 0x8, 0}, + {0xF9, 0x60, 0}, + {0xFA, 0x1e, 0}, + {0xFB, 0x70, 0}, + {0xFC, 0, 0}, + {0xFD, 0, 0}, + {0xFE, 0, 0}, + {0xFF, 0x33, 0}, + {0x100, 0x1e, 0}, + {0x101, 0x62, 0}, + {0x102, 0x11, 0}, + {0x105, 0x3c, 0}, + {0x106, 0x9c, 0}, + {0x107, 0xa, 0}, + {0x108, 0x9d, 0}, + {0x109, 0xa, 0}, + {0x10A, 0, 0}, + {0x10B, 0x40, 0}, + {0x10C, 0x40, 0}, + {0x10D, 0x88, 0}, + {0x10E, 0x10, 0}, + {0x10F, 0xf0, 1}, + {0x110, 0x10, 1}, + {0x111, 0xf0, 1}, + {0x112, 0, 0}, + {0x113, 0, 0}, + {0x114, 0x10, 0}, + {0x115, 0x55, 0}, + {0x116, 0x3f, 1}, + {0x117, 0x36, 1}, + {0x118, 0, 0}, + {0x119, 0, 0}, + {0x11A, 0, 0}, + {0x11B, 0x87, 0}, + {0x11C, 0x11, 0}, + {0x11D, 0, 0}, + {0x11E, 0x33, 0}, + {0x11F, 0x88, 0}, + {0x120, 0, 0}, + {0x121, 0x87, 0}, + {0x122, 0x11, 0}, + {0x123, 0, 0}, + {0x124, 0x33, 0}, + {0x125, 0x88, 0}, + {0x126, 0xe1, 0}, + {0x127, 0x3f, 0}, + {0x128, 0x44, 0}, + {0x129, 0x8c, 1}, + {0x12A, 0x6d, 0}, + {0x12B, 0x22, 0}, + {0x12C, 0xbe, 0}, + {0x12D, 0x55, 1}, + {0x12E, 0xc, 0}, + {0x12F, 0xc, 0}, + {0x130, 0xaa, 0}, + {0x131, 0x2, 0}, + {0x132, 0, 0}, + {0x133, 0x10, 0}, + {0x134, 0x1, 1}, + {0x135, 0, 0}, + {0x136, 0, 0}, + {0x137, 0x80, 0}, + {0x138, 0x60, 0}, + {0x139, 0x44, 0}, + {0x13A, 0x55, 0}, + {0x13B, 0x1, 0}, + {0x13C, 0x55, 0}, + {0x13D, 0x1, 0}, + {0x13E, 0x5, 0}, + {0x13F, 0x55, 0}, + {0x140, 0x55, 0}, + {0x146, 0, 0}, + {0x147, 0, 0}, + {0x148, 0, 0}, + {0x149, 0, 0}, + {0x14A, 0, 0}, + {0x14B, 0, 0}, + {0x14C, 0, 0}, + {0x14D, 0, 0}, + {0x14E, 0, 0}, + {0x14F, 0, 0}, + {0x150, 0, 0}, + {0x151, 0, 0}, + {0x152, 0, 0}, + {0x153, 0, 0}, + {0x154, 0xc, 0}, + {0x155, 0xc, 0}, + {0x156, 0xc, 0}, + {0x157, 0, 0}, + {0x158, 0x2b, 0}, + {0x159, 0x84, 0}, + {0x15A, 0x15, 0}, + {0x15B, 0xf, 0}, + {0x15C, 0, 0}, + {0x15D, 0, 0}, + {0x15E, 0, 1}, + {0x15F, 0, 1}, + {0x160, 0, 1}, + {0x161, 0, 1}, + {0x162, 0, 1}, + {0x163, 0, 1}, + {0x164, 0, 0}, + {0x165, 0, 0}, + {0x166, 0, 0}, + {0x167, 0, 0}, + {0x168, 0, 0}, + {0x169, 0x2, 1}, + {0x16A, 0, 1}, + {0x16B, 0, 1}, + {0x16C, 0, 1}, + {0x16D, 0, 0}, + {0x170, 0, 0}, + {0x171, 0x77, 0}, + {0x172, 0x77, 0}, + {0x173, 0x77, 0}, + {0x174, 0x77, 0}, + {0x175, 0, 0}, + {0x176, 0x3, 0}, + {0x177, 0x37, 0}, + {0x178, 0x3, 0}, + {0x179, 0, 0}, + {0x17A, 0x21, 0}, + {0x17B, 0x21, 0}, + {0x17C, 0, 0}, + {0x17D, 0xaa, 0}, + {0x17E, 0, 0}, + {0x17F, 0xaa, 0}, + {0x180, 0, 0}, + {0x190, 0, 0}, + {0x191, 0x77, 0}, + {0x192, 0x77, 0}, + {0x193, 0x77, 0}, + {0x194, 0x77, 0}, + {0x195, 0, 0}, + {0x196, 0x3, 0}, + {0x197, 0x37, 0}, + {0x198, 0x3, 0}, + {0x199, 0, 0}, + {0x19A, 0x21, 0}, + {0x19B, 0x21, 0}, + {0x19C, 0, 0}, + {0x19D, 0xaa, 0}, + {0x19E, 0, 0}, + {0x19F, 0xaa, 0}, + {0x1A0, 0, 0}, + {0x1A1, 0x2, 0}, + {0x1A2, 0xf, 0}, + {0x1A3, 0xf, 0}, + {0x1A4, 0, 1}, + {0x1A5, 0, 1}, + {0x1A6, 0, 1}, + {0x1A7, 0x2, 0}, + {0x1A8, 0xf, 0}, + {0x1A9, 0xf, 0}, + {0x1AA, 0, 1}, + {0x1AB, 0, 1}, + {0x1AC, 0, 1}, + {0xFFFF, 0, 0}, +}; + +static struct radio_20xx_regs regs_2057_rev5[] = { + {0x00, 0, 1}, + {0x01, 0x57, 1}, + {0x02, 0x20, 1}, + {0x03, 0x1f, 0}, + {0x04, 0x4, 0}, + {0x05, 0x2, 0}, + {0x06, 0x1, 0}, + {0x07, 0x1, 0}, + {0x08, 0x1, 0}, + {0x09, 0x69, 0}, + {0x0A, 0x66, 0}, + {0x0B, 0x6, 0}, + {0x0C, 0x18, 0}, + {0x0D, 0x3, 0}, + {0x0E, 0x20, 0}, + {0x0F, 0x20, 0}, + {0x10, 0, 0}, + {0x11, 0x7c, 0}, + {0x12, 0x42, 0}, + {0x13, 0xbd, 0}, + {0x14, 0x7, 0}, + {0x15, 0x87, 0}, + {0x16, 0x8, 0}, + {0x17, 0x17, 0}, + {0x18, 0x7, 0}, + {0x19, 0, 0}, + {0x1A, 0x2, 0}, + {0x1B, 0x13, 0}, + {0x1C, 0x3e, 0}, + {0x1D, 0x3e, 0}, + {0x1E, 0x96, 0}, + {0x1F, 0x4, 0}, + {0x20, 0, 0}, + {0x21, 0, 0}, + {0x22, 0x17, 0}, + {0x23, 0x6, 1}, + {0x24, 0x1, 0}, + {0x25, 0x6, 0}, + {0x26, 0x4, 0}, + {0x27, 0xd, 0}, + {0x28, 0xd, 0}, + {0x29, 0x30, 0}, + {0x2A, 0x32, 0}, + {0x2B, 0x8, 0}, + {0x2C, 0x1c, 0}, + {0x2D, 0x2, 0}, + {0x2E, 0x4, 0}, + {0x2F, 0x7f, 0}, + {0x30, 0x27, 0}, + {0x31, 0, 1}, + {0x32, 0, 1}, + {0x33, 0, 1}, + {0x34, 0, 0}, + {0x35, 0x20, 0}, + {0x36, 0x18, 0}, + {0x37, 0x7, 0}, + {0x38, 0x66, 0}, + {0x39, 0x66, 0}, + {0x3C, 0xff, 0}, + {0x3D, 0xff, 0}, + {0x40, 0x16, 0}, + {0x41, 0x7, 0}, + {0x45, 0x3, 0}, + {0x46, 0x1, 0}, + {0x47, 0x7, 0}, + {0x4B, 0x66, 0}, + {0x4C, 0x66, 0}, + {0x4D, 0, 0}, + {0x4E, 0x4, 0}, + {0x4F, 0xc, 0}, + {0x50, 0, 0}, + {0x51, 0x70, 1}, + {0x56, 0x7, 0}, + {0x57, 0, 0}, + {0x58, 0, 0}, + {0x59, 0x88, 1}, + {0x5A, 0, 0}, + {0x5B, 0x1f, 0}, + {0x5C, 0x20, 1}, + {0x5D, 0x1, 0}, + {0x5E, 0x30, 0}, + {0x5F, 0x70, 0}, + {0x60, 0, 0}, + {0x61, 0, 0}, + {0x62, 0x33, 1}, + {0x63, 0xf, 1}, + {0x64, 0xf, 1}, + {0x65, 0, 0}, + {0x66, 0x11, 0}, + {0x80, 0x3c, 0}, + {0x81, 0x1, 1}, + {0x82, 0xa, 0}, + {0x85, 0, 0}, + {0x86, 0x40, 0}, + {0x87, 0x40, 0}, + {0x88, 0x88, 0}, + {0x89, 0x10, 0}, + {0x8A, 0xf0, 0}, + {0x8B, 0x10, 0}, + {0x8C, 0xf0, 0}, + {0x8F, 0x10, 0}, + {0x90, 0x55, 0}, + {0x91, 0x3f, 1}, + {0x92, 0x36, 1}, + {0x93, 0, 0}, + {0x94, 0, 0}, + {0x95, 0, 0}, + {0x96, 0x87, 0}, + {0x97, 0x11, 0}, + {0x98, 0, 0}, + {0x99, 0x33, 0}, + {0x9A, 0x88, 0}, + {0xA1, 0x20, 1}, + {0xA2, 0x3f, 0}, + {0xA3, 0x44, 0}, + {0xA4, 0x8c, 0}, + {0xA5, 0x6c, 0}, + {0xA6, 0x22, 0}, + {0xA7, 0xbe, 0}, + {0xA8, 0x55, 0}, + {0xAA, 0xc, 0}, + {0xAB, 0xaa, 0}, + {0xAC, 0x2, 0}, + {0xAD, 0, 0}, + {0xAE, 0x10, 0}, + {0xAF, 0x1, 0}, + {0xB0, 0, 0}, + {0xB1, 0, 0}, + {0xB2, 0x80, 0}, + {0xB3, 0x60, 0}, + {0xB4, 0x44, 0}, + {0xB5, 0x55, 0}, + {0xB6, 0x1, 0}, + {0xB7, 0x55, 0}, + {0xB8, 0x1, 0}, + {0xB9, 0x5, 0}, + {0xBA, 0x55, 0}, + {0xBB, 0x55, 0}, + {0xC3, 0, 0}, + {0xC4, 0, 0}, + {0xC5, 0, 0}, + {0xC6, 0, 0}, + {0xC7, 0, 0}, + {0xC8, 0, 0}, + {0xC9, 0, 0}, + {0xCA, 0, 0}, + {0xCB, 0, 0}, + {0xCD, 0, 0}, + {0xCE, 0x5e, 0}, + {0xCF, 0xc, 0}, + {0xD0, 0xc, 0}, + {0xD1, 0xc, 0}, + {0xD2, 0, 0}, + {0xD3, 0x2b, 0}, + {0xD4, 0xc, 0}, + {0xD5, 0, 0}, + {0xD6, 0x70, 1}, + {0xDB, 0x7, 0}, + {0xDC, 0, 0}, + {0xDD, 0, 0}, + {0xDE, 0x88, 1}, + {0xDF, 0, 0}, + {0xE0, 0x1f, 0}, + {0xE1, 0x20, 1}, + {0xE2, 0x1, 0}, + {0xE3, 0x30, 0}, + {0xE4, 0x70, 0}, + {0xE5, 0, 0}, + {0xE6, 0, 0}, + {0xE7, 0x33, 0}, + {0xE8, 0xf, 1}, + {0xE9, 0xf, 1}, + {0xEA, 0, 0}, + {0xEB, 0x11, 0}, + {0x105, 0x3c, 0}, + {0x106, 0x1, 1}, + {0x107, 0xa, 0}, + {0x10A, 0, 0}, + {0x10B, 0x40, 0}, + {0x10C, 0x40, 0}, + {0x10D, 0x88, 0}, + {0x10E, 0x10, 0}, + {0x10F, 0xf0, 0}, + {0x110, 0x10, 0}, + {0x111, 0xf0, 0}, + {0x114, 0x10, 0}, + {0x115, 0x55, 0}, + {0x116, 0x3f, 1}, + {0x117, 0x36, 1}, + {0x118, 0, 0}, + {0x119, 0, 0}, + {0x11A, 0, 0}, + {0x11B, 0x87, 0}, + {0x11C, 0x11, 0}, + {0x11D, 0, 0}, + {0x11E, 0x33, 0}, + {0x11F, 0x88, 0}, + {0x126, 0x20, 1}, + {0x127, 0x3f, 0}, + {0x128, 0x44, 0}, + {0x129, 0x8c, 0}, + {0x12A, 0x6c, 0}, + {0x12B, 0x22, 0}, + {0x12C, 0xbe, 0}, + {0x12D, 0x55, 0}, + {0x12F, 0xc, 0}, + {0x130, 0xaa, 0}, + {0x131, 0x2, 0}, + {0x132, 0, 0}, + {0x133, 0x10, 0}, + {0x134, 0x1, 0}, + {0x135, 0, 0}, + {0x136, 0, 0}, + {0x137, 0x80, 0}, + {0x138, 0x60, 0}, + {0x139, 0x44, 0}, + {0x13A, 0x55, 0}, + {0x13B, 0x1, 0}, + {0x13C, 0x55, 0}, + {0x13D, 0x1, 0}, + {0x13E, 0x5, 0}, + {0x13F, 0x55, 0}, + {0x140, 0x55, 0}, + {0x148, 0, 0}, + {0x149, 0, 0}, + {0x14A, 0, 0}, + {0x14B, 0, 0}, + {0x14C, 0, 0}, + {0x14D, 0, 0}, + {0x14E, 0, 0}, + {0x14F, 0, 0}, + {0x150, 0, 0}, + {0x154, 0xc, 0}, + {0x155, 0xc, 0}, + {0x156, 0xc, 0}, + {0x157, 0, 0}, + {0x158, 0x2b, 0}, + {0x159, 0x84, 0}, + {0x15A, 0x15, 0}, + {0x15B, 0xf, 0}, + {0x15C, 0, 0}, + {0x15D, 0, 0}, + {0x15E, 0, 1}, + {0x15F, 0, 1}, + {0x160, 0, 1}, + {0x161, 0, 1}, + {0x162, 0, 1}, + {0x163, 0, 1}, + {0x164, 0, 0}, + {0x165, 0, 0}, + {0x166, 0, 0}, + {0x167, 0, 0}, + {0x168, 0, 0}, + {0x169, 0, 0}, + {0x16A, 0, 1}, + {0x16B, 0, 1}, + {0x16C, 0, 1}, + {0x16D, 0, 0}, + {0x170, 0, 0}, + {0x171, 0x77, 0}, + {0x172, 0x77, 0}, + {0x173, 0x77, 0}, + {0x174, 0x77, 0}, + {0x175, 0, 0}, + {0x176, 0x3, 0}, + {0x177, 0x37, 0}, + {0x178, 0x3, 0}, + {0x179, 0, 0}, + {0x17B, 0x21, 0}, + {0x17C, 0, 0}, + {0x17D, 0xaa, 0}, + {0x17E, 0, 0}, + {0x190, 0, 0}, + {0x191, 0x77, 0}, + {0x192, 0x77, 0}, + {0x193, 0x77, 0}, + {0x194, 0x77, 0}, + {0x195, 0, 0}, + {0x196, 0x3, 0}, + {0x197, 0x37, 0}, + {0x198, 0x3, 0}, + {0x199, 0, 0}, + {0x19B, 0x21, 0}, + {0x19C, 0, 0}, + {0x19D, 0xaa, 0}, + {0x19E, 0, 0}, + {0x1A1, 0x2, 0}, + {0x1A2, 0xf, 0}, + {0x1A3, 0xf, 0}, + {0x1A4, 0, 1}, + {0x1A5, 0, 1}, + {0x1A6, 0, 1}, + {0x1A7, 0x2, 0}, + {0x1A8, 0xf, 0}, + {0x1A9, 0xf, 0}, + {0x1AA, 0, 1}, + {0x1AB, 0, 1}, + {0x1AC, 0, 1}, + {0x1AD, 0x84, 0}, + {0x1AE, 0x60, 0}, + {0x1AF, 0x47, 0}, + {0x1B0, 0x47, 0}, + {0x1B1, 0, 0}, + {0x1B2, 0, 0}, + {0x1B3, 0, 0}, + {0x1B4, 0, 0}, + {0x1B5, 0, 0}, + {0x1B6, 0, 0}, + {0x1B7, 0xc, 1}, + {0x1B8, 0, 0}, + {0x1B9, 0, 0}, + {0x1BA, 0, 0}, + {0x1BB, 0, 0}, + {0x1BC, 0, 0}, + {0x1BD, 0, 0}, + {0x1BE, 0, 0}, + {0x1BF, 0, 0}, + {0x1C0, 0, 0}, + {0x1C1, 0x1, 1}, + {0x1C2, 0x80, 1}, + {0x1C3, 0, 0}, + {0x1C4, 0, 0}, + {0x1C5, 0, 0}, + {0x1C6, 0, 0}, + {0x1C7, 0, 0}, + {0x1C8, 0, 0}, + {0x1C9, 0, 0}, + {0x1CA, 0, 0}, + {0xFFFF, 0, 0} +}; + +static struct radio_20xx_regs regs_2057_rev5v1[] = { + {0x00, 0x15, 1}, + {0x01, 0x57, 1}, + {0x02, 0x20, 1}, + {0x03, 0x1f, 0}, + {0x04, 0x4, 0}, + {0x05, 0x2, 0}, + {0x06, 0x1, 0}, + {0x07, 0x1, 0}, + {0x08, 0x1, 0}, + {0x09, 0x69, 0}, + {0x0A, 0x66, 0}, + {0x0B, 0x6, 0}, + {0x0C, 0x18, 0}, + {0x0D, 0x3, 0}, + {0x0E, 0x20, 0}, + {0x0F, 0x20, 0}, + {0x10, 0, 0}, + {0x11, 0x7c, 0}, + {0x12, 0x42, 0}, + {0x13, 0xbd, 0}, + {0x14, 0x7, 0}, + {0x15, 0x87, 0}, + {0x16, 0x8, 0}, + {0x17, 0x17, 0}, + {0x18, 0x7, 0}, + {0x19, 0, 0}, + {0x1A, 0x2, 0}, + {0x1B, 0x13, 0}, + {0x1C, 0x3e, 0}, + {0x1D, 0x3e, 0}, + {0x1E, 0x96, 0}, + {0x1F, 0x4, 0}, + {0x20, 0, 0}, + {0x21, 0, 0}, + {0x22, 0x17, 0}, + {0x23, 0x6, 1}, + {0x24, 0x1, 0}, + {0x25, 0x6, 0}, + {0x26, 0x4, 0}, + {0x27, 0xd, 0}, + {0x28, 0xd, 0}, + {0x29, 0x30, 0}, + {0x2A, 0x32, 0}, + {0x2B, 0x8, 0}, + {0x2C, 0x1c, 0}, + {0x2D, 0x2, 0}, + {0x2E, 0x4, 0}, + {0x2F, 0x7f, 0}, + {0x30, 0x27, 0}, + {0x31, 0, 1}, + {0x32, 0, 1}, + {0x33, 0, 1}, + {0x34, 0, 0}, + {0x35, 0x20, 0}, + {0x36, 0x18, 0}, + {0x37, 0x7, 0}, + {0x38, 0x66, 0}, + {0x39, 0x66, 0}, + {0x3C, 0xff, 0}, + {0x3D, 0xff, 0}, + {0x40, 0x16, 0}, + {0x41, 0x7, 0}, + {0x45, 0x3, 0}, + {0x46, 0x1, 0}, + {0x47, 0x7, 0}, + {0x4B, 0x66, 0}, + {0x4C, 0x66, 0}, + {0x4D, 0, 0}, + {0x4E, 0x4, 0}, + {0x4F, 0xc, 0}, + {0x50, 0, 0}, + {0x51, 0x70, 1}, + {0x56, 0x7, 0}, + {0x57, 0, 0}, + {0x58, 0, 0}, + {0x59, 0x88, 1}, + {0x5A, 0, 0}, + {0x5B, 0x1f, 0}, + {0x5C, 0x20, 1}, + {0x5D, 0x1, 0}, + {0x5E, 0x30, 0}, + {0x5F, 0x70, 0}, + {0x60, 0, 0}, + {0x61, 0, 0}, + {0x62, 0x33, 1}, + {0x63, 0xf, 1}, + {0x64, 0xf, 1}, + {0x65, 0, 0}, + {0x66, 0x11, 0}, + {0x80, 0x3c, 0}, + {0x81, 0x1, 1}, + {0x82, 0xa, 0}, + {0x85, 0, 0}, + {0x86, 0x40, 0}, + {0x87, 0x40, 0}, + {0x88, 0x88, 0}, + {0x89, 0x10, 0}, + {0x8A, 0xf0, 0}, + {0x8B, 0x10, 0}, + {0x8C, 0xf0, 0}, + {0x8F, 0x10, 0}, + {0x90, 0x55, 0}, + {0x91, 0x3f, 1}, + {0x92, 0x36, 1}, + {0x93, 0, 0}, + {0x94, 0, 0}, + {0x95, 0, 0}, + {0x96, 0x87, 0}, + {0x97, 0x11, 0}, + {0x98, 0, 0}, + {0x99, 0x33, 0}, + {0x9A, 0x88, 0}, + {0xA1, 0x20, 1}, + {0xA2, 0x3f, 0}, + {0xA3, 0x44, 0}, + {0xA4, 0x8c, 0}, + {0xA5, 0x6c, 0}, + {0xA6, 0x22, 0}, + {0xA7, 0xbe, 0}, + {0xA8, 0x55, 0}, + {0xAA, 0xc, 0}, + {0xAB, 0xaa, 0}, + {0xAC, 0x2, 0}, + {0xAD, 0, 0}, + {0xAE, 0x10, 0}, + {0xAF, 0x1, 0}, + {0xB0, 0, 0}, + {0xB1, 0, 0}, + {0xB2, 0x80, 0}, + {0xB3, 0x60, 0}, + {0xB4, 0x44, 0}, + {0xB5, 0x55, 0}, + {0xB6, 0x1, 0}, + {0xB7, 0x55, 0}, + {0xB8, 0x1, 0}, + {0xB9, 0x5, 0}, + {0xBA, 0x55, 0}, + {0xBB, 0x55, 0}, + {0xC3, 0, 0}, + {0xC4, 0, 0}, + {0xC5, 0, 0}, + {0xC6, 0, 0}, + {0xC7, 0, 0}, + {0xC8, 0, 0}, + {0xC9, 0x1, 1}, + {0xCA, 0, 0}, + {0xCB, 0, 0}, + {0xCD, 0, 0}, + {0xCE, 0x5e, 0}, + {0xCF, 0xc, 0}, + {0xD0, 0xc, 0}, + {0xD1, 0xc, 0}, + {0xD2, 0, 0}, + {0xD3, 0x2b, 0}, + {0xD4, 0xc, 0}, + {0xD5, 0, 0}, + {0xD6, 0x70, 1}, + {0xDB, 0x7, 0}, + {0xDC, 0, 0}, + {0xDD, 0, 0}, + {0xDE, 0x88, 1}, + {0xDF, 0, 0}, + {0xE0, 0x1f, 0}, + {0xE1, 0x20, 1}, + {0xE2, 0x1, 0}, + {0xE3, 0x30, 0}, + {0xE4, 0x70, 0}, + {0xE5, 0, 0}, + {0xE6, 0, 0}, + {0xE7, 0x33, 0}, + {0xE8, 0xf, 1}, + {0xE9, 0xf, 1}, + {0xEA, 0, 0}, + {0xEB, 0x11, 0}, + {0x105, 0x3c, 0}, + {0x106, 0x1, 1}, + {0x107, 0xa, 0}, + {0x10A, 0, 0}, + {0x10B, 0x40, 0}, + {0x10C, 0x40, 0}, + {0x10D, 0x88, 0}, + {0x10E, 0x10, 0}, + {0x10F, 0xf0, 0}, + {0x110, 0x10, 0}, + {0x111, 0xf0, 0}, + {0x114, 0x10, 0}, + {0x115, 0x55, 0}, + {0x116, 0x3f, 1}, + {0x117, 0x36, 1}, + {0x118, 0, 0}, + {0x119, 0, 0}, + {0x11A, 0, 0}, + {0x11B, 0x87, 0}, + {0x11C, 0x11, 0}, + {0x11D, 0, 0}, + {0x11E, 0x33, 0}, + {0x11F, 0x88, 0}, + {0x126, 0x20, 1}, + {0x127, 0x3f, 0}, + {0x128, 0x44, 0}, + {0x129, 0x8c, 0}, + {0x12A, 0x6c, 0}, + {0x12B, 0x22, 0}, + {0x12C, 0xbe, 0}, + {0x12D, 0x55, 0}, + {0x12F, 0xc, 0}, + {0x130, 0xaa, 0}, + {0x131, 0x2, 0}, + {0x132, 0, 0}, + {0x133, 0x10, 0}, + {0x134, 0x1, 0}, + {0x135, 0, 0}, + {0x136, 0, 0}, + {0x137, 0x80, 0}, + {0x138, 0x60, 0}, + {0x139, 0x44, 0}, + {0x13A, 0x55, 0}, + {0x13B, 0x1, 0}, + {0x13C, 0x55, 0}, + {0x13D, 0x1, 0}, + {0x13E, 0x5, 0}, + {0x13F, 0x55, 0}, + {0x140, 0x55, 0}, + {0x148, 0, 0}, + {0x149, 0, 0}, + {0x14A, 0, 0}, + {0x14B, 0, 0}, + {0x14C, 0, 0}, + {0x14D, 0, 0}, + {0x14E, 0x1, 1}, + {0x14F, 0, 0}, + {0x150, 0, 0}, + {0x154, 0xc, 0}, + {0x155, 0xc, 0}, + {0x156, 0xc, 0}, + {0x157, 0, 0}, + {0x158, 0x2b, 0}, + {0x159, 0x84, 0}, + {0x15A, 0x15, 0}, + {0x15B, 0xf, 0}, + {0x15C, 0, 0}, + {0x15D, 0, 0}, + {0x15E, 0, 1}, + {0x15F, 0, 1}, + {0x160, 0, 1}, + {0x161, 0, 1}, + {0x162, 0, 1}, + {0x163, 0, 1}, + {0x164, 0, 0}, + {0x165, 0, 0}, + {0x166, 0, 0}, + {0x167, 0, 0}, + {0x168, 0, 0}, + {0x169, 0, 0}, + {0x16A, 0, 1}, + {0x16B, 0, 1}, + {0x16C, 0, 1}, + {0x16D, 0, 0}, + {0x170, 0, 0}, + {0x171, 0x77, 0}, + {0x172, 0x77, 0}, + {0x173, 0x77, 0}, + {0x174, 0x77, 0}, + {0x175, 0, 0}, + {0x176, 0x3, 0}, + {0x177, 0x37, 0}, + {0x178, 0x3, 0}, + {0x179, 0, 0}, + {0x17B, 0x21, 0}, + {0x17C, 0, 0}, + {0x17D, 0xaa, 0}, + {0x17E, 0, 0}, + {0x190, 0, 0}, + {0x191, 0x77, 0}, + {0x192, 0x77, 0}, + {0x193, 0x77, 0}, + {0x194, 0x77, 0}, + {0x195, 0, 0}, + {0x196, 0x3, 0}, + {0x197, 0x37, 0}, + {0x198, 0x3, 0}, + {0x199, 0, 0}, + {0x19B, 0x21, 0}, + {0x19C, 0, 0}, + {0x19D, 0xaa, 0}, + {0x19E, 0, 0}, + {0x1A1, 0x2, 0}, + {0x1A2, 0xf, 0}, + {0x1A3, 0xf, 0}, + {0x1A4, 0, 1}, + {0x1A5, 0, 1}, + {0x1A6, 0, 1}, + {0x1A7, 0x2, 0}, + {0x1A8, 0xf, 0}, + {0x1A9, 0xf, 0}, + {0x1AA, 0, 1}, + {0x1AB, 0, 1}, + {0x1AC, 0, 1}, + {0x1AD, 0x84, 0}, + {0x1AE, 0x60, 0}, + {0x1AF, 0x47, 0}, + {0x1B0, 0x47, 0}, + {0x1B1, 0, 0}, + {0x1B2, 0, 0}, + {0x1B3, 0, 0}, + {0x1B4, 0, 0}, + {0x1B5, 0, 0}, + {0x1B6, 0, 0}, + {0x1B7, 0xc, 1}, + {0x1B8, 0, 0}, + {0x1B9, 0, 0}, + {0x1BA, 0, 0}, + {0x1BB, 0, 0}, + {0x1BC, 0, 0}, + {0x1BD, 0, 0}, + {0x1BE, 0, 0}, + {0x1BF, 0, 0}, + {0x1C0, 0, 0}, + {0x1C1, 0x1, 1}, + {0x1C2, 0x80, 1}, + {0x1C3, 0, 0}, + {0x1C4, 0, 0}, + {0x1C5, 0, 0}, + {0x1C6, 0, 0}, + {0x1C7, 0, 0}, + {0x1C8, 0, 0}, + {0x1C9, 0, 0}, + {0x1CA, 0, 0}, + {0xFFFF, 0, 0} +}; + +static struct radio_20xx_regs regs_2057_rev7[] = { + {0x00, 0, 1}, + {0x01, 0x57, 1}, + {0x02, 0x20, 1}, + {0x03, 0x1f, 0}, + {0x04, 0x4, 0}, + {0x05, 0x2, 0}, + {0x06, 0x1, 0}, + {0x07, 0x1, 0}, + {0x08, 0x1, 0}, + {0x09, 0x69, 0}, + {0x0A, 0x66, 0}, + {0x0B, 0x6, 0}, + {0x0C, 0x18, 0}, + {0x0D, 0x3, 0}, + {0x0E, 0x20, 0}, + {0x0F, 0x20, 0}, + {0x10, 0, 0}, + {0x11, 0x7c, 0}, + {0x12, 0x42, 0}, + {0x13, 0xbd, 0}, + {0x14, 0x7, 0}, + {0x15, 0x87, 0}, + {0x16, 0x8, 0}, + {0x17, 0x17, 0}, + {0x18, 0x7, 0}, + {0x19, 0, 0}, + {0x1A, 0x2, 0}, + {0x1B, 0x13, 0}, + {0x1C, 0x3e, 0}, + {0x1D, 0x3e, 0}, + {0x1E, 0x96, 0}, + {0x1F, 0x4, 0}, + {0x20, 0, 0}, + {0x21, 0, 0}, + {0x22, 0x17, 0}, + {0x23, 0x6, 0}, + {0x24, 0x1, 0}, + {0x25, 0x6, 0}, + {0x26, 0x4, 0}, + {0x27, 0xd, 0}, + {0x28, 0xd, 0}, + {0x29, 0x30, 0}, + {0x2A, 0x32, 0}, + {0x2B, 0x8, 0}, + {0x2C, 0x1c, 0}, + {0x2D, 0x2, 0}, + {0x2E, 0x4, 0}, + {0x2F, 0x7f, 0}, + {0x30, 0x27, 0}, + {0x31, 0, 1}, + {0x32, 0, 1}, + {0x33, 0, 1}, + {0x34, 0, 0}, + {0x35, 0x20, 0}, + {0x36, 0x18, 0}, + {0x37, 0x7, 0}, + {0x38, 0x66, 0}, + {0x39, 0x66, 0}, + {0x3A, 0x66, 0}, + {0x3B, 0x66, 0}, + {0x3C, 0xff, 0}, + {0x3D, 0xff, 0}, + {0x3E, 0xff, 0}, + {0x3F, 0xff, 0}, + {0x40, 0x16, 0}, + {0x41, 0x7, 0}, + {0x42, 0x19, 0}, + {0x43, 0x7, 0}, + {0x44, 0x6, 0}, + {0x45, 0x3, 0}, + {0x46, 0x1, 0}, + {0x47, 0x7, 0}, + {0x48, 0x33, 0}, + {0x49, 0x5, 0}, + {0x4A, 0x77, 0}, + {0x4B, 0x66, 0}, + {0x4C, 0x66, 0}, + {0x4D, 0, 0}, + {0x4E, 0x4, 0}, + {0x4F, 0xc, 0}, + {0x50, 0, 0}, + {0x51, 0x70, 1}, + {0x56, 0x7, 0}, + {0x57, 0, 0}, + {0x58, 0, 0}, + {0x59, 0x88, 1}, + {0x5A, 0, 0}, + {0x5B, 0x1f, 0}, + {0x5C, 0x20, 1}, + {0x5D, 0x1, 0}, + {0x5E, 0x30, 0}, + {0x5F, 0x70, 0}, + {0x60, 0, 0}, + {0x61, 0, 0}, + {0x62, 0x33, 1}, + {0x63, 0xf, 1}, + {0x64, 0x13, 1}, + {0x65, 0, 0}, + {0x66, 0xee, 1}, + {0x69, 0, 0}, + {0x6A, 0x7e, 0}, + {0x6B, 0x3f, 0}, + {0x6C, 0x7f, 0}, + {0x6D, 0x78, 0}, + {0x6E, 0x58, 1}, + {0x6F, 0x88, 0}, + {0x70, 0x8, 0}, + {0x71, 0xf, 0}, + {0x72, 0xbc, 0}, + {0x73, 0x8, 0}, + {0x74, 0x60, 0}, + {0x75, 0x13, 1}, + {0x76, 0x70, 0}, + {0x77, 0, 0}, + {0x78, 0, 0}, + {0x79, 0, 0}, + {0x7A, 0x33, 0}, + {0x7B, 0x13, 1}, + {0x7C, 0x14, 1}, + {0x7D, 0xee, 1}, + {0x80, 0x3c, 0}, + {0x81, 0x1, 1}, + {0x82, 0xa, 0}, + {0x83, 0x9d, 0}, + {0x84, 0xa, 0}, + {0x85, 0, 0}, + {0x86, 0x40, 0}, + {0x87, 0x40, 0}, + {0x88, 0x88, 0}, + {0x89, 0x10, 0}, + {0x8A, 0xf0, 0}, + {0x8B, 0x10, 0}, + {0x8C, 0xf0, 0}, + {0x8D, 0, 0}, + {0x8E, 0, 0}, + {0x8F, 0x10, 0}, + {0x90, 0x55, 0}, + {0x91, 0x3f, 1}, + {0x92, 0x36, 1}, + {0x93, 0, 0}, + {0x94, 0, 0}, + {0x95, 0, 0}, + {0x96, 0x87, 0}, + {0x97, 0x11, 0}, + {0x98, 0, 0}, + {0x99, 0x33, 0}, + {0x9A, 0x88, 0}, + {0x9B, 0, 0}, + {0x9C, 0x87, 0}, + {0x9D, 0x11, 0}, + {0x9E, 0, 0}, + {0x9F, 0x33, 0}, + {0xA0, 0x88, 0}, + {0xA1, 0x20, 1}, + {0xA2, 0x3f, 0}, + {0xA3, 0x44, 0}, + {0xA4, 0x8c, 0}, + {0xA5, 0x6c, 0}, + {0xA6, 0x22, 0}, + {0xA7, 0xbe, 0}, + {0xA8, 0x55, 0}, + {0xAA, 0xc, 0}, + {0xAB, 0xaa, 0}, + {0xAC, 0x2, 0}, + {0xAD, 0, 0}, + {0xAE, 0x10, 0}, + {0xAF, 0x1, 0}, + {0xB0, 0, 0}, + {0xB1, 0, 0}, + {0xB2, 0x80, 0}, + {0xB3, 0x60, 0}, + {0xB4, 0x44, 0}, + {0xB5, 0x55, 0}, + {0xB6, 0x1, 0}, + {0xB7, 0x55, 0}, + {0xB8, 0x1, 0}, + {0xB9, 0x5, 0}, + {0xBA, 0x55, 0}, + {0xBB, 0x55, 0}, + {0xC1, 0, 0}, + {0xC2, 0, 0}, + {0xC3, 0, 0}, + {0xC4, 0, 0}, + {0xC5, 0, 0}, + {0xC6, 0, 0}, + {0xC7, 0, 0}, + {0xC8, 0, 0}, + {0xC9, 0, 0}, + {0xCA, 0, 0}, + {0xCB, 0, 0}, + {0xCC, 0, 0}, + {0xCD, 0, 0}, + {0xCE, 0x5e, 0}, + {0xCF, 0xc, 0}, + {0xD0, 0xc, 0}, + {0xD1, 0xc, 0}, + {0xD2, 0, 0}, + {0xD3, 0x2b, 0}, + {0xD4, 0xc, 0}, + {0xD5, 0, 0}, + {0xD6, 0x70, 1}, + {0xDB, 0x7, 0}, + {0xDC, 0, 0}, + {0xDD, 0, 0}, + {0xDE, 0x88, 1}, + {0xDF, 0, 0}, + {0xE0, 0x1f, 0}, + {0xE1, 0x20, 1}, + {0xE2, 0x1, 0}, + {0xE3, 0x30, 0}, + {0xE4, 0x70, 0}, + {0xE5, 0, 0}, + {0xE6, 0, 0}, + {0xE7, 0x33, 0}, + {0xE8, 0xf, 1}, + {0xE9, 0x13, 1}, + {0xEA, 0, 0}, + {0xEB, 0xee, 1}, + {0xEE, 0, 0}, + {0xEF, 0x7e, 0}, + {0xF0, 0x3f, 0}, + {0xF1, 0x7f, 0}, + {0xF2, 0x78, 0}, + {0xF3, 0x58, 1}, + {0xF4, 0x88, 0}, + {0xF5, 0x8, 0}, + {0xF6, 0xf, 0}, + {0xF7, 0xbc, 0}, + {0xF8, 0x8, 0}, + {0xF9, 0x60, 0}, + {0xFA, 0x13, 1}, + {0xFB, 0x70, 0}, + {0xFC, 0, 0}, + {0xFD, 0, 0}, + {0xFE, 0, 0}, + {0xFF, 0x33, 0}, + {0x100, 0x13, 1}, + {0x101, 0x14, 1}, + {0x102, 0xee, 1}, + {0x105, 0x3c, 0}, + {0x106, 0x1, 1}, + {0x107, 0xa, 0}, + {0x108, 0x9d, 0}, + {0x109, 0xa, 0}, + {0x10A, 0, 0}, + {0x10B, 0x40, 0}, + {0x10C, 0x40, 0}, + {0x10D, 0x88, 0}, + {0x10E, 0x10, 0}, + {0x10F, 0xf0, 0}, + {0x110, 0x10, 0}, + {0x111, 0xf0, 0}, + {0x112, 0, 0}, + {0x113, 0, 0}, + {0x114, 0x10, 0}, + {0x115, 0x55, 0}, + {0x116, 0x3f, 1}, + {0x117, 0x36, 1}, + {0x118, 0, 0}, + {0x119, 0, 0}, + {0x11A, 0, 0}, + {0x11B, 0x87, 0}, + {0x11C, 0x11, 0}, + {0x11D, 0, 0}, + {0x11E, 0x33, 0}, + {0x11F, 0x88, 0}, + {0x120, 0, 0}, + {0x121, 0x87, 0}, + {0x122, 0x11, 0}, + {0x123, 0, 0}, + {0x124, 0x33, 0}, + {0x125, 0x88, 0}, + {0x126, 0x20, 1}, + {0x127, 0x3f, 0}, + {0x128, 0x44, 0}, + {0x129, 0x8c, 0}, + {0x12A, 0x6c, 0}, + {0x12B, 0x22, 0}, + {0x12C, 0xbe, 0}, + {0x12D, 0x55, 0}, + {0x12F, 0xc, 0}, + {0x130, 0xaa, 0}, + {0x131, 0x2, 0}, + {0x132, 0, 0}, + {0x133, 0x10, 0}, + {0x134, 0x1, 0}, + {0x135, 0, 0}, + {0x136, 0, 0}, + {0x137, 0x80, 0}, + {0x138, 0x60, 0}, + {0x139, 0x44, 0}, + {0x13A, 0x55, 0}, + {0x13B, 0x1, 0}, + {0x13C, 0x55, 0}, + {0x13D, 0x1, 0}, + {0x13E, 0x5, 0}, + {0x13F, 0x55, 0}, + {0x140, 0x55, 0}, + {0x146, 0, 0}, + {0x147, 0, 0}, + {0x148, 0, 0}, + {0x149, 0, 0}, + {0x14A, 0, 0}, + {0x14B, 0, 0}, + {0x14C, 0, 0}, + {0x14D, 0, 0}, + {0x14E, 0, 0}, + {0x14F, 0, 0}, + {0x150, 0, 0}, + {0x151, 0, 0}, + {0x154, 0xc, 0}, + {0x155, 0xc, 0}, + {0x156, 0xc, 0}, + {0x157, 0, 0}, + {0x158, 0x2b, 0}, + {0x159, 0x84, 0}, + {0x15A, 0x15, 0}, + {0x15B, 0xf, 0}, + {0x15C, 0, 0}, + {0x15D, 0, 0}, + {0x15E, 0, 1}, + {0x15F, 0, 1}, + {0x160, 0, 1}, + {0x161, 0, 1}, + {0x162, 0, 1}, + {0x163, 0, 1}, + {0x164, 0, 0}, + {0x165, 0, 0}, + {0x166, 0, 0}, + {0x167, 0, 0}, + {0x168, 0, 0}, + {0x169, 0, 0}, + {0x16A, 0, 1}, + {0x16B, 0, 1}, + {0x16C, 0, 1}, + {0x16D, 0, 0}, + {0x170, 0, 0}, + {0x171, 0x77, 0}, + {0x172, 0x77, 0}, + {0x173, 0x77, 0}, + {0x174, 0x77, 0}, + {0x175, 0, 0}, + {0x176, 0x3, 0}, + {0x177, 0x37, 0}, + {0x178, 0x3, 0}, + {0x179, 0, 0}, + {0x17A, 0x21, 0}, + {0x17B, 0x21, 0}, + {0x17C, 0, 0}, + {0x17D, 0xaa, 0}, + {0x17E, 0, 0}, + {0x17F, 0xaa, 0}, + {0x180, 0, 0}, + {0x190, 0, 0}, + {0x191, 0x77, 0}, + {0x192, 0x77, 0}, + {0x193, 0x77, 0}, + {0x194, 0x77, 0}, + {0x195, 0, 0}, + {0x196, 0x3, 0}, + {0x197, 0x37, 0}, + {0x198, 0x3, 0}, + {0x199, 0, 0}, + {0x19A, 0x21, 0}, + {0x19B, 0x21, 0}, + {0x19C, 0, 0}, + {0x19D, 0xaa, 0}, + {0x19E, 0, 0}, + {0x19F, 0xaa, 0}, + {0x1A0, 0, 0}, + {0x1A1, 0x2, 0}, + {0x1A2, 0xf, 0}, + {0x1A3, 0xf, 0}, + {0x1A4, 0, 1}, + {0x1A5, 0, 1}, + {0x1A6, 0, 1}, + {0x1A7, 0x2, 0}, + {0x1A8, 0xf, 0}, + {0x1A9, 0xf, 0}, + {0x1AA, 0, 1}, + {0x1AB, 0, 1}, + {0x1AC, 0, 1}, + {0x1AD, 0x84, 0}, + {0x1AE, 0x60, 0}, + {0x1AF, 0x47, 0}, + {0x1B0, 0x47, 0}, + {0x1B1, 0, 0}, + {0x1B2, 0, 0}, + {0x1B3, 0, 0}, + {0x1B4, 0, 0}, + {0x1B5, 0, 0}, + {0x1B6, 0, 0}, + {0x1B7, 0x5, 1}, + {0x1B8, 0, 0}, + {0x1B9, 0, 0}, + {0x1BA, 0, 0}, + {0x1BB, 0, 0}, + {0x1BC, 0, 0}, + {0x1BD, 0, 0}, + {0x1BE, 0, 0}, + {0x1BF, 0, 0}, + {0x1C0, 0, 0}, + {0x1C1, 0, 0}, + {0x1C2, 0xa0, 1}, + {0x1C3, 0, 0}, + {0x1C4, 0, 0}, + {0x1C5, 0, 0}, + {0x1C6, 0, 0}, + {0x1C7, 0, 0}, + {0x1C8, 0, 0}, + {0x1C9, 0, 0}, + {0x1CA, 0, 0}, + {0xFFFF, 0, 0} +}; + +static struct radio_20xx_regs regs_2057_rev8[] = { + {0x00, 0x8, 1}, + {0x01, 0x57, 1}, + {0x02, 0x20, 1}, + {0x03, 0x1f, 0}, + {0x04, 0x4, 0}, + {0x05, 0x2, 0}, + {0x06, 0x1, 0}, + {0x07, 0x1, 0}, + {0x08, 0x1, 0}, + {0x09, 0x69, 0}, + {0x0A, 0x66, 0}, + {0x0B, 0x6, 0}, + {0x0C, 0x18, 0}, + {0x0D, 0x3, 0}, + {0x0E, 0x20, 0}, + {0x0F, 0x20, 0}, + {0x10, 0, 0}, + {0x11, 0x7c, 0}, + {0x12, 0x42, 0}, + {0x13, 0xbd, 0}, + {0x14, 0x7, 0}, + {0x15, 0x87, 0}, + {0x16, 0x8, 0}, + {0x17, 0x17, 0}, + {0x18, 0x7, 0}, + {0x19, 0, 0}, + {0x1A, 0x2, 0}, + {0x1B, 0x13, 0}, + {0x1C, 0x3e, 0}, + {0x1D, 0x3e, 0}, + {0x1E, 0x96, 0}, + {0x1F, 0x4, 0}, + {0x20, 0, 0}, + {0x21, 0, 0}, + {0x22, 0x17, 0}, + {0x23, 0x6, 0}, + {0x24, 0x1, 0}, + {0x25, 0x6, 0}, + {0x26, 0x4, 0}, + {0x27, 0xd, 0}, + {0x28, 0xd, 0}, + {0x29, 0x30, 0}, + {0x2A, 0x32, 0}, + {0x2B, 0x8, 0}, + {0x2C, 0x1c, 0}, + {0x2D, 0x2, 0}, + {0x2E, 0x4, 0}, + {0x2F, 0x7f, 0}, + {0x30, 0x27, 0}, + {0x31, 0, 1}, + {0x32, 0, 1}, + {0x33, 0, 1}, + {0x34, 0, 0}, + {0x35, 0x20, 0}, + {0x36, 0x18, 0}, + {0x37, 0x7, 0}, + {0x38, 0x66, 0}, + {0x39, 0x66, 0}, + {0x3A, 0x66, 0}, + {0x3B, 0x66, 0}, + {0x3C, 0xff, 0}, + {0x3D, 0xff, 0}, + {0x3E, 0xff, 0}, + {0x3F, 0xff, 0}, + {0x40, 0x16, 0}, + {0x41, 0x7, 0}, + {0x42, 0x19, 0}, + {0x43, 0x7, 0}, + {0x44, 0x6, 0}, + {0x45, 0x3, 0}, + {0x46, 0x1, 0}, + {0x47, 0x7, 0}, + {0x48, 0x33, 0}, + {0x49, 0x5, 0}, + {0x4A, 0x77, 0}, + {0x4B, 0x66, 0}, + {0x4C, 0x66, 0}, + {0x4D, 0, 0}, + {0x4E, 0x4, 0}, + {0x4F, 0xc, 0}, + {0x50, 0, 0}, + {0x51, 0x70, 1}, + {0x56, 0x7, 0}, + {0x57, 0, 0}, + {0x58, 0, 0}, + {0x59, 0x88, 1}, + {0x5A, 0, 0}, + {0x5B, 0x1f, 0}, + {0x5C, 0x20, 1}, + {0x5D, 0x1, 0}, + {0x5E, 0x30, 0}, + {0x5F, 0x70, 0}, + {0x60, 0, 0}, + {0x61, 0, 0}, + {0x62, 0x33, 1}, + {0x63, 0xf, 1}, + {0x64, 0xf, 1}, + {0x65, 0, 0}, + {0x66, 0x11, 0}, + {0x69, 0, 0}, + {0x6A, 0x7e, 0}, + {0x6B, 0x3f, 0}, + {0x6C, 0x7f, 0}, + {0x6D, 0x78, 0}, + {0x6E, 0x58, 1}, + {0x6F, 0x88, 0}, + {0x70, 0x8, 0}, + {0x71, 0xf, 0}, + {0x72, 0xbc, 0}, + {0x73, 0x8, 0}, + {0x74, 0x60, 0}, + {0x75, 0x13, 1}, + {0x76, 0x70, 0}, + {0x77, 0, 0}, + {0x78, 0, 0}, + {0x79, 0, 0}, + {0x7A, 0x33, 0}, + {0x7B, 0x13, 1}, + {0x7C, 0xf, 1}, + {0x7D, 0xee, 1}, + {0x80, 0x3c, 0}, + {0x81, 0x1, 1}, + {0x82, 0xa, 0}, + {0x83, 0x9d, 0}, + {0x84, 0xa, 0}, + {0x85, 0, 0}, + {0x86, 0x40, 0}, + {0x87, 0x40, 0}, + {0x88, 0x88, 0}, + {0x89, 0x10, 0}, + {0x8A, 0xf0, 0}, + {0x8B, 0x10, 0}, + {0x8C, 0xf0, 0}, + {0x8D, 0, 0}, + {0x8E, 0, 0}, + {0x8F, 0x10, 0}, + {0x90, 0x55, 0}, + {0x91, 0x3f, 1}, + {0x92, 0x36, 1}, + {0x93, 0, 0}, + {0x94, 0, 0}, + {0x95, 0, 0}, + {0x96, 0x87, 0}, + {0x97, 0x11, 0}, + {0x98, 0, 0}, + {0x99, 0x33, 0}, + {0x9A, 0x88, 0}, + {0x9B, 0, 0}, + {0x9C, 0x87, 0}, + {0x9D, 0x11, 0}, + {0x9E, 0, 0}, + {0x9F, 0x33, 0}, + {0xA0, 0x88, 0}, + {0xA1, 0x20, 1}, + {0xA2, 0x3f, 0}, + {0xA3, 0x44, 0}, + {0xA4, 0x8c, 0}, + {0xA5, 0x6c, 0}, + {0xA6, 0x22, 0}, + {0xA7, 0xbe, 0}, + {0xA8, 0x55, 0}, + {0xAA, 0xc, 0}, + {0xAB, 0xaa, 0}, + {0xAC, 0x2, 0}, + {0xAD, 0, 0}, + {0xAE, 0x10, 0}, + {0xAF, 0x1, 0}, + {0xB0, 0, 0}, + {0xB1, 0, 0}, + {0xB2, 0x80, 0}, + {0xB3, 0x60, 0}, + {0xB4, 0x44, 0}, + {0xB5, 0x55, 0}, + {0xB6, 0x1, 0}, + {0xB7, 0x55, 0}, + {0xB8, 0x1, 0}, + {0xB9, 0x5, 0}, + {0xBA, 0x55, 0}, + {0xBB, 0x55, 0}, + {0xC1, 0, 0}, + {0xC2, 0, 0}, + {0xC3, 0, 0}, + {0xC4, 0, 0}, + {0xC5, 0, 0}, + {0xC6, 0, 0}, + {0xC7, 0, 0}, + {0xC8, 0, 0}, + {0xC9, 0x1, 1}, + {0xCA, 0, 0}, + {0xCB, 0, 0}, + {0xCC, 0, 0}, + {0xCD, 0, 0}, + {0xCE, 0x5e, 0}, + {0xCF, 0xc, 0}, + {0xD0, 0xc, 0}, + {0xD1, 0xc, 0}, + {0xD2, 0, 0}, + {0xD3, 0x2b, 0}, + {0xD4, 0xc, 0}, + {0xD5, 0, 0}, + {0xD6, 0x70, 1}, + {0xDB, 0x7, 0}, + {0xDC, 0, 0}, + {0xDD, 0, 0}, + {0xDE, 0x88, 1}, + {0xDF, 0, 0}, + {0xE0, 0x1f, 0}, + {0xE1, 0x20, 1}, + {0xE2, 0x1, 0}, + {0xE3, 0x30, 0}, + {0xE4, 0x70, 0}, + {0xE5, 0, 0}, + {0xE6, 0, 0}, + {0xE7, 0x33, 0}, + {0xE8, 0xf, 1}, + {0xE9, 0xf, 1}, + {0xEA, 0, 0}, + {0xEB, 0x11, 0}, + {0xEE, 0, 0}, + {0xEF, 0x7e, 0}, + {0xF0, 0x3f, 0}, + {0xF1, 0x7f, 0}, + {0xF2, 0x78, 0}, + {0xF3, 0x58, 1}, + {0xF4, 0x88, 0}, + {0xF5, 0x8, 0}, + {0xF6, 0xf, 0}, + {0xF7, 0xbc, 0}, + {0xF8, 0x8, 0}, + {0xF9, 0x60, 0}, + {0xFA, 0x13, 1}, + {0xFB, 0x70, 0}, + {0xFC, 0, 0}, + {0xFD, 0, 0}, + {0xFE, 0, 0}, + {0xFF, 0x33, 0}, + {0x100, 0x13, 1}, + {0x101, 0xf, 1}, + {0x102, 0xee, 1}, + {0x105, 0x3c, 0}, + {0x106, 0x1, 1}, + {0x107, 0xa, 0}, + {0x108, 0x9d, 0}, + {0x109, 0xa, 0}, + {0x10A, 0, 0}, + {0x10B, 0x40, 0}, + {0x10C, 0x40, 0}, + {0x10D, 0x88, 0}, + {0x10E, 0x10, 0}, + {0x10F, 0xf0, 0}, + {0x110, 0x10, 0}, + {0x111, 0xf0, 0}, + {0x112, 0, 0}, + {0x113, 0, 0}, + {0x114, 0x10, 0}, + {0x115, 0x55, 0}, + {0x116, 0x3f, 1}, + {0x117, 0x36, 1}, + {0x118, 0, 0}, + {0x119, 0, 0}, + {0x11A, 0, 0}, + {0x11B, 0x87, 0}, + {0x11C, 0x11, 0}, + {0x11D, 0, 0}, + {0x11E, 0x33, 0}, + {0x11F, 0x88, 0}, + {0x120, 0, 0}, + {0x121, 0x87, 0}, + {0x122, 0x11, 0}, + {0x123, 0, 0}, + {0x124, 0x33, 0}, + {0x125, 0x88, 0}, + {0x126, 0x20, 1}, + {0x127, 0x3f, 0}, + {0x128, 0x44, 0}, + {0x129, 0x8c, 0}, + {0x12A, 0x6c, 0}, + {0x12B, 0x22, 0}, + {0x12C, 0xbe, 0}, + {0x12D, 0x55, 0}, + {0x12F, 0xc, 0}, + {0x130, 0xaa, 0}, + {0x131, 0x2, 0}, + {0x132, 0, 0}, + {0x133, 0x10, 0}, + {0x134, 0x1, 0}, + {0x135, 0, 0}, + {0x136, 0, 0}, + {0x137, 0x80, 0}, + {0x138, 0x60, 0}, + {0x139, 0x44, 0}, + {0x13A, 0x55, 0}, + {0x13B, 0x1, 0}, + {0x13C, 0x55, 0}, + {0x13D, 0x1, 0}, + {0x13E, 0x5, 0}, + {0x13F, 0x55, 0}, + {0x140, 0x55, 0}, + {0x146, 0, 0}, + {0x147, 0, 0}, + {0x148, 0, 0}, + {0x149, 0, 0}, + {0x14A, 0, 0}, + {0x14B, 0, 0}, + {0x14C, 0, 0}, + {0x14D, 0, 0}, + {0x14E, 0x1, 1}, + {0x14F, 0, 0}, + {0x150, 0, 0}, + {0x151, 0, 0}, + {0x154, 0xc, 0}, + {0x155, 0xc, 0}, + {0x156, 0xc, 0}, + {0x157, 0, 0}, + {0x158, 0x2b, 0}, + {0x159, 0x84, 0}, + {0x15A, 0x15, 0}, + {0x15B, 0xf, 0}, + {0x15C, 0, 0}, + {0x15D, 0, 0}, + {0x15E, 0, 1}, + {0x15F, 0, 1}, + {0x160, 0, 1}, + {0x161, 0, 1}, + {0x162, 0, 1}, + {0x163, 0, 1}, + {0x164, 0, 0}, + {0x165, 0, 0}, + {0x166, 0, 0}, + {0x167, 0, 0}, + {0x168, 0, 0}, + {0x169, 0, 0}, + {0x16A, 0, 1}, + {0x16B, 0, 1}, + {0x16C, 0, 1}, + {0x16D, 0, 0}, + {0x170, 0, 0}, + {0x171, 0x77, 0}, + {0x172, 0x77, 0}, + {0x173, 0x77, 0}, + {0x174, 0x77, 0}, + {0x175, 0, 0}, + {0x176, 0x3, 0}, + {0x177, 0x37, 0}, + {0x178, 0x3, 0}, + {0x179, 0, 0}, + {0x17A, 0x21, 0}, + {0x17B, 0x21, 0}, + {0x17C, 0, 0}, + {0x17D, 0xaa, 0}, + {0x17E, 0, 0}, + {0x17F, 0xaa, 0}, + {0x180, 0, 0}, + {0x190, 0, 0}, + {0x191, 0x77, 0}, + {0x192, 0x77, 0}, + {0x193, 0x77, 0}, + {0x194, 0x77, 0}, + {0x195, 0, 0}, + {0x196, 0x3, 0}, + {0x197, 0x37, 0}, + {0x198, 0x3, 0}, + {0x199, 0, 0}, + {0x19A, 0x21, 0}, + {0x19B, 0x21, 0}, + {0x19C, 0, 0}, + {0x19D, 0xaa, 0}, + {0x19E, 0, 0}, + {0x19F, 0xaa, 0}, + {0x1A0, 0, 0}, + {0x1A1, 0x2, 0}, + {0x1A2, 0xf, 0}, + {0x1A3, 0xf, 0}, + {0x1A4, 0, 1}, + {0x1A5, 0, 1}, + {0x1A6, 0, 1}, + {0x1A7, 0x2, 0}, + {0x1A8, 0xf, 0}, + {0x1A9, 0xf, 0}, + {0x1AA, 0, 1}, + {0x1AB, 0, 1}, + {0x1AC, 0, 1}, + {0x1AD, 0x84, 0}, + {0x1AE, 0x60, 0}, + {0x1AF, 0x47, 0}, + {0x1B0, 0x47, 0}, + {0x1B1, 0, 0}, + {0x1B2, 0, 0}, + {0x1B3, 0, 0}, + {0x1B4, 0, 0}, + {0x1B5, 0, 0}, + {0x1B6, 0, 0}, + {0x1B7, 0x5, 1}, + {0x1B8, 0, 0}, + {0x1B9, 0, 0}, + {0x1BA, 0, 0}, + {0x1BB, 0, 0}, + {0x1BC, 0, 0}, + {0x1BD, 0, 0}, + {0x1BE, 0, 0}, + {0x1BF, 0, 0}, + {0x1C0, 0, 0}, + {0x1C1, 0, 0}, + {0x1C2, 0xa0, 1}, + {0x1C3, 0, 0}, + {0x1C4, 0, 0}, + {0x1C5, 0, 0}, + {0x1C6, 0, 0}, + {0x1C7, 0, 0}, + {0x1C8, 0, 0}, + {0x1C9, 0, 0}, + {0x1CA, 0, 0}, + {0xFFFF, 0, 0} +}; + +static s16 nphy_def_lnagains[] = { -2, 10, 19, 25 }; + +static s32 nphy_lnagain_est0[] = { -315, 40370 }; +static s32 nphy_lnagain_est1[] = { -224, 23242 }; + +static const u16 tbl_iqcal_gainparams_nphy[2][NPHY_IQCAL_NUMGAINS][8] = { + { + {0x000, 0, 0, 2, 0x69, 0x69, 0x69, 0x69}, + {0x700, 7, 0, 0, 0x69, 0x69, 0x69, 0x69}, + {0x710, 7, 1, 0, 0x68, 0x68, 0x68, 0x68}, + {0x720, 7, 2, 0, 0x67, 0x67, 0x67, 0x67}, + {0x730, 7, 3, 0, 0x66, 0x66, 0x66, 0x66}, + {0x740, 7, 4, 0, 0x65, 0x65, 0x65, 0x65}, + {0x741, 7, 4, 1, 0x65, 0x65, 0x65, 0x65}, + {0x742, 7, 4, 2, 0x65, 0x65, 0x65, 0x65}, + {0x743, 7, 4, 3, 0x65, 0x65, 0x65, 0x65} + }, + { + {0x000, 7, 0, 0, 0x79, 0x79, 0x79, 0x79}, + {0x700, 7, 0, 0, 0x79, 0x79, 0x79, 0x79}, + {0x710, 7, 1, 0, 0x79, 0x79, 0x79, 0x79}, + {0x720, 7, 2, 0, 0x78, 0x78, 0x78, 0x78}, + {0x730, 7, 3, 0, 0x78, 0x78, 0x78, 0x78}, + {0x740, 7, 4, 0, 0x78, 0x78, 0x78, 0x78}, + {0x741, 7, 4, 1, 0x78, 0x78, 0x78, 0x78}, + {0x742, 7, 4, 2, 0x78, 0x78, 0x78, 0x78}, + {0x743, 7, 4, 3, 0x78, 0x78, 0x78, 0x78} + } +}; + +static const u32 nphy_tpc_txgain[] = { + 0x03cc2b44, 0x03cc2b42, 0x03cc2a44, 0x03cc2a42, + 0x03cc2944, 0x03c82b44, 0x03c82b42, 0x03c82a44, + 0x03c82a42, 0x03c82944, 0x03c82942, 0x03c82844, + 0x03c82842, 0x03c42b44, 0x03c42b42, 0x03c42a44, + 0x03c42a42, 0x03c42944, 0x03c42942, 0x03c42844, + 0x03c42842, 0x03c42744, 0x03c42742, 0x03c42644, + 0x03c42642, 0x03c42544, 0x03c42542, 0x03c42444, + 0x03c42442, 0x03c02b44, 0x03c02b42, 0x03c02a44, + 0x03c02a42, 0x03c02944, 0x03c02942, 0x03c02844, + 0x03c02842, 0x03c02744, 0x03c02742, 0x03b02b44, + 0x03b02b42, 0x03b02a44, 0x03b02a42, 0x03b02944, + 0x03b02942, 0x03b02844, 0x03b02842, 0x03b02744, + 0x03b02742, 0x03b02644, 0x03b02642, 0x03b02544, + 0x03b02542, 0x03a02b44, 0x03a02b42, 0x03a02a44, + 0x03a02a42, 0x03a02944, 0x03a02942, 0x03a02844, + 0x03a02842, 0x03a02744, 0x03a02742, 0x03902b44, + 0x03902b42, 0x03902a44, 0x03902a42, 0x03902944, + 0x03902942, 0x03902844, 0x03902842, 0x03902744, + 0x03902742, 0x03902644, 0x03902642, 0x03902544, + 0x03902542, 0x03802b44, 0x03802b42, 0x03802a44, + 0x03802a42, 0x03802944, 0x03802942, 0x03802844, + 0x03802842, 0x03802744, 0x03802742, 0x03802644, + 0x03802642, 0x03802544, 0x03802542, 0x03802444, + 0x03802442, 0x03802344, 0x03802342, 0x03802244, + 0x03802242, 0x03802144, 0x03802142, 0x03802044, + 0x03802042, 0x03801f44, 0x03801f42, 0x03801e44, + 0x03801e42, 0x03801d44, 0x03801d42, 0x03801c44, + 0x03801c42, 0x03801b44, 0x03801b42, 0x03801a44, + 0x03801a42, 0x03801944, 0x03801942, 0x03801844, + 0x03801842, 0x03801744, 0x03801742, 0x03801644, + 0x03801642, 0x03801544, 0x03801542, 0x03801444, + 0x03801442, 0x03801344, 0x03801342, 0x00002b00 +}; + +static const u16 nphy_tpc_loscale[] = { + 256, 256, 271, 271, 287, 256, 256, 271, + 271, 287, 287, 304, 304, 256, 256, 271, + 271, 287, 287, 304, 304, 322, 322, 341, + 341, 362, 362, 383, 383, 256, 256, 271, + 271, 287, 287, 304, 304, 322, 322, 256, + 256, 271, 271, 287, 287, 304, 304, 322, + 322, 341, 341, 362, 362, 256, 256, 271, + 271, 287, 287, 304, 304, 322, 322, 256, + 256, 271, 271, 287, 287, 304, 304, 322, + 322, 341, 341, 362, 362, 256, 256, 271, + 271, 287, 287, 304, 304, 322, 322, 341, + 341, 362, 362, 383, 383, 406, 406, 430, + 430, 455, 455, 482, 482, 511, 511, 541, + 541, 573, 573, 607, 607, 643, 643, 681, + 681, 722, 722, 764, 764, 810, 810, 858, + 858, 908, 908, 962, 962, 1019, 1019, 256 +}; + +static u32 nphy_tpc_txgain_ipa[] = { + 0x5ff7002d, 0x5ff7002b, 0x5ff7002a, 0x5ff70029, + 0x5ff70028, 0x5ff70027, 0x5ff70026, 0x5ff70025, + 0x5ef7002d, 0x5ef7002b, 0x5ef7002a, 0x5ef70029, + 0x5ef70028, 0x5ef70027, 0x5ef70026, 0x5ef70025, + 0x5df7002d, 0x5df7002b, 0x5df7002a, 0x5df70029, + 0x5df70028, 0x5df70027, 0x5df70026, 0x5df70025, + 0x5cf7002d, 0x5cf7002b, 0x5cf7002a, 0x5cf70029, + 0x5cf70028, 0x5cf70027, 0x5cf70026, 0x5cf70025, + 0x5bf7002d, 0x5bf7002b, 0x5bf7002a, 0x5bf70029, + 0x5bf70028, 0x5bf70027, 0x5bf70026, 0x5bf70025, + 0x5af7002d, 0x5af7002b, 0x5af7002a, 0x5af70029, + 0x5af70028, 0x5af70027, 0x5af70026, 0x5af70025, + 0x59f7002d, 0x59f7002b, 0x59f7002a, 0x59f70029, + 0x59f70028, 0x59f70027, 0x59f70026, 0x59f70025, + 0x58f7002d, 0x58f7002b, 0x58f7002a, 0x58f70029, + 0x58f70028, 0x58f70027, 0x58f70026, 0x58f70025, + 0x57f7002d, 0x57f7002b, 0x57f7002a, 0x57f70029, + 0x57f70028, 0x57f70027, 0x57f70026, 0x57f70025, + 0x56f7002d, 0x56f7002b, 0x56f7002a, 0x56f70029, + 0x56f70028, 0x56f70027, 0x56f70026, 0x56f70025, + 0x55f7002d, 0x55f7002b, 0x55f7002a, 0x55f70029, + 0x55f70028, 0x55f70027, 0x55f70026, 0x55f70025, + 0x54f7002d, 0x54f7002b, 0x54f7002a, 0x54f70029, + 0x54f70028, 0x54f70027, 0x54f70026, 0x54f70025, + 0x53f7002d, 0x53f7002b, 0x53f7002a, 0x53f70029, + 0x53f70028, 0x53f70027, 0x53f70026, 0x53f70025, + 0x52f7002d, 0x52f7002b, 0x52f7002a, 0x52f70029, + 0x52f70028, 0x52f70027, 0x52f70026, 0x52f70025, + 0x51f7002d, 0x51f7002b, 0x51f7002a, 0x51f70029, + 0x51f70028, 0x51f70027, 0x51f70026, 0x51f70025, + 0x50f7002d, 0x50f7002b, 0x50f7002a, 0x50f70029, + 0x50f70028, 0x50f70027, 0x50f70026, 0x50f70025 +}; + +static u32 nphy_tpc_txgain_ipa_rev5[] = { + 0x1ff7002d, 0x1ff7002b, 0x1ff7002a, 0x1ff70029, + 0x1ff70028, 0x1ff70027, 0x1ff70026, 0x1ff70025, + 0x1ef7002d, 0x1ef7002b, 0x1ef7002a, 0x1ef70029, + 0x1ef70028, 0x1ef70027, 0x1ef70026, 0x1ef70025, + 0x1df7002d, 0x1df7002b, 0x1df7002a, 0x1df70029, + 0x1df70028, 0x1df70027, 0x1df70026, 0x1df70025, + 0x1cf7002d, 0x1cf7002b, 0x1cf7002a, 0x1cf70029, + 0x1cf70028, 0x1cf70027, 0x1cf70026, 0x1cf70025, + 0x1bf7002d, 0x1bf7002b, 0x1bf7002a, 0x1bf70029, + 0x1bf70028, 0x1bf70027, 0x1bf70026, 0x1bf70025, + 0x1af7002d, 0x1af7002b, 0x1af7002a, 0x1af70029, + 0x1af70028, 0x1af70027, 0x1af70026, 0x1af70025, + 0x19f7002d, 0x19f7002b, 0x19f7002a, 0x19f70029, + 0x19f70028, 0x19f70027, 0x19f70026, 0x19f70025, + 0x18f7002d, 0x18f7002b, 0x18f7002a, 0x18f70029, + 0x18f70028, 0x18f70027, 0x18f70026, 0x18f70025, + 0x17f7002d, 0x17f7002b, 0x17f7002a, 0x17f70029, + 0x17f70028, 0x17f70027, 0x17f70026, 0x17f70025, + 0x16f7002d, 0x16f7002b, 0x16f7002a, 0x16f70029, + 0x16f70028, 0x16f70027, 0x16f70026, 0x16f70025, + 0x15f7002d, 0x15f7002b, 0x15f7002a, 0x15f70029, + 0x15f70028, 0x15f70027, 0x15f70026, 0x15f70025, + 0x14f7002d, 0x14f7002b, 0x14f7002a, 0x14f70029, + 0x14f70028, 0x14f70027, 0x14f70026, 0x14f70025, + 0x13f7002d, 0x13f7002b, 0x13f7002a, 0x13f70029, + 0x13f70028, 0x13f70027, 0x13f70026, 0x13f70025, + 0x12f7002d, 0x12f7002b, 0x12f7002a, 0x12f70029, + 0x12f70028, 0x12f70027, 0x12f70026, 0x12f70025, + 0x11f7002d, 0x11f7002b, 0x11f7002a, 0x11f70029, + 0x11f70028, 0x11f70027, 0x11f70026, 0x11f70025, + 0x10f7002d, 0x10f7002b, 0x10f7002a, 0x10f70029, + 0x10f70028, 0x10f70027, 0x10f70026, 0x10f70025 +}; + +static u32 nphy_tpc_txgain_ipa_rev6[] = { + 0x0ff7002d, 0x0ff7002b, 0x0ff7002a, 0x0ff70029, + 0x0ff70028, 0x0ff70027, 0x0ff70026, 0x0ff70025, + 0x0ef7002d, 0x0ef7002b, 0x0ef7002a, 0x0ef70029, + 0x0ef70028, 0x0ef70027, 0x0ef70026, 0x0ef70025, + 0x0df7002d, 0x0df7002b, 0x0df7002a, 0x0df70029, + 0x0df70028, 0x0df70027, 0x0df70026, 0x0df70025, + 0x0cf7002d, 0x0cf7002b, 0x0cf7002a, 0x0cf70029, + 0x0cf70028, 0x0cf70027, 0x0cf70026, 0x0cf70025, + 0x0bf7002d, 0x0bf7002b, 0x0bf7002a, 0x0bf70029, + 0x0bf70028, 0x0bf70027, 0x0bf70026, 0x0bf70025, + 0x0af7002d, 0x0af7002b, 0x0af7002a, 0x0af70029, + 0x0af70028, 0x0af70027, 0x0af70026, 0x0af70025, + 0x09f7002d, 0x09f7002b, 0x09f7002a, 0x09f70029, + 0x09f70028, 0x09f70027, 0x09f70026, 0x09f70025, + 0x08f7002d, 0x08f7002b, 0x08f7002a, 0x08f70029, + 0x08f70028, 0x08f70027, 0x08f70026, 0x08f70025, + 0x07f7002d, 0x07f7002b, 0x07f7002a, 0x07f70029, + 0x07f70028, 0x07f70027, 0x07f70026, 0x07f70025, + 0x06f7002d, 0x06f7002b, 0x06f7002a, 0x06f70029, + 0x06f70028, 0x06f70027, 0x06f70026, 0x06f70025, + 0x05f7002d, 0x05f7002b, 0x05f7002a, 0x05f70029, + 0x05f70028, 0x05f70027, 0x05f70026, 0x05f70025, + 0x04f7002d, 0x04f7002b, 0x04f7002a, 0x04f70029, + 0x04f70028, 0x04f70027, 0x04f70026, 0x04f70025, + 0x03f7002d, 0x03f7002b, 0x03f7002a, 0x03f70029, + 0x03f70028, 0x03f70027, 0x03f70026, 0x03f70025, + 0x02f7002d, 0x02f7002b, 0x02f7002a, 0x02f70029, + 0x02f70028, 0x02f70027, 0x02f70026, 0x02f70025, + 0x01f7002d, 0x01f7002b, 0x01f7002a, 0x01f70029, + 0x01f70028, 0x01f70027, 0x01f70026, 0x01f70025, + 0x00f7002d, 0x00f7002b, 0x00f7002a, 0x00f70029, + 0x00f70028, 0x00f70027, 0x00f70026, 0x00f70025 +}; + +static u32 nphy_tpc_txgain_ipa_2g_2057rev3[] = { + 0x70ff0040, 0x70f7003e, 0x70ef003b, 0x70e70039, + 0x70df0037, 0x70d70036, 0x70cf0033, 0x70c70032, + 0x70bf0031, 0x70b7002f, 0x70af002e, 0x70a7002d, + 0x709f002d, 0x7097002c, 0x708f002c, 0x7087002c, + 0x707f002b, 0x7077002c, 0x706f002c, 0x7067002d, + 0x705f002e, 0x705f002b, 0x705f0029, 0x7057002a, + 0x70570028, 0x704f002a, 0x7047002c, 0x7047002a, + 0x70470028, 0x70470026, 0x70470024, 0x70470022, + 0x7047001f, 0x70370027, 0x70370024, 0x70370022, + 0x70370020, 0x7037001f, 0x7037001d, 0x7037001b, + 0x7037001a, 0x70370018, 0x70370017, 0x7027001e, + 0x7027001d, 0x7027001a, 0x701f0024, 0x701f0022, + 0x701f0020, 0x701f001f, 0x701f001d, 0x701f001b, + 0x701f001a, 0x701f0018, 0x701f0017, 0x701f0015, + 0x701f0014, 0x701f0013, 0x701f0012, 0x701f0011, + 0x70170019, 0x70170018, 0x70170016, 0x70170015, + 0x70170014, 0x70170013, 0x70170012, 0x70170010, + 0x70170010, 0x7017000f, 0x700f001d, 0x700f001b, + 0x700f001a, 0x700f0018, 0x700f0017, 0x700f0015, + 0x700f0015, 0x700f0013, 0x700f0013, 0x700f0011, + 0x700f0010, 0x700f0010, 0x700f000f, 0x700f000e, + 0x700f000d, 0x700f000c, 0x700f000b, 0x700f000b, + 0x700f000b, 0x700f000a, 0x700f0009, 0x700f0009, + 0x700f0009, 0x700f0008, 0x700f0007, 0x700f0007, + 0x700f0006, 0x700f0006, 0x700f0006, 0x700f0006, + 0x700f0005, 0x700f0005, 0x700f0005, 0x700f0004, + 0x700f0004, 0x700f0004, 0x700f0004, 0x700f0004, + 0x700f0004, 0x700f0003, 0x700f0003, 0x700f0003, + 0x700f0003, 0x700f0002, 0x700f0002, 0x700f0002, + 0x700f0002, 0x700f0002, 0x700f0002, 0x700f0001, + 0x700f0001, 0x700f0001, 0x700f0001, 0x700f0001, + 0x700f0001, 0x700f0001, 0x700f0001, 0x700f0001 +}; + +static u32 nphy_tpc_txgain_ipa_2g_2057rev4n6[] = { + 0xf0ff0040, 0xf0f7003e, 0xf0ef003b, 0xf0e70039, + 0xf0df0037, 0xf0d70036, 0xf0cf0033, 0xf0c70032, + 0xf0bf0031, 0xf0b7002f, 0xf0af002e, 0xf0a7002d, + 0xf09f002d, 0xf097002c, 0xf08f002c, 0xf087002c, + 0xf07f002b, 0xf077002c, 0xf06f002c, 0xf067002d, + 0xf05f002e, 0xf05f002b, 0xf05f0029, 0xf057002a, + 0xf0570028, 0xf04f002a, 0xf047002c, 0xf047002a, + 0xf0470028, 0xf0470026, 0xf0470024, 0xf0470022, + 0xf047001f, 0xf0370027, 0xf0370024, 0xf0370022, + 0xf0370020, 0xf037001f, 0xf037001d, 0xf037001b, + 0xf037001a, 0xf0370018, 0xf0370017, 0xf027001e, + 0xf027001d, 0xf027001a, 0xf01f0024, 0xf01f0022, + 0xf01f0020, 0xf01f001f, 0xf01f001d, 0xf01f001b, + 0xf01f001a, 0xf01f0018, 0xf01f0017, 0xf01f0015, + 0xf01f0014, 0xf01f0013, 0xf01f0012, 0xf01f0011, + 0xf0170019, 0xf0170018, 0xf0170016, 0xf0170015, + 0xf0170014, 0xf0170013, 0xf0170012, 0xf0170010, + 0xf0170010, 0xf017000f, 0xf00f001d, 0xf00f001b, + 0xf00f001a, 0xf00f0018, 0xf00f0017, 0xf00f0015, + 0xf00f0015, 0xf00f0013, 0xf00f0013, 0xf00f0011, + 0xf00f0010, 0xf00f0010, 0xf00f000f, 0xf00f000e, + 0xf00f000d, 0xf00f000c, 0xf00f000b, 0xf00f000b, + 0xf00f000b, 0xf00f000a, 0xf00f0009, 0xf00f0009, + 0xf00f0009, 0xf00f0008, 0xf00f0007, 0xf00f0007, + 0xf00f0006, 0xf00f0006, 0xf00f0006, 0xf00f0006, + 0xf00f0005, 0xf00f0005, 0xf00f0005, 0xf00f0004, + 0xf00f0004, 0xf00f0004, 0xf00f0004, 0xf00f0004, + 0xf00f0004, 0xf00f0003, 0xf00f0003, 0xf00f0003, + 0xf00f0003, 0xf00f0002, 0xf00f0002, 0xf00f0002, + 0xf00f0002, 0xf00f0002, 0xf00f0002, 0xf00f0001, + 0xf00f0001, 0xf00f0001, 0xf00f0001, 0xf00f0001, + 0xf00f0001, 0xf00f0001, 0xf00f0001, 0xf00f0001 +}; + +static u32 nphy_tpc_txgain_ipa_2g_2057rev5[] = { + 0x30ff0031, 0x30e70031, 0x30e7002e, 0x30cf002e, + 0x30bf002e, 0x30af002e, 0x309f002f, 0x307f0033, + 0x307f0031, 0x307f002e, 0x3077002e, 0x306f002e, + 0x3067002e, 0x305f002f, 0x30570030, 0x3057002d, + 0x304f002e, 0x30470031, 0x3047002e, 0x3047002c, + 0x30470029, 0x303f002c, 0x303f0029, 0x3037002d, + 0x3037002a, 0x30370028, 0x302f002c, 0x302f002a, + 0x302f0028, 0x302f0026, 0x3027002c, 0x30270029, + 0x30270027, 0x30270025, 0x30270023, 0x301f002c, + 0x301f002a, 0x301f0028, 0x301f0025, 0x301f0024, + 0x301f0022, 0x301f001f, 0x3017002d, 0x3017002b, + 0x30170028, 0x30170026, 0x30170024, 0x30170022, + 0x30170020, 0x3017001e, 0x3017001d, 0x3017001b, + 0x3017001a, 0x30170018, 0x30170017, 0x30170015, + 0x300f002c, 0x300f0029, 0x300f0027, 0x300f0024, + 0x300f0022, 0x300f0021, 0x300f001f, 0x300f001d, + 0x300f001b, 0x300f001a, 0x300f0018, 0x300f0017, + 0x300f0016, 0x300f0015, 0x300f0115, 0x300f0215, + 0x300f0315, 0x300f0415, 0x300f0515, 0x300f0615, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715 +}; + +static u32 nphy_tpc_txgain_ipa_2g_2057rev7[] = { + 0x30ff0031, 0x30e70031, 0x30e7002e, 0x30cf002e, + 0x30bf002e, 0x30af002e, 0x309f002f, 0x307f0033, + 0x307f0031, 0x307f002e, 0x3077002e, 0x306f002e, + 0x3067002e, 0x305f002f, 0x30570030, 0x3057002d, + 0x304f002e, 0x30470031, 0x3047002e, 0x3047002c, + 0x30470029, 0x303f002c, 0x303f0029, 0x3037002d, + 0x3037002a, 0x30370028, 0x302f002c, 0x302f002a, + 0x302f0028, 0x302f0026, 0x3027002c, 0x30270029, + 0x30270027, 0x30270025, 0x30270023, 0x301f002c, + 0x301f002a, 0x301f0028, 0x301f0025, 0x301f0024, + 0x301f0022, 0x301f001f, 0x3017002d, 0x3017002b, + 0x30170028, 0x30170026, 0x30170024, 0x30170022, + 0x30170020, 0x3017001e, 0x3017001d, 0x3017001b, + 0x3017001a, 0x30170018, 0x30170017, 0x30170015, + 0x300f002c, 0x300f0029, 0x300f0027, 0x300f0024, + 0x300f0022, 0x300f0021, 0x300f001f, 0x300f001d, + 0x300f001b, 0x300f001a, 0x300f0018, 0x300f0017, + 0x300f0016, 0x300f0015, 0x300f0115, 0x300f0215, + 0x300f0315, 0x300f0415, 0x300f0515, 0x300f0615, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715, + 0x300f0715, 0x300f0715, 0x300f0715, 0x300f0715 +}; + +static u32 nphy_tpc_txgain_ipa_5g[] = { + 0x7ff70035, 0x7ff70033, 0x7ff70032, 0x7ff70031, + 0x7ff7002f, 0x7ff7002e, 0x7ff7002d, 0x7ff7002b, + 0x7ff7002a, 0x7ff70029, 0x7ff70028, 0x7ff70027, + 0x7ff70026, 0x7ff70024, 0x7ff70023, 0x7ff70022, + 0x7ef70028, 0x7ef70027, 0x7ef70026, 0x7ef70025, + 0x7ef70024, 0x7ef70023, 0x7df70028, 0x7df70027, + 0x7df70026, 0x7df70025, 0x7df70024, 0x7df70023, + 0x7df70022, 0x7cf70029, 0x7cf70028, 0x7cf70027, + 0x7cf70026, 0x7cf70025, 0x7cf70023, 0x7cf70022, + 0x7bf70029, 0x7bf70028, 0x7bf70026, 0x7bf70025, + 0x7bf70024, 0x7bf70023, 0x7bf70022, 0x7bf70021, + 0x7af70029, 0x7af70028, 0x7af70027, 0x7af70026, + 0x7af70025, 0x7af70024, 0x7af70023, 0x7af70022, + 0x79f70029, 0x79f70028, 0x79f70027, 0x79f70026, + 0x79f70025, 0x79f70024, 0x79f70023, 0x79f70022, + 0x78f70029, 0x78f70028, 0x78f70027, 0x78f70026, + 0x78f70025, 0x78f70024, 0x78f70023, 0x78f70022, + 0x77f70029, 0x77f70028, 0x77f70027, 0x77f70026, + 0x77f70025, 0x77f70024, 0x77f70023, 0x77f70022, + 0x76f70029, 0x76f70028, 0x76f70027, 0x76f70026, + 0x76f70024, 0x76f70023, 0x76f70022, 0x76f70021, + 0x75f70029, 0x75f70028, 0x75f70027, 0x75f70026, + 0x75f70025, 0x75f70024, 0x75f70023, 0x74f70029, + 0x74f70028, 0x74f70026, 0x74f70025, 0x74f70024, + 0x74f70023, 0x74f70022, 0x73f70029, 0x73f70027, + 0x73f70026, 0x73f70025, 0x73f70024, 0x73f70023, + 0x73f70022, 0x72f70028, 0x72f70027, 0x72f70026, + 0x72f70025, 0x72f70024, 0x72f70023, 0x72f70022, + 0x71f70028, 0x71f70027, 0x71f70026, 0x71f70025, + 0x71f70024, 0x71f70023, 0x70f70028, 0x70f70027, + 0x70f70026, 0x70f70024, 0x70f70023, 0x70f70022, + 0x70f70021, 0x70f70020, 0x70f70020, 0x70f7001f +}; + +static u32 nphy_tpc_txgain_ipa_5g_2057[] = { + 0x7f7f0044, 0x7f7f0040, 0x7f7f003c, 0x7f7f0039, + 0x7f7f0036, 0x7e7f003c, 0x7e7f0038, 0x7e7f0035, + 0x7d7f003c, 0x7d7f0039, 0x7d7f0036, 0x7d7f0033, + 0x7c7f003b, 0x7c7f0037, 0x7c7f0034, 0x7b7f003a, + 0x7b7f0036, 0x7b7f0033, 0x7a7f003c, 0x7a7f0039, + 0x7a7f0036, 0x7a7f0033, 0x797f003b, 0x797f0038, + 0x797f0035, 0x797f0032, 0x787f003b, 0x787f0038, + 0x787f0035, 0x787f0032, 0x777f003a, 0x777f0037, + 0x777f0034, 0x777f0031, 0x767f003a, 0x767f0036, + 0x767f0033, 0x767f0031, 0x757f003a, 0x757f0037, + 0x757f0034, 0x747f003c, 0x747f0039, 0x747f0036, + 0x747f0033, 0x737f003b, 0x737f0038, 0x737f0035, + 0x737f0032, 0x727f0039, 0x727f0036, 0x727f0033, + 0x727f0030, 0x717f003a, 0x717f0037, 0x717f0034, + 0x707f003b, 0x707f0038, 0x707f0035, 0x707f0032, + 0x707f002f, 0x707f002d, 0x707f002a, 0x707f0028, + 0x707f0025, 0x707f0023, 0x707f0021, 0x707f0020, + 0x707f001e, 0x707f001c, 0x707f001b, 0x707f0019, + 0x707f0018, 0x707f0016, 0x707f0015, 0x707f0014, + 0x707f0013, 0x707f0012, 0x707f0011, 0x707f0010, + 0x707f000f, 0x707f000e, 0x707f000d, 0x707f000d, + 0x707f000c, 0x707f000b, 0x707f000b, 0x707f000a, + 0x707f0009, 0x707f0009, 0x707f0008, 0x707f0008, + 0x707f0007, 0x707f0007, 0x707f0007, 0x707f0006, + 0x707f0006, 0x707f0006, 0x707f0005, 0x707f0005, + 0x707f0005, 0x707f0004, 0x707f0004, 0x707f0004, + 0x707f0004, 0x707f0004, 0x707f0003, 0x707f0003, + 0x707f0003, 0x707f0003, 0x707f0003, 0x707f0003, + 0x707f0002, 0x707f0002, 0x707f0002, 0x707f0002, + 0x707f0002, 0x707f0002, 0x707f0002, 0x707f0002, + 0x707f0001, 0x707f0001, 0x707f0001, 0x707f0001, + 0x707f0001, 0x707f0001, 0x707f0001, 0x707f0001 +}; + +static u32 nphy_tpc_txgain_ipa_5g_2057rev7[] = { + 0x6f7f0031, 0x6f7f002e, 0x6f7f002c, 0x6f7f002a, + 0x6f7f0027, 0x6e7f002e, 0x6e7f002c, 0x6e7f002a, + 0x6d7f0030, 0x6d7f002d, 0x6d7f002a, 0x6d7f0028, + 0x6c7f0030, 0x6c7f002d, 0x6c7f002b, 0x6b7f002e, + 0x6b7f002c, 0x6b7f002a, 0x6b7f0027, 0x6a7f002e, + 0x6a7f002c, 0x6a7f002a, 0x697f0030, 0x697f002e, + 0x697f002b, 0x697f0029, 0x687f002f, 0x687f002d, + 0x687f002a, 0x687f0027, 0x677f002f, 0x677f002d, + 0x677f002a, 0x667f0031, 0x667f002e, 0x667f002c, + 0x667f002a, 0x657f0030, 0x657f002e, 0x657f002b, + 0x657f0029, 0x647f0030, 0x647f002d, 0x647f002b, + 0x647f0029, 0x637f002f, 0x637f002d, 0x637f002a, + 0x627f0030, 0x627f002d, 0x627f002b, 0x627f0029, + 0x617f0030, 0x617f002e, 0x617f002b, 0x617f0029, + 0x607f002f, 0x607f002d, 0x607f002a, 0x607f0027, + 0x607f0026, 0x607f0023, 0x607f0021, 0x607f0020, + 0x607f001e, 0x607f001c, 0x607f001a, 0x607f0019, + 0x607f0018, 0x607f0016, 0x607f0015, 0x607f0014, + 0x607f0012, 0x607f0012, 0x607f0011, 0x607f000f, + 0x607f000f, 0x607f000e, 0x607f000d, 0x607f000c, + 0x607f000c, 0x607f000b, 0x607f000b, 0x607f000a, + 0x607f0009, 0x607f0009, 0x607f0008, 0x607f0008, + 0x607f0008, 0x607f0007, 0x607f0007, 0x607f0006, + 0x607f0006, 0x607f0005, 0x607f0005, 0x607f0005, + 0x607f0005, 0x607f0005, 0x607f0004, 0x607f0004, + 0x607f0004, 0x607f0004, 0x607f0003, 0x607f0003, + 0x607f0003, 0x607f0003, 0x607f0002, 0x607f0002, + 0x607f0002, 0x607f0002, 0x607f0002, 0x607f0002, + 0x607f0002, 0x607f0002, 0x607f0002, 0x607f0002, + 0x607f0002, 0x607f0002, 0x607f0002, 0x607f0002, + 0x607f0002, 0x607f0001, 0x607f0001, 0x607f0001, + 0x607f0001, 0x607f0001, 0x607f0001, 0x607f0001 +}; + +static s8 nphy_papd_pga_gain_delta_ipa_2g[] = { + -114, -108, -98, -91, -84, -78, -70, -62, + -54, -46, -39, -31, -23, -15, -8, 0 +}; + +static s8 nphy_papd_pga_gain_delta_ipa_5g[] = { + -100, -95, -89, -83, -77, -70, -63, -56, + -48, -41, -33, -25, -19, -12, -6, 0 +}; + +static s16 nphy_papd_padgain_dlt_2g_2057rev3n4[] = { + -159, -113, -86, -72, -62, -54, -48, -43, + -39, -35, -31, -28, -25, -23, -20, -18, + -17, -15, -13, -11, -10, -8, -7, -6, + -5, -4, -3, -3, -2, -1, -1, 0 +}; + +static s16 nphy_papd_padgain_dlt_2g_2057rev5[] = { + -109, -109, -82, -68, -58, -50, -44, -39, + -35, -31, -28, -26, -23, -21, -19, -17, + -16, -14, -13, -11, -10, -9, -8, -7, + -5, -5, -4, -3, -2, -1, -1, 0 +}; + +static s16 nphy_papd_padgain_dlt_2g_2057rev7[] = { + -122, -122, -95, -80, -69, -61, -54, -49, + -43, -39, -35, -32, -28, -26, -23, -21, + -18, -16, -15, -13, -11, -10, -8, -7, + -6, -5, -4, -3, -2, -1, -1, 0 +}; + +static s8 nphy_papd_pgagain_dlt_5g_2057[] = { + -107, -101, -92, -85, -78, -71, -62, -55, + -47, -39, -32, -24, -19, -12, -6, 0 +}; + +static s8 nphy_papd_pgagain_dlt_5g_2057rev7[] = { + -110, -104, -95, -88, -81, -74, -66, -58, + -50, -44, -36, -28, -23, -15, -8, 0 +}; + +static u8 pad_gain_codes_used_2057rev5[] = { + 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, + 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 +}; + +static u8 pad_gain_codes_used_2057rev7[] = { + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, + 5, 4, 3, 2, 1 +}; + +static u8 pad_all_gain_codes_2057[] = { + 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, + 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, + 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, + 1, 0 +}; + +static u8 pga_all_gain_codes_2057[] = { + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 +}; + +static u32 nphy_papd_scaltbl[] = { + 0x0ae2002f, 0x0a3b0032, 0x09a70035, 0x09220038, + 0x0887003c, 0x081f003f, 0x07a20043, 0x07340047, + 0x06d2004b, 0x067a004f, 0x06170054, 0x05bf0059, + 0x0571005e, 0x051e0064, 0x04d3006a, 0x04910070, + 0x044c0077, 0x040f007e, 0x03d90085, 0x03a1008d, + 0x036f0095, 0x033d009e, 0x030b00a8, 0x02e000b2, + 0x02b900bc, 0x029200c7, 0x026d00d3, 0x024900e0, + 0x022900ed, 0x020a00fb, 0x01ec010a, 0x01d0011a, + 0x01b7012a, 0x019e013c, 0x0187014f, 0x01720162, + 0x015d0177, 0x0149018e, 0x013701a5, 0x012601be, + 0x011501d9, 0x010501f5, 0x00f70212, 0x00e90232, + 0x00dc0253, 0x00d00276, 0x00c4029c, 0x00b902c3, + 0x00af02ed, 0x00a5031a, 0x009c0349, 0x0093037a, + 0x008b03af, 0x008303e7, 0x007c0422, 0x00750461, + 0x006e04a3, 0x006804ea, 0x00620534, 0x005d0583, + 0x005805d7, 0x0053062f, 0x004e068d, 0x004a06f1 +}; + +static u32 nphy_tpc_txgain_rev3[] = { + 0x1f410044, 0x1f410042, 0x1f410040, 0x1f41003e, + 0x1f41003c, 0x1f41003b, 0x1f410039, 0x1f410037, + 0x1e410044, 0x1e410042, 0x1e410040, 0x1e41003e, + 0x1e41003c, 0x1e41003b, 0x1e410039, 0x1e410037, + 0x1d410044, 0x1d410042, 0x1d410040, 0x1d41003e, + 0x1d41003c, 0x1d41003b, 0x1d410039, 0x1d410037, + 0x1c410044, 0x1c410042, 0x1c410040, 0x1c41003e, + 0x1c41003c, 0x1c41003b, 0x1c410039, 0x1c410037, + 0x1b410044, 0x1b410042, 0x1b410040, 0x1b41003e, + 0x1b41003c, 0x1b41003b, 0x1b410039, 0x1b410037, + 0x1a410044, 0x1a410042, 0x1a410040, 0x1a41003e, + 0x1a41003c, 0x1a41003b, 0x1a410039, 0x1a410037, + 0x19410044, 0x19410042, 0x19410040, 0x1941003e, + 0x1941003c, 0x1941003b, 0x19410039, 0x19410037, + 0x18410044, 0x18410042, 0x18410040, 0x1841003e, + 0x1841003c, 0x1841003b, 0x18410039, 0x18410037, + 0x17410044, 0x17410042, 0x17410040, 0x1741003e, + 0x1741003c, 0x1741003b, 0x17410039, 0x17410037, + 0x16410044, 0x16410042, 0x16410040, 0x1641003e, + 0x1641003c, 0x1641003b, 0x16410039, 0x16410037, + 0x15410044, 0x15410042, 0x15410040, 0x1541003e, + 0x1541003c, 0x1541003b, 0x15410039, 0x15410037, + 0x14410044, 0x14410042, 0x14410040, 0x1441003e, + 0x1441003c, 0x1441003b, 0x14410039, 0x14410037, + 0x13410044, 0x13410042, 0x13410040, 0x1341003e, + 0x1341003c, 0x1341003b, 0x13410039, 0x13410037, + 0x12410044, 0x12410042, 0x12410040, 0x1241003e, + 0x1241003c, 0x1241003b, 0x12410039, 0x12410037, + 0x11410044, 0x11410042, 0x11410040, 0x1141003e, + 0x1141003c, 0x1141003b, 0x11410039, 0x11410037, + 0x10410044, 0x10410042, 0x10410040, 0x1041003e, + 0x1041003c, 0x1041003b, 0x10410039, 0x10410037 +}; + +static u32 nphy_tpc_txgain_HiPwrEPA[] = { + 0x0f410044, 0x0f410042, 0x0f410040, 0x0f41003e, + 0x0f41003c, 0x0f41003b, 0x0f410039, 0x0f410037, + 0x0e410044, 0x0e410042, 0x0e410040, 0x0e41003e, + 0x0e41003c, 0x0e41003b, 0x0e410039, 0x0e410037, + 0x0d410044, 0x0d410042, 0x0d410040, 0x0d41003e, + 0x0d41003c, 0x0d41003b, 0x0d410039, 0x0d410037, + 0x0c410044, 0x0c410042, 0x0c410040, 0x0c41003e, + 0x0c41003c, 0x0c41003b, 0x0c410039, 0x0c410037, + 0x0b410044, 0x0b410042, 0x0b410040, 0x0b41003e, + 0x0b41003c, 0x0b41003b, 0x0b410039, 0x0b410037, + 0x0a410044, 0x0a410042, 0x0a410040, 0x0a41003e, + 0x0a41003c, 0x0a41003b, 0x0a410039, 0x0a410037, + 0x09410044, 0x09410042, 0x09410040, 0x0941003e, + 0x0941003c, 0x0941003b, 0x09410039, 0x09410037, + 0x08410044, 0x08410042, 0x08410040, 0x0841003e, + 0x0841003c, 0x0841003b, 0x08410039, 0x08410037, + 0x07410044, 0x07410042, 0x07410040, 0x0741003e, + 0x0741003c, 0x0741003b, 0x07410039, 0x07410037, + 0x06410044, 0x06410042, 0x06410040, 0x0641003e, + 0x0641003c, 0x0641003b, 0x06410039, 0x06410037, + 0x05410044, 0x05410042, 0x05410040, 0x0541003e, + 0x0541003c, 0x0541003b, 0x05410039, 0x05410037, + 0x04410044, 0x04410042, 0x04410040, 0x0441003e, + 0x0441003c, 0x0441003b, 0x04410039, 0x04410037, + 0x03410044, 0x03410042, 0x03410040, 0x0341003e, + 0x0341003c, 0x0341003b, 0x03410039, 0x03410037, + 0x02410044, 0x02410042, 0x02410040, 0x0241003e, + 0x0241003c, 0x0241003b, 0x02410039, 0x02410037, + 0x01410044, 0x01410042, 0x01410040, 0x0141003e, + 0x0141003c, 0x0141003b, 0x01410039, 0x01410037, + 0x00410044, 0x00410042, 0x00410040, 0x0041003e, + 0x0041003c, 0x0041003b, 0x00410039, 0x00410037 +}; + +static u32 nphy_tpc_txgain_epa_2057rev3[] = { + 0x80f90040, 0x80e10040, 0x80e1003c, 0x80c9003d, + 0x80b9003c, 0x80a9003d, 0x80a1003c, 0x8099003b, + 0x8091003b, 0x8089003a, 0x8081003a, 0x80790039, + 0x80710039, 0x8069003a, 0x8061003b, 0x8059003d, + 0x8051003f, 0x80490042, 0x8049003e, 0x8049003b, + 0x8041003e, 0x8041003b, 0x8039003e, 0x8039003b, + 0x80390038, 0x80390035, 0x8031003a, 0x80310036, + 0x80310033, 0x8029003a, 0x80290037, 0x80290034, + 0x80290031, 0x80210039, 0x80210036, 0x80210033, + 0x80210030, 0x8019003c, 0x80190039, 0x80190036, + 0x80190033, 0x80190030, 0x8019002d, 0x8019002b, + 0x80190028, 0x8011003a, 0x80110036, 0x80110033, + 0x80110030, 0x8011002e, 0x8011002b, 0x80110029, + 0x80110027, 0x80110024, 0x80110022, 0x80110020, + 0x8011001f, 0x8011001d, 0x8009003a, 0x80090037, + 0x80090034, 0x80090031, 0x8009002e, 0x8009002c, + 0x80090029, 0x80090027, 0x80090025, 0x80090023, + 0x80090021, 0x8009001f, 0x8009001d, 0x8009011d, + 0x8009021d, 0x8009031d, 0x8009041d, 0x8009051d, + 0x8009061d, 0x8009071d, 0x8009071d, 0x8009071d, + 0x8009071d, 0x8009071d, 0x8009071d, 0x8009071d, + 0x8009071d, 0x8009071d, 0x8009071d, 0x8009071d, + 0x8009071d, 0x8009071d, 0x8009071d, 0x8009071d, + 0x8009071d, 0x8009071d, 0x8009071d, 0x8009071d, + 0x8009071d, 0x8009071d, 0x8009071d, 0x8009071d, + 0x8009071d, 0x8009071d, 0x8009071d, 0x8009071d, + 0x8009071d, 0x8009071d, 0x8009071d, 0x8009071d, + 0x8009071d, 0x8009071d, 0x8009071d, 0x8009071d, + 0x8009071d, 0x8009071d, 0x8009071d, 0x8009071d, + 0x8009071d, 0x8009071d, 0x8009071d, 0x8009071d, + 0x8009071d, 0x8009071d, 0x8009071d, 0x8009071d, + 0x8009071d, 0x8009071d, 0x8009071d, 0x8009071d +}; + +static u32 nphy_tpc_txgain_epa_2057rev5[] = { + 0x10f90040, 0x10e10040, 0x10e1003c, 0x10c9003d, + 0x10b9003c, 0x10a9003d, 0x10a1003c, 0x1099003b, + 0x1091003b, 0x1089003a, 0x1081003a, 0x10790039, + 0x10710039, 0x1069003a, 0x1061003b, 0x1059003d, + 0x1051003f, 0x10490042, 0x1049003e, 0x1049003b, + 0x1041003e, 0x1041003b, 0x1039003e, 0x1039003b, + 0x10390038, 0x10390035, 0x1031003a, 0x10310036, + 0x10310033, 0x1029003a, 0x10290037, 0x10290034, + 0x10290031, 0x10210039, 0x10210036, 0x10210033, + 0x10210030, 0x1019003c, 0x10190039, 0x10190036, + 0x10190033, 0x10190030, 0x1019002d, 0x1019002b, + 0x10190028, 0x1011003a, 0x10110036, 0x10110033, + 0x10110030, 0x1011002e, 0x1011002b, 0x10110029, + 0x10110027, 0x10110024, 0x10110022, 0x10110020, + 0x1011001f, 0x1011001d, 0x1009003a, 0x10090037, + 0x10090034, 0x10090031, 0x1009002e, 0x1009002c, + 0x10090029, 0x10090027, 0x10090025, 0x10090023, + 0x10090021, 0x1009001f, 0x1009001d, 0x1009001b, + 0x1009001a, 0x10090018, 0x10090017, 0x10090016, + 0x10090015, 0x10090013, 0x10090012, 0x10090011, + 0x10090010, 0x1009000f, 0x1009000f, 0x1009000e, + 0x1009000d, 0x1009000c, 0x1009000c, 0x1009000b, + 0x1009000a, 0x1009000a, 0x10090009, 0x10090009, + 0x10090008, 0x10090008, 0x10090007, 0x10090007, + 0x10090007, 0x10090006, 0x10090006, 0x10090005, + 0x10090005, 0x10090005, 0x10090005, 0x10090004, + 0x10090004, 0x10090004, 0x10090004, 0x10090003, + 0x10090003, 0x10090003, 0x10090003, 0x10090003, + 0x10090003, 0x10090002, 0x10090002, 0x10090002, + 0x10090002, 0x10090002, 0x10090002, 0x10090002, + 0x10090002, 0x10090002, 0x10090001, 0x10090001, + 0x10090001, 0x10090001, 0x10090001, 0x10090001 +}; + +static u32 nphy_tpc_5GHz_txgain_rev3[] = { + 0xcff70044, 0xcff70042, 0xcff70040, 0xcff7003e, + 0xcff7003c, 0xcff7003b, 0xcff70039, 0xcff70037, + 0xcef70044, 0xcef70042, 0xcef70040, 0xcef7003e, + 0xcef7003c, 0xcef7003b, 0xcef70039, 0xcef70037, + 0xcdf70044, 0xcdf70042, 0xcdf70040, 0xcdf7003e, + 0xcdf7003c, 0xcdf7003b, 0xcdf70039, 0xcdf70037, + 0xccf70044, 0xccf70042, 0xccf70040, 0xccf7003e, + 0xccf7003c, 0xccf7003b, 0xccf70039, 0xccf70037, + 0xcbf70044, 0xcbf70042, 0xcbf70040, 0xcbf7003e, + 0xcbf7003c, 0xcbf7003b, 0xcbf70039, 0xcbf70037, + 0xcaf70044, 0xcaf70042, 0xcaf70040, 0xcaf7003e, + 0xcaf7003c, 0xcaf7003b, 0xcaf70039, 0xcaf70037, + 0xc9f70044, 0xc9f70042, 0xc9f70040, 0xc9f7003e, + 0xc9f7003c, 0xc9f7003b, 0xc9f70039, 0xc9f70037, + 0xc8f70044, 0xc8f70042, 0xc8f70040, 0xc8f7003e, + 0xc8f7003c, 0xc8f7003b, 0xc8f70039, 0xc8f70037, + 0xc7f70044, 0xc7f70042, 0xc7f70040, 0xc7f7003e, + 0xc7f7003c, 0xc7f7003b, 0xc7f70039, 0xc7f70037, + 0xc6f70044, 0xc6f70042, 0xc6f70040, 0xc6f7003e, + 0xc6f7003c, 0xc6f7003b, 0xc6f70039, 0xc6f70037, + 0xc5f70044, 0xc5f70042, 0xc5f70040, 0xc5f7003e, + 0xc5f7003c, 0xc5f7003b, 0xc5f70039, 0xc5f70037, + 0xc4f70044, 0xc4f70042, 0xc4f70040, 0xc4f7003e, + 0xc4f7003c, 0xc4f7003b, 0xc4f70039, 0xc4f70037, + 0xc3f70044, 0xc3f70042, 0xc3f70040, 0xc3f7003e, + 0xc3f7003c, 0xc3f7003b, 0xc3f70039, 0xc3f70037, + 0xc2f70044, 0xc2f70042, 0xc2f70040, 0xc2f7003e, + 0xc2f7003c, 0xc2f7003b, 0xc2f70039, 0xc2f70037, + 0xc1f70044, 0xc1f70042, 0xc1f70040, 0xc1f7003e, + 0xc1f7003c, 0xc1f7003b, 0xc1f70039, 0xc1f70037, + 0xc0f70044, 0xc0f70042, 0xc0f70040, 0xc0f7003e, + 0xc0f7003c, 0xc0f7003b, 0xc0f70039, 0xc0f70037 +}; + +static u32 nphy_tpc_5GHz_txgain_rev4[] = { + 0x2ff20044, 0x2ff20042, 0x2ff20040, 0x2ff2003e, + 0x2ff2003c, 0x2ff2003b, 0x2ff20039, 0x2ff20037, + 0x2ef20044, 0x2ef20042, 0x2ef20040, 0x2ef2003e, + 0x2ef2003c, 0x2ef2003b, 0x2ef20039, 0x2ef20037, + 0x2df20044, 0x2df20042, 0x2df20040, 0x2df2003e, + 0x2df2003c, 0x2df2003b, 0x2df20039, 0x2df20037, + 0x2cf20044, 0x2cf20042, 0x2cf20040, 0x2cf2003e, + 0x2cf2003c, 0x2cf2003b, 0x2cf20039, 0x2cf20037, + 0x2bf20044, 0x2bf20042, 0x2bf20040, 0x2bf2003e, + 0x2bf2003c, 0x2bf2003b, 0x2bf20039, 0x2bf20037, + 0x2af20044, 0x2af20042, 0x2af20040, 0x2af2003e, + 0x2af2003c, 0x2af2003b, 0x2af20039, 0x2af20037, + 0x29f20044, 0x29f20042, 0x29f20040, 0x29f2003e, + 0x29f2003c, 0x29f2003b, 0x29f20039, 0x29f20037, + 0x28f20044, 0x28f20042, 0x28f20040, 0x28f2003e, + 0x28f2003c, 0x28f2003b, 0x28f20039, 0x28f20037, + 0x27f20044, 0x27f20042, 0x27f20040, 0x27f2003e, + 0x27f2003c, 0x27f2003b, 0x27f20039, 0x27f20037, + 0x26f20044, 0x26f20042, 0x26f20040, 0x26f2003e, + 0x26f2003c, 0x26f2003b, 0x26f20039, 0x26f20037, + 0x25f20044, 0x25f20042, 0x25f20040, 0x25f2003e, + 0x25f2003c, 0x25f2003b, 0x25f20039, 0x25f20037, + 0x24f20044, 0x24f20042, 0x24f20040, 0x24f2003e, + 0x24f2003c, 0x24f2003b, 0x24f20039, 0x24f20038, + 0x23f20041, 0x23f20040, 0x23f2003f, 0x23f2003e, + 0x23f2003c, 0x23f2003b, 0x23f20039, 0x23f20037, + 0x22f20044, 0x22f20042, 0x22f20040, 0x22f2003e, + 0x22f2003c, 0x22f2003b, 0x22f20039, 0x22f20037, + 0x21f20044, 0x21f20042, 0x21f20040, 0x21f2003e, + 0x21f2003c, 0x21f2003b, 0x21f20039, 0x21f20037, + 0x20d20043, 0x20d20041, 0x20d2003e, 0x20d2003c, + 0x20d2003a, 0x20d20038, 0x20d20036, 0x20d20034 +}; + +static u32 nphy_tpc_5GHz_txgain_rev5[] = { + 0x0f62004a, 0x0f620048, 0x0f620046, 0x0f620044, + 0x0f620042, 0x0f620040, 0x0f62003e, 0x0f62003c, + 0x0e620044, 0x0e620042, 0x0e620040, 0x0e62003e, + 0x0e62003c, 0x0e62003d, 0x0e62003b, 0x0e62003a, + 0x0d620043, 0x0d620041, 0x0d620040, 0x0d62003e, + 0x0d62003d, 0x0d62003c, 0x0d62003b, 0x0d62003a, + 0x0c620041, 0x0c620040, 0x0c62003f, 0x0c62003e, + 0x0c62003c, 0x0c62003b, 0x0c620039, 0x0c620037, + 0x0b620046, 0x0b620044, 0x0b620042, 0x0b620040, + 0x0b62003e, 0x0b62003c, 0x0b62003b, 0x0b62003a, + 0x0a620041, 0x0a620040, 0x0a62003e, 0x0a62003c, + 0x0a62003b, 0x0a62003a, 0x0a620039, 0x0a620038, + 0x0962003e, 0x0962003d, 0x0962003c, 0x0962003b, + 0x09620039, 0x09620037, 0x09620035, 0x09620033, + 0x08620044, 0x08620042, 0x08620040, 0x0862003e, + 0x0862003c, 0x0862003b, 0x0862003a, 0x08620039, + 0x07620043, 0x07620042, 0x07620040, 0x0762003f, + 0x0762003d, 0x0762003b, 0x0762003a, 0x07620039, + 0x0662003e, 0x0662003d, 0x0662003c, 0x0662003b, + 0x06620039, 0x06620037, 0x06620035, 0x06620033, + 0x05620046, 0x05620044, 0x05620042, 0x05620040, + 0x0562003e, 0x0562003c, 0x0562003b, 0x05620039, + 0x04620044, 0x04620042, 0x04620040, 0x0462003e, + 0x0462003c, 0x0462003b, 0x04620039, 0x04620038, + 0x0362003c, 0x0362003b, 0x0362003a, 0x03620039, + 0x03620038, 0x03620037, 0x03620035, 0x03620033, + 0x0262004c, 0x0262004a, 0x02620048, 0x02620047, + 0x02620046, 0x02620044, 0x02620043, 0x02620042, + 0x0162004a, 0x01620048, 0x01620046, 0x01620044, + 0x01620043, 0x01620042, 0x01620041, 0x01620040, + 0x00620042, 0x00620040, 0x0062003e, 0x0062003c, + 0x0062003b, 0x00620039, 0x00620037, 0x00620035 +}; + +static u32 nphy_tpc_5GHz_txgain_HiPwrEPA[] = { + 0x2ff10044, 0x2ff10042, 0x2ff10040, 0x2ff1003e, + 0x2ff1003c, 0x2ff1003b, 0x2ff10039, 0x2ff10037, + 0x2ef10044, 0x2ef10042, 0x2ef10040, 0x2ef1003e, + 0x2ef1003c, 0x2ef1003b, 0x2ef10039, 0x2ef10037, + 0x2df10044, 0x2df10042, 0x2df10040, 0x2df1003e, + 0x2df1003c, 0x2df1003b, 0x2df10039, 0x2df10037, + 0x2cf10044, 0x2cf10042, 0x2cf10040, 0x2cf1003e, + 0x2cf1003c, 0x2cf1003b, 0x2cf10039, 0x2cf10037, + 0x2bf10044, 0x2bf10042, 0x2bf10040, 0x2bf1003e, + 0x2bf1003c, 0x2bf1003b, 0x2bf10039, 0x2bf10037, + 0x2af10044, 0x2af10042, 0x2af10040, 0x2af1003e, + 0x2af1003c, 0x2af1003b, 0x2af10039, 0x2af10037, + 0x29f10044, 0x29f10042, 0x29f10040, 0x29f1003e, + 0x29f1003c, 0x29f1003b, 0x29f10039, 0x29f10037, + 0x28f10044, 0x28f10042, 0x28f10040, 0x28f1003e, + 0x28f1003c, 0x28f1003b, 0x28f10039, 0x28f10037, + 0x27f10044, 0x27f10042, 0x27f10040, 0x27f1003e, + 0x27f1003c, 0x27f1003b, 0x27f10039, 0x27f10037, + 0x26f10044, 0x26f10042, 0x26f10040, 0x26f1003e, + 0x26f1003c, 0x26f1003b, 0x26f10039, 0x26f10037, + 0x25f10044, 0x25f10042, 0x25f10040, 0x25f1003e, + 0x25f1003c, 0x25f1003b, 0x25f10039, 0x25f10037, + 0x24f10044, 0x24f10042, 0x24f10040, 0x24f1003e, + 0x24f1003c, 0x24f1003b, 0x24f10039, 0x24f10038, + 0x23f10041, 0x23f10040, 0x23f1003f, 0x23f1003e, + 0x23f1003c, 0x23f1003b, 0x23f10039, 0x23f10037, + 0x22f10044, 0x22f10042, 0x22f10040, 0x22f1003e, + 0x22f1003c, 0x22f1003b, 0x22f10039, 0x22f10037, + 0x21f10044, 0x21f10042, 0x21f10040, 0x21f1003e, + 0x21f1003c, 0x21f1003b, 0x21f10039, 0x21f10037, + 0x20d10043, 0x20d10041, 0x20d1003e, 0x20d1003c, + 0x20d1003a, 0x20d10038, 0x20d10036, 0x20d10034 +}; + +static u8 ant_sw_ctrl_tbl_rev8_2o3[] = { 0x14, 0x18 }; +static u8 ant_sw_ctrl_tbl_rev8[] = { 0x4, 0x8, 0x4, 0x8, 0x11, 0x12 }; +static u8 ant_sw_ctrl_tbl_rev8_2057v7_core0[] = { + 0x09, 0x0a, 0x15, 0x16, 0x09, 0x0a +}; +static u8 ant_sw_ctrl_tbl_rev8_2057v7_core1[] = { + 0x09, 0x0a, 0x09, 0x0a, 0x15, 0x16 +}; + +bool wlc_phy_bist_check_phy(struct brcms_phy_pub *pih) +{ + struct brcms_phy *pi = (struct brcms_phy *) pih; + u32 phybist0, phybist1, phybist2, phybist3, phybist4; + + if (NREV_GE(pi->pubpi.phy_rev, 16)) + return true; + + phybist0 = read_phy_reg(pi, 0x0e); + phybist1 = read_phy_reg(pi, 0x0f); + phybist2 = read_phy_reg(pi, 0xea); + phybist3 = read_phy_reg(pi, 0xeb); + phybist4 = read_phy_reg(pi, 0x156); + + if ((phybist0 == 0) && (phybist1 == 0x4000) && (phybist2 == 0x1fe0) && + (phybist3 == 0) && (phybist4 == 0)) + return true; + + return false; +} + +static void wlc_phy_bphy_init_nphy(struct brcms_phy *pi) +{ + u16 addr, val; + + val = 0x1e1f; + for (addr = (NPHY_TO_BPHY_OFF + BPHY_RSSI_LUT); + addr <= (NPHY_TO_BPHY_OFF + BPHY_RSSI_LUT_END); addr++) { + write_phy_reg(pi, addr, val); + if (addr == (NPHY_TO_BPHY_OFF + 0x97)) + val = 0x3e3f; + else + val -= 0x0202; + } + + write_phy_reg(pi, NPHY_TO_BPHY_OFF + BPHY_STEP, 0x668); +} + +void +wlc_phy_table_write_nphy(struct brcms_phy *pi, u32 id, u32 len, u32 offset, + u32 width, const void *data) +{ + struct phytbl_info tbl; + + tbl.tbl_id = id; + tbl.tbl_len = len; + tbl.tbl_offset = offset; + tbl.tbl_width = width; + tbl.tbl_ptr = data; + wlc_phy_write_table_nphy(pi, &tbl); +} + +void +wlc_phy_table_read_nphy(struct brcms_phy *pi, u32 id, u32 len, u32 offset, + u32 width, void *data) +{ + struct phytbl_info tbl; + + tbl.tbl_id = id; + tbl.tbl_len = len; + tbl.tbl_offset = offset; + tbl.tbl_width = width; + tbl.tbl_ptr = data; + wlc_phy_read_table_nphy(pi, &tbl); +} + +static void +wlc_phy_static_table_download_nphy(struct brcms_phy *pi) +{ + uint idx; + + if (NREV_GE(pi->pubpi.phy_rev, 16)) { + for (idx = 0; idx < mimophytbl_info_sz_rev16; idx++) + wlc_phy_write_table_nphy(pi, + &mimophytbl_info_rev16[idx]); + } else if (NREV_GE(pi->pubpi.phy_rev, 7)) { + for (idx = 0; idx < mimophytbl_info_sz_rev7; idx++) + wlc_phy_write_table_nphy(pi, + &mimophytbl_info_rev7[idx]); + } else if (NREV_GE(pi->pubpi.phy_rev, 3)) { + for (idx = 0; idx < mimophytbl_info_sz_rev3; idx++) + wlc_phy_write_table_nphy(pi, + &mimophytbl_info_rev3[idx]); + } else { + for (idx = 0; idx < mimophytbl_info_sz_rev0; idx++) + wlc_phy_write_table_nphy(pi, + &mimophytbl_info_rev0[idx]); + } +} + +static void wlc_phy_tbl_init_nphy(struct brcms_phy *pi) +{ + uint idx = 0; + u8 antswctrllut; + + if (pi->phy_init_por) + wlc_phy_static_table_download_nphy(pi); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + antswctrllut = CHSPEC_IS2G(pi->radio_chanspec) ? + pi->srom_fem2g.antswctrllut : pi->srom_fem5g. + antswctrllut; + + switch (antswctrllut) { + case 0: + + break; + + case 1: + + if (pi->aa2g == 7) + wlc_phy_table_write_nphy( + pi, + NPHY_TBL_ID_ANTSWCTRLLUT, + 2, 0x21, 8, + &ant_sw_ctrl_tbl_rev8_2o3[0]); + else + wlc_phy_table_write_nphy( + pi, + NPHY_TBL_ID_ANTSWCTRLLUT, + 2, 0x21, 8, + &ant_sw_ctrl_tbl_rev8 + [0]); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_ANTSWCTRLLUT, + 2, 0x25, 8, + &ant_sw_ctrl_tbl_rev8[2]); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_ANTSWCTRLLUT, + 2, 0x29, 8, + &ant_sw_ctrl_tbl_rev8[4]); + break; + + case 2: + + wlc_phy_table_write_nphy( + pi, NPHY_TBL_ID_ANTSWCTRLLUT, + 2, 0x1, 8, + &ant_sw_ctrl_tbl_rev8_2057v7_core0[0]); + wlc_phy_table_write_nphy( + pi, NPHY_TBL_ID_ANTSWCTRLLUT, + 2, 0x5, 8, + &ant_sw_ctrl_tbl_rev8_2057v7_core0[2]); + wlc_phy_table_write_nphy( + pi, NPHY_TBL_ID_ANTSWCTRLLUT, + 2, 0x9, 8, + &ant_sw_ctrl_tbl_rev8_2057v7_core0[4]); + + wlc_phy_table_write_nphy( + pi, NPHY_TBL_ID_ANTSWCTRLLUT, + 2, 0x21, 8, + &ant_sw_ctrl_tbl_rev8_2057v7_core1[0]); + wlc_phy_table_write_nphy( + pi, NPHY_TBL_ID_ANTSWCTRLLUT, + 2, 0x25, 8, + &ant_sw_ctrl_tbl_rev8_2057v7_core1[2]); + wlc_phy_table_write_nphy( + pi, NPHY_TBL_ID_ANTSWCTRLLUT, + 2, 0x29, 8, + &ant_sw_ctrl_tbl_rev8_2057v7_core1[4]); + break; + + default: + break; + } + + } else if (NREV_GE(pi->pubpi.phy_rev, 3)) { + for (idx = 0; idx < mimophytbl_info_sz_rev3_volatile; idx++) { + + if (idx == ANT_SWCTRL_TBL_REV3_IDX) { + antswctrllut = + CHSPEC_IS2G(pi->radio_chanspec) ? + pi->srom_fem2g.antswctrllut : + pi->srom_fem5g.antswctrllut; + switch (antswctrllut) { + case 0: + wlc_phy_write_table_nphy( + pi, + &mimophytbl_info_rev3_volatile + [idx]); + break; + case 1: + wlc_phy_write_table_nphy( + pi, + &mimophytbl_info_rev3_volatile1 + [idx]); + break; + case 2: + wlc_phy_write_table_nphy( + pi, + &mimophytbl_info_rev3_volatile2 + [idx]); + break; + case 3: + wlc_phy_write_table_nphy( + pi, + &mimophytbl_info_rev3_volatile3 + [idx]); + break; + default: + break; + } + } else { + wlc_phy_write_table_nphy( + pi, + &mimophytbl_info_rev3_volatile[idx]); + } + } + } else { + for (idx = 0; idx < mimophytbl_info_sz_rev0_volatile; idx++) + wlc_phy_write_table_nphy(pi, + &mimophytbl_info_rev0_volatile + [idx]); + } +} + +static void +wlc_phy_write_txmacreg_nphy(struct brcms_phy *pi, u16 holdoff, u16 delay) +{ + write_phy_reg(pi, 0x77, holdoff); + write_phy_reg(pi, 0xb4, delay); +} + +void wlc_phy_nphy_tkip_rifs_war(struct brcms_phy *pi, u8 rifs) +{ + u16 holdoff, delay; + + if (rifs) { + + holdoff = 0x10; + delay = 0x258; + } else { + + holdoff = 0x15; + delay = 0x320; + } + + wlc_phy_write_txmacreg_nphy(pi, holdoff, delay); + + if (pi->sh && (pi->sh->_rifs_phy != rifs)) + pi->sh->_rifs_phy = rifs; +} + +static void wlc_phy_txpwrctrl_config_nphy(struct brcms_phy *pi) +{ + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + pi->nphy_txpwrctrl = PHY_TPC_HW_ON; + pi->phy_5g_pwrgain = true; + return; + } + + pi->nphy_txpwrctrl = PHY_TPC_HW_OFF; + pi->phy_5g_pwrgain = false; + + if ((pi->sh->boardflags2 & BFL2_TXPWRCTRL_EN) && + NREV_GE(pi->pubpi.phy_rev, 2) && (pi->sh->sromrev >= 4)) + pi->nphy_txpwrctrl = PHY_TPC_HW_ON; + else if ((pi->sh->sromrev >= 4) + && (pi->sh->boardflags2 & BFL2_5G_PWRGAIN)) + pi->phy_5g_pwrgain = true; +} + +static void wlc_phy_txpwr_srom_read_ppr_nphy(struct brcms_phy *pi) +{ + u16 bw40po, cddpo, stbcpo, bwduppo; + uint band_num; + struct ssb_sprom *sprom = &pi->d11core->bus->sprom; + + if (pi->sh->sromrev >= 9) + return; + + bw40po = sprom->bw40po; + pi->bw402gpo = bw40po & 0xf; + pi->bw405gpo = (bw40po & 0xf0) >> 4; + pi->bw405glpo = (bw40po & 0xf00) >> 8; + pi->bw405ghpo = (bw40po & 0xf000) >> 12; + + cddpo = sprom->cddpo; + pi->cdd2gpo = cddpo & 0xf; + pi->cdd5gpo = (cddpo & 0xf0) >> 4; + pi->cdd5glpo = (cddpo & 0xf00) >> 8; + pi->cdd5ghpo = (cddpo & 0xf000) >> 12; + + stbcpo = sprom->stbcpo; + pi->stbc2gpo = stbcpo & 0xf; + pi->stbc5gpo = (stbcpo & 0xf0) >> 4; + pi->stbc5glpo = (stbcpo & 0xf00) >> 8; + pi->stbc5ghpo = (stbcpo & 0xf000) >> 12; + + bwduppo = sprom->bwduppo; + pi->bwdup2gpo = bwduppo & 0xf; + pi->bwdup5gpo = (bwduppo & 0xf0) >> 4; + pi->bwdup5glpo = (bwduppo & 0xf00) >> 8; + pi->bwdup5ghpo = (bwduppo & 0xf000) >> 12; + + for (band_num = 0; band_num < (CH_2G_GROUP + CH_5G_GROUP); + band_num++) { + switch (band_num) { + case 0: + pi->nphy_pwrctrl_info[PHY_CORE_0].max_pwr_2g = + sprom->core_pwr_info[0].maxpwr_2g; + pi->nphy_pwrctrl_info[PHY_CORE_1].max_pwr_2g = + sprom->core_pwr_info[1].maxpwr_2g; + pi->nphy_pwrctrl_info[PHY_CORE_0].pwrdet_2g_a1 = + sprom->core_pwr_info[0].pa_2g[0]; + pi->nphy_pwrctrl_info[PHY_CORE_1].pwrdet_2g_a1 = + sprom->core_pwr_info[1].pa_2g[0]; + pi->nphy_pwrctrl_info[PHY_CORE_0].pwrdet_2g_b0 = + sprom->core_pwr_info[0].pa_2g[1]; + pi->nphy_pwrctrl_info[PHY_CORE_1].pwrdet_2g_b0 = + sprom->core_pwr_info[1].pa_2g[1]; + pi->nphy_pwrctrl_info[PHY_CORE_0].pwrdet_2g_b1 = + sprom->core_pwr_info[0].pa_2g[2]; + pi->nphy_pwrctrl_info[PHY_CORE_1].pwrdet_2g_b1 = + sprom->core_pwr_info[1].pa_2g[2]; + pi->nphy_pwrctrl_info[PHY_CORE_0].idle_targ_2g = + sprom->core_pwr_info[0].itssi_2g; + pi->nphy_pwrctrl_info[PHY_CORE_1].idle_targ_2g = + sprom->core_pwr_info[1].itssi_2g; + + pi->cck2gpo = sprom->cck2gpo; + + pi->ofdm2gpo = sprom->ofdm2gpo; + + pi->mcs2gpo[0] = sprom->mcs2gpo[0]; + pi->mcs2gpo[1] = sprom->mcs2gpo[1]; + pi->mcs2gpo[2] = sprom->mcs2gpo[2]; + pi->mcs2gpo[3] = sprom->mcs2gpo[3]; + pi->mcs2gpo[4] = sprom->mcs2gpo[4]; + pi->mcs2gpo[5] = sprom->mcs2gpo[5]; + pi->mcs2gpo[6] = sprom->mcs2gpo[6]; + pi->mcs2gpo[7] = sprom->mcs2gpo[7]; + break; + case 1: + + pi->nphy_pwrctrl_info[PHY_CORE_0].max_pwr_5gm = + sprom->core_pwr_info[0].maxpwr_5g; + pi->nphy_pwrctrl_info[PHY_CORE_1].max_pwr_5gm = + sprom->core_pwr_info[1].maxpwr_5g; + pi->nphy_pwrctrl_info[PHY_CORE_0].pwrdet_5gm_a1 = + sprom->core_pwr_info[0].pa_5g[0]; + pi->nphy_pwrctrl_info[PHY_CORE_1].pwrdet_5gm_a1 = + sprom->core_pwr_info[1].pa_5g[0]; + pi->nphy_pwrctrl_info[PHY_CORE_0].pwrdet_5gm_b0 = + sprom->core_pwr_info[0].pa_5g[1]; + pi->nphy_pwrctrl_info[PHY_CORE_1].pwrdet_5gm_b0 = + sprom->core_pwr_info[1].pa_5g[1]; + pi->nphy_pwrctrl_info[PHY_CORE_0].pwrdet_5gm_b1 = + sprom->core_pwr_info[0].pa_5g[2]; + pi->nphy_pwrctrl_info[PHY_CORE_1].pwrdet_5gm_b1 = + sprom->core_pwr_info[1].pa_5g[2]; + pi->nphy_pwrctrl_info[PHY_CORE_0].idle_targ_5gm = + sprom->core_pwr_info[0].itssi_5g; + pi->nphy_pwrctrl_info[PHY_CORE_1].idle_targ_5gm = + sprom->core_pwr_info[1].itssi_5g; + + pi->ofdm5gpo = sprom->ofdm5gpo; + + pi->mcs5gpo[0] = sprom->mcs5gpo[0]; + pi->mcs5gpo[1] = sprom->mcs5gpo[1]; + pi->mcs5gpo[2] = sprom->mcs5gpo[2]; + pi->mcs5gpo[3] = sprom->mcs5gpo[3]; + pi->mcs5gpo[4] = sprom->mcs5gpo[4]; + pi->mcs5gpo[5] = sprom->mcs5gpo[5]; + pi->mcs5gpo[6] = sprom->mcs5gpo[6]; + pi->mcs5gpo[7] = sprom->mcs5gpo[7]; + break; + case 2: + + pi->nphy_pwrctrl_info[0].max_pwr_5gl = + sprom->core_pwr_info[0].maxpwr_5gl; + pi->nphy_pwrctrl_info[1].max_pwr_5gl = + sprom->core_pwr_info[1].maxpwr_5gl; + pi->nphy_pwrctrl_info[0].pwrdet_5gl_a1 = + sprom->core_pwr_info[0].pa_5gl[0]; + pi->nphy_pwrctrl_info[1].pwrdet_5gl_a1 = + sprom->core_pwr_info[1].pa_5gl[0]; + pi->nphy_pwrctrl_info[0].pwrdet_5gl_b0 = + sprom->core_pwr_info[0].pa_5gl[1]; + pi->nphy_pwrctrl_info[1].pwrdet_5gl_b0 = + sprom->core_pwr_info[1].pa_5gl[1]; + pi->nphy_pwrctrl_info[0].pwrdet_5gl_b1 = + sprom->core_pwr_info[0].pa_5gl[2]; + pi->nphy_pwrctrl_info[1].pwrdet_5gl_b1 = + sprom->core_pwr_info[1].pa_5gl[2]; + pi->nphy_pwrctrl_info[0].idle_targ_5gl = 0; + pi->nphy_pwrctrl_info[1].idle_targ_5gl = 0; + + pi->ofdm5glpo = sprom->ofdm5glpo; + + pi->mcs5glpo[0] = sprom->mcs5glpo[0]; + pi->mcs5glpo[1] = sprom->mcs5glpo[1]; + pi->mcs5glpo[2] = sprom->mcs5glpo[2]; + pi->mcs5glpo[3] = sprom->mcs5glpo[3]; + pi->mcs5glpo[4] = sprom->mcs5glpo[4]; + pi->mcs5glpo[5] = sprom->mcs5glpo[5]; + pi->mcs5glpo[6] = sprom->mcs5glpo[6]; + pi->mcs5glpo[7] = sprom->mcs5glpo[7]; + break; + case 3: + + pi->nphy_pwrctrl_info[0].max_pwr_5gh = + sprom->core_pwr_info[0].maxpwr_5gh; + pi->nphy_pwrctrl_info[1].max_pwr_5gh = + sprom->core_pwr_info[1].maxpwr_5gh; + pi->nphy_pwrctrl_info[0].pwrdet_5gh_a1 = + sprom->core_pwr_info[0].pa_5gh[0]; + pi->nphy_pwrctrl_info[1].pwrdet_5gh_a1 = + sprom->core_pwr_info[1].pa_5gh[0]; + pi->nphy_pwrctrl_info[0].pwrdet_5gh_b0 = + sprom->core_pwr_info[0].pa_5gh[1]; + pi->nphy_pwrctrl_info[1].pwrdet_5gh_b0 = + sprom->core_pwr_info[1].pa_5gh[1]; + pi->nphy_pwrctrl_info[0].pwrdet_5gh_b1 = + sprom->core_pwr_info[0].pa_5gh[2]; + pi->nphy_pwrctrl_info[1].pwrdet_5gh_b1 = + sprom->core_pwr_info[1].pa_5gh[2]; + pi->nphy_pwrctrl_info[0].idle_targ_5gh = 0; + pi->nphy_pwrctrl_info[1].idle_targ_5gh = 0; + + pi->ofdm5ghpo = sprom->ofdm5ghpo; + + pi->mcs5ghpo[0] = sprom->mcs5ghpo[0]; + pi->mcs5ghpo[1] = sprom->mcs5ghpo[1]; + pi->mcs5ghpo[2] = sprom->mcs5ghpo[2]; + pi->mcs5ghpo[3] = sprom->mcs5ghpo[3]; + pi->mcs5ghpo[4] = sprom->mcs5ghpo[4]; + pi->mcs5ghpo[5] = sprom->mcs5ghpo[5]; + pi->mcs5ghpo[6] = sprom->mcs5ghpo[6]; + pi->mcs5ghpo[7] = sprom->mcs5ghpo[7]; + break; + } + } + + wlc_phy_txpwr_apply_nphy(pi); +} + +static bool wlc_phy_txpwr_srom_read_nphy(struct brcms_phy *pi) +{ + struct ssb_sprom *sprom = &pi->d11core->bus->sprom; + + pi->antswitch = sprom->antswitch; + pi->aa2g = sprom->ant_available_bg; + pi->aa5g = sprom->ant_available_a; + + pi->srom_fem2g.tssipos = sprom->fem.ghz2.tssipos; + pi->srom_fem2g.extpagain = sprom->fem.ghz2.extpa_gain; + pi->srom_fem2g.pdetrange = sprom->fem.ghz2.pdet_range; + pi->srom_fem2g.triso = sprom->fem.ghz2.tr_iso; + pi->srom_fem2g.antswctrllut = sprom->fem.ghz2.antswlut; + + pi->srom_fem5g.tssipos = sprom->fem.ghz5.tssipos; + pi->srom_fem5g.extpagain = sprom->fem.ghz5.extpa_gain; + pi->srom_fem5g.pdetrange = sprom->fem.ghz5.pdet_range; + pi->srom_fem5g.triso = sprom->fem.ghz5.tr_iso; + if (sprom->fem.ghz5.antswlut) + pi->srom_fem5g.antswctrllut = sprom->fem.ghz5.antswlut; + else + pi->srom_fem5g.antswctrllut = sprom->fem.ghz2.antswlut; + + wlc_phy_txpower_ipa_upd(pi); + + pi->phy_txcore_disable_temp = sprom->tempthresh; + if (pi->phy_txcore_disable_temp == 0) + pi->phy_txcore_disable_temp = PHY_CHAIN_TX_DISABLE_TEMP; + + pi->phy_tempsense_offset = sprom->tempoffset; + if (pi->phy_tempsense_offset != 0) { + if (pi->phy_tempsense_offset > + (NPHY_SROM_TEMPSHIFT + NPHY_SROM_MAXTEMPOFFSET)) + pi->phy_tempsense_offset = NPHY_SROM_MAXTEMPOFFSET; + else if (pi->phy_tempsense_offset < (NPHY_SROM_TEMPSHIFT + + NPHY_SROM_MINTEMPOFFSET)) + pi->phy_tempsense_offset = NPHY_SROM_MINTEMPOFFSET; + else + pi->phy_tempsense_offset -= NPHY_SROM_TEMPSHIFT; + } + + pi->phy_txcore_enable_temp = + pi->phy_txcore_disable_temp - PHY_HYSTERESIS_DELTATEMP; + + pi->phycal_tempdelta = sprom->phycal_tempdelta; + if (pi->phycal_tempdelta > NPHY_CAL_MAXTEMPDELTA) + pi->phycal_tempdelta = 0; + + wlc_phy_txpwr_srom_read_ppr_nphy(pi); + + return true; +} + +bool wlc_phy_attach_nphy(struct brcms_phy *pi) +{ + uint i; + + if (NREV_GE(pi->pubpi.phy_rev, 3) && NREV_LT(pi->pubpi.phy_rev, 6)) + pi->phyhang_avoid = true; + + if (NREV_GE(pi->pubpi.phy_rev, 3) && NREV_LT(pi->pubpi.phy_rev, 7)) { + pi->nphy_gband_spurwar_en = true; + if (pi->sh->boardflags2 & BFL2_SPUR_WAR) + pi->nphy_aband_spurwar_en = true; + } + if (NREV_GE(pi->pubpi.phy_rev, 6) && NREV_LT(pi->pubpi.phy_rev, 7)) { + if (pi->sh->boardflags2 & BFL2_2G_SPUR_WAR) + pi->nphy_gband_spurwar2_en = true; + } + + pi->n_preamble_override = AUTO; + if (NREV_IS(pi->pubpi.phy_rev, 3) || NREV_IS(pi->pubpi.phy_rev, 4)) + pi->n_preamble_override = BRCMS_N_PREAMBLE_MIXEDMODE; + + pi->nphy_txrx_chain = AUTO; + pi->phy_scraminit = AUTO; + + pi->nphy_rxcalparams = 0x010100B5; + + pi->nphy_perical = PHY_PERICAL_MPHASE; + pi->mphase_cal_phase_id = MPHASE_CAL_STATE_IDLE; + pi->mphase_txcal_numcmds = MPHASE_TXCAL_NUMCMDS; + + pi->nphy_gain_boost = true; + pi->nphy_elna_gain_config = false; + pi->radio_is_on = false; + + for (i = 0; i < pi->pubpi.phy_corenum; i++) + pi->nphy_txpwrindex[i].index = AUTO; + + wlc_phy_txpwrctrl_config_nphy(pi); + if (pi->nphy_txpwrctrl == PHY_TPC_HW_ON) + pi->hwpwrctrl_capable = true; + + pi->pi_fptr.init = wlc_phy_init_nphy; + pi->pi_fptr.calinit = wlc_phy_cal_init_nphy; + pi->pi_fptr.chanset = wlc_phy_chanspec_set_nphy; + pi->pi_fptr.txpwrrecalc = wlc_phy_txpower_recalc_target_nphy; + + if (!wlc_phy_txpwr_srom_read_nphy(pi)) + return false; + + return true; +} + +static s32 get_rf_pwr_offset(struct brcms_phy *pi, s16 pga_gn, s16 pad_gn) +{ + s32 rfpwr_offset = 0; + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if ((pi->pubpi.radiorev == 3) || + (pi->pubpi.radiorev == 4) || + (pi->pubpi.radiorev == 6)) + rfpwr_offset = (s16) + nphy_papd_padgain_dlt_2g_2057rev3n4 + [pad_gn]; + else if (pi->pubpi.radiorev == 5) + rfpwr_offset = (s16) + nphy_papd_padgain_dlt_2g_2057rev5 + [pad_gn]; + else if ((pi->pubpi.radiorev == 7) + || (pi->pubpi.radiorev == + 8)) + rfpwr_offset = (s16) + nphy_papd_padgain_dlt_2g_2057rev7 + [pad_gn]; + } else { + if ((pi->pubpi.radiorev == 3) || + (pi->pubpi.radiorev == 4) || + (pi->pubpi.radiorev == 6)) + rfpwr_offset = (s16) + nphy_papd_pgagain_dlt_5g_2057 + [pga_gn]; + else if ((pi->pubpi.radiorev == 7) + || (pi->pubpi.radiorev == + 8)) + rfpwr_offset = (s16) + nphy_papd_pgagain_dlt_5g_2057rev7 + [pga_gn]; + } + return rfpwr_offset; +} + +static void wlc_phy_update_mimoconfig_nphy(struct brcms_phy *pi, s32 preamble) +{ + bool gf_preamble = false; + u16 val; + + if (preamble == BRCMS_N_PREAMBLE_GF) + gf_preamble = true; + + val = read_phy_reg(pi, 0xed); + + val |= RX_GF_MM_AUTO; + val &= ~RX_GF_OR_MM; + if (gf_preamble) + val |= RX_GF_OR_MM; + + write_phy_reg(pi, 0xed, val); +} + +static void wlc_phy_ipa_set_tx_digi_filts_nphy(struct brcms_phy *pi) +{ + int j, type; + u16 addr_offset[] = { 0x186, 0x195, 0x2c5}; + + for (type = 0; type < 3; type++) { + for (j = 0; j < NPHY_NUM_DIG_FILT_COEFFS; j++) + write_phy_reg(pi, addr_offset[type] + j, + NPHY_IPA_REV4_txdigi_filtcoeffs[type][j]); + } + + if (pi->bw == WL_CHANSPEC_BW_40) { + for (j = 0; j < NPHY_NUM_DIG_FILT_COEFFS; j++) + write_phy_reg(pi, 0x186 + j, + NPHY_IPA_REV4_txdigi_filtcoeffs[3][j]); + } else { + if (CHSPEC_IS5G(pi->radio_chanspec)) { + for (j = 0; j < NPHY_NUM_DIG_FILT_COEFFS; j++) + write_phy_reg(pi, 0x186 + j, + NPHY_IPA_REV4_txdigi_filtcoeffs[5][j]); + } + + if (CHSPEC_CHANNEL(pi->radio_chanspec) == 14) { + for (j = 0; j < NPHY_NUM_DIG_FILT_COEFFS; j++) + write_phy_reg(pi, 0x2c5 + j, + NPHY_IPA_REV4_txdigi_filtcoeffs[6][j]); + } + } +} + +static void wlc_phy_ipa_restore_tx_digi_filts_nphy(struct brcms_phy *pi) +{ + int j; + + if (pi->bw == WL_CHANSPEC_BW_40) { + for (j = 0; j < NPHY_NUM_DIG_FILT_COEFFS; j++) + write_phy_reg(pi, 0x195 + j, + NPHY_IPA_REV4_txdigi_filtcoeffs[4][j]); + } else { + for (j = 0; j < NPHY_NUM_DIG_FILT_COEFFS; j++) + write_phy_reg(pi, 0x186 + j, + NPHY_IPA_REV4_txdigi_filtcoeffs[3][j]); + } +} + +static void +wlc_phy_set_rfseq_nphy(struct brcms_phy *pi, u8 cmd, u8 *events, u8 *dlys, + u8 len) +{ + u32 t1_offset, t2_offset; + u8 ctr; + u8 end_event = + NREV_GE(pi->pubpi.phy_rev, + 3) ? NPHY_REV3_RFSEQ_CMD_END : NPHY_RFSEQ_CMD_END; + u8 end_dly = 1; + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + t1_offset = cmd << 4; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, len, t1_offset, 8, + events); + t2_offset = t1_offset + 0x080; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, len, t2_offset, 8, + dlys); + + for (ctr = len; ctr < 16; ctr++) { + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, 1, + t1_offset + ctr, 8, &end_event); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, 1, + t2_offset + ctr, 8, &end_dly); + } + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, false); +} + +static u16 wlc_phy_read_lpf_bw_ctl_nphy(struct brcms_phy *pi, u16 offset) +{ + u16 lpf_bw_ctl_val = 0; + u16 rx2tx_lpf_rc_lut_offset = 0; + + if (offset == 0) { + if (CHSPEC_IS40(pi->radio_chanspec)) + rx2tx_lpf_rc_lut_offset = 0x159; + else + rx2tx_lpf_rc_lut_offset = 0x154; + } else { + rx2tx_lpf_rc_lut_offset = offset; + } + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_RFSEQ, 1, + (u32) rx2tx_lpf_rc_lut_offset, 16, + &lpf_bw_ctl_val); + + lpf_bw_ctl_val = lpf_bw_ctl_val & 0x7; + + return lpf_bw_ctl_val; +} + +static void +wlc_phy_rfctrl_override_nphy_rev7(struct brcms_phy *pi, u16 field, u16 value, + u8 core_mask, u8 off, u8 override_id) +{ + u8 core_num; + u16 addr = 0, en_addr = 0, val_addr = 0, en_mask = 0, val_mask = 0; + u8 val_shift = 0; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + en_mask = field; + for (core_num = 0; core_num < 2; core_num++) { + if (override_id == NPHY_REV7_RFCTRLOVERRIDE_ID0) { + + switch (field) { + case (0x1 << 2): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7a : + 0x7d; + val_mask = (0x1 << 1); + val_shift = 1; + break; + case (0x1 << 3): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7a : + 0x7d; + val_mask = (0x1 << 2); + val_shift = 2; + break; + case (0x1 << 4): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7a : + 0x7d; + val_mask = (0x1 << 4); + val_shift = 4; + break; + case (0x1 << 5): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7a : + 0x7d; + val_mask = (0x1 << 5); + val_shift = 5; + break; + case (0x1 << 6): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7a : + 0x7d; + val_mask = (0x1 << 6); + val_shift = 6; + break; + case (0x1 << 7): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7a : + 0x7d; + val_mask = (0x1 << 7); + val_shift = 7; + break; + case (0x1 << 10): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0xf8 : + 0xfa; + val_mask = (0x7 << 4); + val_shift = 4; + break; + case (0x1 << 11): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7b : + 0x7e; + val_mask = (0xffff << 0); + val_shift = 0; + break; + case (0x1 << 12): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7c : + 0x7f; + val_mask = (0xffff << 0); + val_shift = 0; + break; + case (0x3 << 13): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x348 : + 0x349; + val_mask = (0xff << 0); + val_shift = 0; + break; + case (0x1 << 13): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x348 : + 0x349; + val_mask = (0xf << 0); + val_shift = 0; + break; + default: + addr = 0xffff; + break; + } + } else if (override_id == + NPHY_REV7_RFCTRLOVERRIDE_ID1) { + + switch (field) { + case (0x1 << 1): + en_addr = (core_num == 0) ? 0x342 : + 0x343; + val_addr = (core_num == 0) ? 0x340 : + 0x341; + val_mask = (0x1 << 1); + val_shift = 1; + break; + case (0x1 << 3): + en_addr = (core_num == 0) ? 0x342 : + 0x343; + val_addr = (core_num == 0) ? 0x340 : + 0x341; + val_mask = (0x1 << 3); + val_shift = 3; + break; + case (0x1 << 5): + en_addr = (core_num == 0) ? 0x342 : + 0x343; + val_addr = (core_num == 0) ? 0x340 : + 0x341; + val_mask = (0x1 << 5); + val_shift = 5; + break; + case (0x1 << 4): + en_addr = (core_num == 0) ? 0x342 : + 0x343; + val_addr = (core_num == 0) ? 0x340 : + 0x341; + val_mask = (0x1 << 4); + val_shift = 4; + break; + case (0x1 << 2): + + en_addr = (core_num == 0) ? 0x342 : + 0x343; + val_addr = (core_num == 0) ? 0x340 : + 0x341; + val_mask = (0x1 << 2); + val_shift = 2; + break; + case (0x1 << 7): + + en_addr = (core_num == 0) ? 0x342 : + 0x343; + val_addr = (core_num == 0) ? 0x340 : + 0x341; + val_mask = (0x7 << 8); + val_shift = 8; + break; + case (0x1 << 11): + en_addr = (core_num == 0) ? 0x342 : + 0x343; + val_addr = (core_num == 0) ? 0x340 : + 0x341; + val_mask = (0x1 << 14); + val_shift = 14; + break; + case (0x1 << 10): + en_addr = (core_num == 0) ? 0x342 : + 0x343; + val_addr = (core_num == 0) ? 0x340 : + 0x341; + val_mask = (0x1 << 13); + val_shift = 13; + break; + case (0x1 << 9): + en_addr = (core_num == 0) ? 0x342 : + 0x343; + val_addr = (core_num == 0) ? 0x340 : + 0x341; + val_mask = (0x1 << 12); + val_shift = 12; + break; + case (0x1 << 8): + en_addr = (core_num == 0) ? 0x342 : + 0x343; + val_addr = (core_num == 0) ? 0x340 : + 0x341; + val_mask = (0x1 << 11); + val_shift = 11; + break; + case (0x1 << 6): + en_addr = (core_num == 0) ? 0x342 : + 0x343; + val_addr = (core_num == 0) ? 0x340 : + 0x341; + val_mask = (0x1 << 6); + val_shift = 6; + break; + case (0x1 << 0): + en_addr = (core_num == 0) ? 0x342 : + 0x343; + val_addr = (core_num == 0) ? 0x340 : + 0x341; + val_mask = (0x1 << 0); + val_shift = 0; + break; + default: + addr = 0xffff; + break; + } + } else if (override_id == + NPHY_REV7_RFCTRLOVERRIDE_ID2) { + + switch (field) { + case (0x1 << 3): + en_addr = (core_num == 0) ? 0x346 : + 0x347; + val_addr = (core_num == 0) ? 0x344 : + 0x345; + val_mask = (0x1 << 3); + val_shift = 3; + break; + case (0x1 << 1): + en_addr = (core_num == 0) ? 0x346 : + 0x347; + val_addr = (core_num == 0) ? 0x344 : + 0x345; + val_mask = (0x1 << 1); + val_shift = 1; + break; + case (0x1 << 0): + en_addr = (core_num == 0) ? 0x346 : + 0x347; + val_addr = (core_num == 0) ? 0x344 : + 0x345; + val_mask = (0x1 << 0); + val_shift = 0; + break; + case (0x1 << 2): + en_addr = (core_num == 0) ? 0x346 : + 0x347; + val_addr = (core_num == 0) ? 0x344 : + 0x345; + val_mask = (0x1 << 2); + val_shift = 2; + break; + case (0x1 << 4): + en_addr = (core_num == 0) ? 0x346 : + 0x347; + val_addr = (core_num == 0) ? 0x344 : + 0x345; + val_mask = (0x1 << 4); + val_shift = 4; + break; + default: + addr = 0xffff; + break; + } + } + + if (off) { + and_phy_reg(pi, en_addr, ~en_mask); + and_phy_reg(pi, val_addr, ~val_mask); + } else { + + if ((core_mask == 0) + || (core_mask & (1 << core_num))) { + or_phy_reg(pi, en_addr, en_mask); + + if (addr != 0xffff) + mod_phy_reg(pi, val_addr, + val_mask, + (value << + val_shift)); + } + } + } + } +} + +static void wlc_phy_adjust_lnagaintbl_nphy(struct brcms_phy *pi) +{ + uint core; + int ctr; + s16 gain_delta[2]; + u8 curr_channel; + u16 minmax_gain[2]; + u16 regval[4]; + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + if (pi->nphy_gain_boost) { + if ((CHSPEC_IS2G(pi->radio_chanspec))) { + + gain_delta[0] = 6; + gain_delta[1] = 6; + } else { + + curr_channel = CHSPEC_CHANNEL(pi->radio_chanspec); + gain_delta[0] = + (s16) + PHY_HW_ROUND(((nphy_lnagain_est0[0] * + curr_channel) + + nphy_lnagain_est0[1]), 13); + gain_delta[1] = + (s16) + PHY_HW_ROUND(((nphy_lnagain_est1[0] * + curr_channel) + + nphy_lnagain_est1[1]), 13); + } + } else { + + gain_delta[0] = 0; + gain_delta[1] = 0; + } + + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + if (pi->nphy_elna_gain_config) { + + regval[0] = nphy_def_lnagains[2] + gain_delta[core]; + regval[1] = nphy_def_lnagains[3] + gain_delta[core]; + regval[2] = nphy_def_lnagains[3] + gain_delta[core]; + regval[3] = nphy_def_lnagains[3] + gain_delta[core]; + } else { + for (ctr = 0; ctr < 4; ctr++) + regval[ctr] = + nphy_def_lnagains[ctr] + + gain_delta[core]; + } + wlc_phy_table_write_nphy(pi, core, 4, 8, 16, regval); + + minmax_gain[core] = + (u16) (nphy_def_lnagains[2] + gain_delta[core] + 4); + } + + mod_phy_reg(pi, 0x1e, (0xff << 0), (minmax_gain[0] << 0)); + mod_phy_reg(pi, 0x34, (0xff << 0), (minmax_gain[1] << 0)); + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, false); +} + +static void +wlc_phy_war_force_trsw_to_R_cliplo_nphy(struct brcms_phy *pi, u8 core) +{ + if (core == PHY_CORE_0) { + write_phy_reg(pi, 0x38, 0x4); + if (CHSPEC_IS2G(pi->radio_chanspec)) + write_phy_reg(pi, 0x37, 0x0060); + else + write_phy_reg(pi, 0x37, 0x1080); + } else if (core == PHY_CORE_1) { + write_phy_reg(pi, 0x2ae, 0x4); + if (CHSPEC_IS2G(pi->radio_chanspec)) + write_phy_reg(pi, 0x2ad, 0x0060); + else + write_phy_reg(pi, 0x2ad, 0x1080); + } +} + +static void wlc_phy_war_txchain_upd_nphy(struct brcms_phy *pi, u8 txchain) +{ + u8 txchain0, txchain1; + + txchain0 = txchain & 0x1; + txchain1 = (txchain & 0x2) >> 1; + if (!txchain0) + wlc_phy_war_force_trsw_to_R_cliplo_nphy(pi, PHY_CORE_0); + + if (!txchain1) + wlc_phy_war_force_trsw_to_R_cliplo_nphy(pi, PHY_CORE_1); +} + +static void wlc_phy_workarounds_nphy_gainctrl_2057_rev5(struct brcms_phy *pi) +{ + s8 lna1_gain_db[] = { 8, 13, 17, 22 }; + s8 lna2_gain_db[] = { -2, 7, 11, 15 }; + s8 tia_gain_db[] = { -4, -1, 2, 5, 5, 5, 5, 5, 5, 5 }; + s8 tia_gainbits[] = { + 0x0, 0x01, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 }; + + mod_phy_reg(pi, 0x1c, (0x1 << 13), (1 << 13)); + mod_phy_reg(pi, 0x32, (0x1 << 13), (1 << 13)); + + mod_phy_reg(pi, 0x289, (0xff << 0), (0x46 << 0)); + + mod_phy_reg(pi, 0x283, (0xff << 0), (0x3c << 0)); + mod_phy_reg(pi, 0x280, (0xff << 0), (0x3c << 0)); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN1, 4, 0x8, 8, + lna1_gain_db); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN2, 4, 0x8, 8, + lna1_gain_db); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN1, 4, 0x10, 8, + lna2_gain_db); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN2, 4, 0x10, 8, + lna2_gain_db); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN1, 10, 0x20, 8, + tia_gain_db); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN2, 10, 0x20, 8, + tia_gain_db); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAINBITS1, 10, 0x20, 8, + tia_gainbits); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAINBITS2, 10, 0x20, 8, + tia_gainbits); + + write_phy_reg(pi, 0x37, 0x74); + write_phy_reg(pi, 0x2ad, 0x74); + write_phy_reg(pi, 0x38, 0x18); + write_phy_reg(pi, 0x2ae, 0x18); + + write_phy_reg(pi, 0x2b, 0xe8); + write_phy_reg(pi, 0x41, 0xe8); + + if (CHSPEC_IS20(pi->radio_chanspec)) { + + mod_phy_reg(pi, 0x300, (0x3f << 0), (0x12 << 0)); + mod_phy_reg(pi, 0x301, (0x3f << 0), (0x12 << 0)); + } else { + + mod_phy_reg(pi, 0x300, (0x3f << 0), (0x10 << 0)); + mod_phy_reg(pi, 0x301, (0x3f << 0), (0x10 << 0)); + } +} + +static void wlc_phy_workarounds_nphy_gainctrl_2057_rev6(struct brcms_phy *pi) +{ + u16 currband; + s8 lna1G_gain_db_rev7[] = { 9, 14, 19, 24 }; + s8 *lna1_gain_db = NULL; + s8 *lna1_gain_db_2 = NULL; + s8 *lna2_gain_db = NULL; + s8 tiaA_gain_db_rev7[] = { -9, -6, -3, 0, 3, 3, 3, 3, 3, 3 }; + s8 *tia_gain_db; + s8 tiaA_gainbits_rev7[] = { 0, 1, 2, 3, 4, 4, 4, 4, 4, 4 }; + s8 *tia_gainbits; + u16 rfseqA_init_gain_rev7[] = { 0x624f, 0x624f }; + u16 *rfseq_init_gain; + u16 init_gaincode; + u16 clip1hi_gaincode; + u16 clip1md_gaincode = 0; + u16 clip1md_gaincode_B; + u16 clip1lo_gaincode; + u16 clip1lo_gaincode_B; + u8 crsminl_th = 0; + u8 crsminu_th; + u16 nbclip_th = 0; + u8 w1clip_th; + u16 freq; + s8 nvar_baseline_offset0 = 0, nvar_baseline_offset1 = 0; + u8 chg_nbclip_th = 0; + + mod_phy_reg(pi, 0x1c, (0x1 << 13), (1 << 13)); + mod_phy_reg(pi, 0x32, (0x1 << 13), (1 << 13)); + + currband = read_phy_reg(pi, 0x09) & NPHY_BandControl_currentBand; + if (currband == 0) { + + lna1_gain_db = lna1G_gain_db_rev7; + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN1, 4, 8, 8, + lna1_gain_db); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN2, 4, 8, 8, + lna1_gain_db); + + mod_phy_reg(pi, 0x283, (0xff << 0), (0x40 << 0)); + + if (CHSPEC_IS40(pi->radio_chanspec)) { + mod_phy_reg(pi, 0x280, (0xff << 0), (0x3e << 0)); + mod_phy_reg(pi, 0x283, (0xff << 0), (0x3e << 0)); + } + + mod_phy_reg(pi, 0x289, (0xff << 0), (0x46 << 0)); + + if (CHSPEC_IS20(pi->radio_chanspec)) { + mod_phy_reg(pi, 0x300, (0x3f << 0), (13 << 0)); + mod_phy_reg(pi, 0x301, (0x3f << 0), (13 << 0)); + } + } else { + + init_gaincode = 0x9e; + clip1hi_gaincode = 0x9e; + clip1md_gaincode_B = 0x24; + clip1lo_gaincode = 0x8a; + clip1lo_gaincode_B = 8; + rfseq_init_gain = rfseqA_init_gain_rev7; + + tia_gain_db = tiaA_gain_db_rev7; + tia_gainbits = tiaA_gainbits_rev7; + + freq = CHAN5G_FREQ(CHSPEC_CHANNEL(pi->radio_chanspec)); + if (CHSPEC_IS20(pi->radio_chanspec)) { + + w1clip_th = 25; + clip1md_gaincode = 0x82; + + if ((freq <= 5080) || (freq == 5825)) { + + s8 lna1A_gain_db_rev7[] = { 11, 16, 20, 24 }; + s8 lna1A_gain_db_2_rev7[] = { + 11, 17, 22, 25}; + s8 lna2A_gain_db_rev7[] = { -1, 6, 10, 14 }; + + crsminu_th = 0x3e; + lna1_gain_db = lna1A_gain_db_rev7; + lna1_gain_db_2 = lna1A_gain_db_2_rev7; + lna2_gain_db = lna2A_gain_db_rev7; + } else if ((freq >= 5500) && (freq <= 5700)) { + + s8 lna1A_gain_db_rev7[] = { 11, 17, 21, 25 }; + s8 lna1A_gain_db_2_rev7[] = { + 12, 18, 22, 26}; + s8 lna2A_gain_db_rev7[] = { 1, 8, 12, 16 }; + + crsminu_th = 0x45; + clip1md_gaincode_B = 0x14; + nbclip_th = 0xff; + chg_nbclip_th = 1; + lna1_gain_db = lna1A_gain_db_rev7; + lna1_gain_db_2 = lna1A_gain_db_2_rev7; + lna2_gain_db = lna2A_gain_db_rev7; + } else { + + s8 lna1A_gain_db_rev7[] = { 12, 18, 22, 26 }; + s8 lna1A_gain_db_2_rev7[] = { + 12, 18, 22, 26}; + s8 lna2A_gain_db_rev7[] = { -1, 6, 10, 14 }; + + crsminu_th = 0x41; + lna1_gain_db = lna1A_gain_db_rev7; + lna1_gain_db_2 = lna1A_gain_db_2_rev7; + lna2_gain_db = lna2A_gain_db_rev7; + } + + if (freq <= 4920) { + nvar_baseline_offset0 = 5; + nvar_baseline_offset1 = 5; + } else if ((freq > 4920) && (freq <= 5320)) { + nvar_baseline_offset0 = 3; + nvar_baseline_offset1 = 5; + } else if ((freq > 5320) && (freq <= 5700)) { + nvar_baseline_offset0 = 3; + nvar_baseline_offset1 = 2; + } else { + nvar_baseline_offset0 = 4; + nvar_baseline_offset1 = 0; + } + } else { + + crsminu_th = 0x3a; + crsminl_th = 0x3a; + w1clip_th = 20; + + if ((freq >= 4920) && (freq <= 5320)) { + nvar_baseline_offset0 = 4; + nvar_baseline_offset1 = 5; + } else if ((freq > 5320) && (freq <= 5550)) { + nvar_baseline_offset0 = 4; + nvar_baseline_offset1 = 2; + } else { + nvar_baseline_offset0 = 5; + nvar_baseline_offset1 = 3; + } + } + + write_phy_reg(pi, 0x20, init_gaincode); + write_phy_reg(pi, 0x2a7, init_gaincode); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, + pi->pubpi.phy_corenum, 0x106, 16, + rfseq_init_gain); + + write_phy_reg(pi, 0x22, clip1hi_gaincode); + write_phy_reg(pi, 0x2a9, clip1hi_gaincode); + + write_phy_reg(pi, 0x36, clip1md_gaincode_B); + write_phy_reg(pi, 0x2ac, clip1md_gaincode_B); + + write_phy_reg(pi, 0x37, clip1lo_gaincode); + write_phy_reg(pi, 0x2ad, clip1lo_gaincode); + write_phy_reg(pi, 0x38, clip1lo_gaincode_B); + write_phy_reg(pi, 0x2ae, clip1lo_gaincode_B); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN1, 10, 0x20, 8, + tia_gain_db); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN2, 10, 0x20, 8, + tia_gain_db); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAINBITS1, 10, 0x20, 8, + tia_gainbits); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAINBITS2, 10, 0x20, 8, + tia_gainbits); + + mod_phy_reg(pi, 0x283, (0xff << 0), (crsminu_th << 0)); + + if (chg_nbclip_th == 1) { + write_phy_reg(pi, 0x2b, nbclip_th); + write_phy_reg(pi, 0x41, nbclip_th); + } + + mod_phy_reg(pi, 0x300, (0x3f << 0), (w1clip_th << 0)); + mod_phy_reg(pi, 0x301, (0x3f << 0), (w1clip_th << 0)); + + mod_phy_reg(pi, 0x2e4, + (0x3f << 0), (nvar_baseline_offset0 << 0)); + + mod_phy_reg(pi, 0x2e4, + (0x3f << 6), (nvar_baseline_offset1 << 6)); + + if (CHSPEC_IS20(pi->radio_chanspec)) { + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN1, 4, 8, 8, + lna1_gain_db); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN2, 4, 8, 8, + lna1_gain_db_2); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN1, 4, 0x10, + 8, lna2_gain_db); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN2, 4, 0x10, + 8, lna2_gain_db); + + write_phy_reg(pi, 0x24, clip1md_gaincode); + write_phy_reg(pi, 0x2ab, clip1md_gaincode); + } else { + mod_phy_reg(pi, 0x280, (0xff << 0), (crsminl_th << 0)); + } + } +} + +static void wlc_phy_workarounds_nphy_gainctrl(struct brcms_phy *pi) +{ + u16 w1th, hpf_code, currband; + int ctr; + u8 rfseq_updategainu_events[] = { + NPHY_RFSEQ_CMD_RX_GAIN, + NPHY_RFSEQ_CMD_CLR_HIQ_DIS, + NPHY_RFSEQ_CMD_SET_HPF_BW + }; + u8 rfseq_updategainu_dlys[] = { 10, 30, 1 }; + s8 lna1G_gain_db[] = { 7, 11, 16, 23 }; + s8 lna1G_gain_db_rev4[] = { 8, 12, 17, 25 }; + s8 lna1G_gain_db_rev5[] = { 9, 13, 18, 26 }; + s8 lna1G_gain_db_rev6[] = { 8, 13, 18, 25 }; + s8 lna1G_gain_db_rev6_224B0[] = { 10, 14, 19, 27 }; + s8 lna1A_gain_db[] = { 7, 11, 17, 23 }; + s8 lna1A_gain_db_rev4[] = { 8, 12, 18, 23 }; + s8 lna1A_gain_db_rev5[] = { 6, 10, 16, 21 }; + s8 lna1A_gain_db_rev6[] = { 6, 10, 16, 21 }; + s8 *lna1_gain_db = NULL; + s8 lna2G_gain_db[] = { -5, 6, 10, 14 }; + s8 lna2G_gain_db_rev5[] = { -3, 7, 11, 16 }; + s8 lna2G_gain_db_rev6[] = { -5, 6, 10, 14 }; + s8 lna2G_gain_db_rev6_224B0[] = { -5, 6, 10, 15 }; + s8 lna2A_gain_db[] = { -6, 2, 6, 10 }; + s8 lna2A_gain_db_rev4[] = { -5, 2, 6, 10 }; + s8 lna2A_gain_db_rev5[] = { -7, 0, 4, 8 }; + s8 lna2A_gain_db_rev6[] = { -7, 0, 4, 8 }; + s8 *lna2_gain_db = NULL; + s8 tiaG_gain_db[] = { + 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A }; + s8 tiaA_gain_db[] = { + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13 }; + s8 tiaA_gain_db_rev4[] = { + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d }; + s8 tiaA_gain_db_rev5[] = { + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d }; + s8 tiaA_gain_db_rev6[] = { + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d }; + s8 *tia_gain_db; + s8 tiaG_gainbits[] = { + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 }; + s8 tiaA_gainbits[] = { + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06 }; + s8 tiaA_gainbits_rev4[] = { + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04 }; + s8 tiaA_gainbits_rev5[] = { + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04 }; + s8 tiaA_gainbits_rev6[] = { + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04 }; + s8 *tia_gainbits; + s8 lpf_gain_db[] = { 0x00, 0x06, 0x0c, 0x12, 0x12, 0x12 }; + s8 lpf_gainbits[] = { 0x00, 0x01, 0x02, 0x03, 0x03, 0x03 }; + u16 rfseqG_init_gain[] = { 0x613f, 0x613f, 0x613f, 0x613f }; + u16 rfseqG_init_gain_rev4[] = { 0x513f, 0x513f, 0x513f, 0x513f }; + u16 rfseqG_init_gain_rev5[] = { 0x413f, 0x413f, 0x413f, 0x413f }; + u16 rfseqG_init_gain_rev5_elna[] = { + 0x013f, 0x013f, 0x013f, 0x013f }; + u16 rfseqG_init_gain_rev6[] = { 0x513f, 0x513f }; + u16 rfseqG_init_gain_rev6_224B0[] = { 0x413f, 0x413f }; + u16 rfseqG_init_gain_rev6_elna[] = { 0x113f, 0x113f }; + u16 rfseqA_init_gain[] = { 0x516f, 0x516f, 0x516f, 0x516f }; + u16 rfseqA_init_gain_rev4[] = { 0x614f, 0x614f, 0x614f, 0x614f }; + u16 rfseqA_init_gain_rev4_elna[] = { + 0x314f, 0x314f, 0x314f, 0x314f }; + u16 rfseqA_init_gain_rev5[] = { 0x714f, 0x714f, 0x714f, 0x714f }; + u16 rfseqA_init_gain_rev6[] = { 0x714f, 0x714f }; + u16 *rfseq_init_gain; + u16 initG_gaincode = 0x627e; + u16 initG_gaincode_rev4 = 0x527e; + u16 initG_gaincode_rev5 = 0x427e; + u16 initG_gaincode_rev5_elna = 0x027e; + u16 initG_gaincode_rev6 = 0x527e; + u16 initG_gaincode_rev6_224B0 = 0x427e; + u16 initG_gaincode_rev6_elna = 0x127e; + u16 initA_gaincode = 0x52de; + u16 initA_gaincode_rev4 = 0x629e; + u16 initA_gaincode_rev4_elna = 0x329e; + u16 initA_gaincode_rev5 = 0x729e; + u16 initA_gaincode_rev6 = 0x729e; + u16 init_gaincode; + u16 clip1hiG_gaincode = 0x107e; + u16 clip1hiG_gaincode_rev4 = 0x007e; + u16 clip1hiG_gaincode_rev5 = 0x1076; + u16 clip1hiG_gaincode_rev6 = 0x007e; + u16 clip1hiA_gaincode = 0x00de; + u16 clip1hiA_gaincode_rev4 = 0x029e; + u16 clip1hiA_gaincode_rev5 = 0x029e; + u16 clip1hiA_gaincode_rev6 = 0x029e; + u16 clip1hi_gaincode; + u16 clip1mdG_gaincode = 0x0066; + u16 clip1mdA_gaincode = 0x00ca; + u16 clip1mdA_gaincode_rev4 = 0x1084; + u16 clip1mdA_gaincode_rev5 = 0x2084; + u16 clip1mdA_gaincode_rev6 = 0x2084; + u16 clip1md_gaincode = 0; + u16 clip1loG_gaincode = 0x0074; + u16 clip1loG_gaincode_rev5[] = { + 0x0062, 0x0064, 0x006a, 0x106a, 0x106c, 0x1074, 0x107c, 0x207c + }; + u16 clip1loG_gaincode_rev6[] = { + 0x106a, 0x106c, 0x1074, 0x107c, 0x007e, 0x107e, 0x207e, 0x307e + }; + u16 clip1loG_gaincode_rev6_224B0 = 0x1074; + u16 clip1loA_gaincode = 0x00cc; + u16 clip1loA_gaincode_rev4 = 0x0086; + u16 clip1loA_gaincode_rev5 = 0x2086; + u16 clip1loA_gaincode_rev6 = 0x2086; + u16 clip1lo_gaincode; + u8 crsminG_th = 0x18; + u8 crsminG_th_rev5 = 0x18; + u8 crsminG_th_rev6 = 0x18; + u8 crsminA_th = 0x1e; + u8 crsminA_th_rev4 = 0x24; + u8 crsminA_th_rev5 = 0x24; + u8 crsminA_th_rev6 = 0x24; + u8 crsmin_th; + u8 crsminlG_th = 0x18; + u8 crsminlG_th_rev5 = 0x18; + u8 crsminlG_th_rev6 = 0x18; + u8 crsminlA_th = 0x1e; + u8 crsminlA_th_rev4 = 0x24; + u8 crsminlA_th_rev5 = 0x24; + u8 crsminlA_th_rev6 = 0x24; + u8 crsminl_th = 0; + u8 crsminuG_th = 0x18; + u8 crsminuG_th_rev5 = 0x18; + u8 crsminuG_th_rev6 = 0x18; + u8 crsminuA_th = 0x1e; + u8 crsminuA_th_rev4 = 0x24; + u8 crsminuA_th_rev5 = 0x24; + u8 crsminuA_th_rev6 = 0x24; + u8 crsminuA_th_rev6_224B0 = 0x2d; + u8 crsminu_th; + u16 nbclipG_th = 0x20d; + u16 nbclipG_th_rev4 = 0x1a1; + u16 nbclipG_th_rev5 = 0x1d0; + u16 nbclipG_th_rev6 = 0x1d0; + u16 nbclipA_th = 0x1a1; + u16 nbclipA_th_rev4 = 0x107; + u16 nbclipA_th_rev5 = 0x0a9; + u16 nbclipA_th_rev6 = 0x0f0; + u16 nbclip_th = 0; + u8 w1clipG_th = 5; + u8 w1clipG_th_rev5 = 9; + u8 w1clipG_th_rev6 = 5; + u8 w1clipA_th = 25, w1clip_th; + u8 rssi_gain_default = 0x50; + u8 rssiG_gain_rev6_224B0 = 0x50; + u8 rssiA_gain_rev5 = 0x90; + u8 rssiA_gain_rev6 = 0x90; + u8 rssi_gain; + u16 regval[21]; + u8 triso; + + triso = (CHSPEC_IS5G(pi->radio_chanspec)) ? pi->srom_fem5g.triso : + pi->srom_fem2g.triso; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + if (pi->pubpi.radiorev == 5) { + wlc_phy_workarounds_nphy_gainctrl_2057_rev5(pi); + } else if (pi->pubpi.radiorev == 7) { + wlc_phy_workarounds_nphy_gainctrl_2057_rev6(pi); + + mod_phy_reg(pi, 0x283, (0xff << 0), (0x44 << 0)); + mod_phy_reg(pi, 0x280, (0xff << 0), (0x44 << 0)); + + } else if ((pi->pubpi.radiorev == 3) + || (pi->pubpi.radiorev == 8)) { + wlc_phy_workarounds_nphy_gainctrl_2057_rev6(pi); + + if (pi->pubpi.radiorev == 8) { + mod_phy_reg(pi, 0x283, + (0xff << 0), (0x44 << 0)); + mod_phy_reg(pi, 0x280, + (0xff << 0), (0x44 << 0)); + } + } else { + wlc_phy_workarounds_nphy_gainctrl_2057_rev6(pi); + } + } else if (NREV_GE(pi->pubpi.phy_rev, 3)) { + + mod_phy_reg(pi, 0xa0, (0x1 << 6), (1 << 6)); + + mod_phy_reg(pi, 0x1c, (0x1 << 13), (1 << 13)); + mod_phy_reg(pi, 0x32, (0x1 << 13), (1 << 13)); + + currband = + read_phy_reg(pi, 0x09) & NPHY_BandControl_currentBand; + if (currband == 0) { + if (NREV_GE(pi->pubpi.phy_rev, 6)) { + if (pi->pubpi.radiorev == 11) { + lna1_gain_db = lna1G_gain_db_rev6_224B0; + lna2_gain_db = lna2G_gain_db_rev6_224B0; + rfseq_init_gain = + rfseqG_init_gain_rev6_224B0; + init_gaincode = + initG_gaincode_rev6_224B0; + clip1hi_gaincode = + clip1hiG_gaincode_rev6; + clip1lo_gaincode = + clip1loG_gaincode_rev6_224B0; + nbclip_th = nbclipG_th_rev6; + w1clip_th = w1clipG_th_rev6; + crsmin_th = crsminG_th_rev6; + crsminl_th = crsminlG_th_rev6; + crsminu_th = crsminuG_th_rev6; + rssi_gain = rssiG_gain_rev6_224B0; + } else { + lna1_gain_db = lna1G_gain_db_rev6; + lna2_gain_db = lna2G_gain_db_rev6; + if (pi->sh->boardflags & BFL_EXTLNA) { + + rfseq_init_gain = + rfseqG_init_gain_rev6_elna; + init_gaincode = + initG_gaincode_rev6_elna; + } else { + rfseq_init_gain = + rfseqG_init_gain_rev6; + init_gaincode = + initG_gaincode_rev6; + } + clip1hi_gaincode = + clip1hiG_gaincode_rev6; + switch (triso) { + case 0: + clip1lo_gaincode = + clip1loG_gaincode_rev6 + [0]; + break; + case 1: + clip1lo_gaincode = + clip1loG_gaincode_rev6 + [1]; + break; + case 2: + clip1lo_gaincode = + clip1loG_gaincode_rev6 + [2]; + break; + case 3: + default: + + clip1lo_gaincode = + clip1loG_gaincode_rev6 + [3]; + break; + case 4: + clip1lo_gaincode = + clip1loG_gaincode_rev6 + [4]; + break; + case 5: + clip1lo_gaincode = + clip1loG_gaincode_rev6 + [5]; + break; + case 6: + clip1lo_gaincode = + clip1loG_gaincode_rev6 + [6]; + break; + case 7: + clip1lo_gaincode = + clip1loG_gaincode_rev6 + [7]; + break; + } + nbclip_th = nbclipG_th_rev6; + w1clip_th = w1clipG_th_rev6; + crsmin_th = crsminG_th_rev6; + crsminl_th = crsminlG_th_rev6; + crsminu_th = crsminuG_th_rev6; + rssi_gain = rssi_gain_default; + } + } else if (NREV_IS(pi->pubpi.phy_rev, 5)) { + lna1_gain_db = lna1G_gain_db_rev5; + lna2_gain_db = lna2G_gain_db_rev5; + if (pi->sh->boardflags & BFL_EXTLNA) { + + rfseq_init_gain = + rfseqG_init_gain_rev5_elna; + init_gaincode = + initG_gaincode_rev5_elna; + } else { + rfseq_init_gain = rfseqG_init_gain_rev5; + init_gaincode = initG_gaincode_rev5; + } + clip1hi_gaincode = clip1hiG_gaincode_rev5; + switch (triso) { + case 0: + clip1lo_gaincode = + clip1loG_gaincode_rev5[0]; + break; + case 1: + clip1lo_gaincode = + clip1loG_gaincode_rev5[1]; + break; + case 2: + clip1lo_gaincode = + clip1loG_gaincode_rev5[2]; + break; + case 3: + + clip1lo_gaincode = + clip1loG_gaincode_rev5[3]; + break; + case 4: + clip1lo_gaincode = + clip1loG_gaincode_rev5[4]; + break; + case 5: + clip1lo_gaincode = + clip1loG_gaincode_rev5[5]; + break; + case 6: + clip1lo_gaincode = + clip1loG_gaincode_rev5[6]; + break; + case 7: + clip1lo_gaincode = + clip1loG_gaincode_rev5[7]; + break; + default: + clip1lo_gaincode = + clip1loG_gaincode_rev5[3]; + break; + } + nbclip_th = nbclipG_th_rev5; + w1clip_th = w1clipG_th_rev5; + crsmin_th = crsminG_th_rev5; + crsminl_th = crsminlG_th_rev5; + crsminu_th = crsminuG_th_rev5; + rssi_gain = rssi_gain_default; + } else if (NREV_IS(pi->pubpi.phy_rev, 4)) { + lna1_gain_db = lna1G_gain_db_rev4; + lna2_gain_db = lna2G_gain_db; + rfseq_init_gain = rfseqG_init_gain_rev4; + init_gaincode = initG_gaincode_rev4; + clip1hi_gaincode = clip1hiG_gaincode_rev4; + clip1lo_gaincode = clip1loG_gaincode; + nbclip_th = nbclipG_th_rev4; + w1clip_th = w1clipG_th; + crsmin_th = crsminG_th; + crsminl_th = crsminlG_th; + crsminu_th = crsminuG_th; + rssi_gain = rssi_gain_default; + } else { + lna1_gain_db = lna1G_gain_db; + lna2_gain_db = lna2G_gain_db; + rfseq_init_gain = rfseqG_init_gain; + init_gaincode = initG_gaincode; + clip1hi_gaincode = clip1hiG_gaincode; + clip1lo_gaincode = clip1loG_gaincode; + nbclip_th = nbclipG_th; + w1clip_th = w1clipG_th; + crsmin_th = crsminG_th; + crsminl_th = crsminlG_th; + crsminu_th = crsminuG_th; + rssi_gain = rssi_gain_default; + } + tia_gain_db = tiaG_gain_db; + tia_gainbits = tiaG_gainbits; + clip1md_gaincode = clip1mdG_gaincode; + } else { + if (NREV_GE(pi->pubpi.phy_rev, 6)) { + lna1_gain_db = lna1A_gain_db_rev6; + lna2_gain_db = lna2A_gain_db_rev6; + tia_gain_db = tiaA_gain_db_rev6; + tia_gainbits = tiaA_gainbits_rev6; + rfseq_init_gain = rfseqA_init_gain_rev6; + init_gaincode = initA_gaincode_rev6; + clip1hi_gaincode = clip1hiA_gaincode_rev6; + clip1md_gaincode = clip1mdA_gaincode_rev6; + clip1lo_gaincode = clip1loA_gaincode_rev6; + crsmin_th = crsminA_th_rev6; + crsminl_th = crsminlA_th_rev6; + if ((pi->pubpi.radiorev == 11) && + (CHSPEC_IS40(pi->radio_chanspec) == 0)) + crsminu_th = crsminuA_th_rev6_224B0; + else + crsminu_th = crsminuA_th_rev6; + + nbclip_th = nbclipA_th_rev6; + rssi_gain = rssiA_gain_rev6; + } else if (NREV_IS(pi->pubpi.phy_rev, 5)) { + lna1_gain_db = lna1A_gain_db_rev5; + lna2_gain_db = lna2A_gain_db_rev5; + tia_gain_db = tiaA_gain_db_rev5; + tia_gainbits = tiaA_gainbits_rev5; + rfseq_init_gain = rfseqA_init_gain_rev5; + init_gaincode = initA_gaincode_rev5; + clip1hi_gaincode = clip1hiA_gaincode_rev5; + clip1md_gaincode = clip1mdA_gaincode_rev5; + clip1lo_gaincode = clip1loA_gaincode_rev5; + crsmin_th = crsminA_th_rev5; + crsminl_th = crsminlA_th_rev5; + crsminu_th = crsminuA_th_rev5; + nbclip_th = nbclipA_th_rev5; + rssi_gain = rssiA_gain_rev5; + } else if (NREV_IS(pi->pubpi.phy_rev, 4)) { + lna1_gain_db = lna1A_gain_db_rev4; + lna2_gain_db = lna2A_gain_db_rev4; + tia_gain_db = tiaA_gain_db_rev4; + tia_gainbits = tiaA_gainbits_rev4; + if (pi->sh->boardflags & BFL_EXTLNA_5GHz) { + + rfseq_init_gain = + rfseqA_init_gain_rev4_elna; + init_gaincode = + initA_gaincode_rev4_elna; + } else { + rfseq_init_gain = rfseqA_init_gain_rev4; + init_gaincode = initA_gaincode_rev4; + } + clip1hi_gaincode = clip1hiA_gaincode_rev4; + clip1md_gaincode = clip1mdA_gaincode_rev4; + clip1lo_gaincode = clip1loA_gaincode_rev4; + crsmin_th = crsminA_th_rev4; + crsminl_th = crsminlA_th_rev4; + crsminu_th = crsminuA_th_rev4; + nbclip_th = nbclipA_th_rev4; + rssi_gain = rssi_gain_default; + } else { + lna1_gain_db = lna1A_gain_db; + lna2_gain_db = lna2A_gain_db; + tia_gain_db = tiaA_gain_db; + tia_gainbits = tiaA_gainbits; + rfseq_init_gain = rfseqA_init_gain; + init_gaincode = initA_gaincode; + clip1hi_gaincode = clip1hiA_gaincode; + clip1md_gaincode = clip1mdA_gaincode; + clip1lo_gaincode = clip1loA_gaincode; + crsmin_th = crsminA_th; + crsminl_th = crsminlA_th; + crsminu_th = crsminuA_th; + nbclip_th = nbclipA_th; + rssi_gain = rssi_gain_default; + } + w1clip_th = w1clipA_th; + } + + write_radio_reg(pi, + (RADIO_2056_RX_BIASPOLE_LNAG1_IDAC | + RADIO_2056_RX0), 0x17); + write_radio_reg(pi, + (RADIO_2056_RX_BIASPOLE_LNAG1_IDAC | + RADIO_2056_RX1), 0x17); + + write_radio_reg(pi, (RADIO_2056_RX_LNAG2_IDAC | RADIO_2056_RX0), + 0xf0); + write_radio_reg(pi, (RADIO_2056_RX_LNAG2_IDAC | RADIO_2056_RX1), + 0xf0); + + write_radio_reg(pi, (RADIO_2056_RX_RSSI_POLE | RADIO_2056_RX0), + 0x0); + write_radio_reg(pi, (RADIO_2056_RX_RSSI_POLE | RADIO_2056_RX1), + 0x0); + + write_radio_reg(pi, (RADIO_2056_RX_RSSI_GAIN | RADIO_2056_RX0), + rssi_gain); + write_radio_reg(pi, (RADIO_2056_RX_RSSI_GAIN | RADIO_2056_RX1), + rssi_gain); + + write_radio_reg(pi, + (RADIO_2056_RX_BIASPOLE_LNAA1_IDAC | + RADIO_2056_RX0), 0x17); + write_radio_reg(pi, + (RADIO_2056_RX_BIASPOLE_LNAA1_IDAC | + RADIO_2056_RX1), 0x17); + + write_radio_reg(pi, (RADIO_2056_RX_LNAA2_IDAC | RADIO_2056_RX0), + 0xFF); + write_radio_reg(pi, (RADIO_2056_RX_LNAA2_IDAC | RADIO_2056_RX1), + 0xFF); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN1, 4, 8, + 8, lna1_gain_db); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN2, 4, 8, + 8, lna1_gain_db); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN1, 4, 0x10, + 8, lna2_gain_db); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN2, 4, 0x10, + 8, lna2_gain_db); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN1, 10, 0x20, + 8, tia_gain_db); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN2, 10, 0x20, + 8, tia_gain_db); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAINBITS1, 10, 0x20, + 8, tia_gainbits); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAINBITS2, 10, 0x20, + 8, tia_gainbits); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN1, 6, 0x40, + 8, &lpf_gain_db); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAIN2, 6, 0x40, + 8, &lpf_gain_db); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAINBITS1, 6, 0x40, + 8, &lpf_gainbits); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_GAINBITS2, 6, 0x40, + 8, &lpf_gainbits); + + write_phy_reg(pi, 0x20, init_gaincode); + write_phy_reg(pi, 0x2a7, init_gaincode); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, + pi->pubpi.phy_corenum, 0x106, 16, + rfseq_init_gain); + + write_phy_reg(pi, 0x22, clip1hi_gaincode); + write_phy_reg(pi, 0x2a9, clip1hi_gaincode); + + write_phy_reg(pi, 0x24, clip1md_gaincode); + write_phy_reg(pi, 0x2ab, clip1md_gaincode); + + write_phy_reg(pi, 0x37, clip1lo_gaincode); + write_phy_reg(pi, 0x2ad, clip1lo_gaincode); + + mod_phy_reg(pi, 0x27d, (0xff << 0), (crsmin_th << 0)); + mod_phy_reg(pi, 0x280, (0xff << 0), (crsminl_th << 0)); + mod_phy_reg(pi, 0x283, (0xff << 0), (crsminu_th << 0)); + + write_phy_reg(pi, 0x2b, nbclip_th); + write_phy_reg(pi, 0x41, nbclip_th); + + mod_phy_reg(pi, 0x27, (0x3f << 0), (w1clip_th << 0)); + mod_phy_reg(pi, 0x3d, (0x3f << 0), (w1clip_th << 0)); + + write_phy_reg(pi, 0x150, 0x809c); + + } else { + + mod_phy_reg(pi, 0x1c, (0x1 << 13), (1 << 13)); + mod_phy_reg(pi, 0x32, (0x1 << 13), (1 << 13)); + + write_phy_reg(pi, 0x2b, 0x84); + write_phy_reg(pi, 0x41, 0x84); + + if (CHSPEC_IS20(pi->radio_chanspec)) { + write_phy_reg(pi, 0x6b, 0x2b); + write_phy_reg(pi, 0x6c, 0x2b); + write_phy_reg(pi, 0x6d, 0x9); + write_phy_reg(pi, 0x6e, 0x9); + } + + w1th = NPHY_RSSICAL_W1_TARGET - 4; + mod_phy_reg(pi, 0x27, (0x3f << 0), (w1th << 0)); + mod_phy_reg(pi, 0x3d, (0x3f << 0), (w1th << 0)); + + if (CHSPEC_IS20(pi->radio_chanspec)) { + mod_phy_reg(pi, 0x1c, (0x1f << 0), (0x1 << 0)); + mod_phy_reg(pi, 0x32, (0x1f << 0), (0x1 << 0)); + + mod_phy_reg(pi, 0x1d, (0x1f << 0), (0x1 << 0)); + mod_phy_reg(pi, 0x33, (0x1f << 0), (0x1 << 0)); + } + + write_phy_reg(pi, 0x150, 0x809c); + + if (pi->nphy_gain_boost) + if ((CHSPEC_IS2G(pi->radio_chanspec)) && + (CHSPEC_IS40(pi->radio_chanspec))) + hpf_code = 4; + else + hpf_code = 5; + else if (CHSPEC_IS40(pi->radio_chanspec)) + hpf_code = 6; + else + hpf_code = 7; + + mod_phy_reg(pi, 0x20, (0x1f << 7), (hpf_code << 7)); + mod_phy_reg(pi, 0x36, (0x1f << 7), (hpf_code << 7)); + + for (ctr = 0; ctr < 4; ctr++) + regval[ctr] = (hpf_code << 8) | 0x7c; + wlc_phy_table_write_nphy(pi, 7, 4, 0x106, 16, regval); + + wlc_phy_adjust_lnagaintbl_nphy(pi); + + if (pi->nphy_elna_gain_config) { + regval[0] = 0; + regval[1] = 1; + regval[2] = 1; + regval[3] = 1; + wlc_phy_table_write_nphy(pi, 2, 4, 8, 16, regval); + wlc_phy_table_write_nphy(pi, 3, 4, 8, 16, regval); + + for (ctr = 0; ctr < 4; ctr++) + regval[ctr] = (hpf_code << 8) | 0x74; + wlc_phy_table_write_nphy(pi, 7, 4, 0x106, 16, regval); + } + + if (NREV_IS(pi->pubpi.phy_rev, 2)) { + for (ctr = 0; ctr < 21; ctr++) + regval[ctr] = 3 * ctr; + wlc_phy_table_write_nphy(pi, 0, 21, 32, 16, regval); + wlc_phy_table_write_nphy(pi, 1, 21, 32, 16, regval); + + for (ctr = 0; ctr < 21; ctr++) + regval[ctr] = (u16) ctr; + wlc_phy_table_write_nphy(pi, 2, 21, 32, 16, regval); + wlc_phy_table_write_nphy(pi, 3, 21, 32, 16, regval); + } + + wlc_phy_set_rfseq_nphy(pi, NPHY_RFSEQ_UPDATEGAINU, + rfseq_updategainu_events, + rfseq_updategainu_dlys, + sizeof(rfseq_updategainu_events) / + sizeof(rfseq_updategainu_events[0])); + + mod_phy_reg(pi, 0x153, (0xff << 8), (90 << 8)); + + if (CHSPEC_IS2G(pi->radio_chanspec)) + mod_phy_reg(pi, + (NPHY_TO_BPHY_OFF + BPHY_OPTIONAL_MODES), + 0x7f, 0x4); + } +} + +static void wlc_phy_workarounds_nphy(struct brcms_phy *pi) +{ + u8 rfseq_rx2tx_events[] = { + NPHY_RFSEQ_CMD_NOP, + NPHY_RFSEQ_CMD_RXG_FBW, + NPHY_RFSEQ_CMD_TR_SWITCH, + NPHY_RFSEQ_CMD_CLR_HIQ_DIS, + NPHY_RFSEQ_CMD_RXPD_TXPD, + NPHY_RFSEQ_CMD_TX_GAIN, + NPHY_RFSEQ_CMD_EXT_PA + }; + u8 rfseq_rx2tx_dlys[] = { 8, 6, 6, 2, 4, 60, 1 }; + u8 rfseq_tx2rx_events[] = { + NPHY_RFSEQ_CMD_NOP, + NPHY_RFSEQ_CMD_EXT_PA, + NPHY_RFSEQ_CMD_TX_GAIN, + NPHY_RFSEQ_CMD_RXPD_TXPD, + NPHY_RFSEQ_CMD_TR_SWITCH, + NPHY_RFSEQ_CMD_RXG_FBW, + NPHY_RFSEQ_CMD_CLR_HIQ_DIS + }; + u8 rfseq_tx2rx_dlys[] = { 8, 6, 2, 4, 4, 6, 1 }; + u8 rfseq_tx2rx_events_rev3[] = { + NPHY_REV3_RFSEQ_CMD_EXT_PA, + NPHY_REV3_RFSEQ_CMD_INT_PA_PU, + NPHY_REV3_RFSEQ_CMD_TX_GAIN, + NPHY_REV3_RFSEQ_CMD_RXPD_TXPD, + NPHY_REV3_RFSEQ_CMD_TR_SWITCH, + NPHY_REV3_RFSEQ_CMD_RXG_FBW, + NPHY_REV3_RFSEQ_CMD_CLR_HIQ_DIS, + NPHY_REV3_RFSEQ_CMD_END + }; + u8 rfseq_tx2rx_dlys_rev3[] = { 8, 4, 2, 2, 4, 4, 6, 1 }; + u8 rfseq_rx2tx_events_rev3[] = { + NPHY_REV3_RFSEQ_CMD_NOP, + NPHY_REV3_RFSEQ_CMD_RXG_FBW, + NPHY_REV3_RFSEQ_CMD_TR_SWITCH, + NPHY_REV3_RFSEQ_CMD_CLR_HIQ_DIS, + NPHY_REV3_RFSEQ_CMD_RXPD_TXPD, + NPHY_REV3_RFSEQ_CMD_TX_GAIN, + NPHY_REV3_RFSEQ_CMD_INT_PA_PU, + NPHY_REV3_RFSEQ_CMD_EXT_PA, + NPHY_REV3_RFSEQ_CMD_END + }; + u8 rfseq_rx2tx_dlys_rev3[] = { 8, 6, 6, 4, 4, 18, 42, 1, 1 }; + + u8 rfseq_rx2tx_events_rev3_ipa[] = { + NPHY_REV3_RFSEQ_CMD_NOP, + NPHY_REV3_RFSEQ_CMD_RXG_FBW, + NPHY_REV3_RFSEQ_CMD_TR_SWITCH, + NPHY_REV3_RFSEQ_CMD_CLR_HIQ_DIS, + NPHY_REV3_RFSEQ_CMD_RXPD_TXPD, + NPHY_REV3_RFSEQ_CMD_TX_GAIN, + NPHY_REV3_RFSEQ_CMD_CLR_RXRX_BIAS, + NPHY_REV3_RFSEQ_CMD_INT_PA_PU, + NPHY_REV3_RFSEQ_CMD_END + }; + u8 rfseq_rx2tx_dlys_rev3_ipa[] = { 8, 6, 6, 4, 4, 16, 43, 1, 1 }; + u16 rfseq_rx2tx_dacbufpu_rev7[] = { 0x10f, 0x10f }; + + s16 alpha0, alpha1, alpha2; + s16 beta0, beta1, beta2; + u32 leg_data_weights, ht_data_weights, nss1_data_weights, + stbc_data_weights; + u8 chan_freq_range = 0; + u16 dac_control = 0x0002; + u16 aux_adc_vmid_rev7_core0[] = { 0x8e, 0x96, 0x96, 0x96 }; + u16 aux_adc_vmid_rev7_core1[] = { 0x8f, 0x9f, 0x9f, 0x96 }; + u16 aux_adc_vmid_rev4[] = { 0xa2, 0xb4, 0xb4, 0x89 }; + u16 aux_adc_vmid_rev3[] = { 0xa2, 0xb4, 0xb4, 0x89 }; + u16 *aux_adc_vmid; + u16 aux_adc_gain_rev7[] = { 0x02, 0x02, 0x02, 0x02 }; + u16 aux_adc_gain_rev4[] = { 0x02, 0x02, 0x02, 0x00 }; + u16 aux_adc_gain_rev3[] = { 0x02, 0x02, 0x02, 0x00 }; + u16 *aux_adc_gain; + u16 sk_adc_vmid[] = { 0xb4, 0xb4, 0xb4, 0x24 }; + u16 sk_adc_gain[] = { 0x02, 0x02, 0x02, 0x02 }; + s32 min_nvar_val = 0x18d; + s32 min_nvar_offset_6mbps = 20; + u8 pdetrange; + u8 triso; + u16 regval; + u16 afectrl_adc_ctrl1_rev7 = 0x20; + u16 afectrl_adc_ctrl2_rev7 = 0x0; + u16 rfseq_rx2tx_lpf_h_hpc_rev7 = 0x77; + u16 rfseq_tx2rx_lpf_h_hpc_rev7 = 0x77; + u16 rfseq_pktgn_lpf_h_hpc_rev7 = 0x77; + u16 rfseq_htpktgn_lpf_hpc_rev7[] = { 0x77, 0x11, 0x11 }; + u16 rfseq_pktgn_lpf_hpc_rev7[] = { 0x11, 0x11 }; + u16 rfseq_cckpktgn_lpf_hpc_rev7[] = { 0x11, 0x11 }; + u16 ipalvlshift_3p3_war_en = 0; + u16 rccal_bcap_val, rccal_scap_val; + u16 rccal_tx20_11b_bcap = 0; + u16 rccal_tx20_11b_scap = 0; + u16 rccal_tx20_11n_bcap = 0; + u16 rccal_tx20_11n_scap = 0; + u16 rccal_tx40_11n_bcap = 0; + u16 rccal_tx40_11n_scap = 0; + u16 rx2tx_lpf_rc_lut_tx20_11b = 0; + u16 rx2tx_lpf_rc_lut_tx20_11n = 0; + u16 rx2tx_lpf_rc_lut_tx40_11n = 0; + u16 tx_lpf_bw_ofdm_20mhz = 0; + u16 tx_lpf_bw_ofdm_40mhz = 0; + u16 tx_lpf_bw_11b = 0; + u16 ipa2g_mainbias, ipa2g_casconv, ipa2g_biasfilt; + u16 txgm_idac_bleed = 0; + bool rccal_ovrd = false; + u16 freq; + int coreNum; + + if (CHSPEC_IS5G(pi->radio_chanspec)) + wlc_phy_classifier_nphy(pi, NPHY_ClassifierCtrl_cck_en, 0); + else + wlc_phy_classifier_nphy(pi, NPHY_ClassifierCtrl_cck_en, 1); + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + or_phy_reg(pi, 0xb1, NPHY_IQFlip_ADC1 | NPHY_IQFlip_ADC2); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + if (NREV_IS(pi->pubpi.phy_rev, 7)) { + mod_phy_reg(pi, 0x221, (0x1 << 4), (1 << 4)); + + mod_phy_reg(pi, 0x160, (0x7f << 0), (32 << 0)); + mod_phy_reg(pi, 0x160, (0x7f << 8), (39 << 8)); + mod_phy_reg(pi, 0x161, (0x7f << 0), (46 << 0)); + mod_phy_reg(pi, 0x161, (0x7f << 8), (51 << 8)); + mod_phy_reg(pi, 0x162, (0x7f << 0), (55 << 0)); + mod_phy_reg(pi, 0x162, (0x7f << 8), (58 << 8)); + mod_phy_reg(pi, 0x163, (0x7f << 0), (60 << 0)); + mod_phy_reg(pi, 0x163, (0x7f << 8), (62 << 8)); + mod_phy_reg(pi, 0x164, (0x7f << 0), (62 << 0)); + mod_phy_reg(pi, 0x164, (0x7f << 8), (63 << 8)); + mod_phy_reg(pi, 0x165, (0x7f << 0), (63 << 0)); + mod_phy_reg(pi, 0x165, (0x7f << 8), (64 << 8)); + mod_phy_reg(pi, 0x166, (0x7f << 0), (64 << 0)); + mod_phy_reg(pi, 0x166, (0x7f << 8), (64 << 8)); + mod_phy_reg(pi, 0x167, (0x7f << 0), (64 << 0)); + mod_phy_reg(pi, 0x167, (0x7f << 8), (64 << 8)); + } + + if (NREV_LE(pi->pubpi.phy_rev, 8)) { + write_phy_reg(pi, 0x23f, 0x1b0); + write_phy_reg(pi, 0x240, 0x1b0); + } + + if (NREV_GE(pi->pubpi.phy_rev, 8)) + mod_phy_reg(pi, 0xbd, (0xff << 0), (114 << 0)); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x00, 16, + &dac_control); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x10, 16, + &dac_control); + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_CMPMETRICDATAWEIGHTTBL, + 1, 0, 32, &leg_data_weights); + leg_data_weights = leg_data_weights & 0xffffff; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_CMPMETRICDATAWEIGHTTBL, + 1, 0, 32, &leg_data_weights); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, + 2, 0x15e, 16, + rfseq_rx2tx_dacbufpu_rev7); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, 2, 0x16e, 16, + rfseq_rx2tx_dacbufpu_rev7); + + if (PHY_IPA(pi)) + wlc_phy_set_rfseq_nphy(pi, NPHY_RFSEQ_RX2TX, + rfseq_rx2tx_events_rev3_ipa, + rfseq_rx2tx_dlys_rev3_ipa, + ARRAY_SIZE(rfseq_rx2tx_events_rev3_ipa)); + + mod_phy_reg(pi, 0x299, (0x3 << 14), (0x1 << 14)); + mod_phy_reg(pi, 0x29d, (0x3 << 14), (0x1 << 14)); + + tx_lpf_bw_ofdm_20mhz = wlc_phy_read_lpf_bw_ctl_nphy(pi, 0x154); + tx_lpf_bw_ofdm_40mhz = wlc_phy_read_lpf_bw_ctl_nphy(pi, 0x159); + tx_lpf_bw_11b = wlc_phy_read_lpf_bw_ctl_nphy(pi, 0x152); + + if (PHY_IPA(pi)) { + + if (((pi->pubpi.radiorev == 5) + && (CHSPEC_IS40(pi->radio_chanspec) == 1)) + || (pi->pubpi.radiorev == 7) + || (pi->pubpi.radiorev == 8)) { + + rccal_bcap_val = + read_radio_reg( + pi, + RADIO_2057_RCCAL_BCAP_VAL); + rccal_scap_val = + read_radio_reg( + pi, + RADIO_2057_RCCAL_SCAP_VAL); + + rccal_tx20_11b_bcap = rccal_bcap_val; + rccal_tx20_11b_scap = rccal_scap_val; + + if ((pi->pubpi.radiorev == 5) && + (CHSPEC_IS40(pi->radio_chanspec) == 1)) { + + rccal_tx20_11n_bcap = rccal_bcap_val; + rccal_tx20_11n_scap = rccal_scap_val; + rccal_tx40_11n_bcap = 0xc; + rccal_tx40_11n_scap = 0xc; + + rccal_ovrd = true; + + } else if ((pi->pubpi.radiorev == 7) + || (pi->pubpi.radiorev == 8)) { + + tx_lpf_bw_ofdm_20mhz = 4; + tx_lpf_bw_11b = 1; + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + rccal_tx20_11n_bcap = 0xc; + rccal_tx20_11n_scap = 0xc; + rccal_tx40_11n_bcap = 0xa; + rccal_tx40_11n_scap = 0xa; + } else { + rccal_tx20_11n_bcap = 0x14; + rccal_tx20_11n_scap = 0x14; + rccal_tx40_11n_bcap = 0xf; + rccal_tx40_11n_scap = 0xf; + } + + rccal_ovrd = true; + } + } + + } else { + + if (pi->pubpi.radiorev == 5) { + + tx_lpf_bw_ofdm_20mhz = 1; + tx_lpf_bw_ofdm_40mhz = 3; + + rccal_bcap_val = + read_radio_reg( + pi, + RADIO_2057_RCCAL_BCAP_VAL); + rccal_scap_val = + read_radio_reg( + pi, + RADIO_2057_RCCAL_SCAP_VAL); + + rccal_tx20_11b_bcap = rccal_bcap_val; + rccal_tx20_11b_scap = rccal_scap_val; + + rccal_tx20_11n_bcap = 0x13; + rccal_tx20_11n_scap = 0x11; + rccal_tx40_11n_bcap = 0x13; + rccal_tx40_11n_scap = 0x11; + + rccal_ovrd = true; + } + } + + if (rccal_ovrd) { + + rx2tx_lpf_rc_lut_tx20_11b = + (rccal_tx20_11b_bcap << 8) | + (rccal_tx20_11b_scap << 3) | + tx_lpf_bw_11b; + rx2tx_lpf_rc_lut_tx20_11n = + (rccal_tx20_11n_bcap << 8) | + (rccal_tx20_11n_scap << 3) | + tx_lpf_bw_ofdm_20mhz; + rx2tx_lpf_rc_lut_tx40_11n = + (rccal_tx40_11n_bcap << 8) | + (rccal_tx40_11n_scap << 3) | + tx_lpf_bw_ofdm_40mhz; + + for (coreNum = 0; coreNum <= 1; coreNum++) { + wlc_phy_table_write_nphy( + pi, NPHY_TBL_ID_RFSEQ, + 1, + 0x152 + coreNum * 0x10, + 16, + &rx2tx_lpf_rc_lut_tx20_11b); + wlc_phy_table_write_nphy( + pi, NPHY_TBL_ID_RFSEQ, + 1, + 0x153 + coreNum * 0x10, + 16, + &rx2tx_lpf_rc_lut_tx20_11n); + wlc_phy_table_write_nphy( + pi, NPHY_TBL_ID_RFSEQ, + 1, + 0x154 + coreNum * 0x10, + 16, + &rx2tx_lpf_rc_lut_tx20_11n); + wlc_phy_table_write_nphy( + pi, NPHY_TBL_ID_RFSEQ, + 1, + 0x155 + coreNum * 0x10, + 16, + &rx2tx_lpf_rc_lut_tx40_11n); + wlc_phy_table_write_nphy( + pi, NPHY_TBL_ID_RFSEQ, + 1, + 0x156 + coreNum * 0x10, + 16, + &rx2tx_lpf_rc_lut_tx40_11n); + wlc_phy_table_write_nphy( + pi, NPHY_TBL_ID_RFSEQ, + 1, + 0x157 + coreNum * 0x10, + 16, + &rx2tx_lpf_rc_lut_tx40_11n); + wlc_phy_table_write_nphy( + pi, NPHY_TBL_ID_RFSEQ, + 1, + 0x158 + coreNum * 0x10, + 16, + &rx2tx_lpf_rc_lut_tx40_11n); + wlc_phy_table_write_nphy( + pi, NPHY_TBL_ID_RFSEQ, + 1, + 0x159 + coreNum * 0x10, + 16, + &rx2tx_lpf_rc_lut_tx40_11n); + } + + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 4), + 1, 0x3, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID2); + } + + write_phy_reg(pi, 0x32f, 0x3); + + if ((pi->pubpi.radiorev == 4) || (pi->pubpi.radiorev == 6)) + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 2), + 1, 0x3, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + + if ((pi->pubpi.radiorev == 3) || (pi->pubpi.radiorev == 4) || + (pi->pubpi.radiorev == 6)) { + if ((pi->sh->sromrev >= 8) + && (pi->sh->boardflags2 & BFL2_IPALVLSHIFT_3P3)) + ipalvlshift_3p3_war_en = 1; + + if (ipalvlshift_3p3_war_en) { + write_radio_reg(pi, RADIO_2057_GPAIO_CONFIG, + 0x5); + write_radio_reg(pi, RADIO_2057_GPAIO_SEL1, + 0x30); + write_radio_reg(pi, RADIO_2057_GPAIO_SEL0, 0x0); + or_radio_reg(pi, + RADIO_2057_RXTXBIAS_CONFIG_CORE0, + 0x1); + or_radio_reg(pi, + RADIO_2057_RXTXBIAS_CONFIG_CORE1, + 0x1); + + ipa2g_mainbias = 0x1f; + + ipa2g_casconv = 0x6f; + + ipa2g_biasfilt = 0xaa; + } else { + + ipa2g_mainbias = 0x2b; + + ipa2g_casconv = 0x7f; + + ipa2g_biasfilt = 0xee; + } + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + for (coreNum = 0; coreNum <= 1; coreNum++) { + WRITE_RADIO_REG4(pi, RADIO_2057, CORE, + coreNum, IPA2G_IMAIN, + ipa2g_mainbias); + WRITE_RADIO_REG4(pi, RADIO_2057, CORE, + coreNum, IPA2G_CASCONV, + ipa2g_casconv); + WRITE_RADIO_REG4(pi, RADIO_2057, CORE, + coreNum, + IPA2G_BIAS_FILTER, + ipa2g_biasfilt); + } + } + } + + if (PHY_IPA(pi)) { + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if ((pi->pubpi.radiorev == 3) + || (pi->pubpi.radiorev == 4) + || (pi->pubpi.radiorev == 6)) + txgm_idac_bleed = 0x7f; + + for (coreNum = 0; coreNum <= 1; coreNum++) { + if (txgm_idac_bleed != 0) + WRITE_RADIO_REG4( + pi, RADIO_2057, + CORE, coreNum, + TXGM_IDAC_BLEED, + txgm_idac_bleed); + } + + if (pi->pubpi.radiorev == 5) { + + for (coreNum = 0; coreNum <= 1; + coreNum++) { + WRITE_RADIO_REG4(pi, RADIO_2057, + CORE, coreNum, + IPA2G_CASCONV, + 0x13); + WRITE_RADIO_REG4(pi, RADIO_2057, + CORE, coreNum, + IPA2G_IMAIN, + 0x1f); + WRITE_RADIO_REG4( + pi, RADIO_2057, + CORE, coreNum, + IPA2G_BIAS_FILTER, + 0xee); + WRITE_RADIO_REG4(pi, RADIO_2057, + CORE, coreNum, + PAD2G_IDACS, + 0x8a); + WRITE_RADIO_REG4( + pi, RADIO_2057, + CORE, coreNum, + PAD_BIAS_FILTER_BWS, + 0x3e); + } + + } else if ((pi->pubpi.radiorev == 7) + || (pi->pubpi.radiorev == 8)) { + + if (CHSPEC_IS40(pi->radio_chanspec) == + 0) { + WRITE_RADIO_REG4(pi, RADIO_2057, + CORE, 0, + IPA2G_IMAIN, + 0x14); + WRITE_RADIO_REG4(pi, RADIO_2057, + CORE, 1, + IPA2G_IMAIN, + 0x12); + } else { + WRITE_RADIO_REG4(pi, RADIO_2057, + CORE, 0, + IPA2G_IMAIN, + 0x16); + WRITE_RADIO_REG4(pi, RADIO_2057, + CORE, 1, + IPA2G_IMAIN, + 0x16); + } + } + + } else { + freq = CHAN5G_FREQ(CHSPEC_CHANNEL( + pi->radio_chanspec)); + if (((freq >= 5180) && (freq <= 5230)) + || ((freq >= 5745) && (freq <= 5805))) { + WRITE_RADIO_REG4(pi, RADIO_2057, CORE, + 0, IPA5G_BIAS_FILTER, + 0xff); + WRITE_RADIO_REG4(pi, RADIO_2057, CORE, + 1, IPA5G_BIAS_FILTER, + 0xff); + } + } + } else { + + if (pi->pubpi.radiorev != 5) { + for (coreNum = 0; coreNum <= 1; coreNum++) { + WRITE_RADIO_REG4(pi, RADIO_2057, CORE, + coreNum, + TXMIX2G_TUNE_BOOST_PU, + 0x61); + WRITE_RADIO_REG4(pi, RADIO_2057, CORE, + coreNum, + TXGM_IDAC_BLEED, 0x70); + } + } + } + + if (pi->pubpi.radiorev == 4) { + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, + 0x05, 16, + &afectrl_adc_ctrl1_rev7); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, + 0x15, 16, + &afectrl_adc_ctrl1_rev7); + + for (coreNum = 0; coreNum <= 1; coreNum++) { + WRITE_RADIO_REG4(pi, RADIO_2057, CORE, coreNum, + AFE_VCM_CAL_MASTER, 0x0); + WRITE_RADIO_REG4(pi, RADIO_2057, CORE, coreNum, + AFE_SET_VCM_I, 0x3f); + WRITE_RADIO_REG4(pi, RADIO_2057, CORE, coreNum, + AFE_SET_VCM_Q, 0x3f); + } + } else { + mod_phy_reg(pi, 0xa6, (0x1 << 2), (0x1 << 2)); + mod_phy_reg(pi, 0x8f, (0x1 << 2), (0x1 << 2)); + mod_phy_reg(pi, 0xa7, (0x1 << 2), (0x1 << 2)); + mod_phy_reg(pi, 0xa5, (0x1 << 2), (0x1 << 2)); + + mod_phy_reg(pi, 0xa6, (0x1 << 0), 0); + mod_phy_reg(pi, 0x8f, (0x1 << 0), (0x1 << 0)); + mod_phy_reg(pi, 0xa7, (0x1 << 0), 0); + mod_phy_reg(pi, 0xa5, (0x1 << 0), (0x1 << 0)); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, + 0x05, 16, + &afectrl_adc_ctrl2_rev7); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, + 0x15, 16, + &afectrl_adc_ctrl2_rev7); + + mod_phy_reg(pi, 0xa6, (0x1 << 2), 0); + mod_phy_reg(pi, 0x8f, (0x1 << 2), 0); + mod_phy_reg(pi, 0xa7, (0x1 << 2), 0); + mod_phy_reg(pi, 0xa5, (0x1 << 2), 0); + } + + write_phy_reg(pi, 0x6a, 0x2); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_NOISEVAR, 1, 256, 32, + &min_nvar_offset_6mbps); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, 2, 0x138, 16, + &rfseq_pktgn_lpf_hpc_rev7); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, 1, 0x141, 16, + &rfseq_pktgn_lpf_h_hpc_rev7); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, 3, 0x133, 16, + &rfseq_htpktgn_lpf_hpc_rev7); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, 2, 0x146, 16, + &rfseq_cckpktgn_lpf_hpc_rev7); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, 1, 0x123, 16, + &rfseq_tx2rx_lpf_h_hpc_rev7); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, 1, 0x12A, 16, + &rfseq_rx2tx_lpf_h_hpc_rev7); + + if (CHSPEC_IS40(pi->radio_chanspec) == 0) { + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_NOISEVAR, 1, 3, + 32, &min_nvar_val); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_NOISEVAR, 1, + 127, 32, &min_nvar_val); + } else { + min_nvar_val = noise_var_tbl_rev7[3]; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_NOISEVAR, 1, 3, + 32, &min_nvar_val); + + min_nvar_val = noise_var_tbl_rev7[127]; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_NOISEVAR, 1, + 127, 32, &min_nvar_val); + } + + wlc_phy_workarounds_nphy_gainctrl(pi); + + pdetrange = + (CHSPEC_IS5G(pi->radio_chanspec)) ? pi->srom_fem5g. + pdetrange : pi->srom_fem2g.pdetrange; + + if (pdetrange == 0) { + chan_freq_range = + wlc_phy_get_chan_freq_range_nphy(pi, 0); + if (chan_freq_range != WL_CHAN_FREQ_RANGE_2G) { + aux_adc_vmid_rev7_core0[3] = 0x70; + aux_adc_vmid_rev7_core1[3] = 0x70; + aux_adc_gain_rev7[3] = 2; + } else { + aux_adc_vmid_rev7_core0[3] = 0x80; + aux_adc_vmid_rev7_core1[3] = 0x80; + aux_adc_gain_rev7[3] = 3; + } + } else if (pdetrange == 1) { + if (chan_freq_range != WL_CHAN_FREQ_RANGE_2G) { + aux_adc_vmid_rev7_core0[3] = 0x7c; + aux_adc_vmid_rev7_core1[3] = 0x7c; + aux_adc_gain_rev7[3] = 2; + } else { + aux_adc_vmid_rev7_core0[3] = 0x8c; + aux_adc_vmid_rev7_core1[3] = 0x8c; + aux_adc_gain_rev7[3] = 1; + } + } else if (pdetrange == 2) { + if (pi->pubpi.radioid == BCM2057_ID) { + if ((pi->pubpi.radiorev == 5) + || (pi->pubpi.radiorev == 7) + || (pi->pubpi.radiorev == 8)) { + if (chan_freq_range == + WL_CHAN_FREQ_RANGE_2G) { + aux_adc_vmid_rev7_core0[3] = + 0x8c; + aux_adc_vmid_rev7_core1[3] = + 0x8c; + aux_adc_gain_rev7[3] = 0; + } else { + aux_adc_vmid_rev7_core0[3] = + 0x96; + aux_adc_vmid_rev7_core1[3] = + 0x96; + aux_adc_gain_rev7[3] = 0; + } + } + } + + } else if (pdetrange == 3) { + if (chan_freq_range == WL_CHAN_FREQ_RANGE_2G) { + aux_adc_vmid_rev7_core0[3] = 0x89; + aux_adc_vmid_rev7_core1[3] = 0x89; + aux_adc_gain_rev7[3] = 0; + } + + } else if (pdetrange == 5) { + + if (chan_freq_range != WL_CHAN_FREQ_RANGE_2G) { + aux_adc_vmid_rev7_core0[3] = 0x80; + aux_adc_vmid_rev7_core1[3] = 0x80; + aux_adc_gain_rev7[3] = 3; + } else { + aux_adc_vmid_rev7_core0[3] = 0x70; + aux_adc_vmid_rev7_core1[3] = 0x70; + aux_adc_gain_rev7[3] = 2; + } + } + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, 0x08, 16, + &aux_adc_vmid_rev7_core0); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, 0x18, 16, + &aux_adc_vmid_rev7_core1); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, 0x0c, 16, + &aux_adc_gain_rev7); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, 0x1c, 16, + &aux_adc_gain_rev7); + + } else if (NREV_GE(pi->pubpi.phy_rev, 3)) { + + write_phy_reg(pi, 0x23f, 0x1f8); + write_phy_reg(pi, 0x240, 0x1f8); + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_CMPMETRICDATAWEIGHTTBL, + 1, 0, 32, &leg_data_weights); + leg_data_weights = leg_data_weights & 0xffffff; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_CMPMETRICDATAWEIGHTTBL, + 1, 0, 32, &leg_data_weights); + + alpha0 = 293; + alpha1 = 435; + alpha2 = 261; + beta0 = 366; + beta1 = 205; + beta2 = 32; + write_phy_reg(pi, 0x145, alpha0); + write_phy_reg(pi, 0x146, alpha1); + write_phy_reg(pi, 0x147, alpha2); + write_phy_reg(pi, 0x148, beta0); + write_phy_reg(pi, 0x149, beta1); + write_phy_reg(pi, 0x14a, beta2); + + write_phy_reg(pi, 0x38, 0xC); + write_phy_reg(pi, 0x2ae, 0xC); + + wlc_phy_set_rfseq_nphy(pi, NPHY_RFSEQ_TX2RX, + rfseq_tx2rx_events_rev3, + rfseq_tx2rx_dlys_rev3, + ARRAY_SIZE(rfseq_tx2rx_events_rev3)); + + if (PHY_IPA(pi)) + wlc_phy_set_rfseq_nphy(pi, NPHY_RFSEQ_RX2TX, + rfseq_rx2tx_events_rev3_ipa, + rfseq_rx2tx_dlys_rev3_ipa, + ARRAY_SIZE(rfseq_rx2tx_events_rev3_ipa)); + + if ((pi->sh->hw_phyrxchain != 0x3) && + (pi->sh->hw_phyrxchain != pi->sh->hw_phytxchain)) { + + if (PHY_IPA(pi)) { + rfseq_rx2tx_dlys_rev3[5] = 59; + rfseq_rx2tx_dlys_rev3[6] = 1; + rfseq_rx2tx_events_rev3[7] = + NPHY_REV3_RFSEQ_CMD_END; + } + + wlc_phy_set_rfseq_nphy( + pi, NPHY_RFSEQ_RX2TX, + rfseq_rx2tx_events_rev3, + rfseq_rx2tx_dlys_rev3, + ARRAY_SIZE(rfseq_rx2tx_events_rev3)); + } + + if (CHSPEC_IS2G(pi->radio_chanspec)) + write_phy_reg(pi, 0x6a, 0x2); + else + write_phy_reg(pi, 0x6a, 0x9c40); + + mod_phy_reg(pi, 0x294, (0xf << 8), (7 << 8)); + + if (CHSPEC_IS40(pi->radio_chanspec) == 0) { + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_NOISEVAR, 1, 3, + 32, &min_nvar_val); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_NOISEVAR, 1, + 127, 32, &min_nvar_val); + } else { + min_nvar_val = noise_var_tbl_rev3[3]; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_NOISEVAR, 1, 3, + 32, &min_nvar_val); + + min_nvar_val = noise_var_tbl_rev3[127]; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_NOISEVAR, 1, + 127, 32, &min_nvar_val); + } + + wlc_phy_workarounds_nphy_gainctrl(pi); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x00, 16, + &dac_control); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x10, 16, + &dac_control); + + pdetrange = + (CHSPEC_IS5G(pi->radio_chanspec)) ? pi->srom_fem5g. + pdetrange : pi->srom_fem2g.pdetrange; + + if (pdetrange == 0) { + if (NREV_GE(pi->pubpi.phy_rev, 4)) { + aux_adc_vmid = aux_adc_vmid_rev4; + aux_adc_gain = aux_adc_gain_rev4; + } else { + aux_adc_vmid = aux_adc_vmid_rev3; + aux_adc_gain = aux_adc_gain_rev3; + } + chan_freq_range = + wlc_phy_get_chan_freq_range_nphy(pi, 0); + if (chan_freq_range != WL_CHAN_FREQ_RANGE_2G) { + switch (chan_freq_range) { + case WL_CHAN_FREQ_RANGE_5GL: + aux_adc_vmid[3] = 0x89; + aux_adc_gain[3] = 0; + break; + case WL_CHAN_FREQ_RANGE_5GM: + aux_adc_vmid[3] = 0x89; + aux_adc_gain[3] = 0; + break; + case WL_CHAN_FREQ_RANGE_5GH: + aux_adc_vmid[3] = 0x89; + aux_adc_gain[3] = 0; + break; + default: + break; + } + } + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, + 0x08, 16, aux_adc_vmid); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, + 0x18, 16, aux_adc_vmid); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, + 0x0c, 16, aux_adc_gain); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, + 0x1c, 16, aux_adc_gain); + } else if (pdetrange == 1) { + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, + 0x08, 16, sk_adc_vmid); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, + 0x18, 16, sk_adc_vmid); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, + 0x0c, 16, sk_adc_gain); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, + 0x1c, 16, sk_adc_gain); + } else if (pdetrange == 2) { + + u16 bcm_adc_vmid[] = { 0xa2, 0xb4, 0xb4, 0x74 }; + u16 bcm_adc_gain[] = { 0x02, 0x02, 0x02, 0x04 }; + + if (NREV_GE(pi->pubpi.phy_rev, 6)) { + chan_freq_range = + wlc_phy_get_chan_freq_range_nphy(pi, 0); + if (chan_freq_range != WL_CHAN_FREQ_RANGE_2G) { + bcm_adc_vmid[3] = 0x8e; + bcm_adc_gain[3] = 0x03; + } else { + bcm_adc_vmid[3] = 0x94; + bcm_adc_gain[3] = 0x03; + } + } else if (NREV_IS(pi->pubpi.phy_rev, 5)) { + bcm_adc_vmid[3] = 0x84; + bcm_adc_gain[3] = 0x02; + } + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, + 0x08, 16, bcm_adc_vmid); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, + 0x18, 16, bcm_adc_vmid); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, + 0x0c, 16, bcm_adc_gain); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, + 0x1c, 16, bcm_adc_gain); + } else if (pdetrange == 3) { + chan_freq_range = + wlc_phy_get_chan_freq_range_nphy(pi, 0); + if ((NREV_GE(pi->pubpi.phy_rev, 4)) + && (chan_freq_range == WL_CHAN_FREQ_RANGE_2G)) { + + u16 auxadc_vmid[] = { + 0xa2, 0xb4, 0xb4, 0x270 + }; + u16 auxadc_gain[] = { + 0x02, 0x02, 0x02, 0x00 + }; + + wlc_phy_table_write_nphy(pi, + NPHY_TBL_ID_AFECTRL, 4, + 0x08, 16, auxadc_vmid); + wlc_phy_table_write_nphy(pi, + NPHY_TBL_ID_AFECTRL, 4, + 0x18, 16, auxadc_vmid); + wlc_phy_table_write_nphy(pi, + NPHY_TBL_ID_AFECTRL, 4, + 0x0c, 16, auxadc_gain); + wlc_phy_table_write_nphy(pi, + NPHY_TBL_ID_AFECTRL, 4, + 0x1c, 16, auxadc_gain); + } + } else if ((pdetrange == 4) || (pdetrange == 5)) { + u16 bcm_adc_vmid[] = { 0xa2, 0xb4, 0xb4, 0x0 }; + u16 bcm_adc_gain[] = { 0x02, 0x02, 0x02, 0x0 }; + u16 Vmid[2], Av[2]; + + chan_freq_range = + wlc_phy_get_chan_freq_range_nphy(pi, 0); + if (chan_freq_range != WL_CHAN_FREQ_RANGE_2G) { + Vmid[0] = (pdetrange == 4) ? 0x8e : 0x89; + Vmid[1] = (pdetrange == 4) ? 0x96 : 0x89; + Av[0] = (pdetrange == 4) ? 2 : 0; + Av[1] = (pdetrange == 4) ? 2 : 0; + } else { + Vmid[0] = (pdetrange == 4) ? 0x89 : 0x74; + Vmid[1] = (pdetrange == 4) ? 0x8b : 0x70; + Av[0] = (pdetrange == 4) ? 2 : 0; + Av[1] = (pdetrange == 4) ? 2 : 0; + } + + bcm_adc_vmid[3] = Vmid[0]; + bcm_adc_gain[3] = Av[0]; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, + 0x08, 16, bcm_adc_vmid); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, + 0x0c, 16, bcm_adc_gain); + + bcm_adc_vmid[3] = Vmid[1]; + bcm_adc_gain[3] = Av[1]; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, + 0x18, 16, bcm_adc_vmid); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 4, + 0x1c, 16, bcm_adc_gain); + } + + write_radio_reg(pi, + (RADIO_2056_RX_MIXA_MAST_BIAS | RADIO_2056_RX0), + 0x0); + write_radio_reg(pi, + (RADIO_2056_RX_MIXA_MAST_BIAS | RADIO_2056_RX1), + 0x0); + + write_radio_reg(pi, + (RADIO_2056_RX_MIXA_BIAS_MAIN | RADIO_2056_RX0), + 0x6); + write_radio_reg(pi, + (RADIO_2056_RX_MIXA_BIAS_MAIN | RADIO_2056_RX1), + 0x6); + + write_radio_reg(pi, + (RADIO_2056_RX_MIXA_BIAS_AUX | RADIO_2056_RX0), + 0x7); + write_radio_reg(pi, + (RADIO_2056_RX_MIXA_BIAS_AUX | RADIO_2056_RX1), + 0x7); + + write_radio_reg(pi, + (RADIO_2056_RX_MIXA_LOB_BIAS | RADIO_2056_RX0), + 0x88); + write_radio_reg(pi, + (RADIO_2056_RX_MIXA_LOB_BIAS | RADIO_2056_RX1), + 0x88); + + write_radio_reg(pi, + (RADIO_2056_RX_MIXA_CMFB_IDAC | RADIO_2056_RX0), + 0x0); + write_radio_reg(pi, + (RADIO_2056_RX_MIXA_CMFB_IDAC | RADIO_2056_RX1), + 0x0); + + write_radio_reg(pi, + (RADIO_2056_RX_MIXG_CMFB_IDAC | RADIO_2056_RX0), + 0x0); + write_radio_reg(pi, + (RADIO_2056_RX_MIXG_CMFB_IDAC | RADIO_2056_RX1), + 0x0); + + triso = + (CHSPEC_IS5G(pi->radio_chanspec)) ? pi->srom_fem5g. + triso : pi->srom_fem2g.triso; + if (triso == 7) { + wlc_phy_war_force_trsw_to_R_cliplo_nphy(pi, PHY_CORE_0); + wlc_phy_war_force_trsw_to_R_cliplo_nphy(pi, PHY_CORE_1); + } + + wlc_phy_war_txchain_upd_nphy(pi, pi->sh->hw_phytxchain); + + if (((pi->sh->boardflags2 & BFL2_APLL_WAR) && + (CHSPEC_IS5G(pi->radio_chanspec))) || + (((pi->sh->boardflags2 & BFL2_GPLL_WAR) || + (pi->sh->boardflags2 & BFL2_GPLL_WAR2)) && + (CHSPEC_IS2G(pi->radio_chanspec)))) { + nss1_data_weights = 0x00088888; + ht_data_weights = 0x00088888; + stbc_data_weights = 0x00088888; + } else { + nss1_data_weights = 0x88888888; + ht_data_weights = 0x88888888; + stbc_data_weights = 0x88888888; + } + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_CMPMETRICDATAWEIGHTTBL, + 1, 1, 32, &nss1_data_weights); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_CMPMETRICDATAWEIGHTTBL, + 1, 2, 32, &ht_data_weights); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_CMPMETRICDATAWEIGHTTBL, + 1, 3, 32, &stbc_data_weights); + + if (NREV_IS(pi->pubpi.phy_rev, 4)) { + if (CHSPEC_IS5G(pi->radio_chanspec)) { + write_radio_reg(pi, + RADIO_2056_TX_GMBB_IDAC | + RADIO_2056_TX0, 0x70); + write_radio_reg(pi, + RADIO_2056_TX_GMBB_IDAC | + RADIO_2056_TX1, 0x70); + } + } + + if (!pi->edcrs_threshold_lock) { + write_phy_reg(pi, 0x224, 0x3eb); + write_phy_reg(pi, 0x225, 0x3eb); + write_phy_reg(pi, 0x226, 0x341); + write_phy_reg(pi, 0x227, 0x341); + write_phy_reg(pi, 0x228, 0x42b); + write_phy_reg(pi, 0x229, 0x42b); + write_phy_reg(pi, 0x22a, 0x381); + write_phy_reg(pi, 0x22b, 0x381); + write_phy_reg(pi, 0x22c, 0x42b); + write_phy_reg(pi, 0x22d, 0x42b); + write_phy_reg(pi, 0x22e, 0x381); + write_phy_reg(pi, 0x22f, 0x381); + } + + if (NREV_GE(pi->pubpi.phy_rev, 6)) { + + if (pi->sh->boardflags2 & BFL2_SINGLEANT_CCK) + wlapi_bmac_mhf(pi->sh->physhim, MHF4, + MHF4_BPHY_TXCORE0, + MHF4_BPHY_TXCORE0, BRCM_BAND_ALL); + } + } else { + + if (pi->sh->boardflags2 & BFL2_SKWRKFEM_BRD || + (pi->sh->boardtype == 0x8b)) { + uint i; + u8 war_dlys[] = { 1, 6, 6, 2, 4, 20, 1 }; + for (i = 0; i < ARRAY_SIZE(rfseq_rx2tx_dlys); i++) + rfseq_rx2tx_dlys[i] = war_dlys[i]; + } + + if (CHSPEC_IS5G(pi->radio_chanspec) && pi->phy_5g_pwrgain) { + and_radio_reg(pi, RADIO_2055_CORE1_TX_RF_SPARE, 0xf7); + and_radio_reg(pi, RADIO_2055_CORE2_TX_RF_SPARE, 0xf7); + } else { + or_radio_reg(pi, RADIO_2055_CORE1_TX_RF_SPARE, 0x8); + or_radio_reg(pi, RADIO_2055_CORE2_TX_RF_SPARE, 0x8); + } + + regval = 0x000a; + wlc_phy_table_write_nphy(pi, 8, 1, 0, 16, ®val); + wlc_phy_table_write_nphy(pi, 8, 1, 0x10, 16, ®val); + + if (NREV_LT(pi->pubpi.phy_rev, 3)) { + regval = 0xcdaa; + wlc_phy_table_write_nphy(pi, 8, 1, 0x02, 16, ®val); + wlc_phy_table_write_nphy(pi, 8, 1, 0x12, 16, ®val); + } + + if (NREV_LT(pi->pubpi.phy_rev, 2)) { + regval = 0x0000; + wlc_phy_table_write_nphy(pi, 8, 1, 0x08, 16, ®val); + wlc_phy_table_write_nphy(pi, 8, 1, 0x18, 16, ®val); + + regval = 0x7aab; + wlc_phy_table_write_nphy(pi, 8, 1, 0x07, 16, ®val); + wlc_phy_table_write_nphy(pi, 8, 1, 0x17, 16, ®val); + + regval = 0x0800; + wlc_phy_table_write_nphy(pi, 8, 1, 0x06, 16, ®val); + wlc_phy_table_write_nphy(pi, 8, 1, 0x16, 16, ®val); + } + + write_phy_reg(pi, 0xf8, 0x02d8); + write_phy_reg(pi, 0xf9, 0x0301); + write_phy_reg(pi, 0xfa, 0x02d8); + write_phy_reg(pi, 0xfb, 0x0301); + + wlc_phy_set_rfseq_nphy(pi, NPHY_RFSEQ_RX2TX, rfseq_rx2tx_events, + rfseq_rx2tx_dlys, + ARRAY_SIZE(rfseq_rx2tx_events)); + + wlc_phy_set_rfseq_nphy(pi, NPHY_RFSEQ_TX2RX, rfseq_tx2rx_events, + rfseq_tx2rx_dlys, + ARRAY_SIZE(rfseq_tx2rx_events)); + + wlc_phy_workarounds_nphy_gainctrl(pi); + + if (NREV_LT(pi->pubpi.phy_rev, 2)) { + + if (read_phy_reg(pi, 0xa0) & NPHY_MLenable) + wlapi_bmac_mhf(pi->sh->physhim, MHF3, + MHF3_NPHY_MLADV_WAR, + MHF3_NPHY_MLADV_WAR, + BRCM_BAND_ALL); + + } else if (NREV_IS(pi->pubpi.phy_rev, 2)) { + write_phy_reg(pi, 0x1e3, 0x0); + write_phy_reg(pi, 0x1e4, 0x0); + } + + if (NREV_LT(pi->pubpi.phy_rev, 2)) + mod_phy_reg(pi, 0x90, (0x1 << 7), 0); + + alpha0 = 293; + alpha1 = 435; + alpha2 = 261; + beta0 = 366; + beta1 = 205; + beta2 = 32; + write_phy_reg(pi, 0x145, alpha0); + write_phy_reg(pi, 0x146, alpha1); + write_phy_reg(pi, 0x147, alpha2); + write_phy_reg(pi, 0x148, beta0); + write_phy_reg(pi, 0x149, beta1); + write_phy_reg(pi, 0x14a, beta2); + + if (NREV_LT(pi->pubpi.phy_rev, 3)) { + mod_phy_reg(pi, 0x142, (0xf << 12), 0); + + write_phy_reg(pi, 0x192, 0xb5); + write_phy_reg(pi, 0x193, 0xa4); + write_phy_reg(pi, 0x194, 0x0); + } + + if (NREV_IS(pi->pubpi.phy_rev, 2)) + mod_phy_reg(pi, 0x221, + NPHY_FORCESIG_DECODEGATEDCLKS, + NPHY_FORCESIG_DECODEGATEDCLKS); + } + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, false); +} + +static void wlc_phy_extpa_set_tx_digi_filts_nphy(struct brcms_phy *pi) +{ + int j, type = 2; + u16 addr_offset = 0x2c5; + + for (j = 0; j < NPHY_NUM_DIG_FILT_COEFFS; j++) + write_phy_reg(pi, addr_offset + j, + NPHY_IPA_REV4_txdigi_filtcoeffs[type][j]); +} + +static void wlc_phy_clip_det_nphy(struct brcms_phy *pi, u8 write, u16 *vals) +{ + + if (write == 0) { + vals[0] = read_phy_reg(pi, 0x2c); + vals[1] = read_phy_reg(pi, 0x42); + } else { + write_phy_reg(pi, 0x2c, vals[0]); + write_phy_reg(pi, 0x42, vals[1]); + } +} + +static void wlc_phy_ipa_internal_tssi_setup_nphy(struct brcms_phy *pi) +{ + u8 core; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + if (CHSPEC_IS2G(pi->radio_chanspec)) { + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TX_SSI_MASTER, 0x5); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TX_SSI_MUX, 0xe); + + if (pi->pubpi.radiorev != 5) + WRITE_RADIO_REG3(pi, RADIO_2057, TX, + core, TSSIA, 0); + + if (!NREV_IS(pi->pubpi.phy_rev, 7)) + WRITE_RADIO_REG3(pi, RADIO_2057, TX, + core, TSSIG, 0x1); + else + WRITE_RADIO_REG3(pi, RADIO_2057, TX, + core, TSSIG, 0x31); + } else { + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TX_SSI_MASTER, 0x9); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TX_SSI_MUX, 0xc); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TSSIG, 0); + + if (pi->pubpi.radiorev != 5) { + if (!NREV_IS(pi->pubpi.phy_rev, 7)) + WRITE_RADIO_REG3(pi, RADIO_2057, + TX, core, + TSSIA, 0x1); + else + WRITE_RADIO_REG3(pi, RADIO_2057, + TX, core, + TSSIA, 0x31); + } + } + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, IQCAL_VCM_HG, + 0); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, IQCAL_IDAC, + 0); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, TSSI_VCM, + 0x3); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, TSSI_MISC1, + 0x0); + } + } else { + WRITE_RADIO_SYN(pi, RADIO_2056, RESERVED_ADDR31, + (CHSPEC_IS2G(pi->radio_chanspec)) ? 0x128 : + 0x80); + WRITE_RADIO_SYN(pi, RADIO_2056, RESERVED_ADDR30, 0x0); + WRITE_RADIO_SYN(pi, RADIO_2056, GPIO_MASTER1, 0x29); + + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, IQCAL_VCM_HG, + 0x0); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, IQCAL_IDAC, + 0x0); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, TSSI_VCM, + 0x3); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, TX_AMP_DET, + 0x0); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, TSSI_MISC1, + 0x8); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, TSSI_MISC2, + 0x0); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, TSSI_MISC3, + 0x0); + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + TX_SSI_MASTER, 0x5); + + if (pi->pubpi.radiorev != 5) + WRITE_RADIO_REG2(pi, RADIO_2056, TX, + core, TSSIA, 0x0); + if (NREV_GE(pi->pubpi.phy_rev, 5)) + WRITE_RADIO_REG2(pi, RADIO_2056, TX, + core, TSSIG, 0x31); + else + WRITE_RADIO_REG2(pi, RADIO_2056, TX, + core, TSSIG, 0x11); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + TX_SSI_MUX, 0xe); + } else { + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + TX_SSI_MASTER, 0x9); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + TSSIA, 0x31); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + TSSIG, 0x0); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + TX_SSI_MUX, 0xc); + } + } + } +} + +static void +wlc_phy_rfctrl_override_nphy(struct brcms_phy *pi, u16 field, u16 value, + u8 core_mask, u8 off) +{ + u8 core_num; + u16 addr = 0, mask = 0, en_addr = 0, val_addr = 0, en_mask = + 0, val_mask = 0; + u8 shift = 0, val_shift = 0; + + if (NREV_GE(pi->pubpi.phy_rev, 3) && NREV_LT(pi->pubpi.phy_rev, 7)) { + + en_mask = field; + for (core_num = 0; core_num < 2; core_num++) { + + switch (field) { + case (0x1 << 1): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7a : 0x7d; + val_mask = (0x1 << 0); + val_shift = 0; + break; + case (0x1 << 2): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7a : 0x7d; + val_mask = (0x1 << 1); + val_shift = 1; + break; + case (0x1 << 3): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7a : 0x7d; + val_mask = (0x1 << 2); + val_shift = 2; + break; + case (0x1 << 4): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7a : 0x7d; + val_mask = (0x1 << 4); + val_shift = 4; + break; + case (0x1 << 5): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7a : 0x7d; + val_mask = (0x1 << 5); + val_shift = 5; + break; + case (0x1 << 6): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7a : 0x7d; + val_mask = (0x1 << 6); + val_shift = 6; + break; + case (0x1 << 7): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7a : 0x7d; + val_mask = (0x1 << 7); + val_shift = 7; + break; + case (0x1 << 8): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7a : 0x7d; + val_mask = (0x7 << 8); + val_shift = 8; + break; + case (0x1 << 11): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7a : 0x7d; + val_mask = (0x7 << 13); + val_shift = 13; + break; + + case (0x1 << 9): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0xf8 : 0xfa; + val_mask = (0x7 << 0); + val_shift = 0; + break; + + case (0x1 << 10): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0xf8 : 0xfa; + val_mask = (0x7 << 4); + val_shift = 4; + break; + + case (0x1 << 12): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7b : 0x7e; + val_mask = (0xffff << 0); + val_shift = 0; + break; + case (0x1 << 13): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0x7c : 0x7f; + val_mask = (0xffff << 0); + val_shift = 0; + break; + case (0x1 << 14): + en_addr = (core_num == 0) ? 0xe7 : 0xec; + val_addr = (core_num == 0) ? 0xf9 : 0xfb; + val_mask = (0x3 << 6); + val_shift = 6; + break; + case (0x1 << 0): + en_addr = (core_num == 0) ? 0xe5 : 0xe6; + val_addr = (core_num == 0) ? 0xf9 : 0xfb; + val_mask = (0x1 << 15); + val_shift = 15; + break; + default: + addr = 0xffff; + break; + } + + if (off) { + and_phy_reg(pi, en_addr, ~en_mask); + and_phy_reg(pi, val_addr, ~val_mask); + } else { + + if ((core_mask == 0) + || (core_mask & (1 << core_num))) { + or_phy_reg(pi, en_addr, en_mask); + + if (addr != 0xffff) + mod_phy_reg(pi, val_addr, + val_mask, + (value << + val_shift)); + } + } + } + } else { + + if (off) { + and_phy_reg(pi, 0xec, ~field); + value = 0x0; + } else { + or_phy_reg(pi, 0xec, field); + } + + for (core_num = 0; core_num < 2; core_num++) { + + switch (field) { + case (0x1 << 1): + case (0x1 << 9): + case (0x1 << 12): + case (0x1 << 13): + case (0x1 << 14): + addr = 0x78; + + core_mask = 0x1; + break; + case (0x1 << 2): + case (0x1 << 3): + case (0x1 << 4): + case (0x1 << 5): + case (0x1 << 6): + case (0x1 << 7): + case (0x1 << 8): + addr = (core_num == 0) ? 0x7a : 0x7d; + break; + case (0x1 << 10): + addr = (core_num == 0) ? 0x7b : 0x7e; + break; + case (0x1 << 11): + addr = (core_num == 0) ? 0x7c : 0x7f; + break; + default: + addr = 0xffff; + } + + switch (field) { + case (0x1 << 1): + mask = (0x7 << 3); + shift = 3; + break; + case (0x1 << 9): + mask = (0x1 << 2); + shift = 2; + break; + case (0x1 << 12): + mask = (0x1 << 8); + shift = 8; + break; + case (0x1 << 13): + mask = (0x1 << 9); + shift = 9; + break; + case (0x1 << 14): + mask = (0xf << 12); + shift = 12; + break; + case (0x1 << 2): + mask = (0x1 << 0); + shift = 0; + break; + case (0x1 << 3): + mask = (0x1 << 1); + shift = 1; + break; + case (0x1 << 4): + mask = (0x1 << 2); + shift = 2; + break; + case (0x1 << 5): + mask = (0x3 << 4); + shift = 4; + break; + case (0x1 << 6): + mask = (0x3 << 6); + shift = 6; + break; + case (0x1 << 7): + mask = (0x1 << 8); + shift = 8; + break; + case (0x1 << 8): + mask = (0x1 << 9); + shift = 9; + break; + case (0x1 << 10): + mask = 0x1fff; + shift = 0x0; + break; + case (0x1 << 11): + mask = 0x1fff; + shift = 0x0; + break; + default: + mask = 0x0; + shift = 0x0; + break; + } + + if ((addr != 0xffff) && (core_mask & (1 << core_num))) + mod_phy_reg(pi, addr, mask, (value << shift)); + } + + or_phy_reg(pi, 0xec, (0x1 << 0)); + or_phy_reg(pi, 0x78, (0x1 << 0)); + udelay(1); + and_phy_reg(pi, 0xec, ~(0x1 << 0)); + } +} + +static void wlc_phy_txpwrctrl_idle_tssi_nphy(struct brcms_phy *pi) +{ + s32 rssi_buf[4]; + s32 int_val; + + if (SCAN_RM_IN_PROGRESS(pi) || PLT_INPROG_PHY(pi) || PHY_MUTED(pi)) + + return; + + if (PHY_IPA(pi)) + wlc_phy_ipa_internal_tssi_setup_nphy(pi); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 12), + 0, 0x3, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + else if (NREV_GE(pi->pubpi.phy_rev, 3)) + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 13), 0, 3, 0); + + wlc_phy_stopplayback_nphy(pi); + + wlc_phy_tx_tone_nphy(pi, 4000, 0, 0, 0, false); + + udelay(20); + int_val = + wlc_phy_poll_rssi_nphy(pi, (u8) NPHY_RSSI_SEL_TSSI_2G, rssi_buf, + 1); + wlc_phy_stopplayback_nphy(pi); + wlc_phy_rssisel_nphy(pi, RADIO_MIMO_CORESEL_OFF, 0); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 12), + 0, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + else if (NREV_GE(pi->pubpi.phy_rev, 3)) + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 13), 0, 3, 1); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + + pi->nphy_pwrctrl_info[PHY_CORE_0].idle_tssi_2g = + (u8) ((int_val >> 24) & 0xff); + pi->nphy_pwrctrl_info[PHY_CORE_0].idle_tssi_5g = + (u8) ((int_val >> 24) & 0xff); + + pi->nphy_pwrctrl_info[PHY_CORE_1].idle_tssi_2g = + (u8) ((int_val >> 8) & 0xff); + pi->nphy_pwrctrl_info[PHY_CORE_1].idle_tssi_5g = + (u8) ((int_val >> 8) & 0xff); + } else { + pi->nphy_pwrctrl_info[PHY_CORE_0].idle_tssi_2g = + (u8) ((int_val >> 24) & 0xff); + + pi->nphy_pwrctrl_info[PHY_CORE_1].idle_tssi_2g = + (u8) ((int_val >> 8) & 0xff); + + pi->nphy_pwrctrl_info[PHY_CORE_0].idle_tssi_5g = + (u8) ((int_val >> 16) & 0xff); + pi->nphy_pwrctrl_info[PHY_CORE_1].idle_tssi_5g = + (u8) ((int_val) & 0xff); + } + +} + +static void wlc_phy_txpwr_limit_to_tbl_nphy(struct brcms_phy *pi) +{ + u8 idx, idx2, i, delta_ind; + + for (idx = TXP_FIRST_CCK; idx <= TXP_LAST_CCK; idx++) + pi->adj_pwr_tbl_nphy[idx] = pi->tx_power_offset[idx]; + + for (i = 0; i < 4; i++) { + idx2 = 0; + + delta_ind = 0; + + switch (i) { + case 0: + + if (CHSPEC_IS40(pi->radio_chanspec) + && NPHY_IS_SROM_REINTERPRET) { + idx = TXP_FIRST_MCS_40_SISO; + } else { + idx = (CHSPEC_IS40(pi->radio_chanspec)) ? + TXP_FIRST_OFDM_40_SISO : TXP_FIRST_OFDM; + delta_ind = 1; + } + break; + + case 1: + + idx = (CHSPEC_IS40(pi->radio_chanspec)) ? + TXP_FIRST_MCS_40_CDD : TXP_FIRST_MCS_20_CDD; + break; + + case 2: + + idx = (CHSPEC_IS40(pi->radio_chanspec)) ? + TXP_FIRST_MCS_40_STBC : TXP_FIRST_MCS_20_STBC; + break; + + case 3: + + idx = (CHSPEC_IS40(pi->radio_chanspec)) ? + TXP_FIRST_MCS_40_SDM : TXP_FIRST_MCS_20_SDM; + break; + } + + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx]; + idx = idx + delta_ind; + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx]; + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx]; + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx++]; + + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx++]; + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx]; + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx]; + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx++]; + + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx++]; + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx]; + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx]; + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx++]; + + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx]; + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx++]; + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx]; + idx = idx + 1 - delta_ind; + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx]; + + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx]; + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx]; + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx]; + pi->adj_pwr_tbl_nphy[4 + 4 * (idx2++) + i] = + pi->tx_power_offset[idx]; + } +} + +static void wlc_phy_txpwrctrl_pwr_setup_nphy(struct brcms_phy *pi) +{ + u32 idx; + s16 a1[2], b0[2], b1[2]; + s8 target_pwr_qtrdbm[2]; + s32 num, den, pwr_est; + u8 chan_freq_range; + u8 idle_tssi[2]; + u32 tbl_id, tbl_len, tbl_offset; + u32 regval[64]; + u8 core; + + if (D11REV_IS(pi->sh->corerev, 11) || D11REV_IS(pi->sh->corerev, 12)) { + wlapi_bmac_mctrl(pi->sh->physhim, MCTL_PHYLOCK, MCTL_PHYLOCK); + (void)bcma_read32(pi->d11core, D11REGOFFS(maccontrol)); + udelay(1); + } + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + or_phy_reg(pi, 0x122, (0x1 << 0)); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) + and_phy_reg(pi, 0x1e7, (u16) (~(0x1 << 15))); + else + or_phy_reg(pi, 0x1e7, (0x1 << 15)); + + if (D11REV_IS(pi->sh->corerev, 11) || D11REV_IS(pi->sh->corerev, 12)) + wlapi_bmac_mctrl(pi->sh->physhim, MCTL_PHYLOCK, 0); + + if (pi->sh->sromrev < 4) { + idle_tssi[0] = pi->nphy_pwrctrl_info[0].idle_tssi_2g; + idle_tssi[1] = pi->nphy_pwrctrl_info[1].idle_tssi_2g; + a1[0] = -424; + a1[1] = -424; + b0[0] = 5612; + b0[1] = 5612; + b1[1] = -1393; + b1[0] = -1393; + } else { + + chan_freq_range = wlc_phy_get_chan_freq_range_nphy(pi, 0); + switch (chan_freq_range) { + case WL_CHAN_FREQ_RANGE_2G: + idle_tssi[0] = pi->nphy_pwrctrl_info[0].idle_tssi_2g; + idle_tssi[1] = pi->nphy_pwrctrl_info[1].idle_tssi_2g; + a1[0] = pi->nphy_pwrctrl_info[0].pwrdet_2g_a1; + a1[1] = pi->nphy_pwrctrl_info[1].pwrdet_2g_a1; + b0[0] = pi->nphy_pwrctrl_info[0].pwrdet_2g_b0; + b0[1] = pi->nphy_pwrctrl_info[1].pwrdet_2g_b0; + b1[0] = pi->nphy_pwrctrl_info[0].pwrdet_2g_b1; + b1[1] = pi->nphy_pwrctrl_info[1].pwrdet_2g_b1; + break; + case WL_CHAN_FREQ_RANGE_5GL: + idle_tssi[0] = pi->nphy_pwrctrl_info[0].idle_tssi_5g; + idle_tssi[1] = pi->nphy_pwrctrl_info[1].idle_tssi_5g; + a1[0] = pi->nphy_pwrctrl_info[0].pwrdet_5gl_a1; + a1[1] = pi->nphy_pwrctrl_info[1].pwrdet_5gl_a1; + b0[0] = pi->nphy_pwrctrl_info[0].pwrdet_5gl_b0; + b0[1] = pi->nphy_pwrctrl_info[1].pwrdet_5gl_b0; + b1[0] = pi->nphy_pwrctrl_info[0].pwrdet_5gl_b1; + b1[1] = pi->nphy_pwrctrl_info[1].pwrdet_5gl_b1; + break; + case WL_CHAN_FREQ_RANGE_5GM: + idle_tssi[0] = pi->nphy_pwrctrl_info[0].idle_tssi_5g; + idle_tssi[1] = pi->nphy_pwrctrl_info[1].idle_tssi_5g; + a1[0] = pi->nphy_pwrctrl_info[0].pwrdet_5gm_a1; + a1[1] = pi->nphy_pwrctrl_info[1].pwrdet_5gm_a1; + b0[0] = pi->nphy_pwrctrl_info[0].pwrdet_5gm_b0; + b0[1] = pi->nphy_pwrctrl_info[1].pwrdet_5gm_b0; + b1[0] = pi->nphy_pwrctrl_info[0].pwrdet_5gm_b1; + b1[1] = pi->nphy_pwrctrl_info[1].pwrdet_5gm_b1; + break; + case WL_CHAN_FREQ_RANGE_5GH: + idle_tssi[0] = pi->nphy_pwrctrl_info[0].idle_tssi_5g; + idle_tssi[1] = pi->nphy_pwrctrl_info[1].idle_tssi_5g; + a1[0] = pi->nphy_pwrctrl_info[0].pwrdet_5gh_a1; + a1[1] = pi->nphy_pwrctrl_info[1].pwrdet_5gh_a1; + b0[0] = pi->nphy_pwrctrl_info[0].pwrdet_5gh_b0; + b0[1] = pi->nphy_pwrctrl_info[1].pwrdet_5gh_b0; + b1[0] = pi->nphy_pwrctrl_info[0].pwrdet_5gh_b1; + b1[1] = pi->nphy_pwrctrl_info[1].pwrdet_5gh_b1; + break; + default: + idle_tssi[0] = pi->nphy_pwrctrl_info[0].idle_tssi_2g; + idle_tssi[1] = pi->nphy_pwrctrl_info[1].idle_tssi_2g; + a1[0] = -424; + a1[1] = -424; + b0[0] = 5612; + b0[1] = 5612; + b1[1] = -1393; + b1[0] = -1393; + break; + } + } + + /* use the provided transmit power */ + target_pwr_qtrdbm[0] = (s8) pi->tx_power_max; + target_pwr_qtrdbm[1] = (s8) pi->tx_power_max; + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + if (pi->srom_fem2g.tssipos) + or_phy_reg(pi, 0x1e9, (0x1 << 14)); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + for (core = 0; core <= 1; core++) { + if (PHY_IPA(pi)) { + if (CHSPEC_IS2G(pi->radio_chanspec)) + WRITE_RADIO_REG3(pi, RADIO_2057, + TX, core, + TX_SSI_MUX, + 0xe); + else + WRITE_RADIO_REG3(pi, RADIO_2057, + TX, core, + TX_SSI_MUX, + 0xc); + } + } + } else { + if (PHY_IPA(pi)) { + + write_radio_reg(pi, RADIO_2056_TX_TX_SSI_MUX | + RADIO_2056_TX0, + (CHSPEC_IS5G + (pi->radio_chanspec)) ? + 0xc : 0xe); + write_radio_reg(pi, + RADIO_2056_TX_TX_SSI_MUX | + RADIO_2056_TX1, + (CHSPEC_IS5G + (pi->radio_chanspec)) ? + 0xc : 0xe); + } else { + + write_radio_reg(pi, RADIO_2056_TX_TX_SSI_MUX | + RADIO_2056_TX0, 0x11); + write_radio_reg(pi, RADIO_2056_TX_TX_SSI_MUX | + RADIO_2056_TX1, 0x11); + } + } + } + + if (D11REV_IS(pi->sh->corerev, 11) || D11REV_IS(pi->sh->corerev, 12)) { + wlapi_bmac_mctrl(pi->sh->physhim, MCTL_PHYLOCK, MCTL_PHYLOCK); + (void)bcma_read32(pi->d11core, D11REGOFFS(maccontrol)); + udelay(1); + } + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + mod_phy_reg(pi, 0x1e7, (0x7f << 0), + (NPHY_TxPwrCtrlCmd_pwrIndex_init_rev7 << 0)); + else + mod_phy_reg(pi, 0x1e7, (0x7f << 0), + (NPHY_TxPwrCtrlCmd_pwrIndex_init << 0)); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + mod_phy_reg(pi, 0x222, (0xff << 0), + (NPHY_TxPwrCtrlCmd_pwrIndex_init_rev7 << 0)); + else if (NREV_GT(pi->pubpi.phy_rev, 1)) + mod_phy_reg(pi, 0x222, (0xff << 0), + (NPHY_TxPwrCtrlCmd_pwrIndex_init << 0)); + + if (D11REV_IS(pi->sh->corerev, 11) || D11REV_IS(pi->sh->corerev, 12)) + wlapi_bmac_mctrl(pi->sh->physhim, MCTL_PHYLOCK, 0); + + write_phy_reg(pi, 0x1e8, (0x3 << 8) | (240 << 0)); + + write_phy_reg(pi, 0x1e9, + (1 << 15) | (idle_tssi[0] << 0) | (idle_tssi[1] << 8)); + + write_phy_reg(pi, 0x1ea, + (target_pwr_qtrdbm[0] << 0) | + (target_pwr_qtrdbm[1] << 8)); + + tbl_len = 64; + tbl_offset = 0; + for (tbl_id = NPHY_TBL_ID_CORE1TXPWRCTL; + tbl_id <= NPHY_TBL_ID_CORE2TXPWRCTL; tbl_id++) { + + for (idx = 0; idx < tbl_len; idx++) { + num = 8 * + (16 * b0[tbl_id - 26] + b1[tbl_id - 26] * idx); + den = 32768 + a1[tbl_id - 26] * idx; + pwr_est = max(((4 * num + den / 2) / den), -8); + if (NREV_LT(pi->pubpi.phy_rev, 3)) { + if (idx <= + (uint) (31 - idle_tssi[tbl_id - 26] + 1)) + pwr_est = + max(pwr_est, + target_pwr_qtrdbm + [tbl_id - 26] + 1); + } + regval[idx] = (u32) pwr_est; + } + wlc_phy_table_write_nphy(pi, tbl_id, tbl_len, tbl_offset, 32, + regval); + } + + wlc_phy_txpwr_limit_to_tbl_nphy(pi); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_CORE1TXPWRCTL, 84, 64, 8, + pi->adj_pwr_tbl_nphy); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_CORE2TXPWRCTL, 84, 64, 8, + pi->adj_pwr_tbl_nphy); + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, false); +} + +static u32 *wlc_phy_get_ipa_gaintbl_nphy(struct brcms_phy *pi) +{ + u32 *tx_pwrctrl_tbl = NULL; + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + if ((pi->pubpi.radiorev == 4) + || (pi->pubpi.radiorev == 6)) + tx_pwrctrl_tbl = + nphy_tpc_txgain_ipa_2g_2057rev4n6; + else if (pi->pubpi.radiorev == 3) + tx_pwrctrl_tbl = + nphy_tpc_txgain_ipa_2g_2057rev3; + else if (pi->pubpi.radiorev == 5) + tx_pwrctrl_tbl = + nphy_tpc_txgain_ipa_2g_2057rev5; + else if ((pi->pubpi.radiorev == 7) + || (pi->pubpi.radiorev == 8)) + tx_pwrctrl_tbl = + nphy_tpc_txgain_ipa_2g_2057rev7; + } else if (NREV_IS(pi->pubpi.phy_rev, 6)) { + tx_pwrctrl_tbl = nphy_tpc_txgain_ipa_rev6; + if (pi->sh->chip == BCMA_CHIP_ID_BCM47162) + tx_pwrctrl_tbl = nphy_tpc_txgain_ipa_rev5; + } else if (NREV_IS(pi->pubpi.phy_rev, 5)) { + tx_pwrctrl_tbl = nphy_tpc_txgain_ipa_rev5; + } else { + tx_pwrctrl_tbl = nphy_tpc_txgain_ipa; + } + } else { + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + if ((pi->pubpi.radiorev == 3) || + (pi->pubpi.radiorev == 4) || + (pi->pubpi.radiorev == 6)) + tx_pwrctrl_tbl = nphy_tpc_txgain_ipa_5g_2057; + else if ((pi->pubpi.radiorev == 7) + || (pi->pubpi.radiorev == 8)) + tx_pwrctrl_tbl = + nphy_tpc_txgain_ipa_5g_2057rev7; + } else { + tx_pwrctrl_tbl = nphy_tpc_txgain_ipa_5g; + } + } + + return tx_pwrctrl_tbl; +} + +static void wlc_phy_restore_rssical_nphy(struct brcms_phy *pi) +{ + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if (pi->nphy_rssical_chanspec_2G == 0) + return; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + mod_radio_reg(pi, RADIO_2057_NB_MASTER_CORE0, + RADIO_2057_VCM_MASK, + pi->rssical_cache. + rssical_radio_regs_2G[0]); + mod_radio_reg(pi, RADIO_2057_NB_MASTER_CORE1, + RADIO_2057_VCM_MASK, + pi->rssical_cache. + rssical_radio_regs_2G[1]); + } else { + mod_radio_reg(pi, + RADIO_2056_RX_RSSI_MISC | RADIO_2056_RX0, + RADIO_2056_VCM_MASK, + pi->rssical_cache. + rssical_radio_regs_2G[0]); + mod_radio_reg(pi, + RADIO_2056_RX_RSSI_MISC | RADIO_2056_RX1, + RADIO_2056_VCM_MASK, + pi->rssical_cache. + rssical_radio_regs_2G[1]); + } + + write_phy_reg(pi, 0x1a6, + pi->rssical_cache.rssical_phyregs_2G[0]); + write_phy_reg(pi, 0x1ac, + pi->rssical_cache.rssical_phyregs_2G[1]); + write_phy_reg(pi, 0x1b2, + pi->rssical_cache.rssical_phyregs_2G[2]); + write_phy_reg(pi, 0x1b8, + pi->rssical_cache.rssical_phyregs_2G[3]); + write_phy_reg(pi, 0x1a4, + pi->rssical_cache.rssical_phyregs_2G[4]); + write_phy_reg(pi, 0x1aa, + pi->rssical_cache.rssical_phyregs_2G[5]); + write_phy_reg(pi, 0x1b0, + pi->rssical_cache.rssical_phyregs_2G[6]); + write_phy_reg(pi, 0x1b6, + pi->rssical_cache.rssical_phyregs_2G[7]); + write_phy_reg(pi, 0x1a5, + pi->rssical_cache.rssical_phyregs_2G[8]); + write_phy_reg(pi, 0x1ab, + pi->rssical_cache.rssical_phyregs_2G[9]); + write_phy_reg(pi, 0x1b1, + pi->rssical_cache.rssical_phyregs_2G[10]); + write_phy_reg(pi, 0x1b7, + pi->rssical_cache.rssical_phyregs_2G[11]); + + } else { + if (pi->nphy_rssical_chanspec_5G == 0) + return; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + mod_radio_reg(pi, RADIO_2057_NB_MASTER_CORE0, + RADIO_2057_VCM_MASK, + pi->rssical_cache. + rssical_radio_regs_5G[0]); + mod_radio_reg(pi, RADIO_2057_NB_MASTER_CORE1, + RADIO_2057_VCM_MASK, + pi->rssical_cache. + rssical_radio_regs_5G[1]); + } else { + mod_radio_reg(pi, + RADIO_2056_RX_RSSI_MISC | RADIO_2056_RX0, + RADIO_2056_VCM_MASK, + pi->rssical_cache. + rssical_radio_regs_5G[0]); + mod_radio_reg(pi, + RADIO_2056_RX_RSSI_MISC | RADIO_2056_RX1, + RADIO_2056_VCM_MASK, + pi->rssical_cache. + rssical_radio_regs_5G[1]); + } + + write_phy_reg(pi, 0x1a6, + pi->rssical_cache.rssical_phyregs_5G[0]); + write_phy_reg(pi, 0x1ac, + pi->rssical_cache.rssical_phyregs_5G[1]); + write_phy_reg(pi, 0x1b2, + pi->rssical_cache.rssical_phyregs_5G[2]); + write_phy_reg(pi, 0x1b8, + pi->rssical_cache.rssical_phyregs_5G[3]); + write_phy_reg(pi, 0x1a4, + pi->rssical_cache.rssical_phyregs_5G[4]); + write_phy_reg(pi, 0x1aa, + pi->rssical_cache.rssical_phyregs_5G[5]); + write_phy_reg(pi, 0x1b0, + pi->rssical_cache.rssical_phyregs_5G[6]); + write_phy_reg(pi, 0x1b6, + pi->rssical_cache.rssical_phyregs_5G[7]); + write_phy_reg(pi, 0x1a5, + pi->rssical_cache.rssical_phyregs_5G[8]); + write_phy_reg(pi, 0x1ab, + pi->rssical_cache.rssical_phyregs_5G[9]); + write_phy_reg(pi, 0x1b1, + pi->rssical_cache.rssical_phyregs_5G[10]); + write_phy_reg(pi, 0x1b7, + pi->rssical_cache.rssical_phyregs_5G[11]); + } +} + +static void wlc_phy_internal_cal_txgain_nphy(struct brcms_phy *pi) +{ + u16 txcal_gain[2]; + + pi->nphy_txcal_pwr_idx[0] = pi->nphy_cal_orig_pwr_idx[0]; + pi->nphy_txcal_pwr_idx[1] = pi->nphy_cal_orig_pwr_idx[0]; + wlc_phy_txpwr_index_nphy(pi, 1, pi->nphy_cal_orig_pwr_idx[0], true); + wlc_phy_txpwr_index_nphy(pi, 2, pi->nphy_cal_orig_pwr_idx[1], true); + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_RFSEQ, 2, 0x110, 16, + txcal_gain); + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + txcal_gain[0] = (txcal_gain[0] & 0xF000) | 0x0F40; + txcal_gain[1] = (txcal_gain[1] & 0xF000) | 0x0F40; + } else { + txcal_gain[0] = (txcal_gain[0] & 0xF000) | 0x0F60; + txcal_gain[1] = (txcal_gain[1] & 0xF000) | 0x0F60; + } + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, 2, 0x110, 16, + txcal_gain); +} + +static void wlc_phy_precal_txgain_nphy(struct brcms_phy *pi) +{ + bool save_bbmult = false; + u8 txcal_index_2057_rev5n7 = 0; + u8 txcal_index_2057_rev3n4n6 = 10; + + if (pi->use_int_tx_iqlo_cal_nphy) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + if ((pi->pubpi.radiorev == 3) || + (pi->pubpi.radiorev == 4) || + (pi->pubpi.radiorev == 6)) { + + pi->nphy_txcal_pwr_idx[0] = + txcal_index_2057_rev3n4n6; + pi->nphy_txcal_pwr_idx[1] = + txcal_index_2057_rev3n4n6; + wlc_phy_txpwr_index_nphy( + pi, 3, + txcal_index_2057_rev3n4n6, + false); + } else { + + pi->nphy_txcal_pwr_idx[0] = + txcal_index_2057_rev5n7; + pi->nphy_txcal_pwr_idx[1] = + txcal_index_2057_rev5n7; + wlc_phy_txpwr_index_nphy( + pi, 3, + txcal_index_2057_rev5n7, + false); + } + save_bbmult = true; + + } else if (NREV_LT(pi->pubpi.phy_rev, 5)) { + wlc_phy_cal_txgainctrl_nphy(pi, 11, false); + if (pi->sh->hw_phytxchain != 3) { + pi->nphy_txcal_pwr_idx[1] = + pi->nphy_txcal_pwr_idx[0]; + wlc_phy_txpwr_index_nphy(pi, 3, + pi-> + nphy_txcal_pwr_idx[0], + true); + save_bbmult = true; + } + + } else if (NREV_IS(pi->pubpi.phy_rev, 5)) { + if (PHY_IPA(pi)) { + if (CHSPEC_IS2G(pi->radio_chanspec)) { + wlc_phy_cal_txgainctrl_nphy(pi, 12, + false); + } else { + pi->nphy_txcal_pwr_idx[0] = 80; + pi->nphy_txcal_pwr_idx[1] = 80; + wlc_phy_txpwr_index_nphy(pi, 3, 80, + false); + save_bbmult = true; + } + } else { + wlc_phy_internal_cal_txgain_nphy(pi); + save_bbmult = true; + } + + } else if (NREV_IS(pi->pubpi.phy_rev, 6)) { + if (PHY_IPA(pi)) { + if (CHSPEC_IS2G(pi->radio_chanspec)) + wlc_phy_cal_txgainctrl_nphy(pi, 12, + false); + else + wlc_phy_cal_txgainctrl_nphy(pi, 14, + false); + } else { + wlc_phy_internal_cal_txgain_nphy(pi); + save_bbmult = true; + } + } + + } else { + wlc_phy_cal_txgainctrl_nphy(pi, 10, false); + } + + if (save_bbmult) + wlc_phy_table_read_nphy(pi, 15, 1, 87, 16, + &pi->nphy_txcal_bbmult); +} + +static void +wlc_phy_rfctrlintc_override_nphy(struct brcms_phy *pi, u8 field, u16 value, + u8 core_code) +{ + u16 mask; + u16 val; + u8 core; + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + if (core_code == RADIO_MIMO_CORESEL_CORE1 + && core == PHY_CORE_1) + continue; + else if (core_code == RADIO_MIMO_CORESEL_CORE2 + && core == PHY_CORE_0) + continue; + + if (NREV_LT(pi->pubpi.phy_rev, 7)) { + + mask = (0x1 << 10); + val = 1 << 10; + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x91 : + 0x92, mask, val); + } + + if (field == NPHY_RfctrlIntc_override_OFF) { + + write_phy_reg(pi, (core == PHY_CORE_0) ? 0x91 : + 0x92, 0); + + wlc_phy_force_rfseq_nphy(pi, + NPHY_RFSEQ_RESET2RX); + } else if (field == NPHY_RfctrlIntc_override_TRSW) { + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + mask = (0x1 << 6) | (0x1 << 7); + + val = value << 6; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 : 0x92, + mask, val); + + or_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 : 0x92, + (0x1 << 10)); + + and_phy_reg(pi, 0x2ff, (u16) + ~(0x3 << 14)); + or_phy_reg(pi, 0x2ff, (0x1 << 13)); + or_phy_reg(pi, 0x2ff, (0x1 << 0)); + } else { + + mask = (0x1 << 6) | + (0x1 << 7) | + (0x1 << 8) | (0x1 << 9); + val = value << 6; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 : 0x92, + mask, val); + + mask = (0x1 << 0); + val = 1 << 0; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0xe7 : 0xec, + mask, val); + + mask = (core == PHY_CORE_0) ? + (0x1 << 0) : (0x1 << 1); + val = 1 << ((core == PHY_CORE_0) ? + 0 : 1); + mod_phy_reg(pi, 0x78, mask, val); + + SPINWAIT(((read_phy_reg(pi, 0x78) & val) + != 0), 10000); + if (WARN(read_phy_reg(pi, 0x78) & val, + "HW error: override failed")) + return; + + mask = (0x1 << 0); + val = 0 << 0; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0xe7 : 0xec, + mask, val); + } + } else if (field == NPHY_RfctrlIntc_override_PA) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + mask = (0x1 << 4) | (0x1 << 5); + + if (CHSPEC_IS5G(pi->radio_chanspec)) + val = value << 5; + else + val = value << 4; + + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 : 0x92, + mask, val); + + or_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 : 0x92, + (0x1 << 12)); + } else { + + if (CHSPEC_IS5G(pi->radio_chanspec)) { + mask = (0x1 << 5); + val = value << 5; + } else { + mask = (0x1 << 4); + val = value << 4; + } + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 : 0x92, + mask, val); + } + } else if (field == + NPHY_RfctrlIntc_override_EXT_LNA_PU) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + if (CHSPEC_IS5G(pi->radio_chanspec)) { + + mask = (0x1 << 0); + val = value << 0; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 + : 0x92, mask, val); + + mask = (0x1 << 2); + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 + : 0x92, mask, 0); + } else { + + mask = (0x1 << 2); + val = value << 2; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 + : 0x92, mask, val); + + mask = (0x1 << 0); + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 + : 0x92, mask, 0); + } + + mask = (0x1 << 11); + val = 1 << 11; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 : 0x92, + mask, val); + } else { + + if (CHSPEC_IS5G(pi->radio_chanspec)) { + mask = (0x1 << 0); + val = value << 0; + } else { + mask = (0x1 << 2); + val = value << 2; + } + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 : 0x92, + mask, val); + } + } else if (field == + NPHY_RfctrlIntc_override_EXT_LNA_GAIN) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + if (CHSPEC_IS5G(pi->radio_chanspec)) { + + mask = (0x1 << 1); + val = value << 1; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 + : 0x92, mask, val); + + mask = (0x1 << 3); + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 + : 0x92, mask, 0); + } else { + + mask = (0x1 << 3); + val = value << 3; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 + : 0x92, mask, val); + + mask = (0x1 << 1); + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 + : 0x92, mask, 0); + } + + mask = (0x1 << 11); + val = 1 << 11; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 : 0x92, + mask, val); + } else { + + if (CHSPEC_IS5G(pi->radio_chanspec)) { + mask = (0x1 << 1); + val = value << 1; + } else { + mask = (0x1 << 3); + val = value << 3; + } + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x91 : 0x92, + mask, val); + } + } + } + } +} + +void +wlc_phy_cal_txgainctrl_nphy(struct brcms_phy *pi, s32 dBm_targetpower, + bool debug) +{ + int gainctrl_loopidx; + uint core; + u16 m0m1, curr_m0m1; + s32 delta_power; + s32 txpwrindex; + s32 qdBm_power[2]; + u16 orig_BBConfig; + u16 phy_saveregs[4]; + u32 freq_test; + u16 ampl_test = 250; + uint stepsize; + bool phyhang_avoid_state = false; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + stepsize = 2; + else + stepsize = 1; + + if (CHSPEC_IS40(pi->radio_chanspec)) + freq_test = 5000; + else + freq_test = 2500; + + wlc_phy_txpwr_index_nphy(pi, 1, pi->nphy_cal_orig_pwr_idx[0], true); + wlc_phy_txpwr_index_nphy(pi, 2, pi->nphy_cal_orig_pwr_idx[1], true); + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + phyhang_avoid_state = pi->phyhang_avoid; + pi->phyhang_avoid = false; + + phy_saveregs[0] = read_phy_reg(pi, 0x91); + phy_saveregs[1] = read_phy_reg(pi, 0x92); + phy_saveregs[2] = read_phy_reg(pi, 0xe7); + phy_saveregs[3] = read_phy_reg(pi, 0xec); + wlc_phy_rfctrlintc_override_nphy(pi, NPHY_RfctrlIntc_override_PA, 1, + RADIO_MIMO_CORESEL_CORE1 | + RADIO_MIMO_CORESEL_CORE2); + + if (!debug) { + wlc_phy_rfctrlintc_override_nphy(pi, + NPHY_RfctrlIntc_override_TRSW, + 0x2, RADIO_MIMO_CORESEL_CORE1); + wlc_phy_rfctrlintc_override_nphy(pi, + NPHY_RfctrlIntc_override_TRSW, + 0x8, RADIO_MIMO_CORESEL_CORE2); + } else { + wlc_phy_rfctrlintc_override_nphy(pi, + NPHY_RfctrlIntc_override_TRSW, + 0x1, RADIO_MIMO_CORESEL_CORE1); + wlc_phy_rfctrlintc_override_nphy(pi, + NPHY_RfctrlIntc_override_TRSW, + 0x7, RADIO_MIMO_CORESEL_CORE2); + } + + orig_BBConfig = read_phy_reg(pi, 0x01); + mod_phy_reg(pi, 0x01, (0x1 << 15), 0); + + wlc_phy_table_read_nphy(pi, 15, 1, 87, 16, &m0m1); + + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + txpwrindex = (s32) pi->nphy_cal_orig_pwr_idx[core]; + + for (gainctrl_loopidx = 0; gainctrl_loopidx < 2; + gainctrl_loopidx++) { + wlc_phy_tx_tone_nphy(pi, freq_test, ampl_test, 0, 0, + false); + + if (core == PHY_CORE_0) + curr_m0m1 = m0m1 & 0xff00; + else + curr_m0m1 = m0m1 & 0x00ff; + + wlc_phy_table_write_nphy(pi, 15, 1, 87, 16, &curr_m0m1); + wlc_phy_table_write_nphy(pi, 15, 1, 95, 16, &curr_m0m1); + + udelay(50); + + wlc_phy_est_tonepwr_nphy(pi, qdBm_power, + NPHY_CAL_TSSISAMPS); + + pi->nphy_bb_mult_save = 0; + wlc_phy_stopplayback_nphy(pi); + + delta_power = (dBm_targetpower * 4) - qdBm_power[core]; + + txpwrindex -= stepsize * delta_power; + if (txpwrindex < 0) + txpwrindex = 0; + else if (txpwrindex > 127) + txpwrindex = 127; + + if (CHSPEC_IS5G(pi->radio_chanspec)) { + if (NREV_IS(pi->pubpi.phy_rev, 4) && + (pi->srom_fem5g.extpagain == 3)) { + if (txpwrindex < 30) + txpwrindex = 30; + } + } else { + if (NREV_GE(pi->pubpi.phy_rev, 5) && + (pi->srom_fem2g.extpagain == 3)) { + if (txpwrindex < 50) + txpwrindex = 50; + } + } + + wlc_phy_txpwr_index_nphy(pi, (1 << core), + (u8) txpwrindex, true); + } + + pi->nphy_txcal_pwr_idx[core] = (u8) txpwrindex; + + if (debug) { + u16 radio_gain; + u16 dbg_m0m1; + + wlc_phy_table_read_nphy(pi, 15, 1, 87, 16, &dbg_m0m1); + + wlc_phy_tx_tone_nphy(pi, freq_test, ampl_test, 0, 0, + false); + + wlc_phy_table_write_nphy(pi, 15, 1, 87, 16, &dbg_m0m1); + wlc_phy_table_write_nphy(pi, 15, 1, 95, 16, &dbg_m0m1); + + udelay(100); + + wlc_phy_est_tonepwr_nphy(pi, qdBm_power, + NPHY_CAL_TSSISAMPS); + + wlc_phy_table_read_nphy(pi, 7, 1, (0x110 + core), 16, + &radio_gain); + + mdelay(4000); + pi->nphy_bb_mult_save = 0; + wlc_phy_stopplayback_nphy(pi); + } + } + + wlc_phy_txpwr_index_nphy(pi, 1, pi->nphy_txcal_pwr_idx[0], true); + wlc_phy_txpwr_index_nphy(pi, 2, pi->nphy_txcal_pwr_idx[1], true); + + wlc_phy_table_read_nphy(pi, 15, 1, 87, 16, &pi->nphy_txcal_bbmult); + + write_phy_reg(pi, 0x01, orig_BBConfig); + + write_phy_reg(pi, 0x91, phy_saveregs[0]); + write_phy_reg(pi, 0x92, phy_saveregs[1]); + write_phy_reg(pi, 0xe7, phy_saveregs[2]); + write_phy_reg(pi, 0xec, phy_saveregs[3]); + + pi->phyhang_avoid = phyhang_avoid_state; + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, false); +} + +static void wlc_phy_savecal_nphy(struct brcms_phy *pi) +{ + void *tbl_ptr; + int coreNum; + u16 *txcal_radio_regs = NULL; + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + + wlc_phy_rx_iq_coeffs_nphy(pi, 0, + &pi->calibration_cache. + rxcal_coeffs_2G); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + txcal_radio_regs = + pi->calibration_cache.txcal_radio_regs_2G; + } else if (NREV_GE(pi->pubpi.phy_rev, 3)) { + + pi->calibration_cache.txcal_radio_regs_2G[0] = + read_radio_reg(pi, + RADIO_2056_TX_LOFT_FINE_I | + RADIO_2056_TX0); + pi->calibration_cache.txcal_radio_regs_2G[1] = + read_radio_reg(pi, + RADIO_2056_TX_LOFT_FINE_Q | + RADIO_2056_TX0); + pi->calibration_cache.txcal_radio_regs_2G[2] = + read_radio_reg(pi, + RADIO_2056_TX_LOFT_FINE_I | + RADIO_2056_TX1); + pi->calibration_cache.txcal_radio_regs_2G[3] = + read_radio_reg(pi, + RADIO_2056_TX_LOFT_FINE_Q | + RADIO_2056_TX1); + + pi->calibration_cache.txcal_radio_regs_2G[4] = + read_radio_reg(pi, + RADIO_2056_TX_LOFT_COARSE_I | + RADIO_2056_TX0); + pi->calibration_cache.txcal_radio_regs_2G[5] = + read_radio_reg(pi, + RADIO_2056_TX_LOFT_COARSE_Q | + RADIO_2056_TX0); + pi->calibration_cache.txcal_radio_regs_2G[6] = + read_radio_reg(pi, + RADIO_2056_TX_LOFT_COARSE_I | + RADIO_2056_TX1); + pi->calibration_cache.txcal_radio_regs_2G[7] = + read_radio_reg(pi, + RADIO_2056_TX_LOFT_COARSE_Q | + RADIO_2056_TX1); + } else { + pi->calibration_cache.txcal_radio_regs_2G[0] = + read_radio_reg(pi, RADIO_2055_CORE1_TX_VOS_CNCL); + pi->calibration_cache.txcal_radio_regs_2G[1] = + read_radio_reg(pi, RADIO_2055_CORE2_TX_VOS_CNCL); + pi->calibration_cache.txcal_radio_regs_2G[2] = + read_radio_reg(pi, RADIO_2055_CORE1_TX_BB_MXGM); + pi->calibration_cache.txcal_radio_regs_2G[3] = + read_radio_reg(pi, RADIO_2055_CORE2_TX_BB_MXGM); + } + + pi->nphy_iqcal_chanspec_2G = pi->radio_chanspec; + tbl_ptr = pi->calibration_cache.txcal_coeffs_2G; + } else { + + wlc_phy_rx_iq_coeffs_nphy(pi, 0, + &pi->calibration_cache. + rxcal_coeffs_5G); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + txcal_radio_regs = + pi->calibration_cache.txcal_radio_regs_5G; + } else if (NREV_GE(pi->pubpi.phy_rev, 3)) { + + pi->calibration_cache.txcal_radio_regs_5G[0] = + read_radio_reg(pi, + RADIO_2056_TX_LOFT_FINE_I | + RADIO_2056_TX0); + pi->calibration_cache.txcal_radio_regs_5G[1] = + read_radio_reg(pi, + RADIO_2056_TX_LOFT_FINE_Q | + RADIO_2056_TX0); + pi->calibration_cache.txcal_radio_regs_5G[2] = + read_radio_reg(pi, + RADIO_2056_TX_LOFT_FINE_I | + RADIO_2056_TX1); + pi->calibration_cache.txcal_radio_regs_5G[3] = + read_radio_reg(pi, + RADIO_2056_TX_LOFT_FINE_Q | + RADIO_2056_TX1); + + pi->calibration_cache.txcal_radio_regs_5G[4] = + read_radio_reg(pi, + RADIO_2056_TX_LOFT_COARSE_I | + RADIO_2056_TX0); + pi->calibration_cache.txcal_radio_regs_5G[5] = + read_radio_reg(pi, + RADIO_2056_TX_LOFT_COARSE_Q | + RADIO_2056_TX0); + pi->calibration_cache.txcal_radio_regs_5G[6] = + read_radio_reg(pi, + RADIO_2056_TX_LOFT_COARSE_I | + RADIO_2056_TX1); + pi->calibration_cache.txcal_radio_regs_5G[7] = + read_radio_reg(pi, + RADIO_2056_TX_LOFT_COARSE_Q | + RADIO_2056_TX1); + } else { + pi->calibration_cache.txcal_radio_regs_5G[0] = + read_radio_reg(pi, RADIO_2055_CORE1_TX_VOS_CNCL); + pi->calibration_cache.txcal_radio_regs_5G[1] = + read_radio_reg(pi, RADIO_2055_CORE2_TX_VOS_CNCL); + pi->calibration_cache.txcal_radio_regs_5G[2] = + read_radio_reg(pi, RADIO_2055_CORE1_TX_BB_MXGM); + pi->calibration_cache.txcal_radio_regs_5G[3] = + read_radio_reg(pi, RADIO_2055_CORE2_TX_BB_MXGM); + } + + pi->nphy_iqcal_chanspec_5G = pi->radio_chanspec; + tbl_ptr = pi->calibration_cache.txcal_coeffs_5G; + } + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + for (coreNum = 0; coreNum <= 1; coreNum++) { + + txcal_radio_regs[2 * coreNum] = + READ_RADIO_REG3(pi, RADIO_2057, TX, coreNum, + LOFT_FINE_I); + txcal_radio_regs[2 * coreNum + 1] = + READ_RADIO_REG3(pi, RADIO_2057, TX, coreNum, + LOFT_FINE_Q); + + txcal_radio_regs[2 * coreNum + 4] = + READ_RADIO_REG3(pi, RADIO_2057, TX, coreNum, + LOFT_COARSE_I); + txcal_radio_regs[2 * coreNum + 5] = + READ_RADIO_REG3(pi, RADIO_2057, TX, coreNum, + LOFT_COARSE_Q); + } + } + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_IQLOCAL, 8, 80, 16, tbl_ptr); + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, false); +} + +static void wlc_phy_tx_iq_war_nphy(struct brcms_phy *pi) +{ + struct nphy_iq_comp tx_comp; + + wlc_phy_table_read_nphy(pi, 15, 4, 0x50, 16, &tx_comp); + + wlapi_bmac_write_shm(pi->sh->physhim, M_20IN40_IQ, tx_comp.a0); + wlapi_bmac_write_shm(pi->sh->physhim, M_20IN40_IQ + 2, tx_comp.b0); + wlapi_bmac_write_shm(pi->sh->physhim, M_20IN40_IQ + 4, tx_comp.a1); + wlapi_bmac_write_shm(pi->sh->physhim, M_20IN40_IQ + 6, tx_comp.b1); +} + +static void wlc_phy_restorecal_nphy(struct brcms_phy *pi) +{ + u16 *loft_comp; + u16 txcal_coeffs_bphy[4]; + u16 *tbl_ptr; + int coreNum; + u16 *txcal_radio_regs = NULL; + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if (pi->nphy_iqcal_chanspec_2G == 0) + return; + + tbl_ptr = pi->calibration_cache.txcal_coeffs_2G; + loft_comp = &pi->calibration_cache.txcal_coeffs_2G[5]; + } else { + if (pi->nphy_iqcal_chanspec_5G == 0) + return; + + tbl_ptr = pi->calibration_cache.txcal_coeffs_5G; + loft_comp = &pi->calibration_cache.txcal_coeffs_5G[5]; + } + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, 4, 80, 16, tbl_ptr); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + txcal_coeffs_bphy[0] = tbl_ptr[0]; + txcal_coeffs_bphy[1] = tbl_ptr[1]; + txcal_coeffs_bphy[2] = tbl_ptr[2]; + txcal_coeffs_bphy[3] = tbl_ptr[3]; + } else { + txcal_coeffs_bphy[0] = 0; + txcal_coeffs_bphy[1] = 0; + txcal_coeffs_bphy[2] = 0; + txcal_coeffs_bphy[3] = 0; + } + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, 4, 88, 16, + txcal_coeffs_bphy); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, 2, 85, 16, loft_comp); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, 2, 93, 16, loft_comp); + + if (NREV_LT(pi->pubpi.phy_rev, 2)) + wlc_phy_tx_iq_war_nphy(pi); + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + txcal_radio_regs = + pi->calibration_cache.txcal_radio_regs_2G; + } else if (NREV_GE(pi->pubpi.phy_rev, 3)) { + + write_radio_reg(pi, + RADIO_2056_TX_LOFT_FINE_I | + RADIO_2056_TX0, + pi->calibration_cache. + txcal_radio_regs_2G[0]); + write_radio_reg(pi, + RADIO_2056_TX_LOFT_FINE_Q | + RADIO_2056_TX0, + pi->calibration_cache. + txcal_radio_regs_2G[1]); + write_radio_reg(pi, + RADIO_2056_TX_LOFT_FINE_I | + RADIO_2056_TX1, + pi->calibration_cache. + txcal_radio_regs_2G[2]); + write_radio_reg(pi, + RADIO_2056_TX_LOFT_FINE_Q | + RADIO_2056_TX1, + pi->calibration_cache. + txcal_radio_regs_2G[3]); + + write_radio_reg(pi, + RADIO_2056_TX_LOFT_COARSE_I | + RADIO_2056_TX0, + pi->calibration_cache. + txcal_radio_regs_2G[4]); + write_radio_reg(pi, + RADIO_2056_TX_LOFT_COARSE_Q | + RADIO_2056_TX0, + pi->calibration_cache. + txcal_radio_regs_2G[5]); + write_radio_reg(pi, + RADIO_2056_TX_LOFT_COARSE_I | + RADIO_2056_TX1, + pi->calibration_cache. + txcal_radio_regs_2G[6]); + write_radio_reg(pi, + RADIO_2056_TX_LOFT_COARSE_Q | + RADIO_2056_TX1, + pi->calibration_cache. + txcal_radio_regs_2G[7]); + } else { + write_radio_reg(pi, RADIO_2055_CORE1_TX_VOS_CNCL, + pi->calibration_cache. + txcal_radio_regs_2G[0]); + write_radio_reg(pi, RADIO_2055_CORE2_TX_VOS_CNCL, + pi->calibration_cache. + txcal_radio_regs_2G[1]); + write_radio_reg(pi, RADIO_2055_CORE1_TX_BB_MXGM, + pi->calibration_cache. + txcal_radio_regs_2G[2]); + write_radio_reg(pi, RADIO_2055_CORE2_TX_BB_MXGM, + pi->calibration_cache. + txcal_radio_regs_2G[3]); + } + + wlc_phy_rx_iq_coeffs_nphy(pi, 1, + &pi->calibration_cache. + rxcal_coeffs_2G); + } else { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + txcal_radio_regs = + pi->calibration_cache.txcal_radio_regs_5G; + } else if (NREV_GE(pi->pubpi.phy_rev, 3)) { + + write_radio_reg(pi, + RADIO_2056_TX_LOFT_FINE_I | + RADIO_2056_TX0, + pi->calibration_cache. + txcal_radio_regs_5G[0]); + write_radio_reg(pi, + RADIO_2056_TX_LOFT_FINE_Q | + RADIO_2056_TX0, + pi->calibration_cache. + txcal_radio_regs_5G[1]); + write_radio_reg(pi, + RADIO_2056_TX_LOFT_FINE_I | + RADIO_2056_TX1, + pi->calibration_cache. + txcal_radio_regs_5G[2]); + write_radio_reg(pi, + RADIO_2056_TX_LOFT_FINE_Q | + RADIO_2056_TX1, + pi->calibration_cache. + txcal_radio_regs_5G[3]); + + write_radio_reg(pi, + RADIO_2056_TX_LOFT_COARSE_I | + RADIO_2056_TX0, + pi->calibration_cache. + txcal_radio_regs_5G[4]); + write_radio_reg(pi, + RADIO_2056_TX_LOFT_COARSE_Q | + RADIO_2056_TX0, + pi->calibration_cache. + txcal_radio_regs_5G[5]); + write_radio_reg(pi, + RADIO_2056_TX_LOFT_COARSE_I | + RADIO_2056_TX1, + pi->calibration_cache. + txcal_radio_regs_5G[6]); + write_radio_reg(pi, + RADIO_2056_TX_LOFT_COARSE_Q | + RADIO_2056_TX1, + pi->calibration_cache. + txcal_radio_regs_5G[7]); + } else { + write_radio_reg(pi, RADIO_2055_CORE1_TX_VOS_CNCL, + pi->calibration_cache. + txcal_radio_regs_5G[0]); + write_radio_reg(pi, RADIO_2055_CORE2_TX_VOS_CNCL, + pi->calibration_cache. + txcal_radio_regs_5G[1]); + write_radio_reg(pi, RADIO_2055_CORE1_TX_BB_MXGM, + pi->calibration_cache. + txcal_radio_regs_5G[2]); + write_radio_reg(pi, RADIO_2055_CORE2_TX_BB_MXGM, + pi->calibration_cache. + txcal_radio_regs_5G[3]); + } + + wlc_phy_rx_iq_coeffs_nphy(pi, 1, + &pi->calibration_cache. + rxcal_coeffs_5G); + } + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + for (coreNum = 0; coreNum <= 1; coreNum++) { + + WRITE_RADIO_REG3(pi, RADIO_2057, TX, coreNum, + LOFT_FINE_I, + txcal_radio_regs[2 * coreNum]); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, coreNum, + LOFT_FINE_Q, + txcal_radio_regs[2 * coreNum + 1]); + + WRITE_RADIO_REG3(pi, RADIO_2057, TX, coreNum, + LOFT_COARSE_I, + txcal_radio_regs[2 * coreNum + 4]); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, coreNum, + LOFT_COARSE_Q, + txcal_radio_regs[2 * coreNum + 5]); + } + } +} + +static void wlc_phy_txpwrctrl_coeff_setup_nphy(struct brcms_phy *pi) +{ + u32 idx; + u16 iqloCalbuf[7]; + u32 iqcomp, locomp, curr_locomp; + s8 locomp_i, locomp_q; + s8 curr_locomp_i, curr_locomp_q; + u32 tbl_id, tbl_len, tbl_offset; + u32 regval[128]; + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + wlc_phy_table_read_nphy(pi, 15, 7, 80, 16, iqloCalbuf); + + tbl_len = 128; + tbl_offset = 320; + for (tbl_id = NPHY_TBL_ID_CORE1TXPWRCTL; + tbl_id <= NPHY_TBL_ID_CORE2TXPWRCTL; tbl_id++) { + iqcomp = + (tbl_id == + 26) ? (((u32) (iqloCalbuf[0] & 0x3ff)) << 10) | + (iqloCalbuf[1] & 0x3ff) + : (((u32) (iqloCalbuf[2] & 0x3ff)) << 10) | + (iqloCalbuf[3] & 0x3ff); + + for (idx = 0; idx < tbl_len; idx++) + regval[idx] = iqcomp; + wlc_phy_table_write_nphy(pi, tbl_id, tbl_len, tbl_offset, 32, + regval); + } + + tbl_offset = 448; + for (tbl_id = NPHY_TBL_ID_CORE1TXPWRCTL; + tbl_id <= NPHY_TBL_ID_CORE2TXPWRCTL; tbl_id++) { + + locomp = + (u32) ((tbl_id == 26) ? iqloCalbuf[5] : iqloCalbuf[6]); + locomp_i = (s8) ((locomp >> 8) & 0xff); + locomp_q = (s8) ((locomp) & 0xff); + for (idx = 0; idx < tbl_len; idx++) { + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + curr_locomp_i = locomp_i; + curr_locomp_q = locomp_q; + } else { + curr_locomp_i = (s8) ((locomp_i * + nphy_tpc_loscale[idx] + + 128) >> 8); + curr_locomp_q = + (s8) ((locomp_q * + nphy_tpc_loscale[idx] + + 128) >> 8); + } + curr_locomp = (u32) ((curr_locomp_i & 0xff) << 8); + curr_locomp |= (u32) (curr_locomp_q & 0xff); + regval[idx] = curr_locomp; + } + wlc_phy_table_write_nphy(pi, tbl_id, tbl_len, tbl_offset, 32, + regval); + } + + if (NREV_LT(pi->pubpi.phy_rev, 2)) { + + wlapi_bmac_write_shm(pi->sh->physhim, M_CURR_IDX1, 0xFFFF); + wlapi_bmac_write_shm(pi->sh->physhim, M_CURR_IDX2, 0xFFFF); + } + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, false); +} + +static void wlc_phy_txlpfbw_nphy(struct brcms_phy *pi) +{ + u8 tx_lpf_bw = 0; + + if (NREV_GE(pi->pubpi.phy_rev, 3) && NREV_LT(pi->pubpi.phy_rev, 7)) { + if (CHSPEC_IS40(pi->radio_chanspec)) + tx_lpf_bw = 3; + else + tx_lpf_bw = 1; + + if (PHY_IPA(pi)) { + if (CHSPEC_IS40(pi->radio_chanspec)) + tx_lpf_bw = 5; + else + tx_lpf_bw = 4; + } + + write_phy_reg(pi, 0xe8, + (tx_lpf_bw << 0) | + (tx_lpf_bw << 3) | + (tx_lpf_bw << 6) | (tx_lpf_bw << 9)); + + if (PHY_IPA(pi)) { + + if (CHSPEC_IS40(pi->radio_chanspec)) + tx_lpf_bw = 4; + else + tx_lpf_bw = 1; + + write_phy_reg(pi, 0xe9, + (tx_lpf_bw << 0) | + (tx_lpf_bw << 3) | + (tx_lpf_bw << 6) | (tx_lpf_bw << 9)); + } + } +} + +static void +wlc_phy_adjust_rx_analpfbw_nphy(struct brcms_phy *pi, u16 reduction_factr) +{ + if (NREV_GE(pi->pubpi.phy_rev, 3) && NREV_LT(pi->pubpi.phy_rev, 7)) { + if ((CHSPEC_CHANNEL(pi->radio_chanspec) == 11) && + CHSPEC_IS40(pi->radio_chanspec)) { + if (!pi->nphy_anarxlpf_adjusted) { + write_radio_reg(pi, + (RADIO_2056_RX_RXLPF_RCCAL_LPC | + RADIO_2056_RX0), + ((pi->nphy_rccal_value + + reduction_factr) | 0x80)); + + pi->nphy_anarxlpf_adjusted = true; + } + } else { + if (pi->nphy_anarxlpf_adjusted) { + write_radio_reg(pi, + (RADIO_2056_RX_RXLPF_RCCAL_LPC | + RADIO_2056_RX0), + (pi->nphy_rccal_value | 0x80)); + + pi->nphy_anarxlpf_adjusted = false; + } + } + } +} + +static void +wlc_phy_adjust_min_noisevar_nphy(struct brcms_phy *pi, int ntones, + int *tone_id_buf, u32 *noise_var_buf) +{ + int i; + u32 offset; + int tone_id; + int tbllen = + CHSPEC_IS40(pi->radio_chanspec) ? + NPHY_NOISEVAR_TBLLEN40 : NPHY_NOISEVAR_TBLLEN20; + + if (pi->nphy_noisevars_adjusted) { + for (i = 0; i < pi->nphy_saved_noisevars.bufcount; i++) { + tone_id = pi->nphy_saved_noisevars.tone_id[i]; + offset = (tone_id >= 0) ? + ((tone_id * + 2) + 1) : (tbllen + (tone_id * 2) + 1); + wlc_phy_table_write_nphy( + pi, NPHY_TBL_ID_NOISEVAR, 1, + offset, 32, + &pi->nphy_saved_noisevars.min_noise_vars[i]); + } + + pi->nphy_saved_noisevars.bufcount = 0; + pi->nphy_noisevars_adjusted = false; + } + + if ((noise_var_buf != NULL) && (tone_id_buf != NULL)) { + pi->nphy_saved_noisevars.bufcount = 0; + + for (i = 0; i < ntones; i++) { + tone_id = tone_id_buf[i]; + offset = (tone_id >= 0) ? + ((tone_id * 2) + 1) : + (tbllen + (tone_id * 2) + 1); + pi->nphy_saved_noisevars.tone_id[i] = tone_id; + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_NOISEVAR, 1, + offset, 32, + &pi->nphy_saved_noisevars. + min_noise_vars[i]); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_NOISEVAR, 1, + offset, 32, &noise_var_buf[i]); + pi->nphy_saved_noisevars.bufcount++; + } + + pi->nphy_noisevars_adjusted = true; + } +} + +static void wlc_phy_adjust_crsminpwr_nphy(struct brcms_phy *pi, u8 minpwr) +{ + u16 regval; + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + if ((CHSPEC_CHANNEL(pi->radio_chanspec) == 11) && + CHSPEC_IS40(pi->radio_chanspec)) { + if (!pi->nphy_crsminpwr_adjusted) { + regval = read_phy_reg(pi, 0x27d); + pi->nphy_crsminpwr[0] = regval & 0xff; + regval &= 0xff00; + regval |= (u16) minpwr; + write_phy_reg(pi, 0x27d, regval); + + regval = read_phy_reg(pi, 0x280); + pi->nphy_crsminpwr[1] = regval & 0xff; + regval &= 0xff00; + regval |= (u16) minpwr; + write_phy_reg(pi, 0x280, regval); + + regval = read_phy_reg(pi, 0x283); + pi->nphy_crsminpwr[2] = regval & 0xff; + regval &= 0xff00; + regval |= (u16) minpwr; + write_phy_reg(pi, 0x283, regval); + + pi->nphy_crsminpwr_adjusted = true; + } + } else { + if (pi->nphy_crsminpwr_adjusted) { + regval = read_phy_reg(pi, 0x27d); + regval &= 0xff00; + regval |= pi->nphy_crsminpwr[0]; + write_phy_reg(pi, 0x27d, regval); + + regval = read_phy_reg(pi, 0x280); + regval &= 0xff00; + regval |= pi->nphy_crsminpwr[1]; + write_phy_reg(pi, 0x280, regval); + + regval = read_phy_reg(pi, 0x283); + regval &= 0xff00; + regval |= pi->nphy_crsminpwr[2]; + write_phy_reg(pi, 0x283, regval); + + pi->nphy_crsminpwr_adjusted = false; + } + } + } +} + +static void wlc_phy_spurwar_nphy(struct brcms_phy *pi) +{ + u16 cur_channel = 0; + int nphy_adj_tone_id_buf[] = { 57, 58 }; + u32 nphy_adj_noise_var_buf[] = { 0x3ff, 0x3ff }; + bool isAdjustNoiseVar = false; + uint numTonesAdjust = 0; + u32 tempval = 0; + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + cur_channel = CHSPEC_CHANNEL(pi->radio_chanspec); + + if (pi->nphy_gband_spurwar_en) { + + wlc_phy_adjust_rx_analpfbw_nphy( + pi, + NPHY_ANARXLPFBW_REDUCTIONFACT); + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if ((cur_channel == 11) + && CHSPEC_IS40(pi->radio_chanspec)) + wlc_phy_adjust_min_noisevar_nphy( + pi, 2, + nphy_adj_tone_id_buf, + nphy_adj_noise_var_buf); + else + wlc_phy_adjust_min_noisevar_nphy(pi, 0, + NULL, + NULL); + } + + wlc_phy_adjust_crsminpwr_nphy(pi, + NPHY_ADJUSTED_MINCRSPOWER); + } + + if ((pi->nphy_gband_spurwar2_en) + && CHSPEC_IS2G(pi->radio_chanspec)) { + + if (CHSPEC_IS40(pi->radio_chanspec)) { + switch (cur_channel) { + case 3: + nphy_adj_tone_id_buf[0] = 57; + nphy_adj_tone_id_buf[1] = 58; + nphy_adj_noise_var_buf[0] = 0x22f; + nphy_adj_noise_var_buf[1] = 0x25f; + isAdjustNoiseVar = true; + break; + case 4: + nphy_adj_tone_id_buf[0] = 41; + nphy_adj_tone_id_buf[1] = 42; + nphy_adj_noise_var_buf[0] = 0x22f; + nphy_adj_noise_var_buf[1] = 0x25f; + isAdjustNoiseVar = true; + break; + case 5: + nphy_adj_tone_id_buf[0] = 25; + nphy_adj_tone_id_buf[1] = 26; + nphy_adj_noise_var_buf[0] = 0x24f; + nphy_adj_noise_var_buf[1] = 0x25f; + isAdjustNoiseVar = true; + break; + case 6: + nphy_adj_tone_id_buf[0] = 9; + nphy_adj_tone_id_buf[1] = 10; + nphy_adj_noise_var_buf[0] = 0x22f; + nphy_adj_noise_var_buf[1] = 0x24f; + isAdjustNoiseVar = true; + break; + case 7: + nphy_adj_tone_id_buf[0] = 121; + nphy_adj_tone_id_buf[1] = 122; + nphy_adj_noise_var_buf[0] = 0x18f; + nphy_adj_noise_var_buf[1] = 0x24f; + isAdjustNoiseVar = true; + break; + case 8: + nphy_adj_tone_id_buf[0] = 105; + nphy_adj_tone_id_buf[1] = 106; + nphy_adj_noise_var_buf[0] = 0x22f; + nphy_adj_noise_var_buf[1] = 0x25f; + isAdjustNoiseVar = true; + break; + case 9: + nphy_adj_tone_id_buf[0] = 89; + nphy_adj_tone_id_buf[1] = 90; + nphy_adj_noise_var_buf[0] = 0x22f; + nphy_adj_noise_var_buf[1] = 0x24f; + isAdjustNoiseVar = true; + break; + case 10: + nphy_adj_tone_id_buf[0] = 73; + nphy_adj_tone_id_buf[1] = 74; + nphy_adj_noise_var_buf[0] = 0x22f; + nphy_adj_noise_var_buf[1] = 0x24f; + isAdjustNoiseVar = true; + break; + default: + isAdjustNoiseVar = false; + break; + } + } + + if (isAdjustNoiseVar) { + numTonesAdjust = ARRAY_SIZE(nphy_adj_tone_id_buf); + + wlc_phy_adjust_min_noisevar_nphy( + pi, + numTonesAdjust, + nphy_adj_tone_id_buf, + nphy_adj_noise_var_buf); + + tempval = 0; + + } else { + wlc_phy_adjust_min_noisevar_nphy(pi, 0, NULL, + NULL); + } + } + + if ((pi->nphy_aband_spurwar_en) && + (CHSPEC_IS5G(pi->radio_chanspec))) { + switch (cur_channel) { + case 54: + nphy_adj_tone_id_buf[0] = 32; + nphy_adj_noise_var_buf[0] = 0x25f; + break; + case 38: + case 102: + case 118: + if ((pi->sh->chip == BCMA_CHIP_ID_BCM4716) && + (pi->sh->chippkg == BCMA_PKG_ID_BCM4717)) { + nphy_adj_tone_id_buf[0] = 32; + nphy_adj_noise_var_buf[0] = 0x21f; + } else { + nphy_adj_tone_id_buf[0] = 0; + nphy_adj_noise_var_buf[0] = 0x0; + } + break; + case 134: + nphy_adj_tone_id_buf[0] = 32; + nphy_adj_noise_var_buf[0] = 0x21f; + break; + case 151: + nphy_adj_tone_id_buf[0] = 16; + nphy_adj_noise_var_buf[0] = 0x23f; + break; + case 153: + case 161: + nphy_adj_tone_id_buf[0] = 48; + nphy_adj_noise_var_buf[0] = 0x23f; + break; + default: + nphy_adj_tone_id_buf[0] = 0; + nphy_adj_noise_var_buf[0] = 0x0; + break; + } + + if (nphy_adj_tone_id_buf[0] + && nphy_adj_noise_var_buf[0]) + wlc_phy_adjust_min_noisevar_nphy( + pi, 1, + nphy_adj_tone_id_buf, + nphy_adj_noise_var_buf); + else + wlc_phy_adjust_min_noisevar_nphy(pi, 0, NULL, + NULL); + } + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, false); + } +} + +void wlc_phy_init_nphy(struct brcms_phy *pi) +{ + u16 val; + u16 clip1_ths[2]; + struct nphy_txgains target_gain; + u8 tx_pwr_ctrl_state; + bool do_nphy_cal = false; + uint core; + u32 d11_clk_ctl_st; + bool do_rssi_cal = false; + + core = 0; + + if (!(pi->measure_hold & PHY_HOLD_FOR_SCAN)) + pi->measure_hold |= PHY_HOLD_FOR_NOT_ASSOC; + + if ((ISNPHY(pi)) && (NREV_GE(pi->pubpi.phy_rev, 5)) && + ((pi->sh->chippkg == BCMA_PKG_ID_BCM4717) || + (pi->sh->chippkg == BCMA_PKG_ID_BCM4718))) { + if ((pi->sh->boardflags & BFL_EXTLNA) && + (CHSPEC_IS2G(pi->radio_chanspec))) + bcma_cc_set32(&pi->d11core->bus->drv_cc, + BCMA_CC_CHIPCTL, 0x40); + } + + if ((!PHY_IPA(pi)) && (pi->sh->chip == BCMA_CHIP_ID_BCM5357)) + bcma_chipco_chipctl_maskset(&pi->d11core->bus->drv_cc, 1, + ~CCTRL5357_EXTPA, CCTRL5357_EXTPA); + + if ((pi->nphy_gband_spurwar2_en) && CHSPEC_IS2G(pi->radio_chanspec) && + CHSPEC_IS40(pi->radio_chanspec)) { + + d11_clk_ctl_st = bcma_read32(pi->d11core, + D11REGOFFS(clk_ctl_st)); + bcma_mask32(pi->d11core, D11REGOFFS(clk_ctl_st), + ~(CCS_FORCEHT | CCS_HTAREQ)); + + bcma_write32(pi->d11core, D11REGOFFS(clk_ctl_st), + d11_clk_ctl_st); + } + + pi->use_int_tx_iqlo_cal_nphy = + (PHY_IPA(pi) || + (NREV_GE(pi->pubpi.phy_rev, 7) || + (NREV_GE(pi->pubpi.phy_rev, 5) + && pi->sh->boardflags2 & BFL2_INTERNDET_TXIQCAL))); + + pi->internal_tx_iqlo_cal_tapoff_intpa_nphy = false; + + pi->nphy_deaf_count = 0; + + wlc_phy_tbl_init_nphy(pi); + + pi->nphy_crsminpwr_adjusted = false; + pi->nphy_noisevars_adjusted = false; + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + write_phy_reg(pi, 0xe7, 0); + write_phy_reg(pi, 0xec, 0); + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + write_phy_reg(pi, 0x342, 0); + write_phy_reg(pi, 0x343, 0); + write_phy_reg(pi, 0x346, 0); + write_phy_reg(pi, 0x347, 0); + } + write_phy_reg(pi, 0xe5, 0); + write_phy_reg(pi, 0xe6, 0); + } else { + write_phy_reg(pi, 0xec, 0); + } + + write_phy_reg(pi, 0x91, 0); + write_phy_reg(pi, 0x92, 0); + if (NREV_LT(pi->pubpi.phy_rev, 6)) { + write_phy_reg(pi, 0x93, 0); + write_phy_reg(pi, 0x94, 0); + } + + and_phy_reg(pi, 0xa1, ~3); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + write_phy_reg(pi, 0x8f, 0); + write_phy_reg(pi, 0xa5, 0); + } else { + write_phy_reg(pi, 0xa5, 0); + } + + if (NREV_IS(pi->pubpi.phy_rev, 2)) + mod_phy_reg(pi, 0xdc, 0x00ff, 0x3b); + else if (NREV_LT(pi->pubpi.phy_rev, 2)) + mod_phy_reg(pi, 0xdc, 0x00ff, 0x40); + + write_phy_reg(pi, 0x203, 32); + write_phy_reg(pi, 0x201, 32); + + if (pi->sh->boardflags2 & BFL2_SKWRKFEM_BRD) + write_phy_reg(pi, 0x20d, 160); + else + write_phy_reg(pi, 0x20d, 184); + + write_phy_reg(pi, 0x13a, 200); + + write_phy_reg(pi, 0x70, 80); + + write_phy_reg(pi, 0x1ff, 48); + + if (NREV_LT(pi->pubpi.phy_rev, 8)) + wlc_phy_update_mimoconfig_nphy(pi, pi->n_preamble_override); + + wlc_phy_stf_chain_upd_nphy(pi); + + if (NREV_LT(pi->pubpi.phy_rev, 2)) { + write_phy_reg(pi, 0x180, 0xaa8); + write_phy_reg(pi, 0x181, 0x9a4); + } + + if (PHY_IPA(pi)) { + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 0), (1) << 0); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x298 : + 0x29c, (0x1ff << 7), + (pi->nphy_papd_epsilon_offset[core]) << 7); + + } + + wlc_phy_ipa_set_tx_digi_filts_nphy(pi); + } else if (NREV_GE(pi->pubpi.phy_rev, 5)) { + wlc_phy_extpa_set_tx_digi_filts_nphy(pi); + } + + wlc_phy_workarounds_nphy(pi); + + wlapi_bmac_phyclk_fgc(pi->sh->physhim, ON); + + val = read_phy_reg(pi, 0x01); + write_phy_reg(pi, 0x01, val | BBCFG_RESETCCA); + write_phy_reg(pi, 0x01, val & (~BBCFG_RESETCCA)); + wlapi_bmac_phyclk_fgc(pi->sh->physhim, OFF); + + wlapi_bmac_macphyclk_set(pi->sh->physhim, ON); + + wlc_phy_pa_override_nphy(pi, OFF); + wlc_phy_force_rfseq_nphy(pi, NPHY_RFSEQ_RX2TX); + wlc_phy_force_rfseq_nphy(pi, NPHY_RFSEQ_RESET2RX); + wlc_phy_pa_override_nphy(pi, ON); + + wlc_phy_classifier_nphy(pi, 0, 0); + wlc_phy_clip_det_nphy(pi, 0, clip1_ths); + + if (CHSPEC_IS2G(pi->radio_chanspec)) + wlc_phy_bphy_init_nphy(pi); + + tx_pwr_ctrl_state = pi->nphy_txpwrctrl; + wlc_phy_txpwrctrl_enable_nphy(pi, PHY_TPC_HW_OFF); + + wlc_phy_txpwr_fixpower_nphy(pi); + + wlc_phy_txpwrctrl_idle_tssi_nphy(pi); + + wlc_phy_txpwrctrl_pwr_setup_nphy(pi); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + u32 *tx_pwrctrl_tbl = NULL; + u16 idx; + s16 pga_gn = 0; + s16 pad_gn = 0; + s32 rfpwr_offset; + + if (PHY_IPA(pi)) { + tx_pwrctrl_tbl = wlc_phy_get_ipa_gaintbl_nphy(pi); + } else { + if (CHSPEC_IS5G(pi->radio_chanspec)) { + if (NREV_IS(pi->pubpi.phy_rev, 3)) + tx_pwrctrl_tbl = + nphy_tpc_5GHz_txgain_rev3; + else if (NREV_IS(pi->pubpi.phy_rev, 4)) + tx_pwrctrl_tbl = + (pi->srom_fem5g.extpagain == + 3) ? + nphy_tpc_5GHz_txgain_HiPwrEPA : + nphy_tpc_5GHz_txgain_rev4; + else + tx_pwrctrl_tbl = + nphy_tpc_5GHz_txgain_rev5; + } else { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + if (pi->pubpi.radiorev == 5) + tx_pwrctrl_tbl = + nphy_tpc_txgain_epa_2057rev5; + else if (pi->pubpi.radiorev == 3) + tx_pwrctrl_tbl = + nphy_tpc_txgain_epa_2057rev3; + } else { + if (NREV_GE(pi->pubpi.phy_rev, 5) && + (pi->srom_fem2g.extpagain == 3)) + tx_pwrctrl_tbl = + nphy_tpc_txgain_HiPwrEPA; + else + tx_pwrctrl_tbl = + nphy_tpc_txgain_rev3; + } + } + } + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_CORE1TXPWRCTL, 128, + 192, 32, tx_pwrctrl_tbl); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_CORE2TXPWRCTL, 128, + 192, 32, tx_pwrctrl_tbl); + + pi->nphy_gmval = (u16) ((*tx_pwrctrl_tbl >> 16) & 0x7000); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + for (idx = 0; idx < 128; idx++) { + pga_gn = (tx_pwrctrl_tbl[idx] >> 24) & 0xf; + pad_gn = (tx_pwrctrl_tbl[idx] >> 19) & 0x1f; + rfpwr_offset = get_rf_pwr_offset(pi, pga_gn, + pad_gn); + wlc_phy_table_write_nphy( + pi, + NPHY_TBL_ID_CORE1TXPWRCTL, + 1, 576 + idx, 32, + &rfpwr_offset); + wlc_phy_table_write_nphy( + pi, + NPHY_TBL_ID_CORE2TXPWRCTL, + 1, 576 + idx, 32, + &rfpwr_offset); + } + } else { + + for (idx = 0; idx < 128; idx++) { + pga_gn = (tx_pwrctrl_tbl[idx] >> 24) & 0xf; + if (CHSPEC_IS2G(pi->radio_chanspec)) + rfpwr_offset = (s16) + nphy_papd_pga_gain_delta_ipa_2g + [pga_gn]; + else + rfpwr_offset = (s16) + nphy_papd_pga_gain_delta_ipa_5g + [pga_gn]; + + wlc_phy_table_write_nphy( + pi, + NPHY_TBL_ID_CORE1TXPWRCTL, + 1, 576 + idx, 32, + &rfpwr_offset); + wlc_phy_table_write_nphy( + pi, + NPHY_TBL_ID_CORE2TXPWRCTL, + 1, 576 + idx, 32, + &rfpwr_offset); + } + + } + } else { + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_CORE1TXPWRCTL, 128, + 192, 32, nphy_tpc_txgain); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_CORE2TXPWRCTL, 128, + 192, 32, nphy_tpc_txgain); + } + + if (pi->sh->phyrxchain != 0x3) + wlc_phy_rxcore_setstate_nphy((struct brcms_phy_pub *) pi, + pi->sh->phyrxchain); + + if (PHY_PERICAL_MPHASE_PENDING(pi)) + wlc_phy_cal_perical_mphase_restart(pi); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + do_rssi_cal = (CHSPEC_IS2G(pi->radio_chanspec)) ? + (pi->nphy_rssical_chanspec_2G == 0) : + (pi->nphy_rssical_chanspec_5G == 0); + + if (do_rssi_cal) + wlc_phy_rssi_cal_nphy(pi); + else + wlc_phy_restore_rssical_nphy(pi); + } else { + wlc_phy_rssi_cal_nphy(pi); + } + + if (!SCAN_RM_IN_PROGRESS(pi)) + do_nphy_cal = (CHSPEC_IS2G(pi->radio_chanspec)) ? + (pi->nphy_iqcal_chanspec_2G == 0) : + (pi->nphy_iqcal_chanspec_5G == 0); + + if (!pi->do_initcal) + do_nphy_cal = false; + + if (do_nphy_cal) { + + target_gain = wlc_phy_get_tx_gain_nphy(pi); + + if (pi->antsel_type == ANTSEL_2x3) + wlc_phy_antsel_init((struct brcms_phy_pub *) pi, + true); + + if (pi->nphy_perical != PHY_PERICAL_MPHASE) { + wlc_phy_rssi_cal_nphy(pi); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + pi->nphy_cal_orig_pwr_idx[0] = + pi->nphy_txpwrindex[PHY_CORE_0] + . + index_internal; + pi->nphy_cal_orig_pwr_idx[1] = + pi->nphy_txpwrindex[PHY_CORE_1] + . + index_internal; + + wlc_phy_precal_txgain_nphy(pi); + target_gain = + wlc_phy_get_tx_gain_nphy(pi); + } + + if (wlc_phy_cal_txiqlo_nphy + (pi, target_gain, true, + false) == 0) { + if (wlc_phy_cal_rxiq_nphy + (pi, target_gain, 2, + false) == 0) + wlc_phy_savecal_nphy(pi); + + } + } else if (pi->mphase_cal_phase_id == + MPHASE_CAL_STATE_IDLE) { + wlc_phy_cal_perical((struct brcms_phy_pub *) pi, + PHY_PERICAL_PHYINIT); + } + } else { + wlc_phy_restorecal_nphy(pi); + } + + wlc_phy_txpwrctrl_coeff_setup_nphy(pi); + + wlc_phy_txpwrctrl_enable_nphy(pi, tx_pwr_ctrl_state); + + wlc_phy_nphy_tkip_rifs_war(pi, pi->sh->_rifs_phy); + + if (NREV_GE(pi->pubpi.phy_rev, 3) && NREV_LE(pi->pubpi.phy_rev, 6)) + + write_phy_reg(pi, 0x70, 50); + + wlc_phy_txlpfbw_nphy(pi); + + wlc_phy_spurwar_nphy(pi); + +} + +static void wlc_phy_resetcca_nphy(struct brcms_phy *pi) +{ + u16 val; + + wlapi_bmac_phyclk_fgc(pi->sh->physhim, ON); + + val = read_phy_reg(pi, 0x01); + write_phy_reg(pi, 0x01, val | BBCFG_RESETCCA); + udelay(1); + write_phy_reg(pi, 0x01, val & (~BBCFG_RESETCCA)); + + wlapi_bmac_phyclk_fgc(pi->sh->physhim, OFF); + + wlc_phy_force_rfseq_nphy(pi, NPHY_RFSEQ_RESET2RX); +} + +void wlc_phy_pa_override_nphy(struct brcms_phy *pi, bool en) +{ + u16 rfctrlintc_override_val; + + if (!en) { + + pi->rfctrlIntc1_save = read_phy_reg(pi, 0x91); + pi->rfctrlIntc2_save = read_phy_reg(pi, 0x92); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + rfctrlintc_override_val = 0x1480; + else if (NREV_GE(pi->pubpi.phy_rev, 3)) + rfctrlintc_override_val = + CHSPEC_IS5G(pi->radio_chanspec) ? 0x600 : 0x480; + else + rfctrlintc_override_val = + CHSPEC_IS5G(pi->radio_chanspec) ? 0x180 : 0x120; + + write_phy_reg(pi, 0x91, rfctrlintc_override_val); + write_phy_reg(pi, 0x92, rfctrlintc_override_val); + } else { + write_phy_reg(pi, 0x91, pi->rfctrlIntc1_save); + write_phy_reg(pi, 0x92, pi->rfctrlIntc2_save); + } + +} + +void wlc_phy_stf_chain_upd_nphy(struct brcms_phy *pi) +{ + + u16 txrx_chain = + (NPHY_RfseqCoreActv_TxRxChain0 | NPHY_RfseqCoreActv_TxRxChain1); + bool CoreActv_override = false; + + if (pi->nphy_txrx_chain == BRCMS_N_TXRX_CHAIN0) { + txrx_chain = NPHY_RfseqCoreActv_TxRxChain0; + CoreActv_override = true; + + if (NREV_LE(pi->pubpi.phy_rev, 2)) + and_phy_reg(pi, 0xa0, ~0x20); + } else if (pi->nphy_txrx_chain == BRCMS_N_TXRX_CHAIN1) { + txrx_chain = NPHY_RfseqCoreActv_TxRxChain1; + CoreActv_override = true; + + if (NREV_LE(pi->pubpi.phy_rev, 2)) + or_phy_reg(pi, 0xa0, 0x20); + } + + mod_phy_reg(pi, 0xa2, ((0xf << 0) | (0xf << 4)), txrx_chain); + + if (CoreActv_override) { + pi->nphy_perical = PHY_PERICAL_DISABLE; + or_phy_reg(pi, 0xa1, NPHY_RfseqMode_CoreActv_override); + } else { + pi->nphy_perical = PHY_PERICAL_MPHASE; + and_phy_reg(pi, 0xa1, ~NPHY_RfseqMode_CoreActv_override); + } +} + +void wlc_phy_rxcore_setstate_nphy(struct brcms_phy_pub *pih, u8 rxcore_bitmask) +{ + u16 regval; + u16 tbl_buf[16]; + uint i; + struct brcms_phy *pi = (struct brcms_phy *) pih; + u16 tbl_opcode; + bool suspend; + + pi->sh->phyrxchain = rxcore_bitmask; + + if (!pi->sh->clk) + return; + + suspend = (0 == (bcma_read32(pi->d11core, D11REGOFFS(maccontrol)) & + MCTL_EN_MAC)); + if (!suspend) + wlapi_suspend_mac_and_wait(pi->sh->physhim); + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + regval = read_phy_reg(pi, 0xa2); + regval &= ~(0xf << 4); + regval |= ((u16) (rxcore_bitmask & 0x3)) << 4; + write_phy_reg(pi, 0xa2, regval); + + if ((rxcore_bitmask & 0x3) != 0x3) { + + write_phy_reg(pi, 0x20e, 1); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + if (pi->rx2tx_biasentry == -1) { + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_RFSEQ, + ARRAY_SIZE(tbl_buf), 80, + 16, tbl_buf); + + for (i = 0; i < ARRAY_SIZE(tbl_buf); i++) { + if (tbl_buf[i] == + NPHY_REV3_RFSEQ_CMD_CLR_RXRX_BIAS) { + pi->rx2tx_biasentry = (u8) i; + tbl_opcode = + NPHY_REV3_RFSEQ_CMD_NOP; + wlc_phy_table_write_nphy( + pi, + NPHY_TBL_ID_RFSEQ, + 1, i, + 16, + &tbl_opcode); + break; + } else if (tbl_buf[i] == + NPHY_REV3_RFSEQ_CMD_END) + break; + } + } + } + } else { + + write_phy_reg(pi, 0x20e, 30); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + if (pi->rx2tx_biasentry != -1) { + tbl_opcode = NPHY_REV3_RFSEQ_CMD_CLR_RXRX_BIAS; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, + 1, pi->rx2tx_biasentry, + 16, &tbl_opcode); + pi->rx2tx_biasentry = -1; + } + } + } + + wlc_phy_force_rfseq_nphy(pi, NPHY_RFSEQ_RESET2RX); + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, false); + + if (!suspend) + wlapi_enable_mac(pi->sh->physhim); +} + +u8 wlc_phy_rxcore_getstate_nphy(struct brcms_phy_pub *pih) +{ + u16 regval, rxen_bits; + struct brcms_phy *pi = (struct brcms_phy *) pih; + + regval = read_phy_reg(pi, 0xa2); + rxen_bits = (regval >> 4) & 0xf; + + return (u8) rxen_bits; +} + +bool wlc_phy_n_txpower_ipa_ison(struct brcms_phy *pi) +{ + return PHY_IPA(pi); +} + +void wlc_phy_cal_init_nphy(struct brcms_phy *pi) +{ +} + +static void wlc_phy_radio_preinit_205x(struct brcms_phy *pi) +{ + + and_phy_reg(pi, 0x78, ~RFCC_CHIP0_PU); + and_phy_reg(pi, 0x78, RFCC_OE_POR_FORCE); + + or_phy_reg(pi, 0x78, ~RFCC_OE_POR_FORCE); + or_phy_reg(pi, 0x78, RFCC_CHIP0_PU); + +} + +static void wlc_phy_radio_init_2057(struct brcms_phy *pi) +{ + struct radio_20xx_regs *regs_2057_ptr = NULL; + + if (NREV_IS(pi->pubpi.phy_rev, 7)) { + regs_2057_ptr = regs_2057_rev4; + } else if (NREV_IS(pi->pubpi.phy_rev, 8) + || NREV_IS(pi->pubpi.phy_rev, 9)) { + switch (pi->pubpi.radiorev) { + case 5: + + if (NREV_IS(pi->pubpi.phy_rev, 8)) + regs_2057_ptr = regs_2057_rev5; + else if (NREV_IS(pi->pubpi.phy_rev, 9)) + regs_2057_ptr = regs_2057_rev5v1; + break; + + case 7: + + regs_2057_ptr = regs_2057_rev7; + break; + + case 8: + + regs_2057_ptr = regs_2057_rev8; + break; + + default: + break; + } + } + + wlc_phy_init_radio_regs_allbands(pi, regs_2057_ptr); +} + +static u16 wlc_phy_radio205x_rcal(struct brcms_phy *pi) +{ + u16 rcal_reg = 0; + int i; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + if (pi->pubpi.radiorev == 5) { + + and_phy_reg(pi, 0x342, ~(0x1 << 1)); + + udelay(10); + + mod_radio_reg(pi, RADIO_2057_IQTEST_SEL_PU, 0x1, 0x1); + mod_radio_reg(pi, RADIO_2057v7_IQTEST_SEL_PU2, 0x2, + 0x1); + } + mod_radio_reg(pi, RADIO_2057_RCAL_CONFIG, 0x1, 0x1); + + udelay(10); + + mod_radio_reg(pi, RADIO_2057_RCAL_CONFIG, 0x3, 0x3); + + for (i = 0; i < MAX_205x_RCAL_WAITLOOPS; i++) { + rcal_reg = read_radio_reg(pi, RADIO_2057_RCAL_STATUS); + if (rcal_reg & 0x1) + break; + + udelay(100); + } + + if (WARN(i == MAX_205x_RCAL_WAITLOOPS, + "HW error: radio calib2")) + return 0; + + mod_radio_reg(pi, RADIO_2057_RCAL_CONFIG, 0x2, 0x0); + + rcal_reg = read_radio_reg(pi, RADIO_2057_RCAL_STATUS) & 0x3e; + + mod_radio_reg(pi, RADIO_2057_RCAL_CONFIG, 0x1, 0x0); + if (pi->pubpi.radiorev == 5) { + + mod_radio_reg(pi, RADIO_2057_IQTEST_SEL_PU, 0x1, 0x0); + mod_radio_reg(pi, RADIO_2057v7_IQTEST_SEL_PU2, 0x2, + 0x0); + } + + if ((pi->pubpi.radiorev <= 4) || (pi->pubpi.radiorev == 6)) { + + mod_radio_reg(pi, RADIO_2057_TEMPSENSE_CONFIG, 0x3c, + rcal_reg); + mod_radio_reg(pi, RADIO_2057_BANDGAP_RCAL_TRIM, 0xf0, + rcal_reg << 2); + } + + } else if (NREV_IS(pi->pubpi.phy_rev, 3)) { + u16 savereg; + + savereg = + read_radio_reg( + pi, + RADIO_2056_SYN_PLL_MAST2 | + RADIO_2056_SYN); + write_radio_reg(pi, RADIO_2056_SYN_PLL_MAST2 | RADIO_2056_SYN, + savereg | 0x7); + udelay(10); + + write_radio_reg(pi, RADIO_2056_SYN_RCAL_MASTER | RADIO_2056_SYN, + 0x1); + udelay(10); + + write_radio_reg(pi, RADIO_2056_SYN_RCAL_MASTER | RADIO_2056_SYN, + 0x9); + + for (i = 0; i < MAX_205x_RCAL_WAITLOOPS; i++) { + rcal_reg = read_radio_reg( + pi, + RADIO_2056_SYN_RCAL_CODE_OUT | + RADIO_2056_SYN); + if (rcal_reg & 0x80) + break; + + udelay(100); + } + + if (WARN(i == MAX_205x_RCAL_WAITLOOPS, + "HW error: radio calib3")) + return 0; + + write_radio_reg(pi, RADIO_2056_SYN_RCAL_MASTER | RADIO_2056_SYN, + 0x1); + + rcal_reg = + read_radio_reg(pi, + RADIO_2056_SYN_RCAL_CODE_OUT | + RADIO_2056_SYN); + + write_radio_reg(pi, RADIO_2056_SYN_RCAL_MASTER | RADIO_2056_SYN, + 0x0); + + write_radio_reg(pi, RADIO_2056_SYN_PLL_MAST2 | RADIO_2056_SYN, + savereg); + + return rcal_reg & 0x1f; + } + return rcal_reg & 0x3e; +} + +static u16 wlc_phy_radio2057_rccal(struct brcms_phy *pi) +{ + u16 rccal_valid; + int i; + bool chip43226_6362A0; + + chip43226_6362A0 = ((pi->pubpi.radiorev == 3) + || (pi->pubpi.radiorev == 4) + || (pi->pubpi.radiorev == 6)); + + rccal_valid = 0; + if (chip43226_6362A0) { + write_radio_reg(pi, RADIO_2057_RCCAL_MASTER, 0x61); + write_radio_reg(pi, RADIO_2057_RCCAL_TRC0, 0xc0); + } else { + write_radio_reg(pi, RADIO_2057v7_RCCAL_MASTER, 0x61); + + write_radio_reg(pi, RADIO_2057_RCCAL_TRC0, 0xe9); + } + write_radio_reg(pi, RADIO_2057_RCCAL_X1, 0x6e); + write_radio_reg(pi, RADIO_2057_RCCAL_START_R1_Q1_P1, 0x55); + + for (i = 0; i < MAX_205x_RCAL_WAITLOOPS; i++) { + rccal_valid = read_radio_reg(pi, RADIO_2057_RCCAL_DONE_OSCCAP); + if (rccal_valid & 0x2) + break; + + udelay(500); + } + + write_radio_reg(pi, RADIO_2057_RCCAL_START_R1_Q1_P1, 0x15); + + rccal_valid = 0; + if (chip43226_6362A0) { + write_radio_reg(pi, RADIO_2057_RCCAL_MASTER, 0x69); + write_radio_reg(pi, RADIO_2057_RCCAL_TRC0, 0xb0); + } else { + write_radio_reg(pi, RADIO_2057v7_RCCAL_MASTER, 0x69); + + write_radio_reg(pi, RADIO_2057_RCCAL_TRC0, 0xd5); + } + write_radio_reg(pi, RADIO_2057_RCCAL_X1, 0x6e); + write_radio_reg(pi, RADIO_2057_RCCAL_START_R1_Q1_P1, 0x55); + + for (i = 0; i < MAX_205x_RCAL_WAITLOOPS; i++) { + rccal_valid = read_radio_reg(pi, RADIO_2057_RCCAL_DONE_OSCCAP); + if (rccal_valid & 0x2) + break; + + udelay(500); + } + + write_radio_reg(pi, RADIO_2057_RCCAL_START_R1_Q1_P1, 0x15); + + rccal_valid = 0; + if (chip43226_6362A0) { + write_radio_reg(pi, RADIO_2057_RCCAL_MASTER, 0x73); + + write_radio_reg(pi, RADIO_2057_RCCAL_X1, 0x28); + write_radio_reg(pi, RADIO_2057_RCCAL_TRC0, 0xb0); + } else { + write_radio_reg(pi, RADIO_2057v7_RCCAL_MASTER, 0x73); + write_radio_reg(pi, RADIO_2057_RCCAL_X1, 0x6e); + write_radio_reg(pi, RADIO_2057_RCCAL_TRC0, 0x99); + } + write_radio_reg(pi, RADIO_2057_RCCAL_START_R1_Q1_P1, 0x55); + + for (i = 0; i < MAX_205x_RCAL_WAITLOOPS; i++) { + rccal_valid = read_radio_reg(pi, RADIO_2057_RCCAL_DONE_OSCCAP); + if (rccal_valid & 0x2) + break; + + udelay(500); + } + + if (WARN(!(rccal_valid & 0x2), "HW error: radio calib4")) + return 0; + + write_radio_reg(pi, RADIO_2057_RCCAL_START_R1_Q1_P1, 0x15); + + return rccal_valid; +} + +static void wlc_phy_radio_postinit_2057(struct brcms_phy *pi) +{ + + mod_radio_reg(pi, RADIO_2057_XTALPUOVR_PINCTRL, 0x1, 0x1); + + mod_radio_reg(pi, RADIO_2057_RFPLL_MISC_CAL_RESETN, 0x78, 0x78); + mod_radio_reg(pi, RADIO_2057_XTAL_CONFIG2, 0x80, 0x80); + mdelay(2); + mod_radio_reg(pi, RADIO_2057_RFPLL_MISC_CAL_RESETN, 0x78, 0x0); + mod_radio_reg(pi, RADIO_2057_XTAL_CONFIG2, 0x80, 0x0); + + if (pi->phy_init_por) { + wlc_phy_radio205x_rcal(pi); + wlc_phy_radio2057_rccal(pi); + } + + mod_radio_reg(pi, RADIO_2057_RFPLL_MASTER, 0x8, 0x0); +} + +static void wlc_phy_radio_init_2056(struct brcms_phy *pi) +{ + const struct radio_regs *regs_SYN_2056_ptr = NULL; + const struct radio_regs *regs_TX_2056_ptr = NULL; + const struct radio_regs *regs_RX_2056_ptr = NULL; + + if (NREV_IS(pi->pubpi.phy_rev, 3)) { + regs_SYN_2056_ptr = regs_SYN_2056; + regs_TX_2056_ptr = regs_TX_2056; + regs_RX_2056_ptr = regs_RX_2056; + } else if (NREV_IS(pi->pubpi.phy_rev, 4)) { + regs_SYN_2056_ptr = regs_SYN_2056_A1; + regs_TX_2056_ptr = regs_TX_2056_A1; + regs_RX_2056_ptr = regs_RX_2056_A1; + } else { + switch (pi->pubpi.radiorev) { + case 5: + regs_SYN_2056_ptr = regs_SYN_2056_rev5; + regs_TX_2056_ptr = regs_TX_2056_rev5; + regs_RX_2056_ptr = regs_RX_2056_rev5; + break; + + case 6: + regs_SYN_2056_ptr = regs_SYN_2056_rev6; + regs_TX_2056_ptr = regs_TX_2056_rev6; + regs_RX_2056_ptr = regs_RX_2056_rev6; + break; + + case 7: + case 9: + regs_SYN_2056_ptr = regs_SYN_2056_rev7; + regs_TX_2056_ptr = regs_TX_2056_rev7; + regs_RX_2056_ptr = regs_RX_2056_rev7; + break; + + case 8: + regs_SYN_2056_ptr = regs_SYN_2056_rev8; + regs_TX_2056_ptr = regs_TX_2056_rev8; + regs_RX_2056_ptr = regs_RX_2056_rev8; + break; + + case 11: + regs_SYN_2056_ptr = regs_SYN_2056_rev11; + regs_TX_2056_ptr = regs_TX_2056_rev11; + regs_RX_2056_ptr = regs_RX_2056_rev11; + break; + + default: + break; + } + } + + wlc_phy_init_radio_regs(pi, regs_SYN_2056_ptr, (u16) RADIO_2056_SYN); + + wlc_phy_init_radio_regs(pi, regs_TX_2056_ptr, (u16) RADIO_2056_TX0); + + wlc_phy_init_radio_regs(pi, regs_TX_2056_ptr, (u16) RADIO_2056_TX1); + + wlc_phy_init_radio_regs(pi, regs_RX_2056_ptr, (u16) RADIO_2056_RX0); + + wlc_phy_init_radio_regs(pi, regs_RX_2056_ptr, (u16) RADIO_2056_RX1); +} + +static void wlc_phy_radio_postinit_2056(struct brcms_phy *pi) +{ + mod_radio_reg(pi, RADIO_2056_SYN_COM_CTRL, 0xb, 0xb); + + mod_radio_reg(pi, RADIO_2056_SYN_COM_PU, 0x2, 0x2); + mod_radio_reg(pi, RADIO_2056_SYN_COM_RESET, 0x2, 0x2); + udelay(1000); + mod_radio_reg(pi, RADIO_2056_SYN_COM_RESET, 0x2, 0x0); + + if ((pi->sh->boardflags2 & BFL2_LEGACY) + || (pi->sh->boardflags2 & BFL2_XTALBUFOUTEN)) + mod_radio_reg(pi, RADIO_2056_SYN_PLL_MAST2, 0xf4, 0x0); + else + mod_radio_reg(pi, RADIO_2056_SYN_PLL_MAST2, 0xfc, 0x0); + + mod_radio_reg(pi, RADIO_2056_SYN_RCCAL_CTRL0, 0x1, 0x0); + + if (pi->phy_init_por) + wlc_phy_radio205x_rcal(pi); +} + +static void wlc_phy_radio_preinit_2055(struct brcms_phy *pi) +{ + + and_phy_reg(pi, 0x78, ~RFCC_POR_FORCE); + or_phy_reg(pi, 0x78, RFCC_CHIP0_PU | RFCC_OE_POR_FORCE); + + or_phy_reg(pi, 0x78, RFCC_POR_FORCE); +} + +static void wlc_phy_radio_init_2055(struct brcms_phy *pi) +{ + wlc_phy_init_radio_regs(pi, regs_2055, RADIO_DEFAULT_CORE); +} + +static void wlc_phy_radio_postinit_2055(struct brcms_phy *pi) +{ + + and_radio_reg(pi, RADIO_2055_MASTER_CNTRL1, + ~(RADIO_2055_JTAGCTRL_MASK | RADIO_2055_JTAGSYNC_MASK)); + + if (((pi->sh->sromrev >= 4) + && !(pi->sh->boardflags2 & BFL2_RXBB_INT_REG_DIS)) + || ((pi->sh->sromrev < 4))) { + and_radio_reg(pi, RADIO_2055_CORE1_RXBB_REGULATOR, 0x7F); + and_radio_reg(pi, RADIO_2055_CORE2_RXBB_REGULATOR, 0x7F); + } + + mod_radio_reg(pi, RADIO_2055_RRCCAL_N_OPT_SEL, 0x3F, 0x2C); + write_radio_reg(pi, RADIO_2055_CAL_MISC, 0x3C); + + and_radio_reg(pi, RADIO_2055_CAL_MISC, + ~(RADIO_2055_RRCAL_START | RADIO_2055_RRCAL_RST_N)); + + or_radio_reg(pi, RADIO_2055_CAL_LPO_CNTRL, RADIO_2055_CAL_LPO_ENABLE); + + or_radio_reg(pi, RADIO_2055_CAL_MISC, RADIO_2055_RRCAL_RST_N); + + udelay(1000); + + or_radio_reg(pi, RADIO_2055_CAL_MISC, RADIO_2055_RRCAL_START); + + SPINWAIT(((read_radio_reg(pi, RADIO_2055_CAL_COUNTER_OUT2) & + RADIO_2055_RCAL_DONE) != RADIO_2055_RCAL_DONE), 2000); + + if (WARN((read_radio_reg(pi, RADIO_2055_CAL_COUNTER_OUT2) & + RADIO_2055_RCAL_DONE) != RADIO_2055_RCAL_DONE, + "HW error: radio calibration1\n")) + return; + + and_radio_reg(pi, RADIO_2055_CAL_LPO_CNTRL, + ~(RADIO_2055_CAL_LPO_ENABLE)); + + wlc_phy_chanspec_set((struct brcms_phy_pub *) pi, pi->radio_chanspec); + + write_radio_reg(pi, RADIO_2055_CORE1_RXBB_LPF, 9); + write_radio_reg(pi, RADIO_2055_CORE2_RXBB_LPF, 9); + + write_radio_reg(pi, RADIO_2055_CORE1_RXBB_MIDAC_HIPAS, 0x83); + write_radio_reg(pi, RADIO_2055_CORE2_RXBB_MIDAC_HIPAS, 0x83); + + mod_radio_reg(pi, RADIO_2055_CORE1_LNA_GAINBST, + RADIO_2055_GAINBST_VAL_MASK, RADIO_2055_GAINBST_CODE); + mod_radio_reg(pi, RADIO_2055_CORE2_LNA_GAINBST, + RADIO_2055_GAINBST_VAL_MASK, RADIO_2055_GAINBST_CODE); + if (pi->nphy_gain_boost) { + and_radio_reg(pi, RADIO_2055_CORE1_RXRF_SPC1, + ~(RADIO_2055_GAINBST_DISABLE)); + and_radio_reg(pi, RADIO_2055_CORE2_RXRF_SPC1, + ~(RADIO_2055_GAINBST_DISABLE)); + } else { + or_radio_reg(pi, RADIO_2055_CORE1_RXRF_SPC1, + RADIO_2055_GAINBST_DISABLE); + or_radio_reg(pi, RADIO_2055_CORE2_RXRF_SPC1, + RADIO_2055_GAINBST_DISABLE); + } + + udelay(2); +} + +void wlc_phy_switch_radio_nphy(struct brcms_phy *pi, bool on) +{ + if (on) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + if (!pi->radio_is_on) { + wlc_phy_radio_preinit_205x(pi); + wlc_phy_radio_init_2057(pi); + wlc_phy_radio_postinit_2057(pi); + } + + wlc_phy_chanspec_set((struct brcms_phy_pub *) pi, + pi->radio_chanspec); + } else if (NREV_GE(pi->pubpi.phy_rev, 3)) { + wlc_phy_radio_preinit_205x(pi); + wlc_phy_radio_init_2056(pi); + wlc_phy_radio_postinit_2056(pi); + + wlc_phy_chanspec_set((struct brcms_phy_pub *) pi, + pi->radio_chanspec); + } else { + wlc_phy_radio_preinit_2055(pi); + wlc_phy_radio_init_2055(pi); + wlc_phy_radio_postinit_2055(pi); + } + + pi->radio_is_on = true; + + } else { + + if (NREV_GE(pi->pubpi.phy_rev, 3) + && NREV_LT(pi->pubpi.phy_rev, 7)) { + and_phy_reg(pi, 0x78, ~RFCC_CHIP0_PU); + mod_radio_reg(pi, RADIO_2056_SYN_COM_PU, 0x2, 0x0); + + write_radio_reg(pi, + RADIO_2056_TX_PADA_BOOST_TUNE | + RADIO_2056_TX0, 0); + write_radio_reg(pi, + RADIO_2056_TX_PADG_BOOST_TUNE | + RADIO_2056_TX0, 0); + write_radio_reg(pi, + RADIO_2056_TX_PGAA_BOOST_TUNE | + RADIO_2056_TX0, 0); + write_radio_reg(pi, + RADIO_2056_TX_PGAG_BOOST_TUNE | + RADIO_2056_TX0, 0); + mod_radio_reg(pi, + RADIO_2056_TX_MIXA_BOOST_TUNE | + RADIO_2056_TX0, 0xf0, 0); + write_radio_reg(pi, + RADIO_2056_TX_MIXG_BOOST_TUNE | + RADIO_2056_TX0, 0); + + write_radio_reg(pi, + RADIO_2056_TX_PADA_BOOST_TUNE | + RADIO_2056_TX1, 0); + write_radio_reg(pi, + RADIO_2056_TX_PADG_BOOST_TUNE | + RADIO_2056_TX1, 0); + write_radio_reg(pi, + RADIO_2056_TX_PGAA_BOOST_TUNE | + RADIO_2056_TX1, 0); + write_radio_reg(pi, + RADIO_2056_TX_PGAG_BOOST_TUNE | + RADIO_2056_TX1, 0); + mod_radio_reg(pi, + RADIO_2056_TX_MIXA_BOOST_TUNE | + RADIO_2056_TX1, 0xf0, 0); + write_radio_reg(pi, + RADIO_2056_TX_MIXG_BOOST_TUNE | + RADIO_2056_TX1, 0); + + pi->radio_is_on = false; + } + + if (NREV_GE(pi->pubpi.phy_rev, 8)) { + and_phy_reg(pi, 0x78, ~RFCC_CHIP0_PU); + pi->radio_is_on = false; + } + + } +} + +static bool +wlc_phy_chan2freq_nphy(struct brcms_phy *pi, uint channel, int *f, + const struct chan_info_nphy_radio2057 **t0, + const struct chan_info_nphy_radio205x **t1, + const struct chan_info_nphy_radio2057_rev5 **t2, + const struct chan_info_nphy_2055 **t3) +{ + uint i; + const struct chan_info_nphy_radio2057 *chan_info_tbl_p_0 = NULL; + const struct chan_info_nphy_radio205x *chan_info_tbl_p_1 = NULL; + const struct chan_info_nphy_radio2057_rev5 *chan_info_tbl_p_2 = NULL; + u32 tbl_len = 0; + + int freq = 0; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + if (NREV_IS(pi->pubpi.phy_rev, 7)) { + + chan_info_tbl_p_0 = chan_info_nphyrev7_2057_rev4; + tbl_len = ARRAY_SIZE(chan_info_nphyrev7_2057_rev4); + + } else if (NREV_IS(pi->pubpi.phy_rev, 8) + || NREV_IS(pi->pubpi.phy_rev, 9)) { + switch (pi->pubpi.radiorev) { + + case 5: + + if (pi->pubpi.radiover == 0x0) { + + chan_info_tbl_p_2 = + chan_info_nphyrev8_2057_rev5; + tbl_len = ARRAY_SIZE( + chan_info_nphyrev8_2057_rev5); + + } else if (pi->pubpi.radiover == 0x1) { + + chan_info_tbl_p_2 = + chan_info_nphyrev9_2057_rev5v1; + tbl_len = ARRAY_SIZE( + chan_info_nphyrev9_2057_rev5v1); + + } + break; + + case 7: + chan_info_tbl_p_0 = + chan_info_nphyrev8_2057_rev7; + tbl_len = ARRAY_SIZE( + chan_info_nphyrev8_2057_rev7); + break; + + case 8: + chan_info_tbl_p_0 = + chan_info_nphyrev8_2057_rev8; + tbl_len = ARRAY_SIZE( + chan_info_nphyrev8_2057_rev8); + break; + + default: + break; + } + } else if (NREV_IS(pi->pubpi.phy_rev, 16)) { + + chan_info_tbl_p_0 = chan_info_nphyrev8_2057_rev8; + tbl_len = ARRAY_SIZE(chan_info_nphyrev8_2057_rev8); + } else { + goto fail; + } + + for (i = 0; i < tbl_len; i++) { + if (pi->pubpi.radiorev == 5) { + + if (chan_info_tbl_p_2[i].chan == channel) + break; + } else { + + if (chan_info_tbl_p_0[i].chan == channel) + break; + } + } + + if (i >= tbl_len) + goto fail; + + if (pi->pubpi.radiorev == 5) { + *t2 = &chan_info_tbl_p_2[i]; + freq = chan_info_tbl_p_2[i].freq; + } else { + *t0 = &chan_info_tbl_p_0[i]; + freq = chan_info_tbl_p_0[i].freq; + } + + } else if (NREV_GE(pi->pubpi.phy_rev, 3)) { + if (NREV_IS(pi->pubpi.phy_rev, 3)) { + chan_info_tbl_p_1 = chan_info_nphyrev3_2056; + tbl_len = ARRAY_SIZE(chan_info_nphyrev3_2056); + } else if (NREV_IS(pi->pubpi.phy_rev, 4)) { + chan_info_tbl_p_1 = chan_info_nphyrev4_2056_A1; + tbl_len = ARRAY_SIZE(chan_info_nphyrev4_2056_A1); + } else if (NREV_IS(pi->pubpi.phy_rev, 5) + || NREV_IS(pi->pubpi.phy_rev, 6)) { + switch (pi->pubpi.radiorev) { + case 5: + chan_info_tbl_p_1 = chan_info_nphyrev5_2056v5; + tbl_len = ARRAY_SIZE(chan_info_nphyrev5_2056v5); + break; + case 6: + chan_info_tbl_p_1 = chan_info_nphyrev6_2056v6; + tbl_len = ARRAY_SIZE(chan_info_nphyrev6_2056v6); + break; + case 7: + case 9: + chan_info_tbl_p_1 = chan_info_nphyrev5n6_2056v7; + tbl_len = + ARRAY_SIZE(chan_info_nphyrev5n6_2056v7); + break; + case 8: + chan_info_tbl_p_1 = chan_info_nphyrev6_2056v8; + tbl_len = ARRAY_SIZE(chan_info_nphyrev6_2056v8); + break; + case 11: + chan_info_tbl_p_1 = chan_info_nphyrev6_2056v11; + tbl_len = ARRAY_SIZE( + chan_info_nphyrev6_2056v11); + break; + default: + break; + } + } + + for (i = 0; i < tbl_len; i++) { + if (chan_info_tbl_p_1[i].chan == channel) + break; + } + + if (i >= tbl_len) + goto fail; + + *t1 = &chan_info_tbl_p_1[i]; + freq = chan_info_tbl_p_1[i].freq; + + } else { + for (i = 0; i < ARRAY_SIZE(chan_info_nphy_2055); i++) + if (chan_info_nphy_2055[i].chan == channel) + break; + + if (i >= ARRAY_SIZE(chan_info_nphy_2055)) + goto fail; + + *t3 = &chan_info_nphy_2055[i]; + freq = chan_info_nphy_2055[i].freq; + } + + *f = freq; + return true; + +fail: + *f = WL_CHAN_FREQ_RANGE_2G; + return false; +} + +u8 wlc_phy_get_chan_freq_range_nphy(struct brcms_phy *pi, uint channel) +{ + int freq; + const struct chan_info_nphy_radio2057 *t0 = NULL; + const struct chan_info_nphy_radio205x *t1 = NULL; + const struct chan_info_nphy_radio2057_rev5 *t2 = NULL; + const struct chan_info_nphy_2055 *t3 = NULL; + + if (channel == 0) + channel = CHSPEC_CHANNEL(pi->radio_chanspec); + + wlc_phy_chan2freq_nphy(pi, channel, &freq, &t0, &t1, &t2, &t3); + + if (CHSPEC_IS2G(pi->radio_chanspec)) + return WL_CHAN_FREQ_RANGE_2G; + + if ((freq >= BASE_LOW_5G_CHAN) && (freq < BASE_MID_5G_CHAN)) + return WL_CHAN_FREQ_RANGE_5GL; + else if ((freq >= BASE_MID_5G_CHAN) && (freq < BASE_HIGH_5G_CHAN)) + return WL_CHAN_FREQ_RANGE_5GM; + else + return WL_CHAN_FREQ_RANGE_5GH; +} + +static void +wlc_phy_chanspec_radio2055_setup(struct brcms_phy *pi, + const struct chan_info_nphy_2055 *ci) +{ + + write_radio_reg(pi, RADIO_2055_PLL_REF, ci->RF_pll_ref); + write_radio_reg(pi, RADIO_2055_RF_PLL_MOD0, ci->RF_rf_pll_mod0); + write_radio_reg(pi, RADIO_2055_RF_PLL_MOD1, ci->RF_rf_pll_mod1); + write_radio_reg(pi, RADIO_2055_VCO_CAP_TAIL, ci->RF_vco_cap_tail); + + BRCMS_PHY_WAR_PR51571(pi); + + write_radio_reg(pi, RADIO_2055_VCO_CAL1, ci->RF_vco_cal1); + write_radio_reg(pi, RADIO_2055_VCO_CAL2, ci->RF_vco_cal2); + write_radio_reg(pi, RADIO_2055_PLL_LF_C1, ci->RF_pll_lf_c1); + write_radio_reg(pi, RADIO_2055_PLL_LF_R1, ci->RF_pll_lf_r1); + + BRCMS_PHY_WAR_PR51571(pi); + + write_radio_reg(pi, RADIO_2055_PLL_LF_C2, ci->RF_pll_lf_c2); + write_radio_reg(pi, RADIO_2055_LGBUF_CEN_BUF, ci->RF_lgbuf_cen_buf); + write_radio_reg(pi, RADIO_2055_LGEN_TUNE1, ci->RF_lgen_tune1); + write_radio_reg(pi, RADIO_2055_LGEN_TUNE2, ci->RF_lgen_tune2); + + BRCMS_PHY_WAR_PR51571(pi); + + write_radio_reg(pi, RADIO_2055_CORE1_LGBUF_A_TUNE, + ci->RF_core1_lgbuf_a_tune); + write_radio_reg(pi, RADIO_2055_CORE1_LGBUF_G_TUNE, + ci->RF_core1_lgbuf_g_tune); + write_radio_reg(pi, RADIO_2055_CORE1_RXRF_REG1, ci->RF_core1_rxrf_reg1); + write_radio_reg(pi, RADIO_2055_CORE1_TX_PGA_PAD_TN, + ci->RF_core1_tx_pga_pad_tn); + + BRCMS_PHY_WAR_PR51571(pi); + + write_radio_reg(pi, RADIO_2055_CORE1_TX_MX_BGTRIM, + ci->RF_core1_tx_mx_bgtrim); + write_radio_reg(pi, RADIO_2055_CORE2_LGBUF_A_TUNE, + ci->RF_core2_lgbuf_a_tune); + write_radio_reg(pi, RADIO_2055_CORE2_LGBUF_G_TUNE, + ci->RF_core2_lgbuf_g_tune); + write_radio_reg(pi, RADIO_2055_CORE2_RXRF_REG1, ci->RF_core2_rxrf_reg1); + + BRCMS_PHY_WAR_PR51571(pi); + + write_radio_reg(pi, RADIO_2055_CORE2_TX_PGA_PAD_TN, + ci->RF_core2_tx_pga_pad_tn); + write_radio_reg(pi, RADIO_2055_CORE2_TX_MX_BGTRIM, + ci->RF_core2_tx_mx_bgtrim); + + udelay(50); + + write_radio_reg(pi, RADIO_2055_VCO_CAL10, 0x05); + write_radio_reg(pi, RADIO_2055_VCO_CAL10, 0x45); + + BRCMS_PHY_WAR_PR51571(pi); + + write_radio_reg(pi, RADIO_2055_VCO_CAL10, 0x65); + + udelay(300); +} + +static void +wlc_phy_chanspec_radio2056_setup(struct brcms_phy *pi, + const struct chan_info_nphy_radio205x *ci) +{ + const struct radio_regs *regs_SYN_2056_ptr = NULL; + + write_radio_reg(pi, + RADIO_2056_SYN_PLL_VCOCAL1 | RADIO_2056_SYN, + ci->RF_SYN_pll_vcocal1); + write_radio_reg(pi, RADIO_2056_SYN_PLL_VCOCAL2 | RADIO_2056_SYN, + ci->RF_SYN_pll_vcocal2); + write_radio_reg(pi, RADIO_2056_SYN_PLL_REFDIV | RADIO_2056_SYN, + ci->RF_SYN_pll_refdiv); + write_radio_reg(pi, RADIO_2056_SYN_PLL_MMD2 | RADIO_2056_SYN, + ci->RF_SYN_pll_mmd2); + write_radio_reg(pi, RADIO_2056_SYN_PLL_MMD1 | RADIO_2056_SYN, + ci->RF_SYN_pll_mmd1); + write_radio_reg(pi, RADIO_2056_SYN_PLL_LOOPFILTER1 | RADIO_2056_SYN, + ci->RF_SYN_pll_loopfilter1); + write_radio_reg(pi, RADIO_2056_SYN_PLL_LOOPFILTER2 | RADIO_2056_SYN, + ci->RF_SYN_pll_loopfilter2); + write_radio_reg(pi, RADIO_2056_SYN_PLL_LOOPFILTER3 | RADIO_2056_SYN, + ci->RF_SYN_pll_loopfilter3); + write_radio_reg(pi, RADIO_2056_SYN_PLL_LOOPFILTER4 | RADIO_2056_SYN, + ci->RF_SYN_pll_loopfilter4); + write_radio_reg(pi, RADIO_2056_SYN_PLL_LOOPFILTER5 | RADIO_2056_SYN, + ci->RF_SYN_pll_loopfilter5); + write_radio_reg(pi, RADIO_2056_SYN_RESERVED_ADDR27 | RADIO_2056_SYN, + ci->RF_SYN_reserved_addr27); + write_radio_reg(pi, RADIO_2056_SYN_RESERVED_ADDR28 | RADIO_2056_SYN, + ci->RF_SYN_reserved_addr28); + write_radio_reg(pi, RADIO_2056_SYN_RESERVED_ADDR29 | RADIO_2056_SYN, + ci->RF_SYN_reserved_addr29); + write_radio_reg(pi, RADIO_2056_SYN_LOGEN_VCOBUF1 | RADIO_2056_SYN, + ci->RF_SYN_logen_VCOBUF1); + write_radio_reg(pi, RADIO_2056_SYN_LOGEN_MIXER2 | RADIO_2056_SYN, + ci->RF_SYN_logen_MIXER2); + write_radio_reg(pi, RADIO_2056_SYN_LOGEN_BUF3 | RADIO_2056_SYN, + ci->RF_SYN_logen_BUF3); + write_radio_reg(pi, RADIO_2056_SYN_LOGEN_BUF4 | RADIO_2056_SYN, + ci->RF_SYN_logen_BUF4); + + write_radio_reg(pi, + RADIO_2056_RX_LNAA_TUNE | RADIO_2056_RX0, + ci->RF_RX0_lnaa_tune); + write_radio_reg(pi, RADIO_2056_RX_LNAG_TUNE | RADIO_2056_RX0, + ci->RF_RX0_lnag_tune); + write_radio_reg(pi, RADIO_2056_TX_INTPAA_BOOST_TUNE | RADIO_2056_TX0, + ci->RF_TX0_intpaa_boost_tune); + write_radio_reg(pi, RADIO_2056_TX_INTPAG_BOOST_TUNE | RADIO_2056_TX0, + ci->RF_TX0_intpag_boost_tune); + write_radio_reg(pi, RADIO_2056_TX_PADA_BOOST_TUNE | RADIO_2056_TX0, + ci->RF_TX0_pada_boost_tune); + write_radio_reg(pi, RADIO_2056_TX_PADG_BOOST_TUNE | RADIO_2056_TX0, + ci->RF_TX0_padg_boost_tune); + write_radio_reg(pi, RADIO_2056_TX_PGAA_BOOST_TUNE | RADIO_2056_TX0, + ci->RF_TX0_pgaa_boost_tune); + write_radio_reg(pi, RADIO_2056_TX_PGAG_BOOST_TUNE | RADIO_2056_TX0, + ci->RF_TX0_pgag_boost_tune); + write_radio_reg(pi, RADIO_2056_TX_MIXA_BOOST_TUNE | RADIO_2056_TX0, + ci->RF_TX0_mixa_boost_tune); + write_radio_reg(pi, RADIO_2056_TX_MIXG_BOOST_TUNE | RADIO_2056_TX0, + ci->RF_TX0_mixg_boost_tune); + + write_radio_reg(pi, + RADIO_2056_RX_LNAA_TUNE | RADIO_2056_RX1, + ci->RF_RX1_lnaa_tune); + write_radio_reg(pi, RADIO_2056_RX_LNAG_TUNE | RADIO_2056_RX1, + ci->RF_RX1_lnag_tune); + write_radio_reg(pi, RADIO_2056_TX_INTPAA_BOOST_TUNE | RADIO_2056_TX1, + ci->RF_TX1_intpaa_boost_tune); + write_radio_reg(pi, RADIO_2056_TX_INTPAG_BOOST_TUNE | RADIO_2056_TX1, + ci->RF_TX1_intpag_boost_tune); + write_radio_reg(pi, RADIO_2056_TX_PADA_BOOST_TUNE | RADIO_2056_TX1, + ci->RF_TX1_pada_boost_tune); + write_radio_reg(pi, RADIO_2056_TX_PADG_BOOST_TUNE | RADIO_2056_TX1, + ci->RF_TX1_padg_boost_tune); + write_radio_reg(pi, RADIO_2056_TX_PGAA_BOOST_TUNE | RADIO_2056_TX1, + ci->RF_TX1_pgaa_boost_tune); + write_radio_reg(pi, RADIO_2056_TX_PGAG_BOOST_TUNE | RADIO_2056_TX1, + ci->RF_TX1_pgag_boost_tune); + write_radio_reg(pi, RADIO_2056_TX_MIXA_BOOST_TUNE | RADIO_2056_TX1, + ci->RF_TX1_mixa_boost_tune); + write_radio_reg(pi, RADIO_2056_TX_MIXG_BOOST_TUNE | RADIO_2056_TX1, + ci->RF_TX1_mixg_boost_tune); + + if (NREV_IS(pi->pubpi.phy_rev, 3)) + regs_SYN_2056_ptr = regs_SYN_2056; + else if (NREV_IS(pi->pubpi.phy_rev, 4)) + regs_SYN_2056_ptr = regs_SYN_2056_A1; + else { + switch (pi->pubpi.radiorev) { + case 5: + regs_SYN_2056_ptr = regs_SYN_2056_rev5; + break; + case 6: + regs_SYN_2056_ptr = regs_SYN_2056_rev6; + break; + case 7: + case 9: + regs_SYN_2056_ptr = regs_SYN_2056_rev7; + break; + case 8: + regs_SYN_2056_ptr = regs_SYN_2056_rev8; + break; + case 11: + regs_SYN_2056_ptr = regs_SYN_2056_rev11; + break; + } + } + if (CHSPEC_IS2G(pi->radio_chanspec)) + write_radio_reg(pi, RADIO_2056_SYN_PLL_CP2 | + RADIO_2056_SYN, + (u16) regs_SYN_2056_ptr[0x49 - 2].init_g); + else + write_radio_reg(pi, RADIO_2056_SYN_PLL_CP2 | + RADIO_2056_SYN, + (u16) regs_SYN_2056_ptr[0x49 - 2].init_a); + + if (pi->sh->boardflags2 & BFL2_GPLL_WAR) { + if (CHSPEC_IS2G(pi->radio_chanspec)) { + write_radio_reg(pi, RADIO_2056_SYN_PLL_LOOPFILTER1 | + RADIO_2056_SYN, 0x1f); + write_radio_reg(pi, RADIO_2056_SYN_PLL_LOOPFILTER2 | + RADIO_2056_SYN, 0x1f); + + if ((pi->sh->chip == BCMA_CHIP_ID_BCM4716) || + (pi->sh->chip == BCMA_CHIP_ID_BCM47162)) { + write_radio_reg(pi, + RADIO_2056_SYN_PLL_LOOPFILTER4 | + RADIO_2056_SYN, 0x14); + write_radio_reg(pi, + RADIO_2056_SYN_PLL_CP2 | + RADIO_2056_SYN, 0x00); + } else { + write_radio_reg(pi, + RADIO_2056_SYN_PLL_LOOPFILTER4 | + RADIO_2056_SYN, 0xb); + write_radio_reg(pi, + RADIO_2056_SYN_PLL_CP2 | + RADIO_2056_SYN, 0x14); + } + } + } + + if ((pi->sh->boardflags2 & BFL2_GPLL_WAR2) && + (CHSPEC_IS2G(pi->radio_chanspec))) { + write_radio_reg(pi, + RADIO_2056_SYN_PLL_LOOPFILTER1 | RADIO_2056_SYN, + 0x1f); + write_radio_reg(pi, + RADIO_2056_SYN_PLL_LOOPFILTER2 | RADIO_2056_SYN, + 0x1f); + write_radio_reg(pi, + RADIO_2056_SYN_PLL_LOOPFILTER4 | RADIO_2056_SYN, + 0xb); + write_radio_reg(pi, RADIO_2056_SYN_PLL_CP2 | RADIO_2056_SYN, + 0x20); + } + + if (pi->sh->boardflags2 & BFL2_APLL_WAR) { + if (CHSPEC_IS5G(pi->radio_chanspec)) { + write_radio_reg(pi, RADIO_2056_SYN_PLL_LOOPFILTER1 | + RADIO_2056_SYN, 0x1f); + write_radio_reg(pi, RADIO_2056_SYN_PLL_LOOPFILTER2 | + RADIO_2056_SYN, 0x1f); + write_radio_reg(pi, RADIO_2056_SYN_PLL_LOOPFILTER4 | + RADIO_2056_SYN, 0x5); + write_radio_reg(pi, RADIO_2056_SYN_PLL_CP2 | + RADIO_2056_SYN, 0xc); + } + } + + if (PHY_IPA(pi) && CHSPEC_IS2G(pi->radio_chanspec)) { + u16 pag_boost_tune; + u16 padg_boost_tune; + u16 pgag_boost_tune; + u16 mixg_boost_tune; + u16 bias, cascbias; + uint core; + + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + + if (NREV_GE(pi->pubpi.phy_rev, 5)) { + + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + PADG_IDAC, 0xcc); + + if ((pi->sh->chip == BCMA_CHIP_ID_BCM4716) || + (pi->sh->chip == BCMA_CHIP_ID_BCM47162)) { + bias = 0x40; + cascbias = 0x45; + pag_boost_tune = 0x5; + pgag_boost_tune = 0x33; + padg_boost_tune = 0x77; + mixg_boost_tune = 0x55; + } else { + bias = 0x25; + cascbias = 0x20; + + if ((pi->sh->chip == BCMA_CHIP_ID_BCM43224 || + pi->sh->chip == BCMA_CHIP_ID_BCM43225) && + pi->sh->chippkg == BCMA_PKG_ID_BCM43224_FAB_SMIC) { + bias = 0x2a; + cascbias = 0x38; + } + + pag_boost_tune = 0x4; + pgag_boost_tune = 0x03; + padg_boost_tune = 0x77; + mixg_boost_tune = 0x65; + } + + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAG_IMAIN_STAT, bias); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAG_IAUX_STAT, bias); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAG_CASCBIAS, cascbias); + + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAG_BOOST_TUNE, + pag_boost_tune); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + PGAG_BOOST_TUNE, + pgag_boost_tune); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + PADG_BOOST_TUNE, + padg_boost_tune); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + MIXG_BOOST_TUNE, + mixg_boost_tune); + } else { + + bias = (pi->bw == WL_CHANSPEC_BW_40) ? + 0x40 : 0x20; + + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAG_IMAIN_STAT, bias); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAG_IAUX_STAT, bias); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAG_CASCBIAS, 0x30); + } + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, PA_SPARE1, + 0xee); + } + } + + if (PHY_IPA(pi) && NREV_IS(pi->pubpi.phy_rev, 6) + && CHSPEC_IS5G(pi->radio_chanspec)) { + u16 paa_boost_tune; + u16 pada_boost_tune; + u16 pgaa_boost_tune; + u16 mixa_boost_tune; + u16 freq, pabias, cascbias; + uint core; + + freq = CHAN5G_FREQ(CHSPEC_CHANNEL(pi->radio_chanspec)); + + if (freq < 5150) { + + paa_boost_tune = 0xa; + pada_boost_tune = 0x77; + pgaa_boost_tune = 0xf; + mixa_boost_tune = 0xf; + } else if (freq < 5340) { + + paa_boost_tune = 0x8; + pada_boost_tune = 0x77; + pgaa_boost_tune = 0xfb; + mixa_boost_tune = 0xf; + } else if (freq < 5650) { + + paa_boost_tune = 0x0; + pada_boost_tune = 0x77; + pgaa_boost_tune = 0xb; + mixa_boost_tune = 0xf; + } else { + + paa_boost_tune = 0x0; + pada_boost_tune = 0x77; + if (freq != 5825) + pgaa_boost_tune = -(int)(freq - 18) / 36 + 168; + else + pgaa_boost_tune = 6; + + mixa_boost_tune = 0xf; + } + + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAA_BOOST_TUNE, paa_boost_tune); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + PADA_BOOST_TUNE, pada_boost_tune); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + PGAA_BOOST_TUNE, pgaa_boost_tune); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + MIXA_BOOST_TUNE, mixa_boost_tune); + + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + TXSPARE1, 0x30); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + PA_SPARE2, 0xee); + + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + PADA_CASCBIAS, 0x3); + + cascbias = 0x30; + + if ((pi->sh->chip == BCMA_CHIP_ID_BCM43224 || + pi->sh->chip == BCMA_CHIP_ID_BCM43225) && + pi->sh->chippkg == BCMA_PKG_ID_BCM43224_FAB_SMIC) + cascbias = 0x35; + + pabias = (pi->phy_pabias == 0) ? 0x30 : pi->phy_pabias; + + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAA_IAUX_STAT, pabias); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAA_IMAIN_STAT, pabias); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAA_CASCBIAS, cascbias); + } + } + + udelay(50); + + wlc_phy_radio205x_vcocal_nphy(pi); +} + +void wlc_phy_radio205x_vcocal_nphy(struct brcms_phy *pi) +{ + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + mod_radio_reg(pi, RADIO_2057_RFPLL_MISC_EN, 0x01, 0x0); + mod_radio_reg(pi, RADIO_2057_RFPLL_MISC_CAL_RESETN, 0x04, 0x0); + mod_radio_reg(pi, RADIO_2057_RFPLL_MISC_CAL_RESETN, 0x04, + (1 << 2)); + mod_radio_reg(pi, RADIO_2057_RFPLL_MISC_EN, 0x01, 0x01); + } else if (NREV_GE(pi->pubpi.phy_rev, 3)) { + write_radio_reg(pi, RADIO_2056_SYN_PLL_VCOCAL12, 0x0); + write_radio_reg(pi, RADIO_2056_SYN_PLL_MAST3, 0x38); + write_radio_reg(pi, RADIO_2056_SYN_PLL_MAST3, 0x18); + write_radio_reg(pi, RADIO_2056_SYN_PLL_MAST3, 0x38); + write_radio_reg(pi, RADIO_2056_SYN_PLL_MAST3, 0x39); + } + + udelay(300); +} + +static void +wlc_phy_chanspec_radio2057_setup( + struct brcms_phy *pi, + const struct chan_info_nphy_radio2057 *ci, + const struct chan_info_nphy_radio2057_rev5 * + ci2) +{ + int coreNum; + u16 txmix2g_tune_boost_pu = 0; + u16 pad2g_tune_pus = 0; + + if (pi->pubpi.radiorev == 5) { + + write_radio_reg(pi, + RADIO_2057_VCOCAL_COUNTVAL0, + ci2->RF_vcocal_countval0); + write_radio_reg(pi, RADIO_2057_VCOCAL_COUNTVAL1, + ci2->RF_vcocal_countval1); + write_radio_reg(pi, RADIO_2057_RFPLL_REFMASTER_SPAREXTALSIZE, + ci2->RF_rfpll_refmaster_sparextalsize); + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_R1, + ci2->RF_rfpll_loopfilter_r1); + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_C2, + ci2->RF_rfpll_loopfilter_c2); + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_C1, + ci2->RF_rfpll_loopfilter_c1); + write_radio_reg(pi, RADIO_2057_CP_KPD_IDAC, + ci2->RF_cp_kpd_idac); + write_radio_reg(pi, RADIO_2057_RFPLL_MMD0, ci2->RF_rfpll_mmd0); + write_radio_reg(pi, RADIO_2057_RFPLL_MMD1, ci2->RF_rfpll_mmd1); + write_radio_reg(pi, + RADIO_2057_VCOBUF_TUNE, ci2->RF_vcobuf_tune); + write_radio_reg(pi, + RADIO_2057_LOGEN_MX2G_TUNE, + ci2->RF_logen_mx2g_tune); + write_radio_reg(pi, RADIO_2057_LOGEN_INDBUF2G_TUNE, + ci2->RF_logen_indbuf2g_tune); + + write_radio_reg(pi, + RADIO_2057_TXMIX2G_TUNE_BOOST_PU_CORE0, + ci2->RF_txmix2g_tune_boost_pu_core0); + write_radio_reg(pi, + RADIO_2057_PAD2G_TUNE_PUS_CORE0, + ci2->RF_pad2g_tune_pus_core0); + write_radio_reg(pi, RADIO_2057_LNA2G_TUNE_CORE0, + ci2->RF_lna2g_tune_core0); + + write_radio_reg(pi, + RADIO_2057_TXMIX2G_TUNE_BOOST_PU_CORE1, + ci2->RF_txmix2g_tune_boost_pu_core1); + write_radio_reg(pi, + RADIO_2057_PAD2G_TUNE_PUS_CORE1, + ci2->RF_pad2g_tune_pus_core1); + write_radio_reg(pi, RADIO_2057_LNA2G_TUNE_CORE1, + ci2->RF_lna2g_tune_core1); + + } else { + + write_radio_reg(pi, + RADIO_2057_VCOCAL_COUNTVAL0, + ci->RF_vcocal_countval0); + write_radio_reg(pi, RADIO_2057_VCOCAL_COUNTVAL1, + ci->RF_vcocal_countval1); + write_radio_reg(pi, RADIO_2057_RFPLL_REFMASTER_SPAREXTALSIZE, + ci->RF_rfpll_refmaster_sparextalsize); + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_R1, + ci->RF_rfpll_loopfilter_r1); + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_C2, + ci->RF_rfpll_loopfilter_c2); + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_C1, + ci->RF_rfpll_loopfilter_c1); + write_radio_reg(pi, RADIO_2057_CP_KPD_IDAC, ci->RF_cp_kpd_idac); + write_radio_reg(pi, RADIO_2057_RFPLL_MMD0, ci->RF_rfpll_mmd0); + write_radio_reg(pi, RADIO_2057_RFPLL_MMD1, ci->RF_rfpll_mmd1); + write_radio_reg(pi, RADIO_2057_VCOBUF_TUNE, ci->RF_vcobuf_tune); + write_radio_reg(pi, + RADIO_2057_LOGEN_MX2G_TUNE, + ci->RF_logen_mx2g_tune); + write_radio_reg(pi, RADIO_2057_LOGEN_MX5G_TUNE, + ci->RF_logen_mx5g_tune); + write_radio_reg(pi, RADIO_2057_LOGEN_INDBUF2G_TUNE, + ci->RF_logen_indbuf2g_tune); + write_radio_reg(pi, RADIO_2057_LOGEN_INDBUF5G_TUNE, + ci->RF_logen_indbuf5g_tune); + + write_radio_reg(pi, + RADIO_2057_TXMIX2G_TUNE_BOOST_PU_CORE0, + ci->RF_txmix2g_tune_boost_pu_core0); + write_radio_reg(pi, + RADIO_2057_PAD2G_TUNE_PUS_CORE0, + ci->RF_pad2g_tune_pus_core0); + write_radio_reg(pi, RADIO_2057_PGA_BOOST_TUNE_CORE0, + ci->RF_pga_boost_tune_core0); + write_radio_reg(pi, RADIO_2057_TXMIX5G_BOOST_TUNE_CORE0, + ci->RF_txmix5g_boost_tune_core0); + write_radio_reg(pi, RADIO_2057_PAD5G_TUNE_MISC_PUS_CORE0, + ci->RF_pad5g_tune_misc_pus_core0); + write_radio_reg(pi, RADIO_2057_LNA2G_TUNE_CORE0, + ci->RF_lna2g_tune_core0); + write_radio_reg(pi, RADIO_2057_LNA5G_TUNE_CORE0, + ci->RF_lna5g_tune_core0); + + write_radio_reg(pi, + RADIO_2057_TXMIX2G_TUNE_BOOST_PU_CORE1, + ci->RF_txmix2g_tune_boost_pu_core1); + write_radio_reg(pi, + RADIO_2057_PAD2G_TUNE_PUS_CORE1, + ci->RF_pad2g_tune_pus_core1); + write_radio_reg(pi, RADIO_2057_PGA_BOOST_TUNE_CORE1, + ci->RF_pga_boost_tune_core1); + write_radio_reg(pi, RADIO_2057_TXMIX5G_BOOST_TUNE_CORE1, + ci->RF_txmix5g_boost_tune_core1); + write_radio_reg(pi, RADIO_2057_PAD5G_TUNE_MISC_PUS_CORE1, + ci->RF_pad5g_tune_misc_pus_core1); + write_radio_reg(pi, RADIO_2057_LNA2G_TUNE_CORE1, + ci->RF_lna2g_tune_core1); + write_radio_reg(pi, RADIO_2057_LNA5G_TUNE_CORE1, + ci->RF_lna5g_tune_core1); + } + + if ((pi->pubpi.radiorev <= 4) || (pi->pubpi.radiorev == 6)) { + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_R1, + 0x3f); + write_radio_reg(pi, RADIO_2057_CP_KPD_IDAC, 0x3f); + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_C1, + 0x8); + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_C2, + 0x8); + } else { + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_R1, + 0x1f); + write_radio_reg(pi, RADIO_2057_CP_KPD_IDAC, 0x3f); + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_C1, + 0x8); + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_C2, + 0x8); + } + } else if ((pi->pubpi.radiorev == 5) || (pi->pubpi.radiorev == 7) || + (pi->pubpi.radiorev == 8)) { + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_R1, + 0x1b); + write_radio_reg(pi, RADIO_2057_CP_KPD_IDAC, 0x30); + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_C1, + 0xa); + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_C2, + 0xa); + } else { + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_R1, + 0x1f); + write_radio_reg(pi, RADIO_2057_CP_KPD_IDAC, 0x3f); + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_C1, + 0x8); + write_radio_reg(pi, RADIO_2057_RFPLL_LOOPFILTER_C2, + 0x8); + } + + } + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if (PHY_IPA(pi)) { + if (pi->pubpi.radiorev == 3) + txmix2g_tune_boost_pu = 0x6b; + + if (pi->pubpi.radiorev == 5) + pad2g_tune_pus = 0x73; + + } else { + if (pi->pubpi.radiorev != 5) { + pad2g_tune_pus = 0x3; + + txmix2g_tune_boost_pu = 0x61; + } + } + + for (coreNum = 0; coreNum <= 1; coreNum++) { + + if (txmix2g_tune_boost_pu != 0) + WRITE_RADIO_REG4(pi, RADIO_2057, CORE, coreNum, + TXMIX2G_TUNE_BOOST_PU, + txmix2g_tune_boost_pu); + + if (pad2g_tune_pus != 0) + WRITE_RADIO_REG4(pi, RADIO_2057, CORE, coreNum, + PAD2G_TUNE_PUS, + pad2g_tune_pus); + } + } + + udelay(50); + + wlc_phy_radio205x_vcocal_nphy(pi); +} + +static void +wlc_phy_chanspec_nphy_setup(struct brcms_phy *pi, u16 chanspec, + const struct nphy_sfo_cfg *ci) +{ + u16 val; + + val = read_phy_reg(pi, 0x09) & NPHY_BandControl_currentBand; + if (CHSPEC_IS5G(chanspec) && !val) { + + val = bcma_read16(pi->d11core, D11REGOFFS(psm_phy_hdr_param)); + bcma_write16(pi->d11core, D11REGOFFS(psm_phy_hdr_param), + (val | MAC_PHY_FORCE_CLK)); + + or_phy_reg(pi, (NPHY_TO_BPHY_OFF + BPHY_BB_CONFIG), + (BBCFG_RESETCCA | BBCFG_RESETRX)); + + bcma_write16(pi->d11core, D11REGOFFS(psm_phy_hdr_param), val); + + or_phy_reg(pi, 0x09, NPHY_BandControl_currentBand); + } else if (!CHSPEC_IS5G(chanspec) && val) { + + and_phy_reg(pi, 0x09, ~NPHY_BandControl_currentBand); + + val = bcma_read16(pi->d11core, D11REGOFFS(psm_phy_hdr_param)); + bcma_write16(pi->d11core, D11REGOFFS(psm_phy_hdr_param), + (val | MAC_PHY_FORCE_CLK)); + + and_phy_reg(pi, (NPHY_TO_BPHY_OFF + BPHY_BB_CONFIG), + (u16) (~(BBCFG_RESETCCA | BBCFG_RESETRX))); + + bcma_write16(pi->d11core, D11REGOFFS(psm_phy_hdr_param), val); + } + + write_phy_reg(pi, 0x1ce, ci->PHY_BW1a); + write_phy_reg(pi, 0x1cf, ci->PHY_BW2); + write_phy_reg(pi, 0x1d0, ci->PHY_BW3); + + write_phy_reg(pi, 0x1d1, ci->PHY_BW4); + write_phy_reg(pi, 0x1d2, ci->PHY_BW5); + write_phy_reg(pi, 0x1d3, ci->PHY_BW6); + + if (CHSPEC_CHANNEL(pi->radio_chanspec) == 14) { + wlc_phy_classifier_nphy(pi, NPHY_ClassifierCtrl_ofdm_en, 0); + + or_phy_reg(pi, NPHY_TO_BPHY_OFF + BPHY_TEST, 0x800); + } else { + wlc_phy_classifier_nphy(pi, NPHY_ClassifierCtrl_ofdm_en, + NPHY_ClassifierCtrl_ofdm_en); + + if (CHSPEC_IS2G(chanspec)) + and_phy_reg(pi, NPHY_TO_BPHY_OFF + BPHY_TEST, ~0x840); + } + + if (pi->nphy_txpwrctrl == PHY_TPC_HW_OFF) + wlc_phy_txpwr_fixpower_nphy(pi); + + if (NREV_LT(pi->pubpi.phy_rev, 3)) + wlc_phy_adjust_lnagaintbl_nphy(pi); + + wlc_phy_txlpfbw_nphy(pi); + + if (NREV_GE(pi->pubpi.phy_rev, 3) + && (pi->phy_spuravoid != SPURAVOID_DISABLE)) { + u8 spuravoid = 0; + + val = CHSPEC_CHANNEL(chanspec); + if (!CHSPEC_IS40(pi->radio_chanspec)) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + if ((val == 13) || (val == 14) || (val == 153)) + spuravoid = 1; + } else if (((val >= 5) && (val <= 8)) || (val == 13) + || (val == 14)) { + spuravoid = 1; + } + } else if (NREV_GE(pi->pubpi.phy_rev, 7)) { + if (val == 54) + spuravoid = 1; + } else if (pi->nphy_aband_spurwar_en && + ((val == 38) || (val == 102) || (val == 118))) { + if ((pi->sh->chip == BCMA_CHIP_ID_BCM4716) + && (pi->sh->chippkg == BCMA_PKG_ID_BCM4717)) { + spuravoid = 0; + } else { + spuravoid = 1; + } + } + + if (pi->phy_spuravoid == SPURAVOID_FORCEON) + spuravoid = 1; + + if ((pi->sh->chip == BCMA_CHIP_ID_BCM4716) || + (pi->sh->chip == BCMA_CHIP_ID_BCM43225)) { + bcma_pmu_spuravoid_pllupdate(&pi->d11core->bus->drv_cc, + spuravoid); + } else { + wlapi_bmac_core_phypll_ctl(pi->sh->physhim, false); + bcma_pmu_spuravoid_pllupdate(&pi->d11core->bus->drv_cc, + spuravoid); + wlapi_bmac_core_phypll_ctl(pi->sh->physhim, true); + } + + if ((pi->sh->chip == BCMA_CHIP_ID_BCM43224) || + (pi->sh->chip == BCMA_CHIP_ID_BCM43225)) { + if (spuravoid == 1) { + bcma_write16(pi->d11core, + D11REGOFFS(tsf_clk_frac_l), + 0x5341); + bcma_write16(pi->d11core, + D11REGOFFS(tsf_clk_frac_h), 0x8); + } else { + bcma_write16(pi->d11core, + D11REGOFFS(tsf_clk_frac_l), + 0x8889); + bcma_write16(pi->d11core, + D11REGOFFS(tsf_clk_frac_h), 0x8); + } + } + + if (!((pi->sh->chip == BCMA_CHIP_ID_BCM4716) || + (pi->sh->chip == BCMA_CHIP_ID_BCM47162))) + wlapi_bmac_core_phypll_reset(pi->sh->physhim); + + mod_phy_reg(pi, 0x01, (0x1 << 15), + ((spuravoid > 0) ? (0x1 << 15) : 0)); + + wlc_phy_resetcca_nphy(pi); + + pi->phy_isspuravoid = (spuravoid > 0); + } + + if (NREV_LT(pi->pubpi.phy_rev, 7)) + write_phy_reg(pi, 0x17e, 0x3830); + + wlc_phy_spurwar_nphy(pi); +} + +void wlc_phy_chanspec_set_nphy(struct brcms_phy *pi, u16 chanspec) +{ + int freq; + const struct chan_info_nphy_radio2057 *t0 = NULL; + const struct chan_info_nphy_radio205x *t1 = NULL; + const struct chan_info_nphy_radio2057_rev5 *t2 = NULL; + const struct chan_info_nphy_2055 *t3 = NULL; + + if (!wlc_phy_chan2freq_nphy + (pi, CHSPEC_CHANNEL(chanspec), &freq, &t0, &t1, &t2, &t3)) + return; + + wlc_phy_chanspec_radio_set((struct brcms_phy_pub *) pi, chanspec); + + if (CHSPEC_BW(chanspec) != pi->bw) + wlapi_bmac_bw_set(pi->sh->physhim, CHSPEC_BW(chanspec)); + + if (CHSPEC_IS40(chanspec)) { + if (CHSPEC_SB_UPPER(chanspec)) { + or_phy_reg(pi, 0xa0, BPHY_BAND_SEL_UP20); + if (NREV_GE(pi->pubpi.phy_rev, 7)) + or_phy_reg(pi, 0x310, PRIM_SEL_UP20); + } else { + and_phy_reg(pi, 0xa0, ~BPHY_BAND_SEL_UP20); + if (NREV_GE(pi->pubpi.phy_rev, 7)) + and_phy_reg(pi, 0x310, + (~PRIM_SEL_UP20 & 0xffff)); + } + } + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + if ((pi->pubpi.radiorev <= 4) + || (pi->pubpi.radiorev == 6)) { + mod_radio_reg(pi, RADIO_2057_TIA_CONFIG_CORE0, + 0x2, + (CHSPEC_IS5G(chanspec) ? (1 << 1) + : 0)); + mod_radio_reg(pi, RADIO_2057_TIA_CONFIG_CORE1, + 0x2, + (CHSPEC_IS5G(chanspec) ? (1 << 1) + : 0)); + } + + wlc_phy_chanspec_radio2057_setup(pi, t0, t2); + wlc_phy_chanspec_nphy_setup(pi, chanspec, + (pi->pubpi.radiorev == 5) ? + (const struct nphy_sfo_cfg *)&(t2->PHY_BW1a) : + (const struct nphy_sfo_cfg *)&(t0->PHY_BW1a)); + + } else { + + mod_radio_reg(pi, + RADIO_2056_SYN_COM_CTRL | RADIO_2056_SYN, + 0x4, + (CHSPEC_IS5G(chanspec) ? (0x1 << 2) : 0)); + wlc_phy_chanspec_radio2056_setup(pi, t1); + + wlc_phy_chanspec_nphy_setup(pi, chanspec, + (const struct nphy_sfo_cfg *) &(t1->PHY_BW1a)); + } + + } else { + + mod_radio_reg(pi, RADIO_2055_MASTER_CNTRL1, 0x70, + (CHSPEC_IS5G(chanspec) ? (0x02 << 4) + : (0x05 << 4))); + + wlc_phy_chanspec_radio2055_setup(pi, t3); + wlc_phy_chanspec_nphy_setup(pi, chanspec, + (const struct nphy_sfo_cfg *) + &(t3->PHY_BW1a)); + } + +} + +void wlc_phy_antsel_init(struct brcms_phy_pub *ppi, bool lut_init) +{ + struct brcms_phy *pi = (struct brcms_phy *) ppi; + u16 mask = 0xfc00; + u32 mc = 0; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + return; + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + u16 v0 = 0x211, v1 = 0x222, v2 = 0x144, v3 = 0x188; + + if (!lut_init) + return; + + if (pi->srom_fem2g.antswctrllut == 0) { + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_ANTSWCTRLLUT, + 1, 0x02, 16, &v0); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_ANTSWCTRLLUT, + 1, 0x03, 16, &v1); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_ANTSWCTRLLUT, + 1, 0x08, 16, &v2); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_ANTSWCTRLLUT, + 1, 0x0C, 16, &v3); + } + + if (pi->srom_fem5g.antswctrllut == 0) { + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_ANTSWCTRLLUT, + 1, 0x12, 16, &v0); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_ANTSWCTRLLUT, + 1, 0x13, 16, &v1); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_ANTSWCTRLLUT, + 1, 0x18, 16, &v2); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_ANTSWCTRLLUT, + 1, 0x1C, 16, &v3); + } + } else { + + write_phy_reg(pi, 0xc8, 0x0); + write_phy_reg(pi, 0xc9, 0x0); + + bcma_chipco_gpio_control(&pi->d11core->bus->drv_cc, mask, mask); + + mc = bcma_read32(pi->d11core, D11REGOFFS(maccontrol)); + mc &= ~MCTL_GPOUT_SEL_MASK; + bcma_write32(pi->d11core, D11REGOFFS(maccontrol), mc); + + bcma_set16(pi->d11core, D11REGOFFS(psm_gpio_oe), mask); + + bcma_mask16(pi->d11core, D11REGOFFS(psm_gpio_out), ~mask); + + if (lut_init) { + write_phy_reg(pi, 0xf8, 0x02d8); + write_phy_reg(pi, 0xf9, 0x0301); + write_phy_reg(pi, 0xfa, 0x02d8); + write_phy_reg(pi, 0xfb, 0x0301); + } + } +} + +u16 wlc_phy_classifier_nphy(struct brcms_phy *pi, u16 mask, u16 val) +{ + u16 curr_ctl, new_ctl; + bool suspended = false; + + if (D11REV_IS(pi->sh->corerev, 16)) { + suspended = (bcma_read32(pi->d11core, D11REGOFFS(maccontrol)) & + MCTL_EN_MAC) ? false : true; + if (!suspended) + wlapi_suspend_mac_and_wait(pi->sh->physhim); + } + + curr_ctl = read_phy_reg(pi, 0xb0) & (0x7 << 0); + + new_ctl = (curr_ctl & (~mask)) | (val & mask); + + mod_phy_reg(pi, 0xb0, (0x7 << 0), new_ctl); + + if (D11REV_IS(pi->sh->corerev, 16) && !suspended) + wlapi_enable_mac(pi->sh->physhim); + + return new_ctl; +} + +void wlc_phy_force_rfseq_nphy(struct brcms_phy *pi, u8 cmd) +{ + u16 trigger_mask, status_mask; + u16 orig_RfseqCoreActv; + + switch (cmd) { + case NPHY_RFSEQ_RX2TX: + trigger_mask = NPHY_RfseqTrigger_rx2tx; + status_mask = NPHY_RfseqStatus_rx2tx; + break; + case NPHY_RFSEQ_TX2RX: + trigger_mask = NPHY_RfseqTrigger_tx2rx; + status_mask = NPHY_RfseqStatus_tx2rx; + break; + case NPHY_RFSEQ_RESET2RX: + trigger_mask = NPHY_RfseqTrigger_reset2rx; + status_mask = NPHY_RfseqStatus_reset2rx; + break; + case NPHY_RFSEQ_UPDATEGAINH: + trigger_mask = NPHY_RfseqTrigger_updategainh; + status_mask = NPHY_RfseqStatus_updategainh; + break; + case NPHY_RFSEQ_UPDATEGAINL: + trigger_mask = NPHY_RfseqTrigger_updategainl; + status_mask = NPHY_RfseqStatus_updategainl; + break; + case NPHY_RFSEQ_UPDATEGAINU: + trigger_mask = NPHY_RfseqTrigger_updategainu; + status_mask = NPHY_RfseqStatus_updategainu; + break; + default: + return; + } + + orig_RfseqCoreActv = read_phy_reg(pi, 0xa1); + or_phy_reg(pi, 0xa1, + (NPHY_RfseqMode_CoreActv_override | + NPHY_RfseqMode_Trigger_override)); + or_phy_reg(pi, 0xa3, trigger_mask); + SPINWAIT((read_phy_reg(pi, 0xa4) & status_mask), 200000); + write_phy_reg(pi, 0xa1, orig_RfseqCoreActv); + WARN(read_phy_reg(pi, 0xa4) & status_mask, "HW error in rf"); +} + +static void +wlc_phy_rfctrl_override_1tomany_nphy(struct brcms_phy *pi, u16 cmd, u16 value, + u8 core_mask, u8 off) +{ + u16 rfmxgain = 0, lpfgain = 0; + u16 tgain = 0; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + switch (cmd) { + case NPHY_REV7_RfctrlOverride_cmd_rxrf_pu: + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 5), + value, core_mask, off, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 4), value, + core_mask, off, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 3), value, + core_mask, off, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + break; + case NPHY_REV7_RfctrlOverride_cmd_rx_pu: + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 2), + value, core_mask, off, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 1), value, + core_mask, off, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 0), value, + core_mask, off, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 1), value, + core_mask, off, + NPHY_REV7_RFCTRLOVERRIDE_ID2); + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 11), 0, + core_mask, off, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + break; + case NPHY_REV7_RfctrlOverride_cmd_tx_pu: + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 2), + value, core_mask, off, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 1), value, + core_mask, off, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 0), value, + core_mask, off, + NPHY_REV7_RFCTRLOVERRIDE_ID2); + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 2), value, + core_mask, off, + NPHY_REV7_RFCTRLOVERRIDE_ID2); + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 11), 1, + core_mask, off, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + break; + case NPHY_REV7_RfctrlOverride_cmd_rxgain: + rfmxgain = value & 0x000ff; + lpfgain = value & 0x0ff00; + lpfgain = lpfgain >> 8; + + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 11), + rfmxgain, core_mask, + off, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x3 << 13), + lpfgain, core_mask, + off, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + break; + case NPHY_REV7_RfctrlOverride_cmd_txgain: + tgain = value & 0x7fff; + lpfgain = value & 0x8000; + lpfgain = lpfgain >> 14; + + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 12), + tgain, core_mask, off, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 13), + lpfgain, core_mask, + off, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + break; + } + } +} + +static void +wlc_phy_scale_offset_rssi_nphy(struct brcms_phy *pi, u16 scale, s8 offset, + u8 coresel, u8 rail, u8 rssi_type) +{ + u16 valuetostuff; + + offset = (offset > NPHY_RSSICAL_MAXREAD) ? + NPHY_RSSICAL_MAXREAD : offset; + offset = (offset < (-NPHY_RSSICAL_MAXREAD - 1)) ? + -NPHY_RSSICAL_MAXREAD - 1 : offset; + + valuetostuff = ((scale & 0x3f) << 8) | (offset & 0x3f); + + if (((coresel == RADIO_MIMO_CORESEL_CORE1) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_I) && (rssi_type == NPHY_RSSI_SEL_NB)) + write_phy_reg(pi, 0x1a6, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE1) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_Q) && (rssi_type == NPHY_RSSI_SEL_NB)) + write_phy_reg(pi, 0x1ac, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE2) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_I) && (rssi_type == NPHY_RSSI_SEL_NB)) + write_phy_reg(pi, 0x1b2, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE2) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_Q) && (rssi_type == NPHY_RSSI_SEL_NB)) + write_phy_reg(pi, 0x1b8, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE1) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_I) && (rssi_type == NPHY_RSSI_SEL_W1)) + write_phy_reg(pi, 0x1a4, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE1) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_Q) && (rssi_type == NPHY_RSSI_SEL_W1)) + write_phy_reg(pi, 0x1aa, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE2) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_I) && (rssi_type == NPHY_RSSI_SEL_W1)) + write_phy_reg(pi, 0x1b0, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE2) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_Q) && (rssi_type == NPHY_RSSI_SEL_W1)) + write_phy_reg(pi, 0x1b6, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE1) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_I) && (rssi_type == NPHY_RSSI_SEL_W2)) + write_phy_reg(pi, 0x1a5, valuetostuff); + if (((coresel == RADIO_MIMO_CORESEL_CORE1) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_Q) && (rssi_type == NPHY_RSSI_SEL_W2)) + write_phy_reg(pi, 0x1ab, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE2) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_I) && (rssi_type == NPHY_RSSI_SEL_W2)) + write_phy_reg(pi, 0x1b1, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE2) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_Q) && (rssi_type == NPHY_RSSI_SEL_W2)) + write_phy_reg(pi, 0x1b7, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE1) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_I) && (rssi_type == NPHY_RSSI_SEL_TBD)) + write_phy_reg(pi, 0x1a7, valuetostuff); + if (((coresel == RADIO_MIMO_CORESEL_CORE1) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_Q) && (rssi_type == NPHY_RSSI_SEL_TBD)) + write_phy_reg(pi, 0x1ad, valuetostuff); + if (((coresel == RADIO_MIMO_CORESEL_CORE2) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_I) && (rssi_type == NPHY_RSSI_SEL_TBD)) + write_phy_reg(pi, 0x1b3, valuetostuff); + if (((coresel == RADIO_MIMO_CORESEL_CORE2) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_Q) && (rssi_type == NPHY_RSSI_SEL_TBD)) + write_phy_reg(pi, 0x1b9, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE1) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_I) && (rssi_type == NPHY_RSSI_SEL_IQ)) + write_phy_reg(pi, 0x1a8, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE1) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_Q) && (rssi_type == NPHY_RSSI_SEL_IQ)) + write_phy_reg(pi, 0x1ae, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE2) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_I) && (rssi_type == NPHY_RSSI_SEL_IQ)) + write_phy_reg(pi, 0x1b4, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE2) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rail == NPHY_RAIL_Q) && (rssi_type == NPHY_RSSI_SEL_IQ)) + write_phy_reg(pi, 0x1ba, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE1) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rssi_type == NPHY_RSSI_SEL_TSSI_2G)) + write_phy_reg(pi, 0x1a9, valuetostuff); + if (((coresel == RADIO_MIMO_CORESEL_CORE2) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rssi_type == NPHY_RSSI_SEL_TSSI_2G)) + write_phy_reg(pi, 0x1b5, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE1) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rssi_type == NPHY_RSSI_SEL_TSSI_5G)) + write_phy_reg(pi, 0x1af, valuetostuff); + + if (((coresel == RADIO_MIMO_CORESEL_CORE2) || + (coresel == RADIO_MIMO_CORESEL_ALLRX)) && + (rssi_type == NPHY_RSSI_SEL_TSSI_5G)) + write_phy_reg(pi, 0x1bb, valuetostuff); +} + +static void brcms_phy_wr_tx_mux(struct brcms_phy *pi, u8 core) +{ + if (PHY_IPA(pi)) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) + write_radio_reg(pi, + ((core == PHY_CORE_0) ? + RADIO_2057_TX0_TX_SSI_MUX : + RADIO_2057_TX1_TX_SSI_MUX), + (CHSPEC_IS5G(pi->radio_chanspec) ? + 0xc : 0xe)); + else + write_radio_reg(pi, + RADIO_2056_TX_TX_SSI_MUX | + ((core == PHY_CORE_0) ? + RADIO_2056_TX0 : RADIO_2056_TX1), + (CHSPEC_IS5G(pi->radio_chanspec) ? + 0xc : 0xe)); + } else { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + write_radio_reg(pi, + ((core == PHY_CORE_0) ? + RADIO_2057_TX0_TX_SSI_MUX : + RADIO_2057_TX1_TX_SSI_MUX), + 0x11); + + if (pi->pubpi.radioid == BCM2057_ID) + write_radio_reg(pi, + RADIO_2057_IQTEST_SEL_PU, 0x1); + + } else { + write_radio_reg(pi, + RADIO_2056_TX_TX_SSI_MUX | + ((core == PHY_CORE_0) ? + RADIO_2056_TX0 : RADIO_2056_TX1), + 0x11); + } + } +} + +void wlc_phy_rssisel_nphy(struct brcms_phy *pi, u8 core_code, u8 rssi_type) +{ + u16 mask, val; + u16 afectrlovr_rssi_val, rfctrlcmd_rxen_val, rfctrlcmd_coresel_val, + startseq; + u16 rfctrlovr_rssi_val, rfctrlovr_rxen_val, rfctrlovr_coresel_val, + rfctrlovr_trigger_val; + u16 afectrlovr_rssi_mask, rfctrlcmd_mask, rfctrlovr_mask; + u16 rfctrlcmd_val, rfctrlovr_val; + u8 core; + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + if (core_code == RADIO_MIMO_CORESEL_OFF) { + mod_phy_reg(pi, 0x8f, (0x1 << 9), 0); + mod_phy_reg(pi, 0xa5, (0x1 << 9), 0); + + mod_phy_reg(pi, 0xa6, (0x3 << 8), 0); + mod_phy_reg(pi, 0xa7, (0x3 << 8), 0); + + mod_phy_reg(pi, 0xe5, (0x1 << 5), 0); + mod_phy_reg(pi, 0xe6, (0x1 << 5), 0); + + mask = (0x1 << 2) | + (0x1 << 3) | (0x1 << 4) | (0x1 << 5); + mod_phy_reg(pi, 0xf9, mask, 0); + mod_phy_reg(pi, 0xfb, mask, 0); + + } else { + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + if (core_code == RADIO_MIMO_CORESEL_CORE1 + && core == PHY_CORE_1) + continue; + else if (core_code == RADIO_MIMO_CORESEL_CORE2 + && core == PHY_CORE_0) + continue; + + mod_phy_reg(pi, (core == PHY_CORE_0) ? + 0x8f : 0xa5, (0x1 << 9), 1 << 9); + + if (rssi_type == NPHY_RSSI_SEL_W1 || + rssi_type == NPHY_RSSI_SEL_W2 || + rssi_type == NPHY_RSSI_SEL_NB) { + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0xa6 : 0xa7, + (0x3 << 8), 0); + + mask = (0x1 << 2) | + (0x1 << 3) | + (0x1 << 4) | (0x1 << 5); + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0xf9 : 0xfb, + mask, 0); + + if (rssi_type == NPHY_RSSI_SEL_W1) { + if (CHSPEC_IS5G( + pi->radio_chanspec)) { + mask = (0x1 << 2); + val = 1 << 2; + } else { + mask = (0x1 << 3); + val = 1 << 3; + } + } else if (rssi_type == + NPHY_RSSI_SEL_W2) { + mask = (0x1 << 4); + val = 1 << 4; + } else { + mask = (0x1 << 5); + val = 1 << 5; + } + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0xf9 : 0xfb, + mask, val); + + mask = (0x1 << 5); + val = 1 << 5; + mod_phy_reg(pi, (core == PHY_CORE_0) ? + 0xe5 : 0xe6, mask, val); + } else { + if (rssi_type == NPHY_RSSI_SEL_TBD) { + mask = (0x3 << 8); + val = 1 << 8; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0xa6 + : 0xa7, mask, val); + mask = (0x3 << 10); + val = 1 << 10; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0xa6 + : 0xa7, mask, val); + } else if (rssi_type == + NPHY_RSSI_SEL_IQ) { + mask = (0x3 << 8); + val = 2 << 8; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0xa6 + : 0xa7, mask, val); + mask = (0x3 << 10); + val = 2 << 10; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0xa6 + : 0xa7, mask, val); + } else { + mask = (0x3 << 8); + val = 3 << 8; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0xa6 + : 0xa7, mask, val); + mask = (0x3 << 10); + val = 3 << 10; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0xa6 + : 0xa7, mask, val); + brcms_phy_wr_tx_mux(pi, core); + afectrlovr_rssi_val = 1 << 9; + mod_phy_reg(pi, + (core == + PHY_CORE_0) ? 0x8f + : 0xa5, (0x1 << 9), + afectrlovr_rssi_val); + } + } + } + } + } else { + + if ((rssi_type == NPHY_RSSI_SEL_W1) || + (rssi_type == NPHY_RSSI_SEL_W2) || + (rssi_type == NPHY_RSSI_SEL_NB)) + val = 0x0; + else if (rssi_type == NPHY_RSSI_SEL_TBD) + val = 0x1; + else if (rssi_type == NPHY_RSSI_SEL_IQ) + val = 0x2; + else + val = 0x3; + + mask = ((0x3 << 12) | (0x3 << 14)); + val = (val << 12) | (val << 14); + mod_phy_reg(pi, 0xa6, mask, val); + mod_phy_reg(pi, 0xa7, mask, val); + + if ((rssi_type == NPHY_RSSI_SEL_W1) || + (rssi_type == NPHY_RSSI_SEL_W2) || + (rssi_type == NPHY_RSSI_SEL_NB)) { + if (rssi_type == NPHY_RSSI_SEL_W1) + val = 0x1; + if (rssi_type == NPHY_RSSI_SEL_W2) + val = 0x2; + if (rssi_type == NPHY_RSSI_SEL_NB) + val = 0x3; + + mask = (0x3 << 4); + val = (val << 4); + mod_phy_reg(pi, 0x7a, mask, val); + mod_phy_reg(pi, 0x7d, mask, val); + } + + if (core_code == RADIO_MIMO_CORESEL_OFF) { + afectrlovr_rssi_val = 0; + rfctrlcmd_rxen_val = 0; + rfctrlcmd_coresel_val = 0; + rfctrlovr_rssi_val = 0; + rfctrlovr_rxen_val = 0; + rfctrlovr_coresel_val = 0; + rfctrlovr_trigger_val = 0; + startseq = 0; + } else { + afectrlovr_rssi_val = 1; + rfctrlcmd_rxen_val = 1; + rfctrlcmd_coresel_val = core_code; + rfctrlovr_rssi_val = 1; + rfctrlovr_rxen_val = 1; + rfctrlovr_coresel_val = 1; + rfctrlovr_trigger_val = 1; + startseq = 1; + } + + afectrlovr_rssi_mask = ((0x1 << 12) | (0x1 << 13)); + afectrlovr_rssi_val = (afectrlovr_rssi_val << + 12) | (afectrlovr_rssi_val << 13); + mod_phy_reg(pi, 0xa5, afectrlovr_rssi_mask, + afectrlovr_rssi_val); + + if ((rssi_type == NPHY_RSSI_SEL_W1) || + (rssi_type == NPHY_RSSI_SEL_W2) || + (rssi_type == NPHY_RSSI_SEL_NB)) { + rfctrlcmd_mask = ((0x1 << 8) | (0x7 << 3)); + rfctrlcmd_val = (rfctrlcmd_rxen_val << 8) | + (rfctrlcmd_coresel_val << 3); + + rfctrlovr_mask = ((0x1 << 5) | + (0x1 << 12) | + (0x1 << 1) | (0x1 << 0)); + rfctrlovr_val = (rfctrlovr_rssi_val << + 5) | + (rfctrlovr_rxen_val << 12) | + (rfctrlovr_coresel_val << 1) | + (rfctrlovr_trigger_val << 0); + + mod_phy_reg(pi, 0x78, rfctrlcmd_mask, rfctrlcmd_val); + mod_phy_reg(pi, 0xec, rfctrlovr_mask, rfctrlovr_val); + + mod_phy_reg(pi, 0x78, (0x1 << 0), (startseq << 0)); + udelay(20); + + mod_phy_reg(pi, 0xec, (0x1 << 0), 0); + } + } +} + +int +wlc_phy_poll_rssi_nphy(struct brcms_phy *pi, u8 rssi_type, s32 *rssi_buf, + u8 nsamps) +{ + s16 rssi0, rssi1; + u16 afectrlCore1_save = 0; + u16 afectrlCore2_save = 0; + u16 afectrlOverride1_save = 0; + u16 afectrlOverride2_save = 0; + u16 rfctrlOverrideAux0_save = 0; + u16 rfctrlOverrideAux1_save = 0; + u16 rfctrlMiscReg1_save = 0; + u16 rfctrlMiscReg2_save = 0; + u16 rfctrlcmd_save = 0; + u16 rfctrloverride_save = 0; + u16 rfctrlrssiothers1_save = 0; + u16 rfctrlrssiothers2_save = 0; + s8 tmp_buf[4]; + u8 ctr = 0, samp = 0; + s32 rssi_out_val; + u16 gpiosel_orig; + + afectrlCore1_save = read_phy_reg(pi, 0xa6); + afectrlCore2_save = read_phy_reg(pi, 0xa7); + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + rfctrlMiscReg1_save = read_phy_reg(pi, 0xf9); + rfctrlMiscReg2_save = read_phy_reg(pi, 0xfb); + afectrlOverride1_save = read_phy_reg(pi, 0x8f); + afectrlOverride2_save = read_phy_reg(pi, 0xa5); + rfctrlOverrideAux0_save = read_phy_reg(pi, 0xe5); + rfctrlOverrideAux1_save = read_phy_reg(pi, 0xe6); + } else { + afectrlOverride1_save = read_phy_reg(pi, 0xa5); + rfctrlcmd_save = read_phy_reg(pi, 0x78); + rfctrloverride_save = read_phy_reg(pi, 0xec); + rfctrlrssiothers1_save = read_phy_reg(pi, 0x7a); + rfctrlrssiothers2_save = read_phy_reg(pi, 0x7d); + } + + wlc_phy_rssisel_nphy(pi, RADIO_MIMO_CORESEL_ALLRX, rssi_type); + + gpiosel_orig = read_phy_reg(pi, 0xca); + if (NREV_LT(pi->pubpi.phy_rev, 2)) + write_phy_reg(pi, 0xca, 5); + + for (ctr = 0; ctr < 4; ctr++) + rssi_buf[ctr] = 0; + + for (samp = 0; samp < nsamps; samp++) { + if (NREV_LT(pi->pubpi.phy_rev, 2)) { + rssi0 = read_phy_reg(pi, 0x1c9); + rssi1 = read_phy_reg(pi, 0x1ca); + } else { + rssi0 = read_phy_reg(pi, 0x219); + rssi1 = read_phy_reg(pi, 0x21a); + } + + ctr = 0; + tmp_buf[ctr++] = ((s8) ((rssi0 & 0x3f) << 2)) >> 2; + tmp_buf[ctr++] = ((s8) (((rssi0 >> 8) & 0x3f) << 2)) >> 2; + tmp_buf[ctr++] = ((s8) ((rssi1 & 0x3f) << 2)) >> 2; + tmp_buf[ctr++] = ((s8) (((rssi1 >> 8) & 0x3f) << 2)) >> 2; + + for (ctr = 0; ctr < 4; ctr++) + rssi_buf[ctr] += tmp_buf[ctr]; + + } + + rssi_out_val = rssi_buf[3] & 0xff; + rssi_out_val |= (rssi_buf[2] & 0xff) << 8; + rssi_out_val |= (rssi_buf[1] & 0xff) << 16; + rssi_out_val |= (rssi_buf[0] & 0xff) << 24; + + if (NREV_LT(pi->pubpi.phy_rev, 2)) + write_phy_reg(pi, 0xca, gpiosel_orig); + + write_phy_reg(pi, 0xa6, afectrlCore1_save); + write_phy_reg(pi, 0xa7, afectrlCore2_save); + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + write_phy_reg(pi, 0xf9, rfctrlMiscReg1_save); + write_phy_reg(pi, 0xfb, rfctrlMiscReg2_save); + write_phy_reg(pi, 0x8f, afectrlOverride1_save); + write_phy_reg(pi, 0xa5, afectrlOverride2_save); + write_phy_reg(pi, 0xe5, rfctrlOverrideAux0_save); + write_phy_reg(pi, 0xe6, rfctrlOverrideAux1_save); + } else { + write_phy_reg(pi, 0xa5, afectrlOverride1_save); + write_phy_reg(pi, 0x78, rfctrlcmd_save); + write_phy_reg(pi, 0xec, rfctrloverride_save); + write_phy_reg(pi, 0x7a, rfctrlrssiothers1_save); + write_phy_reg(pi, 0x7d, rfctrlrssiothers2_save); + } + + return rssi_out_val; +} + +s16 wlc_phy_tempsense_nphy(struct brcms_phy *pi) +{ + u16 core1_txrf_iqcal1_save, core1_txrf_iqcal2_save; + u16 core2_txrf_iqcal1_save, core2_txrf_iqcal2_save; + u16 pwrdet_rxtx_core1_save; + u16 pwrdet_rxtx_core2_save; + u16 afectrlCore1_save; + u16 afectrlCore2_save; + u16 afectrlOverride_save; + u16 afectrlOverride2_save; + u16 pd_pll_ts_save; + u16 gpioSel_save; + s32 radio_temp[4]; + s32 radio_temp2[4]; + u16 syn_tempprocsense_save; + s16 offset = 0; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + u16 auxADC_Vmid, auxADC_Av, auxADC_Vmid_save, auxADC_Av_save; + u16 auxADC_rssi_ctrlL_save, auxADC_rssi_ctrlH_save; + u16 auxADC_rssi_ctrlL, auxADC_rssi_ctrlH; + s32 auxADC_Vl; + u16 RfctrlOverride5_save, RfctrlOverride6_save; + u16 RfctrlMiscReg5_save, RfctrlMiscReg6_save; + u16 RSSIMultCoef0QPowerDet_save; + u16 tempsense_Rcal; + + syn_tempprocsense_save = + read_radio_reg(pi, RADIO_2057_TEMPSENSE_CONFIG); + + afectrlCore1_save = read_phy_reg(pi, 0xa6); + afectrlCore2_save = read_phy_reg(pi, 0xa7); + afectrlOverride_save = read_phy_reg(pi, 0x8f); + afectrlOverride2_save = read_phy_reg(pi, 0xa5); + RSSIMultCoef0QPowerDet_save = read_phy_reg(pi, 0x1ae); + RfctrlOverride5_save = read_phy_reg(pi, 0x346); + RfctrlOverride6_save = read_phy_reg(pi, 0x347); + RfctrlMiscReg5_save = read_phy_reg(pi, 0x344); + RfctrlMiscReg6_save = read_phy_reg(pi, 0x345); + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x0A, 16, + &auxADC_Vmid_save); + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x0E, 16, + &auxADC_Av_save); + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x02, 16, + &auxADC_rssi_ctrlL_save); + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x03, 16, + &auxADC_rssi_ctrlH_save); + + write_phy_reg(pi, 0x1ae, 0x0); + + auxADC_rssi_ctrlL = 0x0; + auxADC_rssi_ctrlH = 0x20; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x02, 16, + &auxADC_rssi_ctrlL); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x03, 16, + &auxADC_rssi_ctrlH); + + tempsense_Rcal = syn_tempprocsense_save & 0x1c; + + write_radio_reg(pi, RADIO_2057_TEMPSENSE_CONFIG, + tempsense_Rcal | 0x01); + + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 1), + 1, 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID2); + mod_phy_reg(pi, 0xa6, (0x1 << 7), 0); + mod_phy_reg(pi, 0xa7, (0x1 << 7), 0); + mod_phy_reg(pi, 0x8f, (0x1 << 7), (0x1 << 7)); + mod_phy_reg(pi, 0xa5, (0x1 << 7), (0x1 << 7)); + + mod_phy_reg(pi, 0xa6, (0x1 << 2), (0x1 << 2)); + mod_phy_reg(pi, 0xa7, (0x1 << 2), (0x1 << 2)); + mod_phy_reg(pi, 0x8f, (0x1 << 2), (0x1 << 2)); + mod_phy_reg(pi, 0xa5, (0x1 << 2), (0x1 << 2)); + udelay(5); + mod_phy_reg(pi, 0xa6, (0x1 << 2), 0); + mod_phy_reg(pi, 0xa7, (0x1 << 2), 0); + mod_phy_reg(pi, 0xa6, (0x1 << 3), 0); + mod_phy_reg(pi, 0xa7, (0x1 << 3), 0); + mod_phy_reg(pi, 0x8f, (0x1 << 3), (0x1 << 3)); + mod_phy_reg(pi, 0xa5, (0x1 << 3), (0x1 << 3)); + mod_phy_reg(pi, 0xa6, (0x1 << 6), 0); + mod_phy_reg(pi, 0xa7, (0x1 << 6), 0); + mod_phy_reg(pi, 0x8f, (0x1 << 6), (0x1 << 6)); + mod_phy_reg(pi, 0xa5, (0x1 << 6), (0x1 << 6)); + + auxADC_Vmid = 0xA3; + auxADC_Av = 0x0; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x0A, 16, + &auxADC_Vmid); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x0E, 16, + &auxADC_Av); + + udelay(3); + + wlc_phy_poll_rssi_nphy(pi, NPHY_RSSI_SEL_IQ, radio_temp, 1); + write_radio_reg(pi, RADIO_2057_TEMPSENSE_CONFIG, + tempsense_Rcal | 0x03); + + udelay(5); + wlc_phy_poll_rssi_nphy(pi, NPHY_RSSI_SEL_IQ, radio_temp2, 1); + + auxADC_Av = 0x7; + if (radio_temp[1] + radio_temp2[1] < -30) { + auxADC_Vmid = 0x45; + auxADC_Vl = 263; + } else if (radio_temp[1] + radio_temp2[1] < -9) { + auxADC_Vmid = 0x200; + auxADC_Vl = 467; + } else if (radio_temp[1] + radio_temp2[1] < 11) { + auxADC_Vmid = 0x266; + auxADC_Vl = 634; + } else { + auxADC_Vmid = 0x2D5; + auxADC_Vl = 816; + } + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x0A, 16, + &auxADC_Vmid); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x0E, 16, + &auxADC_Av); + + udelay(3); + + wlc_phy_poll_rssi_nphy(pi, NPHY_RSSI_SEL_IQ, radio_temp2, 1); + write_radio_reg(pi, RADIO_2057_TEMPSENSE_CONFIG, + tempsense_Rcal | 0x01); + + udelay(5); + wlc_phy_poll_rssi_nphy(pi, NPHY_RSSI_SEL_IQ, radio_temp, 1); + + write_radio_reg(pi, RADIO_2057_TEMPSENSE_CONFIG, + syn_tempprocsense_save); + + write_phy_reg(pi, 0xa6, afectrlCore1_save); + write_phy_reg(pi, 0xa7, afectrlCore2_save); + write_phy_reg(pi, 0x8f, afectrlOverride_save); + write_phy_reg(pi, 0xa5, afectrlOverride2_save); + write_phy_reg(pi, 0x1ae, RSSIMultCoef0QPowerDet_save); + write_phy_reg(pi, 0x346, RfctrlOverride5_save); + write_phy_reg(pi, 0x347, RfctrlOverride6_save); + write_phy_reg(pi, 0x344, RfctrlMiscReg5_save); + write_phy_reg(pi, 0x345, RfctrlMiscReg5_save); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x0A, 16, + &auxADC_Vmid_save); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x0E, 16, + &auxADC_Av_save); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x02, 16, + &auxADC_rssi_ctrlL_save); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 0x03, 16, + &auxADC_rssi_ctrlH_save); + + if (pi->sh->chip == BCMA_CHIP_ID_BCM5357) { + radio_temp[0] = (193 * (radio_temp[1] + radio_temp2[1]) + + 88 * (auxADC_Vl) - 27111 + + 128) / 256; + } else { + radio_temp[0] = (179 * (radio_temp[1] + radio_temp2[1]) + + 82 * (auxADC_Vl) - 28861 + + 128) / 256; + } + + offset = (s16) pi->phy_tempsense_offset; + + } else if (NREV_GE(pi->pubpi.phy_rev, 3)) { + syn_tempprocsense_save = + read_radio_reg(pi, RADIO_2056_SYN_TEMPPROCSENSE); + + afectrlCore1_save = read_phy_reg(pi, 0xa6); + afectrlCore2_save = read_phy_reg(pi, 0xa7); + afectrlOverride_save = read_phy_reg(pi, 0x8f); + afectrlOverride2_save = read_phy_reg(pi, 0xa5); + gpioSel_save = read_phy_reg(pi, 0xca); + + write_radio_reg(pi, RADIO_2056_SYN_TEMPPROCSENSE, 0x01); + + wlc_phy_poll_rssi_nphy(pi, NPHY_RSSI_SEL_IQ, radio_temp, 1); + if (NREV_LT(pi->pubpi.phy_rev, 7)) + write_radio_reg(pi, RADIO_2056_SYN_TEMPPROCSENSE, 0x05); + + wlc_phy_poll_rssi_nphy(pi, NPHY_RSSI_SEL_IQ, radio_temp2, 1); + if (NREV_GE(pi->pubpi.phy_rev, 7)) + write_radio_reg(pi, RADIO_2057_TEMPSENSE_CONFIG, 0x01); + else + write_radio_reg(pi, RADIO_2056_SYN_TEMPPROCSENSE, 0x01); + + radio_temp[0] = + (126 * (radio_temp[1] + radio_temp2[1]) + 3987) / 64; + + write_radio_reg(pi, RADIO_2056_SYN_TEMPPROCSENSE, + syn_tempprocsense_save); + + write_phy_reg(pi, 0xca, gpioSel_save); + write_phy_reg(pi, 0xa6, afectrlCore1_save); + write_phy_reg(pi, 0xa7, afectrlCore2_save); + write_phy_reg(pi, 0x8f, afectrlOverride_save); + write_phy_reg(pi, 0xa5, afectrlOverride2_save); + + offset = (s16) pi->phy_tempsense_offset; + } else { + + pwrdet_rxtx_core1_save = + read_radio_reg(pi, RADIO_2055_PWRDET_RXTX_CORE1); + pwrdet_rxtx_core2_save = + read_radio_reg(pi, RADIO_2055_PWRDET_RXTX_CORE2); + core1_txrf_iqcal1_save = + read_radio_reg(pi, RADIO_2055_CORE1_TXRF_IQCAL1); + core1_txrf_iqcal2_save = + read_radio_reg(pi, RADIO_2055_CORE1_TXRF_IQCAL2); + core2_txrf_iqcal1_save = + read_radio_reg(pi, RADIO_2055_CORE2_TXRF_IQCAL1); + core2_txrf_iqcal2_save = + read_radio_reg(pi, RADIO_2055_CORE2_TXRF_IQCAL2); + pd_pll_ts_save = read_radio_reg(pi, RADIO_2055_PD_PLL_TS); + + afectrlCore1_save = read_phy_reg(pi, 0xa6); + afectrlCore2_save = read_phy_reg(pi, 0xa7); + afectrlOverride_save = read_phy_reg(pi, 0xa5); + gpioSel_save = read_phy_reg(pi, 0xca); + + write_radio_reg(pi, RADIO_2055_CORE1_TXRF_IQCAL1, 0x01); + write_radio_reg(pi, RADIO_2055_CORE2_TXRF_IQCAL1, 0x01); + write_radio_reg(pi, RADIO_2055_CORE1_TXRF_IQCAL2, 0x08); + write_radio_reg(pi, RADIO_2055_CORE2_TXRF_IQCAL2, 0x08); + write_radio_reg(pi, RADIO_2055_PWRDET_RXTX_CORE1, 0x04); + write_radio_reg(pi, RADIO_2055_PWRDET_RXTX_CORE2, 0x04); + write_radio_reg(pi, RADIO_2055_PD_PLL_TS, 0x00); + + wlc_phy_poll_rssi_nphy(pi, NPHY_RSSI_SEL_IQ, radio_temp, 1); + xor_radio_reg(pi, RADIO_2055_CAL_TS, 0x80); + + wlc_phy_poll_rssi_nphy(pi, NPHY_RSSI_SEL_IQ, radio_temp, 1); + xor_radio_reg(pi, RADIO_2055_CAL_TS, 0x80); + + wlc_phy_poll_rssi_nphy(pi, NPHY_RSSI_SEL_IQ, radio_temp2, 1); + xor_radio_reg(pi, RADIO_2055_CAL_TS, 0x80); + + radio_temp[0] = (radio_temp[0] + radio_temp2[0]); + radio_temp[1] = (radio_temp[1] + radio_temp2[1]); + radio_temp[2] = (radio_temp[2] + radio_temp2[2]); + radio_temp[3] = (radio_temp[3] + radio_temp2[3]); + + radio_temp[0] = + (radio_temp[0] + radio_temp[1] + radio_temp[2] + + radio_temp[3]); + + radio_temp[0] = + (radio_temp[0] + + (8 * 32)) * (950 - 350) / 63 + (350 * 8); + + radio_temp[0] = (radio_temp[0] - (8 * 420)) / 38; + + write_radio_reg(pi, RADIO_2055_PWRDET_RXTX_CORE1, + pwrdet_rxtx_core1_save); + write_radio_reg(pi, RADIO_2055_PWRDET_RXTX_CORE2, + pwrdet_rxtx_core2_save); + write_radio_reg(pi, RADIO_2055_CORE1_TXRF_IQCAL1, + core1_txrf_iqcal1_save); + write_radio_reg(pi, RADIO_2055_CORE2_TXRF_IQCAL1, + core2_txrf_iqcal1_save); + write_radio_reg(pi, RADIO_2055_CORE1_TXRF_IQCAL2, + core1_txrf_iqcal2_save); + write_radio_reg(pi, RADIO_2055_CORE2_TXRF_IQCAL2, + core2_txrf_iqcal2_save); + write_radio_reg(pi, RADIO_2055_PD_PLL_TS, pd_pll_ts_save); + + write_phy_reg(pi, 0xca, gpioSel_save); + write_phy_reg(pi, 0xa6, afectrlCore1_save); + write_phy_reg(pi, 0xa7, afectrlCore2_save); + write_phy_reg(pi, 0xa5, afectrlOverride_save); + } + + return (s16) radio_temp[0] + offset; +} + +static void +wlc_phy_set_rssi_2055_vcm(struct brcms_phy *pi, u8 rssi_type, u8 *vcm_buf) +{ + u8 core; + + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + if (rssi_type == NPHY_RSSI_SEL_NB) { + if (core == PHY_CORE_0) { + mod_radio_reg(pi, + RADIO_2055_CORE1_B0_NBRSSI_VCM, + RADIO_2055_NBRSSI_VCM_I_MASK, + vcm_buf[2 * + core] << + RADIO_2055_NBRSSI_VCM_I_SHIFT); + mod_radio_reg(pi, + RADIO_2055_CORE1_RXBB_RSSI_CTRL5, + RADIO_2055_NBRSSI_VCM_Q_MASK, + vcm_buf[2 * core + + 1] << + RADIO_2055_NBRSSI_VCM_Q_SHIFT); + } else { + mod_radio_reg(pi, + RADIO_2055_CORE2_B0_NBRSSI_VCM, + RADIO_2055_NBRSSI_VCM_I_MASK, + vcm_buf[2 * + core] << + RADIO_2055_NBRSSI_VCM_I_SHIFT); + mod_radio_reg(pi, + RADIO_2055_CORE2_RXBB_RSSI_CTRL5, + RADIO_2055_NBRSSI_VCM_Q_MASK, + vcm_buf[2 * core + + 1] << + RADIO_2055_NBRSSI_VCM_Q_SHIFT); + } + } else { + if (core == PHY_CORE_0) + mod_radio_reg(pi, + RADIO_2055_CORE1_RXBB_RSSI_CTRL5, + RADIO_2055_WBRSSI_VCM_IQ_MASK, + vcm_buf[2 * + core] << + RADIO_2055_WBRSSI_VCM_IQ_SHIFT); + else + mod_radio_reg(pi, + RADIO_2055_CORE2_RXBB_RSSI_CTRL5, + RADIO_2055_WBRSSI_VCM_IQ_MASK, + vcm_buf[2 * + core] << + RADIO_2055_WBRSSI_VCM_IQ_SHIFT); + } + } +} + +static void wlc_phy_rssi_cal_nphy_rev3(struct brcms_phy *pi) +{ + u16 classif_state; + u16 clip_state[2]; + u16 clip_off[] = { 0xffff, 0xffff }; + s32 target_code; + u8 vcm, min_vcm; + u8 vcm_final = 0; + u8 result_idx; + s32 poll_results[8][4] = { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0} + }; + s32 poll_result_core[4] = { 0, 0, 0, 0 }; + s32 min_d = NPHY_RSSICAL_MAXD, curr_d; + s32 fine_digital_offset[4]; + s32 poll_results_min[4] = { 0, 0, 0, 0 }; + s32 min_poll; + u8 vcm_level_max; + u8 core; + u8 wb_cnt; + u8 rssi_type; + u16 NPHY_Rfctrlintc1_save, NPHY_Rfctrlintc2_save; + u16 NPHY_AfectrlOverride1_save, NPHY_AfectrlOverride2_save; + u16 NPHY_AfectrlCore1_save, NPHY_AfectrlCore2_save; + u16 NPHY_RfctrlOverride0_save, NPHY_RfctrlOverride1_save; + u16 NPHY_RfctrlOverrideAux0_save, NPHY_RfctrlOverrideAux1_save; + u16 NPHY_RfctrlCmd_save; + u16 NPHY_RfctrlMiscReg1_save, NPHY_RfctrlMiscReg2_save; + u16 NPHY_RfctrlRSSIOTHERS1_save, NPHY_RfctrlRSSIOTHERS2_save; + u8 rxcore_state; + u16 NPHY_REV7_RfctrlOverride3_save, NPHY_REV7_RfctrlOverride4_save; + u16 NPHY_REV7_RfctrlOverride5_save, NPHY_REV7_RfctrlOverride6_save; + u16 NPHY_REV7_RfctrlMiscReg3_save, NPHY_REV7_RfctrlMiscReg4_save; + u16 NPHY_REV7_RfctrlMiscReg5_save, NPHY_REV7_RfctrlMiscReg6_save; + + NPHY_REV7_RfctrlOverride3_save = + NPHY_REV7_RfctrlOverride4_save = + NPHY_REV7_RfctrlOverride5_save = + NPHY_REV7_RfctrlOverride6_save = + NPHY_REV7_RfctrlMiscReg3_save = + NPHY_REV7_RfctrlMiscReg4_save = + NPHY_REV7_RfctrlMiscReg5_save = + NPHY_REV7_RfctrlMiscReg6_save = 0; + + classif_state = wlc_phy_classifier_nphy(pi, 0, 0); + wlc_phy_classifier_nphy(pi, (0x7 << 0), 4); + wlc_phy_clip_det_nphy(pi, 0, clip_state); + wlc_phy_clip_det_nphy(pi, 1, clip_off); + + NPHY_Rfctrlintc1_save = read_phy_reg(pi, 0x91); + NPHY_Rfctrlintc2_save = read_phy_reg(pi, 0x92); + NPHY_AfectrlOverride1_save = read_phy_reg(pi, 0x8f); + NPHY_AfectrlOverride2_save = read_phy_reg(pi, 0xa5); + NPHY_AfectrlCore1_save = read_phy_reg(pi, 0xa6); + NPHY_AfectrlCore2_save = read_phy_reg(pi, 0xa7); + NPHY_RfctrlOverride0_save = read_phy_reg(pi, 0xe7); + NPHY_RfctrlOverride1_save = read_phy_reg(pi, 0xec); + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + NPHY_REV7_RfctrlOverride3_save = read_phy_reg(pi, 0x342); + NPHY_REV7_RfctrlOverride4_save = read_phy_reg(pi, 0x343); + NPHY_REV7_RfctrlOverride5_save = read_phy_reg(pi, 0x346); + NPHY_REV7_RfctrlOverride6_save = read_phy_reg(pi, 0x347); + } + NPHY_RfctrlOverrideAux0_save = read_phy_reg(pi, 0xe5); + NPHY_RfctrlOverrideAux1_save = read_phy_reg(pi, 0xe6); + NPHY_RfctrlCmd_save = read_phy_reg(pi, 0x78); + NPHY_RfctrlMiscReg1_save = read_phy_reg(pi, 0xf9); + NPHY_RfctrlMiscReg2_save = read_phy_reg(pi, 0xfb); + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + NPHY_REV7_RfctrlMiscReg3_save = read_phy_reg(pi, 0x340); + NPHY_REV7_RfctrlMiscReg4_save = read_phy_reg(pi, 0x341); + NPHY_REV7_RfctrlMiscReg5_save = read_phy_reg(pi, 0x344); + NPHY_REV7_RfctrlMiscReg6_save = read_phy_reg(pi, 0x345); + } + NPHY_RfctrlRSSIOTHERS1_save = read_phy_reg(pi, 0x7a); + NPHY_RfctrlRSSIOTHERS2_save = read_phy_reg(pi, 0x7d); + + wlc_phy_rfctrlintc_override_nphy(pi, NPHY_RfctrlIntc_override_OFF, 0, + RADIO_MIMO_CORESEL_ALLRXTX); + wlc_phy_rfctrlintc_override_nphy(pi, NPHY_RfctrlIntc_override_TRSW, 1, + RADIO_MIMO_CORESEL_ALLRXTX); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + wlc_phy_rfctrl_override_1tomany_nphy( + pi, + NPHY_REV7_RfctrlOverride_cmd_rxrf_pu, + 0, 0, 0); + else + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 0), 0, 0, 0); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + wlc_phy_rfctrl_override_1tomany_nphy( + pi, + NPHY_REV7_RfctrlOverride_cmd_rx_pu, + 1, 0, 0); + else + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 1), 1, 0, 0); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 7), + 1, 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 6), 1, 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + } else { + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 7), 1, 0, 0); + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 6), 1, 0, 0); + } + + if (CHSPEC_IS5G(pi->radio_chanspec)) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 5), + 0, 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 4), 1, 0, + 0, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + } else { + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 5), 0, 0, 0); + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 4), 1, 0, 0); + } + + } else { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 4), + 0, 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 5), 1, 0, + 0, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + } else { + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 4), 0, 0, 0); + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 5), 1, 0, 0); + } + } + + rxcore_state = wlc_phy_rxcore_getstate_nphy( + (struct brcms_phy_pub *) pi); + + vcm_level_max = 8; + + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + + if ((rxcore_state & (1 << core)) == 0) + continue; + + wlc_phy_scale_offset_rssi_nphy(pi, 0x0, 0x0, + core == + PHY_CORE_0 ? + RADIO_MIMO_CORESEL_CORE1 : + RADIO_MIMO_CORESEL_CORE2, + NPHY_RAIL_I, NPHY_RSSI_SEL_NB); + wlc_phy_scale_offset_rssi_nphy(pi, 0x0, 0x0, + core == + PHY_CORE_0 ? + RADIO_MIMO_CORESEL_CORE1 : + RADIO_MIMO_CORESEL_CORE2, + NPHY_RAIL_Q, NPHY_RSSI_SEL_NB); + + for (vcm = 0; vcm < vcm_level_max; vcm++) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) + mod_radio_reg(pi, (core == PHY_CORE_0) ? + RADIO_2057_NB_MASTER_CORE0 : + RADIO_2057_NB_MASTER_CORE1, + RADIO_2057_VCM_MASK, vcm); + else + mod_radio_reg(pi, RADIO_2056_RX_RSSI_MISC | + ((core == + PHY_CORE_0) ? RADIO_2056_RX0 : + RADIO_2056_RX1), + RADIO_2056_VCM_MASK, + vcm << RADIO_2056_RSSI_VCM_SHIFT); + + wlc_phy_poll_rssi_nphy(pi, NPHY_RSSI_SEL_NB, + &poll_results[vcm][0], + NPHY_RSSICAL_NPOLL); + } + + for (result_idx = 0; result_idx < 4; result_idx++) { + if ((core == result_idx / 2) && + (result_idx % 2 == 0)) { + + min_d = NPHY_RSSICAL_MAXD; + min_vcm = 0; + min_poll = + NPHY_RSSICAL_MAXREAD * + NPHY_RSSICAL_NPOLL + 1; + for (vcm = 0; vcm < vcm_level_max; vcm++) { + curr_d = + poll_results[vcm][result_idx] * + poll_results[vcm][result_idx] + + poll_results[vcm][result_idx + + 1] * + poll_results[vcm][result_idx + + 1]; + if (curr_d < min_d) { + min_d = curr_d; + min_vcm = vcm; + } + if (poll_results[vcm][result_idx] < + min_poll) + min_poll = + poll_results[vcm] + [result_idx]; + } + vcm_final = min_vcm; + poll_results_min[result_idx] = min_poll; + } + } + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + mod_radio_reg(pi, (core == PHY_CORE_0) ? + RADIO_2057_NB_MASTER_CORE0 : + RADIO_2057_NB_MASTER_CORE1, + RADIO_2057_VCM_MASK, vcm_final); + else + mod_radio_reg(pi, RADIO_2056_RX_RSSI_MISC | + ((core == + PHY_CORE_0) ? RADIO_2056_RX0 : + RADIO_2056_RX1), RADIO_2056_VCM_MASK, + vcm_final << RADIO_2056_RSSI_VCM_SHIFT); + + for (result_idx = 0; result_idx < 4; result_idx++) { + if (core == result_idx / 2) { + fine_digital_offset[result_idx] = + (NPHY_RSSICAL_NB_TARGET * + NPHY_RSSICAL_NPOLL) - + poll_results[vcm_final][result_idx]; + if (fine_digital_offset[result_idx] < 0) { + fine_digital_offset[result_idx] = + abs(fine_digital_offset + [result_idx]); + fine_digital_offset[result_idx] += + (NPHY_RSSICAL_NPOLL / 2); + fine_digital_offset[result_idx] /= + NPHY_RSSICAL_NPOLL; + fine_digital_offset[result_idx] = + -fine_digital_offset[ + result_idx]; + } else { + fine_digital_offset[result_idx] += + (NPHY_RSSICAL_NPOLL / 2); + fine_digital_offset[result_idx] /= + NPHY_RSSICAL_NPOLL; + } + + if (poll_results_min[result_idx] == + NPHY_RSSICAL_MAXREAD * NPHY_RSSICAL_NPOLL) + fine_digital_offset[result_idx] = + (NPHY_RSSICAL_NB_TARGET - + NPHY_RSSICAL_MAXREAD - 1); + + wlc_phy_scale_offset_rssi_nphy( + pi, 0x0, + (s8) + fine_digital_offset + [result_idx], + (result_idx / 2 == 0) ? + RADIO_MIMO_CORESEL_CORE1 : + RADIO_MIMO_CORESEL_CORE2, + (result_idx % 2 == 0) ? + NPHY_RAIL_I : NPHY_RAIL_Q, + NPHY_RSSI_SEL_NB); + } + } + + } + + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + + if ((rxcore_state & (1 << core)) == 0) + continue; + + for (wb_cnt = 0; wb_cnt < 2; wb_cnt++) { + if (wb_cnt == 0) { + rssi_type = NPHY_RSSI_SEL_W1; + target_code = NPHY_RSSICAL_W1_TARGET_REV3; + } else { + rssi_type = NPHY_RSSI_SEL_W2; + target_code = NPHY_RSSICAL_W2_TARGET_REV3; + } + + wlc_phy_scale_offset_rssi_nphy(pi, 0x0, 0x0, + core == + PHY_CORE_0 ? + RADIO_MIMO_CORESEL_CORE1 + : + RADIO_MIMO_CORESEL_CORE2, + NPHY_RAIL_I, rssi_type); + wlc_phy_scale_offset_rssi_nphy(pi, 0x0, 0x0, + core == + PHY_CORE_0 ? + RADIO_MIMO_CORESEL_CORE1 + : + RADIO_MIMO_CORESEL_CORE2, + NPHY_RAIL_Q, rssi_type); + + wlc_phy_poll_rssi_nphy(pi, rssi_type, poll_result_core, + NPHY_RSSICAL_NPOLL); + + for (result_idx = 0; result_idx < 4; result_idx++) { + if (core == result_idx / 2) { + fine_digital_offset[result_idx] = + (target_code * + NPHY_RSSICAL_NPOLL) - + poll_result_core[result_idx]; + if (fine_digital_offset[result_idx] < + 0) { + fine_digital_offset[result_idx] + = abs( + fine_digital_offset + [result_idx]); + fine_digital_offset[result_idx] + += (NPHY_RSSICAL_NPOLL + / 2); + fine_digital_offset[result_idx] + /= NPHY_RSSICAL_NPOLL; + fine_digital_offset[result_idx] + = -fine_digital_offset + [result_idx]; + } else { + fine_digital_offset[result_idx] + += (NPHY_RSSICAL_NPOLL + / 2); + fine_digital_offset[result_idx] + /= NPHY_RSSICAL_NPOLL; + } + + wlc_phy_scale_offset_rssi_nphy( + pi, 0x0, + (s8) + fine_digital_offset + [core * + 2], + (core == PHY_CORE_0) ? + RADIO_MIMO_CORESEL_CORE1 : + RADIO_MIMO_CORESEL_CORE2, + (result_idx % 2 == 0) ? + NPHY_RAIL_I : + NPHY_RAIL_Q, + rssi_type); + } + } + + } + } + + write_phy_reg(pi, 0x91, NPHY_Rfctrlintc1_save); + write_phy_reg(pi, 0x92, NPHY_Rfctrlintc2_save); + + wlc_phy_force_rfseq_nphy(pi, NPHY_RFSEQ_RESET2RX); + + mod_phy_reg(pi, 0xe7, (0x1 << 0), 1 << 0); + mod_phy_reg(pi, 0x78, (0x1 << 0), 1 << 0); + mod_phy_reg(pi, 0xe7, (0x1 << 0), 0); + + mod_phy_reg(pi, 0xec, (0x1 << 0), 1 << 0); + mod_phy_reg(pi, 0x78, (0x1 << 1), 1 << 1); + mod_phy_reg(pi, 0xec, (0x1 << 0), 0); + + write_phy_reg(pi, 0x8f, NPHY_AfectrlOverride1_save); + write_phy_reg(pi, 0xa5, NPHY_AfectrlOverride2_save); + write_phy_reg(pi, 0xa6, NPHY_AfectrlCore1_save); + write_phy_reg(pi, 0xa7, NPHY_AfectrlCore2_save); + write_phy_reg(pi, 0xe7, NPHY_RfctrlOverride0_save); + write_phy_reg(pi, 0xec, NPHY_RfctrlOverride1_save); + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + write_phy_reg(pi, 0x342, NPHY_REV7_RfctrlOverride3_save); + write_phy_reg(pi, 0x343, NPHY_REV7_RfctrlOverride4_save); + write_phy_reg(pi, 0x346, NPHY_REV7_RfctrlOverride5_save); + write_phy_reg(pi, 0x347, NPHY_REV7_RfctrlOverride6_save); + } + write_phy_reg(pi, 0xe5, NPHY_RfctrlOverrideAux0_save); + write_phy_reg(pi, 0xe6, NPHY_RfctrlOverrideAux1_save); + write_phy_reg(pi, 0x78, NPHY_RfctrlCmd_save); + write_phy_reg(pi, 0xf9, NPHY_RfctrlMiscReg1_save); + write_phy_reg(pi, 0xfb, NPHY_RfctrlMiscReg2_save); + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + write_phy_reg(pi, 0x340, NPHY_REV7_RfctrlMiscReg3_save); + write_phy_reg(pi, 0x341, NPHY_REV7_RfctrlMiscReg4_save); + write_phy_reg(pi, 0x344, NPHY_REV7_RfctrlMiscReg5_save); + write_phy_reg(pi, 0x345, NPHY_REV7_RfctrlMiscReg6_save); + } + write_phy_reg(pi, 0x7a, NPHY_RfctrlRSSIOTHERS1_save); + write_phy_reg(pi, 0x7d, NPHY_RfctrlRSSIOTHERS2_save); + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + pi->rssical_cache.rssical_radio_regs_2G[0] = + read_radio_reg(pi, RADIO_2057_NB_MASTER_CORE0); + pi->rssical_cache.rssical_radio_regs_2G[1] = + read_radio_reg(pi, RADIO_2057_NB_MASTER_CORE1); + } else { + pi->rssical_cache.rssical_radio_regs_2G[0] = + read_radio_reg(pi, + RADIO_2056_RX_RSSI_MISC | + RADIO_2056_RX0); + pi->rssical_cache.rssical_radio_regs_2G[1] = + read_radio_reg(pi, + RADIO_2056_RX_RSSI_MISC | + RADIO_2056_RX1); + } + + pi->rssical_cache.rssical_phyregs_2G[0] = + read_phy_reg(pi, 0x1a6); + pi->rssical_cache.rssical_phyregs_2G[1] = + read_phy_reg(pi, 0x1ac); + pi->rssical_cache.rssical_phyregs_2G[2] = + read_phy_reg(pi, 0x1b2); + pi->rssical_cache.rssical_phyregs_2G[3] = + read_phy_reg(pi, 0x1b8); + pi->rssical_cache.rssical_phyregs_2G[4] = + read_phy_reg(pi, 0x1a4); + pi->rssical_cache.rssical_phyregs_2G[5] = + read_phy_reg(pi, 0x1aa); + pi->rssical_cache.rssical_phyregs_2G[6] = + read_phy_reg(pi, 0x1b0); + pi->rssical_cache.rssical_phyregs_2G[7] = + read_phy_reg(pi, 0x1b6); + pi->rssical_cache.rssical_phyregs_2G[8] = + read_phy_reg(pi, 0x1a5); + pi->rssical_cache.rssical_phyregs_2G[9] = + read_phy_reg(pi, 0x1ab); + pi->rssical_cache.rssical_phyregs_2G[10] = + read_phy_reg(pi, 0x1b1); + pi->rssical_cache.rssical_phyregs_2G[11] = + read_phy_reg(pi, 0x1b7); + + pi->nphy_rssical_chanspec_2G = pi->radio_chanspec; + } else { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + pi->rssical_cache.rssical_radio_regs_5G[0] = + read_radio_reg(pi, RADIO_2057_NB_MASTER_CORE0); + pi->rssical_cache.rssical_radio_regs_5G[1] = + read_radio_reg(pi, RADIO_2057_NB_MASTER_CORE1); + } else { + pi->rssical_cache.rssical_radio_regs_5G[0] = + read_radio_reg(pi, + RADIO_2056_RX_RSSI_MISC | + RADIO_2056_RX0); + pi->rssical_cache.rssical_radio_regs_5G[1] = + read_radio_reg(pi, + RADIO_2056_RX_RSSI_MISC | + RADIO_2056_RX1); + } + + pi->rssical_cache.rssical_phyregs_5G[0] = + read_phy_reg(pi, 0x1a6); + pi->rssical_cache.rssical_phyregs_5G[1] = + read_phy_reg(pi, 0x1ac); + pi->rssical_cache.rssical_phyregs_5G[2] = + read_phy_reg(pi, 0x1b2); + pi->rssical_cache.rssical_phyregs_5G[3] = + read_phy_reg(pi, 0x1b8); + pi->rssical_cache.rssical_phyregs_5G[4] = + read_phy_reg(pi, 0x1a4); + pi->rssical_cache.rssical_phyregs_5G[5] = + read_phy_reg(pi, 0x1aa); + pi->rssical_cache.rssical_phyregs_5G[6] = + read_phy_reg(pi, 0x1b0); + pi->rssical_cache.rssical_phyregs_5G[7] = + read_phy_reg(pi, 0x1b6); + pi->rssical_cache.rssical_phyregs_5G[8] = + read_phy_reg(pi, 0x1a5); + pi->rssical_cache.rssical_phyregs_5G[9] = + read_phy_reg(pi, 0x1ab); + pi->rssical_cache.rssical_phyregs_5G[10] = + read_phy_reg(pi, 0x1b1); + pi->rssical_cache.rssical_phyregs_5G[11] = + read_phy_reg(pi, 0x1b7); + + pi->nphy_rssical_chanspec_5G = pi->radio_chanspec; + } + + wlc_phy_classifier_nphy(pi, (0x7 << 0), classif_state); + wlc_phy_clip_det_nphy(pi, 1, clip_state); +} + +static void wlc_phy_rssi_cal_nphy_rev2(struct brcms_phy *pi, u8 rssi_type) +{ + s32 target_code; + u16 classif_state; + u16 clip_state[2]; + u16 rssi_ctrl_state[2], pd_state[2]; + u16 rfctrlintc_state[2], rfpdcorerxtx_state[2]; + u16 rfctrlintc_override_val; + u16 clip_off[] = { 0xffff, 0xffff }; + u16 rf_pd_val, pd_mask, rssi_ctrl_mask; + u8 vcm, min_vcm, vcm_tmp[4]; + u8 vcm_final[4] = { 0, 0, 0, 0 }; + u8 result_idx, ctr; + s32 poll_results[4][4] = { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0} + }; + s32 poll_miniq[4][2] = { + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0} + }; + s32 min_d, curr_d; + s32 fine_digital_offset[4]; + s32 poll_results_min[4] = { 0, 0, 0, 0 }; + s32 min_poll; + + switch (rssi_type) { + case NPHY_RSSI_SEL_NB: + target_code = NPHY_RSSICAL_NB_TARGET; + break; + case NPHY_RSSI_SEL_W1: + target_code = NPHY_RSSICAL_W1_TARGET; + break; + case NPHY_RSSI_SEL_W2: + target_code = NPHY_RSSICAL_W2_TARGET; + break; + default: + return; + break; + } + + classif_state = wlc_phy_classifier_nphy(pi, 0, 0); + wlc_phy_classifier_nphy(pi, (0x7 << 0), 4); + wlc_phy_clip_det_nphy(pi, 0, clip_state); + wlc_phy_clip_det_nphy(pi, 1, clip_off); + + rf_pd_val = (rssi_type == NPHY_RSSI_SEL_NB) ? 0x6 : 0x4; + rfctrlintc_override_val = + CHSPEC_IS5G(pi->radio_chanspec) ? 0x140 : 0x110; + + rfctrlintc_state[0] = read_phy_reg(pi, 0x91); + rfpdcorerxtx_state[0] = read_radio_reg(pi, RADIO_2055_PD_CORE1_RXTX); + write_phy_reg(pi, 0x91, rfctrlintc_override_val); + write_radio_reg(pi, RADIO_2055_PD_CORE1_RXTX, rf_pd_val); + + rfctrlintc_state[1] = read_phy_reg(pi, 0x92); + rfpdcorerxtx_state[1] = read_radio_reg(pi, RADIO_2055_PD_CORE2_RXTX); + write_phy_reg(pi, 0x92, rfctrlintc_override_val); + write_radio_reg(pi, RADIO_2055_PD_CORE2_RXTX, rf_pd_val); + + pd_mask = RADIO_2055_NBRSSI_PD | RADIO_2055_WBRSSI_G1_PD | + RADIO_2055_WBRSSI_G2_PD; + pd_state[0] = + read_radio_reg(pi, RADIO_2055_PD_CORE1_RSSI_MISC) & pd_mask; + pd_state[1] = + read_radio_reg(pi, RADIO_2055_PD_CORE2_RSSI_MISC) & pd_mask; + mod_radio_reg(pi, RADIO_2055_PD_CORE1_RSSI_MISC, pd_mask, 0); + mod_radio_reg(pi, RADIO_2055_PD_CORE2_RSSI_MISC, pd_mask, 0); + rssi_ctrl_mask = RADIO_2055_NBRSSI_SEL | RADIO_2055_WBRSSI_G1_SEL | + RADIO_2055_WBRSSI_G2_SEL; + rssi_ctrl_state[0] = + read_radio_reg(pi, RADIO_2055_SP_RSSI_CORE1) & rssi_ctrl_mask; + rssi_ctrl_state[1] = + read_radio_reg(pi, RADIO_2055_SP_RSSI_CORE2) & rssi_ctrl_mask; + wlc_phy_rssisel_nphy(pi, RADIO_MIMO_CORESEL_ALLRX, rssi_type); + + wlc_phy_scale_offset_rssi_nphy(pi, 0x0, 0x0, RADIO_MIMO_CORESEL_ALLRX, + NPHY_RAIL_I, rssi_type); + wlc_phy_scale_offset_rssi_nphy(pi, 0x0, 0x0, RADIO_MIMO_CORESEL_ALLRX, + NPHY_RAIL_Q, rssi_type); + + for (vcm = 0; vcm < 4; vcm++) { + + vcm_tmp[0] = vcm_tmp[1] = vcm_tmp[2] = vcm_tmp[3] = vcm; + if (rssi_type != NPHY_RSSI_SEL_W2) + wlc_phy_set_rssi_2055_vcm(pi, rssi_type, vcm_tmp); + + wlc_phy_poll_rssi_nphy(pi, rssi_type, &poll_results[vcm][0], + NPHY_RSSICAL_NPOLL); + + if ((rssi_type == NPHY_RSSI_SEL_W1) + || (rssi_type == NPHY_RSSI_SEL_W2)) { + for (ctr = 0; ctr < 2; ctr++) + poll_miniq[vcm][ctr] = + min(poll_results[vcm][ctr * 2 + 0], + poll_results[vcm][ctr * 2 + 1]); + } + } + + for (result_idx = 0; result_idx < 4; result_idx++) { + min_d = NPHY_RSSICAL_MAXD; + min_vcm = 0; + min_poll = NPHY_RSSICAL_MAXREAD * NPHY_RSSICAL_NPOLL + 1; + for (vcm = 0; vcm < 4; vcm++) { + curr_d = abs(((rssi_type == NPHY_RSSI_SEL_NB) ? + poll_results[vcm][result_idx] : + poll_miniq[vcm][result_idx / 2]) - + (target_code * NPHY_RSSICAL_NPOLL)); + if (curr_d < min_d) { + min_d = curr_d; + min_vcm = vcm; + } + if (poll_results[vcm][result_idx] < min_poll) + min_poll = poll_results[vcm][result_idx]; + } + vcm_final[result_idx] = min_vcm; + poll_results_min[result_idx] = min_poll; + } + + if (rssi_type != NPHY_RSSI_SEL_W2) + wlc_phy_set_rssi_2055_vcm(pi, rssi_type, vcm_final); + + for (result_idx = 0; result_idx < 4; result_idx++) { + fine_digital_offset[result_idx] = + (target_code * NPHY_RSSICAL_NPOLL) - + poll_results[vcm_final[result_idx]][result_idx]; + if (fine_digital_offset[result_idx] < 0) { + fine_digital_offset[result_idx] = + abs(fine_digital_offset[result_idx]); + fine_digital_offset[result_idx] += + (NPHY_RSSICAL_NPOLL / 2); + fine_digital_offset[result_idx] /= NPHY_RSSICAL_NPOLL; + fine_digital_offset[result_idx] = + -fine_digital_offset[result_idx]; + } else { + fine_digital_offset[result_idx] += + (NPHY_RSSICAL_NPOLL / 2); + fine_digital_offset[result_idx] /= NPHY_RSSICAL_NPOLL; + } + + if (poll_results_min[result_idx] == + NPHY_RSSICAL_MAXREAD * NPHY_RSSICAL_NPOLL) + fine_digital_offset[result_idx] = + (target_code - NPHY_RSSICAL_MAXREAD - 1); + + wlc_phy_scale_offset_rssi_nphy(pi, 0x0, + (s8) + fine_digital_offset[result_idx], + (result_idx / 2 == + 0) ? RADIO_MIMO_CORESEL_CORE1 : + RADIO_MIMO_CORESEL_CORE2, + (result_idx % 2 == + 0) ? NPHY_RAIL_I : NPHY_RAIL_Q, + rssi_type); + } + + mod_radio_reg(pi, RADIO_2055_PD_CORE1_RSSI_MISC, pd_mask, pd_state[0]); + mod_radio_reg(pi, RADIO_2055_PD_CORE2_RSSI_MISC, pd_mask, pd_state[1]); + if (rssi_ctrl_state[0] == RADIO_2055_NBRSSI_SEL) + wlc_phy_rssisel_nphy(pi, RADIO_MIMO_CORESEL_CORE1, + NPHY_RSSI_SEL_NB); + else if (rssi_ctrl_state[0] == RADIO_2055_WBRSSI_G1_SEL) + wlc_phy_rssisel_nphy(pi, RADIO_MIMO_CORESEL_CORE1, + NPHY_RSSI_SEL_W1); + else if (rssi_ctrl_state[0] == RADIO_2055_WBRSSI_G2_SEL) + wlc_phy_rssisel_nphy(pi, RADIO_MIMO_CORESEL_CORE1, + NPHY_RSSI_SEL_W2); + else + wlc_phy_rssisel_nphy(pi, RADIO_MIMO_CORESEL_CORE1, + NPHY_RSSI_SEL_W2); + if (rssi_ctrl_state[1] == RADIO_2055_NBRSSI_SEL) + wlc_phy_rssisel_nphy(pi, RADIO_MIMO_CORESEL_CORE2, + NPHY_RSSI_SEL_NB); + else if (rssi_ctrl_state[1] == RADIO_2055_WBRSSI_G1_SEL) + wlc_phy_rssisel_nphy(pi, RADIO_MIMO_CORESEL_CORE2, + NPHY_RSSI_SEL_W1); + else if (rssi_ctrl_state[1] == RADIO_2055_WBRSSI_G2_SEL) + wlc_phy_rssisel_nphy(pi, RADIO_MIMO_CORESEL_CORE2, + NPHY_RSSI_SEL_W2); + else + wlc_phy_rssisel_nphy(pi, RADIO_MIMO_CORESEL_CORE2, + NPHY_RSSI_SEL_W2); + + wlc_phy_rssisel_nphy(pi, RADIO_MIMO_CORESEL_OFF, rssi_type); + + write_phy_reg(pi, 0x91, rfctrlintc_state[0]); + write_radio_reg(pi, RADIO_2055_PD_CORE1_RXTX, rfpdcorerxtx_state[0]); + write_phy_reg(pi, 0x92, rfctrlintc_state[1]); + write_radio_reg(pi, RADIO_2055_PD_CORE2_RXTX, rfpdcorerxtx_state[1]); + + wlc_phy_classifier_nphy(pi, (0x7 << 0), classif_state); + wlc_phy_clip_det_nphy(pi, 1, clip_state); + + wlc_phy_resetcca_nphy(pi); +} + +void wlc_phy_rssi_cal_nphy(struct brcms_phy *pi) +{ + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + wlc_phy_rssi_cal_nphy_rev3(pi); + } else { + wlc_phy_rssi_cal_nphy_rev2(pi, NPHY_RSSI_SEL_NB); + wlc_phy_rssi_cal_nphy_rev2(pi, NPHY_RSSI_SEL_W1); + wlc_phy_rssi_cal_nphy_rev2(pi, NPHY_RSSI_SEL_W2); + } +} + +int +wlc_phy_rssi_compute_nphy(struct brcms_phy *pi, struct d11rxhdr *rxh) +{ + s16 rxpwr, rxpwr0, rxpwr1; + s16 phyRx0_l, phyRx2_l; + + rxpwr = 0; + rxpwr0 = rxh->PhyRxStatus_1 & PRXS1_nphy_PWR0_MASK; + rxpwr1 = (rxh->PhyRxStatus_1 & PRXS1_nphy_PWR1_MASK) >> 8; + + if (rxpwr0 > 127) + rxpwr0 -= 256; + if (rxpwr1 > 127) + rxpwr1 -= 256; + + phyRx0_l = rxh->PhyRxStatus_0 & 0x00ff; + phyRx2_l = rxh->PhyRxStatus_2 & 0x00ff; + if (phyRx2_l > 127) + phyRx2_l -= 256; + + if (((rxpwr0 == 16) || (rxpwr0 == 32))) { + rxpwr0 = rxpwr1; + rxpwr1 = phyRx2_l; + } + + if (pi->sh->rssi_mode == RSSI_ANT_MERGE_MAX) + rxpwr = (rxpwr0 > rxpwr1) ? rxpwr0 : rxpwr1; + else if (pi->sh->rssi_mode == RSSI_ANT_MERGE_MIN) + rxpwr = (rxpwr0 < rxpwr1) ? rxpwr0 : rxpwr1; + else if (pi->sh->rssi_mode == RSSI_ANT_MERGE_AVG) + rxpwr = (rxpwr0 + rxpwr1) >> 1; + + return rxpwr; +} + +static void +wlc_phy_loadsampletable_nphy(struct brcms_phy *pi, struct cordic_iq *tone_buf, + u16 num_samps) +{ + u16 t; + u32 *data_buf = NULL; + + data_buf = kmalloc(sizeof(u32) * num_samps, GFP_ATOMIC); + if (data_buf == NULL) + return; + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + for (t = 0; t < num_samps; t++) + data_buf[t] = ((((unsigned int)tone_buf[t].i) & 0x3ff) << 10) | + (((unsigned int)tone_buf[t].q) & 0x3ff); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_SAMPLEPLAY, num_samps, 0, 32, + data_buf); + + kfree(data_buf); + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, false); +} + +static u16 +wlc_phy_gen_load_samples_nphy(struct brcms_phy *pi, u32 f_kHz, u16 max_val, + u8 dac_test_mode) +{ + u8 phy_bw, is_phybw40; + u16 num_samps, t, spur; + s32 theta = 0, rot = 0; + u32 tbl_len; + struct cordic_iq *tone_buf = NULL; + + is_phybw40 = CHSPEC_IS40(pi->radio_chanspec); + phy_bw = (is_phybw40 == 1) ? 40 : 20; + tbl_len = (phy_bw << 3); + + if (dac_test_mode == 1) { + spur = read_phy_reg(pi, 0x01); + spur = (spur >> 15) & 1; + phy_bw = (spur == 1) ? 82 : 80; + phy_bw = (is_phybw40 == 1) ? (phy_bw << 1) : phy_bw; + + tbl_len = (phy_bw << 1); + } + + tone_buf = kmalloc(sizeof(struct cordic_iq) * tbl_len, GFP_ATOMIC); + if (tone_buf == NULL) + return 0; + + num_samps = (u16) tbl_len; + rot = ((f_kHz * 36) / phy_bw) / 100; + theta = 0; + + for (t = 0; t < num_samps; t++) { + + tone_buf[t] = cordic_calc_iq(theta); + + theta += rot; + + tone_buf[t].q = (s32) FLOAT(tone_buf[t].q * max_val); + tone_buf[t].i = (s32) FLOAT(tone_buf[t].i * max_val); + } + + wlc_phy_loadsampletable_nphy(pi, tone_buf, num_samps); + + kfree(tone_buf); + + return num_samps; +} + +static void +wlc_phy_runsamples_nphy(struct brcms_phy *pi, u16 num_samps, u16 loops, + u16 wait, u8 iqmode, u8 dac_test_mode, + bool modify_bbmult) +{ + u16 bb_mult; + u8 phy_bw, sample_cmd; + u16 orig_RfseqCoreActv; + u16 lpf_bw_ctl_override3, lpf_bw_ctl_override4, lpf_bw_ctl_miscreg3, + lpf_bw_ctl_miscreg4; + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + phy_bw = 20; + if (CHSPEC_IS40(pi->radio_chanspec)) + phy_bw = 40; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + lpf_bw_ctl_override3 = read_phy_reg(pi, 0x342) & (0x1 << 7); + lpf_bw_ctl_override4 = read_phy_reg(pi, 0x343) & (0x1 << 7); + if (lpf_bw_ctl_override3 | lpf_bw_ctl_override4) { + lpf_bw_ctl_miscreg3 = read_phy_reg(pi, 0x340) & + (0x7 << 8); + lpf_bw_ctl_miscreg4 = read_phy_reg(pi, 0x341) & + (0x7 << 8); + } else { + wlc_phy_rfctrl_override_nphy_rev7( + pi, + (0x1 << 7), + wlc_phy_read_lpf_bw_ctl_nphy + (pi, + 0), 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + + pi->nphy_sample_play_lpf_bw_ctl_ovr = true; + + lpf_bw_ctl_miscreg3 = read_phy_reg(pi, 0x340) & + (0x7 << 8); + lpf_bw_ctl_miscreg4 = read_phy_reg(pi, 0x341) & + (0x7 << 8); + } + } + + if ((pi->nphy_bb_mult_save & BB_MULT_VALID_MASK) == 0) { + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_IQLOCAL, 1, 87, 16, + &bb_mult); + pi->nphy_bb_mult_save = + BB_MULT_VALID_MASK | (bb_mult & BB_MULT_MASK); + } + + if (modify_bbmult) { + bb_mult = (phy_bw == 20) ? 100 : 71; + bb_mult = (bb_mult << 8) + bb_mult; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, 1, 87, 16, + &bb_mult); + } + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, false); + + write_phy_reg(pi, 0xc6, num_samps - 1); + + if (loops != 0xffff) + write_phy_reg(pi, 0xc4, loops - 1); + else + write_phy_reg(pi, 0xc4, loops); + + write_phy_reg(pi, 0xc5, wait); + + orig_RfseqCoreActv = read_phy_reg(pi, 0xa1); + or_phy_reg(pi, 0xa1, NPHY_RfseqMode_CoreActv_override); + if (iqmode) { + + and_phy_reg(pi, 0xc2, 0x7FFF); + + or_phy_reg(pi, 0xc2, 0x8000); + } else { + + sample_cmd = (dac_test_mode == 1) ? 0x5 : 0x1; + write_phy_reg(pi, 0xc3, sample_cmd); + } + + SPINWAIT(((read_phy_reg(pi, 0xa4) & 0x1) == 1), 1000); + + write_phy_reg(pi, 0xa1, orig_RfseqCoreActv); +} + +int +wlc_phy_tx_tone_nphy(struct brcms_phy *pi, u32 f_kHz, u16 max_val, + u8 iqmode, u8 dac_test_mode, bool modify_bbmult) +{ + u16 num_samps; + u16 loops = 0xffff; + u16 wait = 0; + + num_samps = wlc_phy_gen_load_samples_nphy(pi, f_kHz, max_val, + dac_test_mode); + if (num_samps == 0) + return -EBADE; + + wlc_phy_runsamples_nphy(pi, num_samps, loops, wait, iqmode, + dac_test_mode, modify_bbmult); + + return 0; +} + +void wlc_phy_stopplayback_nphy(struct brcms_phy *pi) +{ + u16 playback_status; + u16 bb_mult; + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + playback_status = read_phy_reg(pi, 0xc7); + if (playback_status & 0x1) + or_phy_reg(pi, 0xc3, NPHY_sampleCmd_STOP); + else if (playback_status & 0x2) + and_phy_reg(pi, 0xc2, + (u16) ~NPHY_iqloCalCmdGctl_IQLO_CAL_EN); + + and_phy_reg(pi, 0xc3, (u16) ~(0x1 << 2)); + + if ((pi->nphy_bb_mult_save & BB_MULT_VALID_MASK) != 0) { + + bb_mult = pi->nphy_bb_mult_save & BB_MULT_MASK; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, 1, 87, 16, + &bb_mult); + + pi->nphy_bb_mult_save = 0; + } + + if (NREV_IS(pi->pubpi.phy_rev, 7) || NREV_GE(pi->pubpi.phy_rev, 8)) { + if (pi->nphy_sample_play_lpf_bw_ctl_ovr) { + wlc_phy_rfctrl_override_nphy_rev7( + pi, + (0x1 << 7), + 0, 0, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + pi->nphy_sample_play_lpf_bw_ctl_ovr = false; + } + } + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, false); +} + +static u32 *brcms_phy_get_tx_pwrctrl_tbl(struct brcms_phy *pi) +{ + u32 *tx_pwrctrl_tbl = NULL; + uint phyrev = pi->pubpi.phy_rev; + + if (PHY_IPA(pi)) { + tx_pwrctrl_tbl = + wlc_phy_get_ipa_gaintbl_nphy(pi); + } else { + if (CHSPEC_IS5G(pi->radio_chanspec)) { + if (NREV_IS(phyrev, 3)) + tx_pwrctrl_tbl = nphy_tpc_5GHz_txgain_rev3; + else if (NREV_IS(phyrev, 4)) + tx_pwrctrl_tbl = + (pi->srom_fem5g.extpagain == 3) ? + nphy_tpc_5GHz_txgain_HiPwrEPA : + nphy_tpc_5GHz_txgain_rev4; + else + tx_pwrctrl_tbl = nphy_tpc_5GHz_txgain_rev5; + } else { + if (NREV_GE(phyrev, 7)) { + if (pi->pubpi.radiorev == 3) + tx_pwrctrl_tbl = + nphy_tpc_txgain_epa_2057rev3; + else if (pi->pubpi.radiorev == 5) + tx_pwrctrl_tbl = + nphy_tpc_txgain_epa_2057rev5; + } else { + if (NREV_GE(phyrev, 5) && + (pi->srom_fem2g.extpagain == 3)) + tx_pwrctrl_tbl = + nphy_tpc_txgain_HiPwrEPA; + else + tx_pwrctrl_tbl = + nphy_tpc_txgain_rev3; + } + } + } + return tx_pwrctrl_tbl; +} + +struct nphy_txgains wlc_phy_get_tx_gain_nphy(struct brcms_phy *pi) +{ + u16 base_idx[2], curr_gain[2]; + u8 core_no; + struct nphy_txgains target_gain; + u32 *tx_pwrctrl_tbl = NULL; + + if (pi->nphy_txpwrctrl == PHY_TPC_HW_OFF) { + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_RFSEQ, 2, 0x110, 16, + curr_gain); + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, false); + + for (core_no = 0; core_no < 2; core_no++) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + target_gain.ipa[core_no] = + curr_gain[core_no] & 0x0007; + target_gain.pad[core_no] = + ((curr_gain[core_no] & 0x00F8) >> 3); + target_gain.pga[core_no] = + ((curr_gain[core_no] & 0x0F00) >> 8); + target_gain.txgm[core_no] = + ((curr_gain[core_no] & 0x7000) >> 12); + target_gain.txlpf[core_no] = + ((curr_gain[core_no] & 0x8000) >> 15); + } else if (NREV_GE(pi->pubpi.phy_rev, 3)) { + target_gain.ipa[core_no] = + curr_gain[core_no] & 0x000F; + target_gain.pad[core_no] = + ((curr_gain[core_no] & 0x00F0) >> 4); + target_gain.pga[core_no] = + ((curr_gain[core_no] & 0x0F00) >> 8); + target_gain.txgm[core_no] = + ((curr_gain[core_no] & 0x7000) >> 12); + } else { + target_gain.ipa[core_no] = + curr_gain[core_no] & 0x0003; + target_gain.pad[core_no] = + ((curr_gain[core_no] & 0x000C) >> 2); + target_gain.pga[core_no] = + ((curr_gain[core_no] & 0x0070) >> 4); + target_gain.txgm[core_no] = + ((curr_gain[core_no] & 0x0380) >> 7); + } + } + } else { + uint phyrev = pi->pubpi.phy_rev; + + base_idx[0] = (read_phy_reg(pi, 0x1ed) >> 8) & 0x7f; + base_idx[1] = (read_phy_reg(pi, 0x1ee) >> 8) & 0x7f; + for (core_no = 0; core_no < 2; core_no++) { + if (NREV_GE(phyrev, 3)) { + tx_pwrctrl_tbl = + brcms_phy_get_tx_pwrctrl_tbl(pi); + if (NREV_GE(phyrev, 7)) { + target_gain.ipa[core_no] = + (tx_pwrctrl_tbl + [base_idx[core_no]] + >> 16) & 0x7; + target_gain.pad[core_no] = + (tx_pwrctrl_tbl + [base_idx[core_no]] + >> 19) & 0x1f; + target_gain.pga[core_no] = + (tx_pwrctrl_tbl + [base_idx[core_no]] + >> 24) & 0xf; + target_gain.txgm[core_no] = + (tx_pwrctrl_tbl + [base_idx[core_no]] + >> 28) & 0x7; + target_gain.txlpf[core_no] = + (tx_pwrctrl_tbl + [base_idx[core_no]] + >> 31) & 0x1; + } else { + target_gain.ipa[core_no] = + (tx_pwrctrl_tbl + [base_idx[core_no]] + >> 16) & 0xf; + target_gain.pad[core_no] = + (tx_pwrctrl_tbl + [base_idx[core_no]] + >> 20) & 0xf; + target_gain.pga[core_no] = + (tx_pwrctrl_tbl + [base_idx[core_no]] + >> 24) & 0xf; + target_gain.txgm[core_no] = + (tx_pwrctrl_tbl + [base_idx[core_no]] + >> 28) & 0x7; + } + } else { + target_gain.ipa[core_no] = + (nphy_tpc_txgain[base_idx[core_no]] >> + 16) & 0x3; + target_gain.pad[core_no] = + (nphy_tpc_txgain[base_idx[core_no]] >> + 18) & 0x3; + target_gain.pga[core_no] = + (nphy_tpc_txgain[base_idx[core_no]] >> + 20) & 0x7; + target_gain.txgm[core_no] = + (nphy_tpc_txgain[base_idx[core_no]] >> + 23) & 0x7; + } + } + } + + return target_gain; +} + +static void +wlc_phy_iqcal_gainparams_nphy(struct brcms_phy *pi, u16 core_no, + struct nphy_txgains target_gain, + struct nphy_iqcal_params *params) +{ + u8 k; + int idx; + u16 gain_index; + u8 band_idx = (CHSPEC_IS5G(pi->radio_chanspec) ? 1 : 0); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) + params->txlpf = target_gain.txlpf[core_no]; + + params->txgm = target_gain.txgm[core_no]; + params->pga = target_gain.pga[core_no]; + params->pad = target_gain.pad[core_no]; + params->ipa = target_gain.ipa[core_no]; + if (NREV_GE(pi->pubpi.phy_rev, 7)) + params->cal_gain = + ((params->txlpf << 15) | (params->txgm << 12) | + (params->pga << 8) | + (params->pad << 3) | (params->ipa)); + else + params->cal_gain = + ((params->txgm << 12) | (params->pga << 8) | + (params->pad << 4) | (params->ipa)); + + params->ncorr[0] = 0x79; + params->ncorr[1] = 0x79; + params->ncorr[2] = 0x79; + params->ncorr[3] = 0x79; + params->ncorr[4] = 0x79; + } else { + + gain_index = ((target_gain.pad[core_no] << 0) | + (target_gain.pga[core_no] << 4) | + (target_gain.txgm[core_no] << 8)); + + idx = -1; + for (k = 0; k < NPHY_IQCAL_NUMGAINS; k++) { + if (tbl_iqcal_gainparams_nphy[band_idx][k][0] == + gain_index) { + idx = k; + break; + } + } + + params->txgm = tbl_iqcal_gainparams_nphy[band_idx][k][1]; + params->pga = tbl_iqcal_gainparams_nphy[band_idx][k][2]; + params->pad = tbl_iqcal_gainparams_nphy[band_idx][k][3]; + params->cal_gain = ((params->txgm << 7) | (params->pga << 4) | + (params->pad << 2)); + params->ncorr[0] = tbl_iqcal_gainparams_nphy[band_idx][k][4]; + params->ncorr[1] = tbl_iqcal_gainparams_nphy[band_idx][k][5]; + params->ncorr[2] = tbl_iqcal_gainparams_nphy[band_idx][k][6]; + params->ncorr[3] = tbl_iqcal_gainparams_nphy[band_idx][k][7]; + } +} + +static void wlc_phy_txcal_radio_setup_nphy(struct brcms_phy *pi) +{ + u16 jtag_core, core; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + for (core = 0; core <= 1; core++) { + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 0] = + READ_RADIO_REG3(pi, RADIO_2057, TX, core, + TX_SSI_MASTER); + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 1] = + READ_RADIO_REG3(pi, RADIO_2057, TX, core, + IQCAL_VCM_HG); + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 2] = + READ_RADIO_REG3(pi, RADIO_2057, TX, core, + IQCAL_IDAC); + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 3] = + READ_RADIO_REG3(pi, RADIO_2057, TX, core, + TSSI_VCM); + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 4] = 0; + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 5] = + READ_RADIO_REG3(pi, RADIO_2057, TX, core, + TX_SSI_MUX); + + if (pi->pubpi.radiorev != 5) + pi->tx_rx_cal_radio_saveregs[(core * 11) + 6] = + READ_RADIO_REG3(pi, RADIO_2057, TX, + core, + TSSIA); + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 7] = + READ_RADIO_REG3(pi, RADIO_2057, TX, core, TSSIG); + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 8] = + READ_RADIO_REG3(pi, RADIO_2057, TX, core, + TSSI_MISC1); + + if (CHSPEC_IS5G(pi->radio_chanspec)) { + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TX_SSI_MASTER, 0x0a); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + IQCAL_VCM_HG, 0x43); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + IQCAL_IDAC, 0x55); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TSSI_VCM, 0x00); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TSSIG, 0x00); + if (pi->use_int_tx_iqlo_cal_nphy) { + WRITE_RADIO_REG3(pi, RADIO_2057, TX, + core, TX_SSI_MUX, 0x4); + if (!(pi-> + internal_tx_iqlo_cal_tapoff_intpa_nphy)) + WRITE_RADIO_REG3(pi, RADIO_2057, + TX, core, + TSSIA, 0x31); + else + WRITE_RADIO_REG3(pi, RADIO_2057, + TX, core, + TSSIA, 0x21); + } + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TSSI_MISC1, 0x00); + } else { + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TX_SSI_MASTER, 0x06); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + IQCAL_VCM_HG, 0x43); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + IQCAL_IDAC, 0x55); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TSSI_VCM, 0x00); + + if (pi->pubpi.radiorev != 5) + WRITE_RADIO_REG3(pi, RADIO_2057, TX, + core, TSSIA, 0x00); + if (pi->use_int_tx_iqlo_cal_nphy) { + WRITE_RADIO_REG3(pi, RADIO_2057, TX, + core, TX_SSI_MUX, + 0x06); + if (!(pi-> + internal_tx_iqlo_cal_tapoff_intpa_nphy)) + WRITE_RADIO_REG3(pi, RADIO_2057, + TX, core, + TSSIG, 0x31); + else + WRITE_RADIO_REG3(pi, RADIO_2057, + TX, core, + TSSIG, 0x21); + } + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TSSI_MISC1, 0x00); + } + } + } else if (NREV_GE(pi->pubpi.phy_rev, 3)) { + + for (core = 0; core <= 1; core++) { + jtag_core = + (core == + PHY_CORE_0) ? RADIO_2056_TX0 : RADIO_2056_TX1; + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 0] = + read_radio_reg(pi, + RADIO_2056_TX_TX_SSI_MASTER | + jtag_core); + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 1] = + read_radio_reg(pi, + RADIO_2056_TX_IQCAL_VCM_HG | + jtag_core); + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 2] = + read_radio_reg(pi, + RADIO_2056_TX_IQCAL_IDAC | + jtag_core); + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 3] = + read_radio_reg( + pi, + RADIO_2056_TX_TSSI_VCM | + jtag_core); + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 4] = + read_radio_reg(pi, + RADIO_2056_TX_TX_AMP_DET | + jtag_core); + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 5] = + read_radio_reg(pi, + RADIO_2056_TX_TX_SSI_MUX | + jtag_core); + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 6] = + read_radio_reg(pi, + RADIO_2056_TX_TSSIA | jtag_core); + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 7] = + read_radio_reg(pi, + RADIO_2056_TX_TSSIG | jtag_core); + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 8] = + read_radio_reg(pi, + RADIO_2056_TX_TSSI_MISC1 | + jtag_core); + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 9] = + read_radio_reg(pi, + RADIO_2056_TX_TSSI_MISC2 | + jtag_core); + + pi->tx_rx_cal_radio_saveregs[(core * 11) + 10] = + read_radio_reg(pi, + RADIO_2056_TX_TSSI_MISC3 | + jtag_core); + + if (CHSPEC_IS5G(pi->radio_chanspec)) { + write_radio_reg(pi, + RADIO_2056_TX_TX_SSI_MASTER | + jtag_core, 0x0a); + write_radio_reg(pi, + RADIO_2056_TX_IQCAL_VCM_HG | + jtag_core, 0x40); + write_radio_reg(pi, + RADIO_2056_TX_IQCAL_IDAC | + jtag_core, 0x55); + write_radio_reg(pi, + RADIO_2056_TX_TSSI_VCM | + jtag_core, 0x00); + write_radio_reg(pi, + RADIO_2056_TX_TX_AMP_DET | + jtag_core, 0x00); + + if (PHY_IPA(pi)) { + write_radio_reg( + pi, + RADIO_2056_TX_TX_SSI_MUX + | jtag_core, 0x4); + write_radio_reg(pi, + RADIO_2056_TX_TSSIA | + jtag_core, 0x1); + } else { + write_radio_reg( + pi, + RADIO_2056_TX_TX_SSI_MUX + | jtag_core, 0x00); + write_radio_reg(pi, + RADIO_2056_TX_TSSIA | + jtag_core, 0x2f); + } + write_radio_reg(pi, + RADIO_2056_TX_TSSIG | jtag_core, + 0x00); + write_radio_reg(pi, + RADIO_2056_TX_TSSI_MISC1 | + jtag_core, 0x00); + + write_radio_reg(pi, + RADIO_2056_TX_TSSI_MISC2 | + jtag_core, 0x00); + write_radio_reg(pi, + RADIO_2056_TX_TSSI_MISC3 | + jtag_core, 0x00); + } else { + write_radio_reg(pi, + RADIO_2056_TX_TX_SSI_MASTER | + jtag_core, 0x06); + write_radio_reg(pi, + RADIO_2056_TX_IQCAL_VCM_HG | + jtag_core, 0x40); + write_radio_reg(pi, + RADIO_2056_TX_IQCAL_IDAC | + jtag_core, 0x55); + write_radio_reg(pi, + RADIO_2056_TX_TSSI_VCM | + jtag_core, 0x00); + write_radio_reg(pi, + RADIO_2056_TX_TX_AMP_DET | + jtag_core, 0x00); + write_radio_reg(pi, + RADIO_2056_TX_TSSIA | jtag_core, + 0x00); + + if (PHY_IPA(pi)) { + + write_radio_reg( + pi, + RADIO_2056_TX_TX_SSI_MUX + | jtag_core, 0x06); + if (NREV_LT(pi->pubpi.phy_rev, 5)) + write_radio_reg( + pi, + RADIO_2056_TX_TSSIG + | jtag_core, + 0x11); + else + write_radio_reg( + pi, + RADIO_2056_TX_TSSIG + | jtag_core, + 0x1); + } else { + write_radio_reg( + pi, + RADIO_2056_TX_TX_SSI_MUX + | jtag_core, 0x00); + write_radio_reg(pi, + RADIO_2056_TX_TSSIG | + jtag_core, 0x20); + } + + write_radio_reg(pi, + RADIO_2056_TX_TSSI_MISC1 | + jtag_core, 0x00); + write_radio_reg(pi, + RADIO_2056_TX_TSSI_MISC2 | + jtag_core, 0x00); + write_radio_reg(pi, + RADIO_2056_TX_TSSI_MISC3 | + jtag_core, 0x00); + } + } + } else { + + pi->tx_rx_cal_radio_saveregs[0] = + read_radio_reg(pi, RADIO_2055_CORE1_TXRF_IQCAL1); + write_radio_reg(pi, RADIO_2055_CORE1_TXRF_IQCAL1, 0x29); + pi->tx_rx_cal_radio_saveregs[1] = + read_radio_reg(pi, RADIO_2055_CORE1_TXRF_IQCAL2); + write_radio_reg(pi, RADIO_2055_CORE1_TXRF_IQCAL2, 0x54); + + pi->tx_rx_cal_radio_saveregs[2] = + read_radio_reg(pi, RADIO_2055_CORE2_TXRF_IQCAL1); + write_radio_reg(pi, RADIO_2055_CORE2_TXRF_IQCAL1, 0x29); + pi->tx_rx_cal_radio_saveregs[3] = + read_radio_reg(pi, RADIO_2055_CORE2_TXRF_IQCAL2); + write_radio_reg(pi, RADIO_2055_CORE2_TXRF_IQCAL2, 0x54); + + pi->tx_rx_cal_radio_saveregs[4] = + read_radio_reg(pi, RADIO_2055_PWRDET_RXTX_CORE1); + pi->tx_rx_cal_radio_saveregs[5] = + read_radio_reg(pi, RADIO_2055_PWRDET_RXTX_CORE2); + + if ((read_phy_reg(pi, 0x09) & NPHY_BandControl_currentBand) == + 0) { + + write_radio_reg(pi, RADIO_2055_PWRDET_RXTX_CORE1, 0x04); + write_radio_reg(pi, RADIO_2055_PWRDET_RXTX_CORE2, 0x04); + } else { + + write_radio_reg(pi, RADIO_2055_PWRDET_RXTX_CORE1, 0x20); + write_radio_reg(pi, RADIO_2055_PWRDET_RXTX_CORE2, 0x20); + } + + if (NREV_LT(pi->pubpi.phy_rev, 2)) { + + or_radio_reg(pi, RADIO_2055_CORE1_TX_BB_MXGM, 0x20); + or_radio_reg(pi, RADIO_2055_CORE2_TX_BB_MXGM, 0x20); + } else { + + and_radio_reg(pi, RADIO_2055_CORE1_TX_BB_MXGM, 0xdf); + and_radio_reg(pi, RADIO_2055_CORE2_TX_BB_MXGM, 0xdf); + } + } +} + +static void wlc_phy_txcal_radio_cleanup_nphy(struct brcms_phy *pi) +{ + u16 jtag_core, core; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + for (core = 0; core <= 1; core++) { + + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TX_SSI_MASTER, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 0]); + + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, IQCAL_VCM_HG, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 1]); + + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, IQCAL_IDAC, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 2]); + + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, TSSI_VCM, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 3]); + + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, TX_SSI_MUX, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 5]); + + if (pi->pubpi.radiorev != 5) + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TSSIA, + pi->tx_rx_cal_radio_saveregs + [(core * 11) + 6]); + + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, TSSIG, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 7]); + + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, TSSI_MISC1, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 8]); + } + } else if (NREV_GE(pi->pubpi.phy_rev, 3)) { + for (core = 0; core <= 1; core++) { + jtag_core = (core == PHY_CORE_0) ? + RADIO_2056_TX0 : RADIO_2056_TX1; + + write_radio_reg(pi, + RADIO_2056_TX_TX_SSI_MASTER | jtag_core, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 0]); + + write_radio_reg(pi, + RADIO_2056_TX_IQCAL_VCM_HG | jtag_core, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 1]); + + write_radio_reg(pi, + RADIO_2056_TX_IQCAL_IDAC | jtag_core, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 2]); + + write_radio_reg(pi, RADIO_2056_TX_TSSI_VCM | jtag_core, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 3]); + + write_radio_reg(pi, + RADIO_2056_TX_TX_AMP_DET | jtag_core, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 4]); + + write_radio_reg(pi, + RADIO_2056_TX_TX_SSI_MUX | jtag_core, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 5]); + + write_radio_reg(pi, RADIO_2056_TX_TSSIA | jtag_core, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 6]); + + write_radio_reg(pi, RADIO_2056_TX_TSSIG | jtag_core, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 7]); + + write_radio_reg(pi, + RADIO_2056_TX_TSSI_MISC1 | jtag_core, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 8]); + + write_radio_reg(pi, + RADIO_2056_TX_TSSI_MISC2 | jtag_core, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 9]); + + write_radio_reg(pi, + RADIO_2056_TX_TSSI_MISC3 | jtag_core, + pi-> + tx_rx_cal_radio_saveregs[(core * 11) + + 10]); + } + } else { + + write_radio_reg(pi, RADIO_2055_CORE1_TXRF_IQCAL1, + pi->tx_rx_cal_radio_saveregs[0]); + write_radio_reg(pi, RADIO_2055_CORE1_TXRF_IQCAL2, + pi->tx_rx_cal_radio_saveregs[1]); + write_radio_reg(pi, RADIO_2055_CORE2_TXRF_IQCAL1, + pi->tx_rx_cal_radio_saveregs[2]); + write_radio_reg(pi, RADIO_2055_CORE2_TXRF_IQCAL2, + pi->tx_rx_cal_radio_saveregs[3]); + write_radio_reg(pi, RADIO_2055_PWRDET_RXTX_CORE1, + pi->tx_rx_cal_radio_saveregs[4]); + write_radio_reg(pi, RADIO_2055_PWRDET_RXTX_CORE2, + pi->tx_rx_cal_radio_saveregs[5]); + } +} + +static void wlc_phy_txcal_physetup_nphy(struct brcms_phy *pi) +{ + u16 val, mask; + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + pi->tx_rx_cal_phy_saveregs[0] = read_phy_reg(pi, 0xa6); + pi->tx_rx_cal_phy_saveregs[1] = read_phy_reg(pi, 0xa7); + + mask = ((0x3 << 8) | (0x3 << 10)); + val = (0x2 << 8); + val |= (0x2 << 10); + mod_phy_reg(pi, 0xa6, mask, val); + mod_phy_reg(pi, 0xa7, mask, val); + + val = read_phy_reg(pi, 0x8f); + pi->tx_rx_cal_phy_saveregs[2] = val; + val |= ((0x1 << 9) | (0x1 << 10)); + write_phy_reg(pi, 0x8f, val); + + val = read_phy_reg(pi, 0xa5); + pi->tx_rx_cal_phy_saveregs[3] = val; + val |= ((0x1 << 9) | (0x1 << 10)); + write_phy_reg(pi, 0xa5, val); + + pi->tx_rx_cal_phy_saveregs[4] = read_phy_reg(pi, 0x01); + mod_phy_reg(pi, 0x01, (0x1 << 15), 0); + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 3, 16, + &val); + pi->tx_rx_cal_phy_saveregs[5] = val; + val = 0; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 3, 16, + &val); + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 19, 16, + &val); + pi->tx_rx_cal_phy_saveregs[6] = val; + val = 0; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 19, 16, + &val); + + pi->tx_rx_cal_phy_saveregs[7] = read_phy_reg(pi, 0x91); + pi->tx_rx_cal_phy_saveregs[8] = read_phy_reg(pi, 0x92); + + if (!(pi->use_int_tx_iqlo_cal_nphy)) + wlc_phy_rfctrlintc_override_nphy( + pi, + NPHY_RfctrlIntc_override_PA, + 1, + RADIO_MIMO_CORESEL_CORE1 + | + RADIO_MIMO_CORESEL_CORE2); + else + wlc_phy_rfctrlintc_override_nphy( + pi, + NPHY_RfctrlIntc_override_PA, + 0, + RADIO_MIMO_CORESEL_CORE1 + | + RADIO_MIMO_CORESEL_CORE2); + + wlc_phy_rfctrlintc_override_nphy(pi, + NPHY_RfctrlIntc_override_TRSW, + 0x2, RADIO_MIMO_CORESEL_CORE1); + wlc_phy_rfctrlintc_override_nphy(pi, + NPHY_RfctrlIntc_override_TRSW, + 0x8, RADIO_MIMO_CORESEL_CORE2); + + pi->tx_rx_cal_phy_saveregs[9] = read_phy_reg(pi, 0x297); + pi->tx_rx_cal_phy_saveregs[10] = read_phy_reg(pi, 0x29b); + mod_phy_reg(pi, (0 == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 0), (0) << 0); + + mod_phy_reg(pi, (1 == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 0), (0) << 0); + + if (NREV_IS(pi->pubpi.phy_rev, 7) + || NREV_GE(pi->pubpi.phy_rev, 8)) + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 7), + wlc_phy_read_lpf_bw_ctl_nphy + (pi, + 0), 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + + if (pi->use_int_tx_iqlo_cal_nphy + && !(pi->internal_tx_iqlo_cal_tapoff_intpa_nphy)) { + + if (NREV_IS(pi->pubpi.phy_rev, 7)) { + + mod_radio_reg(pi, RADIO_2057_OVR_REG0, 1 << 4, + 1 << 4); + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + mod_radio_reg( + pi, + RADIO_2057_PAD2G_TUNE_PUS_CORE0, + 1, 0); + mod_radio_reg( + pi, + RADIO_2057_PAD2G_TUNE_PUS_CORE1, + 1, 0); + } else { + mod_radio_reg( + pi, + RADIO_2057_IPA5G_CASCOFFV_PU_CORE0, + 1, 0); + mod_radio_reg( + pi, + RADIO_2057_IPA5G_CASCOFFV_PU_CORE1, + 1, 0); + } + } else if (NREV_GE(pi->pubpi.phy_rev, 8)) { + wlc_phy_rfctrl_override_nphy_rev7( + pi, + (0x1 << 3), 0, + 0x3, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + } + } + } else { + pi->tx_rx_cal_phy_saveregs[0] = read_phy_reg(pi, 0xa6); + pi->tx_rx_cal_phy_saveregs[1] = read_phy_reg(pi, 0xa7); + + mask = ((0x3 << 12) | (0x3 << 14)); + val = (0x2 << 12); + val |= (0x2 << 14); + mod_phy_reg(pi, 0xa6, mask, val); + mod_phy_reg(pi, 0xa7, mask, val); + + val = read_phy_reg(pi, 0xa5); + pi->tx_rx_cal_phy_saveregs[2] = val; + val |= ((0x1 << 12) | (0x1 << 13)); + write_phy_reg(pi, 0xa5, val); + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 2, 16, + &val); + pi->tx_rx_cal_phy_saveregs[3] = val; + val |= 0x2000; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 2, 16, + &val); + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 18, 16, + &val); + pi->tx_rx_cal_phy_saveregs[4] = val; + val |= 0x2000; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 18, 16, + &val); + + pi->tx_rx_cal_phy_saveregs[5] = read_phy_reg(pi, 0x91); + pi->tx_rx_cal_phy_saveregs[6] = read_phy_reg(pi, 0x92); + val = CHSPEC_IS5G(pi->radio_chanspec) ? 0x180 : 0x120; + write_phy_reg(pi, 0x91, val); + write_phy_reg(pi, 0x92, val); + } +} + +static void wlc_phy_txcal_phycleanup_nphy(struct brcms_phy *pi) +{ + u16 mask; + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + write_phy_reg(pi, 0xa6, pi->tx_rx_cal_phy_saveregs[0]); + write_phy_reg(pi, 0xa7, pi->tx_rx_cal_phy_saveregs[1]); + write_phy_reg(pi, 0x8f, pi->tx_rx_cal_phy_saveregs[2]); + write_phy_reg(pi, 0xa5, pi->tx_rx_cal_phy_saveregs[3]); + write_phy_reg(pi, 0x01, pi->tx_rx_cal_phy_saveregs[4]); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 3, 16, + &pi->tx_rx_cal_phy_saveregs[5]); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 19, 16, + &pi->tx_rx_cal_phy_saveregs[6]); + + write_phy_reg(pi, 0x91, pi->tx_rx_cal_phy_saveregs[7]); + write_phy_reg(pi, 0x92, pi->tx_rx_cal_phy_saveregs[8]); + + write_phy_reg(pi, 0x297, pi->tx_rx_cal_phy_saveregs[9]); + write_phy_reg(pi, 0x29b, pi->tx_rx_cal_phy_saveregs[10]); + + if (NREV_IS(pi->pubpi.phy_rev, 7) + || NREV_GE(pi->pubpi.phy_rev, 8)) + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 7), 0, 0, + 1, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + + wlc_phy_resetcca_nphy(pi); + + if (pi->use_int_tx_iqlo_cal_nphy + && !(pi->internal_tx_iqlo_cal_tapoff_intpa_nphy)) { + + if (NREV_IS(pi->pubpi.phy_rev, 7)) { + if (CHSPEC_IS2G(pi->radio_chanspec)) { + mod_radio_reg( + pi, + RADIO_2057_PAD2G_TUNE_PUS_CORE0, + 1, 1); + mod_radio_reg( + pi, + RADIO_2057_PAD2G_TUNE_PUS_CORE1, + 1, 1); + } else { + mod_radio_reg( + pi, + RADIO_2057_IPA5G_CASCOFFV_PU_CORE0, + 1, 1); + mod_radio_reg( + pi, + RADIO_2057_IPA5G_CASCOFFV_PU_CORE1, + 1, 1); + } + + mod_radio_reg(pi, RADIO_2057_OVR_REG0, 1 << 4, + 0); + } else if (NREV_GE(pi->pubpi.phy_rev, 8)) { + wlc_phy_rfctrl_override_nphy_rev7( + pi, + (0x1 << 3), 0, + 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + } + } + } else { + mask = ((0x3 << 12) | (0x3 << 14)); + mod_phy_reg(pi, 0xa6, mask, pi->tx_rx_cal_phy_saveregs[0]); + mod_phy_reg(pi, 0xa7, mask, pi->tx_rx_cal_phy_saveregs[1]); + write_phy_reg(pi, 0xa5, pi->tx_rx_cal_phy_saveregs[2]); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 2, 16, + &pi->tx_rx_cal_phy_saveregs[3]); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_AFECTRL, 1, 18, 16, + &pi->tx_rx_cal_phy_saveregs[4]); + + write_phy_reg(pi, 0x91, pi->tx_rx_cal_phy_saveregs[5]); + write_phy_reg(pi, 0x92, pi->tx_rx_cal_phy_saveregs[6]); + } +} + +void +wlc_phy_est_tonepwr_nphy(struct brcms_phy *pi, s32 *qdBm_pwrbuf, u8 num_samps) +{ + u16 tssi_reg; + s32 temp, pwrindex[2]; + s32 idle_tssi[2]; + s32 rssi_buf[4]; + s32 tssival[2]; + u8 tssi_type; + + tssi_reg = read_phy_reg(pi, 0x1e9); + + temp = (s32) (tssi_reg & 0x3f); + idle_tssi[0] = (temp <= 31) ? temp : (temp - 64); + + temp = (s32) ((tssi_reg >> 8) & 0x3f); + idle_tssi[1] = (temp <= 31) ? temp : (temp - 64); + + tssi_type = + CHSPEC_IS5G(pi->radio_chanspec) ? + (u8)NPHY_RSSI_SEL_TSSI_5G : (u8)NPHY_RSSI_SEL_TSSI_2G; + + wlc_phy_poll_rssi_nphy(pi, tssi_type, rssi_buf, num_samps); + + tssival[0] = rssi_buf[0] / ((s32) num_samps); + tssival[1] = rssi_buf[2] / ((s32) num_samps); + + pwrindex[0] = idle_tssi[0] - tssival[0] + 64; + pwrindex[1] = idle_tssi[1] - tssival[1] + 64; + + if (pwrindex[0] < 0) + pwrindex[0] = 0; + else if (pwrindex[0] > 63) + pwrindex[0] = 63; + + if (pwrindex[1] < 0) + pwrindex[1] = 0; + else if (pwrindex[1] > 63) + pwrindex[1] = 63; + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_CORE1TXPWRCTL, 1, + (u32) pwrindex[0], 32, &qdBm_pwrbuf[0]); + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_CORE2TXPWRCTL, 1, + (u32) pwrindex[1], 32, &qdBm_pwrbuf[1]); +} + +static void wlc_phy_update_txcal_ladder_nphy(struct brcms_phy *pi, u16 core) +{ + int index; + u32 bbmult_scale; + u16 bbmult; + u16 tblentry; + + struct nphy_txiqcal_ladder ladder_lo[] = { + {3, 0}, {4, 0}, {6, 0}, {9, 0}, {13, 0}, {18, 0}, + {25, 0}, {25, 1}, {25, 2}, {25, 3}, {25, 4}, {25, 5}, + {25, 6}, {25, 7}, {35, 7}, {50, 7}, {71, 7}, {100, 7} + }; + + struct nphy_txiqcal_ladder ladder_iq[] = { + {3, 0}, {4, 0}, {6, 0}, {9, 0}, {13, 0}, {18, 0}, + {25, 0}, {35, 0}, {50, 0}, {71, 0}, {100, 0}, {100, 1}, + {100, 2}, {100, 3}, {100, 4}, {100, 5}, {100, 6}, {100, 7} + }; + + bbmult = (core == PHY_CORE_0) ? + ((pi->nphy_txcal_bbmult >> 8) & 0xff) : + (pi->nphy_txcal_bbmult & 0xff); + + for (index = 0; index < 18; index++) { + bbmult_scale = ladder_lo[index].percent * bbmult; + bbmult_scale /= 100; + + tblentry = + ((bbmult_scale & 0xff) << 8) | ladder_lo[index].g_env; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, 1, index, 16, + &tblentry); + + bbmult_scale = ladder_iq[index].percent * bbmult; + bbmult_scale /= 100; + + tblentry = + ((bbmult_scale & 0xff) << 8) | ladder_iq[index].g_env; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, 1, index + 32, + 16, &tblentry); + } +} + +static u8 wlc_phy_txpwr_idx_cur_get_nphy(struct brcms_phy *pi, u8 core) +{ + u16 tmp; + tmp = read_phy_reg(pi, ((core == PHY_CORE_0) ? 0x1ed : 0x1ee)); + + tmp = (tmp & (0x7f << 8)) >> 8; + return (u8) tmp; +} + +static void +wlc_phy_txpwr_idx_cur_set_nphy(struct brcms_phy *pi, u8 idx0, u8 idx1) +{ + mod_phy_reg(pi, 0x1e7, (0x7f << 0), idx0); + + if (NREV_GT(pi->pubpi.phy_rev, 1)) + mod_phy_reg(pi, 0x222, (0xff << 0), idx1); +} + +static u16 wlc_phy_ipa_get_bbmult_nphy(struct brcms_phy *pi) +{ + u16 m0m1; + + wlc_phy_table_read_nphy(pi, 15, 1, 87, 16, &m0m1); + + return m0m1; +} + +static void wlc_phy_ipa_set_bbmult_nphy(struct brcms_phy *pi, u8 m0, u8 m1) +{ + u16 m0m1 = (u16) ((m0 << 8) | m1); + + wlc_phy_table_write_nphy(pi, 15, 1, 87, 16, &m0m1); + wlc_phy_table_write_nphy(pi, 15, 1, 95, 16, &m0m1); +} + +static void +wlc_phy_papd_cal_setup_nphy(struct brcms_phy *pi, + struct nphy_papd_restore_state *state, u8 core) +{ + s32 tone_freq; + u8 off_core; + u16 mixgain = 0; + + off_core = core ^ 0x1; + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + if (NREV_IS(pi->pubpi.phy_rev, 7) + || NREV_GE(pi->pubpi.phy_rev, 8)) + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 7), + wlc_phy_read_lpf_bw_ctl_nphy + (pi, + 0), 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if (pi->pubpi.radiorev == 5) + mixgain = (core == 0) ? 0x20 : 0x00; + else if ((pi->pubpi.radiorev == 7) + || (pi->pubpi.radiorev == 8)) + mixgain = 0x00; + else if ((pi->pubpi.radiorev <= 4) + || (pi->pubpi.radiorev == 6)) + mixgain = 0x00; + } else { + if ((pi->pubpi.radiorev == 4) || + (pi->pubpi.radiorev == 6)) + mixgain = 0x50; + else if ((pi->pubpi.radiorev == 3) + || (pi->pubpi.radiorev == 7) + || (pi->pubpi.radiorev == 8)) + mixgain = 0x0; + } + + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 11), + mixgain, (1 << core), 0, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + + wlc_phy_rfctrl_override_1tomany_nphy( + pi, + NPHY_REV7_RfctrlOverride_cmd_tx_pu, + 1, (1 << core), 0); + wlc_phy_rfctrl_override_1tomany_nphy( + pi, + NPHY_REV7_RfctrlOverride_cmd_tx_pu, + 0, (1 << off_core), 0); + + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 3), + 0, 0x3, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 2), 1, + (1 << core), 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 0), 0, + (1 << core), 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 1), 1, + (1 << core), 0, + NPHY_REV7_RFCTRLOVERRIDE_ID2); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 8), 0, + (1 << core), 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 9), 1, + (1 << core), 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 10), 0, + (1 << core), 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 3), 1, + (1 << core), 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 5), + 0, (1 << core), 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 4), 0, + (1 << core), 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + + state->afectrl[core] = read_phy_reg(pi, (core == PHY_CORE_0) ? + 0xa6 : 0xa7); + state->afeoverride[core] = + read_phy_reg(pi, (core == PHY_CORE_0) ? 0x8f : 0xa5); + state->afectrl[off_core] = + read_phy_reg(pi, (core == PHY_CORE_0) ? 0xa7 : 0xa6); + state->afeoverride[off_core] = + read_phy_reg(pi, (core == PHY_CORE_0) ? 0xa5 : 0x8f); + + mod_phy_reg(pi, ((core == PHY_CORE_0) ? 0xa6 : 0xa7), + (0x1 << 2), 0); + mod_phy_reg(pi, ((core == PHY_CORE_0) ? 0x8f : + 0xa5), (0x1 << 2), (0x1 << 2)); + + mod_phy_reg(pi, ((core == PHY_CORE_0) ? 0xa7 : 0xa6), + (0x1 << 2), (0x1 << 2)); + mod_phy_reg(pi, ((core == PHY_CORE_0) ? 0xa5 : + 0x8f), (0x1 << 2), (0x1 << 2)); + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + state->pwrup[core] = + READ_RADIO_REG3(pi, RADIO_2057, TX, core, + TXRXCOUPLE_2G_PWRUP); + state->atten[core] = + READ_RADIO_REG3(pi, RADIO_2057, TX, core, + TXRXCOUPLE_2G_ATTEN); + state->pwrup[off_core] = + READ_RADIO_REG3(pi, RADIO_2057, TX, off_core, + TXRXCOUPLE_2G_PWRUP); + state->atten[off_core] = + READ_RADIO_REG3(pi, RADIO_2057, TX, off_core, + TXRXCOUPLE_2G_ATTEN); + + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TXRXCOUPLE_2G_PWRUP, 0xc); + + if ((pi->pubpi.radiorev == 3) || + (pi->pubpi.radiorev == 4) || + (pi->pubpi.radiorev == 6)) + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TXRXCOUPLE_2G_ATTEN, 0xf0); + else if (pi->pubpi.radiorev == 5) + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TXRXCOUPLE_2G_ATTEN, + (core == 0) ? 0xf7 : 0xf2); + else if ((pi->pubpi.radiorev == 7) + || (pi->pubpi.radiorev == 8)) + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TXRXCOUPLE_2G_ATTEN, 0xf0); + + WRITE_RADIO_REG3(pi, RADIO_2057, TX, off_core, + TXRXCOUPLE_2G_PWRUP, 0x0); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, off_core, + TXRXCOUPLE_2G_ATTEN, 0xff); + } else { + state->pwrup[core] = + READ_RADIO_REG3(pi, RADIO_2057, TX, core, + TXRXCOUPLE_5G_PWRUP); + state->atten[core] = + READ_RADIO_REG3(pi, RADIO_2057, TX, core, + TXRXCOUPLE_5G_ATTEN); + state->pwrup[off_core] = + READ_RADIO_REG3(pi, RADIO_2057, TX, off_core, + TXRXCOUPLE_5G_PWRUP); + state->atten[off_core] = + READ_RADIO_REG3(pi, RADIO_2057, TX, off_core, + TXRXCOUPLE_5G_ATTEN); + + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TXRXCOUPLE_5G_PWRUP, 0xc); + + if ((pi->pubpi.radiorev == 7) + || (pi->pubpi.radiorev == 8)) + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TXRXCOUPLE_5G_ATTEN, 0xf4); + + else + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TXRXCOUPLE_5G_ATTEN, 0xf0); + + WRITE_RADIO_REG3(pi, RADIO_2057, TX, off_core, + TXRXCOUPLE_5G_PWRUP, 0x0); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, off_core, + TXRXCOUPLE_5G_ATTEN, 0xff); + } + + tone_freq = 4000; + + wlc_phy_tx_tone_nphy(pi, tone_freq, 181, 0, 0, false); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 0), (NPHY_PAPD_COMP_ON) << 0); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x1 << 13), (1) << 13); + + mod_phy_reg(pi, (off_core == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 0), (NPHY_PAPD_COMP_OFF) << 0); + + mod_phy_reg(pi, (off_core == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x1 << 13), (0) << 13); + + } else { + + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 12), 0, 0x3, 0); + + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 3), 1, 0, 0); + + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 0), 0, 0x3, 0); + + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 2), 1, 0x3, 0); + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 1), 1, 0x3, 0); + + state->afectrl[core] = read_phy_reg(pi, (core == PHY_CORE_0) ? + 0xa6 : 0xa7); + state->afeoverride[core] = + read_phy_reg(pi, (core == PHY_CORE_0) ? 0x8f : 0xa5); + + mod_phy_reg(pi, ((core == PHY_CORE_0) ? 0xa6 : 0xa7), + (0x1 << 0) | (0x1 << 1) | (0x1 << 2), 0); + mod_phy_reg(pi, ((core == PHY_CORE_0) ? 0x8f : + 0xa5), + (0x1 << 0) | + (0x1 << 1) | + (0x1 << 2), (0x1 << 0) | (0x1 << 1) | (0x1 << 2)); + + state->vga_master[core] = + READ_RADIO_REG2(pi, RADIO_2056, RX, core, VGA_MASTER); + WRITE_RADIO_REG2(pi, RADIO_2056, RX, core, VGA_MASTER, 0x2b); + if (CHSPEC_IS2G(pi->radio_chanspec)) { + state->fbmix[core] = + READ_RADIO_REG2(pi, RADIO_2056, RX, core, + TXFBMIX_G); + state->intpa_master[core] = + READ_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAG_MASTER); + + WRITE_RADIO_REG2(pi, RADIO_2056, RX, core, TXFBMIX_G, + 0x03); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAG_MASTER, 0x04); + } else { + state->fbmix[core] = + READ_RADIO_REG2(pi, RADIO_2056, RX, core, + TXFBMIX_A); + state->intpa_master[core] = + READ_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAA_MASTER); + + WRITE_RADIO_REG2(pi, RADIO_2056, RX, core, TXFBMIX_A, + 0x03); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAA_MASTER, 0x04); + + } + + tone_freq = 4000; + + wlc_phy_tx_tone_nphy(pi, tone_freq, 181, 0, 0, false); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 0), (1) << 0); + + mod_phy_reg(pi, (off_core == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 0), (0) << 0); + + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 3), 0, 0x3, 0); + } +} + +static void +wlc_phy_papd_cal_cleanup_nphy(struct brcms_phy *pi, + struct nphy_papd_restore_state *state) +{ + u8 core; + + wlc_phy_stopplayback_nphy(pi); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TXRXCOUPLE_2G_PWRUP, 0); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TXRXCOUPLE_2G_ATTEN, + state->atten[core]); + } else { + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TXRXCOUPLE_5G_PWRUP, 0); + WRITE_RADIO_REG3(pi, RADIO_2057, TX, core, + TXRXCOUPLE_5G_ATTEN, + state->atten[core]); + } + } + + if ((pi->pubpi.radiorev == 4) || (pi->pubpi.radiorev == 6)) + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 2), + 1, 0x3, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + else + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 2), + 0, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 1), + 0, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 0), 0, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID2); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 2), 0, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID2); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 11), 1, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 3), 0, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 11), 0, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 12), 0, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 2), 1, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 0), 0, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 1), 1, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID2); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 8), 0, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 9), 1, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 10), 0, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 3), 1, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 5), 0, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 4), 0, 0x3, 1, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + + write_phy_reg(pi, (core == PHY_CORE_0) ? + 0xa6 : 0xa7, state->afectrl[core]); + write_phy_reg(pi, (core == PHY_CORE_0) ? 0x8f : + 0xa5, state->afeoverride[core]); + } + + wlc_phy_ipa_set_bbmult_nphy(pi, (state->mm >> 8) & 0xff, + (state->mm & 0xff)); + + if (NREV_IS(pi->pubpi.phy_rev, 7) + || NREV_GE(pi->pubpi.phy_rev, 8)) + wlc_phy_rfctrl_override_nphy_rev7( + pi, (0x1 << 7), 0, 0, + 1, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + } else { + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 12), 0, 0x3, 1); + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 13), 0, 0x3, 1); + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 0), 0, 0x3, 1); + + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 2), 0, 0x3, 1); + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 1), 0, 0x3, 1); + + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + + WRITE_RADIO_REG2(pi, RADIO_2056, RX, core, VGA_MASTER, + state->vga_master[core]); + if (CHSPEC_IS2G(pi->radio_chanspec)) { + WRITE_RADIO_REG2(pi, RADIO_2056, RX, core, + TXFBMIX_G, state->fbmix[core]); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAG_MASTER, + state->intpa_master[core]); + } else { + WRITE_RADIO_REG2(pi, RADIO_2056, RX, core, + TXFBMIX_A, state->fbmix[core]); + WRITE_RADIO_REG2(pi, RADIO_2056, TX, core, + INTPAA_MASTER, + state->intpa_master[core]); + } + + write_phy_reg(pi, (core == PHY_CORE_0) ? + 0xa6 : 0xa7, state->afectrl[core]); + write_phy_reg(pi, (core == PHY_CORE_0) ? 0x8f : + 0xa5, state->afeoverride[core]); + } + + wlc_phy_ipa_set_bbmult_nphy(pi, (state->mm >> 8) & 0xff, + (state->mm & 0xff)); + + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 3), 0, 0x3, 1); + } +} + +static void +wlc_phy_a1_nphy(struct brcms_phy *pi, u8 core, u32 winsz, u32 start, + u32 end) +{ + u32 *buf, *src, *dst, sz; + + sz = end - start + 1; + + buf = kmalloc(2 * sizeof(u32) * NPHY_PAPD_EPS_TBL_SIZE, GFP_ATOMIC); + if (NULL == buf) + return; + + src = buf; + dst = buf + NPHY_PAPD_EPS_TBL_SIZE; + + wlc_phy_table_read_nphy(pi, + (core == + PHY_CORE_0 ? NPHY_TBL_ID_EPSILONTBL0 : + NPHY_TBL_ID_EPSILONTBL1), + NPHY_PAPD_EPS_TBL_SIZE, 0, 32, src); + + do { + u32 phy_a1, phy_a2; + s32 phy_a3, phy_a4, phy_a5, phy_a6, phy_a7; + + phy_a1 = end - min(end, (winsz >> 1)); + phy_a2 = min_t(u32, NPHY_PAPD_EPS_TBL_SIZE - 1, + end + (winsz >> 1)); + phy_a3 = phy_a2 - phy_a1 + 1; + phy_a6 = 0; + phy_a7 = 0; + + do { + wlc_phy_papd_decode_epsilon(src[phy_a2], &phy_a4, + &phy_a5); + phy_a6 += phy_a4; + phy_a7 += phy_a5; + } while (phy_a2-- != phy_a1); + + phy_a6 /= phy_a3; + phy_a7 /= phy_a3; + dst[end] = ((u32) phy_a7 << 13) | ((u32) phy_a6 & 0x1fff); + } while (end-- != start); + + wlc_phy_table_write_nphy(pi, + (core == + PHY_CORE_0) ? NPHY_TBL_ID_EPSILONTBL0 : + NPHY_TBL_ID_EPSILONTBL1, sz, start, 32, dst); + + kfree(buf); +} + +static void +wlc_phy_a2_nphy(struct brcms_phy *pi, struct nphy_ipa_txcalgains *txgains, + enum phy_cal_mode cal_mode, u8 core) +{ + u16 phy_a1, phy_a2, phy_a3; + u16 phy_a4, phy_a5; + bool phy_a6; + u8 phy_a7, m[2]; + u32 phy_a8 = 0; + struct nphy_txgains phy_a9; + + if (NREV_LT(pi->pubpi.phy_rev, 3)) + return; + + phy_a7 = (core == PHY_CORE_0) ? 1 : 0; + + phy_a6 = ((cal_mode == CAL_GCTRL) + || (cal_mode == CAL_SOFT)) ? true : false; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + phy_a9 = wlc_phy_get_tx_gain_nphy(pi); + + if (CHSPEC_IS2G(pi->radio_chanspec)) + phy_a5 = ((phy_a9.txlpf[core] << 15) | + (phy_a9.txgm[core] << 12) | + (phy_a9.pga[core] << 8) | + (txgains->gains.pad[core] << 3) | + (phy_a9.ipa[core])); + else + phy_a5 = ((phy_a9.txlpf[core] << 15) | + (phy_a9.txgm[core] << 12) | + (txgains->gains.pga[core] << 8) | + (phy_a9.pad[core] << 3) | (phy_a9.ipa[core])); + + wlc_phy_rfctrl_override_1tomany_nphy( + pi, + NPHY_REV7_RfctrlOverride_cmd_txgain, + phy_a5, (1 << core), 0); + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if ((pi->pubpi.radiorev <= 4) + || (pi->pubpi.radiorev == 6)) + m[core] = (pi->bw == WL_CHANSPEC_BW_40) ? + 60 : 79; + else + m[core] = (pi->bw == WL_CHANSPEC_BW_40) ? + 45 : 64; + } else { + m[core] = (pi->bw == WL_CHANSPEC_BW_40) ? 75 : 107; + } + + m[phy_a7] = 0; + wlc_phy_ipa_set_bbmult_nphy(pi, m[0], m[1]); + + phy_a2 = 63; + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if ((pi->pubpi.radiorev == 4) + || (pi->pubpi.radiorev == 6)) { + phy_a1 = 30; + phy_a3 = 30; + } else { + phy_a1 = 25; + phy_a3 = 25; + } + } else { + if ((pi->pubpi.radiorev == 5) + || (pi->pubpi.radiorev == 7) + || (pi->pubpi.radiorev == 8)) { + phy_a1 = 25; + phy_a3 = 25; + } else { + phy_a1 = 35; + phy_a3 = 35; + } + } + + if (cal_mode == CAL_GCTRL) { + if ((pi->pubpi.radiorev == 5) + && (CHSPEC_IS2G(pi->radio_chanspec))) + phy_a1 = 55; + else if (((pi->pubpi.radiorev == 7) && + (CHSPEC_IS2G(pi->radio_chanspec))) || + ((pi->pubpi.radiorev == 8) && + (CHSPEC_IS2G(pi->radio_chanspec)))) + phy_a1 = 60; + else + phy_a1 = 63; + + } else if ((cal_mode != CAL_FULL) && (cal_mode != CAL_SOFT)) { + + phy_a1 = 35; + phy_a3 = 35; + } + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 0), (1) << 0); + + mod_phy_reg(pi, (phy_a7 == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 0), (0) << 0); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x1 << 13), (1) << 13); + + mod_phy_reg(pi, (phy_a7 == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x1 << 13), (0) << 13); + + write_phy_reg(pi, 0x2a1, 0x80); + write_phy_reg(pi, 0x2a2, 0x100); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x7 << 4), (11) << 4); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x7 << 8), (11) << 8); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x7 << 0), (0x3) << 0); + + write_phy_reg(pi, 0x2e5, 0x20); + + mod_phy_reg(pi, 0x2a0, (0x3f << 0), (phy_a3) << 0); + + mod_phy_reg(pi, 0x29f, (0x3f << 0), (phy_a1) << 0); + + mod_phy_reg(pi, 0x29f, (0x3f << 8), (phy_a2) << 8); + + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 3), + 1, ((core == 0) ? 1 : 2), 0, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 3), + 0, ((core == 0) ? 2 : 1), 0, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + + write_phy_reg(pi, 0x2be, 1); + SPINWAIT(read_phy_reg(pi, 0x2be), 10 * 1000 * 1000); + + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 3), + 0, 0x3, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + + wlc_phy_table_write_nphy(pi, + (core == + PHY_CORE_0) ? NPHY_TBL_ID_EPSILONTBL0 + : NPHY_TBL_ID_EPSILONTBL1, 1, phy_a3, + 32, &phy_a8); + + if (cal_mode != CAL_GCTRL) { + if (CHSPEC_IS5G(pi->radio_chanspec)) + wlc_phy_a1_nphy(pi, core, 5, 0, 35); + } + + wlc_phy_rfctrl_override_1tomany_nphy( + pi, + NPHY_REV7_RfctrlOverride_cmd_txgain, + phy_a5, (1 << core), 1); + + } else { + + if (txgains) { + if (txgains->useindex) { + phy_a4 = 15 - ((txgains->index) >> 3); + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if (NREV_GE(pi->pubpi.phy_rev, 6) && + pi->sh->chip == BCMA_CHIP_ID_BCM47162) { + phy_a5 = 0x10f7 | (phy_a4 << 8); + } else if (NREV_GE(pi->pubpi.phy_rev, 6)) { + phy_a5 = 0x00f7 | (phy_a4 << 8); + } else if (NREV_IS(pi->pubpi.phy_rev, 5)) { + phy_a5 = 0x10f7 | (phy_a4 << 8); + } else { + phy_a5 = 0x50f7 | (phy_a4 << 8); + } + } else { + phy_a5 = 0x70f7 | (phy_a4 << 8); + } + wlc_phy_rfctrl_override_nphy(pi, + (0x1 << 13), + phy_a5, + (1 << core), 0); + } else { + wlc_phy_rfctrl_override_nphy(pi, + (0x1 << 13), + 0x5bf7, + (1 << core), 0); + } + } + + if (CHSPEC_IS2G(pi->radio_chanspec)) + m[core] = (pi->bw == WL_CHANSPEC_BW_40) ? 45 : 64; + else + m[core] = (pi->bw == WL_CHANSPEC_BW_40) ? 75 : 107; + + m[phy_a7] = 0; + wlc_phy_ipa_set_bbmult_nphy(pi, m[0], m[1]); + + phy_a2 = 63; + + if (cal_mode == CAL_FULL) { + phy_a1 = 25; + phy_a3 = 25; + } else if (cal_mode == CAL_SOFT) { + phy_a1 = 25; + phy_a3 = 25; + } else if (cal_mode == CAL_GCTRL) { + phy_a1 = 63; + phy_a3 = 25; + } else { + + phy_a1 = 25; + phy_a3 = 25; + } + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 0), (1) << 0); + + mod_phy_reg(pi, (phy_a7 == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 0), (0) << 0); + + if (NREV_GE(pi->pubpi.phy_rev, 6)) { + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x1 << 13), (1) << 13); + + mod_phy_reg(pi, (phy_a7 == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x1 << 13), (0) << 13); + + write_phy_reg(pi, 0x2a1, 0x20); + write_phy_reg(pi, 0x2a2, 0x60); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0xf << 4), (9) << 4); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0xf << 8), (9) << 8); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0xf << 0), (0x2) << 0); + + write_phy_reg(pi, 0x2e5, 0x20); + } else { + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x1 << 11), (1) << 11); + + mod_phy_reg(pi, (phy_a7 == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x1 << 11), (0) << 11); + + write_phy_reg(pi, 0x2a1, 0x80); + write_phy_reg(pi, 0x2a2, 0x600); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x7 << 4), (0) << 4); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x7 << 8), (0) << 8); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x7 << 0), (0x3) << 0); + + mod_phy_reg(pi, 0x2a0, (0x3f << 8), (0x20) << 8); + + } + + mod_phy_reg(pi, 0x2a0, (0x3f << 0), (phy_a3) << 0); + + mod_phy_reg(pi, 0x29f, (0x3f << 0), (phy_a1) << 0); + + mod_phy_reg(pi, 0x29f, (0x3f << 8), (phy_a2) << 8); + + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 3), 1, 0x3, 0); + + write_phy_reg(pi, 0x2be, 1); + SPINWAIT(read_phy_reg(pi, 0x2be), 10 * 1000 * 1000); + + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 3), 0, 0x3, 0); + + wlc_phy_table_write_nphy(pi, + (core == + PHY_CORE_0) ? NPHY_TBL_ID_EPSILONTBL0 + : NPHY_TBL_ID_EPSILONTBL1, 1, phy_a3, + 32, &phy_a8); + + if (cal_mode != CAL_GCTRL) + wlc_phy_a1_nphy(pi, core, 5, 0, 40); + } +} + +static u8 wlc_phy_a3_nphy(struct brcms_phy *pi, u8 start_gain, u8 core) +{ + int phy_a1; + int phy_a2; + bool phy_a3; + struct nphy_ipa_txcalgains phy_a4; + bool phy_a5 = false; + bool phy_a6 = true; + s32 phy_a7, phy_a8; + u32 phy_a9; + int phy_a10; + bool phy_a11 = false; + int phy_a12; + u8 phy_a13 = 0; + u8 phy_a14; + u8 *phy_a15 = NULL; + + phy_a4.useindex = true; + phy_a12 = start_gain; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + phy_a2 = 20; + phy_a1 = 1; + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if (pi->pubpi.radiorev == 5) { + + phy_a15 = pad_gain_codes_used_2057rev5; + phy_a13 = + ARRAY_SIZE(pad_gain_codes_used_2057rev5) - 1; + + } else if ((pi->pubpi.radiorev == 7) + || (pi->pubpi.radiorev == 8)) { + + phy_a15 = pad_gain_codes_used_2057rev7; + phy_a13 = + ARRAY_SIZE(pad_gain_codes_used_2057rev7) - 1; + + } else { + + phy_a15 = pad_all_gain_codes_2057; + phy_a13 = ARRAY_SIZE(pad_all_gain_codes_2057) - + 1; + } + + } else { + + phy_a15 = pga_all_gain_codes_2057; + phy_a13 = ARRAY_SIZE(pga_all_gain_codes_2057) - 1; + } + + phy_a14 = 0; + + for (phy_a10 = 0; phy_a10 < phy_a2; phy_a10++) { + if (CHSPEC_IS2G(pi->radio_chanspec)) + phy_a4.gains.pad[core] = + (u16) phy_a15[phy_a12]; + else + phy_a4.gains.pga[core] = + (u16) phy_a15[phy_a12]; + + wlc_phy_a2_nphy(pi, &phy_a4, CAL_GCTRL, core); + + wlc_phy_table_read_nphy(pi, + (core == + PHY_CORE_0 ? + NPHY_TBL_ID_EPSILONTBL0 : + NPHY_TBL_ID_EPSILONTBL1), 1, + 63, 32, &phy_a9); + + wlc_phy_papd_decode_epsilon(phy_a9, &phy_a7, &phy_a8); + + phy_a3 = ((phy_a7 == 4095) || (phy_a7 == -4096) || + (phy_a8 == 4095) || (phy_a8 == -4096)); + + if (!phy_a6 && (phy_a3 != phy_a5)) { + if (!phy_a3) + phy_a12 -= (u8) phy_a1; + + phy_a11 = true; + break; + } + + if (phy_a3) + phy_a12 += (u8) phy_a1; + else + phy_a12 -= (u8) phy_a1; + + if ((phy_a12 < phy_a14) || (phy_a12 > phy_a13)) { + if (phy_a12 < phy_a14) + phy_a12 = phy_a14; + else + phy_a12 = phy_a13; + + phy_a11 = true; + break; + } + + phy_a6 = false; + phy_a5 = phy_a3; + } + + } else { + phy_a2 = 10; + phy_a1 = 8; + for (phy_a10 = 0; phy_a10 < phy_a2; phy_a10++) { + phy_a4.index = (u8) phy_a12; + wlc_phy_a2_nphy(pi, &phy_a4, CAL_GCTRL, core); + + wlc_phy_table_read_nphy(pi, + (core == + PHY_CORE_0 ? + NPHY_TBL_ID_EPSILONTBL0 : + NPHY_TBL_ID_EPSILONTBL1), 1, + 63, 32, &phy_a9); + + wlc_phy_papd_decode_epsilon(phy_a9, &phy_a7, &phy_a8); + + phy_a3 = ((phy_a7 == 4095) || (phy_a7 == -4096) || + (phy_a8 == 4095) || (phy_a8 == -4096)); + + if (!phy_a6 && (phy_a3 != phy_a5)) { + if (!phy_a3) + phy_a12 -= (u8) phy_a1; + + phy_a11 = true; + break; + } + + if (phy_a3) + phy_a12 += (u8) phy_a1; + else + phy_a12 -= (u8) phy_a1; + + if ((phy_a12 < 0) || (phy_a12 > 127)) { + if (phy_a12 < 0) + phy_a12 = 0; + else + phy_a12 = 127; + + phy_a11 = true; + break; + } + + phy_a6 = false; + phy_a5 = phy_a3; + } + + } + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + return (u8) phy_a15[phy_a12]; + else + return (u8) phy_a12; + +} + +static void wlc_phy_a4(struct brcms_phy *pi, bool full_cal) +{ + struct nphy_ipa_txcalgains phy_b1[2]; + struct nphy_papd_restore_state phy_b2; + bool phy_b3; + u8 phy_b4; + u8 phy_b5; + s16 phy_b6, phy_b7, phy_b8; + u16 phy_b9; + s16 phy_b10, phy_b11, phy_b12; + + phy_b11 = 0; + phy_b12 = 0; + phy_b7 = 0; + phy_b8 = 0; + phy_b6 = 0; + + if (pi->nphy_papd_skip == 1) + return; + + phy_b3 = (0 == (bcma_read32(pi->d11core, D11REGOFFS(maccontrol)) & + MCTL_EN_MAC)); + if (!phy_b3) + wlapi_suspend_mac_and_wait(pi->sh->physhim); + + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + pi->nphy_force_papd_cal = false; + + for (phy_b5 = 0; phy_b5 < pi->pubpi.phy_corenum; phy_b5++) + pi->nphy_papd_tx_gain_at_last_cal[phy_b5] = + wlc_phy_txpwr_idx_cur_get_nphy(pi, phy_b5); + + pi->nphy_papd_last_cal = pi->sh->now; + pi->nphy_papd_recal_counter++; + + phy_b4 = pi->nphy_txpwrctrl; + wlc_phy_txpwrctrl_enable_nphy(pi, PHY_TPC_HW_OFF); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_SCALARTBL0, 64, 0, 32, + nphy_papd_scaltbl); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_SCALARTBL1, 64, 0, 32, + nphy_papd_scaltbl); + + phy_b9 = read_phy_reg(pi, 0x01); + mod_phy_reg(pi, 0x01, (0x1 << 15), 0); + + for (phy_b5 = 0; phy_b5 < pi->pubpi.phy_corenum; phy_b5++) { + s32 i, val = 0; + for (i = 0; i < 64; i++) + wlc_phy_table_write_nphy(pi, + ((phy_b5 == + PHY_CORE_0) ? + NPHY_TBL_ID_EPSILONTBL0 : + NPHY_TBL_ID_EPSILONTBL1), 1, + i, 32, &val); + } + + wlc_phy_ipa_restore_tx_digi_filts_nphy(pi); + + phy_b2.mm = wlc_phy_ipa_get_bbmult_nphy(pi); + for (phy_b5 = 0; phy_b5 < pi->pubpi.phy_corenum; phy_b5++) { + wlc_phy_papd_cal_setup_nphy(pi, &phy_b2, phy_b5); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if ((pi->pubpi.radiorev == 3) + || (pi->pubpi.radiorev == 4) + || (pi->pubpi.radiorev == 6)) { + pi->nphy_papd_cal_gain_index[phy_b5] = + 23; + } else if (pi->pubpi.radiorev == 5) { + pi->nphy_papd_cal_gain_index[phy_b5] = + 0; + pi->nphy_papd_cal_gain_index[phy_b5] = + wlc_phy_a3_nphy( + pi, + pi-> + nphy_papd_cal_gain_index + [phy_b5], + phy_b5); + + } else if ((pi->pubpi.radiorev == 7) + || (pi->pubpi.radiorev == 8)) { + + pi->nphy_papd_cal_gain_index[phy_b5] = + 0; + pi->nphy_papd_cal_gain_index[phy_b5] = + wlc_phy_a3_nphy( + pi, + pi-> + nphy_papd_cal_gain_index + [phy_b5], + phy_b5); + + } + + phy_b1[phy_b5].gains.pad[phy_b5] = + pi->nphy_papd_cal_gain_index[phy_b5]; + + } else { + pi->nphy_papd_cal_gain_index[phy_b5] = 0; + pi->nphy_papd_cal_gain_index[phy_b5] = + wlc_phy_a3_nphy( + pi, + pi-> + nphy_papd_cal_gain_index + [phy_b5], phy_b5); + phy_b1[phy_b5].gains.pga[phy_b5] = + pi->nphy_papd_cal_gain_index[phy_b5]; + } + } else { + phy_b1[phy_b5].useindex = true; + phy_b1[phy_b5].index = 16; + phy_b1[phy_b5].index = + wlc_phy_a3_nphy(pi, phy_b1[phy_b5].index, + phy_b5); + + pi->nphy_papd_cal_gain_index[phy_b5] = + 15 - ((phy_b1[phy_b5].index) >> 3); + } + + switch (pi->nphy_papd_cal_type) { + case 0: + wlc_phy_a2_nphy(pi, &phy_b1[phy_b5], CAL_FULL, phy_b5); + break; + case 1: + wlc_phy_a2_nphy(pi, &phy_b1[phy_b5], CAL_SOFT, phy_b5); + break; + } + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + wlc_phy_papd_cal_cleanup_nphy(pi, &phy_b2); + } + + if (NREV_LT(pi->pubpi.phy_rev, 7)) + wlc_phy_papd_cal_cleanup_nphy(pi, &phy_b2); + + for (phy_b5 = 0; phy_b5 < pi->pubpi.phy_corenum; phy_b5++) { + int eps_offset = 0; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + if (CHSPEC_IS2G(pi->radio_chanspec)) { + if (pi->pubpi.radiorev == 3) + eps_offset = -2; + else if (pi->pubpi.radiorev == 5) + eps_offset = 3; + else + eps_offset = -1; + } else { + eps_offset = 2; + } + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + phy_b8 = phy_b1[phy_b5].gains.pad[phy_b5]; + phy_b10 = 0; + if ((pi->pubpi.radiorev == 3) || + (pi->pubpi.radiorev == 4) || + (pi->pubpi.radiorev == 6)) { + phy_b12 = -( + nphy_papd_padgain_dlt_2g_2057rev3n4 + [phy_b8] + 1) / 2; + phy_b10 = -1; + } else if (pi->pubpi.radiorev == 5) { + phy_b12 = -( + nphy_papd_padgain_dlt_2g_2057rev5 + [phy_b8] + 1) / 2; + } else if ((pi->pubpi.radiorev == 7) || + (pi->pubpi.radiorev == 8)) { + phy_b12 = -( + nphy_papd_padgain_dlt_2g_2057rev7 + [phy_b8] + 1) / 2; + } + } else { + phy_b7 = phy_b1[phy_b5].gains.pga[phy_b5]; + if ((pi->pubpi.radiorev == 3) || + (pi->pubpi.radiorev == 4) || + (pi->pubpi.radiorev == 6)) + phy_b11 = + -(nphy_papd_pgagain_dlt_5g_2057 + [phy_b7] + + 1) / 2; + else if ((pi->pubpi.radiorev == 7) + || (pi->pubpi.radiorev == 8)) + phy_b11 = -( + nphy_papd_pgagain_dlt_5g_2057rev7 + [phy_b7] + 1) / 2; + + phy_b10 = -9; + } + + if (CHSPEC_IS2G(pi->radio_chanspec)) + phy_b6 = + -60 + 27 + eps_offset + phy_b12 + + phy_b10; + else + phy_b6 = + -60 + 27 + eps_offset + phy_b11 + + phy_b10; + + mod_phy_reg(pi, (phy_b5 == PHY_CORE_0) ? 0x298 : + 0x29c, (0x1ff << 7), (phy_b6) << 7); + + pi->nphy_papd_epsilon_offset[phy_b5] = phy_b6; + } else { + if (NREV_LT(pi->pubpi.phy_rev, 5)) + eps_offset = 4; + else + eps_offset = 2; + + phy_b7 = 15 - ((phy_b1[phy_b5].index) >> 3); + + if (CHSPEC_IS2G(pi->radio_chanspec)) { + phy_b11 = + -(nphy_papd_pga_gain_delta_ipa_2g[ + phy_b7] + + 1) / 2; + phy_b10 = 0; + } else { + phy_b11 = + -(nphy_papd_pga_gain_delta_ipa_5g[ + phy_b7] + + 1) / 2; + phy_b10 = -9; + } + + phy_b6 = -60 + 27 + eps_offset + phy_b11 + phy_b10; + + mod_phy_reg(pi, (phy_b5 == PHY_CORE_0) ? 0x298 : + 0x29c, (0x1ff << 7), (phy_b6) << 7); + + pi->nphy_papd_epsilon_offset[phy_b5] = phy_b6; + } + } + + mod_phy_reg(pi, (0 == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 0), (NPHY_PAPD_COMP_ON) << 0); + + mod_phy_reg(pi, (1 == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 0), (NPHY_PAPD_COMP_ON) << 0); + + if (NREV_GE(pi->pubpi.phy_rev, 6)) { + mod_phy_reg(pi, (0 == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x1 << 13), (0) << 13); + + mod_phy_reg(pi, (1 == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x1 << 13), (0) << 13); + + } else { + mod_phy_reg(pi, (0 == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x1 << 11), (0) << 11); + + mod_phy_reg(pi, (1 == PHY_CORE_0) ? 0x2a3 : + 0x2a4, (0x1 << 11), (0) << 11); + + } + pi->nphy_papdcomp = NPHY_PAPD_COMP_ON; + + write_phy_reg(pi, 0x01, phy_b9); + + wlc_phy_ipa_set_tx_digi_filts_nphy(pi); + + wlc_phy_txpwrctrl_enable_nphy(pi, phy_b4); + if (phy_b4 == PHY_TPC_HW_OFF) { + wlc_phy_txpwr_index_nphy(pi, (1 << 0), + (s8) (pi->nphy_txpwrindex[0]. + index_internal), false); + wlc_phy_txpwr_index_nphy(pi, (1 << 1), + (s8) (pi->nphy_txpwrindex[1]. + index_internal), false); + } + + wlc_phy_stay_in_carriersearch_nphy(pi, false); + + if (!phy_b3) + wlapi_enable_mac(pi->sh->physhim); +} + +void wlc_phy_cal_perical_nphy_run(struct brcms_phy *pi, u8 caltype) +{ + struct nphy_txgains target_gain; + u8 tx_pwr_ctrl_state; + bool fullcal = true; + bool restore_tx_gain = false; + bool mphase; + + if (PHY_MUTED(pi)) + return; + + if (caltype == PHY_PERICAL_AUTO) + fullcal = (pi->radio_chanspec != pi->nphy_txiqlocal_chanspec); + else if (caltype == PHY_PERICAL_PARTIAL) + fullcal = false; + + if (pi->cal_type_override != PHY_PERICAL_AUTO) + fullcal = + (pi->cal_type_override == + PHY_PERICAL_FULL) ? true : false; + + if ((pi->mphase_cal_phase_id > MPHASE_CAL_STATE_INIT)) { + if (pi->nphy_txiqlocal_chanspec != pi->radio_chanspec) + wlc_phy_cal_perical_mphase_restart(pi); + } + + if ((pi->mphase_cal_phase_id == MPHASE_CAL_STATE_RXCAL)) + wlapi_bmac_write_shm(pi->sh->physhim, M_CTS_DURATION, 10000); + + wlapi_suspend_mac_and_wait(pi->sh->physhim); + + wlc_phyreg_enter((struct brcms_phy_pub *) pi); + + if ((pi->mphase_cal_phase_id == MPHASE_CAL_STATE_IDLE) || + (pi->mphase_cal_phase_id == MPHASE_CAL_STATE_INIT)) { + pi->nphy_cal_orig_pwr_idx[0] = + (u8) ((read_phy_reg(pi, 0x1ed) >> 8) & 0x7f); + pi->nphy_cal_orig_pwr_idx[1] = + (u8) ((read_phy_reg(pi, 0x1ee) >> 8) & 0x7f); + + if (pi->nphy_txpwrctrl != PHY_TPC_HW_OFF) { + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_RFSEQ, 2, + 0x110, 16, + pi->nphy_cal_orig_tx_gain); + } else { + pi->nphy_cal_orig_tx_gain[0] = 0; + pi->nphy_cal_orig_tx_gain[1] = 0; + } + } + target_gain = wlc_phy_get_tx_gain_nphy(pi); + tx_pwr_ctrl_state = pi->nphy_txpwrctrl; + wlc_phy_txpwrctrl_enable_nphy(pi, PHY_TPC_HW_OFF); + + if (pi->antsel_type == ANTSEL_2x3) + wlc_phy_antsel_init((struct brcms_phy_pub *) pi, true); + + mphase = (pi->mphase_cal_phase_id != MPHASE_CAL_STATE_IDLE); + if (!mphase) { + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + wlc_phy_precal_txgain_nphy(pi); + pi->nphy_cal_target_gain = wlc_phy_get_tx_gain_nphy(pi); + restore_tx_gain = true; + + target_gain = pi->nphy_cal_target_gain; + } + if (0 == + wlc_phy_cal_txiqlo_nphy(pi, target_gain, fullcal, + mphase)) { + if (PHY_IPA(pi)) + wlc_phy_a4(pi, true); + + wlc_phyreg_exit((struct brcms_phy_pub *) pi); + wlapi_enable_mac(pi->sh->physhim); + wlapi_bmac_write_shm(pi->sh->physhim, M_CTS_DURATION, + 10000); + wlapi_suspend_mac_and_wait(pi->sh->physhim); + wlc_phyreg_enter((struct brcms_phy_pub *) pi); + + if (0 == wlc_phy_cal_rxiq_nphy(pi, target_gain, + (pi->first_cal_after_assoc || + (pi->cal_type_override == + PHY_PERICAL_FULL)) ? 2 : 0, false)) { + wlc_phy_savecal_nphy(pi); + + wlc_phy_txpwrctrl_coeff_setup_nphy(pi); + + pi->nphy_perical_last = pi->sh->now; + } + } + if (caltype != PHY_PERICAL_AUTO) + wlc_phy_rssi_cal_nphy(pi); + + if (pi->first_cal_after_assoc + || (pi->cal_type_override == PHY_PERICAL_FULL)) { + pi->first_cal_after_assoc = false; + wlc_phy_txpwrctrl_idle_tssi_nphy(pi); + wlc_phy_txpwrctrl_pwr_setup_nphy(pi); + } + + if (NREV_GE(pi->pubpi.phy_rev, 3)) + wlc_phy_radio205x_vcocal_nphy(pi); + } else { + switch (pi->mphase_cal_phase_id) { + case MPHASE_CAL_STATE_INIT: + pi->nphy_perical_last = pi->sh->now; + pi->nphy_txiqlocal_chanspec = pi->radio_chanspec; + + if (NREV_GE(pi->pubpi.phy_rev, 3)) + wlc_phy_precal_txgain_nphy(pi); + + pi->nphy_cal_target_gain = wlc_phy_get_tx_gain_nphy(pi); + pi->mphase_cal_phase_id++; + break; + + case MPHASE_CAL_STATE_TXPHASE0: + case MPHASE_CAL_STATE_TXPHASE1: + case MPHASE_CAL_STATE_TXPHASE2: + case MPHASE_CAL_STATE_TXPHASE3: + case MPHASE_CAL_STATE_TXPHASE4: + case MPHASE_CAL_STATE_TXPHASE5: + if ((pi->radar_percal_mask & 0x10) != 0) + pi->nphy_rxcal_active = true; + + if (wlc_phy_cal_txiqlo_nphy + (pi, pi->nphy_cal_target_gain, fullcal, + true) != 0) { + + wlc_phy_cal_perical_mphase_reset(pi); + break; + } + + if (NREV_LE(pi->pubpi.phy_rev, 2) && + (pi->mphase_cal_phase_id == + MPHASE_CAL_STATE_TXPHASE4)) + pi->mphase_cal_phase_id += 2; + else + pi->mphase_cal_phase_id++; + break; + + case MPHASE_CAL_STATE_PAPDCAL: + if ((pi->radar_percal_mask & 0x2) != 0) + pi->nphy_rxcal_active = true; + + if (PHY_IPA(pi)) + wlc_phy_a4(pi, true); + + pi->mphase_cal_phase_id++; + break; + + case MPHASE_CAL_STATE_RXCAL: + if ((pi->radar_percal_mask & 0x1) != 0) + pi->nphy_rxcal_active = true; + if (wlc_phy_cal_rxiq_nphy(pi, target_gain, + (pi->first_cal_after_assoc || + (pi->cal_type_override == + PHY_PERICAL_FULL)) ? 2 : 0, + false) == 0) + wlc_phy_savecal_nphy(pi); + + pi->mphase_cal_phase_id++; + break; + + case MPHASE_CAL_STATE_RSSICAL: + if ((pi->radar_percal_mask & 0x4) != 0) + pi->nphy_rxcal_active = true; + wlc_phy_txpwrctrl_coeff_setup_nphy(pi); + wlc_phy_rssi_cal_nphy(pi); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) + wlc_phy_radio205x_vcocal_nphy(pi); + + restore_tx_gain = true; + + if (pi->first_cal_after_assoc) + pi->mphase_cal_phase_id++; + else + wlc_phy_cal_perical_mphase_reset(pi); + + break; + + case MPHASE_CAL_STATE_IDLETSSI: + if ((pi->radar_percal_mask & 0x8) != 0) + pi->nphy_rxcal_active = true; + + if (pi->first_cal_after_assoc) { + pi->first_cal_after_assoc = false; + wlc_phy_txpwrctrl_idle_tssi_nphy(pi); + wlc_phy_txpwrctrl_pwr_setup_nphy(pi); + } + + wlc_phy_cal_perical_mphase_reset(pi); + break; + + default: + wlc_phy_cal_perical_mphase_reset(pi); + break; + } + } + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + if (restore_tx_gain) { + if (tx_pwr_ctrl_state != PHY_TPC_HW_OFF) { + + wlc_phy_txpwr_index_nphy(pi, 1, + pi-> + nphy_cal_orig_pwr_idx + [0], false); + wlc_phy_txpwr_index_nphy(pi, 2, + pi-> + nphy_cal_orig_pwr_idx + [1], false); + + pi->nphy_txpwrindex[0].index = -1; + pi->nphy_txpwrindex[1].index = -1; + } else { + wlc_phy_txpwr_index_nphy(pi, (1 << 0), + (s8) (pi-> + nphy_txpwrindex + [0]. + index_internal), + false); + wlc_phy_txpwr_index_nphy(pi, (1 << 1), + (s8) (pi-> + nphy_txpwrindex + [1]. + index_internal), + false); + } + } + } + + wlc_phy_txpwrctrl_enable_nphy(pi, tx_pwr_ctrl_state); + wlc_phyreg_exit((struct brcms_phy_pub *) pi); + wlapi_enable_mac(pi->sh->physhim); +} + +int +wlc_phy_cal_txiqlo_nphy(struct brcms_phy *pi, struct nphy_txgains target_gain, + bool fullcal, bool mphase) +{ + u16 val; + u16 tbl_buf[11]; + u8 cal_cnt; + u16 cal_cmd; + u8 num_cals, max_cal_cmds; + u16 core_no, cal_type; + u16 diq_start = 0; + u8 phy_bw; + u16 max_val; + u16 tone_freq; + u16 gain_save[2]; + u16 cal_gain[2]; + struct nphy_iqcal_params cal_params[2]; + u32 tbl_len; + void *tbl_ptr; + bool ladder_updated[2]; + u8 mphase_cal_lastphase = 0; + int bcmerror = 0; + bool phyhang_avoid_state = false; + + u16 tbl_tx_iqlo_cal_loft_ladder_20[] = { + 0x0300, 0x0500, 0x0700, 0x0900, 0x0d00, 0x1100, 0x1900, 0x1901, + 0x1902, + 0x1903, 0x1904, 0x1905, 0x1906, 0x1907, 0x2407, 0x3207, 0x4607, + 0x6407 + }; + + u16 tbl_tx_iqlo_cal_iqimb_ladder_20[] = { + 0x0200, 0x0300, 0x0600, 0x0900, 0x0d00, 0x1100, 0x1900, 0x2400, + 0x3200, + 0x4600, 0x6400, 0x6401, 0x6402, 0x6403, 0x6404, 0x6405, 0x6406, + 0x6407 + }; + + u16 tbl_tx_iqlo_cal_loft_ladder_40[] = { + 0x0200, 0x0300, 0x0400, 0x0700, 0x0900, 0x0c00, 0x1200, 0x1201, + 0x1202, + 0x1203, 0x1204, 0x1205, 0x1206, 0x1207, 0x1907, 0x2307, 0x3207, + 0x4707 + }; + + u16 tbl_tx_iqlo_cal_iqimb_ladder_40[] = { + 0x0100, 0x0200, 0x0400, 0x0700, 0x0900, 0x0c00, 0x1200, 0x1900, + 0x2300, + 0x3200, 0x4700, 0x4701, 0x4702, 0x4703, 0x4704, 0x4705, 0x4706, + 0x4707 + }; + + u16 tbl_tx_iqlo_cal_startcoefs[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000 + }; + + u16 tbl_tx_iqlo_cal_cmds_fullcal[] = { + 0x8123, 0x8264, 0x8086, 0x8245, 0x8056, + 0x9123, 0x9264, 0x9086, 0x9245, 0x9056 + }; + + u16 tbl_tx_iqlo_cal_cmds_recal[] = { + 0x8101, 0x8253, 0x8053, 0x8234, 0x8034, + 0x9101, 0x9253, 0x9053, 0x9234, 0x9034 + }; + + u16 tbl_tx_iqlo_cal_startcoefs_nphyrev3[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000 + }; + + u16 tbl_tx_iqlo_cal_cmds_fullcal_nphyrev3[] = { + 0x8434, 0x8334, 0x8084, 0x8267, 0x8056, 0x8234, + 0x9434, 0x9334, 0x9084, 0x9267, 0x9056, 0x9234 + }; + + u16 tbl_tx_iqlo_cal_cmds_recal_nphyrev3[] = { + 0x8423, 0x8323, 0x8073, 0x8256, 0x8045, 0x8223, + 0x9423, 0x9323, 0x9073, 0x9256, 0x9045, 0x9223 + }; + + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + if (NREV_GE(pi->pubpi.phy_rev, 4)) { + phyhang_avoid_state = pi->phyhang_avoid; + pi->phyhang_avoid = false; + } + + if (CHSPEC_IS40(pi->radio_chanspec)) + phy_bw = 40; + else + phy_bw = 20; + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_RFSEQ, 2, 0x110, 16, gain_save); + + for (core_no = 0; core_no <= 1; core_no++) { + wlc_phy_iqcal_gainparams_nphy(pi, core_no, target_gain, + &cal_params[core_no]); + cal_gain[core_no] = cal_params[core_no].cal_gain; + } + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, 2, 0x110, 16, cal_gain); + + wlc_phy_txcal_radio_setup_nphy(pi); + + wlc_phy_txcal_physetup_nphy(pi); + + ladder_updated[0] = ladder_updated[1] = false; + if (!(NREV_GE(pi->pubpi.phy_rev, 6) || + (NREV_IS(pi->pubpi.phy_rev, 5) && PHY_IPA(pi) + && (CHSPEC_IS2G(pi->radio_chanspec))))) { + + if (phy_bw == 40) { + tbl_ptr = tbl_tx_iqlo_cal_loft_ladder_40; + tbl_len = ARRAY_SIZE(tbl_tx_iqlo_cal_loft_ladder_40); + } else { + tbl_ptr = tbl_tx_iqlo_cal_loft_ladder_20; + tbl_len = ARRAY_SIZE(tbl_tx_iqlo_cal_loft_ladder_20); + } + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, tbl_len, 0, + 16, tbl_ptr); + + if (phy_bw == 40) { + tbl_ptr = tbl_tx_iqlo_cal_iqimb_ladder_40; + tbl_len = ARRAY_SIZE(tbl_tx_iqlo_cal_iqimb_ladder_40); + } else { + tbl_ptr = tbl_tx_iqlo_cal_iqimb_ladder_20; + tbl_len = ARRAY_SIZE(tbl_tx_iqlo_cal_iqimb_ladder_20); + } + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, tbl_len, 32, + 16, tbl_ptr); + } + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + write_phy_reg(pi, 0xc2, 0x8ad9); + else + write_phy_reg(pi, 0xc2, 0x8aa9); + + max_val = 250; + tone_freq = (phy_bw == 20) ? 2500 : 5000; + + if (pi->mphase_cal_phase_id > MPHASE_CAL_STATE_TXPHASE0) { + wlc_phy_runsamples_nphy(pi, phy_bw * 8, 0xffff, 0, 1, 0, false); + bcmerror = 0; + } else { + bcmerror = + wlc_phy_tx_tone_nphy(pi, tone_freq, max_val, 1, 0, + false); + } + + if (bcmerror == 0) { + + if (pi->mphase_cal_phase_id > MPHASE_CAL_STATE_TXPHASE0) { + tbl_ptr = pi->mphase_txcal_bestcoeffs; + tbl_len = ARRAY_SIZE(pi->mphase_txcal_bestcoeffs); + if (NREV_LT(pi->pubpi.phy_rev, 3)) + tbl_len -= 2; + } else { + if ((!fullcal) && (pi->nphy_txiqlocal_coeffsvalid)) { + + tbl_ptr = pi->nphy_txiqlocal_bestc; + tbl_len = ARRAY_SIZE(pi->nphy_txiqlocal_bestc); + if (NREV_LT(pi->pubpi.phy_rev, 3)) + tbl_len -= 2; + } else { + + fullcal = true; + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + tbl_ptr = + tbl_tx_iqlo_cal_startcoefs_nphyrev3; + tbl_len = ARRAY_SIZE( + tbl_tx_iqlo_cal_startcoefs_nphyrev3); + } else { + tbl_ptr = tbl_tx_iqlo_cal_startcoefs; + tbl_len = ARRAY_SIZE( + tbl_tx_iqlo_cal_startcoefs); + } + } + } + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, tbl_len, 64, + 16, tbl_ptr); + + if (fullcal) { + max_cal_cmds = (NREV_GE(pi->pubpi.phy_rev, 3)) ? + ARRAY_SIZE( + tbl_tx_iqlo_cal_cmds_fullcal_nphyrev3) : + ARRAY_SIZE(tbl_tx_iqlo_cal_cmds_fullcal); + } else { + max_cal_cmds = (NREV_GE(pi->pubpi.phy_rev, 3)) ? + ARRAY_SIZE( + tbl_tx_iqlo_cal_cmds_recal_nphyrev3) : + ARRAY_SIZE(tbl_tx_iqlo_cal_cmds_recal); + } + + if (mphase) { + cal_cnt = pi->mphase_txcal_cmdidx; + if ((cal_cnt + pi->mphase_txcal_numcmds) < max_cal_cmds) + num_cals = cal_cnt + pi->mphase_txcal_numcmds; + else + num_cals = max_cal_cmds; + } else { + cal_cnt = 0; + num_cals = max_cal_cmds; + } + + for (; cal_cnt < num_cals; cal_cnt++) { + + if (fullcal) { + cal_cmd = (NREV_GE(pi->pubpi.phy_rev, 3)) ? + tbl_tx_iqlo_cal_cmds_fullcal_nphyrev3 + [cal_cnt] : + tbl_tx_iqlo_cal_cmds_fullcal[cal_cnt]; + } else { + cal_cmd = (NREV_GE(pi->pubpi.phy_rev, 3)) ? + tbl_tx_iqlo_cal_cmds_recal_nphyrev3[ + cal_cnt] + : tbl_tx_iqlo_cal_cmds_recal[cal_cnt]; + } + + core_no = ((cal_cmd & 0x3000) >> 12); + cal_type = ((cal_cmd & 0x0F00) >> 8); + + if (NREV_GE(pi->pubpi.phy_rev, 6) || + (NREV_IS(pi->pubpi.phy_rev, 5) && + PHY_IPA(pi) + && (CHSPEC_IS2G(pi->radio_chanspec)))) { + if (!ladder_updated[core_no]) { + wlc_phy_update_txcal_ladder_nphy( + pi, + core_no); + ladder_updated[core_no] = true; + } + } + + val = + (cal_params[core_no]. + ncorr[cal_type] << 8) | NPHY_N_GCTL; + write_phy_reg(pi, 0xc1, val); + + if ((cal_type == 1) || (cal_type == 3) + || (cal_type == 4)) { + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_IQLOCAL, + 1, 69 + core_no, 16, + tbl_buf); + + diq_start = tbl_buf[0]; + + tbl_buf[0] = 0; + wlc_phy_table_write_nphy(pi, + NPHY_TBL_ID_IQLOCAL, 1, + 69 + core_no, 16, + tbl_buf); + } + + write_phy_reg(pi, 0xc0, cal_cmd); + + SPINWAIT(((read_phy_reg(pi, 0xc0) & 0xc000) != 0), + 20000); + if (WARN(read_phy_reg(pi, 0xc0) & 0xc000, + "HW error: txiq calib")) + return -EIO; + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_IQLOCAL, + tbl_len, 96, 16, tbl_buf); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, + tbl_len, 64, 16, tbl_buf); + + if ((cal_type == 1) || (cal_type == 3) + || (cal_type == 4)) { + + tbl_buf[0] = diq_start; + + } + + } + + if (mphase) { + pi->mphase_txcal_cmdidx = num_cals; + if (pi->mphase_txcal_cmdidx >= max_cal_cmds) + pi->mphase_txcal_cmdidx = 0; + } + + mphase_cal_lastphase = + (NREV_LE(pi->pubpi.phy_rev, 2)) ? + MPHASE_CAL_STATE_TXPHASE4 : MPHASE_CAL_STATE_TXPHASE5; + + if (!mphase + || (pi->mphase_cal_phase_id == mphase_cal_lastphase)) { + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_IQLOCAL, 4, 96, + 16, tbl_buf); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, 4, 80, + 16, tbl_buf); + + if (NREV_LT(pi->pubpi.phy_rev, 2)) { + + tbl_buf[0] = 0; + tbl_buf[1] = 0; + tbl_buf[2] = 0; + tbl_buf[3] = 0; + + } + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, 4, 88, + 16, tbl_buf); + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_IQLOCAL, 2, 101, + 16, tbl_buf); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, 2, 85, + 16, tbl_buf); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, 2, 93, + 16, tbl_buf); + + tbl_len = ARRAY_SIZE(pi->nphy_txiqlocal_bestc); + if (NREV_LT(pi->pubpi.phy_rev, 3)) + tbl_len -= 2; + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_IQLOCAL, + tbl_len, 96, 16, + pi->nphy_txiqlocal_bestc); + + pi->nphy_txiqlocal_coeffsvalid = true; + pi->nphy_txiqlocal_chanspec = pi->radio_chanspec; + } else { + tbl_len = ARRAY_SIZE(pi->mphase_txcal_bestcoeffs); + if (NREV_LT(pi->pubpi.phy_rev, 3)) + tbl_len -= 2; + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_IQLOCAL, + tbl_len, 96, 16, + pi->mphase_txcal_bestcoeffs); + } + + wlc_phy_stopplayback_nphy(pi); + + write_phy_reg(pi, 0xc2, 0x0000); + + } + + wlc_phy_txcal_phycleanup_nphy(pi); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, 2, 0x110, 16, + gain_save); + + wlc_phy_txcal_radio_cleanup_nphy(pi); + + if (NREV_LT(pi->pubpi.phy_rev, 2)) { + if (!mphase + || (pi->mphase_cal_phase_id == mphase_cal_lastphase)) + wlc_phy_tx_iq_war_nphy(pi); + } + + if (NREV_GE(pi->pubpi.phy_rev, 4)) + pi->phyhang_avoid = phyhang_avoid_state; + + wlc_phy_stay_in_carriersearch_nphy(pi, false); + + return bcmerror; +} + +static void wlc_phy_reapply_txcal_coeffs_nphy(struct brcms_phy *pi) +{ + u16 tbl_buf[7]; + + if ((pi->nphy_txiqlocal_chanspec == pi->radio_chanspec) && + (pi->nphy_txiqlocal_coeffsvalid)) { + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_IQLOCAL, + ARRAY_SIZE(tbl_buf), 80, 16, tbl_buf); + + if ((pi->nphy_txiqlocal_bestc[0] != tbl_buf[0]) || + (pi->nphy_txiqlocal_bestc[1] != tbl_buf[1]) || + (pi->nphy_txiqlocal_bestc[2] != tbl_buf[2]) || + (pi->nphy_txiqlocal_bestc[3] != tbl_buf[3])) { + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, 4, 80, + 16, pi->nphy_txiqlocal_bestc); + + tbl_buf[0] = 0; + tbl_buf[1] = 0; + tbl_buf[2] = 0; + tbl_buf[3] = 0; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, 4, 88, + 16, tbl_buf); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, 2, 85, + 16, + &pi->nphy_txiqlocal_bestc[5]); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_IQLOCAL, 2, 93, + 16, + &pi->nphy_txiqlocal_bestc[5]); + } + } +} + +void +wlc_phy_rx_iq_coeffs_nphy(struct brcms_phy *pi, u8 write, + struct nphy_iq_comp *pcomp) +{ + if (write) { + write_phy_reg(pi, 0x9a, pcomp->a0); + write_phy_reg(pi, 0x9b, pcomp->b0); + write_phy_reg(pi, 0x9c, pcomp->a1); + write_phy_reg(pi, 0x9d, pcomp->b1); + } else { + pcomp->a0 = read_phy_reg(pi, 0x9a); + pcomp->b0 = read_phy_reg(pi, 0x9b); + pcomp->a1 = read_phy_reg(pi, 0x9c); + pcomp->b1 = read_phy_reg(pi, 0x9d); + } +} + +void +wlc_phy_rx_iq_est_nphy(struct brcms_phy *pi, struct phy_iq_est *est, + u16 num_samps, u8 wait_time, u8 wait_for_crs) +{ + u8 core; + + write_phy_reg(pi, 0x12b, num_samps); + mod_phy_reg(pi, 0x12a, (0xff << 0), (wait_time << 0)); + mod_phy_reg(pi, 0x129, NPHY_IqestCmd_iqMode, + (wait_for_crs) ? NPHY_IqestCmd_iqMode : 0); + + mod_phy_reg(pi, 0x129, NPHY_IqestCmd_iqstart, NPHY_IqestCmd_iqstart); + + SPINWAIT(((read_phy_reg(pi, 0x129) & NPHY_IqestCmd_iqstart) != 0), + 10000); + if (WARN(read_phy_reg(pi, 0x129) & NPHY_IqestCmd_iqstart, + "HW error: rxiq est")) + return; + + if ((read_phy_reg(pi, 0x129) & NPHY_IqestCmd_iqstart) == 0) { + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + est[core].i_pwr = + (read_phy_reg(pi, + NPHY_IqestipwrAccHi(core)) << 16) + | read_phy_reg(pi, NPHY_IqestipwrAccLo(core)); + est[core].q_pwr = + (read_phy_reg(pi, + NPHY_IqestqpwrAccHi(core)) << 16) + | read_phy_reg(pi, NPHY_IqestqpwrAccLo(core)); + est[core].iq_prod = + (read_phy_reg(pi, + NPHY_IqestIqAccHi(core)) << 16) | + read_phy_reg(pi, NPHY_IqestIqAccLo(core)); + } + } +} + +#define CAL_RETRY_CNT 2 +static void wlc_phy_calc_rx_iq_comp_nphy(struct brcms_phy *pi, u8 core_mask) +{ + u8 curr_core; + struct phy_iq_est est[PHY_CORE_MAX]; + struct nphy_iq_comp old_comp, new_comp; + s32 iq = 0; + u32 ii = 0, qq = 0; + s16 iq_nbits, qq_nbits, brsh, arsh; + s32 a, b, temp; + int bcmerror = 0; + uint cal_retry = 0; + + if (core_mask == 0x0) + return; + + wlc_phy_rx_iq_coeffs_nphy(pi, 0, &old_comp); + new_comp.a0 = new_comp.b0 = new_comp.a1 = new_comp.b1 = 0x0; + wlc_phy_rx_iq_coeffs_nphy(pi, 1, &new_comp); + +cal_try: + wlc_phy_rx_iq_est_nphy(pi, est, 0x4000, 32, 0); + + new_comp = old_comp; + + for (curr_core = 0; curr_core < pi->pubpi.phy_corenum; curr_core++) { + + if ((curr_core == PHY_CORE_0) && (core_mask & 0x1)) { + iq = est[curr_core].iq_prod; + ii = est[curr_core].i_pwr; + qq = est[curr_core].q_pwr; + } else if ((curr_core == PHY_CORE_1) && (core_mask & 0x2)) { + iq = est[curr_core].iq_prod; + ii = est[curr_core].i_pwr; + qq = est[curr_core].q_pwr; + } else { + continue; + } + + if ((ii + qq) < NPHY_MIN_RXIQ_PWR) { + bcmerror = -EBADE; + break; + } + + iq_nbits = wlc_phy_nbits(iq); + qq_nbits = wlc_phy_nbits(qq); + + arsh = 10 - (30 - iq_nbits); + if (arsh >= 0) { + a = (-(iq << (30 - iq_nbits)) + (ii >> (1 + arsh))); + temp = (s32) (ii >> arsh); + if (temp == 0) { + bcmerror = -EBADE; + break; + } + } else { + a = (-(iq << (30 - iq_nbits)) + (ii << (-1 - arsh))); + temp = (s32) (ii << -arsh); + if (temp == 0) { + bcmerror = -EBADE; + break; + } + } + + a /= temp; + + brsh = qq_nbits - 31 + 20; + if (brsh >= 0) { + b = (qq << (31 - qq_nbits)); + temp = (s32) (ii >> brsh); + if (temp == 0) { + bcmerror = -EBADE; + break; + } + } else { + b = (qq << (31 - qq_nbits)); + temp = (s32) (ii << -brsh); + if (temp == 0) { + bcmerror = -EBADE; + break; + } + } + b /= temp; + b -= a * a; + b = (s32) int_sqrt((unsigned long) b); + b -= (1 << 10); + + if ((curr_core == PHY_CORE_0) && (core_mask & 0x1)) { + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + new_comp.a0 = (s16) a & 0x3ff; + new_comp.b0 = (s16) b & 0x3ff; + } else { + + new_comp.a0 = (s16) b & 0x3ff; + new_comp.b0 = (s16) a & 0x3ff; + } + } + if ((curr_core == PHY_CORE_1) && (core_mask & 0x2)) { + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + new_comp.a1 = (s16) a & 0x3ff; + new_comp.b1 = (s16) b & 0x3ff; + } else { + + new_comp.a1 = (s16) b & 0x3ff; + new_comp.b1 = (s16) a & 0x3ff; + } + } + } + + if (bcmerror != 0) { + pr_debug("%s: Failed, cnt = %d\n", __func__, cal_retry); + + if (cal_retry < CAL_RETRY_CNT) { + cal_retry++; + goto cal_try; + } + + new_comp = old_comp; + } + + wlc_phy_rx_iq_coeffs_nphy(pi, 1, &new_comp); +} + +static void wlc_phy_rxcal_radio_setup_nphy(struct brcms_phy *pi, u8 rx_core) +{ + u16 offtune_val; + u16 bias_g = 0; + u16 bias_a = 0; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + if (rx_core == PHY_CORE_0) { + if (CHSPEC_IS5G(pi->radio_chanspec)) { + pi->tx_rx_cal_radio_saveregs[0] = + read_radio_reg(pi, + RADIO_2057_TX0_TXRXCOUPLE_5G_PWRUP); + pi->tx_rx_cal_radio_saveregs[1] = + read_radio_reg(pi, + RADIO_2057_TX0_TXRXCOUPLE_5G_ATTEN); + + write_radio_reg(pi, + RADIO_2057_TX0_TXRXCOUPLE_5G_PWRUP, + 0x3); + write_radio_reg(pi, + RADIO_2057_TX0_TXRXCOUPLE_5G_ATTEN, + 0xaf); + + } else { + pi->tx_rx_cal_radio_saveregs[0] = + read_radio_reg(pi, + RADIO_2057_TX0_TXRXCOUPLE_2G_PWRUP); + pi->tx_rx_cal_radio_saveregs[1] = + read_radio_reg(pi, + RADIO_2057_TX0_TXRXCOUPLE_2G_ATTEN); + + write_radio_reg( + pi, + RADIO_2057_TX0_TXRXCOUPLE_2G_PWRUP, + 0x3); + write_radio_reg( + pi, + RADIO_2057_TX0_TXRXCOUPLE_2G_ATTEN, + 0x7f); + } + + } else { + if (CHSPEC_IS5G(pi->radio_chanspec)) { + pi->tx_rx_cal_radio_saveregs[0] = + read_radio_reg(pi, + RADIO_2057_TX1_TXRXCOUPLE_5G_PWRUP); + pi->tx_rx_cal_radio_saveregs[1] = + read_radio_reg(pi, + RADIO_2057_TX1_TXRXCOUPLE_5G_ATTEN); + + write_radio_reg( + pi, + RADIO_2057_TX1_TXRXCOUPLE_5G_PWRUP, + 0x3); + write_radio_reg( + pi, + RADIO_2057_TX1_TXRXCOUPLE_5G_ATTEN, + 0xaf); + + } else { + pi->tx_rx_cal_radio_saveregs[0] = + read_radio_reg(pi, + RADIO_2057_TX1_TXRXCOUPLE_2G_PWRUP); + pi->tx_rx_cal_radio_saveregs[1] = + read_radio_reg(pi, + RADIO_2057_TX1_TXRXCOUPLE_2G_ATTEN); + + write_radio_reg(pi, + RADIO_2057_TX1_TXRXCOUPLE_2G_PWRUP, + 0x3); + write_radio_reg(pi, + RADIO_2057_TX1_TXRXCOUPLE_2G_ATTEN, + 0x7f); + } + } + + } else { + if (rx_core == PHY_CORE_0) { + pi->tx_rx_cal_radio_saveregs[0] = + read_radio_reg(pi, + RADIO_2056_TX_RXIQCAL_TXMUX | + RADIO_2056_TX1); + pi->tx_rx_cal_radio_saveregs[1] = + read_radio_reg(pi, + RADIO_2056_RX_RXIQCAL_RXMUX | + RADIO_2056_RX0); + + if (pi->pubpi.radiorev >= 5) { + pi->tx_rx_cal_radio_saveregs[2] = + read_radio_reg(pi, + RADIO_2056_RX_RXSPARE2 | + RADIO_2056_RX0); + pi->tx_rx_cal_radio_saveregs[3] = + read_radio_reg(pi, + RADIO_2056_TX_TXSPARE2 | + RADIO_2056_TX1); + } + + if (CHSPEC_IS5G(pi->radio_chanspec)) { + + if (pi->pubpi.radiorev >= 5) { + pi->tx_rx_cal_radio_saveregs[4] = + read_radio_reg(pi, + RADIO_2056_RX_LNAA_MASTER + | RADIO_2056_RX0); + + write_radio_reg( + pi, + RADIO_2056_RX_LNAA_MASTER + | RADIO_2056_RX0, 0x40); + + write_radio_reg(pi, + RADIO_2056_TX_TXSPARE2 | + RADIO_2056_TX1, bias_a); + + write_radio_reg(pi, + RADIO_2056_RX_RXSPARE2 | + RADIO_2056_RX0, bias_a); + } else { + pi->tx_rx_cal_radio_saveregs[4] = + read_radio_reg(pi, + RADIO_2056_RX_LNAA_TUNE + | RADIO_2056_RX0); + + offtune_val = + (pi->tx_rx_cal_radio_saveregs + [2] & 0xF0) >> 8; + offtune_val = + (offtune_val <= 0x7) ? 0xF : 0; + + mod_radio_reg(pi, + RADIO_2056_RX_LNAA_TUNE | + RADIO_2056_RX0, 0xF0, + (offtune_val << 8)); + } + + write_radio_reg(pi, + RADIO_2056_TX_RXIQCAL_TXMUX | + RADIO_2056_TX1, 0x9); + write_radio_reg(pi, + RADIO_2056_RX_RXIQCAL_RXMUX | + RADIO_2056_RX0, 0x9); + } else { + if (pi->pubpi.radiorev >= 5) { + pi->tx_rx_cal_radio_saveregs[4] = + read_radio_reg( + pi, + RADIO_2056_RX_LNAG_MASTER + | RADIO_2056_RX0); + + write_radio_reg( + pi, + RADIO_2056_RX_LNAG_MASTER + | RADIO_2056_RX0, 0x40); + + write_radio_reg( + pi, + RADIO_2056_TX_TXSPARE2 + | + RADIO_2056_TX1, bias_g); + + write_radio_reg( + pi, + RADIO_2056_RX_RXSPARE2 + | + RADIO_2056_RX0, bias_g); + + } else { + pi->tx_rx_cal_radio_saveregs[4] = + read_radio_reg( + pi, + RADIO_2056_RX_LNAG_TUNE + | RADIO_2056_RX0); + + offtune_val = + (pi-> + tx_rx_cal_radio_saveregs[2] & + 0xF0) >> 8; + offtune_val = + (offtune_val <= 0x7) ? 0xF : 0; + + mod_radio_reg(pi, + RADIO_2056_RX_LNAG_TUNE | + RADIO_2056_RX0, 0xF0, + (offtune_val << 8)); + } + + write_radio_reg(pi, + RADIO_2056_TX_RXIQCAL_TXMUX | + RADIO_2056_TX1, 0x6); + write_radio_reg(pi, + RADIO_2056_RX_RXIQCAL_RXMUX | + RADIO_2056_RX0, 0x6); + } + + } else { + pi->tx_rx_cal_radio_saveregs[0] = + read_radio_reg(pi, + RADIO_2056_TX_RXIQCAL_TXMUX | + RADIO_2056_TX0); + pi->tx_rx_cal_radio_saveregs[1] = + read_radio_reg(pi, + RADIO_2056_RX_RXIQCAL_RXMUX | + RADIO_2056_RX1); + + if (pi->pubpi.radiorev >= 5) { + pi->tx_rx_cal_radio_saveregs[2] = + read_radio_reg(pi, + RADIO_2056_RX_RXSPARE2 | + RADIO_2056_RX1); + pi->tx_rx_cal_radio_saveregs[3] = + read_radio_reg(pi, + RADIO_2056_TX_TXSPARE2 | + RADIO_2056_TX0); + } + + if (CHSPEC_IS5G(pi->radio_chanspec)) { + + if (pi->pubpi.radiorev >= 5) { + pi->tx_rx_cal_radio_saveregs[4] = + read_radio_reg( + pi, + RADIO_2056_RX_LNAA_MASTER + | RADIO_2056_RX1); + + write_radio_reg( + pi, + RADIO_2056_RX_LNAA_MASTER | + RADIO_2056_RX1, 0x40); + + write_radio_reg( + pi, + RADIO_2056_TX_TXSPARE2 + | + RADIO_2056_TX0, bias_a); + + write_radio_reg( + pi, + RADIO_2056_RX_RXSPARE2 + | + RADIO_2056_RX1, bias_a); + } else { + pi->tx_rx_cal_radio_saveregs[4] = + read_radio_reg( + pi, + RADIO_2056_RX_LNAA_TUNE + | RADIO_2056_RX1); + + offtune_val = + (pi-> + tx_rx_cal_radio_saveregs[2] & + 0xF0) >> 8; + offtune_val = + (offtune_val <= 0x7) ? 0xF : 0; + + mod_radio_reg(pi, + RADIO_2056_RX_LNAA_TUNE | + RADIO_2056_RX1, 0xF0, + (offtune_val << 8)); + } + + write_radio_reg(pi, + RADIO_2056_TX_RXIQCAL_TXMUX | + RADIO_2056_TX0, 0x9); + write_radio_reg(pi, + RADIO_2056_RX_RXIQCAL_RXMUX | + RADIO_2056_RX1, 0x9); + } else { + if (pi->pubpi.radiorev >= 5) { + pi->tx_rx_cal_radio_saveregs[4] = + read_radio_reg( + pi, + RADIO_2056_RX_LNAG_MASTER + | RADIO_2056_RX1); + + write_radio_reg( + pi, + RADIO_2056_RX_LNAG_MASTER + | RADIO_2056_RX1, 0x40); + + write_radio_reg( + pi, + RADIO_2056_TX_TXSPARE2 + | + RADIO_2056_TX0, bias_g); + + write_radio_reg( + pi, + RADIO_2056_RX_RXSPARE2 + | + RADIO_2056_RX1, bias_g); + } else { + pi->tx_rx_cal_radio_saveregs[4] = + read_radio_reg( + pi, + RADIO_2056_RX_LNAG_TUNE + | RADIO_2056_RX1); + + offtune_val = + (pi-> + tx_rx_cal_radio_saveregs[2] & + 0xF0) >> 8; + offtune_val = + (offtune_val <= 0x7) ? 0xF : 0; + + mod_radio_reg(pi, + RADIO_2056_RX_LNAG_TUNE | + RADIO_2056_RX1, 0xF0, + (offtune_val << 8)); + } + + write_radio_reg(pi, + RADIO_2056_TX_RXIQCAL_TXMUX | + RADIO_2056_TX0, 0x6); + write_radio_reg(pi, + RADIO_2056_RX_RXIQCAL_RXMUX | + RADIO_2056_RX1, 0x6); + } + } + } +} + +static void wlc_phy_rxcal_radio_cleanup_nphy(struct brcms_phy *pi, u8 rx_core) +{ + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + if (rx_core == PHY_CORE_0) { + if (CHSPEC_IS5G(pi->radio_chanspec)) { + write_radio_reg( + pi, + RADIO_2057_TX0_TXRXCOUPLE_5G_PWRUP, + pi-> + tx_rx_cal_radio_saveregs[0]); + write_radio_reg( + pi, + RADIO_2057_TX0_TXRXCOUPLE_5G_ATTEN, + pi-> + tx_rx_cal_radio_saveregs[1]); + + } else { + write_radio_reg( + pi, + RADIO_2057_TX0_TXRXCOUPLE_2G_PWRUP, + pi-> + tx_rx_cal_radio_saveregs[0]); + write_radio_reg( + pi, + RADIO_2057_TX0_TXRXCOUPLE_2G_ATTEN, + pi-> + tx_rx_cal_radio_saveregs[1]); + } + + } else { + if (CHSPEC_IS5G(pi->radio_chanspec)) { + write_radio_reg( + pi, + RADIO_2057_TX1_TXRXCOUPLE_5G_PWRUP, + pi-> + tx_rx_cal_radio_saveregs[0]); + write_radio_reg( + pi, + RADIO_2057_TX1_TXRXCOUPLE_5G_ATTEN, + pi-> + tx_rx_cal_radio_saveregs[1]); + + } else { + write_radio_reg( + pi, + RADIO_2057_TX1_TXRXCOUPLE_2G_PWRUP, + pi-> + tx_rx_cal_radio_saveregs[0]); + write_radio_reg( + pi, + RADIO_2057_TX1_TXRXCOUPLE_2G_ATTEN, + pi-> + tx_rx_cal_radio_saveregs[1]); + } + } + + } else { + if (rx_core == PHY_CORE_0) { + write_radio_reg(pi, + RADIO_2056_TX_RXIQCAL_TXMUX | + RADIO_2056_TX1, + pi->tx_rx_cal_radio_saveregs[0]); + + write_radio_reg(pi, + RADIO_2056_RX_RXIQCAL_RXMUX | + RADIO_2056_RX0, + pi->tx_rx_cal_radio_saveregs[1]); + + if (pi->pubpi.radiorev >= 5) { + write_radio_reg(pi, + RADIO_2056_RX_RXSPARE2 | + RADIO_2056_RX0, + pi-> + tx_rx_cal_radio_saveregs[2]); + + write_radio_reg(pi, + RADIO_2056_TX_TXSPARE2 | + RADIO_2056_TX1, + pi-> + tx_rx_cal_radio_saveregs[3]); + } + + if (CHSPEC_IS5G(pi->radio_chanspec)) { + if (pi->pubpi.radiorev >= 5) + write_radio_reg( + pi, + RADIO_2056_RX_LNAA_MASTER + | RADIO_2056_RX0, + pi-> + tx_rx_cal_radio_saveregs + [4]); + else + write_radio_reg( + pi, + RADIO_2056_RX_LNAA_TUNE + | RADIO_2056_RX0, + pi-> + tx_rx_cal_radio_saveregs + [4]); + } else { + if (pi->pubpi.radiorev >= 5) + write_radio_reg( + pi, + RADIO_2056_RX_LNAG_MASTER + | RADIO_2056_RX0, + pi-> + tx_rx_cal_radio_saveregs + [4]); + else + write_radio_reg( + pi, + RADIO_2056_RX_LNAG_TUNE + | RADIO_2056_RX0, + pi-> + tx_rx_cal_radio_saveregs + [4]); + } + + } else { + write_radio_reg(pi, + RADIO_2056_TX_RXIQCAL_TXMUX | + RADIO_2056_TX0, + pi->tx_rx_cal_radio_saveregs[0]); + + write_radio_reg(pi, + RADIO_2056_RX_RXIQCAL_RXMUX | + RADIO_2056_RX1, + pi->tx_rx_cal_radio_saveregs[1]); + + if (pi->pubpi.radiorev >= 5) { + write_radio_reg(pi, + RADIO_2056_RX_RXSPARE2 | + RADIO_2056_RX1, + pi-> + tx_rx_cal_radio_saveregs[2]); + + write_radio_reg(pi, + RADIO_2056_TX_TXSPARE2 | + RADIO_2056_TX0, + pi-> + tx_rx_cal_radio_saveregs[3]); + } + + if (CHSPEC_IS5G(pi->radio_chanspec)) { + if (pi->pubpi.radiorev >= 5) + write_radio_reg( + pi, + RADIO_2056_RX_LNAA_MASTER + | RADIO_2056_RX1, + pi-> + tx_rx_cal_radio_saveregs + [4]); + else + write_radio_reg( + pi, + RADIO_2056_RX_LNAA_TUNE + | RADIO_2056_RX1, + pi-> + tx_rx_cal_radio_saveregs + [4]); + } else { + if (pi->pubpi.radiorev >= 5) + write_radio_reg( + pi, + RADIO_2056_RX_LNAG_MASTER + | RADIO_2056_RX1, + pi-> + tx_rx_cal_radio_saveregs + [4]); + else + write_radio_reg( + pi, + RADIO_2056_RX_LNAG_TUNE + | RADIO_2056_RX1, + pi-> + tx_rx_cal_radio_saveregs + [4]); + } + } + } +} + +static void wlc_phy_rxcal_physetup_nphy(struct brcms_phy *pi, u8 rx_core) +{ + u8 tx_core; + u16 rx_antval, tx_antval; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + tx_core = rx_core; + else + tx_core = (rx_core == PHY_CORE_0) ? 1 : 0; + + pi->tx_rx_cal_phy_saveregs[0] = read_phy_reg(pi, 0xa2); + pi->tx_rx_cal_phy_saveregs[1] = + read_phy_reg(pi, (rx_core == PHY_CORE_0) ? 0xa6 : 0xa7); + pi->tx_rx_cal_phy_saveregs[2] = + read_phy_reg(pi, (rx_core == PHY_CORE_0) ? 0x8f : 0xa5); + pi->tx_rx_cal_phy_saveregs[3] = read_phy_reg(pi, 0x91); + pi->tx_rx_cal_phy_saveregs[4] = read_phy_reg(pi, 0x92); + pi->tx_rx_cal_phy_saveregs[5] = read_phy_reg(pi, 0x7a); + pi->tx_rx_cal_phy_saveregs[6] = read_phy_reg(pi, 0x7d); + pi->tx_rx_cal_phy_saveregs[7] = read_phy_reg(pi, 0xe7); + pi->tx_rx_cal_phy_saveregs[8] = read_phy_reg(pi, 0xec); + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + pi->tx_rx_cal_phy_saveregs[11] = read_phy_reg(pi, 0x342); + pi->tx_rx_cal_phy_saveregs[12] = read_phy_reg(pi, 0x343); + pi->tx_rx_cal_phy_saveregs[13] = read_phy_reg(pi, 0x346); + pi->tx_rx_cal_phy_saveregs[14] = read_phy_reg(pi, 0x347); + } + + pi->tx_rx_cal_phy_saveregs[9] = read_phy_reg(pi, 0x297); + pi->tx_rx_cal_phy_saveregs[10] = read_phy_reg(pi, 0x29b); + mod_phy_reg(pi, (0 == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 0), (0) << 0); + + mod_phy_reg(pi, (1 == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 0), (0) << 0); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + mod_phy_reg(pi, 0xa2, (0xf << 0), (1 << tx_core) << 0); + + mod_phy_reg(pi, 0xa2, (0xf << 12), (1 << (1 - rx_core)) << 12); + + } else { + + mod_phy_reg(pi, 0xa2, (0xf << 12), (1 << tx_core) << 12); + mod_phy_reg(pi, 0xa2, (0xf << 0), (1 << tx_core) << 0); + mod_phy_reg(pi, 0xa2, (0xf << 4), (1 << rx_core) << 4); + mod_phy_reg(pi, 0xa2, (0xf << 8), (1 << rx_core) << 8); + } + + mod_phy_reg(pi, ((rx_core == PHY_CORE_0) ? 0xa6 : 0xa7), (0x1 << 2), 0); + mod_phy_reg(pi, (rx_core == PHY_CORE_0) ? 0x8f : 0xa5, + (0x1 << 2), (0x1 << 2)); + if (NREV_LT(pi->pubpi.phy_rev, 7)) { + mod_phy_reg(pi, ((rx_core == PHY_CORE_0) ? 0xa6 : 0xa7), + (0x1 << 0) | (0x1 << 1), 0); + mod_phy_reg(pi, (rx_core == PHY_CORE_0) ? + 0x8f : 0xa5, + (0x1 << 0) | (0x1 << 1), (0x1 << 0) | (0x1 << 1)); + } + + wlc_phy_rfctrlintc_override_nphy(pi, NPHY_RfctrlIntc_override_PA, 0, + RADIO_MIMO_CORESEL_CORE1 | + RADIO_MIMO_CORESEL_CORE2); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 3), + 0, 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID0); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 9), 0, 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 10), 1, 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 0), 1, 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 1), 1, 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID2); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 11), 0, 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + if (CHSPEC_IS40(pi->radio_chanspec)) + wlc_phy_rfctrl_override_nphy_rev7( + pi, + (0x1 << 7), + 2, 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + else + wlc_phy_rfctrl_override_nphy_rev7( + pi, + (0x1 << 7), + 0, 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 7), + 0, 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + wlc_phy_rfctrl_override_nphy_rev7(pi, (0x1 << 5), 0, 0, 0, + NPHY_REV7_RFCTRLOVERRIDE_ID1); + } else { + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 3), 0, 3, 0); + } + + wlc_phy_force_rfseq_nphy(pi, NPHY_RFSEQ_RX2TX); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + + wlc_phy_rfctrlintc_override_nphy(pi, + NPHY_RfctrlIntc_override_TRSW, + 0x1, rx_core + 1); + } else { + + if (rx_core == PHY_CORE_0) { + rx_antval = 0x1; + tx_antval = 0x8; + } else { + rx_antval = 0x4; + tx_antval = 0x2; + } + + wlc_phy_rfctrlintc_override_nphy(pi, + NPHY_RfctrlIntc_override_TRSW, + rx_antval, rx_core + 1); + wlc_phy_rfctrlintc_override_nphy(pi, + NPHY_RfctrlIntc_override_TRSW, + tx_antval, tx_core + 1); + } +} + +static void wlc_phy_rxcal_phycleanup_nphy(struct brcms_phy *pi, u8 rx_core) +{ + + write_phy_reg(pi, 0xa2, pi->tx_rx_cal_phy_saveregs[0]); + write_phy_reg(pi, (rx_core == PHY_CORE_0) ? 0xa6 : 0xa7, + pi->tx_rx_cal_phy_saveregs[1]); + write_phy_reg(pi, (rx_core == PHY_CORE_0) ? 0x8f : 0xa5, + pi->tx_rx_cal_phy_saveregs[2]); + write_phy_reg(pi, 0x91, pi->tx_rx_cal_phy_saveregs[3]); + write_phy_reg(pi, 0x92, pi->tx_rx_cal_phy_saveregs[4]); + + write_phy_reg(pi, 0x7a, pi->tx_rx_cal_phy_saveregs[5]); + write_phy_reg(pi, 0x7d, pi->tx_rx_cal_phy_saveregs[6]); + write_phy_reg(pi, 0xe7, pi->tx_rx_cal_phy_saveregs[7]); + write_phy_reg(pi, 0xec, pi->tx_rx_cal_phy_saveregs[8]); + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + write_phy_reg(pi, 0x342, pi->tx_rx_cal_phy_saveregs[11]); + write_phy_reg(pi, 0x343, pi->tx_rx_cal_phy_saveregs[12]); + write_phy_reg(pi, 0x346, pi->tx_rx_cal_phy_saveregs[13]); + write_phy_reg(pi, 0x347, pi->tx_rx_cal_phy_saveregs[14]); + } + + write_phy_reg(pi, 0x297, pi->tx_rx_cal_phy_saveregs[9]); + write_phy_reg(pi, 0x29b, pi->tx_rx_cal_phy_saveregs[10]); +} + +static void +wlc_phy_rxcal_gainctrl_nphy_rev5(struct brcms_phy *pi, u8 rx_core, + u16 *rxgain, u8 cal_type) +{ + + u16 num_samps; + struct phy_iq_est est[PHY_CORE_MAX]; + u8 tx_core; + struct nphy_iq_comp save_comp, zero_comp; + u32 i_pwr, q_pwr, curr_pwr, optim_pwr = 0, prev_pwr = 0, + thresh_pwr = 10000; + s16 desired_log2_pwr, actual_log2_pwr, delta_pwr; + bool gainctrl_done = false; + u8 mix_tia_gain = 3; + s8 optim_gaintbl_index = 0, prev_gaintbl_index = 0; + s8 curr_gaintbl_index = 3; + u8 gainctrl_dirn = NPHY_RXCAL_GAIN_INIT; + const struct nphy_ipa_txrxgain *nphy_rxcal_gaintbl; + u16 hpvga, lpf_biq1, lpf_biq0, lna2, lna1; + int fine_gain_idx; + s8 txpwrindex; + u16 nphy_rxcal_txgain[2]; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + tx_core = rx_core; + else + tx_core = 1 - rx_core; + + num_samps = 1024; + desired_log2_pwr = (cal_type == 0) ? 13 : 13; + + wlc_phy_rx_iq_coeffs_nphy(pi, 0, &save_comp); + zero_comp.a0 = zero_comp.b0 = zero_comp.a1 = zero_comp.b1 = 0x0; + wlc_phy_rx_iq_coeffs_nphy(pi, 1, &zero_comp); + + if (CHSPEC_IS5G(pi->radio_chanspec)) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) + mix_tia_gain = 3; + else if (NREV_GE(pi->pubpi.phy_rev, 4)) + mix_tia_gain = 4; + else + mix_tia_gain = 6; + if (NREV_GE(pi->pubpi.phy_rev, 7)) + nphy_rxcal_gaintbl = nphy_ipa_rxcal_gaintbl_5GHz_rev7; + else + nphy_rxcal_gaintbl = nphy_ipa_rxcal_gaintbl_5GHz; + } else { + if (NREV_GE(pi->pubpi.phy_rev, 7)) + nphy_rxcal_gaintbl = nphy_ipa_rxcal_gaintbl_2GHz_rev7; + else + nphy_rxcal_gaintbl = nphy_ipa_rxcal_gaintbl_2GHz; + } + + do { + + hpvga = (NREV_GE(pi->pubpi.phy_rev, 7)) ? + 0 : nphy_rxcal_gaintbl[curr_gaintbl_index].hpvga; + lpf_biq1 = nphy_rxcal_gaintbl[curr_gaintbl_index].lpf_biq1; + lpf_biq0 = nphy_rxcal_gaintbl[curr_gaintbl_index].lpf_biq0; + lna2 = nphy_rxcal_gaintbl[curr_gaintbl_index].lna2; + lna1 = nphy_rxcal_gaintbl[curr_gaintbl_index].lna1; + txpwrindex = nphy_rxcal_gaintbl[curr_gaintbl_index].txpwrindex; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + wlc_phy_rfctrl_override_1tomany_nphy( + pi, + NPHY_REV7_RfctrlOverride_cmd_rxgain, + ((lpf_biq1 << 12) | + (lpf_biq0 << 8) | + (mix_tia_gain << 4) | (lna2 << 2) + | lna1), 0x3, 0); + else + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 12), + ((hpvga << 12) | + (lpf_biq1 << 10) | + (lpf_biq0 << 8) | + (mix_tia_gain << 4) | + (lna2 << 2) | lna1), 0x3, + 0); + + pi->nphy_rxcal_pwr_idx[tx_core] = txpwrindex; + + if (txpwrindex == -1) { + nphy_rxcal_txgain[0] = 0x8ff0 | pi->nphy_gmval; + nphy_rxcal_txgain[1] = 0x8ff0 | pi->nphy_gmval; + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, + 2, 0x110, 16, + nphy_rxcal_txgain); + } else { + wlc_phy_txpwr_index_nphy(pi, tx_core + 1, txpwrindex, + false); + } + + wlc_phy_tx_tone_nphy(pi, (CHSPEC_IS40(pi->radio_chanspec)) ? + NPHY_RXCAL_TONEFREQ_40MHz : + NPHY_RXCAL_TONEFREQ_20MHz, + NPHY_RXCAL_TONEAMP, 0, cal_type, false); + + wlc_phy_rx_iq_est_nphy(pi, est, num_samps, 32, 0); + i_pwr = (est[rx_core].i_pwr + num_samps / 2) / num_samps; + q_pwr = (est[rx_core].q_pwr + num_samps / 2) / num_samps; + curr_pwr = i_pwr + q_pwr; + + switch (gainctrl_dirn) { + case NPHY_RXCAL_GAIN_INIT: + if (curr_pwr > thresh_pwr) { + gainctrl_dirn = NPHY_RXCAL_GAIN_DOWN; + prev_gaintbl_index = curr_gaintbl_index; + curr_gaintbl_index--; + } else { + gainctrl_dirn = NPHY_RXCAL_GAIN_UP; + prev_gaintbl_index = curr_gaintbl_index; + curr_gaintbl_index++; + } + break; + + case NPHY_RXCAL_GAIN_UP: + if (curr_pwr > thresh_pwr) { + gainctrl_done = true; + optim_pwr = prev_pwr; + optim_gaintbl_index = prev_gaintbl_index; + } else { + prev_gaintbl_index = curr_gaintbl_index; + curr_gaintbl_index++; + } + break; + + case NPHY_RXCAL_GAIN_DOWN: + if (curr_pwr > thresh_pwr) { + prev_gaintbl_index = curr_gaintbl_index; + curr_gaintbl_index--; + } else { + gainctrl_done = true; + optim_pwr = curr_pwr; + optim_gaintbl_index = curr_gaintbl_index; + } + break; + + default: + break; + } + + if ((curr_gaintbl_index < 0) || + (curr_gaintbl_index > NPHY_IPA_RXCAL_MAXGAININDEX)) { + gainctrl_done = true; + optim_pwr = curr_pwr; + optim_gaintbl_index = prev_gaintbl_index; + } else { + prev_pwr = curr_pwr; + } + + wlc_phy_stopplayback_nphy(pi); + } while (!gainctrl_done); + + hpvga = nphy_rxcal_gaintbl[optim_gaintbl_index].hpvga; + lpf_biq1 = nphy_rxcal_gaintbl[optim_gaintbl_index].lpf_biq1; + lpf_biq0 = nphy_rxcal_gaintbl[optim_gaintbl_index].lpf_biq0; + lna2 = nphy_rxcal_gaintbl[optim_gaintbl_index].lna2; + lna1 = nphy_rxcal_gaintbl[optim_gaintbl_index].lna1; + txpwrindex = nphy_rxcal_gaintbl[optim_gaintbl_index].txpwrindex; + + actual_log2_pwr = wlc_phy_nbits(optim_pwr); + delta_pwr = desired_log2_pwr - actual_log2_pwr; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + fine_gain_idx = (int)lpf_biq1 + delta_pwr; + + if (fine_gain_idx + (int)lpf_biq0 > 10) + lpf_biq1 = 10 - lpf_biq0; + else + lpf_biq1 = (u16) max(fine_gain_idx, 0); + + wlc_phy_rfctrl_override_1tomany_nphy( + pi, + NPHY_REV7_RfctrlOverride_cmd_rxgain, + ((lpf_biq1 << 12) | + (lpf_biq0 << 8) | + (mix_tia_gain << 4) | + (lna2 << 2) | lna1), 0x3, + 0); + } else { + hpvga = (u16) max(min(((int)hpvga) + delta_pwr, 10), 0); + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 12), + ((hpvga << 12) | + (lpf_biq1 << 10) | + (lpf_biq0 << 8) | + (mix_tia_gain << 4) | + (lna2 << 2) | + lna1), 0x3, 0); + } + + if (rxgain != NULL) { + *rxgain++ = lna1; + *rxgain++ = lna2; + *rxgain++ = mix_tia_gain; + *rxgain++ = lpf_biq0; + *rxgain++ = lpf_biq1; + *rxgain = hpvga; + } + + wlc_phy_rx_iq_coeffs_nphy(pi, 1, &save_comp); +} + +static void +wlc_phy_rxcal_gainctrl_nphy(struct brcms_phy *pi, u8 rx_core, u16 *rxgain, + u8 cal_type) +{ + wlc_phy_rxcal_gainctrl_nphy_rev5(pi, rx_core, rxgain, cal_type); +} + +static u8 +wlc_phy_rc_sweep_nphy(struct brcms_phy *pi, u8 core_idx, u8 loopback_type) +{ + u32 target_bws[2] = { 9500, 21000 }; + u32 ref_tones[2] = { 3000, 6000 }; + u32 target_bw, ref_tone; + + u32 target_pwr_ratios[2] = { 28606, 18468 }; + u32 target_pwr_ratio, pwr_ratio, last_pwr_ratio = 0; + + u16 start_rccal_ovr_val = 128; + u16 txlpf_rccal_lpc_ovr_val = 128; + u16 rxlpf_rccal_hpc_ovr_val = 159; + + u16 orig_txlpf_rccal_lpc_ovr_val; + u16 orig_rxlpf_rccal_hpc_ovr_val; + u16 radio_addr_offset_rx; + u16 radio_addr_offset_tx; + u16 orig_dcBypass; + u16 orig_RxStrnFilt40Num[6]; + u16 orig_RxStrnFilt40Den[4]; + u16 orig_rfctrloverride[2]; + u16 orig_rfctrlauxreg[2]; + u16 orig_rfctrlrssiothers; + u16 tx_lpf_bw = 4; + + u16 rx_lpf_bw, rx_lpf_bws[2] = { 2, 4 }; + u16 lpf_hpc = 7, hpvga_hpc = 7; + + s8 rccal_stepsize; + u16 rccal_val, last_rccal_val = 0, best_rccal_val = 0; + u32 ref_iq_vals = 0, target_iq_vals = 0; + u16 num_samps, log_num_samps = 10; + struct phy_iq_est est[PHY_CORE_MAX]; + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + return 0; + + num_samps = (1 << log_num_samps); + + if (CHSPEC_IS40(pi->radio_chanspec)) { + target_bw = target_bws[1]; + target_pwr_ratio = target_pwr_ratios[1]; + ref_tone = ref_tones[1]; + rx_lpf_bw = rx_lpf_bws[1]; + } else { + target_bw = target_bws[0]; + target_pwr_ratio = target_pwr_ratios[0]; + ref_tone = ref_tones[0]; + rx_lpf_bw = rx_lpf_bws[0]; + } + + if (core_idx == 0) { + radio_addr_offset_rx = RADIO_2056_RX0; + radio_addr_offset_tx = + (loopback_type == 0) ? RADIO_2056_TX0 : RADIO_2056_TX1; + } else { + radio_addr_offset_rx = RADIO_2056_RX1; + radio_addr_offset_tx = + (loopback_type == 0) ? RADIO_2056_TX1 : RADIO_2056_TX0; + } + + orig_txlpf_rccal_lpc_ovr_val = + read_radio_reg(pi, + (RADIO_2056_TX_TXLPF_RCCAL | + radio_addr_offset_tx)); + orig_rxlpf_rccal_hpc_ovr_val = + read_radio_reg(pi, + (RADIO_2056_RX_RXLPF_RCCAL_HPC | + radio_addr_offset_rx)); + + orig_dcBypass = ((read_phy_reg(pi, 0x48) >> 8) & 1); + + orig_RxStrnFilt40Num[0] = read_phy_reg(pi, 0x267); + orig_RxStrnFilt40Num[1] = read_phy_reg(pi, 0x268); + orig_RxStrnFilt40Num[2] = read_phy_reg(pi, 0x269); + orig_RxStrnFilt40Den[0] = read_phy_reg(pi, 0x26a); + orig_RxStrnFilt40Den[1] = read_phy_reg(pi, 0x26b); + orig_RxStrnFilt40Num[3] = read_phy_reg(pi, 0x26c); + orig_RxStrnFilt40Num[4] = read_phy_reg(pi, 0x26d); + orig_RxStrnFilt40Num[5] = read_phy_reg(pi, 0x26e); + orig_RxStrnFilt40Den[2] = read_phy_reg(pi, 0x26f); + orig_RxStrnFilt40Den[3] = read_phy_reg(pi, 0x270); + + orig_rfctrloverride[0] = read_phy_reg(pi, 0xe7); + orig_rfctrloverride[1] = read_phy_reg(pi, 0xec); + orig_rfctrlauxreg[0] = read_phy_reg(pi, 0xf8); + orig_rfctrlauxreg[1] = read_phy_reg(pi, 0xfa); + orig_rfctrlrssiothers = read_phy_reg(pi, (core_idx == 0) ? 0x7a : 0x7d); + + write_radio_reg(pi, (RADIO_2056_TX_TXLPF_RCCAL | radio_addr_offset_tx), + txlpf_rccal_lpc_ovr_val); + + write_radio_reg(pi, + (RADIO_2056_RX_RXLPF_RCCAL_HPC | radio_addr_offset_rx), + rxlpf_rccal_hpc_ovr_val); + + mod_phy_reg(pi, 0x48, (0x1 << 8), (0x1 << 8)); + + write_phy_reg(pi, 0x267, 0x02d4); + write_phy_reg(pi, 0x268, 0x0000); + write_phy_reg(pi, 0x269, 0x0000); + write_phy_reg(pi, 0x26a, 0x0000); + write_phy_reg(pi, 0x26b, 0x0000); + write_phy_reg(pi, 0x26c, 0x02d4); + write_phy_reg(pi, 0x26d, 0x0000); + write_phy_reg(pi, 0x26e, 0x0000); + write_phy_reg(pi, 0x26f, 0x0000); + write_phy_reg(pi, 0x270, 0x0000); + + or_phy_reg(pi, (core_idx == 0) ? 0xe7 : 0xec, (0x1 << 8)); + or_phy_reg(pi, (core_idx == 0) ? 0xec : 0xe7, (0x1 << 15)); + or_phy_reg(pi, (core_idx == 0) ? 0xe7 : 0xec, (0x1 << 9)); + or_phy_reg(pi, (core_idx == 0) ? 0xe7 : 0xec, (0x1 << 10)); + + mod_phy_reg(pi, (core_idx == 0) ? 0xfa : 0xf8, + (0x7 << 10), (tx_lpf_bw << 10)); + mod_phy_reg(pi, (core_idx == 0) ? 0xf8 : 0xfa, + (0x7 << 0), (hpvga_hpc << 0)); + mod_phy_reg(pi, (core_idx == 0) ? 0xf8 : 0xfa, + (0x7 << 4), (lpf_hpc << 4)); + mod_phy_reg(pi, (core_idx == 0) ? 0x7a : 0x7d, + (0x7 << 8), (rx_lpf_bw << 8)); + + rccal_stepsize = 16; + rccal_val = start_rccal_ovr_val + rccal_stepsize; + + while (rccal_stepsize >= 0) { + write_radio_reg(pi, + (RADIO_2056_RX_RXLPF_RCCAL_LPC | + radio_addr_offset_rx), rccal_val); + + if (rccal_stepsize == 16) { + + wlc_phy_tx_tone_nphy(pi, ref_tone, NPHY_RXCAL_TONEAMP, + 0, 1, false); + udelay(2); + + wlc_phy_rx_iq_est_nphy(pi, est, num_samps, 32, 0); + + if (core_idx == 0) + ref_iq_vals = + max_t(u32, (est[0].i_pwr + + est[0].q_pwr) >> + (log_num_samps + 1), + 1); + else + ref_iq_vals = + max_t(u32, (est[1].i_pwr + + est[1].q_pwr) >> + (log_num_samps + 1), + 1); + + wlc_phy_tx_tone_nphy(pi, target_bw, NPHY_RXCAL_TONEAMP, + 0, 1, false); + udelay(2); + } + + wlc_phy_rx_iq_est_nphy(pi, est, num_samps, 32, 0); + + if (core_idx == 0) + target_iq_vals = (est[0].i_pwr + est[0].q_pwr) >> + (log_num_samps + 1); + else + target_iq_vals = + (est[1].i_pwr + + est[1].q_pwr) >> (log_num_samps + 1); + + pwr_ratio = (uint) ((target_iq_vals << 16) / ref_iq_vals); + + if (rccal_stepsize == 0) + rccal_stepsize--; + else if (rccal_stepsize == 1) { + last_rccal_val = rccal_val; + rccal_val += (pwr_ratio > target_pwr_ratio) ? 1 : -1; + last_pwr_ratio = pwr_ratio; + rccal_stepsize--; + } else { + rccal_stepsize = (rccal_stepsize >> 1); + rccal_val += ((pwr_ratio > target_pwr_ratio) ? + rccal_stepsize : (-rccal_stepsize)); + } + + if (rccal_stepsize == -1) { + best_rccal_val = + (abs((int)last_pwr_ratio - + (int)target_pwr_ratio) < + abs((int)pwr_ratio - + (int)target_pwr_ratio)) ? last_rccal_val : + rccal_val; + + if (CHSPEC_IS40(pi->radio_chanspec)) { + if ((best_rccal_val > 140) + || (best_rccal_val < 135)) + best_rccal_val = 138; + } else { + if ((best_rccal_val > 142) + || (best_rccal_val < 137)) + best_rccal_val = 140; + } + + write_radio_reg(pi, + (RADIO_2056_RX_RXLPF_RCCAL_LPC | + radio_addr_offset_rx), best_rccal_val); + } + } + + wlc_phy_stopplayback_nphy(pi); + + write_radio_reg(pi, (RADIO_2056_TX_TXLPF_RCCAL | radio_addr_offset_tx), + orig_txlpf_rccal_lpc_ovr_val); + write_radio_reg(pi, + (RADIO_2056_RX_RXLPF_RCCAL_HPC | radio_addr_offset_rx), + orig_rxlpf_rccal_hpc_ovr_val); + + mod_phy_reg(pi, 0x48, (0x1 << 8), (orig_dcBypass << 8)); + + write_phy_reg(pi, 0x267, orig_RxStrnFilt40Num[0]); + write_phy_reg(pi, 0x268, orig_RxStrnFilt40Num[1]); + write_phy_reg(pi, 0x269, orig_RxStrnFilt40Num[2]); + write_phy_reg(pi, 0x26a, orig_RxStrnFilt40Den[0]); + write_phy_reg(pi, 0x26b, orig_RxStrnFilt40Den[1]); + write_phy_reg(pi, 0x26c, orig_RxStrnFilt40Num[3]); + write_phy_reg(pi, 0x26d, orig_RxStrnFilt40Num[4]); + write_phy_reg(pi, 0x26e, orig_RxStrnFilt40Num[5]); + write_phy_reg(pi, 0x26f, orig_RxStrnFilt40Den[2]); + write_phy_reg(pi, 0x270, orig_RxStrnFilt40Den[3]); + + write_phy_reg(pi, 0xe7, orig_rfctrloverride[0]); + write_phy_reg(pi, 0xec, orig_rfctrloverride[1]); + write_phy_reg(pi, 0xf8, orig_rfctrlauxreg[0]); + write_phy_reg(pi, 0xfa, orig_rfctrlauxreg[1]); + write_phy_reg(pi, (core_idx == 0) ? 0x7a : 0x7d, orig_rfctrlrssiothers); + + pi->nphy_anarxlpf_adjusted = false; + + return best_rccal_val - 0x80; +} + +#define WAIT_FOR_SCOPE 4000 +static int wlc_phy_cal_rxiq_nphy_rev3(struct brcms_phy *pi, + struct nphy_txgains target_gain, + u8 cal_type, bool debug) +{ + u16 orig_BBConfig; + u8 core_no, rx_core; + u8 best_rccal[2]; + u16 gain_save[2]; + u16 cal_gain[2]; + struct nphy_iqcal_params cal_params[2]; + u8 rxcore_state; + s8 rxlpf_rccal_hpc, txlpf_rccal_lpc; + s8 txlpf_idac; + bool phyhang_avoid_state = false; + bool skip_rxiqcal = false; + + orig_BBConfig = read_phy_reg(pi, 0x01); + mod_phy_reg(pi, 0x01, (0x1 << 15), 0); + + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + if (NREV_GE(pi->pubpi.phy_rev, 4)) { + phyhang_avoid_state = pi->phyhang_avoid; + pi->phyhang_avoid = false; + } + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_RFSEQ, 2, 0x110, 16, gain_save); + + for (core_no = 0; core_no <= 1; core_no++) { + wlc_phy_iqcal_gainparams_nphy(pi, core_no, target_gain, + &cal_params[core_no]); + cal_gain[core_no] = cal_params[core_no].cal_gain; + } + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, 2, 0x110, 16, cal_gain); + + rxcore_state = wlc_phy_rxcore_getstate_nphy( + (struct brcms_phy_pub *) pi); + + for (rx_core = 0; rx_core < pi->pubpi.phy_corenum; rx_core++) { + + skip_rxiqcal = + ((rxcore_state & (1 << rx_core)) == 0) ? true : false; + + wlc_phy_rxcal_physetup_nphy(pi, rx_core); + + wlc_phy_rxcal_radio_setup_nphy(pi, rx_core); + + if ((!skip_rxiqcal) && ((cal_type == 0) || (cal_type == 2))) { + + wlc_phy_rxcal_gainctrl_nphy(pi, rx_core, NULL, 0); + + wlc_phy_tx_tone_nphy(pi, + (CHSPEC_IS40( + pi->radio_chanspec)) ? + NPHY_RXCAL_TONEFREQ_40MHz : + NPHY_RXCAL_TONEFREQ_20MHz, + NPHY_RXCAL_TONEAMP, 0, cal_type, + false); + + if (debug) + mdelay(WAIT_FOR_SCOPE); + + wlc_phy_calc_rx_iq_comp_nphy(pi, rx_core + 1); + wlc_phy_stopplayback_nphy(pi); + } + + if (((cal_type == 1) || (cal_type == 2)) + && NREV_LT(pi->pubpi.phy_rev, 7)) { + + if (rx_core == PHY_CORE_1) { + + if (rxcore_state == 1) + wlc_phy_rxcore_setstate_nphy( + (struct brcms_phy_pub *) pi, 3); + + wlc_phy_rxcal_gainctrl_nphy(pi, rx_core, NULL, + 1); + + best_rccal[rx_core] = + wlc_phy_rc_sweep_nphy(pi, rx_core, 1); + pi->nphy_rccal_value = best_rccal[rx_core]; + + if (rxcore_state == 1) + wlc_phy_rxcore_setstate_nphy( + (struct brcms_phy_pub *) pi, + rxcore_state); + } + } + + wlc_phy_rxcal_radio_cleanup_nphy(pi, rx_core); + + wlc_phy_rxcal_phycleanup_nphy(pi, rx_core); + wlc_phy_force_rfseq_nphy(pi, NPHY_RFSEQ_RESET2RX); + } + + if ((cal_type == 1) || (cal_type == 2)) { + + best_rccal[0] = best_rccal[1]; + write_radio_reg(pi, + (RADIO_2056_RX_RXLPF_RCCAL_LPC | + RADIO_2056_RX0), (best_rccal[0] | 0x80)); + + for (rx_core = 0; rx_core < pi->pubpi.phy_corenum; rx_core++) { + rxlpf_rccal_hpc = + (((int)best_rccal[rx_core] - 12) >> 1) + 10; + txlpf_rccal_lpc = ((int)best_rccal[rx_core] - 12) + 10; + + if (PHY_IPA(pi)) { + txlpf_rccal_lpc += + (pi->bw == WL_CHANSPEC_BW_40) ? 24 : 12; + txlpf_idac = (pi->bw == WL_CHANSPEC_BW_40) ? + 0x0e : 0x13; + WRITE_RADIO_REG2(pi, RADIO_2056, TX, rx_core, + TXLPF_IDAC_4, txlpf_idac); + } + + rxlpf_rccal_hpc = max(min_t(u8, rxlpf_rccal_hpc, 31), + 0); + txlpf_rccal_lpc = max(min_t(u8, txlpf_rccal_lpc, 31), + 0); + + write_radio_reg(pi, (RADIO_2056_RX_RXLPF_RCCAL_HPC | + ((rx_core == + PHY_CORE_0) ? RADIO_2056_RX0 : + RADIO_2056_RX1)), + (rxlpf_rccal_hpc | 0x80)); + + write_radio_reg(pi, (RADIO_2056_TX_TXLPF_RCCAL | + ((rx_core == + PHY_CORE_0) ? RADIO_2056_TX0 : + RADIO_2056_TX1)), + (txlpf_rccal_lpc | 0x80)); + } + } + + write_phy_reg(pi, 0x01, orig_BBConfig); + + wlc_phy_resetcca_nphy(pi); + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + wlc_phy_rfctrl_override_1tomany_nphy( + pi, + NPHY_REV7_RfctrlOverride_cmd_rxgain, + 0, 0x3, 1); + else + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 12), 0, 0x3, 1); + + wlc_phy_force_rfseq_nphy(pi, NPHY_RFSEQ_RESET2RX); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, 2, 0x110, 16, + gain_save); + + if (NREV_GE(pi->pubpi.phy_rev, 4)) + pi->phyhang_avoid = phyhang_avoid_state; + + wlc_phy_stay_in_carriersearch_nphy(pi, false); + + return 0; +} + +static int +wlc_phy_cal_rxiq_nphy_rev2(struct brcms_phy *pi, + struct nphy_txgains target_gain, bool debug) +{ + struct phy_iq_est est[PHY_CORE_MAX]; + u8 core_num, rx_core, tx_core; + u16 lna_vals[] = { 0x3, 0x3, 0x1 }; + u16 hpf1_vals[] = { 0x7, 0x2, 0x0 }; + u16 hpf2_vals[] = { 0x2, 0x0, 0x0 }; + s16 curr_hpf1, curr_hpf2, curr_hpf, curr_lna; + s16 desired_log2_pwr, actual_log2_pwr, hpf_change; + u16 orig_RfseqCoreActv, orig_AfectrlCore, orig_AfectrlOverride; + u16 orig_RfctrlIntcRx, orig_RfctrlIntcTx; + u16 num_samps; + u32 i_pwr, q_pwr, tot_pwr[3]; + u8 gain_pass, use_hpf_num; + u16 mask, val1, val2; + u16 core_no; + u16 gain_save[2]; + u16 cal_gain[2]; + struct nphy_iqcal_params cal_params[2]; + u8 phy_bw; + int bcmerror = 0; + bool first_playtone = true; + + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + if (NREV_LT(pi->pubpi.phy_rev, 2)) + wlc_phy_reapply_txcal_coeffs_nphy(pi); + + wlc_phy_table_read_nphy(pi, NPHY_TBL_ID_RFSEQ, 2, 0x110, 16, gain_save); + + for (core_no = 0; core_no <= 1; core_no++) { + wlc_phy_iqcal_gainparams_nphy(pi, core_no, target_gain, + &cal_params[core_no]); + cal_gain[core_no] = cal_params[core_no].cal_gain; + } + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, 2, 0x110, 16, cal_gain); + + num_samps = 1024; + desired_log2_pwr = 13; + + for (core_num = 0; core_num < 2; core_num++) { + + rx_core = core_num; + tx_core = 1 - core_num; + + orig_RfseqCoreActv = read_phy_reg(pi, 0xa2); + orig_AfectrlCore = read_phy_reg(pi, (rx_core == PHY_CORE_0) ? + 0xa6 : 0xa7); + orig_AfectrlOverride = read_phy_reg(pi, 0xa5); + orig_RfctrlIntcRx = read_phy_reg(pi, (rx_core == PHY_CORE_0) ? + 0x91 : 0x92); + orig_RfctrlIntcTx = read_phy_reg(pi, (tx_core == PHY_CORE_0) ? + 0x91 : 0x92); + + mod_phy_reg(pi, 0xa2, (0xf << 12), (1 << tx_core) << 12); + mod_phy_reg(pi, 0xa2, (0xf << 0), (1 << tx_core) << 0); + + or_phy_reg(pi, ((rx_core == PHY_CORE_0) ? 0xa6 : 0xa7), + ((0x1 << 1) | (0x1 << 2))); + or_phy_reg(pi, 0xa5, ((0x1 << 1) | (0x1 << 2))); + + if (((pi->nphy_rxcalparams) & 0xff000000)) + write_phy_reg(pi, + (rx_core == PHY_CORE_0) ? 0x91 : 0x92, + (CHSPEC_IS5G(pi->radio_chanspec) ? + 0x140 : 0x110)); + else + write_phy_reg(pi, + (rx_core == PHY_CORE_0) ? 0x91 : 0x92, + (CHSPEC_IS5G(pi->radio_chanspec) ? + 0x180 : 0x120)); + + write_phy_reg(pi, (tx_core == PHY_CORE_0) ? 0x91 : 0x92, + (CHSPEC_IS5G(pi->radio_chanspec) ? 0x148 : + 0x114)); + + mask = RADIO_2055_COUPLE_RX_MASK | RADIO_2055_COUPLE_TX_MASK; + if (rx_core == PHY_CORE_0) { + val1 = RADIO_2055_COUPLE_RX_MASK; + val2 = RADIO_2055_COUPLE_TX_MASK; + } else { + val1 = RADIO_2055_COUPLE_TX_MASK; + val2 = RADIO_2055_COUPLE_RX_MASK; + } + + if ((pi->nphy_rxcalparams & 0x10000)) { + mod_radio_reg(pi, RADIO_2055_CORE1_GEN_SPARE2, mask, + val1); + mod_radio_reg(pi, RADIO_2055_CORE2_GEN_SPARE2, mask, + val2); + } + + for (gain_pass = 0; gain_pass < 4; gain_pass++) { + + if (debug) + mdelay(WAIT_FOR_SCOPE); + + if (gain_pass < 3) { + curr_lna = lna_vals[gain_pass]; + curr_hpf1 = hpf1_vals[gain_pass]; + curr_hpf2 = hpf2_vals[gain_pass]; + } else { + + if (tot_pwr[1] > 10000) { + curr_lna = lna_vals[2]; + curr_hpf1 = hpf1_vals[2]; + curr_hpf2 = hpf2_vals[2]; + use_hpf_num = 1; + curr_hpf = curr_hpf1; + actual_log2_pwr = + wlc_phy_nbits(tot_pwr[2]); + } else { + if (tot_pwr[0] > 10000) { + curr_lna = lna_vals[1]; + curr_hpf1 = hpf1_vals[1]; + curr_hpf2 = hpf2_vals[1]; + use_hpf_num = 1; + curr_hpf = curr_hpf1; + actual_log2_pwr = + wlc_phy_nbits( + tot_pwr[1]); + } else { + curr_lna = lna_vals[0]; + curr_hpf1 = hpf1_vals[0]; + curr_hpf2 = hpf2_vals[0]; + use_hpf_num = 2; + curr_hpf = curr_hpf2; + actual_log2_pwr = + wlc_phy_nbits( + tot_pwr[0]); + } + } + + hpf_change = desired_log2_pwr - actual_log2_pwr; + curr_hpf += hpf_change; + curr_hpf = max(min_t(u16, curr_hpf, 10), 0); + if (use_hpf_num == 1) + curr_hpf1 = curr_hpf; + else + curr_hpf2 = curr_hpf; + } + + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 10), + ((curr_hpf2 << 8) | + (curr_hpf1 << 4) | + (curr_lna << 2)), 0x3, 0); + wlc_phy_force_rfseq_nphy(pi, NPHY_RFSEQ_RESET2RX); + + wlc_phy_stopplayback_nphy(pi); + + if (first_playtone) { + bcmerror = wlc_phy_tx_tone_nphy(pi, 4000, + (u16) (pi->nphy_rxcalparams & + 0xffff), 0, 0, true); + first_playtone = false; + } else { + phy_bw = (CHSPEC_IS40(pi->radio_chanspec)) ? + 40 : 20; + wlc_phy_runsamples_nphy(pi, phy_bw * 8, 0xffff, + 0, 0, 0, true); + } + + if (bcmerror == 0) { + if (gain_pass < 3) { + + wlc_phy_rx_iq_est_nphy(pi, est, + num_samps, 32, + 0); + i_pwr = (est[rx_core].i_pwr + + num_samps / 2) / num_samps; + q_pwr = (est[rx_core].q_pwr + + num_samps / 2) / num_samps; + tot_pwr[gain_pass] = i_pwr + q_pwr; + } else { + + wlc_phy_calc_rx_iq_comp_nphy(pi, + (1 << + rx_core)); + } + + wlc_phy_stopplayback_nphy(pi); + } + + if (bcmerror != 0) + break; + } + + and_radio_reg(pi, RADIO_2055_CORE1_GEN_SPARE2, ~mask); + and_radio_reg(pi, RADIO_2055_CORE2_GEN_SPARE2, ~mask); + + write_phy_reg(pi, (tx_core == PHY_CORE_0) ? 0x91 : + 0x92, orig_RfctrlIntcTx); + write_phy_reg(pi, (rx_core == PHY_CORE_0) ? 0x91 : + 0x92, orig_RfctrlIntcRx); + write_phy_reg(pi, 0xa5, orig_AfectrlOverride); + write_phy_reg(pi, (rx_core == PHY_CORE_0) ? 0xa6 : + 0xa7, orig_AfectrlCore); + write_phy_reg(pi, 0xa2, orig_RfseqCoreActv); + + if (bcmerror != 0) + break; + } + + wlc_phy_rfctrl_override_nphy(pi, (0x1 << 10), 0, 0x3, 1); + wlc_phy_force_rfseq_nphy(pi, NPHY_RFSEQ_RESET2RX); + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_RFSEQ, 2, 0x110, 16, + gain_save); + + wlc_phy_stay_in_carriersearch_nphy(pi, false); + + return bcmerror; +} + +int +wlc_phy_cal_rxiq_nphy(struct brcms_phy *pi, struct nphy_txgains target_gain, + u8 cal_type, bool debug) +{ + if (NREV_GE(pi->pubpi.phy_rev, 7)) + cal_type = 0; + + if (NREV_GE(pi->pubpi.phy_rev, 3)) + return wlc_phy_cal_rxiq_nphy_rev3(pi, target_gain, cal_type, + debug); + else + return wlc_phy_cal_rxiq_nphy_rev2(pi, target_gain, debug); +} + +void wlc_phy_txpwr_fixpower_nphy(struct brcms_phy *pi) +{ + uint core; + u32 txgain; + u16 rad_gain, dac_gain, bbmult, m1m2; + u8 txpi[2], chan_freq_range; + s32 rfpwr_offset; + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + if (pi->sh->sromrev < 4) { + txpi[0] = txpi[1] = 72; + } else { + + chan_freq_range = wlc_phy_get_chan_freq_range_nphy(pi, 0); + switch (chan_freq_range) { + case WL_CHAN_FREQ_RANGE_2G: + case WL_CHAN_FREQ_RANGE_5GL: + case WL_CHAN_FREQ_RANGE_5GM: + case WL_CHAN_FREQ_RANGE_5GH: + txpi[0] = 0; + txpi[1] = 0; + break; + default: + txpi[0] = txpi[1] = 91; + break; + } + } + + if (NREV_GE(pi->pubpi.phy_rev, 7)) + txpi[0] = txpi[1] = 30; + else if (NREV_GE(pi->pubpi.phy_rev, 3)) + txpi[0] = txpi[1] = 40; + + if (NREV_LT(pi->pubpi.phy_rev, 7)) { + + if ((txpi[0] < 40) || (txpi[0] > 100) || + (txpi[1] < 40) || (txpi[1] > 100)) + txpi[0] = txpi[1] = 91; + } + + pi->nphy_txpwrindex[PHY_CORE_0].index_internal = txpi[0]; + pi->nphy_txpwrindex[PHY_CORE_1].index_internal = txpi[1]; + pi->nphy_txpwrindex[PHY_CORE_0].index_internal_save = txpi[0]; + pi->nphy_txpwrindex[PHY_CORE_1].index_internal_save = txpi[1]; + + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + uint phyrev = pi->pubpi.phy_rev; + + if (NREV_GE(phyrev, 3)) { + if (PHY_IPA(pi)) { + u32 *tx_gaintbl = + wlc_phy_get_ipa_gaintbl_nphy(pi); + txgain = tx_gaintbl[txpi[core]]; + } else { + if (CHSPEC_IS5G(pi->radio_chanspec)) { + if (NREV_IS(phyrev, 3)) { + txgain = + nphy_tpc_5GHz_txgain_rev3 + [txpi[core]]; + } else if (NREV_IS(phyrev, 4)) { + txgain = ( + pi->srom_fem5g.extpagain == + 3) ? + nphy_tpc_5GHz_txgain_HiPwrEPA + [txpi[core]] : + nphy_tpc_5GHz_txgain_rev4 + [txpi[core]]; + } else { + txgain = + nphy_tpc_5GHz_txgain_rev5 + [txpi[core]]; + } + } else { + if (NREV_GE(phyrev, 5) && + (pi->srom_fem2g.extpagain == 3)) { + txgain = + nphy_tpc_txgain_HiPwrEPA + [txpi[core]]; + } else { + txgain = nphy_tpc_txgain_rev3 + [txpi[core]]; + } + } + } + } else { + txgain = nphy_tpc_txgain[txpi[core]]; + } + + if (NREV_GE(phyrev, 3)) + rad_gain = (txgain >> 16) & ((1 << (32 - 16 + 1)) - 1); + else + rad_gain = (txgain >> 16) & ((1 << (28 - 16 + 1)) - 1); + + if (NREV_GE(phyrev, 7)) + dac_gain = (txgain >> 8) & ((1 << (10 - 8 + 1)) - 1); + else + dac_gain = (txgain >> 8) & ((1 << (13 - 8 + 1)) - 1); + + bbmult = (txgain >> 0) & ((1 << (7 - 0 + 1)) - 1); + + if (NREV_GE(phyrev, 3)) + mod_phy_reg(pi, ((core == PHY_CORE_0) ? 0x8f : + 0xa5), (0x1 << 8), (0x1 << 8)); + else + mod_phy_reg(pi, 0xa5, (0x1 << 14), (0x1 << 14)); + + write_phy_reg(pi, (core == PHY_CORE_0) ? 0xaa : 0xab, dac_gain); + + wlc_phy_table_write_nphy(pi, 7, 1, (0x110 + core), 16, + &rad_gain); + + wlc_phy_table_read_nphy(pi, 15, 1, 87, 16, &m1m2); + m1m2 &= ((core == PHY_CORE_0) ? 0x00ff : 0xff00); + m1m2 |= ((core == PHY_CORE_0) ? (bbmult << 8) : (bbmult << 0)); + wlc_phy_table_write_nphy(pi, 15, 1, 87, 16, &m1m2); + + if (PHY_IPA(pi)) { + wlc_phy_table_read_nphy(pi, + (core == + PHY_CORE_0 ? + NPHY_TBL_ID_CORE1TXPWRCTL : + NPHY_TBL_ID_CORE2TXPWRCTL), 1, + 576 + txpi[core], 32, + &rfpwr_offset); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1ff << 4), + ((s16) rfpwr_offset) << 4); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 2), (1) << 2); + + } + } + + and_phy_reg(pi, 0xbf, (u16) (~(0x1f << 0))); + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, false); +} + +static void +wlc_phy_txpwr_nphy_srom_convert(u8 *srom_max, u16 *pwr_offset, + u8 tmp_max_pwr, u8 rate_start, + u8 rate_end) +{ + u8 rate; + u8 word_num, nibble_num; + u8 tmp_nibble; + + for (rate = rate_start; rate <= rate_end; rate++) { + word_num = (rate - rate_start) >> 2; + nibble_num = (rate - rate_start) & 0x3; + tmp_nibble = (pwr_offset[word_num] >> 4 * nibble_num) & 0xf; + + srom_max[rate] = tmp_max_pwr - 2 * tmp_nibble; + } +} + +static void +wlc_phy_txpwr_nphy_po_apply(u8 *srom_max, u8 pwr_offset, + u8 rate_start, u8 rate_end) +{ + u8 rate; + + for (rate = rate_start; rate <= rate_end; rate++) + srom_max[rate] -= 2 * pwr_offset; +} + +void +wlc_phy_ofdm_to_mcs_powers_nphy(u8 *power, u8 rate_mcs_start, + u8 rate_mcs_end, u8 rate_ofdm_start) +{ + u8 rate1, rate2; + + rate2 = rate_ofdm_start; + for (rate1 = rate_mcs_start; rate1 <= rate_mcs_end - 1; rate1++) { + power[rate1] = power[rate2]; + rate2 += (rate1 == rate_mcs_start) ? 2 : 1; + } + power[rate_mcs_end] = power[rate_mcs_end - 1]; +} + +void +wlc_phy_mcs_to_ofdm_powers_nphy(u8 *power, u8 rate_ofdm_start, + u8 rate_ofdm_end, u8 rate_mcs_start) +{ + u8 rate1, rate2; + + for (rate1 = rate_ofdm_start, rate2 = rate_mcs_start; + rate1 <= rate_ofdm_end; rate1++, rate2++) { + power[rate1] = power[rate2]; + if (rate1 == rate_ofdm_start) + power[++rate1] = power[rate2]; + } +} + +void wlc_phy_txpwr_apply_nphy(struct brcms_phy *pi) +{ + uint rate1, rate2, band_num; + u8 tmp_bw40po = 0, tmp_cddpo = 0, tmp_stbcpo = 0; + u8 tmp_max_pwr = 0; + u16 pwr_offsets1[2], *pwr_offsets2 = NULL; + u8 *tx_srom_max_rate = NULL; + + for (band_num = 0; band_num < (CH_2G_GROUP + CH_5G_GROUP); + band_num++) { + switch (band_num) { + case 0: + + tmp_max_pwr = min(pi->nphy_pwrctrl_info[0].max_pwr_2g, + pi->nphy_pwrctrl_info[1].max_pwr_2g); + + pwr_offsets1[0] = pi->cck2gpo; + wlc_phy_txpwr_nphy_srom_convert(pi->tx_srom_max_rate_2g, + pwr_offsets1, + tmp_max_pwr, + TXP_FIRST_CCK, + TXP_LAST_CCK); + + pwr_offsets1[0] = (u16) (pi->ofdm2gpo & 0xffff); + pwr_offsets1[1] = + (u16) (pi->ofdm2gpo >> 16) & 0xffff; + + pwr_offsets2 = pi->mcs2gpo; + + tmp_cddpo = pi->cdd2gpo; + tmp_stbcpo = pi->stbc2gpo; + tmp_bw40po = pi->bw402gpo; + + tx_srom_max_rate = pi->tx_srom_max_rate_2g; + break; + case 1: + + tmp_max_pwr = min(pi->nphy_pwrctrl_info[0].max_pwr_5gm, + pi->nphy_pwrctrl_info[1].max_pwr_5gm); + + pwr_offsets1[0] = (u16) (pi->ofdm5gpo & 0xffff); + pwr_offsets1[1] = + (u16) (pi->ofdm5gpo >> 16) & 0xffff; + + pwr_offsets2 = pi->mcs5gpo; + + tmp_cddpo = pi->cdd5gpo; + tmp_stbcpo = pi->stbc5gpo; + tmp_bw40po = pi->bw405gpo; + + tx_srom_max_rate = pi->tx_srom_max_rate_5g_mid; + break; + case 2: + + tmp_max_pwr = min(pi->nphy_pwrctrl_info[0].max_pwr_5gl, + pi->nphy_pwrctrl_info[1].max_pwr_5gl); + + pwr_offsets1[0] = (u16) (pi->ofdm5glpo & 0xffff); + pwr_offsets1[1] = + (u16) (pi->ofdm5glpo >> 16) & 0xffff; + + pwr_offsets2 = pi->mcs5glpo; + + tmp_cddpo = pi->cdd5glpo; + tmp_stbcpo = pi->stbc5glpo; + tmp_bw40po = pi->bw405glpo; + + tx_srom_max_rate = pi->tx_srom_max_rate_5g_low; + break; + case 3: + + tmp_max_pwr = min(pi->nphy_pwrctrl_info[0].max_pwr_5gh, + pi->nphy_pwrctrl_info[1].max_pwr_5gh); + + pwr_offsets1[0] = (u16) (pi->ofdm5ghpo & 0xffff); + pwr_offsets1[1] = + (u16) (pi->ofdm5ghpo >> 16) & 0xffff; + + pwr_offsets2 = pi->mcs5ghpo; + + tmp_cddpo = pi->cdd5ghpo; + tmp_stbcpo = pi->stbc5ghpo; + tmp_bw40po = pi->bw405ghpo; + + tx_srom_max_rate = pi->tx_srom_max_rate_5g_hi; + break; + } + + wlc_phy_txpwr_nphy_srom_convert(tx_srom_max_rate, pwr_offsets1, + tmp_max_pwr, TXP_FIRST_OFDM, + TXP_LAST_OFDM); + + wlc_phy_ofdm_to_mcs_powers_nphy(tx_srom_max_rate, + TXP_FIRST_MCS_20_SISO, + TXP_LAST_MCS_20_SISO, + TXP_FIRST_OFDM); + + wlc_phy_txpwr_nphy_srom_convert(tx_srom_max_rate, pwr_offsets2, + tmp_max_pwr, + TXP_FIRST_MCS_20_CDD, + TXP_LAST_MCS_20_CDD); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) + wlc_phy_txpwr_nphy_po_apply(tx_srom_max_rate, tmp_cddpo, + TXP_FIRST_MCS_20_CDD, + TXP_LAST_MCS_20_CDD); + + wlc_phy_mcs_to_ofdm_powers_nphy(tx_srom_max_rate, + TXP_FIRST_OFDM_20_CDD, + TXP_LAST_OFDM_20_CDD, + TXP_FIRST_MCS_20_CDD); + + wlc_phy_txpwr_nphy_srom_convert(tx_srom_max_rate, pwr_offsets2, + tmp_max_pwr, + TXP_FIRST_MCS_20_STBC, + TXP_LAST_MCS_20_STBC); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) + wlc_phy_txpwr_nphy_po_apply(tx_srom_max_rate, + tmp_stbcpo, + TXP_FIRST_MCS_20_STBC, + TXP_LAST_MCS_20_STBC); + + wlc_phy_txpwr_nphy_srom_convert(tx_srom_max_rate, + &pwr_offsets2[2], tmp_max_pwr, + TXP_FIRST_MCS_20_SDM, + TXP_LAST_MCS_20_SDM); + + if (NPHY_IS_SROM_REINTERPRET) { + + wlc_phy_txpwr_nphy_srom_convert(tx_srom_max_rate, + &pwr_offsets2[4], + tmp_max_pwr, + TXP_FIRST_MCS_40_SISO, + TXP_LAST_MCS_40_SISO); + + wlc_phy_mcs_to_ofdm_powers_nphy(tx_srom_max_rate, + TXP_FIRST_OFDM_40_SISO, + TXP_LAST_OFDM_40_SISO, + TXP_FIRST_MCS_40_SISO); + + wlc_phy_txpwr_nphy_srom_convert(tx_srom_max_rate, + &pwr_offsets2[4], + tmp_max_pwr, + TXP_FIRST_MCS_40_CDD, + TXP_LAST_MCS_40_CDD); + + wlc_phy_txpwr_nphy_po_apply(tx_srom_max_rate, tmp_cddpo, + TXP_FIRST_MCS_40_CDD, + TXP_LAST_MCS_40_CDD); + + wlc_phy_mcs_to_ofdm_powers_nphy(tx_srom_max_rate, + TXP_FIRST_OFDM_40_CDD, + TXP_LAST_OFDM_40_CDD, + TXP_FIRST_MCS_40_CDD); + + wlc_phy_txpwr_nphy_srom_convert(tx_srom_max_rate, + &pwr_offsets2[4], + tmp_max_pwr, + TXP_FIRST_MCS_40_STBC, + TXP_LAST_MCS_40_STBC); + + wlc_phy_txpwr_nphy_po_apply(tx_srom_max_rate, + tmp_stbcpo, + TXP_FIRST_MCS_40_STBC, + TXP_LAST_MCS_40_STBC); + + wlc_phy_txpwr_nphy_srom_convert(tx_srom_max_rate, + &pwr_offsets2[6], + tmp_max_pwr, + TXP_FIRST_MCS_40_SDM, + TXP_LAST_MCS_40_SDM); + } else { + + for (rate1 = TXP_FIRST_OFDM_40_SISO, rate2 = + TXP_FIRST_OFDM; + rate1 <= TXP_LAST_MCS_40_SDM; + rate1++, rate2++) + tx_srom_max_rate[rate1] = + tx_srom_max_rate[rate2]; + } + + if (NREV_GE(pi->pubpi.phy_rev, 3)) + wlc_phy_txpwr_nphy_po_apply(tx_srom_max_rate, + tmp_bw40po, + TXP_FIRST_OFDM_40_SISO, + TXP_LAST_MCS_40_SDM); + + tx_srom_max_rate[TXP_MCS_32] = + tx_srom_max_rate[TXP_FIRST_MCS_40_CDD]; + } + + return; +} + +void wlc_phy_txpower_recalc_target_nphy(struct brcms_phy *pi) +{ + u8 tx_pwr_ctrl_state; + wlc_phy_txpwr_limit_to_tbl_nphy(pi); + wlc_phy_txpwrctrl_pwr_setup_nphy(pi); + + tx_pwr_ctrl_state = pi->nphy_txpwrctrl; + + if (D11REV_IS(pi->sh->corerev, 11) || D11REV_IS(pi->sh->corerev, 12)) { + wlapi_bmac_mctrl(pi->sh->physhim, MCTL_PHYLOCK, MCTL_PHYLOCK); + (void)bcma_read32(pi->d11core, D11REGOFFS(maccontrol)); + udelay(1); + } + + wlc_phy_txpwrctrl_enable_nphy(pi, tx_pwr_ctrl_state); + + if (D11REV_IS(pi->sh->corerev, 11) || D11REV_IS(pi->sh->corerev, 12)) + wlapi_bmac_mctrl(pi->sh->physhim, MCTL_PHYLOCK, 0); +} + +static bool wlc_phy_txpwr_ison_nphy(struct brcms_phy *pi) +{ + return read_phy_reg((pi), 0x1e7) & ((0x1 << 15) | + (0x1 << 14) | (0x1 << 13)); +} + +u16 wlc_phy_txpwr_idx_get_nphy(struct brcms_phy *pi) +{ + u16 tmp; + u16 pwr_idx[2]; + + if (wlc_phy_txpwr_ison_nphy(pi)) { + pwr_idx[0] = wlc_phy_txpwr_idx_cur_get_nphy(pi, PHY_CORE_0); + pwr_idx[1] = wlc_phy_txpwr_idx_cur_get_nphy(pi, PHY_CORE_1); + + tmp = (pwr_idx[0] << 8) | pwr_idx[1]; + } else { + tmp = ((pi->nphy_txpwrindex[PHY_CORE_0].index_internal & 0xff) + << 8) | + (pi->nphy_txpwrindex[PHY_CORE_1].index_internal & 0xff); + } + + return tmp; +} + +void wlc_phy_txpwr_papd_cal_nphy(struct brcms_phy *pi) +{ + if (PHY_IPA(pi) + && (pi->nphy_force_papd_cal + || (wlc_phy_txpwr_ison_nphy(pi) + && + (((u32) + abs(wlc_phy_txpwr_idx_cur_get_nphy(pi, 0) - + pi->nphy_papd_tx_gain_at_last_cal[0]) >= 4) + || ((u32) + abs(wlc_phy_txpwr_idx_cur_get_nphy(pi, 1) - + pi->nphy_papd_tx_gain_at_last_cal[1]) >= 4))))) + wlc_phy_a4(pi, true); +} + +void wlc_phy_txpwrctrl_enable_nphy(struct brcms_phy *pi, u8 ctrl_type) +{ + u16 mask = 0, val = 0, ishw = 0; + u8 ctr; + uint core; + u32 tbl_offset; + u32 tbl_len; + u16 regval[84]; + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + switch (ctrl_type) { + case PHY_TPC_HW_OFF: + case PHY_TPC_HW_ON: + pi->nphy_txpwrctrl = ctrl_type; + break; + default: + break; + } + + if (ctrl_type == PHY_TPC_HW_OFF) { + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + + if (wlc_phy_txpwr_ison_nphy(pi)) { + for (core = 0; core < pi->pubpi.phy_corenum; + core++) + pi->nphy_txpwr_idx[core] = + wlc_phy_txpwr_idx_cur_get_nphy( + pi, + (u8) core); + } + + } + + tbl_len = 84; + tbl_offset = 64; + for (ctr = 0; ctr < tbl_len; ctr++) + regval[ctr] = 0; + wlc_phy_table_write_nphy(pi, 26, tbl_len, tbl_offset, 16, + regval); + wlc_phy_table_write_nphy(pi, 27, tbl_len, tbl_offset, 16, + regval); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) + and_phy_reg(pi, 0x1e7, + (u16) (~((0x1 << 15) | + (0x1 << 14) | (0x1 << 13)))); + else + and_phy_reg(pi, 0x1e7, + (u16) (~((0x1 << 14) | (0x1 << 13)))); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + or_phy_reg(pi, 0x8f, (0x1 << 8)); + or_phy_reg(pi, 0xa5, (0x1 << 8)); + } else { + or_phy_reg(pi, 0xa5, (0x1 << 14)); + } + + if (NREV_IS(pi->pubpi.phy_rev, 2)) + mod_phy_reg(pi, 0xdc, 0x00ff, 0x53); + else if (NREV_LT(pi->pubpi.phy_rev, 2)) + mod_phy_reg(pi, 0xdc, 0x00ff, 0x5a); + + if (NREV_LT(pi->pubpi.phy_rev, 2) && + pi->bw == WL_CHANSPEC_BW_40) + wlapi_bmac_mhf(pi->sh->physhim, MHF1, MHF1_IQSWAP_WAR, + MHF1_IQSWAP_WAR, BRCM_BAND_ALL); + + } else { + + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_CORE1TXPWRCTL, 84, 64, + 8, pi->adj_pwr_tbl_nphy); + wlc_phy_table_write_nphy(pi, NPHY_TBL_ID_CORE2TXPWRCTL, 84, 64, + 8, pi->adj_pwr_tbl_nphy); + + ishw = (ctrl_type == PHY_TPC_HW_ON) ? 0x1 : 0x0; + mask = (0x1 << 14) | (0x1 << 13); + val = (ishw << 14) | (ishw << 13); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + mask |= (0x1 << 15); + val |= (ishw << 15); + } + + mod_phy_reg(pi, 0x1e7, mask, val); + + if (CHSPEC_IS5G(pi->radio_chanspec)) { + if (NREV_GE(pi->pubpi.phy_rev, 7)) { + mod_phy_reg(pi, 0x1e7, (0x7f << 0), 0x32); + mod_phy_reg(pi, 0x222, (0xff << 0), 0x32); + } else { + mod_phy_reg(pi, 0x1e7, (0x7f << 0), 0x64); + if (NREV_GT(pi->pubpi.phy_rev, 1)) + mod_phy_reg(pi, 0x222, + (0xff << 0), 0x64); + } + } + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + if ((pi->nphy_txpwr_idx[0] != 128) + && (pi->nphy_txpwr_idx[1] != 128)) + wlc_phy_txpwr_idx_cur_set_nphy(pi, + pi-> + nphy_txpwr_idx + [0], + pi-> + nphy_txpwr_idx + [1]); + } + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + and_phy_reg(pi, 0x8f, ~(0x1 << 8)); + and_phy_reg(pi, 0xa5, ~(0x1 << 8)); + } else { + and_phy_reg(pi, 0xa5, ~(0x1 << 14)); + } + + if (NREV_IS(pi->pubpi.phy_rev, 2)) + mod_phy_reg(pi, 0xdc, 0x00ff, 0x3b); + else if (NREV_LT(pi->pubpi.phy_rev, 2)) + mod_phy_reg(pi, 0xdc, 0x00ff, 0x40); + + if (NREV_LT(pi->pubpi.phy_rev, 2) && + pi->bw == WL_CHANSPEC_BW_40) + wlapi_bmac_mhf(pi->sh->physhim, MHF1, MHF1_IQSWAP_WAR, + 0x0, BRCM_BAND_ALL); + + if (PHY_IPA(pi)) { + mod_phy_reg(pi, (0 == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 2), (0) << 2); + + mod_phy_reg(pi, (1 == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 2), (0) << 2); + + } + + } + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, false); +} + +void +wlc_phy_txpwr_index_nphy(struct brcms_phy *pi, u8 core_mask, s8 txpwrindex, + bool restore_cals) +{ + u8 core, txpwrctl_tbl; + u16 tx_ind0, iq_ind0, lo_ind0; + u16 m1m2; + u32 txgain; + u16 rad_gain, dac_gain; + u8 bbmult; + u32 iqcomp; + u16 iqcomp_a, iqcomp_b; + u32 locomp; + u16 tmpval; + u8 tx_pwr_ctrl_state; + s32 rfpwr_offset; + u16 regval[2]; + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + + tx_ind0 = 192; + iq_ind0 = 320; + lo_ind0 = 448; + + for (core = 0; core < pi->pubpi.phy_corenum; core++) { + + if ((core_mask & (1 << core)) == 0) + continue; + + txpwrctl_tbl = (core == PHY_CORE_0) ? 26 : 27; + + if (txpwrindex < 0) { + if (pi->nphy_txpwrindex[core].index < 0) + continue; + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + mod_phy_reg(pi, 0x8f, + (0x1 << 8), + pi->nphy_txpwrindex[core]. + AfectrlOverride); + mod_phy_reg(pi, 0xa5, (0x1 << 8), + pi->nphy_txpwrindex[core]. + AfectrlOverride); + } else { + mod_phy_reg(pi, 0xa5, + (0x1 << 14), + pi->nphy_txpwrindex[core]. + AfectrlOverride); + } + + write_phy_reg(pi, (core == PHY_CORE_0) ? + 0xaa : 0xab, + pi->nphy_txpwrindex[core].AfeCtrlDacGain); + + wlc_phy_table_write_nphy(pi, 7, 1, (0x110 + core), 16, + &pi->nphy_txpwrindex[core]. + rad_gain); + + wlc_phy_table_read_nphy(pi, 15, 1, 87, 16, &m1m2); + m1m2 &= ((core == PHY_CORE_0) ? 0x00ff : 0xff00); + m1m2 |= ((core == PHY_CORE_0) ? + (pi->nphy_txpwrindex[core].bbmult << 8) : + (pi->nphy_txpwrindex[core].bbmult << 0)); + wlc_phy_table_write_nphy(pi, 15, 1, 87, 16, &m1m2); + + if (restore_cals) { + wlc_phy_table_write_nphy( + pi, 15, 2, (80 + 2 * core), 16, + &pi->nphy_txpwrindex[core].iqcomp_a); + wlc_phy_table_write_nphy( + pi, 15, 1, (85 + core), 16, + &pi->nphy_txpwrindex[core].locomp); + wlc_phy_table_write_nphy( + pi, 15, 1, (93 + core), 16, + &pi->nphy_txpwrindex[core].locomp); + } + + wlc_phy_txpwrctrl_enable_nphy(pi, pi->nphy_txpwrctrl); + + pi->nphy_txpwrindex[core].index_internal = + pi->nphy_txpwrindex[core].index_internal_save; + } else { + + if (pi->nphy_txpwrindex[core].index < 0) { + + if (NREV_GE(pi->pubpi.phy_rev, 3)) { + mod_phy_reg(pi, 0x8f, + (0x1 << 8), + pi->nphy_txpwrindex[core]. + AfectrlOverride); + mod_phy_reg(pi, 0xa5, (0x1 << 8), + pi->nphy_txpwrindex[core]. + AfectrlOverride); + } else { + pi->nphy_txpwrindex[core]. + AfectrlOverride = + read_phy_reg(pi, 0xa5); + } + + pi->nphy_txpwrindex[core].AfeCtrlDacGain = + read_phy_reg(pi, (core == PHY_CORE_0) ? + 0xaa : 0xab); + + wlc_phy_table_read_nphy(pi, 7, 1, + (0x110 + core), 16, + &pi-> + nphy_txpwrindex[core]. + rad_gain); + + wlc_phy_table_read_nphy(pi, 15, 1, 87, 16, + &tmpval); + tmpval >>= ((core == PHY_CORE_0) ? 8 : 0); + tmpval &= 0xff; + pi->nphy_txpwrindex[core].bbmult = (u8) tmpval; + + wlc_phy_table_read_nphy(pi, 15, 2, + (80 + 2 * core), 16, + &pi-> + nphy_txpwrindex[core]. + iqcomp_a); + + wlc_phy_table_read_nphy(pi, 15, 1, (85 + core), + 16, + &pi-> + nphy_txpwrindex[core]. + locomp); + + pi->nphy_txpwrindex[core].index_internal_save = + pi->nphy_txpwrindex[core]. + index_internal; + } + + tx_pwr_ctrl_state = pi->nphy_txpwrctrl; + wlc_phy_txpwrctrl_enable_nphy(pi, PHY_TPC_HW_OFF); + + if (NREV_IS(pi->pubpi.phy_rev, 1)) + wlapi_bmac_phyclk_fgc(pi->sh->physhim, ON); + + wlc_phy_table_read_nphy(pi, txpwrctl_tbl, 1, + (tx_ind0 + txpwrindex), 32, + &txgain); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) + rad_gain = (txgain >> 16) & + ((1 << (32 - 16 + 1)) - 1); + else + rad_gain = (txgain >> 16) & + ((1 << (28 - 16 + 1)) - 1); + + dac_gain = (txgain >> 8) & ((1 << (13 - 8 + 1)) - 1); + bbmult = (txgain >> 0) & ((1 << (7 - 0 + 1)) - 1); + + if (NREV_GE(pi->pubpi.phy_rev, 3)) + mod_phy_reg(pi, ((core == PHY_CORE_0) ? 0x8f : + 0xa5), (0x1 << 8), (0x1 << 8)); + else + mod_phy_reg(pi, 0xa5, (0x1 << 14), (0x1 << 14)); + + write_phy_reg(pi, (core == PHY_CORE_0) ? + 0xaa : 0xab, dac_gain); + + wlc_phy_table_write_nphy(pi, 7, 1, (0x110 + core), 16, + &rad_gain); + + wlc_phy_table_read_nphy(pi, 15, 1, 87, 16, &m1m2); + m1m2 &= ((core == PHY_CORE_0) ? 0x00ff : 0xff00); + m1m2 |= ((core == PHY_CORE_0) ? + (bbmult << 8) : (bbmult << 0)); + + wlc_phy_table_write_nphy(pi, 15, 1, 87, 16, &m1m2); + + wlc_phy_table_read_nphy(pi, txpwrctl_tbl, 1, + (iq_ind0 + txpwrindex), 32, + &iqcomp); + iqcomp_a = (iqcomp >> 10) & ((1 << (19 - 10 + 1)) - 1); + iqcomp_b = (iqcomp >> 0) & ((1 << (9 - 0 + 1)) - 1); + + if (restore_cals) { + regval[0] = (u16) iqcomp_a; + regval[1] = (u16) iqcomp_b; + wlc_phy_table_write_nphy(pi, 15, 2, + (80 + 2 * core), 16, + regval); + } + + wlc_phy_table_read_nphy(pi, txpwrctl_tbl, 1, + (lo_ind0 + txpwrindex), 32, + &locomp); + if (restore_cals) + wlc_phy_table_write_nphy(pi, 15, 1, (85 + core), + 16, &locomp); + + if (NREV_IS(pi->pubpi.phy_rev, 1)) + wlapi_bmac_phyclk_fgc(pi->sh->physhim, OFF); + + if (PHY_IPA(pi)) { + wlc_phy_table_read_nphy(pi, + (core == PHY_CORE_0 ? + NPHY_TBL_ID_CORE1TXPWRCTL : + NPHY_TBL_ID_CORE2TXPWRCTL), + 1, 576 + txpwrindex, 32, + &rfpwr_offset); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1ff << 4), + ((s16) rfpwr_offset) << 4); + + mod_phy_reg(pi, (core == PHY_CORE_0) ? 0x297 : + 0x29b, (0x1 << 2), (1) << 2); + + } + + wlc_phy_txpwrctrl_enable_nphy(pi, tx_pwr_ctrl_state); + } + + pi->nphy_txpwrindex[core].index = txpwrindex; + } + + if (pi->phyhang_avoid) + wlc_phy_stay_in_carriersearch_nphy(pi, false); +} + +void +wlc_phy_txpower_sromlimit_get_nphy(struct brcms_phy *pi, uint chan, u8 *max_pwr, + u8 txp_rate_idx) +{ + u8 chan_freq_range; + + chan_freq_range = wlc_phy_get_chan_freq_range_nphy(pi, chan); + switch (chan_freq_range) { + case WL_CHAN_FREQ_RANGE_2G: + *max_pwr = pi->tx_srom_max_rate_2g[txp_rate_idx]; + break; + case WL_CHAN_FREQ_RANGE_5GM: + *max_pwr = pi->tx_srom_max_rate_5g_mid[txp_rate_idx]; + break; + case WL_CHAN_FREQ_RANGE_5GL: + *max_pwr = pi->tx_srom_max_rate_5g_low[txp_rate_idx]; + break; + case WL_CHAN_FREQ_RANGE_5GH: + *max_pwr = pi->tx_srom_max_rate_5g_hi[txp_rate_idx]; + break; + default: + *max_pwr = pi->tx_srom_max_rate_2g[txp_rate_idx]; + break; + } + + return; +} + +void wlc_phy_stay_in_carriersearch_nphy(struct brcms_phy *pi, bool enable) +{ + u16 clip_off[] = { 0xffff, 0xffff }; + + if (enable) { + if (pi->nphy_deaf_count == 0) { + pi->classifier_state = + wlc_phy_classifier_nphy(pi, 0, 0); + wlc_phy_classifier_nphy(pi, (0x7 << 0), 4); + wlc_phy_clip_det_nphy(pi, 0, pi->clip_state); + wlc_phy_clip_det_nphy(pi, 1, clip_off); + } + + pi->nphy_deaf_count++; + + wlc_phy_resetcca_nphy(pi); + + } else { + pi->nphy_deaf_count--; + + if (pi->nphy_deaf_count == 0) { + wlc_phy_classifier_nphy(pi, (0x7 << 0), + pi->classifier_state); + wlc_phy_clip_det_nphy(pi, 1, pi->clip_state); + } + } +} + +void wlc_nphy_deaf_mode(struct brcms_phy *pi, bool mode) +{ + wlapi_suspend_mac_and_wait(pi->sh->physhim); + + if (mode) { + if (pi->nphy_deaf_count == 0) + wlc_phy_stay_in_carriersearch_nphy(pi, true); + } else if (pi->nphy_deaf_count > 0) { + wlc_phy_stay_in_carriersearch_nphy(pi, false); + } + + wlapi_enable_mac(pi->sh->physhim); +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_qmath.c b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_qmath.c new file mode 100644 index 0000000..faf1ebe --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_qmath.c @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "phy_qmath.h" + +/* + * Description: This function make 16 bit unsigned multiplication. + * To fit the output into 16 bits the 32 bit multiplication result is right + * shifted by 16 bits. + */ +u16 qm_mulu16(u16 op1, u16 op2) +{ + return (u16) (((u32) op1 * (u32) op2) >> 16); +} + +/* + * Description: This function make 16 bit multiplication and return the result + * in 16 bits. To fit the multiplication result into 16 bits the multiplication + * result is right shifted by 15 bits. Right shifting 15 bits instead of 16 bits + * is done to remove the extra sign bit formed due to the multiplication. + * When both the 16bit inputs are 0x8000 then the output is saturated to + * 0x7fffffff. + */ +s16 qm_muls16(s16 op1, s16 op2) +{ + s32 result; + if (op1 == (s16) 0x8000 && op2 == (s16) 0x8000) + result = 0x7fffffff; + else + result = ((s32) (op1) * (s32) (op2)); + + return (s16) (result >> 15); +} + +/* + * Description: This function add two 32 bit numbers and return the 32bit + * result. If the result overflow 32 bits, the output will be saturated to + * 32bits. + */ +s32 qm_add32(s32 op1, s32 op2) +{ + s32 result; + result = op1 + op2; + if (op1 < 0 && op2 < 0 && result > 0) + result = 0x80000000; + else if (op1 > 0 && op2 > 0 && result < 0) + result = 0x7fffffff; + + return result; +} + +/* + * Description: This function add two 16 bit numbers and return the 16bit + * result. If the result overflow 16 bits, the output will be saturated to + * 16bits. + */ +s16 qm_add16(s16 op1, s16 op2) +{ + s16 result; + s32 temp = (s32) op1 + (s32) op2; + if (temp > (s32) 0x7fff) + result = (s16) 0x7fff; + else if (temp < (s32) 0xffff8000) + result = (s16) 0xffff8000; + else + result = (s16) temp; + + return result; +} + +/* + * Description: This function make 16 bit subtraction and return the 16bit + * result. If the result overflow 16 bits, the output will be saturated to + * 16bits. + */ +s16 qm_sub16(s16 op1, s16 op2) +{ + s16 result; + s32 temp = (s32) op1 - (s32) op2; + if (temp > (s32) 0x7fff) + result = (s16) 0x7fff; + else if (temp < (s32) 0xffff8000) + result = (s16) 0xffff8000; + else + result = (s16) temp; + + return result; +} + +/* + * Description: This function make a 32 bit saturated left shift when the + * specified shift is +ve. This function will make a 32 bit right shift when + * the specified shift is -ve. This function return the result after shifting + * operation. + */ +s32 qm_shl32(s32 op, int shift) +{ + int i; + s32 result; + result = op; + if (shift > 31) + shift = 31; + else if (shift < -31) + shift = -31; + if (shift >= 0) { + for (i = 0; i < shift; i++) + result = qm_add32(result, result); + } else { + result = result >> (-shift); + } + + return result; +} + +/* + * Description: This function make a 16 bit saturated left shift when the + * specified shift is +ve. This function will make a 16 bit right shift when + * the specified shift is -ve. This function return the result after shifting + * operation. + */ +s16 qm_shl16(s16 op, int shift) +{ + int i; + s16 result; + result = op; + if (shift > 15) + shift = 15; + else if (shift < -15) + shift = -15; + if (shift > 0) { + for (i = 0; i < shift; i++) + result = qm_add16(result, result); + } else { + result = result >> (-shift); + } + + return result; +} + +/* + * Description: This function make a 16 bit right shift when shift is +ve. + * This function make a 16 bit saturated left shift when shift is -ve. This + * function return the result of the shift operation. + */ +s16 qm_shr16(s16 op, int shift) +{ + return qm_shl16(op, -shift); +} + +/* + * Description: This function return the number of redundant sign bits in a + * 32 bit number. Example: qm_norm32(0x00000080) = 23 + */ +s16 qm_norm32(s32 op) +{ + u16 u16extraSignBits; + if (op == 0) { + return 31; + } else { + u16extraSignBits = 0; + while ((op >> 31) == (op >> 30)) { + u16extraSignBits++; + op = op << 1; + } + } + return u16extraSignBits; +} + +/* This table is log2(1+(i/32)) where i=[0:1:31], in q.15 format */ +static const s16 log_table[] = { + 0, + 1455, + 2866, + 4236, + 5568, + 6863, + 8124, + 9352, + 10549, + 11716, + 12855, + 13968, + 15055, + 16117, + 17156, + 18173, + 19168, + 20143, + 21098, + 22034, + 22952, + 23852, + 24736, + 25604, + 26455, + 27292, + 28114, + 28922, + 29717, + 30498, + 31267, + 32024 +}; + +#define LOG_TABLE_SIZE 32 /* log_table size */ +#define LOG2_LOG_TABLE_SIZE 5 /* log2(log_table size) */ +#define Q_LOG_TABLE 15 /* qformat of log_table */ +#define LOG10_2 19728 /* log10(2) in q.16 */ + +/* + * Description: + * This routine takes the input number N and its q format qN and compute + * the log10(N). This routine first normalizes the input no N. Then N is in + * mag*(2^x) format. mag is any number in the range 2^30-(2^31 - 1). + * Then log2(mag * 2^x) = log2(mag) + x is computed. From that + * log10(mag * 2^x) = log2(mag * 2^x) * log10(2) is computed. + * This routine looks the log2 value in the table considering + * LOG2_LOG_TABLE_SIZE+1 MSBs. As the MSB is always 1, only next + * LOG2_OF_LOG_TABLE_SIZE MSBs are used for table lookup. Next 16 MSBs are used + * for interpolation. + * Inputs: + * N - number to which log10 has to be found. + * qN - q format of N + * log10N - address where log10(N) will be written. + * qLog10N - address where log10N qformat will be written. + * Note/Problem: + * For accurate results input should be in normalized or near normalized form. + */ +void qm_log10(s32 N, s16 qN, s16 *log10N, s16 *qLog10N) +{ + s16 s16norm, s16tableIndex, s16errorApproximation; + u16 u16offset; + s32 s32log; + + /* normalize the N. */ + s16norm = qm_norm32(N); + N = N << s16norm; + + /* The qformat of N after normalization. + * -30 is added to treat the no as between 1.0 to 2.0 + * i.e. after adding the -30 to the qformat the decimal point will be + * just rigtht of the MSB. (i.e. after sign bit and 1st MSB). i.e. + * at the right side of 30th bit. + */ + qN = qN + s16norm - 30; + + /* take the table index as the LOG2_OF_LOG_TABLE_SIZE bits right of the + * MSB */ + s16tableIndex = (s16) (N >> (32 - (2 + LOG2_LOG_TABLE_SIZE))); + + /* remove the MSB. the MSB is always 1 after normalization. */ + s16tableIndex = + s16tableIndex & (s16) ((1 << LOG2_LOG_TABLE_SIZE) - 1); + + /* remove the (1+LOG2_OF_LOG_TABLE_SIZE) MSBs in the N. */ + N = N & ((1 << (32 - (2 + LOG2_LOG_TABLE_SIZE))) - 1); + + /* take the offset as the 16 MSBS after table index. + */ + u16offset = (u16) (N >> (32 - (2 + LOG2_LOG_TABLE_SIZE + 16))); + + /* look the log value in the table. */ + s32log = log_table[s16tableIndex]; /* q.15 format */ + + /* interpolate using the offset. q.15 format. */ + s16errorApproximation = (s16) qm_mulu16(u16offset, + (u16) (log_table[s16tableIndex + 1] - + log_table[s16tableIndex])); + + /* q.15 format */ + s32log = qm_add16((s16) s32log, s16errorApproximation); + + /* adjust for the qformat of the N as + * log2(mag * 2^x) = log2(mag) + x + */ + s32log = qm_add32(s32log, ((s32) -qN) << 15); /* q.15 format */ + + /* normalize the result. */ + s16norm = qm_norm32(s32log); + + /* bring all the important bits into lower 16 bits */ + /* q.15+s16norm-16 format */ + s32log = qm_shl32(s32log, s16norm - 16); + + /* compute the log10(N) by multiplying log2(N) with log10(2). + * as log10(mag * 2^x) = log2(mag * 2^x) * log10(2) + * log10N in q.15+s16norm-16+1 (LOG10_2 is in q.16) + */ + *log10N = qm_muls16((s16) s32log, (s16) LOG10_2); + + /* write the q format of the result. */ + *qLog10N = 15 + s16norm - 16 + 1; + + return; +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_qmath.h b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_qmath.h new file mode 100644 index 0000000..20e3783 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_qmath.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_QMATH_H_ +#define _BRCM_QMATH_H_ + +#include + +u16 qm_mulu16(u16 op1, u16 op2); + +s16 qm_muls16(s16 op1, s16 op2); + +s32 qm_add32(s32 op1, s32 op2); + +s16 qm_add16(s16 op1, s16 op2); + +s16 qm_sub16(s16 op1, s16 op2); + +s32 qm_shl32(s32 op, int shift); + +s16 qm_shl16(s16 op, int shift); + +s16 qm_shr16(s16 op, int shift); + +s16 qm_norm32(s32 op); + +void qm_log10(s32 N, s16 qN, s16 *log10N, s16 *qLog10N); + +#endif /* #ifndef _BRCM_QMATH_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_radio.h b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_radio.h new file mode 100644 index 0000000..c3a6754 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/phy/phy_radio.h @@ -0,0 +1,1533 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_PHY_RADIO_H_ +#define _BRCM_PHY_RADIO_H_ + +#define RADIO_IDCODE 0x01 + +#define RADIO_DEFAULT_CORE 0 + +#define RXC0_RSSI_RST 0x80 +#define RXC0_MODE_RSSI 0x40 +#define RXC0_MODE_OFF 0x20 +#define RXC0_MODE_CM 0x10 +#define RXC0_LAN_LOAD 0x08 +#define RXC0_OFF_ADJ_MASK 0x07 + +#define TXC0_MODE_TXLPF 0x04 +#define TXC0_PA_TSSI_EN 0x02 +#define TXC0_TSSI_EN 0x01 + +#define TXC1_PA_GAIN_MASK 0x60 +#define TXC1_PA_GAIN_3DB 0x40 +#define TXC1_PA_GAIN_2DB 0x20 +#define TXC1_TX_MIX_GAIN 0x10 +#define TXC1_OFF_I_MASK 0x0c +#define TXC1_OFF_Q_MASK 0x03 + +#define RADIO_2055_READ_OFF 0x100 +#define RADIO_2057_READ_OFF 0x200 + +#define RADIO_2055_GEN_SPARE 0x00 +#define RADIO_2055_SP_PIN_PD 0x02 +#define RADIO_2055_SP_RSSI_CORE1 0x03 +#define RADIO_2055_SP_PD_MISC_CORE1 0x04 +#define RADIO_2055_SP_RSSI_CORE2 0x05 +#define RADIO_2055_SP_PD_MISC_CORE2 0x06 +#define RADIO_2055_SP_RX_GC1_CORE1 0x07 +#define RADIO_2055_SP_RX_GC2_CORE1 0x08 +#define RADIO_2055_SP_RX_GC1_CORE2 0x09 +#define RADIO_2055_SP_RX_GC2_CORE2 0x0a +#define RADIO_2055_SP_LPF_BW_SELECT_CORE1 0x0b +#define RADIO_2055_SP_LPF_BW_SELECT_CORE2 0x0c +#define RADIO_2055_SP_TX_GC1_CORE1 0x0d +#define RADIO_2055_SP_TX_GC2_CORE1 0x0e +#define RADIO_2055_SP_TX_GC1_CORE2 0x0f +#define RADIO_2055_SP_TX_GC2_CORE2 0x10 +#define RADIO_2055_MASTER_CNTRL1 0x11 +#define RADIO_2055_MASTER_CNTRL2 0x12 +#define RADIO_2055_PD_LGEN 0x13 +#define RADIO_2055_PD_PLL_TS 0x14 +#define RADIO_2055_PD_CORE1_LGBUF 0x15 +#define RADIO_2055_PD_CORE1_TX 0x16 +#define RADIO_2055_PD_CORE1_RXTX 0x17 +#define RADIO_2055_PD_CORE1_RSSI_MISC 0x18 +#define RADIO_2055_PD_CORE2_LGBUF 0x19 +#define RADIO_2055_PD_CORE2_TX 0x1a +#define RADIO_2055_PD_CORE2_RXTX 0x1b +#define RADIO_2055_PD_CORE2_RSSI_MISC 0x1c +#define RADIO_2055_PWRDET_LGEN 0x1d +#define RADIO_2055_PWRDET_LGBUF_CORE1 0x1e +#define RADIO_2055_PWRDET_RXTX_CORE1 0x1f +#define RADIO_2055_PWRDET_LGBUF_CORE2 0x20 +#define RADIO_2055_PWRDET_RXTX_CORE2 0x21 +#define RADIO_2055_RRCCAL_CNTRL_SPARE 0x22 +#define RADIO_2055_RRCCAL_N_OPT_SEL 0x23 +#define RADIO_2055_CAL_MISC 0x24 +#define RADIO_2055_CAL_COUNTER_OUT 0x25 +#define RADIO_2055_CAL_COUNTER_OUT2 0x26 +#define RADIO_2055_CAL_CVAR_CNTRL 0x27 +#define RADIO_2055_CAL_RVAR_CNTRL 0x28 +#define RADIO_2055_CAL_LPO_CNTRL 0x29 +#define RADIO_2055_CAL_TS 0x2a +#define RADIO_2055_CAL_RCCAL_READ_TS 0x2b +#define RADIO_2055_CAL_RCAL_READ_TS 0x2c +#define RADIO_2055_PAD_DRIVER 0x2d +#define RADIO_2055_XO_CNTRL1 0x2e +#define RADIO_2055_XO_CNTRL2 0x2f +#define RADIO_2055_XO_REGULATOR 0x30 +#define RADIO_2055_XO_MISC 0x31 +#define RADIO_2055_PLL_LF_C1 0x32 +#define RADIO_2055_PLL_CAL_VTH 0x33 +#define RADIO_2055_PLL_LF_C2 0x34 +#define RADIO_2055_PLL_REF 0x35 +#define RADIO_2055_PLL_LF_R1 0x36 +#define RADIO_2055_PLL_PFD_CP 0x37 +#define RADIO_2055_PLL_IDAC_CPOPAMP 0x38 +#define RADIO_2055_PLL_CP_REGULATOR 0x39 +#define RADIO_2055_PLL_RCAL 0x3a +#define RADIO_2055_RF_PLL_MOD0 0x3b +#define RADIO_2055_RF_PLL_MOD1 0x3c +#define RADIO_2055_RF_MMD_IDAC1 0x3d +#define RADIO_2055_RF_MMD_IDAC0 0x3e +#define RADIO_2055_RF_MMD_SPARE 0x3f +#define RADIO_2055_VCO_CAL1 0x40 +#define RADIO_2055_VCO_CAL2 0x41 +#define RADIO_2055_VCO_CAL3 0x42 +#define RADIO_2055_VCO_CAL4 0x43 +#define RADIO_2055_VCO_CAL5 0x44 +#define RADIO_2055_VCO_CAL6 0x45 +#define RADIO_2055_VCO_CAL7 0x46 +#define RADIO_2055_VCO_CAL8 0x47 +#define RADIO_2055_VCO_CAL9 0x48 +#define RADIO_2055_VCO_CAL10 0x49 +#define RADIO_2055_VCO_CAL11 0x4a +#define RADIO_2055_VCO_CAL12 0x4b +#define RADIO_2055_VCO_CAL13 0x4c +#define RADIO_2055_VCO_CAL14 0x4d +#define RADIO_2055_VCO_CAL15 0x4e +#define RADIO_2055_VCO_CAL16 0x4f +#define RADIO_2055_VCO_KVCO 0x50 +#define RADIO_2055_VCO_CAP_TAIL 0x51 +#define RADIO_2055_VCO_IDAC_VCO 0x52 +#define RADIO_2055_VCO_REGULATOR 0x53 +#define RADIO_2055_PLL_RF_VTH 0x54 +#define RADIO_2055_LGBUF_CEN_BUF 0x55 +#define RADIO_2055_LGEN_TUNE1 0x56 +#define RADIO_2055_LGEN_TUNE2 0x57 +#define RADIO_2055_LGEN_IDAC1 0x58 +#define RADIO_2055_LGEN_IDAC2 0x59 +#define RADIO_2055_LGEN_BIAS_CNT 0x5a +#define RADIO_2055_LGEN_BIAS_IDAC 0x5b +#define RADIO_2055_LGEN_RCAL 0x5c +#define RADIO_2055_LGEN_DIV 0x5d +#define RADIO_2055_LGEN_SPARE2 0x5e +#define RADIO_2055_CORE1_LGBUF_A_TUNE 0x5f +#define RADIO_2055_CORE1_LGBUF_G_TUNE 0x60 +#define RADIO_2055_CORE1_LGBUF_DIV 0x61 +#define RADIO_2055_CORE1_LGBUF_A_IDAC 0x62 +#define RADIO_2055_CORE1_LGBUF_G_IDAC 0x63 +#define RADIO_2055_CORE1_LGBUF_IDACFIL_OVR 0x64 +#define RADIO_2055_CORE1_LGBUF_SPARE 0x65 +#define RADIO_2055_CORE1_RXRF_SPC1 0x66 +#define RADIO_2055_CORE1_RXRF_REG1 0x67 +#define RADIO_2055_CORE1_RXRF_REG2 0x68 +#define RADIO_2055_CORE1_RXRF_RCAL 0x69 +#define RADIO_2055_CORE1_RXBB_BUFI_LPFCMP 0x6a +#define RADIO_2055_CORE1_RXBB_LPF 0x6b +#define RADIO_2055_CORE1_RXBB_MIDAC_HIPAS 0x6c +#define RADIO_2055_CORE1_RXBB_VGA1_IDAC 0x6d +#define RADIO_2055_CORE1_RXBB_VGA2_IDAC 0x6e +#define RADIO_2055_CORE1_RXBB_VGA3_IDAC 0x6f +#define RADIO_2055_CORE1_RXBB_BUFO_CTRL 0x70 +#define RADIO_2055_CORE1_RXBB_RCCAL_CTRL 0x71 +#define RADIO_2055_CORE1_RXBB_RSSI_CTRL1 0x72 +#define RADIO_2055_CORE1_RXBB_RSSI_CTRL2 0x73 +#define RADIO_2055_CORE1_RXBB_RSSI_CTRL3 0x74 +#define RADIO_2055_CORE1_RXBB_RSSI_CTRL4 0x75 +#define RADIO_2055_CORE1_RXBB_RSSI_CTRL5 0x76 +#define RADIO_2055_CORE1_RXBB_REGULATOR 0x77 +#define RADIO_2055_CORE1_RXBB_SPARE1 0x78 +#define RADIO_2055_CORE1_RXTXBB_RCAL 0x79 +#define RADIO_2055_CORE1_TXRF_SGM_PGA 0x7a +#define RADIO_2055_CORE1_TXRF_SGM_PAD 0x7b +#define RADIO_2055_CORE1_TXRF_CNTR_PGA1 0x7c +#define RADIO_2055_CORE1_TXRF_CNTR_PAD1 0x7d +#define RADIO_2055_CORE1_TX_RFPGA_IDAC 0x7e +#define RADIO_2055_CORE1_TX_PGA_PAD_TN 0x7f +#define RADIO_2055_CORE1_TX_PAD_IDAC1 0x80 +#define RADIO_2055_CORE1_TX_PAD_IDAC2 0x81 +#define RADIO_2055_CORE1_TX_MX_BGTRIM 0x82 +#define RADIO_2055_CORE1_TXRF_RCAL 0x83 +#define RADIO_2055_CORE1_TXRF_PAD_TSSI1 0x84 +#define RADIO_2055_CORE1_TXRF_PAD_TSSI2 0x85 +#define RADIO_2055_CORE1_TX_RF_SPARE 0x86 +#define RADIO_2055_CORE1_TXRF_IQCAL1 0x87 +#define RADIO_2055_CORE1_TXRF_IQCAL2 0x88 +#define RADIO_2055_CORE1_TXBB_RCCAL_CTRL 0x89 +#define RADIO_2055_CORE1_TXBB_LPF1 0x8a +#define RADIO_2055_CORE1_TX_VOS_CNCL 0x8b +#define RADIO_2055_CORE1_TX_LPF_MXGM_IDAC 0x8c +#define RADIO_2055_CORE1_TX_BB_MXGM 0x8d +#define RADIO_2055_CORE2_LGBUF_A_TUNE 0x8e +#define RADIO_2055_CORE2_LGBUF_G_TUNE 0x8f +#define RADIO_2055_CORE2_LGBUF_DIV 0x90 +#define RADIO_2055_CORE2_LGBUF_A_IDAC 0x91 +#define RADIO_2055_CORE2_LGBUF_G_IDAC 0x92 +#define RADIO_2055_CORE2_LGBUF_IDACFIL_OVR 0x93 +#define RADIO_2055_CORE2_LGBUF_SPARE 0x94 +#define RADIO_2055_CORE2_RXRF_SPC1 0x95 +#define RADIO_2055_CORE2_RXRF_REG1 0x96 +#define RADIO_2055_CORE2_RXRF_REG2 0x97 +#define RADIO_2055_CORE2_RXRF_RCAL 0x98 +#define RADIO_2055_CORE2_RXBB_BUFI_LPFCMP 0x99 +#define RADIO_2055_CORE2_RXBB_LPF 0x9a +#define RADIO_2055_CORE2_RXBB_MIDAC_HIPAS 0x9b +#define RADIO_2055_CORE2_RXBB_VGA1_IDAC 0x9c +#define RADIO_2055_CORE2_RXBB_VGA2_IDAC 0x9d +#define RADIO_2055_CORE2_RXBB_VGA3_IDAC 0x9e +#define RADIO_2055_CORE2_RXBB_BUFO_CTRL 0x9f +#define RADIO_2055_CORE2_RXBB_RCCAL_CTRL 0xa0 +#define RADIO_2055_CORE2_RXBB_RSSI_CTRL1 0xa1 +#define RADIO_2055_CORE2_RXBB_RSSI_CTRL2 0xa2 +#define RADIO_2055_CORE2_RXBB_RSSI_CTRL3 0xa3 +#define RADIO_2055_CORE2_RXBB_RSSI_CTRL4 0xa4 +#define RADIO_2055_CORE2_RXBB_RSSI_CTRL5 0xa5 +#define RADIO_2055_CORE2_RXBB_REGULATOR 0xa6 +#define RADIO_2055_CORE2_RXBB_SPARE1 0xa7 +#define RADIO_2055_CORE2_RXTXBB_RCAL 0xa8 +#define RADIO_2055_CORE2_TXRF_SGM_PGA 0xa9 +#define RADIO_2055_CORE2_TXRF_SGM_PAD 0xaa +#define RADIO_2055_CORE2_TXRF_CNTR_PGA1 0xab +#define RADIO_2055_CORE2_TXRF_CNTR_PAD1 0xac +#define RADIO_2055_CORE2_TX_RFPGA_IDAC 0xad +#define RADIO_2055_CORE2_TX_PGA_PAD_TN 0xae +#define RADIO_2055_CORE2_TX_PAD_IDAC1 0xaf +#define RADIO_2055_CORE2_TX_PAD_IDAC2 0xb0 +#define RADIO_2055_CORE2_TX_MX_BGTRIM 0xb1 +#define RADIO_2055_CORE2_TXRF_RCAL 0xb2 +#define RADIO_2055_CORE2_TXRF_PAD_TSSI1 0xb3 +#define RADIO_2055_CORE2_TXRF_PAD_TSSI2 0xb4 +#define RADIO_2055_CORE2_TX_RF_SPARE 0xb5 +#define RADIO_2055_CORE2_TXRF_IQCAL1 0xb6 +#define RADIO_2055_CORE2_TXRF_IQCAL2 0xb7 +#define RADIO_2055_CORE2_TXBB_RCCAL_CTRL 0xb8 +#define RADIO_2055_CORE2_TXBB_LPF1 0xb9 +#define RADIO_2055_CORE2_TX_VOS_CNCL 0xba +#define RADIO_2055_CORE2_TX_LPF_MXGM_IDAC 0xbb +#define RADIO_2055_CORE2_TX_BB_MXGM 0xbc +#define RADIO_2055_PRG_GC_HPVGA23_21 0xbd +#define RADIO_2055_PRG_GC_HPVGA23_22 0xbe +#define RADIO_2055_PRG_GC_HPVGA23_23 0xbf +#define RADIO_2055_PRG_GC_HPVGA23_24 0xc0 +#define RADIO_2055_PRG_GC_HPVGA23_25 0xc1 +#define RADIO_2055_PRG_GC_HPVGA23_26 0xc2 +#define RADIO_2055_PRG_GC_HPVGA23_27 0xc3 +#define RADIO_2055_PRG_GC_HPVGA23_28 0xc4 +#define RADIO_2055_PRG_GC_HPVGA23_29 0xc5 +#define RADIO_2055_PRG_GC_HPVGA23_30 0xc6 +#define RADIO_2055_CORE1_LNA_GAINBST 0xcd +#define RADIO_2055_CORE1_B0_NBRSSI_VCM 0xd2 +#define RADIO_2055_CORE1_GEN_SPARE2 0xd6 +#define RADIO_2055_CORE2_LNA_GAINBST 0xd9 +#define RADIO_2055_CORE2_B0_NBRSSI_VCM 0xde +#define RADIO_2055_CORE2_GEN_SPARE2 0xe2 + +#define RADIO_2055_GAINBST_GAIN_DB 6 +#define RADIO_2055_GAINBST_CODE 0x6 + +#define RADIO_2055_JTAGCTRL_MASK 0x04 +#define RADIO_2055_JTAGSYNC_MASK 0x08 +#define RADIO_2055_RRCAL_START 0x40 +#define RADIO_2055_RRCAL_RST_N 0x01 +#define RADIO_2055_CAL_LPO_ENABLE 0x80 +#define RADIO_2055_RCAL_DONE 0x80 +#define RADIO_2055_NBRSSI_VCM_I_MASK 0x03 +#define RADIO_2055_NBRSSI_VCM_I_SHIFT 0x00 +#define RADIO_2055_NBRSSI_VCM_Q_MASK 0x03 +#define RADIO_2055_NBRSSI_VCM_Q_SHIFT 0x00 +#define RADIO_2055_WBRSSI_VCM_IQ_MASK 0x0c +#define RADIO_2055_WBRSSI_VCM_IQ_SHIFT 0x02 +#define RADIO_2055_NBRSSI_PD 0x01 +#define RADIO_2055_WBRSSI_G1_PD 0x04 +#define RADIO_2055_WBRSSI_G2_PD 0x02 +#define RADIO_2055_NBRSSI_SEL 0x01 +#define RADIO_2055_WBRSSI_G1_SEL 0x04 +#define RADIO_2055_WBRSSI_G2_SEL 0x02 +#define RADIO_2055_COUPLE_RX_MASK 0x01 +#define RADIO_2055_COUPLE_TX_MASK 0x02 +#define RADIO_2055_GAINBST_DISABLE 0x02 +#define RADIO_2055_GAINBST_VAL_MASK 0x07 +#define RADIO_2055_RXMX_GC_MASK 0x0c + +#define RADIO_MIMO_CORESEL_OFF 0x0 +#define RADIO_MIMO_CORESEL_CORE1 0x1 +#define RADIO_MIMO_CORESEL_CORE2 0x2 +#define RADIO_MIMO_CORESEL_CORE3 0x3 +#define RADIO_MIMO_CORESEL_CORE4 0x4 +#define RADIO_MIMO_CORESEL_ALLRX 0x5 +#define RADIO_MIMO_CORESEL_ALLTX 0x6 +#define RADIO_MIMO_CORESEL_ALLRXTX 0x7 + +#define RADIO_2064_READ_OFF 0x200 + +#define RADIO_2064_REG000 0x0 +#define RADIO_2064_REG001 0x1 +#define RADIO_2064_REG002 0x2 +#define RADIO_2064_REG003 0x3 +#define RADIO_2064_REG004 0x4 +#define RADIO_2064_REG005 0x5 +#define RADIO_2064_REG006 0x6 +#define RADIO_2064_REG007 0x7 +#define RADIO_2064_REG008 0x8 +#define RADIO_2064_REG009 0x9 +#define RADIO_2064_REG00A 0xa +#define RADIO_2064_REG00B 0xb +#define RADIO_2064_REG00C 0xc +#define RADIO_2064_REG00D 0xd +#define RADIO_2064_REG00E 0xe +#define RADIO_2064_REG00F 0xf +#define RADIO_2064_REG010 0x10 +#define RADIO_2064_REG011 0x11 +#define RADIO_2064_REG012 0x12 +#define RADIO_2064_REG013 0x13 +#define RADIO_2064_REG014 0x14 +#define RADIO_2064_REG015 0x15 +#define RADIO_2064_REG016 0x16 +#define RADIO_2064_REG017 0x17 +#define RADIO_2064_REG018 0x18 +#define RADIO_2064_REG019 0x19 +#define RADIO_2064_REG01A 0x1a +#define RADIO_2064_REG01B 0x1b +#define RADIO_2064_REG01C 0x1c +#define RADIO_2064_REG01D 0x1d +#define RADIO_2064_REG01E 0x1e +#define RADIO_2064_REG01F 0x1f +#define RADIO_2064_REG020 0x20 +#define RADIO_2064_REG021 0x21 +#define RADIO_2064_REG022 0x22 +#define RADIO_2064_REG023 0x23 +#define RADIO_2064_REG024 0x24 +#define RADIO_2064_REG025 0x25 +#define RADIO_2064_REG026 0x26 +#define RADIO_2064_REG027 0x27 +#define RADIO_2064_REG028 0x28 +#define RADIO_2064_REG029 0x29 +#define RADIO_2064_REG02A 0x2a +#define RADIO_2064_REG02B 0x2b +#define RADIO_2064_REG02C 0x2c +#define RADIO_2064_REG02D 0x2d +#define RADIO_2064_REG02E 0x2e +#define RADIO_2064_REG02F 0x2f +#define RADIO_2064_REG030 0x30 +#define RADIO_2064_REG031 0x31 +#define RADIO_2064_REG032 0x32 +#define RADIO_2064_REG033 0x33 +#define RADIO_2064_REG034 0x34 +#define RADIO_2064_REG035 0x35 +#define RADIO_2064_REG036 0x36 +#define RADIO_2064_REG037 0x37 +#define RADIO_2064_REG038 0x38 +#define RADIO_2064_REG039 0x39 +#define RADIO_2064_REG03A 0x3a +#define RADIO_2064_REG03B 0x3b +#define RADIO_2064_REG03C 0x3c +#define RADIO_2064_REG03D 0x3d +#define RADIO_2064_REG03E 0x3e +#define RADIO_2064_REG03F 0x3f +#define RADIO_2064_REG040 0x40 +#define RADIO_2064_REG041 0x41 +#define RADIO_2064_REG042 0x42 +#define RADIO_2064_REG043 0x43 +#define RADIO_2064_REG044 0x44 +#define RADIO_2064_REG045 0x45 +#define RADIO_2064_REG046 0x46 +#define RADIO_2064_REG047 0x47 +#define RADIO_2064_REG048 0x48 +#define RADIO_2064_REG049 0x49 +#define RADIO_2064_REG04A 0x4a +#define RADIO_2064_REG04B 0x4b +#define RADIO_2064_REG04C 0x4c +#define RADIO_2064_REG04D 0x4d +#define RADIO_2064_REG04E 0x4e +#define RADIO_2064_REG04F 0x4f +#define RADIO_2064_REG050 0x50 +#define RADIO_2064_REG051 0x51 +#define RADIO_2064_REG052 0x52 +#define RADIO_2064_REG053 0x53 +#define RADIO_2064_REG054 0x54 +#define RADIO_2064_REG055 0x55 +#define RADIO_2064_REG056 0x56 +#define RADIO_2064_REG057 0x57 +#define RADIO_2064_REG058 0x58 +#define RADIO_2064_REG059 0x59 +#define RADIO_2064_REG05A 0x5a +#define RADIO_2064_REG05B 0x5b +#define RADIO_2064_REG05C 0x5c +#define RADIO_2064_REG05D 0x5d +#define RADIO_2064_REG05E 0x5e +#define RADIO_2064_REG05F 0x5f +#define RADIO_2064_REG060 0x60 +#define RADIO_2064_REG061 0x61 +#define RADIO_2064_REG062 0x62 +#define RADIO_2064_REG063 0x63 +#define RADIO_2064_REG064 0x64 +#define RADIO_2064_REG065 0x65 +#define RADIO_2064_REG066 0x66 +#define RADIO_2064_REG067 0x67 +#define RADIO_2064_REG068 0x68 +#define RADIO_2064_REG069 0x69 +#define RADIO_2064_REG06A 0x6a +#define RADIO_2064_REG06B 0x6b +#define RADIO_2064_REG06C 0x6c +#define RADIO_2064_REG06D 0x6d +#define RADIO_2064_REG06E 0x6e +#define RADIO_2064_REG06F 0x6f +#define RADIO_2064_REG070 0x70 +#define RADIO_2064_REG071 0x71 +#define RADIO_2064_REG072 0x72 +#define RADIO_2064_REG073 0x73 +#define RADIO_2064_REG074 0x74 +#define RADIO_2064_REG075 0x75 +#define RADIO_2064_REG076 0x76 +#define RADIO_2064_REG077 0x77 +#define RADIO_2064_REG078 0x78 +#define RADIO_2064_REG079 0x79 +#define RADIO_2064_REG07A 0x7a +#define RADIO_2064_REG07B 0x7b +#define RADIO_2064_REG07C 0x7c +#define RADIO_2064_REG07D 0x7d +#define RADIO_2064_REG07E 0x7e +#define RADIO_2064_REG07F 0x7f +#define RADIO_2064_REG080 0x80 +#define RADIO_2064_REG081 0x81 +#define RADIO_2064_REG082 0x82 +#define RADIO_2064_REG083 0x83 +#define RADIO_2064_REG084 0x84 +#define RADIO_2064_REG085 0x85 +#define RADIO_2064_REG086 0x86 +#define RADIO_2064_REG087 0x87 +#define RADIO_2064_REG088 0x88 +#define RADIO_2064_REG089 0x89 +#define RADIO_2064_REG08A 0x8a +#define RADIO_2064_REG08B 0x8b +#define RADIO_2064_REG08C 0x8c +#define RADIO_2064_REG08D 0x8d +#define RADIO_2064_REG08E 0x8e +#define RADIO_2064_REG08F 0x8f +#define RADIO_2064_REG090 0x90 +#define RADIO_2064_REG091 0x91 +#define RADIO_2064_REG092 0x92 +#define RADIO_2064_REG093 0x93 +#define RADIO_2064_REG094 0x94 +#define RADIO_2064_REG095 0x95 +#define RADIO_2064_REG096 0x96 +#define RADIO_2064_REG097 0x97 +#define RADIO_2064_REG098 0x98 +#define RADIO_2064_REG099 0x99 +#define RADIO_2064_REG09A 0x9a +#define RADIO_2064_REG09B 0x9b +#define RADIO_2064_REG09C 0x9c +#define RADIO_2064_REG09D 0x9d +#define RADIO_2064_REG09E 0x9e +#define RADIO_2064_REG09F 0x9f +#define RADIO_2064_REG0A0 0xa0 +#define RADIO_2064_REG0A1 0xa1 +#define RADIO_2064_REG0A2 0xa2 +#define RADIO_2064_REG0A3 0xa3 +#define RADIO_2064_REG0A4 0xa4 +#define RADIO_2064_REG0A5 0xa5 +#define RADIO_2064_REG0A6 0xa6 +#define RADIO_2064_REG0A7 0xa7 +#define RADIO_2064_REG0A8 0xa8 +#define RADIO_2064_REG0A9 0xa9 +#define RADIO_2064_REG0AA 0xaa +#define RADIO_2064_REG0AB 0xab +#define RADIO_2064_REG0AC 0xac +#define RADIO_2064_REG0AD 0xad +#define RADIO_2064_REG0AE 0xae +#define RADIO_2064_REG0AF 0xaf +#define RADIO_2064_REG0B0 0xb0 +#define RADIO_2064_REG0B1 0xb1 +#define RADIO_2064_REG0B2 0xb2 +#define RADIO_2064_REG0B3 0xb3 +#define RADIO_2064_REG0B4 0xb4 +#define RADIO_2064_REG0B5 0xb5 +#define RADIO_2064_REG0B6 0xb6 +#define RADIO_2064_REG0B7 0xb7 +#define RADIO_2064_REG0B8 0xb8 +#define RADIO_2064_REG0B9 0xb9 +#define RADIO_2064_REG0BA 0xba +#define RADIO_2064_REG0BB 0xbb +#define RADIO_2064_REG0BC 0xbc +#define RADIO_2064_REG0BD 0xbd +#define RADIO_2064_REG0BE 0xbe +#define RADIO_2064_REG0BF 0xbf +#define RADIO_2064_REG0C0 0xc0 +#define RADIO_2064_REG0C1 0xc1 +#define RADIO_2064_REG0C2 0xc2 +#define RADIO_2064_REG0C3 0xc3 +#define RADIO_2064_REG0C4 0xc4 +#define RADIO_2064_REG0C5 0xc5 +#define RADIO_2064_REG0C6 0xc6 +#define RADIO_2064_REG0C7 0xc7 +#define RADIO_2064_REG0C8 0xc8 +#define RADIO_2064_REG0C9 0xc9 +#define RADIO_2064_REG0CA 0xca +#define RADIO_2064_REG0CB 0xcb +#define RADIO_2064_REG0CC 0xcc +#define RADIO_2064_REG0CD 0xcd +#define RADIO_2064_REG0CE 0xce +#define RADIO_2064_REG0CF 0xcf +#define RADIO_2064_REG0D0 0xd0 +#define RADIO_2064_REG0D1 0xd1 +#define RADIO_2064_REG0D2 0xd2 +#define RADIO_2064_REG0D3 0xd3 +#define RADIO_2064_REG0D4 0xd4 +#define RADIO_2064_REG0D5 0xd5 +#define RADIO_2064_REG0D6 0xd6 +#define RADIO_2064_REG0D7 0xd7 +#define RADIO_2064_REG0D8 0xd8 +#define RADIO_2064_REG0D9 0xd9 +#define RADIO_2064_REG0DA 0xda +#define RADIO_2064_REG0DB 0xdb +#define RADIO_2064_REG0DC 0xdc +#define RADIO_2064_REG0DD 0xdd +#define RADIO_2064_REG0DE 0xde +#define RADIO_2064_REG0DF 0xdf +#define RADIO_2064_REG0E0 0xe0 +#define RADIO_2064_REG0E1 0xe1 +#define RADIO_2064_REG0E2 0xe2 +#define RADIO_2064_REG0E3 0xe3 +#define RADIO_2064_REG0E4 0xe4 +#define RADIO_2064_REG0E5 0xe5 +#define RADIO_2064_REG0E6 0xe6 +#define RADIO_2064_REG0E7 0xe7 +#define RADIO_2064_REG0E8 0xe8 +#define RADIO_2064_REG0E9 0xe9 +#define RADIO_2064_REG0EA 0xea +#define RADIO_2064_REG0EB 0xeb +#define RADIO_2064_REG0EC 0xec +#define RADIO_2064_REG0ED 0xed +#define RADIO_2064_REG0EE 0xee +#define RADIO_2064_REG0EF 0xef +#define RADIO_2064_REG0F0 0xf0 +#define RADIO_2064_REG0F1 0xf1 +#define RADIO_2064_REG0F2 0xf2 +#define RADIO_2064_REG0F3 0xf3 +#define RADIO_2064_REG0F4 0xf4 +#define RADIO_2064_REG0F5 0xf5 +#define RADIO_2064_REG0F6 0xf6 +#define RADIO_2064_REG0F7 0xf7 +#define RADIO_2064_REG0F8 0xf8 +#define RADIO_2064_REG0F9 0xf9 +#define RADIO_2064_REG0FA 0xfa +#define RADIO_2064_REG0FB 0xfb +#define RADIO_2064_REG0FC 0xfc +#define RADIO_2064_REG0FD 0xfd +#define RADIO_2064_REG0FE 0xfe +#define RADIO_2064_REG0FF 0xff +#define RADIO_2064_REG100 0x100 +#define RADIO_2064_REG101 0x101 +#define RADIO_2064_REG102 0x102 +#define RADIO_2064_REG103 0x103 +#define RADIO_2064_REG104 0x104 +#define RADIO_2064_REG105 0x105 +#define RADIO_2064_REG106 0x106 +#define RADIO_2064_REG107 0x107 +#define RADIO_2064_REG108 0x108 +#define RADIO_2064_REG109 0x109 +#define RADIO_2064_REG10A 0x10a +#define RADIO_2064_REG10B 0x10b +#define RADIO_2064_REG10C 0x10c +#define RADIO_2064_REG10D 0x10d +#define RADIO_2064_REG10E 0x10e +#define RADIO_2064_REG10F 0x10f +#define RADIO_2064_REG110 0x110 +#define RADIO_2064_REG111 0x111 +#define RADIO_2064_REG112 0x112 +#define RADIO_2064_REG113 0x113 +#define RADIO_2064_REG114 0x114 +#define RADIO_2064_REG115 0x115 +#define RADIO_2064_REG116 0x116 +#define RADIO_2064_REG117 0x117 +#define RADIO_2064_REG118 0x118 +#define RADIO_2064_REG119 0x119 +#define RADIO_2064_REG11A 0x11a +#define RADIO_2064_REG11B 0x11b +#define RADIO_2064_REG11C 0x11c +#define RADIO_2064_REG11D 0x11d +#define RADIO_2064_REG11E 0x11e +#define RADIO_2064_REG11F 0x11f +#define RADIO_2064_REG120 0x120 +#define RADIO_2064_REG121 0x121 +#define RADIO_2064_REG122 0x122 +#define RADIO_2064_REG123 0x123 +#define RADIO_2064_REG124 0x124 +#define RADIO_2064_REG125 0x125 +#define RADIO_2064_REG126 0x126 +#define RADIO_2064_REG127 0x127 +#define RADIO_2064_REG128 0x128 +#define RADIO_2064_REG129 0x129 +#define RADIO_2064_REG12A 0x12a +#define RADIO_2064_REG12B 0x12b +#define RADIO_2064_REG12C 0x12c +#define RADIO_2064_REG12D 0x12d +#define RADIO_2064_REG12E 0x12e +#define RADIO_2064_REG12F 0x12f +#define RADIO_2064_REG130 0x130 + +#define RADIO_2056_SYN (0x0 << 12) +#define RADIO_2056_TX0 (0x2 << 12) +#define RADIO_2056_TX1 (0x3 << 12) +#define RADIO_2056_RX0 (0x6 << 12) +#define RADIO_2056_RX1 (0x7 << 12) +#define RADIO_2056_ALLTX (0xe << 12) +#define RADIO_2056_ALLRX (0xf << 12) + +#define RADIO_2056_SYN_RESERVED_ADDR0 0x0 +#define RADIO_2056_SYN_IDCODE 0x1 +#define RADIO_2056_SYN_RESERVED_ADDR2 0x2 +#define RADIO_2056_SYN_RESERVED_ADDR3 0x3 +#define RADIO_2056_SYN_RESERVED_ADDR4 0x4 +#define RADIO_2056_SYN_RESERVED_ADDR5 0x5 +#define RADIO_2056_SYN_RESERVED_ADDR6 0x6 +#define RADIO_2056_SYN_RESERVED_ADDR7 0x7 +#define RADIO_2056_SYN_COM_CTRL 0x8 +#define RADIO_2056_SYN_COM_PU 0x9 +#define RADIO_2056_SYN_COM_OVR 0xa +#define RADIO_2056_SYN_COM_RESET 0xb +#define RADIO_2056_SYN_COM_RCAL 0xc +#define RADIO_2056_SYN_COM_RC_RXLPF 0xd +#define RADIO_2056_SYN_COM_RC_TXLPF 0xe +#define RADIO_2056_SYN_COM_RC_RXHPF 0xf +#define RADIO_2056_SYN_RESERVED_ADDR16 0x10 +#define RADIO_2056_SYN_RESERVED_ADDR17 0x11 +#define RADIO_2056_SYN_RESERVED_ADDR18 0x12 +#define RADIO_2056_SYN_RESERVED_ADDR19 0x13 +#define RADIO_2056_SYN_RESERVED_ADDR20 0x14 +#define RADIO_2056_SYN_RESERVED_ADDR21 0x15 +#define RADIO_2056_SYN_RESERVED_ADDR22 0x16 +#define RADIO_2056_SYN_RESERVED_ADDR23 0x17 +#define RADIO_2056_SYN_RESERVED_ADDR24 0x18 +#define RADIO_2056_SYN_RESERVED_ADDR25 0x19 +#define RADIO_2056_SYN_RESERVED_ADDR26 0x1a +#define RADIO_2056_SYN_RESERVED_ADDR27 0x1b +#define RADIO_2056_SYN_RESERVED_ADDR28 0x1c +#define RADIO_2056_SYN_RESERVED_ADDR29 0x1d +#define RADIO_2056_SYN_RESERVED_ADDR30 0x1e +#define RADIO_2056_SYN_RESERVED_ADDR31 0x1f +#define RADIO_2056_SYN_GPIO_MASTER1 0x20 +#define RADIO_2056_SYN_GPIO_MASTER2 0x21 +#define RADIO_2056_SYN_TOPBIAS_MASTER 0x22 +#define RADIO_2056_SYN_TOPBIAS_RCAL 0x23 +#define RADIO_2056_SYN_AFEREG 0x24 +#define RADIO_2056_SYN_TEMPPROCSENSE 0x25 +#define RADIO_2056_SYN_TEMPPROCSENSEIDAC 0x26 +#define RADIO_2056_SYN_TEMPPROCSENSERCAL 0x27 +#define RADIO_2056_SYN_LPO 0x28 +#define RADIO_2056_SYN_VDDCAL_MASTER 0x29 +#define RADIO_2056_SYN_VDDCAL_IDAC 0x2a +#define RADIO_2056_SYN_VDDCAL_STATUS 0x2b +#define RADIO_2056_SYN_RCAL_MASTER 0x2c +#define RADIO_2056_SYN_RCAL_CODE_OUT 0x2d +#define RADIO_2056_SYN_RCCAL_CTRL0 0x2e +#define RADIO_2056_SYN_RCCAL_CTRL1 0x2f +#define RADIO_2056_SYN_RCCAL_CTRL2 0x30 +#define RADIO_2056_SYN_RCCAL_CTRL3 0x31 +#define RADIO_2056_SYN_RCCAL_CTRL4 0x32 +#define RADIO_2056_SYN_RCCAL_CTRL5 0x33 +#define RADIO_2056_SYN_RCCAL_CTRL6 0x34 +#define RADIO_2056_SYN_RCCAL_CTRL7 0x35 +#define RADIO_2056_SYN_RCCAL_CTRL8 0x36 +#define RADIO_2056_SYN_RCCAL_CTRL9 0x37 +#define RADIO_2056_SYN_RCCAL_CTRL10 0x38 +#define RADIO_2056_SYN_RCCAL_CTRL11 0x39 +#define RADIO_2056_SYN_ZCAL_SPARE1 0x3a +#define RADIO_2056_SYN_ZCAL_SPARE2 0x3b +#define RADIO_2056_SYN_PLL_MAST1 0x3c +#define RADIO_2056_SYN_PLL_MAST2 0x3d +#define RADIO_2056_SYN_PLL_MAST3 0x3e +#define RADIO_2056_SYN_PLL_BIAS_RESET 0x3f +#define RADIO_2056_SYN_PLL_XTAL0 0x40 +#define RADIO_2056_SYN_PLL_XTAL1 0x41 +#define RADIO_2056_SYN_PLL_XTAL3 0x42 +#define RADIO_2056_SYN_PLL_XTAL4 0x43 +#define RADIO_2056_SYN_PLL_XTAL5 0x44 +#define RADIO_2056_SYN_PLL_XTAL6 0x45 +#define RADIO_2056_SYN_PLL_REFDIV 0x46 +#define RADIO_2056_SYN_PLL_PFD 0x47 +#define RADIO_2056_SYN_PLL_CP1 0x48 +#define RADIO_2056_SYN_PLL_CP2 0x49 +#define RADIO_2056_SYN_PLL_CP3 0x4a +#define RADIO_2056_SYN_PLL_LOOPFILTER1 0x4b +#define RADIO_2056_SYN_PLL_LOOPFILTER2 0x4c +#define RADIO_2056_SYN_PLL_LOOPFILTER3 0x4d +#define RADIO_2056_SYN_PLL_LOOPFILTER4 0x4e +#define RADIO_2056_SYN_PLL_LOOPFILTER5 0x4f +#define RADIO_2056_SYN_PLL_MMD1 0x50 +#define RADIO_2056_SYN_PLL_MMD2 0x51 +#define RADIO_2056_SYN_PLL_VCO1 0x52 +#define RADIO_2056_SYN_PLL_VCO2 0x53 +#define RADIO_2056_SYN_PLL_MONITOR1 0x54 +#define RADIO_2056_SYN_PLL_MONITOR2 0x55 +#define RADIO_2056_SYN_PLL_VCOCAL1 0x56 +#define RADIO_2056_SYN_PLL_VCOCAL2 0x57 +#define RADIO_2056_SYN_PLL_VCOCAL4 0x58 +#define RADIO_2056_SYN_PLL_VCOCAL5 0x59 +#define RADIO_2056_SYN_PLL_VCOCAL6 0x5a +#define RADIO_2056_SYN_PLL_VCOCAL7 0x5b +#define RADIO_2056_SYN_PLL_VCOCAL8 0x5c +#define RADIO_2056_SYN_PLL_VCOCAL9 0x5d +#define RADIO_2056_SYN_PLL_VCOCAL10 0x5e +#define RADIO_2056_SYN_PLL_VCOCAL11 0x5f +#define RADIO_2056_SYN_PLL_VCOCAL12 0x60 +#define RADIO_2056_SYN_PLL_VCOCAL13 0x61 +#define RADIO_2056_SYN_PLL_VREG 0x62 +#define RADIO_2056_SYN_PLL_STATUS1 0x63 +#define RADIO_2056_SYN_PLL_STATUS2 0x64 +#define RADIO_2056_SYN_PLL_STATUS3 0x65 +#define RADIO_2056_SYN_LOGEN_PU0 0x66 +#define RADIO_2056_SYN_LOGEN_PU1 0x67 +#define RADIO_2056_SYN_LOGEN_PU2 0x68 +#define RADIO_2056_SYN_LOGEN_PU3 0x69 +#define RADIO_2056_SYN_LOGEN_PU5 0x6a +#define RADIO_2056_SYN_LOGEN_PU6 0x6b +#define RADIO_2056_SYN_LOGEN_PU7 0x6c +#define RADIO_2056_SYN_LOGEN_PU8 0x6d +#define RADIO_2056_SYN_LOGEN_BIAS_RESET 0x6e +#define RADIO_2056_SYN_LOGEN_RCCR1 0x6f +#define RADIO_2056_SYN_LOGEN_VCOBUF1 0x70 +#define RADIO_2056_SYN_LOGEN_MIXER1 0x71 +#define RADIO_2056_SYN_LOGEN_MIXER2 0x72 +#define RADIO_2056_SYN_LOGEN_BUF1 0x73 +#define RADIO_2056_SYN_LOGENBUF2 0x74 +#define RADIO_2056_SYN_LOGEN_BUF3 0x75 +#define RADIO_2056_SYN_LOGEN_BUF4 0x76 +#define RADIO_2056_SYN_LOGEN_DIV1 0x77 +#define RADIO_2056_SYN_LOGEN_DIV2 0x78 +#define RADIO_2056_SYN_LOGEN_DIV3 0x79 +#define RADIO_2056_SYN_LOGEN_ACL1 0x7a +#define RADIO_2056_SYN_LOGEN_ACL2 0x7b +#define RADIO_2056_SYN_LOGEN_ACL3 0x7c +#define RADIO_2056_SYN_LOGEN_ACL4 0x7d +#define RADIO_2056_SYN_LOGEN_ACL5 0x7e +#define RADIO_2056_SYN_LOGEN_ACL6 0x7f +#define RADIO_2056_SYN_LOGEN_ACLOUT 0x80 +#define RADIO_2056_SYN_LOGEN_ACLCAL1 0x81 +#define RADIO_2056_SYN_LOGEN_ACLCAL2 0x82 +#define RADIO_2056_SYN_LOGEN_ACLCAL3 0x83 +#define RADIO_2056_SYN_CALEN 0x84 +#define RADIO_2056_SYN_LOGEN_PEAKDET1 0x85 +#define RADIO_2056_SYN_LOGEN_CORE_ACL_OVR 0x86 +#define RADIO_2056_SYN_LOGEN_RX_DIFF_ACL_OVR 0x87 +#define RADIO_2056_SYN_LOGEN_TX_DIFF_ACL_OVR 0x88 +#define RADIO_2056_SYN_LOGEN_RX_CMOS_ACL_OVR 0x89 +#define RADIO_2056_SYN_LOGEN_TX_CMOS_ACL_OVR 0x8a +#define RADIO_2056_SYN_LOGEN_VCOBUF2 0x8b +#define RADIO_2056_SYN_LOGEN_MIXER3 0x8c +#define RADIO_2056_SYN_LOGEN_BUF5 0x8d +#define RADIO_2056_SYN_LOGEN_BUF6 0x8e +#define RADIO_2056_SYN_LOGEN_CBUFRX1 0x8f +#define RADIO_2056_SYN_LOGEN_CBUFRX2 0x90 +#define RADIO_2056_SYN_LOGEN_CBUFRX3 0x91 +#define RADIO_2056_SYN_LOGEN_CBUFRX4 0x92 +#define RADIO_2056_SYN_LOGEN_CBUFTX1 0x93 +#define RADIO_2056_SYN_LOGEN_CBUFTX2 0x94 +#define RADIO_2056_SYN_LOGEN_CBUFTX3 0x95 +#define RADIO_2056_SYN_LOGEN_CBUFTX4 0x96 +#define RADIO_2056_SYN_LOGEN_CMOSRX1 0x97 +#define RADIO_2056_SYN_LOGEN_CMOSRX2 0x98 +#define RADIO_2056_SYN_LOGEN_CMOSRX3 0x99 +#define RADIO_2056_SYN_LOGEN_CMOSRX4 0x9a +#define RADIO_2056_SYN_LOGEN_CMOSTX1 0x9b +#define RADIO_2056_SYN_LOGEN_CMOSTX2 0x9c +#define RADIO_2056_SYN_LOGEN_CMOSTX3 0x9d +#define RADIO_2056_SYN_LOGEN_CMOSTX4 0x9e +#define RADIO_2056_SYN_LOGEN_VCOBUF2_OVRVAL 0x9f +#define RADIO_2056_SYN_LOGEN_MIXER3_OVRVAL 0xa0 +#define RADIO_2056_SYN_LOGEN_BUF5_OVRVAL 0xa1 +#define RADIO_2056_SYN_LOGEN_BUF6_OVRVAL 0xa2 +#define RADIO_2056_SYN_LOGEN_CBUFRX1_OVRVAL 0xa3 +#define RADIO_2056_SYN_LOGEN_CBUFRX2_OVRVAL 0xa4 +#define RADIO_2056_SYN_LOGEN_CBUFRX3_OVRVAL 0xa5 +#define RADIO_2056_SYN_LOGEN_CBUFRX4_OVRVAL 0xa6 +#define RADIO_2056_SYN_LOGEN_CBUFTX1_OVRVAL 0xa7 +#define RADIO_2056_SYN_LOGEN_CBUFTX2_OVRVAL 0xa8 +#define RADIO_2056_SYN_LOGEN_CBUFTX3_OVRVAL 0xa9 +#define RADIO_2056_SYN_LOGEN_CBUFTX4_OVRVAL 0xaa +#define RADIO_2056_SYN_LOGEN_CMOSRX1_OVRVAL 0xab +#define RADIO_2056_SYN_LOGEN_CMOSRX2_OVRVAL 0xac +#define RADIO_2056_SYN_LOGEN_CMOSRX3_OVRVAL 0xad +#define RADIO_2056_SYN_LOGEN_CMOSRX4_OVRVAL 0xae +#define RADIO_2056_SYN_LOGEN_CMOSTX1_OVRVAL 0xaf +#define RADIO_2056_SYN_LOGEN_CMOSTX2_OVRVAL 0xb0 +#define RADIO_2056_SYN_LOGEN_CMOSTX3_OVRVAL 0xb1 +#define RADIO_2056_SYN_LOGEN_CMOSTX4_OVRVAL 0xb2 +#define RADIO_2056_SYN_LOGEN_ACL_WAITCNT 0xb3 +#define RADIO_2056_SYN_LOGEN_CORE_CALVALID 0xb4 +#define RADIO_2056_SYN_LOGEN_RX_CMOS_CALVALID 0xb5 +#define RADIO_2056_SYN_LOGEN_TX_CMOS_VALID 0xb6 + +#define RADIO_2056_TX_RESERVED_ADDR0 0x0 +#define RADIO_2056_TX_IDCODE 0x1 +#define RADIO_2056_TX_RESERVED_ADDR2 0x2 +#define RADIO_2056_TX_RESERVED_ADDR3 0x3 +#define RADIO_2056_TX_RESERVED_ADDR4 0x4 +#define RADIO_2056_TX_RESERVED_ADDR5 0x5 +#define RADIO_2056_TX_RESERVED_ADDR6 0x6 +#define RADIO_2056_TX_RESERVED_ADDR7 0x7 +#define RADIO_2056_TX_COM_CTRL 0x8 +#define RADIO_2056_TX_COM_PU 0x9 +#define RADIO_2056_TX_COM_OVR 0xa +#define RADIO_2056_TX_COM_RESET 0xb +#define RADIO_2056_TX_COM_RCAL 0xc +#define RADIO_2056_TX_COM_RC_RXLPF 0xd +#define RADIO_2056_TX_COM_RC_TXLPF 0xe +#define RADIO_2056_TX_COM_RC_RXHPF 0xf +#define RADIO_2056_TX_RESERVED_ADDR16 0x10 +#define RADIO_2056_TX_RESERVED_ADDR17 0x11 +#define RADIO_2056_TX_RESERVED_ADDR18 0x12 +#define RADIO_2056_TX_RESERVED_ADDR19 0x13 +#define RADIO_2056_TX_RESERVED_ADDR20 0x14 +#define RADIO_2056_TX_RESERVED_ADDR21 0x15 +#define RADIO_2056_TX_RESERVED_ADDR22 0x16 +#define RADIO_2056_TX_RESERVED_ADDR23 0x17 +#define RADIO_2056_TX_RESERVED_ADDR24 0x18 +#define RADIO_2056_TX_RESERVED_ADDR25 0x19 +#define RADIO_2056_TX_RESERVED_ADDR26 0x1a +#define RADIO_2056_TX_RESERVED_ADDR27 0x1b +#define RADIO_2056_TX_RESERVED_ADDR28 0x1c +#define RADIO_2056_TX_RESERVED_ADDR29 0x1d +#define RADIO_2056_TX_RESERVED_ADDR30 0x1e +#define RADIO_2056_TX_RESERVED_ADDR31 0x1f +#define RADIO_2056_TX_IQCAL_GAIN_BW 0x20 +#define RADIO_2056_TX_LOFT_FINE_I 0x21 +#define RADIO_2056_TX_LOFT_FINE_Q 0x22 +#define RADIO_2056_TX_LOFT_COARSE_I 0x23 +#define RADIO_2056_TX_LOFT_COARSE_Q 0x24 +#define RADIO_2056_TX_TX_COM_MASTER1 0x25 +#define RADIO_2056_TX_TX_COM_MASTER2 0x26 +#define RADIO_2056_TX_RXIQCAL_TXMUX 0x27 +#define RADIO_2056_TX_TX_SSI_MASTER 0x28 +#define RADIO_2056_TX_IQCAL_VCM_HG 0x29 +#define RADIO_2056_TX_IQCAL_IDAC 0x2a +#define RADIO_2056_TX_TSSI_VCM 0x2b +#define RADIO_2056_TX_TX_AMP_DET 0x2c +#define RADIO_2056_TX_TX_SSI_MUX 0x2d +#define RADIO_2056_TX_TSSIA 0x2e +#define RADIO_2056_TX_TSSIG 0x2f +#define RADIO_2056_TX_TSSI_MISC1 0x30 +#define RADIO_2056_TX_TSSI_MISC2 0x31 +#define RADIO_2056_TX_TSSI_MISC3 0x32 +#define RADIO_2056_TX_PA_SPARE1 0x33 +#define RADIO_2056_TX_PA_SPARE2 0x34 +#define RADIO_2056_TX_INTPAA_MASTER 0x35 +#define RADIO_2056_TX_INTPAA_GAIN 0x36 +#define RADIO_2056_TX_INTPAA_BOOST_TUNE 0x37 +#define RADIO_2056_TX_INTPAA_IAUX_STAT 0x38 +#define RADIO_2056_TX_INTPAA_IAUX_DYN 0x39 +#define RADIO_2056_TX_INTPAA_IMAIN_STAT 0x3a +#define RADIO_2056_TX_INTPAA_IMAIN_DYN 0x3b +#define RADIO_2056_TX_INTPAA_CASCBIAS 0x3c +#define RADIO_2056_TX_INTPAA_PASLOPE 0x3d +#define RADIO_2056_TX_INTPAA_PA_MISC 0x3e +#define RADIO_2056_TX_INTPAG_MASTER 0x3f +#define RADIO_2056_TX_INTPAG_GAIN 0x40 +#define RADIO_2056_TX_INTPAG_BOOST_TUNE 0x41 +#define RADIO_2056_TX_INTPAG_IAUX_STAT 0x42 +#define RADIO_2056_TX_INTPAG_IAUX_DYN 0x43 +#define RADIO_2056_TX_INTPAG_IMAIN_STAT 0x44 +#define RADIO_2056_TX_INTPAG_IMAIN_DYN 0x45 +#define RADIO_2056_TX_INTPAG_CASCBIAS 0x46 +#define RADIO_2056_TX_INTPAG_PASLOPE 0x47 +#define RADIO_2056_TX_INTPAG_PA_MISC 0x48 +#define RADIO_2056_TX_PADA_MASTER 0x49 +#define RADIO_2056_TX_PADA_IDAC 0x4a +#define RADIO_2056_TX_PADA_CASCBIAS 0x4b +#define RADIO_2056_TX_PADA_GAIN 0x4c +#define RADIO_2056_TX_PADA_BOOST_TUNE 0x4d +#define RADIO_2056_TX_PADA_SLOPE 0x4e +#define RADIO_2056_TX_PADG_MASTER 0x4f +#define RADIO_2056_TX_PADG_IDAC 0x50 +#define RADIO_2056_TX_PADG_CASCBIAS 0x51 +#define RADIO_2056_TX_PADG_GAIN 0x52 +#define RADIO_2056_TX_PADG_BOOST_TUNE 0x53 +#define RADIO_2056_TX_PADG_SLOPE 0x54 +#define RADIO_2056_TX_PGAA_MASTER 0x55 +#define RADIO_2056_TX_PGAA_IDAC 0x56 +#define RADIO_2056_TX_PGAA_GAIN 0x57 +#define RADIO_2056_TX_PGAA_BOOST_TUNE 0x58 +#define RADIO_2056_TX_PGAA_SLOPE 0x59 +#define RADIO_2056_TX_PGAA_MISC 0x5a +#define RADIO_2056_TX_PGAG_MASTER 0x5b +#define RADIO_2056_TX_PGAG_IDAC 0x5c +#define RADIO_2056_TX_PGAG_GAIN 0x5d +#define RADIO_2056_TX_PGAG_BOOST_TUNE 0x5e +#define RADIO_2056_TX_PGAG_SLOPE 0x5f +#define RADIO_2056_TX_PGAG_MISC 0x60 +#define RADIO_2056_TX_MIXA_MASTER 0x61 +#define RADIO_2056_TX_MIXA_BOOST_TUNE 0x62 +#define RADIO_2056_TX_MIXG 0x63 +#define RADIO_2056_TX_MIXG_BOOST_TUNE 0x64 +#define RADIO_2056_TX_BB_GM_MASTER 0x65 +#define RADIO_2056_TX_GMBB_GM 0x66 +#define RADIO_2056_TX_GMBB_IDAC 0x67 +#define RADIO_2056_TX_TXLPF_MASTER 0x68 +#define RADIO_2056_TX_TXLPF_RCCAL 0x69 +#define RADIO_2056_TX_TXLPF_RCCAL_OFF0 0x6a +#define RADIO_2056_TX_TXLPF_RCCAL_OFF1 0x6b +#define RADIO_2056_TX_TXLPF_RCCAL_OFF2 0x6c +#define RADIO_2056_TX_TXLPF_RCCAL_OFF3 0x6d +#define RADIO_2056_TX_TXLPF_RCCAL_OFF4 0x6e +#define RADIO_2056_TX_TXLPF_RCCAL_OFF5 0x6f +#define RADIO_2056_TX_TXLPF_RCCAL_OFF6 0x70 +#define RADIO_2056_TX_TXLPF_BW 0x71 +#define RADIO_2056_TX_TXLPF_GAIN 0x72 +#define RADIO_2056_TX_TXLPF_IDAC 0x73 +#define RADIO_2056_TX_TXLPF_IDAC_0 0x74 +#define RADIO_2056_TX_TXLPF_IDAC_1 0x75 +#define RADIO_2056_TX_TXLPF_IDAC_2 0x76 +#define RADIO_2056_TX_TXLPF_IDAC_3 0x77 +#define RADIO_2056_TX_TXLPF_IDAC_4 0x78 +#define RADIO_2056_TX_TXLPF_IDAC_5 0x79 +#define RADIO_2056_TX_TXLPF_IDAC_6 0x7a +#define RADIO_2056_TX_TXLPF_OPAMP_IDAC 0x7b +#define RADIO_2056_TX_TXLPF_MISC 0x7c +#define RADIO_2056_TX_TXSPARE1 0x7d +#define RADIO_2056_TX_TXSPARE2 0x7e +#define RADIO_2056_TX_TXSPARE3 0x7f +#define RADIO_2056_TX_TXSPARE4 0x80 +#define RADIO_2056_TX_TXSPARE5 0x81 +#define RADIO_2056_TX_TXSPARE6 0x82 +#define RADIO_2056_TX_TXSPARE7 0x83 +#define RADIO_2056_TX_TXSPARE8 0x84 +#define RADIO_2056_TX_TXSPARE9 0x85 +#define RADIO_2056_TX_TXSPARE10 0x86 +#define RADIO_2056_TX_TXSPARE11 0x87 +#define RADIO_2056_TX_TXSPARE12 0x88 +#define RADIO_2056_TX_TXSPARE13 0x89 +#define RADIO_2056_TX_TXSPARE14 0x8a +#define RADIO_2056_TX_TXSPARE15 0x8b +#define RADIO_2056_TX_TXSPARE16 0x8c +#define RADIO_2056_TX_STATUS_INTPA_GAIN 0x8d +#define RADIO_2056_TX_STATUS_PAD_GAIN 0x8e +#define RADIO_2056_TX_STATUS_PGA_GAIN 0x8f +#define RADIO_2056_TX_STATUS_GM_TXLPF_GAIN 0x90 +#define RADIO_2056_TX_STATUS_TXLPF_BW 0x91 +#define RADIO_2056_TX_STATUS_TXLPF_RC 0x92 +#define RADIO_2056_TX_GMBB_IDAC0 0x93 +#define RADIO_2056_TX_GMBB_IDAC1 0x94 +#define RADIO_2056_TX_GMBB_IDAC2 0x95 +#define RADIO_2056_TX_GMBB_IDAC3 0x96 +#define RADIO_2056_TX_GMBB_IDAC4 0x97 +#define RADIO_2056_TX_GMBB_IDAC5 0x98 +#define RADIO_2056_TX_GMBB_IDAC6 0x99 +#define RADIO_2056_TX_GMBB_IDAC7 0x9a + +#define RADIO_2056_RX_RESERVED_ADDR0 0x0 +#define RADIO_2056_RX_IDCODE 0x1 +#define RADIO_2056_RX_RESERVED_ADDR2 0x2 +#define RADIO_2056_RX_RESERVED_ADDR3 0x3 +#define RADIO_2056_RX_RESERVED_ADDR4 0x4 +#define RADIO_2056_RX_RESERVED_ADDR5 0x5 +#define RADIO_2056_RX_RESERVED_ADDR6 0x6 +#define RADIO_2056_RX_RESERVED_ADDR7 0x7 +#define RADIO_2056_RX_COM_CTRL 0x8 +#define RADIO_2056_RX_COM_PU 0x9 +#define RADIO_2056_RX_COM_OVR 0xa +#define RADIO_2056_RX_COM_RESET 0xb +#define RADIO_2056_RX_COM_RCAL 0xc +#define RADIO_2056_RX_COM_RC_RXLPF 0xd +#define RADIO_2056_RX_COM_RC_TXLPF 0xe +#define RADIO_2056_RX_COM_RC_RXHPF 0xf +#define RADIO_2056_RX_RESERVED_ADDR16 0x10 +#define RADIO_2056_RX_RESERVED_ADDR17 0x11 +#define RADIO_2056_RX_RESERVED_ADDR18 0x12 +#define RADIO_2056_RX_RESERVED_ADDR19 0x13 +#define RADIO_2056_RX_RESERVED_ADDR20 0x14 +#define RADIO_2056_RX_RESERVED_ADDR21 0x15 +#define RADIO_2056_RX_RESERVED_ADDR22 0x16 +#define RADIO_2056_RX_RESERVED_ADDR23 0x17 +#define RADIO_2056_RX_RESERVED_ADDR24 0x18 +#define RADIO_2056_RX_RESERVED_ADDR25 0x19 +#define RADIO_2056_RX_RESERVED_ADDR26 0x1a +#define RADIO_2056_RX_RESERVED_ADDR27 0x1b +#define RADIO_2056_RX_RESERVED_ADDR28 0x1c +#define RADIO_2056_RX_RESERVED_ADDR29 0x1d +#define RADIO_2056_RX_RESERVED_ADDR30 0x1e +#define RADIO_2056_RX_RESERVED_ADDR31 0x1f +#define RADIO_2056_RX_RXIQCAL_RXMUX 0x20 +#define RADIO_2056_RX_RSSI_PU 0x21 +#define RADIO_2056_RX_RSSI_SEL 0x22 +#define RADIO_2056_RX_RSSI_GAIN 0x23 +#define RADIO_2056_RX_RSSI_NB_IDAC 0x24 +#define RADIO_2056_RX_RSSI_WB2I_IDAC_1 0x25 +#define RADIO_2056_RX_RSSI_WB2I_IDAC_2 0x26 +#define RADIO_2056_RX_RSSI_WB2Q_IDAC_1 0x27 +#define RADIO_2056_RX_RSSI_WB2Q_IDAC_2 0x28 +#define RADIO_2056_RX_RSSI_POLE 0x29 +#define RADIO_2056_RX_RSSI_WB1_IDAC 0x2a +#define RADIO_2056_RX_RSSI_MISC 0x2b +#define RADIO_2056_RX_LNAA_MASTER 0x2c +#define RADIO_2056_RX_LNAA_TUNE 0x2d +#define RADIO_2056_RX_LNAA_GAIN 0x2e +#define RADIO_2056_RX_LNA_A_SLOPE 0x2f +#define RADIO_2056_RX_BIASPOLE_LNAA1_IDAC 0x30 +#define RADIO_2056_RX_LNAA2_IDAC 0x31 +#define RADIO_2056_RX_LNA1A_MISC 0x32 +#define RADIO_2056_RX_LNAG_MASTER 0x33 +#define RADIO_2056_RX_LNAG_TUNE 0x34 +#define RADIO_2056_RX_LNAG_GAIN 0x35 +#define RADIO_2056_RX_LNA_G_SLOPE 0x36 +#define RADIO_2056_RX_BIASPOLE_LNAG1_IDAC 0x37 +#define RADIO_2056_RX_LNAG2_IDAC 0x38 +#define RADIO_2056_RX_LNA1G_MISC 0x39 +#define RADIO_2056_RX_MIXA_MASTER 0x3a +#define RADIO_2056_RX_MIXA_VCM 0x3b +#define RADIO_2056_RX_MIXA_CTRLPTAT 0x3c +#define RADIO_2056_RX_MIXA_LOB_BIAS 0x3d +#define RADIO_2056_RX_MIXA_CORE_IDAC 0x3e +#define RADIO_2056_RX_MIXA_CMFB_IDAC 0x3f +#define RADIO_2056_RX_MIXA_BIAS_AUX 0x40 +#define RADIO_2056_RX_MIXA_BIAS_MAIN 0x41 +#define RADIO_2056_RX_MIXA_BIAS_MISC 0x42 +#define RADIO_2056_RX_MIXA_MAST_BIAS 0x43 +#define RADIO_2056_RX_MIXG_MASTER 0x44 +#define RADIO_2056_RX_MIXG_VCM 0x45 +#define RADIO_2056_RX_MIXG_CTRLPTAT 0x46 +#define RADIO_2056_RX_MIXG_LOB_BIAS 0x47 +#define RADIO_2056_RX_MIXG_CORE_IDAC 0x48 +#define RADIO_2056_RX_MIXG_CMFB_IDAC 0x49 +#define RADIO_2056_RX_MIXG_BIAS_AUX 0x4a +#define RADIO_2056_RX_MIXG_BIAS_MAIN 0x4b +#define RADIO_2056_RX_MIXG_BIAS_MISC 0x4c +#define RADIO_2056_RX_MIXG_MAST_BIAS 0x4d +#define RADIO_2056_RX_TIA_MASTER 0x4e +#define RADIO_2056_RX_TIA_IOPAMP 0x4f +#define RADIO_2056_RX_TIA_QOPAMP 0x50 +#define RADIO_2056_RX_TIA_IMISC 0x51 +#define RADIO_2056_RX_TIA_QMISC 0x52 +#define RADIO_2056_RX_TIA_GAIN 0x53 +#define RADIO_2056_RX_TIA_SPARE1 0x54 +#define RADIO_2056_RX_TIA_SPARE2 0x55 +#define RADIO_2056_RX_BB_LPF_MASTER 0x56 +#define RADIO_2056_RX_AACI_MASTER 0x57 +#define RADIO_2056_RX_RXLPF_IDAC 0x58 +#define RADIO_2056_RX_RXLPF_OPAMPBIAS_LOWQ 0x59 +#define RADIO_2056_RX_RXLPF_OPAMPBIAS_HIGHQ 0x5a +#define RADIO_2056_RX_RXLPF_BIAS_DCCANCEL 0x5b +#define RADIO_2056_RX_RXLPF_OUTVCM 0x5c +#define RADIO_2056_RX_RXLPF_INVCM_BODY 0x5d +#define RADIO_2056_RX_RXLPF_CC_OP 0x5e +#define RADIO_2056_RX_RXLPF_GAIN 0x5f +#define RADIO_2056_RX_RXLPF_Q_BW 0x60 +#define RADIO_2056_RX_RXLPF_HP_CORNER_BW 0x61 +#define RADIO_2056_RX_RXLPF_RCCAL_HPC 0x62 +#define RADIO_2056_RX_RXHPF_OFF0 0x63 +#define RADIO_2056_RX_RXHPF_OFF1 0x64 +#define RADIO_2056_RX_RXHPF_OFF2 0x65 +#define RADIO_2056_RX_RXHPF_OFF3 0x66 +#define RADIO_2056_RX_RXHPF_OFF4 0x67 +#define RADIO_2056_RX_RXHPF_OFF5 0x68 +#define RADIO_2056_RX_RXHPF_OFF6 0x69 +#define RADIO_2056_RX_RXHPF_OFF7 0x6a +#define RADIO_2056_RX_RXLPF_RCCAL_LPC 0x6b +#define RADIO_2056_RX_RXLPF_OFF_0 0x6c +#define RADIO_2056_RX_RXLPF_OFF_1 0x6d +#define RADIO_2056_RX_RXLPF_OFF_2 0x6e +#define RADIO_2056_RX_RXLPF_OFF_3 0x6f +#define RADIO_2056_RX_RXLPF_OFF_4 0x70 +#define RADIO_2056_RX_UNUSED 0x71 +#define RADIO_2056_RX_VGA_MASTER 0x72 +#define RADIO_2056_RX_VGA_BIAS 0x73 +#define RADIO_2056_RX_VGA_BIAS_DCCANCEL 0x74 +#define RADIO_2056_RX_VGA_GAIN 0x75 +#define RADIO_2056_RX_VGA_HP_CORNER_BW 0x76 +#define RADIO_2056_RX_VGABUF_BIAS 0x77 +#define RADIO_2056_RX_VGABUF_GAIN_BW 0x78 +#define RADIO_2056_RX_TXFBMIX_A 0x79 +#define RADIO_2056_RX_TXFBMIX_G 0x7a +#define RADIO_2056_RX_RXSPARE1 0x7b +#define RADIO_2056_RX_RXSPARE2 0x7c +#define RADIO_2056_RX_RXSPARE3 0x7d +#define RADIO_2056_RX_RXSPARE4 0x7e +#define RADIO_2056_RX_RXSPARE5 0x7f +#define RADIO_2056_RX_RXSPARE6 0x80 +#define RADIO_2056_RX_RXSPARE7 0x81 +#define RADIO_2056_RX_RXSPARE8 0x82 +#define RADIO_2056_RX_RXSPARE9 0x83 +#define RADIO_2056_RX_RXSPARE10 0x84 +#define RADIO_2056_RX_RXSPARE11 0x85 +#define RADIO_2056_RX_RXSPARE12 0x86 +#define RADIO_2056_RX_RXSPARE13 0x87 +#define RADIO_2056_RX_RXSPARE14 0x88 +#define RADIO_2056_RX_RXSPARE15 0x89 +#define RADIO_2056_RX_RXSPARE16 0x8a +#define RADIO_2056_RX_STATUS_LNAA_GAIN 0x8b +#define RADIO_2056_RX_STATUS_LNAG_GAIN 0x8c +#define RADIO_2056_RX_STATUS_MIXTIA_GAIN 0x8d +#define RADIO_2056_RX_STATUS_RXLPF_GAIN 0x8e +#define RADIO_2056_RX_STATUS_VGA_BUF_GAIN 0x8f +#define RADIO_2056_RX_STATUS_RXLPF_Q 0x90 +#define RADIO_2056_RX_STATUS_RXLPF_BUF_BW 0x91 +#define RADIO_2056_RX_STATUS_RXLPF_VGA_HPC 0x92 +#define RADIO_2056_RX_STATUS_RXLPF_RC 0x93 +#define RADIO_2056_RX_STATUS_HPC_RC 0x94 + +#define RADIO_2056_LNA1_A_PU 0x01 +#define RADIO_2056_LNA2_A_PU 0x02 +#define RADIO_2056_LNA1_G_PU 0x01 +#define RADIO_2056_LNA2_G_PU 0x02 +#define RADIO_2056_MIXA_PU_I 0x01 +#define RADIO_2056_MIXA_PU_Q 0x02 +#define RADIO_2056_MIXA_PU_GM 0x10 +#define RADIO_2056_MIXG_PU_I 0x01 +#define RADIO_2056_MIXG_PU_Q 0x02 +#define RADIO_2056_MIXG_PU_GM 0x10 +#define RADIO_2056_TIA_PU 0x01 +#define RADIO_2056_BB_LPF_PU 0x20 +#define RADIO_2056_W1_PU 0x02 +#define RADIO_2056_W2_PU 0x04 +#define RADIO_2056_NB_PU 0x08 +#define RADIO_2056_RSSI_W1_SEL 0x02 +#define RADIO_2056_RSSI_W2_SEL 0x04 +#define RADIO_2056_RSSI_NB_SEL 0x08 +#define RADIO_2056_VCM_MASK 0x1c +#define RADIO_2056_RSSI_VCM_SHIFT 0x02 + +#define RADIO_2057_DACBUF_VINCM_CORE0 0x0 +#define RADIO_2057_IDCODE 0x1 +#define RADIO_2057_RCCAL_MASTER 0x2 +#define RADIO_2057_RCCAL_CAP_SIZE 0x3 +#define RADIO_2057_RCAL_CONFIG 0x4 +#define RADIO_2057_GPAIO_CONFIG 0x5 +#define RADIO_2057_GPAIO_SEL1 0x6 +#define RADIO_2057_GPAIO_SEL0 0x7 +#define RADIO_2057_CLPO_CONFIG 0x8 +#define RADIO_2057_BANDGAP_CONFIG 0x9 +#define RADIO_2057_BANDGAP_RCAL_TRIM 0xa +#define RADIO_2057_AFEREG_CONFIG 0xb +#define RADIO_2057_TEMPSENSE_CONFIG 0xc +#define RADIO_2057_XTAL_CONFIG1 0xd +#define RADIO_2057_XTAL_ICORE_SIZE 0xe +#define RADIO_2057_XTAL_BUF_SIZE 0xf +#define RADIO_2057_XTAL_PULLCAP_SIZE 0x10 +#define RADIO_2057_RFPLL_MASTER 0x11 +#define RADIO_2057_VCOMONITOR_VTH_L 0x12 +#define RADIO_2057_VCOMONITOR_VTH_H 0x13 +#define RADIO_2057_VCOCAL_BIASRESET_RFPLLREG_VOUT 0x14 +#define RADIO_2057_VCO_VARCSIZE_IDAC 0x15 +#define RADIO_2057_VCOCAL_COUNTVAL0 0x16 +#define RADIO_2057_VCOCAL_COUNTVAL1 0x17 +#define RADIO_2057_VCOCAL_INTCLK_COUNT 0x18 +#define RADIO_2057_VCOCAL_MASTER 0x19 +#define RADIO_2057_VCOCAL_NUMCAPCHANGE 0x1a +#define RADIO_2057_VCOCAL_WINSIZE 0x1b +#define RADIO_2057_VCOCAL_DELAY_AFTER_REFRESH 0x1c +#define RADIO_2057_VCOCAL_DELAY_AFTER_CLOSELOOP 0x1d +#define RADIO_2057_VCOCAL_DELAY_AFTER_OPENLOOP 0x1e +#define RADIO_2057_VCOCAL_DELAY_BEFORE_OPENLOOP 0x1f +#define RADIO_2057_VCO_FORCECAPEN_FORCECAP1 0x20 +#define RADIO_2057_VCO_FORCECAP0 0x21 +#define RADIO_2057_RFPLL_REFMASTER_SPAREXTALSIZE 0x22 +#define RADIO_2057_RFPLL_PFD_RESET_PW 0x23 +#define RADIO_2057_RFPLL_LOOPFILTER_R2 0x24 +#define RADIO_2057_RFPLL_LOOPFILTER_R1 0x25 +#define RADIO_2057_RFPLL_LOOPFILTER_C3 0x26 +#define RADIO_2057_RFPLL_LOOPFILTER_C2 0x27 +#define RADIO_2057_RFPLL_LOOPFILTER_C1 0x28 +#define RADIO_2057_CP_KPD_IDAC 0x29 +#define RADIO_2057_RFPLL_IDACS 0x2a +#define RADIO_2057_RFPLL_MISC_EN 0x2b +#define RADIO_2057_RFPLL_MMD0 0x2c +#define RADIO_2057_RFPLL_MMD1 0x2d +#define RADIO_2057_RFPLL_MISC_CAL_RESETN 0x2e +#define RADIO_2057_JTAGXTAL_SIZE_CPBIAS_FILTRES 0x2f +#define RADIO_2057_VCO_ALCREF_BBPLLXTAL_SIZE 0x30 +#define RADIO_2057_VCOCAL_READCAP0 0x31 +#define RADIO_2057_VCOCAL_READCAP1 0x32 +#define RADIO_2057_VCOCAL_STATUS 0x33 +#define RADIO_2057_LOGEN_PUS 0x34 +#define RADIO_2057_LOGEN_PTAT_RESETS 0x35 +#define RADIO_2057_VCOBUF_IDACS 0x36 +#define RADIO_2057_VCOBUF_TUNE 0x37 +#define RADIO_2057_CMOSBUF_TX2GQ_IDACS 0x38 +#define RADIO_2057_CMOSBUF_TX2GI_IDACS 0x39 +#define RADIO_2057_CMOSBUF_TX5GQ_IDACS 0x3a +#define RADIO_2057_CMOSBUF_TX5GI_IDACS 0x3b +#define RADIO_2057_CMOSBUF_RX2GQ_IDACS 0x3c +#define RADIO_2057_CMOSBUF_RX2GI_IDACS 0x3d +#define RADIO_2057_CMOSBUF_RX5GQ_IDACS 0x3e +#define RADIO_2057_CMOSBUF_RX5GI_IDACS 0x3f +#define RADIO_2057_LOGEN_MX2G_IDACS 0x40 +#define RADIO_2057_LOGEN_MX2G_TUNE 0x41 +#define RADIO_2057_LOGEN_MX5G_IDACS 0x42 +#define RADIO_2057_LOGEN_MX5G_TUNE 0x43 +#define RADIO_2057_LOGEN_MX5G_RCCR 0x44 +#define RADIO_2057_LOGEN_INDBUF2G_IDAC 0x45 +#define RADIO_2057_LOGEN_INDBUF2G_IBOOST 0x46 +#define RADIO_2057_LOGEN_INDBUF2G_TUNE 0x47 +#define RADIO_2057_LOGEN_INDBUF5G_IDAC 0x48 +#define RADIO_2057_LOGEN_INDBUF5G_IBOOST 0x49 +#define RADIO_2057_LOGEN_INDBUF5G_TUNE 0x4a +#define RADIO_2057_CMOSBUF_TX_RCCR 0x4b +#define RADIO_2057_CMOSBUF_RX_RCCR 0x4c +#define RADIO_2057_LOGEN_SEL_PKDET 0x4d +#define RADIO_2057_CMOSBUF_SHAREIQ_PTAT 0x4e +#define RADIO_2057_RXTXBIAS_CONFIG_CORE0 0x4f +#define RADIO_2057_TXGM_TXRF_PUS_CORE0 0x50 +#define RADIO_2057_TXGM_IDAC_BLEED_CORE0 0x51 +#define RADIO_2057_TXGM_GAIN_CORE0 0x56 +#define RADIO_2057_TXGM2G_PKDET_PUS_CORE0 0x57 +#define RADIO_2057_PAD2G_PTATS_CORE0 0x58 +#define RADIO_2057_PAD2G_IDACS_CORE0 0x59 +#define RADIO_2057_PAD2G_BOOST_PU_CORE0 0x5a +#define RADIO_2057_PAD2G_CASCV_GAIN_CORE0 0x5b +#define RADIO_2057_TXMIX2G_TUNE_BOOST_PU_CORE0 0x5c +#define RADIO_2057_TXMIX2G_LODC_CORE0 0x5d +#define RADIO_2057_PAD2G_TUNE_PUS_CORE0 0x5e +#define RADIO_2057_IPA2G_GAIN_CORE0 0x5f +#define RADIO_2057_TSSI2G_SPARE1_CORE0 0x60 +#define RADIO_2057_TSSI2G_SPARE2_CORE0 0x61 +#define RADIO_2057_IPA2G_TUNEV_CASCV_PTAT_CORE0 0x62 +#define RADIO_2057_IPA2G_IMAIN_CORE0 0x63 +#define RADIO_2057_IPA2G_CASCONV_CORE0 0x64 +#define RADIO_2057_IPA2G_CASCOFFV_CORE0 0x65 +#define RADIO_2057_IPA2G_BIAS_FILTER_CORE0 0x66 +#define RADIO_2057_TX5G_PKDET_CORE0 0x69 +#define RADIO_2057_PGA_PTAT_TXGM5G_PU_CORE0 0x6a +#define RADIO_2057_PAD5G_PTATS1_CORE0 0x6b +#define RADIO_2057_PAD5G_CLASS_PTATS2_CORE0 0x6c +#define RADIO_2057_PGA_BOOSTPTAT_IMAIN_CORE0 0x6d +#define RADIO_2057_PAD5G_CASCV_IMAIN_CORE0 0x6e +#define RADIO_2057_TXMIX5G_IBOOST_PAD_IAUX_CORE0 0x6f +#define RADIO_2057_PGA_BOOST_TUNE_CORE0 0x70 +#define RADIO_2057_PGA_GAIN_CORE0 0x71 +#define RADIO_2057_PAD5G_CASCOFFV_GAIN_PUS_CORE0 0x72 +#define RADIO_2057_TXMIX5G_BOOST_TUNE_CORE0 0x73 +#define RADIO_2057_PAD5G_TUNE_MISC_PUS_CORE0 0x74 +#define RADIO_2057_IPA5G_IAUX_CORE0 0x75 +#define RADIO_2057_IPA5G_GAIN_CORE0 0x76 +#define RADIO_2057_TSSI5G_SPARE1_CORE0 0x77 +#define RADIO_2057_TSSI5G_SPARE2_CORE0 0x78 +#define RADIO_2057_IPA5G_CASCOFFV_PU_CORE0 0x79 +#define RADIO_2057_IPA5G_PTAT_CORE0 0x7a +#define RADIO_2057_IPA5G_IMAIN_CORE0 0x7b +#define RADIO_2057_IPA5G_CASCONV_CORE0 0x7c +#define RADIO_2057_IPA5G_BIAS_FILTER_CORE0 0x7d +#define RADIO_2057_PAD_BIAS_FILTER_BWS_CORE0 0x80 +#define RADIO_2057_TR2G_CONFIG1_CORE0_NU 0x81 +#define RADIO_2057_TR2G_CONFIG2_CORE0_NU 0x82 +#define RADIO_2057_LNA5G_RFEN_CORE0 0x83 +#define RADIO_2057_TR5G_CONFIG2_CORE0_NU 0x84 +#define RADIO_2057_RXRFBIAS_IBOOST_PU_CORE0 0x85 +#define RADIO_2057_RXRF_IABAND_RXGM_IMAIN_PTAT_CORE0 0x86 +#define RADIO_2057_RXGM_CMFBITAIL_AUXPTAT_CORE0 0x87 +#define RADIO_2057_RXMIX_ICORE_RXGM_IAUX_CORE0 0x88 +#define RADIO_2057_RXMIX_CMFBITAIL_PU_CORE0 0x89 +#define RADIO_2057_LNA2_IMAIN_PTAT_PU_CORE0 0x8a +#define RADIO_2057_LNA2_IAUX_PTAT_CORE0 0x8b +#define RADIO_2057_LNA1_IMAIN_PTAT_PU_CORE0 0x8c +#define RADIO_2057_LNA15G_INPUT_MATCH_TUNE_CORE0 0x8d +#define RADIO_2057_RXRFBIAS_BANDSEL_CORE0 0x8e +#define RADIO_2057_TIA_CONFIG_CORE0 0x8f +#define RADIO_2057_TIA_IQGAIN_CORE0 0x90 +#define RADIO_2057_TIA_IBIAS2_CORE0 0x91 +#define RADIO_2057_TIA_IBIAS1_CORE0 0x92 +#define RADIO_2057_TIA_SPARE_Q_CORE0 0x93 +#define RADIO_2057_TIA_SPARE_I_CORE0 0x94 +#define RADIO_2057_RXMIX2G_PUS_CORE0 0x95 +#define RADIO_2057_RXMIX2G_VCMREFS_CORE0 0x96 +#define RADIO_2057_RXMIX2G_LODC_QI_CORE0 0x97 +#define RADIO_2057_W12G_BW_LNA2G_PUS_CORE0 0x98 +#define RADIO_2057_LNA2G_GAIN_CORE0 0x99 +#define RADIO_2057_LNA2G_TUNE_CORE0 0x9a +#define RADIO_2057_RXMIX5G_PUS_CORE0 0x9b +#define RADIO_2057_RXMIX5G_VCMREFS_CORE0 0x9c +#define RADIO_2057_RXMIX5G_LODC_QI_CORE0 0x9d +#define RADIO_2057_W15G_BW_LNA5G_PUS_CORE0 0x9e +#define RADIO_2057_LNA5G_GAIN_CORE0 0x9f +#define RADIO_2057_LNA5G_TUNE_CORE0 0xa0 +#define RADIO_2057_LPFSEL_TXRX_RXBB_PUS_CORE0 0xa1 +#define RADIO_2057_RXBB_BIAS_MASTER_CORE0 0xa2 +#define RADIO_2057_RXBB_VGABUF_IDACS_CORE0 0xa3 +#define RADIO_2057_LPF_VCMREF_TXBUF_VCMREF_CORE0 0xa4 +#define RADIO_2057_TXBUF_VINCM_CORE0 0xa5 +#define RADIO_2057_TXBUF_IDACS_CORE0 0xa6 +#define RADIO_2057_LPF_RESP_RXBUF_BW_CORE0 0xa7 +#define RADIO_2057_RXBB_CC_CORE0 0xa8 +#define RADIO_2057_RXBB_SPARE3_CORE0 0xa9 +#define RADIO_2057_RXBB_RCCAL_HPC_CORE0 0xaa +#define RADIO_2057_LPF_IDACS_CORE0 0xab +#define RADIO_2057_LPFBYP_DCLOOP_BYP_IDAC_CORE0 0xac +#define RADIO_2057_TXBUF_GAIN_CORE0 0xad +#define RADIO_2057_AFELOOPBACK_AACI_RESP_CORE0 0xae +#define RADIO_2057_RXBUF_DEGEN_CORE0 0xaf +#define RADIO_2057_RXBB_SPARE2_CORE0 0xb0 +#define RADIO_2057_RXBB_SPARE1_CORE0 0xb1 +#define RADIO_2057_RSSI_MASTER_CORE0 0xb2 +#define RADIO_2057_W2_MASTER_CORE0 0xb3 +#define RADIO_2057_NB_MASTER_CORE0 0xb4 +#define RADIO_2057_W2_IDACS0_Q_CORE0 0xb5 +#define RADIO_2057_W2_IDACS1_Q_CORE0 0xb6 +#define RADIO_2057_W2_IDACS0_I_CORE0 0xb7 +#define RADIO_2057_W2_IDACS1_I_CORE0 0xb8 +#define RADIO_2057_RSSI_GPAIOSEL_W1_IDACS_CORE0 0xb9 +#define RADIO_2057_NB_IDACS_Q_CORE0 0xba +#define RADIO_2057_NB_IDACS_I_CORE0 0xbb +#define RADIO_2057_BACKUP4_CORE0 0xc1 +#define RADIO_2057_BACKUP3_CORE0 0xc2 +#define RADIO_2057_BACKUP2_CORE0 0xc3 +#define RADIO_2057_BACKUP1_CORE0 0xc4 +#define RADIO_2057_SPARE16_CORE0 0xc5 +#define RADIO_2057_SPARE15_CORE0 0xc6 +#define RADIO_2057_SPARE14_CORE0 0xc7 +#define RADIO_2057_SPARE13_CORE0 0xc8 +#define RADIO_2057_SPARE12_CORE0 0xc9 +#define RADIO_2057_SPARE11_CORE0 0xca +#define RADIO_2057_TX2G_BIAS_RESETS_CORE0 0xcb +#define RADIO_2057_TX5G_BIAS_RESETS_CORE0 0xcc +#define RADIO_2057_IQTEST_SEL_PU 0xcd +#define RADIO_2057_XTAL_CONFIG2 0xce +#define RADIO_2057_BUFS_MISC_LPFBW_CORE0 0xcf +#define RADIO_2057_TXLPF_RCCAL_CORE0 0xd0 +#define RADIO_2057_RXBB_GPAIOSEL_RXLPF_RCCAL_CORE0 0xd1 +#define RADIO_2057_LPF_GAIN_CORE0 0xd2 +#define RADIO_2057_DACBUF_IDACS_BW_CORE0 0xd3 +#define RADIO_2057_RXTXBIAS_CONFIG_CORE1 0xd4 +#define RADIO_2057_TXGM_TXRF_PUS_CORE1 0xd5 +#define RADIO_2057_TXGM_IDAC_BLEED_CORE1 0xd6 +#define RADIO_2057_TXGM_GAIN_CORE1 0xdb +#define RADIO_2057_TXGM2G_PKDET_PUS_CORE1 0xdc +#define RADIO_2057_PAD2G_PTATS_CORE1 0xdd +#define RADIO_2057_PAD2G_IDACS_CORE1 0xde +#define RADIO_2057_PAD2G_BOOST_PU_CORE1 0xdf +#define RADIO_2057_PAD2G_CASCV_GAIN_CORE1 0xe0 +#define RADIO_2057_TXMIX2G_TUNE_BOOST_PU_CORE1 0xe1 +#define RADIO_2057_TXMIX2G_LODC_CORE1 0xe2 +#define RADIO_2057_PAD2G_TUNE_PUS_CORE1 0xe3 +#define RADIO_2057_IPA2G_GAIN_CORE1 0xe4 +#define RADIO_2057_TSSI2G_SPARE1_CORE1 0xe5 +#define RADIO_2057_TSSI2G_SPARE2_CORE1 0xe6 +#define RADIO_2057_IPA2G_TUNEV_CASCV_PTAT_CORE1 0xe7 +#define RADIO_2057_IPA2G_IMAIN_CORE1 0xe8 +#define RADIO_2057_IPA2G_CASCONV_CORE1 0xe9 +#define RADIO_2057_IPA2G_CASCOFFV_CORE1 0xea +#define RADIO_2057_IPA2G_BIAS_FILTER_CORE1 0xeb +#define RADIO_2057_TX5G_PKDET_CORE1 0xee +#define RADIO_2057_PGA_PTAT_TXGM5G_PU_CORE1 0xef +#define RADIO_2057_PAD5G_PTATS1_CORE1 0xf0 +#define RADIO_2057_PAD5G_CLASS_PTATS2_CORE1 0xf1 +#define RADIO_2057_PGA_BOOSTPTAT_IMAIN_CORE1 0xf2 +#define RADIO_2057_PAD5G_CASCV_IMAIN_CORE1 0xf3 +#define RADIO_2057_TXMIX5G_IBOOST_PAD_IAUX_CORE1 0xf4 +#define RADIO_2057_PGA_BOOST_TUNE_CORE1 0xf5 +#define RADIO_2057_PGA_GAIN_CORE1 0xf6 +#define RADIO_2057_PAD5G_CASCOFFV_GAIN_PUS_CORE1 0xf7 +#define RADIO_2057_TXMIX5G_BOOST_TUNE_CORE1 0xf8 +#define RADIO_2057_PAD5G_TUNE_MISC_PUS_CORE1 0xf9 +#define RADIO_2057_IPA5G_IAUX_CORE1 0xfa +#define RADIO_2057_IPA5G_GAIN_CORE1 0xfb +#define RADIO_2057_TSSI5G_SPARE1_CORE1 0xfc +#define RADIO_2057_TSSI5G_SPARE2_CORE1 0xfd +#define RADIO_2057_IPA5G_CASCOFFV_PU_CORE1 0xfe +#define RADIO_2057_IPA5G_PTAT_CORE1 0xff +#define RADIO_2057_IPA5G_IMAIN_CORE1 0x100 +#define RADIO_2057_IPA5G_CASCONV_CORE1 0x101 +#define RADIO_2057_IPA5G_BIAS_FILTER_CORE1 0x102 +#define RADIO_2057_PAD_BIAS_FILTER_BWS_CORE1 0x105 +#define RADIO_2057_TR2G_CONFIG1_CORE1_NU 0x106 +#define RADIO_2057_TR2G_CONFIG2_CORE1_NU 0x107 +#define RADIO_2057_LNA5G_RFEN_CORE1 0x108 +#define RADIO_2057_TR5G_CONFIG2_CORE1_NU 0x109 +#define RADIO_2057_RXRFBIAS_IBOOST_PU_CORE1 0x10a +#define RADIO_2057_RXRF_IABAND_RXGM_IMAIN_PTAT_CORE1 0x10b +#define RADIO_2057_RXGM_CMFBITAIL_AUXPTAT_CORE1 0x10c +#define RADIO_2057_RXMIX_ICORE_RXGM_IAUX_CORE1 0x10d +#define RADIO_2057_RXMIX_CMFBITAIL_PU_CORE1 0x10e +#define RADIO_2057_LNA2_IMAIN_PTAT_PU_CORE1 0x10f +#define RADIO_2057_LNA2_IAUX_PTAT_CORE1 0x110 +#define RADIO_2057_LNA1_IMAIN_PTAT_PU_CORE1 0x111 +#define RADIO_2057_LNA15G_INPUT_MATCH_TUNE_CORE1 0x112 +#define RADIO_2057_RXRFBIAS_BANDSEL_CORE1 0x113 +#define RADIO_2057_TIA_CONFIG_CORE1 0x114 +#define RADIO_2057_TIA_IQGAIN_CORE1 0x115 +#define RADIO_2057_TIA_IBIAS2_CORE1 0x116 +#define RADIO_2057_TIA_IBIAS1_CORE1 0x117 +#define RADIO_2057_TIA_SPARE_Q_CORE1 0x118 +#define RADIO_2057_TIA_SPARE_I_CORE1 0x119 +#define RADIO_2057_RXMIX2G_PUS_CORE1 0x11a +#define RADIO_2057_RXMIX2G_VCMREFS_CORE1 0x11b +#define RADIO_2057_RXMIX2G_LODC_QI_CORE1 0x11c +#define RADIO_2057_W12G_BW_LNA2G_PUS_CORE1 0x11d +#define RADIO_2057_LNA2G_GAIN_CORE1 0x11e +#define RADIO_2057_LNA2G_TUNE_CORE1 0x11f +#define RADIO_2057_RXMIX5G_PUS_CORE1 0x120 +#define RADIO_2057_RXMIX5G_VCMREFS_CORE1 0x121 +#define RADIO_2057_RXMIX5G_LODC_QI_CORE1 0x122 +#define RADIO_2057_W15G_BW_LNA5G_PUS_CORE1 0x123 +#define RADIO_2057_LNA5G_GAIN_CORE1 0x124 +#define RADIO_2057_LNA5G_TUNE_CORE1 0x125 +#define RADIO_2057_LPFSEL_TXRX_RXBB_PUS_CORE1 0x126 +#define RADIO_2057_RXBB_BIAS_MASTER_CORE1 0x127 +#define RADIO_2057_RXBB_VGABUF_IDACS_CORE1 0x128 +#define RADIO_2057_LPF_VCMREF_TXBUF_VCMREF_CORE1 0x129 +#define RADIO_2057_TXBUF_VINCM_CORE1 0x12a +#define RADIO_2057_TXBUF_IDACS_CORE1 0x12b +#define RADIO_2057_LPF_RESP_RXBUF_BW_CORE1 0x12c +#define RADIO_2057_RXBB_CC_CORE1 0x12d +#define RADIO_2057_RXBB_SPARE3_CORE1 0x12e +#define RADIO_2057_RXBB_RCCAL_HPC_CORE1 0x12f +#define RADIO_2057_LPF_IDACS_CORE1 0x130 +#define RADIO_2057_LPFBYP_DCLOOP_BYP_IDAC_CORE1 0x131 +#define RADIO_2057_TXBUF_GAIN_CORE1 0x132 +#define RADIO_2057_AFELOOPBACK_AACI_RESP_CORE1 0x133 +#define RADIO_2057_RXBUF_DEGEN_CORE1 0x134 +#define RADIO_2057_RXBB_SPARE2_CORE1 0x135 +#define RADIO_2057_RXBB_SPARE1_CORE1 0x136 +#define RADIO_2057_RSSI_MASTER_CORE1 0x137 +#define RADIO_2057_W2_MASTER_CORE1 0x138 +#define RADIO_2057_NB_MASTER_CORE1 0x139 +#define RADIO_2057_W2_IDACS0_Q_CORE1 0x13a +#define RADIO_2057_W2_IDACS1_Q_CORE1 0x13b +#define RADIO_2057_W2_IDACS0_I_CORE1 0x13c +#define RADIO_2057_W2_IDACS1_I_CORE1 0x13d +#define RADIO_2057_RSSI_GPAIOSEL_W1_IDACS_CORE1 0x13e +#define RADIO_2057_NB_IDACS_Q_CORE1 0x13f +#define RADIO_2057_NB_IDACS_I_CORE1 0x140 +#define RADIO_2057_BACKUP4_CORE1 0x146 +#define RADIO_2057_BACKUP3_CORE1 0x147 +#define RADIO_2057_BACKUP2_CORE1 0x148 +#define RADIO_2057_BACKUP1_CORE1 0x149 +#define RADIO_2057_SPARE16_CORE1 0x14a +#define RADIO_2057_SPARE15_CORE1 0x14b +#define RADIO_2057_SPARE14_CORE1 0x14c +#define RADIO_2057_SPARE13_CORE1 0x14d +#define RADIO_2057_SPARE12_CORE1 0x14e +#define RADIO_2057_SPARE11_CORE1 0x14f +#define RADIO_2057_TX2G_BIAS_RESETS_CORE1 0x150 +#define RADIO_2057_TX5G_BIAS_RESETS_CORE1 0x151 +#define RADIO_2057_SPARE8_CORE1 0x152 +#define RADIO_2057_SPARE7_CORE1 0x153 +#define RADIO_2057_BUFS_MISC_LPFBW_CORE1 0x154 +#define RADIO_2057_TXLPF_RCCAL_CORE1 0x155 +#define RADIO_2057_RXBB_GPAIOSEL_RXLPF_RCCAL_CORE1 0x156 +#define RADIO_2057_LPF_GAIN_CORE1 0x157 +#define RADIO_2057_DACBUF_IDACS_BW_CORE1 0x158 +#define RADIO_2057_DACBUF_VINCM_CORE1 0x159 +#define RADIO_2057_RCCAL_START_R1_Q1_P1 0x15a +#define RADIO_2057_RCCAL_X1 0x15b +#define RADIO_2057_RCCAL_TRC0 0x15c +#define RADIO_2057_RCCAL_TRC1 0x15d +#define RADIO_2057_RCCAL_DONE_OSCCAP 0x15e +#define RADIO_2057_RCCAL_N0_0 0x15f +#define RADIO_2057_RCCAL_N0_1 0x160 +#define RADIO_2057_RCCAL_N1_0 0x161 +#define RADIO_2057_RCCAL_N1_1 0x162 +#define RADIO_2057_RCAL_STATUS 0x163 +#define RADIO_2057_XTALPUOVR_PINCTRL 0x164 +#define RADIO_2057_OVR_REG0 0x165 +#define RADIO_2057_OVR_REG1 0x166 +#define RADIO_2057_OVR_REG2 0x167 +#define RADIO_2057_OVR_REG3 0x168 +#define RADIO_2057_OVR_REG4 0x169 +#define RADIO_2057_RCCAL_SCAP_VAL 0x16a +#define RADIO_2057_RCCAL_BCAP_VAL 0x16b +#define RADIO_2057_RCCAL_HPC_VAL 0x16c +#define RADIO_2057_RCCAL_OVERRIDES 0x16d +#define RADIO_2057_TX0_IQCAL_GAIN_BW 0x170 +#define RADIO_2057_TX0_LOFT_FINE_I 0x171 +#define RADIO_2057_TX0_LOFT_FINE_Q 0x172 +#define RADIO_2057_TX0_LOFT_COARSE_I 0x173 +#define RADIO_2057_TX0_LOFT_COARSE_Q 0x174 +#define RADIO_2057_TX0_TX_SSI_MASTER 0x175 +#define RADIO_2057_TX0_IQCAL_VCM_HG 0x176 +#define RADIO_2057_TX0_IQCAL_IDAC 0x177 +#define RADIO_2057_TX0_TSSI_VCM 0x178 +#define RADIO_2057_TX0_TX_SSI_MUX 0x179 +#define RADIO_2057_TX0_TSSIA 0x17a +#define RADIO_2057_TX0_TSSIG 0x17b +#define RADIO_2057_TX0_TSSI_MISC1 0x17c +#define RADIO_2057_TX0_TXRXCOUPLE_2G_ATTEN 0x17d +#define RADIO_2057_TX0_TXRXCOUPLE_2G_PWRUP 0x17e +#define RADIO_2057_TX0_TXRXCOUPLE_5G_ATTEN 0x17f +#define RADIO_2057_TX0_TXRXCOUPLE_5G_PWRUP 0x180 +#define RADIO_2057_TX1_IQCAL_GAIN_BW 0x190 +#define RADIO_2057_TX1_LOFT_FINE_I 0x191 +#define RADIO_2057_TX1_LOFT_FINE_Q 0x192 +#define RADIO_2057_TX1_LOFT_COARSE_I 0x193 +#define RADIO_2057_TX1_LOFT_COARSE_Q 0x194 +#define RADIO_2057_TX1_TX_SSI_MASTER 0x195 +#define RADIO_2057_TX1_IQCAL_VCM_HG 0x196 +#define RADIO_2057_TX1_IQCAL_IDAC 0x197 +#define RADIO_2057_TX1_TSSI_VCM 0x198 +#define RADIO_2057_TX1_TX_SSI_MUX 0x199 +#define RADIO_2057_TX1_TSSIA 0x19a +#define RADIO_2057_TX1_TSSIG 0x19b +#define RADIO_2057_TX1_TSSI_MISC1 0x19c +#define RADIO_2057_TX1_TXRXCOUPLE_2G_ATTEN 0x19d +#define RADIO_2057_TX1_TXRXCOUPLE_2G_PWRUP 0x19e +#define RADIO_2057_TX1_TXRXCOUPLE_5G_ATTEN 0x19f +#define RADIO_2057_TX1_TXRXCOUPLE_5G_PWRUP 0x1a0 +#define RADIO_2057_AFE_VCM_CAL_MASTER_CORE0 0x1a1 +#define RADIO_2057_AFE_SET_VCM_I_CORE0 0x1a2 +#define RADIO_2057_AFE_SET_VCM_Q_CORE0 0x1a3 +#define RADIO_2057_AFE_STATUS_VCM_IQADC_CORE0 0x1a4 +#define RADIO_2057_AFE_STATUS_VCM_I_CORE0 0x1a5 +#define RADIO_2057_AFE_STATUS_VCM_Q_CORE0 0x1a6 +#define RADIO_2057_AFE_VCM_CAL_MASTER_CORE1 0x1a7 +#define RADIO_2057_AFE_SET_VCM_I_CORE1 0x1a8 +#define RADIO_2057_AFE_SET_VCM_Q_CORE1 0x1a9 +#define RADIO_2057_AFE_STATUS_VCM_IQADC_CORE1 0x1aa +#define RADIO_2057_AFE_STATUS_VCM_I_CORE1 0x1ab +#define RADIO_2057_AFE_STATUS_VCM_Q_CORE1 0x1ac + +#define RADIO_2057v7_DACBUF_VINCM_CORE0 0x1ad +#define RADIO_2057v7_RCCAL_MASTER 0x1ae +#define RADIO_2057v7_TR2G_CONFIG3_CORE0_NU 0x1af +#define RADIO_2057v7_TR2G_CONFIG3_CORE1_NU 0x1b0 +#define RADIO_2057v7_LOGEN_PUS1 0x1b1 +#define RADIO_2057v7_OVR_REG5 0x1b2 +#define RADIO_2057v7_OVR_REG6 0x1b3 +#define RADIO_2057v7_OVR_REG7 0x1b4 +#define RADIO_2057v7_OVR_REG8 0x1b5 +#define RADIO_2057v7_OVR_REG9 0x1b6 +#define RADIO_2057v7_OVR_REG10 0x1b7 +#define RADIO_2057v7_OVR_REG11 0x1b8 +#define RADIO_2057v7_OVR_REG12 0x1b9 +#define RADIO_2057v7_OVR_REG13 0x1ba +#define RADIO_2057v7_OVR_REG14 0x1bb +#define RADIO_2057v7_OVR_REG15 0x1bc +#define RADIO_2057v7_OVR_REG16 0x1bd +#define RADIO_2057v7_OVR_REG1 0x1be +#define RADIO_2057v7_OVR_REG18 0x1bf +#define RADIO_2057v7_OVR_REG19 0x1c0 +#define RADIO_2057v7_OVR_REG20 0x1c1 +#define RADIO_2057v7_OVR_REG21 0x1c2 +#define RADIO_2057v7_OVR_REG2 0x1c3 +#define RADIO_2057v7_OVR_REG23 0x1c4 +#define RADIO_2057v7_OVR_REG24 0x1c5 +#define RADIO_2057v7_OVR_REG25 0x1c6 +#define RADIO_2057v7_OVR_REG26 0x1c7 +#define RADIO_2057v7_OVR_REG27 0x1c8 +#define RADIO_2057v7_OVR_REG28 0x1c9 +#define RADIO_2057v7_IQTEST_SEL_PU2 0x1ca + +#define RADIO_2057_VCM_MASK 0x7 + +#endif /* _BRCM_PHY_RADIO_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/phy/phyreg_n.h b/drivers/net/wireless/brcm80211/brcmsmac/phy/phyreg_n.h new file mode 100644 index 0000000..a97c3a7 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/phy/phyreg_n.h @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define NPHY_TBL_ID_GAIN1 0 +#define NPHY_TBL_ID_GAIN2 1 +#define NPHY_TBL_ID_GAINBITS1 2 +#define NPHY_TBL_ID_GAINBITS2 3 +#define NPHY_TBL_ID_GAINLIMIT 4 +#define NPHY_TBL_ID_WRSSIGainLimit 5 +#define NPHY_TBL_ID_RFSEQ 7 +#define NPHY_TBL_ID_AFECTRL 8 +#define NPHY_TBL_ID_ANTSWCTRLLUT 9 +#define NPHY_TBL_ID_IQLOCAL 15 +#define NPHY_TBL_ID_NOISEVAR 16 +#define NPHY_TBL_ID_SAMPLEPLAY 17 +#define NPHY_TBL_ID_CORE1TXPWRCTL 26 +#define NPHY_TBL_ID_CORE2TXPWRCTL 27 +#define NPHY_TBL_ID_CMPMETRICDATAWEIGHTTBL 30 + +#define NPHY_TBL_ID_EPSILONTBL0 31 +#define NPHY_TBL_ID_SCALARTBL0 32 +#define NPHY_TBL_ID_EPSILONTBL1 33 +#define NPHY_TBL_ID_SCALARTBL1 34 + +#define NPHY_TO_BPHY_OFF 0xc00 + +#define NPHY_BandControl_currentBand 0x0001 +#define RFCC_CHIP0_PU 0x0400 +#define RFCC_POR_FORCE 0x0040 +#define RFCC_OE_POR_FORCE 0x0080 +#define NPHY_RfctrlIntc_override_OFF 0 +#define NPHY_RfctrlIntc_override_TRSW 1 +#define NPHY_RfctrlIntc_override_PA 2 +#define NPHY_RfctrlIntc_override_EXT_LNA_PU 3 +#define NPHY_RfctrlIntc_override_EXT_LNA_GAIN 4 +#define RIFS_ENABLE 0x80 +#define BPHY_BAND_SEL_UP20 0x10 +#define NPHY_MLenable 0x02 + +#define NPHY_RfseqMode_CoreActv_override 0x0001 +#define NPHY_RfseqMode_Trigger_override 0x0002 +#define NPHY_RfseqCoreActv_TxRxChain0 (0x11) +#define NPHY_RfseqCoreActv_TxRxChain1 (0x22) + +#define NPHY_RfseqTrigger_rx2tx 0x0001 +#define NPHY_RfseqTrigger_tx2rx 0x0002 +#define NPHY_RfseqTrigger_updategainh 0x0004 +#define NPHY_RfseqTrigger_updategainl 0x0008 +#define NPHY_RfseqTrigger_updategainu 0x0010 +#define NPHY_RfseqTrigger_reset2rx 0x0020 +#define NPHY_RfseqStatus_rx2tx 0x0001 +#define NPHY_RfseqStatus_tx2rx 0x0002 +#define NPHY_RfseqStatus_updategainh 0x0004 +#define NPHY_RfseqStatus_updategainl 0x0008 +#define NPHY_RfseqStatus_updategainu 0x0010 +#define NPHY_RfseqStatus_reset2rx 0x0020 +#define NPHY_ClassifierCtrl_cck_en 0x1 +#define NPHY_ClassifierCtrl_ofdm_en 0x2 +#define NPHY_ClassifierCtrl_waited_en 0x4 +#define NPHY_IQFlip_ADC1 0x0001 +#define NPHY_IQFlip_ADC2 0x0010 +#define NPHY_sampleCmd_STOP 0x0002 + +#define RX_GF_OR_MM 0x0004 +#define RX_GF_MM_AUTO 0x0100 + +#define NPHY_iqloCalCmdGctl_IQLO_CAL_EN 0x8000 + +#define NPHY_IqestCmd_iqstart 0x1 +#define NPHY_IqestCmd_iqMode 0x2 + +#define NPHY_TxPwrCtrlCmd_pwrIndex_init 0x40 +#define NPHY_TxPwrCtrlCmd_pwrIndex_init_rev7 0x19 + +#define PRIM_SEL_UP20 0x8000 + +#define NPHY_RFSEQ_RX2TX 0x0 +#define NPHY_RFSEQ_TX2RX 0x1 +#define NPHY_RFSEQ_RESET2RX 0x2 +#define NPHY_RFSEQ_UPDATEGAINH 0x3 +#define NPHY_RFSEQ_UPDATEGAINL 0x4 +#define NPHY_RFSEQ_UPDATEGAINU 0x5 + +#define NPHY_RFSEQ_CMD_NOP 0x0 +#define NPHY_RFSEQ_CMD_RXG_FBW 0x1 +#define NPHY_RFSEQ_CMD_TR_SWITCH 0x2 +#define NPHY_RFSEQ_CMD_EXT_PA 0x3 +#define NPHY_RFSEQ_CMD_RXPD_TXPD 0x4 +#define NPHY_RFSEQ_CMD_TX_GAIN 0x5 +#define NPHY_RFSEQ_CMD_RX_GAIN 0x6 +#define NPHY_RFSEQ_CMD_SET_HPF_BW 0x7 +#define NPHY_RFSEQ_CMD_CLR_HIQ_DIS 0x8 +#define NPHY_RFSEQ_CMD_END 0xf + +#define NPHY_REV3_RFSEQ_CMD_NOP 0x0 +#define NPHY_REV3_RFSEQ_CMD_RXG_FBW 0x1 +#define NPHY_REV3_RFSEQ_CMD_TR_SWITCH 0x2 +#define NPHY_REV3_RFSEQ_CMD_INT_PA_PU 0x3 +#define NPHY_REV3_RFSEQ_CMD_EXT_PA 0x4 +#define NPHY_REV3_RFSEQ_CMD_RXPD_TXPD 0x5 +#define NPHY_REV3_RFSEQ_CMD_TX_GAIN 0x6 +#define NPHY_REV3_RFSEQ_CMD_RX_GAIN 0x7 +#define NPHY_REV3_RFSEQ_CMD_CLR_HIQ_DIS 0x8 +#define NPHY_REV3_RFSEQ_CMD_SET_HPF_H_HPC 0x9 +#define NPHY_REV3_RFSEQ_CMD_SET_LPF_H_HPC 0xa +#define NPHY_REV3_RFSEQ_CMD_SET_HPF_M_HPC 0xb +#define NPHY_REV3_RFSEQ_CMD_SET_LPF_M_HPC 0xc +#define NPHY_REV3_RFSEQ_CMD_SET_HPF_L_HPC 0xd +#define NPHY_REV3_RFSEQ_CMD_SET_LPF_L_HPC 0xe +#define NPHY_REV3_RFSEQ_CMD_CLR_RXRX_BIAS 0xf +#define NPHY_REV3_RFSEQ_CMD_END 0x1f + +#define NPHY_RSSI_SEL_W1 0x0 +#define NPHY_RSSI_SEL_W2 0x1 +#define NPHY_RSSI_SEL_NB 0x2 +#define NPHY_RSSI_SEL_IQ 0x3 +#define NPHY_RSSI_SEL_TSSI_2G 0x4 +#define NPHY_RSSI_SEL_TSSI_5G 0x5 +#define NPHY_RSSI_SEL_TBD 0x6 + +#define NPHY_RAIL_I 0x0 +#define NPHY_RAIL_Q 0x1 + +#define NPHY_FORCESIG_DECODEGATEDCLKS 0x8 + +#define NPHY_REV7_RfctrlOverride_cmd_rxrf_pu 0x0 +#define NPHY_REV7_RfctrlOverride_cmd_rx_pu 0x1 +#define NPHY_REV7_RfctrlOverride_cmd_tx_pu 0x2 +#define NPHY_REV7_RfctrlOverride_cmd_rxgain 0x3 +#define NPHY_REV7_RfctrlOverride_cmd_txgain 0x4 + +#define NPHY_REV7_RXGAINCODE_RFMXGAIN_MASK 0x000ff +#define NPHY_REV7_RXGAINCODE_LPFGAIN_MASK 0x0ff00 +#define NPHY_REV7_RXGAINCODE_DVGAGAIN_MASK 0xf0000 + +#define NPHY_REV7_TXGAINCODE_TGAIN_MASK 0x7fff +#define NPHY_REV7_TXGAINCODE_LPFGAIN_MASK 0x8000 +#define NPHY_REV7_TXGAINCODE_BIQ0GAIN_SHIFT 14 + +#define NPHY_REV7_RFCTRLOVERRIDE_ID0 0x0 +#define NPHY_REV7_RFCTRLOVERRIDE_ID1 0x1 +#define NPHY_REV7_RFCTRLOVERRIDE_ID2 0x2 + +#define NPHY_IqestIqAccLo(core) ((core == 0) ? 0x12c : 0x134) + +#define NPHY_IqestIqAccHi(core) ((core == 0) ? 0x12d : 0x135) + +#define NPHY_IqestipwrAccLo(core) ((core == 0) ? 0x12e : 0x136) + +#define NPHY_IqestipwrAccHi(core) ((core == 0) ? 0x12f : 0x137) + +#define NPHY_IqestqpwrAccLo(core) ((core == 0) ? 0x130 : 0x138) + +#define NPHY_IqestqpwrAccHi(core) ((core == 0) ? 0x131 : 0x139) diff --git a/drivers/net/wireless/brcm80211/brcmsmac/phy/phytbl_lcn.c b/drivers/net/wireless/brcm80211/brcmsmac/phy/phytbl_lcn.c new file mode 100644 index 0000000..622c01c --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/phy/phytbl_lcn.c @@ -0,0 +1,3250 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "phytbl_lcn.h" + +static const u32 dot11lcn_gain_tbl_rev0[] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000004, + 0x00000000, + 0x00000004, + 0x00000008, + 0x00000001, + 0x00000005, + 0x00000009, + 0x0000000d, + 0x0000004d, + 0x0000008d, + 0x0000000d, + 0x0000004d, + 0x0000008d, + 0x000000cd, + 0x0000004f, + 0x0000008f, + 0x000000cf, + 0x000000d3, + 0x00000113, + 0x00000513, + 0x00000913, + 0x00000953, + 0x00000d53, + 0x00001153, + 0x00001193, + 0x00005193, + 0x00009193, + 0x0000d193, + 0x00011193, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000004, + 0x00000000, + 0x00000004, + 0x00000008, + 0x00000001, + 0x00000005, + 0x00000009, + 0x0000000d, + 0x0000004d, + 0x0000008d, + 0x0000000d, + 0x0000004d, + 0x0000008d, + 0x000000cd, + 0x0000004f, + 0x0000008f, + 0x000000cf, + 0x000000d3, + 0x00000113, + 0x00000513, + 0x00000913, + 0x00000953, + 0x00000d53, + 0x00001153, + 0x00005153, + 0x00009153, + 0x0000d153, + 0x00011153, + 0x00015153, + 0x00019153, + 0x0001d153, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u32 dot11lcn_gain_tbl_rev1[] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000008, + 0x00000004, + 0x00000008, + 0x00000001, + 0x00000005, + 0x00000009, + 0x0000000D, + 0x00000011, + 0x00000051, + 0x00000091, + 0x00000011, + 0x00000051, + 0x00000091, + 0x000000d1, + 0x00000053, + 0x00000093, + 0x000000d3, + 0x000000d7, + 0x00000117, + 0x00000517, + 0x00000917, + 0x00000957, + 0x00000d57, + 0x00001157, + 0x00001197, + 0x00005197, + 0x00009197, + 0x0000d197, + 0x00011197, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000008, + 0x00000004, + 0x00000008, + 0x00000001, + 0x00000005, + 0x00000009, + 0x0000000D, + 0x00000011, + 0x00000051, + 0x00000091, + 0x00000011, + 0x00000051, + 0x00000091, + 0x000000d1, + 0x00000053, + 0x00000093, + 0x000000d3, + 0x000000d7, + 0x00000117, + 0x00000517, + 0x00000917, + 0x00000957, + 0x00000d57, + 0x00001157, + 0x00005157, + 0x00009157, + 0x0000d157, + 0x00011157, + 0x00015157, + 0x00019157, + 0x0001d157, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u16 dot11lcn_aux_gain_idx_tbl_rev0[] = { + 0x0401, + 0x0402, + 0x0403, + 0x0404, + 0x0405, + 0x0406, + 0x0407, + 0x0408, + 0x0409, + 0x040a, + 0x058b, + 0x058c, + 0x058d, + 0x058e, + 0x058f, + 0x0090, + 0x0091, + 0x0092, + 0x0193, + 0x0194, + 0x0195, + 0x0196, + 0x0197, + 0x0198, + 0x0199, + 0x019a, + 0x019b, + 0x019c, + 0x019d, + 0x019e, + 0x019f, + 0x01a0, + 0x01a1, + 0x01a2, + 0x01a3, + 0x01a4, + 0x01a5, + 0x0000, +}; + +static const u32 dot11lcn_gain_idx_tbl_rev0[] = { + 0x00000000, + 0x00000000, + 0x10000000, + 0x00000000, + 0x20000000, + 0x00000000, + 0x30000000, + 0x00000000, + 0x40000000, + 0x00000000, + 0x50000000, + 0x00000000, + 0x60000000, + 0x00000000, + 0x70000000, + 0x00000000, + 0x80000000, + 0x00000000, + 0x90000000, + 0x00000008, + 0xa0000000, + 0x00000008, + 0xb0000000, + 0x00000008, + 0xc0000000, + 0x00000008, + 0xd0000000, + 0x00000008, + 0xe0000000, + 0x00000008, + 0xf0000000, + 0x00000008, + 0x00000000, + 0x00000009, + 0x10000000, + 0x00000009, + 0x20000000, + 0x00000019, + 0x30000000, + 0x00000019, + 0x40000000, + 0x00000019, + 0x50000000, + 0x00000019, + 0x60000000, + 0x00000019, + 0x70000000, + 0x00000019, + 0x80000000, + 0x00000019, + 0x90000000, + 0x00000019, + 0xa0000000, + 0x00000019, + 0xb0000000, + 0x00000019, + 0xc0000000, + 0x00000019, + 0xd0000000, + 0x00000019, + 0xe0000000, + 0x00000019, + 0xf0000000, + 0x00000019, + 0x00000000, + 0x0000001a, + 0x10000000, + 0x0000001a, + 0x20000000, + 0x0000001a, + 0x30000000, + 0x0000001a, + 0x40000000, + 0x0000001a, + 0x50000000, + 0x00000002, + 0x60000000, + 0x00000002, + 0x70000000, + 0x00000002, + 0x80000000, + 0x00000002, + 0x90000000, + 0x00000002, + 0xa0000000, + 0x00000002, + 0xb0000000, + 0x00000002, + 0xc0000000, + 0x0000000a, + 0xd0000000, + 0x0000000a, + 0xe0000000, + 0x0000000a, + 0xf0000000, + 0x0000000a, + 0x00000000, + 0x0000000b, + 0x10000000, + 0x0000000b, + 0x20000000, + 0x0000000b, + 0x30000000, + 0x0000000b, + 0x40000000, + 0x0000000b, + 0x50000000, + 0x0000001b, + 0x60000000, + 0x0000001b, + 0x70000000, + 0x0000001b, + 0x80000000, + 0x0000001b, + 0x90000000, + 0x0000001b, + 0xa0000000, + 0x0000001b, + 0xb0000000, + 0x0000001b, + 0xc0000000, + 0x0000001b, + 0xd0000000, + 0x0000001b, + 0xe0000000, + 0x0000001b, + 0xf0000000, + 0x0000001b, + 0x00000000, + 0x0000001c, + 0x10000000, + 0x0000001c, + 0x20000000, + 0x0000001c, + 0x30000000, + 0x0000001c, + 0x40000000, + 0x0000001c, + 0x50000000, + 0x0000001c, + 0x60000000, + 0x0000001c, + 0x70000000, + 0x0000001c, + 0x80000000, + 0x0000001c, + 0x90000000, + 0x0000001c, +}; + +static const u16 dot11lcn_aux_gain_idx_tbl_2G[] = { + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0001, + 0x0080, + 0x0081, + 0x0100, + 0x0101, + 0x0180, + 0x0181, + 0x0182, + 0x0183, + 0x0184, + 0x0185, + 0x0186, + 0x0187, + 0x0188, + 0x0285, + 0x0289, + 0x028a, + 0x028b, + 0x028c, + 0x028d, + 0x028e, + 0x028f, + 0x0290, + 0x0291, + 0x0292, + 0x0293, + 0x0294, + 0x0295, + 0x0296, + 0x0297, + 0x0298, + 0x0299, + 0x029a, + 0x0000 +}; + +static const u8 dot11lcn_gain_val_tbl_2G[] = { + 0xfc, + 0x02, + 0x08, + 0x0e, + 0x13, + 0x1b, + 0xfc, + 0x02, + 0x08, + 0x0e, + 0x13, + 0x1b, + 0xfc, + 0x00, + 0x0c, + 0x03, + 0xeb, + 0xfe, + 0x07, + 0x0b, + 0x0f, + 0xfb, + 0xfe, + 0x01, + 0x05, + 0x08, + 0x0b, + 0x0e, + 0x11, + 0x14, + 0x17, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x06, + 0x09, + 0x0c, + 0x0f, + 0x12, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x06, + 0x09, + 0x0c, + 0x0f, + 0x12, + 0x15, + 0x18, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00 +}; + +static const u32 dot11lcn_gain_idx_tbl_2G[] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x10000000, + 0x00000000, + 0x00000000, + 0x00000008, + 0x10000000, + 0x00000008, + 0x00000000, + 0x00000010, + 0x10000000, + 0x00000010, + 0x00000000, + 0x00000018, + 0x10000000, + 0x00000018, + 0x20000000, + 0x00000018, + 0x30000000, + 0x00000018, + 0x40000000, + 0x00000018, + 0x50000000, + 0x00000018, + 0x60000000, + 0x00000018, + 0x70000000, + 0x00000018, + 0x80000000, + 0x00000018, + 0x50000000, + 0x00000028, + 0x90000000, + 0x00000028, + 0xa0000000, + 0x00000028, + 0xb0000000, + 0x00000028, + 0xc0000000, + 0x00000028, + 0xd0000000, + 0x00000028, + 0xe0000000, + 0x00000028, + 0xf0000000, + 0x00000028, + 0x00000000, + 0x00000029, + 0x10000000, + 0x00000029, + 0x20000000, + 0x00000029, + 0x30000000, + 0x00000029, + 0x40000000, + 0x00000029, + 0x50000000, + 0x00000029, + 0x60000000, + 0x00000029, + 0x70000000, + 0x00000029, + 0x80000000, + 0x00000029, + 0x90000000, + 0x00000029, + 0xa0000000, + 0x00000029, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x10000000, + 0x00000000, + 0x00000000, + 0x00000008, + 0x10000000, + 0x00000008, + 0x00000000, + 0x00000010, + 0x10000000, + 0x00000010, + 0x00000000, + 0x00000018, + 0x10000000, + 0x00000018, + 0x20000000, + 0x00000018, + 0x30000000, + 0x00000018, + 0x40000000, + 0x00000018, + 0x50000000, + 0x00000018, + 0x60000000, + 0x00000018, + 0x70000000, + 0x00000018, + 0x80000000, + 0x00000018, + 0x50000000, + 0x00000028, + 0x90000000, + 0x00000028, + 0xa0000000, + 0x00000028, + 0xb0000000, + 0x00000028, + 0xc0000000, + 0x00000028, + 0xd0000000, + 0x00000028, + 0xe0000000, + 0x00000028, + 0xf0000000, + 0x00000028, + 0x00000000, + 0x00000029, + 0x10000000, + 0x00000029, + 0x20000000, + 0x00000029, + 0x30000000, + 0x00000029, + 0x40000000, + 0x00000029, + 0x50000000, + 0x00000029, + 0x60000000, + 0x00000029, + 0x70000000, + 0x00000029, + 0x80000000, + 0x00000029, + 0x90000000, + 0x00000029, + 0xa0000000, + 0x00000029, + 0xb0000000, + 0x00000029, + 0xc0000000, + 0x00000029, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000 +}; + +static const u32 dot11lcn_gain_tbl_2G[] = { + 0x00000000, + 0x00000004, + 0x00000008, + 0x00000001, + 0x00000005, + 0x00000009, + 0x0000000d, + 0x0000004d, + 0x0000008d, + 0x00000049, + 0x00000089, + 0x000000c9, + 0x0000004b, + 0x0000008b, + 0x000000cb, + 0x000000cf, + 0x0000010f, + 0x0000050f, + 0x0000090f, + 0x0000094f, + 0x00000d4f, + 0x0000114f, + 0x0000118f, + 0x0000518f, + 0x0000918f, + 0x0000d18f, + 0x0001118f, + 0x0001518f, + 0x0001918f, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000 +}; + +static const u32 dot11lcn_gain_tbl_extlna_2G[] = { + 0x00000000, + 0x00000004, + 0x00000008, + 0x00000001, + 0x00000005, + 0x00000009, + 0x0000000d, + 0x00000003, + 0x00000007, + 0x0000000b, + 0x0000000f, + 0x0000004f, + 0x0000008f, + 0x000000cf, + 0x0000010f, + 0x0000014f, + 0x0000018f, + 0x0000058f, + 0x0000098f, + 0x00000d8f, + 0x00008000, + 0x00008004, + 0x00008008, + 0x00008001, + 0x00008005, + 0x00008009, + 0x0000800d, + 0x00008003, + 0x00008007, + 0x0000800b, + 0x0000800f, + 0x0000804f, + 0x0000808f, + 0x000080cf, + 0x0000810f, + 0x0000814f, + 0x0000818f, + 0x0000858f, + 0x0000898f, + 0x00008d8f, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000 +}; + +static const u16 dot11lcn_aux_gain_idx_tbl_extlna_2G[] = { + 0x0400, + 0x0400, + 0x0400, + 0x0400, + 0x0400, + 0x0400, + 0x0400, + 0x0400, + 0x0400, + 0x0401, + 0x0402, + 0x0403, + 0x0404, + 0x0483, + 0x0484, + 0x0485, + 0x0486, + 0x0583, + 0x0584, + 0x0585, + 0x0587, + 0x0588, + 0x0589, + 0x058a, + 0x0687, + 0x0688, + 0x0689, + 0x068a, + 0x068b, + 0x068c, + 0x068d, + 0x068e, + 0x068f, + 0x0690, + 0x0691, + 0x0692, + 0x0693, + 0x0000 +}; + +static const u8 dot11lcn_gain_val_tbl_extlna_2G[] = { + 0xfc, + 0x02, + 0x08, + 0x0e, + 0x13, + 0x1b, + 0xfc, + 0x02, + 0x08, + 0x0e, + 0x13, + 0x1b, + 0xfc, + 0x00, + 0x0f, + 0x03, + 0xeb, + 0xfe, + 0x07, + 0x0b, + 0x0f, + 0xfb, + 0xfe, + 0x01, + 0x05, + 0x08, + 0x0b, + 0x0e, + 0x11, + 0x14, + 0x17, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x06, + 0x09, + 0x0c, + 0x0f, + 0x12, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x06, + 0x09, + 0x0c, + 0x0f, + 0x12, + 0x15, + 0x18, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00 +}; + +static const u32 dot11lcn_gain_idx_tbl_extlna_2G[] = { + 0x00000000, + 0x00000040, + 0x00000000, + 0x00000040, + 0x00000000, + 0x00000040, + 0x00000000, + 0x00000040, + 0x00000000, + 0x00000040, + 0x00000000, + 0x00000040, + 0x00000000, + 0x00000040, + 0x00000000, + 0x00000040, + 0x00000000, + 0x00000040, + 0x10000000, + 0x00000040, + 0x20000000, + 0x00000040, + 0x30000000, + 0x00000040, + 0x40000000, + 0x00000040, + 0x30000000, + 0x00000048, + 0x40000000, + 0x00000048, + 0x50000000, + 0x00000048, + 0x60000000, + 0x00000048, + 0x30000000, + 0x00000058, + 0x40000000, + 0x00000058, + 0x50000000, + 0x00000058, + 0x70000000, + 0x00000058, + 0x80000000, + 0x00000058, + 0x90000000, + 0x00000058, + 0xa0000000, + 0x00000058, + 0x70000000, + 0x00000068, + 0x80000000, + 0x00000068, + 0x90000000, + 0x00000068, + 0xa0000000, + 0x00000068, + 0xb0000000, + 0x00000068, + 0xc0000000, + 0x00000068, + 0xd0000000, + 0x00000068, + 0xe0000000, + 0x00000068, + 0xf0000000, + 0x00000068, + 0x00000000, + 0x00000069, + 0x10000000, + 0x00000069, + 0x20000000, + 0x00000069, + 0x30000000, + 0x00000069, + 0x40000000, + 0x00000041, + 0x40000000, + 0x00000041, + 0x40000000, + 0x00000041, + 0x40000000, + 0x00000041, + 0x40000000, + 0x00000041, + 0x40000000, + 0x00000041, + 0x40000000, + 0x00000041, + 0x40000000, + 0x00000041, + 0x40000000, + 0x00000041, + 0x50000000, + 0x00000041, + 0x60000000, + 0x00000041, + 0x70000000, + 0x00000041, + 0x80000000, + 0x00000041, + 0x70000000, + 0x00000049, + 0x80000000, + 0x00000049, + 0x90000000, + 0x00000049, + 0xa0000000, + 0x00000049, + 0x70000000, + 0x00000059, + 0x80000000, + 0x00000059, + 0x90000000, + 0x00000059, + 0xb0000000, + 0x00000059, + 0xc0000000, + 0x00000059, + 0xd0000000, + 0x00000059, + 0xe0000000, + 0x00000059, + 0xb0000000, + 0x00000069, + 0xc0000000, + 0x00000069, + 0xd0000000, + 0x00000069, + 0xe0000000, + 0x00000069, + 0xf0000000, + 0x00000069, + 0x00000000, + 0x0000006a, + 0x10000000, + 0x0000006a, + 0x20000000, + 0x0000006a, + 0x30000000, + 0x0000006a, + 0x40000000, + 0x0000006a, + 0x50000000, + 0x0000006a, + 0x60000000, + 0x0000006a, + 0x70000000, + 0x0000006a, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000 +}; + +static const u32 dot11lcn_aux_gain_idx_tbl_5G[] = { + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0001, + 0x0002, + 0x0003, + 0x0004, + 0x0083, + 0x0084, + 0x0085, + 0x0086, + 0x0087, + 0x0186, + 0x0187, + 0x0188, + 0x0189, + 0x018a, + 0x018b, + 0x018c, + 0x018d, + 0x018e, + 0x018f, + 0x0190, + 0x0191, + 0x0192, + 0x0193, + 0x0194, + 0x0195, + 0x0196, + 0x0197, + 0x0198, + 0x0199, + 0x019a, + 0x019b, + 0x019c, + 0x019d, + 0x0000 +}; + +static const u32 dot11lcn_gain_val_tbl_5G[] = { + 0xf7, + 0xfd, + 0x00, + 0x04, + 0x04, + 0x04, + 0xf7, + 0xfd, + 0x00, + 0x04, + 0x04, + 0x04, + 0xf6, + 0x00, + 0x0c, + 0x03, + 0xeb, + 0xfe, + 0x06, + 0x0a, + 0x10, + 0x00, + 0x03, + 0x06, + 0x09, + 0x0c, + 0x0f, + 0x12, + 0x15, + 0x18, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x06, + 0x09, + 0x0c, + 0x0f, + 0x12, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x06, + 0x09, + 0x0c, + 0x0f, + 0x12, + 0x15, + 0x18, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00 +}; + +static const u32 dot11lcn_gain_idx_tbl_5G[] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x10000000, + 0x00000000, + 0x20000000, + 0x00000000, + 0x30000000, + 0x00000000, + 0x40000000, + 0x00000000, + 0x30000000, + 0x00000008, + 0x40000000, + 0x00000008, + 0x50000000, + 0x00000008, + 0x60000000, + 0x00000008, + 0x70000000, + 0x00000008, + 0x60000000, + 0x00000018, + 0x70000000, + 0x00000018, + 0x80000000, + 0x00000018, + 0x90000000, + 0x00000018, + 0xa0000000, + 0x00000018, + 0xb0000000, + 0x00000018, + 0xc0000000, + 0x00000018, + 0xd0000000, + 0x00000018, + 0xe0000000, + 0x00000018, + 0xf0000000, + 0x00000018, + 0x00000000, + 0x00000019, + 0x10000000, + 0x00000019, + 0x20000000, + 0x00000019, + 0x30000000, + 0x00000019, + 0x40000000, + 0x00000019, + 0x50000000, + 0x00000019, + 0x60000000, + 0x00000019, + 0x70000000, + 0x00000019, + 0x80000000, + 0x00000019, + 0x90000000, + 0x00000019, + 0xa0000000, + 0x00000019, + 0xb0000000, + 0x00000019, + 0xc0000000, + 0x00000019, + 0xd0000000, + 0x00000019, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000 +}; + +static const u32 dot11lcn_gain_tbl_5G[] = { + 0x00000000, + 0x00000040, + 0x00000080, + 0x00000001, + 0x00000005, + 0x00000009, + 0x0000000d, + 0x00000011, + 0x00000015, + 0x00000055, + 0x00000095, + 0x00000017, + 0x0000001b, + 0x0000005b, + 0x0000009b, + 0x000000db, + 0x0000011b, + 0x0000015b, + 0x0000019b, + 0x0000059b, + 0x0000099b, + 0x00000d9b, + 0x0000119b, + 0x0000519b, + 0x0000919b, + 0x0000d19b, + 0x0001119b, + 0x0001519b, + 0x0001919b, + 0x0001d19b, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000 +}; + +const struct phytbl_info dot11lcnphytbl_rx_gain_info_rev0[] = { + {&dot11lcn_gain_tbl_rev0, + sizeof(dot11lcn_gain_tbl_rev0) / sizeof(dot11lcn_gain_tbl_rev0[0]), 18, + 0, 32} + , + {&dot11lcn_aux_gain_idx_tbl_rev0, + sizeof(dot11lcn_aux_gain_idx_tbl_rev0) / + sizeof(dot11lcn_aux_gain_idx_tbl_rev0[0]), 14, 0, 16} + , + {&dot11lcn_gain_idx_tbl_rev0, + sizeof(dot11lcn_gain_idx_tbl_rev0) / + sizeof(dot11lcn_gain_idx_tbl_rev0[0]), 13, 0, 32} + , +}; + +static const struct phytbl_info dot11lcnphytbl_rx_gain_info_rev1[] = { + {&dot11lcn_gain_tbl_rev1, + sizeof(dot11lcn_gain_tbl_rev1) / sizeof(dot11lcn_gain_tbl_rev1[0]), 18, + 0, 32} + , + {&dot11lcn_aux_gain_idx_tbl_rev0, + sizeof(dot11lcn_aux_gain_idx_tbl_rev0) / + sizeof(dot11lcn_aux_gain_idx_tbl_rev0[0]), 14, 0, 16} + , + {&dot11lcn_gain_idx_tbl_rev0, + sizeof(dot11lcn_gain_idx_tbl_rev0) / + sizeof(dot11lcn_gain_idx_tbl_rev0[0]), 13, 0, 32} + , +}; + +const struct phytbl_info dot11lcnphytbl_rx_gain_info_2G_rev2[] = { + {&dot11lcn_gain_tbl_2G, + sizeof(dot11lcn_gain_tbl_2G) / sizeof(dot11lcn_gain_tbl_2G[0]), 18, 0, + 32} + , + {&dot11lcn_aux_gain_idx_tbl_2G, + sizeof(dot11lcn_aux_gain_idx_tbl_2G) / + sizeof(dot11lcn_aux_gain_idx_tbl_2G[0]), 14, 0, 16} + , + {&dot11lcn_gain_idx_tbl_2G, + sizeof(dot11lcn_gain_idx_tbl_2G) / sizeof(dot11lcn_gain_idx_tbl_2G[0]), + 13, 0, 32} + , + {&dot11lcn_gain_val_tbl_2G, + sizeof(dot11lcn_gain_val_tbl_2G) / sizeof(dot11lcn_gain_val_tbl_2G[0]), + 17, 0, 8} +}; + +const struct phytbl_info dot11lcnphytbl_rx_gain_info_5G_rev2[] = { + {&dot11lcn_gain_tbl_5G, + sizeof(dot11lcn_gain_tbl_5G) / sizeof(dot11lcn_gain_tbl_5G[0]), 18, 0, + 32} + , + {&dot11lcn_aux_gain_idx_tbl_5G, + sizeof(dot11lcn_aux_gain_idx_tbl_5G) / + sizeof(dot11lcn_aux_gain_idx_tbl_5G[0]), 14, 0, 16} + , + {&dot11lcn_gain_idx_tbl_5G, + sizeof(dot11lcn_gain_idx_tbl_5G) / sizeof(dot11lcn_gain_idx_tbl_5G[0]), + 13, 0, 32} + , + {&dot11lcn_gain_val_tbl_5G, + sizeof(dot11lcn_gain_val_tbl_5G) / sizeof(dot11lcn_gain_val_tbl_5G[0]), + 17, 0, 8} +}; + +const struct phytbl_info dot11lcnphytbl_rx_gain_info_extlna_2G_rev2[] = { + {&dot11lcn_gain_tbl_extlna_2G, + sizeof(dot11lcn_gain_tbl_extlna_2G) / + sizeof(dot11lcn_gain_tbl_extlna_2G[0]), 18, 0, 32} + , + {&dot11lcn_aux_gain_idx_tbl_extlna_2G, + sizeof(dot11lcn_aux_gain_idx_tbl_extlna_2G) / + sizeof(dot11lcn_aux_gain_idx_tbl_extlna_2G[0]), 14, 0, 16} + , + {&dot11lcn_gain_idx_tbl_extlna_2G, + sizeof(dot11lcn_gain_idx_tbl_extlna_2G) / + sizeof(dot11lcn_gain_idx_tbl_extlna_2G[0]), 13, 0, 32} + , + {&dot11lcn_gain_val_tbl_extlna_2G, + sizeof(dot11lcn_gain_val_tbl_extlna_2G) / + sizeof(dot11lcn_gain_val_tbl_extlna_2G[0]), 17, 0, 8} +}; + +const struct phytbl_info dot11lcnphytbl_rx_gain_info_extlna_5G_rev2[] = { + {&dot11lcn_gain_tbl_5G, + sizeof(dot11lcn_gain_tbl_5G) / sizeof(dot11lcn_gain_tbl_5G[0]), 18, 0, + 32} + , + {&dot11lcn_aux_gain_idx_tbl_5G, + sizeof(dot11lcn_aux_gain_idx_tbl_5G) / + sizeof(dot11lcn_aux_gain_idx_tbl_5G[0]), 14, 0, 16} + , + {&dot11lcn_gain_idx_tbl_5G, + sizeof(dot11lcn_gain_idx_tbl_5G) / sizeof(dot11lcn_gain_idx_tbl_5G[0]), + 13, 0, 32} + , + {&dot11lcn_gain_val_tbl_5G, + sizeof(dot11lcn_gain_val_tbl_5G) / sizeof(dot11lcn_gain_val_tbl_5G[0]), + 17, 0, 8} +}; + +const u32 dot11lcnphytbl_rx_gain_info_sz_rev0 = + sizeof(dot11lcnphytbl_rx_gain_info_rev0) / + sizeof(dot11lcnphytbl_rx_gain_info_rev0[0]); + +const u32 dot11lcnphytbl_rx_gain_info_2G_rev2_sz = + sizeof(dot11lcnphytbl_rx_gain_info_2G_rev2) / + sizeof(dot11lcnphytbl_rx_gain_info_2G_rev2[0]); + +const u32 dot11lcnphytbl_rx_gain_info_5G_rev2_sz = + sizeof(dot11lcnphytbl_rx_gain_info_5G_rev2) / + sizeof(dot11lcnphytbl_rx_gain_info_5G_rev2[0]); + +static const u16 dot11lcn_min_sig_sq_tbl_rev0[] = { + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, + 0x014d, +}; + +static const u16 dot11lcn_noise_scale_tbl_rev0[] = { + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, +}; + +static const u32 dot11lcn_fltr_ctrl_tbl_rev0[] = { + 0x000141f8, + 0x000021f8, + 0x000021fb, + 0x000041fb, + 0x0001fe4b, + 0x0000217b, + 0x00002133, + 0x000040eb, + 0x0001fea3, + 0x0000024b, +}; + +static const u32 dot11lcn_ps_ctrl_tbl_rev0[] = { + 0x00100001, + 0x00200010, + 0x00300001, + 0x00400010, + 0x00500022, + 0x00600122, + 0x00700222, + 0x00800322, + 0x00900422, + 0x00a00522, + 0x00b00622, + 0x00c00722, + 0x00d00822, + 0x00f00922, + 0x00100a22, + 0x00200b22, + 0x00300c22, + 0x00400d22, + 0x00500e22, + 0x00600f22, +}; + +static const u16 dot11lcn_sw_ctrl_tbl_4313_epa_rev0_combo[] = { + 0x0007, + 0x0005, + 0x0006, + 0x0004, + 0x0007, + 0x0005, + 0x0006, + 0x0004, + 0x0007, + 0x0005, + 0x0006, + 0x0004, + 0x0007, + 0x0005, + 0x0006, + 0x0004, + 0x000b, + 0x000b, + 0x000a, + 0x000a, + 0x000b, + 0x000b, + 0x000a, + 0x000a, + 0x000b, + 0x000b, + 0x000a, + 0x000a, + 0x000b, + 0x000b, + 0x000a, + 0x000a, + 0x0007, + 0x0005, + 0x0006, + 0x0004, + 0x0007, + 0x0005, + 0x0006, + 0x0004, + 0x0007, + 0x0005, + 0x0006, + 0x0004, + 0x0007, + 0x0005, + 0x0006, + 0x0004, + 0x000b, + 0x000b, + 0x000a, + 0x000a, + 0x000b, + 0x000b, + 0x000a, + 0x000a, + 0x000b, + 0x000b, + 0x000a, + 0x000a, + 0x000b, + 0x000b, + 0x000a, + 0x000a, + +}; + +static const u16 dot11lcn_sw_ctrl_tbl_4313_bt_epa_p250_rev0[] = { + 0x0007, + 0x0005, + 0x0002, + 0x0000, + 0x0007, + 0x0005, + 0x0002, + 0x0000, + 0x0007, + 0x0005, + 0x0002, + 0x0000, + 0x0007, + 0x0005, + 0x0002, + 0x0000, + 0x0007, + 0x0007, + 0x0002, + 0x0002, + 0x0007, + 0x0007, + 0x0002, + 0x0002, + 0x0007, + 0x0007, + 0x0002, + 0x0002, + 0x0007, + 0x0007, + 0x0002, + 0x0002, + 0x0007, + 0x0005, + 0x0002, + 0x0000, + 0x0007, + 0x0005, + 0x0002, + 0x0000, + 0x0007, + 0x0005, + 0x0002, + 0x0000, + 0x0007, + 0x0005, + 0x0002, + 0x0000, + 0x0007, + 0x0007, + 0x0002, + 0x0002, + 0x0007, + 0x0007, + 0x0002, + 0x0002, + 0x0007, + 0x0007, + 0x0002, + 0x0002, + 0x0007, + 0x0007, + 0x0002, + 0x0002, +}; + +static const u16 dot11lcn_sw_ctrl_tbl_4313_epa_rev0[] = { + 0x0002, + 0x0008, + 0x0004, + 0x0001, + 0x0002, + 0x0008, + 0x0004, + 0x0001, + 0x0002, + 0x0008, + 0x0004, + 0x0001, + 0x0002, + 0x0008, + 0x0004, + 0x0001, + 0x0002, + 0x0008, + 0x0004, + 0x0001, + 0x0002, + 0x0008, + 0x0004, + 0x0001, + 0x0002, + 0x0008, + 0x0004, + 0x0001, + 0x0002, + 0x0008, + 0x0004, + 0x0001, + 0x0002, + 0x0008, + 0x0004, + 0x0001, + 0x0002, + 0x0008, + 0x0004, + 0x0001, + 0x0002, + 0x0008, + 0x0004, + 0x0001, + 0x0002, + 0x0008, + 0x0004, + 0x0001, + 0x0002, + 0x0008, + 0x0004, + 0x0001, + 0x0002, + 0x0008, + 0x0004, + 0x0001, + 0x0002, + 0x0008, + 0x0004, + 0x0001, + 0x0002, + 0x0008, + 0x0004, + 0x0001, +}; + +static const u16 dot11lcn_sw_ctrl_tbl_4313_rev0[] = { + 0x000a, + 0x0009, + 0x0006, + 0x0005, + 0x000a, + 0x0009, + 0x0006, + 0x0005, + 0x000a, + 0x0009, + 0x0006, + 0x0005, + 0x000a, + 0x0009, + 0x0006, + 0x0005, + 0x000a, + 0x0009, + 0x0006, + 0x0005, + 0x000a, + 0x0009, + 0x0006, + 0x0005, + 0x000a, + 0x0009, + 0x0006, + 0x0005, + 0x000a, + 0x0009, + 0x0006, + 0x0005, + 0x000a, + 0x0009, + 0x0006, + 0x0005, + 0x000a, + 0x0009, + 0x0006, + 0x0005, + 0x000a, + 0x0009, + 0x0006, + 0x0005, + 0x000a, + 0x0009, + 0x0006, + 0x0005, + 0x000a, + 0x0009, + 0x0006, + 0x0005, + 0x000a, + 0x0009, + 0x0006, + 0x0005, + 0x000a, + 0x0009, + 0x0006, + 0x0005, + 0x000a, + 0x0009, + 0x0006, + 0x0005, +}; + +static const u16 dot11lcn_sw_ctrl_tbl_rev0[] = { + 0x0004, + 0x0004, + 0x0002, + 0x0002, + 0x0004, + 0x0004, + 0x0002, + 0x0002, + 0x0004, + 0x0004, + 0x0002, + 0x0002, + 0x0004, + 0x0004, + 0x0002, + 0x0002, + 0x0004, + 0x0004, + 0x0002, + 0x0002, + 0x0004, + 0x0004, + 0x0002, + 0x0002, + 0x0004, + 0x0004, + 0x0002, + 0x0002, + 0x0004, + 0x0004, + 0x0002, + 0x0002, + 0x0004, + 0x0004, + 0x0002, + 0x0002, + 0x0004, + 0x0004, + 0x0002, + 0x0002, + 0x0004, + 0x0004, + 0x0002, + 0x0002, + 0x0004, + 0x0004, + 0x0002, + 0x0002, + 0x0004, + 0x0004, + 0x0002, + 0x0002, + 0x0004, + 0x0004, + 0x0002, + 0x0002, + 0x0004, + 0x0004, + 0x0002, + 0x0002, + 0x0004, + 0x0004, + 0x0002, + 0x0002, +}; + +static const u8 dot11lcn_nf_table_rev0[] = { + 0x5f, + 0x36, + 0x29, + 0x1f, + 0x5f, + 0x36, + 0x29, + 0x1f, + 0x5f, + 0x36, + 0x29, + 0x1f, + 0x5f, + 0x36, + 0x29, + 0x1f, +}; + +static const u8 dot11lcn_gain_val_tbl_rev0[] = { + 0x09, + 0x0f, + 0x14, + 0x18, + 0xfe, + 0x07, + 0x0b, + 0x0f, + 0xfb, + 0xfe, + 0x01, + 0x05, + 0x08, + 0x0b, + 0x0e, + 0x11, + 0x14, + 0x17, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x06, + 0x09, + 0x0c, + 0x0f, + 0x12, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x06, + 0x09, + 0x0c, + 0x0f, + 0x12, + 0x15, + 0x18, + 0x1b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0xeb, + 0x00, + 0x00, +}; + +static const u8 dot11lcn_spur_tbl_rev0[] = { + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x02, + 0x03, + 0x01, + 0x03, + 0x02, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x02, + 0x03, + 0x01, + 0x03, + 0x02, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, + 0x01, +}; + +static const u16 dot11lcn_unsup_mcs_tbl_rev0[] = { + 0x001a, + 0x0034, + 0x004e, + 0x0068, + 0x009c, + 0x00d0, + 0x00ea, + 0x0104, + 0x0034, + 0x0068, + 0x009c, + 0x00d0, + 0x0138, + 0x01a0, + 0x01d4, + 0x0208, + 0x004e, + 0x009c, + 0x00ea, + 0x0138, + 0x01d4, + 0x0270, + 0x02be, + 0x030c, + 0x0068, + 0x00d0, + 0x0138, + 0x01a0, + 0x0270, + 0x0340, + 0x03a8, + 0x0410, + 0x0018, + 0x009c, + 0x00d0, + 0x0104, + 0x00ea, + 0x0138, + 0x0186, + 0x00d0, + 0x0104, + 0x0104, + 0x0138, + 0x016c, + 0x016c, + 0x01a0, + 0x0138, + 0x0186, + 0x0186, + 0x01d4, + 0x0222, + 0x0222, + 0x0270, + 0x0104, + 0x0138, + 0x016c, + 0x0138, + 0x016c, + 0x01a0, + 0x01d4, + 0x01a0, + 0x01d4, + 0x0208, + 0x0208, + 0x023c, + 0x0186, + 0x01d4, + 0x0222, + 0x01d4, + 0x0222, + 0x0270, + 0x02be, + 0x0270, + 0x02be, + 0x030c, + 0x030c, + 0x035a, + 0x0036, + 0x006c, + 0x00a2, + 0x00d8, + 0x0144, + 0x01b0, + 0x01e6, + 0x021c, + 0x006c, + 0x00d8, + 0x0144, + 0x01b0, + 0x0288, + 0x0360, + 0x03cc, + 0x0438, + 0x00a2, + 0x0144, + 0x01e6, + 0x0288, + 0x03cc, + 0x0510, + 0x05b2, + 0x0654, + 0x00d8, + 0x01b0, + 0x0288, + 0x0360, + 0x0510, + 0x06c0, + 0x0798, + 0x0870, + 0x0018, + 0x0144, + 0x01b0, + 0x021c, + 0x01e6, + 0x0288, + 0x032a, + 0x01b0, + 0x021c, + 0x021c, + 0x0288, + 0x02f4, + 0x02f4, + 0x0360, + 0x0288, + 0x032a, + 0x032a, + 0x03cc, + 0x046e, + 0x046e, + 0x0510, + 0x021c, + 0x0288, + 0x02f4, + 0x0288, + 0x02f4, + 0x0360, + 0x03cc, + 0x0360, + 0x03cc, + 0x0438, + 0x0438, + 0x04a4, + 0x032a, + 0x03cc, + 0x046e, + 0x03cc, + 0x046e, + 0x0510, + 0x05b2, + 0x0510, + 0x05b2, + 0x0654, + 0x0654, + 0x06f6, +}; + +static const u16 dot11lcn_iq_local_tbl_rev0[] = { + 0x0200, + 0x0300, + 0x0400, + 0x0600, + 0x0800, + 0x0b00, + 0x1000, + 0x1001, + 0x1002, + 0x1003, + 0x1004, + 0x1005, + 0x1006, + 0x1007, + 0x1707, + 0x2007, + 0x2d07, + 0x4007, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0200, + 0x0300, + 0x0400, + 0x0600, + 0x0800, + 0x0b00, + 0x1000, + 0x1001, + 0x1002, + 0x1003, + 0x1004, + 0x1005, + 0x1006, + 0x1007, + 0x1707, + 0x2007, + 0x2d07, + 0x4007, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x4000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, +}; + +static const u32 dot11lcn_papd_compdelta_tbl_rev0[] = { + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, + 0x00080000, +}; + +const struct phytbl_info dot11lcnphytbl_info_rev0[] = { + {&dot11lcn_min_sig_sq_tbl_rev0, + sizeof(dot11lcn_min_sig_sq_tbl_rev0) / + sizeof(dot11lcn_min_sig_sq_tbl_rev0[0]), 2, 0, 16} + , + {&dot11lcn_noise_scale_tbl_rev0, + sizeof(dot11lcn_noise_scale_tbl_rev0) / + sizeof(dot11lcn_noise_scale_tbl_rev0[0]), 1, 0, 16} + , + {&dot11lcn_fltr_ctrl_tbl_rev0, + sizeof(dot11lcn_fltr_ctrl_tbl_rev0) / + sizeof(dot11lcn_fltr_ctrl_tbl_rev0[0]), 11, 0, 32} + , + {&dot11lcn_ps_ctrl_tbl_rev0, + sizeof(dot11lcn_ps_ctrl_tbl_rev0) / + sizeof(dot11lcn_ps_ctrl_tbl_rev0[0]), 12, 0, 32} + , + {&dot11lcn_gain_idx_tbl_rev0, + sizeof(dot11lcn_gain_idx_tbl_rev0) / + sizeof(dot11lcn_gain_idx_tbl_rev0[0]), 13, 0, 32} + , + {&dot11lcn_aux_gain_idx_tbl_rev0, + sizeof(dot11lcn_aux_gain_idx_tbl_rev0) / + sizeof(dot11lcn_aux_gain_idx_tbl_rev0[0]), 14, 0, 16} + , + {&dot11lcn_sw_ctrl_tbl_rev0, + sizeof(dot11lcn_sw_ctrl_tbl_rev0) / + sizeof(dot11lcn_sw_ctrl_tbl_rev0[0]), 15, 0, 16} + , + {&dot11lcn_nf_table_rev0, + sizeof(dot11lcn_nf_table_rev0) / sizeof(dot11lcn_nf_table_rev0[0]), 16, + 0, 8} + , + {&dot11lcn_gain_val_tbl_rev0, + sizeof(dot11lcn_gain_val_tbl_rev0) / + sizeof(dot11lcn_gain_val_tbl_rev0[0]), 17, 0, 8} + , + {&dot11lcn_gain_tbl_rev0, + sizeof(dot11lcn_gain_tbl_rev0) / sizeof(dot11lcn_gain_tbl_rev0[0]), 18, + 0, 32} + , + {&dot11lcn_spur_tbl_rev0, + sizeof(dot11lcn_spur_tbl_rev0) / sizeof(dot11lcn_spur_tbl_rev0[0]), 20, + 0, 8} + , + {&dot11lcn_unsup_mcs_tbl_rev0, + sizeof(dot11lcn_unsup_mcs_tbl_rev0) / + sizeof(dot11lcn_unsup_mcs_tbl_rev0[0]), 23, 0, 16} + , + {&dot11lcn_iq_local_tbl_rev0, + sizeof(dot11lcn_iq_local_tbl_rev0) / + sizeof(dot11lcn_iq_local_tbl_rev0[0]), 0, 0, 16} + , + {&dot11lcn_papd_compdelta_tbl_rev0, + sizeof(dot11lcn_papd_compdelta_tbl_rev0) / + sizeof(dot11lcn_papd_compdelta_tbl_rev0[0]), 24, 0, 32} + , +}; + +const struct phytbl_info dot11lcn_sw_ctrl_tbl_info_4313 = { + &dot11lcn_sw_ctrl_tbl_4313_rev0, + sizeof(dot11lcn_sw_ctrl_tbl_4313_rev0) / + sizeof(dot11lcn_sw_ctrl_tbl_4313_rev0[0]), 15, 0, 16 +}; + +const struct phytbl_info dot11lcn_sw_ctrl_tbl_info_4313_epa = { + &dot11lcn_sw_ctrl_tbl_4313_epa_rev0, + sizeof(dot11lcn_sw_ctrl_tbl_4313_epa_rev0) / + sizeof(dot11lcn_sw_ctrl_tbl_4313_epa_rev0[0]), 15, 0, 16 +}; + +const struct phytbl_info dot11lcn_sw_ctrl_tbl_info_4313_bt_epa = { + &dot11lcn_sw_ctrl_tbl_4313_epa_rev0_combo, + sizeof(dot11lcn_sw_ctrl_tbl_4313_epa_rev0_combo) / + sizeof(dot11lcn_sw_ctrl_tbl_4313_epa_rev0_combo[0]), 15, 0, 16 +}; + +const struct phytbl_info dot11lcn_sw_ctrl_tbl_info_4313_bt_epa_p250 = { + &dot11lcn_sw_ctrl_tbl_4313_bt_epa_p250_rev0, + sizeof(dot11lcn_sw_ctrl_tbl_4313_bt_epa_p250_rev0) / + sizeof(dot11lcn_sw_ctrl_tbl_4313_bt_epa_p250_rev0[0]), 15, 0, 16 +}; + +const u32 dot11lcnphytbl_info_sz_rev0 = + sizeof(dot11lcnphytbl_info_rev0) / sizeof(dot11lcnphytbl_info_rev0[0]); + +const struct lcnphy_tx_gain_tbl_entry +dot11lcnphy_2GHz_extPA_gaintable_rev0[128] = { + {3, 0, 31, 0, 72}, + {3, 0, 31, 0, 70}, + {3, 0, 31, 0, 68}, + {3, 0, 30, 0, 67}, + {3, 0, 29, 0, 68}, + {3, 0, 28, 0, 68}, + {3, 0, 27, 0, 69}, + {3, 0, 26, 0, 70}, + {3, 0, 25, 0, 70}, + {3, 0, 24, 0, 71}, + {3, 0, 23, 0, 72}, + {3, 0, 23, 0, 70}, + {3, 0, 22, 0, 71}, + {3, 0, 21, 0, 72}, + {3, 0, 21, 0, 70}, + {3, 0, 21, 0, 68}, + {3, 0, 21, 0, 66}, + {3, 0, 21, 0, 64}, + {3, 0, 21, 0, 63}, + {3, 0, 20, 0, 64}, + {3, 0, 19, 0, 65}, + {3, 0, 19, 0, 64}, + {3, 0, 18, 0, 65}, + {3, 0, 18, 0, 64}, + {3, 0, 17, 0, 65}, + {3, 0, 17, 0, 64}, + {3, 0, 16, 0, 65}, + {3, 0, 16, 0, 64}, + {3, 0, 16, 0, 62}, + {3, 0, 16, 0, 60}, + {3, 0, 16, 0, 58}, + {3, 0, 15, 0, 61}, + {3, 0, 15, 0, 59}, + {3, 0, 14, 0, 61}, + {3, 0, 14, 0, 60}, + {3, 0, 14, 0, 58}, + {3, 0, 13, 0, 60}, + {3, 0, 13, 0, 59}, + {3, 0, 12, 0, 62}, + {3, 0, 12, 0, 60}, + {3, 0, 12, 0, 58}, + {3, 0, 11, 0, 62}, + {3, 0, 11, 0, 60}, + {3, 0, 11, 0, 59}, + {3, 0, 11, 0, 57}, + {3, 0, 10, 0, 61}, + {3, 0, 10, 0, 59}, + {3, 0, 10, 0, 57}, + {3, 0, 9, 0, 62}, + {3, 0, 9, 0, 60}, + {3, 0, 9, 0, 58}, + {3, 0, 9, 0, 57}, + {3, 0, 8, 0, 62}, + {3, 0, 8, 0, 60}, + {3, 0, 8, 0, 58}, + {3, 0, 8, 0, 57}, + {3, 0, 8, 0, 55}, + {3, 0, 7, 0, 61}, + {3, 0, 7, 0, 60}, + {3, 0, 7, 0, 58}, + {3, 0, 7, 0, 56}, + {3, 0, 7, 0, 55}, + {3, 0, 6, 0, 62}, + {3, 0, 6, 0, 60}, + {3, 0, 6, 0, 58}, + {3, 0, 6, 0, 57}, + {3, 0, 6, 0, 55}, + {3, 0, 6, 0, 54}, + {3, 0, 6, 0, 52}, + {3, 0, 5, 0, 61}, + {3, 0, 5, 0, 59}, + {3, 0, 5, 0, 57}, + {3, 0, 5, 0, 56}, + {3, 0, 5, 0, 54}, + {3, 0, 5, 0, 53}, + {3, 0, 5, 0, 51}, + {3, 0, 4, 0, 62}, + {3, 0, 4, 0, 60}, + {3, 0, 4, 0, 58}, + {3, 0, 4, 0, 57}, + {3, 0, 4, 0, 55}, + {3, 0, 4, 0, 54}, + {3, 0, 4, 0, 52}, + {3, 0, 4, 0, 51}, + {3, 0, 4, 0, 49}, + {3, 0, 4, 0, 48}, + {3, 0, 4, 0, 46}, + {3, 0, 3, 0, 60}, + {3, 0, 3, 0, 58}, + {3, 0, 3, 0, 57}, + {3, 0, 3, 0, 55}, + {3, 0, 3, 0, 54}, + {3, 0, 3, 0, 52}, + {3, 0, 3, 0, 51}, + {3, 0, 3, 0, 49}, + {3, 0, 3, 0, 48}, + {3, 0, 3, 0, 46}, + {3, 0, 3, 0, 45}, + {3, 0, 3, 0, 44}, + {3, 0, 3, 0, 43}, + {3, 0, 3, 0, 41}, + {3, 0, 2, 0, 61}, + {3, 0, 2, 0, 59}, + {3, 0, 2, 0, 57}, + {3, 0, 2, 0, 56}, + {3, 0, 2, 0, 54}, + {3, 0, 2, 0, 53}, + {3, 0, 2, 0, 51}, + {3, 0, 2, 0, 50}, + {3, 0, 2, 0, 48}, + {3, 0, 2, 0, 47}, + {3, 0, 2, 0, 46}, + {3, 0, 2, 0, 44}, + {3, 0, 2, 0, 43}, + {3, 0, 2, 0, 42}, + {3, 0, 2, 0, 41}, + {3, 0, 2, 0, 39}, + {3, 0, 2, 0, 38}, + {3, 0, 2, 0, 37}, + {3, 0, 2, 0, 36}, + {3, 0, 2, 0, 35}, + {3, 0, 2, 0, 34}, + {3, 0, 2, 0, 33}, + {3, 0, 2, 0, 32}, + {3, 0, 1, 0, 63}, + {3, 0, 1, 0, 61}, + {3, 0, 1, 0, 59}, + {3, 0, 1, 0, 57}, +}; + +const struct lcnphy_tx_gain_tbl_entry dot11lcnphy_2GHz_gaintable_rev0[128] = { + {7, 0, 31, 0, 72}, + {7, 0, 31, 0, 70}, + {7, 0, 31, 0, 68}, + {7, 0, 30, 0, 67}, + {7, 0, 29, 0, 68}, + {7, 0, 28, 0, 68}, + {7, 0, 27, 0, 69}, + {7, 0, 26, 0, 70}, + {7, 0, 25, 0, 70}, + {7, 0, 24, 0, 71}, + {7, 0, 23, 0, 72}, + {7, 0, 23, 0, 70}, + {7, 0, 22, 0, 71}, + {7, 0, 21, 0, 72}, + {7, 0, 21, 0, 70}, + {7, 0, 21, 0, 68}, + {7, 0, 21, 0, 66}, + {7, 0, 21, 0, 64}, + {7, 0, 21, 0, 63}, + {7, 0, 20, 0, 64}, + {7, 0, 19, 0, 65}, + {7, 0, 19, 0, 64}, + {7, 0, 18, 0, 65}, + {7, 0, 18, 0, 64}, + {7, 0, 17, 0, 65}, + {7, 0, 17, 0, 64}, + {7, 0, 16, 0, 65}, + {7, 0, 16, 0, 64}, + {7, 0, 16, 0, 62}, + {7, 0, 16, 0, 60}, + {7, 0, 16, 0, 58}, + {7, 0, 15, 0, 61}, + {7, 0, 15, 0, 59}, + {7, 0, 14, 0, 61}, + {7, 0, 14, 0, 60}, + {7, 0, 14, 0, 58}, + {7, 0, 13, 0, 60}, + {7, 0, 13, 0, 59}, + {7, 0, 12, 0, 62}, + {7, 0, 12, 0, 60}, + {7, 0, 12, 0, 58}, + {7, 0, 11, 0, 62}, + {7, 0, 11, 0, 60}, + {7, 0, 11, 0, 59}, + {7, 0, 11, 0, 57}, + {7, 0, 10, 0, 61}, + {7, 0, 10, 0, 59}, + {7, 0, 10, 0, 57}, + {7, 0, 9, 0, 62}, + {7, 0, 9, 0, 60}, + {7, 0, 9, 0, 58}, + {7, 0, 9, 0, 57}, + {7, 0, 8, 0, 62}, + {7, 0, 8, 0, 60}, + {7, 0, 8, 0, 58}, + {7, 0, 8, 0, 57}, + {7, 0, 8, 0, 55}, + {7, 0, 7, 0, 61}, + {7, 0, 7, 0, 60}, + {7, 0, 7, 0, 58}, + {7, 0, 7, 0, 56}, + {7, 0, 7, 0, 55}, + {7, 0, 6, 0, 62}, + {7, 0, 6, 0, 60}, + {7, 0, 6, 0, 58}, + {7, 0, 6, 0, 57}, + {7, 0, 6, 0, 55}, + {7, 0, 6, 0, 54}, + {7, 0, 6, 0, 52}, + {7, 0, 5, 0, 61}, + {7, 0, 5, 0, 59}, + {7, 0, 5, 0, 57}, + {7, 0, 5, 0, 56}, + {7, 0, 5, 0, 54}, + {7, 0, 5, 0, 53}, + {7, 0, 5, 0, 51}, + {7, 0, 4, 0, 62}, + {7, 0, 4, 0, 60}, + {7, 0, 4, 0, 58}, + {7, 0, 4, 0, 57}, + {7, 0, 4, 0, 55}, + {7, 0, 4, 0, 54}, + {7, 0, 4, 0, 52}, + {7, 0, 4, 0, 51}, + {7, 0, 4, 0, 49}, + {7, 0, 4, 0, 48}, + {7, 0, 4, 0, 46}, + {7, 0, 3, 0, 60}, + {7, 0, 3, 0, 58}, + {7, 0, 3, 0, 57}, + {7, 0, 3, 0, 55}, + {7, 0, 3, 0, 54}, + {7, 0, 3, 0, 52}, + {7, 0, 3, 0, 51}, + {7, 0, 3, 0, 49}, + {7, 0, 3, 0, 48}, + {7, 0, 3, 0, 46}, + {7, 0, 3, 0, 45}, + {7, 0, 3, 0, 44}, + {7, 0, 3, 0, 43}, + {7, 0, 3, 0, 41}, + {7, 0, 2, 0, 61}, + {7, 0, 2, 0, 59}, + {7, 0, 2, 0, 57}, + {7, 0, 2, 0, 56}, + {7, 0, 2, 0, 54}, + {7, 0, 2, 0, 53}, + {7, 0, 2, 0, 51}, + {7, 0, 2, 0, 50}, + {7, 0, 2, 0, 48}, + {7, 0, 2, 0, 47}, + {7, 0, 2, 0, 46}, + {7, 0, 2, 0, 44}, + {7, 0, 2, 0, 43}, + {7, 0, 2, 0, 42}, + {7, 0, 2, 0, 41}, + {7, 0, 2, 0, 39}, + {7, 0, 2, 0, 38}, + {7, 0, 2, 0, 37}, + {7, 0, 2, 0, 36}, + {7, 0, 2, 0, 35}, + {7, 0, 2, 0, 34}, + {7, 0, 2, 0, 33}, + {7, 0, 2, 0, 32}, + {7, 0, 1, 0, 63}, + {7, 0, 1, 0, 61}, + {7, 0, 1, 0, 59}, + {7, 0, 1, 0, 57}, +}; + +const struct lcnphy_tx_gain_tbl_entry dot11lcnphy_5GHz_gaintable_rev0[128] = { + {255, 255, 0xf0, 0, 152}, + {255, 255, 0xf0, 0, 147}, + {255, 255, 0xf0, 0, 143}, + {255, 255, 0xf0, 0, 139}, + {255, 255, 0xf0, 0, 135}, + {255, 255, 0xf0, 0, 131}, + {255, 255, 0xf0, 0, 128}, + {255, 255, 0xf0, 0, 124}, + {255, 255, 0xf0, 0, 121}, + {255, 255, 0xf0, 0, 117}, + {255, 255, 0xf0, 0, 114}, + {255, 255, 0xf0, 0, 111}, + {255, 255, 0xf0, 0, 107}, + {255, 255, 0xf0, 0, 104}, + {255, 255, 0xf0, 0, 101}, + {255, 255, 0xf0, 0, 99}, + {255, 255, 0xf0, 0, 96}, + {255, 255, 0xf0, 0, 93}, + {255, 255, 0xf0, 0, 90}, + {255, 255, 0xf0, 0, 88}, + {255, 255, 0xf0, 0, 85}, + {255, 255, 0xf0, 0, 83}, + {255, 255, 0xf0, 0, 81}, + {255, 255, 0xf0, 0, 78}, + {255, 255, 0xf0, 0, 76}, + {255, 255, 0xf0, 0, 74}, + {255, 255, 0xf0, 0, 72}, + {255, 255, 0xf0, 0, 70}, + {255, 255, 0xf0, 0, 68}, + {255, 255, 0xf0, 0, 66}, + {255, 255, 0xf0, 0, 64}, + {255, 248, 0xf0, 0, 64}, + {255, 241, 0xf0, 0, 64}, + {255, 251, 0xe0, 0, 64}, + {255, 244, 0xe0, 0, 64}, + {255, 254, 0xd0, 0, 64}, + {255, 246, 0xd0, 0, 64}, + {255, 239, 0xd0, 0, 64}, + {255, 249, 0xc0, 0, 64}, + {255, 242, 0xc0, 0, 64}, + {255, 255, 0xb0, 0, 64}, + {255, 248, 0xb0, 0, 64}, + {255, 241, 0xb0, 0, 64}, + {255, 254, 0xa0, 0, 64}, + {255, 246, 0xa0, 0, 64}, + {255, 239, 0xa0, 0, 64}, + {255, 255, 0x90, 0, 64}, + {255, 248, 0x90, 0, 64}, + {255, 241, 0x90, 0, 64}, + {255, 234, 0x90, 0, 64}, + {255, 255, 0x80, 0, 64}, + {255, 248, 0x80, 0, 64}, + {255, 241, 0x80, 0, 64}, + {255, 234, 0x80, 0, 64}, + {255, 255, 0x70, 0, 64}, + {255, 248, 0x70, 0, 64}, + {255, 241, 0x70, 0, 64}, + {255, 234, 0x70, 0, 64}, + {255, 227, 0x70, 0, 64}, + {255, 221, 0x70, 0, 64}, + {255, 215, 0x70, 0, 64}, + {255, 208, 0x70, 0, 64}, + {255, 203, 0x70, 0, 64}, + {255, 197, 0x70, 0, 64}, + {255, 255, 0x60, 0, 64}, + {255, 248, 0x60, 0, 64}, + {255, 241, 0x60, 0, 64}, + {255, 234, 0x60, 0, 64}, + {255, 227, 0x60, 0, 64}, + {255, 221, 0x60, 0, 64}, + {255, 255, 0x50, 0, 64}, + {255, 248, 0x50, 0, 64}, + {255, 241, 0x50, 0, 64}, + {255, 234, 0x50, 0, 64}, + {255, 227, 0x50, 0, 64}, + {255, 221, 0x50, 0, 64}, + {255, 215, 0x50, 0, 64}, + {255, 208, 0x50, 0, 64}, + {255, 255, 0x40, 0, 64}, + {255, 248, 0x40, 0, 64}, + {255, 241, 0x40, 0, 64}, + {255, 234, 0x40, 0, 64}, + {255, 227, 0x40, 0, 64}, + {255, 221, 0x40, 0, 64}, + {255, 215, 0x40, 0, 64}, + {255, 208, 0x40, 0, 64}, + {255, 203, 0x40, 0, 64}, + {255, 197, 0x40, 0, 64}, + {255, 255, 0x30, 0, 64}, + {255, 248, 0x30, 0, 64}, + {255, 241, 0x30, 0, 64}, + {255, 234, 0x30, 0, 64}, + {255, 227, 0x30, 0, 64}, + {255, 221, 0x30, 0, 64}, + {255, 215, 0x30, 0, 64}, + {255, 208, 0x30, 0, 64}, + {255, 203, 0x30, 0, 64}, + {255, 197, 0x30, 0, 64}, + {255, 191, 0x30, 0, 64}, + {255, 186, 0x30, 0, 64}, + {255, 181, 0x30, 0, 64}, + {255, 175, 0x30, 0, 64}, + {255, 255, 0x20, 0, 64}, + {255, 248, 0x20, 0, 64}, + {255, 241, 0x20, 0, 64}, + {255, 234, 0x20, 0, 64}, + {255, 227, 0x20, 0, 64}, + {255, 221, 0x20, 0, 64}, + {255, 215, 0x20, 0, 64}, + {255, 208, 0x20, 0, 64}, + {255, 203, 0x20, 0, 64}, + {255, 197, 0x20, 0, 64}, + {255, 191, 0x20, 0, 64}, + {255, 186, 0x20, 0, 64}, + {255, 181, 0x20, 0, 64}, + {255, 175, 0x20, 0, 64}, + {255, 170, 0x20, 0, 64}, + {255, 166, 0x20, 0, 64}, + {255, 161, 0x20, 0, 64}, + {255, 156, 0x20, 0, 64}, + {255, 152, 0x20, 0, 64}, + {255, 148, 0x20, 0, 64}, + {255, 143, 0x20, 0, 64}, + {255, 139, 0x20, 0, 64}, + {255, 135, 0x20, 0, 64}, + {255, 132, 0x20, 0, 64}, + {255, 255, 0x10, 0, 64}, + {255, 248, 0x10, 0, 64}, +}; diff --git a/drivers/net/wireless/brcm80211/brcmsmac/phy/phytbl_lcn.h b/drivers/net/wireless/brcm80211/brcmsmac/phy/phytbl_lcn.h new file mode 100644 index 0000000..5f75e16 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/phy/phytbl_lcn.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "phy_int.h" + +extern const struct phytbl_info dot11lcnphytbl_rx_gain_info_rev0[]; +extern const u32 dot11lcnphytbl_rx_gain_info_sz_rev0; +extern const struct phytbl_info dot11lcn_sw_ctrl_tbl_info_4313; +extern const struct phytbl_info dot11lcn_sw_ctrl_tbl_info_4313_epa; +extern const struct phytbl_info dot11lcn_sw_ctrl_tbl_info_4313_epa_combo; +extern const struct phytbl_info dot11lcn_sw_ctrl_tbl_info_4313_bt_epa; +extern const struct phytbl_info dot11lcn_sw_ctrl_tbl_info_4313_bt_epa_p250; + +extern const struct phytbl_info dot11lcnphytbl_info_rev0[]; +extern const u32 dot11lcnphytbl_info_sz_rev0; + +extern const struct phytbl_info dot11lcnphytbl_rx_gain_info_2G_rev2[]; +extern const u32 dot11lcnphytbl_rx_gain_info_2G_rev2_sz; + +extern const struct phytbl_info dot11lcnphytbl_rx_gain_info_5G_rev2[]; +extern const u32 dot11lcnphytbl_rx_gain_info_5G_rev2_sz; + +extern const struct phytbl_info dot11lcnphytbl_rx_gain_info_extlna_2G_rev2[]; + +extern const struct phytbl_info dot11lcnphytbl_rx_gain_info_extlna_5G_rev2[]; + +struct lcnphy_tx_gain_tbl_entry { + unsigned char gm; + unsigned char pga; + unsigned char pad; + unsigned char dac; + unsigned char bb_mult; +}; + +extern const struct lcnphy_tx_gain_tbl_entry dot11lcnphy_2GHz_gaintable_rev0[]; + +extern const struct +lcnphy_tx_gain_tbl_entry dot11lcnphy_2GHz_extPA_gaintable_rev0[]; + +extern const struct lcnphy_tx_gain_tbl_entry dot11lcnphy_5GHz_gaintable_rev0[]; diff --git a/drivers/net/wireless/brcm80211/brcmsmac/phy/phytbl_n.c b/drivers/net/wireless/brcm80211/brcmsmac/phy/phytbl_n.c new file mode 100644 index 0000000..dbf50ef --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/phy/phytbl_n.c @@ -0,0 +1,10630 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "phytbl_n.h" + +static const u32 frame_struct_rev0[] = { + 0x08004a04, + 0x00100000, + 0x01000a05, + 0x00100020, + 0x09804506, + 0x00100030, + 0x09804507, + 0x00100030, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x08004a0c, + 0x00100004, + 0x01000a0d, + 0x00100024, + 0x0980450e, + 0x00100034, + 0x0980450f, + 0x00100034, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000a04, + 0x00100000, + 0x11008a05, + 0x00100020, + 0x1980c506, + 0x00100030, + 0x21810506, + 0x00100030, + 0x21810506, + 0x00100030, + 0x01800504, + 0x00100030, + 0x11808505, + 0x00100030, + 0x29814507, + 0x01100030, + 0x00000a04, + 0x00100000, + 0x11008a05, + 0x00100020, + 0x21810506, + 0x00100030, + 0x21810506, + 0x00100030, + 0x29814507, + 0x01100030, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000a0c, + 0x00100008, + 0x11008a0d, + 0x00100028, + 0x1980c50e, + 0x00100038, + 0x2181050e, + 0x00100038, + 0x2181050e, + 0x00100038, + 0x0180050c, + 0x00100038, + 0x1180850d, + 0x00100038, + 0x2981450f, + 0x01100038, + 0x00000a0c, + 0x00100008, + 0x11008a0d, + 0x00100028, + 0x2181050e, + 0x00100038, + 0x2181050e, + 0x00100038, + 0x2981450f, + 0x01100038, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x08004a04, + 0x00100000, + 0x01000a05, + 0x00100020, + 0x1980c506, + 0x00100030, + 0x1980c506, + 0x00100030, + 0x11808504, + 0x00100030, + 0x3981ca05, + 0x00100030, + 0x29814507, + 0x01100030, + 0x00000000, + 0x00000000, + 0x10008a04, + 0x00100000, + 0x3981ca05, + 0x00100030, + 0x1980c506, + 0x00100030, + 0x29814507, + 0x01100030, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x08004a0c, + 0x00100008, + 0x01000a0d, + 0x00100028, + 0x1980c50e, + 0x00100038, + 0x1980c50e, + 0x00100038, + 0x1180850c, + 0x00100038, + 0x3981ca0d, + 0x00100038, + 0x2981450f, + 0x01100038, + 0x00000000, + 0x00000000, + 0x10008a0c, + 0x00100008, + 0x3981ca0d, + 0x00100038, + 0x1980c50e, + 0x00100038, + 0x2981450f, + 0x01100038, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x40021404, + 0x00100000, + 0x02001405, + 0x00100040, + 0x0b004a06, + 0x01900060, + 0x13008a06, + 0x01900060, + 0x13008a06, + 0x01900060, + 0x43020a04, + 0x00100060, + 0x1b00ca05, + 0x00100060, + 0x23010a07, + 0x01500060, + 0x40021404, + 0x00100000, + 0x1a00d405, + 0x00100040, + 0x13008a06, + 0x01900060, + 0x13008a06, + 0x01900060, + 0x23010a07, + 0x01500060, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x4002140c, + 0x00100010, + 0x0200140d, + 0x00100050, + 0x0b004a0e, + 0x01900070, + 0x13008a0e, + 0x01900070, + 0x13008a0e, + 0x01900070, + 0x43020a0c, + 0x00100070, + 0x1b00ca0d, + 0x00100070, + 0x23010a0f, + 0x01500070, + 0x4002140c, + 0x00100010, + 0x1a00d40d, + 0x00100050, + 0x13008a0e, + 0x01900070, + 0x13008a0e, + 0x01900070, + 0x23010a0f, + 0x01500070, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x50029404, + 0x00100000, + 0x32019405, + 0x00100040, + 0x0b004a06, + 0x01900060, + 0x0b004a06, + 0x01900060, + 0x5b02ca04, + 0x00100060, + 0x3b01d405, + 0x00100060, + 0x23010a07, + 0x01500060, + 0x00000000, + 0x00000000, + 0x5802d404, + 0x00100000, + 0x3b01d405, + 0x00100060, + 0x0b004a06, + 0x01900060, + 0x23010a07, + 0x01500060, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x5002940c, + 0x00100010, + 0x3201940d, + 0x00100050, + 0x0b004a0e, + 0x01900070, + 0x0b004a0e, + 0x01900070, + 0x5b02ca0c, + 0x00100070, + 0x3b01d40d, + 0x00100070, + 0x23010a0f, + 0x01500070, + 0x00000000, + 0x00000000, + 0x5802d40c, + 0x00100010, + 0x3b01d40d, + 0x00100070, + 0x0b004a0e, + 0x01900070, + 0x23010a0f, + 0x01500070, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x40021404, + 0x000f4800, + 0x62031405, + 0x00100040, + 0x53028a06, + 0x01900060, + 0x53028a07, + 0x01900060, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x4002140c, + 0x000f4808, + 0x6203140d, + 0x00100048, + 0x53028a0e, + 0x01900068, + 0x53028a0f, + 0x01900068, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000a0c, + 0x00100004, + 0x11008a0d, + 0x00100024, + 0x1980c50e, + 0x00100034, + 0x2181050e, + 0x00100034, + 0x2181050e, + 0x00100034, + 0x0180050c, + 0x00100038, + 0x1180850d, + 0x00100038, + 0x1181850d, + 0x00100038, + 0x2981450f, + 0x01100038, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000a0c, + 0x00100008, + 0x11008a0d, + 0x00100028, + 0x2181050e, + 0x00100038, + 0x2181050e, + 0x00100038, + 0x1181850d, + 0x00100038, + 0x2981450f, + 0x01100038, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x08004a04, + 0x00100000, + 0x01000a05, + 0x00100020, + 0x0180c506, + 0x00100030, + 0x0180c506, + 0x00100030, + 0x2180c50c, + 0x00100030, + 0x49820a0d, + 0x0016a130, + 0x41824a0d, + 0x0016a130, + 0x2981450f, + 0x01100030, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x2000ca0c, + 0x00100000, + 0x49820a0d, + 0x0016a130, + 0x1980c50e, + 0x00100030, + 0x41824a0d, + 0x0016a130, + 0x2981450f, + 0x01100030, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x4002140c, + 0x00100008, + 0x0200140d, + 0x00100048, + 0x0b004a0e, + 0x01900068, + 0x13008a0e, + 0x01900068, + 0x13008a0e, + 0x01900068, + 0x43020a0c, + 0x00100070, + 0x1b00ca0d, + 0x00100070, + 0x1b014a0d, + 0x00100070, + 0x23010a0f, + 0x01500070, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x4002140c, + 0x00100010, + 0x1a00d40d, + 0x00100050, + 0x13008a0e, + 0x01900070, + 0x13008a0e, + 0x01900070, + 0x1b014a0d, + 0x00100070, + 0x23010a0f, + 0x01500070, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x50029404, + 0x00100000, + 0x32019405, + 0x00100040, + 0x03004a06, + 0x01900060, + 0x03004a06, + 0x01900060, + 0x6b030a0c, + 0x00100060, + 0x4b02140d, + 0x0016a160, + 0x4302540d, + 0x0016a160, + 0x23010a0f, + 0x01500060, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x6b03140c, + 0x00100060, + 0x4b02140d, + 0x0016a160, + 0x0b004a0e, + 0x01900060, + 0x4302540d, + 0x0016a160, + 0x23010a0f, + 0x01500060, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x40021404, + 0x00100000, + 0x1a00d405, + 0x00100040, + 0x53028a06, + 0x01900060, + 0x5b02ca06, + 0x01900060, + 0x5b02ca06, + 0x01900060, + 0x43020a04, + 0x00100060, + 0x1b00ca05, + 0x00100060, + 0x53028a07, + 0x0190c060, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x4002140c, + 0x00100010, + 0x1a00d40d, + 0x00100050, + 0x53028a0e, + 0x01900070, + 0x5b02ca0e, + 0x01900070, + 0x5b02ca0e, + 0x01900070, + 0x43020a0c, + 0x00100070, + 0x1b00ca0d, + 0x00100070, + 0x53028a0f, + 0x0190c070, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x40021404, + 0x00100000, + 0x1a00d405, + 0x00100040, + 0x5b02ca06, + 0x01900060, + 0x5b02ca06, + 0x01900060, + 0x53028a07, + 0x0190c060, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x4002140c, + 0x00100010, + 0x1a00d40d, + 0x00100050, + 0x5b02ca0e, + 0x01900070, + 0x5b02ca0e, + 0x01900070, + 0x53028a0f, + 0x0190c070, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u8 frame_lut_rev0[] = { + 0x02, + 0x04, + 0x14, + 0x14, + 0x03, + 0x05, + 0x16, + 0x16, + 0x0a, + 0x0c, + 0x1c, + 0x1c, + 0x0b, + 0x0d, + 0x1e, + 0x1e, + 0x06, + 0x08, + 0x18, + 0x18, + 0x07, + 0x09, + 0x1a, + 0x1a, + 0x0e, + 0x10, + 0x20, + 0x28, + 0x0f, + 0x11, + 0x22, + 0x2a, +}; + +static const u32 tmap_tbl_rev0[] = { + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0xf1111110, + 0x11111111, + 0x11f11111, + 0x00000111, + 0x11000000, + 0x1111f111, + 0x11111111, + 0x111111f1, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x000aa888, + 0x88880000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0xa1111110, + 0x11111111, + 0x11c11111, + 0x00000111, + 0x11000000, + 0x1111a111, + 0x11111111, + 0x111111a1, + 0xa2222220, + 0x22222222, + 0x22c22222, + 0x00000222, + 0x22000000, + 0x2222a222, + 0x22222222, + 0x222222a2, + 0xf1111110, + 0x11111111, + 0x11f11111, + 0x00011111, + 0x11110000, + 0x1111f111, + 0x11111111, + 0x111111f1, + 0xa8aa88a0, + 0xa88888a8, + 0xa8a8a88a, + 0x00088aaa, + 0xaaaa0000, + 0xa8a8aa88, + 0xa88aaaaa, + 0xaaaa8a8a, + 0xaaa8aaa0, + 0x8aaa8aaa, + 0xaa8a8a8a, + 0x000aaa88, + 0x8aaa0000, + 0xaaa8a888, + 0x8aa88a8a, + 0x8a88a888, + 0x08080a00, + 0x0a08080a, + 0x080a0a08, + 0x00080808, + 0x080a0000, + 0x080a0808, + 0x080a0808, + 0x0a0a0a08, + 0xa0a0a0a0, + 0x80a0a080, + 0x8080a0a0, + 0x00008080, + 0x80a00000, + 0x80a080a0, + 0xa080a0a0, + 0x8080a0a0, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x99999000, + 0x9b9b99bb, + 0x9bb99999, + 0x9999b9b9, + 0x9b99bb90, + 0x9bbbbb9b, + 0x9b9b9bb9, + 0x00000999, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00aaa888, + 0x22000000, + 0x2222b222, + 0x22222222, + 0x222222b2, + 0xb2222220, + 0x22222222, + 0x22d22222, + 0x00000222, + 0x11000000, + 0x1111a111, + 0x11111111, + 0x111111a1, + 0xa1111110, + 0x11111111, + 0x11c11111, + 0x00000111, + 0x33000000, + 0x3333b333, + 0x33333333, + 0x333333b3, + 0xb3333330, + 0x33333333, + 0x33d33333, + 0x00000333, + 0x22000000, + 0x2222a222, + 0x22222222, + 0x222222a2, + 0xa2222220, + 0x22222222, + 0x22c22222, + 0x00000222, + 0x99b99b00, + 0x9b9b99bb, + 0x9bb99999, + 0x9999b9b9, + 0x9b99bb99, + 0x9bbbbb9b, + 0x9b9b9bb9, + 0x00000999, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa88, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x08aaa888, + 0x22222200, + 0x2222f222, + 0x22222222, + 0x222222f2, + 0x22222222, + 0x22222222, + 0x22f22222, + 0x00000222, + 0x11000000, + 0x1111f111, + 0x11111111, + 0x11111111, + 0xf1111111, + 0x11111111, + 0x11f11111, + 0x01111111, + 0xbb9bb900, + 0xb9b9bb99, + 0xb99bbbbb, + 0xbbbb9b9b, + 0xb9bb99bb, + 0xb99999b9, + 0xb9b9b99b, + 0x00000bbb, + 0xaa000000, + 0xa8a8aa88, + 0xa88aaaaa, + 0xaaaa8a8a, + 0xa8aa88aa, + 0xa88888a8, + 0xa8a8a88a, + 0x0a888aaa, + 0xaa000000, + 0xa8a8aa88, + 0xa88aaaaa, + 0xaaaa8a8a, + 0xa8aa88a0, + 0xa88888a8, + 0xa8a8a88a, + 0x00000aaa, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0xbbbbbb00, + 0x999bbbbb, + 0x9bb99b9b, + 0xb9b9b9bb, + 0xb9b99bbb, + 0xb9b9b9bb, + 0xb9bb9b99, + 0x00000999, + 0x8a000000, + 0xaa88a888, + 0xa88888aa, + 0xa88a8a88, + 0xa88aa88a, + 0x88a8aaaa, + 0xa8aa8aaa, + 0x0888a88a, + 0x0b0b0b00, + 0x090b0b0b, + 0x0b090b0b, + 0x0909090b, + 0x09090b0b, + 0x09090b0b, + 0x09090b09, + 0x00000909, + 0x0a000000, + 0x0a080808, + 0x080a080a, + 0x080a0a08, + 0x080a080a, + 0x0808080a, + 0x0a0a0a08, + 0x0808080a, + 0xb0b0b000, + 0x9090b0b0, + 0x90b09090, + 0xb0b0b090, + 0xb0b090b0, + 0x90b0b0b0, + 0xb0b09090, + 0x00000090, + 0x80000000, + 0xa080a080, + 0xa08080a0, + 0xa0808080, + 0xa080a080, + 0x80a0a0a0, + 0xa0a080a0, + 0x00a0a0a0, + 0x22000000, + 0x2222f222, + 0x22222222, + 0x222222f2, + 0xf2222220, + 0x22222222, + 0x22f22222, + 0x00000222, + 0x11000000, + 0x1111f111, + 0x11111111, + 0x111111f1, + 0xf1111110, + 0x11111111, + 0x11f11111, + 0x00000111, + 0x33000000, + 0x3333f333, + 0x33333333, + 0x333333f3, + 0xf3333330, + 0x33333333, + 0x33f33333, + 0x00000333, + 0x22000000, + 0x2222f222, + 0x22222222, + 0x222222f2, + 0xf2222220, + 0x22222222, + 0x22f22222, + 0x00000222, + 0x99000000, + 0x9b9b99bb, + 0x9bb99999, + 0x9999b9b9, + 0x9b99bb90, + 0x9bbbbb9b, + 0x9b9b9bb9, + 0x00000999, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x88888000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00aaa888, + 0x88a88a00, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa88, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa88, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x08aaa888, + 0x11000000, + 0x1111a111, + 0x11111111, + 0x111111a1, + 0xa1111110, + 0x11111111, + 0x11c11111, + 0x00000111, + 0x11000000, + 0x1111a111, + 0x11111111, + 0x111111a1, + 0xa1111110, + 0x11111111, + 0x11c11111, + 0x00000111, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u32 tdtrn_tbl_rev0[] = { + 0x061c061c, + 0x0050ee68, + 0xf592fe36, + 0xfe5212f6, + 0x00000c38, + 0xfe5212f6, + 0xf592fe36, + 0x0050ee68, + 0x061c061c, + 0xee680050, + 0xfe36f592, + 0x12f6fe52, + 0x0c380000, + 0x12f6fe52, + 0xfe36f592, + 0xee680050, + 0x061c061c, + 0x0050ee68, + 0xf592fe36, + 0xfe5212f6, + 0x00000c38, + 0xfe5212f6, + 0xf592fe36, + 0x0050ee68, + 0x061c061c, + 0xee680050, + 0xfe36f592, + 0x12f6fe52, + 0x0c380000, + 0x12f6fe52, + 0xfe36f592, + 0xee680050, + 0x05e305e3, + 0x004def0c, + 0xf5f3fe47, + 0xfe611246, + 0x00000bc7, + 0xfe611246, + 0xf5f3fe47, + 0x004def0c, + 0x05e305e3, + 0xef0c004d, + 0xfe47f5f3, + 0x1246fe61, + 0x0bc70000, + 0x1246fe61, + 0xfe47f5f3, + 0xef0c004d, + 0x05e305e3, + 0x004def0c, + 0xf5f3fe47, + 0xfe611246, + 0x00000bc7, + 0xfe611246, + 0xf5f3fe47, + 0x004def0c, + 0x05e305e3, + 0xef0c004d, + 0xfe47f5f3, + 0x1246fe61, + 0x0bc70000, + 0x1246fe61, + 0xfe47f5f3, + 0xef0c004d, + 0xfa58fa58, + 0xf895043b, + 0xff4c09c0, + 0xfbc6ffa8, + 0xfb84f384, + 0x0798f6f9, + 0x05760122, + 0x058409f6, + 0x0b500000, + 0x05b7f542, + 0x08860432, + 0x06ddfee7, + 0xfb84f384, + 0xf9d90664, + 0xf7e8025c, + 0x00fff7bd, + 0x05a805a8, + 0xf7bd00ff, + 0x025cf7e8, + 0x0664f9d9, + 0xf384fb84, + 0xfee706dd, + 0x04320886, + 0xf54205b7, + 0x00000b50, + 0x09f60584, + 0x01220576, + 0xf6f90798, + 0xf384fb84, + 0xffa8fbc6, + 0x09c0ff4c, + 0x043bf895, + 0x02d402d4, + 0x07de0270, + 0xfc96079c, + 0xf90afe94, + 0xfe00ff2c, + 0x02d4065d, + 0x092a0096, + 0x0014fbb8, + 0xfd2cfd2c, + 0x076afb3c, + 0x0096f752, + 0xf991fd87, + 0xfb2c0200, + 0xfeb8f960, + 0x08e0fc96, + 0x049802a8, + 0xfd2cfd2c, + 0x02a80498, + 0xfc9608e0, + 0xf960feb8, + 0x0200fb2c, + 0xfd87f991, + 0xf7520096, + 0xfb3c076a, + 0xfd2cfd2c, + 0xfbb80014, + 0x0096092a, + 0x065d02d4, + 0xff2cfe00, + 0xfe94f90a, + 0x079cfc96, + 0x027007de, + 0x02d402d4, + 0x027007de, + 0x079cfc96, + 0xfe94f90a, + 0xff2cfe00, + 0x065d02d4, + 0x0096092a, + 0xfbb80014, + 0xfd2cfd2c, + 0xfb3c076a, + 0xf7520096, + 0xfd87f991, + 0x0200fb2c, + 0xf960feb8, + 0xfc9608e0, + 0x02a80498, + 0xfd2cfd2c, + 0x049802a8, + 0x08e0fc96, + 0xfeb8f960, + 0xfb2c0200, + 0xf991fd87, + 0x0096f752, + 0x076afb3c, + 0xfd2cfd2c, + 0x0014fbb8, + 0x092a0096, + 0x02d4065d, + 0xfe00ff2c, + 0xf90afe94, + 0xfc96079c, + 0x07de0270, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x062a0000, + 0xfefa0759, + 0x08b80908, + 0xf396fc2d, + 0xf9d6045c, + 0xfc4ef608, + 0xf748f596, + 0x07b207bf, + 0x062a062a, + 0xf84ef841, + 0xf748f596, + 0x03b209f8, + 0xf9d6045c, + 0x0c6a03d3, + 0x08b80908, + 0x0106f8a7, + 0x062a0000, + 0xfefaf8a7, + 0x08b8f6f8, + 0xf39603d3, + 0xf9d6fba4, + 0xfc4e09f8, + 0xf7480a6a, + 0x07b2f841, + 0x062af9d6, + 0xf84e07bf, + 0xf7480a6a, + 0x03b2f608, + 0xf9d6fba4, + 0x0c6afc2d, + 0x08b8f6f8, + 0x01060759, + 0x062a0000, + 0xfefa0759, + 0x08b80908, + 0xf396fc2d, + 0xf9d6045c, + 0xfc4ef608, + 0xf748f596, + 0x07b207bf, + 0x062a062a, + 0xf84ef841, + 0xf748f596, + 0x03b209f8, + 0xf9d6045c, + 0x0c6a03d3, + 0x08b80908, + 0x0106f8a7, + 0x062a0000, + 0xfefaf8a7, + 0x08b8f6f8, + 0xf39603d3, + 0xf9d6fba4, + 0xfc4e09f8, + 0xf7480a6a, + 0x07b2f841, + 0x062af9d6, + 0xf84e07bf, + 0xf7480a6a, + 0x03b2f608, + 0xf9d6fba4, + 0x0c6afc2d, + 0x08b8f6f8, + 0x01060759, + 0x061c061c, + 0xff30009d, + 0xffb21141, + 0xfd87fb54, + 0xf65dfe59, + 0x02eef99e, + 0x0166f03c, + 0xfff809b6, + 0x000008a4, + 0x000af42b, + 0x00eff577, + 0xfa840bf2, + 0xfc02ff51, + 0x08260f67, + 0xfff0036f, + 0x0842f9c3, + 0x00000000, + 0x063df7be, + 0xfc910010, + 0xf099f7da, + 0x00af03fe, + 0xf40e057c, + 0x0a89ff11, + 0x0bd5fff6, + 0xf75c0000, + 0xf64a0008, + 0x0fc4fe9a, + 0x0662fd12, + 0x01a709a3, + 0x04ac0279, + 0xeebf004e, + 0xff6300d0, + 0xf9e4f9e4, + 0x00d0ff63, + 0x004eeebf, + 0x027904ac, + 0x09a301a7, + 0xfd120662, + 0xfe9a0fc4, + 0x0008f64a, + 0x0000f75c, + 0xfff60bd5, + 0xff110a89, + 0x057cf40e, + 0x03fe00af, + 0xf7daf099, + 0x0010fc91, + 0xf7be063d, + 0x00000000, + 0xf9c30842, + 0x036ffff0, + 0x0f670826, + 0xff51fc02, + 0x0bf2fa84, + 0xf57700ef, + 0xf42b000a, + 0x08a40000, + 0x09b6fff8, + 0xf03c0166, + 0xf99e02ee, + 0xfe59f65d, + 0xfb54fd87, + 0x1141ffb2, + 0x009dff30, + 0x05e30000, + 0xff060705, + 0x085408a0, + 0xf425fc59, + 0xfa1d042a, + 0xfc78f67a, + 0xf7acf60e, + 0x075a0766, + 0x05e305e3, + 0xf8a6f89a, + 0xf7acf60e, + 0x03880986, + 0xfa1d042a, + 0x0bdb03a7, + 0x085408a0, + 0x00faf8fb, + 0x05e30000, + 0xff06f8fb, + 0x0854f760, + 0xf42503a7, + 0xfa1dfbd6, + 0xfc780986, + 0xf7ac09f2, + 0x075af89a, + 0x05e3fa1d, + 0xf8a60766, + 0xf7ac09f2, + 0x0388f67a, + 0xfa1dfbd6, + 0x0bdbfc59, + 0x0854f760, + 0x00fa0705, + 0x05e30000, + 0xff060705, + 0x085408a0, + 0xf425fc59, + 0xfa1d042a, + 0xfc78f67a, + 0xf7acf60e, + 0x075a0766, + 0x05e305e3, + 0xf8a6f89a, + 0xf7acf60e, + 0x03880986, + 0xfa1d042a, + 0x0bdb03a7, + 0x085408a0, + 0x00faf8fb, + 0x05e30000, + 0xff06f8fb, + 0x0854f760, + 0xf42503a7, + 0xfa1dfbd6, + 0xfc780986, + 0xf7ac09f2, + 0x075af89a, + 0x05e3fa1d, + 0xf8a60766, + 0xf7ac09f2, + 0x0388f67a, + 0xfa1dfbd6, + 0x0bdbfc59, + 0x0854f760, + 0x00fa0705, + 0xfa58fa58, + 0xf8f0fe00, + 0x0448073d, + 0xfdc9fe46, + 0xf9910258, + 0x089d0407, + 0xfd5cf71a, + 0x02affde0, + 0x083e0496, + 0xff5a0740, + 0xff7afd97, + 0x00fe01f1, + 0x0009082e, + 0xfa94ff75, + 0xfecdf8ea, + 0xffb0f693, + 0xfd2cfa58, + 0x0433ff16, + 0xfba405dd, + 0xfa610341, + 0x06a606cb, + 0x0039fd2d, + 0x0677fa97, + 0x01fa05e0, + 0xf896003e, + 0x075a068b, + 0x012cfc3e, + 0xfa23f98d, + 0xfc7cfd43, + 0xff90fc0d, + 0x01c10982, + 0x00c601d6, + 0xfd2cfd2c, + 0x01d600c6, + 0x098201c1, + 0xfc0dff90, + 0xfd43fc7c, + 0xf98dfa23, + 0xfc3e012c, + 0x068b075a, + 0x003ef896, + 0x05e001fa, + 0xfa970677, + 0xfd2d0039, + 0x06cb06a6, + 0x0341fa61, + 0x05ddfba4, + 0xff160433, + 0xfa58fd2c, + 0xf693ffb0, + 0xf8eafecd, + 0xff75fa94, + 0x082e0009, + 0x01f100fe, + 0xfd97ff7a, + 0x0740ff5a, + 0x0496083e, + 0xfde002af, + 0xf71afd5c, + 0x0407089d, + 0x0258f991, + 0xfe46fdc9, + 0x073d0448, + 0xfe00f8f0, + 0xfd2cfd2c, + 0xfce00500, + 0xfc09fddc, + 0xfe680157, + 0x04c70571, + 0xfc3aff21, + 0xfcd70228, + 0x056d0277, + 0x0200fe00, + 0x0022f927, + 0xfe3c032b, + 0xfc44ff3c, + 0x03e9fbdb, + 0x04570313, + 0x04c9ff5c, + 0x000d03b8, + 0xfa580000, + 0xfbe900d2, + 0xf9d0fe0b, + 0x0125fdf9, + 0x042501bf, + 0x0328fa2b, + 0xffa902f0, + 0xfa250157, + 0x0200fe00, + 0x03740438, + 0xff0405fd, + 0x030cfe52, + 0x0037fb39, + 0xff6904c5, + 0x04f8fd23, + 0xfd31fc1b, + 0xfd2cfd2c, + 0xfc1bfd31, + 0xfd2304f8, + 0x04c5ff69, + 0xfb390037, + 0xfe52030c, + 0x05fdff04, + 0x04380374, + 0xfe000200, + 0x0157fa25, + 0x02f0ffa9, + 0xfa2b0328, + 0x01bf0425, + 0xfdf90125, + 0xfe0bf9d0, + 0x00d2fbe9, + 0x0000fa58, + 0x03b8000d, + 0xff5c04c9, + 0x03130457, + 0xfbdb03e9, + 0xff3cfc44, + 0x032bfe3c, + 0xf9270022, + 0xfe000200, + 0x0277056d, + 0x0228fcd7, + 0xff21fc3a, + 0x057104c7, + 0x0157fe68, + 0xfddcfc09, + 0x0500fce0, + 0xfd2cfd2c, + 0x0500fce0, + 0xfddcfc09, + 0x0157fe68, + 0x057104c7, + 0xff21fc3a, + 0x0228fcd7, + 0x0277056d, + 0xfe000200, + 0xf9270022, + 0x032bfe3c, + 0xff3cfc44, + 0xfbdb03e9, + 0x03130457, + 0xff5c04c9, + 0x03b8000d, + 0x0000fa58, + 0x00d2fbe9, + 0xfe0bf9d0, + 0xfdf90125, + 0x01bf0425, + 0xfa2b0328, + 0x02f0ffa9, + 0x0157fa25, + 0xfe000200, + 0x04380374, + 0x05fdff04, + 0xfe52030c, + 0xfb390037, + 0x04c5ff69, + 0xfd2304f8, + 0xfc1bfd31, + 0xfd2cfd2c, + 0xfd31fc1b, + 0x04f8fd23, + 0xff6904c5, + 0x0037fb39, + 0x030cfe52, + 0xff0405fd, + 0x03740438, + 0x0200fe00, + 0xfa250157, + 0xffa902f0, + 0x0328fa2b, + 0x042501bf, + 0x0125fdf9, + 0xf9d0fe0b, + 0xfbe900d2, + 0xfa580000, + 0x000d03b8, + 0x04c9ff5c, + 0x04570313, + 0x03e9fbdb, + 0xfc44ff3c, + 0xfe3c032b, + 0x0022f927, + 0x0200fe00, + 0x056d0277, + 0xfcd70228, + 0xfc3aff21, + 0x04c70571, + 0xfe680157, + 0xfc09fddc, + 0xfce00500, + 0x05a80000, + 0xff1006be, + 0x0800084a, + 0xf49cfc7e, + 0xfa580400, + 0xfc9cf6da, + 0xf800f672, + 0x0710071c, + 0x05a805a8, + 0xf8f0f8e4, + 0xf800f672, + 0x03640926, + 0xfa580400, + 0x0b640382, + 0x0800084a, + 0x00f0f942, + 0x05a80000, + 0xff10f942, + 0x0800f7b6, + 0xf49c0382, + 0xfa58fc00, + 0xfc9c0926, + 0xf800098e, + 0x0710f8e4, + 0x05a8fa58, + 0xf8f0071c, + 0xf800098e, + 0x0364f6da, + 0xfa58fc00, + 0x0b64fc7e, + 0x0800f7b6, + 0x00f006be, + 0x05a80000, + 0xff1006be, + 0x0800084a, + 0xf49cfc7e, + 0xfa580400, + 0xfc9cf6da, + 0xf800f672, + 0x0710071c, + 0x05a805a8, + 0xf8f0f8e4, + 0xf800f672, + 0x03640926, + 0xfa580400, + 0x0b640382, + 0x0800084a, + 0x00f0f942, + 0x05a80000, + 0xff10f942, + 0x0800f7b6, + 0xf49c0382, + 0xfa58fc00, + 0xfc9c0926, + 0xf800098e, + 0x0710f8e4, + 0x05a8fa58, + 0xf8f0071c, + 0xf800098e, + 0x0364f6da, + 0xfa58fc00, + 0x0b64fc7e, + 0x0800f7b6, + 0x00f006be, +}; + +static const u32 intlv_tbl_rev0[] = { + 0x00802070, + 0x0671188d, + 0x0a60192c, + 0x0a300e46, + 0x00c1188d, + 0x080024d2, + 0x00000070, +}; + +static const u16 pilot_tbl_rev0[] = { + 0xff08, + 0xff08, + 0xff08, + 0xff08, + 0xff08, + 0xff08, + 0xff08, + 0xff08, + 0x80d5, + 0x80d5, + 0x80d5, + 0x80d5, + 0x80d5, + 0x80d5, + 0x80d5, + 0x80d5, + 0xff0a, + 0xff82, + 0xffa0, + 0xff28, + 0xffff, + 0xffff, + 0xffff, + 0xffff, + 0xff82, + 0xffa0, + 0xff28, + 0xff0a, + 0xffff, + 0xffff, + 0xffff, + 0xffff, + 0xf83f, + 0xfa1f, + 0xfa97, + 0xfab5, + 0xf2bd, + 0xf0bf, + 0xffff, + 0xffff, + 0xf017, + 0xf815, + 0xf215, + 0xf095, + 0xf035, + 0xf01d, + 0xffff, + 0xffff, + 0xff08, + 0xff02, + 0xff80, + 0xff20, + 0xff08, + 0xff02, + 0xff80, + 0xff20, + 0xf01f, + 0xf817, + 0xfa15, + 0xf295, + 0xf0b5, + 0xf03d, + 0xffff, + 0xffff, + 0xf82a, + 0xfa0a, + 0xfa82, + 0xfaa0, + 0xf2a8, + 0xf0aa, + 0xffff, + 0xffff, + 0xf002, + 0xf800, + 0xf200, + 0xf080, + 0xf020, + 0xf008, + 0xffff, + 0xffff, + 0xf00a, + 0xf802, + 0xfa00, + 0xf280, + 0xf0a0, + 0xf028, + 0xffff, + 0xffff, +}; + +static const u32 pltlut_tbl_rev0[] = { + 0x76540123, + 0x62407351, + 0x76543201, + 0x76540213, + 0x76540123, + 0x76430521, +}; + +static const u32 tdi_tbl20_ant0_rev0[] = { + 0x00091226, + 0x000a1429, + 0x000b56ad, + 0x000c58b0, + 0x000d5ab3, + 0x000e9cb6, + 0x000f9eba, + 0x0000c13d, + 0x00020301, + 0x00030504, + 0x00040708, + 0x0005090b, + 0x00064b8e, + 0x00095291, + 0x000a5494, + 0x000b9718, + 0x000c9927, + 0x000d9b2a, + 0x000edd2e, + 0x000fdf31, + 0x000101b4, + 0x000243b7, + 0x000345bb, + 0x000447be, + 0x00058982, + 0x00068c05, + 0x00099309, + 0x000a950c, + 0x000bd78f, + 0x000cd992, + 0x000ddb96, + 0x000f1d99, + 0x00005fa8, + 0x0001422c, + 0x0002842f, + 0x00038632, + 0x00048835, + 0x0005ca38, + 0x0006ccbc, + 0x0009d3bf, + 0x000b1603, + 0x000c1806, + 0x000d1a0a, + 0x000e1c0d, + 0x000f5e10, + 0x00008093, + 0x00018297, + 0x0002c49a, + 0x0003c680, + 0x0004c880, + 0x00060b00, + 0x00070d00, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u32 tdi_tbl20_ant1_rev0[] = { + 0x00014b26, + 0x00028d29, + 0x000393ad, + 0x00049630, + 0x0005d833, + 0x0006da36, + 0x00099c3a, + 0x000a9e3d, + 0x000bc081, + 0x000cc284, + 0x000dc488, + 0x000f068b, + 0x0000488e, + 0x00018b91, + 0x0002d214, + 0x0003d418, + 0x0004d6a7, + 0x000618aa, + 0x00071aae, + 0x0009dcb1, + 0x000b1eb4, + 0x000c0137, + 0x000d033b, + 0x000e053e, + 0x000f4702, + 0x00008905, + 0x00020c09, + 0x0003128c, + 0x0004148f, + 0x00051712, + 0x00065916, + 0x00091b19, + 0x000a1d28, + 0x000b5f2c, + 0x000c41af, + 0x000d43b2, + 0x000e85b5, + 0x000f87b8, + 0x0000c9bc, + 0x00024cbf, + 0x00035303, + 0x00045506, + 0x0005978a, + 0x0006998d, + 0x00095b90, + 0x000a5d93, + 0x000b9f97, + 0x000c821a, + 0x000d8400, + 0x000ec600, + 0x000fc800, + 0x00010a00, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u32 tdi_tbl40_ant0_rev0[] = { + 0x0011a346, + 0x00136ccf, + 0x0014f5d9, + 0x001641e2, + 0x0017cb6b, + 0x00195475, + 0x001b2383, + 0x001cad0c, + 0x001e7616, + 0x0000821f, + 0x00020ba8, + 0x0003d4b2, + 0x00056447, + 0x00072dd0, + 0x0008b6da, + 0x000a02e3, + 0x000b8c6c, + 0x000d15f6, + 0x0011e484, + 0x0013ae0d, + 0x00153717, + 0x00168320, + 0x00180ca9, + 0x00199633, + 0x001b6548, + 0x001ceed1, + 0x001eb7db, + 0x0000c3e4, + 0x00024d6d, + 0x000416f7, + 0x0005a585, + 0x00076f0f, + 0x0008f818, + 0x000a4421, + 0x000bcdab, + 0x000d9734, + 0x00122649, + 0x0013efd2, + 0x001578dc, + 0x0016c4e5, + 0x00184e6e, + 0x001a17f8, + 0x001ba686, + 0x001d3010, + 0x001ef999, + 0x00010522, + 0x00028eac, + 0x00045835, + 0x0005e74a, + 0x0007b0d3, + 0x00093a5d, + 0x000a85e6, + 0x000c0f6f, + 0x000dd8f9, + 0x00126787, + 0x00143111, + 0x0015ba9a, + 0x00170623, + 0x00188fad, + 0x001a5936, + 0x001be84b, + 0x001db1d4, + 0x001f3b5e, + 0x000146e7, + 0x00031070, + 0x000499fa, + 0x00062888, + 0x0007f212, + 0x00097b9b, + 0x000ac7a4, + 0x000c50ae, + 0x000e1a37, + 0x0012a94c, + 0x001472d5, + 0x0015fc5f, + 0x00174868, + 0x0018d171, + 0x001a9afb, + 0x001c2989, + 0x001df313, + 0x001f7c9c, + 0x000188a5, + 0x000351af, + 0x0004db38, + 0x0006aa4d, + 0x000833d7, + 0x0009bd60, + 0x000b0969, + 0x000c9273, + 0x000e5bfc, + 0x00132a8a, + 0x0014b414, + 0x00163d9d, + 0x001789a6, + 0x001912b0, + 0x001adc39, + 0x001c6bce, + 0x001e34d8, + 0x001fbe61, + 0x0001ca6a, + 0x00039374, + 0x00051cfd, + 0x0006ec0b, + 0x00087515, + 0x0009fe9e, + 0x000b4aa7, + 0x000cd3b1, + 0x000e9d3a, + 0x00000000, + 0x00000000, +}; + +static const u32 tdi_tbl40_ant1_rev0[] = { + 0x001edb36, + 0x000129ca, + 0x0002b353, + 0x00047cdd, + 0x0005c8e6, + 0x000791ef, + 0x00091bf9, + 0x000aaa07, + 0x000c3391, + 0x000dfd1a, + 0x00120923, + 0x0013d22d, + 0x00155c37, + 0x0016eacb, + 0x00187454, + 0x001a3dde, + 0x001b89e7, + 0x001d12f0, + 0x001f1cfa, + 0x00016b88, + 0x00033492, + 0x0004be1b, + 0x00060a24, + 0x0007d32e, + 0x00095d38, + 0x000aec4c, + 0x000c7555, + 0x000e3edf, + 0x00124ae8, + 0x001413f1, + 0x0015a37b, + 0x00172c89, + 0x0018b593, + 0x001a419c, + 0x001bcb25, + 0x001d942f, + 0x001f63b9, + 0x0001ad4d, + 0x00037657, + 0x0004c260, + 0x00068be9, + 0x000814f3, + 0x0009a47c, + 0x000b2d8a, + 0x000cb694, + 0x000e429d, + 0x00128c26, + 0x001455b0, + 0x0015e4ba, + 0x00176e4e, + 0x0018f758, + 0x001a8361, + 0x001c0cea, + 0x001dd674, + 0x001fa57d, + 0x0001ee8b, + 0x0003b795, + 0x0005039e, + 0x0006cd27, + 0x000856b1, + 0x0009e5c6, + 0x000b6f4f, + 0x000cf859, + 0x000e8462, + 0x00130deb, + 0x00149775, + 0x00162603, + 0x0017af8c, + 0x00193896, + 0x001ac49f, + 0x001c4e28, + 0x001e17b2, + 0x0000a6c7, + 0x00023050, + 0x0003f9da, + 0x00054563, + 0x00070eec, + 0x00089876, + 0x000a2704, + 0x000bb08d, + 0x000d3a17, + 0x001185a0, + 0x00134f29, + 0x0014d8b3, + 0x001667c8, + 0x0017f151, + 0x00197adb, + 0x001b0664, + 0x001c8fed, + 0x001e5977, + 0x0000e805, + 0x0002718f, + 0x00043b18, + 0x000586a1, + 0x0007502b, + 0x0008d9b4, + 0x000a68c9, + 0x000bf252, + 0x000dbbdc, + 0x0011c7e5, + 0x001390ee, + 0x00151a78, + 0x0016a906, + 0x00183290, + 0x0019bc19, + 0x001b4822, + 0x001cd12c, + 0x001e9ab5, + 0x00000000, + 0x00000000, +}; + +static const u16 bdi_tbl_rev0[] = { + 0x0070, + 0x0126, + 0x012c, + 0x0246, + 0x048d, + 0x04d2, +}; + +static const u32 chanest_tbl_rev0[] = { + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, +}; + +static const u8 mcs_tbl_rev0[] = { + 0x00, + 0x08, + 0x0a, + 0x10, + 0x12, + 0x19, + 0x1a, + 0x1c, + 0x40, + 0x48, + 0x4a, + 0x50, + 0x52, + 0x59, + 0x5a, + 0x5c, + 0x80, + 0x88, + 0x8a, + 0x90, + 0x92, + 0x99, + 0x9a, + 0x9c, + 0xc0, + 0xc8, + 0xca, + 0xd0, + 0xd2, + 0xd9, + 0xda, + 0xdc, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + 0x02, + 0x04, + 0x08, + 0x09, + 0x0a, + 0x0c, + 0x10, + 0x11, + 0x12, + 0x14, + 0x18, + 0x19, + 0x1a, + 0x1c, + 0x20, + 0x21, + 0x22, + 0x24, + 0x40, + 0x41, + 0x42, + 0x44, + 0x48, + 0x49, + 0x4a, + 0x4c, + 0x50, + 0x51, + 0x52, + 0x54, + 0x58, + 0x59, + 0x5a, + 0x5c, + 0x60, + 0x61, + 0x62, + 0x64, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; + +static const u32 noise_var_tbl0_rev0[] = { + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, +}; + +static const u32 noise_var_tbl1_rev0[] = { + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, +}; + +static const u8 est_pwr_lut_core0_rev0[] = { + 0x50, + 0x4f, + 0x4e, + 0x4d, + 0x4c, + 0x4b, + 0x4a, + 0x49, + 0x48, + 0x47, + 0x46, + 0x45, + 0x44, + 0x43, + 0x42, + 0x41, + 0x40, + 0x3f, + 0x3e, + 0x3d, + 0x3c, + 0x3b, + 0x3a, + 0x39, + 0x38, + 0x37, + 0x36, + 0x35, + 0x34, + 0x33, + 0x32, + 0x31, + 0x30, + 0x2f, + 0x2e, + 0x2d, + 0x2c, + 0x2b, + 0x2a, + 0x29, + 0x28, + 0x27, + 0x26, + 0x25, + 0x24, + 0x23, + 0x22, + 0x21, + 0x20, + 0x1f, + 0x1e, + 0x1d, + 0x1c, + 0x1b, + 0x1a, + 0x19, + 0x18, + 0x17, + 0x16, + 0x15, + 0x14, + 0x13, + 0x12, + 0x11, +}; + +static const u8 est_pwr_lut_core1_rev0[] = { + 0x50, + 0x4f, + 0x4e, + 0x4d, + 0x4c, + 0x4b, + 0x4a, + 0x49, + 0x48, + 0x47, + 0x46, + 0x45, + 0x44, + 0x43, + 0x42, + 0x41, + 0x40, + 0x3f, + 0x3e, + 0x3d, + 0x3c, + 0x3b, + 0x3a, + 0x39, + 0x38, + 0x37, + 0x36, + 0x35, + 0x34, + 0x33, + 0x32, + 0x31, + 0x30, + 0x2f, + 0x2e, + 0x2d, + 0x2c, + 0x2b, + 0x2a, + 0x29, + 0x28, + 0x27, + 0x26, + 0x25, + 0x24, + 0x23, + 0x22, + 0x21, + 0x20, + 0x1f, + 0x1e, + 0x1d, + 0x1c, + 0x1b, + 0x1a, + 0x19, + 0x18, + 0x17, + 0x16, + 0x15, + 0x14, + 0x13, + 0x12, + 0x11, +}; + +static const u8 adj_pwr_lut_core0_rev0[] = { + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; + +static const u8 adj_pwr_lut_core1_rev0[] = { + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; + +static const u32 gainctrl_lut_core0_rev0[] = { + 0x03cc2b44, + 0x03cc2b42, + 0x03cc2b40, + 0x03cc2b3e, + 0x03cc2b3d, + 0x03cc2b3b, + 0x03c82b44, + 0x03c82b42, + 0x03c82b40, + 0x03c82b3e, + 0x03c82b3d, + 0x03c82b3b, + 0x03c82b39, + 0x03c82b38, + 0x03c82b36, + 0x03c82b34, + 0x03c42b44, + 0x03c42b42, + 0x03c42b40, + 0x03c42b3e, + 0x03c42b3d, + 0x03c42b3b, + 0x03c42b39, + 0x03c42b38, + 0x03c42b36, + 0x03c42b34, + 0x03c42b33, + 0x03c42b32, + 0x03c42b30, + 0x03c42b2f, + 0x03c42b2d, + 0x03c02b44, + 0x03c02b42, + 0x03c02b40, + 0x03c02b3e, + 0x03c02b3d, + 0x03c02b3b, + 0x03c02b39, + 0x03c02b38, + 0x03c02b36, + 0x03c02b34, + 0x03b02b44, + 0x03b02b42, + 0x03b02b40, + 0x03b02b3e, + 0x03b02b3d, + 0x03b02b3b, + 0x03b02b39, + 0x03b02b38, + 0x03b02b36, + 0x03b02b34, + 0x03b02b33, + 0x03b02b32, + 0x03b02b30, + 0x03b02b2f, + 0x03b02b2d, + 0x03a02b44, + 0x03a02b42, + 0x03a02b40, + 0x03a02b3e, + 0x03a02b3d, + 0x03a02b3b, + 0x03a02b39, + 0x03a02b38, + 0x03a02b36, + 0x03a02b34, + 0x03902b44, + 0x03902b42, + 0x03902b40, + 0x03902b3e, + 0x03902b3d, + 0x03902b3b, + 0x03902b39, + 0x03902b38, + 0x03902b36, + 0x03902b34, + 0x03902b33, + 0x03902b32, + 0x03902b30, + 0x03802b44, + 0x03802b42, + 0x03802b40, + 0x03802b3e, + 0x03802b3d, + 0x03802b3b, + 0x03802b39, + 0x03802b38, + 0x03802b36, + 0x03802b34, + 0x03802b33, + 0x03802b32, + 0x03802b30, + 0x03802b2f, + 0x03802b2d, + 0x03802b2c, + 0x03802b2b, + 0x03802b2a, + 0x03802b29, + 0x03802b27, + 0x03802b26, + 0x03802b25, + 0x03802b24, + 0x03802b23, + 0x03802b22, + 0x03802b21, + 0x03802b20, + 0x03802b1f, + 0x03802b1e, + 0x03802b1e, + 0x03802b1d, + 0x03802b1c, + 0x03802b1b, + 0x03802b1a, + 0x03802b1a, + 0x03802b19, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x00002b00, +}; + +static const u32 gainctrl_lut_core1_rev0[] = { + 0x03cc2b44, + 0x03cc2b42, + 0x03cc2b40, + 0x03cc2b3e, + 0x03cc2b3d, + 0x03cc2b3b, + 0x03c82b44, + 0x03c82b42, + 0x03c82b40, + 0x03c82b3e, + 0x03c82b3d, + 0x03c82b3b, + 0x03c82b39, + 0x03c82b38, + 0x03c82b36, + 0x03c82b34, + 0x03c42b44, + 0x03c42b42, + 0x03c42b40, + 0x03c42b3e, + 0x03c42b3d, + 0x03c42b3b, + 0x03c42b39, + 0x03c42b38, + 0x03c42b36, + 0x03c42b34, + 0x03c42b33, + 0x03c42b32, + 0x03c42b30, + 0x03c42b2f, + 0x03c42b2d, + 0x03c02b44, + 0x03c02b42, + 0x03c02b40, + 0x03c02b3e, + 0x03c02b3d, + 0x03c02b3b, + 0x03c02b39, + 0x03c02b38, + 0x03c02b36, + 0x03c02b34, + 0x03b02b44, + 0x03b02b42, + 0x03b02b40, + 0x03b02b3e, + 0x03b02b3d, + 0x03b02b3b, + 0x03b02b39, + 0x03b02b38, + 0x03b02b36, + 0x03b02b34, + 0x03b02b33, + 0x03b02b32, + 0x03b02b30, + 0x03b02b2f, + 0x03b02b2d, + 0x03a02b44, + 0x03a02b42, + 0x03a02b40, + 0x03a02b3e, + 0x03a02b3d, + 0x03a02b3b, + 0x03a02b39, + 0x03a02b38, + 0x03a02b36, + 0x03a02b34, + 0x03902b44, + 0x03902b42, + 0x03902b40, + 0x03902b3e, + 0x03902b3d, + 0x03902b3b, + 0x03902b39, + 0x03902b38, + 0x03902b36, + 0x03902b34, + 0x03902b33, + 0x03902b32, + 0x03902b30, + 0x03802b44, + 0x03802b42, + 0x03802b40, + 0x03802b3e, + 0x03802b3d, + 0x03802b3b, + 0x03802b39, + 0x03802b38, + 0x03802b36, + 0x03802b34, + 0x03802b33, + 0x03802b32, + 0x03802b30, + 0x03802b2f, + 0x03802b2d, + 0x03802b2c, + 0x03802b2b, + 0x03802b2a, + 0x03802b29, + 0x03802b27, + 0x03802b26, + 0x03802b25, + 0x03802b24, + 0x03802b23, + 0x03802b22, + 0x03802b21, + 0x03802b20, + 0x03802b1f, + 0x03802b1e, + 0x03802b1e, + 0x03802b1d, + 0x03802b1c, + 0x03802b1b, + 0x03802b1a, + 0x03802b1a, + 0x03802b19, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x03802b18, + 0x00002b00, +}; + +static const u32 iq_lut_core0_rev0[] = { + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, +}; + +static const u32 iq_lut_core1_rev0[] = { + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, + 0x0000007f, +}; + +static const u16 loft_lut_core0_rev0[] = { + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, +}; + +static const u16 loft_lut_core1_rev0[] = { + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, + 0x0000, + 0x0101, + 0x0002, + 0x0103, +}; + +const struct phytbl_info mimophytbl_info_rev0_volatile[] = { + {&bdi_tbl_rev0, sizeof(bdi_tbl_rev0) / sizeof(bdi_tbl_rev0[0]), 21, 0, + 16} + , + {&pltlut_tbl_rev0, sizeof(pltlut_tbl_rev0) / sizeof(pltlut_tbl_rev0[0]), + 20, 0, 32} + , + {&gainctrl_lut_core0_rev0, + sizeof(gainctrl_lut_core0_rev0) / sizeof(gainctrl_lut_core0_rev0[0]), + 26, 192, 32} + , + {&gainctrl_lut_core1_rev0, + sizeof(gainctrl_lut_core1_rev0) / sizeof(gainctrl_lut_core1_rev0[0]), + 27, 192, 32} + , + + {&est_pwr_lut_core0_rev0, + sizeof(est_pwr_lut_core0_rev0) / sizeof(est_pwr_lut_core0_rev0[0]), 26, + 0, 8} + , + {&est_pwr_lut_core1_rev0, + sizeof(est_pwr_lut_core1_rev0) / sizeof(est_pwr_lut_core1_rev0[0]), 27, + 0, 8} + , + {&adj_pwr_lut_core0_rev0, + sizeof(adj_pwr_lut_core0_rev0) / sizeof(adj_pwr_lut_core0_rev0[0]), 26, + 64, 8} + , + {&adj_pwr_lut_core1_rev0, + sizeof(adj_pwr_lut_core1_rev0) / sizeof(adj_pwr_lut_core1_rev0[0]), 27, + 64, 8} + , + {&iq_lut_core0_rev0, + sizeof(iq_lut_core0_rev0) / sizeof(iq_lut_core0_rev0[0]), 26, 320, 32} + , + {&iq_lut_core1_rev0, + sizeof(iq_lut_core1_rev0) / sizeof(iq_lut_core1_rev0[0]), 27, 320, 32} + , + {&loft_lut_core0_rev0, + sizeof(loft_lut_core0_rev0) / sizeof(loft_lut_core0_rev0[0]), 26, 448, + 16} + , + {&loft_lut_core1_rev0, + sizeof(loft_lut_core1_rev0) / sizeof(loft_lut_core1_rev0[0]), 27, 448, + 16} + , +}; + +const struct phytbl_info mimophytbl_info_rev0[] = { + {&frame_struct_rev0, + sizeof(frame_struct_rev0) / sizeof(frame_struct_rev0[0]), 10, 0, 32} + , + {&frame_lut_rev0, sizeof(frame_lut_rev0) / sizeof(frame_lut_rev0[0]), + 24, 0, 8} + , + {&tmap_tbl_rev0, sizeof(tmap_tbl_rev0) / sizeof(tmap_tbl_rev0[0]), 12, + 0, 32} + , + {&tdtrn_tbl_rev0, sizeof(tdtrn_tbl_rev0) / sizeof(tdtrn_tbl_rev0[0]), + 14, 0, 32} + , + {&intlv_tbl_rev0, sizeof(intlv_tbl_rev0) / sizeof(intlv_tbl_rev0[0]), + 13, 0, 32} + , + {&pilot_tbl_rev0, sizeof(pilot_tbl_rev0) / sizeof(pilot_tbl_rev0[0]), + 11, 0, 16} + , + {&tdi_tbl20_ant0_rev0, + sizeof(tdi_tbl20_ant0_rev0) / sizeof(tdi_tbl20_ant0_rev0[0]), 19, 128, + 32} + , + {&tdi_tbl20_ant1_rev0, + sizeof(tdi_tbl20_ant1_rev0) / sizeof(tdi_tbl20_ant1_rev0[0]), 19, 256, + 32} + , + {&tdi_tbl40_ant0_rev0, + sizeof(tdi_tbl40_ant0_rev0) / sizeof(tdi_tbl40_ant0_rev0[0]), 19, 640, + 32} + , + {&tdi_tbl40_ant1_rev0, + sizeof(tdi_tbl40_ant1_rev0) / sizeof(tdi_tbl40_ant1_rev0[0]), 19, 768, + 32} + , + {&chanest_tbl_rev0, + sizeof(chanest_tbl_rev0) / sizeof(chanest_tbl_rev0[0]), 22, 0, 32} + , + {&mcs_tbl_rev0, sizeof(mcs_tbl_rev0) / sizeof(mcs_tbl_rev0[0]), 18, 0, + 8} + , + {&noise_var_tbl0_rev0, + sizeof(noise_var_tbl0_rev0) / sizeof(noise_var_tbl0_rev0[0]), 16, 0, + 32} + , + {&noise_var_tbl1_rev0, + sizeof(noise_var_tbl1_rev0) / sizeof(noise_var_tbl1_rev0[0]), 16, 128, + 32} + , +}; + +const u32 mimophytbl_info_sz_rev0 = + sizeof(mimophytbl_info_rev0) / sizeof(mimophytbl_info_rev0[0]); +const u32 mimophytbl_info_sz_rev0_volatile = + sizeof(mimophytbl_info_rev0_volatile) / + sizeof(mimophytbl_info_rev0_volatile[0]); + +static const u16 ant_swctrl_tbl_rev3[] = { + 0x0082, + 0x0082, + 0x0211, + 0x0222, + 0x0328, + 0x0000, + 0x0000, + 0x0000, + 0x0144, + 0x0000, + 0x0000, + 0x0000, + 0x0188, + 0x0000, + 0x0000, + 0x0000, + 0x0082, + 0x0082, + 0x0211, + 0x0222, + 0x0328, + 0x0000, + 0x0000, + 0x0000, + 0x0144, + 0x0000, + 0x0000, + 0x0000, + 0x0188, + 0x0000, + 0x0000, + 0x0000, +}; + +static const u16 ant_swctrl_tbl_rev3_1[] = { + 0x0022, + 0x0022, + 0x0011, + 0x0022, + 0x0022, + 0x0000, + 0x0000, + 0x0000, + 0x0011, + 0x0000, + 0x0000, + 0x0000, + 0x0022, + 0x0000, + 0x0000, + 0x0000, + 0x0022, + 0x0022, + 0x0011, + 0x0022, + 0x0022, + 0x0000, + 0x0000, + 0x0000, + 0x0011, + 0x0000, + 0x0000, + 0x0000, + 0x0022, + 0x0000, + 0x0000, + 0x0000, +}; + +static const u16 ant_swctrl_tbl_rev3_2[] = { + 0x0088, + 0x0088, + 0x0044, + 0x0088, + 0x0088, + 0x0000, + 0x0000, + 0x0000, + 0x0044, + 0x0000, + 0x0000, + 0x0000, + 0x0088, + 0x0000, + 0x0000, + 0x0000, + 0x0088, + 0x0088, + 0x0044, + 0x0088, + 0x0088, + 0x0000, + 0x0000, + 0x0000, + 0x0044, + 0x0000, + 0x0000, + 0x0000, + 0x0088, + 0x0000, + 0x0000, + 0x0000, +}; + +static const u16 ant_swctrl_tbl_rev3_3[] = { + 0x022, + 0x022, + 0x011, + 0x022, + 0x000, + 0x000, + 0x000, + 0x000, + 0x011, + 0x000, + 0x000, + 0x000, + 0x022, + 0x000, + 0x000, + 0x3cc, + 0x022, + 0x022, + 0x011, + 0x022, + 0x000, + 0x000, + 0x000, + 0x000, + 0x011, + 0x000, + 0x000, + 0x000, + 0x022, + 0x000, + 0x000, + 0x3cc +}; + +static const u32 frame_struct_rev3[] = { + 0x08004a04, + 0x00100000, + 0x01000a05, + 0x00100020, + 0x09804506, + 0x00100030, + 0x09804507, + 0x00100030, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x08004a0c, + 0x00100004, + 0x01000a0d, + 0x00100024, + 0x0980450e, + 0x00100034, + 0x0980450f, + 0x00100034, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000a04, + 0x00100000, + 0x11008a05, + 0x00100020, + 0x1980c506, + 0x00100030, + 0x21810506, + 0x00100030, + 0x21810506, + 0x00100030, + 0x01800504, + 0x00100030, + 0x11808505, + 0x00100030, + 0x29814507, + 0x01100030, + 0x00000a04, + 0x00100000, + 0x11008a05, + 0x00100020, + 0x21810506, + 0x00100030, + 0x21810506, + 0x00100030, + 0x29814507, + 0x01100030, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000a0c, + 0x00100008, + 0x11008a0d, + 0x00100028, + 0x1980c50e, + 0x00100038, + 0x2181050e, + 0x00100038, + 0x2181050e, + 0x00100038, + 0x0180050c, + 0x00100038, + 0x1180850d, + 0x00100038, + 0x2981450f, + 0x01100038, + 0x00000a0c, + 0x00100008, + 0x11008a0d, + 0x00100028, + 0x2181050e, + 0x00100038, + 0x2181050e, + 0x00100038, + 0x2981450f, + 0x01100038, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x08004a04, + 0x00100000, + 0x01000a05, + 0x00100020, + 0x1980c506, + 0x00100030, + 0x1980c506, + 0x00100030, + 0x11808504, + 0x00100030, + 0x3981ca05, + 0x00100030, + 0x29814507, + 0x01100030, + 0x00000000, + 0x00000000, + 0x10008a04, + 0x00100000, + 0x3981ca05, + 0x00100030, + 0x1980c506, + 0x00100030, + 0x29814507, + 0x01100030, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x08004a0c, + 0x00100008, + 0x01000a0d, + 0x00100028, + 0x1980c50e, + 0x00100038, + 0x1980c50e, + 0x00100038, + 0x1180850c, + 0x00100038, + 0x3981ca0d, + 0x00100038, + 0x2981450f, + 0x01100038, + 0x00000000, + 0x00000000, + 0x10008a0c, + 0x00100008, + 0x3981ca0d, + 0x00100038, + 0x1980c50e, + 0x00100038, + 0x2981450f, + 0x01100038, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x40021404, + 0x00100000, + 0x02001405, + 0x00100040, + 0x0b004a06, + 0x01900060, + 0x13008a06, + 0x01900060, + 0x13008a06, + 0x01900060, + 0x43020a04, + 0x00100060, + 0x1b00ca05, + 0x00100060, + 0x23010a07, + 0x01500060, + 0x40021404, + 0x00100000, + 0x1a00d405, + 0x00100040, + 0x13008a06, + 0x01900060, + 0x13008a06, + 0x01900060, + 0x23010a07, + 0x01500060, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x4002140c, + 0x00100010, + 0x0200140d, + 0x00100050, + 0x0b004a0e, + 0x01900070, + 0x13008a0e, + 0x01900070, + 0x13008a0e, + 0x01900070, + 0x43020a0c, + 0x00100070, + 0x1b00ca0d, + 0x00100070, + 0x23010a0f, + 0x01500070, + 0x4002140c, + 0x00100010, + 0x1a00d40d, + 0x00100050, + 0x13008a0e, + 0x01900070, + 0x13008a0e, + 0x01900070, + 0x23010a0f, + 0x01500070, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x50029404, + 0x00100000, + 0x32019405, + 0x00100040, + 0x0b004a06, + 0x01900060, + 0x0b004a06, + 0x01900060, + 0x5b02ca04, + 0x00100060, + 0x3b01d405, + 0x00100060, + 0x23010a07, + 0x01500060, + 0x00000000, + 0x00000000, + 0x5802d404, + 0x00100000, + 0x3b01d405, + 0x00100060, + 0x0b004a06, + 0x01900060, + 0x23010a07, + 0x01500060, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x5002940c, + 0x00100010, + 0x3201940d, + 0x00100050, + 0x0b004a0e, + 0x01900070, + 0x0b004a0e, + 0x01900070, + 0x5b02ca0c, + 0x00100070, + 0x3b01d40d, + 0x00100070, + 0x23010a0f, + 0x01500070, + 0x00000000, + 0x00000000, + 0x5802d40c, + 0x00100010, + 0x3b01d40d, + 0x00100070, + 0x0b004a0e, + 0x01900070, + 0x23010a0f, + 0x01500070, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x40021404, + 0x000f4800, + 0x62031405, + 0x00100040, + 0x53028a06, + 0x01900060, + 0x53028a07, + 0x01900060, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x4002140c, + 0x000f4808, + 0x6203140d, + 0x00100048, + 0x53028a0e, + 0x01900068, + 0x53028a0f, + 0x01900068, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000a0c, + 0x00100004, + 0x11008a0d, + 0x00100024, + 0x1980c50e, + 0x00100034, + 0x2181050e, + 0x00100034, + 0x2181050e, + 0x00100034, + 0x0180050c, + 0x00100038, + 0x1180850d, + 0x00100038, + 0x1181850d, + 0x00100038, + 0x2981450f, + 0x01100038, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000a0c, + 0x00100008, + 0x11008a0d, + 0x00100028, + 0x2181050e, + 0x00100038, + 0x2181050e, + 0x00100038, + 0x1181850d, + 0x00100038, + 0x2981450f, + 0x01100038, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x08004a04, + 0x00100000, + 0x01000a05, + 0x00100020, + 0x0180c506, + 0x00100030, + 0x0180c506, + 0x00100030, + 0x2180c50c, + 0x00100030, + 0x49820a0d, + 0x0016a130, + 0x41824a0d, + 0x0016a130, + 0x2981450f, + 0x01100030, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x2000ca0c, + 0x00100000, + 0x49820a0d, + 0x0016a130, + 0x1980c50e, + 0x00100030, + 0x41824a0d, + 0x0016a130, + 0x2981450f, + 0x01100030, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x4002140c, + 0x00100008, + 0x0200140d, + 0x00100048, + 0x0b004a0e, + 0x01900068, + 0x13008a0e, + 0x01900068, + 0x13008a0e, + 0x01900068, + 0x43020a0c, + 0x00100070, + 0x1b00ca0d, + 0x00100070, + 0x1b014a0d, + 0x00100070, + 0x23010a0f, + 0x01500070, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x4002140c, + 0x00100010, + 0x1a00d40d, + 0x00100050, + 0x13008a0e, + 0x01900070, + 0x13008a0e, + 0x01900070, + 0x1b014a0d, + 0x00100070, + 0x23010a0f, + 0x01500070, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x50029404, + 0x00100000, + 0x32019405, + 0x00100040, + 0x03004a06, + 0x01900060, + 0x03004a06, + 0x01900060, + 0x6b030a0c, + 0x00100060, + 0x4b02140d, + 0x0016a160, + 0x4302540d, + 0x0016a160, + 0x23010a0f, + 0x01500060, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x6b03140c, + 0x00100060, + 0x4b02140d, + 0x0016a160, + 0x0b004a0e, + 0x01900060, + 0x4302540d, + 0x0016a160, + 0x23010a0f, + 0x01500060, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x40021404, + 0x00100000, + 0x1a00d405, + 0x00100040, + 0x53028a06, + 0x01900060, + 0x5b02ca06, + 0x01900060, + 0x5b02ca06, + 0x01900060, + 0x43020a04, + 0x00100060, + 0x1b00ca05, + 0x00100060, + 0x53028a07, + 0x0190c060, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x4002140c, + 0x00100010, + 0x1a00d40d, + 0x00100050, + 0x53028a0e, + 0x01900070, + 0x5b02ca0e, + 0x01900070, + 0x5b02ca0e, + 0x01900070, + 0x43020a0c, + 0x00100070, + 0x1b00ca0d, + 0x00100070, + 0x53028a0f, + 0x0190c070, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x40021404, + 0x00100000, + 0x1a00d405, + 0x00100040, + 0x5b02ca06, + 0x01900060, + 0x5b02ca06, + 0x01900060, + 0x53028a07, + 0x0190c060, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x4002140c, + 0x00100010, + 0x1a00d40d, + 0x00100050, + 0x5b02ca0e, + 0x01900070, + 0x5b02ca0e, + 0x01900070, + 0x53028a0f, + 0x0190c070, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u16 pilot_tbl_rev3[] = { + 0xff08, + 0xff08, + 0xff08, + 0xff08, + 0xff08, + 0xff08, + 0xff08, + 0xff08, + 0x80d5, + 0x80d5, + 0x80d5, + 0x80d5, + 0x80d5, + 0x80d5, + 0x80d5, + 0x80d5, + 0xff0a, + 0xff82, + 0xffa0, + 0xff28, + 0xffff, + 0xffff, + 0xffff, + 0xffff, + 0xff82, + 0xffa0, + 0xff28, + 0xff0a, + 0xffff, + 0xffff, + 0xffff, + 0xffff, + 0xf83f, + 0xfa1f, + 0xfa97, + 0xfab5, + 0xf2bd, + 0xf0bf, + 0xffff, + 0xffff, + 0xf017, + 0xf815, + 0xf215, + 0xf095, + 0xf035, + 0xf01d, + 0xffff, + 0xffff, + 0xff08, + 0xff02, + 0xff80, + 0xff20, + 0xff08, + 0xff02, + 0xff80, + 0xff20, + 0xf01f, + 0xf817, + 0xfa15, + 0xf295, + 0xf0b5, + 0xf03d, + 0xffff, + 0xffff, + 0xf82a, + 0xfa0a, + 0xfa82, + 0xfaa0, + 0xf2a8, + 0xf0aa, + 0xffff, + 0xffff, + 0xf002, + 0xf800, + 0xf200, + 0xf080, + 0xf020, + 0xf008, + 0xffff, + 0xffff, + 0xf00a, + 0xf802, + 0xfa00, + 0xf280, + 0xf0a0, + 0xf028, + 0xffff, + 0xffff, +}; + +static const u32 tmap_tbl_rev3[] = { + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0xf1111110, + 0x11111111, + 0x11f11111, + 0x00000111, + 0x11000000, + 0x1111f111, + 0x11111111, + 0x111111f1, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x000aa888, + 0x88880000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0xa1111110, + 0x11111111, + 0x11c11111, + 0x00000111, + 0x11000000, + 0x1111a111, + 0x11111111, + 0x111111a1, + 0xa2222220, + 0x22222222, + 0x22c22222, + 0x00000222, + 0x22000000, + 0x2222a222, + 0x22222222, + 0x222222a2, + 0xf1111110, + 0x11111111, + 0x11f11111, + 0x00011111, + 0x11110000, + 0x1111f111, + 0x11111111, + 0x111111f1, + 0xa8aa88a0, + 0xa88888a8, + 0xa8a8a88a, + 0x00088aaa, + 0xaaaa0000, + 0xa8a8aa88, + 0xa88aaaaa, + 0xaaaa8a8a, + 0xaaa8aaa0, + 0x8aaa8aaa, + 0xaa8a8a8a, + 0x000aaa88, + 0x8aaa0000, + 0xaaa8a888, + 0x8aa88a8a, + 0x8a88a888, + 0x08080a00, + 0x0a08080a, + 0x080a0a08, + 0x00080808, + 0x080a0000, + 0x080a0808, + 0x080a0808, + 0x0a0a0a08, + 0xa0a0a0a0, + 0x80a0a080, + 0x8080a0a0, + 0x00008080, + 0x80a00000, + 0x80a080a0, + 0xa080a0a0, + 0x8080a0a0, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x99999000, + 0x9b9b99bb, + 0x9bb99999, + 0x9999b9b9, + 0x9b99bb90, + 0x9bbbbb9b, + 0x9b9b9bb9, + 0x00000999, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00aaa888, + 0x22000000, + 0x2222b222, + 0x22222222, + 0x222222b2, + 0xb2222220, + 0x22222222, + 0x22d22222, + 0x00000222, + 0x11000000, + 0x1111a111, + 0x11111111, + 0x111111a1, + 0xa1111110, + 0x11111111, + 0x11c11111, + 0x00000111, + 0x33000000, + 0x3333b333, + 0x33333333, + 0x333333b3, + 0xb3333330, + 0x33333333, + 0x33d33333, + 0x00000333, + 0x22000000, + 0x2222a222, + 0x22222222, + 0x222222a2, + 0xa2222220, + 0x22222222, + 0x22c22222, + 0x00000222, + 0x99b99b00, + 0x9b9b99bb, + 0x9bb99999, + 0x9999b9b9, + 0x9b99bb99, + 0x9bbbbb9b, + 0x9b9b9bb9, + 0x00000999, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa88, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x08aaa888, + 0x22222200, + 0x2222f222, + 0x22222222, + 0x222222f2, + 0x22222222, + 0x22222222, + 0x22f22222, + 0x00000222, + 0x11000000, + 0x1111f111, + 0x11111111, + 0x11111111, + 0xf1111111, + 0x11111111, + 0x11f11111, + 0x01111111, + 0xbb9bb900, + 0xb9b9bb99, + 0xb99bbbbb, + 0xbbbb9b9b, + 0xb9bb99bb, + 0xb99999b9, + 0xb9b9b99b, + 0x00000bbb, + 0xaa000000, + 0xa8a8aa88, + 0xa88aaaaa, + 0xaaaa8a8a, + 0xa8aa88aa, + 0xa88888a8, + 0xa8a8a88a, + 0x0a888aaa, + 0xaa000000, + 0xa8a8aa88, + 0xa88aaaaa, + 0xaaaa8a8a, + 0xa8aa88a0, + 0xa88888a8, + 0xa8a8a88a, + 0x00000aaa, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0xbbbbbb00, + 0x999bbbbb, + 0x9bb99b9b, + 0xb9b9b9bb, + 0xb9b99bbb, + 0xb9b9b9bb, + 0xb9bb9b99, + 0x00000999, + 0x8a000000, + 0xaa88a888, + 0xa88888aa, + 0xa88a8a88, + 0xa88aa88a, + 0x88a8aaaa, + 0xa8aa8aaa, + 0x0888a88a, + 0x0b0b0b00, + 0x090b0b0b, + 0x0b090b0b, + 0x0909090b, + 0x09090b0b, + 0x09090b0b, + 0x09090b09, + 0x00000909, + 0x0a000000, + 0x0a080808, + 0x080a080a, + 0x080a0a08, + 0x080a080a, + 0x0808080a, + 0x0a0a0a08, + 0x0808080a, + 0xb0b0b000, + 0x9090b0b0, + 0x90b09090, + 0xb0b0b090, + 0xb0b090b0, + 0x90b0b0b0, + 0xb0b09090, + 0x00000090, + 0x80000000, + 0xa080a080, + 0xa08080a0, + 0xa0808080, + 0xa080a080, + 0x80a0a0a0, + 0xa0a080a0, + 0x00a0a0a0, + 0x22000000, + 0x2222f222, + 0x22222222, + 0x222222f2, + 0xf2222220, + 0x22222222, + 0x22f22222, + 0x00000222, + 0x11000000, + 0x1111f111, + 0x11111111, + 0x111111f1, + 0xf1111110, + 0x11111111, + 0x11f11111, + 0x00000111, + 0x33000000, + 0x3333f333, + 0x33333333, + 0x333333f3, + 0xf3333330, + 0x33333333, + 0x33f33333, + 0x00000333, + 0x22000000, + 0x2222f222, + 0x22222222, + 0x222222f2, + 0xf2222220, + 0x22222222, + 0x22f22222, + 0x00000222, + 0x99000000, + 0x9b9b99bb, + 0x9bb99999, + 0x9999b9b9, + 0x9b99bb90, + 0x9bbbbb9b, + 0x9b9b9bb9, + 0x00000999, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x88888000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00aaa888, + 0x88a88a00, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa88, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa88, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x08aaa888, + 0x11000000, + 0x1111a111, + 0x11111111, + 0x111111a1, + 0xa1111110, + 0x11111111, + 0x11c11111, + 0x00000111, + 0x11000000, + 0x1111a111, + 0x11111111, + 0x111111a1, + 0xa1111110, + 0x11111111, + 0x11c11111, + 0x00000111, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u32 intlv_tbl_rev3[] = { + 0x00802070, + 0x0671188d, + 0x0a60192c, + 0x0a300e46, + 0x00c1188d, + 0x080024d2, + 0x00000070, +}; + +static const u32 tdtrn_tbl_rev3[] = { + 0x061c061c, + 0x0050ee68, + 0xf592fe36, + 0xfe5212f6, + 0x00000c38, + 0xfe5212f6, + 0xf592fe36, + 0x0050ee68, + 0x061c061c, + 0xee680050, + 0xfe36f592, + 0x12f6fe52, + 0x0c380000, + 0x12f6fe52, + 0xfe36f592, + 0xee680050, + 0x061c061c, + 0x0050ee68, + 0xf592fe36, + 0xfe5212f6, + 0x00000c38, + 0xfe5212f6, + 0xf592fe36, + 0x0050ee68, + 0x061c061c, + 0xee680050, + 0xfe36f592, + 0x12f6fe52, + 0x0c380000, + 0x12f6fe52, + 0xfe36f592, + 0xee680050, + 0x05e305e3, + 0x004def0c, + 0xf5f3fe47, + 0xfe611246, + 0x00000bc7, + 0xfe611246, + 0xf5f3fe47, + 0x004def0c, + 0x05e305e3, + 0xef0c004d, + 0xfe47f5f3, + 0x1246fe61, + 0x0bc70000, + 0x1246fe61, + 0xfe47f5f3, + 0xef0c004d, + 0x05e305e3, + 0x004def0c, + 0xf5f3fe47, + 0xfe611246, + 0x00000bc7, + 0xfe611246, + 0xf5f3fe47, + 0x004def0c, + 0x05e305e3, + 0xef0c004d, + 0xfe47f5f3, + 0x1246fe61, + 0x0bc70000, + 0x1246fe61, + 0xfe47f5f3, + 0xef0c004d, + 0xfa58fa58, + 0xf895043b, + 0xff4c09c0, + 0xfbc6ffa8, + 0xfb84f384, + 0x0798f6f9, + 0x05760122, + 0x058409f6, + 0x0b500000, + 0x05b7f542, + 0x08860432, + 0x06ddfee7, + 0xfb84f384, + 0xf9d90664, + 0xf7e8025c, + 0x00fff7bd, + 0x05a805a8, + 0xf7bd00ff, + 0x025cf7e8, + 0x0664f9d9, + 0xf384fb84, + 0xfee706dd, + 0x04320886, + 0xf54205b7, + 0x00000b50, + 0x09f60584, + 0x01220576, + 0xf6f90798, + 0xf384fb84, + 0xffa8fbc6, + 0x09c0ff4c, + 0x043bf895, + 0x02d402d4, + 0x07de0270, + 0xfc96079c, + 0xf90afe94, + 0xfe00ff2c, + 0x02d4065d, + 0x092a0096, + 0x0014fbb8, + 0xfd2cfd2c, + 0x076afb3c, + 0x0096f752, + 0xf991fd87, + 0xfb2c0200, + 0xfeb8f960, + 0x08e0fc96, + 0x049802a8, + 0xfd2cfd2c, + 0x02a80498, + 0xfc9608e0, + 0xf960feb8, + 0x0200fb2c, + 0xfd87f991, + 0xf7520096, + 0xfb3c076a, + 0xfd2cfd2c, + 0xfbb80014, + 0x0096092a, + 0x065d02d4, + 0xff2cfe00, + 0xfe94f90a, + 0x079cfc96, + 0x027007de, + 0x02d402d4, + 0x027007de, + 0x079cfc96, + 0xfe94f90a, + 0xff2cfe00, + 0x065d02d4, + 0x0096092a, + 0xfbb80014, + 0xfd2cfd2c, + 0xfb3c076a, + 0xf7520096, + 0xfd87f991, + 0x0200fb2c, + 0xf960feb8, + 0xfc9608e0, + 0x02a80498, + 0xfd2cfd2c, + 0x049802a8, + 0x08e0fc96, + 0xfeb8f960, + 0xfb2c0200, + 0xf991fd87, + 0x0096f752, + 0x076afb3c, + 0xfd2cfd2c, + 0x0014fbb8, + 0x092a0096, + 0x02d4065d, + 0xfe00ff2c, + 0xf90afe94, + 0xfc96079c, + 0x07de0270, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x062a0000, + 0xfefa0759, + 0x08b80908, + 0xf396fc2d, + 0xf9d6045c, + 0xfc4ef608, + 0xf748f596, + 0x07b207bf, + 0x062a062a, + 0xf84ef841, + 0xf748f596, + 0x03b209f8, + 0xf9d6045c, + 0x0c6a03d3, + 0x08b80908, + 0x0106f8a7, + 0x062a0000, + 0xfefaf8a7, + 0x08b8f6f8, + 0xf39603d3, + 0xf9d6fba4, + 0xfc4e09f8, + 0xf7480a6a, + 0x07b2f841, + 0x062af9d6, + 0xf84e07bf, + 0xf7480a6a, + 0x03b2f608, + 0xf9d6fba4, + 0x0c6afc2d, + 0x08b8f6f8, + 0x01060759, + 0x062a0000, + 0xfefa0759, + 0x08b80908, + 0xf396fc2d, + 0xf9d6045c, + 0xfc4ef608, + 0xf748f596, + 0x07b207bf, + 0x062a062a, + 0xf84ef841, + 0xf748f596, + 0x03b209f8, + 0xf9d6045c, + 0x0c6a03d3, + 0x08b80908, + 0x0106f8a7, + 0x062a0000, + 0xfefaf8a7, + 0x08b8f6f8, + 0xf39603d3, + 0xf9d6fba4, + 0xfc4e09f8, + 0xf7480a6a, + 0x07b2f841, + 0x062af9d6, + 0xf84e07bf, + 0xf7480a6a, + 0x03b2f608, + 0xf9d6fba4, + 0x0c6afc2d, + 0x08b8f6f8, + 0x01060759, + 0x061c061c, + 0xff30009d, + 0xffb21141, + 0xfd87fb54, + 0xf65dfe59, + 0x02eef99e, + 0x0166f03c, + 0xfff809b6, + 0x000008a4, + 0x000af42b, + 0x00eff577, + 0xfa840bf2, + 0xfc02ff51, + 0x08260f67, + 0xfff0036f, + 0x0842f9c3, + 0x00000000, + 0x063df7be, + 0xfc910010, + 0xf099f7da, + 0x00af03fe, + 0xf40e057c, + 0x0a89ff11, + 0x0bd5fff6, + 0xf75c0000, + 0xf64a0008, + 0x0fc4fe9a, + 0x0662fd12, + 0x01a709a3, + 0x04ac0279, + 0xeebf004e, + 0xff6300d0, + 0xf9e4f9e4, + 0x00d0ff63, + 0x004eeebf, + 0x027904ac, + 0x09a301a7, + 0xfd120662, + 0xfe9a0fc4, + 0x0008f64a, + 0x0000f75c, + 0xfff60bd5, + 0xff110a89, + 0x057cf40e, + 0x03fe00af, + 0xf7daf099, + 0x0010fc91, + 0xf7be063d, + 0x00000000, + 0xf9c30842, + 0x036ffff0, + 0x0f670826, + 0xff51fc02, + 0x0bf2fa84, + 0xf57700ef, + 0xf42b000a, + 0x08a40000, + 0x09b6fff8, + 0xf03c0166, + 0xf99e02ee, + 0xfe59f65d, + 0xfb54fd87, + 0x1141ffb2, + 0x009dff30, + 0x05e30000, + 0xff060705, + 0x085408a0, + 0xf425fc59, + 0xfa1d042a, + 0xfc78f67a, + 0xf7acf60e, + 0x075a0766, + 0x05e305e3, + 0xf8a6f89a, + 0xf7acf60e, + 0x03880986, + 0xfa1d042a, + 0x0bdb03a7, + 0x085408a0, + 0x00faf8fb, + 0x05e30000, + 0xff06f8fb, + 0x0854f760, + 0xf42503a7, + 0xfa1dfbd6, + 0xfc780986, + 0xf7ac09f2, + 0x075af89a, + 0x05e3fa1d, + 0xf8a60766, + 0xf7ac09f2, + 0x0388f67a, + 0xfa1dfbd6, + 0x0bdbfc59, + 0x0854f760, + 0x00fa0705, + 0x05e30000, + 0xff060705, + 0x085408a0, + 0xf425fc59, + 0xfa1d042a, + 0xfc78f67a, + 0xf7acf60e, + 0x075a0766, + 0x05e305e3, + 0xf8a6f89a, + 0xf7acf60e, + 0x03880986, + 0xfa1d042a, + 0x0bdb03a7, + 0x085408a0, + 0x00faf8fb, + 0x05e30000, + 0xff06f8fb, + 0x0854f760, + 0xf42503a7, + 0xfa1dfbd6, + 0xfc780986, + 0xf7ac09f2, + 0x075af89a, + 0x05e3fa1d, + 0xf8a60766, + 0xf7ac09f2, + 0x0388f67a, + 0xfa1dfbd6, + 0x0bdbfc59, + 0x0854f760, + 0x00fa0705, + 0xfa58fa58, + 0xf8f0fe00, + 0x0448073d, + 0xfdc9fe46, + 0xf9910258, + 0x089d0407, + 0xfd5cf71a, + 0x02affde0, + 0x083e0496, + 0xff5a0740, + 0xff7afd97, + 0x00fe01f1, + 0x0009082e, + 0xfa94ff75, + 0xfecdf8ea, + 0xffb0f693, + 0xfd2cfa58, + 0x0433ff16, + 0xfba405dd, + 0xfa610341, + 0x06a606cb, + 0x0039fd2d, + 0x0677fa97, + 0x01fa05e0, + 0xf896003e, + 0x075a068b, + 0x012cfc3e, + 0xfa23f98d, + 0xfc7cfd43, + 0xff90fc0d, + 0x01c10982, + 0x00c601d6, + 0xfd2cfd2c, + 0x01d600c6, + 0x098201c1, + 0xfc0dff90, + 0xfd43fc7c, + 0xf98dfa23, + 0xfc3e012c, + 0x068b075a, + 0x003ef896, + 0x05e001fa, + 0xfa970677, + 0xfd2d0039, + 0x06cb06a6, + 0x0341fa61, + 0x05ddfba4, + 0xff160433, + 0xfa58fd2c, + 0xf693ffb0, + 0xf8eafecd, + 0xff75fa94, + 0x082e0009, + 0x01f100fe, + 0xfd97ff7a, + 0x0740ff5a, + 0x0496083e, + 0xfde002af, + 0xf71afd5c, + 0x0407089d, + 0x0258f991, + 0xfe46fdc9, + 0x073d0448, + 0xfe00f8f0, + 0xfd2cfd2c, + 0xfce00500, + 0xfc09fddc, + 0xfe680157, + 0x04c70571, + 0xfc3aff21, + 0xfcd70228, + 0x056d0277, + 0x0200fe00, + 0x0022f927, + 0xfe3c032b, + 0xfc44ff3c, + 0x03e9fbdb, + 0x04570313, + 0x04c9ff5c, + 0x000d03b8, + 0xfa580000, + 0xfbe900d2, + 0xf9d0fe0b, + 0x0125fdf9, + 0x042501bf, + 0x0328fa2b, + 0xffa902f0, + 0xfa250157, + 0x0200fe00, + 0x03740438, + 0xff0405fd, + 0x030cfe52, + 0x0037fb39, + 0xff6904c5, + 0x04f8fd23, + 0xfd31fc1b, + 0xfd2cfd2c, + 0xfc1bfd31, + 0xfd2304f8, + 0x04c5ff69, + 0xfb390037, + 0xfe52030c, + 0x05fdff04, + 0x04380374, + 0xfe000200, + 0x0157fa25, + 0x02f0ffa9, + 0xfa2b0328, + 0x01bf0425, + 0xfdf90125, + 0xfe0bf9d0, + 0x00d2fbe9, + 0x0000fa58, + 0x03b8000d, + 0xff5c04c9, + 0x03130457, + 0xfbdb03e9, + 0xff3cfc44, + 0x032bfe3c, + 0xf9270022, + 0xfe000200, + 0x0277056d, + 0x0228fcd7, + 0xff21fc3a, + 0x057104c7, + 0x0157fe68, + 0xfddcfc09, + 0x0500fce0, + 0xfd2cfd2c, + 0x0500fce0, + 0xfddcfc09, + 0x0157fe68, + 0x057104c7, + 0xff21fc3a, + 0x0228fcd7, + 0x0277056d, + 0xfe000200, + 0xf9270022, + 0x032bfe3c, + 0xff3cfc44, + 0xfbdb03e9, + 0x03130457, + 0xff5c04c9, + 0x03b8000d, + 0x0000fa58, + 0x00d2fbe9, + 0xfe0bf9d0, + 0xfdf90125, + 0x01bf0425, + 0xfa2b0328, + 0x02f0ffa9, + 0x0157fa25, + 0xfe000200, + 0x04380374, + 0x05fdff04, + 0xfe52030c, + 0xfb390037, + 0x04c5ff69, + 0xfd2304f8, + 0xfc1bfd31, + 0xfd2cfd2c, + 0xfd31fc1b, + 0x04f8fd23, + 0xff6904c5, + 0x0037fb39, + 0x030cfe52, + 0xff0405fd, + 0x03740438, + 0x0200fe00, + 0xfa250157, + 0xffa902f0, + 0x0328fa2b, + 0x042501bf, + 0x0125fdf9, + 0xf9d0fe0b, + 0xfbe900d2, + 0xfa580000, + 0x000d03b8, + 0x04c9ff5c, + 0x04570313, + 0x03e9fbdb, + 0xfc44ff3c, + 0xfe3c032b, + 0x0022f927, + 0x0200fe00, + 0x056d0277, + 0xfcd70228, + 0xfc3aff21, + 0x04c70571, + 0xfe680157, + 0xfc09fddc, + 0xfce00500, + 0x05a80000, + 0xff1006be, + 0x0800084a, + 0xf49cfc7e, + 0xfa580400, + 0xfc9cf6da, + 0xf800f672, + 0x0710071c, + 0x05a805a8, + 0xf8f0f8e4, + 0xf800f672, + 0x03640926, + 0xfa580400, + 0x0b640382, + 0x0800084a, + 0x00f0f942, + 0x05a80000, + 0xff10f942, + 0x0800f7b6, + 0xf49c0382, + 0xfa58fc00, + 0xfc9c0926, + 0xf800098e, + 0x0710f8e4, + 0x05a8fa58, + 0xf8f0071c, + 0xf800098e, + 0x0364f6da, + 0xfa58fc00, + 0x0b64fc7e, + 0x0800f7b6, + 0x00f006be, + 0x05a80000, + 0xff1006be, + 0x0800084a, + 0xf49cfc7e, + 0xfa580400, + 0xfc9cf6da, + 0xf800f672, + 0x0710071c, + 0x05a805a8, + 0xf8f0f8e4, + 0xf800f672, + 0x03640926, + 0xfa580400, + 0x0b640382, + 0x0800084a, + 0x00f0f942, + 0x05a80000, + 0xff10f942, + 0x0800f7b6, + 0xf49c0382, + 0xfa58fc00, + 0xfc9c0926, + 0xf800098e, + 0x0710f8e4, + 0x05a8fa58, + 0xf8f0071c, + 0xf800098e, + 0x0364f6da, + 0xfa58fc00, + 0x0b64fc7e, + 0x0800f7b6, + 0x00f006be, +}; + +const u32 noise_var_tbl_rev3[] = { + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, + 0x02110211, + 0x0000014d, +}; + +static const u16 mcs_tbl_rev3[] = { + 0x0000, + 0x0008, + 0x000a, + 0x0010, + 0x0012, + 0x0019, + 0x001a, + 0x001c, + 0x0080, + 0x0088, + 0x008a, + 0x0090, + 0x0092, + 0x0099, + 0x009a, + 0x009c, + 0x0100, + 0x0108, + 0x010a, + 0x0110, + 0x0112, + 0x0119, + 0x011a, + 0x011c, + 0x0180, + 0x0188, + 0x018a, + 0x0190, + 0x0192, + 0x0199, + 0x019a, + 0x019c, + 0x0000, + 0x0098, + 0x00a0, + 0x00a8, + 0x009a, + 0x00a2, + 0x00aa, + 0x0120, + 0x0128, + 0x0128, + 0x0130, + 0x0138, + 0x0138, + 0x0140, + 0x0122, + 0x012a, + 0x012a, + 0x0132, + 0x013a, + 0x013a, + 0x0142, + 0x01a8, + 0x01b0, + 0x01b8, + 0x01b0, + 0x01b8, + 0x01c0, + 0x01c8, + 0x01c0, + 0x01c8, + 0x01d0, + 0x01d0, + 0x01d8, + 0x01aa, + 0x01b2, + 0x01ba, + 0x01b2, + 0x01ba, + 0x01c2, + 0x01ca, + 0x01c2, + 0x01ca, + 0x01d2, + 0x01d2, + 0x01da, + 0x0001, + 0x0002, + 0x0004, + 0x0009, + 0x000c, + 0x0011, + 0x0014, + 0x0018, + 0x0020, + 0x0021, + 0x0022, + 0x0024, + 0x0081, + 0x0082, + 0x0084, + 0x0089, + 0x008c, + 0x0091, + 0x0094, + 0x0098, + 0x00a0, + 0x00a1, + 0x00a2, + 0x00a4, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, + 0x0007, +}; + +static const u32 tdi_tbl20_ant0_rev3[] = { + 0x00091226, + 0x000a1429, + 0x000b56ad, + 0x000c58b0, + 0x000d5ab3, + 0x000e9cb6, + 0x000f9eba, + 0x0000c13d, + 0x00020301, + 0x00030504, + 0x00040708, + 0x0005090b, + 0x00064b8e, + 0x00095291, + 0x000a5494, + 0x000b9718, + 0x000c9927, + 0x000d9b2a, + 0x000edd2e, + 0x000fdf31, + 0x000101b4, + 0x000243b7, + 0x000345bb, + 0x000447be, + 0x00058982, + 0x00068c05, + 0x00099309, + 0x000a950c, + 0x000bd78f, + 0x000cd992, + 0x000ddb96, + 0x000f1d99, + 0x00005fa8, + 0x0001422c, + 0x0002842f, + 0x00038632, + 0x00048835, + 0x0005ca38, + 0x0006ccbc, + 0x0009d3bf, + 0x000b1603, + 0x000c1806, + 0x000d1a0a, + 0x000e1c0d, + 0x000f5e10, + 0x00008093, + 0x00018297, + 0x0002c49a, + 0x0003c680, + 0x0004c880, + 0x00060b00, + 0x00070d00, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u32 tdi_tbl20_ant1_rev3[] = { + 0x00014b26, + 0x00028d29, + 0x000393ad, + 0x00049630, + 0x0005d833, + 0x0006da36, + 0x00099c3a, + 0x000a9e3d, + 0x000bc081, + 0x000cc284, + 0x000dc488, + 0x000f068b, + 0x0000488e, + 0x00018b91, + 0x0002d214, + 0x0003d418, + 0x0004d6a7, + 0x000618aa, + 0x00071aae, + 0x0009dcb1, + 0x000b1eb4, + 0x000c0137, + 0x000d033b, + 0x000e053e, + 0x000f4702, + 0x00008905, + 0x00020c09, + 0x0003128c, + 0x0004148f, + 0x00051712, + 0x00065916, + 0x00091b19, + 0x000a1d28, + 0x000b5f2c, + 0x000c41af, + 0x000d43b2, + 0x000e85b5, + 0x000f87b8, + 0x0000c9bc, + 0x00024cbf, + 0x00035303, + 0x00045506, + 0x0005978a, + 0x0006998d, + 0x00095b90, + 0x000a5d93, + 0x000b9f97, + 0x000c821a, + 0x000d8400, + 0x000ec600, + 0x000fc800, + 0x00010a00, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u32 tdi_tbl40_ant0_rev3[] = { + 0x0011a346, + 0x00136ccf, + 0x0014f5d9, + 0x001641e2, + 0x0017cb6b, + 0x00195475, + 0x001b2383, + 0x001cad0c, + 0x001e7616, + 0x0000821f, + 0x00020ba8, + 0x0003d4b2, + 0x00056447, + 0x00072dd0, + 0x0008b6da, + 0x000a02e3, + 0x000b8c6c, + 0x000d15f6, + 0x0011e484, + 0x0013ae0d, + 0x00153717, + 0x00168320, + 0x00180ca9, + 0x00199633, + 0x001b6548, + 0x001ceed1, + 0x001eb7db, + 0x0000c3e4, + 0x00024d6d, + 0x000416f7, + 0x0005a585, + 0x00076f0f, + 0x0008f818, + 0x000a4421, + 0x000bcdab, + 0x000d9734, + 0x00122649, + 0x0013efd2, + 0x001578dc, + 0x0016c4e5, + 0x00184e6e, + 0x001a17f8, + 0x001ba686, + 0x001d3010, + 0x001ef999, + 0x00010522, + 0x00028eac, + 0x00045835, + 0x0005e74a, + 0x0007b0d3, + 0x00093a5d, + 0x000a85e6, + 0x000c0f6f, + 0x000dd8f9, + 0x00126787, + 0x00143111, + 0x0015ba9a, + 0x00170623, + 0x00188fad, + 0x001a5936, + 0x001be84b, + 0x001db1d4, + 0x001f3b5e, + 0x000146e7, + 0x00031070, + 0x000499fa, + 0x00062888, + 0x0007f212, + 0x00097b9b, + 0x000ac7a4, + 0x000c50ae, + 0x000e1a37, + 0x0012a94c, + 0x001472d5, + 0x0015fc5f, + 0x00174868, + 0x0018d171, + 0x001a9afb, + 0x001c2989, + 0x001df313, + 0x001f7c9c, + 0x000188a5, + 0x000351af, + 0x0004db38, + 0x0006aa4d, + 0x000833d7, + 0x0009bd60, + 0x000b0969, + 0x000c9273, + 0x000e5bfc, + 0x00132a8a, + 0x0014b414, + 0x00163d9d, + 0x001789a6, + 0x001912b0, + 0x001adc39, + 0x001c6bce, + 0x001e34d8, + 0x001fbe61, + 0x0001ca6a, + 0x00039374, + 0x00051cfd, + 0x0006ec0b, + 0x00087515, + 0x0009fe9e, + 0x000b4aa7, + 0x000cd3b1, + 0x000e9d3a, + 0x00000000, + 0x00000000, +}; + +static const u32 tdi_tbl40_ant1_rev3[] = { + 0x001edb36, + 0x000129ca, + 0x0002b353, + 0x00047cdd, + 0x0005c8e6, + 0x000791ef, + 0x00091bf9, + 0x000aaa07, + 0x000c3391, + 0x000dfd1a, + 0x00120923, + 0x0013d22d, + 0x00155c37, + 0x0016eacb, + 0x00187454, + 0x001a3dde, + 0x001b89e7, + 0x001d12f0, + 0x001f1cfa, + 0x00016b88, + 0x00033492, + 0x0004be1b, + 0x00060a24, + 0x0007d32e, + 0x00095d38, + 0x000aec4c, + 0x000c7555, + 0x000e3edf, + 0x00124ae8, + 0x001413f1, + 0x0015a37b, + 0x00172c89, + 0x0018b593, + 0x001a419c, + 0x001bcb25, + 0x001d942f, + 0x001f63b9, + 0x0001ad4d, + 0x00037657, + 0x0004c260, + 0x00068be9, + 0x000814f3, + 0x0009a47c, + 0x000b2d8a, + 0x000cb694, + 0x000e429d, + 0x00128c26, + 0x001455b0, + 0x0015e4ba, + 0x00176e4e, + 0x0018f758, + 0x001a8361, + 0x001c0cea, + 0x001dd674, + 0x001fa57d, + 0x0001ee8b, + 0x0003b795, + 0x0005039e, + 0x0006cd27, + 0x000856b1, + 0x0009e5c6, + 0x000b6f4f, + 0x000cf859, + 0x000e8462, + 0x00130deb, + 0x00149775, + 0x00162603, + 0x0017af8c, + 0x00193896, + 0x001ac49f, + 0x001c4e28, + 0x001e17b2, + 0x0000a6c7, + 0x00023050, + 0x0003f9da, + 0x00054563, + 0x00070eec, + 0x00089876, + 0x000a2704, + 0x000bb08d, + 0x000d3a17, + 0x001185a0, + 0x00134f29, + 0x0014d8b3, + 0x001667c8, + 0x0017f151, + 0x00197adb, + 0x001b0664, + 0x001c8fed, + 0x001e5977, + 0x0000e805, + 0x0002718f, + 0x00043b18, + 0x000586a1, + 0x0007502b, + 0x0008d9b4, + 0x000a68c9, + 0x000bf252, + 0x000dbbdc, + 0x0011c7e5, + 0x001390ee, + 0x00151a78, + 0x0016a906, + 0x00183290, + 0x0019bc19, + 0x001b4822, + 0x001cd12c, + 0x001e9ab5, + 0x00000000, + 0x00000000, +}; + +static const u32 pltlut_tbl_rev3[] = { + 0x76540213, + 0x62407351, + 0x76543210, + 0x76540213, + 0x76540213, + 0x76430521, +}; + +static const u32 chanest_tbl_rev3[] = { + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x44444444, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, + 0x10101010, +}; + +static const u8 frame_lut_rev3[] = { + 0x02, + 0x04, + 0x14, + 0x14, + 0x03, + 0x05, + 0x16, + 0x16, + 0x0a, + 0x0c, + 0x1c, + 0x1c, + 0x0b, + 0x0d, + 0x1e, + 0x1e, + 0x06, + 0x08, + 0x18, + 0x18, + 0x07, + 0x09, + 0x1a, + 0x1a, + 0x0e, + 0x10, + 0x20, + 0x28, + 0x0f, + 0x11, + 0x22, + 0x2a, +}; + +static const u8 est_pwr_lut_core0_rev3[] = { + 0x55, + 0x54, + 0x54, + 0x53, + 0x52, + 0x52, + 0x51, + 0x51, + 0x50, + 0x4f, + 0x4f, + 0x4e, + 0x4e, + 0x4d, + 0x4c, + 0x4c, + 0x4b, + 0x4a, + 0x49, + 0x49, + 0x48, + 0x47, + 0x46, + 0x46, + 0x45, + 0x44, + 0x43, + 0x42, + 0x41, + 0x40, + 0x40, + 0x3f, + 0x3e, + 0x3d, + 0x3c, + 0x3a, + 0x39, + 0x38, + 0x37, + 0x36, + 0x35, + 0x33, + 0x32, + 0x31, + 0x2f, + 0x2e, + 0x2c, + 0x2b, + 0x29, + 0x27, + 0x25, + 0x23, + 0x21, + 0x1f, + 0x1d, + 0x1a, + 0x18, + 0x15, + 0x12, + 0x0e, + 0x0b, + 0x07, + 0x02, + 0xfd, +}; + +static const u8 est_pwr_lut_core1_rev3[] = { + 0x55, + 0x54, + 0x54, + 0x53, + 0x52, + 0x52, + 0x51, + 0x51, + 0x50, + 0x4f, + 0x4f, + 0x4e, + 0x4e, + 0x4d, + 0x4c, + 0x4c, + 0x4b, + 0x4a, + 0x49, + 0x49, + 0x48, + 0x47, + 0x46, + 0x46, + 0x45, + 0x44, + 0x43, + 0x42, + 0x41, + 0x40, + 0x40, + 0x3f, + 0x3e, + 0x3d, + 0x3c, + 0x3a, + 0x39, + 0x38, + 0x37, + 0x36, + 0x35, + 0x33, + 0x32, + 0x31, + 0x2f, + 0x2e, + 0x2c, + 0x2b, + 0x29, + 0x27, + 0x25, + 0x23, + 0x21, + 0x1f, + 0x1d, + 0x1a, + 0x18, + 0x15, + 0x12, + 0x0e, + 0x0b, + 0x07, + 0x02, + 0xfd, +}; + +static const u8 adj_pwr_lut_core0_rev3[] = { + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; + +static const u8 adj_pwr_lut_core1_rev3[] = { + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; + +static const u32 gainctrl_lut_core0_rev3[] = { + 0x5bf70044, + 0x5bf70042, + 0x5bf70040, + 0x5bf7003e, + 0x5bf7003c, + 0x5bf7003b, + 0x5bf70039, + 0x5bf70037, + 0x5bf70036, + 0x5bf70034, + 0x5bf70033, + 0x5bf70031, + 0x5bf70030, + 0x5ba70044, + 0x5ba70042, + 0x5ba70040, + 0x5ba7003e, + 0x5ba7003c, + 0x5ba7003b, + 0x5ba70039, + 0x5ba70037, + 0x5ba70036, + 0x5ba70034, + 0x5ba70033, + 0x5b770044, + 0x5b770042, + 0x5b770040, + 0x5b77003e, + 0x5b77003c, + 0x5b77003b, + 0x5b770039, + 0x5b770037, + 0x5b770036, + 0x5b770034, + 0x5b770033, + 0x5b770031, + 0x5b770030, + 0x5b77002f, + 0x5b77002d, + 0x5b77002c, + 0x5b470044, + 0x5b470042, + 0x5b470040, + 0x5b47003e, + 0x5b47003c, + 0x5b47003b, + 0x5b470039, + 0x5b470037, + 0x5b470036, + 0x5b470034, + 0x5b470033, + 0x5b470031, + 0x5b470030, + 0x5b47002f, + 0x5b47002d, + 0x5b47002c, + 0x5b47002b, + 0x5b47002a, + 0x5b270044, + 0x5b270042, + 0x5b270040, + 0x5b27003e, + 0x5b27003c, + 0x5b27003b, + 0x5b270039, + 0x5b270037, + 0x5b270036, + 0x5b270034, + 0x5b270033, + 0x5b270031, + 0x5b270030, + 0x5b27002f, + 0x5b170044, + 0x5b170042, + 0x5b170040, + 0x5b17003e, + 0x5b17003c, + 0x5b17003b, + 0x5b170039, + 0x5b170037, + 0x5b170036, + 0x5b170034, + 0x5b170033, + 0x5b170031, + 0x5b170030, + 0x5b17002f, + 0x5b17002d, + 0x5b17002c, + 0x5b17002b, + 0x5b17002a, + 0x5b170028, + 0x5b170027, + 0x5b170026, + 0x5b170025, + 0x5b170024, + 0x5b170023, + 0x5b070044, + 0x5b070042, + 0x5b070040, + 0x5b07003e, + 0x5b07003c, + 0x5b07003b, + 0x5b070039, + 0x5b070037, + 0x5b070036, + 0x5b070034, + 0x5b070033, + 0x5b070031, + 0x5b070030, + 0x5b07002f, + 0x5b07002d, + 0x5b07002c, + 0x5b07002b, + 0x5b07002a, + 0x5b070028, + 0x5b070027, + 0x5b070026, + 0x5b070025, + 0x5b070024, + 0x5b070023, + 0x5b070022, + 0x5b070021, + 0x5b070020, + 0x5b07001f, + 0x5b07001e, + 0x5b07001d, + 0x5b07001d, + 0x5b07001c, +}; + +static const u32 gainctrl_lut_core1_rev3[] = { + 0x5bf70044, + 0x5bf70042, + 0x5bf70040, + 0x5bf7003e, + 0x5bf7003c, + 0x5bf7003b, + 0x5bf70039, + 0x5bf70037, + 0x5bf70036, + 0x5bf70034, + 0x5bf70033, + 0x5bf70031, + 0x5bf70030, + 0x5ba70044, + 0x5ba70042, + 0x5ba70040, + 0x5ba7003e, + 0x5ba7003c, + 0x5ba7003b, + 0x5ba70039, + 0x5ba70037, + 0x5ba70036, + 0x5ba70034, + 0x5ba70033, + 0x5b770044, + 0x5b770042, + 0x5b770040, + 0x5b77003e, + 0x5b77003c, + 0x5b77003b, + 0x5b770039, + 0x5b770037, + 0x5b770036, + 0x5b770034, + 0x5b770033, + 0x5b770031, + 0x5b770030, + 0x5b77002f, + 0x5b77002d, + 0x5b77002c, + 0x5b470044, + 0x5b470042, + 0x5b470040, + 0x5b47003e, + 0x5b47003c, + 0x5b47003b, + 0x5b470039, + 0x5b470037, + 0x5b470036, + 0x5b470034, + 0x5b470033, + 0x5b470031, + 0x5b470030, + 0x5b47002f, + 0x5b47002d, + 0x5b47002c, + 0x5b47002b, + 0x5b47002a, + 0x5b270044, + 0x5b270042, + 0x5b270040, + 0x5b27003e, + 0x5b27003c, + 0x5b27003b, + 0x5b270039, + 0x5b270037, + 0x5b270036, + 0x5b270034, + 0x5b270033, + 0x5b270031, + 0x5b270030, + 0x5b27002f, + 0x5b170044, + 0x5b170042, + 0x5b170040, + 0x5b17003e, + 0x5b17003c, + 0x5b17003b, + 0x5b170039, + 0x5b170037, + 0x5b170036, + 0x5b170034, + 0x5b170033, + 0x5b170031, + 0x5b170030, + 0x5b17002f, + 0x5b17002d, + 0x5b17002c, + 0x5b17002b, + 0x5b17002a, + 0x5b170028, + 0x5b170027, + 0x5b170026, + 0x5b170025, + 0x5b170024, + 0x5b170023, + 0x5b070044, + 0x5b070042, + 0x5b070040, + 0x5b07003e, + 0x5b07003c, + 0x5b07003b, + 0x5b070039, + 0x5b070037, + 0x5b070036, + 0x5b070034, + 0x5b070033, + 0x5b070031, + 0x5b070030, + 0x5b07002f, + 0x5b07002d, + 0x5b07002c, + 0x5b07002b, + 0x5b07002a, + 0x5b070028, + 0x5b070027, + 0x5b070026, + 0x5b070025, + 0x5b070024, + 0x5b070023, + 0x5b070022, + 0x5b070021, + 0x5b070020, + 0x5b07001f, + 0x5b07001e, + 0x5b07001d, + 0x5b07001d, + 0x5b07001c, +}; + +static const u32 iq_lut_core0_rev3[] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u32 iq_lut_core1_rev3[] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u16 loft_lut_core0_rev3[] = { + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, +}; + +static const u16 loft_lut_core1_rev3[] = { + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, + 0x0000, +}; + +static const u16 papd_comp_rfpwr_tbl_core0_rev3[] = { + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x002a, + 0x002a, + 0x002a, + 0x002a, + 0x002a, + 0x002a, + 0x002a, + 0x002a, + 0x002a, + 0x002a, + 0x002a, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, +}; + +static const u16 papd_comp_rfpwr_tbl_core1_rev3[] = { + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x0036, + 0x002a, + 0x002a, + 0x002a, + 0x002a, + 0x002a, + 0x002a, + 0x002a, + 0x002a, + 0x002a, + 0x002a, + 0x002a, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x001e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x000e, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01fc, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01ee, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, + 0x01d6, +}; + +static const u32 papd_comp_epsilon_tbl_core0_rev3[] = { + 0x00000000, + 0x00001fa0, + 0x00019f78, + 0x0001df7e, + 0x03fa9f86, + 0x03fd1f90, + 0x03fe5f8a, + 0x03fb1f94, + 0x03fd9fa0, + 0x00009f98, + 0x03fd1fac, + 0x03ff9fa2, + 0x03fe9fae, + 0x00001fae, + 0x03fddfb4, + 0x03ff1fb8, + 0x03ff9fbc, + 0x03ffdfbe, + 0x03fe9fc2, + 0x03fedfc6, + 0x03fedfc6, + 0x03ff9fc8, + 0x03ff5fc6, + 0x03fedfc2, + 0x03ff9fc0, + 0x03ff5fac, + 0x03ff5fac, + 0x03ff9fa2, + 0x03ff9fa6, + 0x03ff9faa, + 0x03ff5fb0, + 0x03ff5fb4, + 0x03ff1fca, + 0x03ff5fce, + 0x03fcdfdc, + 0x03fb4006, + 0x00000030, + 0x03ff808a, + 0x03ff80da, + 0x0000016c, + 0x03ff8318, + 0x03ff063a, + 0x03fd8bd6, + 0x00014ffe, + 0x00034ffe, + 0x00034ffe, + 0x0003cffe, + 0x00040ffe, + 0x00040ffe, + 0x0003cffe, + 0x0003cffe, + 0x00020ffe, + 0x03fe0ffe, + 0x03fdcffe, + 0x03f94ffe, + 0x03f54ffe, + 0x03f44ffe, + 0x03ef8ffe, + 0x03ee0ffe, + 0x03ebcffe, + 0x03e8cffe, + 0x03e74ffe, + 0x03e4cffe, + 0x03e38ffe, +}; + +static const u32 papd_cal_scalars_tbl_core0_rev3[] = { + 0x05af005a, + 0x0571005e, + 0x05040066, + 0x04bd006c, + 0x047d0072, + 0x04430078, + 0x03f70081, + 0x03cb0087, + 0x03870091, + 0x035e0098, + 0x032e00a1, + 0x030300aa, + 0x02d800b4, + 0x02ae00bf, + 0x028900ca, + 0x026400d6, + 0x024100e3, + 0x022200f0, + 0x020200ff, + 0x01e5010e, + 0x01ca011e, + 0x01b0012f, + 0x01990140, + 0x01830153, + 0x016c0168, + 0x0158017d, + 0x01450193, + 0x013301ab, + 0x012101c5, + 0x011101e0, + 0x010201fc, + 0x00f4021a, + 0x00e6011d, + 0x00d9012e, + 0x00cd0140, + 0x00c20153, + 0x00b70167, + 0x00ac017c, + 0x00a30193, + 0x009a01ab, + 0x009101c4, + 0x008901df, + 0x008101fb, + 0x007a0219, + 0x00730239, + 0x006d025b, + 0x0067027e, + 0x006102a4, + 0x005c02cc, + 0x005602f6, + 0x00520323, + 0x004d0353, + 0x00490385, + 0x004503bb, + 0x004103f3, + 0x003d042f, + 0x003a046f, + 0x003704b2, + 0x003404f9, + 0x00310545, + 0x002e0596, + 0x002b05f5, + 0x00290640, + 0x002606a4, +}; + +static const u32 papd_comp_epsilon_tbl_core1_rev3[] = { + 0x00000000, + 0x00001fa0, + 0x00019f78, + 0x0001df7e, + 0x03fa9f86, + 0x03fd1f90, + 0x03fe5f8a, + 0x03fb1f94, + 0x03fd9fa0, + 0x00009f98, + 0x03fd1fac, + 0x03ff9fa2, + 0x03fe9fae, + 0x00001fae, + 0x03fddfb4, + 0x03ff1fb8, + 0x03ff9fbc, + 0x03ffdfbe, + 0x03fe9fc2, + 0x03fedfc6, + 0x03fedfc6, + 0x03ff9fc8, + 0x03ff5fc6, + 0x03fedfc2, + 0x03ff9fc0, + 0x03ff5fac, + 0x03ff5fac, + 0x03ff9fa2, + 0x03ff9fa6, + 0x03ff9faa, + 0x03ff5fb0, + 0x03ff5fb4, + 0x03ff1fca, + 0x03ff5fce, + 0x03fcdfdc, + 0x03fb4006, + 0x00000030, + 0x03ff808a, + 0x03ff80da, + 0x0000016c, + 0x03ff8318, + 0x03ff063a, + 0x03fd8bd6, + 0x00014ffe, + 0x00034ffe, + 0x00034ffe, + 0x0003cffe, + 0x00040ffe, + 0x00040ffe, + 0x0003cffe, + 0x0003cffe, + 0x00020ffe, + 0x03fe0ffe, + 0x03fdcffe, + 0x03f94ffe, + 0x03f54ffe, + 0x03f44ffe, + 0x03ef8ffe, + 0x03ee0ffe, + 0x03ebcffe, + 0x03e8cffe, + 0x03e74ffe, + 0x03e4cffe, + 0x03e38ffe, +}; + +static const u32 papd_cal_scalars_tbl_core1_rev3[] = { + 0x05af005a, + 0x0571005e, + 0x05040066, + 0x04bd006c, + 0x047d0072, + 0x04430078, + 0x03f70081, + 0x03cb0087, + 0x03870091, + 0x035e0098, + 0x032e00a1, + 0x030300aa, + 0x02d800b4, + 0x02ae00bf, + 0x028900ca, + 0x026400d6, + 0x024100e3, + 0x022200f0, + 0x020200ff, + 0x01e5010e, + 0x01ca011e, + 0x01b0012f, + 0x01990140, + 0x01830153, + 0x016c0168, + 0x0158017d, + 0x01450193, + 0x013301ab, + 0x012101c5, + 0x011101e0, + 0x010201fc, + 0x00f4021a, + 0x00e6011d, + 0x00d9012e, + 0x00cd0140, + 0x00c20153, + 0x00b70167, + 0x00ac017c, + 0x00a30193, + 0x009a01ab, + 0x009101c4, + 0x008901df, + 0x008101fb, + 0x007a0219, + 0x00730239, + 0x006d025b, + 0x0067027e, + 0x006102a4, + 0x005c02cc, + 0x005602f6, + 0x00520323, + 0x004d0353, + 0x00490385, + 0x004503bb, + 0x004103f3, + 0x003d042f, + 0x003a046f, + 0x003704b2, + 0x003404f9, + 0x00310545, + 0x002e0596, + 0x002b05f5, + 0x00290640, + 0x002606a4, +}; + +const struct phytbl_info mimophytbl_info_rev3_volatile[] = { + {&ant_swctrl_tbl_rev3, + sizeof(ant_swctrl_tbl_rev3) / sizeof(ant_swctrl_tbl_rev3[0]), 9, 0, 16} + , +}; + +const struct phytbl_info mimophytbl_info_rev3_volatile1[] = { + {&ant_swctrl_tbl_rev3_1, + sizeof(ant_swctrl_tbl_rev3_1) / sizeof(ant_swctrl_tbl_rev3_1[0]), 9, 0, + 16} + , +}; + +const struct phytbl_info mimophytbl_info_rev3_volatile2[] = { + {&ant_swctrl_tbl_rev3_2, + sizeof(ant_swctrl_tbl_rev3_2) / sizeof(ant_swctrl_tbl_rev3_2[0]), 9, 0, + 16} + , +}; + +const struct phytbl_info mimophytbl_info_rev3_volatile3[] = { + {&ant_swctrl_tbl_rev3_3, + sizeof(ant_swctrl_tbl_rev3_3) / sizeof(ant_swctrl_tbl_rev3_3[0]), 9, 0, + 16} + , +}; + +const struct phytbl_info mimophytbl_info_rev3[] = { + {&frame_struct_rev3, + sizeof(frame_struct_rev3) / sizeof(frame_struct_rev3[0]), 10, 0, 32} + , + {&pilot_tbl_rev3, sizeof(pilot_tbl_rev3) / sizeof(pilot_tbl_rev3[0]), + 11, 0, 16} + , + {&tmap_tbl_rev3, sizeof(tmap_tbl_rev3) / sizeof(tmap_tbl_rev3[0]), 12, + 0, 32} + , + {&intlv_tbl_rev3, sizeof(intlv_tbl_rev3) / sizeof(intlv_tbl_rev3[0]), + 13, 0, 32} + , + {&tdtrn_tbl_rev3, sizeof(tdtrn_tbl_rev3) / sizeof(tdtrn_tbl_rev3[0]), + 14, 0, 32} + , + {&noise_var_tbl_rev3, + sizeof(noise_var_tbl_rev3) / sizeof(noise_var_tbl_rev3[0]), 16, 0, 32} + , + {&mcs_tbl_rev3, sizeof(mcs_tbl_rev3) / sizeof(mcs_tbl_rev3[0]), 18, 0, + 16} + , + {&tdi_tbl20_ant0_rev3, + sizeof(tdi_tbl20_ant0_rev3) / sizeof(tdi_tbl20_ant0_rev3[0]), 19, 128, + 32} + , + {&tdi_tbl20_ant1_rev3, + sizeof(tdi_tbl20_ant1_rev3) / sizeof(tdi_tbl20_ant1_rev3[0]), 19, 256, + 32} + , + {&tdi_tbl40_ant0_rev3, + sizeof(tdi_tbl40_ant0_rev3) / sizeof(tdi_tbl40_ant0_rev3[0]), 19, 640, + 32} + , + {&tdi_tbl40_ant1_rev3, + sizeof(tdi_tbl40_ant1_rev3) / sizeof(tdi_tbl40_ant1_rev3[0]), 19, 768, + 32} + , + {&pltlut_tbl_rev3, sizeof(pltlut_tbl_rev3) / sizeof(pltlut_tbl_rev3[0]), + 20, 0, 32} + , + {&chanest_tbl_rev3, + sizeof(chanest_tbl_rev3) / sizeof(chanest_tbl_rev3[0]), 22, 0, 32} + , + {&frame_lut_rev3, sizeof(frame_lut_rev3) / sizeof(frame_lut_rev3[0]), + 24, 0, 8} + , + {&est_pwr_lut_core0_rev3, + sizeof(est_pwr_lut_core0_rev3) / sizeof(est_pwr_lut_core0_rev3[0]), 26, + 0, 8} + , + {&est_pwr_lut_core1_rev3, + sizeof(est_pwr_lut_core1_rev3) / sizeof(est_pwr_lut_core1_rev3[0]), 27, + 0, 8} + , + {&adj_pwr_lut_core0_rev3, + sizeof(adj_pwr_lut_core0_rev3) / sizeof(adj_pwr_lut_core0_rev3[0]), 26, + 64, 8} + , + {&adj_pwr_lut_core1_rev3, + sizeof(adj_pwr_lut_core1_rev3) / sizeof(adj_pwr_lut_core1_rev3[0]), 27, + 64, 8} + , + {&gainctrl_lut_core0_rev3, + sizeof(gainctrl_lut_core0_rev3) / sizeof(gainctrl_lut_core0_rev3[0]), + 26, 192, 32} + , + {&gainctrl_lut_core1_rev3, + sizeof(gainctrl_lut_core1_rev3) / sizeof(gainctrl_lut_core1_rev3[0]), + 27, 192, 32} + , + {&iq_lut_core0_rev3, + sizeof(iq_lut_core0_rev3) / sizeof(iq_lut_core0_rev3[0]), 26, 320, 32} + , + {&iq_lut_core1_rev3, + sizeof(iq_lut_core1_rev3) / sizeof(iq_lut_core1_rev3[0]), 27, 320, 32} + , + {&loft_lut_core0_rev3, + sizeof(loft_lut_core0_rev3) / sizeof(loft_lut_core0_rev3[0]), 26, 448, + 16} + , + {&loft_lut_core1_rev3, + sizeof(loft_lut_core1_rev3) / sizeof(loft_lut_core1_rev3[0]), 27, 448, + 16} +}; + +const u32 mimophytbl_info_sz_rev3 = + sizeof(mimophytbl_info_rev3) / sizeof(mimophytbl_info_rev3[0]); +const u32 mimophytbl_info_sz_rev3_volatile = + sizeof(mimophytbl_info_rev3_volatile) / + sizeof(mimophytbl_info_rev3_volatile[0]); +const u32 mimophytbl_info_sz_rev3_volatile1 = + sizeof(mimophytbl_info_rev3_volatile1) / + sizeof(mimophytbl_info_rev3_volatile1[0]); +const u32 mimophytbl_info_sz_rev3_volatile2 = + sizeof(mimophytbl_info_rev3_volatile2) / + sizeof(mimophytbl_info_rev3_volatile2[0]); +const u32 mimophytbl_info_sz_rev3_volatile3 = + sizeof(mimophytbl_info_rev3_volatile3) / + sizeof(mimophytbl_info_rev3_volatile3[0]); + +static const u32 tmap_tbl_rev7[] = { + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0xf1111110, + 0x11111111, + 0x11f11111, + 0x00000111, + 0x11000000, + 0x1111f111, + 0x11111111, + 0x111111f1, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x000aa888, + 0x88880000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0xa1111110, + 0x11111111, + 0x11c11111, + 0x00000111, + 0x11000000, + 0x1111a111, + 0x11111111, + 0x111111a1, + 0xa2222220, + 0x22222222, + 0x22c22222, + 0x00000222, + 0x22000000, + 0x2222a222, + 0x22222222, + 0x222222a2, + 0xf1111110, + 0x11111111, + 0x11f11111, + 0x00011111, + 0x11110000, + 0x1111f111, + 0x11111111, + 0x111111f1, + 0xa8aa88a0, + 0xa88888a8, + 0xa8a8a88a, + 0x00088aaa, + 0xaaaa0000, + 0xa8a8aa88, + 0xa88aaaaa, + 0xaaaa8a8a, + 0xaaa8aaa0, + 0x8aaa8aaa, + 0xaa8a8a8a, + 0x000aaa88, + 0x8aaa0000, + 0xaaa8a888, + 0x8aa88a8a, + 0x8a88a888, + 0x08080a00, + 0x0a08080a, + 0x080a0a08, + 0x00080808, + 0x080a0000, + 0x080a0808, + 0x080a0808, + 0x0a0a0a08, + 0xa0a0a0a0, + 0x80a0a080, + 0x8080a0a0, + 0x00008080, + 0x80a00000, + 0x80a080a0, + 0xa080a0a0, + 0x8080a0a0, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x99999000, + 0x9b9b99bb, + 0x9bb99999, + 0x9999b9b9, + 0x9b99bb90, + 0x9bbbbb9b, + 0x9b9b9bb9, + 0x00000999, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00aaa888, + 0x22000000, + 0x2222b222, + 0x22222222, + 0x222222b2, + 0xb2222220, + 0x22222222, + 0x22d22222, + 0x00000222, + 0x11000000, + 0x1111a111, + 0x11111111, + 0x111111a1, + 0xa1111110, + 0x11111111, + 0x11c11111, + 0x00000111, + 0x33000000, + 0x3333b333, + 0x33333333, + 0x333333b3, + 0xb3333330, + 0x33333333, + 0x33d33333, + 0x00000333, + 0x22000000, + 0x2222a222, + 0x22222222, + 0x222222a2, + 0xa2222220, + 0x22222222, + 0x22c22222, + 0x00000222, + 0x99b99b00, + 0x9b9b99bb, + 0x9bb99999, + 0x9999b9b9, + 0x9b99bb99, + 0x9bbbbb9b, + 0x9b9b9bb9, + 0x00000999, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa88, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x08aaa888, + 0x22222200, + 0x2222f222, + 0x22222222, + 0x222222f2, + 0x22222222, + 0x22222222, + 0x22f22222, + 0x00000222, + 0x11000000, + 0x1111f111, + 0x11111111, + 0x11111111, + 0xf1111111, + 0x11111111, + 0x11f11111, + 0x01111111, + 0xbb9bb900, + 0xb9b9bb99, + 0xb99bbbbb, + 0xbbbb9b9b, + 0xb9bb99bb, + 0xb99999b9, + 0xb9b9b99b, + 0x00000bbb, + 0xaa000000, + 0xa8a8aa88, + 0xa88aaaaa, + 0xaaaa8a8a, + 0xa8aa88aa, + 0xa88888a8, + 0xa8a8a88a, + 0x0a888aaa, + 0xaa000000, + 0xa8a8aa88, + 0xa88aaaaa, + 0xaaaa8a8a, + 0xa8aa88a0, + 0xa88888a8, + 0xa8a8a88a, + 0x00000aaa, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0xbbbbbb00, + 0x999bbbbb, + 0x9bb99b9b, + 0xb9b9b9bb, + 0xb9b99bbb, + 0xb9b9b9bb, + 0xb9bb9b99, + 0x00000999, + 0x8a000000, + 0xaa88a888, + 0xa88888aa, + 0xa88a8a88, + 0xa88aa88a, + 0x88a8aaaa, + 0xa8aa8aaa, + 0x0888a88a, + 0x0b0b0b00, + 0x090b0b0b, + 0x0b090b0b, + 0x0909090b, + 0x09090b0b, + 0x09090b0b, + 0x09090b09, + 0x00000909, + 0x0a000000, + 0x0a080808, + 0x080a080a, + 0x080a0a08, + 0x080a080a, + 0x0808080a, + 0x0a0a0a08, + 0x0808080a, + 0xb0b0b000, + 0x9090b0b0, + 0x90b09090, + 0xb0b0b090, + 0xb0b090b0, + 0x90b0b0b0, + 0xb0b09090, + 0x00000090, + 0x80000000, + 0xa080a080, + 0xa08080a0, + 0xa0808080, + 0xa080a080, + 0x80a0a0a0, + 0xa0a080a0, + 0x00a0a0a0, + 0x22000000, + 0x2222f222, + 0x22222222, + 0x222222f2, + 0xf2222220, + 0x22222222, + 0x22f22222, + 0x00000222, + 0x11000000, + 0x1111f111, + 0x11111111, + 0x111111f1, + 0xf1111110, + 0x11111111, + 0x11f11111, + 0x00000111, + 0x33000000, + 0x3333f333, + 0x33333333, + 0x333333f3, + 0xf3333330, + 0x33333333, + 0x33f33333, + 0x00000333, + 0x22000000, + 0x2222f222, + 0x22222222, + 0x222222f2, + 0xf2222220, + 0x22222222, + 0x22f22222, + 0x00000222, + 0x99000000, + 0x9b9b99bb, + 0x9bb99999, + 0x9999b9b9, + 0x9b99bb90, + 0x9bbbbb9b, + 0x9b9b9bb9, + 0x00000999, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x88888000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00aaa888, + 0x88a88a00, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa88, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x000aa888, + 0x88880000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa88, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x08aaa888, + 0x11000000, + 0x1111a111, + 0x11111111, + 0x111111a1, + 0xa1111110, + 0x11111111, + 0x11c11111, + 0x00000111, + 0x11000000, + 0x1111a111, + 0x11111111, + 0x111111a1, + 0xa1111110, + 0x11111111, + 0x11c11111, + 0x00000111, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x88000000, + 0x8a8a88aa, + 0x8aa88888, + 0x8888a8a8, + 0x8a88aa80, + 0x8aaaaa8a, + 0x8a8a8aa8, + 0x00000888, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +const u32 noise_var_tbl_rev7[] = { + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, + 0x020c020c, + 0x0000014d, +}; + +static const u32 papd_comp_epsilon_tbl_core0_rev7[] = { + 0x00000000, + 0x00000000, + 0x00016023, + 0x00006028, + 0x00034036, + 0x0003402e, + 0x0007203c, + 0x0006e037, + 0x00070030, + 0x0009401f, + 0x0009a00f, + 0x000b600d, + 0x000c8007, + 0x000ce007, + 0x00101fff, + 0x00121ff9, + 0x0012e004, + 0x0014dffc, + 0x0016dff6, + 0x0018dfe9, + 0x001b3fe5, + 0x001c5fd0, + 0x001ddfc2, + 0x001f1fb6, + 0x00207fa4, + 0x00219f8f, + 0x0022ff7d, + 0x00247f6c, + 0x0024df5b, + 0x00267f4b, + 0x0027df3b, + 0x0029bf3b, + 0x002b5f2f, + 0x002d3f2e, + 0x002f5f2a, + 0x002fff15, + 0x00315f0b, + 0x0032defa, + 0x0033beeb, + 0x0034fed9, + 0x00353ec5, + 0x00361eb0, + 0x00363e9b, + 0x0036be87, + 0x0036be70, + 0x0038fe67, + 0x0044beb2, + 0x00513ef3, + 0x00595f11, + 0x00669f3d, + 0x0078dfdf, + 0x00a143aa, + 0x01642fff, + 0x0162afff, + 0x01620fff, + 0x0160cfff, + 0x015f0fff, + 0x015dafff, + 0x015bcfff, + 0x015bcfff, + 0x015b4fff, + 0x015acfff, + 0x01590fff, + 0x0156cfff, +}; + +static const u32 papd_cal_scalars_tbl_core0_rev7[] = { + 0x0b5e002d, + 0x0ae2002f, + 0x0a3b0032, + 0x09a70035, + 0x09220038, + 0x08ab003b, + 0x081f003f, + 0x07a20043, + 0x07340047, + 0x06d2004b, + 0x067a004f, + 0x06170054, + 0x05bf0059, + 0x0571005e, + 0x051e0064, + 0x04d3006a, + 0x04910070, + 0x044c0077, + 0x040f007e, + 0x03d90085, + 0x03a1008d, + 0x036f0095, + 0x033d009e, + 0x030b00a8, + 0x02e000b2, + 0x02b900bc, + 0x029200c7, + 0x026d00d3, + 0x024900e0, + 0x022900ed, + 0x020a00fb, + 0x01ec010a, + 0x01d20119, + 0x01b7012a, + 0x019e013c, + 0x0188014e, + 0x01720162, + 0x015d0177, + 0x0149018e, + 0x013701a5, + 0x012601be, + 0x011501d8, + 0x010601f4, + 0x00f70212, + 0x00e90231, + 0x00dc0253, + 0x00d00276, + 0x00c4029b, + 0x00b902c3, + 0x00af02ed, + 0x00a50319, + 0x009c0348, + 0x0093037a, + 0x008b03af, + 0x008303e6, + 0x007c0422, + 0x00750460, + 0x006e04a3, + 0x006804e9, + 0x00620533, + 0x005d0582, + 0x005805d6, + 0x0053062e, + 0x004e068c, +}; + +static const u32 papd_comp_epsilon_tbl_core1_rev7[] = { + 0x00000000, + 0x00000000, + 0x00016023, + 0x00006028, + 0x00034036, + 0x0003402e, + 0x0007203c, + 0x0006e037, + 0x00070030, + 0x0009401f, + 0x0009a00f, + 0x000b600d, + 0x000c8007, + 0x000ce007, + 0x00101fff, + 0x00121ff9, + 0x0012e004, + 0x0014dffc, + 0x0016dff6, + 0x0018dfe9, + 0x001b3fe5, + 0x001c5fd0, + 0x001ddfc2, + 0x001f1fb6, + 0x00207fa4, + 0x00219f8f, + 0x0022ff7d, + 0x00247f6c, + 0x0024df5b, + 0x00267f4b, + 0x0027df3b, + 0x0029bf3b, + 0x002b5f2f, + 0x002d3f2e, + 0x002f5f2a, + 0x002fff15, + 0x00315f0b, + 0x0032defa, + 0x0033beeb, + 0x0034fed9, + 0x00353ec5, + 0x00361eb0, + 0x00363e9b, + 0x0036be87, + 0x0036be70, + 0x0038fe67, + 0x0044beb2, + 0x00513ef3, + 0x00595f11, + 0x00669f3d, + 0x0078dfdf, + 0x00a143aa, + 0x01642fff, + 0x0162afff, + 0x01620fff, + 0x0160cfff, + 0x015f0fff, + 0x015dafff, + 0x015bcfff, + 0x015bcfff, + 0x015b4fff, + 0x015acfff, + 0x01590fff, + 0x0156cfff, +}; + +static const u32 papd_cal_scalars_tbl_core1_rev7[] = { + 0x0b5e002d, + 0x0ae2002f, + 0x0a3b0032, + 0x09a70035, + 0x09220038, + 0x08ab003b, + 0x081f003f, + 0x07a20043, + 0x07340047, + 0x06d2004b, + 0x067a004f, + 0x06170054, + 0x05bf0059, + 0x0571005e, + 0x051e0064, + 0x04d3006a, + 0x04910070, + 0x044c0077, + 0x040f007e, + 0x03d90085, + 0x03a1008d, + 0x036f0095, + 0x033d009e, + 0x030b00a8, + 0x02e000b2, + 0x02b900bc, + 0x029200c7, + 0x026d00d3, + 0x024900e0, + 0x022900ed, + 0x020a00fb, + 0x01ec010a, + 0x01d20119, + 0x01b7012a, + 0x019e013c, + 0x0188014e, + 0x01720162, + 0x015d0177, + 0x0149018e, + 0x013701a5, + 0x012601be, + 0x011501d8, + 0x010601f4, + 0x00f70212, + 0x00e90231, + 0x00dc0253, + 0x00d00276, + 0x00c4029b, + 0x00b902c3, + 0x00af02ed, + 0x00a50319, + 0x009c0348, + 0x0093037a, + 0x008b03af, + 0x008303e6, + 0x007c0422, + 0x00750460, + 0x006e04a3, + 0x006804e9, + 0x00620533, + 0x005d0582, + 0x005805d6, + 0x0053062e, + 0x004e068c, +}; + +const struct phytbl_info mimophytbl_info_rev7[] = { + {&frame_struct_rev3, + sizeof(frame_struct_rev3) / sizeof(frame_struct_rev3[0]), 10, 0, 32} + , + {&pilot_tbl_rev3, sizeof(pilot_tbl_rev3) / sizeof(pilot_tbl_rev3[0]), + 11, 0, 16} + , + {&tmap_tbl_rev7, sizeof(tmap_tbl_rev7) / sizeof(tmap_tbl_rev7[0]), 12, + 0, 32} + , + {&intlv_tbl_rev3, sizeof(intlv_tbl_rev3) / sizeof(intlv_tbl_rev3[0]), + 13, 0, 32} + , + {&tdtrn_tbl_rev3, sizeof(tdtrn_tbl_rev3) / sizeof(tdtrn_tbl_rev3[0]), + 14, 0, 32} + , + {&noise_var_tbl_rev7, + sizeof(noise_var_tbl_rev7) / sizeof(noise_var_tbl_rev7[0]), 16, 0, 32} + , + {&mcs_tbl_rev3, sizeof(mcs_tbl_rev3) / sizeof(mcs_tbl_rev3[0]), 18, 0, + 16} + , + {&tdi_tbl20_ant0_rev3, + sizeof(tdi_tbl20_ant0_rev3) / sizeof(tdi_tbl20_ant0_rev3[0]), 19, 128, + 32} + , + {&tdi_tbl20_ant1_rev3, + sizeof(tdi_tbl20_ant1_rev3) / sizeof(tdi_tbl20_ant1_rev3[0]), 19, 256, + 32} + , + {&tdi_tbl40_ant0_rev3, + sizeof(tdi_tbl40_ant0_rev3) / sizeof(tdi_tbl40_ant0_rev3[0]), 19, 640, + 32} + , + {&tdi_tbl40_ant1_rev3, + sizeof(tdi_tbl40_ant1_rev3) / sizeof(tdi_tbl40_ant1_rev3[0]), 19, 768, + 32} + , + {&pltlut_tbl_rev3, sizeof(pltlut_tbl_rev3) / sizeof(pltlut_tbl_rev3[0]), + 20, 0, 32} + , + {&chanest_tbl_rev3, + sizeof(chanest_tbl_rev3) / sizeof(chanest_tbl_rev3[0]), 22, 0, 32} + , + {&frame_lut_rev3, sizeof(frame_lut_rev3) / sizeof(frame_lut_rev3[0]), + 24, 0, 8} + , + {&est_pwr_lut_core0_rev3, + sizeof(est_pwr_lut_core0_rev3) / sizeof(est_pwr_lut_core0_rev3[0]), 26, + 0, 8} + , + {&est_pwr_lut_core1_rev3, + sizeof(est_pwr_lut_core1_rev3) / sizeof(est_pwr_lut_core1_rev3[0]), 27, + 0, 8} + , + {&adj_pwr_lut_core0_rev3, + sizeof(adj_pwr_lut_core0_rev3) / sizeof(adj_pwr_lut_core0_rev3[0]), 26, + 64, 8} + , + {&adj_pwr_lut_core1_rev3, + sizeof(adj_pwr_lut_core1_rev3) / sizeof(adj_pwr_lut_core1_rev3[0]), 27, + 64, 8} + , + {&gainctrl_lut_core0_rev3, + sizeof(gainctrl_lut_core0_rev3) / sizeof(gainctrl_lut_core0_rev3[0]), + 26, 192, 32} + , + {&gainctrl_lut_core1_rev3, + sizeof(gainctrl_lut_core1_rev3) / sizeof(gainctrl_lut_core1_rev3[0]), + 27, 192, 32} + , + {&iq_lut_core0_rev3, + sizeof(iq_lut_core0_rev3) / sizeof(iq_lut_core0_rev3[0]), 26, 320, 32} + , + {&iq_lut_core1_rev3, + sizeof(iq_lut_core1_rev3) / sizeof(iq_lut_core1_rev3[0]), 27, 320, 32} + , + {&loft_lut_core0_rev3, + sizeof(loft_lut_core0_rev3) / sizeof(loft_lut_core0_rev3[0]), 26, 448, + 16} + , + {&loft_lut_core1_rev3, + sizeof(loft_lut_core1_rev3) / sizeof(loft_lut_core1_rev3[0]), 27, 448, + 16} + , + {&papd_comp_rfpwr_tbl_core0_rev3, + sizeof(papd_comp_rfpwr_tbl_core0_rev3) / + sizeof(papd_comp_rfpwr_tbl_core0_rev3[0]), 26, 576, 16} + , + {&papd_comp_rfpwr_tbl_core1_rev3, + sizeof(papd_comp_rfpwr_tbl_core1_rev3) / + sizeof(papd_comp_rfpwr_tbl_core1_rev3[0]), 27, 576, 16} + , + {&papd_comp_epsilon_tbl_core0_rev7, + sizeof(papd_comp_epsilon_tbl_core0_rev7) / + sizeof(papd_comp_epsilon_tbl_core0_rev7[0]), 31, 0, 32} + , + {&papd_cal_scalars_tbl_core0_rev7, + sizeof(papd_cal_scalars_tbl_core0_rev7) / + sizeof(papd_cal_scalars_tbl_core0_rev7[0]), 32, 0, 32} + , + {&papd_comp_epsilon_tbl_core1_rev7, + sizeof(papd_comp_epsilon_tbl_core1_rev7) / + sizeof(papd_comp_epsilon_tbl_core1_rev7[0]), 33, 0, 32} + , + {&papd_cal_scalars_tbl_core1_rev7, + sizeof(papd_cal_scalars_tbl_core1_rev7) / + sizeof(papd_cal_scalars_tbl_core1_rev7[0]), 34, 0, 32} + , +}; + +const u32 mimophytbl_info_sz_rev7 = + sizeof(mimophytbl_info_rev7) / sizeof(mimophytbl_info_rev7[0]); + +const struct phytbl_info mimophytbl_info_rev16[] = { + {&noise_var_tbl_rev7, + sizeof(noise_var_tbl_rev7) / sizeof(noise_var_tbl_rev7[0]), 16, 0, 32} + , + {&est_pwr_lut_core0_rev3, + sizeof(est_pwr_lut_core0_rev3) / sizeof(est_pwr_lut_core0_rev3[0]), 26, + 0, 8} + , + {&est_pwr_lut_core1_rev3, + sizeof(est_pwr_lut_core1_rev3) / sizeof(est_pwr_lut_core1_rev3[0]), 27, + 0, 8} + , + {&adj_pwr_lut_core0_rev3, + sizeof(adj_pwr_lut_core0_rev3) / sizeof(adj_pwr_lut_core0_rev3[0]), 26, + 64, 8} + , + {&adj_pwr_lut_core1_rev3, + sizeof(adj_pwr_lut_core1_rev3) / sizeof(adj_pwr_lut_core1_rev3[0]), 27, + 64, 8} + , + {&gainctrl_lut_core0_rev3, + sizeof(gainctrl_lut_core0_rev3) / sizeof(gainctrl_lut_core0_rev3[0]), + 26, 192, 32} + , + {&gainctrl_lut_core1_rev3, + sizeof(gainctrl_lut_core1_rev3) / sizeof(gainctrl_lut_core1_rev3[0]), + 27, 192, 32} + , + {&iq_lut_core0_rev3, + sizeof(iq_lut_core0_rev3) / sizeof(iq_lut_core0_rev3[0]), 26, 320, 32} + , + {&iq_lut_core1_rev3, + sizeof(iq_lut_core1_rev3) / sizeof(iq_lut_core1_rev3[0]), 27, 320, 32} + , + {&loft_lut_core0_rev3, + sizeof(loft_lut_core0_rev3) / sizeof(loft_lut_core0_rev3[0]), 26, 448, + 16} + , + {&loft_lut_core1_rev3, + sizeof(loft_lut_core1_rev3) / sizeof(loft_lut_core1_rev3[0]), 27, 448, + 16} + , +}; + +const u32 mimophytbl_info_sz_rev16 = + sizeof(mimophytbl_info_rev16) / sizeof(mimophytbl_info_rev16[0]); diff --git a/drivers/net/wireless/brcm80211/brcmsmac/phy/phytbl_n.h b/drivers/net/wireless/brcm80211/brcmsmac/phy/phytbl_n.h new file mode 100644 index 0000000..dc8a84e --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/phy/phytbl_n.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define ANT_SWCTRL_TBL_REV3_IDX (0) + +#include +#include "phy_int.h" + +extern const struct phytbl_info mimophytbl_info_rev0[], + mimophytbl_info_rev0_volatile[]; + +extern const u32 mimophytbl_info_sz_rev0, + mimophytbl_info_sz_rev0_volatile; + +extern const struct phytbl_info mimophytbl_info_rev3[], + mimophytbl_info_rev3_volatile[], + mimophytbl_info_rev3_volatile1[], + mimophytbl_info_rev3_volatile2[], + mimophytbl_info_rev3_volatile3[]; + +extern const u32 mimophytbl_info_sz_rev3, + mimophytbl_info_sz_rev3_volatile, + mimophytbl_info_sz_rev3_volatile1, + mimophytbl_info_sz_rev3_volatile2, + mimophytbl_info_sz_rev3_volatile3; + +extern const u32 noise_var_tbl_rev3[]; + +extern const struct phytbl_info mimophytbl_info_rev7[]; + +extern const u32 mimophytbl_info_sz_rev7; + +extern const u32 noise_var_tbl_rev7[]; + +extern const struct phytbl_info mimophytbl_info_rev16[]; + +extern const u32 mimophytbl_info_sz_rev16; diff --git a/drivers/net/wireless/brcm80211/brcmsmac/phy_shim.c b/drivers/net/wireless/brcm80211/brcmsmac/phy_shim.c new file mode 100644 index 0000000..a0de5db --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/phy_shim.c @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is "two-way" interface, acting as the SHIM layer between driver + * and PHY layer. The driver can optionally call this translation layer + * to do some preprocessing, then reach PHY. On the PHY->driver direction, + * all calls go through this layer since PHY doesn't have access to the + * driver's brcms_hardware pointer. + */ +#include +#include + +#include "main.h" +#include "mac80211_if.h" +#include "phy_shim.h" + +/* PHY SHIM module specific state */ +struct phy_shim_info { + struct brcms_hardware *wlc_hw; /* pointer to main wlc_hw structure */ + struct brcms_c_info *wlc; /* pointer to main wlc structure */ + struct brcms_info *wl; /* pointer to os-specific private state */ +}; + +struct phy_shim_info *wlc_phy_shim_attach(struct brcms_hardware *wlc_hw, + struct brcms_info *wl, + struct brcms_c_info *wlc) { + struct phy_shim_info *physhim = NULL; + + physhim = kzalloc(sizeof(struct phy_shim_info), GFP_ATOMIC); + if (!physhim) + return NULL; + + physhim->wlc_hw = wlc_hw; + physhim->wlc = wlc; + physhim->wl = wl; + + return physhim; +} + +void wlc_phy_shim_detach(struct phy_shim_info *physhim) +{ + kfree(physhim); +} + +struct wlapi_timer *wlapi_init_timer(struct phy_shim_info *physhim, + void (*fn)(struct brcms_phy *pi), + void *arg, const char *name) +{ + return (struct wlapi_timer *) + brcms_init_timer(physhim->wl, (void (*)(void *))fn, + arg, name); +} + +void wlapi_free_timer(struct wlapi_timer *t) +{ + brcms_free_timer((struct brcms_timer *)t); +} + +void +wlapi_add_timer(struct wlapi_timer *t, uint ms, int periodic) +{ + brcms_add_timer((struct brcms_timer *)t, ms, periodic); +} + +bool wlapi_del_timer(struct wlapi_timer *t) +{ + return brcms_del_timer((struct brcms_timer *)t); +} + +void wlapi_intrson(struct phy_shim_info *physhim) +{ + brcms_intrson(physhim->wl); +} + +u32 wlapi_intrsoff(struct phy_shim_info *physhim) +{ + return brcms_intrsoff(physhim->wl); +} + +void wlapi_intrsrestore(struct phy_shim_info *physhim, u32 macintmask) +{ + brcms_intrsrestore(physhim->wl, macintmask); +} + +void wlapi_bmac_write_shm(struct phy_shim_info *physhim, uint offset, u16 v) +{ + brcms_b_write_shm(physhim->wlc_hw, offset, v); +} + +u16 wlapi_bmac_read_shm(struct phy_shim_info *physhim, uint offset) +{ + return brcms_b_read_shm(physhim->wlc_hw, offset); +} + +void +wlapi_bmac_mhf(struct phy_shim_info *physhim, u8 idx, u16 mask, + u16 val, int bands) +{ + brcms_b_mhf(physhim->wlc_hw, idx, mask, val, bands); +} + +void wlapi_bmac_corereset(struct phy_shim_info *physhim, u32 flags) +{ + brcms_b_corereset(physhim->wlc_hw, flags); +} + +void wlapi_suspend_mac_and_wait(struct phy_shim_info *physhim) +{ + brcms_c_suspend_mac_and_wait(physhim->wlc); +} + +void wlapi_switch_macfreq(struct phy_shim_info *physhim, u8 spurmode) +{ + brcms_b_switch_macfreq(physhim->wlc_hw, spurmode); +} + +void wlapi_enable_mac(struct phy_shim_info *physhim) +{ + brcms_c_enable_mac(physhim->wlc); +} + +void wlapi_bmac_mctrl(struct phy_shim_info *physhim, u32 mask, u32 val) +{ + brcms_b_mctrl(physhim->wlc_hw, mask, val); +} + +void wlapi_bmac_phy_reset(struct phy_shim_info *physhim) +{ + brcms_b_phy_reset(physhim->wlc_hw); +} + +void wlapi_bmac_bw_set(struct phy_shim_info *physhim, u16 bw) +{ + brcms_b_bw_set(physhim->wlc_hw, bw); +} + +u16 wlapi_bmac_get_txant(struct phy_shim_info *physhim) +{ + return brcms_b_get_txant(physhim->wlc_hw); +} + +void wlapi_bmac_phyclk_fgc(struct phy_shim_info *physhim, bool clk) +{ + brcms_b_phyclk_fgc(physhim->wlc_hw, clk); +} + +void wlapi_bmac_macphyclk_set(struct phy_shim_info *physhim, bool clk) +{ + brcms_b_macphyclk_set(physhim->wlc_hw, clk); +} + +void wlapi_bmac_core_phypll_ctl(struct phy_shim_info *physhim, bool on) +{ + brcms_b_core_phypll_ctl(physhim->wlc_hw, on); +} + +void wlapi_bmac_core_phypll_reset(struct phy_shim_info *physhim) +{ + brcms_b_core_phypll_reset(physhim->wlc_hw); +} + +void wlapi_bmac_ucode_wake_override_phyreg_set(struct phy_shim_info *physhim) +{ + brcms_c_ucode_wake_override_set(physhim->wlc_hw, + BRCMS_WAKE_OVERRIDE_PHYREG); +} + +void wlapi_bmac_ucode_wake_override_phyreg_clear(struct phy_shim_info *physhim) +{ + brcms_c_ucode_wake_override_clear(physhim->wlc_hw, + BRCMS_WAKE_OVERRIDE_PHYREG); +} + +void +wlapi_bmac_write_template_ram(struct phy_shim_info *physhim, int offset, + int len, void *buf) +{ + brcms_b_write_template_ram(physhim->wlc_hw, offset, len, buf); +} + +u16 wlapi_bmac_rate_shm_offset(struct phy_shim_info *physhim, u8 rate) +{ + return brcms_b_rate_shm_offset(physhim->wlc_hw, rate); +} + +void wlapi_ucode_sample_init(struct phy_shim_info *physhim) +{ +} + +void +wlapi_copyfrom_objmem(struct phy_shim_info *physhim, uint offset, void *buf, + int len, u32 sel) +{ + brcms_b_copyfrom_objmem(physhim->wlc_hw, offset, buf, len, sel); +} + +void +wlapi_copyto_objmem(struct phy_shim_info *physhim, uint offset, const void *buf, + int l, u32 sel) +{ + brcms_b_copyto_objmem(physhim->wlc_hw, offset, buf, l, sel); +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/phy_shim.h b/drivers/net/wireless/brcm80211/brcmsmac/phy_shim.h new file mode 100644 index 0000000..2c5b66b --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/phy_shim.h @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * phy_shim.h: stuff defined in phy_shim.c and included only by the phy + */ + +#ifndef _BRCM_PHY_SHIM_H_ +#define _BRCM_PHY_SHIM_H_ + +#include "types.h" + +#define RADAR_TYPE_NONE 0 /* Radar type None */ +#define RADAR_TYPE_ETSI_1 1 /* ETSI 1 Radar type */ +#define RADAR_TYPE_ETSI_2 2 /* ETSI 2 Radar type */ +#define RADAR_TYPE_ETSI_3 3 /* ETSI 3 Radar type */ +#define RADAR_TYPE_ITU_E 4 /* ITU E Radar type */ +#define RADAR_TYPE_ITU_K 5 /* ITU K Radar type */ +#define RADAR_TYPE_UNCLASSIFIED 6 /* Unclassified Radar type */ +#define RADAR_TYPE_BIN5 7 /* long pulse radar type */ +#define RADAR_TYPE_STG2 8 /* staggered-2 radar */ +#define RADAR_TYPE_STG3 9 /* staggered-3 radar */ +#define RADAR_TYPE_FRA 10 /* French radar */ + +/* French radar pulse widths */ +#define FRA_T1_20MHZ 52770 +#define FRA_T2_20MHZ 61538 +#define FRA_T3_20MHZ 66002 +#define FRA_T1_40MHZ 105541 +#define FRA_T2_40MHZ 123077 +#define FRA_T3_40MHZ 132004 +#define FRA_ERR_20MHZ 60 +#define FRA_ERR_40MHZ 120 + +#define ANTSEL_NA 0 /* No boardlevel selection available */ +#define ANTSEL_2x4 1 /* 2x4 boardlevel selection available */ +#define ANTSEL_2x3 2 /* 2x3 CB2 boardlevel selection available */ + +/* Rx Antenna diversity control values */ +#define ANT_RX_DIV_FORCE_0 0 /* Use antenna 0 */ +#define ANT_RX_DIV_FORCE_1 1 /* Use antenna 1 */ +#define ANT_RX_DIV_START_1 2 /* Choose starting with 1 */ +#define ANT_RX_DIV_START_0 3 /* Choose starting with 0 */ +#define ANT_RX_DIV_ENABLE 3 /* APHY bbConfig Enable RX Diversity */ +#define ANT_RX_DIV_DEF ANT_RX_DIV_START_0 /* default antdiv setting */ + +#define WL_ANT_RX_MAX 2 /* max 2 receive antennas */ +#define WL_ANT_HT_RX_MAX 3 /* max 3 receive antennas/cores */ +#define WL_ANT_IDX_1 0 /* antenna index 1 */ +#define WL_ANT_IDX_2 1 /* antenna index 2 */ + +/* values for n_preamble_type */ +#define BRCMS_N_PREAMBLE_MIXEDMODE 0 +#define BRCMS_N_PREAMBLE_GF 1 +#define BRCMS_N_PREAMBLE_GF_BRCM 2 + +#define WL_TX_POWER_RATES_LEGACY 45 +#define WL_TX_POWER_MCS20_FIRST 12 +#define WL_TX_POWER_MCS20_NUM 16 +#define WL_TX_POWER_MCS40_FIRST 28 +#define WL_TX_POWER_MCS40_NUM 17 + + +#define WL_TX_POWER_RATES 101 +#define WL_TX_POWER_CCK_FIRST 0 +#define WL_TX_POWER_CCK_NUM 4 +/* Index for first 20MHz OFDM SISO rate */ +#define WL_TX_POWER_OFDM_FIRST 4 +/* Index for first 20MHz OFDM CDD rate */ +#define WL_TX_POWER_OFDM20_CDD_FIRST 12 +/* Index for first 40MHz OFDM SISO rate */ +#define WL_TX_POWER_OFDM40_SISO_FIRST 52 +/* Index for first 40MHz OFDM CDD rate */ +#define WL_TX_POWER_OFDM40_CDD_FIRST 60 +#define WL_TX_POWER_OFDM_NUM 8 +/* Index for first 20MHz MCS SISO rate */ +#define WL_TX_POWER_MCS20_SISO_FIRST 20 +/* Index for first 20MHz MCS CDD rate */ +#define WL_TX_POWER_MCS20_CDD_FIRST 28 +/* Index for first 20MHz MCS STBC rate */ +#define WL_TX_POWER_MCS20_STBC_FIRST 36 +/* Index for first 20MHz MCS SDM rate */ +#define WL_TX_POWER_MCS20_SDM_FIRST 44 +/* Index for first 40MHz MCS SISO rate */ +#define WL_TX_POWER_MCS40_SISO_FIRST 68 +/* Index for first 40MHz MCS CDD rate */ +#define WL_TX_POWER_MCS40_CDD_FIRST 76 +/* Index for first 40MHz MCS STBC rate */ +#define WL_TX_POWER_MCS40_STBC_FIRST 84 +/* Index for first 40MHz MCS SDM rate */ +#define WL_TX_POWER_MCS40_SDM_FIRST 92 +#define WL_TX_POWER_MCS_1_STREAM_NUM 8 +#define WL_TX_POWER_MCS_2_STREAM_NUM 8 +/* Index for 40MHz rate MCS 32 */ +#define WL_TX_POWER_MCS_32 100 +#define WL_TX_POWER_MCS_32_NUM 1 + +/* sslpnphy specifics */ +/* Index for first 20MHz MCS SISO rate */ +#define WL_TX_POWER_MCS20_SISO_FIRST_SSN 12 + +/* struct tx_power::flags bits */ +#define WL_TX_POWER_F_ENABLED 1 +#define WL_TX_POWER_F_HW 2 +#define WL_TX_POWER_F_MIMO 4 +#define WL_TX_POWER_F_SISO 8 + +/* values to force tx/rx chain */ +#define BRCMS_N_TXRX_CHAIN0 0 +#define BRCMS_N_TXRX_CHAIN1 1 + +struct brcms_phy; + +extern struct phy_shim_info *wlc_phy_shim_attach(struct brcms_hardware *wlc_hw, + struct brcms_info *wl, + struct brcms_c_info *wlc); +extern void wlc_phy_shim_detach(struct phy_shim_info *physhim); + +/* PHY to WL utility functions */ +extern struct wlapi_timer *wlapi_init_timer(struct phy_shim_info *physhim, + void (*fn) (struct brcms_phy *pi), + void *arg, const char *name); +extern void wlapi_free_timer(struct wlapi_timer *t); +extern void wlapi_add_timer(struct wlapi_timer *t, uint ms, int periodic); +extern bool wlapi_del_timer(struct wlapi_timer *t); +extern void wlapi_intrson(struct phy_shim_info *physhim); +extern u32 wlapi_intrsoff(struct phy_shim_info *physhim); +extern void wlapi_intrsrestore(struct phy_shim_info *physhim, + u32 macintmask); + +extern void wlapi_bmac_write_shm(struct phy_shim_info *physhim, uint offset, + u16 v); +extern u16 wlapi_bmac_read_shm(struct phy_shim_info *physhim, uint offset); +extern void wlapi_bmac_mhf(struct phy_shim_info *physhim, u8 idx, + u16 mask, u16 val, int bands); +extern void wlapi_bmac_corereset(struct phy_shim_info *physhim, u32 flags); +extern void wlapi_suspend_mac_and_wait(struct phy_shim_info *physhim); +extern void wlapi_switch_macfreq(struct phy_shim_info *physhim, u8 spurmode); +extern void wlapi_enable_mac(struct phy_shim_info *physhim); +extern void wlapi_bmac_mctrl(struct phy_shim_info *physhim, u32 mask, + u32 val); +extern void wlapi_bmac_phy_reset(struct phy_shim_info *physhim); +extern void wlapi_bmac_bw_set(struct phy_shim_info *physhim, u16 bw); +extern void wlapi_bmac_phyclk_fgc(struct phy_shim_info *physhim, bool clk); +extern void wlapi_bmac_macphyclk_set(struct phy_shim_info *physhim, bool clk); +extern void wlapi_bmac_core_phypll_ctl(struct phy_shim_info *physhim, bool on); +extern void wlapi_bmac_core_phypll_reset(struct phy_shim_info *physhim); +extern void wlapi_bmac_ucode_wake_override_phyreg_set(struct phy_shim_info * + physhim); +extern void wlapi_bmac_ucode_wake_override_phyreg_clear(struct phy_shim_info * + physhim); +extern void wlapi_bmac_write_template_ram(struct phy_shim_info *physhim, int o, + int len, void *buf); +extern u16 wlapi_bmac_rate_shm_offset(struct phy_shim_info *physhim, + u8 rate); +extern void wlapi_ucode_sample_init(struct phy_shim_info *physhim); +extern void wlapi_copyfrom_objmem(struct phy_shim_info *physhim, uint, + void *buf, int, u32 sel); +extern void wlapi_copyto_objmem(struct phy_shim_info *physhim, uint, + const void *buf, int, u32); + +extern void wlapi_high_update_phy_mode(struct phy_shim_info *physhim, + u32 phy_mode); +extern u16 wlapi_bmac_get_txant(struct phy_shim_info *physhim); + +#endif /* _BRCM_PHY_SHIM_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/pmu.c b/drivers/net/wireless/brcm80211/brcmsmac/pmu.c new file mode 100644 index 0000000..71b8038 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/pmu.c @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2011 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include +#include "pub.h" +#include "aiutils.h" +#include "pmu.h" +#include "soc.h" + +/* + * external LPO crystal frequency + */ +#define EXT_ILP_HZ 32768 + +/* + * Duration for ILP clock frequency measurment in milliseconds + * + * remark: 1000 must be an integer multiple of this duration + */ +#define ILP_CALC_DUR 10 + +/* Fields in pmucontrol */ +#define PCTL_ILP_DIV_MASK 0xffff0000 +#define PCTL_ILP_DIV_SHIFT 16 +#define PCTL_PLL_PLLCTL_UPD 0x00000400 /* rev 2 */ +#define PCTL_NOILP_ON_WAIT 0x00000200 /* rev 1 */ +#define PCTL_HT_REQ_EN 0x00000100 +#define PCTL_ALP_REQ_EN 0x00000080 +#define PCTL_XTALFREQ_MASK 0x0000007c +#define PCTL_XTALFREQ_SHIFT 2 +#define PCTL_ILP_DIV_EN 0x00000002 +#define PCTL_LPO_SEL 0x00000001 + +/* ILP clock */ +#define ILP_CLOCK 32000 + +/* ALP clock on pre-PMU chips */ +#define ALP_CLOCK 20000000 + +/* pmustatus */ +#define PST_EXTLPOAVAIL 0x0100 +#define PST_WDRESET 0x0080 +#define PST_INTPEND 0x0040 +#define PST_SBCLKST 0x0030 +#define PST_SBCLKST_ILP 0x0010 +#define PST_SBCLKST_ALP 0x0020 +#define PST_SBCLKST_HT 0x0030 +#define PST_ALPAVAIL 0x0008 +#define PST_HTAVAIL 0x0004 +#define PST_RESINIT 0x0003 + +/* PMU resource bit position */ +#define PMURES_BIT(bit) (1 << (bit)) + +/* PMU corerev and chip specific PLL controls. + * PMU_PLL_XX where is PMU corerev and is an arbitrary + * number to differentiate different PLLs controlled by the same PMU rev. + */ + +/* pmu XtalFreqRatio */ +#define PMU_XTALFREQ_REG_ILPCTR_MASK 0x00001FFF +#define PMU_XTALFREQ_REG_MEASURE_MASK 0x80000000 +#define PMU_XTALFREQ_REG_MEASURE_SHIFT 31 + +/* 4313 resources */ +#define RES4313_BB_PU_RSRC 0 +#define RES4313_ILP_REQ_RSRC 1 +#define RES4313_XTAL_PU_RSRC 2 +#define RES4313_ALP_AVAIL_RSRC 3 +#define RES4313_RADIO_PU_RSRC 4 +#define RES4313_BG_PU_RSRC 5 +#define RES4313_VREG1P4_PU_RSRC 6 +#define RES4313_AFE_PWRSW_RSRC 7 +#define RES4313_RX_PWRSW_RSRC 8 +#define RES4313_TX_PWRSW_RSRC 9 +#define RES4313_BB_PWRSW_RSRC 10 +#define RES4313_SYNTH_PWRSW_RSRC 11 +#define RES4313_MISC_PWRSW_RSRC 12 +#define RES4313_BB_PLL_PWRSW_RSRC 13 +#define RES4313_HT_AVAIL_RSRC 14 +#define RES4313_MACPHY_CLK_AVAIL_RSRC 15 + +u16 si_pmu_fast_pwrup_delay(struct si_pub *sih) +{ + uint delay = PMU_MAX_TRANSITION_DLY; + + switch (ai_get_chip_id(sih)) { + case BCMA_CHIP_ID_BCM43224: + case BCMA_CHIP_ID_BCM43225: + case BCMA_CHIP_ID_BCM4313: + delay = 3700; + break; + default: + break; + } + + return (u16) delay; +} + +u32 si_pmu_measure_alpclk(struct si_pub *sih) +{ + struct si_info *sii = container_of(sih, struct si_info, pub); + struct bcma_device *core; + u32 alp_khz; + + if (ai_get_pmurev(sih) < 10) + return 0; + + /* Remember original core before switch to chipc */ + core = sii->icbus->drv_cc.core; + + if (bcma_read32(core, CHIPCREGOFFS(pmustatus)) & PST_EXTLPOAVAIL) { + u32 ilp_ctr, alp_hz; + + /* + * Enable the reg to measure the freq, + * in case it was disabled before + */ + bcma_write32(core, CHIPCREGOFFS(pmu_xtalfreq), + 1U << PMU_XTALFREQ_REG_MEASURE_SHIFT); + + /* Delay for well over 4 ILP clocks */ + udelay(1000); + + /* Read the latched number of ALP ticks per 4 ILP ticks */ + ilp_ctr = bcma_read32(core, CHIPCREGOFFS(pmu_xtalfreq)) & + PMU_XTALFREQ_REG_ILPCTR_MASK; + + /* + * Turn off the PMU_XTALFREQ_REG_MEASURE_SHIFT + * bit to save power + */ + bcma_write32(core, CHIPCREGOFFS(pmu_xtalfreq), 0); + + /* Calculate ALP frequency */ + alp_hz = (ilp_ctr * EXT_ILP_HZ) / 4; + + /* + * Round to nearest 100KHz, and at + * the same time convert to KHz + */ + alp_khz = (alp_hz + 50000) / 100000 * 100; + } else + alp_khz = 0; + + return alp_khz; +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/pmu.h b/drivers/net/wireless/brcm80211/brcmsmac/pmu.h new file mode 100644 index 0000000..20e2012 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/pmu.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +#ifndef _BRCM_PMU_H_ +#define _BRCM_PMU_H_ + +#include "types.h" + +extern u16 si_pmu_fast_pwrup_delay(struct si_pub *sih); +extern u32 si_pmu_measure_alpclk(struct si_pub *sih); + +#endif /* _BRCM_PMU_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/pub.h b/drivers/net/wireless/brcm80211/brcmsmac/pub.h new file mode 100644 index 0000000..d36ea5e --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/pub.h @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_PUB_H_ +#define _BRCM_PUB_H_ + +#include +#include +#include "types.h" +#include "defs.h" + +#define BRCMS_NUMRATES 16 /* max # of rates in a rateset */ + +/* phy types */ +#define PHY_TYPE_A 0 /* Phy type A */ +#define PHY_TYPE_G 2 /* Phy type G */ +#define PHY_TYPE_N 4 /* Phy type N */ +#define PHY_TYPE_LP 5 /* Phy type Low Power A/B/G */ +#define PHY_TYPE_SSN 6 /* Phy type Single Stream N */ +#define PHY_TYPE_LCN 8 /* Phy type Single Stream N */ +#define PHY_TYPE_LCNXN 9 /* Phy type 2-stream N */ +#define PHY_TYPE_HT 7 /* Phy type 3-Stream N */ + +/* bw */ +#define BRCMS_10_MHZ 10 /* 10Mhz nphy channel bandwidth */ +#define BRCMS_20_MHZ 20 /* 20Mhz nphy channel bandwidth */ +#define BRCMS_40_MHZ 40 /* 40Mhz nphy channel bandwidth */ + +#define BRCMS_RSSI_MINVAL -200 /* Low value, e.g. for forcing roam */ +#define BRCMS_RSSI_NO_SIGNAL -91 /* NDIS RSSI link quality cutoffs */ +#define BRCMS_RSSI_VERY_LOW -80 /* Very low quality cutoffs */ +#define BRCMS_RSSI_LOW -70 /* Low quality cutoffs */ +#define BRCMS_RSSI_GOOD -68 /* Good quality cutoffs */ +#define BRCMS_RSSI_VERY_GOOD -58 /* Very good quality cutoffs */ +#define BRCMS_RSSI_EXCELLENT -57 /* Excellent quality cutoffs */ + +/* a large TX Power as an init value to factor out of min() calculations, + * keep low enough to fit in an s8, units are .25 dBm + */ +#define BRCMS_TXPWR_MAX (127) /* ~32 dBm = 1,500 mW */ + +/* rate related definitions */ +#define BRCMS_RATE_FLAG 0x80 /* Flag to indicate it is a basic rate */ +#define BRCMS_RATE_MASK 0x7f /* Rate value mask w/o basic rate flag */ + +/* legacy rx Antenna diversity for SISO rates */ +#define ANT_RX_DIV_FORCE_0 0 /* Use antenna 0 */ +#define ANT_RX_DIV_FORCE_1 1 /* Use antenna 1 */ +#define ANT_RX_DIV_START_1 2 /* Choose starting with 1 */ +#define ANT_RX_DIV_START_0 3 /* Choose starting with 0 */ +#define ANT_RX_DIV_ENABLE 3 /* APHY bbConfig Enable RX Diversity */ +/* default antdiv setting */ +#define ANT_RX_DIV_DEF ANT_RX_DIV_START_0 + +/* legacy rx Antenna diversity for SISO rates */ +/* Tx on antenna 0, "legacy term Main" */ +#define ANT_TX_FORCE_0 0 +/* Tx on antenna 1, "legacy term Aux" */ +#define ANT_TX_FORCE_1 1 +/* Tx on phy's last good Rx antenna */ +#define ANT_TX_LAST_RX 3 +/* driver's default tx antenna setting */ +#define ANT_TX_DEF 3 + +/* Tx Chain values */ +/* def bitmap of txchain */ +#define TXCHAIN_DEF 0x1 +/* default bitmap of tx chains for nphy */ +#define TXCHAIN_DEF_NPHY 0x3 +/* default bitmap of tx chains for nphy */ +#define TXCHAIN_DEF_HTPHY 0x7 +/* def bitmap of rxchain */ +#define RXCHAIN_DEF 0x1 +/* default bitmap of rx chains for nphy */ +#define RXCHAIN_DEF_NPHY 0x3 +/* default bitmap of rx chains for nphy */ +#define RXCHAIN_DEF_HTPHY 0x7 +/* no antenna switch */ +#define ANTSWITCH_NONE 0 +/* antenna switch on 4321CB2, 2of3 */ +#define ANTSWITCH_TYPE_1 1 +/* antenna switch on 4321MPCI, 2of3 */ +#define ANTSWITCH_TYPE_2 2 +/* antenna switch on 4322, 2of3 */ +#define ANTSWITCH_TYPE_3 3 + +#define RXBUFSZ PKTBUFSZ + +#define MAX_STREAMS_SUPPORTED 4 /* max number of streams supported */ + +struct brcm_rateset { + /* # rates in this set */ + u32 count; + /* rates in 500kbps units w/hi bit set if basic */ + u8 rates[WL_NUMRATES]; +}; + +struct brcms_c_rateset { + uint count; /* number of rates in rates[] */ + /* rates in 500kbps units w/hi bit set if basic */ + u8 rates[BRCMS_NUMRATES]; + u8 htphy_membership; /* HT PHY Membership */ + u8 mcs[MCSSET_LEN]; /* supported mcs index bit map */ +}; + +/* All the HT-specific default advertised capabilities (including AMPDU) + * should be grouped here at one place + */ +#define AMPDU_DEF_MPDU_DENSITY 6 /* default mpdu density (110 ==> 4us) */ + +/* wlc internal bss_info */ +struct brcms_bss_info { + u8 BSSID[ETH_ALEN]; /* network BSSID */ + u16 flags; /* flags for internal attributes */ + u8 SSID_len; /* the length of SSID */ + u8 SSID[32]; /* SSID string */ + s16 RSSI; /* receive signal strength (in dBm) */ + s16 SNR; /* receive signal SNR in dB */ + u16 beacon_period; /* units are Kusec */ + u16 chanspec; /* Channel num, bw, ctrl_sb and band */ + struct brcms_c_rateset rateset; /* supported rates */ +}; + +#define MAC80211_PROMISC_BCNS (1 << 0) +#define MAC80211_SCAN (1 << 1) + +/* + * Public portion of common driver state structure. + * The wlc handle points at this. + */ +struct brcms_pub { + struct brcms_c_info *wlc; + struct ieee80211_hw *ieee_hw; + struct scb_ampdu *global_ampdu; + uint mac80211_state; + uint unit; /* device instance number */ + uint corerev; /* core revision */ + struct si_pub *sih; /* SI handle (cookie for siutils calls) */ + bool up; /* interface up and running */ + bool hw_off; /* HW is off */ + bool hw_up; /* one time hw up/down */ + bool _piomode; /* true if pio mode */ + uint _nbands; /* # bands supported */ + uint now; /* # elapsed seconds */ + + bool delayed_down; /* down delayed */ + bool associated; /* true:part of [I]BSS, false: not */ + /* (union of stas_associated, aps_associated) */ + bool _ampdu; /* ampdu enabled or not */ + u8 _n_enab; /* bitmap of 11N + HT support */ + + u8 cur_etheraddr[ETH_ALEN]; /* our local ethernet address */ + + u32 radio_disabled; /* bit vector for radio disabled reasons */ + + u16 boardrev; /* version # of particular board */ + u8 sromrev; /* version # of the srom */ + char srom_ccode[BRCM_CNTRY_BUF_SZ]; /* Country Code in SROM */ + u32 boardflags; /* Board specific flags from srom */ + u32 boardflags2; /* More board flags if sromrev >= 4 */ + bool phy_11ncapable; /* the PHY/HW is capable of 802.11N */ + + struct wl_cnt *_cnt; /* low-level counters in driver */ + struct dentry *dbgfs_dir; +}; + +enum wlc_par_id { + IOV_MPC = 1, + IOV_RTSTHRESH, + IOV_QTXPOWER, + IOV_BCN_LI_BCN /* Beacon listen interval in # of beacons */ +}; + +/*********************************************** + * Feature-related macros to optimize out code * + * ********************************************* + */ + +#define ENAB_1x1 0x01 +#define ENAB_2x2 0x02 +#define ENAB_3x3 0x04 +#define ENAB_4x4 0x08 +#define SUPPORT_11N (ENAB_1x1|ENAB_2x2) +#define SUPPORT_HT (ENAB_1x1|ENAB_2x2|ENAB_3x3) + +/* WL11N Support */ +#define AMPDU_AGG_HOST 1 + +/* network protection config */ +#define BRCMS_PROT_G_SPEC 1 /* SPEC g protection */ +#define BRCMS_PROT_G_OVR 2 /* SPEC g prot override */ +#define BRCMS_PROT_G_USER 3 /* gmode specified by user */ +#define BRCMS_PROT_OVERLAP 4 /* overlap */ +#define BRCMS_PROT_N_USER 10 /* nmode specified by user */ +#define BRCMS_PROT_N_CFG 11 /* n protection */ +#define BRCMS_PROT_N_CFG_OVR 12 /* n protection override */ +#define BRCMS_PROT_N_NONGF 13 /* non-GF protection */ +#define BRCMS_PROT_N_NONGF_OVR 14 /* non-GF protection override */ +#define BRCMS_PROT_N_PAM_OVR 15 /* n preamble override */ +#define BRCMS_PROT_N_OBSS 16 /* non-HT OBSS present */ + +/* + * 54g modes (basic bits may still be overridden) + * + * GMODE_LEGACY_B + * Rateset: 1b, 2b, 5.5, 11 + * Preamble: Long + * Shortslot: Off + * GMODE_AUTO + * Rateset: 1b, 2b, 5.5b, 11b, 18, 24, 36, 54 + * Extended Rateset: 6, 9, 12, 48 + * Preamble: Long + * Shortslot: Auto + * GMODE_ONLY + * Rateset: 1b, 2b, 5.5b, 11b, 18, 24b, 36, 54 + * Extended Rateset: 6b, 9, 12b, 48 + * Preamble: Short required + * Shortslot: Auto + * GMODE_B_DEFERRED + * Rateset: 1b, 2b, 5.5b, 11b, 18, 24, 36, 54 + * Extended Rateset: 6, 9, 12, 48 + * Preamble: Long + * Shortslot: On + * GMODE_PERFORMANCE + * Rateset: 1b, 2b, 5.5b, 6b, 9, 11b, 12b, 18, 24b, 36, 48, 54 + * Preamble: Short required + * Shortslot: On and required + * GMODE_LRS + * Rateset: 1b, 2b, 5.5b, 11b + * Extended Rateset: 6, 9, 12, 18, 24, 36, 48, 54 + * Preamble: Long + * Shortslot: Auto + */ +#define GMODE_LEGACY_B 0 +#define GMODE_AUTO 1 +#define GMODE_ONLY 2 +#define GMODE_B_DEFERRED 3 +#define GMODE_PERFORMANCE 4 +#define GMODE_LRS 5 +#define GMODE_MAX 6 + +/* MCS values greater than this enable multiple streams */ +#define HIGHEST_SINGLE_STREAM_MCS 7 + +#define MAXBANDS 2 /* Maximum #of bands */ + +/* max number of antenna configurations */ +#define ANT_SELCFG_MAX 4 + +struct brcms_antselcfg { + u8 ant_config[ANT_SELCFG_MAX]; /* antenna configuration */ + u8 num_antcfg; /* number of available antenna configurations */ +}; + +/* common functions for every port */ +extern struct brcms_c_info * +brcms_c_attach(struct brcms_info *wl, struct bcma_device *core, uint unit, + bool piomode, uint *perr); +extern uint brcms_c_detach(struct brcms_c_info *wlc); +extern int brcms_c_up(struct brcms_c_info *wlc); +extern uint brcms_c_down(struct brcms_c_info *wlc); + +extern bool brcms_c_chipmatch(struct bcma_device *core); +extern void brcms_c_init(struct brcms_c_info *wlc, bool mute_tx); +extern void brcms_c_reset(struct brcms_c_info *wlc); + +extern void brcms_c_intrson(struct brcms_c_info *wlc); +extern u32 brcms_c_intrsoff(struct brcms_c_info *wlc); +extern void brcms_c_intrsrestore(struct brcms_c_info *wlc, u32 macintmask); +extern bool brcms_c_intrsupd(struct brcms_c_info *wlc); +extern bool brcms_c_isr(struct brcms_c_info *wlc); +extern bool brcms_c_dpc(struct brcms_c_info *wlc, bool bounded); +extern bool brcms_c_sendpkt_mac80211(struct brcms_c_info *wlc, + struct sk_buff *sdu, + struct ieee80211_hw *hw); +extern bool brcms_c_aggregatable(struct brcms_c_info *wlc, u8 tid); +extern void brcms_c_protection_upd(struct brcms_c_info *wlc, uint idx, + int val); +extern int brcms_c_get_header_len(void); +extern void brcms_c_set_addrmatch(struct brcms_c_info *wlc, + int match_reg_offset, + const u8 *addr); +extern void brcms_c_wme_setparams(struct brcms_c_info *wlc, u16 aci, + const struct ieee80211_tx_queue_params *arg, + bool suspend); +extern struct brcms_pub *brcms_c_pub(struct brcms_c_info *wlc); +extern void brcms_c_ampdu_flush(struct brcms_c_info *wlc, + struct ieee80211_sta *sta, u16 tid); +extern void brcms_c_ampdu_tx_operational(struct brcms_c_info *wlc, u8 tid, + u8 ba_wsize, uint max_rx_ampdu_bytes); +extern int brcms_c_module_register(struct brcms_pub *pub, + const char *name, struct brcms_info *hdl, + int (*down_fn)(void *handle)); +extern int brcms_c_module_unregister(struct brcms_pub *pub, const char *name, + struct brcms_info *hdl); +extern void brcms_c_suspend_mac_and_wait(struct brcms_c_info *wlc); +extern void brcms_c_enable_mac(struct brcms_c_info *wlc); +extern void brcms_c_associate_upd(struct brcms_c_info *wlc, bool state); +extern void brcms_c_scan_start(struct brcms_c_info *wlc); +extern void brcms_c_scan_stop(struct brcms_c_info *wlc); +extern int brcms_c_get_curband(struct brcms_c_info *wlc); +extern int brcms_c_set_channel(struct brcms_c_info *wlc, u16 channel); +extern int brcms_c_set_rate_limit(struct brcms_c_info *wlc, u16 srl, u16 lrl); +extern void brcms_c_get_current_rateset(struct brcms_c_info *wlc, + struct brcm_rateset *currs); +extern int brcms_c_set_rateset(struct brcms_c_info *wlc, + struct brcm_rateset *rs); +extern int brcms_c_set_beacon_period(struct brcms_c_info *wlc, u16 period); +extern u16 brcms_c_get_phy_type(struct brcms_c_info *wlc, int phyidx); +extern void brcms_c_set_shortslot_override(struct brcms_c_info *wlc, + s8 sslot_override); +extern void brcms_c_set_beacon_listen_interval(struct brcms_c_info *wlc, + u8 interval); +extern u64 brcms_c_tsf_get(struct brcms_c_info *wlc); +extern void brcms_c_tsf_set(struct brcms_c_info *wlc, u64 tsf); +extern int brcms_c_set_tx_power(struct brcms_c_info *wlc, int txpwr); +extern int brcms_c_get_tx_power(struct brcms_c_info *wlc); +extern bool brcms_c_check_radio_disabled(struct brcms_c_info *wlc); +extern void brcms_c_mute(struct brcms_c_info *wlc, bool on); +extern bool brcms_c_tx_flush_completed(struct brcms_c_info *wlc); +extern void brcms_c_start_station(struct brcms_c_info *wlc, u8 *addr); +extern void brcms_c_start_ap(struct brcms_c_info *wlc, u8 *addr, + const u8 *bssid, u8 *ssid, size_t ssid_len); +extern void brcms_c_start_adhoc(struct brcms_c_info *wlc, u8 *addr); +extern void brcms_c_update_beacon(struct brcms_c_info *wlc); +extern void brcms_c_set_new_beacon(struct brcms_c_info *wlc, + struct sk_buff *beacon, u16 tim_offset, + u16 dtim_period); +extern void brcms_c_set_new_probe_resp(struct brcms_c_info *wlc, + struct sk_buff *probe_resp); +extern void brcms_c_enable_probe_resp(struct brcms_c_info *wlc, bool enable); +extern void brcms_c_set_ssid(struct brcms_c_info *wlc, u8 *ssid, + size_t ssid_len); + +#endif /* _BRCM_PUB_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/rate.c b/drivers/net/wireless/brcm80211/brcmsmac/rate.c new file mode 100644 index 0000000..0a0c0ad --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/rate.c @@ -0,0 +1,514 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "d11.h" +#include "pub.h" +#include "rate.h" + +/* + * Rate info per rate: It tells whether a rate is ofdm or not and its phy_rate + * value + */ +const u8 rate_info[BRCM_MAXRATE + 1] = { + /* 0 1 2 3 4 5 6 7 8 9 */ +/* 0 */ 0x00, 0x00, 0x0a, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 10 */ 0x00, 0x37, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x00, +/* 20 */ 0x00, 0x00, 0x6e, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 30 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, +/* 40 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x00, +/* 50 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 60 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 70 */ 0x00, 0x00, 0x8d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 80 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 90 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, +/* 100 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c +}; + +/* rates are in units of Kbps */ +const struct brcms_mcs_info mcs_table[MCS_TABLE_SIZE] = { + /* MCS 0: SS 1, MOD: BPSK, CR 1/2 */ + {6500, 13500, CEIL(6500 * 10, 9), CEIL(13500 * 10, 9), 0x00, + BRCM_RATE_6M}, + /* MCS 1: SS 1, MOD: QPSK, CR 1/2 */ + {13000, 27000, CEIL(13000 * 10, 9), CEIL(27000 * 10, 9), 0x08, + BRCM_RATE_12M}, + /* MCS 2: SS 1, MOD: QPSK, CR 3/4 */ + {19500, 40500, CEIL(19500 * 10, 9), CEIL(40500 * 10, 9), 0x0A, + BRCM_RATE_18M}, + /* MCS 3: SS 1, MOD: 16QAM, CR 1/2 */ + {26000, 54000, CEIL(26000 * 10, 9), CEIL(54000 * 10, 9), 0x10, + BRCM_RATE_24M}, + /* MCS 4: SS 1, MOD: 16QAM, CR 3/4 */ + {39000, 81000, CEIL(39000 * 10, 9), CEIL(81000 * 10, 9), 0x12, + BRCM_RATE_36M}, + /* MCS 5: SS 1, MOD: 64QAM, CR 2/3 */ + {52000, 108000, CEIL(52000 * 10, 9), CEIL(108000 * 10, 9), 0x19, + BRCM_RATE_48M}, + /* MCS 6: SS 1, MOD: 64QAM, CR 3/4 */ + {58500, 121500, CEIL(58500 * 10, 9), CEIL(121500 * 10, 9), 0x1A, + BRCM_RATE_54M}, + /* MCS 7: SS 1, MOD: 64QAM, CR 5/6 */ + {65000, 135000, CEIL(65000 * 10, 9), CEIL(135000 * 10, 9), 0x1C, + BRCM_RATE_54M}, + /* MCS 8: SS 2, MOD: BPSK, CR 1/2 */ + {13000, 27000, CEIL(13000 * 10, 9), CEIL(27000 * 10, 9), 0x40, + BRCM_RATE_6M}, + /* MCS 9: SS 2, MOD: QPSK, CR 1/2 */ + {26000, 54000, CEIL(26000 * 10, 9), CEIL(54000 * 10, 9), 0x48, + BRCM_RATE_12M}, + /* MCS 10: SS 2, MOD: QPSK, CR 3/4 */ + {39000, 81000, CEIL(39000 * 10, 9), CEIL(81000 * 10, 9), 0x4A, + BRCM_RATE_18M}, + /* MCS 11: SS 2, MOD: 16QAM, CR 1/2 */ + {52000, 108000, CEIL(52000 * 10, 9), CEIL(108000 * 10, 9), 0x50, + BRCM_RATE_24M}, + /* MCS 12: SS 2, MOD: 16QAM, CR 3/4 */ + {78000, 162000, CEIL(78000 * 10, 9), CEIL(162000 * 10, 9), 0x52, + BRCM_RATE_36M}, + /* MCS 13: SS 2, MOD: 64QAM, CR 2/3 */ + {104000, 216000, CEIL(104000 * 10, 9), CEIL(216000 * 10, 9), 0x59, + BRCM_RATE_48M}, + /* MCS 14: SS 2, MOD: 64QAM, CR 3/4 */ + {117000, 243000, CEIL(117000 * 10, 9), CEIL(243000 * 10, 9), 0x5A, + BRCM_RATE_54M}, + /* MCS 15: SS 2, MOD: 64QAM, CR 5/6 */ + {130000, 270000, CEIL(130000 * 10, 9), CEIL(270000 * 10, 9), 0x5C, + BRCM_RATE_54M}, + /* MCS 16: SS 3, MOD: BPSK, CR 1/2 */ + {19500, 40500, CEIL(19500 * 10, 9), CEIL(40500 * 10, 9), 0x80, + BRCM_RATE_6M}, + /* MCS 17: SS 3, MOD: QPSK, CR 1/2 */ + {39000, 81000, CEIL(39000 * 10, 9), CEIL(81000 * 10, 9), 0x88, + BRCM_RATE_12M}, + /* MCS 18: SS 3, MOD: QPSK, CR 3/4 */ + {58500, 121500, CEIL(58500 * 10, 9), CEIL(121500 * 10, 9), 0x8A, + BRCM_RATE_18M}, + /* MCS 19: SS 3, MOD: 16QAM, CR 1/2 */ + {78000, 162000, CEIL(78000 * 10, 9), CEIL(162000 * 10, 9), 0x90, + BRCM_RATE_24M}, + /* MCS 20: SS 3, MOD: 16QAM, CR 3/4 */ + {117000, 243000, CEIL(117000 * 10, 9), CEIL(243000 * 10, 9), 0x92, + BRCM_RATE_36M}, + /* MCS 21: SS 3, MOD: 64QAM, CR 2/3 */ + {156000, 324000, CEIL(156000 * 10, 9), CEIL(324000 * 10, 9), 0x99, + BRCM_RATE_48M}, + /* MCS 22: SS 3, MOD: 64QAM, CR 3/4 */ + {175500, 364500, CEIL(175500 * 10, 9), CEIL(364500 * 10, 9), 0x9A, + BRCM_RATE_54M}, + /* MCS 23: SS 3, MOD: 64QAM, CR 5/6 */ + {195000, 405000, CEIL(195000 * 10, 9), CEIL(405000 * 10, 9), 0x9B, + BRCM_RATE_54M}, + /* MCS 24: SS 4, MOD: BPSK, CR 1/2 */ + {26000, 54000, CEIL(26000 * 10, 9), CEIL(54000 * 10, 9), 0xC0, + BRCM_RATE_6M}, + /* MCS 25: SS 4, MOD: QPSK, CR 1/2 */ + {52000, 108000, CEIL(52000 * 10, 9), CEIL(108000 * 10, 9), 0xC8, + BRCM_RATE_12M}, + /* MCS 26: SS 4, MOD: QPSK, CR 3/4 */ + {78000, 162000, CEIL(78000 * 10, 9), CEIL(162000 * 10, 9), 0xCA, + BRCM_RATE_18M}, + /* MCS 27: SS 4, MOD: 16QAM, CR 1/2 */ + {104000, 216000, CEIL(104000 * 10, 9), CEIL(216000 * 10, 9), 0xD0, + BRCM_RATE_24M}, + /* MCS 28: SS 4, MOD: 16QAM, CR 3/4 */ + {156000, 324000, CEIL(156000 * 10, 9), CEIL(324000 * 10, 9), 0xD2, + BRCM_RATE_36M}, + /* MCS 29: SS 4, MOD: 64QAM, CR 2/3 */ + {208000, 432000, CEIL(208000 * 10, 9), CEIL(432000 * 10, 9), 0xD9, + BRCM_RATE_48M}, + /* MCS 30: SS 4, MOD: 64QAM, CR 3/4 */ + {234000, 486000, CEIL(234000 * 10, 9), CEIL(486000 * 10, 9), 0xDA, + BRCM_RATE_54M}, + /* MCS 31: SS 4, MOD: 64QAM, CR 5/6 */ + {260000, 540000, CEIL(260000 * 10, 9), CEIL(540000 * 10, 9), 0xDB, + BRCM_RATE_54M}, + /* MCS 32: SS 1, MOD: BPSK, CR 1/2 */ + {0, 6000, 0, CEIL(6000 * 10, 9), 0x00, BRCM_RATE_6M}, +}; + +/* + * phycfg for legacy OFDM frames: code rate, modulation scheme, spatial streams + * Number of spatial streams: always 1 other fields: refer to table 78 of + * section 17.3.2.2 of the original .11a standard + */ +struct legacy_phycfg { + u32 rate_ofdm; /* ofdm mac rate */ + /* phy ctl byte 3, code rate, modulation type, # of streams */ + u8 tx_phy_ctl3; +}; + +/* Number of legacy_rate_cfg entries in the table */ +#define LEGACY_PHYCFG_TABLE_SIZE 12 + +/* + * In CCK mode LPPHY overloads OFDM Modulation bits with CCK Data Rate + * Eventually MIMOPHY would also be converted to this format + * 0 = 1Mbps; 1 = 2Mbps; 2 = 5.5Mbps; 3 = 11Mbps + */ +static const struct +legacy_phycfg legacy_phycfg_table[LEGACY_PHYCFG_TABLE_SIZE] = { + {BRCM_RATE_1M, 0x00}, /* CCK 1Mbps, data rate 0 */ + {BRCM_RATE_2M, 0x08}, /* CCK 2Mbps, data rate 1 */ + {BRCM_RATE_5M5, 0x10}, /* CCK 5.5Mbps, data rate 2 */ + {BRCM_RATE_11M, 0x18}, /* CCK 11Mbps, data rate 3 */ + /* OFDM 6Mbps, code rate 1/2, BPSK, 1 spatial stream */ + {BRCM_RATE_6M, 0x00}, + /* OFDM 9Mbps, code rate 3/4, BPSK, 1 spatial stream */ + {BRCM_RATE_9M, 0x02}, + /* OFDM 12Mbps, code rate 1/2, QPSK, 1 spatial stream */ + {BRCM_RATE_12M, 0x08}, + /* OFDM 18Mbps, code rate 3/4, QPSK, 1 spatial stream */ + {BRCM_RATE_18M, 0x0A}, + /* OFDM 24Mbps, code rate 1/2, 16-QAM, 1 spatial stream */ + {BRCM_RATE_24M, 0x10}, + /* OFDM 36Mbps, code rate 3/4, 16-QAM, 1 spatial stream */ + {BRCM_RATE_36M, 0x12}, + /* OFDM 48Mbps, code rate 2/3, 64-QAM, 1 spatial stream */ + {BRCM_RATE_48M, 0x19}, + /* OFDM 54Mbps, code rate 3/4, 64-QAM, 1 spatial stream */ + {BRCM_RATE_54M, 0x1A}, +}; + +/* Hardware rates (also encodes default basic rates) */ + +const struct brcms_c_rateset cck_ofdm_mimo_rates = { + 12, + /* 1b, 2b, 5.5b, 6, 9, 11b, 12, 18, 24, 36, 48, */ + { 0x82, 0x84, 0x8b, 0x0c, 0x12, 0x96, 0x18, 0x24, 0x30, 0x48, 0x60, + /* 54 Mbps */ + 0x6c}, + 0x00, + { 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +const struct brcms_c_rateset ofdm_mimo_rates = { + 8, + /* 6b, 9, 12b, 18, 24b, 36, 48, 54 Mbps */ + { 0x8c, 0x12, 0x98, 0x24, 0xb0, 0x48, 0x60, 0x6c}, + 0x00, + { 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +/* Default ratesets that include MCS32 for 40BW channels */ +static const struct brcms_c_rateset cck_ofdm_40bw_mimo_rates = { + 12, + /* 1b, 2b, 5.5b, 6, 9, 11b, 12, 18, 24, 36, 48 */ + { 0x82, 0x84, 0x8b, 0x0c, 0x12, 0x96, 0x18, 0x24, 0x30, 0x48, 0x60, + /* 54 Mbps */ + 0x6c}, + 0x00, + { 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +static const struct brcms_c_rateset ofdm_40bw_mimo_rates = { + 8, + /* 6b, 9, 12b, 18, 24b, 36, 48, 54 Mbps */ + { 0x8c, 0x12, 0x98, 0x24, 0xb0, 0x48, 0x60, 0x6c}, + 0x00, + { 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +const struct brcms_c_rateset cck_ofdm_rates = { + 12, + /* 1b, 2b, 5.5b, 6, 9, 11b, 12, 18, 24, 36, 48,*/ + { 0x82, 0x84, 0x8b, 0x0c, 0x12, 0x96, 0x18, 0x24, 0x30, 0x48, 0x60, + /*54 Mbps */ + 0x6c}, + 0x00, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +const struct brcms_c_rateset gphy_legacy_rates = { + 4, + /* 1b, 2b, 5.5b, 11b Mbps */ + { 0x82, 0x84, 0x8b, 0x96}, + 0x00, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +const struct brcms_c_rateset ofdm_rates = { + 8, + /* 6b, 9, 12b, 18, 24b, 36, 48, 54 Mbps */ + { 0x8c, 0x12, 0x98, 0x24, 0xb0, 0x48, 0x60, 0x6c}, + 0x00, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +const struct brcms_c_rateset cck_rates = { + 4, + /* 1b, 2b, 5.5, 11 Mbps */ + { 0x82, 0x84, 0x0b, 0x16}, + 0x00, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +/* check if rateset is valid. + * if check_brate is true, rateset without a basic rate is considered NOT valid. + */ +static bool brcms_c_rateset_valid(struct brcms_c_rateset *rs, bool check_brate) +{ + uint idx; + + if (!rs->count) + return false; + + if (!check_brate) + return true; + + /* error if no basic rates */ + for (idx = 0; idx < rs->count; idx++) { + if (rs->rates[idx] & BRCMS_RATE_FLAG) + return true; + } + return false; +} + +void brcms_c_rateset_mcs_upd(struct brcms_c_rateset *rs, u8 txstreams) +{ + int i; + for (i = txstreams; i < MAX_STREAMS_SUPPORTED; i++) + rs->mcs[i] = 0; +} + +/* + * filter based on hardware rateset, and sort filtered rateset with basic + * bit(s) preserved, and check if resulting rateset is valid. +*/ +bool +brcms_c_rate_hwrs_filter_sort_validate(struct brcms_c_rateset *rs, + const struct brcms_c_rateset *hw_rs, + bool check_brate, u8 txstreams) +{ + u8 rateset[BRCM_MAXRATE + 1]; + u8 r; + uint count; + uint i; + + memset(rateset, 0, sizeof(rateset)); + count = rs->count; + + for (i = 0; i < count; i++) { + /* mask off "basic rate" bit, BRCMS_RATE_FLAG */ + r = (int)rs->rates[i] & BRCMS_RATE_MASK; + if ((r > BRCM_MAXRATE) || (rate_info[r] == 0)) + continue; + rateset[r] = rs->rates[i]; /* preserve basic bit! */ + } + + /* fill out the rates in order, looking at only supported rates */ + count = 0; + for (i = 0; i < hw_rs->count; i++) { + r = hw_rs->rates[i] & BRCMS_RATE_MASK; + if (rateset[r]) + rs->rates[count++] = rateset[r]; + } + + rs->count = count; + + /* only set the mcs rate bit if the equivalent hw mcs bit is set */ + for (i = 0; i < MCSSET_LEN; i++) + rs->mcs[i] = (rs->mcs[i] & hw_rs->mcs[i]); + + if (brcms_c_rateset_valid(rs, check_brate)) + return true; + else + return false; +} + +/* calculate the rate of a rx'd frame and return it as a ratespec */ +u32 brcms_c_compute_rspec(struct d11rxhdr *rxh, u8 *plcp) +{ + int phy_type; + u32 rspec = PHY_TXC1_BW_20MHZ << RSPEC_BW_SHIFT; + + phy_type = + ((rxh->RxChan & RXS_CHAN_PHYTYPE_MASK) >> RXS_CHAN_PHYTYPE_SHIFT); + + if ((phy_type == PHY_TYPE_N) || (phy_type == PHY_TYPE_SSN) || + (phy_type == PHY_TYPE_LCN) || (phy_type == PHY_TYPE_HT)) { + switch (rxh->PhyRxStatus_0 & PRXS0_FT_MASK) { + case PRXS0_CCK: + rspec = + cck_phy2mac_rate( + ((struct cck_phy_hdr *) plcp)->signal); + break; + case PRXS0_OFDM: + rspec = + ofdm_phy2mac_rate( + ((struct ofdm_phy_hdr *) plcp)->rlpt[0]); + break; + case PRXS0_PREN: + rspec = (plcp[0] & MIMO_PLCP_MCS_MASK) | RSPEC_MIMORATE; + if (plcp[0] & MIMO_PLCP_40MHZ) { + /* indicate rspec is for 40 MHz mode */ + rspec &= ~RSPEC_BW_MASK; + rspec |= (PHY_TXC1_BW_40MHZ << RSPEC_BW_SHIFT); + } + break; + case PRXS0_STDN: + /* fallthru */ + default: + /* not supported, error condition */ + break; + } + if (plcp3_issgi(plcp[3])) + rspec |= RSPEC_SHORT_GI; + } else + if ((phy_type == PHY_TYPE_A) || (rxh->PhyRxStatus_0 & PRXS0_OFDM)) + rspec = ofdm_phy2mac_rate( + ((struct ofdm_phy_hdr *) plcp)->rlpt[0]); + else + rspec = cck_phy2mac_rate( + ((struct cck_phy_hdr *) plcp)->signal); + + return rspec; +} + +/* copy rateset src to dst as-is (no masking or sorting) */ +void brcms_c_rateset_copy(const struct brcms_c_rateset *src, + struct brcms_c_rateset *dst) +{ + memcpy(dst, src, sizeof(struct brcms_c_rateset)); +} + +/* + * Copy and selectively filter one rateset to another. + * 'basic_only' means only copy basic rates. + * 'rates' indicates cck (11b) and ofdm rates combinations. + * - 0: cck and ofdm + * - 1: cck only + * - 2: ofdm only + * 'xmask' is the copy mask (typically 0x7f or 0xff). + */ +void +brcms_c_rateset_filter(struct brcms_c_rateset *src, struct brcms_c_rateset *dst, + bool basic_only, u8 rates, uint xmask, bool mcsallow) +{ + uint i; + uint r; + uint count; + + count = 0; + for (i = 0; i < src->count; i++) { + r = src->rates[i]; + if (basic_only && !(r & BRCMS_RATE_FLAG)) + continue; + if (rates == BRCMS_RATES_CCK && + is_ofdm_rate((r & BRCMS_RATE_MASK))) + continue; + if (rates == BRCMS_RATES_OFDM && + is_cck_rate((r & BRCMS_RATE_MASK))) + continue; + dst->rates[count++] = r & xmask; + } + dst->count = count; + dst->htphy_membership = src->htphy_membership; + + if (mcsallow && rates != BRCMS_RATES_CCK) + memcpy(&dst->mcs[0], &src->mcs[0], MCSSET_LEN); + else + brcms_c_rateset_mcs_clear(dst); +} + +/* select rateset for a given phy_type and bandtype and filter it, sort it + * and fill rs_tgt with result + */ +void +brcms_c_rateset_default(struct brcms_c_rateset *rs_tgt, + const struct brcms_c_rateset *rs_hw, + uint phy_type, int bandtype, bool cck_only, + uint rate_mask, bool mcsallow, u8 bw, u8 txstreams) +{ + const struct brcms_c_rateset *rs_dflt; + struct brcms_c_rateset rs_sel; + if ((PHYTYPE_IS(phy_type, PHY_TYPE_HT)) || + (PHYTYPE_IS(phy_type, PHY_TYPE_N)) || + (PHYTYPE_IS(phy_type, PHY_TYPE_LCN)) || + (PHYTYPE_IS(phy_type, PHY_TYPE_SSN))) { + if (bandtype == BRCM_BAND_5G) + rs_dflt = (bw == BRCMS_20_MHZ ? + &ofdm_mimo_rates : &ofdm_40bw_mimo_rates); + else + rs_dflt = (bw == BRCMS_20_MHZ ? + &cck_ofdm_mimo_rates : + &cck_ofdm_40bw_mimo_rates); + } else if (PHYTYPE_IS(phy_type, PHY_TYPE_LP)) { + rs_dflt = (bandtype == BRCM_BAND_5G) ? + &ofdm_rates : &cck_ofdm_rates; + } else if (PHYTYPE_IS(phy_type, PHY_TYPE_A)) { + rs_dflt = &ofdm_rates; + } else if (PHYTYPE_IS(phy_type, PHY_TYPE_G)) { + rs_dflt = &cck_ofdm_rates; + } else { + /* should not happen, error condition */ + rs_dflt = &cck_rates; /* force cck */ + } + + /* if hw rateset is not supplied, assign selected rateset to it */ + if (!rs_hw) + rs_hw = rs_dflt; + + brcms_c_rateset_copy(rs_dflt, &rs_sel); + brcms_c_rateset_mcs_upd(&rs_sel, txstreams); + brcms_c_rateset_filter(&rs_sel, rs_tgt, false, + cck_only ? BRCMS_RATES_CCK : BRCMS_RATES_CCK_OFDM, + rate_mask, mcsallow); + brcms_c_rate_hwrs_filter_sort_validate(rs_tgt, rs_hw, false, + mcsallow ? txstreams : 1); +} + +s16 brcms_c_rate_legacy_phyctl(uint rate) +{ + uint i; + for (i = 0; i < LEGACY_PHYCFG_TABLE_SIZE; i++) + if (rate == legacy_phycfg_table[i].rate_ofdm) + return legacy_phycfg_table[i].tx_phy_ctl3; + + return -1; +} + +void brcms_c_rateset_mcs_clear(struct brcms_c_rateset *rateset) +{ + uint i; + for (i = 0; i < MCSSET_LEN; i++) + rateset->mcs[i] = 0; +} + +void brcms_c_rateset_mcs_build(struct brcms_c_rateset *rateset, u8 txstreams) +{ + memcpy(&rateset->mcs[0], &cck_ofdm_mimo_rates.mcs[0], MCSSET_LEN); + brcms_c_rateset_mcs_upd(rateset, txstreams); +} + +/* Based on bandwidth passed, allow/disallow MCS 32 in the rateset */ +void brcms_c_rateset_bw_mcs_filter(struct brcms_c_rateset *rateset, u8 bw) +{ + if (bw == BRCMS_40_MHZ) + setbit(rateset->mcs, 32); + else + clrbit(rateset->mcs, 32); +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/rate.h b/drivers/net/wireless/brcm80211/brcmsmac/rate.h new file mode 100644 index 0000000..980d578 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/rate.h @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_RATE_H_ +#define _BRCM_RATE_H_ + +#include "types.h" +#include "d11.h" +#include "phy_hal.h" + +extern const u8 rate_info[]; +extern const struct brcms_c_rateset cck_ofdm_mimo_rates; +extern const struct brcms_c_rateset ofdm_mimo_rates; +extern const struct brcms_c_rateset cck_ofdm_rates; +extern const struct brcms_c_rateset ofdm_rates; +extern const struct brcms_c_rateset cck_rates; +extern const struct brcms_c_rateset gphy_legacy_rates; +extern const struct brcms_c_rateset rate_limit_1_2; + +struct brcms_mcs_info { + /* phy rate in kbps [20Mhz] */ + u32 phy_rate_20; + /* phy rate in kbps [40Mhz] */ + u32 phy_rate_40; + /* phy rate in kbps [20Mhz] with SGI */ + u32 phy_rate_20_sgi; + /* phy rate in kbps [40Mhz] with SGI */ + u32 phy_rate_40_sgi; + /* phy ctl byte 3, code rate, modulation type, # of streams */ + u8 tx_phy_ctl3; + /* matching legacy ofdm rate in 500bkps */ + u8 leg_ofdm; +}; + +#define BRCMS_MAXMCS 32 /* max valid mcs index */ +#define MCS_TABLE_SIZE 33 /* Number of mcs entries in the table */ +extern const struct brcms_mcs_info mcs_table[]; + +#define MCS_TXS_MASK 0xc0 /* num tx streams - 1 bit mask */ +#define MCS_TXS_SHIFT 6 /* num tx streams - 1 bit shift */ + +/* returns num tx streams - 1 */ +static inline u8 mcs_2_txstreams(u8 mcs) +{ + return (mcs_table[mcs].tx_phy_ctl3 & MCS_TXS_MASK) >> MCS_TXS_SHIFT; +} + +static inline uint mcs_2_rate(u8 mcs, bool is40, bool sgi) +{ + if (sgi) { + if (is40) + return mcs_table[mcs].phy_rate_40_sgi; + return mcs_table[mcs].phy_rate_20_sgi; + } + if (is40) + return mcs_table[mcs].phy_rate_40; + + return mcs_table[mcs].phy_rate_20; +} + +/* Macro to use the rate_info table */ +#define BRCMS_RATE_MASK_FULL 0xff /* Rate value mask with basic rate flag */ + +/* + * rate spec : holds rate and mode specific information required to generate a + * tx frame. Legacy CCK and OFDM information is held in the same manner as was + * done in the past (in the lower byte) the upper 3 bytes primarily hold MIMO + * specific information + */ + +/* rate spec bit fields */ + +/* Either 500Kbps units or MIMO MCS idx */ +#define RSPEC_RATE_MASK 0x0000007F +/* mimo MCS is stored in RSPEC_RATE_MASK */ +#define RSPEC_MIMORATE 0x08000000 +/* mimo bw mask */ +#define RSPEC_BW_MASK 0x00000700 +/* mimo bw shift */ +#define RSPEC_BW_SHIFT 8 +/* mimo Space/Time/Frequency mode mask */ +#define RSPEC_STF_MASK 0x00003800 +/* mimo Space/Time/Frequency mode shift */ +#define RSPEC_STF_SHIFT 11 +/* mimo coding type mask */ +#define RSPEC_CT_MASK 0x0000C000 +/* mimo coding type shift */ +#define RSPEC_CT_SHIFT 14 +/* mimo num STC streams per PLCP defn. */ +#define RSPEC_STC_MASK 0x00300000 +/* mimo num STC streams per PLCP defn. */ +#define RSPEC_STC_SHIFT 20 +/* mimo bit indicates adv coding in use */ +#define RSPEC_LDPC_CODING 0x00400000 +/* mimo bit indicates short GI in use */ +#define RSPEC_SHORT_GI 0x00800000 +/* bit indicates override both rate & mode */ +#define RSPEC_OVERRIDE 0x80000000 +/* bit indicates override rate only */ +#define RSPEC_OVERRIDE_MCS_ONLY 0x40000000 + +static inline bool rspec_active(u32 rspec) +{ + return rspec & (RSPEC_RATE_MASK | RSPEC_MIMORATE); +} + +static inline u8 rspec_phytxbyte2(u32 rspec) +{ + return (rspec & 0xff00) >> 8; +} + +static inline u32 rspec_get_bw(u32 rspec) +{ + return (rspec & RSPEC_BW_MASK) >> RSPEC_BW_SHIFT; +} + +static inline bool rspec_issgi(u32 rspec) +{ + return (rspec & RSPEC_SHORT_GI) == RSPEC_SHORT_GI; +} + +static inline bool rspec_is40mhz(u32 rspec) +{ + u32 bw = rspec_get_bw(rspec); + + return bw == PHY_TXC1_BW_40MHZ || bw == PHY_TXC1_BW_40MHZ_DUP; +} + +static inline uint rspec2rate(u32 rspec) +{ + if (rspec & RSPEC_MIMORATE) + return mcs_2_rate(rspec & RSPEC_RATE_MASK, rspec_is40mhz(rspec), + rspec_issgi(rspec)); + return rspec & RSPEC_RATE_MASK; +} + +static inline u8 rspec_mimoplcp3(u32 rspec) +{ + return (rspec & 0xf00000) >> 16; +} + +static inline bool plcp3_issgi(u8 plcp) +{ + return (plcp & (RSPEC_SHORT_GI >> 16)) != 0; +} + +static inline uint rspec_stc(u32 rspec) +{ + return (rspec & RSPEC_STC_MASK) >> RSPEC_STC_SHIFT; +} + +static inline uint rspec_stf(u32 rspec) +{ + return (rspec & RSPEC_STF_MASK) >> RSPEC_STF_SHIFT; +} + +static inline bool is_mcs_rate(u32 ratespec) +{ + return (ratespec & RSPEC_MIMORATE) != 0; +} + +static inline bool is_ofdm_rate(u32 ratespec) +{ + return !is_mcs_rate(ratespec) && + (rate_info[ratespec & RSPEC_RATE_MASK] & BRCMS_RATE_FLAG); +} + +static inline bool is_cck_rate(u32 ratespec) +{ + u32 rate = (ratespec & BRCMS_RATE_MASK); + + return !is_mcs_rate(ratespec) && ( + rate == BRCM_RATE_1M || rate == BRCM_RATE_2M || + rate == BRCM_RATE_5M5 || rate == BRCM_RATE_11M); +} + +static inline bool is_single_stream(u8 mcs) +{ + return mcs <= HIGHEST_SINGLE_STREAM_MCS || mcs == 32; +} + +static inline u8 cck_rspec(u8 cck) +{ + return cck & RSPEC_RATE_MASK; +} + +/* Convert encoded rate value in plcp header to numerical rates in 500 KHz + * increments */ +static inline u8 ofdm_phy2mac_rate(u8 rlpt) +{ + return wlc_phy_get_ofdm_rate_lookup()[rlpt & 0x7]; +} + +static inline u8 cck_phy2mac_rate(u8 signal) +{ + return signal/5; +} + +/* Rates specified in brcms_c_rateset_filter() */ +#define BRCMS_RATES_CCK_OFDM 0 +#define BRCMS_RATES_CCK 1 +#define BRCMS_RATES_OFDM 2 + +/* sanitize, and sort a rateset with the basic bit(s) preserved, validate + * rateset */ +extern bool +brcms_c_rate_hwrs_filter_sort_validate(struct brcms_c_rateset *rs, + const struct brcms_c_rateset *hw_rs, + bool check_brate, u8 txstreams); +/* copy rateset src to dst as-is (no masking or sorting) */ +extern void brcms_c_rateset_copy(const struct brcms_c_rateset *src, + struct brcms_c_rateset *dst); + +/* would be nice to have these documented ... */ +extern u32 brcms_c_compute_rspec(struct d11rxhdr *rxh, u8 *plcp); + +extern void brcms_c_rateset_filter(struct brcms_c_rateset *src, + struct brcms_c_rateset *dst, bool basic_only, u8 rates, uint xmask, + bool mcsallow); + +extern void +brcms_c_rateset_default(struct brcms_c_rateset *rs_tgt, + const struct brcms_c_rateset *rs_hw, uint phy_type, + int bandtype, bool cck_only, uint rate_mask, + bool mcsallow, u8 bw, u8 txstreams); + +extern s16 brcms_c_rate_legacy_phyctl(uint rate); + +extern void brcms_c_rateset_mcs_upd(struct brcms_c_rateset *rs, u8 txstreams); +extern void brcms_c_rateset_mcs_clear(struct brcms_c_rateset *rateset); +extern void brcms_c_rateset_mcs_build(struct brcms_c_rateset *rateset, + u8 txstreams); +extern void brcms_c_rateset_bw_mcs_filter(struct brcms_c_rateset *rateset, + u8 bw); + +#endif /* _BRCM_RATE_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/scb.h b/drivers/net/wireless/brcm80211/brcmsmac/scb.h new file mode 100644 index 0000000..3a3d736 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/scb.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_SCB_H_ +#define _BRCM_SCB_H_ + +#include +#include +#include +#include "types.h" + +#define AMPDU_TX_BA_MAX_WSIZE 64 /* max Tx ba window size (in pdu) */ + +#define AMPDU_MAX_SCB_TID NUMPRIO + +/* scb flags */ +#define SCB_WMECAP 0x0040 +#define SCB_HTCAP 0x10000 /* HT (MIMO) capable device */ +#define SCB_IS40 0x80000 /* 40MHz capable */ +#define SCB_STBCCAP 0x40000000 /* STBC Capable */ + +#define SCB_MAGIC 0xbeefcafe + +/* structure to store per-tid state for the ampdu initiator */ +struct scb_ampdu_tid_ini { + u8 tid; /* initiator tid for easy lookup */ + /* tx retry count; indexed by seq modulo */ + u8 txretry[AMPDU_TX_BA_MAX_WSIZE]; + struct scb *scb; /* backptr for easy lookup */ + u8 ba_wsize; /* negotiated ba window size (in pdu) */ +}; + +struct scb_ampdu { + struct scb *scb; /* back pointer for easy reference */ + u8 mpdu_density; /* mpdu density */ + u8 max_pdu; /* max pdus allowed in ampdu */ + u8 release; /* # of mpdus released at a time */ + u16 min_len; /* min mpdu len to support the density */ + u32 max_rx_ampdu_bytes; /* max ampdu rcv length; 8k, 16k, 32k, 64k */ + + /* + * This could easily be a ini[] pointer and we keep this info in wl + * itself instead of having mac80211 hold it for us. Also could be made + * dynamic per tid instead of static. + */ + /* initiator info - per tid (NUMPRIO): */ + struct scb_ampdu_tid_ini ini[AMPDU_MAX_SCB_TID]; +}; + +/* station control block - one per remote MAC address */ +struct scb { + u32 magic; + u32 flags; /* various bit flags as defined below */ + u32 flags2; /* various bit flags2 as defined below */ + u8 state; /* current state bitfield of auth/assoc process */ + u8 ea[ETH_ALEN]; /* station address */ + uint fragresid[NUMPRIO];/* #bytes unused in frag buffer per prio */ + + u16 seqctl[NUMPRIO]; /* seqctl of last received frame (for dups) */ + /* seqctl of last received frame (for dups) for non-QoS data and + * management */ + u16 seqctl_nonqos; + u16 seqnum[NUMPRIO];/* WME: driver maintained sw seqnum per priority */ + + struct scb_ampdu scb_ampdu; /* AMPDU state including per tid info */ +}; + +#endif /* _BRCM_SCB_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/stf.c b/drivers/net/wireless/brcm80211/brcmsmac/stf.c new file mode 100644 index 0000000..dd91627 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/stf.c @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "types.h" +#include "d11.h" +#include "rate.h" +#include "phy/phy_hal.h" +#include "channel.h" +#include "main.h" +#include "stf.h" +#include "debug.h" + +#define MIN_SPATIAL_EXPANSION 0 +#define MAX_SPATIAL_EXPANSION 1 + +#define BRCMS_STF_SS_STBC_RX(wlc) (BRCMS_ISNPHY(wlc->band) && \ + NREV_GT(wlc->band->phyrev, 3) && NREV_LE(wlc->band->phyrev, 6)) + +#define NSTS_1 1 +#define NSTS_2 2 +#define NSTS_3 3 +#define NSTS_4 4 + +static const u8 txcore_default[5] = { + (0), /* bitmap of the core enabled */ + (0x01), /* For Nsts = 1, enable core 1 */ + (0x03), /* For Nsts = 2, enable core 1 & 2 */ + (0x07), /* For Nsts = 3, enable core 1, 2 & 3 */ + (0x0f) /* For Nsts = 4, enable all cores */ +}; + +static void brcms_c_stf_stbc_rx_ht_update(struct brcms_c_info *wlc, int val) +{ + /* MIMOPHYs rev3-6 cannot receive STBC with only one rx core active */ + if (BRCMS_STF_SS_STBC_RX(wlc)) { + if ((wlc->stf->rxstreams == 1) && (val != HT_CAP_RX_STBC_NO)) + return; + } + + if (wlc->pub->up) { + brcms_c_update_beacon(wlc); + brcms_c_update_probe_resp(wlc, true); + } +} + +/* + * every WLC_TEMPSENSE_PERIOD seconds temperature check to decide whether to + * turn on/off txchain. + */ +void brcms_c_tempsense_upd(struct brcms_c_info *wlc) +{ + struct brcms_phy_pub *pi = wlc->band->pi; + uint active_chains, txchain; + + /* Check if the chip is too hot. Disable one Tx chain, if it is */ + /* high 4 bits are for Rx chain, low 4 bits are for Tx chain */ + active_chains = wlc_phy_stf_chain_active_get(pi); + txchain = active_chains & 0xf; + + if (wlc->stf->txchain == wlc->stf->hw_txchain) { + if (txchain && (txchain < wlc->stf->hw_txchain)) + /* turn off 1 tx chain */ + brcms_c_stf_txchain_set(wlc, txchain, true); + } else if (wlc->stf->txchain < wlc->stf->hw_txchain) { + if (txchain == wlc->stf->hw_txchain) + /* turn back on txchain */ + brcms_c_stf_txchain_set(wlc, txchain, true); + } +} + +void +brcms_c_stf_ss_algo_channel_get(struct brcms_c_info *wlc, u16 *ss_algo_channel, + u16 chanspec) +{ + struct tx_power power; + u8 siso_mcs_id, cdd_mcs_id, stbc_mcs_id; + + /* Clear previous settings */ + *ss_algo_channel = 0; + + if (!wlc->pub->up) { + *ss_algo_channel = (u16) -1; + return; + } + + wlc_phy_txpower_get_current(wlc->band->pi, &power, + CHSPEC_CHANNEL(chanspec)); + + siso_mcs_id = (CHSPEC_IS40(chanspec)) ? + WL_TX_POWER_MCS40_SISO_FIRST : WL_TX_POWER_MCS20_SISO_FIRST; + cdd_mcs_id = (CHSPEC_IS40(chanspec)) ? + WL_TX_POWER_MCS40_CDD_FIRST : WL_TX_POWER_MCS20_CDD_FIRST; + stbc_mcs_id = (CHSPEC_IS40(chanspec)) ? + WL_TX_POWER_MCS40_STBC_FIRST : WL_TX_POWER_MCS20_STBC_FIRST; + + /* criteria to choose stf mode */ + + /* + * the "+3dbm (12 0.25db units)" is to account for the fact that with + * CDD, tx occurs on both chains + */ + if (power.target[siso_mcs_id] > (power.target[cdd_mcs_id] + 12)) + setbit(ss_algo_channel, PHY_TXC1_MODE_SISO); + else + setbit(ss_algo_channel, PHY_TXC1_MODE_CDD); + + /* + * STBC is ORed into to algo channel as STBC requires per-packet SCB + * capability check so cannot be default mode of operation. One of + * SISO, CDD have to be set + */ + if (power.target[siso_mcs_id] <= (power.target[stbc_mcs_id] + 12)) + setbit(ss_algo_channel, PHY_TXC1_MODE_STBC); +} + +static bool brcms_c_stf_stbc_tx_set(struct brcms_c_info *wlc, s32 int_val) +{ + if ((int_val != AUTO) && (int_val != OFF) && (int_val != ON)) + return false; + + if ((int_val == ON) && (wlc->stf->txstreams == 1)) + return false; + + wlc->bandstate[BAND_2G_INDEX]->band_stf_stbc_tx = (s8) int_val; + wlc->bandstate[BAND_5G_INDEX]->band_stf_stbc_tx = (s8) int_val; + + return true; +} + +bool brcms_c_stf_stbc_rx_set(struct brcms_c_info *wlc, s32 int_val) +{ + if ((int_val != HT_CAP_RX_STBC_NO) + && (int_val != HT_CAP_RX_STBC_ONE_STREAM)) + return false; + + if (BRCMS_STF_SS_STBC_RX(wlc)) { + if ((int_val != HT_CAP_RX_STBC_NO) + && (wlc->stf->rxstreams == 1)) + return false; + } + + brcms_c_stf_stbc_rx_ht_update(wlc, int_val); + return true; +} + +static int brcms_c_stf_txcore_set(struct brcms_c_info *wlc, u8 Nsts, + u8 core_mask) +{ + brcms_dbg_ht(wlc->hw->d11core, "wl%d: Nsts %d core_mask %x\n", + wlc->pub->unit, Nsts, core_mask); + + if (hweight8(core_mask) > wlc->stf->txstreams) + core_mask = 0; + + if ((hweight8(core_mask) == wlc->stf->txstreams) && + ((core_mask & ~wlc->stf->txchain) + || !(core_mask & wlc->stf->txchain))) + core_mask = wlc->stf->txchain; + + wlc->stf->txcore[Nsts] = core_mask; + /* Nsts = 1..4, txcore index = 1..4 */ + if (Nsts == 1) { + /* Needs to update beacon and ucode generated response + * frames when 1 stream core map changed + */ + wlc->stf->phytxant = core_mask << PHY_TXC_ANT_SHIFT; + brcms_b_txant_set(wlc->hw, wlc->stf->phytxant); + if (wlc->clk) { + brcms_c_suspend_mac_and_wait(wlc); + brcms_c_beacon_phytxctl_txant_upd(wlc, wlc->bcn_rspec); + brcms_c_enable_mac(wlc); + } + } + + return 0; +} + +static int brcms_c_stf_spatial_policy_set(struct brcms_c_info *wlc, int val) +{ + int i; + u8 core_mask = 0; + + brcms_dbg_ht(wlc->hw->d11core, "wl%d: val %x\n", wlc->pub->unit, + val); + + wlc->stf->spatial_policy = (s8) val; + for (i = 1; i <= MAX_STREAMS_SUPPORTED; i++) { + core_mask = (val == MAX_SPATIAL_EXPANSION) ? + wlc->stf->txchain : txcore_default[i]; + brcms_c_stf_txcore_set(wlc, (u8) i, core_mask); + } + return 0; +} + +/* + * Centralized txant update function. call it whenever wlc->stf->txant and/or + * wlc->stf->txchain change. + * + * Antennas are controlled by ucode indirectly, which drives PHY or GPIO to + * achieve various tx/rx antenna selection schemes + * + * legacy phy, bit 6 and bit 7 means antenna 0 and 1 respectively, bit6+bit7 + * means auto(last rx). + * for NREV<3, bit 6 and bit 7 means antenna 0 and 1 respectively, bit6+bit7 + * means last rx and do tx-antenna selection for SISO transmissions + * for NREV=3, bit 6 and bit _8_ means antenna 0 and 1 respectively, bit6+bit7 + * means last rx and do tx-antenna selection for SISO transmissions + * for NREV>=7, bit 6 and bit 7 mean antenna 0 and 1 respectively, nit6+bit7 + * means both cores active +*/ +static void _brcms_c_stf_phy_txant_upd(struct brcms_c_info *wlc) +{ + s8 txant; + + txant = (s8) wlc->stf->txant; + if (BRCMS_PHY_11N_CAP(wlc->band)) { + if (txant == ANT_TX_FORCE_0) { + wlc->stf->phytxant = PHY_TXC_ANT_0; + } else if (txant == ANT_TX_FORCE_1) { + wlc->stf->phytxant = PHY_TXC_ANT_1; + + if (BRCMS_ISNPHY(wlc->band) && + NREV_GE(wlc->band->phyrev, 3) + && NREV_LT(wlc->band->phyrev, 7)) + wlc->stf->phytxant = PHY_TXC_ANT_2; + } else { + if (BRCMS_ISLCNPHY(wlc->band) || + BRCMS_ISSSLPNPHY(wlc->band)) + wlc->stf->phytxant = PHY_TXC_LCNPHY_ANT_LAST; + else { + /* catch out of sync wlc->stf->txcore */ + WARN_ON(wlc->stf->txchain <= 0); + wlc->stf->phytxant = + wlc->stf->txchain << PHY_TXC_ANT_SHIFT; + } + } + } else { + if (txant == ANT_TX_FORCE_0) + wlc->stf->phytxant = PHY_TXC_OLD_ANT_0; + else if (txant == ANT_TX_FORCE_1) + wlc->stf->phytxant = PHY_TXC_OLD_ANT_1; + else + wlc->stf->phytxant = PHY_TXC_OLD_ANT_LAST; + } + + brcms_b_txant_set(wlc->hw, wlc->stf->phytxant); +} + +int brcms_c_stf_txchain_set(struct brcms_c_info *wlc, s32 int_val, bool force) +{ + u8 txchain = (u8) int_val; + u8 txstreams; + uint i; + + if (wlc->stf->txchain == txchain) + return 0; + + if ((txchain & ~wlc->stf->hw_txchain) + || !(txchain & wlc->stf->hw_txchain)) + return -EINVAL; + + /* + * if nrate override is configured to be non-SISO STF mode, reject + * reducing txchain to 1 + */ + txstreams = (u8) hweight8(txchain); + if (txstreams > MAX_STREAMS_SUPPORTED) + return -EINVAL; + + wlc->stf->txchain = txchain; + wlc->stf->txstreams = txstreams; + brcms_c_stf_stbc_tx_set(wlc, wlc->band->band_stf_stbc_tx); + brcms_c_stf_ss_update(wlc, wlc->bandstate[BAND_2G_INDEX]); + brcms_c_stf_ss_update(wlc, wlc->bandstate[BAND_5G_INDEX]); + wlc->stf->txant = + (wlc->stf->txstreams == 1) ? ANT_TX_FORCE_0 : ANT_TX_DEF; + _brcms_c_stf_phy_txant_upd(wlc); + + wlc_phy_stf_chain_set(wlc->band->pi, wlc->stf->txchain, + wlc->stf->rxchain); + + for (i = 1; i <= MAX_STREAMS_SUPPORTED; i++) + brcms_c_stf_txcore_set(wlc, (u8) i, txcore_default[i]); + + return 0; +} + +/* + * update wlc->stf->ss_opmode which represents the operational stf_ss mode + * we're using + */ +int brcms_c_stf_ss_update(struct brcms_c_info *wlc, struct brcms_band *band) +{ + int ret_code = 0; + u8 prev_stf_ss; + u8 upd_stf_ss; + + prev_stf_ss = wlc->stf->ss_opmode; + + /* + * NOTE: opmode can only be SISO or CDD as STBC is decided on a + * per-packet basis + */ + if (BRCMS_STBC_CAP_PHY(wlc) && + wlc->stf->ss_algosel_auto + && (wlc->stf->ss_algo_channel != (u16) -1)) { + upd_stf_ss = (wlc->stf->txstreams == 1 || + isset(&wlc->stf->ss_algo_channel, + PHY_TXC1_MODE_SISO)) ? + PHY_TXC1_MODE_SISO : PHY_TXC1_MODE_CDD; + } else { + if (wlc->band != band) + return ret_code; + upd_stf_ss = (wlc->stf->txstreams == 1) ? + PHY_TXC1_MODE_SISO : band->band_stf_ss_mode; + } + if (prev_stf_ss != upd_stf_ss) { + wlc->stf->ss_opmode = upd_stf_ss; + brcms_b_band_stf_ss_set(wlc->hw, upd_stf_ss); + } + + return ret_code; +} + +int brcms_c_stf_attach(struct brcms_c_info *wlc) +{ + wlc->bandstate[BAND_2G_INDEX]->band_stf_ss_mode = PHY_TXC1_MODE_SISO; + wlc->bandstate[BAND_5G_INDEX]->band_stf_ss_mode = PHY_TXC1_MODE_CDD; + + if (BRCMS_ISNPHY(wlc->band) && + (wlc_phy_txpower_hw_ctrl_get(wlc->band->pi) != PHY_TPC_HW_ON)) + wlc->bandstate[BAND_2G_INDEX]->band_stf_ss_mode = + PHY_TXC1_MODE_CDD; + brcms_c_stf_ss_update(wlc, wlc->bandstate[BAND_2G_INDEX]); + brcms_c_stf_ss_update(wlc, wlc->bandstate[BAND_5G_INDEX]); + + brcms_c_stf_stbc_rx_ht_update(wlc, HT_CAP_RX_STBC_NO); + wlc->bandstate[BAND_2G_INDEX]->band_stf_stbc_tx = OFF; + wlc->bandstate[BAND_5G_INDEX]->band_stf_stbc_tx = OFF; + + if (BRCMS_STBC_CAP_PHY(wlc)) { + wlc->stf->ss_algosel_auto = true; + /* Init the default value */ + wlc->stf->ss_algo_channel = (u16) -1; + } + return 0; +} + +void brcms_c_stf_detach(struct brcms_c_info *wlc) +{ +} + +void brcms_c_stf_phy_txant_upd(struct brcms_c_info *wlc) +{ + _brcms_c_stf_phy_txant_upd(wlc); +} + +void brcms_c_stf_phy_chain_calc(struct brcms_c_info *wlc) +{ + struct ssb_sprom *sprom = &wlc->hw->d11core->bus->sprom; + + /* get available rx/tx chains */ + wlc->stf->hw_txchain = sprom->txchain; + wlc->stf->hw_rxchain = sprom->rxchain; + + /* these parameter are intended to be used for all PHY types */ + if (wlc->stf->hw_txchain == 0 || wlc->stf->hw_txchain == 0xf) { + if (BRCMS_ISNPHY(wlc->band)) + wlc->stf->hw_txchain = TXCHAIN_DEF_NPHY; + else + wlc->stf->hw_txchain = TXCHAIN_DEF; + } + + wlc->stf->txchain = wlc->stf->hw_txchain; + wlc->stf->txstreams = (u8) hweight8(wlc->stf->hw_txchain); + + if (wlc->stf->hw_rxchain == 0 || wlc->stf->hw_rxchain == 0xf) { + if (BRCMS_ISNPHY(wlc->band)) + wlc->stf->hw_rxchain = RXCHAIN_DEF_NPHY; + else + wlc->stf->hw_rxchain = RXCHAIN_DEF; + } + + wlc->stf->rxchain = wlc->stf->hw_rxchain; + wlc->stf->rxstreams = (u8) hweight8(wlc->stf->hw_rxchain); + + /* initialize the txcore table */ + memcpy(wlc->stf->txcore, txcore_default, sizeof(wlc->stf->txcore)); + + /* default spatial_policy */ + wlc->stf->spatial_policy = MIN_SPATIAL_EXPANSION; + brcms_c_stf_spatial_policy_set(wlc, MIN_SPATIAL_EXPANSION); +} + +static u16 _brcms_c_stf_phytxchain_sel(struct brcms_c_info *wlc, + u32 rspec) +{ + u16 phytxant = wlc->stf->phytxant; + + if (rspec_stf(rspec) != PHY_TXC1_MODE_SISO) + phytxant = wlc->stf->txchain << PHY_TXC_ANT_SHIFT; + else if (wlc->stf->txant == ANT_TX_DEF) + phytxant = wlc->stf->txchain << PHY_TXC_ANT_SHIFT; + phytxant &= PHY_TXC_ANT_MASK; + return phytxant; +} + +u16 brcms_c_stf_phytxchain_sel(struct brcms_c_info *wlc, u32 rspec) +{ + return _brcms_c_stf_phytxchain_sel(wlc, rspec); +} + +u16 brcms_c_stf_d11hdrs_phyctl_txant(struct brcms_c_info *wlc, u32 rspec) +{ + u16 phytxant = wlc->stf->phytxant; + u16 mask = PHY_TXC_ANT_MASK; + + /* for non-siso rates or default setting, use the available chains */ + if (BRCMS_ISNPHY(wlc->band)) { + phytxant = _brcms_c_stf_phytxchain_sel(wlc, rspec); + mask = PHY_TXC_HTANT_MASK; + } + phytxant |= phytxant & mask; + return phytxant; +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/stf.h b/drivers/net/wireless/brcm80211/brcmsmac/stf.h new file mode 100644 index 0000000..19f6580 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/stf.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_STF_H_ +#define _BRCM_STF_H_ + +#include "types.h" + +extern int brcms_c_stf_attach(struct brcms_c_info *wlc); +extern void brcms_c_stf_detach(struct brcms_c_info *wlc); + +extern void brcms_c_tempsense_upd(struct brcms_c_info *wlc); +extern void brcms_c_stf_ss_algo_channel_get(struct brcms_c_info *wlc, + u16 *ss_algo_channel, + u16 chanspec); +extern int brcms_c_stf_ss_update(struct brcms_c_info *wlc, + struct brcms_band *band); +extern void brcms_c_stf_phy_txant_upd(struct brcms_c_info *wlc); +extern int brcms_c_stf_txchain_set(struct brcms_c_info *wlc, s32 int_val, + bool force); +extern bool brcms_c_stf_stbc_rx_set(struct brcms_c_info *wlc, s32 int_val); +extern void brcms_c_stf_phy_txant_upd(struct brcms_c_info *wlc); +extern void brcms_c_stf_phy_chain_calc(struct brcms_c_info *wlc); +extern u16 brcms_c_stf_phytxchain_sel(struct brcms_c_info *wlc, + u32 rspec); +extern u16 brcms_c_stf_d11hdrs_phyctl_txant(struct brcms_c_info *wlc, + u32 rspec); + +#endif /* _BRCM_STF_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/types.h b/drivers/net/wireless/brcm80211/brcmsmac/types.h new file mode 100644 index 0000000..ae1f3ad --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/types.h @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_TYPES_H_ +#define _BRCM_TYPES_H_ + +#include +#include + +#define WL_CHAN_FREQ_RANGE_2G 0 +#define WL_CHAN_FREQ_RANGE_5GL 1 +#define WL_CHAN_FREQ_RANGE_5GM 2 +#define WL_CHAN_FREQ_RANGE_5GH 3 + +/* boardflags */ + +/* Board has gpio 9 controlling the PA */ +#define BFL_PACTRL 0x00000002 +/* Not ok to power down the chip pll and oscillator */ +#define BFL_NOPLLDOWN 0x00000020 +/* Board supports the Front End Module */ +#define BFL_FEM 0x00000800 +/* Board has an external LNA in 2.4GHz band */ +#define BFL_EXTLNA 0x00001000 +/* Board has no PA */ +#define BFL_NOPA 0x00010000 +/* Power topology uses BUCKBOOST */ +#define BFL_BUCKBOOST 0x00200000 +/* Board has FEM and switch to share antenna w/ BT */ +#define BFL_FEM_BT 0x00400000 +/* Power topology doesn't use CBUCK */ +#define BFL_NOCBUCK 0x00800000 +/* Power topology uses PALDO */ +#define BFL_PALDO 0x02000000 +/* Board has an external LNA in 5GHz band */ +#define BFL_EXTLNA_5GHz 0x10000000 + +/* boardflags2 */ + +/* Board has an external rxbb regulator */ +#define BFL2_RXBB_INT_REG_DIS 0x00000001 +/* Flag to implement alternative A-band PLL settings */ +#define BFL2_APLL_WAR 0x00000002 +/* Board permits enabling TX Power Control */ +#define BFL2_TXPWRCTRL_EN 0x00000004 +/* Board supports the 2X4 diversity switch */ +#define BFL2_2X4_DIV 0x00000008 +/* Board supports 5G band power gain */ +#define BFL2_5G_PWRGAIN 0x00000010 +/* Board overrides ASPM and Clkreq settings */ +#define BFL2_PCIEWAR_OVR 0x00000020 +#define BFL2_LEGACY 0x00000080 +/* 4321mcm93 board uses Skyworks FEM */ +#define BFL2_SKWRKFEM_BRD 0x00000100 +/* Board has a WAR for clock-harmonic spurs */ +#define BFL2_SPUR_WAR 0x00000200 +/* Flag to narrow G-band PLL loop b/w */ +#define BFL2_GPLL_WAR 0x00000400 +/* Tx CCK pkts on Ant 0 only */ +#define BFL2_SINGLEANT_CCK 0x00001000 +/* WAR to reduce and avoid clock-harmonic spurs in 2G */ +#define BFL2_2G_SPUR_WAR 0x00002000 +/* Flag to widen G-band PLL loop b/w */ +#define BFL2_GPLL_WAR2 0x00010000 +#define BFL2_IPALVLSHIFT_3P3 0x00020000 +/* Use internal envelope detector for TX IQCAL */ +#define BFL2_INTERNDET_TXIQCAL 0x00040000 +/* Keep the buffered Xtal output from radio "ON". Most drivers will turn it + * off without this flag to save power. */ +#define BFL2_XTALBUFOUTEN 0x00080000 + +/* + * board specific GPIO assignment, gpio 0-3 are also customer-configurable + * led + */ + +/* bit 9 controls the PA on new 4306 boards */ +#define BOARD_GPIO_PACTRL 0x200 +#define BOARD_GPIO_12 0x1000 +#define BOARD_GPIO_13 0x2000 + +/* **** Core type/rev defaults **** */ +#define D11CONF 0x0fffffb0 /* Supported D11 revs: 4, 5, 7-27 + * also need to update wlc.h MAXCOREREV + */ + +#define NCONF 0x000001ff /* Supported nphy revs: + * 0 4321a0 + * 1 4321a1 + * 2 4321b0/b1/c0/c1 + * 3 4322a0 + * 4 4322a1 + * 5 4716a0 + * 6 43222a0, 43224a0 + * 7 43226a0 + * 8 5357a0, 43236a0 + */ + +#define LCNCONF 0x00000007 /* Supported lcnphy revs: + * 0 4313a0, 4336a0, 4330a0 + * 1 + * 2 4330a0 + */ + +#define SSLPNCONF 0x0000000f /* Supported sslpnphy revs: + * 0 4329a0/k0 + * 1 4329b0/4329C0 + * 2 4319a0 + * 3 5356a0 + */ + +/******************************************************************** + * Phy/Core Configuration. Defines macros to to check core phy/rev * + * compile-time configuration. Defines default core support. * + * ****************************************************************** + */ + +/* Basic macros to check a configuration bitmask */ + +#define CONF_HAS(config, val) ((config) & (1 << (val))) +#define CONF_MSK(config, mask) ((config) & (mask)) +#define MSK_RANGE(low, hi) ((1 << ((hi)+1)) - (1 << (low))) +#define CONF_RANGE(config, low, hi) (CONF_MSK(config, MSK_RANGE(low, high))) + +#define CONF_IS(config, val) ((config) == (1 << (val))) +#define CONF_GE(config, val) ((config) & (0-(1 << (val)))) +#define CONF_GT(config, val) ((config) & (0-2*(1 << (val)))) +#define CONF_LT(config, val) ((config) & ((1 << (val))-1)) +#define CONF_LE(config, val) ((config) & (2*(1 << (val))-1)) + +/* Wrappers for some of the above, specific to config constants */ + +#define NCONF_HAS(val) CONF_HAS(NCONF, val) +#define NCONF_MSK(mask) CONF_MSK(NCONF, mask) +#define NCONF_IS(val) CONF_IS(NCONF, val) +#define NCONF_GE(val) CONF_GE(NCONF, val) +#define NCONF_GT(val) CONF_GT(NCONF, val) +#define NCONF_LT(val) CONF_LT(NCONF, val) +#define NCONF_LE(val) CONF_LE(NCONF, val) + +#define LCNCONF_HAS(val) CONF_HAS(LCNCONF, val) +#define LCNCONF_MSK(mask) CONF_MSK(LCNCONF, mask) +#define LCNCONF_IS(val) CONF_IS(LCNCONF, val) +#define LCNCONF_GE(val) CONF_GE(LCNCONF, val) +#define LCNCONF_GT(val) CONF_GT(LCNCONF, val) +#define LCNCONF_LT(val) CONF_LT(LCNCONF, val) +#define LCNCONF_LE(val) CONF_LE(LCNCONF, val) + +#define D11CONF_HAS(val) CONF_HAS(D11CONF, val) +#define D11CONF_MSK(mask) CONF_MSK(D11CONF, mask) +#define D11CONF_IS(val) CONF_IS(D11CONF, val) +#define D11CONF_GE(val) CONF_GE(D11CONF, val) +#define D11CONF_GT(val) CONF_GT(D11CONF, val) +#define D11CONF_LT(val) CONF_LT(D11CONF, val) +#define D11CONF_LE(val) CONF_LE(D11CONF, val) + +#define PHYCONF_HAS(val) CONF_HAS(PHYTYPE, val) +#define PHYCONF_IS(val) CONF_IS(PHYTYPE, val) + +#define NREV_IS(var, val) \ + (NCONF_HAS(val) && (NCONF_IS(val) || ((var) == (val)))) + +#define NREV_GE(var, val) \ + (NCONF_GE(val) && (!NCONF_LT(val) || ((var) >= (val)))) + +#define NREV_GT(var, val) \ + (NCONF_GT(val) && (!NCONF_LE(val) || ((var) > (val)))) + +#define NREV_LT(var, val) \ + (NCONF_LT(val) && (!NCONF_GE(val) || ((var) < (val)))) + +#define NREV_LE(var, val) \ + (NCONF_LE(val) && (!NCONF_GT(val) || ((var) <= (val)))) + +#define LCNREV_IS(var, val) \ + (LCNCONF_HAS(val) && (LCNCONF_IS(val) || ((var) == (val)))) + +#define LCNREV_GE(var, val) \ + (LCNCONF_GE(val) && (!LCNCONF_LT(val) || ((var) >= (val)))) + +#define LCNREV_GT(var, val) \ + (LCNCONF_GT(val) && (!LCNCONF_LE(val) || ((var) > (val)))) + +#define LCNREV_LT(var, val) \ + (LCNCONF_LT(val) && (!LCNCONF_GE(val) || ((var) < (val)))) + +#define LCNREV_LE(var, val) \ + (LCNCONF_LE(val) && (!LCNCONF_GT(val) || ((var) <= (val)))) + +#define D11REV_IS(var, val) \ + (D11CONF_HAS(val) && (D11CONF_IS(val) || ((var) == (val)))) + +#define D11REV_GE(var, val) \ + (D11CONF_GE(val) && (!D11CONF_LT(val) || ((var) >= (val)))) + +#define D11REV_GT(var, val) \ + (D11CONF_GT(val) && (!D11CONF_LE(val) || ((var) > (val)))) + +#define D11REV_LT(var, val) \ + (D11CONF_LT(val) && (!D11CONF_GE(val) || ((var) < (val)))) + +#define D11REV_LE(var, val) \ + (D11CONF_LE(val) && (!D11CONF_GT(val) || ((var) <= (val)))) + +#define PHYTYPE_IS(var, val)\ + (PHYCONF_HAS(val) && (PHYCONF_IS(val) || ((var) == (val)))) + +/* Set up PHYTYPE automatically: (depends on PHY_TYPE_X, from d11.h) */ + +#define _PHYCONF_N (1 << PHY_TYPE_N) +#define _PHYCONF_LCN (1 << PHY_TYPE_LCN) +#define _PHYCONF_SSLPN (1 << PHY_TYPE_SSN) + +#define PHYTYPE (_PHYCONF_N | _PHYCONF_LCN | _PHYCONF_SSLPN) + +/* Utility macro to identify 802.11n (HT) capable PHYs */ +#define PHYTYPE_11N_CAP(phytype) \ + (PHYTYPE_IS(phytype, PHY_TYPE_N) || \ + PHYTYPE_IS(phytype, PHY_TYPE_LCN) || \ + PHYTYPE_IS(phytype, PHY_TYPE_SSN)) + +/* Last but not least: shorter wlc-specific var checks */ +#define BRCMS_ISNPHY(band) PHYTYPE_IS((band)->phytype, PHY_TYPE_N) +#define BRCMS_ISLCNPHY(band) PHYTYPE_IS((band)->phytype, PHY_TYPE_LCN) +#define BRCMS_ISSSLPNPHY(band) PHYTYPE_IS((band)->phytype, PHY_TYPE_SSN) + +#define BRCMS_PHY_11N_CAP(band) PHYTYPE_11N_CAP((band)->phytype) + +/********************************************************************** + * ------------- End of Core phy/rev configuration. ----------------- * + * ******************************************************************** + */ + +#define BCMMSG(dev, fmt, args...) \ +do { \ + if (brcm_msg_level & BRCM_DL_INFO) \ + wiphy_err(dev, "%s: " fmt, __func__, ##args); \ +} while (0) + +#ifdef CONFIG_BCM47XX +/* + * bcm4716 (which includes 4717 & 4718), plus 4706 on PCIe can reorder + * transactions. As a fix, a read after write is performed on certain places + * in the code. Older chips and the newer 5357 family don't require this fix. + */ +#define bcma_wflush16(c, o, v) \ + ({ bcma_write16(c, o, v); (void)bcma_read16(c, o); }) +#else +#define bcma_wflush16(c, o, v) bcma_write16(c, o, v) +#endif /* CONFIG_BCM47XX */ + +/* multi-bool data type: set of bools, mbool is true if any is set */ + +/* set one bool */ +#define mboolset(mb, bit) ((mb) |= (bit)) +/* clear one bool */ +#define mboolclr(mb, bit) ((mb) &= ~(bit)) +/* true if one bool is set */ +#define mboolisset(mb, bit) (((mb) & (bit)) != 0) +#define mboolmaskset(mb, mask, val) ((mb) = (((mb) & ~(mask)) | (val))) + +#define CEIL(x, y) (((x) + ((y)-1)) / (y)) + +/* forward declarations */ +struct wiphy; +struct ieee80211_sta; +struct ieee80211_tx_queue_params; +struct brcms_info; +struct brcms_c_info; +struct brcms_hardware; +struct brcms_band; +struct dma_pub; +struct si_pub; +struct tx_status; +struct d11rxhdr; +struct txpwr_limits; + +/* iovar structure */ +struct brcmu_iovar { + const char *name; /* name for lookup and display */ + u16 varid; /* id for switch */ + u16 flags; /* driver-specific flag bits */ + u16 type; /* base type of argument */ + u16 minlen; /* min length for buffer vars */ +}; + +/* brcm_msg_level is a bit vector with defs in defs.h */ +extern u32 brcm_msg_level; + +#endif /* _BRCM_TYPES_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmsmac/ucode_loader.c b/drivers/net/wireless/brcm80211/brcmsmac/ucode_loader.c new file mode 100644 index 0000000..80e3ccf --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/ucode_loader.c @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "types.h" +#include + +enum { + D11UCODE_NAMETAG_START = 0, + D11LCN0BSINITVALS24, + D11LCN0INITVALS24, + D11LCN1BSINITVALS24, + D11LCN1INITVALS24, + D11LCN2BSINITVALS24, + D11LCN2INITVALS24, + D11N0ABSINITVALS16, + D11N0BSINITVALS16, + D11N0INITVALS16, + D11UCODE_OVERSIGHT16_MIMO, + D11UCODE_OVERSIGHT16_MIMOSZ, + D11UCODE_OVERSIGHT24_LCN, + D11UCODE_OVERSIGHT24_LCNSZ, + D11UCODE_OVERSIGHT_BOMMAJOR, + D11UCODE_OVERSIGHT_BOMMINOR +}; + +int brcms_ucode_data_init(struct brcms_info *wl, struct brcms_ucode *ucode) +{ + int rc; + + rc = brcms_check_firmwares(wl); + + rc = rc < 0 ? rc : + brcms_ucode_init_buf(wl, (void **)&ucode->d11lcn0bsinitvals24, + D11LCN0BSINITVALS24); + rc = rc < 0 ? + rc : brcms_ucode_init_buf(wl, (void **)&ucode->d11lcn0initvals24, + D11LCN0INITVALS24); + rc = rc < 0 ? + rc : brcms_ucode_init_buf(wl, (void **)&ucode->d11lcn1bsinitvals24, + D11LCN1BSINITVALS24); + rc = rc < 0 ? + rc : brcms_ucode_init_buf(wl, (void **)&ucode->d11lcn1initvals24, + D11LCN1INITVALS24); + rc = rc < 0 ? rc : + brcms_ucode_init_buf(wl, (void **)&ucode->d11lcn2bsinitvals24, + D11LCN2BSINITVALS24); + rc = rc < 0 ? + rc : brcms_ucode_init_buf(wl, (void **)&ucode->d11lcn2initvals24, + D11LCN2INITVALS24); + rc = rc < 0 ? + rc : brcms_ucode_init_buf(wl, (void **)&ucode->d11n0absinitvals16, + D11N0ABSINITVALS16); + rc = rc < 0 ? + rc : brcms_ucode_init_buf(wl, (void **)&ucode->d11n0bsinitvals16, + D11N0BSINITVALS16); + rc = rc < 0 ? + rc : brcms_ucode_init_buf(wl, (void **)&ucode->d11n0initvals16, + D11N0INITVALS16); + rc = rc < 0 ? + rc : brcms_ucode_init_buf(wl, (void **)&ucode->bcm43xx_16_mimo, + D11UCODE_OVERSIGHT16_MIMO); + rc = rc < 0 ? + rc : brcms_ucode_init_uint(wl, &ucode->bcm43xx_16_mimosz, + D11UCODE_OVERSIGHT16_MIMOSZ); + rc = rc < 0 ? + rc : brcms_ucode_init_buf(wl, (void **)&ucode->bcm43xx_24_lcn, + D11UCODE_OVERSIGHT24_LCN); + rc = rc < 0 ? + rc : brcms_ucode_init_uint(wl, &ucode->bcm43xx_24_lcnsz, + D11UCODE_OVERSIGHT24_LCNSZ); + rc = rc < 0 ? + rc : brcms_ucode_init_buf(wl, (void **)&ucode->bcm43xx_bommajor, + D11UCODE_OVERSIGHT_BOMMAJOR); + rc = rc < 0 ? + rc : brcms_ucode_init_buf(wl, (void **)&ucode->bcm43xx_bomminor, + D11UCODE_OVERSIGHT_BOMMINOR); + return rc; +} + +void brcms_ucode_data_free(struct brcms_ucode *ucode) +{ + brcms_ucode_free_buf((void *)ucode->d11lcn0bsinitvals24); + brcms_ucode_free_buf((void *)ucode->d11lcn0initvals24); + brcms_ucode_free_buf((void *)ucode->d11lcn1bsinitvals24); + brcms_ucode_free_buf((void *)ucode->d11lcn1initvals24); + brcms_ucode_free_buf((void *)ucode->d11lcn2bsinitvals24); + brcms_ucode_free_buf((void *)ucode->d11lcn2initvals24); + brcms_ucode_free_buf((void *)ucode->d11n0absinitvals16); + brcms_ucode_free_buf((void *)ucode->d11n0bsinitvals16); + brcms_ucode_free_buf((void *)ucode->d11n0initvals16); + brcms_ucode_free_buf((void *)ucode->bcm43xx_16_mimo); + brcms_ucode_free_buf((void *)ucode->bcm43xx_24_lcn); + brcms_ucode_free_buf((void *)ucode->bcm43xx_bommajor); + brcms_ucode_free_buf((void *)ucode->bcm43xx_bomminor); +} diff --git a/drivers/net/wireless/brcm80211/brcmsmac/ucode_loader.h b/drivers/net/wireless/brcm80211/brcmsmac/ucode_loader.h new file mode 100644 index 0000000..18750a8 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/ucode_loader.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef _BRCM_UCODE_H_ +#define _BRCM_UCODE_H_ + +#include "types.h" /* forward structure declarations */ + +#define MIN_FW_SIZE 40000 /* minimum firmware file size in bytes */ +#define MAX_FW_SIZE 150000 + +#define UCODE_LOADER_API_VER 0 + +struct d11init; + +struct brcms_ucode { + struct d11init *d11lcn0bsinitvals24; + struct d11init *d11lcn0initvals24; + struct d11init *d11lcn1bsinitvals24; + struct d11init *d11lcn1initvals24; + struct d11init *d11lcn2bsinitvals24; + struct d11init *d11lcn2initvals24; + struct d11init *d11n0absinitvals16; + struct d11init *d11n0bsinitvals16; + struct d11init *d11n0initvals16; + __le32 *bcm43xx_16_mimo; + size_t bcm43xx_16_mimosz; + __le32 *bcm43xx_24_lcn; + size_t bcm43xx_24_lcnsz; + u32 *bcm43xx_bommajor; + u32 *bcm43xx_bomminor; +}; + +extern int +brcms_ucode_data_init(struct brcms_info *wl, struct brcms_ucode *ucode); + +extern void brcms_ucode_data_free(struct brcms_ucode *ucode); + +extern int brcms_ucode_init_buf(struct brcms_info *wl, void **pbuf, + unsigned int idx); +extern int brcms_ucode_init_uint(struct brcms_info *wl, size_t *n_bytes, + unsigned int idx); +extern void brcms_ucode_free_buf(void *); +extern int brcms_check_firmwares(struct brcms_info *wl); + +#endif /* _BRCM_UCODE_H_ */ diff --git a/drivers/net/wireless/brcm80211/brcmutil/Makefile b/drivers/net/wireless/brcm80211/brcmutil/Makefile new file mode 100644 index 0000000..6c03283 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmutil/Makefile @@ -0,0 +1,23 @@ +# +# Makefile fragment for Broadcom 802.11n Networking Device Driver Utilities +# +# Copyright (c) 2011 Broadcom Corporation +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ccflags-y := \ + -I$(backport_srctree)/drivers/net/wireless/brcm80211/brcmutil \ + -I$(backport_srctree)/drivers/net/wireless/brcm80211/include + +obj-$(CPTCFG_BRCMUTIL) += brcmutil.o +brcmutil-objs = utils.o d11.o diff --git a/drivers/net/wireless/brcm80211/brcmutil/d11.c b/drivers/net/wireless/brcm80211/brcmutil/d11.c new file mode 100644 index 0000000..30e54e2 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmutil/d11.c @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2013 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +/*********************channel spec common functions*********************/ + +#include + +#include +#include +#include + +static void brcmu_d11n_encchspec(struct brcmu_chan *ch) +{ + ch->chspec = ch->chnum & BRCMU_CHSPEC_CH_MASK; + + switch (ch->bw) { + case BRCMU_CHAN_BW_20: + ch->chspec |= BRCMU_CHSPEC_D11N_BW_20 | BRCMU_CHSPEC_D11N_SB_N; + break; + case BRCMU_CHAN_BW_40: + default: + WARN_ON_ONCE(1); + break; + } + + if (ch->chnum <= CH_MAX_2G_CHANNEL) + ch->chspec |= BRCMU_CHSPEC_D11N_BND_2G; + else + ch->chspec |= BRCMU_CHSPEC_D11N_BND_5G; +} + +static void brcmu_d11ac_encchspec(struct brcmu_chan *ch) +{ + ch->chspec = ch->chnum & BRCMU_CHSPEC_CH_MASK; + + switch (ch->bw) { + case BRCMU_CHAN_BW_20: + ch->chspec |= BRCMU_CHSPEC_D11AC_BW_20; + break; + case BRCMU_CHAN_BW_40: + case BRCMU_CHAN_BW_80: + case BRCMU_CHAN_BW_80P80: + case BRCMU_CHAN_BW_160: + default: + WARN_ON_ONCE(1); + break; + } + + if (ch->chnum <= CH_MAX_2G_CHANNEL) + ch->chspec |= BRCMU_CHSPEC_D11AC_BND_2G; + else + ch->chspec |= BRCMU_CHSPEC_D11AC_BND_5G; +} + +static void brcmu_d11n_decchspec(struct brcmu_chan *ch) +{ + u16 val; + + ch->chnum = (u8)(ch->chspec & BRCMU_CHSPEC_CH_MASK); + + switch (ch->chspec & BRCMU_CHSPEC_D11N_BW_MASK) { + case BRCMU_CHSPEC_D11N_BW_20: + ch->bw = BRCMU_CHAN_BW_20; + break; + case BRCMU_CHSPEC_D11N_BW_40: + ch->bw = BRCMU_CHAN_BW_40; + val = ch->chspec & BRCMU_CHSPEC_D11N_SB_MASK; + if (val == BRCMU_CHSPEC_D11N_SB_L) { + ch->sb = BRCMU_CHAN_SB_L; + ch->chnum -= CH_10MHZ_APART; + } else { + ch->sb = BRCMU_CHAN_SB_U; + ch->chnum += CH_10MHZ_APART; + } + break; + default: + WARN_ON_ONCE(1); + break; + } + + switch (ch->chspec & BRCMU_CHSPEC_D11N_BND_MASK) { + case BRCMU_CHSPEC_D11N_BND_5G: + ch->band = BRCMU_CHAN_BAND_5G; + break; + case BRCMU_CHSPEC_D11N_BND_2G: + ch->band = BRCMU_CHAN_BAND_2G; + break; + default: + WARN_ON_ONCE(1); + break; + } +} + +static void brcmu_d11ac_decchspec(struct brcmu_chan *ch) +{ + u16 val; + + ch->chnum = (u8)(ch->chspec & BRCMU_CHSPEC_CH_MASK); + + switch (ch->chspec & BRCMU_CHSPEC_D11AC_BW_MASK) { + case BRCMU_CHSPEC_D11AC_BW_20: + ch->bw = BRCMU_CHAN_BW_20; + break; + case BRCMU_CHSPEC_D11AC_BW_40: + ch->bw = BRCMU_CHAN_BW_40; + val = ch->chspec & BRCMU_CHSPEC_D11AC_SB_MASK; + if (val == BRCMU_CHSPEC_D11AC_SB_L) { + ch->sb = BRCMU_CHAN_SB_L; + ch->chnum -= CH_10MHZ_APART; + } else if (val == BRCMU_CHSPEC_D11AC_SB_U) { + ch->sb = BRCMU_CHAN_SB_U; + ch->chnum += CH_10MHZ_APART; + } else { + WARN_ON_ONCE(1); + } + break; + case BRCMU_CHSPEC_D11AC_BW_80: + ch->bw = BRCMU_CHAN_BW_80; + break; + case BRCMU_CHSPEC_D11AC_BW_8080: + case BRCMU_CHSPEC_D11AC_BW_160: + default: + WARN_ON_ONCE(1); + break; + } + + switch (ch->chspec & BRCMU_CHSPEC_D11AC_BND_MASK) { + case BRCMU_CHSPEC_D11AC_BND_5G: + ch->band = BRCMU_CHAN_BAND_5G; + break; + case BRCMU_CHSPEC_D11AC_BND_2G: + ch->band = BRCMU_CHAN_BAND_2G; + break; + default: + WARN_ON_ONCE(1); + break; + } +} + +void brcmu_d11_attach(struct brcmu_d11inf *d11inf) +{ + if (d11inf->io_type == BRCMU_D11N_IOTYPE) { + d11inf->encchspec = brcmu_d11n_encchspec; + d11inf->decchspec = brcmu_d11n_decchspec; + } else { + d11inf->encchspec = brcmu_d11ac_encchspec; + d11inf->decchspec = brcmu_d11ac_decchspec; + } +} +EXPORT_SYMBOL(brcmu_d11_attach); diff --git a/drivers/net/wireless/brcm80211/brcmutil/utils.c b/drivers/net/wireless/brcm80211/brcmutil/utils.c new file mode 100644 index 0000000..0f7e1c7 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmutil/utils.c @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include + +#include + +MODULE_AUTHOR("Broadcom Corporation"); +MODULE_DESCRIPTION("Broadcom 802.11n wireless LAN driver utilities."); +MODULE_SUPPORTED_DEVICE("Broadcom 802.11n WLAN cards"); +MODULE_LICENSE("Dual BSD/GPL"); + +struct sk_buff *brcmu_pkt_buf_get_skb(uint len) +{ + struct sk_buff *skb; + + skb = dev_alloc_skb(len); + if (skb) { + skb_put(skb, len); + skb->priority = 0; + } + + return skb; +} +EXPORT_SYMBOL(brcmu_pkt_buf_get_skb); + +/* Free the driver packet. Free the tag if present */ +void brcmu_pkt_buf_free_skb(struct sk_buff *skb) +{ + if (!skb) + return; + + WARN_ON(skb->next); + dev_kfree_skb_any(skb); +} +EXPORT_SYMBOL(brcmu_pkt_buf_free_skb); + +/* + * osl multiple-precedence packet queue + * hi_prec is always >= the number of the highest non-empty precedence + */ +struct sk_buff *brcmu_pktq_penq(struct pktq *pq, int prec, + struct sk_buff *p) +{ + struct sk_buff_head *q; + + if (pktq_full(pq) || pktq_pfull(pq, prec)) + return NULL; + + q = &pq->q[prec].skblist; + skb_queue_tail(q, p); + pq->len++; + + if (pq->hi_prec < prec) + pq->hi_prec = (u8) prec; + + return p; +} +EXPORT_SYMBOL(brcmu_pktq_penq); + +struct sk_buff *brcmu_pktq_penq_head(struct pktq *pq, int prec, + struct sk_buff *p) +{ + struct sk_buff_head *q; + + if (pktq_full(pq) || pktq_pfull(pq, prec)) + return NULL; + + q = &pq->q[prec].skblist; + skb_queue_head(q, p); + pq->len++; + + if (pq->hi_prec < prec) + pq->hi_prec = (u8) prec; + + return p; +} +EXPORT_SYMBOL(brcmu_pktq_penq_head); + +struct sk_buff *brcmu_pktq_pdeq(struct pktq *pq, int prec) +{ + struct sk_buff_head *q; + struct sk_buff *p; + + q = &pq->q[prec].skblist; + p = skb_dequeue(q); + if (p == NULL) + return NULL; + + pq->len--; + return p; +} +EXPORT_SYMBOL(brcmu_pktq_pdeq); + +/* + * precedence based dequeue with match function. Passing a NULL pointer + * for the match function parameter is considered to be a wildcard so + * any packet on the queue is returned. In that case it is no different + * from brcmu_pktq_pdeq() above. + */ +struct sk_buff *brcmu_pktq_pdeq_match(struct pktq *pq, int prec, + bool (*match_fn)(struct sk_buff *skb, + void *arg), void *arg) +{ + struct sk_buff_head *q; + struct sk_buff *p, *next; + + q = &pq->q[prec].skblist; + skb_queue_walk_safe(q, p, next) { + if (match_fn == NULL || match_fn(p, arg)) { + skb_unlink(p, q); + pq->len--; + return p; + } + } + return NULL; +} +EXPORT_SYMBOL(brcmu_pktq_pdeq_match); + +struct sk_buff *brcmu_pktq_pdeq_tail(struct pktq *pq, int prec) +{ + struct sk_buff_head *q; + struct sk_buff *p; + + q = &pq->q[prec].skblist; + p = skb_dequeue_tail(q); + if (p == NULL) + return NULL; + + pq->len--; + return p; +} +EXPORT_SYMBOL(brcmu_pktq_pdeq_tail); + +void +brcmu_pktq_pflush(struct pktq *pq, int prec, bool dir, + bool (*fn)(struct sk_buff *, void *), void *arg) +{ + struct sk_buff_head *q; + struct sk_buff *p, *next; + + q = &pq->q[prec].skblist; + skb_queue_walk_safe(q, p, next) { + if (fn == NULL || (*fn) (p, arg)) { + skb_unlink(p, q); + brcmu_pkt_buf_free_skb(p); + pq->len--; + } + } +} +EXPORT_SYMBOL(brcmu_pktq_pflush); + +void brcmu_pktq_flush(struct pktq *pq, bool dir, + bool (*fn)(struct sk_buff *, void *), void *arg) +{ + int prec; + for (prec = 0; prec < pq->num_prec; prec++) + brcmu_pktq_pflush(pq, prec, dir, fn, arg); +} +EXPORT_SYMBOL(brcmu_pktq_flush); + +void brcmu_pktq_init(struct pktq *pq, int num_prec, int max_len) +{ + int prec; + + /* pq is variable size; only zero out what's requested */ + memset(pq, 0, + offsetof(struct pktq, q) + (sizeof(struct pktq_prec) * num_prec)); + + pq->num_prec = (u16) num_prec; + + pq->max = (u16) max_len; + + for (prec = 0; prec < num_prec; prec++) { + pq->q[prec].max = pq->max; + skb_queue_head_init(&pq->q[prec].skblist); + } +} +EXPORT_SYMBOL(brcmu_pktq_init); + +struct sk_buff *brcmu_pktq_peek_tail(struct pktq *pq, int *prec_out) +{ + int prec; + + if (pq->len == 0) + return NULL; + + for (prec = 0; prec < pq->hi_prec; prec++) + if (!skb_queue_empty(&pq->q[prec].skblist)) + break; + + if (prec_out) + *prec_out = prec; + + return skb_peek_tail(&pq->q[prec].skblist); +} +EXPORT_SYMBOL(brcmu_pktq_peek_tail); + +/* Return sum of lengths of a specific set of precedences */ +int brcmu_pktq_mlen(struct pktq *pq, uint prec_bmp) +{ + int prec, len; + + len = 0; + + for (prec = 0; prec <= pq->hi_prec; prec++) + if (prec_bmp & (1 << prec)) + len += pq->q[prec].skblist.qlen; + + return len; +} +EXPORT_SYMBOL(brcmu_pktq_mlen); + +/* Priority dequeue from a specific set of precedences */ +struct sk_buff *brcmu_pktq_mdeq(struct pktq *pq, uint prec_bmp, + int *prec_out) +{ + struct sk_buff_head *q; + struct sk_buff *p; + int prec; + + if (pq->len == 0) + return NULL; + + while ((prec = pq->hi_prec) > 0 && + skb_queue_empty(&pq->q[prec].skblist)) + pq->hi_prec--; + + while ((prec_bmp & (1 << prec)) == 0 || + skb_queue_empty(&pq->q[prec].skblist)) + if (prec-- == 0) + return NULL; + + q = &pq->q[prec].skblist; + p = skb_dequeue(q); + if (p == NULL) + return NULL; + + pq->len--; + + if (prec_out) + *prec_out = prec; + + return p; +} +EXPORT_SYMBOL(brcmu_pktq_mdeq); + +#if defined(DEBUG) +/* pretty hex print a pkt buffer chain */ +void brcmu_prpkt(const char *msg, struct sk_buff *p0) +{ + struct sk_buff *p; + + if (msg && (msg[0] != '\0')) + pr_debug("%s:\n", msg); + + for (p = p0; p; p = p->next) + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, p->data, p->len); +} +EXPORT_SYMBOL(brcmu_prpkt); + +void brcmu_dbg_hex_dump(const void *data, size_t size, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + pr_debug("%pV", &vaf); + + va_end(args); + + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, data, size); +} +EXPORT_SYMBOL(brcmu_dbg_hex_dump); +#endif /* defined(DEBUG) */ diff --git a/drivers/net/wireless/brcm80211/include/brcm_hw_ids.h b/drivers/net/wireless/brcm80211/include/brcm_hw_ids.h new file mode 100644 index 0000000..c1fe245 --- /dev/null +++ b/drivers/net/wireless/brcm80211/include/brcm_hw_ids.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_HW_IDS_H_ +#define _BRCM_HW_IDS_H_ + +#define BCM4313_D11N2G_ID 0x4727 /* 4313 802.11n 2.4G device */ + +#define BCM43224_D11N_ID 0x4353 /* 43224 802.11n dualband device */ +#define BCM43224_D11N_ID_VEN1 0x0576 /* Vendor specific 43224 802.11n db */ + +#define BCM43225_D11N2G_ID 0x4357 /* 43225 802.11n 2.4GHz device */ + +#define BCM43236_D11N_ID 0x4346 /* 43236 802.11n dualband device */ +#define BCM43236_D11N2G_ID 0x4347 /* 43236 802.11n 2.4GHz device */ + +/* Chipcommon Core Chip IDs */ +#define BCM4313_CHIP_ID 0x4313 +#define BCM43143_CHIP_ID 43143 +#define BCM43224_CHIP_ID 43224 +#define BCM43225_CHIP_ID 43225 +#define BCM43235_CHIP_ID 43235 +#define BCM43236_CHIP_ID 43236 +#define BCM43238_CHIP_ID 43238 +#define BCM43241_CHIP_ID 0x4324 +#define BCM4329_CHIP_ID 0x4329 +#define BCM4330_CHIP_ID 0x4330 +#define BCM4331_CHIP_ID 0x4331 +#define BCM4334_CHIP_ID 0x4334 +#define BCM4335_CHIP_ID 0x4335 + +#endif /* _BRCM_HW_IDS_H_ */ diff --git a/drivers/net/wireless/brcm80211/include/brcmu_d11.h b/drivers/net/wireless/brcm80211/include/brcmu_d11.h new file mode 100644 index 0000000..92623f0 --- /dev/null +++ b/drivers/net/wireless/brcm80211/include/brcmu_d11.h @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCMU_D11_H_ +#define _BRCMU_D11_H_ + +/* d11 io type */ +#define BRCMU_D11N_IOTYPE 1 +#define BRCMU_D11AC_IOTYPE 2 + +/* A chanspec (channel specification) holds the channel number, band, + * bandwidth and control sideband + */ + +/* chanspec binary format */ + +#define BRCMU_CHSPEC_INVALID 255 +/* bit 0~7 channel number + * for 80+80 channels: bit 0~3 low channel id, bit 4~7 high channel id + */ +#define BRCMU_CHSPEC_CH_MASK 0x00ff +#define BRCMU_CHSPEC_CH_SHIFT 0 +#define BRCMU_CHSPEC_CHL_MASK 0x000f +#define BRCMU_CHSPEC_CHL_SHIFT 0 +#define BRCMU_CHSPEC_CHH_MASK 0x00f0 +#define BRCMU_CHSPEC_CHH_SHIFT 4 + +/* bit 8~16 for dot 11n IO types + * bit 8~9 sideband + * bit 10~11 bandwidth + * bit 12~13 spectral band + * bit 14~15 not used + */ +#define BRCMU_CHSPEC_D11N_SB_MASK 0x0300 +#define BRCMU_CHSPEC_D11N_SB_SHIFT 8 +#define BRCMU_CHSPEC_D11N_SB_L 0x0100 /* control lower */ +#define BRCMU_CHSPEC_D11N_SB_U 0x0200 /* control upper */ +#define BRCMU_CHSPEC_D11N_SB_N 0x0300 /* none */ +#define BRCMU_CHSPEC_D11N_BW_MASK 0x0c00 +#define BRCMU_CHSPEC_D11N_BW_SHIFT 10 +#define BRCMU_CHSPEC_D11N_BW_10 0x0400 +#define BRCMU_CHSPEC_D11N_BW_20 0x0800 +#define BRCMU_CHSPEC_D11N_BW_40 0x0c00 +#define BRCMU_CHSPEC_D11N_BND_MASK 0x3000 +#define BRCMU_CHSPEC_D11N_BND_SHIFT 12 +#define BRCMU_CHSPEC_D11N_BND_5G 0x1000 +#define BRCMU_CHSPEC_D11N_BND_2G 0x2000 + +/* bit 8~16 for dot 11ac IO types + * bit 8~10 sideband + * bit 11~13 bandwidth + * bit 14~15 spectral band + */ +#define BRCMU_CHSPEC_D11AC_SB_MASK 0x0700 +#define BRCMU_CHSPEC_D11AC_SB_SHIFT 8 +#define BRCMU_CHSPEC_D11AC_SB_LLL 0x0000 +#define BRCMU_CHSPEC_D11AC_SB_LLU 0x0100 +#define BRCMU_CHSPEC_D11AC_SB_LUL 0x0200 +#define BRCMU_CHSPEC_D11AC_SB_LUU 0x0300 +#define BRCMU_CHSPEC_D11AC_SB_ULL 0x0400 +#define BRCMU_CHSPEC_D11AC_SB_ULU 0x0500 +#define BRCMU_CHSPEC_D11AC_SB_UUL 0x0600 +#define BRCMU_CHSPEC_D11AC_SB_UUU 0x0700 +#define BRCMU_CHSPEC_D11AC_SB_LL BRCMU_CHSPEC_D11AC_SB_LLL +#define BRCMU_CHSPEC_D11AC_SB_LU BRCMU_CHSPEC_D11AC_SB_LLU +#define BRCMU_CHSPEC_D11AC_SB_UL BRCMU_CHSPEC_D11AC_SB_LUL +#define BRCMU_CHSPEC_D11AC_SB_UU BRCMU_CHSPEC_D11AC_SB_LUU +#define BRCMU_CHSPEC_D11AC_SB_L BRCMU_CHSPEC_D11AC_SB_LLL +#define BRCMU_CHSPEC_D11AC_SB_U BRCMU_CHSPEC_D11AC_SB_LLU +#define BRCMU_CHSPEC_D11AC_BW_MASK 0x3800 +#define BRCMU_CHSPEC_D11AC_BW_SHIFT 11 +#define BRCMU_CHSPEC_D11AC_BW_5 0x0000 +#define BRCMU_CHSPEC_D11AC_BW_10 0x0800 +#define BRCMU_CHSPEC_D11AC_BW_20 0x1000 +#define BRCMU_CHSPEC_D11AC_BW_40 0x1800 +#define BRCMU_CHSPEC_D11AC_BW_80 0x2000 +#define BRCMU_CHSPEC_D11AC_BW_160 0x2800 +#define BRCMU_CHSPEC_D11AC_BW_8080 0x3000 +#define BRCMU_CHSPEC_D11AC_BND_MASK 0xc000 +#define BRCMU_CHSPEC_D11AC_BND_SHIFT 14 +#define BRCMU_CHSPEC_D11AC_BND_2G 0x0000 +#define BRCMU_CHSPEC_D11AC_BND_3G 0x4000 +#define BRCMU_CHSPEC_D11AC_BND_4G 0x8000 +#define BRCMU_CHSPEC_D11AC_BND_5G 0xc000 + +#define BRCMU_CHAN_BAND_2G 0 +#define BRCMU_CHAN_BAND_5G 1 + +enum brcmu_chan_bw { + BRCMU_CHAN_BW_20, + BRCMU_CHAN_BW_40, + BRCMU_CHAN_BW_80, + BRCMU_CHAN_BW_80P80, + BRCMU_CHAN_BW_160, +}; + +enum brcmu_chan_sb { + BRCMU_CHAN_SB_NONE = 0, + BRCMU_CHAN_SB_L, + BRCMU_CHAN_SB_U, + BRCMU_CHAN_SB_LL, + BRCMU_CHAN_SB_LU, + BRCMU_CHAN_SB_UL, + BRCMU_CHAN_SB_UU, + BRCMU_CHAN_SB_LLL, + BRCMU_CHAN_SB_LLU, + BRCMU_CHAN_SB_LUL, + BRCMU_CHAN_SB_LUU, + BRCMU_CHAN_SB_ULL, + BRCMU_CHAN_SB_ULU, + BRCMU_CHAN_SB_UUL, + BRCMU_CHAN_SB_UUU, +}; + +struct brcmu_chan { + u16 chspec; + u8 chnum; + u8 band; + enum brcmu_chan_bw bw; + enum brcmu_chan_sb sb; +}; + +struct brcmu_d11inf { + u8 io_type; + + void (*encchspec)(struct brcmu_chan *ch); + void (*decchspec)(struct brcmu_chan *ch); +}; + +extern void brcmu_d11_attach(struct brcmu_d11inf *d11inf); + +#endif /* _BRCMU_CHANNELS_H_ */ diff --git a/drivers/net/wireless/brcm80211/include/brcmu_utils.h b/drivers/net/wireless/brcm80211/include/brcmu_utils.h new file mode 100644 index 0000000..898cacb --- /dev/null +++ b/drivers/net/wireless/brcm80211/include/brcmu_utils.h @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCMU_UTILS_H_ +#define _BRCMU_UTILS_H_ + +#include + +/* + * Spin at most 'us' microseconds while 'exp' is true. + * Caller should explicitly test 'exp' when this completes + * and take appropriate error action if 'exp' is still true. + */ +#define SPINWAIT(exp, us) { \ + uint countdown = (us) + 9; \ + while ((exp) && (countdown >= 10)) {\ + udelay(10); \ + countdown -= 10; \ + } \ +} + +/* osl multi-precedence packet queue */ +#define PKTQ_LEN_DEFAULT 128 /* Max 128 packets */ +#define PKTQ_MAX_PREC 16 /* Maximum precedence levels */ + +#define BCME_STRLEN 64 /* Max string length for BCM errors */ + +/* the largest reasonable packet buffer driver uses for ethernet MTU in bytes */ +#define PKTBUFSZ 2048 + +#ifndef setbit +#ifndef NBBY /* the BSD family defines NBBY */ +#define NBBY 8 /* 8 bits per byte */ +#endif /* #ifndef NBBY */ +#define setbit(a, i) (((u8 *)a)[(i)/NBBY] |= 1<<((i)%NBBY)) +#define clrbit(a, i) (((u8 *)a)[(i)/NBBY] &= ~(1<<((i)%NBBY))) +#define isset(a, i) (((const u8 *)a)[(i)/NBBY] & (1<<((i)%NBBY))) +#define isclr(a, i) ((((const u8 *)a)[(i)/NBBY] & (1<<((i)%NBBY))) == 0) +#endif /* setbit */ + +#define NBITS(type) (sizeof(type) * 8) +#define NBITVAL(nbits) (1 << (nbits)) +#define MAXBITVAL(nbits) ((1 << (nbits)) - 1) +#define NBITMASK(nbits) MAXBITVAL(nbits) +#define MAXNBVAL(nbyte) MAXBITVAL((nbyte) * 8) + +/* crc defines */ +#define CRC16_INIT_VALUE 0xffff /* Initial CRC16 checksum value */ +#define CRC16_GOOD_VALUE 0xf0b8 /* Good final CRC16 checksum value */ + +/* 18-bytes of Ethernet address buffer length */ +#define ETHER_ADDR_STR_LEN 18 + +struct pktq_prec { + struct sk_buff_head skblist; + u16 max; /* maximum number of queued packets */ +}; + +/* multi-priority pkt queue */ +struct pktq { + u16 num_prec; /* number of precedences in use */ + u16 hi_prec; /* rapid dequeue hint (>= highest non-empty prec) */ + u16 max; /* total max packets */ + u16 len; /* total number of packets */ + /* + * q array must be last since # of elements can be either + * PKTQ_MAX_PREC or 1 + */ + struct pktq_prec q[PKTQ_MAX_PREC]; +}; + +/* operations on a specific precedence in packet queue */ + +static inline int pktq_plen(struct pktq *pq, int prec) +{ + return pq->q[prec].skblist.qlen; +} + +static inline int pktq_pavail(struct pktq *pq, int prec) +{ + return pq->q[prec].max - pq->q[prec].skblist.qlen; +} + +static inline bool pktq_pfull(struct pktq *pq, int prec) +{ + return pq->q[prec].skblist.qlen >= pq->q[prec].max; +} + +static inline bool pktq_pempty(struct pktq *pq, int prec) +{ + return skb_queue_empty(&pq->q[prec].skblist); +} + +static inline struct sk_buff *pktq_ppeek(struct pktq *pq, int prec) +{ + return skb_peek(&pq->q[prec].skblist); +} + +static inline struct sk_buff *pktq_ppeek_tail(struct pktq *pq, int prec) +{ + return skb_peek_tail(&pq->q[prec].skblist); +} + +extern struct sk_buff *brcmu_pktq_penq(struct pktq *pq, int prec, + struct sk_buff *p); +extern struct sk_buff *brcmu_pktq_penq_head(struct pktq *pq, int prec, + struct sk_buff *p); +extern struct sk_buff *brcmu_pktq_pdeq(struct pktq *pq, int prec); +extern struct sk_buff *brcmu_pktq_pdeq_tail(struct pktq *pq, int prec); +extern struct sk_buff *brcmu_pktq_pdeq_match(struct pktq *pq, int prec, + bool (*match_fn)(struct sk_buff *p, + void *arg), + void *arg); + +/* packet primitives */ +extern struct sk_buff *brcmu_pkt_buf_get_skb(uint len); +extern void brcmu_pkt_buf_free_skb(struct sk_buff *skb); + +/* Empty the queue at particular precedence level */ +/* callback function fn(pkt, arg) returns true if pkt belongs to if */ +extern void brcmu_pktq_pflush(struct pktq *pq, int prec, + bool dir, bool (*fn)(struct sk_buff *, void *), void *arg); + +/* operations on a set of precedences in packet queue */ + +extern int brcmu_pktq_mlen(struct pktq *pq, uint prec_bmp); +extern struct sk_buff *brcmu_pktq_mdeq(struct pktq *pq, uint prec_bmp, + int *prec_out); + +/* operations on packet queue as a whole */ + +static inline int pktq_len(struct pktq *pq) +{ + return (int)pq->len; +} + +static inline int pktq_max(struct pktq *pq) +{ + return (int)pq->max; +} + +static inline int pktq_avail(struct pktq *pq) +{ + return (int)(pq->max - pq->len); +} + +static inline bool pktq_full(struct pktq *pq) +{ + return pq->len >= pq->max; +} + +static inline bool pktq_empty(struct pktq *pq) +{ + return pq->len == 0; +} + +extern void brcmu_pktq_init(struct pktq *pq, int num_prec, int max_len); +/* prec_out may be NULL if caller is not interested in return value */ +extern struct sk_buff *brcmu_pktq_peek_tail(struct pktq *pq, int *prec_out); +extern void brcmu_pktq_flush(struct pktq *pq, bool dir, + bool (*fn)(struct sk_buff *, void *), void *arg); + +/* externs */ +/* ip address */ +struct ipv4_addr; + +/* + * bitfield macros using masking and shift + * + * remark: the mask parameter should be a shifted mask. + */ +static inline void brcmu_maskset32(u32 *var, u32 mask, u8 shift, u32 value) +{ + value = (value << shift) & mask; + *var = (*var & ~mask) | value; +} +static inline u32 brcmu_maskget32(u32 var, u32 mask, u8 shift) +{ + return (var & mask) >> shift; +} +static inline void brcmu_maskset16(u16 *var, u16 mask, u8 shift, u16 value) +{ + value = (value << shift) & mask; + *var = (*var & ~mask) | value; +} +static inline u16 brcmu_maskget16(u16 var, u16 mask, u8 shift) +{ + return (var & mask) >> shift; +} + +/* externs */ +/* format/print */ +#ifdef DEBUG +extern void brcmu_prpkt(const char *msg, struct sk_buff *p0); +#else +#define brcmu_prpkt(a, b) +#endif /* DEBUG */ + +#ifdef DEBUG +extern __printf(3, 4) +void brcmu_dbg_hex_dump(const void *data, size_t size, const char *fmt, ...); +#else +__printf(3, 4) +static inline +void brcmu_dbg_hex_dump(const void *data, size_t size, const char *fmt, ...) +{ +} +#endif + +#endif /* _BRCMU_UTILS_H_ */ diff --git a/drivers/net/wireless/brcm80211/include/brcmu_wifi.h b/drivers/net/wireless/brcm80211/include/brcmu_wifi.h new file mode 100644 index 0000000..0505cc0 --- /dev/null +++ b/drivers/net/wireless/brcm80211/include/brcmu_wifi.h @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCMU_WIFI_H_ +#define _BRCMU_WIFI_H_ + +#include /* for ETH_ALEN */ +#include /* for WLAN_PMKID_LEN */ + +/* + * A chanspec (u16) holds the channel number, band, bandwidth and control + * sideband + */ + +/* channel defines */ +#define CH_UPPER_SB 0x01 +#define CH_LOWER_SB 0x02 +#define CH_EWA_VALID 0x04 +#define CH_20MHZ_APART 4 +#define CH_10MHZ_APART 2 +#define CH_5MHZ_APART 1 /* 2G band channels are 5 Mhz apart */ +#define CH_MIN_2G_CHANNEL 1 +#define CH_MAX_2G_CHANNEL 14 /* Max channel in 2G band */ +#define CH_MIN_5G_CHANNEL 34 + +/* bandstate array indices */ +#define BAND_2G_INDEX 0 /* wlc->bandstate[x] index */ +#define BAND_5G_INDEX 1 /* wlc->bandstate[x] index */ + +/* + * max # supported channels. The max channel no is 216, this is that + 1 + * rounded up to a multiple of NBBY (8). DO NOT MAKE it > 255: channels are + * u8's all over +*/ +#define MAXCHANNEL 224 + +#define WL_CHANSPEC_CHAN_MASK 0x00ff +#define WL_CHANSPEC_CHAN_SHIFT 0 + +#define WL_CHANSPEC_CTL_SB_MASK 0x0300 +#define WL_CHANSPEC_CTL_SB_SHIFT 8 +#define WL_CHANSPEC_CTL_SB_LOWER 0x0100 +#define WL_CHANSPEC_CTL_SB_UPPER 0x0200 +#define WL_CHANSPEC_CTL_SB_NONE 0x0300 + +#define WL_CHANSPEC_BW_MASK 0x0C00 +#define WL_CHANSPEC_BW_SHIFT 10 +#define WL_CHANSPEC_BW_10 0x0400 +#define WL_CHANSPEC_BW_20 0x0800 +#define WL_CHANSPEC_BW_40 0x0C00 +#define WL_CHANSPEC_BW_80 0x2000 + +#define WL_CHANSPEC_BAND_MASK 0xf000 +#define WL_CHANSPEC_BAND_SHIFT 12 +#define WL_CHANSPEC_BAND_5G 0x1000 +#define WL_CHANSPEC_BAND_2G 0x2000 +#define INVCHANSPEC 255 + +#define WL_CHAN_VALID_HW (1 << 0) /* valid with current HW */ +#define WL_CHAN_VALID_SW (1 << 1) /* valid with country sett. */ +#define WL_CHAN_BAND_5G (1 << 2) /* 5GHz-band channel */ +#define WL_CHAN_RADAR (1 << 3) /* radar sensitive channel */ +#define WL_CHAN_INACTIVE (1 << 4) /* inactive due to radar */ +#define WL_CHAN_PASSIVE (1 << 5) /* channel in passive mode */ +#define WL_CHAN_RESTRICTED (1 << 6) /* restricted use channel */ + +/* values for band specific 40MHz capabilities */ +#define WLC_N_BW_20ALL 0 +#define WLC_N_BW_40ALL 1 +#define WLC_N_BW_20IN2G_40IN5G 2 + +/* band types */ +#define WLC_BAND_AUTO 0 /* auto-select */ +#define WLC_BAND_5G 1 /* 5 Ghz */ +#define WLC_BAND_2G 2 /* 2.4 Ghz */ +#define WLC_BAND_ALL 3 /* all bands */ + +#define CHSPEC_CHANNEL(chspec) ((u8)((chspec) & WL_CHANSPEC_CHAN_MASK)) +#define CHSPEC_BAND(chspec) ((chspec) & WL_CHANSPEC_BAND_MASK) + +#define CHSPEC_CTL_SB(chspec) ((chspec) & WL_CHANSPEC_CTL_SB_MASK) +#define CHSPEC_BW(chspec) ((chspec) & WL_CHANSPEC_BW_MASK) + +#define CHSPEC_IS10(chspec) \ + (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_10) + +#define CHSPEC_IS20(chspec) \ + (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_20) + +#define CHSPEC_IS40(chspec) \ + (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_40) + +#define CHSPEC_IS80(chspec) \ + (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_80) + +#define CHSPEC_IS5G(chspec) \ + (((chspec) & WL_CHANSPEC_BAND_MASK) == WL_CHANSPEC_BAND_5G) + +#define CHSPEC_IS2G(chspec) \ + (((chspec) & WL_CHANSPEC_BAND_MASK) == WL_CHANSPEC_BAND_2G) + +#define CHSPEC_SB_NONE(chspec) \ + (((chspec) & WL_CHANSPEC_CTL_SB_MASK) == WL_CHANSPEC_CTL_SB_NONE) + +#define CHSPEC_SB_UPPER(chspec) \ + (((chspec) & WL_CHANSPEC_CTL_SB_MASK) == WL_CHANSPEC_CTL_SB_UPPER) + +#define CHSPEC_SB_LOWER(chspec) \ + (((chspec) & WL_CHANSPEC_CTL_SB_MASK) == WL_CHANSPEC_CTL_SB_LOWER) + +#define CHSPEC_CTL_CHAN(chspec) \ + ((CHSPEC_SB_LOWER(chspec)) ? \ + (lower_20_sb(((chspec) & WL_CHANSPEC_CHAN_MASK))) : \ + (upper_20_sb(((chspec) & WL_CHANSPEC_CHAN_MASK)))) + +#define CHSPEC2BAND(chspec) (CHSPEC_IS5G(chspec) ? BRCM_BAND_5G : BRCM_BAND_2G) + +#define CHANSPEC_STR_LEN 8 + +static inline int lower_20_sb(int channel) +{ + return channel > CH_10MHZ_APART ? (channel - CH_10MHZ_APART) : 0; +} + +static inline int upper_20_sb(int channel) +{ + return (channel < (MAXCHANNEL - CH_10MHZ_APART)) ? + channel + CH_10MHZ_APART : 0; +} + +static inline int chspec_bandunit(u16 chspec) +{ + return CHSPEC_IS5G(chspec) ? BAND_5G_INDEX : BAND_2G_INDEX; +} + +static inline u16 ch20mhz_chspec(int channel) +{ + u16 rc = channel <= CH_MAX_2G_CHANNEL ? + WL_CHANSPEC_BAND_2G : WL_CHANSPEC_BAND_5G; + + return (u16)((u16)channel | WL_CHANSPEC_BW_20 | + WL_CHANSPEC_CTL_SB_NONE | rc); +} + +static inline int next_20mhz_chan(int channel) +{ + return channel < (MAXCHANNEL - CH_20MHZ_APART) ? + channel + CH_20MHZ_APART : 0; +} + +/* defined rate in 500kbps */ +#define BRCM_MAXRATE 108 /* in 500kbps units */ +#define BRCM_RATE_1M 2 /* in 500kbps units */ +#define BRCM_RATE_2M 4 /* in 500kbps units */ +#define BRCM_RATE_5M5 11 /* in 500kbps units */ +#define BRCM_RATE_11M 22 /* in 500kbps units */ +#define BRCM_RATE_6M 12 /* in 500kbps units */ +#define BRCM_RATE_9M 18 /* in 500kbps units */ +#define BRCM_RATE_12M 24 /* in 500kbps units */ +#define BRCM_RATE_18M 36 /* in 500kbps units */ +#define BRCM_RATE_24M 48 /* in 500kbps units */ +#define BRCM_RATE_36M 72 /* in 500kbps units */ +#define BRCM_RATE_48M 96 /* in 500kbps units */ +#define BRCM_RATE_54M 108 /* in 500kbps units */ + +#define BRCM_2G_25MHZ_OFFSET 5 /* 2.4GHz band channel offset */ + +#define MCSSET_LEN 16 + +static inline bool ac_bitmap_tst(u8 bitmap, int prec) +{ + return (bitmap & (1 << (prec))) != 0; +} + +/* Enumerate crypto algorithms */ +#define CRYPTO_ALGO_OFF 0 +#define CRYPTO_ALGO_WEP1 1 +#define CRYPTO_ALGO_TKIP 2 +#define CRYPTO_ALGO_WEP128 3 +#define CRYPTO_ALGO_AES_CCM 4 +#define CRYPTO_ALGO_AES_RESERVED1 5 +#define CRYPTO_ALGO_AES_RESERVED2 6 +#define CRYPTO_ALGO_NALG 7 + +/* wireless security bitvec */ + +#define WEP_ENABLED 0x0001 +#define TKIP_ENABLED 0x0002 +#define AES_ENABLED 0x0004 +#define WSEC_SWFLAG 0x0008 +/* to go into transition mode without setting wep */ +#define SES_OW_ENABLED 0x0040 + +/* WPA authentication mode bitvec */ +#define WPA_AUTH_DISABLED 0x0000 /* Legacy (i.e., non-WPA) */ +#define WPA_AUTH_NONE 0x0001 /* none (IBSS) */ +#define WPA_AUTH_UNSPECIFIED 0x0002 /* over 802.1x */ +#define WPA_AUTH_PSK 0x0004 /* Pre-shared key */ +#define WPA_AUTH_RESERVED1 0x0008 +#define WPA_AUTH_RESERVED2 0x0010 + +#define WPA2_AUTH_RESERVED1 0x0020 +#define WPA2_AUTH_UNSPECIFIED 0x0040 /* over 802.1x */ +#define WPA2_AUTH_PSK 0x0080 /* Pre-shared key */ +#define WPA2_AUTH_RESERVED3 0x0200 +#define WPA2_AUTH_RESERVED4 0x0400 +#define WPA2_AUTH_RESERVED5 0x0800 + +/* pmkid */ +#define MAXPMKID 16 + +#define DOT11_DEFAULT_RTS_LEN 2347 +#define DOT11_DEFAULT_FRAG_LEN 2346 + +#define DOT11_ICV_AES_LEN 8 +#define DOT11_QOS_LEN 2 +#define DOT11_IV_MAX_LEN 8 +#define DOT11_A4_HDR_LEN 30 + +#define HT_CAP_RX_STBC_NO 0x0 +#define HT_CAP_RX_STBC_ONE_STREAM 0x1 + +struct pmkid { + u8 BSSID[ETH_ALEN]; + u8 PMKID[WLAN_PMKID_LEN]; +}; + +struct pmkid_list { + __le32 npmkid; + struct pmkid pmkid[1]; +}; + +struct pmkid_cand { + u8 BSSID[ETH_ALEN]; + u8 preauth; +}; + +struct pmkid_cand_list { + u32 npmkid_cand; + struct pmkid_cand pmkid_cand[1]; +}; + +#endif /* _BRCMU_WIFI_H_ */ diff --git a/drivers/net/wireless/brcm80211/include/chipcommon.h b/drivers/net/wireless/brcm80211/include/chipcommon.h new file mode 100644 index 0000000..d242333 --- /dev/null +++ b/drivers/net/wireless/brcm80211/include/chipcommon.h @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _SBCHIPC_H +#define _SBCHIPC_H + +#include "defs.h" /* for PAD macro */ + +#define CHIPCREGOFFS(field) offsetof(struct chipcregs, field) + +struct chipcregs { + u32 chipid; /* 0x0 */ + u32 capabilities; + u32 corecontrol; /* corerev >= 1 */ + u32 bist; + + /* OTP */ + u32 otpstatus; /* 0x10, corerev >= 10 */ + u32 otpcontrol; + u32 otpprog; + u32 otplayout; /* corerev >= 23 */ + + /* Interrupt control */ + u32 intstatus; /* 0x20 */ + u32 intmask; + + /* Chip specific regs */ + u32 chipcontrol; /* 0x28, rev >= 11 */ + u32 chipstatus; /* 0x2c, rev >= 11 */ + + /* Jtag Master */ + u32 jtagcmd; /* 0x30, rev >= 10 */ + u32 jtagir; + u32 jtagdr; + u32 jtagctrl; + + /* serial flash interface registers */ + u32 flashcontrol; /* 0x40 */ + u32 flashaddress; + u32 flashdata; + u32 PAD[1]; + + /* Silicon backplane configuration broadcast control */ + u32 broadcastaddress; /* 0x50 */ + u32 broadcastdata; + + /* gpio - cleared only by power-on-reset */ + u32 gpiopullup; /* 0x58, corerev >= 20 */ + u32 gpiopulldown; /* 0x5c, corerev >= 20 */ + u32 gpioin; /* 0x60 */ + u32 gpioout; /* 0x64 */ + u32 gpioouten; /* 0x68 */ + u32 gpiocontrol; /* 0x6C */ + u32 gpiointpolarity; /* 0x70 */ + u32 gpiointmask; /* 0x74 */ + + /* GPIO events corerev >= 11 */ + u32 gpioevent; + u32 gpioeventintmask; + + /* Watchdog timer */ + u32 watchdog; /* 0x80 */ + + /* GPIO events corerev >= 11 */ + u32 gpioeventintpolarity; + + /* GPIO based LED powersave registers corerev >= 16 */ + u32 gpiotimerval; /* 0x88 */ + u32 gpiotimeroutmask; + + /* clock control */ + u32 clockcontrol_n; /* 0x90 */ + u32 clockcontrol_sb; /* aka m0 */ + u32 clockcontrol_pci; /* aka m1 */ + u32 clockcontrol_m2; /* mii/uart/mipsref */ + u32 clockcontrol_m3; /* cpu */ + u32 clkdiv; /* corerev >= 3 */ + u32 gpiodebugsel; /* corerev >= 28 */ + u32 capabilities_ext; /* 0xac */ + + /* pll delay registers (corerev >= 4) */ + u32 pll_on_delay; /* 0xb0 */ + u32 fref_sel_delay; + u32 slow_clk_ctl; /* 5 < corerev < 10 */ + u32 PAD; + + /* Instaclock registers (corerev >= 10) */ + u32 system_clk_ctl; /* 0xc0 */ + u32 clkstatestretch; + u32 PAD[2]; + + /* Indirect backplane access (corerev >= 22) */ + u32 bp_addrlow; /* 0xd0 */ + u32 bp_addrhigh; + u32 bp_data; + u32 PAD; + u32 bp_indaccess; + u32 PAD[3]; + + /* More clock dividers (corerev >= 32) */ + u32 clkdiv2; + u32 PAD[2]; + + /* In AI chips, pointer to erom */ + u32 eromptr; /* 0xfc */ + + /* ExtBus control registers (corerev >= 3) */ + u32 pcmcia_config; /* 0x100 */ + u32 pcmcia_memwait; + u32 pcmcia_attrwait; + u32 pcmcia_iowait; + u32 ide_config; + u32 ide_memwait; + u32 ide_attrwait; + u32 ide_iowait; + u32 prog_config; + u32 prog_waitcount; + u32 flash_config; + u32 flash_waitcount; + u32 SECI_config; /* 0x130 SECI configuration */ + u32 PAD[3]; + + /* Enhanced Coexistence Interface (ECI) registers (corerev >= 21) */ + u32 eci_output; /* 0x140 */ + u32 eci_control; + u32 eci_inputlo; + u32 eci_inputmi; + u32 eci_inputhi; + u32 eci_inputintpolaritylo; + u32 eci_inputintpolaritymi; + u32 eci_inputintpolarityhi; + u32 eci_intmasklo; + u32 eci_intmaskmi; + u32 eci_intmaskhi; + u32 eci_eventlo; + u32 eci_eventmi; + u32 eci_eventhi; + u32 eci_eventmasklo; + u32 eci_eventmaskmi; + u32 eci_eventmaskhi; + u32 PAD[3]; + + /* SROM interface (corerev >= 32) */ + u32 sromcontrol; /* 0x190 */ + u32 sromaddress; + u32 sromdata; + u32 PAD[17]; + + /* Clock control and hardware workarounds (corerev >= 20) */ + u32 clk_ctl_st; /* 0x1e0 */ + u32 hw_war; + u32 PAD[70]; + + /* UARTs */ + u8 uart0data; /* 0x300 */ + u8 uart0imr; + u8 uart0fcr; + u8 uart0lcr; + u8 uart0mcr; + u8 uart0lsr; + u8 uart0msr; + u8 uart0scratch; + u8 PAD[248]; /* corerev >= 1 */ + + u8 uart1data; /* 0x400 */ + u8 uart1imr; + u8 uart1fcr; + u8 uart1lcr; + u8 uart1mcr; + u8 uart1lsr; + u8 uart1msr; + u8 uart1scratch; + u32 PAD[126]; + + /* PMU registers (corerev >= 20) */ + u32 pmucontrol; /* 0x600 */ + u32 pmucapabilities; + u32 pmustatus; + u32 res_state; + u32 res_pending; + u32 pmutimer; + u32 min_res_mask; + u32 max_res_mask; + u32 res_table_sel; + u32 res_dep_mask; + u32 res_updn_timer; + u32 res_timer; + u32 clkstretch; + u32 pmuwatchdog; + u32 gpiosel; /* 0x638, rev >= 1 */ + u32 gpioenable; /* 0x63c, rev >= 1 */ + u32 res_req_timer_sel; + u32 res_req_timer; + u32 res_req_mask; + u32 pmucapabilities_ext; /* 0x64c, pmurev >=15 */ + u32 chipcontrol_addr; /* 0x650 */ + u32 chipcontrol_data; /* 0x654 */ + u32 regcontrol_addr; + u32 regcontrol_data; + u32 pllcontrol_addr; + u32 pllcontrol_data; + u32 pmustrapopt; /* 0x668, corerev >= 28 */ + u32 pmu_xtalfreq; /* 0x66C, pmurev >= 10 */ + u32 retention_ctl; /* 0x670, pmurev >= 15 */ + u32 PAD[3]; + u32 retention_grpidx; /* 0x680 */ + u32 retention_grpctl; /* 0x684 */ + u32 PAD[94]; + u16 sromotp[768]; +}; + +/* chipid */ +#define CID_ID_MASK 0x0000ffff /* Chip Id mask */ +#define CID_REV_MASK 0x000f0000 /* Chip Revision mask */ +#define CID_REV_SHIFT 16 /* Chip Revision shift */ +#define CID_PKG_MASK 0x00f00000 /* Package Option mask */ +#define CID_PKG_SHIFT 20 /* Package Option shift */ +#define CID_CC_MASK 0x0f000000 /* CoreCount (corerev >= 4) */ +#define CID_CC_SHIFT 24 +#define CID_TYPE_MASK 0xf0000000 /* Chip Type */ +#define CID_TYPE_SHIFT 28 + +/* capabilities */ +#define CC_CAP_UARTS_MASK 0x00000003 /* Number of UARTs */ +#define CC_CAP_MIPSEB 0x00000004 /* MIPS is in big-endian mode */ +#define CC_CAP_UCLKSEL 0x00000018 /* UARTs clock select */ +/* UARTs are driven by internal divided clock */ +#define CC_CAP_UINTCLK 0x00000008 +#define CC_CAP_UARTGPIO 0x00000020 /* UARTs own GPIOs 15:12 */ +#define CC_CAP_EXTBUS_MASK 0x000000c0 /* External bus mask */ +#define CC_CAP_EXTBUS_NONE 0x00000000 /* No ExtBus present */ +#define CC_CAP_EXTBUS_FULL 0x00000040 /* ExtBus: PCMCIA, IDE & Prog */ +#define CC_CAP_EXTBUS_PROG 0x00000080 /* ExtBus: ProgIf only */ +#define CC_CAP_FLASH_MASK 0x00000700 /* Type of flash */ +#define CC_CAP_PLL_MASK 0x00038000 /* Type of PLL */ +#define CC_CAP_PWR_CTL 0x00040000 /* Power control */ +#define CC_CAP_OTPSIZE 0x00380000 /* OTP Size (0 = none) */ +#define CC_CAP_OTPSIZE_SHIFT 19 /* OTP Size shift */ +#define CC_CAP_OTPSIZE_BASE 5 /* OTP Size base */ +#define CC_CAP_JTAGP 0x00400000 /* JTAG Master Present */ +#define CC_CAP_ROM 0x00800000 /* Internal boot rom active */ +#define CC_CAP_BKPLN64 0x08000000 /* 64-bit backplane */ +#define CC_CAP_PMU 0x10000000 /* PMU Present, rev >= 20 */ +#define CC_CAP_SROM 0x40000000 /* Srom Present, rev >= 32 */ +/* Nand flash present, rev >= 35 */ +#define CC_CAP_NFLASH 0x80000000 + +#define CC_CAP2_SECI 0x00000001 /* SECI Present, rev >= 36 */ +/* GSIO (spi/i2c) present, rev >= 37 */ +#define CC_CAP2_GSIO 0x00000002 + +/* pmucapabilities */ +#define PCAP_REV_MASK 0x000000ff +#define PCAP_RC_MASK 0x00001f00 +#define PCAP_RC_SHIFT 8 +#define PCAP_TC_MASK 0x0001e000 +#define PCAP_TC_SHIFT 13 +#define PCAP_PC_MASK 0x001e0000 +#define PCAP_PC_SHIFT 17 +#define PCAP_VC_MASK 0x01e00000 +#define PCAP_VC_SHIFT 21 +#define PCAP_CC_MASK 0x1e000000 +#define PCAP_CC_SHIFT 25 +#define PCAP5_PC_MASK 0x003e0000 /* PMU corerev >= 5 */ +#define PCAP5_PC_SHIFT 17 +#define PCAP5_VC_MASK 0x07c00000 +#define PCAP5_VC_SHIFT 22 +#define PCAP5_CC_MASK 0xf8000000 +#define PCAP5_CC_SHIFT 27 +/* pmucapabilites_ext PMU rev >= 15 */ +#define PCAPEXT_SR_SUPPORTED_MASK (1 << 1) +/* retention_ctl PMU rev >= 15 */ +#define PMU_RCTL_MACPHY_DISABLE_MASK (1 << 26) +#define PMU_RCTL_LOGIC_DISABLE_MASK (1 << 27) + + +/* +* Maximum delay for the PMU state transition in us. +* This is an upper bound intended for spinwaits etc. +*/ +#define PMU_MAX_TRANSITION_DLY 15000 + +#endif /* _SBCHIPC_H */ diff --git a/drivers/net/wireless/brcm80211/include/defs.h b/drivers/net/wireless/brcm80211/include/defs.h new file mode 100644 index 0000000..fb7cbcf --- /dev/null +++ b/drivers/net/wireless/brcm80211/include/defs.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_DEFS_H_ +#define _BRCM_DEFS_H_ + +#include + +#define SI_BUS 0 +#define PCI_BUS 1 +#define PCMCIA_BUS 2 +#define SDIO_BUS 3 +#define JTAG_BUS 4 +#define USB_BUS 5 +#define SPI_BUS 6 + +#define OFF 0 +#define ON 1 /* ON = 1 */ +#define AUTO (-1) /* Auto = -1 */ + +/* + * Priority definitions according 802.1D + */ +#define PRIO_8021D_NONE 2 +#define PRIO_8021D_BK 1 +#define PRIO_8021D_BE 0 +#define PRIO_8021D_EE 3 +#define PRIO_8021D_CL 4 +#define PRIO_8021D_VI 5 +#define PRIO_8021D_VO 6 +#define PRIO_8021D_NC 7 + +#define MAXPRIO 7 +#define NUMPRIO (MAXPRIO + 1) + +#define WL_NUMRATES 16 /* max # of rates in a rateset */ + +#define BRCM_CNTRY_BUF_SZ 4 /* Country string is 3 bytes + NUL */ + +#define BRCM_SET_CHANNEL 30 +#define BRCM_SET_SRL 32 +#define BRCM_SET_LRL 34 +#define BRCM_SET_BCNPRD 76 + +#define BRCM_GET_CURR_RATESET 114 /* current rateset */ +#define BRCM_GET_PHYLIST 180 + +/* Bit masks for radio disabled status - returned by WL_GET_RADIO */ + +#define WL_RADIO_SW_DISABLE (1<<0) +#define WL_RADIO_HW_DISABLE (1<<1) +/* some countries don't support any channel */ +#define WL_RADIO_COUNTRY_DISABLE (1<<3) + +/* Override bit for SET_TXPWR. if set, ignore other level limits */ +#define WL_TXPWR_OVERRIDE (1U<<31) + +/* band types */ +#define BRCM_BAND_AUTO 0 /* auto-select */ +#define BRCM_BAND_5G 1 /* 5 Ghz */ +#define BRCM_BAND_2G 2 /* 2.4 Ghz */ +#define BRCM_BAND_ALL 3 /* all bands */ + +/* Values for PM */ +#define PM_OFF 0 +#define PM_MAX 1 + +/* Debug levels */ +#define BRCM_DL_INFO 0x00000001 +#define BRCM_DL_MAC80211 0x00000002 +#define BRCM_DL_RX 0x00000004 +#define BRCM_DL_TX 0x00000008 +#define BRCM_DL_INT 0x00000010 +#define BRCM_DL_DMA 0x00000020 +#define BRCM_DL_HT 0x00000040 + +#define PM_OFF 0 +#define PM_MAX 1 +#define PM_FAST 2 + +/* + * Sonics Configuration Space Registers. + */ + +/* core sbconfig regs are top 256bytes of regs */ +#define SBCONFIGOFF 0xf00 + +/* cpp contortions to concatenate w/arg prescan */ +#ifndef PAD +#define _PADLINE(line) pad ## line +#define _XSTR(line) _PADLINE(line) +#define PAD _XSTR(__LINE__) +#endif + +#endif /* _BRCM_DEFS_H_ */ diff --git a/drivers/net/wireless/brcm80211/include/soc.h b/drivers/net/wireless/brcm80211/include/soc.h new file mode 100644 index 0000000..123cfa8 --- /dev/null +++ b/drivers/net/wireless/brcm80211/include/soc.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2010 Broadcom Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BRCM_SOC_H +#define _BRCM_SOC_H + +#define SI_ENUM_BASE 0x18000000 /* Enumeration space base */ + +/* Common core control flags */ +#define SICF_BIST_EN 0x8000 +#define SICF_PME_EN 0x4000 +#define SICF_CORE_BITS 0x3ffc +#define SICF_FGC 0x0002 +#define SICF_CLOCK_EN 0x0001 + +/* Common core status flags */ +#define SISF_BIST_DONE 0x8000 +#define SISF_BIST_ERROR 0x4000 +#define SISF_GATED_CLK 0x2000 +#define SISF_DMA64 0x1000 +#define SISF_CORE_BITS 0x0fff + +#endif /* _BRCM_SOC_H */ diff --git a/drivers/net/wireless/ipw2x00/Kconfig b/drivers/net/wireless/ipw2x00/Kconfig new file mode 100644 index 0000000..1e12304 --- /dev/null +++ b/drivers/net/wireless/ipw2x00/Kconfig @@ -0,0 +1,201 @@ +# +# Intel Centrino wireless drivers +# + +config IPW2100 + tristate "Intel PRO/Wireless 2100 Network Connection" + depends on m + depends on PCI && CFG80211 + depends on WIRELESS_EXT + depends on WEXT_SPY + depends on WEXT_PRIV + select BACKPORT_FW_LOADER + select LIB80211 + select LIBIPW + ---help--- + A driver for the Intel PRO/Wireless 2100 Network + Connection 802.11b wireless network adapter. + + See for information on + the capabilities currently enabled in this driver and for tips + for debugging issues and problems. + + In order to use this driver, you will need a firmware image for it. + You can obtain the firmware from + . Once you have the firmware image, you + will need to place it in /lib/firmware. + + You will also very likely need the Wireless Tools in order to + configure your card: + + . + + It is recommended that you compile this driver as a module (M) + rather than built-in (Y). This driver requires firmware at device + initialization time, and when built-in this typically happens + before the filesystem is accessible (hence firmware will be + unavailable and initialization will fail). If you do choose to build + this driver into your kernel image, you can avoid this problem by + including the firmware and a firmware loader in an initramfs. + +config IPW2100_MONITOR + bool "Enable promiscuous mode" + depends on IPW2100 + ---help--- + Enables promiscuous/monitor mode support for the ipw2100 driver. + With this feature compiled into the driver, you can switch to + promiscuous mode via the Wireless Tool's Monitor mode. While in this + mode, no packets can be sent. + +config IPW2100_DEBUG + bool "Enable full debugging output in IPW2100 module." + depends on IPW2100 + ---help--- + This option will enable debug tracing output for the IPW2100. + + This will result in the kernel module being ~60k larger. You can + control which debug output is sent to the kernel log by setting the + value in + + /sys/bus/pci/drivers/ipw2100/debug_level + + This entry will only exist if this option is enabled. + + If you are not trying to debug or develop the IPW2100 driver, you + most likely want to say N here. + +config IPW2200 + tristate "Intel PRO/Wireless 2200BG and 2915ABG Network Connection" + depends on m + depends on PCI && CFG80211 && CFG80211_WEXT + depends on WIRELESS_EXT + depends on WEXT_SPY + depends on WEXT_PRIV + select BACKPORT_FW_LOADER + select LIB80211 + select LIBIPW + ---help--- + A driver for the Intel PRO/Wireless 2200BG and 2915ABG Network + Connection adapters. + + See for + information on the capabilities currently enabled in this + driver and for tips for debugging issues and problems. + + In order to use this driver, you will need a firmware image for it. + You can obtain the firmware from + . See the above referenced README.ipw2200 + for information on where to install the firmware images. + + You will also very likely need the Wireless Tools in order to + configure your card: + + . + + It is recommended that you compile this driver as a module (M) + rather than built-in (Y). This driver requires firmware at device + initialization time, and when built-in this typically happens + before the filesystem is accessible (hence firmware will be + unavailable and initialization will fail). If you do choose to build + this driver into your kernel image, you can avoid this problem by + including the firmware and a firmware loader in an initramfs. + +config IPW2200_MONITOR + bool "Enable promiscuous mode" + depends on IPW2200 + ---help--- + Enables promiscuous/monitor mode support for the ipw2200 driver. + With this feature compiled into the driver, you can switch to + promiscuous mode via the Wireless Tool's Monitor mode. While in this + mode, no packets can be sent. + +config IPW2200_RADIOTAP + bool "Enable radiotap format 802.11 raw packet support" + depends on IPW2200_MONITOR + +config IPW2200_PROMISCUOUS + bool "Enable creation of a RF radiotap promiscuous interface" + depends on IPW2200_MONITOR + select IPW2200_RADIOTAP + ---help--- + Enables the creation of a second interface prefixed 'rtap'. + This second interface will provide every received in radiotap + format. + + This is useful for performing wireless network analysis while + maintaining an active association. + + Example usage: + + % modprobe ipw2200 rtap_iface=1 + % ifconfig rtap0 up + % tethereal -i rtap0 + + If you do not specify 'rtap_iface=1' as a module parameter then + the rtap interface will not be created and you will need to turn + it on via sysfs: + + % echo 1 > /sys/bus/pci/drivers/ipw2200/*/rtap_iface + +config IPW2200_QOS + bool "Enable QoS support" + depends on IPW2200 + +config IPW2200_DEBUG + bool "Enable full debugging output in IPW2200 module." + depends on IPW2200 + ---help--- + This option will enable low level debug tracing output for IPW2200. + + Note, normal debug code is already compiled in. This low level + debug option enables debug on hot paths (e.g Tx, Rx, ISR) and + will result in the kernel module being ~70 larger. Most users + will typically not need this high verbosity debug information. + + If you are not sure, say N here. + +config LIBIPW + tristate + depends on m + depends on PCI && CFG80211 + depends on WIRELESS_EXT + depends on WEXT_SPY + depends on CRYPTO + depends on CRYPTO_ARC4 + depends on CRYPTO_ECB + depends on CRYPTO_AES + depends on CRYPTO_MICHAEL_MIC + depends on CRYPTO_ECB + depends on CRC32 + select LIB80211 + select LIB80211_CRYPT_WEP + select LIB80211_CRYPT_TKIP + select LIB80211_CRYPT_CCMP + ---help--- + This option enables the hardware independent IEEE 802.11 + networking stack. This component is deprecated in favor of the + mac80211 component. + +config LIBIPW_DEBUG + depends on !BACKPORT_KERNEL_2_6_25 + bool "Full debugging output for the LIBIPW component" + depends on LIBIPW + ---help--- + This option will enable debug tracing output for the + libipw component. + + This will result in the kernel module being ~70k larger. You + can control which debug output is sent to the kernel log by + setting the value in + + /proc/net/ieee80211/debug_level + + For example: + + % echo 0x00000FFO > /proc/net/ieee80211/debug_level + + For a list of values you can assign to debug_level, you + can look at the bit mask values in ieee80211.h + + If you are not trying to debug or develop the libipw + component, you most likely want to say N here. diff --git a/drivers/net/wireless/ipw2x00/Makefile b/drivers/net/wireless/ipw2x00/Makefile new file mode 100644 index 0000000..da8e794 --- /dev/null +++ b/drivers/net/wireless/ipw2x00/Makefile @@ -0,0 +1,14 @@ +# +# Makefile for the Intel Centrino wireless drivers +# + +obj-$(CPTCFG_IPW2100) += ipw2100.o +obj-$(CPTCFG_IPW2200) += ipw2200.o + +obj-$(CPTCFG_LIBIPW) += libipw.o +libipw-objs := \ + libipw_module.o \ + libipw_tx.o \ + libipw_rx.o \ + libipw_wx.o \ + libipw_geo.o diff --git a/drivers/net/wireless/ipw2x00/ipw.h b/drivers/net/wireless/ipw2x00/ipw.h new file mode 100644 index 0000000..4007bf5 --- /dev/null +++ b/drivers/net/wireless/ipw2x00/ipw.h @@ -0,0 +1,23 @@ +/* + * Intel Pro/Wireless 2100, 2200BG, 2915ABG network connection driver + * + * Copyright 2012 Stanislav Yakovlev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __IPW_H__ +#define __IPW_H__ + +#include + +static const u32 ipw_cipher_suites[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, +}; + +#endif diff --git a/drivers/net/wireless/ipw2x00/ipw2100.c b/drivers/net/wireless/ipw2x00/ipw2100.c new file mode 100644 index 0000000..48e45da --- /dev/null +++ b/drivers/net/wireless/ipw2x00/ipw2100.c @@ -0,0 +1,8664 @@ +/****************************************************************************** + + Copyright(c) 2003 - 2006 Intel Corporation. All rights reserved. + + This program is free software; you can redistribute it and/or modify it + under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Contact Information: + Intel Linux Wireless + Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + + Portions of this file are based on the sample_* files provided by Wireless + Extensions 0.26 package and copyright (c) 1997-2003 Jean Tourrilhes + + + Portions of this file are based on the Host AP project, + Copyright (c) 2001-2002, SSH Communications Security Corp and Jouni Malinen + + Copyright (c) 2002-2003, Jouni Malinen + + Portions of ipw2100_mod_firmware_load, ipw2100_do_mod_firmware_load, and + ipw2100_fw_load are loosely based on drivers/sound/sound_firmware.c + available in the 2.4.25 kernel sources, and are copyright (c) Alan Cox + +******************************************************************************/ +/* + + Initial driver on which this is based was developed by Janusz Gorycki, + Maciej Urbaniak, and Maciej Sosnowski. + + Promiscuous mode support added by Jacek Wysoczynski and Maciej Urbaniak. + +Theory of Operation + +Tx - Commands and Data + +Firmware and host share a circular queue of Transmit Buffer Descriptors (TBDs) +Each TBD contains a pointer to the physical (dma_addr_t) address of data being +sent to the firmware as well as the length of the data. + +The host writes to the TBD queue at the WRITE index. The WRITE index points +to the _next_ packet to be written and is advanced when after the TBD has been +filled. + +The firmware pulls from the TBD queue at the READ index. The READ index points +to the currently being read entry, and is advanced once the firmware is +done with a packet. + +When data is sent to the firmware, the first TBD is used to indicate to the +firmware if a Command or Data is being sent. If it is Command, all of the +command information is contained within the physical address referred to by the +TBD. If it is Data, the first TBD indicates the type of data packet, number +of fragments, etc. The next TBD then refers to the actual packet location. + +The Tx flow cycle is as follows: + +1) ipw2100_tx() is called by kernel with SKB to transmit +2) Packet is move from the tx_free_list and appended to the transmit pending + list (tx_pend_list) +3) work is scheduled to move pending packets into the shared circular queue. +4) when placing packet in the circular queue, the incoming SKB is DMA mapped + to a physical address. That address is entered into a TBD. Two TBDs are + filled out. The first indicating a data packet, the second referring to the + actual payload data. +5) the packet is removed from tx_pend_list and placed on the end of the + firmware pending list (fw_pend_list) +6) firmware is notified that the WRITE index has +7) Once the firmware has processed the TBD, INTA is triggered. +8) For each Tx interrupt received from the firmware, the READ index is checked + to see which TBDs are done being processed. +9) For each TBD that has been processed, the ISR pulls the oldest packet + from the fw_pend_list. +10)The packet structure contained in the fw_pend_list is then used + to unmap the DMA address and to free the SKB originally passed to the driver + from the kernel. +11)The packet structure is placed onto the tx_free_list + +The above steps are the same for commands, only the msg_free_list/msg_pend_list +are used instead of tx_free_list/tx_pend_list + +... + +Critical Sections / Locking : + +There are two locks utilized. The first is the low level lock (priv->low_lock) +that protects the following: + +- Access to the Tx/Rx queue lists via priv->low_lock. The lists are as follows: + + tx_free_list : Holds pre-allocated Tx buffers. + TAIL modified in __ipw2100_tx_process() + HEAD modified in ipw2100_tx() + + tx_pend_list : Holds used Tx buffers waiting to go into the TBD ring + TAIL modified ipw2100_tx() + HEAD modified by ipw2100_tx_send_data() + + msg_free_list : Holds pre-allocated Msg (Command) buffers + TAIL modified in __ipw2100_tx_process() + HEAD modified in ipw2100_hw_send_command() + + msg_pend_list : Holds used Msg buffers waiting to go into the TBD ring + TAIL modified in ipw2100_hw_send_command() + HEAD modified in ipw2100_tx_send_commands() + + The flow of data on the TX side is as follows: + + MSG_FREE_LIST + COMMAND => MSG_PEND_LIST => TBD => MSG_FREE_LIST + TX_FREE_LIST + DATA => TX_PEND_LIST => TBD => TX_FREE_LIST + + The methods that work on the TBD ring are protected via priv->low_lock. + +- The internal data state of the device itself +- Access to the firmware read/write indexes for the BD queues + and associated logic + +All external entry functions are locked with the priv->action_lock to ensure +that only one external action is invoked at a time. + + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ipw2100.h" +#include "ipw.h" + +#define IPW2100_VERSION "git-1.2.2" + +#define DRV_NAME "ipw2100" +#define DRV_VERSION IPW2100_VERSION +#define DRV_DESCRIPTION "Intel(R) PRO/Wireless 2100 Network Driver" +#define DRV_COPYRIGHT "Copyright(c) 2003-2006 Intel Corporation" + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,2,0)) +static struct pm_qos_request_list ipw2100_pm_qos_req; +#else +static struct pm_qos_request ipw2100_pm_qos_req; +#endif + +/* Debugging stuff */ +#ifdef CPTCFG_IPW2100_DEBUG +#define IPW2100_RX_DEBUG /* Reception debugging */ +#endif + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR(DRV_COPYRIGHT); +MODULE_LICENSE("GPL"); + +static int debug = 0; +static int network_mode = 0; +static int channel = 0; +static int associate = 0; +static int disable = 0; +#ifdef CONFIG_PM +static struct ipw2100_fw ipw2100_firmware; +#endif + +#include +module_param(debug, int, 0444); +module_param_named(mode, network_mode, int, 0444); +module_param(channel, int, 0444); +module_param(associate, int, 0444); +module_param(disable, int, 0444); + +MODULE_PARM_DESC(debug, "debug level"); +MODULE_PARM_DESC(mode, "network mode (0=BSS,1=IBSS,2=Monitor)"); +MODULE_PARM_DESC(channel, "channel"); +MODULE_PARM_DESC(associate, "auto associate when scanning (default off)"); +MODULE_PARM_DESC(disable, "manually disable the radio (default 0 [radio on])"); + +static u32 ipw2100_debug_level = IPW_DL_NONE; + +#ifdef CPTCFG_IPW2100_DEBUG +#define IPW_DEBUG(level, message...) \ +do { \ + if (ipw2100_debug_level & (level)) { \ + printk(KERN_DEBUG "ipw2100: %c %s ", \ + in_interrupt() ? 'I' : 'U', __func__); \ + printk(message); \ + } \ +} while (0) +#else +#define IPW_DEBUG(level, message...) do {} while (0) +#endif /* CPTCFG_IPW2100_DEBUG */ + +#ifdef CPTCFG_IPW2100_DEBUG +static const char *command_types[] = { + "undefined", + "unused", /* HOST_ATTENTION */ + "HOST_COMPLETE", + "unused", /* SLEEP */ + "unused", /* HOST_POWER_DOWN */ + "unused", + "SYSTEM_CONFIG", + "unused", /* SET_IMR */ + "SSID", + "MANDATORY_BSSID", + "AUTHENTICATION_TYPE", + "ADAPTER_ADDRESS", + "PORT_TYPE", + "INTERNATIONAL_MODE", + "CHANNEL", + "RTS_THRESHOLD", + "FRAG_THRESHOLD", + "POWER_MODE", + "TX_RATES", + "BASIC_TX_RATES", + "WEP_KEY_INFO", + "unused", + "unused", + "unused", + "unused", + "WEP_KEY_INDEX", + "WEP_FLAGS", + "ADD_MULTICAST", + "CLEAR_ALL_MULTICAST", + "BEACON_INTERVAL", + "ATIM_WINDOW", + "CLEAR_STATISTICS", + "undefined", + "undefined", + "undefined", + "undefined", + "TX_POWER_INDEX", + "undefined", + "undefined", + "undefined", + "undefined", + "undefined", + "undefined", + "BROADCAST_SCAN", + "CARD_DISABLE", + "PREFERRED_BSSID", + "SET_SCAN_OPTIONS", + "SCAN_DWELL_TIME", + "SWEEP_TABLE", + "AP_OR_STATION_TABLE", + "GROUP_ORDINALS", + "SHORT_RETRY_LIMIT", + "LONG_RETRY_LIMIT", + "unused", /* SAVE_CALIBRATION */ + "unused", /* RESTORE_CALIBRATION */ + "undefined", + "undefined", + "undefined", + "HOST_PRE_POWER_DOWN", + "unused", /* HOST_INTERRUPT_COALESCING */ + "undefined", + "CARD_DISABLE_PHY_OFF", + "MSDU_TX_RATES", + "undefined", + "SET_STATION_STAT_BITS", + "CLEAR_STATIONS_STAT_BITS", + "LEAP_ROGUE_MODE", + "SET_SECURITY_INFORMATION", + "DISASSOCIATION_BSSID", + "SET_WPA_ASS_IE" +}; +#endif + +static const long ipw2100_frequencies[] = { + 2412, 2417, 2422, 2427, + 2432, 2437, 2442, 2447, + 2452, 2457, 2462, 2467, + 2472, 2484 +}; + +#define FREQ_COUNT ARRAY_SIZE(ipw2100_frequencies) + +static struct ieee80211_rate ipw2100_bg_rates[] = { + { .bitrate = 10 }, + { .bitrate = 20, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 55, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 110, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, +}; + +#define RATE_COUNT ARRAY_SIZE(ipw2100_bg_rates) + +/* Pre-decl until we get the code solid and then we can clean it up */ +static void ipw2100_tx_send_commands(struct ipw2100_priv *priv); +static void ipw2100_tx_send_data(struct ipw2100_priv *priv); +static int ipw2100_adapter_setup(struct ipw2100_priv *priv); + +static void ipw2100_queues_initialize(struct ipw2100_priv *priv); +static void ipw2100_queues_free(struct ipw2100_priv *priv); +static int ipw2100_queues_allocate(struct ipw2100_priv *priv); + +static int ipw2100_fw_download(struct ipw2100_priv *priv, + struct ipw2100_fw *fw); +static int ipw2100_get_firmware(struct ipw2100_priv *priv, + struct ipw2100_fw *fw); +static int ipw2100_get_fwversion(struct ipw2100_priv *priv, char *buf, + size_t max); +static int ipw2100_get_ucodeversion(struct ipw2100_priv *priv, char *buf, + size_t max); +static void ipw2100_release_firmware(struct ipw2100_priv *priv, + struct ipw2100_fw *fw); +static int ipw2100_ucode_download(struct ipw2100_priv *priv, + struct ipw2100_fw *fw); +static void ipw2100_wx_event_work(struct work_struct *work); +static struct iw_statistics *ipw2100_wx_wireless_stats(struct net_device *dev); +static struct iw_handler_def ipw2100_wx_handler_def; + +static inline void read_register(struct net_device *dev, u32 reg, u32 * val) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + + *val = ioread32(priv->ioaddr + reg); + IPW_DEBUG_IO("r: 0x%08X => 0x%08X\n", reg, *val); +} + +static inline void write_register(struct net_device *dev, u32 reg, u32 val) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + + iowrite32(val, priv->ioaddr + reg); + IPW_DEBUG_IO("w: 0x%08X <= 0x%08X\n", reg, val); +} + +static inline void read_register_word(struct net_device *dev, u32 reg, + u16 * val) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + + *val = ioread16(priv->ioaddr + reg); + IPW_DEBUG_IO("r: 0x%08X => %04X\n", reg, *val); +} + +static inline void read_register_byte(struct net_device *dev, u32 reg, u8 * val) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + + *val = ioread8(priv->ioaddr + reg); + IPW_DEBUG_IO("r: 0x%08X => %02X\n", reg, *val); +} + +static inline void write_register_word(struct net_device *dev, u32 reg, u16 val) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + + iowrite16(val, priv->ioaddr + reg); + IPW_DEBUG_IO("w: 0x%08X <= %04X\n", reg, val); +} + +static inline void write_register_byte(struct net_device *dev, u32 reg, u8 val) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + + iowrite8(val, priv->ioaddr + reg); + IPW_DEBUG_IO("w: 0x%08X =< %02X\n", reg, val); +} + +static inline void read_nic_dword(struct net_device *dev, u32 addr, u32 * val) +{ + write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, + addr & IPW_REG_INDIRECT_ADDR_MASK); + read_register(dev, IPW_REG_INDIRECT_ACCESS_DATA, val); +} + +static inline void write_nic_dword(struct net_device *dev, u32 addr, u32 val) +{ + write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, + addr & IPW_REG_INDIRECT_ADDR_MASK); + write_register(dev, IPW_REG_INDIRECT_ACCESS_DATA, val); +} + +static inline void read_nic_word(struct net_device *dev, u32 addr, u16 * val) +{ + write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, + addr & IPW_REG_INDIRECT_ADDR_MASK); + read_register_word(dev, IPW_REG_INDIRECT_ACCESS_DATA, val); +} + +static inline void write_nic_word(struct net_device *dev, u32 addr, u16 val) +{ + write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, + addr & IPW_REG_INDIRECT_ADDR_MASK); + write_register_word(dev, IPW_REG_INDIRECT_ACCESS_DATA, val); +} + +static inline void read_nic_byte(struct net_device *dev, u32 addr, u8 * val) +{ + write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, + addr & IPW_REG_INDIRECT_ADDR_MASK); + read_register_byte(dev, IPW_REG_INDIRECT_ACCESS_DATA, val); +} + +static inline void write_nic_byte(struct net_device *dev, u32 addr, u8 val) +{ + write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, + addr & IPW_REG_INDIRECT_ADDR_MASK); + write_register_byte(dev, IPW_REG_INDIRECT_ACCESS_DATA, val); +} + +static inline void write_nic_auto_inc_address(struct net_device *dev, u32 addr) +{ + write_register(dev, IPW_REG_AUTOINCREMENT_ADDRESS, + addr & IPW_REG_INDIRECT_ADDR_MASK); +} + +static inline void write_nic_dword_auto_inc(struct net_device *dev, u32 val) +{ + write_register(dev, IPW_REG_AUTOINCREMENT_DATA, val); +} + +static void write_nic_memory(struct net_device *dev, u32 addr, u32 len, + const u8 * buf) +{ + u32 aligned_addr; + u32 aligned_len; + u32 dif_len; + u32 i; + + /* read first nibble byte by byte */ + aligned_addr = addr & (~0x3); + dif_len = addr - aligned_addr; + if (dif_len) { + /* Start reading at aligned_addr + dif_len */ + write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, + aligned_addr); + for (i = dif_len; i < 4; i++, buf++) + write_register_byte(dev, + IPW_REG_INDIRECT_ACCESS_DATA + i, + *buf); + + len -= dif_len; + aligned_addr += 4; + } + + /* read DWs through autoincrement registers */ + write_register(dev, IPW_REG_AUTOINCREMENT_ADDRESS, aligned_addr); + aligned_len = len & (~0x3); + for (i = 0; i < aligned_len; i += 4, buf += 4, aligned_addr += 4) + write_register(dev, IPW_REG_AUTOINCREMENT_DATA, *(u32 *) buf); + + /* copy the last nibble */ + dif_len = len - aligned_len; + write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, aligned_addr); + for (i = 0; i < dif_len; i++, buf++) + write_register_byte(dev, IPW_REG_INDIRECT_ACCESS_DATA + i, + *buf); +} + +static void read_nic_memory(struct net_device *dev, u32 addr, u32 len, + u8 * buf) +{ + u32 aligned_addr; + u32 aligned_len; + u32 dif_len; + u32 i; + + /* read first nibble byte by byte */ + aligned_addr = addr & (~0x3); + dif_len = addr - aligned_addr; + if (dif_len) { + /* Start reading at aligned_addr + dif_len */ + write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, + aligned_addr); + for (i = dif_len; i < 4; i++, buf++) + read_register_byte(dev, + IPW_REG_INDIRECT_ACCESS_DATA + i, + buf); + + len -= dif_len; + aligned_addr += 4; + } + + /* read DWs through autoincrement registers */ + write_register(dev, IPW_REG_AUTOINCREMENT_ADDRESS, aligned_addr); + aligned_len = len & (~0x3); + for (i = 0; i < aligned_len; i += 4, buf += 4, aligned_addr += 4) + read_register(dev, IPW_REG_AUTOINCREMENT_DATA, (u32 *) buf); + + /* copy the last nibble */ + dif_len = len - aligned_len; + write_register(dev, IPW_REG_INDIRECT_ACCESS_ADDRESS, aligned_addr); + for (i = 0; i < dif_len; i++, buf++) + read_register_byte(dev, IPW_REG_INDIRECT_ACCESS_DATA + i, buf); +} + +static bool ipw2100_hw_is_adapter_in_system(struct net_device *dev) +{ + u32 dbg; + + read_register(dev, IPW_REG_DOA_DEBUG_AREA_START, &dbg); + + return dbg == IPW_DATA_DOA_DEBUG_VALUE; +} + +static int ipw2100_get_ordinal(struct ipw2100_priv *priv, u32 ord, + void *val, u32 * len) +{ + struct ipw2100_ordinals *ordinals = &priv->ordinals; + u32 addr; + u32 field_info; + u16 field_len; + u16 field_count; + u32 total_length; + + if (ordinals->table1_addr == 0) { + printk(KERN_WARNING DRV_NAME ": attempt to use fw ordinals " + "before they have been loaded.\n"); + return -EINVAL; + } + + if (IS_ORDINAL_TABLE_ONE(ordinals, ord)) { + if (*len < IPW_ORD_TAB_1_ENTRY_SIZE) { + *len = IPW_ORD_TAB_1_ENTRY_SIZE; + + printk(KERN_WARNING DRV_NAME + ": ordinal buffer length too small, need %zd\n", + IPW_ORD_TAB_1_ENTRY_SIZE); + + return -EINVAL; + } + + read_nic_dword(priv->net_dev, + ordinals->table1_addr + (ord << 2), &addr); + read_nic_dword(priv->net_dev, addr, val); + + *len = IPW_ORD_TAB_1_ENTRY_SIZE; + + return 0; + } + + if (IS_ORDINAL_TABLE_TWO(ordinals, ord)) { + + ord -= IPW_START_ORD_TAB_2; + + /* get the address of statistic */ + read_nic_dword(priv->net_dev, + ordinals->table2_addr + (ord << 3), &addr); + + /* get the second DW of statistics ; + * two 16-bit words - first is length, second is count */ + read_nic_dword(priv->net_dev, + ordinals->table2_addr + (ord << 3) + sizeof(u32), + &field_info); + + /* get each entry length */ + field_len = *((u16 *) & field_info); + + /* get number of entries */ + field_count = *(((u16 *) & field_info) + 1); + + /* abort if no enough memory */ + total_length = field_len * field_count; + if (total_length > *len) { + *len = total_length; + return -EINVAL; + } + + *len = total_length; + if (!total_length) + return 0; + + /* read the ordinal data from the SRAM */ + read_nic_memory(priv->net_dev, addr, total_length, val); + + return 0; + } + + printk(KERN_WARNING DRV_NAME ": ordinal %d neither in table 1 nor " + "in table 2\n", ord); + + return -EINVAL; +} + +static int ipw2100_set_ordinal(struct ipw2100_priv *priv, u32 ord, u32 * val, + u32 * len) +{ + struct ipw2100_ordinals *ordinals = &priv->ordinals; + u32 addr; + + if (IS_ORDINAL_TABLE_ONE(ordinals, ord)) { + if (*len != IPW_ORD_TAB_1_ENTRY_SIZE) { + *len = IPW_ORD_TAB_1_ENTRY_SIZE; + IPW_DEBUG_INFO("wrong size\n"); + return -EINVAL; + } + + read_nic_dword(priv->net_dev, + ordinals->table1_addr + (ord << 2), &addr); + + write_nic_dword(priv->net_dev, addr, *val); + + *len = IPW_ORD_TAB_1_ENTRY_SIZE; + + return 0; + } + + IPW_DEBUG_INFO("wrong table\n"); + if (IS_ORDINAL_TABLE_TWO(ordinals, ord)) + return -EINVAL; + + return -EINVAL; +} + +static char *snprint_line(char *buf, size_t count, + const u8 * data, u32 len, u32 ofs) +{ + int out, i, j, l; + char c; + + out = snprintf(buf, count, "%08X", ofs); + + for (l = 0, i = 0; i < 2; i++) { + out += snprintf(buf + out, count - out, " "); + for (j = 0; j < 8 && l < len; j++, l++) + out += snprintf(buf + out, count - out, "%02X ", + data[(i * 8 + j)]); + for (; j < 8; j++) + out += snprintf(buf + out, count - out, " "); + } + + out += snprintf(buf + out, count - out, " "); + for (l = 0, i = 0; i < 2; i++) { + out += snprintf(buf + out, count - out, " "); + for (j = 0; j < 8 && l < len; j++, l++) { + c = data[(i * 8 + j)]; + if (!isascii(c) || !isprint(c)) + c = '.'; + + out += snprintf(buf + out, count - out, "%c", c); + } + + for (; j < 8; j++) + out += snprintf(buf + out, count - out, " "); + } + + return buf; +} + +static void printk_buf(int level, const u8 * data, u32 len) +{ + char line[81]; + u32 ofs = 0; + if (!(ipw2100_debug_level & level)) + return; + + while (len) { + printk(KERN_DEBUG "%s\n", + snprint_line(line, sizeof(line), &data[ofs], + min(len, 16U), ofs)); + ofs += 16; + len -= min(len, 16U); + } +} + +#define MAX_RESET_BACKOFF 10 + +static void schedule_reset(struct ipw2100_priv *priv) +{ + unsigned long now = get_seconds(); + + /* If we haven't received a reset request within the backoff period, + * then we can reset the backoff interval so this reset occurs + * immediately */ + if (priv->reset_backoff && + (now - priv->last_reset > priv->reset_backoff)) + priv->reset_backoff = 0; + + priv->last_reset = get_seconds(); + + if (!(priv->status & STATUS_RESET_PENDING)) { + IPW_DEBUG_INFO("%s: Scheduling firmware restart (%ds).\n", + priv->net_dev->name, priv->reset_backoff); + netif_carrier_off(priv->net_dev); + netif_stop_queue(priv->net_dev); + priv->status |= STATUS_RESET_PENDING; + if (priv->reset_backoff) + schedule_delayed_work(&priv->reset_work, + priv->reset_backoff * HZ); + else + schedule_delayed_work(&priv->reset_work, 0); + + if (priv->reset_backoff < MAX_RESET_BACKOFF) + priv->reset_backoff++; + + wake_up_interruptible(&priv->wait_command_queue); + } else + IPW_DEBUG_INFO("%s: Firmware restart already in progress.\n", + priv->net_dev->name); + +} + +#define HOST_COMPLETE_TIMEOUT (2 * HZ) +static int ipw2100_hw_send_command(struct ipw2100_priv *priv, + struct host_command *cmd) +{ + struct list_head *element; + struct ipw2100_tx_packet *packet; + unsigned long flags; + int err = 0; + + IPW_DEBUG_HC("Sending %s command (#%d), %d bytes\n", + command_types[cmd->host_command], cmd->host_command, + cmd->host_command_length); + printk_buf(IPW_DL_HC, (u8 *) cmd->host_command_parameters, + cmd->host_command_length); + + spin_lock_irqsave(&priv->low_lock, flags); + + if (priv->fatal_error) { + IPW_DEBUG_INFO + ("Attempt to send command while hardware in fatal error condition.\n"); + err = -EIO; + goto fail_unlock; + } + + if (!(priv->status & STATUS_RUNNING)) { + IPW_DEBUG_INFO + ("Attempt to send command while hardware is not running.\n"); + err = -EIO; + goto fail_unlock; + } + + if (priv->status & STATUS_CMD_ACTIVE) { + IPW_DEBUG_INFO + ("Attempt to send command while another command is pending.\n"); + err = -EBUSY; + goto fail_unlock; + } + + if (list_empty(&priv->msg_free_list)) { + IPW_DEBUG_INFO("no available msg buffers\n"); + goto fail_unlock; + } + + priv->status |= STATUS_CMD_ACTIVE; + priv->messages_sent++; + + element = priv->msg_free_list.next; + + packet = list_entry(element, struct ipw2100_tx_packet, list); + packet->jiffy_start = jiffies; + + /* initialize the firmware command packet */ + packet->info.c_struct.cmd->host_command_reg = cmd->host_command; + packet->info.c_struct.cmd->host_command_reg1 = cmd->host_command1; + packet->info.c_struct.cmd->host_command_len_reg = + cmd->host_command_length; + packet->info.c_struct.cmd->sequence = cmd->host_command_sequence; + + memcpy(packet->info.c_struct.cmd->host_command_params_reg, + cmd->host_command_parameters, + sizeof(packet->info.c_struct.cmd->host_command_params_reg)); + + list_del(element); + DEC_STAT(&priv->msg_free_stat); + + list_add_tail(element, &priv->msg_pend_list); + INC_STAT(&priv->msg_pend_stat); + + ipw2100_tx_send_commands(priv); + ipw2100_tx_send_data(priv); + + spin_unlock_irqrestore(&priv->low_lock, flags); + + /* + * We must wait for this command to complete before another + * command can be sent... but if we wait more than 3 seconds + * then there is a problem. + */ + + err = + wait_event_interruptible_timeout(priv->wait_command_queue, + !(priv-> + status & STATUS_CMD_ACTIVE), + HOST_COMPLETE_TIMEOUT); + + if (err == 0) { + IPW_DEBUG_INFO("Command completion failed out after %dms.\n", + 1000 * (HOST_COMPLETE_TIMEOUT / HZ)); + priv->fatal_error = IPW2100_ERR_MSG_TIMEOUT; + priv->status &= ~STATUS_CMD_ACTIVE; + schedule_reset(priv); + return -EIO; + } + + if (priv->fatal_error) { + printk(KERN_WARNING DRV_NAME ": %s: firmware fatal error\n", + priv->net_dev->name); + return -EIO; + } + + /* !!!!! HACK TEST !!!!! + * When lots of debug trace statements are enabled, the driver + * doesn't seem to have as many firmware restart cycles... + * + * As a test, we're sticking in a 1/100s delay here */ + schedule_timeout_uninterruptible(msecs_to_jiffies(10)); + + return 0; + + fail_unlock: + spin_unlock_irqrestore(&priv->low_lock, flags); + + return err; +} + +/* + * Verify the values and data access of the hardware + * No locks needed or used. No functions called. + */ +static int ipw2100_verify(struct ipw2100_priv *priv) +{ + u32 data1, data2; + u32 address; + + u32 val1 = 0x76543210; + u32 val2 = 0xFEDCBA98; + + /* Domain 0 check - all values should be DOA_DEBUG */ + for (address = IPW_REG_DOA_DEBUG_AREA_START; + address < IPW_REG_DOA_DEBUG_AREA_END; address += sizeof(u32)) { + read_register(priv->net_dev, address, &data1); + if (data1 != IPW_DATA_DOA_DEBUG_VALUE) + return -EIO; + } + + /* Domain 1 check - use arbitrary read/write compare */ + for (address = 0; address < 5; address++) { + /* The memory area is not used now */ + write_register(priv->net_dev, IPW_REG_DOMAIN_1_OFFSET + 0x32, + val1); + write_register(priv->net_dev, IPW_REG_DOMAIN_1_OFFSET + 0x36, + val2); + read_register(priv->net_dev, IPW_REG_DOMAIN_1_OFFSET + 0x32, + &data1); + read_register(priv->net_dev, IPW_REG_DOMAIN_1_OFFSET + 0x36, + &data2); + if (val1 == data1 && val2 == data2) + return 0; + } + + return -EIO; +} + +/* + * + * Loop until the CARD_DISABLED bit is the same value as the + * supplied parameter + * + * TODO: See if it would be more efficient to do a wait/wake + * cycle and have the completion event trigger the wakeup + * + */ +#define IPW_CARD_DISABLE_COMPLETE_WAIT 100 // 100 milli +static int ipw2100_wait_for_card_state(struct ipw2100_priv *priv, int state) +{ + int i; + u32 card_state; + u32 len = sizeof(card_state); + int err; + + for (i = 0; i <= IPW_CARD_DISABLE_COMPLETE_WAIT * 1000; i += 50) { + err = ipw2100_get_ordinal(priv, IPW_ORD_CARD_DISABLED, + &card_state, &len); + if (err) { + IPW_DEBUG_INFO("Query of CARD_DISABLED ordinal " + "failed.\n"); + return 0; + } + + /* We'll break out if either the HW state says it is + * in the state we want, or if HOST_COMPLETE command + * finishes */ + if ((card_state == state) || + ((priv->status & STATUS_ENABLED) ? + IPW_HW_STATE_ENABLED : IPW_HW_STATE_DISABLED) == state) { + if (state == IPW_HW_STATE_ENABLED) + priv->status |= STATUS_ENABLED; + else + priv->status &= ~STATUS_ENABLED; + + return 0; + } + + udelay(50); + } + + IPW_DEBUG_INFO("ipw2100_wait_for_card_state to %s state timed out\n", + state ? "DISABLED" : "ENABLED"); + return -EIO; +} + +/********************************************************************* + Procedure : sw_reset_and_clock + Purpose : Asserts s/w reset, asserts clock initialization + and waits for clock stabilization + ********************************************************************/ +static int sw_reset_and_clock(struct ipw2100_priv *priv) +{ + int i; + u32 r; + + // assert s/w reset + write_register(priv->net_dev, IPW_REG_RESET_REG, + IPW_AUX_HOST_RESET_REG_SW_RESET); + + // wait for clock stabilization + for (i = 0; i < 1000; i++) { + udelay(IPW_WAIT_RESET_ARC_COMPLETE_DELAY); + + // check clock ready bit + read_register(priv->net_dev, IPW_REG_RESET_REG, &r); + if (r & IPW_AUX_HOST_RESET_REG_PRINCETON_RESET) + break; + } + + if (i == 1000) + return -EIO; // TODO: better error value + + /* set "initialization complete" bit to move adapter to + * D0 state */ + write_register(priv->net_dev, IPW_REG_GP_CNTRL, + IPW_AUX_HOST_GP_CNTRL_BIT_INIT_DONE); + + /* wait for clock stabilization */ + for (i = 0; i < 10000; i++) { + udelay(IPW_WAIT_CLOCK_STABILIZATION_DELAY * 4); + + /* check clock ready bit */ + read_register(priv->net_dev, IPW_REG_GP_CNTRL, &r); + if (r & IPW_AUX_HOST_GP_CNTRL_BIT_CLOCK_READY) + break; + } + + if (i == 10000) + return -EIO; /* TODO: better error value */ + + /* set D0 standby bit */ + read_register(priv->net_dev, IPW_REG_GP_CNTRL, &r); + write_register(priv->net_dev, IPW_REG_GP_CNTRL, + r | IPW_AUX_HOST_GP_CNTRL_BIT_HOST_ALLOWS_STANDBY); + + return 0; +} + +/********************************************************************* + Procedure : ipw2100_download_firmware + Purpose : Initiaze adapter after power on. + The sequence is: + 1. assert s/w reset first! + 2. awake clocks & wait for clock stabilization + 3. hold ARC (don't ask me why...) + 4. load Dino ucode and reset/clock init again + 5. zero-out shared mem + 6. download f/w + *******************************************************************/ +static int ipw2100_download_firmware(struct ipw2100_priv *priv) +{ + u32 address; + int err; + +#ifndef CONFIG_PM + /* Fetch the firmware and microcode */ + struct ipw2100_fw ipw2100_firmware; +#endif + + if (priv->fatal_error) { + IPW_DEBUG_ERROR("%s: ipw2100_download_firmware called after " + "fatal error %d. Interface must be brought down.\n", + priv->net_dev->name, priv->fatal_error); + return -EINVAL; + } +#ifdef CONFIG_PM + if (!ipw2100_firmware.version) { + err = ipw2100_get_firmware(priv, &ipw2100_firmware); + if (err) { + IPW_DEBUG_ERROR("%s: ipw2100_get_firmware failed: %d\n", + priv->net_dev->name, err); + priv->fatal_error = IPW2100_ERR_FW_LOAD; + goto fail; + } + } +#else + err = ipw2100_get_firmware(priv, &ipw2100_firmware); + if (err) { + IPW_DEBUG_ERROR("%s: ipw2100_get_firmware failed: %d\n", + priv->net_dev->name, err); + priv->fatal_error = IPW2100_ERR_FW_LOAD; + goto fail; + } +#endif + priv->firmware_version = ipw2100_firmware.version; + + /* s/w reset and clock stabilization */ + err = sw_reset_and_clock(priv); + if (err) { + IPW_DEBUG_ERROR("%s: sw_reset_and_clock failed: %d\n", + priv->net_dev->name, err); + goto fail; + } + + err = ipw2100_verify(priv); + if (err) { + IPW_DEBUG_ERROR("%s: ipw2100_verify failed: %d\n", + priv->net_dev->name, err); + goto fail; + } + + /* Hold ARC */ + write_nic_dword(priv->net_dev, + IPW_INTERNAL_REGISTER_HALT_AND_RESET, 0x80000000); + + /* allow ARC to run */ + write_register(priv->net_dev, IPW_REG_RESET_REG, 0); + + /* load microcode */ + err = ipw2100_ucode_download(priv, &ipw2100_firmware); + if (err) { + printk(KERN_ERR DRV_NAME ": %s: Error loading microcode: %d\n", + priv->net_dev->name, err); + goto fail; + } + + /* release ARC */ + write_nic_dword(priv->net_dev, + IPW_INTERNAL_REGISTER_HALT_AND_RESET, 0x00000000); + + /* s/w reset and clock stabilization (again!!!) */ + err = sw_reset_and_clock(priv); + if (err) { + printk(KERN_ERR DRV_NAME + ": %s: sw_reset_and_clock failed: %d\n", + priv->net_dev->name, err); + goto fail; + } + + /* load f/w */ + err = ipw2100_fw_download(priv, &ipw2100_firmware); + if (err) { + IPW_DEBUG_ERROR("%s: Error loading firmware: %d\n", + priv->net_dev->name, err); + goto fail; + } +#ifndef CONFIG_PM + /* + * When the .resume method of the driver is called, the other + * part of the system, i.e. the ide driver could still stay in + * the suspend stage. This prevents us from loading the firmware + * from the disk. --YZ + */ + + /* free any storage allocated for firmware image */ + ipw2100_release_firmware(priv, &ipw2100_firmware); +#endif + + /* zero out Domain 1 area indirectly (Si requirement) */ + for (address = IPW_HOST_FW_SHARED_AREA0; + address < IPW_HOST_FW_SHARED_AREA0_END; address += 4) + write_nic_dword(priv->net_dev, address, 0); + for (address = IPW_HOST_FW_SHARED_AREA1; + address < IPW_HOST_FW_SHARED_AREA1_END; address += 4) + write_nic_dword(priv->net_dev, address, 0); + for (address = IPW_HOST_FW_SHARED_AREA2; + address < IPW_HOST_FW_SHARED_AREA2_END; address += 4) + write_nic_dword(priv->net_dev, address, 0); + for (address = IPW_HOST_FW_SHARED_AREA3; + address < IPW_HOST_FW_SHARED_AREA3_END; address += 4) + write_nic_dword(priv->net_dev, address, 0); + for (address = IPW_HOST_FW_INTERRUPT_AREA; + address < IPW_HOST_FW_INTERRUPT_AREA_END; address += 4) + write_nic_dword(priv->net_dev, address, 0); + + return 0; + + fail: + ipw2100_release_firmware(priv, &ipw2100_firmware); + return err; +} + +static inline void ipw2100_enable_interrupts(struct ipw2100_priv *priv) +{ + if (priv->status & STATUS_INT_ENABLED) + return; + priv->status |= STATUS_INT_ENABLED; + write_register(priv->net_dev, IPW_REG_INTA_MASK, IPW_INTERRUPT_MASK); +} + +static inline void ipw2100_disable_interrupts(struct ipw2100_priv *priv) +{ + if (!(priv->status & STATUS_INT_ENABLED)) + return; + priv->status &= ~STATUS_INT_ENABLED; + write_register(priv->net_dev, IPW_REG_INTA_MASK, 0x0); +} + +static void ipw2100_initialize_ordinals(struct ipw2100_priv *priv) +{ + struct ipw2100_ordinals *ord = &priv->ordinals; + + IPW_DEBUG_INFO("enter\n"); + + read_register(priv->net_dev, IPW_MEM_HOST_SHARED_ORDINALS_TABLE_1, + &ord->table1_addr); + + read_register(priv->net_dev, IPW_MEM_HOST_SHARED_ORDINALS_TABLE_2, + &ord->table2_addr); + + read_nic_dword(priv->net_dev, ord->table1_addr, &ord->table1_size); + read_nic_dword(priv->net_dev, ord->table2_addr, &ord->table2_size); + + ord->table2_size &= 0x0000FFFF; + + IPW_DEBUG_INFO("table 1 size: %d\n", ord->table1_size); + IPW_DEBUG_INFO("table 2 size: %d\n", ord->table2_size); + IPW_DEBUG_INFO("exit\n"); +} + +static inline void ipw2100_hw_set_gpio(struct ipw2100_priv *priv) +{ + u32 reg = 0; + /* + * Set GPIO 3 writable by FW; GPIO 1 writable + * by driver and enable clock + */ + reg = (IPW_BIT_GPIO_GPIO3_MASK | IPW_BIT_GPIO_GPIO1_ENABLE | + IPW_BIT_GPIO_LED_OFF); + write_register(priv->net_dev, IPW_REG_GPIO, reg); +} + +static int rf_kill_active(struct ipw2100_priv *priv) +{ +#define MAX_RF_KILL_CHECKS 5 +#define RF_KILL_CHECK_DELAY 40 + + unsigned short value = 0; + u32 reg = 0; + int i; + + if (!(priv->hw_features & HW_FEATURE_RFKILL)) { + wiphy_rfkill_set_hw_state(priv->ieee->wdev.wiphy, false); + priv->status &= ~STATUS_RF_KILL_HW; + return 0; + } + + for (i = 0; i < MAX_RF_KILL_CHECKS; i++) { + udelay(RF_KILL_CHECK_DELAY); + read_register(priv->net_dev, IPW_REG_GPIO, ®); + value = (value << 1) | ((reg & IPW_BIT_GPIO_RF_KILL) ? 0 : 1); + } + + if (value == 0) { + wiphy_rfkill_set_hw_state(priv->ieee->wdev.wiphy, true); + priv->status |= STATUS_RF_KILL_HW; + } else { + wiphy_rfkill_set_hw_state(priv->ieee->wdev.wiphy, false); + priv->status &= ~STATUS_RF_KILL_HW; + } + + return (value == 0); +} + +static int ipw2100_get_hw_features(struct ipw2100_priv *priv) +{ + u32 addr, len; + u32 val; + + /* + * EEPROM_SRAM_DB_START_ADDRESS using ordinal in ordinal table 1 + */ + len = sizeof(addr); + if (ipw2100_get_ordinal + (priv, IPW_ORD_EEPROM_SRAM_DB_BLOCK_START_ADDRESS, &addr, &len)) { + IPW_DEBUG_INFO("failed querying ordinals at line %d\n", + __LINE__); + return -EIO; + } + + IPW_DEBUG_INFO("EEPROM address: %08X\n", addr); + + /* + * EEPROM version is the byte at offset 0xfd in firmware + * We read 4 bytes, then shift out the byte we actually want */ + read_nic_dword(priv->net_dev, addr + 0xFC, &val); + priv->eeprom_version = (val >> 24) & 0xFF; + IPW_DEBUG_INFO("EEPROM version: %d\n", priv->eeprom_version); + + /* + * HW RF Kill enable is bit 0 in byte at offset 0x21 in firmware + * + * notice that the EEPROM bit is reverse polarity, i.e. + * bit = 0 signifies HW RF kill switch is supported + * bit = 1 signifies HW RF kill switch is NOT supported + */ + read_nic_dword(priv->net_dev, addr + 0x20, &val); + if (!((val >> 24) & 0x01)) + priv->hw_features |= HW_FEATURE_RFKILL; + + IPW_DEBUG_INFO("HW RF Kill: %ssupported.\n", + (priv->hw_features & HW_FEATURE_RFKILL) ? "" : "not "); + + return 0; +} + +/* + * Start firmware execution after power on and intialization + * The sequence is: + * 1. Release ARC + * 2. Wait for f/w initialization completes; + */ +static int ipw2100_start_adapter(struct ipw2100_priv *priv) +{ + int i; + u32 inta, inta_mask, gpio; + + IPW_DEBUG_INFO("enter\n"); + + if (priv->status & STATUS_RUNNING) + return 0; + + /* + * Initialize the hw - drive adapter to DO state by setting + * init_done bit. Wait for clk_ready bit and Download + * fw & dino ucode + */ + if (ipw2100_download_firmware(priv)) { + printk(KERN_ERR DRV_NAME + ": %s: Failed to power on the adapter.\n", + priv->net_dev->name); + return -EIO; + } + + /* Clear the Tx, Rx and Msg queues and the r/w indexes + * in the firmware RBD and TBD ring queue */ + ipw2100_queues_initialize(priv); + + ipw2100_hw_set_gpio(priv); + + /* TODO -- Look at disabling interrupts here to make sure none + * get fired during FW initialization */ + + /* Release ARC - clear reset bit */ + write_register(priv->net_dev, IPW_REG_RESET_REG, 0); + + /* wait for f/w intialization complete */ + IPW_DEBUG_FW("Waiting for f/w initialization to complete...\n"); + i = 5000; + do { + schedule_timeout_uninterruptible(msecs_to_jiffies(40)); + /* Todo... wait for sync command ... */ + + read_register(priv->net_dev, IPW_REG_INTA, &inta); + + /* check "init done" bit */ + if (inta & IPW2100_INTA_FW_INIT_DONE) { + /* reset "init done" bit */ + write_register(priv->net_dev, IPW_REG_INTA, + IPW2100_INTA_FW_INIT_DONE); + break; + } + + /* check error conditions : we check these after the firmware + * check so that if there is an error, the interrupt handler + * will see it and the adapter will be reset */ + if (inta & + (IPW2100_INTA_FATAL_ERROR | IPW2100_INTA_PARITY_ERROR)) { + /* clear error conditions */ + write_register(priv->net_dev, IPW_REG_INTA, + IPW2100_INTA_FATAL_ERROR | + IPW2100_INTA_PARITY_ERROR); + } + } while (--i); + + /* Clear out any pending INTAs since we aren't supposed to have + * interrupts enabled at this point... */ + read_register(priv->net_dev, IPW_REG_INTA, &inta); + read_register(priv->net_dev, IPW_REG_INTA_MASK, &inta_mask); + inta &= IPW_INTERRUPT_MASK; + /* Clear out any pending interrupts */ + if (inta & inta_mask) + write_register(priv->net_dev, IPW_REG_INTA, inta); + + IPW_DEBUG_FW("f/w initialization complete: %s\n", + i ? "SUCCESS" : "FAILED"); + + if (!i) { + printk(KERN_WARNING DRV_NAME + ": %s: Firmware did not initialize.\n", + priv->net_dev->name); + return -EIO; + } + + /* allow firmware to write to GPIO1 & GPIO3 */ + read_register(priv->net_dev, IPW_REG_GPIO, &gpio); + + gpio |= (IPW_BIT_GPIO_GPIO1_MASK | IPW_BIT_GPIO_GPIO3_MASK); + + write_register(priv->net_dev, IPW_REG_GPIO, gpio); + + /* Ready to receive commands */ + priv->status |= STATUS_RUNNING; + + /* The adapter has been reset; we are not associated */ + priv->status &= ~(STATUS_ASSOCIATING | STATUS_ASSOCIATED); + + IPW_DEBUG_INFO("exit\n"); + + return 0; +} + +static inline void ipw2100_reset_fatalerror(struct ipw2100_priv *priv) +{ + if (!priv->fatal_error) + return; + + priv->fatal_errors[priv->fatal_index++] = priv->fatal_error; + priv->fatal_index %= IPW2100_ERROR_QUEUE; + priv->fatal_error = 0; +} + +/* NOTE: Our interrupt is disabled when this method is called */ +static int ipw2100_power_cycle_adapter(struct ipw2100_priv *priv) +{ + u32 reg; + int i; + + IPW_DEBUG_INFO("Power cycling the hardware.\n"); + + ipw2100_hw_set_gpio(priv); + + /* Step 1. Stop Master Assert */ + write_register(priv->net_dev, IPW_REG_RESET_REG, + IPW_AUX_HOST_RESET_REG_STOP_MASTER); + + /* Step 2. Wait for stop Master Assert + * (not more than 50us, otherwise ret error */ + i = 5; + do { + udelay(IPW_WAIT_RESET_MASTER_ASSERT_COMPLETE_DELAY); + read_register(priv->net_dev, IPW_REG_RESET_REG, ®); + + if (reg & IPW_AUX_HOST_RESET_REG_MASTER_DISABLED) + break; + } while (--i); + + priv->status &= ~STATUS_RESET_PENDING; + + if (!i) { + IPW_DEBUG_INFO + ("exit - waited too long for master assert stop\n"); + return -EIO; + } + + write_register(priv->net_dev, IPW_REG_RESET_REG, + IPW_AUX_HOST_RESET_REG_SW_RESET); + + /* Reset any fatal_error conditions */ + ipw2100_reset_fatalerror(priv); + + /* At this point, the adapter is now stopped and disabled */ + priv->status &= ~(STATUS_RUNNING | STATUS_ASSOCIATING | + STATUS_ASSOCIATED | STATUS_ENABLED); + + return 0; +} + +/* + * Send the CARD_DISABLE_PHY_OFF command to the card to disable it + * + * After disabling, if the card was associated, a STATUS_ASSN_LOST will be sent. + * + * STATUS_CARD_DISABLE_NOTIFICATION will be sent regardless of + * if STATUS_ASSN_LOST is sent. + */ +static int ipw2100_hw_phy_off(struct ipw2100_priv *priv) +{ + +#define HW_PHY_OFF_LOOP_DELAY (HZ / 5000) + + struct host_command cmd = { + .host_command = CARD_DISABLE_PHY_OFF, + .host_command_sequence = 0, + .host_command_length = 0, + }; + int err, i; + u32 val1, val2; + + IPW_DEBUG_HC("CARD_DISABLE_PHY_OFF\n"); + + /* Turn off the radio */ + err = ipw2100_hw_send_command(priv, &cmd); + if (err) + return err; + + for (i = 0; i < 2500; i++) { + read_nic_dword(priv->net_dev, IPW2100_CONTROL_REG, &val1); + read_nic_dword(priv->net_dev, IPW2100_COMMAND, &val2); + + if ((val1 & IPW2100_CONTROL_PHY_OFF) && + (val2 & IPW2100_COMMAND_PHY_OFF)) + return 0; + + schedule_timeout_uninterruptible(HW_PHY_OFF_LOOP_DELAY); + } + + return -EIO; +} + +static int ipw2100_enable_adapter(struct ipw2100_priv *priv) +{ + struct host_command cmd = { + .host_command = HOST_COMPLETE, + .host_command_sequence = 0, + .host_command_length = 0 + }; + int err = 0; + + IPW_DEBUG_HC("HOST_COMPLETE\n"); + + if (priv->status & STATUS_ENABLED) + return 0; + + mutex_lock(&priv->adapter_mutex); + + if (rf_kill_active(priv)) { + IPW_DEBUG_HC("Command aborted due to RF kill active.\n"); + goto fail_up; + } + + err = ipw2100_hw_send_command(priv, &cmd); + if (err) { + IPW_DEBUG_INFO("Failed to send HOST_COMPLETE command\n"); + goto fail_up; + } + + err = ipw2100_wait_for_card_state(priv, IPW_HW_STATE_ENABLED); + if (err) { + IPW_DEBUG_INFO("%s: card not responding to init command.\n", + priv->net_dev->name); + goto fail_up; + } + + if (priv->stop_hang_check) { + priv->stop_hang_check = 0; + schedule_delayed_work(&priv->hang_check, HZ / 2); + } + + fail_up: + mutex_unlock(&priv->adapter_mutex); + return err; +} + +static int ipw2100_hw_stop_adapter(struct ipw2100_priv *priv) +{ +#define HW_POWER_DOWN_DELAY (msecs_to_jiffies(100)) + + struct host_command cmd = { + .host_command = HOST_PRE_POWER_DOWN, + .host_command_sequence = 0, + .host_command_length = 0, + }; + int err, i; + u32 reg; + + if (!(priv->status & STATUS_RUNNING)) + return 0; + + priv->status |= STATUS_STOPPING; + + /* We can only shut down the card if the firmware is operational. So, + * if we haven't reset since a fatal_error, then we can not send the + * shutdown commands. */ + if (!priv->fatal_error) { + /* First, make sure the adapter is enabled so that the PHY_OFF + * command can shut it down */ + ipw2100_enable_adapter(priv); + + err = ipw2100_hw_phy_off(priv); + if (err) + printk(KERN_WARNING DRV_NAME + ": Error disabling radio %d\n", err); + + /* + * If in D0-standby mode going directly to D3 may cause a + * PCI bus violation. Therefore we must change out of the D0 + * state. + * + * Sending the PREPARE_FOR_POWER_DOWN will restrict the + * hardware from going into standby mode and will transition + * out of D0-standby if it is already in that state. + * + * STATUS_PREPARE_POWER_DOWN_COMPLETE will be sent by the + * driver upon completion. Once received, the driver can + * proceed to the D3 state. + * + * Prepare for power down command to fw. This command would + * take HW out of D0-standby and prepare it for D3 state. + * + * Currently FW does not support event notification for this + * event. Therefore, skip waiting for it. Just wait a fixed + * 100ms + */ + IPW_DEBUG_HC("HOST_PRE_POWER_DOWN\n"); + + err = ipw2100_hw_send_command(priv, &cmd); + if (err) + printk(KERN_WARNING DRV_NAME ": " + "%s: Power down command failed: Error %d\n", + priv->net_dev->name, err); + else + schedule_timeout_uninterruptible(HW_POWER_DOWN_DELAY); + } + + priv->status &= ~STATUS_ENABLED; + + /* + * Set GPIO 3 writable by FW; GPIO 1 writable + * by driver and enable clock + */ + ipw2100_hw_set_gpio(priv); + + /* + * Power down adapter. Sequence: + * 1. Stop master assert (RESET_REG[9]=1) + * 2. Wait for stop master (RESET_REG[8]==1) + * 3. S/w reset assert (RESET_REG[7] = 1) + */ + + /* Stop master assert */ + write_register(priv->net_dev, IPW_REG_RESET_REG, + IPW_AUX_HOST_RESET_REG_STOP_MASTER); + + /* wait stop master not more than 50 usec. + * Otherwise return error. */ + for (i = 5; i > 0; i--) { + udelay(10); + + /* Check master stop bit */ + read_register(priv->net_dev, IPW_REG_RESET_REG, ®); + + if (reg & IPW_AUX_HOST_RESET_REG_MASTER_DISABLED) + break; + } + + if (i == 0) + printk(KERN_WARNING DRV_NAME + ": %s: Could now power down adapter.\n", + priv->net_dev->name); + + /* assert s/w reset */ + write_register(priv->net_dev, IPW_REG_RESET_REG, + IPW_AUX_HOST_RESET_REG_SW_RESET); + + priv->status &= ~(STATUS_RUNNING | STATUS_STOPPING); + + return 0; +} + +static int ipw2100_disable_adapter(struct ipw2100_priv *priv) +{ + struct host_command cmd = { + .host_command = CARD_DISABLE, + .host_command_sequence = 0, + .host_command_length = 0 + }; + int err = 0; + + IPW_DEBUG_HC("CARD_DISABLE\n"); + + if (!(priv->status & STATUS_ENABLED)) + return 0; + + /* Make sure we clear the associated state */ + priv->status &= ~(STATUS_ASSOCIATED | STATUS_ASSOCIATING); + + if (!priv->stop_hang_check) { + priv->stop_hang_check = 1; + cancel_delayed_work(&priv->hang_check); + } + + mutex_lock(&priv->adapter_mutex); + + err = ipw2100_hw_send_command(priv, &cmd); + if (err) { + printk(KERN_WARNING DRV_NAME + ": exit - failed to send CARD_DISABLE command\n"); + goto fail_up; + } + + err = ipw2100_wait_for_card_state(priv, IPW_HW_STATE_DISABLED); + if (err) { + printk(KERN_WARNING DRV_NAME + ": exit - card failed to change to DISABLED\n"); + goto fail_up; + } + + IPW_DEBUG_INFO("TODO: implement scan state machine\n"); + + fail_up: + mutex_unlock(&priv->adapter_mutex); + return err; +} + +static int ipw2100_set_scan_options(struct ipw2100_priv *priv) +{ + struct host_command cmd = { + .host_command = SET_SCAN_OPTIONS, + .host_command_sequence = 0, + .host_command_length = 8 + }; + int err; + + IPW_DEBUG_INFO("enter\n"); + + IPW_DEBUG_SCAN("setting scan options\n"); + + cmd.host_command_parameters[0] = 0; + + if (!(priv->config & CFG_ASSOCIATE)) + cmd.host_command_parameters[0] |= IPW_SCAN_NOASSOCIATE; + if ((priv->ieee->sec.flags & SEC_ENABLED) && priv->ieee->sec.enabled) + cmd.host_command_parameters[0] |= IPW_SCAN_MIXED_CELL; + if (priv->config & CFG_PASSIVE_SCAN) + cmd.host_command_parameters[0] |= IPW_SCAN_PASSIVE; + + cmd.host_command_parameters[1] = priv->channel_mask; + + err = ipw2100_hw_send_command(priv, &cmd); + + IPW_DEBUG_HC("SET_SCAN_OPTIONS 0x%04X\n", + cmd.host_command_parameters[0]); + + return err; +} + +static int ipw2100_start_scan(struct ipw2100_priv *priv) +{ + struct host_command cmd = { + .host_command = BROADCAST_SCAN, + .host_command_sequence = 0, + .host_command_length = 4 + }; + int err; + + IPW_DEBUG_HC("START_SCAN\n"); + + cmd.host_command_parameters[0] = 0; + + /* No scanning if in monitor mode */ + if (priv->ieee->iw_mode == IW_MODE_MONITOR) + return 1; + + if (priv->status & STATUS_SCANNING) { + IPW_DEBUG_SCAN("Scan requested while already in scan...\n"); + return 0; + } + + IPW_DEBUG_INFO("enter\n"); + + /* Not clearing here; doing so makes iwlist always return nothing... + * + * We should modify the table logic to use aging tables vs. clearing + * the table on each scan start. + */ + IPW_DEBUG_SCAN("starting scan\n"); + + priv->status |= STATUS_SCANNING; + err = ipw2100_hw_send_command(priv, &cmd); + if (err) + priv->status &= ~STATUS_SCANNING; + + IPW_DEBUG_INFO("exit\n"); + + return err; +} + +static const struct libipw_geo ipw_geos[] = { + { /* Restricted */ + "---", + .bg_channels = 14, + .bg = {{2412, 1}, {2417, 2}, {2422, 3}, + {2427, 4}, {2432, 5}, {2437, 6}, + {2442, 7}, {2447, 8}, {2452, 9}, + {2457, 10}, {2462, 11}, {2467, 12}, + {2472, 13}, {2484, 14}}, + }, +}; + +static int ipw2100_up(struct ipw2100_priv *priv, int deferred) +{ + unsigned long flags; + int rc = 0; + u32 lock; + u32 ord_len = sizeof(lock); + + /* Age scan list entries found before suspend */ + if (priv->suspend_time) { + libipw_networks_age(priv->ieee, priv->suspend_time); + priv->suspend_time = 0; + } + + /* Quiet if manually disabled. */ + if (priv->status & STATUS_RF_KILL_SW) { + IPW_DEBUG_INFO("%s: Radio is disabled by Manual Disable " + "switch\n", priv->net_dev->name); + return 0; + } + + /* the ipw2100 hardware really doesn't want power management delays + * longer than 175usec + */ + pm_qos_update_request(&ipw2100_pm_qos_req, 175); + + /* If the interrupt is enabled, turn it off... */ + spin_lock_irqsave(&priv->low_lock, flags); + ipw2100_disable_interrupts(priv); + + /* Reset any fatal_error conditions */ + ipw2100_reset_fatalerror(priv); + spin_unlock_irqrestore(&priv->low_lock, flags); + + if (priv->status & STATUS_POWERED || + (priv->status & STATUS_RESET_PENDING)) { + /* Power cycle the card ... */ + if (ipw2100_power_cycle_adapter(priv)) { + printk(KERN_WARNING DRV_NAME + ": %s: Could not cycle adapter.\n", + priv->net_dev->name); + rc = 1; + goto exit; + } + } else + priv->status |= STATUS_POWERED; + + /* Load the firmware, start the clocks, etc. */ + if (ipw2100_start_adapter(priv)) { + printk(KERN_ERR DRV_NAME + ": %s: Failed to start the firmware.\n", + priv->net_dev->name); + rc = 1; + goto exit; + } + + ipw2100_initialize_ordinals(priv); + + /* Determine capabilities of this particular HW configuration */ + if (ipw2100_get_hw_features(priv)) { + printk(KERN_ERR DRV_NAME + ": %s: Failed to determine HW features.\n", + priv->net_dev->name); + rc = 1; + goto exit; + } + + /* Initialize the geo */ + libipw_set_geo(priv->ieee, &ipw_geos[0]); + priv->ieee->freq_band = LIBIPW_24GHZ_BAND; + + lock = LOCK_NONE; + if (ipw2100_set_ordinal(priv, IPW_ORD_PERS_DB_LOCK, &lock, &ord_len)) { + printk(KERN_ERR DRV_NAME + ": %s: Failed to clear ordinal lock.\n", + priv->net_dev->name); + rc = 1; + goto exit; + } + + priv->status &= ~STATUS_SCANNING; + + if (rf_kill_active(priv)) { + printk(KERN_INFO "%s: Radio is disabled by RF switch.\n", + priv->net_dev->name); + + if (priv->stop_rf_kill) { + priv->stop_rf_kill = 0; + schedule_delayed_work(&priv->rf_kill, + round_jiffies_relative(HZ)); + } + + deferred = 1; + } + + /* Turn on the interrupt so that commands can be processed */ + ipw2100_enable_interrupts(priv); + + /* Send all of the commands that must be sent prior to + * HOST_COMPLETE */ + if (ipw2100_adapter_setup(priv)) { + printk(KERN_ERR DRV_NAME ": %s: Failed to start the card.\n", + priv->net_dev->name); + rc = 1; + goto exit; + } + + if (!deferred) { + /* Enable the adapter - sends HOST_COMPLETE */ + if (ipw2100_enable_adapter(priv)) { + printk(KERN_ERR DRV_NAME ": " + "%s: failed in call to enable adapter.\n", + priv->net_dev->name); + ipw2100_hw_stop_adapter(priv); + rc = 1; + goto exit; + } + + /* Start a scan . . . */ + ipw2100_set_scan_options(priv); + ipw2100_start_scan(priv); + } + + exit: + return rc; +} + +static void ipw2100_down(struct ipw2100_priv *priv) +{ + unsigned long flags; + union iwreq_data wrqu = { + .ap_addr = { + .sa_family = ARPHRD_ETHER} + }; + int associated = priv->status & STATUS_ASSOCIATED; + + /* Kill the RF switch timer */ + if (!priv->stop_rf_kill) { + priv->stop_rf_kill = 1; + cancel_delayed_work(&priv->rf_kill); + } + + /* Kill the firmware hang check timer */ + if (!priv->stop_hang_check) { + priv->stop_hang_check = 1; + cancel_delayed_work(&priv->hang_check); + } + + /* Kill any pending resets */ + if (priv->status & STATUS_RESET_PENDING) + cancel_delayed_work(&priv->reset_work); + + /* Make sure the interrupt is on so that FW commands will be + * processed correctly */ + spin_lock_irqsave(&priv->low_lock, flags); + ipw2100_enable_interrupts(priv); + spin_unlock_irqrestore(&priv->low_lock, flags); + + if (ipw2100_hw_stop_adapter(priv)) + printk(KERN_ERR DRV_NAME ": %s: Error stopping adapter.\n", + priv->net_dev->name); + + /* Do not disable the interrupt until _after_ we disable + * the adaptor. Otherwise the CARD_DISABLE command will never + * be ack'd by the firmware */ + spin_lock_irqsave(&priv->low_lock, flags); + ipw2100_disable_interrupts(priv); + spin_unlock_irqrestore(&priv->low_lock, flags); + + pm_qos_update_request(&ipw2100_pm_qos_req, PM_QOS_DEFAULT_VALUE); + + /* We have to signal any supplicant if we are disassociating */ + if (associated) + wireless_send_event(priv->net_dev, SIOCGIWAP, &wrqu, NULL); + + priv->status &= ~(STATUS_ASSOCIATED | STATUS_ASSOCIATING); + netif_carrier_off(priv->net_dev); + netif_stop_queue(priv->net_dev); +} + +static int ipw2100_wdev_init(struct net_device *dev) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + const struct libipw_geo *geo = libipw_get_geo(priv->ieee); + struct wireless_dev *wdev = &priv->ieee->wdev; + int i; + + memcpy(wdev->wiphy->perm_addr, priv->mac_addr, ETH_ALEN); + + /* fill-out priv->ieee->bg_band */ + if (geo->bg_channels) { + struct ieee80211_supported_band *bg_band = &priv->ieee->bg_band; + + bg_band->band = IEEE80211_BAND_2GHZ; + bg_band->n_channels = geo->bg_channels; + bg_band->channels = kcalloc(geo->bg_channels, + sizeof(struct ieee80211_channel), + GFP_KERNEL); + if (!bg_band->channels) { + ipw2100_down(priv); + return -ENOMEM; + } + /* translate geo->bg to bg_band.channels */ + for (i = 0; i < geo->bg_channels; i++) { + bg_band->channels[i].band = IEEE80211_BAND_2GHZ; + bg_band->channels[i].center_freq = geo->bg[i].freq; + bg_band->channels[i].hw_value = geo->bg[i].channel; + bg_band->channels[i].max_power = geo->bg[i].max_power; + if (geo->bg[i].flags & LIBIPW_CH_PASSIVE_ONLY) + bg_band->channels[i].flags |= + IEEE80211_CHAN_PASSIVE_SCAN; + if (geo->bg[i].flags & LIBIPW_CH_NO_IBSS) + bg_band->channels[i].flags |= + IEEE80211_CHAN_NO_IBSS; + if (geo->bg[i].flags & LIBIPW_CH_RADAR_DETECT) + bg_band->channels[i].flags |= + IEEE80211_CHAN_RADAR; + /* No equivalent for LIBIPW_CH_80211H_RULES, + LIBIPW_CH_UNIFORM_SPREADING, or + LIBIPW_CH_B_ONLY... */ + } + /* point at bitrate info */ + bg_band->bitrates = ipw2100_bg_rates; + bg_band->n_bitrates = RATE_COUNT; + + wdev->wiphy->bands[IEEE80211_BAND_2GHZ] = bg_band; + } + + wdev->wiphy->cipher_suites = ipw_cipher_suites; + wdev->wiphy->n_cipher_suites = ARRAY_SIZE(ipw_cipher_suites); + + set_wiphy_dev(wdev->wiphy, &priv->pci_dev->dev); + if (wiphy_register(wdev->wiphy)) + return -EIO; + return 0; +} + +static void ipw2100_reset_adapter(struct work_struct *work) +{ + struct ipw2100_priv *priv = + container_of(work, struct ipw2100_priv, reset_work.work); + unsigned long flags; + union iwreq_data wrqu = { + .ap_addr = { + .sa_family = ARPHRD_ETHER} + }; + int associated = priv->status & STATUS_ASSOCIATED; + + spin_lock_irqsave(&priv->low_lock, flags); + IPW_DEBUG_INFO(": %s: Restarting adapter.\n", priv->net_dev->name); + priv->resets++; + priv->status &= ~(STATUS_ASSOCIATED | STATUS_ASSOCIATING); + priv->status |= STATUS_SECURITY_UPDATED; + + /* Force a power cycle even if interface hasn't been opened + * yet */ + cancel_delayed_work(&priv->reset_work); + priv->status |= STATUS_RESET_PENDING; + spin_unlock_irqrestore(&priv->low_lock, flags); + + mutex_lock(&priv->action_mutex); + /* stop timed checks so that they don't interfere with reset */ + priv->stop_hang_check = 1; + cancel_delayed_work(&priv->hang_check); + + /* We have to signal any supplicant if we are disassociating */ + if (associated) + wireless_send_event(priv->net_dev, SIOCGIWAP, &wrqu, NULL); + + ipw2100_up(priv, 0); + mutex_unlock(&priv->action_mutex); + +} + +static void isr_indicate_associated(struct ipw2100_priv *priv, u32 status) +{ + +#define MAC_ASSOCIATION_READ_DELAY (HZ) + int ret; + unsigned int len, essid_len; + char essid[IW_ESSID_MAX_SIZE]; + u32 txrate; + u32 chan; + char *txratename; + u8 bssid[ETH_ALEN]; + DECLARE_SSID_BUF(ssid); + + /* + * TBD: BSSID is usually 00:00:00:00:00:00 here and not + * an actual MAC of the AP. Seems like FW sets this + * address too late. Read it later and expose through + * /proc or schedule a later task to query and update + */ + + essid_len = IW_ESSID_MAX_SIZE; + ret = ipw2100_get_ordinal(priv, IPW_ORD_STAT_ASSN_SSID, + essid, &essid_len); + if (ret) { + IPW_DEBUG_INFO("failed querying ordinals at line %d\n", + __LINE__); + return; + } + + len = sizeof(u32); + ret = ipw2100_get_ordinal(priv, IPW_ORD_CURRENT_TX_RATE, &txrate, &len); + if (ret) { + IPW_DEBUG_INFO("failed querying ordinals at line %d\n", + __LINE__); + return; + } + + len = sizeof(u32); + ret = ipw2100_get_ordinal(priv, IPW_ORD_OUR_FREQ, &chan, &len); + if (ret) { + IPW_DEBUG_INFO("failed querying ordinals at line %d\n", + __LINE__); + return; + } + len = ETH_ALEN; + ret = ipw2100_get_ordinal(priv, IPW_ORD_STAT_ASSN_AP_BSSID, bssid, + &len); + if (ret) { + IPW_DEBUG_INFO("failed querying ordinals at line %d\n", + __LINE__); + return; + } + memcpy(priv->ieee->bssid, bssid, ETH_ALEN); + + switch (txrate) { + case TX_RATE_1_MBIT: + txratename = "1Mbps"; + break; + case TX_RATE_2_MBIT: + txratename = "2Mbsp"; + break; + case TX_RATE_5_5_MBIT: + txratename = "5.5Mbps"; + break; + case TX_RATE_11_MBIT: + txratename = "11Mbps"; + break; + default: + IPW_DEBUG_INFO("Unknown rate: %d\n", txrate); + txratename = "unknown rate"; + break; + } + + IPW_DEBUG_INFO("%s: Associated with '%s' at %s, channel %d (BSSID=%pM)\n", + priv->net_dev->name, print_ssid(ssid, essid, essid_len), + txratename, chan, bssid); + + /* now we copy read ssid into dev */ + if (!(priv->config & CFG_STATIC_ESSID)) { + priv->essid_len = min((u8) essid_len, (u8) IW_ESSID_MAX_SIZE); + memcpy(priv->essid, essid, priv->essid_len); + } + priv->channel = chan; + memcpy(priv->bssid, bssid, ETH_ALEN); + + priv->status |= STATUS_ASSOCIATING; + priv->connect_start = get_seconds(); + + schedule_delayed_work(&priv->wx_event_work, HZ / 10); +} + +static int ipw2100_set_essid(struct ipw2100_priv *priv, char *essid, + int length, int batch_mode) +{ + int ssid_len = min(length, IW_ESSID_MAX_SIZE); + struct host_command cmd = { + .host_command = SSID, + .host_command_sequence = 0, + .host_command_length = ssid_len + }; + int err; + DECLARE_SSID_BUF(ssid); + + IPW_DEBUG_HC("SSID: '%s'\n", print_ssid(ssid, essid, ssid_len)); + + if (ssid_len) + memcpy(cmd.host_command_parameters, essid, ssid_len); + + if (!batch_mode) { + err = ipw2100_disable_adapter(priv); + if (err) + return err; + } + + /* Bug in FW currently doesn't honor bit 0 in SET_SCAN_OPTIONS to + * disable auto association -- so we cheat by setting a bogus SSID */ + if (!ssid_len && !(priv->config & CFG_ASSOCIATE)) { + int i; + u8 *bogus = (u8 *) cmd.host_command_parameters; + for (i = 0; i < IW_ESSID_MAX_SIZE; i++) + bogus[i] = 0x18 + i; + cmd.host_command_length = IW_ESSID_MAX_SIZE; + } + + /* NOTE: We always send the SSID command even if the provided ESSID is + * the same as what we currently think is set. */ + + err = ipw2100_hw_send_command(priv, &cmd); + if (!err) { + memset(priv->essid + ssid_len, 0, IW_ESSID_MAX_SIZE - ssid_len); + memcpy(priv->essid, essid, ssid_len); + priv->essid_len = ssid_len; + } + + if (!batch_mode) { + if (ipw2100_enable_adapter(priv)) + err = -EIO; + } + + return err; +} + +static void isr_indicate_association_lost(struct ipw2100_priv *priv, u32 status) +{ + DECLARE_SSID_BUF(ssid); + + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | IPW_DL_ASSOC, + "disassociated: '%s' %pM\n", + print_ssid(ssid, priv->essid, priv->essid_len), + priv->bssid); + + priv->status &= ~(STATUS_ASSOCIATED | STATUS_ASSOCIATING); + + if (priv->status & STATUS_STOPPING) { + IPW_DEBUG_INFO("Card is stopping itself, discard ASSN_LOST.\n"); + return; + } + + memset(priv->bssid, 0, ETH_ALEN); + memset(priv->ieee->bssid, 0, ETH_ALEN); + + netif_carrier_off(priv->net_dev); + netif_stop_queue(priv->net_dev); + + if (!(priv->status & STATUS_RUNNING)) + return; + + if (priv->status & STATUS_SECURITY_UPDATED) + schedule_delayed_work(&priv->security_work, 0); + + schedule_delayed_work(&priv->wx_event_work, 0); +} + +static void isr_indicate_rf_kill(struct ipw2100_priv *priv, u32 status) +{ + IPW_DEBUG_INFO("%s: RF Kill state changed to radio OFF.\n", + priv->net_dev->name); + + /* RF_KILL is now enabled (else we wouldn't be here) */ + wiphy_rfkill_set_hw_state(priv->ieee->wdev.wiphy, true); + priv->status |= STATUS_RF_KILL_HW; + + /* Make sure the RF Kill check timer is running */ + priv->stop_rf_kill = 0; + mod_delayed_work(system_wq, &priv->rf_kill, round_jiffies_relative(HZ)); +} + +static void ipw2100_scan_event(struct work_struct *work) +{ + struct ipw2100_priv *priv = container_of(work, struct ipw2100_priv, + scan_event.work); + union iwreq_data wrqu; + + wrqu.data.length = 0; + wrqu.data.flags = 0; + wireless_send_event(priv->net_dev, SIOCGIWSCAN, &wrqu, NULL); +} + +static void isr_scan_complete(struct ipw2100_priv *priv, u32 status) +{ + IPW_DEBUG_SCAN("scan complete\n"); + /* Age the scan results... */ + priv->ieee->scans++; + priv->status &= ~STATUS_SCANNING; + + /* Only userspace-requested scan completion events go out immediately */ + if (!priv->user_requested_scan) { + schedule_delayed_work(&priv->scan_event, + round_jiffies_relative(msecs_to_jiffies(4000))); + } else { + priv->user_requested_scan = 0; + mod_delayed_work(system_wq, &priv->scan_event, 0); + } +} + +#ifdef CPTCFG_IPW2100_DEBUG +#define IPW2100_HANDLER(v, f) { v, f, # v } +struct ipw2100_status_indicator { + int status; + void (*cb) (struct ipw2100_priv * priv, u32 status); + char *name; +}; +#else +#define IPW2100_HANDLER(v, f) { v, f } +struct ipw2100_status_indicator { + int status; + void (*cb) (struct ipw2100_priv * priv, u32 status); +}; +#endif /* CPTCFG_IPW2100_DEBUG */ + +static void isr_indicate_scanning(struct ipw2100_priv *priv, u32 status) +{ + IPW_DEBUG_SCAN("Scanning...\n"); + priv->status |= STATUS_SCANNING; +} + +static const struct ipw2100_status_indicator status_handlers[] = { + IPW2100_HANDLER(IPW_STATE_INITIALIZED, NULL), + IPW2100_HANDLER(IPW_STATE_COUNTRY_FOUND, NULL), + IPW2100_HANDLER(IPW_STATE_ASSOCIATED, isr_indicate_associated), + IPW2100_HANDLER(IPW_STATE_ASSN_LOST, isr_indicate_association_lost), + IPW2100_HANDLER(IPW_STATE_ASSN_CHANGED, NULL), + IPW2100_HANDLER(IPW_STATE_SCAN_COMPLETE, isr_scan_complete), + IPW2100_HANDLER(IPW_STATE_ENTERED_PSP, NULL), + IPW2100_HANDLER(IPW_STATE_LEFT_PSP, NULL), + IPW2100_HANDLER(IPW_STATE_RF_KILL, isr_indicate_rf_kill), + IPW2100_HANDLER(IPW_STATE_DISABLED, NULL), + IPW2100_HANDLER(IPW_STATE_POWER_DOWN, NULL), + IPW2100_HANDLER(IPW_STATE_SCANNING, isr_indicate_scanning), + IPW2100_HANDLER(-1, NULL) +}; + +static void isr_status_change(struct ipw2100_priv *priv, int status) +{ + int i; + + if (status == IPW_STATE_SCANNING && + priv->status & STATUS_ASSOCIATED && + !(priv->status & STATUS_SCANNING)) { + IPW_DEBUG_INFO("Scan detected while associated, with " + "no scan request. Restarting firmware.\n"); + + /* Wake up any sleeping jobs */ + schedule_reset(priv); + } + + for (i = 0; status_handlers[i].status != -1; i++) { + if (status == status_handlers[i].status) { + IPW_DEBUG_NOTIF("Status change: %s\n", + status_handlers[i].name); + if (status_handlers[i].cb) + status_handlers[i].cb(priv, status); + priv->wstats.status = status; + return; + } + } + + IPW_DEBUG_NOTIF("unknown status received: %04x\n", status); +} + +static void isr_rx_complete_command(struct ipw2100_priv *priv, + struct ipw2100_cmd_header *cmd) +{ +#ifdef CPTCFG_IPW2100_DEBUG + if (cmd->host_command_reg < ARRAY_SIZE(command_types)) { + IPW_DEBUG_HC("Command completed '%s (%d)'\n", + command_types[cmd->host_command_reg], + cmd->host_command_reg); + } +#endif + if (cmd->host_command_reg == HOST_COMPLETE) + priv->status |= STATUS_ENABLED; + + if (cmd->host_command_reg == CARD_DISABLE) + priv->status &= ~STATUS_ENABLED; + + priv->status &= ~STATUS_CMD_ACTIVE; + + wake_up_interruptible(&priv->wait_command_queue); +} + +#ifdef CPTCFG_IPW2100_DEBUG +static const char *frame_types[] = { + "COMMAND_STATUS_VAL", + "STATUS_CHANGE_VAL", + "P80211_DATA_VAL", + "P8023_DATA_VAL", + "HOST_NOTIFICATION_VAL" +}; +#endif + +static int ipw2100_alloc_skb(struct ipw2100_priv *priv, + struct ipw2100_rx_packet *packet) +{ + packet->skb = dev_alloc_skb(sizeof(struct ipw2100_rx)); + if (!packet->skb) + return -ENOMEM; + + packet->rxp = (struct ipw2100_rx *)packet->skb->data; + packet->dma_addr = pci_map_single(priv->pci_dev, packet->skb->data, + sizeof(struct ipw2100_rx), + PCI_DMA_FROMDEVICE); + /* NOTE: pci_map_single does not return an error code, and 0 is a valid + * dma_addr */ + + return 0; +} + +#define SEARCH_ERROR 0xffffffff +#define SEARCH_FAIL 0xfffffffe +#define SEARCH_SUCCESS 0xfffffff0 +#define SEARCH_DISCARD 0 +#define SEARCH_SNAPSHOT 1 + +#define SNAPSHOT_ADDR(ofs) (priv->snapshot[((ofs) >> 12) & 0xff] + ((ofs) & 0xfff)) +static void ipw2100_snapshot_free(struct ipw2100_priv *priv) +{ + int i; + if (!priv->snapshot[0]) + return; + for (i = 0; i < 0x30; i++) + kfree(priv->snapshot[i]); + priv->snapshot[0] = NULL; +} + +#ifdef IPW2100_DEBUG_C3 +static int ipw2100_snapshot_alloc(struct ipw2100_priv *priv) +{ + int i; + if (priv->snapshot[0]) + return 1; + for (i = 0; i < 0x30; i++) { + priv->snapshot[i] = kmalloc(0x1000, GFP_ATOMIC); + if (!priv->snapshot[i]) { + IPW_DEBUG_INFO("%s: Error allocating snapshot " + "buffer %d\n", priv->net_dev->name, i); + while (i > 0) + kfree(priv->snapshot[--i]); + priv->snapshot[0] = NULL; + return 0; + } + } + + return 1; +} + +static u32 ipw2100_match_buf(struct ipw2100_priv *priv, u8 * in_buf, + size_t len, int mode) +{ + u32 i, j; + u32 tmp; + u8 *s, *d; + u32 ret; + + s = in_buf; + if (mode == SEARCH_SNAPSHOT) { + if (!ipw2100_snapshot_alloc(priv)) + mode = SEARCH_DISCARD; + } + + for (ret = SEARCH_FAIL, i = 0; i < 0x30000; i += 4) { + read_nic_dword(priv->net_dev, i, &tmp); + if (mode == SEARCH_SNAPSHOT) + *(u32 *) SNAPSHOT_ADDR(i) = tmp; + if (ret == SEARCH_FAIL) { + d = (u8 *) & tmp; + for (j = 0; j < 4; j++) { + if (*s != *d) { + s = in_buf; + continue; + } + + s++; + d++; + + if ((s - in_buf) == len) + ret = (i + j) - len + 1; + } + } else if (mode == SEARCH_DISCARD) + return ret; + } + + return ret; +} +#endif + +/* + * + * 0) Disconnect the SKB from the firmware (just unmap) + * 1) Pack the ETH header into the SKB + * 2) Pass the SKB to the network stack + * + * When packet is provided by the firmware, it contains the following: + * + * . libipw_hdr + * . libipw_snap_hdr + * + * The size of the constructed ethernet + * + */ +#ifdef IPW2100_RX_DEBUG +static u8 packet_data[IPW_RX_NIC_BUFFER_LENGTH]; +#endif + +static void ipw2100_corruption_detected(struct ipw2100_priv *priv, int i) +{ +#ifdef IPW2100_DEBUG_C3 + struct ipw2100_status *status = &priv->status_queue.drv[i]; + u32 match, reg; + int j; +#endif + + IPW_DEBUG_INFO(": PCI latency error detected at 0x%04zX.\n", + i * sizeof(struct ipw2100_status)); + +#ifdef IPW2100_DEBUG_C3 + /* Halt the firmware so we can get a good image */ + write_register(priv->net_dev, IPW_REG_RESET_REG, + IPW_AUX_HOST_RESET_REG_STOP_MASTER); + j = 5; + do { + udelay(IPW_WAIT_RESET_MASTER_ASSERT_COMPLETE_DELAY); + read_register(priv->net_dev, IPW_REG_RESET_REG, ®); + + if (reg & IPW_AUX_HOST_RESET_REG_MASTER_DISABLED) + break; + } while (j--); + + match = ipw2100_match_buf(priv, (u8 *) status, + sizeof(struct ipw2100_status), + SEARCH_SNAPSHOT); + if (match < SEARCH_SUCCESS) + IPW_DEBUG_INFO("%s: DMA status match in Firmware at " + "offset 0x%06X, length %d:\n", + priv->net_dev->name, match, + sizeof(struct ipw2100_status)); + else + IPW_DEBUG_INFO("%s: No DMA status match in " + "Firmware.\n", priv->net_dev->name); + + printk_buf((u8 *) priv->status_queue.drv, + sizeof(struct ipw2100_status) * RX_QUEUE_LENGTH); +#endif + + priv->fatal_error = IPW2100_ERR_C3_CORRUPTION; + priv->net_dev->stats.rx_errors++; + schedule_reset(priv); +} + +static void isr_rx(struct ipw2100_priv *priv, int i, + struct libipw_rx_stats *stats) +{ + struct net_device *dev = priv->net_dev; + struct ipw2100_status *status = &priv->status_queue.drv[i]; + struct ipw2100_rx_packet *packet = &priv->rx_buffers[i]; + + IPW_DEBUG_RX("Handler...\n"); + + if (unlikely(status->frame_size > skb_tailroom(packet->skb))) { + IPW_DEBUG_INFO("%s: frame_size (%u) > skb_tailroom (%u)!" + " Dropping.\n", + dev->name, + status->frame_size, skb_tailroom(packet->skb)); + dev->stats.rx_errors++; + return; + } + + if (unlikely(!netif_running(dev))) { + dev->stats.rx_errors++; + priv->wstats.discard.misc++; + IPW_DEBUG_DROP("Dropping packet while interface is not up.\n"); + return; + } + + if (unlikely(priv->ieee->iw_mode != IW_MODE_MONITOR && + !(priv->status & STATUS_ASSOCIATED))) { + IPW_DEBUG_DROP("Dropping packet while not associated.\n"); + priv->wstats.discard.misc++; + return; + } + + pci_unmap_single(priv->pci_dev, + packet->dma_addr, + sizeof(struct ipw2100_rx), PCI_DMA_FROMDEVICE); + + skb_put(packet->skb, status->frame_size); + +#ifdef IPW2100_RX_DEBUG + /* Make a copy of the frame so we can dump it to the logs if + * libipw_rx fails */ + skb_copy_from_linear_data(packet->skb, packet_data, + min_t(u32, status->frame_size, + IPW_RX_NIC_BUFFER_LENGTH)); +#endif + + if (!libipw_rx(priv->ieee, packet->skb, stats)) { +#ifdef IPW2100_RX_DEBUG + IPW_DEBUG_DROP("%s: Non consumed packet:\n", + dev->name); + printk_buf(IPW_DL_DROP, packet_data, status->frame_size); +#endif + dev->stats.rx_errors++; + + /* libipw_rx failed, so it didn't free the SKB */ + dev_kfree_skb_any(packet->skb); + packet->skb = NULL; + } + + /* We need to allocate a new SKB and attach it to the RDB. */ + if (unlikely(ipw2100_alloc_skb(priv, packet))) { + printk(KERN_WARNING DRV_NAME ": " + "%s: Unable to allocate SKB onto RBD ring - disabling " + "adapter.\n", dev->name); + /* TODO: schedule adapter shutdown */ + IPW_DEBUG_INFO("TODO: Shutdown adapter...\n"); + } + + /* Update the RDB entry */ + priv->rx_queue.drv[i].host_addr = packet->dma_addr; +} + +#ifdef CPTCFG_IPW2100_MONITOR + +static void isr_rx_monitor(struct ipw2100_priv *priv, int i, + struct libipw_rx_stats *stats) +{ + struct net_device *dev = priv->net_dev; + struct ipw2100_status *status = &priv->status_queue.drv[i]; + struct ipw2100_rx_packet *packet = &priv->rx_buffers[i]; + + /* Magic struct that slots into the radiotap header -- no reason + * to build this manually element by element, we can write it much + * more efficiently than we can parse it. ORDER MATTERS HERE */ + struct ipw_rt_hdr { + struct ieee80211_radiotap_header rt_hdr; + s8 rt_dbmsignal; /* signal in dbM, kluged to signed */ + } *ipw_rt; + + IPW_DEBUG_RX("Handler...\n"); + + if (unlikely(status->frame_size > skb_tailroom(packet->skb) - + sizeof(struct ipw_rt_hdr))) { + IPW_DEBUG_INFO("%s: frame_size (%u) > skb_tailroom (%u)!" + " Dropping.\n", + dev->name, + status->frame_size, + skb_tailroom(packet->skb)); + dev->stats.rx_errors++; + return; + } + + if (unlikely(!netif_running(dev))) { + dev->stats.rx_errors++; + priv->wstats.discard.misc++; + IPW_DEBUG_DROP("Dropping packet while interface is not up.\n"); + return; + } + + if (unlikely(priv->config & CFG_CRC_CHECK && + status->flags & IPW_STATUS_FLAG_CRC_ERROR)) { + IPW_DEBUG_RX("CRC error in packet. Dropping.\n"); + dev->stats.rx_errors++; + return; + } + + pci_unmap_single(priv->pci_dev, packet->dma_addr, + sizeof(struct ipw2100_rx), PCI_DMA_FROMDEVICE); + memmove(packet->skb->data + sizeof(struct ipw_rt_hdr), + packet->skb->data, status->frame_size); + + ipw_rt = (struct ipw_rt_hdr *) packet->skb->data; + + ipw_rt->rt_hdr.it_version = PKTHDR_RADIOTAP_VERSION; + ipw_rt->rt_hdr.it_pad = 0; /* always good to zero */ + ipw_rt->rt_hdr.it_len = cpu_to_le16(sizeof(struct ipw_rt_hdr)); /* total hdr+data */ + + ipw_rt->rt_hdr.it_present = cpu_to_le32(1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL); + + ipw_rt->rt_dbmsignal = status->rssi + IPW2100_RSSI_TO_DBM; + + skb_put(packet->skb, status->frame_size + sizeof(struct ipw_rt_hdr)); + + if (!libipw_rx(priv->ieee, packet->skb, stats)) { + dev->stats.rx_errors++; + + /* libipw_rx failed, so it didn't free the SKB */ + dev_kfree_skb_any(packet->skb); + packet->skb = NULL; + } + + /* We need to allocate a new SKB and attach it to the RDB. */ + if (unlikely(ipw2100_alloc_skb(priv, packet))) { + IPW_DEBUG_WARNING( + "%s: Unable to allocate SKB onto RBD ring - disabling " + "adapter.\n", dev->name); + /* TODO: schedule adapter shutdown */ + IPW_DEBUG_INFO("TODO: Shutdown adapter...\n"); + } + + /* Update the RDB entry */ + priv->rx_queue.drv[i].host_addr = packet->dma_addr; +} + +#endif + +static int ipw2100_corruption_check(struct ipw2100_priv *priv, int i) +{ + struct ipw2100_status *status = &priv->status_queue.drv[i]; + struct ipw2100_rx *u = priv->rx_buffers[i].rxp; + u16 frame_type = status->status_fields & STATUS_TYPE_MASK; + + switch (frame_type) { + case COMMAND_STATUS_VAL: + return (status->frame_size != sizeof(u->rx_data.command)); + case STATUS_CHANGE_VAL: + return (status->frame_size != sizeof(u->rx_data.status)); + case HOST_NOTIFICATION_VAL: + return (status->frame_size < sizeof(u->rx_data.notification)); + case P80211_DATA_VAL: + case P8023_DATA_VAL: +#ifdef CPTCFG_IPW2100_MONITOR + return 0; +#else + switch (WLAN_FC_GET_TYPE(le16_to_cpu(u->rx_data.header.frame_ctl))) { + case IEEE80211_FTYPE_MGMT: + case IEEE80211_FTYPE_CTL: + return 0; + case IEEE80211_FTYPE_DATA: + return (status->frame_size > + IPW_MAX_802_11_PAYLOAD_LENGTH); + } +#endif + } + + return 1; +} + +/* + * ipw2100 interrupts are disabled at this point, and the ISR + * is the only code that calls this method. So, we do not need + * to play with any locks. + * + * RX Queue works as follows: + * + * Read index - firmware places packet in entry identified by the + * Read index and advances Read index. In this manner, + * Read index will always point to the next packet to + * be filled--but not yet valid. + * + * Write index - driver fills this entry with an unused RBD entry. + * This entry has not filled by the firmware yet. + * + * In between the W and R indexes are the RBDs that have been received + * but not yet processed. + * + * The process of handling packets will start at WRITE + 1 and advance + * until it reaches the READ index. + * + * The WRITE index is cached in the variable 'priv->rx_queue.next'. + * + */ +static void __ipw2100_rx_process(struct ipw2100_priv *priv) +{ + struct ipw2100_bd_queue *rxq = &priv->rx_queue; + struct ipw2100_status_queue *sq = &priv->status_queue; + struct ipw2100_rx_packet *packet; + u16 frame_type; + u32 r, w, i, s; + struct ipw2100_rx *u; + struct libipw_rx_stats stats = { + .mac_time = jiffies, + }; + + read_register(priv->net_dev, IPW_MEM_HOST_SHARED_RX_READ_INDEX, &r); + read_register(priv->net_dev, IPW_MEM_HOST_SHARED_RX_WRITE_INDEX, &w); + + if (r >= rxq->entries) { + IPW_DEBUG_RX("exit - bad read index\n"); + return; + } + + i = (rxq->next + 1) % rxq->entries; + s = i; + while (i != r) { + /* IPW_DEBUG_RX("r = %d : w = %d : processing = %d\n", + r, rxq->next, i); */ + + packet = &priv->rx_buffers[i]; + + /* Sync the DMA for the RX buffer so CPU is sure to get + * the correct values */ + pci_dma_sync_single_for_cpu(priv->pci_dev, packet->dma_addr, + sizeof(struct ipw2100_rx), + PCI_DMA_FROMDEVICE); + + if (unlikely(ipw2100_corruption_check(priv, i))) { + ipw2100_corruption_detected(priv, i); + goto increment; + } + + u = packet->rxp; + frame_type = sq->drv[i].status_fields & STATUS_TYPE_MASK; + stats.rssi = sq->drv[i].rssi + IPW2100_RSSI_TO_DBM; + stats.len = sq->drv[i].frame_size; + + stats.mask = 0; + if (stats.rssi != 0) + stats.mask |= LIBIPW_STATMASK_RSSI; + stats.freq = LIBIPW_24GHZ_BAND; + + IPW_DEBUG_RX("%s: '%s' frame type received (%d).\n", + priv->net_dev->name, frame_types[frame_type], + stats.len); + + switch (frame_type) { + case COMMAND_STATUS_VAL: + /* Reset Rx watchdog */ + isr_rx_complete_command(priv, &u->rx_data.command); + break; + + case STATUS_CHANGE_VAL: + isr_status_change(priv, u->rx_data.status); + break; + + case P80211_DATA_VAL: + case P8023_DATA_VAL: +#ifdef CPTCFG_IPW2100_MONITOR + if (priv->ieee->iw_mode == IW_MODE_MONITOR) { + isr_rx_monitor(priv, i, &stats); + break; + } +#endif + if (stats.len < sizeof(struct libipw_hdr_3addr)) + break; + switch (WLAN_FC_GET_TYPE(le16_to_cpu(u->rx_data.header.frame_ctl))) { + case IEEE80211_FTYPE_MGMT: + libipw_rx_mgt(priv->ieee, + &u->rx_data.header, &stats); + break; + + case IEEE80211_FTYPE_CTL: + break; + + case IEEE80211_FTYPE_DATA: + isr_rx(priv, i, &stats); + break; + + } + break; + } + + increment: + /* clear status field associated with this RBD */ + rxq->drv[i].status.info.field = 0; + + i = (i + 1) % rxq->entries; + } + + if (i != s) { + /* backtrack one entry, wrapping to end if at 0 */ + rxq->next = (i ? i : rxq->entries) - 1; + + write_register(priv->net_dev, + IPW_MEM_HOST_SHARED_RX_WRITE_INDEX, rxq->next); + } +} + +/* + * __ipw2100_tx_process + * + * This routine will determine whether the next packet on + * the fw_pend_list has been processed by the firmware yet. + * + * If not, then it does nothing and returns. + * + * If so, then it removes the item from the fw_pend_list, frees + * any associated storage, and places the item back on the + * free list of its source (either msg_free_list or tx_free_list) + * + * TX Queue works as follows: + * + * Read index - points to the next TBD that the firmware will + * process. The firmware will read the data, and once + * done processing, it will advance the Read index. + * + * Write index - driver fills this entry with an constructed TBD + * entry. The Write index is not advanced until the + * packet has been configured. + * + * In between the W and R indexes are the TBDs that have NOT been + * processed. Lagging behind the R index are packets that have + * been processed but have not been freed by the driver. + * + * In order to free old storage, an internal index will be maintained + * that points to the next packet to be freed. When all used + * packets have been freed, the oldest index will be the same as the + * firmware's read index. + * + * The OLDEST index is cached in the variable 'priv->tx_queue.oldest' + * + * Because the TBD structure can not contain arbitrary data, the + * driver must keep an internal queue of cached allocations such that + * it can put that data back into the tx_free_list and msg_free_list + * for use by future command and data packets. + * + */ +static int __ipw2100_tx_process(struct ipw2100_priv *priv) +{ + struct ipw2100_bd_queue *txq = &priv->tx_queue; + struct ipw2100_bd *tbd; + struct list_head *element; + struct ipw2100_tx_packet *packet; + int descriptors_used; + int e, i; + u32 r, w, frag_num = 0; + + if (list_empty(&priv->fw_pend_list)) + return 0; + + element = priv->fw_pend_list.next; + + packet = list_entry(element, struct ipw2100_tx_packet, list); + tbd = &txq->drv[packet->index]; + + /* Determine how many TBD entries must be finished... */ + switch (packet->type) { + case COMMAND: + /* COMMAND uses only one slot; don't advance */ + descriptors_used = 1; + e = txq->oldest; + break; + + case DATA: + /* DATA uses two slots; advance and loop position. */ + descriptors_used = tbd->num_fragments; + frag_num = tbd->num_fragments - 1; + e = txq->oldest + frag_num; + e %= txq->entries; + break; + + default: + printk(KERN_WARNING DRV_NAME ": %s: Bad fw_pend_list entry!\n", + priv->net_dev->name); + return 0; + } + + /* if the last TBD is not done by NIC yet, then packet is + * not ready to be released. + * + */ + read_register(priv->net_dev, IPW_MEM_HOST_SHARED_TX_QUEUE_READ_INDEX, + &r); + read_register(priv->net_dev, IPW_MEM_HOST_SHARED_TX_QUEUE_WRITE_INDEX, + &w); + if (w != txq->next) + printk(KERN_WARNING DRV_NAME ": %s: write index mismatch\n", + priv->net_dev->name); + + /* + * txq->next is the index of the last packet written txq->oldest is + * the index of the r is the index of the next packet to be read by + * firmware + */ + + /* + * Quick graphic to help you visualize the following + * if / else statement + * + * ===>| s---->|=============== + * e>| + * | a | b | c | d | e | f | g | h | i | j | k | l + * r---->| + * w + * + * w - updated by driver + * r - updated by firmware + * s - start of oldest BD entry (txq->oldest) + * e - end of oldest BD entry + * + */ + if (!((r <= w && (e < r || e >= w)) || (e < r && e >= w))) { + IPW_DEBUG_TX("exit - no processed packets ready to release.\n"); + return 0; + } + + list_del(element); + DEC_STAT(&priv->fw_pend_stat); + +#ifdef CPTCFG_IPW2100_DEBUG + { + i = txq->oldest; + IPW_DEBUG_TX("TX%d V=%p P=%04X T=%04X L=%d\n", i, + &txq->drv[i], + (u32) (txq->nic + i * sizeof(struct ipw2100_bd)), + txq->drv[i].host_addr, txq->drv[i].buf_length); + + if (packet->type == DATA) { + i = (i + 1) % txq->entries; + + IPW_DEBUG_TX("TX%d V=%p P=%04X T=%04X L=%d\n", i, + &txq->drv[i], + (u32) (txq->nic + i * + sizeof(struct ipw2100_bd)), + (u32) txq->drv[i].host_addr, + txq->drv[i].buf_length); + } + } +#endif + + switch (packet->type) { + case DATA: + if (txq->drv[txq->oldest].status.info.fields.txType != 0) + printk(KERN_WARNING DRV_NAME ": %s: Queue mismatch. " + "Expecting DATA TBD but pulled " + "something else: ids %d=%d.\n", + priv->net_dev->name, txq->oldest, packet->index); + + /* DATA packet; we have to unmap and free the SKB */ + for (i = 0; i < frag_num; i++) { + tbd = &txq->drv[(packet->index + 1 + i) % txq->entries]; + + IPW_DEBUG_TX("TX%d P=%08x L=%d\n", + (packet->index + 1 + i) % txq->entries, + tbd->host_addr, tbd->buf_length); + + pci_unmap_single(priv->pci_dev, + tbd->host_addr, + tbd->buf_length, PCI_DMA_TODEVICE); + } + + libipw_txb_free(packet->info.d_struct.txb); + packet->info.d_struct.txb = NULL; + + list_add_tail(element, &priv->tx_free_list); + INC_STAT(&priv->tx_free_stat); + + /* We have a free slot in the Tx queue, so wake up the + * transmit layer if it is stopped. */ + if (priv->status & STATUS_ASSOCIATED) + netif_wake_queue(priv->net_dev); + + /* A packet was processed by the hardware, so update the + * watchdog */ + priv->net_dev->trans_start = jiffies; + + break; + + case COMMAND: + if (txq->drv[txq->oldest].status.info.fields.txType != 1) + printk(KERN_WARNING DRV_NAME ": %s: Queue mismatch. " + "Expecting COMMAND TBD but pulled " + "something else: ids %d=%d.\n", + priv->net_dev->name, txq->oldest, packet->index); + +#ifdef CPTCFG_IPW2100_DEBUG + if (packet->info.c_struct.cmd->host_command_reg < + ARRAY_SIZE(command_types)) + IPW_DEBUG_TX("Command '%s (%d)' processed: %d.\n", + command_types[packet->info.c_struct.cmd-> + host_command_reg], + packet->info.c_struct.cmd-> + host_command_reg, + packet->info.c_struct.cmd->cmd_status_reg); +#endif + + list_add_tail(element, &priv->msg_free_list); + INC_STAT(&priv->msg_free_stat); + break; + } + + /* advance oldest used TBD pointer to start of next entry */ + txq->oldest = (e + 1) % txq->entries; + /* increase available TBDs number */ + txq->available += descriptors_used; + SET_STAT(&priv->txq_stat, txq->available); + + IPW_DEBUG_TX("packet latency (send to process) %ld jiffies\n", + jiffies - packet->jiffy_start); + + return (!list_empty(&priv->fw_pend_list)); +} + +static inline void __ipw2100_tx_complete(struct ipw2100_priv *priv) +{ + int i = 0; + + while (__ipw2100_tx_process(priv) && i < 200) + i++; + + if (i == 200) { + printk(KERN_WARNING DRV_NAME ": " + "%s: Driver is running slow (%d iters).\n", + priv->net_dev->name, i); + } +} + +static void ipw2100_tx_send_commands(struct ipw2100_priv *priv) +{ + struct list_head *element; + struct ipw2100_tx_packet *packet; + struct ipw2100_bd_queue *txq = &priv->tx_queue; + struct ipw2100_bd *tbd; + int next = txq->next; + + while (!list_empty(&priv->msg_pend_list)) { + /* if there isn't enough space in TBD queue, then + * don't stuff a new one in. + * NOTE: 3 are needed as a command will take one, + * and there is a minimum of 2 that must be + * maintained between the r and w indexes + */ + if (txq->available <= 3) { + IPW_DEBUG_TX("no room in tx_queue\n"); + break; + } + + element = priv->msg_pend_list.next; + list_del(element); + DEC_STAT(&priv->msg_pend_stat); + + packet = list_entry(element, struct ipw2100_tx_packet, list); + + IPW_DEBUG_TX("using TBD at virt=%p, phys=%04X\n", + &txq->drv[txq->next], + (u32) (txq->nic + txq->next * + sizeof(struct ipw2100_bd))); + + packet->index = txq->next; + + tbd = &txq->drv[txq->next]; + + /* initialize TBD */ + tbd->host_addr = packet->info.c_struct.cmd_phys; + tbd->buf_length = sizeof(struct ipw2100_cmd_header); + /* not marking number of fragments causes problems + * with f/w debug version */ + tbd->num_fragments = 1; + tbd->status.info.field = + IPW_BD_STATUS_TX_FRAME_COMMAND | + IPW_BD_STATUS_TX_INTERRUPT_ENABLE; + + /* update TBD queue counters */ + txq->next++; + txq->next %= txq->entries; + txq->available--; + DEC_STAT(&priv->txq_stat); + + list_add_tail(element, &priv->fw_pend_list); + INC_STAT(&priv->fw_pend_stat); + } + + if (txq->next != next) { + /* kick off the DMA by notifying firmware the + * write index has moved; make sure TBD stores are sync'd */ + wmb(); + write_register(priv->net_dev, + IPW_MEM_HOST_SHARED_TX_QUEUE_WRITE_INDEX, + txq->next); + } +} + +/* + * ipw2100_tx_send_data + * + */ +static void ipw2100_tx_send_data(struct ipw2100_priv *priv) +{ + struct list_head *element; + struct ipw2100_tx_packet *packet; + struct ipw2100_bd_queue *txq = &priv->tx_queue; + struct ipw2100_bd *tbd; + int next = txq->next; + int i = 0; + struct ipw2100_data_header *ipw_hdr; + struct libipw_hdr_3addr *hdr; + + while (!list_empty(&priv->tx_pend_list)) { + /* if there isn't enough space in TBD queue, then + * don't stuff a new one in. + * NOTE: 4 are needed as a data will take two, + * and there is a minimum of 2 that must be + * maintained between the r and w indexes + */ + element = priv->tx_pend_list.next; + packet = list_entry(element, struct ipw2100_tx_packet, list); + + if (unlikely(1 + packet->info.d_struct.txb->nr_frags > + IPW_MAX_BDS)) { + /* TODO: Support merging buffers if more than + * IPW_MAX_BDS are used */ + IPW_DEBUG_INFO("%s: Maximum BD threshold exceeded. " + "Increase fragmentation level.\n", + priv->net_dev->name); + } + + if (txq->available <= 3 + packet->info.d_struct.txb->nr_frags) { + IPW_DEBUG_TX("no room in tx_queue\n"); + break; + } + + list_del(element); + DEC_STAT(&priv->tx_pend_stat); + + tbd = &txq->drv[txq->next]; + + packet->index = txq->next; + + ipw_hdr = packet->info.d_struct.data; + hdr = (struct libipw_hdr_3addr *)packet->info.d_struct.txb-> + fragments[0]->data; + + if (priv->ieee->iw_mode == IW_MODE_INFRA) { + /* To DS: Addr1 = BSSID, Addr2 = SA, + Addr3 = DA */ + memcpy(ipw_hdr->src_addr, hdr->addr2, ETH_ALEN); + memcpy(ipw_hdr->dst_addr, hdr->addr3, ETH_ALEN); + } else if (priv->ieee->iw_mode == IW_MODE_ADHOC) { + /* not From/To DS: Addr1 = DA, Addr2 = SA, + Addr3 = BSSID */ + memcpy(ipw_hdr->src_addr, hdr->addr2, ETH_ALEN); + memcpy(ipw_hdr->dst_addr, hdr->addr1, ETH_ALEN); + } + + ipw_hdr->host_command_reg = SEND; + ipw_hdr->host_command_reg1 = 0; + + /* For now we only support host based encryption */ + ipw_hdr->needs_encryption = 0; + ipw_hdr->encrypted = packet->info.d_struct.txb->encrypted; + if (packet->info.d_struct.txb->nr_frags > 1) + ipw_hdr->fragment_size = + packet->info.d_struct.txb->frag_size - + LIBIPW_3ADDR_LEN; + else + ipw_hdr->fragment_size = 0; + + tbd->host_addr = packet->info.d_struct.data_phys; + tbd->buf_length = sizeof(struct ipw2100_data_header); + tbd->num_fragments = 1 + packet->info.d_struct.txb->nr_frags; + tbd->status.info.field = + IPW_BD_STATUS_TX_FRAME_802_3 | + IPW_BD_STATUS_TX_FRAME_NOT_LAST_FRAGMENT; + txq->next++; + txq->next %= txq->entries; + + IPW_DEBUG_TX("data header tbd TX%d P=%08x L=%d\n", + packet->index, tbd->host_addr, tbd->buf_length); +#ifdef CPTCFG_IPW2100_DEBUG + if (packet->info.d_struct.txb->nr_frags > 1) + IPW_DEBUG_FRAG("fragment Tx: %d frames\n", + packet->info.d_struct.txb->nr_frags); +#endif + + for (i = 0; i < packet->info.d_struct.txb->nr_frags; i++) { + tbd = &txq->drv[txq->next]; + if (i == packet->info.d_struct.txb->nr_frags - 1) + tbd->status.info.field = + IPW_BD_STATUS_TX_FRAME_802_3 | + IPW_BD_STATUS_TX_INTERRUPT_ENABLE; + else + tbd->status.info.field = + IPW_BD_STATUS_TX_FRAME_802_3 | + IPW_BD_STATUS_TX_FRAME_NOT_LAST_FRAGMENT; + + tbd->buf_length = packet->info.d_struct.txb-> + fragments[i]->len - LIBIPW_3ADDR_LEN; + + tbd->host_addr = pci_map_single(priv->pci_dev, + packet->info.d_struct. + txb->fragments[i]-> + data + + LIBIPW_3ADDR_LEN, + tbd->buf_length, + PCI_DMA_TODEVICE); + + IPW_DEBUG_TX("data frag tbd TX%d P=%08x L=%d\n", + txq->next, tbd->host_addr, + tbd->buf_length); + + pci_dma_sync_single_for_device(priv->pci_dev, + tbd->host_addr, + tbd->buf_length, + PCI_DMA_TODEVICE); + + txq->next++; + txq->next %= txq->entries; + } + + txq->available -= 1 + packet->info.d_struct.txb->nr_frags; + SET_STAT(&priv->txq_stat, txq->available); + + list_add_tail(element, &priv->fw_pend_list); + INC_STAT(&priv->fw_pend_stat); + } + + if (txq->next != next) { + /* kick off the DMA by notifying firmware the + * write index has moved; make sure TBD stores are sync'd */ + write_register(priv->net_dev, + IPW_MEM_HOST_SHARED_TX_QUEUE_WRITE_INDEX, + txq->next); + } +} + +static void ipw2100_irq_tasklet(struct ipw2100_priv *priv) +{ + struct net_device *dev = priv->net_dev; + unsigned long flags; + u32 inta, tmp; + + spin_lock_irqsave(&priv->low_lock, flags); + ipw2100_disable_interrupts(priv); + + read_register(dev, IPW_REG_INTA, &inta); + + IPW_DEBUG_ISR("enter - INTA: 0x%08lX\n", + (unsigned long)inta & IPW_INTERRUPT_MASK); + + priv->in_isr++; + priv->interrupts++; + + /* We do not loop and keep polling for more interrupts as this + * is frowned upon and doesn't play nicely with other potentially + * chained IRQs */ + IPW_DEBUG_ISR("INTA: 0x%08lX\n", + (unsigned long)inta & IPW_INTERRUPT_MASK); + + if (inta & IPW2100_INTA_FATAL_ERROR) { + printk(KERN_WARNING DRV_NAME + ": Fatal interrupt. Scheduling firmware restart.\n"); + priv->inta_other++; + write_register(dev, IPW_REG_INTA, IPW2100_INTA_FATAL_ERROR); + + read_nic_dword(dev, IPW_NIC_FATAL_ERROR, &priv->fatal_error); + IPW_DEBUG_INFO("%s: Fatal error value: 0x%08X\n", + priv->net_dev->name, priv->fatal_error); + + read_nic_dword(dev, IPW_ERROR_ADDR(priv->fatal_error), &tmp); + IPW_DEBUG_INFO("%s: Fatal error address value: 0x%08X\n", + priv->net_dev->name, tmp); + + /* Wake up any sleeping jobs */ + schedule_reset(priv); + } + + if (inta & IPW2100_INTA_PARITY_ERROR) { + printk(KERN_ERR DRV_NAME + ": ***** PARITY ERROR INTERRUPT !!!!\n"); + priv->inta_other++; + write_register(dev, IPW_REG_INTA, IPW2100_INTA_PARITY_ERROR); + } + + if (inta & IPW2100_INTA_RX_TRANSFER) { + IPW_DEBUG_ISR("RX interrupt\n"); + + priv->rx_interrupts++; + + write_register(dev, IPW_REG_INTA, IPW2100_INTA_RX_TRANSFER); + + __ipw2100_rx_process(priv); + __ipw2100_tx_complete(priv); + } + + if (inta & IPW2100_INTA_TX_TRANSFER) { + IPW_DEBUG_ISR("TX interrupt\n"); + + priv->tx_interrupts++; + + write_register(dev, IPW_REG_INTA, IPW2100_INTA_TX_TRANSFER); + + __ipw2100_tx_complete(priv); + ipw2100_tx_send_commands(priv); + ipw2100_tx_send_data(priv); + } + + if (inta & IPW2100_INTA_TX_COMPLETE) { + IPW_DEBUG_ISR("TX complete\n"); + priv->inta_other++; + write_register(dev, IPW_REG_INTA, IPW2100_INTA_TX_COMPLETE); + + __ipw2100_tx_complete(priv); + } + + if (inta & IPW2100_INTA_EVENT_INTERRUPT) { + /* ipw2100_handle_event(dev); */ + priv->inta_other++; + write_register(dev, IPW_REG_INTA, IPW2100_INTA_EVENT_INTERRUPT); + } + + if (inta & IPW2100_INTA_FW_INIT_DONE) { + IPW_DEBUG_ISR("FW init done interrupt\n"); + priv->inta_other++; + + read_register(dev, IPW_REG_INTA, &tmp); + if (tmp & (IPW2100_INTA_FATAL_ERROR | + IPW2100_INTA_PARITY_ERROR)) { + write_register(dev, IPW_REG_INTA, + IPW2100_INTA_FATAL_ERROR | + IPW2100_INTA_PARITY_ERROR); + } + + write_register(dev, IPW_REG_INTA, IPW2100_INTA_FW_INIT_DONE); + } + + if (inta & IPW2100_INTA_STATUS_CHANGE) { + IPW_DEBUG_ISR("Status change interrupt\n"); + priv->inta_other++; + write_register(dev, IPW_REG_INTA, IPW2100_INTA_STATUS_CHANGE); + } + + if (inta & IPW2100_INTA_SLAVE_MODE_HOST_COMMAND_DONE) { + IPW_DEBUG_ISR("slave host mode interrupt\n"); + priv->inta_other++; + write_register(dev, IPW_REG_INTA, + IPW2100_INTA_SLAVE_MODE_HOST_COMMAND_DONE); + } + + priv->in_isr--; + ipw2100_enable_interrupts(priv); + + spin_unlock_irqrestore(&priv->low_lock, flags); + + IPW_DEBUG_ISR("exit\n"); +} + +static irqreturn_t ipw2100_interrupt(int irq, void *data) +{ + struct ipw2100_priv *priv = data; + u32 inta, inta_mask; + + if (!data) + return IRQ_NONE; + + spin_lock(&priv->low_lock); + + /* We check to see if we should be ignoring interrupts before + * we touch the hardware. During ucode load if we try and handle + * an interrupt we can cause keyboard problems as well as cause + * the ucode to fail to initialize */ + if (!(priv->status & STATUS_INT_ENABLED)) { + /* Shared IRQ */ + goto none; + } + + read_register(priv->net_dev, IPW_REG_INTA_MASK, &inta_mask); + read_register(priv->net_dev, IPW_REG_INTA, &inta); + + if (inta == 0xFFFFFFFF) { + /* Hardware disappeared */ + printk(KERN_WARNING DRV_NAME ": IRQ INTA == 0xFFFFFFFF\n"); + goto none; + } + + inta &= IPW_INTERRUPT_MASK; + + if (!(inta & inta_mask)) { + /* Shared interrupt */ + goto none; + } + + /* We disable the hardware interrupt here just to prevent unneeded + * calls to be made. We disable this again within the actual + * work tasklet, so if another part of the code re-enables the + * interrupt, that is fine */ + ipw2100_disable_interrupts(priv); + + tasklet_schedule(&priv->irq_tasklet); + spin_unlock(&priv->low_lock); + + return IRQ_HANDLED; + none: + spin_unlock(&priv->low_lock); + return IRQ_NONE; +} + +static netdev_tx_t ipw2100_tx(struct libipw_txb *txb, + struct net_device *dev, int pri) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + struct list_head *element; + struct ipw2100_tx_packet *packet; + unsigned long flags; + + spin_lock_irqsave(&priv->low_lock, flags); + + if (!(priv->status & STATUS_ASSOCIATED)) { + IPW_DEBUG_INFO("Can not transmit when not connected.\n"); + priv->net_dev->stats.tx_carrier_errors++; + netif_stop_queue(dev); + goto fail_unlock; + } + + if (list_empty(&priv->tx_free_list)) + goto fail_unlock; + + element = priv->tx_free_list.next; + packet = list_entry(element, struct ipw2100_tx_packet, list); + + packet->info.d_struct.txb = txb; + + IPW_DEBUG_TX("Sending fragment (%d bytes):\n", txb->fragments[0]->len); + printk_buf(IPW_DL_TX, txb->fragments[0]->data, txb->fragments[0]->len); + + packet->jiffy_start = jiffies; + + list_del(element); + DEC_STAT(&priv->tx_free_stat); + + list_add_tail(element, &priv->tx_pend_list); + INC_STAT(&priv->tx_pend_stat); + + ipw2100_tx_send_data(priv); + + spin_unlock_irqrestore(&priv->low_lock, flags); + return NETDEV_TX_OK; + +fail_unlock: + netif_stop_queue(dev); + spin_unlock_irqrestore(&priv->low_lock, flags); + return NETDEV_TX_BUSY; +} + +static int ipw2100_msg_allocate(struct ipw2100_priv *priv) +{ + int i, j, err = -EINVAL; + void *v; + dma_addr_t p; + + priv->msg_buffers = + kmalloc(IPW_COMMAND_POOL_SIZE * sizeof(struct ipw2100_tx_packet), + GFP_KERNEL); + if (!priv->msg_buffers) + return -ENOMEM; + + for (i = 0; i < IPW_COMMAND_POOL_SIZE; i++) { + v = pci_alloc_consistent(priv->pci_dev, + sizeof(struct ipw2100_cmd_header), &p); + if (!v) { + printk(KERN_ERR DRV_NAME ": " + "%s: PCI alloc failed for msg " + "buffers.\n", priv->net_dev->name); + err = -ENOMEM; + break; + } + + memset(v, 0, sizeof(struct ipw2100_cmd_header)); + + priv->msg_buffers[i].type = COMMAND; + priv->msg_buffers[i].info.c_struct.cmd = + (struct ipw2100_cmd_header *)v; + priv->msg_buffers[i].info.c_struct.cmd_phys = p; + } + + if (i == IPW_COMMAND_POOL_SIZE) + return 0; + + for (j = 0; j < i; j++) { + pci_free_consistent(priv->pci_dev, + sizeof(struct ipw2100_cmd_header), + priv->msg_buffers[j].info.c_struct.cmd, + priv->msg_buffers[j].info.c_struct. + cmd_phys); + } + + kfree(priv->msg_buffers); + priv->msg_buffers = NULL; + + return err; +} + +static int ipw2100_msg_initialize(struct ipw2100_priv *priv) +{ + int i; + + INIT_LIST_HEAD(&priv->msg_free_list); + INIT_LIST_HEAD(&priv->msg_pend_list); + + for (i = 0; i < IPW_COMMAND_POOL_SIZE; i++) + list_add_tail(&priv->msg_buffers[i].list, &priv->msg_free_list); + SET_STAT(&priv->msg_free_stat, i); + + return 0; +} + +static void ipw2100_msg_free(struct ipw2100_priv *priv) +{ + int i; + + if (!priv->msg_buffers) + return; + + for (i = 0; i < IPW_COMMAND_POOL_SIZE; i++) { + pci_free_consistent(priv->pci_dev, + sizeof(struct ipw2100_cmd_header), + priv->msg_buffers[i].info.c_struct.cmd, + priv->msg_buffers[i].info.c_struct. + cmd_phys); + } + + kfree(priv->msg_buffers); + priv->msg_buffers = NULL; +} + +static ssize_t show_pci(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct pci_dev *pci_dev = container_of(d, struct pci_dev, dev); + char *out = buf; + int i, j; + u32 val; + + for (i = 0; i < 16; i++) { + out += sprintf(out, "[%08X] ", i * 16); + for (j = 0; j < 16; j += 4) { + pci_read_config_dword(pci_dev, i * 16 + j, &val); + out += sprintf(out, "%08X ", val); + } + out += sprintf(out, "\n"); + } + + return out - buf; +} + +static DEVICE_ATTR(pci, S_IRUGO, show_pci, NULL); + +static ssize_t show_cfg(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct ipw2100_priv *p = dev_get_drvdata(d); + return sprintf(buf, "0x%08x\n", (int)p->config); +} + +static DEVICE_ATTR(cfg, S_IRUGO, show_cfg, NULL); + +static ssize_t show_status(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct ipw2100_priv *p = dev_get_drvdata(d); + return sprintf(buf, "0x%08x\n", (int)p->status); +} + +static DEVICE_ATTR(status, S_IRUGO, show_status, NULL); + +static ssize_t show_capability(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct ipw2100_priv *p = dev_get_drvdata(d); + return sprintf(buf, "0x%08x\n", (int)p->capability); +} + +static DEVICE_ATTR(capability, S_IRUGO, show_capability, NULL); + +#define IPW2100_REG(x) { IPW_ ##x, #x } +static const struct { + u32 addr; + const char *name; +} hw_data[] = { +IPW2100_REG(REG_GP_CNTRL), + IPW2100_REG(REG_GPIO), + IPW2100_REG(REG_INTA), + IPW2100_REG(REG_INTA_MASK), IPW2100_REG(REG_RESET_REG),}; +#define IPW2100_NIC(x, s) { x, #x, s } +static const struct { + u32 addr; + const char *name; + size_t size; +} nic_data[] = { +IPW2100_NIC(IPW2100_CONTROL_REG, 2), + IPW2100_NIC(0x210014, 1), IPW2100_NIC(0x210000, 1),}; +#define IPW2100_ORD(x, d) { IPW_ORD_ ##x, #x, d } +static const struct { + u8 index; + const char *name; + const char *desc; +} ord_data[] = { +IPW2100_ORD(STAT_TX_HOST_REQUESTS, "requested Host Tx's (MSDU)"), + IPW2100_ORD(STAT_TX_HOST_COMPLETE, + "successful Host Tx's (MSDU)"), + IPW2100_ORD(STAT_TX_DIR_DATA, + "successful Directed Tx's (MSDU)"), + IPW2100_ORD(STAT_TX_DIR_DATA1, + "successful Directed Tx's (MSDU) @ 1MB"), + IPW2100_ORD(STAT_TX_DIR_DATA2, + "successful Directed Tx's (MSDU) @ 2MB"), + IPW2100_ORD(STAT_TX_DIR_DATA5_5, + "successful Directed Tx's (MSDU) @ 5_5MB"), + IPW2100_ORD(STAT_TX_DIR_DATA11, + "successful Directed Tx's (MSDU) @ 11MB"), + IPW2100_ORD(STAT_TX_NODIR_DATA1, + "successful Non_Directed Tx's (MSDU) @ 1MB"), + IPW2100_ORD(STAT_TX_NODIR_DATA2, + "successful Non_Directed Tx's (MSDU) @ 2MB"), + IPW2100_ORD(STAT_TX_NODIR_DATA5_5, + "successful Non_Directed Tx's (MSDU) @ 5.5MB"), + IPW2100_ORD(STAT_TX_NODIR_DATA11, + "successful Non_Directed Tx's (MSDU) @ 11MB"), + IPW2100_ORD(STAT_NULL_DATA, "successful NULL data Tx's"), + IPW2100_ORD(STAT_TX_RTS, "successful Tx RTS"), + IPW2100_ORD(STAT_TX_CTS, "successful Tx CTS"), + IPW2100_ORD(STAT_TX_ACK, "successful Tx ACK"), + IPW2100_ORD(STAT_TX_ASSN, "successful Association Tx's"), + IPW2100_ORD(STAT_TX_ASSN_RESP, + "successful Association response Tx's"), + IPW2100_ORD(STAT_TX_REASSN, + "successful Reassociation Tx's"), + IPW2100_ORD(STAT_TX_REASSN_RESP, + "successful Reassociation response Tx's"), + IPW2100_ORD(STAT_TX_PROBE, + "probes successfully transmitted"), + IPW2100_ORD(STAT_TX_PROBE_RESP, + "probe responses successfully transmitted"), + IPW2100_ORD(STAT_TX_BEACON, "tx beacon"), + IPW2100_ORD(STAT_TX_ATIM, "Tx ATIM"), + IPW2100_ORD(STAT_TX_DISASSN, + "successful Disassociation TX"), + IPW2100_ORD(STAT_TX_AUTH, "successful Authentication Tx"), + IPW2100_ORD(STAT_TX_DEAUTH, + "successful Deauthentication TX"), + IPW2100_ORD(STAT_TX_TOTAL_BYTES, + "Total successful Tx data bytes"), + IPW2100_ORD(STAT_TX_RETRIES, "Tx retries"), + IPW2100_ORD(STAT_TX_RETRY1, "Tx retries at 1MBPS"), + IPW2100_ORD(STAT_TX_RETRY2, "Tx retries at 2MBPS"), + IPW2100_ORD(STAT_TX_RETRY5_5, "Tx retries at 5.5MBPS"), + IPW2100_ORD(STAT_TX_RETRY11, "Tx retries at 11MBPS"), + IPW2100_ORD(STAT_TX_FAILURES, "Tx Failures"), + IPW2100_ORD(STAT_TX_MAX_TRIES_IN_HOP, + "times max tries in a hop failed"), + IPW2100_ORD(STAT_TX_DISASSN_FAIL, + "times disassociation failed"), + IPW2100_ORD(STAT_TX_ERR_CTS, "missed/bad CTS frames"), + IPW2100_ORD(STAT_TX_ERR_ACK, "tx err due to acks"), + IPW2100_ORD(STAT_RX_HOST, "packets passed to host"), + IPW2100_ORD(STAT_RX_DIR_DATA, "directed packets"), + IPW2100_ORD(STAT_RX_DIR_DATA1, "directed packets at 1MB"), + IPW2100_ORD(STAT_RX_DIR_DATA2, "directed packets at 2MB"), + IPW2100_ORD(STAT_RX_DIR_DATA5_5, + "directed packets at 5.5MB"), + IPW2100_ORD(STAT_RX_DIR_DATA11, "directed packets at 11MB"), + IPW2100_ORD(STAT_RX_NODIR_DATA, "nondirected packets"), + IPW2100_ORD(STAT_RX_NODIR_DATA1, + "nondirected packets at 1MB"), + IPW2100_ORD(STAT_RX_NODIR_DATA2, + "nondirected packets at 2MB"), + IPW2100_ORD(STAT_RX_NODIR_DATA5_5, + "nondirected packets at 5.5MB"), + IPW2100_ORD(STAT_RX_NODIR_DATA11, + "nondirected packets at 11MB"), + IPW2100_ORD(STAT_RX_NULL_DATA, "null data rx's"), + IPW2100_ORD(STAT_RX_RTS, "Rx RTS"), IPW2100_ORD(STAT_RX_CTS, + "Rx CTS"), + IPW2100_ORD(STAT_RX_ACK, "Rx ACK"), + IPW2100_ORD(STAT_RX_CFEND, "Rx CF End"), + IPW2100_ORD(STAT_RX_CFEND_ACK, "Rx CF End + CF Ack"), + IPW2100_ORD(STAT_RX_ASSN, "Association Rx's"), + IPW2100_ORD(STAT_RX_ASSN_RESP, "Association response Rx's"), + IPW2100_ORD(STAT_RX_REASSN, "Reassociation Rx's"), + IPW2100_ORD(STAT_RX_REASSN_RESP, + "Reassociation response Rx's"), + IPW2100_ORD(STAT_RX_PROBE, "probe Rx's"), + IPW2100_ORD(STAT_RX_PROBE_RESP, "probe response Rx's"), + IPW2100_ORD(STAT_RX_BEACON, "Rx beacon"), + IPW2100_ORD(STAT_RX_ATIM, "Rx ATIM"), + IPW2100_ORD(STAT_RX_DISASSN, "disassociation Rx"), + IPW2100_ORD(STAT_RX_AUTH, "authentication Rx"), + IPW2100_ORD(STAT_RX_DEAUTH, "deauthentication Rx"), + IPW2100_ORD(STAT_RX_TOTAL_BYTES, + "Total rx data bytes received"), + IPW2100_ORD(STAT_RX_ERR_CRC, "packets with Rx CRC error"), + IPW2100_ORD(STAT_RX_ERR_CRC1, "Rx CRC errors at 1MB"), + IPW2100_ORD(STAT_RX_ERR_CRC2, "Rx CRC errors at 2MB"), + IPW2100_ORD(STAT_RX_ERR_CRC5_5, "Rx CRC errors at 5.5MB"), + IPW2100_ORD(STAT_RX_ERR_CRC11, "Rx CRC errors at 11MB"), + IPW2100_ORD(STAT_RX_DUPLICATE1, + "duplicate rx packets at 1MB"), + IPW2100_ORD(STAT_RX_DUPLICATE2, + "duplicate rx packets at 2MB"), + IPW2100_ORD(STAT_RX_DUPLICATE5_5, + "duplicate rx packets at 5.5MB"), + IPW2100_ORD(STAT_RX_DUPLICATE11, + "duplicate rx packets at 11MB"), + IPW2100_ORD(STAT_RX_DUPLICATE, "duplicate rx packets"), + IPW2100_ORD(PERS_DB_LOCK, "locking fw permanent db"), + IPW2100_ORD(PERS_DB_SIZE, "size of fw permanent db"), + IPW2100_ORD(PERS_DB_ADDR, "address of fw permanent db"), + IPW2100_ORD(STAT_RX_INVALID_PROTOCOL, + "rx frames with invalid protocol"), + IPW2100_ORD(SYS_BOOT_TIME, "Boot time"), + IPW2100_ORD(STAT_RX_NO_BUFFER, + "rx frames rejected due to no buffer"), + IPW2100_ORD(STAT_RX_MISSING_FRAG, + "rx frames dropped due to missing fragment"), + IPW2100_ORD(STAT_RX_ORPHAN_FRAG, + "rx frames dropped due to non-sequential fragment"), + IPW2100_ORD(STAT_RX_ORPHAN_FRAME, + "rx frames dropped due to unmatched 1st frame"), + IPW2100_ORD(STAT_RX_FRAG_AGEOUT, + "rx frames dropped due to uncompleted frame"), + IPW2100_ORD(STAT_RX_ICV_ERRORS, + "ICV errors during decryption"), + IPW2100_ORD(STAT_PSP_SUSPENSION, "times adapter suspended"), + IPW2100_ORD(STAT_PSP_BCN_TIMEOUT, "beacon timeout"), + IPW2100_ORD(STAT_PSP_POLL_TIMEOUT, + "poll response timeouts"), + IPW2100_ORD(STAT_PSP_NONDIR_TIMEOUT, + "timeouts waiting for last {broad,multi}cast pkt"), + IPW2100_ORD(STAT_PSP_RX_DTIMS, "PSP DTIMs received"), + IPW2100_ORD(STAT_PSP_RX_TIMS, "PSP TIMs received"), + IPW2100_ORD(STAT_PSP_STATION_ID, "PSP Station ID"), + IPW2100_ORD(LAST_ASSN_TIME, "RTC time of last association"), + IPW2100_ORD(STAT_PERCENT_MISSED_BCNS, + "current calculation of % missed beacons"), + IPW2100_ORD(STAT_PERCENT_RETRIES, + "current calculation of % missed tx retries"), + IPW2100_ORD(ASSOCIATED_AP_PTR, + "0 if not associated, else pointer to AP table entry"), + IPW2100_ORD(AVAILABLE_AP_CNT, + "AP's decsribed in the AP table"), + IPW2100_ORD(AP_LIST_PTR, "Ptr to list of available APs"), + IPW2100_ORD(STAT_AP_ASSNS, "associations"), + IPW2100_ORD(STAT_ASSN_FAIL, "association failures"), + IPW2100_ORD(STAT_ASSN_RESP_FAIL, + "failures due to response fail"), + IPW2100_ORD(STAT_FULL_SCANS, "full scans"), + IPW2100_ORD(CARD_DISABLED, "Card Disabled"), + IPW2100_ORD(STAT_ROAM_INHIBIT, + "times roaming was inhibited due to activity"), + IPW2100_ORD(RSSI_AT_ASSN, + "RSSI of associated AP at time of association"), + IPW2100_ORD(STAT_ASSN_CAUSE1, + "reassociation: no probe response or TX on hop"), + IPW2100_ORD(STAT_ASSN_CAUSE2, + "reassociation: poor tx/rx quality"), + IPW2100_ORD(STAT_ASSN_CAUSE3, + "reassociation: tx/rx quality (excessive AP load"), + IPW2100_ORD(STAT_ASSN_CAUSE4, + "reassociation: AP RSSI level"), + IPW2100_ORD(STAT_ASSN_CAUSE5, + "reassociations due to load leveling"), + IPW2100_ORD(STAT_AUTH_FAIL, "times authentication failed"), + IPW2100_ORD(STAT_AUTH_RESP_FAIL, + "times authentication response failed"), + IPW2100_ORD(STATION_TABLE_CNT, + "entries in association table"), + IPW2100_ORD(RSSI_AVG_CURR, "Current avg RSSI"), + IPW2100_ORD(POWER_MGMT_MODE, "Power mode - 0=CAM, 1=PSP"), + IPW2100_ORD(COUNTRY_CODE, + "IEEE country code as recv'd from beacon"), + IPW2100_ORD(COUNTRY_CHANNELS, + "channels supported by country"), + IPW2100_ORD(RESET_CNT, "adapter resets (warm)"), + IPW2100_ORD(BEACON_INTERVAL, "Beacon interval"), + IPW2100_ORD(ANTENNA_DIVERSITY, + "TRUE if antenna diversity is disabled"), + IPW2100_ORD(DTIM_PERIOD, "beacon intervals between DTIMs"), + IPW2100_ORD(OUR_FREQ, + "current radio freq lower digits - channel ID"), + IPW2100_ORD(RTC_TIME, "current RTC time"), + IPW2100_ORD(PORT_TYPE, "operating mode"), + IPW2100_ORD(CURRENT_TX_RATE, "current tx rate"), + IPW2100_ORD(SUPPORTED_RATES, "supported tx rates"), + IPW2100_ORD(ATIM_WINDOW, "current ATIM Window"), + IPW2100_ORD(BASIC_RATES, "basic tx rates"), + IPW2100_ORD(NIC_HIGHEST_RATE, "NIC highest tx rate"), + IPW2100_ORD(AP_HIGHEST_RATE, "AP highest tx rate"), + IPW2100_ORD(CAPABILITIES, + "Management frame capability field"), + IPW2100_ORD(AUTH_TYPE, "Type of authentication"), + IPW2100_ORD(RADIO_TYPE, "Adapter card platform type"), + IPW2100_ORD(RTS_THRESHOLD, + "Min packet length for RTS handshaking"), + IPW2100_ORD(INT_MODE, "International mode"), + IPW2100_ORD(FRAGMENTATION_THRESHOLD, + "protocol frag threshold"), + IPW2100_ORD(EEPROM_SRAM_DB_BLOCK_START_ADDRESS, + "EEPROM offset in SRAM"), + IPW2100_ORD(EEPROM_SRAM_DB_BLOCK_SIZE, + "EEPROM size in SRAM"), + IPW2100_ORD(EEPROM_SKU_CAPABILITY, "EEPROM SKU Capability"), + IPW2100_ORD(EEPROM_IBSS_11B_CHANNELS, + "EEPROM IBSS 11b channel set"), + IPW2100_ORD(MAC_VERSION, "MAC Version"), + IPW2100_ORD(MAC_REVISION, "MAC Revision"), + IPW2100_ORD(RADIO_VERSION, "Radio Version"), + IPW2100_ORD(NIC_MANF_DATE_TIME, "MANF Date/Time STAMP"), + IPW2100_ORD(UCODE_VERSION, "Ucode Version"),}; + +static ssize_t show_registers(struct device *d, struct device_attribute *attr, + char *buf) +{ + int i; + struct ipw2100_priv *priv = dev_get_drvdata(d); + struct net_device *dev = priv->net_dev; + char *out = buf; + u32 val = 0; + + out += sprintf(out, "%30s [Address ] : Hex\n", "Register"); + + for (i = 0; i < ARRAY_SIZE(hw_data); i++) { + read_register(dev, hw_data[i].addr, &val); + out += sprintf(out, "%30s [%08X] : %08X\n", + hw_data[i].name, hw_data[i].addr, val); + } + + return out - buf; +} + +static DEVICE_ATTR(registers, S_IRUGO, show_registers, NULL); + +static ssize_t show_hardware(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct ipw2100_priv *priv = dev_get_drvdata(d); + struct net_device *dev = priv->net_dev; + char *out = buf; + int i; + + out += sprintf(out, "%30s [Address ] : Hex\n", "NIC entry"); + + for (i = 0; i < ARRAY_SIZE(nic_data); i++) { + u8 tmp8; + u16 tmp16; + u32 tmp32; + + switch (nic_data[i].size) { + case 1: + read_nic_byte(dev, nic_data[i].addr, &tmp8); + out += sprintf(out, "%30s [%08X] : %02X\n", + nic_data[i].name, nic_data[i].addr, + tmp8); + break; + case 2: + read_nic_word(dev, nic_data[i].addr, &tmp16); + out += sprintf(out, "%30s [%08X] : %04X\n", + nic_data[i].name, nic_data[i].addr, + tmp16); + break; + case 4: + read_nic_dword(dev, nic_data[i].addr, &tmp32); + out += sprintf(out, "%30s [%08X] : %08X\n", + nic_data[i].name, nic_data[i].addr, + tmp32); + break; + } + } + return out - buf; +} + +static DEVICE_ATTR(hardware, S_IRUGO, show_hardware, NULL); + +static ssize_t show_memory(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct ipw2100_priv *priv = dev_get_drvdata(d); + struct net_device *dev = priv->net_dev; + static unsigned long loop = 0; + int len = 0; + u32 buffer[4]; + int i; + char line[81]; + + if (loop >= 0x30000) + loop = 0; + + /* sysfs provides us PAGE_SIZE buffer */ + while (len < PAGE_SIZE - 128 && loop < 0x30000) { + + if (priv->snapshot[0]) + for (i = 0; i < 4; i++) + buffer[i] = + *(u32 *) SNAPSHOT_ADDR(loop + i * 4); + else + for (i = 0; i < 4; i++) + read_nic_dword(dev, loop + i * 4, &buffer[i]); + + if (priv->dump_raw) + len += sprintf(buf + len, + "%c%c%c%c" + "%c%c%c%c" + "%c%c%c%c" + "%c%c%c%c", + ((u8 *) buffer)[0x0], + ((u8 *) buffer)[0x1], + ((u8 *) buffer)[0x2], + ((u8 *) buffer)[0x3], + ((u8 *) buffer)[0x4], + ((u8 *) buffer)[0x5], + ((u8 *) buffer)[0x6], + ((u8 *) buffer)[0x7], + ((u8 *) buffer)[0x8], + ((u8 *) buffer)[0x9], + ((u8 *) buffer)[0xa], + ((u8 *) buffer)[0xb], + ((u8 *) buffer)[0xc], + ((u8 *) buffer)[0xd], + ((u8 *) buffer)[0xe], + ((u8 *) buffer)[0xf]); + else + len += sprintf(buf + len, "%s\n", + snprint_line(line, sizeof(line), + (u8 *) buffer, 16, loop)); + loop += 16; + } + + return len; +} + +static ssize_t store_memory(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ipw2100_priv *priv = dev_get_drvdata(d); + struct net_device *dev = priv->net_dev; + const char *p = buf; + + (void)dev; /* kill unused-var warning for debug-only code */ + + if (count < 1) + return count; + + if (p[0] == '1' || + (count >= 2 && tolower(p[0]) == 'o' && tolower(p[1]) == 'n')) { + IPW_DEBUG_INFO("%s: Setting memory dump to RAW mode.\n", + dev->name); + priv->dump_raw = 1; + + } else if (p[0] == '0' || (count >= 2 && tolower(p[0]) == 'o' && + tolower(p[1]) == 'f')) { + IPW_DEBUG_INFO("%s: Setting memory dump to HEX mode.\n", + dev->name); + priv->dump_raw = 0; + + } else if (tolower(p[0]) == 'r') { + IPW_DEBUG_INFO("%s: Resetting firmware snapshot.\n", dev->name); + ipw2100_snapshot_free(priv); + + } else + IPW_DEBUG_INFO("%s: Usage: 0|on = HEX, 1|off = RAW, " + "reset = clear memory snapshot\n", dev->name); + + return count; +} + +static DEVICE_ATTR(memory, S_IWUSR | S_IRUGO, show_memory, store_memory); + +static ssize_t show_ordinals(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct ipw2100_priv *priv = dev_get_drvdata(d); + u32 val = 0; + int len = 0; + u32 val_len; + static int loop = 0; + + if (priv->status & STATUS_RF_KILL_MASK) + return 0; + + if (loop >= ARRAY_SIZE(ord_data)) + loop = 0; + + /* sysfs provides us PAGE_SIZE buffer */ + while (len < PAGE_SIZE - 128 && loop < ARRAY_SIZE(ord_data)) { + val_len = sizeof(u32); + + if (ipw2100_get_ordinal(priv, ord_data[loop].index, &val, + &val_len)) + len += sprintf(buf + len, "[0x%02X] = ERROR %s\n", + ord_data[loop].index, + ord_data[loop].desc); + else + len += sprintf(buf + len, "[0x%02X] = 0x%08X %s\n", + ord_data[loop].index, val, + ord_data[loop].desc); + loop++; + } + + return len; +} + +static DEVICE_ATTR(ordinals, S_IRUGO, show_ordinals, NULL); + +static ssize_t show_stats(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct ipw2100_priv *priv = dev_get_drvdata(d); + char *out = buf; + + out += sprintf(out, "interrupts: %d {tx: %d, rx: %d, other: %d}\n", + priv->interrupts, priv->tx_interrupts, + priv->rx_interrupts, priv->inta_other); + out += sprintf(out, "firmware resets: %d\n", priv->resets); + out += sprintf(out, "firmware hangs: %d\n", priv->hangs); +#ifdef CPTCFG_IPW2100_DEBUG + out += sprintf(out, "packet mismatch image: %s\n", + priv->snapshot[0] ? "YES" : "NO"); +#endif + + return out - buf; +} + +static DEVICE_ATTR(stats, S_IRUGO, show_stats, NULL); + +static int ipw2100_switch_mode(struct ipw2100_priv *priv, u32 mode) +{ + int err; + + if (mode == priv->ieee->iw_mode) + return 0; + + err = ipw2100_disable_adapter(priv); + if (err) { + printk(KERN_ERR DRV_NAME ": %s: Could not disable adapter %d\n", + priv->net_dev->name, err); + return err; + } + + switch (mode) { + case IW_MODE_INFRA: + priv->net_dev->type = ARPHRD_ETHER; + break; + case IW_MODE_ADHOC: + priv->net_dev->type = ARPHRD_ETHER; + break; +#ifdef CPTCFG_IPW2100_MONITOR + case IW_MODE_MONITOR: + priv->last_mode = priv->ieee->iw_mode; + priv->net_dev->type = ARPHRD_IEEE80211_RADIOTAP; + break; +#endif /* CPTCFG_IPW2100_MONITOR */ + } + + priv->ieee->iw_mode = mode; + +#ifdef CONFIG_PM + /* Indicate ipw2100_download_firmware download firmware + * from disk instead of memory. */ + ipw2100_firmware.version = 0; +#endif + + printk(KERN_INFO "%s: Resetting on mode change.\n", priv->net_dev->name); + priv->reset_backoff = 0; + schedule_reset(priv); + + return 0; +} + +static ssize_t show_internals(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct ipw2100_priv *priv = dev_get_drvdata(d); + int len = 0; + +#define DUMP_VAR(x,y) len += sprintf(buf + len, # x ": %" y "\n", priv-> x) + + if (priv->status & STATUS_ASSOCIATED) + len += sprintf(buf + len, "connected: %lu\n", + get_seconds() - priv->connect_start); + else + len += sprintf(buf + len, "not connected\n"); + + DUMP_VAR(ieee->crypt_info.crypt[priv->ieee->crypt_info.tx_keyidx], "p"); + DUMP_VAR(status, "08lx"); + DUMP_VAR(config, "08lx"); + DUMP_VAR(capability, "08lx"); + + len += + sprintf(buf + len, "last_rtc: %lu\n", + (unsigned long)priv->last_rtc); + + DUMP_VAR(fatal_error, "d"); + DUMP_VAR(stop_hang_check, "d"); + DUMP_VAR(stop_rf_kill, "d"); + DUMP_VAR(messages_sent, "d"); + + DUMP_VAR(tx_pend_stat.value, "d"); + DUMP_VAR(tx_pend_stat.hi, "d"); + + DUMP_VAR(tx_free_stat.value, "d"); + DUMP_VAR(tx_free_stat.lo, "d"); + + DUMP_VAR(msg_free_stat.value, "d"); + DUMP_VAR(msg_free_stat.lo, "d"); + + DUMP_VAR(msg_pend_stat.value, "d"); + DUMP_VAR(msg_pend_stat.hi, "d"); + + DUMP_VAR(fw_pend_stat.value, "d"); + DUMP_VAR(fw_pend_stat.hi, "d"); + + DUMP_VAR(txq_stat.value, "d"); + DUMP_VAR(txq_stat.lo, "d"); + + DUMP_VAR(ieee->scans, "d"); + DUMP_VAR(reset_backoff, "d"); + + return len; +} + +static DEVICE_ATTR(internals, S_IRUGO, show_internals, NULL); + +static ssize_t show_bssinfo(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct ipw2100_priv *priv = dev_get_drvdata(d); + char essid[IW_ESSID_MAX_SIZE + 1]; + u8 bssid[ETH_ALEN]; + u32 chan = 0; + char *out = buf; + unsigned int length; + int ret; + + if (priv->status & STATUS_RF_KILL_MASK) + return 0; + + memset(essid, 0, sizeof(essid)); + memset(bssid, 0, sizeof(bssid)); + + length = IW_ESSID_MAX_SIZE; + ret = ipw2100_get_ordinal(priv, IPW_ORD_STAT_ASSN_SSID, essid, &length); + if (ret) + IPW_DEBUG_INFO("failed querying ordinals at line %d\n", + __LINE__); + + length = sizeof(bssid); + ret = ipw2100_get_ordinal(priv, IPW_ORD_STAT_ASSN_AP_BSSID, + bssid, &length); + if (ret) + IPW_DEBUG_INFO("failed querying ordinals at line %d\n", + __LINE__); + + length = sizeof(u32); + ret = ipw2100_get_ordinal(priv, IPW_ORD_OUR_FREQ, &chan, &length); + if (ret) + IPW_DEBUG_INFO("failed querying ordinals at line %d\n", + __LINE__); + + out += sprintf(out, "ESSID: %s\n", essid); + out += sprintf(out, "BSSID: %pM\n", bssid); + out += sprintf(out, "Channel: %d\n", chan); + + return out - buf; +} + +static DEVICE_ATTR(bssinfo, S_IRUGO, show_bssinfo, NULL); + +#ifdef CPTCFG_IPW2100_DEBUG +static ssize_t show_debug_level(struct device_driver *d, char *buf) +{ + return sprintf(buf, "0x%08X\n", ipw2100_debug_level); +} + +static ssize_t store_debug_level(struct device_driver *d, + const char *buf, size_t count) +{ + u32 val; + int ret; + + ret = kstrtou32(buf, 0, &val); + if (ret) + IPW_DEBUG_INFO(": %s is not in hex or decimal form.\n", buf); + else + ipw2100_debug_level = val; + + return strnlen(buf, count); +} + +static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO, show_debug_level, + store_debug_level); +#endif /* CPTCFG_IPW2100_DEBUG */ + +static ssize_t show_fatal_error(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct ipw2100_priv *priv = dev_get_drvdata(d); + char *out = buf; + int i; + + if (priv->fatal_error) + out += sprintf(out, "0x%08X\n", priv->fatal_error); + else + out += sprintf(out, "0\n"); + + for (i = 1; i <= IPW2100_ERROR_QUEUE; i++) { + if (!priv->fatal_errors[(priv->fatal_index - i) % + IPW2100_ERROR_QUEUE]) + continue; + + out += sprintf(out, "%d. 0x%08X\n", i, + priv->fatal_errors[(priv->fatal_index - i) % + IPW2100_ERROR_QUEUE]); + } + + return out - buf; +} + +static ssize_t store_fatal_error(struct device *d, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct ipw2100_priv *priv = dev_get_drvdata(d); + schedule_reset(priv); + return count; +} + +static DEVICE_ATTR(fatal_error, S_IWUSR | S_IRUGO, show_fatal_error, + store_fatal_error); + +static ssize_t show_scan_age(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct ipw2100_priv *priv = dev_get_drvdata(d); + return sprintf(buf, "%d\n", priv->ieee->scan_age); +} + +static ssize_t store_scan_age(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ipw2100_priv *priv = dev_get_drvdata(d); + struct net_device *dev = priv->net_dev; + unsigned long val; + int ret; + + (void)dev; /* kill unused-var warning for debug-only code */ + + IPW_DEBUG_INFO("enter\n"); + + ret = kstrtoul(buf, 0, &val); + if (ret) { + IPW_DEBUG_INFO("%s: user supplied invalid value.\n", dev->name); + } else { + priv->ieee->scan_age = val; + IPW_DEBUG_INFO("set scan_age = %u\n", priv->ieee->scan_age); + } + + IPW_DEBUG_INFO("exit\n"); + return strnlen(buf, count); +} + +static DEVICE_ATTR(scan_age, S_IWUSR | S_IRUGO, show_scan_age, store_scan_age); + +static ssize_t show_rf_kill(struct device *d, struct device_attribute *attr, + char *buf) +{ + /* 0 - RF kill not enabled + 1 - SW based RF kill active (sysfs) + 2 - HW based RF kill active + 3 - Both HW and SW baed RF kill active */ + struct ipw2100_priv *priv = dev_get_drvdata(d); + int val = ((priv->status & STATUS_RF_KILL_SW) ? 0x1 : 0x0) | + (rf_kill_active(priv) ? 0x2 : 0x0); + return sprintf(buf, "%i\n", val); +} + +static int ipw_radio_kill_sw(struct ipw2100_priv *priv, int disable_radio) +{ + if ((disable_radio ? 1 : 0) == + (priv->status & STATUS_RF_KILL_SW ? 1 : 0)) + return 0; + + IPW_DEBUG_RF_KILL("Manual SW RF Kill set to: RADIO %s\n", + disable_radio ? "OFF" : "ON"); + + mutex_lock(&priv->action_mutex); + + if (disable_radio) { + priv->status |= STATUS_RF_KILL_SW; + ipw2100_down(priv); + } else { + priv->status &= ~STATUS_RF_KILL_SW; + if (rf_kill_active(priv)) { + IPW_DEBUG_RF_KILL("Can not turn radio back on - " + "disabled by HW switch\n"); + /* Make sure the RF_KILL check timer is running */ + priv->stop_rf_kill = 0; + mod_delayed_work(system_wq, &priv->rf_kill, + round_jiffies_relative(HZ)); + } else + schedule_reset(priv); + } + + mutex_unlock(&priv->action_mutex); + return 1; +} + +static ssize_t store_rf_kill(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ipw2100_priv *priv = dev_get_drvdata(d); + ipw_radio_kill_sw(priv, buf[0] == '1'); + return count; +} + +static DEVICE_ATTR(rf_kill, S_IWUSR | S_IRUGO, show_rf_kill, store_rf_kill); + +static struct attribute *ipw2100_sysfs_entries[] = { + &dev_attr_hardware.attr, + &dev_attr_registers.attr, + &dev_attr_ordinals.attr, + &dev_attr_pci.attr, + &dev_attr_stats.attr, + &dev_attr_internals.attr, + &dev_attr_bssinfo.attr, + &dev_attr_memory.attr, + &dev_attr_scan_age.attr, + &dev_attr_fatal_error.attr, + &dev_attr_rf_kill.attr, + &dev_attr_cfg.attr, + &dev_attr_status.attr, + &dev_attr_capability.attr, + NULL, +}; + +static struct attribute_group ipw2100_attribute_group = { + .attrs = ipw2100_sysfs_entries, +}; + +static int status_queue_allocate(struct ipw2100_priv *priv, int entries) +{ + struct ipw2100_status_queue *q = &priv->status_queue; + + IPW_DEBUG_INFO("enter\n"); + + q->size = entries * sizeof(struct ipw2100_status); + q->drv = + (struct ipw2100_status *)pci_alloc_consistent(priv->pci_dev, + q->size, &q->nic); + if (!q->drv) { + IPW_DEBUG_WARNING("Can not allocate status queue.\n"); + return -ENOMEM; + } + + memset(q->drv, 0, q->size); + + IPW_DEBUG_INFO("exit\n"); + + return 0; +} + +static void status_queue_free(struct ipw2100_priv *priv) +{ + IPW_DEBUG_INFO("enter\n"); + + if (priv->status_queue.drv) { + pci_free_consistent(priv->pci_dev, priv->status_queue.size, + priv->status_queue.drv, + priv->status_queue.nic); + priv->status_queue.drv = NULL; + } + + IPW_DEBUG_INFO("exit\n"); +} + +static int bd_queue_allocate(struct ipw2100_priv *priv, + struct ipw2100_bd_queue *q, int entries) +{ + IPW_DEBUG_INFO("enter\n"); + + memset(q, 0, sizeof(struct ipw2100_bd_queue)); + + q->entries = entries; + q->size = entries * sizeof(struct ipw2100_bd); + q->drv = pci_alloc_consistent(priv->pci_dev, q->size, &q->nic); + if (!q->drv) { + IPW_DEBUG_INFO + ("can't allocate shared memory for buffer descriptors\n"); + return -ENOMEM; + } + memset(q->drv, 0, q->size); + + IPW_DEBUG_INFO("exit\n"); + + return 0; +} + +static void bd_queue_free(struct ipw2100_priv *priv, struct ipw2100_bd_queue *q) +{ + IPW_DEBUG_INFO("enter\n"); + + if (!q) + return; + + if (q->drv) { + pci_free_consistent(priv->pci_dev, q->size, q->drv, q->nic); + q->drv = NULL; + } + + IPW_DEBUG_INFO("exit\n"); +} + +static void bd_queue_initialize(struct ipw2100_priv *priv, + struct ipw2100_bd_queue *q, u32 base, u32 size, + u32 r, u32 w) +{ + IPW_DEBUG_INFO("enter\n"); + + IPW_DEBUG_INFO("initializing bd queue at virt=%p, phys=%08x\n", q->drv, + (u32) q->nic); + + write_register(priv->net_dev, base, q->nic); + write_register(priv->net_dev, size, q->entries); + write_register(priv->net_dev, r, q->oldest); + write_register(priv->net_dev, w, q->next); + + IPW_DEBUG_INFO("exit\n"); +} + +static void ipw2100_kill_works(struct ipw2100_priv *priv) +{ + priv->stop_rf_kill = 1; + priv->stop_hang_check = 1; + cancel_delayed_work_sync(&priv->reset_work); + cancel_delayed_work_sync(&priv->security_work); + cancel_delayed_work_sync(&priv->wx_event_work); + cancel_delayed_work_sync(&priv->hang_check); + cancel_delayed_work_sync(&priv->rf_kill); + cancel_delayed_work_sync(&priv->scan_event); +} + +static int ipw2100_tx_allocate(struct ipw2100_priv *priv) +{ + int i, j, err = -EINVAL; + void *v; + dma_addr_t p; + + IPW_DEBUG_INFO("enter\n"); + + err = bd_queue_allocate(priv, &priv->tx_queue, TX_QUEUE_LENGTH); + if (err) { + IPW_DEBUG_ERROR("%s: failed bd_queue_allocate\n", + priv->net_dev->name); + return err; + } + + priv->tx_buffers = kmalloc_array(TX_PENDED_QUEUE_LENGTH, + sizeof(struct ipw2100_tx_packet), + GFP_ATOMIC); + if (!priv->tx_buffers) { + bd_queue_free(priv, &priv->tx_queue); + return -ENOMEM; + } + + for (i = 0; i < TX_PENDED_QUEUE_LENGTH; i++) { + v = pci_alloc_consistent(priv->pci_dev, + sizeof(struct ipw2100_data_header), + &p); + if (!v) { + printk(KERN_ERR DRV_NAME + ": %s: PCI alloc failed for tx " "buffers.\n", + priv->net_dev->name); + err = -ENOMEM; + break; + } + + priv->tx_buffers[i].type = DATA; + priv->tx_buffers[i].info.d_struct.data = + (struct ipw2100_data_header *)v; + priv->tx_buffers[i].info.d_struct.data_phys = p; + priv->tx_buffers[i].info.d_struct.txb = NULL; + } + + if (i == TX_PENDED_QUEUE_LENGTH) + return 0; + + for (j = 0; j < i; j++) { + pci_free_consistent(priv->pci_dev, + sizeof(struct ipw2100_data_header), + priv->tx_buffers[j].info.d_struct.data, + priv->tx_buffers[j].info.d_struct. + data_phys); + } + + kfree(priv->tx_buffers); + priv->tx_buffers = NULL; + + return err; +} + +static void ipw2100_tx_initialize(struct ipw2100_priv *priv) +{ + int i; + + IPW_DEBUG_INFO("enter\n"); + + /* + * reinitialize packet info lists + */ + INIT_LIST_HEAD(&priv->fw_pend_list); + INIT_STAT(&priv->fw_pend_stat); + + /* + * reinitialize lists + */ + INIT_LIST_HEAD(&priv->tx_pend_list); + INIT_LIST_HEAD(&priv->tx_free_list); + INIT_STAT(&priv->tx_pend_stat); + INIT_STAT(&priv->tx_free_stat); + + for (i = 0; i < TX_PENDED_QUEUE_LENGTH; i++) { + /* We simply drop any SKBs that have been queued for + * transmit */ + if (priv->tx_buffers[i].info.d_struct.txb) { + libipw_txb_free(priv->tx_buffers[i].info.d_struct. + txb); + priv->tx_buffers[i].info.d_struct.txb = NULL; + } + + list_add_tail(&priv->tx_buffers[i].list, &priv->tx_free_list); + } + + SET_STAT(&priv->tx_free_stat, i); + + priv->tx_queue.oldest = 0; + priv->tx_queue.available = priv->tx_queue.entries; + priv->tx_queue.next = 0; + INIT_STAT(&priv->txq_stat); + SET_STAT(&priv->txq_stat, priv->tx_queue.available); + + bd_queue_initialize(priv, &priv->tx_queue, + IPW_MEM_HOST_SHARED_TX_QUEUE_BD_BASE, + IPW_MEM_HOST_SHARED_TX_QUEUE_BD_SIZE, + IPW_MEM_HOST_SHARED_TX_QUEUE_READ_INDEX, + IPW_MEM_HOST_SHARED_TX_QUEUE_WRITE_INDEX); + + IPW_DEBUG_INFO("exit\n"); + +} + +static void ipw2100_tx_free(struct ipw2100_priv *priv) +{ + int i; + + IPW_DEBUG_INFO("enter\n"); + + bd_queue_free(priv, &priv->tx_queue); + + if (!priv->tx_buffers) + return; + + for (i = 0; i < TX_PENDED_QUEUE_LENGTH; i++) { + if (priv->tx_buffers[i].info.d_struct.txb) { + libipw_txb_free(priv->tx_buffers[i].info.d_struct. + txb); + priv->tx_buffers[i].info.d_struct.txb = NULL; + } + if (priv->tx_buffers[i].info.d_struct.data) + pci_free_consistent(priv->pci_dev, + sizeof(struct ipw2100_data_header), + priv->tx_buffers[i].info.d_struct. + data, + priv->tx_buffers[i].info.d_struct. + data_phys); + } + + kfree(priv->tx_buffers); + priv->tx_buffers = NULL; + + IPW_DEBUG_INFO("exit\n"); +} + +static int ipw2100_rx_allocate(struct ipw2100_priv *priv) +{ + int i, j, err = -EINVAL; + + IPW_DEBUG_INFO("enter\n"); + + err = bd_queue_allocate(priv, &priv->rx_queue, RX_QUEUE_LENGTH); + if (err) { + IPW_DEBUG_INFO("failed bd_queue_allocate\n"); + return err; + } + + err = status_queue_allocate(priv, RX_QUEUE_LENGTH); + if (err) { + IPW_DEBUG_INFO("failed status_queue_allocate\n"); + bd_queue_free(priv, &priv->rx_queue); + return err; + } + + /* + * allocate packets + */ + priv->rx_buffers = kmalloc(RX_QUEUE_LENGTH * + sizeof(struct ipw2100_rx_packet), + GFP_KERNEL); + if (!priv->rx_buffers) { + IPW_DEBUG_INFO("can't allocate rx packet buffer table\n"); + + bd_queue_free(priv, &priv->rx_queue); + + status_queue_free(priv); + + return -ENOMEM; + } + + for (i = 0; i < RX_QUEUE_LENGTH; i++) { + struct ipw2100_rx_packet *packet = &priv->rx_buffers[i]; + + err = ipw2100_alloc_skb(priv, packet); + if (unlikely(err)) { + err = -ENOMEM; + break; + } + + /* The BD holds the cache aligned address */ + priv->rx_queue.drv[i].host_addr = packet->dma_addr; + priv->rx_queue.drv[i].buf_length = IPW_RX_NIC_BUFFER_LENGTH; + priv->status_queue.drv[i].status_fields = 0; + } + + if (i == RX_QUEUE_LENGTH) + return 0; + + for (j = 0; j < i; j++) { + pci_unmap_single(priv->pci_dev, priv->rx_buffers[j].dma_addr, + sizeof(struct ipw2100_rx_packet), + PCI_DMA_FROMDEVICE); + dev_kfree_skb(priv->rx_buffers[j].skb); + } + + kfree(priv->rx_buffers); + priv->rx_buffers = NULL; + + bd_queue_free(priv, &priv->rx_queue); + + status_queue_free(priv); + + return err; +} + +static void ipw2100_rx_initialize(struct ipw2100_priv *priv) +{ + IPW_DEBUG_INFO("enter\n"); + + priv->rx_queue.oldest = 0; + priv->rx_queue.available = priv->rx_queue.entries - 1; + priv->rx_queue.next = priv->rx_queue.entries - 1; + + INIT_STAT(&priv->rxq_stat); + SET_STAT(&priv->rxq_stat, priv->rx_queue.available); + + bd_queue_initialize(priv, &priv->rx_queue, + IPW_MEM_HOST_SHARED_RX_BD_BASE, + IPW_MEM_HOST_SHARED_RX_BD_SIZE, + IPW_MEM_HOST_SHARED_RX_READ_INDEX, + IPW_MEM_HOST_SHARED_RX_WRITE_INDEX); + + /* set up the status queue */ + write_register(priv->net_dev, IPW_MEM_HOST_SHARED_RX_STATUS_BASE, + priv->status_queue.nic); + + IPW_DEBUG_INFO("exit\n"); +} + +static void ipw2100_rx_free(struct ipw2100_priv *priv) +{ + int i; + + IPW_DEBUG_INFO("enter\n"); + + bd_queue_free(priv, &priv->rx_queue); + status_queue_free(priv); + + if (!priv->rx_buffers) + return; + + for (i = 0; i < RX_QUEUE_LENGTH; i++) { + if (priv->rx_buffers[i].rxp) { + pci_unmap_single(priv->pci_dev, + priv->rx_buffers[i].dma_addr, + sizeof(struct ipw2100_rx), + PCI_DMA_FROMDEVICE); + dev_kfree_skb(priv->rx_buffers[i].skb); + } + } + + kfree(priv->rx_buffers); + priv->rx_buffers = NULL; + + IPW_DEBUG_INFO("exit\n"); +} + +static int ipw2100_read_mac_address(struct ipw2100_priv *priv) +{ + u32 length = ETH_ALEN; + u8 addr[ETH_ALEN]; + + int err; + + err = ipw2100_get_ordinal(priv, IPW_ORD_STAT_ADAPTER_MAC, addr, &length); + if (err) { + IPW_DEBUG_INFO("MAC address read failed\n"); + return -EIO; + } + + memcpy(priv->net_dev->dev_addr, addr, ETH_ALEN); + IPW_DEBUG_INFO("card MAC is %pM\n", priv->net_dev->dev_addr); + + return 0; +} + +/******************************************************************** + * + * Firmware Commands + * + ********************************************************************/ + +static int ipw2100_set_mac_address(struct ipw2100_priv *priv, int batch_mode) +{ + struct host_command cmd = { + .host_command = ADAPTER_ADDRESS, + .host_command_sequence = 0, + .host_command_length = ETH_ALEN + }; + int err; + + IPW_DEBUG_HC("SET_MAC_ADDRESS\n"); + + IPW_DEBUG_INFO("enter\n"); + + if (priv->config & CFG_CUSTOM_MAC) { + memcpy(cmd.host_command_parameters, priv->mac_addr, ETH_ALEN); + memcpy(priv->net_dev->dev_addr, priv->mac_addr, ETH_ALEN); + } else + memcpy(cmd.host_command_parameters, priv->net_dev->dev_addr, + ETH_ALEN); + + err = ipw2100_hw_send_command(priv, &cmd); + + IPW_DEBUG_INFO("exit\n"); + return err; +} + +static int ipw2100_set_port_type(struct ipw2100_priv *priv, u32 port_type, + int batch_mode) +{ + struct host_command cmd = { + .host_command = PORT_TYPE, + .host_command_sequence = 0, + .host_command_length = sizeof(u32) + }; + int err; + + switch (port_type) { + case IW_MODE_INFRA: + cmd.host_command_parameters[0] = IPW_BSS; + break; + case IW_MODE_ADHOC: + cmd.host_command_parameters[0] = IPW_IBSS; + break; + } + + IPW_DEBUG_HC("PORT_TYPE: %s\n", + port_type == IPW_IBSS ? "Ad-Hoc" : "Managed"); + + if (!batch_mode) { + err = ipw2100_disable_adapter(priv); + if (err) { + printk(KERN_ERR DRV_NAME + ": %s: Could not disable adapter %d\n", + priv->net_dev->name, err); + return err; + } + } + + /* send cmd to firmware */ + err = ipw2100_hw_send_command(priv, &cmd); + + if (!batch_mode) + ipw2100_enable_adapter(priv); + + return err; +} + +static int ipw2100_set_channel(struct ipw2100_priv *priv, u32 channel, + int batch_mode) +{ + struct host_command cmd = { + .host_command = CHANNEL, + .host_command_sequence = 0, + .host_command_length = sizeof(u32) + }; + int err; + + cmd.host_command_parameters[0] = channel; + + IPW_DEBUG_HC("CHANNEL: %d\n", channel); + + /* If BSS then we don't support channel selection */ + if (priv->ieee->iw_mode == IW_MODE_INFRA) + return 0; + + if ((channel != 0) && + ((channel < REG_MIN_CHANNEL) || (channel > REG_MAX_CHANNEL))) + return -EINVAL; + + if (!batch_mode) { + err = ipw2100_disable_adapter(priv); + if (err) + return err; + } + + err = ipw2100_hw_send_command(priv, &cmd); + if (err) { + IPW_DEBUG_INFO("Failed to set channel to %d", channel); + return err; + } + + if (channel) + priv->config |= CFG_STATIC_CHANNEL; + else + priv->config &= ~CFG_STATIC_CHANNEL; + + priv->channel = channel; + + if (!batch_mode) { + err = ipw2100_enable_adapter(priv); + if (err) + return err; + } + + return 0; +} + +static int ipw2100_system_config(struct ipw2100_priv *priv, int batch_mode) +{ + struct host_command cmd = { + .host_command = SYSTEM_CONFIG, + .host_command_sequence = 0, + .host_command_length = 12, + }; + u32 ibss_mask, len = sizeof(u32); + int err; + + /* Set system configuration */ + + if (!batch_mode) { + err = ipw2100_disable_adapter(priv); + if (err) + return err; + } + + if (priv->ieee->iw_mode == IW_MODE_ADHOC) + cmd.host_command_parameters[0] |= IPW_CFG_IBSS_AUTO_START; + + cmd.host_command_parameters[0] |= IPW_CFG_IBSS_MASK | + IPW_CFG_BSS_MASK | IPW_CFG_802_1x_ENABLE; + + if (!(priv->config & CFG_LONG_PREAMBLE)) + cmd.host_command_parameters[0] |= IPW_CFG_PREAMBLE_AUTO; + + err = ipw2100_get_ordinal(priv, + IPW_ORD_EEPROM_IBSS_11B_CHANNELS, + &ibss_mask, &len); + if (err) + ibss_mask = IPW_IBSS_11B_DEFAULT_MASK; + + cmd.host_command_parameters[1] = REG_CHANNEL_MASK; + cmd.host_command_parameters[2] = REG_CHANNEL_MASK & ibss_mask; + + /* 11b only */ + /*cmd.host_command_parameters[0] |= DIVERSITY_ANTENNA_A; */ + + err = ipw2100_hw_send_command(priv, &cmd); + if (err) + return err; + +/* If IPv6 is configured in the kernel then we don't want to filter out all + * of the multicast packets as IPv6 needs some. */ +#if !defined(CONFIG_IPV6) && !defined(CONFIG_IPV6_MODULE) + cmd.host_command = ADD_MULTICAST; + cmd.host_command_sequence = 0; + cmd.host_command_length = 0; + + ipw2100_hw_send_command(priv, &cmd); +#endif + if (!batch_mode) { + err = ipw2100_enable_adapter(priv); + if (err) + return err; + } + + return 0; +} + +static int ipw2100_set_tx_rates(struct ipw2100_priv *priv, u32 rate, + int batch_mode) +{ + struct host_command cmd = { + .host_command = BASIC_TX_RATES, + .host_command_sequence = 0, + .host_command_length = 4 + }; + int err; + + cmd.host_command_parameters[0] = rate & TX_RATE_MASK; + + if (!batch_mode) { + err = ipw2100_disable_adapter(priv); + if (err) + return err; + } + + /* Set BASIC TX Rate first */ + ipw2100_hw_send_command(priv, &cmd); + + /* Set TX Rate */ + cmd.host_command = TX_RATES; + ipw2100_hw_send_command(priv, &cmd); + + /* Set MSDU TX Rate */ + cmd.host_command = MSDU_TX_RATES; + ipw2100_hw_send_command(priv, &cmd); + + if (!batch_mode) { + err = ipw2100_enable_adapter(priv); + if (err) + return err; + } + + priv->tx_rates = rate; + + return 0; +} + +static int ipw2100_set_power_mode(struct ipw2100_priv *priv, int power_level) +{ + struct host_command cmd = { + .host_command = POWER_MODE, + .host_command_sequence = 0, + .host_command_length = 4 + }; + int err; + + cmd.host_command_parameters[0] = power_level; + + err = ipw2100_hw_send_command(priv, &cmd); + if (err) + return err; + + if (power_level == IPW_POWER_MODE_CAM) + priv->power_mode = IPW_POWER_LEVEL(priv->power_mode); + else + priv->power_mode = IPW_POWER_ENABLED | power_level; + +#ifdef IPW2100_TX_POWER + if (priv->port_type == IBSS && priv->adhoc_power != DFTL_IBSS_TX_POWER) { + /* Set beacon interval */ + cmd.host_command = TX_POWER_INDEX; + cmd.host_command_parameters[0] = (u32) priv->adhoc_power; + + err = ipw2100_hw_send_command(priv, &cmd); + if (err) + return err; + } +#endif + + return 0; +} + +static int ipw2100_set_rts_threshold(struct ipw2100_priv *priv, u32 threshold) +{ + struct host_command cmd = { + .host_command = RTS_THRESHOLD, + .host_command_sequence = 0, + .host_command_length = 4 + }; + int err; + + if (threshold & RTS_DISABLED) + cmd.host_command_parameters[0] = MAX_RTS_THRESHOLD; + else + cmd.host_command_parameters[0] = threshold & ~RTS_DISABLED; + + err = ipw2100_hw_send_command(priv, &cmd); + if (err) + return err; + + priv->rts_threshold = threshold; + + return 0; +} + +#if 0 +int ipw2100_set_fragmentation_threshold(struct ipw2100_priv *priv, + u32 threshold, int batch_mode) +{ + struct host_command cmd = { + .host_command = FRAG_THRESHOLD, + .host_command_sequence = 0, + .host_command_length = 4, + .host_command_parameters[0] = 0, + }; + int err; + + if (!batch_mode) { + err = ipw2100_disable_adapter(priv); + if (err) + return err; + } + + if (threshold == 0) + threshold = DEFAULT_FRAG_THRESHOLD; + else { + threshold = max(threshold, MIN_FRAG_THRESHOLD); + threshold = min(threshold, MAX_FRAG_THRESHOLD); + } + + cmd.host_command_parameters[0] = threshold; + + IPW_DEBUG_HC("FRAG_THRESHOLD: %u\n", threshold); + + err = ipw2100_hw_send_command(priv, &cmd); + + if (!batch_mode) + ipw2100_enable_adapter(priv); + + if (!err) + priv->frag_threshold = threshold; + + return err; +} +#endif + +static int ipw2100_set_short_retry(struct ipw2100_priv *priv, u32 retry) +{ + struct host_command cmd = { + .host_command = SHORT_RETRY_LIMIT, + .host_command_sequence = 0, + .host_command_length = 4 + }; + int err; + + cmd.host_command_parameters[0] = retry; + + err = ipw2100_hw_send_command(priv, &cmd); + if (err) + return err; + + priv->short_retry_limit = retry; + + return 0; +} + +static int ipw2100_set_long_retry(struct ipw2100_priv *priv, u32 retry) +{ + struct host_command cmd = { + .host_command = LONG_RETRY_LIMIT, + .host_command_sequence = 0, + .host_command_length = 4 + }; + int err; + + cmd.host_command_parameters[0] = retry; + + err = ipw2100_hw_send_command(priv, &cmd); + if (err) + return err; + + priv->long_retry_limit = retry; + + return 0; +} + +static int ipw2100_set_mandatory_bssid(struct ipw2100_priv *priv, u8 * bssid, + int batch_mode) +{ + struct host_command cmd = { + .host_command = MANDATORY_BSSID, + .host_command_sequence = 0, + .host_command_length = (bssid == NULL) ? 0 : ETH_ALEN + }; + int err; + +#ifdef CPTCFG_IPW2100_DEBUG + if (bssid != NULL) + IPW_DEBUG_HC("MANDATORY_BSSID: %pM\n", bssid); + else + IPW_DEBUG_HC("MANDATORY_BSSID: \n"); +#endif + /* if BSSID is empty then we disable mandatory bssid mode */ + if (bssid != NULL) + memcpy(cmd.host_command_parameters, bssid, ETH_ALEN); + + if (!batch_mode) { + err = ipw2100_disable_adapter(priv); + if (err) + return err; + } + + err = ipw2100_hw_send_command(priv, &cmd); + + if (!batch_mode) + ipw2100_enable_adapter(priv); + + return err; +} + +static int ipw2100_disassociate_bssid(struct ipw2100_priv *priv) +{ + struct host_command cmd = { + .host_command = DISASSOCIATION_BSSID, + .host_command_sequence = 0, + .host_command_length = ETH_ALEN + }; + int err; + int len; + + IPW_DEBUG_HC("DISASSOCIATION_BSSID\n"); + + len = ETH_ALEN; + /* The Firmware currently ignores the BSSID and just disassociates from + * the currently associated AP -- but in the off chance that a future + * firmware does use the BSSID provided here, we go ahead and try and + * set it to the currently associated AP's BSSID */ + memcpy(cmd.host_command_parameters, priv->bssid, ETH_ALEN); + + err = ipw2100_hw_send_command(priv, &cmd); + + return err; +} + +static int ipw2100_set_wpa_ie(struct ipw2100_priv *, + struct ipw2100_wpa_assoc_frame *, int) + __attribute__ ((unused)); + +static int ipw2100_set_wpa_ie(struct ipw2100_priv *priv, + struct ipw2100_wpa_assoc_frame *wpa_frame, + int batch_mode) +{ + struct host_command cmd = { + .host_command = SET_WPA_IE, + .host_command_sequence = 0, + .host_command_length = sizeof(struct ipw2100_wpa_assoc_frame), + }; + int err; + + IPW_DEBUG_HC("SET_WPA_IE\n"); + + if (!batch_mode) { + err = ipw2100_disable_adapter(priv); + if (err) + return err; + } + + memcpy(cmd.host_command_parameters, wpa_frame, + sizeof(struct ipw2100_wpa_assoc_frame)); + + err = ipw2100_hw_send_command(priv, &cmd); + + if (!batch_mode) { + if (ipw2100_enable_adapter(priv)) + err = -EIO; + } + + return err; +} + +struct security_info_params { + u32 allowed_ciphers; + u16 version; + u8 auth_mode; + u8 replay_counters_number; + u8 unicast_using_group; +} __packed; + +static int ipw2100_set_security_information(struct ipw2100_priv *priv, + int auth_mode, + int security_level, + int unicast_using_group, + int batch_mode) +{ + struct host_command cmd = { + .host_command = SET_SECURITY_INFORMATION, + .host_command_sequence = 0, + .host_command_length = sizeof(struct security_info_params) + }; + struct security_info_params *security = + (struct security_info_params *)&cmd.host_command_parameters; + int err; + memset(security, 0, sizeof(*security)); + + /* If shared key AP authentication is turned on, then we need to + * configure the firmware to try and use it. + * + * Actual data encryption/decryption is handled by the host. */ + security->auth_mode = auth_mode; + security->unicast_using_group = unicast_using_group; + + switch (security_level) { + default: + case SEC_LEVEL_0: + security->allowed_ciphers = IPW_NONE_CIPHER; + break; + case SEC_LEVEL_1: + security->allowed_ciphers = IPW_WEP40_CIPHER | + IPW_WEP104_CIPHER; + break; + case SEC_LEVEL_2: + security->allowed_ciphers = IPW_WEP40_CIPHER | + IPW_WEP104_CIPHER | IPW_TKIP_CIPHER; + break; + case SEC_LEVEL_2_CKIP: + security->allowed_ciphers = IPW_WEP40_CIPHER | + IPW_WEP104_CIPHER | IPW_CKIP_CIPHER; + break; + case SEC_LEVEL_3: + security->allowed_ciphers = IPW_WEP40_CIPHER | + IPW_WEP104_CIPHER | IPW_TKIP_CIPHER | IPW_CCMP_CIPHER; + break; + } + + IPW_DEBUG_HC + ("SET_SECURITY_INFORMATION: auth:%d cipher:0x%02X (level %d)\n", + security->auth_mode, security->allowed_ciphers, security_level); + + security->replay_counters_number = 0; + + if (!batch_mode) { + err = ipw2100_disable_adapter(priv); + if (err) + return err; + } + + err = ipw2100_hw_send_command(priv, &cmd); + + if (!batch_mode) + ipw2100_enable_adapter(priv); + + return err; +} + +static int ipw2100_set_tx_power(struct ipw2100_priv *priv, u32 tx_power) +{ + struct host_command cmd = { + .host_command = TX_POWER_INDEX, + .host_command_sequence = 0, + .host_command_length = 4 + }; + int err = 0; + u32 tmp = tx_power; + + if (tx_power != IPW_TX_POWER_DEFAULT) + tmp = (tx_power - IPW_TX_POWER_MIN_DBM) * 16 / + (IPW_TX_POWER_MAX_DBM - IPW_TX_POWER_MIN_DBM); + + cmd.host_command_parameters[0] = tmp; + + if (priv->ieee->iw_mode == IW_MODE_ADHOC) + err = ipw2100_hw_send_command(priv, &cmd); + if (!err) + priv->tx_power = tx_power; + + return 0; +} + +static int ipw2100_set_ibss_beacon_interval(struct ipw2100_priv *priv, + u32 interval, int batch_mode) +{ + struct host_command cmd = { + .host_command = BEACON_INTERVAL, + .host_command_sequence = 0, + .host_command_length = 4 + }; + int err; + + cmd.host_command_parameters[0] = interval; + + IPW_DEBUG_INFO("enter\n"); + + if (priv->ieee->iw_mode == IW_MODE_ADHOC) { + if (!batch_mode) { + err = ipw2100_disable_adapter(priv); + if (err) + return err; + } + + ipw2100_hw_send_command(priv, &cmd); + + if (!batch_mode) { + err = ipw2100_enable_adapter(priv); + if (err) + return err; + } + } + + IPW_DEBUG_INFO("exit\n"); + + return 0; +} + +static void ipw2100_queues_initialize(struct ipw2100_priv *priv) +{ + ipw2100_tx_initialize(priv); + ipw2100_rx_initialize(priv); + ipw2100_msg_initialize(priv); +} + +static void ipw2100_queues_free(struct ipw2100_priv *priv) +{ + ipw2100_tx_free(priv); + ipw2100_rx_free(priv); + ipw2100_msg_free(priv); +} + +static int ipw2100_queues_allocate(struct ipw2100_priv *priv) +{ + if (ipw2100_tx_allocate(priv) || + ipw2100_rx_allocate(priv) || ipw2100_msg_allocate(priv)) + goto fail; + + return 0; + + fail: + ipw2100_tx_free(priv); + ipw2100_rx_free(priv); + ipw2100_msg_free(priv); + return -ENOMEM; +} + +#define IPW_PRIVACY_CAPABLE 0x0008 + +static int ipw2100_set_wep_flags(struct ipw2100_priv *priv, u32 flags, + int batch_mode) +{ + struct host_command cmd = { + .host_command = WEP_FLAGS, + .host_command_sequence = 0, + .host_command_length = 4 + }; + int err; + + cmd.host_command_parameters[0] = flags; + + IPW_DEBUG_HC("WEP_FLAGS: flags = 0x%08X\n", flags); + + if (!batch_mode) { + err = ipw2100_disable_adapter(priv); + if (err) { + printk(KERN_ERR DRV_NAME + ": %s: Could not disable adapter %d\n", + priv->net_dev->name, err); + return err; + } + } + + /* send cmd to firmware */ + err = ipw2100_hw_send_command(priv, &cmd); + + if (!batch_mode) + ipw2100_enable_adapter(priv); + + return err; +} + +struct ipw2100_wep_key { + u8 idx; + u8 len; + u8 key[13]; +}; + +/* Macros to ease up priting WEP keys */ +#define WEP_FMT_64 "%02X%02X%02X%02X-%02X" +#define WEP_FMT_128 "%02X%02X%02X%02X-%02X%02X%02X%02X-%02X%02X%02X" +#define WEP_STR_64(x) x[0],x[1],x[2],x[3],x[4] +#define WEP_STR_128(x) x[0],x[1],x[2],x[3],x[4],x[5],x[6],x[7],x[8],x[9],x[10] + +/** + * Set a the wep key + * + * @priv: struct to work on + * @idx: index of the key we want to set + * @key: ptr to the key data to set + * @len: length of the buffer at @key + * @batch_mode: FIXME perform the operation in batch mode, not + * disabling the device. + * + * @returns 0 if OK, < 0 errno code on error. + * + * Fill out a command structure with the new wep key, length an + * index and send it down the wire. + */ +static int ipw2100_set_key(struct ipw2100_priv *priv, + int idx, char *key, int len, int batch_mode) +{ + int keylen = len ? (len <= 5 ? 5 : 13) : 0; + struct host_command cmd = { + .host_command = WEP_KEY_INFO, + .host_command_sequence = 0, + .host_command_length = sizeof(struct ipw2100_wep_key), + }; + struct ipw2100_wep_key *wep_key = (void *)cmd.host_command_parameters; + int err; + + IPW_DEBUG_HC("WEP_KEY_INFO: index = %d, len = %d/%d\n", + idx, keylen, len); + + /* NOTE: We don't check cached values in case the firmware was reset + * or some other problem is occurring. If the user is setting the key, + * then we push the change */ + + wep_key->idx = idx; + wep_key->len = keylen; + + if (keylen) { + memcpy(wep_key->key, key, len); + memset(wep_key->key + len, 0, keylen - len); + } + + /* Will be optimized out on debug not being configured in */ + if (keylen == 0) + IPW_DEBUG_WEP("%s: Clearing key %d\n", + priv->net_dev->name, wep_key->idx); + else if (keylen == 5) + IPW_DEBUG_WEP("%s: idx: %d, len: %d key: " WEP_FMT_64 "\n", + priv->net_dev->name, wep_key->idx, wep_key->len, + WEP_STR_64(wep_key->key)); + else + IPW_DEBUG_WEP("%s: idx: %d, len: %d key: " WEP_FMT_128 + "\n", + priv->net_dev->name, wep_key->idx, wep_key->len, + WEP_STR_128(wep_key->key)); + + if (!batch_mode) { + err = ipw2100_disable_adapter(priv); + /* FIXME: IPG: shouldn't this prink be in _disable_adapter()? */ + if (err) { + printk(KERN_ERR DRV_NAME + ": %s: Could not disable adapter %d\n", + priv->net_dev->name, err); + return err; + } + } + + /* send cmd to firmware */ + err = ipw2100_hw_send_command(priv, &cmd); + + if (!batch_mode) { + int err2 = ipw2100_enable_adapter(priv); + if (err == 0) + err = err2; + } + return err; +} + +static int ipw2100_set_key_index(struct ipw2100_priv *priv, + int idx, int batch_mode) +{ + struct host_command cmd = { + .host_command = WEP_KEY_INDEX, + .host_command_sequence = 0, + .host_command_length = 4, + .host_command_parameters = {idx}, + }; + int err; + + IPW_DEBUG_HC("WEP_KEY_INDEX: index = %d\n", idx); + + if (idx < 0 || idx > 3) + return -EINVAL; + + if (!batch_mode) { + err = ipw2100_disable_adapter(priv); + if (err) { + printk(KERN_ERR DRV_NAME + ": %s: Could not disable adapter %d\n", + priv->net_dev->name, err); + return err; + } + } + + /* send cmd to firmware */ + err = ipw2100_hw_send_command(priv, &cmd); + + if (!batch_mode) + ipw2100_enable_adapter(priv); + + return err; +} + +static int ipw2100_configure_security(struct ipw2100_priv *priv, int batch_mode) +{ + int i, err, auth_mode, sec_level, use_group; + + if (!(priv->status & STATUS_RUNNING)) + return 0; + + if (!batch_mode) { + err = ipw2100_disable_adapter(priv); + if (err) + return err; + } + + if (!priv->ieee->sec.enabled) { + err = + ipw2100_set_security_information(priv, IPW_AUTH_OPEN, + SEC_LEVEL_0, 0, 1); + } else { + auth_mode = IPW_AUTH_OPEN; + if (priv->ieee->sec.flags & SEC_AUTH_MODE) { + if (priv->ieee->sec.auth_mode == WLAN_AUTH_SHARED_KEY) + auth_mode = IPW_AUTH_SHARED; + else if (priv->ieee->sec.auth_mode == WLAN_AUTH_LEAP) + auth_mode = IPW_AUTH_LEAP_CISCO_ID; + } + + sec_level = SEC_LEVEL_0; + if (priv->ieee->sec.flags & SEC_LEVEL) + sec_level = priv->ieee->sec.level; + + use_group = 0; + if (priv->ieee->sec.flags & SEC_UNICAST_GROUP) + use_group = priv->ieee->sec.unicast_uses_group; + + err = + ipw2100_set_security_information(priv, auth_mode, sec_level, + use_group, 1); + } + + if (err) + goto exit; + + if (priv->ieee->sec.enabled) { + for (i = 0; i < 4; i++) { + if (!(priv->ieee->sec.flags & (1 << i))) { + memset(priv->ieee->sec.keys[i], 0, WEP_KEY_LEN); + priv->ieee->sec.key_sizes[i] = 0; + } else { + err = ipw2100_set_key(priv, i, + priv->ieee->sec.keys[i], + priv->ieee->sec. + key_sizes[i], 1); + if (err) + goto exit; + } + } + + ipw2100_set_key_index(priv, priv->ieee->crypt_info.tx_keyidx, 1); + } + + /* Always enable privacy so the Host can filter WEP packets if + * encrypted data is sent up */ + err = + ipw2100_set_wep_flags(priv, + priv->ieee->sec. + enabled ? IPW_PRIVACY_CAPABLE : 0, 1); + if (err) + goto exit; + + priv->status &= ~STATUS_SECURITY_UPDATED; + + exit: + if (!batch_mode) + ipw2100_enable_adapter(priv); + + return err; +} + +static void ipw2100_security_work(struct work_struct *work) +{ + struct ipw2100_priv *priv = + container_of(work, struct ipw2100_priv, security_work.work); + + /* If we happen to have reconnected before we get a chance to + * process this, then update the security settings--which causes + * a disassociation to occur */ + if (!(priv->status & STATUS_ASSOCIATED) && + priv->status & STATUS_SECURITY_UPDATED) + ipw2100_configure_security(priv, 0); +} + +static void shim__set_security(struct net_device *dev, + struct libipw_security *sec) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + int i, force_update = 0; + + mutex_lock(&priv->action_mutex); + if (!(priv->status & STATUS_INITIALIZED)) + goto done; + + for (i = 0; i < 4; i++) { + if (sec->flags & (1 << i)) { + priv->ieee->sec.key_sizes[i] = sec->key_sizes[i]; + if (sec->key_sizes[i] == 0) + priv->ieee->sec.flags &= ~(1 << i); + else + memcpy(priv->ieee->sec.keys[i], sec->keys[i], + sec->key_sizes[i]); + if (sec->level == SEC_LEVEL_1) { + priv->ieee->sec.flags |= (1 << i); + priv->status |= STATUS_SECURITY_UPDATED; + } else + priv->ieee->sec.flags &= ~(1 << i); + } + } + + if ((sec->flags & SEC_ACTIVE_KEY) && + priv->ieee->sec.active_key != sec->active_key) { + if (sec->active_key <= 3) { + priv->ieee->sec.active_key = sec->active_key; + priv->ieee->sec.flags |= SEC_ACTIVE_KEY; + } else + priv->ieee->sec.flags &= ~SEC_ACTIVE_KEY; + + priv->status |= STATUS_SECURITY_UPDATED; + } + + if ((sec->flags & SEC_AUTH_MODE) && + (priv->ieee->sec.auth_mode != sec->auth_mode)) { + priv->ieee->sec.auth_mode = sec->auth_mode; + priv->ieee->sec.flags |= SEC_AUTH_MODE; + priv->status |= STATUS_SECURITY_UPDATED; + } + + if (sec->flags & SEC_ENABLED && priv->ieee->sec.enabled != sec->enabled) { + priv->ieee->sec.flags |= SEC_ENABLED; + priv->ieee->sec.enabled = sec->enabled; + priv->status |= STATUS_SECURITY_UPDATED; + force_update = 1; + } + + if (sec->flags & SEC_ENCRYPT) + priv->ieee->sec.encrypt = sec->encrypt; + + if (sec->flags & SEC_LEVEL && priv->ieee->sec.level != sec->level) { + priv->ieee->sec.level = sec->level; + priv->ieee->sec.flags |= SEC_LEVEL; + priv->status |= STATUS_SECURITY_UPDATED; + } + + IPW_DEBUG_WEP("Security flags: %c %c%c%c%c %c%c%c%c\n", + priv->ieee->sec.flags & (1 << 8) ? '1' : '0', + priv->ieee->sec.flags & (1 << 7) ? '1' : '0', + priv->ieee->sec.flags & (1 << 6) ? '1' : '0', + priv->ieee->sec.flags & (1 << 5) ? '1' : '0', + priv->ieee->sec.flags & (1 << 4) ? '1' : '0', + priv->ieee->sec.flags & (1 << 3) ? '1' : '0', + priv->ieee->sec.flags & (1 << 2) ? '1' : '0', + priv->ieee->sec.flags & (1 << 1) ? '1' : '0', + priv->ieee->sec.flags & (1 << 0) ? '1' : '0'); + +/* As a temporary work around to enable WPA until we figure out why + * wpa_supplicant toggles the security capability of the driver, which + * forces a disassocation with force_update... + * + * if (force_update || !(priv->status & STATUS_ASSOCIATED))*/ + if (!(priv->status & (STATUS_ASSOCIATED | STATUS_ASSOCIATING))) + ipw2100_configure_security(priv, 0); + done: + mutex_unlock(&priv->action_mutex); +} + +static int ipw2100_adapter_setup(struct ipw2100_priv *priv) +{ + int err; + int batch_mode = 1; + u8 *bssid; + + IPW_DEBUG_INFO("enter\n"); + + err = ipw2100_disable_adapter(priv); + if (err) + return err; +#ifdef CPTCFG_IPW2100_MONITOR + if (priv->ieee->iw_mode == IW_MODE_MONITOR) { + err = ipw2100_set_channel(priv, priv->channel, batch_mode); + if (err) + return err; + + IPW_DEBUG_INFO("exit\n"); + + return 0; + } +#endif /* CPTCFG_IPW2100_MONITOR */ + + err = ipw2100_read_mac_address(priv); + if (err) + return -EIO; + + err = ipw2100_set_mac_address(priv, batch_mode); + if (err) + return err; + + err = ipw2100_set_port_type(priv, priv->ieee->iw_mode, batch_mode); + if (err) + return err; + + if (priv->ieee->iw_mode == IW_MODE_ADHOC) { + err = ipw2100_set_channel(priv, priv->channel, batch_mode); + if (err) + return err; + } + + err = ipw2100_system_config(priv, batch_mode); + if (err) + return err; + + err = ipw2100_set_tx_rates(priv, priv->tx_rates, batch_mode); + if (err) + return err; + + /* Default to power mode OFF */ + err = ipw2100_set_power_mode(priv, IPW_POWER_MODE_CAM); + if (err) + return err; + + err = ipw2100_set_rts_threshold(priv, priv->rts_threshold); + if (err) + return err; + + if (priv->config & CFG_STATIC_BSSID) + bssid = priv->bssid; + else + bssid = NULL; + err = ipw2100_set_mandatory_bssid(priv, bssid, batch_mode); + if (err) + return err; + + if (priv->config & CFG_STATIC_ESSID) + err = ipw2100_set_essid(priv, priv->essid, priv->essid_len, + batch_mode); + else + err = ipw2100_set_essid(priv, NULL, 0, batch_mode); + if (err) + return err; + + err = ipw2100_configure_security(priv, batch_mode); + if (err) + return err; + + if (priv->ieee->iw_mode == IW_MODE_ADHOC) { + err = + ipw2100_set_ibss_beacon_interval(priv, + priv->beacon_interval, + batch_mode); + if (err) + return err; + + err = ipw2100_set_tx_power(priv, priv->tx_power); + if (err) + return err; + } + + /* + err = ipw2100_set_fragmentation_threshold( + priv, priv->frag_threshold, batch_mode); + if (err) + return err; + */ + + IPW_DEBUG_INFO("exit\n"); + + return 0; +} + +/************************************************************************* + * + * EXTERNALLY CALLED METHODS + * + *************************************************************************/ + +/* This method is called by the network layer -- not to be confused with + * ipw2100_set_mac_address() declared above called by this driver (and this + * method as well) to talk to the firmware */ +static int ipw2100_set_address(struct net_device *dev, void *p) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + struct sockaddr *addr = p; + int err = 0; + + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + + mutex_lock(&priv->action_mutex); + + priv->config |= CFG_CUSTOM_MAC; + memcpy(priv->mac_addr, addr->sa_data, ETH_ALEN); + + err = ipw2100_set_mac_address(priv, 0); + if (err) + goto done; + + priv->reset_backoff = 0; + mutex_unlock(&priv->action_mutex); + ipw2100_reset_adapter(&priv->reset_work.work); + return 0; + + done: + mutex_unlock(&priv->action_mutex); + return err; +} + +static int ipw2100_open(struct net_device *dev) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + unsigned long flags; + IPW_DEBUG_INFO("dev->open\n"); + + spin_lock_irqsave(&priv->low_lock, flags); + if (priv->status & STATUS_ASSOCIATED) { + netif_carrier_on(dev); + netif_start_queue(dev); + } + spin_unlock_irqrestore(&priv->low_lock, flags); + + return 0; +} + +static int ipw2100_close(struct net_device *dev) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + unsigned long flags; + struct list_head *element; + struct ipw2100_tx_packet *packet; + + IPW_DEBUG_INFO("enter\n"); + + spin_lock_irqsave(&priv->low_lock, flags); + + if (priv->status & STATUS_ASSOCIATED) + netif_carrier_off(dev); + netif_stop_queue(dev); + + /* Flush the TX queue ... */ + while (!list_empty(&priv->tx_pend_list)) { + element = priv->tx_pend_list.next; + packet = list_entry(element, struct ipw2100_tx_packet, list); + + list_del(element); + DEC_STAT(&priv->tx_pend_stat); + + libipw_txb_free(packet->info.d_struct.txb); + packet->info.d_struct.txb = NULL; + + list_add_tail(element, &priv->tx_free_list); + INC_STAT(&priv->tx_free_stat); + } + spin_unlock_irqrestore(&priv->low_lock, flags); + + IPW_DEBUG_INFO("exit\n"); + + return 0; +} + +/* + * TODO: Fix this function... its just wrong + */ +static void ipw2100_tx_timeout(struct net_device *dev) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + + dev->stats.tx_errors++; + +#ifdef CPTCFG_IPW2100_MONITOR + if (priv->ieee->iw_mode == IW_MODE_MONITOR) + return; +#endif + + IPW_DEBUG_INFO("%s: TX timed out. Scheduling firmware restart.\n", + dev->name); + schedule_reset(priv); +} + +static int ipw2100_wpa_enable(struct ipw2100_priv *priv, int value) +{ + /* This is called when wpa_supplicant loads and closes the driver + * interface. */ + priv->ieee->wpa_enabled = value; + return 0; +} + +static int ipw2100_wpa_set_auth_algs(struct ipw2100_priv *priv, int value) +{ + + struct libipw_device *ieee = priv->ieee; + struct libipw_security sec = { + .flags = SEC_AUTH_MODE, + }; + int ret = 0; + + if (value & IW_AUTH_ALG_SHARED_KEY) { + sec.auth_mode = WLAN_AUTH_SHARED_KEY; + ieee->open_wep = 0; + } else if (value & IW_AUTH_ALG_OPEN_SYSTEM) { + sec.auth_mode = WLAN_AUTH_OPEN; + ieee->open_wep = 1; + } else if (value & IW_AUTH_ALG_LEAP) { + sec.auth_mode = WLAN_AUTH_LEAP; + ieee->open_wep = 1; + } else + return -EINVAL; + + if (ieee->set_security) + ieee->set_security(ieee->dev, &sec); + else + ret = -EOPNOTSUPP; + + return ret; +} + +static void ipw2100_wpa_assoc_frame(struct ipw2100_priv *priv, + char *wpa_ie, int wpa_ie_len) +{ + + struct ipw2100_wpa_assoc_frame frame; + + frame.fixed_ie_mask = 0; + + /* copy WPA IE */ + memcpy(frame.var_ie, wpa_ie, wpa_ie_len); + frame.var_ie_len = wpa_ie_len; + + /* make sure WPA is enabled */ + ipw2100_wpa_enable(priv, 1); + ipw2100_set_wpa_ie(priv, &frame, 0); +} + +static void ipw_ethtool_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + char fw_ver[64], ucode_ver[64]; + + strlcpy(info->driver, DRV_NAME, sizeof(info->driver)); + strlcpy(info->version, DRV_VERSION, sizeof(info->version)); + + ipw2100_get_fwversion(priv, fw_ver, sizeof(fw_ver)); + ipw2100_get_ucodeversion(priv, ucode_ver, sizeof(ucode_ver)); + + snprintf(info->fw_version, sizeof(info->fw_version), "%s:%d:%s", + fw_ver, priv->eeprom_version, ucode_ver); + + strlcpy(info->bus_info, pci_name(priv->pci_dev), + sizeof(info->bus_info)); +} + +static u32 ipw2100_ethtool_get_link(struct net_device *dev) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + return (priv->status & STATUS_ASSOCIATED) ? 1 : 0; +} + +static const struct ethtool_ops ipw2100_ethtool_ops = { + .get_link = ipw2100_ethtool_get_link, + .get_drvinfo = ipw_ethtool_get_drvinfo, +}; + +static void ipw2100_hang_check(struct work_struct *work) +{ + struct ipw2100_priv *priv = + container_of(work, struct ipw2100_priv, hang_check.work); + unsigned long flags; + u32 rtc = 0xa5a5a5a5; + u32 len = sizeof(rtc); + int restart = 0; + + spin_lock_irqsave(&priv->low_lock, flags); + + if (priv->fatal_error != 0) { + /* If fatal_error is set then we need to restart */ + IPW_DEBUG_INFO("%s: Hardware fatal error detected.\n", + priv->net_dev->name); + + restart = 1; + } else if (ipw2100_get_ordinal(priv, IPW_ORD_RTC_TIME, &rtc, &len) || + (rtc == priv->last_rtc)) { + /* Check if firmware is hung */ + IPW_DEBUG_INFO("%s: Firmware RTC stalled.\n", + priv->net_dev->name); + + restart = 1; + } + + if (restart) { + /* Kill timer */ + priv->stop_hang_check = 1; + priv->hangs++; + + /* Restart the NIC */ + schedule_reset(priv); + } + + priv->last_rtc = rtc; + + if (!priv->stop_hang_check) + schedule_delayed_work(&priv->hang_check, HZ / 2); + + spin_unlock_irqrestore(&priv->low_lock, flags); +} + +static void ipw2100_rf_kill(struct work_struct *work) +{ + struct ipw2100_priv *priv = + container_of(work, struct ipw2100_priv, rf_kill.work); + unsigned long flags; + + spin_lock_irqsave(&priv->low_lock, flags); + + if (rf_kill_active(priv)) { + IPW_DEBUG_RF_KILL("RF Kill active, rescheduling GPIO check\n"); + if (!priv->stop_rf_kill) + schedule_delayed_work(&priv->rf_kill, + round_jiffies_relative(HZ)); + goto exit_unlock; + } + + /* RF Kill is now disabled, so bring the device back up */ + + if (!(priv->status & STATUS_RF_KILL_MASK)) { + IPW_DEBUG_RF_KILL("HW RF Kill no longer active, restarting " + "device\n"); + schedule_reset(priv); + } else + IPW_DEBUG_RF_KILL("HW RF Kill deactivated. SW RF Kill still " + "enabled\n"); + + exit_unlock: + spin_unlock_irqrestore(&priv->low_lock, flags); +} + +static void ipw2100_irq_tasklet(struct ipw2100_priv *priv); + +static const struct net_device_ops ipw2100_netdev_ops = { + .ndo_open = ipw2100_open, + .ndo_stop = ipw2100_close, + .ndo_start_xmit = libipw_xmit, + .ndo_change_mtu = libipw_change_mtu, + .ndo_tx_timeout = ipw2100_tx_timeout, + .ndo_set_mac_address = ipw2100_set_address, + .ndo_validate_addr = eth_validate_addr, +}; + +/* Look into using netdev destructor to shutdown libipw? */ + +static struct net_device *ipw2100_alloc_device(struct pci_dev *pci_dev, + void __iomem * ioaddr) +{ + struct ipw2100_priv *priv; + struct net_device *dev; + + dev = alloc_libipw(sizeof(struct ipw2100_priv), 0); + if (!dev) + return NULL; + priv = libipw_priv(dev); + priv->ieee = netdev_priv(dev); + priv->pci_dev = pci_dev; + priv->net_dev = dev; + priv->ioaddr = ioaddr; + + priv->ieee->hard_start_xmit = ipw2100_tx; + priv->ieee->set_security = shim__set_security; + + priv->ieee->perfect_rssi = -20; + priv->ieee->worst_rssi = -85; + + netdev_attach_ops(dev, &ipw2100_netdev_ops); + dev->ethtool_ops = &ipw2100_ethtool_ops; + dev->wireless_handlers = &ipw2100_wx_handler_def; +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,31)) + priv->wireless_data.libipw = priv->ieee; +#else + priv->wireless_data.ieee80211 = (struct ieee80211_device *) priv->ieee; +#endif + dev->wireless_data = &priv->wireless_data; + dev->watchdog_timeo = 3 * HZ; + dev->irq = 0; + + /* NOTE: We don't use the wireless_handlers hook + * in dev as the system will start throwing WX requests + * to us before we're actually initialized and it just + * ends up causing problems. So, we just handle + * the WX extensions through the ipw2100_ioctl interface */ + + /* memset() puts everything to 0, so we only have explicitly set + * those values that need to be something else */ + + /* If power management is turned on, default to AUTO mode */ + priv->power_mode = IPW_POWER_AUTO; + +#ifdef CPTCFG_IPW2100_MONITOR + priv->config |= CFG_CRC_CHECK; +#endif + priv->ieee->wpa_enabled = 0; + priv->ieee->drop_unencrypted = 0; + priv->ieee->privacy_invoked = 0; + priv->ieee->ieee802_1x = 1; + + /* Set module parameters */ + switch (network_mode) { + case 1: + priv->ieee->iw_mode = IW_MODE_ADHOC; + break; +#ifdef CPTCFG_IPW2100_MONITOR + case 2: + priv->ieee->iw_mode = IW_MODE_MONITOR; + break; +#endif + default: + case 0: + priv->ieee->iw_mode = IW_MODE_INFRA; + break; + } + + if (disable == 1) + priv->status |= STATUS_RF_KILL_SW; + + if (channel != 0 && + ((channel >= REG_MIN_CHANNEL) && (channel <= REG_MAX_CHANNEL))) { + priv->config |= CFG_STATIC_CHANNEL; + priv->channel = channel; + } + + if (associate) + priv->config |= CFG_ASSOCIATE; + + priv->beacon_interval = DEFAULT_BEACON_INTERVAL; + priv->short_retry_limit = DEFAULT_SHORT_RETRY_LIMIT; + priv->long_retry_limit = DEFAULT_LONG_RETRY_LIMIT; + priv->rts_threshold = DEFAULT_RTS_THRESHOLD | RTS_DISABLED; + priv->frag_threshold = DEFAULT_FTS | FRAG_DISABLED; + priv->tx_power = IPW_TX_POWER_DEFAULT; + priv->tx_rates = DEFAULT_TX_RATES; + + strcpy(priv->nick, "ipw2100"); + + spin_lock_init(&priv->low_lock); + mutex_init(&priv->action_mutex); + mutex_init(&priv->adapter_mutex); + + init_waitqueue_head(&priv->wait_command_queue); + + netif_carrier_off(dev); + + INIT_LIST_HEAD(&priv->msg_free_list); + INIT_LIST_HEAD(&priv->msg_pend_list); + INIT_STAT(&priv->msg_free_stat); + INIT_STAT(&priv->msg_pend_stat); + + INIT_LIST_HEAD(&priv->tx_free_list); + INIT_LIST_HEAD(&priv->tx_pend_list); + INIT_STAT(&priv->tx_free_stat); + INIT_STAT(&priv->tx_pend_stat); + + INIT_LIST_HEAD(&priv->fw_pend_list); + INIT_STAT(&priv->fw_pend_stat); + + INIT_DELAYED_WORK(&priv->reset_work, ipw2100_reset_adapter); + INIT_DELAYED_WORK(&priv->security_work, ipw2100_security_work); + INIT_DELAYED_WORK(&priv->wx_event_work, ipw2100_wx_event_work); + INIT_DELAYED_WORK(&priv->hang_check, ipw2100_hang_check); + INIT_DELAYED_WORK(&priv->rf_kill, ipw2100_rf_kill); + INIT_DELAYED_WORK(&priv->scan_event, ipw2100_scan_event); + + tasklet_init(&priv->irq_tasklet, (void (*)(unsigned long)) + ipw2100_irq_tasklet, (unsigned long)priv); + + /* NOTE: We do not start the deferred work for status checks yet */ + priv->stop_rf_kill = 1; + priv->stop_hang_check = 1; + + return dev; +} + +static int ipw2100_pci_init_one(struct pci_dev *pci_dev, + const struct pci_device_id *ent) +{ + void __iomem *ioaddr; + struct net_device *dev = NULL; + struct ipw2100_priv *priv = NULL; + int err = 0; + int registered = 0; + u32 val; + + IPW_DEBUG_INFO("enter\n"); + + if (!(pci_resource_flags(pci_dev, 0) & IORESOURCE_MEM)) { + IPW_DEBUG_INFO("weird - resource type is not memory\n"); + err = -ENODEV; + goto out; + } + + ioaddr = pci_iomap(pci_dev, 0, 0); + if (!ioaddr) { + printk(KERN_WARNING DRV_NAME + "Error calling ioremap_nocache.\n"); + err = -EIO; + goto fail; + } + + /* allocate and initialize our net_device */ + dev = ipw2100_alloc_device(pci_dev, ioaddr); + if (!dev) { + printk(KERN_WARNING DRV_NAME + "Error calling ipw2100_alloc_device.\n"); + err = -ENOMEM; + goto fail; + } + + /* set up PCI mappings for device */ + err = pci_enable_device(pci_dev); + if (err) { + printk(KERN_WARNING DRV_NAME + "Error calling pci_enable_device.\n"); + return err; + } + + priv = libipw_priv(dev); + + pci_set_master(pci_dev); + pci_set_drvdata(pci_dev, priv); + + err = pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32)); + if (err) { + printk(KERN_WARNING DRV_NAME + "Error calling pci_set_dma_mask.\n"); + pci_disable_device(pci_dev); + return err; + } + + err = pci_request_regions(pci_dev, DRV_NAME); + if (err) { + printk(KERN_WARNING DRV_NAME + "Error calling pci_request_regions.\n"); + pci_disable_device(pci_dev); + return err; + } + + /* We disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state */ + pci_read_config_dword(pci_dev, 0x40, &val); + if ((val & 0x0000ff00) != 0) + pci_write_config_dword(pci_dev, 0x40, val & 0xffff00ff); + + pci_set_power_state(pci_dev, PCI_D0); + + if (!ipw2100_hw_is_adapter_in_system(dev)) { + printk(KERN_WARNING DRV_NAME + "Device not found via register read.\n"); + err = -ENODEV; + goto fail; + } + + SET_NETDEV_DEV(dev, &pci_dev->dev); + + /* Force interrupts to be shut off on the device */ + priv->status |= STATUS_INT_ENABLED; + ipw2100_disable_interrupts(priv); + + /* Allocate and initialize the Tx/Rx queues and lists */ + if (ipw2100_queues_allocate(priv)) { + printk(KERN_WARNING DRV_NAME + "Error calling ipw2100_queues_allocate.\n"); + err = -ENOMEM; + goto fail; + } + ipw2100_queues_initialize(priv); + + err = request_irq(pci_dev->irq, + ipw2100_interrupt, IRQF_SHARED, dev->name, priv); + if (err) { + printk(KERN_WARNING DRV_NAME + "Error calling request_irq: %d.\n", pci_dev->irq); + goto fail; + } + dev->irq = pci_dev->irq; + + IPW_DEBUG_INFO("Attempting to register device...\n"); + + printk(KERN_INFO DRV_NAME + ": Detected Intel PRO/Wireless 2100 Network Connection\n"); + + err = ipw2100_up(priv, 1); + if (err) + goto fail; + + err = ipw2100_wdev_init(dev); + if (err) + goto fail; + registered = 1; + + /* Bring up the interface. Pre 0.46, after we registered the + * network device we would call ipw2100_up. This introduced a race + * condition with newer hotplug configurations (network was coming + * up and making calls before the device was initialized). + */ + err = register_netdev(dev); + if (err) { + printk(KERN_WARNING DRV_NAME + "Error calling register_netdev.\n"); + goto fail; + } + registered = 2; + + mutex_lock(&priv->action_mutex); + + IPW_DEBUG_INFO("%s: Bound to %s\n", dev->name, pci_name(pci_dev)); + + /* perform this after register_netdev so that dev->name is set */ + err = sysfs_create_group(&pci_dev->dev.kobj, &ipw2100_attribute_group); + if (err) + goto fail_unlock; + + /* If the RF Kill switch is disabled, go ahead and complete the + * startup sequence */ + if (!(priv->status & STATUS_RF_KILL_MASK)) { + /* Enable the adapter - sends HOST_COMPLETE */ + if (ipw2100_enable_adapter(priv)) { + printk(KERN_WARNING DRV_NAME + ": %s: failed in call to enable adapter.\n", + priv->net_dev->name); + ipw2100_hw_stop_adapter(priv); + err = -EIO; + goto fail_unlock; + } + + /* Start a scan . . . */ + ipw2100_set_scan_options(priv); + ipw2100_start_scan(priv); + } + + IPW_DEBUG_INFO("exit\n"); + + priv->status |= STATUS_INITIALIZED; + + mutex_unlock(&priv->action_mutex); +out: + return err; + + fail_unlock: + mutex_unlock(&priv->action_mutex); + fail: + if (dev) { + if (registered >= 2) + unregister_netdev(dev); + + if (registered) { + wiphy_unregister(priv->ieee->wdev.wiphy); + kfree(priv->ieee->bg_band.channels); + } + + ipw2100_hw_stop_adapter(priv); + + ipw2100_disable_interrupts(priv); + + if (dev->irq) + free_irq(dev->irq, priv); + + ipw2100_kill_works(priv); + + /* These are safe to call even if they weren't allocated */ + ipw2100_queues_free(priv); + sysfs_remove_group(&pci_dev->dev.kobj, + &ipw2100_attribute_group); + + free_libipw(dev, 0); + pci_set_drvdata(pci_dev, NULL); + } + + pci_iounmap(pci_dev, ioaddr); + + pci_release_regions(pci_dev); + pci_disable_device(pci_dev); + goto out; +} + +static void ipw2100_pci_remove_one(struct pci_dev *pci_dev) +{ + struct ipw2100_priv *priv = pci_get_drvdata(pci_dev); + struct net_device *dev = priv->net_dev; + + mutex_lock(&priv->action_mutex); + + priv->status &= ~STATUS_INITIALIZED; + + sysfs_remove_group(&pci_dev->dev.kobj, &ipw2100_attribute_group); + +#ifdef CONFIG_PM + if (ipw2100_firmware.version) + ipw2100_release_firmware(priv, &ipw2100_firmware); +#endif + /* Take down the hardware */ + ipw2100_down(priv); + + /* Release the mutex so that the network subsystem can + * complete any needed calls into the driver... */ + mutex_unlock(&priv->action_mutex); + + /* Unregister the device first - this results in close() + * being called if the device is open. If we free storage + * first, then close() will crash. + * FIXME: remove the comment above. */ + unregister_netdev(dev); + + ipw2100_kill_works(priv); + + ipw2100_queues_free(priv); + + /* Free potential debugging firmware snapshot */ + ipw2100_snapshot_free(priv); + + free_irq(dev->irq, priv); + + pci_iounmap(pci_dev, priv->ioaddr); + + /* wiphy_unregister needs to be here, before free_libipw */ + wiphy_unregister(priv->ieee->wdev.wiphy); + kfree(priv->ieee->bg_band.channels); + free_libipw(dev, 0); + + pci_release_regions(pci_dev); + pci_disable_device(pci_dev); + + IPW_DEBUG_INFO("exit\n"); +} + +#ifdef CONFIG_PM +static int ipw2100_suspend(struct pci_dev *pci_dev, pm_message_t state) +{ + struct ipw2100_priv *priv = pci_get_drvdata(pci_dev); + struct net_device *dev = priv->net_dev; + + IPW_DEBUG_INFO("%s: Going into suspend...\n", dev->name); + + mutex_lock(&priv->action_mutex); + if (priv->status & STATUS_INITIALIZED) { + /* Take down the device; powers it off, etc. */ + ipw2100_down(priv); + } + + /* Remove the PRESENT state of the device */ + netif_device_detach(dev); + + pci_save_state(pci_dev); + pci_disable_device(pci_dev); + pci_set_power_state(pci_dev, PCI_D3hot); + + priv->suspend_at = get_seconds(); + + mutex_unlock(&priv->action_mutex); + + return 0; +} + +static int ipw2100_resume(struct pci_dev *pci_dev) +{ + struct ipw2100_priv *priv = pci_get_drvdata(pci_dev); + struct net_device *dev = priv->net_dev; + int err; + u32 val; + + if (IPW2100_PM_DISABLED) + return 0; + + mutex_lock(&priv->action_mutex); + + IPW_DEBUG_INFO("%s: Coming out of suspend...\n", dev->name); + + pci_set_power_state(pci_dev, PCI_D0); + err = pci_enable_device(pci_dev); + if (err) { + printk(KERN_ERR "%s: pci_enable_device failed on resume\n", + dev->name); + mutex_unlock(&priv->action_mutex); + return err; + } + pci_restore_state(pci_dev); + + /* + * Suspend/Resume resets the PCI configuration space, so we have to + * re-disable the RETRY_TIMEOUT register (0x41) to keep PCI Tx retries + * from interfering with C3 CPU state. pci_restore_state won't help + * here since it only restores the first 64 bytes pci config header. + */ + pci_read_config_dword(pci_dev, 0x40, &val); + if ((val & 0x0000ff00) != 0) + pci_write_config_dword(pci_dev, 0x40, val & 0xffff00ff); + + /* Set the device back into the PRESENT state; this will also wake + * the queue of needed */ + netif_device_attach(dev); + + priv->suspend_time = get_seconds() - priv->suspend_at; + + /* Bring the device back up */ + if (!(priv->status & STATUS_RF_KILL_SW)) + ipw2100_up(priv, 0); + + mutex_unlock(&priv->action_mutex); + + return 0; +} +#endif + +static void ipw2100_shutdown(struct pci_dev *pci_dev) +{ + struct ipw2100_priv *priv = pci_get_drvdata(pci_dev); + + /* Take down the device; powers it off, etc. */ + ipw2100_down(priv); + + pci_disable_device(pci_dev); +} + +#define IPW2100_DEV_ID(x) { PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, x } + +static DEFINE_PCI_DEVICE_TABLE(ipw2100_pci_id_table) = { + IPW2100_DEV_ID(0x2520), /* IN 2100A mPCI 3A */ + IPW2100_DEV_ID(0x2521), /* IN 2100A mPCI 3B */ + IPW2100_DEV_ID(0x2524), /* IN 2100A mPCI 3B */ + IPW2100_DEV_ID(0x2525), /* IN 2100A mPCI 3B */ + IPW2100_DEV_ID(0x2526), /* IN 2100A mPCI Gen A3 */ + IPW2100_DEV_ID(0x2522), /* IN 2100 mPCI 3B */ + IPW2100_DEV_ID(0x2523), /* IN 2100 mPCI 3A */ + IPW2100_DEV_ID(0x2527), /* IN 2100 mPCI 3B */ + IPW2100_DEV_ID(0x2528), /* IN 2100 mPCI 3B */ + IPW2100_DEV_ID(0x2529), /* IN 2100 mPCI 3B */ + IPW2100_DEV_ID(0x252B), /* IN 2100 mPCI 3A */ + IPW2100_DEV_ID(0x252C), /* IN 2100 mPCI 3A */ + IPW2100_DEV_ID(0x252D), /* IN 2100 mPCI 3A */ + + IPW2100_DEV_ID(0x2550), /* IB 2100A mPCI 3B */ + IPW2100_DEV_ID(0x2551), /* IB 2100 mPCI 3B */ + IPW2100_DEV_ID(0x2553), /* IB 2100 mPCI 3B */ + IPW2100_DEV_ID(0x2554), /* IB 2100 mPCI 3B */ + IPW2100_DEV_ID(0x2555), /* IB 2100 mPCI 3B */ + + IPW2100_DEV_ID(0x2560), /* DE 2100A mPCI 3A */ + IPW2100_DEV_ID(0x2562), /* DE 2100A mPCI 3A */ + IPW2100_DEV_ID(0x2563), /* DE 2100A mPCI 3A */ + IPW2100_DEV_ID(0x2561), /* DE 2100 mPCI 3A */ + IPW2100_DEV_ID(0x2565), /* DE 2100 mPCI 3A */ + IPW2100_DEV_ID(0x2566), /* DE 2100 mPCI 3A */ + IPW2100_DEV_ID(0x2567), /* DE 2100 mPCI 3A */ + + IPW2100_DEV_ID(0x2570), /* GA 2100 mPCI 3B */ + + IPW2100_DEV_ID(0x2580), /* TO 2100A mPCI 3B */ + IPW2100_DEV_ID(0x2582), /* TO 2100A mPCI 3B */ + IPW2100_DEV_ID(0x2583), /* TO 2100A mPCI 3B */ + IPW2100_DEV_ID(0x2581), /* TO 2100 mPCI 3B */ + IPW2100_DEV_ID(0x2585), /* TO 2100 mPCI 3B */ + IPW2100_DEV_ID(0x2586), /* TO 2100 mPCI 3B */ + IPW2100_DEV_ID(0x2587), /* TO 2100 mPCI 3B */ + + IPW2100_DEV_ID(0x2590), /* SO 2100A mPCI 3B */ + IPW2100_DEV_ID(0x2592), /* SO 2100A mPCI 3B */ + IPW2100_DEV_ID(0x2591), /* SO 2100 mPCI 3B */ + IPW2100_DEV_ID(0x2593), /* SO 2100 mPCI 3B */ + IPW2100_DEV_ID(0x2596), /* SO 2100 mPCI 3B */ + IPW2100_DEV_ID(0x2598), /* SO 2100 mPCI 3B */ + + IPW2100_DEV_ID(0x25A0), /* HP 2100 mPCI 3B */ + {0,}, +}; + +MODULE_DEVICE_TABLE(pci, ipw2100_pci_id_table); + +static struct pci_driver ipw2100_pci_driver = { + .name = DRV_NAME, + .id_table = ipw2100_pci_id_table, + .probe = ipw2100_pci_init_one, + .remove = ipw2100_pci_remove_one, +#ifdef CONFIG_PM + .suspend = ipw2100_suspend, + .resume = ipw2100_resume, +#endif + .shutdown = ipw2100_shutdown, +}; + +/** + * Initialize the ipw2100 driver/module + * + * @returns 0 if ok, < 0 errno node con error. + * + * Note: we cannot init the /proc stuff until the PCI driver is there, + * or we risk an unlikely race condition on someone accessing + * uninitialized data in the PCI dev struct through /proc. + */ +static int __init ipw2100_init(void) +{ + int ret; + + printk(KERN_INFO DRV_NAME ": %s, %s\n", DRV_DESCRIPTION, DRV_VERSION); + printk(KERN_INFO DRV_NAME ": %s\n", DRV_COPYRIGHT); + + pm_qos_add_request(&ipw2100_pm_qos_req, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + + ret = pci_register_driver(&ipw2100_pci_driver); + if (ret) + goto out; + +#ifdef CPTCFG_IPW2100_DEBUG + ipw2100_debug_level = debug; + ret = driver_create_file(&ipw2100_pci_driver.driver, + &driver_attr_debug_level); +#endif + +out: + return ret; +} + +/** + * Cleanup ipw2100 driver registration + */ +static void __exit ipw2100_exit(void) +{ + /* FIXME: IPG: check that we have no instances of the devices open */ +#ifdef CPTCFG_IPW2100_DEBUG + driver_remove_file(&ipw2100_pci_driver.driver, + &driver_attr_debug_level); +#endif + pci_unregister_driver(&ipw2100_pci_driver); + pm_qos_remove_request(&ipw2100_pm_qos_req); +} + +module_init(ipw2100_init); +module_exit(ipw2100_exit); + +static int ipw2100_wx_get_name(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + if (!(priv->status & STATUS_ASSOCIATED)) + strcpy(wrqu->name, "unassociated"); + else + snprintf(wrqu->name, IFNAMSIZ, "IEEE 802.11b"); + + IPW_DEBUG_WX("Name: %s\n", wrqu->name); + return 0; +} + +static int ipw2100_wx_set_freq(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + struct iw_freq *fwrq = &wrqu->freq; + int err = 0; + + if (priv->ieee->iw_mode == IW_MODE_INFRA) + return -EOPNOTSUPP; + + mutex_lock(&priv->action_mutex); + if (!(priv->status & STATUS_INITIALIZED)) { + err = -EIO; + goto done; + } + + /* if setting by freq convert to channel */ + if (fwrq->e == 1) { + if ((fwrq->m >= (int)2.412e8 && fwrq->m <= (int)2.487e8)) { + int f = fwrq->m / 100000; + int c = 0; + + while ((c < REG_MAX_CHANNEL) && + (f != ipw2100_frequencies[c])) + c++; + + /* hack to fall through */ + fwrq->e = 0; + fwrq->m = c + 1; + } + } + + if (fwrq->e > 0 || fwrq->m > 1000) { + err = -EOPNOTSUPP; + goto done; + } else { /* Set the channel */ + IPW_DEBUG_WX("SET Freq/Channel -> %d\n", fwrq->m); + err = ipw2100_set_channel(priv, fwrq->m, 0); + } + + done: + mutex_unlock(&priv->action_mutex); + return err; +} + +static int ipw2100_wx_get_freq(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + + wrqu->freq.e = 0; + + /* If we are associated, trying to associate, or have a statically + * configured CHANNEL then return that; otherwise return ANY */ + if (priv->config & CFG_STATIC_CHANNEL || + priv->status & STATUS_ASSOCIATED) + wrqu->freq.m = priv->channel; + else + wrqu->freq.m = 0; + + IPW_DEBUG_WX("GET Freq/Channel -> %d\n", priv->channel); + return 0; + +} + +static int ipw2100_wx_set_mode(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + int err = 0; + + IPW_DEBUG_WX("SET Mode -> %d\n", wrqu->mode); + + if (wrqu->mode == priv->ieee->iw_mode) + return 0; + + mutex_lock(&priv->action_mutex); + if (!(priv->status & STATUS_INITIALIZED)) { + err = -EIO; + goto done; + } + + switch (wrqu->mode) { +#ifdef CPTCFG_IPW2100_MONITOR + case IW_MODE_MONITOR: + err = ipw2100_switch_mode(priv, IW_MODE_MONITOR); + break; +#endif /* CPTCFG_IPW2100_MONITOR */ + case IW_MODE_ADHOC: + err = ipw2100_switch_mode(priv, IW_MODE_ADHOC); + break; + case IW_MODE_INFRA: + case IW_MODE_AUTO: + default: + err = ipw2100_switch_mode(priv, IW_MODE_INFRA); + break; + } + + done: + mutex_unlock(&priv->action_mutex); + return err; +} + +static int ipw2100_wx_get_mode(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + + wrqu->mode = priv->ieee->iw_mode; + IPW_DEBUG_WX("GET Mode -> %d\n", wrqu->mode); + + return 0; +} + +#define POWER_MODES 5 + +/* Values are in microsecond */ +static const s32 timeout_duration[POWER_MODES] = { + 350000, + 250000, + 75000, + 37000, + 25000, +}; + +static const s32 period_duration[POWER_MODES] = { + 400000, + 700000, + 1000000, + 1000000, + 1000000 +}; + +static int ipw2100_wx_get_range(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + struct iw_range *range = (struct iw_range *)extra; + u16 val; + int i, level; + + wrqu->data.length = sizeof(*range); + memset(range, 0, sizeof(*range)); + + /* Let's try to keep this struct in the same order as in + * linux/include/wireless.h + */ + + /* TODO: See what values we can set, and remove the ones we can't + * set, or fill them with some default data. + */ + + /* ~5 Mb/s real (802.11b) */ + range->throughput = 5 * 1000 * 1000; + +// range->sensitivity; /* signal level threshold range */ + + range->max_qual.qual = 100; + /* TODO: Find real max RSSI and stick here */ + range->max_qual.level = 0; + range->max_qual.noise = 0; + range->max_qual.updated = 7; /* Updated all three */ + + range->avg_qual.qual = 70; /* > 8% missed beacons is 'bad' */ + /* TODO: Find real 'good' to 'bad' threshold value for RSSI */ + range->avg_qual.level = 20 + IPW2100_RSSI_TO_DBM; + range->avg_qual.noise = 0; + range->avg_qual.updated = 7; /* Updated all three */ + + range->num_bitrates = RATE_COUNT; + + for (i = 0; i < RATE_COUNT && i < IW_MAX_BITRATES; i++) { + range->bitrate[i] = ipw2100_bg_rates[i].bitrate * 100 * 1000; + } + + range->min_rts = MIN_RTS_THRESHOLD; + range->max_rts = MAX_RTS_THRESHOLD; + range->min_frag = MIN_FRAG_THRESHOLD; + range->max_frag = MAX_FRAG_THRESHOLD; + + range->min_pmp = period_duration[0]; /* Minimal PM period */ + range->max_pmp = period_duration[POWER_MODES - 1]; /* Maximal PM period */ + range->min_pmt = timeout_duration[POWER_MODES - 1]; /* Minimal PM timeout */ + range->max_pmt = timeout_duration[0]; /* Maximal PM timeout */ + + /* How to decode max/min PM period */ + range->pmp_flags = IW_POWER_PERIOD; + /* How to decode max/min PM period */ + range->pmt_flags = IW_POWER_TIMEOUT; + /* What PM options are supported */ + range->pm_capa = IW_POWER_TIMEOUT | IW_POWER_PERIOD; + + range->encoding_size[0] = 5; + range->encoding_size[1] = 13; /* Different token sizes */ + range->num_encoding_sizes = 2; /* Number of entry in the list */ + range->max_encoding_tokens = WEP_KEYS; /* Max number of tokens */ +// range->encoding_login_index; /* token index for login token */ + + if (priv->ieee->iw_mode == IW_MODE_ADHOC) { + range->txpower_capa = IW_TXPOW_DBM; + range->num_txpower = IW_MAX_TXPOWER; + for (i = 0, level = (IPW_TX_POWER_MAX_DBM * 16); + i < IW_MAX_TXPOWER; + i++, level -= + ((IPW_TX_POWER_MAX_DBM - + IPW_TX_POWER_MIN_DBM) * 16) / (IW_MAX_TXPOWER - 1)) + range->txpower[i] = level / 16; + } else { + range->txpower_capa = 0; + range->num_txpower = 0; + } + + /* Set the Wireless Extension versions */ + range->we_version_compiled = WIRELESS_EXT; + range->we_version_source = 18; + +// range->retry_capa; /* What retry options are supported */ +// range->retry_flags; /* How to decode max/min retry limit */ +// range->r_time_flags; /* How to decode max/min retry life */ +// range->min_retry; /* Minimal number of retries */ +// range->max_retry; /* Maximal number of retries */ +// range->min_r_time; /* Minimal retry lifetime */ +// range->max_r_time; /* Maximal retry lifetime */ + + range->num_channels = FREQ_COUNT; + + val = 0; + for (i = 0; i < FREQ_COUNT; i++) { + // TODO: Include only legal frequencies for some countries +// if (local->channel_mask & (1 << i)) { + range->freq[val].i = i + 1; + range->freq[val].m = ipw2100_frequencies[i] * 100000; + range->freq[val].e = 1; + val++; +// } + if (val == IW_MAX_FREQUENCIES) + break; + } + range->num_frequency = val; + + /* Event capability (kernel + driver) */ + range->event_capa[0] = (IW_EVENT_CAPA_K_0 | + IW_EVENT_CAPA_MASK(SIOCGIWAP)); + range->event_capa[1] = IW_EVENT_CAPA_K_1; + + range->enc_capa = IW_ENC_CAPA_WPA | IW_ENC_CAPA_WPA2 | + IW_ENC_CAPA_CIPHER_TKIP | IW_ENC_CAPA_CIPHER_CCMP; + + IPW_DEBUG_WX("GET Range\n"); + + return 0; +} + +static int ipw2100_wx_set_wap(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + int err = 0; + + // sanity checks + if (wrqu->ap_addr.sa_family != ARPHRD_ETHER) + return -EINVAL; + + mutex_lock(&priv->action_mutex); + if (!(priv->status & STATUS_INITIALIZED)) { + err = -EIO; + goto done; + } + + if (is_broadcast_ether_addr(wrqu->ap_addr.sa_data) || + is_zero_ether_addr(wrqu->ap_addr.sa_data)) { + /* we disable mandatory BSSID association */ + IPW_DEBUG_WX("exit - disable mandatory BSSID\n"); + priv->config &= ~CFG_STATIC_BSSID; + err = ipw2100_set_mandatory_bssid(priv, NULL, 0); + goto done; + } + + priv->config |= CFG_STATIC_BSSID; + memcpy(priv->mandatory_bssid_mac, wrqu->ap_addr.sa_data, ETH_ALEN); + + err = ipw2100_set_mandatory_bssid(priv, wrqu->ap_addr.sa_data, 0); + + IPW_DEBUG_WX("SET BSSID -> %pM\n", wrqu->ap_addr.sa_data); + + done: + mutex_unlock(&priv->action_mutex); + return err; +} + +static int ipw2100_wx_get_wap(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + + /* If we are associated, trying to associate, or have a statically + * configured BSSID then return that; otherwise return ANY */ + if (priv->config & CFG_STATIC_BSSID || priv->status & STATUS_ASSOCIATED) { + wrqu->ap_addr.sa_family = ARPHRD_ETHER; + memcpy(wrqu->ap_addr.sa_data, priv->bssid, ETH_ALEN); + } else + memset(wrqu->ap_addr.sa_data, 0, ETH_ALEN); + + IPW_DEBUG_WX("Getting WAP BSSID: %pM\n", wrqu->ap_addr.sa_data); + return 0; +} + +static int ipw2100_wx_set_essid(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + char *essid = ""; /* ANY */ + int length = 0; + int err = 0; + DECLARE_SSID_BUF(ssid); + + mutex_lock(&priv->action_mutex); + if (!(priv->status & STATUS_INITIALIZED)) { + err = -EIO; + goto done; + } + + if (wrqu->essid.flags && wrqu->essid.length) { + length = wrqu->essid.length; + essid = extra; + } + + if (length == 0) { + IPW_DEBUG_WX("Setting ESSID to ANY\n"); + priv->config &= ~CFG_STATIC_ESSID; + err = ipw2100_set_essid(priv, NULL, 0, 0); + goto done; + } + + length = min(length, IW_ESSID_MAX_SIZE); + + priv->config |= CFG_STATIC_ESSID; + + if (priv->essid_len == length && !memcmp(priv->essid, extra, length)) { + IPW_DEBUG_WX("ESSID set to current ESSID.\n"); + err = 0; + goto done; + } + + IPW_DEBUG_WX("Setting ESSID: '%s' (%d)\n", + print_ssid(ssid, essid, length), length); + + priv->essid_len = length; + memcpy(priv->essid, essid, priv->essid_len); + + err = ipw2100_set_essid(priv, essid, length, 0); + + done: + mutex_unlock(&priv->action_mutex); + return err; +} + +static int ipw2100_wx_get_essid(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + DECLARE_SSID_BUF(ssid); + + /* If we are associated, trying to associate, or have a statically + * configured ESSID then return that; otherwise return ANY */ + if (priv->config & CFG_STATIC_ESSID || priv->status & STATUS_ASSOCIATED) { + IPW_DEBUG_WX("Getting essid: '%s'\n", + print_ssid(ssid, priv->essid, priv->essid_len)); + memcpy(extra, priv->essid, priv->essid_len); + wrqu->essid.length = priv->essid_len; + wrqu->essid.flags = 1; /* active */ + } else { + IPW_DEBUG_WX("Getting essid: ANY\n"); + wrqu->essid.length = 0; + wrqu->essid.flags = 0; /* active */ + } + + return 0; +} + +static int ipw2100_wx_set_nick(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + + if (wrqu->data.length > IW_ESSID_MAX_SIZE) + return -E2BIG; + + wrqu->data.length = min((size_t) wrqu->data.length, sizeof(priv->nick)); + memset(priv->nick, 0, sizeof(priv->nick)); + memcpy(priv->nick, extra, wrqu->data.length); + + IPW_DEBUG_WX("SET Nickname -> %s\n", priv->nick); + + return 0; +} + +static int ipw2100_wx_get_nick(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + + wrqu->data.length = strlen(priv->nick); + memcpy(extra, priv->nick, wrqu->data.length); + wrqu->data.flags = 1; /* active */ + + IPW_DEBUG_WX("GET Nickname -> %s\n", extra); + + return 0; +} + +static int ipw2100_wx_set_rate(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + u32 target_rate = wrqu->bitrate.value; + u32 rate; + int err = 0; + + mutex_lock(&priv->action_mutex); + if (!(priv->status & STATUS_INITIALIZED)) { + err = -EIO; + goto done; + } + + rate = 0; + + if (target_rate == 1000000 || + (!wrqu->bitrate.fixed && target_rate > 1000000)) + rate |= TX_RATE_1_MBIT; + if (target_rate == 2000000 || + (!wrqu->bitrate.fixed && target_rate > 2000000)) + rate |= TX_RATE_2_MBIT; + if (target_rate == 5500000 || + (!wrqu->bitrate.fixed && target_rate > 5500000)) + rate |= TX_RATE_5_5_MBIT; + if (target_rate == 11000000 || + (!wrqu->bitrate.fixed && target_rate > 11000000)) + rate |= TX_RATE_11_MBIT; + if (rate == 0) + rate = DEFAULT_TX_RATES; + + err = ipw2100_set_tx_rates(priv, rate, 0); + + IPW_DEBUG_WX("SET Rate -> %04X\n", rate); + done: + mutex_unlock(&priv->action_mutex); + return err; +} + +static int ipw2100_wx_get_rate(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + int val; + unsigned int len = sizeof(val); + int err = 0; + + if (!(priv->status & STATUS_ENABLED) || + priv->status & STATUS_RF_KILL_MASK || + !(priv->status & STATUS_ASSOCIATED)) { + wrqu->bitrate.value = 0; + return 0; + } + + mutex_lock(&priv->action_mutex); + if (!(priv->status & STATUS_INITIALIZED)) { + err = -EIO; + goto done; + } + + err = ipw2100_get_ordinal(priv, IPW_ORD_CURRENT_TX_RATE, &val, &len); + if (err) { + IPW_DEBUG_WX("failed querying ordinals.\n"); + goto done; + } + + switch (val & TX_RATE_MASK) { + case TX_RATE_1_MBIT: + wrqu->bitrate.value = 1000000; + break; + case TX_RATE_2_MBIT: + wrqu->bitrate.value = 2000000; + break; + case TX_RATE_5_5_MBIT: + wrqu->bitrate.value = 5500000; + break; + case TX_RATE_11_MBIT: + wrqu->bitrate.value = 11000000; + break; + default: + wrqu->bitrate.value = 0; + } + + IPW_DEBUG_WX("GET Rate -> %d\n", wrqu->bitrate.value); + + done: + mutex_unlock(&priv->action_mutex); + return err; +} + +static int ipw2100_wx_set_rts(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + int value, err; + + /* Auto RTS not yet supported */ + if (wrqu->rts.fixed == 0) + return -EINVAL; + + mutex_lock(&priv->action_mutex); + if (!(priv->status & STATUS_INITIALIZED)) { + err = -EIO; + goto done; + } + + if (wrqu->rts.disabled) + value = priv->rts_threshold | RTS_DISABLED; + else { + if (wrqu->rts.value < 1 || wrqu->rts.value > 2304) { + err = -EINVAL; + goto done; + } + value = wrqu->rts.value; + } + + err = ipw2100_set_rts_threshold(priv, value); + + IPW_DEBUG_WX("SET RTS Threshold -> 0x%08X\n", value); + done: + mutex_unlock(&priv->action_mutex); + return err; +} + +static int ipw2100_wx_get_rts(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + + wrqu->rts.value = priv->rts_threshold & ~RTS_DISABLED; + wrqu->rts.fixed = 1; /* no auto select */ + + /* If RTS is set to the default value, then it is disabled */ + wrqu->rts.disabled = (priv->rts_threshold & RTS_DISABLED) ? 1 : 0; + + IPW_DEBUG_WX("GET RTS Threshold -> 0x%08X\n", wrqu->rts.value); + + return 0; +} + +static int ipw2100_wx_set_txpow(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + int err = 0, value; + + if (ipw_radio_kill_sw(priv, wrqu->txpower.disabled)) + return -EINPROGRESS; + + if (priv->ieee->iw_mode != IW_MODE_ADHOC) + return 0; + + if ((wrqu->txpower.flags & IW_TXPOW_TYPE) != IW_TXPOW_DBM) + return -EINVAL; + + if (wrqu->txpower.fixed == 0) + value = IPW_TX_POWER_DEFAULT; + else { + if (wrqu->txpower.value < IPW_TX_POWER_MIN_DBM || + wrqu->txpower.value > IPW_TX_POWER_MAX_DBM) + return -EINVAL; + + value = wrqu->txpower.value; + } + + mutex_lock(&priv->action_mutex); + if (!(priv->status & STATUS_INITIALIZED)) { + err = -EIO; + goto done; + } + + err = ipw2100_set_tx_power(priv, value); + + IPW_DEBUG_WX("SET TX Power -> %d\n", value); + + done: + mutex_unlock(&priv->action_mutex); + return err; +} + +static int ipw2100_wx_get_txpow(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + + wrqu->txpower.disabled = (priv->status & STATUS_RF_KILL_MASK) ? 1 : 0; + + if (priv->tx_power == IPW_TX_POWER_DEFAULT) { + wrqu->txpower.fixed = 0; + wrqu->txpower.value = IPW_TX_POWER_MAX_DBM; + } else { + wrqu->txpower.fixed = 1; + wrqu->txpower.value = priv->tx_power; + } + + wrqu->txpower.flags = IW_TXPOW_DBM; + + IPW_DEBUG_WX("GET TX Power -> %d\n", wrqu->txpower.value); + + return 0; +} + +static int ipw2100_wx_set_frag(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + + if (!wrqu->frag.fixed) + return -EINVAL; + + if (wrqu->frag.disabled) { + priv->frag_threshold |= FRAG_DISABLED; + priv->ieee->fts = DEFAULT_FTS; + } else { + if (wrqu->frag.value < MIN_FRAG_THRESHOLD || + wrqu->frag.value > MAX_FRAG_THRESHOLD) + return -EINVAL; + + priv->ieee->fts = wrqu->frag.value & ~0x1; + priv->frag_threshold = priv->ieee->fts; + } + + IPW_DEBUG_WX("SET Frag Threshold -> %d\n", priv->ieee->fts); + + return 0; +} + +static int ipw2100_wx_get_frag(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + wrqu->frag.value = priv->frag_threshold & ~FRAG_DISABLED; + wrqu->frag.fixed = 0; /* no auto select */ + wrqu->frag.disabled = (priv->frag_threshold & FRAG_DISABLED) ? 1 : 0; + + IPW_DEBUG_WX("GET Frag Threshold -> %d\n", wrqu->frag.value); + + return 0; +} + +static int ipw2100_wx_set_retry(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + int err = 0; + + if (wrqu->retry.flags & IW_RETRY_LIFETIME || wrqu->retry.disabled) + return -EINVAL; + + if (!(wrqu->retry.flags & IW_RETRY_LIMIT)) + return 0; + + mutex_lock(&priv->action_mutex); + if (!(priv->status & STATUS_INITIALIZED)) { + err = -EIO; + goto done; + } + + if (wrqu->retry.flags & IW_RETRY_SHORT) { + err = ipw2100_set_short_retry(priv, wrqu->retry.value); + IPW_DEBUG_WX("SET Short Retry Limit -> %d\n", + wrqu->retry.value); + goto done; + } + + if (wrqu->retry.flags & IW_RETRY_LONG) { + err = ipw2100_set_long_retry(priv, wrqu->retry.value); + IPW_DEBUG_WX("SET Long Retry Limit -> %d\n", + wrqu->retry.value); + goto done; + } + + err = ipw2100_set_short_retry(priv, wrqu->retry.value); + if (!err) + err = ipw2100_set_long_retry(priv, wrqu->retry.value); + + IPW_DEBUG_WX("SET Both Retry Limits -> %d\n", wrqu->retry.value); + + done: + mutex_unlock(&priv->action_mutex); + return err; +} + +static int ipw2100_wx_get_retry(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + + wrqu->retry.disabled = 0; /* can't be disabled */ + + if ((wrqu->retry.flags & IW_RETRY_TYPE) == IW_RETRY_LIFETIME) + return -EINVAL; + + if (wrqu->retry.flags & IW_RETRY_LONG) { + wrqu->retry.flags = IW_RETRY_LIMIT | IW_RETRY_LONG; + wrqu->retry.value = priv->long_retry_limit; + } else { + wrqu->retry.flags = + (priv->short_retry_limit != + priv->long_retry_limit) ? + IW_RETRY_LIMIT | IW_RETRY_SHORT : IW_RETRY_LIMIT; + + wrqu->retry.value = priv->short_retry_limit; + } + + IPW_DEBUG_WX("GET Retry -> %d\n", wrqu->retry.value); + + return 0; +} + +static int ipw2100_wx_set_scan(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + int err = 0; + + mutex_lock(&priv->action_mutex); + if (!(priv->status & STATUS_INITIALIZED)) { + err = -EIO; + goto done; + } + + IPW_DEBUG_WX("Initiating scan...\n"); + + priv->user_requested_scan = 1; + if (ipw2100_set_scan_options(priv) || ipw2100_start_scan(priv)) { + IPW_DEBUG_WX("Start scan failed.\n"); + + /* TODO: Mark a scan as pending so when hardware initialized + * a scan starts */ + } + + done: + mutex_unlock(&priv->action_mutex); + return err; +} + +static int ipw2100_wx_get_scan(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + return libipw_wx_get_scan(priv->ieee, info, wrqu, extra); +} + +/* + * Implementation based on code in hostap-driver v0.1.3 hostap_ioctl.c + */ +static int ipw2100_wx_set_encode(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *key) +{ + /* + * No check of STATUS_INITIALIZED required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + return libipw_wx_set_encode(priv->ieee, info, wrqu, key); +} + +static int ipw2100_wx_get_encode(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *key) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + return libipw_wx_get_encode(priv->ieee, info, wrqu, key); +} + +static int ipw2100_wx_set_power(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + int err = 0; + + mutex_lock(&priv->action_mutex); + if (!(priv->status & STATUS_INITIALIZED)) { + err = -EIO; + goto done; + } + + if (wrqu->power.disabled) { + priv->power_mode = IPW_POWER_LEVEL(priv->power_mode); + err = ipw2100_set_power_mode(priv, IPW_POWER_MODE_CAM); + IPW_DEBUG_WX("SET Power Management Mode -> off\n"); + goto done; + } + + switch (wrqu->power.flags & IW_POWER_MODE) { + case IW_POWER_ON: /* If not specified */ + case IW_POWER_MODE: /* If set all mask */ + case IW_POWER_ALL_R: /* If explicitly state all */ + break; + default: /* Otherwise we don't support it */ + IPW_DEBUG_WX("SET PM Mode: %X not supported.\n", + wrqu->power.flags); + err = -EOPNOTSUPP; + goto done; + } + + /* If the user hasn't specified a power management mode yet, default + * to BATTERY */ + priv->power_mode = IPW_POWER_ENABLED | priv->power_mode; + err = ipw2100_set_power_mode(priv, IPW_POWER_LEVEL(priv->power_mode)); + + IPW_DEBUG_WX("SET Power Management Mode -> 0x%02X\n", priv->power_mode); + + done: + mutex_unlock(&priv->action_mutex); + return err; + +} + +static int ipw2100_wx_get_power(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + + if (!(priv->power_mode & IPW_POWER_ENABLED)) + wrqu->power.disabled = 1; + else { + wrqu->power.disabled = 0; + wrqu->power.flags = 0; + } + + IPW_DEBUG_WX("GET Power Management Mode -> %02X\n", priv->power_mode); + + return 0; +} + +/* + * WE-18 WPA support + */ + +/* SIOCSIWGENIE */ +static int ipw2100_wx_set_genie(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + + struct ipw2100_priv *priv = libipw_priv(dev); + struct libipw_device *ieee = priv->ieee; + u8 *buf; + + if (!ieee->wpa_enabled) + return -EOPNOTSUPP; + + if (wrqu->data.length > MAX_WPA_IE_LEN || + (wrqu->data.length && extra == NULL)) + return -EINVAL; + + if (wrqu->data.length) { + buf = kmemdup(extra, wrqu->data.length, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + kfree(ieee->wpa_ie); + ieee->wpa_ie = buf; + ieee->wpa_ie_len = wrqu->data.length; + } else { + kfree(ieee->wpa_ie); + ieee->wpa_ie = NULL; + ieee->wpa_ie_len = 0; + } + + ipw2100_wpa_assoc_frame(priv, ieee->wpa_ie, ieee->wpa_ie_len); + + return 0; +} + +/* SIOCGIWGENIE */ +static int ipw2100_wx_get_genie(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + struct libipw_device *ieee = priv->ieee; + + if (ieee->wpa_ie_len == 0 || ieee->wpa_ie == NULL) { + wrqu->data.length = 0; + return 0; + } + + if (wrqu->data.length < ieee->wpa_ie_len) + return -E2BIG; + + wrqu->data.length = ieee->wpa_ie_len; + memcpy(extra, ieee->wpa_ie, ieee->wpa_ie_len); + + return 0; +} + +/* SIOCSIWAUTH */ +static int ipw2100_wx_set_auth(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + struct libipw_device *ieee = priv->ieee; + struct iw_param *param = &wrqu->param; + struct lib80211_crypt_data *crypt; + unsigned long flags; + int ret = 0; + + switch (param->flags & IW_AUTH_INDEX) { + case IW_AUTH_WPA_VERSION: + case IW_AUTH_CIPHER_PAIRWISE: + case IW_AUTH_CIPHER_GROUP: + case IW_AUTH_KEY_MGMT: + /* + * ipw2200 does not use these parameters + */ + break; + + case IW_AUTH_TKIP_COUNTERMEASURES: + crypt = priv->ieee->crypt_info.crypt[priv->ieee->crypt_info.tx_keyidx]; + if (!crypt || !crypt->ops->set_flags || !crypt->ops->get_flags) + break; + + flags = crypt->ops->get_flags(crypt->priv); + + if (param->value) + flags |= IEEE80211_CRYPTO_TKIP_COUNTERMEASURES; + else + flags &= ~IEEE80211_CRYPTO_TKIP_COUNTERMEASURES; + + crypt->ops->set_flags(flags, crypt->priv); + + break; + + case IW_AUTH_DROP_UNENCRYPTED:{ + /* HACK: + * + * wpa_supplicant calls set_wpa_enabled when the driver + * is loaded and unloaded, regardless of if WPA is being + * used. No other calls are made which can be used to + * determine if encryption will be used or not prior to + * association being expected. If encryption is not being + * used, drop_unencrypted is set to false, else true -- we + * can use this to determine if the CAP_PRIVACY_ON bit should + * be set. + */ + struct libipw_security sec = { + .flags = SEC_ENABLED, + .enabled = param->value, + }; + priv->ieee->drop_unencrypted = param->value; + /* We only change SEC_LEVEL for open mode. Others + * are set by ipw_wpa_set_encryption. + */ + if (!param->value) { + sec.flags |= SEC_LEVEL; + sec.level = SEC_LEVEL_0; + } else { + sec.flags |= SEC_LEVEL; + sec.level = SEC_LEVEL_1; + } + if (priv->ieee->set_security) + priv->ieee->set_security(priv->ieee->dev, &sec); + break; + } + + case IW_AUTH_80211_AUTH_ALG: + ret = ipw2100_wpa_set_auth_algs(priv, param->value); + break; + + case IW_AUTH_WPA_ENABLED: + ret = ipw2100_wpa_enable(priv, param->value); + break; + + case IW_AUTH_RX_UNENCRYPTED_EAPOL: + ieee->ieee802_1x = param->value; + break; + + //case IW_AUTH_ROAMING_CONTROL: + case IW_AUTH_PRIVACY_INVOKED: + ieee->privacy_invoked = param->value; + break; + + default: + return -EOPNOTSUPP; + } + return ret; +} + +/* SIOCGIWAUTH */ +static int ipw2100_wx_get_auth(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + struct libipw_device *ieee = priv->ieee; + struct lib80211_crypt_data *crypt; + struct iw_param *param = &wrqu->param; + int ret = 0; + + switch (param->flags & IW_AUTH_INDEX) { + case IW_AUTH_WPA_VERSION: + case IW_AUTH_CIPHER_PAIRWISE: + case IW_AUTH_CIPHER_GROUP: + case IW_AUTH_KEY_MGMT: + /* + * wpa_supplicant will control these internally + */ + ret = -EOPNOTSUPP; + break; + + case IW_AUTH_TKIP_COUNTERMEASURES: + crypt = priv->ieee->crypt_info.crypt[priv->ieee->crypt_info.tx_keyidx]; + if (!crypt || !crypt->ops->get_flags) { + IPW_DEBUG_WARNING("Can't get TKIP countermeasures: " + "crypt not set!\n"); + break; + } + + param->value = (crypt->ops->get_flags(crypt->priv) & + IEEE80211_CRYPTO_TKIP_COUNTERMEASURES) ? 1 : 0; + + break; + + case IW_AUTH_DROP_UNENCRYPTED: + param->value = ieee->drop_unencrypted; + break; + + case IW_AUTH_80211_AUTH_ALG: + param->value = priv->ieee->sec.auth_mode; + break; + + case IW_AUTH_WPA_ENABLED: + param->value = ieee->wpa_enabled; + break; + + case IW_AUTH_RX_UNENCRYPTED_EAPOL: + param->value = ieee->ieee802_1x; + break; + + case IW_AUTH_ROAMING_CONTROL: + case IW_AUTH_PRIVACY_INVOKED: + param->value = ieee->privacy_invoked; + break; + + default: + return -EOPNOTSUPP; + } + return 0; +} + +/* SIOCSIWENCODEEXT */ +static int ipw2100_wx_set_encodeext(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + return libipw_wx_set_encodeext(priv->ieee, info, wrqu, extra); +} + +/* SIOCGIWENCODEEXT */ +static int ipw2100_wx_get_encodeext(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + return libipw_wx_get_encodeext(priv->ieee, info, wrqu, extra); +} + +/* SIOCSIWMLME */ +static int ipw2100_wx_set_mlme(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + struct iw_mlme *mlme = (struct iw_mlme *)extra; + __le16 reason; + + reason = cpu_to_le16(mlme->reason_code); + + switch (mlme->cmd) { + case IW_MLME_DEAUTH: + // silently ignore + break; + + case IW_MLME_DISASSOC: + ipw2100_disassociate_bssid(priv); + break; + + default: + return -EOPNOTSUPP; + } + return 0; +} + +/* + * + * IWPRIV handlers + * + */ +#ifdef CPTCFG_IPW2100_MONITOR +static int ipw2100_wx_set_promisc(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + int *parms = (int *)extra; + int enable = (parms[0] > 0); + int err = 0; + + mutex_lock(&priv->action_mutex); + if (!(priv->status & STATUS_INITIALIZED)) { + err = -EIO; + goto done; + } + + if (enable) { + if (priv->ieee->iw_mode == IW_MODE_MONITOR) { + err = ipw2100_set_channel(priv, parms[1], 0); + goto done; + } + priv->channel = parms[1]; + err = ipw2100_switch_mode(priv, IW_MODE_MONITOR); + } else { + if (priv->ieee->iw_mode == IW_MODE_MONITOR) + err = ipw2100_switch_mode(priv, priv->last_mode); + } + done: + mutex_unlock(&priv->action_mutex); + return err; +} + +static int ipw2100_wx_reset(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + if (priv->status & STATUS_INITIALIZED) + schedule_reset(priv); + return 0; +} + +#endif + +static int ipw2100_wx_set_powermode(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + int err = 0, mode = *(int *)extra; + + mutex_lock(&priv->action_mutex); + if (!(priv->status & STATUS_INITIALIZED)) { + err = -EIO; + goto done; + } + + if ((mode < 0) || (mode > POWER_MODES)) + mode = IPW_POWER_AUTO; + + if (IPW_POWER_LEVEL(priv->power_mode) != mode) + err = ipw2100_set_power_mode(priv, mode); + done: + mutex_unlock(&priv->action_mutex); + return err; +} + +#define MAX_POWER_STRING 80 +static int ipw2100_wx_get_powermode(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + int level = IPW_POWER_LEVEL(priv->power_mode); + s32 timeout, period; + + if (!(priv->power_mode & IPW_POWER_ENABLED)) { + snprintf(extra, MAX_POWER_STRING, + "Power save level: %d (Off)", level); + } else { + switch (level) { + case IPW_POWER_MODE_CAM: + snprintf(extra, MAX_POWER_STRING, + "Power save level: %d (None)", level); + break; + case IPW_POWER_AUTO: + snprintf(extra, MAX_POWER_STRING, + "Power save level: %d (Auto)", level); + break; + default: + timeout = timeout_duration[level - 1] / 1000; + period = period_duration[level - 1] / 1000; + snprintf(extra, MAX_POWER_STRING, + "Power save level: %d " + "(Timeout %dms, Period %dms)", + level, timeout, period); + } + } + + wrqu->data.length = strlen(extra) + 1; + + return 0; +} + +static int ipw2100_wx_set_preamble(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + int err, mode = *(int *)extra; + + mutex_lock(&priv->action_mutex); + if (!(priv->status & STATUS_INITIALIZED)) { + err = -EIO; + goto done; + } + + if (mode == 1) + priv->config |= CFG_LONG_PREAMBLE; + else if (mode == 0) + priv->config &= ~CFG_LONG_PREAMBLE; + else { + err = -EINVAL; + goto done; + } + + err = ipw2100_system_config(priv, 0); + + done: + mutex_unlock(&priv->action_mutex); + return err; +} + +static int ipw2100_wx_get_preamble(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + + if (priv->config & CFG_LONG_PREAMBLE) + snprintf(wrqu->name, IFNAMSIZ, "long (1)"); + else + snprintf(wrqu->name, IFNAMSIZ, "auto (0)"); + + return 0; +} + +#ifdef CPTCFG_IPW2100_MONITOR +static int ipw2100_wx_set_crc_check(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw2100_priv *priv = libipw_priv(dev); + int err, mode = *(int *)extra; + + mutex_lock(&priv->action_mutex); + if (!(priv->status & STATUS_INITIALIZED)) { + err = -EIO; + goto done; + } + + if (mode == 1) + priv->config |= CFG_CRC_CHECK; + else if (mode == 0) + priv->config &= ~CFG_CRC_CHECK; + else { + err = -EINVAL; + goto done; + } + err = 0; + + done: + mutex_unlock(&priv->action_mutex); + return err; +} + +static int ipw2100_wx_get_crc_check(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* + * This can be called at any time. No action lock required + */ + + struct ipw2100_priv *priv = libipw_priv(dev); + + if (priv->config & CFG_CRC_CHECK) + snprintf(wrqu->name, IFNAMSIZ, "CRC checked (1)"); + else + snprintf(wrqu->name, IFNAMSIZ, "CRC ignored (0)"); + + return 0; +} +#endif /* CPTCFG_IPW2100_MONITOR */ + +static iw_handler ipw2100_wx_handlers[] = { + IW_HANDLER(SIOCGIWNAME, ipw2100_wx_get_name), + IW_HANDLER(SIOCSIWFREQ, ipw2100_wx_set_freq), + IW_HANDLER(SIOCGIWFREQ, ipw2100_wx_get_freq), + IW_HANDLER(SIOCSIWMODE, ipw2100_wx_set_mode), + IW_HANDLER(SIOCGIWMODE, ipw2100_wx_get_mode), + IW_HANDLER(SIOCGIWRANGE, ipw2100_wx_get_range), + IW_HANDLER(SIOCSIWAP, ipw2100_wx_set_wap), + IW_HANDLER(SIOCGIWAP, ipw2100_wx_get_wap), + IW_HANDLER(SIOCSIWMLME, ipw2100_wx_set_mlme), + IW_HANDLER(SIOCSIWSCAN, ipw2100_wx_set_scan), + IW_HANDLER(SIOCGIWSCAN, ipw2100_wx_get_scan), + IW_HANDLER(SIOCSIWESSID, ipw2100_wx_set_essid), + IW_HANDLER(SIOCGIWESSID, ipw2100_wx_get_essid), + IW_HANDLER(SIOCSIWNICKN, ipw2100_wx_set_nick), + IW_HANDLER(SIOCGIWNICKN, ipw2100_wx_get_nick), + IW_HANDLER(SIOCSIWRATE, ipw2100_wx_set_rate), + IW_HANDLER(SIOCGIWRATE, ipw2100_wx_get_rate), + IW_HANDLER(SIOCSIWRTS, ipw2100_wx_set_rts), + IW_HANDLER(SIOCGIWRTS, ipw2100_wx_get_rts), + IW_HANDLER(SIOCSIWFRAG, ipw2100_wx_set_frag), + IW_HANDLER(SIOCGIWFRAG, ipw2100_wx_get_frag), + IW_HANDLER(SIOCSIWTXPOW, ipw2100_wx_set_txpow), + IW_HANDLER(SIOCGIWTXPOW, ipw2100_wx_get_txpow), + IW_HANDLER(SIOCSIWRETRY, ipw2100_wx_set_retry), + IW_HANDLER(SIOCGIWRETRY, ipw2100_wx_get_retry), + IW_HANDLER(SIOCSIWENCODE, ipw2100_wx_set_encode), + IW_HANDLER(SIOCGIWENCODE, ipw2100_wx_get_encode), + IW_HANDLER(SIOCSIWPOWER, ipw2100_wx_set_power), + IW_HANDLER(SIOCGIWPOWER, ipw2100_wx_get_power), + IW_HANDLER(SIOCSIWGENIE, ipw2100_wx_set_genie), + IW_HANDLER(SIOCGIWGENIE, ipw2100_wx_get_genie), + IW_HANDLER(SIOCSIWAUTH, ipw2100_wx_set_auth), + IW_HANDLER(SIOCGIWAUTH, ipw2100_wx_get_auth), + IW_HANDLER(SIOCSIWENCODEEXT, ipw2100_wx_set_encodeext), + IW_HANDLER(SIOCGIWENCODEEXT, ipw2100_wx_get_encodeext), +}; + +#define IPW2100_PRIV_SET_MONITOR SIOCIWFIRSTPRIV +#define IPW2100_PRIV_RESET SIOCIWFIRSTPRIV+1 +#define IPW2100_PRIV_SET_POWER SIOCIWFIRSTPRIV+2 +#define IPW2100_PRIV_GET_POWER SIOCIWFIRSTPRIV+3 +#define IPW2100_PRIV_SET_LONGPREAMBLE SIOCIWFIRSTPRIV+4 +#define IPW2100_PRIV_GET_LONGPREAMBLE SIOCIWFIRSTPRIV+5 +#define IPW2100_PRIV_SET_CRC_CHECK SIOCIWFIRSTPRIV+6 +#define IPW2100_PRIV_GET_CRC_CHECK SIOCIWFIRSTPRIV+7 + +static const struct iw_priv_args ipw2100_private_args[] = { + +#ifdef CPTCFG_IPW2100_MONITOR + { + IPW2100_PRIV_SET_MONITOR, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2, 0, "monitor"}, + { + IPW2100_PRIV_RESET, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 0, 0, "reset"}, +#endif /* CPTCFG_IPW2100_MONITOR */ + + { + IPW2100_PRIV_SET_POWER, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "set_power"}, + { + IPW2100_PRIV_GET_POWER, + 0, IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_POWER_STRING, + "get_power"}, + { + IPW2100_PRIV_SET_LONGPREAMBLE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "set_preamble"}, + { + IPW2100_PRIV_GET_LONGPREAMBLE, + 0, IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | IFNAMSIZ, "get_preamble"}, +#ifdef CPTCFG_IPW2100_MONITOR + { + IPW2100_PRIV_SET_CRC_CHECK, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "set_crc_check"}, + { + IPW2100_PRIV_GET_CRC_CHECK, + 0, IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | IFNAMSIZ, "get_crc_check"}, +#endif /* CPTCFG_IPW2100_MONITOR */ +}; + +static iw_handler ipw2100_private_handler[] = { +#ifdef CPTCFG_IPW2100_MONITOR + ipw2100_wx_set_promisc, + ipw2100_wx_reset, +#else /* CPTCFG_IPW2100_MONITOR */ + NULL, + NULL, +#endif /* CPTCFG_IPW2100_MONITOR */ + ipw2100_wx_set_powermode, + ipw2100_wx_get_powermode, + ipw2100_wx_set_preamble, + ipw2100_wx_get_preamble, +#ifdef CPTCFG_IPW2100_MONITOR + ipw2100_wx_set_crc_check, + ipw2100_wx_get_crc_check, +#else /* CPTCFG_IPW2100_MONITOR */ + NULL, + NULL, +#endif /* CPTCFG_IPW2100_MONITOR */ +}; + +/* + * Get wireless statistics. + * Called by /proc/net/wireless + * Also called by SIOCGIWSTATS + */ +static struct iw_statistics *ipw2100_wx_wireless_stats(struct net_device *dev) +{ + enum { + POOR = 30, + FAIR = 60, + GOOD = 80, + VERY_GOOD = 90, + EXCELLENT = 95, + PERFECT = 100 + }; + int rssi_qual; + int tx_qual; + int beacon_qual; + int quality; + + struct ipw2100_priv *priv = libipw_priv(dev); + struct iw_statistics *wstats; + u32 rssi, tx_retries, missed_beacons, tx_failures; + u32 ord_len = sizeof(u32); + + if (!priv) + return (struct iw_statistics *)NULL; + + wstats = &priv->wstats; + + /* if hw is disabled, then ipw2100_get_ordinal() can't be called. + * ipw2100_wx_wireless_stats seems to be called before fw is + * initialized. STATUS_ASSOCIATED will only be set if the hw is up + * and associated; if not associcated, the values are all meaningless + * anyway, so set them all to NULL and INVALID */ + if (!(priv->status & STATUS_ASSOCIATED)) { + wstats->miss.beacon = 0; + wstats->discard.retries = 0; + wstats->qual.qual = 0; + wstats->qual.level = 0; + wstats->qual.noise = 0; + wstats->qual.updated = 7; + wstats->qual.updated |= IW_QUAL_NOISE_INVALID | + IW_QUAL_QUAL_INVALID | IW_QUAL_LEVEL_INVALID; + return wstats; + } + + if (ipw2100_get_ordinal(priv, IPW_ORD_STAT_PERCENT_MISSED_BCNS, + &missed_beacons, &ord_len)) + goto fail_get_ordinal; + + /* If we don't have a connection the quality and level is 0 */ + if (!(priv->status & STATUS_ASSOCIATED)) { + wstats->qual.qual = 0; + wstats->qual.level = 0; + } else { + if (ipw2100_get_ordinal(priv, IPW_ORD_RSSI_AVG_CURR, + &rssi, &ord_len)) + goto fail_get_ordinal; + wstats->qual.level = rssi + IPW2100_RSSI_TO_DBM; + if (rssi < 10) + rssi_qual = rssi * POOR / 10; + else if (rssi < 15) + rssi_qual = (rssi - 10) * (FAIR - POOR) / 5 + POOR; + else if (rssi < 20) + rssi_qual = (rssi - 15) * (GOOD - FAIR) / 5 + FAIR; + else if (rssi < 30) + rssi_qual = (rssi - 20) * (VERY_GOOD - GOOD) / + 10 + GOOD; + else + rssi_qual = (rssi - 30) * (PERFECT - VERY_GOOD) / + 10 + VERY_GOOD; + + if (ipw2100_get_ordinal(priv, IPW_ORD_STAT_PERCENT_RETRIES, + &tx_retries, &ord_len)) + goto fail_get_ordinal; + + if (tx_retries > 75) + tx_qual = (90 - tx_retries) * POOR / 15; + else if (tx_retries > 70) + tx_qual = (75 - tx_retries) * (FAIR - POOR) / 5 + POOR; + else if (tx_retries > 65) + tx_qual = (70 - tx_retries) * (GOOD - FAIR) / 5 + FAIR; + else if (tx_retries > 50) + tx_qual = (65 - tx_retries) * (VERY_GOOD - GOOD) / + 15 + GOOD; + else + tx_qual = (50 - tx_retries) * + (PERFECT - VERY_GOOD) / 50 + VERY_GOOD; + + if (missed_beacons > 50) + beacon_qual = (60 - missed_beacons) * POOR / 10; + else if (missed_beacons > 40) + beacon_qual = (50 - missed_beacons) * (FAIR - POOR) / + 10 + POOR; + else if (missed_beacons > 32) + beacon_qual = (40 - missed_beacons) * (GOOD - FAIR) / + 18 + FAIR; + else if (missed_beacons > 20) + beacon_qual = (32 - missed_beacons) * + (VERY_GOOD - GOOD) / 20 + GOOD; + else + beacon_qual = (20 - missed_beacons) * + (PERFECT - VERY_GOOD) / 20 + VERY_GOOD; + + quality = min(tx_qual, rssi_qual); + quality = min(beacon_qual, quality); + +#ifdef CPTCFG_IPW2100_DEBUG + if (beacon_qual == quality) + IPW_DEBUG_WX("Quality clamped by Missed Beacons\n"); + else if (tx_qual == quality) + IPW_DEBUG_WX("Quality clamped by Tx Retries\n"); + else if (quality != 100) + IPW_DEBUG_WX("Quality clamped by Signal Strength\n"); + else + IPW_DEBUG_WX("Quality not clamped.\n"); +#endif + + wstats->qual.qual = quality; + wstats->qual.level = rssi + IPW2100_RSSI_TO_DBM; + } + + wstats->qual.noise = 0; + wstats->qual.updated = 7; + wstats->qual.updated |= IW_QUAL_NOISE_INVALID; + + /* FIXME: this is percent and not a # */ + wstats->miss.beacon = missed_beacons; + + if (ipw2100_get_ordinal(priv, IPW_ORD_STAT_TX_FAILURES, + &tx_failures, &ord_len)) + goto fail_get_ordinal; + wstats->discard.retries = tx_failures; + + return wstats; + + fail_get_ordinal: + IPW_DEBUG_WX("failed querying ordinals.\n"); + + return (struct iw_statistics *)NULL; +} + +static struct iw_handler_def ipw2100_wx_handler_def = { + .standard = ipw2100_wx_handlers, + .num_standard = ARRAY_SIZE(ipw2100_wx_handlers), + .num_private = ARRAY_SIZE(ipw2100_private_handler), + .num_private_args = ARRAY_SIZE(ipw2100_private_args), + .private = (iw_handler *) ipw2100_private_handler, + .private_args = (struct iw_priv_args *)ipw2100_private_args, + .get_wireless_stats = ipw2100_wx_wireless_stats, +}; + +static void ipw2100_wx_event_work(struct work_struct *work) +{ + struct ipw2100_priv *priv = + container_of(work, struct ipw2100_priv, wx_event_work.work); + union iwreq_data wrqu; + unsigned int len = ETH_ALEN; + + if (priv->status & STATUS_STOPPING) + return; + + mutex_lock(&priv->action_mutex); + + IPW_DEBUG_WX("enter\n"); + + mutex_unlock(&priv->action_mutex); + + wrqu.ap_addr.sa_family = ARPHRD_ETHER; + + /* Fetch BSSID from the hardware */ + if (!(priv->status & (STATUS_ASSOCIATING | STATUS_ASSOCIATED)) || + priv->status & STATUS_RF_KILL_MASK || + ipw2100_get_ordinal(priv, IPW_ORD_STAT_ASSN_AP_BSSID, + &priv->bssid, &len)) { + memset(wrqu.ap_addr.sa_data, 0, ETH_ALEN); + } else { + /* We now have the BSSID, so can finish setting to the full + * associated state */ + memcpy(wrqu.ap_addr.sa_data, priv->bssid, ETH_ALEN); + memcpy(priv->ieee->bssid, priv->bssid, ETH_ALEN); + priv->status &= ~STATUS_ASSOCIATING; + priv->status |= STATUS_ASSOCIATED; + netif_carrier_on(priv->net_dev); + netif_wake_queue(priv->net_dev); + } + + if (!(priv->status & STATUS_ASSOCIATED)) { + IPW_DEBUG_WX("Configuring ESSID\n"); + mutex_lock(&priv->action_mutex); + /* This is a disassociation event, so kick the firmware to + * look for another AP */ + if (priv->config & CFG_STATIC_ESSID) + ipw2100_set_essid(priv, priv->essid, priv->essid_len, + 0); + else + ipw2100_set_essid(priv, NULL, 0, 0); + mutex_unlock(&priv->action_mutex); + } + + wireless_send_event(priv->net_dev, SIOCGIWAP, &wrqu, NULL); +} + +#define IPW2100_FW_MAJOR_VERSION 1 +#define IPW2100_FW_MINOR_VERSION 3 + +#define IPW2100_FW_MINOR(x) ((x & 0xff) >> 8) +#define IPW2100_FW_MAJOR(x) (x & 0xff) + +#define IPW2100_FW_VERSION ((IPW2100_FW_MINOR_VERSION << 8) | \ + IPW2100_FW_MAJOR_VERSION) + +#define IPW2100_FW_PREFIX "ipw2100-" __stringify(IPW2100_FW_MAJOR_VERSION) \ +"." __stringify(IPW2100_FW_MINOR_VERSION) + +#define IPW2100_FW_NAME(x) IPW2100_FW_PREFIX "" x ".fw" + +/* + +BINARY FIRMWARE HEADER FORMAT + +offset length desc +0 2 version +2 2 mode == 0:BSS,1:IBSS,2:MONITOR +4 4 fw_len +8 4 uc_len +C fw_len firmware data +12 + fw_len uc_len microcode data + +*/ + +struct ipw2100_fw_header { + short version; + short mode; + unsigned int fw_size; + unsigned int uc_size; +} __packed; + +static int ipw2100_mod_firmware_load(struct ipw2100_fw *fw) +{ + struct ipw2100_fw_header *h = + (struct ipw2100_fw_header *)fw->fw_entry->data; + + if (IPW2100_FW_MAJOR(h->version) != IPW2100_FW_MAJOR_VERSION) { + printk(KERN_WARNING DRV_NAME ": Firmware image not compatible " + "(detected version id of %u). " + "See Documentation/networking/README.ipw2100\n", + h->version); + return 1; + } + + fw->version = h->version; + fw->fw.data = fw->fw_entry->data + sizeof(struct ipw2100_fw_header); + fw->fw.size = h->fw_size; + fw->uc.data = fw->fw.data + h->fw_size; + fw->uc.size = h->uc_size; + + return 0; +} + +static int ipw2100_get_firmware(struct ipw2100_priv *priv, + struct ipw2100_fw *fw) +{ + char *fw_name; + int rc; + + IPW_DEBUG_INFO("%s: Using hotplug firmware load.\n", + priv->net_dev->name); + + switch (priv->ieee->iw_mode) { + case IW_MODE_ADHOC: + fw_name = IPW2100_FW_NAME("-i"); + break; +#ifdef CPTCFG_IPW2100_MONITOR + case IW_MODE_MONITOR: + fw_name = IPW2100_FW_NAME("-p"); + break; +#endif + case IW_MODE_INFRA: + default: + fw_name = IPW2100_FW_NAME(""); + break; + } + + rc = request_firmware(&fw->fw_entry, fw_name, &priv->pci_dev->dev); + + if (rc < 0) { + printk(KERN_ERR DRV_NAME ": " + "%s: Firmware '%s' not available or load failed.\n", + priv->net_dev->name, fw_name); + return rc; + } + IPW_DEBUG_INFO("firmware data %p size %zd\n", fw->fw_entry->data, + fw->fw_entry->size); + + ipw2100_mod_firmware_load(fw); + + return 0; +} + +MODULE_FIRMWARE(IPW2100_FW_NAME("-i")); +#ifdef CPTCFG_IPW2100_MONITOR +MODULE_FIRMWARE(IPW2100_FW_NAME("-p")); +#endif +MODULE_FIRMWARE(IPW2100_FW_NAME("")); + +static void ipw2100_release_firmware(struct ipw2100_priv *priv, + struct ipw2100_fw *fw) +{ + fw->version = 0; + release_firmware(fw->fw_entry); + fw->fw_entry = NULL; +} + +static int ipw2100_get_fwversion(struct ipw2100_priv *priv, char *buf, + size_t max) +{ + char ver[MAX_FW_VERSION_LEN]; + u32 len = MAX_FW_VERSION_LEN; + u32 tmp; + int i; + /* firmware version is an ascii string (max len of 14) */ + if (ipw2100_get_ordinal(priv, IPW_ORD_STAT_FW_VER_NUM, ver, &len)) + return -EIO; + tmp = max; + if (len >= max) + len = max - 1; + for (i = 0; i < len; i++) + buf[i] = ver[i]; + buf[i] = '\0'; + return tmp; +} + +static int ipw2100_get_ucodeversion(struct ipw2100_priv *priv, char *buf, + size_t max) +{ + u32 ver; + u32 len = sizeof(ver); + /* microcode version is a 32 bit integer */ + if (ipw2100_get_ordinal(priv, IPW_ORD_UCODE_VERSION, &ver, &len)) + return -EIO; + return snprintf(buf, max, "%08X", ver); +} + +/* + * On exit, the firmware will have been freed from the fw list + */ +static int ipw2100_fw_download(struct ipw2100_priv *priv, struct ipw2100_fw *fw) +{ + /* firmware is constructed of N contiguous entries, each entry is + * structured as: + * + * offset sie desc + * 0 4 address to write to + * 4 2 length of data run + * 6 length data + */ + unsigned int addr; + unsigned short len; + + const unsigned char *firmware_data = fw->fw.data; + unsigned int firmware_data_left = fw->fw.size; + + while (firmware_data_left > 0) { + addr = *(u32 *) (firmware_data); + firmware_data += 4; + firmware_data_left -= 4; + + len = *(u16 *) (firmware_data); + firmware_data += 2; + firmware_data_left -= 2; + + if (len > 32) { + printk(KERN_ERR DRV_NAME ": " + "Invalid firmware run-length of %d bytes\n", + len); + return -EINVAL; + } + + write_nic_memory(priv->net_dev, addr, len, firmware_data); + firmware_data += len; + firmware_data_left -= len; + } + + return 0; +} + +struct symbol_alive_response { + u8 cmd_id; + u8 seq_num; + u8 ucode_rev; + u8 eeprom_valid; + u16 valid_flags; + u8 IEEE_addr[6]; + u16 flags; + u16 pcb_rev; + u16 clock_settle_time; // 1us LSB + u16 powerup_settle_time; // 1us LSB + u16 hop_settle_time; // 1us LSB + u8 date[3]; // month, day, year + u8 time[2]; // hours, minutes + u8 ucode_valid; +}; + +static int ipw2100_ucode_download(struct ipw2100_priv *priv, + struct ipw2100_fw *fw) +{ + struct net_device *dev = priv->net_dev; + const unsigned char *microcode_data = fw->uc.data; + unsigned int microcode_data_left = fw->uc.size; + void __iomem *reg = priv->ioaddr; + + struct symbol_alive_response response; + int i, j; + u8 data; + + /* Symbol control */ + write_nic_word(dev, IPW2100_CONTROL_REG, 0x703); + readl(reg); + write_nic_word(dev, IPW2100_CONTROL_REG, 0x707); + readl(reg); + + /* HW config */ + write_nic_byte(dev, 0x210014, 0x72); /* fifo width =16 */ + readl(reg); + write_nic_byte(dev, 0x210014, 0x72); /* fifo width =16 */ + readl(reg); + + /* EN_CS_ACCESS bit to reset control store pointer */ + write_nic_byte(dev, 0x210000, 0x40); + readl(reg); + write_nic_byte(dev, 0x210000, 0x0); + readl(reg); + write_nic_byte(dev, 0x210000, 0x40); + readl(reg); + + /* copy microcode from buffer into Symbol */ + + while (microcode_data_left > 0) { + write_nic_byte(dev, 0x210010, *microcode_data++); + write_nic_byte(dev, 0x210010, *microcode_data++); + microcode_data_left -= 2; + } + + /* EN_CS_ACCESS bit to reset the control store pointer */ + write_nic_byte(dev, 0x210000, 0x0); + readl(reg); + + /* Enable System (Reg 0) + * first enable causes garbage in RX FIFO */ + write_nic_byte(dev, 0x210000, 0x0); + readl(reg); + write_nic_byte(dev, 0x210000, 0x80); + readl(reg); + + /* Reset External Baseband Reg */ + write_nic_word(dev, IPW2100_CONTROL_REG, 0x703); + readl(reg); + write_nic_word(dev, IPW2100_CONTROL_REG, 0x707); + readl(reg); + + /* HW Config (Reg 5) */ + write_nic_byte(dev, 0x210014, 0x72); // fifo width =16 + readl(reg); + write_nic_byte(dev, 0x210014, 0x72); // fifo width =16 + readl(reg); + + /* Enable System (Reg 0) + * second enable should be OK */ + write_nic_byte(dev, 0x210000, 0x00); // clear enable system + readl(reg); + write_nic_byte(dev, 0x210000, 0x80); // set enable system + + /* check Symbol is enabled - upped this from 5 as it wasn't always + * catching the update */ + for (i = 0; i < 10; i++) { + udelay(10); + + /* check Dino is enabled bit */ + read_nic_byte(dev, 0x210000, &data); + if (data & 0x1) + break; + } + + if (i == 10) { + printk(KERN_ERR DRV_NAME ": %s: Error initializing Symbol\n", + dev->name); + return -EIO; + } + + /* Get Symbol alive response */ + for (i = 0; i < 30; i++) { + /* Read alive response structure */ + for (j = 0; + j < (sizeof(struct symbol_alive_response) >> 1); j++) + read_nic_word(dev, 0x210004, ((u16 *) & response) + j); + + if ((response.cmd_id == 1) && (response.ucode_valid == 0x1)) + break; + udelay(10); + } + + if (i == 30) { + printk(KERN_ERR DRV_NAME + ": %s: No response from Symbol - hw not alive\n", + dev->name); + printk_buf(IPW_DL_ERROR, (u8 *) & response, sizeof(response)); + return -EIO; + } + + return 0; +} diff --git a/drivers/net/wireless/ipw2x00/ipw2100.h b/drivers/net/wireless/ipw2x00/ipw2100.h new file mode 100644 index 0000000..dae16d2 --- /dev/null +++ b/drivers/net/wireless/ipw2x00/ipw2100.h @@ -0,0 +1,1156 @@ +/****************************************************************************** + + Copyright(c) 2003 - 2006 Intel Corporation. All rights reserved. + + This program is free software; you can redistribute it and/or modify it + under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Contact Information: + Intel Linux Wireless + Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + +******************************************************************************/ +#ifndef _IPW2100_H +#define _IPW2100_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // new driver API + +#ifdef CPTCFG_IPW2100_MONITOR +#include +#endif + +#include +#include + +#include "libipw.h" + +struct ipw2100_priv; +struct ipw2100_tx_packet; +struct ipw2100_rx_packet; + +#define IPW_DL_UNINIT 0x80000000 +#define IPW_DL_NONE 0x00000000 +#define IPW_DL_ALL 0x7FFFFFFF + +/* + * To use the debug system; + * + * If you are defining a new debug classification, simply add it to the #define + * list here in the form of: + * + * #define IPW_DL_xxxx VALUE + * + * shifting value to the left one bit from the previous entry. xxxx should be + * the name of the classification (for example, WEP) + * + * You then need to either add a IPW2100_xxxx_DEBUG() macro definition for your + * classification, or use IPW_DEBUG(IPW_DL_xxxx, ...) whenever you want + * to send output to that classification. + * + * To add your debug level to the list of levels seen when you perform + * + * % cat /proc/net/ipw2100/debug_level + * + * you simply need to add your entry to the ipw2100_debug_levels array. + * + * If you do not see debug_level in /proc/net/ipw2100 then you do not have + * CPTCFG_IPW2100_DEBUG defined in your kernel configuration + * + */ + +#define IPW_DL_ERROR (1<<0) +#define IPW_DL_WARNING (1<<1) +#define IPW_DL_INFO (1<<2) +#define IPW_DL_WX (1<<3) +#define IPW_DL_HC (1<<5) +#define IPW_DL_STATE (1<<6) + +#define IPW_DL_NOTIF (1<<10) +#define IPW_DL_SCAN (1<<11) +#define IPW_DL_ASSOC (1<<12) +#define IPW_DL_DROP (1<<13) + +#define IPW_DL_IOCTL (1<<14) +#define IPW_DL_RF_KILL (1<<17) + +#define IPW_DL_MANAGE (1<<15) +#define IPW_DL_FW (1<<16) + +#define IPW_DL_FRAG (1<<21) +#define IPW_DL_WEP (1<<22) +#define IPW_DL_TX (1<<23) +#define IPW_DL_RX (1<<24) +#define IPW_DL_ISR (1<<25) +#define IPW_DL_IO (1<<26) +#define IPW_DL_TRACE (1<<28) + +#define IPW_DEBUG_ERROR(f, a...) printk(KERN_ERR DRV_NAME ": " f, ## a) +#define IPW_DEBUG_WARNING(f, a...) printk(KERN_WARNING DRV_NAME ": " f, ## a) +#define IPW_DEBUG_INFO(f...) IPW_DEBUG(IPW_DL_INFO, ## f) +#define IPW_DEBUG_WX(f...) IPW_DEBUG(IPW_DL_WX, ## f) +#define IPW_DEBUG_SCAN(f...) IPW_DEBUG(IPW_DL_SCAN, ## f) +#define IPW_DEBUG_NOTIF(f...) IPW_DEBUG(IPW_DL_NOTIF, ## f) +#define IPW_DEBUG_TRACE(f...) IPW_DEBUG(IPW_DL_TRACE, ## f) +#define IPW_DEBUG_RX(f...) IPW_DEBUG(IPW_DL_RX, ## f) +#define IPW_DEBUG_TX(f...) IPW_DEBUG(IPW_DL_TX, ## f) +#define IPW_DEBUG_ISR(f...) IPW_DEBUG(IPW_DL_ISR, ## f) +#define IPW_DEBUG_MANAGEMENT(f...) IPW_DEBUG(IPW_DL_MANAGE, ## f) +#define IPW_DEBUG_WEP(f...) IPW_DEBUG(IPW_DL_WEP, ## f) +#define IPW_DEBUG_HC(f...) IPW_DEBUG(IPW_DL_HC, ## f) +#define IPW_DEBUG_FRAG(f...) IPW_DEBUG(IPW_DL_FRAG, ## f) +#define IPW_DEBUG_FW(f...) IPW_DEBUG(IPW_DL_FW, ## f) +#define IPW_DEBUG_RF_KILL(f...) IPW_DEBUG(IPW_DL_RF_KILL, ## f) +#define IPW_DEBUG_DROP(f...) IPW_DEBUG(IPW_DL_DROP, ## f) +#define IPW_DEBUG_IO(f...) IPW_DEBUG(IPW_DL_IO, ## f) +#define IPW_DEBUG_IOCTL(f...) IPW_DEBUG(IPW_DL_IOCTL, ## f) +#define IPW_DEBUG_STATE(f, a...) IPW_DEBUG(IPW_DL_STATE | IPW_DL_ASSOC | IPW_DL_INFO, f, ## a) +#define IPW_DEBUG_ASSOC(f, a...) IPW_DEBUG(IPW_DL_ASSOC | IPW_DL_INFO, f, ## a) + +enum { + IPW_HW_STATE_DISABLED = 1, + IPW_HW_STATE_ENABLED = 0 +}; + +extern const char *port_type_str[]; +extern const char *band_str[]; + +#define NUMBER_OF_BD_PER_COMMAND_PACKET 1 +#define NUMBER_OF_BD_PER_DATA_PACKET 2 + +#define IPW_MAX_BDS 6 +#define NUMBER_OF_OVERHEAD_BDS_PER_PACKETR 2 +#define NUMBER_OF_BDS_TO_LEAVE_FOR_COMMANDS 1 + +#define REQUIRED_SPACE_IN_RING_FOR_COMMAND_PACKET \ + (IPW_BD_QUEUE_W_R_MIN_SPARE + NUMBER_OF_BD_PER_COMMAND_PACKET) + +struct bd_status { + union { + struct { + u8 nlf:1, txType:2, intEnabled:1, reserved:4; + } fields; + u8 field; + } info; +} __packed; + +struct ipw2100_bd { + u32 host_addr; + u32 buf_length; + struct bd_status status; + /* number of fragments for frame (should be set only for + * 1st TBD) */ + u8 num_fragments; + u8 reserved[6]; +} __packed; + +#define IPW_BD_QUEUE_LENGTH(n) (1<value = (x)->hi = 0; \ + (x)->lo = 0x7fffffff; \ +} while (0) +#define SET_STAT(x,y) do { \ + (x)->value = y; \ + if ((x)->value > (x)->hi) (x)->hi = (x)->value; \ + if ((x)->value < (x)->lo) (x)->lo = (x)->value; \ +} while (0) +#define INC_STAT(x) do { if (++(x)->value > (x)->hi) (x)->hi = (x)->value; } \ +while (0) +#define DEC_STAT(x) do { if (--(x)->value < (x)->lo) (x)->lo = (x)->value; } \ +while (0) + +#define IPW2100_ERROR_QUEUE 5 + +/* Power management code: enable or disable? */ +enum { +#ifdef CONFIG_PM + IPW2100_PM_DISABLED = 0, + PM_STATE_SIZE = 16, +#else + IPW2100_PM_DISABLED = 1, + PM_STATE_SIZE = 0, +#endif +}; + +#define STATUS_POWERED (1<<0) +#define STATUS_CMD_ACTIVE (1<<1) /**< host command in progress */ +#define STATUS_RUNNING (1<<2) /* Card initialized, but not enabled */ +#define STATUS_ENABLED (1<<3) /* Card enabled -- can scan,Tx,Rx */ +#define STATUS_STOPPING (1<<4) /* Card is in shutdown phase */ +#define STATUS_INITIALIZED (1<<5) /* Card is ready for external calls */ +#define STATUS_ASSOCIATING (1<<9) /* Associated, but no BSSID yet */ +#define STATUS_ASSOCIATED (1<<10) /* Associated and BSSID valid */ +#define STATUS_INT_ENABLED (1<<11) +#define STATUS_RF_KILL_HW (1<<12) +#define STATUS_RF_KILL_SW (1<<13) +#define STATUS_RF_KILL_MASK (STATUS_RF_KILL_HW | STATUS_RF_KILL_SW) +#define STATUS_EXIT_PENDING (1<<14) + +#define STATUS_SCAN_PENDING (1<<23) +#define STATUS_SCANNING (1<<24) +#define STATUS_SCAN_ABORTING (1<<25) +#define STATUS_SCAN_COMPLETE (1<<26) +#define STATUS_WX_EVENT_PENDING (1<<27) +#define STATUS_RESET_PENDING (1<<29) +#define STATUS_SECURITY_UPDATED (1<<30) /* Security sync needed */ + +/* Internal NIC states */ +#define IPW_STATE_INITIALIZED (1<<0) +#define IPW_STATE_COUNTRY_FOUND (1<<1) +#define IPW_STATE_ASSOCIATED (1<<2) +#define IPW_STATE_ASSN_LOST (1<<3) +#define IPW_STATE_ASSN_CHANGED (1<<4) +#define IPW_STATE_SCAN_COMPLETE (1<<5) +#define IPW_STATE_ENTERED_PSP (1<<6) +#define IPW_STATE_LEFT_PSP (1<<7) +#define IPW_STATE_RF_KILL (1<<8) +#define IPW_STATE_DISABLED (1<<9) +#define IPW_STATE_POWER_DOWN (1<<10) +#define IPW_STATE_SCANNING (1<<11) + +#define CFG_STATIC_CHANNEL (1<<0) /* Restrict assoc. to single channel */ +#define CFG_STATIC_ESSID (1<<1) /* Restrict assoc. to single SSID */ +#define CFG_STATIC_BSSID (1<<2) /* Restrict assoc. to single BSSID */ +#define CFG_CUSTOM_MAC (1<<3) +#define CFG_LONG_PREAMBLE (1<<4) +#define CFG_ASSOCIATE (1<<6) +#define CFG_FIXED_RATE (1<<7) +#define CFG_ADHOC_CREATE (1<<8) +#define CFG_PASSIVE_SCAN (1<<10) +#ifdef CPTCFG_IPW2100_MONITOR +#define CFG_CRC_CHECK (1<<11) +#endif + +#define CAP_SHARED_KEY (1<<0) /* Off = OPEN */ +#define CAP_PRIVACY_ON (1<<1) /* Off = No privacy */ + +struct ipw2100_priv { + void __iomem *ioaddr; + + int stop_hang_check; /* Set 1 when shutting down to kill hang_check */ + int stop_rf_kill; /* Set 1 when shutting down to kill rf_kill */ + + struct libipw_device *ieee; + unsigned long status; + unsigned long config; + unsigned long capability; + + /* Statistics */ + int resets; + int reset_backoff; + + /* Context */ + u8 essid[IW_ESSID_MAX_SIZE]; + u8 essid_len; + u8 bssid[ETH_ALEN]; + u8 channel; + int last_mode; + + unsigned long connect_start; + unsigned long last_reset; + + u32 channel_mask; + u32 fatal_error; + u32 fatal_errors[IPW2100_ERROR_QUEUE]; + u32 fatal_index; + int eeprom_version; + int firmware_version; + unsigned long hw_features; + int hangs; + u32 last_rtc; + int dump_raw; /* 1 to dump raw bytes in /sys/.../memory */ + u8 *snapshot[0x30]; + + u8 mandatory_bssid_mac[ETH_ALEN]; + u8 mac_addr[ETH_ALEN]; + + int power_mode; + + int messages_sent; + + int short_retry_limit; + int long_retry_limit; + + u32 rts_threshold; + u32 frag_threshold; + + int in_isr; + + u32 tx_rates; + int tx_power; + u32 beacon_interval; + + char nick[IW_ESSID_MAX_SIZE + 1]; + + struct ipw2100_status_queue status_queue; + + struct statistic txq_stat; + struct statistic rxq_stat; + struct ipw2100_bd_queue rx_queue; + struct ipw2100_bd_queue tx_queue; + struct ipw2100_rx_packet *rx_buffers; + + struct statistic fw_pend_stat; + struct list_head fw_pend_list; + + struct statistic msg_free_stat; + struct statistic msg_pend_stat; + struct list_head msg_free_list; + struct list_head msg_pend_list; + struct ipw2100_tx_packet *msg_buffers; + + struct statistic tx_free_stat; + struct statistic tx_pend_stat; + struct list_head tx_free_list; + struct list_head tx_pend_list; + struct ipw2100_tx_packet *tx_buffers; + + struct ipw2100_ordinals ordinals; + + struct pci_dev *pci_dev; + + struct proc_dir_entry *dir_dev; + + struct net_device *net_dev; + struct iw_statistics wstats; + + struct iw_public_data wireless_data; + + struct tasklet_struct irq_tasklet; + + struct delayed_work reset_work; + struct delayed_work security_work; + struct delayed_work wx_event_work; + struct delayed_work hang_check; + struct delayed_work rf_kill; + struct delayed_work scan_event; + + int user_requested_scan; + + /* Track time in suspend */ + unsigned long suspend_at; + unsigned long suspend_time; + + u32 interrupts; + int tx_interrupts; + int rx_interrupts; + int inta_other; + + spinlock_t low_lock; + struct mutex action_mutex; + struct mutex adapter_mutex; + + wait_queue_head_t wait_command_queue; +}; + +/********************************************************* + * Host Command -> From Driver to FW + *********************************************************/ + +/** + * Host command identifiers + */ +#define HOST_COMPLETE 2 +#define SYSTEM_CONFIG 6 +#define SSID 8 +#define MANDATORY_BSSID 9 +#define AUTHENTICATION_TYPE 10 +#define ADAPTER_ADDRESS 11 +#define PORT_TYPE 12 +#define INTERNATIONAL_MODE 13 +#define CHANNEL 14 +#define RTS_THRESHOLD 15 +#define FRAG_THRESHOLD 16 +#define POWER_MODE 17 +#define TX_RATES 18 +#define BASIC_TX_RATES 19 +#define WEP_KEY_INFO 20 +#define WEP_KEY_INDEX 25 +#define WEP_FLAGS 26 +#define ADD_MULTICAST 27 +#define CLEAR_ALL_MULTICAST 28 +#define BEACON_INTERVAL 29 +#define ATIM_WINDOW 30 +#define CLEAR_STATISTICS 31 +#define SEND 33 +#define TX_POWER_INDEX 36 +#define BROADCAST_SCAN 43 +#define CARD_DISABLE 44 +#define PREFERRED_BSSID 45 +#define SET_SCAN_OPTIONS 46 +#define SCAN_DWELL_TIME 47 +#define SWEEP_TABLE 48 +#define AP_OR_STATION_TABLE 49 +#define GROUP_ORDINALS 50 +#define SHORT_RETRY_LIMIT 51 +#define LONG_RETRY_LIMIT 52 + +#define HOST_PRE_POWER_DOWN 58 +#define CARD_DISABLE_PHY_OFF 61 +#define MSDU_TX_RATES 62 + +/* Rogue AP Detection */ +#define SET_STATION_STAT_BITS 64 +#define CLEAR_STATIONS_STAT_BITS 65 +#define LEAP_ROGUE_MODE 66 //TODO tbw replaced by CFG_LEAP_ROGUE_AP +#define SET_SECURITY_INFORMATION 67 +#define DISASSOCIATION_BSSID 68 +#define SET_WPA_IE 69 + +/* system configuration bit mask: */ +#define IPW_CFG_MONITOR 0x00004 +#define IPW_CFG_PREAMBLE_AUTO 0x00010 +#define IPW_CFG_IBSS_AUTO_START 0x00020 +#define IPW_CFG_LOOPBACK 0x00100 +#define IPW_CFG_ANSWER_BCSSID_PROBE 0x00800 +#define IPW_CFG_BT_SIDEBAND_SIGNAL 0x02000 +#define IPW_CFG_802_1x_ENABLE 0x04000 +#define IPW_CFG_BSS_MASK 0x08000 +#define IPW_CFG_IBSS_MASK 0x10000 + +#define IPW_SCAN_NOASSOCIATE (1<<0) +#define IPW_SCAN_MIXED_CELL (1<<1) +/* RESERVED (1<<2) */ +#define IPW_SCAN_PASSIVE (1<<3) + +#define IPW_NIC_FATAL_ERROR 0x2A7F0 +#define IPW_ERROR_ADDR(x) (x & 0x3FFFF) +#define IPW_ERROR_CODE(x) ((x & 0xFF000000) >> 24) +#define IPW2100_ERR_C3_CORRUPTION (0x10 << 24) +#define IPW2100_ERR_MSG_TIMEOUT (0x11 << 24) +#define IPW2100_ERR_FW_LOAD (0x12 << 24) + +#define IPW_MEM_SRAM_HOST_SHARED_LOWER_BOUND 0x200 +#define IPW_MEM_SRAM_HOST_INTERRUPT_AREA_LOWER_BOUND IPW_MEM_SRAM_HOST_SHARED_LOWER_BOUND + 0x0D80 + +#define IPW_MEM_HOST_SHARED_RX_BD_BASE (IPW_MEM_SRAM_HOST_SHARED_LOWER_BOUND + 0x40) +#define IPW_MEM_HOST_SHARED_RX_STATUS_BASE (IPW_MEM_SRAM_HOST_SHARED_LOWER_BOUND + 0x44) +#define IPW_MEM_HOST_SHARED_RX_BD_SIZE (IPW_MEM_SRAM_HOST_SHARED_LOWER_BOUND + 0x48) +#define IPW_MEM_HOST_SHARED_RX_READ_INDEX (IPW_MEM_SRAM_HOST_SHARED_LOWER_BOUND + 0xa0) + +#define IPW_MEM_HOST_SHARED_TX_QUEUE_BD_BASE (IPW_MEM_SRAM_HOST_SHARED_LOWER_BOUND + 0x00) +#define IPW_MEM_HOST_SHARED_TX_QUEUE_BD_SIZE (IPW_MEM_SRAM_HOST_SHARED_LOWER_BOUND + 0x04) +#define IPW_MEM_HOST_SHARED_TX_QUEUE_READ_INDEX (IPW_MEM_SRAM_HOST_SHARED_LOWER_BOUND + 0x80) + +#define IPW_MEM_HOST_SHARED_RX_WRITE_INDEX \ + (IPW_MEM_SRAM_HOST_INTERRUPT_AREA_LOWER_BOUND + 0x20) + +#define IPW_MEM_HOST_SHARED_TX_QUEUE_WRITE_INDEX \ + (IPW_MEM_SRAM_HOST_INTERRUPT_AREA_LOWER_BOUND) + +#define IPW_MEM_HOST_SHARED_ORDINALS_TABLE_1 (IPW_MEM_SRAM_HOST_SHARED_LOWER_BOUND + 0x180) +#define IPW_MEM_HOST_SHARED_ORDINALS_TABLE_2 (IPW_MEM_SRAM_HOST_SHARED_LOWER_BOUND + 0x184) + +#define IPW2100_INTA_TX_TRANSFER (0x00000001) // Bit 0 (LSB) +#define IPW2100_INTA_RX_TRANSFER (0x00000002) // Bit 1 +#define IPW2100_INTA_TX_COMPLETE (0x00000004) // Bit 2 +#define IPW2100_INTA_EVENT_INTERRUPT (0x00000008) // Bit 3 +#define IPW2100_INTA_STATUS_CHANGE (0x00000010) // Bit 4 +#define IPW2100_INTA_BEACON_PERIOD_EXPIRED (0x00000020) // Bit 5 +#define IPW2100_INTA_SLAVE_MODE_HOST_COMMAND_DONE (0x00010000) // Bit 16 +#define IPW2100_INTA_FW_INIT_DONE (0x01000000) // Bit 24 +#define IPW2100_INTA_FW_CALIBRATION_CALC (0x02000000) // Bit 25 +#define IPW2100_INTA_FATAL_ERROR (0x40000000) // Bit 30 +#define IPW2100_INTA_PARITY_ERROR (0x80000000) // Bit 31 (MSB) + +#define IPW_AUX_HOST_RESET_REG_PRINCETON_RESET (0x00000001) +#define IPW_AUX_HOST_RESET_REG_FORCE_NMI (0x00000002) +#define IPW_AUX_HOST_RESET_REG_PCI_HOST_CLUSTER_FATAL_NMI (0x00000004) +#define IPW_AUX_HOST_RESET_REG_CORE_FATAL_NMI (0x00000008) +#define IPW_AUX_HOST_RESET_REG_SW_RESET (0x00000080) +#define IPW_AUX_HOST_RESET_REG_MASTER_DISABLED (0x00000100) +#define IPW_AUX_HOST_RESET_REG_STOP_MASTER (0x00000200) + +#define IPW_AUX_HOST_GP_CNTRL_BIT_CLOCK_READY (0x00000001) // Bit 0 (LSB) +#define IPW_AUX_HOST_GP_CNTRL_BIT_HOST_ALLOWS_STANDBY (0x00000002) // Bit 1 +#define IPW_AUX_HOST_GP_CNTRL_BIT_INIT_DONE (0x00000004) // Bit 2 +#define IPW_AUX_HOST_GP_CNTRL_BITS_SYS_CONFIG (0x000007c0) // Bits 6-10 +#define IPW_AUX_HOST_GP_CNTRL_BIT_BUS_TYPE (0x00000200) // Bit 9 +#define IPW_AUX_HOST_GP_CNTRL_BIT_BAR0_BLOCK_SIZE (0x00000400) // Bit 10 +#define IPW_AUX_HOST_GP_CNTRL_BIT_USB_MODE (0x20000000) // Bit 29 +#define IPW_AUX_HOST_GP_CNTRL_BIT_HOST_FORCES_SYS_CLK (0x40000000) // Bit 30 +#define IPW_AUX_HOST_GP_CNTRL_BIT_FW_FORCES_SYS_CLK (0x80000000) // Bit 31 (MSB) + +#define IPW_BIT_GPIO_GPIO1_MASK 0x0000000C +#define IPW_BIT_GPIO_GPIO3_MASK 0x000000C0 +#define IPW_BIT_GPIO_GPIO1_ENABLE 0x00000008 +#define IPW_BIT_GPIO_RF_KILL 0x00010000 + +#define IPW_BIT_GPIO_LED_OFF 0x00002000 // Bit 13 = 1 + +#define IPW_REG_DOMAIN_0_OFFSET 0x0000 +#define IPW_REG_DOMAIN_1_OFFSET IPW_MEM_SRAM_HOST_SHARED_LOWER_BOUND + +#define IPW_REG_INTA IPW_REG_DOMAIN_0_OFFSET + 0x0008 +#define IPW_REG_INTA_MASK IPW_REG_DOMAIN_0_OFFSET + 0x000C +#define IPW_REG_INDIRECT_ACCESS_ADDRESS IPW_REG_DOMAIN_0_OFFSET + 0x0010 +#define IPW_REG_INDIRECT_ACCESS_DATA IPW_REG_DOMAIN_0_OFFSET + 0x0014 +#define IPW_REG_AUTOINCREMENT_ADDRESS IPW_REG_DOMAIN_0_OFFSET + 0x0018 +#define IPW_REG_AUTOINCREMENT_DATA IPW_REG_DOMAIN_0_OFFSET + 0x001C +#define IPW_REG_RESET_REG IPW_REG_DOMAIN_0_OFFSET + 0x0020 +#define IPW_REG_GP_CNTRL IPW_REG_DOMAIN_0_OFFSET + 0x0024 +#define IPW_REG_GPIO IPW_REG_DOMAIN_0_OFFSET + 0x0030 +#define IPW_REG_FW_TYPE IPW_REG_DOMAIN_1_OFFSET + 0x0188 +#define IPW_REG_FW_VERSION IPW_REG_DOMAIN_1_OFFSET + 0x018C +#define IPW_REG_FW_COMPATABILITY_VERSION IPW_REG_DOMAIN_1_OFFSET + 0x0190 + +#define IPW_REG_INDIRECT_ADDR_MASK 0x00FFFFFC + +#define IPW_INTERRUPT_MASK 0xC1010013 + +#define IPW2100_CONTROL_REG 0x220000 +#define IPW2100_CONTROL_PHY_OFF 0x8 + +#define IPW2100_COMMAND 0x00300004 +#define IPW2100_COMMAND_PHY_ON 0x0 +#define IPW2100_COMMAND_PHY_OFF 0x1 + +/* in DEBUG_AREA, values of memory always 0xd55555d5 */ +#define IPW_REG_DOA_DEBUG_AREA_START IPW_REG_DOMAIN_0_OFFSET + 0x0090 +#define IPW_REG_DOA_DEBUG_AREA_END IPW_REG_DOMAIN_0_OFFSET + 0x00FF +#define IPW_DATA_DOA_DEBUG_VALUE 0xd55555d5 + +#define IPW_INTERNAL_REGISTER_HALT_AND_RESET 0x003000e0 + +#define IPW_WAIT_CLOCK_STABILIZATION_DELAY 50 // micro seconds +#define IPW_WAIT_RESET_ARC_COMPLETE_DELAY 10 // micro seconds +#define IPW_WAIT_RESET_MASTER_ASSERT_COMPLETE_DELAY 10 // micro seconds + +// BD ring queue read/write difference +#define IPW_BD_QUEUE_W_R_MIN_SPARE 2 + +#define IPW_CACHE_LINE_LENGTH_DEFAULT 0x80 + +#define IPW_CARD_DISABLE_PHY_OFF_COMPLETE_WAIT 100 // 100 milli +#define IPW_PREPARE_POWER_DOWN_COMPLETE_WAIT 100 // 100 milli + +#define IPW_HEADER_802_11_SIZE sizeof(struct libipw_hdr_3addr) +#define IPW_MAX_80211_PAYLOAD_SIZE 2304U +#define IPW_MAX_802_11_PAYLOAD_LENGTH 2312 +#define IPW_MAX_ACCEPTABLE_TX_FRAME_LENGTH 1536 +#define IPW_MIN_ACCEPTABLE_RX_FRAME_LENGTH 60 +#define IPW_MAX_ACCEPTABLE_RX_FRAME_LENGTH \ + (IPW_MAX_ACCEPTABLE_TX_FRAME_LENGTH + IPW_HEADER_802_11_SIZE - \ + sizeof(struct ethhdr)) + +#define IPW_802_11_FCS_LENGTH 4 +#define IPW_RX_NIC_BUFFER_LENGTH \ + (IPW_MAX_802_11_PAYLOAD_LENGTH + IPW_HEADER_802_11_SIZE + \ + IPW_802_11_FCS_LENGTH) + +#define IPW_802_11_PAYLOAD_OFFSET \ + (sizeof(struct libipw_hdr_3addr) + \ + sizeof(struct libipw_snap_hdr)) + +struct ipw2100_rx { + union { + unsigned char payload[IPW_RX_NIC_BUFFER_LENGTH]; + struct libipw_hdr_4addr header; + u32 status; + struct ipw2100_notification notification; + struct ipw2100_cmd_header command; + } rx_data; +} __packed; + +/* Bit 0-7 are for 802.11b tx rates - . Bit 5-7 are reserved */ +#define TX_RATE_1_MBIT 0x0001 +#define TX_RATE_2_MBIT 0x0002 +#define TX_RATE_5_5_MBIT 0x0004 +#define TX_RATE_11_MBIT 0x0008 +#define TX_RATE_MASK 0x000F +#define DEFAULT_TX_RATES 0x000F + +#define IPW_POWER_MODE_CAM 0x00 //(always on) +#define IPW_POWER_INDEX_1 0x01 +#define IPW_POWER_INDEX_2 0x02 +#define IPW_POWER_INDEX_3 0x03 +#define IPW_POWER_INDEX_4 0x04 +#define IPW_POWER_INDEX_5 0x05 +#define IPW_POWER_AUTO 0x06 +#define IPW_POWER_MASK 0x0F +#define IPW_POWER_ENABLED 0x10 +#define IPW_POWER_LEVEL(x) ((x) & IPW_POWER_MASK) + +#define IPW_TX_POWER_AUTO 0 +#define IPW_TX_POWER_ENHANCED 1 + +#define IPW_TX_POWER_DEFAULT 32 +#define IPW_TX_POWER_MIN 0 +#define IPW_TX_POWER_MAX 16 +#define IPW_TX_POWER_MIN_DBM (-12) +#define IPW_TX_POWER_MAX_DBM 16 + +#define FW_SCAN_DONOT_ASSOCIATE 0x0001 // Dont Attempt to Associate after Scan +#define FW_SCAN_PASSIVE 0x0008 // Force PASSSIVE Scan + +#define REG_MIN_CHANNEL 0 +#define REG_MAX_CHANNEL 14 + +#define REG_CHANNEL_MASK 0x00003FFF +#define IPW_IBSS_11B_DEFAULT_MASK 0x87ff + +#define DIVERSITY_EITHER 0 // Use both antennas +#define DIVERSITY_ANTENNA_A 1 // Use antenna A +#define DIVERSITY_ANTENNA_B 2 // Use antenna B + +#define HOST_COMMAND_WAIT 0 +#define HOST_COMMAND_NO_WAIT 1 + +#define LOCK_NONE 0 +#define LOCK_DRIVER 1 +#define LOCK_FW 2 + +#define TYPE_SWEEP_ORD 0x000D +#define TYPE_IBSS_STTN_ORD 0x000E +#define TYPE_BSS_AP_ORD 0x000F +#define TYPE_RAW_BEACON_ENTRY 0x0010 +#define TYPE_CALIBRATION_DATA 0x0011 +#define TYPE_ROGUE_AP_DATA 0x0012 +#define TYPE_ASSOCIATION_REQUEST 0x0013 +#define TYPE_REASSOCIATION_REQUEST 0x0014 + +#define HW_FEATURE_RFKILL 0x0001 +#define RF_KILLSWITCH_OFF 1 +#define RF_KILLSWITCH_ON 0 + +#define IPW_COMMAND_POOL_SIZE 40 + +#define IPW_START_ORD_TAB_1 1 +#define IPW_START_ORD_TAB_2 1000 + +#define IPW_ORD_TAB_1_ENTRY_SIZE sizeof(u32) + +#define IS_ORDINAL_TABLE_ONE(mgr,id) \ + ((id >= IPW_START_ORD_TAB_1) && (id < mgr->table1_size)) +#define IS_ORDINAL_TABLE_TWO(mgr,id) \ + ((id >= IPW_START_ORD_TAB_2) && (id < (mgr->table2_size + IPW_START_ORD_TAB_2))) + +#define BSS_ID_LENGTH 6 + +// Fixed size data: Ordinal Table 1 +typedef enum _ORDINAL_TABLE_1 { // NS - means Not Supported by FW +// Transmit statistics + IPW_ORD_STAT_TX_HOST_REQUESTS = 1, // # of requested Host Tx's (MSDU) + IPW_ORD_STAT_TX_HOST_COMPLETE, // # of successful Host Tx's (MSDU) + IPW_ORD_STAT_TX_DIR_DATA, // # of successful Directed Tx's (MSDU) + + IPW_ORD_STAT_TX_DIR_DATA1 = 4, // # of successful Directed Tx's (MSDU) @ 1MB + IPW_ORD_STAT_TX_DIR_DATA2, // # of successful Directed Tx's (MSDU) @ 2MB + IPW_ORD_STAT_TX_DIR_DATA5_5, // # of successful Directed Tx's (MSDU) @ 5_5MB + IPW_ORD_STAT_TX_DIR_DATA11, // # of successful Directed Tx's (MSDU) @ 11MB + IPW_ORD_STAT_TX_DIR_DATA22, // # of successful Directed Tx's (MSDU) @ 22MB + + IPW_ORD_STAT_TX_NODIR_DATA1 = 13, // # of successful Non_Directed Tx's (MSDU) @ 1MB + IPW_ORD_STAT_TX_NODIR_DATA2, // # of successful Non_Directed Tx's (MSDU) @ 2MB + IPW_ORD_STAT_TX_NODIR_DATA5_5, // # of successful Non_Directed Tx's (MSDU) @ 5.5MB + IPW_ORD_STAT_TX_NODIR_DATA11, // # of successful Non_Directed Tx's (MSDU) @ 11MB + + IPW_ORD_STAT_NULL_DATA = 21, // # of successful NULL data Tx's + IPW_ORD_STAT_TX_RTS, // # of successful Tx RTS + IPW_ORD_STAT_TX_CTS, // # of successful Tx CTS + IPW_ORD_STAT_TX_ACK, // # of successful Tx ACK + IPW_ORD_STAT_TX_ASSN, // # of successful Association Tx's + IPW_ORD_STAT_TX_ASSN_RESP, // # of successful Association response Tx's + IPW_ORD_STAT_TX_REASSN, // # of successful Reassociation Tx's + IPW_ORD_STAT_TX_REASSN_RESP, // # of successful Reassociation response Tx's + IPW_ORD_STAT_TX_PROBE, // # of probes successfully transmitted + IPW_ORD_STAT_TX_PROBE_RESP, // # of probe responses successfully transmitted + IPW_ORD_STAT_TX_BEACON, // # of tx beacon + IPW_ORD_STAT_TX_ATIM, // # of Tx ATIM + IPW_ORD_STAT_TX_DISASSN, // # of successful Disassociation TX + IPW_ORD_STAT_TX_AUTH, // # of successful Authentication Tx + IPW_ORD_STAT_TX_DEAUTH, // # of successful Deauthentication TX + + IPW_ORD_STAT_TX_TOTAL_BYTES = 41, // Total successful Tx data bytes + IPW_ORD_STAT_TX_RETRIES, // # of Tx retries + IPW_ORD_STAT_TX_RETRY1, // # of Tx retries at 1MBPS + IPW_ORD_STAT_TX_RETRY2, // # of Tx retries at 2MBPS + IPW_ORD_STAT_TX_RETRY5_5, // # of Tx retries at 5.5MBPS + IPW_ORD_STAT_TX_RETRY11, // # of Tx retries at 11MBPS + + IPW_ORD_STAT_TX_FAILURES = 51, // # of Tx Failures + IPW_ORD_STAT_TX_ABORT_AT_HOP, //NS // # of Tx's aborted at hop time + IPW_ORD_STAT_TX_MAX_TRIES_IN_HOP, // # of times max tries in a hop failed + IPW_ORD_STAT_TX_ABORT_LATE_DMA, //NS // # of times tx aborted due to late dma setup + IPW_ORD_STAT_TX_ABORT_STX, //NS // # of times backoff aborted + IPW_ORD_STAT_TX_DISASSN_FAIL, // # of times disassociation failed + IPW_ORD_STAT_TX_ERR_CTS, // # of missed/bad CTS frames + IPW_ORD_STAT_TX_BPDU, //NS // # of spanning tree BPDUs sent + IPW_ORD_STAT_TX_ERR_ACK, // # of tx err due to acks + + // Receive statistics + IPW_ORD_STAT_RX_HOST = 61, // # of packets passed to host + IPW_ORD_STAT_RX_DIR_DATA, // # of directed packets + IPW_ORD_STAT_RX_DIR_DATA1, // # of directed packets at 1MB + IPW_ORD_STAT_RX_DIR_DATA2, // # of directed packets at 2MB + IPW_ORD_STAT_RX_DIR_DATA5_5, // # of directed packets at 5.5MB + IPW_ORD_STAT_RX_DIR_DATA11, // # of directed packets at 11MB + IPW_ORD_STAT_RX_DIR_DATA22, // # of directed packets at 22MB + + IPW_ORD_STAT_RX_NODIR_DATA = 71, // # of nondirected packets + IPW_ORD_STAT_RX_NODIR_DATA1, // # of nondirected packets at 1MB + IPW_ORD_STAT_RX_NODIR_DATA2, // # of nondirected packets at 2MB + IPW_ORD_STAT_RX_NODIR_DATA5_5, // # of nondirected packets at 5.5MB + IPW_ORD_STAT_RX_NODIR_DATA11, // # of nondirected packets at 11MB + + IPW_ORD_STAT_RX_NULL_DATA = 80, // # of null data rx's + IPW_ORD_STAT_RX_POLL, //NS // # of poll rx + IPW_ORD_STAT_RX_RTS, // # of Rx RTS + IPW_ORD_STAT_RX_CTS, // # of Rx CTS + IPW_ORD_STAT_RX_ACK, // # of Rx ACK + IPW_ORD_STAT_RX_CFEND, // # of Rx CF End + IPW_ORD_STAT_RX_CFEND_ACK, // # of Rx CF End + CF Ack + IPW_ORD_STAT_RX_ASSN, // # of Association Rx's + IPW_ORD_STAT_RX_ASSN_RESP, // # of Association response Rx's + IPW_ORD_STAT_RX_REASSN, // # of Reassociation Rx's + IPW_ORD_STAT_RX_REASSN_RESP, // # of Reassociation response Rx's + IPW_ORD_STAT_RX_PROBE, // # of probe Rx's + IPW_ORD_STAT_RX_PROBE_RESP, // # of probe response Rx's + IPW_ORD_STAT_RX_BEACON, // # of Rx beacon + IPW_ORD_STAT_RX_ATIM, // # of Rx ATIM + IPW_ORD_STAT_RX_DISASSN, // # of disassociation Rx + IPW_ORD_STAT_RX_AUTH, // # of authentication Rx + IPW_ORD_STAT_RX_DEAUTH, // # of deauthentication Rx + + IPW_ORD_STAT_RX_TOTAL_BYTES = 101, // Total rx data bytes received + IPW_ORD_STAT_RX_ERR_CRC, // # of packets with Rx CRC error + IPW_ORD_STAT_RX_ERR_CRC1, // # of Rx CRC errors at 1MB + IPW_ORD_STAT_RX_ERR_CRC2, // # of Rx CRC errors at 2MB + IPW_ORD_STAT_RX_ERR_CRC5_5, // # of Rx CRC errors at 5.5MB + IPW_ORD_STAT_RX_ERR_CRC11, // # of Rx CRC errors at 11MB + + IPW_ORD_STAT_RX_DUPLICATE1 = 112, // # of duplicate rx packets at 1MB + IPW_ORD_STAT_RX_DUPLICATE2, // # of duplicate rx packets at 2MB + IPW_ORD_STAT_RX_DUPLICATE5_5, // # of duplicate rx packets at 5.5MB + IPW_ORD_STAT_RX_DUPLICATE11, // # of duplicate rx packets at 11MB + IPW_ORD_STAT_RX_DUPLICATE = 119, // # of duplicate rx packets + + IPW_ORD_PERS_DB_LOCK = 120, // # locking fw permanent db + IPW_ORD_PERS_DB_SIZE, // # size of fw permanent db + IPW_ORD_PERS_DB_ADDR, // # address of fw permanent db + IPW_ORD_STAT_RX_INVALID_PROTOCOL, // # of rx frames with invalid protocol + IPW_ORD_SYS_BOOT_TIME, // # Boot time + IPW_ORD_STAT_RX_NO_BUFFER, // # of rx frames rejected due to no buffer + IPW_ORD_STAT_RX_ABORT_LATE_DMA, //NS // # of rx frames rejected due to dma setup too late + IPW_ORD_STAT_RX_ABORT_AT_HOP, //NS // # of rx frames aborted due to hop + IPW_ORD_STAT_RX_MISSING_FRAG, // # of rx frames dropped due to missing fragment + IPW_ORD_STAT_RX_ORPHAN_FRAG, // # of rx frames dropped due to non-sequential fragment + IPW_ORD_STAT_RX_ORPHAN_FRAME, // # of rx frames dropped due to unmatched 1st frame + IPW_ORD_STAT_RX_FRAG_AGEOUT, // # of rx frames dropped due to uncompleted frame + IPW_ORD_STAT_RX_BAD_SSID, //NS // Bad SSID (unused) + IPW_ORD_STAT_RX_ICV_ERRORS, // # of ICV errors during decryption + +// PSP Statistics + IPW_ORD_STAT_PSP_SUSPENSION = 137, // # of times adapter suspended + IPW_ORD_STAT_PSP_BCN_TIMEOUT, // # of beacon timeout + IPW_ORD_STAT_PSP_POLL_TIMEOUT, // # of poll response timeouts + IPW_ORD_STAT_PSP_NONDIR_TIMEOUT, // # of timeouts waiting for last broadcast/muticast pkt + IPW_ORD_STAT_PSP_RX_DTIMS, // # of PSP DTIMs received + IPW_ORD_STAT_PSP_RX_TIMS, // # of PSP TIMs received + IPW_ORD_STAT_PSP_STATION_ID, // PSP Station ID + +// Association and roaming + IPW_ORD_LAST_ASSN_TIME = 147, // RTC time of last association + IPW_ORD_STAT_PERCENT_MISSED_BCNS, // current calculation of % missed beacons + IPW_ORD_STAT_PERCENT_RETRIES, // current calculation of % missed tx retries + IPW_ORD_ASSOCIATED_AP_PTR, // If associated, this is ptr to the associated + // AP table entry. set to 0 if not associated + IPW_ORD_AVAILABLE_AP_CNT, // # of AP's decsribed in the AP table + IPW_ORD_AP_LIST_PTR, // Ptr to list of available APs + IPW_ORD_STAT_AP_ASSNS, // # of associations + IPW_ORD_STAT_ASSN_FAIL, // # of association failures + IPW_ORD_STAT_ASSN_RESP_FAIL, // # of failuresdue to response fail + IPW_ORD_STAT_FULL_SCANS, // # of full scans + + IPW_ORD_CARD_DISABLED, // # Card Disabled + IPW_ORD_STAT_ROAM_INHIBIT, // # of times roaming was inhibited due to ongoing activity + IPW_FILLER_40, + IPW_ORD_RSSI_AT_ASSN = 160, // RSSI of associated AP at time of association + IPW_ORD_STAT_ASSN_CAUSE1, // # of reassociations due to no tx from AP in last N + // hops or no prob_ responses in last 3 minutes + IPW_ORD_STAT_ASSN_CAUSE2, // # of reassociations due to poor tx/rx quality + IPW_ORD_STAT_ASSN_CAUSE3, // # of reassociations due to tx/rx quality with excessive + // load at the AP + IPW_ORD_STAT_ASSN_CAUSE4, // # of reassociations due to AP RSSI level fell below + // eligible group + IPW_ORD_STAT_ASSN_CAUSE5, // # of reassociations due to load leveling + IPW_ORD_STAT_ASSN_CAUSE6, //NS // # of reassociations due to dropped by Ap + IPW_FILLER_41, + IPW_FILLER_42, + IPW_FILLER_43, + IPW_ORD_STAT_AUTH_FAIL, // # of times authentication failed + IPW_ORD_STAT_AUTH_RESP_FAIL, // # of times authentication response failed + IPW_ORD_STATION_TABLE_CNT, // # of entries in association table + +// Other statistics + IPW_ORD_RSSI_AVG_CURR = 173, // Current avg RSSI + IPW_ORD_STEST_RESULTS_CURR, //NS // Current self test results word + IPW_ORD_STEST_RESULTS_CUM, //NS // Cummulative self test results word + IPW_ORD_SELF_TEST_STATUS, //NS // + IPW_ORD_POWER_MGMT_MODE, // Power mode - 0=CAM, 1=PSP + IPW_ORD_POWER_MGMT_INDEX, //NS // + IPW_ORD_COUNTRY_CODE, // IEEE country code as recv'd from beacon + IPW_ORD_COUNTRY_CHANNELS, // channels supported by country +// IPW_ORD_COUNTRY_CHANNELS: +// For 11b the lower 2-byte are used for channels from 1-14 +// and the higher 2-byte are not used. + IPW_ORD_RESET_CNT, // # of adapter resets (warm) + IPW_ORD_BEACON_INTERVAL, // Beacon interval + + IPW_ORD_PRINCETON_VERSION = 184, //NS // Princeton Version + IPW_ORD_ANTENNA_DIVERSITY, // TRUE if antenna diversity is disabled + IPW_ORD_CCA_RSSI, //NS // CCA RSSI value (factory programmed) + IPW_ORD_STAT_EEPROM_UPDATE, //NS // # of times config EEPROM updated + IPW_ORD_DTIM_PERIOD, // # of beacon intervals between DTIMs + IPW_ORD_OUR_FREQ, // current radio freq lower digits - channel ID + + IPW_ORD_RTC_TIME = 190, // current RTC time + IPW_ORD_PORT_TYPE, // operating mode + IPW_ORD_CURRENT_TX_RATE, // current tx rate + IPW_ORD_SUPPORTED_RATES, // Bitmap of supported tx rates + IPW_ORD_ATIM_WINDOW, // current ATIM Window + IPW_ORD_BASIC_RATES, // bitmap of basic tx rates + IPW_ORD_NIC_HIGHEST_RATE, // bitmap of basic tx rates + IPW_ORD_AP_HIGHEST_RATE, // bitmap of basic tx rates + IPW_ORD_CAPABILITIES, // Management frame capability field + IPW_ORD_AUTH_TYPE, // Type of authentication + IPW_ORD_RADIO_TYPE, // Adapter card platform type + IPW_ORD_RTS_THRESHOLD = 201, // Min length of packet after which RTS handshaking is used + IPW_ORD_INT_MODE, // International mode + IPW_ORD_FRAGMENTATION_THRESHOLD, // protocol frag threshold + IPW_ORD_EEPROM_SRAM_DB_BLOCK_START_ADDRESS, // EEPROM offset in SRAM + IPW_ORD_EEPROM_SRAM_DB_BLOCK_SIZE, // EEPROM size in SRAM + IPW_ORD_EEPROM_SKU_CAPABILITY, // EEPROM SKU Capability 206 = + IPW_ORD_EEPROM_IBSS_11B_CHANNELS, // EEPROM IBSS 11b channel set + + IPW_ORD_MAC_VERSION = 209, // MAC Version + IPW_ORD_MAC_REVISION, // MAC Revision + IPW_ORD_RADIO_VERSION, // Radio Version + IPW_ORD_NIC_MANF_DATE_TIME, // MANF Date/Time STAMP + IPW_ORD_UCODE_VERSION, // Ucode Version + IPW_ORD_HW_RF_SWITCH_STATE = 214, // HW RF Kill Switch State +} ORDINALTABLE1; + +// ordinal table 2 +// Variable length data: +#define IPW_FIRST_VARIABLE_LENGTH_ORDINAL 1001 + +typedef enum _ORDINAL_TABLE_2 { // NS - means Not Supported by FW + IPW_ORD_STAT_BASE = 1000, // contains number of variable ORDs + IPW_ORD_STAT_ADAPTER_MAC = 1001, // 6 bytes: our adapter MAC address + IPW_ORD_STAT_PREFERRED_BSSID = 1002, // 6 bytes: BSSID of the preferred AP + IPW_ORD_STAT_MANDATORY_BSSID = 1003, // 6 bytes: BSSID of the mandatory AP + IPW_FILL_1, //NS // + IPW_ORD_STAT_COUNTRY_TEXT = 1005, // 36 bytes: Country name text, First two bytes are Country code + IPW_ORD_STAT_ASSN_SSID = 1006, // 32 bytes: ESSID String + IPW_ORD_STATION_TABLE = 1007, // ? bytes: Station/AP table (via Direct SSID Scans) + IPW_ORD_STAT_SWEEP_TABLE = 1008, // ? bytes: Sweep/Host Table table (via Broadcast Scans) + IPW_ORD_STAT_ROAM_LOG = 1009, // ? bytes: Roaming log + IPW_ORD_STAT_RATE_LOG = 1010, //NS // 0 bytes: Rate log + IPW_ORD_STAT_FIFO = 1011, //NS // 0 bytes: Fifo buffer data structures + IPW_ORD_STAT_FW_VER_NUM = 1012, // 14 bytes: fw version ID string as in (a.bb.ccc; "0.08.011") + IPW_ORD_STAT_FW_DATE = 1013, // 14 bytes: fw date string (mmm dd yyyy; "Mar 13 2002") + IPW_ORD_STAT_ASSN_AP_BSSID = 1014, // 6 bytes: MAC address of associated AP + IPW_ORD_STAT_DEBUG = 1015, //NS // ? bytes: + IPW_ORD_STAT_NIC_BPA_NUM = 1016, // 11 bytes: NIC BPA number in ASCII + IPW_ORD_STAT_UCODE_DATE = 1017, // 5 bytes: uCode date + IPW_ORD_SECURITY_NGOTIATION_RESULT = 1018, +} ORDINALTABLE2; // NS - means Not Supported by FW + +#define IPW_LAST_VARIABLE_LENGTH_ORDINAL 1018 + +#ifndef WIRELESS_SPY +#define WIRELESS_SPY // enable iwspy support +#endif + +#define IPW_HOST_FW_SHARED_AREA0 0x0002f200 +#define IPW_HOST_FW_SHARED_AREA0_END 0x0002f510 // 0x310 bytes + +#define IPW_HOST_FW_SHARED_AREA1 0x0002f610 +#define IPW_HOST_FW_SHARED_AREA1_END 0x0002f630 // 0x20 bytes + +#define IPW_HOST_FW_SHARED_AREA2 0x0002fa00 +#define IPW_HOST_FW_SHARED_AREA2_END 0x0002fa20 // 0x20 bytes + +#define IPW_HOST_FW_SHARED_AREA3 0x0002fc00 +#define IPW_HOST_FW_SHARED_AREA3_END 0x0002fc10 // 0x10 bytes + +#define IPW_HOST_FW_INTERRUPT_AREA 0x0002ff80 +#define IPW_HOST_FW_INTERRUPT_AREA_END 0x00030000 // 0x80 bytes + +struct ipw2100_fw_chunk { + unsigned char *buf; + long len; + long pos; + struct list_head list; +}; + +struct ipw2100_fw_chunk_set { + const void *data; + unsigned long size; +}; + +struct ipw2100_fw { + int version; + struct ipw2100_fw_chunk_set fw; + struct ipw2100_fw_chunk_set uc; + const struct firmware *fw_entry; +}; + +#define MAX_FW_VERSION_LEN 14 + +#endif /* _IPW2100_H */ diff --git a/drivers/net/wireless/ipw2x00/ipw2200.c b/drivers/net/wireless/ipw2x00/ipw2200.c new file mode 100644 index 0000000..744c942 --- /dev/null +++ b/drivers/net/wireless/ipw2x00/ipw2200.c @@ -0,0 +1,12158 @@ +/****************************************************************************** + + Copyright(c) 2003 - 2006 Intel Corporation. All rights reserved. + + 802.11 status code portion of this file from ethereal-0.10.6: + Copyright 2000, Axis Communications AB + Ethereal - Network traffic analyzer + By Gerald Combs + Copyright 1998 Gerald Combs + + This program is free software; you can redistribute it and/or modify it + under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Contact Information: + Intel Linux Wireless + Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + +******************************************************************************/ + +#include +#include +#include +#include "ipw2200.h" +#include "ipw.h" + + +#ifndef KBUILD_EXTMOD +#define VK "k" +#else +#define VK +#endif + +#ifdef CPTCFG_IPW2200_DEBUG +#define VD "d" +#else +#define VD +#endif + +#ifdef CPTCFG_IPW2200_MONITOR +#define VM "m" +#else +#define VM +#endif + +#ifdef CPTCFG_IPW2200_PROMISCUOUS +#define VP "p" +#else +#define VP +#endif + +#ifdef CPTCFG_IPW2200_RADIOTAP +#define VR "r" +#else +#define VR +#endif + +#ifdef CPTCFG_IPW2200_QOS +#define VQ "q" +#else +#define VQ +#endif + +#define IPW2200_VERSION "1.2.2" VK VD VM VP VR VQ +#define DRV_DESCRIPTION "Intel(R) PRO/Wireless 2200/2915 Network Driver" +#define DRV_COPYRIGHT "Copyright(c) 2003-2006 Intel Corporation" +#define DRV_VERSION IPW2200_VERSION + +#define ETH_P_80211_STATS (ETH_P_80211_RAW + 1) + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR(DRV_COPYRIGHT); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("ipw2200-ibss.fw"); +#ifdef CPTCFG_IPW2200_MONITOR +MODULE_FIRMWARE("ipw2200-sniffer.fw"); +#endif +MODULE_FIRMWARE("ipw2200-bss.fw"); + +static int cmdlog = 0; +static int debug = 0; +static int default_channel = 0; +static int network_mode = 0; + +static u32 ipw_debug_level; +static int associate; +static int auto_create = 1; +static int led_support = 1; +static int disable = 0; +static int bt_coexist = 0; +static int hwcrypto = 0; +static int roaming = 1; +static const char ipw_modes[] = { + 'a', 'b', 'g', '?' +}; +static int antenna = CFG_SYS_ANTENNA_BOTH; + +#ifdef CPTCFG_IPW2200_PROMISCUOUS +static int rtap_iface = 0; /* def: 0 -- do not create rtap interface */ +#endif + +static struct ieee80211_rate ipw2200_rates[] = { + { .bitrate = 10 }, + { .bitrate = 20, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 55, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 110, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 60 }, + { .bitrate = 90 }, + { .bitrate = 120 }, + { .bitrate = 180 }, + { .bitrate = 240 }, + { .bitrate = 360 }, + { .bitrate = 480 }, + { .bitrate = 540 } +}; + +#define ipw2200_a_rates (ipw2200_rates + 4) +#define ipw2200_num_a_rates 8 +#define ipw2200_bg_rates (ipw2200_rates + 0) +#define ipw2200_num_bg_rates 12 + +/* Ugly macro to convert literal channel numbers into their mhz equivalents + * There are certianly some conditions that will break this (like feeding it '30') + * but they shouldn't arise since nothing talks on channel 30. */ +#define ieee80211chan2mhz(x) \ + (((x) <= 14) ? \ + (((x) == 14) ? 2484 : ((x) * 5) + 2407) : \ + ((x) + 1000) * 5) + +#ifdef CPTCFG_IPW2200_QOS +static int qos_enable = 0; +static int qos_burst_enable = 0; +static int qos_no_ack_mask = 0; +static int burst_duration_CCK = 0; +static int burst_duration_OFDM = 0; + +static struct libipw_qos_parameters def_qos_parameters_OFDM = { + {QOS_TX0_CW_MIN_OFDM, QOS_TX1_CW_MIN_OFDM, QOS_TX2_CW_MIN_OFDM, + QOS_TX3_CW_MIN_OFDM}, + {QOS_TX0_CW_MAX_OFDM, QOS_TX1_CW_MAX_OFDM, QOS_TX2_CW_MAX_OFDM, + QOS_TX3_CW_MAX_OFDM}, + {QOS_TX0_AIFS, QOS_TX1_AIFS, QOS_TX2_AIFS, QOS_TX3_AIFS}, + {QOS_TX0_ACM, QOS_TX1_ACM, QOS_TX2_ACM, QOS_TX3_ACM}, + {QOS_TX0_TXOP_LIMIT_OFDM, QOS_TX1_TXOP_LIMIT_OFDM, + QOS_TX2_TXOP_LIMIT_OFDM, QOS_TX3_TXOP_LIMIT_OFDM} +}; + +static struct libipw_qos_parameters def_qos_parameters_CCK = { + {QOS_TX0_CW_MIN_CCK, QOS_TX1_CW_MIN_CCK, QOS_TX2_CW_MIN_CCK, + QOS_TX3_CW_MIN_CCK}, + {QOS_TX0_CW_MAX_CCK, QOS_TX1_CW_MAX_CCK, QOS_TX2_CW_MAX_CCK, + QOS_TX3_CW_MAX_CCK}, + {QOS_TX0_AIFS, QOS_TX1_AIFS, QOS_TX2_AIFS, QOS_TX3_AIFS}, + {QOS_TX0_ACM, QOS_TX1_ACM, QOS_TX2_ACM, QOS_TX3_ACM}, + {QOS_TX0_TXOP_LIMIT_CCK, QOS_TX1_TXOP_LIMIT_CCK, QOS_TX2_TXOP_LIMIT_CCK, + QOS_TX3_TXOP_LIMIT_CCK} +}; + +static struct libipw_qos_parameters def_parameters_OFDM = { + {DEF_TX0_CW_MIN_OFDM, DEF_TX1_CW_MIN_OFDM, DEF_TX2_CW_MIN_OFDM, + DEF_TX3_CW_MIN_OFDM}, + {DEF_TX0_CW_MAX_OFDM, DEF_TX1_CW_MAX_OFDM, DEF_TX2_CW_MAX_OFDM, + DEF_TX3_CW_MAX_OFDM}, + {DEF_TX0_AIFS, DEF_TX1_AIFS, DEF_TX2_AIFS, DEF_TX3_AIFS}, + {DEF_TX0_ACM, DEF_TX1_ACM, DEF_TX2_ACM, DEF_TX3_ACM}, + {DEF_TX0_TXOP_LIMIT_OFDM, DEF_TX1_TXOP_LIMIT_OFDM, + DEF_TX2_TXOP_LIMIT_OFDM, DEF_TX3_TXOP_LIMIT_OFDM} +}; + +static struct libipw_qos_parameters def_parameters_CCK = { + {DEF_TX0_CW_MIN_CCK, DEF_TX1_CW_MIN_CCK, DEF_TX2_CW_MIN_CCK, + DEF_TX3_CW_MIN_CCK}, + {DEF_TX0_CW_MAX_CCK, DEF_TX1_CW_MAX_CCK, DEF_TX2_CW_MAX_CCK, + DEF_TX3_CW_MAX_CCK}, + {DEF_TX0_AIFS, DEF_TX1_AIFS, DEF_TX2_AIFS, DEF_TX3_AIFS}, + {DEF_TX0_ACM, DEF_TX1_ACM, DEF_TX2_ACM, DEF_TX3_ACM}, + {DEF_TX0_TXOP_LIMIT_CCK, DEF_TX1_TXOP_LIMIT_CCK, DEF_TX2_TXOP_LIMIT_CCK, + DEF_TX3_TXOP_LIMIT_CCK} +}; + +static u8 qos_oui[QOS_OUI_LEN] = { 0x00, 0x50, 0xF2 }; + +static int from_priority_to_tx_queue[] = { + IPW_TX_QUEUE_1, IPW_TX_QUEUE_2, IPW_TX_QUEUE_2, IPW_TX_QUEUE_1, + IPW_TX_QUEUE_3, IPW_TX_QUEUE_3, IPW_TX_QUEUE_4, IPW_TX_QUEUE_4 +}; + +static u32 ipw_qos_get_burst_duration(struct ipw_priv *priv); + +static int ipw_send_qos_params_command(struct ipw_priv *priv, struct libipw_qos_parameters + *qos_param); +static int ipw_send_qos_info_command(struct ipw_priv *priv, struct libipw_qos_information_element + *qos_param); +#endif /* CPTCFG_IPW2200_QOS */ + +static struct iw_statistics *ipw_get_wireless_stats(struct net_device *dev); +static void ipw_remove_current_network(struct ipw_priv *priv); +static void ipw_rx(struct ipw_priv *priv); +static int ipw_queue_tx_reclaim(struct ipw_priv *priv, + struct clx2_tx_queue *txq, int qindex); +static int ipw_queue_reset(struct ipw_priv *priv); + +static int ipw_queue_tx_hcmd(struct ipw_priv *priv, int hcmd, void *buf, + int len, int sync); + +static void ipw_tx_queue_free(struct ipw_priv *); + +static struct ipw_rx_queue *ipw_rx_queue_alloc(struct ipw_priv *); +static void ipw_rx_queue_free(struct ipw_priv *, struct ipw_rx_queue *); +static void ipw_rx_queue_replenish(void *); +static int ipw_up(struct ipw_priv *); +static void ipw_bg_up(struct work_struct *work); +static void ipw_down(struct ipw_priv *); +static void ipw_bg_down(struct work_struct *work); +static int ipw_config(struct ipw_priv *); +static int init_supported_rates(struct ipw_priv *priv, + struct ipw_supported_rates *prates); +static void ipw_set_hwcrypto_keys(struct ipw_priv *); +static void ipw_send_wep_keys(struct ipw_priv *, int); + +static int snprint_line(char *buf, size_t count, + const u8 * data, u32 len, u32 ofs) +{ + int out, i, j, l; + char c; + + out = snprintf(buf, count, "%08X", ofs); + + for (l = 0, i = 0; i < 2; i++) { + out += snprintf(buf + out, count - out, " "); + for (j = 0; j < 8 && l < len; j++, l++) + out += snprintf(buf + out, count - out, "%02X ", + data[(i * 8 + j)]); + for (; j < 8; j++) + out += snprintf(buf + out, count - out, " "); + } + + out += snprintf(buf + out, count - out, " "); + for (l = 0, i = 0; i < 2; i++) { + out += snprintf(buf + out, count - out, " "); + for (j = 0; j < 8 && l < len; j++, l++) { + c = data[(i * 8 + j)]; + if (!isascii(c) || !isprint(c)) + c = '.'; + + out += snprintf(buf + out, count - out, "%c", c); + } + + for (; j < 8; j++) + out += snprintf(buf + out, count - out, " "); + } + + return out; +} + +static void printk_buf(int level, const u8 * data, u32 len) +{ + char line[81]; + u32 ofs = 0; + if (!(ipw_debug_level & level)) + return; + + while (len) { + snprint_line(line, sizeof(line), &data[ofs], + min(len, 16U), ofs); + printk(KERN_DEBUG "%s\n", line); + ofs += 16; + len -= min(len, 16U); + } +} + +static int snprintk_buf(u8 * output, size_t size, const u8 * data, size_t len) +{ + size_t out = size; + u32 ofs = 0; + int total = 0; + + while (size && len) { + out = snprint_line(output, size, &data[ofs], + min_t(size_t, len, 16U), ofs); + + ofs += 16; + output += out; + size -= out; + len -= min_t(size_t, len, 16U); + total += out; + } + return total; +} + +/* alias for 32-bit indirect read (for SRAM/reg above 4K), with debug wrapper */ +static u32 _ipw_read_reg32(struct ipw_priv *priv, u32 reg); +#define ipw_read_reg32(a, b) _ipw_read_reg32(a, b) + +/* alias for 8-bit indirect read (for SRAM/reg above 4K), with debug wrapper */ +static u8 _ipw_read_reg8(struct ipw_priv *ipw, u32 reg); +#define ipw_read_reg8(a, b) _ipw_read_reg8(a, b) + +/* 8-bit indirect write (for SRAM/reg above 4K), with debug wrapper */ +static void _ipw_write_reg8(struct ipw_priv *priv, u32 reg, u8 value); +static inline void ipw_write_reg8(struct ipw_priv *a, u32 b, u8 c) +{ + IPW_DEBUG_IO("%s %d: write_indirect8(0x%08X, 0x%08X)\n", __FILE__, + __LINE__, (u32) (b), (u32) (c)); + _ipw_write_reg8(a, b, c); +} + +/* 16-bit indirect write (for SRAM/reg above 4K), with debug wrapper */ +static void _ipw_write_reg16(struct ipw_priv *priv, u32 reg, u16 value); +static inline void ipw_write_reg16(struct ipw_priv *a, u32 b, u16 c) +{ + IPW_DEBUG_IO("%s %d: write_indirect16(0x%08X, 0x%08X)\n", __FILE__, + __LINE__, (u32) (b), (u32) (c)); + _ipw_write_reg16(a, b, c); +} + +/* 32-bit indirect write (for SRAM/reg above 4K), with debug wrapper */ +static void _ipw_write_reg32(struct ipw_priv *priv, u32 reg, u32 value); +static inline void ipw_write_reg32(struct ipw_priv *a, u32 b, u32 c) +{ + IPW_DEBUG_IO("%s %d: write_indirect32(0x%08X, 0x%08X)\n", __FILE__, + __LINE__, (u32) (b), (u32) (c)); + _ipw_write_reg32(a, b, c); +} + +/* 8-bit direct write (low 4K) */ +static inline void _ipw_write8(struct ipw_priv *ipw, unsigned long ofs, + u8 val) +{ + writeb(val, ipw->hw_base + ofs); +} + +/* 8-bit direct write (for low 4K of SRAM/regs), with debug wrapper */ +#define ipw_write8(ipw, ofs, val) do { \ + IPW_DEBUG_IO("%s %d: write_direct8(0x%08X, 0x%08X)\n", __FILE__, \ + __LINE__, (u32)(ofs), (u32)(val)); \ + _ipw_write8(ipw, ofs, val); \ +} while (0) + +/* 16-bit direct write (low 4K) */ +static inline void _ipw_write16(struct ipw_priv *ipw, unsigned long ofs, + u16 val) +{ + writew(val, ipw->hw_base + ofs); +} + +/* 16-bit direct write (for low 4K of SRAM/regs), with debug wrapper */ +#define ipw_write16(ipw, ofs, val) do { \ + IPW_DEBUG_IO("%s %d: write_direct16(0x%08X, 0x%08X)\n", __FILE__, \ + __LINE__, (u32)(ofs), (u32)(val)); \ + _ipw_write16(ipw, ofs, val); \ +} while (0) + +/* 32-bit direct write (low 4K) */ +static inline void _ipw_write32(struct ipw_priv *ipw, unsigned long ofs, + u32 val) +{ + writel(val, ipw->hw_base + ofs); +} + +/* 32-bit direct write (for low 4K of SRAM/regs), with debug wrapper */ +#define ipw_write32(ipw, ofs, val) do { \ + IPW_DEBUG_IO("%s %d: write_direct32(0x%08X, 0x%08X)\n", __FILE__, \ + __LINE__, (u32)(ofs), (u32)(val)); \ + _ipw_write32(ipw, ofs, val); \ +} while (0) + +/* 8-bit direct read (low 4K) */ +static inline u8 _ipw_read8(struct ipw_priv *ipw, unsigned long ofs) +{ + return readb(ipw->hw_base + ofs); +} + +/* alias to 8-bit direct read (low 4K of SRAM/regs), with debug wrapper */ +#define ipw_read8(ipw, ofs) ({ \ + IPW_DEBUG_IO("%s %d: read_direct8(0x%08X)\n", __FILE__, __LINE__, \ + (u32)(ofs)); \ + _ipw_read8(ipw, ofs); \ +}) + +/* 16-bit direct read (low 4K) */ +static inline u16 _ipw_read16(struct ipw_priv *ipw, unsigned long ofs) +{ + return readw(ipw->hw_base + ofs); +} + +/* alias to 16-bit direct read (low 4K of SRAM/regs), with debug wrapper */ +#define ipw_read16(ipw, ofs) ({ \ + IPW_DEBUG_IO("%s %d: read_direct16(0x%08X)\n", __FILE__, __LINE__, \ + (u32)(ofs)); \ + _ipw_read16(ipw, ofs); \ +}) + +/* 32-bit direct read (low 4K) */ +static inline u32 _ipw_read32(struct ipw_priv *ipw, unsigned long ofs) +{ + return readl(ipw->hw_base + ofs); +} + +/* alias to 32-bit direct read (low 4K of SRAM/regs), with debug wrapper */ +#define ipw_read32(ipw, ofs) ({ \ + IPW_DEBUG_IO("%s %d: read_direct32(0x%08X)\n", __FILE__, __LINE__, \ + (u32)(ofs)); \ + _ipw_read32(ipw, ofs); \ +}) + +static void _ipw_read_indirect(struct ipw_priv *, u32, u8 *, int); +/* alias to multi-byte read (SRAM/regs above 4K), with debug wrapper */ +#define ipw_read_indirect(a, b, c, d) ({ \ + IPW_DEBUG_IO("%s %d: read_indirect(0x%08X) %u bytes\n", __FILE__, \ + __LINE__, (u32)(b), (u32)(d)); \ + _ipw_read_indirect(a, b, c, d); \ +}) + +/* alias to multi-byte read (SRAM/regs above 4K), with debug wrapper */ +static void _ipw_write_indirect(struct ipw_priv *priv, u32 addr, u8 * data, + int num); +#define ipw_write_indirect(a, b, c, d) do { \ + IPW_DEBUG_IO("%s %d: write_indirect(0x%08X) %u bytes\n", __FILE__, \ + __LINE__, (u32)(b), (u32)(d)); \ + _ipw_write_indirect(a, b, c, d); \ +} while (0) + +/* 32-bit indirect write (above 4K) */ +static void _ipw_write_reg32(struct ipw_priv *priv, u32 reg, u32 value) +{ + IPW_DEBUG_IO(" %p : reg = 0x%8X : value = 0x%8X\n", priv, reg, value); + _ipw_write32(priv, IPW_INDIRECT_ADDR, reg); + _ipw_write32(priv, IPW_INDIRECT_DATA, value); +} + +/* 8-bit indirect write (above 4K) */ +static void _ipw_write_reg8(struct ipw_priv *priv, u32 reg, u8 value) +{ + u32 aligned_addr = reg & IPW_INDIRECT_ADDR_MASK; /* dword align */ + u32 dif_len = reg - aligned_addr; + + IPW_DEBUG_IO(" reg = 0x%8X : value = 0x%8X\n", reg, value); + _ipw_write32(priv, IPW_INDIRECT_ADDR, aligned_addr); + _ipw_write8(priv, IPW_INDIRECT_DATA + dif_len, value); +} + +/* 16-bit indirect write (above 4K) */ +static void _ipw_write_reg16(struct ipw_priv *priv, u32 reg, u16 value) +{ + u32 aligned_addr = reg & IPW_INDIRECT_ADDR_MASK; /* dword align */ + u32 dif_len = (reg - aligned_addr) & (~0x1ul); + + IPW_DEBUG_IO(" reg = 0x%8X : value = 0x%8X\n", reg, value); + _ipw_write32(priv, IPW_INDIRECT_ADDR, aligned_addr); + _ipw_write16(priv, IPW_INDIRECT_DATA + dif_len, value); +} + +/* 8-bit indirect read (above 4K) */ +static u8 _ipw_read_reg8(struct ipw_priv *priv, u32 reg) +{ + u32 word; + _ipw_write32(priv, IPW_INDIRECT_ADDR, reg & IPW_INDIRECT_ADDR_MASK); + IPW_DEBUG_IO(" reg = 0x%8X :\n", reg); + word = _ipw_read32(priv, IPW_INDIRECT_DATA); + return (word >> ((reg & 0x3) * 8)) & 0xff; +} + +/* 32-bit indirect read (above 4K) */ +static u32 _ipw_read_reg32(struct ipw_priv *priv, u32 reg) +{ + u32 value; + + IPW_DEBUG_IO("%p : reg = 0x%08x\n", priv, reg); + + _ipw_write32(priv, IPW_INDIRECT_ADDR, reg); + value = _ipw_read32(priv, IPW_INDIRECT_DATA); + IPW_DEBUG_IO(" reg = 0x%4X : value = 0x%4x\n", reg, value); + return value; +} + +/* General purpose, no alignment requirement, iterative (multi-byte) read, */ +/* for area above 1st 4K of SRAM/reg space */ +static void _ipw_read_indirect(struct ipw_priv *priv, u32 addr, u8 * buf, + int num) +{ + u32 aligned_addr = addr & IPW_INDIRECT_ADDR_MASK; /* dword align */ + u32 dif_len = addr - aligned_addr; + u32 i; + + IPW_DEBUG_IO("addr = %i, buf = %p, num = %i\n", addr, buf, num); + + if (num <= 0) { + return; + } + + /* Read the first dword (or portion) byte by byte */ + if (unlikely(dif_len)) { + _ipw_write32(priv, IPW_INDIRECT_ADDR, aligned_addr); + /* Start reading at aligned_addr + dif_len */ + for (i = dif_len; ((i < 4) && (num > 0)); i++, num--) + *buf++ = _ipw_read8(priv, IPW_INDIRECT_DATA + i); + aligned_addr += 4; + } + + /* Read all of the middle dwords as dwords, with auto-increment */ + _ipw_write32(priv, IPW_AUTOINC_ADDR, aligned_addr); + for (; num >= 4; buf += 4, aligned_addr += 4, num -= 4) + *(u32 *) buf = _ipw_read32(priv, IPW_AUTOINC_DATA); + + /* Read the last dword (or portion) byte by byte */ + if (unlikely(num)) { + _ipw_write32(priv, IPW_INDIRECT_ADDR, aligned_addr); + for (i = 0; num > 0; i++, num--) + *buf++ = ipw_read8(priv, IPW_INDIRECT_DATA + i); + } +} + +/* General purpose, no alignment requirement, iterative (multi-byte) write, */ +/* for area above 1st 4K of SRAM/reg space */ +static void _ipw_write_indirect(struct ipw_priv *priv, u32 addr, u8 * buf, + int num) +{ + u32 aligned_addr = addr & IPW_INDIRECT_ADDR_MASK; /* dword align */ + u32 dif_len = addr - aligned_addr; + u32 i; + + IPW_DEBUG_IO("addr = %i, buf = %p, num = %i\n", addr, buf, num); + + if (num <= 0) { + return; + } + + /* Write the first dword (or portion) byte by byte */ + if (unlikely(dif_len)) { + _ipw_write32(priv, IPW_INDIRECT_ADDR, aligned_addr); + /* Start writing at aligned_addr + dif_len */ + for (i = dif_len; ((i < 4) && (num > 0)); i++, num--, buf++) + _ipw_write8(priv, IPW_INDIRECT_DATA + i, *buf); + aligned_addr += 4; + } + + /* Write all of the middle dwords as dwords, with auto-increment */ + _ipw_write32(priv, IPW_AUTOINC_ADDR, aligned_addr); + for (; num >= 4; buf += 4, aligned_addr += 4, num -= 4) + _ipw_write32(priv, IPW_AUTOINC_DATA, *(u32 *) buf); + + /* Write the last dword (or portion) byte by byte */ + if (unlikely(num)) { + _ipw_write32(priv, IPW_INDIRECT_ADDR, aligned_addr); + for (i = 0; num > 0; i++, num--, buf++) + _ipw_write8(priv, IPW_INDIRECT_DATA + i, *buf); + } +} + +/* General purpose, no alignment requirement, iterative (multi-byte) write, */ +/* for 1st 4K of SRAM/regs space */ +static void ipw_write_direct(struct ipw_priv *priv, u32 addr, void *buf, + int num) +{ + memcpy_toio((priv->hw_base + addr), buf, num); +} + +/* Set bit(s) in low 4K of SRAM/regs */ +static inline void ipw_set_bit(struct ipw_priv *priv, u32 reg, u32 mask) +{ + ipw_write32(priv, reg, ipw_read32(priv, reg) | mask); +} + +/* Clear bit(s) in low 4K of SRAM/regs */ +static inline void ipw_clear_bit(struct ipw_priv *priv, u32 reg, u32 mask) +{ + ipw_write32(priv, reg, ipw_read32(priv, reg) & ~mask); +} + +static inline void __ipw_enable_interrupts(struct ipw_priv *priv) +{ + if (priv->status & STATUS_INT_ENABLED) + return; + priv->status |= STATUS_INT_ENABLED; + ipw_write32(priv, IPW_INTA_MASK_R, IPW_INTA_MASK_ALL); +} + +static inline void __ipw_disable_interrupts(struct ipw_priv *priv) +{ + if (!(priv->status & STATUS_INT_ENABLED)) + return; + priv->status &= ~STATUS_INT_ENABLED; + ipw_write32(priv, IPW_INTA_MASK_R, ~IPW_INTA_MASK_ALL); +} + +static inline void ipw_enable_interrupts(struct ipw_priv *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + __ipw_enable_interrupts(priv); + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static inline void ipw_disable_interrupts(struct ipw_priv *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->irq_lock, flags); + __ipw_disable_interrupts(priv); + spin_unlock_irqrestore(&priv->irq_lock, flags); +} + +static char *ipw_error_desc(u32 val) +{ + switch (val) { + case IPW_FW_ERROR_OK: + return "ERROR_OK"; + case IPW_FW_ERROR_FAIL: + return "ERROR_FAIL"; + case IPW_FW_ERROR_MEMORY_UNDERFLOW: + return "MEMORY_UNDERFLOW"; + case IPW_FW_ERROR_MEMORY_OVERFLOW: + return "MEMORY_OVERFLOW"; + case IPW_FW_ERROR_BAD_PARAM: + return "BAD_PARAM"; + case IPW_FW_ERROR_BAD_CHECKSUM: + return "BAD_CHECKSUM"; + case IPW_FW_ERROR_NMI_INTERRUPT: + return "NMI_INTERRUPT"; + case IPW_FW_ERROR_BAD_DATABASE: + return "BAD_DATABASE"; + case IPW_FW_ERROR_ALLOC_FAIL: + return "ALLOC_FAIL"; + case IPW_FW_ERROR_DMA_UNDERRUN: + return "DMA_UNDERRUN"; + case IPW_FW_ERROR_DMA_STATUS: + return "DMA_STATUS"; + case IPW_FW_ERROR_DINO_ERROR: + return "DINO_ERROR"; + case IPW_FW_ERROR_EEPROM_ERROR: + return "EEPROM_ERROR"; + case IPW_FW_ERROR_SYSASSERT: + return "SYSASSERT"; + case IPW_FW_ERROR_FATAL_ERROR: + return "FATAL_ERROR"; + default: + return "UNKNOWN_ERROR"; + } +} + +static void ipw_dump_error_log(struct ipw_priv *priv, + struct ipw_fw_error *error) +{ + u32 i; + + if (!error) { + IPW_ERROR("Error allocating and capturing error log. " + "Nothing to dump.\n"); + return; + } + + IPW_ERROR("Start IPW Error Log Dump:\n"); + IPW_ERROR("Status: 0x%08X, Config: %08X\n", + error->status, error->config); + + for (i = 0; i < error->elem_len; i++) + IPW_ERROR("%s %i 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n", + ipw_error_desc(error->elem[i].desc), + error->elem[i].time, + error->elem[i].blink1, + error->elem[i].blink2, + error->elem[i].link1, + error->elem[i].link2, error->elem[i].data); + for (i = 0; i < error->log_len; i++) + IPW_ERROR("%i\t0x%08x\t%i\n", + error->log[i].time, + error->log[i].data, error->log[i].event); +} + +static inline int ipw_is_init(struct ipw_priv *priv) +{ + return (priv->status & STATUS_INIT) ? 1 : 0; +} + +static int ipw_get_ordinal(struct ipw_priv *priv, u32 ord, void *val, u32 * len) +{ + u32 addr, field_info, field_len, field_count, total_len; + + IPW_DEBUG_ORD("ordinal = %i\n", ord); + + if (!priv || !val || !len) { + IPW_DEBUG_ORD("Invalid argument\n"); + return -EINVAL; + } + + /* verify device ordinal tables have been initialized */ + if (!priv->table0_addr || !priv->table1_addr || !priv->table2_addr) { + IPW_DEBUG_ORD("Access ordinals before initialization\n"); + return -EINVAL; + } + + switch (IPW_ORD_TABLE_ID_MASK & ord) { + case IPW_ORD_TABLE_0_MASK: + /* + * TABLE 0: Direct access to a table of 32 bit values + * + * This is a very simple table with the data directly + * read from the table + */ + + /* remove the table id from the ordinal */ + ord &= IPW_ORD_TABLE_VALUE_MASK; + + /* boundary check */ + if (ord > priv->table0_len) { + IPW_DEBUG_ORD("ordinal value (%i) longer then " + "max (%i)\n", ord, priv->table0_len); + return -EINVAL; + } + + /* verify we have enough room to store the value */ + if (*len < sizeof(u32)) { + IPW_DEBUG_ORD("ordinal buffer length too small, " + "need %zd\n", sizeof(u32)); + return -EINVAL; + } + + IPW_DEBUG_ORD("Reading TABLE0[%i] from offset 0x%08x\n", + ord, priv->table0_addr + (ord << 2)); + + *len = sizeof(u32); + ord <<= 2; + *((u32 *) val) = ipw_read32(priv, priv->table0_addr + ord); + break; + + case IPW_ORD_TABLE_1_MASK: + /* + * TABLE 1: Indirect access to a table of 32 bit values + * + * This is a fairly large table of u32 values each + * representing starting addr for the data (which is + * also a u32) + */ + + /* remove the table id from the ordinal */ + ord &= IPW_ORD_TABLE_VALUE_MASK; + + /* boundary check */ + if (ord > priv->table1_len) { + IPW_DEBUG_ORD("ordinal value too long\n"); + return -EINVAL; + } + + /* verify we have enough room to store the value */ + if (*len < sizeof(u32)) { + IPW_DEBUG_ORD("ordinal buffer length too small, " + "need %zd\n", sizeof(u32)); + return -EINVAL; + } + + *((u32 *) val) = + ipw_read_reg32(priv, (priv->table1_addr + (ord << 2))); + *len = sizeof(u32); + break; + + case IPW_ORD_TABLE_2_MASK: + /* + * TABLE 2: Indirect access to a table of variable sized values + * + * This table consist of six values, each containing + * - dword containing the starting offset of the data + * - dword containing the lengh in the first 16bits + * and the count in the second 16bits + */ + + /* remove the table id from the ordinal */ + ord &= IPW_ORD_TABLE_VALUE_MASK; + + /* boundary check */ + if (ord > priv->table2_len) { + IPW_DEBUG_ORD("ordinal value too long\n"); + return -EINVAL; + } + + /* get the address of statistic */ + addr = ipw_read_reg32(priv, priv->table2_addr + (ord << 3)); + + /* get the second DW of statistics ; + * two 16-bit words - first is length, second is count */ + field_info = + ipw_read_reg32(priv, + priv->table2_addr + (ord << 3) + + sizeof(u32)); + + /* get each entry length */ + field_len = *((u16 *) & field_info); + + /* get number of entries */ + field_count = *(((u16 *) & field_info) + 1); + + /* abort if not enough memory */ + total_len = field_len * field_count; + if (total_len > *len) { + *len = total_len; + return -EINVAL; + } + + *len = total_len; + if (!total_len) + return 0; + + IPW_DEBUG_ORD("addr = 0x%08x, total_len = %i, " + "field_info = 0x%08x\n", + addr, total_len, field_info); + ipw_read_indirect(priv, addr, val, total_len); + break; + + default: + IPW_DEBUG_ORD("Invalid ordinal!\n"); + return -EINVAL; + + } + + return 0; +} + +static void ipw_init_ordinals(struct ipw_priv *priv) +{ + priv->table0_addr = IPW_ORDINALS_TABLE_LOWER; + priv->table0_len = ipw_read32(priv, priv->table0_addr); + + IPW_DEBUG_ORD("table 0 offset at 0x%08x, len = %i\n", + priv->table0_addr, priv->table0_len); + + priv->table1_addr = ipw_read32(priv, IPW_ORDINALS_TABLE_1); + priv->table1_len = ipw_read_reg32(priv, priv->table1_addr); + + IPW_DEBUG_ORD("table 1 offset at 0x%08x, len = %i\n", + priv->table1_addr, priv->table1_len); + + priv->table2_addr = ipw_read32(priv, IPW_ORDINALS_TABLE_2); + priv->table2_len = ipw_read_reg32(priv, priv->table2_addr); + priv->table2_len &= 0x0000ffff; /* use first two bytes */ + + IPW_DEBUG_ORD("table 2 offset at 0x%08x, len = %i\n", + priv->table2_addr, priv->table2_len); + +} + +static u32 ipw_register_toggle(u32 reg) +{ + reg &= ~IPW_START_STANDBY; + if (reg & IPW_GATE_ODMA) + reg &= ~IPW_GATE_ODMA; + if (reg & IPW_GATE_IDMA) + reg &= ~IPW_GATE_IDMA; + if (reg & IPW_GATE_ADMA) + reg &= ~IPW_GATE_ADMA; + return reg; +} + +/* + * LED behavior: + * - On radio ON, turn on any LEDs that require to be on during start + * - On initialization, start unassociated blink + * - On association, disable unassociated blink + * - On disassociation, start unassociated blink + * - On radio OFF, turn off any LEDs started during radio on + * + */ +#define LD_TIME_LINK_ON msecs_to_jiffies(300) +#define LD_TIME_LINK_OFF msecs_to_jiffies(2700) +#define LD_TIME_ACT_ON msecs_to_jiffies(250) + +static void ipw_led_link_on(struct ipw_priv *priv) +{ + unsigned long flags; + u32 led; + + /* If configured to not use LEDs, or nic_type is 1, + * then we don't toggle a LINK led */ + if (priv->config & CFG_NO_LED || priv->nic_type == EEPROM_NIC_TYPE_1) + return; + + spin_lock_irqsave(&priv->lock, flags); + + if (!(priv->status & STATUS_RF_KILL_MASK) && + !(priv->status & STATUS_LED_LINK_ON)) { + IPW_DEBUG_LED("Link LED On\n"); + led = ipw_read_reg32(priv, IPW_EVENT_REG); + led |= priv->led_association_on; + + led = ipw_register_toggle(led); + + IPW_DEBUG_LED("Reg: 0x%08X\n", led); + ipw_write_reg32(priv, IPW_EVENT_REG, led); + + priv->status |= STATUS_LED_LINK_ON; + + /* If we aren't associated, schedule turning the LED off */ + if (!(priv->status & STATUS_ASSOCIATED)) + schedule_delayed_work(&priv->led_link_off, + LD_TIME_LINK_ON); + } + + spin_unlock_irqrestore(&priv->lock, flags); +} + +static void ipw_bg_led_link_on(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, led_link_on.work); + mutex_lock(&priv->mutex); + ipw_led_link_on(priv); + mutex_unlock(&priv->mutex); +} + +static void ipw_led_link_off(struct ipw_priv *priv) +{ + unsigned long flags; + u32 led; + + /* If configured not to use LEDs, or nic type is 1, + * then we don't goggle the LINK led. */ + if (priv->config & CFG_NO_LED || priv->nic_type == EEPROM_NIC_TYPE_1) + return; + + spin_lock_irqsave(&priv->lock, flags); + + if (priv->status & STATUS_LED_LINK_ON) { + led = ipw_read_reg32(priv, IPW_EVENT_REG); + led &= priv->led_association_off; + led = ipw_register_toggle(led); + + IPW_DEBUG_LED("Reg: 0x%08X\n", led); + ipw_write_reg32(priv, IPW_EVENT_REG, led); + + IPW_DEBUG_LED("Link LED Off\n"); + + priv->status &= ~STATUS_LED_LINK_ON; + + /* If we aren't associated and the radio is on, schedule + * turning the LED on (blink while unassociated) */ + if (!(priv->status & STATUS_RF_KILL_MASK) && + !(priv->status & STATUS_ASSOCIATED)) + schedule_delayed_work(&priv->led_link_on, + LD_TIME_LINK_OFF); + + } + + spin_unlock_irqrestore(&priv->lock, flags); +} + +static void ipw_bg_led_link_off(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, led_link_off.work); + mutex_lock(&priv->mutex); + ipw_led_link_off(priv); + mutex_unlock(&priv->mutex); +} + +static void __ipw_led_activity_on(struct ipw_priv *priv) +{ + u32 led; + + if (priv->config & CFG_NO_LED) + return; + + if (priv->status & STATUS_RF_KILL_MASK) + return; + + if (!(priv->status & STATUS_LED_ACT_ON)) { + led = ipw_read_reg32(priv, IPW_EVENT_REG); + led |= priv->led_activity_on; + + led = ipw_register_toggle(led); + + IPW_DEBUG_LED("Reg: 0x%08X\n", led); + ipw_write_reg32(priv, IPW_EVENT_REG, led); + + IPW_DEBUG_LED("Activity LED On\n"); + + priv->status |= STATUS_LED_ACT_ON; + + cancel_delayed_work(&priv->led_act_off); + schedule_delayed_work(&priv->led_act_off, LD_TIME_ACT_ON); + } else { + /* Reschedule LED off for full time period */ + cancel_delayed_work(&priv->led_act_off); + schedule_delayed_work(&priv->led_act_off, LD_TIME_ACT_ON); + } +} + +#if 0 +void ipw_led_activity_on(struct ipw_priv *priv) +{ + unsigned long flags; + spin_lock_irqsave(&priv->lock, flags); + __ipw_led_activity_on(priv); + spin_unlock_irqrestore(&priv->lock, flags); +} +#endif /* 0 */ + +static void ipw_led_activity_off(struct ipw_priv *priv) +{ + unsigned long flags; + u32 led; + + if (priv->config & CFG_NO_LED) + return; + + spin_lock_irqsave(&priv->lock, flags); + + if (priv->status & STATUS_LED_ACT_ON) { + led = ipw_read_reg32(priv, IPW_EVENT_REG); + led &= priv->led_activity_off; + + led = ipw_register_toggle(led); + + IPW_DEBUG_LED("Reg: 0x%08X\n", led); + ipw_write_reg32(priv, IPW_EVENT_REG, led); + + IPW_DEBUG_LED("Activity LED Off\n"); + + priv->status &= ~STATUS_LED_ACT_ON; + } + + spin_unlock_irqrestore(&priv->lock, flags); +} + +static void ipw_bg_led_activity_off(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, led_act_off.work); + mutex_lock(&priv->mutex); + ipw_led_activity_off(priv); + mutex_unlock(&priv->mutex); +} + +static void ipw_led_band_on(struct ipw_priv *priv) +{ + unsigned long flags; + u32 led; + + /* Only nic type 1 supports mode LEDs */ + if (priv->config & CFG_NO_LED || + priv->nic_type != EEPROM_NIC_TYPE_1 || !priv->assoc_network) + return; + + spin_lock_irqsave(&priv->lock, flags); + + led = ipw_read_reg32(priv, IPW_EVENT_REG); + if (priv->assoc_network->mode == IEEE_A) { + led |= priv->led_ofdm_on; + led &= priv->led_association_off; + IPW_DEBUG_LED("Mode LED On: 802.11a\n"); + } else if (priv->assoc_network->mode == IEEE_G) { + led |= priv->led_ofdm_on; + led |= priv->led_association_on; + IPW_DEBUG_LED("Mode LED On: 802.11g\n"); + } else { + led &= priv->led_ofdm_off; + led |= priv->led_association_on; + IPW_DEBUG_LED("Mode LED On: 802.11b\n"); + } + + led = ipw_register_toggle(led); + + IPW_DEBUG_LED("Reg: 0x%08X\n", led); + ipw_write_reg32(priv, IPW_EVENT_REG, led); + + spin_unlock_irqrestore(&priv->lock, flags); +} + +static void ipw_led_band_off(struct ipw_priv *priv) +{ + unsigned long flags; + u32 led; + + /* Only nic type 1 supports mode LEDs */ + if (priv->config & CFG_NO_LED || priv->nic_type != EEPROM_NIC_TYPE_1) + return; + + spin_lock_irqsave(&priv->lock, flags); + + led = ipw_read_reg32(priv, IPW_EVENT_REG); + led &= priv->led_ofdm_off; + led &= priv->led_association_off; + + led = ipw_register_toggle(led); + + IPW_DEBUG_LED("Reg: 0x%08X\n", led); + ipw_write_reg32(priv, IPW_EVENT_REG, led); + + spin_unlock_irqrestore(&priv->lock, flags); +} + +static void ipw_led_radio_on(struct ipw_priv *priv) +{ + ipw_led_link_on(priv); +} + +static void ipw_led_radio_off(struct ipw_priv *priv) +{ + ipw_led_activity_off(priv); + ipw_led_link_off(priv); +} + +static void ipw_led_link_up(struct ipw_priv *priv) +{ + /* Set the Link Led on for all nic types */ + ipw_led_link_on(priv); +} + +static void ipw_led_link_down(struct ipw_priv *priv) +{ + ipw_led_activity_off(priv); + ipw_led_link_off(priv); + + if (priv->status & STATUS_RF_KILL_MASK) + ipw_led_radio_off(priv); +} + +static void ipw_led_init(struct ipw_priv *priv) +{ + priv->nic_type = priv->eeprom[EEPROM_NIC_TYPE]; + + /* Set the default PINs for the link and activity leds */ + priv->led_activity_on = IPW_ACTIVITY_LED; + priv->led_activity_off = ~(IPW_ACTIVITY_LED); + + priv->led_association_on = IPW_ASSOCIATED_LED; + priv->led_association_off = ~(IPW_ASSOCIATED_LED); + + /* Set the default PINs for the OFDM leds */ + priv->led_ofdm_on = IPW_OFDM_LED; + priv->led_ofdm_off = ~(IPW_OFDM_LED); + + switch (priv->nic_type) { + case EEPROM_NIC_TYPE_1: + /* In this NIC type, the LEDs are reversed.... */ + priv->led_activity_on = IPW_ASSOCIATED_LED; + priv->led_activity_off = ~(IPW_ASSOCIATED_LED); + priv->led_association_on = IPW_ACTIVITY_LED; + priv->led_association_off = ~(IPW_ACTIVITY_LED); + + if (!(priv->config & CFG_NO_LED)) + ipw_led_band_on(priv); + + /* And we don't blink link LEDs for this nic, so + * just return here */ + return; + + case EEPROM_NIC_TYPE_3: + case EEPROM_NIC_TYPE_2: + case EEPROM_NIC_TYPE_4: + case EEPROM_NIC_TYPE_0: + break; + + default: + IPW_DEBUG_INFO("Unknown NIC type from EEPROM: %d\n", + priv->nic_type); + priv->nic_type = EEPROM_NIC_TYPE_0; + break; + } + + if (!(priv->config & CFG_NO_LED)) { + if (priv->status & STATUS_ASSOCIATED) + ipw_led_link_on(priv); + else + ipw_led_link_off(priv); + } +} + +static void ipw_led_shutdown(struct ipw_priv *priv) +{ + ipw_led_activity_off(priv); + ipw_led_link_off(priv); + ipw_led_band_off(priv); + cancel_delayed_work(&priv->led_link_on); + cancel_delayed_work(&priv->led_link_off); + cancel_delayed_work(&priv->led_act_off); +} + +/* + * The following adds a new attribute to the sysfs representation + * of this device driver (i.e. a new file in /sys/bus/pci/drivers/ipw/) + * used for controlling the debug level. + * + * See the level definitions in ipw for details. + */ +static ssize_t show_debug_level(struct device_driver *d, char *buf) +{ + return sprintf(buf, "0x%08X\n", ipw_debug_level); +} + +static ssize_t store_debug_level(struct device_driver *d, const char *buf, + size_t count) +{ + char *p = (char *)buf; + u32 val; + + if (p[1] == 'x' || p[1] == 'X' || p[0] == 'x' || p[0] == 'X') { + p++; + if (p[0] == 'x' || p[0] == 'X') + p++; + val = simple_strtoul(p, &p, 16); + } else + val = simple_strtoul(p, &p, 10); + if (p == buf) + printk(KERN_INFO DRV_NAME + ": %s is not in hex or decimal form.\n", buf); + else + ipw_debug_level = val; + + return strnlen(buf, count); +} + +static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO, + show_debug_level, store_debug_level); + +static inline u32 ipw_get_event_log_len(struct ipw_priv *priv) +{ + /* length = 1st dword in log */ + return ipw_read_reg32(priv, ipw_read32(priv, IPW_EVENT_LOG)); +} + +static void ipw_capture_event_log(struct ipw_priv *priv, + u32 log_len, struct ipw_event *log) +{ + u32 base; + + if (log_len) { + base = ipw_read32(priv, IPW_EVENT_LOG); + ipw_read_indirect(priv, base + sizeof(base) + sizeof(u32), + (u8 *) log, sizeof(*log) * log_len); + } +} + +static struct ipw_fw_error *ipw_alloc_error_log(struct ipw_priv *priv) +{ + struct ipw_fw_error *error; + u32 log_len = ipw_get_event_log_len(priv); + u32 base = ipw_read32(priv, IPW_ERROR_LOG); + u32 elem_len = ipw_read_reg32(priv, base); + + error = kmalloc(sizeof(*error) + + sizeof(*error->elem) * elem_len + + sizeof(*error->log) * log_len, GFP_ATOMIC); + if (!error) { + IPW_ERROR("Memory allocation for firmware error log " + "failed.\n"); + return NULL; + } + error->jiffies = jiffies; + error->status = priv->status; + error->config = priv->config; + error->elem_len = elem_len; + error->log_len = log_len; + error->elem = (struct ipw_error_elem *)error->payload; + error->log = (struct ipw_event *)(error->elem + elem_len); + + ipw_capture_event_log(priv, log_len, error->log); + + if (elem_len) + ipw_read_indirect(priv, base + sizeof(base), (u8 *) error->elem, + sizeof(*error->elem) * elem_len); + + return error; +} + +static ssize_t show_event_log(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + u32 log_len = ipw_get_event_log_len(priv); + u32 log_size; + struct ipw_event *log; + u32 len = 0, i; + + /* not using min() because of its strict type checking */ + log_size = PAGE_SIZE / sizeof(*log) > log_len ? + sizeof(*log) * log_len : PAGE_SIZE; + log = kzalloc(log_size, GFP_KERNEL); + if (!log) { + IPW_ERROR("Unable to allocate memory for log\n"); + return 0; + } + log_len = log_size / sizeof(*log); + ipw_capture_event_log(priv, log_len, log); + + len += snprintf(buf + len, PAGE_SIZE - len, "%08X", log_len); + for (i = 0; i < log_len; i++) + len += snprintf(buf + len, PAGE_SIZE - len, + "\n%08X%08X%08X", + log[i].time, log[i].event, log[i].data); + len += snprintf(buf + len, PAGE_SIZE - len, "\n"); + kfree(log); + return len; +} + +static DEVICE_ATTR(event_log, S_IRUGO, show_event_log, NULL); + +static ssize_t show_error(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + u32 len = 0, i; + if (!priv->error) + return 0; + len += snprintf(buf + len, PAGE_SIZE - len, + "%08lX%08X%08X%08X", + priv->error->jiffies, + priv->error->status, + priv->error->config, priv->error->elem_len); + for (i = 0; i < priv->error->elem_len; i++) + len += snprintf(buf + len, PAGE_SIZE - len, + "\n%08X%08X%08X%08X%08X%08X%08X", + priv->error->elem[i].time, + priv->error->elem[i].desc, + priv->error->elem[i].blink1, + priv->error->elem[i].blink2, + priv->error->elem[i].link1, + priv->error->elem[i].link2, + priv->error->elem[i].data); + + len += snprintf(buf + len, PAGE_SIZE - len, + "\n%08X", priv->error->log_len); + for (i = 0; i < priv->error->log_len; i++) + len += snprintf(buf + len, PAGE_SIZE - len, + "\n%08X%08X%08X", + priv->error->log[i].time, + priv->error->log[i].event, + priv->error->log[i].data); + len += snprintf(buf + len, PAGE_SIZE - len, "\n"); + return len; +} + +static ssize_t clear_error(struct device *d, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + + kfree(priv->error); + priv->error = NULL; + return count; +} + +static DEVICE_ATTR(error, S_IRUGO | S_IWUSR, show_error, clear_error); + +static ssize_t show_cmd_log(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + u32 len = 0, i; + if (!priv->cmdlog) + return 0; + for (i = (priv->cmdlog_pos + 1) % priv->cmdlog_len; + (i != priv->cmdlog_pos) && (PAGE_SIZE - len); + i = (i + 1) % priv->cmdlog_len) { + len += + snprintf(buf + len, PAGE_SIZE - len, + "\n%08lX%08X%08X%08X\n", priv->cmdlog[i].jiffies, + priv->cmdlog[i].retcode, priv->cmdlog[i].cmd.cmd, + priv->cmdlog[i].cmd.len); + len += + snprintk_buf(buf + len, PAGE_SIZE - len, + (u8 *) priv->cmdlog[i].cmd.param, + priv->cmdlog[i].cmd.len); + len += snprintf(buf + len, PAGE_SIZE - len, "\n"); + } + len += snprintf(buf + len, PAGE_SIZE - len, "\n"); + return len; +} + +static DEVICE_ATTR(cmd_log, S_IRUGO, show_cmd_log, NULL); + +#ifdef CPTCFG_IPW2200_PROMISCUOUS +static void ipw_prom_free(struct ipw_priv *priv); +static int ipw_prom_alloc(struct ipw_priv *priv); +static ssize_t store_rtap_iface(struct device *d, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + int rc = 0; + + if (count < 1) + return -EINVAL; + + switch (buf[0]) { + case '0': + if (!rtap_iface) + return count; + + if (netif_running(priv->prom_net_dev)) { + IPW_WARNING("Interface is up. Cannot unregister.\n"); + return count; + } + + ipw_prom_free(priv); + rtap_iface = 0; + break; + + case '1': + if (rtap_iface) + return count; + + rc = ipw_prom_alloc(priv); + if (!rc) + rtap_iface = 1; + break; + + default: + return -EINVAL; + } + + if (rc) { + IPW_ERROR("Failed to register promiscuous network " + "device (error %d).\n", rc); + } + + return count; +} + +static ssize_t show_rtap_iface(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + if (rtap_iface) + return sprintf(buf, "%s", priv->prom_net_dev->name); + else { + buf[0] = '-'; + buf[1] = '1'; + buf[2] = '\0'; + return 3; + } +} + +static DEVICE_ATTR(rtap_iface, S_IWUSR | S_IRUSR, show_rtap_iface, + store_rtap_iface); + +static ssize_t store_rtap_filter(struct device *d, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + + if (!priv->prom_priv) { + IPW_ERROR("Attempting to set filter without " + "rtap_iface enabled.\n"); + return -EPERM; + } + + priv->prom_priv->filter = simple_strtol(buf, NULL, 0); + + IPW_DEBUG_INFO("Setting rtap filter to " BIT_FMT16 "\n", + BIT_ARG16(priv->prom_priv->filter)); + + return count; +} + +static ssize_t show_rtap_filter(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + return sprintf(buf, "0x%04X", + priv->prom_priv ? priv->prom_priv->filter : 0); +} + +static DEVICE_ATTR(rtap_filter, S_IWUSR | S_IRUSR, show_rtap_filter, + store_rtap_filter); +#endif + +static ssize_t show_scan_age(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + return sprintf(buf, "%d\n", priv->ieee->scan_age); +} + +static ssize_t store_scan_age(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + struct net_device *dev = priv->net_dev; + char buffer[] = "00000000"; + unsigned long len = + (sizeof(buffer) - 1) > count ? count : sizeof(buffer) - 1; + unsigned long val; + char *p = buffer; + + IPW_DEBUG_INFO("enter\n"); + + strncpy(buffer, buf, len); + buffer[len] = 0; + + if (p[1] == 'x' || p[1] == 'X' || p[0] == 'x' || p[0] == 'X') { + p++; + if (p[0] == 'x' || p[0] == 'X') + p++; + val = simple_strtoul(p, &p, 16); + } else + val = simple_strtoul(p, &p, 10); + if (p == buffer) { + IPW_DEBUG_INFO("%s: user supplied invalid value.\n", dev->name); + } else { + priv->ieee->scan_age = val; + IPW_DEBUG_INFO("set scan_age = %u\n", priv->ieee->scan_age); + } + + IPW_DEBUG_INFO("exit\n"); + return len; +} + +static DEVICE_ATTR(scan_age, S_IWUSR | S_IRUGO, show_scan_age, store_scan_age); + +static ssize_t show_led(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + return sprintf(buf, "%d\n", (priv->config & CFG_NO_LED) ? 0 : 1); +} + +static ssize_t store_led(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + + IPW_DEBUG_INFO("enter\n"); + + if (count == 0) + return 0; + + if (*buf == 0) { + IPW_DEBUG_LED("Disabling LED control.\n"); + priv->config |= CFG_NO_LED; + ipw_led_shutdown(priv); + } else { + IPW_DEBUG_LED("Enabling LED control.\n"); + priv->config &= ~CFG_NO_LED; + ipw_led_init(priv); + } + + IPW_DEBUG_INFO("exit\n"); + return count; +} + +static DEVICE_ATTR(led, S_IWUSR | S_IRUGO, show_led, store_led); + +static ssize_t show_status(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct ipw_priv *p = dev_get_drvdata(d); + return sprintf(buf, "0x%08x\n", (int)p->status); +} + +static DEVICE_ATTR(status, S_IRUGO, show_status, NULL); + +static ssize_t show_cfg(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct ipw_priv *p = dev_get_drvdata(d); + return sprintf(buf, "0x%08x\n", (int)p->config); +} + +static DEVICE_ATTR(cfg, S_IRUGO, show_cfg, NULL); + +static ssize_t show_nic_type(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + return sprintf(buf, "TYPE: %d\n", priv->nic_type); +} + +static DEVICE_ATTR(nic_type, S_IRUGO, show_nic_type, NULL); + +static ssize_t show_ucode_version(struct device *d, + struct device_attribute *attr, char *buf) +{ + u32 len = sizeof(u32), tmp = 0; + struct ipw_priv *p = dev_get_drvdata(d); + + if (ipw_get_ordinal(p, IPW_ORD_STAT_UCODE_VERSION, &tmp, &len)) + return 0; + + return sprintf(buf, "0x%08x\n", tmp); +} + +static DEVICE_ATTR(ucode_version, S_IWUSR | S_IRUGO, show_ucode_version, NULL); + +static ssize_t show_rtc(struct device *d, struct device_attribute *attr, + char *buf) +{ + u32 len = sizeof(u32), tmp = 0; + struct ipw_priv *p = dev_get_drvdata(d); + + if (ipw_get_ordinal(p, IPW_ORD_STAT_RTC, &tmp, &len)) + return 0; + + return sprintf(buf, "0x%08x\n", tmp); +} + +static DEVICE_ATTR(rtc, S_IWUSR | S_IRUGO, show_rtc, NULL); + +/* + * Add a device attribute to view/control the delay between eeprom + * operations. + */ +static ssize_t show_eeprom_delay(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct ipw_priv *p = dev_get_drvdata(d); + int n = p->eeprom_delay; + return sprintf(buf, "%i\n", n); +} +static ssize_t store_eeprom_delay(struct device *d, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ipw_priv *p = dev_get_drvdata(d); + sscanf(buf, "%i", &p->eeprom_delay); + return strnlen(buf, count); +} + +static DEVICE_ATTR(eeprom_delay, S_IWUSR | S_IRUGO, + show_eeprom_delay, store_eeprom_delay); + +static ssize_t show_command_event_reg(struct device *d, + struct device_attribute *attr, char *buf) +{ + u32 reg = 0; + struct ipw_priv *p = dev_get_drvdata(d); + + reg = ipw_read_reg32(p, IPW_INTERNAL_CMD_EVENT); + return sprintf(buf, "0x%08x\n", reg); +} +static ssize_t store_command_event_reg(struct device *d, + struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 reg; + struct ipw_priv *p = dev_get_drvdata(d); + + sscanf(buf, "%x", ®); + ipw_write_reg32(p, IPW_INTERNAL_CMD_EVENT, reg); + return strnlen(buf, count); +} + +static DEVICE_ATTR(command_event_reg, S_IWUSR | S_IRUGO, + show_command_event_reg, store_command_event_reg); + +static ssize_t show_mem_gpio_reg(struct device *d, + struct device_attribute *attr, char *buf) +{ + u32 reg = 0; + struct ipw_priv *p = dev_get_drvdata(d); + + reg = ipw_read_reg32(p, 0x301100); + return sprintf(buf, "0x%08x\n", reg); +} +static ssize_t store_mem_gpio_reg(struct device *d, + struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 reg; + struct ipw_priv *p = dev_get_drvdata(d); + + sscanf(buf, "%x", ®); + ipw_write_reg32(p, 0x301100, reg); + return strnlen(buf, count); +} + +static DEVICE_ATTR(mem_gpio_reg, S_IWUSR | S_IRUGO, + show_mem_gpio_reg, store_mem_gpio_reg); + +static ssize_t show_indirect_dword(struct device *d, + struct device_attribute *attr, char *buf) +{ + u32 reg = 0; + struct ipw_priv *priv = dev_get_drvdata(d); + + if (priv->status & STATUS_INDIRECT_DWORD) + reg = ipw_read_reg32(priv, priv->indirect_dword); + else + reg = 0; + + return sprintf(buf, "0x%08x\n", reg); +} +static ssize_t store_indirect_dword(struct device *d, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + + sscanf(buf, "%x", &priv->indirect_dword); + priv->status |= STATUS_INDIRECT_DWORD; + return strnlen(buf, count); +} + +static DEVICE_ATTR(indirect_dword, S_IWUSR | S_IRUGO, + show_indirect_dword, store_indirect_dword); + +static ssize_t show_indirect_byte(struct device *d, + struct device_attribute *attr, char *buf) +{ + u8 reg = 0; + struct ipw_priv *priv = dev_get_drvdata(d); + + if (priv->status & STATUS_INDIRECT_BYTE) + reg = ipw_read_reg8(priv, priv->indirect_byte); + else + reg = 0; + + return sprintf(buf, "0x%02x\n", reg); +} +static ssize_t store_indirect_byte(struct device *d, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + + sscanf(buf, "%x", &priv->indirect_byte); + priv->status |= STATUS_INDIRECT_BYTE; + return strnlen(buf, count); +} + +static DEVICE_ATTR(indirect_byte, S_IWUSR | S_IRUGO, + show_indirect_byte, store_indirect_byte); + +static ssize_t show_direct_dword(struct device *d, + struct device_attribute *attr, char *buf) +{ + u32 reg = 0; + struct ipw_priv *priv = dev_get_drvdata(d); + + if (priv->status & STATUS_DIRECT_DWORD) + reg = ipw_read32(priv, priv->direct_dword); + else + reg = 0; + + return sprintf(buf, "0x%08x\n", reg); +} +static ssize_t store_direct_dword(struct device *d, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + + sscanf(buf, "%x", &priv->direct_dword); + priv->status |= STATUS_DIRECT_DWORD; + return strnlen(buf, count); +} + +static DEVICE_ATTR(direct_dword, S_IWUSR | S_IRUGO, + show_direct_dword, store_direct_dword); + +static int rf_kill_active(struct ipw_priv *priv) +{ + if (0 == (ipw_read32(priv, 0x30) & 0x10000)) { + priv->status |= STATUS_RF_KILL_HW; + wiphy_rfkill_set_hw_state(priv->ieee->wdev.wiphy, true); + } else { + priv->status &= ~STATUS_RF_KILL_HW; + wiphy_rfkill_set_hw_state(priv->ieee->wdev.wiphy, false); + } + + return (priv->status & STATUS_RF_KILL_HW) ? 1 : 0; +} + +static ssize_t show_rf_kill(struct device *d, struct device_attribute *attr, + char *buf) +{ + /* 0 - RF kill not enabled + 1 - SW based RF kill active (sysfs) + 2 - HW based RF kill active + 3 - Both HW and SW baed RF kill active */ + struct ipw_priv *priv = dev_get_drvdata(d); + int val = ((priv->status & STATUS_RF_KILL_SW) ? 0x1 : 0x0) | + (rf_kill_active(priv) ? 0x2 : 0x0); + return sprintf(buf, "%i\n", val); +} + +static int ipw_radio_kill_sw(struct ipw_priv *priv, int disable_radio) +{ + if ((disable_radio ? 1 : 0) == + ((priv->status & STATUS_RF_KILL_SW) ? 1 : 0)) + return 0; + + IPW_DEBUG_RF_KILL("Manual SW RF Kill set to: RADIO %s\n", + disable_radio ? "OFF" : "ON"); + + if (disable_radio) { + priv->status |= STATUS_RF_KILL_SW; + + cancel_delayed_work(&priv->request_scan); + cancel_delayed_work(&priv->request_direct_scan); + cancel_delayed_work(&priv->request_passive_scan); + cancel_delayed_work(&priv->scan_event); + schedule_work(&priv->down); + } else { + priv->status &= ~STATUS_RF_KILL_SW; + if (rf_kill_active(priv)) { + IPW_DEBUG_RF_KILL("Can not turn radio back on - " + "disabled by HW switch\n"); + /* Make sure the RF_KILL check timer is running */ + cancel_delayed_work(&priv->rf_kill); + schedule_delayed_work(&priv->rf_kill, + round_jiffies_relative(2 * HZ)); + } else + schedule_work(&priv->up); + } + + return 1; +} + +static ssize_t store_rf_kill(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + + ipw_radio_kill_sw(priv, buf[0] == '1'); + + return count; +} + +static DEVICE_ATTR(rf_kill, S_IWUSR | S_IRUGO, show_rf_kill, store_rf_kill); + +static ssize_t show_speed_scan(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + int pos = 0, len = 0; + if (priv->config & CFG_SPEED_SCAN) { + while (priv->speed_scan[pos] != 0) + len += sprintf(&buf[len], "%d ", + priv->speed_scan[pos++]); + return len + sprintf(&buf[len], "\n"); + } + + return sprintf(buf, "0\n"); +} + +static ssize_t store_speed_scan(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + int channel, pos = 0; + const char *p = buf; + + /* list of space separated channels to scan, optionally ending with 0 */ + while ((channel = simple_strtol(p, NULL, 0))) { + if (pos == MAX_SPEED_SCAN - 1) { + priv->speed_scan[pos] = 0; + break; + } + + if (libipw_is_valid_channel(priv->ieee, channel)) + priv->speed_scan[pos++] = channel; + else + IPW_WARNING("Skipping invalid channel request: %d\n", + channel); + p = strchr(p, ' '); + if (!p) + break; + while (*p == ' ' || *p == '\t') + p++; + } + + if (pos == 0) + priv->config &= ~CFG_SPEED_SCAN; + else { + priv->speed_scan_pos = 0; + priv->config |= CFG_SPEED_SCAN; + } + + return count; +} + +static DEVICE_ATTR(speed_scan, S_IWUSR | S_IRUGO, show_speed_scan, + store_speed_scan); + +static ssize_t show_net_stats(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + return sprintf(buf, "%c\n", (priv->config & CFG_NET_STATS) ? '1' : '0'); +} + +static ssize_t store_net_stats(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + if (buf[0] == '1') + priv->config |= CFG_NET_STATS; + else + priv->config &= ~CFG_NET_STATS; + + return count; +} + +static DEVICE_ATTR(net_stats, S_IWUSR | S_IRUGO, + show_net_stats, store_net_stats); + +static ssize_t show_channels(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct ipw_priv *priv = dev_get_drvdata(d); + const struct libipw_geo *geo = libipw_get_geo(priv->ieee); + int len = 0, i; + + len = sprintf(&buf[len], + "Displaying %d channels in 2.4Ghz band " + "(802.11bg):\n", geo->bg_channels); + + for (i = 0; i < geo->bg_channels; i++) { + len += sprintf(&buf[len], "%d: BSS%s%s, %s, Band %s.\n", + geo->bg[i].channel, + geo->bg[i].flags & LIBIPW_CH_RADAR_DETECT ? + " (radar spectrum)" : "", + ((geo->bg[i].flags & LIBIPW_CH_NO_IBSS) || + (geo->bg[i].flags & LIBIPW_CH_RADAR_DETECT)) + ? "" : ", IBSS", + geo->bg[i].flags & LIBIPW_CH_PASSIVE_ONLY ? + "passive only" : "active/passive", + geo->bg[i].flags & LIBIPW_CH_B_ONLY ? + "B" : "B/G"); + } + + len += sprintf(&buf[len], + "Displaying %d channels in 5.2Ghz band " + "(802.11a):\n", geo->a_channels); + for (i = 0; i < geo->a_channels; i++) { + len += sprintf(&buf[len], "%d: BSS%s%s, %s.\n", + geo->a[i].channel, + geo->a[i].flags & LIBIPW_CH_RADAR_DETECT ? + " (radar spectrum)" : "", + ((geo->a[i].flags & LIBIPW_CH_NO_IBSS) || + (geo->a[i].flags & LIBIPW_CH_RADAR_DETECT)) + ? "" : ", IBSS", + geo->a[i].flags & LIBIPW_CH_PASSIVE_ONLY ? + "passive only" : "active/passive"); + } + + return len; +} + +static DEVICE_ATTR(channels, S_IRUSR, show_channels, NULL); + +static void notify_wx_assoc_event(struct ipw_priv *priv) +{ + union iwreq_data wrqu; + wrqu.ap_addr.sa_family = ARPHRD_ETHER; + if (priv->status & STATUS_ASSOCIATED) + memcpy(wrqu.ap_addr.sa_data, priv->bssid, ETH_ALEN); + else + memset(wrqu.ap_addr.sa_data, 0, ETH_ALEN); + wireless_send_event(priv->net_dev, SIOCGIWAP, &wrqu, NULL); +} + +static void ipw_irq_tasklet(struct ipw_priv *priv) +{ + u32 inta, inta_mask, handled = 0; + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&priv->irq_lock, flags); + + inta = ipw_read32(priv, IPW_INTA_RW); + inta_mask = ipw_read32(priv, IPW_INTA_MASK_R); + + if (inta == 0xFFFFFFFF) { + /* Hardware disappeared */ + IPW_WARNING("TASKLET INTA == 0xFFFFFFFF\n"); + /* Only handle the cached INTA values */ + inta = 0; + } + inta &= (IPW_INTA_MASK_ALL & inta_mask); + + /* Add any cached INTA values that need to be handled */ + inta |= priv->isr_inta; + + spin_unlock_irqrestore(&priv->irq_lock, flags); + + spin_lock_irqsave(&priv->lock, flags); + + /* handle all the justifications for the interrupt */ + if (inta & IPW_INTA_BIT_RX_TRANSFER) { + ipw_rx(priv); + handled |= IPW_INTA_BIT_RX_TRANSFER; + } + + if (inta & IPW_INTA_BIT_TX_CMD_QUEUE) { + IPW_DEBUG_HC("Command completed.\n"); + rc = ipw_queue_tx_reclaim(priv, &priv->txq_cmd, -1); + priv->status &= ~STATUS_HCMD_ACTIVE; + wake_up_interruptible(&priv->wait_command_queue); + handled |= IPW_INTA_BIT_TX_CMD_QUEUE; + } + + if (inta & IPW_INTA_BIT_TX_QUEUE_1) { + IPW_DEBUG_TX("TX_QUEUE_1\n"); + rc = ipw_queue_tx_reclaim(priv, &priv->txq[0], 0); + handled |= IPW_INTA_BIT_TX_QUEUE_1; + } + + if (inta & IPW_INTA_BIT_TX_QUEUE_2) { + IPW_DEBUG_TX("TX_QUEUE_2\n"); + rc = ipw_queue_tx_reclaim(priv, &priv->txq[1], 1); + handled |= IPW_INTA_BIT_TX_QUEUE_2; + } + + if (inta & IPW_INTA_BIT_TX_QUEUE_3) { + IPW_DEBUG_TX("TX_QUEUE_3\n"); + rc = ipw_queue_tx_reclaim(priv, &priv->txq[2], 2); + handled |= IPW_INTA_BIT_TX_QUEUE_3; + } + + if (inta & IPW_INTA_BIT_TX_QUEUE_4) { + IPW_DEBUG_TX("TX_QUEUE_4\n"); + rc = ipw_queue_tx_reclaim(priv, &priv->txq[3], 3); + handled |= IPW_INTA_BIT_TX_QUEUE_4; + } + + if (inta & IPW_INTA_BIT_STATUS_CHANGE) { + IPW_WARNING("STATUS_CHANGE\n"); + handled |= IPW_INTA_BIT_STATUS_CHANGE; + } + + if (inta & IPW_INTA_BIT_BEACON_PERIOD_EXPIRED) { + IPW_WARNING("TX_PERIOD_EXPIRED\n"); + handled |= IPW_INTA_BIT_BEACON_PERIOD_EXPIRED; + } + + if (inta & IPW_INTA_BIT_SLAVE_MODE_HOST_CMD_DONE) { + IPW_WARNING("HOST_CMD_DONE\n"); + handled |= IPW_INTA_BIT_SLAVE_MODE_HOST_CMD_DONE; + } + + if (inta & IPW_INTA_BIT_FW_INITIALIZATION_DONE) { + IPW_WARNING("FW_INITIALIZATION_DONE\n"); + handled |= IPW_INTA_BIT_FW_INITIALIZATION_DONE; + } + + if (inta & IPW_INTA_BIT_FW_CARD_DISABLE_PHY_OFF_DONE) { + IPW_WARNING("PHY_OFF_DONE\n"); + handled |= IPW_INTA_BIT_FW_CARD_DISABLE_PHY_OFF_DONE; + } + + if (inta & IPW_INTA_BIT_RF_KILL_DONE) { + IPW_DEBUG_RF_KILL("RF_KILL_DONE\n"); + priv->status |= STATUS_RF_KILL_HW; + wiphy_rfkill_set_hw_state(priv->ieee->wdev.wiphy, true); + wake_up_interruptible(&priv->wait_command_queue); + priv->status &= ~(STATUS_ASSOCIATED | STATUS_ASSOCIATING); + cancel_delayed_work(&priv->request_scan); + cancel_delayed_work(&priv->request_direct_scan); + cancel_delayed_work(&priv->request_passive_scan); + cancel_delayed_work(&priv->scan_event); + schedule_work(&priv->link_down); + schedule_delayed_work(&priv->rf_kill, 2 * HZ); + handled |= IPW_INTA_BIT_RF_KILL_DONE; + } + + if (inta & IPW_INTA_BIT_FATAL_ERROR) { + IPW_WARNING("Firmware error detected. Restarting.\n"); + if (priv->error) { + IPW_DEBUG_FW("Sysfs 'error' log already exists.\n"); + if (ipw_debug_level & IPW_DL_FW_ERRORS) { + struct ipw_fw_error *error = + ipw_alloc_error_log(priv); + ipw_dump_error_log(priv, error); + kfree(error); + } + } else { + priv->error = ipw_alloc_error_log(priv); + if (priv->error) + IPW_DEBUG_FW("Sysfs 'error' log captured.\n"); + else + IPW_DEBUG_FW("Error allocating sysfs 'error' " + "log.\n"); + if (ipw_debug_level & IPW_DL_FW_ERRORS) + ipw_dump_error_log(priv, priv->error); + } + + /* XXX: If hardware encryption is for WPA/WPA2, + * we have to notify the supplicant. */ + if (priv->ieee->sec.encrypt) { + priv->status &= ~STATUS_ASSOCIATED; + notify_wx_assoc_event(priv); + } + + /* Keep the restart process from trying to send host + * commands by clearing the INIT status bit */ + priv->status &= ~STATUS_INIT; + + /* Cancel currently queued command. */ + priv->status &= ~STATUS_HCMD_ACTIVE; + wake_up_interruptible(&priv->wait_command_queue); + + schedule_work(&priv->adapter_restart); + handled |= IPW_INTA_BIT_FATAL_ERROR; + } + + if (inta & IPW_INTA_BIT_PARITY_ERROR) { + IPW_ERROR("Parity error\n"); + handled |= IPW_INTA_BIT_PARITY_ERROR; + } + + if (handled != inta) { + IPW_ERROR("Unhandled INTA bits 0x%08x\n", inta & ~handled); + } + + spin_unlock_irqrestore(&priv->lock, flags); + + /* enable all interrupts */ + ipw_enable_interrupts(priv); +} + +#define IPW_CMD(x) case IPW_CMD_ ## x : return #x +static char *get_cmd_string(u8 cmd) +{ + switch (cmd) { + IPW_CMD(HOST_COMPLETE); + IPW_CMD(POWER_DOWN); + IPW_CMD(SYSTEM_CONFIG); + IPW_CMD(MULTICAST_ADDRESS); + IPW_CMD(SSID); + IPW_CMD(ADAPTER_ADDRESS); + IPW_CMD(PORT_TYPE); + IPW_CMD(RTS_THRESHOLD); + IPW_CMD(FRAG_THRESHOLD); + IPW_CMD(POWER_MODE); + IPW_CMD(WEP_KEY); + IPW_CMD(TGI_TX_KEY); + IPW_CMD(SCAN_REQUEST); + IPW_CMD(SCAN_REQUEST_EXT); + IPW_CMD(ASSOCIATE); + IPW_CMD(SUPPORTED_RATES); + IPW_CMD(SCAN_ABORT); + IPW_CMD(TX_FLUSH); + IPW_CMD(QOS_PARAMETERS); + IPW_CMD(DINO_CONFIG); + IPW_CMD(RSN_CAPABILITIES); + IPW_CMD(RX_KEY); + IPW_CMD(CARD_DISABLE); + IPW_CMD(SEED_NUMBER); + IPW_CMD(TX_POWER); + IPW_CMD(COUNTRY_INFO); + IPW_CMD(AIRONET_INFO); + IPW_CMD(AP_TX_POWER); + IPW_CMD(CCKM_INFO); + IPW_CMD(CCX_VER_INFO); + IPW_CMD(SET_CALIBRATION); + IPW_CMD(SENSITIVITY_CALIB); + IPW_CMD(RETRY_LIMIT); + IPW_CMD(IPW_PRE_POWER_DOWN); + IPW_CMD(VAP_BEACON_TEMPLATE); + IPW_CMD(VAP_DTIM_PERIOD); + IPW_CMD(EXT_SUPPORTED_RATES); + IPW_CMD(VAP_LOCAL_TX_PWR_CONSTRAINT); + IPW_CMD(VAP_QUIET_INTERVALS); + IPW_CMD(VAP_CHANNEL_SWITCH); + IPW_CMD(VAP_MANDATORY_CHANNELS); + IPW_CMD(VAP_CELL_PWR_LIMIT); + IPW_CMD(VAP_CF_PARAM_SET); + IPW_CMD(VAP_SET_BEACONING_STATE); + IPW_CMD(MEASUREMENT); + IPW_CMD(POWER_CAPABILITY); + IPW_CMD(SUPPORTED_CHANNELS); + IPW_CMD(TPC_REPORT); + IPW_CMD(WME_INFO); + IPW_CMD(PRODUCTION_COMMAND); + default: + return "UNKNOWN"; + } +} + +#define HOST_COMPLETE_TIMEOUT HZ + +static int __ipw_send_cmd(struct ipw_priv *priv, struct host_cmd *cmd) +{ + int rc = 0; + unsigned long flags; + unsigned long now, end; + + spin_lock_irqsave(&priv->lock, flags); + if (priv->status & STATUS_HCMD_ACTIVE) { + IPW_ERROR("Failed to send %s: Already sending a command.\n", + get_cmd_string(cmd->cmd)); + spin_unlock_irqrestore(&priv->lock, flags); + return -EAGAIN; + } + + priv->status |= STATUS_HCMD_ACTIVE; + + if (priv->cmdlog) { + priv->cmdlog[priv->cmdlog_pos].jiffies = jiffies; + priv->cmdlog[priv->cmdlog_pos].cmd.cmd = cmd->cmd; + priv->cmdlog[priv->cmdlog_pos].cmd.len = cmd->len; + memcpy(priv->cmdlog[priv->cmdlog_pos].cmd.param, cmd->param, + cmd->len); + priv->cmdlog[priv->cmdlog_pos].retcode = -1; + } + + IPW_DEBUG_HC("%s command (#%d) %d bytes: 0x%08X\n", + get_cmd_string(cmd->cmd), cmd->cmd, cmd->len, + priv->status); + +#ifndef DEBUG_CMD_WEP_KEY + if (cmd->cmd == IPW_CMD_WEP_KEY) + IPW_DEBUG_HC("WEP_KEY command masked out for secure.\n"); + else +#endif + printk_buf(IPW_DL_HOST_COMMAND, (u8 *) cmd->param, cmd->len); + + rc = ipw_queue_tx_hcmd(priv, cmd->cmd, cmd->param, cmd->len, 0); + if (rc) { + priv->status &= ~STATUS_HCMD_ACTIVE; + IPW_ERROR("Failed to send %s: Reason %d\n", + get_cmd_string(cmd->cmd), rc); + spin_unlock_irqrestore(&priv->lock, flags); + goto exit; + } + spin_unlock_irqrestore(&priv->lock, flags); + + now = jiffies; + end = now + HOST_COMPLETE_TIMEOUT; +again: + rc = wait_event_interruptible_timeout(priv->wait_command_queue, + !(priv-> + status & STATUS_HCMD_ACTIVE), + end - now); + if (rc < 0) { + now = jiffies; + if (time_before(now, end)) + goto again; + rc = 0; + } + + if (rc == 0) { + spin_lock_irqsave(&priv->lock, flags); + if (priv->status & STATUS_HCMD_ACTIVE) { + IPW_ERROR("Failed to send %s: Command timed out.\n", + get_cmd_string(cmd->cmd)); + priv->status &= ~STATUS_HCMD_ACTIVE; + spin_unlock_irqrestore(&priv->lock, flags); + rc = -EIO; + goto exit; + } + spin_unlock_irqrestore(&priv->lock, flags); + } else + rc = 0; + + if (priv->status & STATUS_RF_KILL_HW) { + IPW_ERROR("Failed to send %s: Aborted due to RF kill switch.\n", + get_cmd_string(cmd->cmd)); + rc = -EIO; + goto exit; + } + + exit: + if (priv->cmdlog) { + priv->cmdlog[priv->cmdlog_pos++].retcode = rc; + priv->cmdlog_pos %= priv->cmdlog_len; + } + return rc; +} + +static int ipw_send_cmd_simple(struct ipw_priv *priv, u8 command) +{ + struct host_cmd cmd = { + .cmd = command, + }; + + return __ipw_send_cmd(priv, &cmd); +} + +static int ipw_send_cmd_pdu(struct ipw_priv *priv, u8 command, u8 len, + void *data) +{ + struct host_cmd cmd = { + .cmd = command, + .len = len, + .param = data, + }; + + return __ipw_send_cmd(priv, &cmd); +} + +static int ipw_send_host_complete(struct ipw_priv *priv) +{ + if (!priv) { + IPW_ERROR("Invalid args\n"); + return -1; + } + + return ipw_send_cmd_simple(priv, IPW_CMD_HOST_COMPLETE); +} + +static int ipw_send_system_config(struct ipw_priv *priv) +{ + return ipw_send_cmd_pdu(priv, IPW_CMD_SYSTEM_CONFIG, + sizeof(priv->sys_config), + &priv->sys_config); +} + +static int ipw_send_ssid(struct ipw_priv *priv, u8 * ssid, int len) +{ + if (!priv || !ssid) { + IPW_ERROR("Invalid args\n"); + return -1; + } + + return ipw_send_cmd_pdu(priv, IPW_CMD_SSID, min(len, IW_ESSID_MAX_SIZE), + ssid); +} + +static int ipw_send_adapter_address(struct ipw_priv *priv, u8 * mac) +{ + if (!priv || !mac) { + IPW_ERROR("Invalid args\n"); + return -1; + } + + IPW_DEBUG_INFO("%s: Setting MAC to %pM\n", + priv->net_dev->name, mac); + + return ipw_send_cmd_pdu(priv, IPW_CMD_ADAPTER_ADDRESS, ETH_ALEN, mac); +} + +static void ipw_adapter_restart(void *adapter) +{ + struct ipw_priv *priv = adapter; + + if (priv->status & STATUS_RF_KILL_MASK) + return; + + ipw_down(priv); + + if (priv->assoc_network && + (priv->assoc_network->capability & WLAN_CAPABILITY_IBSS)) + ipw_remove_current_network(priv); + + if (ipw_up(priv)) { + IPW_ERROR("Failed to up device\n"); + return; + } +} + +static void ipw_bg_adapter_restart(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, adapter_restart); + mutex_lock(&priv->mutex); + ipw_adapter_restart(priv); + mutex_unlock(&priv->mutex); +} + +static void ipw_abort_scan(struct ipw_priv *priv); + +#define IPW_SCAN_CHECK_WATCHDOG (5 * HZ) + +static void ipw_scan_check(void *data) +{ + struct ipw_priv *priv = data; + + if (priv->status & STATUS_SCAN_ABORTING) { + IPW_DEBUG_SCAN("Scan completion watchdog resetting " + "adapter after (%dms).\n", + jiffies_to_msecs(IPW_SCAN_CHECK_WATCHDOG)); + schedule_work(&priv->adapter_restart); + } else if (priv->status & STATUS_SCANNING) { + IPW_DEBUG_SCAN("Scan completion watchdog aborting scan " + "after (%dms).\n", + jiffies_to_msecs(IPW_SCAN_CHECK_WATCHDOG)); + ipw_abort_scan(priv); + schedule_delayed_work(&priv->scan_check, HZ); + } +} + +static void ipw_bg_scan_check(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, scan_check.work); + mutex_lock(&priv->mutex); + ipw_scan_check(priv); + mutex_unlock(&priv->mutex); +} + +static int ipw_send_scan_request_ext(struct ipw_priv *priv, + struct ipw_scan_request_ext *request) +{ + return ipw_send_cmd_pdu(priv, IPW_CMD_SCAN_REQUEST_EXT, + sizeof(*request), request); +} + +static int ipw_send_scan_abort(struct ipw_priv *priv) +{ + if (!priv) { + IPW_ERROR("Invalid args\n"); + return -1; + } + + return ipw_send_cmd_simple(priv, IPW_CMD_SCAN_ABORT); +} + +static int ipw_set_sensitivity(struct ipw_priv *priv, u16 sens) +{ + struct ipw_sensitivity_calib calib = { + .beacon_rssi_raw = cpu_to_le16(sens), + }; + + return ipw_send_cmd_pdu(priv, IPW_CMD_SENSITIVITY_CALIB, sizeof(calib), + &calib); +} + +static int ipw_send_associate(struct ipw_priv *priv, + struct ipw_associate *associate) +{ + if (!priv || !associate) { + IPW_ERROR("Invalid args\n"); + return -1; + } + + return ipw_send_cmd_pdu(priv, IPW_CMD_ASSOCIATE, sizeof(*associate), + associate); +} + +static int ipw_send_supported_rates(struct ipw_priv *priv, + struct ipw_supported_rates *rates) +{ + if (!priv || !rates) { + IPW_ERROR("Invalid args\n"); + return -1; + } + + return ipw_send_cmd_pdu(priv, IPW_CMD_SUPPORTED_RATES, sizeof(*rates), + rates); +} + +static int ipw_set_random_seed(struct ipw_priv *priv) +{ + u32 val; + + if (!priv) { + IPW_ERROR("Invalid args\n"); + return -1; + } + + get_random_bytes(&val, sizeof(val)); + + return ipw_send_cmd_pdu(priv, IPW_CMD_SEED_NUMBER, sizeof(val), &val); +} + +static int ipw_send_card_disable(struct ipw_priv *priv, u32 phy_off) +{ + __le32 v = cpu_to_le32(phy_off); + if (!priv) { + IPW_ERROR("Invalid args\n"); + return -1; + } + + return ipw_send_cmd_pdu(priv, IPW_CMD_CARD_DISABLE, sizeof(v), &v); +} + +static int ipw_send_tx_power(struct ipw_priv *priv, struct ipw_tx_power *power) +{ + if (!priv || !power) { + IPW_ERROR("Invalid args\n"); + return -1; + } + + return ipw_send_cmd_pdu(priv, IPW_CMD_TX_POWER, sizeof(*power), power); +} + +static int ipw_set_tx_power(struct ipw_priv *priv) +{ + const struct libipw_geo *geo = libipw_get_geo(priv->ieee); + struct ipw_tx_power tx_power; + s8 max_power; + int i; + + memset(&tx_power, 0, sizeof(tx_power)); + + /* configure device for 'G' band */ + tx_power.ieee_mode = IPW_G_MODE; + tx_power.num_channels = geo->bg_channels; + for (i = 0; i < geo->bg_channels; i++) { + max_power = geo->bg[i].max_power; + tx_power.channels_tx_power[i].channel_number = + geo->bg[i].channel; + tx_power.channels_tx_power[i].tx_power = max_power ? + min(max_power, priv->tx_power) : priv->tx_power; + } + if (ipw_send_tx_power(priv, &tx_power)) + return -EIO; + + /* configure device to also handle 'B' band */ + tx_power.ieee_mode = IPW_B_MODE; + if (ipw_send_tx_power(priv, &tx_power)) + return -EIO; + + /* configure device to also handle 'A' band */ + if (priv->ieee->abg_true) { + tx_power.ieee_mode = IPW_A_MODE; + tx_power.num_channels = geo->a_channels; + for (i = 0; i < tx_power.num_channels; i++) { + max_power = geo->a[i].max_power; + tx_power.channels_tx_power[i].channel_number = + geo->a[i].channel; + tx_power.channels_tx_power[i].tx_power = max_power ? + min(max_power, priv->tx_power) : priv->tx_power; + } + if (ipw_send_tx_power(priv, &tx_power)) + return -EIO; + } + return 0; +} + +static int ipw_send_rts_threshold(struct ipw_priv *priv, u16 rts) +{ + struct ipw_rts_threshold rts_threshold = { + .rts_threshold = cpu_to_le16(rts), + }; + + if (!priv) { + IPW_ERROR("Invalid args\n"); + return -1; + } + + return ipw_send_cmd_pdu(priv, IPW_CMD_RTS_THRESHOLD, + sizeof(rts_threshold), &rts_threshold); +} + +static int ipw_send_frag_threshold(struct ipw_priv *priv, u16 frag) +{ + struct ipw_frag_threshold frag_threshold = { + .frag_threshold = cpu_to_le16(frag), + }; + + if (!priv) { + IPW_ERROR("Invalid args\n"); + return -1; + } + + return ipw_send_cmd_pdu(priv, IPW_CMD_FRAG_THRESHOLD, + sizeof(frag_threshold), &frag_threshold); +} + +static int ipw_send_power_mode(struct ipw_priv *priv, u32 mode) +{ + __le32 param; + + if (!priv) { + IPW_ERROR("Invalid args\n"); + return -1; + } + + /* If on battery, set to 3, if AC set to CAM, else user + * level */ + switch (mode) { + case IPW_POWER_BATTERY: + param = cpu_to_le32(IPW_POWER_INDEX_3); + break; + case IPW_POWER_AC: + param = cpu_to_le32(IPW_POWER_MODE_CAM); + break; + default: + param = cpu_to_le32(mode); + break; + } + + return ipw_send_cmd_pdu(priv, IPW_CMD_POWER_MODE, sizeof(param), + ¶m); +} + +static int ipw_send_retry_limit(struct ipw_priv *priv, u8 slimit, u8 llimit) +{ + struct ipw_retry_limit retry_limit = { + .short_retry_limit = slimit, + .long_retry_limit = llimit + }; + + if (!priv) { + IPW_ERROR("Invalid args\n"); + return -1; + } + + return ipw_send_cmd_pdu(priv, IPW_CMD_RETRY_LIMIT, sizeof(retry_limit), + &retry_limit); +} + +/* + * The IPW device contains a Microwire compatible EEPROM that stores + * various data like the MAC address. Usually the firmware has exclusive + * access to the eeprom, but during device initialization (before the + * device driver has sent the HostComplete command to the firmware) the + * device driver has read access to the EEPROM by way of indirect addressing + * through a couple of memory mapped registers. + * + * The following is a simplified implementation for pulling data out of the + * the eeprom, along with some helper functions to find information in + * the per device private data's copy of the eeprom. + * + * NOTE: To better understand how these functions work (i.e what is a chip + * select and why do have to keep driving the eeprom clock?), read + * just about any data sheet for a Microwire compatible EEPROM. + */ + +/* write a 32 bit value into the indirect accessor register */ +static inline void eeprom_write_reg(struct ipw_priv *p, u32 data) +{ + ipw_write_reg32(p, FW_MEM_REG_EEPROM_ACCESS, data); + + /* the eeprom requires some time to complete the operation */ + udelay(p->eeprom_delay); +} + +/* perform a chip select operation */ +static void eeprom_cs(struct ipw_priv *priv) +{ + eeprom_write_reg(priv, 0); + eeprom_write_reg(priv, EEPROM_BIT_CS); + eeprom_write_reg(priv, EEPROM_BIT_CS | EEPROM_BIT_SK); + eeprom_write_reg(priv, EEPROM_BIT_CS); +} + +/* perform a chip select operation */ +static void eeprom_disable_cs(struct ipw_priv *priv) +{ + eeprom_write_reg(priv, EEPROM_BIT_CS); + eeprom_write_reg(priv, 0); + eeprom_write_reg(priv, EEPROM_BIT_SK); +} + +/* push a single bit down to the eeprom */ +static inline void eeprom_write_bit(struct ipw_priv *p, u8 bit) +{ + int d = (bit ? EEPROM_BIT_DI : 0); + eeprom_write_reg(p, EEPROM_BIT_CS | d); + eeprom_write_reg(p, EEPROM_BIT_CS | d | EEPROM_BIT_SK); +} + +/* push an opcode followed by an address down to the eeprom */ +static void eeprom_op(struct ipw_priv *priv, u8 op, u8 addr) +{ + int i; + + eeprom_cs(priv); + eeprom_write_bit(priv, 1); + eeprom_write_bit(priv, op & 2); + eeprom_write_bit(priv, op & 1); + for (i = 7; i >= 0; i--) { + eeprom_write_bit(priv, addr & (1 << i)); + } +} + +/* pull 16 bits off the eeprom, one bit at a time */ +static u16 eeprom_read_u16(struct ipw_priv *priv, u8 addr) +{ + int i; + u16 r = 0; + + /* Send READ Opcode */ + eeprom_op(priv, EEPROM_CMD_READ, addr); + + /* Send dummy bit */ + eeprom_write_reg(priv, EEPROM_BIT_CS); + + /* Read the byte off the eeprom one bit at a time */ + for (i = 0; i < 16; i++) { + u32 data = 0; + eeprom_write_reg(priv, EEPROM_BIT_CS | EEPROM_BIT_SK); + eeprom_write_reg(priv, EEPROM_BIT_CS); + data = ipw_read_reg32(priv, FW_MEM_REG_EEPROM_ACCESS); + r = (r << 1) | ((data & EEPROM_BIT_DO) ? 1 : 0); + } + + /* Send another dummy bit */ + eeprom_write_reg(priv, 0); + eeprom_disable_cs(priv); + + return r; +} + +/* helper function for pulling the mac address out of the private */ +/* data's copy of the eeprom data */ +static void eeprom_parse_mac(struct ipw_priv *priv, u8 * mac) +{ + memcpy(mac, &priv->eeprom[EEPROM_MAC_ADDRESS], 6); +} + +static void ipw_read_eeprom(struct ipw_priv *priv) +{ + int i; + __le16 *eeprom = (__le16 *) priv->eeprom; + + IPW_DEBUG_TRACE(">>\n"); + + /* read entire contents of eeprom into private buffer */ + for (i = 0; i < 128; i++) + eeprom[i] = cpu_to_le16(eeprom_read_u16(priv, (u8) i)); + + IPW_DEBUG_TRACE("<<\n"); +} + +/* + * Either the device driver (i.e. the host) or the firmware can + * load eeprom data into the designated region in SRAM. If neither + * happens then the FW will shutdown with a fatal error. + * + * In order to signal the FW to load the EEPROM, the EEPROM_LOAD_DISABLE + * bit needs region of shared SRAM needs to be non-zero. + */ +static void ipw_eeprom_init_sram(struct ipw_priv *priv) +{ + int i; + + IPW_DEBUG_TRACE(">>\n"); + + /* + If the data looks correct, then copy it to our private + copy. Otherwise let the firmware know to perform the operation + on its own. + */ + if (priv->eeprom[EEPROM_VERSION] != 0) { + IPW_DEBUG_INFO("Writing EEPROM data into SRAM\n"); + + /* write the eeprom data to sram */ + for (i = 0; i < IPW_EEPROM_IMAGE_SIZE; i++) + ipw_write8(priv, IPW_EEPROM_DATA + i, priv->eeprom[i]); + + /* Do not load eeprom data on fatal error or suspend */ + ipw_write32(priv, IPW_EEPROM_LOAD_DISABLE, 0); + } else { + IPW_DEBUG_INFO("Enabling FW initializationg of SRAM\n"); + + /* Load eeprom data on fatal error or suspend */ + ipw_write32(priv, IPW_EEPROM_LOAD_DISABLE, 1); + } + + IPW_DEBUG_TRACE("<<\n"); +} + +static void ipw_zero_memory(struct ipw_priv *priv, u32 start, u32 count) +{ + count >>= 2; + if (!count) + return; + _ipw_write32(priv, IPW_AUTOINC_ADDR, start); + while (count--) + _ipw_write32(priv, IPW_AUTOINC_DATA, 0); +} + +static inline void ipw_fw_dma_reset_command_blocks(struct ipw_priv *priv) +{ + ipw_zero_memory(priv, IPW_SHARED_SRAM_DMA_CONTROL, + CB_NUMBER_OF_ELEMENTS_SMALL * + sizeof(struct command_block)); +} + +static int ipw_fw_dma_enable(struct ipw_priv *priv) +{ /* start dma engine but no transfers yet */ + + IPW_DEBUG_FW(">> :\n"); + + /* Start the dma */ + ipw_fw_dma_reset_command_blocks(priv); + + /* Write CB base address */ + ipw_write_reg32(priv, IPW_DMA_I_CB_BASE, IPW_SHARED_SRAM_DMA_CONTROL); + + IPW_DEBUG_FW("<< :\n"); + return 0; +} + +static void ipw_fw_dma_abort(struct ipw_priv *priv) +{ + u32 control = 0; + + IPW_DEBUG_FW(">> :\n"); + + /* set the Stop and Abort bit */ + control = DMA_CONTROL_SMALL_CB_CONST_VALUE | DMA_CB_STOP_AND_ABORT; + ipw_write_reg32(priv, IPW_DMA_I_DMA_CONTROL, control); + priv->sram_desc.last_cb_index = 0; + + IPW_DEBUG_FW("<<\n"); +} + +static int ipw_fw_dma_write_command_block(struct ipw_priv *priv, int index, + struct command_block *cb) +{ + u32 address = + IPW_SHARED_SRAM_DMA_CONTROL + + (sizeof(struct command_block) * index); + IPW_DEBUG_FW(">> :\n"); + + ipw_write_indirect(priv, address, (u8 *) cb, + (int)sizeof(struct command_block)); + + IPW_DEBUG_FW("<< :\n"); + return 0; + +} + +static int ipw_fw_dma_kick(struct ipw_priv *priv) +{ + u32 control = 0; + u32 index = 0; + + IPW_DEBUG_FW(">> :\n"); + + for (index = 0; index < priv->sram_desc.last_cb_index; index++) + ipw_fw_dma_write_command_block(priv, index, + &priv->sram_desc.cb_list[index]); + + /* Enable the DMA in the CSR register */ + ipw_clear_bit(priv, IPW_RESET_REG, + IPW_RESET_REG_MASTER_DISABLED | + IPW_RESET_REG_STOP_MASTER); + + /* Set the Start bit. */ + control = DMA_CONTROL_SMALL_CB_CONST_VALUE | DMA_CB_START; + ipw_write_reg32(priv, IPW_DMA_I_DMA_CONTROL, control); + + IPW_DEBUG_FW("<< :\n"); + return 0; +} + +static void ipw_fw_dma_dump_command_block(struct ipw_priv *priv) +{ + u32 address; + u32 register_value = 0; + u32 cb_fields_address = 0; + + IPW_DEBUG_FW(">> :\n"); + address = ipw_read_reg32(priv, IPW_DMA_I_CURRENT_CB); + IPW_DEBUG_FW_INFO("Current CB is 0x%x\n", address); + + /* Read the DMA Controlor register */ + register_value = ipw_read_reg32(priv, IPW_DMA_I_DMA_CONTROL); + IPW_DEBUG_FW_INFO("IPW_DMA_I_DMA_CONTROL is 0x%x\n", register_value); + + /* Print the CB values */ + cb_fields_address = address; + register_value = ipw_read_reg32(priv, cb_fields_address); + IPW_DEBUG_FW_INFO("Current CB Control Field is 0x%x\n", register_value); + + cb_fields_address += sizeof(u32); + register_value = ipw_read_reg32(priv, cb_fields_address); + IPW_DEBUG_FW_INFO("Current CB Source Field is 0x%x\n", register_value); + + cb_fields_address += sizeof(u32); + register_value = ipw_read_reg32(priv, cb_fields_address); + IPW_DEBUG_FW_INFO("Current CB Destination Field is 0x%x\n", + register_value); + + cb_fields_address += sizeof(u32); + register_value = ipw_read_reg32(priv, cb_fields_address); + IPW_DEBUG_FW_INFO("Current CB Status Field is 0x%x\n", register_value); + + IPW_DEBUG_FW(">> :\n"); +} + +static int ipw_fw_dma_command_block_index(struct ipw_priv *priv) +{ + u32 current_cb_address = 0; + u32 current_cb_index = 0; + + IPW_DEBUG_FW("<< :\n"); + current_cb_address = ipw_read_reg32(priv, IPW_DMA_I_CURRENT_CB); + + current_cb_index = (current_cb_address - IPW_SHARED_SRAM_DMA_CONTROL) / + sizeof(struct command_block); + + IPW_DEBUG_FW_INFO("Current CB index 0x%x address = 0x%X\n", + current_cb_index, current_cb_address); + + IPW_DEBUG_FW(">> :\n"); + return current_cb_index; + +} + +static int ipw_fw_dma_add_command_block(struct ipw_priv *priv, + u32 src_address, + u32 dest_address, + u32 length, + int interrupt_enabled, int is_last) +{ + + u32 control = CB_VALID | CB_SRC_LE | CB_DEST_LE | CB_SRC_AUTOINC | + CB_SRC_IO_GATED | CB_DEST_AUTOINC | CB_SRC_SIZE_LONG | + CB_DEST_SIZE_LONG; + struct command_block *cb; + u32 last_cb_element = 0; + + IPW_DEBUG_FW_INFO("src_address=0x%x dest_address=0x%x length=0x%x\n", + src_address, dest_address, length); + + if (priv->sram_desc.last_cb_index >= CB_NUMBER_OF_ELEMENTS_SMALL) + return -1; + + last_cb_element = priv->sram_desc.last_cb_index; + cb = &priv->sram_desc.cb_list[last_cb_element]; + priv->sram_desc.last_cb_index++; + + /* Calculate the new CB control word */ + if (interrupt_enabled) + control |= CB_INT_ENABLED; + + if (is_last) + control |= CB_LAST_VALID; + + control |= length; + + /* Calculate the CB Element's checksum value */ + cb->status = control ^ src_address ^ dest_address; + + /* Copy the Source and Destination addresses */ + cb->dest_addr = dest_address; + cb->source_addr = src_address; + + /* Copy the Control Word last */ + cb->control = control; + + return 0; +} + +static int ipw_fw_dma_add_buffer(struct ipw_priv *priv, dma_addr_t *src_address, + int nr, u32 dest_address, u32 len) +{ + int ret, i; + u32 size; + + IPW_DEBUG_FW(">>\n"); + IPW_DEBUG_FW_INFO("nr=%d dest_address=0x%x len=0x%x\n", + nr, dest_address, len); + + for (i = 0; i < nr; i++) { + size = min_t(u32, len - i * CB_MAX_LENGTH, CB_MAX_LENGTH); + ret = ipw_fw_dma_add_command_block(priv, src_address[i], + dest_address + + i * CB_MAX_LENGTH, size, + 0, 0); + if (ret) { + IPW_DEBUG_FW_INFO(": Failed\n"); + return -1; + } else + IPW_DEBUG_FW_INFO(": Added new cb\n"); + } + + IPW_DEBUG_FW("<<\n"); + return 0; +} + +static int ipw_fw_dma_wait(struct ipw_priv *priv) +{ + u32 current_index = 0, previous_index; + u32 watchdog = 0; + + IPW_DEBUG_FW(">> :\n"); + + current_index = ipw_fw_dma_command_block_index(priv); + IPW_DEBUG_FW_INFO("sram_desc.last_cb_index:0x%08X\n", + (int)priv->sram_desc.last_cb_index); + + while (current_index < priv->sram_desc.last_cb_index) { + udelay(50); + previous_index = current_index; + current_index = ipw_fw_dma_command_block_index(priv); + + if (previous_index < current_index) { + watchdog = 0; + continue; + } + if (++watchdog > 400) { + IPW_DEBUG_FW_INFO("Timeout\n"); + ipw_fw_dma_dump_command_block(priv); + ipw_fw_dma_abort(priv); + return -1; + } + } + + ipw_fw_dma_abort(priv); + + /*Disable the DMA in the CSR register */ + ipw_set_bit(priv, IPW_RESET_REG, + IPW_RESET_REG_MASTER_DISABLED | IPW_RESET_REG_STOP_MASTER); + + IPW_DEBUG_FW("<< dmaWaitSync\n"); + return 0; +} + +static void ipw_remove_current_network(struct ipw_priv *priv) +{ + struct list_head *element, *safe; + struct libipw_network *network = NULL; + unsigned long flags; + + spin_lock_irqsave(&priv->ieee->lock, flags); + list_for_each_safe(element, safe, &priv->ieee->network_list) { + network = list_entry(element, struct libipw_network, list); + if (!memcmp(network->bssid, priv->bssid, ETH_ALEN)) { + list_del(element); + list_add_tail(&network->list, + &priv->ieee->network_free_list); + } + } + spin_unlock_irqrestore(&priv->ieee->lock, flags); +} + +/** + * Check that card is still alive. + * Reads debug register from domain0. + * If card is present, pre-defined value should + * be found there. + * + * @param priv + * @return 1 if card is present, 0 otherwise + */ +static inline int ipw_alive(struct ipw_priv *priv) +{ + return ipw_read32(priv, 0x90) == 0xd55555d5; +} + +/* timeout in msec, attempted in 10-msec quanta */ +static int ipw_poll_bit(struct ipw_priv *priv, u32 addr, u32 mask, + int timeout) +{ + int i = 0; + + do { + if ((ipw_read32(priv, addr) & mask) == mask) + return i; + mdelay(10); + i += 10; + } while (i < timeout); + + return -ETIME; +} + +/* These functions load the firmware and micro code for the operation of + * the ipw hardware. It assumes the buffer has all the bits for the + * image and the caller is handling the memory allocation and clean up. + */ + +static int ipw_stop_master(struct ipw_priv *priv) +{ + int rc; + + IPW_DEBUG_TRACE(">>\n"); + /* stop master. typical delay - 0 */ + ipw_set_bit(priv, IPW_RESET_REG, IPW_RESET_REG_STOP_MASTER); + + /* timeout is in msec, polled in 10-msec quanta */ + rc = ipw_poll_bit(priv, IPW_RESET_REG, + IPW_RESET_REG_MASTER_DISABLED, 100); + if (rc < 0) { + IPW_ERROR("wait for stop master failed after 100ms\n"); + return -1; + } + + IPW_DEBUG_INFO("stop master %dms\n", rc); + + return rc; +} + +static void ipw_arc_release(struct ipw_priv *priv) +{ + IPW_DEBUG_TRACE(">>\n"); + mdelay(5); + + ipw_clear_bit(priv, IPW_RESET_REG, CBD_RESET_REG_PRINCETON_RESET); + + /* no one knows timing, for safety add some delay */ + mdelay(5); +} + +struct fw_chunk { + __le32 address; + __le32 length; +}; + +static int ipw_load_ucode(struct ipw_priv *priv, u8 * data, size_t len) +{ + int rc = 0, i, addr; + u8 cr = 0; + __le16 *image; + + image = (__le16 *) data; + + IPW_DEBUG_TRACE(">>\n"); + + rc = ipw_stop_master(priv); + + if (rc < 0) + return rc; + + for (addr = IPW_SHARED_LOWER_BOUND; + addr < IPW_REGISTER_DOMAIN1_END; addr += 4) { + ipw_write32(priv, addr, 0); + } + + /* no ucode (yet) */ + memset(&priv->dino_alive, 0, sizeof(priv->dino_alive)); + /* destroy DMA queues */ + /* reset sequence */ + + ipw_write_reg32(priv, IPW_MEM_HALT_AND_RESET, IPW_BIT_HALT_RESET_ON); + ipw_arc_release(priv); + ipw_write_reg32(priv, IPW_MEM_HALT_AND_RESET, IPW_BIT_HALT_RESET_OFF); + mdelay(1); + + /* reset PHY */ + ipw_write_reg32(priv, IPW_INTERNAL_CMD_EVENT, IPW_BASEBAND_POWER_DOWN); + mdelay(1); + + ipw_write_reg32(priv, IPW_INTERNAL_CMD_EVENT, 0); + mdelay(1); + + /* enable ucode store */ + ipw_write_reg8(priv, IPW_BASEBAND_CONTROL_STATUS, 0x0); + ipw_write_reg8(priv, IPW_BASEBAND_CONTROL_STATUS, DINO_ENABLE_CS); + mdelay(1); + + /* write ucode */ + /** + * @bug + * Do NOT set indirect address register once and then + * store data to indirect data register in the loop. + * It seems very reasonable, but in this case DINO do not + * accept ucode. It is essential to set address each time. + */ + /* load new ipw uCode */ + for (i = 0; i < len / 2; i++) + ipw_write_reg16(priv, IPW_BASEBAND_CONTROL_STORE, + le16_to_cpu(image[i])); + + /* enable DINO */ + ipw_write_reg8(priv, IPW_BASEBAND_CONTROL_STATUS, 0); + ipw_write_reg8(priv, IPW_BASEBAND_CONTROL_STATUS, DINO_ENABLE_SYSTEM); + + /* this is where the igx / win driver deveates from the VAP driver. */ + + /* wait for alive response */ + for (i = 0; i < 100; i++) { + /* poll for incoming data */ + cr = ipw_read_reg8(priv, IPW_BASEBAND_CONTROL_STATUS); + if (cr & DINO_RXFIFO_DATA) + break; + mdelay(1); + } + + if (cr & DINO_RXFIFO_DATA) { + /* alive_command_responce size is NOT multiple of 4 */ + __le32 response_buffer[(sizeof(priv->dino_alive) + 3) / 4]; + + for (i = 0; i < ARRAY_SIZE(response_buffer); i++) + response_buffer[i] = + cpu_to_le32(ipw_read_reg32(priv, + IPW_BASEBAND_RX_FIFO_READ)); + memcpy(&priv->dino_alive, response_buffer, + sizeof(priv->dino_alive)); + if (priv->dino_alive.alive_command == 1 + && priv->dino_alive.ucode_valid == 1) { + rc = 0; + IPW_DEBUG_INFO + ("Microcode OK, rev. %d (0x%x) dev. %d (0x%x) " + "of %02d/%02d/%02d %02d:%02d\n", + priv->dino_alive.software_revision, + priv->dino_alive.software_revision, + priv->dino_alive.device_identifier, + priv->dino_alive.device_identifier, + priv->dino_alive.time_stamp[0], + priv->dino_alive.time_stamp[1], + priv->dino_alive.time_stamp[2], + priv->dino_alive.time_stamp[3], + priv->dino_alive.time_stamp[4]); + } else { + IPW_DEBUG_INFO("Microcode is not alive\n"); + rc = -EINVAL; + } + } else { + IPW_DEBUG_INFO("No alive response from DINO\n"); + rc = -ETIME; + } + + /* disable DINO, otherwise for some reason + firmware have problem getting alive resp. */ + ipw_write_reg8(priv, IPW_BASEBAND_CONTROL_STATUS, 0); + + return rc; +} + +static int ipw_load_firmware(struct ipw_priv *priv, u8 * data, size_t len) +{ + int ret = -1; + int offset = 0; + struct fw_chunk *chunk; + int total_nr = 0; + int i; + struct pci_pool *pool; + void **virts; + dma_addr_t *phys; + + IPW_DEBUG_TRACE("<< :\n"); + + virts = kmalloc(sizeof(void *) * CB_NUMBER_OF_ELEMENTS_SMALL, + GFP_KERNEL); + if (!virts) + return -ENOMEM; + + phys = kmalloc(sizeof(dma_addr_t) * CB_NUMBER_OF_ELEMENTS_SMALL, + GFP_KERNEL); + if (!phys) { + kfree(virts); + return -ENOMEM; + } + pool = pci_pool_create("ipw2200", priv->pci_dev, CB_MAX_LENGTH, 0, 0); + if (!pool) { + IPW_ERROR("pci_pool_create failed\n"); + kfree(phys); + kfree(virts); + return -ENOMEM; + } + + /* Start the Dma */ + ret = ipw_fw_dma_enable(priv); + + /* the DMA is already ready this would be a bug. */ + BUG_ON(priv->sram_desc.last_cb_index > 0); + + do { + u32 chunk_len; + u8 *start; + int size; + int nr = 0; + + chunk = (struct fw_chunk *)(data + offset); + offset += sizeof(struct fw_chunk); + chunk_len = le32_to_cpu(chunk->length); + start = data + offset; + + nr = (chunk_len + CB_MAX_LENGTH - 1) / CB_MAX_LENGTH; + for (i = 0; i < nr; i++) { + virts[total_nr] = pci_pool_alloc(pool, GFP_KERNEL, + &phys[total_nr]); + if (!virts[total_nr]) { + ret = -ENOMEM; + goto out; + } + size = min_t(u32, chunk_len - i * CB_MAX_LENGTH, + CB_MAX_LENGTH); + memcpy(virts[total_nr], start, size); + start += size; + total_nr++; + /* We don't support fw chunk larger than 64*8K */ + BUG_ON(total_nr > CB_NUMBER_OF_ELEMENTS_SMALL); + } + + /* build DMA packet and queue up for sending */ + /* dma to chunk->address, the chunk->length bytes from data + + * offeset*/ + /* Dma loading */ + ret = ipw_fw_dma_add_buffer(priv, &phys[total_nr - nr], + nr, le32_to_cpu(chunk->address), + chunk_len); + if (ret) { + IPW_DEBUG_INFO("dmaAddBuffer Failed\n"); + goto out; + } + + offset += chunk_len; + } while (offset < len); + + /* Run the DMA and wait for the answer */ + ret = ipw_fw_dma_kick(priv); + if (ret) { + IPW_ERROR("dmaKick Failed\n"); + goto out; + } + + ret = ipw_fw_dma_wait(priv); + if (ret) { + IPW_ERROR("dmaWaitSync Failed\n"); + goto out; + } + out: + for (i = 0; i < total_nr; i++) + pci_pool_free(pool, virts[i], phys[i]); + + pci_pool_destroy(pool); + kfree(phys); + kfree(virts); + + return ret; +} + +/* stop nic */ +static int ipw_stop_nic(struct ipw_priv *priv) +{ + int rc = 0; + + /* stop */ + ipw_write32(priv, IPW_RESET_REG, IPW_RESET_REG_STOP_MASTER); + + rc = ipw_poll_bit(priv, IPW_RESET_REG, + IPW_RESET_REG_MASTER_DISABLED, 500); + if (rc < 0) { + IPW_ERROR("wait for reg master disabled failed after 500ms\n"); + return rc; + } + + ipw_set_bit(priv, IPW_RESET_REG, CBD_RESET_REG_PRINCETON_RESET); + + return rc; +} + +static void ipw_start_nic(struct ipw_priv *priv) +{ + IPW_DEBUG_TRACE(">>\n"); + + /* prvHwStartNic release ARC */ + ipw_clear_bit(priv, IPW_RESET_REG, + IPW_RESET_REG_MASTER_DISABLED | + IPW_RESET_REG_STOP_MASTER | + CBD_RESET_REG_PRINCETON_RESET); + + /* enable power management */ + ipw_set_bit(priv, IPW_GP_CNTRL_RW, + IPW_GP_CNTRL_BIT_HOST_ALLOWS_STANDBY); + + IPW_DEBUG_TRACE("<<\n"); +} + +static int ipw_init_nic(struct ipw_priv *priv) +{ + int rc; + + IPW_DEBUG_TRACE(">>\n"); + /* reset */ + /*prvHwInitNic */ + /* set "initialization complete" bit to move adapter to D0 state */ + ipw_set_bit(priv, IPW_GP_CNTRL_RW, IPW_GP_CNTRL_BIT_INIT_DONE); + + /* low-level PLL activation */ + ipw_write32(priv, IPW_READ_INT_REGISTER, + IPW_BIT_INT_HOST_SRAM_READ_INT_REGISTER); + + /* wait for clock stabilization */ + rc = ipw_poll_bit(priv, IPW_GP_CNTRL_RW, + IPW_GP_CNTRL_BIT_CLOCK_READY, 250); + if (rc < 0) + IPW_DEBUG_INFO("FAILED wait for clock stablization\n"); + + /* assert SW reset */ + ipw_set_bit(priv, IPW_RESET_REG, IPW_RESET_REG_SW_RESET); + + udelay(10); + + /* set "initialization complete" bit to move adapter to D0 state */ + ipw_set_bit(priv, IPW_GP_CNTRL_RW, IPW_GP_CNTRL_BIT_INIT_DONE); + + IPW_DEBUG_TRACE(">>\n"); + return 0; +} + +/* Call this function from process context, it will sleep in request_firmware. + * Probe is an ok place to call this from. + */ +static int ipw_reset_nic(struct ipw_priv *priv) +{ + int rc = 0; + unsigned long flags; + + IPW_DEBUG_TRACE(">>\n"); + + rc = ipw_init_nic(priv); + + spin_lock_irqsave(&priv->lock, flags); + /* Clear the 'host command active' bit... */ + priv->status &= ~STATUS_HCMD_ACTIVE; + wake_up_interruptible(&priv->wait_command_queue); + priv->status &= ~(STATUS_SCANNING | STATUS_SCAN_ABORTING); + wake_up_interruptible(&priv->wait_state); + spin_unlock_irqrestore(&priv->lock, flags); + + IPW_DEBUG_TRACE("<<\n"); + return rc; +} + + +struct ipw_fw { + __le32 ver; + __le32 boot_size; + __le32 ucode_size; + __le32 fw_size; + u8 data[0]; +}; + +static int ipw_get_fw(struct ipw_priv *priv, + const struct firmware **raw, const char *name) +{ + struct ipw_fw *fw; + int rc; + + /* ask firmware_class module to get the boot firmware off disk */ + rc = request_firmware(raw, name, &priv->pci_dev->dev); + if (rc < 0) { + IPW_ERROR("%s request_firmware failed: Reason %d\n", name, rc); + return rc; + } + + if ((*raw)->size < sizeof(*fw)) { + IPW_ERROR("%s is too small (%zd)\n", name, (*raw)->size); + return -EINVAL; + } + + fw = (void *)(*raw)->data; + + if ((*raw)->size < sizeof(*fw) + le32_to_cpu(fw->boot_size) + + le32_to_cpu(fw->ucode_size) + le32_to_cpu(fw->fw_size)) { + IPW_ERROR("%s is too small or corrupt (%zd)\n", + name, (*raw)->size); + return -EINVAL; + } + + IPW_DEBUG_INFO("Read firmware '%s' image v%d.%d (%zd bytes)\n", + name, + le32_to_cpu(fw->ver) >> 16, + le32_to_cpu(fw->ver) & 0xff, + (*raw)->size - sizeof(*fw)); + return 0; +} + +#define IPW_RX_BUF_SIZE (3000) + +static void ipw_rx_queue_reset(struct ipw_priv *priv, + struct ipw_rx_queue *rxq) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&rxq->lock, flags); + + INIT_LIST_HEAD(&rxq->rx_free); + INIT_LIST_HEAD(&rxq->rx_used); + + /* Fill the rx_used queue with _all_ of the Rx buffers */ + for (i = 0; i < RX_FREE_BUFFERS + RX_QUEUE_SIZE; i++) { + /* In the reset function, these buffers may have been allocated + * to an SKB, so we need to unmap and free potential storage */ + if (rxq->pool[i].skb != NULL) { + pci_unmap_single(priv->pci_dev, rxq->pool[i].dma_addr, + IPW_RX_BUF_SIZE, PCI_DMA_FROMDEVICE); + dev_kfree_skb(rxq->pool[i].skb); + rxq->pool[i].skb = NULL; + } + list_add_tail(&rxq->pool[i].list, &rxq->rx_used); + } + + /* Set us so that we have processed and used all buffers, but have + * not restocked the Rx queue with fresh buffers */ + rxq->read = rxq->write = 0; + rxq->free_count = 0; + spin_unlock_irqrestore(&rxq->lock, flags); +} + +#ifdef CONFIG_PM +static int fw_loaded = 0; +static const struct firmware *raw = NULL; + +static void free_firmware(void) +{ + if (fw_loaded) { + release_firmware(raw); + raw = NULL; + fw_loaded = 0; + } +} +#else +#define free_firmware() do {} while (0) +#endif + +static int ipw_load(struct ipw_priv *priv) +{ +#ifndef CONFIG_PM + const struct firmware *raw = NULL; +#endif + struct ipw_fw *fw; + u8 *boot_img, *ucode_img, *fw_img; + u8 *name = NULL; + int rc = 0, retries = 3; + + switch (priv->ieee->iw_mode) { + case IW_MODE_ADHOC: + name = "ipw2200-ibss.fw"; + break; +#ifdef CPTCFG_IPW2200_MONITOR + case IW_MODE_MONITOR: + name = "ipw2200-sniffer.fw"; + break; +#endif + case IW_MODE_INFRA: + name = "ipw2200-bss.fw"; + break; + } + + if (!name) { + rc = -EINVAL; + goto error; + } + +#ifdef CONFIG_PM + if (!fw_loaded) { +#endif + rc = ipw_get_fw(priv, &raw, name); + if (rc < 0) + goto error; +#ifdef CONFIG_PM + } +#endif + + fw = (void *)raw->data; + boot_img = &fw->data[0]; + ucode_img = &fw->data[le32_to_cpu(fw->boot_size)]; + fw_img = &fw->data[le32_to_cpu(fw->boot_size) + + le32_to_cpu(fw->ucode_size)]; + + if (rc < 0) + goto error; + + if (!priv->rxq) + priv->rxq = ipw_rx_queue_alloc(priv); + else + ipw_rx_queue_reset(priv, priv->rxq); + if (!priv->rxq) { + IPW_ERROR("Unable to initialize Rx queue\n"); + goto error; + } + + retry: + /* Ensure interrupts are disabled */ + ipw_write32(priv, IPW_INTA_MASK_R, ~IPW_INTA_MASK_ALL); + priv->status &= ~STATUS_INT_ENABLED; + + /* ack pending interrupts */ + ipw_write32(priv, IPW_INTA_RW, IPW_INTA_MASK_ALL); + + ipw_stop_nic(priv); + + rc = ipw_reset_nic(priv); + if (rc < 0) { + IPW_ERROR("Unable to reset NIC\n"); + goto error; + } + + ipw_zero_memory(priv, IPW_NIC_SRAM_LOWER_BOUND, + IPW_NIC_SRAM_UPPER_BOUND - IPW_NIC_SRAM_LOWER_BOUND); + + /* DMA the initial boot firmware into the device */ + rc = ipw_load_firmware(priv, boot_img, le32_to_cpu(fw->boot_size)); + if (rc < 0) { + IPW_ERROR("Unable to load boot firmware: %d\n", rc); + goto error; + } + + /* kick start the device */ + ipw_start_nic(priv); + + /* wait for the device to finish its initial startup sequence */ + rc = ipw_poll_bit(priv, IPW_INTA_RW, + IPW_INTA_BIT_FW_INITIALIZATION_DONE, 500); + if (rc < 0) { + IPW_ERROR("device failed to boot initial fw image\n"); + goto error; + } + IPW_DEBUG_INFO("initial device response after %dms\n", rc); + + /* ack fw init done interrupt */ + ipw_write32(priv, IPW_INTA_RW, IPW_INTA_BIT_FW_INITIALIZATION_DONE); + + /* DMA the ucode into the device */ + rc = ipw_load_ucode(priv, ucode_img, le32_to_cpu(fw->ucode_size)); + if (rc < 0) { + IPW_ERROR("Unable to load ucode: %d\n", rc); + goto error; + } + + /* stop nic */ + ipw_stop_nic(priv); + + /* DMA bss firmware into the device */ + rc = ipw_load_firmware(priv, fw_img, le32_to_cpu(fw->fw_size)); + if (rc < 0) { + IPW_ERROR("Unable to load firmware: %d\n", rc); + goto error; + } +#ifdef CONFIG_PM + fw_loaded = 1; +#endif + + ipw_write32(priv, IPW_EEPROM_LOAD_DISABLE, 0); + + rc = ipw_queue_reset(priv); + if (rc < 0) { + IPW_ERROR("Unable to initialize queues\n"); + goto error; + } + + /* Ensure interrupts are disabled */ + ipw_write32(priv, IPW_INTA_MASK_R, ~IPW_INTA_MASK_ALL); + /* ack pending interrupts */ + ipw_write32(priv, IPW_INTA_RW, IPW_INTA_MASK_ALL); + + /* kick start the device */ + ipw_start_nic(priv); + + if (ipw_read32(priv, IPW_INTA_RW) & IPW_INTA_BIT_PARITY_ERROR) { + if (retries > 0) { + IPW_WARNING("Parity error. Retrying init.\n"); + retries--; + goto retry; + } + + IPW_ERROR("TODO: Handle parity error -- schedule restart?\n"); + rc = -EIO; + goto error; + } + + /* wait for the device */ + rc = ipw_poll_bit(priv, IPW_INTA_RW, + IPW_INTA_BIT_FW_INITIALIZATION_DONE, 500); + if (rc < 0) { + IPW_ERROR("device failed to start within 500ms\n"); + goto error; + } + IPW_DEBUG_INFO("device response after %dms\n", rc); + + /* ack fw init done interrupt */ + ipw_write32(priv, IPW_INTA_RW, IPW_INTA_BIT_FW_INITIALIZATION_DONE); + + /* read eeprom data */ + priv->eeprom_delay = 1; + ipw_read_eeprom(priv); + /* initialize the eeprom region of sram */ + ipw_eeprom_init_sram(priv); + + /* enable interrupts */ + ipw_enable_interrupts(priv); + + /* Ensure our queue has valid packets */ + ipw_rx_queue_replenish(priv); + + ipw_write32(priv, IPW_RX_READ_INDEX, priv->rxq->read); + + /* ack pending interrupts */ + ipw_write32(priv, IPW_INTA_RW, IPW_INTA_MASK_ALL); + +#ifndef CONFIG_PM + release_firmware(raw); +#endif + return 0; + + error: + if (priv->rxq) { + ipw_rx_queue_free(priv, priv->rxq); + priv->rxq = NULL; + } + ipw_tx_queue_free(priv); + release_firmware(raw); +#ifdef CONFIG_PM + fw_loaded = 0; + raw = NULL; +#endif + + return rc; +} + +/** + * DMA services + * + * Theory of operation + * + * A queue is a circular buffers with 'Read' and 'Write' pointers. + * 2 empty entries always kept in the buffer to protect from overflow. + * + * For Tx queue, there are low mark and high mark limits. If, after queuing + * the packet for Tx, free space become < low mark, Tx queue stopped. When + * reclaiming packets (on 'tx done IRQ), if free space become > high mark, + * Tx queue resumed. + * + * The IPW operates with six queues, one receive queue in the device's + * sram, one transmit queue for sending commands to the device firmware, + * and four transmit queues for data. + * + * The four transmit queues allow for performing quality of service (qos) + * transmissions as per the 802.11 protocol. Currently Linux does not + * provide a mechanism to the user for utilizing prioritized queues, so + * we only utilize the first data transmit queue (queue1). + */ + +/** + * Driver allocates buffers of this size for Rx + */ + +/** + * ipw_rx_queue_space - Return number of free slots available in queue. + */ +static int ipw_rx_queue_space(const struct ipw_rx_queue *q) +{ + int s = q->read - q->write; + if (s <= 0) + s += RX_QUEUE_SIZE; + /* keep some buffer to not confuse full and empty queue */ + s -= 2; + if (s < 0) + s = 0; + return s; +} + +static inline int ipw_tx_queue_space(const struct clx2_queue *q) +{ + int s = q->last_used - q->first_empty; + if (s <= 0) + s += q->n_bd; + s -= 2; /* keep some reserve to not confuse empty and full situations */ + if (s < 0) + s = 0; + return s; +} + +static inline int ipw_queue_inc_wrap(int index, int n_bd) +{ + return (++index == n_bd) ? 0 : index; +} + +/** + * Initialize common DMA queue structure + * + * @param q queue to init + * @param count Number of BD's to allocate. Should be power of 2 + * @param read_register Address for 'read' register + * (not offset within BAR, full address) + * @param write_register Address for 'write' register + * (not offset within BAR, full address) + * @param base_register Address for 'base' register + * (not offset within BAR, full address) + * @param size Address for 'size' register + * (not offset within BAR, full address) + */ +static void ipw_queue_init(struct ipw_priv *priv, struct clx2_queue *q, + int count, u32 read, u32 write, u32 base, u32 size) +{ + q->n_bd = count; + + q->low_mark = q->n_bd / 4; + if (q->low_mark < 4) + q->low_mark = 4; + + q->high_mark = q->n_bd / 8; + if (q->high_mark < 2) + q->high_mark = 2; + + q->first_empty = q->last_used = 0; + q->reg_r = read; + q->reg_w = write; + + ipw_write32(priv, base, q->dma_addr); + ipw_write32(priv, size, count); + ipw_write32(priv, read, 0); + ipw_write32(priv, write, 0); + + _ipw_read32(priv, 0x90); +} + +static int ipw_queue_tx_init(struct ipw_priv *priv, + struct clx2_tx_queue *q, + int count, u32 read, u32 write, u32 base, u32 size) +{ + struct pci_dev *dev = priv->pci_dev; + + q->txb = kmalloc(sizeof(q->txb[0]) * count, GFP_KERNEL); + if (!q->txb) { + IPW_ERROR("vmalloc for auxiliary BD structures failed\n"); + return -ENOMEM; + } + + q->bd = + pci_alloc_consistent(dev, sizeof(q->bd[0]) * count, &q->q.dma_addr); + if (!q->bd) { + IPW_ERROR("pci_alloc_consistent(%zd) failed\n", + sizeof(q->bd[0]) * count); + kfree(q->txb); + q->txb = NULL; + return -ENOMEM; + } + + ipw_queue_init(priv, &q->q, count, read, write, base, size); + return 0; +} + +/** + * Free one TFD, those at index [txq->q.last_used]. + * Do NOT advance any indexes + * + * @param dev + * @param txq + */ +static void ipw_queue_tx_free_tfd(struct ipw_priv *priv, + struct clx2_tx_queue *txq) +{ + struct tfd_frame *bd = &txq->bd[txq->q.last_used]; + struct pci_dev *dev = priv->pci_dev; + int i; + + /* classify bd */ + if (bd->control_flags.message_type == TX_HOST_COMMAND_TYPE) + /* nothing to cleanup after for host commands */ + return; + + /* sanity check */ + if (le32_to_cpu(bd->u.data.num_chunks) > NUM_TFD_CHUNKS) { + IPW_ERROR("Too many chunks: %i\n", + le32_to_cpu(bd->u.data.num_chunks)); + /** @todo issue fatal error, it is quite serious situation */ + return; + } + + /* unmap chunks if any */ + for (i = 0; i < le32_to_cpu(bd->u.data.num_chunks); i++) { + pci_unmap_single(dev, le32_to_cpu(bd->u.data.chunk_ptr[i]), + le16_to_cpu(bd->u.data.chunk_len[i]), + PCI_DMA_TODEVICE); + if (txq->txb[txq->q.last_used]) { + libipw_txb_free(txq->txb[txq->q.last_used]); + txq->txb[txq->q.last_used] = NULL; + } + } +} + +/** + * Deallocate DMA queue. + * + * Empty queue by removing and destroying all BD's. + * Free all buffers. + * + * @param dev + * @param q + */ +static void ipw_queue_tx_free(struct ipw_priv *priv, struct clx2_tx_queue *txq) +{ + struct clx2_queue *q = &txq->q; + struct pci_dev *dev = priv->pci_dev; + + if (q->n_bd == 0) + return; + + /* first, empty all BD's */ + for (; q->first_empty != q->last_used; + q->last_used = ipw_queue_inc_wrap(q->last_used, q->n_bd)) { + ipw_queue_tx_free_tfd(priv, txq); + } + + /* free buffers belonging to queue itself */ + pci_free_consistent(dev, sizeof(txq->bd[0]) * q->n_bd, txq->bd, + q->dma_addr); + kfree(txq->txb); + + /* 0 fill whole structure */ + memset(txq, 0, sizeof(*txq)); +} + +/** + * Destroy all DMA queues and structures + * + * @param priv + */ +static void ipw_tx_queue_free(struct ipw_priv *priv) +{ + /* Tx CMD queue */ + ipw_queue_tx_free(priv, &priv->txq_cmd); + + /* Tx queues */ + ipw_queue_tx_free(priv, &priv->txq[0]); + ipw_queue_tx_free(priv, &priv->txq[1]); + ipw_queue_tx_free(priv, &priv->txq[2]); + ipw_queue_tx_free(priv, &priv->txq[3]); +} + +static void ipw_create_bssid(struct ipw_priv *priv, u8 * bssid) +{ + /* First 3 bytes are manufacturer */ + bssid[0] = priv->mac_addr[0]; + bssid[1] = priv->mac_addr[1]; + bssid[2] = priv->mac_addr[2]; + + /* Last bytes are random */ + get_random_bytes(&bssid[3], ETH_ALEN - 3); + + bssid[0] &= 0xfe; /* clear multicast bit */ + bssid[0] |= 0x02; /* set local assignment bit (IEEE802) */ +} + +static u8 ipw_add_station(struct ipw_priv *priv, u8 * bssid) +{ + struct ipw_station_entry entry; + int i; + + for (i = 0; i < priv->num_stations; i++) { + if (!memcmp(priv->stations[i], bssid, ETH_ALEN)) { + /* Another node is active in network */ + priv->missed_adhoc_beacons = 0; + if (!(priv->config & CFG_STATIC_CHANNEL)) + /* when other nodes drop out, we drop out */ + priv->config &= ~CFG_ADHOC_PERSIST; + + return i; + } + } + + if (i == MAX_STATIONS) + return IPW_INVALID_STATION; + + IPW_DEBUG_SCAN("Adding AdHoc station: %pM\n", bssid); + + entry.reserved = 0; + entry.support_mode = 0; + memcpy(entry.mac_addr, bssid, ETH_ALEN); + memcpy(priv->stations[i], bssid, ETH_ALEN); + ipw_write_direct(priv, IPW_STATION_TABLE_LOWER + i * sizeof(entry), + &entry, sizeof(entry)); + priv->num_stations++; + + return i; +} + +static u8 ipw_find_station(struct ipw_priv *priv, u8 * bssid) +{ + int i; + + for (i = 0; i < priv->num_stations; i++) + if (!memcmp(priv->stations[i], bssid, ETH_ALEN)) + return i; + + return IPW_INVALID_STATION; +} + +static void ipw_send_disassociate(struct ipw_priv *priv, int quiet) +{ + int err; + + if (priv->status & STATUS_ASSOCIATING) { + IPW_DEBUG_ASSOC("Disassociating while associating.\n"); + schedule_work(&priv->disassociate); + return; + } + + if (!(priv->status & STATUS_ASSOCIATED)) { + IPW_DEBUG_ASSOC("Disassociating while not associated.\n"); + return; + } + + IPW_DEBUG_ASSOC("Disassocation attempt from %pM " + "on channel %d.\n", + priv->assoc_request.bssid, + priv->assoc_request.channel); + + priv->status &= ~(STATUS_ASSOCIATING | STATUS_ASSOCIATED); + priv->status |= STATUS_DISASSOCIATING; + + if (quiet) + priv->assoc_request.assoc_type = HC_DISASSOC_QUIET; + else + priv->assoc_request.assoc_type = HC_DISASSOCIATE; + + err = ipw_send_associate(priv, &priv->assoc_request); + if (err) { + IPW_DEBUG_HC("Attempt to send [dis]associate command " + "failed.\n"); + return; + } + +} + +static int ipw_disassociate(void *data) +{ + struct ipw_priv *priv = data; + if (!(priv->status & (STATUS_ASSOCIATED | STATUS_ASSOCIATING))) + return 0; + ipw_send_disassociate(data, 0); + netif_carrier_off(priv->net_dev); + return 1; +} + +static void ipw_bg_disassociate(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, disassociate); + mutex_lock(&priv->mutex); + ipw_disassociate(priv); + mutex_unlock(&priv->mutex); +} + +static void ipw_system_config(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, system_config); + +#ifdef CPTCFG_IPW2200_PROMISCUOUS + if (priv->prom_net_dev && netif_running(priv->prom_net_dev)) { + priv->sys_config.accept_all_data_frames = 1; + priv->sys_config.accept_non_directed_frames = 1; + priv->sys_config.accept_all_mgmt_bcpr = 1; + priv->sys_config.accept_all_mgmt_frames = 1; + } +#endif + + ipw_send_system_config(priv); +} + +struct ipw_status_code { + u16 status; + const char *reason; +}; + +static const struct ipw_status_code ipw_status_codes[] = { + {0x00, "Successful"}, + {0x01, "Unspecified failure"}, + {0x0A, "Cannot support all requested capabilities in the " + "Capability information field"}, + {0x0B, "Reassociation denied due to inability to confirm that " + "association exists"}, + {0x0C, "Association denied due to reason outside the scope of this " + "standard"}, + {0x0D, + "Responding station does not support the specified authentication " + "algorithm"}, + {0x0E, + "Received an Authentication frame with authentication sequence " + "transaction sequence number out of expected sequence"}, + {0x0F, "Authentication rejected because of challenge failure"}, + {0x10, "Authentication rejected due to timeout waiting for next " + "frame in sequence"}, + {0x11, "Association denied because AP is unable to handle additional " + "associated stations"}, + {0x12, + "Association denied due to requesting station not supporting all " + "of the datarates in the BSSBasicServiceSet Parameter"}, + {0x13, + "Association denied due to requesting station not supporting " + "short preamble operation"}, + {0x14, + "Association denied due to requesting station not supporting " + "PBCC encoding"}, + {0x15, + "Association denied due to requesting station not supporting " + "channel agility"}, + {0x19, + "Association denied due to requesting station not supporting " + "short slot operation"}, + {0x1A, + "Association denied due to requesting station not supporting " + "DSSS-OFDM operation"}, + {0x28, "Invalid Information Element"}, + {0x29, "Group Cipher is not valid"}, + {0x2A, "Pairwise Cipher is not valid"}, + {0x2B, "AKMP is not valid"}, + {0x2C, "Unsupported RSN IE version"}, + {0x2D, "Invalid RSN IE Capabilities"}, + {0x2E, "Cipher suite is rejected per security policy"}, +}; + +static const char *ipw_get_status_code(u16 status) +{ + int i; + for (i = 0; i < ARRAY_SIZE(ipw_status_codes); i++) + if (ipw_status_codes[i].status == (status & 0xff)) + return ipw_status_codes[i].reason; + return "Unknown status value."; +} + +static void inline average_init(struct average *avg) +{ + memset(avg, 0, sizeof(*avg)); +} + +#define DEPTH_RSSI 8 +#define DEPTH_NOISE 16 +static s16 exponential_average(s16 prev_avg, s16 val, u8 depth) +{ + return ((depth-1)*prev_avg + val)/depth; +} + +static void average_add(struct average *avg, s16 val) +{ + avg->sum -= avg->entries[avg->pos]; + avg->sum += val; + avg->entries[avg->pos++] = val; + if (unlikely(avg->pos == AVG_ENTRIES)) { + avg->init = 1; + avg->pos = 0; + } +} + +static s16 average_value(struct average *avg) +{ + if (!unlikely(avg->init)) { + if (avg->pos) + return avg->sum / avg->pos; + return 0; + } + + return avg->sum / AVG_ENTRIES; +} + +static void ipw_reset_stats(struct ipw_priv *priv) +{ + u32 len = sizeof(u32); + + priv->quality = 0; + + average_init(&priv->average_missed_beacons); + priv->exp_avg_rssi = -60; + priv->exp_avg_noise = -85 + 0x100; + + priv->last_rate = 0; + priv->last_missed_beacons = 0; + priv->last_rx_packets = 0; + priv->last_tx_packets = 0; + priv->last_tx_failures = 0; + + /* Firmware managed, reset only when NIC is restarted, so we have to + * normalize on the current value */ + ipw_get_ordinal(priv, IPW_ORD_STAT_RX_ERR_CRC, + &priv->last_rx_err, &len); + ipw_get_ordinal(priv, IPW_ORD_STAT_TX_FAILURE, + &priv->last_tx_failures, &len); + + /* Driver managed, reset with each association */ + priv->missed_adhoc_beacons = 0; + priv->missed_beacons = 0; + priv->tx_packets = 0; + priv->rx_packets = 0; + +} + +static u32 ipw_get_max_rate(struct ipw_priv *priv) +{ + u32 i = 0x80000000; + u32 mask = priv->rates_mask; + /* If currently associated in B mode, restrict the maximum + * rate match to B rates */ + if (priv->assoc_request.ieee_mode == IPW_B_MODE) + mask &= LIBIPW_CCK_RATES_MASK; + + /* TODO: Verify that the rate is supported by the current rates + * list. */ + + while (i && !(mask & i)) + i >>= 1; + switch (i) { + case LIBIPW_CCK_RATE_1MB_MASK: + return 1000000; + case LIBIPW_CCK_RATE_2MB_MASK: + return 2000000; + case LIBIPW_CCK_RATE_5MB_MASK: + return 5500000; + case LIBIPW_OFDM_RATE_6MB_MASK: + return 6000000; + case LIBIPW_OFDM_RATE_9MB_MASK: + return 9000000; + case LIBIPW_CCK_RATE_11MB_MASK: + return 11000000; + case LIBIPW_OFDM_RATE_12MB_MASK: + return 12000000; + case LIBIPW_OFDM_RATE_18MB_MASK: + return 18000000; + case LIBIPW_OFDM_RATE_24MB_MASK: + return 24000000; + case LIBIPW_OFDM_RATE_36MB_MASK: + return 36000000; + case LIBIPW_OFDM_RATE_48MB_MASK: + return 48000000; + case LIBIPW_OFDM_RATE_54MB_MASK: + return 54000000; + } + + if (priv->ieee->mode == IEEE_B) + return 11000000; + else + return 54000000; +} + +static u32 ipw_get_current_rate(struct ipw_priv *priv) +{ + u32 rate, len = sizeof(rate); + int err; + + if (!(priv->status & STATUS_ASSOCIATED)) + return 0; + + if (priv->tx_packets > IPW_REAL_RATE_RX_PACKET_THRESHOLD) { + err = ipw_get_ordinal(priv, IPW_ORD_STAT_TX_CURR_RATE, &rate, + &len); + if (err) { + IPW_DEBUG_INFO("failed querying ordinals.\n"); + return 0; + } + } else + return ipw_get_max_rate(priv); + + switch (rate) { + case IPW_TX_RATE_1MB: + return 1000000; + case IPW_TX_RATE_2MB: + return 2000000; + case IPW_TX_RATE_5MB: + return 5500000; + case IPW_TX_RATE_6MB: + return 6000000; + case IPW_TX_RATE_9MB: + return 9000000; + case IPW_TX_RATE_11MB: + return 11000000; + case IPW_TX_RATE_12MB: + return 12000000; + case IPW_TX_RATE_18MB: + return 18000000; + case IPW_TX_RATE_24MB: + return 24000000; + case IPW_TX_RATE_36MB: + return 36000000; + case IPW_TX_RATE_48MB: + return 48000000; + case IPW_TX_RATE_54MB: + return 54000000; + } + + return 0; +} + +#define IPW_STATS_INTERVAL (2 * HZ) +static void ipw_gather_stats(struct ipw_priv *priv) +{ + u32 rx_err, rx_err_delta, rx_packets_delta; + u32 tx_failures, tx_failures_delta, tx_packets_delta; + u32 missed_beacons_percent, missed_beacons_delta; + u32 quality = 0; + u32 len = sizeof(u32); + s16 rssi; + u32 beacon_quality, signal_quality, tx_quality, rx_quality, + rate_quality; + u32 max_rate; + + if (!(priv->status & STATUS_ASSOCIATED)) { + priv->quality = 0; + return; + } + + /* Update the statistics */ + ipw_get_ordinal(priv, IPW_ORD_STAT_MISSED_BEACONS, + &priv->missed_beacons, &len); + missed_beacons_delta = priv->missed_beacons - priv->last_missed_beacons; + priv->last_missed_beacons = priv->missed_beacons; + if (priv->assoc_request.beacon_interval) { + missed_beacons_percent = missed_beacons_delta * + (HZ * le16_to_cpu(priv->assoc_request.beacon_interval)) / + (IPW_STATS_INTERVAL * 10); + } else { + missed_beacons_percent = 0; + } + average_add(&priv->average_missed_beacons, missed_beacons_percent); + + ipw_get_ordinal(priv, IPW_ORD_STAT_RX_ERR_CRC, &rx_err, &len); + rx_err_delta = rx_err - priv->last_rx_err; + priv->last_rx_err = rx_err; + + ipw_get_ordinal(priv, IPW_ORD_STAT_TX_FAILURE, &tx_failures, &len); + tx_failures_delta = tx_failures - priv->last_tx_failures; + priv->last_tx_failures = tx_failures; + + rx_packets_delta = priv->rx_packets - priv->last_rx_packets; + priv->last_rx_packets = priv->rx_packets; + + tx_packets_delta = priv->tx_packets - priv->last_tx_packets; + priv->last_tx_packets = priv->tx_packets; + + /* Calculate quality based on the following: + * + * Missed beacon: 100% = 0, 0% = 70% missed + * Rate: 60% = 1Mbs, 100% = Max + * Rx and Tx errors represent a straight % of total Rx/Tx + * RSSI: 100% = > -50, 0% = < -80 + * Rx errors: 100% = 0, 0% = 50% missed + * + * The lowest computed quality is used. + * + */ +#define BEACON_THRESHOLD 5 + beacon_quality = 100 - missed_beacons_percent; + if (beacon_quality < BEACON_THRESHOLD) + beacon_quality = 0; + else + beacon_quality = (beacon_quality - BEACON_THRESHOLD) * 100 / + (100 - BEACON_THRESHOLD); + IPW_DEBUG_STATS("Missed beacon: %3d%% (%d%%)\n", + beacon_quality, missed_beacons_percent); + + priv->last_rate = ipw_get_current_rate(priv); + max_rate = ipw_get_max_rate(priv); + rate_quality = priv->last_rate * 40 / max_rate + 60; + IPW_DEBUG_STATS("Rate quality : %3d%% (%dMbs)\n", + rate_quality, priv->last_rate / 1000000); + + if (rx_packets_delta > 100 && rx_packets_delta + rx_err_delta) + rx_quality = 100 - (rx_err_delta * 100) / + (rx_packets_delta + rx_err_delta); + else + rx_quality = 100; + IPW_DEBUG_STATS("Rx quality : %3d%% (%u errors, %u packets)\n", + rx_quality, rx_err_delta, rx_packets_delta); + + if (tx_packets_delta > 100 && tx_packets_delta + tx_failures_delta) + tx_quality = 100 - (tx_failures_delta * 100) / + (tx_packets_delta + tx_failures_delta); + else + tx_quality = 100; + IPW_DEBUG_STATS("Tx quality : %3d%% (%u errors, %u packets)\n", + tx_quality, tx_failures_delta, tx_packets_delta); + + rssi = priv->exp_avg_rssi; + signal_quality = + (100 * + (priv->ieee->perfect_rssi - priv->ieee->worst_rssi) * + (priv->ieee->perfect_rssi - priv->ieee->worst_rssi) - + (priv->ieee->perfect_rssi - rssi) * + (15 * (priv->ieee->perfect_rssi - priv->ieee->worst_rssi) + + 62 * (priv->ieee->perfect_rssi - rssi))) / + ((priv->ieee->perfect_rssi - priv->ieee->worst_rssi) * + (priv->ieee->perfect_rssi - priv->ieee->worst_rssi)); + if (signal_quality > 100) + signal_quality = 100; + else if (signal_quality < 1) + signal_quality = 0; + + IPW_DEBUG_STATS("Signal level : %3d%% (%d dBm)\n", + signal_quality, rssi); + + quality = min(rx_quality, signal_quality); + quality = min(tx_quality, quality); + quality = min(rate_quality, quality); + quality = min(beacon_quality, quality); + if (quality == beacon_quality) + IPW_DEBUG_STATS("Quality (%d%%): Clamped to missed beacons.\n", + quality); + if (quality == rate_quality) + IPW_DEBUG_STATS("Quality (%d%%): Clamped to rate quality.\n", + quality); + if (quality == tx_quality) + IPW_DEBUG_STATS("Quality (%d%%): Clamped to Tx quality.\n", + quality); + if (quality == rx_quality) + IPW_DEBUG_STATS("Quality (%d%%): Clamped to Rx quality.\n", + quality); + if (quality == signal_quality) + IPW_DEBUG_STATS("Quality (%d%%): Clamped to signal quality.\n", + quality); + + priv->quality = quality; + + schedule_delayed_work(&priv->gather_stats, IPW_STATS_INTERVAL); +} + +static void ipw_bg_gather_stats(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, gather_stats.work); + mutex_lock(&priv->mutex); + ipw_gather_stats(priv); + mutex_unlock(&priv->mutex); +} + +/* Missed beacon behavior: + * 1st missed -> roaming_threshold, just wait, don't do any scan/roam. + * roaming_threshold -> disassociate_threshold, scan and roam for better signal. + * Above disassociate threshold, give up and stop scanning. + * Roaming is disabled if disassociate_threshold <= roaming_threshold */ +static void ipw_handle_missed_beacon(struct ipw_priv *priv, + int missed_count) +{ + priv->notif_missed_beacons = missed_count; + + if (missed_count > priv->disassociate_threshold && + priv->status & STATUS_ASSOCIATED) { + /* If associated and we've hit the missed + * beacon threshold, disassociate, turn + * off roaming, and abort any active scans */ + IPW_DEBUG(IPW_DL_INFO | IPW_DL_NOTIF | + IPW_DL_STATE | IPW_DL_ASSOC, + "Missed beacon: %d - disassociate\n", missed_count); + priv->status &= ~STATUS_ROAMING; + if (priv->status & STATUS_SCANNING) { + IPW_DEBUG(IPW_DL_INFO | IPW_DL_NOTIF | + IPW_DL_STATE, + "Aborting scan with missed beacon.\n"); + schedule_work(&priv->abort_scan); + } + + schedule_work(&priv->disassociate); + return; + } + + if (priv->status & STATUS_ROAMING) { + /* If we are currently roaming, then just + * print a debug statement... */ + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE, + "Missed beacon: %d - roam in progress\n", + missed_count); + return; + } + + if (roaming && + (missed_count > priv->roaming_threshold && + missed_count <= priv->disassociate_threshold)) { + /* If we are not already roaming, set the ROAM + * bit in the status and kick off a scan. + * This can happen several times before we reach + * disassociate_threshold. */ + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE, + "Missed beacon: %d - initiate " + "roaming\n", missed_count); + if (!(priv->status & STATUS_ROAMING)) { + priv->status |= STATUS_ROAMING; + if (!(priv->status & STATUS_SCANNING)) + schedule_delayed_work(&priv->request_scan, 0); + } + return; + } + + if (priv->status & STATUS_SCANNING && + missed_count > IPW_MB_SCAN_CANCEL_THRESHOLD) { + /* Stop scan to keep fw from getting + * stuck (only if we aren't roaming -- + * otherwise we'll never scan more than 2 or 3 + * channels..) */ + IPW_DEBUG(IPW_DL_INFO | IPW_DL_NOTIF | IPW_DL_STATE, + "Aborting scan with missed beacon.\n"); + schedule_work(&priv->abort_scan); + } + + IPW_DEBUG_NOTIF("Missed beacon: %d\n", missed_count); +} + +static void ipw_scan_event(struct work_struct *work) +{ + union iwreq_data wrqu; + + struct ipw_priv *priv = + container_of(work, struct ipw_priv, scan_event.work); + + wrqu.data.length = 0; + wrqu.data.flags = 0; + wireless_send_event(priv->net_dev, SIOCGIWSCAN, &wrqu, NULL); +} + +static void handle_scan_event(struct ipw_priv *priv) +{ + /* Only userspace-requested scan completion events go out immediately */ + if (!priv->user_requested_scan) { + schedule_delayed_work(&priv->scan_event, + round_jiffies_relative(msecs_to_jiffies(4000))); + } else { + priv->user_requested_scan = 0; + mod_delayed_work(system_wq, &priv->scan_event, 0); + } +} + +/** + * Handle host notification packet. + * Called from interrupt routine + */ +static void ipw_rx_notification(struct ipw_priv *priv, + struct ipw_rx_notification *notif) +{ + DECLARE_SSID_BUF(ssid); + u16 size = le16_to_cpu(notif->size); + + IPW_DEBUG_NOTIF("type = %i (%d bytes)\n", notif->subtype, size); + + switch (notif->subtype) { + case HOST_NOTIFICATION_STATUS_ASSOCIATED:{ + struct notif_association *assoc = ¬if->u.assoc; + + switch (assoc->state) { + case CMAS_ASSOCIATED:{ + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | + IPW_DL_ASSOC, + "associated: '%s' %pM\n", + print_ssid(ssid, priv->essid, + priv->essid_len), + priv->bssid); + + switch (priv->ieee->iw_mode) { + case IW_MODE_INFRA: + memcpy(priv->ieee->bssid, + priv->bssid, ETH_ALEN); + break; + + case IW_MODE_ADHOC: + memcpy(priv->ieee->bssid, + priv->bssid, ETH_ALEN); + + /* clear out the station table */ + priv->num_stations = 0; + + IPW_DEBUG_ASSOC + ("queueing adhoc check\n"); + schedule_delayed_work( + &priv->adhoc_check, + le16_to_cpu(priv-> + assoc_request. + beacon_interval)); + break; + } + + priv->status &= ~STATUS_ASSOCIATING; + priv->status |= STATUS_ASSOCIATED; + schedule_work(&priv->system_config); + +#ifdef CPTCFG_IPW2200_QOS +#define IPW_GET_PACKET_STYPE(x) WLAN_FC_GET_STYPE( \ + le16_to_cpu(((struct ieee80211_hdr *)(x))->frame_control)) + if ((priv->status & STATUS_AUTH) && + (IPW_GET_PACKET_STYPE(¬if->u.raw) + == IEEE80211_STYPE_ASSOC_RESP)) { + if ((sizeof + (struct + libipw_assoc_response) + <= size) + && (size <= 2314)) { + struct + libipw_rx_stats + stats = { + .len = size - 1, + }; + + IPW_DEBUG_QOS + ("QoS Associate " + "size %d\n", size); + libipw_rx_mgt(priv-> + ieee, + (struct + libipw_hdr_4addr + *) + ¬if->u.raw, &stats); + } + } +#endif + + schedule_work(&priv->link_up); + + break; + } + + case CMAS_AUTHENTICATED:{ + if (priv-> + status & (STATUS_ASSOCIATED | + STATUS_AUTH)) { + struct notif_authenticate *auth + = ¬if->u.auth; + IPW_DEBUG(IPW_DL_NOTIF | + IPW_DL_STATE | + IPW_DL_ASSOC, + "deauthenticated: '%s' " + "%pM" + ": (0x%04X) - %s\n", + print_ssid(ssid, + priv-> + essid, + priv-> + essid_len), + priv->bssid, + le16_to_cpu(auth->status), + ipw_get_status_code + (le16_to_cpu + (auth->status))); + + priv->status &= + ~(STATUS_ASSOCIATING | + STATUS_AUTH | + STATUS_ASSOCIATED); + + schedule_work(&priv->link_down); + break; + } + + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | + IPW_DL_ASSOC, + "authenticated: '%s' %pM\n", + print_ssid(ssid, priv->essid, + priv->essid_len), + priv->bssid); + break; + } + + case CMAS_INIT:{ + if (priv->status & STATUS_AUTH) { + struct + libipw_assoc_response + *resp; + resp = + (struct + libipw_assoc_response + *)¬if->u.raw; + IPW_DEBUG(IPW_DL_NOTIF | + IPW_DL_STATE | + IPW_DL_ASSOC, + "association failed (0x%04X): %s\n", + le16_to_cpu(resp->status), + ipw_get_status_code + (le16_to_cpu + (resp->status))); + } + + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | + IPW_DL_ASSOC, + "disassociated: '%s' %pM\n", + print_ssid(ssid, priv->essid, + priv->essid_len), + priv->bssid); + + priv->status &= + ~(STATUS_DISASSOCIATING | + STATUS_ASSOCIATING | + STATUS_ASSOCIATED | STATUS_AUTH); + if (priv->assoc_network + && (priv->assoc_network-> + capability & + WLAN_CAPABILITY_IBSS)) + ipw_remove_current_network + (priv); + + schedule_work(&priv->link_down); + + break; + } + + case CMAS_RX_ASSOC_RESP: + break; + + default: + IPW_ERROR("assoc: unknown (%d)\n", + assoc->state); + break; + } + + break; + } + + case HOST_NOTIFICATION_STATUS_AUTHENTICATE:{ + struct notif_authenticate *auth = ¬if->u.auth; + switch (auth->state) { + case CMAS_AUTHENTICATED: + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE, + "authenticated: '%s' %pM\n", + print_ssid(ssid, priv->essid, + priv->essid_len), + priv->bssid); + priv->status |= STATUS_AUTH; + break; + + case CMAS_INIT: + if (priv->status & STATUS_AUTH) { + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | + IPW_DL_ASSOC, + "authentication failed (0x%04X): %s\n", + le16_to_cpu(auth->status), + ipw_get_status_code(le16_to_cpu + (auth-> + status))); + } + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | + IPW_DL_ASSOC, + "deauthenticated: '%s' %pM\n", + print_ssid(ssid, priv->essid, + priv->essid_len), + priv->bssid); + + priv->status &= ~(STATUS_ASSOCIATING | + STATUS_AUTH | + STATUS_ASSOCIATED); + + schedule_work(&priv->link_down); + break; + + case CMAS_TX_AUTH_SEQ_1: + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | + IPW_DL_ASSOC, "AUTH_SEQ_1\n"); + break; + case CMAS_RX_AUTH_SEQ_2: + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | + IPW_DL_ASSOC, "AUTH_SEQ_2\n"); + break; + case CMAS_AUTH_SEQ_1_PASS: + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | + IPW_DL_ASSOC, "AUTH_SEQ_1_PASS\n"); + break; + case CMAS_AUTH_SEQ_1_FAIL: + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | + IPW_DL_ASSOC, "AUTH_SEQ_1_FAIL\n"); + break; + case CMAS_TX_AUTH_SEQ_3: + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | + IPW_DL_ASSOC, "AUTH_SEQ_3\n"); + break; + case CMAS_RX_AUTH_SEQ_4: + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | + IPW_DL_ASSOC, "RX_AUTH_SEQ_4\n"); + break; + case CMAS_AUTH_SEQ_2_PASS: + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | + IPW_DL_ASSOC, "AUTH_SEQ_2_PASS\n"); + break; + case CMAS_AUTH_SEQ_2_FAIL: + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | + IPW_DL_ASSOC, "AUT_SEQ_2_FAIL\n"); + break; + case CMAS_TX_ASSOC: + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | + IPW_DL_ASSOC, "TX_ASSOC\n"); + break; + case CMAS_RX_ASSOC_RESP: + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | + IPW_DL_ASSOC, "RX_ASSOC_RESP\n"); + + break; + case CMAS_ASSOCIATED: + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE | + IPW_DL_ASSOC, "ASSOCIATED\n"); + break; + default: + IPW_DEBUG_NOTIF("auth: failure - %d\n", + auth->state); + break; + } + break; + } + + case HOST_NOTIFICATION_STATUS_SCAN_CHANNEL_RESULT:{ + struct notif_channel_result *x = + ¬if->u.channel_result; + + if (size == sizeof(*x)) { + IPW_DEBUG_SCAN("Scan result for channel %d\n", + x->channel_num); + } else { + IPW_DEBUG_SCAN("Scan result of wrong size %d " + "(should be %zd)\n", + size, sizeof(*x)); + } + break; + } + + case HOST_NOTIFICATION_STATUS_SCAN_COMPLETED:{ + struct notif_scan_complete *x = ¬if->u.scan_complete; + if (size == sizeof(*x)) { + IPW_DEBUG_SCAN + ("Scan completed: type %d, %d channels, " + "%d status\n", x->scan_type, + x->num_channels, x->status); + } else { + IPW_ERROR("Scan completed of wrong size %d " + "(should be %zd)\n", + size, sizeof(*x)); + } + + priv->status &= + ~(STATUS_SCANNING | STATUS_SCAN_ABORTING); + + wake_up_interruptible(&priv->wait_state); + cancel_delayed_work(&priv->scan_check); + + if (priv->status & STATUS_EXIT_PENDING) + break; + + priv->ieee->scans++; + +#ifdef CPTCFG_IPW2200_MONITOR + if (priv->ieee->iw_mode == IW_MODE_MONITOR) { + priv->status |= STATUS_SCAN_FORCED; + schedule_delayed_work(&priv->request_scan, 0); + break; + } + priv->status &= ~STATUS_SCAN_FORCED; +#endif /* CPTCFG_IPW2200_MONITOR */ + + /* Do queued direct scans first */ + if (priv->status & STATUS_DIRECT_SCAN_PENDING) + schedule_delayed_work(&priv->request_direct_scan, 0); + + if (!(priv->status & (STATUS_ASSOCIATED | + STATUS_ASSOCIATING | + STATUS_ROAMING | + STATUS_DISASSOCIATING))) + schedule_work(&priv->associate); + else if (priv->status & STATUS_ROAMING) { + if (x->status == SCAN_COMPLETED_STATUS_COMPLETE) + /* If a scan completed and we are in roam mode, then + * the scan that completed was the one requested as a + * result of entering roam... so, schedule the + * roam work */ + schedule_work(&priv->roam); + else + /* Don't schedule if we aborted the scan */ + priv->status &= ~STATUS_ROAMING; + } else if (priv->status & STATUS_SCAN_PENDING) + schedule_delayed_work(&priv->request_scan, 0); + else if (priv->config & CFG_BACKGROUND_SCAN + && priv->status & STATUS_ASSOCIATED) + schedule_delayed_work(&priv->request_scan, + round_jiffies_relative(HZ)); + + /* Send an empty event to user space. + * We don't send the received data on the event because + * it would require us to do complex transcoding, and + * we want to minimise the work done in the irq handler + * Use a request to extract the data. + * Also, we generate this even for any scan, regardless + * on how the scan was initiated. User space can just + * sync on periodic scan to get fresh data... + * Jean II */ + if (x->status == SCAN_COMPLETED_STATUS_COMPLETE) + handle_scan_event(priv); + break; + } + + case HOST_NOTIFICATION_STATUS_FRAG_LENGTH:{ + struct notif_frag_length *x = ¬if->u.frag_len; + + if (size == sizeof(*x)) + IPW_ERROR("Frag length: %d\n", + le16_to_cpu(x->frag_length)); + else + IPW_ERROR("Frag length of wrong size %d " + "(should be %zd)\n", + size, sizeof(*x)); + break; + } + + case HOST_NOTIFICATION_STATUS_LINK_DETERIORATION:{ + struct notif_link_deterioration *x = + ¬if->u.link_deterioration; + + if (size == sizeof(*x)) { + IPW_DEBUG(IPW_DL_NOTIF | IPW_DL_STATE, + "link deterioration: type %d, cnt %d\n", + x->silence_notification_type, + x->silence_count); + memcpy(&priv->last_link_deterioration, x, + sizeof(*x)); + } else { + IPW_ERROR("Link Deterioration of wrong size %d " + "(should be %zd)\n", + size, sizeof(*x)); + } + break; + } + + case HOST_NOTIFICATION_DINO_CONFIG_RESPONSE:{ + IPW_ERROR("Dino config\n"); + if (priv->hcmd + && priv->hcmd->cmd != HOST_CMD_DINO_CONFIG) + IPW_ERROR("Unexpected DINO_CONFIG_RESPONSE\n"); + + break; + } + + case HOST_NOTIFICATION_STATUS_BEACON_STATE:{ + struct notif_beacon_state *x = ¬if->u.beacon_state; + if (size != sizeof(*x)) { + IPW_ERROR + ("Beacon state of wrong size %d (should " + "be %zd)\n", size, sizeof(*x)); + break; + } + + if (le32_to_cpu(x->state) == + HOST_NOTIFICATION_STATUS_BEACON_MISSING) + ipw_handle_missed_beacon(priv, + le32_to_cpu(x-> + number)); + + break; + } + + case HOST_NOTIFICATION_STATUS_TGI_TX_KEY:{ + struct notif_tgi_tx_key *x = ¬if->u.tgi_tx_key; + if (size == sizeof(*x)) { + IPW_ERROR("TGi Tx Key: state 0x%02x sec type " + "0x%02x station %d\n", + x->key_state, x->security_type, + x->station_index); + break; + } + + IPW_ERROR + ("TGi Tx Key of wrong size %d (should be %zd)\n", + size, sizeof(*x)); + break; + } + + case HOST_NOTIFICATION_CALIB_KEEP_RESULTS:{ + struct notif_calibration *x = ¬if->u.calibration; + + if (size == sizeof(*x)) { + memcpy(&priv->calib, x, sizeof(*x)); + IPW_DEBUG_INFO("TODO: Calibration\n"); + break; + } + + IPW_ERROR + ("Calibration of wrong size %d (should be %zd)\n", + size, sizeof(*x)); + break; + } + + case HOST_NOTIFICATION_NOISE_STATS:{ + if (size == sizeof(u32)) { + priv->exp_avg_noise = + exponential_average(priv->exp_avg_noise, + (u8) (le32_to_cpu(notif->u.noise.value) & 0xff), + DEPTH_NOISE); + break; + } + + IPW_ERROR + ("Noise stat is wrong size %d (should be %zd)\n", + size, sizeof(u32)); + break; + } + + default: + IPW_DEBUG_NOTIF("Unknown notification: " + "subtype=%d,flags=0x%2x,size=%d\n", + notif->subtype, notif->flags, size); + } +} + +/** + * Destroys all DMA structures and initialise them again + * + * @param priv + * @return error code + */ +static int ipw_queue_reset(struct ipw_priv *priv) +{ + int rc = 0; + /** @todo customize queue sizes */ + int nTx = 64, nTxCmd = 8; + ipw_tx_queue_free(priv); + /* Tx CMD queue */ + rc = ipw_queue_tx_init(priv, &priv->txq_cmd, nTxCmd, + IPW_TX_CMD_QUEUE_READ_INDEX, + IPW_TX_CMD_QUEUE_WRITE_INDEX, + IPW_TX_CMD_QUEUE_BD_BASE, + IPW_TX_CMD_QUEUE_BD_SIZE); + if (rc) { + IPW_ERROR("Tx Cmd queue init failed\n"); + goto error; + } + /* Tx queue(s) */ + rc = ipw_queue_tx_init(priv, &priv->txq[0], nTx, + IPW_TX_QUEUE_0_READ_INDEX, + IPW_TX_QUEUE_0_WRITE_INDEX, + IPW_TX_QUEUE_0_BD_BASE, IPW_TX_QUEUE_0_BD_SIZE); + if (rc) { + IPW_ERROR("Tx 0 queue init failed\n"); + goto error; + } + rc = ipw_queue_tx_init(priv, &priv->txq[1], nTx, + IPW_TX_QUEUE_1_READ_INDEX, + IPW_TX_QUEUE_1_WRITE_INDEX, + IPW_TX_QUEUE_1_BD_BASE, IPW_TX_QUEUE_1_BD_SIZE); + if (rc) { + IPW_ERROR("Tx 1 queue init failed\n"); + goto error; + } + rc = ipw_queue_tx_init(priv, &priv->txq[2], nTx, + IPW_TX_QUEUE_2_READ_INDEX, + IPW_TX_QUEUE_2_WRITE_INDEX, + IPW_TX_QUEUE_2_BD_BASE, IPW_TX_QUEUE_2_BD_SIZE); + if (rc) { + IPW_ERROR("Tx 2 queue init failed\n"); + goto error; + } + rc = ipw_queue_tx_init(priv, &priv->txq[3], nTx, + IPW_TX_QUEUE_3_READ_INDEX, + IPW_TX_QUEUE_3_WRITE_INDEX, + IPW_TX_QUEUE_3_BD_BASE, IPW_TX_QUEUE_3_BD_SIZE); + if (rc) { + IPW_ERROR("Tx 3 queue init failed\n"); + goto error; + } + /* statistics */ + priv->rx_bufs_min = 0; + priv->rx_pend_max = 0; + return rc; + + error: + ipw_tx_queue_free(priv); + return rc; +} + +/** + * Reclaim Tx queue entries no more used by NIC. + * + * When FW advances 'R' index, all entries between old and + * new 'R' index need to be reclaimed. As result, some free space + * forms. If there is enough free space (> low mark), wake Tx queue. + * + * @note Need to protect against garbage in 'R' index + * @param priv + * @param txq + * @param qindex + * @return Number of used entries remains in the queue + */ +static int ipw_queue_tx_reclaim(struct ipw_priv *priv, + struct clx2_tx_queue *txq, int qindex) +{ + u32 hw_tail; + int used; + struct clx2_queue *q = &txq->q; + + hw_tail = ipw_read32(priv, q->reg_r); + if (hw_tail >= q->n_bd) { + IPW_ERROR + ("Read index for DMA queue (%d) is out of range [0-%d)\n", + hw_tail, q->n_bd); + goto done; + } + for (; q->last_used != hw_tail; + q->last_used = ipw_queue_inc_wrap(q->last_used, q->n_bd)) { + ipw_queue_tx_free_tfd(priv, txq); + priv->tx_packets++; + } + done: + if ((ipw_tx_queue_space(q) > q->low_mark) && + (qindex >= 0)) + netif_wake_queue(priv->net_dev); + used = q->first_empty - q->last_used; + if (used < 0) + used += q->n_bd; + + return used; +} + +static int ipw_queue_tx_hcmd(struct ipw_priv *priv, int hcmd, void *buf, + int len, int sync) +{ + struct clx2_tx_queue *txq = &priv->txq_cmd; + struct clx2_queue *q = &txq->q; + struct tfd_frame *tfd; + + if (ipw_tx_queue_space(q) < (sync ? 1 : 2)) { + IPW_ERROR("No space for Tx\n"); + return -EBUSY; + } + + tfd = &txq->bd[q->first_empty]; + txq->txb[q->first_empty] = NULL; + + memset(tfd, 0, sizeof(*tfd)); + tfd->control_flags.message_type = TX_HOST_COMMAND_TYPE; + tfd->control_flags.control_bits = TFD_NEED_IRQ_MASK; + priv->hcmd_seq++; + tfd->u.cmd.index = hcmd; + tfd->u.cmd.length = len; + memcpy(tfd->u.cmd.payload, buf, len); + q->first_empty = ipw_queue_inc_wrap(q->first_empty, q->n_bd); + ipw_write32(priv, q->reg_w, q->first_empty); + _ipw_read32(priv, 0x90); + + return 0; +} + +/* + * Rx theory of operation + * + * The host allocates 32 DMA target addresses and passes the host address + * to the firmware at register IPW_RFDS_TABLE_LOWER + N * RFD_SIZE where N is + * 0 to 31 + * + * Rx Queue Indexes + * The host/firmware share two index registers for managing the Rx buffers. + * + * The READ index maps to the first position that the firmware may be writing + * to -- the driver can read up to (but not including) this position and get + * good data. + * The READ index is managed by the firmware once the card is enabled. + * + * The WRITE index maps to the last position the driver has read from -- the + * position preceding WRITE is the last slot the firmware can place a packet. + * + * The queue is empty (no good data) if WRITE = READ - 1, and is full if + * WRITE = READ. + * + * During initialization the host sets up the READ queue position to the first + * INDEX position, and WRITE to the last (READ - 1 wrapped) + * + * When the firmware places a packet in a buffer it will advance the READ index + * and fire the RX interrupt. The driver can then query the READ index and + * process as many packets as possible, moving the WRITE index forward as it + * resets the Rx queue buffers with new memory. + * + * The management in the driver is as follows: + * + A list of pre-allocated SKBs is stored in ipw->rxq->rx_free. When + * ipw->rxq->free_count drops to or below RX_LOW_WATERMARK, work is scheduled + * to replensish the ipw->rxq->rx_free. + * + In ipw_rx_queue_replenish (scheduled) if 'processed' != 'read' then the + * ipw->rxq is replenished and the READ INDEX is updated (updating the + * 'processed' and 'read' driver indexes as well) + * + A received packet is processed and handed to the kernel network stack, + * detached from the ipw->rxq. The driver 'processed' index is updated. + * + The Host/Firmware ipw->rxq is replenished at tasklet time from the rx_free + * list. If there are no allocated buffers in ipw->rxq->rx_free, the READ + * INDEX is not incremented and ipw->status(RX_STALLED) is set. If there + * were enough free buffers and RX_STALLED is set it is cleared. + * + * + * Driver sequence: + * + * ipw_rx_queue_alloc() Allocates rx_free + * ipw_rx_queue_replenish() Replenishes rx_free list from rx_used, and calls + * ipw_rx_queue_restock + * ipw_rx_queue_restock() Moves available buffers from rx_free into Rx + * queue, updates firmware pointers, and updates + * the WRITE index. If insufficient rx_free buffers + * are available, schedules ipw_rx_queue_replenish + * + * -- enable interrupts -- + * ISR - ipw_rx() Detach ipw_rx_mem_buffers from pool up to the + * READ INDEX, detaching the SKB from the pool. + * Moves the packet buffer from queue to rx_used. + * Calls ipw_rx_queue_restock to refill any empty + * slots. + * ... + * + */ + +/* + * If there are slots in the RX queue that need to be restocked, + * and we have free pre-allocated buffers, fill the ranks as much + * as we can pulling from rx_free. + * + * This moves the 'write' index forward to catch up with 'processed', and + * also updates the memory address in the firmware to reference the new + * target buffer. + */ +static void ipw_rx_queue_restock(struct ipw_priv *priv) +{ + struct ipw_rx_queue *rxq = priv->rxq; + struct list_head *element; + struct ipw_rx_mem_buffer *rxb; + unsigned long flags; + int write; + + spin_lock_irqsave(&rxq->lock, flags); + write = rxq->write; + while ((ipw_rx_queue_space(rxq) > 0) && (rxq->free_count)) { + element = rxq->rx_free.next; + rxb = list_entry(element, struct ipw_rx_mem_buffer, list); + list_del(element); + + ipw_write32(priv, IPW_RFDS_TABLE_LOWER + rxq->write * RFD_SIZE, + rxb->dma_addr); + rxq->queue[rxq->write] = rxb; + rxq->write = (rxq->write + 1) % RX_QUEUE_SIZE; + rxq->free_count--; + } + spin_unlock_irqrestore(&rxq->lock, flags); + + /* If the pre-allocated buffer pool is dropping low, schedule to + * refill it */ + if (rxq->free_count <= RX_LOW_WATERMARK) + schedule_work(&priv->rx_replenish); + + /* If we've added more space for the firmware to place data, tell it */ + if (write != rxq->write) + ipw_write32(priv, IPW_RX_WRITE_INDEX, rxq->write); +} + +/* + * Move all used packet from rx_used to rx_free, allocating a new SKB for each. + * Also restock the Rx queue via ipw_rx_queue_restock. + * + * This is called as a scheduled work item (except for during intialization) + */ +static void ipw_rx_queue_replenish(void *data) +{ + struct ipw_priv *priv = data; + struct ipw_rx_queue *rxq = priv->rxq; + struct list_head *element; + struct ipw_rx_mem_buffer *rxb; + unsigned long flags; + + spin_lock_irqsave(&rxq->lock, flags); + while (!list_empty(&rxq->rx_used)) { + element = rxq->rx_used.next; + rxb = list_entry(element, struct ipw_rx_mem_buffer, list); + rxb->skb = alloc_skb(IPW_RX_BUF_SIZE, GFP_ATOMIC); + if (!rxb->skb) { + printk(KERN_CRIT "%s: Can not allocate SKB buffers.\n", + priv->net_dev->name); + /* We don't reschedule replenish work here -- we will + * call the restock method and if it still needs + * more buffers it will schedule replenish */ + break; + } + list_del(element); + + rxb->dma_addr = + pci_map_single(priv->pci_dev, rxb->skb->data, + IPW_RX_BUF_SIZE, PCI_DMA_FROMDEVICE); + + list_add_tail(&rxb->list, &rxq->rx_free); + rxq->free_count++; + } + spin_unlock_irqrestore(&rxq->lock, flags); + + ipw_rx_queue_restock(priv); +} + +static void ipw_bg_rx_queue_replenish(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, rx_replenish); + mutex_lock(&priv->mutex); + ipw_rx_queue_replenish(priv); + mutex_unlock(&priv->mutex); +} + +/* Assumes that the skb field of the buffers in 'pool' is kept accurate. + * If an SKB has been detached, the POOL needs to have its SKB set to NULL + * This free routine walks the list of POOL entries and if SKB is set to + * non NULL it is unmapped and freed + */ +static void ipw_rx_queue_free(struct ipw_priv *priv, struct ipw_rx_queue *rxq) +{ + int i; + + if (!rxq) + return; + + for (i = 0; i < RX_QUEUE_SIZE + RX_FREE_BUFFERS; i++) { + if (rxq->pool[i].skb != NULL) { + pci_unmap_single(priv->pci_dev, rxq->pool[i].dma_addr, + IPW_RX_BUF_SIZE, PCI_DMA_FROMDEVICE); + dev_kfree_skb(rxq->pool[i].skb); + } + } + + kfree(rxq); +} + +static struct ipw_rx_queue *ipw_rx_queue_alloc(struct ipw_priv *priv) +{ + struct ipw_rx_queue *rxq; + int i; + + rxq = kzalloc(sizeof(*rxq), GFP_KERNEL); + if (unlikely(!rxq)) { + IPW_ERROR("memory allocation failed\n"); + return NULL; + } + spin_lock_init(&rxq->lock); + INIT_LIST_HEAD(&rxq->rx_free); + INIT_LIST_HEAD(&rxq->rx_used); + + /* Fill the rx_used queue with _all_ of the Rx buffers */ + for (i = 0; i < RX_FREE_BUFFERS + RX_QUEUE_SIZE; i++) + list_add_tail(&rxq->pool[i].list, &rxq->rx_used); + + /* Set us so that we have processed and used all buffers, but have + * not restocked the Rx queue with fresh buffers */ + rxq->read = rxq->write = 0; + rxq->free_count = 0; + + return rxq; +} + +static int ipw_is_rate_in_mask(struct ipw_priv *priv, int ieee_mode, u8 rate) +{ + rate &= ~LIBIPW_BASIC_RATE_MASK; + if (ieee_mode == IEEE_A) { + switch (rate) { + case LIBIPW_OFDM_RATE_6MB: + return priv->rates_mask & LIBIPW_OFDM_RATE_6MB_MASK ? + 1 : 0; + case LIBIPW_OFDM_RATE_9MB: + return priv->rates_mask & LIBIPW_OFDM_RATE_9MB_MASK ? + 1 : 0; + case LIBIPW_OFDM_RATE_12MB: + return priv-> + rates_mask & LIBIPW_OFDM_RATE_12MB_MASK ? 1 : 0; + case LIBIPW_OFDM_RATE_18MB: + return priv-> + rates_mask & LIBIPW_OFDM_RATE_18MB_MASK ? 1 : 0; + case LIBIPW_OFDM_RATE_24MB: + return priv-> + rates_mask & LIBIPW_OFDM_RATE_24MB_MASK ? 1 : 0; + case LIBIPW_OFDM_RATE_36MB: + return priv-> + rates_mask & LIBIPW_OFDM_RATE_36MB_MASK ? 1 : 0; + case LIBIPW_OFDM_RATE_48MB: + return priv-> + rates_mask & LIBIPW_OFDM_RATE_48MB_MASK ? 1 : 0; + case LIBIPW_OFDM_RATE_54MB: + return priv-> + rates_mask & LIBIPW_OFDM_RATE_54MB_MASK ? 1 : 0; + default: + return 0; + } + } + + /* B and G mixed */ + switch (rate) { + case LIBIPW_CCK_RATE_1MB: + return priv->rates_mask & LIBIPW_CCK_RATE_1MB_MASK ? 1 : 0; + case LIBIPW_CCK_RATE_2MB: + return priv->rates_mask & LIBIPW_CCK_RATE_2MB_MASK ? 1 : 0; + case LIBIPW_CCK_RATE_5MB: + return priv->rates_mask & LIBIPW_CCK_RATE_5MB_MASK ? 1 : 0; + case LIBIPW_CCK_RATE_11MB: + return priv->rates_mask & LIBIPW_CCK_RATE_11MB_MASK ? 1 : 0; + } + + /* If we are limited to B modulations, bail at this point */ + if (ieee_mode == IEEE_B) + return 0; + + /* G */ + switch (rate) { + case LIBIPW_OFDM_RATE_6MB: + return priv->rates_mask & LIBIPW_OFDM_RATE_6MB_MASK ? 1 : 0; + case LIBIPW_OFDM_RATE_9MB: + return priv->rates_mask & LIBIPW_OFDM_RATE_9MB_MASK ? 1 : 0; + case LIBIPW_OFDM_RATE_12MB: + return priv->rates_mask & LIBIPW_OFDM_RATE_12MB_MASK ? 1 : 0; + case LIBIPW_OFDM_RATE_18MB: + return priv->rates_mask & LIBIPW_OFDM_RATE_18MB_MASK ? 1 : 0; + case LIBIPW_OFDM_RATE_24MB: + return priv->rates_mask & LIBIPW_OFDM_RATE_24MB_MASK ? 1 : 0; + case LIBIPW_OFDM_RATE_36MB: + return priv->rates_mask & LIBIPW_OFDM_RATE_36MB_MASK ? 1 : 0; + case LIBIPW_OFDM_RATE_48MB: + return priv->rates_mask & LIBIPW_OFDM_RATE_48MB_MASK ? 1 : 0; + case LIBIPW_OFDM_RATE_54MB: + return priv->rates_mask & LIBIPW_OFDM_RATE_54MB_MASK ? 1 : 0; + } + + return 0; +} + +static int ipw_compatible_rates(struct ipw_priv *priv, + const struct libipw_network *network, + struct ipw_supported_rates *rates) +{ + int num_rates, i; + + memset(rates, 0, sizeof(*rates)); + num_rates = min(network->rates_len, (u8) IPW_MAX_RATES); + rates->num_rates = 0; + for (i = 0; i < num_rates; i++) { + if (!ipw_is_rate_in_mask(priv, network->mode, + network->rates[i])) { + + if (network->rates[i] & LIBIPW_BASIC_RATE_MASK) { + IPW_DEBUG_SCAN("Adding masked mandatory " + "rate %02X\n", + network->rates[i]); + rates->supported_rates[rates->num_rates++] = + network->rates[i]; + continue; + } + + IPW_DEBUG_SCAN("Rate %02X masked : 0x%08X\n", + network->rates[i], priv->rates_mask); + continue; + } + + rates->supported_rates[rates->num_rates++] = network->rates[i]; + } + + num_rates = min(network->rates_ex_len, + (u8) (IPW_MAX_RATES - num_rates)); + for (i = 0; i < num_rates; i++) { + if (!ipw_is_rate_in_mask(priv, network->mode, + network->rates_ex[i])) { + if (network->rates_ex[i] & LIBIPW_BASIC_RATE_MASK) { + IPW_DEBUG_SCAN("Adding masked mandatory " + "rate %02X\n", + network->rates_ex[i]); + rates->supported_rates[rates->num_rates++] = + network->rates[i]; + continue; + } + + IPW_DEBUG_SCAN("Rate %02X masked : 0x%08X\n", + network->rates_ex[i], priv->rates_mask); + continue; + } + + rates->supported_rates[rates->num_rates++] = + network->rates_ex[i]; + } + + return 1; +} + +static void ipw_copy_rates(struct ipw_supported_rates *dest, + const struct ipw_supported_rates *src) +{ + u8 i; + for (i = 0; i < src->num_rates; i++) + dest->supported_rates[i] = src->supported_rates[i]; + dest->num_rates = src->num_rates; +} + +/* TODO: Look at sniffed packets in the air to determine if the basic rate + * mask should ever be used -- right now all callers to add the scan rates are + * set with the modulation = CCK, so BASIC_RATE_MASK is never set... */ +static void ipw_add_cck_scan_rates(struct ipw_supported_rates *rates, + u8 modulation, u32 rate_mask) +{ + u8 basic_mask = (LIBIPW_OFDM_MODULATION == modulation) ? + LIBIPW_BASIC_RATE_MASK : 0; + + if (rate_mask & LIBIPW_CCK_RATE_1MB_MASK) + rates->supported_rates[rates->num_rates++] = + LIBIPW_BASIC_RATE_MASK | LIBIPW_CCK_RATE_1MB; + + if (rate_mask & LIBIPW_CCK_RATE_2MB_MASK) + rates->supported_rates[rates->num_rates++] = + LIBIPW_BASIC_RATE_MASK | LIBIPW_CCK_RATE_2MB; + + if (rate_mask & LIBIPW_CCK_RATE_5MB_MASK) + rates->supported_rates[rates->num_rates++] = basic_mask | + LIBIPW_CCK_RATE_5MB; + + if (rate_mask & LIBIPW_CCK_RATE_11MB_MASK) + rates->supported_rates[rates->num_rates++] = basic_mask | + LIBIPW_CCK_RATE_11MB; +} + +static void ipw_add_ofdm_scan_rates(struct ipw_supported_rates *rates, + u8 modulation, u32 rate_mask) +{ + u8 basic_mask = (LIBIPW_OFDM_MODULATION == modulation) ? + LIBIPW_BASIC_RATE_MASK : 0; + + if (rate_mask & LIBIPW_OFDM_RATE_6MB_MASK) + rates->supported_rates[rates->num_rates++] = basic_mask | + LIBIPW_OFDM_RATE_6MB; + + if (rate_mask & LIBIPW_OFDM_RATE_9MB_MASK) + rates->supported_rates[rates->num_rates++] = + LIBIPW_OFDM_RATE_9MB; + + if (rate_mask & LIBIPW_OFDM_RATE_12MB_MASK) + rates->supported_rates[rates->num_rates++] = basic_mask | + LIBIPW_OFDM_RATE_12MB; + + if (rate_mask & LIBIPW_OFDM_RATE_18MB_MASK) + rates->supported_rates[rates->num_rates++] = + LIBIPW_OFDM_RATE_18MB; + + if (rate_mask & LIBIPW_OFDM_RATE_24MB_MASK) + rates->supported_rates[rates->num_rates++] = basic_mask | + LIBIPW_OFDM_RATE_24MB; + + if (rate_mask & LIBIPW_OFDM_RATE_36MB_MASK) + rates->supported_rates[rates->num_rates++] = + LIBIPW_OFDM_RATE_36MB; + + if (rate_mask & LIBIPW_OFDM_RATE_48MB_MASK) + rates->supported_rates[rates->num_rates++] = + LIBIPW_OFDM_RATE_48MB; + + if (rate_mask & LIBIPW_OFDM_RATE_54MB_MASK) + rates->supported_rates[rates->num_rates++] = + LIBIPW_OFDM_RATE_54MB; +} + +struct ipw_network_match { + struct libipw_network *network; + struct ipw_supported_rates rates; +}; + +static int ipw_find_adhoc_network(struct ipw_priv *priv, + struct ipw_network_match *match, + struct libipw_network *network, + int roaming) +{ + struct ipw_supported_rates rates; + DECLARE_SSID_BUF(ssid); + + /* Verify that this network's capability is compatible with the + * current mode (AdHoc or Infrastructure) */ + if ((priv->ieee->iw_mode == IW_MODE_ADHOC && + !(network->capability & WLAN_CAPABILITY_IBSS))) { + IPW_DEBUG_MERGE("Network '%s (%pM)' excluded due to " + "capability mismatch.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid); + return 0; + } + + if (unlikely(roaming)) { + /* If we are roaming, then ensure check if this is a valid + * network to try and roam to */ + if ((network->ssid_len != match->network->ssid_len) || + memcmp(network->ssid, match->network->ssid, + network->ssid_len)) { + IPW_DEBUG_MERGE("Network '%s (%pM)' excluded " + "because of non-network ESSID.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid); + return 0; + } + } else { + /* If an ESSID has been configured then compare the broadcast + * ESSID to ours */ + if ((priv->config & CFG_STATIC_ESSID) && + ((network->ssid_len != priv->essid_len) || + memcmp(network->ssid, priv->essid, + min(network->ssid_len, priv->essid_len)))) { + char escaped[IW_ESSID_MAX_SIZE * 2 + 1]; + + strncpy(escaped, + print_ssid(ssid, network->ssid, + network->ssid_len), + sizeof(escaped)); + IPW_DEBUG_MERGE("Network '%s (%pM)' excluded " + "because of ESSID mismatch: '%s'.\n", + escaped, network->bssid, + print_ssid(ssid, priv->essid, + priv->essid_len)); + return 0; + } + } + + /* If the old network rate is better than this one, don't bother + * testing everything else. */ + + if (network->time_stamp[0] < match->network->time_stamp[0]) { + IPW_DEBUG_MERGE("Network '%s excluded because newer than " + "current network.\n", + print_ssid(ssid, match->network->ssid, + match->network->ssid_len)); + return 0; + } else if (network->time_stamp[1] < match->network->time_stamp[1]) { + IPW_DEBUG_MERGE("Network '%s excluded because newer than " + "current network.\n", + print_ssid(ssid, match->network->ssid, + match->network->ssid_len)); + return 0; + } + + /* Now go through and see if the requested network is valid... */ + if (priv->ieee->scan_age != 0 && + time_after(jiffies, network->last_scanned + priv->ieee->scan_age)) { + IPW_DEBUG_MERGE("Network '%s (%pM)' excluded " + "because of age: %ums.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid, + jiffies_to_msecs(jiffies - + network->last_scanned)); + return 0; + } + + if ((priv->config & CFG_STATIC_CHANNEL) && + (network->channel != priv->channel)) { + IPW_DEBUG_MERGE("Network '%s (%pM)' excluded " + "because of channel mismatch: %d != %d.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid, + network->channel, priv->channel); + return 0; + } + + /* Verify privacy compatibility */ + if (((priv->capability & CAP_PRIVACY_ON) ? 1 : 0) != + ((network->capability & WLAN_CAPABILITY_PRIVACY) ? 1 : 0)) { + IPW_DEBUG_MERGE("Network '%s (%pM)' excluded " + "because of privacy mismatch: %s != %s.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid, + priv-> + capability & CAP_PRIVACY_ON ? "on" : "off", + network-> + capability & WLAN_CAPABILITY_PRIVACY ? "on" : + "off"); + return 0; + } + + if (!memcmp(network->bssid, priv->bssid, ETH_ALEN)) { + IPW_DEBUG_MERGE("Network '%s (%pM)' excluded " + "because of the same BSSID match: %pM" + ".\n", print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid, + priv->bssid); + return 0; + } + + /* Filter out any incompatible freq / mode combinations */ + if (!libipw_is_valid_mode(priv->ieee, network->mode)) { + IPW_DEBUG_MERGE("Network '%s (%pM)' excluded " + "because of invalid frequency/mode " + "combination.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid); + return 0; + } + + /* Ensure that the rates supported by the driver are compatible with + * this AP, including verification of basic rates (mandatory) */ + if (!ipw_compatible_rates(priv, network, &rates)) { + IPW_DEBUG_MERGE("Network '%s (%pM)' excluded " + "because configured rate mask excludes " + "AP mandatory rate.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid); + return 0; + } + + if (rates.num_rates == 0) { + IPW_DEBUG_MERGE("Network '%s (%pM)' excluded " + "because of no compatible rates.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid); + return 0; + } + + /* TODO: Perform any further minimal comparititive tests. We do not + * want to put too much policy logic here; intelligent scan selection + * should occur within a generic IEEE 802.11 user space tool. */ + + /* Set up 'new' AP to this network */ + ipw_copy_rates(&match->rates, &rates); + match->network = network; + IPW_DEBUG_MERGE("Network '%s (%pM)' is a viable match.\n", + print_ssid(ssid, network->ssid, network->ssid_len), + network->bssid); + + return 1; +} + +static void ipw_merge_adhoc_network(struct work_struct *work) +{ + DECLARE_SSID_BUF(ssid); + struct ipw_priv *priv = + container_of(work, struct ipw_priv, merge_networks); + struct libipw_network *network = NULL; + struct ipw_network_match match = { + .network = priv->assoc_network + }; + + if ((priv->status & STATUS_ASSOCIATED) && + (priv->ieee->iw_mode == IW_MODE_ADHOC)) { + /* First pass through ROAM process -- look for a better + * network */ + unsigned long flags; + + spin_lock_irqsave(&priv->ieee->lock, flags); + list_for_each_entry(network, &priv->ieee->network_list, list) { + if (network != priv->assoc_network) + ipw_find_adhoc_network(priv, &match, network, + 1); + } + spin_unlock_irqrestore(&priv->ieee->lock, flags); + + if (match.network == priv->assoc_network) { + IPW_DEBUG_MERGE("No better ADHOC in this network to " + "merge to.\n"); + return; + } + + mutex_lock(&priv->mutex); + if ((priv->ieee->iw_mode == IW_MODE_ADHOC)) { + IPW_DEBUG_MERGE("remove network %s\n", + print_ssid(ssid, priv->essid, + priv->essid_len)); + ipw_remove_current_network(priv); + } + + ipw_disassociate(priv); + priv->assoc_network = match.network; + mutex_unlock(&priv->mutex); + return; + } +} + +static int ipw_best_network(struct ipw_priv *priv, + struct ipw_network_match *match, + struct libipw_network *network, int roaming) +{ + struct ipw_supported_rates rates; + DECLARE_SSID_BUF(ssid); + + /* Verify that this network's capability is compatible with the + * current mode (AdHoc or Infrastructure) */ + if ((priv->ieee->iw_mode == IW_MODE_INFRA && + !(network->capability & WLAN_CAPABILITY_ESS)) || + (priv->ieee->iw_mode == IW_MODE_ADHOC && + !(network->capability & WLAN_CAPABILITY_IBSS))) { + IPW_DEBUG_ASSOC("Network '%s (%pM)' excluded due to " + "capability mismatch.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid); + return 0; + } + + if (unlikely(roaming)) { + /* If we are roaming, then ensure check if this is a valid + * network to try and roam to */ + if ((network->ssid_len != match->network->ssid_len) || + memcmp(network->ssid, match->network->ssid, + network->ssid_len)) { + IPW_DEBUG_ASSOC("Network '%s (%pM)' excluded " + "because of non-network ESSID.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid); + return 0; + } + } else { + /* If an ESSID has been configured then compare the broadcast + * ESSID to ours */ + if ((priv->config & CFG_STATIC_ESSID) && + ((network->ssid_len != priv->essid_len) || + memcmp(network->ssid, priv->essid, + min(network->ssid_len, priv->essid_len)))) { + char escaped[IW_ESSID_MAX_SIZE * 2 + 1]; + strncpy(escaped, + print_ssid(ssid, network->ssid, + network->ssid_len), + sizeof(escaped)); + IPW_DEBUG_ASSOC("Network '%s (%pM)' excluded " + "because of ESSID mismatch: '%s'.\n", + escaped, network->bssid, + print_ssid(ssid, priv->essid, + priv->essid_len)); + return 0; + } + } + + /* If the old network rate is better than this one, don't bother + * testing everything else. */ + if (match->network && match->network->stats.rssi > network->stats.rssi) { + char escaped[IW_ESSID_MAX_SIZE * 2 + 1]; + strncpy(escaped, + print_ssid(ssid, network->ssid, network->ssid_len), + sizeof(escaped)); + IPW_DEBUG_ASSOC("Network '%s (%pM)' excluded because " + "'%s (%pM)' has a stronger signal.\n", + escaped, network->bssid, + print_ssid(ssid, match->network->ssid, + match->network->ssid_len), + match->network->bssid); + return 0; + } + + /* If this network has already had an association attempt within the + * last 3 seconds, do not try and associate again... */ + if (network->last_associate && + time_after(network->last_associate + (HZ * 3UL), jiffies)) { + IPW_DEBUG_ASSOC("Network '%s (%pM)' excluded " + "because of storming (%ums since last " + "assoc attempt).\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid, + jiffies_to_msecs(jiffies - + network->last_associate)); + return 0; + } + + /* Now go through and see if the requested network is valid... */ + if (priv->ieee->scan_age != 0 && + time_after(jiffies, network->last_scanned + priv->ieee->scan_age)) { + IPW_DEBUG_ASSOC("Network '%s (%pM)' excluded " + "because of age: %ums.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid, + jiffies_to_msecs(jiffies - + network->last_scanned)); + return 0; + } + + if ((priv->config & CFG_STATIC_CHANNEL) && + (network->channel != priv->channel)) { + IPW_DEBUG_ASSOC("Network '%s (%pM)' excluded " + "because of channel mismatch: %d != %d.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid, + network->channel, priv->channel); + return 0; + } + + /* Verify privacy compatibility */ + if (((priv->capability & CAP_PRIVACY_ON) ? 1 : 0) != + ((network->capability & WLAN_CAPABILITY_PRIVACY) ? 1 : 0)) { + IPW_DEBUG_ASSOC("Network '%s (%pM)' excluded " + "because of privacy mismatch: %s != %s.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid, + priv->capability & CAP_PRIVACY_ON ? "on" : + "off", + network->capability & + WLAN_CAPABILITY_PRIVACY ? "on" : "off"); + return 0; + } + + if ((priv->config & CFG_STATIC_BSSID) && + memcmp(network->bssid, priv->bssid, ETH_ALEN)) { + IPW_DEBUG_ASSOC("Network '%s (%pM)' excluded " + "because of BSSID mismatch: %pM.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid, priv->bssid); + return 0; + } + + /* Filter out any incompatible freq / mode combinations */ + if (!libipw_is_valid_mode(priv->ieee, network->mode)) { + IPW_DEBUG_ASSOC("Network '%s (%pM)' excluded " + "because of invalid frequency/mode " + "combination.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid); + return 0; + } + + /* Filter out invalid channel in current GEO */ + if (!libipw_is_valid_channel(priv->ieee, network->channel)) { + IPW_DEBUG_ASSOC("Network '%s (%pM)' excluded " + "because of invalid channel in current GEO\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid); + return 0; + } + + /* Ensure that the rates supported by the driver are compatible with + * this AP, including verification of basic rates (mandatory) */ + if (!ipw_compatible_rates(priv, network, &rates)) { + IPW_DEBUG_ASSOC("Network '%s (%pM)' excluded " + "because configured rate mask excludes " + "AP mandatory rate.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid); + return 0; + } + + if (rates.num_rates == 0) { + IPW_DEBUG_ASSOC("Network '%s (%pM)' excluded " + "because of no compatible rates.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid); + return 0; + } + + /* TODO: Perform any further minimal comparititive tests. We do not + * want to put too much policy logic here; intelligent scan selection + * should occur within a generic IEEE 802.11 user space tool. */ + + /* Set up 'new' AP to this network */ + ipw_copy_rates(&match->rates, &rates); + match->network = network; + + IPW_DEBUG_ASSOC("Network '%s (%pM)' is a viable match.\n", + print_ssid(ssid, network->ssid, network->ssid_len), + network->bssid); + + return 1; +} + +static void ipw_adhoc_create(struct ipw_priv *priv, + struct libipw_network *network) +{ + const struct libipw_geo *geo = libipw_get_geo(priv->ieee); + int i; + + /* + * For the purposes of scanning, we can set our wireless mode + * to trigger scans across combinations of bands, but when it + * comes to creating a new ad-hoc network, we have tell the FW + * exactly which band to use. + * + * We also have the possibility of an invalid channel for the + * chossen band. Attempting to create a new ad-hoc network + * with an invalid channel for wireless mode will trigger a + * FW fatal error. + * + */ + switch (libipw_is_valid_channel(priv->ieee, priv->channel)) { + case LIBIPW_52GHZ_BAND: + network->mode = IEEE_A; + i = libipw_channel_to_index(priv->ieee, priv->channel); + BUG_ON(i == -1); + if (geo->a[i].flags & LIBIPW_CH_PASSIVE_ONLY) { + IPW_WARNING("Overriding invalid channel\n"); + priv->channel = geo->a[0].channel; + } + break; + + case LIBIPW_24GHZ_BAND: + if (priv->ieee->mode & IEEE_G) + network->mode = IEEE_G; + else + network->mode = IEEE_B; + i = libipw_channel_to_index(priv->ieee, priv->channel); + BUG_ON(i == -1); + if (geo->bg[i].flags & LIBIPW_CH_PASSIVE_ONLY) { + IPW_WARNING("Overriding invalid channel\n"); + priv->channel = geo->bg[0].channel; + } + break; + + default: + IPW_WARNING("Overriding invalid channel\n"); + if (priv->ieee->mode & IEEE_A) { + network->mode = IEEE_A; + priv->channel = geo->a[0].channel; + } else if (priv->ieee->mode & IEEE_G) { + network->mode = IEEE_G; + priv->channel = geo->bg[0].channel; + } else { + network->mode = IEEE_B; + priv->channel = geo->bg[0].channel; + } + break; + } + + network->channel = priv->channel; + priv->config |= CFG_ADHOC_PERSIST; + ipw_create_bssid(priv, network->bssid); + network->ssid_len = priv->essid_len; + memcpy(network->ssid, priv->essid, priv->essid_len); + memset(&network->stats, 0, sizeof(network->stats)); + network->capability = WLAN_CAPABILITY_IBSS; + if (!(priv->config & CFG_PREAMBLE_LONG)) + network->capability |= WLAN_CAPABILITY_SHORT_PREAMBLE; + if (priv->capability & CAP_PRIVACY_ON) + network->capability |= WLAN_CAPABILITY_PRIVACY; + network->rates_len = min(priv->rates.num_rates, MAX_RATES_LENGTH); + memcpy(network->rates, priv->rates.supported_rates, network->rates_len); + network->rates_ex_len = priv->rates.num_rates - network->rates_len; + memcpy(network->rates_ex, + &priv->rates.supported_rates[network->rates_len], + network->rates_ex_len); + network->last_scanned = 0; + network->flags = 0; + network->last_associate = 0; + network->time_stamp[0] = 0; + network->time_stamp[1] = 0; + network->beacon_interval = 100; /* Default */ + network->listen_interval = 10; /* Default */ + network->atim_window = 0; /* Default */ + network->wpa_ie_len = 0; + network->rsn_ie_len = 0; +} + +static void ipw_send_tgi_tx_key(struct ipw_priv *priv, int type, int index) +{ + struct ipw_tgi_tx_key key; + + if (!(priv->ieee->sec.flags & (1 << index))) + return; + + key.key_id = index; + memcpy(key.key, priv->ieee->sec.keys[index], SCM_TEMPORAL_KEY_LENGTH); + key.security_type = type; + key.station_index = 0; /* always 0 for BSS */ + key.flags = 0; + /* 0 for new key; previous value of counter (after fatal error) */ + key.tx_counter[0] = cpu_to_le32(0); + key.tx_counter[1] = cpu_to_le32(0); + + ipw_send_cmd_pdu(priv, IPW_CMD_TGI_TX_KEY, sizeof(key), &key); +} + +static void ipw_send_wep_keys(struct ipw_priv *priv, int type) +{ + struct ipw_wep_key key; + int i; + + key.cmd_id = DINO_CMD_WEP_KEY; + key.seq_num = 0; + + /* Note: AES keys cannot be set for multiple times. + * Only set it at the first time. */ + for (i = 0; i < 4; i++) { + key.key_index = i | type; + if (!(priv->ieee->sec.flags & (1 << i))) { + key.key_size = 0; + continue; + } + + key.key_size = priv->ieee->sec.key_sizes[i]; + memcpy(key.key, priv->ieee->sec.keys[i], key.key_size); + + ipw_send_cmd_pdu(priv, IPW_CMD_WEP_KEY, sizeof(key), &key); + } +} + +static void ipw_set_hw_decrypt_unicast(struct ipw_priv *priv, int level) +{ + if (priv->ieee->host_encrypt) + return; + + switch (level) { + case SEC_LEVEL_3: + priv->sys_config.disable_unicast_decryption = 0; + priv->ieee->host_decrypt = 0; + break; + case SEC_LEVEL_2: + priv->sys_config.disable_unicast_decryption = 1; + priv->ieee->host_decrypt = 1; + break; + case SEC_LEVEL_1: + priv->sys_config.disable_unicast_decryption = 0; + priv->ieee->host_decrypt = 0; + break; + case SEC_LEVEL_0: + priv->sys_config.disable_unicast_decryption = 1; + break; + default: + break; + } +} + +static void ipw_set_hw_decrypt_multicast(struct ipw_priv *priv, int level) +{ + if (priv->ieee->host_encrypt) + return; + + switch (level) { + case SEC_LEVEL_3: + priv->sys_config.disable_multicast_decryption = 0; + break; + case SEC_LEVEL_2: + priv->sys_config.disable_multicast_decryption = 1; + break; + case SEC_LEVEL_1: + priv->sys_config.disable_multicast_decryption = 0; + break; + case SEC_LEVEL_0: + priv->sys_config.disable_multicast_decryption = 1; + break; + default: + break; + } +} + +static void ipw_set_hwcrypto_keys(struct ipw_priv *priv) +{ + switch (priv->ieee->sec.level) { + case SEC_LEVEL_3: + if (priv->ieee->sec.flags & SEC_ACTIVE_KEY) + ipw_send_tgi_tx_key(priv, + DCT_FLAG_EXT_SECURITY_CCM, + priv->ieee->sec.active_key); + + if (!priv->ieee->host_mc_decrypt) + ipw_send_wep_keys(priv, DCW_WEP_KEY_SEC_TYPE_CCM); + break; + case SEC_LEVEL_2: + if (priv->ieee->sec.flags & SEC_ACTIVE_KEY) + ipw_send_tgi_tx_key(priv, + DCT_FLAG_EXT_SECURITY_TKIP, + priv->ieee->sec.active_key); + break; + case SEC_LEVEL_1: + ipw_send_wep_keys(priv, DCW_WEP_KEY_SEC_TYPE_WEP); + ipw_set_hw_decrypt_unicast(priv, priv->ieee->sec.level); + ipw_set_hw_decrypt_multicast(priv, priv->ieee->sec.level); + break; + case SEC_LEVEL_0: + default: + break; + } +} + +static void ipw_adhoc_check(void *data) +{ + struct ipw_priv *priv = data; + + if (priv->missed_adhoc_beacons++ > priv->disassociate_threshold && + !(priv->config & CFG_ADHOC_PERSIST)) { + IPW_DEBUG(IPW_DL_INFO | IPW_DL_NOTIF | + IPW_DL_STATE | IPW_DL_ASSOC, + "Missed beacon: %d - disassociate\n", + priv->missed_adhoc_beacons); + ipw_remove_current_network(priv); + ipw_disassociate(priv); + return; + } + + schedule_delayed_work(&priv->adhoc_check, + le16_to_cpu(priv->assoc_request.beacon_interval)); +} + +static void ipw_bg_adhoc_check(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, adhoc_check.work); + mutex_lock(&priv->mutex); + ipw_adhoc_check(priv); + mutex_unlock(&priv->mutex); +} + +static void ipw_debug_config(struct ipw_priv *priv) +{ + DECLARE_SSID_BUF(ssid); + IPW_DEBUG_INFO("Scan completed, no valid APs matched " + "[CFG 0x%08X]\n", priv->config); + if (priv->config & CFG_STATIC_CHANNEL) + IPW_DEBUG_INFO("Channel locked to %d\n", priv->channel); + else + IPW_DEBUG_INFO("Channel unlocked.\n"); + if (priv->config & CFG_STATIC_ESSID) + IPW_DEBUG_INFO("ESSID locked to '%s'\n", + print_ssid(ssid, priv->essid, priv->essid_len)); + else + IPW_DEBUG_INFO("ESSID unlocked.\n"); + if (priv->config & CFG_STATIC_BSSID) + IPW_DEBUG_INFO("BSSID locked to %pM\n", priv->bssid); + else + IPW_DEBUG_INFO("BSSID unlocked.\n"); + if (priv->capability & CAP_PRIVACY_ON) + IPW_DEBUG_INFO("PRIVACY on\n"); + else + IPW_DEBUG_INFO("PRIVACY off\n"); + IPW_DEBUG_INFO("RATE MASK: 0x%08X\n", priv->rates_mask); +} + +static void ipw_set_fixed_rate(struct ipw_priv *priv, int mode) +{ + /* TODO: Verify that this works... */ + struct ipw_fixed_rate fr; + u32 reg; + u16 mask = 0; + u16 new_tx_rates = priv->rates_mask; + + /* Identify 'current FW band' and match it with the fixed + * Tx rates */ + + switch (priv->ieee->freq_band) { + case LIBIPW_52GHZ_BAND: /* A only */ + /* IEEE_A */ + if (priv->rates_mask & ~LIBIPW_OFDM_RATES_MASK) { + /* Invalid fixed rate mask */ + IPW_DEBUG_WX + ("invalid fixed rate mask in ipw_set_fixed_rate\n"); + new_tx_rates = 0; + break; + } + + new_tx_rates >>= LIBIPW_OFDM_SHIFT_MASK_A; + break; + + default: /* 2.4Ghz or Mixed */ + /* IEEE_B */ + if (mode == IEEE_B) { + if (new_tx_rates & ~LIBIPW_CCK_RATES_MASK) { + /* Invalid fixed rate mask */ + IPW_DEBUG_WX + ("invalid fixed rate mask in ipw_set_fixed_rate\n"); + new_tx_rates = 0; + } + break; + } + + /* IEEE_G */ + if (new_tx_rates & ~(LIBIPW_CCK_RATES_MASK | + LIBIPW_OFDM_RATES_MASK)) { + /* Invalid fixed rate mask */ + IPW_DEBUG_WX + ("invalid fixed rate mask in ipw_set_fixed_rate\n"); + new_tx_rates = 0; + break; + } + + if (LIBIPW_OFDM_RATE_6MB_MASK & new_tx_rates) { + mask |= (LIBIPW_OFDM_RATE_6MB_MASK >> 1); + new_tx_rates &= ~LIBIPW_OFDM_RATE_6MB_MASK; + } + + if (LIBIPW_OFDM_RATE_9MB_MASK & new_tx_rates) { + mask |= (LIBIPW_OFDM_RATE_9MB_MASK >> 1); + new_tx_rates &= ~LIBIPW_OFDM_RATE_9MB_MASK; + } + + if (LIBIPW_OFDM_RATE_12MB_MASK & new_tx_rates) { + mask |= (LIBIPW_OFDM_RATE_12MB_MASK >> 1); + new_tx_rates &= ~LIBIPW_OFDM_RATE_12MB_MASK; + } + + new_tx_rates |= mask; + break; + } + + fr.tx_rates = cpu_to_le16(new_tx_rates); + + reg = ipw_read32(priv, IPW_MEM_FIXED_OVERRIDE); + ipw_write_reg32(priv, reg, *(u32 *) & fr); +} + +static void ipw_abort_scan(struct ipw_priv *priv) +{ + int err; + + if (priv->status & STATUS_SCAN_ABORTING) { + IPW_DEBUG_HC("Ignoring concurrent scan abort request.\n"); + return; + } + priv->status |= STATUS_SCAN_ABORTING; + + err = ipw_send_scan_abort(priv); + if (err) + IPW_DEBUG_HC("Request to abort scan failed.\n"); +} + +static void ipw_add_scan_channels(struct ipw_priv *priv, + struct ipw_scan_request_ext *scan, + int scan_type) +{ + int channel_index = 0; + const struct libipw_geo *geo; + int i; + + geo = libipw_get_geo(priv->ieee); + + if (priv->ieee->freq_band & LIBIPW_52GHZ_BAND) { + int start = channel_index; + for (i = 0; i < geo->a_channels; i++) { + if ((priv->status & STATUS_ASSOCIATED) && + geo->a[i].channel == priv->channel) + continue; + channel_index++; + scan->channels_list[channel_index] = geo->a[i].channel; + ipw_set_scan_type(scan, channel_index, + geo->a[i]. + flags & LIBIPW_CH_PASSIVE_ONLY ? + IPW_SCAN_PASSIVE_FULL_DWELL_SCAN : + scan_type); + } + + if (start != channel_index) { + scan->channels_list[start] = (u8) (IPW_A_MODE << 6) | + (channel_index - start); + channel_index++; + } + } + + if (priv->ieee->freq_band & LIBIPW_24GHZ_BAND) { + int start = channel_index; + if (priv->config & CFG_SPEED_SCAN) { + int index; + u8 channels[LIBIPW_24GHZ_CHANNELS] = { + /* nop out the list */ + [0] = 0 + }; + + u8 channel; + while (channel_index < IPW_SCAN_CHANNELS - 1) { + channel = + priv->speed_scan[priv->speed_scan_pos]; + if (channel == 0) { + priv->speed_scan_pos = 0; + channel = priv->speed_scan[0]; + } + if ((priv->status & STATUS_ASSOCIATED) && + channel == priv->channel) { + priv->speed_scan_pos++; + continue; + } + + /* If this channel has already been + * added in scan, break from loop + * and this will be the first channel + * in the next scan. + */ + if (channels[channel - 1] != 0) + break; + + channels[channel - 1] = 1; + priv->speed_scan_pos++; + channel_index++; + scan->channels_list[channel_index] = channel; + index = + libipw_channel_to_index(priv->ieee, channel); + ipw_set_scan_type(scan, channel_index, + geo->bg[index]. + flags & + LIBIPW_CH_PASSIVE_ONLY ? + IPW_SCAN_PASSIVE_FULL_DWELL_SCAN + : scan_type); + } + } else { + for (i = 0; i < geo->bg_channels; i++) { + if ((priv->status & STATUS_ASSOCIATED) && + geo->bg[i].channel == priv->channel) + continue; + channel_index++; + scan->channels_list[channel_index] = + geo->bg[i].channel; + ipw_set_scan_type(scan, channel_index, + geo->bg[i]. + flags & + LIBIPW_CH_PASSIVE_ONLY ? + IPW_SCAN_PASSIVE_FULL_DWELL_SCAN + : scan_type); + } + } + + if (start != channel_index) { + scan->channels_list[start] = (u8) (IPW_B_MODE << 6) | + (channel_index - start); + } + } +} + +static int ipw_passive_dwell_time(struct ipw_priv *priv) +{ + /* staying on passive channels longer than the DTIM interval during a + * scan, while associated, causes the firmware to cancel the scan + * without notification. Hence, don't stay on passive channels longer + * than the beacon interval. + */ + if (priv->status & STATUS_ASSOCIATED + && priv->assoc_network->beacon_interval > 10) + return priv->assoc_network->beacon_interval - 10; + else + return 120; +} + +static int ipw_request_scan_helper(struct ipw_priv *priv, int type, int direct) +{ + struct ipw_scan_request_ext scan; + int err = 0, scan_type; + + if (!(priv->status & STATUS_INIT) || + (priv->status & STATUS_EXIT_PENDING)) + return 0; + + mutex_lock(&priv->mutex); + + if (direct && (priv->direct_scan_ssid_len == 0)) { + IPW_DEBUG_HC("Direct scan requested but no SSID to scan for\n"); + priv->status &= ~STATUS_DIRECT_SCAN_PENDING; + goto done; + } + + if (priv->status & STATUS_SCANNING) { + IPW_DEBUG_HC("Concurrent scan requested. Queuing.\n"); + priv->status |= direct ? STATUS_DIRECT_SCAN_PENDING : + STATUS_SCAN_PENDING; + goto done; + } + + if (!(priv->status & STATUS_SCAN_FORCED) && + priv->status & STATUS_SCAN_ABORTING) { + IPW_DEBUG_HC("Scan request while abort pending. Queuing.\n"); + priv->status |= direct ? STATUS_DIRECT_SCAN_PENDING : + STATUS_SCAN_PENDING; + goto done; + } + + if (priv->status & STATUS_RF_KILL_MASK) { + IPW_DEBUG_HC("Queuing scan due to RF Kill activation\n"); + priv->status |= direct ? STATUS_DIRECT_SCAN_PENDING : + STATUS_SCAN_PENDING; + goto done; + } + + memset(&scan, 0, sizeof(scan)); + scan.full_scan_index = cpu_to_le32(libipw_get_scans(priv->ieee)); + + if (type == IW_SCAN_TYPE_PASSIVE) { + IPW_DEBUG_WX("use passive scanning\n"); + scan_type = IPW_SCAN_PASSIVE_FULL_DWELL_SCAN; + scan.dwell_time[IPW_SCAN_PASSIVE_FULL_DWELL_SCAN] = + cpu_to_le16(ipw_passive_dwell_time(priv)); + ipw_add_scan_channels(priv, &scan, scan_type); + goto send_request; + } + + /* Use active scan by default. */ + if (priv->config & CFG_SPEED_SCAN) + scan.dwell_time[IPW_SCAN_ACTIVE_BROADCAST_SCAN] = + cpu_to_le16(30); + else + scan.dwell_time[IPW_SCAN_ACTIVE_BROADCAST_SCAN] = + cpu_to_le16(20); + + scan.dwell_time[IPW_SCAN_ACTIVE_BROADCAST_AND_DIRECT_SCAN] = + cpu_to_le16(20); + + scan.dwell_time[IPW_SCAN_PASSIVE_FULL_DWELL_SCAN] = + cpu_to_le16(ipw_passive_dwell_time(priv)); + scan.dwell_time[IPW_SCAN_ACTIVE_DIRECT_SCAN] = cpu_to_le16(20); + +#ifdef CPTCFG_IPW2200_MONITOR + if (priv->ieee->iw_mode == IW_MODE_MONITOR) { + u8 channel; + u8 band = 0; + + switch (libipw_is_valid_channel(priv->ieee, priv->channel)) { + case LIBIPW_52GHZ_BAND: + band = (u8) (IPW_A_MODE << 6) | 1; + channel = priv->channel; + break; + + case LIBIPW_24GHZ_BAND: + band = (u8) (IPW_B_MODE << 6) | 1; + channel = priv->channel; + break; + + default: + band = (u8) (IPW_B_MODE << 6) | 1; + channel = 9; + break; + } + + scan.channels_list[0] = band; + scan.channels_list[1] = channel; + ipw_set_scan_type(&scan, 1, IPW_SCAN_PASSIVE_FULL_DWELL_SCAN); + + /* NOTE: The card will sit on this channel for this time + * period. Scan aborts are timing sensitive and frequently + * result in firmware restarts. As such, it is best to + * set a small dwell_time here and just keep re-issuing + * scans. Otherwise fast channel hopping will not actually + * hop channels. + * + * TODO: Move SPEED SCAN support to all modes and bands */ + scan.dwell_time[IPW_SCAN_PASSIVE_FULL_DWELL_SCAN] = + cpu_to_le16(2000); + } else { +#endif /* CPTCFG_IPW2200_MONITOR */ + /* Honor direct scans first, otherwise if we are roaming make + * this a direct scan for the current network. Finally, + * ensure that every other scan is a fast channel hop scan */ + if (direct) { + err = ipw_send_ssid(priv, priv->direct_scan_ssid, + priv->direct_scan_ssid_len); + if (err) { + IPW_DEBUG_HC("Attempt to send SSID command " + "failed\n"); + goto done; + } + + scan_type = IPW_SCAN_ACTIVE_BROADCAST_AND_DIRECT_SCAN; + } else if ((priv->status & STATUS_ROAMING) + || (!(priv->status & STATUS_ASSOCIATED) + && (priv->config & CFG_STATIC_ESSID) + && (le32_to_cpu(scan.full_scan_index) % 2))) { + err = ipw_send_ssid(priv, priv->essid, priv->essid_len); + if (err) { + IPW_DEBUG_HC("Attempt to send SSID command " + "failed.\n"); + goto done; + } + + scan_type = IPW_SCAN_ACTIVE_BROADCAST_AND_DIRECT_SCAN; + } else + scan_type = IPW_SCAN_ACTIVE_BROADCAST_SCAN; + + ipw_add_scan_channels(priv, &scan, scan_type); +#ifdef CPTCFG_IPW2200_MONITOR + } +#endif + +send_request: + err = ipw_send_scan_request_ext(priv, &scan); + if (err) { + IPW_DEBUG_HC("Sending scan command failed: %08X\n", err); + goto done; + } + + priv->status |= STATUS_SCANNING; + if (direct) { + priv->status &= ~STATUS_DIRECT_SCAN_PENDING; + priv->direct_scan_ssid_len = 0; + } else + priv->status &= ~STATUS_SCAN_PENDING; + + schedule_delayed_work(&priv->scan_check, IPW_SCAN_CHECK_WATCHDOG); +done: + mutex_unlock(&priv->mutex); + return err; +} + +static void ipw_request_passive_scan(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, request_passive_scan.work); + ipw_request_scan_helper(priv, IW_SCAN_TYPE_PASSIVE, 0); +} + +static void ipw_request_scan(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, request_scan.work); + ipw_request_scan_helper(priv, IW_SCAN_TYPE_ACTIVE, 0); +} + +static void ipw_request_direct_scan(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, request_direct_scan.work); + ipw_request_scan_helper(priv, IW_SCAN_TYPE_ACTIVE, 1); +} + +static void ipw_bg_abort_scan(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, abort_scan); + mutex_lock(&priv->mutex); + ipw_abort_scan(priv); + mutex_unlock(&priv->mutex); +} + +static int ipw_wpa_enable(struct ipw_priv *priv, int value) +{ + /* This is called when wpa_supplicant loads and closes the driver + * interface. */ + priv->ieee->wpa_enabled = value; + return 0; +} + +static int ipw_wpa_set_auth_algs(struct ipw_priv *priv, int value) +{ + struct libipw_device *ieee = priv->ieee; + struct libipw_security sec = { + .flags = SEC_AUTH_MODE, + }; + int ret = 0; + + if (value & IW_AUTH_ALG_SHARED_KEY) { + sec.auth_mode = WLAN_AUTH_SHARED_KEY; + ieee->open_wep = 0; + } else if (value & IW_AUTH_ALG_OPEN_SYSTEM) { + sec.auth_mode = WLAN_AUTH_OPEN; + ieee->open_wep = 1; + } else if (value & IW_AUTH_ALG_LEAP) { + sec.auth_mode = WLAN_AUTH_LEAP; + ieee->open_wep = 1; + } else + return -EINVAL; + + if (ieee->set_security) + ieee->set_security(ieee->dev, &sec); + else + ret = -EOPNOTSUPP; + + return ret; +} + +static void ipw_wpa_assoc_frame(struct ipw_priv *priv, char *wpa_ie, + int wpa_ie_len) +{ + /* make sure WPA is enabled */ + ipw_wpa_enable(priv, 1); +} + +static int ipw_set_rsn_capa(struct ipw_priv *priv, + char *capabilities, int length) +{ + IPW_DEBUG_HC("HOST_CMD_RSN_CAPABILITIES\n"); + + return ipw_send_cmd_pdu(priv, IPW_CMD_RSN_CAPABILITIES, length, + capabilities); +} + +/* + * WE-18 support + */ + +/* SIOCSIWGENIE */ +static int ipw_wx_set_genie(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + struct libipw_device *ieee = priv->ieee; + u8 *buf; + int err = 0; + + if (wrqu->data.length > MAX_WPA_IE_LEN || + (wrqu->data.length && extra == NULL)) + return -EINVAL; + + if (wrqu->data.length) { + buf = kmemdup(extra, wrqu->data.length, GFP_KERNEL); + if (buf == NULL) { + err = -ENOMEM; + goto out; + } + + kfree(ieee->wpa_ie); + ieee->wpa_ie = buf; + ieee->wpa_ie_len = wrqu->data.length; + } else { + kfree(ieee->wpa_ie); + ieee->wpa_ie = NULL; + ieee->wpa_ie_len = 0; + } + + ipw_wpa_assoc_frame(priv, ieee->wpa_ie, ieee->wpa_ie_len); + out: + return err; +} + +/* SIOCGIWGENIE */ +static int ipw_wx_get_genie(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + struct libipw_device *ieee = priv->ieee; + int err = 0; + + if (ieee->wpa_ie_len == 0 || ieee->wpa_ie == NULL) { + wrqu->data.length = 0; + goto out; + } + + if (wrqu->data.length < ieee->wpa_ie_len) { + err = -E2BIG; + goto out; + } + + wrqu->data.length = ieee->wpa_ie_len; + memcpy(extra, ieee->wpa_ie, ieee->wpa_ie_len); + + out: + return err; +} + +static int wext_cipher2level(int cipher) +{ + switch (cipher) { + case IW_AUTH_CIPHER_NONE: + return SEC_LEVEL_0; + case IW_AUTH_CIPHER_WEP40: + case IW_AUTH_CIPHER_WEP104: + return SEC_LEVEL_1; + case IW_AUTH_CIPHER_TKIP: + return SEC_LEVEL_2; + case IW_AUTH_CIPHER_CCMP: + return SEC_LEVEL_3; + default: + return -1; + } +} + +/* SIOCSIWAUTH */ +static int ipw_wx_set_auth(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + struct libipw_device *ieee = priv->ieee; + struct iw_param *param = &wrqu->param; + struct lib80211_crypt_data *crypt; + unsigned long flags; + int ret = 0; + + switch (param->flags & IW_AUTH_INDEX) { + case IW_AUTH_WPA_VERSION: + break; + case IW_AUTH_CIPHER_PAIRWISE: + ipw_set_hw_decrypt_unicast(priv, + wext_cipher2level(param->value)); + break; + case IW_AUTH_CIPHER_GROUP: + ipw_set_hw_decrypt_multicast(priv, + wext_cipher2level(param->value)); + break; + case IW_AUTH_KEY_MGMT: + /* + * ipw2200 does not use these parameters + */ + break; + + case IW_AUTH_TKIP_COUNTERMEASURES: + crypt = priv->ieee->crypt_info.crypt[priv->ieee->crypt_info.tx_keyidx]; + if (!crypt || !crypt->ops->set_flags || !crypt->ops->get_flags) + break; + + flags = crypt->ops->get_flags(crypt->priv); + + if (param->value) + flags |= IEEE80211_CRYPTO_TKIP_COUNTERMEASURES; + else + flags &= ~IEEE80211_CRYPTO_TKIP_COUNTERMEASURES; + + crypt->ops->set_flags(flags, crypt->priv); + + break; + + case IW_AUTH_DROP_UNENCRYPTED:{ + /* HACK: + * + * wpa_supplicant calls set_wpa_enabled when the driver + * is loaded and unloaded, regardless of if WPA is being + * used. No other calls are made which can be used to + * determine if encryption will be used or not prior to + * association being expected. If encryption is not being + * used, drop_unencrypted is set to false, else true -- we + * can use this to determine if the CAP_PRIVACY_ON bit should + * be set. + */ + struct libipw_security sec = { + .flags = SEC_ENABLED, + .enabled = param->value, + }; + priv->ieee->drop_unencrypted = param->value; + /* We only change SEC_LEVEL for open mode. Others + * are set by ipw_wpa_set_encryption. + */ + if (!param->value) { + sec.flags |= SEC_LEVEL; + sec.level = SEC_LEVEL_0; + } else { + sec.flags |= SEC_LEVEL; + sec.level = SEC_LEVEL_1; + } + if (priv->ieee->set_security) + priv->ieee->set_security(priv->ieee->dev, &sec); + break; + } + + case IW_AUTH_80211_AUTH_ALG: + ret = ipw_wpa_set_auth_algs(priv, param->value); + break; + + case IW_AUTH_WPA_ENABLED: + ret = ipw_wpa_enable(priv, param->value); + ipw_disassociate(priv); + break; + + case IW_AUTH_RX_UNENCRYPTED_EAPOL: + ieee->ieee802_1x = param->value; + break; + + case IW_AUTH_PRIVACY_INVOKED: + ieee->privacy_invoked = param->value; + break; + + default: + return -EOPNOTSUPP; + } + return ret; +} + +/* SIOCGIWAUTH */ +static int ipw_wx_get_auth(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + struct libipw_device *ieee = priv->ieee; + struct lib80211_crypt_data *crypt; + struct iw_param *param = &wrqu->param; + + switch (param->flags & IW_AUTH_INDEX) { + case IW_AUTH_WPA_VERSION: + case IW_AUTH_CIPHER_PAIRWISE: + case IW_AUTH_CIPHER_GROUP: + case IW_AUTH_KEY_MGMT: + /* + * wpa_supplicant will control these internally + */ + return -EOPNOTSUPP; + + case IW_AUTH_TKIP_COUNTERMEASURES: + crypt = priv->ieee->crypt_info.crypt[priv->ieee->crypt_info.tx_keyidx]; + if (!crypt || !crypt->ops->get_flags) + break; + + param->value = (crypt->ops->get_flags(crypt->priv) & + IEEE80211_CRYPTO_TKIP_COUNTERMEASURES) ? 1 : 0; + + break; + + case IW_AUTH_DROP_UNENCRYPTED: + param->value = ieee->drop_unencrypted; + break; + + case IW_AUTH_80211_AUTH_ALG: + param->value = ieee->sec.auth_mode; + break; + + case IW_AUTH_WPA_ENABLED: + param->value = ieee->wpa_enabled; + break; + + case IW_AUTH_RX_UNENCRYPTED_EAPOL: + param->value = ieee->ieee802_1x; + break; + + case IW_AUTH_ROAMING_CONTROL: + case IW_AUTH_PRIVACY_INVOKED: + param->value = ieee->privacy_invoked; + break; + + default: + return -EOPNOTSUPP; + } + return 0; +} + +/* SIOCSIWENCODEEXT */ +static int ipw_wx_set_encodeext(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + struct iw_encode_ext *ext = (struct iw_encode_ext *)extra; + + if (hwcrypto) { + if (ext->alg == IW_ENCODE_ALG_TKIP) { + /* IPW HW can't build TKIP MIC, + host decryption still needed */ + if (ext->ext_flags & IW_ENCODE_EXT_GROUP_KEY) + priv->ieee->host_mc_decrypt = 1; + else { + priv->ieee->host_encrypt = 0; + priv->ieee->host_encrypt_msdu = 1; + priv->ieee->host_decrypt = 1; + } + } else { + priv->ieee->host_encrypt = 0; + priv->ieee->host_encrypt_msdu = 0; + priv->ieee->host_decrypt = 0; + priv->ieee->host_mc_decrypt = 0; + } + } + + return libipw_wx_set_encodeext(priv->ieee, info, wrqu, extra); +} + +/* SIOCGIWENCODEEXT */ +static int ipw_wx_get_encodeext(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + return libipw_wx_get_encodeext(priv->ieee, info, wrqu, extra); +} + +/* SIOCSIWMLME */ +static int ipw_wx_set_mlme(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + struct iw_mlme *mlme = (struct iw_mlme *)extra; + __le16 reason; + + reason = cpu_to_le16(mlme->reason_code); + + switch (mlme->cmd) { + case IW_MLME_DEAUTH: + /* silently ignore */ + break; + + case IW_MLME_DISASSOC: + ipw_disassociate(priv); + break; + + default: + return -EOPNOTSUPP; + } + return 0; +} + +#ifdef CPTCFG_IPW2200_QOS + +/* QoS */ +/* +* get the modulation type of the current network or +* the card current mode +*/ +static u8 ipw_qos_current_mode(struct ipw_priv * priv) +{ + u8 mode = 0; + + if (priv->status & STATUS_ASSOCIATED) { + unsigned long flags; + + spin_lock_irqsave(&priv->ieee->lock, flags); + mode = priv->assoc_network->mode; + spin_unlock_irqrestore(&priv->ieee->lock, flags); + } else { + mode = priv->ieee->mode; + } + IPW_DEBUG_QOS("QoS network/card mode %d\n", mode); + return mode; +} + +/* +* Handle management frame beacon and probe response +*/ +static int ipw_qos_handle_probe_response(struct ipw_priv *priv, + int active_network, + struct libipw_network *network) +{ + u32 size = sizeof(struct libipw_qos_parameters); + + if (network->capability & WLAN_CAPABILITY_IBSS) + network->qos_data.active = network->qos_data.supported; + + if (network->flags & NETWORK_HAS_QOS_MASK) { + if (active_network && + (network->flags & NETWORK_HAS_QOS_PARAMETERS)) + network->qos_data.active = network->qos_data.supported; + + if ((network->qos_data.active == 1) && (active_network == 1) && + (network->flags & NETWORK_HAS_QOS_PARAMETERS) && + (network->qos_data.old_param_count != + network->qos_data.param_count)) { + network->qos_data.old_param_count = + network->qos_data.param_count; + schedule_work(&priv->qos_activate); + IPW_DEBUG_QOS("QoS parameters change call " + "qos_activate\n"); + } + } else { + if ((priv->ieee->mode == IEEE_B) || (network->mode == IEEE_B)) + memcpy(&network->qos_data.parameters, + &def_parameters_CCK, size); + else + memcpy(&network->qos_data.parameters, + &def_parameters_OFDM, size); + + if ((network->qos_data.active == 1) && (active_network == 1)) { + IPW_DEBUG_QOS("QoS was disabled call qos_activate\n"); + schedule_work(&priv->qos_activate); + } + + network->qos_data.active = 0; + network->qos_data.supported = 0; + } + if ((priv->status & STATUS_ASSOCIATED) && + (priv->ieee->iw_mode == IW_MODE_ADHOC) && (active_network == 0)) { + if (memcmp(network->bssid, priv->bssid, ETH_ALEN)) + if (network->capability & WLAN_CAPABILITY_IBSS) + if ((network->ssid_len == + priv->assoc_network->ssid_len) && + !memcmp(network->ssid, + priv->assoc_network->ssid, + network->ssid_len)) { + schedule_work(&priv->merge_networks); + } + } + + return 0; +} + +/* +* This function set up the firmware to support QoS. It sends +* IPW_CMD_QOS_PARAMETERS and IPW_CMD_WME_INFO +*/ +static int ipw_qos_activate(struct ipw_priv *priv, + struct libipw_qos_data *qos_network_data) +{ + int err; + struct libipw_qos_parameters qos_parameters[QOS_QOS_SETS]; + struct libipw_qos_parameters *active_one = NULL; + u32 size = sizeof(struct libipw_qos_parameters); + u32 burst_duration; + int i; + u8 type; + + type = ipw_qos_current_mode(priv); + + active_one = &(qos_parameters[QOS_PARAM_SET_DEF_CCK]); + memcpy(active_one, priv->qos_data.def_qos_parm_CCK, size); + active_one = &(qos_parameters[QOS_PARAM_SET_DEF_OFDM]); + memcpy(active_one, priv->qos_data.def_qos_parm_OFDM, size); + + if (qos_network_data == NULL) { + if (type == IEEE_B) { + IPW_DEBUG_QOS("QoS activate network mode %d\n", type); + active_one = &def_parameters_CCK; + } else + active_one = &def_parameters_OFDM; + + memcpy(&qos_parameters[QOS_PARAM_SET_ACTIVE], active_one, size); + burst_duration = ipw_qos_get_burst_duration(priv); + for (i = 0; i < QOS_QUEUE_NUM; i++) + qos_parameters[QOS_PARAM_SET_ACTIVE].tx_op_limit[i] = + cpu_to_le16(burst_duration); + } else if (priv->ieee->iw_mode == IW_MODE_ADHOC) { + if (type == IEEE_B) { + IPW_DEBUG_QOS("QoS activate IBSS network mode %d\n", + type); + if (priv->qos_data.qos_enable == 0) + active_one = &def_parameters_CCK; + else + active_one = priv->qos_data.def_qos_parm_CCK; + } else { + if (priv->qos_data.qos_enable == 0) + active_one = &def_parameters_OFDM; + else + active_one = priv->qos_data.def_qos_parm_OFDM; + } + memcpy(&qos_parameters[QOS_PARAM_SET_ACTIVE], active_one, size); + } else { + unsigned long flags; + int active; + + spin_lock_irqsave(&priv->ieee->lock, flags); + active_one = &(qos_network_data->parameters); + qos_network_data->old_param_count = + qos_network_data->param_count; + memcpy(&qos_parameters[QOS_PARAM_SET_ACTIVE], active_one, size); + active = qos_network_data->supported; + spin_unlock_irqrestore(&priv->ieee->lock, flags); + + if (active == 0) { + burst_duration = ipw_qos_get_burst_duration(priv); + for (i = 0; i < QOS_QUEUE_NUM; i++) + qos_parameters[QOS_PARAM_SET_ACTIVE]. + tx_op_limit[i] = cpu_to_le16(burst_duration); + } + } + + IPW_DEBUG_QOS("QoS sending IPW_CMD_QOS_PARAMETERS\n"); + err = ipw_send_qos_params_command(priv, &qos_parameters[0]); + if (err) + IPW_DEBUG_QOS("QoS IPW_CMD_QOS_PARAMETERS failed\n"); + + return err; +} + +/* +* send IPW_CMD_WME_INFO to the firmware +*/ +static int ipw_qos_set_info_element(struct ipw_priv *priv) +{ + int ret = 0; + struct libipw_qos_information_element qos_info; + + if (priv == NULL) + return -1; + + qos_info.elementID = QOS_ELEMENT_ID; + qos_info.length = sizeof(struct libipw_qos_information_element) - 2; + + qos_info.version = QOS_VERSION_1; + qos_info.ac_info = 0; + + memcpy(qos_info.qui, qos_oui, QOS_OUI_LEN); + qos_info.qui_type = QOS_OUI_TYPE; + qos_info.qui_subtype = QOS_OUI_INFO_SUB_TYPE; + + ret = ipw_send_qos_info_command(priv, &qos_info); + if (ret != 0) { + IPW_DEBUG_QOS("QoS error calling ipw_send_qos_info_command\n"); + } + return ret; +} + +/* +* Set the QoS parameter with the association request structure +*/ +static int ipw_qos_association(struct ipw_priv *priv, + struct libipw_network *network) +{ + int err = 0; + struct libipw_qos_data *qos_data = NULL; + struct libipw_qos_data ibss_data = { + .supported = 1, + .active = 1, + }; + + switch (priv->ieee->iw_mode) { + case IW_MODE_ADHOC: + BUG_ON(!(network->capability & WLAN_CAPABILITY_IBSS)); + + qos_data = &ibss_data; + break; + + case IW_MODE_INFRA: + qos_data = &network->qos_data; + break; + + default: + BUG(); + break; + } + + err = ipw_qos_activate(priv, qos_data); + if (err) { + priv->assoc_request.policy_support &= ~HC_QOS_SUPPORT_ASSOC; + return err; + } + + if (priv->qos_data.qos_enable && qos_data->supported) { + IPW_DEBUG_QOS("QoS will be enabled for this association\n"); + priv->assoc_request.policy_support |= HC_QOS_SUPPORT_ASSOC; + return ipw_qos_set_info_element(priv); + } + + return 0; +} + +/* +* handling the beaconing responses. if we get different QoS setting +* off the network from the associated setting, adjust the QoS +* setting +*/ +static int ipw_qos_association_resp(struct ipw_priv *priv, + struct libipw_network *network) +{ + int ret = 0; + unsigned long flags; + u32 size = sizeof(struct libipw_qos_parameters); + int set_qos_param = 0; + + if ((priv == NULL) || (network == NULL) || + (priv->assoc_network == NULL)) + return ret; + + if (!(priv->status & STATUS_ASSOCIATED)) + return ret; + + if ((priv->ieee->iw_mode != IW_MODE_INFRA)) + return ret; + + spin_lock_irqsave(&priv->ieee->lock, flags); + if (network->flags & NETWORK_HAS_QOS_PARAMETERS) { + memcpy(&priv->assoc_network->qos_data, &network->qos_data, + sizeof(struct libipw_qos_data)); + priv->assoc_network->qos_data.active = 1; + if ((network->qos_data.old_param_count != + network->qos_data.param_count)) { + set_qos_param = 1; + network->qos_data.old_param_count = + network->qos_data.param_count; + } + + } else { + if ((network->mode == IEEE_B) || (priv->ieee->mode == IEEE_B)) + memcpy(&priv->assoc_network->qos_data.parameters, + &def_parameters_CCK, size); + else + memcpy(&priv->assoc_network->qos_data.parameters, + &def_parameters_OFDM, size); + priv->assoc_network->qos_data.active = 0; + priv->assoc_network->qos_data.supported = 0; + set_qos_param = 1; + } + + spin_unlock_irqrestore(&priv->ieee->lock, flags); + + if (set_qos_param == 1) + schedule_work(&priv->qos_activate); + + return ret; +} + +static u32 ipw_qos_get_burst_duration(struct ipw_priv *priv) +{ + u32 ret = 0; + + if ((priv == NULL)) + return 0; + + if (!(priv->ieee->modulation & LIBIPW_OFDM_MODULATION)) + ret = priv->qos_data.burst_duration_CCK; + else + ret = priv->qos_data.burst_duration_OFDM; + + return ret; +} + +/* +* Initialize the setting of QoS global +*/ +static void ipw_qos_init(struct ipw_priv *priv, int enable, + int burst_enable, u32 burst_duration_CCK, + u32 burst_duration_OFDM) +{ + priv->qos_data.qos_enable = enable; + + if (priv->qos_data.qos_enable) { + priv->qos_data.def_qos_parm_CCK = &def_qos_parameters_CCK; + priv->qos_data.def_qos_parm_OFDM = &def_qos_parameters_OFDM; + IPW_DEBUG_QOS("QoS is enabled\n"); + } else { + priv->qos_data.def_qos_parm_CCK = &def_parameters_CCK; + priv->qos_data.def_qos_parm_OFDM = &def_parameters_OFDM; + IPW_DEBUG_QOS("QoS is not enabled\n"); + } + + priv->qos_data.burst_enable = burst_enable; + + if (burst_enable) { + priv->qos_data.burst_duration_CCK = burst_duration_CCK; + priv->qos_data.burst_duration_OFDM = burst_duration_OFDM; + } else { + priv->qos_data.burst_duration_CCK = 0; + priv->qos_data.burst_duration_OFDM = 0; + } +} + +/* +* map the packet priority to the right TX Queue +*/ +static int ipw_get_tx_queue_number(struct ipw_priv *priv, u16 priority) +{ + if (priority > 7 || !priv->qos_data.qos_enable) + priority = 0; + + return from_priority_to_tx_queue[priority] - 1; +} + +static int ipw_is_qos_active(struct net_device *dev, + struct sk_buff *skb) +{ + struct ipw_priv *priv = libipw_priv(dev); + struct libipw_qos_data *qos_data = NULL; + int active, supported; + u8 *daddr = skb->data + ETH_ALEN; + int unicast = !is_multicast_ether_addr(daddr); + + if (!(priv->status & STATUS_ASSOCIATED)) + return 0; + + qos_data = &priv->assoc_network->qos_data; + + if (priv->ieee->iw_mode == IW_MODE_ADHOC) { + if (unicast == 0) + qos_data->active = 0; + else + qos_data->active = qos_data->supported; + } + active = qos_data->active; + supported = qos_data->supported; + IPW_DEBUG_QOS("QoS %d network is QoS active %d supported %d " + "unicast %d\n", + priv->qos_data.qos_enable, active, supported, unicast); + if (active && priv->qos_data.qos_enable) + return 1; + + return 0; + +} +/* +* add QoS parameter to the TX command +*/ +static int ipw_qos_set_tx_queue_command(struct ipw_priv *priv, + u16 priority, + struct tfd_data *tfd) +{ + int tx_queue_id = 0; + + + tx_queue_id = from_priority_to_tx_queue[priority] - 1; + tfd->tx_flags_ext |= DCT_FLAG_EXT_QOS_ENABLED; + + if (priv->qos_data.qos_no_ack_mask & (1UL << tx_queue_id)) { + tfd->tx_flags &= ~DCT_FLAG_ACK_REQD; + tfd->tfd.tfd_26.mchdr.qos_ctrl |= cpu_to_le16(CTRL_QOS_NO_ACK); + } + return 0; +} + +/* +* background support to run QoS activate functionality +*/ +static void ipw_bg_qos_activate(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, qos_activate); + + mutex_lock(&priv->mutex); + + if (priv->status & STATUS_ASSOCIATED) + ipw_qos_activate(priv, &(priv->assoc_network->qos_data)); + + mutex_unlock(&priv->mutex); +} + +static int ipw_handle_probe_response(struct net_device *dev, + struct libipw_probe_response *resp, + struct libipw_network *network) +{ + struct ipw_priv *priv = libipw_priv(dev); + int active_network = ((priv->status & STATUS_ASSOCIATED) && + (network == priv->assoc_network)); + + ipw_qos_handle_probe_response(priv, active_network, network); + + return 0; +} + +static int ipw_handle_beacon(struct net_device *dev, + struct libipw_beacon *resp, + struct libipw_network *network) +{ + struct ipw_priv *priv = libipw_priv(dev); + int active_network = ((priv->status & STATUS_ASSOCIATED) && + (network == priv->assoc_network)); + + ipw_qos_handle_probe_response(priv, active_network, network); + + return 0; +} + +static int ipw_handle_assoc_response(struct net_device *dev, + struct libipw_assoc_response *resp, + struct libipw_network *network) +{ + struct ipw_priv *priv = libipw_priv(dev); + ipw_qos_association_resp(priv, network); + return 0; +} + +static int ipw_send_qos_params_command(struct ipw_priv *priv, struct libipw_qos_parameters + *qos_param) +{ + return ipw_send_cmd_pdu(priv, IPW_CMD_QOS_PARAMETERS, + sizeof(*qos_param) * 3, qos_param); +} + +static int ipw_send_qos_info_command(struct ipw_priv *priv, struct libipw_qos_information_element + *qos_param) +{ + return ipw_send_cmd_pdu(priv, IPW_CMD_WME_INFO, sizeof(*qos_param), + qos_param); +} + +#endif /* CPTCFG_IPW2200_QOS */ + +static int ipw_associate_network(struct ipw_priv *priv, + struct libipw_network *network, + struct ipw_supported_rates *rates, int roaming) +{ + int err; + DECLARE_SSID_BUF(ssid); + + if (priv->config & CFG_FIXED_RATE) + ipw_set_fixed_rate(priv, network->mode); + + if (!(priv->config & CFG_STATIC_ESSID)) { + priv->essid_len = min(network->ssid_len, + (u8) IW_ESSID_MAX_SIZE); + memcpy(priv->essid, network->ssid, priv->essid_len); + } + + network->last_associate = jiffies; + + memset(&priv->assoc_request, 0, sizeof(priv->assoc_request)); + priv->assoc_request.channel = network->channel; + priv->assoc_request.auth_key = 0; + + if ((priv->capability & CAP_PRIVACY_ON) && + (priv->ieee->sec.auth_mode == WLAN_AUTH_SHARED_KEY)) { + priv->assoc_request.auth_type = AUTH_SHARED_KEY; + priv->assoc_request.auth_key = priv->ieee->sec.active_key; + + if (priv->ieee->sec.level == SEC_LEVEL_1) + ipw_send_wep_keys(priv, DCW_WEP_KEY_SEC_TYPE_WEP); + + } else if ((priv->capability & CAP_PRIVACY_ON) && + (priv->ieee->sec.auth_mode == WLAN_AUTH_LEAP)) + priv->assoc_request.auth_type = AUTH_LEAP; + else + priv->assoc_request.auth_type = AUTH_OPEN; + + if (priv->ieee->wpa_ie_len) { + priv->assoc_request.policy_support = cpu_to_le16(0x02); /* RSN active */ + ipw_set_rsn_capa(priv, priv->ieee->wpa_ie, + priv->ieee->wpa_ie_len); + } + + /* + * It is valid for our ieee device to support multiple modes, but + * when it comes to associating to a given network we have to choose + * just one mode. + */ + if (network->mode & priv->ieee->mode & IEEE_A) + priv->assoc_request.ieee_mode = IPW_A_MODE; + else if (network->mode & priv->ieee->mode & IEEE_G) + priv->assoc_request.ieee_mode = IPW_G_MODE; + else if (network->mode & priv->ieee->mode & IEEE_B) + priv->assoc_request.ieee_mode = IPW_B_MODE; + + priv->assoc_request.capability = cpu_to_le16(network->capability); + if ((network->capability & WLAN_CAPABILITY_SHORT_PREAMBLE) + && !(priv->config & CFG_PREAMBLE_LONG)) { + priv->assoc_request.preamble_length = DCT_FLAG_SHORT_PREAMBLE; + } else { + priv->assoc_request.preamble_length = DCT_FLAG_LONG_PREAMBLE; + + /* Clear the short preamble if we won't be supporting it */ + priv->assoc_request.capability &= + ~cpu_to_le16(WLAN_CAPABILITY_SHORT_PREAMBLE); + } + + /* Clear capability bits that aren't used in Ad Hoc */ + if (priv->ieee->iw_mode == IW_MODE_ADHOC) + priv->assoc_request.capability &= + ~cpu_to_le16(WLAN_CAPABILITY_SHORT_SLOT_TIME); + + IPW_DEBUG_ASSOC("%ssociation attempt: '%s', channel %d, " + "802.11%c [%d], %s[:%s], enc=%s%s%s%c%c\n", + roaming ? "Rea" : "A", + print_ssid(ssid, priv->essid, priv->essid_len), + network->channel, + ipw_modes[priv->assoc_request.ieee_mode], + rates->num_rates, + (priv->assoc_request.preamble_length == + DCT_FLAG_LONG_PREAMBLE) ? "long" : "short", + network->capability & + WLAN_CAPABILITY_SHORT_PREAMBLE ? "short" : "long", + priv->capability & CAP_PRIVACY_ON ? "on " : "off", + priv->capability & CAP_PRIVACY_ON ? + (priv->capability & CAP_SHARED_KEY ? "(shared)" : + "(open)") : "", + priv->capability & CAP_PRIVACY_ON ? " key=" : "", + priv->capability & CAP_PRIVACY_ON ? + '1' + priv->ieee->sec.active_key : '.', + priv->capability & CAP_PRIVACY_ON ? '.' : ' '); + + priv->assoc_request.beacon_interval = cpu_to_le16(network->beacon_interval); + if ((priv->ieee->iw_mode == IW_MODE_ADHOC) && + (network->time_stamp[0] == 0) && (network->time_stamp[1] == 0)) { + priv->assoc_request.assoc_type = HC_IBSS_START; + priv->assoc_request.assoc_tsf_msw = 0; + priv->assoc_request.assoc_tsf_lsw = 0; + } else { + if (unlikely(roaming)) + priv->assoc_request.assoc_type = HC_REASSOCIATE; + else + priv->assoc_request.assoc_type = HC_ASSOCIATE; + priv->assoc_request.assoc_tsf_msw = cpu_to_le32(network->time_stamp[1]); + priv->assoc_request.assoc_tsf_lsw = cpu_to_le32(network->time_stamp[0]); + } + + memcpy(priv->assoc_request.bssid, network->bssid, ETH_ALEN); + + if (priv->ieee->iw_mode == IW_MODE_ADHOC) { + memset(&priv->assoc_request.dest, 0xFF, ETH_ALEN); + priv->assoc_request.atim_window = cpu_to_le16(network->atim_window); + } else { + memcpy(priv->assoc_request.dest, network->bssid, ETH_ALEN); + priv->assoc_request.atim_window = 0; + } + + priv->assoc_request.listen_interval = cpu_to_le16(network->listen_interval); + + err = ipw_send_ssid(priv, priv->essid, priv->essid_len); + if (err) { + IPW_DEBUG_HC("Attempt to send SSID command failed.\n"); + return err; + } + + rates->ieee_mode = priv->assoc_request.ieee_mode; + rates->purpose = IPW_RATE_CONNECT; + ipw_send_supported_rates(priv, rates); + + if (priv->assoc_request.ieee_mode == IPW_G_MODE) + priv->sys_config.dot11g_auto_detection = 1; + else + priv->sys_config.dot11g_auto_detection = 0; + + if (priv->ieee->iw_mode == IW_MODE_ADHOC) + priv->sys_config.answer_broadcast_ssid_probe = 1; + else + priv->sys_config.answer_broadcast_ssid_probe = 0; + + err = ipw_send_system_config(priv); + if (err) { + IPW_DEBUG_HC("Attempt to send sys config command failed.\n"); + return err; + } + + IPW_DEBUG_ASSOC("Association sensitivity: %d\n", network->stats.rssi); + err = ipw_set_sensitivity(priv, network->stats.rssi + IPW_RSSI_TO_DBM); + if (err) { + IPW_DEBUG_HC("Attempt to send associate command failed.\n"); + return err; + } + + /* + * If preemption is enabled, it is possible for the association + * to complete before we return from ipw_send_associate. Therefore + * we have to be sure and update our priviate data first. + */ + priv->channel = network->channel; + memcpy(priv->bssid, network->bssid, ETH_ALEN); + priv->status |= STATUS_ASSOCIATING; + priv->status &= ~STATUS_SECURITY_UPDATED; + + priv->assoc_network = network; + +#ifdef CPTCFG_IPW2200_QOS + ipw_qos_association(priv, network); +#endif + + err = ipw_send_associate(priv, &priv->assoc_request); + if (err) { + IPW_DEBUG_HC("Attempt to send associate command failed.\n"); + return err; + } + + IPW_DEBUG(IPW_DL_STATE, "associating: '%s' %pM\n", + print_ssid(ssid, priv->essid, priv->essid_len), + priv->bssid); + + return 0; +} + +static void ipw_roam(void *data) +{ + struct ipw_priv *priv = data; + struct libipw_network *network = NULL; + struct ipw_network_match match = { + .network = priv->assoc_network + }; + + /* The roaming process is as follows: + * + * 1. Missed beacon threshold triggers the roaming process by + * setting the status ROAM bit and requesting a scan. + * 2. When the scan completes, it schedules the ROAM work + * 3. The ROAM work looks at all of the known networks for one that + * is a better network than the currently associated. If none + * found, the ROAM process is over (ROAM bit cleared) + * 4. If a better network is found, a disassociation request is + * sent. + * 5. When the disassociation completes, the roam work is again + * scheduled. The second time through, the driver is no longer + * associated, and the newly selected network is sent an + * association request. + * 6. At this point ,the roaming process is complete and the ROAM + * status bit is cleared. + */ + + /* If we are no longer associated, and the roaming bit is no longer + * set, then we are not actively roaming, so just return */ + if (!(priv->status & (STATUS_ASSOCIATED | STATUS_ROAMING))) + return; + + if (priv->status & STATUS_ASSOCIATED) { + /* First pass through ROAM process -- look for a better + * network */ + unsigned long flags; + u8 rssi = priv->assoc_network->stats.rssi; + priv->assoc_network->stats.rssi = -128; + spin_lock_irqsave(&priv->ieee->lock, flags); + list_for_each_entry(network, &priv->ieee->network_list, list) { + if (network != priv->assoc_network) + ipw_best_network(priv, &match, network, 1); + } + spin_unlock_irqrestore(&priv->ieee->lock, flags); + priv->assoc_network->stats.rssi = rssi; + + if (match.network == priv->assoc_network) { + IPW_DEBUG_ASSOC("No better APs in this network to " + "roam to.\n"); + priv->status &= ~STATUS_ROAMING; + ipw_debug_config(priv); + return; + } + + ipw_send_disassociate(priv, 1); + priv->assoc_network = match.network; + + return; + } + + /* Second pass through ROAM process -- request association */ + ipw_compatible_rates(priv, priv->assoc_network, &match.rates); + ipw_associate_network(priv, priv->assoc_network, &match.rates, 1); + priv->status &= ~STATUS_ROAMING; +} + +static void ipw_bg_roam(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, roam); + mutex_lock(&priv->mutex); + ipw_roam(priv); + mutex_unlock(&priv->mutex); +} + +static int ipw_associate(void *data) +{ + struct ipw_priv *priv = data; + + struct libipw_network *network = NULL; + struct ipw_network_match match = { + .network = NULL + }; + struct ipw_supported_rates *rates; + struct list_head *element; + unsigned long flags; + DECLARE_SSID_BUF(ssid); + + if (priv->ieee->iw_mode == IW_MODE_MONITOR) { + IPW_DEBUG_ASSOC("Not attempting association (monitor mode)\n"); + return 0; + } + + if (priv->status & (STATUS_ASSOCIATED | STATUS_ASSOCIATING)) { + IPW_DEBUG_ASSOC("Not attempting association (already in " + "progress)\n"); + return 0; + } + + if (priv->status & STATUS_DISASSOCIATING) { + IPW_DEBUG_ASSOC("Not attempting association (in " + "disassociating)\n "); + schedule_work(&priv->associate); + return 0; + } + + if (!ipw_is_init(priv) || (priv->status & STATUS_SCANNING)) { + IPW_DEBUG_ASSOC("Not attempting association (scanning or not " + "initialized)\n"); + return 0; + } + + if (!(priv->config & CFG_ASSOCIATE) && + !(priv->config & (CFG_STATIC_ESSID | CFG_STATIC_BSSID))) { + IPW_DEBUG_ASSOC("Not attempting association (associate=0)\n"); + return 0; + } + + /* Protect our use of the network_list */ + spin_lock_irqsave(&priv->ieee->lock, flags); + list_for_each_entry(network, &priv->ieee->network_list, list) + ipw_best_network(priv, &match, network, 0); + + network = match.network; + rates = &match.rates; + + if (network == NULL && + priv->ieee->iw_mode == IW_MODE_ADHOC && + priv->config & CFG_ADHOC_CREATE && + priv->config & CFG_STATIC_ESSID && + priv->config & CFG_STATIC_CHANNEL) { + /* Use oldest network if the free list is empty */ + if (list_empty(&priv->ieee->network_free_list)) { + struct libipw_network *oldest = NULL; + struct libipw_network *target; + + list_for_each_entry(target, &priv->ieee->network_list, list) { + if ((oldest == NULL) || + (target->last_scanned < oldest->last_scanned)) + oldest = target; + } + + /* If there are no more slots, expire the oldest */ + list_del(&oldest->list); + target = oldest; + IPW_DEBUG_ASSOC("Expired '%s' (%pM) from " + "network list.\n", + print_ssid(ssid, target->ssid, + target->ssid_len), + target->bssid); + list_add_tail(&target->list, + &priv->ieee->network_free_list); + } + + element = priv->ieee->network_free_list.next; + network = list_entry(element, struct libipw_network, list); + ipw_adhoc_create(priv, network); + rates = &priv->rates; + list_del(element); + list_add_tail(&network->list, &priv->ieee->network_list); + } + spin_unlock_irqrestore(&priv->ieee->lock, flags); + + /* If we reached the end of the list, then we don't have any valid + * matching APs */ + if (!network) { + ipw_debug_config(priv); + + if (!(priv->status & STATUS_SCANNING)) { + if (!(priv->config & CFG_SPEED_SCAN)) + schedule_delayed_work(&priv->request_scan, + SCAN_INTERVAL); + else + schedule_delayed_work(&priv->request_scan, 0); + } + + return 0; + } + + ipw_associate_network(priv, network, rates, 0); + + return 1; +} + +static void ipw_bg_associate(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, associate); + mutex_lock(&priv->mutex); + ipw_associate(priv); + mutex_unlock(&priv->mutex); +} + +static void ipw_rebuild_decrypted_skb(struct ipw_priv *priv, + struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr; + u16 fc; + + hdr = (struct ieee80211_hdr *)skb->data; + fc = le16_to_cpu(hdr->frame_control); + if (!(fc & IEEE80211_FCTL_PROTECTED)) + return; + + fc &= ~IEEE80211_FCTL_PROTECTED; + hdr->frame_control = cpu_to_le16(fc); + switch (priv->ieee->sec.level) { + case SEC_LEVEL_3: + /* Remove CCMP HDR */ + memmove(skb->data + LIBIPW_3ADDR_LEN, + skb->data + LIBIPW_3ADDR_LEN + 8, + skb->len - LIBIPW_3ADDR_LEN - 8); + skb_trim(skb, skb->len - 16); /* CCMP_HDR_LEN + CCMP_MIC_LEN */ + break; + case SEC_LEVEL_2: + break; + case SEC_LEVEL_1: + /* Remove IV */ + memmove(skb->data + LIBIPW_3ADDR_LEN, + skb->data + LIBIPW_3ADDR_LEN + 4, + skb->len - LIBIPW_3ADDR_LEN - 4); + skb_trim(skb, skb->len - 8); /* IV + ICV */ + break; + case SEC_LEVEL_0: + break; + default: + printk(KERN_ERR "Unknown security level %d\n", + priv->ieee->sec.level); + break; + } +} + +static void ipw_handle_data_packet(struct ipw_priv *priv, + struct ipw_rx_mem_buffer *rxb, + struct libipw_rx_stats *stats) +{ + struct net_device *dev = priv->net_dev; + struct libipw_hdr_4addr *hdr; + struct ipw_rx_packet *pkt = (struct ipw_rx_packet *)rxb->skb->data; + + /* We received data from the HW, so stop the watchdog */ + dev->trans_start = jiffies; + + /* We only process data packets if the + * interface is open */ + if (unlikely((le16_to_cpu(pkt->u.frame.length) + IPW_RX_FRAME_SIZE) > + skb_tailroom(rxb->skb))) { + dev->stats.rx_errors++; + priv->wstats.discard.misc++; + IPW_DEBUG_DROP("Corruption detected! Oh no!\n"); + return; + } else if (unlikely(!netif_running(priv->net_dev))) { + dev->stats.rx_dropped++; + priv->wstats.discard.misc++; + IPW_DEBUG_DROP("Dropping packet while interface is not up.\n"); + return; + } + + /* Advance skb->data to the start of the actual payload */ + skb_reserve(rxb->skb, offsetof(struct ipw_rx_packet, u.frame.data)); + + /* Set the size of the skb to the size of the frame */ + skb_put(rxb->skb, le16_to_cpu(pkt->u.frame.length)); + + IPW_DEBUG_RX("Rx packet of %d bytes.\n", rxb->skb->len); + + /* HW decrypt will not clear the WEP bit, MIC, PN, etc. */ + hdr = (struct libipw_hdr_4addr *)rxb->skb->data; + if (priv->ieee->iw_mode != IW_MODE_MONITOR && + (is_multicast_ether_addr(hdr->addr1) ? + !priv->ieee->host_mc_decrypt : !priv->ieee->host_decrypt)) + ipw_rebuild_decrypted_skb(priv, rxb->skb); + + if (!libipw_rx(priv->ieee, rxb->skb, stats)) + dev->stats.rx_errors++; + else { /* libipw_rx succeeded, so it now owns the SKB */ + rxb->skb = NULL; + __ipw_led_activity_on(priv); + } +} + +#ifdef CPTCFG_IPW2200_RADIOTAP +static void ipw_handle_data_packet_monitor(struct ipw_priv *priv, + struct ipw_rx_mem_buffer *rxb, + struct libipw_rx_stats *stats) +{ + struct net_device *dev = priv->net_dev; + struct ipw_rx_packet *pkt = (struct ipw_rx_packet *)rxb->skb->data; + struct ipw_rx_frame *frame = &pkt->u.frame; + + /* initial pull of some data */ + u16 received_channel = frame->received_channel; + u8 antennaAndPhy = frame->antennaAndPhy; + s8 antsignal = frame->rssi_dbm - IPW_RSSI_TO_DBM; /* call it signed anyhow */ + u16 pktrate = frame->rate; + + /* Magic struct that slots into the radiotap header -- no reason + * to build this manually element by element, we can write it much + * more efficiently than we can parse it. ORDER MATTERS HERE */ + struct ipw_rt_hdr *ipw_rt; + + unsigned short len = le16_to_cpu(pkt->u.frame.length); + + /* We received data from the HW, so stop the watchdog */ + dev->trans_start = jiffies; + + /* We only process data packets if the + * interface is open */ + if (unlikely((le16_to_cpu(pkt->u.frame.length) + IPW_RX_FRAME_SIZE) > + skb_tailroom(rxb->skb))) { + dev->stats.rx_errors++; + priv->wstats.discard.misc++; + IPW_DEBUG_DROP("Corruption detected! Oh no!\n"); + return; + } else if (unlikely(!netif_running(priv->net_dev))) { + dev->stats.rx_dropped++; + priv->wstats.discard.misc++; + IPW_DEBUG_DROP("Dropping packet while interface is not up.\n"); + return; + } + + /* Libpcap 0.9.3+ can handle variable length radiotap, so we'll use + * that now */ + if (len > IPW_RX_BUF_SIZE - sizeof(struct ipw_rt_hdr)) { + /* FIXME: Should alloc bigger skb instead */ + dev->stats.rx_dropped++; + priv->wstats.discard.misc++; + IPW_DEBUG_DROP("Dropping too large packet in monitor\n"); + return; + } + + /* copy the frame itself */ + memmove(rxb->skb->data + sizeof(struct ipw_rt_hdr), + rxb->skb->data + IPW_RX_FRAME_SIZE, len); + + ipw_rt = (struct ipw_rt_hdr *)rxb->skb->data; + + ipw_rt->rt_hdr.it_version = PKTHDR_RADIOTAP_VERSION; + ipw_rt->rt_hdr.it_pad = 0; /* always good to zero */ + ipw_rt->rt_hdr.it_len = cpu_to_le16(sizeof(struct ipw_rt_hdr)); /* total header+data */ + + /* Big bitfield of all the fields we provide in radiotap */ + ipw_rt->rt_hdr.it_present = cpu_to_le32( + (1 << IEEE80211_RADIOTAP_TSFT) | + (1 << IEEE80211_RADIOTAP_FLAGS) | + (1 << IEEE80211_RADIOTAP_RATE) | + (1 << IEEE80211_RADIOTAP_CHANNEL) | + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | + (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | + (1 << IEEE80211_RADIOTAP_ANTENNA)); + + /* Zero the flags, we'll add to them as we go */ + ipw_rt->rt_flags = 0; + ipw_rt->rt_tsf = (u64)(frame->parent_tsf[3] << 24 | + frame->parent_tsf[2] << 16 | + frame->parent_tsf[1] << 8 | + frame->parent_tsf[0]); + + /* Convert signal to DBM */ + ipw_rt->rt_dbmsignal = antsignal; + ipw_rt->rt_dbmnoise = (s8) le16_to_cpu(frame->noise); + + /* Convert the channel data and set the flags */ + ipw_rt->rt_channel = cpu_to_le16(ieee80211chan2mhz(received_channel)); + if (received_channel > 14) { /* 802.11a */ + ipw_rt->rt_chbitmask = + cpu_to_le16((IEEE80211_CHAN_OFDM | IEEE80211_CHAN_5GHZ)); + } else if (antennaAndPhy & 32) { /* 802.11b */ + ipw_rt->rt_chbitmask = + cpu_to_le16((IEEE80211_CHAN_CCK | IEEE80211_CHAN_2GHZ)); + } else { /* 802.11g */ + ipw_rt->rt_chbitmask = + cpu_to_le16(IEEE80211_CHAN_OFDM | IEEE80211_CHAN_2GHZ); + } + + /* set the rate in multiples of 500k/s */ + switch (pktrate) { + case IPW_TX_RATE_1MB: + ipw_rt->rt_rate = 2; + break; + case IPW_TX_RATE_2MB: + ipw_rt->rt_rate = 4; + break; + case IPW_TX_RATE_5MB: + ipw_rt->rt_rate = 10; + break; + case IPW_TX_RATE_6MB: + ipw_rt->rt_rate = 12; + break; + case IPW_TX_RATE_9MB: + ipw_rt->rt_rate = 18; + break; + case IPW_TX_RATE_11MB: + ipw_rt->rt_rate = 22; + break; + case IPW_TX_RATE_12MB: + ipw_rt->rt_rate = 24; + break; + case IPW_TX_RATE_18MB: + ipw_rt->rt_rate = 36; + break; + case IPW_TX_RATE_24MB: + ipw_rt->rt_rate = 48; + break; + case IPW_TX_RATE_36MB: + ipw_rt->rt_rate = 72; + break; + case IPW_TX_RATE_48MB: + ipw_rt->rt_rate = 96; + break; + case IPW_TX_RATE_54MB: + ipw_rt->rt_rate = 108; + break; + default: + ipw_rt->rt_rate = 0; + break; + } + + /* antenna number */ + ipw_rt->rt_antenna = (antennaAndPhy & 3); /* Is this right? */ + + /* set the preamble flag if we have it */ + if ((antennaAndPhy & 64)) + ipw_rt->rt_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; + + /* Set the size of the skb to the size of the frame */ + skb_put(rxb->skb, len + sizeof(struct ipw_rt_hdr)); + + IPW_DEBUG_RX("Rx packet of %d bytes.\n", rxb->skb->len); + + if (!libipw_rx(priv->ieee, rxb->skb, stats)) + dev->stats.rx_errors++; + else { /* libipw_rx succeeded, so it now owns the SKB */ + rxb->skb = NULL; + /* no LED during capture */ + } +} +#endif + +#ifdef CPTCFG_IPW2200_PROMISCUOUS +#define libipw_is_probe_response(fc) \ + ((fc & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_MGMT && \ + (fc & IEEE80211_FCTL_STYPE) == IEEE80211_STYPE_PROBE_RESP ) + +#define libipw_is_management(fc) \ + ((fc & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_MGMT) + +#define libipw_is_control(fc) \ + ((fc & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_CTL) + +#define libipw_is_data(fc) \ + ((fc & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_DATA) + +#define libipw_is_assoc_request(fc) \ + ((fc & IEEE80211_FCTL_STYPE) == IEEE80211_STYPE_ASSOC_REQ) + +#define libipw_is_reassoc_request(fc) \ + ((fc & IEEE80211_FCTL_STYPE) == IEEE80211_STYPE_REASSOC_REQ) + +static void ipw_handle_promiscuous_rx(struct ipw_priv *priv, + struct ipw_rx_mem_buffer *rxb, + struct libipw_rx_stats *stats) +{ + struct net_device *dev = priv->prom_net_dev; + struct ipw_rx_packet *pkt = (struct ipw_rx_packet *)rxb->skb->data; + struct ipw_rx_frame *frame = &pkt->u.frame; + struct ipw_rt_hdr *ipw_rt; + + /* First cache any information we need before we overwrite + * the information provided in the skb from the hardware */ + struct ieee80211_hdr *hdr; + u16 channel = frame->received_channel; + u8 phy_flags = frame->antennaAndPhy; + s8 signal = frame->rssi_dbm - IPW_RSSI_TO_DBM; + s8 noise = (s8) le16_to_cpu(frame->noise); + u8 rate = frame->rate; + unsigned short len = le16_to_cpu(pkt->u.frame.length); + struct sk_buff *skb; + int hdr_only = 0; + u16 filter = priv->prom_priv->filter; + + /* If the filter is set to not include Rx frames then return */ + if (filter & IPW_PROM_NO_RX) + return; + + /* We received data from the HW, so stop the watchdog */ + dev->trans_start = jiffies; + + if (unlikely((len + IPW_RX_FRAME_SIZE) > skb_tailroom(rxb->skb))) { + dev->stats.rx_errors++; + IPW_DEBUG_DROP("Corruption detected! Oh no!\n"); + return; + } + + /* We only process data packets if the interface is open */ + if (unlikely(!netif_running(dev))) { + dev->stats.rx_dropped++; + IPW_DEBUG_DROP("Dropping packet while interface is not up.\n"); + return; + } + + /* Libpcap 0.9.3+ can handle variable length radiotap, so we'll use + * that now */ + if (len > IPW_RX_BUF_SIZE - sizeof(struct ipw_rt_hdr)) { + /* FIXME: Should alloc bigger skb instead */ + dev->stats.rx_dropped++; + IPW_DEBUG_DROP("Dropping too large packet in monitor\n"); + return; + } + + hdr = (void *)rxb->skb->data + IPW_RX_FRAME_SIZE; + if (libipw_is_management(le16_to_cpu(hdr->frame_control))) { + if (filter & IPW_PROM_NO_MGMT) + return; + if (filter & IPW_PROM_MGMT_HEADER_ONLY) + hdr_only = 1; + } else if (libipw_is_control(le16_to_cpu(hdr->frame_control))) { + if (filter & IPW_PROM_NO_CTL) + return; + if (filter & IPW_PROM_CTL_HEADER_ONLY) + hdr_only = 1; + } else if (libipw_is_data(le16_to_cpu(hdr->frame_control))) { + if (filter & IPW_PROM_NO_DATA) + return; + if (filter & IPW_PROM_DATA_HEADER_ONLY) + hdr_only = 1; + } + + /* Copy the SKB since this is for the promiscuous side */ + skb = skb_copy(rxb->skb, GFP_ATOMIC); + if (skb == NULL) { + IPW_ERROR("skb_clone failed for promiscuous copy.\n"); + return; + } + + /* copy the frame data to write after where the radiotap header goes */ + ipw_rt = (void *)skb->data; + + if (hdr_only) + len = libipw_get_hdrlen(le16_to_cpu(hdr->frame_control)); + + memcpy(ipw_rt->payload, hdr, len); + + ipw_rt->rt_hdr.it_version = PKTHDR_RADIOTAP_VERSION; + ipw_rt->rt_hdr.it_pad = 0; /* always good to zero */ + ipw_rt->rt_hdr.it_len = cpu_to_le16(sizeof(*ipw_rt)); /* total header+data */ + + /* Set the size of the skb to the size of the frame */ + skb_put(skb, sizeof(*ipw_rt) + len); + + /* Big bitfield of all the fields we provide in radiotap */ + ipw_rt->rt_hdr.it_present = cpu_to_le32( + (1 << IEEE80211_RADIOTAP_TSFT) | + (1 << IEEE80211_RADIOTAP_FLAGS) | + (1 << IEEE80211_RADIOTAP_RATE) | + (1 << IEEE80211_RADIOTAP_CHANNEL) | + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | + (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | + (1 << IEEE80211_RADIOTAP_ANTENNA)); + + /* Zero the flags, we'll add to them as we go */ + ipw_rt->rt_flags = 0; + ipw_rt->rt_tsf = (u64)(frame->parent_tsf[3] << 24 | + frame->parent_tsf[2] << 16 | + frame->parent_tsf[1] << 8 | + frame->parent_tsf[0]); + + /* Convert to DBM */ + ipw_rt->rt_dbmsignal = signal; + ipw_rt->rt_dbmnoise = noise; + + /* Convert the channel data and set the flags */ + ipw_rt->rt_channel = cpu_to_le16(ieee80211chan2mhz(channel)); + if (channel > 14) { /* 802.11a */ + ipw_rt->rt_chbitmask = + cpu_to_le16((IEEE80211_CHAN_OFDM | IEEE80211_CHAN_5GHZ)); + } else if (phy_flags & (1 << 5)) { /* 802.11b */ + ipw_rt->rt_chbitmask = + cpu_to_le16((IEEE80211_CHAN_CCK | IEEE80211_CHAN_2GHZ)); + } else { /* 802.11g */ + ipw_rt->rt_chbitmask = + cpu_to_le16(IEEE80211_CHAN_OFDM | IEEE80211_CHAN_2GHZ); + } + + /* set the rate in multiples of 500k/s */ + switch (rate) { + case IPW_TX_RATE_1MB: + ipw_rt->rt_rate = 2; + break; + case IPW_TX_RATE_2MB: + ipw_rt->rt_rate = 4; + break; + case IPW_TX_RATE_5MB: + ipw_rt->rt_rate = 10; + break; + case IPW_TX_RATE_6MB: + ipw_rt->rt_rate = 12; + break; + case IPW_TX_RATE_9MB: + ipw_rt->rt_rate = 18; + break; + case IPW_TX_RATE_11MB: + ipw_rt->rt_rate = 22; + break; + case IPW_TX_RATE_12MB: + ipw_rt->rt_rate = 24; + break; + case IPW_TX_RATE_18MB: + ipw_rt->rt_rate = 36; + break; + case IPW_TX_RATE_24MB: + ipw_rt->rt_rate = 48; + break; + case IPW_TX_RATE_36MB: + ipw_rt->rt_rate = 72; + break; + case IPW_TX_RATE_48MB: + ipw_rt->rt_rate = 96; + break; + case IPW_TX_RATE_54MB: + ipw_rt->rt_rate = 108; + break; + default: + ipw_rt->rt_rate = 0; + break; + } + + /* antenna number */ + ipw_rt->rt_antenna = (phy_flags & 3); + + /* set the preamble flag if we have it */ + if (phy_flags & (1 << 6)) + ipw_rt->rt_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; + + IPW_DEBUG_RX("Rx packet of %d bytes.\n", skb->len); + + if (!libipw_rx(priv->prom_priv->ieee, skb, stats)) { + dev->stats.rx_errors++; + dev_kfree_skb_any(skb); + } +} +#endif + +static int is_network_packet(struct ipw_priv *priv, + struct libipw_hdr_4addr *header) +{ + /* Filter incoming packets to determine if they are targeted toward + * this network, discarding packets coming from ourselves */ + switch (priv->ieee->iw_mode) { + case IW_MODE_ADHOC: /* Header: Dest. | Source | BSSID */ + /* packets from our adapter are dropped (echo) */ + if (!memcmp(header->addr2, priv->net_dev->dev_addr, ETH_ALEN)) + return 0; + + /* {broad,multi}cast packets to our BSSID go through */ + if (is_multicast_ether_addr(header->addr1)) + return !memcmp(header->addr3, priv->bssid, ETH_ALEN); + + /* packets to our adapter go through */ + return !memcmp(header->addr1, priv->net_dev->dev_addr, + ETH_ALEN); + + case IW_MODE_INFRA: /* Header: Dest. | BSSID | Source */ + /* packets from our adapter are dropped (echo) */ + if (!memcmp(header->addr3, priv->net_dev->dev_addr, ETH_ALEN)) + return 0; + + /* {broad,multi}cast packets to our BSS go through */ + if (is_multicast_ether_addr(header->addr1)) + return !memcmp(header->addr2, priv->bssid, ETH_ALEN); + + /* packets to our adapter go through */ + return !memcmp(header->addr1, priv->net_dev->dev_addr, + ETH_ALEN); + } + + return 1; +} + +#define IPW_PACKET_RETRY_TIME HZ + +static int is_duplicate_packet(struct ipw_priv *priv, + struct libipw_hdr_4addr *header) +{ + u16 sc = le16_to_cpu(header->seq_ctl); + u16 seq = WLAN_GET_SEQ_SEQ(sc); + u16 frag = WLAN_GET_SEQ_FRAG(sc); + u16 *last_seq, *last_frag; + unsigned long *last_time; + + switch (priv->ieee->iw_mode) { + case IW_MODE_ADHOC: + { + struct list_head *p; + struct ipw_ibss_seq *entry = NULL; + u8 *mac = header->addr2; + int index = mac[5] % IPW_IBSS_MAC_HASH_SIZE; + + __list_for_each(p, &priv->ibss_mac_hash[index]) { + entry = + list_entry(p, struct ipw_ibss_seq, list); + if (!memcmp(entry->mac, mac, ETH_ALEN)) + break; + } + if (p == &priv->ibss_mac_hash[index]) { + entry = kmalloc(sizeof(*entry), GFP_ATOMIC); + if (!entry) { + IPW_ERROR + ("Cannot malloc new mac entry\n"); + return 0; + } + memcpy(entry->mac, mac, ETH_ALEN); + entry->seq_num = seq; + entry->frag_num = frag; + entry->packet_time = jiffies; + list_add(&entry->list, + &priv->ibss_mac_hash[index]); + return 0; + } + last_seq = &entry->seq_num; + last_frag = &entry->frag_num; + last_time = &entry->packet_time; + break; + } + case IW_MODE_INFRA: + last_seq = &priv->last_seq_num; + last_frag = &priv->last_frag_num; + last_time = &priv->last_packet_time; + break; + default: + return 0; + } + if ((*last_seq == seq) && + time_after(*last_time + IPW_PACKET_RETRY_TIME, jiffies)) { + if (*last_frag == frag) + goto drop; + if (*last_frag + 1 != frag) + /* out-of-order fragment */ + goto drop; + } else + *last_seq = seq; + + *last_frag = frag; + *last_time = jiffies; + return 0; + + drop: + /* Comment this line now since we observed the card receives + * duplicate packets but the FCTL_RETRY bit is not set in the + * IBSS mode with fragmentation enabled. + BUG_ON(!(le16_to_cpu(header->frame_control) & IEEE80211_FCTL_RETRY)); */ + return 1; +} + +static void ipw_handle_mgmt_packet(struct ipw_priv *priv, + struct ipw_rx_mem_buffer *rxb, + struct libipw_rx_stats *stats) +{ + struct sk_buff *skb = rxb->skb; + struct ipw_rx_packet *pkt = (struct ipw_rx_packet *)skb->data; + struct libipw_hdr_4addr *header = (struct libipw_hdr_4addr *) + (skb->data + IPW_RX_FRAME_SIZE); + + libipw_rx_mgt(priv->ieee, header, stats); + + if (priv->ieee->iw_mode == IW_MODE_ADHOC && + ((WLAN_FC_GET_STYPE(le16_to_cpu(header->frame_ctl)) == + IEEE80211_STYPE_PROBE_RESP) || + (WLAN_FC_GET_STYPE(le16_to_cpu(header->frame_ctl)) == + IEEE80211_STYPE_BEACON))) { + if (!memcmp(header->addr3, priv->bssid, ETH_ALEN)) + ipw_add_station(priv, header->addr2); + } + + if (priv->config & CFG_NET_STATS) { + IPW_DEBUG_HC("sending stat packet\n"); + + /* Set the size of the skb to the size of the full + * ipw header and 802.11 frame */ + skb_put(skb, le16_to_cpu(pkt->u.frame.length) + + IPW_RX_FRAME_SIZE); + + /* Advance past the ipw packet header to the 802.11 frame */ + skb_pull(skb, IPW_RX_FRAME_SIZE); + + /* Push the libipw_rx_stats before the 802.11 frame */ + memcpy(skb_push(skb, sizeof(*stats)), stats, sizeof(*stats)); + + skb->dev = priv->ieee->dev; + + /* Point raw at the libipw_stats */ + skb_reset_mac_header(skb); + + skb->pkt_type = PACKET_OTHERHOST; + skb->protocol = cpu_to_be16(ETH_P_80211_STATS); + memset(skb->cb, 0, sizeof(rxb->skb->cb)); + netif_rx(skb); + rxb->skb = NULL; + } +} + +/* + * Main entry function for receiving a packet with 80211 headers. This + * should be called when ever the FW has notified us that there is a new + * skb in the receive queue. + */ +static void ipw_rx(struct ipw_priv *priv) +{ + struct ipw_rx_mem_buffer *rxb; + struct ipw_rx_packet *pkt; + struct libipw_hdr_4addr *header; + u32 r, w, i; + u8 network_packet; + u8 fill_rx = 0; + + r = ipw_read32(priv, IPW_RX_READ_INDEX); + w = ipw_read32(priv, IPW_RX_WRITE_INDEX); + i = priv->rxq->read; + + if (ipw_rx_queue_space (priv->rxq) > (RX_QUEUE_SIZE / 2)) + fill_rx = 1; + + while (i != r) { + rxb = priv->rxq->queue[i]; + if (unlikely(rxb == NULL)) { + printk(KERN_CRIT "Queue not allocated!\n"); + break; + } + priv->rxq->queue[i] = NULL; + + pci_dma_sync_single_for_cpu(priv->pci_dev, rxb->dma_addr, + IPW_RX_BUF_SIZE, + PCI_DMA_FROMDEVICE); + + pkt = (struct ipw_rx_packet *)rxb->skb->data; + IPW_DEBUG_RX("Packet: type=%02X seq=%02X bits=%02X\n", + pkt->header.message_type, + pkt->header.rx_seq_num, pkt->header.control_bits); + + switch (pkt->header.message_type) { + case RX_FRAME_TYPE: /* 802.11 frame */ { + struct libipw_rx_stats stats = { + .rssi = pkt->u.frame.rssi_dbm - + IPW_RSSI_TO_DBM, + .signal = + pkt->u.frame.rssi_dbm - + IPW_RSSI_TO_DBM + 0x100, + .noise = + le16_to_cpu(pkt->u.frame.noise), + .rate = pkt->u.frame.rate, + .mac_time = jiffies, + .received_channel = + pkt->u.frame.received_channel, + .freq = + (pkt->u.frame. + control & (1 << 0)) ? + LIBIPW_24GHZ_BAND : + LIBIPW_52GHZ_BAND, + .len = le16_to_cpu(pkt->u.frame.length), + }; + + if (stats.rssi != 0) + stats.mask |= LIBIPW_STATMASK_RSSI; + if (stats.signal != 0) + stats.mask |= LIBIPW_STATMASK_SIGNAL; + if (stats.noise != 0) + stats.mask |= LIBIPW_STATMASK_NOISE; + if (stats.rate != 0) + stats.mask |= LIBIPW_STATMASK_RATE; + + priv->rx_packets++; + +#ifdef CPTCFG_IPW2200_PROMISCUOUS + if (priv->prom_net_dev && netif_running(priv->prom_net_dev)) + ipw_handle_promiscuous_rx(priv, rxb, &stats); +#endif + +#ifdef CPTCFG_IPW2200_MONITOR + if (priv->ieee->iw_mode == IW_MODE_MONITOR) { +#ifdef CPTCFG_IPW2200_RADIOTAP + + ipw_handle_data_packet_monitor(priv, + rxb, + &stats); +#else + ipw_handle_data_packet(priv, rxb, + &stats); +#endif + break; + } +#endif + + header = + (struct libipw_hdr_4addr *)(rxb->skb-> + data + + IPW_RX_FRAME_SIZE); + /* TODO: Check Ad-Hoc dest/source and make sure + * that we are actually parsing these packets + * correctly -- we should probably use the + * frame control of the packet and disregard + * the current iw_mode */ + + network_packet = + is_network_packet(priv, header); + if (network_packet && priv->assoc_network) { + priv->assoc_network->stats.rssi = + stats.rssi; + priv->exp_avg_rssi = + exponential_average(priv->exp_avg_rssi, + stats.rssi, DEPTH_RSSI); + } + + IPW_DEBUG_RX("Frame: len=%u\n", + le16_to_cpu(pkt->u.frame.length)); + + if (le16_to_cpu(pkt->u.frame.length) < + libipw_get_hdrlen(le16_to_cpu( + header->frame_ctl))) { + IPW_DEBUG_DROP + ("Received packet is too small. " + "Dropping.\n"); + priv->net_dev->stats.rx_errors++; + priv->wstats.discard.misc++; + break; + } + + switch (WLAN_FC_GET_TYPE + (le16_to_cpu(header->frame_ctl))) { + + case IEEE80211_FTYPE_MGMT: + ipw_handle_mgmt_packet(priv, rxb, + &stats); + break; + + case IEEE80211_FTYPE_CTL: + break; + + case IEEE80211_FTYPE_DATA: + if (unlikely(!network_packet || + is_duplicate_packet(priv, + header))) + { + IPW_DEBUG_DROP("Dropping: " + "%pM, " + "%pM, " + "%pM\n", + header->addr1, + header->addr2, + header->addr3); + break; + } + + ipw_handle_data_packet(priv, rxb, + &stats); + + break; + } + break; + } + + case RX_HOST_NOTIFICATION_TYPE:{ + IPW_DEBUG_RX + ("Notification: subtype=%02X flags=%02X size=%d\n", + pkt->u.notification.subtype, + pkt->u.notification.flags, + le16_to_cpu(pkt->u.notification.size)); + ipw_rx_notification(priv, &pkt->u.notification); + break; + } + + default: + IPW_DEBUG_RX("Bad Rx packet of type %d\n", + pkt->header.message_type); + break; + } + + /* For now we just don't re-use anything. We can tweak this + * later to try and re-use notification packets and SKBs that + * fail to Rx correctly */ + if (rxb->skb != NULL) { + dev_kfree_skb_any(rxb->skb); + rxb->skb = NULL; + } + + pci_unmap_single(priv->pci_dev, rxb->dma_addr, + IPW_RX_BUF_SIZE, PCI_DMA_FROMDEVICE); + list_add_tail(&rxb->list, &priv->rxq->rx_used); + + i = (i + 1) % RX_QUEUE_SIZE; + + /* If there are a lot of unsued frames, restock the Rx queue + * so the ucode won't assert */ + if (fill_rx) { + priv->rxq->read = i; + ipw_rx_queue_replenish(priv); + } + } + + /* Backtrack one entry */ + priv->rxq->read = i; + ipw_rx_queue_restock(priv); +} + +#define DEFAULT_RTS_THRESHOLD 2304U +#define MIN_RTS_THRESHOLD 1U +#define MAX_RTS_THRESHOLD 2304U +#define DEFAULT_BEACON_INTERVAL 100U +#define DEFAULT_SHORT_RETRY_LIMIT 7U +#define DEFAULT_LONG_RETRY_LIMIT 4U + +/** + * ipw_sw_reset + * @option: options to control different reset behaviour + * 0 = reset everything except the 'disable' module_param + * 1 = reset everything and print out driver info (for probe only) + * 2 = reset everything + */ +static int ipw_sw_reset(struct ipw_priv *priv, int option) +{ + int band, modulation; + int old_mode = priv->ieee->iw_mode; + + /* Initialize module parameter values here */ + priv->config = 0; + + /* We default to disabling the LED code as right now it causes + * too many systems to lock up... */ + if (!led_support) + priv->config |= CFG_NO_LED; + + if (associate) + priv->config |= CFG_ASSOCIATE; + else + IPW_DEBUG_INFO("Auto associate disabled.\n"); + + if (auto_create) + priv->config |= CFG_ADHOC_CREATE; + else + IPW_DEBUG_INFO("Auto adhoc creation disabled.\n"); + + priv->config &= ~CFG_STATIC_ESSID; + priv->essid_len = 0; + memset(priv->essid, 0, IW_ESSID_MAX_SIZE); + + if (disable && option) { + priv->status |= STATUS_RF_KILL_SW; + IPW_DEBUG_INFO("Radio disabled.\n"); + } + + if (default_channel != 0) { + priv->config |= CFG_STATIC_CHANNEL; + priv->channel = default_channel; + IPW_DEBUG_INFO("Bind to static channel %d\n", default_channel); + /* TODO: Validate that provided channel is in range */ + } +#ifdef CPTCFG_IPW2200_QOS + ipw_qos_init(priv, qos_enable, qos_burst_enable, + burst_duration_CCK, burst_duration_OFDM); +#endif /* CPTCFG_IPW2200_QOS */ + + switch (network_mode) { + case 1: + priv->ieee->iw_mode = IW_MODE_ADHOC; + priv->net_dev->type = ARPHRD_ETHER; + + break; +#ifdef CPTCFG_IPW2200_MONITOR + case 2: + priv->ieee->iw_mode = IW_MODE_MONITOR; +#ifdef CPTCFG_IPW2200_RADIOTAP + priv->net_dev->type = ARPHRD_IEEE80211_RADIOTAP; +#else + priv->net_dev->type = ARPHRD_IEEE80211; +#endif + break; +#endif + default: + case 0: + priv->net_dev->type = ARPHRD_ETHER; + priv->ieee->iw_mode = IW_MODE_INFRA; + break; + } + + if (hwcrypto) { + priv->ieee->host_encrypt = 0; + priv->ieee->host_encrypt_msdu = 0; + priv->ieee->host_decrypt = 0; + priv->ieee->host_mc_decrypt = 0; + } + IPW_DEBUG_INFO("Hardware crypto [%s]\n", hwcrypto ? "on" : "off"); + + /* IPW2200/2915 is abled to do hardware fragmentation. */ + priv->ieee->host_open_frag = 0; + + if ((priv->pci_dev->device == 0x4223) || + (priv->pci_dev->device == 0x4224)) { + if (option == 1) + printk(KERN_INFO DRV_NAME + ": Detected Intel PRO/Wireless 2915ABG Network " + "Connection\n"); + priv->ieee->abg_true = 1; + band = LIBIPW_52GHZ_BAND | LIBIPW_24GHZ_BAND; + modulation = LIBIPW_OFDM_MODULATION | + LIBIPW_CCK_MODULATION; + priv->adapter = IPW_2915ABG; + priv->ieee->mode = IEEE_A | IEEE_G | IEEE_B; + } else { + if (option == 1) + printk(KERN_INFO DRV_NAME + ": Detected Intel PRO/Wireless 2200BG Network " + "Connection\n"); + + priv->ieee->abg_true = 0; + band = LIBIPW_24GHZ_BAND; + modulation = LIBIPW_OFDM_MODULATION | + LIBIPW_CCK_MODULATION; + priv->adapter = IPW_2200BG; + priv->ieee->mode = IEEE_G | IEEE_B; + } + + priv->ieee->freq_band = band; + priv->ieee->modulation = modulation; + + priv->rates_mask = LIBIPW_DEFAULT_RATES_MASK; + + priv->disassociate_threshold = IPW_MB_DISASSOCIATE_THRESHOLD_DEFAULT; + priv->roaming_threshold = IPW_MB_ROAMING_THRESHOLD_DEFAULT; + + priv->rts_threshold = DEFAULT_RTS_THRESHOLD; + priv->short_retry_limit = DEFAULT_SHORT_RETRY_LIMIT; + priv->long_retry_limit = DEFAULT_LONG_RETRY_LIMIT; + + /* If power management is turned on, default to AC mode */ + priv->power_mode = IPW_POWER_AC; + priv->tx_power = IPW_TX_POWER_DEFAULT; + + return old_mode == priv->ieee->iw_mode; +} + +/* + * This file defines the Wireless Extension handlers. It does not + * define any methods of hardware manipulation and relies on the + * functions defined in ipw_main to provide the HW interaction. + * + * The exception to this is the use of the ipw_get_ordinal() + * function used to poll the hardware vs. making unnecessary calls. + * + */ + +static int ipw_set_channel(struct ipw_priv *priv, u8 channel) +{ + if (channel == 0) { + IPW_DEBUG_INFO("Setting channel to ANY (0)\n"); + priv->config &= ~CFG_STATIC_CHANNEL; + IPW_DEBUG_ASSOC("Attempting to associate with new " + "parameters.\n"); + ipw_associate(priv); + return 0; + } + + priv->config |= CFG_STATIC_CHANNEL; + + if (priv->channel == channel) { + IPW_DEBUG_INFO("Request to set channel to current value (%d)\n", + channel); + return 0; + } + + IPW_DEBUG_INFO("Setting channel to %i\n", (int)channel); + priv->channel = channel; + +#ifdef CPTCFG_IPW2200_MONITOR + if (priv->ieee->iw_mode == IW_MODE_MONITOR) { + int i; + if (priv->status & STATUS_SCANNING) { + IPW_DEBUG_SCAN("Scan abort triggered due to " + "channel change.\n"); + ipw_abort_scan(priv); + } + + for (i = 1000; i && (priv->status & STATUS_SCANNING); i--) + udelay(10); + + if (priv->status & STATUS_SCANNING) + IPW_DEBUG_SCAN("Still scanning...\n"); + else + IPW_DEBUG_SCAN("Took %dms to abort current scan\n", + 1000 - i); + + return 0; + } +#endif /* CPTCFG_IPW2200_MONITOR */ + + /* Network configuration changed -- force [re]association */ + IPW_DEBUG_ASSOC("[re]association triggered due to channel change.\n"); + if (!ipw_disassociate(priv)) + ipw_associate(priv); + + return 0; +} + +static int ipw_wx_set_freq(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + const struct libipw_geo *geo = libipw_get_geo(priv->ieee); + struct iw_freq *fwrq = &wrqu->freq; + int ret = 0, i; + u8 channel, flags; + int band; + + if (fwrq->m == 0) { + IPW_DEBUG_WX("SET Freq/Channel -> any\n"); + mutex_lock(&priv->mutex); + ret = ipw_set_channel(priv, 0); + mutex_unlock(&priv->mutex); + return ret; + } + /* if setting by freq convert to channel */ + if (fwrq->e == 1) { + channel = libipw_freq_to_channel(priv->ieee, fwrq->m); + if (channel == 0) + return -EINVAL; + } else + channel = fwrq->m; + + if (!(band = libipw_is_valid_channel(priv->ieee, channel))) + return -EINVAL; + + if (priv->ieee->iw_mode == IW_MODE_ADHOC) { + i = libipw_channel_to_index(priv->ieee, channel); + if (i == -1) + return -EINVAL; + + flags = (band == LIBIPW_24GHZ_BAND) ? + geo->bg[i].flags : geo->a[i].flags; + if (flags & LIBIPW_CH_PASSIVE_ONLY) { + IPW_DEBUG_WX("Invalid Ad-Hoc channel for 802.11a\n"); + return -EINVAL; + } + } + + IPW_DEBUG_WX("SET Freq/Channel -> %d\n", fwrq->m); + mutex_lock(&priv->mutex); + ret = ipw_set_channel(priv, channel); + mutex_unlock(&priv->mutex); + return ret; +} + +static int ipw_wx_get_freq(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + + wrqu->freq.e = 0; + + /* If we are associated, trying to associate, or have a statically + * configured CHANNEL then return that; otherwise return ANY */ + mutex_lock(&priv->mutex); + if (priv->config & CFG_STATIC_CHANNEL || + priv->status & (STATUS_ASSOCIATING | STATUS_ASSOCIATED)) { + int i; + + i = libipw_channel_to_index(priv->ieee, priv->channel); + BUG_ON(i == -1); + wrqu->freq.e = 1; + + switch (libipw_is_valid_channel(priv->ieee, priv->channel)) { + case LIBIPW_52GHZ_BAND: + wrqu->freq.m = priv->ieee->geo.a[i].freq * 100000; + break; + + case LIBIPW_24GHZ_BAND: + wrqu->freq.m = priv->ieee->geo.bg[i].freq * 100000; + break; + + default: + BUG(); + } + } else + wrqu->freq.m = 0; + + mutex_unlock(&priv->mutex); + IPW_DEBUG_WX("GET Freq/Channel -> %d\n", priv->channel); + return 0; +} + +static int ipw_wx_set_mode(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + int err = 0; + + IPW_DEBUG_WX("Set MODE: %d\n", wrqu->mode); + + switch (wrqu->mode) { +#ifdef CPTCFG_IPW2200_MONITOR + case IW_MODE_MONITOR: +#endif + case IW_MODE_ADHOC: + case IW_MODE_INFRA: + break; + case IW_MODE_AUTO: + wrqu->mode = IW_MODE_INFRA; + break; + default: + return -EINVAL; + } + if (wrqu->mode == priv->ieee->iw_mode) + return 0; + + mutex_lock(&priv->mutex); + + ipw_sw_reset(priv, 0); + +#ifdef CPTCFG_IPW2200_MONITOR + if (priv->ieee->iw_mode == IW_MODE_MONITOR) + priv->net_dev->type = ARPHRD_ETHER; + + if (wrqu->mode == IW_MODE_MONITOR) +#ifdef CPTCFG_IPW2200_RADIOTAP + priv->net_dev->type = ARPHRD_IEEE80211_RADIOTAP; +#else + priv->net_dev->type = ARPHRD_IEEE80211; +#endif +#endif /* CPTCFG_IPW2200_MONITOR */ + + /* Free the existing firmware and reset the fw_loaded + * flag so ipw_load() will bring in the new firmware */ + free_firmware(); + + priv->ieee->iw_mode = wrqu->mode; + + schedule_work(&priv->adapter_restart); + mutex_unlock(&priv->mutex); + return err; +} + +static int ipw_wx_get_mode(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + mutex_lock(&priv->mutex); + wrqu->mode = priv->ieee->iw_mode; + IPW_DEBUG_WX("Get MODE -> %d\n", wrqu->mode); + mutex_unlock(&priv->mutex); + return 0; +} + +/* Values are in microsecond */ +static const s32 timeout_duration[] = { + 350000, + 250000, + 75000, + 37000, + 25000, +}; + +static const s32 period_duration[] = { + 400000, + 700000, + 1000000, + 1000000, + 1000000 +}; + +static int ipw_wx_get_range(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + struct iw_range *range = (struct iw_range *)extra; + const struct libipw_geo *geo = libipw_get_geo(priv->ieee); + int i = 0, j; + + wrqu->data.length = sizeof(*range); + memset(range, 0, sizeof(*range)); + + /* 54Mbs == ~27 Mb/s real (802.11g) */ + range->throughput = 27 * 1000 * 1000; + + range->max_qual.qual = 100; + /* TODO: Find real max RSSI and stick here */ + range->max_qual.level = 0; + range->max_qual.noise = 0; + range->max_qual.updated = 7; /* Updated all three */ + + range->avg_qual.qual = 70; + /* TODO: Find real 'good' to 'bad' threshold value for RSSI */ + range->avg_qual.level = 0; /* FIXME to real average level */ + range->avg_qual.noise = 0; + range->avg_qual.updated = 7; /* Updated all three */ + mutex_lock(&priv->mutex); + range->num_bitrates = min(priv->rates.num_rates, (u8) IW_MAX_BITRATES); + + for (i = 0; i < range->num_bitrates; i++) + range->bitrate[i] = (priv->rates.supported_rates[i] & 0x7F) * + 500000; + + range->max_rts = DEFAULT_RTS_THRESHOLD; + range->min_frag = MIN_FRAG_THRESHOLD; + range->max_frag = MAX_FRAG_THRESHOLD; + + range->encoding_size[0] = 5; + range->encoding_size[1] = 13; + range->num_encoding_sizes = 2; + range->max_encoding_tokens = WEP_KEYS; + + /* Set the Wireless Extension versions */ + range->we_version_compiled = WIRELESS_EXT; + range->we_version_source = 18; + + i = 0; + if (priv->ieee->mode & (IEEE_B | IEEE_G)) { + for (j = 0; j < geo->bg_channels && i < IW_MAX_FREQUENCIES; j++) { + if ((priv->ieee->iw_mode == IW_MODE_ADHOC) && + (geo->bg[j].flags & LIBIPW_CH_PASSIVE_ONLY)) + continue; + + range->freq[i].i = geo->bg[j].channel; + range->freq[i].m = geo->bg[j].freq * 100000; + range->freq[i].e = 1; + i++; + } + } + + if (priv->ieee->mode & IEEE_A) { + for (j = 0; j < geo->a_channels && i < IW_MAX_FREQUENCIES; j++) { + if ((priv->ieee->iw_mode == IW_MODE_ADHOC) && + (geo->a[j].flags & LIBIPW_CH_PASSIVE_ONLY)) + continue; + + range->freq[i].i = geo->a[j].channel; + range->freq[i].m = geo->a[j].freq * 100000; + range->freq[i].e = 1; + i++; + } + } + + range->num_channels = i; + range->num_frequency = i; + + mutex_unlock(&priv->mutex); + + /* Event capability (kernel + driver) */ + range->event_capa[0] = (IW_EVENT_CAPA_K_0 | + IW_EVENT_CAPA_MASK(SIOCGIWTHRSPY) | + IW_EVENT_CAPA_MASK(SIOCGIWAP) | + IW_EVENT_CAPA_MASK(SIOCGIWSCAN)); + range->event_capa[1] = IW_EVENT_CAPA_K_1; + + range->enc_capa = IW_ENC_CAPA_WPA | IW_ENC_CAPA_WPA2 | + IW_ENC_CAPA_CIPHER_TKIP | IW_ENC_CAPA_CIPHER_CCMP; + + range->scan_capa = IW_SCAN_CAPA_ESSID | IW_SCAN_CAPA_TYPE; + + IPW_DEBUG_WX("GET Range\n"); + return 0; +} + +static int ipw_wx_set_wap(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + + if (wrqu->ap_addr.sa_family != ARPHRD_ETHER) + return -EINVAL; + mutex_lock(&priv->mutex); + if (is_broadcast_ether_addr(wrqu->ap_addr.sa_data) || + is_zero_ether_addr(wrqu->ap_addr.sa_data)) { + /* we disable mandatory BSSID association */ + IPW_DEBUG_WX("Setting AP BSSID to ANY\n"); + priv->config &= ~CFG_STATIC_BSSID; + IPW_DEBUG_ASSOC("Attempting to associate with new " + "parameters.\n"); + ipw_associate(priv); + mutex_unlock(&priv->mutex); + return 0; + } + + priv->config |= CFG_STATIC_BSSID; + if (!memcmp(priv->bssid, wrqu->ap_addr.sa_data, ETH_ALEN)) { + IPW_DEBUG_WX("BSSID set to current BSSID.\n"); + mutex_unlock(&priv->mutex); + return 0; + } + + IPW_DEBUG_WX("Setting mandatory BSSID to %pM\n", + wrqu->ap_addr.sa_data); + + memcpy(priv->bssid, wrqu->ap_addr.sa_data, ETH_ALEN); + + /* Network configuration changed -- force [re]association */ + IPW_DEBUG_ASSOC("[re]association triggered due to BSSID change.\n"); + if (!ipw_disassociate(priv)) + ipw_associate(priv); + + mutex_unlock(&priv->mutex); + return 0; +} + +static int ipw_wx_get_wap(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + + /* If we are associated, trying to associate, or have a statically + * configured BSSID then return that; otherwise return ANY */ + mutex_lock(&priv->mutex); + if (priv->config & CFG_STATIC_BSSID || + priv->status & (STATUS_ASSOCIATED | STATUS_ASSOCIATING)) { + wrqu->ap_addr.sa_family = ARPHRD_ETHER; + memcpy(wrqu->ap_addr.sa_data, priv->bssid, ETH_ALEN); + } else + memset(wrqu->ap_addr.sa_data, 0, ETH_ALEN); + + IPW_DEBUG_WX("Getting WAP BSSID: %pM\n", + wrqu->ap_addr.sa_data); + mutex_unlock(&priv->mutex); + return 0; +} + +static int ipw_wx_set_essid(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + int length; + DECLARE_SSID_BUF(ssid); + + mutex_lock(&priv->mutex); + + if (!wrqu->essid.flags) + { + IPW_DEBUG_WX("Setting ESSID to ANY\n"); + ipw_disassociate(priv); + priv->config &= ~CFG_STATIC_ESSID; + ipw_associate(priv); + mutex_unlock(&priv->mutex); + return 0; + } + + length = min((int)wrqu->essid.length, IW_ESSID_MAX_SIZE); + + priv->config |= CFG_STATIC_ESSID; + + if (priv->essid_len == length && !memcmp(priv->essid, extra, length) + && (priv->status & (STATUS_ASSOCIATED | STATUS_ASSOCIATING))) { + IPW_DEBUG_WX("ESSID set to current ESSID.\n"); + mutex_unlock(&priv->mutex); + return 0; + } + + IPW_DEBUG_WX("Setting ESSID: '%s' (%d)\n", + print_ssid(ssid, extra, length), length); + + priv->essid_len = length; + memcpy(priv->essid, extra, priv->essid_len); + + /* Network configuration changed -- force [re]association */ + IPW_DEBUG_ASSOC("[re]association triggered due to ESSID change.\n"); + if (!ipw_disassociate(priv)) + ipw_associate(priv); + + mutex_unlock(&priv->mutex); + return 0; +} + +static int ipw_wx_get_essid(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + DECLARE_SSID_BUF(ssid); + + /* If we are associated, trying to associate, or have a statically + * configured ESSID then return that; otherwise return ANY */ + mutex_lock(&priv->mutex); + if (priv->config & CFG_STATIC_ESSID || + priv->status & (STATUS_ASSOCIATED | STATUS_ASSOCIATING)) { + IPW_DEBUG_WX("Getting essid: '%s'\n", + print_ssid(ssid, priv->essid, priv->essid_len)); + memcpy(extra, priv->essid, priv->essid_len); + wrqu->essid.length = priv->essid_len; + wrqu->essid.flags = 1; /* active */ + } else { + IPW_DEBUG_WX("Getting essid: ANY\n"); + wrqu->essid.length = 0; + wrqu->essid.flags = 0; /* active */ + } + mutex_unlock(&priv->mutex); + return 0; +} + +static int ipw_wx_set_nick(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + + IPW_DEBUG_WX("Setting nick to '%s'\n", extra); + if (wrqu->data.length > IW_ESSID_MAX_SIZE) + return -E2BIG; + mutex_lock(&priv->mutex); + wrqu->data.length = min((size_t) wrqu->data.length, sizeof(priv->nick)); + memset(priv->nick, 0, sizeof(priv->nick)); + memcpy(priv->nick, extra, wrqu->data.length); + IPW_DEBUG_TRACE("<<\n"); + mutex_unlock(&priv->mutex); + return 0; + +} + +static int ipw_wx_get_nick(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + IPW_DEBUG_WX("Getting nick\n"); + mutex_lock(&priv->mutex); + wrqu->data.length = strlen(priv->nick); + memcpy(extra, priv->nick, wrqu->data.length); + wrqu->data.flags = 1; /* active */ + mutex_unlock(&priv->mutex); + return 0; +} + +static int ipw_wx_set_sens(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + int err = 0; + + IPW_DEBUG_WX("Setting roaming threshold to %d\n", wrqu->sens.value); + IPW_DEBUG_WX("Setting disassociate threshold to %d\n", 3*wrqu->sens.value); + mutex_lock(&priv->mutex); + + if (wrqu->sens.fixed == 0) + { + priv->roaming_threshold = IPW_MB_ROAMING_THRESHOLD_DEFAULT; + priv->disassociate_threshold = IPW_MB_DISASSOCIATE_THRESHOLD_DEFAULT; + goto out; + } + if ((wrqu->sens.value > IPW_MB_ROAMING_THRESHOLD_MAX) || + (wrqu->sens.value < IPW_MB_ROAMING_THRESHOLD_MIN)) { + err = -EINVAL; + goto out; + } + + priv->roaming_threshold = wrqu->sens.value; + priv->disassociate_threshold = 3*wrqu->sens.value; + out: + mutex_unlock(&priv->mutex); + return err; +} + +static int ipw_wx_get_sens(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + mutex_lock(&priv->mutex); + wrqu->sens.fixed = 1; + wrqu->sens.value = priv->roaming_threshold; + mutex_unlock(&priv->mutex); + + IPW_DEBUG_WX("GET roaming threshold -> %s %d\n", + wrqu->power.disabled ? "OFF" : "ON", wrqu->power.value); + + return 0; +} + +static int ipw_wx_set_rate(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + /* TODO: We should use semaphores or locks for access to priv */ + struct ipw_priv *priv = libipw_priv(dev); + u32 target_rate = wrqu->bitrate.value; + u32 fixed, mask; + + /* value = -1, fixed = 0 means auto only, so we should use all rates offered by AP */ + /* value = X, fixed = 1 means only rate X */ + /* value = X, fixed = 0 means all rates lower equal X */ + + if (target_rate == -1) { + fixed = 0; + mask = LIBIPW_DEFAULT_RATES_MASK; + /* Now we should reassociate */ + goto apply; + } + + mask = 0; + fixed = wrqu->bitrate.fixed; + + if (target_rate == 1000000 || !fixed) + mask |= LIBIPW_CCK_RATE_1MB_MASK; + if (target_rate == 1000000) + goto apply; + + if (target_rate == 2000000 || !fixed) + mask |= LIBIPW_CCK_RATE_2MB_MASK; + if (target_rate == 2000000) + goto apply; + + if (target_rate == 5500000 || !fixed) + mask |= LIBIPW_CCK_RATE_5MB_MASK; + if (target_rate == 5500000) + goto apply; + + if (target_rate == 6000000 || !fixed) + mask |= LIBIPW_OFDM_RATE_6MB_MASK; + if (target_rate == 6000000) + goto apply; + + if (target_rate == 9000000 || !fixed) + mask |= LIBIPW_OFDM_RATE_9MB_MASK; + if (target_rate == 9000000) + goto apply; + + if (target_rate == 11000000 || !fixed) + mask |= LIBIPW_CCK_RATE_11MB_MASK; + if (target_rate == 11000000) + goto apply; + + if (target_rate == 12000000 || !fixed) + mask |= LIBIPW_OFDM_RATE_12MB_MASK; + if (target_rate == 12000000) + goto apply; + + if (target_rate == 18000000 || !fixed) + mask |= LIBIPW_OFDM_RATE_18MB_MASK; + if (target_rate == 18000000) + goto apply; + + if (target_rate == 24000000 || !fixed) + mask |= LIBIPW_OFDM_RATE_24MB_MASK; + if (target_rate == 24000000) + goto apply; + + if (target_rate == 36000000 || !fixed) + mask |= LIBIPW_OFDM_RATE_36MB_MASK; + if (target_rate == 36000000) + goto apply; + + if (target_rate == 48000000 || !fixed) + mask |= LIBIPW_OFDM_RATE_48MB_MASK; + if (target_rate == 48000000) + goto apply; + + if (target_rate == 54000000 || !fixed) + mask |= LIBIPW_OFDM_RATE_54MB_MASK; + if (target_rate == 54000000) + goto apply; + + IPW_DEBUG_WX("invalid rate specified, returning error\n"); + return -EINVAL; + + apply: + IPW_DEBUG_WX("Setting rate mask to 0x%08X [%s]\n", + mask, fixed ? "fixed" : "sub-rates"); + mutex_lock(&priv->mutex); + if (mask == LIBIPW_DEFAULT_RATES_MASK) { + priv->config &= ~CFG_FIXED_RATE; + ipw_set_fixed_rate(priv, priv->ieee->mode); + } else + priv->config |= CFG_FIXED_RATE; + + if (priv->rates_mask == mask) { + IPW_DEBUG_WX("Mask set to current mask.\n"); + mutex_unlock(&priv->mutex); + return 0; + } + + priv->rates_mask = mask; + + /* Network configuration changed -- force [re]association */ + IPW_DEBUG_ASSOC("[re]association triggered due to rates change.\n"); + if (!ipw_disassociate(priv)) + ipw_associate(priv); + + mutex_unlock(&priv->mutex); + return 0; +} + +static int ipw_wx_get_rate(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + mutex_lock(&priv->mutex); + wrqu->bitrate.value = priv->last_rate; + wrqu->bitrate.fixed = (priv->config & CFG_FIXED_RATE) ? 1 : 0; + mutex_unlock(&priv->mutex); + IPW_DEBUG_WX("GET Rate -> %d\n", wrqu->bitrate.value); + return 0; +} + +static int ipw_wx_set_rts(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + mutex_lock(&priv->mutex); + if (wrqu->rts.disabled || !wrqu->rts.fixed) + priv->rts_threshold = DEFAULT_RTS_THRESHOLD; + else { + if (wrqu->rts.value < MIN_RTS_THRESHOLD || + wrqu->rts.value > MAX_RTS_THRESHOLD) { + mutex_unlock(&priv->mutex); + return -EINVAL; + } + priv->rts_threshold = wrqu->rts.value; + } + + ipw_send_rts_threshold(priv, priv->rts_threshold); + mutex_unlock(&priv->mutex); + IPW_DEBUG_WX("SET RTS Threshold -> %d\n", priv->rts_threshold); + return 0; +} + +static int ipw_wx_get_rts(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + mutex_lock(&priv->mutex); + wrqu->rts.value = priv->rts_threshold; + wrqu->rts.fixed = 0; /* no auto select */ + wrqu->rts.disabled = (wrqu->rts.value == DEFAULT_RTS_THRESHOLD); + mutex_unlock(&priv->mutex); + IPW_DEBUG_WX("GET RTS Threshold -> %d\n", wrqu->rts.value); + return 0; +} + +static int ipw_wx_set_txpow(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + int err = 0; + + mutex_lock(&priv->mutex); + if (ipw_radio_kill_sw(priv, wrqu->power.disabled)) { + err = -EINPROGRESS; + goto out; + } + + if (!wrqu->power.fixed) + wrqu->power.value = IPW_TX_POWER_DEFAULT; + + if (wrqu->power.flags != IW_TXPOW_DBM) { + err = -EINVAL; + goto out; + } + + if ((wrqu->power.value > IPW_TX_POWER_MAX) || + (wrqu->power.value < IPW_TX_POWER_MIN)) { + err = -EINVAL; + goto out; + } + + priv->tx_power = wrqu->power.value; + err = ipw_set_tx_power(priv); + out: + mutex_unlock(&priv->mutex); + return err; +} + +static int ipw_wx_get_txpow(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + mutex_lock(&priv->mutex); + wrqu->power.value = priv->tx_power; + wrqu->power.fixed = 1; + wrqu->power.flags = IW_TXPOW_DBM; + wrqu->power.disabled = (priv->status & STATUS_RF_KILL_MASK) ? 1 : 0; + mutex_unlock(&priv->mutex); + + IPW_DEBUG_WX("GET TX Power -> %s %d\n", + wrqu->power.disabled ? "OFF" : "ON", wrqu->power.value); + + return 0; +} + +static int ipw_wx_set_frag(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + mutex_lock(&priv->mutex); + if (wrqu->frag.disabled || !wrqu->frag.fixed) + priv->ieee->fts = DEFAULT_FTS; + else { + if (wrqu->frag.value < MIN_FRAG_THRESHOLD || + wrqu->frag.value > MAX_FRAG_THRESHOLD) { + mutex_unlock(&priv->mutex); + return -EINVAL; + } + + priv->ieee->fts = wrqu->frag.value & ~0x1; + } + + ipw_send_frag_threshold(priv, wrqu->frag.value); + mutex_unlock(&priv->mutex); + IPW_DEBUG_WX("SET Frag Threshold -> %d\n", wrqu->frag.value); + return 0; +} + +static int ipw_wx_get_frag(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + mutex_lock(&priv->mutex); + wrqu->frag.value = priv->ieee->fts; + wrqu->frag.fixed = 0; /* no auto select */ + wrqu->frag.disabled = (wrqu->frag.value == DEFAULT_FTS); + mutex_unlock(&priv->mutex); + IPW_DEBUG_WX("GET Frag Threshold -> %d\n", wrqu->frag.value); + + return 0; +} + +static int ipw_wx_set_retry(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + + if (wrqu->retry.flags & IW_RETRY_LIFETIME || wrqu->retry.disabled) + return -EINVAL; + + if (!(wrqu->retry.flags & IW_RETRY_LIMIT)) + return 0; + + if (wrqu->retry.value < 0 || wrqu->retry.value >= 255) + return -EINVAL; + + mutex_lock(&priv->mutex); + if (wrqu->retry.flags & IW_RETRY_SHORT) + priv->short_retry_limit = (u8) wrqu->retry.value; + else if (wrqu->retry.flags & IW_RETRY_LONG) + priv->long_retry_limit = (u8) wrqu->retry.value; + else { + priv->short_retry_limit = (u8) wrqu->retry.value; + priv->long_retry_limit = (u8) wrqu->retry.value; + } + + ipw_send_retry_limit(priv, priv->short_retry_limit, + priv->long_retry_limit); + mutex_unlock(&priv->mutex); + IPW_DEBUG_WX("SET retry limit -> short:%d long:%d\n", + priv->short_retry_limit, priv->long_retry_limit); + return 0; +} + +static int ipw_wx_get_retry(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + + mutex_lock(&priv->mutex); + wrqu->retry.disabled = 0; + + if ((wrqu->retry.flags & IW_RETRY_TYPE) == IW_RETRY_LIFETIME) { + mutex_unlock(&priv->mutex); + return -EINVAL; + } + + if (wrqu->retry.flags & IW_RETRY_LONG) { + wrqu->retry.flags = IW_RETRY_LIMIT | IW_RETRY_LONG; + wrqu->retry.value = priv->long_retry_limit; + } else if (wrqu->retry.flags & IW_RETRY_SHORT) { + wrqu->retry.flags = IW_RETRY_LIMIT | IW_RETRY_SHORT; + wrqu->retry.value = priv->short_retry_limit; + } else { + wrqu->retry.flags = IW_RETRY_LIMIT; + wrqu->retry.value = priv->short_retry_limit; + } + mutex_unlock(&priv->mutex); + + IPW_DEBUG_WX("GET retry -> %d\n", wrqu->retry.value); + + return 0; +} + +static int ipw_wx_set_scan(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + struct iw_scan_req *req = (struct iw_scan_req *)extra; + struct delayed_work *work = NULL; + + mutex_lock(&priv->mutex); + + priv->user_requested_scan = 1; + + if (wrqu->data.length == sizeof(struct iw_scan_req)) { + if (wrqu->data.flags & IW_SCAN_THIS_ESSID) { + int len = min((int)req->essid_len, + (int)sizeof(priv->direct_scan_ssid)); + memcpy(priv->direct_scan_ssid, req->essid, len); + priv->direct_scan_ssid_len = len; + work = &priv->request_direct_scan; + } else if (req->scan_type == IW_SCAN_TYPE_PASSIVE) { + work = &priv->request_passive_scan; + } + } else { + /* Normal active broadcast scan */ + work = &priv->request_scan; + } + + mutex_unlock(&priv->mutex); + + IPW_DEBUG_WX("Start scan\n"); + + schedule_delayed_work(work, 0); + + return 0; +} + +static int ipw_wx_get_scan(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + return libipw_wx_get_scan(priv->ieee, info, wrqu, extra); +} + +static int ipw_wx_set_encode(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *key) +{ + struct ipw_priv *priv = libipw_priv(dev); + int ret; + u32 cap = priv->capability; + + mutex_lock(&priv->mutex); + ret = libipw_wx_set_encode(priv->ieee, info, wrqu, key); + + /* In IBSS mode, we need to notify the firmware to update + * the beacon info after we changed the capability. */ + if (cap != priv->capability && + priv->ieee->iw_mode == IW_MODE_ADHOC && + priv->status & STATUS_ASSOCIATED) + ipw_disassociate(priv); + + mutex_unlock(&priv->mutex); + return ret; +} + +static int ipw_wx_get_encode(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *key) +{ + struct ipw_priv *priv = libipw_priv(dev); + return libipw_wx_get_encode(priv->ieee, info, wrqu, key); +} + +static int ipw_wx_set_power(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + int err; + mutex_lock(&priv->mutex); + if (wrqu->power.disabled) { + priv->power_mode = IPW_POWER_LEVEL(priv->power_mode); + err = ipw_send_power_mode(priv, IPW_POWER_MODE_CAM); + if (err) { + IPW_DEBUG_WX("failed setting power mode.\n"); + mutex_unlock(&priv->mutex); + return err; + } + IPW_DEBUG_WX("SET Power Management Mode -> off\n"); + mutex_unlock(&priv->mutex); + return 0; + } + + switch (wrqu->power.flags & IW_POWER_MODE) { + case IW_POWER_ON: /* If not specified */ + case IW_POWER_MODE: /* If set all mask */ + case IW_POWER_ALL_R: /* If explicitly state all */ + break; + default: /* Otherwise we don't support it */ + IPW_DEBUG_WX("SET PM Mode: %X not supported.\n", + wrqu->power.flags); + mutex_unlock(&priv->mutex); + return -EOPNOTSUPP; + } + + /* If the user hasn't specified a power management mode yet, default + * to BATTERY */ + if (IPW_POWER_LEVEL(priv->power_mode) == IPW_POWER_AC) + priv->power_mode = IPW_POWER_ENABLED | IPW_POWER_BATTERY; + else + priv->power_mode = IPW_POWER_ENABLED | priv->power_mode; + + err = ipw_send_power_mode(priv, IPW_POWER_LEVEL(priv->power_mode)); + if (err) { + IPW_DEBUG_WX("failed setting power mode.\n"); + mutex_unlock(&priv->mutex); + return err; + } + + IPW_DEBUG_WX("SET Power Management Mode -> 0x%02X\n", priv->power_mode); + mutex_unlock(&priv->mutex); + return 0; +} + +static int ipw_wx_get_power(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + mutex_lock(&priv->mutex); + if (!(priv->power_mode & IPW_POWER_ENABLED)) + wrqu->power.disabled = 1; + else + wrqu->power.disabled = 0; + + mutex_unlock(&priv->mutex); + IPW_DEBUG_WX("GET Power Management Mode -> %02X\n", priv->power_mode); + + return 0; +} + +static int ipw_wx_set_powermode(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + int mode = *(int *)extra; + int err; + + mutex_lock(&priv->mutex); + if ((mode < 1) || (mode > IPW_POWER_LIMIT)) + mode = IPW_POWER_AC; + + if (IPW_POWER_LEVEL(priv->power_mode) != mode) { + err = ipw_send_power_mode(priv, mode); + if (err) { + IPW_DEBUG_WX("failed setting power mode.\n"); + mutex_unlock(&priv->mutex); + return err; + } + priv->power_mode = IPW_POWER_ENABLED | mode; + } + mutex_unlock(&priv->mutex); + return 0; +} + +#define MAX_WX_STRING 80 +static int ipw_wx_get_powermode(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + int level = IPW_POWER_LEVEL(priv->power_mode); + char *p = extra; + + p += snprintf(p, MAX_WX_STRING, "Power save level: %d ", level); + + switch (level) { + case IPW_POWER_AC: + p += snprintf(p, MAX_WX_STRING - (p - extra), "(AC)"); + break; + case IPW_POWER_BATTERY: + p += snprintf(p, MAX_WX_STRING - (p - extra), "(BATTERY)"); + break; + default: + p += snprintf(p, MAX_WX_STRING - (p - extra), + "(Timeout %dms, Period %dms)", + timeout_duration[level - 1] / 1000, + period_duration[level - 1] / 1000); + } + + if (!(priv->power_mode & IPW_POWER_ENABLED)) + p += snprintf(p, MAX_WX_STRING - (p - extra), " OFF"); + + wrqu->data.length = p - extra + 1; + + return 0; +} + +static int ipw_wx_set_wireless_mode(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + int mode = *(int *)extra; + u8 band = 0, modulation = 0; + + if (mode == 0 || mode & ~IEEE_MODE_MASK) { + IPW_WARNING("Attempt to set invalid wireless mode: %d\n", mode); + return -EINVAL; + } + mutex_lock(&priv->mutex); + if (priv->adapter == IPW_2915ABG) { + priv->ieee->abg_true = 1; + if (mode & IEEE_A) { + band |= LIBIPW_52GHZ_BAND; + modulation |= LIBIPW_OFDM_MODULATION; + } else + priv->ieee->abg_true = 0; + } else { + if (mode & IEEE_A) { + IPW_WARNING("Attempt to set 2200BG into " + "802.11a mode\n"); + mutex_unlock(&priv->mutex); + return -EINVAL; + } + + priv->ieee->abg_true = 0; + } + + if (mode & IEEE_B) { + band |= LIBIPW_24GHZ_BAND; + modulation |= LIBIPW_CCK_MODULATION; + } else + priv->ieee->abg_true = 0; + + if (mode & IEEE_G) { + band |= LIBIPW_24GHZ_BAND; + modulation |= LIBIPW_OFDM_MODULATION; + } else + priv->ieee->abg_true = 0; + + priv->ieee->mode = mode; + priv->ieee->freq_band = band; + priv->ieee->modulation = modulation; + init_supported_rates(priv, &priv->rates); + + /* Network configuration changed -- force [re]association */ + IPW_DEBUG_ASSOC("[re]association triggered due to mode change.\n"); + if (!ipw_disassociate(priv)) { + ipw_send_supported_rates(priv, &priv->rates); + ipw_associate(priv); + } + + /* Update the band LEDs */ + ipw_led_band_on(priv); + + IPW_DEBUG_WX("PRIV SET MODE: %c%c%c\n", + mode & IEEE_A ? 'a' : '.', + mode & IEEE_B ? 'b' : '.', mode & IEEE_G ? 'g' : '.'); + mutex_unlock(&priv->mutex); + return 0; +} + +static int ipw_wx_get_wireless_mode(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + mutex_lock(&priv->mutex); + switch (priv->ieee->mode) { + case IEEE_A: + strncpy(extra, "802.11a (1)", MAX_WX_STRING); + break; + case IEEE_B: + strncpy(extra, "802.11b (2)", MAX_WX_STRING); + break; + case IEEE_A | IEEE_B: + strncpy(extra, "802.11ab (3)", MAX_WX_STRING); + break; + case IEEE_G: + strncpy(extra, "802.11g (4)", MAX_WX_STRING); + break; + case IEEE_A | IEEE_G: + strncpy(extra, "802.11ag (5)", MAX_WX_STRING); + break; + case IEEE_B | IEEE_G: + strncpy(extra, "802.11bg (6)", MAX_WX_STRING); + break; + case IEEE_A | IEEE_B | IEEE_G: + strncpy(extra, "802.11abg (7)", MAX_WX_STRING); + break; + default: + strncpy(extra, "unknown", MAX_WX_STRING); + break; + } + + IPW_DEBUG_WX("PRIV GET MODE: %s\n", extra); + + wrqu->data.length = strlen(extra) + 1; + mutex_unlock(&priv->mutex); + + return 0; +} + +static int ipw_wx_set_preamble(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + int mode = *(int *)extra; + mutex_lock(&priv->mutex); + /* Switching from SHORT -> LONG requires a disassociation */ + if (mode == 1) { + if (!(priv->config & CFG_PREAMBLE_LONG)) { + priv->config |= CFG_PREAMBLE_LONG; + + /* Network configuration changed -- force [re]association */ + IPW_DEBUG_ASSOC + ("[re]association triggered due to preamble change.\n"); + if (!ipw_disassociate(priv)) + ipw_associate(priv); + } + goto done; + } + + if (mode == 0) { + priv->config &= ~CFG_PREAMBLE_LONG; + goto done; + } + mutex_unlock(&priv->mutex); + return -EINVAL; + + done: + mutex_unlock(&priv->mutex); + return 0; +} + +static int ipw_wx_get_preamble(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + mutex_lock(&priv->mutex); + if (priv->config & CFG_PREAMBLE_LONG) + snprintf(wrqu->name, IFNAMSIZ, "long (1)"); + else + snprintf(wrqu->name, IFNAMSIZ, "auto (0)"); + mutex_unlock(&priv->mutex); + return 0; +} + +#ifdef CPTCFG_IPW2200_MONITOR +static int ipw_wx_set_monitor(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + int *parms = (int *)extra; + int enable = (parms[0] > 0); + mutex_lock(&priv->mutex); + IPW_DEBUG_WX("SET MONITOR: %d %d\n", enable, parms[1]); + if (enable) { + if (priv->ieee->iw_mode != IW_MODE_MONITOR) { +#ifdef CPTCFG_IPW2200_RADIOTAP + priv->net_dev->type = ARPHRD_IEEE80211_RADIOTAP; +#else + priv->net_dev->type = ARPHRD_IEEE80211; +#endif + schedule_work(&priv->adapter_restart); + } + + ipw_set_channel(priv, parms[1]); + } else { + if (priv->ieee->iw_mode != IW_MODE_MONITOR) { + mutex_unlock(&priv->mutex); + return 0; + } + priv->net_dev->type = ARPHRD_ETHER; + schedule_work(&priv->adapter_restart); + } + mutex_unlock(&priv->mutex); + return 0; +} + +#endif /* CPTCFG_IPW2200_MONITOR */ + +static int ipw_wx_reset(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + IPW_DEBUG_WX("RESET\n"); + schedule_work(&priv->adapter_restart); + return 0; +} + +static int ipw_wx_sw_reset(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct ipw_priv *priv = libipw_priv(dev); + union iwreq_data wrqu_sec = { + .encoding = { + .flags = IW_ENCODE_DISABLED, + }, + }; + int ret; + + IPW_DEBUG_WX("SW_RESET\n"); + + mutex_lock(&priv->mutex); + + ret = ipw_sw_reset(priv, 2); + if (!ret) { + free_firmware(); + ipw_adapter_restart(priv); + } + + /* The SW reset bit might have been toggled on by the 'disable' + * module parameter, so take appropriate action */ + ipw_radio_kill_sw(priv, priv->status & STATUS_RF_KILL_SW); + + mutex_unlock(&priv->mutex); + libipw_wx_set_encode(priv->ieee, info, &wrqu_sec, NULL); + mutex_lock(&priv->mutex); + + if (!(priv->status & STATUS_RF_KILL_MASK)) { + /* Configuration likely changed -- force [re]association */ + IPW_DEBUG_ASSOC("[re]association triggered due to sw " + "reset.\n"); + if (!ipw_disassociate(priv)) + ipw_associate(priv); + } + + mutex_unlock(&priv->mutex); + + return 0; +} + +/* Rebase the WE IOCTLs to zero for the handler array */ +static iw_handler ipw_wx_handlers[] = { + IW_HANDLER(SIOCGIWNAME, (iw_handler)cfg80211_wext_giwname), + IW_HANDLER(SIOCSIWFREQ, ipw_wx_set_freq), + IW_HANDLER(SIOCGIWFREQ, ipw_wx_get_freq), + IW_HANDLER(SIOCSIWMODE, ipw_wx_set_mode), + IW_HANDLER(SIOCGIWMODE, ipw_wx_get_mode), + IW_HANDLER(SIOCSIWSENS, ipw_wx_set_sens), + IW_HANDLER(SIOCGIWSENS, ipw_wx_get_sens), + IW_HANDLER(SIOCGIWRANGE, ipw_wx_get_range), + IW_HANDLER(SIOCSIWAP, ipw_wx_set_wap), + IW_HANDLER(SIOCGIWAP, ipw_wx_get_wap), + IW_HANDLER(SIOCSIWSCAN, ipw_wx_set_scan), + IW_HANDLER(SIOCGIWSCAN, ipw_wx_get_scan), + IW_HANDLER(SIOCSIWESSID, ipw_wx_set_essid), + IW_HANDLER(SIOCGIWESSID, ipw_wx_get_essid), + IW_HANDLER(SIOCSIWNICKN, ipw_wx_set_nick), + IW_HANDLER(SIOCGIWNICKN, ipw_wx_get_nick), + IW_HANDLER(SIOCSIWRATE, ipw_wx_set_rate), + IW_HANDLER(SIOCGIWRATE, ipw_wx_get_rate), + IW_HANDLER(SIOCSIWRTS, ipw_wx_set_rts), + IW_HANDLER(SIOCGIWRTS, ipw_wx_get_rts), + IW_HANDLER(SIOCSIWFRAG, ipw_wx_set_frag), + IW_HANDLER(SIOCGIWFRAG, ipw_wx_get_frag), + IW_HANDLER(SIOCSIWTXPOW, ipw_wx_set_txpow), + IW_HANDLER(SIOCGIWTXPOW, ipw_wx_get_txpow), + IW_HANDLER(SIOCSIWRETRY, ipw_wx_set_retry), + IW_HANDLER(SIOCGIWRETRY, ipw_wx_get_retry), + IW_HANDLER(SIOCSIWENCODE, ipw_wx_set_encode), + IW_HANDLER(SIOCGIWENCODE, ipw_wx_get_encode), + IW_HANDLER(SIOCSIWPOWER, ipw_wx_set_power), + IW_HANDLER(SIOCGIWPOWER, ipw_wx_get_power), + IW_HANDLER(SIOCSIWSPY, iw_handler_set_spy), + IW_HANDLER(SIOCGIWSPY, iw_handler_get_spy), + IW_HANDLER(SIOCSIWTHRSPY, iw_handler_set_thrspy), + IW_HANDLER(SIOCGIWTHRSPY, iw_handler_get_thrspy), + IW_HANDLER(SIOCSIWGENIE, ipw_wx_set_genie), + IW_HANDLER(SIOCGIWGENIE, ipw_wx_get_genie), + IW_HANDLER(SIOCSIWMLME, ipw_wx_set_mlme), + IW_HANDLER(SIOCSIWAUTH, ipw_wx_set_auth), + IW_HANDLER(SIOCGIWAUTH, ipw_wx_get_auth), + IW_HANDLER(SIOCSIWENCODEEXT, ipw_wx_set_encodeext), + IW_HANDLER(SIOCGIWENCODEEXT, ipw_wx_get_encodeext), +}; + +enum { + IPW_PRIV_SET_POWER = SIOCIWFIRSTPRIV, + IPW_PRIV_GET_POWER, + IPW_PRIV_SET_MODE, + IPW_PRIV_GET_MODE, + IPW_PRIV_SET_PREAMBLE, + IPW_PRIV_GET_PREAMBLE, + IPW_PRIV_RESET, + IPW_PRIV_SW_RESET, +#ifdef CPTCFG_IPW2200_MONITOR + IPW_PRIV_SET_MONITOR, +#endif +}; + +static struct iw_priv_args ipw_priv_args[] = { + { + .cmd = IPW_PRIV_SET_POWER, + .set_args = IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + .name = "set_power"}, + { + .cmd = IPW_PRIV_GET_POWER, + .get_args = IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING, + .name = "get_power"}, + { + .cmd = IPW_PRIV_SET_MODE, + .set_args = IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + .name = "set_mode"}, + { + .cmd = IPW_PRIV_GET_MODE, + .get_args = IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING, + .name = "get_mode"}, + { + .cmd = IPW_PRIV_SET_PREAMBLE, + .set_args = IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + .name = "set_preamble"}, + { + .cmd = IPW_PRIV_GET_PREAMBLE, + .get_args = IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | IFNAMSIZ, + .name = "get_preamble"}, + { + IPW_PRIV_RESET, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 0, 0, "reset"}, + { + IPW_PRIV_SW_RESET, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 0, 0, "sw_reset"}, +#ifdef CPTCFG_IPW2200_MONITOR + { + IPW_PRIV_SET_MONITOR, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2, 0, "monitor"}, +#endif /* CPTCFG_IPW2200_MONITOR */ +}; + +static iw_handler ipw_priv_handler[] = { + ipw_wx_set_powermode, + ipw_wx_get_powermode, + ipw_wx_set_wireless_mode, + ipw_wx_get_wireless_mode, + ipw_wx_set_preamble, + ipw_wx_get_preamble, + ipw_wx_reset, + ipw_wx_sw_reset, +#ifdef CPTCFG_IPW2200_MONITOR + ipw_wx_set_monitor, +#endif +}; + +static struct iw_handler_def ipw_wx_handler_def = { + .standard = ipw_wx_handlers, + .num_standard = ARRAY_SIZE(ipw_wx_handlers), + .num_private = ARRAY_SIZE(ipw_priv_handler), + .num_private_args = ARRAY_SIZE(ipw_priv_args), + .private = ipw_priv_handler, + .private_args = ipw_priv_args, + .get_wireless_stats = ipw_get_wireless_stats, +}; + +/* + * Get wireless statistics. + * Called by /proc/net/wireless + * Also called by SIOCGIWSTATS + */ +static struct iw_statistics *ipw_get_wireless_stats(struct net_device *dev) +{ + struct ipw_priv *priv = libipw_priv(dev); + struct iw_statistics *wstats; + + wstats = &priv->wstats; + + /* if hw is disabled, then ipw_get_ordinal() can't be called. + * netdev->get_wireless_stats seems to be called before fw is + * initialized. STATUS_ASSOCIATED will only be set if the hw is up + * and associated; if not associcated, the values are all meaningless + * anyway, so set them all to NULL and INVALID */ + if (!(priv->status & STATUS_ASSOCIATED)) { + wstats->miss.beacon = 0; + wstats->discard.retries = 0; + wstats->qual.qual = 0; + wstats->qual.level = 0; + wstats->qual.noise = 0; + wstats->qual.updated = 7; + wstats->qual.updated |= IW_QUAL_NOISE_INVALID | + IW_QUAL_QUAL_INVALID | IW_QUAL_LEVEL_INVALID; + return wstats; + } + + wstats->qual.qual = priv->quality; + wstats->qual.level = priv->exp_avg_rssi; + wstats->qual.noise = priv->exp_avg_noise; + wstats->qual.updated = IW_QUAL_QUAL_UPDATED | IW_QUAL_LEVEL_UPDATED | + IW_QUAL_NOISE_UPDATED | IW_QUAL_DBM; + + wstats->miss.beacon = average_value(&priv->average_missed_beacons); + wstats->discard.retries = priv->last_tx_failures; + wstats->discard.code = priv->ieee->ieee_stats.rx_discards_undecryptable; + +/* if (ipw_get_ordinal(priv, IPW_ORD_STAT_TX_RETRY, &tx_retry, &len)) + goto fail_get_ordinal; + wstats->discard.retries += tx_retry; */ + + return wstats; +} + +/* net device stuff */ + +static void init_sys_config(struct ipw_sys_config *sys_config) +{ + memset(sys_config, 0, sizeof(struct ipw_sys_config)); + sys_config->bt_coexistence = 0; + sys_config->answer_broadcast_ssid_probe = 0; + sys_config->accept_all_data_frames = 0; + sys_config->accept_non_directed_frames = 1; + sys_config->exclude_unicast_unencrypted = 0; + sys_config->disable_unicast_decryption = 1; + sys_config->exclude_multicast_unencrypted = 0; + sys_config->disable_multicast_decryption = 1; + if (antenna < CFG_SYS_ANTENNA_BOTH || antenna > CFG_SYS_ANTENNA_B) + antenna = CFG_SYS_ANTENNA_BOTH; + sys_config->antenna_diversity = antenna; + sys_config->pass_crc_to_host = 0; /* TODO: See if 1 gives us FCS */ + sys_config->dot11g_auto_detection = 0; + sys_config->enable_cts_to_self = 0; + sys_config->bt_coexist_collision_thr = 0; + sys_config->pass_noise_stats_to_host = 1; /* 1 -- fix for 256 */ + sys_config->silence_threshold = 0x1e; +} + +static int ipw_net_open(struct net_device *dev) +{ + IPW_DEBUG_INFO("dev->open\n"); + netif_start_queue(dev); + return 0; +} + +static int ipw_net_stop(struct net_device *dev) +{ + IPW_DEBUG_INFO("dev->close\n"); + netif_stop_queue(dev); + return 0; +} + +/* +todo: + +modify to send one tfd per fragment instead of using chunking. otherwise +we need to heavily modify the libipw_skb_to_txb. +*/ + +static int ipw_tx_skb(struct ipw_priv *priv, struct libipw_txb *txb, + int pri) +{ + struct libipw_hdr_3addrqos *hdr = (struct libipw_hdr_3addrqos *) + txb->fragments[0]->data; + int i = 0; + struct tfd_frame *tfd; +#ifdef CPTCFG_IPW2200_QOS + int tx_id = ipw_get_tx_queue_number(priv, pri); + struct clx2_tx_queue *txq = &priv->txq[tx_id]; +#else + struct clx2_tx_queue *txq = &priv->txq[0]; +#endif + struct clx2_queue *q = &txq->q; + u8 id, hdr_len, unicast; + int fc; + + if (!(priv->status & STATUS_ASSOCIATED)) + goto drop; + + hdr_len = libipw_get_hdrlen(le16_to_cpu(hdr->frame_ctl)); + switch (priv->ieee->iw_mode) { + case IW_MODE_ADHOC: + unicast = !is_multicast_ether_addr(hdr->addr1); + id = ipw_find_station(priv, hdr->addr1); + if (id == IPW_INVALID_STATION) { + id = ipw_add_station(priv, hdr->addr1); + if (id == IPW_INVALID_STATION) { + IPW_WARNING("Attempt to send data to " + "invalid cell: %pM\n", + hdr->addr1); + goto drop; + } + } + break; + + case IW_MODE_INFRA: + default: + unicast = !is_multicast_ether_addr(hdr->addr3); + id = 0; + break; + } + + tfd = &txq->bd[q->first_empty]; + txq->txb[q->first_empty] = txb; + memset(tfd, 0, sizeof(*tfd)); + tfd->u.data.station_number = id; + + tfd->control_flags.message_type = TX_FRAME_TYPE; + tfd->control_flags.control_bits = TFD_NEED_IRQ_MASK; + + tfd->u.data.cmd_id = DINO_CMD_TX; + tfd->u.data.len = cpu_to_le16(txb->payload_size); + + if (priv->assoc_request.ieee_mode == IPW_B_MODE) + tfd->u.data.tx_flags_ext |= DCT_FLAG_EXT_MODE_CCK; + else + tfd->u.data.tx_flags_ext |= DCT_FLAG_EXT_MODE_OFDM; + + if (priv->assoc_request.preamble_length == DCT_FLAG_SHORT_PREAMBLE) + tfd->u.data.tx_flags |= DCT_FLAG_SHORT_PREAMBLE; + + fc = le16_to_cpu(hdr->frame_ctl); + hdr->frame_ctl = cpu_to_le16(fc & ~IEEE80211_FCTL_MOREFRAGS); + + memcpy(&tfd->u.data.tfd.tfd_24.mchdr, hdr, hdr_len); + + if (likely(unicast)) + tfd->u.data.tx_flags |= DCT_FLAG_ACK_REQD; + + if (txb->encrypted && !priv->ieee->host_encrypt) { + switch (priv->ieee->sec.level) { + case SEC_LEVEL_3: + tfd->u.data.tfd.tfd_24.mchdr.frame_ctl |= + cpu_to_le16(IEEE80211_FCTL_PROTECTED); + /* XXX: ACK flag must be set for CCMP even if it + * is a multicast/broadcast packet, because CCMP + * group communication encrypted by GTK is + * actually done by the AP. */ + if (!unicast) + tfd->u.data.tx_flags |= DCT_FLAG_ACK_REQD; + + tfd->u.data.tx_flags &= ~DCT_FLAG_NO_WEP; + tfd->u.data.tx_flags_ext |= DCT_FLAG_EXT_SECURITY_CCM; + tfd->u.data.key_index = 0; + tfd->u.data.key_index |= DCT_WEP_INDEX_USE_IMMEDIATE; + break; + case SEC_LEVEL_2: + tfd->u.data.tfd.tfd_24.mchdr.frame_ctl |= + cpu_to_le16(IEEE80211_FCTL_PROTECTED); + tfd->u.data.tx_flags &= ~DCT_FLAG_NO_WEP; + tfd->u.data.tx_flags_ext |= DCT_FLAG_EXT_SECURITY_TKIP; + tfd->u.data.key_index = DCT_WEP_INDEX_USE_IMMEDIATE; + break; + case SEC_LEVEL_1: + tfd->u.data.tfd.tfd_24.mchdr.frame_ctl |= + cpu_to_le16(IEEE80211_FCTL_PROTECTED); + tfd->u.data.key_index = priv->ieee->crypt_info.tx_keyidx; + if (priv->ieee->sec.key_sizes[priv->ieee->crypt_info.tx_keyidx] <= + 40) + tfd->u.data.key_index |= DCT_WEP_KEY_64Bit; + else + tfd->u.data.key_index |= DCT_WEP_KEY_128Bit; + break; + case SEC_LEVEL_0: + break; + default: + printk(KERN_ERR "Unknown security level %d\n", + priv->ieee->sec.level); + break; + } + } else + /* No hardware encryption */ + tfd->u.data.tx_flags |= DCT_FLAG_NO_WEP; + +#ifdef CPTCFG_IPW2200_QOS + if (fc & IEEE80211_STYPE_QOS_DATA) + ipw_qos_set_tx_queue_command(priv, pri, &(tfd->u.data)); +#endif /* CPTCFG_IPW2200_QOS */ + + /* payload */ + tfd->u.data.num_chunks = cpu_to_le32(min((u8) (NUM_TFD_CHUNKS - 2), + txb->nr_frags)); + IPW_DEBUG_FRAG("%i fragments being sent as %i chunks.\n", + txb->nr_frags, le32_to_cpu(tfd->u.data.num_chunks)); + for (i = 0; i < le32_to_cpu(tfd->u.data.num_chunks); i++) { + IPW_DEBUG_FRAG("Adding fragment %i of %i (%d bytes).\n", + i, le32_to_cpu(tfd->u.data.num_chunks), + txb->fragments[i]->len - hdr_len); + IPW_DEBUG_TX("Dumping TX packet frag %i of %i (%d bytes):\n", + i, tfd->u.data.num_chunks, + txb->fragments[i]->len - hdr_len); + printk_buf(IPW_DL_TX, txb->fragments[i]->data + hdr_len, + txb->fragments[i]->len - hdr_len); + + tfd->u.data.chunk_ptr[i] = + cpu_to_le32(pci_map_single + (priv->pci_dev, + txb->fragments[i]->data + hdr_len, + txb->fragments[i]->len - hdr_len, + PCI_DMA_TODEVICE)); + tfd->u.data.chunk_len[i] = + cpu_to_le16(txb->fragments[i]->len - hdr_len); + } + + if (i != txb->nr_frags) { + struct sk_buff *skb; + u16 remaining_bytes = 0; + int j; + + for (j = i; j < txb->nr_frags; j++) + remaining_bytes += txb->fragments[j]->len - hdr_len; + + printk(KERN_INFO "Trying to reallocate for %d bytes\n", + remaining_bytes); + skb = alloc_skb(remaining_bytes, GFP_ATOMIC); + if (skb != NULL) { + tfd->u.data.chunk_len[i] = cpu_to_le16(remaining_bytes); + for (j = i; j < txb->nr_frags; j++) { + int size = txb->fragments[j]->len - hdr_len; + + printk(KERN_INFO "Adding frag %d %d...\n", + j, size); + memcpy(skb_put(skb, size), + txb->fragments[j]->data + hdr_len, size); + } + dev_kfree_skb_any(txb->fragments[i]); + txb->fragments[i] = skb; + tfd->u.data.chunk_ptr[i] = + cpu_to_le32(pci_map_single + (priv->pci_dev, skb->data, + remaining_bytes, + PCI_DMA_TODEVICE)); + + le32_add_cpu(&tfd->u.data.num_chunks, 1); + } + } + + /* kick DMA */ + q->first_empty = ipw_queue_inc_wrap(q->first_empty, q->n_bd); + ipw_write32(priv, q->reg_w, q->first_empty); + + if (ipw_tx_queue_space(q) < q->high_mark) + netif_stop_queue(priv->net_dev); + + return NETDEV_TX_OK; + + drop: + IPW_DEBUG_DROP("Silently dropping Tx packet.\n"); + libipw_txb_free(txb); + return NETDEV_TX_OK; +} + +static int ipw_net_is_queue_full(struct net_device *dev, int pri) +{ + struct ipw_priv *priv = libipw_priv(dev); +#ifdef CPTCFG_IPW2200_QOS + int tx_id = ipw_get_tx_queue_number(priv, pri); + struct clx2_tx_queue *txq = &priv->txq[tx_id]; +#else + struct clx2_tx_queue *txq = &priv->txq[0]; +#endif /* CPTCFG_IPW2200_QOS */ + + if (ipw_tx_queue_space(&txq->q) < txq->q.high_mark) + return 1; + + return 0; +} + +#ifdef CPTCFG_IPW2200_PROMISCUOUS +static void ipw_handle_promiscuous_tx(struct ipw_priv *priv, + struct libipw_txb *txb) +{ + struct libipw_rx_stats dummystats; + struct ieee80211_hdr *hdr; + u8 n; + u16 filter = priv->prom_priv->filter; + int hdr_only = 0; + + if (filter & IPW_PROM_NO_TX) + return; + + memset(&dummystats, 0, sizeof(dummystats)); + + /* Filtering of fragment chains is done against the first fragment */ + hdr = (void *)txb->fragments[0]->data; + if (libipw_is_management(le16_to_cpu(hdr->frame_control))) { + if (filter & IPW_PROM_NO_MGMT) + return; + if (filter & IPW_PROM_MGMT_HEADER_ONLY) + hdr_only = 1; + } else if (libipw_is_control(le16_to_cpu(hdr->frame_control))) { + if (filter & IPW_PROM_NO_CTL) + return; + if (filter & IPW_PROM_CTL_HEADER_ONLY) + hdr_only = 1; + } else if (libipw_is_data(le16_to_cpu(hdr->frame_control))) { + if (filter & IPW_PROM_NO_DATA) + return; + if (filter & IPW_PROM_DATA_HEADER_ONLY) + hdr_only = 1; + } + + for(n=0; nnr_frags; ++n) { + struct sk_buff *src = txb->fragments[n]; + struct sk_buff *dst; + struct ieee80211_radiotap_header *rt_hdr; + int len; + + if (hdr_only) { + hdr = (void *)src->data; + len = libipw_get_hdrlen(le16_to_cpu(hdr->frame_control)); + } else + len = src->len; + + dst = alloc_skb(len + sizeof(*rt_hdr) + sizeof(u16)*2, GFP_ATOMIC); + if (!dst) + continue; + + rt_hdr = (void *)skb_put(dst, sizeof(*rt_hdr)); + + rt_hdr->it_version = PKTHDR_RADIOTAP_VERSION; + rt_hdr->it_pad = 0; + rt_hdr->it_present = 0; /* after all, it's just an idea */ + rt_hdr->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_CHANNEL); + + *(__le16*)skb_put(dst, sizeof(u16)) = cpu_to_le16( + ieee80211chan2mhz(priv->channel)); + if (priv->channel > 14) /* 802.11a */ + *(__le16*)skb_put(dst, sizeof(u16)) = + cpu_to_le16(IEEE80211_CHAN_OFDM | + IEEE80211_CHAN_5GHZ); + else if (priv->ieee->mode == IEEE_B) /* 802.11b */ + *(__le16*)skb_put(dst, sizeof(u16)) = + cpu_to_le16(IEEE80211_CHAN_CCK | + IEEE80211_CHAN_2GHZ); + else /* 802.11g */ + *(__le16*)skb_put(dst, sizeof(u16)) = + cpu_to_le16(IEEE80211_CHAN_OFDM | + IEEE80211_CHAN_2GHZ); + + rt_hdr->it_len = cpu_to_le16(dst->len); + + skb_copy_from_linear_data(src, skb_put(dst, len), len); + + if (!libipw_rx(priv->prom_priv->ieee, dst, &dummystats)) + dev_kfree_skb_any(dst); + } +} +#endif + +static netdev_tx_t ipw_net_hard_start_xmit(struct libipw_txb *txb, + struct net_device *dev, int pri) +{ + struct ipw_priv *priv = libipw_priv(dev); + unsigned long flags; + netdev_tx_t ret; + + IPW_DEBUG_TX("dev->xmit(%d bytes)\n", txb->payload_size); + spin_lock_irqsave(&priv->lock, flags); + +#ifdef CPTCFG_IPW2200_PROMISCUOUS + if (rtap_iface && netif_running(priv->prom_net_dev)) + ipw_handle_promiscuous_tx(priv, txb); +#endif + + ret = ipw_tx_skb(priv, txb, pri); + if (ret == NETDEV_TX_OK) + __ipw_led_activity_on(priv); + spin_unlock_irqrestore(&priv->lock, flags); + + return ret; +} + +static void ipw_net_set_multicast_list(struct net_device *dev) +{ + +} + +static int ipw_net_set_mac_address(struct net_device *dev, void *p) +{ + struct ipw_priv *priv = libipw_priv(dev); + struct sockaddr *addr = p; + + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + mutex_lock(&priv->mutex); + priv->config |= CFG_CUSTOM_MAC; + memcpy(priv->mac_addr, addr->sa_data, ETH_ALEN); + printk(KERN_INFO "%s: Setting MAC to %pM\n", + priv->net_dev->name, priv->mac_addr); + schedule_work(&priv->adapter_restart); + mutex_unlock(&priv->mutex); + return 0; +} + +static void ipw_ethtool_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + struct ipw_priv *p = libipw_priv(dev); + char vers[64]; + char date[32]; + u32 len; + + strlcpy(info->driver, DRV_NAME, sizeof(info->driver)); + strlcpy(info->version, DRV_VERSION, sizeof(info->version)); + + len = sizeof(vers); + ipw_get_ordinal(p, IPW_ORD_STAT_FW_VERSION, vers, &len); + len = sizeof(date); + ipw_get_ordinal(p, IPW_ORD_STAT_FW_DATE, date, &len); + + snprintf(info->fw_version, sizeof(info->fw_version), "%s (%s)", + vers, date); + strlcpy(info->bus_info, pci_name(p->pci_dev), + sizeof(info->bus_info)); + info->eedump_len = IPW_EEPROM_IMAGE_SIZE; +} + +static u32 ipw_ethtool_get_link(struct net_device *dev) +{ + struct ipw_priv *priv = libipw_priv(dev); + return (priv->status & STATUS_ASSOCIATED) != 0; +} + +static int ipw_ethtool_get_eeprom_len(struct net_device *dev) +{ + return IPW_EEPROM_IMAGE_SIZE; +} + +static int ipw_ethtool_get_eeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 * bytes) +{ + struct ipw_priv *p = libipw_priv(dev); + + if (eeprom->offset + eeprom->len > IPW_EEPROM_IMAGE_SIZE) + return -EINVAL; + mutex_lock(&p->mutex); + memcpy(bytes, &p->eeprom[eeprom->offset], eeprom->len); + mutex_unlock(&p->mutex); + return 0; +} + +static int ipw_ethtool_set_eeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 * bytes) +{ + struct ipw_priv *p = libipw_priv(dev); + int i; + + if (eeprom->offset + eeprom->len > IPW_EEPROM_IMAGE_SIZE) + return -EINVAL; + mutex_lock(&p->mutex); + memcpy(&p->eeprom[eeprom->offset], bytes, eeprom->len); + for (i = 0; i < IPW_EEPROM_IMAGE_SIZE; i++) + ipw_write8(p, i + IPW_EEPROM_DATA, p->eeprom[i]); + mutex_unlock(&p->mutex); + return 0; +} + +static const struct ethtool_ops ipw_ethtool_ops = { + .get_link = ipw_ethtool_get_link, + .get_drvinfo = ipw_ethtool_get_drvinfo, + .get_eeprom_len = ipw_ethtool_get_eeprom_len, + .get_eeprom = ipw_ethtool_get_eeprom, + .set_eeprom = ipw_ethtool_set_eeprom, +}; + +static irqreturn_t ipw_isr(int irq, void *data) +{ + struct ipw_priv *priv = data; + u32 inta, inta_mask; + + if (!priv) + return IRQ_NONE; + + spin_lock(&priv->irq_lock); + + if (!(priv->status & STATUS_INT_ENABLED)) { + /* IRQ is disabled */ + goto none; + } + + inta = ipw_read32(priv, IPW_INTA_RW); + inta_mask = ipw_read32(priv, IPW_INTA_MASK_R); + + if (inta == 0xFFFFFFFF) { + /* Hardware disappeared */ + IPW_WARNING("IRQ INTA == 0xFFFFFFFF\n"); + goto none; + } + + if (!(inta & (IPW_INTA_MASK_ALL & inta_mask))) { + /* Shared interrupt */ + goto none; + } + + /* tell the device to stop sending interrupts */ + __ipw_disable_interrupts(priv); + + /* ack current interrupts */ + inta &= (IPW_INTA_MASK_ALL & inta_mask); + ipw_write32(priv, IPW_INTA_RW, inta); + + /* Cache INTA value for our tasklet */ + priv->isr_inta = inta; + + tasklet_schedule(&priv->irq_tasklet); + + spin_unlock(&priv->irq_lock); + + return IRQ_HANDLED; + none: + spin_unlock(&priv->irq_lock); + return IRQ_NONE; +} + +static void ipw_rf_kill(void *adapter) +{ + struct ipw_priv *priv = adapter; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + + if (rf_kill_active(priv)) { + IPW_DEBUG_RF_KILL("RF Kill active, rescheduling GPIO check\n"); + schedule_delayed_work(&priv->rf_kill, 2 * HZ); + goto exit_unlock; + } + + /* RF Kill is now disabled, so bring the device back up */ + + if (!(priv->status & STATUS_RF_KILL_MASK)) { + IPW_DEBUG_RF_KILL("HW RF Kill no longer active, restarting " + "device\n"); + + /* we can not do an adapter restart while inside an irq lock */ + schedule_work(&priv->adapter_restart); + } else + IPW_DEBUG_RF_KILL("HW RF Kill deactivated. SW RF Kill still " + "enabled\n"); + + exit_unlock: + spin_unlock_irqrestore(&priv->lock, flags); +} + +static void ipw_bg_rf_kill(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, rf_kill.work); + mutex_lock(&priv->mutex); + ipw_rf_kill(priv); + mutex_unlock(&priv->mutex); +} + +static void ipw_link_up(struct ipw_priv *priv) +{ + priv->last_seq_num = -1; + priv->last_frag_num = -1; + priv->last_packet_time = 0; + + netif_carrier_on(priv->net_dev); + + cancel_delayed_work(&priv->request_scan); + cancel_delayed_work(&priv->request_direct_scan); + cancel_delayed_work(&priv->request_passive_scan); + cancel_delayed_work(&priv->scan_event); + ipw_reset_stats(priv); + /* Ensure the rate is updated immediately */ + priv->last_rate = ipw_get_current_rate(priv); + ipw_gather_stats(priv); + ipw_led_link_up(priv); + notify_wx_assoc_event(priv); + + if (priv->config & CFG_BACKGROUND_SCAN) + schedule_delayed_work(&priv->request_scan, HZ); +} + +static void ipw_bg_link_up(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, link_up); + mutex_lock(&priv->mutex); + ipw_link_up(priv); + mutex_unlock(&priv->mutex); +} + +static void ipw_link_down(struct ipw_priv *priv) +{ + ipw_led_link_down(priv); + netif_carrier_off(priv->net_dev); + notify_wx_assoc_event(priv); + + /* Cancel any queued work ... */ + cancel_delayed_work(&priv->request_scan); + cancel_delayed_work(&priv->request_direct_scan); + cancel_delayed_work(&priv->request_passive_scan); + cancel_delayed_work(&priv->adhoc_check); + cancel_delayed_work(&priv->gather_stats); + + ipw_reset_stats(priv); + + if (!(priv->status & STATUS_EXIT_PENDING)) { + /* Queue up another scan... */ + schedule_delayed_work(&priv->request_scan, 0); + } else + cancel_delayed_work(&priv->scan_event); +} + +static void ipw_bg_link_down(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, link_down); + mutex_lock(&priv->mutex); + ipw_link_down(priv); + mutex_unlock(&priv->mutex); +} + +static int ipw_setup_deferred_work(struct ipw_priv *priv) +{ + int ret = 0; + + init_waitqueue_head(&priv->wait_command_queue); + init_waitqueue_head(&priv->wait_state); + + INIT_DELAYED_WORK(&priv->adhoc_check, ipw_bg_adhoc_check); + INIT_WORK(&priv->associate, ipw_bg_associate); + INIT_WORK(&priv->disassociate, ipw_bg_disassociate); + INIT_WORK(&priv->system_config, ipw_system_config); + INIT_WORK(&priv->rx_replenish, ipw_bg_rx_queue_replenish); + INIT_WORK(&priv->adapter_restart, ipw_bg_adapter_restart); + INIT_DELAYED_WORK(&priv->rf_kill, ipw_bg_rf_kill); + INIT_WORK(&priv->up, ipw_bg_up); + INIT_WORK(&priv->down, ipw_bg_down); + INIT_DELAYED_WORK(&priv->request_scan, ipw_request_scan); + INIT_DELAYED_WORK(&priv->request_direct_scan, ipw_request_direct_scan); + INIT_DELAYED_WORK(&priv->request_passive_scan, ipw_request_passive_scan); + INIT_DELAYED_WORK(&priv->scan_event, ipw_scan_event); + INIT_DELAYED_WORK(&priv->gather_stats, ipw_bg_gather_stats); + INIT_WORK(&priv->abort_scan, ipw_bg_abort_scan); + INIT_WORK(&priv->roam, ipw_bg_roam); + INIT_DELAYED_WORK(&priv->scan_check, ipw_bg_scan_check); + INIT_WORK(&priv->link_up, ipw_bg_link_up); + INIT_WORK(&priv->link_down, ipw_bg_link_down); + INIT_DELAYED_WORK(&priv->led_link_on, ipw_bg_led_link_on); + INIT_DELAYED_WORK(&priv->led_link_off, ipw_bg_led_link_off); + INIT_DELAYED_WORK(&priv->led_act_off, ipw_bg_led_activity_off); + INIT_WORK(&priv->merge_networks, ipw_merge_adhoc_network); + +#ifdef CPTCFG_IPW2200_QOS + INIT_WORK(&priv->qos_activate, ipw_bg_qos_activate); +#endif /* CPTCFG_IPW2200_QOS */ + + tasklet_init(&priv->irq_tasklet, (void (*)(unsigned long)) + ipw_irq_tasklet, (unsigned long)priv); + + return ret; +} + +static void shim__set_security(struct net_device *dev, + struct libipw_security *sec) +{ + struct ipw_priv *priv = libipw_priv(dev); + int i; + for (i = 0; i < 4; i++) { + if (sec->flags & (1 << i)) { + priv->ieee->sec.encode_alg[i] = sec->encode_alg[i]; + priv->ieee->sec.key_sizes[i] = sec->key_sizes[i]; + if (sec->key_sizes[i] == 0) + priv->ieee->sec.flags &= ~(1 << i); + else { + memcpy(priv->ieee->sec.keys[i], sec->keys[i], + sec->key_sizes[i]); + priv->ieee->sec.flags |= (1 << i); + } + priv->status |= STATUS_SECURITY_UPDATED; + } else if (sec->level != SEC_LEVEL_1) + priv->ieee->sec.flags &= ~(1 << i); + } + + if (sec->flags & SEC_ACTIVE_KEY) { + if (sec->active_key <= 3) { + priv->ieee->sec.active_key = sec->active_key; + priv->ieee->sec.flags |= SEC_ACTIVE_KEY; + } else + priv->ieee->sec.flags &= ~SEC_ACTIVE_KEY; + priv->status |= STATUS_SECURITY_UPDATED; + } else + priv->ieee->sec.flags &= ~SEC_ACTIVE_KEY; + + if ((sec->flags & SEC_AUTH_MODE) && + (priv->ieee->sec.auth_mode != sec->auth_mode)) { + priv->ieee->sec.auth_mode = sec->auth_mode; + priv->ieee->sec.flags |= SEC_AUTH_MODE; + if (sec->auth_mode == WLAN_AUTH_SHARED_KEY) + priv->capability |= CAP_SHARED_KEY; + else + priv->capability &= ~CAP_SHARED_KEY; + priv->status |= STATUS_SECURITY_UPDATED; + } + + if (sec->flags & SEC_ENABLED && priv->ieee->sec.enabled != sec->enabled) { + priv->ieee->sec.flags |= SEC_ENABLED; + priv->ieee->sec.enabled = sec->enabled; + priv->status |= STATUS_SECURITY_UPDATED; + if (sec->enabled) + priv->capability |= CAP_PRIVACY_ON; + else + priv->capability &= ~CAP_PRIVACY_ON; + } + + if (sec->flags & SEC_ENCRYPT) + priv->ieee->sec.encrypt = sec->encrypt; + + if (sec->flags & SEC_LEVEL && priv->ieee->sec.level != sec->level) { + priv->ieee->sec.level = sec->level; + priv->ieee->sec.flags |= SEC_LEVEL; + priv->status |= STATUS_SECURITY_UPDATED; + } + + if (!priv->ieee->host_encrypt && (sec->flags & SEC_ENCRYPT)) + ipw_set_hwcrypto_keys(priv); + + /* To match current functionality of ipw2100 (which works well w/ + * various supplicants, we don't force a disassociate if the + * privacy capability changes ... */ +#if 0 + if ((priv->status & (STATUS_ASSOCIATED | STATUS_ASSOCIATING)) && + (((priv->assoc_request.capability & + cpu_to_le16(WLAN_CAPABILITY_PRIVACY)) && !sec->enabled) || + (!(priv->assoc_request.capability & + cpu_to_le16(WLAN_CAPABILITY_PRIVACY)) && sec->enabled))) { + IPW_DEBUG_ASSOC("Disassociating due to capability " + "change.\n"); + ipw_disassociate(priv); + } +#endif +} + +static int init_supported_rates(struct ipw_priv *priv, + struct ipw_supported_rates *rates) +{ + /* TODO: Mask out rates based on priv->rates_mask */ + + memset(rates, 0, sizeof(*rates)); + /* configure supported rates */ + switch (priv->ieee->freq_band) { + case LIBIPW_52GHZ_BAND: + rates->ieee_mode = IPW_A_MODE; + rates->purpose = IPW_RATE_CAPABILITIES; + ipw_add_ofdm_scan_rates(rates, LIBIPW_CCK_MODULATION, + LIBIPW_OFDM_DEFAULT_RATES_MASK); + break; + + default: /* Mixed or 2.4Ghz */ + rates->ieee_mode = IPW_G_MODE; + rates->purpose = IPW_RATE_CAPABILITIES; + ipw_add_cck_scan_rates(rates, LIBIPW_CCK_MODULATION, + LIBIPW_CCK_DEFAULT_RATES_MASK); + if (priv->ieee->modulation & LIBIPW_OFDM_MODULATION) { + ipw_add_ofdm_scan_rates(rates, LIBIPW_CCK_MODULATION, + LIBIPW_OFDM_DEFAULT_RATES_MASK); + } + break; + } + + return 0; +} + +static int ipw_config(struct ipw_priv *priv) +{ + /* This is only called from ipw_up, which resets/reloads the firmware + so, we don't need to first disable the card before we configure + it */ + if (ipw_set_tx_power(priv)) + goto error; + + /* initialize adapter address */ + if (ipw_send_adapter_address(priv, priv->net_dev->dev_addr)) + goto error; + + /* set basic system config settings */ + init_sys_config(&priv->sys_config); + + /* Support Bluetooth if we have BT h/w on board, and user wants to. + * Does not support BT priority yet (don't abort or defer our Tx) */ + if (bt_coexist) { + unsigned char bt_caps = priv->eeprom[EEPROM_SKU_CAPABILITY]; + + if (bt_caps & EEPROM_SKU_CAP_BT_CHANNEL_SIG) + priv->sys_config.bt_coexistence + |= CFG_BT_COEXISTENCE_SIGNAL_CHNL; + if (bt_caps & EEPROM_SKU_CAP_BT_OOB) + priv->sys_config.bt_coexistence + |= CFG_BT_COEXISTENCE_OOB; + } + +#ifdef CPTCFG_IPW2200_PROMISCUOUS + if (priv->prom_net_dev && netif_running(priv->prom_net_dev)) { + priv->sys_config.accept_all_data_frames = 1; + priv->sys_config.accept_non_directed_frames = 1; + priv->sys_config.accept_all_mgmt_bcpr = 1; + priv->sys_config.accept_all_mgmt_frames = 1; + } +#endif + + if (priv->ieee->iw_mode == IW_MODE_ADHOC) + priv->sys_config.answer_broadcast_ssid_probe = 1; + else + priv->sys_config.answer_broadcast_ssid_probe = 0; + + if (ipw_send_system_config(priv)) + goto error; + + init_supported_rates(priv, &priv->rates); + if (ipw_send_supported_rates(priv, &priv->rates)) + goto error; + + /* Set request-to-send threshold */ + if (priv->rts_threshold) { + if (ipw_send_rts_threshold(priv, priv->rts_threshold)) + goto error; + } +#ifdef CPTCFG_IPW2200_QOS + IPW_DEBUG_QOS("QoS: call ipw_qos_activate\n"); + ipw_qos_activate(priv, NULL); +#endif /* CPTCFG_IPW2200_QOS */ + + if (ipw_set_random_seed(priv)) + goto error; + + /* final state transition to the RUN state */ + if (ipw_send_host_complete(priv)) + goto error; + + priv->status |= STATUS_INIT; + + ipw_led_init(priv); + ipw_led_radio_on(priv); + priv->notif_missed_beacons = 0; + + /* Set hardware WEP key if it is configured. */ + if ((priv->capability & CAP_PRIVACY_ON) && + (priv->ieee->sec.level == SEC_LEVEL_1) && + !(priv->ieee->host_encrypt || priv->ieee->host_decrypt)) + ipw_set_hwcrypto_keys(priv); + + return 0; + + error: + return -EIO; +} + +/* + * NOTE: + * + * These tables have been tested in conjunction with the + * Intel PRO/Wireless 2200BG and 2915ABG Network Connection Adapters. + * + * Altering this values, using it on other hardware, or in geographies + * not intended for resale of the above mentioned Intel adapters has + * not been tested. + * + * Remember to update the table in README.ipw2200 when changing this + * table. + * + */ +static const struct libipw_geo ipw_geos[] = { + { /* Restricted */ + "---", + .bg_channels = 11, + .bg = {{2412, 1}, {2417, 2}, {2422, 3}, + {2427, 4}, {2432, 5}, {2437, 6}, + {2442, 7}, {2447, 8}, {2452, 9}, + {2457, 10}, {2462, 11}}, + }, + + { /* Custom US/Canada */ + "ZZF", + .bg_channels = 11, + .bg = {{2412, 1}, {2417, 2}, {2422, 3}, + {2427, 4}, {2432, 5}, {2437, 6}, + {2442, 7}, {2447, 8}, {2452, 9}, + {2457, 10}, {2462, 11}}, + .a_channels = 8, + .a = {{5180, 36}, + {5200, 40}, + {5220, 44}, + {5240, 48}, + {5260, 52, LIBIPW_CH_PASSIVE_ONLY}, + {5280, 56, LIBIPW_CH_PASSIVE_ONLY}, + {5300, 60, LIBIPW_CH_PASSIVE_ONLY}, + {5320, 64, LIBIPW_CH_PASSIVE_ONLY}}, + }, + + { /* Rest of World */ + "ZZD", + .bg_channels = 13, + .bg = {{2412, 1}, {2417, 2}, {2422, 3}, + {2427, 4}, {2432, 5}, {2437, 6}, + {2442, 7}, {2447, 8}, {2452, 9}, + {2457, 10}, {2462, 11}, {2467, 12}, + {2472, 13}}, + }, + + { /* Custom USA & Europe & High */ + "ZZA", + .bg_channels = 11, + .bg = {{2412, 1}, {2417, 2}, {2422, 3}, + {2427, 4}, {2432, 5}, {2437, 6}, + {2442, 7}, {2447, 8}, {2452, 9}, + {2457, 10}, {2462, 11}}, + .a_channels = 13, + .a = {{5180, 36}, + {5200, 40}, + {5220, 44}, + {5240, 48}, + {5260, 52, LIBIPW_CH_PASSIVE_ONLY}, + {5280, 56, LIBIPW_CH_PASSIVE_ONLY}, + {5300, 60, LIBIPW_CH_PASSIVE_ONLY}, + {5320, 64, LIBIPW_CH_PASSIVE_ONLY}, + {5745, 149}, + {5765, 153}, + {5785, 157}, + {5805, 161}, + {5825, 165}}, + }, + + { /* Custom NA & Europe */ + "ZZB", + .bg_channels = 11, + .bg = {{2412, 1}, {2417, 2}, {2422, 3}, + {2427, 4}, {2432, 5}, {2437, 6}, + {2442, 7}, {2447, 8}, {2452, 9}, + {2457, 10}, {2462, 11}}, + .a_channels = 13, + .a = {{5180, 36}, + {5200, 40}, + {5220, 44}, + {5240, 48}, + {5260, 52, LIBIPW_CH_PASSIVE_ONLY}, + {5280, 56, LIBIPW_CH_PASSIVE_ONLY}, + {5300, 60, LIBIPW_CH_PASSIVE_ONLY}, + {5320, 64, LIBIPW_CH_PASSIVE_ONLY}, + {5745, 149, LIBIPW_CH_PASSIVE_ONLY}, + {5765, 153, LIBIPW_CH_PASSIVE_ONLY}, + {5785, 157, LIBIPW_CH_PASSIVE_ONLY}, + {5805, 161, LIBIPW_CH_PASSIVE_ONLY}, + {5825, 165, LIBIPW_CH_PASSIVE_ONLY}}, + }, + + { /* Custom Japan */ + "ZZC", + .bg_channels = 11, + .bg = {{2412, 1}, {2417, 2}, {2422, 3}, + {2427, 4}, {2432, 5}, {2437, 6}, + {2442, 7}, {2447, 8}, {2452, 9}, + {2457, 10}, {2462, 11}}, + .a_channels = 4, + .a = {{5170, 34}, {5190, 38}, + {5210, 42}, {5230, 46}}, + }, + + { /* Custom */ + "ZZM", + .bg_channels = 11, + .bg = {{2412, 1}, {2417, 2}, {2422, 3}, + {2427, 4}, {2432, 5}, {2437, 6}, + {2442, 7}, {2447, 8}, {2452, 9}, + {2457, 10}, {2462, 11}}, + }, + + { /* Europe */ + "ZZE", + .bg_channels = 13, + .bg = {{2412, 1}, {2417, 2}, {2422, 3}, + {2427, 4}, {2432, 5}, {2437, 6}, + {2442, 7}, {2447, 8}, {2452, 9}, + {2457, 10}, {2462, 11}, {2467, 12}, + {2472, 13}}, + .a_channels = 19, + .a = {{5180, 36}, + {5200, 40}, + {5220, 44}, + {5240, 48}, + {5260, 52, LIBIPW_CH_PASSIVE_ONLY}, + {5280, 56, LIBIPW_CH_PASSIVE_ONLY}, + {5300, 60, LIBIPW_CH_PASSIVE_ONLY}, + {5320, 64, LIBIPW_CH_PASSIVE_ONLY}, + {5500, 100, LIBIPW_CH_PASSIVE_ONLY}, + {5520, 104, LIBIPW_CH_PASSIVE_ONLY}, + {5540, 108, LIBIPW_CH_PASSIVE_ONLY}, + {5560, 112, LIBIPW_CH_PASSIVE_ONLY}, + {5580, 116, LIBIPW_CH_PASSIVE_ONLY}, + {5600, 120, LIBIPW_CH_PASSIVE_ONLY}, + {5620, 124, LIBIPW_CH_PASSIVE_ONLY}, + {5640, 128, LIBIPW_CH_PASSIVE_ONLY}, + {5660, 132, LIBIPW_CH_PASSIVE_ONLY}, + {5680, 136, LIBIPW_CH_PASSIVE_ONLY}, + {5700, 140, LIBIPW_CH_PASSIVE_ONLY}}, + }, + + { /* Custom Japan */ + "ZZJ", + .bg_channels = 14, + .bg = {{2412, 1}, {2417, 2}, {2422, 3}, + {2427, 4}, {2432, 5}, {2437, 6}, + {2442, 7}, {2447, 8}, {2452, 9}, + {2457, 10}, {2462, 11}, {2467, 12}, + {2472, 13}, {2484, 14, LIBIPW_CH_B_ONLY}}, + .a_channels = 4, + .a = {{5170, 34}, {5190, 38}, + {5210, 42}, {5230, 46}}, + }, + + { /* Rest of World */ + "ZZR", + .bg_channels = 14, + .bg = {{2412, 1}, {2417, 2}, {2422, 3}, + {2427, 4}, {2432, 5}, {2437, 6}, + {2442, 7}, {2447, 8}, {2452, 9}, + {2457, 10}, {2462, 11}, {2467, 12}, + {2472, 13}, {2484, 14, LIBIPW_CH_B_ONLY | + LIBIPW_CH_PASSIVE_ONLY}}, + }, + + { /* High Band */ + "ZZH", + .bg_channels = 13, + .bg = {{2412, 1}, {2417, 2}, {2422, 3}, + {2427, 4}, {2432, 5}, {2437, 6}, + {2442, 7}, {2447, 8}, {2452, 9}, + {2457, 10}, {2462, 11}, + {2467, 12, LIBIPW_CH_PASSIVE_ONLY}, + {2472, 13, LIBIPW_CH_PASSIVE_ONLY}}, + .a_channels = 4, + .a = {{5745, 149}, {5765, 153}, + {5785, 157}, {5805, 161}}, + }, + + { /* Custom Europe */ + "ZZG", + .bg_channels = 13, + .bg = {{2412, 1}, {2417, 2}, {2422, 3}, + {2427, 4}, {2432, 5}, {2437, 6}, + {2442, 7}, {2447, 8}, {2452, 9}, + {2457, 10}, {2462, 11}, + {2467, 12}, {2472, 13}}, + .a_channels = 4, + .a = {{5180, 36}, {5200, 40}, + {5220, 44}, {5240, 48}}, + }, + + { /* Europe */ + "ZZK", + .bg_channels = 13, + .bg = {{2412, 1}, {2417, 2}, {2422, 3}, + {2427, 4}, {2432, 5}, {2437, 6}, + {2442, 7}, {2447, 8}, {2452, 9}, + {2457, 10}, {2462, 11}, + {2467, 12, LIBIPW_CH_PASSIVE_ONLY}, + {2472, 13, LIBIPW_CH_PASSIVE_ONLY}}, + .a_channels = 24, + .a = {{5180, 36, LIBIPW_CH_PASSIVE_ONLY}, + {5200, 40, LIBIPW_CH_PASSIVE_ONLY}, + {5220, 44, LIBIPW_CH_PASSIVE_ONLY}, + {5240, 48, LIBIPW_CH_PASSIVE_ONLY}, + {5260, 52, LIBIPW_CH_PASSIVE_ONLY}, + {5280, 56, LIBIPW_CH_PASSIVE_ONLY}, + {5300, 60, LIBIPW_CH_PASSIVE_ONLY}, + {5320, 64, LIBIPW_CH_PASSIVE_ONLY}, + {5500, 100, LIBIPW_CH_PASSIVE_ONLY}, + {5520, 104, LIBIPW_CH_PASSIVE_ONLY}, + {5540, 108, LIBIPW_CH_PASSIVE_ONLY}, + {5560, 112, LIBIPW_CH_PASSIVE_ONLY}, + {5580, 116, LIBIPW_CH_PASSIVE_ONLY}, + {5600, 120, LIBIPW_CH_PASSIVE_ONLY}, + {5620, 124, LIBIPW_CH_PASSIVE_ONLY}, + {5640, 128, LIBIPW_CH_PASSIVE_ONLY}, + {5660, 132, LIBIPW_CH_PASSIVE_ONLY}, + {5680, 136, LIBIPW_CH_PASSIVE_ONLY}, + {5700, 140, LIBIPW_CH_PASSIVE_ONLY}, + {5745, 149, LIBIPW_CH_PASSIVE_ONLY}, + {5765, 153, LIBIPW_CH_PASSIVE_ONLY}, + {5785, 157, LIBIPW_CH_PASSIVE_ONLY}, + {5805, 161, LIBIPW_CH_PASSIVE_ONLY}, + {5825, 165, LIBIPW_CH_PASSIVE_ONLY}}, + }, + + { /* Europe */ + "ZZL", + .bg_channels = 11, + .bg = {{2412, 1}, {2417, 2}, {2422, 3}, + {2427, 4}, {2432, 5}, {2437, 6}, + {2442, 7}, {2447, 8}, {2452, 9}, + {2457, 10}, {2462, 11}}, + .a_channels = 13, + .a = {{5180, 36, LIBIPW_CH_PASSIVE_ONLY}, + {5200, 40, LIBIPW_CH_PASSIVE_ONLY}, + {5220, 44, LIBIPW_CH_PASSIVE_ONLY}, + {5240, 48, LIBIPW_CH_PASSIVE_ONLY}, + {5260, 52, LIBIPW_CH_PASSIVE_ONLY}, + {5280, 56, LIBIPW_CH_PASSIVE_ONLY}, + {5300, 60, LIBIPW_CH_PASSIVE_ONLY}, + {5320, 64, LIBIPW_CH_PASSIVE_ONLY}, + {5745, 149, LIBIPW_CH_PASSIVE_ONLY}, + {5765, 153, LIBIPW_CH_PASSIVE_ONLY}, + {5785, 157, LIBIPW_CH_PASSIVE_ONLY}, + {5805, 161, LIBIPW_CH_PASSIVE_ONLY}, + {5825, 165, LIBIPW_CH_PASSIVE_ONLY}}, + } +}; + +static void ipw_set_geo(struct ipw_priv *priv) +{ + int j; + + for (j = 0; j < ARRAY_SIZE(ipw_geos); j++) { + if (!memcmp(&priv->eeprom[EEPROM_COUNTRY_CODE], + ipw_geos[j].name, 3)) + break; + } + + if (j == ARRAY_SIZE(ipw_geos)) { + IPW_WARNING("SKU [%c%c%c] not recognized.\n", + priv->eeprom[EEPROM_COUNTRY_CODE + 0], + priv->eeprom[EEPROM_COUNTRY_CODE + 1], + priv->eeprom[EEPROM_COUNTRY_CODE + 2]); + j = 0; + } + + libipw_set_geo(priv->ieee, &ipw_geos[j]); +} + +#define MAX_HW_RESTARTS 5 +static int ipw_up(struct ipw_priv *priv) +{ + int rc, i; + + /* Age scan list entries found before suspend */ + if (priv->suspend_time) { + libipw_networks_age(priv->ieee, priv->suspend_time); + priv->suspend_time = 0; + } + + if (priv->status & STATUS_EXIT_PENDING) + return -EIO; + + if (cmdlog && !priv->cmdlog) { + priv->cmdlog = kcalloc(cmdlog, sizeof(*priv->cmdlog), + GFP_KERNEL); + if (priv->cmdlog == NULL) { + IPW_ERROR("Error allocating %d command log entries.\n", + cmdlog); + return -ENOMEM; + } else { + priv->cmdlog_len = cmdlog; + } + } + + for (i = 0; i < MAX_HW_RESTARTS; i++) { + /* Load the microcode, firmware, and eeprom. + * Also start the clocks. */ + rc = ipw_load(priv); + if (rc) { + IPW_ERROR("Unable to load firmware: %d\n", rc); + return rc; + } + + ipw_init_ordinals(priv); + if (!(priv->config & CFG_CUSTOM_MAC)) + eeprom_parse_mac(priv, priv->mac_addr); + memcpy(priv->net_dev->dev_addr, priv->mac_addr, ETH_ALEN); + + ipw_set_geo(priv); + + if (priv->status & STATUS_RF_KILL_SW) { + IPW_WARNING("Radio disabled by module parameter.\n"); + return 0; + } else if (rf_kill_active(priv)) { + IPW_WARNING("Radio Frequency Kill Switch is On:\n" + "Kill switch must be turned off for " + "wireless networking to work.\n"); + schedule_delayed_work(&priv->rf_kill, 2 * HZ); + return 0; + } + + rc = ipw_config(priv); + if (!rc) { + IPW_DEBUG_INFO("Configured device on count %i\n", i); + + /* If configure to try and auto-associate, kick + * off a scan. */ + schedule_delayed_work(&priv->request_scan, 0); + + return 0; + } + + IPW_DEBUG_INFO("Device configuration failed: 0x%08X\n", rc); + IPW_DEBUG_INFO("Failed to config device on retry %d of %d\n", + i, MAX_HW_RESTARTS); + + /* We had an error bringing up the hardware, so take it + * all the way back down so we can try again */ + ipw_down(priv); + } + + /* tried to restart and config the device for as long as our + * patience could withstand */ + IPW_ERROR("Unable to initialize device after %d attempts.\n", i); + + return -EIO; +} + +static void ipw_bg_up(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, up); + mutex_lock(&priv->mutex); + ipw_up(priv); + mutex_unlock(&priv->mutex); +} + +static void ipw_deinit(struct ipw_priv *priv) +{ + int i; + + if (priv->status & STATUS_SCANNING) { + IPW_DEBUG_INFO("Aborting scan during shutdown.\n"); + ipw_abort_scan(priv); + } + + if (priv->status & STATUS_ASSOCIATED) { + IPW_DEBUG_INFO("Disassociating during shutdown.\n"); + ipw_disassociate(priv); + } + + ipw_led_shutdown(priv); + + /* Wait up to 1s for status to change to not scanning and not + * associated (disassociation can take a while for a ful 802.11 + * exchange */ + for (i = 1000; i && (priv->status & + (STATUS_DISASSOCIATING | + STATUS_ASSOCIATED | STATUS_SCANNING)); i--) + udelay(10); + + if (priv->status & (STATUS_DISASSOCIATING | + STATUS_ASSOCIATED | STATUS_SCANNING)) + IPW_DEBUG_INFO("Still associated or scanning...\n"); + else + IPW_DEBUG_INFO("Took %dms to de-init\n", 1000 - i); + + /* Attempt to disable the card */ + ipw_send_card_disable(priv, 0); + + priv->status &= ~STATUS_INIT; +} + +static void ipw_down(struct ipw_priv *priv) +{ + int exit_pending = priv->status & STATUS_EXIT_PENDING; + + priv->status |= STATUS_EXIT_PENDING; + + if (ipw_is_init(priv)) + ipw_deinit(priv); + + /* Wipe out the EXIT_PENDING status bit if we are not actually + * exiting the module */ + if (!exit_pending) + priv->status &= ~STATUS_EXIT_PENDING; + + /* tell the device to stop sending interrupts */ + ipw_disable_interrupts(priv); + + /* Clear all bits but the RF Kill */ + priv->status &= STATUS_RF_KILL_MASK | STATUS_EXIT_PENDING; + netif_carrier_off(priv->net_dev); + + ipw_stop_nic(priv); + + ipw_led_radio_off(priv); +} + +static void ipw_bg_down(struct work_struct *work) +{ + struct ipw_priv *priv = + container_of(work, struct ipw_priv, down); + mutex_lock(&priv->mutex); + ipw_down(priv); + mutex_unlock(&priv->mutex); +} + +static int ipw_wdev_init(struct net_device *dev) +{ + int i, rc = 0; + struct ipw_priv *priv = libipw_priv(dev); + const struct libipw_geo *geo = libipw_get_geo(priv->ieee); + struct wireless_dev *wdev = &priv->ieee->wdev; + + memcpy(wdev->wiphy->perm_addr, priv->mac_addr, ETH_ALEN); + + /* fill-out priv->ieee->bg_band */ + if (geo->bg_channels) { + struct ieee80211_supported_band *bg_band = &priv->ieee->bg_band; + + bg_band->band = IEEE80211_BAND_2GHZ; + bg_band->n_channels = geo->bg_channels; + bg_band->channels = kcalloc(geo->bg_channels, + sizeof(struct ieee80211_channel), + GFP_KERNEL); + if (!bg_band->channels) { + rc = -ENOMEM; + goto out; + } + /* translate geo->bg to bg_band.channels */ + for (i = 0; i < geo->bg_channels; i++) { + bg_band->channels[i].band = IEEE80211_BAND_2GHZ; + bg_band->channels[i].center_freq = geo->bg[i].freq; + bg_band->channels[i].hw_value = geo->bg[i].channel; + bg_band->channels[i].max_power = geo->bg[i].max_power; + if (geo->bg[i].flags & LIBIPW_CH_PASSIVE_ONLY) + bg_band->channels[i].flags |= + IEEE80211_CHAN_PASSIVE_SCAN; + if (geo->bg[i].flags & LIBIPW_CH_NO_IBSS) + bg_band->channels[i].flags |= + IEEE80211_CHAN_NO_IBSS; + if (geo->bg[i].flags & LIBIPW_CH_RADAR_DETECT) + bg_band->channels[i].flags |= + IEEE80211_CHAN_RADAR; + /* No equivalent for LIBIPW_CH_80211H_RULES, + LIBIPW_CH_UNIFORM_SPREADING, or + LIBIPW_CH_B_ONLY... */ + } + /* point at bitrate info */ + bg_band->bitrates = ipw2200_bg_rates; + bg_band->n_bitrates = ipw2200_num_bg_rates; + + wdev->wiphy->bands[IEEE80211_BAND_2GHZ] = bg_band; + } + + /* fill-out priv->ieee->a_band */ + if (geo->a_channels) { + struct ieee80211_supported_band *a_band = &priv->ieee->a_band; + + a_band->band = IEEE80211_BAND_5GHZ; + a_band->n_channels = geo->a_channels; + a_band->channels = kcalloc(geo->a_channels, + sizeof(struct ieee80211_channel), + GFP_KERNEL); + if (!a_band->channels) { + rc = -ENOMEM; + goto out; + } + /* translate geo->a to a_band.channels */ + for (i = 0; i < geo->a_channels; i++) { + a_band->channels[i].band = IEEE80211_BAND_5GHZ; + a_band->channels[i].center_freq = geo->a[i].freq; + a_band->channels[i].hw_value = geo->a[i].channel; + a_band->channels[i].max_power = geo->a[i].max_power; + if (geo->a[i].flags & LIBIPW_CH_PASSIVE_ONLY) + a_band->channels[i].flags |= + IEEE80211_CHAN_PASSIVE_SCAN; + if (geo->a[i].flags & LIBIPW_CH_NO_IBSS) + a_band->channels[i].flags |= + IEEE80211_CHAN_NO_IBSS; + if (geo->a[i].flags & LIBIPW_CH_RADAR_DETECT) + a_band->channels[i].flags |= + IEEE80211_CHAN_RADAR; + /* No equivalent for LIBIPW_CH_80211H_RULES, + LIBIPW_CH_UNIFORM_SPREADING, or + LIBIPW_CH_B_ONLY... */ + } + /* point at bitrate info */ + a_band->bitrates = ipw2200_a_rates; + a_band->n_bitrates = ipw2200_num_a_rates; + + wdev->wiphy->bands[IEEE80211_BAND_5GHZ] = a_band; + } + + wdev->wiphy->cipher_suites = ipw_cipher_suites; + wdev->wiphy->n_cipher_suites = ARRAY_SIZE(ipw_cipher_suites); + + set_wiphy_dev(wdev->wiphy, &priv->pci_dev->dev); + + /* With that information in place, we can now register the wiphy... */ + if (wiphy_register(wdev->wiphy)) + rc = -EIO; +out: + return rc; +} + +/* PCI driver stuff */ +static DEFINE_PCI_DEVICE_TABLE(card_ids) = { + {PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, 0x2701, 0, 0, 0}, + {PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, 0x2702, 0, 0, 0}, + {PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, 0x2711, 0, 0, 0}, + {PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, 0x2712, 0, 0, 0}, + {PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, 0x2721, 0, 0, 0}, + {PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, 0x2722, 0, 0, 0}, + {PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, 0x2731, 0, 0, 0}, + {PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, 0x2732, 0, 0, 0}, + {PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, 0x2741, 0, 0, 0}, + {PCI_VENDOR_ID_INTEL, 0x1043, 0x103c, 0x2741, 0, 0, 0}, + {PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, 0x2742, 0, 0, 0}, + {PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, 0x2751, 0, 0, 0}, + {PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, 0x2752, 0, 0, 0}, + {PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, 0x2753, 0, 0, 0}, + {PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, 0x2754, 0, 0, 0}, + {PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, 0x2761, 0, 0, 0}, + {PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, 0x2762, 0, 0, 0}, + {PCI_VDEVICE(INTEL, 0x104f), 0}, + {PCI_VDEVICE(INTEL, 0x4220), 0}, /* BG */ + {PCI_VDEVICE(INTEL, 0x4221), 0}, /* BG */ + {PCI_VDEVICE(INTEL, 0x4223), 0}, /* ABG */ + {PCI_VDEVICE(INTEL, 0x4224), 0}, /* ABG */ + + /* required last entry */ + {0,} +}; + +MODULE_DEVICE_TABLE(pci, card_ids); + +static struct attribute *ipw_sysfs_entries[] = { + &dev_attr_rf_kill.attr, + &dev_attr_direct_dword.attr, + &dev_attr_indirect_byte.attr, + &dev_attr_indirect_dword.attr, + &dev_attr_mem_gpio_reg.attr, + &dev_attr_command_event_reg.attr, + &dev_attr_nic_type.attr, + &dev_attr_status.attr, + &dev_attr_cfg.attr, + &dev_attr_error.attr, + &dev_attr_event_log.attr, + &dev_attr_cmd_log.attr, + &dev_attr_eeprom_delay.attr, + &dev_attr_ucode_version.attr, + &dev_attr_rtc.attr, + &dev_attr_scan_age.attr, + &dev_attr_led.attr, + &dev_attr_speed_scan.attr, + &dev_attr_net_stats.attr, + &dev_attr_channels.attr, +#ifdef CPTCFG_IPW2200_PROMISCUOUS + &dev_attr_rtap_iface.attr, + &dev_attr_rtap_filter.attr, +#endif + NULL +}; + +static struct attribute_group ipw_attribute_group = { + .name = NULL, /* put in device directory */ + .attrs = ipw_sysfs_entries, +}; + +#ifdef CPTCFG_IPW2200_PROMISCUOUS +static int ipw_prom_open(struct net_device *dev) +{ + struct ipw_prom_priv *prom_priv = libipw_priv(dev); + struct ipw_priv *priv = prom_priv->priv; + + IPW_DEBUG_INFO("prom dev->open\n"); + netif_carrier_off(dev); + + if (priv->ieee->iw_mode != IW_MODE_MONITOR) { + priv->sys_config.accept_all_data_frames = 1; + priv->sys_config.accept_non_directed_frames = 1; + priv->sys_config.accept_all_mgmt_bcpr = 1; + priv->sys_config.accept_all_mgmt_frames = 1; + + ipw_send_system_config(priv); + } + + return 0; +} + +static int ipw_prom_stop(struct net_device *dev) +{ + struct ipw_prom_priv *prom_priv = libipw_priv(dev); + struct ipw_priv *priv = prom_priv->priv; + + IPW_DEBUG_INFO("prom dev->stop\n"); + + if (priv->ieee->iw_mode != IW_MODE_MONITOR) { + priv->sys_config.accept_all_data_frames = 0; + priv->sys_config.accept_non_directed_frames = 0; + priv->sys_config.accept_all_mgmt_bcpr = 0; + priv->sys_config.accept_all_mgmt_frames = 0; + + ipw_send_system_config(priv); + } + + return 0; +} + +static netdev_tx_t ipw_prom_hard_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + IPW_DEBUG_INFO("prom dev->xmit\n"); + dev_kfree_skb(skb); + return NETDEV_TX_OK; +} + +static const struct net_device_ops ipw_prom_netdev_ops = { + .ndo_open = ipw_prom_open, + .ndo_stop = ipw_prom_stop, + .ndo_start_xmit = ipw_prom_hard_start_xmit, + .ndo_change_mtu = libipw_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static int ipw_prom_alloc(struct ipw_priv *priv) +{ + int rc = 0; + + if (priv->prom_net_dev) + return -EPERM; + + priv->prom_net_dev = alloc_libipw(sizeof(struct ipw_prom_priv), 1); + if (priv->prom_net_dev == NULL) + return -ENOMEM; + + priv->prom_priv = libipw_priv(priv->prom_net_dev); + priv->prom_priv->ieee = netdev_priv(priv->prom_net_dev); + priv->prom_priv->priv = priv; + + strcpy(priv->prom_net_dev->name, "rtap%d"); + memcpy(priv->prom_net_dev->dev_addr, priv->mac_addr, ETH_ALEN); + + priv->prom_net_dev->type = ARPHRD_IEEE80211_RADIOTAP; + netdev_attach_ops(priv->prom_net_dev, &ipw_prom_netdev_ops); + + priv->prom_priv->ieee->iw_mode = IW_MODE_MONITOR; + SET_NETDEV_DEV(priv->prom_net_dev, &priv->pci_dev->dev); + + rc = register_netdev(priv->prom_net_dev); + if (rc) { + free_libipw(priv->prom_net_dev, 1); + priv->prom_net_dev = NULL; + return rc; + } + + return 0; +} + +static void ipw_prom_free(struct ipw_priv *priv) +{ + if (!priv->prom_net_dev) + return; + + unregister_netdev(priv->prom_net_dev); + free_libipw(priv->prom_net_dev, 1); + + priv->prom_net_dev = NULL; +} + +#endif + +static const struct net_device_ops ipw_netdev_ops = { + .ndo_open = ipw_net_open, + .ndo_stop = ipw_net_stop, + .ndo_set_rx_mode = ipw_net_set_multicast_list, + .ndo_set_mac_address = ipw_net_set_mac_address, + .ndo_start_xmit = libipw_xmit, + .ndo_change_mtu = libipw_change_mtu, + .ndo_validate_addr = eth_validate_addr, +}; + +static int ipw_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int err = 0; + struct net_device *net_dev; + void __iomem *base; + u32 length, val; + struct ipw_priv *priv; + int i; + + net_dev = alloc_libipw(sizeof(struct ipw_priv), 0); + if (net_dev == NULL) { + err = -ENOMEM; + goto out; + } + + priv = libipw_priv(net_dev); + priv->ieee = netdev_priv(net_dev); + + priv->net_dev = net_dev; + priv->pci_dev = pdev; + ipw_debug_level = debug; + spin_lock_init(&priv->irq_lock); + spin_lock_init(&priv->lock); + for (i = 0; i < IPW_IBSS_MAC_HASH_SIZE; i++) + INIT_LIST_HEAD(&priv->ibss_mac_hash[i]); + + mutex_init(&priv->mutex); + if (pci_enable_device(pdev)) { + err = -ENODEV; + goto out_free_libipw; + } + + pci_set_master(pdev); + + err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + if (!err) + err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)); + if (err) { + printk(KERN_WARNING DRV_NAME ": No suitable DMA available.\n"); + goto out_pci_disable_device; + } + + pci_set_drvdata(pdev, priv); + + err = pci_request_regions(pdev, DRV_NAME); + if (err) + goto out_pci_disable_device; + + /* We disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state */ + pci_read_config_dword(pdev, 0x40, &val); + if ((val & 0x0000ff00) != 0) + pci_write_config_dword(pdev, 0x40, val & 0xffff00ff); + + length = pci_resource_len(pdev, 0); + priv->hw_len = length; + + base = pci_ioremap_bar(pdev, 0); + if (!base) { + err = -ENODEV; + goto out_pci_release_regions; + } + + priv->hw_base = base; + IPW_DEBUG_INFO("pci_resource_len = 0x%08x\n", length); + IPW_DEBUG_INFO("pci_resource_base = %p\n", base); + + err = ipw_setup_deferred_work(priv); + if (err) { + IPW_ERROR("Unable to setup deferred work\n"); + goto out_iounmap; + } + + ipw_sw_reset(priv, 1); + + err = request_irq(pdev->irq, ipw_isr, IRQF_SHARED, DRV_NAME, priv); + if (err) { + IPW_ERROR("Error allocating IRQ %d\n", pdev->irq); + goto out_iounmap; + } + + SET_NETDEV_DEV(net_dev, &pdev->dev); + + mutex_lock(&priv->mutex); + + priv->ieee->hard_start_xmit = ipw_net_hard_start_xmit; + priv->ieee->set_security = shim__set_security; + priv->ieee->is_queue_full = ipw_net_is_queue_full; + +#ifdef CPTCFG_IPW2200_QOS + priv->ieee->is_qos_active = ipw_is_qos_active; + priv->ieee->handle_probe_response = ipw_handle_beacon; + priv->ieee->handle_beacon = ipw_handle_probe_response; + priv->ieee->handle_assoc_response = ipw_handle_assoc_response; +#endif /* CPTCFG_IPW2200_QOS */ + + priv->ieee->perfect_rssi = -20; + priv->ieee->worst_rssi = -85; + + netdev_attach_ops(net_dev, &ipw_netdev_ops); + priv->wireless_data.spy_data = &priv->ieee->spy_data; + net_dev->wireless_data = &priv->wireless_data; + net_dev->wireless_handlers = &ipw_wx_handler_def; + net_dev->ethtool_ops = &ipw_ethtool_ops; + + err = sysfs_create_group(&pdev->dev.kobj, &ipw_attribute_group); + if (err) { + IPW_ERROR("failed to create sysfs device attributes\n"); + mutex_unlock(&priv->mutex); + goto out_release_irq; + } + + if (ipw_up(priv)) { + mutex_unlock(&priv->mutex); + err = -EIO; + goto out_remove_sysfs; + } + + mutex_unlock(&priv->mutex); + + err = ipw_wdev_init(net_dev); + if (err) { + IPW_ERROR("failed to register wireless device\n"); + goto out_remove_sysfs; + } + + err = register_netdev(net_dev); + if (err) { + IPW_ERROR("failed to register network device\n"); + goto out_unregister_wiphy; + } + +#ifdef CPTCFG_IPW2200_PROMISCUOUS + if (rtap_iface) { + err = ipw_prom_alloc(priv); + if (err) { + IPW_ERROR("Failed to register promiscuous network " + "device (error %d).\n", err); + unregister_netdev(priv->net_dev); + goto out_unregister_wiphy; + } + } +#endif + + printk(KERN_INFO DRV_NAME ": Detected geography %s (%d 802.11bg " + "channels, %d 802.11a channels)\n", + priv->ieee->geo.name, priv->ieee->geo.bg_channels, + priv->ieee->geo.a_channels); + + return 0; + + out_unregister_wiphy: + wiphy_unregister(priv->ieee->wdev.wiphy); + kfree(priv->ieee->a_band.channels); + kfree(priv->ieee->bg_band.channels); + out_remove_sysfs: + sysfs_remove_group(&pdev->dev.kobj, &ipw_attribute_group); + out_release_irq: + free_irq(pdev->irq, priv); + out_iounmap: + iounmap(priv->hw_base); + out_pci_release_regions: + pci_release_regions(pdev); + out_pci_disable_device: + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); + out_free_libipw: + free_libipw(priv->net_dev, 0); + out: + return err; +} + +static void ipw_pci_remove(struct pci_dev *pdev) +{ + struct ipw_priv *priv = pci_get_drvdata(pdev); + struct list_head *p, *q; + int i; + + if (!priv) + return; + + mutex_lock(&priv->mutex); + + priv->status |= STATUS_EXIT_PENDING; + ipw_down(priv); + sysfs_remove_group(&pdev->dev.kobj, &ipw_attribute_group); + + mutex_unlock(&priv->mutex); + + unregister_netdev(priv->net_dev); + + if (priv->rxq) { + ipw_rx_queue_free(priv, priv->rxq); + priv->rxq = NULL; + } + ipw_tx_queue_free(priv); + + if (priv->cmdlog) { + kfree(priv->cmdlog); + priv->cmdlog = NULL; + } + + /* make sure all works are inactive */ + cancel_delayed_work_sync(&priv->adhoc_check); + cancel_work_sync(&priv->associate); + cancel_work_sync(&priv->disassociate); + cancel_work_sync(&priv->system_config); + cancel_work_sync(&priv->rx_replenish); + cancel_work_sync(&priv->adapter_restart); + cancel_delayed_work_sync(&priv->rf_kill); + cancel_work_sync(&priv->up); + cancel_work_sync(&priv->down); + cancel_delayed_work_sync(&priv->request_scan); + cancel_delayed_work_sync(&priv->request_direct_scan); + cancel_delayed_work_sync(&priv->request_passive_scan); + cancel_delayed_work_sync(&priv->scan_event); + cancel_delayed_work_sync(&priv->gather_stats); + cancel_work_sync(&priv->abort_scan); + cancel_work_sync(&priv->roam); + cancel_delayed_work_sync(&priv->scan_check); + cancel_work_sync(&priv->link_up); + cancel_work_sync(&priv->link_down); + cancel_delayed_work_sync(&priv->led_link_on); + cancel_delayed_work_sync(&priv->led_link_off); + cancel_delayed_work_sync(&priv->led_act_off); + cancel_work_sync(&priv->merge_networks); + + /* Free MAC hash list for ADHOC */ + for (i = 0; i < IPW_IBSS_MAC_HASH_SIZE; i++) { + list_for_each_safe(p, q, &priv->ibss_mac_hash[i]) { + list_del(p); + kfree(list_entry(p, struct ipw_ibss_seq, list)); + } + } + + kfree(priv->error); + priv->error = NULL; + +#ifdef CPTCFG_IPW2200_PROMISCUOUS + ipw_prom_free(priv); +#endif + + free_irq(pdev->irq, priv); + iounmap(priv->hw_base); + pci_release_regions(pdev); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); + /* wiphy_unregister needs to be here, before free_libipw */ + wiphy_unregister(priv->ieee->wdev.wiphy); + kfree(priv->ieee->a_band.channels); + kfree(priv->ieee->bg_band.channels); + free_libipw(priv->net_dev, 0); + free_firmware(); +} + +#ifdef CONFIG_PM +static int ipw_pci_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct ipw_priv *priv = pci_get_drvdata(pdev); + struct net_device *dev = priv->net_dev; + + printk(KERN_INFO "%s: Going into suspend...\n", dev->name); + + /* Take down the device; powers it off, etc. */ + ipw_down(priv); + + /* Remove the PRESENT state of the device */ + netif_device_detach(dev); + + pci_save_state(pdev); + pci_disable_device(pdev); + pci_set_power_state(pdev, pci_choose_state(pdev, state)); + + priv->suspend_at = get_seconds(); + + return 0; +} + +static int ipw_pci_resume(struct pci_dev *pdev) +{ + struct ipw_priv *priv = pci_get_drvdata(pdev); + struct net_device *dev = priv->net_dev; + int err; + u32 val; + + printk(KERN_INFO "%s: Coming out of suspend...\n", dev->name); + + pci_set_power_state(pdev, PCI_D0); + err = pci_enable_device(pdev); + if (err) { + printk(KERN_ERR "%s: pci_enable_device failed on resume\n", + dev->name); + return err; + } + pci_restore_state(pdev); + + /* + * Suspend/Resume resets the PCI configuration space, so we have to + * re-disable the RETRY_TIMEOUT register (0x41) to keep PCI Tx retries + * from interfering with C3 CPU state. pci_restore_state won't help + * here since it only restores the first 64 bytes pci config header. + */ + pci_read_config_dword(pdev, 0x40, &val); + if ((val & 0x0000ff00) != 0) + pci_write_config_dword(pdev, 0x40, val & 0xffff00ff); + + /* Set the device back into the PRESENT state; this will also wake + * the queue of needed */ + netif_device_attach(dev); + + priv->suspend_time = get_seconds() - priv->suspend_at; + + /* Bring the device back up */ + schedule_work(&priv->up); + + return 0; +} +#endif + +static void ipw_pci_shutdown(struct pci_dev *pdev) +{ + struct ipw_priv *priv = pci_get_drvdata(pdev); + + /* Take down the device; powers it off, etc. */ + ipw_down(priv); + + pci_disable_device(pdev); +} + +/* driver initialization stuff */ +static struct pci_driver ipw_driver = { + .name = DRV_NAME, + .id_table = card_ids, + .probe = ipw_pci_probe, + .remove = ipw_pci_remove, +#ifdef CONFIG_PM + .suspend = ipw_pci_suspend, + .resume = ipw_pci_resume, +#endif + .shutdown = ipw_pci_shutdown, +}; + +static int __init ipw_init(void) +{ + int ret; + + printk(KERN_INFO DRV_NAME ": " DRV_DESCRIPTION ", " DRV_VERSION "\n"); + printk(KERN_INFO DRV_NAME ": " DRV_COPYRIGHT "\n"); + + ret = pci_register_driver(&ipw_driver); + if (ret) { + IPW_ERROR("Unable to initialize PCI module\n"); + return ret; + } + + ret = driver_create_file(&ipw_driver.driver, &driver_attr_debug_level); + if (ret) { + IPW_ERROR("Unable to create driver sysfs file\n"); + pci_unregister_driver(&ipw_driver); + return ret; + } + + return ret; +} + +static void __exit ipw_exit(void) +{ + driver_remove_file(&ipw_driver.driver, &driver_attr_debug_level); + pci_unregister_driver(&ipw_driver); +} + +module_param(disable, int, 0444); +MODULE_PARM_DESC(disable, "manually disable the radio (default 0 [radio on])"); + +module_param(associate, int, 0444); +MODULE_PARM_DESC(associate, "auto associate when scanning (default off)"); + +module_param(auto_create, int, 0444); +MODULE_PARM_DESC(auto_create, "auto create adhoc network (default on)"); + +module_param_named(led, led_support, int, 0444); +MODULE_PARM_DESC(led, "enable led control on some systems (default 1 on)"); + +module_param(debug, int, 0444); +MODULE_PARM_DESC(debug, "debug output mask"); + +module_param_named(channel, default_channel, int, 0444); +MODULE_PARM_DESC(channel, "channel to limit associate to (default 0 [ANY])"); + +#ifdef CPTCFG_IPW2200_PROMISCUOUS +module_param(rtap_iface, int, 0444); +MODULE_PARM_DESC(rtap_iface, "create the rtap interface (1 - create, default 0)"); +#endif + +#ifdef CPTCFG_IPW2200_QOS +module_param(qos_enable, int, 0444); +MODULE_PARM_DESC(qos_enable, "enable all QoS functionalitis"); + +module_param(qos_burst_enable, int, 0444); +MODULE_PARM_DESC(qos_burst_enable, "enable QoS burst mode"); + +module_param(qos_no_ack_mask, int, 0444); +MODULE_PARM_DESC(qos_no_ack_mask, "mask Tx_Queue to no ack"); + +module_param(burst_duration_CCK, int, 0444); +MODULE_PARM_DESC(burst_duration_CCK, "set CCK burst value"); + +module_param(burst_duration_OFDM, int, 0444); +MODULE_PARM_DESC(burst_duration_OFDM, "set OFDM burst value"); +#endif /* CPTCFG_IPW2200_QOS */ + +#ifdef CPTCFG_IPW2200_MONITOR +module_param_named(mode, network_mode, int, 0444); +MODULE_PARM_DESC(mode, "network mode (0=BSS,1=IBSS,2=Monitor)"); +#else +module_param_named(mode, network_mode, int, 0444); +MODULE_PARM_DESC(mode, "network mode (0=BSS,1=IBSS)"); +#endif + +module_param(bt_coexist, int, 0444); +MODULE_PARM_DESC(bt_coexist, "enable bluetooth coexistence (default off)"); + +module_param(hwcrypto, int, 0444); +MODULE_PARM_DESC(hwcrypto, "enable hardware crypto (default off)"); + +module_param(cmdlog, int, 0444); +MODULE_PARM_DESC(cmdlog, + "allocate a ring buffer for logging firmware commands"); + +module_param(roaming, int, 0444); +MODULE_PARM_DESC(roaming, "enable roaming support (default on)"); + +module_param(antenna, int, 0444); +MODULE_PARM_DESC(antenna, "select antenna 1=Main, 3=Aux, default 0 [both], 2=slow_diversity (choose the one with lower background noise)"); + +module_exit(ipw_exit); +module_init(ipw_init); diff --git a/drivers/net/wireless/ipw2x00/ipw2200.h b/drivers/net/wireless/ipw2x00/ipw2200.h new file mode 100644 index 0000000..472fa38 --- /dev/null +++ b/drivers/net/wireless/ipw2x00/ipw2200.h @@ -0,0 +1,2002 @@ +/****************************************************************************** + + Copyright(c) 2003 - 2006 Intel Corporation. All rights reserved. + + This program is free software; you can redistribute it and/or modify it + under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Contact Information: + Intel Linux Wireless + Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + +******************************************************************************/ + +#ifndef __ipw2200_h__ +#define __ipw2200_h__ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#define DRV_NAME "ipw2200" + +#include + +#include "libipw.h" + +/* Authentication and Association States */ +enum connection_manager_assoc_states { + CMAS_INIT = 0, + CMAS_TX_AUTH_SEQ_1, + CMAS_RX_AUTH_SEQ_2, + CMAS_AUTH_SEQ_1_PASS, + CMAS_AUTH_SEQ_1_FAIL, + CMAS_TX_AUTH_SEQ_3, + CMAS_RX_AUTH_SEQ_4, + CMAS_AUTH_SEQ_2_PASS, + CMAS_AUTH_SEQ_2_FAIL, + CMAS_AUTHENTICATED, + CMAS_TX_ASSOC, + CMAS_RX_ASSOC_RESP, + CMAS_ASSOCIATED, + CMAS_LAST +}; + +#define IPW_WAIT (1<<0) +#define IPW_QUIET (1<<1) +#define IPW_ROAMING (1<<2) + +#define IPW_POWER_MODE_CAM 0x00 //(always on) +#define IPW_POWER_INDEX_1 0x01 +#define IPW_POWER_INDEX_2 0x02 +#define IPW_POWER_INDEX_3 0x03 +#define IPW_POWER_INDEX_4 0x04 +#define IPW_POWER_INDEX_5 0x05 +#define IPW_POWER_AC 0x06 +#define IPW_POWER_BATTERY 0x07 +#define IPW_POWER_LIMIT 0x07 +#define IPW_POWER_MASK 0x0F +#define IPW_POWER_ENABLED 0x10 +#define IPW_POWER_LEVEL(x) ((x) & IPW_POWER_MASK) + +#define IPW_CMD_HOST_COMPLETE 2 +#define IPW_CMD_POWER_DOWN 4 +#define IPW_CMD_SYSTEM_CONFIG 6 +#define IPW_CMD_MULTICAST_ADDRESS 7 +#define IPW_CMD_SSID 8 +#define IPW_CMD_ADAPTER_ADDRESS 11 +#define IPW_CMD_PORT_TYPE 12 +#define IPW_CMD_RTS_THRESHOLD 15 +#define IPW_CMD_FRAG_THRESHOLD 16 +#define IPW_CMD_POWER_MODE 17 +#define IPW_CMD_WEP_KEY 18 +#define IPW_CMD_TGI_TX_KEY 19 +#define IPW_CMD_SCAN_REQUEST 20 +#define IPW_CMD_ASSOCIATE 21 +#define IPW_CMD_SUPPORTED_RATES 22 +#define IPW_CMD_SCAN_ABORT 23 +#define IPW_CMD_TX_FLUSH 24 +#define IPW_CMD_QOS_PARAMETERS 25 +#define IPW_CMD_SCAN_REQUEST_EXT 26 +#define IPW_CMD_DINO_CONFIG 30 +#define IPW_CMD_RSN_CAPABILITIES 31 +#define IPW_CMD_RX_KEY 32 +#define IPW_CMD_CARD_DISABLE 33 +#define IPW_CMD_SEED_NUMBER 34 +#define IPW_CMD_TX_POWER 35 +#define IPW_CMD_COUNTRY_INFO 36 +#define IPW_CMD_AIRONET_INFO 37 +#define IPW_CMD_AP_TX_POWER 38 +#define IPW_CMD_CCKM_INFO 39 +#define IPW_CMD_CCX_VER_INFO 40 +#define IPW_CMD_SET_CALIBRATION 41 +#define IPW_CMD_SENSITIVITY_CALIB 42 +#define IPW_CMD_RETRY_LIMIT 51 +#define IPW_CMD_IPW_PRE_POWER_DOWN 58 +#define IPW_CMD_VAP_BEACON_TEMPLATE 60 +#define IPW_CMD_VAP_DTIM_PERIOD 61 +#define IPW_CMD_EXT_SUPPORTED_RATES 62 +#define IPW_CMD_VAP_LOCAL_TX_PWR_CONSTRAINT 63 +#define IPW_CMD_VAP_QUIET_INTERVALS 64 +#define IPW_CMD_VAP_CHANNEL_SWITCH 65 +#define IPW_CMD_VAP_MANDATORY_CHANNELS 66 +#define IPW_CMD_VAP_CELL_PWR_LIMIT 67 +#define IPW_CMD_VAP_CF_PARAM_SET 68 +#define IPW_CMD_VAP_SET_BEACONING_STATE 69 +#define IPW_CMD_MEASUREMENT 80 +#define IPW_CMD_POWER_CAPABILITY 81 +#define IPW_CMD_SUPPORTED_CHANNELS 82 +#define IPW_CMD_TPC_REPORT 83 +#define IPW_CMD_WME_INFO 84 +#define IPW_CMD_PRODUCTION_COMMAND 85 +#define IPW_CMD_LINKSYS_EOU_INFO 90 + +#define RFD_SIZE 4 +#define NUM_TFD_CHUNKS 6 + +#define TX_QUEUE_SIZE 32 +#define RX_QUEUE_SIZE 32 + +#define DINO_CMD_WEP_KEY 0x08 +#define DINO_CMD_TX 0x0B +#define DCT_ANTENNA_A 0x01 +#define DCT_ANTENNA_B 0x02 + +#define IPW_A_MODE 0 +#define IPW_B_MODE 1 +#define IPW_G_MODE 2 + +/* + * TX Queue Flag Definitions + */ + +/* tx wep key definition */ +#define DCT_WEP_KEY_NOT_IMMIDIATE 0x00 +#define DCT_WEP_KEY_64Bit 0x40 +#define DCT_WEP_KEY_128Bit 0x80 +#define DCT_WEP_KEY_128bitIV 0xC0 +#define DCT_WEP_KEY_SIZE_MASK 0xC0 + +#define DCT_WEP_KEY_INDEX_MASK 0x0F +#define DCT_WEP_INDEX_USE_IMMEDIATE 0x20 + +/* abort attempt if mgmt frame is rx'd */ +#define DCT_FLAG_ABORT_MGMT 0x01 + +/* require CTS */ +#define DCT_FLAG_CTS_REQUIRED 0x02 + +/* use short preamble */ +#define DCT_FLAG_LONG_PREAMBLE 0x00 +#define DCT_FLAG_SHORT_PREAMBLE 0x04 + +/* RTS/CTS first */ +#define DCT_FLAG_RTS_REQD 0x08 + +/* dont calculate duration field */ +#define DCT_FLAG_DUR_SET 0x10 + +/* even if MAC WEP set (allows pre-encrypt) */ +#define DCT_FLAG_NO_WEP 0x20 + +/* overwrite TSF field */ +#define DCT_FLAG_TSF_REQD 0x40 + +/* ACK rx is expected to follow */ +#define DCT_FLAG_ACK_REQD 0x80 + +/* TX flags extension */ +#define DCT_FLAG_EXT_MODE_CCK 0x01 +#define DCT_FLAG_EXT_MODE_OFDM 0x00 + +#define DCT_FLAG_EXT_SECURITY_WEP 0x00 +#define DCT_FLAG_EXT_SECURITY_NO DCT_FLAG_EXT_SECURITY_WEP +#define DCT_FLAG_EXT_SECURITY_CKIP 0x04 +#define DCT_FLAG_EXT_SECURITY_CCM 0x08 +#define DCT_FLAG_EXT_SECURITY_TKIP 0x0C +#define DCT_FLAG_EXT_SECURITY_MASK 0x0C + +#define DCT_FLAG_EXT_QOS_ENABLED 0x10 + +#define DCT_FLAG_EXT_HC_NO_SIFS_PIFS 0x00 +#define DCT_FLAG_EXT_HC_SIFS 0x20 +#define DCT_FLAG_EXT_HC_PIFS 0x40 + +#define TX_RX_TYPE_MASK 0xFF +#define TX_FRAME_TYPE 0x00 +#define TX_HOST_COMMAND_TYPE 0x01 +#define RX_FRAME_TYPE 0x09 +#define RX_HOST_NOTIFICATION_TYPE 0x03 +#define RX_HOST_CMD_RESPONSE_TYPE 0x04 +#define RX_TX_FRAME_RESPONSE_TYPE 0x05 +#define TFD_NEED_IRQ_MASK 0x04 + +#define HOST_CMD_DINO_CONFIG 30 + +#define HOST_NOTIFICATION_STATUS_ASSOCIATED 10 +#define HOST_NOTIFICATION_STATUS_AUTHENTICATE 11 +#define HOST_NOTIFICATION_STATUS_SCAN_CHANNEL_RESULT 12 +#define HOST_NOTIFICATION_STATUS_SCAN_COMPLETED 13 +#define HOST_NOTIFICATION_STATUS_FRAG_LENGTH 14 +#define HOST_NOTIFICATION_STATUS_LINK_DETERIORATION 15 +#define HOST_NOTIFICATION_DINO_CONFIG_RESPONSE 16 +#define HOST_NOTIFICATION_STATUS_BEACON_STATE 17 +#define HOST_NOTIFICATION_STATUS_TGI_TX_KEY 18 +#define HOST_NOTIFICATION_TX_STATUS 19 +#define HOST_NOTIFICATION_CALIB_KEEP_RESULTS 20 +#define HOST_NOTIFICATION_MEASUREMENT_STARTED 21 +#define HOST_NOTIFICATION_MEASUREMENT_ENDED 22 +#define HOST_NOTIFICATION_CHANNEL_SWITCHED 23 +#define HOST_NOTIFICATION_RX_DURING_QUIET_PERIOD 24 +#define HOST_NOTIFICATION_NOISE_STATS 25 +#define HOST_NOTIFICATION_S36_MEASUREMENT_ACCEPTED 30 +#define HOST_NOTIFICATION_S36_MEASUREMENT_REFUSED 31 + +#define HOST_NOTIFICATION_STATUS_BEACON_MISSING 1 +#define IPW_MB_SCAN_CANCEL_THRESHOLD 3 +#define IPW_MB_ROAMING_THRESHOLD_MIN 1 +#define IPW_MB_ROAMING_THRESHOLD_DEFAULT 8 +#define IPW_MB_ROAMING_THRESHOLD_MAX 30 +#define IPW_MB_DISASSOCIATE_THRESHOLD_DEFAULT 3*IPW_MB_ROAMING_THRESHOLD_DEFAULT +#define IPW_REAL_RATE_RX_PACKET_THRESHOLD 300 + +#define MACADRR_BYTE_LEN 6 + +#define DCR_TYPE_AP 0x01 +#define DCR_TYPE_WLAP 0x02 +#define DCR_TYPE_MU_ESS 0x03 +#define DCR_TYPE_MU_IBSS 0x04 +#define DCR_TYPE_MU_PIBSS 0x05 +#define DCR_TYPE_SNIFFER 0x06 +#define DCR_TYPE_MU_BSS DCR_TYPE_MU_ESS + +/* QoS definitions */ + +#define CW_MIN_OFDM 15 +#define CW_MAX_OFDM 1023 +#define CW_MIN_CCK 31 +#define CW_MAX_CCK 1023 + +#define QOS_TX0_CW_MIN_OFDM cpu_to_le16(CW_MIN_OFDM) +#define QOS_TX1_CW_MIN_OFDM cpu_to_le16(CW_MIN_OFDM) +#define QOS_TX2_CW_MIN_OFDM cpu_to_le16((CW_MIN_OFDM + 1)/2 - 1) +#define QOS_TX3_CW_MIN_OFDM cpu_to_le16((CW_MIN_OFDM + 1)/4 - 1) + +#define QOS_TX0_CW_MIN_CCK cpu_to_le16(CW_MIN_CCK) +#define QOS_TX1_CW_MIN_CCK cpu_to_le16(CW_MIN_CCK) +#define QOS_TX2_CW_MIN_CCK cpu_to_le16((CW_MIN_CCK + 1)/2 - 1) +#define QOS_TX3_CW_MIN_CCK cpu_to_le16((CW_MIN_CCK + 1)/4 - 1) + +#define QOS_TX0_CW_MAX_OFDM cpu_to_le16(CW_MAX_OFDM) +#define QOS_TX1_CW_MAX_OFDM cpu_to_le16(CW_MAX_OFDM) +#define QOS_TX2_CW_MAX_OFDM cpu_to_le16(CW_MIN_OFDM) +#define QOS_TX3_CW_MAX_OFDM cpu_to_le16((CW_MIN_OFDM + 1)/2 - 1) + +#define QOS_TX0_CW_MAX_CCK cpu_to_le16(CW_MAX_CCK) +#define QOS_TX1_CW_MAX_CCK cpu_to_le16(CW_MAX_CCK) +#define QOS_TX2_CW_MAX_CCK cpu_to_le16(CW_MIN_CCK) +#define QOS_TX3_CW_MAX_CCK cpu_to_le16((CW_MIN_CCK + 1)/2 - 1) + +#define QOS_TX0_AIFS (3 - QOS_AIFSN_MIN_VALUE) +#define QOS_TX1_AIFS (7 - QOS_AIFSN_MIN_VALUE) +#define QOS_TX2_AIFS (2 - QOS_AIFSN_MIN_VALUE) +#define QOS_TX3_AIFS (2 - QOS_AIFSN_MIN_VALUE) + +#define QOS_TX0_ACM 0 +#define QOS_TX1_ACM 0 +#define QOS_TX2_ACM 0 +#define QOS_TX3_ACM 0 + +#define QOS_TX0_TXOP_LIMIT_CCK 0 +#define QOS_TX1_TXOP_LIMIT_CCK 0 +#define QOS_TX2_TXOP_LIMIT_CCK cpu_to_le16(6016) +#define QOS_TX3_TXOP_LIMIT_CCK cpu_to_le16(3264) + +#define QOS_TX0_TXOP_LIMIT_OFDM 0 +#define QOS_TX1_TXOP_LIMIT_OFDM 0 +#define QOS_TX2_TXOP_LIMIT_OFDM cpu_to_le16(3008) +#define QOS_TX3_TXOP_LIMIT_OFDM cpu_to_le16(1504) + +#define DEF_TX0_CW_MIN_OFDM cpu_to_le16(CW_MIN_OFDM) +#define DEF_TX1_CW_MIN_OFDM cpu_to_le16(CW_MIN_OFDM) +#define DEF_TX2_CW_MIN_OFDM cpu_to_le16(CW_MIN_OFDM) +#define DEF_TX3_CW_MIN_OFDM cpu_to_le16(CW_MIN_OFDM) + +#define DEF_TX0_CW_MIN_CCK cpu_to_le16(CW_MIN_CCK) +#define DEF_TX1_CW_MIN_CCK cpu_to_le16(CW_MIN_CCK) +#define DEF_TX2_CW_MIN_CCK cpu_to_le16(CW_MIN_CCK) +#define DEF_TX3_CW_MIN_CCK cpu_to_le16(CW_MIN_CCK) + +#define DEF_TX0_CW_MAX_OFDM cpu_to_le16(CW_MAX_OFDM) +#define DEF_TX1_CW_MAX_OFDM cpu_to_le16(CW_MAX_OFDM) +#define DEF_TX2_CW_MAX_OFDM cpu_to_le16(CW_MAX_OFDM) +#define DEF_TX3_CW_MAX_OFDM cpu_to_le16(CW_MAX_OFDM) + +#define DEF_TX0_CW_MAX_CCK cpu_to_le16(CW_MAX_CCK) +#define DEF_TX1_CW_MAX_CCK cpu_to_le16(CW_MAX_CCK) +#define DEF_TX2_CW_MAX_CCK cpu_to_le16(CW_MAX_CCK) +#define DEF_TX3_CW_MAX_CCK cpu_to_le16(CW_MAX_CCK) + +#define DEF_TX0_AIFS 0 +#define DEF_TX1_AIFS 0 +#define DEF_TX2_AIFS 0 +#define DEF_TX3_AIFS 0 + +#define DEF_TX0_ACM 0 +#define DEF_TX1_ACM 0 +#define DEF_TX2_ACM 0 +#define DEF_TX3_ACM 0 + +#define DEF_TX0_TXOP_LIMIT_CCK 0 +#define DEF_TX1_TXOP_LIMIT_CCK 0 +#define DEF_TX2_TXOP_LIMIT_CCK 0 +#define DEF_TX3_TXOP_LIMIT_CCK 0 + +#define DEF_TX0_TXOP_LIMIT_OFDM 0 +#define DEF_TX1_TXOP_LIMIT_OFDM 0 +#define DEF_TX2_TXOP_LIMIT_OFDM 0 +#define DEF_TX3_TXOP_LIMIT_OFDM 0 + +#define QOS_QOS_SETS 3 +#define QOS_PARAM_SET_ACTIVE 0 +#define QOS_PARAM_SET_DEF_CCK 1 +#define QOS_PARAM_SET_DEF_OFDM 2 + +#define CTRL_QOS_NO_ACK (0x0020) + +#define IPW_TX_QUEUE_1 1 +#define IPW_TX_QUEUE_2 2 +#define IPW_TX_QUEUE_3 3 +#define IPW_TX_QUEUE_4 4 + +/* QoS sturctures */ +struct ipw_qos_info { + int qos_enable; + struct libipw_qos_parameters *def_qos_parm_OFDM; + struct libipw_qos_parameters *def_qos_parm_CCK; + u32 burst_duration_CCK; + u32 burst_duration_OFDM; + u16 qos_no_ack_mask; + int burst_enable; +}; + +/**************************************************************/ +/** + * Generic queue structure + * + * Contains common data for Rx and Tx queues + */ +struct clx2_queue { + int n_bd; /**< number of BDs in this queue */ + int first_empty; /**< 1-st empty entry (index) */ + int last_used; /**< last used entry (index) */ + u32 reg_w; /**< 'write' reg (queue head), addr in domain 1 */ + u32 reg_r; /**< 'read' reg (queue tail), addr in domain 1 */ + dma_addr_t dma_addr; /**< physical addr for BD's */ + int low_mark; /**< low watermark, resume queue if free space more than this */ + int high_mark; /**< high watermark, stop queue if free space less than this */ +} __packed; /* XXX */ + +struct machdr32 { + __le16 frame_ctl; + __le16 duration; // watch out for endians! + u8 addr1[MACADRR_BYTE_LEN]; + u8 addr2[MACADRR_BYTE_LEN]; + u8 addr3[MACADRR_BYTE_LEN]; + __le16 seq_ctrl; // more endians! + u8 addr4[MACADRR_BYTE_LEN]; + __le16 qos_ctrl; +} __packed; + +struct machdr30 { + __le16 frame_ctl; + __le16 duration; // watch out for endians! + u8 addr1[MACADRR_BYTE_LEN]; + u8 addr2[MACADRR_BYTE_LEN]; + u8 addr3[MACADRR_BYTE_LEN]; + __le16 seq_ctrl; // more endians! + u8 addr4[MACADRR_BYTE_LEN]; +} __packed; + +struct machdr26 { + __le16 frame_ctl; + __le16 duration; // watch out for endians! + u8 addr1[MACADRR_BYTE_LEN]; + u8 addr2[MACADRR_BYTE_LEN]; + u8 addr3[MACADRR_BYTE_LEN]; + __le16 seq_ctrl; // more endians! + __le16 qos_ctrl; +} __packed; + +struct machdr24 { + __le16 frame_ctl; + __le16 duration; // watch out for endians! + u8 addr1[MACADRR_BYTE_LEN]; + u8 addr2[MACADRR_BYTE_LEN]; + u8 addr3[MACADRR_BYTE_LEN]; + __le16 seq_ctrl; // more endians! +} __packed; + +// TX TFD with 32 byte MAC Header +struct tx_tfd_32 { + struct machdr32 mchdr; // 32 + __le32 uivplaceholder[2]; // 8 +} __packed; + +// TX TFD with 30 byte MAC Header +struct tx_tfd_30 { + struct machdr30 mchdr; // 30 + u8 reserved[2]; // 2 + __le32 uivplaceholder[2]; // 8 +} __packed; + +// tx tfd with 26 byte mac header +struct tx_tfd_26 { + struct machdr26 mchdr; // 26 + u8 reserved1[2]; // 2 + __le32 uivplaceholder[2]; // 8 + u8 reserved2[4]; // 4 +} __packed; + +// tx tfd with 24 byte mac header +struct tx_tfd_24 { + struct machdr24 mchdr; // 24 + __le32 uivplaceholder[2]; // 8 + u8 reserved[8]; // 8 +} __packed; + +#define DCT_WEP_KEY_FIELD_LENGTH 16 + +struct tfd_command { + u8 index; + u8 length; + __le16 reserved; + u8 payload[0]; +} __packed; + +struct tfd_data { + /* Header */ + __le32 work_area_ptr; + u8 station_number; /* 0 for BSS */ + u8 reserved1; + __le16 reserved2; + + /* Tx Parameters */ + u8 cmd_id; + u8 seq_num; + __le16 len; + u8 priority; + u8 tx_flags; + u8 tx_flags_ext; + u8 key_index; + u8 wepkey[DCT_WEP_KEY_FIELD_LENGTH]; + u8 rate; + u8 antenna; + __le16 next_packet_duration; + __le16 next_frag_len; + __le16 back_off_counter; //////txop; + u8 retrylimit; + __le16 cwcurrent; + u8 reserved3; + + /* 802.11 MAC Header */ + union { + struct tx_tfd_24 tfd_24; + struct tx_tfd_26 tfd_26; + struct tx_tfd_30 tfd_30; + struct tx_tfd_32 tfd_32; + } tfd; + + /* Payload DMA info */ + __le32 num_chunks; + __le32 chunk_ptr[NUM_TFD_CHUNKS]; + __le16 chunk_len[NUM_TFD_CHUNKS]; +} __packed; + +struct txrx_control_flags { + u8 message_type; + u8 rx_seq_num; + u8 control_bits; + u8 reserved; +} __packed; + +#define TFD_SIZE 128 +#define TFD_CMD_IMMEDIATE_PAYLOAD_LENGTH (TFD_SIZE - sizeof(struct txrx_control_flags)) + +struct tfd_frame { + struct txrx_control_flags control_flags; + union { + struct tfd_data data; + struct tfd_command cmd; + u8 raw[TFD_CMD_IMMEDIATE_PAYLOAD_LENGTH]; + } u; +} __packed; + +typedef void destructor_func(const void *); + +/** + * Tx Queue for DMA. Queue consists of circular buffer of + * BD's and required locking structures. + */ +struct clx2_tx_queue { + struct clx2_queue q; + struct tfd_frame *bd; + struct libipw_txb **txb; +}; + +/* + * RX related structures and functions + */ +#define RX_FREE_BUFFERS 32 +#define RX_LOW_WATERMARK 8 + +#define SUP_RATE_11A_MAX_NUM_CHANNELS 8 +#define SUP_RATE_11B_MAX_NUM_CHANNELS 4 +#define SUP_RATE_11G_MAX_NUM_CHANNELS 12 + +// Used for passing to driver number of successes and failures per rate +struct rate_histogram { + union { + __le32 a[SUP_RATE_11A_MAX_NUM_CHANNELS]; + __le32 b[SUP_RATE_11B_MAX_NUM_CHANNELS]; + __le32 g[SUP_RATE_11G_MAX_NUM_CHANNELS]; + } success; + union { + __le32 a[SUP_RATE_11A_MAX_NUM_CHANNELS]; + __le32 b[SUP_RATE_11B_MAX_NUM_CHANNELS]; + __le32 g[SUP_RATE_11G_MAX_NUM_CHANNELS]; + } failed; +} __packed; + +/* statistics command response */ +struct ipw_cmd_stats { + u8 cmd_id; + u8 seq_num; + __le16 good_sfd; + __le16 bad_plcp; + __le16 wrong_bssid; + __le16 valid_mpdu; + __le16 bad_mac_header; + __le16 reserved_frame_types; + __le16 rx_ina; + __le16 bad_crc32; + __le16 invalid_cts; + __le16 invalid_acks; + __le16 long_distance_ina_fina; + __le16 dsp_silence_unreachable; + __le16 accumulated_rssi; + __le16 rx_ovfl_frame_tossed; + __le16 rssi_silence_threshold; + __le16 rx_ovfl_frame_supplied; + __le16 last_rx_frame_signal; + __le16 last_rx_frame_noise; + __le16 rx_autodetec_no_ofdm; + __le16 rx_autodetec_no_barker; + __le16 reserved; +} __packed; + +struct notif_channel_result { + u8 channel_num; + struct ipw_cmd_stats stats; + u8 uReserved; +} __packed; + +#define SCAN_COMPLETED_STATUS_COMPLETE 1 +#define SCAN_COMPLETED_STATUS_ABORTED 2 + +struct notif_scan_complete { + u8 scan_type; + u8 num_channels; + u8 status; + u8 reserved; +} __packed; + +struct notif_frag_length { + __le16 frag_length; + __le16 reserved; +} __packed; + +struct notif_beacon_state { + __le32 state; + __le32 number; +} __packed; + +struct notif_tgi_tx_key { + u8 key_state; + u8 security_type; + u8 station_index; + u8 reserved; +} __packed; + +#define SILENCE_OVER_THRESH (1) +#define SILENCE_UNDER_THRESH (2) + +struct notif_link_deterioration { + struct ipw_cmd_stats stats; + u8 rate; + u8 modulation; + struct rate_histogram histogram; + u8 silence_notification_type; /* SILENCE_OVER/UNDER_THRESH */ + __le16 silence_count; +} __packed; + +struct notif_association { + u8 state; +} __packed; + +struct notif_authenticate { + u8 state; + struct machdr24 addr; + __le16 status; +} __packed; + +struct notif_calibration { + u8 data[104]; +} __packed; + +struct notif_noise { + __le32 value; +} __packed; + +struct ipw_rx_notification { + u8 reserved[8]; + u8 subtype; + u8 flags; + __le16 size; + union { + struct notif_association assoc; + struct notif_authenticate auth; + struct notif_channel_result channel_result; + struct notif_scan_complete scan_complete; + struct notif_frag_length frag_len; + struct notif_beacon_state beacon_state; + struct notif_tgi_tx_key tgi_tx_key; + struct notif_link_deterioration link_deterioration; + struct notif_calibration calibration; + struct notif_noise noise; + u8 raw[0]; + } u; +} __packed; + +struct ipw_rx_frame { + __le32 reserved1; + u8 parent_tsf[4]; // fw_use[0] is boolean for OUR_TSF_IS_GREATER + u8 received_channel; // The channel that this frame was received on. + // Note that for .11b this does not have to be + // the same as the channel that it was sent. + // Filled by LMAC + u8 frameStatus; + u8 rate; + u8 rssi; + u8 agc; + u8 rssi_dbm; + __le16 signal; + __le16 noise; + u8 antennaAndPhy; + u8 control; // control bit should be on in bg + u8 rtscts_rate; // rate of rts or cts (in rts cts sequence rate + // is identical) + u8 rtscts_seen; // 0x1 RTS seen ; 0x2 CTS seen + __le16 length; + u8 data[0]; +} __packed; + +struct ipw_rx_header { + u8 message_type; + u8 rx_seq_num; + u8 control_bits; + u8 reserved; +} __packed; + +struct ipw_rx_packet { + struct ipw_rx_header header; + union { + struct ipw_rx_frame frame; + struct ipw_rx_notification notification; + } u; +} __packed; + +#define IPW_RX_NOTIFICATION_SIZE sizeof(struct ipw_rx_header) + 12 +#define IPW_RX_FRAME_SIZE (unsigned int)(sizeof(struct ipw_rx_header) + \ + sizeof(struct ipw_rx_frame)) + +struct ipw_rx_mem_buffer { + dma_addr_t dma_addr; + struct sk_buff *skb; + struct list_head list; +}; /* Not transferred over network, so not __packed */ + +struct ipw_rx_queue { + struct ipw_rx_mem_buffer pool[RX_QUEUE_SIZE + RX_FREE_BUFFERS]; + struct ipw_rx_mem_buffer *queue[RX_QUEUE_SIZE]; + u32 processed; /* Internal index to last handled Rx packet */ + u32 read; /* Shared index to newest available Rx buffer */ + u32 write; /* Shared index to oldest written Rx packet */ + u32 free_count; /* Number of pre-allocated buffers in rx_free */ + /* Each of these lists is used as a FIFO for ipw_rx_mem_buffers */ + struct list_head rx_free; /* Own an SKBs */ + struct list_head rx_used; /* No SKB allocated */ + spinlock_t lock; +}; /* Not transferred over network, so not __packed */ + +struct alive_command_responce { + u8 alive_command; + u8 sequence_number; + __le16 software_revision; + u8 device_identifier; + u8 reserved1[5]; + __le16 reserved2; + __le16 reserved3; + __le16 clock_settle_time; + __le16 powerup_settle_time; + __le16 reserved4; + u8 time_stamp[5]; /* month, day, year, hours, minutes */ + u8 ucode_valid; +} __packed; + +#define IPW_MAX_RATES 12 + +struct ipw_rates { + u8 num_rates; + u8 rates[IPW_MAX_RATES]; +} __packed; + +struct command_block { + unsigned int control; + u32 source_addr; + u32 dest_addr; + unsigned int status; +} __packed; + +#define CB_NUMBER_OF_ELEMENTS_SMALL 64 +struct fw_image_desc { + unsigned long last_cb_index; + unsigned long current_cb_index; + struct command_block cb_list[CB_NUMBER_OF_ELEMENTS_SMALL]; + void *v_addr; + unsigned long p_addr; + unsigned long len; +}; + +struct ipw_sys_config { + u8 bt_coexistence; + u8 reserved1; + u8 answer_broadcast_ssid_probe; + u8 accept_all_data_frames; + u8 accept_non_directed_frames; + u8 exclude_unicast_unencrypted; + u8 disable_unicast_decryption; + u8 exclude_multicast_unencrypted; + u8 disable_multicast_decryption; + u8 antenna_diversity; + u8 pass_crc_to_host; + u8 dot11g_auto_detection; + u8 enable_cts_to_self; + u8 enable_multicast_filtering; + u8 bt_coexist_collision_thr; + u8 silence_threshold; + u8 accept_all_mgmt_bcpr; + u8 accept_all_mgmt_frames; + u8 pass_noise_stats_to_host; + u8 reserved3; +} __packed; + +struct ipw_multicast_addr { + u8 num_of_multicast_addresses; + u8 reserved[3]; + u8 mac1[6]; + u8 mac2[6]; + u8 mac3[6]; + u8 mac4[6]; +} __packed; + +#define DCW_WEP_KEY_INDEX_MASK 0x03 /* bits [0:1] */ +#define DCW_WEP_KEY_SEC_TYPE_MASK 0x30 /* bits [4:5] */ + +#define DCW_WEP_KEY_SEC_TYPE_WEP 0x00 +#define DCW_WEP_KEY_SEC_TYPE_CCM 0x20 +#define DCW_WEP_KEY_SEC_TYPE_TKIP 0x30 + +#define DCW_WEP_KEY_INVALID_SIZE 0x00 /* 0 = Invalid key */ +#define DCW_WEP_KEY64Bit_SIZE 0x05 /* 64-bit encryption */ +#define DCW_WEP_KEY128Bit_SIZE 0x0D /* 128-bit encryption */ +#define DCW_CCM_KEY128Bit_SIZE 0x10 /* 128-bit key */ +//#define DCW_WEP_KEY128BitIV_SIZE 0x10 /* 128-bit key and 128-bit IV */ + +struct ipw_wep_key { + u8 cmd_id; + u8 seq_num; + u8 key_index; + u8 key_size; + u8 key[16]; +} __packed; + +struct ipw_tgi_tx_key { + u8 key_id; + u8 security_type; + u8 station_index; + u8 flags; + u8 key[16]; + __le32 tx_counter[2]; +} __packed; + +#define IPW_SCAN_CHANNELS 54 + +struct ipw_scan_request { + u8 scan_type; + __le16 dwell_time; + u8 channels_list[IPW_SCAN_CHANNELS]; + u8 channels_reserved[3]; +} __packed; + +enum { + IPW_SCAN_PASSIVE_TILL_FIRST_BEACON_SCAN = 0, + IPW_SCAN_PASSIVE_FULL_DWELL_SCAN, + IPW_SCAN_ACTIVE_DIRECT_SCAN, + IPW_SCAN_ACTIVE_BROADCAST_SCAN, + IPW_SCAN_ACTIVE_BROADCAST_AND_DIRECT_SCAN, + IPW_SCAN_TYPES +}; + +struct ipw_scan_request_ext { + __le32 full_scan_index; + u8 channels_list[IPW_SCAN_CHANNELS]; + u8 scan_type[IPW_SCAN_CHANNELS / 2]; + u8 reserved; + __le16 dwell_time[IPW_SCAN_TYPES]; +} __packed; + +static inline u8 ipw_get_scan_type(struct ipw_scan_request_ext *scan, u8 index) +{ + if (index % 2) + return scan->scan_type[index / 2] & 0x0F; + else + return (scan->scan_type[index / 2] & 0xF0) >> 4; +} + +static inline void ipw_set_scan_type(struct ipw_scan_request_ext *scan, + u8 index, u8 scan_type) +{ + if (index % 2) + scan->scan_type[index / 2] = + (scan->scan_type[index / 2] & 0xF0) | (scan_type & 0x0F); + else + scan->scan_type[index / 2] = + (scan->scan_type[index / 2] & 0x0F) | + ((scan_type & 0x0F) << 4); +} + +struct ipw_associate { + u8 channel; +#ifdef __LITTLE_ENDIAN_BITFIELD + u8 auth_type:4, auth_key:4; +#else + u8 auth_key:4, auth_type:4; +#endif + u8 assoc_type; + u8 reserved; + __le16 policy_support; + u8 preamble_length; + u8 ieee_mode; + u8 bssid[ETH_ALEN]; + __le32 assoc_tsf_msw; + __le32 assoc_tsf_lsw; + __le16 capability; + __le16 listen_interval; + __le16 beacon_interval; + u8 dest[ETH_ALEN]; + __le16 atim_window; + u8 smr; + u8 reserved1; + __le16 reserved2; +} __packed; + +struct ipw_supported_rates { + u8 ieee_mode; + u8 num_rates; + u8 purpose; + u8 reserved; + u8 supported_rates[IPW_MAX_RATES]; +} __packed; + +struct ipw_rts_threshold { + __le16 rts_threshold; + __le16 reserved; +} __packed; + +struct ipw_frag_threshold { + __le16 frag_threshold; + __le16 reserved; +} __packed; + +struct ipw_retry_limit { + u8 short_retry_limit; + u8 long_retry_limit; + __le16 reserved; +} __packed; + +struct ipw_dino_config { + __le32 dino_config_addr; + __le16 dino_config_size; + u8 dino_response; + u8 reserved; +} __packed; + +struct ipw_aironet_info { + u8 id; + u8 length; + __le16 reserved; +} __packed; + +struct ipw_rx_key { + u8 station_index; + u8 key_type; + u8 key_id; + u8 key_flag; + u8 key[16]; + u8 station_address[6]; + u8 key_index; + u8 reserved; +} __packed; + +struct ipw_country_channel_info { + u8 first_channel; + u8 no_channels; + s8 max_tx_power; +} __packed; + +struct ipw_country_info { + u8 id; + u8 length; + u8 country_str[IEEE80211_COUNTRY_STRING_LEN]; + struct ipw_country_channel_info groups[7]; +} __packed; + +struct ipw_channel_tx_power { + u8 channel_number; + s8 tx_power; +} __packed; + +#define SCAN_ASSOCIATED_INTERVAL (HZ) +#define SCAN_INTERVAL (HZ / 10) +#define MAX_A_CHANNELS 37 +#define MAX_B_CHANNELS 14 + +struct ipw_tx_power { + u8 num_channels; + u8 ieee_mode; + struct ipw_channel_tx_power channels_tx_power[MAX_A_CHANNELS]; +} __packed; + +struct ipw_rsn_capabilities { + u8 id; + u8 length; + __le16 version; +} __packed; + +struct ipw_sensitivity_calib { + __le16 beacon_rssi_raw; + __le16 reserved; +} __packed; + +/** + * Host command structure. + * + * On input, the following fields should be filled: + * - cmd + * - len + * - status_len + * - param (if needed) + * + * On output, + * - \a status contains status; + * - \a param filled with status parameters. + */ +struct ipw_cmd { /* XXX */ + u32 cmd; /**< Host command */ + u32 status;/**< Status */ + u32 status_len; + /**< How many 32 bit parameters in the status */ + u32 len; /**< incoming parameters length, bytes */ + /** + * command parameters. + * There should be enough space for incoming and + * outcoming parameters. + * Incoming parameters listed 1-st, followed by outcoming params. + * nParams=(len+3)/4+status_len + */ + u32 param[0]; +} __packed; + +#define STATUS_HCMD_ACTIVE (1<<0) /**< host command in progress */ + +#define STATUS_INT_ENABLED (1<<1) +#define STATUS_RF_KILL_HW (1<<2) +#define STATUS_RF_KILL_SW (1<<3) +#define STATUS_RF_KILL_MASK (STATUS_RF_KILL_HW | STATUS_RF_KILL_SW) + +#define STATUS_INIT (1<<5) +#define STATUS_AUTH (1<<6) +#define STATUS_ASSOCIATED (1<<7) +#define STATUS_STATE_MASK (STATUS_INIT | STATUS_AUTH | STATUS_ASSOCIATED) + +#define STATUS_ASSOCIATING (1<<8) +#define STATUS_DISASSOCIATING (1<<9) +#define STATUS_ROAMING (1<<10) +#define STATUS_EXIT_PENDING (1<<11) +#define STATUS_DISASSOC_PENDING (1<<12) +#define STATUS_STATE_PENDING (1<<13) + +#define STATUS_DIRECT_SCAN_PENDING (1<<19) +#define STATUS_SCAN_PENDING (1<<20) +#define STATUS_SCANNING (1<<21) +#define STATUS_SCAN_ABORTING (1<<22) +#define STATUS_SCAN_FORCED (1<<23) + +#define STATUS_LED_LINK_ON (1<<24) +#define STATUS_LED_ACT_ON (1<<25) + +#define STATUS_INDIRECT_BYTE (1<<28) /* sysfs entry configured for access */ +#define STATUS_INDIRECT_DWORD (1<<29) /* sysfs entry configured for access */ +#define STATUS_DIRECT_DWORD (1<<30) /* sysfs entry configured for access */ + +#define STATUS_SECURITY_UPDATED (1<<31) /* Security sync needed */ + +#define CFG_STATIC_CHANNEL (1<<0) /* Restrict assoc. to single channel */ +#define CFG_STATIC_ESSID (1<<1) /* Restrict assoc. to single SSID */ +#define CFG_STATIC_BSSID (1<<2) /* Restrict assoc. to single BSSID */ +#define CFG_CUSTOM_MAC (1<<3) +#define CFG_PREAMBLE_LONG (1<<4) +#define CFG_ADHOC_PERSIST (1<<5) +#define CFG_ASSOCIATE (1<<6) +#define CFG_FIXED_RATE (1<<7) +#define CFG_ADHOC_CREATE (1<<8) +#define CFG_NO_LED (1<<9) +#define CFG_BACKGROUND_SCAN (1<<10) +#define CFG_SPEED_SCAN (1<<11) +#define CFG_NET_STATS (1<<12) + +#define CAP_SHARED_KEY (1<<0) /* Off = OPEN */ +#define CAP_PRIVACY_ON (1<<1) /* Off = No privacy */ + +#define MAX_STATIONS 32 +#define IPW_INVALID_STATION (0xff) + +struct ipw_station_entry { + u8 mac_addr[ETH_ALEN]; + u8 reserved; + u8 support_mode; +}; + +#define AVG_ENTRIES 8 +struct average { + s16 entries[AVG_ENTRIES]; + u8 pos; + u8 init; + s32 sum; +}; + +#define MAX_SPEED_SCAN 100 +#define IPW_IBSS_MAC_HASH_SIZE 31 + +struct ipw_ibss_seq { + u8 mac[ETH_ALEN]; + u16 seq_num; + u16 frag_num; + unsigned long packet_time; + struct list_head list; +}; + +struct ipw_error_elem { /* XXX */ + u32 desc; + u32 time; + u32 blink1; + u32 blink2; + u32 link1; + u32 link2; + u32 data; +}; + +struct ipw_event { /* XXX */ + u32 event; + u32 time; + u32 data; +} __packed; + +struct ipw_fw_error { /* XXX */ + unsigned long jiffies; + u32 status; + u32 config; + u32 elem_len; + u32 log_len; + struct ipw_error_elem *elem; + struct ipw_event *log; + u8 payload[0]; +} __packed; + +#ifdef CPTCFG_IPW2200_PROMISCUOUS + +enum ipw_prom_filter { + IPW_PROM_CTL_HEADER_ONLY = (1 << 0), + IPW_PROM_MGMT_HEADER_ONLY = (1 << 1), + IPW_PROM_DATA_HEADER_ONLY = (1 << 2), + IPW_PROM_ALL_HEADER_ONLY = 0xf, /* bits 0..3 */ + IPW_PROM_NO_TX = (1 << 4), + IPW_PROM_NO_RX = (1 << 5), + IPW_PROM_NO_CTL = (1 << 6), + IPW_PROM_NO_MGMT = (1 << 7), + IPW_PROM_NO_DATA = (1 << 8), +}; + +struct ipw_priv; +struct ipw_prom_priv { + struct ipw_priv *priv; + struct libipw_device *ieee; + enum ipw_prom_filter filter; + int tx_packets; + int rx_packets; +}; +#endif + +#if defined(CPTCFG_IPW2200_RADIOTAP) || defined(CPTCFG_IPW2200_PROMISCUOUS) +/* Magic struct that slots into the radiotap header -- no reason + * to build this manually element by element, we can write it much + * more efficiently than we can parse it. ORDER MATTERS HERE + * + * When sent to us via the simulated Rx interface in sysfs, the entire + * structure is provided regardless of any bits unset. + */ +struct ipw_rt_hdr { + struct ieee80211_radiotap_header rt_hdr; + u64 rt_tsf; /* TSF */ /* XXX */ + u8 rt_flags; /* radiotap packet flags */ + u8 rt_rate; /* rate in 500kb/s */ + __le16 rt_channel; /* channel in mhz */ + __le16 rt_chbitmask; /* channel bitfield */ + s8 rt_dbmsignal; /* signal in dbM, kluged to signed */ + s8 rt_dbmnoise; + u8 rt_antenna; /* antenna number */ + u8 payload[0]; /* payload... */ +} __packed; +#endif + +struct ipw_priv { + /* ieee device used by generic ieee processing code */ + struct libipw_device *ieee; + + spinlock_t lock; + spinlock_t irq_lock; + struct mutex mutex; + + /* basic pci-network driver stuff */ + struct pci_dev *pci_dev; + struct net_device *net_dev; + +#ifdef CPTCFG_IPW2200_PROMISCUOUS + /* Promiscuous mode */ + struct ipw_prom_priv *prom_priv; + struct net_device *prom_net_dev; +#endif + + /* pci hardware address support */ + void __iomem *hw_base; + unsigned long hw_len; + + struct fw_image_desc sram_desc; + + /* result of ucode download */ + struct alive_command_responce dino_alive; + + wait_queue_head_t wait_command_queue; + wait_queue_head_t wait_state; + + /* Rx and Tx DMA processing queues */ + struct ipw_rx_queue *rxq; + struct clx2_tx_queue txq_cmd; + struct clx2_tx_queue txq[4]; + u32 status; + u32 config; + u32 capability; + + struct average average_missed_beacons; + s16 exp_avg_rssi; + s16 exp_avg_noise; + u32 port_type; + int rx_bufs_min; /**< minimum number of bufs in Rx queue */ + int rx_pend_max; /**< maximum pending buffers for one IRQ */ + u32 hcmd_seq; /**< sequence number for hcmd */ + u32 disassociate_threshold; + u32 roaming_threshold; + + struct ipw_associate assoc_request; + struct libipw_network *assoc_network; + + unsigned long ts_scan_abort; + struct ipw_supported_rates rates; + struct ipw_rates phy[3]; /**< PHY restrictions, per band */ + struct ipw_rates supp; /**< software defined */ + struct ipw_rates extended; /**< use for corresp. IE, AP only */ + + struct notif_link_deterioration last_link_deterioration; /** for statistics */ + struct ipw_cmd *hcmd; /**< host command currently executed */ + + wait_queue_head_t hcmd_wq; /**< host command waits for execution */ + u32 tsf_bcn[2]; /**< TSF from latest beacon */ + + struct notif_calibration calib; /**< last calibration */ + + /* ordinal interface with firmware */ + u32 table0_addr; + u32 table0_len; + u32 table1_addr; + u32 table1_len; + u32 table2_addr; + u32 table2_len; + + /* context information */ + u8 essid[IW_ESSID_MAX_SIZE]; + u8 essid_len; + u8 nick[IW_ESSID_MAX_SIZE]; + u16 rates_mask; + u8 channel; + struct ipw_sys_config sys_config; + u32 power_mode; + u8 bssid[ETH_ALEN]; + u16 rts_threshold; + u8 mac_addr[ETH_ALEN]; + u8 num_stations; + u8 stations[MAX_STATIONS][ETH_ALEN]; + u8 short_retry_limit; + u8 long_retry_limit; + + u32 notif_missed_beacons; + + /* Statistics and counters normalized with each association */ + u32 last_missed_beacons; + u32 last_tx_packets; + u32 last_rx_packets; + u32 last_tx_failures; + u32 last_rx_err; + u32 last_rate; + + u32 missed_adhoc_beacons; + u32 missed_beacons; + u32 rx_packets; + u32 tx_packets; + u32 quality; + + u8 speed_scan[MAX_SPEED_SCAN]; + u8 speed_scan_pos; + + u16 last_seq_num; + u16 last_frag_num; + unsigned long last_packet_time; + struct list_head ibss_mac_hash[IPW_IBSS_MAC_HASH_SIZE]; + + /* eeprom */ + u8 eeprom[0x100]; /* 256 bytes of eeprom */ + u8 country[4]; + int eeprom_delay; + + struct iw_statistics wstats; + + struct iw_public_data wireless_data; + + int user_requested_scan; + u8 direct_scan_ssid[IW_ESSID_MAX_SIZE]; + u8 direct_scan_ssid_len; + + struct delayed_work adhoc_check; + struct work_struct associate; + struct work_struct disassociate; + struct work_struct system_config; + struct work_struct rx_replenish; + struct delayed_work request_scan; + struct delayed_work request_direct_scan; + struct delayed_work request_passive_scan; + struct delayed_work scan_event; + struct work_struct adapter_restart; + struct delayed_work rf_kill; + struct work_struct up; + struct work_struct down; + struct delayed_work gather_stats; + struct work_struct abort_scan; + struct work_struct roam; + struct delayed_work scan_check; + struct work_struct link_up; + struct work_struct link_down; + + struct tasklet_struct irq_tasklet; + + /* LED related variables and work_struct */ + u8 nic_type; + u32 led_activity_on; + u32 led_activity_off; + u32 led_association_on; + u32 led_association_off; + u32 led_ofdm_on; + u32 led_ofdm_off; + + struct delayed_work led_link_on; + struct delayed_work led_link_off; + struct delayed_work led_act_off; + struct work_struct merge_networks; + + struct ipw_cmd_log *cmdlog; + int cmdlog_len; + int cmdlog_pos; + +#define IPW_2200BG 1 +#define IPW_2915ABG 2 + u8 adapter; + + s8 tx_power; + + /* Track time in suspend */ + unsigned long suspend_at; + unsigned long suspend_time; + +#ifdef CONFIG_PM + u32 pm_state[16]; +#endif + + struct ipw_fw_error *error; + + /* network state */ + + /* Used to pass the current INTA value from ISR to Tasklet */ + u32 isr_inta; + + /* QoS */ + struct ipw_qos_info qos_data; + struct work_struct qos_activate; + /*********************************/ + + /* debugging info */ + u32 indirect_dword; + u32 direct_dword; + u32 indirect_byte; +}; /*ipw_priv */ + +/* debug macros */ + +/* Debug and printf string expansion helpers for printing bitfields */ +#define BIT_FMT8 "%c%c%c%c-%c%c%c%c" +#define BIT_FMT16 BIT_FMT8 ":" BIT_FMT8 +#define BIT_FMT32 BIT_FMT16 " " BIT_FMT16 + +#define BITC(x,y) (((x>>y)&1)?'1':'0') +#define BIT_ARG8(x) \ +BITC(x,7),BITC(x,6),BITC(x,5),BITC(x,4),\ +BITC(x,3),BITC(x,2),BITC(x,1),BITC(x,0) + +#define BIT_ARG16(x) \ +BITC(x,15),BITC(x,14),BITC(x,13),BITC(x,12),\ +BITC(x,11),BITC(x,10),BITC(x,9),BITC(x,8),\ +BIT_ARG8(x) + +#define BIT_ARG32(x) \ +BITC(x,31),BITC(x,30),BITC(x,29),BITC(x,28),\ +BITC(x,27),BITC(x,26),BITC(x,25),BITC(x,24),\ +BITC(x,23),BITC(x,22),BITC(x,21),BITC(x,20),\ +BITC(x,19),BITC(x,18),BITC(x,17),BITC(x,16),\ +BIT_ARG16(x) + + +#define IPW_DEBUG(level, fmt, args...) \ +do { if (ipw_debug_level & (level)) \ + printk(KERN_DEBUG DRV_NAME": %c %s " fmt, \ + in_interrupt() ? 'I' : 'U', __func__ , ## args); } while (0) + +#ifdef CPTCFG_IPW2200_DEBUG +#define IPW_LL_DEBUG(level, fmt, args...) \ +do { if (ipw_debug_level & (level)) \ + printk(KERN_DEBUG DRV_NAME": %c %s " fmt, \ + in_interrupt() ? 'I' : 'U', __func__ , ## args); } while (0) +#else +#define IPW_LL_DEBUG(level, fmt, args...) do {} while (0) +#endif /* CPTCFG_IPW2200_DEBUG */ + +/* + * To use the debug system; + * + * If you are defining a new debug classification, simply add it to the #define + * list here in the form of: + * + * #define IPW_DL_xxxx VALUE + * + * shifting value to the left one bit from the previous entry. xxxx should be + * the name of the classification (for example, WEP) + * + * You then need to either add a IPW_xxxx_DEBUG() macro definition for your + * classification, or use IPW_DEBUG(IPW_DL_xxxx, ...) whenever you want + * to send output to that classification. + * + * To add your debug level to the list of levels seen when you perform + * + * % cat /proc/net/ipw/debug_level + * + * you simply need to add your entry to the ipw_debug_levels array. + * + * If you do not see debug_level in /proc/net/ipw then you do not have + * CPTCFG_IPW2200_DEBUG defined in your kernel configuration + * + */ + +#define IPW_DL_ERROR (1<<0) +#define IPW_DL_WARNING (1<<1) +#define IPW_DL_INFO (1<<2) +#define IPW_DL_WX (1<<3) +#define IPW_DL_HOST_COMMAND (1<<5) +#define IPW_DL_STATE (1<<6) + +#define IPW_DL_NOTIF (1<<10) +#define IPW_DL_SCAN (1<<11) +#define IPW_DL_ASSOC (1<<12) +#define IPW_DL_DROP (1<<13) +#define IPW_DL_IOCTL (1<<14) + +#define IPW_DL_MANAGE (1<<15) +#define IPW_DL_FW (1<<16) +#define IPW_DL_RF_KILL (1<<17) +#define IPW_DL_FW_ERRORS (1<<18) + +#define IPW_DL_LED (1<<19) + +#define IPW_DL_ORD (1<<20) + +#define IPW_DL_FRAG (1<<21) +#define IPW_DL_WEP (1<<22) +#define IPW_DL_TX (1<<23) +#define IPW_DL_RX (1<<24) +#define IPW_DL_ISR (1<<25) +#define IPW_DL_FW_INFO (1<<26) +#define IPW_DL_IO (1<<27) +#define IPW_DL_TRACE (1<<28) + +#define IPW_DL_STATS (1<<29) +#define IPW_DL_MERGE (1<<30) +#define IPW_DL_QOS (1<<31) + +#define IPW_ERROR(f, a...) printk(KERN_ERR DRV_NAME ": " f, ## a) +#define IPW_WARNING(f, a...) printk(KERN_WARNING DRV_NAME ": " f, ## a) +#define IPW_DEBUG_INFO(f, a...) IPW_DEBUG(IPW_DL_INFO, f, ## a) + +#define IPW_DEBUG_WX(f, a...) IPW_DEBUG(IPW_DL_WX, f, ## a) +#define IPW_DEBUG_SCAN(f, a...) IPW_DEBUG(IPW_DL_SCAN, f, ## a) +#define IPW_DEBUG_TRACE(f, a...) IPW_LL_DEBUG(IPW_DL_TRACE, f, ## a) +#define IPW_DEBUG_RX(f, a...) IPW_LL_DEBUG(IPW_DL_RX, f, ## a) +#define IPW_DEBUG_TX(f, a...) IPW_LL_DEBUG(IPW_DL_TX, f, ## a) +#define IPW_DEBUG_ISR(f, a...) IPW_LL_DEBUG(IPW_DL_ISR, f, ## a) +#define IPW_DEBUG_MANAGEMENT(f, a...) IPW_DEBUG(IPW_DL_MANAGE, f, ## a) +#define IPW_DEBUG_LED(f, a...) IPW_LL_DEBUG(IPW_DL_LED, f, ## a) +#define IPW_DEBUG_WEP(f, a...) IPW_LL_DEBUG(IPW_DL_WEP, f, ## a) +#define IPW_DEBUG_HC(f, a...) IPW_LL_DEBUG(IPW_DL_HOST_COMMAND, f, ## a) +#define IPW_DEBUG_FRAG(f, a...) IPW_LL_DEBUG(IPW_DL_FRAG, f, ## a) +#define IPW_DEBUG_FW(f, a...) IPW_LL_DEBUG(IPW_DL_FW, f, ## a) +#define IPW_DEBUG_RF_KILL(f, a...) IPW_DEBUG(IPW_DL_RF_KILL, f, ## a) +#define IPW_DEBUG_DROP(f, a...) IPW_DEBUG(IPW_DL_DROP, f, ## a) +#define IPW_DEBUG_IO(f, a...) IPW_LL_DEBUG(IPW_DL_IO, f, ## a) +#define IPW_DEBUG_ORD(f, a...) IPW_LL_DEBUG(IPW_DL_ORD, f, ## a) +#define IPW_DEBUG_FW_INFO(f, a...) IPW_LL_DEBUG(IPW_DL_FW_INFO, f, ## a) +#define IPW_DEBUG_NOTIF(f, a...) IPW_DEBUG(IPW_DL_NOTIF, f, ## a) +#define IPW_DEBUG_STATE(f, a...) IPW_DEBUG(IPW_DL_STATE | IPW_DL_ASSOC | IPW_DL_INFO, f, ## a) +#define IPW_DEBUG_ASSOC(f, a...) IPW_DEBUG(IPW_DL_ASSOC | IPW_DL_INFO, f, ## a) +#define IPW_DEBUG_STATS(f, a...) IPW_LL_DEBUG(IPW_DL_STATS, f, ## a) +#define IPW_DEBUG_MERGE(f, a...) IPW_LL_DEBUG(IPW_DL_MERGE, f, ## a) +#define IPW_DEBUG_QOS(f, a...) IPW_LL_DEBUG(IPW_DL_QOS, f, ## a) + +#include + +/* +* Register bit definitions +*/ + +#define IPW_INTA_RW 0x00000008 +#define IPW_INTA_MASK_R 0x0000000C +#define IPW_INDIRECT_ADDR 0x00000010 +#define IPW_INDIRECT_DATA 0x00000014 +#define IPW_AUTOINC_ADDR 0x00000018 +#define IPW_AUTOINC_DATA 0x0000001C +#define IPW_RESET_REG 0x00000020 +#define IPW_GP_CNTRL_RW 0x00000024 + +#define IPW_READ_INT_REGISTER 0xFF4 + +#define IPW_GP_CNTRL_BIT_INIT_DONE 0x00000004 + +#define IPW_REGISTER_DOMAIN1_END 0x00001000 +#define IPW_SRAM_READ_INT_REGISTER 0x00000ff4 + +#define IPW_SHARED_LOWER_BOUND 0x00000200 +#define IPW_INTERRUPT_AREA_LOWER_BOUND 0x00000f80 + +#define IPW_NIC_SRAM_LOWER_BOUND 0x00000000 +#define IPW_NIC_SRAM_UPPER_BOUND 0x00030000 + +#define IPW_BIT_INT_HOST_SRAM_READ_INT_REGISTER (1 << 29) +#define IPW_GP_CNTRL_BIT_CLOCK_READY 0x00000001 +#define IPW_GP_CNTRL_BIT_HOST_ALLOWS_STANDBY 0x00000002 + +/* + * RESET Register Bit Indexes + */ +#define CBD_RESET_REG_PRINCETON_RESET (1<<0) +#define IPW_START_STANDBY (1<<2) +#define IPW_ACTIVITY_LED (1<<4) +#define IPW_ASSOCIATED_LED (1<<5) +#define IPW_OFDM_LED (1<<6) +#define IPW_RESET_REG_SW_RESET (1<<7) +#define IPW_RESET_REG_MASTER_DISABLED (1<<8) +#define IPW_RESET_REG_STOP_MASTER (1<<9) +#define IPW_GATE_ODMA (1<<25) +#define IPW_GATE_IDMA (1<<26) +#define IPW_ARC_KESHET_CONFIG (1<<27) +#define IPW_GATE_ADMA (1<<29) + +#define IPW_CSR_CIS_UPPER_BOUND 0x00000200 +#define IPW_DOMAIN_0_END 0x1000 +#define CLX_MEM_BAR_SIZE 0x1000 + +/* Dino/baseband control registers bits */ + +#define DINO_ENABLE_SYSTEM 0x80 /* 1 = baseband processor on, 0 = reset */ +#define DINO_ENABLE_CS 0x40 /* 1 = enable ucode load */ +#define DINO_RXFIFO_DATA 0x01 /* 1 = data available */ +#define IPW_BASEBAND_CONTROL_STATUS 0X00200000 +#define IPW_BASEBAND_TX_FIFO_WRITE 0X00200004 +#define IPW_BASEBAND_RX_FIFO_READ 0X00200004 +#define IPW_BASEBAND_CONTROL_STORE 0X00200010 + +#define IPW_INTERNAL_CMD_EVENT 0X00300004 +#define IPW_BASEBAND_POWER_DOWN 0x00000001 + +#define IPW_MEM_HALT_AND_RESET 0x003000e0 + +/* defgroup bits_halt_reset MEM_HALT_AND_RESET register bits */ +#define IPW_BIT_HALT_RESET_ON 0x80000000 +#define IPW_BIT_HALT_RESET_OFF 0x00000000 + +#define CB_LAST_VALID 0x20000000 +#define CB_INT_ENABLED 0x40000000 +#define CB_VALID 0x80000000 +#define CB_SRC_LE 0x08000000 +#define CB_DEST_LE 0x04000000 +#define CB_SRC_AUTOINC 0x00800000 +#define CB_SRC_IO_GATED 0x00400000 +#define CB_DEST_AUTOINC 0x00080000 +#define CB_SRC_SIZE_LONG 0x00200000 +#define CB_DEST_SIZE_LONG 0x00020000 + +/* DMA DEFINES */ + +#define DMA_CONTROL_SMALL_CB_CONST_VALUE 0x00540000 +#define DMA_CB_STOP_AND_ABORT 0x00000C00 +#define DMA_CB_START 0x00000100 + +#define IPW_SHARED_SRAM_SIZE 0x00030000 +#define IPW_SHARED_SRAM_DMA_CONTROL 0x00027000 +#define CB_MAX_LENGTH 0x1FFF + +#define IPW_HOST_EEPROM_DATA_SRAM_SIZE 0xA18 +#define IPW_EEPROM_IMAGE_SIZE 0x100 + +/* DMA defs */ +#define IPW_DMA_I_CURRENT_CB 0x003000D0 +#define IPW_DMA_O_CURRENT_CB 0x003000D4 +#define IPW_DMA_I_DMA_CONTROL 0x003000A4 +#define IPW_DMA_I_CB_BASE 0x003000A0 + +#define IPW_TX_CMD_QUEUE_BD_BASE 0x00000200 +#define IPW_TX_CMD_QUEUE_BD_SIZE 0x00000204 +#define IPW_TX_QUEUE_0_BD_BASE 0x00000208 +#define IPW_TX_QUEUE_0_BD_SIZE (0x0000020C) +#define IPW_TX_QUEUE_1_BD_BASE 0x00000210 +#define IPW_TX_QUEUE_1_BD_SIZE 0x00000214 +#define IPW_TX_QUEUE_2_BD_BASE 0x00000218 +#define IPW_TX_QUEUE_2_BD_SIZE (0x0000021C) +#define IPW_TX_QUEUE_3_BD_BASE 0x00000220 +#define IPW_TX_QUEUE_3_BD_SIZE 0x00000224 +#define IPW_RX_BD_BASE 0x00000240 +#define IPW_RX_BD_SIZE 0x00000244 +#define IPW_RFDS_TABLE_LOWER 0x00000500 + +#define IPW_TX_CMD_QUEUE_READ_INDEX 0x00000280 +#define IPW_TX_QUEUE_0_READ_INDEX 0x00000284 +#define IPW_TX_QUEUE_1_READ_INDEX 0x00000288 +#define IPW_TX_QUEUE_2_READ_INDEX (0x0000028C) +#define IPW_TX_QUEUE_3_READ_INDEX 0x00000290 +#define IPW_RX_READ_INDEX (0x000002A0) + +#define IPW_TX_CMD_QUEUE_WRITE_INDEX (0x00000F80) +#define IPW_TX_QUEUE_0_WRITE_INDEX (0x00000F84) +#define IPW_TX_QUEUE_1_WRITE_INDEX (0x00000F88) +#define IPW_TX_QUEUE_2_WRITE_INDEX (0x00000F8C) +#define IPW_TX_QUEUE_3_WRITE_INDEX (0x00000F90) +#define IPW_RX_WRITE_INDEX (0x00000FA0) + +/* + * EEPROM Related Definitions + */ + +#define IPW_EEPROM_DATA_SRAM_ADDRESS (IPW_SHARED_LOWER_BOUND + 0x814) +#define IPW_EEPROM_DATA_SRAM_SIZE (IPW_SHARED_LOWER_BOUND + 0x818) +#define IPW_EEPROM_LOAD_DISABLE (IPW_SHARED_LOWER_BOUND + 0x81C) +#define IPW_EEPROM_DATA (IPW_SHARED_LOWER_BOUND + 0x820) +#define IPW_EEPROM_UPPER_ADDRESS (IPW_SHARED_LOWER_BOUND + 0x9E0) + +#define IPW_STATION_TABLE_LOWER (IPW_SHARED_LOWER_BOUND + 0xA0C) +#define IPW_STATION_TABLE_UPPER (IPW_SHARED_LOWER_BOUND + 0xB0C) +#define IPW_REQUEST_ATIM (IPW_SHARED_LOWER_BOUND + 0xB0C) +#define IPW_ATIM_SENT (IPW_SHARED_LOWER_BOUND + 0xB10) +#define IPW_WHO_IS_AWAKE (IPW_SHARED_LOWER_BOUND + 0xB14) +#define IPW_DURING_ATIM_WINDOW (IPW_SHARED_LOWER_BOUND + 0xB18) + +#define MSB 1 +#define LSB 0 +#define WORD_TO_BYTE(_word) ((_word) * sizeof(u16)) + +#define GET_EEPROM_ADDR(_wordoffset,_byteoffset) \ + ( WORD_TO_BYTE(_wordoffset) + (_byteoffset) ) + +/* EEPROM access by BYTE */ +#define EEPROM_PME_CAPABILITY (GET_EEPROM_ADDR(0x09,MSB)) /* 1 byte */ +#define EEPROM_MAC_ADDRESS (GET_EEPROM_ADDR(0x21,LSB)) /* 6 byte */ +#define EEPROM_VERSION (GET_EEPROM_ADDR(0x24,MSB)) /* 1 byte */ +#define EEPROM_NIC_TYPE (GET_EEPROM_ADDR(0x25,LSB)) /* 1 byte */ +#define EEPROM_SKU_CAPABILITY (GET_EEPROM_ADDR(0x25,MSB)) /* 1 byte */ +#define EEPROM_COUNTRY_CODE (GET_EEPROM_ADDR(0x26,LSB)) /* 3 bytes */ +#define EEPROM_IBSS_CHANNELS_BG (GET_EEPROM_ADDR(0x28,LSB)) /* 2 bytes */ +#define EEPROM_IBSS_CHANNELS_A (GET_EEPROM_ADDR(0x29,MSB)) /* 5 bytes */ +#define EEPROM_BSS_CHANNELS_BG (GET_EEPROM_ADDR(0x2c,LSB)) /* 2 bytes */ +#define EEPROM_HW_VERSION (GET_EEPROM_ADDR(0x72,LSB)) /* 2 bytes */ + +/* NIC type as found in the one byte EEPROM_NIC_TYPE offset */ +#define EEPROM_NIC_TYPE_0 0 +#define EEPROM_NIC_TYPE_1 1 +#define EEPROM_NIC_TYPE_2 2 +#define EEPROM_NIC_TYPE_3 3 +#define EEPROM_NIC_TYPE_4 4 + +/* Bluetooth Coexistence capabilities as found in EEPROM_SKU_CAPABILITY */ +#define EEPROM_SKU_CAP_BT_CHANNEL_SIG 0x01 /* we can tell BT our channel # */ +#define EEPROM_SKU_CAP_BT_PRIORITY 0x02 /* BT can take priority over us */ +#define EEPROM_SKU_CAP_BT_OOB 0x04 /* we can signal BT out-of-band */ + +#define FW_MEM_REG_LOWER_BOUND 0x00300000 +#define FW_MEM_REG_EEPROM_ACCESS (FW_MEM_REG_LOWER_BOUND + 0x40) +#define IPW_EVENT_REG (FW_MEM_REG_LOWER_BOUND + 0x04) +#define EEPROM_BIT_SK (1<<0) +#define EEPROM_BIT_CS (1<<1) +#define EEPROM_BIT_DI (1<<2) +#define EEPROM_BIT_DO (1<<4) + +#define EEPROM_CMD_READ 0x2 + +/* Interrupts masks */ +#define IPW_INTA_NONE 0x00000000 + +#define IPW_INTA_BIT_RX_TRANSFER 0x00000002 +#define IPW_INTA_BIT_STATUS_CHANGE 0x00000010 +#define IPW_INTA_BIT_BEACON_PERIOD_EXPIRED 0x00000020 + +//Inta Bits for CF +#define IPW_INTA_BIT_TX_CMD_QUEUE 0x00000800 +#define IPW_INTA_BIT_TX_QUEUE_1 0x00001000 +#define IPW_INTA_BIT_TX_QUEUE_2 0x00002000 +#define IPW_INTA_BIT_TX_QUEUE_3 0x00004000 +#define IPW_INTA_BIT_TX_QUEUE_4 0x00008000 + +#define IPW_INTA_BIT_SLAVE_MODE_HOST_CMD_DONE 0x00010000 + +#define IPW_INTA_BIT_PREPARE_FOR_POWER_DOWN 0x00100000 +#define IPW_INTA_BIT_POWER_DOWN 0x00200000 + +#define IPW_INTA_BIT_FW_INITIALIZATION_DONE 0x01000000 +#define IPW_INTA_BIT_FW_CARD_DISABLE_PHY_OFF_DONE 0x02000000 +#define IPW_INTA_BIT_RF_KILL_DONE 0x04000000 +#define IPW_INTA_BIT_FATAL_ERROR 0x40000000 +#define IPW_INTA_BIT_PARITY_ERROR 0x80000000 + +/* Interrupts enabled at init time. */ +#define IPW_INTA_MASK_ALL \ + (IPW_INTA_BIT_TX_QUEUE_1 | \ + IPW_INTA_BIT_TX_QUEUE_2 | \ + IPW_INTA_BIT_TX_QUEUE_3 | \ + IPW_INTA_BIT_TX_QUEUE_4 | \ + IPW_INTA_BIT_TX_CMD_QUEUE | \ + IPW_INTA_BIT_RX_TRANSFER | \ + IPW_INTA_BIT_FATAL_ERROR | \ + IPW_INTA_BIT_PARITY_ERROR | \ + IPW_INTA_BIT_STATUS_CHANGE | \ + IPW_INTA_BIT_FW_INITIALIZATION_DONE | \ + IPW_INTA_BIT_BEACON_PERIOD_EXPIRED | \ + IPW_INTA_BIT_SLAVE_MODE_HOST_CMD_DONE | \ + IPW_INTA_BIT_PREPARE_FOR_POWER_DOWN | \ + IPW_INTA_BIT_POWER_DOWN | \ + IPW_INTA_BIT_RF_KILL_DONE ) + +/* FW event log definitions */ +#define EVENT_ELEM_SIZE (3 * sizeof(u32)) +#define EVENT_START_OFFSET (1 * sizeof(u32) + 2 * sizeof(u16)) + +/* FW error log definitions */ +#define ERROR_ELEM_SIZE (7 * sizeof(u32)) +#define ERROR_START_OFFSET (1 * sizeof(u32)) + +/* TX power level (dbm) */ +#define IPW_TX_POWER_MIN -12 +#define IPW_TX_POWER_MAX 20 +#define IPW_TX_POWER_DEFAULT IPW_TX_POWER_MAX + +enum { + IPW_FW_ERROR_OK = 0, + IPW_FW_ERROR_FAIL, + IPW_FW_ERROR_MEMORY_UNDERFLOW, + IPW_FW_ERROR_MEMORY_OVERFLOW, + IPW_FW_ERROR_BAD_PARAM, + IPW_FW_ERROR_BAD_CHECKSUM, + IPW_FW_ERROR_NMI_INTERRUPT, + IPW_FW_ERROR_BAD_DATABASE, + IPW_FW_ERROR_ALLOC_FAIL, + IPW_FW_ERROR_DMA_UNDERRUN, + IPW_FW_ERROR_DMA_STATUS, + IPW_FW_ERROR_DINO_ERROR, + IPW_FW_ERROR_EEPROM_ERROR, + IPW_FW_ERROR_SYSASSERT, + IPW_FW_ERROR_FATAL_ERROR +}; + +#define AUTH_OPEN 0 +#define AUTH_SHARED_KEY 1 +#define AUTH_LEAP 2 +#define AUTH_IGNORE 3 + +#define HC_ASSOCIATE 0 +#define HC_REASSOCIATE 1 +#define HC_DISASSOCIATE 2 +#define HC_IBSS_START 3 +#define HC_IBSS_RECONF 4 +#define HC_DISASSOC_QUIET 5 + +#define HC_QOS_SUPPORT_ASSOC cpu_to_le16(0x01) + +#define IPW_RATE_CAPABILITIES 1 +#define IPW_RATE_CONNECT 0 + +/* + * Rate values and masks + */ +#define IPW_TX_RATE_1MB 0x0A +#define IPW_TX_RATE_2MB 0x14 +#define IPW_TX_RATE_5MB 0x37 +#define IPW_TX_RATE_6MB 0x0D +#define IPW_TX_RATE_9MB 0x0F +#define IPW_TX_RATE_11MB 0x6E +#define IPW_TX_RATE_12MB 0x05 +#define IPW_TX_RATE_18MB 0x07 +#define IPW_TX_RATE_24MB 0x09 +#define IPW_TX_RATE_36MB 0x0B +#define IPW_TX_RATE_48MB 0x01 +#define IPW_TX_RATE_54MB 0x03 + +#define IPW_ORD_TABLE_ID_MASK 0x0000FF00 +#define IPW_ORD_TABLE_VALUE_MASK 0x000000FF + +#define IPW_ORD_TABLE_0_MASK 0x0000F000 +#define IPW_ORD_TABLE_1_MASK 0x0000F100 +#define IPW_ORD_TABLE_2_MASK 0x0000F200 +#define IPW_ORD_TABLE_3_MASK 0x0000F300 +#define IPW_ORD_TABLE_4_MASK 0x0000F400 +#define IPW_ORD_TABLE_5_MASK 0x0000F500 +#define IPW_ORD_TABLE_6_MASK 0x0000F600 +#define IPW_ORD_TABLE_7_MASK 0x0000F700 + +/* + * Table 0 Entries (all entries are 32 bits) + */ +enum { + IPW_ORD_STAT_TX_CURR_RATE = IPW_ORD_TABLE_0_MASK + 1, + IPW_ORD_STAT_FRAG_TRESHOLD, + IPW_ORD_STAT_RTS_THRESHOLD, + IPW_ORD_STAT_TX_HOST_REQUESTS, + IPW_ORD_STAT_TX_HOST_COMPLETE, + IPW_ORD_STAT_TX_DIR_DATA, + IPW_ORD_STAT_TX_DIR_DATA_B_1, + IPW_ORD_STAT_TX_DIR_DATA_B_2, + IPW_ORD_STAT_TX_DIR_DATA_B_5_5, + IPW_ORD_STAT_TX_DIR_DATA_B_11, + /* Hole */ + + IPW_ORD_STAT_TX_DIR_DATA_G_1 = IPW_ORD_TABLE_0_MASK + 19, + IPW_ORD_STAT_TX_DIR_DATA_G_2, + IPW_ORD_STAT_TX_DIR_DATA_G_5_5, + IPW_ORD_STAT_TX_DIR_DATA_G_6, + IPW_ORD_STAT_TX_DIR_DATA_G_9, + IPW_ORD_STAT_TX_DIR_DATA_G_11, + IPW_ORD_STAT_TX_DIR_DATA_G_12, + IPW_ORD_STAT_TX_DIR_DATA_G_18, + IPW_ORD_STAT_TX_DIR_DATA_G_24, + IPW_ORD_STAT_TX_DIR_DATA_G_36, + IPW_ORD_STAT_TX_DIR_DATA_G_48, + IPW_ORD_STAT_TX_DIR_DATA_G_54, + IPW_ORD_STAT_TX_NON_DIR_DATA, + IPW_ORD_STAT_TX_NON_DIR_DATA_B_1, + IPW_ORD_STAT_TX_NON_DIR_DATA_B_2, + IPW_ORD_STAT_TX_NON_DIR_DATA_B_5_5, + IPW_ORD_STAT_TX_NON_DIR_DATA_B_11, + /* Hole */ + + IPW_ORD_STAT_TX_NON_DIR_DATA_G_1 = IPW_ORD_TABLE_0_MASK + 44, + IPW_ORD_STAT_TX_NON_DIR_DATA_G_2, + IPW_ORD_STAT_TX_NON_DIR_DATA_G_5_5, + IPW_ORD_STAT_TX_NON_DIR_DATA_G_6, + IPW_ORD_STAT_TX_NON_DIR_DATA_G_9, + IPW_ORD_STAT_TX_NON_DIR_DATA_G_11, + IPW_ORD_STAT_TX_NON_DIR_DATA_G_12, + IPW_ORD_STAT_TX_NON_DIR_DATA_G_18, + IPW_ORD_STAT_TX_NON_DIR_DATA_G_24, + IPW_ORD_STAT_TX_NON_DIR_DATA_G_36, + IPW_ORD_STAT_TX_NON_DIR_DATA_G_48, + IPW_ORD_STAT_TX_NON_DIR_DATA_G_54, + IPW_ORD_STAT_TX_RETRY, + IPW_ORD_STAT_TX_FAILURE, + IPW_ORD_STAT_RX_ERR_CRC, + IPW_ORD_STAT_RX_ERR_ICV, + IPW_ORD_STAT_RX_NO_BUFFER, + IPW_ORD_STAT_FULL_SCANS, + IPW_ORD_STAT_PARTIAL_SCANS, + IPW_ORD_STAT_TGH_ABORTED_SCANS, + IPW_ORD_STAT_TX_TOTAL_BYTES, + IPW_ORD_STAT_CURR_RSSI_RAW, + IPW_ORD_STAT_RX_BEACON, + IPW_ORD_STAT_MISSED_BEACONS, + IPW_ORD_TABLE_0_LAST +}; + +#define IPW_RSSI_TO_DBM 112 + +/* Table 1 Entries + */ +enum { + IPW_ORD_TABLE_1_LAST = IPW_ORD_TABLE_1_MASK | 1, +}; + +/* + * Table 2 Entries + * + * FW_VERSION: 16 byte string + * FW_DATE: 16 byte string (only 14 bytes used) + * UCODE_VERSION: 4 byte version code + * UCODE_DATE: 5 bytes code code + * ADDAPTER_MAC: 6 byte MAC address + * RTC: 4 byte clock + */ +enum { + IPW_ORD_STAT_FW_VERSION = IPW_ORD_TABLE_2_MASK | 1, + IPW_ORD_STAT_FW_DATE, + IPW_ORD_STAT_UCODE_VERSION, + IPW_ORD_STAT_UCODE_DATE, + IPW_ORD_STAT_ADAPTER_MAC, + IPW_ORD_STAT_RTC, + IPW_ORD_TABLE_2_LAST +}; + +/* Table 3 */ +enum { + IPW_ORD_STAT_TX_PACKET = IPW_ORD_TABLE_3_MASK | 0, + IPW_ORD_STAT_TX_PACKET_FAILURE, + IPW_ORD_STAT_TX_PACKET_SUCCESS, + IPW_ORD_STAT_TX_PACKET_ABORTED, + IPW_ORD_TABLE_3_LAST +}; + +/* Table 4 */ +enum { + IPW_ORD_TABLE_4_LAST = IPW_ORD_TABLE_4_MASK +}; + +/* Table 5 */ +enum { + IPW_ORD_STAT_AVAILABLE_AP_COUNT = IPW_ORD_TABLE_5_MASK, + IPW_ORD_STAT_AP_ASSNS, + IPW_ORD_STAT_ROAM, + IPW_ORD_STAT_ROAM_CAUSE_MISSED_BEACONS, + IPW_ORD_STAT_ROAM_CAUSE_UNASSOC, + IPW_ORD_STAT_ROAM_CAUSE_RSSI, + IPW_ORD_STAT_ROAM_CAUSE_LINK_QUALITY, + IPW_ORD_STAT_ROAM_CAUSE_AP_LOAD_BALANCE, + IPW_ORD_STAT_ROAM_CAUSE_AP_NO_TX, + IPW_ORD_STAT_LINK_UP, + IPW_ORD_STAT_LINK_DOWN, + IPW_ORD_ANTENNA_DIVERSITY, + IPW_ORD_CURR_FREQ, + IPW_ORD_TABLE_5_LAST +}; + +/* Table 6 */ +enum { + IPW_ORD_COUNTRY_CODE = IPW_ORD_TABLE_6_MASK, + IPW_ORD_CURR_BSSID, + IPW_ORD_CURR_SSID, + IPW_ORD_TABLE_6_LAST +}; + +/* Table 7 */ +enum { + IPW_ORD_STAT_PERCENT_MISSED_BEACONS = IPW_ORD_TABLE_7_MASK, + IPW_ORD_STAT_PERCENT_TX_RETRIES, + IPW_ORD_STAT_PERCENT_LINK_QUALITY, + IPW_ORD_STAT_CURR_RSSI_DBM, + IPW_ORD_TABLE_7_LAST +}; + +#define IPW_ERROR_LOG (IPW_SHARED_LOWER_BOUND + 0x410) +#define IPW_EVENT_LOG (IPW_SHARED_LOWER_BOUND + 0x414) +#define IPW_ORDINALS_TABLE_LOWER (IPW_SHARED_LOWER_BOUND + 0x500) +#define IPW_ORDINALS_TABLE_0 (IPW_SHARED_LOWER_BOUND + 0x180) +#define IPW_ORDINALS_TABLE_1 (IPW_SHARED_LOWER_BOUND + 0x184) +#define IPW_ORDINALS_TABLE_2 (IPW_SHARED_LOWER_BOUND + 0x188) +#define IPW_MEM_FIXED_OVERRIDE (IPW_SHARED_LOWER_BOUND + 0x41C) + +struct ipw_fixed_rate { + __le16 tx_rates; + __le16 reserved; +} __packed; + +#define IPW_INDIRECT_ADDR_MASK (~0x3ul) + +struct host_cmd { + u8 cmd; + u8 len; + u16 reserved; + u32 *param; +} __packed; /* XXX */ + +struct cmdlog_host_cmd { + u8 cmd; + u8 len; + __le16 reserved; + char param[124]; +} __packed; + +struct ipw_cmd_log { + unsigned long jiffies; + int retcode; + struct cmdlog_host_cmd cmd; +}; + +/* SysConfig command parameters ... */ +/* bt_coexistence param */ +#define CFG_BT_COEXISTENCE_SIGNAL_CHNL 0x01 /* tell BT our chnl # */ +#define CFG_BT_COEXISTENCE_DEFER 0x02 /* defer our Tx if BT traffic */ +#define CFG_BT_COEXISTENCE_KILL 0x04 /* kill our Tx if BT traffic */ +#define CFG_BT_COEXISTENCE_WME_OVER_BT 0x08 /* multimedia extensions */ +#define CFG_BT_COEXISTENCE_OOB 0x10 /* signal BT via out-of-band */ + +/* clear-to-send to self param */ +#define CFG_CTS_TO_ITSELF_ENABLED_MIN 0x00 +#define CFG_CTS_TO_ITSELF_ENABLED_MAX 0x01 +#define CFG_CTS_TO_ITSELF_ENABLED_DEF CFG_CTS_TO_ITSELF_ENABLED_MIN + +/* Antenna diversity param (h/w can select best antenna, based on signal) */ +#define CFG_SYS_ANTENNA_BOTH 0x00 /* NIC selects best antenna */ +#define CFG_SYS_ANTENNA_A 0x01 /* force antenna A */ +#define CFG_SYS_ANTENNA_B 0x03 /* force antenna B */ +#define CFG_SYS_ANTENNA_SLOW_DIV 0x02 /* consider background noise */ + +#define IPW_MAX_CONFIG_RETRIES 10 + +#endif /* __ipw2200_h__ */ diff --git a/drivers/net/wireless/ipw2x00/libipw.h b/drivers/net/wireless/ipw2x00/libipw.h new file mode 100644 index 0000000..2a2d1bc --- /dev/null +++ b/drivers/net/wireless/ipw2x00/libipw.h @@ -0,0 +1,1024 @@ +/* + * Merged with mainline ieee80211.h in Aug 2004. Original ieee802_11 + * remains copyright by the original authors + * + * Portions of the merged code are based on Host AP (software wireless + * LAN access point) driver for Intersil Prism2/2.5/3. + * + * Copyright (c) 2001-2002, SSH Communications Security Corp and Jouni Malinen + * + * Copyright (c) 2002-2003, Jouni Malinen + * + * Adaption to a generic IEEE 802.11 stack by James Ketrenos + * + * Copyright (c) 2004-2005, Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See README and COPYING for + * more details. + * + * API Version History + * 1.0.x -- Initial version + * 1.1.x -- Added radiotap, QoS, TIM, libipw_geo APIs, + * various structure changes, and crypto API init method + */ +#ifndef LIBIPW_H +#define LIBIPW_H +#include /* ETH_ALEN */ +#include /* ARRAY_SIZE */ +#include +#include + +#include +#include + +#define LIBIPW_VERSION "git-1.1.13" + +#define LIBIPW_DATA_LEN 2304 +/* Maximum size for the MA-UNITDATA primitive, 802.11 standard section + 6.2.1.1.2. + + The figure in section 7.1.2 suggests a body size of up to 2312 + bytes is allowed, which is a bit confusing, I suspect this + represents the 2304 bytes of real data, plus a possible 8 bytes of + WEP IV and ICV. (this interpretation suggested by Ramiro Barreiro) */ + +#define LIBIPW_1ADDR_LEN 10 +#define LIBIPW_2ADDR_LEN 16 +#define LIBIPW_3ADDR_LEN 24 +#define LIBIPW_4ADDR_LEN 30 +#define LIBIPW_FCS_LEN 4 +#define LIBIPW_HLEN (LIBIPW_4ADDR_LEN) +#define LIBIPW_FRAME_LEN (LIBIPW_DATA_LEN + LIBIPW_HLEN) + +#define MIN_FRAG_THRESHOLD 256U +#define MAX_FRAG_THRESHOLD 2346U + +/* QOS control */ +#define LIBIPW_QCTL_TID 0x000F + +/* debug macros */ + +#ifdef CPTCFG_LIBIPW_DEBUG +extern u32 libipw_debug_level; +#define LIBIPW_DEBUG(level, fmt, args...) \ +do { if (libipw_debug_level & (level)) \ + printk(KERN_DEBUG "libipw: %c %s " fmt, \ + in_interrupt() ? 'I' : 'U', __func__ , ## args); } while (0) +#else +#define LIBIPW_DEBUG(level, fmt, args...) do {} while (0) +#endif /* CPTCFG_LIBIPW_DEBUG */ + +/* + * To use the debug system: + * + * If you are defining a new debug classification, simply add it to the #define + * list here in the form of: + * + * #define LIBIPW_DL_xxxx VALUE + * + * shifting value to the left one bit from the previous entry. xxxx should be + * the name of the classification (for example, WEP) + * + * You then need to either add a LIBIPW_xxxx_DEBUG() macro definition for your + * classification, or use LIBIPW_DEBUG(LIBIPW_DL_xxxx, ...) whenever you want + * to send output to that classification. + * + * To add your debug level to the list of levels seen when you perform + * + * % cat /proc/net/ieee80211/debug_level + * + * you simply need to add your entry to the libipw_debug_level array. + * + * If you do not see debug_level in /proc/net/ieee80211 then you do not have + * CPTCFG_LIBIPW_DEBUG defined in your kernel configuration + * + */ + +#define LIBIPW_DL_INFO (1<<0) +#define LIBIPW_DL_WX (1<<1) +#define LIBIPW_DL_SCAN (1<<2) +#define LIBIPW_DL_STATE (1<<3) +#define LIBIPW_DL_MGMT (1<<4) +#define LIBIPW_DL_FRAG (1<<5) +#define LIBIPW_DL_DROP (1<<7) + +#define LIBIPW_DL_TX (1<<8) +#define LIBIPW_DL_RX (1<<9) +#define LIBIPW_DL_QOS (1<<31) + +#define LIBIPW_ERROR(f, a...) printk(KERN_ERR "libipw: " f, ## a) +#define LIBIPW_WARNING(f, a...) printk(KERN_WARNING "libipw: " f, ## a) +#define LIBIPW_DEBUG_INFO(f, a...) LIBIPW_DEBUG(LIBIPW_DL_INFO, f, ## a) + +#define LIBIPW_DEBUG_WX(f, a...) LIBIPW_DEBUG(LIBIPW_DL_WX, f, ## a) +#define LIBIPW_DEBUG_SCAN(f, a...) LIBIPW_DEBUG(LIBIPW_DL_SCAN, f, ## a) +#define LIBIPW_DEBUG_STATE(f, a...) LIBIPW_DEBUG(LIBIPW_DL_STATE, f, ## a) +#define LIBIPW_DEBUG_MGMT(f, a...) LIBIPW_DEBUG(LIBIPW_DL_MGMT, f, ## a) +#define LIBIPW_DEBUG_FRAG(f, a...) LIBIPW_DEBUG(LIBIPW_DL_FRAG, f, ## a) +#define LIBIPW_DEBUG_DROP(f, a...) LIBIPW_DEBUG(LIBIPW_DL_DROP, f, ## a) +#define LIBIPW_DEBUG_TX(f, a...) LIBIPW_DEBUG(LIBIPW_DL_TX, f, ## a) +#define LIBIPW_DEBUG_RX(f, a...) LIBIPW_DEBUG(LIBIPW_DL_RX, f, ## a) +#define LIBIPW_DEBUG_QOS(f, a...) LIBIPW_DEBUG(LIBIPW_DL_QOS, f, ## a) +#include +#include /* ARPHRD_ETHER */ + +#ifndef WIRELESS_SPY +#define WIRELESS_SPY /* enable iwspy support */ +#endif +#include /* new driver API */ + +#define ETH_P_PREAUTH 0x88C7 /* IEEE 802.11i pre-authentication */ + +#ifndef ETH_P_80211_RAW +#define ETH_P_80211_RAW (ETH_P_ECONET + 1) +#endif + +/* IEEE 802.11 defines */ + +#define P80211_OUI_LEN 3 + +struct libipw_snap_hdr { + + u8 dsap; /* always 0xAA */ + u8 ssap; /* always 0xAA */ + u8 ctrl; /* always 0x03 */ + u8 oui[P80211_OUI_LEN]; /* organizational universal id */ + +} __packed; + +#define SNAP_SIZE sizeof(struct libipw_snap_hdr) + +#define WLAN_FC_GET_VERS(fc) ((fc) & IEEE80211_FCTL_VERS) +#define WLAN_FC_GET_TYPE(fc) ((fc) & IEEE80211_FCTL_FTYPE) +#define WLAN_FC_GET_STYPE(fc) ((fc) & IEEE80211_FCTL_STYPE) + +#define WLAN_GET_SEQ_FRAG(seq) ((seq) & IEEE80211_SCTL_FRAG) +#define WLAN_GET_SEQ_SEQ(seq) (((seq) & IEEE80211_SCTL_SEQ) >> 4) + +#define LIBIPW_STATMASK_SIGNAL (1<<0) +#define LIBIPW_STATMASK_RSSI (1<<1) +#define LIBIPW_STATMASK_NOISE (1<<2) +#define LIBIPW_STATMASK_RATE (1<<3) +#define LIBIPW_STATMASK_WEMASK 0x7 + +#define LIBIPW_CCK_MODULATION (1<<0) +#define LIBIPW_OFDM_MODULATION (1<<1) + +#define LIBIPW_24GHZ_BAND (1<<0) +#define LIBIPW_52GHZ_BAND (1<<1) + +#define LIBIPW_CCK_RATE_1MB 0x02 +#define LIBIPW_CCK_RATE_2MB 0x04 +#define LIBIPW_CCK_RATE_5MB 0x0B +#define LIBIPW_CCK_RATE_11MB 0x16 +#define LIBIPW_OFDM_RATE_6MB 0x0C +#define LIBIPW_OFDM_RATE_9MB 0x12 +#define LIBIPW_OFDM_RATE_12MB 0x18 +#define LIBIPW_OFDM_RATE_18MB 0x24 +#define LIBIPW_OFDM_RATE_24MB 0x30 +#define LIBIPW_OFDM_RATE_36MB 0x48 +#define LIBIPW_OFDM_RATE_48MB 0x60 +#define LIBIPW_OFDM_RATE_54MB 0x6C +#define LIBIPW_BASIC_RATE_MASK 0x80 + +#define LIBIPW_CCK_RATE_1MB_MASK (1<<0) +#define LIBIPW_CCK_RATE_2MB_MASK (1<<1) +#define LIBIPW_CCK_RATE_5MB_MASK (1<<2) +#define LIBIPW_CCK_RATE_11MB_MASK (1<<3) +#define LIBIPW_OFDM_RATE_6MB_MASK (1<<4) +#define LIBIPW_OFDM_RATE_9MB_MASK (1<<5) +#define LIBIPW_OFDM_RATE_12MB_MASK (1<<6) +#define LIBIPW_OFDM_RATE_18MB_MASK (1<<7) +#define LIBIPW_OFDM_RATE_24MB_MASK (1<<8) +#define LIBIPW_OFDM_RATE_36MB_MASK (1<<9) +#define LIBIPW_OFDM_RATE_48MB_MASK (1<<10) +#define LIBIPW_OFDM_RATE_54MB_MASK (1<<11) + +#define LIBIPW_CCK_RATES_MASK 0x0000000F +#define LIBIPW_CCK_BASIC_RATES_MASK (LIBIPW_CCK_RATE_1MB_MASK | \ + LIBIPW_CCK_RATE_2MB_MASK) +#define LIBIPW_CCK_DEFAULT_RATES_MASK (LIBIPW_CCK_BASIC_RATES_MASK | \ + LIBIPW_CCK_RATE_5MB_MASK | \ + LIBIPW_CCK_RATE_11MB_MASK) + +#define LIBIPW_OFDM_RATES_MASK 0x00000FF0 +#define LIBIPW_OFDM_BASIC_RATES_MASK (LIBIPW_OFDM_RATE_6MB_MASK | \ + LIBIPW_OFDM_RATE_12MB_MASK | \ + LIBIPW_OFDM_RATE_24MB_MASK) +#define LIBIPW_OFDM_DEFAULT_RATES_MASK (LIBIPW_OFDM_BASIC_RATES_MASK | \ + LIBIPW_OFDM_RATE_9MB_MASK | \ + LIBIPW_OFDM_RATE_18MB_MASK | \ + LIBIPW_OFDM_RATE_36MB_MASK | \ + LIBIPW_OFDM_RATE_48MB_MASK | \ + LIBIPW_OFDM_RATE_54MB_MASK) +#define LIBIPW_DEFAULT_RATES_MASK (LIBIPW_OFDM_DEFAULT_RATES_MASK | \ + LIBIPW_CCK_DEFAULT_RATES_MASK) + +#define LIBIPW_NUM_OFDM_RATES 8 +#define LIBIPW_NUM_CCK_RATES 4 +#define LIBIPW_OFDM_SHIFT_MASK_A 4 + +/* NOTE: This data is for statistical purposes; not all hardware provides this + * information for frames received. + * For libipw_rx_mgt, you need to set at least the 'len' parameter. + */ +struct libipw_rx_stats { + u32 mac_time; + s8 rssi; + u8 signal; + u8 noise; + u16 rate; /* in 100 kbps */ + u8 received_channel; + u8 control; + u8 mask; + u8 freq; + u16 len; + u64 tsf; + u32 beacon_time; +}; + +/* IEEE 802.11 requires that STA supports concurrent reception of at least + * three fragmented frames. This define can be increased to support more + * concurrent frames, but it should be noted that each entry can consume about + * 2 kB of RAM and increasing cache size will slow down frame reassembly. */ +#define LIBIPW_FRAG_CACHE_LEN 4 + +struct libipw_frag_entry { + unsigned long first_frag_time; + unsigned int seq; + unsigned int last_frag; + struct sk_buff *skb; + u8 src_addr[ETH_ALEN]; + u8 dst_addr[ETH_ALEN]; +}; + +struct libipw_stats { + unsigned int tx_unicast_frames; + unsigned int tx_multicast_frames; + unsigned int tx_fragments; + unsigned int tx_unicast_octets; + unsigned int tx_multicast_octets; + unsigned int tx_deferred_transmissions; + unsigned int tx_single_retry_frames; + unsigned int tx_multiple_retry_frames; + unsigned int tx_retry_limit_exceeded; + unsigned int tx_discards; + unsigned int rx_unicast_frames; + unsigned int rx_multicast_frames; + unsigned int rx_fragments; + unsigned int rx_unicast_octets; + unsigned int rx_multicast_octets; + unsigned int rx_fcs_errors; + unsigned int rx_discards_no_buffer; + unsigned int tx_discards_wrong_sa; + unsigned int rx_discards_undecryptable; + unsigned int rx_message_in_msg_fragments; + unsigned int rx_message_in_bad_msg_fragments; +}; + +struct libipw_device; + +#define SEC_KEY_1 (1<<0) +#define SEC_KEY_2 (1<<1) +#define SEC_KEY_3 (1<<2) +#define SEC_KEY_4 (1<<3) +#define SEC_ACTIVE_KEY (1<<4) +#define SEC_AUTH_MODE (1<<5) +#define SEC_UNICAST_GROUP (1<<6) +#define SEC_LEVEL (1<<7) +#define SEC_ENABLED (1<<8) +#define SEC_ENCRYPT (1<<9) + +#define SEC_LEVEL_0 0 /* None */ +#define SEC_LEVEL_1 1 /* WEP 40 and 104 bit */ +#define SEC_LEVEL_2 2 /* Level 1 + TKIP */ +#define SEC_LEVEL_2_CKIP 3 /* Level 1 + CKIP */ +#define SEC_LEVEL_3 4 /* Level 2 + CCMP */ + +#define SEC_ALG_NONE 0 +#define SEC_ALG_WEP 1 +#define SEC_ALG_TKIP 2 +#define SEC_ALG_CCMP 3 + +#define WEP_KEYS 4 +#define WEP_KEY_LEN 13 +#define SCM_KEY_LEN 32 +#define SCM_TEMPORAL_KEY_LENGTH 16 + +struct libipw_security { + u16 active_key:2, enabled:1, unicast_uses_group:1, encrypt:1; + u8 auth_mode; + u8 encode_alg[WEP_KEYS]; + u8 key_sizes[WEP_KEYS]; + u8 keys[WEP_KEYS][SCM_KEY_LEN]; + u8 level; + u16 flags; +} __packed; + +/* + + 802.11 data frame from AP + + ,-------------------------------------------------------------------. +Bytes | 2 | 2 | 6 | 6 | 6 | 2 | 0..2312 | 4 | + |------|------|---------|---------|---------|------|---------|------| +Desc. | ctrl | dura | DA/RA | TA | SA | Sequ | frame | fcs | + | | tion | (BSSID) | | | ence | data | | + `-------------------------------------------------------------------' + +Total: 28-2340 bytes + +*/ + +#define BEACON_PROBE_SSID_ID_POSITION 12 + +struct libipw_hdr_1addr { + __le16 frame_ctl; + __le16 duration_id; + u8 addr1[ETH_ALEN]; + u8 payload[0]; +} __packed; + +struct libipw_hdr_2addr { + __le16 frame_ctl; + __le16 duration_id; + u8 addr1[ETH_ALEN]; + u8 addr2[ETH_ALEN]; + u8 payload[0]; +} __packed; + +struct libipw_hdr_3addr { + __le16 frame_ctl; + __le16 duration_id; + u8 addr1[ETH_ALEN]; + u8 addr2[ETH_ALEN]; + u8 addr3[ETH_ALEN]; + __le16 seq_ctl; + u8 payload[0]; +} __packed; + +struct libipw_hdr_4addr { + __le16 frame_ctl; + __le16 duration_id; + u8 addr1[ETH_ALEN]; + u8 addr2[ETH_ALEN]; + u8 addr3[ETH_ALEN]; + __le16 seq_ctl; + u8 addr4[ETH_ALEN]; + u8 payload[0]; +} __packed; + +struct libipw_hdr_3addrqos { + __le16 frame_ctl; + __le16 duration_id; + u8 addr1[ETH_ALEN]; + u8 addr2[ETH_ALEN]; + u8 addr3[ETH_ALEN]; + __le16 seq_ctl; + u8 payload[0]; + __le16 qos_ctl; +} __packed; + +struct libipw_info_element { + u8 id; + u8 len; + u8 data[0]; +} __packed; + +/* + * These are the data types that can make up management packets + * + u16 auth_algorithm; + u16 auth_sequence; + u16 beacon_interval; + u16 capability; + u8 current_ap[ETH_ALEN]; + u16 listen_interval; + struct { + u16 association_id:14, reserved:2; + } __packed; + u32 time_stamp[2]; + u16 reason; + u16 status; +*/ + +struct libipw_auth { + struct libipw_hdr_3addr header; + __le16 algorithm; + __le16 transaction; + __le16 status; + /* challenge */ + struct libipw_info_element info_element[0]; +} __packed; + +struct libipw_channel_switch { + u8 id; + u8 len; + u8 mode; + u8 channel; + u8 count; +} __packed; + +struct libipw_action { + struct libipw_hdr_3addr header; + u8 category; + u8 action; + union { + struct libipw_action_exchange { + u8 token; + struct libipw_info_element info_element[0]; + } exchange; + struct libipw_channel_switch channel_switch; + + } format; +} __packed; + +struct libipw_disassoc { + struct libipw_hdr_3addr header; + __le16 reason; +} __packed; + +/* Alias deauth for disassoc */ +#define libipw_deauth libipw_disassoc + +struct libipw_probe_request { + struct libipw_hdr_3addr header; + /* SSID, supported rates */ + struct libipw_info_element info_element[0]; +} __packed; + +struct libipw_probe_response { + struct libipw_hdr_3addr header; + __le32 time_stamp[2]; + __le16 beacon_interval; + __le16 capability; + /* SSID, supported rates, FH params, DS params, + * CF params, IBSS params, TIM (if beacon), RSN */ + struct libipw_info_element info_element[0]; +} __packed; + +/* Alias beacon for probe_response */ +#define libipw_beacon libipw_probe_response + +struct libipw_assoc_request { + struct libipw_hdr_3addr header; + __le16 capability; + __le16 listen_interval; + /* SSID, supported rates, RSN */ + struct libipw_info_element info_element[0]; +} __packed; + +struct libipw_reassoc_request { + struct libipw_hdr_3addr header; + __le16 capability; + __le16 listen_interval; + u8 current_ap[ETH_ALEN]; + struct libipw_info_element info_element[0]; +} __packed; + +struct libipw_assoc_response { + struct libipw_hdr_3addr header; + __le16 capability; + __le16 status; + __le16 aid; + /* supported rates */ + struct libipw_info_element info_element[0]; +} __packed; + +struct libipw_txb { + u8 nr_frags; + u8 encrypted; + u8 rts_included; + u8 reserved; + u16 frag_size; + u16 payload_size; + struct sk_buff *fragments[0]; +}; + +/* SWEEP TABLE ENTRIES NUMBER */ +#define MAX_SWEEP_TAB_ENTRIES 42 +#define MAX_SWEEP_TAB_ENTRIES_PER_PACKET 7 +/* MAX_RATES_LENGTH needs to be 12. The spec says 8, and many APs + * only use 8, and then use extended rates for the remaining supported + * rates. Other APs, however, stick all of their supported rates on the + * main rates information element... */ +#define MAX_RATES_LENGTH ((u8)12) +#define MAX_RATES_EX_LENGTH ((u8)16) +#define MAX_NETWORK_COUNT 128 + +#define CRC_LENGTH 4U + +#define MAX_WPA_IE_LEN 64 + +#define NETWORK_HAS_OFDM (1<<1) +#define NETWORK_HAS_CCK (1<<2) + +/* QoS structure */ +#define NETWORK_HAS_QOS_PARAMETERS (1<<3) +#define NETWORK_HAS_QOS_INFORMATION (1<<4) +#define NETWORK_HAS_QOS_MASK (NETWORK_HAS_QOS_PARAMETERS | \ + NETWORK_HAS_QOS_INFORMATION) + +/* 802.11h */ +#define NETWORK_HAS_POWER_CONSTRAINT (1<<5) +#define NETWORK_HAS_CSA (1<<6) +#define NETWORK_HAS_QUIET (1<<7) +#define NETWORK_HAS_IBSS_DFS (1<<8) +#define NETWORK_HAS_TPC_REPORT (1<<9) + +#define NETWORK_HAS_ERP_VALUE (1<<10) + +#define QOS_QUEUE_NUM 4 +#define QOS_OUI_LEN 3 +#define QOS_OUI_TYPE 2 +#define QOS_ELEMENT_ID 221 +#define QOS_OUI_INFO_SUB_TYPE 0 +#define QOS_OUI_PARAM_SUB_TYPE 1 +#define QOS_VERSION_1 1 +#define QOS_AIFSN_MIN_VALUE 2 + +struct libipw_qos_information_element { + u8 elementID; + u8 length; + u8 qui[QOS_OUI_LEN]; + u8 qui_type; + u8 qui_subtype; + u8 version; + u8 ac_info; +} __packed; + +struct libipw_qos_ac_parameter { + u8 aci_aifsn; + u8 ecw_min_max; + __le16 tx_op_limit; +} __packed; + +struct libipw_qos_parameter_info { + struct libipw_qos_information_element info_element; + u8 reserved; + struct libipw_qos_ac_parameter ac_params_record[QOS_QUEUE_NUM]; +} __packed; + +struct libipw_qos_parameters { + __le16 cw_min[QOS_QUEUE_NUM]; + __le16 cw_max[QOS_QUEUE_NUM]; + u8 aifs[QOS_QUEUE_NUM]; + u8 flag[QOS_QUEUE_NUM]; + __le16 tx_op_limit[QOS_QUEUE_NUM]; +} __packed; + +struct libipw_qos_data { + struct libipw_qos_parameters parameters; + int active; + int supported; + u8 param_count; + u8 old_param_count; +}; + +struct libipw_tim_parameters { + u8 tim_count; + u8 tim_period; +} __packed; + +/*******************************************************/ + +struct libipw_tpc_report { + u8 transmit_power; + u8 link_margin; +} __packed; + +struct libipw_channel_map { + u8 channel; + u8 map; +} __packed; + +struct libipw_ibss_dfs { + struct libipw_info_element ie; + u8 owner[ETH_ALEN]; + u8 recovery_interval; + struct libipw_channel_map channel_map[0]; +}; + +struct libipw_csa { + u8 mode; + u8 channel; + u8 count; +} __packed; + +struct libipw_quiet { + u8 count; + u8 period; + u8 duration; + u8 offset; +} __packed; + +struct libipw_network { + /* These entries are used to identify a unique network */ + u8 bssid[ETH_ALEN]; + u8 channel; + /* Ensure null-terminated for any debug msgs */ + u8 ssid[IW_ESSID_MAX_SIZE + 1]; + u8 ssid_len; + + struct libipw_qos_data qos_data; + + /* These are network statistics */ + struct libipw_rx_stats stats; + u16 capability; + u8 rates[MAX_RATES_LENGTH]; + u8 rates_len; + u8 rates_ex[MAX_RATES_EX_LENGTH]; + u8 rates_ex_len; + unsigned long last_scanned; + u8 mode; + u32 flags; + u32 last_associate; + u32 time_stamp[2]; + u16 beacon_interval; + u16 listen_interval; + u16 atim_window; + u8 erp_value; + u8 wpa_ie[MAX_WPA_IE_LEN]; + size_t wpa_ie_len; + u8 rsn_ie[MAX_WPA_IE_LEN]; + size_t rsn_ie_len; + struct libipw_tim_parameters tim; + + /* 802.11h info */ + + /* Power Constraint - mandatory if spctrm mgmt required */ + u8 power_constraint; + + /* TPC Report - mandatory if spctrm mgmt required */ + struct libipw_tpc_report tpc_report; + + /* IBSS DFS - mandatory if spctrm mgmt required and IBSS + * NOTE: This is variable length and so must be allocated dynamically */ + struct libipw_ibss_dfs *ibss_dfs; + + /* Channel Switch Announcement - optional if spctrm mgmt required */ + struct libipw_csa csa; + + /* Quiet - optional if spctrm mgmt required */ + struct libipw_quiet quiet; + + struct list_head list; +}; + +enum libipw_state { + LIBIPW_UNINITIALIZED = 0, + LIBIPW_INITIALIZED, + LIBIPW_ASSOCIATING, + LIBIPW_ASSOCIATED, + LIBIPW_AUTHENTICATING, + LIBIPW_AUTHENTICATED, + LIBIPW_SHUTDOWN +}; + +#define DEFAULT_MAX_SCAN_AGE (15 * HZ) +#define DEFAULT_FTS 2346 + +#define CFG_LIBIPW_RESERVE_FCS (1<<0) +#define CFG_LIBIPW_COMPUTE_FCS (1<<1) +#define CFG_LIBIPW_RTS (1<<2) + +#define LIBIPW_24GHZ_MIN_CHANNEL 1 +#define LIBIPW_24GHZ_MAX_CHANNEL 14 +#define LIBIPW_24GHZ_CHANNELS (LIBIPW_24GHZ_MAX_CHANNEL - \ + LIBIPW_24GHZ_MIN_CHANNEL + 1) + +#define LIBIPW_52GHZ_MIN_CHANNEL 34 +#define LIBIPW_52GHZ_MAX_CHANNEL 165 +#define LIBIPW_52GHZ_CHANNELS (LIBIPW_52GHZ_MAX_CHANNEL - \ + LIBIPW_52GHZ_MIN_CHANNEL + 1) + +enum { + LIBIPW_CH_PASSIVE_ONLY = (1 << 0), + LIBIPW_CH_80211H_RULES = (1 << 1), + LIBIPW_CH_B_ONLY = (1 << 2), + LIBIPW_CH_NO_IBSS = (1 << 3), + LIBIPW_CH_UNIFORM_SPREADING = (1 << 4), + LIBIPW_CH_RADAR_DETECT = (1 << 5), + LIBIPW_CH_INVALID = (1 << 6), +}; + +struct libipw_channel { + u32 freq; /* in MHz */ + u8 channel; + u8 flags; + u8 max_power; /* in dBm */ +}; + +struct libipw_geo { + u8 name[4]; + u8 bg_channels; + u8 a_channels; + struct libipw_channel bg[LIBIPW_24GHZ_CHANNELS]; + struct libipw_channel a[LIBIPW_52GHZ_CHANNELS]; +}; + +struct libipw_device { + struct net_device *dev; + struct wireless_dev wdev; + struct libipw_security sec; + + /* Bookkeeping structures */ + struct libipw_stats ieee_stats; + + struct libipw_geo geo; + struct ieee80211_supported_band bg_band; + struct ieee80211_supported_band a_band; + + /* Probe / Beacon management */ + struct list_head network_free_list; + struct list_head network_list; + struct libipw_network *networks[MAX_NETWORK_COUNT]; + int scans; + int scan_age; + + int iw_mode; /* operating mode (IW_MODE_*) */ + struct iw_spy_data spy_data; /* iwspy support */ + + spinlock_t lock; + + int tx_headroom; /* Set to size of any additional room needed at front + * of allocated Tx SKBs */ + u32 config; + + /* WEP and other encryption related settings at the device level */ + int open_wep; /* Set to 1 to allow unencrypted frames */ + + /* If the host performs {en,de}cryption, then set to 1 */ + int host_encrypt; + int host_encrypt_msdu; + int host_decrypt; + /* host performs multicast decryption */ + int host_mc_decrypt; + + /* host should strip IV and ICV from protected frames */ + /* meaningful only when hardware decryption is being used */ + int host_strip_iv_icv; + + int host_open_frag; + int ieee802_1x; /* is IEEE 802.1X used */ + + /* WPA data */ + int wpa_enabled; + int drop_unencrypted; + int privacy_invoked; + size_t wpa_ie_len; + u8 *wpa_ie; + + struct lib80211_crypt_info crypt_info; + + int bcrx_sta_key; /* use individual keys to override default keys even + * with RX of broad/multicast frames */ + + /* Fragmentation structures */ + struct libipw_frag_entry frag_cache[LIBIPW_FRAG_CACHE_LEN]; + unsigned int frag_next_idx; + u16 fts; /* Fragmentation Threshold */ + u16 rts; /* RTS threshold */ + + /* Association info */ + u8 bssid[ETH_ALEN]; + + enum libipw_state state; + + int mode; /* A, B, G */ + int modulation; /* CCK, OFDM */ + int freq_band; /* 2.4Ghz, 5.2Ghz, Mixed */ + int abg_true; /* ABG flag */ + + int perfect_rssi; + int worst_rssi; + + u16 prev_seq_ctl; /* used to drop duplicate frames */ + + /* Callback functions */ + void (*set_security) (struct net_device * dev, + struct libipw_security * sec); + netdev_tx_t (*hard_start_xmit) (struct libipw_txb * txb, + struct net_device * dev, int pri); + int (*is_queue_full) (struct net_device * dev, int pri); + + int (*handle_management) (struct net_device * dev, + struct libipw_network * network, u16 type); + int (*is_qos_active) (struct net_device *dev, struct sk_buff *skb); + + /* Typical STA methods */ + int (*handle_auth) (struct net_device * dev, + struct libipw_auth * auth); + int (*handle_deauth) (struct net_device * dev, + struct libipw_deauth * auth); + int (*handle_action) (struct net_device * dev, + struct libipw_action * action, + struct libipw_rx_stats * stats); + int (*handle_disassoc) (struct net_device * dev, + struct libipw_disassoc * assoc); + int (*handle_beacon) (struct net_device * dev, + struct libipw_beacon * beacon, + struct libipw_network * network); + int (*handle_probe_response) (struct net_device * dev, + struct libipw_probe_response * resp, + struct libipw_network * network); + int (*handle_probe_request) (struct net_device * dev, + struct libipw_probe_request * req, + struct libipw_rx_stats * stats); + int (*handle_assoc_response) (struct net_device * dev, + struct libipw_assoc_response * resp, + struct libipw_network * network); + + /* Typical AP methods */ + int (*handle_assoc_request) (struct net_device * dev); + int (*handle_reassoc_request) (struct net_device * dev, + struct libipw_reassoc_request * req); + + /* This must be the last item so that it points to the data + * allocated beyond this structure by alloc_libipw */ + u8 priv[0]; +}; + +#define IEEE_A (1<<0) +#define IEEE_B (1<<1) +#define IEEE_G (1<<2) +#define IEEE_MODE_MASK (IEEE_A|IEEE_B|IEEE_G) + +static inline void *libipw_priv(struct net_device *dev) +{ + return ((struct libipw_device *)netdev_priv(dev))->priv; +} + +static inline int libipw_is_valid_mode(struct libipw_device *ieee, + int mode) +{ + /* + * It is possible for both access points and our device to support + * combinations of modes, so as long as there is one valid combination + * of ap/device supported modes, then return success + * + */ + if ((mode & IEEE_A) && + (ieee->modulation & LIBIPW_OFDM_MODULATION) && + (ieee->freq_band & LIBIPW_52GHZ_BAND)) + return 1; + + if ((mode & IEEE_G) && + (ieee->modulation & LIBIPW_OFDM_MODULATION) && + (ieee->freq_band & LIBIPW_24GHZ_BAND)) + return 1; + + if ((mode & IEEE_B) && + (ieee->modulation & LIBIPW_CCK_MODULATION) && + (ieee->freq_band & LIBIPW_24GHZ_BAND)) + return 1; + + return 0; +} + +static inline int libipw_get_hdrlen(u16 fc) +{ + int hdrlen = LIBIPW_3ADDR_LEN; + u16 stype = WLAN_FC_GET_STYPE(fc); + + switch (WLAN_FC_GET_TYPE(fc)) { + case IEEE80211_FTYPE_DATA: + if ((fc & IEEE80211_FCTL_FROMDS) && (fc & IEEE80211_FCTL_TODS)) + hdrlen = LIBIPW_4ADDR_LEN; + if (stype & IEEE80211_STYPE_QOS_DATA) + hdrlen += 2; + break; + case IEEE80211_FTYPE_CTL: + switch (WLAN_FC_GET_STYPE(fc)) { + case IEEE80211_STYPE_CTS: + case IEEE80211_STYPE_ACK: + hdrlen = LIBIPW_1ADDR_LEN; + break; + default: + hdrlen = LIBIPW_2ADDR_LEN; + break; + } + break; + } + + return hdrlen; +} + +static inline u8 *libipw_get_payload(struct ieee80211_hdr *hdr) +{ + switch (libipw_get_hdrlen(le16_to_cpu(hdr->frame_control))) { + case LIBIPW_1ADDR_LEN: + return ((struct libipw_hdr_1addr *)hdr)->payload; + case LIBIPW_2ADDR_LEN: + return ((struct libipw_hdr_2addr *)hdr)->payload; + case LIBIPW_3ADDR_LEN: + return ((struct libipw_hdr_3addr *)hdr)->payload; + case LIBIPW_4ADDR_LEN: + return ((struct libipw_hdr_4addr *)hdr)->payload; + } + return NULL; +} + +static inline int libipw_is_ofdm_rate(u8 rate) +{ + switch (rate & ~LIBIPW_BASIC_RATE_MASK) { + case LIBIPW_OFDM_RATE_6MB: + case LIBIPW_OFDM_RATE_9MB: + case LIBIPW_OFDM_RATE_12MB: + case LIBIPW_OFDM_RATE_18MB: + case LIBIPW_OFDM_RATE_24MB: + case LIBIPW_OFDM_RATE_36MB: + case LIBIPW_OFDM_RATE_48MB: + case LIBIPW_OFDM_RATE_54MB: + return 1; + } + return 0; +} + +static inline int libipw_is_cck_rate(u8 rate) +{ + switch (rate & ~LIBIPW_BASIC_RATE_MASK) { + case LIBIPW_CCK_RATE_1MB: + case LIBIPW_CCK_RATE_2MB: + case LIBIPW_CCK_RATE_5MB: + case LIBIPW_CCK_RATE_11MB: + return 1; + } + return 0; +} + +/* libipw.c */ +extern void free_libipw(struct net_device *dev, int monitor); +extern struct net_device *alloc_libipw(int sizeof_priv, int monitor); +extern int libipw_change_mtu(struct net_device *dev, int new_mtu); + +extern void libipw_networks_age(struct libipw_device *ieee, + unsigned long age_secs); + +extern int libipw_set_encryption(struct libipw_device *ieee); + +/* libipw_tx.c */ +extern netdev_tx_t libipw_xmit(struct sk_buff *skb, + struct net_device *dev); +extern void libipw_txb_free(struct libipw_txb *); + +/* libipw_rx.c */ +extern void libipw_rx_any(struct libipw_device *ieee, + struct sk_buff *skb, struct libipw_rx_stats *stats); +extern int libipw_rx(struct libipw_device *ieee, struct sk_buff *skb, + struct libipw_rx_stats *rx_stats); +/* make sure to set stats->len */ +extern void libipw_rx_mgt(struct libipw_device *ieee, + struct libipw_hdr_4addr *header, + struct libipw_rx_stats *stats); +extern void libipw_network_reset(struct libipw_network *network); + +/* libipw_geo.c */ +extern const struct libipw_geo *libipw_get_geo(struct libipw_device + *ieee); +extern void libipw_set_geo(struct libipw_device *ieee, + const struct libipw_geo *geo); + +extern int libipw_is_valid_channel(struct libipw_device *ieee, + u8 channel); +extern int libipw_channel_to_index(struct libipw_device *ieee, + u8 channel); +extern u8 libipw_freq_to_channel(struct libipw_device *ieee, u32 freq); +extern u8 libipw_get_channel_flags(struct libipw_device *ieee, + u8 channel); +extern const struct libipw_channel *libipw_get_channel(struct + libipw_device + *ieee, u8 channel); +extern u32 libipw_channel_to_freq(struct libipw_device * ieee, + u8 channel); + +/* libipw_wx.c */ +extern int libipw_wx_get_scan(struct libipw_device *ieee, + struct iw_request_info *info, + union iwreq_data *wrqu, char *key); +extern int libipw_wx_set_encode(struct libipw_device *ieee, + struct iw_request_info *info, + union iwreq_data *wrqu, char *key); +extern int libipw_wx_get_encode(struct libipw_device *ieee, + struct iw_request_info *info, + union iwreq_data *wrqu, char *key); +extern int libipw_wx_set_encodeext(struct libipw_device *ieee, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra); +extern int libipw_wx_get_encodeext(struct libipw_device *ieee, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra); + +static inline void libipw_increment_scans(struct libipw_device *ieee) +{ + ieee->scans++; +} + +static inline int libipw_get_scans(struct libipw_device *ieee) +{ + return ieee->scans; +} + +#endif /* LIBIPW_H */ diff --git a/drivers/net/wireless/ipw2x00/libipw_geo.c b/drivers/net/wireless/ipw2x00/libipw_geo.c new file mode 100644 index 0000000..218f2a3 --- /dev/null +++ b/drivers/net/wireless/ipw2x00/libipw_geo.c @@ -0,0 +1,193 @@ +/****************************************************************************** + + Copyright(c) 2005 Intel Corporation. All rights reserved. + + This program is free software; you can redistribute it and/or modify it + under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Contact Information: + Intel Linux Wireless + Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + +******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libipw.h" + +int libipw_is_valid_channel(struct libipw_device *ieee, u8 channel) +{ + int i; + + /* Driver needs to initialize the geography map before using + * these helper functions */ + if (ieee->geo.bg_channels == 0 && ieee->geo.a_channels == 0) + return 0; + + if (ieee->freq_band & LIBIPW_24GHZ_BAND) + for (i = 0; i < ieee->geo.bg_channels; i++) + /* NOTE: If G mode is currently supported but + * this is a B only channel, we don't see it + * as valid. */ + if ((ieee->geo.bg[i].channel == channel) && + !(ieee->geo.bg[i].flags & LIBIPW_CH_INVALID) && + (!(ieee->mode & IEEE_G) || + !(ieee->geo.bg[i].flags & LIBIPW_CH_B_ONLY))) + return LIBIPW_24GHZ_BAND; + + if (ieee->freq_band & LIBIPW_52GHZ_BAND) + for (i = 0; i < ieee->geo.a_channels; i++) + if ((ieee->geo.a[i].channel == channel) && + !(ieee->geo.a[i].flags & LIBIPW_CH_INVALID)) + return LIBIPW_52GHZ_BAND; + + return 0; +} + +int libipw_channel_to_index(struct libipw_device *ieee, u8 channel) +{ + int i; + + /* Driver needs to initialize the geography map before using + * these helper functions */ + if (ieee->geo.bg_channels == 0 && ieee->geo.a_channels == 0) + return -1; + + if (ieee->freq_band & LIBIPW_24GHZ_BAND) + for (i = 0; i < ieee->geo.bg_channels; i++) + if (ieee->geo.bg[i].channel == channel) + return i; + + if (ieee->freq_band & LIBIPW_52GHZ_BAND) + for (i = 0; i < ieee->geo.a_channels; i++) + if (ieee->geo.a[i].channel == channel) + return i; + + return -1; +} + +u32 libipw_channel_to_freq(struct libipw_device * ieee, u8 channel) +{ + const struct libipw_channel * ch; + + /* Driver needs to initialize the geography map before using + * these helper functions */ + if (ieee->geo.bg_channels == 0 && ieee->geo.a_channels == 0) + return 0; + + ch = libipw_get_channel(ieee, channel); + if (!ch->channel) + return 0; + return ch->freq; +} + +u8 libipw_freq_to_channel(struct libipw_device * ieee, u32 freq) +{ + int i; + + /* Driver needs to initialize the geography map before using + * these helper functions */ + if (ieee->geo.bg_channels == 0 && ieee->geo.a_channels == 0) + return 0; + + freq /= 100000; + + if (ieee->freq_band & LIBIPW_24GHZ_BAND) + for (i = 0; i < ieee->geo.bg_channels; i++) + if (ieee->geo.bg[i].freq == freq) + return ieee->geo.bg[i].channel; + + if (ieee->freq_band & LIBIPW_52GHZ_BAND) + for (i = 0; i < ieee->geo.a_channels; i++) + if (ieee->geo.a[i].freq == freq) + return ieee->geo.a[i].channel; + + return 0; +} + +void libipw_set_geo(struct libipw_device *ieee, + const struct libipw_geo *geo) +{ + memcpy(ieee->geo.name, geo->name, 3); + ieee->geo.name[3] = '\0'; + ieee->geo.bg_channels = geo->bg_channels; + ieee->geo.a_channels = geo->a_channels; + memcpy(ieee->geo.bg, geo->bg, geo->bg_channels * + sizeof(struct libipw_channel)); + memcpy(ieee->geo.a, geo->a, ieee->geo.a_channels * + sizeof(struct libipw_channel)); +} + +const struct libipw_geo *libipw_get_geo(struct libipw_device *ieee) +{ + return &ieee->geo; +} + +u8 libipw_get_channel_flags(struct libipw_device * ieee, u8 channel) +{ + int index = libipw_channel_to_index(ieee, channel); + + if (index == -1) + return LIBIPW_CH_INVALID; + + if (channel <= LIBIPW_24GHZ_CHANNELS) + return ieee->geo.bg[index].flags; + + return ieee->geo.a[index].flags; +} + +static const struct libipw_channel bad_channel = { + .channel = 0, + .flags = LIBIPW_CH_INVALID, + .max_power = 0, +}; + +const struct libipw_channel *libipw_get_channel(struct libipw_device + *ieee, u8 channel) +{ + int index = libipw_channel_to_index(ieee, channel); + + if (index == -1) + return &bad_channel; + + if (channel <= LIBIPW_24GHZ_CHANNELS) + return &ieee->geo.bg[index]; + + return &ieee->geo.a[index]; +} + +EXPORT_SYMBOL(libipw_get_channel); +EXPORT_SYMBOL(libipw_get_channel_flags); +EXPORT_SYMBOL(libipw_is_valid_channel); +EXPORT_SYMBOL(libipw_freq_to_channel); +EXPORT_SYMBOL(libipw_channel_to_freq); +EXPORT_SYMBOL(libipw_channel_to_index); +EXPORT_SYMBOL(libipw_set_geo); +EXPORT_SYMBOL(libipw_get_geo); diff --git a/drivers/net/wireless/ipw2x00/libipw_module.c b/drivers/net/wireless/ipw2x00/libipw_module.c new file mode 100644 index 0000000..26361b8 --- /dev/null +++ b/drivers/net/wireless/ipw2x00/libipw_module.c @@ -0,0 +1,335 @@ +/******************************************************************************* + + Copyright(c) 2004-2005 Intel Corporation. All rights reserved. + + Portions of this file are based on the WEP enablement code provided by the + Host AP project hostap-drivers v0.1.3 + Copyright (c) 2001-2002, SSH Communications Security Corp and Jouni Malinen + + Copyright (c) 2002-2003, Jouni Malinen + + This program is free software; you can redistribute it and/or modify it + under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Contact Information: + Intel Linux Wireless + Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + +*******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libipw.h" + +#define DRV_DESCRIPTION "802.11 data/management/control stack" +#define DRV_NAME "libipw" +#define DRV_PROCNAME "ieee80211" +#define DRV_VERSION LIBIPW_VERSION +#define DRV_COPYRIGHT "Copyright (C) 2004-2005 Intel Corporation " + +MODULE_VERSION(DRV_VERSION); +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR(DRV_COPYRIGHT); +MODULE_LICENSE("GPL"); + +static struct cfg80211_ops libipw_config_ops = { }; +static void *libipw_wiphy_privid = &libipw_wiphy_privid; + +static int libipw_networks_allocate(struct libipw_device *ieee) +{ + int i, j; + + for (i = 0; i < MAX_NETWORK_COUNT; i++) { + ieee->networks[i] = kzalloc(sizeof(struct libipw_network), + GFP_KERNEL); + if (!ieee->networks[i]) { + LIBIPW_ERROR("Out of memory allocating beacons\n"); + for (j = 0; j < i; j++) + kfree(ieee->networks[j]); + return -ENOMEM; + } + } + + return 0; +} + +void libipw_network_reset(struct libipw_network *network) +{ + if (!network) + return; + + if (network->ibss_dfs) { + kfree(network->ibss_dfs); + network->ibss_dfs = NULL; + } +} + +static inline void libipw_networks_free(struct libipw_device *ieee) +{ + int i; + + for (i = 0; i < MAX_NETWORK_COUNT; i++) { + if (ieee->networks[i]->ibss_dfs) + kfree(ieee->networks[i]->ibss_dfs); + kfree(ieee->networks[i]); + } +} + +void libipw_networks_age(struct libipw_device *ieee, + unsigned long age_secs) +{ + struct libipw_network *network = NULL; + unsigned long flags; + unsigned long age_jiffies = msecs_to_jiffies(age_secs * MSEC_PER_SEC); + + spin_lock_irqsave(&ieee->lock, flags); + list_for_each_entry(network, &ieee->network_list, list) { + network->last_scanned -= age_jiffies; + } + spin_unlock_irqrestore(&ieee->lock, flags); +} +EXPORT_SYMBOL(libipw_networks_age); + +static void libipw_networks_initialize(struct libipw_device *ieee) +{ + int i; + + INIT_LIST_HEAD(&ieee->network_free_list); + INIT_LIST_HEAD(&ieee->network_list); + for (i = 0; i < MAX_NETWORK_COUNT; i++) + list_add_tail(&ieee->networks[i]->list, + &ieee->network_free_list); +} + +int libipw_change_mtu(struct net_device *dev, int new_mtu) +{ + if ((new_mtu < 68) || (new_mtu > LIBIPW_DATA_LEN)) + return -EINVAL; + dev->mtu = new_mtu; + return 0; +} +EXPORT_SYMBOL(libipw_change_mtu); + +struct net_device *alloc_libipw(int sizeof_priv, int monitor) +{ + struct libipw_device *ieee; + struct net_device *dev; + int err; + + LIBIPW_DEBUG_INFO("Initializing...\n"); + + dev = alloc_etherdev(sizeof(struct libipw_device) + sizeof_priv); + if (!dev) + goto failed; + + ieee = netdev_priv(dev); + + ieee->dev = dev; + + if (!monitor) { + ieee->wdev.wiphy = wiphy_new(&libipw_config_ops, 0); + if (!ieee->wdev.wiphy) { + LIBIPW_ERROR("Unable to allocate wiphy.\n"); + goto failed_free_netdev; + } + + ieee->dev->ieee80211_ptr = &ieee->wdev; + ieee->wdev.iftype = NL80211_IFTYPE_STATION; + + /* Fill-out wiphy structure bits we know... Not enough info + here to call set_wiphy_dev or set MAC address or channel info + -- have to do that in ->ndo_init... */ + ieee->wdev.wiphy->privid = libipw_wiphy_privid; + + ieee->wdev.wiphy->max_scan_ssids = 1; + ieee->wdev.wiphy->max_scan_ie_len = 0; + ieee->wdev.wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) + | BIT(NL80211_IFTYPE_ADHOC); + } + + err = libipw_networks_allocate(ieee); + if (err) { + LIBIPW_ERROR("Unable to allocate beacon storage: %d\n", err); + goto failed_free_wiphy; + } + libipw_networks_initialize(ieee); + + /* Default fragmentation threshold is maximum payload size */ + ieee->fts = DEFAULT_FTS; + ieee->rts = DEFAULT_FTS; + ieee->scan_age = DEFAULT_MAX_SCAN_AGE; + ieee->open_wep = 1; + + /* Default to enabling full open WEP with host based encrypt/decrypt */ + ieee->host_encrypt = 1; + ieee->host_decrypt = 1; + ieee->host_mc_decrypt = 1; + + /* Host fragmentation in Open mode. Default is enabled. + * Note: host fragmentation is always enabled if host encryption + * is enabled. For cards can do hardware encryption, they must do + * hardware fragmentation as well. So we don't need a variable + * like host_enc_frag. */ + ieee->host_open_frag = 1; + ieee->ieee802_1x = 1; /* Default to supporting 802.1x */ + + spin_lock_init(&ieee->lock); + + lib80211_crypt_info_init(&ieee->crypt_info, dev->name, &ieee->lock); + + ieee->wpa_enabled = 0; + ieee->drop_unencrypted = 0; + ieee->privacy_invoked = 0; + + return dev; + +failed_free_wiphy: + if (!monitor) + wiphy_free(ieee->wdev.wiphy); +failed_free_netdev: + free_netdev(dev); +failed: + return NULL; +} +EXPORT_SYMBOL(alloc_libipw); + +void free_libipw(struct net_device *dev, int monitor) +{ + struct libipw_device *ieee = netdev_priv(dev); + + lib80211_crypt_info_free(&ieee->crypt_info); + + libipw_networks_free(ieee); + + /* free cfg80211 resources */ + if (!monitor) + wiphy_free(ieee->wdev.wiphy); + + free_netdev(dev); +} +EXPORT_SYMBOL(free_libipw); + +#ifdef CPTCFG_LIBIPW_DEBUG + +static int debug = 0; +u32 libipw_debug_level = 0; +EXPORT_SYMBOL_GPL(libipw_debug_level); +static struct proc_dir_entry *libipw_proc = NULL; + +static int debug_level_proc_show(struct seq_file *m, void *v) +{ + seq_printf(m, "0x%08X\n", libipw_debug_level); + return 0; +} + +static int debug_level_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, debug_level_proc_show, NULL); +} + +static ssize_t debug_level_proc_write(struct file *file, + const char __user *buffer, size_t count, loff_t *pos) +{ + char buf[] = "0x00000000\n"; + size_t len = min(sizeof(buf) - 1, count); + unsigned long val; + + if (copy_from_user(buf, buffer, len)) + return count; + buf[len] = 0; + if (sscanf(buf, "%li", &val) != 1) + printk(KERN_INFO DRV_NAME + ": %s is not in hex or decimal form.\n", buf); + else + libipw_debug_level = val; + + return strnlen(buf, len); +} + +static const struct file_operations debug_level_proc_fops = { + .owner = THIS_MODULE, + .open = debug_level_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = debug_level_proc_write, +}; +#endif /* CPTCFG_LIBIPW_DEBUG */ + +static int __init libipw_init(void) +{ +#ifdef CPTCFG_LIBIPW_DEBUG + struct proc_dir_entry *e; + + libipw_debug_level = debug; + libipw_proc = proc_mkdir(DRV_PROCNAME, init_net.proc_net); + if (libipw_proc == NULL) { + LIBIPW_ERROR("Unable to create " DRV_PROCNAME + " proc directory\n"); + return -EIO; + } + e = proc_create("debug_level", S_IRUGO | S_IWUSR, libipw_proc, + &debug_level_proc_fops); + if (!e) { + remove_proc_entry(DRV_PROCNAME, init_net.proc_net); + libipw_proc = NULL; + return -EIO; + } +#endif /* CPTCFG_LIBIPW_DEBUG */ + + printk(KERN_INFO DRV_NAME ": " DRV_DESCRIPTION ", " DRV_VERSION "\n"); + printk(KERN_INFO DRV_NAME ": " DRV_COPYRIGHT "\n"); + + return 0; +} + +static void __exit libipw_exit(void) +{ +#ifdef CPTCFG_LIBIPW_DEBUG + if (libipw_proc) { + remove_proc_entry("debug_level", libipw_proc); + remove_proc_entry(DRV_PROCNAME, init_net.proc_net); + libipw_proc = NULL; + } +#endif /* CPTCFG_LIBIPW_DEBUG */ +} + +#ifdef CPTCFG_LIBIPW_DEBUG +#include +module_param(debug, int, 0444); +MODULE_PARM_DESC(debug, "debug output mask"); +#endif /* CPTCFG_LIBIPW_DEBUG */ + +module_exit(libipw_exit); +module_init(libipw_init); diff --git a/drivers/net/wireless/ipw2x00/libipw_rx.c b/drivers/net/wireless/ipw2x00/libipw_rx.c new file mode 100644 index 0000000..e5d2fe4 --- /dev/null +++ b/drivers/net/wireless/ipw2x00/libipw_rx.c @@ -0,0 +1,1797 @@ +/* + * Original code based Host AP (software wireless LAN access point) driver + * for Intersil Prism2/2.5/3 - hostap.o module, common routines + * + * Copyright (c) 2001-2002, SSH Communications Security Corp and Jouni Malinen + * + * Copyright (c) 2002-2003, Jouni Malinen + * Copyright (c) 2004-2005, Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See README and COPYING for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "libipw.h" + +static void libipw_monitor_rx(struct libipw_device *ieee, + struct sk_buff *skb, + struct libipw_rx_stats *rx_stats) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + u16 fc = le16_to_cpu(hdr->frame_control); + + skb->dev = ieee->dev; + skb_reset_mac_header(skb); + skb_pull(skb, libipw_get_hdrlen(fc)); + skb->pkt_type = PACKET_OTHERHOST; + skb->protocol = htons(ETH_P_80211_RAW); + memset(skb->cb, 0, sizeof(skb->cb)); + netif_rx(skb); +} + +/* Called only as a tasklet (software IRQ) */ +static struct libipw_frag_entry *libipw_frag_cache_find(struct + libipw_device + *ieee, + unsigned int seq, + unsigned int frag, + u8 * src, + u8 * dst) +{ + struct libipw_frag_entry *entry; + int i; + + for (i = 0; i < LIBIPW_FRAG_CACHE_LEN; i++) { + entry = &ieee->frag_cache[i]; + if (entry->skb != NULL && + time_after(jiffies, entry->first_frag_time + 2 * HZ)) { + LIBIPW_DEBUG_FRAG("expiring fragment cache entry " + "seq=%u last_frag=%u\n", + entry->seq, entry->last_frag); + dev_kfree_skb_any(entry->skb); + entry->skb = NULL; + } + + if (entry->skb != NULL && entry->seq == seq && + (entry->last_frag + 1 == frag || frag == -1) && + ether_addr_equal(entry->src_addr, src) && + ether_addr_equal(entry->dst_addr, dst)) + return entry; + } + + return NULL; +} + +/* Called only as a tasklet (software IRQ) */ +static struct sk_buff *libipw_frag_cache_get(struct libipw_device *ieee, + struct libipw_hdr_4addr *hdr) +{ + struct sk_buff *skb = NULL; + u16 sc; + unsigned int frag, seq; + struct libipw_frag_entry *entry; + + sc = le16_to_cpu(hdr->seq_ctl); + frag = WLAN_GET_SEQ_FRAG(sc); + seq = WLAN_GET_SEQ_SEQ(sc); + + if (frag == 0) { + /* Reserve enough space to fit maximum frame length */ + skb = dev_alloc_skb(ieee->dev->mtu + + sizeof(struct libipw_hdr_4addr) + + 8 /* LLC */ + + 2 /* alignment */ + + 8 /* WEP */ + ETH_ALEN /* WDS */ ); + if (skb == NULL) + return NULL; + + entry = &ieee->frag_cache[ieee->frag_next_idx]; + ieee->frag_next_idx++; + if (ieee->frag_next_idx >= LIBIPW_FRAG_CACHE_LEN) + ieee->frag_next_idx = 0; + + if (entry->skb != NULL) + dev_kfree_skb_any(entry->skb); + + entry->first_frag_time = jiffies; + entry->seq = seq; + entry->last_frag = frag; + entry->skb = skb; + memcpy(entry->src_addr, hdr->addr2, ETH_ALEN); + memcpy(entry->dst_addr, hdr->addr1, ETH_ALEN); + } else { + /* received a fragment of a frame for which the head fragment + * should have already been received */ + entry = libipw_frag_cache_find(ieee, seq, frag, hdr->addr2, + hdr->addr1); + if (entry != NULL) { + entry->last_frag = frag; + skb = entry->skb; + } + } + + return skb; +} + +/* Called only as a tasklet (software IRQ) */ +static int libipw_frag_cache_invalidate(struct libipw_device *ieee, + struct libipw_hdr_4addr *hdr) +{ + u16 sc; + unsigned int seq; + struct libipw_frag_entry *entry; + + sc = le16_to_cpu(hdr->seq_ctl); + seq = WLAN_GET_SEQ_SEQ(sc); + + entry = libipw_frag_cache_find(ieee, seq, -1, hdr->addr2, + hdr->addr1); + + if (entry == NULL) { + LIBIPW_DEBUG_FRAG("could not invalidate fragment cache " + "entry (seq=%u)\n", seq); + return -1; + } + + entry->skb = NULL; + return 0; +} + +#ifdef NOT_YET +/* libipw_rx_frame_mgtmt + * + * Responsible for handling management control frames + * + * Called by libipw_rx */ +static int +libipw_rx_frame_mgmt(struct libipw_device *ieee, struct sk_buff *skb, + struct libipw_rx_stats *rx_stats, u16 type, + u16 stype) +{ + if (ieee->iw_mode == IW_MODE_MASTER) { + printk(KERN_DEBUG "%s: Master mode not yet supported.\n", + ieee->dev->name); + return 0; +/* + hostap_update_sta_ps(ieee, (struct hostap_libipw_hdr_4addr *) + skb->data);*/ + } + + if (ieee->hostapd && type == WLAN_FC_TYPE_MGMT) { + if (stype == WLAN_FC_STYPE_BEACON && + ieee->iw_mode == IW_MODE_MASTER) { + struct sk_buff *skb2; + /* Process beacon frames also in kernel driver to + * update STA(AP) table statistics */ + skb2 = skb_clone(skb, GFP_ATOMIC); + if (skb2) + hostap_rx(skb2->dev, skb2, rx_stats); + } + + /* send management frames to the user space daemon for + * processing */ + ieee->apdevstats.rx_packets++; + ieee->apdevstats.rx_bytes += skb->len; + prism2_rx_80211(ieee->apdev, skb, rx_stats, PRISM2_RX_MGMT); + return 0; + } + + if (ieee->iw_mode == IW_MODE_MASTER) { + if (type != WLAN_FC_TYPE_MGMT && type != WLAN_FC_TYPE_CTRL) { + printk(KERN_DEBUG "%s: unknown management frame " + "(type=0x%02x, stype=0x%02x) dropped\n", + skb->dev->name, type, stype); + return -1; + } + + hostap_rx(skb->dev, skb, rx_stats); + return 0; + } + + printk(KERN_DEBUG "%s: hostap_rx_frame_mgmt: management frame " + "received in non-Host AP mode\n", skb->dev->name); + return -1; +} +#endif + +/* See IEEE 802.1H for LLC/SNAP encapsulation/decapsulation */ +/* Ethernet-II snap header (RFC1042 for most EtherTypes) */ +static unsigned char libipw_rfc1042_header[] = + { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00 }; + +/* Bridge-Tunnel header (for EtherTypes ETH_P_AARP and ETH_P_IPX) */ +static unsigned char libipw_bridge_tunnel_header[] = + { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0xf8 }; +/* No encapsulation header if EtherType < 0x600 (=length) */ + +/* Called by libipw_rx_frame_decrypt */ +static int libipw_is_eapol_frame(struct libipw_device *ieee, + struct sk_buff *skb) +{ + struct net_device *dev = ieee->dev; + u16 fc, ethertype; + struct libipw_hdr_3addr *hdr; + u8 *pos; + + if (skb->len < 24) + return 0; + + hdr = (struct libipw_hdr_3addr *)skb->data; + fc = le16_to_cpu(hdr->frame_ctl); + + /* check that the frame is unicast frame to us */ + if ((fc & (IEEE80211_FCTL_TODS | IEEE80211_FCTL_FROMDS)) == + IEEE80211_FCTL_TODS && + ether_addr_equal(hdr->addr1, dev->dev_addr) && + ether_addr_equal(hdr->addr3, dev->dev_addr)) { + /* ToDS frame with own addr BSSID and DA */ + } else if ((fc & (IEEE80211_FCTL_TODS | IEEE80211_FCTL_FROMDS)) == + IEEE80211_FCTL_FROMDS && + ether_addr_equal(hdr->addr1, dev->dev_addr)) { + /* FromDS frame with own addr as DA */ + } else + return 0; + + if (skb->len < 24 + 8) + return 0; + + /* check for port access entity Ethernet type */ + pos = skb->data + 24; + ethertype = (pos[6] << 8) | pos[7]; + if (ethertype == ETH_P_PAE) + return 1; + + return 0; +} + +/* Called only as a tasklet (software IRQ), by libipw_rx */ +static int +libipw_rx_frame_decrypt(struct libipw_device *ieee, struct sk_buff *skb, + struct lib80211_crypt_data *crypt) +{ + struct libipw_hdr_3addr *hdr; + int res, hdrlen; + + if (crypt == NULL || crypt->ops->decrypt_mpdu == NULL) + return 0; + + hdr = (struct libipw_hdr_3addr *)skb->data; + hdrlen = libipw_get_hdrlen(le16_to_cpu(hdr->frame_ctl)); + + atomic_inc(&crypt->refcnt); + res = crypt->ops->decrypt_mpdu(skb, hdrlen, crypt->priv); + atomic_dec(&crypt->refcnt); + if (res < 0) { + LIBIPW_DEBUG_DROP("decryption failed (SA=%pM) res=%d\n", + hdr->addr2, res); + if (res == -2) + LIBIPW_DEBUG_DROP("Decryption failed ICV " + "mismatch (key %d)\n", + skb->data[hdrlen + 3] >> 6); + ieee->ieee_stats.rx_discards_undecryptable++; + return -1; + } + + return res; +} + +/* Called only as a tasklet (software IRQ), by libipw_rx */ +static int +libipw_rx_frame_decrypt_msdu(struct libipw_device *ieee, + struct sk_buff *skb, int keyidx, + struct lib80211_crypt_data *crypt) +{ + struct libipw_hdr_3addr *hdr; + int res, hdrlen; + + if (crypt == NULL || crypt->ops->decrypt_msdu == NULL) + return 0; + + hdr = (struct libipw_hdr_3addr *)skb->data; + hdrlen = libipw_get_hdrlen(le16_to_cpu(hdr->frame_ctl)); + + atomic_inc(&crypt->refcnt); + res = crypt->ops->decrypt_msdu(skb, keyidx, hdrlen, crypt->priv); + atomic_dec(&crypt->refcnt); + if (res < 0) { + printk(KERN_DEBUG "%s: MSDU decryption/MIC verification failed" + " (SA=%pM keyidx=%d)\n", ieee->dev->name, hdr->addr2, + keyidx); + return -1; + } + + return 0; +} + +/* All received frames are sent to this function. @skb contains the frame in + * IEEE 802.11 format, i.e., in the format it was sent over air. + * This function is called only as a tasklet (software IRQ). */ +int libipw_rx(struct libipw_device *ieee, struct sk_buff *skb, + struct libipw_rx_stats *rx_stats) +{ + struct net_device *dev = ieee->dev; + struct libipw_hdr_4addr *hdr; + size_t hdrlen; + u16 fc, type, stype, sc; + unsigned int frag; + u8 *payload; + u16 ethertype; +#ifdef NOT_YET + struct net_device *wds = NULL; + struct sk_buff *skb2 = NULL; + struct net_device *wds = NULL; + int frame_authorized = 0; + int from_assoc_ap = 0; + void *sta = NULL; +#endif + u8 dst[ETH_ALEN]; + u8 src[ETH_ALEN]; + struct lib80211_crypt_data *crypt = NULL; + int keyidx = 0; + int can_be_decrypted = 0; + + hdr = (struct libipw_hdr_4addr *)skb->data; + if (skb->len < 10) { + printk(KERN_INFO "%s: SKB length < 10\n", dev->name); + goto rx_dropped; + } + + fc = le16_to_cpu(hdr->frame_ctl); + type = WLAN_FC_GET_TYPE(fc); + stype = WLAN_FC_GET_STYPE(fc); + sc = le16_to_cpu(hdr->seq_ctl); + frag = WLAN_GET_SEQ_FRAG(sc); + hdrlen = libipw_get_hdrlen(fc); + + if (skb->len < hdrlen) { + printk(KERN_INFO "%s: invalid SKB length %d\n", + dev->name, skb->len); + goto rx_dropped; + } + + /* Put this code here so that we avoid duplicating it in all + * Rx paths. - Jean II */ +#ifdef CONFIG_WIRELESS_EXT +#ifdef IW_WIRELESS_SPY /* defined in iw_handler.h */ + /* If spy monitoring on */ + if (ieee->spy_data.spy_number > 0) { + struct iw_quality wstats; + + wstats.updated = 0; + if (rx_stats->mask & LIBIPW_STATMASK_RSSI) { + wstats.level = rx_stats->signal; + wstats.updated |= IW_QUAL_LEVEL_UPDATED; + } else + wstats.updated |= IW_QUAL_LEVEL_INVALID; + + if (rx_stats->mask & LIBIPW_STATMASK_NOISE) { + wstats.noise = rx_stats->noise; + wstats.updated |= IW_QUAL_NOISE_UPDATED; + } else + wstats.updated |= IW_QUAL_NOISE_INVALID; + + if (rx_stats->mask & LIBIPW_STATMASK_SIGNAL) { + wstats.qual = rx_stats->signal; + wstats.updated |= IW_QUAL_QUAL_UPDATED; + } else + wstats.updated |= IW_QUAL_QUAL_INVALID; + + /* Update spy records */ + wireless_spy_update(ieee->dev, hdr->addr2, &wstats); + } +#endif /* IW_WIRELESS_SPY */ +#endif /* CONFIG_WIRELESS_EXT */ + +#ifdef NOT_YET + hostap_update_rx_stats(local->ap, hdr, rx_stats); +#endif + + if (ieee->iw_mode == IW_MODE_MONITOR) { + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb->len; + libipw_monitor_rx(ieee, skb, rx_stats); + return 1; + } + + can_be_decrypted = (is_multicast_ether_addr(hdr->addr1) || + is_broadcast_ether_addr(hdr->addr2)) ? + ieee->host_mc_decrypt : ieee->host_decrypt; + + if (can_be_decrypted) { + if (skb->len >= hdrlen + 3) { + /* Top two-bits of byte 3 are the key index */ + keyidx = skb->data[hdrlen + 3] >> 6; + } + + /* ieee->crypt[] is WEP_KEY (4) in length. Given that keyidx + * is only allowed 2-bits of storage, no value of keyidx can + * be provided via above code that would result in keyidx + * being out of range */ + crypt = ieee->crypt_info.crypt[keyidx]; + +#ifdef NOT_YET + sta = NULL; + + /* Use station specific key to override default keys if the + * receiver address is a unicast address ("individual RA"). If + * bcrx_sta_key parameter is set, station specific key is used + * even with broad/multicast targets (this is against IEEE + * 802.11, but makes it easier to use different keys with + * stations that do not support WEP key mapping). */ + + if (is_unicast_ether_addr(hdr->addr1) || local->bcrx_sta_key) + (void)hostap_handle_sta_crypto(local, hdr, &crypt, + &sta); +#endif + + /* allow NULL decrypt to indicate an station specific override + * for default encryption */ + if (crypt && (crypt->ops == NULL || + crypt->ops->decrypt_mpdu == NULL)) + crypt = NULL; + + if (!crypt && (fc & IEEE80211_FCTL_PROTECTED)) { + /* This seems to be triggered by some (multicast?) + * frames from other than current BSS, so just drop the + * frames silently instead of filling system log with + * these reports. */ + LIBIPW_DEBUG_DROP("Decryption failed (not set)" + " (SA=%pM)\n", hdr->addr2); + ieee->ieee_stats.rx_discards_undecryptable++; + goto rx_dropped; + } + } +#ifdef NOT_YET + if (type != WLAN_FC_TYPE_DATA) { + if (type == WLAN_FC_TYPE_MGMT && stype == WLAN_FC_STYPE_AUTH && + fc & IEEE80211_FCTL_PROTECTED && ieee->host_decrypt && + (keyidx = hostap_rx_frame_decrypt(ieee, skb, crypt)) < 0) { + printk(KERN_DEBUG "%s: failed to decrypt mgmt::auth " + "from %pM\n", dev->name, hdr->addr2); + /* TODO: could inform hostapd about this so that it + * could send auth failure report */ + goto rx_dropped; + } + + if (libipw_rx_frame_mgmt(ieee, skb, rx_stats, type, stype)) + goto rx_dropped; + else + goto rx_exit; + } +#endif + /* drop duplicate 802.11 retransmissions (IEEE 802.11 Chap. 9.29) */ + if (sc == ieee->prev_seq_ctl) + goto rx_dropped; + else + ieee->prev_seq_ctl = sc; + + /* Data frame - extract src/dst addresses */ + if (skb->len < LIBIPW_3ADDR_LEN) + goto rx_dropped; + + switch (fc & (IEEE80211_FCTL_FROMDS | IEEE80211_FCTL_TODS)) { + case IEEE80211_FCTL_FROMDS: + memcpy(dst, hdr->addr1, ETH_ALEN); + memcpy(src, hdr->addr3, ETH_ALEN); + break; + case IEEE80211_FCTL_TODS: + memcpy(dst, hdr->addr3, ETH_ALEN); + memcpy(src, hdr->addr2, ETH_ALEN); + break; + case IEEE80211_FCTL_FROMDS | IEEE80211_FCTL_TODS: + if (skb->len < LIBIPW_4ADDR_LEN) + goto rx_dropped; + memcpy(dst, hdr->addr3, ETH_ALEN); + memcpy(src, hdr->addr4, ETH_ALEN); + break; + case 0: + memcpy(dst, hdr->addr1, ETH_ALEN); + memcpy(src, hdr->addr2, ETH_ALEN); + break; + } + +#ifdef NOT_YET + if (hostap_rx_frame_wds(ieee, hdr, fc, &wds)) + goto rx_dropped; + if (wds) { + skb->dev = dev = wds; + stats = hostap_get_stats(dev); + } + + if (ieee->iw_mode == IW_MODE_MASTER && !wds && + (fc & (IEEE80211_FCTL_TODS | IEEE80211_FCTL_FROMDS)) == + IEEE80211_FCTL_FROMDS && ieee->stadev && + ether_addr_equal(hdr->addr2, ieee->assoc_ap_addr)) { + /* Frame from BSSID of the AP for which we are a client */ + skb->dev = dev = ieee->stadev; + stats = hostap_get_stats(dev); + from_assoc_ap = 1; + } +#endif + +#ifdef NOT_YET + if ((ieee->iw_mode == IW_MODE_MASTER || + ieee->iw_mode == IW_MODE_REPEAT) && !from_assoc_ap) { + switch (hostap_handle_sta_rx(ieee, dev, skb, rx_stats, + wds != NULL)) { + case AP_RX_CONTINUE_NOT_AUTHORIZED: + frame_authorized = 0; + break; + case AP_RX_CONTINUE: + frame_authorized = 1; + break; + case AP_RX_DROP: + goto rx_dropped; + case AP_RX_EXIT: + goto rx_exit; + } + } +#endif + + /* Nullfunc frames may have PS-bit set, so they must be passed to + * hostap_handle_sta_rx() before being dropped here. */ + + stype &= ~IEEE80211_STYPE_QOS_DATA; + + if (stype != IEEE80211_STYPE_DATA && + stype != IEEE80211_STYPE_DATA_CFACK && + stype != IEEE80211_STYPE_DATA_CFPOLL && + stype != IEEE80211_STYPE_DATA_CFACKPOLL) { + if (stype != IEEE80211_STYPE_NULLFUNC) + LIBIPW_DEBUG_DROP("RX: dropped data frame " + "with no data (type=0x%02x, " + "subtype=0x%02x, len=%d)\n", + type, stype, skb->len); + goto rx_dropped; + } + + /* skb: hdr + (possibly fragmented, possibly encrypted) payload */ + + if ((fc & IEEE80211_FCTL_PROTECTED) && can_be_decrypted && + (keyidx = libipw_rx_frame_decrypt(ieee, skb, crypt)) < 0) + goto rx_dropped; + + hdr = (struct libipw_hdr_4addr *)skb->data; + + /* skb: hdr + (possibly fragmented) plaintext payload */ + // PR: FIXME: hostap has additional conditions in the "if" below: + // ieee->host_decrypt && (fc & IEEE80211_FCTL_PROTECTED) && + if ((frag != 0) || (fc & IEEE80211_FCTL_MOREFRAGS)) { + int flen; + struct sk_buff *frag_skb = libipw_frag_cache_get(ieee, hdr); + LIBIPW_DEBUG_FRAG("Rx Fragment received (%u)\n", frag); + + if (!frag_skb) { + LIBIPW_DEBUG(LIBIPW_DL_RX | LIBIPW_DL_FRAG, + "Rx cannot get skb from fragment " + "cache (morefrag=%d seq=%u frag=%u)\n", + (fc & IEEE80211_FCTL_MOREFRAGS) != 0, + WLAN_GET_SEQ_SEQ(sc), frag); + goto rx_dropped; + } + + flen = skb->len; + if (frag != 0) + flen -= hdrlen; + + if (frag_skb->tail + flen > frag_skb->end) { + printk(KERN_WARNING "%s: host decrypted and " + "reassembled frame did not fit skb\n", + dev->name); + libipw_frag_cache_invalidate(ieee, hdr); + goto rx_dropped; + } + + if (frag == 0) { + /* copy first fragment (including full headers) into + * beginning of the fragment cache skb */ + skb_copy_from_linear_data(skb, skb_put(frag_skb, flen), flen); + } else { + /* append frame payload to the end of the fragment + * cache skb */ + skb_copy_from_linear_data_offset(skb, hdrlen, + skb_put(frag_skb, flen), flen); + } + dev_kfree_skb_any(skb); + skb = NULL; + + if (fc & IEEE80211_FCTL_MOREFRAGS) { + /* more fragments expected - leave the skb in fragment + * cache for now; it will be delivered to upper layers + * after all fragments have been received */ + goto rx_exit; + } + + /* this was the last fragment and the frame will be + * delivered, so remove skb from fragment cache */ + skb = frag_skb; + hdr = (struct libipw_hdr_4addr *)skb->data; + libipw_frag_cache_invalidate(ieee, hdr); + } + + /* skb: hdr + (possible reassembled) full MSDU payload; possibly still + * encrypted/authenticated */ + if ((fc & IEEE80211_FCTL_PROTECTED) && can_be_decrypted && + libipw_rx_frame_decrypt_msdu(ieee, skb, keyidx, crypt)) + goto rx_dropped; + + hdr = (struct libipw_hdr_4addr *)skb->data; + if (crypt && !(fc & IEEE80211_FCTL_PROTECTED) && !ieee->open_wep) { + if ( /*ieee->ieee802_1x && */ + libipw_is_eapol_frame(ieee, skb)) { + /* pass unencrypted EAPOL frames even if encryption is + * configured */ + } else { + LIBIPW_DEBUG_DROP("encryption configured, but RX " + "frame not encrypted (SA=%pM)\n", + hdr->addr2); + goto rx_dropped; + } + } + + if (crypt && !(fc & IEEE80211_FCTL_PROTECTED) && !ieee->open_wep && + !libipw_is_eapol_frame(ieee, skb)) { + LIBIPW_DEBUG_DROP("dropped unencrypted RX data " + "frame from %pM (drop_unencrypted=1)\n", + hdr->addr2); + goto rx_dropped; + } + + /* If the frame was decrypted in hardware, we may need to strip off + * any security data (IV, ICV, etc) that was left behind */ + if (!can_be_decrypted && (fc & IEEE80211_FCTL_PROTECTED) && + ieee->host_strip_iv_icv) { + int trimlen = 0; + + /* Top two-bits of byte 3 are the key index */ + if (skb->len >= hdrlen + 3) + keyidx = skb->data[hdrlen + 3] >> 6; + + /* To strip off any security data which appears before the + * payload, we simply increase hdrlen (as the header gets + * chopped off immediately below). For the security data which + * appears after the payload, we use skb_trim. */ + + switch (ieee->sec.encode_alg[keyidx]) { + case SEC_ALG_WEP: + /* 4 byte IV */ + hdrlen += 4; + /* 4 byte ICV */ + trimlen = 4; + break; + case SEC_ALG_TKIP: + /* 4 byte IV, 4 byte ExtIV */ + hdrlen += 8; + /* 8 byte MIC, 4 byte ICV */ + trimlen = 12; + break; + case SEC_ALG_CCMP: + /* 8 byte CCMP header */ + hdrlen += 8; + /* 8 byte MIC */ + trimlen = 8; + break; + } + + if (skb->len < trimlen) + goto rx_dropped; + + __skb_trim(skb, skb->len - trimlen); + + if (skb->len < hdrlen) + goto rx_dropped; + } + + /* skb: hdr + (possible reassembled) full plaintext payload */ + + payload = skb->data + hdrlen; + ethertype = (payload[6] << 8) | payload[7]; + +#ifdef NOT_YET + /* If IEEE 802.1X is used, check whether the port is authorized to send + * the received frame. */ + if (ieee->ieee802_1x && ieee->iw_mode == IW_MODE_MASTER) { + if (ethertype == ETH_P_PAE) { + printk(KERN_DEBUG "%s: RX: IEEE 802.1X frame\n", + dev->name); + if (ieee->hostapd && ieee->apdev) { + /* Send IEEE 802.1X frames to the user + * space daemon for processing */ + prism2_rx_80211(ieee->apdev, skb, rx_stats, + PRISM2_RX_MGMT); + ieee->apdevstats.rx_packets++; + ieee->apdevstats.rx_bytes += skb->len; + goto rx_exit; + } + } else if (!frame_authorized) { + printk(KERN_DEBUG "%s: dropped frame from " + "unauthorized port (IEEE 802.1X): " + "ethertype=0x%04x\n", dev->name, ethertype); + goto rx_dropped; + } + } +#endif + + /* convert hdr + possible LLC headers into Ethernet header */ + if (skb->len - hdrlen >= 8 && + ((memcmp(payload, libipw_rfc1042_header, SNAP_SIZE) == 0 && + ethertype != ETH_P_AARP && ethertype != ETH_P_IPX) || + memcmp(payload, libipw_bridge_tunnel_header, SNAP_SIZE) == 0)) { + /* remove RFC1042 or Bridge-Tunnel encapsulation and + * replace EtherType */ + skb_pull(skb, hdrlen + SNAP_SIZE); + memcpy(skb_push(skb, ETH_ALEN), src, ETH_ALEN); + memcpy(skb_push(skb, ETH_ALEN), dst, ETH_ALEN); + } else { + __be16 len; + /* Leave Ethernet header part of hdr and full payload */ + skb_pull(skb, hdrlen); + len = htons(skb->len); + memcpy(skb_push(skb, 2), &len, 2); + memcpy(skb_push(skb, ETH_ALEN), src, ETH_ALEN); + memcpy(skb_push(skb, ETH_ALEN), dst, ETH_ALEN); + } + +#ifdef NOT_YET + if (wds && ((fc & (IEEE80211_FCTL_TODS | IEEE80211_FCTL_FROMDS)) == + IEEE80211_FCTL_TODS) && skb->len >= ETH_HLEN + ETH_ALEN) { + /* Non-standard frame: get addr4 from its bogus location after + * the payload */ + skb_copy_to_linear_data_offset(skb, ETH_ALEN, + skb->data + skb->len - ETH_ALEN, + ETH_ALEN); + skb_trim(skb, skb->len - ETH_ALEN); + } +#endif + + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb->len; + +#ifdef NOT_YET + if (ieee->iw_mode == IW_MODE_MASTER && !wds && ieee->ap->bridge_packets) { + if (is_multicast_ether_addr(dst)) { + /* copy multicast frame both to the higher layers and + * to the wireless media */ + ieee->ap->bridged_multicast++; + skb2 = skb_clone(skb, GFP_ATOMIC); + if (skb2 == NULL) + printk(KERN_DEBUG "%s: skb_clone failed for " + "multicast frame\n", dev->name); + } else if (hostap_is_sta_assoc(ieee->ap, dst)) { + /* send frame directly to the associated STA using + * wireless media and not passing to higher layers */ + ieee->ap->bridged_unicast++; + skb2 = skb; + skb = NULL; + } + } + + if (skb2 != NULL) { + /* send to wireless media */ + skb2->dev = dev; + skb2->protocol = htons(ETH_P_802_3); + skb_reset_mac_header(skb2); + skb_reset_network_header(skb2); + /* skb2->network_header += ETH_HLEN; */ + dev_queue_xmit(skb2); + } +#endif + + if (skb) { + skb->protocol = eth_type_trans(skb, dev); + memset(skb->cb, 0, sizeof(skb->cb)); + skb->ip_summed = CHECKSUM_NONE; /* 802.11 crc not sufficient */ + if (netif_rx(skb) == NET_RX_DROP) { + /* netif_rx always succeeds, but it might drop + * the packet. If it drops the packet, we log that + * in our stats. */ + LIBIPW_DEBUG_DROP + ("RX: netif_rx dropped the packet\n"); + dev->stats.rx_dropped++; + } + } + + rx_exit: +#ifdef NOT_YET + if (sta) + hostap_handle_sta_release(sta); +#endif + return 1; + + rx_dropped: + dev->stats.rx_dropped++; + + /* Returning 0 indicates to caller that we have not handled the SKB-- + * so it is still allocated and can be used again by underlying + * hardware as a DMA target */ + return 0; +} + +/* Filter out unrelated packets, call libipw_rx[_mgt] + * This function takes over the skb, it should not be used again after calling + * this function. */ +void libipw_rx_any(struct libipw_device *ieee, + struct sk_buff *skb, struct libipw_rx_stats *stats) +{ + struct libipw_hdr_4addr *hdr; + int is_packet_for_us; + u16 fc; + + if (ieee->iw_mode == IW_MODE_MONITOR) { + if (!libipw_rx(ieee, skb, stats)) + dev_kfree_skb_irq(skb); + return; + } + + if (skb->len < sizeof(struct ieee80211_hdr)) + goto drop_free; + + hdr = (struct libipw_hdr_4addr *)skb->data; + fc = le16_to_cpu(hdr->frame_ctl); + + if ((fc & IEEE80211_FCTL_VERS) != 0) + goto drop_free; + + switch (fc & IEEE80211_FCTL_FTYPE) { + case IEEE80211_FTYPE_MGMT: + if (skb->len < sizeof(struct libipw_hdr_3addr)) + goto drop_free; + libipw_rx_mgt(ieee, hdr, stats); + dev_kfree_skb_irq(skb); + return; + case IEEE80211_FTYPE_DATA: + break; + case IEEE80211_FTYPE_CTL: + return; + default: + return; + } + + is_packet_for_us = 0; + switch (ieee->iw_mode) { + case IW_MODE_ADHOC: + /* our BSS and not from/to DS */ + if (memcmp(hdr->addr3, ieee->bssid, ETH_ALEN) == 0) + if ((fc & (IEEE80211_FCTL_TODS+IEEE80211_FCTL_FROMDS)) == 0) { + /* promisc: get all */ + if (ieee->dev->flags & IFF_PROMISC) + is_packet_for_us = 1; + /* to us */ + else if (memcmp(hdr->addr1, ieee->dev->dev_addr, ETH_ALEN) == 0) + is_packet_for_us = 1; + /* mcast */ + else if (is_multicast_ether_addr(hdr->addr1)) + is_packet_for_us = 1; + } + break; + case IW_MODE_INFRA: + /* our BSS (== from our AP) and from DS */ + if (memcmp(hdr->addr2, ieee->bssid, ETH_ALEN) == 0) + if ((fc & (IEEE80211_FCTL_TODS+IEEE80211_FCTL_FROMDS)) == IEEE80211_FCTL_FROMDS) { + /* promisc: get all */ + if (ieee->dev->flags & IFF_PROMISC) + is_packet_for_us = 1; + /* to us */ + else if (memcmp(hdr->addr1, ieee->dev->dev_addr, ETH_ALEN) == 0) + is_packet_for_us = 1; + /* mcast */ + else if (is_multicast_ether_addr(hdr->addr1)) { + /* not our own packet bcasted from AP */ + if (memcmp(hdr->addr3, ieee->dev->dev_addr, ETH_ALEN)) + is_packet_for_us = 1; + } + } + break; + default: + /* ? */ + break; + } + + if (is_packet_for_us) + if (!libipw_rx(ieee, skb, stats)) + dev_kfree_skb_irq(skb); + return; + +drop_free: + dev_kfree_skb_irq(skb); + ieee->dev->stats.rx_dropped++; +} + +#define MGMT_FRAME_FIXED_PART_LENGTH 0x24 + +static u8 qos_oui[QOS_OUI_LEN] = { 0x00, 0x50, 0xF2 }; + +/* +* Make the structure we read from the beacon packet to have +* the right values +*/ +static int libipw_verify_qos_info(struct libipw_qos_information_element + *info_element, int sub_type) +{ + + if (info_element->qui_subtype != sub_type) + return -1; + if (memcmp(info_element->qui, qos_oui, QOS_OUI_LEN)) + return -1; + if (info_element->qui_type != QOS_OUI_TYPE) + return -1; + if (info_element->version != QOS_VERSION_1) + return -1; + + return 0; +} + +/* + * Parse a QoS parameter element + */ +static int libipw_read_qos_param_element(struct libipw_qos_parameter_info + *element_param, struct libipw_info_element + *info_element) +{ + int ret = 0; + u16 size = sizeof(struct libipw_qos_parameter_info) - 2; + + if ((info_element == NULL) || (element_param == NULL)) + return -1; + + if (info_element->id == QOS_ELEMENT_ID && info_element->len == size) { + memcpy(element_param->info_element.qui, info_element->data, + info_element->len); + element_param->info_element.elementID = info_element->id; + element_param->info_element.length = info_element->len; + } else + ret = -1; + if (ret == 0) + ret = libipw_verify_qos_info(&element_param->info_element, + QOS_OUI_PARAM_SUB_TYPE); + return ret; +} + +/* + * Parse a QoS information element + */ +static int libipw_read_qos_info_element(struct + libipw_qos_information_element + *element_info, struct libipw_info_element + *info_element) +{ + int ret = 0; + u16 size = sizeof(struct libipw_qos_information_element) - 2; + + if (element_info == NULL) + return -1; + if (info_element == NULL) + return -1; + + if ((info_element->id == QOS_ELEMENT_ID) && (info_element->len == size)) { + memcpy(element_info->qui, info_element->data, + info_element->len); + element_info->elementID = info_element->id; + element_info->length = info_element->len; + } else + ret = -1; + + if (ret == 0) + ret = libipw_verify_qos_info(element_info, + QOS_OUI_INFO_SUB_TYPE); + return ret; +} + +/* + * Write QoS parameters from the ac parameters. + */ +static int libipw_qos_convert_ac_to_parameters(struct + libipw_qos_parameter_info + *param_elm, struct + libipw_qos_parameters + *qos_param) +{ + int rc = 0; + int i; + struct libipw_qos_ac_parameter *ac_params; + u32 txop; + u8 cw_min; + u8 cw_max; + + for (i = 0; i < QOS_QUEUE_NUM; i++) { + ac_params = &(param_elm->ac_params_record[i]); + + qos_param->aifs[i] = (ac_params->aci_aifsn) & 0x0F; + qos_param->aifs[i] -= (qos_param->aifs[i] < 2) ? 0 : 2; + + cw_min = ac_params->ecw_min_max & 0x0F; + qos_param->cw_min[i] = cpu_to_le16((1 << cw_min) - 1); + + cw_max = (ac_params->ecw_min_max & 0xF0) >> 4; + qos_param->cw_max[i] = cpu_to_le16((1 << cw_max) - 1); + + qos_param->flag[i] = + (ac_params->aci_aifsn & 0x10) ? 0x01 : 0x00; + + txop = le16_to_cpu(ac_params->tx_op_limit) * 32; + qos_param->tx_op_limit[i] = cpu_to_le16(txop); + } + return rc; +} + +/* + * we have a generic data element which it may contain QoS information or + * parameters element. check the information element length to decide + * which type to read + */ +static int libipw_parse_qos_info_param_IE(struct libipw_info_element + *info_element, + struct libipw_network *network) +{ + int rc = 0; + struct libipw_qos_parameters *qos_param = NULL; + struct libipw_qos_information_element qos_info_element; + + rc = libipw_read_qos_info_element(&qos_info_element, info_element); + + if (rc == 0) { + network->qos_data.param_count = qos_info_element.ac_info & 0x0F; + network->flags |= NETWORK_HAS_QOS_INFORMATION; + } else { + struct libipw_qos_parameter_info param_element; + + rc = libipw_read_qos_param_element(¶m_element, + info_element); + if (rc == 0) { + qos_param = &(network->qos_data.parameters); + libipw_qos_convert_ac_to_parameters(¶m_element, + qos_param); + network->flags |= NETWORK_HAS_QOS_PARAMETERS; + network->qos_data.param_count = + param_element.info_element.ac_info & 0x0F; + } + } + + if (rc == 0) { + LIBIPW_DEBUG_QOS("QoS is supported\n"); + network->qos_data.supported = 1; + } + return rc; +} + +#ifdef CPTCFG_LIBIPW_DEBUG +#define MFIE_STRING(x) case WLAN_EID_ ##x: return #x + +static const char *get_info_element_string(u16 id) +{ + switch (id) { + MFIE_STRING(SSID); + MFIE_STRING(SUPP_RATES); + MFIE_STRING(FH_PARAMS); + MFIE_STRING(DS_PARAMS); + MFIE_STRING(CF_PARAMS); + MFIE_STRING(TIM); + MFIE_STRING(IBSS_PARAMS); + MFIE_STRING(COUNTRY); + MFIE_STRING(HP_PARAMS); + MFIE_STRING(HP_TABLE); + MFIE_STRING(REQUEST); + MFIE_STRING(CHALLENGE); + MFIE_STRING(PWR_CONSTRAINT); + MFIE_STRING(PWR_CAPABILITY); + MFIE_STRING(TPC_REQUEST); + MFIE_STRING(TPC_REPORT); + MFIE_STRING(SUPPORTED_CHANNELS); + MFIE_STRING(CHANNEL_SWITCH); + MFIE_STRING(MEASURE_REQUEST); + MFIE_STRING(MEASURE_REPORT); + MFIE_STRING(QUIET); + MFIE_STRING(IBSS_DFS); + MFIE_STRING(ERP_INFO); + MFIE_STRING(RSN); + MFIE_STRING(EXT_SUPP_RATES); + MFIE_STRING(VENDOR_SPECIFIC); + MFIE_STRING(QOS_PARAMETER); + default: + return "UNKNOWN"; + } +} +#endif + +static int libipw_parse_info_param(struct libipw_info_element + *info_element, u16 length, + struct libipw_network *network) +{ + DECLARE_SSID_BUF(ssid); + u8 i; +#ifdef CPTCFG_LIBIPW_DEBUG + char rates_str[64]; + char *p; +#endif + + while (length >= sizeof(*info_element)) { + if (sizeof(*info_element) + info_element->len > length) { + LIBIPW_DEBUG_MGMT("Info elem: parse failed: " + "info_element->len + 2 > left : " + "info_element->len+2=%zd left=%d, id=%d.\n", + info_element->len + + sizeof(*info_element), + length, info_element->id); + /* We stop processing but don't return an error here + * because some misbehaviour APs break this rule. ie. + * Orinoco AP1000. */ + break; + } + + switch (info_element->id) { + case WLAN_EID_SSID: + network->ssid_len = min(info_element->len, + (u8) IW_ESSID_MAX_SIZE); + memcpy(network->ssid, info_element->data, + network->ssid_len); + if (network->ssid_len < IW_ESSID_MAX_SIZE) + memset(network->ssid + network->ssid_len, 0, + IW_ESSID_MAX_SIZE - network->ssid_len); + + LIBIPW_DEBUG_MGMT("WLAN_EID_SSID: '%s' len=%d.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->ssid_len); + break; + + case WLAN_EID_SUPP_RATES: +#ifdef CPTCFG_LIBIPW_DEBUG + p = rates_str; +#endif + network->rates_len = min(info_element->len, + MAX_RATES_LENGTH); + for (i = 0; i < network->rates_len; i++) { + network->rates[i] = info_element->data[i]; +#ifdef CPTCFG_LIBIPW_DEBUG + p += snprintf(p, sizeof(rates_str) - + (p - rates_str), "%02X ", + network->rates[i]); +#endif + if (libipw_is_ofdm_rate + (info_element->data[i])) { + network->flags |= NETWORK_HAS_OFDM; + if (info_element->data[i] & + LIBIPW_BASIC_RATE_MASK) + network->flags &= + ~NETWORK_HAS_CCK; + } + } + + LIBIPW_DEBUG_MGMT("WLAN_EID_SUPP_RATES: '%s' (%d)\n", + rates_str, network->rates_len); + break; + + case WLAN_EID_EXT_SUPP_RATES: +#ifdef CPTCFG_LIBIPW_DEBUG + p = rates_str; +#endif + network->rates_ex_len = min(info_element->len, + MAX_RATES_EX_LENGTH); + for (i = 0; i < network->rates_ex_len; i++) { + network->rates_ex[i] = info_element->data[i]; +#ifdef CPTCFG_LIBIPW_DEBUG + p += snprintf(p, sizeof(rates_str) - + (p - rates_str), "%02X ", + network->rates[i]); +#endif + if (libipw_is_ofdm_rate + (info_element->data[i])) { + network->flags |= NETWORK_HAS_OFDM; + if (info_element->data[i] & + LIBIPW_BASIC_RATE_MASK) + network->flags &= + ~NETWORK_HAS_CCK; + } + } + + LIBIPW_DEBUG_MGMT("WLAN_EID_EXT_SUPP_RATES: '%s' (%d)\n", + rates_str, network->rates_ex_len); + break; + + case WLAN_EID_DS_PARAMS: + LIBIPW_DEBUG_MGMT("WLAN_EID_DS_PARAMS: %d\n", + info_element->data[0]); + network->channel = info_element->data[0]; + break; + + case WLAN_EID_FH_PARAMS: + LIBIPW_DEBUG_MGMT("WLAN_EID_FH_PARAMS: ignored\n"); + break; + + case WLAN_EID_CF_PARAMS: + LIBIPW_DEBUG_MGMT("WLAN_EID_CF_PARAMS: ignored\n"); + break; + + case WLAN_EID_TIM: + network->tim.tim_count = info_element->data[0]; + network->tim.tim_period = info_element->data[1]; + LIBIPW_DEBUG_MGMT("WLAN_EID_TIM: partially ignored\n"); + break; + + case WLAN_EID_ERP_INFO: + network->erp_value = info_element->data[0]; + network->flags |= NETWORK_HAS_ERP_VALUE; + LIBIPW_DEBUG_MGMT("MFIE_TYPE_ERP_SET: %d\n", + network->erp_value); + break; + + case WLAN_EID_IBSS_PARAMS: + network->atim_window = info_element->data[0]; + LIBIPW_DEBUG_MGMT("WLAN_EID_IBSS_PARAMS: %d\n", + network->atim_window); + break; + + case WLAN_EID_CHALLENGE: + LIBIPW_DEBUG_MGMT("WLAN_EID_CHALLENGE: ignored\n"); + break; + + case WLAN_EID_VENDOR_SPECIFIC: + LIBIPW_DEBUG_MGMT("WLAN_EID_VENDOR_SPECIFIC: %d bytes\n", + info_element->len); + if (!libipw_parse_qos_info_param_IE(info_element, + network)) + break; + + if (info_element->len >= 4 && + info_element->data[0] == 0x00 && + info_element->data[1] == 0x50 && + info_element->data[2] == 0xf2 && + info_element->data[3] == 0x01) { + network->wpa_ie_len = min(info_element->len + 2, + MAX_WPA_IE_LEN); + memcpy(network->wpa_ie, info_element, + network->wpa_ie_len); + } + break; + + case WLAN_EID_RSN: + LIBIPW_DEBUG_MGMT("WLAN_EID_RSN: %d bytes\n", + info_element->len); + network->rsn_ie_len = min(info_element->len + 2, + MAX_WPA_IE_LEN); + memcpy(network->rsn_ie, info_element, + network->rsn_ie_len); + break; + + case WLAN_EID_QOS_PARAMETER: + printk(KERN_ERR + "QoS Error need to parse QOS_PARAMETER IE\n"); + break; + /* 802.11h */ + case WLAN_EID_PWR_CONSTRAINT: + network->power_constraint = info_element->data[0]; + network->flags |= NETWORK_HAS_POWER_CONSTRAINT; + break; + + case WLAN_EID_CHANNEL_SWITCH: + network->power_constraint = info_element->data[0]; + network->flags |= NETWORK_HAS_CSA; + break; + + case WLAN_EID_QUIET: + network->quiet.count = info_element->data[0]; + network->quiet.period = info_element->data[1]; + network->quiet.duration = info_element->data[2]; + network->quiet.offset = info_element->data[3]; + network->flags |= NETWORK_HAS_QUIET; + break; + + case WLAN_EID_IBSS_DFS: + if (network->ibss_dfs) + break; + network->ibss_dfs = kmemdup(info_element->data, + info_element->len, + GFP_ATOMIC); + if (!network->ibss_dfs) + return 1; + network->flags |= NETWORK_HAS_IBSS_DFS; + break; + + case WLAN_EID_TPC_REPORT: + network->tpc_report.transmit_power = + info_element->data[0]; + network->tpc_report.link_margin = info_element->data[1]; + network->flags |= NETWORK_HAS_TPC_REPORT; + break; + + default: + LIBIPW_DEBUG_MGMT + ("Unsupported info element: %s (%d)\n", + get_info_element_string(info_element->id), + info_element->id); + break; + } + + length -= sizeof(*info_element) + info_element->len; + info_element = + (struct libipw_info_element *)&info_element-> + data[info_element->len]; + } + + return 0; +} + +static int libipw_handle_assoc_resp(struct libipw_device *ieee, struct libipw_assoc_response + *frame, struct libipw_rx_stats *stats) +{ + struct libipw_network network_resp = { + .ibss_dfs = NULL, + }; + struct libipw_network *network = &network_resp; + struct net_device *dev = ieee->dev; + + network->flags = 0; + network->qos_data.active = 0; + network->qos_data.supported = 0; + network->qos_data.param_count = 0; + network->qos_data.old_param_count = 0; + + //network->atim_window = le16_to_cpu(frame->aid) & (0x3FFF); + network->atim_window = le16_to_cpu(frame->aid); + network->listen_interval = le16_to_cpu(frame->status); + memcpy(network->bssid, frame->header.addr3, ETH_ALEN); + network->capability = le16_to_cpu(frame->capability); + network->last_scanned = jiffies; + network->rates_len = network->rates_ex_len = 0; + network->last_associate = 0; + network->ssid_len = 0; + network->erp_value = + (network->capability & WLAN_CAPABILITY_IBSS) ? 0x3 : 0x0; + + if (stats->freq == LIBIPW_52GHZ_BAND) { + /* for A band (No DS info) */ + network->channel = stats->received_channel; + } else + network->flags |= NETWORK_HAS_CCK; + + network->wpa_ie_len = 0; + network->rsn_ie_len = 0; + + if (libipw_parse_info_param + (frame->info_element, stats->len - sizeof(*frame), network)) + return 1; + + network->mode = 0; + if (stats->freq == LIBIPW_52GHZ_BAND) + network->mode = IEEE_A; + else { + if (network->flags & NETWORK_HAS_OFDM) + network->mode |= IEEE_G; + if (network->flags & NETWORK_HAS_CCK) + network->mode |= IEEE_B; + } + + memcpy(&network->stats, stats, sizeof(network->stats)); + + if (ieee->handle_assoc_response != NULL) + ieee->handle_assoc_response(dev, frame, network); + + return 0; +} + +/***************************************************/ + +static int libipw_network_init(struct libipw_device *ieee, struct libipw_probe_response + *beacon, + struct libipw_network *network, + struct libipw_rx_stats *stats) +{ + DECLARE_SSID_BUF(ssid); + + network->qos_data.active = 0; + network->qos_data.supported = 0; + network->qos_data.param_count = 0; + network->qos_data.old_param_count = 0; + + /* Pull out fixed field data */ + memcpy(network->bssid, beacon->header.addr3, ETH_ALEN); + network->capability = le16_to_cpu(beacon->capability); + network->last_scanned = jiffies; + network->time_stamp[0] = le32_to_cpu(beacon->time_stamp[0]); + network->time_stamp[1] = le32_to_cpu(beacon->time_stamp[1]); + network->beacon_interval = le16_to_cpu(beacon->beacon_interval); + /* Where to pull this? beacon->listen_interval; */ + network->listen_interval = 0x0A; + network->rates_len = network->rates_ex_len = 0; + network->last_associate = 0; + network->ssid_len = 0; + network->flags = 0; + network->atim_window = 0; + network->erp_value = (network->capability & WLAN_CAPABILITY_IBSS) ? + 0x3 : 0x0; + + if (stats->freq == LIBIPW_52GHZ_BAND) { + /* for A band (No DS info) */ + network->channel = stats->received_channel; + } else + network->flags |= NETWORK_HAS_CCK; + + network->wpa_ie_len = 0; + network->rsn_ie_len = 0; + + if (libipw_parse_info_param + (beacon->info_element, stats->len - sizeof(*beacon), network)) + return 1; + + network->mode = 0; + if (stats->freq == LIBIPW_52GHZ_BAND) + network->mode = IEEE_A; + else { + if (network->flags & NETWORK_HAS_OFDM) + network->mode |= IEEE_G; + if (network->flags & NETWORK_HAS_CCK) + network->mode |= IEEE_B; + } + + if (network->mode == 0) { + LIBIPW_DEBUG_SCAN("Filtered out '%s (%pM)' " + "network.\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid); + return 1; + } + + memcpy(&network->stats, stats, sizeof(network->stats)); + + return 0; +} + +static inline int is_same_network(struct libipw_network *src, + struct libipw_network *dst) +{ + /* A network is only a duplicate if the channel, BSSID, and ESSID + * all match. We treat all with the same BSSID and channel + * as one network */ + return ((src->ssid_len == dst->ssid_len) && + (src->channel == dst->channel) && + ether_addr_equal(src->bssid, dst->bssid) && + !memcmp(src->ssid, dst->ssid, src->ssid_len)); +} + +static void update_network(struct libipw_network *dst, + struct libipw_network *src) +{ + int qos_active; + u8 old_param; + + libipw_network_reset(dst); + dst->ibss_dfs = src->ibss_dfs; + + /* We only update the statistics if they were created by receiving + * the network information on the actual channel the network is on. + * + * This keeps beacons received on neighbor channels from bringing + * down the signal level of an AP. */ + if (dst->channel == src->stats.received_channel) + memcpy(&dst->stats, &src->stats, + sizeof(struct libipw_rx_stats)); + else + LIBIPW_DEBUG_SCAN("Network %pM info received " + "off channel (%d vs. %d)\n", src->bssid, + dst->channel, src->stats.received_channel); + + dst->capability = src->capability; + memcpy(dst->rates, src->rates, src->rates_len); + dst->rates_len = src->rates_len; + memcpy(dst->rates_ex, src->rates_ex, src->rates_ex_len); + dst->rates_ex_len = src->rates_ex_len; + + dst->mode = src->mode; + dst->flags = src->flags; + dst->time_stamp[0] = src->time_stamp[0]; + dst->time_stamp[1] = src->time_stamp[1]; + + dst->beacon_interval = src->beacon_interval; + dst->listen_interval = src->listen_interval; + dst->atim_window = src->atim_window; + dst->erp_value = src->erp_value; + dst->tim = src->tim; + + memcpy(dst->wpa_ie, src->wpa_ie, src->wpa_ie_len); + dst->wpa_ie_len = src->wpa_ie_len; + memcpy(dst->rsn_ie, src->rsn_ie, src->rsn_ie_len); + dst->rsn_ie_len = src->rsn_ie_len; + + dst->last_scanned = jiffies; + qos_active = src->qos_data.active; + old_param = dst->qos_data.old_param_count; + if (dst->flags & NETWORK_HAS_QOS_MASK) + memcpy(&dst->qos_data, &src->qos_data, + sizeof(struct libipw_qos_data)); + else { + dst->qos_data.supported = src->qos_data.supported; + dst->qos_data.param_count = src->qos_data.param_count; + } + + if (dst->qos_data.supported == 1) { + if (dst->ssid_len) + LIBIPW_DEBUG_QOS + ("QoS the network %s is QoS supported\n", + dst->ssid); + else + LIBIPW_DEBUG_QOS + ("QoS the network is QoS supported\n"); + } + dst->qos_data.active = qos_active; + dst->qos_data.old_param_count = old_param; + + /* dst->last_associate is not overwritten */ +} + +static inline int is_beacon(__le16 fc) +{ + return (WLAN_FC_GET_STYPE(le16_to_cpu(fc)) == IEEE80211_STYPE_BEACON); +} + +static void libipw_process_probe_response(struct libipw_device + *ieee, struct + libipw_probe_response + *beacon, struct libipw_rx_stats + *stats) +{ + struct net_device *dev = ieee->dev; + struct libipw_network network = { + .ibss_dfs = NULL, + }; + struct libipw_network *target; + struct libipw_network *oldest = NULL; +#ifdef CPTCFG_LIBIPW_DEBUG + struct libipw_info_element *info_element = beacon->info_element; +#endif + unsigned long flags; + DECLARE_SSID_BUF(ssid); + + LIBIPW_DEBUG_SCAN("'%s' (%pM" + "): %c%c%c%c %c%c%c%c-%c%c%c%c %c%c%c%c\n", + print_ssid(ssid, info_element->data, info_element->len), + beacon->header.addr3, + (beacon->capability & cpu_to_le16(1 << 0xf)) ? '1' : '0', + (beacon->capability & cpu_to_le16(1 << 0xe)) ? '1' : '0', + (beacon->capability & cpu_to_le16(1 << 0xd)) ? '1' : '0', + (beacon->capability & cpu_to_le16(1 << 0xc)) ? '1' : '0', + (beacon->capability & cpu_to_le16(1 << 0xb)) ? '1' : '0', + (beacon->capability & cpu_to_le16(1 << 0xa)) ? '1' : '0', + (beacon->capability & cpu_to_le16(1 << 0x9)) ? '1' : '0', + (beacon->capability & cpu_to_le16(1 << 0x8)) ? '1' : '0', + (beacon->capability & cpu_to_le16(1 << 0x7)) ? '1' : '0', + (beacon->capability & cpu_to_le16(1 << 0x6)) ? '1' : '0', + (beacon->capability & cpu_to_le16(1 << 0x5)) ? '1' : '0', + (beacon->capability & cpu_to_le16(1 << 0x4)) ? '1' : '0', + (beacon->capability & cpu_to_le16(1 << 0x3)) ? '1' : '0', + (beacon->capability & cpu_to_le16(1 << 0x2)) ? '1' : '0', + (beacon->capability & cpu_to_le16(1 << 0x1)) ? '1' : '0', + (beacon->capability & cpu_to_le16(1 << 0x0)) ? '1' : '0'); + + if (libipw_network_init(ieee, beacon, &network, stats)) { + LIBIPW_DEBUG_SCAN("Dropped '%s' (%pM) via %s.\n", + print_ssid(ssid, info_element->data, + info_element->len), + beacon->header.addr3, + is_beacon(beacon->header.frame_ctl) ? + "BEACON" : "PROBE RESPONSE"); + return; + } + + /* The network parsed correctly -- so now we scan our known networks + * to see if we can find it in our list. + * + * NOTE: This search is definitely not optimized. Once its doing + * the "right thing" we'll optimize it for efficiency if + * necessary */ + + /* Search for this entry in the list and update it if it is + * already there. */ + + spin_lock_irqsave(&ieee->lock, flags); + + list_for_each_entry(target, &ieee->network_list, list) { + if (is_same_network(target, &network)) + break; + + if ((oldest == NULL) || + time_before(target->last_scanned, oldest->last_scanned)) + oldest = target; + } + + /* If we didn't find a match, then get a new network slot to initialize + * with this beacon's information */ + if (&target->list == &ieee->network_list) { + if (list_empty(&ieee->network_free_list)) { + /* If there are no more slots, expire the oldest */ + list_del(&oldest->list); + target = oldest; + LIBIPW_DEBUG_SCAN("Expired '%s' (%pM) from " + "network list.\n", + print_ssid(ssid, target->ssid, + target->ssid_len), + target->bssid); + libipw_network_reset(target); + } else { + /* Otherwise just pull from the free list */ + target = list_entry(ieee->network_free_list.next, + struct libipw_network, list); + list_del(ieee->network_free_list.next); + } + +#ifdef CPTCFG_LIBIPW_DEBUG + LIBIPW_DEBUG_SCAN("Adding '%s' (%pM) via %s.\n", + print_ssid(ssid, network.ssid, + network.ssid_len), + network.bssid, + is_beacon(beacon->header.frame_ctl) ? + "BEACON" : "PROBE RESPONSE"); +#endif + memcpy(target, &network, sizeof(*target)); + network.ibss_dfs = NULL; + list_add_tail(&target->list, &ieee->network_list); + } else { + LIBIPW_DEBUG_SCAN("Updating '%s' (%pM) via %s.\n", + print_ssid(ssid, target->ssid, + target->ssid_len), + target->bssid, + is_beacon(beacon->header.frame_ctl) ? + "BEACON" : "PROBE RESPONSE"); + update_network(target, &network); + network.ibss_dfs = NULL; + } + + spin_unlock_irqrestore(&ieee->lock, flags); + + if (is_beacon(beacon->header.frame_ctl)) { + if (ieee->handle_beacon != NULL) + ieee->handle_beacon(dev, beacon, target); + } else { + if (ieee->handle_probe_response != NULL) + ieee->handle_probe_response(dev, beacon, target); + } +} + +void libipw_rx_mgt(struct libipw_device *ieee, + struct libipw_hdr_4addr *header, + struct libipw_rx_stats *stats) +{ + switch (WLAN_FC_GET_STYPE(le16_to_cpu(header->frame_ctl))) { + case IEEE80211_STYPE_ASSOC_RESP: + LIBIPW_DEBUG_MGMT("received ASSOCIATION RESPONSE (%d)\n", + WLAN_FC_GET_STYPE(le16_to_cpu + (header->frame_ctl))); + libipw_handle_assoc_resp(ieee, + (struct libipw_assoc_response *) + header, stats); + break; + + case IEEE80211_STYPE_REASSOC_RESP: + LIBIPW_DEBUG_MGMT("received REASSOCIATION RESPONSE (%d)\n", + WLAN_FC_GET_STYPE(le16_to_cpu + (header->frame_ctl))); + break; + + case IEEE80211_STYPE_PROBE_REQ: + LIBIPW_DEBUG_MGMT("received auth (%d)\n", + WLAN_FC_GET_STYPE(le16_to_cpu + (header->frame_ctl))); + + if (ieee->handle_probe_request != NULL) + ieee->handle_probe_request(ieee->dev, + (struct + libipw_probe_request *) + header, stats); + break; + + case IEEE80211_STYPE_PROBE_RESP: + LIBIPW_DEBUG_MGMT("received PROBE RESPONSE (%d)\n", + WLAN_FC_GET_STYPE(le16_to_cpu + (header->frame_ctl))); + LIBIPW_DEBUG_SCAN("Probe response\n"); + libipw_process_probe_response(ieee, + (struct + libipw_probe_response *) + header, stats); + break; + + case IEEE80211_STYPE_BEACON: + LIBIPW_DEBUG_MGMT("received BEACON (%d)\n", + WLAN_FC_GET_STYPE(le16_to_cpu + (header->frame_ctl))); + LIBIPW_DEBUG_SCAN("Beacon\n"); + libipw_process_probe_response(ieee, + (struct + libipw_probe_response *) + header, stats); + break; + case IEEE80211_STYPE_AUTH: + + LIBIPW_DEBUG_MGMT("received auth (%d)\n", + WLAN_FC_GET_STYPE(le16_to_cpu + (header->frame_ctl))); + + if (ieee->handle_auth != NULL) + ieee->handle_auth(ieee->dev, + (struct libipw_auth *)header); + break; + + case IEEE80211_STYPE_DISASSOC: + if (ieee->handle_disassoc != NULL) + ieee->handle_disassoc(ieee->dev, + (struct libipw_disassoc *) + header); + break; + + case IEEE80211_STYPE_ACTION: + LIBIPW_DEBUG_MGMT("ACTION\n"); + if (ieee->handle_action) + ieee->handle_action(ieee->dev, + (struct libipw_action *) + header, stats); + break; + + case IEEE80211_STYPE_REASSOC_REQ: + LIBIPW_DEBUG_MGMT("received reassoc (%d)\n", + WLAN_FC_GET_STYPE(le16_to_cpu + (header->frame_ctl))); + + LIBIPW_DEBUG_MGMT("%s: LIBIPW_REASSOC_REQ received\n", + ieee->dev->name); + if (ieee->handle_reassoc_request != NULL) + ieee->handle_reassoc_request(ieee->dev, + (struct libipw_reassoc_request *) + header); + break; + + case IEEE80211_STYPE_ASSOC_REQ: + LIBIPW_DEBUG_MGMT("received assoc (%d)\n", + WLAN_FC_GET_STYPE(le16_to_cpu + (header->frame_ctl))); + + LIBIPW_DEBUG_MGMT("%s: LIBIPW_ASSOC_REQ received\n", + ieee->dev->name); + if (ieee->handle_assoc_request != NULL) + ieee->handle_assoc_request(ieee->dev); + break; + + case IEEE80211_STYPE_DEAUTH: + LIBIPW_DEBUG_MGMT("DEAUTH\n"); + if (ieee->handle_deauth != NULL) + ieee->handle_deauth(ieee->dev, + (struct libipw_deauth *) + header); + break; + default: + LIBIPW_DEBUG_MGMT("received UNKNOWN (%d)\n", + WLAN_FC_GET_STYPE(le16_to_cpu + (header->frame_ctl))); + LIBIPW_DEBUG_MGMT("%s: Unknown management packet: %d\n", + ieee->dev->name, + WLAN_FC_GET_STYPE(le16_to_cpu + (header->frame_ctl))); + break; + } +} + +EXPORT_SYMBOL_GPL(libipw_rx_any); +EXPORT_SYMBOL(libipw_rx_mgt); +EXPORT_SYMBOL(libipw_rx); diff --git a/drivers/net/wireless/ipw2x00/libipw_tx.c b/drivers/net/wireless/ipw2x00/libipw_tx.c new file mode 100644 index 0000000..e8c0398 --- /dev/null +++ b/drivers/net/wireless/ipw2x00/libipw_tx.c @@ -0,0 +1,536 @@ +/****************************************************************************** + + Copyright(c) 2003 - 2005 Intel Corporation. All rights reserved. + + This program is free software; you can redistribute it and/or modify it + under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Contact Information: + Intel Linux Wireless + Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + +******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libipw.h" + +/* + +802.11 Data Frame + + ,-------------------------------------------------------------------. +Bytes | 2 | 2 | 6 | 6 | 6 | 2 | 0..2312 | 4 | + |------|------|---------|---------|---------|------|---------|------| +Desc. | ctrl | dura | DA/RA | TA | SA | Sequ | Frame | fcs | + | | tion | (BSSID) | | | ence | data | | + `--------------------------------------------------| |------' +Total: 28 non-data bytes `----.----' + | + .- 'Frame data' expands, if WEP enabled, to <----------' + | + V + ,-----------------------. +Bytes | 4 | 0-2296 | 4 | + |-----|-----------|-----| +Desc. | IV | Encrypted | ICV | + | | Packet | | + `-----| |-----' + `-----.-----' + | + .- 'Encrypted Packet' expands to + | + V + ,---------------------------------------------------. +Bytes | 1 | 1 | 1 | 3 | 2 | 0-2304 | + |------|------|---------|----------|------|---------| +Desc. | SNAP | SNAP | Control |Eth Tunnel| Type | IP | + | DSAP | SSAP | | | | Packet | + | 0xAA | 0xAA |0x03 (UI)|0x00-00-F8| | | + `---------------------------------------------------- +Total: 8 non-data bytes + +802.3 Ethernet Data Frame + + ,-----------------------------------------. +Bytes | 6 | 6 | 2 | Variable | 4 | + |-------|-------|------|-----------|------| +Desc. | Dest. | Source| Type | IP Packet | fcs | + | MAC | MAC | | | | + `-----------------------------------------' +Total: 18 non-data bytes + +In the event that fragmentation is required, the incoming payload is split into +N parts of size ieee->fts. The first fragment contains the SNAP header and the +remaining packets are just data. + +If encryption is enabled, each fragment payload size is reduced by enough space +to add the prefix and postfix (IV and ICV totalling 8 bytes in the case of WEP) +So if you have 1500 bytes of payload with ieee->fts set to 500 without +encryption it will take 3 frames. With WEP it will take 4 frames as the +payload of each frame is reduced to 492 bytes. + +* SKB visualization +* +* ,- skb->data +* | +* | ETHERNET HEADER ,-<-- PAYLOAD +* | | 14 bytes from skb->data +* | 2 bytes for Type --> ,T. | (sizeof ethhdr) +* | | | | +* |,-Dest.--. ,--Src.---. | | | +* | 6 bytes| | 6 bytes | | | | +* v | | | | | | +* 0 | v 1 | v | v 2 +* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +* ^ | ^ | ^ | +* | | | | | | +* | | | | `T' <---- 2 bytes for Type +* | | | | +* | | '---SNAP--' <-------- 6 bytes for SNAP +* | | +* `-IV--' <-------------------- 4 bytes for IV (WEP) +* +* SNAP HEADER +* +*/ + +static u8 P802_1H_OUI[P80211_OUI_LEN] = { 0x00, 0x00, 0xf8 }; +static u8 RFC1042_OUI[P80211_OUI_LEN] = { 0x00, 0x00, 0x00 }; + +static int libipw_copy_snap(u8 * data, __be16 h_proto) +{ + struct libipw_snap_hdr *snap; + u8 *oui; + + snap = (struct libipw_snap_hdr *)data; + snap->dsap = 0xaa; + snap->ssap = 0xaa; + snap->ctrl = 0x03; + + if (h_proto == htons(ETH_P_AARP) || h_proto == htons(ETH_P_IPX)) + oui = P802_1H_OUI; + else + oui = RFC1042_OUI; + snap->oui[0] = oui[0]; + snap->oui[1] = oui[1]; + snap->oui[2] = oui[2]; + + memcpy(data + SNAP_SIZE, &h_proto, sizeof(u16)); + + return SNAP_SIZE + sizeof(u16); +} + +static int libipw_encrypt_fragment(struct libipw_device *ieee, + struct sk_buff *frag, int hdr_len) +{ + struct lib80211_crypt_data *crypt = + ieee->crypt_info.crypt[ieee->crypt_info.tx_keyidx]; + int res; + + if (crypt == NULL) + return -1; + + /* To encrypt, frame format is: + * IV (4 bytes), clear payload (including SNAP), ICV (4 bytes) */ + atomic_inc(&crypt->refcnt); + res = 0; + if (crypt->ops && crypt->ops->encrypt_mpdu) + res = crypt->ops->encrypt_mpdu(frag, hdr_len, crypt->priv); + + atomic_dec(&crypt->refcnt); + if (res < 0) { + printk(KERN_INFO "%s: Encryption failed: len=%d.\n", + ieee->dev->name, frag->len); + ieee->ieee_stats.tx_discards++; + return -1; + } + + return 0; +} + +void libipw_txb_free(struct libipw_txb *txb) +{ + int i; + if (unlikely(!txb)) + return; + for (i = 0; i < txb->nr_frags; i++) + if (txb->fragments[i]) + dev_kfree_skb_any(txb->fragments[i]); + kfree(txb); +} + +static struct libipw_txb *libipw_alloc_txb(int nr_frags, int txb_size, + int headroom, gfp_t gfp_mask) +{ + struct libipw_txb *txb; + int i; + txb = kmalloc(sizeof(struct libipw_txb) + (sizeof(u8 *) * nr_frags), + gfp_mask); + if (!txb) + return NULL; + + memset(txb, 0, sizeof(struct libipw_txb)); + txb->nr_frags = nr_frags; + txb->frag_size = txb_size; + + for (i = 0; i < nr_frags; i++) { + txb->fragments[i] = __dev_alloc_skb(txb_size + headroom, + gfp_mask); + if (unlikely(!txb->fragments[i])) { + i--; + break; + } + skb_reserve(txb->fragments[i], headroom); + } + if (unlikely(i != nr_frags)) { + while (i >= 0) + dev_kfree_skb_any(txb->fragments[i--]); + kfree(txb); + return NULL; + } + return txb; +} + +static int libipw_classify(struct sk_buff *skb) +{ + struct ethhdr *eth; + struct iphdr *ip; + + eth = (struct ethhdr *)skb->data; + if (eth->h_proto != htons(ETH_P_IP)) + return 0; + + ip = ip_hdr(skb); + switch (ip->tos & 0xfc) { + case 0x20: + return 2; + case 0x40: + return 1; + case 0x60: + return 3; + case 0x80: + return 4; + case 0xa0: + return 5; + case 0xc0: + return 6; + case 0xe0: + return 7; + default: + return 0; + } +} + +/* Incoming skb is converted to a txb which consists of + * a block of 802.11 fragment packets (stored as skbs) */ +netdev_tx_t libipw_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct libipw_device *ieee = netdev_priv(dev); + struct libipw_txb *txb = NULL; + struct libipw_hdr_3addrqos *frag_hdr; + int i, bytes_per_frag, nr_frags, bytes_last_frag, frag_size, + rts_required; + unsigned long flags; + int encrypt, host_encrypt, host_encrypt_msdu; + __be16 ether_type; + int bytes, fc, hdr_len; + struct sk_buff *skb_frag; + struct libipw_hdr_3addrqos header = {/* Ensure zero initialized */ + .duration_id = 0, + .seq_ctl = 0, + .qos_ctl = 0 + }; + u8 dest[ETH_ALEN], src[ETH_ALEN]; + struct lib80211_crypt_data *crypt; + int priority = skb->priority; + int snapped = 0; + + if (ieee->is_queue_full && (*ieee->is_queue_full) (dev, priority)) + return NETDEV_TX_BUSY; + + spin_lock_irqsave(&ieee->lock, flags); + + /* If there is no driver handler to take the TXB, dont' bother + * creating it... */ + if (!ieee->hard_start_xmit) { + printk(KERN_WARNING "%s: No xmit handler.\n", ieee->dev->name); + goto success; + } + + if (unlikely(skb->len < SNAP_SIZE + sizeof(u16))) { + printk(KERN_WARNING "%s: skb too small (%d).\n", + ieee->dev->name, skb->len); + goto success; + } + + ether_type = ((struct ethhdr *)skb->data)->h_proto; + + crypt = ieee->crypt_info.crypt[ieee->crypt_info.tx_keyidx]; + + encrypt = !(ether_type == htons(ETH_P_PAE) && ieee->ieee802_1x) && + ieee->sec.encrypt; + + host_encrypt = ieee->host_encrypt && encrypt && crypt; + host_encrypt_msdu = ieee->host_encrypt_msdu && encrypt && crypt; + + if (!encrypt && ieee->ieee802_1x && + ieee->drop_unencrypted && ether_type != htons(ETH_P_PAE)) { + dev->stats.tx_dropped++; + goto success; + } + + /* Save source and destination addresses */ + skb_copy_from_linear_data(skb, dest, ETH_ALEN); + skb_copy_from_linear_data_offset(skb, ETH_ALEN, src, ETH_ALEN); + + if (host_encrypt) + fc = IEEE80211_FTYPE_DATA | IEEE80211_STYPE_DATA | + IEEE80211_FCTL_PROTECTED; + else + fc = IEEE80211_FTYPE_DATA | IEEE80211_STYPE_DATA; + + if (ieee->iw_mode == IW_MODE_INFRA) { + fc |= IEEE80211_FCTL_TODS; + /* To DS: Addr1 = BSSID, Addr2 = SA, Addr3 = DA */ + memcpy(header.addr1, ieee->bssid, ETH_ALEN); + memcpy(header.addr2, src, ETH_ALEN); + memcpy(header.addr3, dest, ETH_ALEN); + } else if (ieee->iw_mode == IW_MODE_ADHOC) { + /* not From/To DS: Addr1 = DA, Addr2 = SA, Addr3 = BSSID */ + memcpy(header.addr1, dest, ETH_ALEN); + memcpy(header.addr2, src, ETH_ALEN); + memcpy(header.addr3, ieee->bssid, ETH_ALEN); + } + hdr_len = LIBIPW_3ADDR_LEN; + + if (ieee->is_qos_active && ieee->is_qos_active(dev, skb)) { + fc |= IEEE80211_STYPE_QOS_DATA; + hdr_len += 2; + + skb->priority = libipw_classify(skb); + header.qos_ctl |= cpu_to_le16(skb->priority & LIBIPW_QCTL_TID); + } + header.frame_ctl = cpu_to_le16(fc); + + /* Advance the SKB to the start of the payload */ + skb_pull(skb, sizeof(struct ethhdr)); + + /* Determine total amount of storage required for TXB packets */ + bytes = skb->len + SNAP_SIZE + sizeof(u16); + + /* Encrypt msdu first on the whole data packet. */ + if ((host_encrypt || host_encrypt_msdu) && + crypt && crypt->ops && crypt->ops->encrypt_msdu) { + int res = 0; + int len = bytes + hdr_len + crypt->ops->extra_msdu_prefix_len + + crypt->ops->extra_msdu_postfix_len; + struct sk_buff *skb_new = dev_alloc_skb(len); + + if (unlikely(!skb_new)) + goto failed; + + skb_reserve(skb_new, crypt->ops->extra_msdu_prefix_len); + memcpy(skb_put(skb_new, hdr_len), &header, hdr_len); + snapped = 1; + libipw_copy_snap(skb_put(skb_new, SNAP_SIZE + sizeof(u16)), + ether_type); + skb_copy_from_linear_data(skb, skb_put(skb_new, skb->len), skb->len); + res = crypt->ops->encrypt_msdu(skb_new, hdr_len, crypt->priv); + if (res < 0) { + LIBIPW_ERROR("msdu encryption failed\n"); + dev_kfree_skb_any(skb_new); + goto failed; + } + dev_kfree_skb_any(skb); + skb = skb_new; + bytes += crypt->ops->extra_msdu_prefix_len + + crypt->ops->extra_msdu_postfix_len; + skb_pull(skb, hdr_len); + } + + if (host_encrypt || ieee->host_open_frag) { + /* Determine fragmentation size based on destination (multicast + * and broadcast are not fragmented) */ + if (is_multicast_ether_addr(dest) || + is_broadcast_ether_addr(dest)) + frag_size = MAX_FRAG_THRESHOLD; + else + frag_size = ieee->fts; + + /* Determine amount of payload per fragment. Regardless of if + * this stack is providing the full 802.11 header, one will + * eventually be affixed to this fragment -- so we must account + * for it when determining the amount of payload space. */ + bytes_per_frag = frag_size - hdr_len; + if (ieee->config & + (CFG_LIBIPW_COMPUTE_FCS | CFG_LIBIPW_RESERVE_FCS)) + bytes_per_frag -= LIBIPW_FCS_LEN; + + /* Each fragment may need to have room for encryption + * pre/postfix */ + if (host_encrypt) + bytes_per_frag -= crypt->ops->extra_mpdu_prefix_len + + crypt->ops->extra_mpdu_postfix_len; + + /* Number of fragments is the total + * bytes_per_frag / payload_per_fragment */ + nr_frags = bytes / bytes_per_frag; + bytes_last_frag = bytes % bytes_per_frag; + if (bytes_last_frag) + nr_frags++; + else + bytes_last_frag = bytes_per_frag; + } else { + nr_frags = 1; + bytes_per_frag = bytes_last_frag = bytes; + frag_size = bytes + hdr_len; + } + + rts_required = (frag_size > ieee->rts + && ieee->config & CFG_LIBIPW_RTS); + if (rts_required) + nr_frags++; + + /* When we allocate the TXB we allocate enough space for the reserve + * and full fragment bytes (bytes_per_frag doesn't include prefix, + * postfix, header, FCS, etc.) */ + txb = libipw_alloc_txb(nr_frags, frag_size, + ieee->tx_headroom, GFP_ATOMIC); + if (unlikely(!txb)) { + printk(KERN_WARNING "%s: Could not allocate TXB\n", + ieee->dev->name); + goto failed; + } + txb->encrypted = encrypt; + if (host_encrypt) + txb->payload_size = frag_size * (nr_frags - 1) + + bytes_last_frag; + else + txb->payload_size = bytes; + + if (rts_required) { + skb_frag = txb->fragments[0]; + frag_hdr = + (struct libipw_hdr_3addrqos *)skb_put(skb_frag, hdr_len); + + /* + * Set header frame_ctl to the RTS. + */ + header.frame_ctl = + cpu_to_le16(IEEE80211_FTYPE_CTL | IEEE80211_STYPE_RTS); + memcpy(frag_hdr, &header, hdr_len); + + /* + * Restore header frame_ctl to the original data setting. + */ + header.frame_ctl = cpu_to_le16(fc); + + if (ieee->config & + (CFG_LIBIPW_COMPUTE_FCS | CFG_LIBIPW_RESERVE_FCS)) + skb_put(skb_frag, 4); + + txb->rts_included = 1; + i = 1; + } else + i = 0; + + for (; i < nr_frags; i++) { + skb_frag = txb->fragments[i]; + + if (host_encrypt) + skb_reserve(skb_frag, + crypt->ops->extra_mpdu_prefix_len); + + frag_hdr = + (struct libipw_hdr_3addrqos *)skb_put(skb_frag, hdr_len); + memcpy(frag_hdr, &header, hdr_len); + + /* If this is not the last fragment, then add the MOREFRAGS + * bit to the frame control */ + if (i != nr_frags - 1) { + frag_hdr->frame_ctl = + cpu_to_le16(fc | IEEE80211_FCTL_MOREFRAGS); + bytes = bytes_per_frag; + } else { + /* The last fragment takes the remaining length */ + bytes = bytes_last_frag; + } + + if (i == 0 && !snapped) { + libipw_copy_snap(skb_put + (skb_frag, SNAP_SIZE + sizeof(u16)), + ether_type); + bytes -= SNAP_SIZE + sizeof(u16); + } + + skb_copy_from_linear_data(skb, skb_put(skb_frag, bytes), bytes); + + /* Advance the SKB... */ + skb_pull(skb, bytes); + + /* Encryption routine will move the header forward in order + * to insert the IV between the header and the payload */ + if (host_encrypt) + libipw_encrypt_fragment(ieee, skb_frag, hdr_len); + + if (ieee->config & + (CFG_LIBIPW_COMPUTE_FCS | CFG_LIBIPW_RESERVE_FCS)) + skb_put(skb_frag, 4); + } + + success: + spin_unlock_irqrestore(&ieee->lock, flags); + + dev_kfree_skb_any(skb); + + if (txb) { + netdev_tx_t ret = (*ieee->hard_start_xmit)(txb, dev, priority); + if (ret == NETDEV_TX_OK) { + dev->stats.tx_packets++; + dev->stats.tx_bytes += txb->payload_size; + return NETDEV_TX_OK; + } + + libipw_txb_free(txb); + } + + return NETDEV_TX_OK; + + failed: + spin_unlock_irqrestore(&ieee->lock, flags); + netif_stop_queue(dev); + dev->stats.tx_errors++; + return NETDEV_TX_BUSY; +} +EXPORT_SYMBOL(libipw_xmit); + +EXPORT_SYMBOL(libipw_txb_free); diff --git a/drivers/net/wireless/ipw2x00/libipw_wx.c b/drivers/net/wireless/ipw2x00/libipw_wx.c new file mode 100644 index 0000000..54aba47 --- /dev/null +++ b/drivers/net/wireless/ipw2x00/libipw_wx.c @@ -0,0 +1,744 @@ +/****************************************************************************** + + Copyright(c) 2004-2005 Intel Corporation. All rights reserved. + + Portions of this file are based on the WEP enablement code provided by the + Host AP project hostap-drivers v0.1.3 + Copyright (c) 2001-2002, SSH Communications Security Corp and Jouni Malinen + + Copyright (c) 2002-2003, Jouni Malinen + + This program is free software; you can redistribute it and/or modify it + under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Contact Information: + Intel Linux Wireless + Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + +******************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include + +#include "libipw.h" + +static const char *libipw_modes[] = { + "?", "a", "b", "ab", "g", "ag", "bg", "abg" +}; + +static inline unsigned int elapsed_jiffies_msecs(unsigned long start) +{ + unsigned long end = jiffies; + + if (end >= start) + return jiffies_to_msecs(end - start); + + return jiffies_to_msecs(end + (MAX_JIFFY_OFFSET - start) + 1); +} + +#define MAX_CUSTOM_LEN 64 +static char *libipw_translate_scan(struct libipw_device *ieee, + char *start, char *stop, + struct libipw_network *network, + struct iw_request_info *info) +{ + char custom[MAX_CUSTOM_LEN]; + char *p; + struct iw_event iwe; + int i, j; + char *current_val; /* For rates */ + u8 rate; + + /* First entry *MUST* be the AP MAC address */ + iwe.cmd = SIOCGIWAP; + iwe.u.ap_addr.sa_family = ARPHRD_ETHER; + memcpy(iwe.u.ap_addr.sa_data, network->bssid, ETH_ALEN); + start = iwe_stream_add_event(info, start, stop, &iwe, IW_EV_ADDR_LEN); + + /* Remaining entries will be displayed in the order we provide them */ + + /* Add the ESSID */ + iwe.cmd = SIOCGIWESSID; + iwe.u.data.flags = 1; + iwe.u.data.length = min(network->ssid_len, (u8) 32); + start = iwe_stream_add_point(info, start, stop, + &iwe, network->ssid); + + /* Add the protocol name */ + iwe.cmd = SIOCGIWNAME; + snprintf(iwe.u.name, IFNAMSIZ, "IEEE 802.11%s", + libipw_modes[network->mode]); + start = iwe_stream_add_event(info, start, stop, &iwe, IW_EV_CHAR_LEN); + + /* Add mode */ + iwe.cmd = SIOCGIWMODE; + if (network->capability & (WLAN_CAPABILITY_ESS | WLAN_CAPABILITY_IBSS)) { + if (network->capability & WLAN_CAPABILITY_ESS) + iwe.u.mode = IW_MODE_MASTER; + else + iwe.u.mode = IW_MODE_ADHOC; + + start = iwe_stream_add_event(info, start, stop, + &iwe, IW_EV_UINT_LEN); + } + + /* Add channel and frequency */ + /* Note : userspace automatically computes channel using iwrange */ + iwe.cmd = SIOCGIWFREQ; + iwe.u.freq.m = libipw_channel_to_freq(ieee, network->channel); + iwe.u.freq.e = 6; + iwe.u.freq.i = 0; + start = iwe_stream_add_event(info, start, stop, &iwe, IW_EV_FREQ_LEN); + + /* Add encryption capability */ + iwe.cmd = SIOCGIWENCODE; + if (network->capability & WLAN_CAPABILITY_PRIVACY) + iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY; + else + iwe.u.data.flags = IW_ENCODE_DISABLED; + iwe.u.data.length = 0; + start = iwe_stream_add_point(info, start, stop, + &iwe, network->ssid); + + /* Add basic and extended rates */ + /* Rate : stuffing multiple values in a single event require a bit + * more of magic - Jean II */ + current_val = start + iwe_stream_lcp_len(info); + iwe.cmd = SIOCGIWRATE; + /* Those two flags are ignored... */ + iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0; + + for (i = 0, j = 0; i < network->rates_len;) { + if (j < network->rates_ex_len && + ((network->rates_ex[j] & 0x7F) < + (network->rates[i] & 0x7F))) + rate = network->rates_ex[j++] & 0x7F; + else + rate = network->rates[i++] & 0x7F; + /* Bit rate given in 500 kb/s units (+ 0x80) */ + iwe.u.bitrate.value = ((rate & 0x7f) * 500000); + /* Add new value to event */ + current_val = iwe_stream_add_value(info, start, current_val, + stop, &iwe, IW_EV_PARAM_LEN); + } + for (; j < network->rates_ex_len; j++) { + rate = network->rates_ex[j] & 0x7F; + /* Bit rate given in 500 kb/s units (+ 0x80) */ + iwe.u.bitrate.value = ((rate & 0x7f) * 500000); + /* Add new value to event */ + current_val = iwe_stream_add_value(info, start, current_val, + stop, &iwe, IW_EV_PARAM_LEN); + } + /* Check if we added any rate */ + if ((current_val - start) > iwe_stream_lcp_len(info)) + start = current_val; + + /* Add quality statistics */ + iwe.cmd = IWEVQUAL; + iwe.u.qual.updated = IW_QUAL_QUAL_UPDATED | IW_QUAL_LEVEL_UPDATED | + IW_QUAL_NOISE_UPDATED; + + if (!(network->stats.mask & LIBIPW_STATMASK_RSSI)) { + iwe.u.qual.updated |= IW_QUAL_QUAL_INVALID | + IW_QUAL_LEVEL_INVALID; + iwe.u.qual.qual = 0; + } else { + if (ieee->perfect_rssi == ieee->worst_rssi) + iwe.u.qual.qual = 100; + else + iwe.u.qual.qual = + (100 * + (ieee->perfect_rssi - ieee->worst_rssi) * + (ieee->perfect_rssi - ieee->worst_rssi) - + (ieee->perfect_rssi - network->stats.rssi) * + (15 * (ieee->perfect_rssi - ieee->worst_rssi) + + 62 * (ieee->perfect_rssi - + network->stats.rssi))) / + ((ieee->perfect_rssi - + ieee->worst_rssi) * (ieee->perfect_rssi - + ieee->worst_rssi)); + if (iwe.u.qual.qual > 100) + iwe.u.qual.qual = 100; + else if (iwe.u.qual.qual < 1) + iwe.u.qual.qual = 0; + } + + if (!(network->stats.mask & LIBIPW_STATMASK_NOISE)) { + iwe.u.qual.updated |= IW_QUAL_NOISE_INVALID; + iwe.u.qual.noise = 0; + } else { + iwe.u.qual.noise = network->stats.noise; + } + + if (!(network->stats.mask & LIBIPW_STATMASK_SIGNAL)) { + iwe.u.qual.updated |= IW_QUAL_LEVEL_INVALID; + iwe.u.qual.level = 0; + } else { + iwe.u.qual.level = network->stats.signal; + } + + start = iwe_stream_add_event(info, start, stop, &iwe, IW_EV_QUAL_LEN); + + iwe.cmd = IWEVCUSTOM; + p = custom; + + iwe.u.data.length = p - custom; + if (iwe.u.data.length) + start = iwe_stream_add_point(info, start, stop, &iwe, custom); + + memset(&iwe, 0, sizeof(iwe)); + if (network->wpa_ie_len) { + char buf[MAX_WPA_IE_LEN]; + memcpy(buf, network->wpa_ie, network->wpa_ie_len); + iwe.cmd = IWEVGENIE; + iwe.u.data.length = network->wpa_ie_len; + start = iwe_stream_add_point(info, start, stop, &iwe, buf); + } + + memset(&iwe, 0, sizeof(iwe)); + if (network->rsn_ie_len) { + char buf[MAX_WPA_IE_LEN]; + memcpy(buf, network->rsn_ie, network->rsn_ie_len); + iwe.cmd = IWEVGENIE; + iwe.u.data.length = network->rsn_ie_len; + start = iwe_stream_add_point(info, start, stop, &iwe, buf); + } + + /* Add EXTRA: Age to display seconds since last beacon/probe response + * for given network. */ + iwe.cmd = IWEVCUSTOM; + p = custom; + p += snprintf(p, MAX_CUSTOM_LEN - (p - custom), + " Last beacon: %ums ago", + elapsed_jiffies_msecs(network->last_scanned)); + iwe.u.data.length = p - custom; + if (iwe.u.data.length) + start = iwe_stream_add_point(info, start, stop, &iwe, custom); + + /* Add spectrum management information */ + iwe.cmd = -1; + p = custom; + p += snprintf(p, MAX_CUSTOM_LEN - (p - custom), " Channel flags: "); + + if (libipw_get_channel_flags(ieee, network->channel) & + LIBIPW_CH_INVALID) { + iwe.cmd = IWEVCUSTOM; + p += snprintf(p, MAX_CUSTOM_LEN - (p - custom), "INVALID "); + } + + if (libipw_get_channel_flags(ieee, network->channel) & + LIBIPW_CH_RADAR_DETECT) { + iwe.cmd = IWEVCUSTOM; + p += snprintf(p, MAX_CUSTOM_LEN - (p - custom), "DFS "); + } + + if (iwe.cmd == IWEVCUSTOM) { + iwe.u.data.length = p - custom; + start = iwe_stream_add_point(info, start, stop, &iwe, custom); + } + + return start; +} + +#define SCAN_ITEM_SIZE 128 + +int libipw_wx_get_scan(struct libipw_device *ieee, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct libipw_network *network; + unsigned long flags; + int err = 0; + + char *ev = extra; + char *stop = ev + wrqu->data.length; + int i = 0; + DECLARE_SSID_BUF(ssid); + + LIBIPW_DEBUG_WX("Getting scan\n"); + + spin_lock_irqsave(&ieee->lock, flags); + + list_for_each_entry(network, &ieee->network_list, list) { + i++; + if (stop - ev < SCAN_ITEM_SIZE) { + err = -E2BIG; + break; + } + + if (ieee->scan_age == 0 || + time_after(network->last_scanned + ieee->scan_age, jiffies)) + ev = libipw_translate_scan(ieee, ev, stop, network, + info); + else { + LIBIPW_DEBUG_SCAN("Not showing network '%s (" + "%pM)' due to age (%ums).\n", + print_ssid(ssid, network->ssid, + network->ssid_len), + network->bssid, + elapsed_jiffies_msecs( + network->last_scanned)); + } + } + + spin_unlock_irqrestore(&ieee->lock, flags); + + wrqu->data.length = ev - extra; + wrqu->data.flags = 0; + + LIBIPW_DEBUG_WX("exit: %d networks returned.\n", i); + + return err; +} + +int libipw_wx_set_encode(struct libipw_device *ieee, + struct iw_request_info *info, + union iwreq_data *wrqu, char *keybuf) +{ + struct iw_point *erq = &(wrqu->encoding); + struct net_device *dev = ieee->dev; + struct libipw_security sec = { + .flags = 0 + }; + int i, key, key_provided, len; + struct lib80211_crypt_data **crypt; + int host_crypto = ieee->host_encrypt || ieee->host_decrypt; + DECLARE_SSID_BUF(ssid); + + LIBIPW_DEBUG_WX("SET_ENCODE\n"); + + key = erq->flags & IW_ENCODE_INDEX; + if (key) { + if (key > WEP_KEYS) + return -EINVAL; + key--; + key_provided = 1; + } else { + key_provided = 0; + key = ieee->crypt_info.tx_keyidx; + } + + LIBIPW_DEBUG_WX("Key: %d [%s]\n", key, key_provided ? + "provided" : "default"); + + crypt = &ieee->crypt_info.crypt[key]; + + if (erq->flags & IW_ENCODE_DISABLED) { + if (key_provided && *crypt) { + LIBIPW_DEBUG_WX("Disabling encryption on key %d.\n", + key); + lib80211_crypt_delayed_deinit(&ieee->crypt_info, crypt); + } else + LIBIPW_DEBUG_WX("Disabling encryption.\n"); + + /* Check all the keys to see if any are still configured, + * and if no key index was provided, de-init them all */ + for (i = 0; i < WEP_KEYS; i++) { + if (ieee->crypt_info.crypt[i] != NULL) { + if (key_provided) + break; + lib80211_crypt_delayed_deinit(&ieee->crypt_info, + &ieee->crypt_info.crypt[i]); + } + } + + if (i == WEP_KEYS) { + sec.enabled = 0; + sec.encrypt = 0; + sec.level = SEC_LEVEL_0; + sec.flags |= SEC_ENABLED | SEC_LEVEL | SEC_ENCRYPT; + } + + goto done; + } + + sec.enabled = 1; + sec.encrypt = 1; + sec.flags |= SEC_ENABLED | SEC_ENCRYPT; + + if (*crypt != NULL && (*crypt)->ops != NULL && + strcmp((*crypt)->ops->name, "WEP") != 0) { + /* changing to use WEP; deinit previously used algorithm + * on this key */ + lib80211_crypt_delayed_deinit(&ieee->crypt_info, crypt); + } + + if (*crypt == NULL && host_crypto) { + struct lib80211_crypt_data *new_crypt; + + /* take WEP into use */ + new_crypt = kzalloc(sizeof(struct lib80211_crypt_data), + GFP_KERNEL); + if (new_crypt == NULL) + return -ENOMEM; + new_crypt->ops = lib80211_get_crypto_ops("WEP"); + if (!new_crypt->ops) { + request_module("lib80211_crypt_wep"); + new_crypt->ops = lib80211_get_crypto_ops("WEP"); + } + + if (new_crypt->ops && try_module_get(new_crypt->ops->owner)) + new_crypt->priv = new_crypt->ops->init(key); + + if (!new_crypt->ops || !new_crypt->priv) { + kfree(new_crypt); + new_crypt = NULL; + + printk(KERN_WARNING "%s: could not initialize WEP: " + "load module lib80211_crypt_wep\n", dev->name); + return -EOPNOTSUPP; + } + *crypt = new_crypt; + } + + /* If a new key was provided, set it up */ + if (erq->length > 0) { + len = erq->length <= 5 ? 5 : 13; + memcpy(sec.keys[key], keybuf, erq->length); + if (len > erq->length) + memset(sec.keys[key] + erq->length, 0, + len - erq->length); + LIBIPW_DEBUG_WX("Setting key %d to '%s' (%d:%d bytes)\n", + key, print_ssid(ssid, sec.keys[key], len), + erq->length, len); + sec.key_sizes[key] = len; + if (*crypt) + (*crypt)->ops->set_key(sec.keys[key], len, NULL, + (*crypt)->priv); + sec.flags |= (1 << key); + /* This ensures a key will be activated if no key is + * explicitly set */ + if (key == sec.active_key) + sec.flags |= SEC_ACTIVE_KEY; + + } else { + if (host_crypto) { + len = (*crypt)->ops->get_key(sec.keys[key], WEP_KEY_LEN, + NULL, (*crypt)->priv); + if (len == 0) { + /* Set a default key of all 0 */ + LIBIPW_DEBUG_WX("Setting key %d to all " + "zero.\n", key); + memset(sec.keys[key], 0, 13); + (*crypt)->ops->set_key(sec.keys[key], 13, NULL, + (*crypt)->priv); + sec.key_sizes[key] = 13; + sec.flags |= (1 << key); + } + } + /* No key data - just set the default TX key index */ + if (key_provided) { + LIBIPW_DEBUG_WX("Setting key %d to default Tx " + "key.\n", key); + ieee->crypt_info.tx_keyidx = key; + sec.active_key = key; + sec.flags |= SEC_ACTIVE_KEY; + } + } + if (erq->flags & (IW_ENCODE_OPEN | IW_ENCODE_RESTRICTED)) { + ieee->open_wep = !(erq->flags & IW_ENCODE_RESTRICTED); + sec.auth_mode = ieee->open_wep ? WLAN_AUTH_OPEN : + WLAN_AUTH_SHARED_KEY; + sec.flags |= SEC_AUTH_MODE; + LIBIPW_DEBUG_WX("Auth: %s\n", + sec.auth_mode == WLAN_AUTH_OPEN ? + "OPEN" : "SHARED KEY"); + } + + /* For now we just support WEP, so only set that security level... + * TODO: When WPA is added this is one place that needs to change */ + sec.flags |= SEC_LEVEL; + sec.level = SEC_LEVEL_1; /* 40 and 104 bit WEP */ + sec.encode_alg[key] = SEC_ALG_WEP; + + done: + if (ieee->set_security) + ieee->set_security(dev, &sec); + + return 0; +} + +int libipw_wx_get_encode(struct libipw_device *ieee, + struct iw_request_info *info, + union iwreq_data *wrqu, char *keybuf) +{ + struct iw_point *erq = &(wrqu->encoding); + int len, key; + struct lib80211_crypt_data *crypt; + struct libipw_security *sec = &ieee->sec; + + LIBIPW_DEBUG_WX("GET_ENCODE\n"); + + key = erq->flags & IW_ENCODE_INDEX; + if (key) { + if (key > WEP_KEYS) + return -EINVAL; + key--; + } else + key = ieee->crypt_info.tx_keyidx; + + crypt = ieee->crypt_info.crypt[key]; + erq->flags = key + 1; + + if (!sec->enabled) { + erq->length = 0; + erq->flags |= IW_ENCODE_DISABLED; + return 0; + } + + len = sec->key_sizes[key]; + memcpy(keybuf, sec->keys[key], len); + + erq->length = len; + erq->flags |= IW_ENCODE_ENABLED; + + if (ieee->open_wep) + erq->flags |= IW_ENCODE_OPEN; + else + erq->flags |= IW_ENCODE_RESTRICTED; + + return 0; +} + +int libipw_wx_set_encodeext(struct libipw_device *ieee, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct net_device *dev = ieee->dev; + struct iw_point *encoding = &wrqu->encoding; + struct iw_encode_ext *ext = (struct iw_encode_ext *)extra; + int i, idx, ret = 0; + int group_key = 0; + const char *alg, *module; + struct lib80211_crypto_ops *ops; + struct lib80211_crypt_data **crypt; + + struct libipw_security sec = { + .flags = 0, + }; + + idx = encoding->flags & IW_ENCODE_INDEX; + if (idx) { + if (idx < 1 || idx > WEP_KEYS) + return -EINVAL; + idx--; + } else + idx = ieee->crypt_info.tx_keyidx; + + if (ext->ext_flags & IW_ENCODE_EXT_GROUP_KEY) { + crypt = &ieee->crypt_info.crypt[idx]; + group_key = 1; + } else { + /* some Cisco APs use idx>0 for unicast in dynamic WEP */ + if (idx != 0 && ext->alg != IW_ENCODE_ALG_WEP) + return -EINVAL; + if (ieee->iw_mode == IW_MODE_INFRA) + crypt = &ieee->crypt_info.crypt[idx]; + else + return -EINVAL; + } + + sec.flags |= SEC_ENABLED | SEC_ENCRYPT; + if ((encoding->flags & IW_ENCODE_DISABLED) || + ext->alg == IW_ENCODE_ALG_NONE) { + if (*crypt) + lib80211_crypt_delayed_deinit(&ieee->crypt_info, crypt); + + for (i = 0; i < WEP_KEYS; i++) + if (ieee->crypt_info.crypt[i] != NULL) + break; + + if (i == WEP_KEYS) { + sec.enabled = 0; + sec.encrypt = 0; + sec.level = SEC_LEVEL_0; + sec.flags |= SEC_LEVEL; + } + goto done; + } + + sec.enabled = 1; + sec.encrypt = 1; + + if (group_key ? !ieee->host_mc_decrypt : + !(ieee->host_encrypt || ieee->host_decrypt || + ieee->host_encrypt_msdu)) + goto skip_host_crypt; + + switch (ext->alg) { + case IW_ENCODE_ALG_WEP: + alg = "WEP"; + module = "lib80211_crypt_wep"; + break; + case IW_ENCODE_ALG_TKIP: + alg = "TKIP"; + module = "lib80211_crypt_tkip"; + break; + case IW_ENCODE_ALG_CCMP: + alg = "CCMP"; + module = "lib80211_crypt_ccmp"; + break; + default: + LIBIPW_DEBUG_WX("%s: unknown crypto alg %d\n", + dev->name, ext->alg); + ret = -EINVAL; + goto done; + } + + ops = lib80211_get_crypto_ops(alg); + if (ops == NULL) { + request_module(module); + ops = lib80211_get_crypto_ops(alg); + } + if (ops == NULL) { + LIBIPW_DEBUG_WX("%s: unknown crypto alg %d\n", + dev->name, ext->alg); + ret = -EINVAL; + goto done; + } + + if (*crypt == NULL || (*crypt)->ops != ops) { + struct lib80211_crypt_data *new_crypt; + + lib80211_crypt_delayed_deinit(&ieee->crypt_info, crypt); + + new_crypt = kzalloc(sizeof(*new_crypt), GFP_KERNEL); + if (new_crypt == NULL) { + ret = -ENOMEM; + goto done; + } + new_crypt->ops = ops; + if (new_crypt->ops && try_module_get(new_crypt->ops->owner)) + new_crypt->priv = new_crypt->ops->init(idx); + if (new_crypt->priv == NULL) { + kfree(new_crypt); + ret = -EINVAL; + goto done; + } + *crypt = new_crypt; + } + + if (ext->key_len > 0 && (*crypt)->ops->set_key && + (*crypt)->ops->set_key(ext->key, ext->key_len, ext->rx_seq, + (*crypt)->priv) < 0) { + LIBIPW_DEBUG_WX("%s: key setting failed\n", dev->name); + ret = -EINVAL; + goto done; + } + + skip_host_crypt: + if (ext->ext_flags & IW_ENCODE_EXT_SET_TX_KEY) { + ieee->crypt_info.tx_keyidx = idx; + sec.active_key = idx; + sec.flags |= SEC_ACTIVE_KEY; + } + + if (ext->alg != IW_ENCODE_ALG_NONE) { + memcpy(sec.keys[idx], ext->key, ext->key_len); + sec.key_sizes[idx] = ext->key_len; + sec.flags |= (1 << idx); + if (ext->alg == IW_ENCODE_ALG_WEP) { + sec.encode_alg[idx] = SEC_ALG_WEP; + sec.flags |= SEC_LEVEL; + sec.level = SEC_LEVEL_1; + } else if (ext->alg == IW_ENCODE_ALG_TKIP) { + sec.encode_alg[idx] = SEC_ALG_TKIP; + sec.flags |= SEC_LEVEL; + sec.level = SEC_LEVEL_2; + } else if (ext->alg == IW_ENCODE_ALG_CCMP) { + sec.encode_alg[idx] = SEC_ALG_CCMP; + sec.flags |= SEC_LEVEL; + sec.level = SEC_LEVEL_3; + } + /* Don't set sec level for group keys. */ + if (group_key) + sec.flags &= ~SEC_LEVEL; + } + done: + if (ieee->set_security) + ieee->set_security(dev, &sec); + + return ret; +} + +int libipw_wx_get_encodeext(struct libipw_device *ieee, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct iw_point *encoding = &wrqu->encoding; + struct iw_encode_ext *ext = (struct iw_encode_ext *)extra; + struct libipw_security *sec = &ieee->sec; + int idx, max_key_len; + + max_key_len = encoding->length - sizeof(*ext); + if (max_key_len < 0) + return -EINVAL; + + idx = encoding->flags & IW_ENCODE_INDEX; + if (idx) { + if (idx < 1 || idx > WEP_KEYS) + return -EINVAL; + idx--; + } else + idx = ieee->crypt_info.tx_keyidx; + + if (!(ext->ext_flags & IW_ENCODE_EXT_GROUP_KEY) && + ext->alg != IW_ENCODE_ALG_WEP) + if (idx != 0 || ieee->iw_mode != IW_MODE_INFRA) + return -EINVAL; + + encoding->flags = idx + 1; + memset(ext, 0, sizeof(*ext)); + + if (!sec->enabled) { + ext->alg = IW_ENCODE_ALG_NONE; + ext->key_len = 0; + encoding->flags |= IW_ENCODE_DISABLED; + } else { + if (sec->encode_alg[idx] == SEC_ALG_WEP) + ext->alg = IW_ENCODE_ALG_WEP; + else if (sec->encode_alg[idx] == SEC_ALG_TKIP) + ext->alg = IW_ENCODE_ALG_TKIP; + else if (sec->encode_alg[idx] == SEC_ALG_CCMP) + ext->alg = IW_ENCODE_ALG_CCMP; + else + return -EINVAL; + + ext->key_len = sec->key_sizes[idx]; + memcpy(ext->key, sec->keys[idx], ext->key_len); + encoding->flags |= IW_ENCODE_ENABLED; + if (ext->key_len && + (ext->alg == IW_ENCODE_ALG_TKIP || + ext->alg == IW_ENCODE_ALG_CCMP)) + ext->ext_flags |= IW_ENCODE_EXT_TX_SEQ_VALID; + + } + + return 0; +} + +EXPORT_SYMBOL(libipw_wx_set_encodeext); +EXPORT_SYMBOL(libipw_wx_get_encodeext); + +EXPORT_SYMBOL(libipw_wx_get_scan); +EXPORT_SYMBOL(libipw_wx_set_encode); +EXPORT_SYMBOL(libipw_wx_get_encode); diff --git a/drivers/net/wireless/iwlegacy/3945-debug.c b/drivers/net/wireless/iwlegacy/3945-debug.c new file mode 100644 index 0000000..f767dd1 --- /dev/null +++ b/drivers/net/wireless/iwlegacy/3945-debug.c @@ -0,0 +1,511 @@ +/****************************************************************************** + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + *****************************************************************************/ + +#include "common.h" +#include "3945.h" + +static int +il3945_stats_flag(struct il_priv *il, char *buf, int bufsz) +{ + int p = 0; + + p += scnprintf(buf + p, bufsz - p, "Statistics Flag(0x%X):\n", + le32_to_cpu(il->_3945.stats.flag)); + if (le32_to_cpu(il->_3945.stats.flag) & UCODE_STATS_CLEAR_MSK) + p += scnprintf(buf + p, bufsz - p, + "\tStatistics have been cleared\n"); + p += scnprintf(buf + p, bufsz - p, "\tOperational Frequency: %s\n", + (le32_to_cpu(il->_3945.stats.flag) & + UCODE_STATS_FREQUENCY_MSK) ? "2.4 GHz" : "5.2 GHz"); + p += scnprintf(buf + p, bufsz - p, "\tTGj Narrow Band: %s\n", + (le32_to_cpu(il->_3945.stats.flag) & + UCODE_STATS_NARROW_BAND_MSK) ? "enabled" : "disabled"); + return p; +} + +ssize_t +il3945_ucode_rx_stats_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct il_priv *il = file->private_data; + int pos = 0; + char *buf; + int bufsz = + sizeof(struct iwl39_stats_rx_phy) * 40 + + sizeof(struct iwl39_stats_rx_non_phy) * 40 + 400; + ssize_t ret; + struct iwl39_stats_rx_phy *ofdm, *accum_ofdm, *delta_ofdm, *max_ofdm; + struct iwl39_stats_rx_phy *cck, *accum_cck, *delta_cck, *max_cck; + struct iwl39_stats_rx_non_phy *general, *accum_general; + struct iwl39_stats_rx_non_phy *delta_general, *max_general; + + if (!il_is_alive(il)) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) { + IL_ERR("Can not allocate Buffer\n"); + return -ENOMEM; + } + + /* + * The statistic information display here is based on + * the last stats notification from uCode + * might not reflect the current uCode activity + */ + ofdm = &il->_3945.stats.rx.ofdm; + cck = &il->_3945.stats.rx.cck; + general = &il->_3945.stats.rx.general; + accum_ofdm = &il->_3945.accum_stats.rx.ofdm; + accum_cck = &il->_3945.accum_stats.rx.cck; + accum_general = &il->_3945.accum_stats.rx.general; + delta_ofdm = &il->_3945.delta_stats.rx.ofdm; + delta_cck = &il->_3945.delta_stats.rx.cck; + delta_general = &il->_3945.delta_stats.rx.general; + max_ofdm = &il->_3945.max_delta.rx.ofdm; + max_cck = &il->_3945.max_delta.rx.cck; + max_general = &il->_3945.max_delta.rx.general; + + pos += il3945_stats_flag(il, buf, bufsz); + pos += + scnprintf(buf + pos, bufsz - pos, + "%-32s current" + "acumulative delta max\n", + "Statistics_Rx - OFDM:"); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "ina_cnt:", + le32_to_cpu(ofdm->ina_cnt), accum_ofdm->ina_cnt, + delta_ofdm->ina_cnt, max_ofdm->ina_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "fina_cnt:", + le32_to_cpu(ofdm->fina_cnt), accum_ofdm->fina_cnt, + delta_ofdm->fina_cnt, max_ofdm->fina_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "plcp_err:", + le32_to_cpu(ofdm->plcp_err), accum_ofdm->plcp_err, + delta_ofdm->plcp_err, max_ofdm->plcp_err); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "crc32_err:", + le32_to_cpu(ofdm->crc32_err), accum_ofdm->crc32_err, + delta_ofdm->crc32_err, max_ofdm->crc32_err); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "overrun_err:", + le32_to_cpu(ofdm->overrun_err), accum_ofdm->overrun_err, + delta_ofdm->overrun_err, max_ofdm->overrun_err); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "early_overrun_err:", + le32_to_cpu(ofdm->early_overrun_err), + accum_ofdm->early_overrun_err, + delta_ofdm->early_overrun_err, + max_ofdm->early_overrun_err); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "crc32_good:", + le32_to_cpu(ofdm->crc32_good), accum_ofdm->crc32_good, + delta_ofdm->crc32_good, max_ofdm->crc32_good); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "false_alarm_cnt:", + le32_to_cpu(ofdm->false_alarm_cnt), + accum_ofdm->false_alarm_cnt, delta_ofdm->false_alarm_cnt, + max_ofdm->false_alarm_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "fina_sync_err_cnt:", + le32_to_cpu(ofdm->fina_sync_err_cnt), + accum_ofdm->fina_sync_err_cnt, + delta_ofdm->fina_sync_err_cnt, + max_ofdm->fina_sync_err_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "sfd_timeout:", + le32_to_cpu(ofdm->sfd_timeout), accum_ofdm->sfd_timeout, + delta_ofdm->sfd_timeout, max_ofdm->sfd_timeout); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "fina_timeout:", + le32_to_cpu(ofdm->fina_timeout), accum_ofdm->fina_timeout, + delta_ofdm->fina_timeout, max_ofdm->fina_timeout); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "unresponded_rts:", + le32_to_cpu(ofdm->unresponded_rts), + accum_ofdm->unresponded_rts, delta_ofdm->unresponded_rts, + max_ofdm->unresponded_rts); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", + "rxe_frame_lmt_ovrun:", + le32_to_cpu(ofdm->rxe_frame_limit_overrun), + accum_ofdm->rxe_frame_limit_overrun, + delta_ofdm->rxe_frame_limit_overrun, + max_ofdm->rxe_frame_limit_overrun); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "sent_ack_cnt:", + le32_to_cpu(ofdm->sent_ack_cnt), accum_ofdm->sent_ack_cnt, + delta_ofdm->sent_ack_cnt, max_ofdm->sent_ack_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "sent_cts_cnt:", + le32_to_cpu(ofdm->sent_cts_cnt), accum_ofdm->sent_cts_cnt, + delta_ofdm->sent_cts_cnt, max_ofdm->sent_cts_cnt); + + pos += + scnprintf(buf + pos, bufsz - pos, + "%-32s current" + "acumulative delta max\n", + "Statistics_Rx - CCK:"); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "ina_cnt:", + le32_to_cpu(cck->ina_cnt), accum_cck->ina_cnt, + delta_cck->ina_cnt, max_cck->ina_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "fina_cnt:", + le32_to_cpu(cck->fina_cnt), accum_cck->fina_cnt, + delta_cck->fina_cnt, max_cck->fina_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "plcp_err:", + le32_to_cpu(cck->plcp_err), accum_cck->plcp_err, + delta_cck->plcp_err, max_cck->plcp_err); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "crc32_err:", + le32_to_cpu(cck->crc32_err), accum_cck->crc32_err, + delta_cck->crc32_err, max_cck->crc32_err); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "overrun_err:", + le32_to_cpu(cck->overrun_err), accum_cck->overrun_err, + delta_cck->overrun_err, max_cck->overrun_err); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "early_overrun_err:", + le32_to_cpu(cck->early_overrun_err), + accum_cck->early_overrun_err, + delta_cck->early_overrun_err, max_cck->early_overrun_err); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "crc32_good:", + le32_to_cpu(cck->crc32_good), accum_cck->crc32_good, + delta_cck->crc32_good, max_cck->crc32_good); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "false_alarm_cnt:", + le32_to_cpu(cck->false_alarm_cnt), + accum_cck->false_alarm_cnt, delta_cck->false_alarm_cnt, + max_cck->false_alarm_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "fina_sync_err_cnt:", + le32_to_cpu(cck->fina_sync_err_cnt), + accum_cck->fina_sync_err_cnt, + delta_cck->fina_sync_err_cnt, max_cck->fina_sync_err_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "sfd_timeout:", + le32_to_cpu(cck->sfd_timeout), accum_cck->sfd_timeout, + delta_cck->sfd_timeout, max_cck->sfd_timeout); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "fina_timeout:", + le32_to_cpu(cck->fina_timeout), accum_cck->fina_timeout, + delta_cck->fina_timeout, max_cck->fina_timeout); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "unresponded_rts:", + le32_to_cpu(cck->unresponded_rts), + accum_cck->unresponded_rts, delta_cck->unresponded_rts, + max_cck->unresponded_rts); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", + "rxe_frame_lmt_ovrun:", + le32_to_cpu(cck->rxe_frame_limit_overrun), + accum_cck->rxe_frame_limit_overrun, + delta_cck->rxe_frame_limit_overrun, + max_cck->rxe_frame_limit_overrun); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "sent_ack_cnt:", + le32_to_cpu(cck->sent_ack_cnt), accum_cck->sent_ack_cnt, + delta_cck->sent_ack_cnt, max_cck->sent_ack_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "sent_cts_cnt:", + le32_to_cpu(cck->sent_cts_cnt), accum_cck->sent_cts_cnt, + delta_cck->sent_cts_cnt, max_cck->sent_cts_cnt); + + pos += + scnprintf(buf + pos, bufsz - pos, + "%-32s current" + "acumulative delta max\n", + "Statistics_Rx - GENERAL:"); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "bogus_cts:", + le32_to_cpu(general->bogus_cts), accum_general->bogus_cts, + delta_general->bogus_cts, max_general->bogus_cts); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "bogus_ack:", + le32_to_cpu(general->bogus_ack), accum_general->bogus_ack, + delta_general->bogus_ack, max_general->bogus_ack); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "non_bssid_frames:", + le32_to_cpu(general->non_bssid_frames), + accum_general->non_bssid_frames, + delta_general->non_bssid_frames, + max_general->non_bssid_frames); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "filtered_frames:", + le32_to_cpu(general->filtered_frames), + accum_general->filtered_frames, + delta_general->filtered_frames, + max_general->filtered_frames); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", + "non_channel_beacons:", + le32_to_cpu(general->non_channel_beacons), + accum_general->non_channel_beacons, + delta_general->non_channel_beacons, + max_general->non_channel_beacons); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +ssize_t +il3945_ucode_tx_stats_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct il_priv *il = file->private_data; + int pos = 0; + char *buf; + int bufsz = (sizeof(struct iwl39_stats_tx) * 48) + 250; + ssize_t ret; + struct iwl39_stats_tx *tx, *accum_tx, *delta_tx, *max_tx; + + if (!il_is_alive(il)) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) { + IL_ERR("Can not allocate Buffer\n"); + return -ENOMEM; + } + + /* + * The statistic information display here is based on + * the last stats notification from uCode + * might not reflect the current uCode activity + */ + tx = &il->_3945.stats.tx; + accum_tx = &il->_3945.accum_stats.tx; + delta_tx = &il->_3945.delta_stats.tx; + max_tx = &il->_3945.max_delta.tx; + pos += il3945_stats_flag(il, buf, bufsz); + pos += + scnprintf(buf + pos, bufsz - pos, + "%-32s current" + "acumulative delta max\n", + "Statistics_Tx:"); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "preamble:", + le32_to_cpu(tx->preamble_cnt), accum_tx->preamble_cnt, + delta_tx->preamble_cnt, max_tx->preamble_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "rx_detected_cnt:", + le32_to_cpu(tx->rx_detected_cnt), + accum_tx->rx_detected_cnt, delta_tx->rx_detected_cnt, + max_tx->rx_detected_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "bt_prio_defer_cnt:", + le32_to_cpu(tx->bt_prio_defer_cnt), + accum_tx->bt_prio_defer_cnt, delta_tx->bt_prio_defer_cnt, + max_tx->bt_prio_defer_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "bt_prio_kill_cnt:", + le32_to_cpu(tx->bt_prio_kill_cnt), + accum_tx->bt_prio_kill_cnt, delta_tx->bt_prio_kill_cnt, + max_tx->bt_prio_kill_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "few_bytes_cnt:", + le32_to_cpu(tx->few_bytes_cnt), accum_tx->few_bytes_cnt, + delta_tx->few_bytes_cnt, max_tx->few_bytes_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "cts_timeout:", + le32_to_cpu(tx->cts_timeout), accum_tx->cts_timeout, + delta_tx->cts_timeout, max_tx->cts_timeout); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "ack_timeout:", + le32_to_cpu(tx->ack_timeout), accum_tx->ack_timeout, + delta_tx->ack_timeout, max_tx->ack_timeout); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "expected_ack_cnt:", + le32_to_cpu(tx->expected_ack_cnt), + accum_tx->expected_ack_cnt, delta_tx->expected_ack_cnt, + max_tx->expected_ack_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "actual_ack_cnt:", + le32_to_cpu(tx->actual_ack_cnt), accum_tx->actual_ack_cnt, + delta_tx->actual_ack_cnt, max_tx->actual_ack_cnt); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +ssize_t +il3945_ucode_general_stats_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct il_priv *il = file->private_data; + int pos = 0; + char *buf; + int bufsz = sizeof(struct iwl39_stats_general) * 10 + 300; + ssize_t ret; + struct iwl39_stats_general *general, *accum_general; + struct iwl39_stats_general *delta_general, *max_general; + struct stats_dbg *dbg, *accum_dbg, *delta_dbg, *max_dbg; + struct iwl39_stats_div *div, *accum_div, *delta_div, *max_div; + + if (!il_is_alive(il)) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) { + IL_ERR("Can not allocate Buffer\n"); + return -ENOMEM; + } + + /* + * The statistic information display here is based on + * the last stats notification from uCode + * might not reflect the current uCode activity + */ + general = &il->_3945.stats.general; + dbg = &il->_3945.stats.general.dbg; + div = &il->_3945.stats.general.div; + accum_general = &il->_3945.accum_stats.general; + delta_general = &il->_3945.delta_stats.general; + max_general = &il->_3945.max_delta.general; + accum_dbg = &il->_3945.accum_stats.general.dbg; + delta_dbg = &il->_3945.delta_stats.general.dbg; + max_dbg = &il->_3945.max_delta.general.dbg; + accum_div = &il->_3945.accum_stats.general.div; + delta_div = &il->_3945.delta_stats.general.div; + max_div = &il->_3945.max_delta.general.div; + pos += il3945_stats_flag(il, buf, bufsz); + pos += + scnprintf(buf + pos, bufsz - pos, + "%-32s current" + "acumulative delta max\n", + "Statistics_General:"); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "burst_check:", + le32_to_cpu(dbg->burst_check), accum_dbg->burst_check, + delta_dbg->burst_check, max_dbg->burst_check); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "burst_count:", + le32_to_cpu(dbg->burst_count), accum_dbg->burst_count, + delta_dbg->burst_count, max_dbg->burst_count); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "sleep_time:", + le32_to_cpu(general->sleep_time), + accum_general->sleep_time, delta_general->sleep_time, + max_general->sleep_time); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "slots_out:", + le32_to_cpu(general->slots_out), accum_general->slots_out, + delta_general->slots_out, max_general->slots_out); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "slots_idle:", + le32_to_cpu(general->slots_idle), + accum_general->slots_idle, delta_general->slots_idle, + max_general->slots_idle); + pos += + scnprintf(buf + pos, bufsz - pos, "ttl_timestamp:\t\t\t%u\n", + le32_to_cpu(general->ttl_timestamp)); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "tx_on_a:", + le32_to_cpu(div->tx_on_a), accum_div->tx_on_a, + delta_div->tx_on_a, max_div->tx_on_a); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "tx_on_b:", + le32_to_cpu(div->tx_on_b), accum_div->tx_on_b, + delta_div->tx_on_b, max_div->tx_on_b); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "exec_time:", + le32_to_cpu(div->exec_time), accum_div->exec_time, + delta_div->exec_time, max_div->exec_time); + pos += + scnprintf(buf + pos, bufsz - pos, + " %-30s %10u %10u %10u %10u\n", "probe_time:", + le32_to_cpu(div->probe_time), accum_div->probe_time, + delta_div->probe_time, max_div->probe_time); + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +const struct il_debugfs_ops il3945_debugfs_ops = { + .rx_stats_read = il3945_ucode_rx_stats_read, + .tx_stats_read = il3945_ucode_tx_stats_read, + .general_stats_read = il3945_ucode_general_stats_read, +}; diff --git a/drivers/net/wireless/iwlegacy/3945-mac.c b/drivers/net/wireless/iwlegacy/3945-mac.c new file mode 100644 index 0000000..4ebc006 --- /dev/null +++ b/drivers/net/wireless/iwlegacy/3945-mac.c @@ -0,0 +1,3971 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2011 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define DRV_NAME "iwl3945" + +#include "commands.h" +#include "common.h" +#include "3945.h" +#include "iwl-spectrum.h" + +/* + * module name, copyright, version, etc. + */ + +#define DRV_DESCRIPTION \ +"Intel(R) PRO/Wireless 3945ABG/BG Network Connection driver for Linux" + +#ifdef CPTCFG_IWLEGACY_DEBUG +#define VD "d" +#else +#define VD +#endif + +/* + * add "s" to indicate spectrum measurement included. + * we add it here to be consistent with previous releases in which + * this was configurable. + */ +#define DRV_VERSION IWLWIFI_VERSION VD "s" +#define DRV_COPYRIGHT "Copyright(c) 2003-2011 Intel Corporation" +#define DRV_AUTHOR "" + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR(DRV_COPYRIGHT " " DRV_AUTHOR); +MODULE_LICENSE("GPL"); + + /* module parameters */ +struct il_mod_params il3945_mod_params = { + .sw_crypto = 1, + .restart_fw = 1, + .disable_hw_scan = 1, + /* the rest are 0 by default */ +}; + +/** + * il3945_get_antenna_flags - Get antenna flags for RXON command + * @il: eeprom and antenna fields are used to determine antenna flags + * + * il->eeprom39 is used to determine if antenna AUX/MAIN are reversed + * il3945_mod_params.antenna specifies the antenna diversity mode: + * + * IL_ANTENNA_DIVERSITY - NIC selects best antenna by itself + * IL_ANTENNA_MAIN - Force MAIN antenna + * IL_ANTENNA_AUX - Force AUX antenna + */ +__le32 +il3945_get_antenna_flags(const struct il_priv *il) +{ + struct il3945_eeprom *eeprom = (struct il3945_eeprom *)il->eeprom; + + switch (il3945_mod_params.antenna) { + case IL_ANTENNA_DIVERSITY: + return 0; + + case IL_ANTENNA_MAIN: + if (eeprom->antenna_switch_type) + return RXON_FLG_DIS_DIV_MSK | RXON_FLG_ANT_B_MSK; + return RXON_FLG_DIS_DIV_MSK | RXON_FLG_ANT_A_MSK; + + case IL_ANTENNA_AUX: + if (eeprom->antenna_switch_type) + return RXON_FLG_DIS_DIV_MSK | RXON_FLG_ANT_A_MSK; + return RXON_FLG_DIS_DIV_MSK | RXON_FLG_ANT_B_MSK; + } + + /* bad antenna selector value */ + IL_ERR("Bad antenna selector value (0x%x)\n", + il3945_mod_params.antenna); + + return 0; /* "diversity" is default if error */ +} + +static int +il3945_set_ccmp_dynamic_key_info(struct il_priv *il, + struct ieee80211_key_conf *keyconf, u8 sta_id) +{ + unsigned long flags; + __le16 key_flags = 0; + int ret; + + key_flags |= (STA_KEY_FLG_CCMP | STA_KEY_FLG_MAP_KEY_MSK); + key_flags |= cpu_to_le16(keyconf->keyidx << STA_KEY_FLG_KEYID_POS); + + if (sta_id == il->hw_params.bcast_id) + key_flags |= STA_KEY_MULTICAST_MSK; + + keyconf->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + keyconf->hw_key_idx = keyconf->keyidx; + key_flags &= ~STA_KEY_FLG_INVALID; + + spin_lock_irqsave(&il->sta_lock, flags); + il->stations[sta_id].keyinfo.cipher = keyconf->cipher; + il->stations[sta_id].keyinfo.keylen = keyconf->keylen; + memcpy(il->stations[sta_id].keyinfo.key, keyconf->key, keyconf->keylen); + + memcpy(il->stations[sta_id].sta.key.key, keyconf->key, keyconf->keylen); + + if ((il->stations[sta_id].sta.key. + key_flags & STA_KEY_FLG_ENCRYPT_MSK) == STA_KEY_FLG_NO_ENC) + il->stations[sta_id].sta.key.key_offset = + il_get_free_ucode_key_idx(il); + /* else, we are overriding an existing key => no need to allocated room + * in uCode. */ + + WARN(il->stations[sta_id].sta.key.key_offset == WEP_INVALID_OFFSET, + "no space for a new key"); + + il->stations[sta_id].sta.key.key_flags = key_flags; + il->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_KEY_MASK; + il->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + + D_INFO("hwcrypto: modify ucode station key info\n"); + + ret = il_send_add_sta(il, &il->stations[sta_id].sta, CMD_ASYNC); + + spin_unlock_irqrestore(&il->sta_lock, flags); + + return ret; +} + +static int +il3945_set_tkip_dynamic_key_info(struct il_priv *il, + struct ieee80211_key_conf *keyconf, u8 sta_id) +{ + return -EOPNOTSUPP; +} + +static int +il3945_set_wep_dynamic_key_info(struct il_priv *il, + struct ieee80211_key_conf *keyconf, u8 sta_id) +{ + return -EOPNOTSUPP; +} + +static int +il3945_clear_sta_key_info(struct il_priv *il, u8 sta_id) +{ + unsigned long flags; + struct il_addsta_cmd sta_cmd; + + spin_lock_irqsave(&il->sta_lock, flags); + memset(&il->stations[sta_id].keyinfo, 0, sizeof(struct il_hw_key)); + memset(&il->stations[sta_id].sta.key, 0, sizeof(struct il4965_keyinfo)); + il->stations[sta_id].sta.key.key_flags = STA_KEY_FLG_NO_ENC; + il->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_KEY_MASK; + il->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + memcpy(&sta_cmd, &il->stations[sta_id].sta, + sizeof(struct il_addsta_cmd)); + spin_unlock_irqrestore(&il->sta_lock, flags); + + D_INFO("hwcrypto: clear ucode station key info\n"); + return il_send_add_sta(il, &sta_cmd, CMD_SYNC); +} + +static int +il3945_set_dynamic_key(struct il_priv *il, struct ieee80211_key_conf *keyconf, + u8 sta_id) +{ + int ret = 0; + + keyconf->hw_key_idx = HW_KEY_DYNAMIC; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + ret = il3945_set_ccmp_dynamic_key_info(il, keyconf, sta_id); + break; + case WLAN_CIPHER_SUITE_TKIP: + ret = il3945_set_tkip_dynamic_key_info(il, keyconf, sta_id); + break; + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + ret = il3945_set_wep_dynamic_key_info(il, keyconf, sta_id); + break; + default: + IL_ERR("Unknown alg: %s alg=%x\n", __func__, keyconf->cipher); + ret = -EINVAL; + } + + D_WEP("Set dynamic key: alg=%x len=%d idx=%d sta=%d ret=%d\n", + keyconf->cipher, keyconf->keylen, keyconf->keyidx, sta_id, ret); + + return ret; +} + +static int +il3945_remove_static_key(struct il_priv *il) +{ + int ret = -EOPNOTSUPP; + + return ret; +} + +static int +il3945_set_static_key(struct il_priv *il, struct ieee80211_key_conf *key) +{ + if (key->cipher == WLAN_CIPHER_SUITE_WEP40 || + key->cipher == WLAN_CIPHER_SUITE_WEP104) + return -EOPNOTSUPP; + + IL_ERR("Static key invalid: cipher %x\n", key->cipher); + return -EINVAL; +} + +static void +il3945_clear_free_frames(struct il_priv *il) +{ + struct list_head *element; + + D_INFO("%d frames on pre-allocated heap on clear.\n", il->frames_count); + + while (!list_empty(&il->free_frames)) { + element = il->free_frames.next; + list_del(element); + kfree(list_entry(element, struct il3945_frame, list)); + il->frames_count--; + } + + if (il->frames_count) { + IL_WARN("%d frames still in use. Did we lose one?\n", + il->frames_count); + il->frames_count = 0; + } +} + +static struct il3945_frame * +il3945_get_free_frame(struct il_priv *il) +{ + struct il3945_frame *frame; + struct list_head *element; + if (list_empty(&il->free_frames)) { + frame = kzalloc(sizeof(*frame), GFP_KERNEL); + if (!frame) { + IL_ERR("Could not allocate frame!\n"); + return NULL; + } + + il->frames_count++; + return frame; + } + + element = il->free_frames.next; + list_del(element); + return list_entry(element, struct il3945_frame, list); +} + +static void +il3945_free_frame(struct il_priv *il, struct il3945_frame *frame) +{ + memset(frame, 0, sizeof(*frame)); + list_add(&frame->list, &il->free_frames); +} + +unsigned int +il3945_fill_beacon_frame(struct il_priv *il, struct ieee80211_hdr *hdr, + int left) +{ + + if (!il_is_associated(il) || !il->beacon_skb) + return 0; + + if (il->beacon_skb->len > left) + return 0; + + memcpy(hdr, il->beacon_skb->data, il->beacon_skb->len); + + return il->beacon_skb->len; +} + +static int +il3945_send_beacon_cmd(struct il_priv *il) +{ + struct il3945_frame *frame; + unsigned int frame_size; + int rc; + u8 rate; + + frame = il3945_get_free_frame(il); + + if (!frame) { + IL_ERR("Could not obtain free frame buffer for beacon " + "command.\n"); + return -ENOMEM; + } + + rate = il_get_lowest_plcp(il); + + frame_size = il3945_hw_get_beacon_cmd(il, frame, rate); + + rc = il_send_cmd_pdu(il, C_TX_BEACON, frame_size, &frame->u.cmd[0]); + + il3945_free_frame(il, frame); + + return rc; +} + +static void +il3945_unset_hw_params(struct il_priv *il) +{ + if (il->_3945.shared_virt) + dma_free_coherent(&il->pci_dev->dev, + sizeof(struct il3945_shared), + il->_3945.shared_virt, il->_3945.shared_phys); +} + +static void +il3945_build_tx_cmd_hwcrypto(struct il_priv *il, struct ieee80211_tx_info *info, + struct il_device_cmd *cmd, + struct sk_buff *skb_frag, int sta_id) +{ + struct il3945_tx_cmd *tx_cmd = (struct il3945_tx_cmd *)cmd->cmd.payload; + struct il_hw_key *keyinfo = &il->stations[sta_id].keyinfo; + + tx_cmd->sec_ctl = 0; + + switch (keyinfo->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + tx_cmd->sec_ctl = TX_CMD_SEC_CCM; + memcpy(tx_cmd->key, keyinfo->key, keyinfo->keylen); + D_TX("tx_cmd with AES hwcrypto\n"); + break; + + case WLAN_CIPHER_SUITE_TKIP: + break; + + case WLAN_CIPHER_SUITE_WEP104: + tx_cmd->sec_ctl |= TX_CMD_SEC_KEY128; + /* fall through */ + case WLAN_CIPHER_SUITE_WEP40: + tx_cmd->sec_ctl |= + TX_CMD_SEC_WEP | (info->control.hw_key-> + hw_key_idx & TX_CMD_SEC_MSK) << + TX_CMD_SEC_SHIFT; + + memcpy(&tx_cmd->key[3], keyinfo->key, keyinfo->keylen); + + D_TX("Configuring packet for WEP encryption " "with key %d\n", + info->control.hw_key->hw_key_idx); + break; + + default: + IL_ERR("Unknown encode cipher %x\n", keyinfo->cipher); + break; + } +} + +/* + * handle build C_TX command notification. + */ +static void +il3945_build_tx_cmd_basic(struct il_priv *il, struct il_device_cmd *cmd, + struct ieee80211_tx_info *info, + struct ieee80211_hdr *hdr, u8 std_id) +{ + struct il3945_tx_cmd *tx_cmd = (struct il3945_tx_cmd *)cmd->cmd.payload; + __le32 tx_flags = tx_cmd->tx_flags; + __le16 fc = hdr->frame_control; + + tx_cmd->stop_time.life_time = TX_CMD_LIFE_TIME_INFINITE; + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) { + tx_flags |= TX_CMD_FLG_ACK_MSK; + if (ieee80211_is_mgmt(fc)) + tx_flags |= TX_CMD_FLG_SEQ_CTL_MSK; + if (ieee80211_is_probe_resp(fc) && + !(le16_to_cpu(hdr->seq_ctrl) & 0xf)) + tx_flags |= TX_CMD_FLG_TSF_MSK; + } else { + tx_flags &= (~TX_CMD_FLG_ACK_MSK); + tx_flags |= TX_CMD_FLG_SEQ_CTL_MSK; + } + + tx_cmd->sta_id = std_id; + if (ieee80211_has_morefrags(fc)) + tx_flags |= TX_CMD_FLG_MORE_FRAG_MSK; + + if (ieee80211_is_data_qos(fc)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + tx_cmd->tid_tspec = qc[0] & 0xf; + tx_flags &= ~TX_CMD_FLG_SEQ_CTL_MSK; + } else { + tx_flags |= TX_CMD_FLG_SEQ_CTL_MSK; + } + + il_tx_cmd_protection(il, info, fc, &tx_flags); + + tx_flags &= ~(TX_CMD_FLG_ANT_SEL_MSK); + if (ieee80211_is_mgmt(fc)) { + if (ieee80211_is_assoc_req(fc) || ieee80211_is_reassoc_req(fc)) + tx_cmd->timeout.pm_frame_timeout = cpu_to_le16(3); + else + tx_cmd->timeout.pm_frame_timeout = cpu_to_le16(2); + } else { + tx_cmd->timeout.pm_frame_timeout = 0; + } + + tx_cmd->driver_txop = 0; + tx_cmd->tx_flags = tx_flags; + tx_cmd->next_frame_len = 0; +} + +/* + * start C_TX command process + */ +static int +il3945_tx_skb(struct il_priv *il, + struct ieee80211_sta *sta, + struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct il3945_tx_cmd *tx_cmd; + struct il_tx_queue *txq = NULL; + struct il_queue *q = NULL; + struct il_device_cmd *out_cmd; + struct il_cmd_meta *out_meta; + dma_addr_t phys_addr; + dma_addr_t txcmd_phys; + int txq_id = skb_get_queue_mapping(skb); + u16 len, idx, hdr_len; + u16 firstlen, secondlen; + u8 id; + u8 unicast; + u8 sta_id; + u8 tid = 0; + __le16 fc; + u8 wait_write_ptr = 0; + unsigned long flags; + + spin_lock_irqsave(&il->lock, flags); + if (il_is_rfkill(il)) { + D_DROP("Dropping - RF KILL\n"); + goto drop_unlock; + } + + if ((ieee80211_get_tx_rate(il->hw, info)->hw_value & 0xFF) == + IL_INVALID_RATE) { + IL_ERR("ERROR: No TX rate available.\n"); + goto drop_unlock; + } + + unicast = !is_multicast_ether_addr(hdr->addr1); + id = 0; + + fc = hdr->frame_control; + +#ifdef CPTCFG_IWLEGACY_DEBUG + if (ieee80211_is_auth(fc)) + D_TX("Sending AUTH frame\n"); + else if (ieee80211_is_assoc_req(fc)) + D_TX("Sending ASSOC frame\n"); + else if (ieee80211_is_reassoc_req(fc)) + D_TX("Sending REASSOC frame\n"); +#endif + + spin_unlock_irqrestore(&il->lock, flags); + + hdr_len = ieee80211_hdrlen(fc); + + /* Find idx into station table for destination station */ + sta_id = il_sta_id_or_broadcast(il, sta); + if (sta_id == IL_INVALID_STATION) { + D_DROP("Dropping - INVALID STATION: %pM\n", hdr->addr1); + goto drop; + } + + D_RATE("station Id %d\n", sta_id); + + if (ieee80211_is_data_qos(fc)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + tid = qc[0] & IEEE80211_QOS_CTL_TID_MASK; + if (unlikely(tid >= MAX_TID_COUNT)) + goto drop; + } + + /* Descriptor for chosen Tx queue */ + txq = &il->txq[txq_id]; + q = &txq->q; + + if ((il_queue_space(q) < q->high_mark)) + goto drop; + + spin_lock_irqsave(&il->lock, flags); + + idx = il_get_cmd_idx(q, q->write_ptr, 0); + + txq->skbs[q->write_ptr] = skb; + + /* Init first empty entry in queue's array of Tx/cmd buffers */ + out_cmd = txq->cmd[idx]; + out_meta = &txq->meta[idx]; + tx_cmd = (struct il3945_tx_cmd *)out_cmd->cmd.payload; + memset(&out_cmd->hdr, 0, sizeof(out_cmd->hdr)); + memset(tx_cmd, 0, sizeof(*tx_cmd)); + + /* + * Set up the Tx-command (not MAC!) header. + * Store the chosen Tx queue and TFD idx within the sequence field; + * after Tx, uCode's Tx response will return this value so driver can + * locate the frame within the tx queue and do post-tx processing. + */ + out_cmd->hdr.cmd = C_TX; + out_cmd->hdr.sequence = + cpu_to_le16((u16) + (QUEUE_TO_SEQ(txq_id) | IDX_TO_SEQ(q->write_ptr))); + + /* Copy MAC header from skb into command buffer */ + memcpy(tx_cmd->hdr, hdr, hdr_len); + + if (info->control.hw_key) + il3945_build_tx_cmd_hwcrypto(il, info, out_cmd, skb, sta_id); + + /* TODO need this for burst mode later on */ + il3945_build_tx_cmd_basic(il, out_cmd, info, hdr, sta_id); + + il3945_hw_build_tx_cmd_rate(il, out_cmd, info, hdr, sta_id); + + /* Total # bytes to be transmitted */ + tx_cmd->len = cpu_to_le16((u16) skb->len); + + tx_cmd->tx_flags &= ~TX_CMD_FLG_ANT_A_MSK; + tx_cmd->tx_flags &= ~TX_CMD_FLG_ANT_B_MSK; + + /* + * Use the first empty entry in this queue's command buffer array + * to contain the Tx command and MAC header concatenated together + * (payload data will be in another buffer). + * Size of this varies, due to varying MAC header length. + * If end is not dword aligned, we'll have 2 extra bytes at the end + * of the MAC header (device reads on dword boundaries). + * We'll tell device about this padding later. + */ + len = + sizeof(struct il3945_tx_cmd) + sizeof(struct il_cmd_header) + + hdr_len; + firstlen = (len + 3) & ~3; + + /* Physical address of this Tx command's header (not MAC header!), + * within command buffer array. */ + txcmd_phys = + pci_map_single(il->pci_dev, &out_cmd->hdr, firstlen, + PCI_DMA_TODEVICE); + if (unlikely(pci_dma_mapping_error(il->pci_dev, txcmd_phys))) + goto drop_unlock; + + /* Set up TFD's 2nd entry to point directly to remainder of skb, + * if any (802.11 null frames have no payload). */ + secondlen = skb->len - hdr_len; + if (secondlen > 0) { + phys_addr = + pci_map_single(il->pci_dev, skb->data + hdr_len, secondlen, + PCI_DMA_TODEVICE); + if (unlikely(pci_dma_mapping_error(il->pci_dev, phys_addr))) + goto drop_unlock; + } + + /* Add buffer containing Tx command and MAC(!) header to TFD's + * first entry */ + il->ops->txq_attach_buf_to_tfd(il, txq, txcmd_phys, firstlen, 1, 0); + dma_unmap_addr_set(out_meta, mapping, txcmd_phys); + dma_unmap_len_set(out_meta, len, firstlen); + if (secondlen > 0) + il->ops->txq_attach_buf_to_tfd(il, txq, phys_addr, secondlen, 0, + U32_PAD(secondlen)); + + if (!ieee80211_has_morefrags(hdr->frame_control)) { + txq->need_update = 1; + } else { + wait_write_ptr = 1; + txq->need_update = 0; + } + + il_update_stats(il, true, fc, skb->len); + + D_TX("sequence nr = 0X%x\n", le16_to_cpu(out_cmd->hdr.sequence)); + D_TX("tx_flags = 0X%x\n", le32_to_cpu(tx_cmd->tx_flags)); + il_print_hex_dump(il, IL_DL_TX, tx_cmd, sizeof(*tx_cmd)); + il_print_hex_dump(il, IL_DL_TX, (u8 *) tx_cmd->hdr, + ieee80211_hdrlen(fc)); + + /* Tell device the write idx *just past* this latest filled TFD */ + q->write_ptr = il_queue_inc_wrap(q->write_ptr, q->n_bd); + il_txq_update_write_ptr(il, txq); + spin_unlock_irqrestore(&il->lock, flags); + + if (il_queue_space(q) < q->high_mark && il->mac80211_registered) { + if (wait_write_ptr) { + spin_lock_irqsave(&il->lock, flags); + txq->need_update = 1; + il_txq_update_write_ptr(il, txq); + spin_unlock_irqrestore(&il->lock, flags); + } + + il_stop_queue(il, txq); + } + + return 0; + +drop_unlock: + spin_unlock_irqrestore(&il->lock, flags); +drop: + return -1; +} + +static int +il3945_get_measurement(struct il_priv *il, + struct ieee80211_measurement_params *params, u8 type) +{ + struct il_spectrum_cmd spectrum; + struct il_rx_pkt *pkt; + struct il_host_cmd cmd = { + .id = C_SPECTRUM_MEASUREMENT, + .data = (void *)&spectrum, + .flags = CMD_WANT_SKB, + }; + u32 add_time = le64_to_cpu(params->start_time); + int rc; + int spectrum_resp_status; + int duration = le16_to_cpu(params->duration); + + if (il_is_associated(il)) + add_time = + il_usecs_to_beacons(il, + le64_to_cpu(params->start_time) - + il->_3945.last_tsf, + le16_to_cpu(il->timing.beacon_interval)); + + memset(&spectrum, 0, sizeof(spectrum)); + + spectrum.channel_count = cpu_to_le16(1); + spectrum.flags = + RXON_FLG_TSF2HOST_MSK | RXON_FLG_ANT_A_MSK | RXON_FLG_DIS_DIV_MSK; + spectrum.filter_flags = MEASUREMENT_FILTER_FLAG; + cmd.len = sizeof(spectrum); + spectrum.len = cpu_to_le16(cmd.len - sizeof(spectrum.len)); + + if (il_is_associated(il)) + spectrum.start_time = + il_add_beacon_time(il, il->_3945.last_beacon_time, add_time, + le16_to_cpu(il->timing.beacon_interval)); + else + spectrum.start_time = 0; + + spectrum.channels[0].duration = cpu_to_le32(duration * TIME_UNIT); + spectrum.channels[0].channel = params->channel; + spectrum.channels[0].type = type; + if (il->active.flags & RXON_FLG_BAND_24G_MSK) + spectrum.flags |= + RXON_FLG_BAND_24G_MSK | RXON_FLG_AUTO_DETECT_MSK | + RXON_FLG_TGG_PROTECT_MSK; + + rc = il_send_cmd_sync(il, &cmd); + if (rc) + return rc; + + pkt = (struct il_rx_pkt *)cmd.reply_page; + if (pkt->hdr.flags & IL_CMD_FAILED_MSK) { + IL_ERR("Bad return from N_RX_ON_ASSOC command\n"); + rc = -EIO; + } + + spectrum_resp_status = le16_to_cpu(pkt->u.spectrum.status); + switch (spectrum_resp_status) { + case 0: /* Command will be handled */ + if (pkt->u.spectrum.id != 0xff) { + D_INFO("Replaced existing measurement: %d\n", + pkt->u.spectrum.id); + il->measurement_status &= ~MEASUREMENT_READY; + } + il->measurement_status |= MEASUREMENT_ACTIVE; + rc = 0; + break; + + case 1: /* Command will not be handled */ + rc = -EAGAIN; + break; + } + + il_free_pages(il, cmd.reply_page); + + return rc; +} + +static void +il3945_hdl_alive(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il_alive_resp *palive; + struct delayed_work *pwork; + + palive = &pkt->u.alive_frame; + + D_INFO("Alive ucode status 0x%08X revision " "0x%01X 0x%01X\n", + palive->is_valid, palive->ver_type, palive->ver_subtype); + + if (palive->ver_subtype == INITIALIZE_SUBTYPE) { + D_INFO("Initialization Alive received.\n"); + memcpy(&il->card_alive_init, &pkt->u.alive_frame, + sizeof(struct il_alive_resp)); + pwork = &il->init_alive_start; + } else { + D_INFO("Runtime Alive received.\n"); + memcpy(&il->card_alive, &pkt->u.alive_frame, + sizeof(struct il_alive_resp)); + pwork = &il->alive_start; + il3945_disable_events(il); + } + + /* We delay the ALIVE response by 5ms to + * give the HW RF Kill time to activate... */ + if (palive->is_valid == UCODE_VALID_OK) + queue_delayed_work(il->workqueue, pwork, msecs_to_jiffies(5)); + else + IL_WARN("uCode did not respond OK.\n"); +} + +static void +il3945_hdl_add_sta(struct il_priv *il, struct il_rx_buf *rxb) +{ +#ifdef CPTCFG_IWLEGACY_DEBUG + struct il_rx_pkt *pkt = rxb_addr(rxb); +#endif + + D_RX("Received C_ADD_STA: 0x%02X\n", pkt->u.status); +} + +static void +il3945_hdl_beacon(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il3945_beacon_notif *beacon = &(pkt->u.beacon_status); +#ifdef CPTCFG_IWLEGACY_DEBUG + u8 rate = beacon->beacon_notify_hdr.rate; + + D_RX("beacon status %x retries %d iss %d " "tsf %d %d rate %d\n", + le32_to_cpu(beacon->beacon_notify_hdr.status) & TX_STATUS_MSK, + beacon->beacon_notify_hdr.failure_frame, + le32_to_cpu(beacon->ibss_mgr_status), + le32_to_cpu(beacon->high_tsf), le32_to_cpu(beacon->low_tsf), rate); +#endif + + il->ibss_manager = le32_to_cpu(beacon->ibss_mgr_status); + +} + +/* Handle notification from uCode that card's power state is changing + * due to software, hardware, or critical temperature RFKILL */ +static void +il3945_hdl_card_state(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + u32 flags = le32_to_cpu(pkt->u.card_state_notif.flags); + unsigned long status = il->status; + + IL_WARN("Card state received: HW:%s SW:%s\n", + (flags & HW_CARD_DISABLED) ? "Kill" : "On", + (flags & SW_CARD_DISABLED) ? "Kill" : "On"); + + _il_wr(il, CSR_UCODE_DRV_GP1_SET, CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); + + if (flags & HW_CARD_DISABLED) + set_bit(S_RFKILL, &il->status); + else + clear_bit(S_RFKILL, &il->status); + + il_scan_cancel(il); + + if ((test_bit(S_RFKILL, &status) != + test_bit(S_RFKILL, &il->status))) + wiphy_rfkill_set_hw_state(il->hw->wiphy, + test_bit(S_RFKILL, &il->status)); + else + wake_up(&il->wait_command_queue); +} + +/** + * il3945_setup_handlers - Initialize Rx handler callbacks + * + * Setup the RX handlers for each of the reply types sent from the uCode + * to the host. + * + * This function chains into the hardware specific files for them to setup + * any hardware specific handlers as well. + */ +static void +il3945_setup_handlers(struct il_priv *il) +{ + il->handlers[N_ALIVE] = il3945_hdl_alive; + il->handlers[C_ADD_STA] = il3945_hdl_add_sta; + il->handlers[N_ERROR] = il_hdl_error; + il->handlers[N_CHANNEL_SWITCH] = il_hdl_csa; + il->handlers[N_SPECTRUM_MEASUREMENT] = il_hdl_spectrum_measurement; + il->handlers[N_PM_SLEEP] = il_hdl_pm_sleep; + il->handlers[N_PM_DEBUG_STATS] = il_hdl_pm_debug_stats; + il->handlers[N_BEACON] = il3945_hdl_beacon; + + /* + * The same handler is used for both the REPLY to a discrete + * stats request from the host as well as for the periodic + * stats notifications (after received beacons) from the uCode. + */ + il->handlers[C_STATS] = il3945_hdl_c_stats; + il->handlers[N_STATS] = il3945_hdl_stats; + + il_setup_rx_scan_handlers(il); + il->handlers[N_CARD_STATE] = il3945_hdl_card_state; + + /* Set up hardware specific Rx handlers */ + il3945_hw_handler_setup(il); +} + +/************************** RX-FUNCTIONS ****************************/ +/* + * Rx theory of operation + * + * The host allocates 32 DMA target addresses and passes the host address + * to the firmware at register IL_RFDS_TBL_LOWER + N * RFD_SIZE where N is + * 0 to 31 + * + * Rx Queue Indexes + * The host/firmware share two idx registers for managing the Rx buffers. + * + * The READ idx maps to the first position that the firmware may be writing + * to -- the driver can read up to (but not including) this position and get + * good data. + * The READ idx is managed by the firmware once the card is enabled. + * + * The WRITE idx maps to the last position the driver has read from -- the + * position preceding WRITE is the last slot the firmware can place a packet. + * + * The queue is empty (no good data) if WRITE = READ - 1, and is full if + * WRITE = READ. + * + * During initialization, the host sets up the READ queue position to the first + * IDX position, and WRITE to the last (READ - 1 wrapped) + * + * When the firmware places a packet in a buffer, it will advance the READ idx + * and fire the RX interrupt. The driver can then query the READ idx and + * process as many packets as possible, moving the WRITE idx forward as it + * resets the Rx queue buffers with new memory. + * + * The management in the driver is as follows: + * + A list of pre-allocated SKBs is stored in iwl->rxq->rx_free. When + * iwl->rxq->free_count drops to or below RX_LOW_WATERMARK, work is scheduled + * to replenish the iwl->rxq->rx_free. + * + In il3945_rx_replenish (scheduled) if 'processed' != 'read' then the + * iwl->rxq is replenished and the READ IDX is updated (updating the + * 'processed' and 'read' driver idxes as well) + * + A received packet is processed and handed to the kernel network stack, + * detached from the iwl->rxq. The driver 'processed' idx is updated. + * + The Host/Firmware iwl->rxq is replenished at tasklet time from the rx_free + * list. If there are no allocated buffers in iwl->rxq->rx_free, the READ + * IDX is not incremented and iwl->status(RX_STALLED) is set. If there + * were enough free buffers and RX_STALLED is set it is cleared. + * + * + * Driver sequence: + * + * il3945_rx_replenish() Replenishes rx_free list from rx_used, and calls + * il3945_rx_queue_restock + * il3945_rx_queue_restock() Moves available buffers from rx_free into Rx + * queue, updates firmware pointers, and updates + * the WRITE idx. If insufficient rx_free buffers + * are available, schedules il3945_rx_replenish + * + * -- enable interrupts -- + * ISR - il3945_rx() Detach il_rx_bufs from pool up to the + * READ IDX, detaching the SKB from the pool. + * Moves the packet buffer from queue to rx_used. + * Calls il3945_rx_queue_restock to refill any empty + * slots. + * ... + * + */ + +/** + * il3945_dma_addr2rbd_ptr - convert a DMA address to a uCode read buffer ptr + */ +static inline __le32 +il3945_dma_addr2rbd_ptr(struct il_priv *il, dma_addr_t dma_addr) +{ + return cpu_to_le32((u32) dma_addr); +} + +/** + * il3945_rx_queue_restock - refill RX queue from pre-allocated pool + * + * If there are slots in the RX queue that need to be restocked, + * and we have free pre-allocated buffers, fill the ranks as much + * as we can, pulling from rx_free. + * + * This moves the 'write' idx forward to catch up with 'processed', and + * also updates the memory address in the firmware to reference the new + * target buffer. + */ +static void +il3945_rx_queue_restock(struct il_priv *il) +{ + struct il_rx_queue *rxq = &il->rxq; + struct list_head *element; + struct il_rx_buf *rxb; + unsigned long flags; + int write; + + spin_lock_irqsave(&rxq->lock, flags); + write = rxq->write & ~0x7; + while (il_rx_queue_space(rxq) > 0 && rxq->free_count) { + /* Get next free Rx buffer, remove from free list */ + element = rxq->rx_free.next; + rxb = list_entry(element, struct il_rx_buf, list); + list_del(element); + + /* Point to Rx buffer via next RBD in circular buffer */ + rxq->bd[rxq->write] = + il3945_dma_addr2rbd_ptr(il, rxb->page_dma); + rxq->queue[rxq->write] = rxb; + rxq->write = (rxq->write + 1) & RX_QUEUE_MASK; + rxq->free_count--; + } + spin_unlock_irqrestore(&rxq->lock, flags); + /* If the pre-allocated buffer pool is dropping low, schedule to + * refill it */ + if (rxq->free_count <= RX_LOW_WATERMARK) + queue_work(il->workqueue, &il->rx_replenish); + + /* If we've added more space for the firmware to place data, tell it. + * Increment device's write pointer in multiples of 8. */ + if (rxq->write_actual != (rxq->write & ~0x7) || + abs(rxq->write - rxq->read) > 7) { + spin_lock_irqsave(&rxq->lock, flags); + rxq->need_update = 1; + spin_unlock_irqrestore(&rxq->lock, flags); + il_rx_queue_update_write_ptr(il, rxq); + } +} + +/** + * il3945_rx_replenish - Move all used packet from rx_used to rx_free + * + * When moving to rx_free an SKB is allocated for the slot. + * + * Also restock the Rx queue via il3945_rx_queue_restock. + * This is called as a scheduled work item (except for during initialization) + */ +static void +il3945_rx_allocate(struct il_priv *il, gfp_t priority) +{ + struct il_rx_queue *rxq = &il->rxq; + struct list_head *element; + struct il_rx_buf *rxb; + struct page *page; + dma_addr_t page_dma; + unsigned long flags; + gfp_t gfp_mask = priority; + + while (1) { + spin_lock_irqsave(&rxq->lock, flags); + if (list_empty(&rxq->rx_used)) { + spin_unlock_irqrestore(&rxq->lock, flags); + return; + } + spin_unlock_irqrestore(&rxq->lock, flags); + + if (rxq->free_count > RX_LOW_WATERMARK) + gfp_mask |= __GFP_NOWARN; + + if (il->hw_params.rx_page_order > 0) + gfp_mask |= __GFP_COMP; + + /* Alloc a new receive buffer */ + page = alloc_pages(gfp_mask, il->hw_params.rx_page_order); + if (!page) { + if (net_ratelimit()) + D_INFO("Failed to allocate SKB buffer.\n"); + if (rxq->free_count <= RX_LOW_WATERMARK && + net_ratelimit()) + IL_ERR("Failed to allocate SKB buffer with %0x." + "Only %u free buffers remaining.\n", + priority, rxq->free_count); + /* We don't reschedule replenish work here -- we will + * call the restock method and if it still needs + * more buffers it will schedule replenish */ + break; + } + + /* Get physical address of RB/SKB */ + page_dma = + pci_map_page(il->pci_dev, page, 0, + PAGE_SIZE << il->hw_params.rx_page_order, + PCI_DMA_FROMDEVICE); + + if (unlikely(pci_dma_mapping_error(il->pci_dev, page_dma))) { + __free_pages(page, il->hw_params.rx_page_order); + break; + } + + spin_lock_irqsave(&rxq->lock, flags); + + if (list_empty(&rxq->rx_used)) { + spin_unlock_irqrestore(&rxq->lock, flags); + pci_unmap_page(il->pci_dev, page_dma, + PAGE_SIZE << il->hw_params.rx_page_order, + PCI_DMA_FROMDEVICE); + __free_pages(page, il->hw_params.rx_page_order); + return; + } + + element = rxq->rx_used.next; + rxb = list_entry(element, struct il_rx_buf, list); + list_del(element); + + rxb->page = page; + rxb->page_dma = page_dma; + list_add_tail(&rxb->list, &rxq->rx_free); + rxq->free_count++; + il->alloc_rxb_page++; + + spin_unlock_irqrestore(&rxq->lock, flags); + } +} + +void +il3945_rx_queue_reset(struct il_priv *il, struct il_rx_queue *rxq) +{ + unsigned long flags; + int i; + spin_lock_irqsave(&rxq->lock, flags); + INIT_LIST_HEAD(&rxq->rx_free); + INIT_LIST_HEAD(&rxq->rx_used); + /* Fill the rx_used queue with _all_ of the Rx buffers */ + for (i = 0; i < RX_FREE_BUFFERS + RX_QUEUE_SIZE; i++) { + /* In the reset function, these buffers may have been allocated + * to an SKB, so we need to unmap and free potential storage */ + if (rxq->pool[i].page != NULL) { + pci_unmap_page(il->pci_dev, rxq->pool[i].page_dma, + PAGE_SIZE << il->hw_params.rx_page_order, + PCI_DMA_FROMDEVICE); + __il_free_pages(il, rxq->pool[i].page); + rxq->pool[i].page = NULL; + } + list_add_tail(&rxq->pool[i].list, &rxq->rx_used); + } + + /* Set us so that we have processed and used all buffers, but have + * not restocked the Rx queue with fresh buffers */ + rxq->read = rxq->write = 0; + rxq->write_actual = 0; + rxq->free_count = 0; + spin_unlock_irqrestore(&rxq->lock, flags); +} + +void +il3945_rx_replenish(void *data) +{ + struct il_priv *il = data; + unsigned long flags; + + il3945_rx_allocate(il, GFP_KERNEL); + + spin_lock_irqsave(&il->lock, flags); + il3945_rx_queue_restock(il); + spin_unlock_irqrestore(&il->lock, flags); +} + +static void +il3945_rx_replenish_now(struct il_priv *il) +{ + il3945_rx_allocate(il, GFP_ATOMIC); + + il3945_rx_queue_restock(il); +} + +/* Assumes that the skb field of the buffers in 'pool' is kept accurate. + * If an SKB has been detached, the POOL needs to have its SKB set to NULL + * This free routine walks the list of POOL entries and if SKB is set to + * non NULL it is unmapped and freed + */ +static void +il3945_rx_queue_free(struct il_priv *il, struct il_rx_queue *rxq) +{ + int i; + for (i = 0; i < RX_QUEUE_SIZE + RX_FREE_BUFFERS; i++) { + if (rxq->pool[i].page != NULL) { + pci_unmap_page(il->pci_dev, rxq->pool[i].page_dma, + PAGE_SIZE << il->hw_params.rx_page_order, + PCI_DMA_FROMDEVICE); + __il_free_pages(il, rxq->pool[i].page); + rxq->pool[i].page = NULL; + } + } + + dma_free_coherent(&il->pci_dev->dev, 4 * RX_QUEUE_SIZE, rxq->bd, + rxq->bd_dma); + dma_free_coherent(&il->pci_dev->dev, sizeof(struct il_rb_status), + rxq->rb_stts, rxq->rb_stts_dma); + rxq->bd = NULL; + rxq->rb_stts = NULL; +} + +/* Convert linear signal-to-noise ratio into dB */ +static u8 ratio2dB[100] = { +/* 0 1 2 3 4 5 6 7 8 9 */ + 0, 0, 6, 10, 12, 14, 16, 17, 18, 19, /* 00 - 09 */ + 20, 21, 22, 22, 23, 23, 24, 25, 26, 26, /* 10 - 19 */ + 26, 26, 26, 27, 27, 28, 28, 28, 29, 29, /* 20 - 29 */ + 29, 30, 30, 30, 31, 31, 31, 31, 32, 32, /* 30 - 39 */ + 32, 32, 32, 33, 33, 33, 33, 33, 34, 34, /* 40 - 49 */ + 34, 34, 34, 34, 35, 35, 35, 35, 35, 35, /* 50 - 59 */ + 36, 36, 36, 36, 36, 36, 36, 37, 37, 37, /* 60 - 69 */ + 37, 37, 37, 37, 37, 38, 38, 38, 38, 38, /* 70 - 79 */ + 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, /* 80 - 89 */ + 39, 39, 39, 39, 39, 40, 40, 40, 40, 40 /* 90 - 99 */ +}; + +/* Calculates a relative dB value from a ratio of linear + * (i.e. not dB) signal levels. + * Conversion assumes that levels are voltages (20*log), not powers (10*log). */ +int +il3945_calc_db_from_ratio(int sig_ratio) +{ + /* 1000:1 or higher just report as 60 dB */ + if (sig_ratio >= 1000) + return 60; + + /* 100:1 or higher, divide by 10 and use table, + * add 20 dB to make up for divide by 10 */ + if (sig_ratio >= 100) + return 20 + (int)ratio2dB[sig_ratio / 10]; + + /* We shouldn't see this */ + if (sig_ratio < 1) + return 0; + + /* Use table for ratios 1:1 - 99:1 */ + return (int)ratio2dB[sig_ratio]; +} + +/** + * il3945_rx_handle - Main entry function for receiving responses from uCode + * + * Uses the il->handlers callback function array to invoke + * the appropriate handlers, including command responses, + * frame-received notifications, and other notifications. + */ +static void +il3945_rx_handle(struct il_priv *il) +{ + struct il_rx_buf *rxb; + struct il_rx_pkt *pkt; + struct il_rx_queue *rxq = &il->rxq; + u32 r, i; + int reclaim; + unsigned long flags; + u8 fill_rx = 0; + u32 count = 8; + int total_empty = 0; + + /* uCode's read idx (stored in shared DRAM) indicates the last Rx + * buffer that the driver may process (last buffer filled by ucode). */ + r = le16_to_cpu(rxq->rb_stts->closed_rb_num) & 0x0FFF; + i = rxq->read; + + /* calculate total frames need to be restock after handling RX */ + total_empty = r - rxq->write_actual; + if (total_empty < 0) + total_empty += RX_QUEUE_SIZE; + + if (total_empty > (RX_QUEUE_SIZE / 2)) + fill_rx = 1; + /* Rx interrupt, but nothing sent from uCode */ + if (i == r) + D_RX("r = %d, i = %d\n", r, i); + + while (i != r) { + int len; + + rxb = rxq->queue[i]; + + /* If an RXB doesn't have a Rx queue slot associated with it, + * then a bug has been introduced in the queue refilling + * routines -- catch it here */ + BUG_ON(rxb == NULL); + + rxq->queue[i] = NULL; + + pci_unmap_page(il->pci_dev, rxb->page_dma, + PAGE_SIZE << il->hw_params.rx_page_order, + PCI_DMA_FROMDEVICE); + pkt = rxb_addr(rxb); + + len = le32_to_cpu(pkt->len_n_flags) & IL_RX_FRAME_SIZE_MSK; + len += sizeof(u32); /* account for status word */ + + /* Reclaim a command buffer only if this packet is a response + * to a (driver-originated) command. + * If the packet (e.g. Rx frame) originated from uCode, + * there is no command buffer to reclaim. + * Ucode should set SEQ_RX_FRAME bit if ucode-originated, + * but apparently a few don't get set; catch them here. */ + reclaim = !(pkt->hdr.sequence & SEQ_RX_FRAME) && + pkt->hdr.cmd != N_STATS && pkt->hdr.cmd != C_TX; + + /* Based on type of command response or notification, + * handle those that need handling via function in + * handlers table. See il3945_setup_handlers() */ + if (il->handlers[pkt->hdr.cmd]) { + D_RX("r = %d, i = %d, %s, 0x%02x\n", r, i, + il_get_cmd_string(pkt->hdr.cmd), pkt->hdr.cmd); + il->isr_stats.handlers[pkt->hdr.cmd]++; + il->handlers[pkt->hdr.cmd] (il, rxb); + } else { + /* No handling needed */ + D_RX("r %d i %d No handler needed for %s, 0x%02x\n", r, + i, il_get_cmd_string(pkt->hdr.cmd), pkt->hdr.cmd); + } + + /* + * XXX: After here, we should always check rxb->page + * against NULL before touching it or its virtual + * memory (pkt). Because some handler might have + * already taken or freed the pages. + */ + + if (reclaim) { + /* Invoke any callbacks, transfer the buffer to caller, + * and fire off the (possibly) blocking il_send_cmd() + * as we reclaim the driver command queue */ + if (rxb->page) + il_tx_cmd_complete(il, rxb); + else + IL_WARN("Claim null rxb?\n"); + } + + /* Reuse the page if possible. For notification packets and + * SKBs that fail to Rx correctly, add them back into the + * rx_free list for reuse later. */ + spin_lock_irqsave(&rxq->lock, flags); + if (rxb->page != NULL) { + rxb->page_dma = + pci_map_page(il->pci_dev, rxb->page, 0, + PAGE_SIZE << il->hw_params. + rx_page_order, PCI_DMA_FROMDEVICE); + if (unlikely(pci_dma_mapping_error(il->pci_dev, + rxb->page_dma))) { + __il_free_pages(il, rxb->page); + rxb->page = NULL; + list_add_tail(&rxb->list, &rxq->rx_used); + } else { + list_add_tail(&rxb->list, &rxq->rx_free); + rxq->free_count++; + } + } else + list_add_tail(&rxb->list, &rxq->rx_used); + + spin_unlock_irqrestore(&rxq->lock, flags); + + i = (i + 1) & RX_QUEUE_MASK; + /* If there are a lot of unused frames, + * restock the Rx queue so ucode won't assert. */ + if (fill_rx) { + count++; + if (count >= 8) { + rxq->read = i; + il3945_rx_replenish_now(il); + count = 0; + } + } + } + + /* Backtrack one entry */ + rxq->read = i; + if (fill_rx) + il3945_rx_replenish_now(il); + else + il3945_rx_queue_restock(il); +} + +/* call this function to flush any scheduled tasklet */ +static inline void +il3945_synchronize_irq(struct il_priv *il) +{ + /* wait to make sure we flush pending tasklet */ + synchronize_irq(il->pci_dev->irq); + tasklet_kill(&il->irq_tasklet); +} + +static const char * +il3945_desc_lookup(int i) +{ + switch (i) { + case 1: + return "FAIL"; + case 2: + return "BAD_PARAM"; + case 3: + return "BAD_CHECKSUM"; + case 4: + return "NMI_INTERRUPT"; + case 5: + return "SYSASSERT"; + case 6: + return "FATAL_ERROR"; + } + + return "UNKNOWN"; +} + +#define ERROR_START_OFFSET (1 * sizeof(u32)) +#define ERROR_ELEM_SIZE (7 * sizeof(u32)) + +void +il3945_dump_nic_error_log(struct il_priv *il) +{ + u32 i; + u32 desc, time, count, base, data1; + u32 blink1, blink2, ilink1, ilink2; + + base = le32_to_cpu(il->card_alive.error_event_table_ptr); + + if (!il3945_hw_valid_rtc_data_addr(base)) { + IL_ERR("Not valid error log pointer 0x%08X\n", base); + return; + } + + count = il_read_targ_mem(il, base); + + if (ERROR_START_OFFSET <= count * ERROR_ELEM_SIZE) { + IL_ERR("Start IWL Error Log Dump:\n"); + IL_ERR("Status: 0x%08lX, count: %d\n", il->status, count); + } + + IL_ERR("Desc Time asrtPC blink2 " + "ilink1 nmiPC Line\n"); + for (i = ERROR_START_OFFSET; + i < (count * ERROR_ELEM_SIZE) + ERROR_START_OFFSET; + i += ERROR_ELEM_SIZE) { + desc = il_read_targ_mem(il, base + i); + time = il_read_targ_mem(il, base + i + 1 * sizeof(u32)); + blink1 = il_read_targ_mem(il, base + i + 2 * sizeof(u32)); + blink2 = il_read_targ_mem(il, base + i + 3 * sizeof(u32)); + ilink1 = il_read_targ_mem(il, base + i + 4 * sizeof(u32)); + ilink2 = il_read_targ_mem(il, base + i + 5 * sizeof(u32)); + data1 = il_read_targ_mem(il, base + i + 6 * sizeof(u32)); + + IL_ERR("%-13s (0x%X) %010u 0x%05X 0x%05X 0x%05X 0x%05X %u\n\n", + il3945_desc_lookup(desc), desc, time, blink1, blink2, + ilink1, ilink2, data1); + } +} + +static void +il3945_irq_tasklet(struct il_priv *il) +{ + u32 inta, handled = 0; + u32 inta_fh; + unsigned long flags; +#ifdef CPTCFG_IWLEGACY_DEBUG + u32 inta_mask; +#endif + + spin_lock_irqsave(&il->lock, flags); + + /* Ack/clear/reset pending uCode interrupts. + * Note: Some bits in CSR_INT are "OR" of bits in CSR_FH_INT_STATUS, + * and will clear only when CSR_FH_INT_STATUS gets cleared. */ + inta = _il_rd(il, CSR_INT); + _il_wr(il, CSR_INT, inta); + + /* Ack/clear/reset pending flow-handler (DMA) interrupts. + * Any new interrupts that happen after this, either while we're + * in this tasklet, or later, will show up in next ISR/tasklet. */ + inta_fh = _il_rd(il, CSR_FH_INT_STATUS); + _il_wr(il, CSR_FH_INT_STATUS, inta_fh); + +#ifdef CPTCFG_IWLEGACY_DEBUG + if (il_get_debug_level(il) & IL_DL_ISR) { + /* just for debug */ + inta_mask = _il_rd(il, CSR_INT_MASK); + D_ISR("inta 0x%08x, enabled 0x%08x, fh 0x%08x\n", inta, + inta_mask, inta_fh); + } +#endif + + spin_unlock_irqrestore(&il->lock, flags); + + /* Since CSR_INT and CSR_FH_INT_STATUS reads and clears are not + * atomic, make sure that inta covers all the interrupts that + * we've discovered, even if FH interrupt came in just after + * reading CSR_INT. */ + if (inta_fh & CSR39_FH_INT_RX_MASK) + inta |= CSR_INT_BIT_FH_RX; + if (inta_fh & CSR39_FH_INT_TX_MASK) + inta |= CSR_INT_BIT_FH_TX; + + /* Now service all interrupt bits discovered above. */ + if (inta & CSR_INT_BIT_HW_ERR) { + IL_ERR("Hardware error detected. Restarting.\n"); + + /* Tell the device to stop sending interrupts */ + il_disable_interrupts(il); + + il->isr_stats.hw++; + il_irq_handle_error(il); + + handled |= CSR_INT_BIT_HW_ERR; + + return; + } +#ifdef CPTCFG_IWLEGACY_DEBUG + if (il_get_debug_level(il) & (IL_DL_ISR)) { + /* NIC fires this, but we don't use it, redundant with WAKEUP */ + if (inta & CSR_INT_BIT_SCD) { + D_ISR("Scheduler finished to transmit " + "the frame/frames.\n"); + il->isr_stats.sch++; + } + + /* Alive notification via Rx interrupt will do the real work */ + if (inta & CSR_INT_BIT_ALIVE) { + D_ISR("Alive interrupt\n"); + il->isr_stats.alive++; + } + } +#endif + /* Safely ignore these bits for debug checks below */ + inta &= ~(CSR_INT_BIT_SCD | CSR_INT_BIT_ALIVE); + + /* Error detected by uCode */ + if (inta & CSR_INT_BIT_SW_ERR) { + IL_ERR("Microcode SW error detected. " "Restarting 0x%X.\n", + inta); + il->isr_stats.sw++; + il_irq_handle_error(il); + handled |= CSR_INT_BIT_SW_ERR; + } + + /* uCode wakes up after power-down sleep */ + if (inta & CSR_INT_BIT_WAKEUP) { + D_ISR("Wakeup interrupt\n"); + il_rx_queue_update_write_ptr(il, &il->rxq); + il_txq_update_write_ptr(il, &il->txq[0]); + il_txq_update_write_ptr(il, &il->txq[1]); + il_txq_update_write_ptr(il, &il->txq[2]); + il_txq_update_write_ptr(il, &il->txq[3]); + il_txq_update_write_ptr(il, &il->txq[4]); + il_txq_update_write_ptr(il, &il->txq[5]); + + il->isr_stats.wakeup++; + handled |= CSR_INT_BIT_WAKEUP; + } + + /* All uCode command responses, including Tx command responses, + * Rx "responses" (frame-received notification), and other + * notifications from uCode come through here*/ + if (inta & (CSR_INT_BIT_FH_RX | CSR_INT_BIT_SW_RX)) { + il3945_rx_handle(il); + il->isr_stats.rx++; + handled |= (CSR_INT_BIT_FH_RX | CSR_INT_BIT_SW_RX); + } + + if (inta & CSR_INT_BIT_FH_TX) { + D_ISR("Tx interrupt\n"); + il->isr_stats.tx++; + + _il_wr(il, CSR_FH_INT_STATUS, (1 << 6)); + il_wr(il, FH39_TCSR_CREDIT(FH39_SRVC_CHNL), 0x0); + handled |= CSR_INT_BIT_FH_TX; + } + + if (inta & ~handled) { + IL_ERR("Unhandled INTA bits 0x%08x\n", inta & ~handled); + il->isr_stats.unhandled++; + } + + if (inta & ~il->inta_mask) { + IL_WARN("Disabled INTA bits 0x%08x were pending\n", + inta & ~il->inta_mask); + IL_WARN(" with inta_fh = 0x%08x\n", inta_fh); + } + + /* Re-enable all interrupts */ + /* only Re-enable if disabled by irq */ + if (test_bit(S_INT_ENABLED, &il->status)) + il_enable_interrupts(il); + +#ifdef CPTCFG_IWLEGACY_DEBUG + if (il_get_debug_level(il) & (IL_DL_ISR)) { + inta = _il_rd(il, CSR_INT); + inta_mask = _il_rd(il, CSR_INT_MASK); + inta_fh = _il_rd(il, CSR_FH_INT_STATUS); + D_ISR("End inta 0x%08x, enabled 0x%08x, fh 0x%08x, " + "flags 0x%08lx\n", inta, inta_mask, inta_fh, flags); + } +#endif +} + +static int +il3945_get_channels_for_scan(struct il_priv *il, enum ieee80211_band band, + u8 is_active, u8 n_probes, + struct il3945_scan_channel *scan_ch, + struct ieee80211_vif *vif) +{ + struct ieee80211_channel *chan; + const struct ieee80211_supported_band *sband; + const struct il_channel_info *ch_info; + u16 passive_dwell = 0; + u16 active_dwell = 0; + int added, i; + + sband = il_get_hw_mode(il, band); + if (!sband) + return 0; + + active_dwell = il_get_active_dwell_time(il, band, n_probes); + passive_dwell = il_get_passive_dwell_time(il, band, vif); + + if (passive_dwell <= active_dwell) + passive_dwell = active_dwell + 1; + + for (i = 0, added = 0; i < il->scan_request->n_channels; i++) { + chan = il->scan_request->channels[i]; + + if (chan->band != band) + continue; + + scan_ch->channel = chan->hw_value; + + ch_info = il_get_channel_info(il, band, scan_ch->channel); + if (!il_is_channel_valid(ch_info)) { + D_SCAN("Channel %d is INVALID for this band.\n", + scan_ch->channel); + continue; + } + + scan_ch->active_dwell = cpu_to_le16(active_dwell); + scan_ch->passive_dwell = cpu_to_le16(passive_dwell); + /* If passive , set up for auto-switch + * and use long active_dwell time. + */ + if (!is_active || il_is_channel_passive(ch_info) || + (chan->flags & IEEE80211_CHAN_PASSIVE_SCAN)) { + scan_ch->type = 0; /* passive */ + if (IL_UCODE_API(il->ucode_ver) == 1) + scan_ch->active_dwell = + cpu_to_le16(passive_dwell - 1); + } else { + scan_ch->type = 1; /* active */ + } + + /* Set direct probe bits. These may be used both for active + * scan channels (probes gets sent right away), + * or for passive channels (probes get se sent only after + * hearing clear Rx packet).*/ + if (IL_UCODE_API(il->ucode_ver) >= 2) { + if (n_probes) + scan_ch->type |= IL39_SCAN_PROBE_MASK(n_probes); + } else { + /* uCode v1 does not allow setting direct probe bits on + * passive channel. */ + if ((scan_ch->type & 1) && n_probes) + scan_ch->type |= IL39_SCAN_PROBE_MASK(n_probes); + } + + /* Set txpower levels to defaults */ + scan_ch->tpc.dsp_atten = 110; + /* scan_pwr_info->tpc.dsp_atten; */ + + /*scan_pwr_info->tpc.tx_gain; */ + if (band == IEEE80211_BAND_5GHZ) + scan_ch->tpc.tx_gain = ((1 << 5) | (3 << 3)) | 3; + else { + scan_ch->tpc.tx_gain = ((1 << 5) | (5 << 3)); + /* NOTE: if we were doing 6Mb OFDM for scans we'd use + * power level: + * scan_ch->tpc.tx_gain = ((1 << 5) | (2 << 3)) | 3; + */ + } + + D_SCAN("Scanning %d [%s %d]\n", scan_ch->channel, + (scan_ch->type & 1) ? "ACTIVE" : "PASSIVE", + (scan_ch->type & 1) ? active_dwell : passive_dwell); + + scan_ch++; + added++; + } + + D_SCAN("total channels to scan %d\n", added); + return added; +} + +static void +il3945_init_hw_rates(struct il_priv *il, struct ieee80211_rate *rates) +{ + int i; + + for (i = 0; i < RATE_COUNT_LEGACY; i++) { + rates[i].bitrate = il3945_rates[i].ieee * 5; + rates[i].hw_value = i; /* Rate scaling will work on idxes */ + rates[i].hw_value_short = i; + rates[i].flags = 0; + if (i > IL39_LAST_OFDM_RATE || i < IL_FIRST_OFDM_RATE) { + /* + * If CCK != 1M then set short preamble rate flag. + */ + rates[i].flags |= + (il3945_rates[i].plcp == + 10) ? 0 : IEEE80211_RATE_SHORT_PREAMBLE; + } + } +} + +/****************************************************************************** + * + * uCode download functions + * + ******************************************************************************/ + +static void +il3945_dealloc_ucode_pci(struct il_priv *il) +{ + il_free_fw_desc(il->pci_dev, &il->ucode_code); + il_free_fw_desc(il->pci_dev, &il->ucode_data); + il_free_fw_desc(il->pci_dev, &il->ucode_data_backup); + il_free_fw_desc(il->pci_dev, &il->ucode_init); + il_free_fw_desc(il->pci_dev, &il->ucode_init_data); + il_free_fw_desc(il->pci_dev, &il->ucode_boot); +} + +/** + * il3945_verify_inst_full - verify runtime uCode image in card vs. host, + * looking at all data. + */ +static int +il3945_verify_inst_full(struct il_priv *il, __le32 * image, u32 len) +{ + u32 val; + u32 save_len = len; + int rc = 0; + u32 errcnt; + + D_INFO("ucode inst image size is %u\n", len); + + il_wr(il, HBUS_TARG_MEM_RADDR, IL39_RTC_INST_LOWER_BOUND); + + errcnt = 0; + for (; len > 0; len -= sizeof(u32), image++) { + /* read data comes through single port, auto-incr addr */ + /* NOTE: Use the debugless read so we don't flood kernel log + * if IL_DL_IO is set */ + val = _il_rd(il, HBUS_TARG_MEM_RDAT); + if (val != le32_to_cpu(*image)) { + IL_ERR("uCode INST section is invalid at " + "offset 0x%x, is 0x%x, s/b 0x%x\n", + save_len - len, val, le32_to_cpu(*image)); + rc = -EIO; + errcnt++; + if (errcnt >= 20) + break; + } + } + + if (!errcnt) + D_INFO("ucode image in INSTRUCTION memory is good\n"); + + return rc; +} + +/** + * il3945_verify_inst_sparse - verify runtime uCode image in card vs. host, + * using sample data 100 bytes apart. If these sample points are good, + * it's a pretty good bet that everything between them is good, too. + */ +static int +il3945_verify_inst_sparse(struct il_priv *il, __le32 * image, u32 len) +{ + u32 val; + int rc = 0; + u32 errcnt = 0; + u32 i; + + D_INFO("ucode inst image size is %u\n", len); + + for (i = 0; i < len; i += 100, image += 100 / sizeof(u32)) { + /* read data comes through single port, auto-incr addr */ + /* NOTE: Use the debugless read so we don't flood kernel log + * if IL_DL_IO is set */ + il_wr(il, HBUS_TARG_MEM_RADDR, i + IL39_RTC_INST_LOWER_BOUND); + val = _il_rd(il, HBUS_TARG_MEM_RDAT); + if (val != le32_to_cpu(*image)) { +#if 0 /* Enable this if you want to see details */ + IL_ERR("uCode INST section is invalid at " + "offset 0x%x, is 0x%x, s/b 0x%x\n", i, val, + *image); +#endif + rc = -EIO; + errcnt++; + if (errcnt >= 3) + break; + } + } + + return rc; +} + +/** + * il3945_verify_ucode - determine which instruction image is in SRAM, + * and verify its contents + */ +static int +il3945_verify_ucode(struct il_priv *il) +{ + __le32 *image; + u32 len; + int rc = 0; + + /* Try bootstrap */ + image = (__le32 *) il->ucode_boot.v_addr; + len = il->ucode_boot.len; + rc = il3945_verify_inst_sparse(il, image, len); + if (rc == 0) { + D_INFO("Bootstrap uCode is good in inst SRAM\n"); + return 0; + } + + /* Try initialize */ + image = (__le32 *) il->ucode_init.v_addr; + len = il->ucode_init.len; + rc = il3945_verify_inst_sparse(il, image, len); + if (rc == 0) { + D_INFO("Initialize uCode is good in inst SRAM\n"); + return 0; + } + + /* Try runtime/protocol */ + image = (__le32 *) il->ucode_code.v_addr; + len = il->ucode_code.len; + rc = il3945_verify_inst_sparse(il, image, len); + if (rc == 0) { + D_INFO("Runtime uCode is good in inst SRAM\n"); + return 0; + } + + IL_ERR("NO VALID UCODE IMAGE IN INSTRUCTION SRAM!!\n"); + + /* Since nothing seems to match, show first several data entries in + * instruction SRAM, so maybe visual inspection will give a clue. + * Selection of bootstrap image (vs. other images) is arbitrary. */ + image = (__le32 *) il->ucode_boot.v_addr; + len = il->ucode_boot.len; + rc = il3945_verify_inst_full(il, image, len); + + return rc; +} + +static void +il3945_nic_start(struct il_priv *il) +{ + /* Remove all resets to allow NIC to operate */ + _il_wr(il, CSR_RESET, 0); +} + +#define IL3945_UCODE_GET(item) \ +static u32 il3945_ucode_get_##item(const struct il_ucode_header *ucode)\ +{ \ + return le32_to_cpu(ucode->v1.item); \ +} + +static u32 +il3945_ucode_get_header_size(u32 api_ver) +{ + return 24; +} + +static u8 * +il3945_ucode_get_data(const struct il_ucode_header *ucode) +{ + return (u8 *) ucode->v1.data; +} + +IL3945_UCODE_GET(inst_size); +IL3945_UCODE_GET(data_size); +IL3945_UCODE_GET(init_size); +IL3945_UCODE_GET(init_data_size); +IL3945_UCODE_GET(boot_size); + +/** + * il3945_read_ucode - Read uCode images from disk file. + * + * Copy into buffers for card to fetch via bus-mastering + */ +static int +il3945_read_ucode(struct il_priv *il) +{ + const struct il_ucode_header *ucode; + int ret = -EINVAL, idx; + const struct firmware *ucode_raw; + /* firmware file name contains uCode/driver compatibility version */ + const char *name_pre = il->cfg->fw_name_pre; + const unsigned int api_max = il->cfg->ucode_api_max; + const unsigned int api_min = il->cfg->ucode_api_min; + char buf[25]; + u8 *src; + size_t len; + u32 api_ver, inst_size, data_size, init_size, init_data_size, boot_size; + + /* Ask kernel firmware_class module to get the boot firmware off disk. + * request_firmware() is synchronous, file is in memory on return. */ + for (idx = api_max; idx >= api_min; idx--) { + sprintf(buf, "%s%u%s", name_pre, idx, ".ucode"); + ret = request_firmware(&ucode_raw, buf, &il->pci_dev->dev); + if (ret < 0) { + IL_ERR("%s firmware file req failed: %d\n", buf, ret); + if (ret == -ENOENT) + continue; + else + goto error; + } else { + if (idx < api_max) + IL_ERR("Loaded firmware %s, " + "which is deprecated. " + " Please use API v%u instead.\n", buf, + api_max); + D_INFO("Got firmware '%s' file " + "(%zd bytes) from disk\n", buf, ucode_raw->size); + break; + } + } + + if (ret < 0) + goto error; + + /* Make sure that we got at least our header! */ + if (ucode_raw->size < il3945_ucode_get_header_size(1)) { + IL_ERR("File size way too small!\n"); + ret = -EINVAL; + goto err_release; + } + + /* Data from ucode file: header followed by uCode images */ + ucode = (struct il_ucode_header *)ucode_raw->data; + + il->ucode_ver = le32_to_cpu(ucode->ver); + api_ver = IL_UCODE_API(il->ucode_ver); + inst_size = il3945_ucode_get_inst_size(ucode); + data_size = il3945_ucode_get_data_size(ucode); + init_size = il3945_ucode_get_init_size(ucode); + init_data_size = il3945_ucode_get_init_data_size(ucode); + boot_size = il3945_ucode_get_boot_size(ucode); + src = il3945_ucode_get_data(ucode); + + /* api_ver should match the api version forming part of the + * firmware filename ... but we don't check for that and only rely + * on the API version read from firmware header from here on forward */ + + if (api_ver < api_min || api_ver > api_max) { + IL_ERR("Driver unable to support your firmware API. " + "Driver supports v%u, firmware is v%u.\n", api_max, + api_ver); + il->ucode_ver = 0; + ret = -EINVAL; + goto err_release; + } + if (api_ver != api_max) + IL_ERR("Firmware has old API version. Expected %u, " + "got %u. New firmware can be obtained " + "from http://www.intellinuxwireless.org.\n", api_max, + api_ver); + + IL_INFO("loaded firmware version %u.%u.%u.%u\n", + IL_UCODE_MAJOR(il->ucode_ver), IL_UCODE_MINOR(il->ucode_ver), + IL_UCODE_API(il->ucode_ver), IL_UCODE_SERIAL(il->ucode_ver)); + + snprintf(il->hw->wiphy->fw_version, sizeof(il->hw->wiphy->fw_version), + "%u.%u.%u.%u", IL_UCODE_MAJOR(il->ucode_ver), + IL_UCODE_MINOR(il->ucode_ver), IL_UCODE_API(il->ucode_ver), + IL_UCODE_SERIAL(il->ucode_ver)); + + D_INFO("f/w package hdr ucode version raw = 0x%x\n", il->ucode_ver); + D_INFO("f/w package hdr runtime inst size = %u\n", inst_size); + D_INFO("f/w package hdr runtime data size = %u\n", data_size); + D_INFO("f/w package hdr init inst size = %u\n", init_size); + D_INFO("f/w package hdr init data size = %u\n", init_data_size); + D_INFO("f/w package hdr boot inst size = %u\n", boot_size); + + /* Verify size of file vs. image size info in file's header */ + if (ucode_raw->size != + il3945_ucode_get_header_size(api_ver) + inst_size + data_size + + init_size + init_data_size + boot_size) { + + D_INFO("uCode file size %zd does not match expected size\n", + ucode_raw->size); + ret = -EINVAL; + goto err_release; + } + + /* Verify that uCode images will fit in card's SRAM */ + if (inst_size > IL39_MAX_INST_SIZE) { + D_INFO("uCode instr len %d too large to fit in\n", inst_size); + ret = -EINVAL; + goto err_release; + } + + if (data_size > IL39_MAX_DATA_SIZE) { + D_INFO("uCode data len %d too large to fit in\n", data_size); + ret = -EINVAL; + goto err_release; + } + if (init_size > IL39_MAX_INST_SIZE) { + D_INFO("uCode init instr len %d too large to fit in\n", + init_size); + ret = -EINVAL; + goto err_release; + } + if (init_data_size > IL39_MAX_DATA_SIZE) { + D_INFO("uCode init data len %d too large to fit in\n", + init_data_size); + ret = -EINVAL; + goto err_release; + } + if (boot_size > IL39_MAX_BSM_SIZE) { + D_INFO("uCode boot instr len %d too large to fit in\n", + boot_size); + ret = -EINVAL; + goto err_release; + } + + /* Allocate ucode buffers for card's bus-master loading ... */ + + /* Runtime instructions and 2 copies of data: + * 1) unmodified from disk + * 2) backup cache for save/restore during power-downs */ + il->ucode_code.len = inst_size; + il_alloc_fw_desc(il->pci_dev, &il->ucode_code); + + il->ucode_data.len = data_size; + il_alloc_fw_desc(il->pci_dev, &il->ucode_data); + + il->ucode_data_backup.len = data_size; + il_alloc_fw_desc(il->pci_dev, &il->ucode_data_backup); + + if (!il->ucode_code.v_addr || !il->ucode_data.v_addr || + !il->ucode_data_backup.v_addr) + goto err_pci_alloc; + + /* Initialization instructions and data */ + if (init_size && init_data_size) { + il->ucode_init.len = init_size; + il_alloc_fw_desc(il->pci_dev, &il->ucode_init); + + il->ucode_init_data.len = init_data_size; + il_alloc_fw_desc(il->pci_dev, &il->ucode_init_data); + + if (!il->ucode_init.v_addr || !il->ucode_init_data.v_addr) + goto err_pci_alloc; + } + + /* Bootstrap (instructions only, no data) */ + if (boot_size) { + il->ucode_boot.len = boot_size; + il_alloc_fw_desc(il->pci_dev, &il->ucode_boot); + + if (!il->ucode_boot.v_addr) + goto err_pci_alloc; + } + + /* Copy images into buffers for card's bus-master reads ... */ + + /* Runtime instructions (first block of data in file) */ + len = inst_size; + D_INFO("Copying (but not loading) uCode instr len %zd\n", len); + memcpy(il->ucode_code.v_addr, src, len); + src += len; + + D_INFO("uCode instr buf vaddr = 0x%p, paddr = 0x%08x\n", + il->ucode_code.v_addr, (u32) il->ucode_code.p_addr); + + /* Runtime data (2nd block) + * NOTE: Copy into backup buffer will be done in il3945_up() */ + len = data_size; + D_INFO("Copying (but not loading) uCode data len %zd\n", len); + memcpy(il->ucode_data.v_addr, src, len); + memcpy(il->ucode_data_backup.v_addr, src, len); + src += len; + + /* Initialization instructions (3rd block) */ + if (init_size) { + len = init_size; + D_INFO("Copying (but not loading) init instr len %zd\n", len); + memcpy(il->ucode_init.v_addr, src, len); + src += len; + } + + /* Initialization data (4th block) */ + if (init_data_size) { + len = init_data_size; + D_INFO("Copying (but not loading) init data len %zd\n", len); + memcpy(il->ucode_init_data.v_addr, src, len); + src += len; + } + + /* Bootstrap instructions (5th block) */ + len = boot_size; + D_INFO("Copying (but not loading) boot instr len %zd\n", len); + memcpy(il->ucode_boot.v_addr, src, len); + + /* We have our copies now, allow OS release its copies */ + release_firmware(ucode_raw); + return 0; + +err_pci_alloc: + IL_ERR("failed to allocate pci memory\n"); + ret = -ENOMEM; + il3945_dealloc_ucode_pci(il); + +err_release: + release_firmware(ucode_raw); + +error: + return ret; +} + +/** + * il3945_set_ucode_ptrs - Set uCode address location + * + * Tell initialization uCode where to find runtime uCode. + * + * BSM registers initially contain pointers to initialization uCode. + * We need to replace them to load runtime uCode inst and data, + * and to save runtime data when powering down. + */ +static int +il3945_set_ucode_ptrs(struct il_priv *il) +{ + dma_addr_t pinst; + dma_addr_t pdata; + + /* bits 31:0 for 3945 */ + pinst = il->ucode_code.p_addr; + pdata = il->ucode_data_backup.p_addr; + + /* Tell bootstrap uCode where to find image to load */ + il_wr_prph(il, BSM_DRAM_INST_PTR_REG, pinst); + il_wr_prph(il, BSM_DRAM_DATA_PTR_REG, pdata); + il_wr_prph(il, BSM_DRAM_DATA_BYTECOUNT_REG, il->ucode_data.len); + + /* Inst byte count must be last to set up, bit 31 signals uCode + * that all new ptr/size info is in place */ + il_wr_prph(il, BSM_DRAM_INST_BYTECOUNT_REG, + il->ucode_code.len | BSM_DRAM_INST_LOAD); + + D_INFO("Runtime uCode pointers are set.\n"); + + return 0; +} + +/** + * il3945_init_alive_start - Called after N_ALIVE notification received + * + * Called after N_ALIVE notification received from "initialize" uCode. + * + * Tell "initialize" uCode to go ahead and load the runtime uCode. + */ +static void +il3945_init_alive_start(struct il_priv *il) +{ + /* Check alive response for "valid" sign from uCode */ + if (il->card_alive_init.is_valid != UCODE_VALID_OK) { + /* We had an error bringing up the hardware, so take it + * all the way back down so we can try again */ + D_INFO("Initialize Alive failed.\n"); + goto restart; + } + + /* Bootstrap uCode has loaded initialize uCode ... verify inst image. + * This is a paranoid check, because we would not have gotten the + * "initialize" alive if code weren't properly loaded. */ + if (il3945_verify_ucode(il)) { + /* Runtime instruction load was bad; + * take it all the way back down so we can try again */ + D_INFO("Bad \"initialize\" uCode load.\n"); + goto restart; + } + + /* Send pointers to protocol/runtime uCode image ... init code will + * load and launch runtime uCode, which will send us another "Alive" + * notification. */ + D_INFO("Initialization Alive received.\n"); + if (il3945_set_ucode_ptrs(il)) { + /* Runtime instruction load won't happen; + * take it all the way back down so we can try again */ + D_INFO("Couldn't set up uCode pointers.\n"); + goto restart; + } + return; + +restart: + queue_work(il->workqueue, &il->restart); +} + +/** + * il3945_alive_start - called after N_ALIVE notification received + * from protocol/runtime uCode (initialization uCode's + * Alive gets handled by il3945_init_alive_start()). + */ +static void +il3945_alive_start(struct il_priv *il) +{ + int thermal_spin = 0; + u32 rfkill; + + D_INFO("Runtime Alive received.\n"); + + if (il->card_alive.is_valid != UCODE_VALID_OK) { + /* We had an error bringing up the hardware, so take it + * all the way back down so we can try again */ + D_INFO("Alive failed.\n"); + goto restart; + } + + /* Initialize uCode has loaded Runtime uCode ... verify inst image. + * This is a paranoid check, because we would not have gotten the + * "runtime" alive if code weren't properly loaded. */ + if (il3945_verify_ucode(il)) { + /* Runtime instruction load was bad; + * take it all the way back down so we can try again */ + D_INFO("Bad runtime uCode load.\n"); + goto restart; + } + + rfkill = il_rd_prph(il, APMG_RFKILL_REG); + D_INFO("RFKILL status: 0x%x\n", rfkill); + + if (rfkill & 0x1) { + clear_bit(S_RFKILL, &il->status); + /* if RFKILL is not on, then wait for thermal + * sensor in adapter to kick in */ + while (il3945_hw_get_temperature(il) == 0) { + thermal_spin++; + udelay(10); + } + + if (thermal_spin) + D_INFO("Thermal calibration took %dus\n", + thermal_spin * 10); + } else + set_bit(S_RFKILL, &il->status); + + /* After the ALIVE response, we can send commands to 3945 uCode */ + set_bit(S_ALIVE, &il->status); + + /* Enable watchdog to monitor the driver tx queues */ + il_setup_watchdog(il); + + if (il_is_rfkill(il)) + return; + + ieee80211_wake_queues(il->hw); + + il->active_rate = RATES_MASK_3945; + + il_power_update_mode(il, true); + + if (il_is_associated(il)) { + struct il3945_rxon_cmd *active_rxon = + (struct il3945_rxon_cmd *)(&il->active); + + il->staging.filter_flags |= RXON_FILTER_ASSOC_MSK; + active_rxon->filter_flags &= ~RXON_FILTER_ASSOC_MSK; + } else { + /* Initialize our rx_config data */ + il_connection_init_rx_config(il); + } + + /* Configure Bluetooth device coexistence support */ + il_send_bt_config(il); + + set_bit(S_READY, &il->status); + + /* Configure the adapter for unassociated operation */ + il3945_commit_rxon(il); + + il3945_reg_txpower_periodic(il); + + D_INFO("ALIVE processing complete.\n"); + wake_up(&il->wait_command_queue); + + return; + +restart: + queue_work(il->workqueue, &il->restart); +} + +static void il3945_cancel_deferred_work(struct il_priv *il); + +static void +__il3945_down(struct il_priv *il) +{ + unsigned long flags; + int exit_pending; + + D_INFO(DRV_NAME " is going down\n"); + + il_scan_cancel_timeout(il, 200); + + exit_pending = test_and_set_bit(S_EXIT_PENDING, &il->status); + + /* Stop TX queues watchdog. We need to have S_EXIT_PENDING bit set + * to prevent rearm timer */ + del_timer_sync(&il->watchdog); + + /* Station information will now be cleared in device */ + il_clear_ucode_stations(il); + il_dealloc_bcast_stations(il); + il_clear_driver_stations(il); + + /* Unblock any waiting calls */ + wake_up_all(&il->wait_command_queue); + + /* Wipe out the EXIT_PENDING status bit if we are not actually + * exiting the module */ + if (!exit_pending) + clear_bit(S_EXIT_PENDING, &il->status); + + /* stop and reset the on-board processor */ + _il_wr(il, CSR_RESET, CSR_RESET_REG_FLAG_NEVO_RESET); + + /* tell the device to stop sending interrupts */ + spin_lock_irqsave(&il->lock, flags); + il_disable_interrupts(il); + spin_unlock_irqrestore(&il->lock, flags); + il3945_synchronize_irq(il); + + if (il->mac80211_registered) + ieee80211_stop_queues(il->hw); + + /* If we have not previously called il3945_init() then + * clear all bits but the RF Kill bits and return */ + if (!il_is_init(il)) { + il->status = + test_bit(S_RFKILL, &il->status) << S_RFKILL | + test_bit(S_GEO_CONFIGURED, &il->status) << S_GEO_CONFIGURED | + test_bit(S_EXIT_PENDING, &il->status) << S_EXIT_PENDING; + goto exit; + } + + /* ...otherwise clear out all the status bits but the RF Kill + * bit and continue taking the NIC down. */ + il->status &= + test_bit(S_RFKILL, &il->status) << S_RFKILL | + test_bit(S_GEO_CONFIGURED, &il->status) << S_GEO_CONFIGURED | + test_bit(S_FW_ERROR, &il->status) << S_FW_ERROR | + test_bit(S_EXIT_PENDING, &il->status) << S_EXIT_PENDING; + + /* + * We disabled and synchronized interrupt, and priv->mutex is taken, so + * here is the only thread which will program device registers, but + * still have lockdep assertions, so we are taking reg_lock. + */ + spin_lock_irq(&il->reg_lock); + /* FIXME: il_grab_nic_access if rfkill is off ? */ + + il3945_hw_txq_ctx_stop(il); + il3945_hw_rxq_stop(il); + /* Power-down device's busmaster DMA clocks */ + _il_wr_prph(il, APMG_CLK_DIS_REG, APMG_CLK_VAL_DMA_CLK_RQT); + udelay(5); + /* Stop the device, and put it in low power state */ + _il_apm_stop(il); + + spin_unlock_irq(&il->reg_lock); + + il3945_hw_txq_ctx_free(il); +exit: + memset(&il->card_alive, 0, sizeof(struct il_alive_resp)); + + if (il->beacon_skb) + dev_kfree_skb(il->beacon_skb); + il->beacon_skb = NULL; + + /* clear out any free frames */ + il3945_clear_free_frames(il); +} + +static void +il3945_down(struct il_priv *il) +{ + mutex_lock(&il->mutex); + __il3945_down(il); + mutex_unlock(&il->mutex); + + il3945_cancel_deferred_work(il); +} + +#define MAX_HW_RESTARTS 5 + +static int +il3945_alloc_bcast_station(struct il_priv *il) +{ + unsigned long flags; + u8 sta_id; + + spin_lock_irqsave(&il->sta_lock, flags); + sta_id = il_prep_station(il, il_bcast_addr, false, NULL); + if (sta_id == IL_INVALID_STATION) { + IL_ERR("Unable to prepare broadcast station\n"); + spin_unlock_irqrestore(&il->sta_lock, flags); + + return -EINVAL; + } + + il->stations[sta_id].used |= IL_STA_DRIVER_ACTIVE; + il->stations[sta_id].used |= IL_STA_BCAST; + spin_unlock_irqrestore(&il->sta_lock, flags); + + return 0; +} + +static int +__il3945_up(struct il_priv *il) +{ + int rc, i; + + rc = il3945_alloc_bcast_station(il); + if (rc) + return rc; + + if (test_bit(S_EXIT_PENDING, &il->status)) { + IL_WARN("Exit pending; will not bring the NIC up\n"); + return -EIO; + } + + if (!il->ucode_data_backup.v_addr || !il->ucode_data.v_addr) { + IL_ERR("ucode not available for device bring up\n"); + return -EIO; + } + + /* If platform's RF_KILL switch is NOT set to KILL */ + if (_il_rd(il, CSR_GP_CNTRL) & CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW) + clear_bit(S_RFKILL, &il->status); + else { + set_bit(S_RFKILL, &il->status); + IL_WARN("Radio disabled by HW RF Kill switch\n"); + return -ENODEV; + } + + _il_wr(il, CSR_INT, 0xFFFFFFFF); + + rc = il3945_hw_nic_init(il); + if (rc) { + IL_ERR("Unable to int nic\n"); + return rc; + } + + /* make sure rfkill handshake bits are cleared */ + _il_wr(il, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + _il_wr(il, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); + + /* clear (again), then enable host interrupts */ + _il_wr(il, CSR_INT, 0xFFFFFFFF); + il_enable_interrupts(il); + + /* really make sure rfkill handshake bits are cleared */ + _il_wr(il, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + _il_wr(il, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + + /* Copy original ucode data image from disk into backup cache. + * This will be used to initialize the on-board processor's + * data SRAM for a clean start when the runtime program first loads. */ + memcpy(il->ucode_data_backup.v_addr, il->ucode_data.v_addr, + il->ucode_data.len); + + /* We return success when we resume from suspend and rf_kill is on. */ + if (test_bit(S_RFKILL, &il->status)) + return 0; + + for (i = 0; i < MAX_HW_RESTARTS; i++) { + + /* load bootstrap state machine, + * load bootstrap program into processor's memory, + * prepare to load the "initialize" uCode */ + rc = il->ops->load_ucode(il); + + if (rc) { + IL_ERR("Unable to set up bootstrap uCode: %d\n", rc); + continue; + } + + /* start card; "initialize" will load runtime ucode */ + il3945_nic_start(il); + + D_INFO(DRV_NAME " is coming up\n"); + + return 0; + } + + set_bit(S_EXIT_PENDING, &il->status); + __il3945_down(il); + clear_bit(S_EXIT_PENDING, &il->status); + + /* tried to restart and config the device for as long as our + * patience could withstand */ + IL_ERR("Unable to initialize device after %d attempts.\n", i); + return -EIO; +} + +/***************************************************************************** + * + * Workqueue callbacks + * + *****************************************************************************/ + +static void +il3945_bg_init_alive_start(struct work_struct *data) +{ + struct il_priv *il = + container_of(data, struct il_priv, init_alive_start.work); + + mutex_lock(&il->mutex); + if (test_bit(S_EXIT_PENDING, &il->status)) + goto out; + + il3945_init_alive_start(il); +out: + mutex_unlock(&il->mutex); +} + +static void +il3945_bg_alive_start(struct work_struct *data) +{ + struct il_priv *il = + container_of(data, struct il_priv, alive_start.work); + + mutex_lock(&il->mutex); + if (test_bit(S_EXIT_PENDING, &il->status) || il->txq == NULL) + goto out; + + il3945_alive_start(il); +out: + mutex_unlock(&il->mutex); +} + +/* + * 3945 cannot interrupt driver when hardware rf kill switch toggles; + * driver must poll CSR_GP_CNTRL_REG register for change. This register + * *is* readable even when device has been SW_RESET into low power mode + * (e.g. during RF KILL). + */ +static void +il3945_rfkill_poll(struct work_struct *data) +{ + struct il_priv *il = + container_of(data, struct il_priv, _3945.rfkill_poll.work); + bool old_rfkill = test_bit(S_RFKILL, &il->status); + bool new_rfkill = + !(_il_rd(il, CSR_GP_CNTRL) & CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW); + + if (new_rfkill != old_rfkill) { + if (new_rfkill) + set_bit(S_RFKILL, &il->status); + else + clear_bit(S_RFKILL, &il->status); + + wiphy_rfkill_set_hw_state(il->hw->wiphy, new_rfkill); + + D_RF_KILL("RF_KILL bit toggled to %s.\n", + new_rfkill ? "disable radio" : "enable radio"); + } + + /* Keep this running, even if radio now enabled. This will be + * cancelled in mac_start() if system decides to start again */ + queue_delayed_work(il->workqueue, &il->_3945.rfkill_poll, + round_jiffies_relative(2 * HZ)); + +} + +int +il3945_request_scan(struct il_priv *il, struct ieee80211_vif *vif) +{ + struct il_host_cmd cmd = { + .id = C_SCAN, + .len = sizeof(struct il3945_scan_cmd), + .flags = CMD_SIZE_HUGE, + }; + struct il3945_scan_cmd *scan; + u8 n_probes = 0; + enum ieee80211_band band; + bool is_active = false; + int ret; + u16 len; + + lockdep_assert_held(&il->mutex); + + if (!il->scan_cmd) { + il->scan_cmd = + kmalloc(sizeof(struct il3945_scan_cmd) + IL_MAX_SCAN_SIZE, + GFP_KERNEL); + if (!il->scan_cmd) { + D_SCAN("Fail to allocate scan memory\n"); + return -ENOMEM; + } + } + scan = il->scan_cmd; + memset(scan, 0, sizeof(struct il3945_scan_cmd) + IL_MAX_SCAN_SIZE); + + scan->quiet_plcp_th = IL_PLCP_QUIET_THRESH; + scan->quiet_time = IL_ACTIVE_QUIET_TIME; + + if (il_is_associated(il)) { + u16 interval; + u32 extra; + u32 suspend_time = 100; + u32 scan_suspend_time = 100; + + D_INFO("Scanning while associated...\n"); + + interval = vif->bss_conf.beacon_int; + + scan->suspend_time = 0; + scan->max_out_time = cpu_to_le32(200 * 1024); + if (!interval) + interval = suspend_time; + /* + * suspend time format: + * 0-19: beacon interval in usec (time before exec.) + * 20-23: 0 + * 24-31: number of beacons (suspend between channels) + */ + + extra = (suspend_time / interval) << 24; + scan_suspend_time = + 0xFF0FFFFF & (extra | ((suspend_time % interval) * 1024)); + + scan->suspend_time = cpu_to_le32(scan_suspend_time); + D_SCAN("suspend_time 0x%X beacon interval %d\n", + scan_suspend_time, interval); + } + + if (il->scan_request->n_ssids) { + int i, p = 0; + D_SCAN("Kicking off active scan\n"); + for (i = 0; i < il->scan_request->n_ssids; i++) { + /* always does wildcard anyway */ + if (!il->scan_request->ssids[i].ssid_len) + continue; + scan->direct_scan[p].id = WLAN_EID_SSID; + scan->direct_scan[p].len = + il->scan_request->ssids[i].ssid_len; + memcpy(scan->direct_scan[p].ssid, + il->scan_request->ssids[i].ssid, + il->scan_request->ssids[i].ssid_len); + n_probes++; + p++; + } + is_active = true; + } else + D_SCAN("Kicking off passive scan.\n"); + + /* We don't build a direct scan probe request; the uCode will do + * that based on the direct_mask added to each channel entry */ + scan->tx_cmd.tx_flags = TX_CMD_FLG_SEQ_CTL_MSK; + scan->tx_cmd.sta_id = il->hw_params.bcast_id; + scan->tx_cmd.stop_time.life_time = TX_CMD_LIFE_TIME_INFINITE; + + /* flags + rate selection */ + + switch (il->scan_band) { + case IEEE80211_BAND_2GHZ: + scan->flags = RXON_FLG_BAND_24G_MSK | RXON_FLG_AUTO_DETECT_MSK; + scan->tx_cmd.rate = RATE_1M_PLCP; + band = IEEE80211_BAND_2GHZ; + break; + case IEEE80211_BAND_5GHZ: + scan->tx_cmd.rate = RATE_6M_PLCP; + band = IEEE80211_BAND_5GHZ; + break; + default: + IL_WARN("Invalid scan band\n"); + return -EIO; + } + + /* + * If active scaning is requested but a certain channel is marked + * passive, we can do active scanning if we detect transmissions. For + * passive only scanning disable switching to active on any channel. + */ + scan->good_CRC_th = + is_active ? IL_GOOD_CRC_TH_DEFAULT : IL_GOOD_CRC_TH_NEVER; + + len = + il_fill_probe_req(il, (struct ieee80211_mgmt *)scan->data, + vif->addr, il->scan_request->ie, + il->scan_request->ie_len, + IL_MAX_SCAN_SIZE - sizeof(*scan)); + scan->tx_cmd.len = cpu_to_le16(len); + + /* select Rx antennas */ + scan->flags |= il3945_get_antenna_flags(il); + + scan->channel_count = + il3945_get_channels_for_scan(il, band, is_active, n_probes, + (void *)&scan->data[len], vif); + if (scan->channel_count == 0) { + D_SCAN("channel count %d\n", scan->channel_count); + return -EIO; + } + + cmd.len += + le16_to_cpu(scan->tx_cmd.len) + + scan->channel_count * sizeof(struct il3945_scan_channel); + cmd.data = scan; + scan->len = cpu_to_le16(cmd.len); + + set_bit(S_SCAN_HW, &il->status); + ret = il_send_cmd_sync(il, &cmd); + if (ret) + clear_bit(S_SCAN_HW, &il->status); + return ret; +} + +void +il3945_post_scan(struct il_priv *il) +{ + /* + * Since setting the RXON may have been deferred while + * performing the scan, fire one off if needed + */ + if (memcmp(&il->staging, &il->active, sizeof(il->staging))) + il3945_commit_rxon(il); +} + +static void +il3945_bg_restart(struct work_struct *data) +{ + struct il_priv *il = container_of(data, struct il_priv, restart); + + if (test_bit(S_EXIT_PENDING, &il->status)) + return; + + if (test_and_clear_bit(S_FW_ERROR, &il->status)) { + mutex_lock(&il->mutex); + il->is_open = 0; + mutex_unlock(&il->mutex); + il3945_down(il); + ieee80211_restart_hw(il->hw); + } else { + il3945_down(il); + + mutex_lock(&il->mutex); + if (test_bit(S_EXIT_PENDING, &il->status)) { + mutex_unlock(&il->mutex); + return; + } + + __il3945_up(il); + mutex_unlock(&il->mutex); + } +} + +static void +il3945_bg_rx_replenish(struct work_struct *data) +{ + struct il_priv *il = container_of(data, struct il_priv, rx_replenish); + + mutex_lock(&il->mutex); + if (test_bit(S_EXIT_PENDING, &il->status)) + goto out; + + il3945_rx_replenish(il); +out: + mutex_unlock(&il->mutex); +} + +void +il3945_post_associate(struct il_priv *il) +{ + int rc = 0; + struct ieee80211_conf *conf = NULL; + + if (!il->vif || !il->is_open) + return; + + D_ASSOC("Associated as %d to: %pM\n", il->vif->bss_conf.aid, + il->active.bssid_addr); + + if (test_bit(S_EXIT_PENDING, &il->status)) + return; + + il_scan_cancel_timeout(il, 200); + + conf = &il->hw->conf; + + il->staging.filter_flags &= ~RXON_FILTER_ASSOC_MSK; + il3945_commit_rxon(il); + + rc = il_send_rxon_timing(il); + if (rc) + IL_WARN("C_RXON_TIMING failed - " "Attempting to continue.\n"); + + il->staging.filter_flags |= RXON_FILTER_ASSOC_MSK; + + il->staging.assoc_id = cpu_to_le16(il->vif->bss_conf.aid); + + D_ASSOC("assoc id %d beacon interval %d\n", il->vif->bss_conf.aid, + il->vif->bss_conf.beacon_int); + + if (il->vif->bss_conf.use_short_preamble) + il->staging.flags |= RXON_FLG_SHORT_PREAMBLE_MSK; + else + il->staging.flags &= ~RXON_FLG_SHORT_PREAMBLE_MSK; + + if (il->staging.flags & RXON_FLG_BAND_24G_MSK) { + if (il->vif->bss_conf.use_short_slot) + il->staging.flags |= RXON_FLG_SHORT_SLOT_MSK; + else + il->staging.flags &= ~RXON_FLG_SHORT_SLOT_MSK; + } + + il3945_commit_rxon(il); + + switch (il->vif->type) { + case NL80211_IFTYPE_STATION: + il3945_rate_scale_init(il->hw, IL_AP_ID); + break; + case NL80211_IFTYPE_ADHOC: + il3945_send_beacon_cmd(il); + break; + default: + IL_ERR("%s Should not be called in %d mode\n", __func__, + il->vif->type); + break; + } +} + +/***************************************************************************** + * + * mac80211 entry point functions + * + *****************************************************************************/ + +#define UCODE_READY_TIMEOUT (2 * HZ) + +static int +il3945_mac_start(struct ieee80211_hw *hw) +{ + struct il_priv *il = hw->priv; + int ret; + + /* we should be verifying the device is ready to be opened */ + mutex_lock(&il->mutex); + D_MAC80211("enter\n"); + + /* fetch ucode file from disk, alloc and copy to bus-master buffers ... + * ucode filename and max sizes are card-specific. */ + + if (!il->ucode_code.len) { + ret = il3945_read_ucode(il); + if (ret) { + IL_ERR("Could not read microcode: %d\n", ret); + mutex_unlock(&il->mutex); + goto out_release_irq; + } + } + + ret = __il3945_up(il); + + mutex_unlock(&il->mutex); + + if (ret) + goto out_release_irq; + + D_INFO("Start UP work.\n"); + + /* Wait for START_ALIVE from ucode. Otherwise callbacks from + * mac80211 will not be run successfully. */ + ret = wait_event_timeout(il->wait_command_queue, + test_bit(S_READY, &il->status), + UCODE_READY_TIMEOUT); + if (!ret) { + if (!test_bit(S_READY, &il->status)) { + IL_ERR("Wait for START_ALIVE timeout after %dms.\n", + jiffies_to_msecs(UCODE_READY_TIMEOUT)); + ret = -ETIMEDOUT; + goto out_release_irq; + } + } + + /* ucode is running and will send rfkill notifications, + * no need to poll the killswitch state anymore */ + cancel_delayed_work(&il->_3945.rfkill_poll); + + il->is_open = 1; + D_MAC80211("leave\n"); + return 0; + +out_release_irq: + il->is_open = 0; + D_MAC80211("leave - failed\n"); + return ret; +} + +static void +il3945_mac_stop(struct ieee80211_hw *hw) +{ + struct il_priv *il = hw->priv; + + D_MAC80211("enter\n"); + + if (!il->is_open) { + D_MAC80211("leave - skip\n"); + return; + } + + il->is_open = 0; + + il3945_down(il); + + flush_workqueue(il->workqueue); + + /* start polling the killswitch state again */ + queue_delayed_work(il->workqueue, &il->_3945.rfkill_poll, + round_jiffies_relative(2 * HZ)); + + D_MAC80211("leave\n"); +} + +static void +il3945_mac_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct il_priv *il = hw->priv; + + D_MAC80211("enter\n"); + + D_TX("dev->xmit(%d bytes) at rate 0x%02x\n", skb->len, + ieee80211_get_tx_rate(hw, IEEE80211_SKB_CB(skb))->bitrate); + + if (il3945_tx_skb(il, control->sta, skb)) + dev_kfree_skb_any(skb); + + D_MAC80211("leave\n"); +} + +void +il3945_config_ap(struct il_priv *il) +{ + struct ieee80211_vif *vif = il->vif; + int rc = 0; + + if (test_bit(S_EXIT_PENDING, &il->status)) + return; + + /* The following should be done only at AP bring up */ + if (!(il_is_associated(il))) { + + /* RXON - unassoc (to set timing command) */ + il->staging.filter_flags &= ~RXON_FILTER_ASSOC_MSK; + il3945_commit_rxon(il); + + /* RXON Timing */ + rc = il_send_rxon_timing(il); + if (rc) + IL_WARN("C_RXON_TIMING failed - " + "Attempting to continue.\n"); + + il->staging.assoc_id = 0; + + if (vif->bss_conf.use_short_preamble) + il->staging.flags |= RXON_FLG_SHORT_PREAMBLE_MSK; + else + il->staging.flags &= ~RXON_FLG_SHORT_PREAMBLE_MSK; + + if (il->staging.flags & RXON_FLG_BAND_24G_MSK) { + if (vif->bss_conf.use_short_slot) + il->staging.flags |= RXON_FLG_SHORT_SLOT_MSK; + else + il->staging.flags &= ~RXON_FLG_SHORT_SLOT_MSK; + } + /* restore RXON assoc */ + il->staging.filter_flags |= RXON_FILTER_ASSOC_MSK; + il3945_commit_rxon(il); + } + il3945_send_beacon_cmd(il); +} + +static int +il3945_mac_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct il_priv *il = hw->priv; + int ret = 0; + u8 sta_id = IL_INVALID_STATION; + u8 static_key; + + D_MAC80211("enter\n"); + + if (il3945_mod_params.sw_crypto) { + D_MAC80211("leave - hwcrypto disabled\n"); + return -EOPNOTSUPP; + } + + /* + * To support IBSS RSN, don't program group keys in IBSS, the + * hardware will then not attempt to decrypt the frames. + */ + if (vif->type == NL80211_IFTYPE_ADHOC && + !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) { + D_MAC80211("leave - IBSS RSN\n"); + return -EOPNOTSUPP; + } + + static_key = !il_is_associated(il); + + if (!static_key) { + sta_id = il_sta_id_or_broadcast(il, sta); + if (sta_id == IL_INVALID_STATION) { + D_MAC80211("leave - station not found\n"); + return -EINVAL; + } + } + + mutex_lock(&il->mutex); + il_scan_cancel_timeout(il, 100); + + switch (cmd) { + case SET_KEY: + if (static_key) + ret = il3945_set_static_key(il, key); + else + ret = il3945_set_dynamic_key(il, key, sta_id); + D_MAC80211("enable hwcrypto key\n"); + break; + case DISABLE_KEY: + if (static_key) + ret = il3945_remove_static_key(il); + else + ret = il3945_clear_sta_key_info(il, sta_id); + D_MAC80211("disable hwcrypto key\n"); + break; + default: + ret = -EINVAL; + } + + D_MAC80211("leave ret %d\n", ret); + mutex_unlock(&il->mutex); + + return ret; +} + +static int +il3945_mac_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct il_priv *il = hw->priv; + struct il3945_sta_priv *sta_priv = (void *)sta->drv_priv; + int ret; + bool is_ap = vif->type == NL80211_IFTYPE_STATION; + u8 sta_id; + + mutex_lock(&il->mutex); + D_INFO("station %pM\n", sta->addr); + sta_priv->common.sta_id = IL_INVALID_STATION; + + ret = il_add_station_common(il, sta->addr, is_ap, sta, &sta_id); + if (ret) { + IL_ERR("Unable to add station %pM (%d)\n", sta->addr, ret); + /* Should we return success if return code is EEXIST ? */ + mutex_unlock(&il->mutex); + return ret; + } + + sta_priv->common.sta_id = sta_id; + + /* Initialize rate scaling */ + D_INFO("Initializing rate scaling for station %pM\n", sta->addr); + il3945_rs_rate_init(il, sta, sta_id); + mutex_unlock(&il->mutex); + + return 0; +} + +static void +il3945_configure_filter(struct ieee80211_hw *hw, unsigned int changed_flags, + unsigned int *total_flags, u64 multicast) +{ + struct il_priv *il = hw->priv; + __le32 filter_or = 0, filter_nand = 0; + +#define CHK(test, flag) do { \ + if (*total_flags & (test)) \ + filter_or |= (flag); \ + else \ + filter_nand |= (flag); \ + } while (0) + + D_MAC80211("Enter: changed: 0x%x, total: 0x%x\n", changed_flags, + *total_flags); + + CHK(FIF_OTHER_BSS | FIF_PROMISC_IN_BSS, RXON_FILTER_PROMISC_MSK); + CHK(FIF_CONTROL, RXON_FILTER_CTL2HOST_MSK); + CHK(FIF_BCN_PRBRESP_PROMISC, RXON_FILTER_BCON_AWARE_MSK); + +#undef CHK + + mutex_lock(&il->mutex); + + il->staging.filter_flags &= ~filter_nand; + il->staging.filter_flags |= filter_or; + + /* + * Not committing directly because hardware can perform a scan, + * but even if hw is ready, committing here breaks for some reason, + * we'll eventually commit the filter flags change anyway. + */ + + mutex_unlock(&il->mutex); + + /* + * Receiving all multicast frames is always enabled by the + * default flags setup in il_connection_init_rx_config() + * since we currently do not support programming multicast + * filters into the device. + */ + *total_flags &= + FIF_OTHER_BSS | FIF_ALLMULTI | FIF_PROMISC_IN_BSS | + FIF_BCN_PRBRESP_PROMISC | FIF_CONTROL; +} + +/***************************************************************************** + * + * sysfs attributes + * + *****************************************************************************/ + +#ifdef CPTCFG_IWLEGACY_DEBUG + +/* + * The following adds a new attribute to the sysfs representation + * of this device driver (i.e. a new file in /sys/bus/pci/drivers/iwl/) + * used for controlling the debug level. + * + * See the level definitions in iwl for details. + * + * The debug_level being managed using sysfs below is a per device debug + * level that is used instead of the global debug level if it (the per + * device debug level) is set. + */ +static ssize_t +il3945_show_debug_level(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct il_priv *il = dev_get_drvdata(d); + return sprintf(buf, "0x%08X\n", il_get_debug_level(il)); +} + +static ssize_t +il3945_store_debug_level(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct il_priv *il = dev_get_drvdata(d); + unsigned long val; + int ret; + + ret = strict_strtoul(buf, 0, &val); + if (ret) + IL_INFO("%s is not in hex or decimal form.\n", buf); + else + il->debug_level = val; + + return strnlen(buf, count); +} + +static DEVICE_ATTR(debug_level, S_IWUSR | S_IRUGO, il3945_show_debug_level, + il3945_store_debug_level); + +#endif /* CPTCFG_IWLEGACY_DEBUG */ + +static ssize_t +il3945_show_temperature(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct il_priv *il = dev_get_drvdata(d); + + if (!il_is_alive(il)) + return -EAGAIN; + + return sprintf(buf, "%d\n", il3945_hw_get_temperature(il)); +} + +static DEVICE_ATTR(temperature, S_IRUGO, il3945_show_temperature, NULL); + +static ssize_t +il3945_show_tx_power(struct device *d, struct device_attribute *attr, char *buf) +{ + struct il_priv *il = dev_get_drvdata(d); + return sprintf(buf, "%d\n", il->tx_power_user_lmt); +} + +static ssize_t +il3945_store_tx_power(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct il_priv *il = dev_get_drvdata(d); + char *p = (char *)buf; + u32 val; + + val = simple_strtoul(p, &p, 10); + if (p == buf) + IL_INFO(": %s is not in decimal form.\n", buf); + else + il3945_hw_reg_set_txpower(il, val); + + return count; +} + +static DEVICE_ATTR(tx_power, S_IWUSR | S_IRUGO, il3945_show_tx_power, + il3945_store_tx_power); + +static ssize_t +il3945_show_flags(struct device *d, struct device_attribute *attr, char *buf) +{ + struct il_priv *il = dev_get_drvdata(d); + + return sprintf(buf, "0x%04X\n", il->active.flags); +} + +static ssize_t +il3945_store_flags(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct il_priv *il = dev_get_drvdata(d); + u32 flags = simple_strtoul(buf, NULL, 0); + + mutex_lock(&il->mutex); + if (le32_to_cpu(il->staging.flags) != flags) { + /* Cancel any currently running scans... */ + if (il_scan_cancel_timeout(il, 100)) + IL_WARN("Could not cancel scan.\n"); + else { + D_INFO("Committing rxon.flags = 0x%04X\n", flags); + il->staging.flags = cpu_to_le32(flags); + il3945_commit_rxon(il); + } + } + mutex_unlock(&il->mutex); + + return count; +} + +static DEVICE_ATTR(flags, S_IWUSR | S_IRUGO, il3945_show_flags, + il3945_store_flags); + +static ssize_t +il3945_show_filter_flags(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct il_priv *il = dev_get_drvdata(d); + + return sprintf(buf, "0x%04X\n", le32_to_cpu(il->active.filter_flags)); +} + +static ssize_t +il3945_store_filter_flags(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct il_priv *il = dev_get_drvdata(d); + u32 filter_flags = simple_strtoul(buf, NULL, 0); + + mutex_lock(&il->mutex); + if (le32_to_cpu(il->staging.filter_flags) != filter_flags) { + /* Cancel any currently running scans... */ + if (il_scan_cancel_timeout(il, 100)) + IL_WARN("Could not cancel scan.\n"); + else { + D_INFO("Committing rxon.filter_flags = " "0x%04X\n", + filter_flags); + il->staging.filter_flags = cpu_to_le32(filter_flags); + il3945_commit_rxon(il); + } + } + mutex_unlock(&il->mutex); + + return count; +} + +static DEVICE_ATTR(filter_flags, S_IWUSR | S_IRUGO, il3945_show_filter_flags, + il3945_store_filter_flags); + +static ssize_t +il3945_show_measurement(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct il_priv *il = dev_get_drvdata(d); + struct il_spectrum_notification measure_report; + u32 size = sizeof(measure_report), len = 0, ofs = 0; + u8 *data = (u8 *) &measure_report; + unsigned long flags; + + spin_lock_irqsave(&il->lock, flags); + if (!(il->measurement_status & MEASUREMENT_READY)) { + spin_unlock_irqrestore(&il->lock, flags); + return 0; + } + memcpy(&measure_report, &il->measure_report, size); + il->measurement_status = 0; + spin_unlock_irqrestore(&il->lock, flags); + + while (size && PAGE_SIZE - len) { + hex_dump_to_buffer(data + ofs, size, 16, 1, buf + len, + PAGE_SIZE - len, 1); + len = strlen(buf); + if (PAGE_SIZE - len) + buf[len++] = '\n'; + + ofs += 16; + size -= min(size, 16U); + } + + return len; +} + +static ssize_t +il3945_store_measurement(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct il_priv *il = dev_get_drvdata(d); + struct ieee80211_measurement_params params = { + .channel = le16_to_cpu(il->active.channel), + .start_time = cpu_to_le64(il->_3945.last_tsf), + .duration = cpu_to_le16(1), + }; + u8 type = IL_MEASURE_BASIC; + u8 buffer[32]; + u8 channel; + + if (count) { + char *p = buffer; + strlcpy(buffer, buf, sizeof(buffer)); + channel = simple_strtoul(p, NULL, 0); + if (channel) + params.channel = channel; + + p = buffer; + while (*p && *p != ' ') + p++; + if (*p) + type = simple_strtoul(p + 1, NULL, 0); + } + + D_INFO("Invoking measurement of type %d on " "channel %d (for '%s')\n", + type, params.channel, buf); + il3945_get_measurement(il, ¶ms, type); + + return count; +} + +static DEVICE_ATTR(measurement, S_IRUSR | S_IWUSR, il3945_show_measurement, + il3945_store_measurement); + +static ssize_t +il3945_store_retry_rate(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct il_priv *il = dev_get_drvdata(d); + + il->retry_rate = simple_strtoul(buf, NULL, 0); + if (il->retry_rate <= 0) + il->retry_rate = 1; + + return count; +} + +static ssize_t +il3945_show_retry_rate(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct il_priv *il = dev_get_drvdata(d); + return sprintf(buf, "%d", il->retry_rate); +} + +static DEVICE_ATTR(retry_rate, S_IWUSR | S_IRUSR, il3945_show_retry_rate, + il3945_store_retry_rate); + +static ssize_t +il3945_show_channels(struct device *d, struct device_attribute *attr, char *buf) +{ + /* all this shit doesn't belong into sysfs anyway */ + return 0; +} + +static DEVICE_ATTR(channels, S_IRUSR, il3945_show_channels, NULL); + +static ssize_t +il3945_show_antenna(struct device *d, struct device_attribute *attr, char *buf) +{ + struct il_priv *il = dev_get_drvdata(d); + + if (!il_is_alive(il)) + return -EAGAIN; + + return sprintf(buf, "%d\n", il3945_mod_params.antenna); +} + +static ssize_t +il3945_store_antenna(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct il_priv *il __maybe_unused = dev_get_drvdata(d); + int ant; + + if (count == 0) + return 0; + + if (sscanf(buf, "%1i", &ant) != 1) { + D_INFO("not in hex or decimal form.\n"); + return count; + } + + if (ant >= 0 && ant <= 2) { + D_INFO("Setting antenna select to %d.\n", ant); + il3945_mod_params.antenna = (enum il3945_antenna)ant; + } else + D_INFO("Bad antenna select value %d.\n", ant); + + return count; +} + +static DEVICE_ATTR(antenna, S_IWUSR | S_IRUGO, il3945_show_antenna, + il3945_store_antenna); + +static ssize_t +il3945_show_status(struct device *d, struct device_attribute *attr, char *buf) +{ + struct il_priv *il = dev_get_drvdata(d); + if (!il_is_alive(il)) + return -EAGAIN; + return sprintf(buf, "0x%08x\n", (int)il->status); +} + +static DEVICE_ATTR(status, S_IRUGO, il3945_show_status, NULL); + +static ssize_t +il3945_dump_error_log(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct il_priv *il = dev_get_drvdata(d); + char *p = (char *)buf; + + if (p[0] == '1') + il3945_dump_nic_error_log(il); + + return strnlen(buf, count); +} + +static DEVICE_ATTR(dump_errors, S_IWUSR, NULL, il3945_dump_error_log); + +/***************************************************************************** + * + * driver setup and tear down + * + *****************************************************************************/ + +static void +il3945_setup_deferred_work(struct il_priv *il) +{ + il->workqueue = create_singlethread_workqueue(DRV_NAME); + + init_waitqueue_head(&il->wait_command_queue); + + INIT_WORK(&il->restart, il3945_bg_restart); + INIT_WORK(&il->rx_replenish, il3945_bg_rx_replenish); + INIT_DELAYED_WORK(&il->init_alive_start, il3945_bg_init_alive_start); + INIT_DELAYED_WORK(&il->alive_start, il3945_bg_alive_start); + INIT_DELAYED_WORK(&il->_3945.rfkill_poll, il3945_rfkill_poll); + + il_setup_scan_deferred_work(il); + + il3945_hw_setup_deferred_work(il); + + init_timer(&il->watchdog); + il->watchdog.data = (unsigned long)il; + il->watchdog.function = il_bg_watchdog; + + tasklet_init(&il->irq_tasklet, + (void (*)(unsigned long))il3945_irq_tasklet, + (unsigned long)il); +} + +static void +il3945_cancel_deferred_work(struct il_priv *il) +{ + il3945_hw_cancel_deferred_work(il); + + cancel_delayed_work_sync(&il->init_alive_start); + cancel_delayed_work(&il->alive_start); + + il_cancel_scan_deferred_work(il); +} + +static struct attribute *il3945_sysfs_entries[] = { + &dev_attr_antenna.attr, + &dev_attr_channels.attr, + &dev_attr_dump_errors.attr, + &dev_attr_flags.attr, + &dev_attr_filter_flags.attr, + &dev_attr_measurement.attr, + &dev_attr_retry_rate.attr, + &dev_attr_status.attr, + &dev_attr_temperature.attr, + &dev_attr_tx_power.attr, +#ifdef CPTCFG_IWLEGACY_DEBUG + &dev_attr_debug_level.attr, +#endif + NULL +}; + +static struct attribute_group il3945_attribute_group = { + .name = NULL, /* put in device directory */ + .attrs = il3945_sysfs_entries, +}; + +static struct ieee80211_ops il3945_mac_ops __read_mostly = { + .tx = il3945_mac_tx, + .start = il3945_mac_start, + .stop = il3945_mac_stop, + .add_interface = il_mac_add_interface, + .remove_interface = il_mac_remove_interface, + .change_interface = il_mac_change_interface, + .config = il_mac_config, + .configure_filter = il3945_configure_filter, + .set_key = il3945_mac_set_key, + .conf_tx = il_mac_conf_tx, + .reset_tsf = il_mac_reset_tsf, + .bss_info_changed = il_mac_bss_info_changed, + .hw_scan = il_mac_hw_scan, + .sta_add = il3945_mac_sta_add, + .sta_remove = il_mac_sta_remove, + .tx_last_beacon = il_mac_tx_last_beacon, + .flush = il_mac_flush, +}; + +static int +il3945_init_drv(struct il_priv *il) +{ + int ret; + struct il3945_eeprom *eeprom = (struct il3945_eeprom *)il->eeprom; + + il->retry_rate = 1; + il->beacon_skb = NULL; + + spin_lock_init(&il->sta_lock); + spin_lock_init(&il->hcmd_lock); + + INIT_LIST_HEAD(&il->free_frames); + + mutex_init(&il->mutex); + + il->ieee_channels = NULL; + il->ieee_rates = NULL; + il->band = IEEE80211_BAND_2GHZ; + + il->iw_mode = NL80211_IFTYPE_STATION; + il->missed_beacon_threshold = IL_MISSED_BEACON_THRESHOLD_DEF; + + /* initialize force reset */ + il->force_reset.reset_duration = IL_DELAY_NEXT_FORCE_FW_RELOAD; + + if (eeprom->version < EEPROM_3945_EEPROM_VERSION) { + IL_WARN("Unsupported EEPROM version: 0x%04X\n", + eeprom->version); + ret = -EINVAL; + goto err; + } + ret = il_init_channel_map(il); + if (ret) { + IL_ERR("initializing regulatory failed: %d\n", ret); + goto err; + } + + /* Set up txpower settings in driver for all channels */ + if (il3945_txpower_set_from_eeprom(il)) { + ret = -EIO; + goto err_free_channel_map; + } + + ret = il_init_geos(il); + if (ret) { + IL_ERR("initializing geos failed: %d\n", ret); + goto err_free_channel_map; + } + il3945_init_hw_rates(il, il->ieee_rates); + + return 0; + +err_free_channel_map: + il_free_channel_map(il); +err: + return ret; +} + +#define IL3945_MAX_PROBE_REQUEST 200 + +static int +il3945_setup_mac(struct il_priv *il) +{ + int ret; + struct ieee80211_hw *hw = il->hw; + + hw->rate_control_algorithm = "iwl-3945-rs"; + hw->sta_data_size = sizeof(struct il3945_sta_priv); + hw->vif_data_size = sizeof(struct il_vif_priv); + + /* Tell mac80211 our characteristics */ + hw->flags = IEEE80211_HW_SIGNAL_DBM | IEEE80211_HW_SPECTRUM_MGMT | + IEEE80211_HW_SUPPORTS_PS | IEEE80211_HW_SUPPORTS_DYNAMIC_PS; + + hw->wiphy->interface_modes = + BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_ADHOC); + + hw->wiphy->flags |= + WIPHY_FLAG_CUSTOM_REGULATORY | WIPHY_FLAG_DISABLE_BEACON_HINTS | + WIPHY_FLAG_IBSS_RSN; + + hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + + hw->wiphy->max_scan_ssids = PROBE_OPTION_MAX_3945; + /* we create the 802.11 header and a zero-length SSID element */ + hw->wiphy->max_scan_ie_len = IL3945_MAX_PROBE_REQUEST - 24 - 2; + + /* Default value; 4 EDCA QOS priorities */ + hw->queues = 4; + + if (il->bands[IEEE80211_BAND_2GHZ].n_channels) + il->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = + &il->bands[IEEE80211_BAND_2GHZ]; + + if (il->bands[IEEE80211_BAND_5GHZ].n_channels) + il->hw->wiphy->bands[IEEE80211_BAND_5GHZ] = + &il->bands[IEEE80211_BAND_5GHZ]; + + il_leds_init(il); + + ret = ieee80211_register_hw(il->hw); + if (ret) { + IL_ERR("Failed to register hw (error %d)\n", ret); + return ret; + } + il->mac80211_registered = 1; + + return 0; +} + +static int +il3945_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int err = 0; + struct il_priv *il; + struct ieee80211_hw *hw; + struct il_cfg *cfg = (struct il_cfg *)(ent->driver_data); + struct il3945_eeprom *eeprom; + unsigned long flags; + + /*********************** + * 1. Allocating HW data + * ********************/ + + hw = ieee80211_alloc_hw(sizeof(struct il_priv), &il3945_mac_ops); + if (!hw) { + err = -ENOMEM; + goto out; + } + il = hw->priv; + il->hw = hw; + SET_IEEE80211_DEV(hw, &pdev->dev); + + il->cmd_queue = IL39_CMD_QUEUE_NUM; + + /* + * Disabling hardware scan means that mac80211 will perform scans + * "the hard way", rather than using device's scan. + */ + if (il3945_mod_params.disable_hw_scan) { + D_INFO("Disabling hw_scan\n"); + il3945_mac_ops.hw_scan = NULL; + } + + D_INFO("*** LOAD DRIVER ***\n"); + il->cfg = cfg; + il->ops = &il3945_ops; +#ifdef CPTCFG_IWLEGACY_DEBUGFS + il->debugfs_ops = &il3945_debugfs_ops; +#endif + il->pci_dev = pdev; + il->inta_mask = CSR_INI_SET_MASK; + + /*************************** + * 2. Initializing PCI bus + * *************************/ + pci_disable_link_state(pdev, + PCIE_LINK_STATE_L0S | PCIE_LINK_STATE_L1 | + PCIE_LINK_STATE_CLKPM); + + if (pci_enable_device(pdev)) { + err = -ENODEV; + goto out_ieee80211_free_hw; + } + + pci_set_master(pdev); + + err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + if (!err) + err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)); + if (err) { + IL_WARN("No suitable DMA available.\n"); + goto out_pci_disable_device; + } + + pci_set_drvdata(pdev, il); + err = pci_request_regions(pdev, DRV_NAME); + if (err) + goto out_pci_disable_device; + + /*********************** + * 3. Read REV Register + * ********************/ + il->hw_base = pci_ioremap_bar(pdev, 0); + if (!il->hw_base) { + err = -ENODEV; + goto out_pci_release_regions; + } + + D_INFO("pci_resource_len = 0x%08llx\n", + (unsigned long long)pci_resource_len(pdev, 0)); + D_INFO("pci_resource_base = %p\n", il->hw_base); + + /* We disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state */ + pci_write_config_byte(pdev, 0x41, 0x00); + + /* these spin locks will be used in apm_init and EEPROM access + * we should init now + */ + spin_lock_init(&il->reg_lock); + spin_lock_init(&il->lock); + + /* + * stop and reset the on-board processor just in case it is in a + * strange state ... like being left stranded by a primary kernel + * and this is now the kdump kernel trying to start up + */ + _il_wr(il, CSR_RESET, CSR_RESET_REG_FLAG_NEVO_RESET); + + /*********************** + * 4. Read EEPROM + * ********************/ + + /* Read the EEPROM */ + err = il_eeprom_init(il); + if (err) { + IL_ERR("Unable to init EEPROM\n"); + goto out_iounmap; + } + /* MAC Address location in EEPROM same for 3945/4965 */ + eeprom = (struct il3945_eeprom *)il->eeprom; + D_INFO("MAC address: %pM\n", eeprom->mac_address); + SET_IEEE80211_PERM_ADDR(il->hw, eeprom->mac_address); + + /*********************** + * 5. Setup HW Constants + * ********************/ + /* Device-specific setup */ + if (il3945_hw_set_hw_params(il)) { + IL_ERR("failed to set hw settings\n"); + goto out_eeprom_free; + } + + /*********************** + * 6. Setup il + * ********************/ + + err = il3945_init_drv(il); + if (err) { + IL_ERR("initializing driver failed\n"); + goto out_unset_hw_params; + } + + IL_INFO("Detected Intel Wireless WiFi Link %s\n", il->cfg->name); + + /*********************** + * 7. Setup Services + * ********************/ + + spin_lock_irqsave(&il->lock, flags); + il_disable_interrupts(il); + spin_unlock_irqrestore(&il->lock, flags); + + pci_enable_msi(il->pci_dev); + + err = request_irq(il->pci_dev->irq, il_isr, IRQF_SHARED, DRV_NAME, il); + if (err) { + IL_ERR("Error allocating IRQ %d\n", il->pci_dev->irq); + goto out_disable_msi; + } + + err = sysfs_create_group(&pdev->dev.kobj, &il3945_attribute_group); + if (err) { + IL_ERR("failed to create sysfs device attributes\n"); + goto out_release_irq; + } + + il_set_rxon_channel(il, &il->bands[IEEE80211_BAND_2GHZ].channels[5]); + il3945_setup_deferred_work(il); + il3945_setup_handlers(il); + il_power_initialize(il); + + /********************************* + * 8. Setup and Register mac80211 + * *******************************/ + + il_enable_interrupts(il); + + err = il3945_setup_mac(il); + if (err) + goto out_remove_sysfs; + + err = il_dbgfs_register(il, DRV_NAME); + if (err) + IL_ERR("failed to create debugfs files. Ignoring error: %d\n", + err); + + /* Start monitoring the killswitch */ + queue_delayed_work(il->workqueue, &il->_3945.rfkill_poll, 2 * HZ); + + return 0; + +out_remove_sysfs: + destroy_workqueue(il->workqueue); + il->workqueue = NULL; + sysfs_remove_group(&pdev->dev.kobj, &il3945_attribute_group); +out_release_irq: + free_irq(il->pci_dev->irq, il); +out_disable_msi: + pci_disable_msi(il->pci_dev); + il_free_geos(il); + il_free_channel_map(il); +out_unset_hw_params: + il3945_unset_hw_params(il); +out_eeprom_free: + il_eeprom_free(il); +out_iounmap: + iounmap(il->hw_base); +out_pci_release_regions: + pci_release_regions(pdev); +out_pci_disable_device: + pci_set_drvdata(pdev, NULL); + pci_disable_device(pdev); +out_ieee80211_free_hw: + ieee80211_free_hw(il->hw); +out: + return err; +} + +static void +il3945_pci_remove(struct pci_dev *pdev) +{ + struct il_priv *il = pci_get_drvdata(pdev); + unsigned long flags; + + if (!il) + return; + + D_INFO("*** UNLOAD DRIVER ***\n"); + + il_dbgfs_unregister(il); + + set_bit(S_EXIT_PENDING, &il->status); + + il_leds_exit(il); + + if (il->mac80211_registered) { + ieee80211_unregister_hw(il->hw); + il->mac80211_registered = 0; + } else { + il3945_down(il); + } + + /* + * Make sure device is reset to low power before unloading driver. + * This may be redundant with il_down(), but there are paths to + * run il_down() without calling apm_ops.stop(), and there are + * paths to avoid running il_down() at all before leaving driver. + * This (inexpensive) call *makes sure* device is reset. + */ + il_apm_stop(il); + + /* make sure we flush any pending irq or + * tasklet for the driver + */ + spin_lock_irqsave(&il->lock, flags); + il_disable_interrupts(il); + spin_unlock_irqrestore(&il->lock, flags); + + il3945_synchronize_irq(il); + + sysfs_remove_group(&pdev->dev.kobj, &il3945_attribute_group); + + cancel_delayed_work_sync(&il->_3945.rfkill_poll); + + il3945_dealloc_ucode_pci(il); + + if (il->rxq.bd) + il3945_rx_queue_free(il, &il->rxq); + il3945_hw_txq_ctx_free(il); + + il3945_unset_hw_params(il); + + /*netif_stop_queue(dev); */ + flush_workqueue(il->workqueue); + + /* ieee80211_unregister_hw calls il3945_mac_stop, which flushes + * il->workqueue... so we can't take down the workqueue + * until now... */ + destroy_workqueue(il->workqueue); + il->workqueue = NULL; + + free_irq(pdev->irq, il); + pci_disable_msi(pdev); + + iounmap(il->hw_base); + pci_release_regions(pdev); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); + + il_free_channel_map(il); + il_free_geos(il); + kfree(il->scan_cmd); + if (il->beacon_skb) + dev_kfree_skb(il->beacon_skb); + + ieee80211_free_hw(il->hw); +} + +/***************************************************************************** + * + * driver and module entry point + * + *****************************************************************************/ + +static struct pci_driver il3945_driver = { + .name = DRV_NAME, + .id_table = il3945_hw_card_ids, + .probe = il3945_pci_probe, + .remove = il3945_pci_remove, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) + .driver.pm = IL_LEGACY_PM_OPS, +#elif defined(CONFIG_PM) + .suspend = il_pci_suspend_compat, + .resume = il_pci_resume_compat, +#endif +}; + +static int __init +il3945_init(void) +{ + + int ret; + pr_info(DRV_DESCRIPTION ", " DRV_VERSION "\n"); + pr_info(DRV_COPYRIGHT "\n"); + + ret = il3945_rate_control_register(); + if (ret) { + pr_err("Unable to register rate control algorithm: %d\n", ret); + return ret; + } + + ret = pci_register_driver(&il3945_driver); + if (ret) { + pr_err("Unable to initialize PCI module\n"); + goto error_register; + } + + return ret; + +error_register: + il3945_rate_control_unregister(); + return ret; +} + +static void __exit +il3945_exit(void) +{ + pci_unregister_driver(&il3945_driver); + il3945_rate_control_unregister(); +} + +MODULE_FIRMWARE(IL3945_MODULE_FIRMWARE(IL3945_UCODE_API_MAX)); + +module_param_named(antenna, il3945_mod_params.antenna, int, S_IRUGO); +MODULE_PARM_DESC(antenna, "select antenna (1=Main, 2=Aux, default 0 [both])"); +module_param_named(swcrypto, il3945_mod_params.sw_crypto, int, S_IRUGO); +MODULE_PARM_DESC(swcrypto, "using software crypto (default 1 [software])"); +module_param_named(disable_hw_scan, il3945_mod_params.disable_hw_scan, int, + S_IRUGO); +MODULE_PARM_DESC(disable_hw_scan, "disable hardware scanning (default 1)"); +#ifdef CPTCFG_IWLEGACY_DEBUG +module_param_named(debug, il_debug_level, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "debug output mask"); +#endif +module_param_named(fw_restart, il3945_mod_params.restart_fw, int, S_IRUGO); +MODULE_PARM_DESC(fw_restart, "restart firmware in case of error"); + +module_exit(il3945_exit); +module_init(il3945_init); diff --git a/drivers/net/wireless/iwlegacy/3945-rs.c b/drivers/net/wireless/iwlegacy/3945-rs.c new file mode 100644 index 0000000..d463b5a --- /dev/null +++ b/drivers/net/wireless/iwlegacy/3945-rs.c @@ -0,0 +1,980 @@ +/****************************************************************************** + * + * Copyright(c) 2005 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "commands.h" +#include "3945.h" + +#define RS_NAME "iwl-3945-rs" + +static s32 il3945_expected_tpt_g[RATE_COUNT_3945] = { + 7, 13, 35, 58, 0, 0, 76, 104, 130, 168, 191, 202 +}; + +static s32 il3945_expected_tpt_g_prot[RATE_COUNT_3945] = { + 7, 13, 35, 58, 0, 0, 0, 80, 93, 113, 123, 125 +}; + +static s32 il3945_expected_tpt_a[RATE_COUNT_3945] = { + 0, 0, 0, 0, 40, 57, 72, 98, 121, 154, 177, 186 +}; + +static s32 il3945_expected_tpt_b[RATE_COUNT_3945] = { + 7, 13, 35, 58, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +struct il3945_tpt_entry { + s8 min_rssi; + u8 idx; +}; + +static struct il3945_tpt_entry il3945_tpt_table_a[] = { + {-60, RATE_54M_IDX}, + {-64, RATE_48M_IDX}, + {-72, RATE_36M_IDX}, + {-80, RATE_24M_IDX}, + {-84, RATE_18M_IDX}, + {-85, RATE_12M_IDX}, + {-87, RATE_9M_IDX}, + {-89, RATE_6M_IDX} +}; + +static struct il3945_tpt_entry il3945_tpt_table_g[] = { + {-60, RATE_54M_IDX}, + {-64, RATE_48M_IDX}, + {-68, RATE_36M_IDX}, + {-80, RATE_24M_IDX}, + {-84, RATE_18M_IDX}, + {-85, RATE_12M_IDX}, + {-86, RATE_11M_IDX}, + {-88, RATE_5M_IDX}, + {-90, RATE_2M_IDX}, + {-92, RATE_1M_IDX} +}; + +#define RATE_MAX_WINDOW 62 +#define RATE_FLUSH (3*HZ) +#define RATE_WIN_FLUSH (HZ/2) +#define IL39_RATE_HIGH_TH 11520 +#define IL_SUCCESS_UP_TH 8960 +#define IL_SUCCESS_DOWN_TH 10880 +#define RATE_MIN_FAILURE_TH 6 +#define RATE_MIN_SUCCESS_TH 8 +#define RATE_DECREASE_TH 1920 +#define RATE_RETRY_TH 15 + +static u8 +il3945_get_rate_idx_by_rssi(s32 rssi, enum ieee80211_band band) +{ + u32 idx = 0; + u32 table_size = 0; + struct il3945_tpt_entry *tpt_table = NULL; + + if (rssi < IL_MIN_RSSI_VAL || rssi > IL_MAX_RSSI_VAL) + rssi = IL_MIN_RSSI_VAL; + + switch (band) { + case IEEE80211_BAND_2GHZ: + tpt_table = il3945_tpt_table_g; + table_size = ARRAY_SIZE(il3945_tpt_table_g); + break; + case IEEE80211_BAND_5GHZ: + tpt_table = il3945_tpt_table_a; + table_size = ARRAY_SIZE(il3945_tpt_table_a); + break; + default: + BUG(); + break; + } + + while (idx < table_size && rssi < tpt_table[idx].min_rssi) + idx++; + + idx = min(idx, table_size - 1); + + return tpt_table[idx].idx; +} + +static void +il3945_clear_win(struct il3945_rate_scale_data *win) +{ + win->data = 0; + win->success_counter = 0; + win->success_ratio = -1; + win->counter = 0; + win->average_tpt = IL_INVALID_VALUE; + win->stamp = 0; +} + +/** + * il3945_rate_scale_flush_wins - flush out the rate scale wins + * + * Returns the number of wins that have gathered data but were + * not flushed. If there were any that were not flushed, then + * reschedule the rate flushing routine. + */ +static int +il3945_rate_scale_flush_wins(struct il3945_rs_sta *rs_sta) +{ + int unflushed = 0; + int i; + unsigned long flags; + struct il_priv *il __maybe_unused = rs_sta->il; + + /* + * For each rate, if we have collected data on that rate + * and it has been more than RATE_WIN_FLUSH + * since we flushed, clear out the gathered stats + */ + for (i = 0; i < RATE_COUNT_3945; i++) { + if (!rs_sta->win[i].counter) + continue; + + spin_lock_irqsave(&rs_sta->lock, flags); + if (time_after(jiffies, rs_sta->win[i].stamp + RATE_WIN_FLUSH)) { + D_RATE("flushing %d samples of rate " "idx %d\n", + rs_sta->win[i].counter, i); + il3945_clear_win(&rs_sta->win[i]); + } else + unflushed++; + spin_unlock_irqrestore(&rs_sta->lock, flags); + } + + return unflushed; +} + +#define RATE_FLUSH_MAX 5000 /* msec */ +#define RATE_FLUSH_MIN 50 /* msec */ +#define IL_AVERAGE_PACKETS 1500 + +static void +il3945_bg_rate_scale_flush(unsigned long data) +{ + struct il3945_rs_sta *rs_sta = (void *)data; + struct il_priv *il __maybe_unused = rs_sta->il; + int unflushed = 0; + unsigned long flags; + u32 packet_count, duration, pps; + + D_RATE("enter\n"); + + unflushed = il3945_rate_scale_flush_wins(rs_sta); + + spin_lock_irqsave(&rs_sta->lock, flags); + + /* Number of packets Rx'd since last time this timer ran */ + packet_count = (rs_sta->tx_packets - rs_sta->last_tx_packets) + 1; + + rs_sta->last_tx_packets = rs_sta->tx_packets + 1; + + if (unflushed) { + duration = + jiffies_to_msecs(jiffies - rs_sta->last_partial_flush); + + D_RATE("Tx'd %d packets in %dms\n", packet_count, duration); + + /* Determine packets per second */ + if (duration) + pps = (packet_count * 1000) / duration; + else + pps = 0; + + if (pps) { + duration = (IL_AVERAGE_PACKETS * 1000) / pps; + if (duration < RATE_FLUSH_MIN) + duration = RATE_FLUSH_MIN; + else if (duration > RATE_FLUSH_MAX) + duration = RATE_FLUSH_MAX; + } else + duration = RATE_FLUSH_MAX; + + rs_sta->flush_time = msecs_to_jiffies(duration); + + D_RATE("new flush period: %d msec ave %d\n", duration, + packet_count); + + mod_timer(&rs_sta->rate_scale_flush, + jiffies + rs_sta->flush_time); + + rs_sta->last_partial_flush = jiffies; + } else { + rs_sta->flush_time = RATE_FLUSH; + rs_sta->flush_pending = 0; + } + /* If there weren't any unflushed entries, we don't schedule the timer + * to run again */ + + rs_sta->last_flush = jiffies; + + spin_unlock_irqrestore(&rs_sta->lock, flags); + + D_RATE("leave\n"); +} + +/** + * il3945_collect_tx_data - Update the success/failure sliding win + * + * We keep a sliding win of the last 64 packets transmitted + * at this rate. win->data contains the bitmask of successful + * packets. + */ +static void +il3945_collect_tx_data(struct il3945_rs_sta *rs_sta, + struct il3945_rate_scale_data *win, int success, + int retries, int idx) +{ + unsigned long flags; + s32 fail_count; + struct il_priv *il __maybe_unused = rs_sta->il; + + if (!retries) { + D_RATE("leave: retries == 0 -- should be at least 1\n"); + return; + } + + spin_lock_irqsave(&rs_sta->lock, flags); + + /* + * Keep track of only the latest 62 tx frame attempts in this rate's + * history win; anything older isn't really relevant any more. + * If we have filled up the sliding win, drop the oldest attempt; + * if the oldest attempt (highest bit in bitmap) shows "success", + * subtract "1" from the success counter (this is the main reason + * we keep these bitmaps!). + * */ + while (retries > 0) { + if (win->counter >= RATE_MAX_WINDOW) { + + /* remove earliest */ + win->counter = RATE_MAX_WINDOW - 1; + + if (win->data & (1ULL << (RATE_MAX_WINDOW - 1))) { + win->data &= ~(1ULL << (RATE_MAX_WINDOW - 1)); + win->success_counter--; + } + } + + /* Increment frames-attempted counter */ + win->counter++; + + /* Shift bitmap by one frame (throw away oldest history), + * OR in "1", and increment "success" if this + * frame was successful. */ + win->data <<= 1; + if (success > 0) { + win->success_counter++; + win->data |= 0x1; + success--; + } + + retries--; + } + + /* Calculate current success ratio, avoid divide-by-0! */ + if (win->counter > 0) + win->success_ratio = + 128 * (100 * win->success_counter) / win->counter; + else + win->success_ratio = IL_INVALID_VALUE; + + fail_count = win->counter - win->success_counter; + + /* Calculate average throughput, if we have enough history. */ + if (fail_count >= RATE_MIN_FAILURE_TH || + win->success_counter >= RATE_MIN_SUCCESS_TH) + win->average_tpt = + ((win->success_ratio * rs_sta->expected_tpt[idx] + + 64) / 128); + else + win->average_tpt = IL_INVALID_VALUE; + + /* Tag this win as having been updated */ + win->stamp = jiffies; + + spin_unlock_irqrestore(&rs_sta->lock, flags); +} + +/* + * Called after adding a new station to initialize rate scaling + */ +void +il3945_rs_rate_init(struct il_priv *il, struct ieee80211_sta *sta, u8 sta_id) +{ + struct ieee80211_hw *hw = il->hw; + struct ieee80211_conf *conf = &il->hw->conf; + struct il3945_sta_priv *psta; + struct il3945_rs_sta *rs_sta; + struct ieee80211_supported_band *sband; + int i; + + D_INFO("enter\n"); + if (sta_id == il->hw_params.bcast_id) + goto out; + + psta = (struct il3945_sta_priv *)sta->drv_priv; + rs_sta = &psta->rs_sta; + sband = hw->wiphy->bands[conf->chandef.chan->band]; + + rs_sta->il = il; + + rs_sta->start_rate = RATE_INVALID; + + /* default to just 802.11b */ + rs_sta->expected_tpt = il3945_expected_tpt_b; + + rs_sta->last_partial_flush = jiffies; + rs_sta->last_flush = jiffies; + rs_sta->flush_time = RATE_FLUSH; + rs_sta->last_tx_packets = 0; + + rs_sta->rate_scale_flush.data = (unsigned long)rs_sta; + rs_sta->rate_scale_flush.function = il3945_bg_rate_scale_flush; + + for (i = 0; i < RATE_COUNT_3945; i++) + il3945_clear_win(&rs_sta->win[i]); + + /* TODO: what is a good starting rate for STA? About middle? Maybe not + * the lowest or the highest rate.. Could consider using RSSI from + * previous packets? Need to have IEEE 802.1X auth succeed immediately + * after assoc.. */ + + for (i = sband->n_bitrates - 1; i >= 0; i--) { + if (sta->supp_rates[sband->band] & (1 << i)) { + rs_sta->last_txrate_idx = i; + break; + } + } + + il->_3945.sta_supp_rates = sta->supp_rates[sband->band]; + /* For 5 GHz band it start at IL_FIRST_OFDM_RATE */ + if (sband->band == IEEE80211_BAND_5GHZ) { + rs_sta->last_txrate_idx += IL_FIRST_OFDM_RATE; + il->_3945.sta_supp_rates <<= IL_FIRST_OFDM_RATE; + } + +out: + il->stations[sta_id].used &= ~IL_STA_UCODE_INPROGRESS; + + D_INFO("leave\n"); +} + +static void * +il3945_rs_alloc(struct ieee80211_hw *hw, struct dentry *debugfsdir) +{ + return hw->priv; +} + +/* rate scale requires free function to be implemented */ +static void +il3945_rs_free(void *il) +{ +} + +static void * +il3945_rs_alloc_sta(void *il_priv, struct ieee80211_sta *sta, gfp_t gfp) +{ + struct il3945_rs_sta *rs_sta; + struct il3945_sta_priv *psta = (void *)sta->drv_priv; + struct il_priv *il __maybe_unused = il_priv; + + D_RATE("enter\n"); + + rs_sta = &psta->rs_sta; + + spin_lock_init(&rs_sta->lock); + init_timer(&rs_sta->rate_scale_flush); + + D_RATE("leave\n"); + + return rs_sta; +} + +static void +il3945_rs_free_sta(void *il_priv, struct ieee80211_sta *sta, void *il_sta) +{ + struct il3945_rs_sta *rs_sta = il_sta; + + /* + * Be careful not to use any members of il3945_rs_sta (like trying + * to use il_priv to print out debugging) since it may not be fully + * initialized at this point. + */ + del_timer_sync(&rs_sta->rate_scale_flush); +} + +/** + * il3945_rs_tx_status - Update rate control values based on Tx results + * + * NOTE: Uses il_priv->retry_rate for the # of retries attempted by + * the hardware for each rate. + */ +static void +il3945_rs_tx_status(void *il_rate, struct ieee80211_supported_band *sband, + struct ieee80211_sta *sta, void *il_sta, + struct sk_buff *skb) +{ + s8 retries = 0, current_count; + int scale_rate_idx, first_idx, last_idx; + unsigned long flags; + struct il_priv *il = (struct il_priv *)il_rate; + struct il3945_rs_sta *rs_sta = il_sta; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + D_RATE("enter\n"); + + retries = info->status.rates[0].count; + /* Sanity Check for retries */ + if (retries > RATE_RETRY_TH) + retries = RATE_RETRY_TH; + + first_idx = sband->bitrates[info->status.rates[0].idx].hw_value; + if (first_idx < 0 || first_idx >= RATE_COUNT_3945) { + D_RATE("leave: Rate out of bounds: %d\n", first_idx); + return; + } + + if (!il_sta) { + D_RATE("leave: No STA il data to update!\n"); + return; + } + + /* Treat uninitialized rate scaling data same as non-existing. */ + if (!rs_sta->il) { + D_RATE("leave: STA il data uninitialized!\n"); + return; + } + + rs_sta->tx_packets++; + + scale_rate_idx = first_idx; + last_idx = first_idx; + + /* + * Update the win for each rate. We determine which rates + * were Tx'd based on the total number of retries vs. the number + * of retries configured for each rate -- currently set to the + * il value 'retry_rate' vs. rate specific + * + * On exit from this while loop last_idx indicates the rate + * at which the frame was finally transmitted (or failed if no + * ACK) + */ + while (retries > 1) { + if ((retries - 1) < il->retry_rate) { + current_count = (retries - 1); + last_idx = scale_rate_idx; + } else { + current_count = il->retry_rate; + last_idx = il3945_rs_next_rate(il, scale_rate_idx); + } + + /* Update this rate accounting for as many retries + * as was used for it (per current_count) */ + il3945_collect_tx_data(rs_sta, &rs_sta->win[scale_rate_idx], 0, + current_count, scale_rate_idx); + D_RATE("Update rate %d for %d retries.\n", scale_rate_idx, + current_count); + + retries -= current_count; + + scale_rate_idx = last_idx; + } + + /* Update the last idx win with success/failure based on ACK */ + D_RATE("Update rate %d with %s.\n", last_idx, + (info->flags & IEEE80211_TX_STAT_ACK) ? "success" : "failure"); + il3945_collect_tx_data(rs_sta, &rs_sta->win[last_idx], + info->flags & IEEE80211_TX_STAT_ACK, 1, + last_idx); + + /* We updated the rate scale win -- if its been more than + * flush_time since the last run, schedule the flush + * again */ + spin_lock_irqsave(&rs_sta->lock, flags); + + if (!rs_sta->flush_pending && + time_after(jiffies, rs_sta->last_flush + rs_sta->flush_time)) { + + rs_sta->last_partial_flush = jiffies; + rs_sta->flush_pending = 1; + mod_timer(&rs_sta->rate_scale_flush, + jiffies + rs_sta->flush_time); + } + + spin_unlock_irqrestore(&rs_sta->lock, flags); + + D_RATE("leave\n"); +} + +static u16 +il3945_get_adjacent_rate(struct il3945_rs_sta *rs_sta, u8 idx, u16 rate_mask, + enum ieee80211_band band) +{ + u8 high = RATE_INVALID; + u8 low = RATE_INVALID; + struct il_priv *il __maybe_unused = rs_sta->il; + + /* 802.11A walks to the next literal adjacent rate in + * the rate table */ + if (unlikely(band == IEEE80211_BAND_5GHZ)) { + int i; + u32 mask; + + /* Find the previous rate that is in the rate mask */ + i = idx - 1; + for (mask = (1 << i); i >= 0; i--, mask >>= 1) { + if (rate_mask & mask) { + low = i; + break; + } + } + + /* Find the next rate that is in the rate mask */ + i = idx + 1; + for (mask = (1 << i); i < RATE_COUNT_3945; i++, mask <<= 1) { + if (rate_mask & mask) { + high = i; + break; + } + } + + return (high << 8) | low; + } + + low = idx; + while (low != RATE_INVALID) { + if (rs_sta->tgg) + low = il3945_rates[low].prev_rs_tgg; + else + low = il3945_rates[low].prev_rs; + if (low == RATE_INVALID) + break; + if (rate_mask & (1 << low)) + break; + D_RATE("Skipping masked lower rate: %d\n", low); + } + + high = idx; + while (high != RATE_INVALID) { + if (rs_sta->tgg) + high = il3945_rates[high].next_rs_tgg; + else + high = il3945_rates[high].next_rs; + if (high == RATE_INVALID) + break; + if (rate_mask & (1 << high)) + break; + D_RATE("Skipping masked higher rate: %d\n", high); + } + + return (high << 8) | low; +} + +/** + * il3945_rs_get_rate - find the rate for the requested packet + * + * Returns the ieee80211_rate structure allocated by the driver. + * + * The rate control algorithm has no internal mapping between hw_mode's + * rate ordering and the rate ordering used by the rate control algorithm. + * + * The rate control algorithm uses a single table of rates that goes across + * the entire A/B/G spectrum vs. being limited to just one particular + * hw_mode. + * + * As such, we can't convert the idx obtained below into the hw_mode's + * rate table and must reference the driver allocated rate table + * + */ +static void +il3945_rs_get_rate(void *il_r, struct ieee80211_sta *sta, void *il_sta, + struct ieee80211_tx_rate_control *txrc) +{ + struct ieee80211_supported_band *sband = txrc->sband; + struct sk_buff *skb = txrc->skb; + u8 low = RATE_INVALID; + u8 high = RATE_INVALID; + u16 high_low; + int idx; + struct il3945_rs_sta *rs_sta = il_sta; + struct il3945_rate_scale_data *win = NULL; + int current_tpt = IL_INVALID_VALUE; + int low_tpt = IL_INVALID_VALUE; + int high_tpt = IL_INVALID_VALUE; + u32 fail_count; + s8 scale_action = 0; + unsigned long flags; + u16 rate_mask; + s8 max_rate_idx = -1; + struct il_priv *il __maybe_unused = (struct il_priv *)il_r; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + D_RATE("enter\n"); + + /* Treat uninitialized rate scaling data same as non-existing. */ + if (rs_sta && !rs_sta->il) { + D_RATE("Rate scaling information not initialized yet.\n"); + il_sta = NULL; + } + + if (rate_control_send_low(sta, il_sta, txrc)) + return; + + rate_mask = sta->supp_rates[sband->band]; + + /* get user max rate if set */ + max_rate_idx = txrc->max_rate_idx; + if (sband->band == IEEE80211_BAND_5GHZ && max_rate_idx != -1) + max_rate_idx += IL_FIRST_OFDM_RATE; + if (max_rate_idx < 0 || max_rate_idx >= RATE_COUNT) + max_rate_idx = -1; + + idx = min(rs_sta->last_txrate_idx & 0xffff, RATE_COUNT_3945 - 1); + + if (sband->band == IEEE80211_BAND_5GHZ) + rate_mask = rate_mask << IL_FIRST_OFDM_RATE; + + spin_lock_irqsave(&rs_sta->lock, flags); + + /* for recent assoc, choose best rate regarding + * to rssi value + */ + if (rs_sta->start_rate != RATE_INVALID) { + if (rs_sta->start_rate < idx && + (rate_mask & (1 << rs_sta->start_rate))) + idx = rs_sta->start_rate; + rs_sta->start_rate = RATE_INVALID; + } + + /* force user max rate if set by user */ + if (max_rate_idx != -1 && max_rate_idx < idx) { + if (rate_mask & (1 << max_rate_idx)) + idx = max_rate_idx; + } + + win = &(rs_sta->win[idx]); + + fail_count = win->counter - win->success_counter; + + if (fail_count < RATE_MIN_FAILURE_TH && + win->success_counter < RATE_MIN_SUCCESS_TH) { + spin_unlock_irqrestore(&rs_sta->lock, flags); + + D_RATE("Invalid average_tpt on rate %d: " + "counter: %d, success_counter: %d, " + "expected_tpt is %sNULL\n", idx, win->counter, + win->success_counter, + rs_sta->expected_tpt ? "not " : ""); + + /* Can't calculate this yet; not enough history */ + win->average_tpt = IL_INVALID_VALUE; + goto out; + + } + + current_tpt = win->average_tpt; + + high_low = + il3945_get_adjacent_rate(rs_sta, idx, rate_mask, sband->band); + low = high_low & 0xff; + high = (high_low >> 8) & 0xff; + + /* If user set max rate, dont allow higher than user constrain */ + if (max_rate_idx != -1 && max_rate_idx < high) + high = RATE_INVALID; + + /* Collect Measured throughputs of adjacent rates */ + if (low != RATE_INVALID) + low_tpt = rs_sta->win[low].average_tpt; + + if (high != RATE_INVALID) + high_tpt = rs_sta->win[high].average_tpt; + + spin_unlock_irqrestore(&rs_sta->lock, flags); + + scale_action = 0; + + /* Low success ratio , need to drop the rate */ + if (win->success_ratio < RATE_DECREASE_TH || !current_tpt) { + D_RATE("decrease rate because of low success_ratio\n"); + scale_action = -1; + /* No throughput measured yet for adjacent rates, + * try increase */ + } else if (low_tpt == IL_INVALID_VALUE && high_tpt == IL_INVALID_VALUE) { + + if (high != RATE_INVALID && + win->success_ratio >= RATE_INCREASE_TH) + scale_action = 1; + else if (low != RATE_INVALID) + scale_action = 0; + + /* Both adjacent throughputs are measured, but neither one has + * better throughput; we're using the best rate, don't change + * it! */ + } else if (low_tpt != IL_INVALID_VALUE && high_tpt != IL_INVALID_VALUE + && low_tpt < current_tpt && high_tpt < current_tpt) { + + D_RATE("No action -- low [%d] & high [%d] < " + "current_tpt [%d]\n", low_tpt, high_tpt, current_tpt); + scale_action = 0; + + /* At least one of the rates has better throughput */ + } else { + if (high_tpt != IL_INVALID_VALUE) { + + /* High rate has better throughput, Increase + * rate */ + if (high_tpt > current_tpt && + win->success_ratio >= RATE_INCREASE_TH) + scale_action = 1; + else { + D_RATE("decrease rate because of high tpt\n"); + scale_action = 0; + } + } else if (low_tpt != IL_INVALID_VALUE) { + if (low_tpt > current_tpt) { + D_RATE("decrease rate because of low tpt\n"); + scale_action = -1; + } else if (win->success_ratio >= RATE_INCREASE_TH) { + /* Lower rate has better + * throughput,decrease rate */ + scale_action = 1; + } + } + } + + /* Sanity check; asked for decrease, but success rate or throughput + * has been good at old rate. Don't change it. */ + if (scale_action == -1 && low != RATE_INVALID && + (win->success_ratio > RATE_HIGH_TH || + current_tpt > 100 * rs_sta->expected_tpt[low])) + scale_action = 0; + + switch (scale_action) { + case -1: + /* Decrese rate */ + if (low != RATE_INVALID) + idx = low; + break; + case 1: + /* Increase rate */ + if (high != RATE_INVALID) + idx = high; + + break; + case 0: + default: + /* No change */ + break; + } + + D_RATE("Selected %d (action %d) - low %d high %d\n", idx, scale_action, + low, high); + +out: + + if (sband->band == IEEE80211_BAND_5GHZ) { + if (WARN_ON_ONCE(idx < IL_FIRST_OFDM_RATE)) + idx = IL_FIRST_OFDM_RATE; + rs_sta->last_txrate_idx = idx; + info->control.rates[0].idx = idx - IL_FIRST_OFDM_RATE; + } else { + rs_sta->last_txrate_idx = idx; + info->control.rates[0].idx = rs_sta->last_txrate_idx; + } + info->control.rates[0].count = 1; + + D_RATE("leave: %d\n", idx); +} + +#ifdef CPTCFG_MAC80211_DEBUGFS + +static ssize_t +il3945_sta_dbgfs_stats_table_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + char *buff; + int desc = 0; + int j; + ssize_t ret; + struct il3945_rs_sta *lq_sta = file->private_data; + + buff = kmalloc(1024, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + desc += + sprintf(buff + desc, + "tx packets=%d last rate idx=%d\n" + "rate=0x%X flush time %d\n", lq_sta->tx_packets, + lq_sta->last_txrate_idx, lq_sta->start_rate, + jiffies_to_msecs(lq_sta->flush_time)); + for (j = 0; j < RATE_COUNT_3945; j++) { + desc += + sprintf(buff + desc, "counter=%d success=%d %%=%d\n", + lq_sta->win[j].counter, + lq_sta->win[j].success_counter, + lq_sta->win[j].success_ratio); + } + ret = simple_read_from_buffer(user_buf, count, ppos, buff, desc); + kfree(buff); + return ret; +} + +static const struct file_operations rs_sta_dbgfs_stats_table_ops = { + .read = il3945_sta_dbgfs_stats_table_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static void +il3945_add_debugfs(void *il, void *il_sta, struct dentry *dir) +{ + struct il3945_rs_sta *lq_sta = il_sta; + + lq_sta->rs_sta_dbgfs_stats_table_file = + debugfs_create_file("rate_stats_table", 0600, dir, lq_sta, + &rs_sta_dbgfs_stats_table_ops); + +} + +static void +il3945_remove_debugfs(void *il, void *il_sta) +{ + struct il3945_rs_sta *lq_sta = il_sta; + debugfs_remove(lq_sta->rs_sta_dbgfs_stats_table_file); +} +#endif + +/* + * Initialization of rate scaling information is done by driver after + * the station is added. Since mac80211 calls this function before a + * station is added we ignore it. + */ +static void +il3945_rs_rate_init_stub(void *il_r, struct ieee80211_supported_band *sband, + struct ieee80211_sta *sta, void *il_sta) +{ +} + +static struct rate_control_ops rs_ops = { + .module = NULL, + .name = RS_NAME, + .tx_status = il3945_rs_tx_status, + .get_rate = il3945_rs_get_rate, + .rate_init = il3945_rs_rate_init_stub, + .alloc = il3945_rs_alloc, + .free = il3945_rs_free, + .alloc_sta = il3945_rs_alloc_sta, + .free_sta = il3945_rs_free_sta, +#ifdef CPTCFG_MAC80211_DEBUGFS + .add_sta_debugfs = il3945_add_debugfs, + .remove_sta_debugfs = il3945_remove_debugfs, +#endif + +}; + +void +il3945_rate_scale_init(struct ieee80211_hw *hw, s32 sta_id) +{ + struct il_priv *il = hw->priv; + s32 rssi = 0; + unsigned long flags; + struct il3945_rs_sta *rs_sta; + struct ieee80211_sta *sta; + struct il3945_sta_priv *psta; + + D_RATE("enter\n"); + + rcu_read_lock(); + + sta = ieee80211_find_sta(il->vif, il->stations[sta_id].sta.sta.addr); + if (!sta) { + D_RATE("Unable to find station to initialize rate scaling.\n"); + rcu_read_unlock(); + return; + } + + psta = (void *)sta->drv_priv; + rs_sta = &psta->rs_sta; + + spin_lock_irqsave(&rs_sta->lock, flags); + + rs_sta->tgg = 0; + switch (il->band) { + case IEEE80211_BAND_2GHZ: + /* TODO: this always does G, not a regression */ + if (il->active.flags & RXON_FLG_TGG_PROTECT_MSK) { + rs_sta->tgg = 1; + rs_sta->expected_tpt = il3945_expected_tpt_g_prot; + } else + rs_sta->expected_tpt = il3945_expected_tpt_g; + break; + case IEEE80211_BAND_5GHZ: + rs_sta->expected_tpt = il3945_expected_tpt_a; + break; + default: + BUG(); + break; + } + + spin_unlock_irqrestore(&rs_sta->lock, flags); + + rssi = il->_3945.last_rx_rssi; + if (rssi == 0) + rssi = IL_MIN_RSSI_VAL; + + D_RATE("Network RSSI: %d\n", rssi); + + rs_sta->start_rate = il3945_get_rate_idx_by_rssi(rssi, il->band); + + D_RATE("leave: rssi %d assign rate idx: " "%d (plcp 0x%x)\n", rssi, + rs_sta->start_rate, il3945_rates[rs_sta->start_rate].plcp); + rcu_read_unlock(); +} + +int +il3945_rate_control_register(void) +{ + return ieee80211_rate_control_register(&rs_ops); +} + +void +il3945_rate_control_unregister(void) +{ + ieee80211_rate_control_unregister(&rs_ops); +} diff --git a/drivers/net/wireless/iwlegacy/3945.c b/drivers/net/wireless/iwlegacy/3945.c new file mode 100644 index 0000000..d15eab2 --- /dev/null +++ b/drivers/net/wireless/iwlegacy/3945.c @@ -0,0 +1,2717 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "3945.h" + +/* Send led command */ +static int +il3945_send_led_cmd(struct il_priv *il, struct il_led_cmd *led_cmd) +{ + struct il_host_cmd cmd = { + .id = C_LEDS, + .len = sizeof(struct il_led_cmd), + .data = led_cmd, + .flags = CMD_ASYNC, + .callback = NULL, + }; + + return il_send_cmd(il, &cmd); +} + +#define IL_DECLARE_RATE_INFO(r, ip, in, rp, rn, pp, np) \ + [RATE_##r##M_IDX] = { RATE_##r##M_PLCP, \ + RATE_##r##M_IEEE, \ + RATE_##ip##M_IDX, \ + RATE_##in##M_IDX, \ + RATE_##rp##M_IDX, \ + RATE_##rn##M_IDX, \ + RATE_##pp##M_IDX, \ + RATE_##np##M_IDX, \ + RATE_##r##M_IDX_TBL, \ + RATE_##ip##M_IDX_TBL } + +/* + * Parameter order: + * rate, prev rate, next rate, prev tgg rate, next tgg rate + * + * If there isn't a valid next or previous rate then INV is used which + * maps to RATE_INVALID + * + */ +const struct il3945_rate_info il3945_rates[RATE_COUNT_3945] = { + IL_DECLARE_RATE_INFO(1, INV, 2, INV, 2, INV, 2), /* 1mbps */ + IL_DECLARE_RATE_INFO(2, 1, 5, 1, 5, 1, 5), /* 2mbps */ + IL_DECLARE_RATE_INFO(5, 2, 6, 2, 11, 2, 11), /*5.5mbps */ + IL_DECLARE_RATE_INFO(11, 9, 12, 5, 12, 5, 18), /* 11mbps */ + IL_DECLARE_RATE_INFO(6, 5, 9, 5, 11, 5, 11), /* 6mbps */ + IL_DECLARE_RATE_INFO(9, 6, 11, 5, 11, 5, 11), /* 9mbps */ + IL_DECLARE_RATE_INFO(12, 11, 18, 11, 18, 11, 18), /* 12mbps */ + IL_DECLARE_RATE_INFO(18, 12, 24, 12, 24, 11, 24), /* 18mbps */ + IL_DECLARE_RATE_INFO(24, 18, 36, 18, 36, 18, 36), /* 24mbps */ + IL_DECLARE_RATE_INFO(36, 24, 48, 24, 48, 24, 48), /* 36mbps */ + IL_DECLARE_RATE_INFO(48, 36, 54, 36, 54, 36, 54), /* 48mbps */ + IL_DECLARE_RATE_INFO(54, 48, INV, 48, INV, 48, INV), /* 54mbps */ +}; + +static inline u8 +il3945_get_prev_ieee_rate(u8 rate_idx) +{ + u8 rate = il3945_rates[rate_idx].prev_ieee; + + if (rate == RATE_INVALID) + rate = rate_idx; + return rate; +} + +/* 1 = enable the il3945_disable_events() function */ +#define IL_EVT_DISABLE (0) +#define IL_EVT_DISABLE_SIZE (1532/32) + +/** + * il3945_disable_events - Disable selected events in uCode event log + * + * Disable an event by writing "1"s into "disable" + * bitmap in SRAM. Bit position corresponds to Event # (id/type). + * Default values of 0 enable uCode events to be logged. + * Use for only special debugging. This function is just a placeholder as-is, + * you'll need to provide the special bits! ... + * ... and set IL_EVT_DISABLE to 1. */ +void +il3945_disable_events(struct il_priv *il) +{ + int i; + u32 base; /* SRAM address of event log header */ + u32 disable_ptr; /* SRAM address of event-disable bitmap array */ + u32 array_size; /* # of u32 entries in array */ + static const u32 evt_disable[IL_EVT_DISABLE_SIZE] = { + 0x00000000, /* 31 - 0 Event id numbers */ + 0x00000000, /* 63 - 32 */ + 0x00000000, /* 95 - 64 */ + 0x00000000, /* 127 - 96 */ + 0x00000000, /* 159 - 128 */ + 0x00000000, /* 191 - 160 */ + 0x00000000, /* 223 - 192 */ + 0x00000000, /* 255 - 224 */ + 0x00000000, /* 287 - 256 */ + 0x00000000, /* 319 - 288 */ + 0x00000000, /* 351 - 320 */ + 0x00000000, /* 383 - 352 */ + 0x00000000, /* 415 - 384 */ + 0x00000000, /* 447 - 416 */ + 0x00000000, /* 479 - 448 */ + 0x00000000, /* 511 - 480 */ + 0x00000000, /* 543 - 512 */ + 0x00000000, /* 575 - 544 */ + 0x00000000, /* 607 - 576 */ + 0x00000000, /* 639 - 608 */ + 0x00000000, /* 671 - 640 */ + 0x00000000, /* 703 - 672 */ + 0x00000000, /* 735 - 704 */ + 0x00000000, /* 767 - 736 */ + 0x00000000, /* 799 - 768 */ + 0x00000000, /* 831 - 800 */ + 0x00000000, /* 863 - 832 */ + 0x00000000, /* 895 - 864 */ + 0x00000000, /* 927 - 896 */ + 0x00000000, /* 959 - 928 */ + 0x00000000, /* 991 - 960 */ + 0x00000000, /* 1023 - 992 */ + 0x00000000, /* 1055 - 1024 */ + 0x00000000, /* 1087 - 1056 */ + 0x00000000, /* 1119 - 1088 */ + 0x00000000, /* 1151 - 1120 */ + 0x00000000, /* 1183 - 1152 */ + 0x00000000, /* 1215 - 1184 */ + 0x00000000, /* 1247 - 1216 */ + 0x00000000, /* 1279 - 1248 */ + 0x00000000, /* 1311 - 1280 */ + 0x00000000, /* 1343 - 1312 */ + 0x00000000, /* 1375 - 1344 */ + 0x00000000, /* 1407 - 1376 */ + 0x00000000, /* 1439 - 1408 */ + 0x00000000, /* 1471 - 1440 */ + 0x00000000, /* 1503 - 1472 */ + }; + + base = le32_to_cpu(il->card_alive.log_event_table_ptr); + if (!il3945_hw_valid_rtc_data_addr(base)) { + IL_ERR("Invalid event log pointer 0x%08X\n", base); + return; + } + + disable_ptr = il_read_targ_mem(il, base + (4 * sizeof(u32))); + array_size = il_read_targ_mem(il, base + (5 * sizeof(u32))); + + if (IL_EVT_DISABLE && array_size == IL_EVT_DISABLE_SIZE) { + D_INFO("Disabling selected uCode log events at 0x%x\n", + disable_ptr); + for (i = 0; i < IL_EVT_DISABLE_SIZE; i++) + il_write_targ_mem(il, disable_ptr + (i * sizeof(u32)), + evt_disable[i]); + + } else { + D_INFO("Selected uCode log events may be disabled\n"); + D_INFO(" by writing \"1\"s into disable bitmap\n"); + D_INFO(" in SRAM at 0x%x, size %d u32s\n", disable_ptr, + array_size); + } + +} + +static int +il3945_hwrate_to_plcp_idx(u8 plcp) +{ + int idx; + + for (idx = 0; idx < RATE_COUNT_3945; idx++) + if (il3945_rates[idx].plcp == plcp) + return idx; + return -1; +} + +#ifdef CPTCFG_IWLEGACY_DEBUG +#define TX_STATUS_ENTRY(x) case TX_3945_STATUS_FAIL_ ## x: return #x + +static const char * +il3945_get_tx_fail_reason(u32 status) +{ + switch (status & TX_STATUS_MSK) { + case TX_3945_STATUS_SUCCESS: + return "SUCCESS"; + TX_STATUS_ENTRY(SHORT_LIMIT); + TX_STATUS_ENTRY(LONG_LIMIT); + TX_STATUS_ENTRY(FIFO_UNDERRUN); + TX_STATUS_ENTRY(MGMNT_ABORT); + TX_STATUS_ENTRY(NEXT_FRAG); + TX_STATUS_ENTRY(LIFE_EXPIRE); + TX_STATUS_ENTRY(DEST_PS); + TX_STATUS_ENTRY(ABORTED); + TX_STATUS_ENTRY(BT_RETRY); + TX_STATUS_ENTRY(STA_INVALID); + TX_STATUS_ENTRY(FRAG_DROPPED); + TX_STATUS_ENTRY(TID_DISABLE); + TX_STATUS_ENTRY(FRAME_FLUSHED); + TX_STATUS_ENTRY(INSUFFICIENT_CF_POLL); + TX_STATUS_ENTRY(TX_LOCKED); + TX_STATUS_ENTRY(NO_BEACON_ON_RADAR); + } + + return "UNKNOWN"; +} +#else +static inline const char * +il3945_get_tx_fail_reason(u32 status) +{ + return ""; +} +#endif + +/* + * get ieee prev rate from rate scale table. + * for A and B mode we need to overright prev + * value + */ +int +il3945_rs_next_rate(struct il_priv *il, int rate) +{ + int next_rate = il3945_get_prev_ieee_rate(rate); + + switch (il->band) { + case IEEE80211_BAND_5GHZ: + if (rate == RATE_12M_IDX) + next_rate = RATE_9M_IDX; + else if (rate == RATE_6M_IDX) + next_rate = RATE_6M_IDX; + break; + case IEEE80211_BAND_2GHZ: + if (!(il->_3945.sta_supp_rates & IL_OFDM_RATES_MASK) && + il_is_associated(il)) { + if (rate == RATE_11M_IDX) + next_rate = RATE_5M_IDX; + } + break; + + default: + break; + } + + return next_rate; +} + +/** + * il3945_tx_queue_reclaim - Reclaim Tx queue entries already Tx'd + * + * When FW advances 'R' idx, all entries between old and new 'R' idx + * need to be reclaimed. As result, some free space forms. If there is + * enough free space (> low mark), wake the stack that feeds us. + */ +static void +il3945_tx_queue_reclaim(struct il_priv *il, int txq_id, int idx) +{ + struct il_tx_queue *txq = &il->txq[txq_id]; + struct il_queue *q = &txq->q; + struct sk_buff *skb; + + BUG_ON(txq_id == IL39_CMD_QUEUE_NUM); + + for (idx = il_queue_inc_wrap(idx, q->n_bd); q->read_ptr != idx; + q->read_ptr = il_queue_inc_wrap(q->read_ptr, q->n_bd)) { + + skb = txq->skbs[txq->q.read_ptr]; + ieee80211_tx_status_irqsafe(il->hw, skb); + txq->skbs[txq->q.read_ptr] = NULL; + il->ops->txq_free_tfd(il, txq); + } + + if (il_queue_space(q) > q->low_mark && txq_id >= 0 && + txq_id != IL39_CMD_QUEUE_NUM && il->mac80211_registered) + il_wake_queue(il, txq); +} + +/** + * il3945_hdl_tx - Handle Tx response + */ +static void +il3945_hdl_tx(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + u16 sequence = le16_to_cpu(pkt->hdr.sequence); + int txq_id = SEQ_TO_QUEUE(sequence); + int idx = SEQ_TO_IDX(sequence); + struct il_tx_queue *txq = &il->txq[txq_id]; + struct ieee80211_tx_info *info; + struct il3945_tx_resp *tx_resp = (void *)&pkt->u.raw[0]; + u32 status = le32_to_cpu(tx_resp->status); + int rate_idx; + int fail; + + if (idx >= txq->q.n_bd || il_queue_used(&txq->q, idx) == 0) { + IL_ERR("Read idx for DMA queue txq_id (%d) idx %d " + "is out of range [0-%d] %d %d\n", txq_id, idx, + txq->q.n_bd, txq->q.write_ptr, txq->q.read_ptr); + return; + } + + txq->time_stamp = jiffies; + info = IEEE80211_SKB_CB(txq->skbs[txq->q.read_ptr]); + ieee80211_tx_info_clear_status(info); + + /* Fill the MRR chain with some info about on-chip retransmissions */ + rate_idx = il3945_hwrate_to_plcp_idx(tx_resp->rate); + if (info->band == IEEE80211_BAND_5GHZ) + rate_idx -= IL_FIRST_OFDM_RATE; + + fail = tx_resp->failure_frame; + + info->status.rates[0].idx = rate_idx; + info->status.rates[0].count = fail + 1; /* add final attempt */ + + /* tx_status->rts_retry_count = tx_resp->failure_rts; */ + info->flags |= + ((status & TX_STATUS_MSK) == + TX_STATUS_SUCCESS) ? IEEE80211_TX_STAT_ACK : 0; + + D_TX("Tx queue %d Status %s (0x%08x) plcp rate %d retries %d\n", txq_id, + il3945_get_tx_fail_reason(status), status, tx_resp->rate, + tx_resp->failure_frame); + + D_TX_REPLY("Tx queue reclaim %d\n", idx); + il3945_tx_queue_reclaim(il, txq_id, idx); + + if (status & TX_ABORT_REQUIRED_MSK) + IL_ERR("TODO: Implement Tx ABORT REQUIRED!!!\n"); +} + +/***************************************************************************** + * + * Intel PRO/Wireless 3945ABG/BG Network Connection + * + * RX handler implementations + * + *****************************************************************************/ +#ifdef CPTCFG_IWLEGACY_DEBUGFS +static void +il3945_accumulative_stats(struct il_priv *il, __le32 * stats) +{ + int i; + __le32 *prev_stats; + u32 *accum_stats; + u32 *delta, *max_delta; + + prev_stats = (__le32 *) &il->_3945.stats; + accum_stats = (u32 *) &il->_3945.accum_stats; + delta = (u32 *) &il->_3945.delta_stats; + max_delta = (u32 *) &il->_3945.max_delta; + + for (i = sizeof(__le32); i < sizeof(struct il3945_notif_stats); + i += + sizeof(__le32), stats++, prev_stats++, delta++, max_delta++, + accum_stats++) { + if (le32_to_cpu(*stats) > le32_to_cpu(*prev_stats)) { + *delta = + (le32_to_cpu(*stats) - le32_to_cpu(*prev_stats)); + *accum_stats += *delta; + if (*delta > *max_delta) + *max_delta = *delta; + } + } + + /* reset accumulative stats for "no-counter" type stats */ + il->_3945.accum_stats.general.temperature = + il->_3945.stats.general.temperature; + il->_3945.accum_stats.general.ttl_timestamp = + il->_3945.stats.general.ttl_timestamp; +} +#endif + +void +il3945_hdl_stats(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + + D_RX("Statistics notification received (%d vs %d).\n", + (int)sizeof(struct il3945_notif_stats), + le32_to_cpu(pkt->len_n_flags) & IL_RX_FRAME_SIZE_MSK); +#ifdef CPTCFG_IWLEGACY_DEBUGFS + il3945_accumulative_stats(il, (__le32 *) &pkt->u.raw); +#endif + + memcpy(&il->_3945.stats, pkt->u.raw, sizeof(il->_3945.stats)); +} + +void +il3945_hdl_c_stats(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + __le32 *flag = (__le32 *) &pkt->u.raw; + + if (le32_to_cpu(*flag) & UCODE_STATS_CLEAR_MSK) { +#ifdef CPTCFG_IWLEGACY_DEBUGFS + memset(&il->_3945.accum_stats, 0, + sizeof(struct il3945_notif_stats)); + memset(&il->_3945.delta_stats, 0, + sizeof(struct il3945_notif_stats)); + memset(&il->_3945.max_delta, 0, + sizeof(struct il3945_notif_stats)); +#endif + D_RX("Statistics have been cleared\n"); + } + il3945_hdl_stats(il, rxb); +} + +/****************************************************************************** + * + * Misc. internal state and helper functions + * + ******************************************************************************/ + +/* This is necessary only for a number of stats, see the caller. */ +static int +il3945_is_network_packet(struct il_priv *il, struct ieee80211_hdr *header) +{ + /* Filter incoming packets to determine if they are targeted toward + * this network, discarding packets coming from ourselves */ + switch (il->iw_mode) { + case NL80211_IFTYPE_ADHOC: /* Header: Dest. | Source | BSSID */ + /* packets to our IBSS update information */ + return ether_addr_equal(header->addr3, il->bssid); + case NL80211_IFTYPE_STATION: /* Header: Dest. | AP{BSSID} | Source */ + /* packets to our IBSS update information */ + return ether_addr_equal(header->addr2, il->bssid); + default: + return 1; + } +} + +static void +il3945_pass_packet_to_mac80211(struct il_priv *il, struct il_rx_buf *rxb, + struct ieee80211_rx_status *stats) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)IL_RX_DATA(pkt); + struct il3945_rx_frame_hdr *rx_hdr = IL_RX_HDR(pkt); + struct il3945_rx_frame_end *rx_end = IL_RX_END(pkt); + u16 len = le16_to_cpu(rx_hdr->len); + struct sk_buff *skb; + __le16 fc = hdr->frame_control; + + /* We received data from the HW, so stop the watchdog */ + if (unlikely + (len + IL39_RX_FRAME_SIZE > + PAGE_SIZE << il->hw_params.rx_page_order)) { + D_DROP("Corruption detected!\n"); + return; + } + + /* We only process data packets if the interface is open */ + if (unlikely(!il->is_open)) { + D_DROP("Dropping packet while interface is not open.\n"); + return; + } + + skb = dev_alloc_skb(128); + if (!skb) { + IL_ERR("dev_alloc_skb failed\n"); + return; + } + + if (!il3945_mod_params.sw_crypto) + il_set_decrypted_flag(il, (struct ieee80211_hdr *)rxb_addr(rxb), + le32_to_cpu(rx_end->status), stats); + + skb_add_rx_frag(skb, 0, rxb->page, + (void *)rx_hdr->payload - (void *)pkt, len, + len); + + il_update_stats(il, false, fc, len); + memcpy(IEEE80211_SKB_RXCB(skb), stats, sizeof(*stats)); + + ieee80211_rx(il->hw, skb); + il->alloc_rxb_page--; + rxb->page = NULL; +} + +#define IL_DELAY_NEXT_SCAN_AFTER_ASSOC (HZ*6) + +static void +il3945_hdl_rx(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct ieee80211_hdr *header; + struct ieee80211_rx_status rx_status = {}; + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il3945_rx_frame_stats *rx_stats = IL_RX_STATS(pkt); + struct il3945_rx_frame_hdr *rx_hdr = IL_RX_HDR(pkt); + struct il3945_rx_frame_end *rx_end = IL_RX_END(pkt); + u16 rx_stats_sig_avg __maybe_unused = le16_to_cpu(rx_stats->sig_avg); + u16 rx_stats_noise_diff __maybe_unused = + le16_to_cpu(rx_stats->noise_diff); + u8 network_packet; + + rx_status.flag = 0; + rx_status.mactime = le64_to_cpu(rx_end->timestamp); + rx_status.band = + (rx_hdr-> + phy_flags & RX_RES_PHY_FLAGS_BAND_24_MSK) ? IEEE80211_BAND_2GHZ : + IEEE80211_BAND_5GHZ; + rx_status.freq = + ieee80211_channel_to_frequency(le16_to_cpu(rx_hdr->channel), + rx_status.band); + + rx_status.rate_idx = il3945_hwrate_to_plcp_idx(rx_hdr->rate); + if (rx_status.band == IEEE80211_BAND_5GHZ) + rx_status.rate_idx -= IL_FIRST_OFDM_RATE; + + rx_status.antenna = + (le16_to_cpu(rx_hdr->phy_flags) & RX_RES_PHY_FLAGS_ANTENNA_MSK) >> + 4; + + /* set the preamble flag if appropriate */ + if (rx_hdr->phy_flags & RX_RES_PHY_FLAGS_SHORT_PREAMBLE_MSK) + rx_status.flag |= RX_FLAG_SHORTPRE; + + if ((unlikely(rx_stats->phy_count > 20))) { + D_DROP("dsp size out of range [0,20]: %d/n", + rx_stats->phy_count); + return; + } + + if (!(rx_end->status & RX_RES_STATUS_NO_CRC32_ERROR) || + !(rx_end->status & RX_RES_STATUS_NO_RXE_OVERFLOW)) { + D_RX("Bad CRC or FIFO: 0x%08X.\n", rx_end->status); + return; + } + + /* Convert 3945's rssi indicator to dBm */ + rx_status.signal = rx_stats->rssi - IL39_RSSI_OFFSET; + + D_STATS("Rssi %d sig_avg %d noise_diff %d\n", rx_status.signal, + rx_stats_sig_avg, rx_stats_noise_diff); + + header = (struct ieee80211_hdr *)IL_RX_DATA(pkt); + + network_packet = il3945_is_network_packet(il, header); + + D_STATS("[%c] %d RSSI:%d Signal:%u, Rate:%u\n", + network_packet ? '*' : ' ', le16_to_cpu(rx_hdr->channel), + rx_status.signal, rx_status.signal, rx_status.rate_idx); + + if (network_packet) { + il->_3945.last_beacon_time = + le32_to_cpu(rx_end->beacon_timestamp); + il->_3945.last_tsf = le64_to_cpu(rx_end->timestamp); + il->_3945.last_rx_rssi = rx_status.signal; + } + + il3945_pass_packet_to_mac80211(il, rxb, &rx_status); +} + +int +il3945_hw_txq_attach_buf_to_tfd(struct il_priv *il, struct il_tx_queue *txq, + dma_addr_t addr, u16 len, u8 reset, u8 pad) +{ + int count; + struct il_queue *q; + struct il3945_tfd *tfd, *tfd_tmp; + + q = &txq->q; + tfd_tmp = (struct il3945_tfd *)txq->tfds; + tfd = &tfd_tmp[q->write_ptr]; + + if (reset) + memset(tfd, 0, sizeof(*tfd)); + + count = TFD_CTL_COUNT_GET(le32_to_cpu(tfd->control_flags)); + + if (count >= NUM_TFD_CHUNKS || count < 0) { + IL_ERR("Error can not send more than %d chunks\n", + NUM_TFD_CHUNKS); + return -EINVAL; + } + + tfd->tbs[count].addr = cpu_to_le32(addr); + tfd->tbs[count].len = cpu_to_le32(len); + + count++; + + tfd->control_flags = + cpu_to_le32(TFD_CTL_COUNT_SET(count) | TFD_CTL_PAD_SET(pad)); + + return 0; +} + +/** + * il3945_hw_txq_free_tfd - Free one TFD, those at idx [txq->q.read_ptr] + * + * Does NOT advance any idxes + */ +void +il3945_hw_txq_free_tfd(struct il_priv *il, struct il_tx_queue *txq) +{ + struct il3945_tfd *tfd_tmp = (struct il3945_tfd *)txq->tfds; + int idx = txq->q.read_ptr; + struct il3945_tfd *tfd = &tfd_tmp[idx]; + struct pci_dev *dev = il->pci_dev; + int i; + int counter; + + /* sanity check */ + counter = TFD_CTL_COUNT_GET(le32_to_cpu(tfd->control_flags)); + if (counter > NUM_TFD_CHUNKS) { + IL_ERR("Too many chunks: %i\n", counter); + /* @todo issue fatal error, it is quite serious situation */ + return; + } + + /* Unmap tx_cmd */ + if (counter) + pci_unmap_single(dev, dma_unmap_addr(&txq->meta[idx], mapping), + dma_unmap_len(&txq->meta[idx], len), + PCI_DMA_TODEVICE); + + /* unmap chunks if any */ + + for (i = 1; i < counter; i++) + pci_unmap_single(dev, le32_to_cpu(tfd->tbs[i].addr), + le32_to_cpu(tfd->tbs[i].len), + PCI_DMA_TODEVICE); + + /* free SKB */ + if (txq->skbs) { + struct sk_buff *skb = txq->skbs[txq->q.read_ptr]; + + /* can be called from irqs-disabled context */ + if (skb) { + dev_kfree_skb_any(skb); + txq->skbs[txq->q.read_ptr] = NULL; + } + } +} + +/** + * il3945_hw_build_tx_cmd_rate - Add rate portion to TX_CMD: + * +*/ +void +il3945_hw_build_tx_cmd_rate(struct il_priv *il, struct il_device_cmd *cmd, + struct ieee80211_tx_info *info, + struct ieee80211_hdr *hdr, int sta_id) +{ + u16 hw_value = ieee80211_get_tx_rate(il->hw, info)->hw_value; + u16 rate_idx = min(hw_value & 0xffff, RATE_COUNT_3945 - 1); + u16 rate_mask; + int rate; + const u8 rts_retry_limit = 7; + u8 data_retry_limit; + __le32 tx_flags; + __le16 fc = hdr->frame_control; + struct il3945_tx_cmd *tx_cmd = (struct il3945_tx_cmd *)cmd->cmd.payload; + + rate = il3945_rates[rate_idx].plcp; + tx_flags = tx_cmd->tx_flags; + + /* We need to figure out how to get the sta->supp_rates while + * in this running context */ + rate_mask = RATES_MASK_3945; + + /* Set retry limit on DATA packets and Probe Responses */ + if (ieee80211_is_probe_resp(fc)) + data_retry_limit = 3; + else + data_retry_limit = IL_DEFAULT_TX_RETRY; + tx_cmd->data_retry_limit = data_retry_limit; + /* Set retry limit on RTS packets */ + tx_cmd->rts_retry_limit = min(data_retry_limit, rts_retry_limit); + + tx_cmd->rate = rate; + tx_cmd->tx_flags = tx_flags; + + /* OFDM */ + tx_cmd->supp_rates[0] = + ((rate_mask & IL_OFDM_RATES_MASK) >> IL_FIRST_OFDM_RATE) & 0xFF; + + /* CCK */ + tx_cmd->supp_rates[1] = (rate_mask & 0xF); + + D_RATE("Tx sta id: %d, rate: %d (plcp), flags: 0x%4X " + "cck/ofdm mask: 0x%x/0x%x\n", sta_id, tx_cmd->rate, + le32_to_cpu(tx_cmd->tx_flags), tx_cmd->supp_rates[1], + tx_cmd->supp_rates[0]); +} + +static u8 +il3945_sync_sta(struct il_priv *il, int sta_id, u16 tx_rate) +{ + unsigned long flags_spin; + struct il_station_entry *station; + + if (sta_id == IL_INVALID_STATION) + return IL_INVALID_STATION; + + spin_lock_irqsave(&il->sta_lock, flags_spin); + station = &il->stations[sta_id]; + + station->sta.sta.modify_mask = STA_MODIFY_TX_RATE_MSK; + station->sta.rate_n_flags = cpu_to_le16(tx_rate); + station->sta.mode = STA_CONTROL_MODIFY_MSK; + il_send_add_sta(il, &station->sta, CMD_ASYNC); + spin_unlock_irqrestore(&il->sta_lock, flags_spin); + + D_RATE("SCALE sync station %d to rate %d\n", sta_id, tx_rate); + return sta_id; +} + +static void +il3945_set_pwr_vmain(struct il_priv *il) +{ +/* + * (for documentation purposes) + * to set power to V_AUX, do + + if (pci_pme_capable(il->pci_dev, PCI_D3cold)) { + il_set_bits_mask_prph(il, APMG_PS_CTRL_REG, + APMG_PS_CTRL_VAL_PWR_SRC_VAUX, + ~APMG_PS_CTRL_MSK_PWR_SRC); + + _il_poll_bit(il, CSR_GPIO_IN, + CSR_GPIO_IN_VAL_VAUX_PWR_SRC, + CSR_GPIO_IN_BIT_AUX_POWER, 5000); + } + */ + + il_set_bits_mask_prph(il, APMG_PS_CTRL_REG, + APMG_PS_CTRL_VAL_PWR_SRC_VMAIN, + ~APMG_PS_CTRL_MSK_PWR_SRC); + + _il_poll_bit(il, CSR_GPIO_IN, CSR_GPIO_IN_VAL_VMAIN_PWR_SRC, + CSR_GPIO_IN_BIT_AUX_POWER, 5000); +} + +static int +il3945_rx_init(struct il_priv *il, struct il_rx_queue *rxq) +{ + il_wr(il, FH39_RCSR_RBD_BASE(0), rxq->bd_dma); + il_wr(il, FH39_RCSR_RPTR_ADDR(0), rxq->rb_stts_dma); + il_wr(il, FH39_RCSR_WPTR(0), 0); + il_wr(il, FH39_RCSR_CONFIG(0), + FH39_RCSR_RX_CONFIG_REG_VAL_DMA_CHNL_EN_ENABLE | + FH39_RCSR_RX_CONFIG_REG_VAL_RDRBD_EN_ENABLE | + FH39_RCSR_RX_CONFIG_REG_BIT_WR_STTS_EN | + FH39_RCSR_RX_CONFIG_REG_VAL_MAX_FRAG_SIZE_128 | (RX_QUEUE_SIZE_LOG + << + FH39_RCSR_RX_CONFIG_REG_POS_RBDC_SIZE) + | FH39_RCSR_RX_CONFIG_REG_VAL_IRQ_DEST_INT_HOST | (1 << + FH39_RCSR_RX_CONFIG_REG_POS_IRQ_RBTH) + | FH39_RCSR_RX_CONFIG_REG_VAL_MSG_MODE_FH); + + /* fake read to flush all prev I/O */ + il_rd(il, FH39_RSSR_CTRL); + + return 0; +} + +static int +il3945_tx_reset(struct il_priv *il) +{ + /* bypass mode */ + il_wr_prph(il, ALM_SCD_MODE_REG, 0x2); + + /* RA 0 is active */ + il_wr_prph(il, ALM_SCD_ARASTAT_REG, 0x01); + + /* all 6 fifo are active */ + il_wr_prph(il, ALM_SCD_TXFACT_REG, 0x3f); + + il_wr_prph(il, ALM_SCD_SBYP_MODE_1_REG, 0x010000); + il_wr_prph(il, ALM_SCD_SBYP_MODE_2_REG, 0x030002); + il_wr_prph(il, ALM_SCD_TXF4MF_REG, 0x000004); + il_wr_prph(il, ALM_SCD_TXF5MF_REG, 0x000005); + + il_wr(il, FH39_TSSR_CBB_BASE, il->_3945.shared_phys); + + il_wr(il, FH39_TSSR_MSG_CONFIG, + FH39_TSSR_TX_MSG_CONFIG_REG_VAL_SNOOP_RD_TXPD_ON | + FH39_TSSR_TX_MSG_CONFIG_REG_VAL_ORDER_RD_TXPD_ON | + FH39_TSSR_TX_MSG_CONFIG_REG_VAL_MAX_FRAG_SIZE_128B | + FH39_TSSR_TX_MSG_CONFIG_REG_VAL_SNOOP_RD_TFD_ON | + FH39_TSSR_TX_MSG_CONFIG_REG_VAL_ORDER_RD_CBB_ON | + FH39_TSSR_TX_MSG_CONFIG_REG_VAL_ORDER_RSP_WAIT_TH | + FH39_TSSR_TX_MSG_CONFIG_REG_VAL_RSP_WAIT_TH); + + return 0; +} + +/** + * il3945_txq_ctx_reset - Reset TX queue context + * + * Destroys all DMA structures and initialize them again + */ +static int +il3945_txq_ctx_reset(struct il_priv *il) +{ + int rc, txq_id; + + il3945_hw_txq_ctx_free(il); + + /* allocate tx queue structure */ + rc = il_alloc_txq_mem(il); + if (rc) + return rc; + + /* Tx CMD queue */ + rc = il3945_tx_reset(il); + if (rc) + goto error; + + /* Tx queue(s) */ + for (txq_id = 0; txq_id < il->hw_params.max_txq_num; txq_id++) { + rc = il_tx_queue_init(il, txq_id); + if (rc) { + IL_ERR("Tx %d queue init failed\n", txq_id); + goto error; + } + } + + return rc; + +error: + il3945_hw_txq_ctx_free(il); + return rc; +} + +/* + * Start up 3945's basic functionality after it has been reset + * (e.g. after platform boot, or shutdown via il_apm_stop()) + * NOTE: This does not load uCode nor start the embedded processor + */ +static int +il3945_apm_init(struct il_priv *il) +{ + int ret = il_apm_init(il); + + /* Clear APMG (NIC's internal power management) interrupts */ + il_wr_prph(il, APMG_RTC_INT_MSK_REG, 0x0); + il_wr_prph(il, APMG_RTC_INT_STT_REG, 0xFFFFFFFF); + + /* Reset radio chip */ + il_set_bits_prph(il, APMG_PS_CTRL_REG, APMG_PS_CTRL_VAL_RESET_REQ); + udelay(5); + il_clear_bits_prph(il, APMG_PS_CTRL_REG, APMG_PS_CTRL_VAL_RESET_REQ); + + return ret; +} + +static void +il3945_nic_config(struct il_priv *il) +{ + struct il3945_eeprom *eeprom = (struct il3945_eeprom *)il->eeprom; + unsigned long flags; + u8 rev_id = il->pci_dev->revision; + + spin_lock_irqsave(&il->lock, flags); + + /* Determine HW type */ + D_INFO("HW Revision ID = 0x%X\n", rev_id); + + if (rev_id & PCI_CFG_REV_ID_BIT_RTP) + D_INFO("RTP type\n"); + else if (rev_id & PCI_CFG_REV_ID_BIT_BASIC_SKU) { + D_INFO("3945 RADIO-MB type\n"); + il_set_bit(il, CSR_HW_IF_CONFIG_REG, + CSR39_HW_IF_CONFIG_REG_BIT_3945_MB); + } else { + D_INFO("3945 RADIO-MM type\n"); + il_set_bit(il, CSR_HW_IF_CONFIG_REG, + CSR39_HW_IF_CONFIG_REG_BIT_3945_MM); + } + + if (EEPROM_SKU_CAP_OP_MODE_MRC == eeprom->sku_cap) { + D_INFO("SKU OP mode is mrc\n"); + il_set_bit(il, CSR_HW_IF_CONFIG_REG, + CSR39_HW_IF_CONFIG_REG_BIT_SKU_MRC); + } else + D_INFO("SKU OP mode is basic\n"); + + if ((eeprom->board_revision & 0xF0) == 0xD0) { + D_INFO("3945ABG revision is 0x%X\n", eeprom->board_revision); + il_set_bit(il, CSR_HW_IF_CONFIG_REG, + CSR39_HW_IF_CONFIG_REG_BIT_BOARD_TYPE); + } else { + D_INFO("3945ABG revision is 0x%X\n", eeprom->board_revision); + il_clear_bit(il, CSR_HW_IF_CONFIG_REG, + CSR39_HW_IF_CONFIG_REG_BIT_BOARD_TYPE); + } + + if (eeprom->almgor_m_version <= 1) { + il_set_bit(il, CSR_HW_IF_CONFIG_REG, + CSR39_HW_IF_CONFIG_REG_BITS_SILICON_TYPE_A); + D_INFO("Card M type A version is 0x%X\n", + eeprom->almgor_m_version); + } else { + D_INFO("Card M type B version is 0x%X\n", + eeprom->almgor_m_version); + il_set_bit(il, CSR_HW_IF_CONFIG_REG, + CSR39_HW_IF_CONFIG_REG_BITS_SILICON_TYPE_B); + } + spin_unlock_irqrestore(&il->lock, flags); + + if (eeprom->sku_cap & EEPROM_SKU_CAP_SW_RF_KILL_ENABLE) + D_RF_KILL("SW RF KILL supported in EEPROM.\n"); + + if (eeprom->sku_cap & EEPROM_SKU_CAP_HW_RF_KILL_ENABLE) + D_RF_KILL("HW RF KILL supported in EEPROM.\n"); +} + +int +il3945_hw_nic_init(struct il_priv *il) +{ + int rc; + unsigned long flags; + struct il_rx_queue *rxq = &il->rxq; + + spin_lock_irqsave(&il->lock, flags); + il3945_apm_init(il); + spin_unlock_irqrestore(&il->lock, flags); + + il3945_set_pwr_vmain(il); + il3945_nic_config(il); + + /* Allocate the RX queue, or reset if it is already allocated */ + if (!rxq->bd) { + rc = il_rx_queue_alloc(il); + if (rc) { + IL_ERR("Unable to initialize Rx queue\n"); + return -ENOMEM; + } + } else + il3945_rx_queue_reset(il, rxq); + + il3945_rx_replenish(il); + + il3945_rx_init(il, rxq); + + /* Look at using this instead: + rxq->need_update = 1; + il_rx_queue_update_write_ptr(il, rxq); + */ + + il_wr(il, FH39_RCSR_WPTR(0), rxq->write & ~7); + + rc = il3945_txq_ctx_reset(il); + if (rc) + return rc; + + set_bit(S_INIT, &il->status); + + return 0; +} + +/** + * il3945_hw_txq_ctx_free - Free TXQ Context + * + * Destroy all TX DMA queues and structures + */ +void +il3945_hw_txq_ctx_free(struct il_priv *il) +{ + int txq_id; + + /* Tx queues */ + if (il->txq) + for (txq_id = 0; txq_id < il->hw_params.max_txq_num; txq_id++) + if (txq_id == IL39_CMD_QUEUE_NUM) + il_cmd_queue_free(il); + else + il_tx_queue_free(il, txq_id); + + /* free tx queue structure */ + il_free_txq_mem(il); +} + +void +il3945_hw_txq_ctx_stop(struct il_priv *il) +{ + int txq_id; + + /* stop SCD */ + _il_wr_prph(il, ALM_SCD_MODE_REG, 0); + _il_wr_prph(il, ALM_SCD_TXFACT_REG, 0); + + /* reset TFD queues */ + for (txq_id = 0; txq_id < il->hw_params.max_txq_num; txq_id++) { + _il_wr(il, FH39_TCSR_CONFIG(txq_id), 0x0); + _il_poll_bit(il, FH39_TSSR_TX_STATUS, + FH39_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(txq_id), + FH39_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(txq_id), + 1000); + } +} + +/** + * il3945_hw_reg_adjust_power_by_temp + * return idx delta into power gain settings table +*/ +static int +il3945_hw_reg_adjust_power_by_temp(int new_reading, int old_reading) +{ + return (new_reading - old_reading) * (-11) / 100; +} + +/** + * il3945_hw_reg_temp_out_of_range - Keep temperature in sane range + */ +static inline int +il3945_hw_reg_temp_out_of_range(int temperature) +{ + return (temperature < -260 || temperature > 25) ? 1 : 0; +} + +int +il3945_hw_get_temperature(struct il_priv *il) +{ + return _il_rd(il, CSR_UCODE_DRV_GP2); +} + +/** + * il3945_hw_reg_txpower_get_temperature + * get the current temperature by reading from NIC +*/ +static int +il3945_hw_reg_txpower_get_temperature(struct il_priv *il) +{ + struct il3945_eeprom *eeprom = (struct il3945_eeprom *)il->eeprom; + int temperature; + + temperature = il3945_hw_get_temperature(il); + + /* driver's okay range is -260 to +25. + * human readable okay range is 0 to +285 */ + D_INFO("Temperature: %d\n", temperature + IL_TEMP_CONVERT); + + /* handle insane temp reading */ + if (il3945_hw_reg_temp_out_of_range(temperature)) { + IL_ERR("Error bad temperature value %d\n", temperature); + + /* if really really hot(?), + * substitute the 3rd band/group's temp measured at factory */ + if (il->last_temperature > 100) + temperature = eeprom->groups[2].temperature; + else /* else use most recent "sane" value from driver */ + temperature = il->last_temperature; + } + + return temperature; /* raw, not "human readable" */ +} + +/* Adjust Txpower only if temperature variance is greater than threshold. + * + * Both are lower than older versions' 9 degrees */ +#define IL_TEMPERATURE_LIMIT_TIMER 6 + +/** + * il3945_is_temp_calib_needed - determines if new calibration is needed + * + * records new temperature in tx_mgr->temperature. + * replaces tx_mgr->last_temperature *only* if calib needed + * (assumes caller will actually do the calibration!). */ +static int +il3945_is_temp_calib_needed(struct il_priv *il) +{ + int temp_diff; + + il->temperature = il3945_hw_reg_txpower_get_temperature(il); + temp_diff = il->temperature - il->last_temperature; + + /* get absolute value */ + if (temp_diff < 0) { + D_POWER("Getting cooler, delta %d,\n", temp_diff); + temp_diff = -temp_diff; + } else if (temp_diff == 0) + D_POWER("Same temp,\n"); + else + D_POWER("Getting warmer, delta %d,\n", temp_diff); + + /* if we don't need calibration, *don't* update last_temperature */ + if (temp_diff < IL_TEMPERATURE_LIMIT_TIMER) { + D_POWER("Timed thermal calib not needed\n"); + return 0; + } + + D_POWER("Timed thermal calib needed\n"); + + /* assume that caller will actually do calib ... + * update the "last temperature" value */ + il->last_temperature = il->temperature; + return 1; +} + +#define IL_MAX_GAIN_ENTRIES 78 +#define IL_CCK_FROM_OFDM_POWER_DIFF -5 +#define IL_CCK_FROM_OFDM_IDX_DIFF (10) + +/* radio and DSP power table, each step is 1/2 dB. + * 1st number is for RF analog gain, 2nd number is for DSP pre-DAC gain. */ +static struct il3945_tx_power power_gain_table[2][IL_MAX_GAIN_ENTRIES] = { + { + {251, 127}, /* 2.4 GHz, highest power */ + {251, 127}, + {251, 127}, + {251, 127}, + {251, 125}, + {251, 110}, + {251, 105}, + {251, 98}, + {187, 125}, + {187, 115}, + {187, 108}, + {187, 99}, + {243, 119}, + {243, 111}, + {243, 105}, + {243, 97}, + {243, 92}, + {211, 106}, + {211, 100}, + {179, 120}, + {179, 113}, + {179, 107}, + {147, 125}, + {147, 119}, + {147, 112}, + {147, 106}, + {147, 101}, + {147, 97}, + {147, 91}, + {115, 107}, + {235, 121}, + {235, 115}, + {235, 109}, + {203, 127}, + {203, 121}, + {203, 115}, + {203, 108}, + {203, 102}, + {203, 96}, + {203, 92}, + {171, 110}, + {171, 104}, + {171, 98}, + {139, 116}, + {227, 125}, + {227, 119}, + {227, 113}, + {227, 107}, + {227, 101}, + {227, 96}, + {195, 113}, + {195, 106}, + {195, 102}, + {195, 95}, + {163, 113}, + {163, 106}, + {163, 102}, + {163, 95}, + {131, 113}, + {131, 106}, + {131, 102}, + {131, 95}, + {99, 113}, + {99, 106}, + {99, 102}, + {99, 95}, + {67, 113}, + {67, 106}, + {67, 102}, + {67, 95}, + {35, 113}, + {35, 106}, + {35, 102}, + {35, 95}, + {3, 113}, + {3, 106}, + {3, 102}, + {3, 95} /* 2.4 GHz, lowest power */ + }, + { + {251, 127}, /* 5.x GHz, highest power */ + {251, 120}, + {251, 114}, + {219, 119}, + {219, 101}, + {187, 113}, + {187, 102}, + {155, 114}, + {155, 103}, + {123, 117}, + {123, 107}, + {123, 99}, + {123, 92}, + {91, 108}, + {59, 125}, + {59, 118}, + {59, 109}, + {59, 102}, + {59, 96}, + {59, 90}, + {27, 104}, + {27, 98}, + {27, 92}, + {115, 118}, + {115, 111}, + {115, 104}, + {83, 126}, + {83, 121}, + {83, 113}, + {83, 105}, + {83, 99}, + {51, 118}, + {51, 111}, + {51, 104}, + {51, 98}, + {19, 116}, + {19, 109}, + {19, 102}, + {19, 98}, + {19, 93}, + {171, 113}, + {171, 107}, + {171, 99}, + {139, 120}, + {139, 113}, + {139, 107}, + {139, 99}, + {107, 120}, + {107, 113}, + {107, 107}, + {107, 99}, + {75, 120}, + {75, 113}, + {75, 107}, + {75, 99}, + {43, 120}, + {43, 113}, + {43, 107}, + {43, 99}, + {11, 120}, + {11, 113}, + {11, 107}, + {11, 99}, + {131, 107}, + {131, 99}, + {99, 120}, + {99, 113}, + {99, 107}, + {99, 99}, + {67, 120}, + {67, 113}, + {67, 107}, + {67, 99}, + {35, 120}, + {35, 113}, + {35, 107}, + {35, 99}, + {3, 120} /* 5.x GHz, lowest power */ + } +}; + +static inline u8 +il3945_hw_reg_fix_power_idx(int idx) +{ + if (idx < 0) + return 0; + if (idx >= IL_MAX_GAIN_ENTRIES) + return IL_MAX_GAIN_ENTRIES - 1; + return (u8) idx; +} + +/* Kick off thermal recalibration check every 60 seconds */ +#define REG_RECALIB_PERIOD (60) + +/** + * il3945_hw_reg_set_scan_power - Set Tx power for scan probe requests + * + * Set (in our channel info database) the direct scan Tx power for 1 Mbit (CCK) + * or 6 Mbit (OFDM) rates. + */ +static void +il3945_hw_reg_set_scan_power(struct il_priv *il, u32 scan_tbl_idx, s32 rate_idx, + const s8 *clip_pwrs, + struct il_channel_info *ch_info, int band_idx) +{ + struct il3945_scan_power_info *scan_power_info; + s8 power; + u8 power_idx; + + scan_power_info = &ch_info->scan_pwr_info[scan_tbl_idx]; + + /* use this channel group's 6Mbit clipping/saturation pwr, + * but cap at regulatory scan power restriction (set during init + * based on eeprom channel data) for this channel. */ + power = min(ch_info->scan_power, clip_pwrs[RATE_6M_IDX_TBL]); + + power = min(power, il->tx_power_user_lmt); + scan_power_info->requested_power = power; + + /* find difference between new scan *power* and current "normal" + * Tx *power* for 6Mb. Use this difference (x2) to adjust the + * current "normal" temperature-compensated Tx power *idx* for + * this rate (1Mb or 6Mb) to yield new temp-compensated scan power + * *idx*. */ + power_idx = + ch_info->power_info[rate_idx].power_table_idx - (power - + ch_info-> + power_info + [RATE_6M_IDX_TBL]. + requested_power) * + 2; + + /* store reference idx that we use when adjusting *all* scan + * powers. So we can accommodate user (all channel) or spectrum + * management (single channel) power changes "between" temperature + * feedback compensation procedures. + * don't force fit this reference idx into gain table; it may be a + * negative number. This will help avoid errors when we're at + * the lower bounds (highest gains, for warmest temperatures) + * of the table. */ + + /* don't exceed table bounds for "real" setting */ + power_idx = il3945_hw_reg_fix_power_idx(power_idx); + + scan_power_info->power_table_idx = power_idx; + scan_power_info->tpc.tx_gain = + power_gain_table[band_idx][power_idx].tx_gain; + scan_power_info->tpc.dsp_atten = + power_gain_table[band_idx][power_idx].dsp_atten; +} + +/** + * il3945_send_tx_power - fill in Tx Power command with gain settings + * + * Configures power settings for all rates for the current channel, + * using values from channel info struct, and send to NIC + */ +static int +il3945_send_tx_power(struct il_priv *il) +{ + int rate_idx, i; + const struct il_channel_info *ch_info = NULL; + struct il3945_txpowertable_cmd txpower = { + .channel = il->active.channel, + }; + u16 chan; + + if (WARN_ONCE + (test_bit(S_SCAN_HW, &il->status), + "TX Power requested while scanning!\n")) + return -EAGAIN; + + chan = le16_to_cpu(il->active.channel); + + txpower.band = (il->band == IEEE80211_BAND_5GHZ) ? 0 : 1; + ch_info = il_get_channel_info(il, il->band, chan); + if (!ch_info) { + IL_ERR("Failed to get channel info for channel %d [%d]\n", chan, + il->band); + return -EINVAL; + } + + if (!il_is_channel_valid(ch_info)) { + D_POWER("Not calling TX_PWR_TBL_CMD on " "non-Tx channel.\n"); + return 0; + } + + /* fill cmd with power settings for all rates for current channel */ + /* Fill OFDM rate */ + for (rate_idx = IL_FIRST_OFDM_RATE, i = 0; + rate_idx <= IL39_LAST_OFDM_RATE; rate_idx++, i++) { + + txpower.power[i].tpc = ch_info->power_info[i].tpc; + txpower.power[i].rate = il3945_rates[rate_idx].plcp; + + D_POWER("ch %d:%d rf %d dsp %3d rate code 0x%02x\n", + le16_to_cpu(txpower.channel), txpower.band, + txpower.power[i].tpc.tx_gain, + txpower.power[i].tpc.dsp_atten, txpower.power[i].rate); + } + /* Fill CCK rates */ + for (rate_idx = IL_FIRST_CCK_RATE; rate_idx <= IL_LAST_CCK_RATE; + rate_idx++, i++) { + txpower.power[i].tpc = ch_info->power_info[i].tpc; + txpower.power[i].rate = il3945_rates[rate_idx].plcp; + + D_POWER("ch %d:%d rf %d dsp %3d rate code 0x%02x\n", + le16_to_cpu(txpower.channel), txpower.band, + txpower.power[i].tpc.tx_gain, + txpower.power[i].tpc.dsp_atten, txpower.power[i].rate); + } + + return il_send_cmd_pdu(il, C_TX_PWR_TBL, + sizeof(struct il3945_txpowertable_cmd), + &txpower); + +} + +/** + * il3945_hw_reg_set_new_power - Configures power tables at new levels + * @ch_info: Channel to update. Uses power_info.requested_power. + * + * Replace requested_power and base_power_idx ch_info fields for + * one channel. + * + * Called if user or spectrum management changes power preferences. + * Takes into account h/w and modulation limitations (clip power). + * + * This does *not* send anything to NIC, just sets up ch_info for one channel. + * + * NOTE: reg_compensate_for_temperature_dif() *must* be run after this to + * properly fill out the scan powers, and actual h/w gain settings, + * and send changes to NIC + */ +static int +il3945_hw_reg_set_new_power(struct il_priv *il, struct il_channel_info *ch_info) +{ + struct il3945_channel_power_info *power_info; + int power_changed = 0; + int i; + const s8 *clip_pwrs; + int power; + + /* Get this chnlgrp's rate-to-max/clip-powers table */ + clip_pwrs = il->_3945.clip_groups[ch_info->group_idx].clip_powers; + + /* Get this channel's rate-to-current-power settings table */ + power_info = ch_info->power_info; + + /* update OFDM Txpower settings */ + for (i = RATE_6M_IDX_TBL; i <= RATE_54M_IDX_TBL; i++, ++power_info) { + int delta_idx; + + /* limit new power to be no more than h/w capability */ + power = min(ch_info->curr_txpow, clip_pwrs[i]); + if (power == power_info->requested_power) + continue; + + /* find difference between old and new requested powers, + * update base (non-temp-compensated) power idx */ + delta_idx = (power - power_info->requested_power) * 2; + power_info->base_power_idx -= delta_idx; + + /* save new requested power value */ + power_info->requested_power = power; + + power_changed = 1; + } + + /* update CCK Txpower settings, based on OFDM 12M setting ... + * ... all CCK power settings for a given channel are the *same*. */ + if (power_changed) { + power = + ch_info->power_info[RATE_12M_IDX_TBL].requested_power + + IL_CCK_FROM_OFDM_POWER_DIFF; + + /* do all CCK rates' il3945_channel_power_info structures */ + for (i = RATE_1M_IDX_TBL; i <= RATE_11M_IDX_TBL; i++) { + power_info->requested_power = power; + power_info->base_power_idx = + ch_info->power_info[RATE_12M_IDX_TBL]. + base_power_idx + IL_CCK_FROM_OFDM_IDX_DIFF; + ++power_info; + } + } + + return 0; +} + +/** + * il3945_hw_reg_get_ch_txpower_limit - returns new power limit for channel + * + * NOTE: Returned power limit may be less (but not more) than requested, + * based strictly on regulatory (eeprom and spectrum mgt) limitations + * (no consideration for h/w clipping limitations). + */ +static int +il3945_hw_reg_get_ch_txpower_limit(struct il_channel_info *ch_info) +{ + s8 max_power; + +#if 0 + /* if we're using TGd limits, use lower of TGd or EEPROM */ + if (ch_info->tgd_data.max_power != 0) + max_power = + min(ch_info->tgd_data.max_power, + ch_info->eeprom.max_power_avg); + + /* else just use EEPROM limits */ + else +#endif + max_power = ch_info->eeprom.max_power_avg; + + return min(max_power, ch_info->max_power_avg); +} + +/** + * il3945_hw_reg_comp_txpower_temp - Compensate for temperature + * + * Compensate txpower settings of *all* channels for temperature. + * This only accounts for the difference between current temperature + * and the factory calibration temperatures, and bases the new settings + * on the channel's base_power_idx. + * + * If RxOn is "associated", this sends the new Txpower to NIC! + */ +static int +il3945_hw_reg_comp_txpower_temp(struct il_priv *il) +{ + struct il_channel_info *ch_info = NULL; + struct il3945_eeprom *eeprom = (struct il3945_eeprom *)il->eeprom; + int delta_idx; + const s8 *clip_pwrs; /* array of h/w max power levels for each rate */ + u8 a_band; + u8 rate_idx; + u8 scan_tbl_idx; + u8 i; + int ref_temp; + int temperature = il->temperature; + + if (il->disable_tx_power_cal || test_bit(S_SCANNING, &il->status)) { + /* do not perform tx power calibration */ + return 0; + } + /* set up new Tx power info for each and every channel, 2.4 and 5.x */ + for (i = 0; i < il->channel_count; i++) { + ch_info = &il->channel_info[i]; + a_band = il_is_channel_a_band(ch_info); + + /* Get this chnlgrp's factory calibration temperature */ + ref_temp = (s16) eeprom->groups[ch_info->group_idx].temperature; + + /* get power idx adjustment based on current and factory + * temps */ + delta_idx = + il3945_hw_reg_adjust_power_by_temp(temperature, ref_temp); + + /* set tx power value for all rates, OFDM and CCK */ + for (rate_idx = 0; rate_idx < RATE_COUNT_3945; rate_idx++) { + int power_idx = + ch_info->power_info[rate_idx].base_power_idx; + + /* temperature compensate */ + power_idx += delta_idx; + + /* stay within table range */ + power_idx = il3945_hw_reg_fix_power_idx(power_idx); + ch_info->power_info[rate_idx].power_table_idx = + (u8) power_idx; + ch_info->power_info[rate_idx].tpc = + power_gain_table[a_band][power_idx]; + } + + /* Get this chnlgrp's rate-to-max/clip-powers table */ + clip_pwrs = + il->_3945.clip_groups[ch_info->group_idx].clip_powers; + + /* set scan tx power, 1Mbit for CCK, 6Mbit for OFDM */ + for (scan_tbl_idx = 0; scan_tbl_idx < IL_NUM_SCAN_RATES; + scan_tbl_idx++) { + s32 actual_idx = + (scan_tbl_idx == + 0) ? RATE_1M_IDX_TBL : RATE_6M_IDX_TBL; + il3945_hw_reg_set_scan_power(il, scan_tbl_idx, + actual_idx, clip_pwrs, + ch_info, a_band); + } + } + + /* send Txpower command for current channel to ucode */ + return il->ops->send_tx_power(il); +} + +int +il3945_hw_reg_set_txpower(struct il_priv *il, s8 power) +{ + struct il_channel_info *ch_info; + s8 max_power; + u8 a_band; + u8 i; + + if (il->tx_power_user_lmt == power) { + D_POWER("Requested Tx power same as current " "limit: %ddBm.\n", + power); + return 0; + } + + D_POWER("Setting upper limit clamp to %ddBm.\n", power); + il->tx_power_user_lmt = power; + + /* set up new Tx powers for each and every channel, 2.4 and 5.x */ + + for (i = 0; i < il->channel_count; i++) { + ch_info = &il->channel_info[i]; + a_band = il_is_channel_a_band(ch_info); + + /* find minimum power of all user and regulatory constraints + * (does not consider h/w clipping limitations) */ + max_power = il3945_hw_reg_get_ch_txpower_limit(ch_info); + max_power = min(power, max_power); + if (max_power != ch_info->curr_txpow) { + ch_info->curr_txpow = max_power; + + /* this considers the h/w clipping limitations */ + il3945_hw_reg_set_new_power(il, ch_info); + } + } + + /* update txpower settings for all channels, + * send to NIC if associated. */ + il3945_is_temp_calib_needed(il); + il3945_hw_reg_comp_txpower_temp(il); + + return 0; +} + +static int +il3945_send_rxon_assoc(struct il_priv *il) +{ + int rc = 0; + struct il_rx_pkt *pkt; + struct il3945_rxon_assoc_cmd rxon_assoc; + struct il_host_cmd cmd = { + .id = C_RXON_ASSOC, + .len = sizeof(rxon_assoc), + .flags = CMD_WANT_SKB, + .data = &rxon_assoc, + }; + const struct il_rxon_cmd *rxon1 = &il->staging; + const struct il_rxon_cmd *rxon2 = &il->active; + + if (rxon1->flags == rxon2->flags && + rxon1->filter_flags == rxon2->filter_flags && + rxon1->cck_basic_rates == rxon2->cck_basic_rates && + rxon1->ofdm_basic_rates == rxon2->ofdm_basic_rates) { + D_INFO("Using current RXON_ASSOC. Not resending.\n"); + return 0; + } + + rxon_assoc.flags = il->staging.flags; + rxon_assoc.filter_flags = il->staging.filter_flags; + rxon_assoc.ofdm_basic_rates = il->staging.ofdm_basic_rates; + rxon_assoc.cck_basic_rates = il->staging.cck_basic_rates; + rxon_assoc.reserved = 0; + + rc = il_send_cmd_sync(il, &cmd); + if (rc) + return rc; + + pkt = (struct il_rx_pkt *)cmd.reply_page; + if (pkt->hdr.flags & IL_CMD_FAILED_MSK) { + IL_ERR("Bad return from C_RXON_ASSOC command\n"); + rc = -EIO; + } + + il_free_pages(il, cmd.reply_page); + + return rc; +} + +/** + * il3945_commit_rxon - commit staging_rxon to hardware + * + * The RXON command in staging_rxon is committed to the hardware and + * the active_rxon structure is updated with the new data. This + * function correctly transitions out of the RXON_ASSOC_MSK state if + * a HW tune is required based on the RXON structure changes. + */ +int +il3945_commit_rxon(struct il_priv *il) +{ + /* cast away the const for active_rxon in this function */ + struct il3945_rxon_cmd *active_rxon = (void *)&il->active; + struct il3945_rxon_cmd *staging_rxon = (void *)&il->staging; + int rc = 0; + bool new_assoc = !!(staging_rxon->filter_flags & RXON_FILTER_ASSOC_MSK); + + if (test_bit(S_EXIT_PENDING, &il->status)) + return -EINVAL; + + if (!il_is_alive(il)) + return -1; + + /* always get timestamp with Rx frame */ + staging_rxon->flags |= RXON_FLG_TSF2HOST_MSK; + + /* select antenna */ + staging_rxon->flags &= ~(RXON_FLG_DIS_DIV_MSK | RXON_FLG_ANT_SEL_MSK); + staging_rxon->flags |= il3945_get_antenna_flags(il); + + rc = il_check_rxon_cmd(il); + if (rc) { + IL_ERR("Invalid RXON configuration. Not committing.\n"); + return -EINVAL; + } + + /* If we don't need to send a full RXON, we can use + * il3945_rxon_assoc_cmd which is used to reconfigure filter + * and other flags for the current radio configuration. */ + if (!il_full_rxon_required(il)) { + rc = il_send_rxon_assoc(il); + if (rc) { + IL_ERR("Error setting RXON_ASSOC " + "configuration (%d).\n", rc); + return rc; + } + + memcpy(active_rxon, staging_rxon, sizeof(*active_rxon)); + /* + * We do not commit tx power settings while channel changing, + * do it now if tx power changed. + */ + il_set_tx_power(il, il->tx_power_next, false); + return 0; + } + + /* If we are currently associated and the new config requires + * an RXON_ASSOC and the new config wants the associated mask enabled, + * we must clear the associated from the active configuration + * before we apply the new config */ + if (il_is_associated(il) && new_assoc) { + D_INFO("Toggling associated bit on current RXON\n"); + active_rxon->filter_flags &= ~RXON_FILTER_ASSOC_MSK; + + /* + * reserved4 and 5 could have been filled by the iwlcore code. + * Let's clear them before pushing to the 3945. + */ + active_rxon->reserved4 = 0; + active_rxon->reserved5 = 0; + rc = il_send_cmd_pdu(il, C_RXON, sizeof(struct il3945_rxon_cmd), + &il->active); + + /* If the mask clearing failed then we set + * active_rxon back to what it was previously */ + if (rc) { + active_rxon->filter_flags |= RXON_FILTER_ASSOC_MSK; + IL_ERR("Error clearing ASSOC_MSK on current " + "configuration (%d).\n", rc); + return rc; + } + il_clear_ucode_stations(il); + il_restore_stations(il); + } + + D_INFO("Sending RXON\n" "* with%s RXON_FILTER_ASSOC_MSK\n" + "* channel = %d\n" "* bssid = %pM\n", (new_assoc ? "" : "out"), + le16_to_cpu(staging_rxon->channel), staging_rxon->bssid_addr); + + /* + * reserved4 and 5 could have been filled by the iwlcore code. + * Let's clear them before pushing to the 3945. + */ + staging_rxon->reserved4 = 0; + staging_rxon->reserved5 = 0; + + il_set_rxon_hwcrypto(il, !il3945_mod_params.sw_crypto); + + /* Apply the new configuration */ + rc = il_send_cmd_pdu(il, C_RXON, sizeof(struct il3945_rxon_cmd), + staging_rxon); + if (rc) { + IL_ERR("Error setting new configuration (%d).\n", rc); + return rc; + } + + memcpy(active_rxon, staging_rxon, sizeof(*active_rxon)); + + if (!new_assoc) { + il_clear_ucode_stations(il); + il_restore_stations(il); + } + + /* If we issue a new RXON command which required a tune then we must + * send a new TXPOWER command or we won't be able to Tx any frames */ + rc = il_set_tx_power(il, il->tx_power_next, true); + if (rc) { + IL_ERR("Error setting Tx power (%d).\n", rc); + return rc; + } + + /* Init the hardware's rate fallback order based on the band */ + rc = il3945_init_hw_rate_table(il); + if (rc) { + IL_ERR("Error setting HW rate table: %02X\n", rc); + return -EIO; + } + + return 0; +} + +/** + * il3945_reg_txpower_periodic - called when time to check our temperature. + * + * -- reset periodic timer + * -- see if temp has changed enough to warrant re-calibration ... if so: + * -- correct coeffs for temp (can reset temp timer) + * -- save this temp as "last", + * -- send new set of gain settings to NIC + * NOTE: This should continue working, even when we're not associated, + * so we can keep our internal table of scan powers current. */ +void +il3945_reg_txpower_periodic(struct il_priv *il) +{ + /* This will kick in the "brute force" + * il3945_hw_reg_comp_txpower_temp() below */ + if (!il3945_is_temp_calib_needed(il)) + goto reschedule; + + /* Set up a new set of temp-adjusted TxPowers, send to NIC. + * This is based *only* on current temperature, + * ignoring any previous power measurements */ + il3945_hw_reg_comp_txpower_temp(il); + +reschedule: + queue_delayed_work(il->workqueue, &il->_3945.thermal_periodic, + REG_RECALIB_PERIOD * HZ); +} + +static void +il3945_bg_reg_txpower_periodic(struct work_struct *work) +{ + struct il_priv *il = container_of(work, struct il_priv, + _3945.thermal_periodic.work); + + mutex_lock(&il->mutex); + if (test_bit(S_EXIT_PENDING, &il->status) || il->txq == NULL) + goto out; + + il3945_reg_txpower_periodic(il); +out: + mutex_unlock(&il->mutex); +} + +/** + * il3945_hw_reg_get_ch_grp_idx - find the channel-group idx (0-4) for channel. + * + * This function is used when initializing channel-info structs. + * + * NOTE: These channel groups do *NOT* match the bands above! + * These channel groups are based on factory-tested channels; + * on A-band, EEPROM's "group frequency" entries represent the top + * channel in each group 1-4. Group 5 All B/G channels are in group 0. + */ +static u16 +il3945_hw_reg_get_ch_grp_idx(struct il_priv *il, + const struct il_channel_info *ch_info) +{ + struct il3945_eeprom *eeprom = (struct il3945_eeprom *)il->eeprom; + struct il3945_eeprom_txpower_group *ch_grp = &eeprom->groups[0]; + u8 group; + u16 group_idx = 0; /* based on factory calib frequencies */ + u8 grp_channel; + + /* Find the group idx for the channel ... don't use idx 1(?) */ + if (il_is_channel_a_band(ch_info)) { + for (group = 1; group < 5; group++) { + grp_channel = ch_grp[group].group_channel; + if (ch_info->channel <= grp_channel) { + group_idx = group; + break; + } + } + /* group 4 has a few channels *above* its factory cal freq */ + if (group == 5) + group_idx = 4; + } else + group_idx = 0; /* 2.4 GHz, group 0 */ + + D_POWER("Chnl %d mapped to grp %d\n", ch_info->channel, group_idx); + return group_idx; +} + +/** + * il3945_hw_reg_get_matched_power_idx - Interpolate to get nominal idx + * + * Interpolate to get nominal (i.e. at factory calibration temperature) idx + * into radio/DSP gain settings table for requested power. + */ +static int +il3945_hw_reg_get_matched_power_idx(struct il_priv *il, s8 requested_power, + s32 setting_idx, s32 *new_idx) +{ + const struct il3945_eeprom_txpower_group *chnl_grp = NULL; + struct il3945_eeprom *eeprom = (struct il3945_eeprom *)il->eeprom; + s32 idx0, idx1; + s32 power = 2 * requested_power; + s32 i; + const struct il3945_eeprom_txpower_sample *samples; + s32 gains0, gains1; + s32 res; + s32 denominator; + + chnl_grp = &eeprom->groups[setting_idx]; + samples = chnl_grp->samples; + for (i = 0; i < 5; i++) { + if (power == samples[i].power) { + *new_idx = samples[i].gain_idx; + return 0; + } + } + + if (power > samples[1].power) { + idx0 = 0; + idx1 = 1; + } else if (power > samples[2].power) { + idx0 = 1; + idx1 = 2; + } else if (power > samples[3].power) { + idx0 = 2; + idx1 = 3; + } else { + idx0 = 3; + idx1 = 4; + } + + denominator = (s32) samples[idx1].power - (s32) samples[idx0].power; + if (denominator == 0) + return -EINVAL; + gains0 = (s32) samples[idx0].gain_idx * (1 << 19); + gains1 = (s32) samples[idx1].gain_idx * (1 << 19); + res = + gains0 + (gains1 - gains0) * ((s32) power - + (s32) samples[idx0].power) / + denominator + (1 << 18); + *new_idx = res >> 19; + return 0; +} + +static void +il3945_hw_reg_init_channel_groups(struct il_priv *il) +{ + u32 i; + s32 rate_idx; + struct il3945_eeprom *eeprom = (struct il3945_eeprom *)il->eeprom; + const struct il3945_eeprom_txpower_group *group; + + D_POWER("Initializing factory calib info from EEPROM\n"); + + for (i = 0; i < IL_NUM_TX_CALIB_GROUPS; i++) { + s8 *clip_pwrs; /* table of power levels for each rate */ + s8 satur_pwr; /* saturation power for each chnl group */ + group = &eeprom->groups[i]; + + /* sanity check on factory saturation power value */ + if (group->saturation_power < 40) { + IL_WARN("Error: saturation power is %d, " + "less than minimum expected 40\n", + group->saturation_power); + return; + } + + /* + * Derive requested power levels for each rate, based on + * hardware capabilities (saturation power for band). + * Basic value is 3dB down from saturation, with further + * power reductions for highest 3 data rates. These + * backoffs provide headroom for high rate modulation + * power peaks, without too much distortion (clipping). + */ + /* we'll fill in this array with h/w max power levels */ + clip_pwrs = (s8 *) il->_3945.clip_groups[i].clip_powers; + + /* divide factory saturation power by 2 to find -3dB level */ + satur_pwr = (s8) (group->saturation_power >> 1); + + /* fill in channel group's nominal powers for each rate */ + for (rate_idx = 0; rate_idx < RATE_COUNT_3945; + rate_idx++, clip_pwrs++) { + switch (rate_idx) { + case RATE_36M_IDX_TBL: + if (i == 0) /* B/G */ + *clip_pwrs = satur_pwr; + else /* A */ + *clip_pwrs = satur_pwr - 5; + break; + case RATE_48M_IDX_TBL: + if (i == 0) + *clip_pwrs = satur_pwr - 7; + else + *clip_pwrs = satur_pwr - 10; + break; + case RATE_54M_IDX_TBL: + if (i == 0) + *clip_pwrs = satur_pwr - 9; + else + *clip_pwrs = satur_pwr - 12; + break; + default: + *clip_pwrs = satur_pwr; + break; + } + } + } +} + +/** + * il3945_txpower_set_from_eeprom - Set channel power info based on EEPROM + * + * Second pass (during init) to set up il->channel_info + * + * Set up Tx-power settings in our channel info database for each VALID + * (for this geo/SKU) channel, at all Tx data rates, based on eeprom values + * and current temperature. + * + * Since this is based on current temperature (at init time), these values may + * not be valid for very long, but it gives us a starting/default point, + * and allows us to active (i.e. using Tx) scan. + * + * This does *not* write values to NIC, just sets up our internal table. + */ +int +il3945_txpower_set_from_eeprom(struct il_priv *il) +{ + struct il_channel_info *ch_info = NULL; + struct il3945_channel_power_info *pwr_info; + struct il3945_eeprom *eeprom = (struct il3945_eeprom *)il->eeprom; + int delta_idx; + u8 rate_idx; + u8 scan_tbl_idx; + const s8 *clip_pwrs; /* array of power levels for each rate */ + u8 gain, dsp_atten; + s8 power; + u8 pwr_idx, base_pwr_idx, a_band; + u8 i; + int temperature; + + /* save temperature reference, + * so we can determine next time to calibrate */ + temperature = il3945_hw_reg_txpower_get_temperature(il); + il->last_temperature = temperature; + + il3945_hw_reg_init_channel_groups(il); + + /* initialize Tx power info for each and every channel, 2.4 and 5.x */ + for (i = 0, ch_info = il->channel_info; i < il->channel_count; + i++, ch_info++) { + a_band = il_is_channel_a_band(ch_info); + if (!il_is_channel_valid(ch_info)) + continue; + + /* find this channel's channel group (*not* "band") idx */ + ch_info->group_idx = il3945_hw_reg_get_ch_grp_idx(il, ch_info); + + /* Get this chnlgrp's rate->max/clip-powers table */ + clip_pwrs = + il->_3945.clip_groups[ch_info->group_idx].clip_powers; + + /* calculate power idx *adjustment* value according to + * diff between current temperature and factory temperature */ + delta_idx = + il3945_hw_reg_adjust_power_by_temp(temperature, + eeprom->groups[ch_info-> + group_idx]. + temperature); + + D_POWER("Delta idx for channel %d: %d [%d]\n", ch_info->channel, + delta_idx, temperature + IL_TEMP_CONVERT); + + /* set tx power value for all OFDM rates */ + for (rate_idx = 0; rate_idx < IL_OFDM_RATES; rate_idx++) { + s32 uninitialized_var(power_idx); + int rc; + + /* use channel group's clip-power table, + * but don't exceed channel's max power */ + s8 pwr = min(ch_info->max_power_avg, + clip_pwrs[rate_idx]); + + pwr_info = &ch_info->power_info[rate_idx]; + + /* get base (i.e. at factory-measured temperature) + * power table idx for this rate's power */ + rc = il3945_hw_reg_get_matched_power_idx(il, pwr, + ch_info-> + group_idx, + &power_idx); + if (rc) { + IL_ERR("Invalid power idx\n"); + return rc; + } + pwr_info->base_power_idx = (u8) power_idx; + + /* temperature compensate */ + power_idx += delta_idx; + + /* stay within range of gain table */ + power_idx = il3945_hw_reg_fix_power_idx(power_idx); + + /* fill 1 OFDM rate's il3945_channel_power_info struct */ + pwr_info->requested_power = pwr; + pwr_info->power_table_idx = (u8) power_idx; + pwr_info->tpc.tx_gain = + power_gain_table[a_band][power_idx].tx_gain; + pwr_info->tpc.dsp_atten = + power_gain_table[a_band][power_idx].dsp_atten; + } + + /* set tx power for CCK rates, based on OFDM 12 Mbit settings */ + pwr_info = &ch_info->power_info[RATE_12M_IDX_TBL]; + power = pwr_info->requested_power + IL_CCK_FROM_OFDM_POWER_DIFF; + pwr_idx = pwr_info->power_table_idx + IL_CCK_FROM_OFDM_IDX_DIFF; + base_pwr_idx = + pwr_info->base_power_idx + IL_CCK_FROM_OFDM_IDX_DIFF; + + /* stay within table range */ + pwr_idx = il3945_hw_reg_fix_power_idx(pwr_idx); + gain = power_gain_table[a_band][pwr_idx].tx_gain; + dsp_atten = power_gain_table[a_band][pwr_idx].dsp_atten; + + /* fill each CCK rate's il3945_channel_power_info structure + * NOTE: All CCK-rate Txpwrs are the same for a given chnl! + * NOTE: CCK rates start at end of OFDM rates! */ + for (rate_idx = 0; rate_idx < IL_CCK_RATES; rate_idx++) { + pwr_info = + &ch_info->power_info[rate_idx + IL_OFDM_RATES]; + pwr_info->requested_power = power; + pwr_info->power_table_idx = pwr_idx; + pwr_info->base_power_idx = base_pwr_idx; + pwr_info->tpc.tx_gain = gain; + pwr_info->tpc.dsp_atten = dsp_atten; + } + + /* set scan tx power, 1Mbit for CCK, 6Mbit for OFDM */ + for (scan_tbl_idx = 0; scan_tbl_idx < IL_NUM_SCAN_RATES; + scan_tbl_idx++) { + s32 actual_idx = + (scan_tbl_idx == + 0) ? RATE_1M_IDX_TBL : RATE_6M_IDX_TBL; + il3945_hw_reg_set_scan_power(il, scan_tbl_idx, + actual_idx, clip_pwrs, + ch_info, a_band); + } + } + + return 0; +} + +int +il3945_hw_rxq_stop(struct il_priv *il) +{ + int ret; + + _il_wr(il, FH39_RCSR_CONFIG(0), 0); + ret = _il_poll_bit(il, FH39_RSSR_STATUS, + FH39_RSSR_CHNL0_RX_STATUS_CHNL_IDLE, + FH39_RSSR_CHNL0_RX_STATUS_CHNL_IDLE, + 1000); + if (ret < 0) + IL_ERR("Can't stop Rx DMA.\n"); + + return 0; +} + +int +il3945_hw_tx_queue_init(struct il_priv *il, struct il_tx_queue *txq) +{ + int txq_id = txq->q.id; + + struct il3945_shared *shared_data = il->_3945.shared_virt; + + shared_data->tx_base_ptr[txq_id] = cpu_to_le32((u32) txq->q.dma_addr); + + il_wr(il, FH39_CBCC_CTRL(txq_id), 0); + il_wr(il, FH39_CBCC_BASE(txq_id), 0); + + il_wr(il, FH39_TCSR_CONFIG(txq_id), + FH39_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_NOINT | + FH39_TCSR_TX_CONFIG_REG_VAL_MSG_MODE_TXF | + FH39_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_IFTFD | + FH39_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE_VAL | + FH39_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE); + + /* fake read to flush all prev. writes */ + _il_rd(il, FH39_TSSR_CBB_BASE); + + return 0; +} + +/* + * HCMD utils + */ +static u16 +il3945_get_hcmd_size(u8 cmd_id, u16 len) +{ + switch (cmd_id) { + case C_RXON: + return sizeof(struct il3945_rxon_cmd); + case C_POWER_TBL: + return sizeof(struct il3945_powertable_cmd); + default: + return len; + } +} + +static u16 +il3945_build_addsta_hcmd(const struct il_addsta_cmd *cmd, u8 * data) +{ + struct il3945_addsta_cmd *addsta = (struct il3945_addsta_cmd *)data; + addsta->mode = cmd->mode; + memcpy(&addsta->sta, &cmd->sta, sizeof(struct sta_id_modify)); + memcpy(&addsta->key, &cmd->key, sizeof(struct il4965_keyinfo)); + addsta->station_flags = cmd->station_flags; + addsta->station_flags_msk = cmd->station_flags_msk; + addsta->tid_disable_tx = cpu_to_le16(0); + addsta->rate_n_flags = cmd->rate_n_flags; + addsta->add_immediate_ba_tid = cmd->add_immediate_ba_tid; + addsta->remove_immediate_ba_tid = cmd->remove_immediate_ba_tid; + addsta->add_immediate_ba_ssn = cmd->add_immediate_ba_ssn; + + return (u16) sizeof(struct il3945_addsta_cmd); +} + +static int +il3945_add_bssid_station(struct il_priv *il, const u8 * addr, u8 * sta_id_r) +{ + int ret; + u8 sta_id; + unsigned long flags; + + if (sta_id_r) + *sta_id_r = IL_INVALID_STATION; + + ret = il_add_station_common(il, addr, 0, NULL, &sta_id); + if (ret) { + IL_ERR("Unable to add station %pM\n", addr); + return ret; + } + + if (sta_id_r) + *sta_id_r = sta_id; + + spin_lock_irqsave(&il->sta_lock, flags); + il->stations[sta_id].used |= IL_STA_LOCAL; + spin_unlock_irqrestore(&il->sta_lock, flags); + + return 0; +} + +static int +il3945_manage_ibss_station(struct il_priv *il, struct ieee80211_vif *vif, + bool add) +{ + struct il_vif_priv *vif_priv = (void *)vif->drv_priv; + int ret; + + if (add) { + ret = + il3945_add_bssid_station(il, vif->bss_conf.bssid, + &vif_priv->ibss_bssid_sta_id); + if (ret) + return ret; + + il3945_sync_sta(il, vif_priv->ibss_bssid_sta_id, + (il->band == + IEEE80211_BAND_5GHZ) ? RATE_6M_PLCP : + RATE_1M_PLCP); + il3945_rate_scale_init(il->hw, vif_priv->ibss_bssid_sta_id); + + return 0; + } + + return il_remove_station(il, vif_priv->ibss_bssid_sta_id, + vif->bss_conf.bssid); +} + +/** + * il3945_init_hw_rate_table - Initialize the hardware rate fallback table + */ +int +il3945_init_hw_rate_table(struct il_priv *il) +{ + int rc, i, idx, prev_idx; + struct il3945_rate_scaling_cmd rate_cmd = { + .reserved = {0, 0, 0}, + }; + struct il3945_rate_scaling_info *table = rate_cmd.table; + + for (i = 0; i < ARRAY_SIZE(il3945_rates); i++) { + idx = il3945_rates[i].table_rs_idx; + + table[idx].rate_n_flags = cpu_to_le16(il3945_rates[i].plcp); + table[idx].try_cnt = il->retry_rate; + prev_idx = il3945_get_prev_ieee_rate(i); + table[idx].next_rate_idx = il3945_rates[prev_idx].table_rs_idx; + } + + switch (il->band) { + case IEEE80211_BAND_5GHZ: + D_RATE("Select A mode rate scale\n"); + /* If one of the following CCK rates is used, + * have it fall back to the 6M OFDM rate */ + for (i = RATE_1M_IDX_TBL; i <= RATE_11M_IDX_TBL; i++) + table[i].next_rate_idx = + il3945_rates[IL_FIRST_OFDM_RATE].table_rs_idx; + + /* Don't fall back to CCK rates */ + table[RATE_12M_IDX_TBL].next_rate_idx = RATE_9M_IDX_TBL; + + /* Don't drop out of OFDM rates */ + table[RATE_6M_IDX_TBL].next_rate_idx = + il3945_rates[IL_FIRST_OFDM_RATE].table_rs_idx; + break; + + case IEEE80211_BAND_2GHZ: + D_RATE("Select B/G mode rate scale\n"); + /* If an OFDM rate is used, have it fall back to the + * 1M CCK rates */ + + if (!(il->_3945.sta_supp_rates & IL_OFDM_RATES_MASK) && + il_is_associated(il)) { + + idx = IL_FIRST_CCK_RATE; + for (i = RATE_6M_IDX_TBL; i <= RATE_54M_IDX_TBL; i++) + table[i].next_rate_idx = + il3945_rates[idx].table_rs_idx; + + idx = RATE_11M_IDX_TBL; + /* CCK shouldn't fall back to OFDM... */ + table[idx].next_rate_idx = RATE_5M_IDX_TBL; + } + break; + + default: + WARN_ON(1); + break; + } + + /* Update the rate scaling for control frame Tx */ + rate_cmd.table_id = 0; + rc = il_send_cmd_pdu(il, C_RATE_SCALE, sizeof(rate_cmd), &rate_cmd); + if (rc) + return rc; + + /* Update the rate scaling for data frame Tx */ + rate_cmd.table_id = 1; + return il_send_cmd_pdu(il, C_RATE_SCALE, sizeof(rate_cmd), &rate_cmd); +} + +/* Called when initializing driver */ +int +il3945_hw_set_hw_params(struct il_priv *il) +{ + memset((void *)&il->hw_params, 0, sizeof(struct il_hw_params)); + + il->_3945.shared_virt = + dma_alloc_coherent(&il->pci_dev->dev, sizeof(struct il3945_shared), + &il->_3945.shared_phys, GFP_KERNEL); + if (!il->_3945.shared_virt) + return -ENOMEM; + + il->hw_params.bcast_id = IL3945_BROADCAST_ID; + + /* Assign number of Usable TX queues */ + il->hw_params.max_txq_num = il->cfg->num_of_queues; + + il->hw_params.tfd_size = sizeof(struct il3945_tfd); + il->hw_params.rx_page_order = get_order(IL_RX_BUF_SIZE_3K); + il->hw_params.max_rxq_size = RX_QUEUE_SIZE; + il->hw_params.max_rxq_log = RX_QUEUE_SIZE_LOG; + il->hw_params.max_stations = IL3945_STATION_COUNT; + + il->sta_key_max_num = STA_KEY_MAX_NUM; + + il->hw_params.rx_wrt_ptr_reg = FH39_RSCSR_CHNL0_WPTR; + il->hw_params.max_beacon_itrvl = IL39_MAX_UCODE_BEACON_INTERVAL; + il->hw_params.beacon_time_tsf_bits = IL3945_EXT_BEACON_TIME_POS; + + return 0; +} + +unsigned int +il3945_hw_get_beacon_cmd(struct il_priv *il, struct il3945_frame *frame, + u8 rate) +{ + struct il3945_tx_beacon_cmd *tx_beacon_cmd; + unsigned int frame_size; + + tx_beacon_cmd = (struct il3945_tx_beacon_cmd *)&frame->u; + memset(tx_beacon_cmd, 0, sizeof(*tx_beacon_cmd)); + + tx_beacon_cmd->tx.sta_id = il->hw_params.bcast_id; + tx_beacon_cmd->tx.stop_time.life_time = TX_CMD_LIFE_TIME_INFINITE; + + frame_size = + il3945_fill_beacon_frame(il, tx_beacon_cmd->frame, + sizeof(frame->u) - sizeof(*tx_beacon_cmd)); + + BUG_ON(frame_size > MAX_MPDU_SIZE); + tx_beacon_cmd->tx.len = cpu_to_le16((u16) frame_size); + + tx_beacon_cmd->tx.rate = rate; + tx_beacon_cmd->tx.tx_flags = + (TX_CMD_FLG_SEQ_CTL_MSK | TX_CMD_FLG_TSF_MSK); + + /* supp_rates[0] == OFDM start at IL_FIRST_OFDM_RATE */ + tx_beacon_cmd->tx.supp_rates[0] = + (IL_OFDM_BASIC_RATES_MASK >> IL_FIRST_OFDM_RATE) & 0xFF; + + tx_beacon_cmd->tx.supp_rates[1] = (IL_CCK_BASIC_RATES_MASK & 0xF); + + return sizeof(struct il3945_tx_beacon_cmd) + frame_size; +} + +void +il3945_hw_handler_setup(struct il_priv *il) +{ + il->handlers[C_TX] = il3945_hdl_tx; + il->handlers[N_3945_RX] = il3945_hdl_rx; +} + +void +il3945_hw_setup_deferred_work(struct il_priv *il) +{ + INIT_DELAYED_WORK(&il->_3945.thermal_periodic, + il3945_bg_reg_txpower_periodic); +} + +void +il3945_hw_cancel_deferred_work(struct il_priv *il) +{ + cancel_delayed_work(&il->_3945.thermal_periodic); +} + +/* check contents of special bootstrap uCode SRAM */ +static int +il3945_verify_bsm(struct il_priv *il) +{ + __le32 *image = il->ucode_boot.v_addr; + u32 len = il->ucode_boot.len; + u32 reg; + u32 val; + + D_INFO("Begin verify bsm\n"); + + /* verify BSM SRAM contents */ + val = il_rd_prph(il, BSM_WR_DWCOUNT_REG); + for (reg = BSM_SRAM_LOWER_BOUND; reg < BSM_SRAM_LOWER_BOUND + len; + reg += sizeof(u32), image++) { + val = il_rd_prph(il, reg); + if (val != le32_to_cpu(*image)) { + IL_ERR("BSM uCode verification failed at " + "addr 0x%08X+%u (of %u), is 0x%x, s/b 0x%x\n", + BSM_SRAM_LOWER_BOUND, reg - BSM_SRAM_LOWER_BOUND, + len, val, le32_to_cpu(*image)); + return -EIO; + } + } + + D_INFO("BSM bootstrap uCode image OK\n"); + + return 0; +} + +/****************************************************************************** + * + * EEPROM related functions + * + ******************************************************************************/ + +/* + * Clear the OWNER_MSK, to establish driver (instead of uCode running on + * embedded controller) as EEPROM reader; each read is a series of pulses + * to/from the EEPROM chip, not a single event, so even reads could conflict + * if they weren't arbitrated by some ownership mechanism. Here, the driver + * simply claims ownership, which should be safe when this function is called + * (i.e. before loading uCode!). + */ +static int +il3945_eeprom_acquire_semaphore(struct il_priv *il) +{ + _il_clear_bit(il, CSR_EEPROM_GP, CSR_EEPROM_GP_IF_OWNER_MSK); + return 0; +} + +static void +il3945_eeprom_release_semaphore(struct il_priv *il) +{ + return; +} + + /** + * il3945_load_bsm - Load bootstrap instructions + * + * BSM operation: + * + * The Bootstrap State Machine (BSM) stores a short bootstrap uCode program + * in special SRAM that does not power down during RFKILL. When powering back + * up after power-saving sleeps (or during initial uCode load), the BSM loads + * the bootstrap program into the on-board processor, and starts it. + * + * The bootstrap program loads (via DMA) instructions and data for a new + * program from host DRAM locations indicated by the host driver in the + * BSM_DRAM_* registers. Once the new program is loaded, it starts + * automatically. + * + * When initializing the NIC, the host driver points the BSM to the + * "initialize" uCode image. This uCode sets up some internal data, then + * notifies host via "initialize alive" that it is complete. + * + * The host then replaces the BSM_DRAM_* pointer values to point to the + * normal runtime uCode instructions and a backup uCode data cache buffer + * (filled initially with starting data values for the on-board processor), + * then triggers the "initialize" uCode to load and launch the runtime uCode, + * which begins normal operation. + * + * When doing a power-save shutdown, runtime uCode saves data SRAM into + * the backup data cache in DRAM before SRAM is powered down. + * + * When powering back up, the BSM loads the bootstrap program. This reloads + * the runtime uCode instructions and the backup data cache into SRAM, + * and re-launches the runtime uCode from where it left off. + */ +static int +il3945_load_bsm(struct il_priv *il) +{ + __le32 *image = il->ucode_boot.v_addr; + u32 len = il->ucode_boot.len; + dma_addr_t pinst; + dma_addr_t pdata; + u32 inst_len; + u32 data_len; + int rc; + int i; + u32 done; + u32 reg_offset; + + D_INFO("Begin load bsm\n"); + + /* make sure bootstrap program is no larger than BSM's SRAM size */ + if (len > IL39_MAX_BSM_SIZE) + return -EINVAL; + + /* Tell bootstrap uCode where to find the "Initialize" uCode + * in host DRAM ... host DRAM physical address bits 31:0 for 3945. + * NOTE: il3945_initialize_alive_start() will replace these values, + * after the "initialize" uCode has run, to point to + * runtime/protocol instructions and backup data cache. */ + pinst = il->ucode_init.p_addr; + pdata = il->ucode_init_data.p_addr; + inst_len = il->ucode_init.len; + data_len = il->ucode_init_data.len; + + il_wr_prph(il, BSM_DRAM_INST_PTR_REG, pinst); + il_wr_prph(il, BSM_DRAM_DATA_PTR_REG, pdata); + il_wr_prph(il, BSM_DRAM_INST_BYTECOUNT_REG, inst_len); + il_wr_prph(il, BSM_DRAM_DATA_BYTECOUNT_REG, data_len); + + /* Fill BSM memory with bootstrap instructions */ + for (reg_offset = BSM_SRAM_LOWER_BOUND; + reg_offset < BSM_SRAM_LOWER_BOUND + len; + reg_offset += sizeof(u32), image++) + _il_wr_prph(il, reg_offset, le32_to_cpu(*image)); + + rc = il3945_verify_bsm(il); + if (rc) + return rc; + + /* Tell BSM to copy from BSM SRAM into instruction SRAM, when asked */ + il_wr_prph(il, BSM_WR_MEM_SRC_REG, 0x0); + il_wr_prph(il, BSM_WR_MEM_DST_REG, IL39_RTC_INST_LOWER_BOUND); + il_wr_prph(il, BSM_WR_DWCOUNT_REG, len / sizeof(u32)); + + /* Load bootstrap code into instruction SRAM now, + * to prepare to load "initialize" uCode */ + il_wr_prph(il, BSM_WR_CTRL_REG, BSM_WR_CTRL_REG_BIT_START); + + /* Wait for load of bootstrap uCode to finish */ + for (i = 0; i < 100; i++) { + done = il_rd_prph(il, BSM_WR_CTRL_REG); + if (!(done & BSM_WR_CTRL_REG_BIT_START)) + break; + udelay(10); + } + if (i < 100) + D_INFO("BSM write complete, poll %d iterations\n", i); + else { + IL_ERR("BSM write did not complete!\n"); + return -EIO; + } + + /* Enable future boot loads whenever power management unit triggers it + * (e.g. when powering back up after power-save shutdown) */ + il_wr_prph(il, BSM_WR_CTRL_REG, BSM_WR_CTRL_REG_BIT_START_EN); + + return 0; +} + +const struct il_ops il3945_ops = { + .txq_attach_buf_to_tfd = il3945_hw_txq_attach_buf_to_tfd, + .txq_free_tfd = il3945_hw_txq_free_tfd, + .txq_init = il3945_hw_tx_queue_init, + .load_ucode = il3945_load_bsm, + .dump_nic_error_log = il3945_dump_nic_error_log, + .apm_init = il3945_apm_init, + .send_tx_power = il3945_send_tx_power, + .is_valid_rtc_data_addr = il3945_hw_valid_rtc_data_addr, + .eeprom_acquire_semaphore = il3945_eeprom_acquire_semaphore, + .eeprom_release_semaphore = il3945_eeprom_release_semaphore, + + .rxon_assoc = il3945_send_rxon_assoc, + .commit_rxon = il3945_commit_rxon, + + .get_hcmd_size = il3945_get_hcmd_size, + .build_addsta_hcmd = il3945_build_addsta_hcmd, + .request_scan = il3945_request_scan, + .post_scan = il3945_post_scan, + + .post_associate = il3945_post_associate, + .config_ap = il3945_config_ap, + .manage_ibss_station = il3945_manage_ibss_station, + + .send_led_cmd = il3945_send_led_cmd, +}; + +static struct il_cfg il3945_bg_cfg = { + .name = "3945BG", + .fw_name_pre = IL3945_FW_PRE, + .ucode_api_max = IL3945_UCODE_API_MAX, + .ucode_api_min = IL3945_UCODE_API_MIN, + .sku = IL_SKU_G, + .eeprom_ver = EEPROM_3945_EEPROM_VERSION, + .mod_params = &il3945_mod_params, + .led_mode = IL_LED_BLINK, + + .eeprom_size = IL3945_EEPROM_IMG_SIZE, + .num_of_queues = IL39_NUM_QUEUES, + .pll_cfg_val = CSR39_ANA_PLL_CFG_VAL, + .set_l0s = false, + .use_bsm = true, + .led_compensation = 64, + .wd_timeout = IL_DEF_WD_TIMEOUT, + + .regulatory_bands = { + EEPROM_REGULATORY_BAND_1_CHANNELS, + EEPROM_REGULATORY_BAND_2_CHANNELS, + EEPROM_REGULATORY_BAND_3_CHANNELS, + EEPROM_REGULATORY_BAND_4_CHANNELS, + EEPROM_REGULATORY_BAND_5_CHANNELS, + EEPROM_REGULATORY_BAND_NO_HT40, + EEPROM_REGULATORY_BAND_NO_HT40, + }, +}; + +static struct il_cfg il3945_abg_cfg = { + .name = "3945ABG", + .fw_name_pre = IL3945_FW_PRE, + .ucode_api_max = IL3945_UCODE_API_MAX, + .ucode_api_min = IL3945_UCODE_API_MIN, + .sku = IL_SKU_A | IL_SKU_G, + .eeprom_ver = EEPROM_3945_EEPROM_VERSION, + .mod_params = &il3945_mod_params, + .led_mode = IL_LED_BLINK, + + .eeprom_size = IL3945_EEPROM_IMG_SIZE, + .num_of_queues = IL39_NUM_QUEUES, + .pll_cfg_val = CSR39_ANA_PLL_CFG_VAL, + .set_l0s = false, + .use_bsm = true, + .led_compensation = 64, + .wd_timeout = IL_DEF_WD_TIMEOUT, + + .regulatory_bands = { + EEPROM_REGULATORY_BAND_1_CHANNELS, + EEPROM_REGULATORY_BAND_2_CHANNELS, + EEPROM_REGULATORY_BAND_3_CHANNELS, + EEPROM_REGULATORY_BAND_4_CHANNELS, + EEPROM_REGULATORY_BAND_5_CHANNELS, + EEPROM_REGULATORY_BAND_NO_HT40, + EEPROM_REGULATORY_BAND_NO_HT40, + }, +}; + +DEFINE_PCI_DEVICE_TABLE(il3945_hw_card_ids) = { + {IL_PCI_DEVICE(0x4222, 0x1005, il3945_bg_cfg)}, + {IL_PCI_DEVICE(0x4222, 0x1034, il3945_bg_cfg)}, + {IL_PCI_DEVICE(0x4222, 0x1044, il3945_bg_cfg)}, + {IL_PCI_DEVICE(0x4227, 0x1014, il3945_bg_cfg)}, + {IL_PCI_DEVICE(0x4222, PCI_ANY_ID, il3945_abg_cfg)}, + {IL_PCI_DEVICE(0x4227, PCI_ANY_ID, il3945_abg_cfg)}, + {0} +}; + +MODULE_DEVICE_TABLE(pci, il3945_hw_card_ids); diff --git a/drivers/net/wireless/iwlegacy/3945.h b/drivers/net/wireless/iwlegacy/3945.h new file mode 100644 index 0000000..1a381af --- /dev/null +++ b/drivers/net/wireless/iwlegacy/3945.h @@ -0,0 +1,597 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#ifndef __il_3945_h__ +#define __il_3945_h__ + +#include /* for struct pci_device_id */ +#include +#include + +/* Hardware specific file defines the PCI IDs table for that hardware module */ +extern const struct pci_device_id il3945_hw_card_ids[]; + +#include "common.h" + +extern const struct il_ops il3945_ops; + +/* Highest firmware API version supported */ +#define IL3945_UCODE_API_MAX 2 + +/* Lowest firmware API version supported */ +#define IL3945_UCODE_API_MIN 1 + +#define IL3945_FW_PRE "iwlwifi-3945-" +#define _IL3945_MODULE_FIRMWARE(api) IL3945_FW_PRE #api ".ucode" +#define IL3945_MODULE_FIRMWARE(api) _IL3945_MODULE_FIRMWARE(api) + +/* Default noise level to report when noise measurement is not available. + * This may be because we're: + * 1) Not associated (4965, no beacon stats being sent to driver) + * 2) Scanning (noise measurement does not apply to associated channel) + * 3) Receiving CCK (3945 delivers noise info only for OFDM frames) + * Use default noise value of -127 ... this is below the range of measurable + * Rx dBm for either 3945 or 4965, so it can indicate "unmeasurable" to user. + * Also, -127 works better than 0 when averaging frames with/without + * noise info (e.g. averaging might be done in app); measured dBm values are + * always negative ... using a negative value as the default keeps all + * averages within an s8's (used in some apps) range of negative values. */ +#define IL_NOISE_MEAS_NOT_AVAILABLE (-127) + +/* Module parameters accessible from iwl-*.c */ +extern struct il_mod_params il3945_mod_params; + +struct il3945_rate_scale_data { + u64 data; + s32 success_counter; + s32 success_ratio; + s32 counter; + s32 average_tpt; + unsigned long stamp; +}; + +struct il3945_rs_sta { + spinlock_t lock; + struct il_priv *il; + s32 *expected_tpt; + unsigned long last_partial_flush; + unsigned long last_flush; + u32 flush_time; + u32 last_tx_packets; + u32 tx_packets; + u8 tgg; + u8 flush_pending; + u8 start_rate; + struct timer_list rate_scale_flush; + struct il3945_rate_scale_data win[RATE_COUNT_3945]; +#ifdef CPTCFG_MAC80211_DEBUGFS + struct dentry *rs_sta_dbgfs_stats_table_file; +#endif + + /* used to be in sta_info */ + int last_txrate_idx; +}; + +/* + * The common struct MUST be first because it is shared between + * 3945 and 4965! + */ +struct il3945_sta_priv { + struct il_station_priv_common common; + struct il3945_rs_sta rs_sta; +}; + +enum il3945_antenna { + IL_ANTENNA_DIVERSITY, + IL_ANTENNA_MAIN, + IL_ANTENNA_AUX +}; + +/* + * RTS threshold here is total size [2347] minus 4 FCS bytes + * Per spec: + * a value of 0 means RTS on all data/management packets + * a value > max MSDU size means no RTS + * else RTS for data/management frames where MPDU is larger + * than RTS value. + */ +#define DEFAULT_RTS_THRESHOLD 2347U +#define MIN_RTS_THRESHOLD 0U +#define MAX_RTS_THRESHOLD 2347U +#define MAX_MSDU_SIZE 2304U +#define MAX_MPDU_SIZE 2346U +#define DEFAULT_BEACON_INTERVAL 100U +#define DEFAULT_SHORT_RETRY_LIMIT 7U +#define DEFAULT_LONG_RETRY_LIMIT 4U + +#define IL_TX_FIFO_AC0 0 +#define IL_TX_FIFO_AC1 1 +#define IL_TX_FIFO_AC2 2 +#define IL_TX_FIFO_AC3 3 +#define IL_TX_FIFO_HCCA_1 5 +#define IL_TX_FIFO_HCCA_2 6 +#define IL_TX_FIFO_NONE 7 + +#define IEEE80211_DATA_LEN 2304 +#define IEEE80211_4ADDR_LEN 30 +#define IEEE80211_HLEN (IEEE80211_4ADDR_LEN) +#define IEEE80211_FRAME_LEN (IEEE80211_DATA_LEN + IEEE80211_HLEN) + +struct il3945_frame { + union { + struct ieee80211_hdr frame; + struct il3945_tx_beacon_cmd beacon; + u8 raw[IEEE80211_FRAME_LEN]; + u8 cmd[360]; + } u; + struct list_head list; +}; + +#define SUP_RATE_11A_MAX_NUM_CHANNELS 8 +#define SUP_RATE_11B_MAX_NUM_CHANNELS 4 +#define SUP_RATE_11G_MAX_NUM_CHANNELS 12 + +#define IL_SUPPORTED_RATES_IE_LEN 8 + +#define SCAN_INTERVAL 100 + +#define MAX_TID_COUNT 9 + +#define IL_INVALID_RATE 0xFF +#define IL_INVALID_VALUE -1 + +#define STA_PS_STATUS_WAKE 0 +#define STA_PS_STATUS_SLEEP 1 + +struct il3945_ibss_seq { + u8 mac[ETH_ALEN]; + u16 seq_num; + u16 frag_num; + unsigned long packet_time; + struct list_head list; +}; + +#define IL_RX_HDR(x) ((struct il3945_rx_frame_hdr *)(\ + x->u.rx_frame.stats.payload + \ + x->u.rx_frame.stats.phy_count)) +#define IL_RX_END(x) ((struct il3945_rx_frame_end *)(\ + IL_RX_HDR(x)->payload + \ + le16_to_cpu(IL_RX_HDR(x)->len))) +#define IL_RX_STATS(x) (&x->u.rx_frame.stats) +#define IL_RX_DATA(x) (IL_RX_HDR(x)->payload) + +/****************************************************************************** + * + * Functions implemented in iwl3945-base.c which are forward declared here + * for use by iwl-*.c + * + *****************************************************************************/ +extern int il3945_calc_db_from_ratio(int sig_ratio); +extern void il3945_rx_replenish(void *data); +extern void il3945_rx_queue_reset(struct il_priv *il, struct il_rx_queue *rxq); +extern unsigned int il3945_fill_beacon_frame(struct il_priv *il, + struct ieee80211_hdr *hdr, + int left); +extern int il3945_dump_nic_event_log(struct il_priv *il, bool full_log, + char **buf, bool display); +extern void il3945_dump_nic_error_log(struct il_priv *il); + +/****************************************************************************** + * + * Functions implemented in iwl-[34]*.c which are forward declared here + * for use by iwl3945-base.c + * + * NOTE: The implementation of these functions are hardware specific + * which is why they are in the hardware specific files (vs. iwl-base.c) + * + * Naming convention -- + * il3945_ <-- Its part of iwlwifi (should be changed to il3945_) + * il3945_hw_ <-- Hardware specific (implemented in iwl-XXXX.c by all HW) + * iwlXXXX_ <-- Hardware specific (implemented in iwl-XXXX.c for XXXX) + * il3945_bg_ <-- Called from work queue context + * il3945_mac_ <-- mac80211 callback + * + ****************************************************************************/ +extern void il3945_hw_handler_setup(struct il_priv *il); +extern void il3945_hw_setup_deferred_work(struct il_priv *il); +extern void il3945_hw_cancel_deferred_work(struct il_priv *il); +extern int il3945_hw_rxq_stop(struct il_priv *il); +extern int il3945_hw_set_hw_params(struct il_priv *il); +extern int il3945_hw_nic_init(struct il_priv *il); +extern int il3945_hw_nic_stop_master(struct il_priv *il); +extern void il3945_hw_txq_ctx_free(struct il_priv *il); +extern void il3945_hw_txq_ctx_stop(struct il_priv *il); +extern int il3945_hw_nic_reset(struct il_priv *il); +extern int il3945_hw_txq_attach_buf_to_tfd(struct il_priv *il, + struct il_tx_queue *txq, + dma_addr_t addr, u16 len, u8 reset, + u8 pad); +extern void il3945_hw_txq_free_tfd(struct il_priv *il, struct il_tx_queue *txq); +extern int il3945_hw_get_temperature(struct il_priv *il); +extern int il3945_hw_tx_queue_init(struct il_priv *il, struct il_tx_queue *txq); +extern unsigned int il3945_hw_get_beacon_cmd(struct il_priv *il, + struct il3945_frame *frame, + u8 rate); +void il3945_hw_build_tx_cmd_rate(struct il_priv *il, struct il_device_cmd *cmd, + struct ieee80211_tx_info *info, + struct ieee80211_hdr *hdr, int sta_id); +extern int il3945_hw_reg_send_txpower(struct il_priv *il); +extern int il3945_hw_reg_set_txpower(struct il_priv *il, s8 power); +extern void il3945_hdl_stats(struct il_priv *il, struct il_rx_buf *rxb); +void il3945_hdl_c_stats(struct il_priv *il, struct il_rx_buf *rxb); +extern void il3945_disable_events(struct il_priv *il); +extern int il4965_get_temperature(const struct il_priv *il); +extern void il3945_post_associate(struct il_priv *il); +extern void il3945_config_ap(struct il_priv *il); + +extern int il3945_commit_rxon(struct il_priv *il); + +/** + * il3945_hw_find_station - Find station id for a given BSSID + * @bssid: MAC address of station ID to find + * + * NOTE: This should not be hardware specific but the code has + * not yet been merged into a single common layer for managing the + * station tables. + */ +extern u8 il3945_hw_find_station(struct il_priv *il, const u8 * bssid); + +extern __le32 il3945_get_antenna_flags(const struct il_priv *il); +extern int il3945_init_hw_rate_table(struct il_priv *il); +extern void il3945_reg_txpower_periodic(struct il_priv *il); +extern int il3945_txpower_set_from_eeprom(struct il_priv *il); + +extern int il3945_rs_next_rate(struct il_priv *il, int rate); + +/* scanning */ +int il3945_request_scan(struct il_priv *il, struct ieee80211_vif *vif); +void il3945_post_scan(struct il_priv *il); + +/* rates */ +extern const struct il3945_rate_info il3945_rates[RATE_COUNT_3945]; + +/* RSSI to dBm */ +#define IL39_RSSI_OFFSET 95 + +/* + * EEPROM related constants, enums, and structures. + */ +#define EEPROM_SKU_CAP_OP_MODE_MRC (1 << 7) + +/* + * Mapping of a Tx power level, at factory calibration temperature, + * to a radio/DSP gain table idx. + * One for each of 5 "sample" power levels in each band. + * v_det is measured at the factory, using the 3945's built-in power amplifier + * (PA) output voltage detector. This same detector is used during Tx of + * long packets in normal operation to provide feedback as to proper output + * level. + * Data copied from EEPROM. + * DO NOT ALTER THIS STRUCTURE!!! + */ +struct il3945_eeprom_txpower_sample { + u8 gain_idx; /* idx into power (gain) setup table ... */ + s8 power; /* ... for this pwr level for this chnl group */ + u16 v_det; /* PA output voltage */ +} __packed; + +/* + * Mappings of Tx power levels -> nominal radio/DSP gain table idxes. + * One for each channel group (a.k.a. "band") (1 for BG, 4 for A). + * Tx power setup code interpolates between the 5 "sample" power levels + * to determine the nominal setup for a requested power level. + * Data copied from EEPROM. + * DO NOT ALTER THIS STRUCTURE!!! + */ +struct il3945_eeprom_txpower_group { + struct il3945_eeprom_txpower_sample samples[5]; /* 5 power levels */ + s32 a, b, c, d, e; /* coefficients for voltage->power + * formula (signed) */ + s32 Fa, Fb, Fc, Fd, Fe; /* these modify coeffs based on + * frequency (signed) */ + s8 saturation_power; /* highest power possible by h/w in this + * band */ + u8 group_channel; /* "representative" channel # in this band */ + s16 temperature; /* h/w temperature at factory calib this band + * (signed) */ +} __packed; + +/* + * Temperature-based Tx-power compensation data, not band-specific. + * These coefficients are use to modify a/b/c/d/e coeffs based on + * difference between current temperature and factory calib temperature. + * Data copied from EEPROM. + */ +struct il3945_eeprom_temperature_corr { + u32 Ta; + u32 Tb; + u32 Tc; + u32 Td; + u32 Te; +} __packed; + +/* + * EEPROM map + */ +struct il3945_eeprom { + u8 reserved0[16]; + u16 device_id; /* abs.ofs: 16 */ + u8 reserved1[2]; + u16 pmc; /* abs.ofs: 20 */ + u8 reserved2[20]; + u8 mac_address[6]; /* abs.ofs: 42 */ + u8 reserved3[58]; + u16 board_revision; /* abs.ofs: 106 */ + u8 reserved4[11]; + u8 board_pba_number[9]; /* abs.ofs: 119 */ + u8 reserved5[8]; + u16 version; /* abs.ofs: 136 */ + u8 sku_cap; /* abs.ofs: 138 */ + u8 leds_mode; /* abs.ofs: 139 */ + u16 oem_mode; + u16 wowlan_mode; /* abs.ofs: 142 */ + u16 leds_time_interval; /* abs.ofs: 144 */ + u8 leds_off_time; /* abs.ofs: 146 */ + u8 leds_on_time; /* abs.ofs: 147 */ + u8 almgor_m_version; /* abs.ofs: 148 */ + u8 antenna_switch_type; /* abs.ofs: 149 */ + u8 reserved6[42]; + u8 sku_id[4]; /* abs.ofs: 192 */ + +/* + * Per-channel regulatory data. + * + * Each channel that *might* be supported by 3945 has a fixed location + * in EEPROM containing EEPROM_CHANNEL_* usage flags (LSB) and max regulatory + * txpower (MSB). + * + * Entries immediately below are for 20 MHz channel width. + * + * 2.4 GHz channels 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 + */ + u16 band_1_count; /* abs.ofs: 196 */ + struct il_eeprom_channel band_1_channels[14]; /* abs.ofs: 198 */ + +/* + * 4.9 GHz channels 183, 184, 185, 187, 188, 189, 192, 196, + * 5.0 GHz channels 7, 8, 11, 12, 16 + * (4915-5080MHz) (none of these is ever supported) + */ + u16 band_2_count; /* abs.ofs: 226 */ + struct il_eeprom_channel band_2_channels[13]; /* abs.ofs: 228 */ + +/* + * 5.2 GHz channels 34, 36, 38, 40, 42, 44, 46, 48, 52, 56, 60, 64 + * (5170-5320MHz) + */ + u16 band_3_count; /* abs.ofs: 254 */ + struct il_eeprom_channel band_3_channels[12]; /* abs.ofs: 256 */ + +/* + * 5.5 GHz channels 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140 + * (5500-5700MHz) + */ + u16 band_4_count; /* abs.ofs: 280 */ + struct il_eeprom_channel band_4_channels[11]; /* abs.ofs: 282 */ + +/* + * 5.7 GHz channels 145, 149, 153, 157, 161, 165 + * (5725-5825MHz) + */ + u16 band_5_count; /* abs.ofs: 304 */ + struct il_eeprom_channel band_5_channels[6]; /* abs.ofs: 306 */ + + u8 reserved9[194]; + +/* + * 3945 Txpower calibration data. + */ +#define IL_NUM_TX_CALIB_GROUPS 5 + struct il3945_eeprom_txpower_group groups[IL_NUM_TX_CALIB_GROUPS]; +/* abs.ofs: 512 */ + struct il3945_eeprom_temperature_corr corrections; /* abs.ofs: 832 */ + u8 reserved16[172]; /* fill out to full 1024 byte block */ +} __packed; + +#define IL3945_EEPROM_IMG_SIZE 1024 + +/* End of EEPROM */ + +#define PCI_CFG_REV_ID_BIT_BASIC_SKU (0x40) /* bit 6 */ +#define PCI_CFG_REV_ID_BIT_RTP (0x80) /* bit 7 */ + +/* 4 DATA + 1 CMD. There are 2 HCCA queues that are not used. */ +#define IL39_NUM_QUEUES 5 +#define IL39_CMD_QUEUE_NUM 4 + +#define IL_DEFAULT_TX_RETRY 15 + +/*********************************************/ + +#define RFD_SIZE 4 +#define NUM_TFD_CHUNKS 4 + +#define TFD_CTL_COUNT_SET(n) (n << 24) +#define TFD_CTL_COUNT_GET(ctl) ((ctl >> 24) & 7) +#define TFD_CTL_PAD_SET(n) (n << 28) +#define TFD_CTL_PAD_GET(ctl) (ctl >> 28) + +/* Sizes and addresses for instruction and data memory (SRAM) in + * 3945's embedded processor. Driver access is via HBUS_TARG_MEM_* regs. */ +#define IL39_RTC_INST_LOWER_BOUND (0x000000) +#define IL39_RTC_INST_UPPER_BOUND (0x014000) + +#define IL39_RTC_DATA_LOWER_BOUND (0x800000) +#define IL39_RTC_DATA_UPPER_BOUND (0x808000) + +#define IL39_RTC_INST_SIZE (IL39_RTC_INST_UPPER_BOUND - \ + IL39_RTC_INST_LOWER_BOUND) +#define IL39_RTC_DATA_SIZE (IL39_RTC_DATA_UPPER_BOUND - \ + IL39_RTC_DATA_LOWER_BOUND) + +#define IL39_MAX_INST_SIZE IL39_RTC_INST_SIZE +#define IL39_MAX_DATA_SIZE IL39_RTC_DATA_SIZE + +/* Size of uCode instruction memory in bootstrap state machine */ +#define IL39_MAX_BSM_SIZE IL39_RTC_INST_SIZE + +static inline int +il3945_hw_valid_rtc_data_addr(u32 addr) +{ + return (addr >= IL39_RTC_DATA_LOWER_BOUND && + addr < IL39_RTC_DATA_UPPER_BOUND); +} + +/* Base physical address of il3945_shared is provided to FH39_TSSR_CBB_BASE + * and &il3945_shared.rx_read_ptr[0] is provided to FH39_RCSR_RPTR_ADDR(0) */ +struct il3945_shared { + __le32 tx_base_ptr[8]; +} __packed; + +/************************************/ +/* iwl3945 Flow Handler Definitions */ +/************************************/ + +/** + * This I/O area is directly read/writable by driver (e.g. Linux uses writel()) + * Addresses are offsets from device's PCI hardware base address. + */ +#define FH39_MEM_LOWER_BOUND (0x0800) +#define FH39_MEM_UPPER_BOUND (0x1000) + +#define FH39_CBCC_TBL (FH39_MEM_LOWER_BOUND + 0x140) +#define FH39_TFDB_TBL (FH39_MEM_LOWER_BOUND + 0x180) +#define FH39_RCSR_TBL (FH39_MEM_LOWER_BOUND + 0x400) +#define FH39_RSSR_TBL (FH39_MEM_LOWER_BOUND + 0x4c0) +#define FH39_TCSR_TBL (FH39_MEM_LOWER_BOUND + 0x500) +#define FH39_TSSR_TBL (FH39_MEM_LOWER_BOUND + 0x680) + +/* TFDB (Transmit Frame Buffer Descriptor) */ +#define FH39_TFDB(_ch, buf) (FH39_TFDB_TBL + \ + ((_ch) * 2 + (buf)) * 0x28) +#define FH39_TFDB_CHNL_BUF_CTRL_REG(_ch) (FH39_TFDB_TBL + 0x50 * (_ch)) + +/* CBCC channel is [0,2] */ +#define FH39_CBCC(_ch) (FH39_CBCC_TBL + (_ch) * 0x8) +#define FH39_CBCC_CTRL(_ch) (FH39_CBCC(_ch) + 0x00) +#define FH39_CBCC_BASE(_ch) (FH39_CBCC(_ch) + 0x04) + +/* RCSR channel is [0,2] */ +#define FH39_RCSR(_ch) (FH39_RCSR_TBL + (_ch) * 0x40) +#define FH39_RCSR_CONFIG(_ch) (FH39_RCSR(_ch) + 0x00) +#define FH39_RCSR_RBD_BASE(_ch) (FH39_RCSR(_ch) + 0x04) +#define FH39_RCSR_WPTR(_ch) (FH39_RCSR(_ch) + 0x20) +#define FH39_RCSR_RPTR_ADDR(_ch) (FH39_RCSR(_ch) + 0x24) + +#define FH39_RSCSR_CHNL0_WPTR (FH39_RCSR_WPTR(0)) + +/* RSSR */ +#define FH39_RSSR_CTRL (FH39_RSSR_TBL + 0x000) +#define FH39_RSSR_STATUS (FH39_RSSR_TBL + 0x004) + +/* TCSR */ +#define FH39_TCSR(_ch) (FH39_TCSR_TBL + (_ch) * 0x20) +#define FH39_TCSR_CONFIG(_ch) (FH39_TCSR(_ch) + 0x00) +#define FH39_TCSR_CREDIT(_ch) (FH39_TCSR(_ch) + 0x04) +#define FH39_TCSR_BUFF_STTS(_ch) (FH39_TCSR(_ch) + 0x08) + +/* TSSR */ +#define FH39_TSSR_CBB_BASE (FH39_TSSR_TBL + 0x000) +#define FH39_TSSR_MSG_CONFIG (FH39_TSSR_TBL + 0x008) +#define FH39_TSSR_TX_STATUS (FH39_TSSR_TBL + 0x010) + +/* DBM */ + +#define FH39_SRVC_CHNL (6) + +#define FH39_RCSR_RX_CONFIG_REG_POS_RBDC_SIZE (20) +#define FH39_RCSR_RX_CONFIG_REG_POS_IRQ_RBTH (4) + +#define FH39_RCSR_RX_CONFIG_REG_BIT_WR_STTS_EN (0x08000000) + +#define FH39_RCSR_RX_CONFIG_REG_VAL_DMA_CHNL_EN_ENABLE (0x80000000) + +#define FH39_RCSR_RX_CONFIG_REG_VAL_RDRBD_EN_ENABLE (0x20000000) + +#define FH39_RCSR_RX_CONFIG_REG_VAL_MAX_FRAG_SIZE_128 (0x01000000) + +#define FH39_RCSR_RX_CONFIG_REG_VAL_IRQ_DEST_INT_HOST (0x00001000) + +#define FH39_RCSR_RX_CONFIG_REG_VAL_MSG_MODE_FH (0x00000000) + +#define FH39_TCSR_TX_CONFIG_REG_VAL_MSG_MODE_TXF (0x00000000) +#define FH39_TCSR_TX_CONFIG_REG_VAL_MSG_MODE_DRIVER (0x00000001) + +#define FH39_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_DISABLE_VAL (0x00000000) +#define FH39_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE_VAL (0x00000008) + +#define FH39_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_IFTFD (0x00200000) + +#define FH39_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_NOINT (0x00000000) + +#define FH39_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE (0x00000000) +#define FH39_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE (0x80000000) + +#define FH39_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_VALID (0x00004000) + +#define FH39_TCSR_CHNL_TX_BUF_STS_REG_BIT_TFDB_WPTR (0x00000001) + +#define FH39_TSSR_TX_MSG_CONFIG_REG_VAL_SNOOP_RD_TXPD_ON (0xFF000000) +#define FH39_TSSR_TX_MSG_CONFIG_REG_VAL_ORDER_RD_TXPD_ON (0x00FF0000) + +#define FH39_TSSR_TX_MSG_CONFIG_REG_VAL_MAX_FRAG_SIZE_128B (0x00000400) + +#define FH39_TSSR_TX_MSG_CONFIG_REG_VAL_SNOOP_RD_TFD_ON (0x00000100) +#define FH39_TSSR_TX_MSG_CONFIG_REG_VAL_ORDER_RD_CBB_ON (0x00000080) + +#define FH39_TSSR_TX_MSG_CONFIG_REG_VAL_ORDER_RSP_WAIT_TH (0x00000020) +#define FH39_TSSR_TX_MSG_CONFIG_REG_VAL_RSP_WAIT_TH (0x00000005) + +#define FH39_TSSR_TX_STATUS_REG_BIT_BUFS_EMPTY(_ch) (BIT(_ch) << 24) +#define FH39_TSSR_TX_STATUS_REG_BIT_NO_PEND_REQ(_ch) (BIT(_ch) << 16) + +#define FH39_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(_ch) \ + (FH39_TSSR_TX_STATUS_REG_BIT_BUFS_EMPTY(_ch) | \ + FH39_TSSR_TX_STATUS_REG_BIT_NO_PEND_REQ(_ch)) + +#define FH39_RSSR_CHNL0_RX_STATUS_CHNL_IDLE (0x01000000) + +struct il3945_tfd_tb { + __le32 addr; + __le32 len; +} __packed; + +struct il3945_tfd { + __le32 control_flags; + struct il3945_tfd_tb tbs[4]; + u8 __pad[28]; +} __packed; + +#ifdef CPTCFG_IWLEGACY_DEBUGFS +extern const struct il_debugfs_ops il3945_debugfs_ops; +#endif + +#endif diff --git a/drivers/net/wireless/iwlegacy/4965-calib.c b/drivers/net/wireless/iwlegacy/4965-calib.c new file mode 100644 index 0000000..e78bdef --- /dev/null +++ b/drivers/net/wireless/iwlegacy/4965-calib.c @@ -0,0 +1,934 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2011 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include + +#include "common.h" +#include "4965.h" + +/***************************************************************************** + * INIT calibrations framework + *****************************************************************************/ + +struct stats_general_data { + u32 beacon_silence_rssi_a; + u32 beacon_silence_rssi_b; + u32 beacon_silence_rssi_c; + u32 beacon_energy_a; + u32 beacon_energy_b; + u32 beacon_energy_c; +}; + +/***************************************************************************** + * RUNTIME calibrations framework + *****************************************************************************/ + +/* "false alarms" are signals that our DSP tries to lock onto, + * but then determines that they are either noise, or transmissions + * from a distant wireless network (also "noise", really) that get + * "stepped on" by stronger transmissions within our own network. + * This algorithm attempts to set a sensitivity level that is high + * enough to receive all of our own network traffic, but not so + * high that our DSP gets too busy trying to lock onto non-network + * activity/noise. */ +static int +il4965_sens_energy_cck(struct il_priv *il, u32 norm_fa, u32 rx_enable_time, + struct stats_general_data *rx_info) +{ + u32 max_nrg_cck = 0; + int i = 0; + u8 max_silence_rssi = 0; + u32 silence_ref = 0; + u8 silence_rssi_a = 0; + u8 silence_rssi_b = 0; + u8 silence_rssi_c = 0; + u32 val; + + /* "false_alarms" values below are cross-multiplications to assess the + * numbers of false alarms within the measured period of actual Rx + * (Rx is off when we're txing), vs the min/max expected false alarms + * (some should be expected if rx is sensitive enough) in a + * hypothetical listening period of 200 time units (TU), 204.8 msec: + * + * MIN_FA/fixed-time < false_alarms/actual-rx-time < MAX_FA/beacon-time + * + * */ + u32 false_alarms = norm_fa * 200 * 1024; + u32 max_false_alarms = MAX_FA_CCK * rx_enable_time; + u32 min_false_alarms = MIN_FA_CCK * rx_enable_time; + struct il_sensitivity_data *data = NULL; + const struct il_sensitivity_ranges *ranges = il->hw_params.sens; + + data = &(il->sensitivity_data); + + data->nrg_auto_corr_silence_diff = 0; + + /* Find max silence rssi among all 3 receivers. + * This is background noise, which may include transmissions from other + * networks, measured during silence before our network's beacon */ + silence_rssi_a = + (u8) ((rx_info->beacon_silence_rssi_a & ALL_BAND_FILTER) >> 8); + silence_rssi_b = + (u8) ((rx_info->beacon_silence_rssi_b & ALL_BAND_FILTER) >> 8); + silence_rssi_c = + (u8) ((rx_info->beacon_silence_rssi_c & ALL_BAND_FILTER) >> 8); + + val = max(silence_rssi_b, silence_rssi_c); + max_silence_rssi = max(silence_rssi_a, (u8) val); + + /* Store silence rssi in 20-beacon history table */ + data->nrg_silence_rssi[data->nrg_silence_idx] = max_silence_rssi; + data->nrg_silence_idx++; + if (data->nrg_silence_idx >= NRG_NUM_PREV_STAT_L) + data->nrg_silence_idx = 0; + + /* Find max silence rssi across 20 beacon history */ + for (i = 0; i < NRG_NUM_PREV_STAT_L; i++) { + val = data->nrg_silence_rssi[i]; + silence_ref = max(silence_ref, val); + } + D_CALIB("silence a %u, b %u, c %u, 20-bcn max %u\n", silence_rssi_a, + silence_rssi_b, silence_rssi_c, silence_ref); + + /* Find max rx energy (min value!) among all 3 receivers, + * measured during beacon frame. + * Save it in 10-beacon history table. */ + i = data->nrg_energy_idx; + val = min(rx_info->beacon_energy_b, rx_info->beacon_energy_c); + data->nrg_value[i] = min(rx_info->beacon_energy_a, val); + + data->nrg_energy_idx++; + if (data->nrg_energy_idx >= 10) + data->nrg_energy_idx = 0; + + /* Find min rx energy (max value) across 10 beacon history. + * This is the minimum signal level that we want to receive well. + * Add backoff (margin so we don't miss slightly lower energy frames). + * This establishes an upper bound (min value) for energy threshold. */ + max_nrg_cck = data->nrg_value[0]; + for (i = 1; i < 10; i++) + max_nrg_cck = (u32) max(max_nrg_cck, (data->nrg_value[i])); + max_nrg_cck += 6; + + D_CALIB("rx energy a %u, b %u, c %u, 10-bcn max/min %u\n", + rx_info->beacon_energy_a, rx_info->beacon_energy_b, + rx_info->beacon_energy_c, max_nrg_cck - 6); + + /* Count number of consecutive beacons with fewer-than-desired + * false alarms. */ + if (false_alarms < min_false_alarms) + data->num_in_cck_no_fa++; + else + data->num_in_cck_no_fa = 0; + D_CALIB("consecutive bcns with few false alarms = %u\n", + data->num_in_cck_no_fa); + + /* If we got too many false alarms this time, reduce sensitivity */ + if (false_alarms > max_false_alarms && + data->auto_corr_cck > AUTO_CORR_MAX_TH_CCK) { + D_CALIB("norm FA %u > max FA %u\n", false_alarms, + max_false_alarms); + D_CALIB("... reducing sensitivity\n"); + data->nrg_curr_state = IL_FA_TOO_MANY; + /* Store for "fewer than desired" on later beacon */ + data->nrg_silence_ref = silence_ref; + + /* increase energy threshold (reduce nrg value) + * to decrease sensitivity */ + data->nrg_th_cck = data->nrg_th_cck - NRG_STEP_CCK; + /* Else if we got fewer than desired, increase sensitivity */ + } else if (false_alarms < min_false_alarms) { + data->nrg_curr_state = IL_FA_TOO_FEW; + + /* Compare silence level with silence level for most recent + * healthy number or too many false alarms */ + data->nrg_auto_corr_silence_diff = + (s32) data->nrg_silence_ref - (s32) silence_ref; + + D_CALIB("norm FA %u < min FA %u, silence diff %d\n", + false_alarms, min_false_alarms, + data->nrg_auto_corr_silence_diff); + + /* Increase value to increase sensitivity, but only if: + * 1a) previous beacon did *not* have *too many* false alarms + * 1b) AND there's a significant difference in Rx levels + * from a previous beacon with too many, or healthy # FAs + * OR 2) We've seen a lot of beacons (100) with too few + * false alarms */ + if (data->nrg_prev_state != IL_FA_TOO_MANY && + (data->nrg_auto_corr_silence_diff > NRG_DIFF || + data->num_in_cck_no_fa > MAX_NUMBER_CCK_NO_FA)) { + + D_CALIB("... increasing sensitivity\n"); + /* Increase nrg value to increase sensitivity */ + val = data->nrg_th_cck + NRG_STEP_CCK; + data->nrg_th_cck = min((u32) ranges->min_nrg_cck, val); + } else { + D_CALIB("... but not changing sensitivity\n"); + } + + /* Else we got a healthy number of false alarms, keep status quo */ + } else { + D_CALIB(" FA in safe zone\n"); + data->nrg_curr_state = IL_FA_GOOD_RANGE; + + /* Store for use in "fewer than desired" with later beacon */ + data->nrg_silence_ref = silence_ref; + + /* If previous beacon had too many false alarms, + * give it some extra margin by reducing sensitivity again + * (but don't go below measured energy of desired Rx) */ + if (IL_FA_TOO_MANY == data->nrg_prev_state) { + D_CALIB("... increasing margin\n"); + if (data->nrg_th_cck > (max_nrg_cck + NRG_MARGIN)) + data->nrg_th_cck -= NRG_MARGIN; + else + data->nrg_th_cck = max_nrg_cck; + } + } + + /* Make sure the energy threshold does not go above the measured + * energy of the desired Rx signals (reduced by backoff margin), + * or else we might start missing Rx frames. + * Lower value is higher energy, so we use max()! + */ + data->nrg_th_cck = max(max_nrg_cck, data->nrg_th_cck); + D_CALIB("new nrg_th_cck %u\n", data->nrg_th_cck); + + data->nrg_prev_state = data->nrg_curr_state; + + /* Auto-correlation CCK algorithm */ + if (false_alarms > min_false_alarms) { + + /* increase auto_corr values to decrease sensitivity + * so the DSP won't be disturbed by the noise + */ + if (data->auto_corr_cck < AUTO_CORR_MAX_TH_CCK) + data->auto_corr_cck = AUTO_CORR_MAX_TH_CCK + 1; + else { + val = data->auto_corr_cck + AUTO_CORR_STEP_CCK; + data->auto_corr_cck = + min((u32) ranges->auto_corr_max_cck, val); + } + val = data->auto_corr_cck_mrc + AUTO_CORR_STEP_CCK; + data->auto_corr_cck_mrc = + min((u32) ranges->auto_corr_max_cck_mrc, val); + } else if (false_alarms < min_false_alarms && + (data->nrg_auto_corr_silence_diff > NRG_DIFF || + data->num_in_cck_no_fa > MAX_NUMBER_CCK_NO_FA)) { + + /* Decrease auto_corr values to increase sensitivity */ + val = data->auto_corr_cck - AUTO_CORR_STEP_CCK; + data->auto_corr_cck = max((u32) ranges->auto_corr_min_cck, val); + val = data->auto_corr_cck_mrc - AUTO_CORR_STEP_CCK; + data->auto_corr_cck_mrc = + max((u32) ranges->auto_corr_min_cck_mrc, val); + } + + return 0; +} + +static int +il4965_sens_auto_corr_ofdm(struct il_priv *il, u32 norm_fa, u32 rx_enable_time) +{ + u32 val; + u32 false_alarms = norm_fa * 200 * 1024; + u32 max_false_alarms = MAX_FA_OFDM * rx_enable_time; + u32 min_false_alarms = MIN_FA_OFDM * rx_enable_time; + struct il_sensitivity_data *data = NULL; + const struct il_sensitivity_ranges *ranges = il->hw_params.sens; + + data = &(il->sensitivity_data); + + /* If we got too many false alarms this time, reduce sensitivity */ + if (false_alarms > max_false_alarms) { + + D_CALIB("norm FA %u > max FA %u)\n", false_alarms, + max_false_alarms); + + val = data->auto_corr_ofdm + AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm = + min((u32) ranges->auto_corr_max_ofdm, val); + + val = data->auto_corr_ofdm_mrc + AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_mrc = + min((u32) ranges->auto_corr_max_ofdm_mrc, val); + + val = data->auto_corr_ofdm_x1 + AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_x1 = + min((u32) ranges->auto_corr_max_ofdm_x1, val); + + val = data->auto_corr_ofdm_mrc_x1 + AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_mrc_x1 = + min((u32) ranges->auto_corr_max_ofdm_mrc_x1, val); + } + + /* Else if we got fewer than desired, increase sensitivity */ + else if (false_alarms < min_false_alarms) { + + D_CALIB("norm FA %u < min FA %u\n", false_alarms, + min_false_alarms); + + val = data->auto_corr_ofdm - AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm = + max((u32) ranges->auto_corr_min_ofdm, val); + + val = data->auto_corr_ofdm_mrc - AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_mrc = + max((u32) ranges->auto_corr_min_ofdm_mrc, val); + + val = data->auto_corr_ofdm_x1 - AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_x1 = + max((u32) ranges->auto_corr_min_ofdm_x1, val); + + val = data->auto_corr_ofdm_mrc_x1 - AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_mrc_x1 = + max((u32) ranges->auto_corr_min_ofdm_mrc_x1, val); + } else { + D_CALIB("min FA %u < norm FA %u < max FA %u OK\n", + min_false_alarms, false_alarms, max_false_alarms); + } + return 0; +} + +static void +il4965_prepare_legacy_sensitivity_tbl(struct il_priv *il, + struct il_sensitivity_data *data, + __le16 *tbl) +{ + tbl[HD_AUTO_CORR32_X4_TH_ADD_MIN_IDX] = + cpu_to_le16((u16) data->auto_corr_ofdm); + tbl[HD_AUTO_CORR32_X4_TH_ADD_MIN_MRC_IDX] = + cpu_to_le16((u16) data->auto_corr_ofdm_mrc); + tbl[HD_AUTO_CORR32_X1_TH_ADD_MIN_IDX] = + cpu_to_le16((u16) data->auto_corr_ofdm_x1); + tbl[HD_AUTO_CORR32_X1_TH_ADD_MIN_MRC_IDX] = + cpu_to_le16((u16) data->auto_corr_ofdm_mrc_x1); + + tbl[HD_AUTO_CORR40_X4_TH_ADD_MIN_IDX] = + cpu_to_le16((u16) data->auto_corr_cck); + tbl[HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_IDX] = + cpu_to_le16((u16) data->auto_corr_cck_mrc); + + tbl[HD_MIN_ENERGY_CCK_DET_IDX] = cpu_to_le16((u16) data->nrg_th_cck); + tbl[HD_MIN_ENERGY_OFDM_DET_IDX] = cpu_to_le16((u16) data->nrg_th_ofdm); + + tbl[HD_BARKER_CORR_TH_ADD_MIN_IDX] = + cpu_to_le16(data->barker_corr_th_min); + tbl[HD_BARKER_CORR_TH_ADD_MIN_MRC_IDX] = + cpu_to_le16(data->barker_corr_th_min_mrc); + tbl[HD_OFDM_ENERGY_TH_IN_IDX] = cpu_to_le16(data->nrg_th_cca); + + D_CALIB("ofdm: ac %u mrc %u x1 %u mrc_x1 %u thresh %u\n", + data->auto_corr_ofdm, data->auto_corr_ofdm_mrc, + data->auto_corr_ofdm_x1, data->auto_corr_ofdm_mrc_x1, + data->nrg_th_ofdm); + + D_CALIB("cck: ac %u mrc %u thresh %u\n", data->auto_corr_cck, + data->auto_corr_cck_mrc, data->nrg_th_cck); +} + +/* Prepare a C_SENSITIVITY, send to uCode if values have changed */ +static int +il4965_sensitivity_write(struct il_priv *il) +{ + struct il_sensitivity_cmd cmd; + struct il_sensitivity_data *data = NULL; + struct il_host_cmd cmd_out = { + .id = C_SENSITIVITY, + .len = sizeof(struct il_sensitivity_cmd), + .flags = CMD_ASYNC, + .data = &cmd, + }; + + data = &(il->sensitivity_data); + + memset(&cmd, 0, sizeof(cmd)); + + il4965_prepare_legacy_sensitivity_tbl(il, data, &cmd.table[0]); + + /* Update uCode's "work" table, and copy it to DSP */ + cmd.control = C_SENSITIVITY_CONTROL_WORK_TBL; + + /* Don't send command to uCode if nothing has changed */ + if (!memcmp + (&cmd.table[0], &(il->sensitivity_tbl[0]), + sizeof(u16) * HD_TBL_SIZE)) { + D_CALIB("No change in C_SENSITIVITY\n"); + return 0; + } + + /* Copy table for comparison next time */ + memcpy(&(il->sensitivity_tbl[0]), &(cmd.table[0]), + sizeof(u16) * HD_TBL_SIZE); + + return il_send_cmd(il, &cmd_out); +} + +void +il4965_init_sensitivity(struct il_priv *il) +{ + int ret = 0; + int i; + struct il_sensitivity_data *data = NULL; + const struct il_sensitivity_ranges *ranges = il->hw_params.sens; + + if (il->disable_sens_cal) + return; + + D_CALIB("Start il4965_init_sensitivity\n"); + + /* Clear driver's sensitivity algo data */ + data = &(il->sensitivity_data); + + if (ranges == NULL) + return; + + memset(data, 0, sizeof(struct il_sensitivity_data)); + + data->num_in_cck_no_fa = 0; + data->nrg_curr_state = IL_FA_TOO_MANY; + data->nrg_prev_state = IL_FA_TOO_MANY; + data->nrg_silence_ref = 0; + data->nrg_silence_idx = 0; + data->nrg_energy_idx = 0; + + for (i = 0; i < 10; i++) + data->nrg_value[i] = 0; + + for (i = 0; i < NRG_NUM_PREV_STAT_L; i++) + data->nrg_silence_rssi[i] = 0; + + data->auto_corr_ofdm = ranges->auto_corr_min_ofdm; + data->auto_corr_ofdm_mrc = ranges->auto_corr_min_ofdm_mrc; + data->auto_corr_ofdm_x1 = ranges->auto_corr_min_ofdm_x1; + data->auto_corr_ofdm_mrc_x1 = ranges->auto_corr_min_ofdm_mrc_x1; + data->auto_corr_cck = AUTO_CORR_CCK_MIN_VAL_DEF; + data->auto_corr_cck_mrc = ranges->auto_corr_min_cck_mrc; + data->nrg_th_cck = ranges->nrg_th_cck; + data->nrg_th_ofdm = ranges->nrg_th_ofdm; + data->barker_corr_th_min = ranges->barker_corr_th_min; + data->barker_corr_th_min_mrc = ranges->barker_corr_th_min_mrc; + data->nrg_th_cca = ranges->nrg_th_cca; + + data->last_bad_plcp_cnt_ofdm = 0; + data->last_fa_cnt_ofdm = 0; + data->last_bad_plcp_cnt_cck = 0; + data->last_fa_cnt_cck = 0; + + ret |= il4965_sensitivity_write(il); + D_CALIB("<disable_sens_cal) + return; + + data = &(il->sensitivity_data); + + if (!il_is_any_associated(il)) { + D_CALIB("<< - not associated\n"); + return; + } + + spin_lock_irqsave(&il->lock, flags); + + rx_info = &(((struct il_notif_stats *)resp)->rx.general); + ofdm = &(((struct il_notif_stats *)resp)->rx.ofdm); + cck = &(((struct il_notif_stats *)resp)->rx.cck); + + if (rx_info->interference_data_flag != INTERFERENCE_DATA_AVAILABLE) { + D_CALIB("<< invalid data.\n"); + spin_unlock_irqrestore(&il->lock, flags); + return; + } + + /* Extract Statistics: */ + rx_enable_time = le32_to_cpu(rx_info->channel_load); + fa_cck = le32_to_cpu(cck->false_alarm_cnt); + fa_ofdm = le32_to_cpu(ofdm->false_alarm_cnt); + bad_plcp_cck = le32_to_cpu(cck->plcp_err); + bad_plcp_ofdm = le32_to_cpu(ofdm->plcp_err); + + statis.beacon_silence_rssi_a = + le32_to_cpu(rx_info->beacon_silence_rssi_a); + statis.beacon_silence_rssi_b = + le32_to_cpu(rx_info->beacon_silence_rssi_b); + statis.beacon_silence_rssi_c = + le32_to_cpu(rx_info->beacon_silence_rssi_c); + statis.beacon_energy_a = le32_to_cpu(rx_info->beacon_energy_a); + statis.beacon_energy_b = le32_to_cpu(rx_info->beacon_energy_b); + statis.beacon_energy_c = le32_to_cpu(rx_info->beacon_energy_c); + + spin_unlock_irqrestore(&il->lock, flags); + + D_CALIB("rx_enable_time = %u usecs\n", rx_enable_time); + + if (!rx_enable_time) { + D_CALIB("<< RX Enable Time == 0!\n"); + return; + } + + /* These stats increase monotonically, and do not reset + * at each beacon. Calculate difference from last value, or just + * use the new stats value if it has reset or wrapped around. */ + if (data->last_bad_plcp_cnt_cck > bad_plcp_cck) + data->last_bad_plcp_cnt_cck = bad_plcp_cck; + else { + bad_plcp_cck -= data->last_bad_plcp_cnt_cck; + data->last_bad_plcp_cnt_cck += bad_plcp_cck; + } + + if (data->last_bad_plcp_cnt_ofdm > bad_plcp_ofdm) + data->last_bad_plcp_cnt_ofdm = bad_plcp_ofdm; + else { + bad_plcp_ofdm -= data->last_bad_plcp_cnt_ofdm; + data->last_bad_plcp_cnt_ofdm += bad_plcp_ofdm; + } + + if (data->last_fa_cnt_ofdm > fa_ofdm) + data->last_fa_cnt_ofdm = fa_ofdm; + else { + fa_ofdm -= data->last_fa_cnt_ofdm; + data->last_fa_cnt_ofdm += fa_ofdm; + } + + if (data->last_fa_cnt_cck > fa_cck) + data->last_fa_cnt_cck = fa_cck; + else { + fa_cck -= data->last_fa_cnt_cck; + data->last_fa_cnt_cck += fa_cck; + } + + /* Total aborted signal locks */ + norm_fa_ofdm = fa_ofdm + bad_plcp_ofdm; + norm_fa_cck = fa_cck + bad_plcp_cck; + + D_CALIB("cck: fa %u badp %u ofdm: fa %u badp %u\n", fa_cck, + bad_plcp_cck, fa_ofdm, bad_plcp_ofdm); + + il4965_sens_auto_corr_ofdm(il, norm_fa_ofdm, rx_enable_time); + il4965_sens_energy_cck(il, norm_fa_cck, rx_enable_time, &statis); + + il4965_sensitivity_write(il); +} + +static inline u8 +il4965_find_first_chain(u8 mask) +{ + if (mask & ANT_A) + return CHAIN_A; + if (mask & ANT_B) + return CHAIN_B; + return CHAIN_C; +} + +/** + * Run disconnected antenna algorithm to find out which antennas are + * disconnected. + */ +static void +il4965_find_disconn_antenna(struct il_priv *il, u32 * average_sig, + struct il_chain_noise_data *data) +{ + u32 active_chains = 0; + u32 max_average_sig; + u16 max_average_sig_antenna_i; + u8 num_tx_chains; + u8 first_chain; + u16 i = 0; + + average_sig[0] = + data->chain_signal_a / + il->cfg->chain_noise_num_beacons; + average_sig[1] = + data->chain_signal_b / + il->cfg->chain_noise_num_beacons; + average_sig[2] = + data->chain_signal_c / + il->cfg->chain_noise_num_beacons; + + if (average_sig[0] >= average_sig[1]) { + max_average_sig = average_sig[0]; + max_average_sig_antenna_i = 0; + active_chains = (1 << max_average_sig_antenna_i); + } else { + max_average_sig = average_sig[1]; + max_average_sig_antenna_i = 1; + active_chains = (1 << max_average_sig_antenna_i); + } + + if (average_sig[2] >= max_average_sig) { + max_average_sig = average_sig[2]; + max_average_sig_antenna_i = 2; + active_chains = (1 << max_average_sig_antenna_i); + } + + D_CALIB("average_sig: a %d b %d c %d\n", average_sig[0], average_sig[1], + average_sig[2]); + D_CALIB("max_average_sig = %d, antenna %d\n", max_average_sig, + max_average_sig_antenna_i); + + /* Compare signal strengths for all 3 receivers. */ + for (i = 0; i < NUM_RX_CHAINS; i++) { + if (i != max_average_sig_antenna_i) { + s32 rssi_delta = (max_average_sig - average_sig[i]); + + /* If signal is very weak, compared with + * strongest, mark it as disconnected. */ + if (rssi_delta > MAXIMUM_ALLOWED_PATHLOSS) + data->disconn_array[i] = 1; + else + active_chains |= (1 << i); + D_CALIB("i = %d rssiDelta = %d " + "disconn_array[i] = %d\n", i, rssi_delta, + data->disconn_array[i]); + } + } + + /* + * The above algorithm sometimes fails when the ucode + * reports 0 for all chains. It's not clear why that + * happens to start with, but it is then causing trouble + * because this can make us enable more chains than the + * hardware really has. + * + * To be safe, simply mask out any chains that we know + * are not on the device. + */ + active_chains &= il->hw_params.valid_rx_ant; + + num_tx_chains = 0; + for (i = 0; i < NUM_RX_CHAINS; i++) { + /* loops on all the bits of + * il->hw_setting.valid_tx_ant */ + u8 ant_msk = (1 << i); + if (!(il->hw_params.valid_tx_ant & ant_msk)) + continue; + + num_tx_chains++; + if (data->disconn_array[i] == 0) + /* there is a Tx antenna connected */ + break; + if (num_tx_chains == il->hw_params.tx_chains_num && + data->disconn_array[i]) { + /* + * If all chains are disconnected + * connect the first valid tx chain + */ + first_chain = + il4965_find_first_chain(il->cfg->valid_tx_ant); + data->disconn_array[first_chain] = 0; + active_chains |= BIT(first_chain); + D_CALIB("All Tx chains are disconnected" + "- declare %d as connected\n", first_chain); + break; + } + } + + if (active_chains != il->hw_params.valid_rx_ant && + active_chains != il->chain_noise_data.active_chains) + D_CALIB("Detected that not all antennas are connected! " + "Connected: %#x, valid: %#x.\n", active_chains, + il->hw_params.valid_rx_ant); + + /* Save for use within RXON, TX, SCAN commands, etc. */ + data->active_chains = active_chains; + D_CALIB("active_chains (bitwise) = 0x%x\n", active_chains); +} + +static void +il4965_gain_computation(struct il_priv *il, u32 * average_noise, + u16 min_average_noise_antenna_i, u32 min_average_noise, + u8 default_chain) +{ + int i, ret; + struct il_chain_noise_data *data = &il->chain_noise_data; + + data->delta_gain_code[min_average_noise_antenna_i] = 0; + + for (i = default_chain; i < NUM_RX_CHAINS; i++) { + s32 delta_g = 0; + + if (!data->disconn_array[i] && + data->delta_gain_code[i] == + CHAIN_NOISE_DELTA_GAIN_INIT_VAL) { + delta_g = average_noise[i] - min_average_noise; + data->delta_gain_code[i] = (u8) ((delta_g * 10) / 15); + data->delta_gain_code[i] = + min(data->delta_gain_code[i], + (u8) CHAIN_NOISE_MAX_DELTA_GAIN_CODE); + + data->delta_gain_code[i] = + (data->delta_gain_code[i] | (1 << 2)); + } else { + data->delta_gain_code[i] = 0; + } + } + D_CALIB("delta_gain_codes: a %d b %d c %d\n", data->delta_gain_code[0], + data->delta_gain_code[1], data->delta_gain_code[2]); + + /* Differential gain gets sent to uCode only once */ + if (!data->radio_write) { + struct il_calib_diff_gain_cmd cmd; + data->radio_write = 1; + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.op_code = IL_PHY_CALIBRATE_DIFF_GAIN_CMD; + cmd.diff_gain_a = data->delta_gain_code[0]; + cmd.diff_gain_b = data->delta_gain_code[1]; + cmd.diff_gain_c = data->delta_gain_code[2]; + ret = il_send_cmd_pdu(il, C_PHY_CALIBRATION, sizeof(cmd), &cmd); + if (ret) + D_CALIB("fail sending cmd " "C_PHY_CALIBRATION\n"); + + /* TODO we might want recalculate + * rx_chain in rxon cmd */ + + /* Mark so we run this algo only once! */ + data->state = IL_CHAIN_NOISE_CALIBRATED; + } +} + +/* + * Accumulate 16 beacons of signal and noise stats for each of + * 3 receivers/antennas/rx-chains, then figure out: + * 1) Which antennas are connected. + * 2) Differential rx gain settings to balance the 3 receivers. + */ +void +il4965_chain_noise_calibration(struct il_priv *il, void *stat_resp) +{ + struct il_chain_noise_data *data = NULL; + + u32 chain_noise_a; + u32 chain_noise_b; + u32 chain_noise_c; + u32 chain_sig_a; + u32 chain_sig_b; + u32 chain_sig_c; + u32 average_sig[NUM_RX_CHAINS] = { INITIALIZATION_VALUE }; + u32 average_noise[NUM_RX_CHAINS] = { INITIALIZATION_VALUE }; + u32 min_average_noise = MIN_AVERAGE_NOISE_MAX_VALUE; + u16 min_average_noise_antenna_i = INITIALIZATION_VALUE; + u16 i = 0; + u16 rxon_chnum = INITIALIZATION_VALUE; + u16 stat_chnum = INITIALIZATION_VALUE; + u8 rxon_band24; + u8 stat_band24; + unsigned long flags; + struct stats_rx_non_phy *rx_info; + + if (il->disable_chain_noise_cal) + return; + + data = &(il->chain_noise_data); + + /* + * Accumulate just the first "chain_noise_num_beacons" after + * the first association, then we're done forever. + */ + if (data->state != IL_CHAIN_NOISE_ACCUMULATE) { + if (data->state == IL_CHAIN_NOISE_ALIVE) + D_CALIB("Wait for noise calib reset\n"); + return; + } + + spin_lock_irqsave(&il->lock, flags); + + rx_info = &(((struct il_notif_stats *)stat_resp)->rx.general); + + if (rx_info->interference_data_flag != INTERFERENCE_DATA_AVAILABLE) { + D_CALIB(" << Interference data unavailable\n"); + spin_unlock_irqrestore(&il->lock, flags); + return; + } + + rxon_band24 = !!(il->staging.flags & RXON_FLG_BAND_24G_MSK); + rxon_chnum = le16_to_cpu(il->staging.channel); + + stat_band24 = + !!(((struct il_notif_stats *)stat_resp)-> + flag & STATS_REPLY_FLG_BAND_24G_MSK); + stat_chnum = + le32_to_cpu(((struct il_notif_stats *)stat_resp)->flag) >> 16; + + /* Make sure we accumulate data for just the associated channel + * (even if scanning). */ + if (rxon_chnum != stat_chnum || rxon_band24 != stat_band24) { + D_CALIB("Stats not from chan=%d, band24=%d\n", rxon_chnum, + rxon_band24); + spin_unlock_irqrestore(&il->lock, flags); + return; + } + + /* + * Accumulate beacon stats values across + * "chain_noise_num_beacons" + */ + chain_noise_a = + le32_to_cpu(rx_info->beacon_silence_rssi_a) & IN_BAND_FILTER; + chain_noise_b = + le32_to_cpu(rx_info->beacon_silence_rssi_b) & IN_BAND_FILTER; + chain_noise_c = + le32_to_cpu(rx_info->beacon_silence_rssi_c) & IN_BAND_FILTER; + + chain_sig_a = le32_to_cpu(rx_info->beacon_rssi_a) & IN_BAND_FILTER; + chain_sig_b = le32_to_cpu(rx_info->beacon_rssi_b) & IN_BAND_FILTER; + chain_sig_c = le32_to_cpu(rx_info->beacon_rssi_c) & IN_BAND_FILTER; + + spin_unlock_irqrestore(&il->lock, flags); + + data->beacon_count++; + + data->chain_noise_a = (chain_noise_a + data->chain_noise_a); + data->chain_noise_b = (chain_noise_b + data->chain_noise_b); + data->chain_noise_c = (chain_noise_c + data->chain_noise_c); + + data->chain_signal_a = (chain_sig_a + data->chain_signal_a); + data->chain_signal_b = (chain_sig_b + data->chain_signal_b); + data->chain_signal_c = (chain_sig_c + data->chain_signal_c); + + D_CALIB("chan=%d, band24=%d, beacon=%d\n", rxon_chnum, rxon_band24, + data->beacon_count); + D_CALIB("chain_sig: a %d b %d c %d\n", chain_sig_a, chain_sig_b, + chain_sig_c); + D_CALIB("chain_noise: a %d b %d c %d\n", chain_noise_a, chain_noise_b, + chain_noise_c); + + /* If this is the "chain_noise_num_beacons", determine: + * 1) Disconnected antennas (using signal strengths) + * 2) Differential gain (using silence noise) to balance receivers */ + if (data->beacon_count != il->cfg->chain_noise_num_beacons) + return; + + /* Analyze signal for disconnected antenna */ + il4965_find_disconn_antenna(il, average_sig, data); + + /* Analyze noise for rx balance */ + average_noise[0] = + data->chain_noise_a / il->cfg->chain_noise_num_beacons; + average_noise[1] = + data->chain_noise_b / il->cfg->chain_noise_num_beacons; + average_noise[2] = + data->chain_noise_c / il->cfg->chain_noise_num_beacons; + + for (i = 0; i < NUM_RX_CHAINS; i++) { + if (!data->disconn_array[i] && + average_noise[i] <= min_average_noise) { + /* This means that chain i is active and has + * lower noise values so far: */ + min_average_noise = average_noise[i]; + min_average_noise_antenna_i = i; + } + } + + D_CALIB("average_noise: a %d b %d c %d\n", average_noise[0], + average_noise[1], average_noise[2]); + + D_CALIB("min_average_noise = %d, antenna %d\n", min_average_noise, + min_average_noise_antenna_i); + + il4965_gain_computation(il, average_noise, min_average_noise_antenna_i, + min_average_noise, + il4965_find_first_chain(il->cfg->valid_rx_ant)); + + /* Some power changes may have been made during the calibration. + * Update and commit the RXON + */ + if (il->ops->update_chain_flags) + il->ops->update_chain_flags(il); + + data->state = IL_CHAIN_NOISE_DONE; + il_power_update_mode(il, false); +} + +void +il4965_reset_run_time_calib(struct il_priv *il) +{ + int i; + memset(&(il->sensitivity_data), 0, sizeof(struct il_sensitivity_data)); + memset(&(il->chain_noise_data), 0, sizeof(struct il_chain_noise_data)); + for (i = 0; i < NUM_RX_CHAINS; i++) + il->chain_noise_data.delta_gain_code[i] = + CHAIN_NOISE_DELTA_GAIN_INIT_VAL; + + /* Ask for stats now, the uCode will send notification + * periodically after association */ + il_send_stats_request(il, CMD_ASYNC, true); +} diff --git a/drivers/net/wireless/iwlegacy/4965-debug.c b/drivers/net/wireless/iwlegacy/4965-debug.c new file mode 100644 index 0000000..c8153fc --- /dev/null +++ b/drivers/net/wireless/iwlegacy/4965-debug.c @@ -0,0 +1,752 @@ +/****************************************************************************** +* +* GPL LICENSE SUMMARY +* +* Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of version 2 of the GNU General Public License as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, but +* WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, +* USA +* +* The full GNU General Public License is included in this distribution +* in the file called LICENSE.GPL. +* +* Contact Information: +* Intel Linux Wireless +* Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 +*****************************************************************************/ +#include "common.h" +#include "4965.h" + +static const char *fmt_value = " %-30s %10u\n"; +static const char *fmt_table = " %-30s %10u %10u %10u %10u\n"; +static const char *fmt_header = + "%-32s current cumulative delta max\n"; + +static int +il4965_stats_flag(struct il_priv *il, char *buf, int bufsz) +{ + int p = 0; + u32 flag; + + flag = le32_to_cpu(il->_4965.stats.flag); + + p += scnprintf(buf + p, bufsz - p, "Statistics Flag(0x%X):\n", flag); + if (flag & UCODE_STATS_CLEAR_MSK) + p += scnprintf(buf + p, bufsz - p, + "\tStatistics have been cleared\n"); + p += scnprintf(buf + p, bufsz - p, "\tOperational Frequency: %s\n", + (flag & UCODE_STATS_FREQUENCY_MSK) ? "2.4 GHz" : + "5.2 GHz"); + p += scnprintf(buf + p, bufsz - p, "\tTGj Narrow Band: %s\n", + (flag & UCODE_STATS_NARROW_BAND_MSK) ? "enabled" : + "disabled"); + + return p; +} + +ssize_t +il4965_ucode_rx_stats_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct il_priv *il = file->private_data; + int pos = 0; + char *buf; + int bufsz = + sizeof(struct stats_rx_phy) * 40 + + sizeof(struct stats_rx_non_phy) * 40 + + sizeof(struct stats_rx_ht_phy) * 40 + 400; + ssize_t ret; + struct stats_rx_phy *ofdm, *accum_ofdm, *delta_ofdm, *max_ofdm; + struct stats_rx_phy *cck, *accum_cck, *delta_cck, *max_cck; + struct stats_rx_non_phy *general, *accum_general; + struct stats_rx_non_phy *delta_general, *max_general; + struct stats_rx_ht_phy *ht, *accum_ht, *delta_ht, *max_ht; + + if (!il_is_alive(il)) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) { + IL_ERR("Can not allocate Buffer\n"); + return -ENOMEM; + } + + /* + * the statistic information display here is based on + * the last stats notification from uCode + * might not reflect the current uCode activity + */ + ofdm = &il->_4965.stats.rx.ofdm; + cck = &il->_4965.stats.rx.cck; + general = &il->_4965.stats.rx.general; + ht = &il->_4965.stats.rx.ofdm_ht; + accum_ofdm = &il->_4965.accum_stats.rx.ofdm; + accum_cck = &il->_4965.accum_stats.rx.cck; + accum_general = &il->_4965.accum_stats.rx.general; + accum_ht = &il->_4965.accum_stats.rx.ofdm_ht; + delta_ofdm = &il->_4965.delta_stats.rx.ofdm; + delta_cck = &il->_4965.delta_stats.rx.cck; + delta_general = &il->_4965.delta_stats.rx.general; + delta_ht = &il->_4965.delta_stats.rx.ofdm_ht; + max_ofdm = &il->_4965.max_delta.rx.ofdm; + max_cck = &il->_4965.max_delta.rx.cck; + max_general = &il->_4965.max_delta.rx.general; + max_ht = &il->_4965.max_delta.rx.ofdm_ht; + + pos += il4965_stats_flag(il, buf, bufsz); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_header, + "Statistics_Rx - OFDM:"); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "ina_cnt:", + le32_to_cpu(ofdm->ina_cnt), accum_ofdm->ina_cnt, + delta_ofdm->ina_cnt, max_ofdm->ina_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "fina_cnt:", + le32_to_cpu(ofdm->fina_cnt), accum_ofdm->fina_cnt, + delta_ofdm->fina_cnt, max_ofdm->fina_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "plcp_err:", + le32_to_cpu(ofdm->plcp_err), accum_ofdm->plcp_err, + delta_ofdm->plcp_err, max_ofdm->plcp_err); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "crc32_err:", + le32_to_cpu(ofdm->crc32_err), accum_ofdm->crc32_err, + delta_ofdm->crc32_err, max_ofdm->crc32_err); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "overrun_err:", + le32_to_cpu(ofdm->overrun_err), accum_ofdm->overrun_err, + delta_ofdm->overrun_err, max_ofdm->overrun_err); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "early_overrun_err:", + le32_to_cpu(ofdm->early_overrun_err), + accum_ofdm->early_overrun_err, + delta_ofdm->early_overrun_err, + max_ofdm->early_overrun_err); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "crc32_good:", + le32_to_cpu(ofdm->crc32_good), accum_ofdm->crc32_good, + delta_ofdm->crc32_good, max_ofdm->crc32_good); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "false_alarm_cnt:", + le32_to_cpu(ofdm->false_alarm_cnt), + accum_ofdm->false_alarm_cnt, delta_ofdm->false_alarm_cnt, + max_ofdm->false_alarm_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "fina_sync_err_cnt:", + le32_to_cpu(ofdm->fina_sync_err_cnt), + accum_ofdm->fina_sync_err_cnt, + delta_ofdm->fina_sync_err_cnt, + max_ofdm->fina_sync_err_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "sfd_timeout:", + le32_to_cpu(ofdm->sfd_timeout), accum_ofdm->sfd_timeout, + delta_ofdm->sfd_timeout, max_ofdm->sfd_timeout); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "fina_timeout:", + le32_to_cpu(ofdm->fina_timeout), accum_ofdm->fina_timeout, + delta_ofdm->fina_timeout, max_ofdm->fina_timeout); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "unresponded_rts:", + le32_to_cpu(ofdm->unresponded_rts), + accum_ofdm->unresponded_rts, delta_ofdm->unresponded_rts, + max_ofdm->unresponded_rts); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "rxe_frame_lmt_ovrun:", + le32_to_cpu(ofdm->rxe_frame_limit_overrun), + accum_ofdm->rxe_frame_limit_overrun, + delta_ofdm->rxe_frame_limit_overrun, + max_ofdm->rxe_frame_limit_overrun); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "sent_ack_cnt:", + le32_to_cpu(ofdm->sent_ack_cnt), accum_ofdm->sent_ack_cnt, + delta_ofdm->sent_ack_cnt, max_ofdm->sent_ack_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "sent_cts_cnt:", + le32_to_cpu(ofdm->sent_cts_cnt), accum_ofdm->sent_cts_cnt, + delta_ofdm->sent_cts_cnt, max_ofdm->sent_cts_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "sent_ba_rsp_cnt:", + le32_to_cpu(ofdm->sent_ba_rsp_cnt), + accum_ofdm->sent_ba_rsp_cnt, delta_ofdm->sent_ba_rsp_cnt, + max_ofdm->sent_ba_rsp_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "dsp_self_kill:", + le32_to_cpu(ofdm->dsp_self_kill), + accum_ofdm->dsp_self_kill, delta_ofdm->dsp_self_kill, + max_ofdm->dsp_self_kill); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "mh_format_err:", + le32_to_cpu(ofdm->mh_format_err), + accum_ofdm->mh_format_err, delta_ofdm->mh_format_err, + max_ofdm->mh_format_err); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "re_acq_main_rssi_sum:", + le32_to_cpu(ofdm->re_acq_main_rssi_sum), + accum_ofdm->re_acq_main_rssi_sum, + delta_ofdm->re_acq_main_rssi_sum, + max_ofdm->re_acq_main_rssi_sum); + + pos += + scnprintf(buf + pos, bufsz - pos, fmt_header, + "Statistics_Rx - CCK:"); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "ina_cnt:", + le32_to_cpu(cck->ina_cnt), accum_cck->ina_cnt, + delta_cck->ina_cnt, max_cck->ina_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "fina_cnt:", + le32_to_cpu(cck->fina_cnt), accum_cck->fina_cnt, + delta_cck->fina_cnt, max_cck->fina_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "plcp_err:", + le32_to_cpu(cck->plcp_err), accum_cck->plcp_err, + delta_cck->plcp_err, max_cck->plcp_err); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "crc32_err:", + le32_to_cpu(cck->crc32_err), accum_cck->crc32_err, + delta_cck->crc32_err, max_cck->crc32_err); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "overrun_err:", + le32_to_cpu(cck->overrun_err), accum_cck->overrun_err, + delta_cck->overrun_err, max_cck->overrun_err); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "early_overrun_err:", + le32_to_cpu(cck->early_overrun_err), + accum_cck->early_overrun_err, + delta_cck->early_overrun_err, max_cck->early_overrun_err); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "crc32_good:", + le32_to_cpu(cck->crc32_good), accum_cck->crc32_good, + delta_cck->crc32_good, max_cck->crc32_good); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "false_alarm_cnt:", + le32_to_cpu(cck->false_alarm_cnt), + accum_cck->false_alarm_cnt, delta_cck->false_alarm_cnt, + max_cck->false_alarm_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "fina_sync_err_cnt:", + le32_to_cpu(cck->fina_sync_err_cnt), + accum_cck->fina_sync_err_cnt, + delta_cck->fina_sync_err_cnt, max_cck->fina_sync_err_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "sfd_timeout:", + le32_to_cpu(cck->sfd_timeout), accum_cck->sfd_timeout, + delta_cck->sfd_timeout, max_cck->sfd_timeout); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "fina_timeout:", + le32_to_cpu(cck->fina_timeout), accum_cck->fina_timeout, + delta_cck->fina_timeout, max_cck->fina_timeout); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "unresponded_rts:", + le32_to_cpu(cck->unresponded_rts), + accum_cck->unresponded_rts, delta_cck->unresponded_rts, + max_cck->unresponded_rts); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "rxe_frame_lmt_ovrun:", + le32_to_cpu(cck->rxe_frame_limit_overrun), + accum_cck->rxe_frame_limit_overrun, + delta_cck->rxe_frame_limit_overrun, + max_cck->rxe_frame_limit_overrun); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "sent_ack_cnt:", + le32_to_cpu(cck->sent_ack_cnt), accum_cck->sent_ack_cnt, + delta_cck->sent_ack_cnt, max_cck->sent_ack_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "sent_cts_cnt:", + le32_to_cpu(cck->sent_cts_cnt), accum_cck->sent_cts_cnt, + delta_cck->sent_cts_cnt, max_cck->sent_cts_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "sent_ba_rsp_cnt:", + le32_to_cpu(cck->sent_ba_rsp_cnt), + accum_cck->sent_ba_rsp_cnt, delta_cck->sent_ba_rsp_cnt, + max_cck->sent_ba_rsp_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "dsp_self_kill:", + le32_to_cpu(cck->dsp_self_kill), accum_cck->dsp_self_kill, + delta_cck->dsp_self_kill, max_cck->dsp_self_kill); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "mh_format_err:", + le32_to_cpu(cck->mh_format_err), accum_cck->mh_format_err, + delta_cck->mh_format_err, max_cck->mh_format_err); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "re_acq_main_rssi_sum:", + le32_to_cpu(cck->re_acq_main_rssi_sum), + accum_cck->re_acq_main_rssi_sum, + delta_cck->re_acq_main_rssi_sum, + max_cck->re_acq_main_rssi_sum); + + pos += + scnprintf(buf + pos, bufsz - pos, fmt_header, + "Statistics_Rx - GENERAL:"); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "bogus_cts:", + le32_to_cpu(general->bogus_cts), accum_general->bogus_cts, + delta_general->bogus_cts, max_general->bogus_cts); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "bogus_ack:", + le32_to_cpu(general->bogus_ack), accum_general->bogus_ack, + delta_general->bogus_ack, max_general->bogus_ack); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "non_bssid_frames:", + le32_to_cpu(general->non_bssid_frames), + accum_general->non_bssid_frames, + delta_general->non_bssid_frames, + max_general->non_bssid_frames); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "filtered_frames:", + le32_to_cpu(general->filtered_frames), + accum_general->filtered_frames, + delta_general->filtered_frames, + max_general->filtered_frames); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "non_channel_beacons:", + le32_to_cpu(general->non_channel_beacons), + accum_general->non_channel_beacons, + delta_general->non_channel_beacons, + max_general->non_channel_beacons); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "channel_beacons:", + le32_to_cpu(general->channel_beacons), + accum_general->channel_beacons, + delta_general->channel_beacons, + max_general->channel_beacons); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "num_missed_bcon:", + le32_to_cpu(general->num_missed_bcon), + accum_general->num_missed_bcon, + delta_general->num_missed_bcon, + max_general->num_missed_bcon); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "adc_rx_saturation_time:", + le32_to_cpu(general->adc_rx_saturation_time), + accum_general->adc_rx_saturation_time, + delta_general->adc_rx_saturation_time, + max_general->adc_rx_saturation_time); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "ina_detect_search_tm:", + le32_to_cpu(general->ina_detection_search_time), + accum_general->ina_detection_search_time, + delta_general->ina_detection_search_time, + max_general->ina_detection_search_time); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "beacon_silence_rssi_a:", + le32_to_cpu(general->beacon_silence_rssi_a), + accum_general->beacon_silence_rssi_a, + delta_general->beacon_silence_rssi_a, + max_general->beacon_silence_rssi_a); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "beacon_silence_rssi_b:", + le32_to_cpu(general->beacon_silence_rssi_b), + accum_general->beacon_silence_rssi_b, + delta_general->beacon_silence_rssi_b, + max_general->beacon_silence_rssi_b); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "beacon_silence_rssi_c:", + le32_to_cpu(general->beacon_silence_rssi_c), + accum_general->beacon_silence_rssi_c, + delta_general->beacon_silence_rssi_c, + max_general->beacon_silence_rssi_c); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "interference_data_flag:", + le32_to_cpu(general->interference_data_flag), + accum_general->interference_data_flag, + delta_general->interference_data_flag, + max_general->interference_data_flag); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "channel_load:", + le32_to_cpu(general->channel_load), + accum_general->channel_load, delta_general->channel_load, + max_general->channel_load); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "dsp_false_alarms:", + le32_to_cpu(general->dsp_false_alarms), + accum_general->dsp_false_alarms, + delta_general->dsp_false_alarms, + max_general->dsp_false_alarms); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "beacon_rssi_a:", + le32_to_cpu(general->beacon_rssi_a), + accum_general->beacon_rssi_a, + delta_general->beacon_rssi_a, max_general->beacon_rssi_a); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "beacon_rssi_b:", + le32_to_cpu(general->beacon_rssi_b), + accum_general->beacon_rssi_b, + delta_general->beacon_rssi_b, max_general->beacon_rssi_b); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "beacon_rssi_c:", + le32_to_cpu(general->beacon_rssi_c), + accum_general->beacon_rssi_c, + delta_general->beacon_rssi_c, max_general->beacon_rssi_c); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "beacon_energy_a:", + le32_to_cpu(general->beacon_energy_a), + accum_general->beacon_energy_a, + delta_general->beacon_energy_a, + max_general->beacon_energy_a); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "beacon_energy_b:", + le32_to_cpu(general->beacon_energy_b), + accum_general->beacon_energy_b, + delta_general->beacon_energy_b, + max_general->beacon_energy_b); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "beacon_energy_c:", + le32_to_cpu(general->beacon_energy_c), + accum_general->beacon_energy_c, + delta_general->beacon_energy_c, + max_general->beacon_energy_c); + + pos += + scnprintf(buf + pos, bufsz - pos, fmt_header, + "Statistics_Rx - OFDM_HT:"); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "plcp_err:", + le32_to_cpu(ht->plcp_err), accum_ht->plcp_err, + delta_ht->plcp_err, max_ht->plcp_err); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "overrun_err:", + le32_to_cpu(ht->overrun_err), accum_ht->overrun_err, + delta_ht->overrun_err, max_ht->overrun_err); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "early_overrun_err:", + le32_to_cpu(ht->early_overrun_err), + accum_ht->early_overrun_err, delta_ht->early_overrun_err, + max_ht->early_overrun_err); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "crc32_good:", + le32_to_cpu(ht->crc32_good), accum_ht->crc32_good, + delta_ht->crc32_good, max_ht->crc32_good); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "crc32_err:", + le32_to_cpu(ht->crc32_err), accum_ht->crc32_err, + delta_ht->crc32_err, max_ht->crc32_err); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "mh_format_err:", + le32_to_cpu(ht->mh_format_err), accum_ht->mh_format_err, + delta_ht->mh_format_err, max_ht->mh_format_err); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "agg_crc32_good:", + le32_to_cpu(ht->agg_crc32_good), accum_ht->agg_crc32_good, + delta_ht->agg_crc32_good, max_ht->agg_crc32_good); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "agg_mpdu_cnt:", + le32_to_cpu(ht->agg_mpdu_cnt), accum_ht->agg_mpdu_cnt, + delta_ht->agg_mpdu_cnt, max_ht->agg_mpdu_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "agg_cnt:", + le32_to_cpu(ht->agg_cnt), accum_ht->agg_cnt, + delta_ht->agg_cnt, max_ht->agg_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "unsupport_mcs:", + le32_to_cpu(ht->unsupport_mcs), accum_ht->unsupport_mcs, + delta_ht->unsupport_mcs, max_ht->unsupport_mcs); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +ssize_t +il4965_ucode_tx_stats_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct il_priv *il = file->private_data; + int pos = 0; + char *buf; + int bufsz = (sizeof(struct stats_tx) * 48) + 250; + ssize_t ret; + struct stats_tx *tx, *accum_tx, *delta_tx, *max_tx; + + if (!il_is_alive(il)) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) { + IL_ERR("Can not allocate Buffer\n"); + return -ENOMEM; + } + + /* the statistic information display here is based on + * the last stats notification from uCode + * might not reflect the current uCode activity + */ + tx = &il->_4965.stats.tx; + accum_tx = &il->_4965.accum_stats.tx; + delta_tx = &il->_4965.delta_stats.tx; + max_tx = &il->_4965.max_delta.tx; + + pos += il4965_stats_flag(il, buf, bufsz); + pos += scnprintf(buf + pos, bufsz - pos, fmt_header, "Statistics_Tx:"); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "preamble:", + le32_to_cpu(tx->preamble_cnt), accum_tx->preamble_cnt, + delta_tx->preamble_cnt, max_tx->preamble_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "rx_detected_cnt:", + le32_to_cpu(tx->rx_detected_cnt), + accum_tx->rx_detected_cnt, delta_tx->rx_detected_cnt, + max_tx->rx_detected_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "bt_prio_defer_cnt:", + le32_to_cpu(tx->bt_prio_defer_cnt), + accum_tx->bt_prio_defer_cnt, delta_tx->bt_prio_defer_cnt, + max_tx->bt_prio_defer_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "bt_prio_kill_cnt:", + le32_to_cpu(tx->bt_prio_kill_cnt), + accum_tx->bt_prio_kill_cnt, delta_tx->bt_prio_kill_cnt, + max_tx->bt_prio_kill_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "few_bytes_cnt:", + le32_to_cpu(tx->few_bytes_cnt), accum_tx->few_bytes_cnt, + delta_tx->few_bytes_cnt, max_tx->few_bytes_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "cts_timeout:", + le32_to_cpu(tx->cts_timeout), accum_tx->cts_timeout, + delta_tx->cts_timeout, max_tx->cts_timeout); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "ack_timeout:", + le32_to_cpu(tx->ack_timeout), accum_tx->ack_timeout, + delta_tx->ack_timeout, max_tx->ack_timeout); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "expected_ack_cnt:", + le32_to_cpu(tx->expected_ack_cnt), + accum_tx->expected_ack_cnt, delta_tx->expected_ack_cnt, + max_tx->expected_ack_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "actual_ack_cnt:", + le32_to_cpu(tx->actual_ack_cnt), accum_tx->actual_ack_cnt, + delta_tx->actual_ack_cnt, max_tx->actual_ack_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "dump_msdu_cnt:", + le32_to_cpu(tx->dump_msdu_cnt), accum_tx->dump_msdu_cnt, + delta_tx->dump_msdu_cnt, max_tx->dump_msdu_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "abort_nxt_frame_mismatch:", + le32_to_cpu(tx->burst_abort_next_frame_mismatch_cnt), + accum_tx->burst_abort_next_frame_mismatch_cnt, + delta_tx->burst_abort_next_frame_mismatch_cnt, + max_tx->burst_abort_next_frame_mismatch_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "abort_missing_nxt_frame:", + le32_to_cpu(tx->burst_abort_missing_next_frame_cnt), + accum_tx->burst_abort_missing_next_frame_cnt, + delta_tx->burst_abort_missing_next_frame_cnt, + max_tx->burst_abort_missing_next_frame_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "cts_timeout_collision:", + le32_to_cpu(tx->cts_timeout_collision), + accum_tx->cts_timeout_collision, + delta_tx->cts_timeout_collision, + max_tx->cts_timeout_collision); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "ack_ba_timeout_collision:", + le32_to_cpu(tx->ack_or_ba_timeout_collision), + accum_tx->ack_or_ba_timeout_collision, + delta_tx->ack_or_ba_timeout_collision, + max_tx->ack_or_ba_timeout_collision); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "agg ba_timeout:", + le32_to_cpu(tx->agg.ba_timeout), accum_tx->agg.ba_timeout, + delta_tx->agg.ba_timeout, max_tx->agg.ba_timeout); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "agg ba_resched_frames:", + le32_to_cpu(tx->agg.ba_reschedule_frames), + accum_tx->agg.ba_reschedule_frames, + delta_tx->agg.ba_reschedule_frames, + max_tx->agg.ba_reschedule_frames); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "agg scd_query_agg_frame:", + le32_to_cpu(tx->agg.scd_query_agg_frame_cnt), + accum_tx->agg.scd_query_agg_frame_cnt, + delta_tx->agg.scd_query_agg_frame_cnt, + max_tx->agg.scd_query_agg_frame_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "agg scd_query_no_agg:", + le32_to_cpu(tx->agg.scd_query_no_agg), + accum_tx->agg.scd_query_no_agg, + delta_tx->agg.scd_query_no_agg, + max_tx->agg.scd_query_no_agg); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "agg scd_query_agg:", + le32_to_cpu(tx->agg.scd_query_agg), + accum_tx->agg.scd_query_agg, delta_tx->agg.scd_query_agg, + max_tx->agg.scd_query_agg); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "agg scd_query_mismatch:", + le32_to_cpu(tx->agg.scd_query_mismatch), + accum_tx->agg.scd_query_mismatch, + delta_tx->agg.scd_query_mismatch, + max_tx->agg.scd_query_mismatch); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "agg frame_not_ready:", + le32_to_cpu(tx->agg.frame_not_ready), + accum_tx->agg.frame_not_ready, + delta_tx->agg.frame_not_ready, + max_tx->agg.frame_not_ready); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "agg underrun:", + le32_to_cpu(tx->agg.underrun), accum_tx->agg.underrun, + delta_tx->agg.underrun, max_tx->agg.underrun); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "agg bt_prio_kill:", + le32_to_cpu(tx->agg.bt_prio_kill), + accum_tx->agg.bt_prio_kill, delta_tx->agg.bt_prio_kill, + max_tx->agg.bt_prio_kill); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "agg rx_ba_rsp_cnt:", + le32_to_cpu(tx->agg.rx_ba_rsp_cnt), + accum_tx->agg.rx_ba_rsp_cnt, delta_tx->agg.rx_ba_rsp_cnt, + max_tx->agg.rx_ba_rsp_cnt); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +ssize_t +il4965_ucode_general_stats_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct il_priv *il = file->private_data; + int pos = 0; + char *buf; + int bufsz = sizeof(struct stats_general) * 10 + 300; + ssize_t ret; + struct stats_general_common *general, *accum_general; + struct stats_general_common *delta_general, *max_general; + struct stats_dbg *dbg, *accum_dbg, *delta_dbg, *max_dbg; + struct stats_div *div, *accum_div, *delta_div, *max_div; + + if (!il_is_alive(il)) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) { + IL_ERR("Can not allocate Buffer\n"); + return -ENOMEM; + } + + /* the statistic information display here is based on + * the last stats notification from uCode + * might not reflect the current uCode activity + */ + general = &il->_4965.stats.general.common; + dbg = &il->_4965.stats.general.common.dbg; + div = &il->_4965.stats.general.common.div; + accum_general = &il->_4965.accum_stats.general.common; + accum_dbg = &il->_4965.accum_stats.general.common.dbg; + accum_div = &il->_4965.accum_stats.general.common.div; + delta_general = &il->_4965.delta_stats.general.common; + max_general = &il->_4965.max_delta.general.common; + delta_dbg = &il->_4965.delta_stats.general.common.dbg; + max_dbg = &il->_4965.max_delta.general.common.dbg; + delta_div = &il->_4965.delta_stats.general.common.div; + max_div = &il->_4965.max_delta.general.common.div; + + pos += il4965_stats_flag(il, buf, bufsz); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_header, + "Statistics_General:"); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_value, "temperature:", + le32_to_cpu(general->temperature)); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_value, "ttl_timestamp:", + le32_to_cpu(general->ttl_timestamp)); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "burst_check:", + le32_to_cpu(dbg->burst_check), accum_dbg->burst_check, + delta_dbg->burst_check, max_dbg->burst_check); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "burst_count:", + le32_to_cpu(dbg->burst_count), accum_dbg->burst_count, + delta_dbg->burst_count, max_dbg->burst_count); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, + "wait_for_silence_timeout_count:", + le32_to_cpu(dbg->wait_for_silence_timeout_cnt), + accum_dbg->wait_for_silence_timeout_cnt, + delta_dbg->wait_for_silence_timeout_cnt, + max_dbg->wait_for_silence_timeout_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "sleep_time:", + le32_to_cpu(general->sleep_time), + accum_general->sleep_time, delta_general->sleep_time, + max_general->sleep_time); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "slots_out:", + le32_to_cpu(general->slots_out), accum_general->slots_out, + delta_general->slots_out, max_general->slots_out); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "slots_idle:", + le32_to_cpu(general->slots_idle), + accum_general->slots_idle, delta_general->slots_idle, + max_general->slots_idle); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "tx_on_a:", + le32_to_cpu(div->tx_on_a), accum_div->tx_on_a, + delta_div->tx_on_a, max_div->tx_on_a); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "tx_on_b:", + le32_to_cpu(div->tx_on_b), accum_div->tx_on_b, + delta_div->tx_on_b, max_div->tx_on_b); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "exec_time:", + le32_to_cpu(div->exec_time), accum_div->exec_time, + delta_div->exec_time, max_div->exec_time); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "probe_time:", + le32_to_cpu(div->probe_time), accum_div->probe_time, + delta_div->probe_time, max_div->probe_time); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "rx_enable_counter:", + le32_to_cpu(general->rx_enable_counter), + accum_general->rx_enable_counter, + delta_general->rx_enable_counter, + max_general->rx_enable_counter); + pos += + scnprintf(buf + pos, bufsz - pos, fmt_table, "num_of_sos_states:", + le32_to_cpu(general->num_of_sos_states), + accum_general->num_of_sos_states, + delta_general->num_of_sos_states, + max_general->num_of_sos_states); + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +const struct il_debugfs_ops il4965_debugfs_ops = { + .rx_stats_read = il4965_ucode_rx_stats_read, + .tx_stats_read = il4965_ucode_tx_stats_read, + .general_stats_read = il4965_ucode_general_stats_read, +}; diff --git a/drivers/net/wireless/iwlegacy/4965-mac.c b/drivers/net/wireless/iwlegacy/4965-mac.c new file mode 100644 index 0000000..2f7d930 --- /dev/null +++ b/drivers/net/wireless/iwlegacy/4965-mac.c @@ -0,0 +1,6863 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2011 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define DRV_NAME "iwl4965" + +#include "common.h" +#include "4965.h" + +/****************************************************************************** + * + * module boiler plate + * + ******************************************************************************/ + +/* + * module name, copyright, version, etc. + */ +#define DRV_DESCRIPTION "Intel(R) Wireless WiFi 4965 driver for Linux" + +#ifdef CPTCFG_IWLEGACY_DEBUG +#define VD "d" +#else +#define VD +#endif + +#define DRV_VERSION IWLWIFI_VERSION VD + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR(DRV_COPYRIGHT " " DRV_AUTHOR); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("iwl4965"); + +void +il4965_check_abort_status(struct il_priv *il, u8 frame_count, u32 status) +{ + if (frame_count == 1 && status == TX_STATUS_FAIL_RFKILL_FLUSH) { + IL_ERR("Tx flush command to flush out all frames\n"); + if (!test_bit(S_EXIT_PENDING, &il->status)) + queue_work(il->workqueue, &il->tx_flush); + } +} + +/* + * EEPROM + */ +struct il_mod_params il4965_mod_params = { + .amsdu_size_8K = 1, + .restart_fw = 1, + /* the rest are 0 by default */ +}; + +void +il4965_rx_queue_reset(struct il_priv *il, struct il_rx_queue *rxq) +{ + unsigned long flags; + int i; + spin_lock_irqsave(&rxq->lock, flags); + INIT_LIST_HEAD(&rxq->rx_free); + INIT_LIST_HEAD(&rxq->rx_used); + /* Fill the rx_used queue with _all_ of the Rx buffers */ + for (i = 0; i < RX_FREE_BUFFERS + RX_QUEUE_SIZE; i++) { + /* In the reset function, these buffers may have been allocated + * to an SKB, so we need to unmap and free potential storage */ + if (rxq->pool[i].page != NULL) { + pci_unmap_page(il->pci_dev, rxq->pool[i].page_dma, + PAGE_SIZE << il->hw_params.rx_page_order, + PCI_DMA_FROMDEVICE); + __il_free_pages(il, rxq->pool[i].page); + rxq->pool[i].page = NULL; + } + list_add_tail(&rxq->pool[i].list, &rxq->rx_used); + } + + for (i = 0; i < RX_QUEUE_SIZE; i++) + rxq->queue[i] = NULL; + + /* Set us so that we have processed and used all buffers, but have + * not restocked the Rx queue with fresh buffers */ + rxq->read = rxq->write = 0; + rxq->write_actual = 0; + rxq->free_count = 0; + spin_unlock_irqrestore(&rxq->lock, flags); +} + +int +il4965_rx_init(struct il_priv *il, struct il_rx_queue *rxq) +{ + u32 rb_size; + const u32 rfdnlog = RX_QUEUE_SIZE_LOG; /* 256 RBDs */ + u32 rb_timeout = 0; + + if (il->cfg->mod_params->amsdu_size_8K) + rb_size = FH49_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_8K; + else + rb_size = FH49_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K; + + /* Stop Rx DMA */ + il_wr(il, FH49_MEM_RCSR_CHNL0_CONFIG_REG, 0); + + /* Reset driver's Rx queue write idx */ + il_wr(il, FH49_RSCSR_CHNL0_RBDCB_WPTR_REG, 0); + + /* Tell device where to find RBD circular buffer in DRAM */ + il_wr(il, FH49_RSCSR_CHNL0_RBDCB_BASE_REG, (u32) (rxq->bd_dma >> 8)); + + /* Tell device where in DRAM to update its Rx status */ + il_wr(il, FH49_RSCSR_CHNL0_STTS_WPTR_REG, rxq->rb_stts_dma >> 4); + + /* Enable Rx DMA + * Direct rx interrupts to hosts + * Rx buffer size 4 or 8k + * RB timeout 0x10 + * 256 RBDs + */ + il_wr(il, FH49_MEM_RCSR_CHNL0_CONFIG_REG, + FH49_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL | + FH49_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL | + FH49_RCSR_CHNL0_RX_CONFIG_SINGLE_FRAME_MSK | + rb_size | + (rb_timeout << FH49_RCSR_RX_CONFIG_REG_IRQ_RBTH_POS) | + (rfdnlog << FH49_RCSR_RX_CONFIG_RBDCB_SIZE_POS)); + + /* Set interrupt coalescing timer to default (2048 usecs) */ + il_write8(il, CSR_INT_COALESCING, IL_HOST_INT_TIMEOUT_DEF); + + return 0; +} + +static void +il4965_set_pwr_vmain(struct il_priv *il) +{ +/* + * (for documentation purposes) + * to set power to V_AUX, do: + + if (pci_pme_capable(il->pci_dev, PCI_D3cold)) + il_set_bits_mask_prph(il, APMG_PS_CTRL_REG, + APMG_PS_CTRL_VAL_PWR_SRC_VAUX, + ~APMG_PS_CTRL_MSK_PWR_SRC); + */ + + il_set_bits_mask_prph(il, APMG_PS_CTRL_REG, + APMG_PS_CTRL_VAL_PWR_SRC_VMAIN, + ~APMG_PS_CTRL_MSK_PWR_SRC); +} + +int +il4965_hw_nic_init(struct il_priv *il) +{ + unsigned long flags; + struct il_rx_queue *rxq = &il->rxq; + int ret; + + spin_lock_irqsave(&il->lock, flags); + il_apm_init(il); + /* Set interrupt coalescing calibration timer to default (512 usecs) */ + il_write8(il, CSR_INT_COALESCING, IL_HOST_INT_CALIB_TIMEOUT_DEF); + spin_unlock_irqrestore(&il->lock, flags); + + il4965_set_pwr_vmain(il); + il4965_nic_config(il); + + /* Allocate the RX queue, or reset if it is already allocated */ + if (!rxq->bd) { + ret = il_rx_queue_alloc(il); + if (ret) { + IL_ERR("Unable to initialize Rx queue\n"); + return -ENOMEM; + } + } else + il4965_rx_queue_reset(il, rxq); + + il4965_rx_replenish(il); + + il4965_rx_init(il, rxq); + + spin_lock_irqsave(&il->lock, flags); + + rxq->need_update = 1; + il_rx_queue_update_write_ptr(il, rxq); + + spin_unlock_irqrestore(&il->lock, flags); + + /* Allocate or reset and init all Tx and Command queues */ + if (!il->txq) { + ret = il4965_txq_ctx_alloc(il); + if (ret) + return ret; + } else + il4965_txq_ctx_reset(il); + + set_bit(S_INIT, &il->status); + + return 0; +} + +/** + * il4965_dma_addr2rbd_ptr - convert a DMA address to a uCode read buffer ptr + */ +static inline __le32 +il4965_dma_addr2rbd_ptr(struct il_priv *il, dma_addr_t dma_addr) +{ + return cpu_to_le32((u32) (dma_addr >> 8)); +} + +/** + * il4965_rx_queue_restock - refill RX queue from pre-allocated pool + * + * If there are slots in the RX queue that need to be restocked, + * and we have free pre-allocated buffers, fill the ranks as much + * as we can, pulling from rx_free. + * + * This moves the 'write' idx forward to catch up with 'processed', and + * also updates the memory address in the firmware to reference the new + * target buffer. + */ +void +il4965_rx_queue_restock(struct il_priv *il) +{ + struct il_rx_queue *rxq = &il->rxq; + struct list_head *element; + struct il_rx_buf *rxb; + unsigned long flags; + + spin_lock_irqsave(&rxq->lock, flags); + while (il_rx_queue_space(rxq) > 0 && rxq->free_count) { + /* The overwritten rxb must be a used one */ + rxb = rxq->queue[rxq->write]; + BUG_ON(rxb && rxb->page); + + /* Get next free Rx buffer, remove from free list */ + element = rxq->rx_free.next; + rxb = list_entry(element, struct il_rx_buf, list); + list_del(element); + + /* Point to Rx buffer via next RBD in circular buffer */ + rxq->bd[rxq->write] = + il4965_dma_addr2rbd_ptr(il, rxb->page_dma); + rxq->queue[rxq->write] = rxb; + rxq->write = (rxq->write + 1) & RX_QUEUE_MASK; + rxq->free_count--; + } + spin_unlock_irqrestore(&rxq->lock, flags); + /* If the pre-allocated buffer pool is dropping low, schedule to + * refill it */ + if (rxq->free_count <= RX_LOW_WATERMARK) + queue_work(il->workqueue, &il->rx_replenish); + + /* If we've added more space for the firmware to place data, tell it. + * Increment device's write pointer in multiples of 8. */ + if (rxq->write_actual != (rxq->write & ~0x7)) { + spin_lock_irqsave(&rxq->lock, flags); + rxq->need_update = 1; + spin_unlock_irqrestore(&rxq->lock, flags); + il_rx_queue_update_write_ptr(il, rxq); + } +} + +/** + * il4965_rx_replenish - Move all used packet from rx_used to rx_free + * + * When moving to rx_free an SKB is allocated for the slot. + * + * Also restock the Rx queue via il_rx_queue_restock. + * This is called as a scheduled work item (except for during initialization) + */ +static void +il4965_rx_allocate(struct il_priv *il, gfp_t priority) +{ + struct il_rx_queue *rxq = &il->rxq; + struct list_head *element; + struct il_rx_buf *rxb; + struct page *page; + dma_addr_t page_dma; + unsigned long flags; + gfp_t gfp_mask = priority; + + while (1) { + spin_lock_irqsave(&rxq->lock, flags); + if (list_empty(&rxq->rx_used)) { + spin_unlock_irqrestore(&rxq->lock, flags); + return; + } + spin_unlock_irqrestore(&rxq->lock, flags); + + if (rxq->free_count > RX_LOW_WATERMARK) + gfp_mask |= __GFP_NOWARN; + + if (il->hw_params.rx_page_order > 0) + gfp_mask |= __GFP_COMP; + + /* Alloc a new receive buffer */ + page = alloc_pages(gfp_mask, il->hw_params.rx_page_order); + if (!page) { + if (net_ratelimit()) + D_INFO("alloc_pages failed, " "order: %d\n", + il->hw_params.rx_page_order); + + if (rxq->free_count <= RX_LOW_WATERMARK && + net_ratelimit()) + IL_ERR("Failed to alloc_pages with %s. " + "Only %u free buffers remaining.\n", + priority == + GFP_ATOMIC ? "GFP_ATOMIC" : "GFP_KERNEL", + rxq->free_count); + /* We don't reschedule replenish work here -- we will + * call the restock method and if it still needs + * more buffers it will schedule replenish */ + return; + } + + /* Get physical address of the RB */ + page_dma = + pci_map_page(il->pci_dev, page, 0, + PAGE_SIZE << il->hw_params.rx_page_order, + PCI_DMA_FROMDEVICE); + if (unlikely(pci_dma_mapping_error(il->pci_dev, page_dma))) { + __free_pages(page, il->hw_params.rx_page_order); + break; + } + + spin_lock_irqsave(&rxq->lock, flags); + + if (list_empty(&rxq->rx_used)) { + spin_unlock_irqrestore(&rxq->lock, flags); + pci_unmap_page(il->pci_dev, page_dma, + PAGE_SIZE << il->hw_params.rx_page_order, + PCI_DMA_FROMDEVICE); + __free_pages(page, il->hw_params.rx_page_order); + return; + } + + element = rxq->rx_used.next; + rxb = list_entry(element, struct il_rx_buf, list); + list_del(element); + + BUG_ON(rxb->page); + + rxb->page = page; + rxb->page_dma = page_dma; + list_add_tail(&rxb->list, &rxq->rx_free); + rxq->free_count++; + il->alloc_rxb_page++; + + spin_unlock_irqrestore(&rxq->lock, flags); + } +} + +void +il4965_rx_replenish(struct il_priv *il) +{ + unsigned long flags; + + il4965_rx_allocate(il, GFP_KERNEL); + + spin_lock_irqsave(&il->lock, flags); + il4965_rx_queue_restock(il); + spin_unlock_irqrestore(&il->lock, flags); +} + +void +il4965_rx_replenish_now(struct il_priv *il) +{ + il4965_rx_allocate(il, GFP_ATOMIC); + + il4965_rx_queue_restock(il); +} + +/* Assumes that the skb field of the buffers in 'pool' is kept accurate. + * If an SKB has been detached, the POOL needs to have its SKB set to NULL + * This free routine walks the list of POOL entries and if SKB is set to + * non NULL it is unmapped and freed + */ +void +il4965_rx_queue_free(struct il_priv *il, struct il_rx_queue *rxq) +{ + int i; + for (i = 0; i < RX_QUEUE_SIZE + RX_FREE_BUFFERS; i++) { + if (rxq->pool[i].page != NULL) { + pci_unmap_page(il->pci_dev, rxq->pool[i].page_dma, + PAGE_SIZE << il->hw_params.rx_page_order, + PCI_DMA_FROMDEVICE); + __il_free_pages(il, rxq->pool[i].page); + rxq->pool[i].page = NULL; + } + } + + dma_free_coherent(&il->pci_dev->dev, 4 * RX_QUEUE_SIZE, rxq->bd, + rxq->bd_dma); + dma_free_coherent(&il->pci_dev->dev, sizeof(struct il_rb_status), + rxq->rb_stts, rxq->rb_stts_dma); + rxq->bd = NULL; + rxq->rb_stts = NULL; +} + +int +il4965_rxq_stop(struct il_priv *il) +{ + int ret; + + _il_wr(il, FH49_MEM_RCSR_CHNL0_CONFIG_REG, 0); + ret = _il_poll_bit(il, FH49_MEM_RSSR_RX_STATUS_REG, + FH49_RSSR_CHNL0_RX_STATUS_CHNL_IDLE, + FH49_RSSR_CHNL0_RX_STATUS_CHNL_IDLE, + 1000); + if (ret < 0) + IL_ERR("Can't stop Rx DMA.\n"); + + return 0; +} + +int +il4965_hwrate_to_mac80211_idx(u32 rate_n_flags, enum ieee80211_band band) +{ + int idx = 0; + int band_offset = 0; + + /* HT rate format: mac80211 wants an MCS number, which is just LSB */ + if (rate_n_flags & RATE_MCS_HT_MSK) { + idx = (rate_n_flags & 0xff); + return idx; + /* Legacy rate format, search for match in table */ + } else { + if (band == IEEE80211_BAND_5GHZ) + band_offset = IL_FIRST_OFDM_RATE; + for (idx = band_offset; idx < RATE_COUNT_LEGACY; idx++) + if (il_rates[idx].plcp == (rate_n_flags & 0xFF)) + return idx - band_offset; + } + + return -1; +} + +static int +il4965_calc_rssi(struct il_priv *il, struct il_rx_phy_res *rx_resp) +{ + /* data from PHY/DSP regarding signal strength, etc., + * contents are always there, not configurable by host. */ + struct il4965_rx_non_cfg_phy *ncphy = + (struct il4965_rx_non_cfg_phy *)rx_resp->non_cfg_phy_buf; + u32 agc = + (le16_to_cpu(ncphy->agc_info) & IL49_AGC_DB_MASK) >> + IL49_AGC_DB_POS; + + u32 valid_antennae = + (le16_to_cpu(rx_resp->phy_flags) & IL49_RX_PHY_FLAGS_ANTENNAE_MASK) + >> IL49_RX_PHY_FLAGS_ANTENNAE_OFFSET; + u8 max_rssi = 0; + u32 i; + + /* Find max rssi among 3 possible receivers. + * These values are measured by the digital signal processor (DSP). + * They should stay fairly constant even as the signal strength varies, + * if the radio's automatic gain control (AGC) is working right. + * AGC value (see below) will provide the "interesting" info. */ + for (i = 0; i < 3; i++) + if (valid_antennae & (1 << i)) + max_rssi = max(ncphy->rssi_info[i << 1], max_rssi); + + D_STATS("Rssi In A %d B %d C %d Max %d AGC dB %d\n", + ncphy->rssi_info[0], ncphy->rssi_info[2], ncphy->rssi_info[4], + max_rssi, agc); + + /* dBm = max_rssi dB - agc dB - constant. + * Higher AGC (higher radio gain) means lower signal. */ + return max_rssi - agc - IL4965_RSSI_OFFSET; +} + +static u32 +il4965_translate_rx_status(struct il_priv *il, u32 decrypt_in) +{ + u32 decrypt_out = 0; + + if ((decrypt_in & RX_RES_STATUS_STATION_FOUND) == + RX_RES_STATUS_STATION_FOUND) + decrypt_out |= + (RX_RES_STATUS_STATION_FOUND | + RX_RES_STATUS_NO_STATION_INFO_MISMATCH); + + decrypt_out |= (decrypt_in & RX_RES_STATUS_SEC_TYPE_MSK); + + /* packet was not encrypted */ + if ((decrypt_in & RX_RES_STATUS_SEC_TYPE_MSK) == + RX_RES_STATUS_SEC_TYPE_NONE) + return decrypt_out; + + /* packet was encrypted with unknown alg */ + if ((decrypt_in & RX_RES_STATUS_SEC_TYPE_MSK) == + RX_RES_STATUS_SEC_TYPE_ERR) + return decrypt_out; + + /* decryption was not done in HW */ + if ((decrypt_in & RX_MPDU_RES_STATUS_DEC_DONE_MSK) != + RX_MPDU_RES_STATUS_DEC_DONE_MSK) + return decrypt_out; + + switch (decrypt_in & RX_RES_STATUS_SEC_TYPE_MSK) { + + case RX_RES_STATUS_SEC_TYPE_CCMP: + /* alg is CCM: check MIC only */ + if (!(decrypt_in & RX_MPDU_RES_STATUS_MIC_OK)) + /* Bad MIC */ + decrypt_out |= RX_RES_STATUS_BAD_ICV_MIC; + else + decrypt_out |= RX_RES_STATUS_DECRYPT_OK; + + break; + + case RX_RES_STATUS_SEC_TYPE_TKIP: + if (!(decrypt_in & RX_MPDU_RES_STATUS_TTAK_OK)) { + /* Bad TTAK */ + decrypt_out |= RX_RES_STATUS_BAD_KEY_TTAK; + break; + } + /* fall through if TTAK OK */ + default: + if (!(decrypt_in & RX_MPDU_RES_STATUS_ICV_OK)) + decrypt_out |= RX_RES_STATUS_BAD_ICV_MIC; + else + decrypt_out |= RX_RES_STATUS_DECRYPT_OK; + break; + } + + D_RX("decrypt_in:0x%x decrypt_out = 0x%x\n", decrypt_in, decrypt_out); + + return decrypt_out; +} + +static void +il4965_pass_packet_to_mac80211(struct il_priv *il, struct ieee80211_hdr *hdr, + u16 len, u32 ampdu_status, struct il_rx_buf *rxb, + struct ieee80211_rx_status *stats) +{ + struct sk_buff *skb; + __le16 fc = hdr->frame_control; + + /* We only process data packets if the interface is open */ + if (unlikely(!il->is_open)) { + D_DROP("Dropping packet while interface is not open.\n"); + return; + } + + /* In case of HW accelerated crypto and bad decryption, drop */ + if (!il->cfg->mod_params->sw_crypto && + il_set_decrypted_flag(il, hdr, ampdu_status, stats)) + return; + + skb = dev_alloc_skb(128); + if (!skb) { + IL_ERR("dev_alloc_skb failed\n"); + return; + } + + skb_add_rx_frag(skb, 0, rxb->page, (void *)hdr - rxb_addr(rxb), len, + len); + + il_update_stats(il, false, fc, len); + memcpy(IEEE80211_SKB_RXCB(skb), stats, sizeof(*stats)); + + ieee80211_rx(il->hw, skb); + il->alloc_rxb_page--; + rxb->page = NULL; +} + +/* Called for N_RX (legacy ABG frames), or + * N_RX_MPDU (HT high-throughput N frames). */ +static void +il4965_hdl_rx(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct ieee80211_hdr *header; + struct ieee80211_rx_status rx_status = {}; + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il_rx_phy_res *phy_res; + __le32 rx_pkt_status; + struct il_rx_mpdu_res_start *amsdu; + u32 len; + u32 ampdu_status; + u32 rate_n_flags; + + /** + * N_RX and N_RX_MPDU are handled differently. + * N_RX: physical layer info is in this buffer + * N_RX_MPDU: physical layer info was sent in separate + * command and cached in il->last_phy_res + * + * Here we set up local variables depending on which command is + * received. + */ + if (pkt->hdr.cmd == N_RX) { + phy_res = (struct il_rx_phy_res *)pkt->u.raw; + header = + (struct ieee80211_hdr *)(pkt->u.raw + sizeof(*phy_res) + + phy_res->cfg_phy_cnt); + + len = le16_to_cpu(phy_res->byte_count); + rx_pkt_status = + *(__le32 *) (pkt->u.raw + sizeof(*phy_res) + + phy_res->cfg_phy_cnt + len); + ampdu_status = le32_to_cpu(rx_pkt_status); + } else { + if (!il->_4965.last_phy_res_valid) { + IL_ERR("MPDU frame without cached PHY data\n"); + return; + } + phy_res = &il->_4965.last_phy_res; + amsdu = (struct il_rx_mpdu_res_start *)pkt->u.raw; + header = (struct ieee80211_hdr *)(pkt->u.raw + sizeof(*amsdu)); + len = le16_to_cpu(amsdu->byte_count); + rx_pkt_status = *(__le32 *) (pkt->u.raw + sizeof(*amsdu) + len); + ampdu_status = + il4965_translate_rx_status(il, le32_to_cpu(rx_pkt_status)); + } + + if ((unlikely(phy_res->cfg_phy_cnt > 20))) { + D_DROP("dsp size out of range [0,20]: %d/n", + phy_res->cfg_phy_cnt); + return; + } + + if (!(rx_pkt_status & RX_RES_STATUS_NO_CRC32_ERROR) || + !(rx_pkt_status & RX_RES_STATUS_NO_RXE_OVERFLOW)) { + D_RX("Bad CRC or FIFO: 0x%08X.\n", le32_to_cpu(rx_pkt_status)); + return; + } + + /* This will be used in several places later */ + rate_n_flags = le32_to_cpu(phy_res->rate_n_flags); + + /* rx_status carries information about the packet to mac80211 */ + rx_status.mactime = le64_to_cpu(phy_res->timestamp); + rx_status.band = + (phy_res-> + phy_flags & RX_RES_PHY_FLAGS_BAND_24_MSK) ? IEEE80211_BAND_2GHZ : + IEEE80211_BAND_5GHZ; + rx_status.freq = + ieee80211_channel_to_frequency(le16_to_cpu(phy_res->channel), + rx_status.band); + rx_status.rate_idx = + il4965_hwrate_to_mac80211_idx(rate_n_flags, rx_status.band); + rx_status.flag = 0; + + /* TSF isn't reliable. In order to allow smooth user experience, + * this W/A doesn't propagate it to the mac80211 */ + /*rx_status.flag |= RX_FLAG_MACTIME_START; */ + + il->ucode_beacon_time = le32_to_cpu(phy_res->beacon_time_stamp); + + /* Find max signal strength (dBm) among 3 antenna/receiver chains */ + rx_status.signal = il4965_calc_rssi(il, phy_res); + + D_STATS("Rssi %d, TSF %llu\n", rx_status.signal, + (unsigned long long)rx_status.mactime); + + /* + * "antenna number" + * + * It seems that the antenna field in the phy flags value + * is actually a bit field. This is undefined by radiotap, + * it wants an actual antenna number but I always get "7" + * for most legacy frames I receive indicating that the + * same frame was received on all three RX chains. + * + * I think this field should be removed in favor of a + * new 802.11n radiotap field "RX chains" that is defined + * as a bitmask. + */ + rx_status.antenna = + (le16_to_cpu(phy_res->phy_flags) & RX_RES_PHY_FLAGS_ANTENNA_MSK) >> + RX_RES_PHY_FLAGS_ANTENNA_POS; + + /* set the preamble flag if appropriate */ + if (phy_res->phy_flags & RX_RES_PHY_FLAGS_SHORT_PREAMBLE_MSK) + rx_status.flag |= RX_FLAG_SHORTPRE; + + /* Set up the HT phy flags */ + if (rate_n_flags & RATE_MCS_HT_MSK) + rx_status.flag |= RX_FLAG_HT; + if (rate_n_flags & RATE_MCS_HT40_MSK) + rx_status.flag |= RX_FLAG_40MHZ; + if (rate_n_flags & RATE_MCS_SGI_MSK) + rx_status.flag |= RX_FLAG_SHORT_GI; + + if (phy_res->phy_flags & RX_RES_PHY_FLAGS_AGG_MSK) { + /* We know which subframes of an A-MPDU belong + * together since we get a single PHY response + * from the firmware for all of them. + */ + + rx_status.flag |= RX_FLAG_AMPDU_DETAILS; + rx_status.ampdu_reference = il->_4965.ampdu_ref; + } + + il4965_pass_packet_to_mac80211(il, header, len, ampdu_status, rxb, + &rx_status); +} + +/* Cache phy data (Rx signal strength, etc) for HT frame (N_RX_PHY). + * This will be used later in il_hdl_rx() for N_RX_MPDU. */ +static void +il4965_hdl_rx_phy(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + il->_4965.last_phy_res_valid = true; + il->_4965.ampdu_ref++; + memcpy(&il->_4965.last_phy_res, pkt->u.raw, + sizeof(struct il_rx_phy_res)); +} + +static int +il4965_get_channels_for_scan(struct il_priv *il, struct ieee80211_vif *vif, + enum ieee80211_band band, u8 is_active, + u8 n_probes, struct il_scan_channel *scan_ch) +{ + struct ieee80211_channel *chan; + const struct ieee80211_supported_band *sband; + const struct il_channel_info *ch_info; + u16 passive_dwell = 0; + u16 active_dwell = 0; + int added, i; + u16 channel; + + sband = il_get_hw_mode(il, band); + if (!sband) + return 0; + + active_dwell = il_get_active_dwell_time(il, band, n_probes); + passive_dwell = il_get_passive_dwell_time(il, band, vif); + + if (passive_dwell <= active_dwell) + passive_dwell = active_dwell + 1; + + for (i = 0, added = 0; i < il->scan_request->n_channels; i++) { + chan = il->scan_request->channels[i]; + + if (chan->band != band) + continue; + + channel = chan->hw_value; + scan_ch->channel = cpu_to_le16(channel); + + ch_info = il_get_channel_info(il, band, channel); + if (!il_is_channel_valid(ch_info)) { + D_SCAN("Channel %d is INVALID for this band.\n", + channel); + continue; + } + + if (!is_active || il_is_channel_passive(ch_info) || + (chan->flags & IEEE80211_CHAN_PASSIVE_SCAN)) + scan_ch->type = SCAN_CHANNEL_TYPE_PASSIVE; + else + scan_ch->type = SCAN_CHANNEL_TYPE_ACTIVE; + + if (n_probes) + scan_ch->type |= IL_SCAN_PROBE_MASK(n_probes); + + scan_ch->active_dwell = cpu_to_le16(active_dwell); + scan_ch->passive_dwell = cpu_to_le16(passive_dwell); + + /* Set txpower levels to defaults */ + scan_ch->dsp_atten = 110; + + /* NOTE: if we were doing 6Mb OFDM for scans we'd use + * power level: + * scan_ch->tx_gain = ((1 << 5) | (2 << 3)) | 3; + */ + if (band == IEEE80211_BAND_5GHZ) + scan_ch->tx_gain = ((1 << 5) | (3 << 3)) | 3; + else + scan_ch->tx_gain = ((1 << 5) | (5 << 3)); + + D_SCAN("Scanning ch=%d prob=0x%X [%s %d]\n", channel, + le32_to_cpu(scan_ch->type), + (scan_ch-> + type & SCAN_CHANNEL_TYPE_ACTIVE) ? "ACTIVE" : "PASSIVE", + (scan_ch-> + type & SCAN_CHANNEL_TYPE_ACTIVE) ? active_dwell : + passive_dwell); + + scan_ch++; + added++; + } + + D_SCAN("total channels to scan %d\n", added); + return added; +} + +static void +il4965_toggle_tx_ant(struct il_priv *il, u8 *ant, u8 valid) +{ + int i; + u8 ind = *ant; + + for (i = 0; i < RATE_ANT_NUM - 1; i++) { + ind = (ind + 1) < RATE_ANT_NUM ? ind + 1 : 0; + if (valid & BIT(ind)) { + *ant = ind; + return; + } + } +} + +int +il4965_request_scan(struct il_priv *il, struct ieee80211_vif *vif) +{ + struct il_host_cmd cmd = { + .id = C_SCAN, + .len = sizeof(struct il_scan_cmd), + .flags = CMD_SIZE_HUGE, + }; + struct il_scan_cmd *scan; + u32 rate_flags = 0; + u16 cmd_len; + u16 rx_chain = 0; + enum ieee80211_band band; + u8 n_probes = 0; + u8 rx_ant = il->hw_params.valid_rx_ant; + u8 rate; + bool is_active = false; + int chan_mod; + u8 active_chains; + u8 scan_tx_antennas = il->hw_params.valid_tx_ant; + int ret; + + lockdep_assert_held(&il->mutex); + + if (!il->scan_cmd) { + il->scan_cmd = + kmalloc(sizeof(struct il_scan_cmd) + IL_MAX_SCAN_SIZE, + GFP_KERNEL); + if (!il->scan_cmd) { + D_SCAN("fail to allocate memory for scan\n"); + return -ENOMEM; + } + } + scan = il->scan_cmd; + memset(scan, 0, sizeof(struct il_scan_cmd) + IL_MAX_SCAN_SIZE); + + scan->quiet_plcp_th = IL_PLCP_QUIET_THRESH; + scan->quiet_time = IL_ACTIVE_QUIET_TIME; + + if (il_is_any_associated(il)) { + u16 interval; + u32 extra; + u32 suspend_time = 100; + u32 scan_suspend_time = 100; + + D_INFO("Scanning while associated...\n"); + interval = vif->bss_conf.beacon_int; + + scan->suspend_time = 0; + scan->max_out_time = cpu_to_le32(200 * 1024); + if (!interval) + interval = suspend_time; + + extra = (suspend_time / interval) << 22; + scan_suspend_time = + (extra | ((suspend_time % interval) * 1024)); + scan->suspend_time = cpu_to_le32(scan_suspend_time); + D_SCAN("suspend_time 0x%X beacon interval %d\n", + scan_suspend_time, interval); + } + + if (il->scan_request->n_ssids) { + int i, p = 0; + D_SCAN("Kicking off active scan\n"); + for (i = 0; i < il->scan_request->n_ssids; i++) { + /* always does wildcard anyway */ + if (!il->scan_request->ssids[i].ssid_len) + continue; + scan->direct_scan[p].id = WLAN_EID_SSID; + scan->direct_scan[p].len = + il->scan_request->ssids[i].ssid_len; + memcpy(scan->direct_scan[p].ssid, + il->scan_request->ssids[i].ssid, + il->scan_request->ssids[i].ssid_len); + n_probes++; + p++; + } + is_active = true; + } else + D_SCAN("Start passive scan.\n"); + + scan->tx_cmd.tx_flags = TX_CMD_FLG_SEQ_CTL_MSK; + scan->tx_cmd.sta_id = il->hw_params.bcast_id; + scan->tx_cmd.stop_time.life_time = TX_CMD_LIFE_TIME_INFINITE; + + switch (il->scan_band) { + case IEEE80211_BAND_2GHZ: + scan->flags = RXON_FLG_BAND_24G_MSK | RXON_FLG_AUTO_DETECT_MSK; + chan_mod = + le32_to_cpu(il->active.flags & RXON_FLG_CHANNEL_MODE_MSK) >> + RXON_FLG_CHANNEL_MODE_POS; + if (chan_mod == CHANNEL_MODE_PURE_40) { + rate = RATE_6M_PLCP; + } else { + rate = RATE_1M_PLCP; + rate_flags = RATE_MCS_CCK_MSK; + } + break; + case IEEE80211_BAND_5GHZ: + rate = RATE_6M_PLCP; + break; + default: + IL_WARN("Invalid scan band\n"); + return -EIO; + } + + /* + * If active scanning is requested but a certain channel is + * marked passive, we can do active scanning if we detect + * transmissions. + * + * There is an issue with some firmware versions that triggers + * a sysassert on a "good CRC threshold" of zero (== disabled), + * on a radar channel even though this means that we should NOT + * send probes. + * + * The "good CRC threshold" is the number of frames that we + * need to receive during our dwell time on a channel before + * sending out probes -- setting this to a huge value will + * mean we never reach it, but at the same time work around + * the aforementioned issue. Thus use IL_GOOD_CRC_TH_NEVER + * here instead of IL_GOOD_CRC_TH_DISABLED. + */ + scan->good_CRC_th = + is_active ? IL_GOOD_CRC_TH_DEFAULT : IL_GOOD_CRC_TH_NEVER; + + band = il->scan_band; + + if (il->cfg->scan_rx_antennas[band]) + rx_ant = il->cfg->scan_rx_antennas[band]; + + il4965_toggle_tx_ant(il, &il->scan_tx_ant[band], scan_tx_antennas); + rate_flags |= BIT(il->scan_tx_ant[band]) << RATE_MCS_ANT_POS; + scan->tx_cmd.rate_n_flags = cpu_to_le32(rate | rate_flags); + + /* In power save mode use one chain, otherwise use all chains */ + if (test_bit(S_POWER_PMI, &il->status)) { + /* rx_ant has been set to all valid chains previously */ + active_chains = + rx_ant & ((u8) (il->chain_noise_data.active_chains)); + if (!active_chains) + active_chains = rx_ant; + + D_SCAN("chain_noise_data.active_chains: %u\n", + il->chain_noise_data.active_chains); + + rx_ant = il4965_first_antenna(active_chains); + } + + /* MIMO is not used here, but value is required */ + rx_chain |= il->hw_params.valid_rx_ant << RXON_RX_CHAIN_VALID_POS; + rx_chain |= rx_ant << RXON_RX_CHAIN_FORCE_MIMO_SEL_POS; + rx_chain |= rx_ant << RXON_RX_CHAIN_FORCE_SEL_POS; + rx_chain |= 0x1 << RXON_RX_CHAIN_DRIVER_FORCE_POS; + scan->rx_chain = cpu_to_le16(rx_chain); + + cmd_len = + il_fill_probe_req(il, (struct ieee80211_mgmt *)scan->data, + vif->addr, il->scan_request->ie, + il->scan_request->ie_len, + IL_MAX_SCAN_SIZE - sizeof(*scan)); + scan->tx_cmd.len = cpu_to_le16(cmd_len); + + scan->filter_flags |= + (RXON_FILTER_ACCEPT_GRP_MSK | RXON_FILTER_BCON_AWARE_MSK); + + scan->channel_count = + il4965_get_channels_for_scan(il, vif, band, is_active, n_probes, + (void *)&scan->data[cmd_len]); + if (scan->channel_count == 0) { + D_SCAN("channel count %d\n", scan->channel_count); + return -EIO; + } + + cmd.len += + le16_to_cpu(scan->tx_cmd.len) + + scan->channel_count * sizeof(struct il_scan_channel); + cmd.data = scan; + scan->len = cpu_to_le16(cmd.len); + + set_bit(S_SCAN_HW, &il->status); + + ret = il_send_cmd_sync(il, &cmd); + if (ret) + clear_bit(S_SCAN_HW, &il->status); + + return ret; +} + +int +il4965_manage_ibss_station(struct il_priv *il, struct ieee80211_vif *vif, + bool add) +{ + struct il_vif_priv *vif_priv = (void *)vif->drv_priv; + + if (add) + return il4965_add_bssid_station(il, vif->bss_conf.bssid, + &vif_priv->ibss_bssid_sta_id); + return il_remove_station(il, vif_priv->ibss_bssid_sta_id, + vif->bss_conf.bssid); +} + +void +il4965_free_tfds_in_queue(struct il_priv *il, int sta_id, int tid, int freed) +{ + lockdep_assert_held(&il->sta_lock); + + if (il->stations[sta_id].tid[tid].tfds_in_queue >= freed) + il->stations[sta_id].tid[tid].tfds_in_queue -= freed; + else { + D_TX("free more than tfds_in_queue (%u:%d)\n", + il->stations[sta_id].tid[tid].tfds_in_queue, freed); + il->stations[sta_id].tid[tid].tfds_in_queue = 0; + } +} + +#define IL_TX_QUEUE_MSK 0xfffff + +static bool +il4965_is_single_rx_stream(struct il_priv *il) +{ + return il->current_ht_config.smps == IEEE80211_SMPS_STATIC || + il->current_ht_config.single_chain_sufficient; +} + +#define IL_NUM_RX_CHAINS_MULTIPLE 3 +#define IL_NUM_RX_CHAINS_SINGLE 2 +#define IL_NUM_IDLE_CHAINS_DUAL 2 +#define IL_NUM_IDLE_CHAINS_SINGLE 1 + +/* + * Determine how many receiver/antenna chains to use. + * + * More provides better reception via diversity. Fewer saves power + * at the expense of throughput, but only when not in powersave to + * start with. + * + * MIMO (dual stream) requires at least 2, but works better with 3. + * This does not determine *which* chains to use, just how many. + */ +static int +il4965_get_active_rx_chain_count(struct il_priv *il) +{ + /* # of Rx chains to use when expecting MIMO. */ + if (il4965_is_single_rx_stream(il)) + return IL_NUM_RX_CHAINS_SINGLE; + else + return IL_NUM_RX_CHAINS_MULTIPLE; +} + +/* + * When we are in power saving mode, unless device support spatial + * multiplexing power save, use the active count for rx chain count. + */ +static int +il4965_get_idle_rx_chain_count(struct il_priv *il, int active_cnt) +{ + /* # Rx chains when idling, depending on SMPS mode */ + switch (il->current_ht_config.smps) { + case IEEE80211_SMPS_STATIC: + case IEEE80211_SMPS_DYNAMIC: + return IL_NUM_IDLE_CHAINS_SINGLE; + case IEEE80211_SMPS_OFF: + return active_cnt; + default: + WARN(1, "invalid SMPS mode %d", il->current_ht_config.smps); + return active_cnt; + } +} + +/* up to 4 chains */ +static u8 +il4965_count_chain_bitmap(u32 chain_bitmap) +{ + u8 res; + res = (chain_bitmap & BIT(0)) >> 0; + res += (chain_bitmap & BIT(1)) >> 1; + res += (chain_bitmap & BIT(2)) >> 2; + res += (chain_bitmap & BIT(3)) >> 3; + return res; +} + +/** + * il4965_set_rxon_chain - Set up Rx chain usage in "staging" RXON image + * + * Selects how many and which Rx receivers/antennas/chains to use. + * This should not be used for scan command ... it puts data in wrong place. + */ +void +il4965_set_rxon_chain(struct il_priv *il) +{ + bool is_single = il4965_is_single_rx_stream(il); + bool is_cam = !test_bit(S_POWER_PMI, &il->status); + u8 idle_rx_cnt, active_rx_cnt, valid_rx_cnt; + u32 active_chains; + u16 rx_chain; + + /* Tell uCode which antennas are actually connected. + * Before first association, we assume all antennas are connected. + * Just after first association, il4965_chain_noise_calibration() + * checks which antennas actually *are* connected. */ + if (il->chain_noise_data.active_chains) + active_chains = il->chain_noise_data.active_chains; + else + active_chains = il->hw_params.valid_rx_ant; + + rx_chain = active_chains << RXON_RX_CHAIN_VALID_POS; + + /* How many receivers should we use? */ + active_rx_cnt = il4965_get_active_rx_chain_count(il); + idle_rx_cnt = il4965_get_idle_rx_chain_count(il, active_rx_cnt); + + /* correct rx chain count according hw settings + * and chain noise calibration + */ + valid_rx_cnt = il4965_count_chain_bitmap(active_chains); + if (valid_rx_cnt < active_rx_cnt) + active_rx_cnt = valid_rx_cnt; + + if (valid_rx_cnt < idle_rx_cnt) + idle_rx_cnt = valid_rx_cnt; + + rx_chain |= active_rx_cnt << RXON_RX_CHAIN_MIMO_CNT_POS; + rx_chain |= idle_rx_cnt << RXON_RX_CHAIN_CNT_POS; + + il->staging.rx_chain = cpu_to_le16(rx_chain); + + if (!is_single && active_rx_cnt >= IL_NUM_RX_CHAINS_SINGLE && is_cam) + il->staging.rx_chain |= RXON_RX_CHAIN_MIMO_FORCE_MSK; + else + il->staging.rx_chain &= ~RXON_RX_CHAIN_MIMO_FORCE_MSK; + + D_ASSOC("rx_chain=0x%X active=%d idle=%d\n", il->staging.rx_chain, + active_rx_cnt, idle_rx_cnt); + + WARN_ON(active_rx_cnt == 0 || idle_rx_cnt == 0 || + active_rx_cnt < idle_rx_cnt); +} + +static const char * +il4965_get_fh_string(int cmd) +{ + switch (cmd) { + IL_CMD(FH49_RSCSR_CHNL0_STTS_WPTR_REG); + IL_CMD(FH49_RSCSR_CHNL0_RBDCB_BASE_REG); + IL_CMD(FH49_RSCSR_CHNL0_WPTR); + IL_CMD(FH49_MEM_RCSR_CHNL0_CONFIG_REG); + IL_CMD(FH49_MEM_RSSR_SHARED_CTRL_REG); + IL_CMD(FH49_MEM_RSSR_RX_STATUS_REG); + IL_CMD(FH49_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV); + IL_CMD(FH49_TSSR_TX_STATUS_REG); + IL_CMD(FH49_TSSR_TX_ERROR_REG); + default: + return "UNKNOWN"; + } +} + +int +il4965_dump_fh(struct il_priv *il, char **buf, bool display) +{ + int i; +#ifdef CPTCFG_IWLEGACY_DEBUG + int pos = 0; + size_t bufsz = 0; +#endif + static const u32 fh_tbl[] = { + FH49_RSCSR_CHNL0_STTS_WPTR_REG, + FH49_RSCSR_CHNL0_RBDCB_BASE_REG, + FH49_RSCSR_CHNL0_WPTR, + FH49_MEM_RCSR_CHNL0_CONFIG_REG, + FH49_MEM_RSSR_SHARED_CTRL_REG, + FH49_MEM_RSSR_RX_STATUS_REG, + FH49_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV, + FH49_TSSR_TX_STATUS_REG, + FH49_TSSR_TX_ERROR_REG + }; +#ifdef CPTCFG_IWLEGACY_DEBUG + if (display) { + bufsz = ARRAY_SIZE(fh_tbl) * 48 + 40; + *buf = kmalloc(bufsz, GFP_KERNEL); + if (!*buf) + return -ENOMEM; + pos += + scnprintf(*buf + pos, bufsz - pos, "FH register values:\n"); + for (i = 0; i < ARRAY_SIZE(fh_tbl); i++) { + pos += + scnprintf(*buf + pos, bufsz - pos, + " %34s: 0X%08x\n", + il4965_get_fh_string(fh_tbl[i]), + il_rd(il, fh_tbl[i])); + } + return pos; + } +#endif + IL_ERR("FH register values:\n"); + for (i = 0; i < ARRAY_SIZE(fh_tbl); i++) { + IL_ERR(" %34s: 0X%08x\n", il4965_get_fh_string(fh_tbl[i]), + il_rd(il, fh_tbl[i])); + } + return 0; +} + +static void +il4965_hdl_missed_beacon(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il_missed_beacon_notif *missed_beacon; + + missed_beacon = &pkt->u.missed_beacon; + if (le32_to_cpu(missed_beacon->consecutive_missed_beacons) > + il->missed_beacon_threshold) { + D_CALIB("missed bcn cnsq %d totl %d rcd %d expctd %d\n", + le32_to_cpu(missed_beacon->consecutive_missed_beacons), + le32_to_cpu(missed_beacon->total_missed_becons), + le32_to_cpu(missed_beacon->num_recvd_beacons), + le32_to_cpu(missed_beacon->num_expected_beacons)); + if (!test_bit(S_SCANNING, &il->status)) + il4965_init_sensitivity(il); + } +} + +/* Calculate noise level, based on measurements during network silence just + * before arriving beacon. This measurement can be done only if we know + * exactly when to expect beacons, therefore only when we're associated. */ +static void +il4965_rx_calc_noise(struct il_priv *il) +{ + struct stats_rx_non_phy *rx_info; + int num_active_rx = 0; + int total_silence = 0; + int bcn_silence_a, bcn_silence_b, bcn_silence_c; + int last_rx_noise; + + rx_info = &(il->_4965.stats.rx.general); + bcn_silence_a = + le32_to_cpu(rx_info->beacon_silence_rssi_a) & IN_BAND_FILTER; + bcn_silence_b = + le32_to_cpu(rx_info->beacon_silence_rssi_b) & IN_BAND_FILTER; + bcn_silence_c = + le32_to_cpu(rx_info->beacon_silence_rssi_c) & IN_BAND_FILTER; + + if (bcn_silence_a) { + total_silence += bcn_silence_a; + num_active_rx++; + } + if (bcn_silence_b) { + total_silence += bcn_silence_b; + num_active_rx++; + } + if (bcn_silence_c) { + total_silence += bcn_silence_c; + num_active_rx++; + } + + /* Average among active antennas */ + if (num_active_rx) + last_rx_noise = (total_silence / num_active_rx) - 107; + else + last_rx_noise = IL_NOISE_MEAS_NOT_AVAILABLE; + + D_CALIB("inband silence a %u, b %u, c %u, dBm %d\n", bcn_silence_a, + bcn_silence_b, bcn_silence_c, last_rx_noise); +} + +#ifdef CPTCFG_IWLEGACY_DEBUGFS +/* + * based on the assumption of all stats counter are in DWORD + * FIXME: This function is for debugging, do not deal with + * the case of counters roll-over. + */ +static void +il4965_accumulative_stats(struct il_priv *il, __le32 * stats) +{ + int i, size; + __le32 *prev_stats; + u32 *accum_stats; + u32 *delta, *max_delta; + struct stats_general_common *general, *accum_general; + struct stats_tx *tx, *accum_tx; + + prev_stats = (__le32 *) &il->_4965.stats; + accum_stats = (u32 *) &il->_4965.accum_stats; + size = sizeof(struct il_notif_stats); + general = &il->_4965.stats.general.common; + accum_general = &il->_4965.accum_stats.general.common; + tx = &il->_4965.stats.tx; + accum_tx = &il->_4965.accum_stats.tx; + delta = (u32 *) &il->_4965.delta_stats; + max_delta = (u32 *) &il->_4965.max_delta; + + for (i = sizeof(__le32); i < size; + i += + sizeof(__le32), stats++, prev_stats++, delta++, max_delta++, + accum_stats++) { + if (le32_to_cpu(*stats) > le32_to_cpu(*prev_stats)) { + *delta = + (le32_to_cpu(*stats) - le32_to_cpu(*prev_stats)); + *accum_stats += *delta; + if (*delta > *max_delta) + *max_delta = *delta; + } + } + + /* reset accumulative stats for "no-counter" type stats */ + accum_general->temperature = general->temperature; + accum_general->ttl_timestamp = general->ttl_timestamp; +} +#endif + +static void +il4965_hdl_stats(struct il_priv *il, struct il_rx_buf *rxb) +{ + const int recalib_seconds = 60; + bool change; + struct il_rx_pkt *pkt = rxb_addr(rxb); + + D_RX("Statistics notification received (%d vs %d).\n", + (int)sizeof(struct il_notif_stats), + le32_to_cpu(pkt->len_n_flags) & IL_RX_FRAME_SIZE_MSK); + + change = + ((il->_4965.stats.general.common.temperature != + pkt->u.stats.general.common.temperature) || + ((il->_4965.stats.flag & STATS_REPLY_FLG_HT40_MODE_MSK) != + (pkt->u.stats.flag & STATS_REPLY_FLG_HT40_MODE_MSK))); +#ifdef CPTCFG_IWLEGACY_DEBUGFS + il4965_accumulative_stats(il, (__le32 *) &pkt->u.stats); +#endif + + /* TODO: reading some of stats is unneeded */ + memcpy(&il->_4965.stats, &pkt->u.stats, sizeof(il->_4965.stats)); + + set_bit(S_STATS, &il->status); + + /* + * Reschedule the stats timer to occur in recalib_seconds to ensure + * we get a thermal update even if the uCode doesn't give us one + */ + mod_timer(&il->stats_periodic, + jiffies + msecs_to_jiffies(recalib_seconds * 1000)); + + if (unlikely(!test_bit(S_SCANNING, &il->status)) && + (pkt->hdr.cmd == N_STATS)) { + il4965_rx_calc_noise(il); + queue_work(il->workqueue, &il->run_time_calib_work); + } + + if (change) + il4965_temperature_calib(il); +} + +static void +il4965_hdl_c_stats(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + + if (le32_to_cpu(pkt->u.stats.flag) & UCODE_STATS_CLEAR_MSK) { +#ifdef CPTCFG_IWLEGACY_DEBUGFS + memset(&il->_4965.accum_stats, 0, + sizeof(struct il_notif_stats)); + memset(&il->_4965.delta_stats, 0, + sizeof(struct il_notif_stats)); + memset(&il->_4965.max_delta, 0, sizeof(struct il_notif_stats)); +#endif + D_RX("Statistics have been cleared\n"); + } + il4965_hdl_stats(il, rxb); +} + + +/* + * mac80211 queues, ACs, hardware queues, FIFOs. + * + * Cf. http://wireless.kernel.org/en/developers/Documentation/mac80211/queues + * + * Mac80211 uses the following numbers, which we get as from it + * by way of skb_get_queue_mapping(skb): + * + * VO 0 + * VI 1 + * BE 2 + * BK 3 + * + * + * Regular (not A-MPDU) frames are put into hardware queues corresponding + * to the FIFOs, see comments in iwl-prph.h. Aggregated frames get their + * own queue per aggregation session (RA/TID combination), such queues are + * set up to map into FIFOs too, for which we need an AC->FIFO mapping. In + * order to map frames to the right queue, we also need an AC->hw queue + * mapping. This is implemented here. + * + * Due to the way hw queues are set up (by the hw specific modules like + * 4965.c), the AC->hw queue mapping is the identity + * mapping. + */ + +static const u8 tid_to_ac[] = { + IEEE80211_AC_BE, + IEEE80211_AC_BK, + IEEE80211_AC_BK, + IEEE80211_AC_BE, + IEEE80211_AC_VI, + IEEE80211_AC_VI, + IEEE80211_AC_VO, + IEEE80211_AC_VO +}; + +static inline int +il4965_get_ac_from_tid(u16 tid) +{ + if (likely(tid < ARRAY_SIZE(tid_to_ac))) + return tid_to_ac[tid]; + + /* no support for TIDs 8-15 yet */ + return -EINVAL; +} + +static inline int +il4965_get_fifo_from_tid(u16 tid) +{ + const u8 ac_to_fifo[] = { + IL_TX_FIFO_VO, + IL_TX_FIFO_VI, + IL_TX_FIFO_BE, + IL_TX_FIFO_BK, + }; + + if (likely(tid < ARRAY_SIZE(tid_to_ac))) + return ac_to_fifo[tid_to_ac[tid]]; + + /* no support for TIDs 8-15 yet */ + return -EINVAL; +} + +/* + * handle build C_TX command notification. + */ +static void +il4965_tx_cmd_build_basic(struct il_priv *il, struct sk_buff *skb, + struct il_tx_cmd *tx_cmd, + struct ieee80211_tx_info *info, + struct ieee80211_hdr *hdr, u8 std_id) +{ + __le16 fc = hdr->frame_control; + __le32 tx_flags = tx_cmd->tx_flags; + + tx_cmd->stop_time.life_time = TX_CMD_LIFE_TIME_INFINITE; + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) { + tx_flags |= TX_CMD_FLG_ACK_MSK; + if (ieee80211_is_mgmt(fc)) + tx_flags |= TX_CMD_FLG_SEQ_CTL_MSK; + if (ieee80211_is_probe_resp(fc) && + !(le16_to_cpu(hdr->seq_ctrl) & 0xf)) + tx_flags |= TX_CMD_FLG_TSF_MSK; + } else { + tx_flags &= (~TX_CMD_FLG_ACK_MSK); + tx_flags |= TX_CMD_FLG_SEQ_CTL_MSK; + } + + if (ieee80211_is_back_req(fc)) + tx_flags |= TX_CMD_FLG_ACK_MSK | TX_CMD_FLG_IMM_BA_RSP_MASK; + + tx_cmd->sta_id = std_id; + if (ieee80211_has_morefrags(fc)) + tx_flags |= TX_CMD_FLG_MORE_FRAG_MSK; + + if (ieee80211_is_data_qos(fc)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + tx_cmd->tid_tspec = qc[0] & 0xf; + tx_flags &= ~TX_CMD_FLG_SEQ_CTL_MSK; + } else { + tx_flags |= TX_CMD_FLG_SEQ_CTL_MSK; + } + + il_tx_cmd_protection(il, info, fc, &tx_flags); + + tx_flags &= ~(TX_CMD_FLG_ANT_SEL_MSK); + if (ieee80211_is_mgmt(fc)) { + if (ieee80211_is_assoc_req(fc) || ieee80211_is_reassoc_req(fc)) + tx_cmd->timeout.pm_frame_timeout = cpu_to_le16(3); + else + tx_cmd->timeout.pm_frame_timeout = cpu_to_le16(2); + } else { + tx_cmd->timeout.pm_frame_timeout = 0; + } + + tx_cmd->driver_txop = 0; + tx_cmd->tx_flags = tx_flags; + tx_cmd->next_frame_len = 0; +} + +static void +il4965_tx_cmd_build_rate(struct il_priv *il, + struct il_tx_cmd *tx_cmd, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + __le16 fc) +{ + const u8 rts_retry_limit = 60; + u32 rate_flags; + int rate_idx; + u8 data_retry_limit; + u8 rate_plcp; + + /* Set retry limit on DATA packets and Probe Responses */ + if (ieee80211_is_probe_resp(fc)) + data_retry_limit = 3; + else + data_retry_limit = IL4965_DEFAULT_TX_RETRY; + tx_cmd->data_retry_limit = data_retry_limit; + /* Set retry limit on RTS packets */ + tx_cmd->rts_retry_limit = min(data_retry_limit, rts_retry_limit); + + /* DATA packets will use the uCode station table for rate/antenna + * selection */ + if (ieee80211_is_data(fc)) { + tx_cmd->initial_rate_idx = 0; + tx_cmd->tx_flags |= TX_CMD_FLG_STA_RATE_MSK; + return; + } + + /** + * If the current TX rate stored in mac80211 has the MCS bit set, it's + * not really a TX rate. Thus, we use the lowest supported rate for + * this band. Also use the lowest supported rate if the stored rate + * idx is invalid. + */ + rate_idx = info->control.rates[0].idx; + if ((info->control.rates[0].flags & IEEE80211_TX_RC_MCS) || rate_idx < 0 + || rate_idx > RATE_COUNT_LEGACY) + rate_idx = rate_lowest_index(&il->bands[info->band], sta); + /* For 5 GHZ band, remap mac80211 rate indices into driver indices */ + if (info->band == IEEE80211_BAND_5GHZ) + rate_idx += IL_FIRST_OFDM_RATE; + /* Get PLCP rate for tx_cmd->rate_n_flags */ + rate_plcp = il_rates[rate_idx].plcp; + /* Zero out flags for this packet */ + rate_flags = 0; + + /* Set CCK flag as needed */ + if (rate_idx >= IL_FIRST_CCK_RATE && rate_idx <= IL_LAST_CCK_RATE) + rate_flags |= RATE_MCS_CCK_MSK; + + /* Set up antennas */ + il4965_toggle_tx_ant(il, &il->mgmt_tx_ant, il->hw_params.valid_tx_ant); + rate_flags |= BIT(il->mgmt_tx_ant) << RATE_MCS_ANT_POS; + + /* Set the rate in the TX cmd */ + tx_cmd->rate_n_flags = cpu_to_le32(rate_plcp | rate_flags); +} + +static void +il4965_tx_cmd_build_hwcrypto(struct il_priv *il, struct ieee80211_tx_info *info, + struct il_tx_cmd *tx_cmd, struct sk_buff *skb_frag, + int sta_id) +{ + struct ieee80211_key_conf *keyconf = info->control.hw_key; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + tx_cmd->sec_ctl = TX_CMD_SEC_CCM; + memcpy(tx_cmd->key, keyconf->key, keyconf->keylen); + if (info->flags & IEEE80211_TX_CTL_AMPDU) + tx_cmd->tx_flags |= TX_CMD_FLG_AGG_CCMP_MSK; + D_TX("tx_cmd with AES hwcrypto\n"); + break; + + case WLAN_CIPHER_SUITE_TKIP: + tx_cmd->sec_ctl = TX_CMD_SEC_TKIP; + ieee80211_get_tkip_p2k(keyconf, skb_frag, tx_cmd->key); + D_TX("tx_cmd with tkip hwcrypto\n"); + break; + + case WLAN_CIPHER_SUITE_WEP104: + tx_cmd->sec_ctl |= TX_CMD_SEC_KEY128; + /* fall through */ + case WLAN_CIPHER_SUITE_WEP40: + tx_cmd->sec_ctl |= + (TX_CMD_SEC_WEP | (keyconf->keyidx & TX_CMD_SEC_MSK) << + TX_CMD_SEC_SHIFT); + + memcpy(&tx_cmd->key[3], keyconf->key, keyconf->keylen); + + D_TX("Configuring packet for WEP encryption " "with key %d\n", + keyconf->keyidx); + break; + + default: + IL_ERR("Unknown encode cipher %x\n", keyconf->cipher); + break; + } +} + +/* + * start C_TX command process + */ +int +il4965_tx_skb(struct il_priv *il, + struct ieee80211_sta *sta, + struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct il_station_priv *sta_priv = NULL; + struct il_tx_queue *txq; + struct il_queue *q; + struct il_device_cmd *out_cmd; + struct il_cmd_meta *out_meta; + struct il_tx_cmd *tx_cmd; + int txq_id; + dma_addr_t phys_addr; + dma_addr_t txcmd_phys; + dma_addr_t scratch_phys; + u16 len, firstlen, secondlen; + u16 seq_number = 0; + __le16 fc; + u8 hdr_len; + u8 sta_id; + u8 wait_write_ptr = 0; + u8 tid = 0; + u8 *qc = NULL; + unsigned long flags; + bool is_agg = false; + + spin_lock_irqsave(&il->lock, flags); + if (il_is_rfkill(il)) { + D_DROP("Dropping - RF KILL\n"); + goto drop_unlock; + } + + fc = hdr->frame_control; + +#ifdef CPTCFG_IWLEGACY_DEBUG + if (ieee80211_is_auth(fc)) + D_TX("Sending AUTH frame\n"); + else if (ieee80211_is_assoc_req(fc)) + D_TX("Sending ASSOC frame\n"); + else if (ieee80211_is_reassoc_req(fc)) + D_TX("Sending REASSOC frame\n"); +#endif + + hdr_len = ieee80211_hdrlen(fc); + + /* For management frames use broadcast id to do not break aggregation */ + if (!ieee80211_is_data(fc)) + sta_id = il->hw_params.bcast_id; + else { + /* Find idx into station table for destination station */ + sta_id = il_sta_id_or_broadcast(il, sta); + + if (sta_id == IL_INVALID_STATION) { + D_DROP("Dropping - INVALID STATION: %pM\n", hdr->addr1); + goto drop_unlock; + } + } + + D_TX("station Id %d\n", sta_id); + + if (sta) + sta_priv = (void *)sta->drv_priv; + + if (sta_priv && sta_priv->asleep && + (info->flags & IEEE80211_TX_CTL_NO_PS_BUFFER)) { + /* + * This sends an asynchronous command to the device, + * but we can rely on it being processed before the + * next frame is processed -- and the next frame to + * this station is the one that will consume this + * counter. + * For now set the counter to just 1 since we do not + * support uAPSD yet. + */ + il4965_sta_modify_sleep_tx_count(il, sta_id, 1); + } + + /* FIXME: remove me ? */ + WARN_ON_ONCE(info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM); + + /* Access category (AC) is also the queue number */ + txq_id = skb_get_queue_mapping(skb); + + /* irqs already disabled/saved above when locking il->lock */ + spin_lock(&il->sta_lock); + + if (ieee80211_is_data_qos(fc)) { + qc = ieee80211_get_qos_ctl(hdr); + tid = qc[0] & IEEE80211_QOS_CTL_TID_MASK; + if (WARN_ON_ONCE(tid >= MAX_TID_COUNT)) { + spin_unlock(&il->sta_lock); + goto drop_unlock; + } + seq_number = il->stations[sta_id].tid[tid].seq_number; + seq_number &= IEEE80211_SCTL_SEQ; + hdr->seq_ctrl = + hdr->seq_ctrl & cpu_to_le16(IEEE80211_SCTL_FRAG); + hdr->seq_ctrl |= cpu_to_le16(seq_number); + seq_number += 0x10; + /* aggregation is on for this */ + if (info->flags & IEEE80211_TX_CTL_AMPDU && + il->stations[sta_id].tid[tid].agg.state == IL_AGG_ON) { + txq_id = il->stations[sta_id].tid[tid].agg.txq_id; + is_agg = true; + } + } + + txq = &il->txq[txq_id]; + q = &txq->q; + + if (unlikely(il_queue_space(q) < q->high_mark)) { + spin_unlock(&il->sta_lock); + goto drop_unlock; + } + + if (ieee80211_is_data_qos(fc)) { + il->stations[sta_id].tid[tid].tfds_in_queue++; + if (!ieee80211_has_morefrags(fc)) + il->stations[sta_id].tid[tid].seq_number = seq_number; + } + + spin_unlock(&il->sta_lock); + + txq->skbs[q->write_ptr] = skb; + + /* Set up first empty entry in queue's array of Tx/cmd buffers */ + out_cmd = txq->cmd[q->write_ptr]; + out_meta = &txq->meta[q->write_ptr]; + tx_cmd = &out_cmd->cmd.tx; + memset(&out_cmd->hdr, 0, sizeof(out_cmd->hdr)); + memset(tx_cmd, 0, sizeof(struct il_tx_cmd)); + + /* + * Set up the Tx-command (not MAC!) header. + * Store the chosen Tx queue and TFD idx within the sequence field; + * after Tx, uCode's Tx response will return this value so driver can + * locate the frame within the tx queue and do post-tx processing. + */ + out_cmd->hdr.cmd = C_TX; + out_cmd->hdr.sequence = + cpu_to_le16((u16) + (QUEUE_TO_SEQ(txq_id) | IDX_TO_SEQ(q->write_ptr))); + + /* Copy MAC header from skb into command buffer */ + memcpy(tx_cmd->hdr, hdr, hdr_len); + + /* Total # bytes to be transmitted */ + tx_cmd->len = cpu_to_le16((u16) skb->len); + + if (info->control.hw_key) + il4965_tx_cmd_build_hwcrypto(il, info, tx_cmd, skb, sta_id); + + /* TODO need this for burst mode later on */ + il4965_tx_cmd_build_basic(il, skb, tx_cmd, info, hdr, sta_id); + + il4965_tx_cmd_build_rate(il, tx_cmd, info, sta, fc); + + /* + * Use the first empty entry in this queue's command buffer array + * to contain the Tx command and MAC header concatenated together + * (payload data will be in another buffer). + * Size of this varies, due to varying MAC header length. + * If end is not dword aligned, we'll have 2 extra bytes at the end + * of the MAC header (device reads on dword boundaries). + * We'll tell device about this padding later. + */ + len = sizeof(struct il_tx_cmd) + sizeof(struct il_cmd_header) + hdr_len; + firstlen = (len + 3) & ~3; + + /* Tell NIC about any 2-byte padding after MAC header */ + if (firstlen != len) + tx_cmd->tx_flags |= TX_CMD_FLG_MH_PAD_MSK; + + /* Physical address of this Tx command's header (not MAC header!), + * within command buffer array. */ + txcmd_phys = + pci_map_single(il->pci_dev, &out_cmd->hdr, firstlen, + PCI_DMA_BIDIRECTIONAL); + if (unlikely(pci_dma_mapping_error(il->pci_dev, txcmd_phys))) + goto drop_unlock; + + /* Set up TFD's 2nd entry to point directly to remainder of skb, + * if any (802.11 null frames have no payload). */ + secondlen = skb->len - hdr_len; + if (secondlen > 0) { + phys_addr = + pci_map_single(il->pci_dev, skb->data + hdr_len, secondlen, + PCI_DMA_TODEVICE); + if (unlikely(pci_dma_mapping_error(il->pci_dev, phys_addr))) + goto drop_unlock; + } + + /* Add buffer containing Tx command and MAC(!) header to TFD's + * first entry */ + il->ops->txq_attach_buf_to_tfd(il, txq, txcmd_phys, firstlen, 1, 0); + dma_unmap_addr_set(out_meta, mapping, txcmd_phys); + dma_unmap_len_set(out_meta, len, firstlen); + if (secondlen) + il->ops->txq_attach_buf_to_tfd(il, txq, phys_addr, secondlen, + 0, 0); + + if (!ieee80211_has_morefrags(hdr->frame_control)) { + txq->need_update = 1; + } else { + wait_write_ptr = 1; + txq->need_update = 0; + } + + scratch_phys = + txcmd_phys + sizeof(struct il_cmd_header) + + offsetof(struct il_tx_cmd, scratch); + + /* take back ownership of DMA buffer to enable update */ + pci_dma_sync_single_for_cpu(il->pci_dev, txcmd_phys, firstlen, + PCI_DMA_BIDIRECTIONAL); + tx_cmd->dram_lsb_ptr = cpu_to_le32(scratch_phys); + tx_cmd->dram_msb_ptr = il_get_dma_hi_addr(scratch_phys); + + il_update_stats(il, true, fc, skb->len); + + D_TX("sequence nr = 0X%x\n", le16_to_cpu(out_cmd->hdr.sequence)); + D_TX("tx_flags = 0X%x\n", le32_to_cpu(tx_cmd->tx_flags)); + il_print_hex_dump(il, IL_DL_TX, (u8 *) tx_cmd, sizeof(*tx_cmd)); + il_print_hex_dump(il, IL_DL_TX, (u8 *) tx_cmd->hdr, hdr_len); + + /* Set up entry for this TFD in Tx byte-count array */ + if (info->flags & IEEE80211_TX_CTL_AMPDU) + il->ops->txq_update_byte_cnt_tbl(il, txq, le16_to_cpu(tx_cmd->len)); + + pci_dma_sync_single_for_device(il->pci_dev, txcmd_phys, firstlen, + PCI_DMA_BIDIRECTIONAL); + + /* Tell device the write idx *just past* this latest filled TFD */ + q->write_ptr = il_queue_inc_wrap(q->write_ptr, q->n_bd); + il_txq_update_write_ptr(il, txq); + spin_unlock_irqrestore(&il->lock, flags); + + /* + * At this point the frame is "transmitted" successfully + * and we will get a TX status notification eventually, + * regardless of the value of ret. "ret" only indicates + * whether or not we should update the write pointer. + */ + + /* + * Avoid atomic ops if it isn't an associated client. + * Also, if this is a packet for aggregation, don't + * increase the counter because the ucode will stop + * aggregation queues when their respective station + * goes to sleep. + */ + if (sta_priv && sta_priv->client && !is_agg) + atomic_inc(&sta_priv->pending_frames); + + if (il_queue_space(q) < q->high_mark && il->mac80211_registered) { + if (wait_write_ptr) { + spin_lock_irqsave(&il->lock, flags); + txq->need_update = 1; + il_txq_update_write_ptr(il, txq); + spin_unlock_irqrestore(&il->lock, flags); + } else { + il_stop_queue(il, txq); + } + } + + return 0; + +drop_unlock: + spin_unlock_irqrestore(&il->lock, flags); + return -1; +} + +static inline int +il4965_alloc_dma_ptr(struct il_priv *il, struct il_dma_ptr *ptr, size_t size) +{ + ptr->addr = dma_alloc_coherent(&il->pci_dev->dev, size, &ptr->dma, + GFP_KERNEL); + if (!ptr->addr) + return -ENOMEM; + ptr->size = size; + return 0; +} + +static inline void +il4965_free_dma_ptr(struct il_priv *il, struct il_dma_ptr *ptr) +{ + if (unlikely(!ptr->addr)) + return; + + dma_free_coherent(&il->pci_dev->dev, ptr->size, ptr->addr, ptr->dma); + memset(ptr, 0, sizeof(*ptr)); +} + +/** + * il4965_hw_txq_ctx_free - Free TXQ Context + * + * Destroy all TX DMA queues and structures + */ +void +il4965_hw_txq_ctx_free(struct il_priv *il) +{ + int txq_id; + + /* Tx queues */ + if (il->txq) { + for (txq_id = 0; txq_id < il->hw_params.max_txq_num; txq_id++) + if (txq_id == il->cmd_queue) + il_cmd_queue_free(il); + else + il_tx_queue_free(il, txq_id); + } + il4965_free_dma_ptr(il, &il->kw); + + il4965_free_dma_ptr(il, &il->scd_bc_tbls); + + /* free tx queue structure */ + il_free_txq_mem(il); +} + +/** + * il4965_txq_ctx_alloc - allocate TX queue context + * Allocate all Tx DMA structures and initialize them + * + * @param il + * @return error code + */ +int +il4965_txq_ctx_alloc(struct il_priv *il) +{ + int ret, txq_id; + unsigned long flags; + + /* Free all tx/cmd queues and keep-warm buffer */ + il4965_hw_txq_ctx_free(il); + + ret = + il4965_alloc_dma_ptr(il, &il->scd_bc_tbls, + il->hw_params.scd_bc_tbls_size); + if (ret) { + IL_ERR("Scheduler BC Table allocation failed\n"); + goto error_bc_tbls; + } + /* Alloc keep-warm buffer */ + ret = il4965_alloc_dma_ptr(il, &il->kw, IL_KW_SIZE); + if (ret) { + IL_ERR("Keep Warm allocation failed\n"); + goto error_kw; + } + + /* allocate tx queue structure */ + ret = il_alloc_txq_mem(il); + if (ret) + goto error; + + spin_lock_irqsave(&il->lock, flags); + + /* Turn off all Tx DMA fifos */ + il4965_txq_set_sched(il, 0); + + /* Tell NIC where to find the "keep warm" buffer */ + il_wr(il, FH49_KW_MEM_ADDR_REG, il->kw.dma >> 4); + + spin_unlock_irqrestore(&il->lock, flags); + + /* Alloc and init all Tx queues, including the command queue (#4/#9) */ + for (txq_id = 0; txq_id < il->hw_params.max_txq_num; txq_id++) { + ret = il_tx_queue_init(il, txq_id); + if (ret) { + IL_ERR("Tx %d queue init failed\n", txq_id); + goto error; + } + } + + return ret; + +error: + il4965_hw_txq_ctx_free(il); + il4965_free_dma_ptr(il, &il->kw); +error_kw: + il4965_free_dma_ptr(il, &il->scd_bc_tbls); +error_bc_tbls: + return ret; +} + +void +il4965_txq_ctx_reset(struct il_priv *il) +{ + int txq_id; + unsigned long flags; + + spin_lock_irqsave(&il->lock, flags); + + /* Turn off all Tx DMA fifos */ + il4965_txq_set_sched(il, 0); + /* Tell NIC where to find the "keep warm" buffer */ + il_wr(il, FH49_KW_MEM_ADDR_REG, il->kw.dma >> 4); + + spin_unlock_irqrestore(&il->lock, flags); + + /* Alloc and init all Tx queues, including the command queue (#4) */ + for (txq_id = 0; txq_id < il->hw_params.max_txq_num; txq_id++) + il_tx_queue_reset(il, txq_id); +} + +static void +il4965_txq_ctx_unmap(struct il_priv *il) +{ + int txq_id; + + if (!il->txq) + return; + + /* Unmap DMA from host system and free skb's */ + for (txq_id = 0; txq_id < il->hw_params.max_txq_num; txq_id++) + if (txq_id == il->cmd_queue) + il_cmd_queue_unmap(il); + else + il_tx_queue_unmap(il, txq_id); +} + +/** + * il4965_txq_ctx_stop - Stop all Tx DMA channels + */ +void +il4965_txq_ctx_stop(struct il_priv *il) +{ + int ch, ret; + + _il_wr_prph(il, IL49_SCD_TXFACT, 0); + + /* Stop each Tx DMA channel, and wait for it to be idle */ + for (ch = 0; ch < il->hw_params.dma_chnl_num; ch++) { + _il_wr(il, FH49_TCSR_CHNL_TX_CONFIG_REG(ch), 0x0); + ret = + _il_poll_bit(il, FH49_TSSR_TX_STATUS_REG, + FH49_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(ch), + FH49_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(ch), + 1000); + if (ret < 0) + IL_ERR("Timeout stopping DMA channel %d [0x%08x]", + ch, _il_rd(il, FH49_TSSR_TX_STATUS_REG)); + } +} + +/* + * Find first available (lowest unused) Tx Queue, mark it "active". + * Called only when finding queue for aggregation. + * Should never return anything < 7, because they should already + * be in use as EDCA AC (0-3), Command (4), reserved (5, 6) + */ +static int +il4965_txq_ctx_activate_free(struct il_priv *il) +{ + int txq_id; + + for (txq_id = 0; txq_id < il->hw_params.max_txq_num; txq_id++) + if (!test_and_set_bit(txq_id, &il->txq_ctx_active_msk)) + return txq_id; + return -1; +} + +/** + * il4965_tx_queue_stop_scheduler - Stop queue, but keep configuration + */ +static void +il4965_tx_queue_stop_scheduler(struct il_priv *il, u16 txq_id) +{ + /* Simply stop the queue, but don't change any configuration; + * the SCD_ACT_EN bit is the write-enable mask for the ACTIVE bit. */ + il_wr_prph(il, IL49_SCD_QUEUE_STATUS_BITS(txq_id), + (0 << IL49_SCD_QUEUE_STTS_REG_POS_ACTIVE) | + (1 << IL49_SCD_QUEUE_STTS_REG_POS_SCD_ACT_EN)); +} + +/** + * il4965_tx_queue_set_q2ratid - Map unique receiver/tid combination to a queue + */ +static int +il4965_tx_queue_set_q2ratid(struct il_priv *il, u16 ra_tid, u16 txq_id) +{ + u32 tbl_dw_addr; + u32 tbl_dw; + u16 scd_q2ratid; + + scd_q2ratid = ra_tid & IL_SCD_QUEUE_RA_TID_MAP_RATID_MSK; + + tbl_dw_addr = + il->scd_base_addr + IL49_SCD_TRANSLATE_TBL_OFFSET_QUEUE(txq_id); + + tbl_dw = il_read_targ_mem(il, tbl_dw_addr); + + if (txq_id & 0x1) + tbl_dw = (scd_q2ratid << 16) | (tbl_dw & 0x0000FFFF); + else + tbl_dw = scd_q2ratid | (tbl_dw & 0xFFFF0000); + + il_write_targ_mem(il, tbl_dw_addr, tbl_dw); + + return 0; +} + +/** + * il4965_tx_queue_agg_enable - Set up & enable aggregation for selected queue + * + * NOTE: txq_id must be greater than IL49_FIRST_AMPDU_QUEUE, + * i.e. it must be one of the higher queues used for aggregation + */ +static int +il4965_txq_agg_enable(struct il_priv *il, int txq_id, int tx_fifo, int sta_id, + int tid, u16 ssn_idx) +{ + unsigned long flags; + u16 ra_tid; + int ret; + + if ((IL49_FIRST_AMPDU_QUEUE > txq_id) || + (IL49_FIRST_AMPDU_QUEUE + + il->cfg->num_of_ampdu_queues <= txq_id)) { + IL_WARN("queue number out of range: %d, must be %d to %d\n", + txq_id, IL49_FIRST_AMPDU_QUEUE, + IL49_FIRST_AMPDU_QUEUE + + il->cfg->num_of_ampdu_queues - 1); + return -EINVAL; + } + + ra_tid = BUILD_RAxTID(sta_id, tid); + + /* Modify device's station table to Tx this TID */ + ret = il4965_sta_tx_modify_enable_tid(il, sta_id, tid); + if (ret) + return ret; + + spin_lock_irqsave(&il->lock, flags); + + /* Stop this Tx queue before configuring it */ + il4965_tx_queue_stop_scheduler(il, txq_id); + + /* Map receiver-address / traffic-ID to this queue */ + il4965_tx_queue_set_q2ratid(il, ra_tid, txq_id); + + /* Set this queue as a chain-building queue */ + il_set_bits_prph(il, IL49_SCD_QUEUECHAIN_SEL, (1 << txq_id)); + + /* Place first TFD at idx corresponding to start sequence number. + * Assumes that ssn_idx is valid (!= 0xFFF) */ + il->txq[txq_id].q.read_ptr = (ssn_idx & 0xff); + il->txq[txq_id].q.write_ptr = (ssn_idx & 0xff); + il4965_set_wr_ptrs(il, txq_id, ssn_idx); + + /* Set up Tx win size and frame limit for this queue */ + il_write_targ_mem(il, + il->scd_base_addr + + IL49_SCD_CONTEXT_QUEUE_OFFSET(txq_id), + (SCD_WIN_SIZE << IL49_SCD_QUEUE_CTX_REG1_WIN_SIZE_POS) + & IL49_SCD_QUEUE_CTX_REG1_WIN_SIZE_MSK); + + il_write_targ_mem(il, + il->scd_base_addr + + IL49_SCD_CONTEXT_QUEUE_OFFSET(txq_id) + sizeof(u32), + (SCD_FRAME_LIMIT << + IL49_SCD_QUEUE_CTX_REG2_FRAME_LIMIT_POS) & + IL49_SCD_QUEUE_CTX_REG2_FRAME_LIMIT_MSK); + + il_set_bits_prph(il, IL49_SCD_INTERRUPT_MASK, (1 << txq_id)); + + /* Set up Status area in SRAM, map to Tx DMA/FIFO, activate the queue */ + il4965_tx_queue_set_status(il, &il->txq[txq_id], tx_fifo, 1); + + spin_unlock_irqrestore(&il->lock, flags); + + return 0; +} + +int +il4965_tx_agg_start(struct il_priv *il, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 * ssn) +{ + int sta_id; + int tx_fifo; + int txq_id; + int ret; + unsigned long flags; + struct il_tid_data *tid_data; + + /* FIXME: warning if tx fifo not found ? */ + tx_fifo = il4965_get_fifo_from_tid(tid); + if (unlikely(tx_fifo < 0)) + return tx_fifo; + + D_HT("%s on ra = %pM tid = %d\n", __func__, sta->addr, tid); + + sta_id = il_sta_id(sta); + if (sta_id == IL_INVALID_STATION) { + IL_ERR("Start AGG on invalid station\n"); + return -ENXIO; + } + if (unlikely(tid >= MAX_TID_COUNT)) + return -EINVAL; + + if (il->stations[sta_id].tid[tid].agg.state != IL_AGG_OFF) { + IL_ERR("Start AGG when state is not IL_AGG_OFF !\n"); + return -ENXIO; + } + + txq_id = il4965_txq_ctx_activate_free(il); + if (txq_id == -1) { + IL_ERR("No free aggregation queue available\n"); + return -ENXIO; + } + + spin_lock_irqsave(&il->sta_lock, flags); + tid_data = &il->stations[sta_id].tid[tid]; + *ssn = IEEE80211_SEQ_TO_SN(tid_data->seq_number); + tid_data->agg.txq_id = txq_id; + il_set_swq_id(&il->txq[txq_id], il4965_get_ac_from_tid(tid), txq_id); + spin_unlock_irqrestore(&il->sta_lock, flags); + + ret = il4965_txq_agg_enable(il, txq_id, tx_fifo, sta_id, tid, *ssn); + if (ret) + return ret; + + spin_lock_irqsave(&il->sta_lock, flags); + tid_data = &il->stations[sta_id].tid[tid]; + if (tid_data->tfds_in_queue == 0) { + D_HT("HW queue is empty\n"); + tid_data->agg.state = IL_AGG_ON; + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + } else { + D_HT("HW queue is NOT empty: %d packets in HW queue\n", + tid_data->tfds_in_queue); + tid_data->agg.state = IL_EMPTYING_HW_QUEUE_ADDBA; + } + spin_unlock_irqrestore(&il->sta_lock, flags); + return ret; +} + +/** + * txq_id must be greater than IL49_FIRST_AMPDU_QUEUE + * il->lock must be held by the caller + */ +static int +il4965_txq_agg_disable(struct il_priv *il, u16 txq_id, u16 ssn_idx, u8 tx_fifo) +{ + if ((IL49_FIRST_AMPDU_QUEUE > txq_id) || + (IL49_FIRST_AMPDU_QUEUE + + il->cfg->num_of_ampdu_queues <= txq_id)) { + IL_WARN("queue number out of range: %d, must be %d to %d\n", + txq_id, IL49_FIRST_AMPDU_QUEUE, + IL49_FIRST_AMPDU_QUEUE + + il->cfg->num_of_ampdu_queues - 1); + return -EINVAL; + } + + il4965_tx_queue_stop_scheduler(il, txq_id); + + il_clear_bits_prph(il, IL49_SCD_QUEUECHAIN_SEL, (1 << txq_id)); + + il->txq[txq_id].q.read_ptr = (ssn_idx & 0xff); + il->txq[txq_id].q.write_ptr = (ssn_idx & 0xff); + /* supposes that ssn_idx is valid (!= 0xFFF) */ + il4965_set_wr_ptrs(il, txq_id, ssn_idx); + + il_clear_bits_prph(il, IL49_SCD_INTERRUPT_MASK, (1 << txq_id)); + il_txq_ctx_deactivate(il, txq_id); + il4965_tx_queue_set_status(il, &il->txq[txq_id], tx_fifo, 0); + + return 0; +} + +int +il4965_tx_agg_stop(struct il_priv *il, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid) +{ + int tx_fifo_id, txq_id, sta_id, ssn; + struct il_tid_data *tid_data; + int write_ptr, read_ptr; + unsigned long flags; + + /* FIXME: warning if tx_fifo_id not found ? */ + tx_fifo_id = il4965_get_fifo_from_tid(tid); + if (unlikely(tx_fifo_id < 0)) + return tx_fifo_id; + + sta_id = il_sta_id(sta); + + if (sta_id == IL_INVALID_STATION) { + IL_ERR("Invalid station for AGG tid %d\n", tid); + return -ENXIO; + } + + spin_lock_irqsave(&il->sta_lock, flags); + + tid_data = &il->stations[sta_id].tid[tid]; + ssn = (tid_data->seq_number & IEEE80211_SCTL_SEQ) >> 4; + txq_id = tid_data->agg.txq_id; + + switch (il->stations[sta_id].tid[tid].agg.state) { + case IL_EMPTYING_HW_QUEUE_ADDBA: + /* + * This can happen if the peer stops aggregation + * again before we've had a chance to drain the + * queue we selected previously, i.e. before the + * session was really started completely. + */ + D_HT("AGG stop before setup done\n"); + goto turn_off; + case IL_AGG_ON: + break; + default: + IL_WARN("Stopping AGG while state not ON or starting\n"); + } + + write_ptr = il->txq[txq_id].q.write_ptr; + read_ptr = il->txq[txq_id].q.read_ptr; + + /* The queue is not empty */ + if (write_ptr != read_ptr) { + D_HT("Stopping a non empty AGG HW QUEUE\n"); + il->stations[sta_id].tid[tid].agg.state = + IL_EMPTYING_HW_QUEUE_DELBA; + spin_unlock_irqrestore(&il->sta_lock, flags); + return 0; + } + + D_HT("HW queue is empty\n"); +turn_off: + il->stations[sta_id].tid[tid].agg.state = IL_AGG_OFF; + + /* do not restore/save irqs */ + spin_unlock(&il->sta_lock); + spin_lock(&il->lock); + + /* + * the only reason this call can fail is queue number out of range, + * which can happen if uCode is reloaded and all the station + * information are lost. if it is outside the range, there is no need + * to deactivate the uCode queue, just return "success" to allow + * mac80211 to clean up it own data. + */ + il4965_txq_agg_disable(il, txq_id, ssn, tx_fifo_id); + spin_unlock_irqrestore(&il->lock, flags); + + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + + return 0; +} + +int +il4965_txq_check_empty(struct il_priv *il, int sta_id, u8 tid, int txq_id) +{ + struct il_queue *q = &il->txq[txq_id].q; + u8 *addr = il->stations[sta_id].sta.sta.addr; + struct il_tid_data *tid_data = &il->stations[sta_id].tid[tid]; + + lockdep_assert_held(&il->sta_lock); + + switch (il->stations[sta_id].tid[tid].agg.state) { + case IL_EMPTYING_HW_QUEUE_DELBA: + /* We are reclaiming the last packet of the */ + /* aggregated HW queue */ + if (txq_id == tid_data->agg.txq_id && + q->read_ptr == q->write_ptr) { + u16 ssn = IEEE80211_SEQ_TO_SN(tid_data->seq_number); + int tx_fifo = il4965_get_fifo_from_tid(tid); + D_HT("HW queue empty: continue DELBA flow\n"); + il4965_txq_agg_disable(il, txq_id, ssn, tx_fifo); + tid_data->agg.state = IL_AGG_OFF; + ieee80211_stop_tx_ba_cb_irqsafe(il->vif, addr, tid); + } + break; + case IL_EMPTYING_HW_QUEUE_ADDBA: + /* We are reclaiming the last packet of the queue */ + if (tid_data->tfds_in_queue == 0) { + D_HT("HW queue empty: continue ADDBA flow\n"); + tid_data->agg.state = IL_AGG_ON; + ieee80211_start_tx_ba_cb_irqsafe(il->vif, addr, tid); + } + break; + } + + return 0; +} + +static void +il4965_non_agg_tx_status(struct il_priv *il, const u8 *addr1) +{ + struct ieee80211_sta *sta; + struct il_station_priv *sta_priv; + + rcu_read_lock(); + sta = ieee80211_find_sta(il->vif, addr1); + if (sta) { + sta_priv = (void *)sta->drv_priv; + /* avoid atomic ops if this isn't a client */ + if (sta_priv->client && + atomic_dec_return(&sta_priv->pending_frames) == 0) + ieee80211_sta_block_awake(il->hw, sta, false); + } + rcu_read_unlock(); +} + +static void +il4965_tx_status(struct il_priv *il, struct sk_buff *skb, bool is_agg) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + + if (!is_agg) + il4965_non_agg_tx_status(il, hdr->addr1); + + ieee80211_tx_status_irqsafe(il->hw, skb); +} + +int +il4965_tx_queue_reclaim(struct il_priv *il, int txq_id, int idx) +{ + struct il_tx_queue *txq = &il->txq[txq_id]; + struct il_queue *q = &txq->q; + int nfreed = 0; + struct ieee80211_hdr *hdr; + struct sk_buff *skb; + + if (idx >= q->n_bd || il_queue_used(q, idx) == 0) { + IL_ERR("Read idx for DMA queue txq id (%d), idx %d, " + "is out of range [0-%d] %d %d.\n", txq_id, idx, q->n_bd, + q->write_ptr, q->read_ptr); + return 0; + } + + for (idx = il_queue_inc_wrap(idx, q->n_bd); q->read_ptr != idx; + q->read_ptr = il_queue_inc_wrap(q->read_ptr, q->n_bd)) { + + skb = txq->skbs[txq->q.read_ptr]; + + if (WARN_ON_ONCE(skb == NULL)) + continue; + + hdr = (struct ieee80211_hdr *) skb->data; + if (ieee80211_is_data_qos(hdr->frame_control)) + nfreed++; + + il4965_tx_status(il, skb, txq_id >= IL4965_FIRST_AMPDU_QUEUE); + + txq->skbs[txq->q.read_ptr] = NULL; + il->ops->txq_free_tfd(il, txq); + } + return nfreed; +} + +/** + * il4965_tx_status_reply_compressed_ba - Update tx status from block-ack + * + * Go through block-ack's bitmap of ACK'd frames, update driver's record of + * ACK vs. not. This gets sent to mac80211, then to rate scaling algo. + */ +static int +il4965_tx_status_reply_compressed_ba(struct il_priv *il, struct il_ht_agg *agg, + struct il_compressed_ba_resp *ba_resp) +{ + int i, sh, ack; + u16 seq_ctl = le16_to_cpu(ba_resp->seq_ctl); + u16 scd_flow = le16_to_cpu(ba_resp->scd_flow); + int successes = 0; + struct ieee80211_tx_info *info; + u64 bitmap, sent_bitmap; + + if (unlikely(!agg->wait_for_ba)) { + if (unlikely(ba_resp->bitmap)) + IL_ERR("Received BA when not expected\n"); + return -EINVAL; + } + + /* Mark that the expected block-ack response arrived */ + agg->wait_for_ba = 0; + D_TX_REPLY("BA %d %d\n", agg->start_idx, ba_resp->seq_ctl); + + /* Calculate shift to align block-ack bits with our Tx win bits */ + sh = agg->start_idx - SEQ_TO_IDX(seq_ctl >> 4); + if (sh < 0) /* tbw something is wrong with indices */ + sh += 0x100; + + if (agg->frame_count > (64 - sh)) { + D_TX_REPLY("more frames than bitmap size"); + return -1; + } + + /* don't use 64-bit values for now */ + bitmap = le64_to_cpu(ba_resp->bitmap) >> sh; + + /* check for success or failure according to the + * transmitted bitmap and block-ack bitmap */ + sent_bitmap = bitmap & agg->bitmap; + + /* For each frame attempted in aggregation, + * update driver's record of tx frame's status. */ + i = 0; + while (sent_bitmap) { + ack = sent_bitmap & 1ULL; + successes += ack; + D_TX_REPLY("%s ON i=%d idx=%d raw=%d\n", ack ? "ACK" : "NACK", + i, (agg->start_idx + i) & 0xff, agg->start_idx + i); + sent_bitmap >>= 1; + ++i; + } + + D_TX_REPLY("Bitmap %llx\n", (unsigned long long)bitmap); + + info = IEEE80211_SKB_CB(il->txq[scd_flow].skbs[agg->start_idx]); + memset(&info->status, 0, sizeof(info->status)); + info->flags |= IEEE80211_TX_STAT_ACK; + info->flags |= IEEE80211_TX_STAT_AMPDU; + info->status.ampdu_ack_len = successes; + info->status.ampdu_len = agg->frame_count; + il4965_hwrate_to_tx_control(il, agg->rate_n_flags, info); + + return 0; +} + +static inline bool +il4965_is_tx_success(u32 status) +{ + status &= TX_STATUS_MSK; + return (status == TX_STATUS_SUCCESS || status == TX_STATUS_DIRECT_DONE); +} + +static u8 +il4965_find_station(struct il_priv *il, const u8 *addr) +{ + int i; + int start = 0; + int ret = IL_INVALID_STATION; + unsigned long flags; + + if (il->iw_mode == NL80211_IFTYPE_ADHOC) + start = IL_STA_ID; + + if (is_broadcast_ether_addr(addr)) + return il->hw_params.bcast_id; + + spin_lock_irqsave(&il->sta_lock, flags); + for (i = start; i < il->hw_params.max_stations; i++) + if (il->stations[i].used && + ether_addr_equal(il->stations[i].sta.sta.addr, addr)) { + ret = i; + goto out; + } + + D_ASSOC("can not find STA %pM total %d\n", addr, il->num_stations); + +out: + /* + * It may be possible that more commands interacting with stations + * arrive before we completed processing the adding of + * station + */ + if (ret != IL_INVALID_STATION && + (!(il->stations[ret].used & IL_STA_UCODE_ACTIVE) || + ((il->stations[ret].used & IL_STA_UCODE_ACTIVE) && + (il->stations[ret].used & IL_STA_UCODE_INPROGRESS)))) { + IL_ERR("Requested station info for sta %d before ready.\n", + ret); + ret = IL_INVALID_STATION; + } + spin_unlock_irqrestore(&il->sta_lock, flags); + return ret; +} + +static int +il4965_get_ra_sta_id(struct il_priv *il, struct ieee80211_hdr *hdr) +{ + if (il->iw_mode == NL80211_IFTYPE_STATION) + return IL_AP_ID; + else { + u8 *da = ieee80211_get_DA(hdr); + + return il4965_find_station(il, da); + } +} + +static inline u32 +il4965_get_scd_ssn(struct il4965_tx_resp *tx_resp) +{ + return le32_to_cpup(&tx_resp->u.status + + tx_resp->frame_count) & IEEE80211_MAX_SN; +} + +static inline u32 +il4965_tx_status_to_mac80211(u32 status) +{ + status &= TX_STATUS_MSK; + + switch (status) { + case TX_STATUS_SUCCESS: + case TX_STATUS_DIRECT_DONE: + return IEEE80211_TX_STAT_ACK; + case TX_STATUS_FAIL_DEST_PS: + return IEEE80211_TX_STAT_TX_FILTERED; + default: + return 0; + } +} + +/** + * il4965_tx_status_reply_tx - Handle Tx response for frames in aggregation queue + */ +static int +il4965_tx_status_reply_tx(struct il_priv *il, struct il_ht_agg *agg, + struct il4965_tx_resp *tx_resp, int txq_id, + u16 start_idx) +{ + u16 status; + struct agg_tx_status *frame_status = tx_resp->u.agg_status; + struct ieee80211_tx_info *info = NULL; + struct ieee80211_hdr *hdr = NULL; + u32 rate_n_flags = le32_to_cpu(tx_resp->rate_n_flags); + int i, sh, idx; + u16 seq; + if (agg->wait_for_ba) + D_TX_REPLY("got tx response w/o block-ack\n"); + + agg->frame_count = tx_resp->frame_count; + agg->start_idx = start_idx; + agg->rate_n_flags = rate_n_flags; + agg->bitmap = 0; + + /* num frames attempted by Tx command */ + if (agg->frame_count == 1) { + /* Only one frame was attempted; no block-ack will arrive */ + status = le16_to_cpu(frame_status[0].status); + idx = start_idx; + + D_TX_REPLY("FrameCnt = %d, StartIdx=%d idx=%d\n", + agg->frame_count, agg->start_idx, idx); + + info = IEEE80211_SKB_CB(il->txq[txq_id].skbs[idx]); + info->status.rates[0].count = tx_resp->failure_frame + 1; + info->flags &= ~IEEE80211_TX_CTL_AMPDU; + info->flags |= il4965_tx_status_to_mac80211(status); + il4965_hwrate_to_tx_control(il, rate_n_flags, info); + + D_TX_REPLY("1 Frame 0x%x failure :%d\n", status & 0xff, + tx_resp->failure_frame); + D_TX_REPLY("Rate Info rate_n_flags=%x\n", rate_n_flags); + + agg->wait_for_ba = 0; + } else { + /* Two or more frames were attempted; expect block-ack */ + u64 bitmap = 0; + int start = agg->start_idx; + struct sk_buff *skb; + + /* Construct bit-map of pending frames within Tx win */ + for (i = 0; i < agg->frame_count; i++) { + u16 sc; + status = le16_to_cpu(frame_status[i].status); + seq = le16_to_cpu(frame_status[i].sequence); + idx = SEQ_TO_IDX(seq); + txq_id = SEQ_TO_QUEUE(seq); + + if (status & + (AGG_TX_STATE_FEW_BYTES_MSK | + AGG_TX_STATE_ABORT_MSK)) + continue; + + D_TX_REPLY("FrameCnt = %d, txq_id=%d idx=%d\n", + agg->frame_count, txq_id, idx); + + skb = il->txq[txq_id].skbs[idx]; + if (WARN_ON_ONCE(skb == NULL)) + return -1; + hdr = (struct ieee80211_hdr *) skb->data; + + sc = le16_to_cpu(hdr->seq_ctrl); + if (idx != (IEEE80211_SEQ_TO_SN(sc) & 0xff)) { + IL_ERR("BUG_ON idx doesn't match seq control" + " idx=%d, seq_idx=%d, seq=%d\n", idx, + IEEE80211_SEQ_TO_SN(sc), hdr->seq_ctrl); + return -1; + } + + D_TX_REPLY("AGG Frame i=%d idx %d seq=%d\n", i, idx, + IEEE80211_SEQ_TO_SN(sc)); + + sh = idx - start; + if (sh > 64) { + sh = (start - idx) + 0xff; + bitmap = bitmap << sh; + sh = 0; + start = idx; + } else if (sh < -64) + sh = 0xff - (start - idx); + else if (sh < 0) { + sh = start - idx; + start = idx; + bitmap = bitmap << sh; + sh = 0; + } + bitmap |= 1ULL << sh; + D_TX_REPLY("start=%d bitmap=0x%llx\n", start, + (unsigned long long)bitmap); + } + + agg->bitmap = bitmap; + agg->start_idx = start; + D_TX_REPLY("Frames %d start_idx=%d bitmap=0x%llx\n", + agg->frame_count, agg->start_idx, + (unsigned long long)agg->bitmap); + + if (bitmap) + agg->wait_for_ba = 1; + } + return 0; +} + +/** + * il4965_hdl_tx - Handle standard (non-aggregation) Tx response + */ +static void +il4965_hdl_tx(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + u16 sequence = le16_to_cpu(pkt->hdr.sequence); + int txq_id = SEQ_TO_QUEUE(sequence); + int idx = SEQ_TO_IDX(sequence); + struct il_tx_queue *txq = &il->txq[txq_id]; + struct sk_buff *skb; + struct ieee80211_hdr *hdr; + struct ieee80211_tx_info *info; + struct il4965_tx_resp *tx_resp = (void *)&pkt->u.raw[0]; + u32 status = le32_to_cpu(tx_resp->u.status); + int uninitialized_var(tid); + int sta_id; + int freed; + u8 *qc = NULL; + unsigned long flags; + + if (idx >= txq->q.n_bd || il_queue_used(&txq->q, idx) == 0) { + IL_ERR("Read idx for DMA queue txq_id (%d) idx %d " + "is out of range [0-%d] %d %d\n", txq_id, idx, + txq->q.n_bd, txq->q.write_ptr, txq->q.read_ptr); + return; + } + + txq->time_stamp = jiffies; + + skb = txq->skbs[txq->q.read_ptr]; + info = IEEE80211_SKB_CB(skb); + memset(&info->status, 0, sizeof(info->status)); + + hdr = (struct ieee80211_hdr *) skb->data; + if (ieee80211_is_data_qos(hdr->frame_control)) { + qc = ieee80211_get_qos_ctl(hdr); + tid = qc[0] & 0xf; + } + + sta_id = il4965_get_ra_sta_id(il, hdr); + if (txq->sched_retry && unlikely(sta_id == IL_INVALID_STATION)) { + IL_ERR("Station not known\n"); + return; + } + + spin_lock_irqsave(&il->sta_lock, flags); + if (txq->sched_retry) { + const u32 scd_ssn = il4965_get_scd_ssn(tx_resp); + struct il_ht_agg *agg = NULL; + WARN_ON(!qc); + + agg = &il->stations[sta_id].tid[tid].agg; + + il4965_tx_status_reply_tx(il, agg, tx_resp, txq_id, idx); + + /* check if BAR is needed */ + if (tx_resp->frame_count == 1 && + !il4965_is_tx_success(status)) + info->flags |= IEEE80211_TX_STAT_AMPDU_NO_BACK; + + if (txq->q.read_ptr != (scd_ssn & 0xff)) { + idx = il_queue_dec_wrap(scd_ssn & 0xff, txq->q.n_bd); + D_TX_REPLY("Retry scheduler reclaim scd_ssn " + "%d idx %d\n", scd_ssn, idx); + freed = il4965_tx_queue_reclaim(il, txq_id, idx); + if (qc) + il4965_free_tfds_in_queue(il, sta_id, tid, + freed); + + if (il->mac80211_registered && + il_queue_space(&txq->q) > txq->q.low_mark && + agg->state != IL_EMPTYING_HW_QUEUE_DELBA) + il_wake_queue(il, txq); + } + } else { + info->status.rates[0].count = tx_resp->failure_frame + 1; + info->flags |= il4965_tx_status_to_mac80211(status); + il4965_hwrate_to_tx_control(il, + le32_to_cpu(tx_resp->rate_n_flags), + info); + + D_TX_REPLY("TXQ %d status %s (0x%08x) " + "rate_n_flags 0x%x retries %d\n", txq_id, + il4965_get_tx_fail_reason(status), status, + le32_to_cpu(tx_resp->rate_n_flags), + tx_resp->failure_frame); + + freed = il4965_tx_queue_reclaim(il, txq_id, idx); + if (qc && likely(sta_id != IL_INVALID_STATION)) + il4965_free_tfds_in_queue(il, sta_id, tid, freed); + else if (sta_id == IL_INVALID_STATION) + D_TX_REPLY("Station not known\n"); + + if (il->mac80211_registered && + il_queue_space(&txq->q) > txq->q.low_mark) + il_wake_queue(il, txq); + } + if (qc && likely(sta_id != IL_INVALID_STATION)) + il4965_txq_check_empty(il, sta_id, tid, txq_id); + + il4965_check_abort_status(il, tx_resp->frame_count, status); + + spin_unlock_irqrestore(&il->sta_lock, flags); +} + +/** + * translate ucode response to mac80211 tx status control values + */ +void +il4965_hwrate_to_tx_control(struct il_priv *il, u32 rate_n_flags, + struct ieee80211_tx_info *info) +{ + struct ieee80211_tx_rate *r = &info->status.rates[0]; + + info->status.antenna = + ((rate_n_flags & RATE_MCS_ANT_ABC_MSK) >> RATE_MCS_ANT_POS); + if (rate_n_flags & RATE_MCS_HT_MSK) + r->flags |= IEEE80211_TX_RC_MCS; + if (rate_n_flags & RATE_MCS_GF_MSK) + r->flags |= IEEE80211_TX_RC_GREEN_FIELD; + if (rate_n_flags & RATE_MCS_HT40_MSK) + r->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; + if (rate_n_flags & RATE_MCS_DUP_MSK) + r->flags |= IEEE80211_TX_RC_DUP_DATA; + if (rate_n_flags & RATE_MCS_SGI_MSK) + r->flags |= IEEE80211_TX_RC_SHORT_GI; + r->idx = il4965_hwrate_to_mac80211_idx(rate_n_flags, info->band); +} + +/** + * il4965_hdl_compressed_ba - Handler for N_COMPRESSED_BA + * + * Handles block-acknowledge notification from device, which reports success + * of frames sent via aggregation. + */ +static void +il4965_hdl_compressed_ba(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il_compressed_ba_resp *ba_resp = &pkt->u.compressed_ba; + struct il_tx_queue *txq = NULL; + struct il_ht_agg *agg; + int idx; + int sta_id; + int tid; + unsigned long flags; + + /* "flow" corresponds to Tx queue */ + u16 scd_flow = le16_to_cpu(ba_resp->scd_flow); + + /* "ssn" is start of block-ack Tx win, corresponds to idx + * (in Tx queue's circular buffer) of first TFD/frame in win */ + u16 ba_resp_scd_ssn = le16_to_cpu(ba_resp->scd_ssn); + + if (scd_flow >= il->hw_params.max_txq_num) { + IL_ERR("BUG_ON scd_flow is bigger than number of queues\n"); + return; + } + + txq = &il->txq[scd_flow]; + sta_id = ba_resp->sta_id; + tid = ba_resp->tid; + agg = &il->stations[sta_id].tid[tid].agg; + if (unlikely(agg->txq_id != scd_flow)) { + /* + * FIXME: this is a uCode bug which need to be addressed, + * log the information and return for now! + * since it is possible happen very often and in order + * not to fill the syslog, don't enable the logging by default + */ + D_TX_REPLY("BA scd_flow %d does not match txq_id %d\n", + scd_flow, agg->txq_id); + return; + } + + /* Find idx just before block-ack win */ + idx = il_queue_dec_wrap(ba_resp_scd_ssn & 0xff, txq->q.n_bd); + + spin_lock_irqsave(&il->sta_lock, flags); + + D_TX_REPLY("N_COMPRESSED_BA [%d] Received from %pM, " "sta_id = %d\n", + agg->wait_for_ba, (u8 *) &ba_resp->sta_addr_lo32, + ba_resp->sta_id); + D_TX_REPLY("TID = %d, SeqCtl = %d, bitmap = 0x%llx," "scd_flow = " + "%d, scd_ssn = %d\n", ba_resp->tid, ba_resp->seq_ctl, + (unsigned long long)le64_to_cpu(ba_resp->bitmap), + ba_resp->scd_flow, ba_resp->scd_ssn); + D_TX_REPLY("DAT start_idx = %d, bitmap = 0x%llx\n", agg->start_idx, + (unsigned long long)agg->bitmap); + + /* Update driver's record of ACK vs. not for each frame in win */ + il4965_tx_status_reply_compressed_ba(il, agg, ba_resp); + + /* Release all TFDs before the SSN, i.e. all TFDs in front of + * block-ack win (we assume that they've been successfully + * transmitted ... if not, it's too late anyway). */ + if (txq->q.read_ptr != (ba_resp_scd_ssn & 0xff)) { + /* calculate mac80211 ampdu sw queue to wake */ + int freed = il4965_tx_queue_reclaim(il, scd_flow, idx); + il4965_free_tfds_in_queue(il, sta_id, tid, freed); + + if (il_queue_space(&txq->q) > txq->q.low_mark && + il->mac80211_registered && + agg->state != IL_EMPTYING_HW_QUEUE_DELBA) + il_wake_queue(il, txq); + + il4965_txq_check_empty(il, sta_id, tid, scd_flow); + } + + spin_unlock_irqrestore(&il->sta_lock, flags); +} + +#ifdef CPTCFG_IWLEGACY_DEBUG +const char * +il4965_get_tx_fail_reason(u32 status) +{ +#define TX_STATUS_FAIL(x) case TX_STATUS_FAIL_ ## x: return #x +#define TX_STATUS_POSTPONE(x) case TX_STATUS_POSTPONE_ ## x: return #x + + switch (status & TX_STATUS_MSK) { + case TX_STATUS_SUCCESS: + return "SUCCESS"; + TX_STATUS_POSTPONE(DELAY); + TX_STATUS_POSTPONE(FEW_BYTES); + TX_STATUS_POSTPONE(QUIET_PERIOD); + TX_STATUS_POSTPONE(CALC_TTAK); + TX_STATUS_FAIL(INTERNAL_CROSSED_RETRY); + TX_STATUS_FAIL(SHORT_LIMIT); + TX_STATUS_FAIL(LONG_LIMIT); + TX_STATUS_FAIL(FIFO_UNDERRUN); + TX_STATUS_FAIL(DRAIN_FLOW); + TX_STATUS_FAIL(RFKILL_FLUSH); + TX_STATUS_FAIL(LIFE_EXPIRE); + TX_STATUS_FAIL(DEST_PS); + TX_STATUS_FAIL(HOST_ABORTED); + TX_STATUS_FAIL(BT_RETRY); + TX_STATUS_FAIL(STA_INVALID); + TX_STATUS_FAIL(FRAG_DROPPED); + TX_STATUS_FAIL(TID_DISABLE); + TX_STATUS_FAIL(FIFO_FLUSHED); + TX_STATUS_FAIL(INSUFFICIENT_CF_POLL); + TX_STATUS_FAIL(PASSIVE_NO_RX); + TX_STATUS_FAIL(NO_BEACON_ON_RADAR); + } + + return "UNKNOWN"; + +#undef TX_STATUS_FAIL +#undef TX_STATUS_POSTPONE +} +#endif /* CPTCFG_IWLEGACY_DEBUG */ + +static struct il_link_quality_cmd * +il4965_sta_alloc_lq(struct il_priv *il, u8 sta_id) +{ + int i, r; + struct il_link_quality_cmd *link_cmd; + u32 rate_flags = 0; + __le32 rate_n_flags; + + link_cmd = kzalloc(sizeof(struct il_link_quality_cmd), GFP_KERNEL); + if (!link_cmd) { + IL_ERR("Unable to allocate memory for LQ cmd.\n"); + return NULL; + } + /* Set up the rate scaling to start at selected rate, fall back + * all the way down to 1M in IEEE order, and then spin on 1M */ + if (il->band == IEEE80211_BAND_5GHZ) + r = RATE_6M_IDX; + else + r = RATE_1M_IDX; + + if (r >= IL_FIRST_CCK_RATE && r <= IL_LAST_CCK_RATE) + rate_flags |= RATE_MCS_CCK_MSK; + + rate_flags |= + il4965_first_antenna(il->hw_params. + valid_tx_ant) << RATE_MCS_ANT_POS; + rate_n_flags = cpu_to_le32(il_rates[r].plcp | rate_flags); + for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) + link_cmd->rs_table[i].rate_n_flags = rate_n_flags; + + link_cmd->general_params.single_stream_ant_msk = + il4965_first_antenna(il->hw_params.valid_tx_ant); + + link_cmd->general_params.dual_stream_ant_msk = + il->hw_params.valid_tx_ant & ~il4965_first_antenna(il->hw_params. + valid_tx_ant); + if (!link_cmd->general_params.dual_stream_ant_msk) { + link_cmd->general_params.dual_stream_ant_msk = ANT_AB; + } else if (il4965_num_of_ant(il->hw_params.valid_tx_ant) == 2) { + link_cmd->general_params.dual_stream_ant_msk = + il->hw_params.valid_tx_ant; + } + + link_cmd->agg_params.agg_dis_start_th = LINK_QUAL_AGG_DISABLE_START_DEF; + link_cmd->agg_params.agg_time_limit = + cpu_to_le16(LINK_QUAL_AGG_TIME_LIMIT_DEF); + + link_cmd->sta_id = sta_id; + + return link_cmd; +} + +/* + * il4965_add_bssid_station - Add the special IBSS BSSID station + * + * Function sleeps. + */ +int +il4965_add_bssid_station(struct il_priv *il, const u8 *addr, u8 *sta_id_r) +{ + int ret; + u8 sta_id; + struct il_link_quality_cmd *link_cmd; + unsigned long flags; + + if (sta_id_r) + *sta_id_r = IL_INVALID_STATION; + + ret = il_add_station_common(il, addr, 0, NULL, &sta_id); + if (ret) { + IL_ERR("Unable to add station %pM\n", addr); + return ret; + } + + if (sta_id_r) + *sta_id_r = sta_id; + + spin_lock_irqsave(&il->sta_lock, flags); + il->stations[sta_id].used |= IL_STA_LOCAL; + spin_unlock_irqrestore(&il->sta_lock, flags); + + /* Set up default rate scaling table in device's station table */ + link_cmd = il4965_sta_alloc_lq(il, sta_id); + if (!link_cmd) { + IL_ERR("Unable to initialize rate scaling for station %pM.\n", + addr); + return -ENOMEM; + } + + ret = il_send_lq_cmd(il, link_cmd, CMD_SYNC, true); + if (ret) + IL_ERR("Link quality command failed (%d)\n", ret); + + spin_lock_irqsave(&il->sta_lock, flags); + il->stations[sta_id].lq = link_cmd; + spin_unlock_irqrestore(&il->sta_lock, flags); + + return 0; +} + +static int +il4965_static_wepkey_cmd(struct il_priv *il, bool send_if_empty) +{ + int i; + u8 buff[sizeof(struct il_wep_cmd) + + sizeof(struct il_wep_key) * WEP_KEYS_MAX]; + struct il_wep_cmd *wep_cmd = (struct il_wep_cmd *)buff; + size_t cmd_size = sizeof(struct il_wep_cmd); + struct il_host_cmd cmd = { + .id = C_WEPKEY, + .data = wep_cmd, + .flags = CMD_SYNC, + }; + bool not_empty = false; + + might_sleep(); + + memset(wep_cmd, 0, + cmd_size + (sizeof(struct il_wep_key) * WEP_KEYS_MAX)); + + for (i = 0; i < WEP_KEYS_MAX; i++) { + u8 key_size = il->_4965.wep_keys[i].key_size; + + wep_cmd->key[i].key_idx = i; + if (key_size) { + wep_cmd->key[i].key_offset = i; + not_empty = true; + } else + wep_cmd->key[i].key_offset = WEP_INVALID_OFFSET; + + wep_cmd->key[i].key_size = key_size; + memcpy(&wep_cmd->key[i].key[3], il->_4965.wep_keys[i].key, key_size); + } + + wep_cmd->global_key_type = WEP_KEY_WEP_TYPE; + wep_cmd->num_keys = WEP_KEYS_MAX; + + cmd_size += sizeof(struct il_wep_key) * WEP_KEYS_MAX; + cmd.len = cmd_size; + + if (not_empty || send_if_empty) + return il_send_cmd(il, &cmd); + else + return 0; +} + +int +il4965_restore_default_wep_keys(struct il_priv *il) +{ + lockdep_assert_held(&il->mutex); + + return il4965_static_wepkey_cmd(il, false); +} + +int +il4965_remove_default_wep_key(struct il_priv *il, + struct ieee80211_key_conf *keyconf) +{ + int ret; + int idx = keyconf->keyidx; + + lockdep_assert_held(&il->mutex); + + D_WEP("Removing default WEP key: idx=%d\n", idx); + + memset(&il->_4965.wep_keys[idx], 0, sizeof(struct il_wep_key)); + if (il_is_rfkill(il)) { + D_WEP("Not sending C_WEPKEY command due to RFKILL.\n"); + /* but keys in device are clear anyway so return success */ + return 0; + } + ret = il4965_static_wepkey_cmd(il, 1); + D_WEP("Remove default WEP key: idx=%d ret=%d\n", idx, ret); + + return ret; +} + +int +il4965_set_default_wep_key(struct il_priv *il, + struct ieee80211_key_conf *keyconf) +{ + int ret; + int len = keyconf->keylen; + int idx = keyconf->keyidx; + + lockdep_assert_held(&il->mutex); + + if (len != WEP_KEY_LEN_128 && len != WEP_KEY_LEN_64) { + D_WEP("Bad WEP key length %d\n", keyconf->keylen); + return -EINVAL; + } + + keyconf->flags &= ~IEEE80211_KEY_FLAG_GENERATE_IV; + keyconf->hw_key_idx = HW_KEY_DEFAULT; + il->stations[IL_AP_ID].keyinfo.cipher = keyconf->cipher; + + il->_4965.wep_keys[idx].key_size = len; + memcpy(&il->_4965.wep_keys[idx].key, &keyconf->key, len); + + ret = il4965_static_wepkey_cmd(il, false); + + D_WEP("Set default WEP key: len=%d idx=%d ret=%d\n", len, idx, ret); + return ret; +} + +static int +il4965_set_wep_dynamic_key_info(struct il_priv *il, + struct ieee80211_key_conf *keyconf, u8 sta_id) +{ + unsigned long flags; + __le16 key_flags = 0; + struct il_addsta_cmd sta_cmd; + + lockdep_assert_held(&il->mutex); + + keyconf->flags &= ~IEEE80211_KEY_FLAG_GENERATE_IV; + + key_flags |= (STA_KEY_FLG_WEP | STA_KEY_FLG_MAP_KEY_MSK); + key_flags |= cpu_to_le16(keyconf->keyidx << STA_KEY_FLG_KEYID_POS); + key_flags &= ~STA_KEY_FLG_INVALID; + + if (keyconf->keylen == WEP_KEY_LEN_128) + key_flags |= STA_KEY_FLG_KEY_SIZE_MSK; + + if (sta_id == il->hw_params.bcast_id) + key_flags |= STA_KEY_MULTICAST_MSK; + + spin_lock_irqsave(&il->sta_lock, flags); + + il->stations[sta_id].keyinfo.cipher = keyconf->cipher; + il->stations[sta_id].keyinfo.keylen = keyconf->keylen; + il->stations[sta_id].keyinfo.keyidx = keyconf->keyidx; + + memcpy(il->stations[sta_id].keyinfo.key, keyconf->key, keyconf->keylen); + + memcpy(&il->stations[sta_id].sta.key.key[3], keyconf->key, + keyconf->keylen); + + if ((il->stations[sta_id].sta.key. + key_flags & STA_KEY_FLG_ENCRYPT_MSK) == STA_KEY_FLG_NO_ENC) + il->stations[sta_id].sta.key.key_offset = + il_get_free_ucode_key_idx(il); + /* else, we are overriding an existing key => no need to allocated room + * in uCode. */ + + WARN(il->stations[sta_id].sta.key.key_offset == WEP_INVALID_OFFSET, + "no space for a new key"); + + il->stations[sta_id].sta.key.key_flags = key_flags; + il->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_KEY_MASK; + il->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + + memcpy(&sta_cmd, &il->stations[sta_id].sta, + sizeof(struct il_addsta_cmd)); + spin_unlock_irqrestore(&il->sta_lock, flags); + + return il_send_add_sta(il, &sta_cmd, CMD_SYNC); +} + +static int +il4965_set_ccmp_dynamic_key_info(struct il_priv *il, + struct ieee80211_key_conf *keyconf, u8 sta_id) +{ + unsigned long flags; + __le16 key_flags = 0; + struct il_addsta_cmd sta_cmd; + + lockdep_assert_held(&il->mutex); + + key_flags |= (STA_KEY_FLG_CCMP | STA_KEY_FLG_MAP_KEY_MSK); + key_flags |= cpu_to_le16(keyconf->keyidx << STA_KEY_FLG_KEYID_POS); + key_flags &= ~STA_KEY_FLG_INVALID; + + if (sta_id == il->hw_params.bcast_id) + key_flags |= STA_KEY_MULTICAST_MSK; + + keyconf->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + + spin_lock_irqsave(&il->sta_lock, flags); + il->stations[sta_id].keyinfo.cipher = keyconf->cipher; + il->stations[sta_id].keyinfo.keylen = keyconf->keylen; + + memcpy(il->stations[sta_id].keyinfo.key, keyconf->key, keyconf->keylen); + + memcpy(il->stations[sta_id].sta.key.key, keyconf->key, keyconf->keylen); + + if ((il->stations[sta_id].sta.key. + key_flags & STA_KEY_FLG_ENCRYPT_MSK) == STA_KEY_FLG_NO_ENC) + il->stations[sta_id].sta.key.key_offset = + il_get_free_ucode_key_idx(il); + /* else, we are overriding an existing key => no need to allocated room + * in uCode. */ + + WARN(il->stations[sta_id].sta.key.key_offset == WEP_INVALID_OFFSET, + "no space for a new key"); + + il->stations[sta_id].sta.key.key_flags = key_flags; + il->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_KEY_MASK; + il->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + + memcpy(&sta_cmd, &il->stations[sta_id].sta, + sizeof(struct il_addsta_cmd)); + spin_unlock_irqrestore(&il->sta_lock, flags); + + return il_send_add_sta(il, &sta_cmd, CMD_SYNC); +} + +static int +il4965_set_tkip_dynamic_key_info(struct il_priv *il, + struct ieee80211_key_conf *keyconf, u8 sta_id) +{ + unsigned long flags; + int ret = 0; + __le16 key_flags = 0; + + key_flags |= (STA_KEY_FLG_TKIP | STA_KEY_FLG_MAP_KEY_MSK); + key_flags |= cpu_to_le16(keyconf->keyidx << STA_KEY_FLG_KEYID_POS); + key_flags &= ~STA_KEY_FLG_INVALID; + + if (sta_id == il->hw_params.bcast_id) + key_flags |= STA_KEY_MULTICAST_MSK; + + keyconf->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + keyconf->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC; + + spin_lock_irqsave(&il->sta_lock, flags); + + il->stations[sta_id].keyinfo.cipher = keyconf->cipher; + il->stations[sta_id].keyinfo.keylen = 16; + + if ((il->stations[sta_id].sta.key. + key_flags & STA_KEY_FLG_ENCRYPT_MSK) == STA_KEY_FLG_NO_ENC) + il->stations[sta_id].sta.key.key_offset = + il_get_free_ucode_key_idx(il); + /* else, we are overriding an existing key => no need to allocated room + * in uCode. */ + + WARN(il->stations[sta_id].sta.key.key_offset == WEP_INVALID_OFFSET, + "no space for a new key"); + + il->stations[sta_id].sta.key.key_flags = key_flags; + + /* This copy is acutally not needed: we get the key with each TX */ + memcpy(il->stations[sta_id].keyinfo.key, keyconf->key, 16); + + memcpy(il->stations[sta_id].sta.key.key, keyconf->key, 16); + + spin_unlock_irqrestore(&il->sta_lock, flags); + + return ret; +} + +void +il4965_update_tkip_key(struct il_priv *il, struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, u32 iv32, u16 *phase1key) +{ + u8 sta_id; + unsigned long flags; + int i; + + if (il_scan_cancel(il)) { + /* cancel scan failed, just live w/ bad key and rely + briefly on SW decryption */ + return; + } + + sta_id = il_sta_id_or_broadcast(il, sta); + if (sta_id == IL_INVALID_STATION) + return; + + spin_lock_irqsave(&il->sta_lock, flags); + + il->stations[sta_id].sta.key.tkip_rx_tsc_byte2 = (u8) iv32; + + for (i = 0; i < 5; i++) + il->stations[sta_id].sta.key.tkip_rx_ttak[i] = + cpu_to_le16(phase1key[i]); + + il->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_KEY_MASK; + il->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + + il_send_add_sta(il, &il->stations[sta_id].sta, CMD_ASYNC); + + spin_unlock_irqrestore(&il->sta_lock, flags); +} + +int +il4965_remove_dynamic_key(struct il_priv *il, + struct ieee80211_key_conf *keyconf, u8 sta_id) +{ + unsigned long flags; + u16 key_flags; + u8 keyidx; + struct il_addsta_cmd sta_cmd; + + lockdep_assert_held(&il->mutex); + + il->_4965.key_mapping_keys--; + + spin_lock_irqsave(&il->sta_lock, flags); + key_flags = le16_to_cpu(il->stations[sta_id].sta.key.key_flags); + keyidx = (key_flags >> STA_KEY_FLG_KEYID_POS) & 0x3; + + D_WEP("Remove dynamic key: idx=%d sta=%d\n", keyconf->keyidx, sta_id); + + if (keyconf->keyidx != keyidx) { + /* We need to remove a key with idx different that the one + * in the uCode. This means that the key we need to remove has + * been replaced by another one with different idx. + * Don't do anything and return ok + */ + spin_unlock_irqrestore(&il->sta_lock, flags); + return 0; + } + + if (il->stations[sta_id].sta.key.key_flags & STA_KEY_FLG_INVALID) { + IL_WARN("Removing wrong key %d 0x%x\n", keyconf->keyidx, + key_flags); + spin_unlock_irqrestore(&il->sta_lock, flags); + return 0; + } + + if (!test_and_clear_bit + (il->stations[sta_id].sta.key.key_offset, &il->ucode_key_table)) + IL_ERR("idx %d not used in uCode key table.\n", + il->stations[sta_id].sta.key.key_offset); + memset(&il->stations[sta_id].keyinfo, 0, sizeof(struct il_hw_key)); + memset(&il->stations[sta_id].sta.key, 0, sizeof(struct il4965_keyinfo)); + il->stations[sta_id].sta.key.key_flags = + STA_KEY_FLG_NO_ENC | STA_KEY_FLG_INVALID; + il->stations[sta_id].sta.key.key_offset = keyconf->hw_key_idx; + il->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_KEY_MASK; + il->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + + if (il_is_rfkill(il)) { + D_WEP + ("Not sending C_ADD_STA command because RFKILL enabled.\n"); + spin_unlock_irqrestore(&il->sta_lock, flags); + return 0; + } + memcpy(&sta_cmd, &il->stations[sta_id].sta, + sizeof(struct il_addsta_cmd)); + spin_unlock_irqrestore(&il->sta_lock, flags); + + return il_send_add_sta(il, &sta_cmd, CMD_SYNC); +} + +int +il4965_set_dynamic_key(struct il_priv *il, struct ieee80211_key_conf *keyconf, + u8 sta_id) +{ + int ret; + + lockdep_assert_held(&il->mutex); + + il->_4965.key_mapping_keys++; + keyconf->hw_key_idx = HW_KEY_DYNAMIC; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + ret = + il4965_set_ccmp_dynamic_key_info(il, keyconf, sta_id); + break; + case WLAN_CIPHER_SUITE_TKIP: + ret = + il4965_set_tkip_dynamic_key_info(il, keyconf, sta_id); + break; + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + ret = il4965_set_wep_dynamic_key_info(il, keyconf, sta_id); + break; + default: + IL_ERR("Unknown alg: %s cipher = %x\n", __func__, + keyconf->cipher); + ret = -EINVAL; + } + + D_WEP("Set dynamic key: cipher=%x len=%d idx=%d sta=%d ret=%d\n", + keyconf->cipher, keyconf->keylen, keyconf->keyidx, sta_id, ret); + + return ret; +} + +/** + * il4965_alloc_bcast_station - add broadcast station into driver's station table. + * + * This adds the broadcast station into the driver's station table + * and marks it driver active, so that it will be restored to the + * device at the next best time. + */ +int +il4965_alloc_bcast_station(struct il_priv *il) +{ + struct il_link_quality_cmd *link_cmd; + unsigned long flags; + u8 sta_id; + + spin_lock_irqsave(&il->sta_lock, flags); + sta_id = il_prep_station(il, il_bcast_addr, false, NULL); + if (sta_id == IL_INVALID_STATION) { + IL_ERR("Unable to prepare broadcast station\n"); + spin_unlock_irqrestore(&il->sta_lock, flags); + + return -EINVAL; + } + + il->stations[sta_id].used |= IL_STA_DRIVER_ACTIVE; + il->stations[sta_id].used |= IL_STA_BCAST; + spin_unlock_irqrestore(&il->sta_lock, flags); + + link_cmd = il4965_sta_alloc_lq(il, sta_id); + if (!link_cmd) { + IL_ERR + ("Unable to initialize rate scaling for bcast station.\n"); + return -ENOMEM; + } + + spin_lock_irqsave(&il->sta_lock, flags); + il->stations[sta_id].lq = link_cmd; + spin_unlock_irqrestore(&il->sta_lock, flags); + + return 0; +} + +/** + * il4965_update_bcast_station - update broadcast station's LQ command + * + * Only used by iwl4965. Placed here to have all bcast station management + * code together. + */ +static int +il4965_update_bcast_station(struct il_priv *il) +{ + unsigned long flags; + struct il_link_quality_cmd *link_cmd; + u8 sta_id = il->hw_params.bcast_id; + + link_cmd = il4965_sta_alloc_lq(il, sta_id); + if (!link_cmd) { + IL_ERR("Unable to initialize rate scaling for bcast sta.\n"); + return -ENOMEM; + } + + spin_lock_irqsave(&il->sta_lock, flags); + if (il->stations[sta_id].lq) + kfree(il->stations[sta_id].lq); + else + D_INFO("Bcast sta rate scaling has not been initialized.\n"); + il->stations[sta_id].lq = link_cmd; + spin_unlock_irqrestore(&il->sta_lock, flags); + + return 0; +} + +int +il4965_update_bcast_stations(struct il_priv *il) +{ + return il4965_update_bcast_station(il); +} + +/** + * il4965_sta_tx_modify_enable_tid - Enable Tx for this TID in station table + */ +int +il4965_sta_tx_modify_enable_tid(struct il_priv *il, int sta_id, int tid) +{ + unsigned long flags; + struct il_addsta_cmd sta_cmd; + + lockdep_assert_held(&il->mutex); + + /* Remove "disable" flag, to enable Tx for this TID */ + spin_lock_irqsave(&il->sta_lock, flags); + il->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_TID_DISABLE_TX; + il->stations[sta_id].sta.tid_disable_tx &= cpu_to_le16(~(1 << tid)); + il->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + memcpy(&sta_cmd, &il->stations[sta_id].sta, + sizeof(struct il_addsta_cmd)); + spin_unlock_irqrestore(&il->sta_lock, flags); + + return il_send_add_sta(il, &sta_cmd, CMD_SYNC); +} + +int +il4965_sta_rx_agg_start(struct il_priv *il, struct ieee80211_sta *sta, int tid, + u16 ssn) +{ + unsigned long flags; + int sta_id; + struct il_addsta_cmd sta_cmd; + + lockdep_assert_held(&il->mutex); + + sta_id = il_sta_id(sta); + if (sta_id == IL_INVALID_STATION) + return -ENXIO; + + spin_lock_irqsave(&il->sta_lock, flags); + il->stations[sta_id].sta.station_flags_msk = 0; + il->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_ADDBA_TID_MSK; + il->stations[sta_id].sta.add_immediate_ba_tid = (u8) tid; + il->stations[sta_id].sta.add_immediate_ba_ssn = cpu_to_le16(ssn); + il->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + memcpy(&sta_cmd, &il->stations[sta_id].sta, + sizeof(struct il_addsta_cmd)); + spin_unlock_irqrestore(&il->sta_lock, flags); + + return il_send_add_sta(il, &sta_cmd, CMD_SYNC); +} + +int +il4965_sta_rx_agg_stop(struct il_priv *il, struct ieee80211_sta *sta, int tid) +{ + unsigned long flags; + int sta_id; + struct il_addsta_cmd sta_cmd; + + lockdep_assert_held(&il->mutex); + + sta_id = il_sta_id(sta); + if (sta_id == IL_INVALID_STATION) { + IL_ERR("Invalid station for AGG tid %d\n", tid); + return -ENXIO; + } + + spin_lock_irqsave(&il->sta_lock, flags); + il->stations[sta_id].sta.station_flags_msk = 0; + il->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_DELBA_TID_MSK; + il->stations[sta_id].sta.remove_immediate_ba_tid = (u8) tid; + il->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + memcpy(&sta_cmd, &il->stations[sta_id].sta, + sizeof(struct il_addsta_cmd)); + spin_unlock_irqrestore(&il->sta_lock, flags); + + return il_send_add_sta(il, &sta_cmd, CMD_SYNC); +} + +void +il4965_sta_modify_sleep_tx_count(struct il_priv *il, int sta_id, int cnt) +{ + unsigned long flags; + + spin_lock_irqsave(&il->sta_lock, flags); + il->stations[sta_id].sta.station_flags |= STA_FLG_PWR_SAVE_MSK; + il->stations[sta_id].sta.station_flags_msk = STA_FLG_PWR_SAVE_MSK; + il->stations[sta_id].sta.sta.modify_mask = + STA_MODIFY_SLEEP_TX_COUNT_MSK; + il->stations[sta_id].sta.sleep_tx_count = cpu_to_le16(cnt); + il->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + il_send_add_sta(il, &il->stations[sta_id].sta, CMD_ASYNC); + spin_unlock_irqrestore(&il->sta_lock, flags); + +} + +void +il4965_update_chain_flags(struct il_priv *il) +{ + if (il->ops->set_rxon_chain) { + il->ops->set_rxon_chain(il); + if (il->active.rx_chain != il->staging.rx_chain) + il_commit_rxon(il); + } +} + +static void +il4965_clear_free_frames(struct il_priv *il) +{ + struct list_head *element; + + D_INFO("%d frames on pre-allocated heap on clear.\n", il->frames_count); + + while (!list_empty(&il->free_frames)) { + element = il->free_frames.next; + list_del(element); + kfree(list_entry(element, struct il_frame, list)); + il->frames_count--; + } + + if (il->frames_count) { + IL_WARN("%d frames still in use. Did we lose one?\n", + il->frames_count); + il->frames_count = 0; + } +} + +static struct il_frame * +il4965_get_free_frame(struct il_priv *il) +{ + struct il_frame *frame; + struct list_head *element; + if (list_empty(&il->free_frames)) { + frame = kzalloc(sizeof(*frame), GFP_KERNEL); + if (!frame) { + IL_ERR("Could not allocate frame!\n"); + return NULL; + } + + il->frames_count++; + return frame; + } + + element = il->free_frames.next; + list_del(element); + return list_entry(element, struct il_frame, list); +} + +static void +il4965_free_frame(struct il_priv *il, struct il_frame *frame) +{ + memset(frame, 0, sizeof(*frame)); + list_add(&frame->list, &il->free_frames); +} + +static u32 +il4965_fill_beacon_frame(struct il_priv *il, struct ieee80211_hdr *hdr, + int left) +{ + lockdep_assert_held(&il->mutex); + + if (!il->beacon_skb) + return 0; + + if (il->beacon_skb->len > left) + return 0; + + memcpy(hdr, il->beacon_skb->data, il->beacon_skb->len); + + return il->beacon_skb->len; +} + +/* Parse the beacon frame to find the TIM element and set tim_idx & tim_size */ +static void +il4965_set_beacon_tim(struct il_priv *il, + struct il_tx_beacon_cmd *tx_beacon_cmd, u8 * beacon, + u32 frame_size) +{ + u16 tim_idx; + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)beacon; + + /* + * The idx is relative to frame start but we start looking at the + * variable-length part of the beacon. + */ + tim_idx = mgmt->u.beacon.variable - beacon; + + /* Parse variable-length elements of beacon to find WLAN_EID_TIM */ + while ((tim_idx < (frame_size - 2)) && + (beacon[tim_idx] != WLAN_EID_TIM)) + tim_idx += beacon[tim_idx + 1] + 2; + + /* If TIM field was found, set variables */ + if ((tim_idx < (frame_size - 1)) && (beacon[tim_idx] == WLAN_EID_TIM)) { + tx_beacon_cmd->tim_idx = cpu_to_le16(tim_idx); + tx_beacon_cmd->tim_size = beacon[tim_idx + 1]; + } else + IL_WARN("Unable to find TIM Element in beacon\n"); +} + +static unsigned int +il4965_hw_get_beacon_cmd(struct il_priv *il, struct il_frame *frame) +{ + struct il_tx_beacon_cmd *tx_beacon_cmd; + u32 frame_size; + u32 rate_flags; + u32 rate; + /* + * We have to set up the TX command, the TX Beacon command, and the + * beacon contents. + */ + + lockdep_assert_held(&il->mutex); + + if (!il->beacon_enabled) { + IL_ERR("Trying to build beacon without beaconing enabled\n"); + return 0; + } + + /* Initialize memory */ + tx_beacon_cmd = &frame->u.beacon; + memset(tx_beacon_cmd, 0, sizeof(*tx_beacon_cmd)); + + /* Set up TX beacon contents */ + frame_size = + il4965_fill_beacon_frame(il, tx_beacon_cmd->frame, + sizeof(frame->u) - sizeof(*tx_beacon_cmd)); + if (WARN_ON_ONCE(frame_size > MAX_MPDU_SIZE)) + return 0; + if (!frame_size) + return 0; + + /* Set up TX command fields */ + tx_beacon_cmd->tx.len = cpu_to_le16((u16) frame_size); + tx_beacon_cmd->tx.sta_id = il->hw_params.bcast_id; + tx_beacon_cmd->tx.stop_time.life_time = TX_CMD_LIFE_TIME_INFINITE; + tx_beacon_cmd->tx.tx_flags = + TX_CMD_FLG_SEQ_CTL_MSK | TX_CMD_FLG_TSF_MSK | + TX_CMD_FLG_STA_RATE_MSK; + + /* Set up TX beacon command fields */ + il4965_set_beacon_tim(il, tx_beacon_cmd, (u8 *) tx_beacon_cmd->frame, + frame_size); + + /* Set up packet rate and flags */ + rate = il_get_lowest_plcp(il); + il4965_toggle_tx_ant(il, &il->mgmt_tx_ant, il->hw_params.valid_tx_ant); + rate_flags = BIT(il->mgmt_tx_ant) << RATE_MCS_ANT_POS; + if ((rate >= IL_FIRST_CCK_RATE) && (rate <= IL_LAST_CCK_RATE)) + rate_flags |= RATE_MCS_CCK_MSK; + tx_beacon_cmd->tx.rate_n_flags = cpu_to_le32(rate | rate_flags); + + return sizeof(*tx_beacon_cmd) + frame_size; +} + +int +il4965_send_beacon_cmd(struct il_priv *il) +{ + struct il_frame *frame; + unsigned int frame_size; + int rc; + + frame = il4965_get_free_frame(il); + if (!frame) { + IL_ERR("Could not obtain free frame buffer for beacon " + "command.\n"); + return -ENOMEM; + } + + frame_size = il4965_hw_get_beacon_cmd(il, frame); + if (!frame_size) { + IL_ERR("Error configuring the beacon command\n"); + il4965_free_frame(il, frame); + return -EINVAL; + } + + rc = il_send_cmd_pdu(il, C_TX_BEACON, frame_size, &frame->u.cmd[0]); + + il4965_free_frame(il, frame); + + return rc; +} + +static inline dma_addr_t +il4965_tfd_tb_get_addr(struct il_tfd *tfd, u8 idx) +{ + struct il_tfd_tb *tb = &tfd->tbs[idx]; + + dma_addr_t addr = get_unaligned_le32(&tb->lo); + if (sizeof(dma_addr_t) > sizeof(u32)) + addr |= + ((dma_addr_t) (le16_to_cpu(tb->hi_n_len) & 0xF) << 16) << + 16; + + return addr; +} + +static inline u16 +il4965_tfd_tb_get_len(struct il_tfd *tfd, u8 idx) +{ + struct il_tfd_tb *tb = &tfd->tbs[idx]; + + return le16_to_cpu(tb->hi_n_len) >> 4; +} + +static inline void +il4965_tfd_set_tb(struct il_tfd *tfd, u8 idx, dma_addr_t addr, u16 len) +{ + struct il_tfd_tb *tb = &tfd->tbs[idx]; + u16 hi_n_len = len << 4; + + put_unaligned_le32(addr, &tb->lo); + if (sizeof(dma_addr_t) > sizeof(u32)) + hi_n_len |= ((addr >> 16) >> 16) & 0xF; + + tb->hi_n_len = cpu_to_le16(hi_n_len); + + tfd->num_tbs = idx + 1; +} + +static inline u8 +il4965_tfd_get_num_tbs(struct il_tfd *tfd) +{ + return tfd->num_tbs & 0x1f; +} + +/** + * il4965_hw_txq_free_tfd - Free all chunks referenced by TFD [txq->q.read_ptr] + * @il - driver ilate data + * @txq - tx queue + * + * Does NOT advance any TFD circular buffer read/write idxes + * Does NOT free the TFD itself (which is within circular buffer) + */ +void +il4965_hw_txq_free_tfd(struct il_priv *il, struct il_tx_queue *txq) +{ + struct il_tfd *tfd_tmp = (struct il_tfd *)txq->tfds; + struct il_tfd *tfd; + struct pci_dev *dev = il->pci_dev; + int idx = txq->q.read_ptr; + int i; + int num_tbs; + + tfd = &tfd_tmp[idx]; + + /* Sanity check on number of chunks */ + num_tbs = il4965_tfd_get_num_tbs(tfd); + + if (num_tbs >= IL_NUM_OF_TBS) { + IL_ERR("Too many chunks: %i\n", num_tbs); + /* @todo issue fatal error, it is quite serious situation */ + return; + } + + /* Unmap tx_cmd */ + if (num_tbs) + pci_unmap_single(dev, dma_unmap_addr(&txq->meta[idx], mapping), + dma_unmap_len(&txq->meta[idx], len), + PCI_DMA_BIDIRECTIONAL); + + /* Unmap chunks, if any. */ + for (i = 1; i < num_tbs; i++) + pci_unmap_single(dev, il4965_tfd_tb_get_addr(tfd, i), + il4965_tfd_tb_get_len(tfd, i), + PCI_DMA_TODEVICE); + + /* free SKB */ + if (txq->skbs) { + struct sk_buff *skb = txq->skbs[txq->q.read_ptr]; + + /* can be called from irqs-disabled context */ + if (skb) { + dev_kfree_skb_any(skb); + txq->skbs[txq->q.read_ptr] = NULL; + } + } +} + +int +il4965_hw_txq_attach_buf_to_tfd(struct il_priv *il, struct il_tx_queue *txq, + dma_addr_t addr, u16 len, u8 reset, u8 pad) +{ + struct il_queue *q; + struct il_tfd *tfd, *tfd_tmp; + u32 num_tbs; + + q = &txq->q; + tfd_tmp = (struct il_tfd *)txq->tfds; + tfd = &tfd_tmp[q->write_ptr]; + + if (reset) + memset(tfd, 0, sizeof(*tfd)); + + num_tbs = il4965_tfd_get_num_tbs(tfd); + + /* Each TFD can point to a maximum 20 Tx buffers */ + if (num_tbs >= IL_NUM_OF_TBS) { + IL_ERR("Error can not send more than %d chunks\n", + IL_NUM_OF_TBS); + return -EINVAL; + } + + BUG_ON(addr & ~DMA_BIT_MASK(36)); + if (unlikely(addr & ~IL_TX_DMA_MASK)) + IL_ERR("Unaligned address = %llx\n", (unsigned long long)addr); + + il4965_tfd_set_tb(tfd, num_tbs, addr, len); + + return 0; +} + +/* + * Tell nic where to find circular buffer of Tx Frame Descriptors for + * given Tx queue, and enable the DMA channel used for that queue. + * + * 4965 supports up to 16 Tx queues in DRAM, mapped to up to 8 Tx DMA + * channels supported in hardware. + */ +int +il4965_hw_tx_queue_init(struct il_priv *il, struct il_tx_queue *txq) +{ + int txq_id = txq->q.id; + + /* Circular buffer (TFD queue in DRAM) physical base address */ + il_wr(il, FH49_MEM_CBBC_QUEUE(txq_id), txq->q.dma_addr >> 8); + + return 0; +} + +/****************************************************************************** + * + * Generic RX handler implementations + * + ******************************************************************************/ +static void +il4965_hdl_alive(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il_alive_resp *palive; + struct delayed_work *pwork; + + palive = &pkt->u.alive_frame; + + D_INFO("Alive ucode status 0x%08X revision " "0x%01X 0x%01X\n", + palive->is_valid, palive->ver_type, palive->ver_subtype); + + if (palive->ver_subtype == INITIALIZE_SUBTYPE) { + D_INFO("Initialization Alive received.\n"); + memcpy(&il->card_alive_init, &pkt->u.alive_frame, + sizeof(struct il_init_alive_resp)); + pwork = &il->init_alive_start; + } else { + D_INFO("Runtime Alive received.\n"); + memcpy(&il->card_alive, &pkt->u.alive_frame, + sizeof(struct il_alive_resp)); + pwork = &il->alive_start; + } + + /* We delay the ALIVE response by 5ms to + * give the HW RF Kill time to activate... */ + if (palive->is_valid == UCODE_VALID_OK) + queue_delayed_work(il->workqueue, pwork, msecs_to_jiffies(5)); + else + IL_WARN("uCode did not respond OK.\n"); +} + +/** + * il4965_bg_stats_periodic - Timer callback to queue stats + * + * This callback is provided in order to send a stats request. + * + * This timer function is continually reset to execute within + * 60 seconds since the last N_STATS was received. We need to + * ensure we receive the stats in order to update the temperature + * used for calibrating the TXPOWER. + */ +static void +il4965_bg_stats_periodic(unsigned long data) +{ + struct il_priv *il = (struct il_priv *)data; + + if (test_bit(S_EXIT_PENDING, &il->status)) + return; + + /* dont send host command if rf-kill is on */ + if (!il_is_ready_rf(il)) + return; + + il_send_stats_request(il, CMD_ASYNC, false); +} + +static void +il4965_hdl_beacon(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il4965_beacon_notif *beacon = + (struct il4965_beacon_notif *)pkt->u.raw; +#ifdef CPTCFG_IWLEGACY_DEBUG + u8 rate = il4965_hw_get_rate(beacon->beacon_notify_hdr.rate_n_flags); + + D_RX("beacon status %x retries %d iss %d tsf:0x%.8x%.8x rate %d\n", + le32_to_cpu(beacon->beacon_notify_hdr.u.status) & TX_STATUS_MSK, + beacon->beacon_notify_hdr.failure_frame, + le32_to_cpu(beacon->ibss_mgr_status), + le32_to_cpu(beacon->high_tsf), le32_to_cpu(beacon->low_tsf), rate); +#endif + il->ibss_manager = le32_to_cpu(beacon->ibss_mgr_status); +} + +static void +il4965_perform_ct_kill_task(struct il_priv *il) +{ + unsigned long flags; + + D_POWER("Stop all queues\n"); + + if (il->mac80211_registered) + ieee80211_stop_queues(il->hw); + + _il_wr(il, CSR_UCODE_DRV_GP1_SET, + CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT); + _il_rd(il, CSR_UCODE_DRV_GP1); + + spin_lock_irqsave(&il->reg_lock, flags); + if (likely(_il_grab_nic_access(il))) + _il_release_nic_access(il); + spin_unlock_irqrestore(&il->reg_lock, flags); +} + +/* Handle notification from uCode that card's power state is changing + * due to software, hardware, or critical temperature RFKILL */ +static void +il4965_hdl_card_state(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + u32 flags = le32_to_cpu(pkt->u.card_state_notif.flags); + unsigned long status = il->status; + + D_RF_KILL("Card state received: HW:%s SW:%s CT:%s\n", + (flags & HW_CARD_DISABLED) ? "Kill" : "On", + (flags & SW_CARD_DISABLED) ? "Kill" : "On", + (flags & CT_CARD_DISABLED) ? "Reached" : "Not reached"); + + if (flags & (SW_CARD_DISABLED | HW_CARD_DISABLED | CT_CARD_DISABLED)) { + + _il_wr(il, CSR_UCODE_DRV_GP1_SET, + CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); + + il_wr(il, HBUS_TARG_MBX_C, HBUS_TARG_MBX_C_REG_BIT_CMD_BLOCKED); + + if (!(flags & RXON_CARD_DISABLED)) { + _il_wr(il, CSR_UCODE_DRV_GP1_CLR, + CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); + il_wr(il, HBUS_TARG_MBX_C, + HBUS_TARG_MBX_C_REG_BIT_CMD_BLOCKED); + } + } + + if (flags & CT_CARD_DISABLED) + il4965_perform_ct_kill_task(il); + + if (flags & HW_CARD_DISABLED) + set_bit(S_RFKILL, &il->status); + else + clear_bit(S_RFKILL, &il->status); + + if (!(flags & RXON_CARD_DISABLED)) + il_scan_cancel(il); + + if ((test_bit(S_RFKILL, &status) != + test_bit(S_RFKILL, &il->status))) + wiphy_rfkill_set_hw_state(il->hw->wiphy, + test_bit(S_RFKILL, &il->status)); + else + wake_up(&il->wait_command_queue); +} + +/** + * il4965_setup_handlers - Initialize Rx handler callbacks + * + * Setup the RX handlers for each of the reply types sent from the uCode + * to the host. + * + * This function chains into the hardware specific files for them to setup + * any hardware specific handlers as well. + */ +static void +il4965_setup_handlers(struct il_priv *il) +{ + il->handlers[N_ALIVE] = il4965_hdl_alive; + il->handlers[N_ERROR] = il_hdl_error; + il->handlers[N_CHANNEL_SWITCH] = il_hdl_csa; + il->handlers[N_SPECTRUM_MEASUREMENT] = il_hdl_spectrum_measurement; + il->handlers[N_PM_SLEEP] = il_hdl_pm_sleep; + il->handlers[N_PM_DEBUG_STATS] = il_hdl_pm_debug_stats; + il->handlers[N_BEACON] = il4965_hdl_beacon; + + /* + * The same handler is used for both the REPLY to a discrete + * stats request from the host as well as for the periodic + * stats notifications (after received beacons) from the uCode. + */ + il->handlers[C_STATS] = il4965_hdl_c_stats; + il->handlers[N_STATS] = il4965_hdl_stats; + + il_setup_rx_scan_handlers(il); + + /* status change handler */ + il->handlers[N_CARD_STATE] = il4965_hdl_card_state; + + il->handlers[N_MISSED_BEACONS] = il4965_hdl_missed_beacon; + /* Rx handlers */ + il->handlers[N_RX_PHY] = il4965_hdl_rx_phy; + il->handlers[N_RX_MPDU] = il4965_hdl_rx; + il->handlers[N_RX] = il4965_hdl_rx; + /* block ack */ + il->handlers[N_COMPRESSED_BA] = il4965_hdl_compressed_ba; + /* Tx response */ + il->handlers[C_TX] = il4965_hdl_tx; +} + +/** + * il4965_rx_handle - Main entry function for receiving responses from uCode + * + * Uses the il->handlers callback function array to invoke + * the appropriate handlers, including command responses, + * frame-received notifications, and other notifications. + */ +void +il4965_rx_handle(struct il_priv *il) +{ + struct il_rx_buf *rxb; + struct il_rx_pkt *pkt; + struct il_rx_queue *rxq = &il->rxq; + u32 r, i; + int reclaim; + unsigned long flags; + u8 fill_rx = 0; + u32 count = 8; + int total_empty; + + /* uCode's read idx (stored in shared DRAM) indicates the last Rx + * buffer that the driver may process (last buffer filled by ucode). */ + r = le16_to_cpu(rxq->rb_stts->closed_rb_num) & 0x0FFF; + i = rxq->read; + + /* Rx interrupt, but nothing sent from uCode */ + if (i == r) + D_RX("r = %d, i = %d\n", r, i); + + /* calculate total frames need to be restock after handling RX */ + total_empty = r - rxq->write_actual; + if (total_empty < 0) + total_empty += RX_QUEUE_SIZE; + + if (total_empty > (RX_QUEUE_SIZE / 2)) + fill_rx = 1; + + while (i != r) { + int len; + + rxb = rxq->queue[i]; + + /* If an RXB doesn't have a Rx queue slot associated with it, + * then a bug has been introduced in the queue refilling + * routines -- catch it here */ + BUG_ON(rxb == NULL); + + rxq->queue[i] = NULL; + + pci_unmap_page(il->pci_dev, rxb->page_dma, + PAGE_SIZE << il->hw_params.rx_page_order, + PCI_DMA_FROMDEVICE); + pkt = rxb_addr(rxb); + + len = le32_to_cpu(pkt->len_n_flags) & IL_RX_FRAME_SIZE_MSK; + len += sizeof(u32); /* account for status word */ + + /* Reclaim a command buffer only if this packet is a response + * to a (driver-originated) command. + * If the packet (e.g. Rx frame) originated from uCode, + * there is no command buffer to reclaim. + * Ucode should set SEQ_RX_FRAME bit if ucode-originated, + * but apparently a few don't get set; catch them here. */ + reclaim = !(pkt->hdr.sequence & SEQ_RX_FRAME) && + (pkt->hdr.cmd != N_RX_PHY) && (pkt->hdr.cmd != N_RX) && + (pkt->hdr.cmd != N_RX_MPDU) && + (pkt->hdr.cmd != N_COMPRESSED_BA) && + (pkt->hdr.cmd != N_STATS) && (pkt->hdr.cmd != C_TX); + + /* Based on type of command response or notification, + * handle those that need handling via function in + * handlers table. See il4965_setup_handlers() */ + if (il->handlers[pkt->hdr.cmd]) { + D_RX("r = %d, i = %d, %s, 0x%02x\n", r, i, + il_get_cmd_string(pkt->hdr.cmd), pkt->hdr.cmd); + il->isr_stats.handlers[pkt->hdr.cmd]++; + il->handlers[pkt->hdr.cmd] (il, rxb); + } else { + /* No handling needed */ + D_RX("r %d i %d No handler needed for %s, 0x%02x\n", r, + i, il_get_cmd_string(pkt->hdr.cmd), pkt->hdr.cmd); + } + + /* + * XXX: After here, we should always check rxb->page + * against NULL before touching it or its virtual + * memory (pkt). Because some handler might have + * already taken or freed the pages. + */ + + if (reclaim) { + /* Invoke any callbacks, transfer the buffer to caller, + * and fire off the (possibly) blocking il_send_cmd() + * as we reclaim the driver command queue */ + if (rxb->page) + il_tx_cmd_complete(il, rxb); + else + IL_WARN("Claim null rxb?\n"); + } + + /* Reuse the page if possible. For notification packets and + * SKBs that fail to Rx correctly, add them back into the + * rx_free list for reuse later. */ + spin_lock_irqsave(&rxq->lock, flags); + if (rxb->page != NULL) { + rxb->page_dma = + pci_map_page(il->pci_dev, rxb->page, 0, + PAGE_SIZE << il->hw_params. + rx_page_order, PCI_DMA_FROMDEVICE); + + if (unlikely(pci_dma_mapping_error(il->pci_dev, + rxb->page_dma))) { + __il_free_pages(il, rxb->page); + rxb->page = NULL; + list_add_tail(&rxb->list, &rxq->rx_used); + } else { + list_add_tail(&rxb->list, &rxq->rx_free); + rxq->free_count++; + } + } else + list_add_tail(&rxb->list, &rxq->rx_used); + + spin_unlock_irqrestore(&rxq->lock, flags); + + i = (i + 1) & RX_QUEUE_MASK; + /* If there are a lot of unused frames, + * restock the Rx queue so ucode wont assert. */ + if (fill_rx) { + count++; + if (count >= 8) { + rxq->read = i; + il4965_rx_replenish_now(il); + count = 0; + } + } + } + + /* Backtrack one entry */ + rxq->read = i; + if (fill_rx) + il4965_rx_replenish_now(il); + else + il4965_rx_queue_restock(il); +} + +/* call this function to flush any scheduled tasklet */ +static inline void +il4965_synchronize_irq(struct il_priv *il) +{ + /* wait to make sure we flush pending tasklet */ + synchronize_irq(il->pci_dev->irq); + tasklet_kill(&il->irq_tasklet); +} + +static void +il4965_irq_tasklet(struct il_priv *il) +{ + u32 inta, handled = 0; + u32 inta_fh; + unsigned long flags; + u32 i; +#ifdef CPTCFG_IWLEGACY_DEBUG + u32 inta_mask; +#endif + + spin_lock_irqsave(&il->lock, flags); + + /* Ack/clear/reset pending uCode interrupts. + * Note: Some bits in CSR_INT are "OR" of bits in CSR_FH_INT_STATUS, + * and will clear only when CSR_FH_INT_STATUS gets cleared. */ + inta = _il_rd(il, CSR_INT); + _il_wr(il, CSR_INT, inta); + + /* Ack/clear/reset pending flow-handler (DMA) interrupts. + * Any new interrupts that happen after this, either while we're + * in this tasklet, or later, will show up in next ISR/tasklet. */ + inta_fh = _il_rd(il, CSR_FH_INT_STATUS); + _il_wr(il, CSR_FH_INT_STATUS, inta_fh); + +#ifdef CPTCFG_IWLEGACY_DEBUG + if (il_get_debug_level(il) & IL_DL_ISR) { + /* just for debug */ + inta_mask = _il_rd(il, CSR_INT_MASK); + D_ISR("inta 0x%08x, enabled 0x%08x, fh 0x%08x\n", inta, + inta_mask, inta_fh); + } +#endif + + spin_unlock_irqrestore(&il->lock, flags); + + /* Since CSR_INT and CSR_FH_INT_STATUS reads and clears are not + * atomic, make sure that inta covers all the interrupts that + * we've discovered, even if FH interrupt came in just after + * reading CSR_INT. */ + if (inta_fh & CSR49_FH_INT_RX_MASK) + inta |= CSR_INT_BIT_FH_RX; + if (inta_fh & CSR49_FH_INT_TX_MASK) + inta |= CSR_INT_BIT_FH_TX; + + /* Now service all interrupt bits discovered above. */ + if (inta & CSR_INT_BIT_HW_ERR) { + IL_ERR("Hardware error detected. Restarting.\n"); + + /* Tell the device to stop sending interrupts */ + il_disable_interrupts(il); + + il->isr_stats.hw++; + il_irq_handle_error(il); + + handled |= CSR_INT_BIT_HW_ERR; + + return; + } +#ifdef CPTCFG_IWLEGACY_DEBUG + if (il_get_debug_level(il) & (IL_DL_ISR)) { + /* NIC fires this, but we don't use it, redundant with WAKEUP */ + if (inta & CSR_INT_BIT_SCD) { + D_ISR("Scheduler finished to transmit " + "the frame/frames.\n"); + il->isr_stats.sch++; + } + + /* Alive notification via Rx interrupt will do the real work */ + if (inta & CSR_INT_BIT_ALIVE) { + D_ISR("Alive interrupt\n"); + il->isr_stats.alive++; + } + } +#endif + /* Safely ignore these bits for debug checks below */ + inta &= ~(CSR_INT_BIT_SCD | CSR_INT_BIT_ALIVE); + + /* HW RF KILL switch toggled */ + if (inta & CSR_INT_BIT_RF_KILL) { + int hw_rf_kill = 0; + + if (!(_il_rd(il, CSR_GP_CNTRL) & CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW)) + hw_rf_kill = 1; + + IL_WARN("RF_KILL bit toggled to %s.\n", + hw_rf_kill ? "disable radio" : "enable radio"); + + il->isr_stats.rfkill++; + + /* driver only loads ucode once setting the interface up. + * the driver allows loading the ucode even if the radio + * is killed. Hence update the killswitch state here. The + * rfkill handler will care about restarting if needed. + */ + if (hw_rf_kill) { + set_bit(S_RFKILL, &il->status); + } else { + clear_bit(S_RFKILL, &il->status); + il_force_reset(il, true); + } + wiphy_rfkill_set_hw_state(il->hw->wiphy, hw_rf_kill); + + handled |= CSR_INT_BIT_RF_KILL; + } + + /* Chip got too hot and stopped itself */ + if (inta & CSR_INT_BIT_CT_KILL) { + IL_ERR("Microcode CT kill error detected.\n"); + il->isr_stats.ctkill++; + handled |= CSR_INT_BIT_CT_KILL; + } + + /* Error detected by uCode */ + if (inta & CSR_INT_BIT_SW_ERR) { + IL_ERR("Microcode SW error detected. " " Restarting 0x%X.\n", + inta); + il->isr_stats.sw++; + il_irq_handle_error(il); + handled |= CSR_INT_BIT_SW_ERR; + } + + /* + * uCode wakes up after power-down sleep. + * Tell device about any new tx or host commands enqueued, + * and about any Rx buffers made available while asleep. + */ + if (inta & CSR_INT_BIT_WAKEUP) { + D_ISR("Wakeup interrupt\n"); + il_rx_queue_update_write_ptr(il, &il->rxq); + for (i = 0; i < il->hw_params.max_txq_num; i++) + il_txq_update_write_ptr(il, &il->txq[i]); + il->isr_stats.wakeup++; + handled |= CSR_INT_BIT_WAKEUP; + } + + /* All uCode command responses, including Tx command responses, + * Rx "responses" (frame-received notification), and other + * notifications from uCode come through here*/ + if (inta & (CSR_INT_BIT_FH_RX | CSR_INT_BIT_SW_RX)) { + il4965_rx_handle(il); + il->isr_stats.rx++; + handled |= (CSR_INT_BIT_FH_RX | CSR_INT_BIT_SW_RX); + } + + /* This "Tx" DMA channel is used only for loading uCode */ + if (inta & CSR_INT_BIT_FH_TX) { + D_ISR("uCode load interrupt\n"); + il->isr_stats.tx++; + handled |= CSR_INT_BIT_FH_TX; + /* Wake up uCode load routine, now that load is complete */ + il->ucode_write_complete = 1; + wake_up(&il->wait_command_queue); + } + + if (inta & ~handled) { + IL_ERR("Unhandled INTA bits 0x%08x\n", inta & ~handled); + il->isr_stats.unhandled++; + } + + if (inta & ~(il->inta_mask)) { + IL_WARN("Disabled INTA bits 0x%08x were pending\n", + inta & ~il->inta_mask); + IL_WARN(" with FH49_INT = 0x%08x\n", inta_fh); + } + + /* Re-enable all interrupts */ + /* only Re-enable if disabled by irq */ + if (test_bit(S_INT_ENABLED, &il->status)) + il_enable_interrupts(il); + /* Re-enable RF_KILL if it occurred */ + else if (handled & CSR_INT_BIT_RF_KILL) + il_enable_rfkill_int(il); + +#ifdef CPTCFG_IWLEGACY_DEBUG + if (il_get_debug_level(il) & (IL_DL_ISR)) { + inta = _il_rd(il, CSR_INT); + inta_mask = _il_rd(il, CSR_INT_MASK); + inta_fh = _il_rd(il, CSR_FH_INT_STATUS); + D_ISR("End inta 0x%08x, enabled 0x%08x, fh 0x%08x, " + "flags 0x%08lx\n", inta, inta_mask, inta_fh, flags); + } +#endif +} + +/***************************************************************************** + * + * sysfs attributes + * + *****************************************************************************/ + +#ifdef CPTCFG_IWLEGACY_DEBUG + +/* + * The following adds a new attribute to the sysfs representation + * of this device driver (i.e. a new file in /sys/class/net/wlan0/device/) + * used for controlling the debug level. + * + * See the level definitions in iwl for details. + * + * The debug_level being managed using sysfs below is a per device debug + * level that is used instead of the global debug level if it (the per + * device debug level) is set. + */ +static ssize_t +il4965_show_debug_level(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct il_priv *il = dev_get_drvdata(d); + return sprintf(buf, "0x%08X\n", il_get_debug_level(il)); +} + +static ssize_t +il4965_store_debug_level(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct il_priv *il = dev_get_drvdata(d); + unsigned long val; + int ret; + + ret = strict_strtoul(buf, 0, &val); + if (ret) + IL_ERR("%s is not in hex or decimal form.\n", buf); + else + il->debug_level = val; + + return strnlen(buf, count); +} + +static DEVICE_ATTR(debug_level, S_IWUSR | S_IRUGO, il4965_show_debug_level, + il4965_store_debug_level); + +#endif /* CPTCFG_IWLEGACY_DEBUG */ + +static ssize_t +il4965_show_temperature(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct il_priv *il = dev_get_drvdata(d); + + if (!il_is_alive(il)) + return -EAGAIN; + + return sprintf(buf, "%d\n", il->temperature); +} + +static DEVICE_ATTR(temperature, S_IRUGO, il4965_show_temperature, NULL); + +static ssize_t +il4965_show_tx_power(struct device *d, struct device_attribute *attr, char *buf) +{ + struct il_priv *il = dev_get_drvdata(d); + + if (!il_is_ready_rf(il)) + return sprintf(buf, "off\n"); + else + return sprintf(buf, "%d\n", il->tx_power_user_lmt); +} + +static ssize_t +il4965_store_tx_power(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct il_priv *il = dev_get_drvdata(d); + unsigned long val; + int ret; + + ret = strict_strtoul(buf, 10, &val); + if (ret) + IL_INFO("%s is not in decimal form.\n", buf); + else { + ret = il_set_tx_power(il, val, false); + if (ret) + IL_ERR("failed setting tx power (0x%d).\n", ret); + else + ret = count; + } + return ret; +} + +static DEVICE_ATTR(tx_power, S_IWUSR | S_IRUGO, il4965_show_tx_power, + il4965_store_tx_power); + +static struct attribute *il_sysfs_entries[] = { + &dev_attr_temperature.attr, + &dev_attr_tx_power.attr, +#ifdef CPTCFG_IWLEGACY_DEBUG + &dev_attr_debug_level.attr, +#endif + NULL +}; + +static struct attribute_group il_attribute_group = { + .name = NULL, /* put in device directory */ + .attrs = il_sysfs_entries, +}; + +/****************************************************************************** + * + * uCode download functions + * + ******************************************************************************/ + +static void +il4965_dealloc_ucode_pci(struct il_priv *il) +{ + il_free_fw_desc(il->pci_dev, &il->ucode_code); + il_free_fw_desc(il->pci_dev, &il->ucode_data); + il_free_fw_desc(il->pci_dev, &il->ucode_data_backup); + il_free_fw_desc(il->pci_dev, &il->ucode_init); + il_free_fw_desc(il->pci_dev, &il->ucode_init_data); + il_free_fw_desc(il->pci_dev, &il->ucode_boot); +} + +static void +il4965_nic_start(struct il_priv *il) +{ + /* Remove all resets to allow NIC to operate */ + _il_wr(il, CSR_RESET, 0); +} + +static void il4965_ucode_callback(const struct firmware *ucode_raw, + void *context); +static int il4965_mac_setup_register(struct il_priv *il, u32 max_probe_length); + +static int __must_check +il4965_request_firmware(struct il_priv *il, bool first) +{ + const char *name_pre = il->cfg->fw_name_pre; + char tag[8]; + + if (first) { + il->fw_idx = il->cfg->ucode_api_max; + sprintf(tag, "%d", il->fw_idx); + } else { + il->fw_idx--; + sprintf(tag, "%d", il->fw_idx); + } + + if (il->fw_idx < il->cfg->ucode_api_min) { + IL_ERR("no suitable firmware found!\n"); + return -ENOENT; + } + + sprintf(il->firmware_name, "%s%s%s", name_pre, tag, ".ucode"); + + D_INFO("attempting to load firmware '%s'\n", il->firmware_name); + + return request_firmware_nowait(THIS_MODULE, 1, il->firmware_name, + &il->pci_dev->dev, GFP_KERNEL, il, + il4965_ucode_callback); +} + +struct il4965_firmware_pieces { + const void *inst, *data, *init, *init_data, *boot; + size_t inst_size, data_size, init_size, init_data_size, boot_size; +}; + +static int +il4965_load_firmware(struct il_priv *il, const struct firmware *ucode_raw, + struct il4965_firmware_pieces *pieces) +{ + struct il_ucode_header *ucode = (void *)ucode_raw->data; + u32 api_ver, hdr_size; + const u8 *src; + + il->ucode_ver = le32_to_cpu(ucode->ver); + api_ver = IL_UCODE_API(il->ucode_ver); + + switch (api_ver) { + default: + case 0: + case 1: + case 2: + hdr_size = 24; + if (ucode_raw->size < hdr_size) { + IL_ERR("File size too small!\n"); + return -EINVAL; + } + pieces->inst_size = le32_to_cpu(ucode->v1.inst_size); + pieces->data_size = le32_to_cpu(ucode->v1.data_size); + pieces->init_size = le32_to_cpu(ucode->v1.init_size); + pieces->init_data_size = le32_to_cpu(ucode->v1.init_data_size); + pieces->boot_size = le32_to_cpu(ucode->v1.boot_size); + src = ucode->v1.data; + break; + } + + /* Verify size of file vs. image size info in file's header */ + if (ucode_raw->size != + hdr_size + pieces->inst_size + pieces->data_size + + pieces->init_size + pieces->init_data_size + pieces->boot_size) { + + IL_ERR("uCode file size %d does not match expected size\n", + (int)ucode_raw->size); + return -EINVAL; + } + + pieces->inst = src; + src += pieces->inst_size; + pieces->data = src; + src += pieces->data_size; + pieces->init = src; + src += pieces->init_size; + pieces->init_data = src; + src += pieces->init_data_size; + pieces->boot = src; + src += pieces->boot_size; + + return 0; +} + +/** + * il4965_ucode_callback - callback when firmware was loaded + * + * If loaded successfully, copies the firmware into buffers + * for the card to fetch (via DMA). + */ +static void +il4965_ucode_callback(const struct firmware *ucode_raw, void *context) +{ + struct il_priv *il = context; + struct il_ucode_header *ucode; + int err; + struct il4965_firmware_pieces pieces; + const unsigned int api_max = il->cfg->ucode_api_max; + const unsigned int api_min = il->cfg->ucode_api_min; + u32 api_ver; + + u32 max_probe_length = 200; + u32 standard_phy_calibration_size = + IL_DEFAULT_STANDARD_PHY_CALIBRATE_TBL_SIZE; + + memset(&pieces, 0, sizeof(pieces)); + + if (!ucode_raw) { + if (il->fw_idx <= il->cfg->ucode_api_max) + IL_ERR("request for firmware file '%s' failed.\n", + il->firmware_name); + goto try_again; + } + + D_INFO("Loaded firmware file '%s' (%zd bytes).\n", il->firmware_name, + ucode_raw->size); + + /* Make sure that we got at least the API version number */ + if (ucode_raw->size < 4) { + IL_ERR("File size way too small!\n"); + goto try_again; + } + + /* Data from ucode file: header followed by uCode images */ + ucode = (struct il_ucode_header *)ucode_raw->data; + + err = il4965_load_firmware(il, ucode_raw, &pieces); + + if (err) + goto try_again; + + api_ver = IL_UCODE_API(il->ucode_ver); + + /* + * api_ver should match the api version forming part of the + * firmware filename ... but we don't check for that and only rely + * on the API version read from firmware header from here on forward + */ + if (api_ver < api_min || api_ver > api_max) { + IL_ERR("Driver unable to support your firmware API. " + "Driver supports v%u, firmware is v%u.\n", api_max, + api_ver); + goto try_again; + } + + if (api_ver != api_max) + IL_ERR("Firmware has old API version. Expected v%u, " + "got v%u. New firmware can be obtained " + "from http://www.intellinuxwireless.org.\n", api_max, + api_ver); + + IL_INFO("loaded firmware version %u.%u.%u.%u\n", + IL_UCODE_MAJOR(il->ucode_ver), IL_UCODE_MINOR(il->ucode_ver), + IL_UCODE_API(il->ucode_ver), IL_UCODE_SERIAL(il->ucode_ver)); + + snprintf(il->hw->wiphy->fw_version, sizeof(il->hw->wiphy->fw_version), + "%u.%u.%u.%u", IL_UCODE_MAJOR(il->ucode_ver), + IL_UCODE_MINOR(il->ucode_ver), IL_UCODE_API(il->ucode_ver), + IL_UCODE_SERIAL(il->ucode_ver)); + + /* + * For any of the failures below (before allocating pci memory) + * we will try to load a version with a smaller API -- maybe the + * user just got a corrupted version of the latest API. + */ + + D_INFO("f/w package hdr ucode version raw = 0x%x\n", il->ucode_ver); + D_INFO("f/w package hdr runtime inst size = %Zd\n", pieces.inst_size); + D_INFO("f/w package hdr runtime data size = %Zd\n", pieces.data_size); + D_INFO("f/w package hdr init inst size = %Zd\n", pieces.init_size); + D_INFO("f/w package hdr init data size = %Zd\n", pieces.init_data_size); + D_INFO("f/w package hdr boot inst size = %Zd\n", pieces.boot_size); + + /* Verify that uCode images will fit in card's SRAM */ + if (pieces.inst_size > il->hw_params.max_inst_size) { + IL_ERR("uCode instr len %Zd too large to fit in\n", + pieces.inst_size); + goto try_again; + } + + if (pieces.data_size > il->hw_params.max_data_size) { + IL_ERR("uCode data len %Zd too large to fit in\n", + pieces.data_size); + goto try_again; + } + + if (pieces.init_size > il->hw_params.max_inst_size) { + IL_ERR("uCode init instr len %Zd too large to fit in\n", + pieces.init_size); + goto try_again; + } + + if (pieces.init_data_size > il->hw_params.max_data_size) { + IL_ERR("uCode init data len %Zd too large to fit in\n", + pieces.init_data_size); + goto try_again; + } + + if (pieces.boot_size > il->hw_params.max_bsm_size) { + IL_ERR("uCode boot instr len %Zd too large to fit in\n", + pieces.boot_size); + goto try_again; + } + + /* Allocate ucode buffers for card's bus-master loading ... */ + + /* Runtime instructions and 2 copies of data: + * 1) unmodified from disk + * 2) backup cache for save/restore during power-downs */ + il->ucode_code.len = pieces.inst_size; + il_alloc_fw_desc(il->pci_dev, &il->ucode_code); + + il->ucode_data.len = pieces.data_size; + il_alloc_fw_desc(il->pci_dev, &il->ucode_data); + + il->ucode_data_backup.len = pieces.data_size; + il_alloc_fw_desc(il->pci_dev, &il->ucode_data_backup); + + if (!il->ucode_code.v_addr || !il->ucode_data.v_addr || + !il->ucode_data_backup.v_addr) + goto err_pci_alloc; + + /* Initialization instructions and data */ + if (pieces.init_size && pieces.init_data_size) { + il->ucode_init.len = pieces.init_size; + il_alloc_fw_desc(il->pci_dev, &il->ucode_init); + + il->ucode_init_data.len = pieces.init_data_size; + il_alloc_fw_desc(il->pci_dev, &il->ucode_init_data); + + if (!il->ucode_init.v_addr || !il->ucode_init_data.v_addr) + goto err_pci_alloc; + } + + /* Bootstrap (instructions only, no data) */ + if (pieces.boot_size) { + il->ucode_boot.len = pieces.boot_size; + il_alloc_fw_desc(il->pci_dev, &il->ucode_boot); + + if (!il->ucode_boot.v_addr) + goto err_pci_alloc; + } + + /* Now that we can no longer fail, copy information */ + + il->sta_key_max_num = STA_KEY_MAX_NUM; + + /* Copy images into buffers for card's bus-master reads ... */ + + /* Runtime instructions (first block of data in file) */ + D_INFO("Copying (but not loading) uCode instr len %Zd\n", + pieces.inst_size); + memcpy(il->ucode_code.v_addr, pieces.inst, pieces.inst_size); + + D_INFO("uCode instr buf vaddr = 0x%p, paddr = 0x%08x\n", + il->ucode_code.v_addr, (u32) il->ucode_code.p_addr); + + /* + * Runtime data + * NOTE: Copy into backup buffer will be done in il_up() + */ + D_INFO("Copying (but not loading) uCode data len %Zd\n", + pieces.data_size); + memcpy(il->ucode_data.v_addr, pieces.data, pieces.data_size); + memcpy(il->ucode_data_backup.v_addr, pieces.data, pieces.data_size); + + /* Initialization instructions */ + if (pieces.init_size) { + D_INFO("Copying (but not loading) init instr len %Zd\n", + pieces.init_size); + memcpy(il->ucode_init.v_addr, pieces.init, pieces.init_size); + } + + /* Initialization data */ + if (pieces.init_data_size) { + D_INFO("Copying (but not loading) init data len %Zd\n", + pieces.init_data_size); + memcpy(il->ucode_init_data.v_addr, pieces.init_data, + pieces.init_data_size); + } + + /* Bootstrap instructions */ + D_INFO("Copying (but not loading) boot instr len %Zd\n", + pieces.boot_size); + memcpy(il->ucode_boot.v_addr, pieces.boot, pieces.boot_size); + + /* + * figure out the offset of chain noise reset and gain commands + * base on the size of standard phy calibration commands table size + */ + il->_4965.phy_calib_chain_noise_reset_cmd = + standard_phy_calibration_size; + il->_4965.phy_calib_chain_noise_gain_cmd = + standard_phy_calibration_size + 1; + + /************************************************** + * This is still part of probe() in a sense... + * + * 9. Setup and register with mac80211 and debugfs + **************************************************/ + err = il4965_mac_setup_register(il, max_probe_length); + if (err) + goto out_unbind; + + err = il_dbgfs_register(il, DRV_NAME); + if (err) + IL_ERR("failed to create debugfs files. Ignoring error: %d\n", + err); + + err = sysfs_create_group(&il->pci_dev->dev.kobj, &il_attribute_group); + if (err) { + IL_ERR("failed to create sysfs device attributes\n"); + goto out_unbind; + } + + /* We have our copies now, allow OS release its copies */ + release_firmware(ucode_raw); + complete(&il->_4965.firmware_loading_complete); + return; + +try_again: + /* try next, if any */ + if (il4965_request_firmware(il, false)) + goto out_unbind; + release_firmware(ucode_raw); + return; + +err_pci_alloc: + IL_ERR("failed to allocate pci memory\n"); + il4965_dealloc_ucode_pci(il); +out_unbind: + complete(&il->_4965.firmware_loading_complete); + device_release_driver(&il->pci_dev->dev); + release_firmware(ucode_raw); +} + +static const char *const desc_lookup_text[] = { + "OK", + "FAIL", + "BAD_PARAM", + "BAD_CHECKSUM", + "NMI_INTERRUPT_WDG", + "SYSASSERT", + "FATAL_ERROR", + "BAD_COMMAND", + "HW_ERROR_TUNE_LOCK", + "HW_ERROR_TEMPERATURE", + "ILLEGAL_CHAN_FREQ", + "VCC_NOT_STBL", + "FH49_ERROR", + "NMI_INTERRUPT_HOST", + "NMI_INTERRUPT_ACTION_PT", + "NMI_INTERRUPT_UNKNOWN", + "UCODE_VERSION_MISMATCH", + "HW_ERROR_ABS_LOCK", + "HW_ERROR_CAL_LOCK_FAIL", + "NMI_INTERRUPT_INST_ACTION_PT", + "NMI_INTERRUPT_DATA_ACTION_PT", + "NMI_TRM_HW_ER", + "NMI_INTERRUPT_TRM", + "NMI_INTERRUPT_BREAK_POINT", + "DEBUG_0", + "DEBUG_1", + "DEBUG_2", + "DEBUG_3", +}; + +static struct { + char *name; + u8 num; +} advanced_lookup[] = { + { + "NMI_INTERRUPT_WDG", 0x34}, { + "SYSASSERT", 0x35}, { + "UCODE_VERSION_MISMATCH", 0x37}, { + "BAD_COMMAND", 0x38}, { + "NMI_INTERRUPT_DATA_ACTION_PT", 0x3C}, { + "FATAL_ERROR", 0x3D}, { + "NMI_TRM_HW_ERR", 0x46}, { + "NMI_INTERRUPT_TRM", 0x4C}, { + "NMI_INTERRUPT_BREAK_POINT", 0x54}, { + "NMI_INTERRUPT_WDG_RXF_FULL", 0x5C}, { + "NMI_INTERRUPT_WDG_NO_RBD_RXF_FULL", 0x64}, { + "NMI_INTERRUPT_HOST", 0x66}, { + "NMI_INTERRUPT_ACTION_PT", 0x7C}, { + "NMI_INTERRUPT_UNKNOWN", 0x84}, { + "NMI_INTERRUPT_INST_ACTION_PT", 0x86}, { +"ADVANCED_SYSASSERT", 0},}; + +static const char * +il4965_desc_lookup(u32 num) +{ + int i; + int max = ARRAY_SIZE(desc_lookup_text); + + if (num < max) + return desc_lookup_text[num]; + + max = ARRAY_SIZE(advanced_lookup) - 1; + for (i = 0; i < max; i++) { + if (advanced_lookup[i].num == num) + break; + } + return advanced_lookup[i].name; +} + +#define ERROR_START_OFFSET (1 * sizeof(u32)) +#define ERROR_ELEM_SIZE (7 * sizeof(u32)) + +void +il4965_dump_nic_error_log(struct il_priv *il) +{ + u32 data2, line; + u32 desc, time, count, base, data1; + u32 blink1, blink2, ilink1, ilink2; + u32 pc, hcmd; + + if (il->ucode_type == UCODE_INIT) + base = le32_to_cpu(il->card_alive_init.error_event_table_ptr); + else + base = le32_to_cpu(il->card_alive.error_event_table_ptr); + + if (!il->ops->is_valid_rtc_data_addr(base)) { + IL_ERR("Not valid error log pointer 0x%08X for %s uCode\n", + base, (il->ucode_type == UCODE_INIT) ? "Init" : "RT"); + return; + } + + count = il_read_targ_mem(il, base); + + if (ERROR_START_OFFSET <= count * ERROR_ELEM_SIZE) { + IL_ERR("Start IWL Error Log Dump:\n"); + IL_ERR("Status: 0x%08lX, count: %d\n", il->status, count); + } + + desc = il_read_targ_mem(il, base + 1 * sizeof(u32)); + il->isr_stats.err_code = desc; + pc = il_read_targ_mem(il, base + 2 * sizeof(u32)); + blink1 = il_read_targ_mem(il, base + 3 * sizeof(u32)); + blink2 = il_read_targ_mem(il, base + 4 * sizeof(u32)); + ilink1 = il_read_targ_mem(il, base + 5 * sizeof(u32)); + ilink2 = il_read_targ_mem(il, base + 6 * sizeof(u32)); + data1 = il_read_targ_mem(il, base + 7 * sizeof(u32)); + data2 = il_read_targ_mem(il, base + 8 * sizeof(u32)); + line = il_read_targ_mem(il, base + 9 * sizeof(u32)); + time = il_read_targ_mem(il, base + 11 * sizeof(u32)); + hcmd = il_read_targ_mem(il, base + 22 * sizeof(u32)); + + IL_ERR("Desc Time " + "data1 data2 line\n"); + IL_ERR("%-28s (0x%04X) %010u 0x%08X 0x%08X %u\n", + il4965_desc_lookup(desc), desc, time, data1, data2, line); + IL_ERR("pc blink1 blink2 ilink1 ilink2 hcmd\n"); + IL_ERR("0x%05X 0x%05X 0x%05X 0x%05X 0x%05X 0x%05X\n", pc, blink1, + blink2, ilink1, ilink2, hcmd); +} + +static void +il4965_rf_kill_ct_config(struct il_priv *il) +{ + struct il_ct_kill_config cmd; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&il->lock, flags); + _il_wr(il, CSR_UCODE_DRV_GP1_CLR, + CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT); + spin_unlock_irqrestore(&il->lock, flags); + + cmd.critical_temperature_R = + cpu_to_le32(il->hw_params.ct_kill_threshold); + + ret = il_send_cmd_pdu(il, C_CT_KILL_CONFIG, sizeof(cmd), &cmd); + if (ret) + IL_ERR("C_CT_KILL_CONFIG failed\n"); + else + D_INFO("C_CT_KILL_CONFIG " "succeeded, " + "critical temperature is %d\n", + il->hw_params.ct_kill_threshold); +} + +static const s8 default_queue_to_tx_fifo[] = { + IL_TX_FIFO_VO, + IL_TX_FIFO_VI, + IL_TX_FIFO_BE, + IL_TX_FIFO_BK, + IL49_CMD_FIFO_NUM, + IL_TX_FIFO_UNUSED, + IL_TX_FIFO_UNUSED, +}; + +#define IL_MASK(lo, hi) ((1 << (hi)) | ((1 << (hi)) - (1 << (lo)))) + +static int +il4965_alive_notify(struct il_priv *il) +{ + u32 a; + unsigned long flags; + int i, chan; + u32 reg_val; + + spin_lock_irqsave(&il->lock, flags); + + /* Clear 4965's internal Tx Scheduler data base */ + il->scd_base_addr = il_rd_prph(il, IL49_SCD_SRAM_BASE_ADDR); + a = il->scd_base_addr + IL49_SCD_CONTEXT_DATA_OFFSET; + for (; a < il->scd_base_addr + IL49_SCD_TX_STTS_BITMAP_OFFSET; a += 4) + il_write_targ_mem(il, a, 0); + for (; a < il->scd_base_addr + IL49_SCD_TRANSLATE_TBL_OFFSET; a += 4) + il_write_targ_mem(il, a, 0); + for (; + a < + il->scd_base_addr + + IL49_SCD_TRANSLATE_TBL_OFFSET_QUEUE(il->hw_params.max_txq_num); + a += 4) + il_write_targ_mem(il, a, 0); + + /* Tel 4965 where to find Tx byte count tables */ + il_wr_prph(il, IL49_SCD_DRAM_BASE_ADDR, il->scd_bc_tbls.dma >> 10); + + /* Enable DMA channel */ + for (chan = 0; chan < FH49_TCSR_CHNL_NUM; chan++) + il_wr(il, FH49_TCSR_CHNL_TX_CONFIG_REG(chan), + FH49_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE | + FH49_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE); + + /* Update FH chicken bits */ + reg_val = il_rd(il, FH49_TX_CHICKEN_BITS_REG); + il_wr(il, FH49_TX_CHICKEN_BITS_REG, + reg_val | FH49_TX_CHICKEN_BITS_SCD_AUTO_RETRY_EN); + + /* Disable chain mode for all queues */ + il_wr_prph(il, IL49_SCD_QUEUECHAIN_SEL, 0); + + /* Initialize each Tx queue (including the command queue) */ + for (i = 0; i < il->hw_params.max_txq_num; i++) { + + /* TFD circular buffer read/write idxes */ + il_wr_prph(il, IL49_SCD_QUEUE_RDPTR(i), 0); + il_wr(il, HBUS_TARG_WRPTR, 0 | (i << 8)); + + /* Max Tx Window size for Scheduler-ACK mode */ + il_write_targ_mem(il, + il->scd_base_addr + + IL49_SCD_CONTEXT_QUEUE_OFFSET(i), + (SCD_WIN_SIZE << + IL49_SCD_QUEUE_CTX_REG1_WIN_SIZE_POS) & + IL49_SCD_QUEUE_CTX_REG1_WIN_SIZE_MSK); + + /* Frame limit */ + il_write_targ_mem(il, + il->scd_base_addr + + IL49_SCD_CONTEXT_QUEUE_OFFSET(i) + + sizeof(u32), + (SCD_FRAME_LIMIT << + IL49_SCD_QUEUE_CTX_REG2_FRAME_LIMIT_POS) & + IL49_SCD_QUEUE_CTX_REG2_FRAME_LIMIT_MSK); + + } + il_wr_prph(il, IL49_SCD_INTERRUPT_MASK, + (1 << il->hw_params.max_txq_num) - 1); + + /* Activate all Tx DMA/FIFO channels */ + il4965_txq_set_sched(il, IL_MASK(0, 6)); + + il4965_set_wr_ptrs(il, IL_DEFAULT_CMD_QUEUE_NUM, 0); + + /* make sure all queue are not stopped */ + memset(&il->queue_stopped[0], 0, sizeof(il->queue_stopped)); + for (i = 0; i < 4; i++) + atomic_set(&il->queue_stop_count[i], 0); + + /* reset to 0 to enable all the queue first */ + il->txq_ctx_active_msk = 0; + /* Map each Tx/cmd queue to its corresponding fifo */ + BUILD_BUG_ON(ARRAY_SIZE(default_queue_to_tx_fifo) != 7); + + for (i = 0; i < ARRAY_SIZE(default_queue_to_tx_fifo); i++) { + int ac = default_queue_to_tx_fifo[i]; + + il_txq_ctx_activate(il, i); + + if (ac == IL_TX_FIFO_UNUSED) + continue; + + il4965_tx_queue_set_status(il, &il->txq[i], ac, 0); + } + + spin_unlock_irqrestore(&il->lock, flags); + + return 0; +} + +/** + * il4965_alive_start - called after N_ALIVE notification received + * from protocol/runtime uCode (initialization uCode's + * Alive gets handled by il_init_alive_start()). + */ +static void +il4965_alive_start(struct il_priv *il) +{ + int ret = 0; + + D_INFO("Runtime Alive received.\n"); + + if (il->card_alive.is_valid != UCODE_VALID_OK) { + /* We had an error bringing up the hardware, so take it + * all the way back down so we can try again */ + D_INFO("Alive failed.\n"); + goto restart; + } + + /* Initialize uCode has loaded Runtime uCode ... verify inst image. + * This is a paranoid check, because we would not have gotten the + * "runtime" alive if code weren't properly loaded. */ + if (il4965_verify_ucode(il)) { + /* Runtime instruction load was bad; + * take it all the way back down so we can try again */ + D_INFO("Bad runtime uCode load.\n"); + goto restart; + } + + ret = il4965_alive_notify(il); + if (ret) { + IL_WARN("Could not complete ALIVE transition [ntf]: %d\n", ret); + goto restart; + } + + /* After the ALIVE response, we can send host commands to the uCode */ + set_bit(S_ALIVE, &il->status); + + /* Enable watchdog to monitor the driver tx queues */ + il_setup_watchdog(il); + + if (il_is_rfkill(il)) + return; + + ieee80211_wake_queues(il->hw); + + il->active_rate = RATES_MASK; + + il_power_update_mode(il, true); + D_INFO("Updated power mode\n"); + + if (il_is_associated(il)) { + struct il_rxon_cmd *active_rxon = + (struct il_rxon_cmd *)&il->active; + /* apply any changes in staging */ + il->staging.filter_flags |= RXON_FILTER_ASSOC_MSK; + active_rxon->filter_flags &= ~RXON_FILTER_ASSOC_MSK; + } else { + /* Initialize our rx_config data */ + il_connection_init_rx_config(il); + + if (il->ops->set_rxon_chain) + il->ops->set_rxon_chain(il); + } + + /* Configure bluetooth coexistence if enabled */ + il_send_bt_config(il); + + il4965_reset_run_time_calib(il); + + set_bit(S_READY, &il->status); + + /* Configure the adapter for unassociated operation */ + il_commit_rxon(il); + + /* At this point, the NIC is initialized and operational */ + il4965_rf_kill_ct_config(il); + + D_INFO("ALIVE processing complete.\n"); + wake_up(&il->wait_command_queue); + + return; + +restart: + queue_work(il->workqueue, &il->restart); +} + +static void il4965_cancel_deferred_work(struct il_priv *il); + +static void +__il4965_down(struct il_priv *il) +{ + unsigned long flags; + int exit_pending; + + D_INFO(DRV_NAME " is going down\n"); + + il_scan_cancel_timeout(il, 200); + + exit_pending = test_and_set_bit(S_EXIT_PENDING, &il->status); + + /* Stop TX queues watchdog. We need to have S_EXIT_PENDING bit set + * to prevent rearm timer */ + del_timer_sync(&il->watchdog); + + il_clear_ucode_stations(il); + + /* FIXME: race conditions ? */ + spin_lock_irq(&il->sta_lock); + /* + * Remove all key information that is not stored as part + * of station information since mac80211 may not have had + * a chance to remove all the keys. When device is + * reconfigured by mac80211 after an error all keys will + * be reconfigured. + */ + memset(il->_4965.wep_keys, 0, sizeof(il->_4965.wep_keys)); + il->_4965.key_mapping_keys = 0; + spin_unlock_irq(&il->sta_lock); + + il_dealloc_bcast_stations(il); + il_clear_driver_stations(il); + + /* Unblock any waiting calls */ + wake_up_all(&il->wait_command_queue); + + /* Wipe out the EXIT_PENDING status bit if we are not actually + * exiting the module */ + if (!exit_pending) + clear_bit(S_EXIT_PENDING, &il->status); + + /* stop and reset the on-board processor */ + _il_wr(il, CSR_RESET, CSR_RESET_REG_FLAG_NEVO_RESET); + + /* tell the device to stop sending interrupts */ + spin_lock_irqsave(&il->lock, flags); + il_disable_interrupts(il); + spin_unlock_irqrestore(&il->lock, flags); + il4965_synchronize_irq(il); + + if (il->mac80211_registered) + ieee80211_stop_queues(il->hw); + + /* If we have not previously called il_init() then + * clear all bits but the RF Kill bit and return */ + if (!il_is_init(il)) { + il->status = + test_bit(S_RFKILL, &il->status) << S_RFKILL | + test_bit(S_GEO_CONFIGURED, &il->status) << S_GEO_CONFIGURED | + test_bit(S_EXIT_PENDING, &il->status) << S_EXIT_PENDING; + goto exit; + } + + /* ...otherwise clear out all the status bits but the RF Kill + * bit and continue taking the NIC down. */ + il->status &= + test_bit(S_RFKILL, &il->status) << S_RFKILL | + test_bit(S_GEO_CONFIGURED, &il->status) << S_GEO_CONFIGURED | + test_bit(S_FW_ERROR, &il->status) << S_FW_ERROR | + test_bit(S_EXIT_PENDING, &il->status) << S_EXIT_PENDING; + + /* + * We disabled and synchronized interrupt, and priv->mutex is taken, so + * here is the only thread which will program device registers, but + * still have lockdep assertions, so we are taking reg_lock. + */ + spin_lock_irq(&il->reg_lock); + /* FIXME: il_grab_nic_access if rfkill is off ? */ + + il4965_txq_ctx_stop(il); + il4965_rxq_stop(il); + /* Power-down device's busmaster DMA clocks */ + _il_wr_prph(il, APMG_CLK_DIS_REG, APMG_CLK_VAL_DMA_CLK_RQT); + udelay(5); + /* Make sure (redundant) we've released our request to stay awake */ + _il_clear_bit(il, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + /* Stop the device, and put it in low power state */ + _il_apm_stop(il); + + spin_unlock_irq(&il->reg_lock); + + il4965_txq_ctx_unmap(il); +exit: + memset(&il->card_alive, 0, sizeof(struct il_alive_resp)); + + dev_kfree_skb(il->beacon_skb); + il->beacon_skb = NULL; + + /* clear out any free frames */ + il4965_clear_free_frames(il); +} + +static void +il4965_down(struct il_priv *il) +{ + mutex_lock(&il->mutex); + __il4965_down(il); + mutex_unlock(&il->mutex); + + il4965_cancel_deferred_work(il); +} + + +static void +il4965_set_hw_ready(struct il_priv *il) +{ + int ret; + + il_set_bit(il, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_NIC_READY); + + /* See if we got it */ + ret = _il_poll_bit(il, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_NIC_READY, + CSR_HW_IF_CONFIG_REG_BIT_NIC_READY, + 100); + if (ret >= 0) + il->hw_ready = true; + + D_INFO("hardware %s ready\n", (il->hw_ready) ? "" : "not"); +} + +static void +il4965_prepare_card_hw(struct il_priv *il) +{ + int ret; + + il->hw_ready = false; + + il4965_set_hw_ready(il); + if (il->hw_ready) + return; + + /* If HW is not ready, prepare the conditions to check again */ + il_set_bit(il, CSR_HW_IF_CONFIG_REG, CSR_HW_IF_CONFIG_REG_PREPARE); + + ret = + _il_poll_bit(il, CSR_HW_IF_CONFIG_REG, + ~CSR_HW_IF_CONFIG_REG_BIT_NIC_PREPARE_DONE, + CSR_HW_IF_CONFIG_REG_BIT_NIC_PREPARE_DONE, 150000); + + /* HW should be ready by now, check again. */ + if (ret != -ETIMEDOUT) + il4965_set_hw_ready(il); +} + +#define MAX_HW_RESTARTS 5 + +static int +__il4965_up(struct il_priv *il) +{ + int i; + int ret; + + if (test_bit(S_EXIT_PENDING, &il->status)) { + IL_WARN("Exit pending; will not bring the NIC up\n"); + return -EIO; + } + + if (!il->ucode_data_backup.v_addr || !il->ucode_data.v_addr) { + IL_ERR("ucode not available for device bringup\n"); + return -EIO; + } + + ret = il4965_alloc_bcast_station(il); + if (ret) { + il_dealloc_bcast_stations(il); + return ret; + } + + il4965_prepare_card_hw(il); + if (!il->hw_ready) { + IL_ERR("HW not ready\n"); + return -EIO; + } + + /* If platform's RF_KILL switch is NOT set to KILL */ + if (_il_rd(il, CSR_GP_CNTRL) & CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW) + clear_bit(S_RFKILL, &il->status); + else { + set_bit(S_RFKILL, &il->status); + wiphy_rfkill_set_hw_state(il->hw->wiphy, true); + + il_enable_rfkill_int(il); + IL_WARN("Radio disabled by HW RF Kill switch\n"); + return 0; + } + + _il_wr(il, CSR_INT, 0xFFFFFFFF); + + /* must be initialised before il_hw_nic_init */ + il->cmd_queue = IL_DEFAULT_CMD_QUEUE_NUM; + + ret = il4965_hw_nic_init(il); + if (ret) { + IL_ERR("Unable to init nic\n"); + return ret; + } + + /* make sure rfkill handshake bits are cleared */ + _il_wr(il, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + _il_wr(il, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); + + /* clear (again), then enable host interrupts */ + _il_wr(il, CSR_INT, 0xFFFFFFFF); + il_enable_interrupts(il); + + /* really make sure rfkill handshake bits are cleared */ + _il_wr(il, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + _il_wr(il, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + + /* Copy original ucode data image from disk into backup cache. + * This will be used to initialize the on-board processor's + * data SRAM for a clean start when the runtime program first loads. */ + memcpy(il->ucode_data_backup.v_addr, il->ucode_data.v_addr, + il->ucode_data.len); + + for (i = 0; i < MAX_HW_RESTARTS; i++) { + + /* load bootstrap state machine, + * load bootstrap program into processor's memory, + * prepare to load the "initialize" uCode */ + ret = il->ops->load_ucode(il); + + if (ret) { + IL_ERR("Unable to set up bootstrap uCode: %d\n", ret); + continue; + } + + /* start card; "initialize" will load runtime ucode */ + il4965_nic_start(il); + + D_INFO(DRV_NAME " is coming up\n"); + + return 0; + } + + set_bit(S_EXIT_PENDING, &il->status); + __il4965_down(il); + clear_bit(S_EXIT_PENDING, &il->status); + + /* tried to restart and config the device for as long as our + * patience could withstand */ + IL_ERR("Unable to initialize device after %d attempts.\n", i); + return -EIO; +} + +/***************************************************************************** + * + * Workqueue callbacks + * + *****************************************************************************/ + +static void +il4965_bg_init_alive_start(struct work_struct *data) +{ + struct il_priv *il = + container_of(data, struct il_priv, init_alive_start.work); + + mutex_lock(&il->mutex); + if (test_bit(S_EXIT_PENDING, &il->status)) + goto out; + + il->ops->init_alive_start(il); +out: + mutex_unlock(&il->mutex); +} + +static void +il4965_bg_alive_start(struct work_struct *data) +{ + struct il_priv *il = + container_of(data, struct il_priv, alive_start.work); + + mutex_lock(&il->mutex); + if (test_bit(S_EXIT_PENDING, &il->status)) + goto out; + + il4965_alive_start(il); +out: + mutex_unlock(&il->mutex); +} + +static void +il4965_bg_run_time_calib_work(struct work_struct *work) +{ + struct il_priv *il = container_of(work, struct il_priv, + run_time_calib_work); + + mutex_lock(&il->mutex); + + if (test_bit(S_EXIT_PENDING, &il->status) || + test_bit(S_SCANNING, &il->status)) { + mutex_unlock(&il->mutex); + return; + } + + if (il->start_calib) { + il4965_chain_noise_calibration(il, (void *)&il->_4965.stats); + il4965_sensitivity_calibration(il, (void *)&il->_4965.stats); + } + + mutex_unlock(&il->mutex); +} + +static void +il4965_bg_restart(struct work_struct *data) +{ + struct il_priv *il = container_of(data, struct il_priv, restart); + + if (test_bit(S_EXIT_PENDING, &il->status)) + return; + + if (test_and_clear_bit(S_FW_ERROR, &il->status)) { + mutex_lock(&il->mutex); + il->is_open = 0; + + __il4965_down(il); + + mutex_unlock(&il->mutex); + il4965_cancel_deferred_work(il); + ieee80211_restart_hw(il->hw); + } else { + il4965_down(il); + + mutex_lock(&il->mutex); + if (test_bit(S_EXIT_PENDING, &il->status)) { + mutex_unlock(&il->mutex); + return; + } + + __il4965_up(il); + mutex_unlock(&il->mutex); + } +} + +static void +il4965_bg_rx_replenish(struct work_struct *data) +{ + struct il_priv *il = container_of(data, struct il_priv, rx_replenish); + + if (test_bit(S_EXIT_PENDING, &il->status)) + return; + + mutex_lock(&il->mutex); + il4965_rx_replenish(il); + mutex_unlock(&il->mutex); +} + +/***************************************************************************** + * + * mac80211 entry point functions + * + *****************************************************************************/ + +#define UCODE_READY_TIMEOUT (4 * HZ) + +/* + * Not a mac80211 entry point function, but it fits in with all the + * other mac80211 functions grouped here. + */ +static int +il4965_mac_setup_register(struct il_priv *il, u32 max_probe_length) +{ + int ret; + struct ieee80211_hw *hw = il->hw; + + hw->rate_control_algorithm = "iwl-4965-rs"; + + /* Tell mac80211 our characteristics */ + hw->flags = + IEEE80211_HW_SIGNAL_DBM | IEEE80211_HW_AMPDU_AGGREGATION | + IEEE80211_HW_NEED_DTIM_BEFORE_ASSOC | IEEE80211_HW_SPECTRUM_MGMT | + IEEE80211_HW_SUPPORTS_PS | IEEE80211_HW_SUPPORTS_DYNAMIC_PS; + if (il->cfg->sku & IL_SKU_N) + hw->flags |= + IEEE80211_HW_SUPPORTS_DYNAMIC_SMPS | + IEEE80211_HW_SUPPORTS_STATIC_SMPS; + + hw->sta_data_size = sizeof(struct il_station_priv); + hw->vif_data_size = sizeof(struct il_vif_priv); + + hw->wiphy->interface_modes = + BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_ADHOC); + + hw->wiphy->flags |= + WIPHY_FLAG_CUSTOM_REGULATORY | WIPHY_FLAG_DISABLE_BEACON_HINTS | + WIPHY_FLAG_IBSS_RSN; + + /* + * For now, disable PS by default because it affects + * RX performance significantly. + */ + hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + + hw->wiphy->max_scan_ssids = PROBE_OPTION_MAX; + /* we create the 802.11 header and a zero-length SSID element */ + hw->wiphy->max_scan_ie_len = max_probe_length - 24 - 2; + + /* Default value; 4 EDCA QOS priorities */ + hw->queues = 4; + + hw->max_listen_interval = IL_CONN_MAX_LISTEN_INTERVAL; + + if (il->bands[IEEE80211_BAND_2GHZ].n_channels) + il->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = + &il->bands[IEEE80211_BAND_2GHZ]; + if (il->bands[IEEE80211_BAND_5GHZ].n_channels) + il->hw->wiphy->bands[IEEE80211_BAND_5GHZ] = + &il->bands[IEEE80211_BAND_5GHZ]; + + il_leds_init(il); + + ret = ieee80211_register_hw(il->hw); + if (ret) { + IL_ERR("Failed to register hw (error %d)\n", ret); + return ret; + } + il->mac80211_registered = 1; + + return 0; +} + +int +il4965_mac_start(struct ieee80211_hw *hw) +{ + struct il_priv *il = hw->priv; + int ret; + + D_MAC80211("enter\n"); + + /* we should be verifying the device is ready to be opened */ + mutex_lock(&il->mutex); + ret = __il4965_up(il); + mutex_unlock(&il->mutex); + + if (ret) + return ret; + + if (il_is_rfkill(il)) + goto out; + + D_INFO("Start UP work done.\n"); + + /* Wait for START_ALIVE from Run Time ucode. Otherwise callbacks from + * mac80211 will not be run successfully. */ + ret = wait_event_timeout(il->wait_command_queue, + test_bit(S_READY, &il->status), + UCODE_READY_TIMEOUT); + if (!ret) { + if (!test_bit(S_READY, &il->status)) { + IL_ERR("START_ALIVE timeout after %dms.\n", + jiffies_to_msecs(UCODE_READY_TIMEOUT)); + return -ETIMEDOUT; + } + } + + il4965_led_enable(il); + +out: + il->is_open = 1; + D_MAC80211("leave\n"); + return 0; +} + +void +il4965_mac_stop(struct ieee80211_hw *hw) +{ + struct il_priv *il = hw->priv; + + D_MAC80211("enter\n"); + + if (!il->is_open) + return; + + il->is_open = 0; + + il4965_down(il); + + flush_workqueue(il->workqueue); + + /* User space software may expect getting rfkill changes + * even if interface is down */ + _il_wr(il, CSR_INT, 0xFFFFFFFF); + il_enable_rfkill_int(il); + + D_MAC80211("leave\n"); +} + +void +il4965_mac_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct il_priv *il = hw->priv; + + D_MACDUMP("enter\n"); + + D_TX("dev->xmit(%d bytes) at rate 0x%02x\n", skb->len, + ieee80211_get_tx_rate(hw, IEEE80211_SKB_CB(skb))->bitrate); + + if (il4965_tx_skb(il, control->sta, skb)) + dev_kfree_skb_any(skb); + + D_MACDUMP("leave\n"); +} + +void +il4965_mac_update_tkip_key(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, u32 iv32, u16 * phase1key) +{ + struct il_priv *il = hw->priv; + + D_MAC80211("enter\n"); + + il4965_update_tkip_key(il, keyconf, sta, iv32, phase1key); + + D_MAC80211("leave\n"); +} + +int +il4965_mac_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct il_priv *il = hw->priv; + int ret; + u8 sta_id; + bool is_default_wep_key = false; + + D_MAC80211("enter\n"); + + if (il->cfg->mod_params->sw_crypto) { + D_MAC80211("leave - hwcrypto disabled\n"); + return -EOPNOTSUPP; + } + + /* + * To support IBSS RSN, don't program group keys in IBSS, the + * hardware will then not attempt to decrypt the frames. + */ + if (vif->type == NL80211_IFTYPE_ADHOC && + !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) { + D_MAC80211("leave - ad-hoc group key\n"); + return -EOPNOTSUPP; + } + + sta_id = il_sta_id_or_broadcast(il, sta); + if (sta_id == IL_INVALID_STATION) + return -EINVAL; + + mutex_lock(&il->mutex); + il_scan_cancel_timeout(il, 100); + + /* + * If we are getting WEP group key and we didn't receive any key mapping + * so far, we are in legacy wep mode (group key only), otherwise we are + * in 1X mode. + * In legacy wep mode, we use another host command to the uCode. + */ + if ((key->cipher == WLAN_CIPHER_SUITE_WEP40 || + key->cipher == WLAN_CIPHER_SUITE_WEP104) && !sta) { + if (cmd == SET_KEY) + is_default_wep_key = !il->_4965.key_mapping_keys; + else + is_default_wep_key = + (key->hw_key_idx == HW_KEY_DEFAULT); + } + + switch (cmd) { + case SET_KEY: + if (is_default_wep_key) + ret = il4965_set_default_wep_key(il, key); + else + ret = il4965_set_dynamic_key(il, key, sta_id); + + D_MAC80211("enable hwcrypto key\n"); + break; + case DISABLE_KEY: + if (is_default_wep_key) + ret = il4965_remove_default_wep_key(il, key); + else + ret = il4965_remove_dynamic_key(il, key, sta_id); + + D_MAC80211("disable hwcrypto key\n"); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&il->mutex); + D_MAC80211("leave\n"); + + return ret; +} + +int +il4965_mac_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, u16 * ssn, + u8 buf_size) +{ + struct il_priv *il = hw->priv; + int ret = -EINVAL; + + D_HT("A-MPDU action on addr %pM tid %d\n", sta->addr, tid); + + if (!(il->cfg->sku & IL_SKU_N)) + return -EACCES; + + mutex_lock(&il->mutex); + + switch (action) { + case IEEE80211_AMPDU_RX_START: + D_HT("start Rx\n"); + ret = il4965_sta_rx_agg_start(il, sta, tid, *ssn); + break; + case IEEE80211_AMPDU_RX_STOP: + D_HT("stop Rx\n"); + ret = il4965_sta_rx_agg_stop(il, sta, tid); + if (test_bit(S_EXIT_PENDING, &il->status)) + ret = 0; + break; + case IEEE80211_AMPDU_TX_START: + D_HT("start Tx\n"); + ret = il4965_tx_agg_start(il, vif, sta, tid, ssn); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + D_HT("stop Tx\n"); + ret = il4965_tx_agg_stop(il, vif, sta, tid); + if (test_bit(S_EXIT_PENDING, &il->status)) + ret = 0; + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + ret = 0; + break; + } + mutex_unlock(&il->mutex); + + return ret; +} + +int +il4965_mac_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct il_priv *il = hw->priv; + struct il_station_priv *sta_priv = (void *)sta->drv_priv; + bool is_ap = vif->type == NL80211_IFTYPE_STATION; + int ret; + u8 sta_id; + + D_INFO("received request to add station %pM\n", sta->addr); + mutex_lock(&il->mutex); + D_INFO("proceeding to add station %pM\n", sta->addr); + sta_priv->common.sta_id = IL_INVALID_STATION; + + atomic_set(&sta_priv->pending_frames, 0); + + ret = + il_add_station_common(il, sta->addr, is_ap, sta, &sta_id); + if (ret) { + IL_ERR("Unable to add station %pM (%d)\n", sta->addr, ret); + /* Should we return success if return code is EEXIST ? */ + mutex_unlock(&il->mutex); + return ret; + } + + sta_priv->common.sta_id = sta_id; + + /* Initialize rate scaling */ + D_INFO("Initializing rate scaling for station %pM\n", sta->addr); + il4965_rs_rate_init(il, sta, sta_id); + mutex_unlock(&il->mutex); + + return 0; +} + +void +il4965_mac_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_channel_switch *ch_switch) +{ + struct il_priv *il = hw->priv; + const struct il_channel_info *ch_info; + struct ieee80211_conf *conf = &hw->conf; + struct ieee80211_channel *channel = ch_switch->chandef.chan; + struct il_ht_config *ht_conf = &il->current_ht_config; + u16 ch; + + D_MAC80211("enter\n"); + + mutex_lock(&il->mutex); + + if (il_is_rfkill(il)) + goto out; + + if (test_bit(S_EXIT_PENDING, &il->status) || + test_bit(S_SCANNING, &il->status) || + test_bit(S_CHANNEL_SWITCH_PENDING, &il->status)) + goto out; + + if (!il_is_associated(il)) + goto out; + + if (!il->ops->set_channel_switch) + goto out; + + ch = channel->hw_value; + if (le16_to_cpu(il->active.channel) == ch) + goto out; + + ch_info = il_get_channel_info(il, channel->band, ch); + if (!il_is_channel_valid(ch_info)) { + D_MAC80211("invalid channel\n"); + goto out; + } + + spin_lock_irq(&il->lock); + + il->current_ht_config.smps = conf->smps_mode; + + /* Configure HT40 channels */ + switch (cfg80211_get_chandef_type(&ch_switch->chandef)) { + case NL80211_CHAN_NO_HT: + case NL80211_CHAN_HT20: + il->ht.is_40mhz = false; + il->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE; + break; + case NL80211_CHAN_HT40MINUS: + il->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW; + il->ht.is_40mhz = true; + break; + case NL80211_CHAN_HT40PLUS: + il->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + il->ht.is_40mhz = true; + break; + } + + if ((le16_to_cpu(il->staging.channel) != ch)) + il->staging.flags = 0; + + il_set_rxon_channel(il, channel); + il_set_rxon_ht(il, ht_conf); + il_set_flags_for_band(il, channel->band, il->vif); + + spin_unlock_irq(&il->lock); + + il_set_rate(il); + /* + * at this point, staging_rxon has the + * configuration for channel switch + */ + set_bit(S_CHANNEL_SWITCH_PENDING, &il->status); + il->switch_channel = cpu_to_le16(ch); + if (il->ops->set_channel_switch(il, ch_switch)) { + clear_bit(S_CHANNEL_SWITCH_PENDING, &il->status); + il->switch_channel = 0; + ieee80211_chswitch_done(il->vif, false); + } + +out: + mutex_unlock(&il->mutex); + D_MAC80211("leave\n"); +} + +void +il4965_configure_filter(struct ieee80211_hw *hw, unsigned int changed_flags, + unsigned int *total_flags, u64 multicast) +{ + struct il_priv *il = hw->priv; + __le32 filter_or = 0, filter_nand = 0; + +#define CHK(test, flag) do { \ + if (*total_flags & (test)) \ + filter_or |= (flag); \ + else \ + filter_nand |= (flag); \ + } while (0) + + D_MAC80211("Enter: changed: 0x%x, total: 0x%x\n", changed_flags, + *total_flags); + + CHK(FIF_OTHER_BSS | FIF_PROMISC_IN_BSS, RXON_FILTER_PROMISC_MSK); + /* Setting _just_ RXON_FILTER_CTL2HOST_MSK causes FH errors */ + CHK(FIF_CONTROL, RXON_FILTER_CTL2HOST_MSK | RXON_FILTER_PROMISC_MSK); + CHK(FIF_BCN_PRBRESP_PROMISC, RXON_FILTER_BCON_AWARE_MSK); + +#undef CHK + + mutex_lock(&il->mutex); + + il->staging.filter_flags &= ~filter_nand; + il->staging.filter_flags |= filter_or; + + /* + * Not committing directly because hardware can perform a scan, + * but we'll eventually commit the filter flags change anyway. + */ + + mutex_unlock(&il->mutex); + + /* + * Receiving all multicast frames is always enabled by the + * default flags setup in il_connection_init_rx_config() + * since we currently do not support programming multicast + * filters into the device. + */ + *total_flags &= + FIF_OTHER_BSS | FIF_ALLMULTI | FIF_PROMISC_IN_BSS | + FIF_BCN_PRBRESP_PROMISC | FIF_CONTROL; +} + +/***************************************************************************** + * + * driver setup and teardown + * + *****************************************************************************/ + +static void +il4965_bg_txpower_work(struct work_struct *work) +{ + struct il_priv *il = container_of(work, struct il_priv, + txpower_work); + + mutex_lock(&il->mutex); + + /* If a scan happened to start before we got here + * then just return; the stats notification will + * kick off another scheduled work to compensate for + * any temperature delta we missed here. */ + if (test_bit(S_EXIT_PENDING, &il->status) || + test_bit(S_SCANNING, &il->status)) + goto out; + + /* Regardless of if we are associated, we must reconfigure the + * TX power since frames can be sent on non-radar channels while + * not associated */ + il->ops->send_tx_power(il); + + /* Update last_temperature to keep is_calib_needed from running + * when it isn't needed... */ + il->last_temperature = il->temperature; +out: + mutex_unlock(&il->mutex); +} + +static void +il4965_setup_deferred_work(struct il_priv *il) +{ + il->workqueue = create_singlethread_workqueue(DRV_NAME); + + init_waitqueue_head(&il->wait_command_queue); + + INIT_WORK(&il->restart, il4965_bg_restart); + INIT_WORK(&il->rx_replenish, il4965_bg_rx_replenish); + INIT_WORK(&il->run_time_calib_work, il4965_bg_run_time_calib_work); + INIT_DELAYED_WORK(&il->init_alive_start, il4965_bg_init_alive_start); + INIT_DELAYED_WORK(&il->alive_start, il4965_bg_alive_start); + + il_setup_scan_deferred_work(il); + + INIT_WORK(&il->txpower_work, il4965_bg_txpower_work); + + init_timer(&il->stats_periodic); + il->stats_periodic.data = (unsigned long)il; + il->stats_periodic.function = il4965_bg_stats_periodic; + + init_timer(&il->watchdog); + il->watchdog.data = (unsigned long)il; + il->watchdog.function = il_bg_watchdog; + + tasklet_init(&il->irq_tasklet, + (void (*)(unsigned long))il4965_irq_tasklet, + (unsigned long)il); +} + +static void +il4965_cancel_deferred_work(struct il_priv *il) +{ + cancel_work_sync(&il->txpower_work); + cancel_delayed_work_sync(&il->init_alive_start); + cancel_delayed_work(&il->alive_start); + cancel_work_sync(&il->run_time_calib_work); + + il_cancel_scan_deferred_work(il); + + del_timer_sync(&il->stats_periodic); +} + +static void +il4965_init_hw_rates(struct il_priv *il, struct ieee80211_rate *rates) +{ + int i; + + for (i = 0; i < RATE_COUNT_LEGACY; i++) { + rates[i].bitrate = il_rates[i].ieee * 5; + rates[i].hw_value = i; /* Rate scaling will work on idxes */ + rates[i].hw_value_short = i; + rates[i].flags = 0; + if ((i >= IL_FIRST_CCK_RATE) && (i <= IL_LAST_CCK_RATE)) { + /* + * If CCK != 1M then set short preamble rate flag. + */ + rates[i].flags |= + (il_rates[i].plcp == + RATE_1M_PLCP) ? 0 : IEEE80211_RATE_SHORT_PREAMBLE; + } + } +} + +/* + * Acquire il->lock before calling this function ! + */ +void +il4965_set_wr_ptrs(struct il_priv *il, int txq_id, u32 idx) +{ + il_wr(il, HBUS_TARG_WRPTR, (idx & 0xff) | (txq_id << 8)); + il_wr_prph(il, IL49_SCD_QUEUE_RDPTR(txq_id), idx); +} + +void +il4965_tx_queue_set_status(struct il_priv *il, struct il_tx_queue *txq, + int tx_fifo_id, int scd_retry) +{ + int txq_id = txq->q.id; + + /* Find out whether to activate Tx queue */ + int active = test_bit(txq_id, &il->txq_ctx_active_msk) ? 1 : 0; + + /* Set up and activate */ + il_wr_prph(il, IL49_SCD_QUEUE_STATUS_BITS(txq_id), + (active << IL49_SCD_QUEUE_STTS_REG_POS_ACTIVE) | + (tx_fifo_id << IL49_SCD_QUEUE_STTS_REG_POS_TXF) | + (scd_retry << IL49_SCD_QUEUE_STTS_REG_POS_WSL) | + (scd_retry << IL49_SCD_QUEUE_STTS_REG_POS_SCD_ACK) | + IL49_SCD_QUEUE_STTS_REG_MSK); + + txq->sched_retry = scd_retry; + + D_INFO("%s %s Queue %d on AC %d\n", active ? "Activate" : "Deactivate", + scd_retry ? "BA" : "AC", txq_id, tx_fifo_id); +} + +static const struct ieee80211_ops il4965_mac_ops = { + .tx = il4965_mac_tx, + .start = il4965_mac_start, + .stop = il4965_mac_stop, + .add_interface = il_mac_add_interface, + .remove_interface = il_mac_remove_interface, + .change_interface = il_mac_change_interface, + .config = il_mac_config, + .configure_filter = il4965_configure_filter, + .set_key = il4965_mac_set_key, + .update_tkip_key = il4965_mac_update_tkip_key, + .conf_tx = il_mac_conf_tx, + .reset_tsf = il_mac_reset_tsf, + .bss_info_changed = il_mac_bss_info_changed, + .ampdu_action = il4965_mac_ampdu_action, + .hw_scan = il_mac_hw_scan, + .sta_add = il4965_mac_sta_add, + .sta_remove = il_mac_sta_remove, + .channel_switch = il4965_mac_channel_switch, + .tx_last_beacon = il_mac_tx_last_beacon, + .flush = il_mac_flush, +}; + +static int +il4965_init_drv(struct il_priv *il) +{ + int ret; + + spin_lock_init(&il->sta_lock); + spin_lock_init(&il->hcmd_lock); + + INIT_LIST_HEAD(&il->free_frames); + + mutex_init(&il->mutex); + + il->ieee_channels = NULL; + il->ieee_rates = NULL; + il->band = IEEE80211_BAND_2GHZ; + + il->iw_mode = NL80211_IFTYPE_STATION; + il->current_ht_config.smps = IEEE80211_SMPS_STATIC; + il->missed_beacon_threshold = IL_MISSED_BEACON_THRESHOLD_DEF; + + /* initialize force reset */ + il->force_reset.reset_duration = IL_DELAY_NEXT_FORCE_FW_RELOAD; + + /* Choose which receivers/antennas to use */ + if (il->ops->set_rxon_chain) + il->ops->set_rxon_chain(il); + + il_init_scan_params(il); + + ret = il_init_channel_map(il); + if (ret) { + IL_ERR("initializing regulatory failed: %d\n", ret); + goto err; + } + + ret = il_init_geos(il); + if (ret) { + IL_ERR("initializing geos failed: %d\n", ret); + goto err_free_channel_map; + } + il4965_init_hw_rates(il, il->ieee_rates); + + return 0; + +err_free_channel_map: + il_free_channel_map(il); +err: + return ret; +} + +static void +il4965_uninit_drv(struct il_priv *il) +{ + il_free_geos(il); + il_free_channel_map(il); + kfree(il->scan_cmd); +} + +static void +il4965_hw_detect(struct il_priv *il) +{ + il->hw_rev = _il_rd(il, CSR_HW_REV); + il->hw_wa_rev = _il_rd(il, CSR_HW_REV_WA_REG); + il->rev_id = il->pci_dev->revision; + D_INFO("HW Revision ID = 0x%X\n", il->rev_id); +} + +static struct il_sensitivity_ranges il4965_sensitivity = { + .min_nrg_cck = 97, + .max_nrg_cck = 0, /* not used, set to 0 */ + + .auto_corr_min_ofdm = 85, + .auto_corr_min_ofdm_mrc = 170, + .auto_corr_min_ofdm_x1 = 105, + .auto_corr_min_ofdm_mrc_x1 = 220, + + .auto_corr_max_ofdm = 120, + .auto_corr_max_ofdm_mrc = 210, + .auto_corr_max_ofdm_x1 = 140, + .auto_corr_max_ofdm_mrc_x1 = 270, + + .auto_corr_min_cck = 125, + .auto_corr_max_cck = 200, + .auto_corr_min_cck_mrc = 200, + .auto_corr_max_cck_mrc = 400, + + .nrg_th_cck = 100, + .nrg_th_ofdm = 100, + + .barker_corr_th_min = 190, + .barker_corr_th_min_mrc = 390, + .nrg_th_cca = 62, +}; + +static void +il4965_set_hw_params(struct il_priv *il) +{ + il->hw_params.bcast_id = IL4965_BROADCAST_ID; + il->hw_params.max_rxq_size = RX_QUEUE_SIZE; + il->hw_params.max_rxq_log = RX_QUEUE_SIZE_LOG; + if (il->cfg->mod_params->amsdu_size_8K) + il->hw_params.rx_page_order = get_order(IL_RX_BUF_SIZE_8K); + else + il->hw_params.rx_page_order = get_order(IL_RX_BUF_SIZE_4K); + + il->hw_params.max_beacon_itrvl = IL_MAX_UCODE_BEACON_INTERVAL; + + if (il->cfg->mod_params->disable_11n) + il->cfg->sku &= ~IL_SKU_N; + + if (il->cfg->mod_params->num_of_queues >= IL_MIN_NUM_QUEUES && + il->cfg->mod_params->num_of_queues <= IL49_NUM_QUEUES) + il->cfg->num_of_queues = + il->cfg->mod_params->num_of_queues; + + il->hw_params.max_txq_num = il->cfg->num_of_queues; + il->hw_params.dma_chnl_num = FH49_TCSR_CHNL_NUM; + il->hw_params.scd_bc_tbls_size = + il->cfg->num_of_queues * + sizeof(struct il4965_scd_bc_tbl); + + il->hw_params.tfd_size = sizeof(struct il_tfd); + il->hw_params.max_stations = IL4965_STATION_COUNT; + il->hw_params.max_data_size = IL49_RTC_DATA_SIZE; + il->hw_params.max_inst_size = IL49_RTC_INST_SIZE; + il->hw_params.max_bsm_size = BSM_SRAM_SIZE; + il->hw_params.ht40_channel = BIT(IEEE80211_BAND_5GHZ); + + il->hw_params.rx_wrt_ptr_reg = FH49_RSCSR_CHNL0_WPTR; + + il->hw_params.tx_chains_num = il4965_num_of_ant(il->cfg->valid_tx_ant); + il->hw_params.rx_chains_num = il4965_num_of_ant(il->cfg->valid_rx_ant); + il->hw_params.valid_tx_ant = il->cfg->valid_tx_ant; + il->hw_params.valid_rx_ant = il->cfg->valid_rx_ant; + + il->hw_params.ct_kill_threshold = + CELSIUS_TO_KELVIN(CT_KILL_THRESHOLD_LEGACY); + + il->hw_params.sens = &il4965_sensitivity; + il->hw_params.beacon_time_tsf_bits = IL4965_EXT_BEACON_TIME_POS; +} + +static int +il4965_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int err = 0; + struct il_priv *il; + struct ieee80211_hw *hw; + struct il_cfg *cfg = (struct il_cfg *)(ent->driver_data); + unsigned long flags; + u16 pci_cmd; + + /************************ + * 1. Allocating HW data + ************************/ + + hw = ieee80211_alloc_hw(sizeof(struct il_priv), &il4965_mac_ops); + if (!hw) { + err = -ENOMEM; + goto out; + } + il = hw->priv; + il->hw = hw; + SET_IEEE80211_DEV(hw, &pdev->dev); + + D_INFO("*** LOAD DRIVER ***\n"); + il->cfg = cfg; + il->ops = &il4965_ops; +#ifdef CPTCFG_IWLEGACY_DEBUGFS + il->debugfs_ops = &il4965_debugfs_ops; +#endif + il->pci_dev = pdev; + il->inta_mask = CSR_INI_SET_MASK; + + /************************** + * 2. Initializing PCI bus + **************************/ + pci_disable_link_state(pdev, + PCIE_LINK_STATE_L0S | PCIE_LINK_STATE_L1 | + PCIE_LINK_STATE_CLKPM); + + if (pci_enable_device(pdev)) { + err = -ENODEV; + goto out_ieee80211_free_hw; + } + + pci_set_master(pdev); + + err = pci_set_dma_mask(pdev, DMA_BIT_MASK(36)); + if (!err) + err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(36)); + if (err) { + err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + if (!err) + err = + pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)); + /* both attempts failed: */ + if (err) { + IL_WARN("No suitable DMA available.\n"); + goto out_pci_disable_device; + } + } + + err = pci_request_regions(pdev, DRV_NAME); + if (err) + goto out_pci_disable_device; + + pci_set_drvdata(pdev, il); + + /*********************** + * 3. Read REV register + ***********************/ + il->hw_base = pci_ioremap_bar(pdev, 0); + if (!il->hw_base) { + err = -ENODEV; + goto out_pci_release_regions; + } + + D_INFO("pci_resource_len = 0x%08llx\n", + (unsigned long long)pci_resource_len(pdev, 0)); + D_INFO("pci_resource_base = %p\n", il->hw_base); + + /* these spin locks will be used in apm_ops.init and EEPROM access + * we should init now + */ + spin_lock_init(&il->reg_lock); + spin_lock_init(&il->lock); + + /* + * stop and reset the on-board processor just in case it is in a + * strange state ... like being left stranded by a primary kernel + * and this is now the kdump kernel trying to start up + */ + _il_wr(il, CSR_RESET, CSR_RESET_REG_FLAG_NEVO_RESET); + + il4965_hw_detect(il); + IL_INFO("Detected %s, REV=0x%X\n", il->cfg->name, il->hw_rev); + + /* We disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state */ + pci_write_config_byte(pdev, PCI_CFG_RETRY_TIMEOUT, 0x00); + + il4965_prepare_card_hw(il); + if (!il->hw_ready) { + IL_WARN("Failed, HW not ready\n"); + err = -EIO; + goto out_iounmap; + } + + /***************** + * 4. Read EEPROM + *****************/ + /* Read the EEPROM */ + err = il_eeprom_init(il); + if (err) { + IL_ERR("Unable to init EEPROM\n"); + goto out_iounmap; + } + err = il4965_eeprom_check_version(il); + if (err) + goto out_free_eeprom; + + /* extract MAC Address */ + il4965_eeprom_get_mac(il, il->addresses[0].addr); + D_INFO("MAC address: %pM\n", il->addresses[0].addr); + il->hw->wiphy->addresses = il->addresses; + il->hw->wiphy->n_addresses = 1; + + /************************ + * 5. Setup HW constants + ************************/ + il4965_set_hw_params(il); + + /******************* + * 6. Setup il + *******************/ + + err = il4965_init_drv(il); + if (err) + goto out_free_eeprom; + /* At this point both hw and il are initialized. */ + + /******************** + * 7. Setup services + ********************/ + spin_lock_irqsave(&il->lock, flags); + il_disable_interrupts(il); + spin_unlock_irqrestore(&il->lock, flags); + + pci_enable_msi(il->pci_dev); + + err = request_irq(il->pci_dev->irq, il_isr, IRQF_SHARED, DRV_NAME, il); + if (err) { + IL_ERR("Error allocating IRQ %d\n", il->pci_dev->irq); + goto out_disable_msi; + } + + il4965_setup_deferred_work(il); + il4965_setup_handlers(il); + + /********************************************* + * 8. Enable interrupts and read RFKILL state + *********************************************/ + + /* enable rfkill interrupt: hw bug w/a */ + pci_read_config_word(il->pci_dev, PCI_COMMAND, &pci_cmd); + if (pci_cmd & PCI_COMMAND_INTX_DISABLE) { + pci_cmd &= ~PCI_COMMAND_INTX_DISABLE; + pci_write_config_word(il->pci_dev, PCI_COMMAND, pci_cmd); + } + + il_enable_rfkill_int(il); + + /* If platform's RF_KILL switch is NOT set to KILL */ + if (_il_rd(il, CSR_GP_CNTRL) & CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW) + clear_bit(S_RFKILL, &il->status); + else + set_bit(S_RFKILL, &il->status); + + wiphy_rfkill_set_hw_state(il->hw->wiphy, + test_bit(S_RFKILL, &il->status)); + + il_power_initialize(il); + + init_completion(&il->_4965.firmware_loading_complete); + + err = il4965_request_firmware(il, true); + if (err) + goto out_destroy_workqueue; + + return 0; + +out_destroy_workqueue: + destroy_workqueue(il->workqueue); + il->workqueue = NULL; + free_irq(il->pci_dev->irq, il); +out_disable_msi: + pci_disable_msi(il->pci_dev); + il4965_uninit_drv(il); +out_free_eeprom: + il_eeprom_free(il); +out_iounmap: + iounmap(il->hw_base); +out_pci_release_regions: + pci_set_drvdata(pdev, NULL); + pci_release_regions(pdev); +out_pci_disable_device: + pci_disable_device(pdev); +out_ieee80211_free_hw: + ieee80211_free_hw(il->hw); +out: + return err; +} + +static void +il4965_pci_remove(struct pci_dev *pdev) +{ + struct il_priv *il = pci_get_drvdata(pdev); + unsigned long flags; + + if (!il) + return; + + wait_for_completion(&il->_4965.firmware_loading_complete); + + D_INFO("*** UNLOAD DRIVER ***\n"); + + il_dbgfs_unregister(il); + sysfs_remove_group(&pdev->dev.kobj, &il_attribute_group); + + /* ieee80211_unregister_hw call wil cause il_mac_stop to + * to be called and il4965_down since we are removing the device + * we need to set S_EXIT_PENDING bit. + */ + set_bit(S_EXIT_PENDING, &il->status); + + il_leds_exit(il); + + if (il->mac80211_registered) { + ieee80211_unregister_hw(il->hw); + il->mac80211_registered = 0; + } else { + il4965_down(il); + } + + /* + * Make sure device is reset to low power before unloading driver. + * This may be redundant with il4965_down(), but there are paths to + * run il4965_down() without calling apm_ops.stop(), and there are + * paths to avoid running il4965_down() at all before leaving driver. + * This (inexpensive) call *makes sure* device is reset. + */ + il_apm_stop(il); + + /* make sure we flush any pending irq or + * tasklet for the driver + */ + spin_lock_irqsave(&il->lock, flags); + il_disable_interrupts(il); + spin_unlock_irqrestore(&il->lock, flags); + + il4965_synchronize_irq(il); + + il4965_dealloc_ucode_pci(il); + + if (il->rxq.bd) + il4965_rx_queue_free(il, &il->rxq); + il4965_hw_txq_ctx_free(il); + + il_eeprom_free(il); + + /*netif_stop_queue(dev); */ + flush_workqueue(il->workqueue); + + /* ieee80211_unregister_hw calls il_mac_stop, which flushes + * il->workqueue... so we can't take down the workqueue + * until now... */ + destroy_workqueue(il->workqueue); + il->workqueue = NULL; + + free_irq(il->pci_dev->irq, il); + pci_disable_msi(il->pci_dev); + iounmap(il->hw_base); + pci_release_regions(pdev); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); + + il4965_uninit_drv(il); + + dev_kfree_skb(il->beacon_skb); + + ieee80211_free_hw(il->hw); +} + +/* + * Activate/Deactivate Tx DMA/FIFO channels according tx fifos mask + * must be called under il->lock and mac access + */ +void +il4965_txq_set_sched(struct il_priv *il, u32 mask) +{ + il_wr_prph(il, IL49_SCD_TXFACT, mask); +} + +/***************************************************************************** + * + * driver and module entry point + * + *****************************************************************************/ + +/* Hardware specific file defines the PCI IDs table for that hardware module */ +static DEFINE_PCI_DEVICE_TABLE(il4965_hw_card_ids) = { + {IL_PCI_DEVICE(0x4229, PCI_ANY_ID, il4965_cfg)}, + {IL_PCI_DEVICE(0x4230, PCI_ANY_ID, il4965_cfg)}, + {0} +}; +MODULE_DEVICE_TABLE(pci, il4965_hw_card_ids); + +static struct pci_driver il4965_driver = { + .name = DRV_NAME, + .id_table = il4965_hw_card_ids, + .probe = il4965_pci_probe, + .remove = il4965_pci_remove, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) + .driver.pm = IL_LEGACY_PM_OPS, +#elif defined(CONFIG_PM) + .suspend = il_pci_suspend_compat, + .resume = il_pci_resume_compat, +#endif +}; + +static int __init +il4965_init(void) +{ + + int ret; + pr_info(DRV_DESCRIPTION ", " DRV_VERSION "\n"); + pr_info(DRV_COPYRIGHT "\n"); + + ret = il4965_rate_control_register(); + if (ret) { + pr_err("Unable to register rate control algorithm: %d\n", ret); + return ret; + } + + ret = pci_register_driver(&il4965_driver); + if (ret) { + pr_err("Unable to initialize PCI module\n"); + goto error_register; + } + + return ret; + +error_register: + il4965_rate_control_unregister(); + return ret; +} + +static void __exit +il4965_exit(void) +{ + pci_unregister_driver(&il4965_driver); + il4965_rate_control_unregister(); +} + +module_exit(il4965_exit); +module_init(il4965_init); + +#ifdef CPTCFG_IWLEGACY_DEBUG +module_param_named(debug, il_debug_level, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "debug output mask"); +#endif + +module_param_named(swcrypto, il4965_mod_params.sw_crypto, int, S_IRUGO); +MODULE_PARM_DESC(swcrypto, "using crypto in software (default 0 [hardware])"); +module_param_named(queues_num, il4965_mod_params.num_of_queues, int, S_IRUGO); +MODULE_PARM_DESC(queues_num, "number of hw queues."); +module_param_named(11n_disable, il4965_mod_params.disable_11n, int, S_IRUGO); +MODULE_PARM_DESC(11n_disable, "disable 11n functionality"); +module_param_named(amsdu_size_8K, il4965_mod_params.amsdu_size_8K, int, + S_IRUGO); +MODULE_PARM_DESC(amsdu_size_8K, "enable 8K amsdu size"); +module_param_named(fw_restart, il4965_mod_params.restart_fw, int, S_IRUGO); +MODULE_PARM_DESC(fw_restart, "restart firmware in case of error"); diff --git a/drivers/net/wireless/iwlegacy/4965-rs.c b/drivers/net/wireless/iwlegacy/4965-rs.c new file mode 100644 index 0000000..d7cda1c --- /dev/null +++ b/drivers/net/wireless/iwlegacy/4965-rs.c @@ -0,0 +1,2836 @@ +/****************************************************************************** + * + * Copyright(c) 2005 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "common.h" +#include "4965.h" + +#define IL4965_RS_NAME "iwl-4965-rs" + +#define NUM_TRY_BEFORE_ANT_TOGGLE 1 +#define IL_NUMBER_TRY 1 +#define IL_HT_NUMBER_TRY 3 + +#define RATE_MAX_WINDOW 62 /* # tx in history win */ +#define RATE_MIN_FAILURE_TH 6 /* min failures to calc tpt */ +#define RATE_MIN_SUCCESS_TH 8 /* min successes to calc tpt */ + +/* max allowed rate miss before sync LQ cmd */ +#define IL_MISSED_RATE_MAX 15 +/* max time to accum history 2 seconds */ +#define RATE_SCALE_FLUSH_INTVL (3*HZ) + +static u8 rs_ht_to_legacy[] = { + RATE_6M_IDX, RATE_6M_IDX, + RATE_6M_IDX, RATE_6M_IDX, + RATE_6M_IDX, + RATE_6M_IDX, RATE_9M_IDX, + RATE_12M_IDX, RATE_18M_IDX, + RATE_24M_IDX, RATE_36M_IDX, + RATE_48M_IDX, RATE_54M_IDX +}; + +static const u8 ant_toggle_lookup[] = { + /*ANT_NONE -> */ ANT_NONE, + /*ANT_A -> */ ANT_B, + /*ANT_B -> */ ANT_C, + /*ANT_AB -> */ ANT_BC, + /*ANT_C -> */ ANT_A, + /*ANT_AC -> */ ANT_AB, + /*ANT_BC -> */ ANT_AC, + /*ANT_ABC -> */ ANT_ABC, +}; + +#define IL_DECLARE_RATE_INFO(r, s, ip, in, rp, rn, pp, np) \ + [RATE_##r##M_IDX] = { RATE_##r##M_PLCP, \ + RATE_SISO_##s##M_PLCP, \ + RATE_MIMO2_##s##M_PLCP,\ + RATE_##r##M_IEEE, \ + RATE_##ip##M_IDX, \ + RATE_##in##M_IDX, \ + RATE_##rp##M_IDX, \ + RATE_##rn##M_IDX, \ + RATE_##pp##M_IDX, \ + RATE_##np##M_IDX } + +/* + * Parameter order: + * rate, ht rate, prev rate, next rate, prev tgg rate, next tgg rate + * + * If there isn't a valid next or previous rate then INV is used which + * maps to RATE_INVALID + * + */ +const struct il_rate_info il_rates[RATE_COUNT] = { + IL_DECLARE_RATE_INFO(1, INV, INV, 2, INV, 2, INV, 2), /* 1mbps */ + IL_DECLARE_RATE_INFO(2, INV, 1, 5, 1, 5, 1, 5), /* 2mbps */ + IL_DECLARE_RATE_INFO(5, INV, 2, 6, 2, 11, 2, 11), /*5.5mbps */ + IL_DECLARE_RATE_INFO(11, INV, 9, 12, 9, 12, 5, 18), /* 11mbps */ + IL_DECLARE_RATE_INFO(6, 6, 5, 9, 5, 11, 5, 11), /* 6mbps */ + IL_DECLARE_RATE_INFO(9, 6, 6, 11, 6, 11, 5, 11), /* 9mbps */ + IL_DECLARE_RATE_INFO(12, 12, 11, 18, 11, 18, 11, 18), /* 12mbps */ + IL_DECLARE_RATE_INFO(18, 18, 12, 24, 12, 24, 11, 24), /* 18mbps */ + IL_DECLARE_RATE_INFO(24, 24, 18, 36, 18, 36, 18, 36), /* 24mbps */ + IL_DECLARE_RATE_INFO(36, 36, 24, 48, 24, 48, 24, 48), /* 36mbps */ + IL_DECLARE_RATE_INFO(48, 48, 36, 54, 36, 54, 36, 54), /* 48mbps */ + IL_DECLARE_RATE_INFO(54, 54, 48, INV, 48, INV, 48, INV),/* 54mbps */ + IL_DECLARE_RATE_INFO(60, 60, 48, INV, 48, INV, 48, INV),/* 60mbps */ +}; + +static int +il4965_hwrate_to_plcp_idx(u32 rate_n_flags) +{ + int idx = 0; + + /* HT rate format */ + if (rate_n_flags & RATE_MCS_HT_MSK) { + idx = (rate_n_flags & 0xff); + + if (idx >= RATE_MIMO2_6M_PLCP) + idx = idx - RATE_MIMO2_6M_PLCP; + + idx += IL_FIRST_OFDM_RATE; + /* skip 9M not supported in ht */ + if (idx >= RATE_9M_IDX) + idx += 1; + if (idx >= IL_FIRST_OFDM_RATE && idx <= IL_LAST_OFDM_RATE) + return idx; + + /* legacy rate format, search for match in table */ + } else { + for (idx = 0; idx < ARRAY_SIZE(il_rates); idx++) + if (il_rates[idx].plcp == (rate_n_flags & 0xFF)) + return idx; + } + + return -1; +} + +static void il4965_rs_rate_scale_perform(struct il_priv *il, + struct sk_buff *skb, + struct ieee80211_sta *sta, + struct il_lq_sta *lq_sta); +static void il4965_rs_fill_link_cmd(struct il_priv *il, + struct il_lq_sta *lq_sta, u32 rate_n_flags); +static void il4965_rs_stay_in_table(struct il_lq_sta *lq_sta, + bool force_search); + +#ifdef CPTCFG_MAC80211_DEBUGFS +static void il4965_rs_dbgfs_set_mcs(struct il_lq_sta *lq_sta, + u32 *rate_n_flags, int idx); +#else +static void +il4965_rs_dbgfs_set_mcs(struct il_lq_sta *lq_sta, u32 * rate_n_flags, int idx) +{ +} +#endif + +/** + * The following tables contain the expected throughput metrics for all rates + * + * 1, 2, 5.5, 11, 6, 9, 12, 18, 24, 36, 48, 54, 60 MBits + * + * where invalid entries are zeros. + * + * CCK rates are only valid in legacy table and will only be used in G + * (2.4 GHz) band. + */ + +static s32 expected_tpt_legacy[RATE_COUNT] = { + 7, 13, 35, 58, 40, 57, 72, 98, 121, 154, 177, 186, 0 +}; + +static s32 expected_tpt_siso20MHz[4][RATE_COUNT] = { + {0, 0, 0, 0, 42, 0, 76, 102, 124, 158, 183, 193, 202}, /* Norm */ + {0, 0, 0, 0, 46, 0, 82, 110, 132, 167, 192, 202, 210}, /* SGI */ + {0, 0, 0, 0, 48, 0, 93, 135, 176, 251, 319, 351, 381}, /* AGG */ + {0, 0, 0, 0, 53, 0, 102, 149, 193, 275, 348, 381, 413}, /* AGG+SGI */ +}; + +static s32 expected_tpt_siso40MHz[4][RATE_COUNT] = { + {0, 0, 0, 0, 77, 0, 127, 160, 184, 220, 242, 250, 257}, /* Norm */ + {0, 0, 0, 0, 83, 0, 135, 169, 193, 229, 250, 257, 264}, /* SGI */ + {0, 0, 0, 0, 96, 0, 182, 259, 328, 451, 553, 598, 640}, /* AGG */ + {0, 0, 0, 0, 106, 0, 199, 282, 357, 487, 593, 640, 683}, /* AGG+SGI */ +}; + +static s32 expected_tpt_mimo2_20MHz[4][RATE_COUNT] = { + {0, 0, 0, 0, 74, 0, 123, 155, 179, 213, 235, 243, 250}, /* Norm */ + {0, 0, 0, 0, 81, 0, 131, 164, 187, 221, 242, 250, 256}, /* SGI */ + {0, 0, 0, 0, 92, 0, 175, 250, 317, 436, 534, 578, 619}, /* AGG */ + {0, 0, 0, 0, 102, 0, 192, 273, 344, 470, 573, 619, 660}, /* AGG+SGI */ +}; + +static s32 expected_tpt_mimo2_40MHz[4][RATE_COUNT] = { + {0, 0, 0, 0, 123, 0, 182, 214, 235, 264, 279, 285, 289}, /* Norm */ + {0, 0, 0, 0, 131, 0, 191, 222, 242, 270, 284, 289, 293}, /* SGI */ + {0, 0, 0, 0, 180, 0, 327, 446, 545, 708, 828, 878, 922}, /* AGG */ + {0, 0, 0, 0, 197, 0, 355, 481, 584, 752, 872, 922, 966}, /* AGG+SGI */ +}; + +/* mbps, mcs */ +static const struct il_rate_mcs_info il_rate_mcs[RATE_COUNT] = { + {"1", "BPSK DSSS"}, + {"2", "QPSK DSSS"}, + {"5.5", "BPSK CCK"}, + {"11", "QPSK CCK"}, + {"6", "BPSK 1/2"}, + {"9", "BPSK 1/2"}, + {"12", "QPSK 1/2"}, + {"18", "QPSK 3/4"}, + {"24", "16QAM 1/2"}, + {"36", "16QAM 3/4"}, + {"48", "64QAM 2/3"}, + {"54", "64QAM 3/4"}, + {"60", "64QAM 5/6"}, +}; + +#define MCS_IDX_PER_STREAM (8) + +static inline u8 +il4965_rs_extract_rate(u32 rate_n_flags) +{ + return (u8) (rate_n_flags & 0xFF); +} + +static void +il4965_rs_rate_scale_clear_win(struct il_rate_scale_data *win) +{ + win->data = 0; + win->success_counter = 0; + win->success_ratio = IL_INVALID_VALUE; + win->counter = 0; + win->average_tpt = IL_INVALID_VALUE; + win->stamp = 0; +} + +static inline u8 +il4965_rs_is_valid_ant(u8 valid_antenna, u8 ant_type) +{ + return (ant_type & valid_antenna) == ant_type; +} + +/* + * removes the old data from the stats. All data that is older than + * TID_MAX_TIME_DIFF, will be deleted. + */ +static void +il4965_rs_tl_rm_old_stats(struct il_traffic_load *tl, u32 curr_time) +{ + /* The oldest age we want to keep */ + u32 oldest_time = curr_time - TID_MAX_TIME_DIFF; + + while (tl->queue_count && tl->time_stamp < oldest_time) { + tl->total -= tl->packet_count[tl->head]; + tl->packet_count[tl->head] = 0; + tl->time_stamp += TID_QUEUE_CELL_SPACING; + tl->queue_count--; + tl->head++; + if (tl->head >= TID_QUEUE_MAX_SIZE) + tl->head = 0; + } +} + +/* + * increment traffic load value for tid and also remove + * any old values if passed the certain time period + */ +static u8 +il4965_rs_tl_add_packet(struct il_lq_sta *lq_data, struct ieee80211_hdr *hdr) +{ + u32 curr_time = jiffies_to_msecs(jiffies); + u32 time_diff; + s32 idx; + struct il_traffic_load *tl = NULL; + u8 tid; + + if (ieee80211_is_data_qos(hdr->frame_control)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + tid = qc[0] & 0xf; + } else + return MAX_TID_COUNT; + + if (unlikely(tid >= TID_MAX_LOAD_COUNT)) + return MAX_TID_COUNT; + + tl = &lq_data->load[tid]; + + curr_time -= curr_time % TID_ROUND_VALUE; + + /* Happens only for the first packet. Initialize the data */ + if (!(tl->queue_count)) { + tl->total = 1; + tl->time_stamp = curr_time; + tl->queue_count = 1; + tl->head = 0; + tl->packet_count[0] = 1; + return MAX_TID_COUNT; + } + + time_diff = TIME_WRAP_AROUND(tl->time_stamp, curr_time); + idx = time_diff / TID_QUEUE_CELL_SPACING; + + /* The history is too long: remove data that is older than */ + /* TID_MAX_TIME_DIFF */ + if (idx >= TID_QUEUE_MAX_SIZE) + il4965_rs_tl_rm_old_stats(tl, curr_time); + + idx = (tl->head + idx) % TID_QUEUE_MAX_SIZE; + tl->packet_count[idx] = tl->packet_count[idx] + 1; + tl->total = tl->total + 1; + + if ((idx + 1) > tl->queue_count) + tl->queue_count = idx + 1; + + return tid; +} + +/* + get the traffic load value for tid +*/ +static u32 +il4965_rs_tl_get_load(struct il_lq_sta *lq_data, u8 tid) +{ + u32 curr_time = jiffies_to_msecs(jiffies); + u32 time_diff; + s32 idx; + struct il_traffic_load *tl = NULL; + + if (tid >= TID_MAX_LOAD_COUNT) + return 0; + + tl = &(lq_data->load[tid]); + + curr_time -= curr_time % TID_ROUND_VALUE; + + if (!(tl->queue_count)) + return 0; + + time_diff = TIME_WRAP_AROUND(tl->time_stamp, curr_time); + idx = time_diff / TID_QUEUE_CELL_SPACING; + + /* The history is too long: remove data that is older than */ + /* TID_MAX_TIME_DIFF */ + if (idx >= TID_QUEUE_MAX_SIZE) + il4965_rs_tl_rm_old_stats(tl, curr_time); + + return tl->total; +} + +static int +il4965_rs_tl_turn_on_agg_for_tid(struct il_priv *il, struct il_lq_sta *lq_data, + u8 tid, struct ieee80211_sta *sta) +{ + int ret = -EAGAIN; + u32 load; + + load = il4965_rs_tl_get_load(lq_data, tid); + + if (load > IL_AGG_LOAD_THRESHOLD) { + D_HT("Starting Tx agg: STA: %pM tid: %d\n", sta->addr, tid); + ret = ieee80211_start_tx_ba_session(sta, tid, 5000); + if (ret == -EAGAIN) { + /* + * driver and mac80211 is out of sync + * this might be cause by reloading firmware + * stop the tx ba session here + */ + IL_ERR("Fail start Tx agg on tid: %d\n", tid); + ieee80211_stop_tx_ba_session(sta, tid); + } + } else + D_HT("Aggregation not enabled for tid %d because load = %u\n", + tid, load); + + return ret; +} + +static void +il4965_rs_tl_turn_on_agg(struct il_priv *il, u8 tid, struct il_lq_sta *lq_data, + struct ieee80211_sta *sta) +{ + if (tid < TID_MAX_LOAD_COUNT) + il4965_rs_tl_turn_on_agg_for_tid(il, lq_data, tid, sta); + else + IL_ERR("tid exceeds max load count: %d/%d\n", tid, + TID_MAX_LOAD_COUNT); +} + +static inline int +il4965_get_il4965_num_of_ant_from_rate(u32 rate_n_flags) +{ + return !!(rate_n_flags & RATE_MCS_ANT_A_MSK) + + !!(rate_n_flags & RATE_MCS_ANT_B_MSK) + + !!(rate_n_flags & RATE_MCS_ANT_C_MSK); +} + +/* + * Static function to get the expected throughput from an il_scale_tbl_info + * that wraps a NULL pointer check + */ +static s32 +il4965_get_expected_tpt(struct il_scale_tbl_info *tbl, int rs_idx) +{ + if (tbl->expected_tpt) + return tbl->expected_tpt[rs_idx]; + return 0; +} + +/** + * il4965_rs_collect_tx_data - Update the success/failure sliding win + * + * We keep a sliding win of the last 62 packets transmitted + * at this rate. win->data contains the bitmask of successful + * packets. + */ +static int +il4965_rs_collect_tx_data(struct il_scale_tbl_info *tbl, int scale_idx, + int attempts, int successes) +{ + struct il_rate_scale_data *win = NULL; + static const u64 mask = (((u64) 1) << (RATE_MAX_WINDOW - 1)); + s32 fail_count, tpt; + + if (scale_idx < 0 || scale_idx >= RATE_COUNT) + return -EINVAL; + + /* Select win for current tx bit rate */ + win = &(tbl->win[scale_idx]); + + /* Get expected throughput */ + tpt = il4965_get_expected_tpt(tbl, scale_idx); + + /* + * Keep track of only the latest 62 tx frame attempts in this rate's + * history win; anything older isn't really relevant any more. + * If we have filled up the sliding win, drop the oldest attempt; + * if the oldest attempt (highest bit in bitmap) shows "success", + * subtract "1" from the success counter (this is the main reason + * we keep these bitmaps!). + */ + while (attempts > 0) { + if (win->counter >= RATE_MAX_WINDOW) { + + /* remove earliest */ + win->counter = RATE_MAX_WINDOW - 1; + + if (win->data & mask) { + win->data &= ~mask; + win->success_counter--; + } + } + + /* Increment frames-attempted counter */ + win->counter++; + + /* Shift bitmap by one frame to throw away oldest history */ + win->data <<= 1; + + /* Mark the most recent #successes attempts as successful */ + if (successes > 0) { + win->success_counter++; + win->data |= 0x1; + successes--; + } + + attempts--; + } + + /* Calculate current success ratio, avoid divide-by-0! */ + if (win->counter > 0) + win->success_ratio = + 128 * (100 * win->success_counter) / win->counter; + else + win->success_ratio = IL_INVALID_VALUE; + + fail_count = win->counter - win->success_counter; + + /* Calculate average throughput, if we have enough history. */ + if (fail_count >= RATE_MIN_FAILURE_TH || + win->success_counter >= RATE_MIN_SUCCESS_TH) + win->average_tpt = (win->success_ratio * tpt + 64) / 128; + else + win->average_tpt = IL_INVALID_VALUE; + + /* Tag this win as having been updated */ + win->stamp = jiffies; + + return 0; +} + +/* + * Fill uCode API rate_n_flags field, based on "search" or "active" table. + */ +static u32 +il4965_rate_n_flags_from_tbl(struct il_priv *il, struct il_scale_tbl_info *tbl, + int idx, u8 use_green) +{ + u32 rate_n_flags = 0; + + if (is_legacy(tbl->lq_type)) { + rate_n_flags = il_rates[idx].plcp; + if (idx >= IL_FIRST_CCK_RATE && idx <= IL_LAST_CCK_RATE) + rate_n_flags |= RATE_MCS_CCK_MSK; + + } else if (is_Ht(tbl->lq_type)) { + if (idx > IL_LAST_OFDM_RATE) { + IL_ERR("Invalid HT rate idx %d\n", idx); + idx = IL_LAST_OFDM_RATE; + } + rate_n_flags = RATE_MCS_HT_MSK; + + if (is_siso(tbl->lq_type)) + rate_n_flags |= il_rates[idx].plcp_siso; + else + rate_n_flags |= il_rates[idx].plcp_mimo2; + } else { + IL_ERR("Invalid tbl->lq_type %d\n", tbl->lq_type); + } + + rate_n_flags |= + ((tbl->ant_type << RATE_MCS_ANT_POS) & RATE_MCS_ANT_ABC_MSK); + + if (is_Ht(tbl->lq_type)) { + if (tbl->is_ht40) { + if (tbl->is_dup) + rate_n_flags |= RATE_MCS_DUP_MSK; + else + rate_n_flags |= RATE_MCS_HT40_MSK; + } + if (tbl->is_SGI) + rate_n_flags |= RATE_MCS_SGI_MSK; + + if (use_green) { + rate_n_flags |= RATE_MCS_GF_MSK; + if (is_siso(tbl->lq_type) && tbl->is_SGI) { + rate_n_flags &= ~RATE_MCS_SGI_MSK; + IL_ERR("GF was set with SGI:SISO\n"); + } + } + } + return rate_n_flags; +} + +/* + * Interpret uCode API's rate_n_flags format, + * fill "search" or "active" tx mode table. + */ +static int +il4965_rs_get_tbl_info_from_mcs(const u32 rate_n_flags, + enum ieee80211_band band, + struct il_scale_tbl_info *tbl, int *rate_idx) +{ + u32 ant_msk = (rate_n_flags & RATE_MCS_ANT_ABC_MSK); + u8 il4965_num_of_ant = + il4965_get_il4965_num_of_ant_from_rate(rate_n_flags); + u8 mcs; + + memset(tbl, 0, sizeof(struct il_scale_tbl_info)); + *rate_idx = il4965_hwrate_to_plcp_idx(rate_n_flags); + + if (*rate_idx == RATE_INVALID) { + *rate_idx = -1; + return -EINVAL; + } + tbl->is_SGI = 0; /* default legacy setup */ + tbl->is_ht40 = 0; + tbl->is_dup = 0; + tbl->ant_type = (ant_msk >> RATE_MCS_ANT_POS); + tbl->lq_type = LQ_NONE; + tbl->max_search = IL_MAX_SEARCH; + + /* legacy rate format */ + if (!(rate_n_flags & RATE_MCS_HT_MSK)) { + if (il4965_num_of_ant == 1) { + if (band == IEEE80211_BAND_5GHZ) + tbl->lq_type = LQ_A; + else + tbl->lq_type = LQ_G; + } + /* HT rate format */ + } else { + if (rate_n_flags & RATE_MCS_SGI_MSK) + tbl->is_SGI = 1; + + if ((rate_n_flags & RATE_MCS_HT40_MSK) || + (rate_n_flags & RATE_MCS_DUP_MSK)) + tbl->is_ht40 = 1; + + if (rate_n_flags & RATE_MCS_DUP_MSK) + tbl->is_dup = 1; + + mcs = il4965_rs_extract_rate(rate_n_flags); + + /* SISO */ + if (mcs <= RATE_SISO_60M_PLCP) { + if (il4965_num_of_ant == 1) + tbl->lq_type = LQ_SISO; /*else NONE */ + /* MIMO2 */ + } else { + if (il4965_num_of_ant == 2) + tbl->lq_type = LQ_MIMO2; + } + } + return 0; +} + +/* switch to another antenna/antennas and return 1 */ +/* if no other valid antenna found, return 0 */ +static int +il4965_rs_toggle_antenna(u32 valid_ant, u32 *rate_n_flags, + struct il_scale_tbl_info *tbl) +{ + u8 new_ant_type; + + if (!tbl->ant_type || tbl->ant_type > ANT_ABC) + return 0; + + if (!il4965_rs_is_valid_ant(valid_ant, tbl->ant_type)) + return 0; + + new_ant_type = ant_toggle_lookup[tbl->ant_type]; + + while (new_ant_type != tbl->ant_type && + !il4965_rs_is_valid_ant(valid_ant, new_ant_type)) + new_ant_type = ant_toggle_lookup[new_ant_type]; + + if (new_ant_type == tbl->ant_type) + return 0; + + tbl->ant_type = new_ant_type; + *rate_n_flags &= ~RATE_MCS_ANT_ABC_MSK; + *rate_n_flags |= new_ant_type << RATE_MCS_ANT_POS; + return 1; +} + +/** + * Green-field mode is valid if the station supports it and + * there are no non-GF stations present in the BSS. + */ +static bool +il4965_rs_use_green(struct il_priv *il, struct ieee80211_sta *sta) +{ + return (sta->ht_cap.cap & IEEE80211_HT_CAP_GRN_FLD) && + !il->ht.non_gf_sta_present; +} + +/** + * il4965_rs_get_supported_rates - get the available rates + * + * if management frame or broadcast frame only return + * basic available rates. + * + */ +static u16 +il4965_rs_get_supported_rates(struct il_lq_sta *lq_sta, + struct ieee80211_hdr *hdr, + enum il_table_type rate_type) +{ + if (is_legacy(rate_type)) { + return lq_sta->active_legacy_rate; + } else { + if (is_siso(rate_type)) + return lq_sta->active_siso_rate; + else + return lq_sta->active_mimo2_rate; + } +} + +static u16 +il4965_rs_get_adjacent_rate(struct il_priv *il, u8 idx, u16 rate_mask, + int rate_type) +{ + u8 high = RATE_INVALID; + u8 low = RATE_INVALID; + + /* 802.11A or ht walks to the next literal adjacent rate in + * the rate table */ + if (is_a_band(rate_type) || !is_legacy(rate_type)) { + int i; + u32 mask; + + /* Find the previous rate that is in the rate mask */ + i = idx - 1; + for (mask = (1 << i); i >= 0; i--, mask >>= 1) { + if (rate_mask & mask) { + low = i; + break; + } + } + + /* Find the next rate that is in the rate mask */ + i = idx + 1; + for (mask = (1 << i); i < RATE_COUNT; i++, mask <<= 1) { + if (rate_mask & mask) { + high = i; + break; + } + } + + return (high << 8) | low; + } + + low = idx; + while (low != RATE_INVALID) { + low = il_rates[low].prev_rs; + if (low == RATE_INVALID) + break; + if (rate_mask & (1 << low)) + break; + D_RATE("Skipping masked lower rate: %d\n", low); + } + + high = idx; + while (high != RATE_INVALID) { + high = il_rates[high].next_rs; + if (high == RATE_INVALID) + break; + if (rate_mask & (1 << high)) + break; + D_RATE("Skipping masked higher rate: %d\n", high); + } + + return (high << 8) | low; +} + +static u32 +il4965_rs_get_lower_rate(struct il_lq_sta *lq_sta, + struct il_scale_tbl_info *tbl, u8 scale_idx, + u8 ht_possible) +{ + s32 low; + u16 rate_mask; + u16 high_low; + u8 switch_to_legacy = 0; + u8 is_green = lq_sta->is_green; + struct il_priv *il = lq_sta->drv; + + /* check if we need to switch from HT to legacy rates. + * assumption is that mandatory rates (1Mbps or 6Mbps) + * are always supported (spec demand) */ + if (!is_legacy(tbl->lq_type) && (!ht_possible || !scale_idx)) { + switch_to_legacy = 1; + scale_idx = rs_ht_to_legacy[scale_idx]; + if (lq_sta->band == IEEE80211_BAND_5GHZ) + tbl->lq_type = LQ_A; + else + tbl->lq_type = LQ_G; + + if (il4965_num_of_ant(tbl->ant_type) > 1) + tbl->ant_type = + il4965_first_antenna(il->hw_params.valid_tx_ant); + + tbl->is_ht40 = 0; + tbl->is_SGI = 0; + tbl->max_search = IL_MAX_SEARCH; + } + + rate_mask = il4965_rs_get_supported_rates(lq_sta, NULL, tbl->lq_type); + + /* Mask with station rate restriction */ + if (is_legacy(tbl->lq_type)) { + /* supp_rates has no CCK bits in A mode */ + if (lq_sta->band == IEEE80211_BAND_5GHZ) + rate_mask = + (u16) (rate_mask & + (lq_sta->supp_rates << IL_FIRST_OFDM_RATE)); + else + rate_mask = (u16) (rate_mask & lq_sta->supp_rates); + } + + /* If we switched from HT to legacy, check current rate */ + if (switch_to_legacy && (rate_mask & (1 << scale_idx))) { + low = scale_idx; + goto out; + } + + high_low = + il4965_rs_get_adjacent_rate(lq_sta->drv, scale_idx, rate_mask, + tbl->lq_type); + low = high_low & 0xff; + + if (low == RATE_INVALID) + low = scale_idx; + +out: + return il4965_rate_n_flags_from_tbl(lq_sta->drv, tbl, low, is_green); +} + +/* + * Simple function to compare two rate scale table types + */ +static bool +il4965_table_type_matches(struct il_scale_tbl_info *a, + struct il_scale_tbl_info *b) +{ + return (a->lq_type == b->lq_type && a->ant_type == b->ant_type && + a->is_SGI == b->is_SGI); +} + +/* + * mac80211 sends us Tx status + */ +static void +il4965_rs_tx_status(void *il_r, struct ieee80211_supported_band *sband, + struct ieee80211_sta *sta, void *il_sta, + struct sk_buff *skb) +{ + int legacy_success; + int retries; + int rs_idx, mac_idx, i; + struct il_lq_sta *lq_sta = il_sta; + struct il_link_quality_cmd *table; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct il_priv *il = (struct il_priv *)il_r; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + enum mac80211_rate_control_flags mac_flags; + u32 tx_rate; + struct il_scale_tbl_info tbl_type; + struct il_scale_tbl_info *curr_tbl, *other_tbl, *tmp_tbl; + + D_RATE("get frame ack response, update rate scale win\n"); + + /* Treat uninitialized rate scaling data same as non-existing. */ + if (!lq_sta) { + D_RATE("Station rate scaling not created yet.\n"); + return; + } else if (!lq_sta->drv) { + D_RATE("Rate scaling not initialized yet.\n"); + return; + } + + if (!ieee80211_is_data(hdr->frame_control) || + (info->flags & IEEE80211_TX_CTL_NO_ACK)) + return; + + /* This packet was aggregated but doesn't carry status info */ + if ((info->flags & IEEE80211_TX_CTL_AMPDU) && + !(info->flags & IEEE80211_TX_STAT_AMPDU)) + return; + + /* + * Ignore this Tx frame response if its initial rate doesn't match + * that of latest Link Quality command. There may be stragglers + * from a previous Link Quality command, but we're no longer interested + * in those; they're either from the "active" mode while we're trying + * to check "search" mode, or a prior "search" mode after we've moved + * to a new "search" mode (which might become the new "active" mode). + */ + table = &lq_sta->lq; + tx_rate = le32_to_cpu(table->rs_table[0].rate_n_flags); + il4965_rs_get_tbl_info_from_mcs(tx_rate, il->band, &tbl_type, &rs_idx); + if (il->band == IEEE80211_BAND_5GHZ) + rs_idx -= IL_FIRST_OFDM_RATE; + mac_flags = info->status.rates[0].flags; + mac_idx = info->status.rates[0].idx; + /* For HT packets, map MCS to PLCP */ + if (mac_flags & IEEE80211_TX_RC_MCS) { + mac_idx &= RATE_MCS_CODE_MSK; /* Remove # of streams */ + if (mac_idx >= (RATE_9M_IDX - IL_FIRST_OFDM_RATE)) + mac_idx++; + /* + * mac80211 HT idx is always zero-idxed; we need to move + * HT OFDM rates after CCK rates in 2.4 GHz band + */ + if (il->band == IEEE80211_BAND_2GHZ) + mac_idx += IL_FIRST_OFDM_RATE; + } + /* Here we actually compare this rate to the latest LQ command */ + if (mac_idx < 0 || + tbl_type.is_SGI != !!(mac_flags & IEEE80211_TX_RC_SHORT_GI) || + tbl_type.is_ht40 != !!(mac_flags & IEEE80211_TX_RC_40_MHZ_WIDTH) || + tbl_type.is_dup != !!(mac_flags & IEEE80211_TX_RC_DUP_DATA) || + tbl_type.ant_type != info->status.antenna || + !!(tx_rate & RATE_MCS_HT_MSK) != !!(mac_flags & IEEE80211_TX_RC_MCS) + || !!(tx_rate & RATE_MCS_GF_MSK) != + !!(mac_flags & IEEE80211_TX_RC_GREEN_FIELD) || rs_idx != mac_idx) { + D_RATE("initial rate %d does not match %d (0x%x)\n", mac_idx, + rs_idx, tx_rate); + /* + * Since rates mis-match, the last LQ command may have failed. + * After IL_MISSED_RATE_MAX mis-matches, resync the uCode with + * ... driver. + */ + lq_sta->missed_rate_counter++; + if (lq_sta->missed_rate_counter > IL_MISSED_RATE_MAX) { + lq_sta->missed_rate_counter = 0; + il_send_lq_cmd(il, &lq_sta->lq, CMD_ASYNC, false); + } + /* Regardless, ignore this status info for outdated rate */ + return; + } else + /* Rate did match, so reset the missed_rate_counter */ + lq_sta->missed_rate_counter = 0; + + /* Figure out if rate scale algorithm is in active or search table */ + if (il4965_table_type_matches + (&tbl_type, &(lq_sta->lq_info[lq_sta->active_tbl]))) { + curr_tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + other_tbl = &(lq_sta->lq_info[1 - lq_sta->active_tbl]); + } else + if (il4965_table_type_matches + (&tbl_type, &lq_sta->lq_info[1 - lq_sta->active_tbl])) { + curr_tbl = &(lq_sta->lq_info[1 - lq_sta->active_tbl]); + other_tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + } else { + D_RATE("Neither active nor search matches tx rate\n"); + tmp_tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + D_RATE("active- lq:%x, ant:%x, SGI:%d\n", tmp_tbl->lq_type, + tmp_tbl->ant_type, tmp_tbl->is_SGI); + tmp_tbl = &(lq_sta->lq_info[1 - lq_sta->active_tbl]); + D_RATE("search- lq:%x, ant:%x, SGI:%d\n", tmp_tbl->lq_type, + tmp_tbl->ant_type, tmp_tbl->is_SGI); + D_RATE("actual- lq:%x, ant:%x, SGI:%d\n", tbl_type.lq_type, + tbl_type.ant_type, tbl_type.is_SGI); + /* + * no matching table found, let's by-pass the data collection + * and continue to perform rate scale to find the rate table + */ + il4965_rs_stay_in_table(lq_sta, true); + goto done; + } + + /* + * Updating the frame history depends on whether packets were + * aggregated. + * + * For aggregation, all packets were transmitted at the same rate, the + * first idx into rate scale table. + */ + if (info->flags & IEEE80211_TX_STAT_AMPDU) { + tx_rate = le32_to_cpu(table->rs_table[0].rate_n_flags); + il4965_rs_get_tbl_info_from_mcs(tx_rate, il->band, &tbl_type, + &rs_idx); + il4965_rs_collect_tx_data(curr_tbl, rs_idx, + info->status.ampdu_len, + info->status.ampdu_ack_len); + + /* Update success/fail counts if not searching for new mode */ + if (lq_sta->stay_in_tbl) { + lq_sta->total_success += info->status.ampdu_ack_len; + lq_sta->total_failed += + (info->status.ampdu_len - + info->status.ampdu_ack_len); + } + } else { + /* + * For legacy, update frame history with for each Tx retry. + */ + retries = info->status.rates[0].count - 1; + /* HW doesn't send more than 15 retries */ + retries = min(retries, 15); + + /* The last transmission may have been successful */ + legacy_success = !!(info->flags & IEEE80211_TX_STAT_ACK); + /* Collect data for each rate used during failed TX attempts */ + for (i = 0; i <= retries; ++i) { + tx_rate = le32_to_cpu(table->rs_table[i].rate_n_flags); + il4965_rs_get_tbl_info_from_mcs(tx_rate, il->band, + &tbl_type, &rs_idx); + /* + * Only collect stats if retried rate is in the same RS + * table as active/search. + */ + if (il4965_table_type_matches(&tbl_type, curr_tbl)) + tmp_tbl = curr_tbl; + else if (il4965_table_type_matches + (&tbl_type, other_tbl)) + tmp_tbl = other_tbl; + else + continue; + il4965_rs_collect_tx_data(tmp_tbl, rs_idx, 1, + i < + retries ? 0 : legacy_success); + } + + /* Update success/fail counts if not searching for new mode */ + if (lq_sta->stay_in_tbl) { + lq_sta->total_success += legacy_success; + lq_sta->total_failed += retries + (1 - legacy_success); + } + } + /* The last TX rate is cached in lq_sta; it's set in if/else above */ + lq_sta->last_rate_n_flags = tx_rate; +done: + /* See if there's a better rate or modulation mode to try. */ + if (sta->supp_rates[sband->band]) + il4965_rs_rate_scale_perform(il, skb, sta, lq_sta); +} + +/* + * Begin a period of staying with a selected modulation mode. + * Set "stay_in_tbl" flag to prevent any mode switches. + * Set frame tx success limits according to legacy vs. high-throughput, + * and reset overall (spanning all rates) tx success history stats. + * These control how long we stay using same modulation mode before + * searching for a new mode. + */ +static void +il4965_rs_set_stay_in_table(struct il_priv *il, u8 is_legacy, + struct il_lq_sta *lq_sta) +{ + D_RATE("we are staying in the same table\n"); + lq_sta->stay_in_tbl = 1; /* only place this gets set */ + if (is_legacy) { + lq_sta->table_count_limit = IL_LEGACY_TBL_COUNT; + lq_sta->max_failure_limit = IL_LEGACY_FAILURE_LIMIT; + lq_sta->max_success_limit = IL_LEGACY_SUCCESS_LIMIT; + } else { + lq_sta->table_count_limit = IL_NONE_LEGACY_TBL_COUNT; + lq_sta->max_failure_limit = IL_NONE_LEGACY_FAILURE_LIMIT; + lq_sta->max_success_limit = IL_NONE_LEGACY_SUCCESS_LIMIT; + } + lq_sta->table_count = 0; + lq_sta->total_failed = 0; + lq_sta->total_success = 0; + lq_sta->flush_timer = jiffies; + lq_sta->action_counter = 0; +} + +/* + * Find correct throughput table for given mode of modulation + */ +static void +il4965_rs_set_expected_tpt_table(struct il_lq_sta *lq_sta, + struct il_scale_tbl_info *tbl) +{ + /* Used to choose among HT tables */ + s32(*ht_tbl_pointer)[RATE_COUNT]; + + /* Check for invalid LQ type */ + if (WARN_ON_ONCE(!is_legacy(tbl->lq_type) && !is_Ht(tbl->lq_type))) { + tbl->expected_tpt = expected_tpt_legacy; + return; + } + + /* Legacy rates have only one table */ + if (is_legacy(tbl->lq_type)) { + tbl->expected_tpt = expected_tpt_legacy; + return; + } + + /* Choose among many HT tables depending on number of streams + * (SISO/MIMO2), channel width (20/40), SGI, and aggregation + * status */ + if (is_siso(tbl->lq_type) && (!tbl->is_ht40 || lq_sta->is_dup)) + ht_tbl_pointer = expected_tpt_siso20MHz; + else if (is_siso(tbl->lq_type)) + ht_tbl_pointer = expected_tpt_siso40MHz; + else if (is_mimo2(tbl->lq_type) && (!tbl->is_ht40 || lq_sta->is_dup)) + ht_tbl_pointer = expected_tpt_mimo2_20MHz; + else /* if (is_mimo2(tbl->lq_type)) <-- must be true */ + ht_tbl_pointer = expected_tpt_mimo2_40MHz; + + if (!tbl->is_SGI && !lq_sta->is_agg) /* Normal */ + tbl->expected_tpt = ht_tbl_pointer[0]; + else if (tbl->is_SGI && !lq_sta->is_agg) /* SGI */ + tbl->expected_tpt = ht_tbl_pointer[1]; + else if (!tbl->is_SGI && lq_sta->is_agg) /* AGG */ + tbl->expected_tpt = ht_tbl_pointer[2]; + else /* AGG+SGI */ + tbl->expected_tpt = ht_tbl_pointer[3]; +} + +/* + * Find starting rate for new "search" high-throughput mode of modulation. + * Goal is to find lowest expected rate (under perfect conditions) that is + * above the current measured throughput of "active" mode, to give new mode + * a fair chance to prove itself without too many challenges. + * + * This gets called when transitioning to more aggressive modulation + * (i.e. legacy to SISO or MIMO, or SISO to MIMO), as well as less aggressive + * (i.e. MIMO to SISO). When moving to MIMO, bit rate will typically need + * to decrease to match "active" throughput. When moving from MIMO to SISO, + * bit rate will typically need to increase, but not if performance was bad. + */ +static s32 +il4965_rs_get_best_rate(struct il_priv *il, struct il_lq_sta *lq_sta, + struct il_scale_tbl_info *tbl, /* "search" */ + u16 rate_mask, s8 idx) +{ + /* "active" values */ + struct il_scale_tbl_info *active_tbl = + &(lq_sta->lq_info[lq_sta->active_tbl]); + s32 active_sr = active_tbl->win[idx].success_ratio; + s32 active_tpt = active_tbl->expected_tpt[idx]; + + /* expected "search" throughput */ + s32 *tpt_tbl = tbl->expected_tpt; + + s32 new_rate, high, low, start_hi; + u16 high_low; + s8 rate = idx; + + new_rate = high = low = start_hi = RATE_INVALID; + + for (;;) { + high_low = + il4965_rs_get_adjacent_rate(il, rate, rate_mask, + tbl->lq_type); + + low = high_low & 0xff; + high = (high_low >> 8) & 0xff; + + /* + * Lower the "search" bit rate, to give new "search" mode + * approximately the same throughput as "active" if: + * + * 1) "Active" mode has been working modestly well (but not + * great), and expected "search" throughput (under perfect + * conditions) at candidate rate is above the actual + * measured "active" throughput (but less than expected + * "active" throughput under perfect conditions). + * OR + * 2) "Active" mode has been working perfectly or very well + * and expected "search" throughput (under perfect + * conditions) at candidate rate is above expected + * "active" throughput (under perfect conditions). + */ + if ((100 * tpt_tbl[rate] > lq_sta->last_tpt && + (active_sr > RATE_DECREASE_TH && active_sr <= RATE_HIGH_TH + && tpt_tbl[rate] <= active_tpt)) || + (active_sr >= RATE_SCALE_SWITCH && + tpt_tbl[rate] > active_tpt)) { + + /* (2nd or later pass) + * If we've already tried to raise the rate, and are + * now trying to lower it, use the higher rate. */ + if (start_hi != RATE_INVALID) { + new_rate = start_hi; + break; + } + + new_rate = rate; + + /* Loop again with lower rate */ + if (low != RATE_INVALID) + rate = low; + + /* Lower rate not available, use the original */ + else + break; + + /* Else try to raise the "search" rate to match "active" */ + } else { + /* (2nd or later pass) + * If we've already tried to lower the rate, and are + * now trying to raise it, use the lower rate. */ + if (new_rate != RATE_INVALID) + break; + + /* Loop again with higher rate */ + else if (high != RATE_INVALID) { + start_hi = high; + rate = high; + + /* Higher rate not available, use the original */ + } else { + new_rate = rate; + break; + } + } + } + + return new_rate; +} + +/* + * Set up search table for MIMO2 + */ +static int +il4965_rs_switch_to_mimo2(struct il_priv *il, struct il_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, + struct il_scale_tbl_info *tbl, int idx) +{ + u16 rate_mask; + s32 rate; + s8 is_green = lq_sta->is_green; + + if (!conf_is_ht(conf) || !sta->ht_cap.ht_supported) + return -1; + + if (sta->smps_mode == IEEE80211_SMPS_STATIC) + return -1; + + /* Need both Tx chains/antennas to support MIMO */ + if (il->hw_params.tx_chains_num < 2) + return -1; + + D_RATE("LQ: try to switch to MIMO2\n"); + + tbl->lq_type = LQ_MIMO2; + tbl->is_dup = lq_sta->is_dup; + tbl->action = 0; + tbl->max_search = IL_MAX_SEARCH; + rate_mask = lq_sta->active_mimo2_rate; + + if (il_is_ht40_tx_allowed(il, &sta->ht_cap)) + tbl->is_ht40 = 1; + else + tbl->is_ht40 = 0; + + il4965_rs_set_expected_tpt_table(lq_sta, tbl); + + rate = il4965_rs_get_best_rate(il, lq_sta, tbl, rate_mask, idx); + + D_RATE("LQ: MIMO2 best rate %d mask %X\n", rate, rate_mask); + if (rate == RATE_INVALID || !((1 << rate) & rate_mask)) { + D_RATE("Can't switch with idx %d rate mask %x\n", rate, + rate_mask); + return -1; + } + tbl->current_rate = + il4965_rate_n_flags_from_tbl(il, tbl, rate, is_green); + + D_RATE("LQ: Switch to new mcs %X idx is green %X\n", tbl->current_rate, + is_green); + return 0; +} + +/* + * Set up search table for SISO + */ +static int +il4965_rs_switch_to_siso(struct il_priv *il, struct il_lq_sta *lq_sta, + struct ieee80211_conf *conf, struct ieee80211_sta *sta, + struct il_scale_tbl_info *tbl, int idx) +{ + u16 rate_mask; + u8 is_green = lq_sta->is_green; + s32 rate; + + if (!conf_is_ht(conf) || !sta->ht_cap.ht_supported) + return -1; + + D_RATE("LQ: try to switch to SISO\n"); + + tbl->is_dup = lq_sta->is_dup; + tbl->lq_type = LQ_SISO; + tbl->action = 0; + tbl->max_search = IL_MAX_SEARCH; + rate_mask = lq_sta->active_siso_rate; + + if (il_is_ht40_tx_allowed(il, &sta->ht_cap)) + tbl->is_ht40 = 1; + else + tbl->is_ht40 = 0; + + if (is_green) + tbl->is_SGI = 0; /*11n spec: no SGI in SISO+Greenfield */ + + il4965_rs_set_expected_tpt_table(lq_sta, tbl); + rate = il4965_rs_get_best_rate(il, lq_sta, tbl, rate_mask, idx); + + D_RATE("LQ: get best rate %d mask %X\n", rate, rate_mask); + if (rate == RATE_INVALID || !((1 << rate) & rate_mask)) { + D_RATE("can not switch with idx %d rate mask %x\n", rate, + rate_mask); + return -1; + } + tbl->current_rate = + il4965_rate_n_flags_from_tbl(il, tbl, rate, is_green); + D_RATE("LQ: Switch to new mcs %X idx is green %X\n", tbl->current_rate, + is_green); + return 0; +} + +/* + * Try to switch to new modulation mode from legacy + */ +static int +il4965_rs_move_legacy_other(struct il_priv *il, struct il_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, int idx) +{ + struct il_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + struct il_scale_tbl_info *search_tbl = + &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + struct il_rate_scale_data *win = &(tbl->win[idx]); + u32 sz = + (sizeof(struct il_scale_tbl_info) - + (sizeof(struct il_rate_scale_data) * RATE_COUNT)); + u8 start_action; + u8 valid_tx_ant = il->hw_params.valid_tx_ant; + u8 tx_chains_num = il->hw_params.tx_chains_num; + int ret = 0; + u8 update_search_tbl_counter = 0; + + tbl->action = IL_LEGACY_SWITCH_SISO; + + start_action = tbl->action; + for (;;) { + lq_sta->action_counter++; + switch (tbl->action) { + case IL_LEGACY_SWITCH_ANTENNA1: + case IL_LEGACY_SWITCH_ANTENNA2: + D_RATE("LQ: Legacy toggle Antenna\n"); + + if ((tbl->action == IL_LEGACY_SWITCH_ANTENNA1 && + tx_chains_num <= 1) || + (tbl->action == IL_LEGACY_SWITCH_ANTENNA2 && + tx_chains_num <= 2)) + break; + + /* Don't change antenna if success has been great */ + if (win->success_ratio >= IL_RS_GOOD_RATIO) + break; + + /* Set up search table to try other antenna */ + memcpy(search_tbl, tbl, sz); + + if (il4965_rs_toggle_antenna + (valid_tx_ant, &search_tbl->current_rate, + search_tbl)) { + update_search_tbl_counter = 1; + il4965_rs_set_expected_tpt_table(lq_sta, + search_tbl); + goto out; + } + break; + case IL_LEGACY_SWITCH_SISO: + D_RATE("LQ: Legacy switch to SISO\n"); + + /* Set up search table to try SISO */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + ret = + il4965_rs_switch_to_siso(il, lq_sta, conf, sta, + search_tbl, idx); + if (!ret) { + lq_sta->action_counter = 0; + goto out; + } + + break; + case IL_LEGACY_SWITCH_MIMO2_AB: + case IL_LEGACY_SWITCH_MIMO2_AC: + case IL_LEGACY_SWITCH_MIMO2_BC: + D_RATE("LQ: Legacy switch to MIMO2\n"); + + /* Set up search table to try MIMO */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + + if (tbl->action == IL_LEGACY_SWITCH_MIMO2_AB) + search_tbl->ant_type = ANT_AB; + else if (tbl->action == IL_LEGACY_SWITCH_MIMO2_AC) + search_tbl->ant_type = ANT_AC; + else + search_tbl->ant_type = ANT_BC; + + if (!il4965_rs_is_valid_ant + (valid_tx_ant, search_tbl->ant_type)) + break; + + ret = + il4965_rs_switch_to_mimo2(il, lq_sta, conf, sta, + search_tbl, idx); + if (!ret) { + lq_sta->action_counter = 0; + goto out; + } + break; + } + tbl->action++; + if (tbl->action > IL_LEGACY_SWITCH_MIMO2_BC) + tbl->action = IL_LEGACY_SWITCH_ANTENNA1; + + if (tbl->action == start_action) + break; + + } + search_tbl->lq_type = LQ_NONE; + return 0; + +out: + lq_sta->search_better_tbl = 1; + tbl->action++; + if (tbl->action > IL_LEGACY_SWITCH_MIMO2_BC) + tbl->action = IL_LEGACY_SWITCH_ANTENNA1; + if (update_search_tbl_counter) + search_tbl->action = tbl->action; + return 0; + +} + +/* + * Try to switch to new modulation mode from SISO + */ +static int +il4965_rs_move_siso_to_other(struct il_priv *il, struct il_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, int idx) +{ + u8 is_green = lq_sta->is_green; + struct il_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + struct il_scale_tbl_info *search_tbl = + &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + struct il_rate_scale_data *win = &(tbl->win[idx]); + struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + u32 sz = + (sizeof(struct il_scale_tbl_info) - + (sizeof(struct il_rate_scale_data) * RATE_COUNT)); + u8 start_action; + u8 valid_tx_ant = il->hw_params.valid_tx_ant; + u8 tx_chains_num = il->hw_params.tx_chains_num; + u8 update_search_tbl_counter = 0; + int ret; + + start_action = tbl->action; + + for (;;) { + lq_sta->action_counter++; + switch (tbl->action) { + case IL_SISO_SWITCH_ANTENNA1: + case IL_SISO_SWITCH_ANTENNA2: + D_RATE("LQ: SISO toggle Antenna\n"); + if ((tbl->action == IL_SISO_SWITCH_ANTENNA1 && + tx_chains_num <= 1) || + (tbl->action == IL_SISO_SWITCH_ANTENNA2 && + tx_chains_num <= 2)) + break; + + if (win->success_ratio >= IL_RS_GOOD_RATIO) + break; + + memcpy(search_tbl, tbl, sz); + if (il4965_rs_toggle_antenna + (valid_tx_ant, &search_tbl->current_rate, + search_tbl)) { + update_search_tbl_counter = 1; + goto out; + } + break; + case IL_SISO_SWITCH_MIMO2_AB: + case IL_SISO_SWITCH_MIMO2_AC: + case IL_SISO_SWITCH_MIMO2_BC: + D_RATE("LQ: SISO switch to MIMO2\n"); + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + + if (tbl->action == IL_SISO_SWITCH_MIMO2_AB) + search_tbl->ant_type = ANT_AB; + else if (tbl->action == IL_SISO_SWITCH_MIMO2_AC) + search_tbl->ant_type = ANT_AC; + else + search_tbl->ant_type = ANT_BC; + + if (!il4965_rs_is_valid_ant + (valid_tx_ant, search_tbl->ant_type)) + break; + + ret = + il4965_rs_switch_to_mimo2(il, lq_sta, conf, sta, + search_tbl, idx); + if (!ret) + goto out; + break; + case IL_SISO_SWITCH_GI: + if (!tbl->is_ht40 && + !(ht_cap->cap & IEEE80211_HT_CAP_SGI_20)) + break; + if (tbl->is_ht40 && + !(ht_cap->cap & IEEE80211_HT_CAP_SGI_40)) + break; + + D_RATE("LQ: SISO toggle SGI/NGI\n"); + + memcpy(search_tbl, tbl, sz); + if (is_green) { + if (!tbl->is_SGI) + break; + else + IL_ERR("SGI was set in GF+SISO\n"); + } + search_tbl->is_SGI = !tbl->is_SGI; + il4965_rs_set_expected_tpt_table(lq_sta, search_tbl); + if (tbl->is_SGI) { + s32 tpt = lq_sta->last_tpt / 100; + if (tpt >= search_tbl->expected_tpt[idx]) + break; + } + search_tbl->current_rate = + il4965_rate_n_flags_from_tbl(il, search_tbl, idx, + is_green); + update_search_tbl_counter = 1; + goto out; + } + tbl->action++; + if (tbl->action > IL_SISO_SWITCH_GI) + tbl->action = IL_SISO_SWITCH_ANTENNA1; + + if (tbl->action == start_action) + break; + } + search_tbl->lq_type = LQ_NONE; + return 0; + +out: + lq_sta->search_better_tbl = 1; + tbl->action++; + if (tbl->action > IL_SISO_SWITCH_GI) + tbl->action = IL_SISO_SWITCH_ANTENNA1; + if (update_search_tbl_counter) + search_tbl->action = tbl->action; + + return 0; +} + +/* + * Try to switch to new modulation mode from MIMO2 + */ +static int +il4965_rs_move_mimo2_to_other(struct il_priv *il, struct il_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, int idx) +{ + s8 is_green = lq_sta->is_green; + struct il_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + struct il_scale_tbl_info *search_tbl = + &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + struct il_rate_scale_data *win = &(tbl->win[idx]); + struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + u32 sz = + (sizeof(struct il_scale_tbl_info) - + (sizeof(struct il_rate_scale_data) * RATE_COUNT)); + u8 start_action; + u8 valid_tx_ant = il->hw_params.valid_tx_ant; + u8 tx_chains_num = il->hw_params.tx_chains_num; + u8 update_search_tbl_counter = 0; + int ret; + + start_action = tbl->action; + for (;;) { + lq_sta->action_counter++; + switch (tbl->action) { + case IL_MIMO2_SWITCH_ANTENNA1: + case IL_MIMO2_SWITCH_ANTENNA2: + D_RATE("LQ: MIMO2 toggle Antennas\n"); + + if (tx_chains_num <= 2) + break; + + if (win->success_ratio >= IL_RS_GOOD_RATIO) + break; + + memcpy(search_tbl, tbl, sz); + if (il4965_rs_toggle_antenna + (valid_tx_ant, &search_tbl->current_rate, + search_tbl)) { + update_search_tbl_counter = 1; + goto out; + } + break; + case IL_MIMO2_SWITCH_SISO_A: + case IL_MIMO2_SWITCH_SISO_B: + case IL_MIMO2_SWITCH_SISO_C: + D_RATE("LQ: MIMO2 switch to SISO\n"); + + /* Set up new search table for SISO */ + memcpy(search_tbl, tbl, sz); + + if (tbl->action == IL_MIMO2_SWITCH_SISO_A) + search_tbl->ant_type = ANT_A; + else if (tbl->action == IL_MIMO2_SWITCH_SISO_B) + search_tbl->ant_type = ANT_B; + else + search_tbl->ant_type = ANT_C; + + if (!il4965_rs_is_valid_ant + (valid_tx_ant, search_tbl->ant_type)) + break; + + ret = + il4965_rs_switch_to_siso(il, lq_sta, conf, sta, + search_tbl, idx); + if (!ret) + goto out; + + break; + + case IL_MIMO2_SWITCH_GI: + if (!tbl->is_ht40 && + !(ht_cap->cap & IEEE80211_HT_CAP_SGI_20)) + break; + if (tbl->is_ht40 && + !(ht_cap->cap & IEEE80211_HT_CAP_SGI_40)) + break; + + D_RATE("LQ: MIMO2 toggle SGI/NGI\n"); + + /* Set up new search table for MIMO2 */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = !tbl->is_SGI; + il4965_rs_set_expected_tpt_table(lq_sta, search_tbl); + /* + * If active table already uses the fastest possible + * modulation (dual stream with short guard interval), + * and it's working well, there's no need to look + * for a better type of modulation! + */ + if (tbl->is_SGI) { + s32 tpt = lq_sta->last_tpt / 100; + if (tpt >= search_tbl->expected_tpt[idx]) + break; + } + search_tbl->current_rate = + il4965_rate_n_flags_from_tbl(il, search_tbl, idx, + is_green); + update_search_tbl_counter = 1; + goto out; + + } + tbl->action++; + if (tbl->action > IL_MIMO2_SWITCH_GI) + tbl->action = IL_MIMO2_SWITCH_ANTENNA1; + + if (tbl->action == start_action) + break; + } + search_tbl->lq_type = LQ_NONE; + return 0; +out: + lq_sta->search_better_tbl = 1; + tbl->action++; + if (tbl->action > IL_MIMO2_SWITCH_GI) + tbl->action = IL_MIMO2_SWITCH_ANTENNA1; + if (update_search_tbl_counter) + search_tbl->action = tbl->action; + + return 0; + +} + +/* + * Check whether we should continue using same modulation mode, or + * begin search for a new mode, based on: + * 1) # tx successes or failures while using this mode + * 2) # times calling this function + * 3) elapsed time in this mode (not used, for now) + */ +static void +il4965_rs_stay_in_table(struct il_lq_sta *lq_sta, bool force_search) +{ + struct il_scale_tbl_info *tbl; + int i; + int active_tbl; + int flush_interval_passed = 0; + struct il_priv *il; + + il = lq_sta->drv; + active_tbl = lq_sta->active_tbl; + + tbl = &(lq_sta->lq_info[active_tbl]); + + /* If we've been disallowing search, see if we should now allow it */ + if (lq_sta->stay_in_tbl) { + + /* Elapsed time using current modulation mode */ + if (lq_sta->flush_timer) + flush_interval_passed = + time_after(jiffies, + (unsigned long)(lq_sta->flush_timer + + RATE_SCALE_FLUSH_INTVL)); + + /* + * Check if we should allow search for new modulation mode. + * If many frames have failed or succeeded, or we've used + * this same modulation for a long time, allow search, and + * reset history stats that keep track of whether we should + * allow a new search. Also (below) reset all bitmaps and + * stats in active history. + */ + if (force_search || + lq_sta->total_failed > lq_sta->max_failure_limit || + lq_sta->total_success > lq_sta->max_success_limit || + (!lq_sta->search_better_tbl && lq_sta->flush_timer && + flush_interval_passed)) { + D_RATE("LQ: stay is expired %d %d %d\n:", + lq_sta->total_failed, lq_sta->total_success, + flush_interval_passed); + + /* Allow search for new mode */ + lq_sta->stay_in_tbl = 0; /* only place reset */ + lq_sta->total_failed = 0; + lq_sta->total_success = 0; + lq_sta->flush_timer = 0; + + /* + * Else if we've used this modulation mode enough repetitions + * (regardless of elapsed time or success/failure), reset + * history bitmaps and rate-specific stats for all rates in + * active table. + */ + } else { + lq_sta->table_count++; + if (lq_sta->table_count >= lq_sta->table_count_limit) { + lq_sta->table_count = 0; + + D_RATE("LQ: stay in table clear win\n"); + for (i = 0; i < RATE_COUNT; i++) + il4965_rs_rate_scale_clear_win(& + (tbl-> + win + [i])); + } + } + + /* If transitioning to allow "search", reset all history + * bitmaps and stats in active table (this will become the new + * "search" table). */ + if (!lq_sta->stay_in_tbl) { + for (i = 0; i < RATE_COUNT; i++) + il4965_rs_rate_scale_clear_win(&(tbl->win[i])); + } + } +} + +/* + * setup rate table in uCode + */ +static void +il4965_rs_update_rate_tbl(struct il_priv *il, struct il_lq_sta *lq_sta, + struct il_scale_tbl_info *tbl, int idx, u8 is_green) +{ + u32 rate; + + /* Update uCode's rate table. */ + rate = il4965_rate_n_flags_from_tbl(il, tbl, idx, is_green); + il4965_rs_fill_link_cmd(il, lq_sta, rate); + il_send_lq_cmd(il, &lq_sta->lq, CMD_ASYNC, false); +} + +/* + * Do rate scaling and search for new modulation mode. + */ +static void +il4965_rs_rate_scale_perform(struct il_priv *il, struct sk_buff *skb, + struct ieee80211_sta *sta, + struct il_lq_sta *lq_sta) +{ + struct ieee80211_hw *hw = il->hw; + struct ieee80211_conf *conf = &hw->conf; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + int low = RATE_INVALID; + int high = RATE_INVALID; + int idx; + int i; + struct il_rate_scale_data *win = NULL; + int current_tpt = IL_INVALID_VALUE; + int low_tpt = IL_INVALID_VALUE; + int high_tpt = IL_INVALID_VALUE; + u32 fail_count; + s8 scale_action = 0; + u16 rate_mask; + u8 update_lq = 0; + struct il_scale_tbl_info *tbl, *tbl1; + u16 rate_scale_idx_msk = 0; + u8 is_green = 0; + u8 active_tbl = 0; + u8 done_search = 0; + u16 high_low; + s32 sr; + u8 tid = MAX_TID_COUNT; + struct il_tid_data *tid_data; + + D_RATE("rate scale calculate new rate for skb\n"); + + /* Send management frames and NO_ACK data using lowest rate. */ + /* TODO: this could probably be improved.. */ + if (!ieee80211_is_data(hdr->frame_control) || + (info->flags & IEEE80211_TX_CTL_NO_ACK)) + return; + + lq_sta->supp_rates = sta->supp_rates[lq_sta->band]; + + tid = il4965_rs_tl_add_packet(lq_sta, hdr); + if (tid != MAX_TID_COUNT && (lq_sta->tx_agg_tid_en & (1 << tid))) { + tid_data = &il->stations[lq_sta->lq.sta_id].tid[tid]; + if (tid_data->agg.state == IL_AGG_OFF) + lq_sta->is_agg = 0; + else + lq_sta->is_agg = 1; + } else + lq_sta->is_agg = 0; + + /* + * Select rate-scale / modulation-mode table to work with in + * the rest of this function: "search" if searching for better + * modulation mode, or "active" if doing rate scaling within a mode. + */ + if (!lq_sta->search_better_tbl) + active_tbl = lq_sta->active_tbl; + else + active_tbl = 1 - lq_sta->active_tbl; + + tbl = &(lq_sta->lq_info[active_tbl]); + if (is_legacy(tbl->lq_type)) + lq_sta->is_green = 0; + else + lq_sta->is_green = il4965_rs_use_green(il, sta); + is_green = lq_sta->is_green; + + /* current tx rate */ + idx = lq_sta->last_txrate_idx; + + D_RATE("Rate scale idx %d for type %d\n", idx, tbl->lq_type); + + /* rates available for this association, and for modulation mode */ + rate_mask = il4965_rs_get_supported_rates(lq_sta, hdr, tbl->lq_type); + + D_RATE("mask 0x%04X\n", rate_mask); + + /* mask with station rate restriction */ + if (is_legacy(tbl->lq_type)) { + if (lq_sta->band == IEEE80211_BAND_5GHZ) + /* supp_rates has no CCK bits in A mode */ + rate_scale_idx_msk = + (u16) (rate_mask & + (lq_sta->supp_rates << IL_FIRST_OFDM_RATE)); + else + rate_scale_idx_msk = + (u16) (rate_mask & lq_sta->supp_rates); + + } else + rate_scale_idx_msk = rate_mask; + + if (!rate_scale_idx_msk) + rate_scale_idx_msk = rate_mask; + + if (!((1 << idx) & rate_scale_idx_msk)) { + IL_ERR("Current Rate is not valid\n"); + if (lq_sta->search_better_tbl) { + /* revert to active table if search table is not valid */ + tbl->lq_type = LQ_NONE; + lq_sta->search_better_tbl = 0; + tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + /* get "active" rate info */ + idx = il4965_hwrate_to_plcp_idx(tbl->current_rate); + il4965_rs_update_rate_tbl(il, lq_sta, tbl, idx, + is_green); + } + return; + } + + /* Get expected throughput table and history win for current rate */ + if (!tbl->expected_tpt) { + IL_ERR("tbl->expected_tpt is NULL\n"); + return; + } + + /* force user max rate if set by user */ + if (lq_sta->max_rate_idx != -1 && lq_sta->max_rate_idx < idx) { + idx = lq_sta->max_rate_idx; + update_lq = 1; + win = &(tbl->win[idx]); + goto lq_update; + } + + win = &(tbl->win[idx]); + + /* + * If there is not enough history to calculate actual average + * throughput, keep analyzing results of more tx frames, without + * changing rate or mode (bypass most of the rest of this function). + * Set up new rate table in uCode only if old rate is not supported + * in current association (use new rate found above). + */ + fail_count = win->counter - win->success_counter; + if (fail_count < RATE_MIN_FAILURE_TH && + win->success_counter < RATE_MIN_SUCCESS_TH) { + D_RATE("LQ: still below TH. succ=%d total=%d " "for idx %d\n", + win->success_counter, win->counter, idx); + + /* Can't calculate this yet; not enough history */ + win->average_tpt = IL_INVALID_VALUE; + + /* Should we stay with this modulation mode, + * or search for a new one? */ + il4965_rs_stay_in_table(lq_sta, false); + + goto out; + } + /* Else we have enough samples; calculate estimate of + * actual average throughput */ + if (win->average_tpt != + ((win->success_ratio * tbl->expected_tpt[idx] + 64) / 128)) { + IL_ERR("expected_tpt should have been calculated by now\n"); + win->average_tpt = + ((win->success_ratio * tbl->expected_tpt[idx] + 64) / 128); + } + + /* If we are searching for better modulation mode, check success. */ + if (lq_sta->search_better_tbl) { + /* If good success, continue using the "search" mode; + * no need to send new link quality command, since we're + * continuing to use the setup that we've been trying. */ + if (win->average_tpt > lq_sta->last_tpt) { + + D_RATE("LQ: SWITCHING TO NEW TBL " + "suc=%d cur-tpt=%d old-tpt=%d\n", + win->success_ratio, win->average_tpt, + lq_sta->last_tpt); + + if (!is_legacy(tbl->lq_type)) + lq_sta->enable_counter = 1; + + /* Swap tables; "search" becomes "active" */ + lq_sta->active_tbl = active_tbl; + current_tpt = win->average_tpt; + + /* Else poor success; go back to mode in "active" table */ + } else { + + D_RATE("LQ: GOING BACK TO THE OLD TBL " + "suc=%d cur-tpt=%d old-tpt=%d\n", + win->success_ratio, win->average_tpt, + lq_sta->last_tpt); + + /* Nullify "search" table */ + tbl->lq_type = LQ_NONE; + + /* Revert to "active" table */ + active_tbl = lq_sta->active_tbl; + tbl = &(lq_sta->lq_info[active_tbl]); + + /* Revert to "active" rate and throughput info */ + idx = il4965_hwrate_to_plcp_idx(tbl->current_rate); + current_tpt = lq_sta->last_tpt; + + /* Need to set up a new rate table in uCode */ + update_lq = 1; + } + + /* Either way, we've made a decision; modulation mode + * search is done, allow rate adjustment next time. */ + lq_sta->search_better_tbl = 0; + done_search = 1; /* Don't switch modes below! */ + goto lq_update; + } + + /* (Else) not in search of better modulation mode, try for better + * starting rate, while staying in this mode. */ + high_low = + il4965_rs_get_adjacent_rate(il, idx, rate_scale_idx_msk, + tbl->lq_type); + low = high_low & 0xff; + high = (high_low >> 8) & 0xff; + + /* If user set max rate, dont allow higher than user constrain */ + if (lq_sta->max_rate_idx != -1 && lq_sta->max_rate_idx < high) + high = RATE_INVALID; + + sr = win->success_ratio; + + /* Collect measured throughputs for current and adjacent rates */ + current_tpt = win->average_tpt; + if (low != RATE_INVALID) + low_tpt = tbl->win[low].average_tpt; + if (high != RATE_INVALID) + high_tpt = tbl->win[high].average_tpt; + + scale_action = 0; + + /* Too many failures, decrease rate */ + if (sr <= RATE_DECREASE_TH || current_tpt == 0) { + D_RATE("decrease rate because of low success_ratio\n"); + scale_action = -1; + + /* No throughput measured yet for adjacent rates; try increase. */ + } else if (low_tpt == IL_INVALID_VALUE && high_tpt == IL_INVALID_VALUE) { + + if (high != RATE_INVALID && sr >= RATE_INCREASE_TH) + scale_action = 1; + else if (low != RATE_INVALID) + scale_action = 0; + } + + /* Both adjacent throughputs are measured, but neither one has better + * throughput; we're using the best rate, don't change it! */ + else if (low_tpt != IL_INVALID_VALUE && high_tpt != IL_INVALID_VALUE && + low_tpt < current_tpt && high_tpt < current_tpt) + scale_action = 0; + + /* At least one adjacent rate's throughput is measured, + * and may have better performance. */ + else { + /* Higher adjacent rate's throughput is measured */ + if (high_tpt != IL_INVALID_VALUE) { + /* Higher rate has better throughput */ + if (high_tpt > current_tpt && sr >= RATE_INCREASE_TH) + scale_action = 1; + else + scale_action = 0; + + /* Lower adjacent rate's throughput is measured */ + } else if (low_tpt != IL_INVALID_VALUE) { + /* Lower rate has better throughput */ + if (low_tpt > current_tpt) { + D_RATE("decrease rate because of low tpt\n"); + scale_action = -1; + } else if (sr >= RATE_INCREASE_TH) { + scale_action = 1; + } + } + } + + /* Sanity check; asked for decrease, but success rate or throughput + * has been good at old rate. Don't change it. */ + if (scale_action == -1 && low != RATE_INVALID && + (sr > RATE_HIGH_TH || current_tpt > 100 * tbl->expected_tpt[low])) + scale_action = 0; + + switch (scale_action) { + case -1: + /* Decrease starting rate, update uCode's rate table */ + if (low != RATE_INVALID) { + update_lq = 1; + idx = low; + } + + break; + case 1: + /* Increase starting rate, update uCode's rate table */ + if (high != RATE_INVALID) { + update_lq = 1; + idx = high; + } + + break; + case 0: + /* No change */ + default: + break; + } + + D_RATE("choose rate scale idx %d action %d low %d " "high %d type %d\n", + idx, scale_action, low, high, tbl->lq_type); + +lq_update: + /* Replace uCode's rate table for the destination station. */ + if (update_lq) + il4965_rs_update_rate_tbl(il, lq_sta, tbl, idx, is_green); + + /* Should we stay with this modulation mode, + * or search for a new one? */ + il4965_rs_stay_in_table(lq_sta, false); + + /* + * Search for new modulation mode if we're: + * 1) Not changing rates right now + * 2) Not just finishing up a search + * 3) Allowing a new search + */ + if (!update_lq && !done_search && !lq_sta->stay_in_tbl && win->counter) { + /* Save current throughput to compare with "search" throughput */ + lq_sta->last_tpt = current_tpt; + + /* Select a new "search" modulation mode to try. + * If one is found, set up the new "search" table. */ + if (is_legacy(tbl->lq_type)) + il4965_rs_move_legacy_other(il, lq_sta, conf, sta, idx); + else if (is_siso(tbl->lq_type)) + il4965_rs_move_siso_to_other(il, lq_sta, conf, sta, + idx); + else /* (is_mimo2(tbl->lq_type)) */ + il4965_rs_move_mimo2_to_other(il, lq_sta, conf, sta, + idx); + + /* If new "search" mode was selected, set up in uCode table */ + if (lq_sta->search_better_tbl) { + /* Access the "search" table, clear its history. */ + tbl = &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + for (i = 0; i < RATE_COUNT; i++) + il4965_rs_rate_scale_clear_win(&(tbl->win[i])); + + /* Use new "search" start rate */ + idx = il4965_hwrate_to_plcp_idx(tbl->current_rate); + + D_RATE("Switch current mcs: %X idx: %d\n", + tbl->current_rate, idx); + il4965_rs_fill_link_cmd(il, lq_sta, tbl->current_rate); + il_send_lq_cmd(il, &lq_sta->lq, CMD_ASYNC, false); + } else + done_search = 1; + } + + if (done_search && !lq_sta->stay_in_tbl) { + /* If the "active" (non-search) mode was legacy, + * and we've tried switching antennas, + * but we haven't been able to try HT modes (not available), + * stay with best antenna legacy modulation for a while + * before next round of mode comparisons. */ + tbl1 = &(lq_sta->lq_info[lq_sta->active_tbl]); + if (is_legacy(tbl1->lq_type) && !conf_is_ht(conf) && + lq_sta->action_counter > tbl1->max_search) { + D_RATE("LQ: STAY in legacy table\n"); + il4965_rs_set_stay_in_table(il, 1, lq_sta); + } + + /* If we're in an HT mode, and all 3 mode switch actions + * have been tried and compared, stay in this best modulation + * mode for a while before next round of mode comparisons. */ + if (lq_sta->enable_counter && + lq_sta->action_counter >= tbl1->max_search) { + if (lq_sta->last_tpt > IL_AGG_TPT_THREHOLD && + (lq_sta->tx_agg_tid_en & (1 << tid)) && + tid != MAX_TID_COUNT) { + tid_data = + &il->stations[lq_sta->lq.sta_id].tid[tid]; + if (tid_data->agg.state == IL_AGG_OFF) { + D_RATE("try to aggregate tid %d\n", + tid); + il4965_rs_tl_turn_on_agg(il, tid, + lq_sta, sta); + } + } + il4965_rs_set_stay_in_table(il, 0, lq_sta); + } + } + +out: + tbl->current_rate = + il4965_rate_n_flags_from_tbl(il, tbl, idx, is_green); + i = idx; + lq_sta->last_txrate_idx = i; +} + +/** + * il4965_rs_initialize_lq - Initialize a station's hardware rate table + * + * The uCode's station table contains a table of fallback rates + * for automatic fallback during transmission. + * + * NOTE: This sets up a default set of values. These will be replaced later + * if the driver's iwl-4965-rs rate scaling algorithm is used, instead of + * rc80211_simple. + * + * NOTE: Run C_ADD_STA command to set up station table entry, before + * calling this function (which runs C_TX_LINK_QUALITY_CMD, + * which requires station table entry to exist). + */ +static void +il4965_rs_initialize_lq(struct il_priv *il, struct ieee80211_conf *conf, + struct ieee80211_sta *sta, struct il_lq_sta *lq_sta) +{ + struct il_scale_tbl_info *tbl; + int rate_idx; + int i; + u32 rate; + u8 use_green; + u8 active_tbl = 0; + u8 valid_tx_ant; + struct il_station_priv *sta_priv; + + if (!sta || !lq_sta) + return; + + use_green = il4965_rs_use_green(il, sta); + sta_priv = (void *)sta->drv_priv; + + i = lq_sta->last_txrate_idx; + + valid_tx_ant = il->hw_params.valid_tx_ant; + + if (!lq_sta->search_better_tbl) + active_tbl = lq_sta->active_tbl; + else + active_tbl = 1 - lq_sta->active_tbl; + + tbl = &(lq_sta->lq_info[active_tbl]); + + if (i < 0 || i >= RATE_COUNT) + i = 0; + + rate = il_rates[i].plcp; + tbl->ant_type = il4965_first_antenna(valid_tx_ant); + rate |= tbl->ant_type << RATE_MCS_ANT_POS; + + if (i >= IL_FIRST_CCK_RATE && i <= IL_LAST_CCK_RATE) + rate |= RATE_MCS_CCK_MSK; + + il4965_rs_get_tbl_info_from_mcs(rate, il->band, tbl, &rate_idx); + if (!il4965_rs_is_valid_ant(valid_tx_ant, tbl->ant_type)) + il4965_rs_toggle_antenna(valid_tx_ant, &rate, tbl); + + rate = il4965_rate_n_flags_from_tbl(il, tbl, rate_idx, use_green); + tbl->current_rate = rate; + il4965_rs_set_expected_tpt_table(lq_sta, tbl); + il4965_rs_fill_link_cmd(NULL, lq_sta, rate); + il->stations[lq_sta->lq.sta_id].lq = &lq_sta->lq; + il_send_lq_cmd(il, &lq_sta->lq, CMD_SYNC, true); +} + +static void +il4965_rs_get_rate(void *il_r, struct ieee80211_sta *sta, void *il_sta, + struct ieee80211_tx_rate_control *txrc) +{ + + struct sk_buff *skb = txrc->skb; + struct ieee80211_supported_band *sband = txrc->sband; + struct il_priv *il __maybe_unused = (struct il_priv *)il_r; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct il_lq_sta *lq_sta = il_sta; + int rate_idx; + + D_RATE("rate scale calculate new rate for skb\n"); + + /* Get max rate if user set max rate */ + if (lq_sta) { + lq_sta->max_rate_idx = txrc->max_rate_idx; + if (sband->band == IEEE80211_BAND_5GHZ && + lq_sta->max_rate_idx != -1) + lq_sta->max_rate_idx += IL_FIRST_OFDM_RATE; + if (lq_sta->max_rate_idx < 0 || + lq_sta->max_rate_idx >= RATE_COUNT) + lq_sta->max_rate_idx = -1; + } + + /* Treat uninitialized rate scaling data same as non-existing. */ + if (lq_sta && !lq_sta->drv) { + D_RATE("Rate scaling not initialized yet.\n"); + il_sta = NULL; + } + + /* Send management frames and NO_ACK data using lowest rate. */ + if (rate_control_send_low(sta, il_sta, txrc)) + return; + + if (!lq_sta) + return; + + rate_idx = lq_sta->last_txrate_idx; + + if (lq_sta->last_rate_n_flags & RATE_MCS_HT_MSK) { + rate_idx -= IL_FIRST_OFDM_RATE; + /* 6M and 9M shared same MCS idx */ + rate_idx = (rate_idx > 0) ? (rate_idx - 1) : 0; + if (il4965_rs_extract_rate(lq_sta->last_rate_n_flags) >= + RATE_MIMO2_6M_PLCP) + rate_idx = rate_idx + MCS_IDX_PER_STREAM; + info->control.rates[0].flags = IEEE80211_TX_RC_MCS; + if (lq_sta->last_rate_n_flags & RATE_MCS_SGI_MSK) + info->control.rates[0].flags |= + IEEE80211_TX_RC_SHORT_GI; + if (lq_sta->last_rate_n_flags & RATE_MCS_DUP_MSK) + info->control.rates[0].flags |= + IEEE80211_TX_RC_DUP_DATA; + if (lq_sta->last_rate_n_flags & RATE_MCS_HT40_MSK) + info->control.rates[0].flags |= + IEEE80211_TX_RC_40_MHZ_WIDTH; + if (lq_sta->last_rate_n_flags & RATE_MCS_GF_MSK) + info->control.rates[0].flags |= + IEEE80211_TX_RC_GREEN_FIELD; + } else { + /* Check for invalid rates */ + if (rate_idx < 0 || rate_idx >= RATE_COUNT_LEGACY || + (sband->band == IEEE80211_BAND_5GHZ && + rate_idx < IL_FIRST_OFDM_RATE)) + rate_idx = rate_lowest_index(sband, sta); + /* On valid 5 GHz rate, adjust idx */ + else if (sband->band == IEEE80211_BAND_5GHZ) + rate_idx -= IL_FIRST_OFDM_RATE; + info->control.rates[0].flags = 0; + } + info->control.rates[0].idx = rate_idx; + info->control.rates[0].count = 1; +} + +static void * +il4965_rs_alloc_sta(void *il_rate, struct ieee80211_sta *sta, gfp_t gfp) +{ + struct il_station_priv *sta_priv = + (struct il_station_priv *)sta->drv_priv; + struct il_priv *il; + + il = (struct il_priv *)il_rate; + D_RATE("create station rate scale win\n"); + + return &sta_priv->lq_sta; +} + +/* + * Called after adding a new station to initialize rate scaling + */ +void +il4965_rs_rate_init(struct il_priv *il, struct ieee80211_sta *sta, u8 sta_id) +{ + int i, j; + struct ieee80211_hw *hw = il->hw; + struct ieee80211_conf *conf = &il->hw->conf; + struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + struct il_station_priv *sta_priv; + struct il_lq_sta *lq_sta; + struct ieee80211_supported_band *sband; + + sta_priv = (struct il_station_priv *)sta->drv_priv; + lq_sta = &sta_priv->lq_sta; + sband = hw->wiphy->bands[conf->chandef.chan->band]; + + lq_sta->lq.sta_id = sta_id; + + for (j = 0; j < LQ_SIZE; j++) + for (i = 0; i < RATE_COUNT; i++) + il4965_rs_rate_scale_clear_win(&lq_sta->lq_info[j]. + win[i]); + + lq_sta->flush_timer = 0; + lq_sta->supp_rates = sta->supp_rates[sband->band]; + for (j = 0; j < LQ_SIZE; j++) + for (i = 0; i < RATE_COUNT; i++) + il4965_rs_rate_scale_clear_win(&lq_sta->lq_info[j]. + win[i]); + + D_RATE("LQ:" "*** rate scale station global init for station %d ***\n", + sta_id); + /* TODO: what is a good starting rate for STA? About middle? Maybe not + * the lowest or the highest rate.. Could consider using RSSI from + * previous packets? Need to have IEEE 802.1X auth succeed immediately + * after assoc.. */ + + lq_sta->is_dup = 0; + lq_sta->max_rate_idx = -1; + lq_sta->missed_rate_counter = IL_MISSED_RATE_MAX; + lq_sta->is_green = il4965_rs_use_green(il, sta); + lq_sta->active_legacy_rate = il->active_rate & ~(0x1000); + lq_sta->band = il->band; + /* + * active_siso_rate mask includes 9 MBits (bit 5), and CCK (bits 0-3), + * supp_rates[] does not; shift to convert format, force 9 MBits off. + */ + lq_sta->active_siso_rate = ht_cap->mcs.rx_mask[0] << 1; + lq_sta->active_siso_rate |= ht_cap->mcs.rx_mask[0] & 0x1; + lq_sta->active_siso_rate &= ~((u16) 0x2); + lq_sta->active_siso_rate <<= IL_FIRST_OFDM_RATE; + + /* Same here */ + lq_sta->active_mimo2_rate = ht_cap->mcs.rx_mask[1] << 1; + lq_sta->active_mimo2_rate |= ht_cap->mcs.rx_mask[1] & 0x1; + lq_sta->active_mimo2_rate &= ~((u16) 0x2); + lq_sta->active_mimo2_rate <<= IL_FIRST_OFDM_RATE; + + /* These values will be overridden later */ + lq_sta->lq.general_params.single_stream_ant_msk = + il4965_first_antenna(il->hw_params.valid_tx_ant); + lq_sta->lq.general_params.dual_stream_ant_msk = + il->hw_params.valid_tx_ant & ~il4965_first_antenna(il->hw_params. + valid_tx_ant); + if (!lq_sta->lq.general_params.dual_stream_ant_msk) { + lq_sta->lq.general_params.dual_stream_ant_msk = ANT_AB; + } else if (il4965_num_of_ant(il->hw_params.valid_tx_ant) == 2) { + lq_sta->lq.general_params.dual_stream_ant_msk = + il->hw_params.valid_tx_ant; + } + + /* as default allow aggregation for all tids */ + lq_sta->tx_agg_tid_en = IL_AGG_ALL_TID; + lq_sta->drv = il; + + /* Set last_txrate_idx to lowest rate */ + lq_sta->last_txrate_idx = rate_lowest_index(sband, sta); + if (sband->band == IEEE80211_BAND_5GHZ) + lq_sta->last_txrate_idx += IL_FIRST_OFDM_RATE; + lq_sta->is_agg = 0; + +#ifdef CPTCFG_MAC80211_DEBUGFS + lq_sta->dbg_fixed_rate = 0; +#endif + + il4965_rs_initialize_lq(il, conf, sta, lq_sta); +} + +static void +il4965_rs_fill_link_cmd(struct il_priv *il, struct il_lq_sta *lq_sta, + u32 new_rate) +{ + struct il_scale_tbl_info tbl_type; + int idx = 0; + int rate_idx; + int repeat_rate = 0; + u8 ant_toggle_cnt = 0; + u8 use_ht_possible = 1; + u8 valid_tx_ant = 0; + struct il_link_quality_cmd *lq_cmd = &lq_sta->lq; + + /* Override starting rate (idx 0) if needed for debug purposes */ + il4965_rs_dbgfs_set_mcs(lq_sta, &new_rate, idx); + + /* Interpret new_rate (rate_n_flags) */ + il4965_rs_get_tbl_info_from_mcs(new_rate, lq_sta->band, &tbl_type, + &rate_idx); + + /* How many times should we repeat the initial rate? */ + if (is_legacy(tbl_type.lq_type)) { + ant_toggle_cnt = 1; + repeat_rate = IL_NUMBER_TRY; + } else { + repeat_rate = IL_HT_NUMBER_TRY; + } + + lq_cmd->general_params.mimo_delimiter = + is_mimo(tbl_type.lq_type) ? 1 : 0; + + /* Fill 1st table entry (idx 0) */ + lq_cmd->rs_table[idx].rate_n_flags = cpu_to_le32(new_rate); + + if (il4965_num_of_ant(tbl_type.ant_type) == 1) { + lq_cmd->general_params.single_stream_ant_msk = + tbl_type.ant_type; + } else if (il4965_num_of_ant(tbl_type.ant_type) == 2) { + lq_cmd->general_params.dual_stream_ant_msk = tbl_type.ant_type; + } + /* otherwise we don't modify the existing value */ + idx++; + repeat_rate--; + if (il) + valid_tx_ant = il->hw_params.valid_tx_ant; + + /* Fill rest of rate table */ + while (idx < LINK_QUAL_MAX_RETRY_NUM) { + /* Repeat initial/next rate. + * For legacy IL_NUMBER_TRY == 1, this loop will not execute. + * For HT IL_HT_NUMBER_TRY == 3, this executes twice. */ + while (repeat_rate > 0 && idx < LINK_QUAL_MAX_RETRY_NUM) { + if (is_legacy(tbl_type.lq_type)) { + if (ant_toggle_cnt < NUM_TRY_BEFORE_ANT_TOGGLE) + ant_toggle_cnt++; + else if (il && + il4965_rs_toggle_antenna(valid_tx_ant, + &new_rate, + &tbl_type)) + ant_toggle_cnt = 1; + } + + /* Override next rate if needed for debug purposes */ + il4965_rs_dbgfs_set_mcs(lq_sta, &new_rate, idx); + + /* Fill next table entry */ + lq_cmd->rs_table[idx].rate_n_flags = + cpu_to_le32(new_rate); + repeat_rate--; + idx++; + } + + il4965_rs_get_tbl_info_from_mcs(new_rate, lq_sta->band, + &tbl_type, &rate_idx); + + /* Indicate to uCode which entries might be MIMO. + * If initial rate was MIMO, this will finally end up + * as (IL_HT_NUMBER_TRY * 2), after 2nd pass, otherwise 0. */ + if (is_mimo(tbl_type.lq_type)) + lq_cmd->general_params.mimo_delimiter = idx; + + /* Get next rate */ + new_rate = + il4965_rs_get_lower_rate(lq_sta, &tbl_type, rate_idx, + use_ht_possible); + + /* How many times should we repeat the next rate? */ + if (is_legacy(tbl_type.lq_type)) { + if (ant_toggle_cnt < NUM_TRY_BEFORE_ANT_TOGGLE) + ant_toggle_cnt++; + else if (il && + il4965_rs_toggle_antenna(valid_tx_ant, + &new_rate, &tbl_type)) + ant_toggle_cnt = 1; + + repeat_rate = IL_NUMBER_TRY; + } else { + repeat_rate = IL_HT_NUMBER_TRY; + } + + /* Don't allow HT rates after next pass. + * il4965_rs_get_lower_rate() will change type to LQ_A or LQ_G. */ + use_ht_possible = 0; + + /* Override next rate if needed for debug purposes */ + il4965_rs_dbgfs_set_mcs(lq_sta, &new_rate, idx); + + /* Fill next table entry */ + lq_cmd->rs_table[idx].rate_n_flags = cpu_to_le32(new_rate); + + idx++; + repeat_rate--; + } + + lq_cmd->agg_params.agg_frame_cnt_limit = LINK_QUAL_AGG_FRAME_LIMIT_DEF; + lq_cmd->agg_params.agg_dis_start_th = LINK_QUAL_AGG_DISABLE_START_DEF; + + lq_cmd->agg_params.agg_time_limit = + cpu_to_le16(LINK_QUAL_AGG_TIME_LIMIT_DEF); +} + +static void * +il4965_rs_alloc(struct ieee80211_hw *hw, struct dentry *debugfsdir) +{ + return hw->priv; +} + +/* rate scale requires free function to be implemented */ +static void +il4965_rs_free(void *il_rate) +{ + return; +} + +static void +il4965_rs_free_sta(void *il_r, struct ieee80211_sta *sta, void *il_sta) +{ + struct il_priv *il __maybe_unused = il_r; + + D_RATE("enter\n"); + D_RATE("leave\n"); +} + +#ifdef CPTCFG_MAC80211_DEBUGFS + +static void +il4965_rs_dbgfs_set_mcs(struct il_lq_sta *lq_sta, u32 * rate_n_flags, int idx) +{ + struct il_priv *il; + u8 valid_tx_ant; + u8 ant_sel_tx; + + il = lq_sta->drv; + valid_tx_ant = il->hw_params.valid_tx_ant; + if (lq_sta->dbg_fixed_rate) { + ant_sel_tx = + ((lq_sta-> + dbg_fixed_rate & RATE_MCS_ANT_ABC_MSK) >> + RATE_MCS_ANT_POS); + if ((valid_tx_ant & ant_sel_tx) == ant_sel_tx) { + *rate_n_flags = lq_sta->dbg_fixed_rate; + D_RATE("Fixed rate ON\n"); + } else { + lq_sta->dbg_fixed_rate = 0; + IL_ERR + ("Invalid antenna selection 0x%X, Valid is 0x%X\n", + ant_sel_tx, valid_tx_ant); + D_RATE("Fixed rate OFF\n"); + } + } else { + D_RATE("Fixed rate OFF\n"); + } +} + +static ssize_t +il4965_rs_sta_dbgfs_scale_table_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct il_lq_sta *lq_sta = file->private_data; + struct il_priv *il; + char buf[64]; + size_t buf_size; + u32 parsed_rate; + + il = lq_sta->drv; + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (sscanf(buf, "%x", &parsed_rate) == 1) + lq_sta->dbg_fixed_rate = parsed_rate; + else + lq_sta->dbg_fixed_rate = 0; + + lq_sta->active_legacy_rate = 0x0FFF; /* 1 - 54 MBits, includes CCK */ + lq_sta->active_siso_rate = 0x1FD0; /* 6 - 60 MBits, no 9, no CCK */ + lq_sta->active_mimo2_rate = 0x1FD0; /* 6 - 60 MBits, no 9, no CCK */ + + D_RATE("sta_id %d rate 0x%X\n", lq_sta->lq.sta_id, + lq_sta->dbg_fixed_rate); + + if (lq_sta->dbg_fixed_rate) { + il4965_rs_fill_link_cmd(NULL, lq_sta, lq_sta->dbg_fixed_rate); + il_send_lq_cmd(lq_sta->drv, &lq_sta->lq, CMD_ASYNC, false); + } + + return count; +} + +static ssize_t +il4965_rs_sta_dbgfs_scale_table_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + char *buff; + int desc = 0; + int i = 0; + int idx = 0; + ssize_t ret; + + struct il_lq_sta *lq_sta = file->private_data; + struct il_priv *il; + struct il_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + + il = lq_sta->drv; + buff = kmalloc(1024, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + desc += sprintf(buff + desc, "sta_id %d\n", lq_sta->lq.sta_id); + desc += + sprintf(buff + desc, "failed=%d success=%d rate=0%X\n", + lq_sta->total_failed, lq_sta->total_success, + lq_sta->active_legacy_rate); + desc += + sprintf(buff + desc, "fixed rate 0x%X\n", lq_sta->dbg_fixed_rate); + desc += + sprintf(buff + desc, "valid_tx_ant %s%s%s\n", + (il->hw_params.valid_tx_ant & ANT_A) ? "ANT_A," : "", + (il->hw_params.valid_tx_ant & ANT_B) ? "ANT_B," : "", + (il->hw_params.valid_tx_ant & ANT_C) ? "ANT_C" : ""); + desc += + sprintf(buff + desc, "lq type %s\n", + (is_legacy(tbl->lq_type)) ? "legacy" : "HT"); + if (is_Ht(tbl->lq_type)) { + desc += + sprintf(buff + desc, " %s", + (is_siso(tbl->lq_type)) ? "SISO" : "MIMO2"); + desc += + sprintf(buff + desc, " %s", + (tbl->is_ht40) ? "40MHz" : "20MHz"); + desc += + sprintf(buff + desc, " %s %s %s\n", + (tbl->is_SGI) ? "SGI" : "", + (lq_sta->is_green) ? "GF enabled" : "", + (lq_sta->is_agg) ? "AGG on" : ""); + } + desc += + sprintf(buff + desc, "last tx rate=0x%X\n", + lq_sta->last_rate_n_flags); + desc += + sprintf(buff + desc, + "general:" "flags=0x%X mimo-d=%d s-ant0x%x d-ant=0x%x\n", + lq_sta->lq.general_params.flags, + lq_sta->lq.general_params.mimo_delimiter, + lq_sta->lq.general_params.single_stream_ant_msk, + lq_sta->lq.general_params.dual_stream_ant_msk); + + desc += + sprintf(buff + desc, + "agg:" + "time_limit=%d dist_start_th=%d frame_cnt_limit=%d\n", + le16_to_cpu(lq_sta->lq.agg_params.agg_time_limit), + lq_sta->lq.agg_params.agg_dis_start_th, + lq_sta->lq.agg_params.agg_frame_cnt_limit); + + desc += + sprintf(buff + desc, + "Start idx [0]=0x%x [1]=0x%x [2]=0x%x [3]=0x%x\n", + lq_sta->lq.general_params.start_rate_idx[0], + lq_sta->lq.general_params.start_rate_idx[1], + lq_sta->lq.general_params.start_rate_idx[2], + lq_sta->lq.general_params.start_rate_idx[3]); + + for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) { + idx = + il4965_hwrate_to_plcp_idx(le32_to_cpu + (lq_sta->lq.rs_table[i]. + rate_n_flags)); + if (is_legacy(tbl->lq_type)) { + desc += + sprintf(buff + desc, " rate[%d] 0x%X %smbps\n", i, + le32_to_cpu(lq_sta->lq.rs_table[i]. + rate_n_flags), + il_rate_mcs[idx].mbps); + } else { + desc += + sprintf(buff + desc, " rate[%d] 0x%X %smbps (%s)\n", + i, + le32_to_cpu(lq_sta->lq.rs_table[i]. + rate_n_flags), + il_rate_mcs[idx].mbps, + il_rate_mcs[idx].mcs); + } + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buff, desc); + kfree(buff); + return ret; +} + +static const struct file_operations rs_sta_dbgfs_scale_table_ops = { + .write = il4965_rs_sta_dbgfs_scale_table_write, + .read = il4965_rs_sta_dbgfs_scale_table_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t +il4965_rs_sta_dbgfs_stats_table_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + char *buff; + int desc = 0; + int i, j; + ssize_t ret; + + struct il_lq_sta *lq_sta = file->private_data; + + buff = kmalloc(1024, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + for (i = 0; i < LQ_SIZE; i++) { + desc += + sprintf(buff + desc, + "%s type=%d SGI=%d HT40=%d DUP=%d GF=%d\n" + "rate=0x%X\n", lq_sta->active_tbl == i ? "*" : "x", + lq_sta->lq_info[i].lq_type, + lq_sta->lq_info[i].is_SGI, + lq_sta->lq_info[i].is_ht40, + lq_sta->lq_info[i].is_dup, lq_sta->is_green, + lq_sta->lq_info[i].current_rate); + for (j = 0; j < RATE_COUNT; j++) { + desc += + sprintf(buff + desc, + "counter=%d success=%d %%=%d\n", + lq_sta->lq_info[i].win[j].counter, + lq_sta->lq_info[i].win[j].success_counter, + lq_sta->lq_info[i].win[j].success_ratio); + } + } + ret = simple_read_from_buffer(user_buf, count, ppos, buff, desc); + kfree(buff); + return ret; +} + +static const struct file_operations rs_sta_dbgfs_stats_table_ops = { + .read = il4965_rs_sta_dbgfs_stats_table_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t +il4965_rs_sta_dbgfs_rate_scale_data_read(struct file *file, + char __user *user_buf, size_t count, + loff_t *ppos) +{ + char buff[120]; + int desc = 0; + struct il_lq_sta *lq_sta = file->private_data; + struct il_scale_tbl_info *tbl = &lq_sta->lq_info[lq_sta->active_tbl]; + + if (is_Ht(tbl->lq_type)) + desc += + sprintf(buff + desc, "Bit Rate= %d Mb/s\n", + tbl->expected_tpt[lq_sta->last_txrate_idx]); + else + desc += + sprintf(buff + desc, "Bit Rate= %d Mb/s\n", + il_rates[lq_sta->last_txrate_idx].ieee >> 1); + + return simple_read_from_buffer(user_buf, count, ppos, buff, desc); +} + +static const struct file_operations rs_sta_dbgfs_rate_scale_data_ops = { + .read = il4965_rs_sta_dbgfs_rate_scale_data_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static void +il4965_rs_add_debugfs(void *il, void *il_sta, struct dentry *dir) +{ + struct il_lq_sta *lq_sta = il_sta; + lq_sta->rs_sta_dbgfs_scale_table_file = + debugfs_create_file("rate_scale_table", S_IRUSR | S_IWUSR, dir, + lq_sta, &rs_sta_dbgfs_scale_table_ops); + lq_sta->rs_sta_dbgfs_stats_table_file = + debugfs_create_file("rate_stats_table", S_IRUSR, dir, lq_sta, + &rs_sta_dbgfs_stats_table_ops); + lq_sta->rs_sta_dbgfs_rate_scale_data_file = + debugfs_create_file("rate_scale_data", S_IRUSR, dir, lq_sta, + &rs_sta_dbgfs_rate_scale_data_ops); + lq_sta->rs_sta_dbgfs_tx_agg_tid_en_file = + debugfs_create_u8("tx_agg_tid_enable", S_IRUSR | S_IWUSR, dir, + &lq_sta->tx_agg_tid_en); + +} + +static void +il4965_rs_remove_debugfs(void *il, void *il_sta) +{ + struct il_lq_sta *lq_sta = il_sta; + debugfs_remove(lq_sta->rs_sta_dbgfs_scale_table_file); + debugfs_remove(lq_sta->rs_sta_dbgfs_stats_table_file); + debugfs_remove(lq_sta->rs_sta_dbgfs_rate_scale_data_file); + debugfs_remove(lq_sta->rs_sta_dbgfs_tx_agg_tid_en_file); +} +#endif + +/* + * Initialization of rate scaling information is done by driver after + * the station is added. Since mac80211 calls this function before a + * station is added we ignore it. + */ +static void +il4965_rs_rate_init_stub(void *il_r, struct ieee80211_supported_band *sband, + struct ieee80211_sta *sta, void *il_sta) +{ +} + +static struct rate_control_ops rs_4965_ops = { + .module = NULL, + .name = IL4965_RS_NAME, + .tx_status = il4965_rs_tx_status, + .get_rate = il4965_rs_get_rate, + .rate_init = il4965_rs_rate_init_stub, + .alloc = il4965_rs_alloc, + .free = il4965_rs_free, + .alloc_sta = il4965_rs_alloc_sta, + .free_sta = il4965_rs_free_sta, +#ifdef CPTCFG_MAC80211_DEBUGFS + .add_sta_debugfs = il4965_rs_add_debugfs, + .remove_sta_debugfs = il4965_rs_remove_debugfs, +#endif +}; + +int +il4965_rate_control_register(void) +{ + return ieee80211_rate_control_register(&rs_4965_ops); +} + +void +il4965_rate_control_unregister(void) +{ + ieee80211_rate_control_unregister(&rs_4965_ops); +} diff --git a/drivers/net/wireless/iwlegacy/4965.c b/drivers/net/wireless/iwlegacy/4965.c new file mode 100644 index 0000000..777a578 --- /dev/null +++ b/drivers/net/wireless/iwlegacy/4965.c @@ -0,0 +1,1951 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "4965.h" + +/** + * il_verify_inst_sparse - verify runtime uCode image in card vs. host, + * using sample data 100 bytes apart. If these sample points are good, + * it's a pretty good bet that everything between them is good, too. + */ +static int +il4965_verify_inst_sparse(struct il_priv *il, __le32 * image, u32 len) +{ + u32 val; + int ret = 0; + u32 errcnt = 0; + u32 i; + + D_INFO("ucode inst image size is %u\n", len); + + for (i = 0; i < len; i += 100, image += 100 / sizeof(u32)) { + /* read data comes through single port, auto-incr addr */ + /* NOTE: Use the debugless read so we don't flood kernel log + * if IL_DL_IO is set */ + il_wr(il, HBUS_TARG_MEM_RADDR, i + IL4965_RTC_INST_LOWER_BOUND); + val = _il_rd(il, HBUS_TARG_MEM_RDAT); + if (val != le32_to_cpu(*image)) { + ret = -EIO; + errcnt++; + if (errcnt >= 3) + break; + } + } + + return ret; +} + +/** + * il4965_verify_inst_full - verify runtime uCode image in card vs. host, + * looking at all data. + */ +static int +il4965_verify_inst_full(struct il_priv *il, __le32 * image, u32 len) +{ + u32 val; + u32 save_len = len; + int ret = 0; + u32 errcnt; + + D_INFO("ucode inst image size is %u\n", len); + + il_wr(il, HBUS_TARG_MEM_RADDR, IL4965_RTC_INST_LOWER_BOUND); + + errcnt = 0; + for (; len > 0; len -= sizeof(u32), image++) { + /* read data comes through single port, auto-incr addr */ + /* NOTE: Use the debugless read so we don't flood kernel log + * if IL_DL_IO is set */ + val = _il_rd(il, HBUS_TARG_MEM_RDAT); + if (val != le32_to_cpu(*image)) { + IL_ERR("uCode INST section is invalid at " + "offset 0x%x, is 0x%x, s/b 0x%x\n", + save_len - len, val, le32_to_cpu(*image)); + ret = -EIO; + errcnt++; + if (errcnt >= 20) + break; + } + } + + if (!errcnt) + D_INFO("ucode image in INSTRUCTION memory is good\n"); + + return ret; +} + +/** + * il4965_verify_ucode - determine which instruction image is in SRAM, + * and verify its contents + */ +int +il4965_verify_ucode(struct il_priv *il) +{ + __le32 *image; + u32 len; + int ret; + + /* Try bootstrap */ + image = (__le32 *) il->ucode_boot.v_addr; + len = il->ucode_boot.len; + ret = il4965_verify_inst_sparse(il, image, len); + if (!ret) { + D_INFO("Bootstrap uCode is good in inst SRAM\n"); + return 0; + } + + /* Try initialize */ + image = (__le32 *) il->ucode_init.v_addr; + len = il->ucode_init.len; + ret = il4965_verify_inst_sparse(il, image, len); + if (!ret) { + D_INFO("Initialize uCode is good in inst SRAM\n"); + return 0; + } + + /* Try runtime/protocol */ + image = (__le32 *) il->ucode_code.v_addr; + len = il->ucode_code.len; + ret = il4965_verify_inst_sparse(il, image, len); + if (!ret) { + D_INFO("Runtime uCode is good in inst SRAM\n"); + return 0; + } + + IL_ERR("NO VALID UCODE IMAGE IN INSTRUCTION SRAM!!\n"); + + /* Since nothing seems to match, show first several data entries in + * instruction SRAM, so maybe visual inspection will give a clue. + * Selection of bootstrap image (vs. other images) is arbitrary. */ + image = (__le32 *) il->ucode_boot.v_addr; + len = il->ucode_boot.len; + ret = il4965_verify_inst_full(il, image, len); + + return ret; +} + +/****************************************************************************** + * + * EEPROM related functions + * +******************************************************************************/ + +/* + * The device's EEPROM semaphore prevents conflicts between driver and uCode + * when accessing the EEPROM; each access is a series of pulses to/from the + * EEPROM chip, not a single event, so even reads could conflict if they + * weren't arbitrated by the semaphore. + */ +int +il4965_eeprom_acquire_semaphore(struct il_priv *il) +{ + u16 count; + int ret; + + for (count = 0; count < EEPROM_SEM_RETRY_LIMIT; count++) { + /* Request semaphore */ + il_set_bit(il, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM); + + /* See if we got it */ + ret = + _il_poll_bit(il, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM, + CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM, + EEPROM_SEM_TIMEOUT); + if (ret >= 0) + return ret; + } + + return ret; +} + +void +il4965_eeprom_release_semaphore(struct il_priv *il) +{ + il_clear_bit(il, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM); + +} + +int +il4965_eeprom_check_version(struct il_priv *il) +{ + u16 eeprom_ver; + u16 calib_ver; + + eeprom_ver = il_eeprom_query16(il, EEPROM_VERSION); + calib_ver = il_eeprom_query16(il, EEPROM_4965_CALIB_VERSION_OFFSET); + + if (eeprom_ver < il->cfg->eeprom_ver || + calib_ver < il->cfg->eeprom_calib_ver) + goto err; + + IL_INFO("device EEPROM VER=0x%x, CALIB=0x%x\n", eeprom_ver, calib_ver); + + return 0; +err: + IL_ERR("Unsupported (too old) EEPROM VER=0x%x < 0x%x " + "CALIB=0x%x < 0x%x\n", eeprom_ver, il->cfg->eeprom_ver, + calib_ver, il->cfg->eeprom_calib_ver); + return -EINVAL; + +} + +void +il4965_eeprom_get_mac(const struct il_priv *il, u8 * mac) +{ + const u8 *addr = il_eeprom_query_addr(il, + EEPROM_MAC_ADDRESS); + memcpy(mac, addr, ETH_ALEN); +} + +/* Send led command */ +static int +il4965_send_led_cmd(struct il_priv *il, struct il_led_cmd *led_cmd) +{ + struct il_host_cmd cmd = { + .id = C_LEDS, + .len = sizeof(struct il_led_cmd), + .data = led_cmd, + .flags = CMD_ASYNC, + .callback = NULL, + }; + u32 reg; + + reg = _il_rd(il, CSR_LED_REG); + if (reg != (reg & CSR_LED_BSM_CTRL_MSK)) + _il_wr(il, CSR_LED_REG, reg & CSR_LED_BSM_CTRL_MSK); + + return il_send_cmd(il, &cmd); +} + +/* Set led register off */ +void +il4965_led_enable(struct il_priv *il) +{ + _il_wr(il, CSR_LED_REG, CSR_LED_REG_TRUN_ON); +} + +static int il4965_send_tx_power(struct il_priv *il); +static int il4965_hw_get_temperature(struct il_priv *il); + +/* Highest firmware API version supported */ +#define IL4965_UCODE_API_MAX 2 + +/* Lowest firmware API version supported */ +#define IL4965_UCODE_API_MIN 2 + +#define IL4965_FW_PRE "iwlwifi-4965-" +#define _IL4965_MODULE_FIRMWARE(api) IL4965_FW_PRE #api ".ucode" +#define IL4965_MODULE_FIRMWARE(api) _IL4965_MODULE_FIRMWARE(api) + +/* check contents of special bootstrap uCode SRAM */ +static int +il4965_verify_bsm(struct il_priv *il) +{ + __le32 *image = il->ucode_boot.v_addr; + u32 len = il->ucode_boot.len; + u32 reg; + u32 val; + + D_INFO("Begin verify bsm\n"); + + /* verify BSM SRAM contents */ + val = il_rd_prph(il, BSM_WR_DWCOUNT_REG); + for (reg = BSM_SRAM_LOWER_BOUND; reg < BSM_SRAM_LOWER_BOUND + len; + reg += sizeof(u32), image++) { + val = il_rd_prph(il, reg); + if (val != le32_to_cpu(*image)) { + IL_ERR("BSM uCode verification failed at " + "addr 0x%08X+%u (of %u), is 0x%x, s/b 0x%x\n", + BSM_SRAM_LOWER_BOUND, reg - BSM_SRAM_LOWER_BOUND, + len, val, le32_to_cpu(*image)); + return -EIO; + } + } + + D_INFO("BSM bootstrap uCode image OK\n"); + + return 0; +} + +/** + * il4965_load_bsm - Load bootstrap instructions + * + * BSM operation: + * + * The Bootstrap State Machine (BSM) stores a short bootstrap uCode program + * in special SRAM that does not power down during RFKILL. When powering back + * up after power-saving sleeps (or during initial uCode load), the BSM loads + * the bootstrap program into the on-board processor, and starts it. + * + * The bootstrap program loads (via DMA) instructions and data for a new + * program from host DRAM locations indicated by the host driver in the + * BSM_DRAM_* registers. Once the new program is loaded, it starts + * automatically. + * + * When initializing the NIC, the host driver points the BSM to the + * "initialize" uCode image. This uCode sets up some internal data, then + * notifies host via "initialize alive" that it is complete. + * + * The host then replaces the BSM_DRAM_* pointer values to point to the + * normal runtime uCode instructions and a backup uCode data cache buffer + * (filled initially with starting data values for the on-board processor), + * then triggers the "initialize" uCode to load and launch the runtime uCode, + * which begins normal operation. + * + * When doing a power-save shutdown, runtime uCode saves data SRAM into + * the backup data cache in DRAM before SRAM is powered down. + * + * When powering back up, the BSM loads the bootstrap program. This reloads + * the runtime uCode instructions and the backup data cache into SRAM, + * and re-launches the runtime uCode from where it left off. + */ +static int +il4965_load_bsm(struct il_priv *il) +{ + __le32 *image = il->ucode_boot.v_addr; + u32 len = il->ucode_boot.len; + dma_addr_t pinst; + dma_addr_t pdata; + u32 inst_len; + u32 data_len; + int i; + u32 done; + u32 reg_offset; + int ret; + + D_INFO("Begin load bsm\n"); + + il->ucode_type = UCODE_RT; + + /* make sure bootstrap program is no larger than BSM's SRAM size */ + if (len > IL49_MAX_BSM_SIZE) + return -EINVAL; + + /* Tell bootstrap uCode where to find the "Initialize" uCode + * in host DRAM ... host DRAM physical address bits 35:4 for 4965. + * NOTE: il_init_alive_start() will replace these values, + * after the "initialize" uCode has run, to point to + * runtime/protocol instructions and backup data cache. + */ + pinst = il->ucode_init.p_addr >> 4; + pdata = il->ucode_init_data.p_addr >> 4; + inst_len = il->ucode_init.len; + data_len = il->ucode_init_data.len; + + il_wr_prph(il, BSM_DRAM_INST_PTR_REG, pinst); + il_wr_prph(il, BSM_DRAM_DATA_PTR_REG, pdata); + il_wr_prph(il, BSM_DRAM_INST_BYTECOUNT_REG, inst_len); + il_wr_prph(il, BSM_DRAM_DATA_BYTECOUNT_REG, data_len); + + /* Fill BSM memory with bootstrap instructions */ + for (reg_offset = BSM_SRAM_LOWER_BOUND; + reg_offset < BSM_SRAM_LOWER_BOUND + len; + reg_offset += sizeof(u32), image++) + _il_wr_prph(il, reg_offset, le32_to_cpu(*image)); + + ret = il4965_verify_bsm(il); + if (ret) + return ret; + + /* Tell BSM to copy from BSM SRAM into instruction SRAM, when asked */ + il_wr_prph(il, BSM_WR_MEM_SRC_REG, 0x0); + il_wr_prph(il, BSM_WR_MEM_DST_REG, IL49_RTC_INST_LOWER_BOUND); + il_wr_prph(il, BSM_WR_DWCOUNT_REG, len / sizeof(u32)); + + /* Load bootstrap code into instruction SRAM now, + * to prepare to load "initialize" uCode */ + il_wr_prph(il, BSM_WR_CTRL_REG, BSM_WR_CTRL_REG_BIT_START); + + /* Wait for load of bootstrap uCode to finish */ + for (i = 0; i < 100; i++) { + done = il_rd_prph(il, BSM_WR_CTRL_REG); + if (!(done & BSM_WR_CTRL_REG_BIT_START)) + break; + udelay(10); + } + if (i < 100) + D_INFO("BSM write complete, poll %d iterations\n", i); + else { + IL_ERR("BSM write did not complete!\n"); + return -EIO; + } + + /* Enable future boot loads whenever power management unit triggers it + * (e.g. when powering back up after power-save shutdown) */ + il_wr_prph(il, BSM_WR_CTRL_REG, BSM_WR_CTRL_REG_BIT_START_EN); + + return 0; +} + +/** + * il4965_set_ucode_ptrs - Set uCode address location + * + * Tell initialization uCode where to find runtime uCode. + * + * BSM registers initially contain pointers to initialization uCode. + * We need to replace them to load runtime uCode inst and data, + * and to save runtime data when powering down. + */ +static int +il4965_set_ucode_ptrs(struct il_priv *il) +{ + dma_addr_t pinst; + dma_addr_t pdata; + int ret = 0; + + /* bits 35:4 for 4965 */ + pinst = il->ucode_code.p_addr >> 4; + pdata = il->ucode_data_backup.p_addr >> 4; + + /* Tell bootstrap uCode where to find image to load */ + il_wr_prph(il, BSM_DRAM_INST_PTR_REG, pinst); + il_wr_prph(il, BSM_DRAM_DATA_PTR_REG, pdata); + il_wr_prph(il, BSM_DRAM_DATA_BYTECOUNT_REG, il->ucode_data.len); + + /* Inst byte count must be last to set up, bit 31 signals uCode + * that all new ptr/size info is in place */ + il_wr_prph(il, BSM_DRAM_INST_BYTECOUNT_REG, + il->ucode_code.len | BSM_DRAM_INST_LOAD); + D_INFO("Runtime uCode pointers are set.\n"); + + return ret; +} + +/** + * il4965_init_alive_start - Called after N_ALIVE notification received + * + * Called after N_ALIVE notification received from "initialize" uCode. + * + * The 4965 "initialize" ALIVE reply contains calibration data for: + * Voltage, temperature, and MIMO tx gain correction, now stored in il + * (3945 does not contain this data). + * + * Tell "initialize" uCode to go ahead and load the runtime uCode. +*/ +static void +il4965_init_alive_start(struct il_priv *il) +{ + /* Bootstrap uCode has loaded initialize uCode ... verify inst image. + * This is a paranoid check, because we would not have gotten the + * "initialize" alive if code weren't properly loaded. */ + if (il4965_verify_ucode(il)) { + /* Runtime instruction load was bad; + * take it all the way back down so we can try again */ + D_INFO("Bad \"initialize\" uCode load.\n"); + goto restart; + } + + /* Calculate temperature */ + il->temperature = il4965_hw_get_temperature(il); + + /* Send pointers to protocol/runtime uCode image ... init code will + * load and launch runtime uCode, which will send us another "Alive" + * notification. */ + D_INFO("Initialization Alive received.\n"); + if (il4965_set_ucode_ptrs(il)) { + /* Runtime instruction load won't happen; + * take it all the way back down so we can try again */ + D_INFO("Couldn't set up uCode pointers.\n"); + goto restart; + } + return; + +restart: + queue_work(il->workqueue, &il->restart); +} + +static bool +iw4965_is_ht40_channel(__le32 rxon_flags) +{ + int chan_mod = + le32_to_cpu(rxon_flags & RXON_FLG_CHANNEL_MODE_MSK) >> + RXON_FLG_CHANNEL_MODE_POS; + return (chan_mod == CHANNEL_MODE_PURE_40 || + chan_mod == CHANNEL_MODE_MIXED); +} + +void +il4965_nic_config(struct il_priv *il) +{ + unsigned long flags; + u16 radio_cfg; + + spin_lock_irqsave(&il->lock, flags); + + radio_cfg = il_eeprom_query16(il, EEPROM_RADIO_CONFIG); + + /* write radio config values to register */ + if (EEPROM_RF_CFG_TYPE_MSK(radio_cfg) == EEPROM_4965_RF_CFG_TYPE_MAX) + il_set_bit(il, CSR_HW_IF_CONFIG_REG, + EEPROM_RF_CFG_TYPE_MSK(radio_cfg) | + EEPROM_RF_CFG_STEP_MSK(radio_cfg) | + EEPROM_RF_CFG_DASH_MSK(radio_cfg)); + + /* set CSR_HW_CONFIG_REG for uCode use */ + il_set_bit(il, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI | + CSR_HW_IF_CONFIG_REG_BIT_MAC_SI); + + il->calib_info = + (struct il_eeprom_calib_info *) + il_eeprom_query_addr(il, EEPROM_4965_CALIB_TXPOWER_OFFSET); + + spin_unlock_irqrestore(&il->lock, flags); +} + +/* Reset differential Rx gains in NIC to prepare for chain noise calibration. + * Called after every association, but this runs only once! + * ... once chain noise is calibrated the first time, it's good forever. */ +static void +il4965_chain_noise_reset(struct il_priv *il) +{ + struct il_chain_noise_data *data = &(il->chain_noise_data); + + if (data->state == IL_CHAIN_NOISE_ALIVE && il_is_any_associated(il)) { + struct il_calib_diff_gain_cmd cmd; + + /* clear data for chain noise calibration algorithm */ + data->chain_noise_a = 0; + data->chain_noise_b = 0; + data->chain_noise_c = 0; + data->chain_signal_a = 0; + data->chain_signal_b = 0; + data->chain_signal_c = 0; + data->beacon_count = 0; + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.op_code = IL_PHY_CALIBRATE_DIFF_GAIN_CMD; + cmd.diff_gain_a = 0; + cmd.diff_gain_b = 0; + cmd.diff_gain_c = 0; + if (il_send_cmd_pdu(il, C_PHY_CALIBRATION, sizeof(cmd), &cmd)) + IL_ERR("Could not send C_PHY_CALIBRATION\n"); + data->state = IL_CHAIN_NOISE_ACCUMULATE; + D_CALIB("Run chain_noise_calibrate\n"); + } +} + +static s32 +il4965_math_div_round(s32 num, s32 denom, s32 * res) +{ + s32 sign = 1; + + if (num < 0) { + sign = -sign; + num = -num; + } + if (denom < 0) { + sign = -sign; + denom = -denom; + } + *res = 1; + *res = ((num * 2 + denom) / (denom * 2)) * sign; + + return 1; +} + +/** + * il4965_get_voltage_compensation - Power supply voltage comp for txpower + * + * Determines power supply voltage compensation for txpower calculations. + * Returns number of 1/2-dB steps to subtract from gain table idx, + * to compensate for difference between power supply voltage during + * factory measurements, vs. current power supply voltage. + * + * Voltage indication is higher for lower voltage. + * Lower voltage requires more gain (lower gain table idx). + */ +static s32 +il4965_get_voltage_compensation(s32 eeprom_voltage, s32 current_voltage) +{ + s32 comp = 0; + + if (TX_POWER_IL_ILLEGAL_VOLTAGE == eeprom_voltage || + TX_POWER_IL_ILLEGAL_VOLTAGE == current_voltage) + return 0; + + il4965_math_div_round(current_voltage - eeprom_voltage, + TX_POWER_IL_VOLTAGE_CODES_PER_03V, &comp); + + if (current_voltage > eeprom_voltage) + comp *= 2; + if ((comp < -2) || (comp > 2)) + comp = 0; + + return comp; +} + +static s32 +il4965_get_tx_atten_grp(u16 channel) +{ + if (channel >= CALIB_IL_TX_ATTEN_GR5_FCH && + channel <= CALIB_IL_TX_ATTEN_GR5_LCH) + return CALIB_CH_GROUP_5; + + if (channel >= CALIB_IL_TX_ATTEN_GR1_FCH && + channel <= CALIB_IL_TX_ATTEN_GR1_LCH) + return CALIB_CH_GROUP_1; + + if (channel >= CALIB_IL_TX_ATTEN_GR2_FCH && + channel <= CALIB_IL_TX_ATTEN_GR2_LCH) + return CALIB_CH_GROUP_2; + + if (channel >= CALIB_IL_TX_ATTEN_GR3_FCH && + channel <= CALIB_IL_TX_ATTEN_GR3_LCH) + return CALIB_CH_GROUP_3; + + if (channel >= CALIB_IL_TX_ATTEN_GR4_FCH && + channel <= CALIB_IL_TX_ATTEN_GR4_LCH) + return CALIB_CH_GROUP_4; + + return -EINVAL; +} + +static u32 +il4965_get_sub_band(const struct il_priv *il, u32 channel) +{ + s32 b = -1; + + for (b = 0; b < EEPROM_TX_POWER_BANDS; b++) { + if (il->calib_info->band_info[b].ch_from == 0) + continue; + + if (channel >= il->calib_info->band_info[b].ch_from && + channel <= il->calib_info->band_info[b].ch_to) + break; + } + + return b; +} + +static s32 +il4965_interpolate_value(s32 x, s32 x1, s32 y1, s32 x2, s32 y2) +{ + s32 val; + + if (x2 == x1) + return y1; + else { + il4965_math_div_round((x2 - x) * (y1 - y2), (x2 - x1), &val); + return val + y2; + } +} + +/** + * il4965_interpolate_chan - Interpolate factory measurements for one channel + * + * Interpolates factory measurements from the two sample channels within a + * sub-band, to apply to channel of interest. Interpolation is proportional to + * differences in channel frequencies, which is proportional to differences + * in channel number. + */ +static int +il4965_interpolate_chan(struct il_priv *il, u32 channel, + struct il_eeprom_calib_ch_info *chan_info) +{ + s32 s = -1; + u32 c; + u32 m; + const struct il_eeprom_calib_measure *m1; + const struct il_eeprom_calib_measure *m2; + struct il_eeprom_calib_measure *omeas; + u32 ch_i1; + u32 ch_i2; + + s = il4965_get_sub_band(il, channel); + if (s >= EEPROM_TX_POWER_BANDS) { + IL_ERR("Tx Power can not find channel %d\n", channel); + return -1; + } + + ch_i1 = il->calib_info->band_info[s].ch1.ch_num; + ch_i2 = il->calib_info->band_info[s].ch2.ch_num; + chan_info->ch_num = (u8) channel; + + D_TXPOWER("channel %d subband %d factory cal ch %d & %d\n", channel, s, + ch_i1, ch_i2); + + for (c = 0; c < EEPROM_TX_POWER_TX_CHAINS; c++) { + for (m = 0; m < EEPROM_TX_POWER_MEASUREMENTS; m++) { + m1 = &(il->calib_info->band_info[s].ch1. + measurements[c][m]); + m2 = &(il->calib_info->band_info[s].ch2. + measurements[c][m]); + omeas = &(chan_info->measurements[c][m]); + + omeas->actual_pow = + (u8) il4965_interpolate_value(channel, ch_i1, + m1->actual_pow, ch_i2, + m2->actual_pow); + omeas->gain_idx = + (u8) il4965_interpolate_value(channel, ch_i1, + m1->gain_idx, ch_i2, + m2->gain_idx); + omeas->temperature = + (u8) il4965_interpolate_value(channel, ch_i1, + m1->temperature, + ch_i2, + m2->temperature); + omeas->pa_det = + (s8) il4965_interpolate_value(channel, ch_i1, + m1->pa_det, ch_i2, + m2->pa_det); + + D_TXPOWER("chain %d meas %d AP1=%d AP2=%d AP=%d\n", c, + m, m1->actual_pow, m2->actual_pow, + omeas->actual_pow); + D_TXPOWER("chain %d meas %d NI1=%d NI2=%d NI=%d\n", c, + m, m1->gain_idx, m2->gain_idx, + omeas->gain_idx); + D_TXPOWER("chain %d meas %d PA1=%d PA2=%d PA=%d\n", c, + m, m1->pa_det, m2->pa_det, omeas->pa_det); + D_TXPOWER("chain %d meas %d T1=%d T2=%d T=%d\n", c, + m, m1->temperature, m2->temperature, + omeas->temperature); + } + } + + return 0; +} + +/* bit-rate-dependent table to prevent Tx distortion, in half-dB units, + * for OFDM 6, 12, 18, 24, 36, 48, 54, 60 MBit, and CCK all rates. */ +static s32 back_off_table[] = { + 10, 10, 10, 10, 10, 15, 17, 20, /* OFDM SISO 20 MHz */ + 10, 10, 10, 10, 10, 15, 17, 20, /* OFDM MIMO 20 MHz */ + 10, 10, 10, 10, 10, 15, 17, 20, /* OFDM SISO 40 MHz */ + 10, 10, 10, 10, 10, 15, 17, 20, /* OFDM MIMO 40 MHz */ + 10 /* CCK */ +}; + +/* Thermal compensation values for txpower for various frequency ranges ... + * ratios from 3:1 to 4.5:1 of degrees (Celsius) per half-dB gain adjust */ +static struct il4965_txpower_comp_entry { + s32 degrees_per_05db_a; + s32 degrees_per_05db_a_denom; +} tx_power_cmp_tble[CALIB_CH_GROUP_MAX] = { + { + 9, 2}, /* group 0 5.2, ch 34-43 */ + { + 4, 1}, /* group 1 5.2, ch 44-70 */ + { + 4, 1}, /* group 2 5.2, ch 71-124 */ + { + 4, 1}, /* group 3 5.2, ch 125-200 */ + { + 3, 1} /* group 4 2.4, ch all */ +}; + +static s32 +get_min_power_idx(s32 rate_power_idx, u32 band) +{ + if (!band) { + if ((rate_power_idx & 7) <= 4) + return MIN_TX_GAIN_IDX_52GHZ_EXT; + } + return MIN_TX_GAIN_IDX; +} + +struct gain_entry { + u8 dsp; + u8 radio; +}; + +static const struct gain_entry gain_table[2][108] = { + /* 5.2GHz power gain idx table */ + { + {123, 0x3F}, /* highest txpower */ + {117, 0x3F}, + {110, 0x3F}, + {104, 0x3F}, + {98, 0x3F}, + {110, 0x3E}, + {104, 0x3E}, + {98, 0x3E}, + {110, 0x3D}, + {104, 0x3D}, + {98, 0x3D}, + {110, 0x3C}, + {104, 0x3C}, + {98, 0x3C}, + {110, 0x3B}, + {104, 0x3B}, + {98, 0x3B}, + {110, 0x3A}, + {104, 0x3A}, + {98, 0x3A}, + {110, 0x39}, + {104, 0x39}, + {98, 0x39}, + {110, 0x38}, + {104, 0x38}, + {98, 0x38}, + {110, 0x37}, + {104, 0x37}, + {98, 0x37}, + {110, 0x36}, + {104, 0x36}, + {98, 0x36}, + {110, 0x35}, + {104, 0x35}, + {98, 0x35}, + {110, 0x34}, + {104, 0x34}, + {98, 0x34}, + {110, 0x33}, + {104, 0x33}, + {98, 0x33}, + {110, 0x32}, + {104, 0x32}, + {98, 0x32}, + {110, 0x31}, + {104, 0x31}, + {98, 0x31}, + {110, 0x30}, + {104, 0x30}, + {98, 0x30}, + {110, 0x25}, + {104, 0x25}, + {98, 0x25}, + {110, 0x24}, + {104, 0x24}, + {98, 0x24}, + {110, 0x23}, + {104, 0x23}, + {98, 0x23}, + {110, 0x22}, + {104, 0x18}, + {98, 0x18}, + {110, 0x17}, + {104, 0x17}, + {98, 0x17}, + {110, 0x16}, + {104, 0x16}, + {98, 0x16}, + {110, 0x15}, + {104, 0x15}, + {98, 0x15}, + {110, 0x14}, + {104, 0x14}, + {98, 0x14}, + {110, 0x13}, + {104, 0x13}, + {98, 0x13}, + {110, 0x12}, + {104, 0x08}, + {98, 0x08}, + {110, 0x07}, + {104, 0x07}, + {98, 0x07}, + {110, 0x06}, + {104, 0x06}, + {98, 0x06}, + {110, 0x05}, + {104, 0x05}, + {98, 0x05}, + {110, 0x04}, + {104, 0x04}, + {98, 0x04}, + {110, 0x03}, + {104, 0x03}, + {98, 0x03}, + {110, 0x02}, + {104, 0x02}, + {98, 0x02}, + {110, 0x01}, + {104, 0x01}, + {98, 0x01}, + {110, 0x00}, + {104, 0x00}, + {98, 0x00}, + {93, 0x00}, + {88, 0x00}, + {83, 0x00}, + {78, 0x00}, + }, + /* 2.4GHz power gain idx table */ + { + {110, 0x3f}, /* highest txpower */ + {104, 0x3f}, + {98, 0x3f}, + {110, 0x3e}, + {104, 0x3e}, + {98, 0x3e}, + {110, 0x3d}, + {104, 0x3d}, + {98, 0x3d}, + {110, 0x3c}, + {104, 0x3c}, + {98, 0x3c}, + {110, 0x3b}, + {104, 0x3b}, + {98, 0x3b}, + {110, 0x3a}, + {104, 0x3a}, + {98, 0x3a}, + {110, 0x39}, + {104, 0x39}, + {98, 0x39}, + {110, 0x38}, + {104, 0x38}, + {98, 0x38}, + {110, 0x37}, + {104, 0x37}, + {98, 0x37}, + {110, 0x36}, + {104, 0x36}, + {98, 0x36}, + {110, 0x35}, + {104, 0x35}, + {98, 0x35}, + {110, 0x34}, + {104, 0x34}, + {98, 0x34}, + {110, 0x33}, + {104, 0x33}, + {98, 0x33}, + {110, 0x32}, + {104, 0x32}, + {98, 0x32}, + {110, 0x31}, + {104, 0x31}, + {98, 0x31}, + {110, 0x30}, + {104, 0x30}, + {98, 0x30}, + {110, 0x6}, + {104, 0x6}, + {98, 0x6}, + {110, 0x5}, + {104, 0x5}, + {98, 0x5}, + {110, 0x4}, + {104, 0x4}, + {98, 0x4}, + {110, 0x3}, + {104, 0x3}, + {98, 0x3}, + {110, 0x2}, + {104, 0x2}, + {98, 0x2}, + {110, 0x1}, + {104, 0x1}, + {98, 0x1}, + {110, 0x0}, + {104, 0x0}, + {98, 0x0}, + {97, 0}, + {96, 0}, + {95, 0}, + {94, 0}, + {93, 0}, + {92, 0}, + {91, 0}, + {90, 0}, + {89, 0}, + {88, 0}, + {87, 0}, + {86, 0}, + {85, 0}, + {84, 0}, + {83, 0}, + {82, 0}, + {81, 0}, + {80, 0}, + {79, 0}, + {78, 0}, + {77, 0}, + {76, 0}, + {75, 0}, + {74, 0}, + {73, 0}, + {72, 0}, + {71, 0}, + {70, 0}, + {69, 0}, + {68, 0}, + {67, 0}, + {66, 0}, + {65, 0}, + {64, 0}, + {63, 0}, + {62, 0}, + {61, 0}, + {60, 0}, + {59, 0}, + } +}; + +static int +il4965_fill_txpower_tbl(struct il_priv *il, u8 band, u16 channel, u8 is_ht40, + u8 ctrl_chan_high, + struct il4965_tx_power_db *tx_power_tbl) +{ + u8 saturation_power; + s32 target_power; + s32 user_target_power; + s32 power_limit; + s32 current_temp; + s32 reg_limit; + s32 current_regulatory; + s32 txatten_grp = CALIB_CH_GROUP_MAX; + int i; + int c; + const struct il_channel_info *ch_info = NULL; + struct il_eeprom_calib_ch_info ch_eeprom_info; + const struct il_eeprom_calib_measure *measurement; + s16 voltage; + s32 init_voltage; + s32 voltage_compensation; + s32 degrees_per_05db_num; + s32 degrees_per_05db_denom; + s32 factory_temp; + s32 temperature_comp[2]; + s32 factory_gain_idx[2]; + s32 factory_actual_pwr[2]; + s32 power_idx; + + /* tx_power_user_lmt is in dBm, convert to half-dBm (half-dB units + * are used for idxing into txpower table) */ + user_target_power = 2 * il->tx_power_user_lmt; + + /* Get current (RXON) channel, band, width */ + D_TXPOWER("chan %d band %d is_ht40 %d\n", channel, band, is_ht40); + + ch_info = il_get_channel_info(il, il->band, channel); + + if (!il_is_channel_valid(ch_info)) + return -EINVAL; + + /* get txatten group, used to select 1) thermal txpower adjustment + * and 2) mimo txpower balance between Tx chains. */ + txatten_grp = il4965_get_tx_atten_grp(channel); + if (txatten_grp < 0) { + IL_ERR("Can't find txatten group for channel %d.\n", channel); + return txatten_grp; + } + + D_TXPOWER("channel %d belongs to txatten group %d\n", channel, + txatten_grp); + + if (is_ht40) { + if (ctrl_chan_high) + channel -= 2; + else + channel += 2; + } + + /* hardware txpower limits ... + * saturation (clipping distortion) txpowers are in half-dBm */ + if (band) + saturation_power = il->calib_info->saturation_power24; + else + saturation_power = il->calib_info->saturation_power52; + + if (saturation_power < IL_TX_POWER_SATURATION_MIN || + saturation_power > IL_TX_POWER_SATURATION_MAX) { + if (band) + saturation_power = IL_TX_POWER_DEFAULT_SATURATION_24; + else + saturation_power = IL_TX_POWER_DEFAULT_SATURATION_52; + } + + /* regulatory txpower limits ... reg_limit values are in half-dBm, + * max_power_avg values are in dBm, convert * 2 */ + if (is_ht40) + reg_limit = ch_info->ht40_max_power_avg * 2; + else + reg_limit = ch_info->max_power_avg * 2; + + if ((reg_limit < IL_TX_POWER_REGULATORY_MIN) || + (reg_limit > IL_TX_POWER_REGULATORY_MAX)) { + if (band) + reg_limit = IL_TX_POWER_DEFAULT_REGULATORY_24; + else + reg_limit = IL_TX_POWER_DEFAULT_REGULATORY_52; + } + + /* Interpolate txpower calibration values for this channel, + * based on factory calibration tests on spaced channels. */ + il4965_interpolate_chan(il, channel, &ch_eeprom_info); + + /* calculate tx gain adjustment based on power supply voltage */ + voltage = le16_to_cpu(il->calib_info->voltage); + init_voltage = (s32) le32_to_cpu(il->card_alive_init.voltage); + voltage_compensation = + il4965_get_voltage_compensation(voltage, init_voltage); + + D_TXPOWER("curr volt %d eeprom volt %d volt comp %d\n", init_voltage, + voltage, voltage_compensation); + + /* get current temperature (Celsius) */ + current_temp = max(il->temperature, IL_TX_POWER_TEMPERATURE_MIN); + current_temp = min(il->temperature, IL_TX_POWER_TEMPERATURE_MAX); + current_temp = KELVIN_TO_CELSIUS(current_temp); + + /* select thermal txpower adjustment params, based on channel group + * (same frequency group used for mimo txatten adjustment) */ + degrees_per_05db_num = + tx_power_cmp_tble[txatten_grp].degrees_per_05db_a; + degrees_per_05db_denom = + tx_power_cmp_tble[txatten_grp].degrees_per_05db_a_denom; + + /* get per-chain txpower values from factory measurements */ + for (c = 0; c < 2; c++) { + measurement = &ch_eeprom_info.measurements[c][1]; + + /* txgain adjustment (in half-dB steps) based on difference + * between factory and current temperature */ + factory_temp = measurement->temperature; + il4965_math_div_round((current_temp - + factory_temp) * degrees_per_05db_denom, + degrees_per_05db_num, + &temperature_comp[c]); + + factory_gain_idx[c] = measurement->gain_idx; + factory_actual_pwr[c] = measurement->actual_pow; + + D_TXPOWER("chain = %d\n", c); + D_TXPOWER("fctry tmp %d, " "curr tmp %d, comp %d steps\n", + factory_temp, current_temp, temperature_comp[c]); + + D_TXPOWER("fctry idx %d, fctry pwr %d\n", factory_gain_idx[c], + factory_actual_pwr[c]); + } + + /* for each of 33 bit-rates (including 1 for CCK) */ + for (i = 0; i < POWER_TBL_NUM_ENTRIES; i++) { + u8 is_mimo_rate; + union il4965_tx_power_dual_stream tx_power; + + /* for mimo, reduce each chain's txpower by half + * (3dB, 6 steps), so total output power is regulatory + * compliant. */ + if (i & 0x8) { + current_regulatory = + reg_limit - + IL_TX_POWER_MIMO_REGULATORY_COMPENSATION; + is_mimo_rate = 1; + } else { + current_regulatory = reg_limit; + is_mimo_rate = 0; + } + + /* find txpower limit, either hardware or regulatory */ + power_limit = saturation_power - back_off_table[i]; + if (power_limit > current_regulatory) + power_limit = current_regulatory; + + /* reduce user's txpower request if necessary + * for this rate on this channel */ + target_power = user_target_power; + if (target_power > power_limit) + target_power = power_limit; + + D_TXPOWER("rate %d sat %d reg %d usr %d tgt %d\n", i, + saturation_power - back_off_table[i], + current_regulatory, user_target_power, target_power); + + /* for each of 2 Tx chains (radio transmitters) */ + for (c = 0; c < 2; c++) { + s32 atten_value; + + if (is_mimo_rate) + atten_value = + (s32) le32_to_cpu(il->card_alive_init. + tx_atten[txatten_grp][c]); + else + atten_value = 0; + + /* calculate idx; higher idx means lower txpower */ + power_idx = + (u8) (factory_gain_idx[c] - + (target_power - factory_actual_pwr[c]) - + temperature_comp[c] - voltage_compensation + + atten_value); + +/* D_TXPOWER("calculated txpower idx %d\n", + power_idx); */ + + if (power_idx < get_min_power_idx(i, band)) + power_idx = get_min_power_idx(i, band); + + /* adjust 5 GHz idx to support negative idxes */ + if (!band) + power_idx += 9; + + /* CCK, rate 32, reduce txpower for CCK */ + if (i == POWER_TBL_CCK_ENTRY) + power_idx += + IL_TX_POWER_CCK_COMPENSATION_C_STEP; + + /* stay within the table! */ + if (power_idx > 107) { + IL_WARN("txpower idx %d > 107\n", power_idx); + power_idx = 107; + } + if (power_idx < 0) { + IL_WARN("txpower idx %d < 0\n", power_idx); + power_idx = 0; + } + + /* fill txpower command for this rate/chain */ + tx_power.s.radio_tx_gain[c] = + gain_table[band][power_idx].radio; + tx_power.s.dsp_predis_atten[c] = + gain_table[band][power_idx].dsp; + + D_TXPOWER("chain %d mimo %d idx %d " + "gain 0x%02x dsp %d\n", c, atten_value, + power_idx, tx_power.s.radio_tx_gain[c], + tx_power.s.dsp_predis_atten[c]); + } /* for each chain */ + + tx_power_tbl->power_tbl[i].dw = cpu_to_le32(tx_power.dw); + + } /* for each rate */ + + return 0; +} + +/** + * il4965_send_tx_power - Configure the TXPOWER level user limit + * + * Uses the active RXON for channel, band, and characteristics (ht40, high) + * The power limit is taken from il->tx_power_user_lmt. + */ +static int +il4965_send_tx_power(struct il_priv *il) +{ + struct il4965_txpowertable_cmd cmd = { 0 }; + int ret; + u8 band = 0; + bool is_ht40 = false; + u8 ctrl_chan_high = 0; + + if (WARN_ONCE + (test_bit(S_SCAN_HW, &il->status), + "TX Power requested while scanning!\n")) + return -EAGAIN; + + band = il->band == IEEE80211_BAND_2GHZ; + + is_ht40 = iw4965_is_ht40_channel(il->active.flags); + + if (is_ht40 && (il->active.flags & RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK)) + ctrl_chan_high = 1; + + cmd.band = band; + cmd.channel = il->active.channel; + + ret = + il4965_fill_txpower_tbl(il, band, le16_to_cpu(il->active.channel), + is_ht40, ctrl_chan_high, &cmd.tx_power); + if (ret) + goto out; + + ret = il_send_cmd_pdu(il, C_TX_PWR_TBL, sizeof(cmd), &cmd); + +out: + return ret; +} + +static int +il4965_send_rxon_assoc(struct il_priv *il) +{ + int ret = 0; + struct il4965_rxon_assoc_cmd rxon_assoc; + const struct il_rxon_cmd *rxon1 = &il->staging; + const struct il_rxon_cmd *rxon2 = &il->active; + + if (rxon1->flags == rxon2->flags && + rxon1->filter_flags == rxon2->filter_flags && + rxon1->cck_basic_rates == rxon2->cck_basic_rates && + rxon1->ofdm_ht_single_stream_basic_rates == + rxon2->ofdm_ht_single_stream_basic_rates && + rxon1->ofdm_ht_dual_stream_basic_rates == + rxon2->ofdm_ht_dual_stream_basic_rates && + rxon1->rx_chain == rxon2->rx_chain && + rxon1->ofdm_basic_rates == rxon2->ofdm_basic_rates) { + D_INFO("Using current RXON_ASSOC. Not resending.\n"); + return 0; + } + + rxon_assoc.flags = il->staging.flags; + rxon_assoc.filter_flags = il->staging.filter_flags; + rxon_assoc.ofdm_basic_rates = il->staging.ofdm_basic_rates; + rxon_assoc.cck_basic_rates = il->staging.cck_basic_rates; + rxon_assoc.reserved = 0; + rxon_assoc.ofdm_ht_single_stream_basic_rates = + il->staging.ofdm_ht_single_stream_basic_rates; + rxon_assoc.ofdm_ht_dual_stream_basic_rates = + il->staging.ofdm_ht_dual_stream_basic_rates; + rxon_assoc.rx_chain_select_flags = il->staging.rx_chain; + + ret = + il_send_cmd_pdu_async(il, C_RXON_ASSOC, sizeof(rxon_assoc), + &rxon_assoc, NULL); + + return ret; +} + +static int +il4965_commit_rxon(struct il_priv *il) +{ + /* cast away the const for active_rxon in this function */ + struct il_rxon_cmd *active_rxon = (void *)&il->active; + int ret; + bool new_assoc = !!(il->staging.filter_flags & RXON_FILTER_ASSOC_MSK); + + if (!il_is_alive(il)) + return -EBUSY; + + /* always get timestamp with Rx frame */ + il->staging.flags |= RXON_FLG_TSF2HOST_MSK; + + ret = il_check_rxon_cmd(il); + if (ret) { + IL_ERR("Invalid RXON configuration. Not committing.\n"); + return -EINVAL; + } + + /* + * receive commit_rxon request + * abort any previous channel switch if still in process + */ + if (test_bit(S_CHANNEL_SWITCH_PENDING, &il->status) && + il->switch_channel != il->staging.channel) { + D_11H("abort channel switch on %d\n", + le16_to_cpu(il->switch_channel)); + il_chswitch_done(il, false); + } + + /* If we don't need to send a full RXON, we can use + * il_rxon_assoc_cmd which is used to reconfigure filter + * and other flags for the current radio configuration. */ + if (!il_full_rxon_required(il)) { + ret = il_send_rxon_assoc(il); + if (ret) { + IL_ERR("Error setting RXON_ASSOC (%d)\n", ret); + return ret; + } + + memcpy(active_rxon, &il->staging, sizeof(*active_rxon)); + il_print_rx_config_cmd(il); + /* + * We do not commit tx power settings while channel changing, + * do it now if tx power changed. + */ + il_set_tx_power(il, il->tx_power_next, false); + return 0; + } + + /* If we are currently associated and the new config requires + * an RXON_ASSOC and the new config wants the associated mask enabled, + * we must clear the associated from the active configuration + * before we apply the new config */ + if (il_is_associated(il) && new_assoc) { + D_INFO("Toggling associated bit on current RXON\n"); + active_rxon->filter_flags &= ~RXON_FILTER_ASSOC_MSK; + + ret = + il_send_cmd_pdu(il, C_RXON, + sizeof(struct il_rxon_cmd), active_rxon); + + /* If the mask clearing failed then we set + * active_rxon back to what it was previously */ + if (ret) { + active_rxon->filter_flags |= RXON_FILTER_ASSOC_MSK; + IL_ERR("Error clearing ASSOC_MSK (%d)\n", ret); + return ret; + } + il_clear_ucode_stations(il); + il_restore_stations(il); + ret = il4965_restore_default_wep_keys(il); + if (ret) { + IL_ERR("Failed to restore WEP keys (%d)\n", ret); + return ret; + } + } + + D_INFO("Sending RXON\n" "* with%s RXON_FILTER_ASSOC_MSK\n" + "* channel = %d\n" "* bssid = %pM\n", (new_assoc ? "" : "out"), + le16_to_cpu(il->staging.channel), il->staging.bssid_addr); + + il_set_rxon_hwcrypto(il, !il->cfg->mod_params->sw_crypto); + + /* Apply the new configuration + * RXON unassoc clears the station table in uCode so restoration of + * stations is needed after it (the RXON command) completes + */ + if (!new_assoc) { + ret = + il_send_cmd_pdu(il, C_RXON, + sizeof(struct il_rxon_cmd), &il->staging); + if (ret) { + IL_ERR("Error setting new RXON (%d)\n", ret); + return ret; + } + D_INFO("Return from !new_assoc RXON.\n"); + memcpy(active_rxon, &il->staging, sizeof(*active_rxon)); + il_clear_ucode_stations(il); + il_restore_stations(il); + ret = il4965_restore_default_wep_keys(il); + if (ret) { + IL_ERR("Failed to restore WEP keys (%d)\n", ret); + return ret; + } + } + if (new_assoc) { + il->start_calib = 0; + /* Apply the new configuration + * RXON assoc doesn't clear the station table in uCode, + */ + ret = + il_send_cmd_pdu(il, C_RXON, + sizeof(struct il_rxon_cmd), &il->staging); + if (ret) { + IL_ERR("Error setting new RXON (%d)\n", ret); + return ret; + } + memcpy(active_rxon, &il->staging, sizeof(*active_rxon)); + } + il_print_rx_config_cmd(il); + + il4965_init_sensitivity(il); + + /* If we issue a new RXON command which required a tune then we must + * send a new TXPOWER command or we won't be able to Tx any frames */ + ret = il_set_tx_power(il, il->tx_power_next, true); + if (ret) { + IL_ERR("Error sending TX power (%d)\n", ret); + return ret; + } + + return 0; +} + +static int +il4965_hw_channel_switch(struct il_priv *il, + struct ieee80211_channel_switch *ch_switch) +{ + int rc; + u8 band = 0; + bool is_ht40 = false; + u8 ctrl_chan_high = 0; + struct il4965_channel_switch_cmd cmd; + const struct il_channel_info *ch_info; + u32 switch_time_in_usec, ucode_switch_time; + u16 ch; + u32 tsf_low; + u8 switch_count; + u16 beacon_interval = le16_to_cpu(il->timing.beacon_interval); + struct ieee80211_vif *vif = il->vif; + band = (il->band == IEEE80211_BAND_2GHZ); + + if (WARN_ON_ONCE(vif == NULL)) + return -EIO; + + is_ht40 = iw4965_is_ht40_channel(il->staging.flags); + + if (is_ht40 && (il->staging.flags & RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK)) + ctrl_chan_high = 1; + + cmd.band = band; + cmd.expect_beacon = 0; + ch = ch_switch->chandef.chan->hw_value; + cmd.channel = cpu_to_le16(ch); + cmd.rxon_flags = il->staging.flags; + cmd.rxon_filter_flags = il->staging.filter_flags; + switch_count = ch_switch->count; + tsf_low = ch_switch->timestamp & 0x0ffffffff; + /* + * calculate the ucode channel switch time + * adding TSF as one of the factor for when to switch + */ + if (il->ucode_beacon_time > tsf_low && beacon_interval) { + if (switch_count > + ((il->ucode_beacon_time - tsf_low) / beacon_interval)) { + switch_count -= + (il->ucode_beacon_time - tsf_low) / beacon_interval; + } else + switch_count = 0; + } + if (switch_count <= 1) + cmd.switch_time = cpu_to_le32(il->ucode_beacon_time); + else { + switch_time_in_usec = + vif->bss_conf.beacon_int * switch_count * TIME_UNIT; + ucode_switch_time = + il_usecs_to_beacons(il, switch_time_in_usec, + beacon_interval); + cmd.switch_time = + il_add_beacon_time(il, il->ucode_beacon_time, + ucode_switch_time, beacon_interval); + } + D_11H("uCode time for the switch is 0x%x\n", cmd.switch_time); + ch_info = il_get_channel_info(il, il->band, ch); + if (ch_info) + cmd.expect_beacon = il_is_channel_radar(ch_info); + else { + IL_ERR("invalid channel switch from %u to %u\n", + il->active.channel, ch); + return -EFAULT; + } + + rc = il4965_fill_txpower_tbl(il, band, ch, is_ht40, ctrl_chan_high, + &cmd.tx_power); + if (rc) { + D_11H("error:%d fill txpower_tbl\n", rc); + return rc; + } + + return il_send_cmd_pdu(il, C_CHANNEL_SWITCH, sizeof(cmd), &cmd); +} + +/** + * il4965_txq_update_byte_cnt_tbl - Set up entry in Tx byte-count array + */ +static void +il4965_txq_update_byte_cnt_tbl(struct il_priv *il, struct il_tx_queue *txq, + u16 byte_cnt) +{ + struct il4965_scd_bc_tbl *scd_bc_tbl = il->scd_bc_tbls.addr; + int txq_id = txq->q.id; + int write_ptr = txq->q.write_ptr; + int len = byte_cnt + IL_TX_CRC_SIZE + IL_TX_DELIMITER_SIZE; + __le16 bc_ent; + + WARN_ON(len > 0xFFF || write_ptr >= TFD_QUEUE_SIZE_MAX); + + bc_ent = cpu_to_le16(len & 0xFFF); + /* Set up byte count within first 256 entries */ + scd_bc_tbl[txq_id].tfd_offset[write_ptr] = bc_ent; + + /* If within first 64 entries, duplicate at end */ + if (write_ptr < TFD_QUEUE_SIZE_BC_DUP) + scd_bc_tbl[txq_id].tfd_offset[TFD_QUEUE_SIZE_MAX + write_ptr] = + bc_ent; +} + +/** + * il4965_hw_get_temperature - return the calibrated temperature (in Kelvin) + * @stats: Provides the temperature reading from the uCode + * + * A return of <0 indicates bogus data in the stats + */ +static int +il4965_hw_get_temperature(struct il_priv *il) +{ + s32 temperature; + s32 vt; + s32 R1, R2, R3; + u32 R4; + + if (test_bit(S_TEMPERATURE, &il->status) && + (il->_4965.stats.flag & STATS_REPLY_FLG_HT40_MODE_MSK)) { + D_TEMP("Running HT40 temperature calibration\n"); + R1 = (s32) le32_to_cpu(il->card_alive_init.therm_r1[1]); + R2 = (s32) le32_to_cpu(il->card_alive_init.therm_r2[1]); + R3 = (s32) le32_to_cpu(il->card_alive_init.therm_r3[1]); + R4 = le32_to_cpu(il->card_alive_init.therm_r4[1]); + } else { + D_TEMP("Running temperature calibration\n"); + R1 = (s32) le32_to_cpu(il->card_alive_init.therm_r1[0]); + R2 = (s32) le32_to_cpu(il->card_alive_init.therm_r2[0]); + R3 = (s32) le32_to_cpu(il->card_alive_init.therm_r3[0]); + R4 = le32_to_cpu(il->card_alive_init.therm_r4[0]); + } + + /* + * Temperature is only 23 bits, so sign extend out to 32. + * + * NOTE If we haven't received a stats notification yet + * with an updated temperature, use R4 provided to us in the + * "initialize" ALIVE response. + */ + if (!test_bit(S_TEMPERATURE, &il->status)) + vt = sign_extend32(R4, 23); + else + vt = sign_extend32(le32_to_cpu + (il->_4965.stats.general.common.temperature), + 23); + + D_TEMP("Calib values R[1-3]: %d %d %d R4: %d\n", R1, R2, R3, vt); + + if (R3 == R1) { + IL_ERR("Calibration conflict R1 == R3\n"); + return -1; + } + + /* Calculate temperature in degrees Kelvin, adjust by 97%. + * Add offset to center the adjustment around 0 degrees Centigrade. */ + temperature = TEMPERATURE_CALIB_A_VAL * (vt - R2); + temperature /= (R3 - R1); + temperature = + (temperature * 97) / 100 + TEMPERATURE_CALIB_KELVIN_OFFSET; + + D_TEMP("Calibrated temperature: %dK, %dC\n", temperature, + KELVIN_TO_CELSIUS(temperature)); + + return temperature; +} + +/* Adjust Txpower only if temperature variance is greater than threshold. */ +#define IL_TEMPERATURE_THRESHOLD 3 + +/** + * il4965_is_temp_calib_needed - determines if new calibration is needed + * + * If the temperature changed has changed sufficiently, then a recalibration + * is needed. + * + * Assumes caller will replace il->last_temperature once calibration + * executed. + */ +static int +il4965_is_temp_calib_needed(struct il_priv *il) +{ + int temp_diff; + + if (!test_bit(S_STATS, &il->status)) { + D_TEMP("Temperature not updated -- no stats.\n"); + return 0; + } + + temp_diff = il->temperature - il->last_temperature; + + /* get absolute value */ + if (temp_diff < 0) { + D_POWER("Getting cooler, delta %d\n", temp_diff); + temp_diff = -temp_diff; + } else if (temp_diff == 0) + D_POWER("Temperature unchanged\n"); + else + D_POWER("Getting warmer, delta %d\n", temp_diff); + + if (temp_diff < IL_TEMPERATURE_THRESHOLD) { + D_POWER(" => thermal txpower calib not needed\n"); + return 0; + } + + D_POWER(" => thermal txpower calib needed\n"); + + return 1; +} + +void +il4965_temperature_calib(struct il_priv *il) +{ + s32 temp; + + temp = il4965_hw_get_temperature(il); + if (IL_TX_POWER_TEMPERATURE_OUT_OF_RANGE(temp)) + return; + + if (il->temperature != temp) { + if (il->temperature) + D_TEMP("Temperature changed " "from %dC to %dC\n", + KELVIN_TO_CELSIUS(il->temperature), + KELVIN_TO_CELSIUS(temp)); + else + D_TEMP("Temperature " "initialized to %dC\n", + KELVIN_TO_CELSIUS(temp)); + } + + il->temperature = temp; + set_bit(S_TEMPERATURE, &il->status); + + if (!il->disable_tx_power_cal && + unlikely(!test_bit(S_SCANNING, &il->status)) && + il4965_is_temp_calib_needed(il)) + queue_work(il->workqueue, &il->txpower_work); +} + +static u16 +il4965_get_hcmd_size(u8 cmd_id, u16 len) +{ + switch (cmd_id) { + case C_RXON: + return (u16) sizeof(struct il4965_rxon_cmd); + default: + return len; + } +} + +static u16 +il4965_build_addsta_hcmd(const struct il_addsta_cmd *cmd, u8 * data) +{ + struct il4965_addsta_cmd *addsta = (struct il4965_addsta_cmd *)data; + addsta->mode = cmd->mode; + memcpy(&addsta->sta, &cmd->sta, sizeof(struct sta_id_modify)); + memcpy(&addsta->key, &cmd->key, sizeof(struct il4965_keyinfo)); + addsta->station_flags = cmd->station_flags; + addsta->station_flags_msk = cmd->station_flags_msk; + addsta->tid_disable_tx = cmd->tid_disable_tx; + addsta->add_immediate_ba_tid = cmd->add_immediate_ba_tid; + addsta->remove_immediate_ba_tid = cmd->remove_immediate_ba_tid; + addsta->add_immediate_ba_ssn = cmd->add_immediate_ba_ssn; + addsta->sleep_tx_count = cmd->sleep_tx_count; + addsta->reserved1 = cpu_to_le16(0); + addsta->reserved2 = cpu_to_le16(0); + + return (u16) sizeof(struct il4965_addsta_cmd); +} + +static void +il4965_post_scan(struct il_priv *il) +{ + /* + * Since setting the RXON may have been deferred while + * performing the scan, fire one off if needed + */ + if (memcmp(&il->staging, &il->active, sizeof(il->staging))) + il_commit_rxon(il); +} + +static void +il4965_post_associate(struct il_priv *il) +{ + struct ieee80211_vif *vif = il->vif; + int ret = 0; + + if (!vif || !il->is_open) + return; + + if (test_bit(S_EXIT_PENDING, &il->status)) + return; + + il_scan_cancel_timeout(il, 200); + + il->staging.filter_flags &= ~RXON_FILTER_ASSOC_MSK; + il_commit_rxon(il); + + ret = il_send_rxon_timing(il); + if (ret) + IL_WARN("RXON timing - " "Attempting to continue.\n"); + + il->staging.filter_flags |= RXON_FILTER_ASSOC_MSK; + + il_set_rxon_ht(il, &il->current_ht_config); + + if (il->ops->set_rxon_chain) + il->ops->set_rxon_chain(il); + + il->staging.assoc_id = cpu_to_le16(vif->bss_conf.aid); + + D_ASSOC("assoc id %d beacon interval %d\n", vif->bss_conf.aid, + vif->bss_conf.beacon_int); + + if (vif->bss_conf.use_short_preamble) + il->staging.flags |= RXON_FLG_SHORT_PREAMBLE_MSK; + else + il->staging.flags &= ~RXON_FLG_SHORT_PREAMBLE_MSK; + + if (il->staging.flags & RXON_FLG_BAND_24G_MSK) { + if (vif->bss_conf.use_short_slot) + il->staging.flags |= RXON_FLG_SHORT_SLOT_MSK; + else + il->staging.flags &= ~RXON_FLG_SHORT_SLOT_MSK; + } + + il_commit_rxon(il); + + D_ASSOC("Associated as %d to: %pM\n", vif->bss_conf.aid, + il->active.bssid_addr); + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + break; + case NL80211_IFTYPE_ADHOC: + il4965_send_beacon_cmd(il); + break; + default: + IL_ERR("%s Should not be called in %d mode\n", __func__, + vif->type); + break; + } + + /* the chain noise calibration will enabled PM upon completion + * If chain noise has already been run, then we need to enable + * power management here */ + if (il->chain_noise_data.state == IL_CHAIN_NOISE_DONE) + il_power_update_mode(il, false); + + /* Enable Rx differential gain and sensitivity calibrations */ + il4965_chain_noise_reset(il); + il->start_calib = 1; +} + +static void +il4965_config_ap(struct il_priv *il) +{ + struct ieee80211_vif *vif = il->vif; + int ret = 0; + + lockdep_assert_held(&il->mutex); + + if (test_bit(S_EXIT_PENDING, &il->status)) + return; + + /* The following should be done only at AP bring up */ + if (!il_is_associated(il)) { + + /* RXON - unassoc (to set timing command) */ + il->staging.filter_flags &= ~RXON_FILTER_ASSOC_MSK; + il_commit_rxon(il); + + /* RXON Timing */ + ret = il_send_rxon_timing(il); + if (ret) + IL_WARN("RXON timing failed - " + "Attempting to continue.\n"); + + /* AP has all antennas */ + il->chain_noise_data.active_chains = il->hw_params.valid_rx_ant; + il_set_rxon_ht(il, &il->current_ht_config); + if (il->ops->set_rxon_chain) + il->ops->set_rxon_chain(il); + + il->staging.assoc_id = 0; + + if (vif->bss_conf.use_short_preamble) + il->staging.flags |= RXON_FLG_SHORT_PREAMBLE_MSK; + else + il->staging.flags &= ~RXON_FLG_SHORT_PREAMBLE_MSK; + + if (il->staging.flags & RXON_FLG_BAND_24G_MSK) { + if (vif->bss_conf.use_short_slot) + il->staging.flags |= RXON_FLG_SHORT_SLOT_MSK; + else + il->staging.flags &= ~RXON_FLG_SHORT_SLOT_MSK; + } + /* need to send beacon cmd before committing assoc RXON! */ + il4965_send_beacon_cmd(il); + /* restore RXON assoc */ + il->staging.filter_flags |= RXON_FILTER_ASSOC_MSK; + il_commit_rxon(il); + } + il4965_send_beacon_cmd(il); +} + +const struct il_ops il4965_ops = { + .txq_update_byte_cnt_tbl = il4965_txq_update_byte_cnt_tbl, + .txq_attach_buf_to_tfd = il4965_hw_txq_attach_buf_to_tfd, + .txq_free_tfd = il4965_hw_txq_free_tfd, + .txq_init = il4965_hw_tx_queue_init, + .is_valid_rtc_data_addr = il4965_hw_valid_rtc_data_addr, + .init_alive_start = il4965_init_alive_start, + .load_ucode = il4965_load_bsm, + .dump_nic_error_log = il4965_dump_nic_error_log, + .dump_fh = il4965_dump_fh, + .set_channel_switch = il4965_hw_channel_switch, + .apm_init = il_apm_init, + .send_tx_power = il4965_send_tx_power, + .update_chain_flags = il4965_update_chain_flags, + .eeprom_acquire_semaphore = il4965_eeprom_acquire_semaphore, + .eeprom_release_semaphore = il4965_eeprom_release_semaphore, + + .rxon_assoc = il4965_send_rxon_assoc, + .commit_rxon = il4965_commit_rxon, + .set_rxon_chain = il4965_set_rxon_chain, + + .get_hcmd_size = il4965_get_hcmd_size, + .build_addsta_hcmd = il4965_build_addsta_hcmd, + .request_scan = il4965_request_scan, + .post_scan = il4965_post_scan, + + .post_associate = il4965_post_associate, + .config_ap = il4965_config_ap, + .manage_ibss_station = il4965_manage_ibss_station, + .update_bcast_stations = il4965_update_bcast_stations, + + .send_led_cmd = il4965_send_led_cmd, +}; + +struct il_cfg il4965_cfg = { + .name = "Intel(R) Wireless WiFi Link 4965AGN", + .fw_name_pre = IL4965_FW_PRE, + .ucode_api_max = IL4965_UCODE_API_MAX, + .ucode_api_min = IL4965_UCODE_API_MIN, + .sku = IL_SKU_A | IL_SKU_G | IL_SKU_N, + .valid_tx_ant = ANT_AB, + .valid_rx_ant = ANT_ABC, + .eeprom_ver = EEPROM_4965_EEPROM_VERSION, + .eeprom_calib_ver = EEPROM_4965_TX_POWER_VERSION, + .mod_params = &il4965_mod_params, + .led_mode = IL_LED_BLINK, + /* + * Force use of chains B and C for scan RX on 5 GHz band + * because the device has off-channel reception on chain A. + */ + .scan_rx_antennas[IEEE80211_BAND_5GHZ] = ANT_BC, + + .eeprom_size = IL4965_EEPROM_IMG_SIZE, + .num_of_queues = IL49_NUM_QUEUES, + .num_of_ampdu_queues = IL49_NUM_AMPDU_QUEUES, + .pll_cfg_val = 0, + .set_l0s = true, + .use_bsm = true, + .led_compensation = 61, + .chain_noise_num_beacons = IL4965_CAL_NUM_BEACONS, + .wd_timeout = IL_DEF_WD_TIMEOUT, + .temperature_kelvin = true, + .ucode_tracing = true, + .sensitivity_calib_by_driver = true, + .chain_noise_calib_by_driver = true, + + .regulatory_bands = { + EEPROM_REGULATORY_BAND_1_CHANNELS, + EEPROM_REGULATORY_BAND_2_CHANNELS, + EEPROM_REGULATORY_BAND_3_CHANNELS, + EEPROM_REGULATORY_BAND_4_CHANNELS, + EEPROM_REGULATORY_BAND_5_CHANNELS, + EEPROM_4965_REGULATORY_BAND_24_HT40_CHANNELS, + EEPROM_4965_REGULATORY_BAND_52_HT40_CHANNELS + }, + +}; + +/* Module firmware */ +MODULE_FIRMWARE(IL4965_MODULE_FIRMWARE(IL4965_UCODE_API_MAX)); diff --git a/drivers/net/wireless/iwlegacy/4965.h b/drivers/net/wireless/iwlegacy/4965.h new file mode 100644 index 0000000..1f3ca17 --- /dev/null +++ b/drivers/net/wireless/iwlegacy/4965.h @@ -0,0 +1,1284 @@ +/****************************************************************************** + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#ifndef __il_4965_h__ +#define __il_4965_h__ + +struct il_rx_queue; +struct il_rx_buf; +struct il_rx_pkt; +struct il_tx_queue; +struct il_rxon_context; + +/* configuration for the _4965 devices */ +extern struct il_cfg il4965_cfg; +extern const struct il_ops il4965_ops; + +extern struct il_mod_params il4965_mod_params; + +/* tx queue */ +void il4965_free_tfds_in_queue(struct il_priv *il, int sta_id, int tid, + int freed); + +/* RXON */ +void il4965_set_rxon_chain(struct il_priv *il); + +/* uCode */ +int il4965_verify_ucode(struct il_priv *il); + +/* lib */ +void il4965_check_abort_status(struct il_priv *il, u8 frame_count, u32 status); + +void il4965_rx_queue_reset(struct il_priv *il, struct il_rx_queue *rxq); +int il4965_rx_init(struct il_priv *il, struct il_rx_queue *rxq); +int il4965_hw_nic_init(struct il_priv *il); +int il4965_dump_fh(struct il_priv *il, char **buf, bool display); + +void il4965_nic_config(struct il_priv *il); + +/* rx */ +void il4965_rx_queue_restock(struct il_priv *il); +void il4965_rx_replenish(struct il_priv *il); +void il4965_rx_replenish_now(struct il_priv *il); +void il4965_rx_queue_free(struct il_priv *il, struct il_rx_queue *rxq); +int il4965_rxq_stop(struct il_priv *il); +int il4965_hwrate_to_mac80211_idx(u32 rate_n_flags, enum ieee80211_band band); +void il4965_rx_handle(struct il_priv *il); + +/* tx */ +void il4965_hw_txq_free_tfd(struct il_priv *il, struct il_tx_queue *txq); +int il4965_hw_txq_attach_buf_to_tfd(struct il_priv *il, struct il_tx_queue *txq, + dma_addr_t addr, u16 len, u8 reset, u8 pad); +int il4965_hw_tx_queue_init(struct il_priv *il, struct il_tx_queue *txq); +void il4965_hwrate_to_tx_control(struct il_priv *il, u32 rate_n_flags, + struct ieee80211_tx_info *info); +int il4965_tx_skb(struct il_priv *il, + struct ieee80211_sta *sta, + struct sk_buff *skb); +int il4965_tx_agg_start(struct il_priv *il, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 * ssn); +int il4965_tx_agg_stop(struct il_priv *il, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid); +int il4965_txq_check_empty(struct il_priv *il, int sta_id, u8 tid, int txq_id); +int il4965_tx_queue_reclaim(struct il_priv *il, int txq_id, int idx); +void il4965_hw_txq_ctx_free(struct il_priv *il); +int il4965_txq_ctx_alloc(struct il_priv *il); +void il4965_txq_ctx_reset(struct il_priv *il); +void il4965_txq_ctx_stop(struct il_priv *il); +void il4965_txq_set_sched(struct il_priv *il, u32 mask); + +/* + * Acquire il->lock before calling this function ! + */ +void il4965_set_wr_ptrs(struct il_priv *il, int txq_id, u32 idx); +/** + * il4965_tx_queue_set_status - (optionally) start Tx/Cmd queue + * @tx_fifo_id: Tx DMA/FIFO channel (range 0-7) that the queue will feed + * @scd_retry: (1) Indicates queue will be used in aggregation mode + * + * NOTE: Acquire il->lock before calling this function ! + */ +void il4965_tx_queue_set_status(struct il_priv *il, struct il_tx_queue *txq, + int tx_fifo_id, int scd_retry); + +/* scan */ +int il4965_request_scan(struct il_priv *il, struct ieee80211_vif *vif); + +/* station mgmt */ +int il4965_manage_ibss_station(struct il_priv *il, struct ieee80211_vif *vif, + bool add); + +/* hcmd */ +int il4965_send_beacon_cmd(struct il_priv *il); + +#ifdef CPTCFG_IWLEGACY_DEBUG +const char *il4965_get_tx_fail_reason(u32 status); +#else +static inline const char * +il4965_get_tx_fail_reason(u32 status) +{ + return ""; +} +#endif + +/* station management */ +int il4965_alloc_bcast_station(struct il_priv *il); +int il4965_add_bssid_station(struct il_priv *il, const u8 *addr, u8 *sta_id_r); +int il4965_remove_default_wep_key(struct il_priv *il, + struct ieee80211_key_conf *key); +int il4965_set_default_wep_key(struct il_priv *il, + struct ieee80211_key_conf *key); +int il4965_restore_default_wep_keys(struct il_priv *il); +int il4965_set_dynamic_key(struct il_priv *il, + struct ieee80211_key_conf *key, u8 sta_id); +int il4965_remove_dynamic_key(struct il_priv *il, + struct ieee80211_key_conf *key, u8 sta_id); +void il4965_update_tkip_key(struct il_priv *il, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, u32 iv32, + u16 *phase1key); +int il4965_sta_tx_modify_enable_tid(struct il_priv *il, int sta_id, int tid); +int il4965_sta_rx_agg_start(struct il_priv *il, struct ieee80211_sta *sta, + int tid, u16 ssn); +int il4965_sta_rx_agg_stop(struct il_priv *il, struct ieee80211_sta *sta, + int tid); +void il4965_sta_modify_sleep_tx_count(struct il_priv *il, int sta_id, int cnt); +int il4965_update_bcast_stations(struct il_priv *il); + +/* rate */ +static inline u8 +il4965_hw_get_rate(__le32 rate_n_flags) +{ + return le32_to_cpu(rate_n_flags) & 0xFF; +} + +/* eeprom */ +void il4965_eeprom_get_mac(const struct il_priv *il, u8 * mac); +int il4965_eeprom_acquire_semaphore(struct il_priv *il); +void il4965_eeprom_release_semaphore(struct il_priv *il); +int il4965_eeprom_check_version(struct il_priv *il); + +/* mac80211 handlers (for 4965) */ +void il4965_mac_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb); +int il4965_mac_start(struct ieee80211_hw *hw); +void il4965_mac_stop(struct ieee80211_hw *hw); +void il4965_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, u64 multicast); +int il4965_mac_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ieee80211_key_conf *key); +void il4965_mac_update_tkip_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, u32 iv32, + u16 *phase1key); +int il4965_mac_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, u16 * ssn, + u8 buf_size); +int il4965_mac_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +void il4965_mac_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_channel_switch *ch_switch); + +void il4965_led_enable(struct il_priv *il); + +/* EEPROM */ +#define IL4965_EEPROM_IMG_SIZE 1024 + +/* + * uCode queue management definitions ... + * The first queue used for block-ack aggregation is #7 (4965 only). + * All block-ack aggregation queues should map to Tx DMA/FIFO channel 7. + */ +#define IL49_FIRST_AMPDU_QUEUE 7 + +/* Sizes and addresses for instruction and data memory (SRAM) in + * 4965's embedded processor. Driver access is via HBUS_TARG_MEM_* regs. */ +#define IL49_RTC_INST_LOWER_BOUND (0x000000) +#define IL49_RTC_INST_UPPER_BOUND (0x018000) + +#define IL49_RTC_DATA_LOWER_BOUND (0x800000) +#define IL49_RTC_DATA_UPPER_BOUND (0x80A000) + +#define IL49_RTC_INST_SIZE (IL49_RTC_INST_UPPER_BOUND - \ + IL49_RTC_INST_LOWER_BOUND) +#define IL49_RTC_DATA_SIZE (IL49_RTC_DATA_UPPER_BOUND - \ + IL49_RTC_DATA_LOWER_BOUND) + +#define IL49_MAX_INST_SIZE IL49_RTC_INST_SIZE +#define IL49_MAX_DATA_SIZE IL49_RTC_DATA_SIZE + +/* Size of uCode instruction memory in bootstrap state machine */ +#define IL49_MAX_BSM_SIZE BSM_SRAM_SIZE + +static inline int +il4965_hw_valid_rtc_data_addr(u32 addr) +{ + return (addr >= IL49_RTC_DATA_LOWER_BOUND && + addr < IL49_RTC_DATA_UPPER_BOUND); +} + +/********************* START TEMPERATURE *************************************/ + +/** + * 4965 temperature calculation. + * + * The driver must calculate the device temperature before calculating + * a txpower setting (amplifier gain is temperature dependent). The + * calculation uses 4 measurements, 3 of which (R1, R2, R3) are calibration + * values used for the life of the driver, and one of which (R4) is the + * real-time temperature indicator. + * + * uCode provides all 4 values to the driver via the "initialize alive" + * notification (see struct il4965_init_alive_resp). After the runtime uCode + * image loads, uCode updates the R4 value via stats notifications + * (see N_STATS), which occur after each received beacon + * when associated, or can be requested via C_STATS. + * + * NOTE: uCode provides the R4 value as a 23-bit signed value. Driver + * must sign-extend to 32 bits before applying formula below. + * + * Formula: + * + * degrees Kelvin = ((97 * 259 * (R4 - R2) / (R3 - R1)) / 100) + 8 + * + * NOTE: The basic formula is 259 * (R4-R2) / (R3-R1). The 97/100 is + * an additional correction, which should be centered around 0 degrees + * Celsius (273 degrees Kelvin). The 8 (3 percent of 273) compensates for + * centering the 97/100 correction around 0 degrees K. + * + * Add 273 to Kelvin value to find degrees Celsius, for comparing current + * temperature with factory-measured temperatures when calculating txpower + * settings. + */ +#define TEMPERATURE_CALIB_KELVIN_OFFSET 8 +#define TEMPERATURE_CALIB_A_VAL 259 + +/* Limit range of calculated temperature to be between these Kelvin values */ +#define IL_TX_POWER_TEMPERATURE_MIN (263) +#define IL_TX_POWER_TEMPERATURE_MAX (410) + +#define IL_TX_POWER_TEMPERATURE_OUT_OF_RANGE(t) \ + ((t) < IL_TX_POWER_TEMPERATURE_MIN || \ + (t) > IL_TX_POWER_TEMPERATURE_MAX) + +extern void il4965_temperature_calib(struct il_priv *il); +/********************* END TEMPERATURE ***************************************/ + +/********************* START TXPOWER *****************************************/ + +/** + * 4965 txpower calculations rely on information from three sources: + * + * 1) EEPROM + * 2) "initialize" alive notification + * 3) stats notifications + * + * EEPROM data consists of: + * + * 1) Regulatory information (max txpower and channel usage flags) is provided + * separately for each channel that can possibly supported by 4965. + * 40 MHz wide (.11n HT40) channels are listed separately from 20 MHz + * (legacy) channels. + * + * See struct il4965_eeprom_channel for format, and struct il4965_eeprom + * for locations in EEPROM. + * + * 2) Factory txpower calibration information is provided separately for + * sub-bands of contiguous channels. 2.4GHz has just one sub-band, + * but 5 GHz has several sub-bands. + * + * In addition, per-band (2.4 and 5 Ghz) saturation txpowers are provided. + * + * See struct il4965_eeprom_calib_info (and the tree of structures + * contained within it) for format, and struct il4965_eeprom for + * locations in EEPROM. + * + * "Initialization alive" notification (see struct il4965_init_alive_resp) + * consists of: + * + * 1) Temperature calculation parameters. + * + * 2) Power supply voltage measurement. + * + * 3) Tx gain compensation to balance 2 transmitters for MIMO use. + * + * Statistics notifications deliver: + * + * 1) Current values for temperature param R4. + */ + +/** + * To calculate a txpower setting for a given desired target txpower, channel, + * modulation bit rate, and transmitter chain (4965 has 2 transmitters to + * support MIMO and transmit diversity), driver must do the following: + * + * 1) Compare desired txpower vs. (EEPROM) regulatory limit for this channel. + * Do not exceed regulatory limit; reduce target txpower if necessary. + * + * If setting up txpowers for MIMO rates (rate idxes 8-15, 24-31), + * 2 transmitters will be used simultaneously; driver must reduce the + * regulatory limit by 3 dB (half-power) for each transmitter, so the + * combined total output of the 2 transmitters is within regulatory limits. + * + * + * 2) Compare target txpower vs. (EEPROM) saturation txpower *reduced by + * backoff for this bit rate*. Do not exceed (saturation - backoff[rate]); + * reduce target txpower if necessary. + * + * Backoff values below are in 1/2 dB units (equivalent to steps in + * txpower gain tables): + * + * OFDM 6 - 36 MBit: 10 steps (5 dB) + * OFDM 48 MBit: 15 steps (7.5 dB) + * OFDM 54 MBit: 17 steps (8.5 dB) + * OFDM 60 MBit: 20 steps (10 dB) + * CCK all rates: 10 steps (5 dB) + * + * Backoff values apply to saturation txpower on a per-transmitter basis; + * when using MIMO (2 transmitters), each transmitter uses the same + * saturation level provided in EEPROM, and the same backoff values; + * no reduction (such as with regulatory txpower limits) is required. + * + * Saturation and Backoff values apply equally to 20 Mhz (legacy) channel + * widths and 40 Mhz (.11n HT40) channel widths; there is no separate + * factory measurement for ht40 channels. + * + * The result of this step is the final target txpower. The rest of + * the steps figure out the proper settings for the device to achieve + * that target txpower. + * + * + * 3) Determine (EEPROM) calibration sub band for the target channel, by + * comparing against first and last channels in each sub band + * (see struct il4965_eeprom_calib_subband_info). + * + * + * 4) Linearly interpolate (EEPROM) factory calibration measurement sets, + * referencing the 2 factory-measured (sample) channels within the sub band. + * + * Interpolation is based on difference between target channel's frequency + * and the sample channels' frequencies. Since channel numbers are based + * on frequency (5 MHz between each channel number), this is equivalent + * to interpolating based on channel number differences. + * + * Note that the sample channels may or may not be the channels at the + * edges of the sub band. The target channel may be "outside" of the + * span of the sampled channels. + * + * Driver may choose the pair (for 2 Tx chains) of measurements (see + * struct il4965_eeprom_calib_ch_info) for which the actual measured + * txpower comes closest to the desired txpower. Usually, though, + * the middle set of measurements is closest to the regulatory limits, + * and is therefore a good choice for all txpower calculations (this + * assumes that high accuracy is needed for maximizing legal txpower, + * while lower txpower configurations do not need as much accuracy). + * + * Driver should interpolate both members of the chosen measurement pair, + * i.e. for both Tx chains (radio transmitters), unless the driver knows + * that only one of the chains will be used (e.g. only one tx antenna + * connected, but this should be unusual). The rate scaling algorithm + * switches antennas to find best performance, so both Tx chains will + * be used (although only one at a time) even for non-MIMO transmissions. + * + * Driver should interpolate factory values for temperature, gain table + * idx, and actual power. The power amplifier detector values are + * not used by the driver. + * + * Sanity check: If the target channel happens to be one of the sample + * channels, the results should agree with the sample channel's + * measurements! + * + * + * 5) Find difference between desired txpower and (interpolated) + * factory-measured txpower. Using (interpolated) factory gain table idx + * (shown elsewhere) as a starting point, adjust this idx lower to + * increase txpower, or higher to decrease txpower, until the target + * txpower is reached. Each step in the gain table is 1/2 dB. + * + * For example, if factory measured txpower is 16 dBm, and target txpower + * is 13 dBm, add 6 steps to the factory gain idx to reduce txpower + * by 3 dB. + * + * + * 6) Find difference between current device temperature and (interpolated) + * factory-measured temperature for sub-band. Factory values are in + * degrees Celsius. To calculate current temperature, see comments for + * "4965 temperature calculation". + * + * If current temperature is higher than factory temperature, driver must + * increase gain (lower gain table idx), and vice verse. + * + * Temperature affects gain differently for different channels: + * + * 2.4 GHz all channels: 3.5 degrees per half-dB step + * 5 GHz channels 34-43: 4.5 degrees per half-dB step + * 5 GHz channels >= 44: 4.0 degrees per half-dB step + * + * NOTE: Temperature can increase rapidly when transmitting, especially + * with heavy traffic at high txpowers. Driver should update + * temperature calculations often under these conditions to + * maintain strong txpower in the face of rising temperature. + * + * + * 7) Find difference between current power supply voltage indicator + * (from "initialize alive") and factory-measured power supply voltage + * indicator (EEPROM). + * + * If the current voltage is higher (indicator is lower) than factory + * voltage, gain should be reduced (gain table idx increased) by: + * + * (eeprom - current) / 7 + * + * If the current voltage is lower (indicator is higher) than factory + * voltage, gain should be increased (gain table idx decreased) by: + * + * 2 * (current - eeprom) / 7 + * + * If number of idx steps in either direction turns out to be > 2, + * something is wrong ... just use 0. + * + * NOTE: Voltage compensation is independent of band/channel. + * + * NOTE: "Initialize" uCode measures current voltage, which is assumed + * to be constant after this initial measurement. Voltage + * compensation for txpower (number of steps in gain table) + * may be calculated once and used until the next uCode bootload. + * + * + * 8) If setting up txpowers for MIMO rates (rate idxes 8-15, 24-31), + * adjust txpower for each transmitter chain, so txpower is balanced + * between the two chains. There are 5 pairs of tx_atten[group][chain] + * values in "initialize alive", one pair for each of 5 channel ranges: + * + * Group 0: 5 GHz channel 34-43 + * Group 1: 5 GHz channel 44-70 + * Group 2: 5 GHz channel 71-124 + * Group 3: 5 GHz channel 125-200 + * Group 4: 2.4 GHz all channels + * + * Add the tx_atten[group][chain] value to the idx for the target chain. + * The values are signed, but are in pairs of 0 and a non-negative number, + * so as to reduce gain (if necessary) of the "hotter" channel. This + * avoids any need to double-check for regulatory compliance after + * this step. + * + * + * 9) If setting up for a CCK rate, lower the gain by adding a CCK compensation + * value to the idx: + * + * Hardware rev B: 9 steps (4.5 dB) + * Hardware rev C: 5 steps (2.5 dB) + * + * Hardware rev for 4965 can be determined by reading CSR_HW_REV_WA_REG, + * bits [3:2], 1 = B, 2 = C. + * + * NOTE: This compensation is in addition to any saturation backoff that + * might have been applied in an earlier step. + * + * + * 10) Select the gain table, based on band (2.4 vs 5 GHz). + * + * Limit the adjusted idx to stay within the table! + * + * + * 11) Read gain table entries for DSP and radio gain, place into appropriate + * location(s) in command (struct il4965_txpowertable_cmd). + */ + +/** + * When MIMO is used (2 transmitters operating simultaneously), driver should + * limit each transmitter to deliver a max of 3 dB below the regulatory limit + * for the device. That is, use half power for each transmitter, so total + * txpower is within regulatory limits. + * + * The value "6" represents number of steps in gain table to reduce power 3 dB. + * Each step is 1/2 dB. + */ +#define IL_TX_POWER_MIMO_REGULATORY_COMPENSATION (6) + +/** + * CCK gain compensation. + * + * When calculating txpowers for CCK, after making sure that the target power + * is within regulatory and saturation limits, driver must additionally + * back off gain by adding these values to the gain table idx. + * + * Hardware rev for 4965 can be determined by reading CSR_HW_REV_WA_REG, + * bits [3:2], 1 = B, 2 = C. + */ +#define IL_TX_POWER_CCK_COMPENSATION_B_STEP (9) +#define IL_TX_POWER_CCK_COMPENSATION_C_STEP (5) + +/* + * 4965 power supply voltage compensation for txpower + */ +#define TX_POWER_IL_VOLTAGE_CODES_PER_03V (7) + +/** + * Gain tables. + * + * The following tables contain pair of values for setting txpower, i.e. + * gain settings for the output of the device's digital signal processor (DSP), + * and for the analog gain structure of the transmitter. + * + * Each entry in the gain tables represents a step of 1/2 dB. Note that these + * are *relative* steps, not indications of absolute output power. Output + * power varies with temperature, voltage, and channel frequency, and also + * requires consideration of average power (to satisfy regulatory constraints), + * and peak power (to avoid distortion of the output signal). + * + * Each entry contains two values: + * 1) DSP gain (or sometimes called DSP attenuation). This is a fine-grained + * linear value that multiplies the output of the digital signal processor, + * before being sent to the analog radio. + * 2) Radio gain. This sets the analog gain of the radio Tx path. + * It is a coarser setting, and behaves in a logarithmic (dB) fashion. + * + * EEPROM contains factory calibration data for txpower. This maps actual + * measured txpower levels to gain settings in the "well known" tables + * below ("well-known" means here that both factory calibration *and* the + * driver work with the same table). + * + * There are separate tables for 2.4 GHz and 5 GHz bands. The 5 GHz table + * has an extension (into negative idxes), in case the driver needs to + * boost power setting for high device temperatures (higher than would be + * present during factory calibration). A 5 Ghz EEPROM idx of "40" + * corresponds to the 49th entry in the table used by the driver. + */ +#define MIN_TX_GAIN_IDX (0) /* highest gain, lowest idx, 2.4 */ +#define MIN_TX_GAIN_IDX_52GHZ_EXT (-9) /* highest gain, lowest idx, 5 */ + +/** + * 2.4 GHz gain table + * + * Index Dsp gain Radio gain + * 0 110 0x3f (highest gain) + * 1 104 0x3f + * 2 98 0x3f + * 3 110 0x3e + * 4 104 0x3e + * 5 98 0x3e + * 6 110 0x3d + * 7 104 0x3d + * 8 98 0x3d + * 9 110 0x3c + * 10 104 0x3c + * 11 98 0x3c + * 12 110 0x3b + * 13 104 0x3b + * 14 98 0x3b + * 15 110 0x3a + * 16 104 0x3a + * 17 98 0x3a + * 18 110 0x39 + * 19 104 0x39 + * 20 98 0x39 + * 21 110 0x38 + * 22 104 0x38 + * 23 98 0x38 + * 24 110 0x37 + * 25 104 0x37 + * 26 98 0x37 + * 27 110 0x36 + * 28 104 0x36 + * 29 98 0x36 + * 30 110 0x35 + * 31 104 0x35 + * 32 98 0x35 + * 33 110 0x34 + * 34 104 0x34 + * 35 98 0x34 + * 36 110 0x33 + * 37 104 0x33 + * 38 98 0x33 + * 39 110 0x32 + * 40 104 0x32 + * 41 98 0x32 + * 42 110 0x31 + * 43 104 0x31 + * 44 98 0x31 + * 45 110 0x30 + * 46 104 0x30 + * 47 98 0x30 + * 48 110 0x6 + * 49 104 0x6 + * 50 98 0x6 + * 51 110 0x5 + * 52 104 0x5 + * 53 98 0x5 + * 54 110 0x4 + * 55 104 0x4 + * 56 98 0x4 + * 57 110 0x3 + * 58 104 0x3 + * 59 98 0x3 + * 60 110 0x2 + * 61 104 0x2 + * 62 98 0x2 + * 63 110 0x1 + * 64 104 0x1 + * 65 98 0x1 + * 66 110 0x0 + * 67 104 0x0 + * 68 98 0x0 + * 69 97 0 + * 70 96 0 + * 71 95 0 + * 72 94 0 + * 73 93 0 + * 74 92 0 + * 75 91 0 + * 76 90 0 + * 77 89 0 + * 78 88 0 + * 79 87 0 + * 80 86 0 + * 81 85 0 + * 82 84 0 + * 83 83 0 + * 84 82 0 + * 85 81 0 + * 86 80 0 + * 87 79 0 + * 88 78 0 + * 89 77 0 + * 90 76 0 + * 91 75 0 + * 92 74 0 + * 93 73 0 + * 94 72 0 + * 95 71 0 + * 96 70 0 + * 97 69 0 + * 98 68 0 + */ + +/** + * 5 GHz gain table + * + * Index Dsp gain Radio gain + * -9 123 0x3F (highest gain) + * -8 117 0x3F + * -7 110 0x3F + * -6 104 0x3F + * -5 98 0x3F + * -4 110 0x3E + * -3 104 0x3E + * -2 98 0x3E + * -1 110 0x3D + * 0 104 0x3D + * 1 98 0x3D + * 2 110 0x3C + * 3 104 0x3C + * 4 98 0x3C + * 5 110 0x3B + * 6 104 0x3B + * 7 98 0x3B + * 8 110 0x3A + * 9 104 0x3A + * 10 98 0x3A + * 11 110 0x39 + * 12 104 0x39 + * 13 98 0x39 + * 14 110 0x38 + * 15 104 0x38 + * 16 98 0x38 + * 17 110 0x37 + * 18 104 0x37 + * 19 98 0x37 + * 20 110 0x36 + * 21 104 0x36 + * 22 98 0x36 + * 23 110 0x35 + * 24 104 0x35 + * 25 98 0x35 + * 26 110 0x34 + * 27 104 0x34 + * 28 98 0x34 + * 29 110 0x33 + * 30 104 0x33 + * 31 98 0x33 + * 32 110 0x32 + * 33 104 0x32 + * 34 98 0x32 + * 35 110 0x31 + * 36 104 0x31 + * 37 98 0x31 + * 38 110 0x30 + * 39 104 0x30 + * 40 98 0x30 + * 41 110 0x25 + * 42 104 0x25 + * 43 98 0x25 + * 44 110 0x24 + * 45 104 0x24 + * 46 98 0x24 + * 47 110 0x23 + * 48 104 0x23 + * 49 98 0x23 + * 50 110 0x22 + * 51 104 0x18 + * 52 98 0x18 + * 53 110 0x17 + * 54 104 0x17 + * 55 98 0x17 + * 56 110 0x16 + * 57 104 0x16 + * 58 98 0x16 + * 59 110 0x15 + * 60 104 0x15 + * 61 98 0x15 + * 62 110 0x14 + * 63 104 0x14 + * 64 98 0x14 + * 65 110 0x13 + * 66 104 0x13 + * 67 98 0x13 + * 68 110 0x12 + * 69 104 0x08 + * 70 98 0x08 + * 71 110 0x07 + * 72 104 0x07 + * 73 98 0x07 + * 74 110 0x06 + * 75 104 0x06 + * 76 98 0x06 + * 77 110 0x05 + * 78 104 0x05 + * 79 98 0x05 + * 80 110 0x04 + * 81 104 0x04 + * 82 98 0x04 + * 83 110 0x03 + * 84 104 0x03 + * 85 98 0x03 + * 86 110 0x02 + * 87 104 0x02 + * 88 98 0x02 + * 89 110 0x01 + * 90 104 0x01 + * 91 98 0x01 + * 92 110 0x00 + * 93 104 0x00 + * 94 98 0x00 + * 95 93 0x00 + * 96 88 0x00 + * 97 83 0x00 + * 98 78 0x00 + */ + +/** + * Sanity checks and default values for EEPROM regulatory levels. + * If EEPROM values fall outside MIN/MAX range, use default values. + * + * Regulatory limits refer to the maximum average txpower allowed by + * regulatory agencies in the geographies in which the device is meant + * to be operated. These limits are SKU-specific (i.e. geography-specific), + * and channel-specific; each channel has an individual regulatory limit + * listed in the EEPROM. + * + * Units are in half-dBm (i.e. "34" means 17 dBm). + */ +#define IL_TX_POWER_DEFAULT_REGULATORY_24 (34) +#define IL_TX_POWER_DEFAULT_REGULATORY_52 (34) +#define IL_TX_POWER_REGULATORY_MIN (0) +#define IL_TX_POWER_REGULATORY_MAX (34) + +/** + * Sanity checks and default values for EEPROM saturation levels. + * If EEPROM values fall outside MIN/MAX range, use default values. + * + * Saturation is the highest level that the output power amplifier can produce + * without significant clipping distortion. This is a "peak" power level. + * Different types of modulation (i.e. various "rates", and OFDM vs. CCK) + * require differing amounts of backoff, relative to their average power output, + * in order to avoid clipping distortion. + * + * Driver must make sure that it is violating neither the saturation limit, + * nor the regulatory limit, when calculating Tx power settings for various + * rates. + * + * Units are in half-dBm (i.e. "38" means 19 dBm). + */ +#define IL_TX_POWER_DEFAULT_SATURATION_24 (38) +#define IL_TX_POWER_DEFAULT_SATURATION_52 (38) +#define IL_TX_POWER_SATURATION_MIN (20) +#define IL_TX_POWER_SATURATION_MAX (50) + +/** + * Channel groups used for Tx Attenuation calibration (MIMO tx channel balance) + * and thermal Txpower calibration. + * + * When calculating txpower, driver must compensate for current device + * temperature; higher temperature requires higher gain. Driver must calculate + * current temperature (see "4965 temperature calculation"), then compare vs. + * factory calibration temperature in EEPROM; if current temperature is higher + * than factory temperature, driver must *increase* gain by proportions shown + * in table below. If current temperature is lower than factory, driver must + * *decrease* gain. + * + * Different frequency ranges require different compensation, as shown below. + */ +/* Group 0, 5.2 GHz ch 34-43: 4.5 degrees per 1/2 dB. */ +#define CALIB_IL_TX_ATTEN_GR1_FCH 34 +#define CALIB_IL_TX_ATTEN_GR1_LCH 43 + +/* Group 1, 5.3 GHz ch 44-70: 4.0 degrees per 1/2 dB. */ +#define CALIB_IL_TX_ATTEN_GR2_FCH 44 +#define CALIB_IL_TX_ATTEN_GR2_LCH 70 + +/* Group 2, 5.5 GHz ch 71-124: 4.0 degrees per 1/2 dB. */ +#define CALIB_IL_TX_ATTEN_GR3_FCH 71 +#define CALIB_IL_TX_ATTEN_GR3_LCH 124 + +/* Group 3, 5.7 GHz ch 125-200: 4.0 degrees per 1/2 dB. */ +#define CALIB_IL_TX_ATTEN_GR4_FCH 125 +#define CALIB_IL_TX_ATTEN_GR4_LCH 200 + +/* Group 4, 2.4 GHz all channels: 3.5 degrees per 1/2 dB. */ +#define CALIB_IL_TX_ATTEN_GR5_FCH 1 +#define CALIB_IL_TX_ATTEN_GR5_LCH 20 + +enum { + CALIB_CH_GROUP_1 = 0, + CALIB_CH_GROUP_2 = 1, + CALIB_CH_GROUP_3 = 2, + CALIB_CH_GROUP_4 = 3, + CALIB_CH_GROUP_5 = 4, + CALIB_CH_GROUP_MAX +}; + +/********************* END TXPOWER *****************************************/ + +/** + * Tx/Rx Queues + * + * Most communication between driver and 4965 is via queues of data buffers. + * For example, all commands that the driver issues to device's embedded + * controller (uCode) are via the command queue (one of the Tx queues). All + * uCode command responses/replies/notifications, including Rx frames, are + * conveyed from uCode to driver via the Rx queue. + * + * Most support for these queues, including handshake support, resides in + * structures in host DRAM, shared between the driver and the device. When + * allocating this memory, the driver must make sure that data written by + * the host CPU updates DRAM immediately (and does not get "stuck" in CPU's + * cache memory), so DRAM and cache are consistent, and the device can + * immediately see changes made by the driver. + * + * 4965 supports up to 16 DRAM-based Tx queues, and services these queues via + * up to 7 DMA channels (FIFOs). Each Tx queue is supported by a circular array + * in DRAM containing 256 Transmit Frame Descriptors (TFDs). + */ +#define IL49_NUM_FIFOS 7 +#define IL49_CMD_FIFO_NUM 4 +#define IL49_NUM_QUEUES 16 +#define IL49_NUM_AMPDU_QUEUES 8 + +/** + * struct il4965_schedq_bc_tbl + * + * Byte Count table + * + * Each Tx queue uses a byte-count table containing 320 entries: + * one 16-bit entry for each of 256 TFDs, plus an additional 64 entries that + * duplicate the first 64 entries (to avoid wrap-around within a Tx win; + * max Tx win is 64 TFDs). + * + * When driver sets up a new TFD, it must also enter the total byte count + * of the frame to be transmitted into the corresponding entry in the byte + * count table for the chosen Tx queue. If the TFD idx is 0-63, the driver + * must duplicate the byte count entry in corresponding idx 256-319. + * + * padding puts each byte count table on a 1024-byte boundary; + * 4965 assumes tables are separated by 1024 bytes. + */ +struct il4965_scd_bc_tbl { + __le16 tfd_offset[TFD_QUEUE_BC_SIZE]; + u8 pad[1024 - (TFD_QUEUE_BC_SIZE) * sizeof(__le16)]; +} __packed; + +#define IL4965_RTC_INST_LOWER_BOUND (0x000000) + +/* RSSI to dBm */ +#define IL4965_RSSI_OFFSET 44 + +/* PCI registers */ +#define PCI_CFG_RETRY_TIMEOUT 0x041 + +#define IL4965_DEFAULT_TX_RETRY 15 + +/* EEPROM */ +#define IL4965_FIRST_AMPDU_QUEUE 10 + +/* Calibration */ +void il4965_chain_noise_calibration(struct il_priv *il, void *stat_resp); +void il4965_sensitivity_calibration(struct il_priv *il, void *resp); +void il4965_init_sensitivity(struct il_priv *il); +void il4965_reset_run_time_calib(struct il_priv *il); + +/* Debug */ +#ifdef CPTCFG_IWLEGACY_DEBUGFS +extern const struct il_debugfs_ops il4965_debugfs_ops; +#endif + +/****************************/ +/* Flow Handler Definitions */ +/****************************/ + +/** + * This I/O area is directly read/writable by driver (e.g. Linux uses writel()) + * Addresses are offsets from device's PCI hardware base address. + */ +#define FH49_MEM_LOWER_BOUND (0x1000) +#define FH49_MEM_UPPER_BOUND (0x2000) + +/** + * Keep-Warm (KW) buffer base address. + * + * Driver must allocate a 4KByte buffer that is used by 4965 for keeping the + * host DRAM powered on (via dummy accesses to DRAM) to maintain low-latency + * DRAM access when 4965 is Txing or Rxing. The dummy accesses prevent host + * from going into a power-savings mode that would cause higher DRAM latency, + * and possible data over/under-runs, before all Tx/Rx is complete. + * + * Driver loads FH49_KW_MEM_ADDR_REG with the physical address (bits 35:4) + * of the buffer, which must be 4K aligned. Once this is set up, the 4965 + * automatically invokes keep-warm accesses when normal accesses might not + * be sufficient to maintain fast DRAM response. + * + * Bit fields: + * 31-0: Keep-warm buffer physical base address [35:4], must be 4K aligned + */ +#define FH49_KW_MEM_ADDR_REG (FH49_MEM_LOWER_BOUND + 0x97C) + +/** + * TFD Circular Buffers Base (CBBC) addresses + * + * 4965 has 16 base pointer registers, one for each of 16 host-DRAM-resident + * circular buffers (CBs/queues) containing Transmit Frame Descriptors (TFDs) + * (see struct il_tfd_frame). These 16 pointer registers are offset by 0x04 + * bytes from one another. Each TFD circular buffer in DRAM must be 256-byte + * aligned (address bits 0-7 must be 0). + * + * Bit fields in each pointer register: + * 27-0: TFD CB physical base address [35:8], must be 256-byte aligned + */ +#define FH49_MEM_CBBC_LOWER_BOUND (FH49_MEM_LOWER_BOUND + 0x9D0) +#define FH49_MEM_CBBC_UPPER_BOUND (FH49_MEM_LOWER_BOUND + 0xA10) + +/* Find TFD CB base pointer for given queue (range 0-15). */ +#define FH49_MEM_CBBC_QUEUE(x) (FH49_MEM_CBBC_LOWER_BOUND + (x) * 0x4) + +/** + * Rx SRAM Control and Status Registers (RSCSR) + * + * These registers provide handshake between driver and 4965 for the Rx queue + * (this queue handles *all* command responses, notifications, Rx data, etc. + * sent from 4965 uCode to host driver). Unlike Tx, there is only one Rx + * queue, and only one Rx DMA/FIFO channel. Also unlike Tx, which can + * concatenate up to 20 DRAM buffers to form a Tx frame, each Receive Buffer + * Descriptor (RBD) points to only one Rx Buffer (RB); there is a 1:1 + * mapping between RBDs and RBs. + * + * Driver must allocate host DRAM memory for the following, and set the + * physical address of each into 4965 registers: + * + * 1) Receive Buffer Descriptor (RBD) circular buffer (CB), typically with 256 + * entries (although any power of 2, up to 4096, is selectable by driver). + * Each entry (1 dword) points to a receive buffer (RB) of consistent size + * (typically 4K, although 8K or 16K are also selectable by driver). + * Driver sets up RB size and number of RBDs in the CB via Rx config + * register FH49_MEM_RCSR_CHNL0_CONFIG_REG. + * + * Bit fields within one RBD: + * 27-0: Receive Buffer physical address bits [35:8], 256-byte aligned + * + * Driver sets physical address [35:8] of base of RBD circular buffer + * into FH49_RSCSR_CHNL0_RBDCB_BASE_REG [27:0]. + * + * 2) Rx status buffer, 8 bytes, in which 4965 indicates which Rx Buffers + * (RBs) have been filled, via a "write pointer", actually the idx of + * the RB's corresponding RBD within the circular buffer. Driver sets + * physical address [35:4] into FH49_RSCSR_CHNL0_STTS_WPTR_REG [31:0]. + * + * Bit fields in lower dword of Rx status buffer (upper dword not used + * by driver; see struct il4965_shared, val0): + * 31-12: Not used by driver + * 11- 0: Index of last filled Rx buffer descriptor + * (4965 writes, driver reads this value) + * + * As the driver prepares Receive Buffers (RBs) for 4965 to fill, driver must + * enter pointers to these RBs into contiguous RBD circular buffer entries, + * and update the 4965's "write" idx register, + * FH49_RSCSR_CHNL0_RBDCB_WPTR_REG. + * + * This "write" idx corresponds to the *next* RBD that the driver will make + * available, i.e. one RBD past the tail of the ready-to-fill RBDs within + * the circular buffer. This value should initially be 0 (before preparing any + * RBs), should be 8 after preparing the first 8 RBs (for example), and must + * wrap back to 0 at the end of the circular buffer (but don't wrap before + * "read" idx has advanced past 1! See below). + * NOTE: 4965 EXPECTS THE WRITE IDX TO BE INCREMENTED IN MULTIPLES OF 8. + * + * As the 4965 fills RBs (referenced from contiguous RBDs within the circular + * buffer), it updates the Rx status buffer in host DRAM, 2) described above, + * to tell the driver the idx of the latest filled RBD. The driver must + * read this "read" idx from DRAM after receiving an Rx interrupt from 4965. + * + * The driver must also internally keep track of a third idx, which is the + * next RBD to process. When receiving an Rx interrupt, driver should process + * all filled but unprocessed RBs up to, but not including, the RB + * corresponding to the "read" idx. For example, if "read" idx becomes "1", + * driver may process the RB pointed to by RBD 0. Depending on volume of + * traffic, there may be many RBs to process. + * + * If read idx == write idx, 4965 thinks there is no room to put new data. + * Due to this, the maximum number of filled RBs is 255, instead of 256. To + * be safe, make sure that there is a gap of at least 2 RBDs between "write" + * and "read" idxes; that is, make sure that there are no more than 254 + * buffers waiting to be filled. + */ +#define FH49_MEM_RSCSR_LOWER_BOUND (FH49_MEM_LOWER_BOUND + 0xBC0) +#define FH49_MEM_RSCSR_UPPER_BOUND (FH49_MEM_LOWER_BOUND + 0xC00) +#define FH49_MEM_RSCSR_CHNL0 (FH49_MEM_RSCSR_LOWER_BOUND) + +/** + * Physical base address of 8-byte Rx Status buffer. + * Bit fields: + * 31-0: Rx status buffer physical base address [35:4], must 16-byte aligned. + */ +#define FH49_RSCSR_CHNL0_STTS_WPTR_REG (FH49_MEM_RSCSR_CHNL0) + +/** + * Physical base address of Rx Buffer Descriptor Circular Buffer. + * Bit fields: + * 27-0: RBD CD physical base address [35:8], must be 256-byte aligned. + */ +#define FH49_RSCSR_CHNL0_RBDCB_BASE_REG (FH49_MEM_RSCSR_CHNL0 + 0x004) + +/** + * Rx write pointer (idx, really!). + * Bit fields: + * 11-0: Index of driver's most recent prepared-to-be-filled RBD, + 1. + * NOTE: For 256-entry circular buffer, use only bits [7:0]. + */ +#define FH49_RSCSR_CHNL0_RBDCB_WPTR_REG (FH49_MEM_RSCSR_CHNL0 + 0x008) +#define FH49_RSCSR_CHNL0_WPTR (FH49_RSCSR_CHNL0_RBDCB_WPTR_REG) + +/** + * Rx Config/Status Registers (RCSR) + * Rx Config Reg for channel 0 (only channel used) + * + * Driver must initialize FH49_MEM_RCSR_CHNL0_CONFIG_REG as follows for + * normal operation (see bit fields). + * + * Clearing FH49_MEM_RCSR_CHNL0_CONFIG_REG to 0 turns off Rx DMA. + * Driver should poll FH49_MEM_RSSR_RX_STATUS_REG for + * FH49_RSSR_CHNL0_RX_STATUS_CHNL_IDLE (bit 24) before continuing. + * + * Bit fields: + * 31-30: Rx DMA channel enable: '00' off/pause, '01' pause at end of frame, + * '10' operate normally + * 29-24: reserved + * 23-20: # RBDs in circular buffer = 2^value; use "8" for 256 RBDs (normal), + * min "5" for 32 RBDs, max "12" for 4096 RBDs. + * 19-18: reserved + * 17-16: size of each receive buffer; '00' 4K (normal), '01' 8K, + * '10' 12K, '11' 16K. + * 15-14: reserved + * 13-12: IRQ destination; '00' none, '01' host driver (normal operation) + * 11- 4: timeout for closing Rx buffer and interrupting host (units 32 usec) + * typical value 0x10 (about 1/2 msec) + * 3- 0: reserved + */ +#define FH49_MEM_RCSR_LOWER_BOUND (FH49_MEM_LOWER_BOUND + 0xC00) +#define FH49_MEM_RCSR_UPPER_BOUND (FH49_MEM_LOWER_BOUND + 0xCC0) +#define FH49_MEM_RCSR_CHNL0 (FH49_MEM_RCSR_LOWER_BOUND) + +#define FH49_MEM_RCSR_CHNL0_CONFIG_REG (FH49_MEM_RCSR_CHNL0) + +#define FH49_RCSR_CHNL0_RX_CONFIG_RB_TIMEOUT_MSK (0x00000FF0) /* bits 4-11 */ +#define FH49_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_MSK (0x00001000) /* bits 12 */ +#define FH49_RCSR_CHNL0_RX_CONFIG_SINGLE_FRAME_MSK (0x00008000) /* bit 15 */ +#define FH49_RCSR_CHNL0_RX_CONFIG_RB_SIZE_MSK (0x00030000) /* bits 16-17 */ +#define FH49_RCSR_CHNL0_RX_CONFIG_RBDBC_SIZE_MSK (0x00F00000) /* bits 20-23 */ +#define FH49_RCSR_CHNL0_RX_CONFIG_DMA_CHNL_EN_MSK (0xC0000000) /* bits 30-31 */ + +#define FH49_RCSR_RX_CONFIG_RBDCB_SIZE_POS (20) +#define FH49_RCSR_RX_CONFIG_REG_IRQ_RBTH_POS (4) +#define RX_RB_TIMEOUT (0x10) + +#define FH49_RCSR_RX_CONFIG_CHNL_EN_PAUSE_VAL (0x00000000) +#define FH49_RCSR_RX_CONFIG_CHNL_EN_PAUSE_EOF_VAL (0x40000000) +#define FH49_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL (0x80000000) + +#define FH49_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K (0x00000000) +#define FH49_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_8K (0x00010000) +#define FH49_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_12K (0x00020000) +#define FH49_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_16K (0x00030000) + +#define FH49_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY (0x00000004) +#define FH49_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_NO_INT_VAL (0x00000000) +#define FH49_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL (0x00001000) + +/** + * Rx Shared Status Registers (RSSR) + * + * After stopping Rx DMA channel (writing 0 to + * FH49_MEM_RCSR_CHNL0_CONFIG_REG), driver must poll + * FH49_MEM_RSSR_RX_STATUS_REG until Rx channel is idle. + * + * Bit fields: + * 24: 1 = Channel 0 is idle + * + * FH49_MEM_RSSR_SHARED_CTRL_REG and FH49_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV + * contain default values that should not be altered by the driver. + */ +#define FH49_MEM_RSSR_LOWER_BOUND (FH49_MEM_LOWER_BOUND + 0xC40) +#define FH49_MEM_RSSR_UPPER_BOUND (FH49_MEM_LOWER_BOUND + 0xD00) + +#define FH49_MEM_RSSR_SHARED_CTRL_REG (FH49_MEM_RSSR_LOWER_BOUND) +#define FH49_MEM_RSSR_RX_STATUS_REG (FH49_MEM_RSSR_LOWER_BOUND + 0x004) +#define FH49_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV\ + (FH49_MEM_RSSR_LOWER_BOUND + 0x008) + +#define FH49_RSSR_CHNL0_RX_STATUS_CHNL_IDLE (0x01000000) + +#define FH49_MEM_TFDIB_REG1_ADDR_BITSHIFT 28 + +/* TFDB Area - TFDs buffer table */ +#define FH49_MEM_TFDIB_DRAM_ADDR_LSB_MSK (0xFFFFFFFF) +#define FH49_TFDIB_LOWER_BOUND (FH49_MEM_LOWER_BOUND + 0x900) +#define FH49_TFDIB_UPPER_BOUND (FH49_MEM_LOWER_BOUND + 0x958) +#define FH49_TFDIB_CTRL0_REG(_chnl) (FH49_TFDIB_LOWER_BOUND + 0x8 * (_chnl)) +#define FH49_TFDIB_CTRL1_REG(_chnl) (FH49_TFDIB_LOWER_BOUND + 0x8 * (_chnl) + 0x4) + +/** + * Transmit DMA Channel Control/Status Registers (TCSR) + * + * 4965 has one configuration register for each of 8 Tx DMA/FIFO channels + * supported in hardware (don't confuse these with the 16 Tx queues in DRAM, + * which feed the DMA/FIFO channels); config regs are separated by 0x20 bytes. + * + * To use a Tx DMA channel, driver must initialize its + * FH49_TCSR_CHNL_TX_CONFIG_REG(chnl) with: + * + * FH49_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE | + * FH49_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE_VAL + * + * All other bits should be 0. + * + * Bit fields: + * 31-30: Tx DMA channel enable: '00' off/pause, '01' pause at end of frame, + * '10' operate normally + * 29- 4: Reserved, set to "0" + * 3: Enable internal DMA requests (1, normal operation), disable (0) + * 2- 0: Reserved, set to "0" + */ +#define FH49_TCSR_LOWER_BOUND (FH49_MEM_LOWER_BOUND + 0xD00) +#define FH49_TCSR_UPPER_BOUND (FH49_MEM_LOWER_BOUND + 0xE60) + +/* Find Control/Status reg for given Tx DMA/FIFO channel */ +#define FH49_TCSR_CHNL_NUM (7) +#define FH50_TCSR_CHNL_NUM (8) + +/* TCSR: tx_config register values */ +#define FH49_TCSR_CHNL_TX_CONFIG_REG(_chnl) \ + (FH49_TCSR_LOWER_BOUND + 0x20 * (_chnl)) +#define FH49_TCSR_CHNL_TX_CREDIT_REG(_chnl) \ + (FH49_TCSR_LOWER_BOUND + 0x20 * (_chnl) + 0x4) +#define FH49_TCSR_CHNL_TX_BUF_STS_REG(_chnl) \ + (FH49_TCSR_LOWER_BOUND + 0x20 * (_chnl) + 0x8) + +#define FH49_TCSR_TX_CONFIG_REG_VAL_MSG_MODE_TXF (0x00000000) +#define FH49_TCSR_TX_CONFIG_REG_VAL_MSG_MODE_DRV (0x00000001) + +#define FH49_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_DISABLE (0x00000000) +#define FH49_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE (0x00000008) + +#define FH49_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_NOINT (0x00000000) +#define FH49_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_ENDTFD (0x00100000) +#define FH49_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_IFTFD (0x00200000) + +#define FH49_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_NOINT (0x00000000) +#define FH49_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_ENDTFD (0x00400000) +#define FH49_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_IFTFD (0x00800000) + +#define FH49_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE (0x00000000) +#define FH49_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE_EOF (0x40000000) +#define FH49_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE (0x80000000) + +#define FH49_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_EMPTY (0x00000000) +#define FH49_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_WAIT (0x00002000) +#define FH49_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_VALID (0x00000003) + +#define FH49_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_NUM (20) +#define FH49_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_IDX (12) + +/** + * Tx Shared Status Registers (TSSR) + * + * After stopping Tx DMA channel (writing 0 to + * FH49_TCSR_CHNL_TX_CONFIG_REG(chnl)), driver must poll + * FH49_TSSR_TX_STATUS_REG until selected Tx channel is idle + * (channel's buffers empty | no pending requests). + * + * Bit fields: + * 31-24: 1 = Channel buffers empty (channel 7:0) + * 23-16: 1 = No pending requests (channel 7:0) + */ +#define FH49_TSSR_LOWER_BOUND (FH49_MEM_LOWER_BOUND + 0xEA0) +#define FH49_TSSR_UPPER_BOUND (FH49_MEM_LOWER_BOUND + 0xEC0) + +#define FH49_TSSR_TX_STATUS_REG (FH49_TSSR_LOWER_BOUND + 0x010) + +/** + * Bit fields for TSSR(Tx Shared Status & Control) error status register: + * 31: Indicates an address error when accessed to internal memory + * uCode/driver must write "1" in order to clear this flag + * 30: Indicates that Host did not send the expected number of dwords to FH + * uCode/driver must write "1" in order to clear this flag + * 16-9:Each status bit is for one channel. Indicates that an (Error) ActDMA + * command was received from the scheduler while the TRB was already full + * with previous command + * uCode/driver must write "1" in order to clear this flag + * 7-0: Each status bit indicates a channel's TxCredit error. When an error + * bit is set, it indicates that the FH has received a full indication + * from the RTC TxFIFO and the current value of the TxCredit counter was + * not equal to zero. This mean that the credit mechanism was not + * synchronized to the TxFIFO status + * uCode/driver must write "1" in order to clear this flag + */ +#define FH49_TSSR_TX_ERROR_REG (FH49_TSSR_LOWER_BOUND + 0x018) + +#define FH49_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(_chnl) ((1 << (_chnl)) << 16) + +/* Tx service channels */ +#define FH49_SRVC_CHNL (9) +#define FH49_SRVC_LOWER_BOUND (FH49_MEM_LOWER_BOUND + 0x9C8) +#define FH49_SRVC_UPPER_BOUND (FH49_MEM_LOWER_BOUND + 0x9D0) +#define FH49_SRVC_CHNL_SRAM_ADDR_REG(_chnl) \ + (FH49_SRVC_LOWER_BOUND + ((_chnl) - 9) * 0x4) + +#define FH49_TX_CHICKEN_BITS_REG (FH49_MEM_LOWER_BOUND + 0xE98) +/* Instruct FH to increment the retry count of a packet when + * it is brought from the memory to TX-FIFO + */ +#define FH49_TX_CHICKEN_BITS_SCD_AUTO_RETRY_EN (0x00000002) + +/* Keep Warm Size */ +#define IL_KW_SIZE 0x1000 /* 4k */ + +#endif /* __il_4965_h__ */ diff --git a/drivers/net/wireless/iwlegacy/Kconfig b/drivers/net/wireless/iwlegacy/Kconfig new file mode 100644 index 0000000..3d4b209 --- /dev/null +++ b/drivers/net/wireless/iwlegacy/Kconfig @@ -0,0 +1,103 @@ +config IWLEGACY + tristate + depends on m + select BACKPORT_FW_LOADER + select BACKPORT_NEW_LEDS + select BACKPORT_LEDS_CLASS + select BACKPORT_LEDS_TRIGGERS + select MAC80211_LEDS + +config IWL4965 + tristate "Intel Wireless WiFi 4965AGN (iwl4965)" + depends on m + depends on PCI && MAC80211 + select IWLEGACY + ---help--- + This option enables support for + + Select to build the driver supporting the: + + Intel Wireless WiFi Link 4965AGN + + This driver uses the kernel's mac80211 subsystem. + + In order to use this driver, you will need a microcode (uCode) + image for it. You can obtain the microcode from: + + . + + The microcode is typically installed in /lib/firmware. You can + look in the hotplug script /etc/hotplug/firmware.agent to + determine which directory FIRMWARE_DIR is set to when the script + runs. + + If you want to compile the driver as a module ( = code which can be + inserted in and removed from the running kernel whenever you want), + say M here and read . The + module will be called iwl4965. + +config IWL3945 + tristate "Intel PRO/Wireless 3945ABG/BG Network Connection (iwl3945)" + depends on m + depends on PCI && MAC80211 + select IWLEGACY + ---help--- + Select to build the driver supporting the: + + Intel PRO/Wireless 3945ABG/BG Network Connection + + This driver uses the kernel's mac80211 subsystem. + + In order to use this driver, you will need a microcode (uCode) + image for it. You can obtain the microcode from: + + . + + The microcode is typically installed in /lib/firmware. You can + look in the hotplug script /etc/hotplug/firmware.agent to + determine which directory FIRMWARE_DIR is set to when the script + runs. + + If you want to compile the driver as a module ( = code which can be + inserted in and removed from the running kernel whenever you want), + say M here and read . The + module will be called iwl3945. + +menu "iwl3945 / iwl4965 Debugging Options" + depends on IWLEGACY + +config IWLEGACY_DEBUG + bool "Enable full debugging output in iwlegacy (iwl 3945/4965) drivers" + depends on IWLEGACY + ---help--- + This option will enable debug tracing output for the iwlegacy + drivers. + + This will result in the kernel module being ~100k larger. You can + control which debug output is sent to the kernel log by setting the + value in + + /sys/class/net/wlan0/device/debug_level + + This entry will only exist if this option is enabled. + + To set a value, simply echo an 8-byte hex value to the same file: + + % echo 0x43fff > /sys/class/net/wlan0/device/debug_level + + You can find the list of debug mask values in: + drivers/net/wireless/iwlegacy/common.h + + If this is your first time using this driver, you should say Y here + as the debug information can assist others in helping you resolve + any problems you may encounter. + +config IWLEGACY_DEBUGFS + bool "iwlegacy (iwl 3945/4965) debugfs support" + depends on IWLEGACY && MAC80211_DEBUGFS + ---help--- + Enable creation of debugfs files for the iwlegacy drivers. This + is a low-impact option that allows getting insight into the + driver's state at runtime. + +endmenu diff --git a/drivers/net/wireless/iwlegacy/Makefile b/drivers/net/wireless/iwlegacy/Makefile new file mode 100644 index 0000000..f7065ad --- /dev/null +++ b/drivers/net/wireless/iwlegacy/Makefile @@ -0,0 +1,17 @@ +obj-$(CPTCFG_IWLEGACY) += iwlegacy.o +iwlegacy-objs := common.o +iwlegacy-$(CPTCFG_IWLEGACY_DEBUGFS) += debug.o + +iwlegacy-objs += $(iwlegacy-m) + +# 4965 +obj-$(CPTCFG_IWL4965) += iwl4965.o +iwl4965-objs := 4965.o 4965-mac.o 4965-rs.o 4965-calib.o +iwl4965-$(CPTCFG_IWLEGACY_DEBUGFS) += 4965-debug.o + +# 3945 +obj-$(CPTCFG_IWL3945) += iwl3945.o +iwl3945-objs := 3945-mac.o 3945.o 3945-rs.o +iwl3945-$(CPTCFG_IWLEGACY_DEBUGFS) += 3945-debug.o + +ccflags-y += -D__CHECK_ENDIAN__ diff --git a/drivers/net/wireless/iwlegacy/commands.h b/drivers/net/wireless/iwlegacy/commands.h new file mode 100644 index 0000000..3b6c994 --- /dev/null +++ b/drivers/net/wireless/iwlegacy/commands.h @@ -0,0 +1,3377 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2005 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2011 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#ifndef __il_commands_h__ +#define __il_commands_h__ + +#include + +struct il_priv; + +/* uCode version contains 4 values: Major/Minor/API/Serial */ +#define IL_UCODE_MAJOR(ver) (((ver) & 0xFF000000) >> 24) +#define IL_UCODE_MINOR(ver) (((ver) & 0x00FF0000) >> 16) +#define IL_UCODE_API(ver) (((ver) & 0x0000FF00) >> 8) +#define IL_UCODE_SERIAL(ver) ((ver) & 0x000000FF) + +/* Tx rates */ +#define IL_CCK_RATES 4 +#define IL_OFDM_RATES 8 +#define IL_MAX_RATES (IL_CCK_RATES + IL_OFDM_RATES) + +enum { + N_ALIVE = 0x1, + N_ERROR = 0x2, + + /* RXON and QOS commands */ + C_RXON = 0x10, + C_RXON_ASSOC = 0x11, + C_QOS_PARAM = 0x13, + C_RXON_TIMING = 0x14, + + /* Multi-Station support */ + C_ADD_STA = 0x18, + C_REM_STA = 0x19, + + /* Security */ + C_WEPKEY = 0x20, + + /* RX, TX, LEDs */ + N_3945_RX = 0x1b, /* 3945 only */ + C_TX = 0x1c, + C_RATE_SCALE = 0x47, /* 3945 only */ + C_LEDS = 0x48, + C_TX_LINK_QUALITY_CMD = 0x4e, /* for 4965 */ + + /* 802.11h related */ + C_CHANNEL_SWITCH = 0x72, + N_CHANNEL_SWITCH = 0x73, + C_SPECTRUM_MEASUREMENT = 0x74, + N_SPECTRUM_MEASUREMENT = 0x75, + + /* Power Management */ + C_POWER_TBL = 0x77, + N_PM_SLEEP = 0x7A, + N_PM_DEBUG_STATS = 0x7B, + + /* Scan commands and notifications */ + C_SCAN = 0x80, + C_SCAN_ABORT = 0x81, + N_SCAN_START = 0x82, + N_SCAN_RESULTS = 0x83, + N_SCAN_COMPLETE = 0x84, + + /* IBSS/AP commands */ + N_BEACON = 0x90, + C_TX_BEACON = 0x91, + + /* Miscellaneous commands */ + C_TX_PWR_TBL = 0x97, + + /* Bluetooth device coexistence config command */ + C_BT_CONFIG = 0x9b, + + /* Statistics */ + C_STATS = 0x9c, + N_STATS = 0x9d, + + /* RF-KILL commands and notifications */ + N_CARD_STATE = 0xa1, + + /* Missed beacons notification */ + N_MISSED_BEACONS = 0xa2, + + C_CT_KILL_CONFIG = 0xa4, + C_SENSITIVITY = 0xa8, + C_PHY_CALIBRATION = 0xb0, + N_RX_PHY = 0xc0, + N_RX_MPDU = 0xc1, + N_RX = 0xc3, + N_COMPRESSED_BA = 0xc5, + + IL_CN_MAX = 0xff +}; + +/****************************************************************************** + * (0) + * Commonly used structures and definitions: + * Command header, rate_n_flags, txpower + * + *****************************************************************************/ + +/* il_cmd_header flags value */ +#define IL_CMD_FAILED_MSK 0x40 + +#define SEQ_TO_QUEUE(s) (((s) >> 8) & 0x1f) +#define QUEUE_TO_SEQ(q) (((q) & 0x1f) << 8) +#define SEQ_TO_IDX(s) ((s) & 0xff) +#define IDX_TO_SEQ(i) ((i) & 0xff) +#define SEQ_HUGE_FRAME cpu_to_le16(0x4000) +#define SEQ_RX_FRAME cpu_to_le16(0x8000) + +/** + * struct il_cmd_header + * + * This header format appears in the beginning of each command sent from the + * driver, and each response/notification received from uCode. + */ +struct il_cmd_header { + u8 cmd; /* Command ID: C_RXON, etc. */ + u8 flags; /* 0:5 reserved, 6 abort, 7 internal */ + /* + * The driver sets up the sequence number to values of its choosing. + * uCode does not use this value, but passes it back to the driver + * when sending the response to each driver-originated command, so + * the driver can match the response to the command. Since the values + * don't get used by uCode, the driver may set up an arbitrary format. + * + * There is one exception: uCode sets bit 15 when it originates + * the response/notification, i.e. when the response/notification + * is not a direct response to a command sent by the driver. For + * example, uCode issues N_3945_RX when it sends a received frame + * to the driver; it is not a direct response to any driver command. + * + * The Linux driver uses the following format: + * + * 0:7 tfd idx - position within TX queue + * 8:12 TX queue id + * 13 reserved + * 14 huge - driver sets this to indicate command is in the + * 'huge' storage at the end of the command buffers + * 15 unsolicited RX or uCode-originated notification + */ + __le16 sequence; + + /* command or response/notification data follows immediately */ + u8 data[0]; +} __packed; + +/** + * struct il3945_tx_power + * + * Used in C_TX_PWR_TBL, C_SCAN, C_CHANNEL_SWITCH + * + * Each entry contains two values: + * 1) DSP gain (or sometimes called DSP attenuation). This is a fine-grained + * linear value that multiplies the output of the digital signal processor, + * before being sent to the analog radio. + * 2) Radio gain. This sets the analog gain of the radio Tx path. + * It is a coarser setting, and behaves in a logarithmic (dB) fashion. + * + * Driver obtains values from struct il3945_tx_power power_gain_table[][]. + */ +struct il3945_tx_power { + u8 tx_gain; /* gain for analog radio */ + u8 dsp_atten; /* gain for DSP */ +} __packed; + +/** + * struct il3945_power_per_rate + * + * Used in C_TX_PWR_TBL, C_CHANNEL_SWITCH + */ +struct il3945_power_per_rate { + u8 rate; /* plcp */ + struct il3945_tx_power tpc; + u8 reserved; +} __packed; + +/** + * iwl4965 rate_n_flags bit fields + * + * rate_n_flags format is used in following iwl4965 commands: + * N_RX (response only) + * N_RX_MPDU (response only) + * C_TX (both command and response) + * C_TX_LINK_QUALITY_CMD + * + * High-throughput (HT) rate format for bits 7:0 (bit 8 must be "1"): + * 2-0: 0) 6 Mbps + * 1) 12 Mbps + * 2) 18 Mbps + * 3) 24 Mbps + * 4) 36 Mbps + * 5) 48 Mbps + * 6) 54 Mbps + * 7) 60 Mbps + * + * 4-3: 0) Single stream (SISO) + * 1) Dual stream (MIMO) + * 2) Triple stream (MIMO) + * + * 5: Value of 0x20 in bits 7:0 indicates 6 Mbps HT40 duplicate data + * + * Legacy OFDM rate format for bits 7:0 (bit 8 must be "0", bit 9 "0"): + * 3-0: 0xD) 6 Mbps + * 0xF) 9 Mbps + * 0x5) 12 Mbps + * 0x7) 18 Mbps + * 0x9) 24 Mbps + * 0xB) 36 Mbps + * 0x1) 48 Mbps + * 0x3) 54 Mbps + * + * Legacy CCK rate format for bits 7:0 (bit 8 must be "0", bit 9 "1"): + * 6-0: 10) 1 Mbps + * 20) 2 Mbps + * 55) 5.5 Mbps + * 110) 11 Mbps + */ +#define RATE_MCS_CODE_MSK 0x7 +#define RATE_MCS_SPATIAL_POS 3 +#define RATE_MCS_SPATIAL_MSK 0x18 +#define RATE_MCS_HT_DUP_POS 5 +#define RATE_MCS_HT_DUP_MSK 0x20 + +/* Bit 8: (1) HT format, (0) legacy format in bits 7:0 */ +#define RATE_MCS_FLAGS_POS 8 +#define RATE_MCS_HT_POS 8 +#define RATE_MCS_HT_MSK 0x100 + +/* Bit 9: (1) CCK, (0) OFDM. HT (bit 8) must be "0" for this bit to be valid */ +#define RATE_MCS_CCK_POS 9 +#define RATE_MCS_CCK_MSK 0x200 + +/* Bit 10: (1) Use Green Field preamble */ +#define RATE_MCS_GF_POS 10 +#define RATE_MCS_GF_MSK 0x400 + +/* Bit 11: (1) Use 40Mhz HT40 chnl width, (0) use 20 MHz legacy chnl width */ +#define RATE_MCS_HT40_POS 11 +#define RATE_MCS_HT40_MSK 0x800 + +/* Bit 12: (1) Duplicate data on both 20MHz chnls. HT40 (bit 11) must be set. */ +#define RATE_MCS_DUP_POS 12 +#define RATE_MCS_DUP_MSK 0x1000 + +/* Bit 13: (1) Short guard interval (0.4 usec), (0) normal GI (0.8 usec) */ +#define RATE_MCS_SGI_POS 13 +#define RATE_MCS_SGI_MSK 0x2000 + +/** + * rate_n_flags Tx antenna masks + * 4965 has 2 transmitters + * bit14:16 + */ +#define RATE_MCS_ANT_POS 14 +#define RATE_MCS_ANT_A_MSK 0x04000 +#define RATE_MCS_ANT_B_MSK 0x08000 +#define RATE_MCS_ANT_C_MSK 0x10000 +#define RATE_MCS_ANT_AB_MSK (RATE_MCS_ANT_A_MSK | RATE_MCS_ANT_B_MSK) +#define RATE_MCS_ANT_ABC_MSK (RATE_MCS_ANT_AB_MSK | RATE_MCS_ANT_C_MSK) +#define RATE_ANT_NUM 3 + +#define POWER_TBL_NUM_ENTRIES 33 +#define POWER_TBL_NUM_HT_OFDM_ENTRIES 32 +#define POWER_TBL_CCK_ENTRY 32 + +#define IL_PWR_NUM_HT_OFDM_ENTRIES 24 +#define IL_PWR_CCK_ENTRIES 2 + +/** + * union il4965_tx_power_dual_stream + * + * Host format used for C_TX_PWR_TBL, C_CHANNEL_SWITCH + * Use __le32 version (struct tx_power_dual_stream) when building command. + * + * Driver provides radio gain and DSP attenuation settings to device in pairs, + * one value for each transmitter chain. The first value is for transmitter A, + * second for transmitter B. + * + * For SISO bit rates, both values in a pair should be identical. + * For MIMO rates, one value may be different from the other, + * in order to balance the Tx output between the two transmitters. + * + * See more details in doc for TXPOWER in 4965.h. + */ +union il4965_tx_power_dual_stream { + struct { + u8 radio_tx_gain[2]; + u8 dsp_predis_atten[2]; + } s; + u32 dw; +}; + +/** + * struct tx_power_dual_stream + * + * Table entries in C_TX_PWR_TBL, C_CHANNEL_SWITCH + * + * Same format as il_tx_power_dual_stream, but __le32 + */ +struct tx_power_dual_stream { + __le32 dw; +} __packed; + +/** + * struct il4965_tx_power_db + * + * Entire table within C_TX_PWR_TBL, C_CHANNEL_SWITCH + */ +struct il4965_tx_power_db { + struct tx_power_dual_stream power_tbl[POWER_TBL_NUM_ENTRIES]; +} __packed; + +/****************************************************************************** + * (0a) + * Alive and Error Commands & Responses: + * + *****************************************************************************/ + +#define UCODE_VALID_OK cpu_to_le32(0x1) +#define INITIALIZE_SUBTYPE (9) + +/* + * ("Initialize") N_ALIVE = 0x1 (response only, not a command) + * + * uCode issues this "initialize alive" notification once the initialization + * uCode image has completed its work, and is ready to load the runtime image. + * This is the *first* "alive" notification that the driver will receive after + * rebooting uCode; the "initialize" alive is indicated by subtype field == 9. + * + * See comments documenting "BSM" (bootstrap state machine). + * + * For 4965, this notification contains important calibration data for + * calculating txpower settings: + * + * 1) Power supply voltage indication. The voltage sensor outputs higher + * values for lower voltage, and vice verse. + * + * 2) Temperature measurement parameters, for each of two channel widths + * (20 MHz and 40 MHz) supported by the radios. Temperature sensing + * is done via one of the receiver chains, and channel width influences + * the results. + * + * 3) Tx gain compensation to balance 4965's 2 Tx chains for MIMO operation, + * for each of 5 frequency ranges. + */ +struct il_init_alive_resp { + u8 ucode_minor; + u8 ucode_major; + __le16 reserved1; + u8 sw_rev[8]; + u8 ver_type; + u8 ver_subtype; /* "9" for initialize alive */ + __le16 reserved2; + __le32 log_event_table_ptr; + __le32 error_event_table_ptr; + __le32 timestamp; + __le32 is_valid; + + /* calibration values from "initialize" uCode */ + __le32 voltage; /* signed, higher value is lower voltage */ + __le32 therm_r1[2]; /* signed, 1st for normal, 2nd for HT40 */ + __le32 therm_r2[2]; /* signed */ + __le32 therm_r3[2]; /* signed */ + __le32 therm_r4[2]; /* signed */ + __le32 tx_atten[5][2]; /* signed MIMO gain comp, 5 freq groups, + * 2 Tx chains */ +} __packed; + +/** + * N_ALIVE = 0x1 (response only, not a command) + * + * uCode issues this "alive" notification once the runtime image is ready + * to receive commands from the driver. This is the *second* "alive" + * notification that the driver will receive after rebooting uCode; + * this "alive" is indicated by subtype field != 9. + * + * See comments documenting "BSM" (bootstrap state machine). + * + * This response includes two pointers to structures within the device's + * data SRAM (access via HBUS_TARG_MEM_* regs) that are useful for debugging: + * + * 1) log_event_table_ptr indicates base of the event log. This traces + * a 256-entry history of uCode execution within a circular buffer. + * Its header format is: + * + * __le32 log_size; log capacity (in number of entries) + * __le32 type; (1) timestamp with each entry, (0) no timestamp + * __le32 wraps; # times uCode has wrapped to top of circular buffer + * __le32 write_idx; next circular buffer entry that uCode would fill + * + * The header is followed by the circular buffer of log entries. Entries + * with timestamps have the following format: + * + * __le32 event_id; range 0 - 1500 + * __le32 timestamp; low 32 bits of TSF (of network, if associated) + * __le32 data; event_id-specific data value + * + * Entries without timestamps contain only event_id and data. + * + * + * 2) error_event_table_ptr indicates base of the error log. This contains + * information about any uCode error that occurs. For 4965, the format + * of the error log is: + * + * __le32 valid; (nonzero) valid, (0) log is empty + * __le32 error_id; type of error + * __le32 pc; program counter + * __le32 blink1; branch link + * __le32 blink2; branch link + * __le32 ilink1; interrupt link + * __le32 ilink2; interrupt link + * __le32 data1; error-specific data + * __le32 data2; error-specific data + * __le32 line; source code line of error + * __le32 bcon_time; beacon timer + * __le32 tsf_low; network timestamp function timer + * __le32 tsf_hi; network timestamp function timer + * __le32 gp1; GP1 timer register + * __le32 gp2; GP2 timer register + * __le32 gp3; GP3 timer register + * __le32 ucode_ver; uCode version + * __le32 hw_ver; HW Silicon version + * __le32 brd_ver; HW board version + * __le32 log_pc; log program counter + * __le32 frame_ptr; frame pointer + * __le32 stack_ptr; stack pointer + * __le32 hcmd; last host command + * __le32 isr0; isr status register LMPM_NIC_ISR0: rxtx_flag + * __le32 isr1; isr status register LMPM_NIC_ISR1: host_flag + * __le32 isr2; isr status register LMPM_NIC_ISR2: enc_flag + * __le32 isr3; isr status register LMPM_NIC_ISR3: time_flag + * __le32 isr4; isr status register LMPM_NIC_ISR4: wico interrupt + * __le32 isr_pref; isr status register LMPM_NIC_PREF_STAT + * __le32 wait_event; wait event() caller address + * __le32 l2p_control; L2pControlField + * __le32 l2p_duration; L2pDurationField + * __le32 l2p_mhvalid; L2pMhValidBits + * __le32 l2p_addr_match; L2pAddrMatchStat + * __le32 lmpm_pmg_sel; indicate which clocks are turned on (LMPM_PMG_SEL) + * __le32 u_timestamp; indicate when the date and time of the compilation + * __le32 reserved; + * + * The Linux driver can print both logs to the system log when a uCode error + * occurs. + */ +struct il_alive_resp { + u8 ucode_minor; + u8 ucode_major; + __le16 reserved1; + u8 sw_rev[8]; + u8 ver_type; + u8 ver_subtype; /* not "9" for runtime alive */ + __le16 reserved2; + __le32 log_event_table_ptr; /* SRAM address for event log */ + __le32 error_event_table_ptr; /* SRAM address for error log */ + __le32 timestamp; + __le32 is_valid; +} __packed; + +/* + * N_ERROR = 0x2 (response only, not a command) + */ +struct il_error_resp { + __le32 error_type; + u8 cmd_id; + u8 reserved1; + __le16 bad_cmd_seq_num; + __le32 error_info; + __le64 timestamp; +} __packed; + +/****************************************************************************** + * (1) + * RXON Commands & Responses: + * + *****************************************************************************/ + +/* + * Rx config defines & structure + */ +/* rx_config device types */ +enum { + RXON_DEV_TYPE_AP = 1, + RXON_DEV_TYPE_ESS = 3, + RXON_DEV_TYPE_IBSS = 4, + RXON_DEV_TYPE_SNIFFER = 6, +}; + +#define RXON_RX_CHAIN_DRIVER_FORCE_MSK cpu_to_le16(0x1 << 0) +#define RXON_RX_CHAIN_DRIVER_FORCE_POS (0) +#define RXON_RX_CHAIN_VALID_MSK cpu_to_le16(0x7 << 1) +#define RXON_RX_CHAIN_VALID_POS (1) +#define RXON_RX_CHAIN_FORCE_SEL_MSK cpu_to_le16(0x7 << 4) +#define RXON_RX_CHAIN_FORCE_SEL_POS (4) +#define RXON_RX_CHAIN_FORCE_MIMO_SEL_MSK cpu_to_le16(0x7 << 7) +#define RXON_RX_CHAIN_FORCE_MIMO_SEL_POS (7) +#define RXON_RX_CHAIN_CNT_MSK cpu_to_le16(0x3 << 10) +#define RXON_RX_CHAIN_CNT_POS (10) +#define RXON_RX_CHAIN_MIMO_CNT_MSK cpu_to_le16(0x3 << 12) +#define RXON_RX_CHAIN_MIMO_CNT_POS (12) +#define RXON_RX_CHAIN_MIMO_FORCE_MSK cpu_to_le16(0x1 << 14) +#define RXON_RX_CHAIN_MIMO_FORCE_POS (14) + +/* rx_config flags */ +/* band & modulation selection */ +#define RXON_FLG_BAND_24G_MSK cpu_to_le32(1 << 0) +#define RXON_FLG_CCK_MSK cpu_to_le32(1 << 1) +/* auto detection enable */ +#define RXON_FLG_AUTO_DETECT_MSK cpu_to_le32(1 << 2) +/* TGg protection when tx */ +#define RXON_FLG_TGG_PROTECT_MSK cpu_to_le32(1 << 3) +/* cck short slot & preamble */ +#define RXON_FLG_SHORT_SLOT_MSK cpu_to_le32(1 << 4) +#define RXON_FLG_SHORT_PREAMBLE_MSK cpu_to_le32(1 << 5) +/* antenna selection */ +#define RXON_FLG_DIS_DIV_MSK cpu_to_le32(1 << 7) +#define RXON_FLG_ANT_SEL_MSK cpu_to_le32(0x0f00) +#define RXON_FLG_ANT_A_MSK cpu_to_le32(1 << 8) +#define RXON_FLG_ANT_B_MSK cpu_to_le32(1 << 9) +/* radar detection enable */ +#define RXON_FLG_RADAR_DETECT_MSK cpu_to_le32(1 << 12) +#define RXON_FLG_TGJ_NARROW_BAND_MSK cpu_to_le32(1 << 13) +/* rx response to host with 8-byte TSF +* (according to ON_AIR deassertion) */ +#define RXON_FLG_TSF2HOST_MSK cpu_to_le32(1 << 15) + +/* HT flags */ +#define RXON_FLG_CTRL_CHANNEL_LOC_POS (22) +#define RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK cpu_to_le32(0x1 << 22) + +#define RXON_FLG_HT_OPERATING_MODE_POS (23) + +#define RXON_FLG_HT_PROT_MSK cpu_to_le32(0x1 << 23) +#define RXON_FLG_HT40_PROT_MSK cpu_to_le32(0x2 << 23) + +#define RXON_FLG_CHANNEL_MODE_POS (25) +#define RXON_FLG_CHANNEL_MODE_MSK cpu_to_le32(0x3 << 25) + +/* channel mode */ +enum { + CHANNEL_MODE_LEGACY = 0, + CHANNEL_MODE_PURE_40 = 1, + CHANNEL_MODE_MIXED = 2, + CHANNEL_MODE_RESERVED = 3, +}; +#define RXON_FLG_CHANNEL_MODE_LEGACY \ + cpu_to_le32(CHANNEL_MODE_LEGACY << RXON_FLG_CHANNEL_MODE_POS) +#define RXON_FLG_CHANNEL_MODE_PURE_40 \ + cpu_to_le32(CHANNEL_MODE_PURE_40 << RXON_FLG_CHANNEL_MODE_POS) +#define RXON_FLG_CHANNEL_MODE_MIXED \ + cpu_to_le32(CHANNEL_MODE_MIXED << RXON_FLG_CHANNEL_MODE_POS) + +/* CTS to self (if spec allows) flag */ +#define RXON_FLG_SELF_CTS_EN cpu_to_le32(0x1<<30) + +/* rx_config filter flags */ +/* accept all data frames */ +#define RXON_FILTER_PROMISC_MSK cpu_to_le32(1 << 0) +/* pass control & management to host */ +#define RXON_FILTER_CTL2HOST_MSK cpu_to_le32(1 << 1) +/* accept multi-cast */ +#define RXON_FILTER_ACCEPT_GRP_MSK cpu_to_le32(1 << 2) +/* don't decrypt uni-cast frames */ +#define RXON_FILTER_DIS_DECRYPT_MSK cpu_to_le32(1 << 3) +/* don't decrypt multi-cast frames */ +#define RXON_FILTER_DIS_GRP_DECRYPT_MSK cpu_to_le32(1 << 4) +/* STA is associated */ +#define RXON_FILTER_ASSOC_MSK cpu_to_le32(1 << 5) +/* transfer to host non bssid beacons in associated state */ +#define RXON_FILTER_BCON_AWARE_MSK cpu_to_le32(1 << 6) + +/** + * C_RXON = 0x10 (command, has simple generic response) + * + * RXON tunes the radio tuner to a service channel, and sets up a number + * of parameters that are used primarily for Rx, but also for Tx operations. + * + * NOTE: When tuning to a new channel, driver must set the + * RXON_FILTER_ASSOC_MSK to 0. This will clear station-dependent + * info within the device, including the station tables, tx retry + * rate tables, and txpower tables. Driver must build a new station + * table and txpower table before transmitting anything on the RXON + * channel. + * + * NOTE: All RXONs wipe clean the internal txpower table. Driver must + * issue a new C_TX_PWR_TBL after each C_RXON (0x10), + * regardless of whether RXON_FILTER_ASSOC_MSK is set. + */ + +struct il3945_rxon_cmd { + u8 node_addr[6]; + __le16 reserved1; + u8 bssid_addr[6]; + __le16 reserved2; + u8 wlap_bssid_addr[6]; + __le16 reserved3; + u8 dev_type; + u8 air_propagation; + __le16 reserved4; + u8 ofdm_basic_rates; + u8 cck_basic_rates; + __le16 assoc_id; + __le32 flags; + __le32 filter_flags; + __le16 channel; + __le16 reserved5; +} __packed; + +struct il4965_rxon_cmd { + u8 node_addr[6]; + __le16 reserved1; + u8 bssid_addr[6]; + __le16 reserved2; + u8 wlap_bssid_addr[6]; + __le16 reserved3; + u8 dev_type; + u8 air_propagation; + __le16 rx_chain; + u8 ofdm_basic_rates; + u8 cck_basic_rates; + __le16 assoc_id; + __le32 flags; + __le32 filter_flags; + __le16 channel; + u8 ofdm_ht_single_stream_basic_rates; + u8 ofdm_ht_dual_stream_basic_rates; +} __packed; + +/* Create a common rxon cmd which will be typecast into the 3945 or 4965 + * specific rxon cmd, depending on where it is called from. + */ +struct il_rxon_cmd { + u8 node_addr[6]; + __le16 reserved1; + u8 bssid_addr[6]; + __le16 reserved2; + u8 wlap_bssid_addr[6]; + __le16 reserved3; + u8 dev_type; + u8 air_propagation; + __le16 rx_chain; + u8 ofdm_basic_rates; + u8 cck_basic_rates; + __le16 assoc_id; + __le32 flags; + __le32 filter_flags; + __le16 channel; + u8 ofdm_ht_single_stream_basic_rates; + u8 ofdm_ht_dual_stream_basic_rates; + u8 reserved4; + u8 reserved5; +} __packed; + +/* + * C_RXON_ASSOC = 0x11 (command, has simple generic response) + */ +struct il3945_rxon_assoc_cmd { + __le32 flags; + __le32 filter_flags; + u8 ofdm_basic_rates; + u8 cck_basic_rates; + __le16 reserved; +} __packed; + +struct il4965_rxon_assoc_cmd { + __le32 flags; + __le32 filter_flags; + u8 ofdm_basic_rates; + u8 cck_basic_rates; + u8 ofdm_ht_single_stream_basic_rates; + u8 ofdm_ht_dual_stream_basic_rates; + __le16 rx_chain_select_flags; + __le16 reserved; +} __packed; + +#define IL_CONN_MAX_LISTEN_INTERVAL 10 +#define IL_MAX_UCODE_BEACON_INTERVAL 4 /* 4096 */ +#define IL39_MAX_UCODE_BEACON_INTERVAL 1 /* 1024 */ + +/* + * C_RXON_TIMING = 0x14 (command, has simple generic response) + */ +struct il_rxon_time_cmd { + __le64 timestamp; + __le16 beacon_interval; + __le16 atim_win; + __le32 beacon_init_val; + __le16 listen_interval; + u8 dtim_period; + u8 delta_cp_bss_tbtts; +} __packed; + +/* + * C_CHANNEL_SWITCH = 0x72 (command, has simple generic response) + */ +struct il3945_channel_switch_cmd { + u8 band; + u8 expect_beacon; + __le16 channel; + __le32 rxon_flags; + __le32 rxon_filter_flags; + __le32 switch_time; + struct il3945_power_per_rate power[IL_MAX_RATES]; +} __packed; + +struct il4965_channel_switch_cmd { + u8 band; + u8 expect_beacon; + __le16 channel; + __le32 rxon_flags; + __le32 rxon_filter_flags; + __le32 switch_time; + struct il4965_tx_power_db tx_power; +} __packed; + +/* + * N_CHANNEL_SWITCH = 0x73 (notification only, not a command) + */ +struct il_csa_notification { + __le16 band; + __le16 channel; + __le32 status; /* 0 - OK, 1 - fail */ +} __packed; + +/****************************************************************************** + * (2) + * Quality-of-Service (QOS) Commands & Responses: + * + *****************************************************************************/ + +/** + * struct il_ac_qos -- QOS timing params for C_QOS_PARAM + * One for each of 4 EDCA access categories in struct il_qosparam_cmd + * + * @cw_min: Contention win, start value in numbers of slots. + * Should be a power-of-2, minus 1. Device's default is 0x0f. + * @cw_max: Contention win, max value in numbers of slots. + * Should be a power-of-2, minus 1. Device's default is 0x3f. + * @aifsn: Number of slots in Arbitration Interframe Space (before + * performing random backoff timing prior to Tx). Device default 1. + * @edca_txop: Length of Tx opportunity, in uSecs. Device default is 0. + * + * Device will automatically increase contention win by (2*CW) + 1 for each + * transmission retry. Device uses cw_max as a bit mask, ANDed with new CW + * value, to cap the CW value. + */ +struct il_ac_qos { + __le16 cw_min; + __le16 cw_max; + u8 aifsn; + u8 reserved1; + __le16 edca_txop; +} __packed; + +/* QoS flags defines */ +#define QOS_PARAM_FLG_UPDATE_EDCA_MSK cpu_to_le32(0x01) +#define QOS_PARAM_FLG_TGN_MSK cpu_to_le32(0x02) +#define QOS_PARAM_FLG_TXOP_TYPE_MSK cpu_to_le32(0x10) + +/* Number of Access Categories (AC) (EDCA), queues 0..3 */ +#define AC_NUM 4 + +/* + * C_QOS_PARAM = 0x13 (command, has simple generic response) + * + * This command sets up timings for each of the 4 prioritized EDCA Tx FIFOs + * 0: Background, 1: Best Effort, 2: Video, 3: Voice. + */ +struct il_qosparam_cmd { + __le32 qos_flags; + struct il_ac_qos ac[AC_NUM]; +} __packed; + +/****************************************************************************** + * (3) + * Add/Modify Stations Commands & Responses: + * + *****************************************************************************/ +/* + * Multi station support + */ + +/* Special, dedicated locations within device's station table */ +#define IL_AP_ID 0 +#define IL_STA_ID 2 +#define IL3945_BROADCAST_ID 24 +#define IL3945_STATION_COUNT 25 +#define IL4965_BROADCAST_ID 31 +#define IL4965_STATION_COUNT 32 + +#define IL_STATION_COUNT 32 /* MAX(3945,4965) */ +#define IL_INVALID_STATION 255 + +#define STA_FLG_TX_RATE_MSK cpu_to_le32(1 << 2) +#define STA_FLG_PWR_SAVE_MSK cpu_to_le32(1 << 8) +#define STA_FLG_RTS_MIMO_PROT_MSK cpu_to_le32(1 << 17) +#define STA_FLG_AGG_MPDU_8US_MSK cpu_to_le32(1 << 18) +#define STA_FLG_MAX_AGG_SIZE_POS (19) +#define STA_FLG_MAX_AGG_SIZE_MSK cpu_to_le32(3 << 19) +#define STA_FLG_HT40_EN_MSK cpu_to_le32(1 << 21) +#define STA_FLG_MIMO_DIS_MSK cpu_to_le32(1 << 22) +#define STA_FLG_AGG_MPDU_DENSITY_POS (23) +#define STA_FLG_AGG_MPDU_DENSITY_MSK cpu_to_le32(7 << 23) + +/* Use in mode field. 1: modify existing entry, 0: add new station entry */ +#define STA_CONTROL_MODIFY_MSK 0x01 + +/* key flags __le16*/ +#define STA_KEY_FLG_ENCRYPT_MSK cpu_to_le16(0x0007) +#define STA_KEY_FLG_NO_ENC cpu_to_le16(0x0000) +#define STA_KEY_FLG_WEP cpu_to_le16(0x0001) +#define STA_KEY_FLG_CCMP cpu_to_le16(0x0002) +#define STA_KEY_FLG_TKIP cpu_to_le16(0x0003) + +#define STA_KEY_FLG_KEYID_POS 8 +#define STA_KEY_FLG_INVALID cpu_to_le16(0x0800) +/* wep key is either from global key (0) or from station info array (1) */ +#define STA_KEY_FLG_MAP_KEY_MSK cpu_to_le16(0x0008) + +/* wep key in STA: 5-bytes (0) or 13-bytes (1) */ +#define STA_KEY_FLG_KEY_SIZE_MSK cpu_to_le16(0x1000) +#define STA_KEY_MULTICAST_MSK cpu_to_le16(0x4000) +#define STA_KEY_MAX_NUM 8 + +/* Flags indicate whether to modify vs. don't change various station params */ +#define STA_MODIFY_KEY_MASK 0x01 +#define STA_MODIFY_TID_DISABLE_TX 0x02 +#define STA_MODIFY_TX_RATE_MSK 0x04 +#define STA_MODIFY_ADDBA_TID_MSK 0x08 +#define STA_MODIFY_DELBA_TID_MSK 0x10 +#define STA_MODIFY_SLEEP_TX_COUNT_MSK 0x20 + +/* Receiver address (actually, Rx station's idx into station table), + * combined with Traffic ID (QOS priority), in format used by Tx Scheduler */ +#define BUILD_RAxTID(sta_id, tid) (((sta_id) << 4) + (tid)) + +struct il4965_keyinfo { + __le16 key_flags; + u8 tkip_rx_tsc_byte2; /* TSC[2] for key mix ph1 detection */ + u8 reserved1; + __le16 tkip_rx_ttak[5]; /* 10-byte unicast TKIP TTAK */ + u8 key_offset; + u8 reserved2; + u8 key[16]; /* 16-byte unicast decryption key */ +} __packed; + +/** + * struct sta_id_modify + * @addr[ETH_ALEN]: station's MAC address + * @sta_id: idx of station in uCode's station table + * @modify_mask: STA_MODIFY_*, 1: modify, 0: don't change + * + * Driver selects unused table idx when adding new station, + * or the idx to a pre-existing station entry when modifying that station. + * Some idxes have special purposes (IL_AP_ID, idx 0, is for AP). + * + * modify_mask flags select which parameters to modify vs. leave alone. + */ +struct sta_id_modify { + u8 addr[ETH_ALEN]; + __le16 reserved1; + u8 sta_id; + u8 modify_mask; + __le16 reserved2; +} __packed; + +/* + * C_ADD_STA = 0x18 (command) + * + * The device contains an internal table of per-station information, + * with info on security keys, aggregation parameters, and Tx rates for + * initial Tx attempt and any retries (4965 devices uses + * C_TX_LINK_QUALITY_CMD, + * 3945 uses C_RATE_SCALE to set up rate tables). + * + * C_ADD_STA sets up the table entry for one station, either creating + * a new entry, or modifying a pre-existing one. + * + * NOTE: RXON command (without "associated" bit set) wipes the station table + * clean. Moving into RF_KILL state does this also. Driver must set up + * new station table before transmitting anything on the RXON channel + * (except active scans or active measurements; those commands carry + * their own txpower/rate setup data). + * + * When getting started on a new channel, driver must set up the + * IL_BROADCAST_ID entry (last entry in the table). For a client + * station in a BSS, once an AP is selected, driver sets up the AP STA + * in the IL_AP_ID entry (1st entry in the table). BROADCAST and AP + * are all that are needed for a BSS client station. If the device is + * used as AP, or in an IBSS network, driver must set up station table + * entries for all STAs in network, starting with idx IL_STA_ID. + */ + +struct il3945_addsta_cmd { + u8 mode; /* 1: modify existing, 0: add new station */ + u8 reserved[3]; + struct sta_id_modify sta; + struct il4965_keyinfo key; + __le32 station_flags; /* STA_FLG_* */ + __le32 station_flags_msk; /* STA_FLG_* */ + + /* bit field to disable (1) or enable (0) Tx for Traffic ID (TID) + * corresponding to bit (e.g. bit 5 controls TID 5). + * Set modify_mask bit STA_MODIFY_TID_DISABLE_TX to use this field. */ + __le16 tid_disable_tx; + + __le16 rate_n_flags; + + /* TID for which to add block-ack support. + * Set modify_mask bit STA_MODIFY_ADDBA_TID_MSK to use this field. */ + u8 add_immediate_ba_tid; + + /* TID for which to remove block-ack support. + * Set modify_mask bit STA_MODIFY_DELBA_TID_MSK to use this field. */ + u8 remove_immediate_ba_tid; + + /* Starting Sequence Number for added block-ack support. + * Set modify_mask bit STA_MODIFY_ADDBA_TID_MSK to use this field. */ + __le16 add_immediate_ba_ssn; +} __packed; + +struct il4965_addsta_cmd { + u8 mode; /* 1: modify existing, 0: add new station */ + u8 reserved[3]; + struct sta_id_modify sta; + struct il4965_keyinfo key; + __le32 station_flags; /* STA_FLG_* */ + __le32 station_flags_msk; /* STA_FLG_* */ + + /* bit field to disable (1) or enable (0) Tx for Traffic ID (TID) + * corresponding to bit (e.g. bit 5 controls TID 5). + * Set modify_mask bit STA_MODIFY_TID_DISABLE_TX to use this field. */ + __le16 tid_disable_tx; + + __le16 reserved1; + + /* TID for which to add block-ack support. + * Set modify_mask bit STA_MODIFY_ADDBA_TID_MSK to use this field. */ + u8 add_immediate_ba_tid; + + /* TID for which to remove block-ack support. + * Set modify_mask bit STA_MODIFY_DELBA_TID_MSK to use this field. */ + u8 remove_immediate_ba_tid; + + /* Starting Sequence Number for added block-ack support. + * Set modify_mask bit STA_MODIFY_ADDBA_TID_MSK to use this field. */ + __le16 add_immediate_ba_ssn; + + /* + * Number of packets OK to transmit to station even though + * it is asleep -- used to synchronise PS-poll and u-APSD + * responses while ucode keeps track of STA sleep state. + */ + __le16 sleep_tx_count; + + __le16 reserved2; +} __packed; + +/* Wrapper struct for 3945 and 4965 addsta_cmd structures */ +struct il_addsta_cmd { + u8 mode; /* 1: modify existing, 0: add new station */ + u8 reserved[3]; + struct sta_id_modify sta; + struct il4965_keyinfo key; + __le32 station_flags; /* STA_FLG_* */ + __le32 station_flags_msk; /* STA_FLG_* */ + + /* bit field to disable (1) or enable (0) Tx for Traffic ID (TID) + * corresponding to bit (e.g. bit 5 controls TID 5). + * Set modify_mask bit STA_MODIFY_TID_DISABLE_TX to use this field. */ + __le16 tid_disable_tx; + + __le16 rate_n_flags; /* 3945 only */ + + /* TID for which to add block-ack support. + * Set modify_mask bit STA_MODIFY_ADDBA_TID_MSK to use this field. */ + u8 add_immediate_ba_tid; + + /* TID for which to remove block-ack support. + * Set modify_mask bit STA_MODIFY_DELBA_TID_MSK to use this field. */ + u8 remove_immediate_ba_tid; + + /* Starting Sequence Number for added block-ack support. + * Set modify_mask bit STA_MODIFY_ADDBA_TID_MSK to use this field. */ + __le16 add_immediate_ba_ssn; + + /* + * Number of packets OK to transmit to station even though + * it is asleep -- used to synchronise PS-poll and u-APSD + * responses while ucode keeps track of STA sleep state. + */ + __le16 sleep_tx_count; + + __le16 reserved2; +} __packed; + +#define ADD_STA_SUCCESS_MSK 0x1 +#define ADD_STA_NO_ROOM_IN_TBL 0x2 +#define ADD_STA_NO_BLOCK_ACK_RESOURCE 0x4 +#define ADD_STA_MODIFY_NON_EXIST_STA 0x8 +/* + * C_ADD_STA = 0x18 (response) + */ +struct il_add_sta_resp { + u8 status; /* ADD_STA_* */ +} __packed; + +#define REM_STA_SUCCESS_MSK 0x1 +/* + * C_REM_STA = 0x19 (response) + */ +struct il_rem_sta_resp { + u8 status; +} __packed; + +/* + * C_REM_STA = 0x19 (command) + */ +struct il_rem_sta_cmd { + u8 num_sta; /* number of removed stations */ + u8 reserved[3]; + u8 addr[ETH_ALEN]; /* MAC addr of the first station */ + u8 reserved2[2]; +} __packed; + +#define IL_TX_FIFO_BK_MSK cpu_to_le32(BIT(0)) +#define IL_TX_FIFO_BE_MSK cpu_to_le32(BIT(1)) +#define IL_TX_FIFO_VI_MSK cpu_to_le32(BIT(2)) +#define IL_TX_FIFO_VO_MSK cpu_to_le32(BIT(3)) +#define IL_AGG_TX_QUEUE_MSK cpu_to_le32(0xffc00) + +#define IL_DROP_SINGLE 0 +#define IL_DROP_SELECTED 1 +#define IL_DROP_ALL 2 + +/* + * REPLY_WEP_KEY = 0x20 + */ +struct il_wep_key { + u8 key_idx; + u8 key_offset; + u8 reserved1[2]; + u8 key_size; + u8 reserved2[3]; + u8 key[16]; +} __packed; + +struct il_wep_cmd { + u8 num_keys; + u8 global_key_type; + u8 flags; + u8 reserved; + struct il_wep_key key[0]; +} __packed; + +#define WEP_KEY_WEP_TYPE 1 +#define WEP_KEYS_MAX 4 +#define WEP_INVALID_OFFSET 0xff +#define WEP_KEY_LEN_64 5 +#define WEP_KEY_LEN_128 13 + +/****************************************************************************** + * (4) + * Rx Responses: + * + *****************************************************************************/ + +#define RX_RES_STATUS_NO_CRC32_ERROR cpu_to_le32(1 << 0) +#define RX_RES_STATUS_NO_RXE_OVERFLOW cpu_to_le32(1 << 1) + +#define RX_RES_PHY_FLAGS_BAND_24_MSK cpu_to_le16(1 << 0) +#define RX_RES_PHY_FLAGS_MOD_CCK_MSK cpu_to_le16(1 << 1) +#define RX_RES_PHY_FLAGS_SHORT_PREAMBLE_MSK cpu_to_le16(1 << 2) +#define RX_RES_PHY_FLAGS_NARROW_BAND_MSK cpu_to_le16(1 << 3) +#define RX_RES_PHY_FLAGS_ANTENNA_MSK 0x70 +#define RX_RES_PHY_FLAGS_ANTENNA_POS 4 +#define RX_RES_PHY_FLAGS_AGG_MSK cpu_to_le16(1 << 7) + +#define RX_RES_STATUS_SEC_TYPE_MSK (0x7 << 8) +#define RX_RES_STATUS_SEC_TYPE_NONE (0x0 << 8) +#define RX_RES_STATUS_SEC_TYPE_WEP (0x1 << 8) +#define RX_RES_STATUS_SEC_TYPE_CCMP (0x2 << 8) +#define RX_RES_STATUS_SEC_TYPE_TKIP (0x3 << 8) +#define RX_RES_STATUS_SEC_TYPE_ERR (0x7 << 8) + +#define RX_RES_STATUS_STATION_FOUND (1<<6) +#define RX_RES_STATUS_NO_STATION_INFO_MISMATCH (1<<7) + +#define RX_RES_STATUS_DECRYPT_TYPE_MSK (0x3 << 11) +#define RX_RES_STATUS_NOT_DECRYPT (0x0 << 11) +#define RX_RES_STATUS_DECRYPT_OK (0x3 << 11) +#define RX_RES_STATUS_BAD_ICV_MIC (0x1 << 11) +#define RX_RES_STATUS_BAD_KEY_TTAK (0x2 << 11) + +#define RX_MPDU_RES_STATUS_ICV_OK (0x20) +#define RX_MPDU_RES_STATUS_MIC_OK (0x40) +#define RX_MPDU_RES_STATUS_TTAK_OK (1 << 7) +#define RX_MPDU_RES_STATUS_DEC_DONE_MSK (0x800) + +struct il3945_rx_frame_stats { + u8 phy_count; + u8 id; + u8 rssi; + u8 agc; + __le16 sig_avg; + __le16 noise_diff; + u8 payload[0]; +} __packed; + +struct il3945_rx_frame_hdr { + __le16 channel; + __le16 phy_flags; + u8 reserved1; + u8 rate; + __le16 len; + u8 payload[0]; +} __packed; + +struct il3945_rx_frame_end { + __le32 status; + __le64 timestamp; + __le32 beacon_timestamp; +} __packed; + +/* + * N_3945_RX = 0x1b (response only, not a command) + * + * NOTE: DO NOT dereference from casts to this structure + * It is provided only for calculating minimum data set size. + * The actual offsets of the hdr and end are dynamic based on + * stats.phy_count + */ +struct il3945_rx_frame { + struct il3945_rx_frame_stats stats; + struct il3945_rx_frame_hdr hdr; + struct il3945_rx_frame_end end; +} __packed; + +#define IL39_RX_FRAME_SIZE (4 + sizeof(struct il3945_rx_frame)) + +/* Fixed (non-configurable) rx data from phy */ + +#define IL49_RX_RES_PHY_CNT 14 +#define IL49_RX_PHY_FLAGS_ANTENNAE_OFFSET (4) +#define IL49_RX_PHY_FLAGS_ANTENNAE_MASK (0x70) +#define IL49_AGC_DB_MASK (0x3f80) /* MASK(7,13) */ +#define IL49_AGC_DB_POS (7) +struct il4965_rx_non_cfg_phy { + __le16 ant_selection; /* ant A bit 4, ant B bit 5, ant C bit 6 */ + __le16 agc_info; /* agc code 0:6, agc dB 7:13, reserved 14:15 */ + u8 rssi_info[6]; /* we use even entries, 0/2/4 for A/B/C rssi */ + u8 pad[0]; +} __packed; + +/* + * N_RX = 0xc3 (response only, not a command) + * Used only for legacy (non 11n) frames. + */ +struct il_rx_phy_res { + u8 non_cfg_phy_cnt; /* non configurable DSP phy data byte count */ + u8 cfg_phy_cnt; /* configurable DSP phy data byte count */ + u8 stat_id; /* configurable DSP phy data set ID */ + u8 reserved1; + __le64 timestamp; /* TSF at on air rise */ + __le32 beacon_time_stamp; /* beacon at on-air rise */ + __le16 phy_flags; /* general phy flags: band, modulation, ... */ + __le16 channel; /* channel number */ + u8 non_cfg_phy_buf[32]; /* for various implementations of non_cfg_phy */ + __le32 rate_n_flags; /* RATE_MCS_* */ + __le16 byte_count; /* frame's byte-count */ + __le16 frame_time; /* frame's time on the air */ +} __packed; + +struct il_rx_mpdu_res_start { + __le16 byte_count; + __le16 reserved; +} __packed; + +/****************************************************************************** + * (5) + * Tx Commands & Responses: + * + * Driver must place each C_TX command into one of the prioritized Tx + * queues in host DRAM, shared between driver and device (see comments for + * SCD registers and Tx/Rx Queues). When the device's Tx scheduler and uCode + * are preparing to transmit, the device pulls the Tx command over the PCI + * bus via one of the device's Tx DMA channels, to fill an internal FIFO + * from which data will be transmitted. + * + * uCode handles all timing and protocol related to control frames + * (RTS/CTS/ACK), based on flags in the Tx command. uCode and Tx scheduler + * handle reception of block-acks; uCode updates the host driver via + * N_COMPRESSED_BA. + * + * uCode handles retrying Tx when an ACK is expected but not received. + * This includes trying lower data rates than the one requested in the Tx + * command, as set up by the C_RATE_SCALE (for 3945) or + * C_TX_LINK_QUALITY_CMD (4965). + * + * Driver sets up transmit power for various rates via C_TX_PWR_TBL. + * This command must be executed after every RXON command, before Tx can occur. + *****************************************************************************/ + +/* C_TX Tx flags field */ + +/* + * 1: Use Request-To-Send protocol before this frame. + * Mutually exclusive vs. TX_CMD_FLG_CTS_MSK. + */ +#define TX_CMD_FLG_RTS_MSK cpu_to_le32(1 << 1) + +/* + * 1: Transmit Clear-To-Send to self before this frame. + * Driver should set this for AUTH/DEAUTH/ASSOC-REQ/REASSOC mgmnt frames. + * Mutually exclusive vs. TX_CMD_FLG_RTS_MSK. + */ +#define TX_CMD_FLG_CTS_MSK cpu_to_le32(1 << 2) + +/* 1: Expect ACK from receiving station + * 0: Don't expect ACK (MAC header's duration field s/b 0) + * Set this for unicast frames, but not broadcast/multicast. */ +#define TX_CMD_FLG_ACK_MSK cpu_to_le32(1 << 3) + +/* For 4965 devices: + * 1: Use rate scale table (see C_TX_LINK_QUALITY_CMD). + * Tx command's initial_rate_idx indicates first rate to try; + * uCode walks through table for additional Tx attempts. + * 0: Use Tx rate/MCS from Tx command's rate_n_flags field. + * This rate will be used for all Tx attempts; it will not be scaled. */ +#define TX_CMD_FLG_STA_RATE_MSK cpu_to_le32(1 << 4) + +/* 1: Expect immediate block-ack. + * Set when Txing a block-ack request frame. Also set TX_CMD_FLG_ACK_MSK. */ +#define TX_CMD_FLG_IMM_BA_RSP_MASK cpu_to_le32(1 << 6) + +/* + * 1: Frame requires full Tx-Op protection. + * Set this if either RTS or CTS Tx Flag gets set. + */ +#define TX_CMD_FLG_FULL_TXOP_PROT_MSK cpu_to_le32(1 << 7) + +/* Tx antenna selection field; used only for 3945, reserved (0) for 4965 devices. + * Set field to "0" to allow 3945 uCode to select antenna (normal usage). */ +#define TX_CMD_FLG_ANT_SEL_MSK cpu_to_le32(0xf00) +#define TX_CMD_FLG_ANT_A_MSK cpu_to_le32(1 << 8) +#define TX_CMD_FLG_ANT_B_MSK cpu_to_le32(1 << 9) + +/* 1: uCode overrides sequence control field in MAC header. + * 0: Driver provides sequence control field in MAC header. + * Set this for management frames, non-QOS data frames, non-unicast frames, + * and also in Tx command embedded in C_SCAN for active scans. */ +#define TX_CMD_FLG_SEQ_CTL_MSK cpu_to_le32(1 << 13) + +/* 1: This frame is non-last MPDU; more fragments are coming. + * 0: Last fragment, or not using fragmentation. */ +#define TX_CMD_FLG_MORE_FRAG_MSK cpu_to_le32(1 << 14) + +/* 1: uCode calculates and inserts Timestamp Function (TSF) in outgoing frame. + * 0: No TSF required in outgoing frame. + * Set this for transmitting beacons and probe responses. */ +#define TX_CMD_FLG_TSF_MSK cpu_to_le32(1 << 16) + +/* 1: Driver inserted 2 bytes pad after the MAC header, for (required) dword + * alignment of frame's payload data field. + * 0: No pad + * Set this for MAC headers with 26 or 30 bytes, i.e. those with QOS or ADDR4 + * field (but not both). Driver must align frame data (i.e. data following + * MAC header) to DWORD boundary. */ +#define TX_CMD_FLG_MH_PAD_MSK cpu_to_le32(1 << 20) + +/* accelerate aggregation support + * 0 - no CCMP encryption; 1 - CCMP encryption */ +#define TX_CMD_FLG_AGG_CCMP_MSK cpu_to_le32(1 << 22) + +/* HCCA-AP - disable duration overwriting. */ +#define TX_CMD_FLG_DUR_MSK cpu_to_le32(1 << 25) + +/* + * TX command security control + */ +#define TX_CMD_SEC_WEP 0x01 +#define TX_CMD_SEC_CCM 0x02 +#define TX_CMD_SEC_TKIP 0x03 +#define TX_CMD_SEC_MSK 0x03 +#define TX_CMD_SEC_SHIFT 6 +#define TX_CMD_SEC_KEY128 0x08 + +/* + * security overhead sizes + */ +#define WEP_IV_LEN 4 +#define WEP_ICV_LEN 4 +#define CCMP_MIC_LEN 8 +#define TKIP_ICV_LEN 4 + +/* + * C_TX = 0x1c (command) + */ + +struct il3945_tx_cmd { + /* + * MPDU byte count: + * MAC header (24/26/30/32 bytes) + 2 bytes pad if 26/30 header size, + * + 8 byte IV for CCM or TKIP (not used for WEP) + * + Data payload + * + 8-byte MIC (not used for CCM/WEP) + * NOTE: Does not include Tx command bytes, post-MAC pad bytes, + * MIC (CCM) 8 bytes, ICV (WEP/TKIP/CKIP) 4 bytes, CRC 4 bytes.i + * Range: 14-2342 bytes. + */ + __le16 len; + + /* + * MPDU or MSDU byte count for next frame. + * Used for fragmentation and bursting, but not 11n aggregation. + * Same as "len", but for next frame. Set to 0 if not applicable. + */ + __le16 next_frame_len; + + __le32 tx_flags; /* TX_CMD_FLG_* */ + + u8 rate; + + /* Index of recipient station in uCode's station table */ + u8 sta_id; + u8 tid_tspec; + u8 sec_ctl; + u8 key[16]; + union { + u8 byte[8]; + __le16 word[4]; + __le32 dw[2]; + } tkip_mic; + __le32 next_frame_info; + union { + __le32 life_time; + __le32 attempt; + } stop_time; + u8 supp_rates[2]; + u8 rts_retry_limit; /*byte 50 */ + u8 data_retry_limit; /*byte 51 */ + union { + __le16 pm_frame_timeout; + __le16 attempt_duration; + } timeout; + + /* + * Duration of EDCA burst Tx Opportunity, in 32-usec units. + * Set this if txop time is not specified by HCCA protocol (e.g. by AP). + */ + __le16 driver_txop; + + /* + * MAC header goes here, followed by 2 bytes padding if MAC header + * length is 26 or 30 bytes, followed by payload data + */ + u8 payload[0]; + struct ieee80211_hdr hdr[0]; +} __packed; + +/* + * C_TX = 0x1c (response) + */ +struct il3945_tx_resp { + u8 failure_rts; + u8 failure_frame; + u8 bt_kill_count; + u8 rate; + __le32 wireless_media_time; + __le32 status; /* TX status */ +} __packed; + +/* + * 4965 uCode updates these Tx attempt count values in host DRAM. + * Used for managing Tx retries when expecting block-acks. + * Driver should set these fields to 0. + */ +struct il_dram_scratch { + u8 try_cnt; /* Tx attempts */ + u8 bt_kill_cnt; /* Tx attempts blocked by Bluetooth device */ + __le16 reserved; +} __packed; + +struct il_tx_cmd { + /* + * MPDU byte count: + * MAC header (24/26/30/32 bytes) + 2 bytes pad if 26/30 header size, + * + 8 byte IV for CCM or TKIP (not used for WEP) + * + Data payload + * + 8-byte MIC (not used for CCM/WEP) + * NOTE: Does not include Tx command bytes, post-MAC pad bytes, + * MIC (CCM) 8 bytes, ICV (WEP/TKIP/CKIP) 4 bytes, CRC 4 bytes.i + * Range: 14-2342 bytes. + */ + __le16 len; + + /* + * MPDU or MSDU byte count for next frame. + * Used for fragmentation and bursting, but not 11n aggregation. + * Same as "len", but for next frame. Set to 0 if not applicable. + */ + __le16 next_frame_len; + + __le32 tx_flags; /* TX_CMD_FLG_* */ + + /* uCode may modify this field of the Tx command (in host DRAM!). + * Driver must also set dram_lsb_ptr and dram_msb_ptr in this cmd. */ + struct il_dram_scratch scratch; + + /* Rate for *all* Tx attempts, if TX_CMD_FLG_STA_RATE_MSK is cleared. */ + __le32 rate_n_flags; /* RATE_MCS_* */ + + /* Index of destination station in uCode's station table */ + u8 sta_id; + + /* Type of security encryption: CCM or TKIP */ + u8 sec_ctl; /* TX_CMD_SEC_* */ + + /* + * Index into rate table (see C_TX_LINK_QUALITY_CMD) for initial + * Tx attempt, if TX_CMD_FLG_STA_RATE_MSK is set. Normally "0" for + * data frames, this field may be used to selectively reduce initial + * rate (via non-0 value) for special frames (e.g. management), while + * still supporting rate scaling for all frames. + */ + u8 initial_rate_idx; + u8 reserved; + u8 key[16]; + __le16 next_frame_flags; + __le16 reserved2; + union { + __le32 life_time; + __le32 attempt; + } stop_time; + + /* Host DRAM physical address pointer to "scratch" in this command. + * Must be dword aligned. "0" in dram_lsb_ptr disables usage. */ + __le32 dram_lsb_ptr; + u8 dram_msb_ptr; + + u8 rts_retry_limit; /*byte 50 */ + u8 data_retry_limit; /*byte 51 */ + u8 tid_tspec; + union { + __le16 pm_frame_timeout; + __le16 attempt_duration; + } timeout; + + /* + * Duration of EDCA burst Tx Opportunity, in 32-usec units. + * Set this if txop time is not specified by HCCA protocol (e.g. by AP). + */ + __le16 driver_txop; + + /* + * MAC header goes here, followed by 2 bytes padding if MAC header + * length is 26 or 30 bytes, followed by payload data + */ + u8 payload[0]; + struct ieee80211_hdr hdr[0]; +} __packed; + +/* TX command response is sent after *3945* transmission attempts. + * + * NOTES: + * + * TX_STATUS_FAIL_NEXT_FRAG + * + * If the fragment flag in the MAC header for the frame being transmitted + * is set and there is insufficient time to transmit the next frame, the + * TX status will be returned with 'TX_STATUS_FAIL_NEXT_FRAG'. + * + * TX_STATUS_FIFO_UNDERRUN + * + * Indicates the host did not provide bytes to the FIFO fast enough while + * a TX was in progress. + * + * TX_STATUS_FAIL_MGMNT_ABORT + * + * This status is only possible if the ABORT ON MGMT RX parameter was + * set to true with the TX command. + * + * If the MSB of the status parameter is set then an abort sequence is + * required. This sequence consists of the host activating the TX Abort + * control line, and then waiting for the TX Abort command response. This + * indicates that a the device is no longer in a transmit state, and that the + * command FIFO has been cleared. The host must then deactivate the TX Abort + * control line. Receiving is still allowed in this case. + */ +enum { + TX_3945_STATUS_SUCCESS = 0x01, + TX_3945_STATUS_DIRECT_DONE = 0x02, + TX_3945_STATUS_FAIL_SHORT_LIMIT = 0x82, + TX_3945_STATUS_FAIL_LONG_LIMIT = 0x83, + TX_3945_STATUS_FAIL_FIFO_UNDERRUN = 0x84, + TX_3945_STATUS_FAIL_MGMNT_ABORT = 0x85, + TX_3945_STATUS_FAIL_NEXT_FRAG = 0x86, + TX_3945_STATUS_FAIL_LIFE_EXPIRE = 0x87, + TX_3945_STATUS_FAIL_DEST_PS = 0x88, + TX_3945_STATUS_FAIL_ABORTED = 0x89, + TX_3945_STATUS_FAIL_BT_RETRY = 0x8a, + TX_3945_STATUS_FAIL_STA_INVALID = 0x8b, + TX_3945_STATUS_FAIL_FRAG_DROPPED = 0x8c, + TX_3945_STATUS_FAIL_TID_DISABLE = 0x8d, + TX_3945_STATUS_FAIL_FRAME_FLUSHED = 0x8e, + TX_3945_STATUS_FAIL_INSUFFICIENT_CF_POLL = 0x8f, + TX_3945_STATUS_FAIL_TX_LOCKED = 0x90, + TX_3945_STATUS_FAIL_NO_BEACON_ON_RADAR = 0x91, +}; + +/* + * TX command response is sent after *4965* transmission attempts. + * + * both postpone and abort status are expected behavior from uCode. there is + * no special operation required from driver; except for RFKILL_FLUSH, + * which required tx flush host command to flush all the tx frames in queues + */ +enum { + TX_STATUS_SUCCESS = 0x01, + TX_STATUS_DIRECT_DONE = 0x02, + /* postpone TX */ + TX_STATUS_POSTPONE_DELAY = 0x40, + TX_STATUS_POSTPONE_FEW_BYTES = 0x41, + TX_STATUS_POSTPONE_QUIET_PERIOD = 0x43, + TX_STATUS_POSTPONE_CALC_TTAK = 0x44, + /* abort TX */ + TX_STATUS_FAIL_INTERNAL_CROSSED_RETRY = 0x81, + TX_STATUS_FAIL_SHORT_LIMIT = 0x82, + TX_STATUS_FAIL_LONG_LIMIT = 0x83, + TX_STATUS_FAIL_FIFO_UNDERRUN = 0x84, + TX_STATUS_FAIL_DRAIN_FLOW = 0x85, + TX_STATUS_FAIL_RFKILL_FLUSH = 0x86, + TX_STATUS_FAIL_LIFE_EXPIRE = 0x87, + TX_STATUS_FAIL_DEST_PS = 0x88, + TX_STATUS_FAIL_HOST_ABORTED = 0x89, + TX_STATUS_FAIL_BT_RETRY = 0x8a, + TX_STATUS_FAIL_STA_INVALID = 0x8b, + TX_STATUS_FAIL_FRAG_DROPPED = 0x8c, + TX_STATUS_FAIL_TID_DISABLE = 0x8d, + TX_STATUS_FAIL_FIFO_FLUSHED = 0x8e, + TX_STATUS_FAIL_INSUFFICIENT_CF_POLL = 0x8f, + TX_STATUS_FAIL_PASSIVE_NO_RX = 0x90, + TX_STATUS_FAIL_NO_BEACON_ON_RADAR = 0x91, +}; + +#define TX_PACKET_MODE_REGULAR 0x0000 +#define TX_PACKET_MODE_BURST_SEQ 0x0100 +#define TX_PACKET_MODE_BURST_FIRST 0x0200 + +enum { + TX_POWER_PA_NOT_ACTIVE = 0x0, +}; + +enum { + TX_STATUS_MSK = 0x000000ff, /* bits 0:7 */ + TX_STATUS_DELAY_MSK = 0x00000040, + TX_STATUS_ABORT_MSK = 0x00000080, + TX_PACKET_MODE_MSK = 0x0000ff00, /* bits 8:15 */ + TX_FIFO_NUMBER_MSK = 0x00070000, /* bits 16:18 */ + TX_RESERVED = 0x00780000, /* bits 19:22 */ + TX_POWER_PA_DETECT_MSK = 0x7f800000, /* bits 23:30 */ + TX_ABORT_REQUIRED_MSK = 0x80000000, /* bits 31:31 */ +}; + +/* ******************************* + * TX aggregation status + ******************************* */ + +enum { + AGG_TX_STATE_TRANSMITTED = 0x00, + AGG_TX_STATE_UNDERRUN_MSK = 0x01, + AGG_TX_STATE_FEW_BYTES_MSK = 0x04, + AGG_TX_STATE_ABORT_MSK = 0x08, + AGG_TX_STATE_LAST_SENT_TTL_MSK = 0x10, + AGG_TX_STATE_LAST_SENT_TRY_CNT_MSK = 0x20, + AGG_TX_STATE_SCD_QUERY_MSK = 0x80, + AGG_TX_STATE_TEST_BAD_CRC32_MSK = 0x100, + AGG_TX_STATE_RESPONSE_MSK = 0x1ff, + AGG_TX_STATE_DUMP_TX_MSK = 0x200, + AGG_TX_STATE_DELAY_TX_MSK = 0x400 +}; + +#define AGG_TX_STATUS_MSK 0x00000fff /* bits 0:11 */ +#define AGG_TX_TRY_MSK 0x0000f000 /* bits 12:15 */ + +#define AGG_TX_STATE_LAST_SENT_MSK (AGG_TX_STATE_LAST_SENT_TTL_MSK | \ + AGG_TX_STATE_LAST_SENT_TRY_CNT_MSK) + +/* # tx attempts for first frame in aggregation */ +#define AGG_TX_STATE_TRY_CNT_POS 12 +#define AGG_TX_STATE_TRY_CNT_MSK 0xf000 + +/* Command ID and sequence number of Tx command for this frame */ +#define AGG_TX_STATE_SEQ_NUM_POS 16 +#define AGG_TX_STATE_SEQ_NUM_MSK 0xffff0000 + +/* + * C_TX = 0x1c (response) + * + * This response may be in one of two slightly different formats, indicated + * by the frame_count field: + * + * 1) No aggregation (frame_count == 1). This reports Tx results for + * a single frame. Multiple attempts, at various bit rates, may have + * been made for this frame. + * + * 2) Aggregation (frame_count > 1). This reports Tx results for + * 2 or more frames that used block-acknowledge. All frames were + * transmitted at same rate. Rate scaling may have been used if first + * frame in this new agg block failed in previous agg block(s). + * + * Note that, for aggregation, ACK (block-ack) status is not delivered here; + * block-ack has not been received by the time the 4965 device records + * this status. + * This status relates to reasons the tx might have been blocked or aborted + * within the sending station (this 4965 device), rather than whether it was + * received successfully by the destination station. + */ +struct agg_tx_status { + __le16 status; + __le16 sequence; +} __packed; + +struct il4965_tx_resp { + u8 frame_count; /* 1 no aggregation, >1 aggregation */ + u8 bt_kill_count; /* # blocked by bluetooth (unused for agg) */ + u8 failure_rts; /* # failures due to unsuccessful RTS */ + u8 failure_frame; /* # failures due to no ACK (unused for agg) */ + + /* For non-agg: Rate at which frame was successful. + * For agg: Rate at which all frames were transmitted. */ + __le32 rate_n_flags; /* RATE_MCS_* */ + + /* For non-agg: RTS + CTS + frame tx attempts time + ACK. + * For agg: RTS + CTS + aggregation tx time + block-ack time. */ + __le16 wireless_media_time; /* uSecs */ + + __le16 reserved; + __le32 pa_power1; /* RF power amplifier measurement (not used) */ + __le32 pa_power2; + + /* + * For non-agg: frame status TX_STATUS_* + * For agg: status of 1st frame, AGG_TX_STATE_*; other frame status + * fields follow this one, up to frame_count. + * Bit fields: + * 11- 0: AGG_TX_STATE_* status code + * 15-12: Retry count for 1st frame in aggregation (retries + * occur if tx failed for this frame when it was a + * member of a previous aggregation block). If rate + * scaling is used, retry count indicates the rate + * table entry used for all frames in the new agg. + * 31-16: Sequence # for this frame's Tx cmd (not SSN!) + */ + union { + __le32 status; + struct agg_tx_status agg_status[0]; /* for each agg frame */ + } u; +} __packed; + +/* + * N_COMPRESSED_BA = 0xc5 (response only, not a command) + * + * Reports Block-Acknowledge from recipient station + */ +struct il_compressed_ba_resp { + __le32 sta_addr_lo32; + __le16 sta_addr_hi16; + __le16 reserved; + + /* Index of recipient (BA-sending) station in uCode's station table */ + u8 sta_id; + u8 tid; + __le16 seq_ctl; + __le64 bitmap; + __le16 scd_flow; + __le16 scd_ssn; +} __packed; + +/* + * C_TX_PWR_TBL = 0x97 (command, has simple generic response) + * + * See details under "TXPOWER" in 4965.h. + */ + +struct il3945_txpowertable_cmd { + u8 band; /* 0: 5 GHz, 1: 2.4 GHz */ + u8 reserved; + __le16 channel; + struct il3945_power_per_rate power[IL_MAX_RATES]; +} __packed; + +struct il4965_txpowertable_cmd { + u8 band; /* 0: 5 GHz, 1: 2.4 GHz */ + u8 reserved; + __le16 channel; + struct il4965_tx_power_db tx_power; +} __packed; + +/** + * struct il3945_rate_scaling_cmd - Rate Scaling Command & Response + * + * C_RATE_SCALE = 0x47 (command, has simple generic response) + * + * NOTE: The table of rates passed to the uCode via the + * RATE_SCALE command sets up the corresponding order of + * rates used for all related commands, including rate + * masks, etc. + * + * For example, if you set 9MB (PLCP 0x0f) as the first + * rate in the rate table, the bit mask for that rate + * when passed through ofdm_basic_rates on the C_RXON + * command would be bit 0 (1 << 0) + */ +struct il3945_rate_scaling_info { + __le16 rate_n_flags; + u8 try_cnt; + u8 next_rate_idx; +} __packed; + +struct il3945_rate_scaling_cmd { + u8 table_id; + u8 reserved[3]; + struct il3945_rate_scaling_info table[IL_MAX_RATES]; +} __packed; + +/*RS_NEW_API: only TLC_RTS remains and moved to bit 0 */ +#define LINK_QUAL_FLAGS_SET_STA_TLC_RTS_MSK (1 << 0) + +/* # of EDCA prioritized tx fifos */ +#define LINK_QUAL_AC_NUM AC_NUM + +/* # entries in rate scale table to support Tx retries */ +#define LINK_QUAL_MAX_RETRY_NUM 16 + +/* Tx antenna selection values */ +#define LINK_QUAL_ANT_A_MSK (1 << 0) +#define LINK_QUAL_ANT_B_MSK (1 << 1) +#define LINK_QUAL_ANT_MSK (LINK_QUAL_ANT_A_MSK|LINK_QUAL_ANT_B_MSK) + +/** + * struct il_link_qual_general_params + * + * Used in C_TX_LINK_QUALITY_CMD + */ +struct il_link_qual_general_params { + u8 flags; + + /* No entries at or above this (driver chosen) idx contain MIMO */ + u8 mimo_delimiter; + + /* Best single antenna to use for single stream (legacy, SISO). */ + u8 single_stream_ant_msk; /* LINK_QUAL_ANT_* */ + + /* Best antennas to use for MIMO (unused for 4965, assumes both). */ + u8 dual_stream_ant_msk; /* LINK_QUAL_ANT_* */ + + /* + * If driver needs to use different initial rates for different + * EDCA QOS access categories (as implemented by tx fifos 0-3), + * this table will set that up, by indicating the idxes in the + * rs_table[LINK_QUAL_MAX_RETRY_NUM] rate table at which to start. + * Otherwise, driver should set all entries to 0. + * + * Entry usage: + * 0 = Background, 1 = Best Effort (normal), 2 = Video, 3 = Voice + * TX FIFOs above 3 use same value (typically 0) as TX FIFO 3. + */ + u8 start_rate_idx[LINK_QUAL_AC_NUM]; +} __packed; + +#define LINK_QUAL_AGG_TIME_LIMIT_DEF (4000) /* 4 milliseconds */ +#define LINK_QUAL_AGG_TIME_LIMIT_MAX (8000) +#define LINK_QUAL_AGG_TIME_LIMIT_MIN (100) + +#define LINK_QUAL_AGG_DISABLE_START_DEF (3) +#define LINK_QUAL_AGG_DISABLE_START_MAX (255) +#define LINK_QUAL_AGG_DISABLE_START_MIN (0) + +#define LINK_QUAL_AGG_FRAME_LIMIT_DEF (31) +#define LINK_QUAL_AGG_FRAME_LIMIT_MAX (63) +#define LINK_QUAL_AGG_FRAME_LIMIT_MIN (0) + +/** + * struct il_link_qual_agg_params + * + * Used in C_TX_LINK_QUALITY_CMD + */ +struct il_link_qual_agg_params { + + /* + *Maximum number of uSec in aggregation. + * default set to 4000 (4 milliseconds) if not configured in .cfg + */ + __le16 agg_time_limit; + + /* + * Number of Tx retries allowed for a frame, before that frame will + * no longer be considered for the start of an aggregation sequence + * (scheduler will then try to tx it as single frame). + * Driver should set this to 3. + */ + u8 agg_dis_start_th; + + /* + * Maximum number of frames in aggregation. + * 0 = no limit (default). 1 = no aggregation. + * Other values = max # frames in aggregation. + */ + u8 agg_frame_cnt_limit; + + __le32 reserved; +} __packed; + +/* + * C_TX_LINK_QUALITY_CMD = 0x4e (command, has simple generic response) + * + * For 4965 devices only; 3945 uses C_RATE_SCALE. + * + * Each station in the 4965 device's internal station table has its own table + * of 16 + * Tx rates and modulation modes (e.g. legacy/SISO/MIMO) for retrying Tx when + * an ACK is not received. This command replaces the entire table for + * one station. + * + * NOTE: Station must already be in 4965 device's station table. + * Use C_ADD_STA. + * + * The rate scaling procedures described below work well. Of course, other + * procedures are possible, and may work better for particular environments. + * + * + * FILLING THE RATE TBL + * + * Given a particular initial rate and mode, as determined by the rate + * scaling algorithm described below, the Linux driver uses the following + * formula to fill the rs_table[LINK_QUAL_MAX_RETRY_NUM] rate table in the + * Link Quality command: + * + * + * 1) If using High-throughput (HT) (SISO or MIMO) initial rate: + * a) Use this same initial rate for first 3 entries. + * b) Find next lower available rate using same mode (SISO or MIMO), + * use for next 3 entries. If no lower rate available, switch to + * legacy mode (no HT40 channel, no MIMO, no short guard interval). + * c) If using MIMO, set command's mimo_delimiter to number of entries + * using MIMO (3 or 6). + * d) After trying 2 HT rates, switch to legacy mode (no HT40 channel, + * no MIMO, no short guard interval), at the next lower bit rate + * (e.g. if second HT bit rate was 54, try 48 legacy), and follow + * legacy procedure for remaining table entries. + * + * 2) If using legacy initial rate: + * a) Use the initial rate for only one entry. + * b) For each following entry, reduce the rate to next lower available + * rate, until reaching the lowest available rate. + * c) When reducing rate, also switch antenna selection. + * d) Once lowest available rate is reached, repeat this rate until + * rate table is filled (16 entries), switching antenna each entry. + * + * + * ACCUMULATING HISTORY + * + * The rate scaling algorithm for 4965 devices, as implemented in Linux driver, + * uses two sets of frame Tx success history: One for the current/active + * modulation mode, and one for a speculative/search mode that is being + * attempted. If the speculative mode turns out to be more effective (i.e. + * actual transfer rate is better), then the driver continues to use the + * speculative mode as the new current active mode. + * + * Each history set contains, separately for each possible rate, data for a + * sliding win of the 62 most recent tx attempts at that rate. The data + * includes a shifting bitmap of success(1)/failure(0), and sums of successful + * and attempted frames, from which the driver can additionally calculate a + * success ratio (success / attempted) and number of failures + * (attempted - success), and control the size of the win (attempted). + * The driver uses the bit map to remove successes from the success sum, as + * the oldest tx attempts fall out of the win. + * + * When the 4965 device makes multiple tx attempts for a given frame, each + * attempt might be at a different rate, and have different modulation + * characteristics (e.g. antenna, fat channel, short guard interval), as set + * up in the rate scaling table in the Link Quality command. The driver must + * determine which rate table entry was used for each tx attempt, to determine + * which rate-specific history to update, and record only those attempts that + * match the modulation characteristics of the history set. + * + * When using block-ack (aggregation), all frames are transmitted at the same + * rate, since there is no per-attempt acknowledgment from the destination + * station. The Tx response struct il_tx_resp indicates the Tx rate in + * rate_n_flags field. After receiving a block-ack, the driver can update + * history for the entire block all at once. + * + * + * FINDING BEST STARTING RATE: + * + * When working with a selected initial modulation mode (see below), the + * driver attempts to find a best initial rate. The initial rate is the + * first entry in the Link Quality command's rate table. + * + * 1) Calculate actual throughput (success ratio * expected throughput, see + * table below) for current initial rate. Do this only if enough frames + * have been attempted to make the value meaningful: at least 6 failed + * tx attempts, or at least 8 successes. If not enough, don't try rate + * scaling yet. + * + * 2) Find available rates adjacent to current initial rate. Available means: + * a) supported by hardware && + * b) supported by association && + * c) within any constraints selected by user + * + * 3) Gather measured throughputs for adjacent rates. These might not have + * enough history to calculate a throughput. That's okay, we might try + * using one of them anyway! + * + * 4) Try decreasing rate if, for current rate: + * a) success ratio is < 15% || + * b) lower adjacent rate has better measured throughput || + * c) higher adjacent rate has worse throughput, and lower is unmeasured + * + * As a sanity check, if decrease was determined above, leave rate + * unchanged if: + * a) lower rate unavailable + * b) success ratio at current rate > 85% (very good) + * c) current measured throughput is better than expected throughput + * of lower rate (under perfect 100% tx conditions, see table below) + * + * 5) Try increasing rate if, for current rate: + * a) success ratio is < 15% || + * b) both adjacent rates' throughputs are unmeasured (try it!) || + * b) higher adjacent rate has better measured throughput || + * c) lower adjacent rate has worse throughput, and higher is unmeasured + * + * As a sanity check, if increase was determined above, leave rate + * unchanged if: + * a) success ratio at current rate < 70%. This is not particularly + * good performance; higher rate is sure to have poorer success. + * + * 6) Re-evaluate the rate after each tx frame. If working with block- + * acknowledge, history and stats may be calculated for the entire + * block (including prior history that fits within the history wins), + * before re-evaluation. + * + * FINDING BEST STARTING MODULATION MODE: + * + * After working with a modulation mode for a "while" (and doing rate scaling), + * the driver searches for a new initial mode in an attempt to improve + * throughput. The "while" is measured by numbers of attempted frames: + * + * For legacy mode, search for new mode after: + * 480 successful frames, or 160 failed frames + * For high-throughput modes (SISO or MIMO), search for new mode after: + * 4500 successful frames, or 400 failed frames + * + * Mode switch possibilities are (3 for each mode): + * + * For legacy: + * Change antenna, try SISO (if HT association), try MIMO (if HT association) + * For SISO: + * Change antenna, try MIMO, try shortened guard interval (SGI) + * For MIMO: + * Try SISO antenna A, SISO antenna B, try shortened guard interval (SGI) + * + * When trying a new mode, use the same bit rate as the old/current mode when + * trying antenna switches and shortened guard interval. When switching to + * SISO from MIMO or legacy, or to MIMO from SISO or legacy, use a rate + * for which the expected throughput (under perfect conditions) is about the + * same or slightly better than the actual measured throughput delivered by + * the old/current mode. + * + * Actual throughput can be estimated by multiplying the expected throughput + * by the success ratio (successful / attempted tx frames). Frame size is + * not considered in this calculation; it assumes that frame size will average + * out to be fairly consistent over several samples. The following are + * metric values for expected throughput assuming 100% success ratio. + * Only G band has support for CCK rates: + * + * RATE: 1 2 5 11 6 9 12 18 24 36 48 54 60 + * + * G: 7 13 35 58 40 57 72 98 121 154 177 186 186 + * A: 0 0 0 0 40 57 72 98 121 154 177 186 186 + * SISO 20MHz: 0 0 0 0 42 42 76 102 124 159 183 193 202 + * SGI SISO 20MHz: 0 0 0 0 46 46 82 110 132 168 192 202 211 + * MIMO 20MHz: 0 0 0 0 74 74 123 155 179 214 236 244 251 + * SGI MIMO 20MHz: 0 0 0 0 81 81 131 164 188 222 243 251 257 + * SISO 40MHz: 0 0 0 0 77 77 127 160 184 220 242 250 257 + * SGI SISO 40MHz: 0 0 0 0 83 83 135 169 193 229 250 257 264 + * MIMO 40MHz: 0 0 0 0 123 123 182 214 235 264 279 285 289 + * SGI MIMO 40MHz: 0 0 0 0 131 131 191 222 242 270 284 289 293 + * + * After the new mode has been tried for a short while (minimum of 6 failed + * frames or 8 successful frames), compare success ratio and actual throughput + * estimate of the new mode with the old. If either is better with the new + * mode, continue to use the new mode. + * + * Continue comparing modes until all 3 possibilities have been tried. + * If moving from legacy to HT, try all 3 possibilities from the new HT + * mode. After trying all 3, a best mode is found. Continue to use this mode + * for the longer "while" described above (e.g. 480 successful frames for + * legacy), and then repeat the search process. + * + */ +struct il_link_quality_cmd { + + /* Index of destination/recipient station in uCode's station table */ + u8 sta_id; + u8 reserved1; + __le16 control; /* not used */ + struct il_link_qual_general_params general_params; + struct il_link_qual_agg_params agg_params; + + /* + * Rate info; when using rate-scaling, Tx command's initial_rate_idx + * specifies 1st Tx rate attempted, via idx into this table. + * 4965 devices works its way through table when retrying Tx. + */ + struct { + __le32 rate_n_flags; /* RATE_MCS_*, RATE_* */ + } rs_table[LINK_QUAL_MAX_RETRY_NUM]; + __le32 reserved2; +} __packed; + +/* + * BT configuration enable flags: + * bit 0 - 1: BT channel announcement enabled + * 0: disable + * bit 1 - 1: priority of BT device enabled + * 0: disable + */ +#define BT_COEX_DISABLE (0x0) +#define BT_ENABLE_CHANNEL_ANNOUNCE BIT(0) +#define BT_ENABLE_PRIORITY BIT(1) + +#define BT_COEX_ENABLE (BT_ENABLE_CHANNEL_ANNOUNCE | BT_ENABLE_PRIORITY) + +#define BT_LEAD_TIME_DEF (0x1E) + +#define BT_MAX_KILL_DEF (0x5) + +/* + * C_BT_CONFIG = 0x9b (command, has simple generic response) + * + * 3945 and 4965 devices support hardware handshake with Bluetooth device on + * same platform. Bluetooth device alerts wireless device when it will Tx; + * wireless device can delay or kill its own Tx to accommodate. + */ +struct il_bt_cmd { + u8 flags; + u8 lead_time; + u8 max_kill; + u8 reserved; + __le32 kill_ack_mask; + __le32 kill_cts_mask; +} __packed; + +/****************************************************************************** + * (6) + * Spectrum Management (802.11h) Commands, Responses, Notifications: + * + *****************************************************************************/ + +/* + * Spectrum Management + */ +#define MEASUREMENT_FILTER_FLAG (RXON_FILTER_PROMISC_MSK | \ + RXON_FILTER_CTL2HOST_MSK | \ + RXON_FILTER_ACCEPT_GRP_MSK | \ + RXON_FILTER_DIS_DECRYPT_MSK | \ + RXON_FILTER_DIS_GRP_DECRYPT_MSK | \ + RXON_FILTER_ASSOC_MSK | \ + RXON_FILTER_BCON_AWARE_MSK) + +struct il_measure_channel { + __le32 duration; /* measurement duration in extended beacon + * format */ + u8 channel; /* channel to measure */ + u8 type; /* see enum il_measure_type */ + __le16 reserved; +} __packed; + +/* + * C_SPECTRUM_MEASUREMENT = 0x74 (command) + */ +struct il_spectrum_cmd { + __le16 len; /* number of bytes starting from token */ + u8 token; /* token id */ + u8 id; /* measurement id -- 0 or 1 */ + u8 origin; /* 0 = TGh, 1 = other, 2 = TGk */ + u8 periodic; /* 1 = periodic */ + __le16 path_loss_timeout; + __le32 start_time; /* start time in extended beacon format */ + __le32 reserved2; + __le32 flags; /* rxon flags */ + __le32 filter_flags; /* rxon filter flags */ + __le16 channel_count; /* minimum 1, maximum 10 */ + __le16 reserved3; + struct il_measure_channel channels[10]; +} __packed; + +/* + * C_SPECTRUM_MEASUREMENT = 0x74 (response) + */ +struct il_spectrum_resp { + u8 token; + u8 id; /* id of the prior command replaced, or 0xff */ + __le16 status; /* 0 - command will be handled + * 1 - cannot handle (conflicts with another + * measurement) */ +} __packed; + +enum il_measurement_state { + IL_MEASUREMENT_START = 0, + IL_MEASUREMENT_STOP = 1, +}; + +enum il_measurement_status { + IL_MEASUREMENT_OK = 0, + IL_MEASUREMENT_CONCURRENT = 1, + IL_MEASUREMENT_CSA_CONFLICT = 2, + IL_MEASUREMENT_TGH_CONFLICT = 3, + /* 4-5 reserved */ + IL_MEASUREMENT_STOPPED = 6, + IL_MEASUREMENT_TIMEOUT = 7, + IL_MEASUREMENT_PERIODIC_FAILED = 8, +}; + +#define NUM_ELEMENTS_IN_HISTOGRAM 8 + +struct il_measurement_histogram { + __le32 ofdm[NUM_ELEMENTS_IN_HISTOGRAM]; /* in 0.8usec counts */ + __le32 cck[NUM_ELEMENTS_IN_HISTOGRAM]; /* in 1usec counts */ +} __packed; + +/* clear channel availability counters */ +struct il_measurement_cca_counters { + __le32 ofdm; + __le32 cck; +} __packed; + +enum il_measure_type { + IL_MEASURE_BASIC = (1 << 0), + IL_MEASURE_CHANNEL_LOAD = (1 << 1), + IL_MEASURE_HISTOGRAM_RPI = (1 << 2), + IL_MEASURE_HISTOGRAM_NOISE = (1 << 3), + IL_MEASURE_FRAME = (1 << 4), + /* bits 5:6 are reserved */ + IL_MEASURE_IDLE = (1 << 7), +}; + +/* + * N_SPECTRUM_MEASUREMENT = 0x75 (notification only, not a command) + */ +struct il_spectrum_notification { + u8 id; /* measurement id -- 0 or 1 */ + u8 token; + u8 channel_idx; /* idx in measurement channel list */ + u8 state; /* 0 - start, 1 - stop */ + __le32 start_time; /* lower 32-bits of TSF */ + u8 band; /* 0 - 5.2GHz, 1 - 2.4GHz */ + u8 channel; + u8 type; /* see enum il_measurement_type */ + u8 reserved1; + /* NOTE: cca_ofdm, cca_cck, basic_type, and histogram are only only + * valid if applicable for measurement type requested. */ + __le32 cca_ofdm; /* cca fraction time in 40Mhz clock periods */ + __le32 cca_cck; /* cca fraction time in 44Mhz clock periods */ + __le32 cca_time; /* channel load time in usecs */ + u8 basic_type; /* 0 - bss, 1 - ofdm preamble, 2 - + * unidentified */ + u8 reserved2[3]; + struct il_measurement_histogram histogram; + __le32 stop_time; /* lower 32-bits of TSF */ + __le32 status; /* see il_measurement_status */ +} __packed; + +/****************************************************************************** + * (7) + * Power Management Commands, Responses, Notifications: + * + *****************************************************************************/ + +/** + * struct il_powertable_cmd - Power Table Command + * @flags: See below: + * + * C_POWER_TBL = 0x77 (command, has simple generic response) + * + * PM allow: + * bit 0 - '0' Driver not allow power management + * '1' Driver allow PM (use rest of parameters) + * + * uCode send sleep notifications: + * bit 1 - '0' Don't send sleep notification + * '1' send sleep notification (SEND_PM_NOTIFICATION) + * + * Sleep over DTIM + * bit 2 - '0' PM have to walk up every DTIM + * '1' PM could sleep over DTIM till listen Interval. + * + * PCI power managed + * bit 3 - '0' (PCI_CFG_LINK_CTRL & 0x1) + * '1' !(PCI_CFG_LINK_CTRL & 0x1) + * + * Fast PD + * bit 4 - '1' Put radio to sleep when receiving frame for others + * + * Force sleep Modes + * bit 31/30- '00' use both mac/xtal sleeps + * '01' force Mac sleep + * '10' force xtal sleep + * '11' Illegal set + * + * NOTE: if sleep_interval[SLEEP_INTRVL_TBL_SIZE-1] > DTIM period then + * ucode assume sleep over DTIM is allowed and we don't need to wake up + * for every DTIM. + */ +#define IL_POWER_VEC_SIZE 5 + +#define IL_POWER_DRIVER_ALLOW_SLEEP_MSK cpu_to_le16(BIT(0)) +#define IL_POWER_PCI_PM_MSK cpu_to_le16(BIT(3)) + +struct il3945_powertable_cmd { + __le16 flags; + u8 reserved[2]; + __le32 rx_data_timeout; + __le32 tx_data_timeout; + __le32 sleep_interval[IL_POWER_VEC_SIZE]; +} __packed; + +struct il_powertable_cmd { + __le16 flags; + u8 keep_alive_seconds; /* 3945 reserved */ + u8 debug_flags; /* 3945 reserved */ + __le32 rx_data_timeout; + __le32 tx_data_timeout; + __le32 sleep_interval[IL_POWER_VEC_SIZE]; + __le32 keep_alive_beacons; +} __packed; + +/* + * N_PM_SLEEP = 0x7A (notification only, not a command) + * all devices identical. + */ +struct il_sleep_notification { + u8 pm_sleep_mode; + u8 pm_wakeup_src; + __le16 reserved; + __le32 sleep_time; + __le32 tsf_low; + __le32 bcon_timer; +} __packed; + +/* Sleep states. all devices identical. */ +enum { + IL_PM_NO_SLEEP = 0, + IL_PM_SLP_MAC = 1, + IL_PM_SLP_FULL_MAC_UNASSOCIATE = 2, + IL_PM_SLP_FULL_MAC_CARD_STATE = 3, + IL_PM_SLP_PHY = 4, + IL_PM_SLP_REPENT = 5, + IL_PM_WAKEUP_BY_TIMER = 6, + IL_PM_WAKEUP_BY_DRIVER = 7, + IL_PM_WAKEUP_BY_RFKILL = 8, + /* 3 reserved */ + IL_PM_NUM_OF_MODES = 12, +}; + +/* + * N_CARD_STATE = 0xa1 (notification only, not a command) + */ +struct il_card_state_notif { + __le32 flags; +} __packed; + +#define HW_CARD_DISABLED 0x01 +#define SW_CARD_DISABLED 0x02 +#define CT_CARD_DISABLED 0x04 +#define RXON_CARD_DISABLED 0x10 + +struct il_ct_kill_config { + __le32 reserved; + __le32 critical_temperature_M; + __le32 critical_temperature_R; +} __packed; + +/****************************************************************************** + * (8) + * Scan Commands, Responses, Notifications: + * + *****************************************************************************/ + +#define SCAN_CHANNEL_TYPE_PASSIVE cpu_to_le32(0) +#define SCAN_CHANNEL_TYPE_ACTIVE cpu_to_le32(1) + +/** + * struct il_scan_channel - entry in C_SCAN channel table + * + * One for each channel in the scan list. + * Each channel can independently select: + * 1) SSID for directed active scans + * 2) Txpower setting (for rate specified within Tx command) + * 3) How long to stay on-channel (behavior may be modified by quiet_time, + * quiet_plcp_th, good_CRC_th) + * + * To avoid uCode errors, make sure the following are true (see comments + * under struct il_scan_cmd about max_out_time and quiet_time): + * 1) If using passive_dwell (i.e. passive_dwell != 0): + * active_dwell <= passive_dwell (< max_out_time if max_out_time != 0) + * 2) quiet_time <= active_dwell + * 3) If restricting off-channel time (i.e. max_out_time !=0): + * passive_dwell < max_out_time + * active_dwell < max_out_time + */ +struct il3945_scan_channel { + /* + * type is defined as: + * 0:0 1 = active, 0 = passive + * 1:4 SSID direct bit map; if a bit is set, then corresponding + * SSID IE is transmitted in probe request. + * 5:7 reserved + */ + u8 type; + u8 channel; /* band is selected by il3945_scan_cmd "flags" field */ + struct il3945_tx_power tpc; + __le16 active_dwell; /* in 1024-uSec TU (time units), typ 5-50 */ + __le16 passive_dwell; /* in 1024-uSec TU (time units), typ 20-500 */ +} __packed; + +/* set number of direct probes u8 type */ +#define IL39_SCAN_PROBE_MASK(n) ((BIT(n) | (BIT(n) - BIT(1)))) + +struct il_scan_channel { + /* + * type is defined as: + * 0:0 1 = active, 0 = passive + * 1:20 SSID direct bit map; if a bit is set, then corresponding + * SSID IE is transmitted in probe request. + * 21:31 reserved + */ + __le32 type; + __le16 channel; /* band is selected by il_scan_cmd "flags" field */ + u8 tx_gain; /* gain for analog radio */ + u8 dsp_atten; /* gain for DSP */ + __le16 active_dwell; /* in 1024-uSec TU (time units), typ 5-50 */ + __le16 passive_dwell; /* in 1024-uSec TU (time units), typ 20-500 */ +} __packed; + +/* set number of direct probes __le32 type */ +#define IL_SCAN_PROBE_MASK(n) cpu_to_le32((BIT(n) | (BIT(n) - BIT(1)))) + +/** + * struct il_ssid_ie - directed scan network information element + * + * Up to 20 of these may appear in C_SCAN (Note: Only 4 are in + * 3945 SCAN api), selected by "type" bit field in struct il_scan_channel; + * each channel may select different ssids from among the 20 (4) entries. + * SSID IEs get transmitted in reverse order of entry. + */ +struct il_ssid_ie { + u8 id; + u8 len; + u8 ssid[32]; +} __packed; + +#define PROBE_OPTION_MAX_3945 4 +#define PROBE_OPTION_MAX 20 +#define TX_CMD_LIFE_TIME_INFINITE cpu_to_le32(0xFFFFFFFF) +#define IL_GOOD_CRC_TH_DISABLED 0 +#define IL_GOOD_CRC_TH_DEFAULT cpu_to_le16(1) +#define IL_GOOD_CRC_TH_NEVER cpu_to_le16(0xffff) +#define IL_MAX_SCAN_SIZE 1024 +#define IL_MAX_CMD_SIZE 4096 + +/* + * C_SCAN = 0x80 (command) + * + * The hardware scan command is very powerful; the driver can set it up to + * maintain (relatively) normal network traffic while doing a scan in the + * background. The max_out_time and suspend_time control the ratio of how + * long the device stays on an associated network channel ("service channel") + * vs. how long it's away from the service channel, i.e. tuned to other channels + * for scanning. + * + * max_out_time is the max time off-channel (in usec), and suspend_time + * is how long (in "extended beacon" format) that the scan is "suspended" + * after returning to the service channel. That is, suspend_time is the + * time that we stay on the service channel, doing normal work, between + * scan segments. The driver may set these parameters differently to support + * scanning when associated vs. not associated, and light vs. heavy traffic + * loads when associated. + * + * After receiving this command, the device's scan engine does the following; + * + * 1) Sends SCAN_START notification to driver + * 2) Checks to see if it has time to do scan for one channel + * 3) Sends NULL packet, with power-save (PS) bit set to 1, + * to tell AP that we're going off-channel + * 4) Tunes to first channel in scan list, does active or passive scan + * 5) Sends SCAN_RESULT notification to driver + * 6) Checks to see if it has time to do scan on *next* channel in list + * 7) Repeats 4-6 until it no longer has time to scan the next channel + * before max_out_time expires + * 8) Returns to service channel + * 9) Sends NULL packet with PS=0 to tell AP that we're back + * 10) Stays on service channel until suspend_time expires + * 11) Repeats entire process 2-10 until list is complete + * 12) Sends SCAN_COMPLETE notification + * + * For fast, efficient scans, the scan command also has support for staying on + * a channel for just a short time, if doing active scanning and getting no + * responses to the transmitted probe request. This time is controlled by + * quiet_time, and the number of received packets below which a channel is + * considered "quiet" is controlled by quiet_plcp_threshold. + * + * For active scanning on channels that have regulatory restrictions against + * blindly transmitting, the scan can listen before transmitting, to make sure + * that there is already legitimate activity on the channel. If enough + * packets are cleanly received on the channel (controlled by good_CRC_th, + * typical value 1), the scan engine starts transmitting probe requests. + * + * Driver must use separate scan commands for 2.4 vs. 5 GHz bands. + * + * To avoid uCode errors, see timing restrictions described under + * struct il_scan_channel. + */ + +struct il3945_scan_cmd { + __le16 len; + u8 reserved0; + u8 channel_count; /* # channels in channel list */ + __le16 quiet_time; /* dwell only this # millisecs on quiet channel + * (only for active scan) */ + __le16 quiet_plcp_th; /* quiet chnl is < this # pkts (typ. 1) */ + __le16 good_CRC_th; /* passive -> active promotion threshold */ + __le16 reserved1; + __le32 max_out_time; /* max usec to be away from associated (service) + * channel */ + __le32 suspend_time; /* pause scan this long (in "extended beacon + * format") when returning to service channel: + * 3945; 31:24 # beacons, 19:0 additional usec, + * 4965; 31:22 # beacons, 21:0 additional usec. + */ + __le32 flags; /* RXON_FLG_* */ + __le32 filter_flags; /* RXON_FILTER_* */ + + /* For active scans (set to all-0s for passive scans). + * Does not include payload. Must specify Tx rate; no rate scaling. */ + struct il3945_tx_cmd tx_cmd; + + /* For directed active scans (set to all-0s otherwise) */ + struct il_ssid_ie direct_scan[PROBE_OPTION_MAX_3945]; + + /* + * Probe request frame, followed by channel list. + * + * Size of probe request frame is specified by byte count in tx_cmd. + * Channel list follows immediately after probe request frame. + * Number of channels in list is specified by channel_count. + * Each channel in list is of type: + * + * struct il3945_scan_channel channels[0]; + * + * NOTE: Only one band of channels can be scanned per pass. You + * must not mix 2.4GHz channels and 5.2GHz channels, and you must wait + * for one scan to complete (i.e. receive N_SCAN_COMPLETE) + * before requesting another scan. + */ + u8 data[0]; +} __packed; + +struct il_scan_cmd { + __le16 len; + u8 reserved0; + u8 channel_count; /* # channels in channel list */ + __le16 quiet_time; /* dwell only this # millisecs on quiet channel + * (only for active scan) */ + __le16 quiet_plcp_th; /* quiet chnl is < this # pkts (typ. 1) */ + __le16 good_CRC_th; /* passive -> active promotion threshold */ + __le16 rx_chain; /* RXON_RX_CHAIN_* */ + __le32 max_out_time; /* max usec to be away from associated (service) + * channel */ + __le32 suspend_time; /* pause scan this long (in "extended beacon + * format") when returning to service chnl: + * 3945; 31:24 # beacons, 19:0 additional usec, + * 4965; 31:22 # beacons, 21:0 additional usec. + */ + __le32 flags; /* RXON_FLG_* */ + __le32 filter_flags; /* RXON_FILTER_* */ + + /* For active scans (set to all-0s for passive scans). + * Does not include payload. Must specify Tx rate; no rate scaling. */ + struct il_tx_cmd tx_cmd; + + /* For directed active scans (set to all-0s otherwise) */ + struct il_ssid_ie direct_scan[PROBE_OPTION_MAX]; + + /* + * Probe request frame, followed by channel list. + * + * Size of probe request frame is specified by byte count in tx_cmd. + * Channel list follows immediately after probe request frame. + * Number of channels in list is specified by channel_count. + * Each channel in list is of type: + * + * struct il_scan_channel channels[0]; + * + * NOTE: Only one band of channels can be scanned per pass. You + * must not mix 2.4GHz channels and 5.2GHz channels, and you must wait + * for one scan to complete (i.e. receive N_SCAN_COMPLETE) + * before requesting another scan. + */ + u8 data[0]; +} __packed; + +/* Can abort will notify by complete notification with abort status. */ +#define CAN_ABORT_STATUS cpu_to_le32(0x1) +/* complete notification statuses */ +#define ABORT_STATUS 0x2 + +/* + * C_SCAN = 0x80 (response) + */ +struct il_scanreq_notification { + __le32 status; /* 1: okay, 2: cannot fulfill request */ +} __packed; + +/* + * N_SCAN_START = 0x82 (notification only, not a command) + */ +struct il_scanstart_notification { + __le32 tsf_low; + __le32 tsf_high; + __le32 beacon_timer; + u8 channel; + u8 band; + u8 reserved[2]; + __le32 status; +} __packed; + +#define SCAN_OWNER_STATUS 0x1 +#define MEASURE_OWNER_STATUS 0x2 + +#define IL_PROBE_STATUS_OK 0 +#define IL_PROBE_STATUS_TX_FAILED BIT(0) +/* error statuses combined with TX_FAILED */ +#define IL_PROBE_STATUS_FAIL_TTL BIT(1) +#define IL_PROBE_STATUS_FAIL_BT BIT(2) + +#define NUMBER_OF_STATS 1 /* first __le32 is good CRC */ +/* + * N_SCAN_RESULTS = 0x83 (notification only, not a command) + */ +struct il_scanresults_notification { + u8 channel; + u8 band; + u8 probe_status; + u8 num_probe_not_sent; /* not enough time to send */ + __le32 tsf_low; + __le32 tsf_high; + __le32 stats[NUMBER_OF_STATS]; +} __packed; + +/* + * N_SCAN_COMPLETE = 0x84 (notification only, not a command) + */ +struct il_scancomplete_notification { + u8 scanned_channels; + u8 status; + u8 last_channel; + __le32 tsf_low; + __le32 tsf_high; +} __packed; + +/****************************************************************************** + * (9) + * IBSS/AP Commands and Notifications: + * + *****************************************************************************/ + +enum il_ibss_manager { + IL_NOT_IBSS_MANAGER = 0, + IL_IBSS_MANAGER = 1, +}; + +/* + * N_BEACON = 0x90 (notification only, not a command) + */ + +struct il3945_beacon_notif { + struct il3945_tx_resp beacon_notify_hdr; + __le32 low_tsf; + __le32 high_tsf; + __le32 ibss_mgr_status; +} __packed; + +struct il4965_beacon_notif { + struct il4965_tx_resp beacon_notify_hdr; + __le32 low_tsf; + __le32 high_tsf; + __le32 ibss_mgr_status; +} __packed; + +/* + * C_TX_BEACON= 0x91 (command, has simple generic response) + */ + +struct il3945_tx_beacon_cmd { + struct il3945_tx_cmd tx; + __le16 tim_idx; + u8 tim_size; + u8 reserved1; + struct ieee80211_hdr frame[0]; /* beacon frame */ +} __packed; + +struct il_tx_beacon_cmd { + struct il_tx_cmd tx; + __le16 tim_idx; + u8 tim_size; + u8 reserved1; + struct ieee80211_hdr frame[0]; /* beacon frame */ +} __packed; + +/****************************************************************************** + * (10) + * Statistics Commands and Notifications: + * + *****************************************************************************/ + +#define IL_TEMP_CONVERT 260 + +#define SUP_RATE_11A_MAX_NUM_CHANNELS 8 +#define SUP_RATE_11B_MAX_NUM_CHANNELS 4 +#define SUP_RATE_11G_MAX_NUM_CHANNELS 12 + +/* Used for passing to driver number of successes and failures per rate */ +struct rate_histogram { + union { + __le32 a[SUP_RATE_11A_MAX_NUM_CHANNELS]; + __le32 b[SUP_RATE_11B_MAX_NUM_CHANNELS]; + __le32 g[SUP_RATE_11G_MAX_NUM_CHANNELS]; + } success; + union { + __le32 a[SUP_RATE_11A_MAX_NUM_CHANNELS]; + __le32 b[SUP_RATE_11B_MAX_NUM_CHANNELS]; + __le32 g[SUP_RATE_11G_MAX_NUM_CHANNELS]; + } failed; +} __packed; + +/* stats command response */ + +struct iwl39_stats_rx_phy { + __le32 ina_cnt; + __le32 fina_cnt; + __le32 plcp_err; + __le32 crc32_err; + __le32 overrun_err; + __le32 early_overrun_err; + __le32 crc32_good; + __le32 false_alarm_cnt; + __le32 fina_sync_err_cnt; + __le32 sfd_timeout; + __le32 fina_timeout; + __le32 unresponded_rts; + __le32 rxe_frame_limit_overrun; + __le32 sent_ack_cnt; + __le32 sent_cts_cnt; +} __packed; + +struct iwl39_stats_rx_non_phy { + __le32 bogus_cts; /* CTS received when not expecting CTS */ + __le32 bogus_ack; /* ACK received when not expecting ACK */ + __le32 non_bssid_frames; /* number of frames with BSSID that + * doesn't belong to the STA BSSID */ + __le32 filtered_frames; /* count frames that were dumped in the + * filtering process */ + __le32 non_channel_beacons; /* beacons with our bss id but not on + * our serving channel */ +} __packed; + +struct iwl39_stats_rx { + struct iwl39_stats_rx_phy ofdm; + struct iwl39_stats_rx_phy cck; + struct iwl39_stats_rx_non_phy general; +} __packed; + +struct iwl39_stats_tx { + __le32 preamble_cnt; + __le32 rx_detected_cnt; + __le32 bt_prio_defer_cnt; + __le32 bt_prio_kill_cnt; + __le32 few_bytes_cnt; + __le32 cts_timeout; + __le32 ack_timeout; + __le32 expected_ack_cnt; + __le32 actual_ack_cnt; +} __packed; + +struct stats_dbg { + __le32 burst_check; + __le32 burst_count; + __le32 wait_for_silence_timeout_cnt; + __le32 reserved[3]; +} __packed; + +struct iwl39_stats_div { + __le32 tx_on_a; + __le32 tx_on_b; + __le32 exec_time; + __le32 probe_time; +} __packed; + +struct iwl39_stats_general { + __le32 temperature; + struct stats_dbg dbg; + __le32 sleep_time; + __le32 slots_out; + __le32 slots_idle; + __le32 ttl_timestamp; + struct iwl39_stats_div div; +} __packed; + +struct stats_rx_phy { + __le32 ina_cnt; + __le32 fina_cnt; + __le32 plcp_err; + __le32 crc32_err; + __le32 overrun_err; + __le32 early_overrun_err; + __le32 crc32_good; + __le32 false_alarm_cnt; + __le32 fina_sync_err_cnt; + __le32 sfd_timeout; + __le32 fina_timeout; + __le32 unresponded_rts; + __le32 rxe_frame_limit_overrun; + __le32 sent_ack_cnt; + __le32 sent_cts_cnt; + __le32 sent_ba_rsp_cnt; + __le32 dsp_self_kill; + __le32 mh_format_err; + __le32 re_acq_main_rssi_sum; + __le32 reserved3; +} __packed; + +struct stats_rx_ht_phy { + __le32 plcp_err; + __le32 overrun_err; + __le32 early_overrun_err; + __le32 crc32_good; + __le32 crc32_err; + __le32 mh_format_err; + __le32 agg_crc32_good; + __le32 agg_mpdu_cnt; + __le32 agg_cnt; + __le32 unsupport_mcs; +} __packed; + +#define INTERFERENCE_DATA_AVAILABLE cpu_to_le32(1) + +struct stats_rx_non_phy { + __le32 bogus_cts; /* CTS received when not expecting CTS */ + __le32 bogus_ack; /* ACK received when not expecting ACK */ + __le32 non_bssid_frames; /* number of frames with BSSID that + * doesn't belong to the STA BSSID */ + __le32 filtered_frames; /* count frames that were dumped in the + * filtering process */ + __le32 non_channel_beacons; /* beacons with our bss id but not on + * our serving channel */ + __le32 channel_beacons; /* beacons with our bss id and in our + * serving channel */ + __le32 num_missed_bcon; /* number of missed beacons */ + __le32 adc_rx_saturation_time; /* count in 0.8us units the time the + * ADC was in saturation */ + __le32 ina_detection_search_time; /* total time (in 0.8us) searched + * for INA */ + __le32 beacon_silence_rssi_a; /* RSSI silence after beacon frame */ + __le32 beacon_silence_rssi_b; /* RSSI silence after beacon frame */ + __le32 beacon_silence_rssi_c; /* RSSI silence after beacon frame */ + __le32 interference_data_flag; /* flag for interference data + * availability. 1 when data is + * available. */ + __le32 channel_load; /* counts RX Enable time in uSec */ + __le32 dsp_false_alarms; /* DSP false alarm (both OFDM + * and CCK) counter */ + __le32 beacon_rssi_a; + __le32 beacon_rssi_b; + __le32 beacon_rssi_c; + __le32 beacon_energy_a; + __le32 beacon_energy_b; + __le32 beacon_energy_c; +} __packed; + +struct stats_rx { + struct stats_rx_phy ofdm; + struct stats_rx_phy cck; + struct stats_rx_non_phy general; + struct stats_rx_ht_phy ofdm_ht; +} __packed; + +/** + * struct stats_tx_power - current tx power + * + * @ant_a: current tx power on chain a in 1/2 dB step + * @ant_b: current tx power on chain b in 1/2 dB step + * @ant_c: current tx power on chain c in 1/2 dB step + */ +struct stats_tx_power { + u8 ant_a; + u8 ant_b; + u8 ant_c; + u8 reserved; +} __packed; + +struct stats_tx_non_phy_agg { + __le32 ba_timeout; + __le32 ba_reschedule_frames; + __le32 scd_query_agg_frame_cnt; + __le32 scd_query_no_agg; + __le32 scd_query_agg; + __le32 scd_query_mismatch; + __le32 frame_not_ready; + __le32 underrun; + __le32 bt_prio_kill; + __le32 rx_ba_rsp_cnt; +} __packed; + +struct stats_tx { + __le32 preamble_cnt; + __le32 rx_detected_cnt; + __le32 bt_prio_defer_cnt; + __le32 bt_prio_kill_cnt; + __le32 few_bytes_cnt; + __le32 cts_timeout; + __le32 ack_timeout; + __le32 expected_ack_cnt; + __le32 actual_ack_cnt; + __le32 dump_msdu_cnt; + __le32 burst_abort_next_frame_mismatch_cnt; + __le32 burst_abort_missing_next_frame_cnt; + __le32 cts_timeout_collision; + __le32 ack_or_ba_timeout_collision; + struct stats_tx_non_phy_agg agg; + + __le32 reserved1; +} __packed; + +struct stats_div { + __le32 tx_on_a; + __le32 tx_on_b; + __le32 exec_time; + __le32 probe_time; + __le32 reserved1; + __le32 reserved2; +} __packed; + +struct stats_general_common { + __le32 temperature; /* radio temperature */ + struct stats_dbg dbg; + __le32 sleep_time; + __le32 slots_out; + __le32 slots_idle; + __le32 ttl_timestamp; + struct stats_div div; + __le32 rx_enable_counter; + /* + * num_of_sos_states: + * count the number of times we have to re-tune + * in order to get out of bad PHY status + */ + __le32 num_of_sos_states; +} __packed; + +struct stats_general { + struct stats_general_common common; + __le32 reserved2; + __le32 reserved3; +} __packed; + +#define UCODE_STATS_CLEAR_MSK (0x1 << 0) +#define UCODE_STATS_FREQUENCY_MSK (0x1 << 1) +#define UCODE_STATS_NARROW_BAND_MSK (0x1 << 2) + +/* + * C_STATS = 0x9c, + * all devices identical. + * + * This command triggers an immediate response containing uCode stats. + * The response is in the same format as N_STATS 0x9d, below. + * + * If the CLEAR_STATS configuration flag is set, uCode will clear its + * internal copy of the stats (counters) after issuing the response. + * This flag does not affect N_STATSs after beacons (see below). + * + * If the DISABLE_NOTIF configuration flag is set, uCode will not issue + * N_STATSs after received beacons (see below). This flag + * does not affect the response to the C_STATS 0x9c itself. + */ +#define IL_STATS_CONF_CLEAR_STATS cpu_to_le32(0x1) /* see above */ +#define IL_STATS_CONF_DISABLE_NOTIF cpu_to_le32(0x2) /* see above */ +struct il_stats_cmd { + __le32 configuration_flags; /* IL_STATS_CONF_* */ +} __packed; + +/* + * N_STATS = 0x9d (notification only, not a command) + * + * By default, uCode issues this notification after receiving a beacon + * while associated. To disable this behavior, set DISABLE_NOTIF flag in the + * C_STATS 0x9c, above. + * + * Statistics counters continue to increment beacon after beacon, but are + * cleared when changing channels or when driver issues C_STATS + * 0x9c with CLEAR_STATS bit set (see above). + * + * uCode also issues this notification during scans. uCode clears stats + * appropriately so that each notification contains stats for only the + * one channel that has just been scanned. + */ +#define STATS_REPLY_FLG_BAND_24G_MSK cpu_to_le32(0x2) +#define STATS_REPLY_FLG_HT40_MODE_MSK cpu_to_le32(0x8) + +struct il3945_notif_stats { + __le32 flag; + struct iwl39_stats_rx rx; + struct iwl39_stats_tx tx; + struct iwl39_stats_general general; +} __packed; + +struct il_notif_stats { + __le32 flag; + struct stats_rx rx; + struct stats_tx tx; + struct stats_general general; +} __packed; + +/* + * N_MISSED_BEACONS = 0xa2 (notification only, not a command) + * + * uCode send N_MISSED_BEACONS to driver when detect beacon missed + * in regardless of how many missed beacons, which mean when driver receive the + * notification, inside the command, it can find all the beacons information + * which include number of total missed beacons, number of consecutive missed + * beacons, number of beacons received and number of beacons expected to + * receive. + * + * If uCode detected consecutive_missed_beacons > 5, it will reset the radio + * in order to bring the radio/PHY back to working state; which has no relation + * to when driver will perform sensitivity calibration. + * + * Driver should set it own missed_beacon_threshold to decide when to perform + * sensitivity calibration based on number of consecutive missed beacons in + * order to improve overall performance, especially in noisy environment. + * + */ + +#define IL_MISSED_BEACON_THRESHOLD_MIN (1) +#define IL_MISSED_BEACON_THRESHOLD_DEF (5) +#define IL_MISSED_BEACON_THRESHOLD_MAX IL_MISSED_BEACON_THRESHOLD_DEF + +struct il_missed_beacon_notif { + __le32 consecutive_missed_beacons; + __le32 total_missed_becons; + __le32 num_expected_beacons; + __le32 num_recvd_beacons; +} __packed; + +/****************************************************************************** + * (11) + * Rx Calibration Commands: + * + * With the uCode used for open source drivers, most Tx calibration (except + * for Tx Power) and most Rx calibration is done by uCode during the + * "initialize" phase of uCode boot. Driver must calibrate only: + * + * 1) Tx power (depends on temperature), described elsewhere + * 2) Receiver gain balance (optimize MIMO, and detect disconnected antennas) + * 3) Receiver sensitivity (to optimize signal detection) + * + *****************************************************************************/ + +/** + * C_SENSITIVITY = 0xa8 (command, has simple generic response) + * + * This command sets up the Rx signal detector for a sensitivity level that + * is high enough to lock onto all signals within the associated network, + * but low enough to ignore signals that are below a certain threshold, so as + * not to have too many "false alarms". False alarms are signals that the + * Rx DSP tries to lock onto, but then discards after determining that they + * are noise. + * + * The optimum number of false alarms is between 5 and 50 per 200 TUs + * (200 * 1024 uSecs, i.e. 204.8 milliseconds) of actual Rx time (i.e. + * time listening, not transmitting). Driver must adjust sensitivity so that + * the ratio of actual false alarms to actual Rx time falls within this range. + * + * While associated, uCode delivers N_STATSs after each + * received beacon. These provide information to the driver to analyze the + * sensitivity. Don't analyze stats that come in from scanning, or any + * other non-associated-network source. Pertinent stats include: + * + * From "general" stats (struct stats_rx_non_phy): + * + * (beacon_energy_[abc] & 0x0FF00) >> 8 (unsigned, higher value is lower level) + * Measure of energy of desired signal. Used for establishing a level + * below which the device does not detect signals. + * + * (beacon_silence_rssi_[abc] & 0x0FF00) >> 8 (unsigned, units in dB) + * Measure of background noise in silent period after beacon. + * + * channel_load + * uSecs of actual Rx time during beacon period (varies according to + * how much time was spent transmitting). + * + * From "cck" and "ofdm" stats (struct stats_rx_phy), separately: + * + * false_alarm_cnt + * Signal locks abandoned early (before phy-level header). + * + * plcp_err + * Signal locks abandoned late (during phy-level header). + * + * NOTE: Both false_alarm_cnt and plcp_err increment monotonically from + * beacon to beacon, i.e. each value is an accumulation of all errors + * before and including the latest beacon. Values will wrap around to 0 + * after counting up to 2^32 - 1. Driver must differentiate vs. + * previous beacon's values to determine # false alarms in the current + * beacon period. + * + * Total number of false alarms = false_alarms + plcp_errs + * + * For OFDM, adjust the following table entries in struct il_sensitivity_cmd + * (notice that the start points for OFDM are at or close to settings for + * maximum sensitivity): + * + * START / MIN / MAX + * HD_AUTO_CORR32_X1_TH_ADD_MIN_IDX 90 / 85 / 120 + * HD_AUTO_CORR32_X1_TH_ADD_MIN_MRC_IDX 170 / 170 / 210 + * HD_AUTO_CORR32_X4_TH_ADD_MIN_IDX 105 / 105 / 140 + * HD_AUTO_CORR32_X4_TH_ADD_MIN_MRC_IDX 220 / 220 / 270 + * + * If actual rate of OFDM false alarms (+ plcp_errors) is too high + * (greater than 50 for each 204.8 msecs listening), reduce sensitivity + * by *adding* 1 to all 4 of the table entries above, up to the max for + * each entry. Conversely, if false alarm rate is too low (less than 5 + * for each 204.8 msecs listening), *subtract* 1 from each entry to + * increase sensitivity. + * + * For CCK sensitivity, keep track of the following: + * + * 1). 20-beacon history of maximum background noise, indicated by + * (beacon_silence_rssi_[abc] & 0x0FF00), units in dB, across the + * 3 receivers. For any given beacon, the "silence reference" is + * the maximum of last 60 samples (20 beacons * 3 receivers). + * + * 2). 10-beacon history of strongest signal level, as indicated + * by (beacon_energy_[abc] & 0x0FF00) >> 8, across the 3 receivers, + * i.e. the strength of the signal through the best receiver at the + * moment. These measurements are "upside down", with lower values + * for stronger signals, so max energy will be *minimum* value. + * + * Then for any given beacon, the driver must determine the *weakest* + * of the strongest signals; this is the minimum level that needs to be + * successfully detected, when using the best receiver at the moment. + * "Max cck energy" is the maximum (higher value means lower energy!) + * of the last 10 minima. Once this is determined, driver must add + * a little margin by adding "6" to it. + * + * 3). Number of consecutive beacon periods with too few false alarms. + * Reset this to 0 at the first beacon period that falls within the + * "good" range (5 to 50 false alarms per 204.8 milliseconds rx). + * + * Then, adjust the following CCK table entries in struct il_sensitivity_cmd + * (notice that the start points for CCK are at maximum sensitivity): + * + * START / MIN / MAX + * HD_AUTO_CORR40_X4_TH_ADD_MIN_IDX 125 / 125 / 200 + * HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_IDX 200 / 200 / 400 + * HD_MIN_ENERGY_CCK_DET_IDX 100 / 0 / 100 + * + * If actual rate of CCK false alarms (+ plcp_errors) is too high + * (greater than 50 for each 204.8 msecs listening), method for reducing + * sensitivity is: + * + * 1) *Add* 3 to value in HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_IDX, + * up to max 400. + * + * 2) If current value in HD_AUTO_CORR40_X4_TH_ADD_MIN_IDX is < 160, + * sensitivity has been reduced a significant amount; bring it up to + * a moderate 161. Otherwise, *add* 3, up to max 200. + * + * 3) a) If current value in HD_AUTO_CORR40_X4_TH_ADD_MIN_IDX is > 160, + * sensitivity has been reduced only a moderate or small amount; + * *subtract* 2 from value in HD_MIN_ENERGY_CCK_DET_IDX, + * down to min 0. Otherwise (if gain has been significantly reduced), + * don't change the HD_MIN_ENERGY_CCK_DET_IDX value. + * + * b) Save a snapshot of the "silence reference". + * + * If actual rate of CCK false alarms (+ plcp_errors) is too low + * (less than 5 for each 204.8 msecs listening), method for increasing + * sensitivity is used only if: + * + * 1a) Previous beacon did not have too many false alarms + * 1b) AND difference between previous "silence reference" and current + * "silence reference" (prev - current) is 2 or more, + * OR 2) 100 or more consecutive beacon periods have had rate of + * less than 5 false alarms per 204.8 milliseconds rx time. + * + * Method for increasing sensitivity: + * + * 1) *Subtract* 3 from value in HD_AUTO_CORR40_X4_TH_ADD_MIN_IDX, + * down to min 125. + * + * 2) *Subtract* 3 from value in HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_IDX, + * down to min 200. + * + * 3) *Add* 2 to value in HD_MIN_ENERGY_CCK_DET_IDX, up to max 100. + * + * If actual rate of CCK false alarms (+ plcp_errors) is within good range + * (between 5 and 50 for each 204.8 msecs listening): + * + * 1) Save a snapshot of the silence reference. + * + * 2) If previous beacon had too many CCK false alarms (+ plcp_errors), + * give some extra margin to energy threshold by *subtracting* 8 + * from value in HD_MIN_ENERGY_CCK_DET_IDX. + * + * For all cases (too few, too many, good range), make sure that the CCK + * detection threshold (energy) is below the energy level for robust + * detection over the past 10 beacon periods, the "Max cck energy". + * Lower values mean higher energy; this means making sure that the value + * in HD_MIN_ENERGY_CCK_DET_IDX is at or *above* "Max cck energy". + * + */ + +/* + * Table entries in C_SENSITIVITY (struct il_sensitivity_cmd) + */ +#define HD_TBL_SIZE (11) /* number of entries */ +#define HD_MIN_ENERGY_CCK_DET_IDX (0) /* table idxes */ +#define HD_MIN_ENERGY_OFDM_DET_IDX (1) +#define HD_AUTO_CORR32_X1_TH_ADD_MIN_IDX (2) +#define HD_AUTO_CORR32_X1_TH_ADD_MIN_MRC_IDX (3) +#define HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_IDX (4) +#define HD_AUTO_CORR32_X4_TH_ADD_MIN_IDX (5) +#define HD_AUTO_CORR32_X4_TH_ADD_MIN_MRC_IDX (6) +#define HD_BARKER_CORR_TH_ADD_MIN_IDX (7) +#define HD_BARKER_CORR_TH_ADD_MIN_MRC_IDX (8) +#define HD_AUTO_CORR40_X4_TH_ADD_MIN_IDX (9) +#define HD_OFDM_ENERGY_TH_IN_IDX (10) + +/* Control field in struct il_sensitivity_cmd */ +#define C_SENSITIVITY_CONTROL_DEFAULT_TBL cpu_to_le16(0) +#define C_SENSITIVITY_CONTROL_WORK_TBL cpu_to_le16(1) + +/** + * struct il_sensitivity_cmd + * @control: (1) updates working table, (0) updates default table + * @table: energy threshold values, use HD_* as idx into table + * + * Always use "1" in "control" to update uCode's working table and DSP. + */ +struct il_sensitivity_cmd { + __le16 control; /* always use "1" */ + __le16 table[HD_TBL_SIZE]; /* use HD_* as idx */ +} __packed; + +/** + * C_PHY_CALIBRATION = 0xb0 (command, has simple generic response) + * + * This command sets the relative gains of 4965 device's 3 radio receiver chains. + * + * After the first association, driver should accumulate signal and noise + * stats from the N_STATSs that follow the first 20 + * beacons from the associated network (don't collect stats that come + * in from scanning, or any other non-network source). + * + * DISCONNECTED ANTENNA: + * + * Driver should determine which antennas are actually connected, by comparing + * average beacon signal levels for the 3 Rx chains. Accumulate (add) the + * following values over 20 beacons, one accumulator for each of the chains + * a/b/c, from struct stats_rx_non_phy: + * + * beacon_rssi_[abc] & 0x0FF (unsigned, units in dB) + * + * Find the strongest signal from among a/b/c. Compare the other two to the + * strongest. If any signal is more than 15 dB (times 20, unless you + * divide the accumulated values by 20) below the strongest, the driver + * considers that antenna to be disconnected, and should not try to use that + * antenna/chain for Rx or Tx. If both A and B seem to be disconnected, + * driver should declare the stronger one as connected, and attempt to use it + * (A and B are the only 2 Tx chains!). + * + * + * RX BALANCE: + * + * Driver should balance the 3 receivers (but just the ones that are connected + * to antennas, see above) for gain, by comparing the average signal levels + * detected during the silence after each beacon (background noise). + * Accumulate (add) the following values over 20 beacons, one accumulator for + * each of the chains a/b/c, from struct stats_rx_non_phy: + * + * beacon_silence_rssi_[abc] & 0x0FF (unsigned, units in dB) + * + * Find the weakest background noise level from among a/b/c. This Rx chain + * will be the reference, with 0 gain adjustment. Attenuate other channels by + * finding noise difference: + * + * (accum_noise[i] - accum_noise[reference]) / 30 + * + * The "30" adjusts the dB in the 20 accumulated samples to units of 1.5 dB. + * For use in diff_gain_[abc] fields of struct il_calibration_cmd, the + * driver should limit the difference results to a range of 0-3 (0-4.5 dB), + * and set bit 2 to indicate "reduce gain". The value for the reference + * (weakest) chain should be "0". + * + * diff_gain_[abc] bit fields: + * 2: (1) reduce gain, (0) increase gain + * 1-0: amount of gain, units of 1.5 dB + */ + +/* Phy calibration command for series */ +/* The default calibrate table size if not specified by firmware */ +#define IL_DEFAULT_STANDARD_PHY_CALIBRATE_TBL_SIZE 18 +enum { + IL_PHY_CALIBRATE_DIFF_GAIN_CMD = 7, + IL_MAX_STANDARD_PHY_CALIBRATE_TBL_SIZE = 19, +}; + +#define IL_MAX_PHY_CALIBRATE_TBL_SIZE (253) + +struct il_calib_hdr { + u8 op_code; + u8 first_group; + u8 groups_num; + u8 data_valid; +} __packed; + +/* IL_PHY_CALIBRATE_DIFF_GAIN_CMD (7) */ +struct il_calib_diff_gain_cmd { + struct il_calib_hdr hdr; + s8 diff_gain_a; /* see above */ + s8 diff_gain_b; + s8 diff_gain_c; + u8 reserved1; +} __packed; + +/****************************************************************************** + * (12) + * Miscellaneous Commands: + * + *****************************************************************************/ + +/* + * LEDs Command & Response + * C_LEDS = 0x48 (command, has simple generic response) + * + * For each of 3 possible LEDs (Activity/Link/Tech, selected by "id" field), + * this command turns it on or off, or sets up a periodic blinking cycle. + */ +struct il_led_cmd { + __le32 interval; /* "interval" in uSec */ + u8 id; /* 1: Activity, 2: Link, 3: Tech */ + u8 off; /* # intervals off while blinking; + * "0", with >0 "on" value, turns LED on */ + u8 on; /* # intervals on while blinking; + * "0", regardless of "off", turns LED off */ + u8 reserved; +} __packed; + +/****************************************************************************** + * (13) + * Union of all expected notifications/responses: + * + *****************************************************************************/ + +#define IL_RX_FRAME_SIZE_MSK 0x00003fff + +struct il_rx_pkt { + /* + * The first 4 bytes of the RX frame header contain both the RX frame + * size and some flags. + * Bit fields: + * 31: flag flush RB request + * 30: flag ignore TC (terminal counter) request + * 29: flag fast IRQ request + * 28-14: Reserved + * 13-00: RX frame size + */ + __le32 len_n_flags; + struct il_cmd_header hdr; + union { + struct il3945_rx_frame rx_frame; + struct il3945_tx_resp tx_resp; + struct il3945_beacon_notif beacon_status; + + struct il_alive_resp alive_frame; + struct il_spectrum_notification spectrum_notif; + struct il_csa_notification csa_notif; + struct il_error_resp err_resp; + struct il_card_state_notif card_state_notif; + struct il_add_sta_resp add_sta; + struct il_rem_sta_resp rem_sta; + struct il_sleep_notification sleep_notif; + struct il_spectrum_resp spectrum; + struct il_notif_stats stats; + struct il_compressed_ba_resp compressed_ba; + struct il_missed_beacon_notif missed_beacon; + __le32 status; + u8 raw[0]; + } u; +} __packed; + +#endif /* __il_commands_h__ */ diff --git a/drivers/net/wireless/iwlegacy/common.c b/drivers/net/wireless/iwlegacy/common.c new file mode 100644 index 0000000..c0324bf --- /dev/null +++ b/drivers/net/wireless/iwlegacy/common.c @@ -0,0 +1,5533 @@ +/****************************************************************************** + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +int +_il_poll_bit(struct il_priv *il, u32 addr, u32 bits, u32 mask, int timeout) +{ + const int interval = 10; /* microseconds */ + int t = 0; + + do { + if ((_il_rd(il, addr) & mask) == (bits & mask)) + return t; + udelay(interval); + t += interval; + } while (t < timeout); + + return -ETIMEDOUT; +} +EXPORT_SYMBOL(_il_poll_bit); + +void +il_set_bit(struct il_priv *p, u32 r, u32 m) +{ + unsigned long reg_flags; + + spin_lock_irqsave(&p->reg_lock, reg_flags); + _il_set_bit(p, r, m); + spin_unlock_irqrestore(&p->reg_lock, reg_flags); +} +EXPORT_SYMBOL(il_set_bit); + +void +il_clear_bit(struct il_priv *p, u32 r, u32 m) +{ + unsigned long reg_flags; + + spin_lock_irqsave(&p->reg_lock, reg_flags); + _il_clear_bit(p, r, m); + spin_unlock_irqrestore(&p->reg_lock, reg_flags); +} +EXPORT_SYMBOL(il_clear_bit); + +bool +_il_grab_nic_access(struct il_priv *il) +{ + int ret; + u32 val; + + /* this bit wakes up the NIC */ + _il_set_bit(il, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + + /* + * These bits say the device is running, and should keep running for + * at least a short while (at least as long as MAC_ACCESS_REQ stays 1), + * but they do not indicate that embedded SRAM is restored yet; + * 3945 and 4965 have volatile SRAM, and must save/restore contents + * to/from host DRAM when sleeping/waking for power-saving. + * Each direction takes approximately 1/4 millisecond; with this + * overhead, it's a good idea to grab and hold MAC_ACCESS_REQUEST if a + * series of register accesses are expected (e.g. reading Event Log), + * to keep device from sleeping. + * + * CSR_UCODE_DRV_GP1 register bit MAC_SLEEP == 0 indicates that + * SRAM is okay/restored. We don't check that here because this call + * is just for hardware register access; but GP1 MAC_SLEEP check is a + * good idea before accessing 3945/4965 SRAM (e.g. reading Event Log). + * + */ + ret = + _il_poll_bit(il, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_VAL_MAC_ACCESS_EN, + (CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY | + CSR_GP_CNTRL_REG_FLAG_GOING_TO_SLEEP), 15000); + if (unlikely(ret < 0)) { + val = _il_rd(il, CSR_GP_CNTRL); + WARN_ONCE(1, "Timeout waiting for ucode processor access " + "(CSR_GP_CNTRL 0x%08x)\n", val); + _il_wr(il, CSR_RESET, CSR_RESET_REG_FLAG_FORCE_NMI); + return false; + } + + return true; +} +EXPORT_SYMBOL_GPL(_il_grab_nic_access); + +int +il_poll_bit(struct il_priv *il, u32 addr, u32 mask, int timeout) +{ + const int interval = 10; /* microseconds */ + int t = 0; + + do { + if ((il_rd(il, addr) & mask) == mask) + return t; + udelay(interval); + t += interval; + } while (t < timeout); + + return -ETIMEDOUT; +} +EXPORT_SYMBOL(il_poll_bit); + +u32 +il_rd_prph(struct il_priv *il, u32 reg) +{ + unsigned long reg_flags; + u32 val; + + spin_lock_irqsave(&il->reg_lock, reg_flags); + _il_grab_nic_access(il); + val = _il_rd_prph(il, reg); + _il_release_nic_access(il); + spin_unlock_irqrestore(&il->reg_lock, reg_flags); + return val; +} +EXPORT_SYMBOL(il_rd_prph); + +void +il_wr_prph(struct il_priv *il, u32 addr, u32 val) +{ + unsigned long reg_flags; + + spin_lock_irqsave(&il->reg_lock, reg_flags); + if (likely(_il_grab_nic_access(il))) { + _il_wr_prph(il, addr, val); + _il_release_nic_access(il); + } + spin_unlock_irqrestore(&il->reg_lock, reg_flags); +} +EXPORT_SYMBOL(il_wr_prph); + +u32 +il_read_targ_mem(struct il_priv *il, u32 addr) +{ + unsigned long reg_flags; + u32 value; + + spin_lock_irqsave(&il->reg_lock, reg_flags); + _il_grab_nic_access(il); + + _il_wr(il, HBUS_TARG_MEM_RADDR, addr); + value = _il_rd(il, HBUS_TARG_MEM_RDAT); + + _il_release_nic_access(il); + spin_unlock_irqrestore(&il->reg_lock, reg_flags); + return value; +} +EXPORT_SYMBOL(il_read_targ_mem); + +void +il_write_targ_mem(struct il_priv *il, u32 addr, u32 val) +{ + unsigned long reg_flags; + + spin_lock_irqsave(&il->reg_lock, reg_flags); + if (likely(_il_grab_nic_access(il))) { + _il_wr(il, HBUS_TARG_MEM_WADDR, addr); + _il_wr(il, HBUS_TARG_MEM_WDAT, val); + _il_release_nic_access(il); + } + spin_unlock_irqrestore(&il->reg_lock, reg_flags); +} +EXPORT_SYMBOL(il_write_targ_mem); + +const char * +il_get_cmd_string(u8 cmd) +{ + switch (cmd) { + IL_CMD(N_ALIVE); + IL_CMD(N_ERROR); + IL_CMD(C_RXON); + IL_CMD(C_RXON_ASSOC); + IL_CMD(C_QOS_PARAM); + IL_CMD(C_RXON_TIMING); + IL_CMD(C_ADD_STA); + IL_CMD(C_REM_STA); + IL_CMD(C_WEPKEY); + IL_CMD(N_3945_RX); + IL_CMD(C_TX); + IL_CMD(C_RATE_SCALE); + IL_CMD(C_LEDS); + IL_CMD(C_TX_LINK_QUALITY_CMD); + IL_CMD(C_CHANNEL_SWITCH); + IL_CMD(N_CHANNEL_SWITCH); + IL_CMD(C_SPECTRUM_MEASUREMENT); + IL_CMD(N_SPECTRUM_MEASUREMENT); + IL_CMD(C_POWER_TBL); + IL_CMD(N_PM_SLEEP); + IL_CMD(N_PM_DEBUG_STATS); + IL_CMD(C_SCAN); + IL_CMD(C_SCAN_ABORT); + IL_CMD(N_SCAN_START); + IL_CMD(N_SCAN_RESULTS); + IL_CMD(N_SCAN_COMPLETE); + IL_CMD(N_BEACON); + IL_CMD(C_TX_BEACON); + IL_CMD(C_TX_PWR_TBL); + IL_CMD(C_BT_CONFIG); + IL_CMD(C_STATS); + IL_CMD(N_STATS); + IL_CMD(N_CARD_STATE); + IL_CMD(N_MISSED_BEACONS); + IL_CMD(C_CT_KILL_CONFIG); + IL_CMD(C_SENSITIVITY); + IL_CMD(C_PHY_CALIBRATION); + IL_CMD(N_RX_PHY); + IL_CMD(N_RX_MPDU); + IL_CMD(N_RX); + IL_CMD(N_COMPRESSED_BA); + default: + return "UNKNOWN"; + + } +} +EXPORT_SYMBOL(il_get_cmd_string); + +#define HOST_COMPLETE_TIMEOUT (HZ / 2) + +static void +il_generic_cmd_callback(struct il_priv *il, struct il_device_cmd *cmd, + struct il_rx_pkt *pkt) +{ + if (pkt->hdr.flags & IL_CMD_FAILED_MSK) { + IL_ERR("Bad return from %s (0x%08X)\n", + il_get_cmd_string(cmd->hdr.cmd), pkt->hdr.flags); + return; + } +#ifdef CPTCFG_IWLEGACY_DEBUG + switch (cmd->hdr.cmd) { + case C_TX_LINK_QUALITY_CMD: + case C_SENSITIVITY: + D_HC_DUMP("back from %s (0x%08X)\n", + il_get_cmd_string(cmd->hdr.cmd), pkt->hdr.flags); + break; + default: + D_HC("back from %s (0x%08X)\n", il_get_cmd_string(cmd->hdr.cmd), + pkt->hdr.flags); + } +#endif +} + +static int +il_send_cmd_async(struct il_priv *il, struct il_host_cmd *cmd) +{ + int ret; + + BUG_ON(!(cmd->flags & CMD_ASYNC)); + + /* An asynchronous command can not expect an SKB to be set. */ + BUG_ON(cmd->flags & CMD_WANT_SKB); + + /* Assign a generic callback if one is not provided */ + if (!cmd->callback) + cmd->callback = il_generic_cmd_callback; + + if (test_bit(S_EXIT_PENDING, &il->status)) + return -EBUSY; + + ret = il_enqueue_hcmd(il, cmd); + if (ret < 0) { + IL_ERR("Error sending %s: enqueue_hcmd failed: %d\n", + il_get_cmd_string(cmd->id), ret); + return ret; + } + return 0; +} + +int +il_send_cmd_sync(struct il_priv *il, struct il_host_cmd *cmd) +{ + int cmd_idx; + int ret; + + lockdep_assert_held(&il->mutex); + + BUG_ON(cmd->flags & CMD_ASYNC); + + /* A synchronous command can not have a callback set. */ + BUG_ON(cmd->callback); + + D_INFO("Attempting to send sync command %s\n", + il_get_cmd_string(cmd->id)); + + set_bit(S_HCMD_ACTIVE, &il->status); + D_INFO("Setting HCMD_ACTIVE for command %s\n", + il_get_cmd_string(cmd->id)); + + cmd_idx = il_enqueue_hcmd(il, cmd); + if (cmd_idx < 0) { + ret = cmd_idx; + IL_ERR("Error sending %s: enqueue_hcmd failed: %d\n", + il_get_cmd_string(cmd->id), ret); + goto out; + } + + ret = wait_event_timeout(il->wait_command_queue, + !test_bit(S_HCMD_ACTIVE, &il->status), + HOST_COMPLETE_TIMEOUT); + if (!ret) { + if (test_bit(S_HCMD_ACTIVE, &il->status)) { + IL_ERR("Error sending %s: time out after %dms.\n", + il_get_cmd_string(cmd->id), + jiffies_to_msecs(HOST_COMPLETE_TIMEOUT)); + + clear_bit(S_HCMD_ACTIVE, &il->status); + D_INFO("Clearing HCMD_ACTIVE for command %s\n", + il_get_cmd_string(cmd->id)); + ret = -ETIMEDOUT; + goto cancel; + } + } + + if (test_bit(S_RFKILL, &il->status)) { + IL_ERR("Command %s aborted: RF KILL Switch\n", + il_get_cmd_string(cmd->id)); + ret = -ECANCELED; + goto fail; + } + if (test_bit(S_FW_ERROR, &il->status)) { + IL_ERR("Command %s failed: FW Error\n", + il_get_cmd_string(cmd->id)); + ret = -EIO; + goto fail; + } + if ((cmd->flags & CMD_WANT_SKB) && !cmd->reply_page) { + IL_ERR("Error: Response NULL in '%s'\n", + il_get_cmd_string(cmd->id)); + ret = -EIO; + goto cancel; + } + + ret = 0; + goto out; + +cancel: + if (cmd->flags & CMD_WANT_SKB) { + /* + * Cancel the CMD_WANT_SKB flag for the cmd in the + * TX cmd queue. Otherwise in case the cmd comes + * in later, it will possibly set an invalid + * address (cmd->meta.source). + */ + il->txq[il->cmd_queue].meta[cmd_idx].flags &= ~CMD_WANT_SKB; + } +fail: + if (cmd->reply_page) { + il_free_pages(il, cmd->reply_page); + cmd->reply_page = 0; + } +out: + return ret; +} +EXPORT_SYMBOL(il_send_cmd_sync); + +int +il_send_cmd(struct il_priv *il, struct il_host_cmd *cmd) +{ + if (cmd->flags & CMD_ASYNC) + return il_send_cmd_async(il, cmd); + + return il_send_cmd_sync(il, cmd); +} +EXPORT_SYMBOL(il_send_cmd); + +int +il_send_cmd_pdu(struct il_priv *il, u8 id, u16 len, const void *data) +{ + struct il_host_cmd cmd = { + .id = id, + .len = len, + .data = data, + }; + + return il_send_cmd_sync(il, &cmd); +} +EXPORT_SYMBOL(il_send_cmd_pdu); + +int +il_send_cmd_pdu_async(struct il_priv *il, u8 id, u16 len, const void *data, + void (*callback) (struct il_priv *il, + struct il_device_cmd *cmd, + struct il_rx_pkt *pkt)) +{ + struct il_host_cmd cmd = { + .id = id, + .len = len, + .data = data, + }; + + cmd.flags |= CMD_ASYNC; + cmd.callback = callback; + + return il_send_cmd_async(il, &cmd); +} +EXPORT_SYMBOL(il_send_cmd_pdu_async); + +/* default: IL_LED_BLINK(0) using blinking idx table */ +static int led_mode; +module_param(led_mode, int, S_IRUGO); +MODULE_PARM_DESC(led_mode, + "0=system default, " "1=On(RF On)/Off(RF Off), 2=blinking"); + +/* Throughput OFF time(ms) ON time (ms) + * >300 25 25 + * >200 to 300 40 40 + * >100 to 200 55 55 + * >70 to 100 65 65 + * >50 to 70 75 75 + * >20 to 50 85 85 + * >10 to 20 95 95 + * >5 to 10 110 110 + * >1 to 5 130 130 + * >0 to 1 167 167 + * <=0 SOLID ON + */ +static const struct ieee80211_tpt_blink il_blink[] = { + {.throughput = 0, .blink_time = 334}, + {.throughput = 1 * 1024 - 1, .blink_time = 260}, + {.throughput = 5 * 1024 - 1, .blink_time = 220}, + {.throughput = 10 * 1024 - 1, .blink_time = 190}, + {.throughput = 20 * 1024 - 1, .blink_time = 170}, + {.throughput = 50 * 1024 - 1, .blink_time = 150}, + {.throughput = 70 * 1024 - 1, .blink_time = 130}, + {.throughput = 100 * 1024 - 1, .blink_time = 110}, + {.throughput = 200 * 1024 - 1, .blink_time = 80}, + {.throughput = 300 * 1024 - 1, .blink_time = 50}, +}; + +/* + * Adjust led blink rate to compensate on a MAC Clock difference on every HW + * Led blink rate analysis showed an average deviation of 0% on 3945, + * 5% on 4965 HW. + * Need to compensate on the led on/off time per HW according to the deviation + * to achieve the desired led frequency + * The calculation is: (100-averageDeviation)/100 * blinkTime + * For code efficiency the calculation will be: + * compensation = (100 - averageDeviation) * 64 / 100 + * NewBlinkTime = (compensation * BlinkTime) / 64 + */ +static inline u8 +il_blink_compensation(struct il_priv *il, u8 time, u16 compensation) +{ + if (!compensation) { + IL_ERR("undefined blink compensation: " + "use pre-defined blinking time\n"); + return time; + } + + return (u8) ((time * compensation) >> 6); +} + +/* Set led pattern command */ +static int +il_led_cmd(struct il_priv *il, unsigned long on, unsigned long off) +{ + struct il_led_cmd led_cmd = { + .id = IL_LED_LINK, + .interval = IL_DEF_LED_INTRVL + }; + int ret; + + if (!test_bit(S_READY, &il->status)) + return -EBUSY; + + if (il->blink_on == on && il->blink_off == off) + return 0; + + if (off == 0) { + /* led is SOLID_ON */ + on = IL_LED_SOLID; + } + + D_LED("Led blink time compensation=%u\n", + il->cfg->led_compensation); + led_cmd.on = + il_blink_compensation(il, on, + il->cfg->led_compensation); + led_cmd.off = + il_blink_compensation(il, off, + il->cfg->led_compensation); + + ret = il->ops->send_led_cmd(il, &led_cmd); + if (!ret) { + il->blink_on = on; + il->blink_off = off; + } + return ret; +} + +static void +il_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct il_priv *il = container_of(led_cdev, struct il_priv, led); + unsigned long on = 0; + + if (brightness > 0) + on = IL_LED_SOLID; + + il_led_cmd(il, on, 0); +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)) +static int +il_led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on, + unsigned long *delay_off) +{ + struct il_priv *il = container_of(led_cdev, struct il_priv, led); + + return il_led_cmd(il, *delay_on, *delay_off); +} +#endif + +void +il_leds_init(struct il_priv *il) +{ + int mode = led_mode; + int ret; + + if (mode == IL_LED_DEFAULT) + mode = il->cfg->led_mode; + + il->led.name = + kasprintf(GFP_KERNEL, "%s-led", wiphy_name(il->hw->wiphy)); + il->led.brightness_set = il_led_brightness_set; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)) + il->led.blink_set = il_led_blink_set; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) + il->led.max_brightness = 1; +#endif + + switch (mode) { + case IL_LED_DEFAULT: + WARN_ON(1); + break; + case IL_LED_BLINK: + il->led.default_trigger = + ieee80211_create_tpt_led_trigger(il->hw, + IEEE80211_TPT_LEDTRIG_FL_CONNECTED, + il_blink, + ARRAY_SIZE(il_blink)); + break; + case IL_LED_RF_STATE: + il->led.default_trigger = ieee80211_get_radio_led_name(il->hw); + break; + } + + ret = led_classdev_register(&il->pci_dev->dev, &il->led); + if (ret) { + kfree(il->led.name); + return; + } + + il->led_registered = true; +} +EXPORT_SYMBOL(il_leds_init); + +void +il_leds_exit(struct il_priv *il) +{ + if (!il->led_registered) + return; + + led_classdev_unregister(&il->led); + kfree(il->led.name); +} +EXPORT_SYMBOL(il_leds_exit); + +/************************** EEPROM BANDS **************************** + * + * The il_eeprom_band definitions below provide the mapping from the + * EEPROM contents to the specific channel number supported for each + * band. + * + * For example, il_priv->eeprom.band_3_channels[4] from the band_3 + * definition below maps to physical channel 42 in the 5.2GHz spectrum. + * The specific geography and calibration information for that channel + * is contained in the eeprom map itself. + * + * During init, we copy the eeprom information and channel map + * information into il->channel_info_24/52 and il->channel_map_24/52 + * + * channel_map_24/52 provides the idx in the channel_info array for a + * given channel. We have to have two separate maps as there is channel + * overlap with the 2.4GHz and 5.2GHz spectrum as seen in band_1 and + * band_2 + * + * A value of 0xff stored in the channel_map indicates that the channel + * is not supported by the hardware at all. + * + * A value of 0xfe in the channel_map indicates that the channel is not + * valid for Tx with the current hardware. This means that + * while the system can tune and receive on a given channel, it may not + * be able to associate or transmit any frames on that + * channel. There is no corresponding channel information for that + * entry. + * + *********************************************************************/ + +/* 2.4 GHz */ +const u8 il_eeprom_band_1[14] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 +}; + +/* 5.2 GHz bands */ +static const u8 il_eeprom_band_2[] = { /* 4915-5080MHz */ + 183, 184, 185, 187, 188, 189, 192, 196, 7, 8, 11, 12, 16 +}; + +static const u8 il_eeprom_band_3[] = { /* 5170-5320MHz */ + 34, 36, 38, 40, 42, 44, 46, 48, 52, 56, 60, 64 +}; + +static const u8 il_eeprom_band_4[] = { /* 5500-5700MHz */ + 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140 +}; + +static const u8 il_eeprom_band_5[] = { /* 5725-5825MHz */ + 145, 149, 153, 157, 161, 165 +}; + +static const u8 il_eeprom_band_6[] = { /* 2.4 ht40 channel */ + 1, 2, 3, 4, 5, 6, 7 +}; + +static const u8 il_eeprom_band_7[] = { /* 5.2 ht40 channel */ + 36, 44, 52, 60, 100, 108, 116, 124, 132, 149, 157 +}; + +/****************************************************************************** + * + * EEPROM related functions + * +******************************************************************************/ + +static int +il_eeprom_verify_signature(struct il_priv *il) +{ + u32 gp = _il_rd(il, CSR_EEPROM_GP) & CSR_EEPROM_GP_VALID_MSK; + int ret = 0; + + D_EEPROM("EEPROM signature=0x%08x\n", gp); + switch (gp) { + case CSR_EEPROM_GP_GOOD_SIG_EEP_LESS_THAN_4K: + case CSR_EEPROM_GP_GOOD_SIG_EEP_MORE_THAN_4K: + break; + default: + IL_ERR("bad EEPROM signature," "EEPROM_GP=0x%08x\n", gp); + ret = -ENOENT; + break; + } + return ret; +} + +const u8 * +il_eeprom_query_addr(const struct il_priv *il, size_t offset) +{ + BUG_ON(offset >= il->cfg->eeprom_size); + return &il->eeprom[offset]; +} +EXPORT_SYMBOL(il_eeprom_query_addr); + +u16 +il_eeprom_query16(const struct il_priv *il, size_t offset) +{ + if (!il->eeprom) + return 0; + return (u16) il->eeprom[offset] | ((u16) il->eeprom[offset + 1] << 8); +} +EXPORT_SYMBOL(il_eeprom_query16); + +/** + * il_eeprom_init - read EEPROM contents + * + * Load the EEPROM contents from adapter into il->eeprom + * + * NOTE: This routine uses the non-debug IO access functions. + */ +int +il_eeprom_init(struct il_priv *il) +{ + __le16 *e; + u32 gp = _il_rd(il, CSR_EEPROM_GP); + int sz; + int ret; + u16 addr; + + /* allocate eeprom */ + sz = il->cfg->eeprom_size; + D_EEPROM("NVM size = %d\n", sz); + il->eeprom = kzalloc(sz, GFP_KERNEL); + if (!il->eeprom) { + ret = -ENOMEM; + goto alloc_err; + } + e = (__le16 *) il->eeprom; + + il->ops->apm_init(il); + + ret = il_eeprom_verify_signature(il); + if (ret < 0) { + IL_ERR("EEPROM not found, EEPROM_GP=0x%08x\n", gp); + ret = -ENOENT; + goto err; + } + + /* Make sure driver (instead of uCode) is allowed to read EEPROM */ + ret = il->ops->eeprom_acquire_semaphore(il); + if (ret < 0) { + IL_ERR("Failed to acquire EEPROM semaphore.\n"); + ret = -ENOENT; + goto err; + } + + /* eeprom is an array of 16bit values */ + for (addr = 0; addr < sz; addr += sizeof(u16)) { + u32 r; + + _il_wr(il, CSR_EEPROM_REG, + CSR_EEPROM_REG_MSK_ADDR & (addr << 1)); + + ret = + _il_poll_bit(il, CSR_EEPROM_REG, + CSR_EEPROM_REG_READ_VALID_MSK, + CSR_EEPROM_REG_READ_VALID_MSK, + IL_EEPROM_ACCESS_TIMEOUT); + if (ret < 0) { + IL_ERR("Time out reading EEPROM[%d]\n", addr); + goto done; + } + r = _il_rd(il, CSR_EEPROM_REG); + e[addr / 2] = cpu_to_le16(r >> 16); + } + + D_EEPROM("NVM Type: %s, version: 0x%x\n", "EEPROM", + il_eeprom_query16(il, EEPROM_VERSION)); + + ret = 0; +done: + il->ops->eeprom_release_semaphore(il); + +err: + if (ret) + il_eeprom_free(il); + /* Reset chip to save power until we load uCode during "up". */ + il_apm_stop(il); +alloc_err: + return ret; +} +EXPORT_SYMBOL(il_eeprom_init); + +void +il_eeprom_free(struct il_priv *il) +{ + kfree(il->eeprom); + il->eeprom = NULL; +} +EXPORT_SYMBOL(il_eeprom_free); + +static void +il_init_band_reference(const struct il_priv *il, int eep_band, + int *eeprom_ch_count, + const struct il_eeprom_channel **eeprom_ch_info, + const u8 **eeprom_ch_idx) +{ + u32 offset = il->cfg->regulatory_bands[eep_band - 1]; + + switch (eep_band) { + case 1: /* 2.4GHz band */ + *eeprom_ch_count = ARRAY_SIZE(il_eeprom_band_1); + *eeprom_ch_info = + (struct il_eeprom_channel *)il_eeprom_query_addr(il, + offset); + *eeprom_ch_idx = il_eeprom_band_1; + break; + case 2: /* 4.9GHz band */ + *eeprom_ch_count = ARRAY_SIZE(il_eeprom_band_2); + *eeprom_ch_info = + (struct il_eeprom_channel *)il_eeprom_query_addr(il, + offset); + *eeprom_ch_idx = il_eeprom_band_2; + break; + case 3: /* 5.2GHz band */ + *eeprom_ch_count = ARRAY_SIZE(il_eeprom_band_3); + *eeprom_ch_info = + (struct il_eeprom_channel *)il_eeprom_query_addr(il, + offset); + *eeprom_ch_idx = il_eeprom_band_3; + break; + case 4: /* 5.5GHz band */ + *eeprom_ch_count = ARRAY_SIZE(il_eeprom_band_4); + *eeprom_ch_info = + (struct il_eeprom_channel *)il_eeprom_query_addr(il, + offset); + *eeprom_ch_idx = il_eeprom_band_4; + break; + case 5: /* 5.7GHz band */ + *eeprom_ch_count = ARRAY_SIZE(il_eeprom_band_5); + *eeprom_ch_info = + (struct il_eeprom_channel *)il_eeprom_query_addr(il, + offset); + *eeprom_ch_idx = il_eeprom_band_5; + break; + case 6: /* 2.4GHz ht40 channels */ + *eeprom_ch_count = ARRAY_SIZE(il_eeprom_band_6); + *eeprom_ch_info = + (struct il_eeprom_channel *)il_eeprom_query_addr(il, + offset); + *eeprom_ch_idx = il_eeprom_band_6; + break; + case 7: /* 5 GHz ht40 channels */ + *eeprom_ch_count = ARRAY_SIZE(il_eeprom_band_7); + *eeprom_ch_info = + (struct il_eeprom_channel *)il_eeprom_query_addr(il, + offset); + *eeprom_ch_idx = il_eeprom_band_7; + break; + default: + BUG(); + } +} + +#define CHECK_AND_PRINT(x) ((eeprom_ch->flags & EEPROM_CHANNEL_##x) \ + ? # x " " : "") +/** + * il_mod_ht40_chan_info - Copy ht40 channel info into driver's il. + * + * Does not set up a command, or touch hardware. + */ +static int +il_mod_ht40_chan_info(struct il_priv *il, enum ieee80211_band band, u16 channel, + const struct il_eeprom_channel *eeprom_ch, + u8 clear_ht40_extension_channel) +{ + struct il_channel_info *ch_info; + + ch_info = + (struct il_channel_info *)il_get_channel_info(il, band, channel); + + if (!il_is_channel_valid(ch_info)) + return -1; + + D_EEPROM("HT40 Ch. %d [%sGHz] %s%s%s%s%s(0x%02x %ddBm):" + " Ad-Hoc %ssupported\n", ch_info->channel, + il_is_channel_a_band(ch_info) ? "5.2" : "2.4", + CHECK_AND_PRINT(IBSS), CHECK_AND_PRINT(ACTIVE), + CHECK_AND_PRINT(RADAR), CHECK_AND_PRINT(WIDE), + CHECK_AND_PRINT(DFS), eeprom_ch->flags, + eeprom_ch->max_power_avg, + ((eeprom_ch->flags & EEPROM_CHANNEL_IBSS) && + !(eeprom_ch->flags & EEPROM_CHANNEL_RADAR)) ? "" : "not "); + + ch_info->ht40_eeprom = *eeprom_ch; + ch_info->ht40_max_power_avg = eeprom_ch->max_power_avg; + ch_info->ht40_flags = eeprom_ch->flags; + if (eeprom_ch->flags & EEPROM_CHANNEL_VALID) + ch_info->ht40_extension_channel &= + ~clear_ht40_extension_channel; + + return 0; +} + +#define CHECK_AND_PRINT_I(x) ((eeprom_ch_info[ch].flags & EEPROM_CHANNEL_##x) \ + ? # x " " : "") + +/** + * il_init_channel_map - Set up driver's info for all possible channels + */ +int +il_init_channel_map(struct il_priv *il) +{ + int eeprom_ch_count = 0; + const u8 *eeprom_ch_idx = NULL; + const struct il_eeprom_channel *eeprom_ch_info = NULL; + int band, ch; + struct il_channel_info *ch_info; + + if (il->channel_count) { + D_EEPROM("Channel map already initialized.\n"); + return 0; + } + + D_EEPROM("Initializing regulatory info from EEPROM\n"); + + il->channel_count = + ARRAY_SIZE(il_eeprom_band_1) + ARRAY_SIZE(il_eeprom_band_2) + + ARRAY_SIZE(il_eeprom_band_3) + ARRAY_SIZE(il_eeprom_band_4) + + ARRAY_SIZE(il_eeprom_band_5); + + D_EEPROM("Parsing data for %d channels.\n", il->channel_count); + + il->channel_info = + kzalloc(sizeof(struct il_channel_info) * il->channel_count, + GFP_KERNEL); + if (!il->channel_info) { + IL_ERR("Could not allocate channel_info\n"); + il->channel_count = 0; + return -ENOMEM; + } + + ch_info = il->channel_info; + + /* Loop through the 5 EEPROM bands adding them in order to the + * channel map we maintain (that contains additional information than + * what just in the EEPROM) */ + for (band = 1; band <= 5; band++) { + + il_init_band_reference(il, band, &eeprom_ch_count, + &eeprom_ch_info, &eeprom_ch_idx); + + /* Loop through each band adding each of the channels */ + for (ch = 0; ch < eeprom_ch_count; ch++) { + ch_info->channel = eeprom_ch_idx[ch]; + ch_info->band = + (band == + 1) ? IEEE80211_BAND_2GHZ : IEEE80211_BAND_5GHZ; + + /* permanently store EEPROM's channel regulatory flags + * and max power in channel info database. */ + ch_info->eeprom = eeprom_ch_info[ch]; + + /* Copy the run-time flags so they are there even on + * invalid channels */ + ch_info->flags = eeprom_ch_info[ch].flags; + /* First write that ht40 is not enabled, and then enable + * one by one */ + ch_info->ht40_extension_channel = + IEEE80211_CHAN_NO_HT40; + + if (!(il_is_channel_valid(ch_info))) { + D_EEPROM("Ch. %d Flags %x [%sGHz] - " + "No traffic\n", ch_info->channel, + ch_info->flags, + il_is_channel_a_band(ch_info) ? "5.2" : + "2.4"); + ch_info++; + continue; + } + + /* Initialize regulatory-based run-time data */ + ch_info->max_power_avg = ch_info->curr_txpow = + eeprom_ch_info[ch].max_power_avg; + ch_info->scan_power = eeprom_ch_info[ch].max_power_avg; + ch_info->min_power = 0; + + D_EEPROM("Ch. %d [%sGHz] " "%s%s%s%s%s%s(0x%02x %ddBm):" + " Ad-Hoc %ssupported\n", ch_info->channel, + il_is_channel_a_band(ch_info) ? "5.2" : "2.4", + CHECK_AND_PRINT_I(VALID), + CHECK_AND_PRINT_I(IBSS), + CHECK_AND_PRINT_I(ACTIVE), + CHECK_AND_PRINT_I(RADAR), + CHECK_AND_PRINT_I(WIDE), + CHECK_AND_PRINT_I(DFS), + eeprom_ch_info[ch].flags, + eeprom_ch_info[ch].max_power_avg, + ((eeprom_ch_info[ch]. + flags & EEPROM_CHANNEL_IBSS) && + !(eeprom_ch_info[ch]. + flags & EEPROM_CHANNEL_RADAR)) ? "" : + "not "); + + ch_info++; + } + } + + /* Check if we do have HT40 channels */ + if (il->cfg->regulatory_bands[5] == EEPROM_REGULATORY_BAND_NO_HT40 && + il->cfg->regulatory_bands[6] == EEPROM_REGULATORY_BAND_NO_HT40) + return 0; + + /* Two additional EEPROM bands for 2.4 and 5 GHz HT40 channels */ + for (band = 6; band <= 7; band++) { + enum ieee80211_band ieeeband; + + il_init_band_reference(il, band, &eeprom_ch_count, + &eeprom_ch_info, &eeprom_ch_idx); + + /* EEPROM band 6 is 2.4, band 7 is 5 GHz */ + ieeeband = + (band == 6) ? IEEE80211_BAND_2GHZ : IEEE80211_BAND_5GHZ; + + /* Loop through each band adding each of the channels */ + for (ch = 0; ch < eeprom_ch_count; ch++) { + /* Set up driver's info for lower half */ + il_mod_ht40_chan_info(il, ieeeband, eeprom_ch_idx[ch], + &eeprom_ch_info[ch], + IEEE80211_CHAN_NO_HT40PLUS); + + /* Set up driver's info for upper half */ + il_mod_ht40_chan_info(il, ieeeband, + eeprom_ch_idx[ch] + 4, + &eeprom_ch_info[ch], + IEEE80211_CHAN_NO_HT40MINUS); + } + } + + return 0; +} +EXPORT_SYMBOL(il_init_channel_map); + +/* + * il_free_channel_map - undo allocations in il_init_channel_map + */ +void +il_free_channel_map(struct il_priv *il) +{ + kfree(il->channel_info); + il->channel_count = 0; +} +EXPORT_SYMBOL(il_free_channel_map); + +/** + * il_get_channel_info - Find driver's ilate channel info + * + * Based on band and channel number. + */ +const struct il_channel_info * +il_get_channel_info(const struct il_priv *il, enum ieee80211_band band, + u16 channel) +{ + int i; + + switch (band) { + case IEEE80211_BAND_5GHZ: + for (i = 14; i < il->channel_count; i++) { + if (il->channel_info[i].channel == channel) + return &il->channel_info[i]; + } + break; + case IEEE80211_BAND_2GHZ: + if (channel >= 1 && channel <= 14) + return &il->channel_info[channel - 1]; + break; + default: + BUG(); + } + + return NULL; +} +EXPORT_SYMBOL(il_get_channel_info); + +/* + * Setting power level allows the card to go to sleep when not busy. + * + * We calculate a sleep command based on the required latency, which + * we get from mac80211. In order to handle thermal throttling, we can + * also use pre-defined power levels. + */ + +/* + * This defines the old power levels. They are still used by default + * (level 1) and for thermal throttle (levels 3 through 5) + */ + +struct il_power_vec_entry { + struct il_powertable_cmd cmd; + u8 no_dtim; /* number of skip dtim */ +}; + +static void +il_power_sleep_cam_cmd(struct il_priv *il, struct il_powertable_cmd *cmd) +{ + memset(cmd, 0, sizeof(*cmd)); + + if (il->power_data.pci_pm) + cmd->flags |= IL_POWER_PCI_PM_MSK; + + D_POWER("Sleep command for CAM\n"); +} + +static int +il_set_power(struct il_priv *il, struct il_powertable_cmd *cmd) +{ + D_POWER("Sending power/sleep command\n"); + D_POWER("Flags value = 0x%08X\n", cmd->flags); + D_POWER("Tx timeout = %u\n", le32_to_cpu(cmd->tx_data_timeout)); + D_POWER("Rx timeout = %u\n", le32_to_cpu(cmd->rx_data_timeout)); + D_POWER("Sleep interval vector = { %d , %d , %d , %d , %d }\n", + le32_to_cpu(cmd->sleep_interval[0]), + le32_to_cpu(cmd->sleep_interval[1]), + le32_to_cpu(cmd->sleep_interval[2]), + le32_to_cpu(cmd->sleep_interval[3]), + le32_to_cpu(cmd->sleep_interval[4])); + + return il_send_cmd_pdu(il, C_POWER_TBL, + sizeof(struct il_powertable_cmd), cmd); +} + +static int +il_power_set_mode(struct il_priv *il, struct il_powertable_cmd *cmd, bool force) +{ + int ret; + bool update_chains; + + lockdep_assert_held(&il->mutex); + + /* Don't update the RX chain when chain noise calibration is running */ + update_chains = il->chain_noise_data.state == IL_CHAIN_NOISE_DONE || + il->chain_noise_data.state == IL_CHAIN_NOISE_ALIVE; + + if (!memcmp(&il->power_data.sleep_cmd, cmd, sizeof(*cmd)) && !force) + return 0; + + if (!il_is_ready_rf(il)) + return -EIO; + + /* scan complete use sleep_power_next, need to be updated */ + memcpy(&il->power_data.sleep_cmd_next, cmd, sizeof(*cmd)); + if (test_bit(S_SCANNING, &il->status) && !force) { + D_INFO("Defer power set mode while scanning\n"); + return 0; + } + + if (cmd->flags & IL_POWER_DRIVER_ALLOW_SLEEP_MSK) + set_bit(S_POWER_PMI, &il->status); + + ret = il_set_power(il, cmd); + if (!ret) { + if (!(cmd->flags & IL_POWER_DRIVER_ALLOW_SLEEP_MSK)) + clear_bit(S_POWER_PMI, &il->status); + + if (il->ops->update_chain_flags && update_chains) + il->ops->update_chain_flags(il); + else if (il->ops->update_chain_flags) + D_POWER("Cannot update the power, chain noise " + "calibration running: %d\n", + il->chain_noise_data.state); + + memcpy(&il->power_data.sleep_cmd, cmd, sizeof(*cmd)); + } else + IL_ERR("set power fail, ret = %d", ret); + + return ret; +} + +int +il_power_update_mode(struct il_priv *il, bool force) +{ + struct il_powertable_cmd cmd; + + il_power_sleep_cam_cmd(il, &cmd); + return il_power_set_mode(il, &cmd, force); +} +EXPORT_SYMBOL(il_power_update_mode); + +/* initialize to default */ +void +il_power_initialize(struct il_priv *il) +{ + u16 lctl; + + pcie_capability_read_word(il->pci_dev, PCI_EXP_LNKCTL, &lctl); + il->power_data.pci_pm = !(lctl & PCI_EXP_LNKCTL_ASPM_L0S); + + il->power_data.debug_sleep_level_override = -1; + + memset(&il->power_data.sleep_cmd, 0, sizeof(il->power_data.sleep_cmd)); +} +EXPORT_SYMBOL(il_power_initialize); + +/* For active scan, listen ACTIVE_DWELL_TIME (msec) on each channel after + * sending probe req. This should be set long enough to hear probe responses + * from more than one AP. */ +#define IL_ACTIVE_DWELL_TIME_24 (30) /* all times in msec */ +#define IL_ACTIVE_DWELL_TIME_52 (20) + +#define IL_ACTIVE_DWELL_FACTOR_24GHZ (3) +#define IL_ACTIVE_DWELL_FACTOR_52GHZ (2) + +/* For passive scan, listen PASSIVE_DWELL_TIME (msec) on each channel. + * Must be set longer than active dwell time. + * For the most reliable scan, set > AP beacon interval (typically 100msec). */ +#define IL_PASSIVE_DWELL_TIME_24 (20) /* all times in msec */ +#define IL_PASSIVE_DWELL_TIME_52 (10) +#define IL_PASSIVE_DWELL_BASE (100) +#define IL_CHANNEL_TUNE_TIME 5 + +static int +il_send_scan_abort(struct il_priv *il) +{ + int ret; + struct il_rx_pkt *pkt; + struct il_host_cmd cmd = { + .id = C_SCAN_ABORT, + .flags = CMD_WANT_SKB, + }; + + /* Exit instantly with error when device is not ready + * to receive scan abort command or it does not perform + * hardware scan currently */ + if (!test_bit(S_READY, &il->status) || + !test_bit(S_GEO_CONFIGURED, &il->status) || + !test_bit(S_SCAN_HW, &il->status) || + test_bit(S_FW_ERROR, &il->status) || + test_bit(S_EXIT_PENDING, &il->status)) + return -EIO; + + ret = il_send_cmd_sync(il, &cmd); + if (ret) + return ret; + + pkt = (struct il_rx_pkt *)cmd.reply_page; + if (pkt->u.status != CAN_ABORT_STATUS) { + /* The scan abort will return 1 for success or + * 2 for "failure". A failure condition can be + * due to simply not being in an active scan which + * can occur if we send the scan abort before we + * the microcode has notified us that a scan is + * completed. */ + D_SCAN("SCAN_ABORT ret %d.\n", pkt->u.status); + ret = -EIO; + } + + il_free_pages(il, cmd.reply_page); + return ret; +} + +static void +il_complete_scan(struct il_priv *il, bool aborted) +{ + /* check if scan was requested from mac80211 */ + if (il->scan_request) { + D_SCAN("Complete scan in mac80211\n"); + ieee80211_scan_completed(il->hw, aborted); + } + + il->scan_vif = NULL; + il->scan_request = NULL; +} + +void +il_force_scan_end(struct il_priv *il) +{ + lockdep_assert_held(&il->mutex); + + if (!test_bit(S_SCANNING, &il->status)) { + D_SCAN("Forcing scan end while not scanning\n"); + return; + } + + D_SCAN("Forcing scan end\n"); + clear_bit(S_SCANNING, &il->status); + clear_bit(S_SCAN_HW, &il->status); + clear_bit(S_SCAN_ABORTING, &il->status); + il_complete_scan(il, true); +} + +static void +il_do_scan_abort(struct il_priv *il) +{ + int ret; + + lockdep_assert_held(&il->mutex); + + if (!test_bit(S_SCANNING, &il->status)) { + D_SCAN("Not performing scan to abort\n"); + return; + } + + if (test_and_set_bit(S_SCAN_ABORTING, &il->status)) { + D_SCAN("Scan abort in progress\n"); + return; + } + + ret = il_send_scan_abort(il); + if (ret) { + D_SCAN("Send scan abort failed %d\n", ret); + il_force_scan_end(il); + } else + D_SCAN("Successfully send scan abort\n"); +} + +/** + * il_scan_cancel - Cancel any currently executing HW scan + */ +int +il_scan_cancel(struct il_priv *il) +{ + D_SCAN("Queuing abort scan\n"); + queue_work(il->workqueue, &il->abort_scan); + return 0; +} +EXPORT_SYMBOL(il_scan_cancel); + +/** + * il_scan_cancel_timeout - Cancel any currently executing HW scan + * @ms: amount of time to wait (in milliseconds) for scan to abort + * + */ +int +il_scan_cancel_timeout(struct il_priv *il, unsigned long ms) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(ms); + + lockdep_assert_held(&il->mutex); + + D_SCAN("Scan cancel timeout\n"); + + il_do_scan_abort(il); + + while (time_before_eq(jiffies, timeout)) { + if (!test_bit(S_SCAN_HW, &il->status)) + break; + msleep(20); + } + + return test_bit(S_SCAN_HW, &il->status); +} +EXPORT_SYMBOL(il_scan_cancel_timeout); + +/* Service response to C_SCAN (0x80) */ +static void +il_hdl_scan(struct il_priv *il, struct il_rx_buf *rxb) +{ +#ifdef CPTCFG_IWLEGACY_DEBUG + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il_scanreq_notification *notif = + (struct il_scanreq_notification *)pkt->u.raw; + + D_SCAN("Scan request status = 0x%x\n", notif->status); +#endif +} + +/* Service N_SCAN_START (0x82) */ +static void +il_hdl_scan_start(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il_scanstart_notification *notif = + (struct il_scanstart_notification *)pkt->u.raw; + il->scan_start_tsf = le32_to_cpu(notif->tsf_low); + D_SCAN("Scan start: " "%d [802.11%s] " + "(TSF: 0x%08X:%08X) - %d (beacon timer %u)\n", notif->channel, + notif->band ? "bg" : "a", le32_to_cpu(notif->tsf_high), + le32_to_cpu(notif->tsf_low), notif->status, notif->beacon_timer); +} + +/* Service N_SCAN_RESULTS (0x83) */ +static void +il_hdl_scan_results(struct il_priv *il, struct il_rx_buf *rxb) +{ +#ifdef CPTCFG_IWLEGACY_DEBUG + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il_scanresults_notification *notif = + (struct il_scanresults_notification *)pkt->u.raw; + + D_SCAN("Scan ch.res: " "%d [802.11%s] " "(TSF: 0x%08X:%08X) - %d " + "elapsed=%lu usec\n", notif->channel, notif->band ? "bg" : "a", + le32_to_cpu(notif->tsf_high), le32_to_cpu(notif->tsf_low), + le32_to_cpu(notif->stats[0]), + le32_to_cpu(notif->tsf_low) - il->scan_start_tsf); +#endif +} + +/* Service N_SCAN_COMPLETE (0x84) */ +static void +il_hdl_scan_complete(struct il_priv *il, struct il_rx_buf *rxb) +{ + +#ifdef CPTCFG_IWLEGACY_DEBUG + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il_scancomplete_notification *scan_notif = (void *)pkt->u.raw; +#endif + + D_SCAN("Scan complete: %d channels (TSF 0x%08X:%08X) - %d\n", + scan_notif->scanned_channels, scan_notif->tsf_low, + scan_notif->tsf_high, scan_notif->status); + + /* The HW is no longer scanning */ + clear_bit(S_SCAN_HW, &il->status); + + D_SCAN("Scan on %sGHz took %dms\n", + (il->scan_band == IEEE80211_BAND_2GHZ) ? "2.4" : "5.2", + jiffies_to_msecs(jiffies - il->scan_start)); + + queue_work(il->workqueue, &il->scan_completed); +} + +void +il_setup_rx_scan_handlers(struct il_priv *il) +{ + /* scan handlers */ + il->handlers[C_SCAN] = il_hdl_scan; + il->handlers[N_SCAN_START] = il_hdl_scan_start; + il->handlers[N_SCAN_RESULTS] = il_hdl_scan_results; + il->handlers[N_SCAN_COMPLETE] = il_hdl_scan_complete; +} +EXPORT_SYMBOL(il_setup_rx_scan_handlers); + +u16 +il_get_active_dwell_time(struct il_priv *il, enum ieee80211_band band, + u8 n_probes) +{ + if (band == IEEE80211_BAND_5GHZ) + return IL_ACTIVE_DWELL_TIME_52 + + IL_ACTIVE_DWELL_FACTOR_52GHZ * (n_probes + 1); + else + return IL_ACTIVE_DWELL_TIME_24 + + IL_ACTIVE_DWELL_FACTOR_24GHZ * (n_probes + 1); +} +EXPORT_SYMBOL(il_get_active_dwell_time); + +u16 +il_get_passive_dwell_time(struct il_priv *il, enum ieee80211_band band, + struct ieee80211_vif *vif) +{ + u16 value; + + u16 passive = + (band == + IEEE80211_BAND_2GHZ) ? IL_PASSIVE_DWELL_BASE + + IL_PASSIVE_DWELL_TIME_24 : IL_PASSIVE_DWELL_BASE + + IL_PASSIVE_DWELL_TIME_52; + + if (il_is_any_associated(il)) { + /* + * If we're associated, we clamp the maximum passive + * dwell time to be 98% of the smallest beacon interval + * (minus 2 * channel tune time) + */ + value = il->vif ? il->vif->bss_conf.beacon_int : 0; + if (value > IL_PASSIVE_DWELL_BASE || !value) + value = IL_PASSIVE_DWELL_BASE; + value = (value * 98) / 100 - IL_CHANNEL_TUNE_TIME * 2; + passive = min(value, passive); + } + + return passive; +} +EXPORT_SYMBOL(il_get_passive_dwell_time); + +void +il_init_scan_params(struct il_priv *il) +{ + u8 ant_idx = fls(il->hw_params.valid_tx_ant) - 1; + if (!il->scan_tx_ant[IEEE80211_BAND_5GHZ]) + il->scan_tx_ant[IEEE80211_BAND_5GHZ] = ant_idx; + if (!il->scan_tx_ant[IEEE80211_BAND_2GHZ]) + il->scan_tx_ant[IEEE80211_BAND_2GHZ] = ant_idx; +} +EXPORT_SYMBOL(il_init_scan_params); + +static int +il_scan_initiate(struct il_priv *il, struct ieee80211_vif *vif) +{ + int ret; + + lockdep_assert_held(&il->mutex); + + cancel_delayed_work(&il->scan_check); + + if (!il_is_ready_rf(il)) { + IL_WARN("Request scan called when driver not ready.\n"); + return -EIO; + } + + if (test_bit(S_SCAN_HW, &il->status)) { + D_SCAN("Multiple concurrent scan requests in parallel.\n"); + return -EBUSY; + } + + if (test_bit(S_SCAN_ABORTING, &il->status)) { + D_SCAN("Scan request while abort pending.\n"); + return -EBUSY; + } + + D_SCAN("Starting scan...\n"); + + set_bit(S_SCANNING, &il->status); + il->scan_start = jiffies; + + ret = il->ops->request_scan(il, vif); + if (ret) { + clear_bit(S_SCANNING, &il->status); + return ret; + } + + queue_delayed_work(il->workqueue, &il->scan_check, + IL_SCAN_CHECK_WATCHDOG); + + return 0; +} + +int +il_mac_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct cfg80211_scan_request *req) +{ + struct il_priv *il = hw->priv; + int ret; + + if (req->n_channels == 0) { + IL_ERR("Can not scan on no channels.\n"); + return -EINVAL; + } + + mutex_lock(&il->mutex); + D_MAC80211("enter\n"); + + if (test_bit(S_SCANNING, &il->status)) { + D_SCAN("Scan already in progress.\n"); + ret = -EAGAIN; + goto out_unlock; + } + + /* mac80211 will only ask for one band at a time */ + il->scan_request = req; + il->scan_vif = vif; + il->scan_band = req->channels[0]->band; + + ret = il_scan_initiate(il, vif); + +out_unlock: + D_MAC80211("leave ret %d\n", ret); + mutex_unlock(&il->mutex); + + return ret; +} +EXPORT_SYMBOL(il_mac_hw_scan); + +static void +il_bg_scan_check(struct work_struct *data) +{ + struct il_priv *il = + container_of(data, struct il_priv, scan_check.work); + + D_SCAN("Scan check work\n"); + + /* Since we are here firmware does not finish scan and + * most likely is in bad shape, so we don't bother to + * send abort command, just force scan complete to mac80211 */ + mutex_lock(&il->mutex); + il_force_scan_end(il); + mutex_unlock(&il->mutex); +} + +/** + * il_fill_probe_req - fill in all required fields and IE for probe request + */ + +u16 +il_fill_probe_req(struct il_priv *il, struct ieee80211_mgmt *frame, + const u8 *ta, const u8 *ies, int ie_len, int left) +{ + int len = 0; + u8 *pos = NULL; + + /* Make sure there is enough space for the probe request, + * two mandatory IEs and the data */ + left -= 24; + if (left < 0) + return 0; + + frame->frame_control = cpu_to_le16(IEEE80211_STYPE_PROBE_REQ); + eth_broadcast_addr(frame->da); + memcpy(frame->sa, ta, ETH_ALEN); + eth_broadcast_addr(frame->bssid); + frame->seq_ctrl = 0; + + len += 24; + + /* ...next IE... */ + pos = &frame->u.probe_req.variable[0]; + + /* fill in our indirect SSID IE */ + left -= 2; + if (left < 0) + return 0; + *pos++ = WLAN_EID_SSID; + *pos++ = 0; + + len += 2; + + if (WARN_ON(left < ie_len)) + return len; + + if (ies && ie_len) { + memcpy(pos, ies, ie_len); + len += ie_len; + } + + return (u16) len; +} +EXPORT_SYMBOL(il_fill_probe_req); + +static void +il_bg_abort_scan(struct work_struct *work) +{ + struct il_priv *il = container_of(work, struct il_priv, abort_scan); + + D_SCAN("Abort scan work\n"); + + /* We keep scan_check work queued in case when firmware will not + * report back scan completed notification */ + mutex_lock(&il->mutex); + il_scan_cancel_timeout(il, 200); + mutex_unlock(&il->mutex); +} + +static void +il_bg_scan_completed(struct work_struct *work) +{ + struct il_priv *il = container_of(work, struct il_priv, scan_completed); + bool aborted; + + D_SCAN("Completed scan.\n"); + + cancel_delayed_work(&il->scan_check); + + mutex_lock(&il->mutex); + + aborted = test_and_clear_bit(S_SCAN_ABORTING, &il->status); + if (aborted) + D_SCAN("Aborted scan completed.\n"); + + if (!test_and_clear_bit(S_SCANNING, &il->status)) { + D_SCAN("Scan already completed.\n"); + goto out_settings; + } + + il_complete_scan(il, aborted); + +out_settings: + /* Can we still talk to firmware ? */ + if (!il_is_ready_rf(il)) + goto out; + + /* + * We do not commit power settings while scan is pending, + * do it now if the settings changed. + */ + il_power_set_mode(il, &il->power_data.sleep_cmd_next, false); + il_set_tx_power(il, il->tx_power_next, false); + + il->ops->post_scan(il); + +out: + mutex_unlock(&il->mutex); +} + +void +il_setup_scan_deferred_work(struct il_priv *il) +{ + INIT_WORK(&il->scan_completed, il_bg_scan_completed); + INIT_WORK(&il->abort_scan, il_bg_abort_scan); + INIT_DELAYED_WORK(&il->scan_check, il_bg_scan_check); +} +EXPORT_SYMBOL(il_setup_scan_deferred_work); + +void +il_cancel_scan_deferred_work(struct il_priv *il) +{ + cancel_work_sync(&il->abort_scan); + cancel_work_sync(&il->scan_completed); + + if (cancel_delayed_work_sync(&il->scan_check)) { + mutex_lock(&il->mutex); + il_force_scan_end(il); + mutex_unlock(&il->mutex); + } +} +EXPORT_SYMBOL(il_cancel_scan_deferred_work); + +/* il->sta_lock must be held */ +static void +il_sta_ucode_activate(struct il_priv *il, u8 sta_id) +{ + + if (!(il->stations[sta_id].used & IL_STA_DRIVER_ACTIVE)) + IL_ERR("ACTIVATE a non DRIVER active station id %u addr %pM\n", + sta_id, il->stations[sta_id].sta.sta.addr); + + if (il->stations[sta_id].used & IL_STA_UCODE_ACTIVE) { + D_ASSOC("STA id %u addr %pM already present" + " in uCode (according to driver)\n", sta_id, + il->stations[sta_id].sta.sta.addr); + } else { + il->stations[sta_id].used |= IL_STA_UCODE_ACTIVE; + D_ASSOC("Added STA id %u addr %pM to uCode\n", sta_id, + il->stations[sta_id].sta.sta.addr); + } +} + +static int +il_process_add_sta_resp(struct il_priv *il, struct il_addsta_cmd *addsta, + struct il_rx_pkt *pkt, bool sync) +{ + u8 sta_id = addsta->sta.sta_id; + unsigned long flags; + int ret = -EIO; + + if (pkt->hdr.flags & IL_CMD_FAILED_MSK) { + IL_ERR("Bad return from C_ADD_STA (0x%08X)\n", pkt->hdr.flags); + return ret; + } + + D_INFO("Processing response for adding station %u\n", sta_id); + + spin_lock_irqsave(&il->sta_lock, flags); + + switch (pkt->u.add_sta.status) { + case ADD_STA_SUCCESS_MSK: + D_INFO("C_ADD_STA PASSED\n"); + il_sta_ucode_activate(il, sta_id); + ret = 0; + break; + case ADD_STA_NO_ROOM_IN_TBL: + IL_ERR("Adding station %d failed, no room in table.\n", sta_id); + break; + case ADD_STA_NO_BLOCK_ACK_RESOURCE: + IL_ERR("Adding station %d failed, no block ack resource.\n", + sta_id); + break; + case ADD_STA_MODIFY_NON_EXIST_STA: + IL_ERR("Attempting to modify non-existing station %d\n", + sta_id); + break; + default: + D_ASSOC("Received C_ADD_STA:(0x%08X)\n", pkt->u.add_sta.status); + break; + } + + D_INFO("%s station id %u addr %pM\n", + il->stations[sta_id].sta.mode == + STA_CONTROL_MODIFY_MSK ? "Modified" : "Added", sta_id, + il->stations[sta_id].sta.sta.addr); + + /* + * XXX: The MAC address in the command buffer is often changed from + * the original sent to the device. That is, the MAC address + * written to the command buffer often is not the same MAC address + * read from the command buffer when the command returns. This + * issue has not yet been resolved and this debugging is left to + * observe the problem. + */ + D_INFO("%s station according to cmd buffer %pM\n", + il->stations[sta_id].sta.mode == + STA_CONTROL_MODIFY_MSK ? "Modified" : "Added", addsta->sta.addr); + spin_unlock_irqrestore(&il->sta_lock, flags); + + return ret; +} + +static void +il_add_sta_callback(struct il_priv *il, struct il_device_cmd *cmd, + struct il_rx_pkt *pkt) +{ + struct il_addsta_cmd *addsta = (struct il_addsta_cmd *)cmd->cmd.payload; + + il_process_add_sta_resp(il, addsta, pkt, false); + +} + +int +il_send_add_sta(struct il_priv *il, struct il_addsta_cmd *sta, u8 flags) +{ + struct il_rx_pkt *pkt = NULL; + int ret = 0; + u8 data[sizeof(*sta)]; + struct il_host_cmd cmd = { + .id = C_ADD_STA, + .flags = flags, + .data = data, + }; + u8 sta_id __maybe_unused = sta->sta.sta_id; + + D_INFO("Adding sta %u (%pM) %ssynchronously\n", sta_id, sta->sta.addr, + flags & CMD_ASYNC ? "a" : ""); + + if (flags & CMD_ASYNC) + cmd.callback = il_add_sta_callback; + else { + cmd.flags |= CMD_WANT_SKB; + might_sleep(); + } + + cmd.len = il->ops->build_addsta_hcmd(sta, data); + ret = il_send_cmd(il, &cmd); + + if (ret || (flags & CMD_ASYNC)) + return ret; + + if (ret == 0) { + pkt = (struct il_rx_pkt *)cmd.reply_page; + ret = il_process_add_sta_resp(il, sta, pkt, true); + } + il_free_pages(il, cmd.reply_page); + + return ret; +} +EXPORT_SYMBOL(il_send_add_sta); + +static void +il_set_ht_add_station(struct il_priv *il, u8 idx, struct ieee80211_sta *sta) +{ + struct ieee80211_sta_ht_cap *sta_ht_inf = &sta->ht_cap; + __le32 sta_flags; + + if (!sta || !sta_ht_inf->ht_supported) + goto done; + + D_ASSOC("spatial multiplexing power save mode: %s\n", + (sta->smps_mode == IEEE80211_SMPS_STATIC) ? "static" : + (sta->smps_mode == IEEE80211_SMPS_DYNAMIC) ? "dynamic" : + "disabled"); + + sta_flags = il->stations[idx].sta.station_flags; + + sta_flags &= ~(STA_FLG_RTS_MIMO_PROT_MSK | STA_FLG_MIMO_DIS_MSK); + + switch (sta->smps_mode) { + case IEEE80211_SMPS_STATIC: + sta_flags |= STA_FLG_MIMO_DIS_MSK; + break; + case IEEE80211_SMPS_DYNAMIC: + sta_flags |= STA_FLG_RTS_MIMO_PROT_MSK; + break; + case IEEE80211_SMPS_OFF: + break; + default: + IL_WARN("Invalid MIMO PS mode %d\n", sta->smps_mode); + break; + } + + sta_flags |= + cpu_to_le32((u32) sta_ht_inf-> + ampdu_factor << STA_FLG_MAX_AGG_SIZE_POS); + + sta_flags |= + cpu_to_le32((u32) sta_ht_inf-> + ampdu_density << STA_FLG_AGG_MPDU_DENSITY_POS); + + if (il_is_ht40_tx_allowed(il, &sta->ht_cap)) + sta_flags |= STA_FLG_HT40_EN_MSK; + else + sta_flags &= ~STA_FLG_HT40_EN_MSK; + + il->stations[idx].sta.station_flags = sta_flags; +done: + return; +} + +/** + * il_prep_station - Prepare station information for addition + * + * should be called with sta_lock held + */ +u8 +il_prep_station(struct il_priv *il, const u8 *addr, bool is_ap, + struct ieee80211_sta *sta) +{ + struct il_station_entry *station; + int i; + u8 sta_id = IL_INVALID_STATION; + u16 rate; + + if (is_ap) + sta_id = IL_AP_ID; + else if (is_broadcast_ether_addr(addr)) + sta_id = il->hw_params.bcast_id; + else + for (i = IL_STA_ID; i < il->hw_params.max_stations; i++) { + if (ether_addr_equal(il->stations[i].sta.sta.addr, + addr)) { + sta_id = i; + break; + } + + if (!il->stations[i].used && + sta_id == IL_INVALID_STATION) + sta_id = i; + } + + /* + * These two conditions have the same outcome, but keep them + * separate + */ + if (unlikely(sta_id == IL_INVALID_STATION)) + return sta_id; + + /* + * uCode is not able to deal with multiple requests to add a + * station. Keep track if one is in progress so that we do not send + * another. + */ + if (il->stations[sta_id].used & IL_STA_UCODE_INPROGRESS) { + D_INFO("STA %d already in process of being added.\n", sta_id); + return sta_id; + } + + if ((il->stations[sta_id].used & IL_STA_DRIVER_ACTIVE) && + (il->stations[sta_id].used & IL_STA_UCODE_ACTIVE) && + ether_addr_equal(il->stations[sta_id].sta.sta.addr, addr)) { + D_ASSOC("STA %d (%pM) already added, not adding again.\n", + sta_id, addr); + return sta_id; + } + + station = &il->stations[sta_id]; + station->used = IL_STA_DRIVER_ACTIVE; + D_ASSOC("Add STA to driver ID %d: %pM\n", sta_id, addr); + il->num_stations++; + + /* Set up the C_ADD_STA command to send to device */ + memset(&station->sta, 0, sizeof(struct il_addsta_cmd)); + memcpy(station->sta.sta.addr, addr, ETH_ALEN); + station->sta.mode = 0; + station->sta.sta.sta_id = sta_id; + station->sta.station_flags = 0; + + /* + * OK to call unconditionally, since local stations (IBSS BSSID + * STA and broadcast STA) pass in a NULL sta, and mac80211 + * doesn't allow HT IBSS. + */ + il_set_ht_add_station(il, sta_id, sta); + + /* 3945 only */ + rate = (il->band == IEEE80211_BAND_5GHZ) ? RATE_6M_PLCP : RATE_1M_PLCP; + /* Turn on both antennas for the station... */ + station->sta.rate_n_flags = cpu_to_le16(rate | RATE_MCS_ANT_AB_MSK); + + return sta_id; + +} +EXPORT_SYMBOL_GPL(il_prep_station); + +#define STA_WAIT_TIMEOUT (HZ/2) + +/** + * il_add_station_common - + */ +int +il_add_station_common(struct il_priv *il, const u8 *addr, bool is_ap, + struct ieee80211_sta *sta, u8 *sta_id_r) +{ + unsigned long flags_spin; + int ret = 0; + u8 sta_id; + struct il_addsta_cmd sta_cmd; + + *sta_id_r = 0; + spin_lock_irqsave(&il->sta_lock, flags_spin); + sta_id = il_prep_station(il, addr, is_ap, sta); + if (sta_id == IL_INVALID_STATION) { + IL_ERR("Unable to prepare station %pM for addition\n", addr); + spin_unlock_irqrestore(&il->sta_lock, flags_spin); + return -EINVAL; + } + + /* + * uCode is not able to deal with multiple requests to add a + * station. Keep track if one is in progress so that we do not send + * another. + */ + if (il->stations[sta_id].used & IL_STA_UCODE_INPROGRESS) { + D_INFO("STA %d already in process of being added.\n", sta_id); + spin_unlock_irqrestore(&il->sta_lock, flags_spin); + return -EEXIST; + } + + if ((il->stations[sta_id].used & IL_STA_DRIVER_ACTIVE) && + (il->stations[sta_id].used & IL_STA_UCODE_ACTIVE)) { + D_ASSOC("STA %d (%pM) already added, not adding again.\n", + sta_id, addr); + spin_unlock_irqrestore(&il->sta_lock, flags_spin); + return -EEXIST; + } + + il->stations[sta_id].used |= IL_STA_UCODE_INPROGRESS; + memcpy(&sta_cmd, &il->stations[sta_id].sta, + sizeof(struct il_addsta_cmd)); + spin_unlock_irqrestore(&il->sta_lock, flags_spin); + + /* Add station to device's station table */ + ret = il_send_add_sta(il, &sta_cmd, CMD_SYNC); + if (ret) { + spin_lock_irqsave(&il->sta_lock, flags_spin); + IL_ERR("Adding station %pM failed.\n", + il->stations[sta_id].sta.sta.addr); + il->stations[sta_id].used &= ~IL_STA_DRIVER_ACTIVE; + il->stations[sta_id].used &= ~IL_STA_UCODE_INPROGRESS; + spin_unlock_irqrestore(&il->sta_lock, flags_spin); + } + *sta_id_r = sta_id; + return ret; +} +EXPORT_SYMBOL(il_add_station_common); + +/** + * il_sta_ucode_deactivate - deactivate ucode status for a station + * + * il->sta_lock must be held + */ +static void +il_sta_ucode_deactivate(struct il_priv *il, u8 sta_id) +{ + /* Ucode must be active and driver must be non active */ + if ((il->stations[sta_id]. + used & (IL_STA_UCODE_ACTIVE | IL_STA_DRIVER_ACTIVE)) != + IL_STA_UCODE_ACTIVE) + IL_ERR("removed non active STA %u\n", sta_id); + + il->stations[sta_id].used &= ~IL_STA_UCODE_ACTIVE; + + memset(&il->stations[sta_id], 0, sizeof(struct il_station_entry)); + D_ASSOC("Removed STA %u\n", sta_id); +} + +static int +il_send_remove_station(struct il_priv *il, const u8 * addr, int sta_id, + bool temporary) +{ + struct il_rx_pkt *pkt; + int ret; + + unsigned long flags_spin; + struct il_rem_sta_cmd rm_sta_cmd; + + struct il_host_cmd cmd = { + .id = C_REM_STA, + .len = sizeof(struct il_rem_sta_cmd), + .flags = CMD_SYNC, + .data = &rm_sta_cmd, + }; + + memset(&rm_sta_cmd, 0, sizeof(rm_sta_cmd)); + rm_sta_cmd.num_sta = 1; + memcpy(&rm_sta_cmd.addr, addr, ETH_ALEN); + + cmd.flags |= CMD_WANT_SKB; + + ret = il_send_cmd(il, &cmd); + + if (ret) + return ret; + + pkt = (struct il_rx_pkt *)cmd.reply_page; + if (pkt->hdr.flags & IL_CMD_FAILED_MSK) { + IL_ERR("Bad return from C_REM_STA (0x%08X)\n", pkt->hdr.flags); + ret = -EIO; + } + + if (!ret) { + switch (pkt->u.rem_sta.status) { + case REM_STA_SUCCESS_MSK: + if (!temporary) { + spin_lock_irqsave(&il->sta_lock, flags_spin); + il_sta_ucode_deactivate(il, sta_id); + spin_unlock_irqrestore(&il->sta_lock, + flags_spin); + } + D_ASSOC("C_REM_STA PASSED\n"); + break; + default: + ret = -EIO; + IL_ERR("C_REM_STA failed\n"); + break; + } + } + il_free_pages(il, cmd.reply_page); + + return ret; +} + +/** + * il_remove_station - Remove driver's knowledge of station. + */ +int +il_remove_station(struct il_priv *il, const u8 sta_id, const u8 * addr) +{ + unsigned long flags; + + if (!il_is_ready(il)) { + D_INFO("Unable to remove station %pM, device not ready.\n", + addr); + /* + * It is typical for stations to be removed when we are + * going down. Return success since device will be down + * soon anyway + */ + return 0; + } + + D_ASSOC("Removing STA from driver:%d %pM\n", sta_id, addr); + + if (WARN_ON(sta_id == IL_INVALID_STATION)) + return -EINVAL; + + spin_lock_irqsave(&il->sta_lock, flags); + + if (!(il->stations[sta_id].used & IL_STA_DRIVER_ACTIVE)) { + D_INFO("Removing %pM but non DRIVER active\n", addr); + goto out_err; + } + + if (!(il->stations[sta_id].used & IL_STA_UCODE_ACTIVE)) { + D_INFO("Removing %pM but non UCODE active\n", addr); + goto out_err; + } + + if (il->stations[sta_id].used & IL_STA_LOCAL) { + kfree(il->stations[sta_id].lq); + il->stations[sta_id].lq = NULL; + } + + il->stations[sta_id].used &= ~IL_STA_DRIVER_ACTIVE; + + il->num_stations--; + + BUG_ON(il->num_stations < 0); + + spin_unlock_irqrestore(&il->sta_lock, flags); + + return il_send_remove_station(il, addr, sta_id, false); +out_err: + spin_unlock_irqrestore(&il->sta_lock, flags); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(il_remove_station); + +/** + * il_clear_ucode_stations - clear ucode station table bits + * + * This function clears all the bits in the driver indicating + * which stations are active in the ucode. Call when something + * other than explicit station management would cause this in + * the ucode, e.g. unassociated RXON. + */ +void +il_clear_ucode_stations(struct il_priv *il) +{ + int i; + unsigned long flags_spin; + bool cleared = false; + + D_INFO("Clearing ucode stations in driver\n"); + + spin_lock_irqsave(&il->sta_lock, flags_spin); + for (i = 0; i < il->hw_params.max_stations; i++) { + if (il->stations[i].used & IL_STA_UCODE_ACTIVE) { + D_INFO("Clearing ucode active for station %d\n", i); + il->stations[i].used &= ~IL_STA_UCODE_ACTIVE; + cleared = true; + } + } + spin_unlock_irqrestore(&il->sta_lock, flags_spin); + + if (!cleared) + D_INFO("No active stations found to be cleared\n"); +} +EXPORT_SYMBOL(il_clear_ucode_stations); + +/** + * il_restore_stations() - Restore driver known stations to device + * + * All stations considered active by driver, but not present in ucode, is + * restored. + * + * Function sleeps. + */ +void +il_restore_stations(struct il_priv *il) +{ + struct il_addsta_cmd sta_cmd; + struct il_link_quality_cmd lq; + unsigned long flags_spin; + int i; + bool found = false; + int ret; + bool send_lq; + + if (!il_is_ready(il)) { + D_INFO("Not ready yet, not restoring any stations.\n"); + return; + } + + D_ASSOC("Restoring all known stations ... start.\n"); + spin_lock_irqsave(&il->sta_lock, flags_spin); + for (i = 0; i < il->hw_params.max_stations; i++) { + if ((il->stations[i].used & IL_STA_DRIVER_ACTIVE) && + !(il->stations[i].used & IL_STA_UCODE_ACTIVE)) { + D_ASSOC("Restoring sta %pM\n", + il->stations[i].sta.sta.addr); + il->stations[i].sta.mode = 0; + il->stations[i].used |= IL_STA_UCODE_INPROGRESS; + found = true; + } + } + + for (i = 0; i < il->hw_params.max_stations; i++) { + if ((il->stations[i].used & IL_STA_UCODE_INPROGRESS)) { + memcpy(&sta_cmd, &il->stations[i].sta, + sizeof(struct il_addsta_cmd)); + send_lq = false; + if (il->stations[i].lq) { + memcpy(&lq, il->stations[i].lq, + sizeof(struct il_link_quality_cmd)); + send_lq = true; + } + spin_unlock_irqrestore(&il->sta_lock, flags_spin); + ret = il_send_add_sta(il, &sta_cmd, CMD_SYNC); + if (ret) { + spin_lock_irqsave(&il->sta_lock, flags_spin); + IL_ERR("Adding station %pM failed.\n", + il->stations[i].sta.sta.addr); + il->stations[i].used &= ~IL_STA_DRIVER_ACTIVE; + il->stations[i].used &= + ~IL_STA_UCODE_INPROGRESS; + spin_unlock_irqrestore(&il->sta_lock, + flags_spin); + } + /* + * Rate scaling has already been initialized, send + * current LQ command + */ + if (send_lq) + il_send_lq_cmd(il, &lq, CMD_SYNC, true); + spin_lock_irqsave(&il->sta_lock, flags_spin); + il->stations[i].used &= ~IL_STA_UCODE_INPROGRESS; + } + } + + spin_unlock_irqrestore(&il->sta_lock, flags_spin); + if (!found) + D_INFO("Restoring all known stations" + " .... no stations to be restored.\n"); + else + D_INFO("Restoring all known stations" " .... complete.\n"); +} +EXPORT_SYMBOL(il_restore_stations); + +int +il_get_free_ucode_key_idx(struct il_priv *il) +{ + int i; + + for (i = 0; i < il->sta_key_max_num; i++) + if (!test_and_set_bit(i, &il->ucode_key_table)) + return i; + + return WEP_INVALID_OFFSET; +} +EXPORT_SYMBOL(il_get_free_ucode_key_idx); + +void +il_dealloc_bcast_stations(struct il_priv *il) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&il->sta_lock, flags); + for (i = 0; i < il->hw_params.max_stations; i++) { + if (!(il->stations[i].used & IL_STA_BCAST)) + continue; + + il->stations[i].used &= ~IL_STA_UCODE_ACTIVE; + il->num_stations--; + BUG_ON(il->num_stations < 0); + kfree(il->stations[i].lq); + il->stations[i].lq = NULL; + } + spin_unlock_irqrestore(&il->sta_lock, flags); +} +EXPORT_SYMBOL_GPL(il_dealloc_bcast_stations); + +#ifdef CPTCFG_IWLEGACY_DEBUG +static void +il_dump_lq_cmd(struct il_priv *il, struct il_link_quality_cmd *lq) +{ + int i; + D_RATE("lq station id 0x%x\n", lq->sta_id); + D_RATE("lq ant 0x%X 0x%X\n", lq->general_params.single_stream_ant_msk, + lq->general_params.dual_stream_ant_msk); + + for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) + D_RATE("lq idx %d 0x%X\n", i, lq->rs_table[i].rate_n_flags); +} +#else +static inline void +il_dump_lq_cmd(struct il_priv *il, struct il_link_quality_cmd *lq) +{ +} +#endif + +/** + * il_is_lq_table_valid() - Test one aspect of LQ cmd for validity + * + * It sometimes happens when a HT rate has been in use and we + * loose connectivity with AP then mac80211 will first tell us that the + * current channel is not HT anymore before removing the station. In such a + * scenario the RXON flags will be updated to indicate we are not + * communicating HT anymore, but the LQ command may still contain HT rates. + * Test for this to prevent driver from sending LQ command between the time + * RXON flags are updated and when LQ command is updated. + */ +static bool +il_is_lq_table_valid(struct il_priv *il, struct il_link_quality_cmd *lq) +{ + int i; + + if (il->ht.enabled) + return true; + + D_INFO("Channel %u is not an HT channel\n", il->active.channel); + for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) { + if (le32_to_cpu(lq->rs_table[i].rate_n_flags) & RATE_MCS_HT_MSK) { + D_INFO("idx %d of LQ expects HT channel\n", i); + return false; + } + } + return true; +} + +/** + * il_send_lq_cmd() - Send link quality command + * @init: This command is sent as part of station initialization right + * after station has been added. + * + * The link quality command is sent as the last step of station creation. + * This is the special case in which init is set and we call a callback in + * this case to clear the state indicating that station creation is in + * progress. + */ +int +il_send_lq_cmd(struct il_priv *il, struct il_link_quality_cmd *lq, + u8 flags, bool init) +{ + int ret = 0; + unsigned long flags_spin; + + struct il_host_cmd cmd = { + .id = C_TX_LINK_QUALITY_CMD, + .len = sizeof(struct il_link_quality_cmd), + .flags = flags, + .data = lq, + }; + + if (WARN_ON(lq->sta_id == IL_INVALID_STATION)) + return -EINVAL; + + spin_lock_irqsave(&il->sta_lock, flags_spin); + if (!(il->stations[lq->sta_id].used & IL_STA_DRIVER_ACTIVE)) { + spin_unlock_irqrestore(&il->sta_lock, flags_spin); + return -EINVAL; + } + spin_unlock_irqrestore(&il->sta_lock, flags_spin); + + il_dump_lq_cmd(il, lq); + BUG_ON(init && (cmd.flags & CMD_ASYNC)); + + if (il_is_lq_table_valid(il, lq)) + ret = il_send_cmd(il, &cmd); + else + ret = -EINVAL; + + if (cmd.flags & CMD_ASYNC) + return ret; + + if (init) { + D_INFO("init LQ command complete," + " clearing sta addition status for sta %d\n", + lq->sta_id); + spin_lock_irqsave(&il->sta_lock, flags_spin); + il->stations[lq->sta_id].used &= ~IL_STA_UCODE_INPROGRESS; + spin_unlock_irqrestore(&il->sta_lock, flags_spin); + } + return ret; +} +EXPORT_SYMBOL(il_send_lq_cmd); + +int +il_mac_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct il_priv *il = hw->priv; + struct il_station_priv_common *sta_common = (void *)sta->drv_priv; + int ret; + + mutex_lock(&il->mutex); + D_MAC80211("enter station %pM\n", sta->addr); + + ret = il_remove_station(il, sta_common->sta_id, sta->addr); + if (ret) + IL_ERR("Error removing station %pM\n", sta->addr); + + D_MAC80211("leave ret %d\n", ret); + mutex_unlock(&il->mutex); + + return ret; +} +EXPORT_SYMBOL(il_mac_sta_remove); + +/************************** RX-FUNCTIONS ****************************/ +/* + * Rx theory of operation + * + * Driver allocates a circular buffer of Receive Buffer Descriptors (RBDs), + * each of which point to Receive Buffers to be filled by the NIC. These get + * used not only for Rx frames, but for any command response or notification + * from the NIC. The driver and NIC manage the Rx buffers by means + * of idxes into the circular buffer. + * + * Rx Queue Indexes + * The host/firmware share two idx registers for managing the Rx buffers. + * + * The READ idx maps to the first position that the firmware may be writing + * to -- the driver can read up to (but not including) this position and get + * good data. + * The READ idx is managed by the firmware once the card is enabled. + * + * The WRITE idx maps to the last position the driver has read from -- the + * position preceding WRITE is the last slot the firmware can place a packet. + * + * The queue is empty (no good data) if WRITE = READ - 1, and is full if + * WRITE = READ. + * + * During initialization, the host sets up the READ queue position to the first + * IDX position, and WRITE to the last (READ - 1 wrapped) + * + * When the firmware places a packet in a buffer, it will advance the READ idx + * and fire the RX interrupt. The driver can then query the READ idx and + * process as many packets as possible, moving the WRITE idx forward as it + * resets the Rx queue buffers with new memory. + * + * The management in the driver is as follows: + * + A list of pre-allocated SKBs is stored in iwl->rxq->rx_free. When + * iwl->rxq->free_count drops to or below RX_LOW_WATERMARK, work is scheduled + * to replenish the iwl->rxq->rx_free. + * + In il_rx_replenish (scheduled) if 'processed' != 'read' then the + * iwl->rxq is replenished and the READ IDX is updated (updating the + * 'processed' and 'read' driver idxes as well) + * + A received packet is processed and handed to the kernel network stack, + * detached from the iwl->rxq. The driver 'processed' idx is updated. + * + The Host/Firmware iwl->rxq is replenished at tasklet time from the rx_free + * list. If there are no allocated buffers in iwl->rxq->rx_free, the READ + * IDX is not incremented and iwl->status(RX_STALLED) is set. If there + * were enough free buffers and RX_STALLED is set it is cleared. + * + * + * Driver sequence: + * + * il_rx_queue_alloc() Allocates rx_free + * il_rx_replenish() Replenishes rx_free list from rx_used, and calls + * il_rx_queue_restock + * il_rx_queue_restock() Moves available buffers from rx_free into Rx + * queue, updates firmware pointers, and updates + * the WRITE idx. If insufficient rx_free buffers + * are available, schedules il_rx_replenish + * + * -- enable interrupts -- + * ISR - il_rx() Detach il_rx_bufs from pool up to the + * READ IDX, detaching the SKB from the pool. + * Moves the packet buffer from queue to rx_used. + * Calls il_rx_queue_restock to refill any empty + * slots. + * ... + * + */ + +/** + * il_rx_queue_space - Return number of free slots available in queue. + */ +int +il_rx_queue_space(const struct il_rx_queue *q) +{ + int s = q->read - q->write; + if (s <= 0) + s += RX_QUEUE_SIZE; + /* keep some buffer to not confuse full and empty queue */ + s -= 2; + if (s < 0) + s = 0; + return s; +} +EXPORT_SYMBOL(il_rx_queue_space); + +/** + * il_rx_queue_update_write_ptr - Update the write pointer for the RX queue + */ +void +il_rx_queue_update_write_ptr(struct il_priv *il, struct il_rx_queue *q) +{ + unsigned long flags; + u32 rx_wrt_ptr_reg = il->hw_params.rx_wrt_ptr_reg; + u32 reg; + + spin_lock_irqsave(&q->lock, flags); + + if (q->need_update == 0) + goto exit_unlock; + + /* If power-saving is in use, make sure device is awake */ + if (test_bit(S_POWER_PMI, &il->status)) { + reg = _il_rd(il, CSR_UCODE_DRV_GP1); + + if (reg & CSR_UCODE_DRV_GP1_BIT_MAC_SLEEP) { + D_INFO("Rx queue requesting wakeup," " GP1 = 0x%x\n", + reg); + il_set_bit(il, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + goto exit_unlock; + } + + q->write_actual = (q->write & ~0x7); + il_wr(il, rx_wrt_ptr_reg, q->write_actual); + + /* Else device is assumed to be awake */ + } else { + /* Device expects a multiple of 8 */ + q->write_actual = (q->write & ~0x7); + il_wr(il, rx_wrt_ptr_reg, q->write_actual); + } + + q->need_update = 0; + +exit_unlock: + spin_unlock_irqrestore(&q->lock, flags); +} +EXPORT_SYMBOL(il_rx_queue_update_write_ptr); + +int +il_rx_queue_alloc(struct il_priv *il) +{ + struct il_rx_queue *rxq = &il->rxq; + struct device *dev = &il->pci_dev->dev; + int i; + + spin_lock_init(&rxq->lock); + INIT_LIST_HEAD(&rxq->rx_free); + INIT_LIST_HEAD(&rxq->rx_used); + + /* Alloc the circular buffer of Read Buffer Descriptors (RBDs) */ + rxq->bd = dma_alloc_coherent(dev, 4 * RX_QUEUE_SIZE, &rxq->bd_dma, + GFP_KERNEL); + if (!rxq->bd) + goto err_bd; + + rxq->rb_stts = dma_alloc_coherent(dev, sizeof(struct il_rb_status), + &rxq->rb_stts_dma, GFP_KERNEL); + if (!rxq->rb_stts) + goto err_rb; + + /* Fill the rx_used queue with _all_ of the Rx buffers */ + for (i = 0; i < RX_FREE_BUFFERS + RX_QUEUE_SIZE; i++) + list_add_tail(&rxq->pool[i].list, &rxq->rx_used); + + /* Set us so that we have processed and used all buffers, but have + * not restocked the Rx queue with fresh buffers */ + rxq->read = rxq->write = 0; + rxq->write_actual = 0; + rxq->free_count = 0; + rxq->need_update = 0; + return 0; + +err_rb: + dma_free_coherent(&il->pci_dev->dev, 4 * RX_QUEUE_SIZE, rxq->bd, + rxq->bd_dma); +err_bd: + return -ENOMEM; +} +EXPORT_SYMBOL(il_rx_queue_alloc); + +void +il_hdl_spectrum_measurement(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il_spectrum_notification *report = &(pkt->u.spectrum_notif); + + if (!report->state) { + D_11H("Spectrum Measure Notification: Start\n"); + return; + } + + memcpy(&il->measure_report, report, sizeof(*report)); + il->measurement_status |= MEASUREMENT_READY; +} +EXPORT_SYMBOL(il_hdl_spectrum_measurement); + +/* + * returns non-zero if packet should be dropped + */ +int +il_set_decrypted_flag(struct il_priv *il, struct ieee80211_hdr *hdr, + u32 decrypt_res, struct ieee80211_rx_status *stats) +{ + u16 fc = le16_to_cpu(hdr->frame_control); + + /* + * All contexts have the same setting here due to it being + * a module parameter, so OK to check any context. + */ + if (il->active.filter_flags & RXON_FILTER_DIS_DECRYPT_MSK) + return 0; + + if (!(fc & IEEE80211_FCTL_PROTECTED)) + return 0; + + D_RX("decrypt_res:0x%x\n", decrypt_res); + switch (decrypt_res & RX_RES_STATUS_SEC_TYPE_MSK) { + case RX_RES_STATUS_SEC_TYPE_TKIP: + /* The uCode has got a bad phase 1 Key, pushes the packet. + * Decryption will be done in SW. */ + if ((decrypt_res & RX_RES_STATUS_DECRYPT_TYPE_MSK) == + RX_RES_STATUS_BAD_KEY_TTAK) + break; + + case RX_RES_STATUS_SEC_TYPE_WEP: + if ((decrypt_res & RX_RES_STATUS_DECRYPT_TYPE_MSK) == + RX_RES_STATUS_BAD_ICV_MIC) { + /* bad ICV, the packet is destroyed since the + * decryption is inplace, drop it */ + D_RX("Packet destroyed\n"); + return -1; + } + case RX_RES_STATUS_SEC_TYPE_CCMP: + if ((decrypt_res & RX_RES_STATUS_DECRYPT_TYPE_MSK) == + RX_RES_STATUS_DECRYPT_OK) { + D_RX("hw decrypt successfully!!!\n"); + stats->flag |= RX_FLAG_DECRYPTED; + } + break; + + default: + break; + } + return 0; +} +EXPORT_SYMBOL(il_set_decrypted_flag); + +/** + * il_txq_update_write_ptr - Send new write idx to hardware + */ +void +il_txq_update_write_ptr(struct il_priv *il, struct il_tx_queue *txq) +{ + u32 reg = 0; + int txq_id = txq->q.id; + + if (txq->need_update == 0) + return; + + /* if we're trying to save power */ + if (test_bit(S_POWER_PMI, &il->status)) { + /* wake up nic if it's powered down ... + * uCode will wake up, and interrupt us again, so next + * time we'll skip this part. */ + reg = _il_rd(il, CSR_UCODE_DRV_GP1); + + if (reg & CSR_UCODE_DRV_GP1_BIT_MAC_SLEEP) { + D_INFO("Tx queue %d requesting wakeup," " GP1 = 0x%x\n", + txq_id, reg); + il_set_bit(il, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + return; + } + + il_wr(il, HBUS_TARG_WRPTR, txq->q.write_ptr | (txq_id << 8)); + + /* + * else not in power-save mode, + * uCode will never sleep when we're + * trying to tx (during RFKILL, we're not trying to tx). + */ + } else + _il_wr(il, HBUS_TARG_WRPTR, txq->q.write_ptr | (txq_id << 8)); + txq->need_update = 0; +} +EXPORT_SYMBOL(il_txq_update_write_ptr); + +/** + * il_tx_queue_unmap - Unmap any remaining DMA mappings and free skb's + */ +void +il_tx_queue_unmap(struct il_priv *il, int txq_id) +{ + struct il_tx_queue *txq = &il->txq[txq_id]; + struct il_queue *q = &txq->q; + + if (q->n_bd == 0) + return; + + while (q->write_ptr != q->read_ptr) { + il->ops->txq_free_tfd(il, txq); + q->read_ptr = il_queue_inc_wrap(q->read_ptr, q->n_bd); + } +} +EXPORT_SYMBOL(il_tx_queue_unmap); + +/** + * il_tx_queue_free - Deallocate DMA queue. + * @txq: Transmit queue to deallocate. + * + * Empty queue by removing and destroying all BD's. + * Free all buffers. + * 0-fill, but do not free "txq" descriptor structure. + */ +void +il_tx_queue_free(struct il_priv *il, int txq_id) +{ + struct il_tx_queue *txq = &il->txq[txq_id]; + struct device *dev = &il->pci_dev->dev; + int i; + + il_tx_queue_unmap(il, txq_id); + + /* De-alloc array of command/tx buffers */ + for (i = 0; i < TFD_TX_CMD_SLOTS; i++) + kfree(txq->cmd[i]); + + /* De-alloc circular buffer of TFDs */ + if (txq->q.n_bd) + dma_free_coherent(dev, il->hw_params.tfd_size * txq->q.n_bd, + txq->tfds, txq->q.dma_addr); + + /* De-alloc array of per-TFD driver data */ + kfree(txq->skbs); + txq->skbs = NULL; + + /* deallocate arrays */ + kfree(txq->cmd); + kfree(txq->meta); + txq->cmd = NULL; + txq->meta = NULL; + + /* 0-fill queue descriptor structure */ + memset(txq, 0, sizeof(*txq)); +} +EXPORT_SYMBOL(il_tx_queue_free); + +/** + * il_cmd_queue_unmap - Unmap any remaining DMA mappings from command queue + */ +void +il_cmd_queue_unmap(struct il_priv *il) +{ + struct il_tx_queue *txq = &il->txq[il->cmd_queue]; + struct il_queue *q = &txq->q; + int i; + + if (q->n_bd == 0) + return; + + while (q->read_ptr != q->write_ptr) { + i = il_get_cmd_idx(q, q->read_ptr, 0); + + if (txq->meta[i].flags & CMD_MAPPED) { + pci_unmap_single(il->pci_dev, + dma_unmap_addr(&txq->meta[i], mapping), + dma_unmap_len(&txq->meta[i], len), + PCI_DMA_BIDIRECTIONAL); + txq->meta[i].flags = 0; + } + + q->read_ptr = il_queue_inc_wrap(q->read_ptr, q->n_bd); + } + + i = q->n_win; + if (txq->meta[i].flags & CMD_MAPPED) { + pci_unmap_single(il->pci_dev, + dma_unmap_addr(&txq->meta[i], mapping), + dma_unmap_len(&txq->meta[i], len), + PCI_DMA_BIDIRECTIONAL); + txq->meta[i].flags = 0; + } +} +EXPORT_SYMBOL(il_cmd_queue_unmap); + +/** + * il_cmd_queue_free - Deallocate DMA queue. + * @txq: Transmit queue to deallocate. + * + * Empty queue by removing and destroying all BD's. + * Free all buffers. + * 0-fill, but do not free "txq" descriptor structure. + */ +void +il_cmd_queue_free(struct il_priv *il) +{ + struct il_tx_queue *txq = &il->txq[il->cmd_queue]; + struct device *dev = &il->pci_dev->dev; + int i; + + il_cmd_queue_unmap(il); + + /* De-alloc array of command/tx buffers */ + for (i = 0; i <= TFD_CMD_SLOTS; i++) + kfree(txq->cmd[i]); + + /* De-alloc circular buffer of TFDs */ + if (txq->q.n_bd) + dma_free_coherent(dev, il->hw_params.tfd_size * txq->q.n_bd, + txq->tfds, txq->q.dma_addr); + + /* deallocate arrays */ + kfree(txq->cmd); + kfree(txq->meta); + txq->cmd = NULL; + txq->meta = NULL; + + /* 0-fill queue descriptor structure */ + memset(txq, 0, sizeof(*txq)); +} +EXPORT_SYMBOL(il_cmd_queue_free); + +/*************** DMA-QUEUE-GENERAL-FUNCTIONS ***** + * DMA services + * + * Theory of operation + * + * A Tx or Rx queue resides in host DRAM, and is comprised of a circular buffer + * of buffer descriptors, each of which points to one or more data buffers for + * the device to read from or fill. Driver and device exchange status of each + * queue via "read" and "write" pointers. Driver keeps minimum of 2 empty + * entries in each circular buffer, to protect against confusing empty and full + * queue states. + * + * The device reads or writes the data in the queues via the device's several + * DMA/FIFO channels. Each queue is mapped to a single DMA channel. + * + * For Tx queue, there are low mark and high mark limits. If, after queuing + * the packet for Tx, free space become < low mark, Tx queue stopped. When + * reclaiming packets (on 'tx done IRQ), if free space become > high mark, + * Tx queue resumed. + * + * See more detailed info in 4965.h. + ***************************************************/ + +int +il_queue_space(const struct il_queue *q) +{ + int s = q->read_ptr - q->write_ptr; + + if (q->read_ptr > q->write_ptr) + s -= q->n_bd; + + if (s <= 0) + s += q->n_win; + /* keep some reserve to not confuse empty and full situations */ + s -= 2; + if (s < 0) + s = 0; + return s; +} +EXPORT_SYMBOL(il_queue_space); + + +/** + * il_queue_init - Initialize queue's high/low-water and read/write idxes + */ +static int +il_queue_init(struct il_priv *il, struct il_queue *q, int slots, u32 id) +{ + /* + * TFD_QUEUE_SIZE_MAX must be power-of-two size, otherwise + * il_queue_inc_wrap and il_queue_dec_wrap are broken. + */ + BUILD_BUG_ON(TFD_QUEUE_SIZE_MAX & (TFD_QUEUE_SIZE_MAX - 1)); + /* FIXME: remove q->n_bd */ + q->n_bd = TFD_QUEUE_SIZE_MAX; + + q->n_win = slots; + q->id = id; + + /* slots_must be power-of-two size, otherwise + * il_get_cmd_idx is broken. */ + BUG_ON(!is_power_of_2(slots)); + + q->low_mark = q->n_win / 4; + if (q->low_mark < 4) + q->low_mark = 4; + + q->high_mark = q->n_win / 8; + if (q->high_mark < 2) + q->high_mark = 2; + + q->write_ptr = q->read_ptr = 0; + + return 0; +} + +/** + * il_tx_queue_alloc - Alloc driver data and TFD CB for one Tx/cmd queue + */ +static int +il_tx_queue_alloc(struct il_priv *il, struct il_tx_queue *txq, u32 id) +{ + struct device *dev = &il->pci_dev->dev; + size_t tfd_sz = il->hw_params.tfd_size * TFD_QUEUE_SIZE_MAX; + + /* Driver ilate data, only for Tx (not command) queues, + * not shared with device. */ + if (id != il->cmd_queue) { + txq->skbs = kcalloc(TFD_QUEUE_SIZE_MAX, sizeof(struct skb *), + GFP_KERNEL); + if (!txq->skbs) { + IL_ERR("Fail to alloc skbs\n"); + goto error; + } + } else + txq->skbs = NULL; + + /* Circular buffer of transmit frame descriptors (TFDs), + * shared with device */ + txq->tfds = + dma_alloc_coherent(dev, tfd_sz, &txq->q.dma_addr, GFP_KERNEL); + if (!txq->tfds) + goto error; + + txq->q.id = id; + + return 0; + +error: + kfree(txq->skbs); + txq->skbs = NULL; + + return -ENOMEM; +} + +/** + * il_tx_queue_init - Allocate and initialize one tx/cmd queue + */ +int +il_tx_queue_init(struct il_priv *il, u32 txq_id) +{ + int i, len, ret; + int slots, actual_slots; + struct il_tx_queue *txq = &il->txq[txq_id]; + + /* + * Alloc buffer array for commands (Tx or other types of commands). + * For the command queue (#4/#9), allocate command space + one big + * command for scan, since scan command is very huge; the system will + * not have two scans at the same time, so only one is needed. + * For normal Tx queues (all other queues), no super-size command + * space is needed. + */ + if (txq_id == il->cmd_queue) { + slots = TFD_CMD_SLOTS; + actual_slots = slots + 1; + } else { + slots = TFD_TX_CMD_SLOTS; + actual_slots = slots; + } + + txq->meta = + kzalloc(sizeof(struct il_cmd_meta) * actual_slots, GFP_KERNEL); + txq->cmd = + kzalloc(sizeof(struct il_device_cmd *) * actual_slots, GFP_KERNEL); + + if (!txq->meta || !txq->cmd) + goto out_free_arrays; + + len = sizeof(struct il_device_cmd); + for (i = 0; i < actual_slots; i++) { + /* only happens for cmd queue */ + if (i == slots) + len = IL_MAX_CMD_SIZE; + + txq->cmd[i] = kmalloc(len, GFP_KERNEL); + if (!txq->cmd[i]) + goto err; + } + + /* Alloc driver data array and TFD circular buffer */ + ret = il_tx_queue_alloc(il, txq, txq_id); + if (ret) + goto err; + + txq->need_update = 0; + + /* + * For the default queues 0-3, set up the swq_id + * already -- all others need to get one later + * (if they need one at all). + */ + if (txq_id < 4) + il_set_swq_id(txq, txq_id, txq_id); + + /* Initialize queue's high/low-water marks, and head/tail idxes */ + il_queue_init(il, &txq->q, slots, txq_id); + + /* Tell device where to find queue */ + il->ops->txq_init(il, txq); + + return 0; +err: + for (i = 0; i < actual_slots; i++) + kfree(txq->cmd[i]); +out_free_arrays: + kfree(txq->meta); + kfree(txq->cmd); + + return -ENOMEM; +} +EXPORT_SYMBOL(il_tx_queue_init); + +void +il_tx_queue_reset(struct il_priv *il, u32 txq_id) +{ + int slots, actual_slots; + struct il_tx_queue *txq = &il->txq[txq_id]; + + if (txq_id == il->cmd_queue) { + slots = TFD_CMD_SLOTS; + actual_slots = TFD_CMD_SLOTS + 1; + } else { + slots = TFD_TX_CMD_SLOTS; + actual_slots = TFD_TX_CMD_SLOTS; + } + + memset(txq->meta, 0, sizeof(struct il_cmd_meta) * actual_slots); + txq->need_update = 0; + + /* Initialize queue's high/low-water marks, and head/tail idxes */ + il_queue_init(il, &txq->q, slots, txq_id); + + /* Tell device where to find queue */ + il->ops->txq_init(il, txq); +} +EXPORT_SYMBOL(il_tx_queue_reset); + +/*************** HOST COMMAND QUEUE FUNCTIONS *****/ + +/** + * il_enqueue_hcmd - enqueue a uCode command + * @il: device ilate data point + * @cmd: a point to the ucode command structure + * + * The function returns < 0 values to indicate the operation is + * failed. On success, it turns the idx (> 0) of command in the + * command queue. + */ +int +il_enqueue_hcmd(struct il_priv *il, struct il_host_cmd *cmd) +{ + struct il_tx_queue *txq = &il->txq[il->cmd_queue]; + struct il_queue *q = &txq->q; + struct il_device_cmd *out_cmd; + struct il_cmd_meta *out_meta; + dma_addr_t phys_addr; + unsigned long flags; + int len; + u32 idx; + u16 fix_size; + + cmd->len = il->ops->get_hcmd_size(cmd->id, cmd->len); + fix_size = (u16) (cmd->len + sizeof(out_cmd->hdr)); + + /* If any of the command structures end up being larger than + * the TFD_MAX_PAYLOAD_SIZE, and it sent as a 'small' command then + * we will need to increase the size of the TFD entries + * Also, check to see if command buffer should not exceed the size + * of device_cmd and max_cmd_size. */ + BUG_ON((fix_size > TFD_MAX_PAYLOAD_SIZE) && + !(cmd->flags & CMD_SIZE_HUGE)); + BUG_ON(fix_size > IL_MAX_CMD_SIZE); + + if (il_is_rfkill(il) || il_is_ctkill(il)) { + IL_WARN("Not sending command - %s KILL\n", + il_is_rfkill(il) ? "RF" : "CT"); + return -EIO; + } + + spin_lock_irqsave(&il->hcmd_lock, flags); + + if (il_queue_space(q) < ((cmd->flags & CMD_ASYNC) ? 2 : 1)) { + spin_unlock_irqrestore(&il->hcmd_lock, flags); + + IL_ERR("Restarting adapter due to command queue full\n"); + queue_work(il->workqueue, &il->restart); + return -ENOSPC; + } + + idx = il_get_cmd_idx(q, q->write_ptr, cmd->flags & CMD_SIZE_HUGE); + out_cmd = txq->cmd[idx]; + out_meta = &txq->meta[idx]; + + if (WARN_ON(out_meta->flags & CMD_MAPPED)) { + spin_unlock_irqrestore(&il->hcmd_lock, flags); + return -ENOSPC; + } + + memset(out_meta, 0, sizeof(*out_meta)); /* re-initialize to NULL */ + out_meta->flags = cmd->flags | CMD_MAPPED; + if (cmd->flags & CMD_WANT_SKB) + out_meta->source = cmd; + if (cmd->flags & CMD_ASYNC) + out_meta->callback = cmd->callback; + + out_cmd->hdr.cmd = cmd->id; + memcpy(&out_cmd->cmd.payload, cmd->data, cmd->len); + + /* At this point, the out_cmd now has all of the incoming cmd + * information */ + + out_cmd->hdr.flags = 0; + out_cmd->hdr.sequence = + cpu_to_le16(QUEUE_TO_SEQ(il->cmd_queue) | IDX_TO_SEQ(q->write_ptr)); + if (cmd->flags & CMD_SIZE_HUGE) + out_cmd->hdr.sequence |= SEQ_HUGE_FRAME; + len = sizeof(struct il_device_cmd); + if (idx == TFD_CMD_SLOTS) + len = IL_MAX_CMD_SIZE; + +#ifdef CPTCFG_IWLEGACY_DEBUG + switch (out_cmd->hdr.cmd) { + case C_TX_LINK_QUALITY_CMD: + case C_SENSITIVITY: + D_HC_DUMP("Sending command %s (#%x), seq: 0x%04X, " + "%d bytes at %d[%d]:%d\n", + il_get_cmd_string(out_cmd->hdr.cmd), out_cmd->hdr.cmd, + le16_to_cpu(out_cmd->hdr.sequence), fix_size, + q->write_ptr, idx, il->cmd_queue); + break; + default: + D_HC("Sending command %s (#%x), seq: 0x%04X, " + "%d bytes at %d[%d]:%d\n", + il_get_cmd_string(out_cmd->hdr.cmd), out_cmd->hdr.cmd, + le16_to_cpu(out_cmd->hdr.sequence), fix_size, q->write_ptr, + idx, il->cmd_queue); + } +#endif + + phys_addr = + pci_map_single(il->pci_dev, &out_cmd->hdr, fix_size, + PCI_DMA_BIDIRECTIONAL); + if (unlikely(pci_dma_mapping_error(il->pci_dev, phys_addr))) { + idx = -ENOMEM; + goto out; + } + dma_unmap_addr_set(out_meta, mapping, phys_addr); + dma_unmap_len_set(out_meta, len, fix_size); + + txq->need_update = 1; + + if (il->ops->txq_update_byte_cnt_tbl) + /* Set up entry in queue's byte count circular buffer */ + il->ops->txq_update_byte_cnt_tbl(il, txq, 0); + + il->ops->txq_attach_buf_to_tfd(il, txq, phys_addr, fix_size, 1, + U32_PAD(cmd->len)); + + /* Increment and update queue's write idx */ + q->write_ptr = il_queue_inc_wrap(q->write_ptr, q->n_bd); + il_txq_update_write_ptr(il, txq); + +out: + spin_unlock_irqrestore(&il->hcmd_lock, flags); + return idx; +} + +/** + * il_hcmd_queue_reclaim - Reclaim TX command queue entries already Tx'd + * + * When FW advances 'R' idx, all entries between old and new 'R' idx + * need to be reclaimed. As result, some free space forms. If there is + * enough free space (> low mark), wake the stack that feeds us. + */ +static void +il_hcmd_queue_reclaim(struct il_priv *il, int txq_id, int idx, int cmd_idx) +{ + struct il_tx_queue *txq = &il->txq[txq_id]; + struct il_queue *q = &txq->q; + int nfreed = 0; + + if (idx >= q->n_bd || il_queue_used(q, idx) == 0) { + IL_ERR("Read idx for DMA queue txq id (%d), idx %d, " + "is out of range [0-%d] %d %d.\n", txq_id, idx, q->n_bd, + q->write_ptr, q->read_ptr); + return; + } + + for (idx = il_queue_inc_wrap(idx, q->n_bd); q->read_ptr != idx; + q->read_ptr = il_queue_inc_wrap(q->read_ptr, q->n_bd)) { + + if (nfreed++ > 0) { + IL_ERR("HCMD skipped: idx (%d) %d %d\n", idx, + q->write_ptr, q->read_ptr); + queue_work(il->workqueue, &il->restart); + } + + } +} + +/** + * il_tx_cmd_complete - Pull unused buffers off the queue and reclaim them + * @rxb: Rx buffer to reclaim + * + * If an Rx buffer has an async callback associated with it the callback + * will be executed. The attached skb (if present) will only be freed + * if the callback returns 1 + */ +void +il_tx_cmd_complete(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + u16 sequence = le16_to_cpu(pkt->hdr.sequence); + int txq_id = SEQ_TO_QUEUE(sequence); + int idx = SEQ_TO_IDX(sequence); + int cmd_idx; + bool huge = !!(pkt->hdr.sequence & SEQ_HUGE_FRAME); + struct il_device_cmd *cmd; + struct il_cmd_meta *meta; + struct il_tx_queue *txq = &il->txq[il->cmd_queue]; + unsigned long flags; + + /* If a Tx command is being handled and it isn't in the actual + * command queue then there a command routing bug has been introduced + * in the queue management code. */ + if (WARN + (txq_id != il->cmd_queue, + "wrong command queue %d (should be %d), sequence 0x%X readp=%d writep=%d\n", + txq_id, il->cmd_queue, sequence, il->txq[il->cmd_queue].q.read_ptr, + il->txq[il->cmd_queue].q.write_ptr)) { + il_print_hex_error(il, pkt, 32); + return; + } + + cmd_idx = il_get_cmd_idx(&txq->q, idx, huge); + cmd = txq->cmd[cmd_idx]; + meta = &txq->meta[cmd_idx]; + + txq->time_stamp = jiffies; + + pci_unmap_single(il->pci_dev, dma_unmap_addr(meta, mapping), + dma_unmap_len(meta, len), PCI_DMA_BIDIRECTIONAL); + + /* Input error checking is done when commands are added to queue. */ + if (meta->flags & CMD_WANT_SKB) { + meta->source->reply_page = (unsigned long)rxb_addr(rxb); + rxb->page = NULL; + } else if (meta->callback) + meta->callback(il, cmd, pkt); + + spin_lock_irqsave(&il->hcmd_lock, flags); + + il_hcmd_queue_reclaim(il, txq_id, idx, cmd_idx); + + if (!(meta->flags & CMD_ASYNC)) { + clear_bit(S_HCMD_ACTIVE, &il->status); + D_INFO("Clearing HCMD_ACTIVE for command %s\n", + il_get_cmd_string(cmd->hdr.cmd)); + wake_up(&il->wait_command_queue); + } + + /* Mark as unmapped */ + meta->flags = 0; + + spin_unlock_irqrestore(&il->hcmd_lock, flags); +} +EXPORT_SYMBOL(il_tx_cmd_complete); + +MODULE_DESCRIPTION("iwl-legacy: common functions for 3945 and 4965"); +MODULE_VERSION(IWLWIFI_VERSION); +MODULE_AUTHOR(DRV_COPYRIGHT " " DRV_AUTHOR); +MODULE_LICENSE("GPL"); + +/* + * set bt_coex_active to true, uCode will do kill/defer + * every time the priority line is asserted (BT is sending signals on the + * priority line in the PCIx). + * set bt_coex_active to false, uCode will ignore the BT activity and + * perform the normal operation + * + * User might experience transmit issue on some platform due to WiFi/BT + * co-exist problem. The possible behaviors are: + * Able to scan and finding all the available AP + * Not able to associate with any AP + * On those platforms, WiFi communication can be restored by set + * "bt_coex_active" module parameter to "false" + * + * default: bt_coex_active = true (BT_COEX_ENABLE) + */ +static bool bt_coex_active = true; +module_param(bt_coex_active, bool, S_IRUGO); +MODULE_PARM_DESC(bt_coex_active, "enable wifi/bluetooth co-exist"); + +u32 il_debug_level; +EXPORT_SYMBOL(il_debug_level); + +const u8 il_bcast_addr[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; +EXPORT_SYMBOL(il_bcast_addr); + +#define MAX_BIT_RATE_40_MHZ 150 /* Mbps */ +#define MAX_BIT_RATE_20_MHZ 72 /* Mbps */ +static void +il_init_ht_hw_capab(const struct il_priv *il, + struct ieee80211_sta_ht_cap *ht_info, + enum ieee80211_band band) +{ + u16 max_bit_rate = 0; + u8 rx_chains_num = il->hw_params.rx_chains_num; + u8 tx_chains_num = il->hw_params.tx_chains_num; + + ht_info->cap = 0; + memset(&ht_info->mcs, 0, sizeof(ht_info->mcs)); + + ht_info->ht_supported = true; + + ht_info->cap |= IEEE80211_HT_CAP_SGI_20; + max_bit_rate = MAX_BIT_RATE_20_MHZ; + if (il->hw_params.ht40_channel & BIT(band)) { + ht_info->cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + ht_info->cap |= IEEE80211_HT_CAP_SGI_40; + ht_info->mcs.rx_mask[4] = 0x01; + max_bit_rate = MAX_BIT_RATE_40_MHZ; + } + + if (il->cfg->mod_params->amsdu_size_8K) + ht_info->cap |= IEEE80211_HT_CAP_MAX_AMSDU; + + ht_info->ampdu_factor = CFG_HT_RX_AMPDU_FACTOR_DEF; + ht_info->ampdu_density = CFG_HT_MPDU_DENSITY_DEF; + + ht_info->mcs.rx_mask[0] = 0xFF; + if (rx_chains_num >= 2) + ht_info->mcs.rx_mask[1] = 0xFF; + if (rx_chains_num >= 3) + ht_info->mcs.rx_mask[2] = 0xFF; + + /* Highest supported Rx data rate */ + max_bit_rate *= rx_chains_num; + WARN_ON(max_bit_rate & ~IEEE80211_HT_MCS_RX_HIGHEST_MASK); + ht_info->mcs.rx_highest = cpu_to_le16(max_bit_rate); + + /* Tx MCS capabilities */ + ht_info->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; + if (tx_chains_num != rx_chains_num) { + ht_info->mcs.tx_params |= IEEE80211_HT_MCS_TX_RX_DIFF; + ht_info->mcs.tx_params |= + ((tx_chains_num - + 1) << IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT); + } +} + +/** + * il_init_geos - Initialize mac80211's geo/channel info based from eeprom + */ +int +il_init_geos(struct il_priv *il) +{ + struct il_channel_info *ch; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *channels; + struct ieee80211_channel *geo_ch; + struct ieee80211_rate *rates; + int i = 0; + s8 max_tx_power = 0; + + if (il->bands[IEEE80211_BAND_2GHZ].n_bitrates || + il->bands[IEEE80211_BAND_5GHZ].n_bitrates) { + D_INFO("Geography modes already initialized.\n"); + set_bit(S_GEO_CONFIGURED, &il->status); + return 0; + } + + channels = + kzalloc(sizeof(struct ieee80211_channel) * il->channel_count, + GFP_KERNEL); + if (!channels) + return -ENOMEM; + + rates = + kzalloc((sizeof(struct ieee80211_rate) * RATE_COUNT_LEGACY), + GFP_KERNEL); + if (!rates) { + kfree(channels); + return -ENOMEM; + } + + /* 5.2GHz channels start after the 2.4GHz channels */ + sband = &il->bands[IEEE80211_BAND_5GHZ]; + sband->channels = &channels[ARRAY_SIZE(il_eeprom_band_1)]; + /* just OFDM */ + sband->bitrates = &rates[IL_FIRST_OFDM_RATE]; + sband->n_bitrates = RATE_COUNT_LEGACY - IL_FIRST_OFDM_RATE; + + if (il->cfg->sku & IL_SKU_N) + il_init_ht_hw_capab(il, &sband->ht_cap, IEEE80211_BAND_5GHZ); + + sband = &il->bands[IEEE80211_BAND_2GHZ]; + sband->channels = channels; + /* OFDM & CCK */ + sband->bitrates = rates; + sband->n_bitrates = RATE_COUNT_LEGACY; + + if (il->cfg->sku & IL_SKU_N) + il_init_ht_hw_capab(il, &sband->ht_cap, IEEE80211_BAND_2GHZ); + + il->ieee_channels = channels; + il->ieee_rates = rates; + + for (i = 0; i < il->channel_count; i++) { + ch = &il->channel_info[i]; + + if (!il_is_channel_valid(ch)) + continue; + + sband = &il->bands[ch->band]; + + geo_ch = &sband->channels[sband->n_channels++]; + + geo_ch->center_freq = + ieee80211_channel_to_frequency(ch->channel, ch->band); + geo_ch->max_power = ch->max_power_avg; + geo_ch->max_antenna_gain = 0xff; + geo_ch->hw_value = ch->channel; + + if (il_is_channel_valid(ch)) { + if (!(ch->flags & EEPROM_CHANNEL_IBSS)) + geo_ch->flags |= IEEE80211_CHAN_NO_IBSS; + + if (!(ch->flags & EEPROM_CHANNEL_ACTIVE)) + geo_ch->flags |= IEEE80211_CHAN_PASSIVE_SCAN; + + if (ch->flags & EEPROM_CHANNEL_RADAR) + geo_ch->flags |= IEEE80211_CHAN_RADAR; + + geo_ch->flags |= ch->ht40_extension_channel; + + if (ch->max_power_avg > max_tx_power) + max_tx_power = ch->max_power_avg; + } else { + geo_ch->flags |= IEEE80211_CHAN_DISABLED; + } + + D_INFO("Channel %d Freq=%d[%sGHz] %s flag=0x%X\n", ch->channel, + geo_ch->center_freq, + il_is_channel_a_band(ch) ? "5.2" : "2.4", + geo_ch-> + flags & IEEE80211_CHAN_DISABLED ? "restricted" : "valid", + geo_ch->flags); + } + + il->tx_power_device_lmt = max_tx_power; + il->tx_power_user_lmt = max_tx_power; + il->tx_power_next = max_tx_power; + + if (il->bands[IEEE80211_BAND_5GHZ].n_channels == 0 && + (il->cfg->sku & IL_SKU_A)) { + IL_INFO("Incorrectly detected BG card as ABG. " + "Please send your PCI ID 0x%04X:0x%04X to maintainer.\n", + il->pci_dev->device, il->pci_dev->subsystem_device); + il->cfg->sku &= ~IL_SKU_A; + } + + IL_INFO("Tunable channels: %d 802.11bg, %d 802.11a channels\n", + il->bands[IEEE80211_BAND_2GHZ].n_channels, + il->bands[IEEE80211_BAND_5GHZ].n_channels); + + set_bit(S_GEO_CONFIGURED, &il->status); + + return 0; +} +EXPORT_SYMBOL(il_init_geos); + +/* + * il_free_geos - undo allocations in il_init_geos + */ +void +il_free_geos(struct il_priv *il) +{ + kfree(il->ieee_channels); + kfree(il->ieee_rates); + clear_bit(S_GEO_CONFIGURED, &il->status); +} +EXPORT_SYMBOL(il_free_geos); + +static bool +il_is_channel_extension(struct il_priv *il, enum ieee80211_band band, + u16 channel, u8 extension_chan_offset) +{ + const struct il_channel_info *ch_info; + + ch_info = il_get_channel_info(il, band, channel); + if (!il_is_channel_valid(ch_info)) + return false; + + if (extension_chan_offset == IEEE80211_HT_PARAM_CHA_SEC_ABOVE) + return !(ch_info-> + ht40_extension_channel & IEEE80211_CHAN_NO_HT40PLUS); + else if (extension_chan_offset == IEEE80211_HT_PARAM_CHA_SEC_BELOW) + return !(ch_info-> + ht40_extension_channel & IEEE80211_CHAN_NO_HT40MINUS); + + return false; +} + +bool +il_is_ht40_tx_allowed(struct il_priv *il, struct ieee80211_sta_ht_cap *ht_cap) +{ + if (!il->ht.enabled || !il->ht.is_40mhz) + return false; + + /* + * We do not check for IEEE80211_HT_CAP_SUP_WIDTH_20_40 + * the bit will not set if it is pure 40MHz case + */ + if (ht_cap && !ht_cap->ht_supported) + return false; + +#ifdef CPTCFG_IWLEGACY_DEBUGFS + if (il->disable_ht40) + return false; +#endif + + return il_is_channel_extension(il, il->band, + le16_to_cpu(il->staging.channel), + il->ht.extension_chan_offset); +} +EXPORT_SYMBOL(il_is_ht40_tx_allowed); + +static u16 +il_adjust_beacon_interval(u16 beacon_val, u16 max_beacon_val) +{ + u16 new_val; + u16 beacon_factor; + + /* + * If mac80211 hasn't given us a beacon interval, program + * the default into the device. + */ + if (!beacon_val) + return DEFAULT_BEACON_INTERVAL; + + /* + * If the beacon interval we obtained from the peer + * is too large, we'll have to wake up more often + * (and in IBSS case, we'll beacon too much) + * + * For example, if max_beacon_val is 4096, and the + * requested beacon interval is 7000, we'll have to + * use 3500 to be able to wake up on the beacons. + * + * This could badly influence beacon detection stats. + */ + + beacon_factor = (beacon_val + max_beacon_val) / max_beacon_val; + new_val = beacon_val / beacon_factor; + + if (!new_val) + new_val = max_beacon_val; + + return new_val; +} + +int +il_send_rxon_timing(struct il_priv *il) +{ + u64 tsf; + s32 interval_tm, rem; + struct ieee80211_conf *conf = NULL; + u16 beacon_int; + struct ieee80211_vif *vif = il->vif; + + conf = &il->hw->conf; + + lockdep_assert_held(&il->mutex); + + memset(&il->timing, 0, sizeof(struct il_rxon_time_cmd)); + + il->timing.timestamp = cpu_to_le64(il->timestamp); + il->timing.listen_interval = cpu_to_le16(conf->listen_interval); + + beacon_int = vif ? vif->bss_conf.beacon_int : 0; + + /* + * TODO: For IBSS we need to get atim_win from mac80211, + * for now just always use 0 + */ + il->timing.atim_win = 0; + + beacon_int = + il_adjust_beacon_interval(beacon_int, + il->hw_params.max_beacon_itrvl * + TIME_UNIT); + il->timing.beacon_interval = cpu_to_le16(beacon_int); + + tsf = il->timestamp; /* tsf is modifed by do_div: copy it */ + interval_tm = beacon_int * TIME_UNIT; + rem = do_div(tsf, interval_tm); + il->timing.beacon_init_val = cpu_to_le32(interval_tm - rem); + + il->timing.dtim_period = vif ? (vif->bss_conf.dtim_period ? : 1) : 1; + + D_ASSOC("beacon interval %d beacon timer %d beacon tim %d\n", + le16_to_cpu(il->timing.beacon_interval), + le32_to_cpu(il->timing.beacon_init_val), + le16_to_cpu(il->timing.atim_win)); + + return il_send_cmd_pdu(il, C_RXON_TIMING, sizeof(il->timing), + &il->timing); +} +EXPORT_SYMBOL(il_send_rxon_timing); + +void +il_set_rxon_hwcrypto(struct il_priv *il, int hw_decrypt) +{ + struct il_rxon_cmd *rxon = &il->staging; + + if (hw_decrypt) + rxon->filter_flags &= ~RXON_FILTER_DIS_DECRYPT_MSK; + else + rxon->filter_flags |= RXON_FILTER_DIS_DECRYPT_MSK; + +} +EXPORT_SYMBOL(il_set_rxon_hwcrypto); + +/* validate RXON structure is valid */ +int +il_check_rxon_cmd(struct il_priv *il) +{ + struct il_rxon_cmd *rxon = &il->staging; + bool error = false; + + if (rxon->flags & RXON_FLG_BAND_24G_MSK) { + if (rxon->flags & RXON_FLG_TGJ_NARROW_BAND_MSK) { + IL_WARN("check 2.4G: wrong narrow\n"); + error = true; + } + if (rxon->flags & RXON_FLG_RADAR_DETECT_MSK) { + IL_WARN("check 2.4G: wrong radar\n"); + error = true; + } + } else { + if (!(rxon->flags & RXON_FLG_SHORT_SLOT_MSK)) { + IL_WARN("check 5.2G: not short slot!\n"); + error = true; + } + if (rxon->flags & RXON_FLG_CCK_MSK) { + IL_WARN("check 5.2G: CCK!\n"); + error = true; + } + } + if ((rxon->node_addr[0] | rxon->bssid_addr[0]) & 0x1) { + IL_WARN("mac/bssid mcast!\n"); + error = true; + } + + /* make sure basic rates 6Mbps and 1Mbps are supported */ + if ((rxon->ofdm_basic_rates & RATE_6M_MASK) == 0 && + (rxon->cck_basic_rates & RATE_1M_MASK) == 0) { + IL_WARN("neither 1 nor 6 are basic\n"); + error = true; + } + + if (le16_to_cpu(rxon->assoc_id) > 2007) { + IL_WARN("aid > 2007\n"); + error = true; + } + + if ((rxon->flags & (RXON_FLG_CCK_MSK | RXON_FLG_SHORT_SLOT_MSK)) == + (RXON_FLG_CCK_MSK | RXON_FLG_SHORT_SLOT_MSK)) { + IL_WARN("CCK and short slot\n"); + error = true; + } + + if ((rxon->flags & (RXON_FLG_CCK_MSK | RXON_FLG_AUTO_DETECT_MSK)) == + (RXON_FLG_CCK_MSK | RXON_FLG_AUTO_DETECT_MSK)) { + IL_WARN("CCK and auto detect"); + error = true; + } + + if ((rxon-> + flags & (RXON_FLG_AUTO_DETECT_MSK | RXON_FLG_TGG_PROTECT_MSK)) == + RXON_FLG_TGG_PROTECT_MSK) { + IL_WARN("TGg but no auto-detect\n"); + error = true; + } + + if (error) + IL_WARN("Tuning to channel %d\n", le16_to_cpu(rxon->channel)); + + if (error) { + IL_ERR("Invalid RXON\n"); + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL(il_check_rxon_cmd); + +/** + * il_full_rxon_required - check if full RXON (vs RXON_ASSOC) cmd is needed + * @il: staging_rxon is compared to active_rxon + * + * If the RXON structure is changing enough to require a new tune, + * or is clearing the RXON_FILTER_ASSOC_MSK, then return 1 to indicate that + * a new tune (full RXON command, rather than RXON_ASSOC cmd) is required. + */ +int +il_full_rxon_required(struct il_priv *il) +{ + const struct il_rxon_cmd *staging = &il->staging; + const struct il_rxon_cmd *active = &il->active; + +#define CHK(cond) \ + if ((cond)) { \ + D_INFO("need full RXON - " #cond "\n"); \ + return 1; \ + } + +#define CHK_NEQ(c1, c2) \ + if ((c1) != (c2)) { \ + D_INFO("need full RXON - " \ + #c1 " != " #c2 " - %d != %d\n", \ + (c1), (c2)); \ + return 1; \ + } + + /* These items are only settable from the full RXON command */ + CHK(!il_is_associated(il)); + CHK(!ether_addr_equal(staging->bssid_addr, active->bssid_addr)); + CHK(!ether_addr_equal(staging->node_addr, active->node_addr)); + CHK(!ether_addr_equal(staging->wlap_bssid_addr, + active->wlap_bssid_addr)); + CHK_NEQ(staging->dev_type, active->dev_type); + CHK_NEQ(staging->channel, active->channel); + CHK_NEQ(staging->air_propagation, active->air_propagation); + CHK_NEQ(staging->ofdm_ht_single_stream_basic_rates, + active->ofdm_ht_single_stream_basic_rates); + CHK_NEQ(staging->ofdm_ht_dual_stream_basic_rates, + active->ofdm_ht_dual_stream_basic_rates); + CHK_NEQ(staging->assoc_id, active->assoc_id); + + /* flags, filter_flags, ofdm_basic_rates, and cck_basic_rates can + * be updated with the RXON_ASSOC command -- however only some + * flag transitions are allowed using RXON_ASSOC */ + + /* Check if we are not switching bands */ + CHK_NEQ(staging->flags & RXON_FLG_BAND_24G_MSK, + active->flags & RXON_FLG_BAND_24G_MSK); + + /* Check if we are switching association toggle */ + CHK_NEQ(staging->filter_flags & RXON_FILTER_ASSOC_MSK, + active->filter_flags & RXON_FILTER_ASSOC_MSK); + +#undef CHK +#undef CHK_NEQ + + return 0; +} +EXPORT_SYMBOL(il_full_rxon_required); + +u8 +il_get_lowest_plcp(struct il_priv *il) +{ + /* + * Assign the lowest rate -- should really get this from + * the beacon skb from mac80211. + */ + if (il->staging.flags & RXON_FLG_BAND_24G_MSK) + return RATE_1M_PLCP; + else + return RATE_6M_PLCP; +} +EXPORT_SYMBOL(il_get_lowest_plcp); + +static void +_il_set_rxon_ht(struct il_priv *il, struct il_ht_config *ht_conf) +{ + struct il_rxon_cmd *rxon = &il->staging; + + if (!il->ht.enabled) { + rxon->flags &= + ~(RXON_FLG_CHANNEL_MODE_MSK | + RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK | RXON_FLG_HT40_PROT_MSK + | RXON_FLG_HT_PROT_MSK); + return; + } + + rxon->flags |= + cpu_to_le32(il->ht.protection << RXON_FLG_HT_OPERATING_MODE_POS); + + /* Set up channel bandwidth: + * 20 MHz only, 20/40 mixed or pure 40 if ht40 ok */ + /* clear the HT channel mode before set the mode */ + rxon->flags &= + ~(RXON_FLG_CHANNEL_MODE_MSK | RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK); + if (il_is_ht40_tx_allowed(il, NULL)) { + /* pure ht40 */ + if (il->ht.protection == IEEE80211_HT_OP_MODE_PROTECTION_20MHZ) { + rxon->flags |= RXON_FLG_CHANNEL_MODE_PURE_40; + /* Note: control channel is opposite of extension channel */ + switch (il->ht.extension_chan_offset) { + case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: + rxon->flags &= + ~RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK; + break; + case IEEE80211_HT_PARAM_CHA_SEC_BELOW: + rxon->flags |= RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK; + break; + } + } else { + /* Note: control channel is opposite of extension channel */ + switch (il->ht.extension_chan_offset) { + case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: + rxon->flags &= + ~(RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK); + rxon->flags |= RXON_FLG_CHANNEL_MODE_MIXED; + break; + case IEEE80211_HT_PARAM_CHA_SEC_BELOW: + rxon->flags |= RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK; + rxon->flags |= RXON_FLG_CHANNEL_MODE_MIXED; + break; + case IEEE80211_HT_PARAM_CHA_SEC_NONE: + default: + /* channel location only valid if in Mixed mode */ + IL_ERR("invalid extension channel offset\n"); + break; + } + } + } else { + rxon->flags |= RXON_FLG_CHANNEL_MODE_LEGACY; + } + + if (il->ops->set_rxon_chain) + il->ops->set_rxon_chain(il); + + D_ASSOC("rxon flags 0x%X operation mode :0x%X " + "extension channel offset 0x%x\n", le32_to_cpu(rxon->flags), + il->ht.protection, il->ht.extension_chan_offset); +} + +void +il_set_rxon_ht(struct il_priv *il, struct il_ht_config *ht_conf) +{ + _il_set_rxon_ht(il, ht_conf); +} +EXPORT_SYMBOL(il_set_rxon_ht); + +/* Return valid, unused, channel for a passive scan to reset the RF */ +u8 +il_get_single_channel_number(struct il_priv *il, enum ieee80211_band band) +{ + const struct il_channel_info *ch_info; + int i; + u8 channel = 0; + u8 min, max; + + if (band == IEEE80211_BAND_5GHZ) { + min = 14; + max = il->channel_count; + } else { + min = 0; + max = 14; + } + + for (i = min; i < max; i++) { + channel = il->channel_info[i].channel; + if (channel == le16_to_cpu(il->staging.channel)) + continue; + + ch_info = il_get_channel_info(il, band, channel); + if (il_is_channel_valid(ch_info)) + break; + } + + return channel; +} +EXPORT_SYMBOL(il_get_single_channel_number); + +/** + * il_set_rxon_channel - Set the band and channel values in staging RXON + * @ch: requested channel as a pointer to struct ieee80211_channel + + * NOTE: Does not commit to the hardware; it sets appropriate bit fields + * in the staging RXON flag structure based on the ch->band + */ +int +il_set_rxon_channel(struct il_priv *il, struct ieee80211_channel *ch) +{ + enum ieee80211_band band = ch->band; + u16 channel = ch->hw_value; + + if (le16_to_cpu(il->staging.channel) == channel && il->band == band) + return 0; + + il->staging.channel = cpu_to_le16(channel); + if (band == IEEE80211_BAND_5GHZ) + il->staging.flags &= ~RXON_FLG_BAND_24G_MSK; + else + il->staging.flags |= RXON_FLG_BAND_24G_MSK; + + il->band = band; + + D_INFO("Staging channel set to %d [%d]\n", channel, band); + + return 0; +} +EXPORT_SYMBOL(il_set_rxon_channel); + +void +il_set_flags_for_band(struct il_priv *il, enum ieee80211_band band, + struct ieee80211_vif *vif) +{ + if (band == IEEE80211_BAND_5GHZ) { + il->staging.flags &= + ~(RXON_FLG_BAND_24G_MSK | RXON_FLG_AUTO_DETECT_MSK | + RXON_FLG_CCK_MSK); + il->staging.flags |= RXON_FLG_SHORT_SLOT_MSK; + } else { + /* Copied from il_post_associate() */ + if (vif && vif->bss_conf.use_short_slot) + il->staging.flags |= RXON_FLG_SHORT_SLOT_MSK; + else + il->staging.flags &= ~RXON_FLG_SHORT_SLOT_MSK; + + il->staging.flags |= RXON_FLG_BAND_24G_MSK; + il->staging.flags |= RXON_FLG_AUTO_DETECT_MSK; + il->staging.flags &= ~RXON_FLG_CCK_MSK; + } +} +EXPORT_SYMBOL(il_set_flags_for_band); + +/* + * initialize rxon structure with default values from eeprom + */ +void +il_connection_init_rx_config(struct il_priv *il) +{ + const struct il_channel_info *ch_info; + + memset(&il->staging, 0, sizeof(il->staging)); + + switch (il->iw_mode) { + case NL80211_IFTYPE_UNSPECIFIED: + il->staging.dev_type = RXON_DEV_TYPE_ESS; + break; + case NL80211_IFTYPE_STATION: + il->staging.dev_type = RXON_DEV_TYPE_ESS; + il->staging.filter_flags = RXON_FILTER_ACCEPT_GRP_MSK; + break; + case NL80211_IFTYPE_ADHOC: + il->staging.dev_type = RXON_DEV_TYPE_IBSS; + il->staging.flags = RXON_FLG_SHORT_PREAMBLE_MSK; + il->staging.filter_flags = + RXON_FILTER_BCON_AWARE_MSK | RXON_FILTER_ACCEPT_GRP_MSK; + break; + default: + IL_ERR("Unsupported interface type %d\n", il->vif->type); + return; + } + +#if 0 + /* TODO: Figure out when short_preamble would be set and cache from + * that */ + if (!hw_to_local(il->hw)->short_preamble) + il->staging.flags &= ~RXON_FLG_SHORT_PREAMBLE_MSK; + else + il->staging.flags |= RXON_FLG_SHORT_PREAMBLE_MSK; +#endif + + ch_info = + il_get_channel_info(il, il->band, le16_to_cpu(il->active.channel)); + + if (!ch_info) + ch_info = &il->channel_info[0]; + + il->staging.channel = cpu_to_le16(ch_info->channel); + il->band = ch_info->band; + + il_set_flags_for_band(il, il->band, il->vif); + + il->staging.ofdm_basic_rates = + (IL_OFDM_RATES_MASK >> IL_FIRST_OFDM_RATE) & 0xFF; + il->staging.cck_basic_rates = + (IL_CCK_RATES_MASK >> IL_FIRST_CCK_RATE) & 0xF; + + /* clear both MIX and PURE40 mode flag */ + il->staging.flags &= + ~(RXON_FLG_CHANNEL_MODE_MIXED | RXON_FLG_CHANNEL_MODE_PURE_40); + if (il->vif) + memcpy(il->staging.node_addr, il->vif->addr, ETH_ALEN); + + il->staging.ofdm_ht_single_stream_basic_rates = 0xff; + il->staging.ofdm_ht_dual_stream_basic_rates = 0xff; +} +EXPORT_SYMBOL(il_connection_init_rx_config); + +void +il_set_rate(struct il_priv *il) +{ + const struct ieee80211_supported_band *hw = NULL; + struct ieee80211_rate *rate; + int i; + + hw = il_get_hw_mode(il, il->band); + if (!hw) { + IL_ERR("Failed to set rate: unable to get hw mode\n"); + return; + } + + il->active_rate = 0; + + for (i = 0; i < hw->n_bitrates; i++) { + rate = &(hw->bitrates[i]); + if (rate->hw_value < RATE_COUNT_LEGACY) + il->active_rate |= (1 << rate->hw_value); + } + + D_RATE("Set active_rate = %0x\n", il->active_rate); + + il->staging.cck_basic_rates = + (IL_CCK_BASIC_RATES_MASK >> IL_FIRST_CCK_RATE) & 0xF; + + il->staging.ofdm_basic_rates = + (IL_OFDM_BASIC_RATES_MASK >> IL_FIRST_OFDM_RATE) & 0xFF; +} +EXPORT_SYMBOL(il_set_rate); + +void +il_chswitch_done(struct il_priv *il, bool is_success) +{ + if (test_bit(S_EXIT_PENDING, &il->status)) + return; + + if (test_and_clear_bit(S_CHANNEL_SWITCH_PENDING, &il->status)) + ieee80211_chswitch_done(il->vif, is_success); +} +EXPORT_SYMBOL(il_chswitch_done); + +void +il_hdl_csa(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il_csa_notification *csa = &(pkt->u.csa_notif); + struct il_rxon_cmd *rxon = (void *)&il->active; + + if (!test_bit(S_CHANNEL_SWITCH_PENDING, &il->status)) + return; + + if (!le32_to_cpu(csa->status) && csa->channel == il->switch_channel) { + rxon->channel = csa->channel; + il->staging.channel = csa->channel; + D_11H("CSA notif: channel %d\n", le16_to_cpu(csa->channel)); + il_chswitch_done(il, true); + } else { + IL_ERR("CSA notif (fail) : channel %d\n", + le16_to_cpu(csa->channel)); + il_chswitch_done(il, false); + } +} +EXPORT_SYMBOL(il_hdl_csa); + +#ifdef CPTCFG_IWLEGACY_DEBUG +void +il_print_rx_config_cmd(struct il_priv *il) +{ + struct il_rxon_cmd *rxon = &il->staging; + + D_RADIO("RX CONFIG:\n"); + il_print_hex_dump(il, IL_DL_RADIO, (u8 *) rxon, sizeof(*rxon)); + D_RADIO("u16 channel: 0x%x\n", le16_to_cpu(rxon->channel)); + D_RADIO("u32 flags: 0x%08X\n", le32_to_cpu(rxon->flags)); + D_RADIO("u32 filter_flags: 0x%08x\n", le32_to_cpu(rxon->filter_flags)); + D_RADIO("u8 dev_type: 0x%x\n", rxon->dev_type); + D_RADIO("u8 ofdm_basic_rates: 0x%02x\n", rxon->ofdm_basic_rates); + D_RADIO("u8 cck_basic_rates: 0x%02x\n", rxon->cck_basic_rates); + D_RADIO("u8[6] node_addr: %pM\n", rxon->node_addr); + D_RADIO("u8[6] bssid_addr: %pM\n", rxon->bssid_addr); + D_RADIO("u16 assoc_id: 0x%x\n", le16_to_cpu(rxon->assoc_id)); +} +EXPORT_SYMBOL(il_print_rx_config_cmd); +#endif +/** + * il_irq_handle_error - called for HW or SW error interrupt from card + */ +void +il_irq_handle_error(struct il_priv *il) +{ + /* Set the FW error flag -- cleared on il_down */ + set_bit(S_FW_ERROR, &il->status); + + /* Cancel currently queued command. */ + clear_bit(S_HCMD_ACTIVE, &il->status); + + IL_ERR("Loaded firmware version: %s\n", il->hw->wiphy->fw_version); + + il->ops->dump_nic_error_log(il); + if (il->ops->dump_fh) + il->ops->dump_fh(il, NULL, false); +#ifdef CPTCFG_IWLEGACY_DEBUG + if (il_get_debug_level(il) & IL_DL_FW_ERRORS) + il_print_rx_config_cmd(il); +#endif + + wake_up(&il->wait_command_queue); + + /* Keep the restart process from trying to send host + * commands by clearing the INIT status bit */ + clear_bit(S_READY, &il->status); + + if (!test_bit(S_EXIT_PENDING, &il->status)) { + IL_DBG(IL_DL_FW_ERRORS, + "Restarting adapter due to uCode error.\n"); + + if (il->cfg->mod_params->restart_fw) + queue_work(il->workqueue, &il->restart); + } +} +EXPORT_SYMBOL(il_irq_handle_error); + +static int +_il_apm_stop_master(struct il_priv *il) +{ + int ret = 0; + + /* stop device's busmaster DMA activity */ + _il_set_bit(il, CSR_RESET, CSR_RESET_REG_FLAG_STOP_MASTER); + + ret = + _il_poll_bit(il, CSR_RESET, CSR_RESET_REG_FLAG_MASTER_DISABLED, + CSR_RESET_REG_FLAG_MASTER_DISABLED, 100); + if (ret < 0) + IL_WARN("Master Disable Timed Out, 100 usec\n"); + + D_INFO("stop master\n"); + + return ret; +} + +void +_il_apm_stop(struct il_priv *il) +{ + lockdep_assert_held(&il->reg_lock); + + D_INFO("Stop card, put in low power state\n"); + + /* Stop device's DMA activity */ + _il_apm_stop_master(il); + + /* Reset the entire device */ + _il_set_bit(il, CSR_RESET, CSR_RESET_REG_FLAG_SW_RESET); + + udelay(10); + + /* + * Clear "initialization complete" bit to move adapter from + * D0A* (powered-up Active) --> D0U* (Uninitialized) state. + */ + _il_clear_bit(il, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE); +} +EXPORT_SYMBOL(_il_apm_stop); + +void +il_apm_stop(struct il_priv *il) +{ + unsigned long flags; + + spin_lock_irqsave(&il->reg_lock, flags); + _il_apm_stop(il); + spin_unlock_irqrestore(&il->reg_lock, flags); +} +EXPORT_SYMBOL(il_apm_stop); + +/* + * Start up NIC's basic functionality after it has been reset + * (e.g. after platform boot, or shutdown via il_apm_stop()) + * NOTE: This does not load uCode nor start the embedded processor + */ +int +il_apm_init(struct il_priv *il) +{ + int ret = 0; + u16 lctl; + + D_INFO("Init card's basic functions\n"); + + /* + * Use "set_bit" below rather than "write", to preserve any hardware + * bits already set by default after reset. + */ + + /* Disable L0S exit timer (platform NMI Work/Around) */ + il_set_bit(il, CSR_GIO_CHICKEN_BITS, + CSR_GIO_CHICKEN_BITS_REG_BIT_DIS_L0S_EXIT_TIMER); + + /* + * Disable L0s without affecting L1; + * don't wait for ICH L0s (ICH bug W/A) + */ + il_set_bit(il, CSR_GIO_CHICKEN_BITS, + CSR_GIO_CHICKEN_BITS_REG_BIT_L1A_NO_L0S_RX); + + /* Set FH wait threshold to maximum (HW error during stress W/A) */ + il_set_bit(il, CSR_DBG_HPET_MEM_REG, CSR_DBG_HPET_MEM_REG_VAL); + + /* + * Enable HAP INTA (interrupt from management bus) to + * wake device's PCI Express link L1a -> L0s + * NOTE: This is no-op for 3945 (non-existent bit) + */ + il_set_bit(il, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_HAP_WAKE_L1A); + + /* + * HW bug W/A for instability in PCIe bus L0->L0S->L1 transition. + * Check if BIOS (or OS) enabled L1-ASPM on this device. + * If so (likely), disable L0S, so device moves directly L0->L1; + * costs negligible amount of power savings. + * If not (unlikely), enable L0S, so there is at least some + * power savings, even without L1. + */ + if (il->cfg->set_l0s) { + pcie_capability_read_word(il->pci_dev, PCI_EXP_LNKCTL, &lctl); + if (lctl & PCI_EXP_LNKCTL_ASPM_L1) { + /* L1-ASPM enabled; disable(!) L0S */ + il_set_bit(il, CSR_GIO_REG, + CSR_GIO_REG_VAL_L0S_ENABLED); + D_POWER("L1 Enabled; Disabling L0S\n"); + } else { + /* L1-ASPM disabled; enable(!) L0S */ + il_clear_bit(il, CSR_GIO_REG, + CSR_GIO_REG_VAL_L0S_ENABLED); + D_POWER("L1 Disabled; Enabling L0S\n"); + } + } + + /* Configure analog phase-lock-loop before activating to D0A */ + if (il->cfg->pll_cfg_val) + il_set_bit(il, CSR_ANA_PLL_CFG, + il->cfg->pll_cfg_val); + + /* + * Set "initialization complete" bit to move adapter from + * D0U* --> D0A* (powered-up active) state. + */ + il_set_bit(il, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE); + + /* + * Wait for clock stabilization; once stabilized, access to + * device-internal resources is supported, e.g. il_wr_prph() + * and accesses to uCode SRAM. + */ + ret = + _il_poll_bit(il, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, + CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, 25000); + if (ret < 0) { + D_INFO("Failed to init the card\n"); + goto out; + } + + /* + * Enable DMA and BSM (if used) clocks, wait for them to stabilize. + * BSM (Boostrap State Machine) is only in 3945 and 4965. + * + * Write to "CLK_EN_REG"; "1" bits enable clocks, while "0" bits + * do not disable clocks. This preserves any hardware bits already + * set by default in "CLK_CTRL_REG" after reset. + */ + if (il->cfg->use_bsm) + il_wr_prph(il, APMG_CLK_EN_REG, + APMG_CLK_VAL_DMA_CLK_RQT | APMG_CLK_VAL_BSM_CLK_RQT); + else + il_wr_prph(il, APMG_CLK_EN_REG, APMG_CLK_VAL_DMA_CLK_RQT); + udelay(20); + + /* Disable L1-Active */ + il_set_bits_prph(il, APMG_PCIDEV_STT_REG, + APMG_PCIDEV_STT_VAL_L1_ACT_DIS); + +out: + return ret; +} +EXPORT_SYMBOL(il_apm_init); + +int +il_set_tx_power(struct il_priv *il, s8 tx_power, bool force) +{ + int ret; + s8 prev_tx_power; + bool defer; + + lockdep_assert_held(&il->mutex); + + if (il->tx_power_user_lmt == tx_power && !force) + return 0; + + if (!il->ops->send_tx_power) + return -EOPNOTSUPP; + + /* 0 dBm mean 1 milliwatt */ + if (tx_power < 0) { + IL_WARN("Requested user TXPOWER %d below 1 mW.\n", tx_power); + return -EINVAL; + } + + if (tx_power > il->tx_power_device_lmt) { + IL_WARN("Requested user TXPOWER %d above upper limit %d.\n", + tx_power, il->tx_power_device_lmt); + return -EINVAL; + } + + if (!il_is_ready_rf(il)) + return -EIO; + + /* scan complete and commit_rxon use tx_power_next value, + * it always need to be updated for newest request */ + il->tx_power_next = tx_power; + + /* do not set tx power when scanning or channel changing */ + defer = test_bit(S_SCANNING, &il->status) || + memcmp(&il->active, &il->staging, sizeof(il->staging)); + if (defer && !force) { + D_INFO("Deferring tx power set\n"); + return 0; + } + + prev_tx_power = il->tx_power_user_lmt; + il->tx_power_user_lmt = tx_power; + + ret = il->ops->send_tx_power(il); + + /* if fail to set tx_power, restore the orig. tx power */ + if (ret) { + il->tx_power_user_lmt = prev_tx_power; + il->tx_power_next = prev_tx_power; + } + return ret; +} +EXPORT_SYMBOL(il_set_tx_power); + +void +il_send_bt_config(struct il_priv *il) +{ + struct il_bt_cmd bt_cmd = { + .lead_time = BT_LEAD_TIME_DEF, + .max_kill = BT_MAX_KILL_DEF, + .kill_ack_mask = 0, + .kill_cts_mask = 0, + }; + + if (!bt_coex_active) + bt_cmd.flags = BT_COEX_DISABLE; + else + bt_cmd.flags = BT_COEX_ENABLE; + + D_INFO("BT coex %s\n", + (bt_cmd.flags == BT_COEX_DISABLE) ? "disable" : "active"); + + if (il_send_cmd_pdu(il, C_BT_CONFIG, sizeof(struct il_bt_cmd), &bt_cmd)) + IL_ERR("failed to send BT Coex Config\n"); +} +EXPORT_SYMBOL(il_send_bt_config); + +int +il_send_stats_request(struct il_priv *il, u8 flags, bool clear) +{ + struct il_stats_cmd stats_cmd = { + .configuration_flags = clear ? IL_STATS_CONF_CLEAR_STATS : 0, + }; + + if (flags & CMD_ASYNC) + return il_send_cmd_pdu_async(il, C_STATS, sizeof(struct il_stats_cmd), + &stats_cmd, NULL); + else + return il_send_cmd_pdu(il, C_STATS, sizeof(struct il_stats_cmd), + &stats_cmd); +} +EXPORT_SYMBOL(il_send_stats_request); + +void +il_hdl_pm_sleep(struct il_priv *il, struct il_rx_buf *rxb) +{ +#ifdef CPTCFG_IWLEGACY_DEBUG + struct il_rx_pkt *pkt = rxb_addr(rxb); + struct il_sleep_notification *sleep = &(pkt->u.sleep_notif); + D_RX("sleep mode: %d, src: %d\n", + sleep->pm_sleep_mode, sleep->pm_wakeup_src); +#endif +} +EXPORT_SYMBOL(il_hdl_pm_sleep); + +void +il_hdl_pm_debug_stats(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + u32 len = le32_to_cpu(pkt->len_n_flags) & IL_RX_FRAME_SIZE_MSK; + D_RADIO("Dumping %d bytes of unhandled notification for %s:\n", len, + il_get_cmd_string(pkt->hdr.cmd)); + il_print_hex_dump(il, IL_DL_RADIO, pkt->u.raw, len); +} +EXPORT_SYMBOL(il_hdl_pm_debug_stats); + +void +il_hdl_error(struct il_priv *il, struct il_rx_buf *rxb) +{ + struct il_rx_pkt *pkt = rxb_addr(rxb); + + IL_ERR("Error Reply type 0x%08X cmd %s (0x%02X) " + "seq 0x%04X ser 0x%08X\n", + le32_to_cpu(pkt->u.err_resp.error_type), + il_get_cmd_string(pkt->u.err_resp.cmd_id), + pkt->u.err_resp.cmd_id, + le16_to_cpu(pkt->u.err_resp.bad_cmd_seq_num), + le32_to_cpu(pkt->u.err_resp.error_info)); +} +EXPORT_SYMBOL(il_hdl_error); + +void +il_clear_isr_stats(struct il_priv *il) +{ + memset(&il->isr_stats, 0, sizeof(il->isr_stats)); +} + +int +il_mac_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct il_priv *il = hw->priv; + unsigned long flags; + int q; + + D_MAC80211("enter\n"); + + if (!il_is_ready_rf(il)) { + D_MAC80211("leave - RF not ready\n"); + return -EIO; + } + + if (queue >= AC_NUM) { + D_MAC80211("leave - queue >= AC_NUM %d\n", queue); + return 0; + } + + q = AC_NUM - 1 - queue; + + spin_lock_irqsave(&il->lock, flags); + + il->qos_data.def_qos_parm.ac[q].cw_min = + cpu_to_le16(params->cw_min); + il->qos_data.def_qos_parm.ac[q].cw_max = + cpu_to_le16(params->cw_max); + il->qos_data.def_qos_parm.ac[q].aifsn = params->aifs; + il->qos_data.def_qos_parm.ac[q].edca_txop = + cpu_to_le16((params->txop * 32)); + + il->qos_data.def_qos_parm.ac[q].reserved1 = 0; + + spin_unlock_irqrestore(&il->lock, flags); + + D_MAC80211("leave\n"); + return 0; +} +EXPORT_SYMBOL(il_mac_conf_tx); + +int +il_mac_tx_last_beacon(struct ieee80211_hw *hw) +{ + struct il_priv *il = hw->priv; + int ret; + + D_MAC80211("enter\n"); + + ret = (il->ibss_manager == IL_IBSS_MANAGER); + + D_MAC80211("leave ret %d\n", ret); + return ret; +} +EXPORT_SYMBOL_GPL(il_mac_tx_last_beacon); + +static int +il_set_mode(struct il_priv *il) +{ + il_connection_init_rx_config(il); + + if (il->ops->set_rxon_chain) + il->ops->set_rxon_chain(il); + + return il_commit_rxon(il); +} + +int +il_mac_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct il_priv *il = hw->priv; + int err; + bool reset; + + mutex_lock(&il->mutex); + D_MAC80211("enter: type %d, addr %pM\n", vif->type, vif->addr); + + if (!il_is_ready_rf(il)) { + IL_WARN("Try to add interface when device not ready\n"); + err = -EINVAL; + goto out; + } + + /* + * We do not support multiple virtual interfaces, but on hardware reset + * we have to add the same interface again. + */ + reset = (il->vif == vif); + if (il->vif && !reset) { + err = -EOPNOTSUPP; + goto out; + } + + il->vif = vif; + il->iw_mode = vif->type; + + err = il_set_mode(il); + if (err) { + IL_WARN("Fail to set mode %d\n", vif->type); + if (!reset) { + il->vif = NULL; + il->iw_mode = NL80211_IFTYPE_STATION; + } + } + +out: + D_MAC80211("leave err %d\n", err); + mutex_unlock(&il->mutex); + + return err; +} +EXPORT_SYMBOL(il_mac_add_interface); + +static void +il_teardown_interface(struct il_priv *il, struct ieee80211_vif *vif) +{ + lockdep_assert_held(&il->mutex); + + if (il->scan_vif == vif) { + il_scan_cancel_timeout(il, 200); + il_force_scan_end(il); + } + + il_set_mode(il); +} + +void +il_mac_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct il_priv *il = hw->priv; + + mutex_lock(&il->mutex); + D_MAC80211("enter: type %d, addr %pM\n", vif->type, vif->addr); + + WARN_ON(il->vif != vif); + il->vif = NULL; + il->iw_mode = NL80211_IFTYPE_UNSPECIFIED; + il_teardown_interface(il, vif); + memset(il->bssid, 0, ETH_ALEN); + + D_MAC80211("leave\n"); + mutex_unlock(&il->mutex); +} +EXPORT_SYMBOL(il_mac_remove_interface); + +int +il_alloc_txq_mem(struct il_priv *il) +{ + if (!il->txq) + il->txq = + kzalloc(sizeof(struct il_tx_queue) * + il->cfg->num_of_queues, GFP_KERNEL); + if (!il->txq) { + IL_ERR("Not enough memory for txq\n"); + return -ENOMEM; + } + return 0; +} +EXPORT_SYMBOL(il_alloc_txq_mem); + +void +il_free_txq_mem(struct il_priv *il) +{ + kfree(il->txq); + il->txq = NULL; +} +EXPORT_SYMBOL(il_free_txq_mem); + +int +il_force_reset(struct il_priv *il, bool external) +{ + struct il_force_reset *force_reset; + + if (test_bit(S_EXIT_PENDING, &il->status)) + return -EINVAL; + + force_reset = &il->force_reset; + force_reset->reset_request_count++; + if (!external) { + if (force_reset->last_force_reset_jiffies && + time_after(force_reset->last_force_reset_jiffies + + force_reset->reset_duration, jiffies)) { + D_INFO("force reset rejected\n"); + force_reset->reset_reject_count++; + return -EAGAIN; + } + } + force_reset->reset_success_count++; + force_reset->last_force_reset_jiffies = jiffies; + + /* + * if the request is from external(ex: debugfs), + * then always perform the request in regardless the module + * parameter setting + * if the request is from internal (uCode error or driver + * detect failure), then fw_restart module parameter + * need to be check before performing firmware reload + */ + + if (!external && !il->cfg->mod_params->restart_fw) { + D_INFO("Cancel firmware reload based on " + "module parameter setting\n"); + return 0; + } + + IL_ERR("On demand firmware reload\n"); + + /* Set the FW error flag -- cleared on il_down */ + set_bit(S_FW_ERROR, &il->status); + wake_up(&il->wait_command_queue); + /* + * Keep the restart process from trying to send host + * commands by clearing the INIT status bit + */ + clear_bit(S_READY, &il->status); + queue_work(il->workqueue, &il->restart); + + return 0; +} +EXPORT_SYMBOL(il_force_reset); + +int +il_mac_change_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + enum nl80211_iftype newtype, bool newp2p) +{ + struct il_priv *il = hw->priv; + int err; + + mutex_lock(&il->mutex); + D_MAC80211("enter: type %d, addr %pM newtype %d newp2p %d\n", + vif->type, vif->addr, newtype, newp2p); + + if (newp2p) { + err = -EOPNOTSUPP; + goto out; + } + + if (!il->vif || !il_is_ready_rf(il)) { + /* + * Huh? But wait ... this can maybe happen when + * we're in the middle of a firmware restart! + */ + err = -EBUSY; + goto out; + } + + /* success */ + vif->type = newtype; + vif->p2p = false; + il->iw_mode = newtype; + il_teardown_interface(il, vif); + err = 0; + +out: + D_MAC80211("leave err %d\n", err); + mutex_unlock(&il->mutex); + + return err; +} +EXPORT_SYMBOL(il_mac_change_interface); + +void il_mac_flush(struct ieee80211_hw *hw, u32 queues, bool drop) +{ + struct il_priv *il = hw->priv; + unsigned long timeout = jiffies + msecs_to_jiffies(500); + int i; + + mutex_lock(&il->mutex); + D_MAC80211("enter\n"); + + if (il->txq == NULL) + goto out; + + for (i = 0; i < il->hw_params.max_txq_num; i++) { + struct il_queue *q; + + if (i == il->cmd_queue) + continue; + + q = &il->txq[i].q; + if (q->read_ptr == q->write_ptr) + continue; + + if (time_after(jiffies, timeout)) { + IL_ERR("Failed to flush queue %d\n", q->id); + break; + } + + msleep(20); + } +out: + D_MAC80211("leave\n"); + mutex_unlock(&il->mutex); +} +EXPORT_SYMBOL(il_mac_flush); + +/* + * On every watchdog tick we check (latest) time stamp. If it does not + * change during timeout period and queue is not empty we reset firmware. + */ +static int +il_check_stuck_queue(struct il_priv *il, int cnt) +{ + struct il_tx_queue *txq = &il->txq[cnt]; + struct il_queue *q = &txq->q; + unsigned long timeout; + unsigned long now = jiffies; + int ret; + + if (q->read_ptr == q->write_ptr) { + txq->time_stamp = now; + return 0; + } + + timeout = + txq->time_stamp + + msecs_to_jiffies(il->cfg->wd_timeout); + + if (time_after(now, timeout)) { + IL_ERR("Queue %d stuck for %u ms.\n", q->id, + jiffies_to_msecs(now - txq->time_stamp)); + ret = il_force_reset(il, false); + return (ret == -EAGAIN) ? 0 : 1; + } + + return 0; +} + +/* + * Making watchdog tick be a quarter of timeout assure we will + * discover the queue hung between timeout and 1.25*timeout + */ +#define IL_WD_TICK(timeout) ((timeout) / 4) + +/* + * Watchdog timer callback, we check each tx queue for stuck, if if hung + * we reset the firmware. If everything is fine just rearm the timer. + */ +void +il_bg_watchdog(unsigned long data) +{ + struct il_priv *il = (struct il_priv *)data; + int cnt; + unsigned long timeout; + + if (test_bit(S_EXIT_PENDING, &il->status)) + return; + + timeout = il->cfg->wd_timeout; + if (timeout == 0) + return; + + /* monitor and check for stuck cmd queue */ + if (il_check_stuck_queue(il, il->cmd_queue)) + return; + + /* monitor and check for other stuck queues */ + for (cnt = 0; cnt < il->hw_params.max_txq_num; cnt++) { + /* skip as we already checked the command queue */ + if (cnt == il->cmd_queue) + continue; + if (il_check_stuck_queue(il, cnt)) + return; + } + + mod_timer(&il->watchdog, + jiffies + msecs_to_jiffies(IL_WD_TICK(timeout))); +} +EXPORT_SYMBOL(il_bg_watchdog); + +void +il_setup_watchdog(struct il_priv *il) +{ + unsigned int timeout = il->cfg->wd_timeout; + + if (timeout) + mod_timer(&il->watchdog, + jiffies + msecs_to_jiffies(IL_WD_TICK(timeout))); + else + del_timer(&il->watchdog); +} +EXPORT_SYMBOL(il_setup_watchdog); + +/* + * extended beacon time format + * time in usec will be changed into a 32-bit value in extended:internal format + * the extended part is the beacon counts + * the internal part is the time in usec within one beacon interval + */ +u32 +il_usecs_to_beacons(struct il_priv *il, u32 usec, u32 beacon_interval) +{ + u32 quot; + u32 rem; + u32 interval = beacon_interval * TIME_UNIT; + + if (!interval || !usec) + return 0; + + quot = + (usec / + interval) & (il_beacon_time_mask_high(il, + il->hw_params. + beacon_time_tsf_bits) >> il-> + hw_params.beacon_time_tsf_bits); + rem = + (usec % interval) & il_beacon_time_mask_low(il, + il->hw_params. + beacon_time_tsf_bits); + + return (quot << il->hw_params.beacon_time_tsf_bits) + rem; +} +EXPORT_SYMBOL(il_usecs_to_beacons); + +/* base is usually what we get from ucode with each received frame, + * the same as HW timer counter counting down + */ +__le32 +il_add_beacon_time(struct il_priv *il, u32 base, u32 addon, + u32 beacon_interval) +{ + u32 base_low = base & il_beacon_time_mask_low(il, + il->hw_params. + beacon_time_tsf_bits); + u32 addon_low = addon & il_beacon_time_mask_low(il, + il->hw_params. + beacon_time_tsf_bits); + u32 interval = beacon_interval * TIME_UNIT; + u32 res = (base & il_beacon_time_mask_high(il, + il->hw_params. + beacon_time_tsf_bits)) + + (addon & il_beacon_time_mask_high(il, + il->hw_params. + beacon_time_tsf_bits)); + + if (base_low > addon_low) + res += base_low - addon_low; + else if (base_low < addon_low) { + res += interval + base_low - addon_low; + res += (1 << il->hw_params.beacon_time_tsf_bits); + } else + res += (1 << il->hw_params.beacon_time_tsf_bits); + + return cpu_to_le32(res); +} +EXPORT_SYMBOL(il_add_beacon_time); + +#ifdef CONFIG_PM_SLEEP + +static int +il_pci_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct il_priv *il = pci_get_drvdata(pdev); + + /* + * This function is called when system goes into suspend state + * mac80211 will call il_mac_stop() from the mac80211 suspend function + * first but since il_mac_stop() has no knowledge of who the caller is, + * it will not call apm_ops.stop() to stop the DMA operation. + * Calling apm_ops.stop here to make sure we stop the DMA. + */ + il_apm_stop(il); + + return 0; +} + +static int +il_pci_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct il_priv *il = pci_get_drvdata(pdev); + bool hw_rfkill = false; + + /* + * We disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state. + */ + pci_write_config_byte(pdev, PCI_CFG_RETRY_TIMEOUT, 0x00); + + il_enable_interrupts(il); + + if (!(_il_rd(il, CSR_GP_CNTRL) & CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW)) + hw_rfkill = true; + + if (hw_rfkill) + set_bit(S_RFKILL, &il->status); + else + clear_bit(S_RFKILL, &il->status); + + wiphy_rfkill_set_hw_state(il->hw->wiphy, hw_rfkill); + + return 0; +} + +compat_pci_suspend(il_pci_suspend) +compat_pci_resume(il_pci_resume) + +SIMPLE_DEV_PM_OPS(il_pm_ops, il_pci_suspend, il_pci_resume); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) +EXPORT_SYMBOL(il_pm_ops); +#else +EXPORT_SYMBOL(il_pci_suspend_compat); +EXPORT_SYMBOL(il_pci_resume_compat); +#endif + +#endif /* CONFIG_PM_SLEEP */ + +static void +il_update_qos(struct il_priv *il) +{ + if (test_bit(S_EXIT_PENDING, &il->status)) + return; + + il->qos_data.def_qos_parm.qos_flags = 0; + + if (il->qos_data.qos_active) + il->qos_data.def_qos_parm.qos_flags |= + QOS_PARAM_FLG_UPDATE_EDCA_MSK; + + if (il->ht.enabled) + il->qos_data.def_qos_parm.qos_flags |= QOS_PARAM_FLG_TGN_MSK; + + D_QOS("send QoS cmd with Qos active=%d FLAGS=0x%X\n", + il->qos_data.qos_active, il->qos_data.def_qos_parm.qos_flags); + + il_send_cmd_pdu_async(il, C_QOS_PARAM, sizeof(struct il_qosparam_cmd), + &il->qos_data.def_qos_parm, NULL); +} + +/** + * il_mac_config - mac80211 config callback + */ +int +il_mac_config(struct ieee80211_hw *hw, u32 changed) +{ + struct il_priv *il = hw->priv; + const struct il_channel_info *ch_info; + struct ieee80211_conf *conf = &hw->conf; + struct ieee80211_channel *channel = conf->chandef.chan; + struct il_ht_config *ht_conf = &il->current_ht_config; + unsigned long flags = 0; + int ret = 0; + u16 ch; + int scan_active = 0; + bool ht_changed = false; + + mutex_lock(&il->mutex); + D_MAC80211("enter: channel %d changed 0x%X\n", channel->hw_value, + changed); + + if (unlikely(test_bit(S_SCANNING, &il->status))) { + scan_active = 1; + D_MAC80211("scan active\n"); + } + + if (changed & + (IEEE80211_CONF_CHANGE_SMPS | IEEE80211_CONF_CHANGE_CHANNEL)) { + /* mac80211 uses static for non-HT which is what we want */ + il->current_ht_config.smps = conf->smps_mode; + + /* + * Recalculate chain counts. + * + * If monitor mode is enabled then mac80211 will + * set up the SM PS mode to OFF if an HT channel is + * configured. + */ + if (il->ops->set_rxon_chain) + il->ops->set_rxon_chain(il); + } + + /* during scanning mac80211 will delay channel setting until + * scan finish with changed = 0 + */ + if (!changed || (changed & IEEE80211_CONF_CHANGE_CHANNEL)) { + + if (scan_active) + goto set_ch_out; + + ch = channel->hw_value; + ch_info = il_get_channel_info(il, channel->band, ch); + if (!il_is_channel_valid(ch_info)) { + D_MAC80211("leave - invalid channel\n"); + ret = -EINVAL; + goto set_ch_out; + } + + if (il->iw_mode == NL80211_IFTYPE_ADHOC && + !il_is_channel_ibss(ch_info)) { + D_MAC80211("leave - not IBSS channel\n"); + ret = -EINVAL; + goto set_ch_out; + } + + spin_lock_irqsave(&il->lock, flags); + + /* Configure HT40 channels */ + if (il->ht.enabled != conf_is_ht(conf)) { + il->ht.enabled = conf_is_ht(conf); + ht_changed = true; + } + if (il->ht.enabled) { + if (conf_is_ht40_minus(conf)) { + il->ht.extension_chan_offset = + IEEE80211_HT_PARAM_CHA_SEC_BELOW; + il->ht.is_40mhz = true; + } else if (conf_is_ht40_plus(conf)) { + il->ht.extension_chan_offset = + IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + il->ht.is_40mhz = true; + } else { + il->ht.extension_chan_offset = + IEEE80211_HT_PARAM_CHA_SEC_NONE; + il->ht.is_40mhz = false; + } + } else + il->ht.is_40mhz = false; + + /* + * Default to no protection. Protection mode will + * later be set from BSS config in il_ht_conf + */ + il->ht.protection = IEEE80211_HT_OP_MODE_PROTECTION_NONE; + + /* if we are switching from ht to 2.4 clear flags + * from any ht related info since 2.4 does not + * support ht */ + if ((le16_to_cpu(il->staging.channel) != ch)) + il->staging.flags = 0; + + il_set_rxon_channel(il, channel); + il_set_rxon_ht(il, ht_conf); + + il_set_flags_for_band(il, channel->band, il->vif); + + spin_unlock_irqrestore(&il->lock, flags); + + if (il->ops->update_bcast_stations) + ret = il->ops->update_bcast_stations(il); + +set_ch_out: + /* The list of supported rates and rate mask can be different + * for each band; since the band may have changed, reset + * the rate mask to what mac80211 lists */ + il_set_rate(il); + } + + if (changed & (IEEE80211_CONF_CHANGE_PS | IEEE80211_CONF_CHANGE_IDLE)) { + ret = il_power_update_mode(il, false); + if (ret) + D_MAC80211("Error setting sleep level\n"); + } + + if (changed & IEEE80211_CONF_CHANGE_POWER) { + D_MAC80211("TX Power old=%d new=%d\n", il->tx_power_user_lmt, + conf->power_level); + + il_set_tx_power(il, conf->power_level, false); + } + + if (!il_is_ready(il)) { + D_MAC80211("leave - not ready\n"); + goto out; + } + + if (scan_active) + goto out; + + if (memcmp(&il->active, &il->staging, sizeof(il->staging))) + il_commit_rxon(il); + else + D_INFO("Not re-sending same RXON configuration.\n"); + if (ht_changed) + il_update_qos(il); + +out: + D_MAC80211("leave ret %d\n", ret); + mutex_unlock(&il->mutex); + + return ret; +} +EXPORT_SYMBOL(il_mac_config); + +void +il_mac_reset_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct il_priv *il = hw->priv; + unsigned long flags; + + mutex_lock(&il->mutex); + D_MAC80211("enter: type %d, addr %pM\n", vif->type, vif->addr); + + spin_lock_irqsave(&il->lock, flags); + + memset(&il->current_ht_config, 0, sizeof(struct il_ht_config)); + + /* new association get rid of ibss beacon skb */ + if (il->beacon_skb) + dev_kfree_skb(il->beacon_skb); + il->beacon_skb = NULL; + il->timestamp = 0; + + spin_unlock_irqrestore(&il->lock, flags); + + il_scan_cancel_timeout(il, 100); + if (!il_is_ready_rf(il)) { + D_MAC80211("leave - not ready\n"); + mutex_unlock(&il->mutex); + return; + } + + /* we are restarting association process */ + il->staging.filter_flags &= ~RXON_FILTER_ASSOC_MSK; + il_commit_rxon(il); + + il_set_rate(il); + + D_MAC80211("leave\n"); + mutex_unlock(&il->mutex); +} +EXPORT_SYMBOL(il_mac_reset_tsf); + +static void +il_ht_conf(struct il_priv *il, struct ieee80211_vif *vif) +{ + struct il_ht_config *ht_conf = &il->current_ht_config; + struct ieee80211_sta *sta; + struct ieee80211_bss_conf *bss_conf = &vif->bss_conf; + + D_ASSOC("enter:\n"); + + if (!il->ht.enabled) + return; + + il->ht.protection = + bss_conf->ht_operation_mode & IEEE80211_HT_OP_MODE_PROTECTION; + il->ht.non_gf_sta_present = + !!(bss_conf-> + ht_operation_mode & IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT); + + ht_conf->single_chain_sufficient = false; + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + rcu_read_lock(); + sta = ieee80211_find_sta(vif, bss_conf->bssid); + if (sta) { + struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + int maxstreams; + + maxstreams = + (ht_cap->mcs. + tx_params & IEEE80211_HT_MCS_TX_MAX_STREAMS_MASK) + >> IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT; + maxstreams += 1; + + if (ht_cap->mcs.rx_mask[1] == 0 && + ht_cap->mcs.rx_mask[2] == 0) + ht_conf->single_chain_sufficient = true; + if (maxstreams <= 1) + ht_conf->single_chain_sufficient = true; + } else { + /* + * If at all, this can only happen through a race + * when the AP disconnects us while we're still + * setting up the connection, in that case mac80211 + * will soon tell us about that. + */ + ht_conf->single_chain_sufficient = true; + } + rcu_read_unlock(); + break; + case NL80211_IFTYPE_ADHOC: + ht_conf->single_chain_sufficient = true; + break; + default: + break; + } + + D_ASSOC("leave\n"); +} + +static inline void +il_set_no_assoc(struct il_priv *il, struct ieee80211_vif *vif) +{ + /* + * inform the ucode that there is no longer an + * association and that no more packets should be + * sent + */ + il->staging.filter_flags &= ~RXON_FILTER_ASSOC_MSK; + il->staging.assoc_id = 0; + il_commit_rxon(il); +} + +static void +il_beacon_update(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct il_priv *il = hw->priv; + unsigned long flags; + __le64 timestamp; + struct sk_buff *skb = ieee80211_beacon_get(hw, vif); + + if (!skb) + return; + + D_MAC80211("enter\n"); + + lockdep_assert_held(&il->mutex); + + if (!il->beacon_enabled) { + IL_ERR("update beacon with no beaconing enabled\n"); + dev_kfree_skb(skb); + return; + } + + spin_lock_irqsave(&il->lock, flags); + + if (il->beacon_skb) + dev_kfree_skb(il->beacon_skb); + + il->beacon_skb = skb; + + timestamp = ((struct ieee80211_mgmt *)skb->data)->u.beacon.timestamp; + il->timestamp = le64_to_cpu(timestamp); + + D_MAC80211("leave\n"); + spin_unlock_irqrestore(&il->lock, flags); + + if (!il_is_ready_rf(il)) { + D_MAC80211("leave - RF not ready\n"); + return; + } + + il->ops->post_associate(il); +} + +void +il_mac_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, u32 changes) +{ + struct il_priv *il = hw->priv; + int ret; + + mutex_lock(&il->mutex); + D_MAC80211("enter: changes 0x%x\n", changes); + + if (!il_is_alive(il)) { + D_MAC80211("leave - not alive\n"); + mutex_unlock(&il->mutex); + return; + } + + if (changes & BSS_CHANGED_QOS) { + unsigned long flags; + + spin_lock_irqsave(&il->lock, flags); + il->qos_data.qos_active = bss_conf->qos; + il_update_qos(il); + spin_unlock_irqrestore(&il->lock, flags); + } + + if (changes & BSS_CHANGED_BEACON_ENABLED) { + /* FIXME: can we remove beacon_enabled ? */ + if (vif->bss_conf.enable_beacon) + il->beacon_enabled = true; + else + il->beacon_enabled = false; + } + + if (changes & BSS_CHANGED_BSSID) { + D_MAC80211("BSSID %pM\n", bss_conf->bssid); + + /* + * If there is currently a HW scan going on in the background, + * then we need to cancel it, otherwise sometimes we are not + * able to authenticate (FIXME: why ?) + */ + if (il_scan_cancel_timeout(il, 100)) { + D_MAC80211("leave - scan abort failed\n"); + mutex_unlock(&il->mutex); + return; + } + + /* mac80211 only sets assoc when in STATION mode */ + memcpy(il->staging.bssid_addr, bss_conf->bssid, ETH_ALEN); + + /* FIXME: currently needed in a few places */ + memcpy(il->bssid, bss_conf->bssid, ETH_ALEN); + } + + /* + * This needs to be after setting the BSSID in case + * mac80211 decides to do both changes at once because + * it will invoke post_associate. + */ + if (vif->type == NL80211_IFTYPE_ADHOC && (changes & BSS_CHANGED_BEACON)) + il_beacon_update(hw, vif); + + if (changes & BSS_CHANGED_ERP_PREAMBLE) { + D_MAC80211("ERP_PREAMBLE %d\n", bss_conf->use_short_preamble); + if (bss_conf->use_short_preamble) + il->staging.flags |= RXON_FLG_SHORT_PREAMBLE_MSK; + else + il->staging.flags &= ~RXON_FLG_SHORT_PREAMBLE_MSK; + } + + if (changes & BSS_CHANGED_ERP_CTS_PROT) { + D_MAC80211("ERP_CTS %d\n", bss_conf->use_cts_prot); + if (bss_conf->use_cts_prot && il->band != IEEE80211_BAND_5GHZ) + il->staging.flags |= RXON_FLG_TGG_PROTECT_MSK; + else + il->staging.flags &= ~RXON_FLG_TGG_PROTECT_MSK; + if (bss_conf->use_cts_prot) + il->staging.flags |= RXON_FLG_SELF_CTS_EN; + else + il->staging.flags &= ~RXON_FLG_SELF_CTS_EN; + } + + if (changes & BSS_CHANGED_BASIC_RATES) { + /* XXX use this information + * + * To do that, remove code from il_set_rate() and put something + * like this here: + * + if (A-band) + il->staging.ofdm_basic_rates = + bss_conf->basic_rates; + else + il->staging.ofdm_basic_rates = + bss_conf->basic_rates >> 4; + il->staging.cck_basic_rates = + bss_conf->basic_rates & 0xF; + */ + } + + if (changes & BSS_CHANGED_HT) { + il_ht_conf(il, vif); + + if (il->ops->set_rxon_chain) + il->ops->set_rxon_chain(il); + } + + if (changes & BSS_CHANGED_ASSOC) { + D_MAC80211("ASSOC %d\n", bss_conf->assoc); + if (bss_conf->assoc) { + il->timestamp = bss_conf->sync_tsf; + + if (!il_is_rfkill(il)) + il->ops->post_associate(il); + } else + il_set_no_assoc(il, vif); + } + + if (changes && il_is_associated(il) && bss_conf->aid) { + D_MAC80211("Changes (%#x) while associated\n", changes); + ret = il_send_rxon_assoc(il); + if (!ret) { + /* Sync active_rxon with latest change. */ + memcpy((void *)&il->active, &il->staging, + sizeof(struct il_rxon_cmd)); + } + } + + if (changes & BSS_CHANGED_BEACON_ENABLED) { + if (vif->bss_conf.enable_beacon) { + memcpy(il->staging.bssid_addr, bss_conf->bssid, + ETH_ALEN); + memcpy(il->bssid, bss_conf->bssid, ETH_ALEN); + il->ops->config_ap(il); + } else + il_set_no_assoc(il, vif); + } + + if (changes & BSS_CHANGED_IBSS) { + ret = il->ops->manage_ibss_station(il, vif, + bss_conf->ibss_joined); + if (ret) + IL_ERR("failed to %s IBSS station %pM\n", + bss_conf->ibss_joined ? "add" : "remove", + bss_conf->bssid); + } + + D_MAC80211("leave\n"); + mutex_unlock(&il->mutex); +} +EXPORT_SYMBOL(il_mac_bss_info_changed); + +irqreturn_t +il_isr(int irq, void *data) +{ + struct il_priv *il = data; + u32 inta, inta_mask; + u32 inta_fh; + unsigned long flags; + if (!il) + return IRQ_NONE; + + spin_lock_irqsave(&il->lock, flags); + + /* Disable (but don't clear!) interrupts here to avoid + * back-to-back ISRs and sporadic interrupts from our NIC. + * If we have something to service, the tasklet will re-enable ints. + * If we *don't* have something, we'll re-enable before leaving here. */ + inta_mask = _il_rd(il, CSR_INT_MASK); /* just for debug */ + _il_wr(il, CSR_INT_MASK, 0x00000000); + + /* Discover which interrupts are active/pending */ + inta = _il_rd(il, CSR_INT); + inta_fh = _il_rd(il, CSR_FH_INT_STATUS); + + /* Ignore interrupt if there's nothing in NIC to service. + * This may be due to IRQ shared with another device, + * or due to sporadic interrupts thrown from our NIC. */ + if (!inta && !inta_fh) { + D_ISR("Ignore interrupt, inta == 0, inta_fh == 0\n"); + goto none; + } + + if (inta == 0xFFFFFFFF || (inta & 0xFFFFFFF0) == 0xa5a5a5a0) { + /* Hardware disappeared. It might have already raised + * an interrupt */ + IL_WARN("HARDWARE GONE?? INTA == 0x%08x\n", inta); + goto unplugged; + } + + D_ISR("ISR inta 0x%08x, enabled 0x%08x, fh 0x%08x\n", inta, inta_mask, + inta_fh); + + inta &= ~CSR_INT_BIT_SCD; + + /* il_irq_tasklet() will service interrupts and re-enable them */ + if (likely(inta || inta_fh)) + tasklet_schedule(&il->irq_tasklet); + +unplugged: + spin_unlock_irqrestore(&il->lock, flags); + return IRQ_HANDLED; + +none: + /* re-enable interrupts here since we don't have anything to service. */ + /* only Re-enable if disabled by irq */ + if (test_bit(S_INT_ENABLED, &il->status)) + il_enable_interrupts(il); + spin_unlock_irqrestore(&il->lock, flags); + return IRQ_NONE; +} +EXPORT_SYMBOL(il_isr); + +/* + * il_tx_cmd_protection: Set rts/cts. 3945 and 4965 only share this + * function. + */ +void +il_tx_cmd_protection(struct il_priv *il, struct ieee80211_tx_info *info, + __le16 fc, __le32 *tx_flags) +{ + if (info->control.rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS) { + *tx_flags |= TX_CMD_FLG_RTS_MSK; + *tx_flags &= ~TX_CMD_FLG_CTS_MSK; + *tx_flags |= TX_CMD_FLG_FULL_TXOP_PROT_MSK; + + if (!ieee80211_is_mgmt(fc)) + return; + + switch (fc & cpu_to_le16(IEEE80211_FCTL_STYPE)) { + case cpu_to_le16(IEEE80211_STYPE_AUTH): + case cpu_to_le16(IEEE80211_STYPE_DEAUTH): + case cpu_to_le16(IEEE80211_STYPE_ASSOC_REQ): + case cpu_to_le16(IEEE80211_STYPE_REASSOC_REQ): + *tx_flags &= ~TX_CMD_FLG_RTS_MSK; + *tx_flags |= TX_CMD_FLG_CTS_MSK; + break; + } + } else if (info->control.rates[0]. + flags & IEEE80211_TX_RC_USE_CTS_PROTECT) { + *tx_flags &= ~TX_CMD_FLG_RTS_MSK; + *tx_flags |= TX_CMD_FLG_CTS_MSK; + *tx_flags |= TX_CMD_FLG_FULL_TXOP_PROT_MSK; + } +} +EXPORT_SYMBOL(il_tx_cmd_protection); diff --git a/drivers/net/wireless/iwlegacy/common.h b/drivers/net/wireless/iwlegacy/common.h new file mode 100644 index 0000000..3fa2062 --- /dev/null +++ b/drivers/net/wireless/iwlegacy/common.h @@ -0,0 +1,3042 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ +#ifndef __il_core_h__ +#define __il_core_h__ + +#include +#include /* for struct pci_device_id */ +#include +#include +#include +#include +#include +#include + +#include "commands.h" +#include "csr.h" +#include "prph.h" + +struct il_host_cmd; +struct il_cmd; +struct il_tx_queue; + +#define IL_ERR(f, a...) dev_err(&il->pci_dev->dev, f, ## a) +#define IL_WARN(f, a...) dev_warn(&il->pci_dev->dev, f, ## a) +#define IL_INFO(f, a...) dev_info(&il->pci_dev->dev, f, ## a) + +#define RX_QUEUE_SIZE 256 +#define RX_QUEUE_MASK 255 +#define RX_QUEUE_SIZE_LOG 8 + +/* + * RX related structures and functions + */ +#define RX_FREE_BUFFERS 64 +#define RX_LOW_WATERMARK 8 + +#define U32_PAD(n) ((4-(n))&0x3) + +/* CT-KILL constants */ +#define CT_KILL_THRESHOLD_LEGACY 110 /* in Celsius */ + +/* Default noise level to report when noise measurement is not available. + * This may be because we're: + * 1) Not associated (4965, no beacon stats being sent to driver) + * 2) Scanning (noise measurement does not apply to associated channel) + * 3) Receiving CCK (3945 delivers noise info only for OFDM frames) + * Use default noise value of -127 ... this is below the range of measurable + * Rx dBm for either 3945 or 4965, so it can indicate "unmeasurable" to user. + * Also, -127 works better than 0 when averaging frames with/without + * noise info (e.g. averaging might be done in app); measured dBm values are + * always negative ... using a negative value as the default keeps all + * averages within an s8's (used in some apps) range of negative values. */ +#define IL_NOISE_MEAS_NOT_AVAILABLE (-127) + +/* + * RTS threshold here is total size [2347] minus 4 FCS bytes + * Per spec: + * a value of 0 means RTS on all data/management packets + * a value > max MSDU size means no RTS + * else RTS for data/management frames where MPDU is larger + * than RTS value. + */ +#define DEFAULT_RTS_THRESHOLD 2347U +#define MIN_RTS_THRESHOLD 0U +#define MAX_RTS_THRESHOLD 2347U +#define MAX_MSDU_SIZE 2304U +#define MAX_MPDU_SIZE 2346U +#define DEFAULT_BEACON_INTERVAL 100U +#define DEFAULT_SHORT_RETRY_LIMIT 7U +#define DEFAULT_LONG_RETRY_LIMIT 4U + +struct il_rx_buf { + dma_addr_t page_dma; + struct page *page; + struct list_head list; +}; + +#define rxb_addr(r) page_address(r->page) + +/* defined below */ +struct il_device_cmd; + +struct il_cmd_meta { + /* only for SYNC commands, iff the reply skb is wanted */ + struct il_host_cmd *source; + /* + * only for ASYNC commands + * (which is somewhat stupid -- look at common.c for instance + * which duplicates a bunch of code because the callback isn't + * invoked for SYNC commands, if it were and its result passed + * through it would be simpler...) + */ + void (*callback) (struct il_priv *il, struct il_device_cmd *cmd, + struct il_rx_pkt *pkt); + + /* The CMD_SIZE_HUGE flag bit indicates that the command + * structure is stored at the end of the shared queue memory. */ + u32 flags; + + DEFINE_DMA_UNMAP_ADDR(mapping); + DEFINE_DMA_UNMAP_LEN(len); +}; + +/* + * Generic queue structure + * + * Contains common data for Rx and Tx queues + */ +struct il_queue { + int n_bd; /* number of BDs in this queue */ + int write_ptr; /* 1-st empty entry (idx) host_w */ + int read_ptr; /* last used entry (idx) host_r */ + /* use for monitoring and recovering the stuck queue */ + dma_addr_t dma_addr; /* physical addr for BD's */ + int n_win; /* safe queue win */ + u32 id; + int low_mark; /* low watermark, resume queue if free + * space more than this */ + int high_mark; /* high watermark, stop queue if free + * space less than this */ +}; + +/** + * struct il_tx_queue - Tx Queue for DMA + * @q: generic Rx/Tx queue descriptor + * @bd: base of circular buffer of TFDs + * @cmd: array of command/TX buffer pointers + * @meta: array of meta data for each command/tx buffer + * @dma_addr_cmd: physical address of cmd/tx buffer array + * @skbs: array of per-TFD socket buffer pointers + * @time_stamp: time (in jiffies) of last read_ptr change + * @need_update: indicates need to update read/write idx + * @sched_retry: indicates queue is high-throughput aggregation (HT AGG) enabled + * + * A Tx queue consists of circular buffer of BDs (a.k.a. TFDs, transmit frame + * descriptors) and required locking structures. + */ +#define TFD_TX_CMD_SLOTS 256 +#define TFD_CMD_SLOTS 32 + +struct il_tx_queue { + struct il_queue q; + void *tfds; + struct il_device_cmd **cmd; + struct il_cmd_meta *meta; + struct sk_buff **skbs; + unsigned long time_stamp; + u8 need_update; + u8 sched_retry; + u8 active; + u8 swq_id; +}; + +/* + * EEPROM access time values: + * + * Driver initiates EEPROM read by writing byte address << 1 to CSR_EEPROM_REG. + * Driver then polls CSR_EEPROM_REG for CSR_EEPROM_REG_READ_VALID_MSK (0x1). + * When polling, wait 10 uSec between polling loops, up to a maximum 5000 uSec. + * Driver reads 16-bit value from bits 31-16 of CSR_EEPROM_REG. + */ +#define IL_EEPROM_ACCESS_TIMEOUT 5000 /* uSec */ + +#define IL_EEPROM_SEM_TIMEOUT 10 /* microseconds */ +#define IL_EEPROM_SEM_RETRY_LIMIT 1000 /* number of attempts (not time) */ + +/* + * Regulatory channel usage flags in EEPROM struct il4965_eeprom_channel.flags. + * + * IBSS and/or AP operation is allowed *only* on those channels with + * (VALID && IBSS && ACTIVE && !RADAR). This restriction is in place because + * RADAR detection is not supported by the 4965 driver, but is a + * requirement for establishing a new network for legal operation on channels + * requiring RADAR detection or restricting ACTIVE scanning. + * + * NOTE: "WIDE" flag does not indicate anything about "HT40" 40 MHz channels. + * It only indicates that 20 MHz channel use is supported; HT40 channel + * usage is indicated by a separate set of regulatory flags for each + * HT40 channel pair. + * + * NOTE: Using a channel inappropriately will result in a uCode error! + */ +#define IL_NUM_TX_CALIB_GROUPS 5 +enum { + EEPROM_CHANNEL_VALID = (1 << 0), /* usable for this SKU/geo */ + EEPROM_CHANNEL_IBSS = (1 << 1), /* usable as an IBSS channel */ + /* Bit 2 Reserved */ + EEPROM_CHANNEL_ACTIVE = (1 << 3), /* active scanning allowed */ + EEPROM_CHANNEL_RADAR = (1 << 4), /* radar detection required */ + EEPROM_CHANNEL_WIDE = (1 << 5), /* 20 MHz channel okay */ + /* Bit 6 Reserved (was Narrow Channel) */ + EEPROM_CHANNEL_DFS = (1 << 7), /* dynamic freq selection candidate */ +}; + +/* SKU Capabilities */ +/* 3945 only */ +#define EEPROM_SKU_CAP_SW_RF_KILL_ENABLE (1 << 0) +#define EEPROM_SKU_CAP_HW_RF_KILL_ENABLE (1 << 1) + +/* *regulatory* channel data format in eeprom, one for each channel. + * There are separate entries for HT40 (40 MHz) vs. normal (20 MHz) channels. */ +struct il_eeprom_channel { + u8 flags; /* EEPROM_CHANNEL_* flags copied from EEPROM */ + s8 max_power_avg; /* max power (dBm) on this chnl, limit 31 */ +} __packed; + +/* 3945 Specific */ +#define EEPROM_3945_EEPROM_VERSION (0x2f) + +/* 4965 has two radio transmitters (and 3 radio receivers) */ +#define EEPROM_TX_POWER_TX_CHAINS (2) + +/* 4965 has room for up to 8 sets of txpower calibration data */ +#define EEPROM_TX_POWER_BANDS (8) + +/* 4965 factory calibration measures txpower gain settings for + * each of 3 target output levels */ +#define EEPROM_TX_POWER_MEASUREMENTS (3) + +/* 4965 Specific */ +/* 4965 driver does not work with txpower calibration version < 5 */ +#define EEPROM_4965_TX_POWER_VERSION (5) +#define EEPROM_4965_EEPROM_VERSION (0x2f) +#define EEPROM_4965_CALIB_VERSION_OFFSET (2*0xB6) /* 2 bytes */ +#define EEPROM_4965_CALIB_TXPOWER_OFFSET (2*0xE8) /* 48 bytes */ +#define EEPROM_4965_BOARD_REVISION (2*0x4F) /* 2 bytes */ +#define EEPROM_4965_BOARD_PBA (2*0x56+1) /* 9 bytes */ + +/* 2.4 GHz */ +extern const u8 il_eeprom_band_1[14]; + +/* + * factory calibration data for one txpower level, on one channel, + * measured on one of the 2 tx chains (radio transmitter and associated + * antenna). EEPROM contains: + * + * 1) Temperature (degrees Celsius) of device when measurement was made. + * + * 2) Gain table idx used to achieve the target measurement power. + * This refers to the "well-known" gain tables (see 4965.h). + * + * 3) Actual measured output power, in half-dBm ("34" = 17 dBm). + * + * 4) RF power amplifier detector level measurement (not used). + */ +struct il_eeprom_calib_measure { + u8 temperature; /* Device temperature (Celsius) */ + u8 gain_idx; /* Index into gain table */ + u8 actual_pow; /* Measured RF output power, half-dBm */ + s8 pa_det; /* Power amp detector level (not used) */ +} __packed; + +/* + * measurement set for one channel. EEPROM contains: + * + * 1) Channel number measured + * + * 2) Measurements for each of 3 power levels for each of 2 radio transmitters + * (a.k.a. "tx chains") (6 measurements altogether) + */ +struct il_eeprom_calib_ch_info { + u8 ch_num; + struct il_eeprom_calib_measure + measurements[EEPROM_TX_POWER_TX_CHAINS] + [EEPROM_TX_POWER_MEASUREMENTS]; +} __packed; + +/* + * txpower subband info. + * + * For each frequency subband, EEPROM contains the following: + * + * 1) First and last channels within range of the subband. "0" values + * indicate that this sample set is not being used. + * + * 2) Sample measurement sets for 2 channels close to the range endpoints. + */ +struct il_eeprom_calib_subband_info { + u8 ch_from; /* channel number of lowest channel in subband */ + u8 ch_to; /* channel number of highest channel in subband */ + struct il_eeprom_calib_ch_info ch1; + struct il_eeprom_calib_ch_info ch2; +} __packed; + +/* + * txpower calibration info. EEPROM contains: + * + * 1) Factory-measured saturation power levels (maximum levels at which + * tx power amplifier can output a signal without too much distortion). + * There is one level for 2.4 GHz band and one for 5 GHz band. These + * values apply to all channels within each of the bands. + * + * 2) Factory-measured power supply voltage level. This is assumed to be + * constant (i.e. same value applies to all channels/bands) while the + * factory measurements are being made. + * + * 3) Up to 8 sets of factory-measured txpower calibration values. + * These are for different frequency ranges, since txpower gain + * characteristics of the analog radio circuitry vary with frequency. + * + * Not all sets need to be filled with data; + * struct il_eeprom_calib_subband_info contains range of channels + * (0 if unused) for each set of data. + */ +struct il_eeprom_calib_info { + u8 saturation_power24; /* half-dBm (e.g. "34" = 17 dBm) */ + u8 saturation_power52; /* half-dBm */ + __le16 voltage; /* signed */ + struct il_eeprom_calib_subband_info band_info[EEPROM_TX_POWER_BANDS]; +} __packed; + +/* General */ +#define EEPROM_DEVICE_ID (2*0x08) /* 2 bytes */ +#define EEPROM_MAC_ADDRESS (2*0x15) /* 6 bytes */ +#define EEPROM_BOARD_REVISION (2*0x35) /* 2 bytes */ +#define EEPROM_BOARD_PBA_NUMBER (2*0x3B+1) /* 9 bytes */ +#define EEPROM_VERSION (2*0x44) /* 2 bytes */ +#define EEPROM_SKU_CAP (2*0x45) /* 2 bytes */ +#define EEPROM_OEM_MODE (2*0x46) /* 2 bytes */ +#define EEPROM_WOWLAN_MODE (2*0x47) /* 2 bytes */ +#define EEPROM_RADIO_CONFIG (2*0x48) /* 2 bytes */ +#define EEPROM_NUM_MAC_ADDRESS (2*0x4C) /* 2 bytes */ + +/* The following masks are to be applied on EEPROM_RADIO_CONFIG */ +#define EEPROM_RF_CFG_TYPE_MSK(x) (x & 0x3) /* bits 0-1 */ +#define EEPROM_RF_CFG_STEP_MSK(x) ((x >> 2) & 0x3) /* bits 2-3 */ +#define EEPROM_RF_CFG_DASH_MSK(x) ((x >> 4) & 0x3) /* bits 4-5 */ +#define EEPROM_RF_CFG_PNUM_MSK(x) ((x >> 6) & 0x3) /* bits 6-7 */ +#define EEPROM_RF_CFG_TX_ANT_MSK(x) ((x >> 8) & 0xF) /* bits 8-11 */ +#define EEPROM_RF_CFG_RX_ANT_MSK(x) ((x >> 12) & 0xF) /* bits 12-15 */ + +#define EEPROM_3945_RF_CFG_TYPE_MAX 0x0 +#define EEPROM_4965_RF_CFG_TYPE_MAX 0x1 + +/* + * Per-channel regulatory data. + * + * Each channel that *might* be supported by iwl has a fixed location + * in EEPROM containing EEPROM_CHANNEL_* usage flags (LSB) and max regulatory + * txpower (MSB). + * + * Entries immediately below are for 20 MHz channel width. HT40 (40 MHz) + * channels (only for 4965, not supported by 3945) appear later in the EEPROM. + * + * 2.4 GHz channels 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 + */ +#define EEPROM_REGULATORY_SKU_ID (2*0x60) /* 4 bytes */ +#define EEPROM_REGULATORY_BAND_1 (2*0x62) /* 2 bytes */ +#define EEPROM_REGULATORY_BAND_1_CHANNELS (2*0x63) /* 28 bytes */ + +/* + * 4.9 GHz channels 183, 184, 185, 187, 188, 189, 192, 196, + * 5.0 GHz channels 7, 8, 11, 12, 16 + * (4915-5080MHz) (none of these is ever supported) + */ +#define EEPROM_REGULATORY_BAND_2 (2*0x71) /* 2 bytes */ +#define EEPROM_REGULATORY_BAND_2_CHANNELS (2*0x72) /* 26 bytes */ + +/* + * 5.2 GHz channels 34, 36, 38, 40, 42, 44, 46, 48, 52, 56, 60, 64 + * (5170-5320MHz) + */ +#define EEPROM_REGULATORY_BAND_3 (2*0x7F) /* 2 bytes */ +#define EEPROM_REGULATORY_BAND_3_CHANNELS (2*0x80) /* 24 bytes */ + +/* + * 5.5 GHz channels 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140 + * (5500-5700MHz) + */ +#define EEPROM_REGULATORY_BAND_4 (2*0x8C) /* 2 bytes */ +#define EEPROM_REGULATORY_BAND_4_CHANNELS (2*0x8D) /* 22 bytes */ + +/* + * 5.7 GHz channels 145, 149, 153, 157, 161, 165 + * (5725-5825MHz) + */ +#define EEPROM_REGULATORY_BAND_5 (2*0x98) /* 2 bytes */ +#define EEPROM_REGULATORY_BAND_5_CHANNELS (2*0x99) /* 12 bytes */ + +/* + * 2.4 GHz HT40 channels 1 (5), 2 (6), 3 (7), 4 (8), 5 (9), 6 (10), 7 (11) + * + * The channel listed is the center of the lower 20 MHz half of the channel. + * The overall center frequency is actually 2 channels (10 MHz) above that, + * and the upper half of each HT40 channel is centered 4 channels (20 MHz) away + * from the lower half; e.g. the upper half of HT40 channel 1 is channel 5, + * and the overall HT40 channel width centers on channel 3. + * + * NOTE: The RXON command uses 20 MHz channel numbers to specify the + * control channel to which to tune. RXON also specifies whether the + * control channel is the upper or lower half of a HT40 channel. + * + * NOTE: 4965 does not support HT40 channels on 2.4 GHz. + */ +#define EEPROM_4965_REGULATORY_BAND_24_HT40_CHANNELS (2*0xA0) /* 14 bytes */ + +/* + * 5.2 GHz HT40 channels 36 (40), 44 (48), 52 (56), 60 (64), + * 100 (104), 108 (112), 116 (120), 124 (128), 132 (136), 149 (153), 157 (161) + */ +#define EEPROM_4965_REGULATORY_BAND_52_HT40_CHANNELS (2*0xA8) /* 22 bytes */ + +#define EEPROM_REGULATORY_BAND_NO_HT40 (0) + +int il_eeprom_init(struct il_priv *il); +void il_eeprom_free(struct il_priv *il); +const u8 *il_eeprom_query_addr(const struct il_priv *il, size_t offset); +u16 il_eeprom_query16(const struct il_priv *il, size_t offset); +int il_init_channel_map(struct il_priv *il); +void il_free_channel_map(struct il_priv *il); +const struct il_channel_info *il_get_channel_info(const struct il_priv *il, + enum ieee80211_band band, + u16 channel); + +#define IL_NUM_SCAN_RATES (2) + +struct il4965_channel_tgd_info { + u8 type; + s8 max_power; +}; + +struct il4965_channel_tgh_info { + s64 last_radar_time; +}; + +#define IL4965_MAX_RATE (33) + +struct il3945_clip_group { + /* maximum power level to prevent clipping for each rate, derived by + * us from this band's saturation power in EEPROM */ + const s8 clip_powers[IL_MAX_RATES]; +}; + +/* current Tx power values to use, one for each rate for each channel. + * requested power is limited by: + * -- regulatory EEPROM limits for this channel + * -- hardware capabilities (clip-powers) + * -- spectrum management + * -- user preference (e.g. iwconfig) + * when requested power is set, base power idx must also be set. */ +struct il3945_channel_power_info { + struct il3945_tx_power tpc; /* actual radio and DSP gain settings */ + s8 power_table_idx; /* actual (compenst'd) idx into gain table */ + s8 base_power_idx; /* gain idx for power at factory temp. */ + s8 requested_power; /* power (dBm) requested for this chnl/rate */ +}; + +/* current scan Tx power values to use, one for each scan rate for each + * channel. */ +struct il3945_scan_power_info { + struct il3945_tx_power tpc; /* actual radio and DSP gain settings */ + s8 power_table_idx; /* actual (compenst'd) idx into gain table */ + s8 requested_power; /* scan pwr (dBm) requested for chnl/rate */ +}; + +/* + * One for each channel, holds all channel setup data + * Some of the fields (e.g. eeprom and flags/max_power_avg) are redundant + * with one another! + */ +struct il_channel_info { + struct il4965_channel_tgd_info tgd; + struct il4965_channel_tgh_info tgh; + struct il_eeprom_channel eeprom; /* EEPROM regulatory limit */ + struct il_eeprom_channel ht40_eeprom; /* EEPROM regulatory limit for + * HT40 channel */ + + u8 channel; /* channel number */ + u8 flags; /* flags copied from EEPROM */ + s8 max_power_avg; /* (dBm) regul. eeprom, normal Tx, any rate */ + s8 curr_txpow; /* (dBm) regulatory/spectrum/user (not h/w) limit */ + s8 min_power; /* always 0 */ + s8 scan_power; /* (dBm) regul. eeprom, direct scans, any rate */ + + u8 group_idx; /* 0-4, maps channel to group1/2/3/4/5 */ + u8 band_idx; /* 0-4, maps channel to band1/2/3/4/5 */ + enum ieee80211_band band; + + /* HT40 channel info */ + s8 ht40_max_power_avg; /* (dBm) regul. eeprom, normal Tx, any rate */ + u8 ht40_flags; /* flags copied from EEPROM */ + u8 ht40_extension_channel; /* HT_IE_EXT_CHANNEL_* */ + + /* Radio/DSP gain settings for each "normal" data Tx rate. + * These include, in addition to RF and DSP gain, a few fields for + * remembering/modifying gain settings (idxes). */ + struct il3945_channel_power_info power_info[IL4965_MAX_RATE]; + + /* Radio/DSP gain settings for each scan rate, for directed scans. */ + struct il3945_scan_power_info scan_pwr_info[IL_NUM_SCAN_RATES]; +}; + +#define IL_TX_FIFO_BK 0 /* shared */ +#define IL_TX_FIFO_BE 1 +#define IL_TX_FIFO_VI 2 /* shared */ +#define IL_TX_FIFO_VO 3 +#define IL_TX_FIFO_UNUSED -1 + +/* Minimum number of queues. MAX_NUM is defined in hw specific files. + * Set the minimum to accommodate the 4 standard TX queues, 1 command + * queue, 2 (unused) HCCA queues, and 4 HT queues (one for each AC) */ +#define IL_MIN_NUM_QUEUES 10 + +#define IL_DEFAULT_CMD_QUEUE_NUM 4 + +#define IEEE80211_DATA_LEN 2304 +#define IEEE80211_4ADDR_LEN 30 +#define IEEE80211_HLEN (IEEE80211_4ADDR_LEN) +#define IEEE80211_FRAME_LEN (IEEE80211_DATA_LEN + IEEE80211_HLEN) + +struct il_frame { + union { + struct ieee80211_hdr frame; + struct il_tx_beacon_cmd beacon; + u8 raw[IEEE80211_FRAME_LEN]; + u8 cmd[360]; + } u; + struct list_head list; +}; + +enum { + CMD_SYNC = 0, + CMD_SIZE_NORMAL = 0, + CMD_NO_SKB = 0, + CMD_SIZE_HUGE = (1 << 0), + CMD_ASYNC = (1 << 1), + CMD_WANT_SKB = (1 << 2), + CMD_MAPPED = (1 << 3), +}; + +#define DEF_CMD_PAYLOAD_SIZE 320 + +/** + * struct il_device_cmd + * + * For allocation of the command and tx queues, this establishes the overall + * size of the largest command we send to uCode, except for a scan command + * (which is relatively huge; space is allocated separately). + */ +struct il_device_cmd { + struct il_cmd_header hdr; /* uCode API */ + union { + u32 flags; + u8 val8; + u16 val16; + u32 val32; + struct il_tx_cmd tx; + u8 payload[DEF_CMD_PAYLOAD_SIZE]; + } __packed cmd; +} __packed; + +#define TFD_MAX_PAYLOAD_SIZE (sizeof(struct il_device_cmd)) + +struct il_host_cmd { + const void *data; + unsigned long reply_page; + void (*callback) (struct il_priv *il, struct il_device_cmd *cmd, + struct il_rx_pkt *pkt); + u32 flags; + u16 len; + u8 id; +}; + +#define SUP_RATE_11A_MAX_NUM_CHANNELS 8 +#define SUP_RATE_11B_MAX_NUM_CHANNELS 4 +#define SUP_RATE_11G_MAX_NUM_CHANNELS 12 + +/** + * struct il_rx_queue - Rx queue + * @bd: driver's pointer to buffer of receive buffer descriptors (rbd) + * @bd_dma: bus address of buffer of receive buffer descriptors (rbd) + * @read: Shared idx to newest available Rx buffer + * @write: Shared idx to oldest written Rx packet + * @free_count: Number of pre-allocated buffers in rx_free + * @rx_free: list of free SKBs for use + * @rx_used: List of Rx buffers with no SKB + * @need_update: flag to indicate we need to update read/write idx + * @rb_stts: driver's pointer to receive buffer status + * @rb_stts_dma: bus address of receive buffer status + * + * NOTE: rx_free and rx_used are used as a FIFO for il_rx_bufs + */ +struct il_rx_queue { + __le32 *bd; + dma_addr_t bd_dma; + struct il_rx_buf pool[RX_QUEUE_SIZE + RX_FREE_BUFFERS]; + struct il_rx_buf *queue[RX_QUEUE_SIZE]; + u32 read; + u32 write; + u32 free_count; + u32 write_actual; + struct list_head rx_free; + struct list_head rx_used; + int need_update; + struct il_rb_status *rb_stts; + dma_addr_t rb_stts_dma; + spinlock_t lock; +}; + +#define IL_SUPPORTED_RATES_IE_LEN 8 + +#define MAX_TID_COUNT 9 + +#define IL_INVALID_RATE 0xFF +#define IL_INVALID_VALUE -1 + +/** + * struct il_ht_agg -- aggregation status while waiting for block-ack + * @txq_id: Tx queue used for Tx attempt + * @frame_count: # frames attempted by Tx command + * @wait_for_ba: Expect block-ack before next Tx reply + * @start_idx: Index of 1st Transmit Frame Descriptor (TFD) in Tx win + * @bitmap0: Low order bitmap, one bit for each frame pending ACK in Tx win + * @bitmap1: High order, one bit for each frame pending ACK in Tx win + * @rate_n_flags: Rate at which Tx was attempted + * + * If C_TX indicates that aggregation was attempted, driver must wait + * for block ack (N_COMPRESSED_BA). This struct stores tx reply info + * until block ack arrives. + */ +struct il_ht_agg { + u16 txq_id; + u16 frame_count; + u16 wait_for_ba; + u16 start_idx; + u64 bitmap; + u32 rate_n_flags; +#define IL_AGG_OFF 0 +#define IL_AGG_ON 1 +#define IL_EMPTYING_HW_QUEUE_ADDBA 2 +#define IL_EMPTYING_HW_QUEUE_DELBA 3 + u8 state; +}; + +struct il_tid_data { + u16 seq_number; /* 4965 only */ + u16 tfds_in_queue; + struct il_ht_agg agg; +}; + +struct il_hw_key { + u32 cipher; + int keylen; + u8 keyidx; + u8 key[32]; +}; + +union il_ht_rate_supp { + u16 rates; + struct { + u8 siso_rate; + u8 mimo_rate; + }; +}; + +#define CFG_HT_RX_AMPDU_FACTOR_8K (0x0) +#define CFG_HT_RX_AMPDU_FACTOR_16K (0x1) +#define CFG_HT_RX_AMPDU_FACTOR_32K (0x2) +#define CFG_HT_RX_AMPDU_FACTOR_64K (0x3) +#define CFG_HT_RX_AMPDU_FACTOR_DEF CFG_HT_RX_AMPDU_FACTOR_64K +#define CFG_HT_RX_AMPDU_FACTOR_MAX CFG_HT_RX_AMPDU_FACTOR_64K +#define CFG_HT_RX_AMPDU_FACTOR_MIN CFG_HT_RX_AMPDU_FACTOR_8K + +/* + * Maximal MPDU density for TX aggregation + * 4 - 2us density + * 5 - 4us density + * 6 - 8us density + * 7 - 16us density + */ +#define CFG_HT_MPDU_DENSITY_2USEC (0x4) +#define CFG_HT_MPDU_DENSITY_4USEC (0x5) +#define CFG_HT_MPDU_DENSITY_8USEC (0x6) +#define CFG_HT_MPDU_DENSITY_16USEC (0x7) +#define CFG_HT_MPDU_DENSITY_DEF CFG_HT_MPDU_DENSITY_4USEC +#define CFG_HT_MPDU_DENSITY_MAX CFG_HT_MPDU_DENSITY_16USEC +#define CFG_HT_MPDU_DENSITY_MIN (0x1) + +struct il_ht_config { + bool single_chain_sufficient; + enum ieee80211_smps_mode smps; /* current smps mode */ +}; + +/* QoS structures */ +struct il_qos_info { + int qos_active; + struct il_qosparam_cmd def_qos_parm; +}; + +/* + * Structure should be accessed with sta_lock held. When station addition + * is in progress (IL_STA_UCODE_INPROGRESS) it is possible to access only + * the commands (il_addsta_cmd and il_link_quality_cmd) without + * sta_lock held. + */ +struct il_station_entry { + struct il_addsta_cmd sta; + struct il_tid_data tid[MAX_TID_COUNT]; + u8 used; + struct il_hw_key keyinfo; + struct il_link_quality_cmd *lq; +}; + +struct il_station_priv_common { + u8 sta_id; +}; + +/** + * struct il_vif_priv - driver's ilate per-interface information + * + * When mac80211 allocates a virtual interface, it can allocate + * space for us to put data into. + */ +struct il_vif_priv { + u8 ibss_bssid_sta_id; +}; + +/* one for each uCode image (inst/data, boot/init/runtime) */ +struct fw_desc { + void *v_addr; /* access by driver */ + dma_addr_t p_addr; /* access by card's busmaster DMA */ + u32 len; /* bytes */ +}; + +/* uCode file layout */ +struct il_ucode_header { + __le32 ver; /* major/minor/API/serial */ + struct { + __le32 inst_size; /* bytes of runtime code */ + __le32 data_size; /* bytes of runtime data */ + __le32 init_size; /* bytes of init code */ + __le32 init_data_size; /* bytes of init data */ + __le32 boot_size; /* bytes of bootstrap code */ + u8 data[0]; /* in same order as sizes */ + } v1; +}; + +struct il4965_ibss_seq { + u8 mac[ETH_ALEN]; + u16 seq_num; + u16 frag_num; + unsigned long packet_time; + struct list_head list; +}; + +struct il_sensitivity_ranges { + u16 min_nrg_cck; + u16 max_nrg_cck; + + u16 nrg_th_cck; + u16 nrg_th_ofdm; + + u16 auto_corr_min_ofdm; + u16 auto_corr_min_ofdm_mrc; + u16 auto_corr_min_ofdm_x1; + u16 auto_corr_min_ofdm_mrc_x1; + + u16 auto_corr_max_ofdm; + u16 auto_corr_max_ofdm_mrc; + u16 auto_corr_max_ofdm_x1; + u16 auto_corr_max_ofdm_mrc_x1; + + u16 auto_corr_max_cck; + u16 auto_corr_max_cck_mrc; + u16 auto_corr_min_cck; + u16 auto_corr_min_cck_mrc; + + u16 barker_corr_th_min; + u16 barker_corr_th_min_mrc; + u16 nrg_th_cca; +}; + +#define KELVIN_TO_CELSIUS(x) ((x)-273) +#define CELSIUS_TO_KELVIN(x) ((x)+273) + +/** + * struct il_hw_params + * @bcast_id: f/w broadcast station ID + * @max_txq_num: Max # Tx queues supported + * @dma_chnl_num: Number of Tx DMA/FIFO channels + * @scd_bc_tbls_size: size of scheduler byte count tables + * @tfd_size: TFD size + * @tx/rx_chains_num: Number of TX/RX chains + * @valid_tx/rx_ant: usable antennas + * @max_rxq_size: Max # Rx frames in Rx queue (must be power-of-2) + * @max_rxq_log: Log-base-2 of max_rxq_size + * @rx_page_order: Rx buffer page order + * @rx_wrt_ptr_reg: FH{39}_RSCSR_CHNL0_WPTR + * @max_stations: + * @ht40_channel: is 40MHz width possible in band 2.4 + * BIT(IEEE80211_BAND_5GHZ) BIT(IEEE80211_BAND_5GHZ) + * @sw_crypto: 0 for hw, 1 for sw + * @max_xxx_size: for ucode uses + * @ct_kill_threshold: temperature threshold + * @beacon_time_tsf_bits: number of valid tsf bits for beacon time + * @struct il_sensitivity_ranges: range of sensitivity values + */ +struct il_hw_params { + u8 bcast_id; + u8 max_txq_num; + u8 dma_chnl_num; + u16 scd_bc_tbls_size; + u32 tfd_size; + u8 tx_chains_num; + u8 rx_chains_num; + u8 valid_tx_ant; + u8 valid_rx_ant; + u16 max_rxq_size; + u16 max_rxq_log; + u32 rx_page_order; + u32 rx_wrt_ptr_reg; + u8 max_stations; + u8 ht40_channel; + u8 max_beacon_itrvl; /* in 1024 ms */ + u32 max_inst_size; + u32 max_data_size; + u32 max_bsm_size; + u32 ct_kill_threshold; /* value in hw-dependent units */ + u16 beacon_time_tsf_bits; + const struct il_sensitivity_ranges *sens; +}; + +/****************************************************************************** + * + * Functions implemented in core module which are forward declared here + * for use by iwl-[4-5].c + * + * NOTE: The implementation of these functions are not hardware specific + * which is why they are in the core module files. + * + * Naming convention -- + * il_ <-- Is part of iwlwifi + * iwlXXXX_ <-- Hardware specific (implemented in iwl-XXXX.c for XXXX) + * il4965_bg_ <-- Called from work queue context + * il4965_mac_ <-- mac80211 callback + * + ****************************************************************************/ +extern void il4965_update_chain_flags(struct il_priv *il); +extern const u8 il_bcast_addr[ETH_ALEN]; +extern int il_queue_space(const struct il_queue *q); +static inline int +il_queue_used(const struct il_queue *q, int i) +{ + return q->write_ptr >= q->read_ptr ? (i >= q->read_ptr && + i < q->write_ptr) : !(i < + q->read_ptr + && i >= + q-> + write_ptr); +} + +static inline u8 +il_get_cmd_idx(struct il_queue *q, u32 idx, int is_huge) +{ + /* + * This is for init calibration result and scan command which + * required buffer > TFD_MAX_PAYLOAD_SIZE, + * the big buffer at end of command array + */ + if (is_huge) + return q->n_win; /* must be power of 2 */ + + /* Otherwise, use normal size buffers */ + return idx & (q->n_win - 1); +} + +struct il_dma_ptr { + dma_addr_t dma; + void *addr; + size_t size; +}; + +#define IL_OPERATION_MODE_AUTO 0 +#define IL_OPERATION_MODE_HT_ONLY 1 +#define IL_OPERATION_MODE_MIXED 2 +#define IL_OPERATION_MODE_20MHZ 3 + +#define IL_TX_CRC_SIZE 4 +#define IL_TX_DELIMITER_SIZE 4 + +#define TX_POWER_IL_ILLEGAL_VOLTAGE -10000 + +/* Sensitivity and chain noise calibration */ +#define INITIALIZATION_VALUE 0xFFFF +#define IL4965_CAL_NUM_BEACONS 20 +#define IL_CAL_NUM_BEACONS 16 +#define MAXIMUM_ALLOWED_PATHLOSS 15 + +#define CHAIN_NOISE_MAX_DELTA_GAIN_CODE 3 + +#define MAX_FA_OFDM 50 +#define MIN_FA_OFDM 5 +#define MAX_FA_CCK 50 +#define MIN_FA_CCK 5 + +#define AUTO_CORR_STEP_OFDM 1 + +#define AUTO_CORR_STEP_CCK 3 +#define AUTO_CORR_MAX_TH_CCK 160 + +#define NRG_DIFF 2 +#define NRG_STEP_CCK 2 +#define NRG_MARGIN 8 +#define MAX_NUMBER_CCK_NO_FA 100 + +#define AUTO_CORR_CCK_MIN_VAL_DEF (125) + +#define CHAIN_A 0 +#define CHAIN_B 1 +#define CHAIN_C 2 +#define CHAIN_NOISE_DELTA_GAIN_INIT_VAL 4 +#define ALL_BAND_FILTER 0xFF00 +#define IN_BAND_FILTER 0xFF +#define MIN_AVERAGE_NOISE_MAX_VALUE 0xFFFFFFFF + +#define NRG_NUM_PREV_STAT_L 20 +#define NUM_RX_CHAINS 3 + +enum il4965_false_alarm_state { + IL_FA_TOO_MANY = 0, + IL_FA_TOO_FEW = 1, + IL_FA_GOOD_RANGE = 2, +}; + +enum il4965_chain_noise_state { + IL_CHAIN_NOISE_ALIVE = 0, /* must be 0 */ + IL_CHAIN_NOISE_ACCUMULATE, + IL_CHAIN_NOISE_CALIBRATED, + IL_CHAIN_NOISE_DONE, +}; + +enum ucode_type { + UCODE_NONE = 0, + UCODE_INIT, + UCODE_RT +}; + +/* Sensitivity calib data */ +struct il_sensitivity_data { + u32 auto_corr_ofdm; + u32 auto_corr_ofdm_mrc; + u32 auto_corr_ofdm_x1; + u32 auto_corr_ofdm_mrc_x1; + u32 auto_corr_cck; + u32 auto_corr_cck_mrc; + + u32 last_bad_plcp_cnt_ofdm; + u32 last_fa_cnt_ofdm; + u32 last_bad_plcp_cnt_cck; + u32 last_fa_cnt_cck; + + u32 nrg_curr_state; + u32 nrg_prev_state; + u32 nrg_value[10]; + u8 nrg_silence_rssi[NRG_NUM_PREV_STAT_L]; + u32 nrg_silence_ref; + u32 nrg_energy_idx; + u32 nrg_silence_idx; + u32 nrg_th_cck; + s32 nrg_auto_corr_silence_diff; + u32 num_in_cck_no_fa; + u32 nrg_th_ofdm; + + u16 barker_corr_th_min; + u16 barker_corr_th_min_mrc; + u16 nrg_th_cca; +}; + +/* Chain noise (differential Rx gain) calib data */ +struct il_chain_noise_data { + u32 active_chains; + u32 chain_noise_a; + u32 chain_noise_b; + u32 chain_noise_c; + u32 chain_signal_a; + u32 chain_signal_b; + u32 chain_signal_c; + u16 beacon_count; + u8 disconn_array[NUM_RX_CHAINS]; + u8 delta_gain_code[NUM_RX_CHAINS]; + u8 radio_write; + u8 state; +}; + +#define EEPROM_SEM_TIMEOUT 10 /* milliseconds */ +#define EEPROM_SEM_RETRY_LIMIT 1000 /* number of attempts (not time) */ + +#define IL_TRAFFIC_ENTRIES (256) +#define IL_TRAFFIC_ENTRY_SIZE (64) + +enum { + MEASUREMENT_READY = (1 << 0), + MEASUREMENT_ACTIVE = (1 << 1), +}; + +/* interrupt stats */ +struct isr_stats { + u32 hw; + u32 sw; + u32 err_code; + u32 sch; + u32 alive; + u32 rfkill; + u32 ctkill; + u32 wakeup; + u32 rx; + u32 handlers[IL_CN_MAX]; + u32 tx; + u32 unhandled; +}; + +/* management stats */ +enum il_mgmt_stats { + MANAGEMENT_ASSOC_REQ = 0, + MANAGEMENT_ASSOC_RESP, + MANAGEMENT_REASSOC_REQ, + MANAGEMENT_REASSOC_RESP, + MANAGEMENT_PROBE_REQ, + MANAGEMENT_PROBE_RESP, + MANAGEMENT_BEACON, + MANAGEMENT_ATIM, + MANAGEMENT_DISASSOC, + MANAGEMENT_AUTH, + MANAGEMENT_DEAUTH, + MANAGEMENT_ACTION, + MANAGEMENT_MAX, +}; +/* control stats */ +enum il_ctrl_stats { + CONTROL_BACK_REQ = 0, + CONTROL_BACK, + CONTROL_PSPOLL, + CONTROL_RTS, + CONTROL_CTS, + CONTROL_ACK, + CONTROL_CFEND, + CONTROL_CFENDACK, + CONTROL_MAX, +}; + +struct traffic_stats { +#ifdef CPTCFG_IWLEGACY_DEBUGFS + u32 mgmt[MANAGEMENT_MAX]; + u32 ctrl[CONTROL_MAX]; + u32 data_cnt; + u64 data_bytes; +#endif +}; + +/* + * host interrupt timeout value + * used with setting interrupt coalescing timer + * the CSR_INT_COALESCING is an 8 bit register in 32-usec unit + * + * default interrupt coalescing timer is 64 x 32 = 2048 usecs + * default interrupt coalescing calibration timer is 16 x 32 = 512 usecs + */ +#define IL_HOST_INT_TIMEOUT_MAX (0xFF) +#define IL_HOST_INT_TIMEOUT_DEF (0x40) +#define IL_HOST_INT_TIMEOUT_MIN (0x0) +#define IL_HOST_INT_CALIB_TIMEOUT_MAX (0xFF) +#define IL_HOST_INT_CALIB_TIMEOUT_DEF (0x10) +#define IL_HOST_INT_CALIB_TIMEOUT_MIN (0x0) + +#define IL_DELAY_NEXT_FORCE_FW_RELOAD (HZ*5) + +/* TX queue watchdog timeouts in mSecs */ +#define IL_DEF_WD_TIMEOUT (2000) +#define IL_LONG_WD_TIMEOUT (10000) +#define IL_MAX_WD_TIMEOUT (120000) + +struct il_force_reset { + int reset_request_count; + int reset_success_count; + int reset_reject_count; + unsigned long reset_duration; + unsigned long last_force_reset_jiffies; +}; + +/* extend beacon time format bit shifting */ +/* + * for _3945 devices + * bits 31:24 - extended + * bits 23:0 - interval + */ +#define IL3945_EXT_BEACON_TIME_POS 24 +/* + * for _4965 devices + * bits 31:22 - extended + * bits 21:0 - interval + */ +#define IL4965_EXT_BEACON_TIME_POS 22 + +struct il_rxon_context { + struct ieee80211_vif *vif; +}; + +struct il_power_mgr { + struct il_powertable_cmd sleep_cmd; + struct il_powertable_cmd sleep_cmd_next; + int debug_sleep_level_override; + bool pci_pm; +}; + +struct il_priv { + struct ieee80211_hw *hw; + struct ieee80211_channel *ieee_channels; + struct ieee80211_rate *ieee_rates; + + struct il_cfg *cfg; + const struct il_ops *ops; +#ifdef CPTCFG_IWLEGACY_DEBUGFS + const struct il_debugfs_ops *debugfs_ops; +#endif + + /* temporary frame storage list */ + struct list_head free_frames; + int frames_count; + + enum ieee80211_band band; + int alloc_rxb_page; + + void (*handlers[IL_CN_MAX]) (struct il_priv *il, + struct il_rx_buf *rxb); + + struct ieee80211_supported_band bands[IEEE80211_NUM_BANDS]; + + /* spectrum measurement report caching */ + struct il_spectrum_notification measure_report; + u8 measurement_status; + + /* ucode beacon time */ + u32 ucode_beacon_time; + int missed_beacon_threshold; + + /* track IBSS manager (last beacon) status */ + u32 ibss_manager; + + /* force reset */ + struct il_force_reset force_reset; + + /* we allocate array of il_channel_info for NIC's valid channels. + * Access via channel # using indirect idx array */ + struct il_channel_info *channel_info; /* channel info array */ + u8 channel_count; /* # of channels */ + + /* thermal calibration */ + s32 temperature; /* degrees Kelvin */ + s32 last_temperature; + + /* Scan related variables */ + unsigned long scan_start; + unsigned long scan_start_tsf; + void *scan_cmd; + enum ieee80211_band scan_band; + struct cfg80211_scan_request *scan_request; + struct ieee80211_vif *scan_vif; + u8 scan_tx_ant[IEEE80211_NUM_BANDS]; + u8 mgmt_tx_ant; + + /* spinlock */ + spinlock_t lock; /* protect general shared data */ + spinlock_t hcmd_lock; /* protect hcmd */ + spinlock_t reg_lock; /* protect hw register access */ + struct mutex mutex; + + /* basic pci-network driver stuff */ + struct pci_dev *pci_dev; + + /* pci hardware address support */ + void __iomem *hw_base; + u32 hw_rev; + u32 hw_wa_rev; + u8 rev_id; + + /* command queue number */ + u8 cmd_queue; + + /* max number of station keys */ + u8 sta_key_max_num; + + /* EEPROM MAC addresses */ + struct mac_address addresses[1]; + + /* uCode images, save to reload in case of failure */ + int fw_idx; /* firmware we're trying to load */ + u32 ucode_ver; /* version of ucode, copy of + il_ucode.ver */ + struct fw_desc ucode_code; /* runtime inst */ + struct fw_desc ucode_data; /* runtime data original */ + struct fw_desc ucode_data_backup; /* runtime data save/restore */ + struct fw_desc ucode_init; /* initialization inst */ + struct fw_desc ucode_init_data; /* initialization data */ + struct fw_desc ucode_boot; /* bootstrap inst */ + enum ucode_type ucode_type; + u8 ucode_write_complete; /* the image write is complete */ + char firmware_name[25]; + + struct ieee80211_vif *vif; + + struct il_qos_info qos_data; + + struct { + bool enabled; + bool is_40mhz; + bool non_gf_sta_present; + u8 protection; + u8 extension_chan_offset; + } ht; + + /* + * We declare this const so it can only be + * changed via explicit cast within the + * routines that actually update the physical + * hardware. + */ + const struct il_rxon_cmd active; + struct il_rxon_cmd staging; + + struct il_rxon_time_cmd timing; + + __le16 switch_channel; + + /* 1st responses from initialize and runtime uCode images. + * _4965's initialize alive response contains some calibration data. */ + struct il_init_alive_resp card_alive_init; + struct il_alive_resp card_alive; + + u16 active_rate; + + u8 start_calib; + struct il_sensitivity_data sensitivity_data; + struct il_chain_noise_data chain_noise_data; + __le16 sensitivity_tbl[HD_TBL_SIZE]; + + struct il_ht_config current_ht_config; + + /* Rate scaling data */ + u8 retry_rate; + + wait_queue_head_t wait_command_queue; + + int activity_timer_active; + + /* Rx and Tx DMA processing queues */ + struct il_rx_queue rxq; + struct il_tx_queue *txq; + unsigned long txq_ctx_active_msk; + struct il_dma_ptr kw; /* keep warm address */ + struct il_dma_ptr scd_bc_tbls; + + u32 scd_base_addr; /* scheduler sram base address */ + + unsigned long status; + + /* counts mgmt, ctl, and data packets */ + struct traffic_stats tx_stats; + struct traffic_stats rx_stats; + + /* counts interrupts */ + struct isr_stats isr_stats; + + struct il_power_mgr power_data; + + /* context information */ + u8 bssid[ETH_ALEN]; /* used only on 3945 but filled by core */ + + /* station table variables */ + + /* Note: if lock and sta_lock are needed, lock must be acquired first */ + spinlock_t sta_lock; + int num_stations; + struct il_station_entry stations[IL_STATION_COUNT]; + unsigned long ucode_key_table; + + /* queue refcounts */ +#define IL_MAX_HW_QUEUES 32 + unsigned long queue_stopped[BITS_TO_LONGS(IL_MAX_HW_QUEUES)]; + /* for each AC */ + atomic_t queue_stop_count[4]; + + /* Indication if ieee80211_ops->open has been called */ + u8 is_open; + + u8 mac80211_registered; + + /* eeprom -- this is in the card's little endian byte order */ + u8 *eeprom; + struct il_eeprom_calib_info *calib_info; + + enum nl80211_iftype iw_mode; + + /* Last Rx'd beacon timestamp */ + u64 timestamp; + + union { +#if defined(CPTCFG_IWL3945) || defined(CPTCFG_IWL3945_MODULE) + struct { + void *shared_virt; + dma_addr_t shared_phys; + + struct delayed_work thermal_periodic; + struct delayed_work rfkill_poll; + + struct il3945_notif_stats stats; +#ifdef CPTCFG_IWLEGACY_DEBUGFS + struct il3945_notif_stats accum_stats; + struct il3945_notif_stats delta_stats; + struct il3945_notif_stats max_delta; +#endif + + u32 sta_supp_rates; + int last_rx_rssi; /* From Rx packet stats */ + + /* Rx'd packet timing information */ + u32 last_beacon_time; + u64 last_tsf; + + /* + * each calibration channel group in the + * EEPROM has a derived clip setting for + * each rate. + */ + const struct il3945_clip_group clip_groups[5]; + + } _3945; +#endif +#if defined(CPTCFG_IWL4965) || defined(CPTCFG_IWL4965_MODULE) + struct { + struct il_rx_phy_res last_phy_res; + bool last_phy_res_valid; + u32 ampdu_ref; + + struct completion firmware_loading_complete; + + /* + * chain noise reset and gain commands are the + * two extra calibration commands follows the standard + * phy calibration commands + */ + u8 phy_calib_chain_noise_reset_cmd; + u8 phy_calib_chain_noise_gain_cmd; + + u8 key_mapping_keys; + struct il_wep_key wep_keys[WEP_KEYS_MAX]; + + struct il_notif_stats stats; +#ifdef CPTCFG_IWLEGACY_DEBUGFS + struct il_notif_stats accum_stats; + struct il_notif_stats delta_stats; + struct il_notif_stats max_delta; +#endif + + } _4965; +#endif + }; + + struct il_hw_params hw_params; + + u32 inta_mask; + + struct workqueue_struct *workqueue; + + struct work_struct restart; + struct work_struct scan_completed; + struct work_struct rx_replenish; + struct work_struct abort_scan; + + bool beacon_enabled; + struct sk_buff *beacon_skb; + + struct work_struct tx_flush; + + struct tasklet_struct irq_tasklet; + + struct delayed_work init_alive_start; + struct delayed_work alive_start; + struct delayed_work scan_check; + + /* TX Power */ + s8 tx_power_user_lmt; + s8 tx_power_device_lmt; + s8 tx_power_next; + +#ifdef CPTCFG_IWLEGACY_DEBUG + /* debugging info */ + u32 debug_level; /* per device debugging will override global + il_debug_level if set */ +#endif /* CPTCFG_IWLEGACY_DEBUG */ +#ifdef CPTCFG_IWLEGACY_DEBUGFS + /* debugfs */ + u16 tx_traffic_idx; + u16 rx_traffic_idx; + u8 *tx_traffic; + u8 *rx_traffic; + struct dentry *debugfs_dir; + u32 dbgfs_sram_offset, dbgfs_sram_len; + bool disable_ht40; +#endif /* CPTCFG_IWLEGACY_DEBUGFS */ + + struct work_struct txpower_work; + u32 disable_sens_cal; + u32 disable_chain_noise_cal; + u32 disable_tx_power_cal; + struct work_struct run_time_calib_work; + struct timer_list stats_periodic; + struct timer_list watchdog; + bool hw_ready; + + struct led_classdev led; + unsigned long blink_on, blink_off; + bool led_registered; +}; /*il_priv */ + +static inline void +il_txq_ctx_activate(struct il_priv *il, int txq_id) +{ + set_bit(txq_id, &il->txq_ctx_active_msk); +} + +static inline void +il_txq_ctx_deactivate(struct il_priv *il, int txq_id) +{ + clear_bit(txq_id, &il->txq_ctx_active_msk); +} + +static inline int +il_is_associated(struct il_priv *il) +{ + return (il->active.filter_flags & RXON_FILTER_ASSOC_MSK) ? 1 : 0; +} + +static inline int +il_is_any_associated(struct il_priv *il) +{ + return il_is_associated(il); +} + +static inline int +il_is_channel_valid(const struct il_channel_info *ch_info) +{ + if (ch_info == NULL) + return 0; + return (ch_info->flags & EEPROM_CHANNEL_VALID) ? 1 : 0; +} + +static inline int +il_is_channel_radar(const struct il_channel_info *ch_info) +{ + return (ch_info->flags & EEPROM_CHANNEL_RADAR) ? 1 : 0; +} + +static inline u8 +il_is_channel_a_band(const struct il_channel_info *ch_info) +{ + return ch_info->band == IEEE80211_BAND_5GHZ; +} + +static inline int +il_is_channel_passive(const struct il_channel_info *ch) +{ + return (!(ch->flags & EEPROM_CHANNEL_ACTIVE)) ? 1 : 0; +} + +static inline int +il_is_channel_ibss(const struct il_channel_info *ch) +{ + return (ch->flags & EEPROM_CHANNEL_IBSS) ? 1 : 0; +} + +static inline void +__il_free_pages(struct il_priv *il, struct page *page) +{ + __free_pages(page, il->hw_params.rx_page_order); + il->alloc_rxb_page--; +} + +static inline void +il_free_pages(struct il_priv *il, unsigned long page) +{ + free_pages(page, il->hw_params.rx_page_order); + il->alloc_rxb_page--; +} + +#define IWLWIFI_VERSION "in-tree:" +#define DRV_COPYRIGHT "Copyright(c) 2003-2011 Intel Corporation" +#define DRV_AUTHOR "" + +#define IL_PCI_DEVICE(dev, subdev, cfg) \ + .vendor = PCI_VENDOR_ID_INTEL, .device = (dev), \ + .subvendor = PCI_ANY_ID, .subdevice = (subdev), \ + .driver_data = (kernel_ulong_t)&(cfg) + +#define TIME_UNIT 1024 + +#define IL_SKU_G 0x1 +#define IL_SKU_A 0x2 +#define IL_SKU_N 0x8 + +#define IL_CMD(x) case x: return #x + +/* Size of one Rx buffer in host DRAM */ +#define IL_RX_BUF_SIZE_3K (3 * 1000) /* 3945 only */ +#define IL_RX_BUF_SIZE_4K (4 * 1024) +#define IL_RX_BUF_SIZE_8K (8 * 1024) + +#ifdef CPTCFG_IWLEGACY_DEBUGFS +struct il_debugfs_ops { + ssize_t(*rx_stats_read) (struct file *file, char __user *user_buf, + size_t count, loff_t *ppos); + ssize_t(*tx_stats_read) (struct file *file, char __user *user_buf, + size_t count, loff_t *ppos); + ssize_t(*general_stats_read) (struct file *file, + char __user *user_buf, size_t count, + loff_t *ppos); +}; +#endif + +struct il_ops { + /* Handling TX */ + void (*txq_update_byte_cnt_tbl) (struct il_priv *il, + struct il_tx_queue *txq, + u16 byte_cnt); + int (*txq_attach_buf_to_tfd) (struct il_priv *il, + struct il_tx_queue *txq, dma_addr_t addr, + u16 len, u8 reset, u8 pad); + void (*txq_free_tfd) (struct il_priv *il, struct il_tx_queue *txq); + int (*txq_init) (struct il_priv *il, struct il_tx_queue *txq); + /* alive notification after init uCode load */ + void (*init_alive_start) (struct il_priv *il); + /* check validity of rtc data address */ + int (*is_valid_rtc_data_addr) (u32 addr); + /* 1st ucode load */ + int (*load_ucode) (struct il_priv *il); + + void (*dump_nic_error_log) (struct il_priv *il); + int (*dump_fh) (struct il_priv *il, char **buf, bool display); + int (*set_channel_switch) (struct il_priv *il, + struct ieee80211_channel_switch *ch_switch); + /* power management */ + int (*apm_init) (struct il_priv *il); + + /* tx power */ + int (*send_tx_power) (struct il_priv *il); + void (*update_chain_flags) (struct il_priv *il); + + /* eeprom operations */ + int (*eeprom_acquire_semaphore) (struct il_priv *il); + void (*eeprom_release_semaphore) (struct il_priv *il); + + int (*rxon_assoc) (struct il_priv *il); + int (*commit_rxon) (struct il_priv *il); + void (*set_rxon_chain) (struct il_priv *il); + + u16(*get_hcmd_size) (u8 cmd_id, u16 len); + u16(*build_addsta_hcmd) (const struct il_addsta_cmd *cmd, u8 *data); + + int (*request_scan) (struct il_priv *il, struct ieee80211_vif *vif); + void (*post_scan) (struct il_priv *il); + void (*post_associate) (struct il_priv *il); + void (*config_ap) (struct il_priv *il); + /* station management */ + int (*update_bcast_stations) (struct il_priv *il); + int (*manage_ibss_station) (struct il_priv *il, + struct ieee80211_vif *vif, bool add); + + int (*send_led_cmd) (struct il_priv *il, struct il_led_cmd *led_cmd); +}; + +struct il_mod_params { + int sw_crypto; /* def: 0 = using hardware encryption */ + int disable_hw_scan; /* def: 0 = use h/w scan */ + int num_of_queues; /* def: HW dependent */ + int disable_11n; /* def: 0 = 11n capabilities enabled */ + int amsdu_size_8K; /* def: 1 = enable 8K amsdu size */ + int antenna; /* def: 0 = both antennas (use diversity) */ + int restart_fw; /* def: 1 = restart firmware */ +}; + +#define IL_LED_SOLID 11 +#define IL_DEF_LED_INTRVL cpu_to_le32(1000) + +#define IL_LED_ACTIVITY (0<<1) +#define IL_LED_LINK (1<<1) + +/* + * LED mode + * IL_LED_DEFAULT: use device default + * IL_LED_RF_STATE: turn LED on/off based on RF state + * LED ON = RF ON + * LED OFF = RF OFF + * IL_LED_BLINK: adjust led blink rate based on blink table + */ +enum il_led_mode { + IL_LED_DEFAULT, + IL_LED_RF_STATE, + IL_LED_BLINK, +}; + +void il_leds_init(struct il_priv *il); +void il_leds_exit(struct il_priv *il); + +/** + * struct il_cfg + * @fw_name_pre: Firmware filename prefix. The api version and extension + * (.ucode) will be added to filename before loading from disk. The + * filename is constructed as fw_name_pre.ucode. + * @ucode_api_max: Highest version of uCode API supported by driver. + * @ucode_api_min: Lowest version of uCode API supported by driver. + * @scan_antennas: available antenna for scan operation + * @led_mode: 0=blinking, 1=On(RF On)/Off(RF Off) + * + * We enable the driver to be backward compatible wrt API version. The + * driver specifies which APIs it supports (with @ucode_api_max being the + * highest and @ucode_api_min the lowest). Firmware will only be loaded if + * it has a supported API version. The firmware's API version will be + * stored in @il_priv, enabling the driver to make runtime changes based + * on firmware version used. + * + * For example, + * if (IL_UCODE_API(il->ucode_ver) >= 2) { + * Driver interacts with Firmware API version >= 2. + * } else { + * Driver interacts with Firmware API version 1. + * } + * + * The ideal usage of this infrastructure is to treat a new ucode API + * release as a new hardware revision. That is, through utilizing the + * il_hcmd_utils_ops etc. we accommodate different command structures + * and flows between hardware versions as well as their API + * versions. + * + */ +struct il_cfg { + /* params specific to an individual device within a device family */ + const char *name; + const char *fw_name_pre; + const unsigned int ucode_api_max; + const unsigned int ucode_api_min; + u8 valid_tx_ant; + u8 valid_rx_ant; + unsigned int sku; + u16 eeprom_ver; + u16 eeprom_calib_ver; + /* module based parameters which can be set from modprobe cmd */ + const struct il_mod_params *mod_params; + /* params not likely to change within a device family */ + struct il_base_params *base_params; + /* params likely to change within a device family */ + u8 scan_rx_antennas[IEEE80211_NUM_BANDS]; + enum il_led_mode led_mode; + + int eeprom_size; + int num_of_queues; /* def: HW dependent */ + int num_of_ampdu_queues; /* def: HW dependent */ + /* for il_apm_init() */ + u32 pll_cfg_val; + bool set_l0s; + bool use_bsm; + + u16 led_compensation; + int chain_noise_num_beacons; + unsigned int wd_timeout; + bool temperature_kelvin; + const bool ucode_tracing; + const bool sensitivity_calib_by_driver; + const bool chain_noise_calib_by_driver; + + const u32 regulatory_bands[7]; +}; + +/*************************** + * L i b * + ***************************/ + +int il_mac_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + u16 queue, const struct ieee80211_tx_queue_params *params); +int il_mac_tx_last_beacon(struct ieee80211_hw *hw); + +void il_set_rxon_hwcrypto(struct il_priv *il, int hw_decrypt); +int il_check_rxon_cmd(struct il_priv *il); +int il_full_rxon_required(struct il_priv *il); +int il_set_rxon_channel(struct il_priv *il, struct ieee80211_channel *ch); +void il_set_flags_for_band(struct il_priv *il, enum ieee80211_band band, + struct ieee80211_vif *vif); +u8 il_get_single_channel_number(struct il_priv *il, enum ieee80211_band band); +void il_set_rxon_ht(struct il_priv *il, struct il_ht_config *ht_conf); +bool il_is_ht40_tx_allowed(struct il_priv *il, + struct ieee80211_sta_ht_cap *ht_cap); +void il_connection_init_rx_config(struct il_priv *il); +void il_set_rate(struct il_priv *il); +int il_set_decrypted_flag(struct il_priv *il, struct ieee80211_hdr *hdr, + u32 decrypt_res, struct ieee80211_rx_status *stats); +void il_irq_handle_error(struct il_priv *il); +int il_mac_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif); +void il_mac_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif); +int il_mac_change_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + enum nl80211_iftype newtype, bool newp2p); +void il_mac_flush(struct ieee80211_hw *hw, u32 queues, bool drop); +int il_alloc_txq_mem(struct il_priv *il); +void il_free_txq_mem(struct il_priv *il); + +#ifdef CPTCFG_IWLEGACY_DEBUGFS +extern void il_update_stats(struct il_priv *il, bool is_tx, __le16 fc, u16 len); +#else +static inline void +il_update_stats(struct il_priv *il, bool is_tx, __le16 fc, u16 len) +{ +} +#endif + +/***************************************************** + * Handlers + ***************************************************/ +void il_hdl_pm_sleep(struct il_priv *il, struct il_rx_buf *rxb); +void il_hdl_pm_debug_stats(struct il_priv *il, struct il_rx_buf *rxb); +void il_hdl_error(struct il_priv *il, struct il_rx_buf *rxb); +void il_hdl_csa(struct il_priv *il, struct il_rx_buf *rxb); + +/***************************************************** +* RX +******************************************************/ +void il_cmd_queue_unmap(struct il_priv *il); +void il_cmd_queue_free(struct il_priv *il); +int il_rx_queue_alloc(struct il_priv *il); +void il_rx_queue_update_write_ptr(struct il_priv *il, struct il_rx_queue *q); +int il_rx_queue_space(const struct il_rx_queue *q); +void il_tx_cmd_complete(struct il_priv *il, struct il_rx_buf *rxb); + +void il_hdl_spectrum_measurement(struct il_priv *il, struct il_rx_buf *rxb); +void il_recover_from_stats(struct il_priv *il, struct il_rx_pkt *pkt); +void il_chswitch_done(struct il_priv *il, bool is_success); + +/***************************************************** +* TX +******************************************************/ +extern void il_txq_update_write_ptr(struct il_priv *il, struct il_tx_queue *txq); +extern int il_tx_queue_init(struct il_priv *il, u32 txq_id); +extern void il_tx_queue_reset(struct il_priv *il, u32 txq_id); +extern void il_tx_queue_unmap(struct il_priv *il, int txq_id); +extern void il_tx_queue_free(struct il_priv *il, int txq_id); +extern void il_setup_watchdog(struct il_priv *il); +/***************************************************** + * TX power + ****************************************************/ +int il_set_tx_power(struct il_priv *il, s8 tx_power, bool force); + +/******************************************************************************* + * Rate + ******************************************************************************/ + +u8 il_get_lowest_plcp(struct il_priv *il); + +/******************************************************************************* + * Scanning + ******************************************************************************/ +void il_init_scan_params(struct il_priv *il); +int il_scan_cancel(struct il_priv *il); +int il_scan_cancel_timeout(struct il_priv *il, unsigned long ms); +void il_force_scan_end(struct il_priv *il); +int il_mac_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct cfg80211_scan_request *req); +void il_internal_short_hw_scan(struct il_priv *il); +int il_force_reset(struct il_priv *il, bool external); +u16 il_fill_probe_req(struct il_priv *il, struct ieee80211_mgmt *frame, + const u8 *ta, const u8 *ie, int ie_len, int left); +void il_setup_rx_scan_handlers(struct il_priv *il); +u16 il_get_active_dwell_time(struct il_priv *il, enum ieee80211_band band, + u8 n_probes); +u16 il_get_passive_dwell_time(struct il_priv *il, enum ieee80211_band band, + struct ieee80211_vif *vif); +void il_setup_scan_deferred_work(struct il_priv *il); +void il_cancel_scan_deferred_work(struct il_priv *il); + +/* For faster active scanning, scan will move to the next channel if fewer than + * PLCP_QUIET_THRESH packets are heard on this channel within + * ACTIVE_QUIET_TIME after sending probe request. This shortens the dwell + * time if it's a quiet channel (nothing responded to our probe, and there's + * no other traffic). + * Disable "quiet" feature by setting PLCP_QUIET_THRESH to 0. */ +#define IL_ACTIVE_QUIET_TIME cpu_to_le16(10) /* msec */ +#define IL_PLCP_QUIET_THRESH cpu_to_le16(1) /* packets */ + +#define IL_SCAN_CHECK_WATCHDOG (HZ * 7) + +/***************************************************** + * S e n d i n g H o s t C o m m a n d s * + *****************************************************/ + +const char *il_get_cmd_string(u8 cmd); +int __must_check il_send_cmd_sync(struct il_priv *il, struct il_host_cmd *cmd); +int il_send_cmd(struct il_priv *il, struct il_host_cmd *cmd); +int __must_check il_send_cmd_pdu(struct il_priv *il, u8 id, u16 len, + const void *data); +int il_send_cmd_pdu_async(struct il_priv *il, u8 id, u16 len, const void *data, + void (*callback) (struct il_priv *il, + struct il_device_cmd *cmd, + struct il_rx_pkt *pkt)); + +int il_enqueue_hcmd(struct il_priv *il, struct il_host_cmd *cmd); + +/***************************************************** + * PCI * + *****************************************************/ + +void il_bg_watchdog(unsigned long data); +u32 il_usecs_to_beacons(struct il_priv *il, u32 usec, u32 beacon_interval); +__le32 il_add_beacon_time(struct il_priv *il, u32 base, u32 addon, + u32 beacon_interval); + +#ifdef CONFIG_PM_SLEEP +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)) +int il_pci_suspend_compat(struct pci_dev *pdev, pm_message_t state); +int il_pci_resume_compat(struct pci_dev *pdev); +#elif (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32)) +extern struct dev_pm_ops il_pm_ops; +#else +extern const struct dev_pm_ops il_pm_ops; +#endif + +#define IL_LEGACY_PM_OPS (&il_pm_ops) + +#else /* !CONFIG_PM_SLEEP */ + +#define IL_LEGACY_PM_OPS NULL + +#endif /* !CONFIG_PM_SLEEP */ + +/***************************************************** +* Error Handling Debugging +******************************************************/ +void il4965_dump_nic_error_log(struct il_priv *il); +#ifdef CPTCFG_IWLEGACY_DEBUG +void il_print_rx_config_cmd(struct il_priv *il); +#else +static inline void +il_print_rx_config_cmd(struct il_priv *il) +{ +} +#endif + +void il_clear_isr_stats(struct il_priv *il); + +/***************************************************** +* GEOS +******************************************************/ +int il_init_geos(struct il_priv *il); +void il_free_geos(struct il_priv *il); + +/*************** DRIVER STATUS FUNCTIONS *****/ + +#define S_HCMD_ACTIVE 0 /* host command in progress */ +/* 1 is unused (used to be S_HCMD_SYNC_ACTIVE) */ +#define S_INT_ENABLED 2 +#define S_RFKILL 3 +#define S_CT_KILL 4 +#define S_INIT 5 +#define S_ALIVE 6 +#define S_READY 7 +#define S_TEMPERATURE 8 +#define S_GEO_CONFIGURED 9 +#define S_EXIT_PENDING 10 +#define S_STATS 12 +#define S_SCANNING 13 +#define S_SCAN_ABORTING 14 +#define S_SCAN_HW 15 +#define S_POWER_PMI 16 +#define S_FW_ERROR 17 +#define S_CHANNEL_SWITCH_PENDING 18 + +static inline int +il_is_ready(struct il_priv *il) +{ + /* The adapter is 'ready' if READY and GEO_CONFIGURED bits are + * set but EXIT_PENDING is not */ + return test_bit(S_READY, &il->status) && + test_bit(S_GEO_CONFIGURED, &il->status) && + !test_bit(S_EXIT_PENDING, &il->status); +} + +static inline int +il_is_alive(struct il_priv *il) +{ + return test_bit(S_ALIVE, &il->status); +} + +static inline int +il_is_init(struct il_priv *il) +{ + return test_bit(S_INIT, &il->status); +} + +static inline int +il_is_rfkill(struct il_priv *il) +{ + return test_bit(S_RFKILL, &il->status); +} + +static inline int +il_is_ctkill(struct il_priv *il) +{ + return test_bit(S_CT_KILL, &il->status); +} + +static inline int +il_is_ready_rf(struct il_priv *il) +{ + + if (il_is_rfkill(il)) + return 0; + + return il_is_ready(il); +} + +extern void il_send_bt_config(struct il_priv *il); +extern int il_send_stats_request(struct il_priv *il, u8 flags, bool clear); +extern void il_apm_stop(struct il_priv *il); +extern void _il_apm_stop(struct il_priv *il); + +int il_apm_init(struct il_priv *il); + +int il_send_rxon_timing(struct il_priv *il); + +static inline int +il_send_rxon_assoc(struct il_priv *il) +{ + return il->ops->rxon_assoc(il); +} + +static inline int +il_commit_rxon(struct il_priv *il) +{ + return il->ops->commit_rxon(il); +} + +static inline const struct ieee80211_supported_band * +il_get_hw_mode(struct il_priv *il, enum ieee80211_band band) +{ + return il->hw->wiphy->bands[band]; +} + +/* mac80211 handlers */ +int il_mac_config(struct ieee80211_hw *hw, u32 changed); +void il_mac_reset_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif); +void il_mac_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, u32 changes); +void il_tx_cmd_protection(struct il_priv *il, struct ieee80211_tx_info *info, + __le16 fc, __le32 *tx_flags); + +irqreturn_t il_isr(int irq, void *data); + +extern void il_set_bit(struct il_priv *p, u32 r, u32 m); +extern void il_clear_bit(struct il_priv *p, u32 r, u32 m); +extern bool _il_grab_nic_access(struct il_priv *il); +extern int _il_poll_bit(struct il_priv *il, u32 addr, u32 bits, u32 mask, int timeout); +extern int il_poll_bit(struct il_priv *il, u32 addr, u32 mask, int timeout); +extern u32 il_rd_prph(struct il_priv *il, u32 reg); +extern void il_wr_prph(struct il_priv *il, u32 addr, u32 val); +extern u32 il_read_targ_mem(struct il_priv *il, u32 addr); +extern void il_write_targ_mem(struct il_priv *il, u32 addr, u32 val); + +static inline void +_il_write8(struct il_priv *il, u32 ofs, u8 val) +{ + writeb(val, il->hw_base + ofs); +} +#define il_write8(il, ofs, val) _il_write8(il, ofs, val) + +static inline void +_il_wr(struct il_priv *il, u32 ofs, u32 val) +{ + writel(val, il->hw_base + ofs); +} + +static inline u32 +_il_rd(struct il_priv *il, u32 ofs) +{ + return readl(il->hw_base + ofs); +} + +static inline void +_il_clear_bit(struct il_priv *il, u32 reg, u32 mask) +{ + _il_wr(il, reg, _il_rd(il, reg) & ~mask); +} + +static inline void +_il_set_bit(struct il_priv *il, u32 reg, u32 mask) +{ + _il_wr(il, reg, _il_rd(il, reg) | mask); +} + +static inline void +_il_release_nic_access(struct il_priv *il) +{ + _il_clear_bit(il, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + /* + * In above we are reading CSR_GP_CNTRL register, what will flush any + * previous writes, but still want write, which clear MAC_ACCESS_REQ + * bit, be performed on PCI bus before any other writes scheduled on + * different CPUs (after we drop reg_lock). + */ + mmiowb(); +} + +static inline u32 +il_rd(struct il_priv *il, u32 reg) +{ + u32 value; + unsigned long reg_flags; + + spin_lock_irqsave(&il->reg_lock, reg_flags); + _il_grab_nic_access(il); + value = _il_rd(il, reg); + _il_release_nic_access(il); + spin_unlock_irqrestore(&il->reg_lock, reg_flags); + return value; +} + +static inline void +il_wr(struct il_priv *il, u32 reg, u32 value) +{ + unsigned long reg_flags; + + spin_lock_irqsave(&il->reg_lock, reg_flags); + if (likely(_il_grab_nic_access(il))) { + _il_wr(il, reg, value); + _il_release_nic_access(il); + } + spin_unlock_irqrestore(&il->reg_lock, reg_flags); +} + +static inline u32 +_il_rd_prph(struct il_priv *il, u32 reg) +{ + _il_wr(il, HBUS_TARG_PRPH_RADDR, reg | (3 << 24)); + return _il_rd(il, HBUS_TARG_PRPH_RDAT); +} + +static inline void +_il_wr_prph(struct il_priv *il, u32 addr, u32 val) +{ + _il_wr(il, HBUS_TARG_PRPH_WADDR, ((addr & 0x0000FFFF) | (3 << 24))); + _il_wr(il, HBUS_TARG_PRPH_WDAT, val); +} + +static inline void +il_set_bits_prph(struct il_priv *il, u32 reg, u32 mask) +{ + unsigned long reg_flags; + + spin_lock_irqsave(&il->reg_lock, reg_flags); + if (likely(_il_grab_nic_access(il))) { + _il_wr_prph(il, reg, (_il_rd_prph(il, reg) | mask)); + _il_release_nic_access(il); + } + spin_unlock_irqrestore(&il->reg_lock, reg_flags); +} + +static inline void +il_set_bits_mask_prph(struct il_priv *il, u32 reg, u32 bits, u32 mask) +{ + unsigned long reg_flags; + + spin_lock_irqsave(&il->reg_lock, reg_flags); + if (likely(_il_grab_nic_access(il))) { + _il_wr_prph(il, reg, ((_il_rd_prph(il, reg) & mask) | bits)); + _il_release_nic_access(il); + } + spin_unlock_irqrestore(&il->reg_lock, reg_flags); +} + +static inline void +il_clear_bits_prph(struct il_priv *il, u32 reg, u32 mask) +{ + unsigned long reg_flags; + u32 val; + + spin_lock_irqsave(&il->reg_lock, reg_flags); + if (likely(_il_grab_nic_access(il))) { + val = _il_rd_prph(il, reg); + _il_wr_prph(il, reg, (val & ~mask)); + _il_release_nic_access(il); + } + spin_unlock_irqrestore(&il->reg_lock, reg_flags); +} + +#define HW_KEY_DYNAMIC 0 +#define HW_KEY_DEFAULT 1 + +#define IL_STA_DRIVER_ACTIVE BIT(0) /* driver entry is active */ +#define IL_STA_UCODE_ACTIVE BIT(1) /* ucode entry is active */ +#define IL_STA_UCODE_INPROGRESS BIT(2) /* ucode entry is in process of + being activated */ +#define IL_STA_LOCAL BIT(3) /* station state not directed by mac80211; + (this is for the IBSS BSSID stations) */ +#define IL_STA_BCAST BIT(4) /* this station is the special bcast station */ + +void il_restore_stations(struct il_priv *il); +void il_clear_ucode_stations(struct il_priv *il); +void il_dealloc_bcast_stations(struct il_priv *il); +int il_get_free_ucode_key_idx(struct il_priv *il); +int il_send_add_sta(struct il_priv *il, struct il_addsta_cmd *sta, u8 flags); +int il_add_station_common(struct il_priv *il, const u8 *addr, bool is_ap, + struct ieee80211_sta *sta, u8 *sta_id_r); +int il_remove_station(struct il_priv *il, const u8 sta_id, const u8 * addr); +int il_mac_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta); + +u8 il_prep_station(struct il_priv *il, const u8 *addr, bool is_ap, + struct ieee80211_sta *sta); + +int il_send_lq_cmd(struct il_priv *il, struct il_link_quality_cmd *lq, + u8 flags, bool init); + +/** + * il_clear_driver_stations - clear knowledge of all stations from driver + * @il: iwl il struct + * + * This is called during il_down() to make sure that in the case + * we're coming there from a hardware restart mac80211 will be + * able to reconfigure stations -- if we're getting there in the + * normal down flow then the stations will already be cleared. + */ +static inline void +il_clear_driver_stations(struct il_priv *il) +{ + unsigned long flags; + + spin_lock_irqsave(&il->sta_lock, flags); + memset(il->stations, 0, sizeof(il->stations)); + il->num_stations = 0; + il->ucode_key_table = 0; + spin_unlock_irqrestore(&il->sta_lock, flags); +} + +static inline int +il_sta_id(struct ieee80211_sta *sta) +{ + if (WARN_ON(!sta)) + return IL_INVALID_STATION; + + return ((struct il_station_priv_common *)sta->drv_priv)->sta_id; +} + +/** + * il_sta_id_or_broadcast - return sta_id or broadcast sta + * @il: iwl il + * @context: the current context + * @sta: mac80211 station + * + * In certain circumstances mac80211 passes a station pointer + * that may be %NULL, for example during TX or key setup. In + * that case, we need to use the broadcast station, so this + * inline wraps that pattern. + */ +static inline int +il_sta_id_or_broadcast(struct il_priv *il, struct ieee80211_sta *sta) +{ + int sta_id; + + if (!sta) + return il->hw_params.bcast_id; + + sta_id = il_sta_id(sta); + + /* + * mac80211 should not be passing a partially + * initialised station! + */ + WARN_ON(sta_id == IL_INVALID_STATION); + + return sta_id; +} + +/** + * il_queue_inc_wrap - increment queue idx, wrap back to beginning + * @idx -- current idx + * @n_bd -- total number of entries in queue (must be power of 2) + */ +static inline int +il_queue_inc_wrap(int idx, int n_bd) +{ + return ++idx & (n_bd - 1); +} + +/** + * il_queue_dec_wrap - decrement queue idx, wrap back to end + * @idx -- current idx + * @n_bd -- total number of entries in queue (must be power of 2) + */ +static inline int +il_queue_dec_wrap(int idx, int n_bd) +{ + return --idx & (n_bd - 1); +} + +/* TODO: Move fw_desc functions to iwl-pci.ko */ +static inline void +il_free_fw_desc(struct pci_dev *pci_dev, struct fw_desc *desc) +{ + if (desc->v_addr) + dma_free_coherent(&pci_dev->dev, desc->len, desc->v_addr, + desc->p_addr); + desc->v_addr = NULL; + desc->len = 0; +} + +static inline int +il_alloc_fw_desc(struct pci_dev *pci_dev, struct fw_desc *desc) +{ + if (!desc->len) { + desc->v_addr = NULL; + return -EINVAL; + } + + desc->v_addr = dma_alloc_coherent(&pci_dev->dev, desc->len, + &desc->p_addr, GFP_KERNEL); + return (desc->v_addr != NULL) ? 0 : -ENOMEM; +} + +/* + * we have 8 bits used like this: + * + * 7 6 5 4 3 2 1 0 + * | | | | | | | | + * | | | | | | +-+-------- AC queue (0-3) + * | | | | | | + * | +-+-+-+-+------------ HW queue ID + * | + * +---------------------- unused + */ +static inline void +il_set_swq_id(struct il_tx_queue *txq, u8 ac, u8 hwq) +{ + BUG_ON(ac > 3); /* only have 2 bits */ + BUG_ON(hwq > 31); /* only use 5 bits */ + + txq->swq_id = (hwq << 2) | ac; +} + +static inline void +il_wake_queue(struct il_priv *il, struct il_tx_queue *txq) +{ + u8 queue = txq->swq_id; + u8 ac = queue & 3; + u8 hwq = (queue >> 2) & 0x1f; + + if (test_and_clear_bit(hwq, il->queue_stopped)) + if (atomic_dec_return(&il->queue_stop_count[ac]) <= 0) + ieee80211_wake_queue(il->hw, ac); +} + +static inline void +il_stop_queue(struct il_priv *il, struct il_tx_queue *txq) +{ + u8 queue = txq->swq_id; + u8 ac = queue & 3; + u8 hwq = (queue >> 2) & 0x1f; + + if (!test_and_set_bit(hwq, il->queue_stopped)) + if (atomic_inc_return(&il->queue_stop_count[ac]) > 0) + ieee80211_stop_queue(il->hw, ac); +} + +#ifdef ieee80211_stop_queue +#undef ieee80211_stop_queue +#endif + +#define ieee80211_stop_queue DO_NOT_USE_ieee80211_stop_queue + +#ifdef ieee80211_wake_queue +#undef ieee80211_wake_queue +#endif + +#define ieee80211_wake_queue DO_NOT_USE_ieee80211_wake_queue + +static inline void +il_disable_interrupts(struct il_priv *il) +{ + clear_bit(S_INT_ENABLED, &il->status); + + /* disable interrupts from uCode/NIC to host */ + _il_wr(il, CSR_INT_MASK, 0x00000000); + + /* acknowledge/clear/reset any interrupts still pending + * from uCode or flow handler (Rx/Tx DMA) */ + _il_wr(il, CSR_INT, 0xffffffff); + _il_wr(il, CSR_FH_INT_STATUS, 0xffffffff); +} + +static inline void +il_enable_rfkill_int(struct il_priv *il) +{ + _il_wr(il, CSR_INT_MASK, CSR_INT_BIT_RF_KILL); +} + +static inline void +il_enable_interrupts(struct il_priv *il) +{ + set_bit(S_INT_ENABLED, &il->status); + _il_wr(il, CSR_INT_MASK, il->inta_mask); +} + +/** + * il_beacon_time_mask_low - mask of lower 32 bit of beacon time + * @il -- pointer to il_priv data structure + * @tsf_bits -- number of bits need to shift for masking) + */ +static inline u32 +il_beacon_time_mask_low(struct il_priv *il, u16 tsf_bits) +{ + return (1 << tsf_bits) - 1; +} + +/** + * il_beacon_time_mask_high - mask of higher 32 bit of beacon time + * @il -- pointer to il_priv data structure + * @tsf_bits -- number of bits need to shift for masking) + */ +static inline u32 +il_beacon_time_mask_high(struct il_priv *il, u16 tsf_bits) +{ + return ((1 << (32 - tsf_bits)) - 1) << tsf_bits; +} + +/** + * struct il_rb_status - reseve buffer status host memory mapped FH registers + * + * @closed_rb_num [0:11] - Indicates the idx of the RB which was closed + * @closed_fr_num [0:11] - Indicates the idx of the RX Frame which was closed + * @finished_rb_num [0:11] - Indicates the idx of the current RB + * in which the last frame was written to + * @finished_fr_num [0:11] - Indicates the idx of the RX Frame + * which was transferred + */ +struct il_rb_status { + __le16 closed_rb_num; + __le16 closed_fr_num; + __le16 finished_rb_num; + __le16 finished_fr_nam; + __le32 __unused; /* 3945 only */ +} __packed; + +#define TFD_QUEUE_SIZE_MAX 256 +#define TFD_QUEUE_SIZE_BC_DUP 64 +#define TFD_QUEUE_BC_SIZE (TFD_QUEUE_SIZE_MAX + TFD_QUEUE_SIZE_BC_DUP) +#define IL_TX_DMA_MASK DMA_BIT_MASK(36) +#define IL_NUM_OF_TBS 20 + +static inline u8 +il_get_dma_hi_addr(dma_addr_t addr) +{ + return (sizeof(addr) > sizeof(u32) ? (addr >> 16) >> 16 : 0) & 0xF; +} + +/** + * struct il_tfd_tb transmit buffer descriptor within transmit frame descriptor + * + * This structure contains dma address and length of transmission address + * + * @lo: low [31:0] portion of the dma address of TX buffer every even is + * unaligned on 16 bit boundary + * @hi_n_len: 0-3 [35:32] portion of dma + * 4-15 length of the tx buffer + */ +struct il_tfd_tb { + __le32 lo; + __le16 hi_n_len; +} __packed; + +/** + * struct il_tfd + * + * Transmit Frame Descriptor (TFD) + * + * @ __reserved1[3] reserved + * @ num_tbs 0-4 number of active tbs + * 5 reserved + * 6-7 padding (not used) + * @ tbs[20] transmit frame buffer descriptors + * @ __pad padding + * + * Each Tx queue uses a circular buffer of 256 TFDs stored in host DRAM. + * Both driver and device share these circular buffers, each of which must be + * contiguous 256 TFDs x 128 bytes-per-TFD = 32 KBytes + * + * Driver must indicate the physical address of the base of each + * circular buffer via the FH49_MEM_CBBC_QUEUE registers. + * + * Each TFD contains pointer/size information for up to 20 data buffers + * in host DRAM. These buffers collectively contain the (one) frame described + * by the TFD. Each buffer must be a single contiguous block of memory within + * itself, but buffers may be scattered in host DRAM. Each buffer has max size + * of (4K - 4). The concatenates all of a TFD's buffers into a single + * Tx frame, up to 8 KBytes in size. + * + * A maximum of 255 (not 256!) TFDs may be on a queue waiting for Tx. + */ +struct il_tfd { + u8 __reserved1[3]; + u8 num_tbs; + struct il_tfd_tb tbs[IL_NUM_OF_TBS]; + __le32 __pad; +} __packed; +/* PCI registers */ +#define PCI_CFG_RETRY_TIMEOUT 0x041 + +struct il_rate_info { + u8 plcp; /* uCode API: RATE_6M_PLCP, etc. */ + u8 plcp_siso; /* uCode API: RATE_SISO_6M_PLCP, etc. */ + u8 plcp_mimo2; /* uCode API: RATE_MIMO2_6M_PLCP, etc. */ + u8 ieee; /* MAC header: RATE_6M_IEEE, etc. */ + u8 prev_ieee; /* previous rate in IEEE speeds */ + u8 next_ieee; /* next rate in IEEE speeds */ + u8 prev_rs; /* previous rate used in rs algo */ + u8 next_rs; /* next rate used in rs algo */ + u8 prev_rs_tgg; /* previous rate used in TGG rs algo */ + u8 next_rs_tgg; /* next rate used in TGG rs algo */ +}; + +struct il3945_rate_info { + u8 plcp; /* uCode API: RATE_6M_PLCP, etc. */ + u8 ieee; /* MAC header: RATE_6M_IEEE, etc. */ + u8 prev_ieee; /* previous rate in IEEE speeds */ + u8 next_ieee; /* next rate in IEEE speeds */ + u8 prev_rs; /* previous rate used in rs algo */ + u8 next_rs; /* next rate used in rs algo */ + u8 prev_rs_tgg; /* previous rate used in TGG rs algo */ + u8 next_rs_tgg; /* next rate used in TGG rs algo */ + u8 table_rs_idx; /* idx in rate scale table cmd */ + u8 prev_table_rs; /* prev in rate table cmd */ +}; + +/* + * These serve as idxes into + * struct il_rate_info il_rates[RATE_COUNT]; + */ +enum { + RATE_1M_IDX = 0, + RATE_2M_IDX, + RATE_5M_IDX, + RATE_11M_IDX, + RATE_6M_IDX, + RATE_9M_IDX, + RATE_12M_IDX, + RATE_18M_IDX, + RATE_24M_IDX, + RATE_36M_IDX, + RATE_48M_IDX, + RATE_54M_IDX, + RATE_60M_IDX, + RATE_COUNT, + RATE_COUNT_LEGACY = RATE_COUNT - 1, /* Excluding 60M */ + RATE_COUNT_3945 = RATE_COUNT - 1, + RATE_INVM_IDX = RATE_COUNT, + RATE_INVALID = RATE_COUNT, +}; + +enum { + RATE_6M_IDX_TBL = 0, + RATE_9M_IDX_TBL, + RATE_12M_IDX_TBL, + RATE_18M_IDX_TBL, + RATE_24M_IDX_TBL, + RATE_36M_IDX_TBL, + RATE_48M_IDX_TBL, + RATE_54M_IDX_TBL, + RATE_1M_IDX_TBL, + RATE_2M_IDX_TBL, + RATE_5M_IDX_TBL, + RATE_11M_IDX_TBL, + RATE_INVM_IDX_TBL = RATE_INVM_IDX - 1, +}; + +enum { + IL_FIRST_OFDM_RATE = RATE_6M_IDX, + IL39_LAST_OFDM_RATE = RATE_54M_IDX, + IL_LAST_OFDM_RATE = RATE_60M_IDX, + IL_FIRST_CCK_RATE = RATE_1M_IDX, + IL_LAST_CCK_RATE = RATE_11M_IDX, +}; + +/* #define vs. enum to keep from defaulting to 'large integer' */ +#define RATE_6M_MASK (1 << RATE_6M_IDX) +#define RATE_9M_MASK (1 << RATE_9M_IDX) +#define RATE_12M_MASK (1 << RATE_12M_IDX) +#define RATE_18M_MASK (1 << RATE_18M_IDX) +#define RATE_24M_MASK (1 << RATE_24M_IDX) +#define RATE_36M_MASK (1 << RATE_36M_IDX) +#define RATE_48M_MASK (1 << RATE_48M_IDX) +#define RATE_54M_MASK (1 << RATE_54M_IDX) +#define RATE_60M_MASK (1 << RATE_60M_IDX) +#define RATE_1M_MASK (1 << RATE_1M_IDX) +#define RATE_2M_MASK (1 << RATE_2M_IDX) +#define RATE_5M_MASK (1 << RATE_5M_IDX) +#define RATE_11M_MASK (1 << RATE_11M_IDX) + +/* uCode API values for legacy bit rates, both OFDM and CCK */ +enum { + RATE_6M_PLCP = 13, + RATE_9M_PLCP = 15, + RATE_12M_PLCP = 5, + RATE_18M_PLCP = 7, + RATE_24M_PLCP = 9, + RATE_36M_PLCP = 11, + RATE_48M_PLCP = 1, + RATE_54M_PLCP = 3, + RATE_60M_PLCP = 3, /*FIXME:RS:should be removed */ + RATE_1M_PLCP = 10, + RATE_2M_PLCP = 20, + RATE_5M_PLCP = 55, + RATE_11M_PLCP = 110, + /*FIXME:RS:add RATE_LEGACY_INVM_PLCP = 0, */ +}; + +/* uCode API values for OFDM high-throughput (HT) bit rates */ +enum { + RATE_SISO_6M_PLCP = 0, + RATE_SISO_12M_PLCP = 1, + RATE_SISO_18M_PLCP = 2, + RATE_SISO_24M_PLCP = 3, + RATE_SISO_36M_PLCP = 4, + RATE_SISO_48M_PLCP = 5, + RATE_SISO_54M_PLCP = 6, + RATE_SISO_60M_PLCP = 7, + RATE_MIMO2_6M_PLCP = 0x8, + RATE_MIMO2_12M_PLCP = 0x9, + RATE_MIMO2_18M_PLCP = 0xa, + RATE_MIMO2_24M_PLCP = 0xb, + RATE_MIMO2_36M_PLCP = 0xc, + RATE_MIMO2_48M_PLCP = 0xd, + RATE_MIMO2_54M_PLCP = 0xe, + RATE_MIMO2_60M_PLCP = 0xf, + RATE_SISO_INVM_PLCP, + RATE_MIMO2_INVM_PLCP = RATE_SISO_INVM_PLCP, +}; + +/* MAC header values for bit rates */ +enum { + RATE_6M_IEEE = 12, + RATE_9M_IEEE = 18, + RATE_12M_IEEE = 24, + RATE_18M_IEEE = 36, + RATE_24M_IEEE = 48, + RATE_36M_IEEE = 72, + RATE_48M_IEEE = 96, + RATE_54M_IEEE = 108, + RATE_60M_IEEE = 120, + RATE_1M_IEEE = 2, + RATE_2M_IEEE = 4, + RATE_5M_IEEE = 11, + RATE_11M_IEEE = 22, +}; + +#define IL_CCK_BASIC_RATES_MASK \ + (RATE_1M_MASK | \ + RATE_2M_MASK) + +#define IL_CCK_RATES_MASK \ + (IL_CCK_BASIC_RATES_MASK | \ + RATE_5M_MASK | \ + RATE_11M_MASK) + +#define IL_OFDM_BASIC_RATES_MASK \ + (RATE_6M_MASK | \ + RATE_12M_MASK | \ + RATE_24M_MASK) + +#define IL_OFDM_RATES_MASK \ + (IL_OFDM_BASIC_RATES_MASK | \ + RATE_9M_MASK | \ + RATE_18M_MASK | \ + RATE_36M_MASK | \ + RATE_48M_MASK | \ + RATE_54M_MASK) + +#define IL_BASIC_RATES_MASK \ + (IL_OFDM_BASIC_RATES_MASK | \ + IL_CCK_BASIC_RATES_MASK) + +#define RATES_MASK ((1 << RATE_COUNT) - 1) +#define RATES_MASK_3945 ((1 << RATE_COUNT_3945) - 1) + +#define IL_INVALID_VALUE -1 + +#define IL_MIN_RSSI_VAL -100 +#define IL_MAX_RSSI_VAL 0 + +/* These values specify how many Tx frame attempts before + * searching for a new modulation mode */ +#define IL_LEGACY_FAILURE_LIMIT 160 +#define IL_LEGACY_SUCCESS_LIMIT 480 +#define IL_LEGACY_TBL_COUNT 160 + +#define IL_NONE_LEGACY_FAILURE_LIMIT 400 +#define IL_NONE_LEGACY_SUCCESS_LIMIT 4500 +#define IL_NONE_LEGACY_TBL_COUNT 1500 + +/* Success ratio (ACKed / attempted tx frames) values (perfect is 128 * 100) */ +#define IL_RS_GOOD_RATIO 12800 /* 100% */ +#define RATE_SCALE_SWITCH 10880 /* 85% */ +#define RATE_HIGH_TH 10880 /* 85% */ +#define RATE_INCREASE_TH 6400 /* 50% */ +#define RATE_DECREASE_TH 1920 /* 15% */ + +/* possible actions when in legacy mode */ +#define IL_LEGACY_SWITCH_ANTENNA1 0 +#define IL_LEGACY_SWITCH_ANTENNA2 1 +#define IL_LEGACY_SWITCH_SISO 2 +#define IL_LEGACY_SWITCH_MIMO2_AB 3 +#define IL_LEGACY_SWITCH_MIMO2_AC 4 +#define IL_LEGACY_SWITCH_MIMO2_BC 5 + +/* possible actions when in siso mode */ +#define IL_SISO_SWITCH_ANTENNA1 0 +#define IL_SISO_SWITCH_ANTENNA2 1 +#define IL_SISO_SWITCH_MIMO2_AB 2 +#define IL_SISO_SWITCH_MIMO2_AC 3 +#define IL_SISO_SWITCH_MIMO2_BC 4 +#define IL_SISO_SWITCH_GI 5 + +/* possible actions when in mimo mode */ +#define IL_MIMO2_SWITCH_ANTENNA1 0 +#define IL_MIMO2_SWITCH_ANTENNA2 1 +#define IL_MIMO2_SWITCH_SISO_A 2 +#define IL_MIMO2_SWITCH_SISO_B 3 +#define IL_MIMO2_SWITCH_SISO_C 4 +#define IL_MIMO2_SWITCH_GI 5 + +#define IL_MAX_SEARCH IL_MIMO2_SWITCH_GI + +#define IL_ACTION_LIMIT 3 /* # possible actions */ + +#define LQ_SIZE 2 /* 2 mode tables: "Active" and "Search" */ + +/* load per tid defines for A-MPDU activation */ +#define IL_AGG_TPT_THREHOLD 0 +#define IL_AGG_LOAD_THRESHOLD 10 +#define IL_AGG_ALL_TID 0xff +#define TID_QUEUE_CELL_SPACING 50 /*mS */ +#define TID_QUEUE_MAX_SIZE 20 +#define TID_ROUND_VALUE 5 /* mS */ +#define TID_MAX_LOAD_COUNT 8 + +#define TID_MAX_TIME_DIFF ((TID_QUEUE_MAX_SIZE - 1) * TID_QUEUE_CELL_SPACING) +#define TIME_WRAP_AROUND(x, y) (((y) > (x)) ? (y) - (x) : (0-(x)) + (y)) + +extern const struct il_rate_info il_rates[RATE_COUNT]; + +enum il_table_type { + LQ_NONE, + LQ_G, /* legacy types */ + LQ_A, + LQ_SISO, /* high-throughput types */ + LQ_MIMO2, + LQ_MAX, +}; + +#define is_legacy(tbl) ((tbl) == LQ_G || (tbl) == LQ_A) +#define is_siso(tbl) ((tbl) == LQ_SISO) +#define is_mimo2(tbl) ((tbl) == LQ_MIMO2) +#define is_mimo(tbl) (is_mimo2(tbl)) +#define is_Ht(tbl) (is_siso(tbl) || is_mimo(tbl)) +#define is_a_band(tbl) ((tbl) == LQ_A) +#define is_g_and(tbl) ((tbl) == LQ_G) + +#define ANT_NONE 0x0 +#define ANT_A BIT(0) +#define ANT_B BIT(1) +#define ANT_AB (ANT_A | ANT_B) +#define ANT_C BIT(2) +#define ANT_AC (ANT_A | ANT_C) +#define ANT_BC (ANT_B | ANT_C) +#define ANT_ABC (ANT_AB | ANT_C) + +#define IL_MAX_MCS_DISPLAY_SIZE 12 + +struct il_rate_mcs_info { + char mbps[IL_MAX_MCS_DISPLAY_SIZE]; + char mcs[IL_MAX_MCS_DISPLAY_SIZE]; +}; + +/** + * struct il_rate_scale_data -- tx success history for one rate + */ +struct il_rate_scale_data { + u64 data; /* bitmap of successful frames */ + s32 success_counter; /* number of frames successful */ + s32 success_ratio; /* per-cent * 128 */ + s32 counter; /* number of frames attempted */ + s32 average_tpt; /* success ratio * expected throughput */ + unsigned long stamp; +}; + +/** + * struct il_scale_tbl_info -- tx params and success history for all rates + * + * There are two of these in struct il_lq_sta, + * one for "active", and one for "search". + */ +struct il_scale_tbl_info { + enum il_table_type lq_type; + u8 ant_type; + u8 is_SGI; /* 1 = short guard interval */ + u8 is_ht40; /* 1 = 40 MHz channel width */ + u8 is_dup; /* 1 = duplicated data streams */ + u8 action; /* change modulation; IL_[LEGACY/SISO/MIMO]_SWITCH_* */ + u8 max_search; /* maximun number of tables we can search */ + s32 *expected_tpt; /* throughput metrics; expected_tpt_G, etc. */ + u32 current_rate; /* rate_n_flags, uCode API format */ + struct il_rate_scale_data win[RATE_COUNT]; /* rate histories */ +}; + +struct il_traffic_load { + unsigned long time_stamp; /* age of the oldest stats */ + u32 packet_count[TID_QUEUE_MAX_SIZE]; /* packet count in this time + * slice */ + u32 total; /* total num of packets during the + * last TID_MAX_TIME_DIFF */ + u8 queue_count; /* number of queues that has + * been used since the last cleanup */ + u8 head; /* start of the circular buffer */ +}; + +/** + * struct il_lq_sta -- driver's rate scaling ilate structure + * + * Pointer to this gets passed back and forth between driver and mac80211. + */ +struct il_lq_sta { + u8 active_tbl; /* idx of active table, range 0-1 */ + u8 enable_counter; /* indicates HT mode */ + u8 stay_in_tbl; /* 1: disallow, 0: allow search for new mode */ + u8 search_better_tbl; /* 1: currently trying alternate mode */ + s32 last_tpt; + + /* The following determine when to search for a new mode */ + u32 table_count_limit; + u32 max_failure_limit; /* # failed frames before new search */ + u32 max_success_limit; /* # successful frames before new search */ + u32 table_count; + u32 total_failed; /* total failed frames, any/all rates */ + u32 total_success; /* total successful frames, any/all rates */ + u64 flush_timer; /* time staying in mode before new search */ + + u8 action_counter; /* # mode-switch actions tried */ + u8 is_green; + u8 is_dup; + enum ieee80211_band band; + + /* The following are bitmaps of rates; RATE_6M_MASK, etc. */ + u32 supp_rates; + u16 active_legacy_rate; + u16 active_siso_rate; + u16 active_mimo2_rate; + s8 max_rate_idx; /* Max rate set by user */ + u8 missed_rate_counter; + + struct il_link_quality_cmd lq; + struct il_scale_tbl_info lq_info[LQ_SIZE]; /* "active", "search" */ + struct il_traffic_load load[TID_MAX_LOAD_COUNT]; + u8 tx_agg_tid_en; +#ifdef CPTCFG_MAC80211_DEBUGFS + struct dentry *rs_sta_dbgfs_scale_table_file; + struct dentry *rs_sta_dbgfs_stats_table_file; + struct dentry *rs_sta_dbgfs_rate_scale_data_file; + struct dentry *rs_sta_dbgfs_tx_agg_tid_en_file; + u32 dbg_fixed_rate; +#endif + struct il_priv *drv; + + /* used to be in sta_info */ + int last_txrate_idx; + /* last tx rate_n_flags */ + u32 last_rate_n_flags; + /* packets destined for this STA are aggregated */ + u8 is_agg; +}; + +/* + * il_station_priv: Driver's ilate station information + * + * When mac80211 creates a station it reserves some space (hw->sta_data_size) + * in the structure for use by driver. This structure is places in that + * space. + * + * The common struct MUST be first because it is shared between + * 3945 and 4965! + */ +struct il_station_priv { + struct il_station_priv_common common; + struct il_lq_sta lq_sta; + atomic_t pending_frames; + bool client; + bool asleep; +}; + +static inline u8 +il4965_num_of_ant(u8 m) +{ + return !!(m & ANT_A) + !!(m & ANT_B) + !!(m & ANT_C); +} + +static inline u8 +il4965_first_antenna(u8 mask) +{ + if (mask & ANT_A) + return ANT_A; + if (mask & ANT_B) + return ANT_B; + return ANT_C; +} + +/** + * il3945_rate_scale_init - Initialize the rate scale table based on assoc info + * + * The specific throughput table used is based on the type of network + * the associated with, including A, B, G, and G w/ TGG protection + */ +extern void il3945_rate_scale_init(struct ieee80211_hw *hw, s32 sta_id); + +/* Initialize station's rate scaling information after adding station */ +extern void il4965_rs_rate_init(struct il_priv *il, struct ieee80211_sta *sta, + u8 sta_id); +extern void il3945_rs_rate_init(struct il_priv *il, struct ieee80211_sta *sta, + u8 sta_id); + +/** + * il_rate_control_register - Register the rate control algorithm callbacks + * + * Since the rate control algorithm is hardware specific, there is no need + * or reason to place it as a stand alone module. The driver can call + * il_rate_control_register in order to register the rate control callbacks + * with the mac80211 subsystem. This should be performed prior to calling + * ieee80211_register_hw + * + */ +extern int il4965_rate_control_register(void); +extern int il3945_rate_control_register(void); + +/** + * il_rate_control_unregister - Unregister the rate control callbacks + * + * This should be called after calling ieee80211_unregister_hw, but before + * the driver is unloaded. + */ +extern void il4965_rate_control_unregister(void); +extern void il3945_rate_control_unregister(void); + +extern int il_power_update_mode(struct il_priv *il, bool force); +extern void il_power_initialize(struct il_priv *il); + +extern u32 il_debug_level; + +#ifdef CPTCFG_IWLEGACY_DEBUG +/* + * il_get_debug_level: Return active debug level for device + * + * Using sysfs it is possible to set per device debug level. This debug + * level will be used if set, otherwise the global debug level which can be + * set via module parameter is used. + */ +static inline u32 +il_get_debug_level(struct il_priv *il) +{ + if (il->debug_level) + return il->debug_level; + else + return il_debug_level; +} +#else +static inline u32 +il_get_debug_level(struct il_priv *il) +{ + return il_debug_level; +} +#endif + +#define il_print_hex_error(il, p, len) \ +do { \ + print_hex_dump(KERN_ERR, "iwl data: ", \ + DUMP_PREFIX_OFFSET, 16, 1, p, len, 1); \ +} while (0) + +#ifdef CPTCFG_IWLEGACY_DEBUG +#define IL_DBG(level, fmt, args...) \ +do { \ + if (il_get_debug_level(il) & level) \ + dev_err(&il->hw->wiphy->dev, "%c %s " fmt, \ + in_interrupt() ? 'I' : 'U', __func__ , ##args); \ +} while (0) + +#define il_print_hex_dump(il, level, p, len) \ +do { \ + if (il_get_debug_level(il) & level) \ + print_hex_dump(KERN_DEBUG, "iwl data: ", \ + DUMP_PREFIX_OFFSET, 16, 1, p, len, 1); \ +} while (0) + +#else +#define IL_DBG(level, fmt, args...) +static inline void +il_print_hex_dump(struct il_priv *il, int level, const void *p, u32 len) +{ +} +#endif /* CPTCFG_IWLEGACY_DEBUG */ + +#ifdef CPTCFG_IWLEGACY_DEBUGFS +int il_dbgfs_register(struct il_priv *il, const char *name); +void il_dbgfs_unregister(struct il_priv *il); +#else +static inline int +il_dbgfs_register(struct il_priv *il, const char *name) +{ + return 0; +} + +static inline void +il_dbgfs_unregister(struct il_priv *il) +{ +} +#endif /* CPTCFG_IWLEGACY_DEBUGFS */ + +/* + * To use the debug system: + * + * If you are defining a new debug classification, simply add it to the #define + * list here in the form of + * + * #define IL_DL_xxxx VALUE + * + * where xxxx should be the name of the classification (for example, WEP). + * + * You then need to either add a IL_xxxx_DEBUG() macro definition for your + * classification, or use IL_DBG(IL_DL_xxxx, ...) whenever you want + * to send output to that classification. + * + * The active debug levels can be accessed via files + * + * /sys/module/iwl4965/parameters/debug + * /sys/module/iwl3945/parameters/debug + * /sys/class/net/wlan0/device/debug_level + * + * when CPTCFG_IWLEGACY_DEBUG=y. + */ + +/* 0x0000000F - 0x00000001 */ +#define IL_DL_INFO (1 << 0) +#define IL_DL_MAC80211 (1 << 1) +#define IL_DL_HCMD (1 << 2) +#define IL_DL_STATE (1 << 3) +/* 0x000000F0 - 0x00000010 */ +#define IL_DL_MACDUMP (1 << 4) +#define IL_DL_HCMD_DUMP (1 << 5) +#define IL_DL_EEPROM (1 << 6) +#define IL_DL_RADIO (1 << 7) +/* 0x00000F00 - 0x00000100 */ +#define IL_DL_POWER (1 << 8) +#define IL_DL_TEMP (1 << 9) +#define IL_DL_NOTIF (1 << 10) +#define IL_DL_SCAN (1 << 11) +/* 0x0000F000 - 0x00001000 */ +#define IL_DL_ASSOC (1 << 12) +#define IL_DL_DROP (1 << 13) +#define IL_DL_TXPOWER (1 << 14) +#define IL_DL_AP (1 << 15) +/* 0x000F0000 - 0x00010000 */ +#define IL_DL_FW (1 << 16) +#define IL_DL_RF_KILL (1 << 17) +#define IL_DL_FW_ERRORS (1 << 18) +#define IL_DL_LED (1 << 19) +/* 0x00F00000 - 0x00100000 */ +#define IL_DL_RATE (1 << 20) +#define IL_DL_CALIB (1 << 21) +#define IL_DL_WEP (1 << 22) +#define IL_DL_TX (1 << 23) +/* 0x0F000000 - 0x01000000 */ +#define IL_DL_RX (1 << 24) +#define IL_DL_ISR (1 << 25) +#define IL_DL_HT (1 << 26) +/* 0xF0000000 - 0x10000000 */ +#define IL_DL_11H (1 << 28) +#define IL_DL_STATS (1 << 29) +#define IL_DL_TX_REPLY (1 << 30) +#define IL_DL_QOS (1 << 31) + +#define D_INFO(f, a...) IL_DBG(IL_DL_INFO, f, ## a) +#define D_MAC80211(f, a...) IL_DBG(IL_DL_MAC80211, f, ## a) +#define D_MACDUMP(f, a...) IL_DBG(IL_DL_MACDUMP, f, ## a) +#define D_TEMP(f, a...) IL_DBG(IL_DL_TEMP, f, ## a) +#define D_SCAN(f, a...) IL_DBG(IL_DL_SCAN, f, ## a) +#define D_RX(f, a...) IL_DBG(IL_DL_RX, f, ## a) +#define D_TX(f, a...) IL_DBG(IL_DL_TX, f, ## a) +#define D_ISR(f, a...) IL_DBG(IL_DL_ISR, f, ## a) +#define D_LED(f, a...) IL_DBG(IL_DL_LED, f, ## a) +#define D_WEP(f, a...) IL_DBG(IL_DL_WEP, f, ## a) +#define D_HC(f, a...) IL_DBG(IL_DL_HCMD, f, ## a) +#define D_HC_DUMP(f, a...) IL_DBG(IL_DL_HCMD_DUMP, f, ## a) +#define D_EEPROM(f, a...) IL_DBG(IL_DL_EEPROM, f, ## a) +#define D_CALIB(f, a...) IL_DBG(IL_DL_CALIB, f, ## a) +#define D_FW(f, a...) IL_DBG(IL_DL_FW, f, ## a) +#define D_RF_KILL(f, a...) IL_DBG(IL_DL_RF_KILL, f, ## a) +#define D_DROP(f, a...) IL_DBG(IL_DL_DROP, f, ## a) +#define D_AP(f, a...) IL_DBG(IL_DL_AP, f, ## a) +#define D_TXPOWER(f, a...) IL_DBG(IL_DL_TXPOWER, f, ## a) +#define D_RATE(f, a...) IL_DBG(IL_DL_RATE, f, ## a) +#define D_NOTIF(f, a...) IL_DBG(IL_DL_NOTIF, f, ## a) +#define D_ASSOC(f, a...) IL_DBG(IL_DL_ASSOC, f, ## a) +#define D_HT(f, a...) IL_DBG(IL_DL_HT, f, ## a) +#define D_STATS(f, a...) IL_DBG(IL_DL_STATS, f, ## a) +#define D_TX_REPLY(f, a...) IL_DBG(IL_DL_TX_REPLY, f, ## a) +#define D_QOS(f, a...) IL_DBG(IL_DL_QOS, f, ## a) +#define D_RADIO(f, a...) IL_DBG(IL_DL_RADIO, f, ## a) +#define D_POWER(f, a...) IL_DBG(IL_DL_POWER, f, ## a) +#define D_11H(f, a...) IL_DBG(IL_DL_11H, f, ## a) + +#endif /* __il_core_h__ */ diff --git a/drivers/net/wireless/iwlegacy/csr.h b/drivers/net/wireless/iwlegacy/csr.h new file mode 100644 index 0000000..9138e15 --- /dev/null +++ b/drivers/net/wireless/iwlegacy/csr.h @@ -0,0 +1,419 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2005 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2011 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#ifndef __il_csr_h__ +#define __il_csr_h__ +/* + * CSR (control and status registers) + * + * CSR registers are mapped directly into PCI bus space, and are accessible + * whenever platform supplies power to device, even when device is in + * low power states due to driver-invoked device resets + * (e.g. CSR_RESET_REG_FLAG_SW_RESET) or uCode-driven power-saving modes. + * + * Use _il_wr() and _il_rd() family to access these registers; + * these provide simple PCI bus access, without waking up the MAC. + * Do not use il_wr() family for these registers; + * no need to "grab nic access" via CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ. + * The MAC (uCode processor, etc.) does not need to be powered up for accessing + * the CSR registers. + * + * NOTE: Device does need to be awake in order to read this memory + * via CSR_EEPROM register + */ +#define CSR_BASE (0x000) + +#define CSR_HW_IF_CONFIG_REG (CSR_BASE+0x000) /* hardware interface config */ +#define CSR_INT_COALESCING (CSR_BASE+0x004) /* accum ints, 32-usec units */ +#define CSR_INT (CSR_BASE+0x008) /* host interrupt status/ack */ +#define CSR_INT_MASK (CSR_BASE+0x00c) /* host interrupt enable */ +#define CSR_FH_INT_STATUS (CSR_BASE+0x010) /* busmaster int status/ack */ +#define CSR_GPIO_IN (CSR_BASE+0x018) /* read external chip pins */ +#define CSR_RESET (CSR_BASE+0x020) /* busmaster enable, NMI, etc */ +#define CSR_GP_CNTRL (CSR_BASE+0x024) + +/* 2nd byte of CSR_INT_COALESCING, not accessible via _il_wr()! */ +#define CSR_INT_PERIODIC_REG (CSR_BASE+0x005) + +/* + * Hardware revision info + * Bit fields: + * 31-8: Reserved + * 7-4: Type of device: see CSR_HW_REV_TYPE_xxx definitions + * 3-2: Revision step: 0 = A, 1 = B, 2 = C, 3 = D + * 1-0: "Dash" (-) value, as in A-1, etc. + * + * NOTE: Revision step affects calculation of CCK txpower for 4965. + * NOTE: See also CSR_HW_REV_WA_REG (work-around for bug in 4965). + */ +#define CSR_HW_REV (CSR_BASE+0x028) + +/* + * EEPROM memory reads + * + * NOTE: Device must be awake, initialized via apm_ops.init(), + * in order to read. + */ +#define CSR_EEPROM_REG (CSR_BASE+0x02c) +#define CSR_EEPROM_GP (CSR_BASE+0x030) + +#define CSR_GIO_REG (CSR_BASE+0x03C) +#define CSR_GP_UCODE_REG (CSR_BASE+0x048) +#define CSR_GP_DRIVER_REG (CSR_BASE+0x050) + +/* + * UCODE-DRIVER GP (general purpose) mailbox registers. + * SET/CLR registers set/clear bit(s) if "1" is written. + */ +#define CSR_UCODE_DRV_GP1 (CSR_BASE+0x054) +#define CSR_UCODE_DRV_GP1_SET (CSR_BASE+0x058) +#define CSR_UCODE_DRV_GP1_CLR (CSR_BASE+0x05c) +#define CSR_UCODE_DRV_GP2 (CSR_BASE+0x060) + +#define CSR_LED_REG (CSR_BASE+0x094) +#define CSR_DRAM_INT_TBL_REG (CSR_BASE+0x0A0) + +/* GIO Chicken Bits (PCI Express bus link power management) */ +#define CSR_GIO_CHICKEN_BITS (CSR_BASE+0x100) + +/* Analog phase-lock-loop configuration */ +#define CSR_ANA_PLL_CFG (CSR_BASE+0x20c) + +/* + * CSR Hardware Revision Workaround Register. Indicates hardware rev; + * "step" determines CCK backoff for txpower calculation. Used for 4965 only. + * See also CSR_HW_REV register. + * Bit fields: + * 3-2: 0 = A, 1 = B, 2 = C, 3 = D step + * 1-0: "Dash" (-) value, as in C-1, etc. + */ +#define CSR_HW_REV_WA_REG (CSR_BASE+0x22C) + +#define CSR_DBG_HPET_MEM_REG (CSR_BASE+0x240) +#define CSR_DBG_LINK_PWR_MGMT_REG (CSR_BASE+0x250) + +/* Bits for CSR_HW_IF_CONFIG_REG */ +#define CSR49_HW_IF_CONFIG_REG_BIT_4965_R (0x00000010) +#define CSR_HW_IF_CONFIG_REG_MSK_BOARD_VER (0x00000C00) +#define CSR_HW_IF_CONFIG_REG_BIT_MAC_SI (0x00000100) +#define CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI (0x00000200) + +#define CSR39_HW_IF_CONFIG_REG_BIT_3945_MB (0x00000100) +#define CSR39_HW_IF_CONFIG_REG_BIT_3945_MM (0x00000200) +#define CSR39_HW_IF_CONFIG_REG_BIT_SKU_MRC (0x00000400) +#define CSR39_HW_IF_CONFIG_REG_BIT_BOARD_TYPE (0x00000800) +#define CSR39_HW_IF_CONFIG_REG_BITS_SILICON_TYPE_A (0x00000000) +#define CSR39_HW_IF_CONFIG_REG_BITS_SILICON_TYPE_B (0x00001000) + +#define CSR_HW_IF_CONFIG_REG_BIT_HAP_WAKE_L1A (0x00080000) +#define CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM (0x00200000) +#define CSR_HW_IF_CONFIG_REG_BIT_NIC_READY (0x00400000) /* PCI_OWN_SEM */ +#define CSR_HW_IF_CONFIG_REG_BIT_NIC_PREPARE_DONE (0x02000000) /* ME_OWN */ +#define CSR_HW_IF_CONFIG_REG_PREPARE (0x08000000) /* WAKE_ME */ + +#define CSR_INT_PERIODIC_DIS (0x00) /* disable periodic int */ +#define CSR_INT_PERIODIC_ENA (0xFF) /* 255*32 usec ~ 8 msec */ + +/* interrupt flags in INTA, set by uCode or hardware (e.g. dma), + * acknowledged (reset) by host writing "1" to flagged bits. */ +#define CSR_INT_BIT_FH_RX (1 << 31) /* Rx DMA, cmd responses, FH_INT[17:16] */ +#define CSR_INT_BIT_HW_ERR (1 << 29) /* DMA hardware error FH_INT[31] */ +#define CSR_INT_BIT_RX_PERIODIC (1 << 28) /* Rx periodic */ +#define CSR_INT_BIT_FH_TX (1 << 27) /* Tx DMA FH_INT[1:0] */ +#define CSR_INT_BIT_SCD (1 << 26) /* TXQ pointer advanced */ +#define CSR_INT_BIT_SW_ERR (1 << 25) /* uCode error */ +#define CSR_INT_BIT_RF_KILL (1 << 7) /* HW RFKILL switch GP_CNTRL[27] toggled */ +#define CSR_INT_BIT_CT_KILL (1 << 6) /* Critical temp (chip too hot) rfkill */ +#define CSR_INT_BIT_SW_RX (1 << 3) /* Rx, command responses, 3945 */ +#define CSR_INT_BIT_WAKEUP (1 << 1) /* NIC controller waking up (pwr mgmt) */ +#define CSR_INT_BIT_ALIVE (1 << 0) /* uCode interrupts once it initializes */ + +#define CSR_INI_SET_MASK (CSR_INT_BIT_FH_RX | \ + CSR_INT_BIT_HW_ERR | \ + CSR_INT_BIT_FH_TX | \ + CSR_INT_BIT_SW_ERR | \ + CSR_INT_BIT_RF_KILL | \ + CSR_INT_BIT_SW_RX | \ + CSR_INT_BIT_WAKEUP | \ + CSR_INT_BIT_ALIVE) + +/* interrupt flags in FH (flow handler) (PCI busmaster DMA) */ +#define CSR_FH_INT_BIT_ERR (1 << 31) /* Error */ +#define CSR_FH_INT_BIT_HI_PRIOR (1 << 30) /* High priority Rx, bypass coalescing */ +#define CSR39_FH_INT_BIT_RX_CHNL2 (1 << 18) /* Rx channel 2 (3945 only) */ +#define CSR_FH_INT_BIT_RX_CHNL1 (1 << 17) /* Rx channel 1 */ +#define CSR_FH_INT_BIT_RX_CHNL0 (1 << 16) /* Rx channel 0 */ +#define CSR39_FH_INT_BIT_TX_CHNL6 (1 << 6) /* Tx channel 6 (3945 only) */ +#define CSR_FH_INT_BIT_TX_CHNL1 (1 << 1) /* Tx channel 1 */ +#define CSR_FH_INT_BIT_TX_CHNL0 (1 << 0) /* Tx channel 0 */ + +#define CSR39_FH_INT_RX_MASK (CSR_FH_INT_BIT_HI_PRIOR | \ + CSR39_FH_INT_BIT_RX_CHNL2 | \ + CSR_FH_INT_BIT_RX_CHNL1 | \ + CSR_FH_INT_BIT_RX_CHNL0) + +#define CSR39_FH_INT_TX_MASK (CSR39_FH_INT_BIT_TX_CHNL6 | \ + CSR_FH_INT_BIT_TX_CHNL1 | \ + CSR_FH_INT_BIT_TX_CHNL0) + +#define CSR49_FH_INT_RX_MASK (CSR_FH_INT_BIT_HI_PRIOR | \ + CSR_FH_INT_BIT_RX_CHNL1 | \ + CSR_FH_INT_BIT_RX_CHNL0) + +#define CSR49_FH_INT_TX_MASK (CSR_FH_INT_BIT_TX_CHNL1 | \ + CSR_FH_INT_BIT_TX_CHNL0) + +/* GPIO */ +#define CSR_GPIO_IN_BIT_AUX_POWER (0x00000200) +#define CSR_GPIO_IN_VAL_VAUX_PWR_SRC (0x00000000) +#define CSR_GPIO_IN_VAL_VMAIN_PWR_SRC (0x00000200) + +/* RESET */ +#define CSR_RESET_REG_FLAG_NEVO_RESET (0x00000001) +#define CSR_RESET_REG_FLAG_FORCE_NMI (0x00000002) +#define CSR_RESET_REG_FLAG_SW_RESET (0x00000080) +#define CSR_RESET_REG_FLAG_MASTER_DISABLED (0x00000100) +#define CSR_RESET_REG_FLAG_STOP_MASTER (0x00000200) +#define CSR_RESET_LINK_PWR_MGMT_DISABLED (0x80000000) + +/* + * GP (general purpose) CONTROL REGISTER + * Bit fields: + * 27: HW_RF_KILL_SW + * Indicates state of (platform's) hardware RF-Kill switch + * 26-24: POWER_SAVE_TYPE + * Indicates current power-saving mode: + * 000 -- No power saving + * 001 -- MAC power-down + * 010 -- PHY (radio) power-down + * 011 -- Error + * 9-6: SYS_CONFIG + * Indicates current system configuration, reflecting pins on chip + * as forced high/low by device circuit board. + * 4: GOING_TO_SLEEP + * Indicates MAC is entering a power-saving sleep power-down. + * Not a good time to access device-internal resources. + * 3: MAC_ACCESS_REQ + * Host sets this to request and maintain MAC wakeup, to allow host + * access to device-internal resources. Host must wait for + * MAC_CLOCK_READY (and !GOING_TO_SLEEP) before accessing non-CSR + * device registers. + * 2: INIT_DONE + * Host sets this to put device into fully operational D0 power mode. + * Host resets this after SW_RESET to put device into low power mode. + * 0: MAC_CLOCK_READY + * Indicates MAC (ucode processor, etc.) is powered up and can run. + * Internal resources are accessible. + * NOTE: This does not indicate that the processor is actually running. + * NOTE: This does not indicate that 4965 or 3945 has completed + * init or post-power-down restore of internal SRAM memory. + * Use CSR_UCODE_DRV_GP1_BIT_MAC_SLEEP as indication that + * SRAM is restored and uCode is in normal operation mode. + * Later devices (5xxx/6xxx/1xxx) use non-volatile SRAM, and + * do not need to save/restore it. + * NOTE: After device reset, this bit remains "0" until host sets + * INIT_DONE + */ +#define CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY (0x00000001) +#define CSR_GP_CNTRL_REG_FLAG_INIT_DONE (0x00000004) +#define CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ (0x00000008) +#define CSR_GP_CNTRL_REG_FLAG_GOING_TO_SLEEP (0x00000010) + +#define CSR_GP_CNTRL_REG_VAL_MAC_ACCESS_EN (0x00000001) + +#define CSR_GP_CNTRL_REG_MSK_POWER_SAVE_TYPE (0x07000000) +#define CSR_GP_CNTRL_REG_FLAG_MAC_POWER_SAVE (0x04000000) +#define CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW (0x08000000) + +/* EEPROM REG */ +#define CSR_EEPROM_REG_READ_VALID_MSK (0x00000001) +#define CSR_EEPROM_REG_BIT_CMD (0x00000002) +#define CSR_EEPROM_REG_MSK_ADDR (0x0000FFFC) +#define CSR_EEPROM_REG_MSK_DATA (0xFFFF0000) + +/* EEPROM GP */ +#define CSR_EEPROM_GP_VALID_MSK (0x00000007) /* signature */ +#define CSR_EEPROM_GP_IF_OWNER_MSK (0x00000180) +#define CSR_EEPROM_GP_GOOD_SIG_EEP_LESS_THAN_4K (0x00000002) +#define CSR_EEPROM_GP_GOOD_SIG_EEP_MORE_THAN_4K (0x00000004) + +/* GP REG */ +#define CSR_GP_REG_POWER_SAVE_STATUS_MSK (0x03000000) /* bit 24/25 */ +#define CSR_GP_REG_NO_POWER_SAVE (0x00000000) +#define CSR_GP_REG_MAC_POWER_SAVE (0x01000000) +#define CSR_GP_REG_PHY_POWER_SAVE (0x02000000) +#define CSR_GP_REG_POWER_SAVE_ERROR (0x03000000) + +/* CSR GIO */ +#define CSR_GIO_REG_VAL_L0S_ENABLED (0x00000002) + +/* + * UCODE-DRIVER GP (general purpose) mailbox register 1 + * Host driver and uCode write and/or read this register to communicate with + * each other. + * Bit fields: + * 4: UCODE_DISABLE + * Host sets this to request permanent halt of uCode, same as + * sending CARD_STATE command with "halt" bit set. + * 3: CT_KILL_EXIT + * Host sets this to request exit from CT_KILL state, i.e. host thinks + * device temperature is low enough to continue normal operation. + * 2: CMD_BLOCKED + * Host sets this during RF KILL power-down sequence (HW, SW, CT KILL) + * to release uCode to clear all Tx and command queues, enter + * unassociated mode, and power down. + * NOTE: Some devices also use HBUS_TARG_MBX_C register for this bit. + * 1: SW_BIT_RFKILL + * Host sets this when issuing CARD_STATE command to request + * device sleep. + * 0: MAC_SLEEP + * uCode sets this when preparing a power-saving power-down. + * uCode resets this when power-up is complete and SRAM is sane. + * NOTE: 3945/4965 saves internal SRAM data to host when powering down, + * and must restore this data after powering back up. + * MAC_SLEEP is the best indication that restore is complete. + * Later devices (5xxx/6xxx/1xxx) use non-volatile SRAM, and + * do not need to save/restore it. + */ +#define CSR_UCODE_DRV_GP1_BIT_MAC_SLEEP (0x00000001) +#define CSR_UCODE_SW_BIT_RFKILL (0x00000002) +#define CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED (0x00000004) +#define CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT (0x00000008) + +/* GIO Chicken Bits (PCI Express bus link power management) */ +#define CSR_GIO_CHICKEN_BITS_REG_BIT_L1A_NO_L0S_RX (0x00800000) +#define CSR_GIO_CHICKEN_BITS_REG_BIT_DIS_L0S_EXIT_TIMER (0x20000000) + +/* LED */ +#define CSR_LED_BSM_CTRL_MSK (0xFFFFFFDF) +#define CSR_LED_REG_TRUN_ON (0x78) +#define CSR_LED_REG_TRUN_OFF (0x38) + +/* ANA_PLL */ +#define CSR39_ANA_PLL_CFG_VAL (0x01000000) + +/* HPET MEM debug */ +#define CSR_DBG_HPET_MEM_REG_VAL (0xFFFF0000) + +/* DRAM INT TBL */ +#define CSR_DRAM_INT_TBL_ENABLE (1 << 31) +#define CSR_DRAM_INIT_TBL_WRAP_CHECK (1 << 27) + +/* + * HBUS (Host-side Bus) + * + * HBUS registers are mapped directly into PCI bus space, but are used + * to indirectly access device's internal memory or registers that + * may be powered-down. + * + * Use il_wr()/il_rd() family + * for these registers; + * host must "grab nic access" via CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ + * to make sure the MAC (uCode processor, etc.) is powered up for accessing + * internal resources. + * + * Do not use _il_wr()/_il_rd() family to access these registers; + * these provide only simple PCI bus access, without waking up the MAC. + */ +#define HBUS_BASE (0x400) + +/* + * Registers for accessing device's internal SRAM memory (e.g. SCD SRAM + * structures, error log, event log, verifying uCode load). + * First write to address register, then read from or write to data register + * to complete the job. Once the address register is set up, accesses to + * data registers auto-increment the address by one dword. + * Bit usage for address registers (read or write): + * 0-31: memory address within device + */ +#define HBUS_TARG_MEM_RADDR (HBUS_BASE+0x00c) +#define HBUS_TARG_MEM_WADDR (HBUS_BASE+0x010) +#define HBUS_TARG_MEM_WDAT (HBUS_BASE+0x018) +#define HBUS_TARG_MEM_RDAT (HBUS_BASE+0x01c) + +/* Mailbox C, used as workaround alternative to CSR_UCODE_DRV_GP1 mailbox */ +#define HBUS_TARG_MBX_C (HBUS_BASE+0x030) +#define HBUS_TARG_MBX_C_REG_BIT_CMD_BLOCKED (0x00000004) + +/* + * Registers for accessing device's internal peripheral registers + * (e.g. SCD, BSM, etc.). First write to address register, + * then read from or write to data register to complete the job. + * Bit usage for address registers (read or write): + * 0-15: register address (offset) within device + * 24-25: (# bytes - 1) to read or write (e.g. 3 for dword) + */ +#define HBUS_TARG_PRPH_WADDR (HBUS_BASE+0x044) +#define HBUS_TARG_PRPH_RADDR (HBUS_BASE+0x048) +#define HBUS_TARG_PRPH_WDAT (HBUS_BASE+0x04c) +#define HBUS_TARG_PRPH_RDAT (HBUS_BASE+0x050) + +/* + * Per-Tx-queue write pointer (idx, really!) + * Indicates idx to next TFD that driver will fill (1 past latest filled). + * Bit usage: + * 0-7: queue write idx + * 11-8: queue selector + */ +#define HBUS_TARG_WRPTR (HBUS_BASE+0x060) + +#endif /* !__il_csr_h__ */ diff --git a/drivers/net/wireless/iwlegacy/debug.c b/drivers/net/wireless/iwlegacy/debug.c new file mode 100644 index 0000000..1ebb4fc --- /dev/null +++ b/drivers/net/wireless/iwlegacy/debug.c @@ -0,0 +1,1430 @@ +/****************************************************************************** + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + *****************************************************************************/ +#include +#include +#include + +#include "common.h" + +void +il_clear_traffic_stats(struct il_priv *il) +{ + memset(&il->tx_stats, 0, sizeof(struct traffic_stats)); + memset(&il->rx_stats, 0, sizeof(struct traffic_stats)); +} + +/* + * il_update_stats function record all the MGMT, CTRL and DATA pkt for + * both TX and Rx . Use debugfs to display the rx/rx_stats + */ +void +il_update_stats(struct il_priv *il, bool is_tx, __le16 fc, u16 len) +{ + struct traffic_stats *stats; + + if (is_tx) + stats = &il->tx_stats; + else + stats = &il->rx_stats; + + if (ieee80211_is_mgmt(fc)) { + switch (fc & cpu_to_le16(IEEE80211_FCTL_STYPE)) { + case cpu_to_le16(IEEE80211_STYPE_ASSOC_REQ): + stats->mgmt[MANAGEMENT_ASSOC_REQ]++; + break; + case cpu_to_le16(IEEE80211_STYPE_ASSOC_RESP): + stats->mgmt[MANAGEMENT_ASSOC_RESP]++; + break; + case cpu_to_le16(IEEE80211_STYPE_REASSOC_REQ): + stats->mgmt[MANAGEMENT_REASSOC_REQ]++; + break; + case cpu_to_le16(IEEE80211_STYPE_REASSOC_RESP): + stats->mgmt[MANAGEMENT_REASSOC_RESP]++; + break; + case cpu_to_le16(IEEE80211_STYPE_PROBE_REQ): + stats->mgmt[MANAGEMENT_PROBE_REQ]++; + break; + case cpu_to_le16(IEEE80211_STYPE_PROBE_RESP): + stats->mgmt[MANAGEMENT_PROBE_RESP]++; + break; + case cpu_to_le16(IEEE80211_STYPE_BEACON): + stats->mgmt[MANAGEMENT_BEACON]++; + break; + case cpu_to_le16(IEEE80211_STYPE_ATIM): + stats->mgmt[MANAGEMENT_ATIM]++; + break; + case cpu_to_le16(IEEE80211_STYPE_DISASSOC): + stats->mgmt[MANAGEMENT_DISASSOC]++; + break; + case cpu_to_le16(IEEE80211_STYPE_AUTH): + stats->mgmt[MANAGEMENT_AUTH]++; + break; + case cpu_to_le16(IEEE80211_STYPE_DEAUTH): + stats->mgmt[MANAGEMENT_DEAUTH]++; + break; + case cpu_to_le16(IEEE80211_STYPE_ACTION): + stats->mgmt[MANAGEMENT_ACTION]++; + break; + } + } else if (ieee80211_is_ctl(fc)) { + switch (fc & cpu_to_le16(IEEE80211_FCTL_STYPE)) { + case cpu_to_le16(IEEE80211_STYPE_BACK_REQ): + stats->ctrl[CONTROL_BACK_REQ]++; + break; + case cpu_to_le16(IEEE80211_STYPE_BACK): + stats->ctrl[CONTROL_BACK]++; + break; + case cpu_to_le16(IEEE80211_STYPE_PSPOLL): + stats->ctrl[CONTROL_PSPOLL]++; + break; + case cpu_to_le16(IEEE80211_STYPE_RTS): + stats->ctrl[CONTROL_RTS]++; + break; + case cpu_to_le16(IEEE80211_STYPE_CTS): + stats->ctrl[CONTROL_CTS]++; + break; + case cpu_to_le16(IEEE80211_STYPE_ACK): + stats->ctrl[CONTROL_ACK]++; + break; + case cpu_to_le16(IEEE80211_STYPE_CFEND): + stats->ctrl[CONTROL_CFEND]++; + break; + case cpu_to_le16(IEEE80211_STYPE_CFENDACK): + stats->ctrl[CONTROL_CFENDACK]++; + break; + } + } else { + /* data */ + stats->data_cnt++; + stats->data_bytes += len; + } +} +EXPORT_SYMBOL(il_update_stats); + +/* create and remove of files */ +#define DEBUGFS_ADD_FILE(name, parent, mode) do { \ + if (!debugfs_create_file(#name, mode, parent, il, \ + &il_dbgfs_##name##_ops)) \ + goto err; \ +} while (0) + +#define DEBUGFS_ADD_BOOL(name, parent, ptr) do { \ + struct dentry *__tmp; \ + __tmp = debugfs_create_bool(#name, S_IWUSR | S_IRUSR, \ + parent, ptr); \ + if (IS_ERR(__tmp) || !__tmp) \ + goto err; \ +} while (0) + +#define DEBUGFS_ADD_X32(name, parent, ptr) do { \ + struct dentry *__tmp; \ + __tmp = debugfs_create_x32(#name, S_IWUSR | S_IRUSR, \ + parent, ptr); \ + if (IS_ERR(__tmp) || !__tmp) \ + goto err; \ +} while (0) + +/* file operation */ +#define DEBUGFS_READ_FUNC(name) \ +static ssize_t il_dbgfs_##name##_read(struct file *file, \ + char __user *user_buf, \ + size_t count, loff_t *ppos); + +#define DEBUGFS_WRITE_FUNC(name) \ +static ssize_t il_dbgfs_##name##_write(struct file *file, \ + const char __user *user_buf, \ + size_t count, loff_t *ppos); + + +#define DEBUGFS_READ_FILE_OPS(name) \ + DEBUGFS_READ_FUNC(name); \ +static const struct file_operations il_dbgfs_##name##_ops = { \ + .read = il_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_WRITE_FILE_OPS(name) \ + DEBUGFS_WRITE_FUNC(name); \ +static const struct file_operations il_dbgfs_##name##_ops = { \ + .write = il_dbgfs_##name##_write, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_READ_WRITE_FILE_OPS(name) \ + DEBUGFS_READ_FUNC(name); \ + DEBUGFS_WRITE_FUNC(name); \ +static const struct file_operations il_dbgfs_##name##_ops = { \ + .write = il_dbgfs_##name##_write, \ + .read = il_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +static const char * +il_get_mgmt_string(int cmd) +{ + switch (cmd) { + IL_CMD(MANAGEMENT_ASSOC_REQ); + IL_CMD(MANAGEMENT_ASSOC_RESP); + IL_CMD(MANAGEMENT_REASSOC_REQ); + IL_CMD(MANAGEMENT_REASSOC_RESP); + IL_CMD(MANAGEMENT_PROBE_REQ); + IL_CMD(MANAGEMENT_PROBE_RESP); + IL_CMD(MANAGEMENT_BEACON); + IL_CMD(MANAGEMENT_ATIM); + IL_CMD(MANAGEMENT_DISASSOC); + IL_CMD(MANAGEMENT_AUTH); + IL_CMD(MANAGEMENT_DEAUTH); + IL_CMD(MANAGEMENT_ACTION); + default: + return "UNKNOWN"; + + } +} + +static const char * +il_get_ctrl_string(int cmd) +{ + switch (cmd) { + IL_CMD(CONTROL_BACK_REQ); + IL_CMD(CONTROL_BACK); + IL_CMD(CONTROL_PSPOLL); + IL_CMD(CONTROL_RTS); + IL_CMD(CONTROL_CTS); + IL_CMD(CONTROL_ACK); + IL_CMD(CONTROL_CFEND); + IL_CMD(CONTROL_CFENDACK); + default: + return "UNKNOWN"; + + } +} + +static ssize_t +il_dbgfs_tx_stats_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + + struct il_priv *il = file->private_data; + char *buf; + int pos = 0; + + int cnt; + ssize_t ret; + const size_t bufsz = + 100 + sizeof(char) * 50 * (MANAGEMENT_MAX + CONTROL_MAX); + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + pos += scnprintf(buf + pos, bufsz - pos, "Management:\n"); + for (cnt = 0; cnt < MANAGEMENT_MAX; cnt++) { + pos += + scnprintf(buf + pos, bufsz - pos, "\t%25s\t\t: %u\n", + il_get_mgmt_string(cnt), il->tx_stats.mgmt[cnt]); + } + pos += scnprintf(buf + pos, bufsz - pos, "Control\n"); + for (cnt = 0; cnt < CONTROL_MAX; cnt++) { + pos += + scnprintf(buf + pos, bufsz - pos, "\t%25s\t\t: %u\n", + il_get_ctrl_string(cnt), il->tx_stats.ctrl[cnt]); + } + pos += scnprintf(buf + pos, bufsz - pos, "Data:\n"); + pos += + scnprintf(buf + pos, bufsz - pos, "\tcnt: %u\n", + il->tx_stats.data_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, "\tbytes: %llu\n", + il->tx_stats.data_bytes); + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t +il_dbgfs_clear_traffic_stats_write(struct file *file, + const char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct il_priv *il = file->private_data; + u32 clear_flag; + char buf[8]; + int buf_size; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%x", &clear_flag) != 1) + return -EFAULT; + il_clear_traffic_stats(il); + + return count; +} + +static ssize_t +il_dbgfs_rx_stats_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + + struct il_priv *il = file->private_data; + char *buf; + int pos = 0; + int cnt; + ssize_t ret; + const size_t bufsz = + 100 + sizeof(char) * 50 * (MANAGEMENT_MAX + CONTROL_MAX); + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, "Management:\n"); + for (cnt = 0; cnt < MANAGEMENT_MAX; cnt++) { + pos += + scnprintf(buf + pos, bufsz - pos, "\t%25s\t\t: %u\n", + il_get_mgmt_string(cnt), il->rx_stats.mgmt[cnt]); + } + pos += scnprintf(buf + pos, bufsz - pos, "Control:\n"); + for (cnt = 0; cnt < CONTROL_MAX; cnt++) { + pos += + scnprintf(buf + pos, bufsz - pos, "\t%25s\t\t: %u\n", + il_get_ctrl_string(cnt), il->rx_stats.ctrl[cnt]); + } + pos += scnprintf(buf + pos, bufsz - pos, "Data:\n"); + pos += + scnprintf(buf + pos, bufsz - pos, "\tcnt: %u\n", + il->rx_stats.data_cnt); + pos += + scnprintf(buf + pos, bufsz - pos, "\tbytes: %llu\n", + il->rx_stats.data_bytes); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +#define BYTE1_MASK 0x000000ff; +#define BYTE2_MASK 0x0000ffff; +#define BYTE3_MASK 0x00ffffff; +static ssize_t +il_dbgfs_sram_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + u32 val; + char *buf; + ssize_t ret; + int i; + int pos = 0; + struct il_priv *il = file->private_data; + size_t bufsz; + + /* default is to dump the entire data segment */ + if (!il->dbgfs_sram_offset && !il->dbgfs_sram_len) { + il->dbgfs_sram_offset = 0x800000; + if (il->ucode_type == UCODE_INIT) + il->dbgfs_sram_len = il->ucode_init_data.len; + else + il->dbgfs_sram_len = il->ucode_data.len; + } + bufsz = 30 + il->dbgfs_sram_len * sizeof(char) * 10; + buf = kmalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + pos += + scnprintf(buf + pos, bufsz - pos, "sram_len: 0x%x\n", + il->dbgfs_sram_len); + pos += + scnprintf(buf + pos, bufsz - pos, "sram_offset: 0x%x\n", + il->dbgfs_sram_offset); + for (i = il->dbgfs_sram_len; i > 0; i -= 4) { + val = + il_read_targ_mem(il, + il->dbgfs_sram_offset + + il->dbgfs_sram_len - i); + if (i < 4) { + switch (i) { + case 1: + val &= BYTE1_MASK; + break; + case 2: + val &= BYTE2_MASK; + break; + case 3: + val &= BYTE3_MASK; + break; + } + } + if (!(i % 16)) + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + pos += scnprintf(buf + pos, bufsz - pos, "0x%08x ", val); + } + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t +il_dbgfs_sram_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct il_priv *il = file->private_data; + char buf[64]; + int buf_size; + u32 offset, len; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (sscanf(buf, "%x,%x", &offset, &len) == 2) { + il->dbgfs_sram_offset = offset; + il->dbgfs_sram_len = len; + } else { + il->dbgfs_sram_offset = 0; + il->dbgfs_sram_len = 0; + } + + return count; +} + +static ssize_t +il_dbgfs_stations_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct il_priv *il = file->private_data; + struct il_station_entry *station; + int max_sta = il->hw_params.max_stations; + char *buf; + int i, j, pos = 0; + ssize_t ret; + /* Add 30 for initial string */ + const size_t bufsz = 30 + sizeof(char) * 500 * (il->num_stations); + + buf = kmalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos += + scnprintf(buf + pos, bufsz - pos, "num of stations: %d\n\n", + il->num_stations); + + for (i = 0; i < max_sta; i++) { + station = &il->stations[i]; + if (!station->used) + continue; + pos += + scnprintf(buf + pos, bufsz - pos, + "station %d - addr: %pM, flags: %#x\n", i, + station->sta.sta.addr, + station->sta.station_flags_msk); + pos += + scnprintf(buf + pos, bufsz - pos, + "TID\tseq_num\ttxq_id\tframes\ttfds\t"); + pos += + scnprintf(buf + pos, bufsz - pos, + "start_idx\tbitmap\t\t\trate_n_flags\n"); + + for (j = 0; j < MAX_TID_COUNT; j++) { + pos += + scnprintf(buf + pos, bufsz - pos, + "%d:\t%#x\t%#x\t%u\t%u\t%u\t\t%#.16llx\t%#x", + j, station->tid[j].seq_number, + station->tid[j].agg.txq_id, + station->tid[j].agg.frame_count, + station->tid[j].tfds_in_queue, + station->tid[j].agg.start_idx, + station->tid[j].agg.bitmap, + station->tid[j].agg.rate_n_flags); + + if (station->tid[j].agg.wait_for_ba) + pos += + scnprintf(buf + pos, bufsz - pos, + " - waitforba"); + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + } + + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t +il_dbgfs_nvm_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + ssize_t ret; + struct il_priv *il = file->private_data; + int pos = 0, ofs = 0, buf_size = 0; + const u8 *ptr; + char *buf; + u16 eeprom_ver; + size_t eeprom_len = il->cfg->eeprom_size; + buf_size = 4 * eeprom_len + 256; + + if (eeprom_len % 16) { + IL_ERR("NVM size is not multiple of 16.\n"); + return -ENODATA; + } + + ptr = il->eeprom; + if (!ptr) { + IL_ERR("Invalid EEPROM memory\n"); + return -ENOMEM; + } + + /* 4 characters for byte 0xYY */ + buf = kzalloc(buf_size, GFP_KERNEL); + if (!buf) { + IL_ERR("Can not allocate Buffer\n"); + return -ENOMEM; + } + eeprom_ver = il_eeprom_query16(il, EEPROM_VERSION); + pos += + scnprintf(buf + pos, buf_size - pos, "EEPROM " "version: 0x%x\n", + eeprom_ver); + for (ofs = 0; ofs < eeprom_len; ofs += 16) { + pos += scnprintf(buf + pos, buf_size - pos, "0x%.4x ", ofs); + hex_dump_to_buffer(ptr + ofs, 16, 16, 2, buf + pos, + buf_size - pos, 0); + pos += strlen(buf + pos); + if (buf_size - pos > 0) + buf[pos++] = '\n'; + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t +il_dbgfs_channels_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct il_priv *il = file->private_data; + struct ieee80211_channel *channels = NULL; + const struct ieee80211_supported_band *supp_band = NULL; + int pos = 0, i, bufsz = PAGE_SIZE; + char *buf; + ssize_t ret; + + if (!test_bit(S_GEO_CONFIGURED, &il->status)) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) { + IL_ERR("Can not allocate Buffer\n"); + return -ENOMEM; + } + + supp_band = il_get_hw_mode(il, IEEE80211_BAND_2GHZ); + if (supp_band) { + channels = supp_band->channels; + + pos += + scnprintf(buf + pos, bufsz - pos, + "Displaying %d channels in 2.4GHz band 802.11bg):\n", + supp_band->n_channels); + + for (i = 0; i < supp_band->n_channels; i++) + pos += + scnprintf(buf + pos, bufsz - pos, + "%d: %ddBm: BSS%s%s, %s.\n", + channels[i].hw_value, + channels[i].max_power, + channels[i]. + flags & IEEE80211_CHAN_RADAR ? + " (IEEE 802.11h required)" : "", + ((channels[i]. + flags & IEEE80211_CHAN_NO_IBSS) || + (channels[i]. + flags & IEEE80211_CHAN_RADAR)) ? "" : + ", IBSS", + channels[i]. + flags & IEEE80211_CHAN_PASSIVE_SCAN ? + "passive only" : "active/passive"); + } + supp_band = il_get_hw_mode(il, IEEE80211_BAND_5GHZ); + if (supp_band) { + channels = supp_band->channels; + + pos += + scnprintf(buf + pos, bufsz - pos, + "Displaying %d channels in 5.2GHz band (802.11a)\n", + supp_band->n_channels); + + for (i = 0; i < supp_band->n_channels; i++) + pos += + scnprintf(buf + pos, bufsz - pos, + "%d: %ddBm: BSS%s%s, %s.\n", + channels[i].hw_value, + channels[i].max_power, + channels[i]. + flags & IEEE80211_CHAN_RADAR ? + " (IEEE 802.11h required)" : "", + ((channels[i]. + flags & IEEE80211_CHAN_NO_IBSS) || + (channels[i]. + flags & IEEE80211_CHAN_RADAR)) ? "" : + ", IBSS", + channels[i]. + flags & IEEE80211_CHAN_PASSIVE_SCAN ? + "passive only" : "active/passive"); + } + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t +il_dbgfs_status_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + + struct il_priv *il = file->private_data; + char buf[512]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + pos += + scnprintf(buf + pos, bufsz - pos, "S_HCMD_ACTIVE:\t %d\n", + test_bit(S_HCMD_ACTIVE, &il->status)); + pos += + scnprintf(buf + pos, bufsz - pos, "S_INT_ENABLED:\t %d\n", + test_bit(S_INT_ENABLED, &il->status)); + pos += + scnprintf(buf + pos, bufsz - pos, "S_RFKILL:\t %d\n", + test_bit(S_RFKILL, &il->status)); + pos += + scnprintf(buf + pos, bufsz - pos, "S_CT_KILL:\t\t %d\n", + test_bit(S_CT_KILL, &il->status)); + pos += + scnprintf(buf + pos, bufsz - pos, "S_INIT:\t\t %d\n", + test_bit(S_INIT, &il->status)); + pos += + scnprintf(buf + pos, bufsz - pos, "S_ALIVE:\t\t %d\n", + test_bit(S_ALIVE, &il->status)); + pos += + scnprintf(buf + pos, bufsz - pos, "S_READY:\t\t %d\n", + test_bit(S_READY, &il->status)); + pos += + scnprintf(buf + pos, bufsz - pos, "S_TEMPERATURE:\t %d\n", + test_bit(S_TEMPERATURE, &il->status)); + pos += + scnprintf(buf + pos, bufsz - pos, "S_GEO_CONFIGURED:\t %d\n", + test_bit(S_GEO_CONFIGURED, &il->status)); + pos += + scnprintf(buf + pos, bufsz - pos, "S_EXIT_PENDING:\t %d\n", + test_bit(S_EXIT_PENDING, &il->status)); + pos += + scnprintf(buf + pos, bufsz - pos, "S_STATS:\t %d\n", + test_bit(S_STATS, &il->status)); + pos += + scnprintf(buf + pos, bufsz - pos, "S_SCANNING:\t %d\n", + test_bit(S_SCANNING, &il->status)); + pos += + scnprintf(buf + pos, bufsz - pos, "S_SCAN_ABORTING:\t %d\n", + test_bit(S_SCAN_ABORTING, &il->status)); + pos += + scnprintf(buf + pos, bufsz - pos, "S_SCAN_HW:\t\t %d\n", + test_bit(S_SCAN_HW, &il->status)); + pos += + scnprintf(buf + pos, bufsz - pos, "S_POWER_PMI:\t %d\n", + test_bit(S_POWER_PMI, &il->status)); + pos += + scnprintf(buf + pos, bufsz - pos, "S_FW_ERROR:\t %d\n", + test_bit(S_FW_ERROR, &il->status)); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t +il_dbgfs_interrupt_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + + struct il_priv *il = file->private_data; + int pos = 0; + int cnt = 0; + char *buf; + int bufsz = 24 * 64; /* 24 items * 64 char per item */ + ssize_t ret; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) { + IL_ERR("Can not allocate Buffer\n"); + return -ENOMEM; + } + + pos += + scnprintf(buf + pos, bufsz - pos, "Interrupt Statistics Report:\n"); + + pos += + scnprintf(buf + pos, bufsz - pos, "HW Error:\t\t\t %u\n", + il->isr_stats.hw); + pos += + scnprintf(buf + pos, bufsz - pos, "SW Error:\t\t\t %u\n", + il->isr_stats.sw); + if (il->isr_stats.sw || il->isr_stats.hw) { + pos += + scnprintf(buf + pos, bufsz - pos, + "\tLast Restarting Code: 0x%X\n", + il->isr_stats.err_code); + } +#ifdef CPTCFG_IWLEGACY_DEBUG + pos += + scnprintf(buf + pos, bufsz - pos, "Frame transmitted:\t\t %u\n", + il->isr_stats.sch); + pos += + scnprintf(buf + pos, bufsz - pos, "Alive interrupt:\t\t %u\n", + il->isr_stats.alive); +#endif + pos += + scnprintf(buf + pos, bufsz - pos, + "HW RF KILL switch toggled:\t %u\n", + il->isr_stats.rfkill); + + pos += + scnprintf(buf + pos, bufsz - pos, "CT KILL:\t\t\t %u\n", + il->isr_stats.ctkill); + + pos += + scnprintf(buf + pos, bufsz - pos, "Wakeup Interrupt:\t\t %u\n", + il->isr_stats.wakeup); + + pos += + scnprintf(buf + pos, bufsz - pos, "Rx command responses:\t\t %u\n", + il->isr_stats.rx); + for (cnt = 0; cnt < IL_CN_MAX; cnt++) { + if (il->isr_stats.handlers[cnt] > 0) + pos += + scnprintf(buf + pos, bufsz - pos, + "\tRx handler[%36s]:\t\t %u\n", + il_get_cmd_string(cnt), + il->isr_stats.handlers[cnt]); + } + + pos += + scnprintf(buf + pos, bufsz - pos, "Tx/FH interrupt:\t\t %u\n", + il->isr_stats.tx); + + pos += + scnprintf(buf + pos, bufsz - pos, "Unexpected INTA:\t\t %u\n", + il->isr_stats.unhandled); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t +il_dbgfs_interrupt_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct il_priv *il = file->private_data; + char buf[8]; + int buf_size; + u32 reset_flag; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%x", &reset_flag) != 1) + return -EFAULT; + if (reset_flag == 0) + il_clear_isr_stats(il); + + return count; +} + +static ssize_t +il_dbgfs_qos_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct il_priv *il = file->private_data; + int pos = 0, i; + char buf[256]; + const size_t bufsz = sizeof(buf); + + for (i = 0; i < AC_NUM; i++) { + pos += + scnprintf(buf + pos, bufsz - pos, + "\tcw_min\tcw_max\taifsn\ttxop\n"); + pos += + scnprintf(buf + pos, bufsz - pos, + "AC[%d]\t%u\t%u\t%u\t%u\n", i, + il->qos_data.def_qos_parm.ac[i].cw_min, + il->qos_data.def_qos_parm.ac[i].cw_max, + il->qos_data.def_qos_parm.ac[i].aifsn, + il->qos_data.def_qos_parm.ac[i].edca_txop); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t +il_dbgfs_disable_ht40_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct il_priv *il = file->private_data; + char buf[8]; + int buf_size; + int ht40; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &ht40) != 1) + return -EFAULT; + if (!il_is_any_associated(il)) + il->disable_ht40 = ht40 ? true : false; + else { + IL_ERR("Sta associated with AP - " + "Change to 40MHz channel support is not allowed\n"); + return -EINVAL; + } + + return count; +} + +static ssize_t +il_dbgfs_disable_ht40_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct il_priv *il = file->private_data; + char buf[100]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + pos += + scnprintf(buf + pos, bufsz - pos, "11n 40MHz Mode: %s\n", + il->disable_ht40 ? "Disabled" : "Enabled"); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +DEBUGFS_READ_WRITE_FILE_OPS(sram); +DEBUGFS_READ_FILE_OPS(nvm); +DEBUGFS_READ_FILE_OPS(stations); +DEBUGFS_READ_FILE_OPS(channels); +DEBUGFS_READ_FILE_OPS(status); +DEBUGFS_READ_WRITE_FILE_OPS(interrupt); +DEBUGFS_READ_FILE_OPS(qos); +DEBUGFS_READ_WRITE_FILE_OPS(disable_ht40); + +static ssize_t +il_dbgfs_tx_queue_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + + struct il_priv *il = file->private_data; + struct il_tx_queue *txq; + struct il_queue *q; + char *buf; + int pos = 0; + int cnt; + int ret; + const size_t bufsz = + sizeof(char) * 64 * il->cfg->num_of_queues; + + if (!il->txq) { + IL_ERR("txq not ready\n"); + return -EAGAIN; + } + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (cnt = 0; cnt < il->hw_params.max_txq_num; cnt++) { + txq = &il->txq[cnt]; + q = &txq->q; + pos += + scnprintf(buf + pos, bufsz - pos, + "hwq %.2d: read=%u write=%u stop=%d" + " swq_id=%#.2x (ac %d/hwq %d)\n", cnt, + q->read_ptr, q->write_ptr, + !!test_bit(cnt, il->queue_stopped), + txq->swq_id, txq->swq_id & 3, + (txq->swq_id >> 2) & 0x1f); + if (cnt >= 4) + continue; + /* for the ACs, display the stop count too */ + pos += + scnprintf(buf + pos, bufsz - pos, + " stop-count: %d\n", + atomic_read(&il->queue_stop_count[cnt])); + } + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t +il_dbgfs_rx_queue_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + + struct il_priv *il = file->private_data; + struct il_rx_queue *rxq = &il->rxq; + char buf[256]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, "read: %u\n", rxq->read); + pos += scnprintf(buf + pos, bufsz - pos, "write: %u\n", rxq->write); + pos += + scnprintf(buf + pos, bufsz - pos, "free_count: %u\n", + rxq->free_count); + if (rxq->rb_stts) { + pos += + scnprintf(buf + pos, bufsz - pos, "closed_rb_num: %u\n", + le16_to_cpu(rxq->rb_stts-> + closed_rb_num) & 0x0FFF); + } else { + pos += + scnprintf(buf + pos, bufsz - pos, + "closed_rb_num: Not Allocated\n"); + } + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t +il_dbgfs_ucode_rx_stats_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct il_priv *il = file->private_data; + + return il->debugfs_ops->rx_stats_read(file, user_buf, count, ppos); +} + +static ssize_t +il_dbgfs_ucode_tx_stats_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct il_priv *il = file->private_data; + + return il->debugfs_ops->tx_stats_read(file, user_buf, count, ppos); +} + +static ssize_t +il_dbgfs_ucode_general_stats_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct il_priv *il = file->private_data; + + return il->debugfs_ops->general_stats_read(file, user_buf, count, ppos); +} + +static ssize_t +il_dbgfs_sensitivity_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + + struct il_priv *il = file->private_data; + int pos = 0; + int cnt = 0; + char *buf; + int bufsz = sizeof(struct il_sensitivity_data) * 4 + 100; + ssize_t ret; + struct il_sensitivity_data *data; + + data = &il->sensitivity_data; + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) { + IL_ERR("Can not allocate Buffer\n"); + return -ENOMEM; + } + + pos += + scnprintf(buf + pos, bufsz - pos, "auto_corr_ofdm:\t\t\t %u\n", + data->auto_corr_ofdm); + pos += + scnprintf(buf + pos, bufsz - pos, "auto_corr_ofdm_mrc:\t\t %u\n", + data->auto_corr_ofdm_mrc); + pos += + scnprintf(buf + pos, bufsz - pos, "auto_corr_ofdm_x1:\t\t %u\n", + data->auto_corr_ofdm_x1); + pos += + scnprintf(buf + pos, bufsz - pos, "auto_corr_ofdm_mrc_x1:\t\t %u\n", + data->auto_corr_ofdm_mrc_x1); + pos += + scnprintf(buf + pos, bufsz - pos, "auto_corr_cck:\t\t\t %u\n", + data->auto_corr_cck); + pos += + scnprintf(buf + pos, bufsz - pos, "auto_corr_cck_mrc:\t\t %u\n", + data->auto_corr_cck_mrc); + pos += + scnprintf(buf + pos, bufsz - pos, + "last_bad_plcp_cnt_ofdm:\t\t %u\n", + data->last_bad_plcp_cnt_ofdm); + pos += + scnprintf(buf + pos, bufsz - pos, "last_fa_cnt_ofdm:\t\t %u\n", + data->last_fa_cnt_ofdm); + pos += + scnprintf(buf + pos, bufsz - pos, "last_bad_plcp_cnt_cck:\t\t %u\n", + data->last_bad_plcp_cnt_cck); + pos += + scnprintf(buf + pos, bufsz - pos, "last_fa_cnt_cck:\t\t %u\n", + data->last_fa_cnt_cck); + pos += + scnprintf(buf + pos, bufsz - pos, "nrg_curr_state:\t\t\t %u\n", + data->nrg_curr_state); + pos += + scnprintf(buf + pos, bufsz - pos, "nrg_prev_state:\t\t\t %u\n", + data->nrg_prev_state); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_value:\t\t\t"); + for (cnt = 0; cnt < 10; cnt++) { + pos += + scnprintf(buf + pos, bufsz - pos, " %u", + data->nrg_value[cnt]); + } + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_silence_rssi:\t\t"); + for (cnt = 0; cnt < NRG_NUM_PREV_STAT_L; cnt++) { + pos += + scnprintf(buf + pos, bufsz - pos, " %u", + data->nrg_silence_rssi[cnt]); + } + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + pos += + scnprintf(buf + pos, bufsz - pos, "nrg_silence_ref:\t\t %u\n", + data->nrg_silence_ref); + pos += + scnprintf(buf + pos, bufsz - pos, "nrg_energy_idx:\t\t\t %u\n", + data->nrg_energy_idx); + pos += + scnprintf(buf + pos, bufsz - pos, "nrg_silence_idx:\t\t %u\n", + data->nrg_silence_idx); + pos += + scnprintf(buf + pos, bufsz - pos, "nrg_th_cck:\t\t\t %u\n", + data->nrg_th_cck); + pos += + scnprintf(buf + pos, bufsz - pos, + "nrg_auto_corr_silence_diff:\t %u\n", + data->nrg_auto_corr_silence_diff); + pos += + scnprintf(buf + pos, bufsz - pos, "num_in_cck_no_fa:\t\t %u\n", + data->num_in_cck_no_fa); + pos += + scnprintf(buf + pos, bufsz - pos, "nrg_th_ofdm:\t\t\t %u\n", + data->nrg_th_ofdm); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t +il_dbgfs_chain_noise_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + + struct il_priv *il = file->private_data; + int pos = 0; + int cnt = 0; + char *buf; + int bufsz = sizeof(struct il_chain_noise_data) * 4 + 100; + ssize_t ret; + struct il_chain_noise_data *data; + + data = &il->chain_noise_data; + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) { + IL_ERR("Can not allocate Buffer\n"); + return -ENOMEM; + } + + pos += + scnprintf(buf + pos, bufsz - pos, "active_chains:\t\t\t %u\n", + data->active_chains); + pos += + scnprintf(buf + pos, bufsz - pos, "chain_noise_a:\t\t\t %u\n", + data->chain_noise_a); + pos += + scnprintf(buf + pos, bufsz - pos, "chain_noise_b:\t\t\t %u\n", + data->chain_noise_b); + pos += + scnprintf(buf + pos, bufsz - pos, "chain_noise_c:\t\t\t %u\n", + data->chain_noise_c); + pos += + scnprintf(buf + pos, bufsz - pos, "chain_signal_a:\t\t\t %u\n", + data->chain_signal_a); + pos += + scnprintf(buf + pos, bufsz - pos, "chain_signal_b:\t\t\t %u\n", + data->chain_signal_b); + pos += + scnprintf(buf + pos, bufsz - pos, "chain_signal_c:\t\t\t %u\n", + data->chain_signal_c); + pos += + scnprintf(buf + pos, bufsz - pos, "beacon_count:\t\t\t %u\n", + data->beacon_count); + + pos += scnprintf(buf + pos, bufsz - pos, "disconn_array:\t\t\t"); + for (cnt = 0; cnt < NUM_RX_CHAINS; cnt++) { + pos += + scnprintf(buf + pos, bufsz - pos, " %u", + data->disconn_array[cnt]); + } + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + pos += scnprintf(buf + pos, bufsz - pos, "delta_gain_code:\t\t"); + for (cnt = 0; cnt < NUM_RX_CHAINS; cnt++) { + pos += + scnprintf(buf + pos, bufsz - pos, " %u", + data->delta_gain_code[cnt]); + } + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + pos += + scnprintf(buf + pos, bufsz - pos, "radio_write:\t\t\t %u\n", + data->radio_write); + pos += + scnprintf(buf + pos, bufsz - pos, "state:\t\t\t\t %u\n", + data->state); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t +il_dbgfs_power_save_status_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct il_priv *il = file->private_data; + char buf[60]; + int pos = 0; + const size_t bufsz = sizeof(buf); + u32 pwrsave_status; + + pwrsave_status = + _il_rd(il, CSR_GP_CNTRL) & CSR_GP_REG_POWER_SAVE_STATUS_MSK; + + pos += scnprintf(buf + pos, bufsz - pos, "Power Save Status: "); + pos += + scnprintf(buf + pos, bufsz - pos, "%s\n", + (pwrsave_status == CSR_GP_REG_NO_POWER_SAVE) ? "none" : + (pwrsave_status == CSR_GP_REG_MAC_POWER_SAVE) ? "MAC" : + (pwrsave_status == CSR_GP_REG_PHY_POWER_SAVE) ? "PHY" : + "error"); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t +il_dbgfs_clear_ucode_stats_write(struct file *file, + const char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct il_priv *il = file->private_data; + char buf[8]; + int buf_size; + int clear; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &clear) != 1) + return -EFAULT; + + /* make request to uCode to retrieve stats information */ + mutex_lock(&il->mutex); + il_send_stats_request(il, CMD_SYNC, true); + mutex_unlock(&il->mutex); + + return count; +} + +static ssize_t +il_dbgfs_rxon_flags_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + + struct il_priv *il = file->private_data; + int len = 0; + char buf[20]; + + len = sprintf(buf, "0x%04X\n", le32_to_cpu(il->active.flags)); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t +il_dbgfs_rxon_filter_flags_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + + struct il_priv *il = file->private_data; + int len = 0; + char buf[20]; + + len = + sprintf(buf, "0x%04X\n", le32_to_cpu(il->active.filter_flags)); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t +il_dbgfs_fh_reg_read(struct file *file, char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct il_priv *il = file->private_data; + char *buf; + int pos = 0; + ssize_t ret = -EFAULT; + + if (il->ops->dump_fh) { + ret = pos = il->ops->dump_fh(il, &buf, true); + if (buf) { + ret = + simple_read_from_buffer(user_buf, count, ppos, buf, + pos); + kfree(buf); + } + } + + return ret; +} + +static ssize_t +il_dbgfs_missed_beacon_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + + struct il_priv *il = file->private_data; + int pos = 0; + char buf[12]; + const size_t bufsz = sizeof(buf); + + pos += + scnprintf(buf + pos, bufsz - pos, "%d\n", + il->missed_beacon_threshold); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t +il_dbgfs_missed_beacon_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct il_priv *il = file->private_data; + char buf[8]; + int buf_size; + int missed; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &missed) != 1) + return -EINVAL; + + if (missed < IL_MISSED_BEACON_THRESHOLD_MIN || + missed > IL_MISSED_BEACON_THRESHOLD_MAX) + il->missed_beacon_threshold = IL_MISSED_BEACON_THRESHOLD_DEF; + else + il->missed_beacon_threshold = missed; + + return count; +} + +static ssize_t +il_dbgfs_force_reset_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + + struct il_priv *il = file->private_data; + int pos = 0; + char buf[300]; + const size_t bufsz = sizeof(buf); + struct il_force_reset *force_reset; + + force_reset = &il->force_reset; + + pos += + scnprintf(buf + pos, bufsz - pos, "\tnumber of reset request: %d\n", + force_reset->reset_request_count); + pos += + scnprintf(buf + pos, bufsz - pos, + "\tnumber of reset request success: %d\n", + force_reset->reset_success_count); + pos += + scnprintf(buf + pos, bufsz - pos, + "\tnumber of reset request reject: %d\n", + force_reset->reset_reject_count); + pos += + scnprintf(buf + pos, bufsz - pos, "\treset duration: %lu\n", + force_reset->reset_duration); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t +il_dbgfs_force_reset_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + + int ret; + struct il_priv *il = file->private_data; + + ret = il_force_reset(il, true); + + return ret ? ret : count; +} + +static ssize_t +il_dbgfs_wd_timeout_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + + struct il_priv *il = file->private_data; + char buf[8]; + int buf_size; + int timeout; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &timeout) != 1) + return -EINVAL; + if (timeout < 0 || timeout > IL_MAX_WD_TIMEOUT) + timeout = IL_DEF_WD_TIMEOUT; + + il->cfg->wd_timeout = timeout; + il_setup_watchdog(il); + return count; +} + +DEBUGFS_READ_FILE_OPS(rx_stats); +DEBUGFS_READ_FILE_OPS(tx_stats); +DEBUGFS_READ_FILE_OPS(rx_queue); +DEBUGFS_READ_FILE_OPS(tx_queue); +DEBUGFS_READ_FILE_OPS(ucode_rx_stats); +DEBUGFS_READ_FILE_OPS(ucode_tx_stats); +DEBUGFS_READ_FILE_OPS(ucode_general_stats); +DEBUGFS_READ_FILE_OPS(sensitivity); +DEBUGFS_READ_FILE_OPS(chain_noise); +DEBUGFS_READ_FILE_OPS(power_save_status); +DEBUGFS_WRITE_FILE_OPS(clear_ucode_stats); +DEBUGFS_WRITE_FILE_OPS(clear_traffic_stats); +DEBUGFS_READ_FILE_OPS(fh_reg); +DEBUGFS_READ_WRITE_FILE_OPS(missed_beacon); +DEBUGFS_READ_WRITE_FILE_OPS(force_reset); +DEBUGFS_READ_FILE_OPS(rxon_flags); +DEBUGFS_READ_FILE_OPS(rxon_filter_flags); +DEBUGFS_WRITE_FILE_OPS(wd_timeout); + +/* + * Create the debugfs files and directories + * + */ +int +il_dbgfs_register(struct il_priv *il, const char *name) +{ + struct dentry *phyd = il->hw->wiphy->debugfsdir; + struct dentry *dir_drv, *dir_data, *dir_rf, *dir_debug; + + dir_drv = debugfs_create_dir(name, phyd); + if (!dir_drv) + return -ENOMEM; + + il->debugfs_dir = dir_drv; + + dir_data = debugfs_create_dir("data", dir_drv); + if (!dir_data) + goto err; + dir_rf = debugfs_create_dir("rf", dir_drv); + if (!dir_rf) + goto err; + dir_debug = debugfs_create_dir("debug", dir_drv); + if (!dir_debug) + goto err; + + DEBUGFS_ADD_FILE(nvm, dir_data, S_IRUSR); + DEBUGFS_ADD_FILE(sram, dir_data, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(stations, dir_data, S_IRUSR); + DEBUGFS_ADD_FILE(channels, dir_data, S_IRUSR); + DEBUGFS_ADD_FILE(status, dir_data, S_IRUSR); + DEBUGFS_ADD_FILE(interrupt, dir_data, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(qos, dir_data, S_IRUSR); + DEBUGFS_ADD_FILE(disable_ht40, dir_data, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(rx_stats, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(tx_stats, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(rx_queue, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(tx_queue, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(power_save_status, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(clear_ucode_stats, dir_debug, S_IWUSR); + DEBUGFS_ADD_FILE(clear_traffic_stats, dir_debug, S_IWUSR); + DEBUGFS_ADD_FILE(fh_reg, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(missed_beacon, dir_debug, S_IWUSR); + DEBUGFS_ADD_FILE(force_reset, dir_debug, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(ucode_rx_stats, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(ucode_tx_stats, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(ucode_general_stats, dir_debug, S_IRUSR); + + if (il->cfg->sensitivity_calib_by_driver) + DEBUGFS_ADD_FILE(sensitivity, dir_debug, S_IRUSR); + if (il->cfg->chain_noise_calib_by_driver) + DEBUGFS_ADD_FILE(chain_noise, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(rxon_flags, dir_debug, S_IWUSR); + DEBUGFS_ADD_FILE(rxon_filter_flags, dir_debug, S_IWUSR); + DEBUGFS_ADD_FILE(wd_timeout, dir_debug, S_IWUSR); + if (il->cfg->sensitivity_calib_by_driver) + DEBUGFS_ADD_BOOL(disable_sensitivity, dir_rf, + &il->disable_sens_cal); + if (il->cfg->chain_noise_calib_by_driver) + DEBUGFS_ADD_BOOL(disable_chain_noise, dir_rf, + &il->disable_chain_noise_cal); + DEBUGFS_ADD_BOOL(disable_tx_power, dir_rf, &il->disable_tx_power_cal); + return 0; + +err: + IL_ERR("Can't create the debugfs directory\n"); + il_dbgfs_unregister(il); + return -ENOMEM; +} +EXPORT_SYMBOL(il_dbgfs_register); + +/** + * Remove the debugfs files and directories + * + */ +void +il_dbgfs_unregister(struct il_priv *il) +{ + if (!il->debugfs_dir) + return; + + debugfs_remove_recursive(il->debugfs_dir); + il->debugfs_dir = NULL; +} +EXPORT_SYMBOL(il_dbgfs_unregister); diff --git a/drivers/net/wireless/iwlegacy/iwl-spectrum.h b/drivers/net/wireless/iwlegacy/iwl-spectrum.h new file mode 100644 index 0000000..85fe48e --- /dev/null +++ b/drivers/net/wireless/iwlegacy/iwl-spectrum.h @@ -0,0 +1,92 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2011 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ieee80211 subsystem header files. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#ifndef __il_spectrum_h__ +#define __il_spectrum_h__ +enum { /* ieee80211_basic_report.map */ + IEEE80211_BASIC_MAP_BSS = (1 << 0), + IEEE80211_BASIC_MAP_OFDM = (1 << 1), + IEEE80211_BASIC_MAP_UNIDENTIFIED = (1 << 2), + IEEE80211_BASIC_MAP_RADAR = (1 << 3), + IEEE80211_BASIC_MAP_UNMEASURED = (1 << 4), + /* Bits 5-7 are reserved */ + +}; +struct ieee80211_basic_report { + u8 channel; + __le64 start_time; + __le16 duration; + u8 map; +} __packed; + +enum { /* ieee80211_measurement_request.mode */ + /* Bit 0 is reserved */ + IEEE80211_MEASUREMENT_ENABLE = (1 << 1), + IEEE80211_MEASUREMENT_REQUEST = (1 << 2), + IEEE80211_MEASUREMENT_REPORT = (1 << 3), + /* Bits 4-7 are reserved */ +}; + +enum { + IEEE80211_REPORT_BASIC = 0, /* required */ + IEEE80211_REPORT_CCA = 1, /* optional */ + IEEE80211_REPORT_RPI = 2, /* optional */ + /* 3-255 reserved */ +}; + +struct ieee80211_measurement_params { + u8 channel; + __le64 start_time; + __le16 duration; +} __packed; + +struct ieee80211_info_element { + u8 id; + u8 len; + u8 data[0]; +} __packed; + +struct ieee80211_measurement_request { + struct ieee80211_info_element ie; + u8 token; + u8 mode; + u8 type; + struct ieee80211_measurement_params params[0]; +} __packed; + +struct ieee80211_measurement_report { + struct ieee80211_info_element ie; + u8 token; + u8 mode; + u8 type; + union { + struct ieee80211_basic_report basic[0]; + } u; +} __packed; + +#endif diff --git a/drivers/net/wireless/iwlegacy/prph.h b/drivers/net/wireless/iwlegacy/prph.h new file mode 100644 index 0000000..ffec4b4 --- /dev/null +++ b/drivers/net/wireless/iwlegacy/prph.h @@ -0,0 +1,522 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2005 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2011 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef __il_prph_h__ +#define __il_prph_h__ + +/* + * Registers in this file are internal, not PCI bus memory mapped. + * Driver accesses these via HBUS_TARG_PRPH_* registers. + */ +#define PRPH_BASE (0x00000) +#define PRPH_END (0xFFFFF) + +/* APMG (power management) constants */ +#define APMG_BASE (PRPH_BASE + 0x3000) +#define APMG_CLK_CTRL_REG (APMG_BASE + 0x0000) +#define APMG_CLK_EN_REG (APMG_BASE + 0x0004) +#define APMG_CLK_DIS_REG (APMG_BASE + 0x0008) +#define APMG_PS_CTRL_REG (APMG_BASE + 0x000c) +#define APMG_PCIDEV_STT_REG (APMG_BASE + 0x0010) +#define APMG_RFKILL_REG (APMG_BASE + 0x0014) +#define APMG_RTC_INT_STT_REG (APMG_BASE + 0x001c) +#define APMG_RTC_INT_MSK_REG (APMG_BASE + 0x0020) +#define APMG_DIGITAL_SVR_REG (APMG_BASE + 0x0058) +#define APMG_ANALOG_SVR_REG (APMG_BASE + 0x006C) + +#define APMS_CLK_VAL_MRB_FUNC_MODE (0x00000001) +#define APMG_CLK_VAL_DMA_CLK_RQT (0x00000200) +#define APMG_CLK_VAL_BSM_CLK_RQT (0x00000800) + +#define APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS (0x00400000) +#define APMG_PS_CTRL_VAL_RESET_REQ (0x04000000) +#define APMG_PS_CTRL_MSK_PWR_SRC (0x03000000) +#define APMG_PS_CTRL_VAL_PWR_SRC_VMAIN (0x00000000) +#define APMG_PS_CTRL_VAL_PWR_SRC_MAX (0x01000000) /* 3945 only */ +#define APMG_PS_CTRL_VAL_PWR_SRC_VAUX (0x02000000) +#define APMG_SVR_VOLTAGE_CONFIG_BIT_MSK (0x000001E0) /* bit 8:5 */ +#define APMG_SVR_DIGITAL_VOLTAGE_1_32 (0x00000060) + +#define APMG_PCIDEV_STT_VAL_L1_ACT_DIS (0x00000800) + +/** + * BSM (Bootstrap State Machine) + * + * The Bootstrap State Machine (BSM) stores a short bootstrap uCode program + * in special SRAM that does not power down when the embedded control + * processor is sleeping (e.g. for periodic power-saving shutdowns of radio). + * + * When powering back up after sleeps (or during initial uCode load), the BSM + * internally loads the short bootstrap program from the special SRAM into the + * embedded processor's instruction SRAM, and starts the processor so it runs + * the bootstrap program. + * + * This bootstrap program loads (via PCI busmaster DMA) instructions and data + * images for a uCode program from host DRAM locations. The host driver + * indicates DRAM locations and sizes for instruction and data images via the + * four BSM_DRAM_* registers. Once the bootstrap program loads the new program, + * the new program starts automatically. + * + * The uCode used for open-source drivers includes two programs: + * + * 1) Initialization -- performs hardware calibration and sets up some + * internal data, then notifies host via "initialize alive" notification + * (struct il_init_alive_resp) that it has completed all of its work. + * After signal from host, it then loads and starts the runtime program. + * The initialization program must be used when initially setting up the + * NIC after loading the driver. + * + * 2) Runtime/Protocol -- performs all normal runtime operations. This + * notifies host via "alive" notification (struct il_alive_resp) that it + * is ready to be used. + * + * When initializing the NIC, the host driver does the following procedure: + * + * 1) Load bootstrap program (instructions only, no data image for bootstrap) + * into bootstrap memory. Use dword writes starting at BSM_SRAM_LOWER_BOUND + * + * 2) Point (via BSM_DRAM_*) to the "initialize" uCode data and instruction + * images in host DRAM. + * + * 3) Set up BSM to copy from BSM SRAM into uCode instruction SRAM when asked: + * BSM_WR_MEM_SRC_REG = 0 + * BSM_WR_MEM_DST_REG = RTC_INST_LOWER_BOUND + * BSM_WR_MEM_DWCOUNT_REG = # dwords in bootstrap instruction image + * + * 4) Load bootstrap into instruction SRAM: + * BSM_WR_CTRL_REG = BSM_WR_CTRL_REG_BIT_START + * + * 5) Wait for load completion: + * Poll BSM_WR_CTRL_REG for BSM_WR_CTRL_REG_BIT_START = 0 + * + * 6) Enable future boot loads whenever NIC's power management triggers it: + * BSM_WR_CTRL_REG = BSM_WR_CTRL_REG_BIT_START_EN + * + * 7) Start the NIC by removing all reset bits: + * CSR_RESET = 0 + * + * The bootstrap uCode (already in instruction SRAM) loads initialization + * uCode. Initialization uCode performs data initialization, sends + * "initialize alive" notification to host, and waits for a signal from + * host to load runtime code. + * + * 4) Point (via BSM_DRAM_*) to the "runtime" uCode data and instruction + * images in host DRAM. The last register loaded must be the instruction + * byte count register ("1" in MSbit tells initialization uCode to load + * the runtime uCode): + * BSM_DRAM_INST_BYTECOUNT_REG = byte count | BSM_DRAM_INST_LOAD + * + * 5) Wait for "alive" notification, then issue normal runtime commands. + * + * Data caching during power-downs: + * + * Just before the embedded controller powers down (e.g for automatic + * power-saving modes, or for RFKILL), uCode stores (via PCI busmaster DMA) + * a current snapshot of the embedded processor's data SRAM into host DRAM. + * This caches the data while the embedded processor's memory is powered down. + * Location and size are controlled by BSM_DRAM_DATA_* registers. + * + * NOTE: Instruction SRAM does not need to be saved, since that doesn't + * change during operation; the original image (from uCode distribution + * file) can be used for reload. + * + * When powering back up, the BSM loads the bootstrap program. Bootstrap looks + * at the BSM_DRAM_* registers, which now point to the runtime instruction + * image and the cached (modified) runtime data (*not* the initialization + * uCode). Bootstrap reloads these runtime images into SRAM, and restarts the + * uCode from where it left off before the power-down. + * + * NOTE: Initialization uCode does *not* run as part of the save/restore + * procedure. + * + * This save/restore method is mostly for autonomous power management during + * normal operation (result of C_POWER_TBL). Platform suspend/resume and + * RFKILL should use complete restarts (with total re-initialization) of uCode, + * allowing total shutdown (including BSM memory). + * + * Note that, during normal operation, the host DRAM that held the initial + * startup data for the runtime code is now being used as a backup data cache + * for modified data! If you need to completely re-initialize the NIC, make + * sure that you use the runtime data image from the uCode distribution file, + * not the modified/saved runtime data. You may want to store a separate + * "clean" runtime data image in DRAM to avoid disk reads of distribution file. + */ + +/* BSM bit fields */ +#define BSM_WR_CTRL_REG_BIT_START (0x80000000) /* start boot load now */ +#define BSM_WR_CTRL_REG_BIT_START_EN (0x40000000) /* enable boot after pwrup */ +#define BSM_DRAM_INST_LOAD (0x80000000) /* start program load now */ + +/* BSM addresses */ +#define BSM_BASE (PRPH_BASE + 0x3400) +#define BSM_END (PRPH_BASE + 0x3800) + +#define BSM_WR_CTRL_REG (BSM_BASE + 0x000) /* ctl and status */ +#define BSM_WR_MEM_SRC_REG (BSM_BASE + 0x004) /* source in BSM mem */ +#define BSM_WR_MEM_DST_REG (BSM_BASE + 0x008) /* dest in SRAM mem */ +#define BSM_WR_DWCOUNT_REG (BSM_BASE + 0x00C) /* bytes */ +#define BSM_WR_STATUS_REG (BSM_BASE + 0x010) /* bit 0: 1 == done */ + +/* + * Pointers and size regs for bootstrap load and data SRAM save/restore. + * NOTE: 3945 pointers use bits 31:0 of DRAM address. + * 4965 pointers use bits 35:4 of DRAM address. + */ +#define BSM_DRAM_INST_PTR_REG (BSM_BASE + 0x090) +#define BSM_DRAM_INST_BYTECOUNT_REG (BSM_BASE + 0x094) +#define BSM_DRAM_DATA_PTR_REG (BSM_BASE + 0x098) +#define BSM_DRAM_DATA_BYTECOUNT_REG (BSM_BASE + 0x09C) + +/* + * BSM special memory, stays powered on during power-save sleeps. + * Read/write, address range from LOWER_BOUND to (LOWER_BOUND + SIZE -1) + */ +#define BSM_SRAM_LOWER_BOUND (PRPH_BASE + 0x3800) +#define BSM_SRAM_SIZE (1024) /* bytes */ + +/* 3945 Tx scheduler registers */ +#define ALM_SCD_BASE (PRPH_BASE + 0x2E00) +#define ALM_SCD_MODE_REG (ALM_SCD_BASE + 0x000) +#define ALM_SCD_ARASTAT_REG (ALM_SCD_BASE + 0x004) +#define ALM_SCD_TXFACT_REG (ALM_SCD_BASE + 0x010) +#define ALM_SCD_TXF4MF_REG (ALM_SCD_BASE + 0x014) +#define ALM_SCD_TXF5MF_REG (ALM_SCD_BASE + 0x020) +#define ALM_SCD_SBYP_MODE_1_REG (ALM_SCD_BASE + 0x02C) +#define ALM_SCD_SBYP_MODE_2_REG (ALM_SCD_BASE + 0x030) + +/** + * Tx Scheduler + * + * The Tx Scheduler selects the next frame to be transmitted, choosing TFDs + * (Transmit Frame Descriptors) from up to 16 circular Tx queues resident in + * host DRAM. It steers each frame's Tx command (which contains the frame + * data) into one of up to 7 prioritized Tx DMA FIFO channels within the + * device. A queue maps to only one (selectable by driver) Tx DMA channel, + * but one DMA channel may take input from several queues. + * + * Tx DMA FIFOs have dedicated purposes. For 4965, they are used as follows + * (cf. default_queue_to_tx_fifo in 4965.c): + * + * 0 -- EDCA BK (background) frames, lowest priority + * 1 -- EDCA BE (best effort) frames, normal priority + * 2 -- EDCA VI (video) frames, higher priority + * 3 -- EDCA VO (voice) and management frames, highest priority + * 4 -- Commands (e.g. RXON, etc.) + * 5 -- unused (HCCA) + * 6 -- unused (HCCA) + * 7 -- not used by driver (device-internal only) + * + * + * Driver should normally map queues 0-6 to Tx DMA/FIFO channels 0-6. + * In addition, driver can map the remaining queues to Tx DMA/FIFO + * channels 0-3 to support 11n aggregation via EDCA DMA channels. + * + * The driver sets up each queue to work in one of two modes: + * + * 1) Scheduler-Ack, in which the scheduler automatically supports a + * block-ack (BA) win of up to 64 TFDs. In this mode, each queue + * contains TFDs for a unique combination of Recipient Address (RA) + * and Traffic Identifier (TID), that is, traffic of a given + * Quality-Of-Service (QOS) priority, destined for a single station. + * + * In scheduler-ack mode, the scheduler keeps track of the Tx status of + * each frame within the BA win, including whether it's been transmitted, + * and whether it's been acknowledged by the receiving station. The device + * automatically processes block-acks received from the receiving STA, + * and reschedules un-acked frames to be retransmitted (successful + * Tx completion may end up being out-of-order). + * + * The driver must maintain the queue's Byte Count table in host DRAM + * (struct il4965_sched_queue_byte_cnt_tbl) for this mode. + * This mode does not support fragmentation. + * + * 2) FIFO (a.k.a. non-Scheduler-ACK), in which each TFD is processed in order. + * The device may automatically retry Tx, but will retry only one frame + * at a time, until receiving ACK from receiving station, or reaching + * retry limit and giving up. + * + * The command queue (#4/#9) must use this mode! + * This mode does not require use of the Byte Count table in host DRAM. + * + * Driver controls scheduler operation via 3 means: + * 1) Scheduler registers + * 2) Shared scheduler data base in internal 4956 SRAM + * 3) Shared data in host DRAM + * + * Initialization: + * + * When loading, driver should allocate memory for: + * 1) 16 TFD circular buffers, each with space for (typically) 256 TFDs. + * 2) 16 Byte Count circular buffers in 16 KBytes contiguous memory + * (1024 bytes for each queue). + * + * After receiving "Alive" response from uCode, driver must initialize + * the scheduler (especially for queue #4/#9, the command queue, otherwise + * the driver can't issue commands!): + */ + +/** + * Max Tx win size is the max number of contiguous TFDs that the scheduler + * can keep track of at one time when creating block-ack chains of frames. + * Note that "64" matches the number of ack bits in a block-ack packet. + * Driver should use SCD_WIN_SIZE and SCD_FRAME_LIMIT values to initialize + * IL49_SCD_CONTEXT_QUEUE_OFFSET(x) values. + */ +#define SCD_WIN_SIZE 64 +#define SCD_FRAME_LIMIT 64 + +/* SCD registers are internal, must be accessed via HBUS_TARG_PRPH regs */ +#define IL49_SCD_START_OFFSET 0xa02c00 + +/* + * 4965 tells driver SRAM address for internal scheduler structs via this reg. + * Value is valid only after "Alive" response from uCode. + */ +#define IL49_SCD_SRAM_BASE_ADDR (IL49_SCD_START_OFFSET + 0x0) + +/* + * Driver may need to update queue-empty bits after changing queue's + * write and read pointers (idxes) during (re-)initialization (i.e. when + * scheduler is not tracking what's happening). + * Bit fields: + * 31-16: Write mask -- 1: update empty bit, 0: don't change empty bit + * 15-00: Empty state, one for each queue -- 1: empty, 0: non-empty + * NOTE: This register is not used by Linux driver. + */ +#define IL49_SCD_EMPTY_BITS (IL49_SCD_START_OFFSET + 0x4) + +/* + * Physical base address of array of byte count (BC) circular buffers (CBs). + * Each Tx queue has a BC CB in host DRAM to support Scheduler-ACK mode. + * This register points to BC CB for queue 0, must be on 1024-byte boundary. + * Others are spaced by 1024 bytes. + * Each BC CB is 2 bytes * (256 + 64) = 740 bytes, followed by 384 bytes pad. + * (Index into a queue's BC CB) = (idx into queue's TFD CB) = (SSN & 0xff). + * Bit fields: + * 25-00: Byte Count CB physical address [35:10], must be 1024-byte aligned. + */ +#define IL49_SCD_DRAM_BASE_ADDR (IL49_SCD_START_OFFSET + 0x10) + +/* + * Enables any/all Tx DMA/FIFO channels. + * Scheduler generates requests for only the active channels. + * Set this to 0xff to enable all 8 channels (normal usage). + * Bit fields: + * 7- 0: Enable (1), disable (0), one bit for each channel 0-7 + */ +#define IL49_SCD_TXFACT (IL49_SCD_START_OFFSET + 0x1c) +/* + * Queue (x) Write Pointers (idxes, really!), one for each Tx queue. + * Initialized and updated by driver as new TFDs are added to queue. + * NOTE: If using Block Ack, idx must correspond to frame's + * Start Sequence Number; idx = (SSN & 0xff) + * NOTE: Alternative to HBUS_TARG_WRPTR, which is what Linux driver uses? + */ +#define IL49_SCD_QUEUE_WRPTR(x) (IL49_SCD_START_OFFSET + 0x24 + (x) * 4) + +/* + * Queue (x) Read Pointers (idxes, really!), one for each Tx queue. + * For FIFO mode, idx indicates next frame to transmit. + * For Scheduler-ACK mode, idx indicates first frame in Tx win. + * Initialized by driver, updated by scheduler. + */ +#define IL49_SCD_QUEUE_RDPTR(x) (IL49_SCD_START_OFFSET + 0x64 + (x) * 4) + +/* + * Select which queues work in chain mode (1) vs. not (0). + * Use chain mode to build chains of aggregated frames. + * Bit fields: + * 31-16: Reserved + * 15-00: Mode, one bit for each queue -- 1: Chain mode, 0: one-at-a-time + * NOTE: If driver sets up queue for chain mode, it should be also set up + * Scheduler-ACK mode as well, via SCD_QUEUE_STATUS_BITS(x). + */ +#define IL49_SCD_QUEUECHAIN_SEL (IL49_SCD_START_OFFSET + 0xd0) + +/* + * Select which queues interrupt driver when scheduler increments + * a queue's read pointer (idx). + * Bit fields: + * 31-16: Reserved + * 15-00: Interrupt enable, one bit for each queue -- 1: enabled, 0: disabled + * NOTE: This functionality is apparently a no-op; driver relies on interrupts + * from Rx queue to read Tx command responses and update Tx queues. + */ +#define IL49_SCD_INTERRUPT_MASK (IL49_SCD_START_OFFSET + 0xe4) + +/* + * Queue search status registers. One for each queue. + * Sets up queue mode and assigns queue to Tx DMA channel. + * Bit fields: + * 19-10: Write mask/enable bits for bits 0-9 + * 9: Driver should init to "0" + * 8: Scheduler-ACK mode (1), non-Scheduler-ACK (i.e. FIFO) mode (0). + * Driver should init to "1" for aggregation mode, or "0" otherwise. + * 7-6: Driver should init to "0" + * 5: Window Size Left; indicates whether scheduler can request + * another TFD, based on win size, etc. Driver should init + * this bit to "1" for aggregation mode, or "0" for non-agg. + * 4-1: Tx FIFO to use (range 0-7). + * 0: Queue is active (1), not active (0). + * Other bits should be written as "0" + * + * NOTE: If enabling Scheduler-ACK mode, chain mode should also be enabled + * via SCD_QUEUECHAIN_SEL. + */ +#define IL49_SCD_QUEUE_STATUS_BITS(x)\ + (IL49_SCD_START_OFFSET + 0x104 + (x) * 4) + +/* Bit field positions */ +#define IL49_SCD_QUEUE_STTS_REG_POS_ACTIVE (0) +#define IL49_SCD_QUEUE_STTS_REG_POS_TXF (1) +#define IL49_SCD_QUEUE_STTS_REG_POS_WSL (5) +#define IL49_SCD_QUEUE_STTS_REG_POS_SCD_ACK (8) + +/* Write masks */ +#define IL49_SCD_QUEUE_STTS_REG_POS_SCD_ACT_EN (10) +#define IL49_SCD_QUEUE_STTS_REG_MSK (0x0007FC00) + +/** + * 4965 internal SRAM structures for scheduler, shared with driver ... + * + * Driver should clear and initialize the following areas after receiving + * "Alive" response from 4965 uCode, i.e. after initial + * uCode load, or after a uCode load done for error recovery: + * + * SCD_CONTEXT_DATA_OFFSET (size 128 bytes) + * SCD_TX_STTS_BITMAP_OFFSET (size 256 bytes) + * SCD_TRANSLATE_TBL_OFFSET (size 32 bytes) + * + * Driver accesses SRAM via HBUS_TARG_MEM_* registers. + * Driver reads base address of this scheduler area from SCD_SRAM_BASE_ADDR. + * All OFFSET values must be added to this base address. + */ + +/* + * Queue context. One 8-byte entry for each of 16 queues. + * + * Driver should clear this entire area (size 0x80) to 0 after receiving + * "Alive" notification from uCode. Additionally, driver should init + * each queue's entry as follows: + * + * LS Dword bit fields: + * 0-06: Max Tx win size for Scheduler-ACK. Driver should init to 64. + * + * MS Dword bit fields: + * 16-22: Frame limit. Driver should init to 10 (0xa). + * + * Driver should init all other bits to 0. + * + * Init must be done after driver receives "Alive" response from 4965 uCode, + * and when setting up queue for aggregation. + */ +#define IL49_SCD_CONTEXT_DATA_OFFSET 0x380 +#define IL49_SCD_CONTEXT_QUEUE_OFFSET(x) \ + (IL49_SCD_CONTEXT_DATA_OFFSET + ((x) * 8)) + +#define IL49_SCD_QUEUE_CTX_REG1_WIN_SIZE_POS (0) +#define IL49_SCD_QUEUE_CTX_REG1_WIN_SIZE_MSK (0x0000007F) +#define IL49_SCD_QUEUE_CTX_REG2_FRAME_LIMIT_POS (16) +#define IL49_SCD_QUEUE_CTX_REG2_FRAME_LIMIT_MSK (0x007F0000) + +/* + * Tx Status Bitmap + * + * Driver should clear this entire area (size 0x100) to 0 after receiving + * "Alive" notification from uCode. Area is used only by device itself; + * no other support (besides clearing) is required from driver. + */ +#define IL49_SCD_TX_STTS_BITMAP_OFFSET 0x400 + +/* + * RAxTID to queue translation mapping. + * + * When queue is in Scheduler-ACK mode, frames placed in a that queue must be + * for only one combination of receiver address (RA) and traffic ID (TID), i.e. + * one QOS priority level destined for one station (for this wireless link, + * not final destination). The SCD_TRANSLATE_TBL area provides 16 16-bit + * mappings, one for each of the 16 queues. If queue is not in Scheduler-ACK + * mode, the device ignores the mapping value. + * + * Bit fields, for each 16-bit map: + * 15-9: Reserved, set to 0 + * 8-4: Index into device's station table for recipient station + * 3-0: Traffic ID (tid), range 0-15 + * + * Driver should clear this entire area (size 32 bytes) to 0 after receiving + * "Alive" notification from uCode. To update a 16-bit map value, driver + * must read a dword-aligned value from device SRAM, replace the 16-bit map + * value of interest, and write the dword value back into device SRAM. + */ +#define IL49_SCD_TRANSLATE_TBL_OFFSET 0x500 + +/* Find translation table dword to read/write for given queue */ +#define IL49_SCD_TRANSLATE_TBL_OFFSET_QUEUE(x) \ + ((IL49_SCD_TRANSLATE_TBL_OFFSET + ((x) * 2)) & 0xfffffffc) + +#define IL_SCD_TXFIFO_POS_TID (0) +#define IL_SCD_TXFIFO_POS_RA (4) +#define IL_SCD_QUEUE_RA_TID_MAP_RATID_MSK (0x01FF) + +/*********************** END TX SCHEDULER *************************************/ + +#endif /* __il_prph_h__ */ diff --git a/drivers/net/wireless/iwlwifi/Kconfig b/drivers/net/wireless/iwlwifi/Kconfig new file mode 100644 index 0000000..05a7d5a --- /dev/null +++ b/drivers/net/wireless/iwlwifi/Kconfig @@ -0,0 +1,159 @@ +config IWLWIFI + tristate "Intel Wireless WiFi Next Gen AGN - Wireless-N/Advanced-N/Ultimate-N (iwlwifi) " + depends on m + depends on PCI && MAC80211 && HAS_IOMEM + select BACKPORT_FW_LOADER + select BACKPORT_NEW_LEDS + select BACKPORT_LEDS_CLASS + select BACKPORT_LEDS_TRIGGERS + select MAC80211_LEDS + ---help--- + Select to build the driver supporting the: + + Intel Wireless WiFi Link Next-Gen AGN + + This option enables support for use with the following hardware: + Intel Wireless WiFi Link 6250AGN Adapter + Intel 6000 Series Wi-Fi Adapters (6200AGN and 6300AGN) + Intel WiFi Link 1000BGN + Intel Wireless WiFi 5150AGN + Intel Wireless WiFi 5100AGN, 5300AGN, and 5350AGN + Intel 6005 Series Wi-Fi Adapters + Intel 6030 Series Wi-Fi Adapters + Intel Wireless WiFi Link 6150BGN 2 Adapter + Intel 100 Series Wi-Fi Adapters (100BGN and 130BGN) + Intel 2000 Series Wi-Fi Adapters + + + This driver uses the kernel's mac80211 subsystem. + + In order to use this driver, you will need a microcode (uCode) + image for it. You can obtain the microcode from: + + . + + The microcode is typically installed in /lib/firmware. You can + look in the hotplug script /etc/hotplug/firmware.agent to + determine which directory FIRMWARE_DIR is set to when the script + runs. + + If you want to compile the driver as a module ( = code which can be + inserted in and removed from the running kernel whenever you want), + say M here and read . The + module will be called iwlwifi. + +config IWLDVM + tristate "Intel Wireless WiFi DVM Firmware support" + depends on m + depends on IWLWIFI + default IWLWIFI + help + This is the driver supporting the DVM firmware which is + currently the only firmware available for existing devices. + +config IWLMVM + tristate "Intel Wireless WiFi MVM Firmware support" + depends on m + depends on IWLWIFI + help + This is the driver supporting the MVM firmware which is + currently only available for 7000 series devices. + + Say yes if you have such a device. + +# don't call it _MODULE -- will confuse Kconfig/fixdep/... +config IWLWIFI_OPMODE_MODULAR + bool + default y if IWLDVM=m + default y if IWLMVM=m + +comment "WARNING: iwlwifi is useless without IWLDVM or IWLMVM" + depends on IWLWIFI && IWLDVM=n && IWLMVM=n + +menu "Debugging Options" + depends on IWLWIFI + +config IWLWIFI_DEBUG + bool "Enable full debugging output in the iwlwifi driver" + depends on IWLWIFI + ---help--- + This option will enable debug tracing output for the iwlwifi drivers + + This will result in the kernel module being ~100k larger. You can + control which debug output is sent to the kernel log by setting the + value in + + /sys/module/iwlwifi/parameters/debug + + This entry will only exist if this option is enabled. + + To set a value, simply echo an 8-byte hex value to the same file: + + % echo 0x43fff > /sys/module/iwlwifi/parameters/debug + + You can find the list of debug mask values in: + drivers/net/wireless/iwlwifi/iwl-debug.h + + If this is your first time using this driver, you should say Y here + as the debug information can assist others in helping you resolve + any problems you may encounter. + +config IWLWIFI_DEBUGFS + bool "iwlwifi debugfs support" + depends on IWLWIFI && MAC80211_DEBUGFS + ---help--- + Enable creation of debugfs files for the iwlwifi drivers. This + is a low-impact option that allows getting insight into the + driver's state at runtime. + +config IWLWIFI_DEBUG_EXPERIMENTAL_UCODE + bool "Experimental uCode support" + depends on IWLWIFI && IWLWIFI_DEBUG + ---help--- + Enable use of experimental ucode for testing and debugging. + +config IWLWIFI_DEVICE_TRACING + bool "iwlwifi device access tracing" + depends on IWLWIFI + depends on EVENT_TRACING + help + Say Y here to trace all commands, including TX frames and IO + accesses, sent to the device. If you say yes, iwlwifi will + register with the ftrace framework for event tracing and dump + all this information to the ringbuffer, you may need to + increase the ringbuffer size. See the ftrace documentation + for more information. + + When tracing is not enabled, this option still has some + (though rather small) overhead. + + If unsure, say Y so we can help you better when problems + occur. +endmenu + +config IWLWIFI_DEVICE_TESTMODE + def_bool y + depends on IWLWIFI + depends on NL80211_TESTMODE + help + This option enables the testmode support for iwlwifi device through + NL80211_TESTMODE. This provide the capabilities of enable user space + validation applications to interacts with the device through the + generic netlink message via NL80211_TESTMODE channel. + +config IWLWIFI_P2P + def_bool y + bool "iwlwifi experimental P2P support" + depends on IWLWIFI + help + This option enables experimental P2P support for some devices + based on microcode support. Since P2P support is still under + development, this option may even enable it for some devices + now that turn out to not support it in the future due to + microcode restrictions. + + To determine if your microcode supports the experimental P2P + offered by this option, check if the driver advertises AP + support when it is loaded. + + Say Y only if you want to experiment with P2P. diff --git a/drivers/net/wireless/iwlwifi/Makefile b/drivers/net/wireless/iwlwifi/Makefile new file mode 100644 index 0000000..46823b9 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/Makefile @@ -0,0 +1,21 @@ +# common +obj-$(CPTCFG_IWLWIFI) += iwlwifi.o +iwlwifi-objs += iwl-io.o +iwlwifi-objs += iwl-drv.o +iwlwifi-objs += iwl-debug.o +iwlwifi-objs += iwl-notif-wait.o +iwlwifi-objs += iwl-eeprom-read.o iwl-eeprom-parse.o +iwlwifi-objs += iwl-phy-db.o iwl-nvm-parse.o +iwlwifi-objs += pcie/drv.o pcie/rx.o pcie/tx.o pcie/trans.o +iwlwifi-objs += iwl-1000.o iwl-2000.o iwl-5000.o iwl-6000.o iwl-7000.o + +iwlwifi-$(CPTCFG_IWLWIFI_DEVICE_TRACING) += iwl-devtrace.o +iwlwifi-$(CPTCFG_IWLWIFI_DEVICE_TESTMODE) += iwl-test.o + +ccflags-y += -D__CHECK_ENDIAN__ -I$(src) + + +obj-$(CPTCFG_IWLDVM) += dvm/ +obj-$(CPTCFG_IWLMVM) += mvm/ + +CFLAGS_iwl-devtrace.o := -I$(src) diff --git a/drivers/net/wireless/iwlwifi/dvm/Makefile b/drivers/net/wireless/iwlwifi/dvm/Makefile new file mode 100644 index 0000000..fd05801 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/Makefile @@ -0,0 +1,13 @@ +# DVM +obj-$(CPTCFG_IWLDVM) += iwldvm.o +iwldvm-objs += main.o rs.o mac80211.o ucode.o tx.o +iwldvm-objs += lib.o calib.o tt.o sta.o rx.o + +iwldvm-objs += power.o +iwldvm-objs += scan.o led.o +iwldvm-objs += rxon.o devices.o + +iwldvm-$(CPTCFG_IWLWIFI_DEBUGFS) += debugfs.o +iwldvm-$(CPTCFG_IWLWIFI_DEVICE_TESTMODE) += testmode.o + +ccflags-y += -D__CHECK_ENDIAN__ -I$(src)/../ diff --git a/drivers/net/wireless/iwlwifi/dvm/agn.h b/drivers/net/wireless/iwlwifi/dvm/agn.h new file mode 100644 index 0000000..77c85e0 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/agn.h @@ -0,0 +1,532 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef __iwl_agn_h__ +#define __iwl_agn_h__ + +#include "iwl-config.h" + +#include "dev.h" + +/* The first 11 queues (0-10) are used otherwise */ +#define IWLAGN_FIRST_AMPDU_QUEUE 11 + +/* AUX (TX during scan dwell) queue */ +#define IWL_AUX_QUEUE 10 + +#define IWL_INVALID_STATION 255 + +/* device operations */ +extern struct iwl_lib_ops iwl1000_lib; +extern struct iwl_lib_ops iwl2000_lib; +extern struct iwl_lib_ops iwl2030_lib; +extern struct iwl_lib_ops iwl5000_lib; +extern struct iwl_lib_ops iwl5150_lib; +extern struct iwl_lib_ops iwl6000_lib; +extern struct iwl_lib_ops iwl6030_lib; + + +#define TIME_UNIT 1024 + +/***************************************************** +* DRIVER STATUS FUNCTIONS +******************************************************/ +#define STATUS_RF_KILL_HW 0 +#define STATUS_CT_KILL 1 +#define STATUS_ALIVE 2 +#define STATUS_READY 3 +#define STATUS_EXIT_PENDING 5 +#define STATUS_STATISTICS 6 +#define STATUS_SCANNING 7 +#define STATUS_SCAN_ABORTING 8 +#define STATUS_SCAN_HW 9 +#define STATUS_FW_ERROR 10 +#define STATUS_CHANNEL_SWITCH_PENDING 11 +#define STATUS_SCAN_COMPLETE 12 +#define STATUS_POWER_PMI 13 +#define STATUS_SCAN_ROC_EXPIRED 14 + +struct iwl_ucode_capabilities; + +extern struct ieee80211_ops iwlagn_hw_ops; + +static inline void iwl_set_calib_hdr(struct iwl_calib_hdr *hdr, u8 cmd) +{ + hdr->op_code = cmd; + hdr->first_group = 0; + hdr->groups_num = 1; + hdr->data_valid = 1; +} + +void iwl_down(struct iwl_priv *priv); +void iwl_cancel_deferred_work(struct iwl_priv *priv); +void iwlagn_prepare_restart(struct iwl_priv *priv); +int __must_check iwl_rx_dispatch(struct iwl_op_mode *op_mode, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); + +bool iwl_check_for_ct_kill(struct iwl_priv *priv); + +void iwlagn_lift_passive_no_rx(struct iwl_priv *priv); + +/* MAC80211 */ +struct ieee80211_hw *iwl_alloc_all(void); +int iwlagn_mac_setup_register(struct iwl_priv *priv, + const struct iwl_ucode_capabilities *capa); +void iwlagn_mac_unregister(struct iwl_priv *priv); + +/* commands */ +int iwl_dvm_send_cmd(struct iwl_priv *priv, struct iwl_host_cmd *cmd); +int iwl_dvm_send_cmd_pdu(struct iwl_priv *priv, u8 id, + u32 flags, u16 len, const void *data); + +/* RXON */ +void iwl_connection_init_rx_config(struct iwl_priv *priv, + struct iwl_rxon_context *ctx); +int iwlagn_set_pan_params(struct iwl_priv *priv); +int iwlagn_commit_rxon(struct iwl_priv *priv, struct iwl_rxon_context *ctx); +void iwlagn_set_rxon_chain(struct iwl_priv *priv, struct iwl_rxon_context *ctx); +int iwlagn_mac_config(struct ieee80211_hw *hw, u32 changed); +void iwlagn_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changes); +void iwlagn_config_ht40(struct ieee80211_conf *conf, + struct iwl_rxon_context *ctx); +void iwl_set_rxon_ht(struct iwl_priv *priv, struct iwl_ht_config *ht_conf); +void iwl_set_rxon_channel(struct iwl_priv *priv, struct ieee80211_channel *ch, + struct iwl_rxon_context *ctx); +void iwl_set_flags_for_band(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + enum ieee80211_band band, + struct ieee80211_vif *vif); + +/* uCode */ +int iwl_send_bt_env(struct iwl_priv *priv, u8 action, u8 type); +void iwl_send_prio_tbl(struct iwl_priv *priv); +int iwl_init_alive_start(struct iwl_priv *priv); +int iwl_run_init_ucode(struct iwl_priv *priv); +int iwl_load_ucode_wait_alive(struct iwl_priv *priv, + enum iwl_ucode_type ucode_type); +int iwl_send_calib_results(struct iwl_priv *priv); +int iwl_calib_set(struct iwl_priv *priv, + const struct iwl_calib_hdr *cmd, int len); +void iwl_calib_free_results(struct iwl_priv *priv); +int iwl_dump_nic_event_log(struct iwl_priv *priv, bool full_log, + char **buf); +int iwlagn_hw_valid_rtc_data_addr(u32 addr); + +/* lib */ +int iwlagn_send_tx_power(struct iwl_priv *priv); +void iwlagn_temperature(struct iwl_priv *priv); +int iwlagn_txfifo_flush(struct iwl_priv *priv, u32 scd_q_msk); +void iwlagn_dev_txfifo_flush(struct iwl_priv *priv); +int iwlagn_send_beacon_cmd(struct iwl_priv *priv); +int iwl_send_statistics_request(struct iwl_priv *priv, + u8 flags, bool clear); + +static inline const struct ieee80211_supported_band *iwl_get_hw_mode( + struct iwl_priv *priv, enum ieee80211_band band) +{ + return priv->hw->wiphy->bands[band]; +} + +#ifdef CONFIG_PM_SLEEP +int iwlagn_send_patterns(struct iwl_priv *priv, + struct cfg80211_wowlan *wowlan); +int iwlagn_suspend(struct iwl_priv *priv, struct cfg80211_wowlan *wowlan); +#endif + +/* rx */ +int iwlagn_hwrate_to_mac80211_idx(u32 rate_n_flags, enum ieee80211_band band); +void iwl_setup_rx_handlers(struct iwl_priv *priv); +void iwl_chswitch_done(struct iwl_priv *priv, bool is_success); + + +/* tx */ +int iwlagn_tx_skb(struct iwl_priv *priv, + struct ieee80211_sta *sta, + struct sk_buff *skb); +int iwlagn_tx_agg_start(struct iwl_priv *priv, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 *ssn); +int iwlagn_tx_agg_oper(struct iwl_priv *priv, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u8 buf_size); +int iwlagn_tx_agg_stop(struct iwl_priv *priv, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid); +int iwlagn_tx_agg_flush(struct iwl_priv *priv, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid); +int iwlagn_rx_reply_compressed_ba(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); +int iwlagn_rx_reply_tx(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); + +static inline u32 iwl_tx_status_to_mac80211(u32 status) +{ + status &= TX_STATUS_MSK; + + switch (status) { + case TX_STATUS_SUCCESS: + case TX_STATUS_DIRECT_DONE: + return IEEE80211_TX_STAT_ACK; + case TX_STATUS_FAIL_DEST_PS: + case TX_STATUS_FAIL_PASSIVE_NO_RX: + return IEEE80211_TX_STAT_TX_FILTERED; + default: + return 0; + } +} + +static inline bool iwl_is_tx_success(u32 status) +{ + status &= TX_STATUS_MSK; + return (status == TX_STATUS_SUCCESS) || + (status == TX_STATUS_DIRECT_DONE); +} + +u8 iwl_toggle_tx_ant(struct iwl_priv *priv, u8 ant_idx, u8 valid); + +/* scan */ +void iwlagn_post_scan(struct iwl_priv *priv); +void iwlagn_disable_roc(struct iwl_priv *priv); +int iwl_force_rf_reset(struct iwl_priv *priv, bool external); +void iwl_init_scan_params(struct iwl_priv *priv); +int iwl_scan_cancel(struct iwl_priv *priv); +void iwl_scan_cancel_timeout(struct iwl_priv *priv, unsigned long ms); +void iwl_force_scan_end(struct iwl_priv *priv); +void iwl_internal_short_hw_scan(struct iwl_priv *priv); +void iwl_setup_rx_scan_handlers(struct iwl_priv *priv); +void iwl_setup_scan_deferred_work(struct iwl_priv *priv); +void iwl_cancel_scan_deferred_work(struct iwl_priv *priv); +int __must_check iwl_scan_initiate(struct iwl_priv *priv, + struct ieee80211_vif *vif, + enum iwl_scan_type scan_type, + enum ieee80211_band band); + +void iwl_scan_roc_expired(struct iwl_priv *priv); +void iwl_scan_offchannel_skb(struct iwl_priv *priv); +void iwl_scan_offchannel_skb_status(struct iwl_priv *priv); + +/* For faster active scanning, scan will move to the next channel if fewer than + * PLCP_QUIET_THRESH packets are heard on this channel within + * ACTIVE_QUIET_TIME after sending probe request. This shortens the dwell + * time if it's a quiet channel (nothing responded to our probe, and there's + * no other traffic). + * Disable "quiet" feature by setting PLCP_QUIET_THRESH to 0. */ +#define IWL_ACTIVE_QUIET_TIME cpu_to_le16(10) /* msec */ +#define IWL_PLCP_QUIET_THRESH cpu_to_le16(1) /* packets */ + +#define IWL_SCAN_CHECK_WATCHDOG (HZ * 15) + + +/* bt coex */ +void iwlagn_send_advance_bt_config(struct iwl_priv *priv); +int iwlagn_bt_coex_profile_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); +void iwlagn_bt_rx_handler_setup(struct iwl_priv *priv); +void iwlagn_bt_setup_deferred_work(struct iwl_priv *priv); +void iwlagn_bt_cancel_deferred_work(struct iwl_priv *priv); +void iwlagn_bt_coex_rssi_monitor(struct iwl_priv *priv); +void iwlagn_bt_adjust_rssi_monitor(struct iwl_priv *priv, bool rssi_ena); + +static inline bool iwl_advanced_bt_coexist(struct iwl_priv *priv) +{ + return priv->cfg->bt_params && + priv->cfg->bt_params->advanced_bt_coexist; +} + +#ifdef CPTCFG_IWLWIFI_DEBUG +const char *iwl_get_tx_fail_reason(u32 status); +const char *iwl_get_agg_tx_fail_reason(u16 status); +#else +static inline const char *iwl_get_tx_fail_reason(u32 status) { return ""; } +static inline const char *iwl_get_agg_tx_fail_reason(u16 status) { return ""; } +#endif + + +/* station management */ +int iwlagn_manage_ibss_station(struct iwl_priv *priv, + struct ieee80211_vif *vif, bool add); +#define IWL_STA_DRIVER_ACTIVE BIT(0) /* driver entry is active */ +#define IWL_STA_UCODE_ACTIVE BIT(1) /* ucode entry is active */ +#define IWL_STA_UCODE_INPROGRESS BIT(2) /* ucode entry is in process of + being activated */ +#define IWL_STA_LOCAL BIT(3) /* station state not directed by mac80211; + (this is for the IBSS BSSID stations) */ +#define IWL_STA_BCAST BIT(4) /* this station is the special bcast station */ + + +void iwl_restore_stations(struct iwl_priv *priv, struct iwl_rxon_context *ctx); +void iwl_clear_ucode_stations(struct iwl_priv *priv, + struct iwl_rxon_context *ctx); +void iwl_dealloc_bcast_stations(struct iwl_priv *priv); +int iwl_get_free_ucode_key_offset(struct iwl_priv *priv); +int iwl_send_add_sta(struct iwl_priv *priv, + struct iwl_addsta_cmd *sta, u8 flags); +int iwl_add_station_common(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + const u8 *addr, bool is_ap, + struct ieee80211_sta *sta, u8 *sta_id_r); +int iwl_remove_station(struct iwl_priv *priv, const u8 sta_id, + const u8 *addr); +void iwl_deactivate_station(struct iwl_priv *priv, const u8 sta_id, + const u8 *addr); +u8 iwl_prep_station(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + const u8 *addr, bool is_ap, struct ieee80211_sta *sta); + +int iwl_send_lq_cmd(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + struct iwl_link_quality_cmd *lq, u8 flags, bool init); +int iwl_add_sta_callback(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); +int iwl_sta_update_ht(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + struct ieee80211_sta *sta); + +bool iwl_is_ht40_tx_allowed(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_sta *sta); + +static inline int iwl_sta_id(struct ieee80211_sta *sta) +{ + if (WARN_ON(!sta)) + return IWL_INVALID_STATION; + + return ((struct iwl_station_priv *)sta->drv_priv)->sta_id; +} + +int iwlagn_alloc_bcast_station(struct iwl_priv *priv, + struct iwl_rxon_context *ctx); +int iwlagn_add_bssid_station(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + const u8 *addr, u8 *sta_id_r); +int iwl_remove_default_wep_key(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_key_conf *key); +int iwl_set_default_wep_key(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_key_conf *key); +int iwl_restore_default_wep_keys(struct iwl_priv *priv, + struct iwl_rxon_context *ctx); +int iwl_set_dynamic_key(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + struct ieee80211_key_conf *key, + struct ieee80211_sta *sta); +int iwl_remove_dynamic_key(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + struct ieee80211_key_conf *key, + struct ieee80211_sta *sta); +void iwl_update_tkip_key(struct iwl_priv *priv, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, u32 iv32, u16 *phase1key); +int iwl_sta_tx_modify_enable_tid(struct iwl_priv *priv, int sta_id, int tid); +int iwl_sta_rx_agg_start(struct iwl_priv *priv, struct ieee80211_sta *sta, + int tid, u16 ssn); +int iwl_sta_rx_agg_stop(struct iwl_priv *priv, struct ieee80211_sta *sta, + int tid); +void iwl_sta_modify_sleep_tx_count(struct iwl_priv *priv, int sta_id, int cnt); +int iwl_update_bcast_station(struct iwl_priv *priv, + struct iwl_rxon_context *ctx); +int iwl_update_bcast_stations(struct iwl_priv *priv); + +/* rate */ +static inline u32 iwl_ant_idx_to_flags(u8 ant_idx) +{ + return BIT(ant_idx) << RATE_MCS_ANT_POS; +} + +static inline u8 iwl_hw_get_rate(__le32 rate_n_flags) +{ + return le32_to_cpu(rate_n_flags) & RATE_MCS_RATE_MSK; +} + +static inline __le32 iwl_hw_set_rate_n_flags(u8 rate, u32 flags) +{ + return cpu_to_le32(flags|(u32)rate); +} + +extern int iwl_alive_start(struct iwl_priv *priv); + +/* testmode support */ +#ifdef CPTCFG_IWLWIFI_DEVICE_TESTMODE + +extern int iwlagn_mac_testmode_cmd(struct ieee80211_hw *hw, void *data, + int len); +extern int iwlagn_mac_testmode_dump(struct ieee80211_hw *hw, + struct sk_buff *skb, + struct netlink_callback *cb, + void *data, int len); +extern void iwl_testmode_init(struct iwl_priv *priv); +extern void iwl_testmode_free(struct iwl_priv *priv); + +#else + +static inline +int iwlagn_mac_testmode_cmd(struct ieee80211_hw *hw, void *data, int len) +{ + return -ENOSYS; +} + +static inline +int iwlagn_mac_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *skb, + struct netlink_callback *cb, + void *data, int len) +{ + return -ENOSYS; +} + +static inline void iwl_testmode_init(struct iwl_priv *priv) +{ +} + +static inline void iwl_testmode_free(struct iwl_priv *priv) +{ +} +#endif + +#ifdef CPTCFG_IWLWIFI_DEBUG +void iwl_print_rx_config_cmd(struct iwl_priv *priv, + enum iwl_rxon_context_id ctxid); +#else +static inline void iwl_print_rx_config_cmd(struct iwl_priv *priv, + enum iwl_rxon_context_id ctxid) +{ +} +#endif + +/* status checks */ + +static inline int iwl_is_ready(struct iwl_priv *priv) +{ + /* The adapter is 'ready' if READY EXIT_PENDING is not set */ + return test_bit(STATUS_READY, &priv->status) && + !test_bit(STATUS_EXIT_PENDING, &priv->status); +} + +static inline int iwl_is_alive(struct iwl_priv *priv) +{ + return test_bit(STATUS_ALIVE, &priv->status); +} + +static inline int iwl_is_rfkill(struct iwl_priv *priv) +{ + return test_bit(STATUS_RF_KILL_HW, &priv->status); +} + +static inline int iwl_is_ctkill(struct iwl_priv *priv) +{ + return test_bit(STATUS_CT_KILL, &priv->status); +} + +static inline int iwl_is_ready_rf(struct iwl_priv *priv) +{ + if (iwl_is_rfkill(priv)) + return 0; + + return iwl_is_ready(priv); +} + +static inline void iwl_dvm_set_pmi(struct iwl_priv *priv, bool state) +{ + if (state) + set_bit(STATUS_POWER_PMI, &priv->status); + else + clear_bit(STATUS_POWER_PMI, &priv->status); + iwl_trans_set_pmi(priv->trans, state); +} + +#ifdef CPTCFG_IWLWIFI_DEBUGFS +int iwl_dbgfs_register(struct iwl_priv *priv, struct dentry *dbgfs_dir); +#else +static inline int iwl_dbgfs_register(struct iwl_priv *priv, + struct dentry *dbgfs_dir) +{ + return 0; +} +#endif /* CPTCFG_IWLWIFI_DEBUGFS */ + +#ifdef CPTCFG_IWLWIFI_DEBUG +#define IWL_DEBUG_QUIET_RFKILL(m, fmt, args...) \ +do { \ + if (!iwl_is_rfkill((m))) \ + IWL_ERR(m, fmt, ##args); \ + else \ + __iwl_err((m)->dev, true, \ + !iwl_have_debug_level(IWL_DL_RADIO), \ + fmt, ##args); \ +} while (0) +#else +#define IWL_DEBUG_QUIET_RFKILL(m, fmt, args...) \ +do { \ + if (!iwl_is_rfkill((m))) \ + IWL_ERR(m, fmt, ##args); \ + else \ + __iwl_err((m)->dev, true, true, fmt, ##args); \ +} while (0) +#endif /* CPTCFG_IWLWIFI_DEBUG */ + +extern const char *iwl_dvm_cmd_strings[REPLY_MAX]; + +static inline const char *iwl_dvm_get_cmd_string(u8 cmd) +{ + const char *s = iwl_dvm_cmd_strings[cmd]; + if (s) + return s; + return "UNKNOWN"; +} +#endif /* __iwl_agn_h__ */ diff --git a/drivers/net/wireless/iwlwifi/dvm/calib.c b/drivers/net/wireless/iwlwifi/dvm/calib.c new file mode 100644 index 0000000..d6c4cf2 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/calib.c @@ -0,0 +1,1114 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include + +#include "iwl-trans.h" + +#include "dev.h" +#include "calib.h" +#include "agn.h" + +/***************************************************************************** + * INIT calibrations framework + *****************************************************************************/ + +/* Opaque calibration results */ +struct iwl_calib_result { + struct list_head list; + size_t cmd_len; + struct iwl_calib_hdr hdr; + /* data follows */ +}; + +struct statistics_general_data { + u32 beacon_silence_rssi_a; + u32 beacon_silence_rssi_b; + u32 beacon_silence_rssi_c; + u32 beacon_energy_a; + u32 beacon_energy_b; + u32 beacon_energy_c; +}; + +int iwl_send_calib_results(struct iwl_priv *priv) +{ + struct iwl_host_cmd hcmd = { + .id = REPLY_PHY_CALIBRATION_CMD, + .flags = CMD_SYNC, + }; + struct iwl_calib_result *res; + + list_for_each_entry(res, &priv->calib_results, list) { + int ret; + + hcmd.len[0] = res->cmd_len; + hcmd.data[0] = &res->hdr; + hcmd.dataflags[0] = IWL_HCMD_DFL_NOCOPY; + ret = iwl_dvm_send_cmd(priv, &hcmd); + if (ret) { + IWL_ERR(priv, "Error %d on calib cmd %d\n", + ret, res->hdr.op_code); + return ret; + } + } + + return 0; +} + +int iwl_calib_set(struct iwl_priv *priv, + const struct iwl_calib_hdr *cmd, int len) +{ + struct iwl_calib_result *res, *tmp; + + res = kmalloc(sizeof(*res) + len - sizeof(struct iwl_calib_hdr), + GFP_ATOMIC); + if (!res) + return -ENOMEM; + memcpy(&res->hdr, cmd, len); + res->cmd_len = len; + + list_for_each_entry(tmp, &priv->calib_results, list) { + if (tmp->hdr.op_code == res->hdr.op_code) { + list_replace(&tmp->list, &res->list); + kfree(tmp); + return 0; + } + } + + /* wasn't in list already */ + list_add_tail(&res->list, &priv->calib_results); + + return 0; +} + +void iwl_calib_free_results(struct iwl_priv *priv) +{ + struct iwl_calib_result *res, *tmp; + + list_for_each_entry_safe(res, tmp, &priv->calib_results, list) { + list_del(&res->list); + kfree(res); + } +} + +/***************************************************************************** + * RUNTIME calibrations framework + *****************************************************************************/ + +/* "false alarms" are signals that our DSP tries to lock onto, + * but then determines that they are either noise, or transmissions + * from a distant wireless network (also "noise", really) that get + * "stepped on" by stronger transmissions within our own network. + * This algorithm attempts to set a sensitivity level that is high + * enough to receive all of our own network traffic, but not so + * high that our DSP gets too busy trying to lock onto non-network + * activity/noise. */ +static int iwl_sens_energy_cck(struct iwl_priv *priv, + u32 norm_fa, + u32 rx_enable_time, + struct statistics_general_data *rx_info) +{ + u32 max_nrg_cck = 0; + int i = 0; + u8 max_silence_rssi = 0; + u32 silence_ref = 0; + u8 silence_rssi_a = 0; + u8 silence_rssi_b = 0; + u8 silence_rssi_c = 0; + u32 val; + + /* "false_alarms" values below are cross-multiplications to assess the + * numbers of false alarms within the measured period of actual Rx + * (Rx is off when we're txing), vs the min/max expected false alarms + * (some should be expected if rx is sensitive enough) in a + * hypothetical listening period of 200 time units (TU), 204.8 msec: + * + * MIN_FA/fixed-time < false_alarms/actual-rx-time < MAX_FA/beacon-time + * + * */ + u32 false_alarms = norm_fa * 200 * 1024; + u32 max_false_alarms = MAX_FA_CCK * rx_enable_time; + u32 min_false_alarms = MIN_FA_CCK * rx_enable_time; + struct iwl_sensitivity_data *data = NULL; + const struct iwl_sensitivity_ranges *ranges = priv->hw_params.sens; + + data = &(priv->sensitivity_data); + + data->nrg_auto_corr_silence_diff = 0; + + /* Find max silence rssi among all 3 receivers. + * This is background noise, which may include transmissions from other + * networks, measured during silence before our network's beacon */ + silence_rssi_a = (u8)((rx_info->beacon_silence_rssi_a & + ALL_BAND_FILTER) >> 8); + silence_rssi_b = (u8)((rx_info->beacon_silence_rssi_b & + ALL_BAND_FILTER) >> 8); + silence_rssi_c = (u8)((rx_info->beacon_silence_rssi_c & + ALL_BAND_FILTER) >> 8); + + val = max(silence_rssi_b, silence_rssi_c); + max_silence_rssi = max(silence_rssi_a, (u8) val); + + /* Store silence rssi in 20-beacon history table */ + data->nrg_silence_rssi[data->nrg_silence_idx] = max_silence_rssi; + data->nrg_silence_idx++; + if (data->nrg_silence_idx >= NRG_NUM_PREV_STAT_L) + data->nrg_silence_idx = 0; + + /* Find max silence rssi across 20 beacon history */ + for (i = 0; i < NRG_NUM_PREV_STAT_L; i++) { + val = data->nrg_silence_rssi[i]; + silence_ref = max(silence_ref, val); + } + IWL_DEBUG_CALIB(priv, "silence a %u, b %u, c %u, 20-bcn max %u\n", + silence_rssi_a, silence_rssi_b, silence_rssi_c, + silence_ref); + + /* Find max rx energy (min value!) among all 3 receivers, + * measured during beacon frame. + * Save it in 10-beacon history table. */ + i = data->nrg_energy_idx; + val = min(rx_info->beacon_energy_b, rx_info->beacon_energy_c); + data->nrg_value[i] = min(rx_info->beacon_energy_a, val); + + data->nrg_energy_idx++; + if (data->nrg_energy_idx >= 10) + data->nrg_energy_idx = 0; + + /* Find min rx energy (max value) across 10 beacon history. + * This is the minimum signal level that we want to receive well. + * Add backoff (margin so we don't miss slightly lower energy frames). + * This establishes an upper bound (min value) for energy threshold. */ + max_nrg_cck = data->nrg_value[0]; + for (i = 1; i < 10; i++) + max_nrg_cck = (u32) max(max_nrg_cck, (data->nrg_value[i])); + max_nrg_cck += 6; + + IWL_DEBUG_CALIB(priv, "rx energy a %u, b %u, c %u, 10-bcn max/min %u\n", + rx_info->beacon_energy_a, rx_info->beacon_energy_b, + rx_info->beacon_energy_c, max_nrg_cck - 6); + + /* Count number of consecutive beacons with fewer-than-desired + * false alarms. */ + if (false_alarms < min_false_alarms) + data->num_in_cck_no_fa++; + else + data->num_in_cck_no_fa = 0; + IWL_DEBUG_CALIB(priv, "consecutive bcns with few false alarms = %u\n", + data->num_in_cck_no_fa); + + /* If we got too many false alarms this time, reduce sensitivity */ + if ((false_alarms > max_false_alarms) && + (data->auto_corr_cck > AUTO_CORR_MAX_TH_CCK)) { + IWL_DEBUG_CALIB(priv, "norm FA %u > max FA %u\n", + false_alarms, max_false_alarms); + IWL_DEBUG_CALIB(priv, "... reducing sensitivity\n"); + data->nrg_curr_state = IWL_FA_TOO_MANY; + /* Store for "fewer than desired" on later beacon */ + data->nrg_silence_ref = silence_ref; + + /* increase energy threshold (reduce nrg value) + * to decrease sensitivity */ + data->nrg_th_cck = data->nrg_th_cck - NRG_STEP_CCK; + /* Else if we got fewer than desired, increase sensitivity */ + } else if (false_alarms < min_false_alarms) { + data->nrg_curr_state = IWL_FA_TOO_FEW; + + /* Compare silence level with silence level for most recent + * healthy number or too many false alarms */ + data->nrg_auto_corr_silence_diff = (s32)data->nrg_silence_ref - + (s32)silence_ref; + + IWL_DEBUG_CALIB(priv, "norm FA %u < min FA %u, silence diff %d\n", + false_alarms, min_false_alarms, + data->nrg_auto_corr_silence_diff); + + /* Increase value to increase sensitivity, but only if: + * 1a) previous beacon did *not* have *too many* false alarms + * 1b) AND there's a significant difference in Rx levels + * from a previous beacon with too many, or healthy # FAs + * OR 2) We've seen a lot of beacons (100) with too few + * false alarms */ + if ((data->nrg_prev_state != IWL_FA_TOO_MANY) && + ((data->nrg_auto_corr_silence_diff > NRG_DIFF) || + (data->num_in_cck_no_fa > MAX_NUMBER_CCK_NO_FA))) { + + IWL_DEBUG_CALIB(priv, "... increasing sensitivity\n"); + /* Increase nrg value to increase sensitivity */ + val = data->nrg_th_cck + NRG_STEP_CCK; + data->nrg_th_cck = min((u32)ranges->min_nrg_cck, val); + } else { + IWL_DEBUG_CALIB(priv, "... but not changing sensitivity\n"); + } + + /* Else we got a healthy number of false alarms, keep status quo */ + } else { + IWL_DEBUG_CALIB(priv, " FA in safe zone\n"); + data->nrg_curr_state = IWL_FA_GOOD_RANGE; + + /* Store for use in "fewer than desired" with later beacon */ + data->nrg_silence_ref = silence_ref; + + /* If previous beacon had too many false alarms, + * give it some extra margin by reducing sensitivity again + * (but don't go below measured energy of desired Rx) */ + if (IWL_FA_TOO_MANY == data->nrg_prev_state) { + IWL_DEBUG_CALIB(priv, "... increasing margin\n"); + if (data->nrg_th_cck > (max_nrg_cck + NRG_MARGIN)) + data->nrg_th_cck -= NRG_MARGIN; + else + data->nrg_th_cck = max_nrg_cck; + } + } + + /* Make sure the energy threshold does not go above the measured + * energy of the desired Rx signals (reduced by backoff margin), + * or else we might start missing Rx frames. + * Lower value is higher energy, so we use max()! + */ + data->nrg_th_cck = max(max_nrg_cck, data->nrg_th_cck); + IWL_DEBUG_CALIB(priv, "new nrg_th_cck %u\n", data->nrg_th_cck); + + data->nrg_prev_state = data->nrg_curr_state; + + /* Auto-correlation CCK algorithm */ + if (false_alarms > min_false_alarms) { + + /* increase auto_corr values to decrease sensitivity + * so the DSP won't be disturbed by the noise + */ + if (data->auto_corr_cck < AUTO_CORR_MAX_TH_CCK) + data->auto_corr_cck = AUTO_CORR_MAX_TH_CCK + 1; + else { + val = data->auto_corr_cck + AUTO_CORR_STEP_CCK; + data->auto_corr_cck = + min((u32)ranges->auto_corr_max_cck, val); + } + val = data->auto_corr_cck_mrc + AUTO_CORR_STEP_CCK; + data->auto_corr_cck_mrc = + min((u32)ranges->auto_corr_max_cck_mrc, val); + } else if ((false_alarms < min_false_alarms) && + ((data->nrg_auto_corr_silence_diff > NRG_DIFF) || + (data->num_in_cck_no_fa > MAX_NUMBER_CCK_NO_FA))) { + + /* Decrease auto_corr values to increase sensitivity */ + val = data->auto_corr_cck - AUTO_CORR_STEP_CCK; + data->auto_corr_cck = + max((u32)ranges->auto_corr_min_cck, val); + val = data->auto_corr_cck_mrc - AUTO_CORR_STEP_CCK; + data->auto_corr_cck_mrc = + max((u32)ranges->auto_corr_min_cck_mrc, val); + } + + return 0; +} + + +static int iwl_sens_auto_corr_ofdm(struct iwl_priv *priv, + u32 norm_fa, + u32 rx_enable_time) +{ + u32 val; + u32 false_alarms = norm_fa * 200 * 1024; + u32 max_false_alarms = MAX_FA_OFDM * rx_enable_time; + u32 min_false_alarms = MIN_FA_OFDM * rx_enable_time; + struct iwl_sensitivity_data *data = NULL; + const struct iwl_sensitivity_ranges *ranges = priv->hw_params.sens; + + data = &(priv->sensitivity_data); + + /* If we got too many false alarms this time, reduce sensitivity */ + if (false_alarms > max_false_alarms) { + + IWL_DEBUG_CALIB(priv, "norm FA %u > max FA %u)\n", + false_alarms, max_false_alarms); + + val = data->auto_corr_ofdm + AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm = + min((u32)ranges->auto_corr_max_ofdm, val); + + val = data->auto_corr_ofdm_mrc + AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_mrc = + min((u32)ranges->auto_corr_max_ofdm_mrc, val); + + val = data->auto_corr_ofdm_x1 + AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_x1 = + min((u32)ranges->auto_corr_max_ofdm_x1, val); + + val = data->auto_corr_ofdm_mrc_x1 + AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_mrc_x1 = + min((u32)ranges->auto_corr_max_ofdm_mrc_x1, val); + } + + /* Else if we got fewer than desired, increase sensitivity */ + else if (false_alarms < min_false_alarms) { + + IWL_DEBUG_CALIB(priv, "norm FA %u < min FA %u\n", + false_alarms, min_false_alarms); + + val = data->auto_corr_ofdm - AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm = + max((u32)ranges->auto_corr_min_ofdm, val); + + val = data->auto_corr_ofdm_mrc - AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_mrc = + max((u32)ranges->auto_corr_min_ofdm_mrc, val); + + val = data->auto_corr_ofdm_x1 - AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_x1 = + max((u32)ranges->auto_corr_min_ofdm_x1, val); + + val = data->auto_corr_ofdm_mrc_x1 - AUTO_CORR_STEP_OFDM; + data->auto_corr_ofdm_mrc_x1 = + max((u32)ranges->auto_corr_min_ofdm_mrc_x1, val); + } else { + IWL_DEBUG_CALIB(priv, "min FA %u < norm FA %u < max FA %u OK\n", + min_false_alarms, false_alarms, max_false_alarms); + } + return 0; +} + +static void iwl_prepare_legacy_sensitivity_tbl(struct iwl_priv *priv, + struct iwl_sensitivity_data *data, + __le16 *tbl) +{ + tbl[HD_AUTO_CORR32_X4_TH_ADD_MIN_INDEX] = + cpu_to_le16((u16)data->auto_corr_ofdm); + tbl[HD_AUTO_CORR32_X4_TH_ADD_MIN_MRC_INDEX] = + cpu_to_le16((u16)data->auto_corr_ofdm_mrc); + tbl[HD_AUTO_CORR32_X1_TH_ADD_MIN_INDEX] = + cpu_to_le16((u16)data->auto_corr_ofdm_x1); + tbl[HD_AUTO_CORR32_X1_TH_ADD_MIN_MRC_INDEX] = + cpu_to_le16((u16)data->auto_corr_ofdm_mrc_x1); + + tbl[HD_AUTO_CORR40_X4_TH_ADD_MIN_INDEX] = + cpu_to_le16((u16)data->auto_corr_cck); + tbl[HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_INDEX] = + cpu_to_le16((u16)data->auto_corr_cck_mrc); + + tbl[HD_MIN_ENERGY_CCK_DET_INDEX] = + cpu_to_le16((u16)data->nrg_th_cck); + tbl[HD_MIN_ENERGY_OFDM_DET_INDEX] = + cpu_to_le16((u16)data->nrg_th_ofdm); + + tbl[HD_BARKER_CORR_TH_ADD_MIN_INDEX] = + cpu_to_le16(data->barker_corr_th_min); + tbl[HD_BARKER_CORR_TH_ADD_MIN_MRC_INDEX] = + cpu_to_le16(data->barker_corr_th_min_mrc); + tbl[HD_OFDM_ENERGY_TH_IN_INDEX] = + cpu_to_le16(data->nrg_th_cca); + + IWL_DEBUG_CALIB(priv, "ofdm: ac %u mrc %u x1 %u mrc_x1 %u thresh %u\n", + data->auto_corr_ofdm, data->auto_corr_ofdm_mrc, + data->auto_corr_ofdm_x1, data->auto_corr_ofdm_mrc_x1, + data->nrg_th_ofdm); + + IWL_DEBUG_CALIB(priv, "cck: ac %u mrc %u thresh %u\n", + data->auto_corr_cck, data->auto_corr_cck_mrc, + data->nrg_th_cck); +} + +/* Prepare a SENSITIVITY_CMD, send to uCode if values have changed */ +static int iwl_sensitivity_write(struct iwl_priv *priv) +{ + struct iwl_sensitivity_cmd cmd; + struct iwl_sensitivity_data *data = NULL; + struct iwl_host_cmd cmd_out = { + .id = SENSITIVITY_CMD, + .len = { sizeof(struct iwl_sensitivity_cmd), }, + .flags = CMD_ASYNC, + .data = { &cmd, }, + }; + + data = &(priv->sensitivity_data); + + memset(&cmd, 0, sizeof(cmd)); + + iwl_prepare_legacy_sensitivity_tbl(priv, data, &cmd.table[0]); + + /* Update uCode's "work" table, and copy it to DSP */ + cmd.control = SENSITIVITY_CMD_CONTROL_WORK_TABLE; + + /* Don't send command to uCode if nothing has changed */ + if (!memcmp(&cmd.table[0], &(priv->sensitivity_tbl[0]), + sizeof(u16)*HD_TABLE_SIZE)) { + IWL_DEBUG_CALIB(priv, "No change in SENSITIVITY_CMD\n"); + return 0; + } + + /* Copy table for comparison next time */ + memcpy(&(priv->sensitivity_tbl[0]), &(cmd.table[0]), + sizeof(u16)*HD_TABLE_SIZE); + + return iwl_dvm_send_cmd(priv, &cmd_out); +} + +/* Prepare a SENSITIVITY_CMD, send to uCode if values have changed */ +static int iwl_enhance_sensitivity_write(struct iwl_priv *priv) +{ + struct iwl_enhance_sensitivity_cmd cmd; + struct iwl_sensitivity_data *data = NULL; + struct iwl_host_cmd cmd_out = { + .id = SENSITIVITY_CMD, + .len = { sizeof(struct iwl_enhance_sensitivity_cmd), }, + .flags = CMD_ASYNC, + .data = { &cmd, }, + }; + + data = &(priv->sensitivity_data); + + memset(&cmd, 0, sizeof(cmd)); + + iwl_prepare_legacy_sensitivity_tbl(priv, data, &cmd.enhance_table[0]); + + if (priv->cfg->base_params->hd_v2) { + cmd.enhance_table[HD_INA_NON_SQUARE_DET_OFDM_INDEX] = + HD_INA_NON_SQUARE_DET_OFDM_DATA_V2; + cmd.enhance_table[HD_INA_NON_SQUARE_DET_CCK_INDEX] = + HD_INA_NON_SQUARE_DET_CCK_DATA_V2; + cmd.enhance_table[HD_CORR_11_INSTEAD_OF_CORR_9_EN_INDEX] = + HD_CORR_11_INSTEAD_OF_CORR_9_EN_DATA_V2; + cmd.enhance_table[HD_OFDM_NON_SQUARE_DET_SLOPE_MRC_INDEX] = + HD_OFDM_NON_SQUARE_DET_SLOPE_MRC_DATA_V2; + cmd.enhance_table[HD_OFDM_NON_SQUARE_DET_INTERCEPT_MRC_INDEX] = + HD_OFDM_NON_SQUARE_DET_INTERCEPT_MRC_DATA_V2; + cmd.enhance_table[HD_OFDM_NON_SQUARE_DET_SLOPE_INDEX] = + HD_OFDM_NON_SQUARE_DET_SLOPE_DATA_V2; + cmd.enhance_table[HD_OFDM_NON_SQUARE_DET_INTERCEPT_INDEX] = + HD_OFDM_NON_SQUARE_DET_INTERCEPT_DATA_V2; + cmd.enhance_table[HD_CCK_NON_SQUARE_DET_SLOPE_MRC_INDEX] = + HD_CCK_NON_SQUARE_DET_SLOPE_MRC_DATA_V2; + cmd.enhance_table[HD_CCK_NON_SQUARE_DET_INTERCEPT_MRC_INDEX] = + HD_CCK_NON_SQUARE_DET_INTERCEPT_MRC_DATA_V2; + cmd.enhance_table[HD_CCK_NON_SQUARE_DET_SLOPE_INDEX] = + HD_CCK_NON_SQUARE_DET_SLOPE_DATA_V2; + cmd.enhance_table[HD_CCK_NON_SQUARE_DET_INTERCEPT_INDEX] = + HD_CCK_NON_SQUARE_DET_INTERCEPT_DATA_V2; + } else { + cmd.enhance_table[HD_INA_NON_SQUARE_DET_OFDM_INDEX] = + HD_INA_NON_SQUARE_DET_OFDM_DATA_V1; + cmd.enhance_table[HD_INA_NON_SQUARE_DET_CCK_INDEX] = + HD_INA_NON_SQUARE_DET_CCK_DATA_V1; + cmd.enhance_table[HD_CORR_11_INSTEAD_OF_CORR_9_EN_INDEX] = + HD_CORR_11_INSTEAD_OF_CORR_9_EN_DATA_V1; + cmd.enhance_table[HD_OFDM_NON_SQUARE_DET_SLOPE_MRC_INDEX] = + HD_OFDM_NON_SQUARE_DET_SLOPE_MRC_DATA_V1; + cmd.enhance_table[HD_OFDM_NON_SQUARE_DET_INTERCEPT_MRC_INDEX] = + HD_OFDM_NON_SQUARE_DET_INTERCEPT_MRC_DATA_V1; + cmd.enhance_table[HD_OFDM_NON_SQUARE_DET_SLOPE_INDEX] = + HD_OFDM_NON_SQUARE_DET_SLOPE_DATA_V1; + cmd.enhance_table[HD_OFDM_NON_SQUARE_DET_INTERCEPT_INDEX] = + HD_OFDM_NON_SQUARE_DET_INTERCEPT_DATA_V1; + cmd.enhance_table[HD_CCK_NON_SQUARE_DET_SLOPE_MRC_INDEX] = + HD_CCK_NON_SQUARE_DET_SLOPE_MRC_DATA_V1; + cmd.enhance_table[HD_CCK_NON_SQUARE_DET_INTERCEPT_MRC_INDEX] = + HD_CCK_NON_SQUARE_DET_INTERCEPT_MRC_DATA_V1; + cmd.enhance_table[HD_CCK_NON_SQUARE_DET_SLOPE_INDEX] = + HD_CCK_NON_SQUARE_DET_SLOPE_DATA_V1; + cmd.enhance_table[HD_CCK_NON_SQUARE_DET_INTERCEPT_INDEX] = + HD_CCK_NON_SQUARE_DET_INTERCEPT_DATA_V1; + } + + /* Update uCode's "work" table, and copy it to DSP */ + cmd.control = SENSITIVITY_CMD_CONTROL_WORK_TABLE; + + /* Don't send command to uCode if nothing has changed */ + if (!memcmp(&cmd.enhance_table[0], &(priv->sensitivity_tbl[0]), + sizeof(u16)*HD_TABLE_SIZE) && + !memcmp(&cmd.enhance_table[HD_INA_NON_SQUARE_DET_OFDM_INDEX], + &(priv->enhance_sensitivity_tbl[0]), + sizeof(u16)*ENHANCE_HD_TABLE_ENTRIES)) { + IWL_DEBUG_CALIB(priv, "No change in SENSITIVITY_CMD\n"); + return 0; + } + + /* Copy table for comparison next time */ + memcpy(&(priv->sensitivity_tbl[0]), &(cmd.enhance_table[0]), + sizeof(u16)*HD_TABLE_SIZE); + memcpy(&(priv->enhance_sensitivity_tbl[0]), + &(cmd.enhance_table[HD_INA_NON_SQUARE_DET_OFDM_INDEX]), + sizeof(u16)*ENHANCE_HD_TABLE_ENTRIES); + + return iwl_dvm_send_cmd(priv, &cmd_out); +} + +void iwl_init_sensitivity(struct iwl_priv *priv) +{ + int ret = 0; + int i; + struct iwl_sensitivity_data *data = NULL; + const struct iwl_sensitivity_ranges *ranges = priv->hw_params.sens; + + if (priv->calib_disabled & IWL_SENSITIVITY_CALIB_DISABLED) + return; + + IWL_DEBUG_CALIB(priv, "Start iwl_init_sensitivity\n"); + + /* Clear driver's sensitivity algo data */ + data = &(priv->sensitivity_data); + + if (ranges == NULL) + return; + + memset(data, 0, sizeof(struct iwl_sensitivity_data)); + + data->num_in_cck_no_fa = 0; + data->nrg_curr_state = IWL_FA_TOO_MANY; + data->nrg_prev_state = IWL_FA_TOO_MANY; + data->nrg_silence_ref = 0; + data->nrg_silence_idx = 0; + data->nrg_energy_idx = 0; + + for (i = 0; i < 10; i++) + data->nrg_value[i] = 0; + + for (i = 0; i < NRG_NUM_PREV_STAT_L; i++) + data->nrg_silence_rssi[i] = 0; + + data->auto_corr_ofdm = ranges->auto_corr_min_ofdm; + data->auto_corr_ofdm_mrc = ranges->auto_corr_min_ofdm_mrc; + data->auto_corr_ofdm_x1 = ranges->auto_corr_min_ofdm_x1; + data->auto_corr_ofdm_mrc_x1 = ranges->auto_corr_min_ofdm_mrc_x1; + data->auto_corr_cck = AUTO_CORR_CCK_MIN_VAL_DEF; + data->auto_corr_cck_mrc = ranges->auto_corr_min_cck_mrc; + data->nrg_th_cck = ranges->nrg_th_cck; + data->nrg_th_ofdm = ranges->nrg_th_ofdm; + data->barker_corr_th_min = ranges->barker_corr_th_min; + data->barker_corr_th_min_mrc = ranges->barker_corr_th_min_mrc; + data->nrg_th_cca = ranges->nrg_th_cca; + + data->last_bad_plcp_cnt_ofdm = 0; + data->last_fa_cnt_ofdm = 0; + data->last_bad_plcp_cnt_cck = 0; + data->last_fa_cnt_cck = 0; + + if (priv->fw->enhance_sensitivity_table) + ret |= iwl_enhance_sensitivity_write(priv); + else + ret |= iwl_sensitivity_write(priv); + IWL_DEBUG_CALIB(priv, "<calib_disabled & IWL_SENSITIVITY_CALIB_DISABLED) + return; + + data = &(priv->sensitivity_data); + + if (!iwl_is_any_associated(priv)) { + IWL_DEBUG_CALIB(priv, "<< - not associated\n"); + return; + } + + spin_lock_bh(&priv->statistics.lock); + rx_info = &priv->statistics.rx_non_phy; + ofdm = &priv->statistics.rx_ofdm; + cck = &priv->statistics.rx_cck; + if (rx_info->interference_data_flag != INTERFERENCE_DATA_AVAILABLE) { + IWL_DEBUG_CALIB(priv, "<< invalid data.\n"); + spin_unlock_bh(&priv->statistics.lock); + return; + } + + /* Extract Statistics: */ + rx_enable_time = le32_to_cpu(rx_info->channel_load); + fa_cck = le32_to_cpu(cck->false_alarm_cnt); + fa_ofdm = le32_to_cpu(ofdm->false_alarm_cnt); + bad_plcp_cck = le32_to_cpu(cck->plcp_err); + bad_plcp_ofdm = le32_to_cpu(ofdm->plcp_err); + + statis.beacon_silence_rssi_a = + le32_to_cpu(rx_info->beacon_silence_rssi_a); + statis.beacon_silence_rssi_b = + le32_to_cpu(rx_info->beacon_silence_rssi_b); + statis.beacon_silence_rssi_c = + le32_to_cpu(rx_info->beacon_silence_rssi_c); + statis.beacon_energy_a = + le32_to_cpu(rx_info->beacon_energy_a); + statis.beacon_energy_b = + le32_to_cpu(rx_info->beacon_energy_b); + statis.beacon_energy_c = + le32_to_cpu(rx_info->beacon_energy_c); + + spin_unlock_bh(&priv->statistics.lock); + + IWL_DEBUG_CALIB(priv, "rx_enable_time = %u usecs\n", rx_enable_time); + + if (!rx_enable_time) { + IWL_DEBUG_CALIB(priv, "<< RX Enable Time == 0!\n"); + return; + } + + /* These statistics increase monotonically, and do not reset + * at each beacon. Calculate difference from last value, or just + * use the new statistics value if it has reset or wrapped around. */ + if (data->last_bad_plcp_cnt_cck > bad_plcp_cck) + data->last_bad_plcp_cnt_cck = bad_plcp_cck; + else { + bad_plcp_cck -= data->last_bad_plcp_cnt_cck; + data->last_bad_plcp_cnt_cck += bad_plcp_cck; + } + + if (data->last_bad_plcp_cnt_ofdm > bad_plcp_ofdm) + data->last_bad_plcp_cnt_ofdm = bad_plcp_ofdm; + else { + bad_plcp_ofdm -= data->last_bad_plcp_cnt_ofdm; + data->last_bad_plcp_cnt_ofdm += bad_plcp_ofdm; + } + + if (data->last_fa_cnt_ofdm > fa_ofdm) + data->last_fa_cnt_ofdm = fa_ofdm; + else { + fa_ofdm -= data->last_fa_cnt_ofdm; + data->last_fa_cnt_ofdm += fa_ofdm; + } + + if (data->last_fa_cnt_cck > fa_cck) + data->last_fa_cnt_cck = fa_cck; + else { + fa_cck -= data->last_fa_cnt_cck; + data->last_fa_cnt_cck += fa_cck; + } + + /* Total aborted signal locks */ + norm_fa_ofdm = fa_ofdm + bad_plcp_ofdm; + norm_fa_cck = fa_cck + bad_plcp_cck; + + IWL_DEBUG_CALIB(priv, "cck: fa %u badp %u ofdm: fa %u badp %u\n", fa_cck, + bad_plcp_cck, fa_ofdm, bad_plcp_ofdm); + + iwl_sens_auto_corr_ofdm(priv, norm_fa_ofdm, rx_enable_time); + iwl_sens_energy_cck(priv, norm_fa_cck, rx_enable_time, &statis); + if (priv->fw->enhance_sensitivity_table) + iwl_enhance_sensitivity_write(priv); + else + iwl_sensitivity_write(priv); +} + +static inline u8 find_first_chain(u8 mask) +{ + if (mask & ANT_A) + return CHAIN_A; + if (mask & ANT_B) + return CHAIN_B; + return CHAIN_C; +} + +/** + * Run disconnected antenna algorithm to find out which antennas are + * disconnected. + */ +static void iwl_find_disconn_antenna(struct iwl_priv *priv, u32* average_sig, + struct iwl_chain_noise_data *data) +{ + u32 active_chains = 0; + u32 max_average_sig; + u16 max_average_sig_antenna_i; + u8 num_tx_chains; + u8 first_chain; + u16 i = 0; + + average_sig[0] = data->chain_signal_a / IWL_CAL_NUM_BEACONS; + average_sig[1] = data->chain_signal_b / IWL_CAL_NUM_BEACONS; + average_sig[2] = data->chain_signal_c / IWL_CAL_NUM_BEACONS; + + if (average_sig[0] >= average_sig[1]) { + max_average_sig = average_sig[0]; + max_average_sig_antenna_i = 0; + active_chains = (1 << max_average_sig_antenna_i); + } else { + max_average_sig = average_sig[1]; + max_average_sig_antenna_i = 1; + active_chains = (1 << max_average_sig_antenna_i); + } + + if (average_sig[2] >= max_average_sig) { + max_average_sig = average_sig[2]; + max_average_sig_antenna_i = 2; + active_chains = (1 << max_average_sig_antenna_i); + } + + IWL_DEBUG_CALIB(priv, "average_sig: a %d b %d c %d\n", + average_sig[0], average_sig[1], average_sig[2]); + IWL_DEBUG_CALIB(priv, "max_average_sig = %d, antenna %d\n", + max_average_sig, max_average_sig_antenna_i); + + /* Compare signal strengths for all 3 receivers. */ + for (i = 0; i < NUM_RX_CHAINS; i++) { + if (i != max_average_sig_antenna_i) { + s32 rssi_delta = (max_average_sig - average_sig[i]); + + /* If signal is very weak, compared with + * strongest, mark it as disconnected. */ + if (rssi_delta > MAXIMUM_ALLOWED_PATHLOSS) + data->disconn_array[i] = 1; + else + active_chains |= (1 << i); + IWL_DEBUG_CALIB(priv, "i = %d rssiDelta = %d " + "disconn_array[i] = %d\n", + i, rssi_delta, data->disconn_array[i]); + } + } + + /* + * The above algorithm sometimes fails when the ucode + * reports 0 for all chains. It's not clear why that + * happens to start with, but it is then causing trouble + * because this can make us enable more chains than the + * hardware really has. + * + * To be safe, simply mask out any chains that we know + * are not on the device. + */ + active_chains &= priv->nvm_data->valid_rx_ant; + + num_tx_chains = 0; + for (i = 0; i < NUM_RX_CHAINS; i++) { + /* loops on all the bits of + * priv->hw_setting.valid_tx_ant */ + u8 ant_msk = (1 << i); + if (!(priv->nvm_data->valid_tx_ant & ant_msk)) + continue; + + num_tx_chains++; + if (data->disconn_array[i] == 0) + /* there is a Tx antenna connected */ + break; + if (num_tx_chains == priv->hw_params.tx_chains_num && + data->disconn_array[i]) { + /* + * If all chains are disconnected + * connect the first valid tx chain + */ + first_chain = + find_first_chain(priv->nvm_data->valid_tx_ant); + data->disconn_array[first_chain] = 0; + active_chains |= BIT(first_chain); + IWL_DEBUG_CALIB(priv, + "All Tx chains are disconnected W/A - declare %d as connected\n", + first_chain); + break; + } + } + + if (active_chains != priv->nvm_data->valid_rx_ant && + active_chains != priv->chain_noise_data.active_chains) + IWL_DEBUG_CALIB(priv, + "Detected that not all antennas are connected! " + "Connected: %#x, valid: %#x.\n", + active_chains, + priv->nvm_data->valid_rx_ant); + + /* Save for use within RXON, TX, SCAN commands, etc. */ + data->active_chains = active_chains; + IWL_DEBUG_CALIB(priv, "active_chains (bitwise) = 0x%x\n", + active_chains); +} + +static void iwlagn_gain_computation(struct iwl_priv *priv, + u32 average_noise[NUM_RX_CHAINS], + u8 default_chain) +{ + int i; + s32 delta_g; + struct iwl_chain_noise_data *data = &priv->chain_noise_data; + + /* + * Find Gain Code for the chains based on "default chain" + */ + for (i = default_chain + 1; i < NUM_RX_CHAINS; i++) { + if ((data->disconn_array[i])) { + data->delta_gain_code[i] = 0; + continue; + } + + delta_g = (priv->cfg->base_params->chain_noise_scale * + ((s32)average_noise[default_chain] - + (s32)average_noise[i])) / 1500; + + /* bound gain by 2 bits value max, 3rd bit is sign */ + data->delta_gain_code[i] = + min(abs(delta_g), + (long) CHAIN_NOISE_MAX_DELTA_GAIN_CODE); + + if (delta_g < 0) + /* + * set negative sign ... + * note to Intel developers: This is uCode API format, + * not the format of any internal device registers. + * Do not change this format for e.g. 6050 or similar + * devices. Change format only if more resolution + * (i.e. more than 2 bits magnitude) is needed. + */ + data->delta_gain_code[i] |= (1 << 2); + } + + IWL_DEBUG_CALIB(priv, "Delta gains: ANT_B = %d ANT_C = %d\n", + data->delta_gain_code[1], data->delta_gain_code[2]); + + if (!data->radio_write) { + struct iwl_calib_chain_noise_gain_cmd cmd; + + memset(&cmd, 0, sizeof(cmd)); + + iwl_set_calib_hdr(&cmd.hdr, + priv->phy_calib_chain_noise_gain_cmd); + cmd.delta_gain_1 = data->delta_gain_code[1]; + cmd.delta_gain_2 = data->delta_gain_code[2]; + iwl_dvm_send_cmd_pdu(priv, REPLY_PHY_CALIBRATION_CMD, + CMD_ASYNC, sizeof(cmd), &cmd); + + data->radio_write = 1; + data->state = IWL_CHAIN_NOISE_CALIBRATED; + } +} + +/* + * Accumulate 16 beacons of signal and noise statistics for each of + * 3 receivers/antennas/rx-chains, then figure out: + * 1) Which antennas are connected. + * 2) Differential rx gain settings to balance the 3 receivers. + */ +void iwl_chain_noise_calibration(struct iwl_priv *priv) +{ + struct iwl_chain_noise_data *data = NULL; + + u32 chain_noise_a; + u32 chain_noise_b; + u32 chain_noise_c; + u32 chain_sig_a; + u32 chain_sig_b; + u32 chain_sig_c; + u32 average_sig[NUM_RX_CHAINS] = {INITIALIZATION_VALUE}; + u32 average_noise[NUM_RX_CHAINS] = {INITIALIZATION_VALUE}; + u32 min_average_noise = MIN_AVERAGE_NOISE_MAX_VALUE; + u16 min_average_noise_antenna_i = INITIALIZATION_VALUE; + u16 i = 0; + u16 rxon_chnum = INITIALIZATION_VALUE; + u16 stat_chnum = INITIALIZATION_VALUE; + u8 rxon_band24; + u8 stat_band24; + struct statistics_rx_non_phy *rx_info; + + /* + * MULTI-FIXME: + * When we support multiple interfaces on different channels, + * this must be modified/fixed. + */ + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + + if (priv->calib_disabled & IWL_CHAIN_NOISE_CALIB_DISABLED) + return; + + data = &(priv->chain_noise_data); + + /* + * Accumulate just the first "chain_noise_num_beacons" after + * the first association, then we're done forever. + */ + if (data->state != IWL_CHAIN_NOISE_ACCUMULATE) { + if (data->state == IWL_CHAIN_NOISE_ALIVE) + IWL_DEBUG_CALIB(priv, "Wait for noise calib reset\n"); + return; + } + + spin_lock_bh(&priv->statistics.lock); + + rx_info = &priv->statistics.rx_non_phy; + + if (rx_info->interference_data_flag != INTERFERENCE_DATA_AVAILABLE) { + IWL_DEBUG_CALIB(priv, " << Interference data unavailable\n"); + spin_unlock_bh(&priv->statistics.lock); + return; + } + + rxon_band24 = !!(ctx->staging.flags & RXON_FLG_BAND_24G_MSK); + rxon_chnum = le16_to_cpu(ctx->staging.channel); + stat_band24 = + !!(priv->statistics.flag & STATISTICS_REPLY_FLG_BAND_24G_MSK); + stat_chnum = le32_to_cpu(priv->statistics.flag) >> 16; + + /* Make sure we accumulate data for just the associated channel + * (even if scanning). */ + if ((rxon_chnum != stat_chnum) || (rxon_band24 != stat_band24)) { + IWL_DEBUG_CALIB(priv, "Stats not from chan=%d, band24=%d\n", + rxon_chnum, rxon_band24); + spin_unlock_bh(&priv->statistics.lock); + return; + } + + /* + * Accumulate beacon statistics values across + * "chain_noise_num_beacons" + */ + chain_noise_a = le32_to_cpu(rx_info->beacon_silence_rssi_a) & + IN_BAND_FILTER; + chain_noise_b = le32_to_cpu(rx_info->beacon_silence_rssi_b) & + IN_BAND_FILTER; + chain_noise_c = le32_to_cpu(rx_info->beacon_silence_rssi_c) & + IN_BAND_FILTER; + + chain_sig_a = le32_to_cpu(rx_info->beacon_rssi_a) & IN_BAND_FILTER; + chain_sig_b = le32_to_cpu(rx_info->beacon_rssi_b) & IN_BAND_FILTER; + chain_sig_c = le32_to_cpu(rx_info->beacon_rssi_c) & IN_BAND_FILTER; + + spin_unlock_bh(&priv->statistics.lock); + + data->beacon_count++; + + data->chain_noise_a = (chain_noise_a + data->chain_noise_a); + data->chain_noise_b = (chain_noise_b + data->chain_noise_b); + data->chain_noise_c = (chain_noise_c + data->chain_noise_c); + + data->chain_signal_a = (chain_sig_a + data->chain_signal_a); + data->chain_signal_b = (chain_sig_b + data->chain_signal_b); + data->chain_signal_c = (chain_sig_c + data->chain_signal_c); + + IWL_DEBUG_CALIB(priv, "chan=%d, band24=%d, beacon=%d\n", + rxon_chnum, rxon_band24, data->beacon_count); + IWL_DEBUG_CALIB(priv, "chain_sig: a %d b %d c %d\n", + chain_sig_a, chain_sig_b, chain_sig_c); + IWL_DEBUG_CALIB(priv, "chain_noise: a %d b %d c %d\n", + chain_noise_a, chain_noise_b, chain_noise_c); + + /* If this is the "chain_noise_num_beacons", determine: + * 1) Disconnected antennas (using signal strengths) + * 2) Differential gain (using silence noise) to balance receivers */ + if (data->beacon_count != IWL_CAL_NUM_BEACONS) + return; + + /* Analyze signal for disconnected antenna */ + if (priv->cfg->bt_params && + priv->cfg->bt_params->advanced_bt_coexist) { + /* Disable disconnected antenna algorithm for advanced + bt coex, assuming valid antennas are connected */ + data->active_chains = priv->nvm_data->valid_rx_ant; + for (i = 0; i < NUM_RX_CHAINS; i++) + if (!(data->active_chains & (1<disconn_array[i] = 1; + } else + iwl_find_disconn_antenna(priv, average_sig, data); + + /* Analyze noise for rx balance */ + average_noise[0] = data->chain_noise_a / IWL_CAL_NUM_BEACONS; + average_noise[1] = data->chain_noise_b / IWL_CAL_NUM_BEACONS; + average_noise[2] = data->chain_noise_c / IWL_CAL_NUM_BEACONS; + + for (i = 0; i < NUM_RX_CHAINS; i++) { + if (!(data->disconn_array[i]) && + (average_noise[i] <= min_average_noise)) { + /* This means that chain i is active and has + * lower noise values so far: */ + min_average_noise = average_noise[i]; + min_average_noise_antenna_i = i; + } + } + + IWL_DEBUG_CALIB(priv, "average_noise: a %d b %d c %d\n", + average_noise[0], average_noise[1], + average_noise[2]); + + IWL_DEBUG_CALIB(priv, "min_average_noise = %d, antenna %d\n", + min_average_noise, min_average_noise_antenna_i); + + iwlagn_gain_computation( + priv, average_noise, + find_first_chain(priv->nvm_data->valid_rx_ant)); + + /* Some power changes may have been made during the calibration. + * Update and commit the RXON + */ + iwl_update_chain_flags(priv); + + data->state = IWL_CHAIN_NOISE_DONE; + iwl_power_update_mode(priv, false); +} + +void iwl_reset_run_time_calib(struct iwl_priv *priv) +{ + int i; + memset(&(priv->sensitivity_data), 0, + sizeof(struct iwl_sensitivity_data)); + memset(&(priv->chain_noise_data), 0, + sizeof(struct iwl_chain_noise_data)); + for (i = 0; i < NUM_RX_CHAINS; i++) + priv->chain_noise_data.delta_gain_code[i] = + CHAIN_NOISE_DELTA_GAIN_INIT_VAL; + + /* Ask for statistics now, the uCode will send notification + * periodically after association */ + iwl_send_statistics_request(priv, CMD_ASYNC, true); +} diff --git a/drivers/net/wireless/iwlwifi/dvm/calib.h b/drivers/net/wireless/iwlwifi/dvm/calib.h new file mode 100644 index 0000000..cfddde1 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/calib.h @@ -0,0 +1,74 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ +#ifndef __iwl_calib_h__ +#define __iwl_calib_h__ + +#include "dev.h" +#include "commands.h" + +void iwl_chain_noise_calibration(struct iwl_priv *priv); +void iwl_sensitivity_calibration(struct iwl_priv *priv); + +void iwl_init_sensitivity(struct iwl_priv *priv); +void iwl_reset_run_time_calib(struct iwl_priv *priv); + +#endif /* __iwl_calib_h__ */ diff --git a/drivers/net/wireless/iwlwifi/dvm/commands.h b/drivers/net/wireless/iwlwifi/dvm/commands.h new file mode 100644 index 0000000..95ca026 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/commands.h @@ -0,0 +1,4015 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +/* + * Please use this file (commands.h) only for uCode API definitions. + * Please use iwl-xxxx-hw.h for hardware-related definitions. + * Please use dev.h for driver implementation definitions. + */ + +#ifndef __iwl_commands_h__ +#define __iwl_commands_h__ + +#include +#include + + +enum { + REPLY_ALIVE = 0x1, + REPLY_ERROR = 0x2, + REPLY_ECHO = 0x3, /* test command */ + + /* RXON and QOS commands */ + REPLY_RXON = 0x10, + REPLY_RXON_ASSOC = 0x11, + REPLY_QOS_PARAM = 0x13, + REPLY_RXON_TIMING = 0x14, + + /* Multi-Station support */ + REPLY_ADD_STA = 0x18, + REPLY_REMOVE_STA = 0x19, + REPLY_REMOVE_ALL_STA = 0x1a, /* not used */ + REPLY_TXFIFO_FLUSH = 0x1e, + + /* Security */ + REPLY_WEPKEY = 0x20, + + /* RX, TX, LEDs */ + REPLY_TX = 0x1c, + REPLY_LEDS_CMD = 0x48, + REPLY_TX_LINK_QUALITY_CMD = 0x4e, + + /* WiMAX coexistence */ + COEX_PRIORITY_TABLE_CMD = 0x5a, + COEX_MEDIUM_NOTIFICATION = 0x5b, + COEX_EVENT_CMD = 0x5c, + + /* Calibration */ + TEMPERATURE_NOTIFICATION = 0x62, + CALIBRATION_CFG_CMD = 0x65, + CALIBRATION_RES_NOTIFICATION = 0x66, + CALIBRATION_COMPLETE_NOTIFICATION = 0x67, + + /* 802.11h related */ + REPLY_QUIET_CMD = 0x71, /* not used */ + REPLY_CHANNEL_SWITCH = 0x72, + CHANNEL_SWITCH_NOTIFICATION = 0x73, + REPLY_SPECTRUM_MEASUREMENT_CMD = 0x74, + SPECTRUM_MEASURE_NOTIFICATION = 0x75, + + /* Power Management */ + POWER_TABLE_CMD = 0x77, + PM_SLEEP_NOTIFICATION = 0x7A, + PM_DEBUG_STATISTIC_NOTIFIC = 0x7B, + + /* Scan commands and notifications */ + REPLY_SCAN_CMD = 0x80, + REPLY_SCAN_ABORT_CMD = 0x81, + SCAN_START_NOTIFICATION = 0x82, + SCAN_RESULTS_NOTIFICATION = 0x83, + SCAN_COMPLETE_NOTIFICATION = 0x84, + + /* IBSS/AP commands */ + BEACON_NOTIFICATION = 0x90, + REPLY_TX_BEACON = 0x91, + WHO_IS_AWAKE_NOTIFICATION = 0x94, /* not used */ + + /* Miscellaneous commands */ + REPLY_TX_POWER_DBM_CMD = 0x95, + QUIET_NOTIFICATION = 0x96, /* not used */ + REPLY_TX_PWR_TABLE_CMD = 0x97, + REPLY_TX_POWER_DBM_CMD_V1 = 0x98, /* old version of API */ + TX_ANT_CONFIGURATION_CMD = 0x98, + MEASURE_ABORT_NOTIFICATION = 0x99, /* not used */ + + /* Bluetooth device coexistence config command */ + REPLY_BT_CONFIG = 0x9b, + + /* Statistics */ + REPLY_STATISTICS_CMD = 0x9c, + STATISTICS_NOTIFICATION = 0x9d, + + /* RF-KILL commands and notifications */ + REPLY_CARD_STATE_CMD = 0xa0, + CARD_STATE_NOTIFICATION = 0xa1, + + /* Missed beacons notification */ + MISSED_BEACONS_NOTIFICATION = 0xa2, + + REPLY_CT_KILL_CONFIG_CMD = 0xa4, + SENSITIVITY_CMD = 0xa8, + REPLY_PHY_CALIBRATION_CMD = 0xb0, + REPLY_RX_PHY_CMD = 0xc0, + REPLY_RX_MPDU_CMD = 0xc1, + REPLY_RX = 0xc3, + REPLY_COMPRESSED_BA = 0xc5, + + /* BT Coex */ + REPLY_BT_COEX_PRIO_TABLE = 0xcc, + REPLY_BT_COEX_PROT_ENV = 0xcd, + REPLY_BT_COEX_PROFILE_NOTIF = 0xce, + + /* PAN commands */ + REPLY_WIPAN_PARAMS = 0xb2, + REPLY_WIPAN_RXON = 0xb3, /* use REPLY_RXON structure */ + REPLY_WIPAN_RXON_TIMING = 0xb4, /* use REPLY_RXON_TIMING structure */ + REPLY_WIPAN_RXON_ASSOC = 0xb6, /* use REPLY_RXON_ASSOC structure */ + REPLY_WIPAN_QOS_PARAM = 0xb7, /* use REPLY_QOS_PARAM structure */ + REPLY_WIPAN_WEPKEY = 0xb8, /* use REPLY_WEPKEY structure */ + REPLY_WIPAN_P2P_CHANNEL_SWITCH = 0xb9, + REPLY_WIPAN_NOA_NOTIFICATION = 0xbc, + REPLY_WIPAN_DEACTIVATION_COMPLETE = 0xbd, + + REPLY_WOWLAN_PATTERNS = 0xe0, + REPLY_WOWLAN_WAKEUP_FILTER = 0xe1, + REPLY_WOWLAN_TSC_RSC_PARAMS = 0xe2, + REPLY_WOWLAN_TKIP_PARAMS = 0xe3, + REPLY_WOWLAN_KEK_KCK_MATERIAL = 0xe4, + REPLY_WOWLAN_GET_STATUS = 0xe5, + REPLY_D3_CONFIG = 0xd3, + + REPLY_MAX = 0xff +}; + +/* + * Minimum number of queues. MAX_NUM is defined in hw specific files. + * Set the minimum to accommodate + * - 4 standard TX queues + * - the command queue + * - 4 PAN TX queues + * - the PAN multicast queue, and + * - the AUX (TX during scan dwell) queue. + */ +#define IWL_MIN_NUM_QUEUES 11 + +/* + * Command queue depends on iPAN support. + */ +#define IWL_DEFAULT_CMD_QUEUE_NUM 4 +#define IWL_IPAN_CMD_QUEUE_NUM 9 + +#define IWL_TX_FIFO_BK 0 /* shared */ +#define IWL_TX_FIFO_BE 1 +#define IWL_TX_FIFO_VI 2 /* shared */ +#define IWL_TX_FIFO_VO 3 +#define IWL_TX_FIFO_BK_IPAN IWL_TX_FIFO_BK +#define IWL_TX_FIFO_BE_IPAN 4 +#define IWL_TX_FIFO_VI_IPAN IWL_TX_FIFO_VI +#define IWL_TX_FIFO_VO_IPAN 5 +/* re-uses the VO FIFO, uCode will properly flush/schedule */ +#define IWL_TX_FIFO_AUX 5 +#define IWL_TX_FIFO_UNUSED 255 + +#define IWLAGN_CMD_FIFO_NUM 7 + +/* + * This queue number is required for proper operation + * because the ucode will stop/start the scheduler as + * required. + */ +#define IWL_IPAN_MCAST_QUEUE 8 + +/****************************************************************************** + * (0) + * Commonly used structures and definitions: + * Command header, rate_n_flags, txpower + * + *****************************************************************************/ + +/** + * iwlagn rate_n_flags bit fields + * + * rate_n_flags format is used in following iwlagn commands: + * REPLY_RX (response only) + * REPLY_RX_MPDU (response only) + * REPLY_TX (both command and response) + * REPLY_TX_LINK_QUALITY_CMD + * + * High-throughput (HT) rate format for bits 7:0 (bit 8 must be "1"): + * 2-0: 0) 6 Mbps + * 1) 12 Mbps + * 2) 18 Mbps + * 3) 24 Mbps + * 4) 36 Mbps + * 5) 48 Mbps + * 6) 54 Mbps + * 7) 60 Mbps + * + * 4-3: 0) Single stream (SISO) + * 1) Dual stream (MIMO) + * 2) Triple stream (MIMO) + * + * 5: Value of 0x20 in bits 7:0 indicates 6 Mbps HT40 duplicate data + * + * Legacy OFDM rate format for bits 7:0 (bit 8 must be "0", bit 9 "0"): + * 3-0: 0xD) 6 Mbps + * 0xF) 9 Mbps + * 0x5) 12 Mbps + * 0x7) 18 Mbps + * 0x9) 24 Mbps + * 0xB) 36 Mbps + * 0x1) 48 Mbps + * 0x3) 54 Mbps + * + * Legacy CCK rate format for bits 7:0 (bit 8 must be "0", bit 9 "1"): + * 6-0: 10) 1 Mbps + * 20) 2 Mbps + * 55) 5.5 Mbps + * 110) 11 Mbps + */ +#define RATE_MCS_CODE_MSK 0x7 +#define RATE_MCS_SPATIAL_POS 3 +#define RATE_MCS_SPATIAL_MSK 0x18 +#define RATE_MCS_HT_DUP_POS 5 +#define RATE_MCS_HT_DUP_MSK 0x20 +/* Both legacy and HT use bits 7:0 as the CCK/OFDM rate or HT MCS */ +#define RATE_MCS_RATE_MSK 0xff + +/* Bit 8: (1) HT format, (0) legacy format in bits 7:0 */ +#define RATE_MCS_FLAGS_POS 8 +#define RATE_MCS_HT_POS 8 +#define RATE_MCS_HT_MSK 0x100 + +/* Bit 9: (1) CCK, (0) OFDM. HT (bit 8) must be "0" for this bit to be valid */ +#define RATE_MCS_CCK_POS 9 +#define RATE_MCS_CCK_MSK 0x200 + +/* Bit 10: (1) Use Green Field preamble */ +#define RATE_MCS_GF_POS 10 +#define RATE_MCS_GF_MSK 0x400 + +/* Bit 11: (1) Use 40Mhz HT40 chnl width, (0) use 20 MHz legacy chnl width */ +#define RATE_MCS_HT40_POS 11 +#define RATE_MCS_HT40_MSK 0x800 + +/* Bit 12: (1) Duplicate data on both 20MHz chnls. HT40 (bit 11) must be set. */ +#define RATE_MCS_DUP_POS 12 +#define RATE_MCS_DUP_MSK 0x1000 + +/* Bit 13: (1) Short guard interval (0.4 usec), (0) normal GI (0.8 usec) */ +#define RATE_MCS_SGI_POS 13 +#define RATE_MCS_SGI_MSK 0x2000 + +/** + * rate_n_flags Tx antenna masks + * 4965 has 2 transmitters + * 5100 has 1 transmitter B + * 5150 has 1 transmitter A + * 5300 has 3 transmitters + * 5350 has 3 transmitters + * bit14:16 + */ +#define RATE_MCS_ANT_POS 14 +#define RATE_MCS_ANT_A_MSK 0x04000 +#define RATE_MCS_ANT_B_MSK 0x08000 +#define RATE_MCS_ANT_C_MSK 0x10000 +#define RATE_MCS_ANT_AB_MSK (RATE_MCS_ANT_A_MSK | RATE_MCS_ANT_B_MSK) +#define RATE_MCS_ANT_ABC_MSK (RATE_MCS_ANT_AB_MSK | RATE_MCS_ANT_C_MSK) +#define RATE_ANT_NUM 3 + +#define POWER_TABLE_NUM_ENTRIES 33 +#define POWER_TABLE_NUM_HT_OFDM_ENTRIES 32 +#define POWER_TABLE_CCK_ENTRY 32 + +#define IWL_PWR_NUM_HT_OFDM_ENTRIES 24 +#define IWL_PWR_CCK_ENTRIES 2 + +/** + * struct tx_power_dual_stream + * + * Table entries in REPLY_TX_PWR_TABLE_CMD, REPLY_CHANNEL_SWITCH + * + * Same format as iwl_tx_power_dual_stream, but __le32 + */ +struct tx_power_dual_stream { + __le32 dw; +} __packed; + +/** + * Command REPLY_TX_POWER_DBM_CMD = 0x98 + * struct iwlagn_tx_power_dbm_cmd + */ +#define IWLAGN_TX_POWER_AUTO 0x7f +#define IWLAGN_TX_POWER_NO_CLOSED (0x1 << 6) + +struct iwlagn_tx_power_dbm_cmd { + s8 global_lmt; /*in half-dBm (e.g. 30 = 15 dBm) */ + u8 flags; + s8 srv_chan_lmt; /*in half-dBm (e.g. 30 = 15 dBm) */ + u8 reserved; +} __packed; + +/** + * Command TX_ANT_CONFIGURATION_CMD = 0x98 + * This command is used to configure valid Tx antenna. + * By default uCode concludes the valid antenna according to the radio flavor. + * This command enables the driver to override/modify this conclusion. + */ +struct iwl_tx_ant_config_cmd { + __le32 valid; +} __packed; + +/****************************************************************************** + * (0a) + * Alive and Error Commands & Responses: + * + *****************************************************************************/ + +#define UCODE_VALID_OK cpu_to_le32(0x1) + +/** + * REPLY_ALIVE = 0x1 (response only, not a command) + * + * uCode issues this "alive" notification once the runtime image is ready + * to receive commands from the driver. This is the *second* "alive" + * notification that the driver will receive after rebooting uCode; + * this "alive" is indicated by subtype field != 9. + * + * See comments documenting "BSM" (bootstrap state machine). + * + * This response includes two pointers to structures within the device's + * data SRAM (access via HBUS_TARG_MEM_* regs) that are useful for debugging: + * + * 1) log_event_table_ptr indicates base of the event log. This traces + * a 256-entry history of uCode execution within a circular buffer. + * Its header format is: + * + * __le32 log_size; log capacity (in number of entries) + * __le32 type; (1) timestamp with each entry, (0) no timestamp + * __le32 wraps; # times uCode has wrapped to top of circular buffer + * __le32 write_index; next circular buffer entry that uCode would fill + * + * The header is followed by the circular buffer of log entries. Entries + * with timestamps have the following format: + * + * __le32 event_id; range 0 - 1500 + * __le32 timestamp; low 32 bits of TSF (of network, if associated) + * __le32 data; event_id-specific data value + * + * Entries without timestamps contain only event_id and data. + * + * + * 2) error_event_table_ptr indicates base of the error log. This contains + * information about any uCode error that occurs. For agn, the format + * of the error log is defined by struct iwl_error_event_table. + * + * The Linux driver can print both logs to the system log when a uCode error + * occurs. + */ + +/* + * Note: This structure is read from the device with IO accesses, + * and the reading already does the endian conversion. As it is + * read with u32-sized accesses, any members with a different size + * need to be ordered correctly though! + */ +struct iwl_error_event_table { + u32 valid; /* (nonzero) valid, (0) log is empty */ + u32 error_id; /* type of error */ + u32 pc; /* program counter */ + u32 blink1; /* branch link */ + u32 blink2; /* branch link */ + u32 ilink1; /* interrupt link */ + u32 ilink2; /* interrupt link */ + u32 data1; /* error-specific data */ + u32 data2; /* error-specific data */ + u32 line; /* source code line of error */ + u32 bcon_time; /* beacon timer */ + u32 tsf_low; /* network timestamp function timer */ + u32 tsf_hi; /* network timestamp function timer */ + u32 gp1; /* GP1 timer register */ + u32 gp2; /* GP2 timer register */ + u32 gp3; /* GP3 timer register */ + u32 ucode_ver; /* uCode version */ + u32 hw_ver; /* HW Silicon version */ + u32 brd_ver; /* HW board version */ + u32 log_pc; /* log program counter */ + u32 frame_ptr; /* frame pointer */ + u32 stack_ptr; /* stack pointer */ + u32 hcmd; /* last host command header */ + u32 isr0; /* isr status register LMPM_NIC_ISR0: + * rxtx_flag */ + u32 isr1; /* isr status register LMPM_NIC_ISR1: + * host_flag */ + u32 isr2; /* isr status register LMPM_NIC_ISR2: + * enc_flag */ + u32 isr3; /* isr status register LMPM_NIC_ISR3: + * time_flag */ + u32 isr4; /* isr status register LMPM_NIC_ISR4: + * wico interrupt */ + u32 isr_pref; /* isr status register LMPM_NIC_PREF_STAT */ + u32 wait_event; /* wait event() caller address */ + u32 l2p_control; /* L2pControlField */ + u32 l2p_duration; /* L2pDurationField */ + u32 l2p_mhvalid; /* L2pMhValidBits */ + u32 l2p_addr_match; /* L2pAddrMatchStat */ + u32 lmpm_pmg_sel; /* indicate which clocks are turned on + * (LMPM_PMG_SEL) */ + u32 u_timestamp; /* indicate when the date and time of the + * compilation */ + u32 flow_handler; /* FH read/write pointers, RX credit */ +} __packed; + +struct iwl_alive_resp { + u8 ucode_minor; + u8 ucode_major; + __le16 reserved1; + u8 sw_rev[8]; + u8 ver_type; + u8 ver_subtype; /* not "9" for runtime alive */ + __le16 reserved2; + __le32 log_event_table_ptr; /* SRAM address for event log */ + __le32 error_event_table_ptr; /* SRAM address for error log */ + __le32 timestamp; + __le32 is_valid; +} __packed; + +/* + * REPLY_ERROR = 0x2 (response only, not a command) + */ +struct iwl_error_resp { + __le32 error_type; + u8 cmd_id; + u8 reserved1; + __le16 bad_cmd_seq_num; + __le32 error_info; + __le64 timestamp; +} __packed; + +/****************************************************************************** + * (1) + * RXON Commands & Responses: + * + *****************************************************************************/ + +/* + * Rx config defines & structure + */ +/* rx_config device types */ +enum { + RXON_DEV_TYPE_AP = 1, + RXON_DEV_TYPE_ESS = 3, + RXON_DEV_TYPE_IBSS = 4, + RXON_DEV_TYPE_SNIFFER = 6, + RXON_DEV_TYPE_CP = 7, + RXON_DEV_TYPE_2STA = 8, + RXON_DEV_TYPE_P2P = 9, +}; + + +#define RXON_RX_CHAIN_DRIVER_FORCE_MSK cpu_to_le16(0x1 << 0) +#define RXON_RX_CHAIN_DRIVER_FORCE_POS (0) +#define RXON_RX_CHAIN_VALID_MSK cpu_to_le16(0x7 << 1) +#define RXON_RX_CHAIN_VALID_POS (1) +#define RXON_RX_CHAIN_FORCE_SEL_MSK cpu_to_le16(0x7 << 4) +#define RXON_RX_CHAIN_FORCE_SEL_POS (4) +#define RXON_RX_CHAIN_FORCE_MIMO_SEL_MSK cpu_to_le16(0x7 << 7) +#define RXON_RX_CHAIN_FORCE_MIMO_SEL_POS (7) +#define RXON_RX_CHAIN_CNT_MSK cpu_to_le16(0x3 << 10) +#define RXON_RX_CHAIN_CNT_POS (10) +#define RXON_RX_CHAIN_MIMO_CNT_MSK cpu_to_le16(0x3 << 12) +#define RXON_RX_CHAIN_MIMO_CNT_POS (12) +#define RXON_RX_CHAIN_MIMO_FORCE_MSK cpu_to_le16(0x1 << 14) +#define RXON_RX_CHAIN_MIMO_FORCE_POS (14) + +/* rx_config flags */ +/* band & modulation selection */ +#define RXON_FLG_BAND_24G_MSK cpu_to_le32(1 << 0) +#define RXON_FLG_CCK_MSK cpu_to_le32(1 << 1) +/* auto detection enable */ +#define RXON_FLG_AUTO_DETECT_MSK cpu_to_le32(1 << 2) +/* TGg protection when tx */ +#define RXON_FLG_TGG_PROTECT_MSK cpu_to_le32(1 << 3) +/* cck short slot & preamble */ +#define RXON_FLG_SHORT_SLOT_MSK cpu_to_le32(1 << 4) +#define RXON_FLG_SHORT_PREAMBLE_MSK cpu_to_le32(1 << 5) +/* antenna selection */ +#define RXON_FLG_DIS_DIV_MSK cpu_to_le32(1 << 7) +#define RXON_FLG_ANT_SEL_MSK cpu_to_le32(0x0f00) +#define RXON_FLG_ANT_A_MSK cpu_to_le32(1 << 8) +#define RXON_FLG_ANT_B_MSK cpu_to_le32(1 << 9) +/* radar detection enable */ +#define RXON_FLG_RADAR_DETECT_MSK cpu_to_le32(1 << 12) +#define RXON_FLG_TGJ_NARROW_BAND_MSK cpu_to_le32(1 << 13) +/* rx response to host with 8-byte TSF +* (according to ON_AIR deassertion) */ +#define RXON_FLG_TSF2HOST_MSK cpu_to_le32(1 << 15) + + +/* HT flags */ +#define RXON_FLG_CTRL_CHANNEL_LOC_POS (22) +#define RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK cpu_to_le32(0x1 << 22) + +#define RXON_FLG_HT_OPERATING_MODE_POS (23) + +#define RXON_FLG_HT_PROT_MSK cpu_to_le32(0x1 << 23) +#define RXON_FLG_HT40_PROT_MSK cpu_to_le32(0x2 << 23) + +#define RXON_FLG_CHANNEL_MODE_POS (25) +#define RXON_FLG_CHANNEL_MODE_MSK cpu_to_le32(0x3 << 25) + +/* channel mode */ +enum { + CHANNEL_MODE_LEGACY = 0, + CHANNEL_MODE_PURE_40 = 1, + CHANNEL_MODE_MIXED = 2, + CHANNEL_MODE_RESERVED = 3, +}; +#define RXON_FLG_CHANNEL_MODE_LEGACY cpu_to_le32(CHANNEL_MODE_LEGACY << RXON_FLG_CHANNEL_MODE_POS) +#define RXON_FLG_CHANNEL_MODE_PURE_40 cpu_to_le32(CHANNEL_MODE_PURE_40 << RXON_FLG_CHANNEL_MODE_POS) +#define RXON_FLG_CHANNEL_MODE_MIXED cpu_to_le32(CHANNEL_MODE_MIXED << RXON_FLG_CHANNEL_MODE_POS) + +/* CTS to self (if spec allows) flag */ +#define RXON_FLG_SELF_CTS_EN cpu_to_le32(0x1<<30) + +/* rx_config filter flags */ +/* accept all data frames */ +#define RXON_FILTER_PROMISC_MSK cpu_to_le32(1 << 0) +/* pass control & management to host */ +#define RXON_FILTER_CTL2HOST_MSK cpu_to_le32(1 << 1) +/* accept multi-cast */ +#define RXON_FILTER_ACCEPT_GRP_MSK cpu_to_le32(1 << 2) +/* don't decrypt uni-cast frames */ +#define RXON_FILTER_DIS_DECRYPT_MSK cpu_to_le32(1 << 3) +/* don't decrypt multi-cast frames */ +#define RXON_FILTER_DIS_GRP_DECRYPT_MSK cpu_to_le32(1 << 4) +/* STA is associated */ +#define RXON_FILTER_ASSOC_MSK cpu_to_le32(1 << 5) +/* transfer to host non bssid beacons in associated state */ +#define RXON_FILTER_BCON_AWARE_MSK cpu_to_le32(1 << 6) + +/** + * REPLY_RXON = 0x10 (command, has simple generic response) + * + * RXON tunes the radio tuner to a service channel, and sets up a number + * of parameters that are used primarily for Rx, but also for Tx operations. + * + * NOTE: When tuning to a new channel, driver must set the + * RXON_FILTER_ASSOC_MSK to 0. This will clear station-dependent + * info within the device, including the station tables, tx retry + * rate tables, and txpower tables. Driver must build a new station + * table and txpower table before transmitting anything on the RXON + * channel. + * + * NOTE: All RXONs wipe clean the internal txpower table. Driver must + * issue a new REPLY_TX_PWR_TABLE_CMD after each REPLY_RXON (0x10), + * regardless of whether RXON_FILTER_ASSOC_MSK is set. + */ + +struct iwl_rxon_cmd { + u8 node_addr[6]; + __le16 reserved1; + u8 bssid_addr[6]; + __le16 reserved2; + u8 wlap_bssid_addr[6]; + __le16 reserved3; + u8 dev_type; + u8 air_propagation; + __le16 rx_chain; + u8 ofdm_basic_rates; + u8 cck_basic_rates; + __le16 assoc_id; + __le32 flags; + __le32 filter_flags; + __le16 channel; + u8 ofdm_ht_single_stream_basic_rates; + u8 ofdm_ht_dual_stream_basic_rates; + u8 ofdm_ht_triple_stream_basic_rates; + u8 reserved5; + __le16 acquisition_data; + __le16 reserved6; +} __packed; + +/* + * REPLY_RXON_ASSOC = 0x11 (command, has simple generic response) + */ +struct iwl_rxon_assoc_cmd { + __le32 flags; + __le32 filter_flags; + u8 ofdm_basic_rates; + u8 cck_basic_rates; + __le16 reserved1; + u8 ofdm_ht_single_stream_basic_rates; + u8 ofdm_ht_dual_stream_basic_rates; + u8 ofdm_ht_triple_stream_basic_rates; + u8 reserved2; + __le16 rx_chain_select_flags; + __le16 acquisition_data; + __le32 reserved3; +} __packed; + +#define IWL_CONN_MAX_LISTEN_INTERVAL 10 +#define IWL_MAX_UCODE_BEACON_INTERVAL 4 /* 4096 */ + +/* + * REPLY_RXON_TIMING = 0x14 (command, has simple generic response) + */ +struct iwl_rxon_time_cmd { + __le64 timestamp; + __le16 beacon_interval; + __le16 atim_window; + __le32 beacon_init_val; + __le16 listen_interval; + u8 dtim_period; + u8 delta_cp_bss_tbtts; +} __packed; + +/* + * REPLY_CHANNEL_SWITCH = 0x72 (command, has simple generic response) + */ +/** + * struct iwl5000_channel_switch_cmd + * @band: 0- 5.2GHz, 1- 2.4GHz + * @expect_beacon: 0- resume transmits after channel switch + * 1- wait for beacon to resume transmits + * @channel: new channel number + * @rxon_flags: Rx on flags + * @rxon_filter_flags: filtering parameters + * @switch_time: switch time in extended beacon format + * @reserved: reserved bytes + */ +struct iwl5000_channel_switch_cmd { + u8 band; + u8 expect_beacon; + __le16 channel; + __le32 rxon_flags; + __le32 rxon_filter_flags; + __le32 switch_time; + __le32 reserved[2][IWL_PWR_NUM_HT_OFDM_ENTRIES + IWL_PWR_CCK_ENTRIES]; +} __packed; + +/** + * struct iwl6000_channel_switch_cmd + * @band: 0- 5.2GHz, 1- 2.4GHz + * @expect_beacon: 0- resume transmits after channel switch + * 1- wait for beacon to resume transmits + * @channel: new channel number + * @rxon_flags: Rx on flags + * @rxon_filter_flags: filtering parameters + * @switch_time: switch time in extended beacon format + * @reserved: reserved bytes + */ +struct iwl6000_channel_switch_cmd { + u8 band; + u8 expect_beacon; + __le16 channel; + __le32 rxon_flags; + __le32 rxon_filter_flags; + __le32 switch_time; + __le32 reserved[3][IWL_PWR_NUM_HT_OFDM_ENTRIES + IWL_PWR_CCK_ENTRIES]; +} __packed; + +/* + * CHANNEL_SWITCH_NOTIFICATION = 0x73 (notification only, not a command) + */ +struct iwl_csa_notification { + __le16 band; + __le16 channel; + __le32 status; /* 0 - OK, 1 - fail */ +} __packed; + +/****************************************************************************** + * (2) + * Quality-of-Service (QOS) Commands & Responses: + * + *****************************************************************************/ + +/** + * struct iwl_ac_qos -- QOS timing params for REPLY_QOS_PARAM + * One for each of 4 EDCA access categories in struct iwl_qosparam_cmd + * + * @cw_min: Contention window, start value in numbers of slots. + * Should be a power-of-2, minus 1. Device's default is 0x0f. + * @cw_max: Contention window, max value in numbers of slots. + * Should be a power-of-2, minus 1. Device's default is 0x3f. + * @aifsn: Number of slots in Arbitration Interframe Space (before + * performing random backoff timing prior to Tx). Device default 1. + * @edca_txop: Length of Tx opportunity, in uSecs. Device default is 0. + * + * Device will automatically increase contention window by (2*CW) + 1 for each + * transmission retry. Device uses cw_max as a bit mask, ANDed with new CW + * value, to cap the CW value. + */ +struct iwl_ac_qos { + __le16 cw_min; + __le16 cw_max; + u8 aifsn; + u8 reserved1; + __le16 edca_txop; +} __packed; + +/* QoS flags defines */ +#define QOS_PARAM_FLG_UPDATE_EDCA_MSK cpu_to_le32(0x01) +#define QOS_PARAM_FLG_TGN_MSK cpu_to_le32(0x02) +#define QOS_PARAM_FLG_TXOP_TYPE_MSK cpu_to_le32(0x10) + +/* Number of Access Categories (AC) (EDCA), queues 0..3 */ +#define AC_NUM 4 + +/* + * REPLY_QOS_PARAM = 0x13 (command, has simple generic response) + * + * This command sets up timings for each of the 4 prioritized EDCA Tx FIFOs + * 0: Background, 1: Best Effort, 2: Video, 3: Voice. + */ +struct iwl_qosparam_cmd { + __le32 qos_flags; + struct iwl_ac_qos ac[AC_NUM]; +} __packed; + +/****************************************************************************** + * (3) + * Add/Modify Stations Commands & Responses: + * + *****************************************************************************/ +/* + * Multi station support + */ + +/* Special, dedicated locations within device's station table */ +#define IWL_AP_ID 0 +#define IWL_AP_ID_PAN 1 +#define IWL_STA_ID 2 +#define IWLAGN_PAN_BCAST_ID 14 +#define IWLAGN_BROADCAST_ID 15 +#define IWLAGN_STATION_COUNT 16 + +#define IWL_TID_NON_QOS IWL_MAX_TID_COUNT + +#define STA_FLG_TX_RATE_MSK cpu_to_le32(1 << 2) +#define STA_FLG_PWR_SAVE_MSK cpu_to_le32(1 << 8) +#define STA_FLG_PAN_STATION cpu_to_le32(1 << 13) +#define STA_FLG_RTS_MIMO_PROT_MSK cpu_to_le32(1 << 17) +#define STA_FLG_AGG_MPDU_8US_MSK cpu_to_le32(1 << 18) +#define STA_FLG_MAX_AGG_SIZE_POS (19) +#define STA_FLG_MAX_AGG_SIZE_MSK cpu_to_le32(3 << 19) +#define STA_FLG_HT40_EN_MSK cpu_to_le32(1 << 21) +#define STA_FLG_MIMO_DIS_MSK cpu_to_le32(1 << 22) +#define STA_FLG_AGG_MPDU_DENSITY_POS (23) +#define STA_FLG_AGG_MPDU_DENSITY_MSK cpu_to_le32(7 << 23) + +/* Use in mode field. 1: modify existing entry, 0: add new station entry */ +#define STA_CONTROL_MODIFY_MSK 0x01 + +/* key flags __le16*/ +#define STA_KEY_FLG_ENCRYPT_MSK cpu_to_le16(0x0007) +#define STA_KEY_FLG_NO_ENC cpu_to_le16(0x0000) +#define STA_KEY_FLG_WEP cpu_to_le16(0x0001) +#define STA_KEY_FLG_CCMP cpu_to_le16(0x0002) +#define STA_KEY_FLG_TKIP cpu_to_le16(0x0003) + +#define STA_KEY_FLG_KEYID_POS 8 +#define STA_KEY_FLG_INVALID cpu_to_le16(0x0800) +/* wep key is either from global key (0) or from station info array (1) */ +#define STA_KEY_FLG_MAP_KEY_MSK cpu_to_le16(0x0008) + +/* wep key in STA: 5-bytes (0) or 13-bytes (1) */ +#define STA_KEY_FLG_KEY_SIZE_MSK cpu_to_le16(0x1000) +#define STA_KEY_MULTICAST_MSK cpu_to_le16(0x4000) +#define STA_KEY_MAX_NUM 8 +#define STA_KEY_MAX_NUM_PAN 16 +/* must not match WEP_INVALID_OFFSET */ +#define IWLAGN_HW_KEY_DEFAULT 0xfe + +/* Flags indicate whether to modify vs. don't change various station params */ +#define STA_MODIFY_KEY_MASK 0x01 +#define STA_MODIFY_TID_DISABLE_TX 0x02 +#define STA_MODIFY_TX_RATE_MSK 0x04 +#define STA_MODIFY_ADDBA_TID_MSK 0x08 +#define STA_MODIFY_DELBA_TID_MSK 0x10 +#define STA_MODIFY_SLEEP_TX_COUNT_MSK 0x20 + +/* Receiver address (actually, Rx station's index into station table), + * combined with Traffic ID (QOS priority), in format used by Tx Scheduler */ +#define BUILD_RAxTID(sta_id, tid) (((sta_id) << 4) + (tid)) + +/* agn */ +struct iwl_keyinfo { + __le16 key_flags; + u8 tkip_rx_tsc_byte2; /* TSC[2] for key mix ph1 detection */ + u8 reserved1; + __le16 tkip_rx_ttak[5]; /* 10-byte unicast TKIP TTAK */ + u8 key_offset; + u8 reserved2; + u8 key[16]; /* 16-byte unicast decryption key */ + __le64 tx_secur_seq_cnt; + __le64 hw_tkip_mic_rx_key; + __le64 hw_tkip_mic_tx_key; +} __packed; + +/** + * struct sta_id_modify + * @addr[ETH_ALEN]: station's MAC address + * @sta_id: index of station in uCode's station table + * @modify_mask: STA_MODIFY_*, 1: modify, 0: don't change + * + * Driver selects unused table index when adding new station, + * or the index to a pre-existing station entry when modifying that station. + * Some indexes have special purposes (IWL_AP_ID, index 0, is for AP). + * + * modify_mask flags select which parameters to modify vs. leave alone. + */ +struct sta_id_modify { + u8 addr[ETH_ALEN]; + __le16 reserved1; + u8 sta_id; + u8 modify_mask; + __le16 reserved2; +} __packed; + +/* + * REPLY_ADD_STA = 0x18 (command) + * + * The device contains an internal table of per-station information, + * with info on security keys, aggregation parameters, and Tx rates for + * initial Tx attempt and any retries (agn devices uses + * REPLY_TX_LINK_QUALITY_CMD, + * + * REPLY_ADD_STA sets up the table entry for one station, either creating + * a new entry, or modifying a pre-existing one. + * + * NOTE: RXON command (without "associated" bit set) wipes the station table + * clean. Moving into RF_KILL state does this also. Driver must set up + * new station table before transmitting anything on the RXON channel + * (except active scans or active measurements; those commands carry + * their own txpower/rate setup data). + * + * When getting started on a new channel, driver must set up the + * IWL_BROADCAST_ID entry (last entry in the table). For a client + * station in a BSS, once an AP is selected, driver sets up the AP STA + * in the IWL_AP_ID entry (1st entry in the table). BROADCAST and AP + * are all that are needed for a BSS client station. If the device is + * used as AP, or in an IBSS network, driver must set up station table + * entries for all STAs in network, starting with index IWL_STA_ID. + */ + +struct iwl_addsta_cmd { + u8 mode; /* 1: modify existing, 0: add new station */ + u8 reserved[3]; + struct sta_id_modify sta; + struct iwl_keyinfo key; + __le32 station_flags; /* STA_FLG_* */ + __le32 station_flags_msk; /* STA_FLG_* */ + + /* bit field to disable (1) or enable (0) Tx for Traffic ID (TID) + * corresponding to bit (e.g. bit 5 controls TID 5). + * Set modify_mask bit STA_MODIFY_TID_DISABLE_TX to use this field. */ + __le16 tid_disable_tx; + __le16 legacy_reserved; + + /* TID for which to add block-ack support. + * Set modify_mask bit STA_MODIFY_ADDBA_TID_MSK to use this field. */ + u8 add_immediate_ba_tid; + + /* TID for which to remove block-ack support. + * Set modify_mask bit STA_MODIFY_DELBA_TID_MSK to use this field. */ + u8 remove_immediate_ba_tid; + + /* Starting Sequence Number for added block-ack support. + * Set modify_mask bit STA_MODIFY_ADDBA_TID_MSK to use this field. */ + __le16 add_immediate_ba_ssn; + + /* + * Number of packets OK to transmit to station even though + * it is asleep -- used to synchronise PS-poll and u-APSD + * responses while ucode keeps track of STA sleep state. + */ + __le16 sleep_tx_count; + + __le16 reserved2; +} __packed; + + +#define ADD_STA_SUCCESS_MSK 0x1 +#define ADD_STA_NO_ROOM_IN_TABLE 0x2 +#define ADD_STA_NO_BLOCK_ACK_RESOURCE 0x4 +#define ADD_STA_MODIFY_NON_EXIST_STA 0x8 +/* + * REPLY_ADD_STA = 0x18 (response) + */ +struct iwl_add_sta_resp { + u8 status; /* ADD_STA_* */ +} __packed; + +#define REM_STA_SUCCESS_MSK 0x1 +/* + * REPLY_REM_STA = 0x19 (response) + */ +struct iwl_rem_sta_resp { + u8 status; +} __packed; + +/* + * REPLY_REM_STA = 0x19 (command) + */ +struct iwl_rem_sta_cmd { + u8 num_sta; /* number of removed stations */ + u8 reserved[3]; + u8 addr[ETH_ALEN]; /* MAC addr of the first station */ + u8 reserved2[2]; +} __packed; + + +/* WiFi queues mask */ +#define IWL_SCD_BK_MSK cpu_to_le32(BIT(0)) +#define IWL_SCD_BE_MSK cpu_to_le32(BIT(1)) +#define IWL_SCD_VI_MSK cpu_to_le32(BIT(2)) +#define IWL_SCD_VO_MSK cpu_to_le32(BIT(3)) +#define IWL_SCD_MGMT_MSK cpu_to_le32(BIT(3)) + +/* PAN queues mask */ +#define IWL_PAN_SCD_BK_MSK cpu_to_le32(BIT(4)) +#define IWL_PAN_SCD_BE_MSK cpu_to_le32(BIT(5)) +#define IWL_PAN_SCD_VI_MSK cpu_to_le32(BIT(6)) +#define IWL_PAN_SCD_VO_MSK cpu_to_le32(BIT(7)) +#define IWL_PAN_SCD_MGMT_MSK cpu_to_le32(BIT(7)) +#define IWL_PAN_SCD_MULTICAST_MSK cpu_to_le32(BIT(8)) + +#define IWL_AGG_TX_QUEUE_MSK cpu_to_le32(0xffc00) + +#define IWL_DROP_ALL BIT(1) + +/* + * REPLY_TXFIFO_FLUSH = 0x1e(command and response) + * + * When using full FIFO flush this command checks the scheduler HW block WR/RD + * pointers to check if all the frames were transferred by DMA into the + * relevant TX FIFO queue. Only when the DMA is finished and the queue is + * empty the command can finish. + * This command is used to flush the TXFIFO from transmit commands, it may + * operate on single or multiple queues, the command queue can't be flushed by + * this command. The command response is returned when all the queue flush + * operations are done. Each TX command flushed return response with the FLUSH + * status set in the TX response status. When FIFO flush operation is used, + * the flush operation ends when both the scheduler DMA done and TXFIFO empty + * are set. + * + * @queue_control: bit mask for which queues to flush + * @flush_control: flush controls + * 0: Dump single MSDU + * 1: Dump multiple MSDU according to PS, INVALID STA, TTL, TID disable. + * 2: Dump all FIFO + */ +struct iwl_txfifo_flush_cmd { + __le32 queue_control; + __le16 flush_control; + __le16 reserved; +} __packed; + +/* + * REPLY_WEP_KEY = 0x20 + */ +struct iwl_wep_key { + u8 key_index; + u8 key_offset; + u8 reserved1[2]; + u8 key_size; + u8 reserved2[3]; + u8 key[16]; +} __packed; + +struct iwl_wep_cmd { + u8 num_keys; + u8 global_key_type; + u8 flags; + u8 reserved; + struct iwl_wep_key key[0]; +} __packed; + +#define WEP_KEY_WEP_TYPE 1 +#define WEP_KEYS_MAX 4 +#define WEP_INVALID_OFFSET 0xff +#define WEP_KEY_LEN_64 5 +#define WEP_KEY_LEN_128 13 + +/****************************************************************************** + * (4) + * Rx Responses: + * + *****************************************************************************/ + +#define RX_RES_STATUS_NO_CRC32_ERROR cpu_to_le32(1 << 0) +#define RX_RES_STATUS_NO_RXE_OVERFLOW cpu_to_le32(1 << 1) + +#define RX_RES_PHY_FLAGS_BAND_24_MSK cpu_to_le16(1 << 0) +#define RX_RES_PHY_FLAGS_MOD_CCK_MSK cpu_to_le16(1 << 1) +#define RX_RES_PHY_FLAGS_SHORT_PREAMBLE_MSK cpu_to_le16(1 << 2) +#define RX_RES_PHY_FLAGS_NARROW_BAND_MSK cpu_to_le16(1 << 3) +#define RX_RES_PHY_FLAGS_ANTENNA_MSK 0x70 +#define RX_RES_PHY_FLAGS_ANTENNA_POS 4 +#define RX_RES_PHY_FLAGS_AGG_MSK cpu_to_le16(1 << 7) + +#define RX_RES_STATUS_SEC_TYPE_MSK (0x7 << 8) +#define RX_RES_STATUS_SEC_TYPE_NONE (0x0 << 8) +#define RX_RES_STATUS_SEC_TYPE_WEP (0x1 << 8) +#define RX_RES_STATUS_SEC_TYPE_CCMP (0x2 << 8) +#define RX_RES_STATUS_SEC_TYPE_TKIP (0x3 << 8) +#define RX_RES_STATUS_SEC_TYPE_ERR (0x7 << 8) + +#define RX_RES_STATUS_STATION_FOUND (1<<6) +#define RX_RES_STATUS_NO_STATION_INFO_MISMATCH (1<<7) + +#define RX_RES_STATUS_DECRYPT_TYPE_MSK (0x3 << 11) +#define RX_RES_STATUS_NOT_DECRYPT (0x0 << 11) +#define RX_RES_STATUS_DECRYPT_OK (0x3 << 11) +#define RX_RES_STATUS_BAD_ICV_MIC (0x1 << 11) +#define RX_RES_STATUS_BAD_KEY_TTAK (0x2 << 11) + +#define RX_MPDU_RES_STATUS_ICV_OK (0x20) +#define RX_MPDU_RES_STATUS_MIC_OK (0x40) +#define RX_MPDU_RES_STATUS_TTAK_OK (1 << 7) +#define RX_MPDU_RES_STATUS_DEC_DONE_MSK (0x800) + + +#define IWLAGN_RX_RES_PHY_CNT 8 +#define IWLAGN_RX_RES_AGC_IDX 1 +#define IWLAGN_RX_RES_RSSI_AB_IDX 2 +#define IWLAGN_RX_RES_RSSI_C_IDX 3 +#define IWLAGN_OFDM_AGC_MSK 0xfe00 +#define IWLAGN_OFDM_AGC_BIT_POS 9 +#define IWLAGN_OFDM_RSSI_INBAND_A_BITMSK 0x00ff +#define IWLAGN_OFDM_RSSI_ALLBAND_A_BITMSK 0xff00 +#define IWLAGN_OFDM_RSSI_A_BIT_POS 0 +#define IWLAGN_OFDM_RSSI_INBAND_B_BITMSK 0xff0000 +#define IWLAGN_OFDM_RSSI_ALLBAND_B_BITMSK 0xff000000 +#define IWLAGN_OFDM_RSSI_B_BIT_POS 16 +#define IWLAGN_OFDM_RSSI_INBAND_C_BITMSK 0x00ff +#define IWLAGN_OFDM_RSSI_ALLBAND_C_BITMSK 0xff00 +#define IWLAGN_OFDM_RSSI_C_BIT_POS 0 + +struct iwlagn_non_cfg_phy { + __le32 non_cfg_phy[IWLAGN_RX_RES_PHY_CNT]; /* up to 8 phy entries */ +} __packed; + + +/* + * REPLY_RX = 0xc3 (response only, not a command) + * Used only for legacy (non 11n) frames. + */ +struct iwl_rx_phy_res { + u8 non_cfg_phy_cnt; /* non configurable DSP phy data byte count */ + u8 cfg_phy_cnt; /* configurable DSP phy data byte count */ + u8 stat_id; /* configurable DSP phy data set ID */ + u8 reserved1; + __le64 timestamp; /* TSF at on air rise */ + __le32 beacon_time_stamp; /* beacon at on-air rise */ + __le16 phy_flags; /* general phy flags: band, modulation, ... */ + __le16 channel; /* channel number */ + u8 non_cfg_phy_buf[32]; /* for various implementations of non_cfg_phy */ + __le32 rate_n_flags; /* RATE_MCS_* */ + __le16 byte_count; /* frame's byte-count */ + __le16 frame_time; /* frame's time on the air */ +} __packed; + +struct iwl_rx_mpdu_res_start { + __le16 byte_count; + __le16 reserved; +} __packed; + + +/****************************************************************************** + * (5) + * Tx Commands & Responses: + * + * Driver must place each REPLY_TX command into one of the prioritized Tx + * queues in host DRAM, shared between driver and device (see comments for + * SCD registers and Tx/Rx Queues). When the device's Tx scheduler and uCode + * are preparing to transmit, the device pulls the Tx command over the PCI + * bus via one of the device's Tx DMA channels, to fill an internal FIFO + * from which data will be transmitted. + * + * uCode handles all timing and protocol related to control frames + * (RTS/CTS/ACK), based on flags in the Tx command. uCode and Tx scheduler + * handle reception of block-acks; uCode updates the host driver via + * REPLY_COMPRESSED_BA. + * + * uCode handles retrying Tx when an ACK is expected but not received. + * This includes trying lower data rates than the one requested in the Tx + * command, as set up by the REPLY_TX_LINK_QUALITY_CMD (agn). + * + * Driver sets up transmit power for various rates via REPLY_TX_PWR_TABLE_CMD. + * This command must be executed after every RXON command, before Tx can occur. + *****************************************************************************/ + +/* REPLY_TX Tx flags field */ + +/* + * 1: Use RTS/CTS protocol or CTS-to-self if spec allows it + * before this frame. if CTS-to-self required check + * RXON_FLG_SELF_CTS_EN status. + */ +#define TX_CMD_FLG_PROT_REQUIRE_MSK cpu_to_le32(1 << 0) + +/* 1: Expect ACK from receiving station + * 0: Don't expect ACK (MAC header's duration field s/b 0) + * Set this for unicast frames, but not broadcast/multicast. */ +#define TX_CMD_FLG_ACK_MSK cpu_to_le32(1 << 3) + +/* For agn devices: + * 1: Use rate scale table (see REPLY_TX_LINK_QUALITY_CMD). + * Tx command's initial_rate_index indicates first rate to try; + * uCode walks through table for additional Tx attempts. + * 0: Use Tx rate/MCS from Tx command's rate_n_flags field. + * This rate will be used for all Tx attempts; it will not be scaled. */ +#define TX_CMD_FLG_STA_RATE_MSK cpu_to_le32(1 << 4) + +/* 1: Expect immediate block-ack. + * Set when Txing a block-ack request frame. Also set TX_CMD_FLG_ACK_MSK. */ +#define TX_CMD_FLG_IMM_BA_RSP_MASK cpu_to_le32(1 << 6) + +/* Tx antenna selection field; reserved (0) for agn devices. */ +#define TX_CMD_FLG_ANT_SEL_MSK cpu_to_le32(0xf00) + +/* 1: Ignore Bluetooth priority for this frame. + * 0: Delay Tx until Bluetooth device is done (normal usage). */ +#define TX_CMD_FLG_IGNORE_BT cpu_to_le32(1 << 12) + +/* 1: uCode overrides sequence control field in MAC header. + * 0: Driver provides sequence control field in MAC header. + * Set this for management frames, non-QOS data frames, non-unicast frames, + * and also in Tx command embedded in REPLY_SCAN_CMD for active scans. */ +#define TX_CMD_FLG_SEQ_CTL_MSK cpu_to_le32(1 << 13) + +/* 1: This frame is non-last MPDU; more fragments are coming. + * 0: Last fragment, or not using fragmentation. */ +#define TX_CMD_FLG_MORE_FRAG_MSK cpu_to_le32(1 << 14) + +/* 1: uCode calculates and inserts Timestamp Function (TSF) in outgoing frame. + * 0: No TSF required in outgoing frame. + * Set this for transmitting beacons and probe responses. */ +#define TX_CMD_FLG_TSF_MSK cpu_to_le32(1 << 16) + +/* 1: Driver inserted 2 bytes pad after the MAC header, for (required) dword + * alignment of frame's payload data field. + * 0: No pad + * Set this for MAC headers with 26 or 30 bytes, i.e. those with QOS or ADDR4 + * field (but not both). Driver must align frame data (i.e. data following + * MAC header) to DWORD boundary. */ +#define TX_CMD_FLG_MH_PAD_MSK cpu_to_le32(1 << 20) + +/* accelerate aggregation support + * 0 - no CCMP encryption; 1 - CCMP encryption */ +#define TX_CMD_FLG_AGG_CCMP_MSK cpu_to_le32(1 << 22) + +/* HCCA-AP - disable duration overwriting. */ +#define TX_CMD_FLG_DUR_MSK cpu_to_le32(1 << 25) + + +/* + * TX command security control + */ +#define TX_CMD_SEC_WEP 0x01 +#define TX_CMD_SEC_CCM 0x02 +#define TX_CMD_SEC_TKIP 0x03 +#define TX_CMD_SEC_MSK 0x03 +#define TX_CMD_SEC_SHIFT 6 +#define TX_CMD_SEC_KEY128 0x08 + +/* + * security overhead sizes + */ +#define WEP_IV_LEN 4 +#define WEP_ICV_LEN 4 +#define CCMP_MIC_LEN 8 +#define TKIP_ICV_LEN 4 + +/* + * REPLY_TX = 0x1c (command) + */ + +/* + * 4965 uCode updates these Tx attempt count values in host DRAM. + * Used for managing Tx retries when expecting block-acks. + * Driver should set these fields to 0. + */ +struct iwl_dram_scratch { + u8 try_cnt; /* Tx attempts */ + u8 bt_kill_cnt; /* Tx attempts blocked by Bluetooth device */ + __le16 reserved; +} __packed; + +struct iwl_tx_cmd { + /* + * MPDU byte count: + * MAC header (24/26/30/32 bytes) + 2 bytes pad if 26/30 header size, + * + 8 byte IV for CCM or TKIP (not used for WEP) + * + Data payload + * + 8-byte MIC (not used for CCM/WEP) + * NOTE: Does not include Tx command bytes, post-MAC pad bytes, + * MIC (CCM) 8 bytes, ICV (WEP/TKIP/CKIP) 4 bytes, CRC 4 bytes.i + * Range: 14-2342 bytes. + */ + __le16 len; + + /* + * MPDU or MSDU byte count for next frame. + * Used for fragmentation and bursting, but not 11n aggregation. + * Same as "len", but for next frame. Set to 0 if not applicable. + */ + __le16 next_frame_len; + + __le32 tx_flags; /* TX_CMD_FLG_* */ + + /* uCode may modify this field of the Tx command (in host DRAM!). + * Driver must also set dram_lsb_ptr and dram_msb_ptr in this cmd. */ + struct iwl_dram_scratch scratch; + + /* Rate for *all* Tx attempts, if TX_CMD_FLG_STA_RATE_MSK is cleared. */ + __le32 rate_n_flags; /* RATE_MCS_* */ + + /* Index of destination station in uCode's station table */ + u8 sta_id; + + /* Type of security encryption: CCM or TKIP */ + u8 sec_ctl; /* TX_CMD_SEC_* */ + + /* + * Index into rate table (see REPLY_TX_LINK_QUALITY_CMD) for initial + * Tx attempt, if TX_CMD_FLG_STA_RATE_MSK is set. Normally "0" for + * data frames, this field may be used to selectively reduce initial + * rate (via non-0 value) for special frames (e.g. management), while + * still supporting rate scaling for all frames. + */ + u8 initial_rate_index; + u8 reserved; + u8 key[16]; + __le16 next_frame_flags; + __le16 reserved2; + union { + __le32 life_time; + __le32 attempt; + } stop_time; + + /* Host DRAM physical address pointer to "scratch" in this command. + * Must be dword aligned. "0" in dram_lsb_ptr disables usage. */ + __le32 dram_lsb_ptr; + u8 dram_msb_ptr; + + u8 rts_retry_limit; /*byte 50 */ + u8 data_retry_limit; /*byte 51 */ + u8 tid_tspec; + union { + __le16 pm_frame_timeout; + __le16 attempt_duration; + } timeout; + + /* + * Duration of EDCA burst Tx Opportunity, in 32-usec units. + * Set this if txop time is not specified by HCCA protocol (e.g. by AP). + */ + __le16 driver_txop; + + /* + * MAC header goes here, followed by 2 bytes padding if MAC header + * length is 26 or 30 bytes, followed by payload data + */ + u8 payload[0]; + struct ieee80211_hdr hdr[0]; +} __packed; + +/* + * TX command response is sent after *agn* transmission attempts. + * + * both postpone and abort status are expected behavior from uCode. there is + * no special operation required from driver; except for RFKILL_FLUSH, + * which required tx flush host command to flush all the tx frames in queues + */ +enum { + TX_STATUS_SUCCESS = 0x01, + TX_STATUS_DIRECT_DONE = 0x02, + /* postpone TX */ + TX_STATUS_POSTPONE_DELAY = 0x40, + TX_STATUS_POSTPONE_FEW_BYTES = 0x41, + TX_STATUS_POSTPONE_BT_PRIO = 0x42, + TX_STATUS_POSTPONE_QUIET_PERIOD = 0x43, + TX_STATUS_POSTPONE_CALC_TTAK = 0x44, + /* abort TX */ + TX_STATUS_FAIL_INTERNAL_CROSSED_RETRY = 0x81, + TX_STATUS_FAIL_SHORT_LIMIT = 0x82, + TX_STATUS_FAIL_LONG_LIMIT = 0x83, + TX_STATUS_FAIL_FIFO_UNDERRUN = 0x84, + TX_STATUS_FAIL_DRAIN_FLOW = 0x85, + TX_STATUS_FAIL_RFKILL_FLUSH = 0x86, + TX_STATUS_FAIL_LIFE_EXPIRE = 0x87, + TX_STATUS_FAIL_DEST_PS = 0x88, + TX_STATUS_FAIL_HOST_ABORTED = 0x89, + TX_STATUS_FAIL_BT_RETRY = 0x8a, + TX_STATUS_FAIL_STA_INVALID = 0x8b, + TX_STATUS_FAIL_FRAG_DROPPED = 0x8c, + TX_STATUS_FAIL_TID_DISABLE = 0x8d, + TX_STATUS_FAIL_FIFO_FLUSHED = 0x8e, + TX_STATUS_FAIL_INSUFFICIENT_CF_POLL = 0x8f, + TX_STATUS_FAIL_PASSIVE_NO_RX = 0x90, + TX_STATUS_FAIL_NO_BEACON_ON_RADAR = 0x91, +}; + +#define TX_PACKET_MODE_REGULAR 0x0000 +#define TX_PACKET_MODE_BURST_SEQ 0x0100 +#define TX_PACKET_MODE_BURST_FIRST 0x0200 + +enum { + TX_POWER_PA_NOT_ACTIVE = 0x0, +}; + +enum { + TX_STATUS_MSK = 0x000000ff, /* bits 0:7 */ + TX_STATUS_DELAY_MSK = 0x00000040, + TX_STATUS_ABORT_MSK = 0x00000080, + TX_PACKET_MODE_MSK = 0x0000ff00, /* bits 8:15 */ + TX_FIFO_NUMBER_MSK = 0x00070000, /* bits 16:18 */ + TX_RESERVED = 0x00780000, /* bits 19:22 */ + TX_POWER_PA_DETECT_MSK = 0x7f800000, /* bits 23:30 */ + TX_ABORT_REQUIRED_MSK = 0x80000000, /* bits 31:31 */ +}; + +/* ******************************* + * TX aggregation status + ******************************* */ + +enum { + AGG_TX_STATE_TRANSMITTED = 0x00, + AGG_TX_STATE_UNDERRUN_MSK = 0x01, + AGG_TX_STATE_BT_PRIO_MSK = 0x02, + AGG_TX_STATE_FEW_BYTES_MSK = 0x04, + AGG_TX_STATE_ABORT_MSK = 0x08, + AGG_TX_STATE_LAST_SENT_TTL_MSK = 0x10, + AGG_TX_STATE_LAST_SENT_TRY_CNT_MSK = 0x20, + AGG_TX_STATE_LAST_SENT_BT_KILL_MSK = 0x40, + AGG_TX_STATE_SCD_QUERY_MSK = 0x80, + AGG_TX_STATE_TEST_BAD_CRC32_MSK = 0x100, + AGG_TX_STATE_RESPONSE_MSK = 0x1ff, + AGG_TX_STATE_DUMP_TX_MSK = 0x200, + AGG_TX_STATE_DELAY_TX_MSK = 0x400 +}; + +#define AGG_TX_STATUS_MSK 0x00000fff /* bits 0:11 */ +#define AGG_TX_TRY_MSK 0x0000f000 /* bits 12:15 */ +#define AGG_TX_TRY_POS 12 + +#define AGG_TX_STATE_LAST_SENT_MSK (AGG_TX_STATE_LAST_SENT_TTL_MSK | \ + AGG_TX_STATE_LAST_SENT_TRY_CNT_MSK | \ + AGG_TX_STATE_LAST_SENT_BT_KILL_MSK) + +/* # tx attempts for first frame in aggregation */ +#define AGG_TX_STATE_TRY_CNT_POS 12 +#define AGG_TX_STATE_TRY_CNT_MSK 0xf000 + +/* Command ID and sequence number of Tx command for this frame */ +#define AGG_TX_STATE_SEQ_NUM_POS 16 +#define AGG_TX_STATE_SEQ_NUM_MSK 0xffff0000 + +/* + * REPLY_TX = 0x1c (response) + * + * This response may be in one of two slightly different formats, indicated + * by the frame_count field: + * + * 1) No aggregation (frame_count == 1). This reports Tx results for + * a single frame. Multiple attempts, at various bit rates, may have + * been made for this frame. + * + * 2) Aggregation (frame_count > 1). This reports Tx results for + * 2 or more frames that used block-acknowledge. All frames were + * transmitted at same rate. Rate scaling may have been used if first + * frame in this new agg block failed in previous agg block(s). + * + * Note that, for aggregation, ACK (block-ack) status is not delivered here; + * block-ack has not been received by the time the agn device records + * this status. + * This status relates to reasons the tx might have been blocked or aborted + * within the sending station (this agn device), rather than whether it was + * received successfully by the destination station. + */ +struct agg_tx_status { + __le16 status; + __le16 sequence; +} __packed; + +/* + * definitions for initial rate index field + * bits [3:0] initial rate index + * bits [6:4] rate table color, used for the initial rate + * bit-7 invalid rate indication + * i.e. rate was not chosen from rate table + * or rate table color was changed during frame retries + * refer tlc rate info + */ + +#define IWL50_TX_RES_INIT_RATE_INDEX_POS 0 +#define IWL50_TX_RES_INIT_RATE_INDEX_MSK 0x0f +#define IWL50_TX_RES_RATE_TABLE_COLOR_POS 4 +#define IWL50_TX_RES_RATE_TABLE_COLOR_MSK 0x70 +#define IWL50_TX_RES_INV_RATE_INDEX_MSK 0x80 + +/* refer to ra_tid */ +#define IWLAGN_TX_RES_TID_POS 0 +#define IWLAGN_TX_RES_TID_MSK 0x0f +#define IWLAGN_TX_RES_RA_POS 4 +#define IWLAGN_TX_RES_RA_MSK 0xf0 + +struct iwlagn_tx_resp { + u8 frame_count; /* 1 no aggregation, >1 aggregation */ + u8 bt_kill_count; /* # blocked by bluetooth (unused for agg) */ + u8 failure_rts; /* # failures due to unsuccessful RTS */ + u8 failure_frame; /* # failures due to no ACK (unused for agg) */ + + /* For non-agg: Rate at which frame was successful. + * For agg: Rate at which all frames were transmitted. */ + __le32 rate_n_flags; /* RATE_MCS_* */ + + /* For non-agg: RTS + CTS + frame tx attempts time + ACK. + * For agg: RTS + CTS + aggregation tx time + block-ack time. */ + __le16 wireless_media_time; /* uSecs */ + + u8 pa_status; /* RF power amplifier measurement (not used) */ + u8 pa_integ_res_a[3]; + u8 pa_integ_res_b[3]; + u8 pa_integ_res_C[3]; + + __le32 tfd_info; + __le16 seq_ctl; + __le16 byte_cnt; + u8 tlc_info; + u8 ra_tid; /* tid (0:3), sta_id (4:7) */ + __le16 frame_ctrl; + /* + * For non-agg: frame status TX_STATUS_* + * For agg: status of 1st frame, AGG_TX_STATE_*; other frame status + * fields follow this one, up to frame_count. + * Bit fields: + * 11- 0: AGG_TX_STATE_* status code + * 15-12: Retry count for 1st frame in aggregation (retries + * occur if tx failed for this frame when it was a + * member of a previous aggregation block). If rate + * scaling is used, retry count indicates the rate + * table entry used for all frames in the new agg. + * 31-16: Sequence # for this frame's Tx cmd (not SSN!) + */ + struct agg_tx_status status; /* TX status (in aggregation - + * status of 1st frame) */ +} __packed; +/* + * REPLY_COMPRESSED_BA = 0xc5 (response only, not a command) + * + * Reports Block-Acknowledge from recipient station + */ +struct iwl_compressed_ba_resp { + __le32 sta_addr_lo32; + __le16 sta_addr_hi16; + __le16 reserved; + + /* Index of recipient (BA-sending) station in uCode's station table */ + u8 sta_id; + u8 tid; + __le16 seq_ctl; + __le64 bitmap; + __le16 scd_flow; + __le16 scd_ssn; + u8 txed; /* number of frames sent */ + u8 txed_2_done; /* number of frames acked */ + __le16 reserved1; +} __packed; + +/* + * REPLY_TX_PWR_TABLE_CMD = 0x97 (command, has simple generic response) + * + */ + +/*RS_NEW_API: only TLC_RTS remains and moved to bit 0 */ +#define LINK_QUAL_FLAGS_SET_STA_TLC_RTS_MSK (1 << 0) + +/* # of EDCA prioritized tx fifos */ +#define LINK_QUAL_AC_NUM AC_NUM + +/* # entries in rate scale table to support Tx retries */ +#define LINK_QUAL_MAX_RETRY_NUM 16 + +/* Tx antenna selection values */ +#define LINK_QUAL_ANT_A_MSK (1 << 0) +#define LINK_QUAL_ANT_B_MSK (1 << 1) +#define LINK_QUAL_ANT_MSK (LINK_QUAL_ANT_A_MSK|LINK_QUAL_ANT_B_MSK) + + +/** + * struct iwl_link_qual_general_params + * + * Used in REPLY_TX_LINK_QUALITY_CMD + */ +struct iwl_link_qual_general_params { + u8 flags; + + /* No entries at or above this (driver chosen) index contain MIMO */ + u8 mimo_delimiter; + + /* Best single antenna to use for single stream (legacy, SISO). */ + u8 single_stream_ant_msk; /* LINK_QUAL_ANT_* */ + + /* Best antennas to use for MIMO (unused for 4965, assumes both). */ + u8 dual_stream_ant_msk; /* LINK_QUAL_ANT_* */ + + /* + * If driver needs to use different initial rates for different + * EDCA QOS access categories (as implemented by tx fifos 0-3), + * this table will set that up, by indicating the indexes in the + * rs_table[LINK_QUAL_MAX_RETRY_NUM] rate table at which to start. + * Otherwise, driver should set all entries to 0. + * + * Entry usage: + * 0 = Background, 1 = Best Effort (normal), 2 = Video, 3 = Voice + * TX FIFOs above 3 use same value (typically 0) as TX FIFO 3. + */ + u8 start_rate_index[LINK_QUAL_AC_NUM]; +} __packed; + +#define LINK_QUAL_AGG_TIME_LIMIT_DEF (4000) /* 4 milliseconds */ +#define LINK_QUAL_AGG_TIME_LIMIT_MAX (8000) +#define LINK_QUAL_AGG_TIME_LIMIT_MIN (100) + +#define LINK_QUAL_AGG_DISABLE_START_DEF (3) +#define LINK_QUAL_AGG_DISABLE_START_MAX (255) +#define LINK_QUAL_AGG_DISABLE_START_MIN (0) + +#define LINK_QUAL_AGG_FRAME_LIMIT_DEF (63) +#define LINK_QUAL_AGG_FRAME_LIMIT_MAX (63) +#define LINK_QUAL_AGG_FRAME_LIMIT_MIN (0) + +/** + * struct iwl_link_qual_agg_params + * + * Used in REPLY_TX_LINK_QUALITY_CMD + */ +struct iwl_link_qual_agg_params { + + /* + *Maximum number of uSec in aggregation. + * default set to 4000 (4 milliseconds) if not configured in .cfg + */ + __le16 agg_time_limit; + + /* + * Number of Tx retries allowed for a frame, before that frame will + * no longer be considered for the start of an aggregation sequence + * (scheduler will then try to tx it as single frame). + * Driver should set this to 3. + */ + u8 agg_dis_start_th; + + /* + * Maximum number of frames in aggregation. + * 0 = no limit (default). 1 = no aggregation. + * Other values = max # frames in aggregation. + */ + u8 agg_frame_cnt_limit; + + __le32 reserved; +} __packed; + +/* + * REPLY_TX_LINK_QUALITY_CMD = 0x4e (command, has simple generic response) + * + * For agn devices + * + * Each station in the agn device's internal station table has its own table + * of 16 + * Tx rates and modulation modes (e.g. legacy/SISO/MIMO) for retrying Tx when + * an ACK is not received. This command replaces the entire table for + * one station. + * + * NOTE: Station must already be in agn device's station table. + * Use REPLY_ADD_STA. + * + * The rate scaling procedures described below work well. Of course, other + * procedures are possible, and may work better for particular environments. + * + * + * FILLING THE RATE TABLE + * + * Given a particular initial rate and mode, as determined by the rate + * scaling algorithm described below, the Linux driver uses the following + * formula to fill the rs_table[LINK_QUAL_MAX_RETRY_NUM] rate table in the + * Link Quality command: + * + * + * 1) If using High-throughput (HT) (SISO or MIMO) initial rate: + * a) Use this same initial rate for first 3 entries. + * b) Find next lower available rate using same mode (SISO or MIMO), + * use for next 3 entries. If no lower rate available, switch to + * legacy mode (no HT40 channel, no MIMO, no short guard interval). + * c) If using MIMO, set command's mimo_delimiter to number of entries + * using MIMO (3 or 6). + * d) After trying 2 HT rates, switch to legacy mode (no HT40 channel, + * no MIMO, no short guard interval), at the next lower bit rate + * (e.g. if second HT bit rate was 54, try 48 legacy), and follow + * legacy procedure for remaining table entries. + * + * 2) If using legacy initial rate: + * a) Use the initial rate for only one entry. + * b) For each following entry, reduce the rate to next lower available + * rate, until reaching the lowest available rate. + * c) When reducing rate, also switch antenna selection. + * d) Once lowest available rate is reached, repeat this rate until + * rate table is filled (16 entries), switching antenna each entry. + * + * + * ACCUMULATING HISTORY + * + * The rate scaling algorithm for agn devices, as implemented in Linux driver, + * uses two sets of frame Tx success history: One for the current/active + * modulation mode, and one for a speculative/search mode that is being + * attempted. If the speculative mode turns out to be more effective (i.e. + * actual transfer rate is better), then the driver continues to use the + * speculative mode as the new current active mode. + * + * Each history set contains, separately for each possible rate, data for a + * sliding window of the 62 most recent tx attempts at that rate. The data + * includes a shifting bitmap of success(1)/failure(0), and sums of successful + * and attempted frames, from which the driver can additionally calculate a + * success ratio (success / attempted) and number of failures + * (attempted - success), and control the size of the window (attempted). + * The driver uses the bit map to remove successes from the success sum, as + * the oldest tx attempts fall out of the window. + * + * When the agn device makes multiple tx attempts for a given frame, each + * attempt might be at a different rate, and have different modulation + * characteristics (e.g. antenna, fat channel, short guard interval), as set + * up in the rate scaling table in the Link Quality command. The driver must + * determine which rate table entry was used for each tx attempt, to determine + * which rate-specific history to update, and record only those attempts that + * match the modulation characteristics of the history set. + * + * When using block-ack (aggregation), all frames are transmitted at the same + * rate, since there is no per-attempt acknowledgment from the destination + * station. The Tx response struct iwl_tx_resp indicates the Tx rate in + * rate_n_flags field. After receiving a block-ack, the driver can update + * history for the entire block all at once. + * + * + * FINDING BEST STARTING RATE: + * + * When working with a selected initial modulation mode (see below), the + * driver attempts to find a best initial rate. The initial rate is the + * first entry in the Link Quality command's rate table. + * + * 1) Calculate actual throughput (success ratio * expected throughput, see + * table below) for current initial rate. Do this only if enough frames + * have been attempted to make the value meaningful: at least 6 failed + * tx attempts, or at least 8 successes. If not enough, don't try rate + * scaling yet. + * + * 2) Find available rates adjacent to current initial rate. Available means: + * a) supported by hardware && + * b) supported by association && + * c) within any constraints selected by user + * + * 3) Gather measured throughputs for adjacent rates. These might not have + * enough history to calculate a throughput. That's okay, we might try + * using one of them anyway! + * + * 4) Try decreasing rate if, for current rate: + * a) success ratio is < 15% || + * b) lower adjacent rate has better measured throughput || + * c) higher adjacent rate has worse throughput, and lower is unmeasured + * + * As a sanity check, if decrease was determined above, leave rate + * unchanged if: + * a) lower rate unavailable + * b) success ratio at current rate > 85% (very good) + * c) current measured throughput is better than expected throughput + * of lower rate (under perfect 100% tx conditions, see table below) + * + * 5) Try increasing rate if, for current rate: + * a) success ratio is < 15% || + * b) both adjacent rates' throughputs are unmeasured (try it!) || + * b) higher adjacent rate has better measured throughput || + * c) lower adjacent rate has worse throughput, and higher is unmeasured + * + * As a sanity check, if increase was determined above, leave rate + * unchanged if: + * a) success ratio at current rate < 70%. This is not particularly + * good performance; higher rate is sure to have poorer success. + * + * 6) Re-evaluate the rate after each tx frame. If working with block- + * acknowledge, history and statistics may be calculated for the entire + * block (including prior history that fits within the history windows), + * before re-evaluation. + * + * FINDING BEST STARTING MODULATION MODE: + * + * After working with a modulation mode for a "while" (and doing rate scaling), + * the driver searches for a new initial mode in an attempt to improve + * throughput. The "while" is measured by numbers of attempted frames: + * + * For legacy mode, search for new mode after: + * 480 successful frames, or 160 failed frames + * For high-throughput modes (SISO or MIMO), search for new mode after: + * 4500 successful frames, or 400 failed frames + * + * Mode switch possibilities are (3 for each mode): + * + * For legacy: + * Change antenna, try SISO (if HT association), try MIMO (if HT association) + * For SISO: + * Change antenna, try MIMO, try shortened guard interval (SGI) + * For MIMO: + * Try SISO antenna A, SISO antenna B, try shortened guard interval (SGI) + * + * When trying a new mode, use the same bit rate as the old/current mode when + * trying antenna switches and shortened guard interval. When switching to + * SISO from MIMO or legacy, or to MIMO from SISO or legacy, use a rate + * for which the expected throughput (under perfect conditions) is about the + * same or slightly better than the actual measured throughput delivered by + * the old/current mode. + * + * Actual throughput can be estimated by multiplying the expected throughput + * by the success ratio (successful / attempted tx frames). Frame size is + * not considered in this calculation; it assumes that frame size will average + * out to be fairly consistent over several samples. The following are + * metric values for expected throughput assuming 100% success ratio. + * Only G band has support for CCK rates: + * + * RATE: 1 2 5 11 6 9 12 18 24 36 48 54 60 + * + * G: 7 13 35 58 40 57 72 98 121 154 177 186 186 + * A: 0 0 0 0 40 57 72 98 121 154 177 186 186 + * SISO 20MHz: 0 0 0 0 42 42 76 102 124 159 183 193 202 + * SGI SISO 20MHz: 0 0 0 0 46 46 82 110 132 168 192 202 211 + * MIMO 20MHz: 0 0 0 0 74 74 123 155 179 214 236 244 251 + * SGI MIMO 20MHz: 0 0 0 0 81 81 131 164 188 222 243 251 257 + * SISO 40MHz: 0 0 0 0 77 77 127 160 184 220 242 250 257 + * SGI SISO 40MHz: 0 0 0 0 83 83 135 169 193 229 250 257 264 + * MIMO 40MHz: 0 0 0 0 123 123 182 214 235 264 279 285 289 + * SGI MIMO 40MHz: 0 0 0 0 131 131 191 222 242 270 284 289 293 + * + * After the new mode has been tried for a short while (minimum of 6 failed + * frames or 8 successful frames), compare success ratio and actual throughput + * estimate of the new mode with the old. If either is better with the new + * mode, continue to use the new mode. + * + * Continue comparing modes until all 3 possibilities have been tried. + * If moving from legacy to HT, try all 3 possibilities from the new HT + * mode. After trying all 3, a best mode is found. Continue to use this mode + * for the longer "while" described above (e.g. 480 successful frames for + * legacy), and then repeat the search process. + * + */ +struct iwl_link_quality_cmd { + + /* Index of destination/recipient station in uCode's station table */ + u8 sta_id; + u8 reserved1; + __le16 control; /* not used */ + struct iwl_link_qual_general_params general_params; + struct iwl_link_qual_agg_params agg_params; + + /* + * Rate info; when using rate-scaling, Tx command's initial_rate_index + * specifies 1st Tx rate attempted, via index into this table. + * agn devices works its way through table when retrying Tx. + */ + struct { + __le32 rate_n_flags; /* RATE_MCS_*, IWL_RATE_* */ + } rs_table[LINK_QUAL_MAX_RETRY_NUM]; + __le32 reserved2; +} __packed; + +/* + * BT configuration enable flags: + * bit 0 - 1: BT channel announcement enabled + * 0: disable + * bit 1 - 1: priority of BT device enabled + * 0: disable + * bit 2 - 1: BT 2 wire support enabled + * 0: disable + */ +#define BT_COEX_DISABLE (0x0) +#define BT_ENABLE_CHANNEL_ANNOUNCE BIT(0) +#define BT_ENABLE_PRIORITY BIT(1) +#define BT_ENABLE_2_WIRE BIT(2) + +#define BT_COEX_DISABLE (0x0) +#define BT_COEX_ENABLE (BT_ENABLE_CHANNEL_ANNOUNCE | BT_ENABLE_PRIORITY) + +#define BT_LEAD_TIME_MIN (0x0) +#define BT_LEAD_TIME_DEF (0x1E) +#define BT_LEAD_TIME_MAX (0xFF) + +#define BT_MAX_KILL_MIN (0x1) +#define BT_MAX_KILL_DEF (0x5) +#define BT_MAX_KILL_MAX (0xFF) + +#define BT_DURATION_LIMIT_DEF 625 +#define BT_DURATION_LIMIT_MAX 1250 +#define BT_DURATION_LIMIT_MIN 625 + +#define BT_ON_THRESHOLD_DEF 4 +#define BT_ON_THRESHOLD_MAX 1000 +#define BT_ON_THRESHOLD_MIN 1 + +#define BT_FRAG_THRESHOLD_DEF 0 +#define BT_FRAG_THRESHOLD_MAX 0 +#define BT_FRAG_THRESHOLD_MIN 0 + +#define BT_AGG_THRESHOLD_DEF 1200 +#define BT_AGG_THRESHOLD_MAX 8000 +#define BT_AGG_THRESHOLD_MIN 400 + +/* + * REPLY_BT_CONFIG = 0x9b (command, has simple generic response) + * + * agn devices support hardware handshake with Bluetooth device on + * same platform. Bluetooth device alerts wireless device when it will Tx; + * wireless device can delay or kill its own Tx to accommodate. + */ +struct iwl_bt_cmd { + u8 flags; + u8 lead_time; + u8 max_kill; + u8 reserved; + __le32 kill_ack_mask; + __le32 kill_cts_mask; +} __packed; + +#define IWLAGN_BT_FLAG_CHANNEL_INHIBITION BIT(0) + +#define IWLAGN_BT_FLAG_COEX_MODE_MASK (BIT(3)|BIT(4)|BIT(5)) +#define IWLAGN_BT_FLAG_COEX_MODE_SHIFT 3 +#define IWLAGN_BT_FLAG_COEX_MODE_DISABLED 0 +#define IWLAGN_BT_FLAG_COEX_MODE_LEGACY_2W 1 +#define IWLAGN_BT_FLAG_COEX_MODE_3W 2 +#define IWLAGN_BT_FLAG_COEX_MODE_4W 3 + +#define IWLAGN_BT_FLAG_UCODE_DEFAULT BIT(6) +/* Disable Sync PSPoll on SCO/eSCO */ +#define IWLAGN_BT_FLAG_SYNC_2_BT_DISABLE BIT(7) + +#define IWLAGN_BT_PSP_MIN_RSSI_THRESHOLD -75 /* dBm */ +#define IWLAGN_BT_PSP_MAX_RSSI_THRESHOLD -65 /* dBm */ + +#define IWLAGN_BT_PRIO_BOOST_MAX 0xFF +#define IWLAGN_BT_PRIO_BOOST_MIN 0x00 +#define IWLAGN_BT_PRIO_BOOST_DEFAULT 0xF0 +#define IWLAGN_BT_PRIO_BOOST_DEFAULT32 0xF0F0F0F0 + +#define IWLAGN_BT_MAX_KILL_DEFAULT 5 + +#define IWLAGN_BT3_T7_DEFAULT 1 + +enum iwl_bt_kill_idx { + IWL_BT_KILL_DEFAULT = 0, + IWL_BT_KILL_OVERRIDE = 1, + IWL_BT_KILL_REDUCE = 2, +}; + +#define IWLAGN_BT_KILL_ACK_MASK_DEFAULT cpu_to_le32(0xffff0000) +#define IWLAGN_BT_KILL_CTS_MASK_DEFAULT cpu_to_le32(0xffff0000) +#define IWLAGN_BT_KILL_ACK_CTS_MASK_SCO cpu_to_le32(0xffffffff) +#define IWLAGN_BT_KILL_ACK_CTS_MASK_REDUCE cpu_to_le32(0) + +#define IWLAGN_BT3_PRIO_SAMPLE_DEFAULT 2 + +#define IWLAGN_BT3_T2_DEFAULT 0xc + +#define IWLAGN_BT_VALID_ENABLE_FLAGS cpu_to_le16(BIT(0)) +#define IWLAGN_BT_VALID_BOOST cpu_to_le16(BIT(1)) +#define IWLAGN_BT_VALID_MAX_KILL cpu_to_le16(BIT(2)) +#define IWLAGN_BT_VALID_3W_TIMERS cpu_to_le16(BIT(3)) +#define IWLAGN_BT_VALID_KILL_ACK_MASK cpu_to_le16(BIT(4)) +#define IWLAGN_BT_VALID_KILL_CTS_MASK cpu_to_le16(BIT(5)) +#define IWLAGN_BT_VALID_REDUCED_TX_PWR cpu_to_le16(BIT(6)) +#define IWLAGN_BT_VALID_3W_LUT cpu_to_le16(BIT(7)) + +#define IWLAGN_BT_ALL_VALID_MSK (IWLAGN_BT_VALID_ENABLE_FLAGS | \ + IWLAGN_BT_VALID_BOOST | \ + IWLAGN_BT_VALID_MAX_KILL | \ + IWLAGN_BT_VALID_3W_TIMERS | \ + IWLAGN_BT_VALID_KILL_ACK_MASK | \ + IWLAGN_BT_VALID_KILL_CTS_MASK | \ + IWLAGN_BT_VALID_REDUCED_TX_PWR | \ + IWLAGN_BT_VALID_3W_LUT) + +#define IWLAGN_BT_REDUCED_TX_PWR BIT(0) + +#define IWLAGN_BT_DECISION_LUT_SIZE 12 + +struct iwl_basic_bt_cmd { + u8 flags; + u8 ledtime; /* unused */ + u8 max_kill; + u8 bt3_timer_t7_value; + __le32 kill_ack_mask; + __le32 kill_cts_mask; + u8 bt3_prio_sample_time; + u8 bt3_timer_t2_value; + __le16 bt4_reaction_time; /* unused */ + __le32 bt3_lookup_table[IWLAGN_BT_DECISION_LUT_SIZE]; + /* + * bit 0: use reduced tx power for control frame + * bit 1 - 7: reserved + */ + u8 reduce_txpower; + u8 reserved; + __le16 valid; +}; + +struct iwl_bt_cmd_v1 { + struct iwl_basic_bt_cmd basic; + u8 prio_boost; + /* + * set IWLAGN_BT_VALID_BOOST to "1" in "valid" bitmask + * if configure the following patterns + */ + u8 tx_prio_boost; /* SW boost of WiFi tx priority */ + __le16 rx_prio_boost; /* SW boost of WiFi rx priority */ +}; + +struct iwl_bt_cmd_v2 { + struct iwl_basic_bt_cmd basic; + __le32 prio_boost; + /* + * set IWLAGN_BT_VALID_BOOST to "1" in "valid" bitmask + * if configure the following patterns + */ + u8 reserved; + u8 tx_prio_boost; /* SW boost of WiFi tx priority */ + __le16 rx_prio_boost; /* SW boost of WiFi rx priority */ +}; + +#define IWLAGN_BT_SCO_ACTIVE cpu_to_le32(BIT(0)) + +struct iwlagn_bt_sco_cmd { + __le32 flags; +}; + +/****************************************************************************** + * (6) + * Spectrum Management (802.11h) Commands, Responses, Notifications: + * + *****************************************************************************/ + +/* + * Spectrum Management + */ +#define MEASUREMENT_FILTER_FLAG (RXON_FILTER_PROMISC_MSK | \ + RXON_FILTER_CTL2HOST_MSK | \ + RXON_FILTER_ACCEPT_GRP_MSK | \ + RXON_FILTER_DIS_DECRYPT_MSK | \ + RXON_FILTER_DIS_GRP_DECRYPT_MSK | \ + RXON_FILTER_ASSOC_MSK | \ + RXON_FILTER_BCON_AWARE_MSK) + +struct iwl_measure_channel { + __le32 duration; /* measurement duration in extended beacon + * format */ + u8 channel; /* channel to measure */ + u8 type; /* see enum iwl_measure_type */ + __le16 reserved; +} __packed; + +/* + * REPLY_SPECTRUM_MEASUREMENT_CMD = 0x74 (command) + */ +struct iwl_spectrum_cmd { + __le16 len; /* number of bytes starting from token */ + u8 token; /* token id */ + u8 id; /* measurement id -- 0 or 1 */ + u8 origin; /* 0 = TGh, 1 = other, 2 = TGk */ + u8 periodic; /* 1 = periodic */ + __le16 path_loss_timeout; + __le32 start_time; /* start time in extended beacon format */ + __le32 reserved2; + __le32 flags; /* rxon flags */ + __le32 filter_flags; /* rxon filter flags */ + __le16 channel_count; /* minimum 1, maximum 10 */ + __le16 reserved3; + struct iwl_measure_channel channels[10]; +} __packed; + +/* + * REPLY_SPECTRUM_MEASUREMENT_CMD = 0x74 (response) + */ +struct iwl_spectrum_resp { + u8 token; + u8 id; /* id of the prior command replaced, or 0xff */ + __le16 status; /* 0 - command will be handled + * 1 - cannot handle (conflicts with another + * measurement) */ +} __packed; + +enum iwl_measurement_state { + IWL_MEASUREMENT_START = 0, + IWL_MEASUREMENT_STOP = 1, +}; + +enum iwl_measurement_status { + IWL_MEASUREMENT_OK = 0, + IWL_MEASUREMENT_CONCURRENT = 1, + IWL_MEASUREMENT_CSA_CONFLICT = 2, + IWL_MEASUREMENT_TGH_CONFLICT = 3, + /* 4-5 reserved */ + IWL_MEASUREMENT_STOPPED = 6, + IWL_MEASUREMENT_TIMEOUT = 7, + IWL_MEASUREMENT_PERIODIC_FAILED = 8, +}; + +#define NUM_ELEMENTS_IN_HISTOGRAM 8 + +struct iwl_measurement_histogram { + __le32 ofdm[NUM_ELEMENTS_IN_HISTOGRAM]; /* in 0.8usec counts */ + __le32 cck[NUM_ELEMENTS_IN_HISTOGRAM]; /* in 1usec counts */ +} __packed; + +/* clear channel availability counters */ +struct iwl_measurement_cca_counters { + __le32 ofdm; + __le32 cck; +} __packed; + +enum iwl_measure_type { + IWL_MEASURE_BASIC = (1 << 0), + IWL_MEASURE_CHANNEL_LOAD = (1 << 1), + IWL_MEASURE_HISTOGRAM_RPI = (1 << 2), + IWL_MEASURE_HISTOGRAM_NOISE = (1 << 3), + IWL_MEASURE_FRAME = (1 << 4), + /* bits 5:6 are reserved */ + IWL_MEASURE_IDLE = (1 << 7), +}; + +/* + * SPECTRUM_MEASURE_NOTIFICATION = 0x75 (notification only, not a command) + */ +struct iwl_spectrum_notification { + u8 id; /* measurement id -- 0 or 1 */ + u8 token; + u8 channel_index; /* index in measurement channel list */ + u8 state; /* 0 - start, 1 - stop */ + __le32 start_time; /* lower 32-bits of TSF */ + u8 band; /* 0 - 5.2GHz, 1 - 2.4GHz */ + u8 channel; + u8 type; /* see enum iwl_measurement_type */ + u8 reserved1; + /* NOTE: cca_ofdm, cca_cck, basic_type, and histogram are only only + * valid if applicable for measurement type requested. */ + __le32 cca_ofdm; /* cca fraction time in 40Mhz clock periods */ + __le32 cca_cck; /* cca fraction time in 44Mhz clock periods */ + __le32 cca_time; /* channel load time in usecs */ + u8 basic_type; /* 0 - bss, 1 - ofdm preamble, 2 - + * unidentified */ + u8 reserved2[3]; + struct iwl_measurement_histogram histogram; + __le32 stop_time; /* lower 32-bits of TSF */ + __le32 status; /* see iwl_measurement_status */ +} __packed; + +/****************************************************************************** + * (7) + * Power Management Commands, Responses, Notifications: + * + *****************************************************************************/ + +/** + * struct iwl_powertable_cmd - Power Table Command + * @flags: See below: + * + * POWER_TABLE_CMD = 0x77 (command, has simple generic response) + * + * PM allow: + * bit 0 - '0' Driver not allow power management + * '1' Driver allow PM (use rest of parameters) + * + * uCode send sleep notifications: + * bit 1 - '0' Don't send sleep notification + * '1' send sleep notification (SEND_PM_NOTIFICATION) + * + * Sleep over DTIM + * bit 2 - '0' PM have to walk up every DTIM + * '1' PM could sleep over DTIM till listen Interval. + * + * PCI power managed + * bit 3 - '0' (PCI_CFG_LINK_CTRL & 0x1) + * '1' !(PCI_CFG_LINK_CTRL & 0x1) + * + * Fast PD + * bit 4 - '1' Put radio to sleep when receiving frame for others + * + * Force sleep Modes + * bit 31/30- '00' use both mac/xtal sleeps + * '01' force Mac sleep + * '10' force xtal sleep + * '11' Illegal set + * + * NOTE: if sleep_interval[SLEEP_INTRVL_TABLE_SIZE-1] > DTIM period then + * ucode assume sleep over DTIM is allowed and we don't need to wake up + * for every DTIM. + */ +#define IWL_POWER_VEC_SIZE 5 + +#define IWL_POWER_DRIVER_ALLOW_SLEEP_MSK cpu_to_le16(BIT(0)) +#define IWL_POWER_POWER_SAVE_ENA_MSK cpu_to_le16(BIT(0)) +#define IWL_POWER_POWER_MANAGEMENT_ENA_MSK cpu_to_le16(BIT(1)) +#define IWL_POWER_SLEEP_OVER_DTIM_MSK cpu_to_le16(BIT(2)) +#define IWL_POWER_PCI_PM_MSK cpu_to_le16(BIT(3)) +#define IWL_POWER_FAST_PD cpu_to_le16(BIT(4)) +#define IWL_POWER_BEACON_FILTERING cpu_to_le16(BIT(5)) +#define IWL_POWER_SHADOW_REG_ENA cpu_to_le16(BIT(6)) +#define IWL_POWER_CT_KILL_SET cpu_to_le16(BIT(7)) +#define IWL_POWER_BT_SCO_ENA cpu_to_le16(BIT(8)) +#define IWL_POWER_ADVANCE_PM_ENA_MSK cpu_to_le16(BIT(9)) + +struct iwl_powertable_cmd { + __le16 flags; + u8 keep_alive_seconds; + u8 debug_flags; + __le32 rx_data_timeout; + __le32 tx_data_timeout; + __le32 sleep_interval[IWL_POWER_VEC_SIZE]; + __le32 keep_alive_beacons; +} __packed; + +/* + * PM_SLEEP_NOTIFICATION = 0x7A (notification only, not a command) + * all devices identical. + */ +struct iwl_sleep_notification { + u8 pm_sleep_mode; + u8 pm_wakeup_src; + __le16 reserved; + __le32 sleep_time; + __le32 tsf_low; + __le32 bcon_timer; +} __packed; + +/* Sleep states. all devices identical. */ +enum { + IWL_PM_NO_SLEEP = 0, + IWL_PM_SLP_MAC = 1, + IWL_PM_SLP_FULL_MAC_UNASSOCIATE = 2, + IWL_PM_SLP_FULL_MAC_CARD_STATE = 3, + IWL_PM_SLP_PHY = 4, + IWL_PM_SLP_REPENT = 5, + IWL_PM_WAKEUP_BY_TIMER = 6, + IWL_PM_WAKEUP_BY_DRIVER = 7, + IWL_PM_WAKEUP_BY_RFKILL = 8, + /* 3 reserved */ + IWL_PM_NUM_OF_MODES = 12, +}; + +/* + * REPLY_CARD_STATE_CMD = 0xa0 (command, has simple generic response) + */ +#define CARD_STATE_CMD_DISABLE 0x00 /* Put card to sleep */ +#define CARD_STATE_CMD_ENABLE 0x01 /* Wake up card */ +#define CARD_STATE_CMD_HALT 0x02 /* Power down permanently */ +struct iwl_card_state_cmd { + __le32 status; /* CARD_STATE_CMD_* request new power state */ +} __packed; + +/* + * CARD_STATE_NOTIFICATION = 0xa1 (notification only, not a command) + */ +struct iwl_card_state_notif { + __le32 flags; +} __packed; + +#define HW_CARD_DISABLED 0x01 +#define SW_CARD_DISABLED 0x02 +#define CT_CARD_DISABLED 0x04 +#define RXON_CARD_DISABLED 0x10 + +struct iwl_ct_kill_config { + __le32 reserved; + __le32 critical_temperature_M; + __le32 critical_temperature_R; +} __packed; + +/* 1000, and 6x00 */ +struct iwl_ct_kill_throttling_config { + __le32 critical_temperature_exit; + __le32 reserved; + __le32 critical_temperature_enter; +} __packed; + +/****************************************************************************** + * (8) + * Scan Commands, Responses, Notifications: + * + *****************************************************************************/ + +#define SCAN_CHANNEL_TYPE_PASSIVE cpu_to_le32(0) +#define SCAN_CHANNEL_TYPE_ACTIVE cpu_to_le32(1) + +/** + * struct iwl_scan_channel - entry in REPLY_SCAN_CMD channel table + * + * One for each channel in the scan list. + * Each channel can independently select: + * 1) SSID for directed active scans + * 2) Txpower setting (for rate specified within Tx command) + * 3) How long to stay on-channel (behavior may be modified by quiet_time, + * quiet_plcp_th, good_CRC_th) + * + * To avoid uCode errors, make sure the following are true (see comments + * under struct iwl_scan_cmd about max_out_time and quiet_time): + * 1) If using passive_dwell (i.e. passive_dwell != 0): + * active_dwell <= passive_dwell (< max_out_time if max_out_time != 0) + * 2) quiet_time <= active_dwell + * 3) If restricting off-channel time (i.e. max_out_time !=0): + * passive_dwell < max_out_time + * active_dwell < max_out_time + */ + +struct iwl_scan_channel { + /* + * type is defined as: + * 0:0 1 = active, 0 = passive + * 1:20 SSID direct bit map; if a bit is set, then corresponding + * SSID IE is transmitted in probe request. + * 21:31 reserved + */ + __le32 type; + __le16 channel; /* band is selected by iwl_scan_cmd "flags" field */ + u8 tx_gain; /* gain for analog radio */ + u8 dsp_atten; /* gain for DSP */ + __le16 active_dwell; /* in 1024-uSec TU (time units), typ 5-50 */ + __le16 passive_dwell; /* in 1024-uSec TU (time units), typ 20-500 */ +} __packed; + +/* set number of direct probes __le32 type */ +#define IWL_SCAN_PROBE_MASK(n) cpu_to_le32((BIT(n) | (BIT(n) - BIT(1)))) + +/** + * struct iwl_ssid_ie - directed scan network information element + * + * Up to 20 of these may appear in REPLY_SCAN_CMD, + * selected by "type" bit field in struct iwl_scan_channel; + * each channel may select different ssids from among the 20 entries. + * SSID IEs get transmitted in reverse order of entry. + */ +struct iwl_ssid_ie { + u8 id; + u8 len; + u8 ssid[32]; +} __packed; + +#define PROBE_OPTION_MAX 20 +#define TX_CMD_LIFE_TIME_INFINITE cpu_to_le32(0xFFFFFFFF) +#define IWL_GOOD_CRC_TH_DISABLED 0 +#define IWL_GOOD_CRC_TH_DEFAULT cpu_to_le16(1) +#define IWL_GOOD_CRC_TH_NEVER cpu_to_le16(0xffff) +#define IWL_MAX_CMD_SIZE 4096 + +/* + * REPLY_SCAN_CMD = 0x80 (command) + * + * The hardware scan command is very powerful; the driver can set it up to + * maintain (relatively) normal network traffic while doing a scan in the + * background. The max_out_time and suspend_time control the ratio of how + * long the device stays on an associated network channel ("service channel") + * vs. how long it's away from the service channel, i.e. tuned to other channels + * for scanning. + * + * max_out_time is the max time off-channel (in usec), and suspend_time + * is how long (in "extended beacon" format) that the scan is "suspended" + * after returning to the service channel. That is, suspend_time is the + * time that we stay on the service channel, doing normal work, between + * scan segments. The driver may set these parameters differently to support + * scanning when associated vs. not associated, and light vs. heavy traffic + * loads when associated. + * + * After receiving this command, the device's scan engine does the following; + * + * 1) Sends SCAN_START notification to driver + * 2) Checks to see if it has time to do scan for one channel + * 3) Sends NULL packet, with power-save (PS) bit set to 1, + * to tell AP that we're going off-channel + * 4) Tunes to first channel in scan list, does active or passive scan + * 5) Sends SCAN_RESULT notification to driver + * 6) Checks to see if it has time to do scan on *next* channel in list + * 7) Repeats 4-6 until it no longer has time to scan the next channel + * before max_out_time expires + * 8) Returns to service channel + * 9) Sends NULL packet with PS=0 to tell AP that we're back + * 10) Stays on service channel until suspend_time expires + * 11) Repeats entire process 2-10 until list is complete + * 12) Sends SCAN_COMPLETE notification + * + * For fast, efficient scans, the scan command also has support for staying on + * a channel for just a short time, if doing active scanning and getting no + * responses to the transmitted probe request. This time is controlled by + * quiet_time, and the number of received packets below which a channel is + * considered "quiet" is controlled by quiet_plcp_threshold. + * + * For active scanning on channels that have regulatory restrictions against + * blindly transmitting, the scan can listen before transmitting, to make sure + * that there is already legitimate activity on the channel. If enough + * packets are cleanly received on the channel (controlled by good_CRC_th, + * typical value 1), the scan engine starts transmitting probe requests. + * + * Driver must use separate scan commands for 2.4 vs. 5 GHz bands. + * + * To avoid uCode errors, see timing restrictions described under + * struct iwl_scan_channel. + */ + +enum iwl_scan_flags { + /* BIT(0) currently unused */ + IWL_SCAN_FLAGS_ACTION_FRAME_TX = BIT(1), + /* bits 2-7 reserved */ +}; + +struct iwl_scan_cmd { + __le16 len; + u8 scan_flags; /* scan flags: see enum iwl_scan_flags */ + u8 channel_count; /* # channels in channel list */ + __le16 quiet_time; /* dwell only this # millisecs on quiet channel + * (only for active scan) */ + __le16 quiet_plcp_th; /* quiet chnl is < this # pkts (typ. 1) */ + __le16 good_CRC_th; /* passive -> active promotion threshold */ + __le16 rx_chain; /* RXON_RX_CHAIN_* */ + __le32 max_out_time; /* max usec to be away from associated (service) + * channel */ + __le32 suspend_time; /* pause scan this long (in "extended beacon + * format") when returning to service chnl: + */ + __le32 flags; /* RXON_FLG_* */ + __le32 filter_flags; /* RXON_FILTER_* */ + + /* For active scans (set to all-0s for passive scans). + * Does not include payload. Must specify Tx rate; no rate scaling. */ + struct iwl_tx_cmd tx_cmd; + + /* For directed active scans (set to all-0s otherwise) */ + struct iwl_ssid_ie direct_scan[PROBE_OPTION_MAX]; + + /* + * Probe request frame, followed by channel list. + * + * Size of probe request frame is specified by byte count in tx_cmd. + * Channel list follows immediately after probe request frame. + * Number of channels in list is specified by channel_count. + * Each channel in list is of type: + * + * struct iwl_scan_channel channels[0]; + * + * NOTE: Only one band of channels can be scanned per pass. You + * must not mix 2.4GHz channels and 5.2GHz channels, and you must wait + * for one scan to complete (i.e. receive SCAN_COMPLETE_NOTIFICATION) + * before requesting another scan. + */ + u8 data[0]; +} __packed; + +/* Can abort will notify by complete notification with abort status. */ +#define CAN_ABORT_STATUS cpu_to_le32(0x1) +/* complete notification statuses */ +#define ABORT_STATUS 0x2 + +/* + * REPLY_SCAN_CMD = 0x80 (response) + */ +struct iwl_scanreq_notification { + __le32 status; /* 1: okay, 2: cannot fulfill request */ +} __packed; + +/* + * SCAN_START_NOTIFICATION = 0x82 (notification only, not a command) + */ +struct iwl_scanstart_notification { + __le32 tsf_low; + __le32 tsf_high; + __le32 beacon_timer; + u8 channel; + u8 band; + u8 reserved[2]; + __le32 status; +} __packed; + +#define SCAN_OWNER_STATUS 0x1 +#define MEASURE_OWNER_STATUS 0x2 + +#define IWL_PROBE_STATUS_OK 0 +#define IWL_PROBE_STATUS_TX_FAILED BIT(0) +/* error statuses combined with TX_FAILED */ +#define IWL_PROBE_STATUS_FAIL_TTL BIT(1) +#define IWL_PROBE_STATUS_FAIL_BT BIT(2) + +#define NUMBER_OF_STATISTICS 1 /* first __le32 is good CRC */ +/* + * SCAN_RESULTS_NOTIFICATION = 0x83 (notification only, not a command) + */ +struct iwl_scanresults_notification { + u8 channel; + u8 band; + u8 probe_status; + u8 num_probe_not_sent; /* not enough time to send */ + __le32 tsf_low; + __le32 tsf_high; + __le32 statistics[NUMBER_OF_STATISTICS]; +} __packed; + +/* + * SCAN_COMPLETE_NOTIFICATION = 0x84 (notification only, not a command) + */ +struct iwl_scancomplete_notification { + u8 scanned_channels; + u8 status; + u8 bt_status; /* BT On/Off status */ + u8 last_channel; + __le32 tsf_low; + __le32 tsf_high; +} __packed; + + +/****************************************************************************** + * (9) + * IBSS/AP Commands and Notifications: + * + *****************************************************************************/ + +enum iwl_ibss_manager { + IWL_NOT_IBSS_MANAGER = 0, + IWL_IBSS_MANAGER = 1, +}; + +/* + * BEACON_NOTIFICATION = 0x90 (notification only, not a command) + */ + +struct iwlagn_beacon_notif { + struct iwlagn_tx_resp beacon_notify_hdr; + __le32 low_tsf; + __le32 high_tsf; + __le32 ibss_mgr_status; +} __packed; + +/* + * REPLY_TX_BEACON = 0x91 (command, has simple generic response) + */ + +struct iwl_tx_beacon_cmd { + struct iwl_tx_cmd tx; + __le16 tim_idx; + u8 tim_size; + u8 reserved1; + struct ieee80211_hdr frame[0]; /* beacon frame */ +} __packed; + +/****************************************************************************** + * (10) + * Statistics Commands and Notifications: + * + *****************************************************************************/ + +#define IWL_TEMP_CONVERT 260 + +#define SUP_RATE_11A_MAX_NUM_CHANNELS 8 +#define SUP_RATE_11B_MAX_NUM_CHANNELS 4 +#define SUP_RATE_11G_MAX_NUM_CHANNELS 12 + +/* Used for passing to driver number of successes and failures per rate */ +struct rate_histogram { + union { + __le32 a[SUP_RATE_11A_MAX_NUM_CHANNELS]; + __le32 b[SUP_RATE_11B_MAX_NUM_CHANNELS]; + __le32 g[SUP_RATE_11G_MAX_NUM_CHANNELS]; + } success; + union { + __le32 a[SUP_RATE_11A_MAX_NUM_CHANNELS]; + __le32 b[SUP_RATE_11B_MAX_NUM_CHANNELS]; + __le32 g[SUP_RATE_11G_MAX_NUM_CHANNELS]; + } failed; +} __packed; + +/* statistics command response */ + +struct statistics_dbg { + __le32 burst_check; + __le32 burst_count; + __le32 wait_for_silence_timeout_cnt; + __le32 reserved[3]; +} __packed; + +struct statistics_rx_phy { + __le32 ina_cnt; + __le32 fina_cnt; + __le32 plcp_err; + __le32 crc32_err; + __le32 overrun_err; + __le32 early_overrun_err; + __le32 crc32_good; + __le32 false_alarm_cnt; + __le32 fina_sync_err_cnt; + __le32 sfd_timeout; + __le32 fina_timeout; + __le32 unresponded_rts; + __le32 rxe_frame_limit_overrun; + __le32 sent_ack_cnt; + __le32 sent_cts_cnt; + __le32 sent_ba_rsp_cnt; + __le32 dsp_self_kill; + __le32 mh_format_err; + __le32 re_acq_main_rssi_sum; + __le32 reserved3; +} __packed; + +struct statistics_rx_ht_phy { + __le32 plcp_err; + __le32 overrun_err; + __le32 early_overrun_err; + __le32 crc32_good; + __le32 crc32_err; + __le32 mh_format_err; + __le32 agg_crc32_good; + __le32 agg_mpdu_cnt; + __le32 agg_cnt; + __le32 unsupport_mcs; +} __packed; + +#define INTERFERENCE_DATA_AVAILABLE cpu_to_le32(1) + +struct statistics_rx_non_phy { + __le32 bogus_cts; /* CTS received when not expecting CTS */ + __le32 bogus_ack; /* ACK received when not expecting ACK */ + __le32 non_bssid_frames; /* number of frames with BSSID that + * doesn't belong to the STA BSSID */ + __le32 filtered_frames; /* count frames that were dumped in the + * filtering process */ + __le32 non_channel_beacons; /* beacons with our bss id but not on + * our serving channel */ + __le32 channel_beacons; /* beacons with our bss id and in our + * serving channel */ + __le32 num_missed_bcon; /* number of missed beacons */ + __le32 adc_rx_saturation_time; /* count in 0.8us units the time the + * ADC was in saturation */ + __le32 ina_detection_search_time;/* total time (in 0.8us) searched + * for INA */ + __le32 beacon_silence_rssi_a; /* RSSI silence after beacon frame */ + __le32 beacon_silence_rssi_b; /* RSSI silence after beacon frame */ + __le32 beacon_silence_rssi_c; /* RSSI silence after beacon frame */ + __le32 interference_data_flag; /* flag for interference data + * availability. 1 when data is + * available. */ + __le32 channel_load; /* counts RX Enable time in uSec */ + __le32 dsp_false_alarms; /* DSP false alarm (both OFDM + * and CCK) counter */ + __le32 beacon_rssi_a; + __le32 beacon_rssi_b; + __le32 beacon_rssi_c; + __le32 beacon_energy_a; + __le32 beacon_energy_b; + __le32 beacon_energy_c; +} __packed; + +struct statistics_rx_non_phy_bt { + struct statistics_rx_non_phy common; + /* additional stats for bt */ + __le32 num_bt_kills; + __le32 reserved[2]; +} __packed; + +struct statistics_rx { + struct statistics_rx_phy ofdm; + struct statistics_rx_phy cck; + struct statistics_rx_non_phy general; + struct statistics_rx_ht_phy ofdm_ht; +} __packed; + +struct statistics_rx_bt { + struct statistics_rx_phy ofdm; + struct statistics_rx_phy cck; + struct statistics_rx_non_phy_bt general; + struct statistics_rx_ht_phy ofdm_ht; +} __packed; + +/** + * struct statistics_tx_power - current tx power + * + * @ant_a: current tx power on chain a in 1/2 dB step + * @ant_b: current tx power on chain b in 1/2 dB step + * @ant_c: current tx power on chain c in 1/2 dB step + */ +struct statistics_tx_power { + u8 ant_a; + u8 ant_b; + u8 ant_c; + u8 reserved; +} __packed; + +struct statistics_tx_non_phy_agg { + __le32 ba_timeout; + __le32 ba_reschedule_frames; + __le32 scd_query_agg_frame_cnt; + __le32 scd_query_no_agg; + __le32 scd_query_agg; + __le32 scd_query_mismatch; + __le32 frame_not_ready; + __le32 underrun; + __le32 bt_prio_kill; + __le32 rx_ba_rsp_cnt; +} __packed; + +struct statistics_tx { + __le32 preamble_cnt; + __le32 rx_detected_cnt; + __le32 bt_prio_defer_cnt; + __le32 bt_prio_kill_cnt; + __le32 few_bytes_cnt; + __le32 cts_timeout; + __le32 ack_timeout; + __le32 expected_ack_cnt; + __le32 actual_ack_cnt; + __le32 dump_msdu_cnt; + __le32 burst_abort_next_frame_mismatch_cnt; + __le32 burst_abort_missing_next_frame_cnt; + __le32 cts_timeout_collision; + __le32 ack_or_ba_timeout_collision; + struct statistics_tx_non_phy_agg agg; + /* + * "tx_power" are optional parameters provided by uCode, + * 6000 series is the only device provide the information, + * Those are reserved fields for all the other devices + */ + struct statistics_tx_power tx_power; + __le32 reserved1; +} __packed; + + +struct statistics_div { + __le32 tx_on_a; + __le32 tx_on_b; + __le32 exec_time; + __le32 probe_time; + __le32 reserved1; + __le32 reserved2; +} __packed; + +struct statistics_general_common { + __le32 temperature; /* radio temperature */ + __le32 temperature_m; /* radio voltage */ + struct statistics_dbg dbg; + __le32 sleep_time; + __le32 slots_out; + __le32 slots_idle; + __le32 ttl_timestamp; + struct statistics_div div; + __le32 rx_enable_counter; + /* + * num_of_sos_states: + * count the number of times we have to re-tune + * in order to get out of bad PHY status + */ + __le32 num_of_sos_states; +} __packed; + +struct statistics_bt_activity { + /* Tx statistics */ + __le32 hi_priority_tx_req_cnt; + __le32 hi_priority_tx_denied_cnt; + __le32 lo_priority_tx_req_cnt; + __le32 lo_priority_tx_denied_cnt; + /* Rx statistics */ + __le32 hi_priority_rx_req_cnt; + __le32 hi_priority_rx_denied_cnt; + __le32 lo_priority_rx_req_cnt; + __le32 lo_priority_rx_denied_cnt; +} __packed; + +struct statistics_general { + struct statistics_general_common common; + __le32 reserved2; + __le32 reserved3; +} __packed; + +struct statistics_general_bt { + struct statistics_general_common common; + struct statistics_bt_activity activity; + __le32 reserved2; + __le32 reserved3; +} __packed; + +#define UCODE_STATISTICS_CLEAR_MSK (0x1 << 0) +#define UCODE_STATISTICS_FREQUENCY_MSK (0x1 << 1) +#define UCODE_STATISTICS_NARROW_BAND_MSK (0x1 << 2) + +/* + * REPLY_STATISTICS_CMD = 0x9c, + * all devices identical. + * + * This command triggers an immediate response containing uCode statistics. + * The response is in the same format as STATISTICS_NOTIFICATION 0x9d, below. + * + * If the CLEAR_STATS configuration flag is set, uCode will clear its + * internal copy of the statistics (counters) after issuing the response. + * This flag does not affect STATISTICS_NOTIFICATIONs after beacons (see below). + * + * If the DISABLE_NOTIF configuration flag is set, uCode will not issue + * STATISTICS_NOTIFICATIONs after received beacons (see below). This flag + * does not affect the response to the REPLY_STATISTICS_CMD 0x9c itself. + */ +#define IWL_STATS_CONF_CLEAR_STATS cpu_to_le32(0x1) /* see above */ +#define IWL_STATS_CONF_DISABLE_NOTIF cpu_to_le32(0x2)/* see above */ +struct iwl_statistics_cmd { + __le32 configuration_flags; /* IWL_STATS_CONF_* */ +} __packed; + +/* + * STATISTICS_NOTIFICATION = 0x9d (notification only, not a command) + * + * By default, uCode issues this notification after receiving a beacon + * while associated. To disable this behavior, set DISABLE_NOTIF flag in the + * REPLY_STATISTICS_CMD 0x9c, above. + * + * Statistics counters continue to increment beacon after beacon, but are + * cleared when changing channels or when driver issues REPLY_STATISTICS_CMD + * 0x9c with CLEAR_STATS bit set (see above). + * + * uCode also issues this notification during scans. uCode clears statistics + * appropriately so that each notification contains statistics for only the + * one channel that has just been scanned. + */ +#define STATISTICS_REPLY_FLG_BAND_24G_MSK cpu_to_le32(0x2) +#define STATISTICS_REPLY_FLG_HT40_MODE_MSK cpu_to_le32(0x8) + +struct iwl_notif_statistics { + __le32 flag; + struct statistics_rx rx; + struct statistics_tx tx; + struct statistics_general general; +} __packed; + +struct iwl_bt_notif_statistics { + __le32 flag; + struct statistics_rx_bt rx; + struct statistics_tx tx; + struct statistics_general_bt general; +} __packed; + +/* + * MISSED_BEACONS_NOTIFICATION = 0xa2 (notification only, not a command) + * + * uCode send MISSED_BEACONS_NOTIFICATION to driver when detect beacon missed + * in regardless of how many missed beacons, which mean when driver receive the + * notification, inside the command, it can find all the beacons information + * which include number of total missed beacons, number of consecutive missed + * beacons, number of beacons received and number of beacons expected to + * receive. + * + * If uCode detected consecutive_missed_beacons > 5, it will reset the radio + * in order to bring the radio/PHY back to working state; which has no relation + * to when driver will perform sensitivity calibration. + * + * Driver should set it own missed_beacon_threshold to decide when to perform + * sensitivity calibration based on number of consecutive missed beacons in + * order to improve overall performance, especially in noisy environment. + * + */ + +#define IWL_MISSED_BEACON_THRESHOLD_MIN (1) +#define IWL_MISSED_BEACON_THRESHOLD_DEF (5) +#define IWL_MISSED_BEACON_THRESHOLD_MAX IWL_MISSED_BEACON_THRESHOLD_DEF + +struct iwl_missed_beacon_notif { + __le32 consecutive_missed_beacons; + __le32 total_missed_becons; + __le32 num_expected_beacons; + __le32 num_recvd_beacons; +} __packed; + + +/****************************************************************************** + * (11) + * Rx Calibration Commands: + * + * With the uCode used for open source drivers, most Tx calibration (except + * for Tx Power) and most Rx calibration is done by uCode during the + * "initialize" phase of uCode boot. Driver must calibrate only: + * + * 1) Tx power (depends on temperature), described elsewhere + * 2) Receiver gain balance (optimize MIMO, and detect disconnected antennas) + * 3) Receiver sensitivity (to optimize signal detection) + * + *****************************************************************************/ + +/** + * SENSITIVITY_CMD = 0xa8 (command, has simple generic response) + * + * This command sets up the Rx signal detector for a sensitivity level that + * is high enough to lock onto all signals within the associated network, + * but low enough to ignore signals that are below a certain threshold, so as + * not to have too many "false alarms". False alarms are signals that the + * Rx DSP tries to lock onto, but then discards after determining that they + * are noise. + * + * The optimum number of false alarms is between 5 and 50 per 200 TUs + * (200 * 1024 uSecs, i.e. 204.8 milliseconds) of actual Rx time (i.e. + * time listening, not transmitting). Driver must adjust sensitivity so that + * the ratio of actual false alarms to actual Rx time falls within this range. + * + * While associated, uCode delivers STATISTICS_NOTIFICATIONs after each + * received beacon. These provide information to the driver to analyze the + * sensitivity. Don't analyze statistics that come in from scanning, or any + * other non-associated-network source. Pertinent statistics include: + * + * From "general" statistics (struct statistics_rx_non_phy): + * + * (beacon_energy_[abc] & 0x0FF00) >> 8 (unsigned, higher value is lower level) + * Measure of energy of desired signal. Used for establishing a level + * below which the device does not detect signals. + * + * (beacon_silence_rssi_[abc] & 0x0FF00) >> 8 (unsigned, units in dB) + * Measure of background noise in silent period after beacon. + * + * channel_load + * uSecs of actual Rx time during beacon period (varies according to + * how much time was spent transmitting). + * + * From "cck" and "ofdm" statistics (struct statistics_rx_phy), separately: + * + * false_alarm_cnt + * Signal locks abandoned early (before phy-level header). + * + * plcp_err + * Signal locks abandoned late (during phy-level header). + * + * NOTE: Both false_alarm_cnt and plcp_err increment monotonically from + * beacon to beacon, i.e. each value is an accumulation of all errors + * before and including the latest beacon. Values will wrap around to 0 + * after counting up to 2^32 - 1. Driver must differentiate vs. + * previous beacon's values to determine # false alarms in the current + * beacon period. + * + * Total number of false alarms = false_alarms + plcp_errs + * + * For OFDM, adjust the following table entries in struct iwl_sensitivity_cmd + * (notice that the start points for OFDM are at or close to settings for + * maximum sensitivity): + * + * START / MIN / MAX + * HD_AUTO_CORR32_X1_TH_ADD_MIN_INDEX 90 / 85 / 120 + * HD_AUTO_CORR32_X1_TH_ADD_MIN_MRC_INDEX 170 / 170 / 210 + * HD_AUTO_CORR32_X4_TH_ADD_MIN_INDEX 105 / 105 / 140 + * HD_AUTO_CORR32_X4_TH_ADD_MIN_MRC_INDEX 220 / 220 / 270 + * + * If actual rate of OFDM false alarms (+ plcp_errors) is too high + * (greater than 50 for each 204.8 msecs listening), reduce sensitivity + * by *adding* 1 to all 4 of the table entries above, up to the max for + * each entry. Conversely, if false alarm rate is too low (less than 5 + * for each 204.8 msecs listening), *subtract* 1 from each entry to + * increase sensitivity. + * + * For CCK sensitivity, keep track of the following: + * + * 1). 20-beacon history of maximum background noise, indicated by + * (beacon_silence_rssi_[abc] & 0x0FF00), units in dB, across the + * 3 receivers. For any given beacon, the "silence reference" is + * the maximum of last 60 samples (20 beacons * 3 receivers). + * + * 2). 10-beacon history of strongest signal level, as indicated + * by (beacon_energy_[abc] & 0x0FF00) >> 8, across the 3 receivers, + * i.e. the strength of the signal through the best receiver at the + * moment. These measurements are "upside down", with lower values + * for stronger signals, so max energy will be *minimum* value. + * + * Then for any given beacon, the driver must determine the *weakest* + * of the strongest signals; this is the minimum level that needs to be + * successfully detected, when using the best receiver at the moment. + * "Max cck energy" is the maximum (higher value means lower energy!) + * of the last 10 minima. Once this is determined, driver must add + * a little margin by adding "6" to it. + * + * 3). Number of consecutive beacon periods with too few false alarms. + * Reset this to 0 at the first beacon period that falls within the + * "good" range (5 to 50 false alarms per 204.8 milliseconds rx). + * + * Then, adjust the following CCK table entries in struct iwl_sensitivity_cmd + * (notice that the start points for CCK are at maximum sensitivity): + * + * START / MIN / MAX + * HD_AUTO_CORR40_X4_TH_ADD_MIN_INDEX 125 / 125 / 200 + * HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_INDEX 200 / 200 / 400 + * HD_MIN_ENERGY_CCK_DET_INDEX 100 / 0 / 100 + * + * If actual rate of CCK false alarms (+ plcp_errors) is too high + * (greater than 50 for each 204.8 msecs listening), method for reducing + * sensitivity is: + * + * 1) *Add* 3 to value in HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_INDEX, + * up to max 400. + * + * 2) If current value in HD_AUTO_CORR40_X4_TH_ADD_MIN_INDEX is < 160, + * sensitivity has been reduced a significant amount; bring it up to + * a moderate 161. Otherwise, *add* 3, up to max 200. + * + * 3) a) If current value in HD_AUTO_CORR40_X4_TH_ADD_MIN_INDEX is > 160, + * sensitivity has been reduced only a moderate or small amount; + * *subtract* 2 from value in HD_MIN_ENERGY_CCK_DET_INDEX, + * down to min 0. Otherwise (if gain has been significantly reduced), + * don't change the HD_MIN_ENERGY_CCK_DET_INDEX value. + * + * b) Save a snapshot of the "silence reference". + * + * If actual rate of CCK false alarms (+ plcp_errors) is too low + * (less than 5 for each 204.8 msecs listening), method for increasing + * sensitivity is used only if: + * + * 1a) Previous beacon did not have too many false alarms + * 1b) AND difference between previous "silence reference" and current + * "silence reference" (prev - current) is 2 or more, + * OR 2) 100 or more consecutive beacon periods have had rate of + * less than 5 false alarms per 204.8 milliseconds rx time. + * + * Method for increasing sensitivity: + * + * 1) *Subtract* 3 from value in HD_AUTO_CORR40_X4_TH_ADD_MIN_INDEX, + * down to min 125. + * + * 2) *Subtract* 3 from value in HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_INDEX, + * down to min 200. + * + * 3) *Add* 2 to value in HD_MIN_ENERGY_CCK_DET_INDEX, up to max 100. + * + * If actual rate of CCK false alarms (+ plcp_errors) is within good range + * (between 5 and 50 for each 204.8 msecs listening): + * + * 1) Save a snapshot of the silence reference. + * + * 2) If previous beacon had too many CCK false alarms (+ plcp_errors), + * give some extra margin to energy threshold by *subtracting* 8 + * from value in HD_MIN_ENERGY_CCK_DET_INDEX. + * + * For all cases (too few, too many, good range), make sure that the CCK + * detection threshold (energy) is below the energy level for robust + * detection over the past 10 beacon periods, the "Max cck energy". + * Lower values mean higher energy; this means making sure that the value + * in HD_MIN_ENERGY_CCK_DET_INDEX is at or *above* "Max cck energy". + * + */ + +/* + * Table entries in SENSITIVITY_CMD (struct iwl_sensitivity_cmd) + */ +#define HD_TABLE_SIZE (11) /* number of entries */ +#define HD_MIN_ENERGY_CCK_DET_INDEX (0) /* table indexes */ +#define HD_MIN_ENERGY_OFDM_DET_INDEX (1) +#define HD_AUTO_CORR32_X1_TH_ADD_MIN_INDEX (2) +#define HD_AUTO_CORR32_X1_TH_ADD_MIN_MRC_INDEX (3) +#define HD_AUTO_CORR40_X4_TH_ADD_MIN_MRC_INDEX (4) +#define HD_AUTO_CORR32_X4_TH_ADD_MIN_INDEX (5) +#define HD_AUTO_CORR32_X4_TH_ADD_MIN_MRC_INDEX (6) +#define HD_BARKER_CORR_TH_ADD_MIN_INDEX (7) +#define HD_BARKER_CORR_TH_ADD_MIN_MRC_INDEX (8) +#define HD_AUTO_CORR40_X4_TH_ADD_MIN_INDEX (9) +#define HD_OFDM_ENERGY_TH_IN_INDEX (10) + +/* + * Additional table entries in enhance SENSITIVITY_CMD + */ +#define HD_INA_NON_SQUARE_DET_OFDM_INDEX (11) +#define HD_INA_NON_SQUARE_DET_CCK_INDEX (12) +#define HD_CORR_11_INSTEAD_OF_CORR_9_EN_INDEX (13) +#define HD_OFDM_NON_SQUARE_DET_SLOPE_MRC_INDEX (14) +#define HD_OFDM_NON_SQUARE_DET_INTERCEPT_MRC_INDEX (15) +#define HD_OFDM_NON_SQUARE_DET_SLOPE_INDEX (16) +#define HD_OFDM_NON_SQUARE_DET_INTERCEPT_INDEX (17) +#define HD_CCK_NON_SQUARE_DET_SLOPE_MRC_INDEX (18) +#define HD_CCK_NON_SQUARE_DET_INTERCEPT_MRC_INDEX (19) +#define HD_CCK_NON_SQUARE_DET_SLOPE_INDEX (20) +#define HD_CCK_NON_SQUARE_DET_INTERCEPT_INDEX (21) +#define HD_RESERVED (22) + +/* number of entries for enhanced tbl */ +#define ENHANCE_HD_TABLE_SIZE (23) + +/* number of additional entries for enhanced tbl */ +#define ENHANCE_HD_TABLE_ENTRIES (ENHANCE_HD_TABLE_SIZE - HD_TABLE_SIZE) + +#define HD_INA_NON_SQUARE_DET_OFDM_DATA_V1 cpu_to_le16(0) +#define HD_INA_NON_SQUARE_DET_CCK_DATA_V1 cpu_to_le16(0) +#define HD_CORR_11_INSTEAD_OF_CORR_9_EN_DATA_V1 cpu_to_le16(0) +#define HD_OFDM_NON_SQUARE_DET_SLOPE_MRC_DATA_V1 cpu_to_le16(668) +#define HD_OFDM_NON_SQUARE_DET_INTERCEPT_MRC_DATA_V1 cpu_to_le16(4) +#define HD_OFDM_NON_SQUARE_DET_SLOPE_DATA_V1 cpu_to_le16(486) +#define HD_OFDM_NON_SQUARE_DET_INTERCEPT_DATA_V1 cpu_to_le16(37) +#define HD_CCK_NON_SQUARE_DET_SLOPE_MRC_DATA_V1 cpu_to_le16(853) +#define HD_CCK_NON_SQUARE_DET_INTERCEPT_MRC_DATA_V1 cpu_to_le16(4) +#define HD_CCK_NON_SQUARE_DET_SLOPE_DATA_V1 cpu_to_le16(476) +#define HD_CCK_NON_SQUARE_DET_INTERCEPT_DATA_V1 cpu_to_le16(99) + +#define HD_INA_NON_SQUARE_DET_OFDM_DATA_V2 cpu_to_le16(1) +#define HD_INA_NON_SQUARE_DET_CCK_DATA_V2 cpu_to_le16(1) +#define HD_CORR_11_INSTEAD_OF_CORR_9_EN_DATA_V2 cpu_to_le16(1) +#define HD_OFDM_NON_SQUARE_DET_SLOPE_MRC_DATA_V2 cpu_to_le16(600) +#define HD_OFDM_NON_SQUARE_DET_INTERCEPT_MRC_DATA_V2 cpu_to_le16(40) +#define HD_OFDM_NON_SQUARE_DET_SLOPE_DATA_V2 cpu_to_le16(486) +#define HD_OFDM_NON_SQUARE_DET_INTERCEPT_DATA_V2 cpu_to_le16(45) +#define HD_CCK_NON_SQUARE_DET_SLOPE_MRC_DATA_V2 cpu_to_le16(853) +#define HD_CCK_NON_SQUARE_DET_INTERCEPT_MRC_DATA_V2 cpu_to_le16(60) +#define HD_CCK_NON_SQUARE_DET_SLOPE_DATA_V2 cpu_to_le16(476) +#define HD_CCK_NON_SQUARE_DET_INTERCEPT_DATA_V2 cpu_to_le16(99) + + +/* Control field in struct iwl_sensitivity_cmd */ +#define SENSITIVITY_CMD_CONTROL_DEFAULT_TABLE cpu_to_le16(0) +#define SENSITIVITY_CMD_CONTROL_WORK_TABLE cpu_to_le16(1) + +/** + * struct iwl_sensitivity_cmd + * @control: (1) updates working table, (0) updates default table + * @table: energy threshold values, use HD_* as index into table + * + * Always use "1" in "control" to update uCode's working table and DSP. + */ +struct iwl_sensitivity_cmd { + __le16 control; /* always use "1" */ + __le16 table[HD_TABLE_SIZE]; /* use HD_* as index */ +} __packed; + +/* + * + */ +struct iwl_enhance_sensitivity_cmd { + __le16 control; /* always use "1" */ + __le16 enhance_table[ENHANCE_HD_TABLE_SIZE]; /* use HD_* as index */ +} __packed; + + +/** + * REPLY_PHY_CALIBRATION_CMD = 0xb0 (command, has simple generic response) + * + * This command sets the relative gains of agn device's 3 radio receiver chains. + * + * After the first association, driver should accumulate signal and noise + * statistics from the STATISTICS_NOTIFICATIONs that follow the first 20 + * beacons from the associated network (don't collect statistics that come + * in from scanning, or any other non-network source). + * + * DISCONNECTED ANTENNA: + * + * Driver should determine which antennas are actually connected, by comparing + * average beacon signal levels for the 3 Rx chains. Accumulate (add) the + * following values over 20 beacons, one accumulator for each of the chains + * a/b/c, from struct statistics_rx_non_phy: + * + * beacon_rssi_[abc] & 0x0FF (unsigned, units in dB) + * + * Find the strongest signal from among a/b/c. Compare the other two to the + * strongest. If any signal is more than 15 dB (times 20, unless you + * divide the accumulated values by 20) below the strongest, the driver + * considers that antenna to be disconnected, and should not try to use that + * antenna/chain for Rx or Tx. If both A and B seem to be disconnected, + * driver should declare the stronger one as connected, and attempt to use it + * (A and B are the only 2 Tx chains!). + * + * + * RX BALANCE: + * + * Driver should balance the 3 receivers (but just the ones that are connected + * to antennas, see above) for gain, by comparing the average signal levels + * detected during the silence after each beacon (background noise). + * Accumulate (add) the following values over 20 beacons, one accumulator for + * each of the chains a/b/c, from struct statistics_rx_non_phy: + * + * beacon_silence_rssi_[abc] & 0x0FF (unsigned, units in dB) + * + * Find the weakest background noise level from among a/b/c. This Rx chain + * will be the reference, with 0 gain adjustment. Attenuate other channels by + * finding noise difference: + * + * (accum_noise[i] - accum_noise[reference]) / 30 + * + * The "30" adjusts the dB in the 20 accumulated samples to units of 1.5 dB. + * For use in diff_gain_[abc] fields of struct iwl_calibration_cmd, the + * driver should limit the difference results to a range of 0-3 (0-4.5 dB), + * and set bit 2 to indicate "reduce gain". The value for the reference + * (weakest) chain should be "0". + * + * diff_gain_[abc] bit fields: + * 2: (1) reduce gain, (0) increase gain + * 1-0: amount of gain, units of 1.5 dB + */ + +/* Phy calibration command for series */ +enum { + IWL_PHY_CALIBRATE_DC_CMD = 8, + IWL_PHY_CALIBRATE_LO_CMD = 9, + IWL_PHY_CALIBRATE_TX_IQ_CMD = 11, + IWL_PHY_CALIBRATE_CRYSTAL_FRQ_CMD = 15, + IWL_PHY_CALIBRATE_BASE_BAND_CMD = 16, + IWL_PHY_CALIBRATE_TX_IQ_PERD_CMD = 17, + IWL_PHY_CALIBRATE_TEMP_OFFSET_CMD = 18, +}; + +/* This enum defines the bitmap of various calibrations to enable in both + * init ucode and runtime ucode through CALIBRATION_CFG_CMD. + */ +enum iwl_ucode_calib_cfg { + IWL_CALIB_CFG_RX_BB_IDX = BIT(0), + IWL_CALIB_CFG_DC_IDX = BIT(1), + IWL_CALIB_CFG_LO_IDX = BIT(2), + IWL_CALIB_CFG_TX_IQ_IDX = BIT(3), + IWL_CALIB_CFG_RX_IQ_IDX = BIT(4), + IWL_CALIB_CFG_NOISE_IDX = BIT(5), + IWL_CALIB_CFG_CRYSTAL_IDX = BIT(6), + IWL_CALIB_CFG_TEMPERATURE_IDX = BIT(7), + IWL_CALIB_CFG_PAPD_IDX = BIT(8), + IWL_CALIB_CFG_SENSITIVITY_IDX = BIT(9), + IWL_CALIB_CFG_TX_PWR_IDX = BIT(10), +}; + +#define IWL_CALIB_INIT_CFG_ALL cpu_to_le32(IWL_CALIB_CFG_RX_BB_IDX | \ + IWL_CALIB_CFG_DC_IDX | \ + IWL_CALIB_CFG_LO_IDX | \ + IWL_CALIB_CFG_TX_IQ_IDX | \ + IWL_CALIB_CFG_RX_IQ_IDX | \ + IWL_CALIB_CFG_CRYSTAL_IDX) + +#define IWL_CALIB_RT_CFG_ALL cpu_to_le32(IWL_CALIB_CFG_RX_BB_IDX | \ + IWL_CALIB_CFG_DC_IDX | \ + IWL_CALIB_CFG_LO_IDX | \ + IWL_CALIB_CFG_TX_IQ_IDX | \ + IWL_CALIB_CFG_RX_IQ_IDX | \ + IWL_CALIB_CFG_TEMPERATURE_IDX | \ + IWL_CALIB_CFG_PAPD_IDX | \ + IWL_CALIB_CFG_TX_PWR_IDX | \ + IWL_CALIB_CFG_CRYSTAL_IDX) + +#define IWL_CALIB_CFG_FLAG_SEND_COMPLETE_NTFY_MSK cpu_to_le32(BIT(0)) + +struct iwl_calib_cfg_elmnt_s { + __le32 is_enable; + __le32 start; + __le32 send_res; + __le32 apply_res; + __le32 reserved; +} __packed; + +struct iwl_calib_cfg_status_s { + struct iwl_calib_cfg_elmnt_s once; + struct iwl_calib_cfg_elmnt_s perd; + __le32 flags; +} __packed; + +struct iwl_calib_cfg_cmd { + struct iwl_calib_cfg_status_s ucd_calib_cfg; + struct iwl_calib_cfg_status_s drv_calib_cfg; + __le32 reserved1; +} __packed; + +struct iwl_calib_hdr { + u8 op_code; + u8 first_group; + u8 groups_num; + u8 data_valid; +} __packed; + +struct iwl_calib_cmd { + struct iwl_calib_hdr hdr; + u8 data[0]; +} __packed; + +struct iwl_calib_xtal_freq_cmd { + struct iwl_calib_hdr hdr; + u8 cap_pin1; + u8 cap_pin2; + u8 pad[2]; +} __packed; + +#define DEFAULT_RADIO_SENSOR_OFFSET cpu_to_le16(2700) +struct iwl_calib_temperature_offset_cmd { + struct iwl_calib_hdr hdr; + __le16 radio_sensor_offset; + __le16 reserved; +} __packed; + +struct iwl_calib_temperature_offset_v2_cmd { + struct iwl_calib_hdr hdr; + __le16 radio_sensor_offset_high; + __le16 radio_sensor_offset_low; + __le16 burntVoltageRef; + __le16 reserved; +} __packed; + +/* IWL_PHY_CALIBRATE_CHAIN_NOISE_RESET_CMD */ +struct iwl_calib_chain_noise_reset_cmd { + struct iwl_calib_hdr hdr; + u8 data[0]; +}; + +/* IWL_PHY_CALIBRATE_CHAIN_NOISE_GAIN_CMD */ +struct iwl_calib_chain_noise_gain_cmd { + struct iwl_calib_hdr hdr; + u8 delta_gain_1; + u8 delta_gain_2; + u8 pad[2]; +} __packed; + +/****************************************************************************** + * (12) + * Miscellaneous Commands: + * + *****************************************************************************/ + +/* + * LEDs Command & Response + * REPLY_LEDS_CMD = 0x48 (command, has simple generic response) + * + * For each of 3 possible LEDs (Activity/Link/Tech, selected by "id" field), + * this command turns it on or off, or sets up a periodic blinking cycle. + */ +struct iwl_led_cmd { + __le32 interval; /* "interval" in uSec */ + u8 id; /* 1: Activity, 2: Link, 3: Tech */ + u8 off; /* # intervals off while blinking; + * "0", with >0 "on" value, turns LED on */ + u8 on; /* # intervals on while blinking; + * "0", regardless of "off", turns LED off */ + u8 reserved; +} __packed; + +/* + * station priority table entries + * also used as potential "events" value for both + * COEX_MEDIUM_NOTIFICATION and COEX_EVENT_CMD + */ + +/* + * COEX events entry flag masks + * RP - Requested Priority + * WP - Win Medium Priority: priority assigned when the contention has been won + */ +#define COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG (0x1) +#define COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG (0x2) +#define COEX_EVT_FLAG_DELAY_MEDIUM_FREE_NTFY_FLG (0x4) + +#define COEX_CU_UNASSOC_IDLE_RP 4 +#define COEX_CU_UNASSOC_MANUAL_SCAN_RP 4 +#define COEX_CU_UNASSOC_AUTO_SCAN_RP 4 +#define COEX_CU_CALIBRATION_RP 4 +#define COEX_CU_PERIODIC_CALIBRATION_RP 4 +#define COEX_CU_CONNECTION_ESTAB_RP 4 +#define COEX_CU_ASSOCIATED_IDLE_RP 4 +#define COEX_CU_ASSOC_MANUAL_SCAN_RP 4 +#define COEX_CU_ASSOC_AUTO_SCAN_RP 4 +#define COEX_CU_ASSOC_ACTIVE_LEVEL_RP 4 +#define COEX_CU_RF_ON_RP 6 +#define COEX_CU_RF_OFF_RP 4 +#define COEX_CU_STAND_ALONE_DEBUG_RP 6 +#define COEX_CU_IPAN_ASSOC_LEVEL_RP 4 +#define COEX_CU_RSRVD1_RP 4 +#define COEX_CU_RSRVD2_RP 4 + +#define COEX_CU_UNASSOC_IDLE_WP 3 +#define COEX_CU_UNASSOC_MANUAL_SCAN_WP 3 +#define COEX_CU_UNASSOC_AUTO_SCAN_WP 3 +#define COEX_CU_CALIBRATION_WP 3 +#define COEX_CU_PERIODIC_CALIBRATION_WP 3 +#define COEX_CU_CONNECTION_ESTAB_WP 3 +#define COEX_CU_ASSOCIATED_IDLE_WP 3 +#define COEX_CU_ASSOC_MANUAL_SCAN_WP 3 +#define COEX_CU_ASSOC_AUTO_SCAN_WP 3 +#define COEX_CU_ASSOC_ACTIVE_LEVEL_WP 3 +#define COEX_CU_RF_ON_WP 3 +#define COEX_CU_RF_OFF_WP 3 +#define COEX_CU_STAND_ALONE_DEBUG_WP 6 +#define COEX_CU_IPAN_ASSOC_LEVEL_WP 3 +#define COEX_CU_RSRVD1_WP 3 +#define COEX_CU_RSRVD2_WP 3 + +#define COEX_UNASSOC_IDLE_FLAGS 0 +#define COEX_UNASSOC_MANUAL_SCAN_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG) +#define COEX_UNASSOC_AUTO_SCAN_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG) +#define COEX_CALIBRATION_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG) +#define COEX_PERIODIC_CALIBRATION_FLAGS 0 +/* + * COEX_CONNECTION_ESTAB: + * we need DELAY_MEDIUM_FREE_NTFY to let WiMAX disconnect from network. + */ +#define COEX_CONNECTION_ESTAB_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG | \ + COEX_EVT_FLAG_DELAY_MEDIUM_FREE_NTFY_FLG) +#define COEX_ASSOCIATED_IDLE_FLAGS 0 +#define COEX_ASSOC_MANUAL_SCAN_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG) +#define COEX_ASSOC_AUTO_SCAN_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG) +#define COEX_ASSOC_ACTIVE_LEVEL_FLAGS 0 +#define COEX_RF_ON_FLAGS 0 +#define COEX_RF_OFF_FLAGS 0 +#define COEX_STAND_ALONE_DEBUG_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG) +#define COEX_IPAN_ASSOC_LEVEL_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG | \ + COEX_EVT_FLAG_DELAY_MEDIUM_FREE_NTFY_FLG) +#define COEX_RSRVD1_FLAGS 0 +#define COEX_RSRVD2_FLAGS 0 +/* + * COEX_CU_RF_ON is the event wrapping all radio ownership. + * We need DELAY_MEDIUM_FREE_NTFY to let WiMAX disconnect from network. + */ +#define COEX_CU_RF_ON_FLAGS \ + (COEX_EVT_FLAG_MEDIUM_FREE_NTFY_FLG | \ + COEX_EVT_FLAG_MEDIUM_ACTV_NTFY_FLG | \ + COEX_EVT_FLAG_DELAY_MEDIUM_FREE_NTFY_FLG) + + +enum { + /* un-association part */ + COEX_UNASSOC_IDLE = 0, + COEX_UNASSOC_MANUAL_SCAN = 1, + COEX_UNASSOC_AUTO_SCAN = 2, + /* calibration */ + COEX_CALIBRATION = 3, + COEX_PERIODIC_CALIBRATION = 4, + /* connection */ + COEX_CONNECTION_ESTAB = 5, + /* association part */ + COEX_ASSOCIATED_IDLE = 6, + COEX_ASSOC_MANUAL_SCAN = 7, + COEX_ASSOC_AUTO_SCAN = 8, + COEX_ASSOC_ACTIVE_LEVEL = 9, + /* RF ON/OFF */ + COEX_RF_ON = 10, + COEX_RF_OFF = 11, + COEX_STAND_ALONE_DEBUG = 12, + /* IPAN */ + COEX_IPAN_ASSOC_LEVEL = 13, + /* reserved */ + COEX_RSRVD1 = 14, + COEX_RSRVD2 = 15, + COEX_NUM_OF_EVENTS = 16 +}; + +/* + * Coexistence WIFI/WIMAX Command + * COEX_PRIORITY_TABLE_CMD = 0x5a + * + */ +struct iwl_wimax_coex_event_entry { + u8 request_prio; + u8 win_medium_prio; + u8 reserved; + u8 flags; +} __packed; + +/* COEX flag masks */ + +/* Station table is valid */ +#define COEX_FLAGS_STA_TABLE_VALID_MSK (0x1) +/* UnMask wake up src at unassociated sleep */ +#define COEX_FLAGS_UNASSOC_WA_UNMASK_MSK (0x4) +/* UnMask wake up src at associated sleep */ +#define COEX_FLAGS_ASSOC_WA_UNMASK_MSK (0x8) +/* Enable CoEx feature. */ +#define COEX_FLAGS_COEX_ENABLE_MSK (0x80) + +struct iwl_wimax_coex_cmd { + u8 flags; + u8 reserved[3]; + struct iwl_wimax_coex_event_entry sta_prio[COEX_NUM_OF_EVENTS]; +} __packed; + +/* + * Coexistence MEDIUM NOTIFICATION + * COEX_MEDIUM_NOTIFICATION = 0x5b + * + * notification from uCode to host to indicate medium changes + * + */ +/* + * status field + * bit 0 - 2: medium status + * bit 3: medium change indication + * bit 4 - 31: reserved + */ +/* status option values, (0 - 2 bits) */ +#define COEX_MEDIUM_BUSY (0x0) /* radio belongs to WiMAX */ +#define COEX_MEDIUM_ACTIVE (0x1) /* radio belongs to WiFi */ +#define COEX_MEDIUM_PRE_RELEASE (0x2) /* received radio release */ +#define COEX_MEDIUM_MSK (0x7) + +/* send notification status (1 bit) */ +#define COEX_MEDIUM_CHANGED (0x8) +#define COEX_MEDIUM_CHANGED_MSK (0x8) +#define COEX_MEDIUM_SHIFT (3) + +struct iwl_coex_medium_notification { + __le32 status; + __le32 events; +} __packed; + +/* + * Coexistence EVENT Command + * COEX_EVENT_CMD = 0x5c + * + * send from host to uCode for coex event request. + */ +/* flags options */ +#define COEX_EVENT_REQUEST_MSK (0x1) + +struct iwl_coex_event_cmd { + u8 flags; + u8 event; + __le16 reserved; +} __packed; + +struct iwl_coex_event_resp { + __le32 status; +} __packed; + + +/****************************************************************************** + * Bluetooth Coexistence commands + * + *****************************************************************************/ + +/* + * BT Status notification + * REPLY_BT_COEX_PROFILE_NOTIF = 0xce + */ +enum iwl_bt_coex_profile_traffic_load { + IWL_BT_COEX_TRAFFIC_LOAD_NONE = 0, + IWL_BT_COEX_TRAFFIC_LOAD_LOW = 1, + IWL_BT_COEX_TRAFFIC_LOAD_HIGH = 2, + IWL_BT_COEX_TRAFFIC_LOAD_CONTINUOUS = 3, +/* + * There are no more even though below is a u8, the + * indication from the BT device only has two bits. + */ +}; + +#define BT_SESSION_ACTIVITY_1_UART_MSG 0x1 +#define BT_SESSION_ACTIVITY_2_UART_MSG 0x2 + +/* BT UART message - Share Part (BT -> WiFi) */ +#define BT_UART_MSG_FRAME1MSGTYPE_POS (0) +#define BT_UART_MSG_FRAME1MSGTYPE_MSK \ + (0x7 << BT_UART_MSG_FRAME1MSGTYPE_POS) +#define BT_UART_MSG_FRAME1SSN_POS (3) +#define BT_UART_MSG_FRAME1SSN_MSK \ + (0x3 << BT_UART_MSG_FRAME1SSN_POS) +#define BT_UART_MSG_FRAME1UPDATEREQ_POS (5) +#define BT_UART_MSG_FRAME1UPDATEREQ_MSK \ + (0x1 << BT_UART_MSG_FRAME1UPDATEREQ_POS) +#define BT_UART_MSG_FRAME1RESERVED_POS (6) +#define BT_UART_MSG_FRAME1RESERVED_MSK \ + (0x3 << BT_UART_MSG_FRAME1RESERVED_POS) + +#define BT_UART_MSG_FRAME2OPENCONNECTIONS_POS (0) +#define BT_UART_MSG_FRAME2OPENCONNECTIONS_MSK \ + (0x3 << BT_UART_MSG_FRAME2OPENCONNECTIONS_POS) +#define BT_UART_MSG_FRAME2TRAFFICLOAD_POS (2) +#define BT_UART_MSG_FRAME2TRAFFICLOAD_MSK \ + (0x3 << BT_UART_MSG_FRAME2TRAFFICLOAD_POS) +#define BT_UART_MSG_FRAME2CHLSEQN_POS (4) +#define BT_UART_MSG_FRAME2CHLSEQN_MSK \ + (0x1 << BT_UART_MSG_FRAME2CHLSEQN_POS) +#define BT_UART_MSG_FRAME2INBAND_POS (5) +#define BT_UART_MSG_FRAME2INBAND_MSK \ + (0x1 << BT_UART_MSG_FRAME2INBAND_POS) +#define BT_UART_MSG_FRAME2RESERVED_POS (6) +#define BT_UART_MSG_FRAME2RESERVED_MSK \ + (0x3 << BT_UART_MSG_FRAME2RESERVED_POS) + +#define BT_UART_MSG_FRAME3SCOESCO_POS (0) +#define BT_UART_MSG_FRAME3SCOESCO_MSK \ + (0x1 << BT_UART_MSG_FRAME3SCOESCO_POS) +#define BT_UART_MSG_FRAME3SNIFF_POS (1) +#define BT_UART_MSG_FRAME3SNIFF_MSK \ + (0x1 << BT_UART_MSG_FRAME3SNIFF_POS) +#define BT_UART_MSG_FRAME3A2DP_POS (2) +#define BT_UART_MSG_FRAME3A2DP_MSK \ + (0x1 << BT_UART_MSG_FRAME3A2DP_POS) +#define BT_UART_MSG_FRAME3ACL_POS (3) +#define BT_UART_MSG_FRAME3ACL_MSK \ + (0x1 << BT_UART_MSG_FRAME3ACL_POS) +#define BT_UART_MSG_FRAME3MASTER_POS (4) +#define BT_UART_MSG_FRAME3MASTER_MSK \ + (0x1 << BT_UART_MSG_FRAME3MASTER_POS) +#define BT_UART_MSG_FRAME3OBEX_POS (5) +#define BT_UART_MSG_FRAME3OBEX_MSK \ + (0x1 << BT_UART_MSG_FRAME3OBEX_POS) +#define BT_UART_MSG_FRAME3RESERVED_POS (6) +#define BT_UART_MSG_FRAME3RESERVED_MSK \ + (0x3 << BT_UART_MSG_FRAME3RESERVED_POS) + +#define BT_UART_MSG_FRAME4IDLEDURATION_POS (0) +#define BT_UART_MSG_FRAME4IDLEDURATION_MSK \ + (0x3F << BT_UART_MSG_FRAME4IDLEDURATION_POS) +#define BT_UART_MSG_FRAME4RESERVED_POS (6) +#define BT_UART_MSG_FRAME4RESERVED_MSK \ + (0x3 << BT_UART_MSG_FRAME4RESERVED_POS) + +#define BT_UART_MSG_FRAME5TXACTIVITY_POS (0) +#define BT_UART_MSG_FRAME5TXACTIVITY_MSK \ + (0x3 << BT_UART_MSG_FRAME5TXACTIVITY_POS) +#define BT_UART_MSG_FRAME5RXACTIVITY_POS (2) +#define BT_UART_MSG_FRAME5RXACTIVITY_MSK \ + (0x3 << BT_UART_MSG_FRAME5RXACTIVITY_POS) +#define BT_UART_MSG_FRAME5ESCORETRANSMIT_POS (4) +#define BT_UART_MSG_FRAME5ESCORETRANSMIT_MSK \ + (0x3 << BT_UART_MSG_FRAME5ESCORETRANSMIT_POS) +#define BT_UART_MSG_FRAME5RESERVED_POS (6) +#define BT_UART_MSG_FRAME5RESERVED_MSK \ + (0x3 << BT_UART_MSG_FRAME5RESERVED_POS) + +#define BT_UART_MSG_FRAME6SNIFFINTERVAL_POS (0) +#define BT_UART_MSG_FRAME6SNIFFINTERVAL_MSK \ + (0x1F << BT_UART_MSG_FRAME6SNIFFINTERVAL_POS) +#define BT_UART_MSG_FRAME6DISCOVERABLE_POS (5) +#define BT_UART_MSG_FRAME6DISCOVERABLE_MSK \ + (0x1 << BT_UART_MSG_FRAME6DISCOVERABLE_POS) +#define BT_UART_MSG_FRAME6RESERVED_POS (6) +#define BT_UART_MSG_FRAME6RESERVED_MSK \ + (0x3 << BT_UART_MSG_FRAME6RESERVED_POS) + +#define BT_UART_MSG_FRAME7SNIFFACTIVITY_POS (0) +#define BT_UART_MSG_FRAME7SNIFFACTIVITY_MSK \ + (0x7 << BT_UART_MSG_FRAME7SNIFFACTIVITY_POS) +#define BT_UART_MSG_FRAME7PAGE_POS (3) +#define BT_UART_MSG_FRAME7PAGE_MSK \ + (0x1 << BT_UART_MSG_FRAME7PAGE_POS) +#define BT_UART_MSG_FRAME7INQUIRY_POS (4) +#define BT_UART_MSG_FRAME7INQUIRY_MSK \ + (0x1 << BT_UART_MSG_FRAME7INQUIRY_POS) +#define BT_UART_MSG_FRAME7CONNECTABLE_POS (5) +#define BT_UART_MSG_FRAME7CONNECTABLE_MSK \ + (0x1 << BT_UART_MSG_FRAME7CONNECTABLE_POS) +#define BT_UART_MSG_FRAME7RESERVED_POS (6) +#define BT_UART_MSG_FRAME7RESERVED_MSK \ + (0x3 << BT_UART_MSG_FRAME7RESERVED_POS) + +/* BT Session Activity 2 UART message (BT -> WiFi) */ +#define BT_UART_MSG_2_FRAME1RESERVED1_POS (5) +#define BT_UART_MSG_2_FRAME1RESERVED1_MSK \ + (0x1< + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "iwl-debug.h" +#include "iwl-io.h" +#include "dev.h" +#include "agn.h" + +/* create and remove of files */ +#define DEBUGFS_ADD_FILE(name, parent, mode) do { \ + if (!debugfs_create_file(#name, mode, parent, priv, \ + &iwl_dbgfs_##name##_ops)) \ + goto err; \ +} while (0) + +#define DEBUGFS_ADD_BOOL(name, parent, ptr) do { \ + struct dentry *__tmp; \ + __tmp = debugfs_create_bool(#name, S_IWUSR | S_IRUSR, \ + parent, ptr); \ + if (IS_ERR(__tmp) || !__tmp) \ + goto err; \ +} while (0) + +#define DEBUGFS_ADD_X32(name, parent, ptr) do { \ + struct dentry *__tmp; \ + __tmp = debugfs_create_x32(#name, S_IWUSR | S_IRUSR, \ + parent, ptr); \ + if (IS_ERR(__tmp) || !__tmp) \ + goto err; \ +} while (0) + +#define DEBUGFS_ADD_U32(name, parent, ptr, mode) do { \ + struct dentry *__tmp; \ + __tmp = debugfs_create_u32(#name, mode, \ + parent, ptr); \ + if (IS_ERR(__tmp) || !__tmp) \ + goto err; \ +} while (0) + +/* file operation */ +#define DEBUGFS_READ_FUNC(name) \ +static ssize_t iwl_dbgfs_##name##_read(struct file *file, \ + char __user *user_buf, \ + size_t count, loff_t *ppos); + +#define DEBUGFS_WRITE_FUNC(name) \ +static ssize_t iwl_dbgfs_##name##_write(struct file *file, \ + const char __user *user_buf, \ + size_t count, loff_t *ppos); + + +#define DEBUGFS_READ_FILE_OPS(name) \ + DEBUGFS_READ_FUNC(name); \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .read = iwl_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_WRITE_FILE_OPS(name) \ + DEBUGFS_WRITE_FUNC(name); \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .write = iwl_dbgfs_##name##_write, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + + +#define DEBUGFS_READ_WRITE_FILE_OPS(name) \ + DEBUGFS_READ_FUNC(name); \ + DEBUGFS_WRITE_FUNC(name); \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .write = iwl_dbgfs_##name##_write, \ + .read = iwl_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +static ssize_t iwl_dbgfs_sram_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + u32 val = 0; + char *buf; + ssize_t ret; + int i = 0; + bool device_format = false; + int offset = 0; + int len = 0; + int pos = 0; + int sram; + struct iwl_priv *priv = file->private_data; + const struct fw_img *img; + size_t bufsz; + + if (!iwl_is_ready_rf(priv)) + return -EAGAIN; + + /* default is to dump the entire data segment */ + if (!priv->dbgfs_sram_offset && !priv->dbgfs_sram_len) { + priv->dbgfs_sram_offset = 0x800000; + if (!priv->ucode_loaded) + return -EINVAL; + img = &priv->fw->img[priv->cur_ucode]; + priv->dbgfs_sram_len = img->sec[IWL_UCODE_SECTION_DATA].len; + } + len = priv->dbgfs_sram_len; + + if (len == -4) { + device_format = true; + len = 4; + } + + bufsz = 50 + len * 4; + buf = kmalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, "sram_len: 0x%x\n", + len); + pos += scnprintf(buf + pos, bufsz - pos, "sram_offset: 0x%x\n", + priv->dbgfs_sram_offset); + + /* adjust sram address since reads are only on even u32 boundaries */ + offset = priv->dbgfs_sram_offset & 0x3; + sram = priv->dbgfs_sram_offset & ~0x3; + + /* read the first u32 from sram */ + val = iwl_trans_read_mem32(priv->trans, sram); + + for (; len; len--) { + /* put the address at the start of every line */ + if (i == 0) + pos += scnprintf(buf + pos, bufsz - pos, + "%08X: ", sram + offset); + + if (device_format) + pos += scnprintf(buf + pos, bufsz - pos, + "%02x", (val >> (8 * (3 - offset))) & 0xff); + else + pos += scnprintf(buf + pos, bufsz - pos, + "%02x ", (val >> (8 * offset)) & 0xff); + + /* if all bytes processed, read the next u32 from sram */ + if (++offset == 4) { + sram += 4; + offset = 0; + val = iwl_trans_read_mem32(priv->trans, sram); + } + + /* put in extra spaces and split lines for human readability */ + if (++i == 16) { + i = 0; + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + } else if (!(i & 7)) { + pos += scnprintf(buf + pos, bufsz - pos, " "); + } else if (!(i & 3)) { + pos += scnprintf(buf + pos, bufsz - pos, " "); + } + } + if (i) + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_sram_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[64]; + int buf_size; + u32 offset, len; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (sscanf(buf, "%x,%x", &offset, &len) == 2) { + priv->dbgfs_sram_offset = offset; + priv->dbgfs_sram_len = len; + } else if (sscanf(buf, "%x", &offset) == 1) { + priv->dbgfs_sram_offset = offset; + priv->dbgfs_sram_len = -4; + } else { + priv->dbgfs_sram_offset = 0; + priv->dbgfs_sram_len = 0; + } + + return count; +} + +static ssize_t iwl_dbgfs_wowlan_sram_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + const struct fw_img *img = &priv->fw->img[IWL_UCODE_WOWLAN]; + + if (!priv->wowlan_sram) + return -ENODATA; + + return simple_read_from_buffer(user_buf, count, ppos, + priv->wowlan_sram, + img->sec[IWL_UCODE_SECTION_DATA].len); +} +static ssize_t iwl_dbgfs_stations_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + struct iwl_station_entry *station; + struct iwl_tid_data *tid_data; + char *buf; + int i, j, pos = 0; + ssize_t ret; + /* Add 30 for initial string */ + const size_t bufsz = 30 + sizeof(char) * 500 * (priv->num_stations); + + buf = kmalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, "num of stations: %d\n\n", + priv->num_stations); + + for (i = 0; i < IWLAGN_STATION_COUNT; i++) { + station = &priv->stations[i]; + if (!station->used) + continue; + pos += scnprintf(buf + pos, bufsz - pos, + "station %d - addr: %pM, flags: %#x\n", + i, station->sta.sta.addr, + station->sta.station_flags_msk); + pos += scnprintf(buf + pos, bufsz - pos, + "TID seqno next_rclmd " + "rate_n_flags state txq\n"); + + for (j = 0; j < IWL_MAX_TID_COUNT; j++) { + tid_data = &priv->tid_data[i][j]; + pos += scnprintf(buf + pos, bufsz - pos, + "%d: 0x%.4x 0x%.4x 0x%.8x " + "%d %.2d", + j, tid_data->seq_number, + tid_data->next_reclaimed, + tid_data->agg.rate_n_flags, + tid_data->agg.state, + tid_data->agg.txq_id); + + if (tid_data->agg.wait_for_ba) + pos += scnprintf(buf + pos, bufsz - pos, + " - waitforba"); + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + } + + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_nvm_read(struct file *file, + char __user *user_buf, + size_t count, + loff_t *ppos) +{ + ssize_t ret; + struct iwl_priv *priv = file->private_data; + int pos = 0, ofs = 0, buf_size = 0; + const u8 *ptr; + char *buf; + u16 nvm_ver; + size_t eeprom_len = priv->eeprom_blob_size; + buf_size = 4 * eeprom_len + 256; + + if (eeprom_len % 16) + return -ENODATA; + + ptr = priv->eeprom_blob; + if (!ptr) + return -ENOMEM; + + /* 4 characters for byte 0xYY */ + buf = kzalloc(buf_size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + nvm_ver = priv->nvm_data->nvm_version; + pos += scnprintf(buf + pos, buf_size - pos, + "NVM version: 0x%x\n", nvm_ver); + for (ofs = 0 ; ofs < eeprom_len ; ofs += 16) { + pos += scnprintf(buf + pos, buf_size - pos, "0x%.4x ", ofs); + hex_dump_to_buffer(ptr + ofs, 16 , 16, 2, buf + pos, + buf_size - pos, 0); + pos += strlen(buf + pos); + if (buf_size - pos > 0) + buf[pos++] = '\n'; + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_channels_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + struct ieee80211_channel *channels = NULL; + const struct ieee80211_supported_band *supp_band = NULL; + int pos = 0, i, bufsz = PAGE_SIZE; + char *buf; + ssize_t ret; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + supp_band = iwl_get_hw_mode(priv, IEEE80211_BAND_2GHZ); + if (supp_band) { + channels = supp_band->channels; + + pos += scnprintf(buf + pos, bufsz - pos, + "Displaying %d channels in 2.4GHz band 802.11bg):\n", + supp_band->n_channels); + + for (i = 0; i < supp_band->n_channels; i++) + pos += scnprintf(buf + pos, bufsz - pos, + "%d: %ddBm: BSS%s%s, %s.\n", + channels[i].hw_value, + channels[i].max_power, + channels[i].flags & IEEE80211_CHAN_RADAR ? + " (IEEE 802.11h required)" : "", + ((channels[i].flags & IEEE80211_CHAN_NO_IBSS) + || (channels[i].flags & + IEEE80211_CHAN_RADAR)) ? "" : + ", IBSS", + channels[i].flags & + IEEE80211_CHAN_PASSIVE_SCAN ? + "passive only" : "active/passive"); + } + supp_band = iwl_get_hw_mode(priv, IEEE80211_BAND_5GHZ); + if (supp_band) { + channels = supp_band->channels; + + pos += scnprintf(buf + pos, bufsz - pos, + "Displaying %d channels in 5.2GHz band (802.11a)\n", + supp_band->n_channels); + + for (i = 0; i < supp_band->n_channels; i++) + pos += scnprintf(buf + pos, bufsz - pos, + "%d: %ddBm: BSS%s%s, %s.\n", + channels[i].hw_value, + channels[i].max_power, + channels[i].flags & IEEE80211_CHAN_RADAR ? + " (IEEE 802.11h required)" : "", + ((channels[i].flags & IEEE80211_CHAN_NO_IBSS) + || (channels[i].flags & + IEEE80211_CHAN_RADAR)) ? "" : + ", IBSS", + channels[i].flags & + IEEE80211_CHAN_PASSIVE_SCAN ? + "passive only" : "active/passive"); + } + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_status_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + char buf[512]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_RF_KILL_HW:\t %d\n", + test_bit(STATUS_RF_KILL_HW, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_CT_KILL:\t\t %d\n", + test_bit(STATUS_CT_KILL, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_ALIVE:\t\t %d\n", + test_bit(STATUS_ALIVE, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_READY:\t\t %d\n", + test_bit(STATUS_READY, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_EXIT_PENDING:\t %d\n", + test_bit(STATUS_EXIT_PENDING, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_STATISTICS:\t %d\n", + test_bit(STATUS_STATISTICS, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_SCANNING:\t %d\n", + test_bit(STATUS_SCANNING, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_SCAN_ABORTING:\t %d\n", + test_bit(STATUS_SCAN_ABORTING, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_SCAN_HW:\t\t %d\n", + test_bit(STATUS_SCAN_HW, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_POWER_PMI:\t %d\n", + test_bit(STATUS_POWER_PMI, &priv->status)); + pos += scnprintf(buf + pos, bufsz - pos, "STATUS_FW_ERROR:\t %d\n", + test_bit(STATUS_FW_ERROR, &priv->status)); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_rx_handlers_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + + int pos = 0; + int cnt = 0; + char *buf; + int bufsz = 24 * 64; /* 24 items * 64 char per item */ + ssize_t ret; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (cnt = 0; cnt < REPLY_MAX; cnt++) { + if (priv->rx_handlers_stats[cnt] > 0) + pos += scnprintf(buf + pos, bufsz - pos, + "\tRx handler[%36s]:\t\t %u\n", + iwl_dvm_get_cmd_string(cnt), + priv->rx_handlers_stats[cnt]); + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_rx_handlers_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + + char buf[8]; + int buf_size; + u32 reset_flag; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%x", &reset_flag) != 1) + return -EFAULT; + if (reset_flag == 0) + memset(&priv->rx_handlers_stats[0], 0, + sizeof(priv->rx_handlers_stats)); + + return count; +} + +static ssize_t iwl_dbgfs_qos_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + struct iwl_rxon_context *ctx; + int pos = 0, i; + char buf[256 * NUM_IWL_RXON_CTX]; + const size_t bufsz = sizeof(buf); + + for_each_context(priv, ctx) { + pos += scnprintf(buf + pos, bufsz - pos, "context %d:\n", + ctx->ctxid); + for (i = 0; i < AC_NUM; i++) { + pos += scnprintf(buf + pos, bufsz - pos, + "\tcw_min\tcw_max\taifsn\ttxop\n"); + pos += scnprintf(buf + pos, bufsz - pos, + "AC[%d]\t%u\t%u\t%u\t%u\n", i, + ctx->qos_data.def_qos_parm.ac[i].cw_min, + ctx->qos_data.def_qos_parm.ac[i].cw_max, + ctx->qos_data.def_qos_parm.ac[i].aifsn, + ctx->qos_data.def_qos_parm.ac[i].edca_txop); + } + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + } + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_thermal_throttling_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + struct iwl_tt_restriction *restriction; + char buf[100]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, + "Thermal Throttling Mode: %s\n", + tt->advanced_tt ? "Advance" : "Legacy"); + pos += scnprintf(buf + pos, bufsz - pos, + "Thermal Throttling State: %d\n", + tt->state); + if (tt->advanced_tt) { + restriction = tt->restriction + tt->state; + pos += scnprintf(buf + pos, bufsz - pos, + "Tx mode: %d\n", + restriction->tx_stream); + pos += scnprintf(buf + pos, bufsz - pos, + "Rx mode: %d\n", + restriction->rx_stream); + pos += scnprintf(buf + pos, bufsz - pos, + "HT mode: %d\n", + restriction->is_ht); + } + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_disable_ht40_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + int ht40; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &ht40) != 1) + return -EFAULT; + if (!iwl_is_any_associated(priv)) + priv->disable_ht40 = ht40 ? true : false; + else + return -EINVAL; + + return count; +} + +static ssize_t iwl_dbgfs_disable_ht40_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[100]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, + "11n 40MHz Mode: %s\n", + priv->disable_ht40 ? "Disabled" : "Enabled"); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_temperature_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[8]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, "%d\n", priv->temperature); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + + +static ssize_t iwl_dbgfs_sleep_level_override_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + int value; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (sscanf(buf, "%d", &value) != 1) + return -EINVAL; + + /* + * Our users expect 0 to be "CAM", but 0 isn't actually + * valid here. However, let's not confuse them and present + * IWL_POWER_INDEX_1 as "1", not "0". + */ + if (value == 0) + return -EINVAL; + else if (value > 0) + value -= 1; + + if (value != -1 && (value < 0 || value >= IWL_POWER_NUM)) + return -EINVAL; + + if (!iwl_is_ready_rf(priv)) + return -EAGAIN; + + priv->power_data.debug_sleep_level_override = value; + + mutex_lock(&priv->mutex); + iwl_power_update_mode(priv, true); + mutex_unlock(&priv->mutex); + + return count; +} + +static ssize_t iwl_dbgfs_sleep_level_override_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[10]; + int pos, value; + const size_t bufsz = sizeof(buf); + + /* see the write function */ + value = priv->power_data.debug_sleep_level_override; + if (value >= 0) + value += 1; + + pos = scnprintf(buf, bufsz, "%d\n", value); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_current_sleep_command_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[200]; + int pos = 0, i; + const size_t bufsz = sizeof(buf); + struct iwl_powertable_cmd *cmd = &priv->power_data.sleep_cmd; + + pos += scnprintf(buf + pos, bufsz - pos, + "flags: %#.2x\n", le16_to_cpu(cmd->flags)); + pos += scnprintf(buf + pos, bufsz - pos, + "RX/TX timeout: %d/%d usec\n", + le32_to_cpu(cmd->rx_data_timeout), + le32_to_cpu(cmd->tx_data_timeout)); + for (i = 0; i < IWL_POWER_VEC_SIZE; i++) + pos += scnprintf(buf + pos, bufsz - pos, + "sleep_interval[%d]: %d\n", i, + le32_to_cpu(cmd->sleep_interval[i])); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +DEBUGFS_READ_WRITE_FILE_OPS(sram); +DEBUGFS_READ_FILE_OPS(wowlan_sram); +DEBUGFS_READ_FILE_OPS(nvm); +DEBUGFS_READ_FILE_OPS(stations); +DEBUGFS_READ_FILE_OPS(channels); +DEBUGFS_READ_FILE_OPS(status); +DEBUGFS_READ_WRITE_FILE_OPS(rx_handlers); +DEBUGFS_READ_FILE_OPS(qos); +DEBUGFS_READ_FILE_OPS(thermal_throttling); +DEBUGFS_READ_WRITE_FILE_OPS(disable_ht40); +DEBUGFS_READ_FILE_OPS(temperature); +DEBUGFS_READ_WRITE_FILE_OPS(sleep_level_override); +DEBUGFS_READ_FILE_OPS(current_sleep_command); + +static const char *fmt_value = " %-30s %10u\n"; +static const char *fmt_hex = " %-30s 0x%02X\n"; +static const char *fmt_table = " %-30s %10u %10u %10u %10u\n"; +static const char *fmt_header = + "%-32s current cumulative delta max\n"; + +static int iwl_statistics_flag(struct iwl_priv *priv, char *buf, int bufsz) +{ + int p = 0; + u32 flag; + + lockdep_assert_held(&priv->statistics.lock); + + flag = le32_to_cpu(priv->statistics.flag); + + p += scnprintf(buf + p, bufsz - p, "Statistics Flag(0x%X):\n", flag); + if (flag & UCODE_STATISTICS_CLEAR_MSK) + p += scnprintf(buf + p, bufsz - p, + "\tStatistics have been cleared\n"); + p += scnprintf(buf + p, bufsz - p, "\tOperational Frequency: %s\n", + (flag & UCODE_STATISTICS_FREQUENCY_MSK) + ? "2.4 GHz" : "5.2 GHz"); + p += scnprintf(buf + p, bufsz - p, "\tTGj Narrow Band: %s\n", + (flag & UCODE_STATISTICS_NARROW_BAND_MSK) + ? "enabled" : "disabled"); + + return p; +} + +static ssize_t iwl_dbgfs_ucode_rx_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + int pos = 0; + char *buf; + int bufsz = sizeof(struct statistics_rx_phy) * 40 + + sizeof(struct statistics_rx_non_phy) * 40 + + sizeof(struct statistics_rx_ht_phy) * 40 + 400; + ssize_t ret; + struct statistics_rx_phy *ofdm, *accum_ofdm, *delta_ofdm, *max_ofdm; + struct statistics_rx_phy *cck, *accum_cck, *delta_cck, *max_cck; + struct statistics_rx_non_phy *general, *accum_general; + struct statistics_rx_non_phy *delta_general, *max_general; + struct statistics_rx_ht_phy *ht, *accum_ht, *delta_ht, *max_ht; + + if (!iwl_is_alive(priv)) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* + * the statistic information display here is based on + * the last statistics notification from uCode + * might not reflect the current uCode activity + */ + spin_lock_bh(&priv->statistics.lock); + ofdm = &priv->statistics.rx_ofdm; + cck = &priv->statistics.rx_cck; + general = &priv->statistics.rx_non_phy; + ht = &priv->statistics.rx_ofdm_ht; + accum_ofdm = &priv->accum_stats.rx_ofdm; + accum_cck = &priv->accum_stats.rx_cck; + accum_general = &priv->accum_stats.rx_non_phy; + accum_ht = &priv->accum_stats.rx_ofdm_ht; + delta_ofdm = &priv->delta_stats.rx_ofdm; + delta_cck = &priv->delta_stats.rx_cck; + delta_general = &priv->delta_stats.rx_non_phy; + delta_ht = &priv->delta_stats.rx_ofdm_ht; + max_ofdm = &priv->max_delta_stats.rx_ofdm; + max_cck = &priv->max_delta_stats.rx_cck; + max_general = &priv->max_delta_stats.rx_non_phy; + max_ht = &priv->max_delta_stats.rx_ofdm_ht; + + pos += iwl_statistics_flag(priv, buf, bufsz); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_header, "Statistics_Rx - OFDM:"); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "ina_cnt:", + le32_to_cpu(ofdm->ina_cnt), + accum_ofdm->ina_cnt, + delta_ofdm->ina_cnt, max_ofdm->ina_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "fina_cnt:", + le32_to_cpu(ofdm->fina_cnt), accum_ofdm->fina_cnt, + delta_ofdm->fina_cnt, max_ofdm->fina_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "plcp_err:", + le32_to_cpu(ofdm->plcp_err), accum_ofdm->plcp_err, + delta_ofdm->plcp_err, max_ofdm->plcp_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "crc32_err:", + le32_to_cpu(ofdm->crc32_err), accum_ofdm->crc32_err, + delta_ofdm->crc32_err, max_ofdm->crc32_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "overrun_err:", + le32_to_cpu(ofdm->overrun_err), + accum_ofdm->overrun_err, delta_ofdm->overrun_err, + max_ofdm->overrun_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "early_overrun_err:", + le32_to_cpu(ofdm->early_overrun_err), + accum_ofdm->early_overrun_err, + delta_ofdm->early_overrun_err, + max_ofdm->early_overrun_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "crc32_good:", + le32_to_cpu(ofdm->crc32_good), + accum_ofdm->crc32_good, delta_ofdm->crc32_good, + max_ofdm->crc32_good); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "false_alarm_cnt:", + le32_to_cpu(ofdm->false_alarm_cnt), + accum_ofdm->false_alarm_cnt, + delta_ofdm->false_alarm_cnt, + max_ofdm->false_alarm_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "fina_sync_err_cnt:", + le32_to_cpu(ofdm->fina_sync_err_cnt), + accum_ofdm->fina_sync_err_cnt, + delta_ofdm->fina_sync_err_cnt, + max_ofdm->fina_sync_err_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sfd_timeout:", + le32_to_cpu(ofdm->sfd_timeout), + accum_ofdm->sfd_timeout, delta_ofdm->sfd_timeout, + max_ofdm->sfd_timeout); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "fina_timeout:", + le32_to_cpu(ofdm->fina_timeout), + accum_ofdm->fina_timeout, delta_ofdm->fina_timeout, + max_ofdm->fina_timeout); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "unresponded_rts:", + le32_to_cpu(ofdm->unresponded_rts), + accum_ofdm->unresponded_rts, + delta_ofdm->unresponded_rts, + max_ofdm->unresponded_rts); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "rxe_frame_lmt_ovrun:", + le32_to_cpu(ofdm->rxe_frame_limit_overrun), + accum_ofdm->rxe_frame_limit_overrun, + delta_ofdm->rxe_frame_limit_overrun, + max_ofdm->rxe_frame_limit_overrun); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sent_ack_cnt:", + le32_to_cpu(ofdm->sent_ack_cnt), + accum_ofdm->sent_ack_cnt, delta_ofdm->sent_ack_cnt, + max_ofdm->sent_ack_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sent_cts_cnt:", + le32_to_cpu(ofdm->sent_cts_cnt), + accum_ofdm->sent_cts_cnt, delta_ofdm->sent_cts_cnt, + max_ofdm->sent_cts_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sent_ba_rsp_cnt:", + le32_to_cpu(ofdm->sent_ba_rsp_cnt), + accum_ofdm->sent_ba_rsp_cnt, + delta_ofdm->sent_ba_rsp_cnt, + max_ofdm->sent_ba_rsp_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "dsp_self_kill:", + le32_to_cpu(ofdm->dsp_self_kill), + accum_ofdm->dsp_self_kill, + delta_ofdm->dsp_self_kill, + max_ofdm->dsp_self_kill); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "mh_format_err:", + le32_to_cpu(ofdm->mh_format_err), + accum_ofdm->mh_format_err, + delta_ofdm->mh_format_err, + max_ofdm->mh_format_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "re_acq_main_rssi_sum:", + le32_to_cpu(ofdm->re_acq_main_rssi_sum), + accum_ofdm->re_acq_main_rssi_sum, + delta_ofdm->re_acq_main_rssi_sum, + max_ofdm->re_acq_main_rssi_sum); + + pos += scnprintf(buf + pos, bufsz - pos, + fmt_header, "Statistics_Rx - CCK:"); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "ina_cnt:", + le32_to_cpu(cck->ina_cnt), accum_cck->ina_cnt, + delta_cck->ina_cnt, max_cck->ina_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "fina_cnt:", + le32_to_cpu(cck->fina_cnt), accum_cck->fina_cnt, + delta_cck->fina_cnt, max_cck->fina_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "plcp_err:", + le32_to_cpu(cck->plcp_err), accum_cck->plcp_err, + delta_cck->plcp_err, max_cck->plcp_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "crc32_err:", + le32_to_cpu(cck->crc32_err), accum_cck->crc32_err, + delta_cck->crc32_err, max_cck->crc32_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "overrun_err:", + le32_to_cpu(cck->overrun_err), + accum_cck->overrun_err, delta_cck->overrun_err, + max_cck->overrun_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "early_overrun_err:", + le32_to_cpu(cck->early_overrun_err), + accum_cck->early_overrun_err, + delta_cck->early_overrun_err, + max_cck->early_overrun_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "crc32_good:", + le32_to_cpu(cck->crc32_good), accum_cck->crc32_good, + delta_cck->crc32_good, max_cck->crc32_good); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "false_alarm_cnt:", + le32_to_cpu(cck->false_alarm_cnt), + accum_cck->false_alarm_cnt, + delta_cck->false_alarm_cnt, max_cck->false_alarm_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "fina_sync_err_cnt:", + le32_to_cpu(cck->fina_sync_err_cnt), + accum_cck->fina_sync_err_cnt, + delta_cck->fina_sync_err_cnt, + max_cck->fina_sync_err_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sfd_timeout:", + le32_to_cpu(cck->sfd_timeout), + accum_cck->sfd_timeout, delta_cck->sfd_timeout, + max_cck->sfd_timeout); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "fina_timeout:", + le32_to_cpu(cck->fina_timeout), + accum_cck->fina_timeout, delta_cck->fina_timeout, + max_cck->fina_timeout); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "unresponded_rts:", + le32_to_cpu(cck->unresponded_rts), + accum_cck->unresponded_rts, delta_cck->unresponded_rts, + max_cck->unresponded_rts); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "rxe_frame_lmt_ovrun:", + le32_to_cpu(cck->rxe_frame_limit_overrun), + accum_cck->rxe_frame_limit_overrun, + delta_cck->rxe_frame_limit_overrun, + max_cck->rxe_frame_limit_overrun); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sent_ack_cnt:", + le32_to_cpu(cck->sent_ack_cnt), + accum_cck->sent_ack_cnt, delta_cck->sent_ack_cnt, + max_cck->sent_ack_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sent_cts_cnt:", + le32_to_cpu(cck->sent_cts_cnt), + accum_cck->sent_cts_cnt, delta_cck->sent_cts_cnt, + max_cck->sent_cts_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sent_ba_rsp_cnt:", + le32_to_cpu(cck->sent_ba_rsp_cnt), + accum_cck->sent_ba_rsp_cnt, + delta_cck->sent_ba_rsp_cnt, + max_cck->sent_ba_rsp_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "dsp_self_kill:", + le32_to_cpu(cck->dsp_self_kill), + accum_cck->dsp_self_kill, delta_cck->dsp_self_kill, + max_cck->dsp_self_kill); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "mh_format_err:", + le32_to_cpu(cck->mh_format_err), + accum_cck->mh_format_err, delta_cck->mh_format_err, + max_cck->mh_format_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "re_acq_main_rssi_sum:", + le32_to_cpu(cck->re_acq_main_rssi_sum), + accum_cck->re_acq_main_rssi_sum, + delta_cck->re_acq_main_rssi_sum, + max_cck->re_acq_main_rssi_sum); + + pos += scnprintf(buf + pos, bufsz - pos, + fmt_header, "Statistics_Rx - GENERAL:"); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "bogus_cts:", + le32_to_cpu(general->bogus_cts), + accum_general->bogus_cts, delta_general->bogus_cts, + max_general->bogus_cts); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "bogus_ack:", + le32_to_cpu(general->bogus_ack), + accum_general->bogus_ack, delta_general->bogus_ack, + max_general->bogus_ack); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "non_bssid_frames:", + le32_to_cpu(general->non_bssid_frames), + accum_general->non_bssid_frames, + delta_general->non_bssid_frames, + max_general->non_bssid_frames); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "filtered_frames:", + le32_to_cpu(general->filtered_frames), + accum_general->filtered_frames, + delta_general->filtered_frames, + max_general->filtered_frames); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "non_channel_beacons:", + le32_to_cpu(general->non_channel_beacons), + accum_general->non_channel_beacons, + delta_general->non_channel_beacons, + max_general->non_channel_beacons); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "channel_beacons:", + le32_to_cpu(general->channel_beacons), + accum_general->channel_beacons, + delta_general->channel_beacons, + max_general->channel_beacons); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "num_missed_bcon:", + le32_to_cpu(general->num_missed_bcon), + accum_general->num_missed_bcon, + delta_general->num_missed_bcon, + max_general->num_missed_bcon); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "adc_rx_saturation_time:", + le32_to_cpu(general->adc_rx_saturation_time), + accum_general->adc_rx_saturation_time, + delta_general->adc_rx_saturation_time, + max_general->adc_rx_saturation_time); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "ina_detect_search_tm:", + le32_to_cpu(general->ina_detection_search_time), + accum_general->ina_detection_search_time, + delta_general->ina_detection_search_time, + max_general->ina_detection_search_time); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_silence_rssi_a:", + le32_to_cpu(general->beacon_silence_rssi_a), + accum_general->beacon_silence_rssi_a, + delta_general->beacon_silence_rssi_a, + max_general->beacon_silence_rssi_a); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_silence_rssi_b:", + le32_to_cpu(general->beacon_silence_rssi_b), + accum_general->beacon_silence_rssi_b, + delta_general->beacon_silence_rssi_b, + max_general->beacon_silence_rssi_b); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_silence_rssi_c:", + le32_to_cpu(general->beacon_silence_rssi_c), + accum_general->beacon_silence_rssi_c, + delta_general->beacon_silence_rssi_c, + max_general->beacon_silence_rssi_c); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "interference_data_flag:", + le32_to_cpu(general->interference_data_flag), + accum_general->interference_data_flag, + delta_general->interference_data_flag, + max_general->interference_data_flag); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "channel_load:", + le32_to_cpu(general->channel_load), + accum_general->channel_load, + delta_general->channel_load, + max_general->channel_load); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "dsp_false_alarms:", + le32_to_cpu(general->dsp_false_alarms), + accum_general->dsp_false_alarms, + delta_general->dsp_false_alarms, + max_general->dsp_false_alarms); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_rssi_a:", + le32_to_cpu(general->beacon_rssi_a), + accum_general->beacon_rssi_a, + delta_general->beacon_rssi_a, + max_general->beacon_rssi_a); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_rssi_b:", + le32_to_cpu(general->beacon_rssi_b), + accum_general->beacon_rssi_b, + delta_general->beacon_rssi_b, + max_general->beacon_rssi_b); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_rssi_c:", + le32_to_cpu(general->beacon_rssi_c), + accum_general->beacon_rssi_c, + delta_general->beacon_rssi_c, + max_general->beacon_rssi_c); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_energy_a:", + le32_to_cpu(general->beacon_energy_a), + accum_general->beacon_energy_a, + delta_general->beacon_energy_a, + max_general->beacon_energy_a); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_energy_b:", + le32_to_cpu(general->beacon_energy_b), + accum_general->beacon_energy_b, + delta_general->beacon_energy_b, + max_general->beacon_energy_b); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "beacon_energy_c:", + le32_to_cpu(general->beacon_energy_c), + accum_general->beacon_energy_c, + delta_general->beacon_energy_c, + max_general->beacon_energy_c); + + pos += scnprintf(buf + pos, bufsz - pos, + fmt_header, "Statistics_Rx - OFDM_HT:"); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "plcp_err:", + le32_to_cpu(ht->plcp_err), accum_ht->plcp_err, + delta_ht->plcp_err, max_ht->plcp_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "overrun_err:", + le32_to_cpu(ht->overrun_err), accum_ht->overrun_err, + delta_ht->overrun_err, max_ht->overrun_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "early_overrun_err:", + le32_to_cpu(ht->early_overrun_err), + accum_ht->early_overrun_err, + delta_ht->early_overrun_err, + max_ht->early_overrun_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "crc32_good:", + le32_to_cpu(ht->crc32_good), accum_ht->crc32_good, + delta_ht->crc32_good, max_ht->crc32_good); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "crc32_err:", + le32_to_cpu(ht->crc32_err), accum_ht->crc32_err, + delta_ht->crc32_err, max_ht->crc32_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "mh_format_err:", + le32_to_cpu(ht->mh_format_err), + accum_ht->mh_format_err, + delta_ht->mh_format_err, max_ht->mh_format_err); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg_crc32_good:", + le32_to_cpu(ht->agg_crc32_good), + accum_ht->agg_crc32_good, + delta_ht->agg_crc32_good, max_ht->agg_crc32_good); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg_mpdu_cnt:", + le32_to_cpu(ht->agg_mpdu_cnt), + accum_ht->agg_mpdu_cnt, + delta_ht->agg_mpdu_cnt, max_ht->agg_mpdu_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg_cnt:", + le32_to_cpu(ht->agg_cnt), accum_ht->agg_cnt, + delta_ht->agg_cnt, max_ht->agg_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "unsupport_mcs:", + le32_to_cpu(ht->unsupport_mcs), + accum_ht->unsupport_mcs, + delta_ht->unsupport_mcs, max_ht->unsupport_mcs); + + spin_unlock_bh(&priv->statistics.lock); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_ucode_tx_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + int pos = 0; + char *buf; + int bufsz = (sizeof(struct statistics_tx) * 48) + 250; + ssize_t ret; + struct statistics_tx *tx, *accum_tx, *delta_tx, *max_tx; + + if (!iwl_is_alive(priv)) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* the statistic information display here is based on + * the last statistics notification from uCode + * might not reflect the current uCode activity + */ + spin_lock_bh(&priv->statistics.lock); + + tx = &priv->statistics.tx; + accum_tx = &priv->accum_stats.tx; + delta_tx = &priv->delta_stats.tx; + max_tx = &priv->max_delta_stats.tx; + + pos += iwl_statistics_flag(priv, buf, bufsz); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_header, "Statistics_Tx:"); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "preamble:", + le32_to_cpu(tx->preamble_cnt), + accum_tx->preamble_cnt, + delta_tx->preamble_cnt, max_tx->preamble_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "rx_detected_cnt:", + le32_to_cpu(tx->rx_detected_cnt), + accum_tx->rx_detected_cnt, + delta_tx->rx_detected_cnt, max_tx->rx_detected_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "bt_prio_defer_cnt:", + le32_to_cpu(tx->bt_prio_defer_cnt), + accum_tx->bt_prio_defer_cnt, + delta_tx->bt_prio_defer_cnt, + max_tx->bt_prio_defer_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "bt_prio_kill_cnt:", + le32_to_cpu(tx->bt_prio_kill_cnt), + accum_tx->bt_prio_kill_cnt, + delta_tx->bt_prio_kill_cnt, + max_tx->bt_prio_kill_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "few_bytes_cnt:", + le32_to_cpu(tx->few_bytes_cnt), + accum_tx->few_bytes_cnt, + delta_tx->few_bytes_cnt, max_tx->few_bytes_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "cts_timeout:", + le32_to_cpu(tx->cts_timeout), accum_tx->cts_timeout, + delta_tx->cts_timeout, max_tx->cts_timeout); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "ack_timeout:", + le32_to_cpu(tx->ack_timeout), + accum_tx->ack_timeout, + delta_tx->ack_timeout, max_tx->ack_timeout); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "expected_ack_cnt:", + le32_to_cpu(tx->expected_ack_cnt), + accum_tx->expected_ack_cnt, + delta_tx->expected_ack_cnt, + max_tx->expected_ack_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "actual_ack_cnt:", + le32_to_cpu(tx->actual_ack_cnt), + accum_tx->actual_ack_cnt, + delta_tx->actual_ack_cnt, + max_tx->actual_ack_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "dump_msdu_cnt:", + le32_to_cpu(tx->dump_msdu_cnt), + accum_tx->dump_msdu_cnt, + delta_tx->dump_msdu_cnt, + max_tx->dump_msdu_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "abort_nxt_frame_mismatch:", + le32_to_cpu(tx->burst_abort_next_frame_mismatch_cnt), + accum_tx->burst_abort_next_frame_mismatch_cnt, + delta_tx->burst_abort_next_frame_mismatch_cnt, + max_tx->burst_abort_next_frame_mismatch_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "abort_missing_nxt_frame:", + le32_to_cpu(tx->burst_abort_missing_next_frame_cnt), + accum_tx->burst_abort_missing_next_frame_cnt, + delta_tx->burst_abort_missing_next_frame_cnt, + max_tx->burst_abort_missing_next_frame_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "cts_timeout_collision:", + le32_to_cpu(tx->cts_timeout_collision), + accum_tx->cts_timeout_collision, + delta_tx->cts_timeout_collision, + max_tx->cts_timeout_collision); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "ack_ba_timeout_collision:", + le32_to_cpu(tx->ack_or_ba_timeout_collision), + accum_tx->ack_or_ba_timeout_collision, + delta_tx->ack_or_ba_timeout_collision, + max_tx->ack_or_ba_timeout_collision); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg ba_timeout:", + le32_to_cpu(tx->agg.ba_timeout), + accum_tx->agg.ba_timeout, + delta_tx->agg.ba_timeout, + max_tx->agg.ba_timeout); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg ba_resched_frames:", + le32_to_cpu(tx->agg.ba_reschedule_frames), + accum_tx->agg.ba_reschedule_frames, + delta_tx->agg.ba_reschedule_frames, + max_tx->agg.ba_reschedule_frames); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg scd_query_agg_frame:", + le32_to_cpu(tx->agg.scd_query_agg_frame_cnt), + accum_tx->agg.scd_query_agg_frame_cnt, + delta_tx->agg.scd_query_agg_frame_cnt, + max_tx->agg.scd_query_agg_frame_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg scd_query_no_agg:", + le32_to_cpu(tx->agg.scd_query_no_agg), + accum_tx->agg.scd_query_no_agg, + delta_tx->agg.scd_query_no_agg, + max_tx->agg.scd_query_no_agg); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg scd_query_agg:", + le32_to_cpu(tx->agg.scd_query_agg), + accum_tx->agg.scd_query_agg, + delta_tx->agg.scd_query_agg, + max_tx->agg.scd_query_agg); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg scd_query_mismatch:", + le32_to_cpu(tx->agg.scd_query_mismatch), + accum_tx->agg.scd_query_mismatch, + delta_tx->agg.scd_query_mismatch, + max_tx->agg.scd_query_mismatch); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg frame_not_ready:", + le32_to_cpu(tx->agg.frame_not_ready), + accum_tx->agg.frame_not_ready, + delta_tx->agg.frame_not_ready, + max_tx->agg.frame_not_ready); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg underrun:", + le32_to_cpu(tx->agg.underrun), + accum_tx->agg.underrun, + delta_tx->agg.underrun, max_tx->agg.underrun); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg bt_prio_kill:", + le32_to_cpu(tx->agg.bt_prio_kill), + accum_tx->agg.bt_prio_kill, + delta_tx->agg.bt_prio_kill, + max_tx->agg.bt_prio_kill); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "agg rx_ba_rsp_cnt:", + le32_to_cpu(tx->agg.rx_ba_rsp_cnt), + accum_tx->agg.rx_ba_rsp_cnt, + delta_tx->agg.rx_ba_rsp_cnt, + max_tx->agg.rx_ba_rsp_cnt); + + if (tx->tx_power.ant_a || tx->tx_power.ant_b || tx->tx_power.ant_c) { + pos += scnprintf(buf + pos, bufsz - pos, + "tx power: (1/2 dB step)\n"); + if ((priv->nvm_data->valid_tx_ant & ANT_A) && + tx->tx_power.ant_a) + pos += scnprintf(buf + pos, bufsz - pos, + fmt_hex, "antenna A:", + tx->tx_power.ant_a); + if ((priv->nvm_data->valid_tx_ant & ANT_B) && + tx->tx_power.ant_b) + pos += scnprintf(buf + pos, bufsz - pos, + fmt_hex, "antenna B:", + tx->tx_power.ant_b); + if ((priv->nvm_data->valid_tx_ant & ANT_C) && + tx->tx_power.ant_c) + pos += scnprintf(buf + pos, bufsz - pos, + fmt_hex, "antenna C:", + tx->tx_power.ant_c); + } + + spin_unlock_bh(&priv->statistics.lock); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_ucode_general_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + int pos = 0; + char *buf; + int bufsz = sizeof(struct statistics_general) * 10 + 300; + ssize_t ret; + struct statistics_general_common *general, *accum_general; + struct statistics_general_common *delta_general, *max_general; + struct statistics_dbg *dbg, *accum_dbg, *delta_dbg, *max_dbg; + struct statistics_div *div, *accum_div, *delta_div, *max_div; + + if (!iwl_is_alive(priv)) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* the statistic information display here is based on + * the last statistics notification from uCode + * might not reflect the current uCode activity + */ + + spin_lock_bh(&priv->statistics.lock); + + general = &priv->statistics.common; + dbg = &priv->statistics.common.dbg; + div = &priv->statistics.common.div; + accum_general = &priv->accum_stats.common; + accum_dbg = &priv->accum_stats.common.dbg; + accum_div = &priv->accum_stats.common.div; + delta_general = &priv->delta_stats.common; + max_general = &priv->max_delta_stats.common; + delta_dbg = &priv->delta_stats.common.dbg; + max_dbg = &priv->max_delta_stats.common.dbg; + delta_div = &priv->delta_stats.common.div; + max_div = &priv->max_delta_stats.common.div; + + pos += iwl_statistics_flag(priv, buf, bufsz); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_header, "Statistics_General:"); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_value, "temperature:", + le32_to_cpu(general->temperature)); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_value, "temperature_m:", + le32_to_cpu(general->temperature_m)); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_value, "ttl_timestamp:", + le32_to_cpu(general->ttl_timestamp)); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "burst_check:", + le32_to_cpu(dbg->burst_check), + accum_dbg->burst_check, + delta_dbg->burst_check, max_dbg->burst_check); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "burst_count:", + le32_to_cpu(dbg->burst_count), + accum_dbg->burst_count, + delta_dbg->burst_count, max_dbg->burst_count); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "wait_for_silence_timeout_count:", + le32_to_cpu(dbg->wait_for_silence_timeout_cnt), + accum_dbg->wait_for_silence_timeout_cnt, + delta_dbg->wait_for_silence_timeout_cnt, + max_dbg->wait_for_silence_timeout_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "sleep_time:", + le32_to_cpu(general->sleep_time), + accum_general->sleep_time, + delta_general->sleep_time, max_general->sleep_time); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "slots_out:", + le32_to_cpu(general->slots_out), + accum_general->slots_out, + delta_general->slots_out, max_general->slots_out); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "slots_idle:", + le32_to_cpu(general->slots_idle), + accum_general->slots_idle, + delta_general->slots_idle, max_general->slots_idle); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "tx_on_a:", + le32_to_cpu(div->tx_on_a), accum_div->tx_on_a, + delta_div->tx_on_a, max_div->tx_on_a); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "tx_on_b:", + le32_to_cpu(div->tx_on_b), accum_div->tx_on_b, + delta_div->tx_on_b, max_div->tx_on_b); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "exec_time:", + le32_to_cpu(div->exec_time), accum_div->exec_time, + delta_div->exec_time, max_div->exec_time); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "probe_time:", + le32_to_cpu(div->probe_time), accum_div->probe_time, + delta_div->probe_time, max_div->probe_time); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "rx_enable_counter:", + le32_to_cpu(general->rx_enable_counter), + accum_general->rx_enable_counter, + delta_general->rx_enable_counter, + max_general->rx_enable_counter); + pos += scnprintf(buf + pos, bufsz - pos, + fmt_table, "num_of_sos_states:", + le32_to_cpu(general->num_of_sos_states), + accum_general->num_of_sos_states, + delta_general->num_of_sos_states, + max_general->num_of_sos_states); + + spin_unlock_bh(&priv->statistics.lock); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_ucode_bt_stats_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = (struct iwl_priv *)file->private_data; + int pos = 0; + char *buf; + int bufsz = (sizeof(struct statistics_bt_activity) * 24) + 200; + ssize_t ret; + struct statistics_bt_activity *bt, *accum_bt; + + if (!iwl_is_alive(priv)) + return -EAGAIN; + + if (!priv->bt_enable_flag) + return -EINVAL; + + /* make request to uCode to retrieve statistics information */ + mutex_lock(&priv->mutex); + ret = iwl_send_statistics_request(priv, CMD_SYNC, false); + mutex_unlock(&priv->mutex); + + if (ret) + return -EAGAIN; + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* + * the statistic information display here is based on + * the last statistics notification from uCode + * might not reflect the current uCode activity + */ + + spin_lock_bh(&priv->statistics.lock); + + bt = &priv->statistics.bt_activity; + accum_bt = &priv->accum_stats.bt_activity; + + pos += iwl_statistics_flag(priv, buf, bufsz); + pos += scnprintf(buf + pos, bufsz - pos, "Statistics_BT:\n"); + pos += scnprintf(buf + pos, bufsz - pos, + "\t\t\tcurrent\t\t\taccumulative\n"); + pos += scnprintf(buf + pos, bufsz - pos, + "hi_priority_tx_req_cnt:\t\t%u\t\t\t%u\n", + le32_to_cpu(bt->hi_priority_tx_req_cnt), + accum_bt->hi_priority_tx_req_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + "hi_priority_tx_denied_cnt:\t%u\t\t\t%u\n", + le32_to_cpu(bt->hi_priority_tx_denied_cnt), + accum_bt->hi_priority_tx_denied_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + "lo_priority_tx_req_cnt:\t\t%u\t\t\t%u\n", + le32_to_cpu(bt->lo_priority_tx_req_cnt), + accum_bt->lo_priority_tx_req_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + "lo_priority_tx_denied_cnt:\t%u\t\t\t%u\n", + le32_to_cpu(bt->lo_priority_tx_denied_cnt), + accum_bt->lo_priority_tx_denied_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + "hi_priority_rx_req_cnt:\t\t%u\t\t\t%u\n", + le32_to_cpu(bt->hi_priority_rx_req_cnt), + accum_bt->hi_priority_rx_req_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + "hi_priority_rx_denied_cnt:\t%u\t\t\t%u\n", + le32_to_cpu(bt->hi_priority_rx_denied_cnt), + accum_bt->hi_priority_rx_denied_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + "lo_priority_rx_req_cnt:\t\t%u\t\t\t%u\n", + le32_to_cpu(bt->lo_priority_rx_req_cnt), + accum_bt->lo_priority_rx_req_cnt); + pos += scnprintf(buf + pos, bufsz - pos, + "lo_priority_rx_denied_cnt:\t%u\t\t\t%u\n", + le32_to_cpu(bt->lo_priority_rx_denied_cnt), + accum_bt->lo_priority_rx_denied_cnt); + + pos += scnprintf(buf + pos, bufsz - pos, + "(rx)num_bt_kills:\t\t%u\t\t\t%u\n", + le32_to_cpu(priv->statistics.num_bt_kills), + priv->statistics.accum_num_bt_kills); + + spin_unlock_bh(&priv->statistics.lock); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_reply_tx_error_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = (struct iwl_priv *)file->private_data; + int pos = 0; + char *buf; + int bufsz = (sizeof(struct reply_tx_error_statistics) * 24) + + (sizeof(struct reply_agg_tx_error_statistics) * 24) + 200; + ssize_t ret; + + if (!iwl_is_alive(priv)) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, "Statistics_TX_Error:\n"); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_POSTPONE_DELAY), + priv->reply_tx_stats.pp_delay); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_POSTPONE_FEW_BYTES), + priv->reply_tx_stats.pp_few_bytes); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_POSTPONE_BT_PRIO), + priv->reply_tx_stats.pp_bt_prio); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_POSTPONE_QUIET_PERIOD), + priv->reply_tx_stats.pp_quiet_period); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_POSTPONE_CALC_TTAK), + priv->reply_tx_stats.pp_calc_ttak); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t%u\n", + iwl_get_tx_fail_reason( + TX_STATUS_FAIL_INTERNAL_CROSSED_RETRY), + priv->reply_tx_stats.int_crossed_retry); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_SHORT_LIMIT), + priv->reply_tx_stats.short_limit); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_LONG_LIMIT), + priv->reply_tx_stats.long_limit); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_FIFO_UNDERRUN), + priv->reply_tx_stats.fifo_underrun); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_DRAIN_FLOW), + priv->reply_tx_stats.drain_flow); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_RFKILL_FLUSH), + priv->reply_tx_stats.rfkill_flush); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_LIFE_EXPIRE), + priv->reply_tx_stats.life_expire); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_DEST_PS), + priv->reply_tx_stats.dest_ps); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_HOST_ABORTED), + priv->reply_tx_stats.host_abort); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_BT_RETRY), + priv->reply_tx_stats.pp_delay); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_STA_INVALID), + priv->reply_tx_stats.sta_invalid); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_FRAG_DROPPED), + priv->reply_tx_stats.frag_drop); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_TID_DISABLE), + priv->reply_tx_stats.tid_disable); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_FIFO_FLUSHED), + priv->reply_tx_stats.fifo_flush); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t%u\n", + iwl_get_tx_fail_reason( + TX_STATUS_FAIL_INSUFFICIENT_CF_POLL), + priv->reply_tx_stats.insuff_cf_poll); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_tx_fail_reason(TX_STATUS_FAIL_PASSIVE_NO_RX), + priv->reply_tx_stats.fail_hw_drop); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t%u\n", + iwl_get_tx_fail_reason( + TX_STATUS_FAIL_NO_BEACON_ON_RADAR), + priv->reply_tx_stats.sta_color_mismatch); + pos += scnprintf(buf + pos, bufsz - pos, "UNKNOWN:\t\t\t%u\n", + priv->reply_tx_stats.unknown); + + pos += scnprintf(buf + pos, bufsz - pos, + "\nStatistics_Agg_TX_Error:\n"); + + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_agg_tx_fail_reason(AGG_TX_STATE_UNDERRUN_MSK), + priv->reply_agg_tx_stats.underrun); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_agg_tx_fail_reason(AGG_TX_STATE_BT_PRIO_MSK), + priv->reply_agg_tx_stats.bt_prio); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_agg_tx_fail_reason(AGG_TX_STATE_FEW_BYTES_MSK), + priv->reply_agg_tx_stats.few_bytes); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_agg_tx_fail_reason(AGG_TX_STATE_ABORT_MSK), + priv->reply_agg_tx_stats.abort); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t%u\n", + iwl_get_agg_tx_fail_reason( + AGG_TX_STATE_LAST_SENT_TTL_MSK), + priv->reply_agg_tx_stats.last_sent_ttl); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t%u\n", + iwl_get_agg_tx_fail_reason( + AGG_TX_STATE_LAST_SENT_TRY_CNT_MSK), + priv->reply_agg_tx_stats.last_sent_try); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t%u\n", + iwl_get_agg_tx_fail_reason( + AGG_TX_STATE_LAST_SENT_BT_KILL_MSK), + priv->reply_agg_tx_stats.last_sent_bt_kill); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_agg_tx_fail_reason(AGG_TX_STATE_SCD_QUERY_MSK), + priv->reply_agg_tx_stats.scd_query); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t%u\n", + iwl_get_agg_tx_fail_reason( + AGG_TX_STATE_TEST_BAD_CRC32_MSK), + priv->reply_agg_tx_stats.bad_crc32); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_agg_tx_fail_reason(AGG_TX_STATE_RESPONSE_MSK), + priv->reply_agg_tx_stats.response); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_agg_tx_fail_reason(AGG_TX_STATE_DUMP_TX_MSK), + priv->reply_agg_tx_stats.dump_tx); + pos += scnprintf(buf + pos, bufsz - pos, "%s:\t\t\t%u\n", + iwl_get_agg_tx_fail_reason(AGG_TX_STATE_DELAY_TX_MSK), + priv->reply_agg_tx_stats.delay_tx); + pos += scnprintf(buf + pos, bufsz - pos, "UNKNOWN:\t\t\t%u\n", + priv->reply_agg_tx_stats.unknown); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_sensitivity_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + int pos = 0; + int cnt = 0; + char *buf; + int bufsz = sizeof(struct iwl_sensitivity_data) * 4 + 100; + ssize_t ret; + struct iwl_sensitivity_data *data; + + data = &priv->sensitivity_data; + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, "auto_corr_ofdm:\t\t\t %u\n", + data->auto_corr_ofdm); + pos += scnprintf(buf + pos, bufsz - pos, + "auto_corr_ofdm_mrc:\t\t %u\n", + data->auto_corr_ofdm_mrc); + pos += scnprintf(buf + pos, bufsz - pos, "auto_corr_ofdm_x1:\t\t %u\n", + data->auto_corr_ofdm_x1); + pos += scnprintf(buf + pos, bufsz - pos, + "auto_corr_ofdm_mrc_x1:\t\t %u\n", + data->auto_corr_ofdm_mrc_x1); + pos += scnprintf(buf + pos, bufsz - pos, "auto_corr_cck:\t\t\t %u\n", + data->auto_corr_cck); + pos += scnprintf(buf + pos, bufsz - pos, "auto_corr_cck_mrc:\t\t %u\n", + data->auto_corr_cck_mrc); + pos += scnprintf(buf + pos, bufsz - pos, + "last_bad_plcp_cnt_ofdm:\t\t %u\n", + data->last_bad_plcp_cnt_ofdm); + pos += scnprintf(buf + pos, bufsz - pos, "last_fa_cnt_ofdm:\t\t %u\n", + data->last_fa_cnt_ofdm); + pos += scnprintf(buf + pos, bufsz - pos, + "last_bad_plcp_cnt_cck:\t\t %u\n", + data->last_bad_plcp_cnt_cck); + pos += scnprintf(buf + pos, bufsz - pos, "last_fa_cnt_cck:\t\t %u\n", + data->last_fa_cnt_cck); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_curr_state:\t\t\t %u\n", + data->nrg_curr_state); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_prev_state:\t\t\t %u\n", + data->nrg_prev_state); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_value:\t\t\t"); + for (cnt = 0; cnt < 10; cnt++) { + pos += scnprintf(buf + pos, bufsz - pos, " %u", + data->nrg_value[cnt]); + } + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_silence_rssi:\t\t"); + for (cnt = 0; cnt < NRG_NUM_PREV_STAT_L; cnt++) { + pos += scnprintf(buf + pos, bufsz - pos, " %u", + data->nrg_silence_rssi[cnt]); + } + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_silence_ref:\t\t %u\n", + data->nrg_silence_ref); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_energy_idx:\t\t\t %u\n", + data->nrg_energy_idx); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_silence_idx:\t\t %u\n", + data->nrg_silence_idx); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_th_cck:\t\t\t %u\n", + data->nrg_th_cck); + pos += scnprintf(buf + pos, bufsz - pos, + "nrg_auto_corr_silence_diff:\t %u\n", + data->nrg_auto_corr_silence_diff); + pos += scnprintf(buf + pos, bufsz - pos, "num_in_cck_no_fa:\t\t %u\n", + data->num_in_cck_no_fa); + pos += scnprintf(buf + pos, bufsz - pos, "nrg_th_ofdm:\t\t\t %u\n", + data->nrg_th_ofdm); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + + +static ssize_t iwl_dbgfs_chain_noise_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + int pos = 0; + int cnt = 0; + char *buf; + int bufsz = sizeof(struct iwl_chain_noise_data) * 4 + 100; + ssize_t ret; + struct iwl_chain_noise_data *data; + + data = &priv->chain_noise_data; + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, "active_chains:\t\t\t %u\n", + data->active_chains); + pos += scnprintf(buf + pos, bufsz - pos, "chain_noise_a:\t\t\t %u\n", + data->chain_noise_a); + pos += scnprintf(buf + pos, bufsz - pos, "chain_noise_b:\t\t\t %u\n", + data->chain_noise_b); + pos += scnprintf(buf + pos, bufsz - pos, "chain_noise_c:\t\t\t %u\n", + data->chain_noise_c); + pos += scnprintf(buf + pos, bufsz - pos, "chain_signal_a:\t\t\t %u\n", + data->chain_signal_a); + pos += scnprintf(buf + pos, bufsz - pos, "chain_signal_b:\t\t\t %u\n", + data->chain_signal_b); + pos += scnprintf(buf + pos, bufsz - pos, "chain_signal_c:\t\t\t %u\n", + data->chain_signal_c); + pos += scnprintf(buf + pos, bufsz - pos, "beacon_count:\t\t\t %u\n", + data->beacon_count); + + pos += scnprintf(buf + pos, bufsz - pos, "disconn_array:\t\t\t"); + for (cnt = 0; cnt < NUM_RX_CHAINS; cnt++) { + pos += scnprintf(buf + pos, bufsz - pos, " %u", + data->disconn_array[cnt]); + } + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + pos += scnprintf(buf + pos, bufsz - pos, "delta_gain_code:\t\t"); + for (cnt = 0; cnt < NUM_RX_CHAINS; cnt++) { + pos += scnprintf(buf + pos, bufsz - pos, " %u", + data->delta_gain_code[cnt]); + } + pos += scnprintf(buf + pos, bufsz - pos, "\n"); + pos += scnprintf(buf + pos, bufsz - pos, "radio_write:\t\t\t %u\n", + data->radio_write); + pos += scnprintf(buf + pos, bufsz - pos, "state:\t\t\t\t %u\n", + data->state); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_power_save_status_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[60]; + int pos = 0; + const size_t bufsz = sizeof(buf); + u32 pwrsave_status; + + pwrsave_status = iwl_read32(priv->trans, CSR_GP_CNTRL) & + CSR_GP_REG_POWER_SAVE_STATUS_MSK; + + pos += scnprintf(buf + pos, bufsz - pos, "Power Save Status: "); + pos += scnprintf(buf + pos, bufsz - pos, "%s\n", + (pwrsave_status == CSR_GP_REG_NO_POWER_SAVE) ? "none" : + (pwrsave_status == CSR_GP_REG_MAC_POWER_SAVE) ? "MAC" : + (pwrsave_status == CSR_GP_REG_PHY_POWER_SAVE) ? "PHY" : + "error"); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_clear_ucode_statistics_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + int clear; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &clear) != 1) + return -EFAULT; + + /* make request to uCode to retrieve statistics information */ + mutex_lock(&priv->mutex); + iwl_send_statistics_request(priv, CMD_SYNC, true); + mutex_unlock(&priv->mutex); + + return count; +} + +static ssize_t iwl_dbgfs_ucode_tracing_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + int pos = 0; + char buf[128]; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, "ucode trace timer is %s\n", + priv->event_log.ucode_trace ? "On" : "Off"); + pos += scnprintf(buf + pos, bufsz - pos, "non_wraps_count:\t\t %u\n", + priv->event_log.non_wraps_count); + pos += scnprintf(buf + pos, bufsz - pos, "wraps_once_count:\t\t %u\n", + priv->event_log.wraps_once_count); + pos += scnprintf(buf + pos, bufsz - pos, "wraps_more_count:\t\t %u\n", + priv->event_log.wraps_more_count); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_ucode_tracing_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + int trace; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &trace) != 1) + return -EFAULT; + + if (trace) { + priv->event_log.ucode_trace = true; + if (iwl_is_alive(priv)) { + /* start collecting data now */ + mod_timer(&priv->ucode_trace, jiffies); + } + } else { + priv->event_log.ucode_trace = false; + del_timer_sync(&priv->ucode_trace); + } + + return count; +} + +static ssize_t iwl_dbgfs_rxon_flags_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + int len = 0; + char buf[20]; + + len = sprintf(buf, "0x%04X\n", + le32_to_cpu(priv->contexts[IWL_RXON_CTX_BSS].active.flags)); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t iwl_dbgfs_rxon_filter_flags_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + int len = 0; + char buf[20]; + + len = sprintf(buf, "0x%04X\n", + le32_to_cpu(priv->contexts[IWL_RXON_CTX_BSS].active.filter_flags)); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t iwl_dbgfs_missed_beacon_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + int pos = 0; + char buf[12]; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, "%d\n", + priv->missed_beacon_threshold); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_missed_beacon_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + int missed; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &missed) != 1) + return -EINVAL; + + if (missed < IWL_MISSED_BEACON_THRESHOLD_MIN || + missed > IWL_MISSED_BEACON_THRESHOLD_MAX) + priv->missed_beacon_threshold = + IWL_MISSED_BEACON_THRESHOLD_DEF; + else + priv->missed_beacon_threshold = missed; + + return count; +} + +static ssize_t iwl_dbgfs_plcp_delta_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + int pos = 0; + char buf[12]; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, "%u\n", + priv->plcp_delta_threshold); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_plcp_delta_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + int plcp; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &plcp) != 1) + return -EINVAL; + if ((plcp < IWL_MAX_PLCP_ERR_THRESHOLD_MIN) || + (plcp > IWL_MAX_PLCP_ERR_THRESHOLD_MAX)) + priv->plcp_delta_threshold = + IWL_MAX_PLCP_ERR_THRESHOLD_DISABLE; + else + priv->plcp_delta_threshold = plcp; + return count; +} + +static ssize_t iwl_dbgfs_rf_reset_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + int pos = 0; + char buf[300]; + const size_t bufsz = sizeof(buf); + struct iwl_rf_reset *rf_reset = &priv->rf_reset; + + pos += scnprintf(buf + pos, bufsz - pos, + "RF reset statistics\n"); + pos += scnprintf(buf + pos, bufsz - pos, + "\tnumber of reset request: %d\n", + rf_reset->reset_request_count); + pos += scnprintf(buf + pos, bufsz - pos, + "\tnumber of reset request success: %d\n", + rf_reset->reset_success_count); + pos += scnprintf(buf + pos, bufsz - pos, + "\tnumber of reset request reject: %d\n", + rf_reset->reset_reject_count); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_rf_reset_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + int ret; + + ret = iwl_force_rf_reset(priv, true); + return ret ? ret : count; +} + +static ssize_t iwl_dbgfs_txfifo_flush_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + int flush; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &flush) != 1) + return -EINVAL; + + if (iwl_is_rfkill(priv)) + return -EFAULT; + + iwlagn_dev_txfifo_flush(priv); + + return count; +} + +static ssize_t iwl_dbgfs_bt_traffic_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = (struct iwl_priv *)file->private_data; + int pos = 0; + char buf[200]; + const size_t bufsz = sizeof(buf); + + if (!priv->bt_enable_flag) { + pos += scnprintf(buf + pos, bufsz - pos, "BT coex disabled\n"); + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); + } + pos += scnprintf(buf + pos, bufsz - pos, "BT enable flag: 0x%x\n", + priv->bt_enable_flag); + pos += scnprintf(buf + pos, bufsz - pos, "BT in %s mode\n", + priv->bt_full_concurrent ? "full concurrency" : "3-wire"); + pos += scnprintf(buf + pos, bufsz - pos, "BT status: %s, " + "last traffic notif: %d\n", + priv->bt_status ? "On" : "Off", priv->last_bt_traffic_load); + pos += scnprintf(buf + pos, bufsz - pos, "ch_announcement: %d, " + "kill_ack_mask: %x, kill_cts_mask: %x\n", + priv->bt_ch_announce, priv->kill_ack_mask, + priv->kill_cts_mask); + + pos += scnprintf(buf + pos, bufsz - pos, "bluetooth traffic load: "); + switch (priv->bt_traffic_load) { + case IWL_BT_COEX_TRAFFIC_LOAD_CONTINUOUS: + pos += scnprintf(buf + pos, bufsz - pos, "Continuous\n"); + break; + case IWL_BT_COEX_TRAFFIC_LOAD_HIGH: + pos += scnprintf(buf + pos, bufsz - pos, "High\n"); + break; + case IWL_BT_COEX_TRAFFIC_LOAD_LOW: + pos += scnprintf(buf + pos, bufsz - pos, "Low\n"); + break; + case IWL_BT_COEX_TRAFFIC_LOAD_NONE: + default: + pos += scnprintf(buf + pos, bufsz - pos, "None\n"); + break; + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_protection_mode_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = (struct iwl_priv *)file->private_data; + + int pos = 0; + char buf[40]; + const size_t bufsz = sizeof(buf); + + if (priv->cfg->ht_params) + pos += scnprintf(buf + pos, bufsz - pos, + "use %s for aggregation\n", + (priv->hw_params.use_rts_for_aggregation) ? + "rts/cts" : "cts-to-self"); + else + pos += scnprintf(buf + pos, bufsz - pos, "N/A"); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_protection_mode_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) { + + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + int rts; + + if (!priv->cfg->ht_params) + return -EINVAL; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &rts) != 1) + return -EINVAL; + if (rts) + priv->hw_params.use_rts_for_aggregation = true; + else + priv->hw_params.use_rts_for_aggregation = false; + return count; +} + +static int iwl_cmd_echo_test(struct iwl_priv *priv) +{ + int ret; + struct iwl_host_cmd cmd = { + .id = REPLY_ECHO, + .len = { 0 }, + .flags = CMD_SYNC, + }; + + ret = iwl_dvm_send_cmd(priv, &cmd); + if (ret) + IWL_ERR(priv, "echo testing fail: 0X%x\n", ret); + else + IWL_DEBUG_INFO(priv, "echo testing pass\n"); + return ret; +} + +static ssize_t iwl_dbgfs_echo_test_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[8]; + int buf_size; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + iwl_cmd_echo_test(priv); + return count; +} + +#ifdef CPTCFG_IWLWIFI_DEBUG +static ssize_t iwl_dbgfs_log_event_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char *buf = NULL; + ssize_t ret; + + ret = iwl_dump_nic_event_log(priv, true, &buf); + if (ret > 0) + ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_log_event_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + u32 event_log_flag; + char buf[8]; + int buf_size; + + /* check that the interface is up */ + if (!iwl_is_ready(priv)) + return -EAGAIN; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &event_log_flag) != 1) + return -EFAULT; + if (event_log_flag == 1) + iwl_dump_nic_event_log(priv, true, NULL); + + return count; +} +#endif + +static ssize_t iwl_dbgfs_calib_disabled_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[120]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, + "Sensitivity calibrations %s\n", + (priv->calib_disabled & + IWL_SENSITIVITY_CALIB_DISABLED) ? + "DISABLED" : "ENABLED"); + pos += scnprintf(buf + pos, bufsz - pos, + "Chain noise calibrations %s\n", + (priv->calib_disabled & + IWL_CHAIN_NOISE_CALIB_DISABLED) ? + "DISABLED" : "ENABLED"); + pos += scnprintf(buf + pos, bufsz - pos, + "Tx power calibrations %s\n", + (priv->calib_disabled & + IWL_TX_POWER_CALIB_DISABLED) ? + "DISABLED" : "ENABLED"); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_calib_disabled_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + char buf[8]; + u32 calib_disabled; + int buf_size; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%x", &calib_disabled) != 1) + return -EFAULT; + + priv->calib_disabled = calib_disabled; + + return count; +} + +static ssize_t iwl_dbgfs_fw_restart_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_priv *priv = file->private_data; + bool restart_fw = iwlwifi_mod_params.restart_fw; + int ret; + + iwlwifi_mod_params.restart_fw = true; + + mutex_lock(&priv->mutex); + + /* take the return value to make compiler happy - it will fail anyway */ + ret = iwl_dvm_send_cmd_pdu(priv, REPLY_ERROR, CMD_SYNC, 0, NULL); + + mutex_unlock(&priv->mutex); + + iwlwifi_mod_params.restart_fw = restart_fw; + + return count; +} + +DEBUGFS_READ_FILE_OPS(ucode_rx_stats); +DEBUGFS_READ_FILE_OPS(ucode_tx_stats); +DEBUGFS_READ_FILE_OPS(ucode_general_stats); +DEBUGFS_READ_FILE_OPS(sensitivity); +DEBUGFS_READ_FILE_OPS(chain_noise); +DEBUGFS_READ_FILE_OPS(power_save_status); +DEBUGFS_WRITE_FILE_OPS(clear_ucode_statistics); +DEBUGFS_READ_WRITE_FILE_OPS(ucode_tracing); +DEBUGFS_READ_WRITE_FILE_OPS(missed_beacon); +DEBUGFS_READ_WRITE_FILE_OPS(plcp_delta); +DEBUGFS_READ_WRITE_FILE_OPS(rf_reset); +DEBUGFS_READ_FILE_OPS(rxon_flags); +DEBUGFS_READ_FILE_OPS(rxon_filter_flags); +DEBUGFS_WRITE_FILE_OPS(txfifo_flush); +DEBUGFS_READ_FILE_OPS(ucode_bt_stats); +DEBUGFS_READ_FILE_OPS(bt_traffic); +DEBUGFS_READ_WRITE_FILE_OPS(protection_mode); +DEBUGFS_READ_FILE_OPS(reply_tx_error); +DEBUGFS_WRITE_FILE_OPS(echo_test); +DEBUGFS_WRITE_FILE_OPS(fw_restart); +#ifdef CPTCFG_IWLWIFI_DEBUG +DEBUGFS_READ_WRITE_FILE_OPS(log_event); +#endif +DEBUGFS_READ_WRITE_FILE_OPS(calib_disabled); + +/* + * Create the debugfs files and directories + * + */ +int iwl_dbgfs_register(struct iwl_priv *priv, struct dentry *dbgfs_dir) +{ + struct dentry *dir_data, *dir_rf, *dir_debug; + + priv->debugfs_dir = dbgfs_dir; + + dir_data = debugfs_create_dir("data", dbgfs_dir); + if (!dir_data) + goto err; + dir_rf = debugfs_create_dir("rf", dbgfs_dir); + if (!dir_rf) + goto err; + dir_debug = debugfs_create_dir("debug", dbgfs_dir); + if (!dir_debug) + goto err; + + DEBUGFS_ADD_FILE(nvm, dir_data, S_IRUSR); + DEBUGFS_ADD_FILE(sram, dir_data, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(wowlan_sram, dir_data, S_IRUSR); + DEBUGFS_ADD_FILE(stations, dir_data, S_IRUSR); + DEBUGFS_ADD_FILE(channels, dir_data, S_IRUSR); + DEBUGFS_ADD_FILE(status, dir_data, S_IRUSR); + DEBUGFS_ADD_FILE(rx_handlers, dir_data, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(qos, dir_data, S_IRUSR); + DEBUGFS_ADD_FILE(sleep_level_override, dir_data, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(current_sleep_command, dir_data, S_IRUSR); + DEBUGFS_ADD_FILE(thermal_throttling, dir_data, S_IRUSR); + DEBUGFS_ADD_FILE(disable_ht40, dir_data, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(temperature, dir_data, S_IRUSR); + + DEBUGFS_ADD_FILE(power_save_status, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(clear_ucode_statistics, dir_debug, S_IWUSR); + DEBUGFS_ADD_FILE(missed_beacon, dir_debug, S_IWUSR); + DEBUGFS_ADD_FILE(plcp_delta, dir_debug, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(rf_reset, dir_debug, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(ucode_rx_stats, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(ucode_tx_stats, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(ucode_general_stats, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(txfifo_flush, dir_debug, S_IWUSR); + DEBUGFS_ADD_FILE(protection_mode, dir_debug, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(sensitivity, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(chain_noise, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(ucode_tracing, dir_debug, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(ucode_bt_stats, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(reply_tx_error, dir_debug, S_IRUSR); + DEBUGFS_ADD_FILE(rxon_flags, dir_debug, S_IWUSR); + DEBUGFS_ADD_FILE(rxon_filter_flags, dir_debug, S_IWUSR); + DEBUGFS_ADD_FILE(echo_test, dir_debug, S_IWUSR); + DEBUGFS_ADD_FILE(fw_restart, dir_debug, S_IWUSR); +#ifdef CPTCFG_IWLWIFI_DEBUG + DEBUGFS_ADD_FILE(log_event, dir_debug, S_IWUSR | S_IRUSR); +#endif + + if (iwl_advanced_bt_coexist(priv)) + DEBUGFS_ADD_FILE(bt_traffic, dir_debug, S_IRUSR); + + /* Calibrations disabled/enabled status*/ + DEBUGFS_ADD_FILE(calib_disabled, dir_rf, S_IWUSR | S_IRUSR); + + /* + * Create a symlink with mac80211. This is not very robust, as it does + * not remove the symlink created. The implicit assumption is that + * when the opmode exits, mac80211 will also exit, and will remove + * this symlink as part of its cleanup. + */ + if (priv->mac80211_registered) { + char buf[100]; + struct dentry *mac80211_dir, *dev_dir, *root_dir; + + dev_dir = dbgfs_dir->d_parent; + root_dir = dev_dir->d_parent; + mac80211_dir = priv->hw->wiphy->debugfsdir; + + snprintf(buf, 100, "../../%s/%s", root_dir->d_name.name, + dev_dir->d_name.name); + + if (!debugfs_create_symlink("iwlwifi", mac80211_dir, buf)) + goto err; + } + + return 0; + +err: + IWL_ERR(priv, "failed to create the dvm debugfs entries\n"); + return -ENOMEM; +} diff --git a/drivers/net/wireless/iwlwifi/dvm/dev.h b/drivers/net/wireless/iwlwifi/dvm/dev.h new file mode 100644 index 0000000..a19698c --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/dev.h @@ -0,0 +1,917 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ +/* + * Please use this file (dev.h) for driver implementation definitions. + * Please use commands.h for uCode API definitions. + */ + +#ifndef __iwl_dev_h__ +#define __iwl_dev_h__ + +#include +#include +#include +#include +#include +#include + +#include "iwl-fw.h" +#include "iwl-eeprom-parse.h" +#include "iwl-csr.h" +#include "iwl-debug.h" +#include "iwl-agn-hw.h" +#include "iwl-op-mode.h" +#include "iwl-notif-wait.h" +#include "iwl-trans.h" + +#include "led.h" +#include "power.h" +#include "rs.h" +#include "tt.h" + +#include "iwl-test.h" + +/* CT-KILL constants */ +#define CT_KILL_THRESHOLD_LEGACY 110 /* in Celsius */ +#define CT_KILL_THRESHOLD 114 /* in Celsius */ +#define CT_KILL_EXIT_THRESHOLD 95 /* in Celsius */ + +/* Default noise level to report when noise measurement is not available. + * This may be because we're: + * 1) Not associated no beacon statistics being sent to driver) + * 2) Scanning (noise measurement does not apply to associated channel) + * Use default noise value of -127 ... this is below the range of measurable + * Rx dBm for all agn devices, so it can indicate "unmeasurable" to user. + * Also, -127 works better than 0 when averaging frames with/without + * noise info (e.g. averaging might be done in app); measured dBm values are + * always negative ... using a negative value as the default keeps all + * averages within an s8's (used in some apps) range of negative values. */ +#define IWL_NOISE_MEAS_NOT_AVAILABLE (-127) + +/* + * RTS threshold here is total size [2347] minus 4 FCS bytes + * Per spec: + * a value of 0 means RTS on all data/management packets + * a value > max MSDU size means no RTS + * else RTS for data/management frames where MPDU is larger + * than RTS value. + */ +#define DEFAULT_RTS_THRESHOLD 2347U +#define MIN_RTS_THRESHOLD 0U +#define MAX_RTS_THRESHOLD 2347U +#define MAX_MSDU_SIZE 2304U +#define MAX_MPDU_SIZE 2346U +#define DEFAULT_BEACON_INTERVAL 200U +#define DEFAULT_SHORT_RETRY_LIMIT 7U +#define DEFAULT_LONG_RETRY_LIMIT 4U + +#define IWL_NUM_SCAN_RATES (2) + + +#define IEEE80211_DATA_LEN 2304 +#define IEEE80211_4ADDR_LEN 30 +#define IEEE80211_HLEN (IEEE80211_4ADDR_LEN) +#define IEEE80211_FRAME_LEN (IEEE80211_DATA_LEN + IEEE80211_HLEN) + +#define SUP_RATE_11A_MAX_NUM_CHANNELS 8 +#define SUP_RATE_11B_MAX_NUM_CHANNELS 4 +#define SUP_RATE_11G_MAX_NUM_CHANNELS 12 + +#define IWL_SUPPORTED_RATES_IE_LEN 8 + +#define IWL_INVALID_RATE 0xFF +#define IWL_INVALID_VALUE -1 + +union iwl_ht_rate_supp { + u16 rates; + struct { + u8 siso_rate; + u8 mimo_rate; + }; +}; + +struct iwl_ht_config { + bool single_chain_sufficient; + enum ieee80211_smps_mode smps; /* current smps mode */ +}; + +/* QoS structures */ +struct iwl_qos_info { + int qos_active; + struct iwl_qosparam_cmd def_qos_parm; +}; + +/** + * enum iwl_agg_state + * + * The state machine of the BA agreement establishment / tear down. + * These states relate to a specific RA / TID. + * + * @IWL_AGG_OFF: aggregation is not used + * @IWL_AGG_STARTING: aggregation are starting (between start and oper) + * @IWL_AGG_ON: aggregation session is up + * @IWL_EMPTYING_HW_QUEUE_ADDBA: establishing a BA session - waiting for the + * HW queue to be empty from packets for this RA /TID. + * @IWL_EMPTYING_HW_QUEUE_DELBA: tearing down a BA session - waiting for the + * HW queue to be empty from packets for this RA /TID. + */ +enum iwl_agg_state { + IWL_AGG_OFF = 0, + IWL_AGG_STARTING, + IWL_AGG_ON, + IWL_EMPTYING_HW_QUEUE_ADDBA, + IWL_EMPTYING_HW_QUEUE_DELBA, +}; + +/** + * struct iwl_ht_agg - aggregation state machine + + * This structs holds the states for the BA agreement establishment and tear + * down. It also holds the state during the BA session itself. This struct is + * duplicated for each RA / TID. + + * @rate_n_flags: Rate at which Tx was attempted. Holds the data between the + * Tx response (REPLY_TX), and the block ack notification + * (REPLY_COMPRESSED_BA). + * @state: state of the BA agreement establishment / tear down. + * @txq_id: Tx queue used by the BA session + * @ssn: the first packet to be sent in AGG HW queue in Tx AGG start flow, or + * the first packet to be sent in legacy HW queue in Tx AGG stop flow. + * Basically when next_reclaimed reaches ssn, we can tell mac80211 that + * we are ready to finish the Tx AGG stop / start flow. + * @wait_for_ba: Expect block-ack before next Tx reply + */ +struct iwl_ht_agg { + u32 rate_n_flags; + enum iwl_agg_state state; + u16 txq_id; + u16 ssn; + bool wait_for_ba; +}; + +/** + * struct iwl_tid_data - one for each RA / TID + + * This structs holds the states for each RA / TID. + + * @seq_number: the next WiFi sequence number to use + * @next_reclaimed: the WiFi sequence number of the next packet to be acked. + * This is basically (last acked packet++). + * @agg: aggregation state machine + */ +struct iwl_tid_data { + u16 seq_number; + u16 next_reclaimed; + struct iwl_ht_agg agg; +}; + +/* + * Structure should be accessed with sta_lock held. When station addition + * is in progress (IWL_STA_UCODE_INPROGRESS) it is possible to access only + * the commands (iwl_addsta_cmd and iwl_link_quality_cmd) without sta_lock + * held. + */ +struct iwl_station_entry { + struct iwl_addsta_cmd sta; + u8 used, ctxid; + struct iwl_link_quality_cmd *lq; +}; + +/* + * iwl_station_priv: Driver's private station information + * + * When mac80211 creates a station it reserves some space (hw->sta_data_size) + * in the structure for use by driver. This structure is places in that + * space. + */ +struct iwl_station_priv { + struct iwl_rxon_context *ctx; + struct iwl_lq_sta lq_sta; + atomic_t pending_frames; + bool client; + bool asleep; + u8 max_agg_bufsize; + u8 sta_id; +}; + +/** + * struct iwl_vif_priv - driver's private per-interface information + * + * When mac80211 allocates a virtual interface, it can allocate + * space for us to put data into. + */ +struct iwl_vif_priv { + struct iwl_rxon_context *ctx; + u8 ibss_bssid_sta_id; +}; + +struct iwl_sensitivity_ranges { + u16 min_nrg_cck; + + u16 nrg_th_cck; + u16 nrg_th_ofdm; + + u16 auto_corr_min_ofdm; + u16 auto_corr_min_ofdm_mrc; + u16 auto_corr_min_ofdm_x1; + u16 auto_corr_min_ofdm_mrc_x1; + + u16 auto_corr_max_ofdm; + u16 auto_corr_max_ofdm_mrc; + u16 auto_corr_max_ofdm_x1; + u16 auto_corr_max_ofdm_mrc_x1; + + u16 auto_corr_max_cck; + u16 auto_corr_max_cck_mrc; + u16 auto_corr_min_cck; + u16 auto_corr_min_cck_mrc; + + u16 barker_corr_th_min; + u16 barker_corr_th_min_mrc; + u16 nrg_th_cca; +}; + + +#define KELVIN_TO_CELSIUS(x) ((x)-273) +#define CELSIUS_TO_KELVIN(x) ((x)+273) + + +/****************************************************************************** + * + * Functions implemented in core module which are forward declared here + * for use by iwl-[4-5].c + * + * NOTE: The implementation of these functions are not hardware specific + * which is why they are in the core module files. + * + * Naming convention -- + * iwl_ <-- Is part of iwlwifi + * iwlXXXX_ <-- Hardware specific (implemented in iwl-XXXX.c for XXXX) + * + ****************************************************************************/ +extern void iwl_update_chain_flags(struct iwl_priv *priv); +extern const u8 iwl_bcast_addr[ETH_ALEN]; + +#define IWL_OPERATION_MODE_AUTO 0 +#define IWL_OPERATION_MODE_HT_ONLY 1 +#define IWL_OPERATION_MODE_MIXED 2 +#define IWL_OPERATION_MODE_20MHZ 3 + +#define TX_POWER_IWL_ILLEGAL_VOLTAGE -10000 + +/* Sensitivity and chain noise calibration */ +#define INITIALIZATION_VALUE 0xFFFF +#define IWL_CAL_NUM_BEACONS 16 +#define MAXIMUM_ALLOWED_PATHLOSS 15 + +#define CHAIN_NOISE_MAX_DELTA_GAIN_CODE 3 + +#define MAX_FA_OFDM 50 +#define MIN_FA_OFDM 5 +#define MAX_FA_CCK 50 +#define MIN_FA_CCK 5 + +#define AUTO_CORR_STEP_OFDM 1 + +#define AUTO_CORR_STEP_CCK 3 +#define AUTO_CORR_MAX_TH_CCK 160 + +#define NRG_DIFF 2 +#define NRG_STEP_CCK 2 +#define NRG_MARGIN 8 +#define MAX_NUMBER_CCK_NO_FA 100 + +#define AUTO_CORR_CCK_MIN_VAL_DEF (125) + +#define CHAIN_A 0 +#define CHAIN_B 1 +#define CHAIN_C 2 +#define CHAIN_NOISE_DELTA_GAIN_INIT_VAL 4 +#define ALL_BAND_FILTER 0xFF00 +#define IN_BAND_FILTER 0xFF +#define MIN_AVERAGE_NOISE_MAX_VALUE 0xFFFFFFFF + +#define NRG_NUM_PREV_STAT_L 20 +#define NUM_RX_CHAINS 3 + +enum iwlagn_false_alarm_state { + IWL_FA_TOO_MANY = 0, + IWL_FA_TOO_FEW = 1, + IWL_FA_GOOD_RANGE = 2, +}; + +enum iwlagn_chain_noise_state { + IWL_CHAIN_NOISE_ALIVE = 0, /* must be 0 */ + IWL_CHAIN_NOISE_ACCUMULATE, + IWL_CHAIN_NOISE_CALIBRATED, + IWL_CHAIN_NOISE_DONE, +}; + +/* Sensitivity calib data */ +struct iwl_sensitivity_data { + u32 auto_corr_ofdm; + u32 auto_corr_ofdm_mrc; + u32 auto_corr_ofdm_x1; + u32 auto_corr_ofdm_mrc_x1; + u32 auto_corr_cck; + u32 auto_corr_cck_mrc; + + u32 last_bad_plcp_cnt_ofdm; + u32 last_fa_cnt_ofdm; + u32 last_bad_plcp_cnt_cck; + u32 last_fa_cnt_cck; + + u32 nrg_curr_state; + u32 nrg_prev_state; + u32 nrg_value[10]; + u8 nrg_silence_rssi[NRG_NUM_PREV_STAT_L]; + u32 nrg_silence_ref; + u32 nrg_energy_idx; + u32 nrg_silence_idx; + u32 nrg_th_cck; + s32 nrg_auto_corr_silence_diff; + u32 num_in_cck_no_fa; + u32 nrg_th_ofdm; + + u16 barker_corr_th_min; + u16 barker_corr_th_min_mrc; + u16 nrg_th_cca; +}; + +/* Chain noise (differential Rx gain) calib data */ +struct iwl_chain_noise_data { + u32 active_chains; + u32 chain_noise_a; + u32 chain_noise_b; + u32 chain_noise_c; + u32 chain_signal_a; + u32 chain_signal_b; + u32 chain_signal_c; + u16 beacon_count; + u8 disconn_array[NUM_RX_CHAINS]; + u8 delta_gain_code[NUM_RX_CHAINS]; + u8 radio_write; + u8 state; +}; + +enum { + MEASUREMENT_READY = (1 << 0), + MEASUREMENT_ACTIVE = (1 << 1), +}; + +/* reply_tx_statistics (for _agn devices) */ +struct reply_tx_error_statistics { + u32 pp_delay; + u32 pp_few_bytes; + u32 pp_bt_prio; + u32 pp_quiet_period; + u32 pp_calc_ttak; + u32 int_crossed_retry; + u32 short_limit; + u32 long_limit; + u32 fifo_underrun; + u32 drain_flow; + u32 rfkill_flush; + u32 life_expire; + u32 dest_ps; + u32 host_abort; + u32 bt_retry; + u32 sta_invalid; + u32 frag_drop; + u32 tid_disable; + u32 fifo_flush; + u32 insuff_cf_poll; + u32 fail_hw_drop; + u32 sta_color_mismatch; + u32 unknown; +}; + +/* reply_agg_tx_statistics (for _agn devices) */ +struct reply_agg_tx_error_statistics { + u32 underrun; + u32 bt_prio; + u32 few_bytes; + u32 abort; + u32 last_sent_ttl; + u32 last_sent_try; + u32 last_sent_bt_kill; + u32 scd_query; + u32 bad_crc32; + u32 response; + u32 dump_tx; + u32 delay_tx; + u32 unknown; +}; + +/* + * schedule the timer to wake up every UCODE_TRACE_PERIOD milliseconds + * to perform continuous uCode event logging operation if enabled + */ +#define UCODE_TRACE_PERIOD (10) + +/* + * iwl_event_log: current uCode event log position + * + * @ucode_trace: enable/disable ucode continuous trace timer + * @num_wraps: how many times the event buffer wraps + * @next_entry: the entry just before the next one that uCode would fill + * @non_wraps_count: counter for no wrap detected when dump ucode events + * @wraps_once_count: counter for wrap once detected when dump ucode events + * @wraps_more_count: counter for wrap more than once detected + * when dump ucode events + */ +struct iwl_event_log { + bool ucode_trace; + u32 num_wraps; + u32 next_entry; + int non_wraps_count; + int wraps_once_count; + int wraps_more_count; +}; + +#define IWL_DELAY_NEXT_FORCE_RF_RESET (HZ*3) + +/* BT Antenna Coupling Threshold (dB) */ +#define IWL_BT_ANTENNA_COUPLING_THRESHOLD (35) + +/* Firmware reload counter and Timestamp */ +#define IWL_MIN_RELOAD_DURATION 1000 /* 1000 ms */ +#define IWL_MAX_CONTINUE_RELOAD_CNT 4 + + +struct iwl_rf_reset { + int reset_request_count; + int reset_success_count; + int reset_reject_count; + unsigned long last_reset_jiffies; +}; + +enum iwl_rxon_context_id { + IWL_RXON_CTX_BSS, + IWL_RXON_CTX_PAN, + + NUM_IWL_RXON_CTX +}; + +/* extend beacon time format bit shifting */ +/* + * for _agn devices + * bits 31:22 - extended + * bits 21:0 - interval + */ +#define IWLAGN_EXT_BEACON_TIME_POS 22 + +struct iwl_rxon_context { + struct ieee80211_vif *vif; + + u8 mcast_queue; + u8 ac_to_queue[IEEE80211_NUM_ACS]; + u8 ac_to_fifo[IEEE80211_NUM_ACS]; + + /* + * We could use the vif to indicate active, but we + * also need it to be active during disabling when + * we already removed the vif for type setting. + */ + bool always_active, is_active; + + bool ht_need_multiple_chains; + + enum iwl_rxon_context_id ctxid; + + u32 interface_modes, exclusive_interface_modes; + u8 unused_devtype, ap_devtype, ibss_devtype, station_devtype; + + /* + * We declare this const so it can only be + * changed via explicit cast within the + * routines that actually update the physical + * hardware. + */ + const struct iwl_rxon_cmd active; + struct iwl_rxon_cmd staging; + + struct iwl_rxon_time_cmd timing; + + struct iwl_qos_info qos_data; + + u8 bcast_sta_id, ap_sta_id; + + u8 rxon_cmd, rxon_assoc_cmd, rxon_timing_cmd; + u8 qos_cmd; + u8 wep_key_cmd; + + struct iwl_wep_key wep_keys[WEP_KEYS_MAX]; + u8 key_mapping_keys; + + __le32 station_flags; + + int beacon_int; + + struct { + bool non_gf_sta_present; + u8 protection; + bool enabled, is_40mhz; + u8 extension_chan_offset; + } ht; +}; + +enum iwl_scan_type { + IWL_SCAN_NORMAL, + IWL_SCAN_RADIO_RESET, + IWL_SCAN_ROC, +}; + +/** + * struct iwl_hw_params + * + * Holds the module parameters + * + * @tx_chains_num: Number of TX chains + * @rx_chains_num: Number of RX chains + * @ct_kill_threshold: temperature threshold - in hw dependent unit + * @ct_kill_exit_threshold: when to reeable the device - in hw dependent unit + * relevant for 1000, 6000 and up + * @struct iwl_sensitivity_ranges: range of sensitivity values + * @use_rts_for_aggregation: use rts/cts protection for HT traffic + */ +struct iwl_hw_params { + u8 tx_chains_num; + u8 rx_chains_num; + bool use_rts_for_aggregation; + u32 ct_kill_threshold; + u32 ct_kill_exit_threshold; + + const struct iwl_sensitivity_ranges *sens; +}; + +struct iwl_lib_ops { + /* set hw dependent parameters */ + void (*set_hw_params)(struct iwl_priv *priv); + int (*set_channel_switch)(struct iwl_priv *priv, + struct ieee80211_channel_switch *ch_switch); + /* device specific configuration */ + void (*nic_config)(struct iwl_priv *priv); + + /* temperature */ + void (*temperature)(struct iwl_priv *priv); +}; + +struct iwl_wipan_noa_data { + struct rcu_head rcu_head; + u32 length; + u8 data[]; +}; + +/* Calibration disabling bit mask */ +enum { + IWL_CALIB_ENABLE_ALL = 0, + + IWL_SENSITIVITY_CALIB_DISABLED = BIT(0), + IWL_CHAIN_NOISE_CALIB_DISABLED = BIT(1), + IWL_TX_POWER_CALIB_DISABLED = BIT(2), + + IWL_CALIB_DISABLE_ALL = 0xFFFFFFFF, +}; + +#define IWL_OP_MODE_GET_DVM(_iwl_op_mode) \ + ((struct iwl_priv *) ((_iwl_op_mode)->op_mode_specific)) + +#define IWL_MAC80211_GET_DVM(_hw) \ + ((struct iwl_priv *) ((struct iwl_op_mode *) \ + (_hw)->priv)->op_mode_specific) + +struct iwl_priv { + + struct iwl_trans *trans; + struct device *dev; /* for debug prints only */ + const struct iwl_cfg *cfg; + const struct iwl_fw *fw; + const struct iwl_lib_ops *lib; + unsigned long status; + + spinlock_t sta_lock; + struct mutex mutex; + + unsigned long transport_queue_stop; + bool passive_no_rx; +#define IWL_INVALID_MAC80211_QUEUE 0xff + u8 queue_to_mac80211[IWL_MAX_HW_QUEUES]; + atomic_t queue_stop_count[IWL_MAX_HW_QUEUES]; + + unsigned long agg_q_alloc[BITS_TO_LONGS(IWL_MAX_HW_QUEUES)]; + + /* ieee device used by generic ieee processing code */ + struct ieee80211_hw *hw; + + struct list_head calib_results; + + struct workqueue_struct *workqueue; + + struct iwl_hw_params hw_params; + + enum ieee80211_band band; + u8 valid_contexts; + + int (*rx_handlers[REPLY_MAX])(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); + + struct iwl_notif_wait_data notif_wait; + + /* spectrum measurement report caching */ + struct iwl_spectrum_notification measure_report; + u8 measurement_status; + +#define IWL_OWNERSHIP_DRIVER 0 +#define IWL_OWNERSHIP_TM 1 + u8 ucode_owner; + + /* ucode beacon time */ + u32 ucode_beacon_time; + int missed_beacon_threshold; + + /* track IBSS manager (last beacon) status */ + u32 ibss_manager; + + /* jiffies when last recovery from statistics was performed */ + unsigned long rx_statistics_jiffies; + + /*counters */ + u32 rx_handlers_stats[REPLY_MAX]; + + /* rf reset */ + struct iwl_rf_reset rf_reset; + + /* firmware reload counter and timestamp */ + unsigned long reload_jiffies; + int reload_count; + bool ucode_loaded; + bool init_ucode_run; /* Don't run init uCode again */ + + u8 plcp_delta_threshold; + + /* thermal calibration */ + s32 temperature; /* Celsius */ + s32 last_temperature; + + struct iwl_wipan_noa_data __rcu *noa_data; + + /* Scan related variables */ + unsigned long scan_start; + unsigned long scan_start_tsf; + void *scan_cmd; + enum ieee80211_band scan_band; + struct cfg80211_scan_request *scan_request; + struct ieee80211_vif *scan_vif; + enum iwl_scan_type scan_type; + u8 scan_tx_ant[IEEE80211_NUM_BANDS]; + u8 mgmt_tx_ant; + + /* max number of station keys */ + u8 sta_key_max_num; + + bool new_scan_threshold_behaviour; + + bool wowlan; + + /* EEPROM MAC addresses */ + struct mac_address addresses[2]; + + struct iwl_rxon_context contexts[NUM_IWL_RXON_CTX]; + + __le16 switch_channel; + + u8 start_calib; + struct iwl_sensitivity_data sensitivity_data; + struct iwl_chain_noise_data chain_noise_data; + __le16 sensitivity_tbl[HD_TABLE_SIZE]; + __le16 enhance_sensitivity_tbl[ENHANCE_HD_TABLE_ENTRIES]; + + struct iwl_ht_config current_ht_config; + + /* Rate scaling data */ + u8 retry_rate; + + int activity_timer_active; + + struct iwl_power_mgr power_data; + struct iwl_tt_mgmt thermal_throttle; + + /* station table variables */ + int num_stations; + struct iwl_station_entry stations[IWLAGN_STATION_COUNT]; + unsigned long ucode_key_table; + struct iwl_tid_data tid_data[IWLAGN_STATION_COUNT][IWL_MAX_TID_COUNT]; + atomic_t num_aux_in_flight; + + u8 mac80211_registered; + + /* Indication if ieee80211_ops->open has been called */ + u8 is_open; + + enum nl80211_iftype iw_mode; + + /* Last Rx'd beacon timestamp */ + u64 timestamp; + + struct { + __le32 flag; + struct statistics_general_common common; + struct statistics_rx_non_phy rx_non_phy; + struct statistics_rx_phy rx_ofdm; + struct statistics_rx_ht_phy rx_ofdm_ht; + struct statistics_rx_phy rx_cck; + struct statistics_tx tx; +#ifdef CPTCFG_IWLWIFI_DEBUGFS + struct statistics_bt_activity bt_activity; + __le32 num_bt_kills, accum_num_bt_kills; +#endif + spinlock_t lock; + } statistics; +#ifdef CPTCFG_IWLWIFI_DEBUGFS + struct { + struct statistics_general_common common; + struct statistics_rx_non_phy rx_non_phy; + struct statistics_rx_phy rx_ofdm; + struct statistics_rx_ht_phy rx_ofdm_ht; + struct statistics_rx_phy rx_cck; + struct statistics_tx tx; + struct statistics_bt_activity bt_activity; + } accum_stats, delta_stats, max_delta_stats; +#endif + + /* + * reporting the number of tids has AGG on. 0 means + * no AGGREGATION + */ + u8 agg_tids_count; + + struct iwl_rx_phy_res last_phy_res; + u32 ampdu_ref; + bool last_phy_res_valid; + + /* + * chain noise reset and gain commands are the + * two extra calibration commands follows the standard + * phy calibration commands + */ + u8 phy_calib_chain_noise_reset_cmd; + u8 phy_calib_chain_noise_gain_cmd; + + /* counts reply_tx error */ + struct reply_tx_error_statistics reply_tx_stats; + struct reply_agg_tx_error_statistics reply_agg_tx_stats; + + /* remain-on-channel offload support */ + struct ieee80211_channel *hw_roc_channel; + struct delayed_work hw_roc_disable_work; + int hw_roc_duration; + bool hw_roc_setup, hw_roc_start_notified; + + /* bt coex */ + u8 bt_enable_flag; + u8 bt_status; + u8 bt_traffic_load, last_bt_traffic_load; + bool bt_ch_announce; + bool bt_full_concurrent; + bool bt_ant_couple_ok; + __le32 kill_ack_mask; + __le32 kill_cts_mask; + __le16 bt_valid; + bool reduced_txpower; + u16 bt_on_thresh; + u16 bt_duration; + u16 dynamic_frag_thresh; + u8 bt_ci_compliance; + struct work_struct bt_traffic_change_work; + bool bt_enable_pspoll; + struct iwl_rxon_context *cur_rssi_ctx; + bool bt_is_sco; + + struct work_struct restart; + struct work_struct scan_completed; + struct work_struct abort_scan; + + struct work_struct beacon_update; + struct iwl_rxon_context *beacon_ctx; + struct sk_buff *beacon_skb; + void *beacon_cmd; + + struct work_struct tt_work; + struct work_struct ct_enter; + struct work_struct ct_exit; + struct work_struct start_internal_scan; + struct work_struct tx_flush; + struct work_struct bt_full_concurrency; + struct work_struct bt_runtime_config; + + struct delayed_work scan_check; + + /* TX Power settings */ + s8 tx_power_user_lmt; + s8 tx_power_next; + +#ifdef CPTCFG_IWLWIFI_DEBUGFS + /* debugfs */ + struct dentry *debugfs_dir; + u32 dbgfs_sram_offset, dbgfs_sram_len; + bool disable_ht40; + void *wowlan_sram; +#endif /* CPTCFG_IWLWIFI_DEBUGFS */ + + struct iwl_nvm_data *nvm_data; + /* eeprom blob for debugfs/testmode */ + u8 *eeprom_blob; + size_t eeprom_blob_size; + + struct work_struct txpower_work; + u32 calib_disabled; + struct work_struct run_time_calib_work; + struct timer_list statistics_periodic; + struct timer_list ucode_trace; + + struct iwl_event_log event_log; + + struct led_classdev led; + unsigned long blink_on, blink_off; + bool led_registered; + +#ifdef CPTCFG_IWLWIFI_DEVICE_TESTMODE + struct iwl_test tst; + u32 tm_fixed_rate; +#endif + + /* WoWLAN GTK rekey data */ + u8 kck[NL80211_KCK_LEN], kek[NL80211_KEK_LEN]; + __le64 replay_ctr; + __le16 last_seq_ctl; + bool have_rekey_data; + + /* device_pointers: pointers to ucode event tables */ + struct { + u32 error_event_table; + u32 log_event_table; + } device_pointers; + + /* indicator of loaded ucode image */ + enum iwl_ucode_type cur_ucode; +}; /*iwl_priv */ + +static inline struct iwl_rxon_context * +iwl_rxon_ctx_from_vif(struct ieee80211_vif *vif) +{ + struct iwl_vif_priv *vif_priv = (void *)vif->drv_priv; + + return vif_priv->ctx; +} + +#define for_each_context(priv, ctx) \ + for (ctx = &priv->contexts[IWL_RXON_CTX_BSS]; \ + ctx < &priv->contexts[NUM_IWL_RXON_CTX]; ctx++) \ + if (priv->valid_contexts & BIT(ctx->ctxid)) + +static inline int iwl_is_associated_ctx(struct iwl_rxon_context *ctx) +{ + return (ctx->active.filter_flags & RXON_FILTER_ASSOC_MSK) ? 1 : 0; +} + +static inline int iwl_is_associated(struct iwl_priv *priv, + enum iwl_rxon_context_id ctxid) +{ + return iwl_is_associated_ctx(&priv->contexts[ctxid]); +} + +static inline int iwl_is_any_associated(struct iwl_priv *priv) +{ + struct iwl_rxon_context *ctx; + for_each_context(priv, ctx) + if (iwl_is_associated_ctx(ctx)) + return true; + return false; +} + +#endif /* __iwl_dev_h__ */ diff --git a/drivers/net/wireless/iwlwifi/dvm/devices.c b/drivers/net/wireless/iwlwifi/dvm/devices.c new file mode 100644 index 0000000..c48907c --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/devices.c @@ -0,0 +1,599 @@ +/****************************************************************************** + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +/* + * DVM device-specific data & functions + */ +#include "iwl-io.h" +#include "iwl-prph.h" +#include "iwl-eeprom-parse.h" + +#include "agn.h" +#include "dev.h" +#include "commands.h" + + +/* + * 1000 series + * =========== + */ + +/* + * For 1000, use advance thermal throttling critical temperature threshold, + * but legacy thermal management implementation for now. + * This is for the reason of 1000 uCode using advance thermal throttling API + * but not implement ct_kill_exit based on ct_kill exit temperature + * so the thermal throttling will still based on legacy thermal throttling + * management. + * The code here need to be modified once 1000 uCode has the advanced thermal + * throttling algorithm in place + */ +static void iwl1000_set_ct_threshold(struct iwl_priv *priv) +{ + /* want Celsius */ + priv->hw_params.ct_kill_threshold = CT_KILL_THRESHOLD_LEGACY; + priv->hw_params.ct_kill_exit_threshold = CT_KILL_EXIT_THRESHOLD; +} + +/* NIC configuration for 1000 series */ +static void iwl1000_nic_config(struct iwl_priv *priv) +{ + /* Setting digital SVR for 1000 card to 1.32V */ + /* locking is acquired in iwl_set_bits_mask_prph() function */ + iwl_set_bits_mask_prph(priv->trans, APMG_DIGITAL_SVR_REG, + APMG_SVR_DIGITAL_VOLTAGE_1_32, + ~APMG_SVR_VOLTAGE_CONFIG_BIT_MSK); +} + +/** + * iwl_beacon_time_mask_low - mask of lower 32 bit of beacon time + * @priv -- pointer to iwl_priv data structure + * @tsf_bits -- number of bits need to shift for masking) + */ +static inline u32 iwl_beacon_time_mask_low(struct iwl_priv *priv, + u16 tsf_bits) +{ + return (1 << tsf_bits) - 1; +} + +/** + * iwl_beacon_time_mask_high - mask of higher 32 bit of beacon time + * @priv -- pointer to iwl_priv data structure + * @tsf_bits -- number of bits need to shift for masking) + */ +static inline u32 iwl_beacon_time_mask_high(struct iwl_priv *priv, + u16 tsf_bits) +{ + return ((1 << (32 - tsf_bits)) - 1) << tsf_bits; +} + +/* + * extended beacon time format + * time in usec will be changed into a 32-bit value in extended:internal format + * the extended part is the beacon counts + * the internal part is the time in usec within one beacon interval + */ +static u32 iwl_usecs_to_beacons(struct iwl_priv *priv, u32 usec, + u32 beacon_interval) +{ + u32 quot; + u32 rem; + u32 interval = beacon_interval * TIME_UNIT; + + if (!interval || !usec) + return 0; + + quot = (usec / interval) & + (iwl_beacon_time_mask_high(priv, IWLAGN_EXT_BEACON_TIME_POS) >> + IWLAGN_EXT_BEACON_TIME_POS); + rem = (usec % interval) & iwl_beacon_time_mask_low(priv, + IWLAGN_EXT_BEACON_TIME_POS); + + return (quot << IWLAGN_EXT_BEACON_TIME_POS) + rem; +} + +/* base is usually what we get from ucode with each received frame, + * the same as HW timer counter counting down + */ +static __le32 iwl_add_beacon_time(struct iwl_priv *priv, u32 base, + u32 addon, u32 beacon_interval) +{ + u32 base_low = base & iwl_beacon_time_mask_low(priv, + IWLAGN_EXT_BEACON_TIME_POS); + u32 addon_low = addon & iwl_beacon_time_mask_low(priv, + IWLAGN_EXT_BEACON_TIME_POS); + u32 interval = beacon_interval * TIME_UNIT; + u32 res = (base & iwl_beacon_time_mask_high(priv, + IWLAGN_EXT_BEACON_TIME_POS)) + + (addon & iwl_beacon_time_mask_high(priv, + IWLAGN_EXT_BEACON_TIME_POS)); + + if (base_low > addon_low) + res += base_low - addon_low; + else if (base_low < addon_low) { + res += interval + base_low - addon_low; + res += (1 << IWLAGN_EXT_BEACON_TIME_POS); + } else + res += (1 << IWLAGN_EXT_BEACON_TIME_POS); + + return cpu_to_le32(res); +} + +static const struct iwl_sensitivity_ranges iwl1000_sensitivity = { + .min_nrg_cck = 95, + .auto_corr_min_ofdm = 90, + .auto_corr_min_ofdm_mrc = 170, + .auto_corr_min_ofdm_x1 = 120, + .auto_corr_min_ofdm_mrc_x1 = 240, + + .auto_corr_max_ofdm = 120, + .auto_corr_max_ofdm_mrc = 210, + .auto_corr_max_ofdm_x1 = 155, + .auto_corr_max_ofdm_mrc_x1 = 290, + + .auto_corr_min_cck = 125, + .auto_corr_max_cck = 200, + .auto_corr_min_cck_mrc = 170, + .auto_corr_max_cck_mrc = 400, + .nrg_th_cck = 95, + .nrg_th_ofdm = 95, + + .barker_corr_th_min = 190, + .barker_corr_th_min_mrc = 390, + .nrg_th_cca = 62, +}; + +static void iwl1000_hw_set_hw_params(struct iwl_priv *priv) +{ + iwl1000_set_ct_threshold(priv); + + /* Set initial sensitivity parameters */ + priv->hw_params.sens = &iwl1000_sensitivity; +} + +struct iwl_lib_ops iwl1000_lib = { + .set_hw_params = iwl1000_hw_set_hw_params, + .nic_config = iwl1000_nic_config, + .temperature = iwlagn_temperature, +}; + + +/* + * 2000 series + * =========== + */ + +static void iwl2000_set_ct_threshold(struct iwl_priv *priv) +{ + /* want Celsius */ + priv->hw_params.ct_kill_threshold = CT_KILL_THRESHOLD; + priv->hw_params.ct_kill_exit_threshold = CT_KILL_EXIT_THRESHOLD; +} + +/* NIC configuration for 2000 series */ +static void iwl2000_nic_config(struct iwl_priv *priv) +{ + iwl_set_bit(priv->trans, CSR_GP_DRIVER_REG, + CSR_GP_DRIVER_REG_BIT_RADIO_IQ_INVER); +} + +static const struct iwl_sensitivity_ranges iwl2000_sensitivity = { + .min_nrg_cck = 97, + .auto_corr_min_ofdm = 80, + .auto_corr_min_ofdm_mrc = 128, + .auto_corr_min_ofdm_x1 = 105, + .auto_corr_min_ofdm_mrc_x1 = 192, + + .auto_corr_max_ofdm = 145, + .auto_corr_max_ofdm_mrc = 232, + .auto_corr_max_ofdm_x1 = 110, + .auto_corr_max_ofdm_mrc_x1 = 232, + + .auto_corr_min_cck = 125, + .auto_corr_max_cck = 175, + .auto_corr_min_cck_mrc = 160, + .auto_corr_max_cck_mrc = 310, + .nrg_th_cck = 97, + .nrg_th_ofdm = 100, + + .barker_corr_th_min = 190, + .barker_corr_th_min_mrc = 390, + .nrg_th_cca = 62, +}; + +static void iwl2000_hw_set_hw_params(struct iwl_priv *priv) +{ + iwl2000_set_ct_threshold(priv); + + /* Set initial sensitivity parameters */ + priv->hw_params.sens = &iwl2000_sensitivity; +} + +struct iwl_lib_ops iwl2000_lib = { + .set_hw_params = iwl2000_hw_set_hw_params, + .nic_config = iwl2000_nic_config, + .temperature = iwlagn_temperature, +}; + +struct iwl_lib_ops iwl2030_lib = { + .set_hw_params = iwl2000_hw_set_hw_params, + .nic_config = iwl2000_nic_config, + .temperature = iwlagn_temperature, +}; + +/* + * 5000 series + * =========== + */ + +/* NIC configuration for 5000 series */ +static const struct iwl_sensitivity_ranges iwl5000_sensitivity = { + .min_nrg_cck = 100, + .auto_corr_min_ofdm = 90, + .auto_corr_min_ofdm_mrc = 170, + .auto_corr_min_ofdm_x1 = 105, + .auto_corr_min_ofdm_mrc_x1 = 220, + + .auto_corr_max_ofdm = 120, + .auto_corr_max_ofdm_mrc = 210, + .auto_corr_max_ofdm_x1 = 120, + .auto_corr_max_ofdm_mrc_x1 = 240, + + .auto_corr_min_cck = 125, + .auto_corr_max_cck = 200, + .auto_corr_min_cck_mrc = 200, + .auto_corr_max_cck_mrc = 400, + .nrg_th_cck = 100, + .nrg_th_ofdm = 100, + + .barker_corr_th_min = 190, + .barker_corr_th_min_mrc = 390, + .nrg_th_cca = 62, +}; + +static struct iwl_sensitivity_ranges iwl5150_sensitivity = { + .min_nrg_cck = 95, + .auto_corr_min_ofdm = 90, + .auto_corr_min_ofdm_mrc = 170, + .auto_corr_min_ofdm_x1 = 105, + .auto_corr_min_ofdm_mrc_x1 = 220, + + .auto_corr_max_ofdm = 120, + .auto_corr_max_ofdm_mrc = 210, + /* max = min for performance bug in 5150 DSP */ + .auto_corr_max_ofdm_x1 = 105, + .auto_corr_max_ofdm_mrc_x1 = 220, + + .auto_corr_min_cck = 125, + .auto_corr_max_cck = 200, + .auto_corr_min_cck_mrc = 170, + .auto_corr_max_cck_mrc = 400, + .nrg_th_cck = 95, + .nrg_th_ofdm = 95, + + .barker_corr_th_min = 190, + .barker_corr_th_min_mrc = 390, + .nrg_th_cca = 62, +}; + +#define IWL_5150_VOLTAGE_TO_TEMPERATURE_COEFF (-5) + +static s32 iwl_temp_calib_to_offset(struct iwl_priv *priv) +{ + u16 temperature, voltage; + + temperature = le16_to_cpu(priv->nvm_data->kelvin_temperature); + voltage = le16_to_cpu(priv->nvm_data->kelvin_voltage); + + /* offset = temp - volt / coeff */ + return (s32)(temperature - + voltage / IWL_5150_VOLTAGE_TO_TEMPERATURE_COEFF); +} + +static void iwl5150_set_ct_threshold(struct iwl_priv *priv) +{ + const s32 volt2temp_coef = IWL_5150_VOLTAGE_TO_TEMPERATURE_COEFF; + s32 threshold = (s32)CELSIUS_TO_KELVIN(CT_KILL_THRESHOLD_LEGACY) - + iwl_temp_calib_to_offset(priv); + + priv->hw_params.ct_kill_threshold = threshold * volt2temp_coef; +} + +static void iwl5000_set_ct_threshold(struct iwl_priv *priv) +{ + /* want Celsius */ + priv->hw_params.ct_kill_threshold = CT_KILL_THRESHOLD_LEGACY; +} + +static void iwl5000_hw_set_hw_params(struct iwl_priv *priv) +{ + iwl5000_set_ct_threshold(priv); + + /* Set initial sensitivity parameters */ + priv->hw_params.sens = &iwl5000_sensitivity; +} + +static void iwl5150_hw_set_hw_params(struct iwl_priv *priv) +{ + iwl5150_set_ct_threshold(priv); + + /* Set initial sensitivity parameters */ + priv->hw_params.sens = &iwl5150_sensitivity; +} + +static void iwl5150_temperature(struct iwl_priv *priv) +{ + u32 vt = 0; + s32 offset = iwl_temp_calib_to_offset(priv); + + vt = le32_to_cpu(priv->statistics.common.temperature); + vt = vt / IWL_5150_VOLTAGE_TO_TEMPERATURE_COEFF + offset; + /* now vt hold the temperature in Kelvin */ + priv->temperature = KELVIN_TO_CELSIUS(vt); + iwl_tt_handler(priv); +} + +static int iwl5000_hw_channel_switch(struct iwl_priv *priv, + struct ieee80211_channel_switch *ch_switch) +{ + /* + * MULTI-FIXME + * See iwlagn_mac_channel_switch. + */ + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + struct iwl5000_channel_switch_cmd cmd; + u32 switch_time_in_usec, ucode_switch_time; + u16 ch; + u32 tsf_low; + u8 switch_count; + u16 beacon_interval = le16_to_cpu(ctx->timing.beacon_interval); + struct ieee80211_vif *vif = ctx->vif; + struct iwl_host_cmd hcmd = { + .id = REPLY_CHANNEL_SWITCH, + .len = { sizeof(cmd), }, + .flags = CMD_SYNC, + .data = { &cmd, }, + }; + + cmd.band = priv->band == IEEE80211_BAND_2GHZ; + ch = ch_switch->chandef.chan->hw_value; + IWL_DEBUG_11H(priv, "channel switch from %d to %d\n", + ctx->active.channel, ch); + cmd.channel = cpu_to_le16(ch); + cmd.rxon_flags = ctx->staging.flags; + cmd.rxon_filter_flags = ctx->staging.filter_flags; + switch_count = ch_switch->count; + tsf_low = ch_switch->timestamp & 0x0ffffffff; + /* + * calculate the ucode channel switch time + * adding TSF as one of the factor for when to switch + */ + if ((priv->ucode_beacon_time > tsf_low) && beacon_interval) { + if (switch_count > ((priv->ucode_beacon_time - tsf_low) / + beacon_interval)) { + switch_count -= (priv->ucode_beacon_time - + tsf_low) / beacon_interval; + } else + switch_count = 0; + } + if (switch_count <= 1) + cmd.switch_time = cpu_to_le32(priv->ucode_beacon_time); + else { + switch_time_in_usec = + vif->bss_conf.beacon_int * switch_count * TIME_UNIT; + ucode_switch_time = iwl_usecs_to_beacons(priv, + switch_time_in_usec, + beacon_interval); + cmd.switch_time = iwl_add_beacon_time(priv, + priv->ucode_beacon_time, + ucode_switch_time, + beacon_interval); + } + IWL_DEBUG_11H(priv, "uCode time for the switch is 0x%x\n", + cmd.switch_time); + cmd.expect_beacon = + ch_switch->chandef.chan->flags & IEEE80211_CHAN_RADAR; + + return iwl_dvm_send_cmd(priv, &hcmd); +} + +struct iwl_lib_ops iwl5000_lib = { + .set_hw_params = iwl5000_hw_set_hw_params, + .set_channel_switch = iwl5000_hw_channel_switch, + .temperature = iwlagn_temperature, +}; + +struct iwl_lib_ops iwl5150_lib = { + .set_hw_params = iwl5150_hw_set_hw_params, + .set_channel_switch = iwl5000_hw_channel_switch, + .temperature = iwl5150_temperature, +}; + + + +/* + * 6000 series + * =========== + */ + +static void iwl6000_set_ct_threshold(struct iwl_priv *priv) +{ + /* want Celsius */ + priv->hw_params.ct_kill_threshold = CT_KILL_THRESHOLD; + priv->hw_params.ct_kill_exit_threshold = CT_KILL_EXIT_THRESHOLD; +} + +/* NIC configuration for 6000 series */ +static void iwl6000_nic_config(struct iwl_priv *priv) +{ + switch (priv->cfg->device_family) { + case IWL_DEVICE_FAMILY_6005: + case IWL_DEVICE_FAMILY_6030: + case IWL_DEVICE_FAMILY_6000: + break; + case IWL_DEVICE_FAMILY_6000i: + /* 2x2 IPA phy type */ + iwl_write32(priv->trans, CSR_GP_DRIVER_REG, + CSR_GP_DRIVER_REG_BIT_RADIO_SKU_2x2_IPA); + break; + case IWL_DEVICE_FAMILY_6050: + /* Indicate calibration version to uCode. */ + if (priv->nvm_data->calib_version >= 6) + iwl_set_bit(priv->trans, CSR_GP_DRIVER_REG, + CSR_GP_DRIVER_REG_BIT_CALIB_VERSION6); + break; + case IWL_DEVICE_FAMILY_6150: + /* Indicate calibration version to uCode. */ + if (priv->nvm_data->calib_version >= 6) + iwl_set_bit(priv->trans, CSR_GP_DRIVER_REG, + CSR_GP_DRIVER_REG_BIT_CALIB_VERSION6); + iwl_set_bit(priv->trans, CSR_GP_DRIVER_REG, + CSR_GP_DRIVER_REG_BIT_6050_1x2); + break; + default: + WARN_ON(1); + } +} + +static const struct iwl_sensitivity_ranges iwl6000_sensitivity = { + .min_nrg_cck = 110, + .auto_corr_min_ofdm = 80, + .auto_corr_min_ofdm_mrc = 128, + .auto_corr_min_ofdm_x1 = 105, + .auto_corr_min_ofdm_mrc_x1 = 192, + + .auto_corr_max_ofdm = 145, + .auto_corr_max_ofdm_mrc = 232, + .auto_corr_max_ofdm_x1 = 110, + .auto_corr_max_ofdm_mrc_x1 = 232, + + .auto_corr_min_cck = 125, + .auto_corr_max_cck = 175, + .auto_corr_min_cck_mrc = 160, + .auto_corr_max_cck_mrc = 310, + .nrg_th_cck = 110, + .nrg_th_ofdm = 110, + + .barker_corr_th_min = 190, + .barker_corr_th_min_mrc = 336, + .nrg_th_cca = 62, +}; + +static void iwl6000_hw_set_hw_params(struct iwl_priv *priv) +{ + iwl6000_set_ct_threshold(priv); + + /* Set initial sensitivity parameters */ + priv->hw_params.sens = &iwl6000_sensitivity; + +} + +static int iwl6000_hw_channel_switch(struct iwl_priv *priv, + struct ieee80211_channel_switch *ch_switch) +{ + /* + * MULTI-FIXME + * See iwlagn_mac_channel_switch. + */ + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + struct iwl6000_channel_switch_cmd *cmd; + u32 switch_time_in_usec, ucode_switch_time; + u16 ch; + u32 tsf_low; + u8 switch_count; + u16 beacon_interval = le16_to_cpu(ctx->timing.beacon_interval); + struct ieee80211_vif *vif = ctx->vif; + struct iwl_host_cmd hcmd = { + .id = REPLY_CHANNEL_SWITCH, + .len = { sizeof(*cmd), }, + .flags = CMD_SYNC, + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + }; + int err; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + hcmd.data[0] = cmd; + + cmd->band = priv->band == IEEE80211_BAND_2GHZ; + ch = ch_switch->chandef.chan->hw_value; + IWL_DEBUG_11H(priv, "channel switch from %u to %u\n", + ctx->active.channel, ch); + cmd->channel = cpu_to_le16(ch); + cmd->rxon_flags = ctx->staging.flags; + cmd->rxon_filter_flags = ctx->staging.filter_flags; + switch_count = ch_switch->count; + tsf_low = ch_switch->timestamp & 0x0ffffffff; + /* + * calculate the ucode channel switch time + * adding TSF as one of the factor for when to switch + */ + if ((priv->ucode_beacon_time > tsf_low) && beacon_interval) { + if (switch_count > ((priv->ucode_beacon_time - tsf_low) / + beacon_interval)) { + switch_count -= (priv->ucode_beacon_time - + tsf_low) / beacon_interval; + } else + switch_count = 0; + } + if (switch_count <= 1) + cmd->switch_time = cpu_to_le32(priv->ucode_beacon_time); + else { + switch_time_in_usec = + vif->bss_conf.beacon_int * switch_count * TIME_UNIT; + ucode_switch_time = iwl_usecs_to_beacons(priv, + switch_time_in_usec, + beacon_interval); + cmd->switch_time = iwl_add_beacon_time(priv, + priv->ucode_beacon_time, + ucode_switch_time, + beacon_interval); + } + IWL_DEBUG_11H(priv, "uCode time for the switch is 0x%x\n", + cmd->switch_time); + cmd->expect_beacon = + ch_switch->chandef.chan->flags & IEEE80211_CHAN_RADAR; + + err = iwl_dvm_send_cmd(priv, &hcmd); + kfree(cmd); + return err; +} + +struct iwl_lib_ops iwl6000_lib = { + .set_hw_params = iwl6000_hw_set_hw_params, + .set_channel_switch = iwl6000_hw_channel_switch, + .nic_config = iwl6000_nic_config, + .temperature = iwlagn_temperature, +}; + +struct iwl_lib_ops iwl6030_lib = { + .set_hw_params = iwl6000_hw_set_hw_params, + .set_channel_switch = iwl6000_hw_channel_switch, + .nic_config = iwl6000_nic_config, + .temperature = iwlagn_temperature, +}; diff --git a/drivers/net/wireless/iwlwifi/dvm/led.c b/drivers/net/wireless/iwlwifi/dvm/led.c new file mode 100644 index 0000000..b26b30f --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/led.c @@ -0,0 +1,230 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iwl-io.h" +#include "iwl-trans.h" +#include "iwl-modparams.h" +#include "dev.h" +#include "agn.h" + +/* Throughput OFF time(ms) ON time (ms) + * >300 25 25 + * >200 to 300 40 40 + * >100 to 200 55 55 + * >70 to 100 65 65 + * >50 to 70 75 75 + * >20 to 50 85 85 + * >10 to 20 95 95 + * >5 to 10 110 110 + * >1 to 5 130 130 + * >0 to 1 167 167 + * <=0 SOLID ON + */ +static const struct ieee80211_tpt_blink iwl_blink[] = { + { .throughput = 0, .blink_time = 334 }, + { .throughput = 1 * 1024 - 1, .blink_time = 260 }, + { .throughput = 5 * 1024 - 1, .blink_time = 220 }, + { .throughput = 10 * 1024 - 1, .blink_time = 190 }, + { .throughput = 20 * 1024 - 1, .blink_time = 170 }, + { .throughput = 50 * 1024 - 1, .blink_time = 150 }, + { .throughput = 70 * 1024 - 1, .blink_time = 130 }, + { .throughput = 100 * 1024 - 1, .blink_time = 110 }, + { .throughput = 200 * 1024 - 1, .blink_time = 80 }, + { .throughput = 300 * 1024 - 1, .blink_time = 50 }, +}; + +/* Set led register off */ +void iwlagn_led_enable(struct iwl_priv *priv) +{ + iwl_write32(priv->trans, CSR_LED_REG, CSR_LED_REG_TURN_ON); +} + +/* + * Adjust led blink rate to compensate on a MAC Clock difference on every HW + * Led blink rate analysis showed an average deviation of 20% on 5000 series + * and up. + * Need to compensate on the led on/off time per HW according to the deviation + * to achieve the desired led frequency + * The calculation is: (100-averageDeviation)/100 * blinkTime + * For code efficiency the calculation will be: + * compensation = (100 - averageDeviation) * 64 / 100 + * NewBlinkTime = (compensation * BlinkTime) / 64 + */ +static inline u8 iwl_blink_compensation(struct iwl_priv *priv, + u8 time, u16 compensation) +{ + if (!compensation) { + IWL_ERR(priv, "undefined blink compensation: " + "use pre-defined blinking time\n"); + return time; + } + + return (u8)((time * compensation) >> 6); +} + +static int iwl_send_led_cmd(struct iwl_priv *priv, struct iwl_led_cmd *led_cmd) +{ + struct iwl_host_cmd cmd = { + .id = REPLY_LEDS_CMD, + .len = { sizeof(struct iwl_led_cmd), }, + .data = { led_cmd, }, + .flags = CMD_ASYNC, + }; + u32 reg; + + reg = iwl_read32(priv->trans, CSR_LED_REG); + if (reg != (reg & CSR_LED_BSM_CTRL_MSK)) + iwl_write32(priv->trans, CSR_LED_REG, + reg & CSR_LED_BSM_CTRL_MSK); + + return iwl_dvm_send_cmd(priv, &cmd); +} + +/* Set led pattern command */ +static int iwl_led_cmd(struct iwl_priv *priv, + unsigned long on, + unsigned long off) +{ + struct iwl_led_cmd led_cmd = { + .id = IWL_LED_LINK, + .interval = IWL_DEF_LED_INTRVL + }; + int ret; + + if (!test_bit(STATUS_READY, &priv->status)) + return -EBUSY; + + if (priv->blink_on == on && priv->blink_off == off) + return 0; + + if (off == 0) { + /* led is SOLID_ON */ + on = IWL_LED_SOLID; + } + + IWL_DEBUG_LED(priv, "Led blink time compensation=%u\n", + priv->cfg->base_params->led_compensation); + led_cmd.on = iwl_blink_compensation(priv, on, + priv->cfg->base_params->led_compensation); + led_cmd.off = iwl_blink_compensation(priv, off, + priv->cfg->base_params->led_compensation); + + ret = iwl_send_led_cmd(priv, &led_cmd); + if (!ret) { + priv->blink_on = on; + priv->blink_off = off; + } + return ret; +} + +static void iwl_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct iwl_priv *priv = container_of(led_cdev, struct iwl_priv, led); + unsigned long on = 0; + + if (brightness > 0) + on = IWL_LED_SOLID; + + iwl_led_cmd(priv, on, 0); +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)) +static int iwl_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct iwl_priv *priv = container_of(led_cdev, struct iwl_priv, led); + + return iwl_led_cmd(priv, *delay_on, *delay_off); +} +#endif + +void iwl_leds_init(struct iwl_priv *priv) +{ + int mode = iwlwifi_mod_params.led_mode; + int ret; + + if (mode == IWL_LED_DISABLE) { + IWL_INFO(priv, "Led disabled\n"); + return; + } + if (mode == IWL_LED_DEFAULT) + mode = priv->cfg->led_mode; + + priv->led.name = kasprintf(GFP_KERNEL, "%s-led", + wiphy_name(priv->hw->wiphy)); + priv->led.brightness_set = iwl_led_brightness_set; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)) + priv->led.blink_set = iwl_led_blink_set; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) + priv->led.max_brightness = 1; +#endif + + switch (mode) { + case IWL_LED_DEFAULT: + WARN_ON(1); + break; + case IWL_LED_BLINK: + priv->led.default_trigger = + ieee80211_create_tpt_led_trigger(priv->hw, + IEEE80211_TPT_LEDTRIG_FL_CONNECTED, + iwl_blink, ARRAY_SIZE(iwl_blink)); + break; + case IWL_LED_RF_STATE: + priv->led.default_trigger = + ieee80211_get_radio_led_name(priv->hw); + break; + } + + ret = led_classdev_register(priv->trans->dev, &priv->led); + if (ret) { + kfree(priv->led.name); + return; + } + + priv->led_registered = true; +} + +void iwl_leds_exit(struct iwl_priv *priv) +{ + if (!priv->led_registered) + return; + + led_classdev_unregister(&priv->led); + kfree(priv->led.name); +} diff --git a/drivers/net/wireless/iwlwifi/dvm/led.h b/drivers/net/wireless/iwlwifi/dvm/led.h new file mode 100644 index 0000000..8749dcf --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/led.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#ifndef __iwl_leds_h__ +#define __iwl_leds_h__ + + +struct iwl_priv; + +#define IWL_LED_SOLID 11 +#define IWL_DEF_LED_INTRVL cpu_to_le32(1000) + +#define IWL_LED_ACTIVITY (0<<1) +#define IWL_LED_LINK (1<<1) + +void iwlagn_led_enable(struct iwl_priv *priv); +void iwl_leds_init(struct iwl_priv *priv); +void iwl_leds_exit(struct iwl_priv *priv); + +#endif /* __iwl_leds_h__ */ diff --git a/drivers/net/wireless/iwlwifi/dvm/lib.c b/drivers/net/wireless/iwlwifi/dvm/lib.c new file mode 100644 index 0000000..54f5533 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/lib.c @@ -0,0 +1,1311 @@ +/****************************************************************************** + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ +#include +#include +#include +#include +#include +#include + +#include "iwl-io.h" +#include "iwl-agn-hw.h" +#include "iwl-trans.h" +#include "iwl-modparams.h" + +#include "dev.h" +#include "agn.h" + +int iwlagn_hw_valid_rtc_data_addr(u32 addr) +{ + return (addr >= IWLAGN_RTC_DATA_LOWER_BOUND) && + (addr < IWLAGN_RTC_DATA_UPPER_BOUND); +} + +int iwlagn_send_tx_power(struct iwl_priv *priv) +{ + struct iwlagn_tx_power_dbm_cmd tx_power_cmd; + u8 tx_ant_cfg_cmd; + + if (WARN_ONCE(test_bit(STATUS_SCAN_HW, &priv->status), + "TX Power requested while scanning!\n")) + return -EAGAIN; + + /* half dBm need to multiply */ + tx_power_cmd.global_lmt = (s8)(2 * priv->tx_power_user_lmt); + + if (tx_power_cmd.global_lmt > priv->nvm_data->max_tx_pwr_half_dbm) { + /* + * For the newer devices which using enhanced/extend tx power + * table in EEPROM, the format is in half dBm. driver need to + * convert to dBm format before report to mac80211. + * By doing so, there is a possibility of 1/2 dBm resolution + * lost. driver will perform "round-up" operation before + * reporting, but it will cause 1/2 dBm tx power over the + * regulatory limit. Perform the checking here, if the + * "tx_power_user_lmt" is higher than EEPROM value (in + * half-dBm format), lower the tx power based on EEPROM + */ + tx_power_cmd.global_lmt = + priv->nvm_data->max_tx_pwr_half_dbm; + } + tx_power_cmd.flags = IWLAGN_TX_POWER_NO_CLOSED; + tx_power_cmd.srv_chan_lmt = IWLAGN_TX_POWER_AUTO; + + if (IWL_UCODE_API(priv->fw->ucode_ver) == 1) + tx_ant_cfg_cmd = REPLY_TX_POWER_DBM_CMD_V1; + else + tx_ant_cfg_cmd = REPLY_TX_POWER_DBM_CMD; + + return iwl_dvm_send_cmd_pdu(priv, tx_ant_cfg_cmd, CMD_SYNC, + sizeof(tx_power_cmd), &tx_power_cmd); +} + +void iwlagn_temperature(struct iwl_priv *priv) +{ + lockdep_assert_held(&priv->statistics.lock); + + /* store temperature from correct statistics (in Celsius) */ + priv->temperature = le32_to_cpu(priv->statistics.common.temperature); + iwl_tt_handler(priv); +} + +int iwlagn_hwrate_to_mac80211_idx(u32 rate_n_flags, enum ieee80211_band band) +{ + int idx = 0; + int band_offset = 0; + + /* HT rate format: mac80211 wants an MCS number, which is just LSB */ + if (rate_n_flags & RATE_MCS_HT_MSK) { + idx = (rate_n_flags & 0xff); + return idx; + /* Legacy rate format, search for match in table */ + } else { + if (band == IEEE80211_BAND_5GHZ) + band_offset = IWL_FIRST_OFDM_RATE; + for (idx = band_offset; idx < IWL_RATE_COUNT_LEGACY; idx++) + if (iwl_rates[idx].plcp == (rate_n_flags & 0xFF)) + return idx - band_offset; + } + + return -1; +} + +int iwlagn_manage_ibss_station(struct iwl_priv *priv, + struct ieee80211_vif *vif, bool add) +{ + struct iwl_vif_priv *vif_priv = (void *)vif->drv_priv; + + if (add) + return iwlagn_add_bssid_station(priv, vif_priv->ctx, + vif->bss_conf.bssid, + &vif_priv->ibss_bssid_sta_id); + return iwl_remove_station(priv, vif_priv->ibss_bssid_sta_id, + vif->bss_conf.bssid); +} + +/** + * iwlagn_txfifo_flush: send REPLY_TXFIFO_FLUSH command to uCode + * + * pre-requirements: + * 1. acquire mutex before calling + * 2. make sure rf is on and not in exit state + */ +int iwlagn_txfifo_flush(struct iwl_priv *priv, u32 scd_q_msk) +{ + struct iwl_txfifo_flush_cmd flush_cmd; + struct iwl_host_cmd cmd = { + .id = REPLY_TXFIFO_FLUSH, + .len = { sizeof(struct iwl_txfifo_flush_cmd), }, + .flags = CMD_SYNC, + .data = { &flush_cmd, }, + }; + + memset(&flush_cmd, 0, sizeof(flush_cmd)); + + flush_cmd.queue_control = IWL_SCD_VO_MSK | IWL_SCD_VI_MSK | + IWL_SCD_BE_MSK | IWL_SCD_BK_MSK | + IWL_SCD_MGMT_MSK; + if ((priv->valid_contexts != BIT(IWL_RXON_CTX_BSS))) + flush_cmd.queue_control |= IWL_PAN_SCD_VO_MSK | + IWL_PAN_SCD_VI_MSK | + IWL_PAN_SCD_BE_MSK | + IWL_PAN_SCD_BK_MSK | + IWL_PAN_SCD_MGMT_MSK | + IWL_PAN_SCD_MULTICAST_MSK; + + if (priv->nvm_data->sku_cap_11n_enable) + flush_cmd.queue_control |= IWL_AGG_TX_QUEUE_MSK; + + if (scd_q_msk) + flush_cmd.queue_control = cpu_to_le32(scd_q_msk); + + IWL_DEBUG_INFO(priv, "queue control: 0x%x\n", + flush_cmd.queue_control); + flush_cmd.flush_control = cpu_to_le16(IWL_DROP_ALL); + + return iwl_dvm_send_cmd(priv, &cmd); +} + +void iwlagn_dev_txfifo_flush(struct iwl_priv *priv) +{ + mutex_lock(&priv->mutex); + ieee80211_stop_queues(priv->hw); + if (iwlagn_txfifo_flush(priv, 0)) { + IWL_ERR(priv, "flush request fail\n"); + goto done; + } + IWL_DEBUG_INFO(priv, "wait transmit/flush all frames\n"); + iwl_trans_wait_tx_queue_empty(priv->trans); +done: + ieee80211_wake_queues(priv->hw); + mutex_unlock(&priv->mutex); +} + +/* + * BT coex + */ +/* Notmal TDM */ +static const __le32 iwlagn_def_3w_lookup[IWLAGN_BT_DECISION_LUT_SIZE] = { + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaeaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xcc00ff28), + cpu_to_le32(0x0000aaaa), + cpu_to_le32(0xcc00aaaa), + cpu_to_le32(0x0000aaaa), + cpu_to_le32(0xc0004000), + cpu_to_le32(0x00004000), + cpu_to_le32(0xf0005000), + cpu_to_le32(0xf0005000), +}; + + +/* Loose Coex */ +static const __le32 iwlagn_loose_lookup[IWLAGN_BT_DECISION_LUT_SIZE] = { + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaeaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xcc00ff28), + cpu_to_le32(0x0000aaaa), + cpu_to_le32(0xcc00aaaa), + cpu_to_le32(0x0000aaaa), + cpu_to_le32(0x00000000), + cpu_to_le32(0x00000000), + cpu_to_le32(0xf0005000), + cpu_to_le32(0xf0005000), +}; + +/* Full concurrency */ +static const __le32 iwlagn_concurrent_lookup[IWLAGN_BT_DECISION_LUT_SIZE] = { + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0x00000000), + cpu_to_le32(0x00000000), + cpu_to_le32(0x00000000), + cpu_to_le32(0x00000000), +}; + +void iwlagn_send_advance_bt_config(struct iwl_priv *priv) +{ + struct iwl_basic_bt_cmd basic = { + .max_kill = IWLAGN_BT_MAX_KILL_DEFAULT, + .bt3_timer_t7_value = IWLAGN_BT3_T7_DEFAULT, + .bt3_prio_sample_time = IWLAGN_BT3_PRIO_SAMPLE_DEFAULT, + .bt3_timer_t2_value = IWLAGN_BT3_T2_DEFAULT, + }; + struct iwl_bt_cmd_v1 bt_cmd_v1; + struct iwl_bt_cmd_v2 bt_cmd_v2; + int ret; + + BUILD_BUG_ON(sizeof(iwlagn_def_3w_lookup) != + sizeof(basic.bt3_lookup_table)); + + if (priv->cfg->bt_params) { + /* + * newer generation of devices (2000 series and newer) + * use the version 2 of the bt command + * we need to make sure sending the host command + * with correct data structure to avoid uCode assert + */ + if (priv->cfg->bt_params->bt_session_2) { + bt_cmd_v2.prio_boost = cpu_to_le32( + priv->cfg->bt_params->bt_prio_boost); + bt_cmd_v2.tx_prio_boost = 0; + bt_cmd_v2.rx_prio_boost = 0; + } else { + /* older version only has 8 bits */ + WARN_ON(priv->cfg->bt_params->bt_prio_boost & ~0xFF); + bt_cmd_v1.prio_boost = + priv->cfg->bt_params->bt_prio_boost; + bt_cmd_v1.tx_prio_boost = 0; + bt_cmd_v1.rx_prio_boost = 0; + } + } else { + IWL_ERR(priv, "failed to construct BT Coex Config\n"); + return; + } + + /* + * Possible situations when BT needs to take over for receive, + * at the same time where STA needs to response to AP's frame(s), + * reduce the tx power of the required response frames, by that, + * allow the concurrent BT receive & WiFi transmit + * (BT - ANT A, WiFi -ANT B), without interference to one another + * + * Reduced tx power apply to control frames only (ACK/Back/CTS) + * when indicated by the BT config command + */ + basic.kill_ack_mask = priv->kill_ack_mask; + basic.kill_cts_mask = priv->kill_cts_mask; + if (priv->reduced_txpower) + basic.reduce_txpower = IWLAGN_BT_REDUCED_TX_PWR; + basic.valid = priv->bt_valid; + + /* + * Configure BT coex mode to "no coexistence" when the + * user disabled BT coexistence, we have no interface + * (might be in monitor mode), or the interface is in + * IBSS mode (no proper uCode support for coex then). + */ + if (!iwlwifi_mod_params.bt_coex_active || + priv->iw_mode == NL80211_IFTYPE_ADHOC) { + basic.flags = IWLAGN_BT_FLAG_COEX_MODE_DISABLED; + } else { + basic.flags = IWLAGN_BT_FLAG_COEX_MODE_3W << + IWLAGN_BT_FLAG_COEX_MODE_SHIFT; + + if (!priv->bt_enable_pspoll) + basic.flags |= IWLAGN_BT_FLAG_SYNC_2_BT_DISABLE; + else + basic.flags &= ~IWLAGN_BT_FLAG_SYNC_2_BT_DISABLE; + + if (priv->bt_ch_announce) + basic.flags |= IWLAGN_BT_FLAG_CHANNEL_INHIBITION; + IWL_DEBUG_COEX(priv, "BT coex flag: 0X%x\n", basic.flags); + } + priv->bt_enable_flag = basic.flags; + if (priv->bt_full_concurrent) + memcpy(basic.bt3_lookup_table, iwlagn_concurrent_lookup, + sizeof(iwlagn_concurrent_lookup)); + else + memcpy(basic.bt3_lookup_table, iwlagn_def_3w_lookup, + sizeof(iwlagn_def_3w_lookup)); + + IWL_DEBUG_COEX(priv, "BT coex %s in %s mode\n", + basic.flags ? "active" : "disabled", + priv->bt_full_concurrent ? + "full concurrency" : "3-wire"); + + if (priv->cfg->bt_params->bt_session_2) { + memcpy(&bt_cmd_v2.basic, &basic, + sizeof(basic)); + ret = iwl_dvm_send_cmd_pdu(priv, REPLY_BT_CONFIG, + CMD_SYNC, sizeof(bt_cmd_v2), &bt_cmd_v2); + } else { + memcpy(&bt_cmd_v1.basic, &basic, + sizeof(basic)); + ret = iwl_dvm_send_cmd_pdu(priv, REPLY_BT_CONFIG, + CMD_SYNC, sizeof(bt_cmd_v1), &bt_cmd_v1); + } + if (ret) + IWL_ERR(priv, "failed to send BT Coex Config\n"); + +} + +void iwlagn_bt_adjust_rssi_monitor(struct iwl_priv *priv, bool rssi_ena) +{ + struct iwl_rxon_context *ctx, *found_ctx = NULL; + bool found_ap = false; + + lockdep_assert_held(&priv->mutex); + + /* Check whether AP or GO mode is active. */ + if (rssi_ena) { + for_each_context(priv, ctx) { + if (ctx->vif && ctx->vif->type == NL80211_IFTYPE_AP && + iwl_is_associated_ctx(ctx)) { + found_ap = true; + break; + } + } + } + + /* + * If disable was received or If GO/AP mode, disable RSSI + * measurements. + */ + if (!rssi_ena || found_ap) { + if (priv->cur_rssi_ctx) { + ctx = priv->cur_rssi_ctx; + ieee80211_disable_rssi_reports(ctx->vif); + priv->cur_rssi_ctx = NULL; + } + return; + } + + /* + * If rssi measurements need to be enabled, consider all cases now. + * Figure out how many contexts are active. + */ + for_each_context(priv, ctx) { + if (ctx->vif && ctx->vif->type == NL80211_IFTYPE_STATION && + iwl_is_associated_ctx(ctx)) { + found_ctx = ctx; + break; + } + } + + /* + * rssi monitor already enabled for the correct interface...nothing + * to do. + */ + if (found_ctx == priv->cur_rssi_ctx) + return; + + /* + * Figure out if rssi monitor is currently enabled, and needs + * to be changed. If rssi monitor is already enabled, disable + * it first else just enable rssi measurements on the + * interface found above. + */ + if (priv->cur_rssi_ctx) { + ctx = priv->cur_rssi_ctx; + if (ctx->vif) + ieee80211_disable_rssi_reports(ctx->vif); + } + + priv->cur_rssi_ctx = found_ctx; + + if (!found_ctx) + return; + + ieee80211_enable_rssi_reports(found_ctx->vif, + IWLAGN_BT_PSP_MIN_RSSI_THRESHOLD, + IWLAGN_BT_PSP_MAX_RSSI_THRESHOLD); +} + +static bool iwlagn_bt_traffic_is_sco(struct iwl_bt_uart_msg *uart_msg) +{ + return BT_UART_MSG_FRAME3SCOESCO_MSK & uart_msg->frame3 >> + BT_UART_MSG_FRAME3SCOESCO_POS; +} + +static void iwlagn_bt_traffic_change_work(struct work_struct *work) +{ + struct iwl_priv *priv = + container_of(work, struct iwl_priv, bt_traffic_change_work); + struct iwl_rxon_context *ctx; + int smps_request = -1; + + if (priv->bt_enable_flag == IWLAGN_BT_FLAG_COEX_MODE_DISABLED) { + /* bt coex disabled */ + return; + } + + /* + * Note: bt_traffic_load can be overridden by scan complete and + * coex profile notifications. Ignore that since only bad consequence + * can be not matching debug print with actual state. + */ + IWL_DEBUG_COEX(priv, "BT traffic load changes: %d\n", + priv->bt_traffic_load); + + switch (priv->bt_traffic_load) { + case IWL_BT_COEX_TRAFFIC_LOAD_NONE: + if (priv->bt_status) + smps_request = IEEE80211_SMPS_DYNAMIC; + else + smps_request = IEEE80211_SMPS_AUTOMATIC; + break; + case IWL_BT_COEX_TRAFFIC_LOAD_LOW: + smps_request = IEEE80211_SMPS_DYNAMIC; + break; + case IWL_BT_COEX_TRAFFIC_LOAD_HIGH: + case IWL_BT_COEX_TRAFFIC_LOAD_CONTINUOUS: + smps_request = IEEE80211_SMPS_STATIC; + break; + default: + IWL_ERR(priv, "Invalid BT traffic load: %d\n", + priv->bt_traffic_load); + break; + } + + mutex_lock(&priv->mutex); + + /* + * We can not send command to firmware while scanning. When the scan + * complete we will schedule this work again. We do check with mutex + * locked to prevent new scan request to arrive. We do not check + * STATUS_SCANNING to avoid race when queue_work two times from + * different notifications, but quit and not perform any work at all. + */ + if (test_bit(STATUS_SCAN_HW, &priv->status)) + goto out; + + iwl_update_chain_flags(priv); + + if (smps_request != -1) { + priv->current_ht_config.smps = smps_request; + for_each_context(priv, ctx) { + if (ctx->vif && ctx->vif->type == NL80211_IFTYPE_STATION) + ieee80211_request_smps(ctx->vif, smps_request); + } + } + + /* + * Dynamic PS poll related functionality. Adjust RSSI measurements if + * necessary. + */ + iwlagn_bt_coex_rssi_monitor(priv); +out: + mutex_unlock(&priv->mutex); +} + +/* + * If BT sco traffic, and RSSI monitor is enabled, move measurements to the + * correct interface or disable it if this is the last interface to be + * removed. + */ +void iwlagn_bt_coex_rssi_monitor(struct iwl_priv *priv) +{ + if (priv->bt_is_sco && + priv->bt_traffic_load == IWL_BT_COEX_TRAFFIC_LOAD_CONTINUOUS) + iwlagn_bt_adjust_rssi_monitor(priv, true); + else + iwlagn_bt_adjust_rssi_monitor(priv, false); +} + +static void iwlagn_print_uartmsg(struct iwl_priv *priv, + struct iwl_bt_uart_msg *uart_msg) +{ + IWL_DEBUG_COEX(priv, "Message Type = 0x%X, SSN = 0x%X, " + "Update Req = 0x%X\n", + (BT_UART_MSG_FRAME1MSGTYPE_MSK & uart_msg->frame1) >> + BT_UART_MSG_FRAME1MSGTYPE_POS, + (BT_UART_MSG_FRAME1SSN_MSK & uart_msg->frame1) >> + BT_UART_MSG_FRAME1SSN_POS, + (BT_UART_MSG_FRAME1UPDATEREQ_MSK & uart_msg->frame1) >> + BT_UART_MSG_FRAME1UPDATEREQ_POS); + + IWL_DEBUG_COEX(priv, "Open connections = 0x%X, Traffic load = 0x%X, " + "Chl_SeqN = 0x%X, In band = 0x%X\n", + (BT_UART_MSG_FRAME2OPENCONNECTIONS_MSK & uart_msg->frame2) >> + BT_UART_MSG_FRAME2OPENCONNECTIONS_POS, + (BT_UART_MSG_FRAME2TRAFFICLOAD_MSK & uart_msg->frame2) >> + BT_UART_MSG_FRAME2TRAFFICLOAD_POS, + (BT_UART_MSG_FRAME2CHLSEQN_MSK & uart_msg->frame2) >> + BT_UART_MSG_FRAME2CHLSEQN_POS, + (BT_UART_MSG_FRAME2INBAND_MSK & uart_msg->frame2) >> + BT_UART_MSG_FRAME2INBAND_POS); + + IWL_DEBUG_COEX(priv, "SCO/eSCO = 0x%X, Sniff = 0x%X, A2DP = 0x%X, " + "ACL = 0x%X, Master = 0x%X, OBEX = 0x%X\n", + (BT_UART_MSG_FRAME3SCOESCO_MSK & uart_msg->frame3) >> + BT_UART_MSG_FRAME3SCOESCO_POS, + (BT_UART_MSG_FRAME3SNIFF_MSK & uart_msg->frame3) >> + BT_UART_MSG_FRAME3SNIFF_POS, + (BT_UART_MSG_FRAME3A2DP_MSK & uart_msg->frame3) >> + BT_UART_MSG_FRAME3A2DP_POS, + (BT_UART_MSG_FRAME3ACL_MSK & uart_msg->frame3) >> + BT_UART_MSG_FRAME3ACL_POS, + (BT_UART_MSG_FRAME3MASTER_MSK & uart_msg->frame3) >> + BT_UART_MSG_FRAME3MASTER_POS, + (BT_UART_MSG_FRAME3OBEX_MSK & uart_msg->frame3) >> + BT_UART_MSG_FRAME3OBEX_POS); + + IWL_DEBUG_COEX(priv, "Idle duration = 0x%X\n", + (BT_UART_MSG_FRAME4IDLEDURATION_MSK & uart_msg->frame4) >> + BT_UART_MSG_FRAME4IDLEDURATION_POS); + + IWL_DEBUG_COEX(priv, "Tx Activity = 0x%X, Rx Activity = 0x%X, " + "eSCO Retransmissions = 0x%X\n", + (BT_UART_MSG_FRAME5TXACTIVITY_MSK & uart_msg->frame5) >> + BT_UART_MSG_FRAME5TXACTIVITY_POS, + (BT_UART_MSG_FRAME5RXACTIVITY_MSK & uart_msg->frame5) >> + BT_UART_MSG_FRAME5RXACTIVITY_POS, + (BT_UART_MSG_FRAME5ESCORETRANSMIT_MSK & uart_msg->frame5) >> + BT_UART_MSG_FRAME5ESCORETRANSMIT_POS); + + IWL_DEBUG_COEX(priv, "Sniff Interval = 0x%X, Discoverable = 0x%X\n", + (BT_UART_MSG_FRAME6SNIFFINTERVAL_MSK & uart_msg->frame6) >> + BT_UART_MSG_FRAME6SNIFFINTERVAL_POS, + (BT_UART_MSG_FRAME6DISCOVERABLE_MSK & uart_msg->frame6) >> + BT_UART_MSG_FRAME6DISCOVERABLE_POS); + + IWL_DEBUG_COEX(priv, "Sniff Activity = 0x%X, Page = " + "0x%X, Inquiry = 0x%X, Connectable = 0x%X\n", + (BT_UART_MSG_FRAME7SNIFFACTIVITY_MSK & uart_msg->frame7) >> + BT_UART_MSG_FRAME7SNIFFACTIVITY_POS, + (BT_UART_MSG_FRAME7PAGE_MSK & uart_msg->frame7) >> + BT_UART_MSG_FRAME7PAGE_POS, + (BT_UART_MSG_FRAME7INQUIRY_MSK & uart_msg->frame7) >> + BT_UART_MSG_FRAME7INQUIRY_POS, + (BT_UART_MSG_FRAME7CONNECTABLE_MSK & uart_msg->frame7) >> + BT_UART_MSG_FRAME7CONNECTABLE_POS); +} + +static bool iwlagn_set_kill_msk(struct iwl_priv *priv, + struct iwl_bt_uart_msg *uart_msg) +{ + bool need_update = false; + u8 kill_msk = IWL_BT_KILL_REDUCE; + static const __le32 bt_kill_ack_msg[3] = { + IWLAGN_BT_KILL_ACK_MASK_DEFAULT, + IWLAGN_BT_KILL_ACK_CTS_MASK_SCO, + IWLAGN_BT_KILL_ACK_CTS_MASK_REDUCE}; + static const __le32 bt_kill_cts_msg[3] = { + IWLAGN_BT_KILL_CTS_MASK_DEFAULT, + IWLAGN_BT_KILL_ACK_CTS_MASK_SCO, + IWLAGN_BT_KILL_ACK_CTS_MASK_REDUCE}; + + if (!priv->reduced_txpower) + kill_msk = (BT_UART_MSG_FRAME3SCOESCO_MSK & uart_msg->frame3) + ? IWL_BT_KILL_OVERRIDE : IWL_BT_KILL_DEFAULT; + if (priv->kill_ack_mask != bt_kill_ack_msg[kill_msk] || + priv->kill_cts_mask != bt_kill_cts_msg[kill_msk]) { + priv->bt_valid |= IWLAGN_BT_VALID_KILL_ACK_MASK; + priv->kill_ack_mask = bt_kill_ack_msg[kill_msk]; + priv->bt_valid |= IWLAGN_BT_VALID_KILL_CTS_MASK; + priv->kill_cts_mask = bt_kill_cts_msg[kill_msk]; + need_update = true; + } + return need_update; +} + +/* + * Upon RSSI changes, sends a bt config command with following changes + * 1. enable/disable "reduced control frames tx power + * 2. update the "kill)ack_mask" and "kill_cts_mask" + * + * If "reduced tx power" is enabled, uCode shall + * 1. ACK/Back/CTS rate shall reduced to 6Mbps + * 2. not use duplciate 20/40MHz mode + */ +static bool iwlagn_fill_txpower_mode(struct iwl_priv *priv, + struct iwl_bt_uart_msg *uart_msg) +{ + bool need_update = false; + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + int ave_rssi; + + if (!ctx->vif || (ctx->vif->type != NL80211_IFTYPE_STATION)) { + IWL_DEBUG_INFO(priv, "BSS ctx not active or not in sta mode\n"); + return false; + } + + ave_rssi = ieee80211_ave_rssi(ctx->vif); + if (!ave_rssi) { + /* no rssi data, no changes to reduce tx power */ + IWL_DEBUG_COEX(priv, "no rssi data available\n"); + return need_update; + } + if (!priv->reduced_txpower && + !iwl_is_associated(priv, IWL_RXON_CTX_PAN) && + (ave_rssi > BT_ENABLE_REDUCED_TXPOWER_THRESHOLD) && + (uart_msg->frame3 & (BT_UART_MSG_FRAME3ACL_MSK | + BT_UART_MSG_FRAME3OBEX_MSK)) && + !(uart_msg->frame3 & (BT_UART_MSG_FRAME3SCOESCO_MSK | + BT_UART_MSG_FRAME3SNIFF_MSK | BT_UART_MSG_FRAME3A2DP_MSK))) { + /* enabling reduced tx power */ + priv->reduced_txpower = true; + priv->bt_valid |= IWLAGN_BT_VALID_REDUCED_TX_PWR; + need_update = true; + } else if (priv->reduced_txpower && + (iwl_is_associated(priv, IWL_RXON_CTX_PAN) || + (ave_rssi < BT_DISABLE_REDUCED_TXPOWER_THRESHOLD) || + (uart_msg->frame3 & (BT_UART_MSG_FRAME3SCOESCO_MSK | + BT_UART_MSG_FRAME3SNIFF_MSK | BT_UART_MSG_FRAME3A2DP_MSK)) || + !(uart_msg->frame3 & (BT_UART_MSG_FRAME3ACL_MSK | + BT_UART_MSG_FRAME3OBEX_MSK)))) { + /* disable reduced tx power */ + priv->reduced_txpower = false; + priv->bt_valid |= IWLAGN_BT_VALID_REDUCED_TX_PWR; + need_update = true; + } + + return need_update; +} + +int iwlagn_bt_coex_profile_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_bt_coex_profile_notif *coex = (void *)pkt->data; + struct iwl_bt_uart_msg *uart_msg = &coex->last_bt_uart_msg; + + if (priv->bt_enable_flag == IWLAGN_BT_FLAG_COEX_MODE_DISABLED) { + /* bt coex disabled */ + return 0; + } + + IWL_DEBUG_COEX(priv, "BT Coex notification:\n"); + IWL_DEBUG_COEX(priv, " status: %d\n", coex->bt_status); + IWL_DEBUG_COEX(priv, " traffic load: %d\n", coex->bt_traffic_load); + IWL_DEBUG_COEX(priv, " CI compliance: %d\n", + coex->bt_ci_compliance); + iwlagn_print_uartmsg(priv, uart_msg); + + priv->last_bt_traffic_load = priv->bt_traffic_load; + priv->bt_is_sco = iwlagn_bt_traffic_is_sco(uart_msg); + + if (priv->iw_mode != NL80211_IFTYPE_ADHOC) { + if (priv->bt_status != coex->bt_status || + priv->last_bt_traffic_load != coex->bt_traffic_load) { + if (coex->bt_status) { + /* BT on */ + if (!priv->bt_ch_announce) + priv->bt_traffic_load = + IWL_BT_COEX_TRAFFIC_LOAD_HIGH; + else + priv->bt_traffic_load = + coex->bt_traffic_load; + } else { + /* BT off */ + priv->bt_traffic_load = + IWL_BT_COEX_TRAFFIC_LOAD_NONE; + } + priv->bt_status = coex->bt_status; + queue_work(priv->workqueue, + &priv->bt_traffic_change_work); + } + } + + /* schedule to send runtime bt_config */ + /* check reduce power before change ack/cts kill mask */ + if (iwlagn_fill_txpower_mode(priv, uart_msg) || + iwlagn_set_kill_msk(priv, uart_msg)) + queue_work(priv->workqueue, &priv->bt_runtime_config); + + + /* FIXME: based on notification, adjust the prio_boost */ + + priv->bt_ci_compliance = coex->bt_ci_compliance; + return 0; +} + +void iwlagn_bt_rx_handler_setup(struct iwl_priv *priv) +{ + priv->rx_handlers[REPLY_BT_COEX_PROFILE_NOTIF] = + iwlagn_bt_coex_profile_notif; +} + +void iwlagn_bt_setup_deferred_work(struct iwl_priv *priv) +{ + INIT_WORK(&priv->bt_traffic_change_work, + iwlagn_bt_traffic_change_work); +} + +void iwlagn_bt_cancel_deferred_work(struct iwl_priv *priv) +{ + cancel_work_sync(&priv->bt_traffic_change_work); +} + +static bool is_single_rx_stream(struct iwl_priv *priv) +{ + return priv->current_ht_config.smps == IEEE80211_SMPS_STATIC || + priv->current_ht_config.single_chain_sufficient; +} + +#define IWL_NUM_RX_CHAINS_MULTIPLE 3 +#define IWL_NUM_RX_CHAINS_SINGLE 2 +#define IWL_NUM_IDLE_CHAINS_DUAL 2 +#define IWL_NUM_IDLE_CHAINS_SINGLE 1 + +/* + * Determine how many receiver/antenna chains to use. + * + * More provides better reception via diversity. Fewer saves power + * at the expense of throughput, but only when not in powersave to + * start with. + * + * MIMO (dual stream) requires at least 2, but works better with 3. + * This does not determine *which* chains to use, just how many. + */ +static int iwl_get_active_rx_chain_count(struct iwl_priv *priv) +{ + if (priv->cfg->bt_params && + priv->cfg->bt_params->advanced_bt_coexist && + (priv->bt_full_concurrent || + priv->bt_traffic_load >= IWL_BT_COEX_TRAFFIC_LOAD_HIGH)) { + /* + * only use chain 'A' in bt high traffic load or + * full concurrency mode + */ + return IWL_NUM_RX_CHAINS_SINGLE; + } + /* # of Rx chains to use when expecting MIMO. */ + if (is_single_rx_stream(priv)) + return IWL_NUM_RX_CHAINS_SINGLE; + else + return IWL_NUM_RX_CHAINS_MULTIPLE; +} + +/* + * When we are in power saving mode, unless device support spatial + * multiplexing power save, use the active count for rx chain count. + */ +static int iwl_get_idle_rx_chain_count(struct iwl_priv *priv, int active_cnt) +{ + /* # Rx chains when idling, depending on SMPS mode */ + switch (priv->current_ht_config.smps) { + case IEEE80211_SMPS_STATIC: + case IEEE80211_SMPS_DYNAMIC: + return IWL_NUM_IDLE_CHAINS_SINGLE; + case IEEE80211_SMPS_AUTOMATIC: + case IEEE80211_SMPS_OFF: + return active_cnt; + default: + WARN(1, "invalid SMPS mode %d", + priv->current_ht_config.smps); + return active_cnt; + } +} + +/* up to 4 chains */ +static u8 iwl_count_chain_bitmap(u32 chain_bitmap) +{ + u8 res; + res = (chain_bitmap & BIT(0)) >> 0; + res += (chain_bitmap & BIT(1)) >> 1; + res += (chain_bitmap & BIT(2)) >> 2; + res += (chain_bitmap & BIT(3)) >> 3; + return res; +} + +/** + * iwlagn_set_rxon_chain - Set up Rx chain usage in "staging" RXON image + * + * Selects how many and which Rx receivers/antennas/chains to use. + * This should not be used for scan command ... it puts data in wrong place. + */ +void iwlagn_set_rxon_chain(struct iwl_priv *priv, struct iwl_rxon_context *ctx) +{ + bool is_single = is_single_rx_stream(priv); + bool is_cam = !test_bit(STATUS_POWER_PMI, &priv->status); + u8 idle_rx_cnt, active_rx_cnt, valid_rx_cnt; + u32 active_chains; + u16 rx_chain; + + /* Tell uCode which antennas are actually connected. + * Before first association, we assume all antennas are connected. + * Just after first association, iwl_chain_noise_calibration() + * checks which antennas actually *are* connected. */ + if (priv->chain_noise_data.active_chains) + active_chains = priv->chain_noise_data.active_chains; + else + active_chains = priv->nvm_data->valid_rx_ant; + + if (priv->cfg->bt_params && + priv->cfg->bt_params->advanced_bt_coexist && + (priv->bt_full_concurrent || + priv->bt_traffic_load >= IWL_BT_COEX_TRAFFIC_LOAD_HIGH)) { + /* + * only use chain 'A' in bt high traffic load or + * full concurrency mode + */ + active_chains = first_antenna(active_chains); + } + + rx_chain = active_chains << RXON_RX_CHAIN_VALID_POS; + + /* How many receivers should we use? */ + active_rx_cnt = iwl_get_active_rx_chain_count(priv); + idle_rx_cnt = iwl_get_idle_rx_chain_count(priv, active_rx_cnt); + + + /* correct rx chain count according hw settings + * and chain noise calibration + */ + valid_rx_cnt = iwl_count_chain_bitmap(active_chains); + if (valid_rx_cnt < active_rx_cnt) + active_rx_cnt = valid_rx_cnt; + + if (valid_rx_cnt < idle_rx_cnt) + idle_rx_cnt = valid_rx_cnt; + + rx_chain |= active_rx_cnt << RXON_RX_CHAIN_MIMO_CNT_POS; + rx_chain |= idle_rx_cnt << RXON_RX_CHAIN_CNT_POS; + + ctx->staging.rx_chain = cpu_to_le16(rx_chain); + + if (!is_single && (active_rx_cnt >= IWL_NUM_RX_CHAINS_SINGLE) && is_cam) + ctx->staging.rx_chain |= RXON_RX_CHAIN_MIMO_FORCE_MSK; + else + ctx->staging.rx_chain &= ~RXON_RX_CHAIN_MIMO_FORCE_MSK; + + IWL_DEBUG_ASSOC(priv, "rx_chain=0x%X active=%d idle=%d\n", + ctx->staging.rx_chain, + active_rx_cnt, idle_rx_cnt); + + WARN_ON(active_rx_cnt == 0 || idle_rx_cnt == 0 || + active_rx_cnt < idle_rx_cnt); +} + +u8 iwl_toggle_tx_ant(struct iwl_priv *priv, u8 ant, u8 valid) +{ + int i; + u8 ind = ant; + + if (priv->band == IEEE80211_BAND_2GHZ && + priv->bt_traffic_load >= IWL_BT_COEX_TRAFFIC_LOAD_HIGH) + return 0; + + for (i = 0; i < RATE_ANT_NUM - 1; i++) { + ind = (ind + 1) < RATE_ANT_NUM ? ind + 1 : 0; + if (valid & BIT(ind)) + return ind; + } + return ant; +} + +#ifdef CONFIG_PM_SLEEP +static void iwlagn_convert_p1k(u16 *p1k, __le16 *out) +{ + int i; + + for (i = 0; i < IWLAGN_P1K_SIZE; i++) + out[i] = cpu_to_le16(p1k[i]); +} + +struct wowlan_key_data { + struct iwl_rxon_context *ctx; + struct iwlagn_wowlan_rsc_tsc_params_cmd *rsc_tsc; + struct iwlagn_wowlan_tkip_params_cmd *tkip; + const u8 *bssid; + bool error, use_rsc_tsc, use_tkip; +}; + + +static void iwlagn_wowlan_program_keys(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, + void *_data) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct wowlan_key_data *data = _data; + struct iwl_rxon_context *ctx = data->ctx; + struct aes_sc *aes_sc, *aes_tx_sc = NULL; + struct tkip_sc *tkip_sc, *tkip_tx_sc = NULL; + struct iwlagn_p1k_cache *rx_p1ks; + u8 *rx_mic_key; + struct ieee80211_key_seq seq; + u32 cur_rx_iv32 = 0; + u16 p1k[IWLAGN_P1K_SIZE]; + int ret, i; + + mutex_lock(&priv->mutex); + + if ((key->cipher == WLAN_CIPHER_SUITE_WEP40 || + key->cipher == WLAN_CIPHER_SUITE_WEP104) && + !sta && !ctx->key_mapping_keys) + ret = iwl_set_default_wep_key(priv, ctx, key); + else + ret = iwl_set_dynamic_key(priv, ctx, key, sta); + + if (ret) { + IWL_ERR(priv, "Error setting key during suspend!\n"); + data->error = true; + } + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_TKIP: + if (sta) { + tkip_sc = data->rsc_tsc->all_tsc_rsc.tkip.unicast_rsc; + tkip_tx_sc = &data->rsc_tsc->all_tsc_rsc.tkip.tsc; + + rx_p1ks = data->tkip->rx_uni; + + ieee80211_get_key_tx_seq(key, &seq); + tkip_tx_sc->iv16 = cpu_to_le16(seq.tkip.iv16); + tkip_tx_sc->iv32 = cpu_to_le32(seq.tkip.iv32); + + ieee80211_get_tkip_p1k_iv(key, seq.tkip.iv32, p1k); + iwlagn_convert_p1k(p1k, data->tkip->tx.p1k); + + memcpy(data->tkip->mic_keys.tx, + &key->key[NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY], + IWLAGN_MIC_KEY_SIZE); + + rx_mic_key = data->tkip->mic_keys.rx_unicast; + } else { + tkip_sc = + data->rsc_tsc->all_tsc_rsc.tkip.multicast_rsc; + rx_p1ks = data->tkip->rx_multi; + rx_mic_key = data->tkip->mic_keys.rx_mcast; + } + + /* + * For non-QoS this relies on the fact that both the uCode and + * mac80211 use TID 0 (as they need to to avoid replay attacks) + * for checking the IV in the frames. + */ + for (i = 0; i < IWLAGN_NUM_RSC; i++) { + ieee80211_get_key_rx_seq(key, i, &seq); + tkip_sc[i].iv16 = cpu_to_le16(seq.tkip.iv16); + tkip_sc[i].iv32 = cpu_to_le32(seq.tkip.iv32); + /* wrapping isn't allowed, AP must rekey */ + if (seq.tkip.iv32 > cur_rx_iv32) + cur_rx_iv32 = seq.tkip.iv32; + } + + ieee80211_get_tkip_rx_p1k(key, data->bssid, cur_rx_iv32, p1k); + iwlagn_convert_p1k(p1k, rx_p1ks[0].p1k); + ieee80211_get_tkip_rx_p1k(key, data->bssid, + cur_rx_iv32 + 1, p1k); + iwlagn_convert_p1k(p1k, rx_p1ks[1].p1k); + + memcpy(rx_mic_key, + &key->key[NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY], + IWLAGN_MIC_KEY_SIZE); + + data->use_tkip = true; + data->use_rsc_tsc = true; + break; + case WLAN_CIPHER_SUITE_CCMP: + if (sta) { + u8 *pn = seq.ccmp.pn; + + aes_sc = data->rsc_tsc->all_tsc_rsc.aes.unicast_rsc; + aes_tx_sc = &data->rsc_tsc->all_tsc_rsc.aes.tsc; + + ieee80211_get_key_tx_seq(key, &seq); + aes_tx_sc->pn = cpu_to_le64( + (u64)pn[5] | + ((u64)pn[4] << 8) | + ((u64)pn[3] << 16) | + ((u64)pn[2] << 24) | + ((u64)pn[1] << 32) | + ((u64)pn[0] << 40)); + } else + aes_sc = data->rsc_tsc->all_tsc_rsc.aes.multicast_rsc; + + /* + * For non-QoS this relies on the fact that both the uCode and + * mac80211 use TID 0 for checking the IV in the frames. + */ + for (i = 0; i < IWLAGN_NUM_RSC; i++) { + u8 *pn = seq.ccmp.pn; + + ieee80211_get_key_rx_seq(key, i, &seq); + aes_sc->pn = cpu_to_le64( + (u64)pn[5] | + ((u64)pn[4] << 8) | + ((u64)pn[3] << 16) | + ((u64)pn[2] << 24) | + ((u64)pn[1] << 32) | + ((u64)pn[0] << 40)); + } + data->use_rsc_tsc = true; + break; + } + + mutex_unlock(&priv->mutex); +} + +int iwlagn_send_patterns(struct iwl_priv *priv, + struct cfg80211_wowlan *wowlan) +{ + struct iwlagn_wowlan_patterns_cmd *pattern_cmd; + struct iwl_host_cmd cmd = { + .id = REPLY_WOWLAN_PATTERNS, + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + .flags = CMD_SYNC, + }; + int i, err; + + if (!wowlan->n_patterns) + return 0; + + cmd.len[0] = sizeof(*pattern_cmd) + + wowlan->n_patterns * sizeof(struct iwlagn_wowlan_pattern); + + pattern_cmd = kmalloc(cmd.len[0], GFP_KERNEL); + if (!pattern_cmd) + return -ENOMEM; + + pattern_cmd->n_patterns = cpu_to_le32(wowlan->n_patterns); + + for (i = 0; i < wowlan->n_patterns; i++) { + int mask_len = DIV_ROUND_UP(wowlan->patterns[i].pattern_len, 8); + + memcpy(&pattern_cmd->patterns[i].mask, + wowlan->patterns[i].mask, mask_len); + memcpy(&pattern_cmd->patterns[i].pattern, + wowlan->patterns[i].pattern, + wowlan->patterns[i].pattern_len); + pattern_cmd->patterns[i].mask_size = mask_len; + pattern_cmd->patterns[i].pattern_size = + wowlan->patterns[i].pattern_len; + } + + cmd.data[0] = pattern_cmd; + err = iwl_dvm_send_cmd(priv, &cmd); + kfree(pattern_cmd); + return err; +} + +int iwlagn_suspend(struct iwl_priv *priv, struct cfg80211_wowlan *wowlan) +{ + struct iwlagn_wowlan_wakeup_filter_cmd wakeup_filter_cmd; + struct iwl_rxon_cmd rxon; + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + struct iwlagn_wowlan_kek_kck_material_cmd kek_kck_cmd; + struct iwlagn_wowlan_tkip_params_cmd tkip_cmd = {}; + struct iwlagn_d3_config_cmd d3_cfg_cmd = { + /* + * Program the minimum sleep time to 10 seconds, as many + * platforms have issues processing a wakeup signal while + * still being in the process of suspending. + */ + .min_sleep_time = cpu_to_le32(10 * 1000 * 1000), + }; + struct wowlan_key_data key_data = { + .ctx = ctx, + .bssid = ctx->active.bssid_addr, + .use_rsc_tsc = false, + .tkip = &tkip_cmd, + .use_tkip = false, + }; + int ret, i; + u16 seq; + + key_data.rsc_tsc = kzalloc(sizeof(*key_data.rsc_tsc), GFP_KERNEL); + if (!key_data.rsc_tsc) + return -ENOMEM; + + memset(&wakeup_filter_cmd, 0, sizeof(wakeup_filter_cmd)); + + /* + * We know the last used seqno, and the uCode expects to know that + * one, it will increment before TX. + */ + seq = le16_to_cpu(priv->last_seq_ctl) & IEEE80211_SCTL_SEQ; + wakeup_filter_cmd.non_qos_seq = cpu_to_le16(seq); + + /* + * For QoS counters, we store the one to use next, so subtract 0x10 + * since the uCode will add 0x10 before using the value. + */ + for (i = 0; i < IWL_MAX_TID_COUNT; i++) { + seq = priv->tid_data[IWL_AP_ID][i].seq_number; + seq -= 0x10; + wakeup_filter_cmd.qos_seq[i] = cpu_to_le16(seq); + } + + if (wowlan->disconnect) + wakeup_filter_cmd.enabled |= + cpu_to_le32(IWLAGN_WOWLAN_WAKEUP_BEACON_MISS | + IWLAGN_WOWLAN_WAKEUP_LINK_CHANGE); + if (wowlan->magic_pkt) + wakeup_filter_cmd.enabled |= + cpu_to_le32(IWLAGN_WOWLAN_WAKEUP_MAGIC_PACKET); + if (wowlan->gtk_rekey_failure) + wakeup_filter_cmd.enabled |= + cpu_to_le32(IWLAGN_WOWLAN_WAKEUP_GTK_REKEY_FAIL); + if (wowlan->eap_identity_req) + wakeup_filter_cmd.enabled |= + cpu_to_le32(IWLAGN_WOWLAN_WAKEUP_EAP_IDENT_REQ); + if (wowlan->four_way_handshake) + wakeup_filter_cmd.enabled |= + cpu_to_le32(IWLAGN_WOWLAN_WAKEUP_4WAY_HANDSHAKE); + if (wowlan->n_patterns) + wakeup_filter_cmd.enabled |= + cpu_to_le32(IWLAGN_WOWLAN_WAKEUP_PATTERN_MATCH); + + if (wowlan->rfkill_release) + d3_cfg_cmd.wakeup_flags |= + cpu_to_le32(IWLAGN_D3_WAKEUP_RFKILL); + + iwl_scan_cancel_timeout(priv, 200); + + memcpy(&rxon, &ctx->active, sizeof(rxon)); + + priv->ucode_loaded = false; + iwl_trans_stop_device(priv->trans); + + priv->wowlan = true; + + ret = iwl_load_ucode_wait_alive(priv, IWL_UCODE_WOWLAN); + if (ret) + goto out; + + /* now configure WoWLAN ucode */ + ret = iwl_alive_start(priv); + if (ret) + goto out; + + memcpy(&ctx->staging, &rxon, sizeof(rxon)); + ret = iwlagn_commit_rxon(priv, ctx); + if (ret) + goto out; + + ret = iwl_power_update_mode(priv, true); + if (ret) + goto out; + + if (!iwlwifi_mod_params.sw_crypto) { + /* mark all keys clear */ + priv->ucode_key_table = 0; + ctx->key_mapping_keys = 0; + + /* + * This needs to be unlocked due to lock ordering + * constraints. Since we're in the suspend path + * that isn't really a problem though. + */ + mutex_unlock(&priv->mutex); + ieee80211_iter_keys(priv->hw, ctx->vif, + iwlagn_wowlan_program_keys, + &key_data); + mutex_lock(&priv->mutex); + if (key_data.error) { + ret = -EIO; + goto out; + } + + if (key_data.use_rsc_tsc) { + struct iwl_host_cmd rsc_tsc_cmd = { + .id = REPLY_WOWLAN_TSC_RSC_PARAMS, + .flags = CMD_SYNC, + .data[0] = key_data.rsc_tsc, + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + .len[0] = sizeof(*key_data.rsc_tsc), + }; + + ret = iwl_dvm_send_cmd(priv, &rsc_tsc_cmd); + if (ret) + goto out; + } + + if (key_data.use_tkip) { + ret = iwl_dvm_send_cmd_pdu(priv, + REPLY_WOWLAN_TKIP_PARAMS, + CMD_SYNC, sizeof(tkip_cmd), + &tkip_cmd); + if (ret) + goto out; + } + + if (priv->have_rekey_data) { + memset(&kek_kck_cmd, 0, sizeof(kek_kck_cmd)); + memcpy(kek_kck_cmd.kck, priv->kck, NL80211_KCK_LEN); + kek_kck_cmd.kck_len = cpu_to_le16(NL80211_KCK_LEN); + memcpy(kek_kck_cmd.kek, priv->kek, NL80211_KEK_LEN); + kek_kck_cmd.kek_len = cpu_to_le16(NL80211_KEK_LEN); + kek_kck_cmd.replay_ctr = priv->replay_ctr; + + ret = iwl_dvm_send_cmd_pdu(priv, + REPLY_WOWLAN_KEK_KCK_MATERIAL, + CMD_SYNC, sizeof(kek_kck_cmd), + &kek_kck_cmd); + if (ret) + goto out; + } + } + + ret = iwl_dvm_send_cmd_pdu(priv, REPLY_D3_CONFIG, CMD_SYNC, + sizeof(d3_cfg_cmd), &d3_cfg_cmd); + if (ret) + goto out; + + ret = iwl_dvm_send_cmd_pdu(priv, REPLY_WOWLAN_WAKEUP_FILTER, + CMD_SYNC, sizeof(wakeup_filter_cmd), + &wakeup_filter_cmd); + if (ret) + goto out; + + ret = iwlagn_send_patterns(priv, wowlan); + out: + kfree(key_data.rsc_tsc); + return ret; +} +#endif + +int iwl_dvm_send_cmd(struct iwl_priv *priv, struct iwl_host_cmd *cmd) +{ + if (iwl_is_rfkill(priv) || iwl_is_ctkill(priv)) { + IWL_WARN(priv, "Not sending command - %s KILL\n", + iwl_is_rfkill(priv) ? "RF" : "CT"); + return -EIO; + } + + if (test_bit(STATUS_FW_ERROR, &priv->status)) { + IWL_ERR(priv, "Command %s failed: FW Error\n", + iwl_dvm_get_cmd_string(cmd->id)); + return -EIO; + } + + /* + * This can happen upon FW ASSERT: we clear the STATUS_FW_ERROR flag + * in iwl_down but cancel the workers only later. + */ + if (!priv->ucode_loaded) { + IWL_ERR(priv, "Fw not loaded - dropping CMD: %x\n", cmd->id); + return -EIO; + } + + /* + * Synchronous commands from this op-mode must hold + * the mutex, this ensures we don't try to send two + * (or more) synchronous commands at a time. + */ + if (!(cmd->flags & CMD_ASYNC)) + lockdep_assert_held(&priv->mutex); + + if (priv->ucode_owner == IWL_OWNERSHIP_TM && + !(cmd->flags & CMD_ON_DEMAND)) { + IWL_DEBUG_HC(priv, "tm own the uCode, no regular hcmd send\n"); + return -EIO; + } + + return iwl_trans_send_cmd(priv->trans, cmd); +} + +int iwl_dvm_send_cmd_pdu(struct iwl_priv *priv, u8 id, + u32 flags, u16 len, const void *data) +{ + struct iwl_host_cmd cmd = { + .id = id, + .len = { len, }, + .data = { data, }, + .flags = flags, + }; + + return iwl_dvm_send_cmd(priv, &cmd); +} diff --git a/drivers/net/wireless/iwlwifi/dvm/mac80211.c b/drivers/net/wireless/iwlwifi/dvm/mac80211.c new file mode 100644 index 0000000..7ad4e38 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/mac80211.c @@ -0,0 +1,1788 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "iwl-io.h" +#include "iwl-trans.h" +#include "iwl-op-mode.h" +#include "iwl-modparams.h" + +#include "dev.h" +#include "calib.h" +#include "agn.h" + +/***************************************************************************** + * + * mac80211 entry point functions + * + *****************************************************************************/ + +static const struct ieee80211_iface_limit iwlagn_sta_ap_limits[] = { + { + .max = 1, + .types = BIT(NL80211_IFTYPE_STATION), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_AP), + }, +}; + +static const struct ieee80211_iface_limit iwlagn_2sta_limits[] = { + { + .max = 2, + .types = BIT(NL80211_IFTYPE_STATION), + }, +}; + +static const struct ieee80211_iface_limit iwlagn_p2p_sta_go_limits[] = { + { + .max = 1, + .types = BIT(NL80211_IFTYPE_STATION), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_AP), + }, +}; + +static const struct ieee80211_iface_limit iwlagn_p2p_2sta_limits[] = { + { + .max = 2, + .types = BIT(NL80211_IFTYPE_STATION), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_P2P_CLIENT), + }, +}; + +static const struct ieee80211_iface_combination +iwlagn_iface_combinations_dualmode[] = { + { .num_different_channels = 1, + .max_interfaces = 2, + .beacon_int_infra_match = true, + .limits = iwlagn_sta_ap_limits, + .n_limits = ARRAY_SIZE(iwlagn_sta_ap_limits), + }, + { .num_different_channels = 1, + .max_interfaces = 2, + .limits = iwlagn_2sta_limits, + .n_limits = ARRAY_SIZE(iwlagn_2sta_limits), + }, +}; + +static const struct ieee80211_iface_combination +iwlagn_iface_combinations_p2p[] = { + { .num_different_channels = 1, + .max_interfaces = 2, + .beacon_int_infra_match = true, + .limits = iwlagn_p2p_sta_go_limits, + .n_limits = ARRAY_SIZE(iwlagn_p2p_sta_go_limits), + }, + { .num_different_channels = 1, + .max_interfaces = 2, + .limits = iwlagn_p2p_2sta_limits, + .n_limits = ARRAY_SIZE(iwlagn_p2p_2sta_limits), + }, +}; + +/* + * Not a mac80211 entry point function, but it fits in with all the + * other mac80211 functions grouped here. + */ +int iwlagn_mac_setup_register(struct iwl_priv *priv, + const struct iwl_ucode_capabilities *capa) +{ + int ret; + struct ieee80211_hw *hw = priv->hw; + struct iwl_rxon_context *ctx; + + hw->rate_control_algorithm = "iwl-agn-rs"; + + /* Tell mac80211 our characteristics */ + hw->flags = IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_AMPDU_AGGREGATION | + IEEE80211_HW_NEED_DTIM_BEFORE_ASSOC | + IEEE80211_HW_SPECTRUM_MGMT | + IEEE80211_HW_REPORTS_TX_ACK_STATUS | + IEEE80211_HW_QUEUE_CONTROL | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_SUPPORTS_DYNAMIC_PS | + IEEE80211_HW_WANT_MONITOR_VIF; + + hw->offchannel_tx_hw_queue = IWL_AUX_QUEUE; + hw->radiotap_mcs_details |= IEEE80211_RADIOTAP_MCS_HAVE_FMT; + + /* + * Including the following line will crash some AP's. This + * workaround removes the stimulus which causes the crash until + * the AP software can be fixed. + hw->max_tx_aggregation_subframes = LINK_QUAL_AGG_FRAME_LIMIT_DEF; + */ + + if (priv->nvm_data->sku_cap_11n_enable) + hw->flags |= IEEE80211_HW_SUPPORTS_DYNAMIC_SMPS | + IEEE80211_HW_SUPPORTS_STATIC_SMPS; + + /* + * Enable 11w if advertised by firmware and software crypto + * is not enabled (as the firmware will interpret some mgmt + * packets, so enabling it with software crypto isn't safe) + */ + if (priv->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_MFP && + !iwlwifi_mod_params.sw_crypto) + hw->flags |= IEEE80211_HW_MFP_CAPABLE; + + hw->sta_data_size = sizeof(struct iwl_station_priv); + hw->vif_data_size = sizeof(struct iwl_vif_priv); + + for_each_context(priv, ctx) { + hw->wiphy->interface_modes |= ctx->interface_modes; + hw->wiphy->interface_modes |= ctx->exclusive_interface_modes; + } + + BUILD_BUG_ON(NUM_IWL_RXON_CTX != 2); + + if (hw->wiphy->interface_modes & BIT(NL80211_IFTYPE_P2P_CLIENT)) { + hw->wiphy->iface_combinations = iwlagn_iface_combinations_p2p; + hw->wiphy->n_iface_combinations = + ARRAY_SIZE(iwlagn_iface_combinations_p2p); + } else if (hw->wiphy->interface_modes & BIT(NL80211_IFTYPE_AP)) { + hw->wiphy->iface_combinations = + iwlagn_iface_combinations_dualmode; + hw->wiphy->n_iface_combinations = + ARRAY_SIZE(iwlagn_iface_combinations_dualmode); + } + + hw->wiphy->max_remain_on_channel_duration = 500; + + hw->wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY | + WIPHY_FLAG_DISABLE_BEACON_HINTS | + WIPHY_FLAG_IBSS_RSN; + +#ifdef CONFIG_PM_SLEEP + if (priv->fw->img[IWL_UCODE_WOWLAN].sec[0].len && + priv->trans->ops->d3_suspend && + priv->trans->ops->d3_resume && + device_can_wakeup(priv->trans->dev)) { + hw->wiphy->wowlan.flags = WIPHY_WOWLAN_MAGIC_PKT | + WIPHY_WOWLAN_DISCONNECT | + WIPHY_WOWLAN_EAP_IDENTITY_REQ | + WIPHY_WOWLAN_RFKILL_RELEASE; + if (!iwlwifi_mod_params.sw_crypto) + hw->wiphy->wowlan.flags |= + WIPHY_WOWLAN_SUPPORTS_GTK_REKEY | + WIPHY_WOWLAN_GTK_REKEY_FAILURE; + + hw->wiphy->wowlan.n_patterns = IWLAGN_WOWLAN_MAX_PATTERNS; + hw->wiphy->wowlan.pattern_min_len = + IWLAGN_WOWLAN_MIN_PATTERN_LEN; + hw->wiphy->wowlan.pattern_max_len = + IWLAGN_WOWLAN_MAX_PATTERN_LEN; + } +#endif + + if (iwlwifi_mod_params.power_save) + hw->wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT; + else + hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + + hw->wiphy->max_scan_ssids = PROBE_OPTION_MAX; + /* we create the 802.11 header and a max-length SSID element */ + hw->wiphy->max_scan_ie_len = capa->max_probe_length - 24 - 34; + + /* + * We don't use all queues: 4 and 9 are unused and any + * aggregation queue gets mapped down to the AC queue. + */ + hw->queues = IWLAGN_FIRST_AMPDU_QUEUE; + + hw->max_listen_interval = IWL_CONN_MAX_LISTEN_INTERVAL; + + if (priv->nvm_data->bands[IEEE80211_BAND_2GHZ].n_channels) + priv->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = + &priv->nvm_data->bands[IEEE80211_BAND_2GHZ]; + if (priv->nvm_data->bands[IEEE80211_BAND_5GHZ].n_channels) + priv->hw->wiphy->bands[IEEE80211_BAND_5GHZ] = + &priv->nvm_data->bands[IEEE80211_BAND_5GHZ]; + + hw->wiphy->hw_version = priv->trans->hw_id; + + iwl_leds_init(priv); + + ret = ieee80211_register_hw(priv->hw); + if (ret) { + IWL_ERR(priv, "Failed to register hw (error %d)\n", ret); + iwl_leds_exit(priv); + return ret; + } + priv->mac80211_registered = 1; + + return 0; +} + +void iwlagn_mac_unregister(struct iwl_priv *priv) +{ + if (!priv->mac80211_registered) + return; + iwl_leds_exit(priv); + ieee80211_unregister_hw(priv->hw); + priv->mac80211_registered = 0; +} + +static int __iwl_up(struct iwl_priv *priv) +{ + struct iwl_rxon_context *ctx; + int ret; + + lockdep_assert_held(&priv->mutex); + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) { + IWL_WARN(priv, "Exit pending; will not bring the NIC up\n"); + return -EIO; + } + + for_each_context(priv, ctx) { + ret = iwlagn_alloc_bcast_station(priv, ctx); + if (ret) { + iwl_dealloc_bcast_stations(priv); + return ret; + } + } + + ret = iwl_run_init_ucode(priv); + if (ret) { + IWL_ERR(priv, "Failed to run INIT ucode: %d\n", ret); + goto error; + } + + ret = iwl_load_ucode_wait_alive(priv, IWL_UCODE_REGULAR); + if (ret) { + IWL_ERR(priv, "Failed to start RT ucode: %d\n", ret); + goto error; + } + + ret = iwl_alive_start(priv); + if (ret) + goto error; + return 0; + + error: + set_bit(STATUS_EXIT_PENDING, &priv->status); + iwl_down(priv); + clear_bit(STATUS_EXIT_PENDING, &priv->status); + + IWL_ERR(priv, "Unable to initialize device.\n"); + return ret; +} + +static int iwlagn_mac_start(struct ieee80211_hw *hw) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + int ret; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + /* we should be verifying the device is ready to be opened */ + mutex_lock(&priv->mutex); + ret = __iwl_up(priv); + mutex_unlock(&priv->mutex); + if (ret) + return ret; + + IWL_DEBUG_INFO(priv, "Start UP work done.\n"); + + /* Now we should be done, and the READY bit should be set. */ + if (WARN_ON(!test_bit(STATUS_READY, &priv->status))) + ret = -EIO; + + iwlagn_led_enable(priv); + + priv->is_open = 1; + IWL_DEBUG_MAC80211(priv, "leave\n"); + return 0; +} + +static void iwlagn_mac_stop(struct ieee80211_hw *hw) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + if (!priv->is_open) + return; + + priv->is_open = 0; + + mutex_lock(&priv->mutex); + iwl_down(priv); + mutex_unlock(&priv->mutex); + + iwl_cancel_deferred_work(priv); + + flush_workqueue(priv->workqueue); + + /* User space software may expect getting rfkill changes + * even if interface is down, trans->down will leave the RF + * kill interrupt enabled + */ + iwl_trans_stop_hw(priv->trans, false); + + IWL_DEBUG_MAC80211(priv, "leave\n"); +} + +static void iwlagn_mac_set_rekey_data(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_gtk_rekey_data *data) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + if (iwlwifi_mod_params.sw_crypto) + return; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + mutex_lock(&priv->mutex); + + if (priv->contexts[IWL_RXON_CTX_BSS].vif != vif) + goto out; + + memcpy(priv->kek, data->kek, NL80211_KEK_LEN); + memcpy(priv->kck, data->kck, NL80211_KCK_LEN); + priv->replay_ctr = + cpu_to_le64(be64_to_cpup((__be64 *)&data->replay_ctr)); + priv->have_rekey_data = true; + + out: + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); +} + +#ifdef CONFIG_PM_SLEEP + +static int iwlagn_mac_suspend(struct ieee80211_hw *hw, + struct cfg80211_wowlan *wowlan) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + int ret; + + if (WARN_ON(!wowlan)) + return -EINVAL; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + mutex_lock(&priv->mutex); + + /* Don't attempt WoWLAN when not associated, tear down instead. */ + if (!ctx->vif || ctx->vif->type != NL80211_IFTYPE_STATION || + !iwl_is_associated_ctx(ctx)) { + ret = 1; + goto out; + } + + ret = iwlagn_suspend(priv, wowlan); + if (ret) + goto error; + + iwl_trans_d3_suspend(priv->trans); + + goto out; + + error: + priv->wowlan = false; + iwlagn_prepare_restart(priv); + ieee80211_restart_hw(priv->hw); + out: + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); + + return ret; +} + +struct iwl_resume_data { + struct iwl_priv *priv; + struct iwlagn_wowlan_status *cmd; + bool valid; +}; + +static bool iwl_resume_status_fn(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_resume_data *resume_data = data; + struct iwl_priv *priv = resume_data->priv; + u32 len = le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; + + if (len - 4 != sizeof(*resume_data->cmd)) { + IWL_ERR(priv, "rx wrong size data\n"); + return true; + } + memcpy(resume_data->cmd, pkt->data, sizeof(*resume_data->cmd)); + resume_data->valid = true; + + return true; +} + +static int iwlagn_mac_resume(struct ieee80211_hw *hw) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + struct ieee80211_vif *vif; + u32 base; + int ret; + enum iwl_d3_status d3_status; + struct error_table_start { + /* cf. struct iwl_error_event_table */ + u32 valid; + u32 error_id; + } err_info; + struct iwl_notification_wait status_wait; + static const u8 status_cmd[] = { + REPLY_WOWLAN_GET_STATUS, + }; + struct iwlagn_wowlan_status status_data = {}; + struct iwl_resume_data resume_data = { + .priv = priv, + .cmd = &status_data, + .valid = false, + }; + struct cfg80211_wowlan_wakeup wakeup = { + .pattern_idx = -1, + }; +#ifdef CPTCFG_IWLWIFI_DEBUGFS + const struct fw_img *img; +#endif + + IWL_DEBUG_MAC80211(priv, "enter\n"); + mutex_lock(&priv->mutex); + + /* we'll clear ctx->vif during iwlagn_prepare_restart() */ + vif = ctx->vif; + + ret = iwl_trans_d3_resume(priv->trans, &d3_status); + if (ret) + goto out_unlock; + + if (d3_status != IWL_D3_STATUS_ALIVE) { + IWL_INFO(priv, "Device was reset during suspend\n"); + goto out_unlock; + } + + base = priv->device_pointers.error_event_table; + if (!iwlagn_hw_valid_rtc_data_addr(base)) { + IWL_WARN(priv, "Invalid error table during resume!\n"); + goto out_unlock; + } + + iwl_trans_read_mem_bytes(priv->trans, base, + &err_info, sizeof(err_info)); + + if (err_info.valid) { + IWL_INFO(priv, "error table is valid (%d, 0x%x)\n", + err_info.valid, err_info.error_id); + if (err_info.error_id == RF_KILL_INDICATOR_FOR_WOWLAN) { + wakeup.rfkill_release = true; + ieee80211_report_wowlan_wakeup(vif, &wakeup, + GFP_KERNEL); + } + goto out_unlock; + } + +#ifdef CPTCFG_IWLWIFI_DEBUGFS + img = &priv->fw->img[IWL_UCODE_WOWLAN]; + if (!priv->wowlan_sram) + priv->wowlan_sram = + kzalloc(img->sec[IWL_UCODE_SECTION_DATA].len, + GFP_KERNEL); + + if (priv->wowlan_sram) + iwl_trans_read_mem(priv->trans, 0x800000, + priv->wowlan_sram, + img->sec[IWL_UCODE_SECTION_DATA].len / 4); +#endif + + /* + * This is very strange. The GET_STATUS command is sent but the device + * doesn't reply properly, it seems it doesn't close the RBD so one is + * always left open ... As a result, we need to send another command + * and have to reset the driver afterwards. As we need to switch to + * runtime firmware again that'll happen. + */ + + iwl_init_notification_wait(&priv->notif_wait, &status_wait, status_cmd, + ARRAY_SIZE(status_cmd), iwl_resume_status_fn, + &resume_data); + + iwl_dvm_send_cmd_pdu(priv, REPLY_WOWLAN_GET_STATUS, CMD_ASYNC, 0, NULL); + iwl_dvm_send_cmd_pdu(priv, REPLY_ECHO, CMD_ASYNC, 0, NULL); + /* an RBD is left open in the firmware now! */ + + ret = iwl_wait_notification(&priv->notif_wait, &status_wait, HZ/5); + if (ret) + goto out_unlock; + + if (resume_data.valid && priv->contexts[IWL_RXON_CTX_BSS].vif) { + u32 reasons = le32_to_cpu(status_data.wakeup_reason); + struct cfg80211_wowlan_wakeup *wakeup_report; + + IWL_INFO(priv, "WoWLAN wakeup reason(s): 0x%.8x\n", reasons); + + if (reasons) { + if (reasons & IWLAGN_WOWLAN_WAKEUP_MAGIC_PACKET) + wakeup.magic_pkt = true; + if (reasons & IWLAGN_WOWLAN_WAKEUP_PATTERN_MATCH) + wakeup.pattern_idx = status_data.pattern_number; + if (reasons & (IWLAGN_WOWLAN_WAKEUP_BEACON_MISS | + IWLAGN_WOWLAN_WAKEUP_LINK_CHANGE)) + wakeup.disconnect = true; + if (reasons & IWLAGN_WOWLAN_WAKEUP_GTK_REKEY_FAIL) + wakeup.gtk_rekey_failure = true; + if (reasons & IWLAGN_WOWLAN_WAKEUP_EAP_IDENT_REQ) + wakeup.eap_identity_req = true; + if (reasons & IWLAGN_WOWLAN_WAKEUP_4WAY_HANDSHAKE) + wakeup.four_way_handshake = true; + wakeup_report = &wakeup; + } else { + wakeup_report = NULL; + } + + ieee80211_report_wowlan_wakeup(vif, wakeup_report, GFP_KERNEL); + } + + priv->wowlan = false; + + iwlagn_prepare_restart(priv); + + memset((void *)&ctx->active, 0, sizeof(ctx->active)); + iwl_connection_init_rx_config(priv, ctx); + iwlagn_set_rxon_chain(priv, ctx); + + out_unlock: + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); + + ieee80211_resume_disconnect(vif); + + return 1; +} + +static void iwlagn_mac_set_wakeup(struct ieee80211_hw *hw, bool enabled) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + device_set_wakeup_enable(priv->trans->dev, enabled); +} +#endif + +static void iwlagn_mac_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + if (iwlagn_tx_skb(priv, control->sta, skb)) + ieee80211_free_txskb(hw, skb); +} + +static void iwlagn_mac_update_tkip_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, + u32 iv32, u16 *phase1key) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + iwl_update_tkip_key(priv, vif, keyconf, sta, iv32, phase1key); +} + +static int iwlagn_mac_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_vif_priv *vif_priv = (void *)vif->drv_priv; + struct iwl_rxon_context *ctx = vif_priv->ctx; + int ret; + bool is_default_wep_key = false; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + if (iwlwifi_mod_params.sw_crypto) { + IWL_DEBUG_MAC80211(priv, "leave - hwcrypto disabled\n"); + return -EOPNOTSUPP; + } + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_TKIP: + key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC; + /* fall through */ + case WLAN_CIPHER_SUITE_CCMP: + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + break; + default: + break; + } + + /* + * We could program these keys into the hardware as well, but we + * don't expect much multicast traffic in IBSS and having keys + * for more stations is probably more useful. + * + * Mark key TX-only and return 0. + */ + if (vif->type == NL80211_IFTYPE_ADHOC && + !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) { + key->hw_key_idx = WEP_INVALID_OFFSET; + return 0; + } + + /* If they key was TX-only, accept deletion */ + if (cmd == DISABLE_KEY && key->hw_key_idx == WEP_INVALID_OFFSET) + return 0; + + mutex_lock(&priv->mutex); + iwl_scan_cancel_timeout(priv, 100); + + BUILD_BUG_ON(WEP_INVALID_OFFSET == IWLAGN_HW_KEY_DEFAULT); + + /* + * If we are getting WEP group key and we didn't receive any key mapping + * so far, we are in legacy wep mode (group key only), otherwise we are + * in 1X mode. + * In legacy wep mode, we use another host command to the uCode. + */ + if ((key->cipher == WLAN_CIPHER_SUITE_WEP40 || + key->cipher == WLAN_CIPHER_SUITE_WEP104) && !sta) { + if (cmd == SET_KEY) + is_default_wep_key = !ctx->key_mapping_keys; + else + is_default_wep_key = + key->hw_key_idx == IWLAGN_HW_KEY_DEFAULT; + } + + + switch (cmd) { + case SET_KEY: + if (is_default_wep_key) { + ret = iwl_set_default_wep_key(priv, vif_priv->ctx, key); + break; + } + ret = iwl_set_dynamic_key(priv, vif_priv->ctx, key, sta); + if (ret) { + /* + * can't add key for RX, but we don't need it + * in the device for TX so still return 0 + */ + ret = 0; + key->hw_key_idx = WEP_INVALID_OFFSET; + } + + IWL_DEBUG_MAC80211(priv, "enable hwcrypto key\n"); + break; + case DISABLE_KEY: + if (is_default_wep_key) + ret = iwl_remove_default_wep_key(priv, ctx, key); + else + ret = iwl_remove_dynamic_key(priv, ctx, key, sta); + + IWL_DEBUG_MAC80211(priv, "disable hwcrypto key\n"); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); + + return ret; +} + +static int iwlagn_mac_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, u16 *ssn, + u8 buf_size) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + int ret = -EINVAL; + struct iwl_station_priv *sta_priv = (void *) sta->drv_priv; + + IWL_DEBUG_HT(priv, "A-MPDU action on addr %pM tid %d\n", + sta->addr, tid); + + if (!(priv->nvm_data->sku_cap_11n_enable)) + return -EACCES; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + mutex_lock(&priv->mutex); + + switch (action) { + case IEEE80211_AMPDU_RX_START: + if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_RXAGG) + break; + IWL_DEBUG_HT(priv, "start Rx\n"); + ret = iwl_sta_rx_agg_start(priv, sta, tid, *ssn); + break; + case IEEE80211_AMPDU_RX_STOP: + IWL_DEBUG_HT(priv, "stop Rx\n"); + ret = iwl_sta_rx_agg_stop(priv, sta, tid); + break; + case IEEE80211_AMPDU_TX_START: + if (!priv->trans->ops->txq_enable) + break; + if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_TXAGG) + break; + IWL_DEBUG_HT(priv, "start Tx\n"); + ret = iwlagn_tx_agg_start(priv, vif, sta, tid, ssn); + break; + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + IWL_DEBUG_HT(priv, "Flush Tx\n"); + ret = iwlagn_tx_agg_flush(priv, vif, sta, tid); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + IWL_DEBUG_HT(priv, "stop Tx\n"); + ret = iwlagn_tx_agg_stop(priv, vif, sta, tid); + if ((ret == 0) && (priv->agg_tids_count > 0)) { + priv->agg_tids_count--; + IWL_DEBUG_HT(priv, "priv->agg_tids_count = %u\n", + priv->agg_tids_count); + } + if (!priv->agg_tids_count && + priv->hw_params.use_rts_for_aggregation) { + /* + * switch off RTS/CTS if it was previously enabled + */ + sta_priv->lq_sta.lq.general_params.flags &= + ~LINK_QUAL_FLAGS_SET_STA_TLC_RTS_MSK; + iwl_send_lq_cmd(priv, iwl_rxon_ctx_from_vif(vif), + &sta_priv->lq_sta.lq, CMD_ASYNC, false); + } + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + ret = iwlagn_tx_agg_oper(priv, vif, sta, tid, buf_size); + break; + } + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); + return ret; +} + +static int iwlagn_mac_sta_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_station_priv *sta_priv = (void *)sta->drv_priv; + struct iwl_vif_priv *vif_priv = (void *)vif->drv_priv; + bool is_ap = vif->type == NL80211_IFTYPE_STATION; + int ret; + u8 sta_id; + + IWL_DEBUG_INFO(priv, "proceeding to add station %pM\n", + sta->addr); + sta_priv->sta_id = IWL_INVALID_STATION; + + atomic_set(&sta_priv->pending_frames, 0); + if (vif->type == NL80211_IFTYPE_AP) + sta_priv->client = true; + + ret = iwl_add_station_common(priv, vif_priv->ctx, sta->addr, + is_ap, sta, &sta_id); + if (ret) { + IWL_ERR(priv, "Unable to add station %pM (%d)\n", + sta->addr, ret); + /* Should we return success if return code is EEXIST ? */ + return ret; + } + + sta_priv->sta_id = sta_id; + + return 0; +} + +static int iwlagn_mac_sta_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_station_priv *sta_priv = (void *)sta->drv_priv; + int ret; + + IWL_DEBUG_INFO(priv, "proceeding to remove station %pM\n", sta->addr); + + if (vif->type == NL80211_IFTYPE_STATION) { + /* + * Station will be removed from device when the RXON + * is set to unassociated -- just deactivate it here + * to avoid re-programming it. + */ + ret = 0; + iwl_deactivate_station(priv, sta_priv->sta_id, sta->addr); + } else { + ret = iwl_remove_station(priv, sta_priv->sta_id, sta->addr); + if (ret) + IWL_DEBUG_QUIET_RFKILL(priv, + "Error removing station %pM\n", sta->addr); + } + return ret; +} + +static int iwlagn_mac_sta_state(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + enum ieee80211_sta_state old_state, + enum ieee80211_sta_state new_state) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_vif_priv *vif_priv = (void *)vif->drv_priv; + enum { + NONE, ADD, REMOVE, HT_RATE_INIT, ADD_RATE_INIT, + } op = NONE; + int ret; + + IWL_DEBUG_MAC80211(priv, "station %pM state change %d->%d\n", + sta->addr, old_state, new_state); + + mutex_lock(&priv->mutex); + if (vif->type == NL80211_IFTYPE_STATION) { + if (old_state == IEEE80211_STA_NOTEXIST && + new_state == IEEE80211_STA_NONE) + op = ADD; + else if (old_state == IEEE80211_STA_NONE && + new_state == IEEE80211_STA_NOTEXIST) + op = REMOVE; + else if (old_state == IEEE80211_STA_AUTH && + new_state == IEEE80211_STA_ASSOC) + op = HT_RATE_INIT; + } else { + if (old_state == IEEE80211_STA_AUTH && + new_state == IEEE80211_STA_ASSOC) + op = ADD_RATE_INIT; + else if (old_state == IEEE80211_STA_ASSOC && + new_state == IEEE80211_STA_AUTH) + op = REMOVE; + } + + switch (op) { + case ADD: + ret = iwlagn_mac_sta_add(hw, vif, sta); + if (ret) + break; + /* + * Clear the in-progress flag, the AP station entry was added + * but we'll initialize LQ only when we've associated (which + * would also clear the in-progress flag). This is necessary + * in case we never initialize LQ because association fails. + */ + spin_lock_bh(&priv->sta_lock); + priv->stations[iwl_sta_id(sta)].used &= + ~IWL_STA_UCODE_INPROGRESS; + spin_unlock_bh(&priv->sta_lock); + break; + case REMOVE: + ret = iwlagn_mac_sta_remove(hw, vif, sta); + break; + case ADD_RATE_INIT: + ret = iwlagn_mac_sta_add(hw, vif, sta); + if (ret) + break; + /* Initialize rate scaling */ + IWL_DEBUG_INFO(priv, + "Initializing rate scaling for station %pM\n", + sta->addr); + iwl_rs_rate_init(priv, sta, iwl_sta_id(sta)); + ret = 0; + break; + case HT_RATE_INIT: + /* Initialize rate scaling */ + ret = iwl_sta_update_ht(priv, vif_priv->ctx, sta); + if (ret) + break; + IWL_DEBUG_INFO(priv, + "Initializing rate scaling for station %pM\n", + sta->addr); + iwl_rs_rate_init(priv, sta, iwl_sta_id(sta)); + ret = 0; + break; + default: + ret = 0; + break; + } + + /* + * mac80211 might WARN if we fail, but due the way we + * (badly) handle hard rfkill, we might fail here + */ + if (iwl_is_rfkill(priv)) + ret = 0; + + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); + + return ret; +} + +static void iwlagn_mac_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_channel_switch *ch_switch) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct ieee80211_conf *conf = &hw->conf; + struct ieee80211_channel *channel = ch_switch->chandef.chan; + struct iwl_ht_config *ht_conf = &priv->current_ht_config; + /* + * MULTI-FIXME + * When we add support for multiple interfaces, we need to + * revisit this. The channel switch command in the device + * only affects the BSS context, but what does that really + * mean? And what if we get a CSA on the second interface? + * This needs a lot of work. + */ + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + u16 ch; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + mutex_lock(&priv->mutex); + + if (iwl_is_rfkill(priv)) + goto out; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status) || + test_bit(STATUS_SCANNING, &priv->status) || + test_bit(STATUS_CHANNEL_SWITCH_PENDING, &priv->status)) + goto out; + + if (!iwl_is_associated_ctx(ctx)) + goto out; + + if (!priv->lib->set_channel_switch) + goto out; + + ch = channel->hw_value; + if (le16_to_cpu(ctx->active.channel) == ch) + goto out; + + priv->current_ht_config.smps = conf->smps_mode; + + /* Configure HT40 channels */ + switch (cfg80211_get_chandef_type(&ch_switch->chandef)) { + case NL80211_CHAN_NO_HT: + case NL80211_CHAN_HT20: + ctx->ht.is_40mhz = false; + ctx->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE; + break; + case NL80211_CHAN_HT40MINUS: + ctx->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW; + ctx->ht.is_40mhz = true; + break; + case NL80211_CHAN_HT40PLUS: + ctx->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + ctx->ht.is_40mhz = true; + break; + } + + if ((le16_to_cpu(ctx->staging.channel) != ch)) + ctx->staging.flags = 0; + + iwl_set_rxon_channel(priv, channel, ctx); + iwl_set_rxon_ht(priv, ht_conf); + iwl_set_flags_for_band(priv, ctx, channel->band, ctx->vif); + + /* + * at this point, staging_rxon has the + * configuration for channel switch + */ + set_bit(STATUS_CHANNEL_SWITCH_PENDING, &priv->status); + priv->switch_channel = cpu_to_le16(ch); + if (priv->lib->set_channel_switch(priv, ch_switch)) { + clear_bit(STATUS_CHANNEL_SWITCH_PENDING, &priv->status); + priv->switch_channel = 0; + ieee80211_chswitch_done(ctx->vif, false); + } + +out: + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); +} + +void iwl_chswitch_done(struct iwl_priv *priv, bool is_success) +{ + /* + * MULTI-FIXME + * See iwlagn_mac_channel_switch. + */ + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + if (!test_and_clear_bit(STATUS_CHANNEL_SWITCH_PENDING, &priv->status)) + return; + + if (ctx->vif) + ieee80211_chswitch_done(ctx->vif, is_success); +} + +static void iwlagn_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + __le32 filter_or = 0, filter_nand = 0; + struct iwl_rxon_context *ctx; + +#define CHK(test, flag) do { \ + if (*total_flags & (test)) \ + filter_or |= (flag); \ + else \ + filter_nand |= (flag); \ + } while (0) + + IWL_DEBUG_MAC80211(priv, "Enter: changed: 0x%x, total: 0x%x\n", + changed_flags, *total_flags); + + CHK(FIF_OTHER_BSS | FIF_PROMISC_IN_BSS, RXON_FILTER_PROMISC_MSK); + /* Setting _just_ RXON_FILTER_CTL2HOST_MSK causes FH errors */ + CHK(FIF_CONTROL, RXON_FILTER_CTL2HOST_MSK | RXON_FILTER_PROMISC_MSK); + CHK(FIF_BCN_PRBRESP_PROMISC, RXON_FILTER_BCON_AWARE_MSK); + +#undef CHK + + mutex_lock(&priv->mutex); + + for_each_context(priv, ctx) { + ctx->staging.filter_flags &= ~filter_nand; + ctx->staging.filter_flags |= filter_or; + + /* + * Not committing directly because hardware can perform a scan, + * but we'll eventually commit the filter flags change anyway. + */ + } + + mutex_unlock(&priv->mutex); + + /* + * Receiving all multicast frames is always enabled by the + * default flags setup in iwl_connection_init_rx_config() + * since we currently do not support programming multicast + * filters into the device. + */ + *total_flags &= FIF_OTHER_BSS | FIF_ALLMULTI | FIF_PROMISC_IN_BSS | + FIF_BCN_PRBRESP_PROMISC | FIF_CONTROL; +} + +static void iwlagn_mac_flush(struct ieee80211_hw *hw, u32 queues, bool drop) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + mutex_lock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "enter\n"); + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) { + IWL_DEBUG_TX(priv, "Aborting flush due to device shutdown\n"); + goto done; + } + if (iwl_is_rfkill(priv)) { + IWL_DEBUG_TX(priv, "Aborting flush due to RF Kill\n"); + goto done; + } + + /* + * mac80211 will not push any more frames for transmit + * until the flush is completed + */ + if (drop) { + IWL_DEBUG_MAC80211(priv, "send flush command\n"); + if (iwlagn_txfifo_flush(priv, 0)) { + IWL_ERR(priv, "flush request fail\n"); + goto done; + } + } + IWL_DEBUG_MAC80211(priv, "wait transmit/flush all frames\n"); + iwl_trans_wait_tx_queue_empty(priv->trans); +done: + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); +} + +static int iwlagn_mac_remain_on_channel(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel *channel, + int duration, + enum ieee80211_roc_type type) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_PAN]; + int err = 0; + + if (!(priv->valid_contexts & BIT(IWL_RXON_CTX_PAN))) + return -EOPNOTSUPP; + + if (!(ctx->interface_modes & BIT(NL80211_IFTYPE_P2P_CLIENT))) + return -EOPNOTSUPP; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + mutex_lock(&priv->mutex); + + if (test_bit(STATUS_SCAN_HW, &priv->status)) { + /* mac80211 should not scan while ROC or ROC while scanning */ + if (WARN_ON_ONCE(priv->scan_type != IWL_SCAN_RADIO_RESET)) { + err = -EBUSY; + goto out; + } + + iwl_scan_cancel_timeout(priv, 100); + + if (test_bit(STATUS_SCAN_HW, &priv->status)) { + err = -EBUSY; + goto out; + } + } + + priv->hw_roc_channel = channel; + /* convert from ms to TU */ + priv->hw_roc_duration = DIV_ROUND_UP(1000 * duration, 1024); + priv->hw_roc_start_notified = false; + cancel_delayed_work(&priv->hw_roc_disable_work); + + if (!ctx->is_active) { + static const struct iwl_qos_info default_qos_data = { + .def_qos_parm = { + .ac[0] = { + .cw_min = cpu_to_le16(3), + .cw_max = cpu_to_le16(7), + .aifsn = 2, + .edca_txop = cpu_to_le16(1504), + }, + .ac[1] = { + .cw_min = cpu_to_le16(7), + .cw_max = cpu_to_le16(15), + .aifsn = 2, + .edca_txop = cpu_to_le16(3008), + }, + .ac[2] = { + .cw_min = cpu_to_le16(15), + .cw_max = cpu_to_le16(1023), + .aifsn = 3, + }, + .ac[3] = { + .cw_min = cpu_to_le16(15), + .cw_max = cpu_to_le16(1023), + .aifsn = 7, + }, + }, + }; + + ctx->is_active = true; + ctx->qos_data = default_qos_data; + ctx->staging.dev_type = RXON_DEV_TYPE_P2P; + memcpy(ctx->staging.node_addr, + priv->contexts[IWL_RXON_CTX_BSS].staging.node_addr, + ETH_ALEN); + memcpy(ctx->staging.bssid_addr, + priv->contexts[IWL_RXON_CTX_BSS].staging.node_addr, + ETH_ALEN); + err = iwlagn_commit_rxon(priv, ctx); + if (err) + goto out; + ctx->staging.filter_flags |= RXON_FILTER_ASSOC_MSK | + RXON_FILTER_PROMISC_MSK | + RXON_FILTER_CTL2HOST_MSK; + + err = iwlagn_commit_rxon(priv, ctx); + if (err) { + iwlagn_disable_roc(priv); + goto out; + } + priv->hw_roc_setup = true; + } + + err = iwl_scan_initiate(priv, ctx->vif, IWL_SCAN_ROC, channel->band); + if (err) + iwlagn_disable_roc(priv); + + out: + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); + + return err; +} + +static int iwlagn_mac_cancel_remain_on_channel(struct ieee80211_hw *hw) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + if (!(priv->valid_contexts & BIT(IWL_RXON_CTX_PAN))) + return -EOPNOTSUPP; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + mutex_lock(&priv->mutex); + iwl_scan_cancel_timeout(priv, priv->hw_roc_duration); + iwlagn_disable_roc(priv); + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); + + return 0; +} + +static void iwlagn_mac_rssi_callback(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_rssi_event rssi_event) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + IWL_DEBUG_MAC80211(priv, "enter\n"); + mutex_lock(&priv->mutex); + + if (priv->cfg->bt_params && + priv->cfg->bt_params->advanced_bt_coexist) { + if (rssi_event == RSSI_EVENT_LOW) + priv->bt_enable_pspoll = true; + else if (rssi_event == RSSI_EVENT_HIGH) + priv->bt_enable_pspoll = false; + + iwlagn_send_advance_bt_config(priv); + } else { + IWL_DEBUG_MAC80211(priv, "Advanced BT coex disabled," + "ignoring RSSI callback\n"); + } + + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); +} + +static int iwlagn_mac_set_tim(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, bool set) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + queue_work(priv->workqueue, &priv->beacon_update); + + return 0; +} + +static int iwlagn_mac_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_vif_priv *vif_priv = (void *)vif->drv_priv; + struct iwl_rxon_context *ctx = vif_priv->ctx; + int q; + + if (WARN_ON(!ctx)) + return -EINVAL; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + if (!iwl_is_ready_rf(priv)) { + IWL_DEBUG_MAC80211(priv, "leave - RF not ready\n"); + return -EIO; + } + + if (queue >= AC_NUM) { + IWL_DEBUG_MAC80211(priv, "leave - queue >= AC_NUM %d\n", queue); + return 0; + } + + q = AC_NUM - 1 - queue; + + mutex_lock(&priv->mutex); + + ctx->qos_data.def_qos_parm.ac[q].cw_min = + cpu_to_le16(params->cw_min); + ctx->qos_data.def_qos_parm.ac[q].cw_max = + cpu_to_le16(params->cw_max); + ctx->qos_data.def_qos_parm.ac[q].aifsn = params->aifs; + ctx->qos_data.def_qos_parm.ac[q].edca_txop = + cpu_to_le16((params->txop * 32)); + + ctx->qos_data.def_qos_parm.ac[q].reserved1 = 0; + + mutex_unlock(&priv->mutex); + + IWL_DEBUG_MAC80211(priv, "leave\n"); + return 0; +} + +static int iwlagn_mac_tx_last_beacon(struct ieee80211_hw *hw) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + + return priv->ibss_manager == IWL_IBSS_MANAGER; +} + +static int iwl_set_mode(struct iwl_priv *priv, struct iwl_rxon_context *ctx) +{ + iwl_connection_init_rx_config(priv, ctx); + + iwlagn_set_rxon_chain(priv, ctx); + + return iwlagn_commit_rxon(priv, ctx); +} + +static int iwl_setup_interface(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + struct ieee80211_vif *vif = ctx->vif; + int err, ac; + + lockdep_assert_held(&priv->mutex); + + /* + * This variable will be correct only when there's just + * a single context, but all code using it is for hardware + * that supports only one context. + */ + priv->iw_mode = vif->type; + + ctx->is_active = true; + + err = iwl_set_mode(priv, ctx); + if (err) { + if (!ctx->always_active) + ctx->is_active = false; + return err; + } + + if (priv->cfg->bt_params && priv->cfg->bt_params->advanced_bt_coexist && + vif->type == NL80211_IFTYPE_ADHOC) { + /* + * pretend to have high BT traffic as long as we + * are operating in IBSS mode, as this will cause + * the rate scaling etc. to behave as intended. + */ + priv->bt_traffic_load = IWL_BT_COEX_TRAFFIC_LOAD_HIGH; + } + + /* set up queue mappings */ + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) + vif->hw_queue[ac] = ctx->ac_to_queue[ac]; + + if (vif->type == NL80211_IFTYPE_AP) + vif->cab_queue = ctx->mcast_queue; + else + vif->cab_queue = IEEE80211_INVAL_HW_QUEUE; + + return 0; +} + +static int iwlagn_mac_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_vif_priv *vif_priv = (void *)vif->drv_priv; + struct iwl_rxon_context *tmp, *ctx = NULL; + int err; + enum nl80211_iftype viftype = ieee80211_vif_type_p2p(vif); + bool reset = false; + + IWL_DEBUG_MAC80211(priv, "enter: type %d, addr %pM\n", + viftype, vif->addr); + + cancel_delayed_work_sync(&priv->hw_roc_disable_work); + + mutex_lock(&priv->mutex); + + iwlagn_disable_roc(priv); + + if (!iwl_is_ready_rf(priv)) { + IWL_WARN(priv, "Try to add interface when device not ready\n"); + err = -EINVAL; + goto out; + } + + for_each_context(priv, tmp) { + u32 possible_modes = + tmp->interface_modes | tmp->exclusive_interface_modes; + + if (tmp->vif) { + /* On reset we need to add the same interface again */ + if (tmp->vif == vif) { + reset = true; + ctx = tmp; + break; + } + + /* check if this busy context is exclusive */ + if (tmp->exclusive_interface_modes & + BIT(tmp->vif->type)) { + err = -EINVAL; + goto out; + } + continue; + } + + if (!(possible_modes & BIT(viftype))) + continue; + + /* have maybe usable context w/o interface */ + ctx = tmp; + break; + } + + if (!ctx) { + err = -EOPNOTSUPP; + goto out; + } + + vif_priv->ctx = ctx; + ctx->vif = vif; + + /* + * In SNIFFER device type, the firmware reports the FCS to + * the host, rather than snipping it off. Unfortunately, + * mac80211 doesn't (yet) provide a per-packet flag for + * this, so that we have to set the hardware flag based + * on the interfaces added. As the monitor interface can + * only be present by itself, and will be removed before + * other interfaces are added, this is safe. + */ + if (vif->type == NL80211_IFTYPE_MONITOR) + priv->hw->flags |= IEEE80211_HW_RX_INCLUDES_FCS; + else + priv->hw->flags &= ~IEEE80211_HW_RX_INCLUDES_FCS; + + err = iwl_setup_interface(priv, ctx); + if (!err || reset) + goto out; + + ctx->vif = NULL; + priv->iw_mode = NL80211_IFTYPE_STATION; + out: + mutex_unlock(&priv->mutex); + + IWL_DEBUG_MAC80211(priv, "leave\n"); + return err; +} + +static void iwl_teardown_interface(struct iwl_priv *priv, + struct ieee80211_vif *vif, + bool mode_change) +{ + struct iwl_rxon_context *ctx = iwl_rxon_ctx_from_vif(vif); + + lockdep_assert_held(&priv->mutex); + + if (priv->scan_vif == vif) { + iwl_scan_cancel_timeout(priv, 200); + iwl_force_scan_end(priv); + } + + if (!mode_change) { + iwl_set_mode(priv, ctx); + if (!ctx->always_active) + ctx->is_active = false; + } + + /* + * When removing the IBSS interface, overwrite the + * BT traffic load with the stored one from the last + * notification, if any. If this is a device that + * doesn't implement this, this has no effect since + * both values are the same and zero. + */ + if (vif->type == NL80211_IFTYPE_ADHOC) + priv->bt_traffic_load = priv->last_bt_traffic_load; +} + +static void iwlagn_mac_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_rxon_context *ctx = iwl_rxon_ctx_from_vif(vif); + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + mutex_lock(&priv->mutex); + + if (WARN_ON(ctx->vif != vif)) { + struct iwl_rxon_context *tmp; + IWL_ERR(priv, "ctx->vif = %p, vif = %p\n", ctx->vif, vif); + for_each_context(priv, tmp) + IWL_ERR(priv, "\tID = %d:\tctx = %p\tctx->vif = %p\n", + tmp->ctxid, tmp, tmp->vif); + } + ctx->vif = NULL; + + iwl_teardown_interface(priv, vif, false); + + mutex_unlock(&priv->mutex); + + IWL_DEBUG_MAC80211(priv, "leave\n"); + +} + +static int iwlagn_mac_change_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum nl80211_iftype newtype, bool newp2p) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_rxon_context *ctx, *tmp; + enum nl80211_iftype newviftype = newtype; + u32 interface_modes; + int err; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + newtype = ieee80211_iftype_p2p(newtype, newp2p); + + mutex_lock(&priv->mutex); + + ctx = iwl_rxon_ctx_from_vif(vif); + + /* + * To simplify this code, only support changes on the + * BSS context. The PAN context is usually reassigned + * by creating/removing P2P interfaces anyway. + */ + if (ctx->ctxid != IWL_RXON_CTX_BSS) { + err = -EBUSY; + goto out; + } + + if (!ctx->vif || !iwl_is_ready_rf(priv)) { + /* + * Huh? But wait ... this can maybe happen when + * we're in the middle of a firmware restart! + */ + err = -EBUSY; + goto out; + } + + /* Check if the switch is supported in the same context */ + interface_modes = ctx->interface_modes | ctx->exclusive_interface_modes; + if (!(interface_modes & BIT(newtype))) { + err = -EBUSY; + goto out; + } + + if (ctx->exclusive_interface_modes & BIT(newtype)) { + for_each_context(priv, tmp) { + if (ctx == tmp) + continue; + + if (!tmp->is_active) + continue; + + /* + * The current mode switch would be exclusive, but + * another context is active ... refuse the switch. + */ + err = -EBUSY; + goto out; + } + } + + /* success */ + iwl_teardown_interface(priv, vif, true); + vif->type = newviftype; + vif->p2p = newp2p; + err = iwl_setup_interface(priv, ctx); + WARN_ON(err); + /* + * We've switched internally, but submitting to the + * device may have failed for some reason. Mask this + * error, because otherwise mac80211 will not switch + * (and set the interface type back) and we'll be + * out of sync with it. + */ + err = 0; + + out: + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); + + return err; +} + +static int iwlagn_mac_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_scan_request *req) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + int ret; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + if (req->n_channels == 0) + return -EINVAL; + + mutex_lock(&priv->mutex); + + /* + * If an internal scan is in progress, just set + * up the scan_request as per above. + */ + if (priv->scan_type != IWL_SCAN_NORMAL) { + IWL_DEBUG_SCAN(priv, + "SCAN request during internal scan - defer\n"); + priv->scan_request = req; + priv->scan_vif = vif; + ret = 0; + } else { + priv->scan_request = req; + priv->scan_vif = vif; + /* + * mac80211 will only ask for one band at a time + * so using channels[0] here is ok + */ + ret = iwl_scan_initiate(priv, vif, IWL_SCAN_NORMAL, + req->channels[0]->band); + if (ret) { + priv->scan_request = NULL; + priv->scan_vif = NULL; + } + } + + IWL_DEBUG_MAC80211(priv, "leave\n"); + + mutex_unlock(&priv->mutex); + + return ret; +} + +static void iwl_sta_modify_ps_wake(struct iwl_priv *priv, int sta_id) +{ + struct iwl_addsta_cmd cmd = { + .mode = STA_CONTROL_MODIFY_MSK, + .station_flags_msk = STA_FLG_PWR_SAVE_MSK, + .sta.sta_id = sta_id, + }; + + iwl_send_add_sta(priv, &cmd, CMD_ASYNC); +} + +static void iwlagn_mac_sta_notify(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum sta_notify_cmd cmd, + struct ieee80211_sta *sta) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_station_priv *sta_priv = (void *)sta->drv_priv; + int sta_id; + + IWL_DEBUG_MAC80211(priv, "enter\n"); + + switch (cmd) { + case STA_NOTIFY_SLEEP: + WARN_ON(!sta_priv->client); + sta_priv->asleep = true; + if (atomic_read(&sta_priv->pending_frames) > 0) + ieee80211_sta_block_awake(hw, sta, true); + break; + case STA_NOTIFY_AWAKE: + WARN_ON(!sta_priv->client); + if (!sta_priv->asleep) + break; + sta_priv->asleep = false; + sta_id = iwl_sta_id(sta); + if (sta_id != IWL_INVALID_STATION) + iwl_sta_modify_ps_wake(priv, sta_id); + break; + default: + break; + } + IWL_DEBUG_MAC80211(priv, "leave\n"); +} + +struct ieee80211_ops iwlagn_hw_ops = { + .tx = iwlagn_mac_tx, + .start = iwlagn_mac_start, + .stop = iwlagn_mac_stop, +#ifdef CONFIG_PM_SLEEP + .suspend = iwlagn_mac_suspend, + .resume = iwlagn_mac_resume, + .set_wakeup = iwlagn_mac_set_wakeup, +#endif + .add_interface = iwlagn_mac_add_interface, + .remove_interface = iwlagn_mac_remove_interface, + .change_interface = iwlagn_mac_change_interface, + .config = iwlagn_mac_config, + .configure_filter = iwlagn_configure_filter, + .set_key = iwlagn_mac_set_key, + .update_tkip_key = iwlagn_mac_update_tkip_key, + .set_rekey_data = iwlagn_mac_set_rekey_data, + .conf_tx = iwlagn_mac_conf_tx, + .bss_info_changed = iwlagn_bss_info_changed, + .ampdu_action = iwlagn_mac_ampdu_action, + .hw_scan = iwlagn_mac_hw_scan, + .sta_notify = iwlagn_mac_sta_notify, + .sta_state = iwlagn_mac_sta_state, + .channel_switch = iwlagn_mac_channel_switch, + .flush = iwlagn_mac_flush, + .tx_last_beacon = iwlagn_mac_tx_last_beacon, + .remain_on_channel = iwlagn_mac_remain_on_channel, + .cancel_remain_on_channel = iwlagn_mac_cancel_remain_on_channel, + .rssi_callback = iwlagn_mac_rssi_callback, + CFG80211_TESTMODE_CMD(iwlagn_mac_testmode_cmd) + CFG80211_TESTMODE_DUMP(iwlagn_mac_testmode_dump) + .set_tim = iwlagn_mac_set_tim, +}; + +/* This function both allocates and initializes hw and priv. */ +struct ieee80211_hw *iwl_alloc_all(void) +{ + struct iwl_priv *priv; + struct iwl_op_mode *op_mode; + /* mac80211 allocates memory for this device instance, including + * space for this driver's private structure */ + struct ieee80211_hw *hw; + + hw = ieee80211_alloc_hw(sizeof(struct iwl_priv) + + sizeof(struct iwl_op_mode), &iwlagn_hw_ops); + if (!hw) + goto out; + + op_mode = hw->priv; + priv = IWL_OP_MODE_GET_DVM(op_mode); + priv->hw = hw; + +out: + return hw; +} diff --git a/drivers/net/wireless/iwlwifi/dvm/main.c b/drivers/net/wireless/iwlwifi/dvm/main.c new file mode 100644 index 0000000..0410b24 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/main.c @@ -0,0 +1,2170 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "iwl-eeprom-read.h" +#include "iwl-eeprom-parse.h" +#include "iwl-io.h" +#include "iwl-trans.h" +#include "iwl-op-mode.h" +#include "iwl-drv.h" +#include "iwl-modparams.h" +#include "iwl-prph.h" + +#include "dev.h" +#include "calib.h" +#include "agn.h" + + +/****************************************************************************** + * + * module boiler plate + * + ******************************************************************************/ + +/* + * module name, copyright, version, etc. + */ +#define DRV_DESCRIPTION "Intel(R) Wireless WiFi Link AGN driver for Linux" + +#ifdef CPTCFG_IWLWIFI_DEBUG +#define VD "d" +#else +#define VD +#endif + +#define DRV_VERSION IWLWIFI_VERSION VD + + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR(DRV_COPYRIGHT " " DRV_AUTHOR); +MODULE_LICENSE("GPL"); + +static const struct iwl_op_mode_ops iwl_dvm_ops; + +void iwl_update_chain_flags(struct iwl_priv *priv) +{ + struct iwl_rxon_context *ctx; + + for_each_context(priv, ctx) { + iwlagn_set_rxon_chain(priv, ctx); + if (ctx->active.rx_chain != ctx->staging.rx_chain) + iwlagn_commit_rxon(priv, ctx); + } +} + +/* Parse the beacon frame to find the TIM element and set tim_idx & tim_size */ +static void iwl_set_beacon_tim(struct iwl_priv *priv, + struct iwl_tx_beacon_cmd *tx_beacon_cmd, + u8 *beacon, u32 frame_size) +{ + u16 tim_idx; + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)beacon; + + /* + * The index is relative to frame start but we start looking at the + * variable-length part of the beacon. + */ + tim_idx = mgmt->u.beacon.variable - beacon; + + /* Parse variable-length elements of beacon to find WLAN_EID_TIM */ + while ((tim_idx < (frame_size - 2)) && + (beacon[tim_idx] != WLAN_EID_TIM)) + tim_idx += beacon[tim_idx+1] + 2; + + /* If TIM field was found, set variables */ + if ((tim_idx < (frame_size - 1)) && (beacon[tim_idx] == WLAN_EID_TIM)) { + tx_beacon_cmd->tim_idx = cpu_to_le16(tim_idx); + tx_beacon_cmd->tim_size = beacon[tim_idx+1]; + } else + IWL_WARN(priv, "Unable to find TIM Element in beacon\n"); +} + +int iwlagn_send_beacon_cmd(struct iwl_priv *priv) +{ + struct iwl_tx_beacon_cmd *tx_beacon_cmd; + struct iwl_host_cmd cmd = { + .id = REPLY_TX_BEACON, + .flags = CMD_SYNC, + }; + struct ieee80211_tx_info *info; + u32 frame_size; + u32 rate_flags; + u32 rate; + + /* + * We have to set up the TX command, the TX Beacon command, and the + * beacon contents. + */ + + lockdep_assert_held(&priv->mutex); + + if (!priv->beacon_ctx) { + IWL_ERR(priv, "trying to build beacon w/o beacon context!\n"); + return 0; + } + + if (WARN_ON(!priv->beacon_skb)) + return -EINVAL; + + /* Allocate beacon command */ + if (!priv->beacon_cmd) + priv->beacon_cmd = kzalloc(sizeof(*tx_beacon_cmd), GFP_KERNEL); + tx_beacon_cmd = priv->beacon_cmd; + if (!tx_beacon_cmd) + return -ENOMEM; + + frame_size = priv->beacon_skb->len; + + /* Set up TX command fields */ + tx_beacon_cmd->tx.len = cpu_to_le16((u16)frame_size); + tx_beacon_cmd->tx.sta_id = priv->beacon_ctx->bcast_sta_id; + tx_beacon_cmd->tx.stop_time.life_time = TX_CMD_LIFE_TIME_INFINITE; + tx_beacon_cmd->tx.tx_flags = TX_CMD_FLG_SEQ_CTL_MSK | + TX_CMD_FLG_TSF_MSK | TX_CMD_FLG_STA_RATE_MSK; + + /* Set up TX beacon command fields */ + iwl_set_beacon_tim(priv, tx_beacon_cmd, priv->beacon_skb->data, + frame_size); + + /* Set up packet rate and flags */ + info = IEEE80211_SKB_CB(priv->beacon_skb); + + /* + * Let's set up the rate at least somewhat correctly; + * it will currently not actually be used by the uCode, + * it uses the broadcast station's rate instead. + */ + if (info->control.rates[0].idx < 0 || + info->control.rates[0].flags & IEEE80211_TX_RC_MCS) + rate = 0; + else + rate = info->control.rates[0].idx; + + priv->mgmt_tx_ant = iwl_toggle_tx_ant(priv, priv->mgmt_tx_ant, + priv->nvm_data->valid_tx_ant); + rate_flags = iwl_ant_idx_to_flags(priv->mgmt_tx_ant); + + /* In mac80211, rates for 5 GHz start at 0 */ + if (info->band == IEEE80211_BAND_5GHZ) + rate += IWL_FIRST_OFDM_RATE; + else if (rate >= IWL_FIRST_CCK_RATE && rate <= IWL_LAST_CCK_RATE) + rate_flags |= RATE_MCS_CCK_MSK; + + tx_beacon_cmd->tx.rate_n_flags = + iwl_hw_set_rate_n_flags(rate, rate_flags); + + /* Submit command */ + cmd.len[0] = sizeof(*tx_beacon_cmd); + cmd.data[0] = tx_beacon_cmd; + cmd.dataflags[0] = IWL_HCMD_DFL_NOCOPY; + cmd.len[1] = frame_size; + cmd.data[1] = priv->beacon_skb->data; + cmd.dataflags[1] = IWL_HCMD_DFL_NOCOPY; + + return iwl_dvm_send_cmd(priv, &cmd); +} + +static void iwl_bg_beacon_update(struct work_struct *work) +{ + struct iwl_priv *priv = + container_of(work, struct iwl_priv, beacon_update); + struct sk_buff *beacon; + + mutex_lock(&priv->mutex); + if (!priv->beacon_ctx) { + IWL_ERR(priv, "updating beacon w/o beacon context!\n"); + goto out; + } + + if (priv->beacon_ctx->vif->type != NL80211_IFTYPE_AP) { + /* + * The ucode will send beacon notifications even in + * IBSS mode, but we don't want to process them. But + * we need to defer the type check to here due to + * requiring locking around the beacon_ctx access. + */ + goto out; + } + + /* Pull updated AP beacon from mac80211. will fail if not in AP mode */ + beacon = ieee80211_beacon_get(priv->hw, priv->beacon_ctx->vif); + if (!beacon) { + IWL_ERR(priv, "update beacon failed -- keeping old\n"); + goto out; + } + + /* new beacon skb is allocated every time; dispose previous.*/ + dev_kfree_skb(priv->beacon_skb); + + priv->beacon_skb = beacon; + + iwlagn_send_beacon_cmd(priv); + out: + mutex_unlock(&priv->mutex); +} + +static void iwl_bg_bt_runtime_config(struct work_struct *work) +{ + struct iwl_priv *priv = + container_of(work, struct iwl_priv, bt_runtime_config); + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + /* dont send host command if rf-kill is on */ + if (!iwl_is_ready_rf(priv)) + return; + iwlagn_send_advance_bt_config(priv); +} + +static void iwl_bg_bt_full_concurrency(struct work_struct *work) +{ + struct iwl_priv *priv = + container_of(work, struct iwl_priv, bt_full_concurrency); + struct iwl_rxon_context *ctx; + + mutex_lock(&priv->mutex); + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + goto out; + + /* dont send host command if rf-kill is on */ + if (!iwl_is_ready_rf(priv)) + goto out; + + IWL_DEBUG_INFO(priv, "BT coex in %s mode\n", + priv->bt_full_concurrent ? + "full concurrency" : "3-wire"); + + /* + * LQ & RXON updated cmds must be sent before BT Config cmd + * to avoid 3-wire collisions + */ + for_each_context(priv, ctx) { + iwlagn_set_rxon_chain(priv, ctx); + iwlagn_commit_rxon(priv, ctx); + } + + iwlagn_send_advance_bt_config(priv); +out: + mutex_unlock(&priv->mutex); +} + +int iwl_send_statistics_request(struct iwl_priv *priv, u8 flags, bool clear) +{ + struct iwl_statistics_cmd statistics_cmd = { + .configuration_flags = + clear ? IWL_STATS_CONF_CLEAR_STATS : 0, + }; + + if (flags & CMD_ASYNC) + return iwl_dvm_send_cmd_pdu(priv, REPLY_STATISTICS_CMD, + CMD_ASYNC, + sizeof(struct iwl_statistics_cmd), + &statistics_cmd); + else + return iwl_dvm_send_cmd_pdu(priv, REPLY_STATISTICS_CMD, + CMD_SYNC, + sizeof(struct iwl_statistics_cmd), + &statistics_cmd); +} + +/** + * iwl_bg_statistics_periodic - Timer callback to queue statistics + * + * This callback is provided in order to send a statistics request. + * + * This timer function is continually reset to execute within + * REG_RECALIB_PERIOD seconds since the last STATISTICS_NOTIFICATION + * was received. We need to ensure we receive the statistics in order + * to update the temperature used for calibrating the TXPOWER. + */ +static void iwl_bg_statistics_periodic(unsigned long data) +{ + struct iwl_priv *priv = (struct iwl_priv *)data; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + /* dont send host command if rf-kill is on */ + if (!iwl_is_ready_rf(priv)) + return; + + iwl_send_statistics_request(priv, CMD_ASYNC, false); +} + + +static void iwl_print_cont_event_trace(struct iwl_priv *priv, u32 base, + u32 start_idx, u32 num_events, + u32 capacity, u32 mode) +{ + u32 i; + u32 ptr; /* SRAM byte address of log data */ + u32 ev, time, data; /* event log data */ + unsigned long reg_flags; + + if (mode == 0) + ptr = base + (4 * sizeof(u32)) + (start_idx * 2 * sizeof(u32)); + else + ptr = base + (4 * sizeof(u32)) + (start_idx * 3 * sizeof(u32)); + + /* Make sure device is powered up for SRAM reads */ + if (!iwl_trans_grab_nic_access(priv->trans, false, ®_flags)) + return; + + /* Set starting address; reads will auto-increment */ + iwl_write32(priv->trans, HBUS_TARG_MEM_RADDR, ptr); + + /* + * Refuse to read more than would have fit into the log from + * the current start_idx. This used to happen due to the race + * described below, but now WARN because the code below should + * prevent it from happening here. + */ + if (WARN_ON(num_events > capacity - start_idx)) + num_events = capacity - start_idx; + + /* + * "time" is actually "data" for mode 0 (no timestamp). + * place event id # at far right for easier visual parsing. + */ + for (i = 0; i < num_events; i++) { + ev = iwl_read32(priv->trans, HBUS_TARG_MEM_RDAT); + time = iwl_read32(priv->trans, HBUS_TARG_MEM_RDAT); + if (mode == 0) { + trace_iwlwifi_dev_ucode_cont_event( + priv->trans->dev, 0, time, ev); + } else { + data = iwl_read32(priv->trans, HBUS_TARG_MEM_RDAT); + trace_iwlwifi_dev_ucode_cont_event( + priv->trans->dev, time, data, ev); + } + } + /* Allow device to power down */ + iwl_trans_release_nic_access(priv->trans, ®_flags); +} + +static void iwl_continuous_event_trace(struct iwl_priv *priv) +{ + u32 capacity; /* event log capacity in # entries */ + struct { + u32 capacity; + u32 mode; + u32 wrap_counter; + u32 write_counter; + } __packed read; + u32 base; /* SRAM byte address of event log header */ + u32 mode; /* 0 - no timestamp, 1 - timestamp recorded */ + u32 num_wraps; /* # times uCode wrapped to top of log */ + u32 next_entry; /* index of next entry to be written by uCode */ + + base = priv->device_pointers.log_event_table; + if (iwlagn_hw_valid_rtc_data_addr(base)) { + iwl_trans_read_mem_bytes(priv->trans, base, + &read, sizeof(read)); + capacity = read.capacity; + mode = read.mode; + num_wraps = read.wrap_counter; + next_entry = read.write_counter; + } else + return; + + /* + * Unfortunately, the uCode doesn't use temporary variables. + * Therefore, it can happen that we read next_entry == capacity, + * which really means next_entry == 0. + */ + if (unlikely(next_entry == capacity)) + next_entry = 0; + /* + * Additionally, the uCode increases the write pointer before + * the wraps counter, so if the write pointer is smaller than + * the old write pointer (wrap occurred) but we read that no + * wrap occurred, we actually read between the next_entry and + * num_wraps update (this does happen in practice!!) -- take + * that into account by increasing num_wraps. + */ + if (unlikely(next_entry < priv->event_log.next_entry && + num_wraps == priv->event_log.num_wraps)) + num_wraps++; + + if (num_wraps == priv->event_log.num_wraps) { + iwl_print_cont_event_trace( + priv, base, priv->event_log.next_entry, + next_entry - priv->event_log.next_entry, + capacity, mode); + + priv->event_log.non_wraps_count++; + } else { + if (num_wraps - priv->event_log.num_wraps > 1) + priv->event_log.wraps_more_count++; + else + priv->event_log.wraps_once_count++; + + trace_iwlwifi_dev_ucode_wrap_event(priv->trans->dev, + num_wraps - priv->event_log.num_wraps, + next_entry, priv->event_log.next_entry); + + if (next_entry < priv->event_log.next_entry) { + iwl_print_cont_event_trace( + priv, base, priv->event_log.next_entry, + capacity - priv->event_log.next_entry, + capacity, mode); + + iwl_print_cont_event_trace( + priv, base, 0, next_entry, capacity, mode); + } else { + iwl_print_cont_event_trace( + priv, base, next_entry, + capacity - next_entry, + capacity, mode); + + iwl_print_cont_event_trace( + priv, base, 0, next_entry, capacity, mode); + } + } + + priv->event_log.num_wraps = num_wraps; + priv->event_log.next_entry = next_entry; +} + +/** + * iwl_bg_ucode_trace - Timer callback to log ucode event + * + * The timer is continually set to execute every + * UCODE_TRACE_PERIOD milliseconds after the last timer expired + * this function is to perform continuous uCode event logging operation + * if enabled + */ +static void iwl_bg_ucode_trace(unsigned long data) +{ + struct iwl_priv *priv = (struct iwl_priv *)data; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + if (priv->event_log.ucode_trace) { + iwl_continuous_event_trace(priv); + /* Reschedule the timer to occur in UCODE_TRACE_PERIOD */ + mod_timer(&priv->ucode_trace, + jiffies + msecs_to_jiffies(UCODE_TRACE_PERIOD)); + } +} + +static void iwl_bg_tx_flush(struct work_struct *work) +{ + struct iwl_priv *priv = + container_of(work, struct iwl_priv, tx_flush); + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + /* do nothing if rf-kill is on */ + if (!iwl_is_ready_rf(priv)) + return; + + IWL_DEBUG_INFO(priv, "device request: flush all tx frames\n"); + iwlagn_dev_txfifo_flush(priv); +} + +/* + * queue/FIFO/AC mapping definitions + */ + +static const u8 iwlagn_bss_ac_to_fifo[] = { + IWL_TX_FIFO_VO, + IWL_TX_FIFO_VI, + IWL_TX_FIFO_BE, + IWL_TX_FIFO_BK, +}; + +static const u8 iwlagn_bss_ac_to_queue[] = { + 0, 1, 2, 3, +}; + +static const u8 iwlagn_pan_ac_to_fifo[] = { + IWL_TX_FIFO_VO_IPAN, + IWL_TX_FIFO_VI_IPAN, + IWL_TX_FIFO_BE_IPAN, + IWL_TX_FIFO_BK_IPAN, +}; + +static const u8 iwlagn_pan_ac_to_queue[] = { + 7, 6, 5, 4, +}; + +static void iwl_init_context(struct iwl_priv *priv, u32 ucode_flags) +{ + int i; + + /* + * The default context is always valid, + * the PAN context depends on uCode. + */ + priv->valid_contexts = BIT(IWL_RXON_CTX_BSS); + if (ucode_flags & IWL_UCODE_TLV_FLAGS_PAN) + priv->valid_contexts |= BIT(IWL_RXON_CTX_PAN); + + for (i = 0; i < NUM_IWL_RXON_CTX; i++) + priv->contexts[i].ctxid = i; + + priv->contexts[IWL_RXON_CTX_BSS].always_active = true; + priv->contexts[IWL_RXON_CTX_BSS].is_active = true; + priv->contexts[IWL_RXON_CTX_BSS].rxon_cmd = REPLY_RXON; + priv->contexts[IWL_RXON_CTX_BSS].rxon_timing_cmd = REPLY_RXON_TIMING; + priv->contexts[IWL_RXON_CTX_BSS].rxon_assoc_cmd = REPLY_RXON_ASSOC; + priv->contexts[IWL_RXON_CTX_BSS].qos_cmd = REPLY_QOS_PARAM; + priv->contexts[IWL_RXON_CTX_BSS].ap_sta_id = IWL_AP_ID; + priv->contexts[IWL_RXON_CTX_BSS].wep_key_cmd = REPLY_WEPKEY; + priv->contexts[IWL_RXON_CTX_BSS].bcast_sta_id = IWLAGN_BROADCAST_ID; + priv->contexts[IWL_RXON_CTX_BSS].exclusive_interface_modes = + BIT(NL80211_IFTYPE_ADHOC) | BIT(NL80211_IFTYPE_MONITOR); + priv->contexts[IWL_RXON_CTX_BSS].interface_modes = + BIT(NL80211_IFTYPE_STATION); + priv->contexts[IWL_RXON_CTX_BSS].ap_devtype = RXON_DEV_TYPE_AP; + priv->contexts[IWL_RXON_CTX_BSS].ibss_devtype = RXON_DEV_TYPE_IBSS; + priv->contexts[IWL_RXON_CTX_BSS].station_devtype = RXON_DEV_TYPE_ESS; + priv->contexts[IWL_RXON_CTX_BSS].unused_devtype = RXON_DEV_TYPE_ESS; + memcpy(priv->contexts[IWL_RXON_CTX_BSS].ac_to_queue, + iwlagn_bss_ac_to_queue, sizeof(iwlagn_bss_ac_to_queue)); + memcpy(priv->contexts[IWL_RXON_CTX_BSS].ac_to_fifo, + iwlagn_bss_ac_to_fifo, sizeof(iwlagn_bss_ac_to_fifo)); + + priv->contexts[IWL_RXON_CTX_PAN].rxon_cmd = REPLY_WIPAN_RXON; + priv->contexts[IWL_RXON_CTX_PAN].rxon_timing_cmd = + REPLY_WIPAN_RXON_TIMING; + priv->contexts[IWL_RXON_CTX_PAN].rxon_assoc_cmd = + REPLY_WIPAN_RXON_ASSOC; + priv->contexts[IWL_RXON_CTX_PAN].qos_cmd = REPLY_WIPAN_QOS_PARAM; + priv->contexts[IWL_RXON_CTX_PAN].ap_sta_id = IWL_AP_ID_PAN; + priv->contexts[IWL_RXON_CTX_PAN].wep_key_cmd = REPLY_WIPAN_WEPKEY; + priv->contexts[IWL_RXON_CTX_PAN].bcast_sta_id = IWLAGN_PAN_BCAST_ID; + priv->contexts[IWL_RXON_CTX_PAN].station_flags = STA_FLG_PAN_STATION; + priv->contexts[IWL_RXON_CTX_PAN].interface_modes = + BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP); + + if (ucode_flags & IWL_UCODE_TLV_FLAGS_P2P) + priv->contexts[IWL_RXON_CTX_PAN].interface_modes |= + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO); + + priv->contexts[IWL_RXON_CTX_PAN].ap_devtype = RXON_DEV_TYPE_CP; + priv->contexts[IWL_RXON_CTX_PAN].station_devtype = RXON_DEV_TYPE_2STA; + priv->contexts[IWL_RXON_CTX_PAN].unused_devtype = RXON_DEV_TYPE_P2P; + memcpy(priv->contexts[IWL_RXON_CTX_PAN].ac_to_queue, + iwlagn_pan_ac_to_queue, sizeof(iwlagn_pan_ac_to_queue)); + memcpy(priv->contexts[IWL_RXON_CTX_PAN].ac_to_fifo, + iwlagn_pan_ac_to_fifo, sizeof(iwlagn_pan_ac_to_fifo)); + priv->contexts[IWL_RXON_CTX_PAN].mcast_queue = IWL_IPAN_MCAST_QUEUE; + + BUILD_BUG_ON(NUM_IWL_RXON_CTX != 2); +} + +static void iwl_rf_kill_ct_config(struct iwl_priv *priv) +{ + struct iwl_ct_kill_config cmd; + struct iwl_ct_kill_throttling_config adv_cmd; + int ret = 0; + + iwl_write32(priv->trans, CSR_UCODE_DRV_GP1_CLR, + CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT); + + priv->thermal_throttle.ct_kill_toggle = false; + + if (priv->cfg->base_params->support_ct_kill_exit) { + adv_cmd.critical_temperature_enter = + cpu_to_le32(priv->hw_params.ct_kill_threshold); + adv_cmd.critical_temperature_exit = + cpu_to_le32(priv->hw_params.ct_kill_exit_threshold); + + ret = iwl_dvm_send_cmd_pdu(priv, + REPLY_CT_KILL_CONFIG_CMD, + CMD_SYNC, sizeof(adv_cmd), &adv_cmd); + if (ret) + IWL_ERR(priv, "REPLY_CT_KILL_CONFIG_CMD failed\n"); + else + IWL_DEBUG_INFO(priv, "REPLY_CT_KILL_CONFIG_CMD " + "succeeded, critical temperature enter is %d," + "exit is %d\n", + priv->hw_params.ct_kill_threshold, + priv->hw_params.ct_kill_exit_threshold); + } else { + cmd.critical_temperature_R = + cpu_to_le32(priv->hw_params.ct_kill_threshold); + + ret = iwl_dvm_send_cmd_pdu(priv, + REPLY_CT_KILL_CONFIG_CMD, + CMD_SYNC, sizeof(cmd), &cmd); + if (ret) + IWL_ERR(priv, "REPLY_CT_KILL_CONFIG_CMD failed\n"); + else + IWL_DEBUG_INFO(priv, "REPLY_CT_KILL_CONFIG_CMD " + "succeeded, " + "critical temperature is %d\n", + priv->hw_params.ct_kill_threshold); + } +} + +static int iwlagn_send_calib_cfg_rt(struct iwl_priv *priv, u32 cfg) +{ + struct iwl_calib_cfg_cmd calib_cfg_cmd; + struct iwl_host_cmd cmd = { + .id = CALIBRATION_CFG_CMD, + .len = { sizeof(struct iwl_calib_cfg_cmd), }, + .data = { &calib_cfg_cmd, }, + }; + + memset(&calib_cfg_cmd, 0, sizeof(calib_cfg_cmd)); + calib_cfg_cmd.ucd_calib_cfg.once.is_enable = IWL_CALIB_RT_CFG_ALL; + calib_cfg_cmd.ucd_calib_cfg.once.start = cpu_to_le32(cfg); + + return iwl_dvm_send_cmd(priv, &cmd); +} + + +static int iwlagn_send_tx_ant_config(struct iwl_priv *priv, u8 valid_tx_ant) +{ + struct iwl_tx_ant_config_cmd tx_ant_cmd = { + .valid = cpu_to_le32(valid_tx_ant), + }; + + if (IWL_UCODE_API(priv->fw->ucode_ver) > 1) { + IWL_DEBUG_HC(priv, "select valid tx ant: %u\n", valid_tx_ant); + return iwl_dvm_send_cmd_pdu(priv, + TX_ANT_CONFIGURATION_CMD, + CMD_SYNC, + sizeof(struct iwl_tx_ant_config_cmd), + &tx_ant_cmd); + } else { + IWL_DEBUG_HC(priv, "TX_ANT_CONFIGURATION_CMD not supported\n"); + return -EOPNOTSUPP; + } +} + +static void iwl_send_bt_config(struct iwl_priv *priv) +{ + struct iwl_bt_cmd bt_cmd = { + .lead_time = BT_LEAD_TIME_DEF, + .max_kill = BT_MAX_KILL_DEF, + .kill_ack_mask = 0, + .kill_cts_mask = 0, + }; + + if (!iwlwifi_mod_params.bt_coex_active) + bt_cmd.flags = BT_COEX_DISABLE; + else + bt_cmd.flags = BT_COEX_ENABLE; + + priv->bt_enable_flag = bt_cmd.flags; + IWL_DEBUG_INFO(priv, "BT coex %s\n", + (bt_cmd.flags == BT_COEX_DISABLE) ? "disable" : "active"); + + if (iwl_dvm_send_cmd_pdu(priv, REPLY_BT_CONFIG, + CMD_SYNC, sizeof(struct iwl_bt_cmd), &bt_cmd)) + IWL_ERR(priv, "failed to send BT Coex Config\n"); +} + +/** + * iwl_alive_start - called after REPLY_ALIVE notification received + * from protocol/runtime uCode (initialization uCode's + * Alive gets handled by iwl_init_alive_start()). + */ +int iwl_alive_start(struct iwl_priv *priv) +{ + int ret = 0; + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + + IWL_DEBUG_INFO(priv, "Runtime Alive received.\n"); + + /* After the ALIVE response, we can send host commands to the uCode */ + set_bit(STATUS_ALIVE, &priv->status); + + if (iwl_is_rfkill(priv)) + return -ERFKILL; + + if (priv->event_log.ucode_trace) { + /* start collecting data now */ + mod_timer(&priv->ucode_trace, jiffies); + } + + /* download priority table before any calibration request */ + if (priv->cfg->bt_params && + priv->cfg->bt_params->advanced_bt_coexist) { + /* Configure Bluetooth device coexistence support */ + if (priv->cfg->bt_params->bt_sco_disable) + priv->bt_enable_pspoll = false; + else + priv->bt_enable_pspoll = true; + + priv->bt_valid = IWLAGN_BT_ALL_VALID_MSK; + priv->kill_ack_mask = IWLAGN_BT_KILL_ACK_MASK_DEFAULT; + priv->kill_cts_mask = IWLAGN_BT_KILL_CTS_MASK_DEFAULT; + iwlagn_send_advance_bt_config(priv); + priv->bt_valid = IWLAGN_BT_VALID_ENABLE_FLAGS; + priv->cur_rssi_ctx = NULL; + + iwl_send_prio_tbl(priv); + + /* FIXME: w/a to force change uCode BT state machine */ + ret = iwl_send_bt_env(priv, IWL_BT_COEX_ENV_OPEN, + BT_COEX_PRIO_TBL_EVT_INIT_CALIB2); + if (ret) + return ret; + ret = iwl_send_bt_env(priv, IWL_BT_COEX_ENV_CLOSE, + BT_COEX_PRIO_TBL_EVT_INIT_CALIB2); + if (ret) + return ret; + } else if (priv->cfg->bt_params) { + /* + * default is 2-wire BT coexexistence support + */ + iwl_send_bt_config(priv); + } + + /* + * Perform runtime calibrations, including DC calibration. + */ + iwlagn_send_calib_cfg_rt(priv, IWL_CALIB_CFG_DC_IDX); + + ieee80211_wake_queues(priv->hw); + + /* Configure Tx antenna selection based on H/W config */ + iwlagn_send_tx_ant_config(priv, priv->nvm_data->valid_tx_ant); + + if (iwl_is_associated_ctx(ctx) && !priv->wowlan) { + struct iwl_rxon_cmd *active_rxon = + (struct iwl_rxon_cmd *)&ctx->active; + /* apply any changes in staging */ + ctx->staging.filter_flags |= RXON_FILTER_ASSOC_MSK; + active_rxon->filter_flags &= ~RXON_FILTER_ASSOC_MSK; + } else { + struct iwl_rxon_context *tmp; + /* Initialize our rx_config data */ + for_each_context(priv, tmp) + iwl_connection_init_rx_config(priv, tmp); + + iwlagn_set_rxon_chain(priv, ctx); + } + + if (!priv->wowlan) { + /* WoWLAN ucode will not reply in the same way, skip it */ + iwl_reset_run_time_calib(priv); + } + + set_bit(STATUS_READY, &priv->status); + + /* Configure the adapter for unassociated operation */ + ret = iwlagn_commit_rxon(priv, ctx); + if (ret) + return ret; + + /* At this point, the NIC is initialized and operational */ + iwl_rf_kill_ct_config(priv); + + IWL_DEBUG_INFO(priv, "ALIVE processing complete.\n"); + + return iwl_power_update_mode(priv, true); +} + +/** + * iwl_clear_driver_stations - clear knowledge of all stations from driver + * @priv: iwl priv struct + * + * This is called during iwl_down() to make sure that in the case + * we're coming there from a hardware restart mac80211 will be + * able to reconfigure stations -- if we're getting there in the + * normal down flow then the stations will already be cleared. + */ +static void iwl_clear_driver_stations(struct iwl_priv *priv) +{ + struct iwl_rxon_context *ctx; + + spin_lock_bh(&priv->sta_lock); + memset(priv->stations, 0, sizeof(priv->stations)); + priv->num_stations = 0; + + priv->ucode_key_table = 0; + + for_each_context(priv, ctx) { + /* + * Remove all key information that is not stored as part + * of station information since mac80211 may not have had + * a chance to remove all the keys. When device is + * reconfigured by mac80211 after an error all keys will + * be reconfigured. + */ + memset(ctx->wep_keys, 0, sizeof(ctx->wep_keys)); + ctx->key_mapping_keys = 0; + } + + spin_unlock_bh(&priv->sta_lock); +} + +void iwl_down(struct iwl_priv *priv) +{ + int exit_pending; + + IWL_DEBUG_INFO(priv, DRV_NAME " is going down\n"); + + lockdep_assert_held(&priv->mutex); + + iwl_scan_cancel_timeout(priv, 200); + + /* + * If active, scanning won't cancel it, so say it expired. + * No race since we hold the mutex here and a new one + * can't come in at this time. + */ + if (priv->ucode_loaded && priv->cur_ucode != IWL_UCODE_INIT) + ieee80211_remain_on_channel_expired(priv->hw); + + exit_pending = + test_and_set_bit(STATUS_EXIT_PENDING, &priv->status); + + iwl_clear_ucode_stations(priv, NULL); + iwl_dealloc_bcast_stations(priv); + iwl_clear_driver_stations(priv); + + /* reset BT coex data */ + priv->bt_status = 0; + priv->cur_rssi_ctx = NULL; + priv->bt_is_sco = 0; + if (priv->cfg->bt_params) + priv->bt_traffic_load = + priv->cfg->bt_params->bt_init_traffic_load; + else + priv->bt_traffic_load = 0; + priv->bt_full_concurrent = false; + priv->bt_ci_compliance = 0; + + /* Wipe out the EXIT_PENDING status bit if we are not actually + * exiting the module */ + if (!exit_pending) + clear_bit(STATUS_EXIT_PENDING, &priv->status); + + if (priv->mac80211_registered) + ieee80211_stop_queues(priv->hw); + + priv->ucode_loaded = false; + iwl_trans_stop_device(priv->trans); + + /* Set num_aux_in_flight must be done after the transport is stopped */ + atomic_set(&priv->num_aux_in_flight, 0); + + /* Clear out all status bits but a few that are stable across reset */ + priv->status &= test_bit(STATUS_RF_KILL_HW, &priv->status) << + STATUS_RF_KILL_HW | + test_bit(STATUS_FW_ERROR, &priv->status) << + STATUS_FW_ERROR | + test_bit(STATUS_EXIT_PENDING, &priv->status) << + STATUS_EXIT_PENDING; + + dev_kfree_skb(priv->beacon_skb); + priv->beacon_skb = NULL; +} + +/***************************************************************************** + * + * Workqueue callbacks + * + *****************************************************************************/ + +static void iwl_bg_run_time_calib_work(struct work_struct *work) +{ + struct iwl_priv *priv = container_of(work, struct iwl_priv, + run_time_calib_work); + + mutex_lock(&priv->mutex); + + if (test_bit(STATUS_EXIT_PENDING, &priv->status) || + test_bit(STATUS_SCANNING, &priv->status)) { + mutex_unlock(&priv->mutex); + return; + } + + if (priv->start_calib) { + iwl_chain_noise_calibration(priv); + iwl_sensitivity_calibration(priv); + } + + mutex_unlock(&priv->mutex); +} + +void iwlagn_prepare_restart(struct iwl_priv *priv) +{ + bool bt_full_concurrent; + u8 bt_ci_compliance; + u8 bt_load; + u8 bt_status; + bool bt_is_sco; + int i; + + lockdep_assert_held(&priv->mutex); + + priv->is_open = 0; + + /* + * __iwl_down() will clear the BT status variables, + * which is correct, but when we restart we really + * want to keep them so restore them afterwards. + * + * The restart process will later pick them up and + * re-configure the hw when we reconfigure the BT + * command. + */ + bt_full_concurrent = priv->bt_full_concurrent; + bt_ci_compliance = priv->bt_ci_compliance; + bt_load = priv->bt_traffic_load; + bt_status = priv->bt_status; + bt_is_sco = priv->bt_is_sco; + + iwl_down(priv); + + priv->bt_full_concurrent = bt_full_concurrent; + priv->bt_ci_compliance = bt_ci_compliance; + priv->bt_traffic_load = bt_load; + priv->bt_status = bt_status; + priv->bt_is_sco = bt_is_sco; + + /* reset aggregation queues */ + for (i = IWLAGN_FIRST_AMPDU_QUEUE; i < IWL_MAX_HW_QUEUES; i++) + priv->queue_to_mac80211[i] = IWL_INVALID_MAC80211_QUEUE; + /* and stop counts */ + for (i = 0; i < IWL_MAX_HW_QUEUES; i++) + atomic_set(&priv->queue_stop_count[i], 0); + + memset(priv->agg_q_alloc, 0, sizeof(priv->agg_q_alloc)); +} + +static void iwl_bg_restart(struct work_struct *data) +{ + struct iwl_priv *priv = container_of(data, struct iwl_priv, restart); + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + if (test_and_clear_bit(STATUS_FW_ERROR, &priv->status)) { + mutex_lock(&priv->mutex); + iwlagn_prepare_restart(priv); + mutex_unlock(&priv->mutex); + iwl_cancel_deferred_work(priv); + if (priv->mac80211_registered) + ieee80211_restart_hw(priv->hw); + else + IWL_ERR(priv, + "Cannot request restart before registrating with mac80211"); + } else { + WARN_ON(1); + } +} + + + + +void iwlagn_disable_roc(struct iwl_priv *priv) +{ + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_PAN]; + + lockdep_assert_held(&priv->mutex); + + if (!priv->hw_roc_setup) + return; + + ctx->staging.dev_type = RXON_DEV_TYPE_P2P; + ctx->staging.filter_flags &= ~RXON_FILTER_ASSOC_MSK; + + priv->hw_roc_channel = NULL; + + memset(ctx->staging.node_addr, 0, ETH_ALEN); + + iwlagn_commit_rxon(priv, ctx); + + ctx->is_active = false; + priv->hw_roc_setup = false; +} + +static void iwlagn_disable_roc_work(struct work_struct *work) +{ + struct iwl_priv *priv = container_of(work, struct iwl_priv, + hw_roc_disable_work.work); + + mutex_lock(&priv->mutex); + iwlagn_disable_roc(priv); + mutex_unlock(&priv->mutex); +} + +/***************************************************************************** + * + * driver setup and teardown + * + *****************************************************************************/ + +static void iwl_setup_deferred_work(struct iwl_priv *priv) +{ + priv->workqueue = create_singlethread_workqueue(DRV_NAME); + + INIT_WORK(&priv->restart, iwl_bg_restart); + INIT_WORK(&priv->beacon_update, iwl_bg_beacon_update); + INIT_WORK(&priv->run_time_calib_work, iwl_bg_run_time_calib_work); + INIT_WORK(&priv->tx_flush, iwl_bg_tx_flush); + INIT_WORK(&priv->bt_full_concurrency, iwl_bg_bt_full_concurrency); + INIT_WORK(&priv->bt_runtime_config, iwl_bg_bt_runtime_config); + INIT_DELAYED_WORK(&priv->hw_roc_disable_work, + iwlagn_disable_roc_work); + + iwl_setup_scan_deferred_work(priv); + + if (priv->cfg->bt_params) + iwlagn_bt_setup_deferred_work(priv); + + init_timer(&priv->statistics_periodic); + priv->statistics_periodic.data = (unsigned long)priv; + priv->statistics_periodic.function = iwl_bg_statistics_periodic; + + init_timer(&priv->ucode_trace); + priv->ucode_trace.data = (unsigned long)priv; + priv->ucode_trace.function = iwl_bg_ucode_trace; +} + +void iwl_cancel_deferred_work(struct iwl_priv *priv) +{ + if (priv->cfg->bt_params) + iwlagn_bt_cancel_deferred_work(priv); + + cancel_work_sync(&priv->run_time_calib_work); + cancel_work_sync(&priv->beacon_update); + + iwl_cancel_scan_deferred_work(priv); + + cancel_work_sync(&priv->bt_full_concurrency); + cancel_work_sync(&priv->bt_runtime_config); + cancel_delayed_work_sync(&priv->hw_roc_disable_work); + + del_timer_sync(&priv->statistics_periodic); + del_timer_sync(&priv->ucode_trace); +} + +static int iwl_init_drv(struct iwl_priv *priv) +{ + spin_lock_init(&priv->sta_lock); + + mutex_init(&priv->mutex); + + INIT_LIST_HEAD(&priv->calib_results); + + priv->band = IEEE80211_BAND_2GHZ; + + priv->plcp_delta_threshold = + priv->cfg->base_params->plcp_delta_threshold; + + priv->iw_mode = NL80211_IFTYPE_STATION; + priv->current_ht_config.smps = IEEE80211_SMPS_STATIC; + priv->missed_beacon_threshold = IWL_MISSED_BEACON_THRESHOLD_DEF; + priv->agg_tids_count = 0; + + priv->ucode_owner = IWL_OWNERSHIP_DRIVER; + + priv->rx_statistics_jiffies = jiffies; + + /* Choose which receivers/antennas to use */ + iwlagn_set_rxon_chain(priv, &priv->contexts[IWL_RXON_CTX_BSS]); + + iwl_init_scan_params(priv); + + /* init bt coex */ + if (priv->cfg->bt_params && + priv->cfg->bt_params->advanced_bt_coexist) { + priv->kill_ack_mask = IWLAGN_BT_KILL_ACK_MASK_DEFAULT; + priv->kill_cts_mask = IWLAGN_BT_KILL_CTS_MASK_DEFAULT; + priv->bt_valid = IWLAGN_BT_ALL_VALID_MSK; + priv->bt_on_thresh = BT_ON_THRESHOLD_DEF; + priv->bt_duration = BT_DURATION_LIMIT_DEF; + priv->dynamic_frag_thresh = BT_FRAG_THRESHOLD_DEF; + } + + return 0; +} + +static void iwl_uninit_drv(struct iwl_priv *priv) +{ + kfree(priv->scan_cmd); + kfree(priv->beacon_cmd); + kfree(rcu_dereference_raw(priv->noa_data)); + iwl_calib_free_results(priv); +#ifdef CPTCFG_IWLWIFI_DEBUGFS + kfree(priv->wowlan_sram); +#endif +} + +static void iwl_set_hw_params(struct iwl_priv *priv) +{ + if (priv->cfg->ht_params) + priv->hw_params.use_rts_for_aggregation = + priv->cfg->ht_params->use_rts_for_aggregation; + + /* Device-specific setup */ + priv->lib->set_hw_params(priv); +} + + + +/* show what optional capabilities we have */ +static void iwl_option_config(struct iwl_priv *priv) +{ +#ifdef CPTCFG_IWLWIFI_DEBUG + IWL_INFO(priv, "CPTCFG_IWLWIFI_DEBUG enabled\n"); +#else + IWL_INFO(priv, "CPTCFG_IWLWIFI_DEBUG disabled\n"); +#endif + +#ifdef CPTCFG_IWLWIFI_DEBUGFS + IWL_INFO(priv, "CPTCFG_IWLWIFI_DEBUGFS enabled\n"); +#else + IWL_INFO(priv, "CPTCFG_IWLWIFI_DEBUGFS disabled\n"); +#endif + +#ifdef CPTCFG_IWLWIFI_DEVICE_TRACING + IWL_INFO(priv, "CPTCFG_IWLWIFI_DEVICE_TRACING enabled\n"); +#else + IWL_INFO(priv, "CPTCFG_IWLWIFI_DEVICE_TRACING disabled\n"); +#endif + +#ifdef CPTCFG_IWLWIFI_DEVICE_TESTMODE + IWL_INFO(priv, "CPTCFG_IWLWIFI_DEVICE_TESTMODE enabled\n"); +#else + IWL_INFO(priv, "CPTCFG_IWLWIFI_DEVICE_TESTMODE disabled\n"); +#endif + +#ifdef CPTCFG_IWLWIFI_P2P + IWL_INFO(priv, "CPTCFG_IWLWIFI_P2P enabled\n"); +#else + IWL_INFO(priv, "CPTCFG_IWLWIFI_P2P disabled\n"); +#endif +} + +static int iwl_eeprom_init_hw_params(struct iwl_priv *priv) +{ + struct iwl_nvm_data *data = priv->nvm_data; + char *debug_msg; + + if (data->sku_cap_11n_enable && + !priv->cfg->ht_params) { + IWL_ERR(priv, "Invalid 11n configuration\n"); + return -EINVAL; + } + + if (!data->sku_cap_11n_enable && !data->sku_cap_band_24GHz_enable && + !data->sku_cap_band_52GHz_enable) { + IWL_ERR(priv, "Invalid device sku\n"); + return -EINVAL; + } + + debug_msg = "Device SKU: 24GHz %s %s, 52GHz %s %s, 11.n %s %s\n"; + IWL_DEBUG_INFO(priv, debug_msg, + data->sku_cap_band_24GHz_enable ? "" : "NOT", "enabled", + data->sku_cap_band_52GHz_enable ? "" : "NOT", "enabled", + data->sku_cap_11n_enable ? "" : "NOT", "enabled"); + + priv->hw_params.tx_chains_num = + num_of_ant(data->valid_tx_ant); + if (priv->cfg->rx_with_siso_diversity) + priv->hw_params.rx_chains_num = 1; + else + priv->hw_params.rx_chains_num = + num_of_ant(data->valid_rx_ant); + + IWL_DEBUG_INFO(priv, "Valid Tx ant: 0x%X, Valid Rx ant: 0x%X\n", + data->valid_tx_ant, + data->valid_rx_ant); + + return 0; +} + +static struct iwl_op_mode *iwl_op_mode_dvm_start(struct iwl_trans *trans, + const struct iwl_cfg *cfg, + const struct iwl_fw *fw, + struct dentry *dbgfs_dir) +{ + struct iwl_priv *priv; + struct ieee80211_hw *hw; + struct iwl_op_mode *op_mode; + u16 num_mac; + u32 ucode_flags; + struct iwl_trans_config trans_cfg = {}; + static const u8 no_reclaim_cmds[] = { + REPLY_RX_PHY_CMD, + REPLY_RX_MPDU_CMD, + REPLY_COMPRESSED_BA, + STATISTICS_NOTIFICATION, + REPLY_TX, + }; + int i; + + /************************ + * 1. Allocating HW data + ************************/ + hw = iwl_alloc_all(); + if (!hw) { + pr_err("%s: Cannot allocate network device\n", cfg->name); + goto out; + } + + op_mode = hw->priv; + op_mode->ops = &iwl_dvm_ops; + priv = IWL_OP_MODE_GET_DVM(op_mode); + priv->trans = trans; + priv->dev = trans->dev; + priv->cfg = cfg; + priv->fw = fw; + + switch (priv->cfg->device_family) { + case IWL_DEVICE_FAMILY_1000: + case IWL_DEVICE_FAMILY_100: + priv->lib = &iwl1000_lib; + break; + case IWL_DEVICE_FAMILY_2000: + case IWL_DEVICE_FAMILY_105: + priv->lib = &iwl2000_lib; + break; + case IWL_DEVICE_FAMILY_2030: + case IWL_DEVICE_FAMILY_135: + priv->lib = &iwl2030_lib; + break; + case IWL_DEVICE_FAMILY_5000: + priv->lib = &iwl5000_lib; + break; + case IWL_DEVICE_FAMILY_5150: + priv->lib = &iwl5150_lib; + break; + case IWL_DEVICE_FAMILY_6000: + case IWL_DEVICE_FAMILY_6005: + case IWL_DEVICE_FAMILY_6000i: + case IWL_DEVICE_FAMILY_6050: + case IWL_DEVICE_FAMILY_6150: + priv->lib = &iwl6000_lib; + break; + case IWL_DEVICE_FAMILY_6030: + priv->lib = &iwl6030_lib; + break; + default: + break; + } + + if (WARN_ON(!priv->lib)) + goto out_free_hw; + + /* + * Populate the state variables that the transport layer needs + * to know about. + */ + trans_cfg.op_mode = op_mode; + trans_cfg.no_reclaim_cmds = no_reclaim_cmds; + trans_cfg.n_no_reclaim_cmds = ARRAY_SIZE(no_reclaim_cmds); + trans_cfg.rx_buf_size_8k = iwlwifi_mod_params.amsdu_size_8K; + if (!iwlwifi_mod_params.wd_disable) + trans_cfg.queue_watchdog_timeout = + priv->cfg->base_params->wd_timeout; + else + trans_cfg.queue_watchdog_timeout = IWL_WATCHDOG_DISABLED; + trans_cfg.command_names = iwl_dvm_cmd_strings; + trans_cfg.cmd_fifo = IWLAGN_CMD_FIFO_NUM; + + WARN_ON(sizeof(priv->transport_queue_stop) * BITS_PER_BYTE < + priv->cfg->base_params->num_of_queues); + + ucode_flags = fw->ucode_capa.flags; + +#ifndef CPTCFG_IWLWIFI_P2P + ucode_flags &= ~IWL_UCODE_TLV_FLAGS_P2P; +#endif + + if (ucode_flags & IWL_UCODE_TLV_FLAGS_PAN) { + priv->sta_key_max_num = STA_KEY_MAX_NUM_PAN; + trans_cfg.cmd_queue = IWL_IPAN_CMD_QUEUE_NUM; + } else { + priv->sta_key_max_num = STA_KEY_MAX_NUM; + trans_cfg.cmd_queue = IWL_DEFAULT_CMD_QUEUE_NUM; + } + + /* Configure transport layer */ + iwl_trans_configure(priv->trans, &trans_cfg); + + trans->rx_mpdu_cmd = REPLY_RX_MPDU_CMD; + trans->rx_mpdu_cmd_hdr_size = sizeof(struct iwl_rx_mpdu_res_start); + + /* At this point both hw and priv are allocated. */ + + SET_IEEE80211_DEV(priv->hw, priv->trans->dev); + + iwl_option_config(priv); + + IWL_DEBUG_INFO(priv, "*** LOAD DRIVER ***\n"); + + /* is antenna coupling more than 35dB ? */ + priv->bt_ant_couple_ok = + (iwlwifi_mod_params.ant_coupling > + IWL_BT_ANTENNA_COUPLING_THRESHOLD) ? + true : false; + + /* enable/disable bt channel inhibition */ + priv->bt_ch_announce = iwlwifi_mod_params.bt_ch_announce; + IWL_DEBUG_INFO(priv, "BT channel inhibition is %s\n", + (priv->bt_ch_announce) ? "On" : "Off"); + + /* these spin locks will be used in apm_ops.init and EEPROM access + * we should init now + */ + spin_lock_init(&priv->statistics.lock); + + /*********************** + * 2. Read REV register + ***********************/ + IWL_INFO(priv, "Detected %s, REV=0x%X\n", + priv->cfg->name, priv->trans->hw_rev); + + if (iwl_trans_start_hw(priv->trans)) + goto out_free_hw; + + /* Read the EEPROM */ + if (iwl_read_eeprom(priv->trans, &priv->eeprom_blob, + &priv->eeprom_blob_size)) { + IWL_ERR(priv, "Unable to init EEPROM\n"); + goto out_free_hw; + } + + /* Reset chip to save power until we load uCode during "up". */ + iwl_trans_stop_hw(priv->trans, false); + + priv->nvm_data = iwl_parse_eeprom_data(priv->trans->dev, priv->cfg, + priv->eeprom_blob, + priv->eeprom_blob_size); + if (!priv->nvm_data) + goto out_free_eeprom_blob; + + if (iwl_nvm_check_version(priv->nvm_data, priv->trans)) + goto out_free_eeprom; + + if (iwl_eeprom_init_hw_params(priv)) + goto out_free_eeprom; + + /* extract MAC Address */ + memcpy(priv->addresses[0].addr, priv->nvm_data->hw_addr, ETH_ALEN); + IWL_DEBUG_INFO(priv, "MAC address: %pM\n", priv->addresses[0].addr); + priv->hw->wiphy->addresses = priv->addresses; + priv->hw->wiphy->n_addresses = 1; + num_mac = priv->nvm_data->n_hw_addrs; + if (num_mac > 1) { + memcpy(priv->addresses[1].addr, priv->addresses[0].addr, + ETH_ALEN); + priv->addresses[1].addr[5]++; + priv->hw->wiphy->n_addresses++; + } + + /************************ + * 4. Setup HW constants + ************************/ + iwl_set_hw_params(priv); + + if (!(priv->nvm_data->sku_cap_ipan_enable)) { + IWL_DEBUG_INFO(priv, "Your EEPROM disabled PAN"); + ucode_flags &= ~IWL_UCODE_TLV_FLAGS_PAN; + /* + * if not PAN, then don't support P2P -- might be a uCode + * packaging bug or due to the eeprom check above + */ + ucode_flags &= ~IWL_UCODE_TLV_FLAGS_P2P; + priv->sta_key_max_num = STA_KEY_MAX_NUM; + trans_cfg.cmd_queue = IWL_DEFAULT_CMD_QUEUE_NUM; + + /* Configure transport layer again*/ + iwl_trans_configure(priv->trans, &trans_cfg); + } + + /******************* + * 5. Setup priv + *******************/ + for (i = 0; i < IWL_MAX_HW_QUEUES; i++) { + priv->queue_to_mac80211[i] = IWL_INVALID_MAC80211_QUEUE; + if (i < IWLAGN_FIRST_AMPDU_QUEUE && + i != IWL_DEFAULT_CMD_QUEUE_NUM && + i != IWL_IPAN_CMD_QUEUE_NUM) + priv->queue_to_mac80211[i] = i; + atomic_set(&priv->queue_stop_count[i], 0); + } + + if (iwl_init_drv(priv)) + goto out_free_eeprom; + + /* At this point both hw and priv are initialized. */ + + /******************** + * 6. Setup services + ********************/ + iwl_setup_deferred_work(priv); + iwl_setup_rx_handlers(priv); + iwl_testmode_init(priv); + + iwl_power_initialize(priv); + iwl_tt_initialize(priv); + + snprintf(priv->hw->wiphy->fw_version, + sizeof(priv->hw->wiphy->fw_version), + "%s", fw->fw_version); + + priv->new_scan_threshold_behaviour = + !!(ucode_flags & IWL_UCODE_TLV_FLAGS_NEWSCAN); + + priv->phy_calib_chain_noise_reset_cmd = + fw->ucode_capa.standard_phy_calibration_size; + priv->phy_calib_chain_noise_gain_cmd = + fw->ucode_capa.standard_phy_calibration_size + 1; + + /* initialize all valid contexts */ + iwl_init_context(priv, ucode_flags); + + /************************************************** + * This is still part of probe() in a sense... + * + * 7. Setup and register with mac80211 and debugfs + **************************************************/ + if (iwlagn_mac_setup_register(priv, &fw->ucode_capa)) + goto out_destroy_workqueue; + + if (iwl_dbgfs_register(priv, dbgfs_dir)) + goto out_mac80211_unregister; + + return op_mode; + +out_mac80211_unregister: + iwlagn_mac_unregister(priv); +out_destroy_workqueue: + iwl_tt_exit(priv); + iwl_testmode_free(priv); + iwl_cancel_deferred_work(priv); + destroy_workqueue(priv->workqueue); + priv->workqueue = NULL; + iwl_uninit_drv(priv); +out_free_eeprom_blob: + kfree(priv->eeprom_blob); +out_free_eeprom: + iwl_free_nvm_data(priv->nvm_data); +out_free_hw: + ieee80211_free_hw(priv->hw); +out: + op_mode = NULL; + return op_mode; +} + +static void iwl_op_mode_dvm_stop(struct iwl_op_mode *op_mode) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + + IWL_DEBUG_INFO(priv, "*** UNLOAD DRIVER ***\n"); + + iwl_testmode_free(priv); + iwlagn_mac_unregister(priv); + + iwl_tt_exit(priv); + + kfree(priv->eeprom_blob); + iwl_free_nvm_data(priv->nvm_data); + + /*netif_stop_queue(dev); */ + flush_workqueue(priv->workqueue); + + /* ieee80211_unregister_hw calls iwlagn_mac_stop, which flushes + * priv->workqueue... so we can't take down the workqueue + * until now... */ + destroy_workqueue(priv->workqueue); + priv->workqueue = NULL; + + iwl_uninit_drv(priv); + + dev_kfree_skb(priv->beacon_skb); + + iwl_trans_stop_hw(priv->trans, true); + ieee80211_free_hw(priv->hw); +} + +static const char * const desc_lookup_text[] = { + "OK", + "FAIL", + "BAD_PARAM", + "BAD_CHECKSUM", + "NMI_INTERRUPT_WDG", + "SYSASSERT", + "FATAL_ERROR", + "BAD_COMMAND", + "HW_ERROR_TUNE_LOCK", + "HW_ERROR_TEMPERATURE", + "ILLEGAL_CHAN_FREQ", + "VCC_NOT_STABLE", + "FH_ERROR", + "NMI_INTERRUPT_HOST", + "NMI_INTERRUPT_ACTION_PT", + "NMI_INTERRUPT_UNKNOWN", + "UCODE_VERSION_MISMATCH", + "HW_ERROR_ABS_LOCK", + "HW_ERROR_CAL_LOCK_FAIL", + "NMI_INTERRUPT_INST_ACTION_PT", + "NMI_INTERRUPT_DATA_ACTION_PT", + "NMI_TRM_HW_ER", + "NMI_INTERRUPT_TRM", + "NMI_INTERRUPT_BREAK_POINT", + "DEBUG_0", + "DEBUG_1", + "DEBUG_2", + "DEBUG_3", +}; + +static struct { char *name; u8 num; } advanced_lookup[] = { + { "NMI_INTERRUPT_WDG", 0x34 }, + { "SYSASSERT", 0x35 }, + { "UCODE_VERSION_MISMATCH", 0x37 }, + { "BAD_COMMAND", 0x38 }, + { "NMI_INTERRUPT_DATA_ACTION_PT", 0x3C }, + { "FATAL_ERROR", 0x3D }, + { "NMI_TRM_HW_ERR", 0x46 }, + { "NMI_INTERRUPT_TRM", 0x4C }, + { "NMI_INTERRUPT_BREAK_POINT", 0x54 }, + { "NMI_INTERRUPT_WDG_RXF_FULL", 0x5C }, + { "NMI_INTERRUPT_WDG_NO_RBD_RXF_FULL", 0x64 }, + { "NMI_INTERRUPT_HOST", 0x66 }, + { "NMI_INTERRUPT_ACTION_PT", 0x7C }, + { "NMI_INTERRUPT_UNKNOWN", 0x84 }, + { "NMI_INTERRUPT_INST_ACTION_PT", 0x86 }, + { "ADVANCED_SYSASSERT", 0 }, +}; + +static const char *desc_lookup(u32 num) +{ + int i; + int max = ARRAY_SIZE(desc_lookup_text); + + if (num < max) + return desc_lookup_text[num]; + + max = ARRAY_SIZE(advanced_lookup) - 1; + for (i = 0; i < max; i++) { + if (advanced_lookup[i].num == num) + break; + } + return advanced_lookup[i].name; +} + +#define ERROR_START_OFFSET (1 * sizeof(u32)) +#define ERROR_ELEM_SIZE (7 * sizeof(u32)) + +static void iwl_dump_nic_error_log(struct iwl_priv *priv) +{ + struct iwl_trans *trans = priv->trans; + u32 base; + struct iwl_error_event_table table; + + base = priv->device_pointers.error_event_table; + if (priv->cur_ucode == IWL_UCODE_INIT) { + if (!base) + base = priv->fw->init_errlog_ptr; + } else { + if (!base) + base = priv->fw->inst_errlog_ptr; + } + + if (!iwlagn_hw_valid_rtc_data_addr(base)) { + IWL_ERR(priv, + "Not valid error log pointer 0x%08X for %s uCode\n", + base, + (priv->cur_ucode == IWL_UCODE_INIT) + ? "Init" : "RT"); + return; + } + + /*TODO: Update dbgfs with ISR error stats obtained below */ + iwl_trans_read_mem_bytes(trans, base, &table, sizeof(table)); + + if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) { + IWL_ERR(trans, "Start IWL Error Log Dump:\n"); + IWL_ERR(trans, "Status: 0x%08lX, count: %d\n", + priv->status, table.valid); + } + + trace_iwlwifi_dev_ucode_error(trans->dev, table.error_id, table.tsf_low, + table.data1, table.data2, table.line, + table.blink1, table.blink2, table.ilink1, + table.ilink2, table.bcon_time, table.gp1, + table.gp2, table.gp3, table.ucode_ver, + table.hw_ver, table.brd_ver); + IWL_ERR(priv, "0x%08X | %-28s\n", table.error_id, + desc_lookup(table.error_id)); + IWL_ERR(priv, "0x%08X | uPc\n", table.pc); + IWL_ERR(priv, "0x%08X | branchlink1\n", table.blink1); + IWL_ERR(priv, "0x%08X | branchlink2\n", table.blink2); + IWL_ERR(priv, "0x%08X | interruptlink1\n", table.ilink1); + IWL_ERR(priv, "0x%08X | interruptlink2\n", table.ilink2); + IWL_ERR(priv, "0x%08X | data1\n", table.data1); + IWL_ERR(priv, "0x%08X | data2\n", table.data2); + IWL_ERR(priv, "0x%08X | line\n", table.line); + IWL_ERR(priv, "0x%08X | beacon time\n", table.bcon_time); + IWL_ERR(priv, "0x%08X | tsf low\n", table.tsf_low); + IWL_ERR(priv, "0x%08X | tsf hi\n", table.tsf_hi); + IWL_ERR(priv, "0x%08X | time gp1\n", table.gp1); + IWL_ERR(priv, "0x%08X | time gp2\n", table.gp2); + IWL_ERR(priv, "0x%08X | time gp3\n", table.gp3); + IWL_ERR(priv, "0x%08X | uCode version\n", table.ucode_ver); + IWL_ERR(priv, "0x%08X | hw version\n", table.hw_ver); + IWL_ERR(priv, "0x%08X | board version\n", table.brd_ver); + IWL_ERR(priv, "0x%08X | hcmd\n", table.hcmd); + IWL_ERR(priv, "0x%08X | isr0\n", table.isr0); + IWL_ERR(priv, "0x%08X | isr1\n", table.isr1); + IWL_ERR(priv, "0x%08X | isr2\n", table.isr2); + IWL_ERR(priv, "0x%08X | isr3\n", table.isr3); + IWL_ERR(priv, "0x%08X | isr4\n", table.isr4); + IWL_ERR(priv, "0x%08X | isr_pref\n", table.isr_pref); + IWL_ERR(priv, "0x%08X | wait_event\n", table.wait_event); + IWL_ERR(priv, "0x%08X | l2p_control\n", table.l2p_control); + IWL_ERR(priv, "0x%08X | l2p_duration\n", table.l2p_duration); + IWL_ERR(priv, "0x%08X | l2p_mhvalid\n", table.l2p_mhvalid); + IWL_ERR(priv, "0x%08X | l2p_addr_match\n", table.l2p_addr_match); + IWL_ERR(priv, "0x%08X | lmpm_pmg_sel\n", table.lmpm_pmg_sel); + IWL_ERR(priv, "0x%08X | timestamp\n", table.u_timestamp); + IWL_ERR(priv, "0x%08X | flow_handler\n", table.flow_handler); +} + +#define EVENT_START_OFFSET (4 * sizeof(u32)) + +/** + * iwl_print_event_log - Dump error event log to syslog + * + */ +static int iwl_print_event_log(struct iwl_priv *priv, u32 start_idx, + u32 num_events, u32 mode, + int pos, char **buf, size_t bufsz) +{ + u32 i; + u32 base; /* SRAM byte address of event log header */ + u32 event_size; /* 2 u32s, or 3 u32s if timestamp recorded */ + u32 ptr; /* SRAM byte address of log data */ + u32 ev, time, data; /* event log data */ + unsigned long reg_flags; + + struct iwl_trans *trans = priv->trans; + + if (num_events == 0) + return pos; + + base = priv->device_pointers.log_event_table; + if (priv->cur_ucode == IWL_UCODE_INIT) { + if (!base) + base = priv->fw->init_evtlog_ptr; + } else { + if (!base) + base = priv->fw->inst_evtlog_ptr; + } + + if (mode == 0) + event_size = 2 * sizeof(u32); + else + event_size = 3 * sizeof(u32); + + ptr = base + EVENT_START_OFFSET + (start_idx * event_size); + + /* Make sure device is powered up for SRAM reads */ + if (!iwl_trans_grab_nic_access(trans, false, ®_flags)) + return pos; + + /* Set starting address; reads will auto-increment */ + iwl_write32(trans, HBUS_TARG_MEM_RADDR, ptr); + + /* "time" is actually "data" for mode 0 (no timestamp). + * place event id # at far right for easier visual parsing. */ + for (i = 0; i < num_events; i++) { + ev = iwl_read32(trans, HBUS_TARG_MEM_RDAT); + time = iwl_read32(trans, HBUS_TARG_MEM_RDAT); + if (mode == 0) { + /* data, ev */ + if (bufsz) { + pos += scnprintf(*buf + pos, bufsz - pos, + "EVT_LOG:0x%08x:%04u\n", + time, ev); + } else { + trace_iwlwifi_dev_ucode_event(trans->dev, 0, + time, ev); + IWL_ERR(priv, "EVT_LOG:0x%08x:%04u\n", + time, ev); + } + } else { + data = iwl_read32(trans, HBUS_TARG_MEM_RDAT); + if (bufsz) { + pos += scnprintf(*buf + pos, bufsz - pos, + "EVT_LOGT:%010u:0x%08x:%04u\n", + time, data, ev); + } else { + IWL_ERR(priv, "EVT_LOGT:%010u:0x%08x:%04u\n", + time, data, ev); + trace_iwlwifi_dev_ucode_event(trans->dev, time, + data, ev); + } + } + } + + /* Allow device to power down */ + iwl_trans_release_nic_access(trans, ®_flags); + return pos; +} + +/** + * iwl_print_last_event_logs - Dump the newest # of event log to syslog + */ +static int iwl_print_last_event_logs(struct iwl_priv *priv, u32 capacity, + u32 num_wraps, u32 next_entry, + u32 size, u32 mode, + int pos, char **buf, size_t bufsz) +{ + /* + * display the newest DEFAULT_LOG_ENTRIES entries + * i.e the entries just before the next ont that uCode would fill. + */ + if (num_wraps) { + if (next_entry < size) { + pos = iwl_print_event_log(priv, + capacity - (size - next_entry), + size - next_entry, mode, + pos, buf, bufsz); + pos = iwl_print_event_log(priv, 0, + next_entry, mode, + pos, buf, bufsz); + } else + pos = iwl_print_event_log(priv, next_entry - size, + size, mode, pos, buf, bufsz); + } else { + if (next_entry < size) { + pos = iwl_print_event_log(priv, 0, next_entry, + mode, pos, buf, bufsz); + } else { + pos = iwl_print_event_log(priv, next_entry - size, + size, mode, pos, buf, bufsz); + } + } + return pos; +} + +#define DEFAULT_DUMP_EVENT_LOG_ENTRIES (20) + +int iwl_dump_nic_event_log(struct iwl_priv *priv, bool full_log, + char **buf) +{ + u32 base; /* SRAM byte address of event log header */ + u32 capacity; /* event log capacity in # entries */ + u32 mode; /* 0 - no timestamp, 1 - timestamp recorded */ + u32 num_wraps; /* # times uCode wrapped to top of log */ + u32 next_entry; /* index of next entry to be written by uCode */ + u32 size; /* # entries that we'll print */ + u32 logsize; + int pos = 0; + size_t bufsz = 0; + struct iwl_trans *trans = priv->trans; + + base = priv->device_pointers.log_event_table; + if (priv->cur_ucode == IWL_UCODE_INIT) { + logsize = priv->fw->init_evtlog_size; + if (!base) + base = priv->fw->init_evtlog_ptr; + } else { + logsize = priv->fw->inst_evtlog_size; + if (!base) + base = priv->fw->inst_evtlog_ptr; + } + + if (!iwlagn_hw_valid_rtc_data_addr(base)) { + IWL_ERR(priv, + "Invalid event log pointer 0x%08X for %s uCode\n", + base, + (priv->cur_ucode == IWL_UCODE_INIT) + ? "Init" : "RT"); + return -EINVAL; + } + + /* event log header */ + capacity = iwl_trans_read_mem32(trans, base); + mode = iwl_trans_read_mem32(trans, base + (1 * sizeof(u32))); + num_wraps = iwl_trans_read_mem32(trans, base + (2 * sizeof(u32))); + next_entry = iwl_trans_read_mem32(trans, base + (3 * sizeof(u32))); + + if (capacity > logsize) { + IWL_ERR(priv, "Log capacity %d is bogus, limit to %d " + "entries\n", capacity, logsize); + capacity = logsize; + } + + if (next_entry > logsize) { + IWL_ERR(priv, "Log write index %d is bogus, limit to %d\n", + next_entry, logsize); + next_entry = logsize; + } + + size = num_wraps ? capacity : next_entry; + + /* bail out if nothing in log */ + if (size == 0) { + IWL_ERR(trans, "Start IWL Event Log Dump: nothing in log\n"); + return pos; + } + +#ifdef CPTCFG_IWLWIFI_DEBUG + if (!(iwl_have_debug_level(IWL_DL_FW_ERRORS)) && !full_log) + size = (size > DEFAULT_DUMP_EVENT_LOG_ENTRIES) + ? DEFAULT_DUMP_EVENT_LOG_ENTRIES : size; +#else + size = (size > DEFAULT_DUMP_EVENT_LOG_ENTRIES) + ? DEFAULT_DUMP_EVENT_LOG_ENTRIES : size; +#endif + IWL_ERR(priv, "Start IWL Event Log Dump: display last %u entries\n", + size); + +#ifdef CPTCFG_IWLWIFI_DEBUG + if (buf) { + if (full_log) + bufsz = capacity * 48; + else + bufsz = size * 48; + *buf = kmalloc(bufsz, GFP_KERNEL); + if (!*buf) + return -ENOMEM; + } + if (iwl_have_debug_level(IWL_DL_FW_ERRORS) || full_log) { + /* + * if uCode has wrapped back to top of log, + * start at the oldest entry, + * i.e the next one that uCode would fill. + */ + if (num_wraps) + pos = iwl_print_event_log(priv, next_entry, + capacity - next_entry, mode, + pos, buf, bufsz); + /* (then/else) start at top of log */ + pos = iwl_print_event_log(priv, 0, + next_entry, mode, pos, buf, bufsz); + } else + pos = iwl_print_last_event_logs(priv, capacity, num_wraps, + next_entry, size, mode, + pos, buf, bufsz); +#else + pos = iwl_print_last_event_logs(priv, capacity, num_wraps, + next_entry, size, mode, + pos, buf, bufsz); +#endif + return pos; +} + +static void iwlagn_fw_error(struct iwl_priv *priv, bool ondemand) +{ + unsigned int reload_msec; + unsigned long reload_jiffies; + +#ifdef CPTCFG_IWLWIFI_DEBUG + if (iwl_have_debug_level(IWL_DL_FW_ERRORS)) + iwl_print_rx_config_cmd(priv, IWL_RXON_CTX_BSS); +#endif + + /* uCode is no longer loaded. */ + priv->ucode_loaded = false; + + /* Set the FW error flag -- cleared on iwl_down */ + set_bit(STATUS_FW_ERROR, &priv->status); + + iwl_abort_notification_waits(&priv->notif_wait); + + /* Keep the restart process from trying to send host + * commands by clearing the ready bit */ + clear_bit(STATUS_READY, &priv->status); + + if (!ondemand) { + /* + * If firmware keep reloading, then it indicate something + * serious wrong and firmware having problem to recover + * from it. Instead of keep trying which will fill the syslog + * and hang the system, let's just stop it + */ + reload_jiffies = jiffies; + reload_msec = jiffies_to_msecs((long) reload_jiffies - + (long) priv->reload_jiffies); + priv->reload_jiffies = reload_jiffies; + if (reload_msec <= IWL_MIN_RELOAD_DURATION) { + priv->reload_count++; + if (priv->reload_count >= IWL_MAX_CONTINUE_RELOAD_CNT) { + IWL_ERR(priv, "BUG_ON, Stop restarting\n"); + return; + } + } else + priv->reload_count = 0; + } + + if (!test_bit(STATUS_EXIT_PENDING, &priv->status)) { + if (iwlwifi_mod_params.restart_fw) { + IWL_DEBUG_FW_ERRORS(priv, + "Restarting adapter due to uCode error.\n"); + queue_work(priv->workqueue, &priv->restart); + } else + IWL_DEBUG_FW_ERRORS(priv, + "Detected FW error, but not restarting\n"); + } +} + +static void iwl_nic_error(struct iwl_op_mode *op_mode) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + + IWL_ERR(priv, "Loaded firmware version: %s\n", + priv->fw->fw_version); + + iwl_dump_nic_error_log(priv); + iwl_dump_nic_event_log(priv, false, NULL); + + iwlagn_fw_error(priv, false); +} + +static void iwl_cmd_queue_full(struct iwl_op_mode *op_mode) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + + if (!iwl_check_for_ct_kill(priv)) { + IWL_ERR(priv, "Restarting adapter queue is full\n"); + iwlagn_fw_error(priv, false); + } +} + +#define EEPROM_RF_CONFIG_TYPE_MAX 0x3 + +static void iwl_nic_config(struct iwl_op_mode *op_mode) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + + /* SKU Control */ + iwl_trans_set_bits_mask(priv->trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_MSK_MAC_DASH | + CSR_HW_IF_CONFIG_REG_MSK_MAC_STEP, + (CSR_HW_REV_STEP(priv->trans->hw_rev) << + CSR_HW_IF_CONFIG_REG_POS_MAC_STEP) | + (CSR_HW_REV_DASH(priv->trans->hw_rev) << + CSR_HW_IF_CONFIG_REG_POS_MAC_DASH)); + + /* write radio config values to register */ + if (priv->nvm_data->radio_cfg_type <= EEPROM_RF_CONFIG_TYPE_MAX) { + u32 reg_val = + priv->nvm_data->radio_cfg_type << + CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE | + priv->nvm_data->radio_cfg_step << + CSR_HW_IF_CONFIG_REG_POS_PHY_STEP | + priv->nvm_data->radio_cfg_dash << + CSR_HW_IF_CONFIG_REG_POS_PHY_DASH; + + iwl_trans_set_bits_mask(priv->trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE | + CSR_HW_IF_CONFIG_REG_MSK_PHY_STEP | + CSR_HW_IF_CONFIG_REG_MSK_PHY_DASH, + reg_val); + + IWL_INFO(priv, "Radio type=0x%x-0x%x-0x%x\n", + priv->nvm_data->radio_cfg_type, + priv->nvm_data->radio_cfg_step, + priv->nvm_data->radio_cfg_dash); + } else { + WARN_ON(1); + } + + /* set CSR_HW_CONFIG_REG for uCode use */ + iwl_set_bit(priv->trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI | + CSR_HW_IF_CONFIG_REG_BIT_MAC_SI); + + /* W/A : NIC is stuck in a reset state after Early PCIe power off + * (PCIe power is lost before PERST# is asserted), + * causing ME FW to lose ownership and not being able to obtain it back. + */ + iwl_set_bits_mask_prph(priv->trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS, + ~APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS); + + if (priv->lib->nic_config) + priv->lib->nic_config(priv); +} + +static void iwl_wimax_active(struct iwl_op_mode *op_mode) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + + clear_bit(STATUS_READY, &priv->status); + IWL_ERR(priv, "RF is used by WiMAX\n"); +} + +static void iwl_stop_sw_queue(struct iwl_op_mode *op_mode, int queue) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + int mq = priv->queue_to_mac80211[queue]; + + if (WARN_ON_ONCE(mq == IWL_INVALID_MAC80211_QUEUE)) + return; + + if (atomic_inc_return(&priv->queue_stop_count[mq]) > 1) { + IWL_DEBUG_TX_QUEUES(priv, + "queue %d (mac80211 %d) already stopped\n", + queue, mq); + return; + } + + set_bit(mq, &priv->transport_queue_stop); + ieee80211_stop_queue(priv->hw, mq); +} + +static void iwl_wake_sw_queue(struct iwl_op_mode *op_mode, int queue) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + int mq = priv->queue_to_mac80211[queue]; + + if (WARN_ON_ONCE(mq == IWL_INVALID_MAC80211_QUEUE)) + return; + + if (atomic_dec_return(&priv->queue_stop_count[mq]) > 0) { + IWL_DEBUG_TX_QUEUES(priv, + "queue %d (mac80211 %d) already awake\n", + queue, mq); + return; + } + + clear_bit(mq, &priv->transport_queue_stop); + + if (!priv->passive_no_rx) + ieee80211_wake_queue(priv->hw, mq); +} + +void iwlagn_lift_passive_no_rx(struct iwl_priv *priv) +{ + int mq; + + if (!priv->passive_no_rx) + return; + + for (mq = 0; mq < IWLAGN_FIRST_AMPDU_QUEUE; mq++) { + if (!test_bit(mq, &priv->transport_queue_stop)) { + IWL_DEBUG_TX_QUEUES(priv, "Wake queue %d", mq); + ieee80211_wake_queue(priv->hw, mq); + } else { + IWL_DEBUG_TX_QUEUES(priv, "Don't wake queue %d", mq); + } + } + + priv->passive_no_rx = false; +} + +static void iwl_free_skb(struct iwl_op_mode *op_mode, struct sk_buff *skb) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + struct ieee80211_tx_info *info; + + info = IEEE80211_SKB_CB(skb); + iwl_trans_free_tx_cmd(priv->trans, info->driver_data[1]); + ieee80211_free_txskb(priv->hw, skb); +} + +static void iwl_set_hw_rfkill_state(struct iwl_op_mode *op_mode, bool state) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + + if (state) + set_bit(STATUS_RF_KILL_HW, &priv->status); + else + clear_bit(STATUS_RF_KILL_HW, &priv->status); + + wiphy_rfkill_set_hw_state(priv->hw->wiphy, state); +} + +static const struct iwl_op_mode_ops iwl_dvm_ops = { + .start = iwl_op_mode_dvm_start, + .stop = iwl_op_mode_dvm_stop, + .rx = iwl_rx_dispatch, + .queue_full = iwl_stop_sw_queue, + .queue_not_full = iwl_wake_sw_queue, + .hw_rf_kill = iwl_set_hw_rfkill_state, + .free_skb = iwl_free_skb, + .nic_error = iwl_nic_error, + .cmd_queue_full = iwl_cmd_queue_full, + .nic_config = iwl_nic_config, + .wimax_active = iwl_wimax_active, +}; + +/***************************************************************************** + * + * driver and module entry point + * + *****************************************************************************/ +static int __init iwl_init(void) +{ + + int ret; + + ret = iwlagn_rate_control_register(); + if (ret) { + pr_err("Unable to register rate control algorithm: %d\n", ret); + return ret; + } + + ret = iwl_opmode_register("iwldvm", &iwl_dvm_ops); + if (ret) { + pr_err("Unable to register op_mode: %d\n", ret); + iwlagn_rate_control_unregister(); + } + + return ret; +} +module_init(iwl_init); + +static void __exit iwl_exit(void) +{ + iwl_opmode_deregister("iwldvm"); + iwlagn_rate_control_unregister(); +} +module_exit(iwl_exit); diff --git a/drivers/net/wireless/iwlwifi/dvm/power.c b/drivers/net/wireless/iwlwifi/dvm/power.c new file mode 100644 index 0000000..bd69018 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/power.c @@ -0,0 +1,387 @@ +/****************************************************************************** + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + *****************************************************************************/ + + +#include +#include +#include +#include +#include +#include "iwl-io.h" +#include "iwl-debug.h" +#include "iwl-trans.h" +#include "iwl-modparams.h" +#include "dev.h" +#include "agn.h" +#include "commands.h" +#include "power.h" + +/* + * Setting power level allows the card to go to sleep when not busy. + * + * We calculate a sleep command based on the required latency, which + * we get from mac80211. In order to handle thermal throttling, we can + * also use pre-defined power levels. + */ + +/* + * This defines the old power levels. They are still used by default + * (level 1) and for thermal throttle (levels 3 through 5) + */ + +struct iwl_power_vec_entry { + struct iwl_powertable_cmd cmd; + u8 no_dtim; /* number of skip dtim */ +}; + +#define IWL_DTIM_RANGE_0_MAX 2 +#define IWL_DTIM_RANGE_1_MAX 10 + +#define NOSLP cpu_to_le16(0), 0, 0 +#define SLP IWL_POWER_DRIVER_ALLOW_SLEEP_MSK, 0, 0 +#define ASLP (IWL_POWER_POWER_SAVE_ENA_MSK | \ + IWL_POWER_POWER_MANAGEMENT_ENA_MSK | \ + IWL_POWER_ADVANCE_PM_ENA_MSK) +#define ASLP_TOUT(T) cpu_to_le32(T) +#define TU_TO_USEC 1024 +#define SLP_TOUT(T) cpu_to_le32((T) * TU_TO_USEC) +#define SLP_VEC(X0, X1, X2, X3, X4) {cpu_to_le32(X0), \ + cpu_to_le32(X1), \ + cpu_to_le32(X2), \ + cpu_to_le32(X3), \ + cpu_to_le32(X4)} +/* default power management (not Tx power) table values */ +/* for DTIM period 0 through IWL_DTIM_RANGE_0_MAX */ +/* DTIM 0 - 2 */ +static const struct iwl_power_vec_entry range_0[IWL_POWER_NUM] = { + {{SLP, SLP_TOUT(200), SLP_TOUT(500), SLP_VEC(1, 1, 2, 2, 0xFF)}, 0}, + {{SLP, SLP_TOUT(200), SLP_TOUT(300), SLP_VEC(1, 2, 2, 2, 0xFF)}, 0}, + {{SLP, SLP_TOUT(50), SLP_TOUT(100), SLP_VEC(2, 2, 2, 2, 0xFF)}, 0}, + {{SLP, SLP_TOUT(50), SLP_TOUT(25), SLP_VEC(2, 2, 4, 4, 0xFF)}, 1}, + {{SLP, SLP_TOUT(25), SLP_TOUT(25), SLP_VEC(2, 2, 4, 6, 0xFF)}, 2} +}; + + +/* for DTIM period IWL_DTIM_RANGE_0_MAX + 1 through IWL_DTIM_RANGE_1_MAX */ +/* DTIM 3 - 10 */ +static const struct iwl_power_vec_entry range_1[IWL_POWER_NUM] = { + {{SLP, SLP_TOUT(200), SLP_TOUT(500), SLP_VEC(1, 2, 3, 4, 4)}, 0}, + {{SLP, SLP_TOUT(200), SLP_TOUT(300), SLP_VEC(1, 2, 3, 4, 7)}, 0}, + {{SLP, SLP_TOUT(50), SLP_TOUT(100), SLP_VEC(2, 4, 6, 7, 9)}, 0}, + {{SLP, SLP_TOUT(50), SLP_TOUT(25), SLP_VEC(2, 4, 6, 9, 10)}, 1}, + {{SLP, SLP_TOUT(25), SLP_TOUT(25), SLP_VEC(2, 4, 6, 10, 10)}, 2} +}; + +/* for DTIM period > IWL_DTIM_RANGE_1_MAX */ +/* DTIM 11 - */ +static const struct iwl_power_vec_entry range_2[IWL_POWER_NUM] = { + {{SLP, SLP_TOUT(200), SLP_TOUT(500), SLP_VEC(1, 2, 3, 4, 0xFF)}, 0}, + {{SLP, SLP_TOUT(200), SLP_TOUT(300), SLP_VEC(2, 4, 6, 7, 0xFF)}, 0}, + {{SLP, SLP_TOUT(50), SLP_TOUT(100), SLP_VEC(2, 7, 9, 9, 0xFF)}, 0}, + {{SLP, SLP_TOUT(50), SLP_TOUT(25), SLP_VEC(2, 7, 9, 9, 0xFF)}, 0}, + {{SLP, SLP_TOUT(25), SLP_TOUT(25), SLP_VEC(4, 7, 10, 10, 0xFF)}, 0} +}; + +/* advance power management */ +/* DTIM 0 - 2 */ +static const struct iwl_power_vec_entry apm_range_0[IWL_POWER_NUM] = { + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 6, 8, 0xFF), ASLP_TOUT(2)}, 2} +}; + + +/* for DTIM period IWL_DTIM_RANGE_0_MAX + 1 through IWL_DTIM_RANGE_1_MAX */ +/* DTIM 3 - 10 */ +static const struct iwl_power_vec_entry apm_range_1[IWL_POWER_NUM] = { + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 6, 8, 0xFF), 0}, 2} +}; + +/* for DTIM period > IWL_DTIM_RANGE_1_MAX */ +/* DTIM 11 - */ +static const struct iwl_power_vec_entry apm_range_2[IWL_POWER_NUM] = { + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 4, 6, 0xFF), 0}, 0}, + {{ASLP, 0, 0, ASLP_TOUT(50), ASLP_TOUT(50), + SLP_VEC(1, 2, 6, 8, 0xFF), ASLP_TOUT(2)}, 2} +}; + +static void iwl_static_sleep_cmd(struct iwl_priv *priv, + struct iwl_powertable_cmd *cmd, + enum iwl_power_level lvl, int period) +{ + const struct iwl_power_vec_entry *table; + int max_sleep[IWL_POWER_VEC_SIZE] = { 0 }; + int i; + u8 skip; + u32 slp_itrvl; + + if (priv->cfg->adv_pm) { + table = apm_range_2; + if (period <= IWL_DTIM_RANGE_1_MAX) + table = apm_range_1; + if (period <= IWL_DTIM_RANGE_0_MAX) + table = apm_range_0; + } else { + table = range_2; + if (period <= IWL_DTIM_RANGE_1_MAX) + table = range_1; + if (period <= IWL_DTIM_RANGE_0_MAX) + table = range_0; + } + + if (WARN_ON(lvl < 0 || lvl >= IWL_POWER_NUM)) + memset(cmd, 0, sizeof(*cmd)); + else + *cmd = table[lvl].cmd; + + if (period == 0) { + skip = 0; + period = 1; + for (i = 0; i < IWL_POWER_VEC_SIZE; i++) + max_sleep[i] = 1; + + } else { + skip = table[lvl].no_dtim; + for (i = 0; i < IWL_POWER_VEC_SIZE; i++) + max_sleep[i] = le32_to_cpu(cmd->sleep_interval[i]); + max_sleep[IWL_POWER_VEC_SIZE - 1] = skip + 1; + } + + slp_itrvl = le32_to_cpu(cmd->sleep_interval[IWL_POWER_VEC_SIZE - 1]); + /* figure out the listen interval based on dtim period and skip */ + if (slp_itrvl == 0xFF) + cmd->sleep_interval[IWL_POWER_VEC_SIZE - 1] = + cpu_to_le32(period * (skip + 1)); + + slp_itrvl = le32_to_cpu(cmd->sleep_interval[IWL_POWER_VEC_SIZE - 1]); + if (slp_itrvl > period) + cmd->sleep_interval[IWL_POWER_VEC_SIZE - 1] = + cpu_to_le32((slp_itrvl / period) * period); + + if (skip) + cmd->flags |= IWL_POWER_SLEEP_OVER_DTIM_MSK; + else + cmd->flags &= ~IWL_POWER_SLEEP_OVER_DTIM_MSK; + + if (priv->cfg->base_params->shadow_reg_enable) + cmd->flags |= IWL_POWER_SHADOW_REG_ENA; + else + cmd->flags &= ~IWL_POWER_SHADOW_REG_ENA; + + if (iwl_advanced_bt_coexist(priv)) { + if (!priv->cfg->bt_params->bt_sco_disable) + cmd->flags |= IWL_POWER_BT_SCO_ENA; + else + cmd->flags &= ~IWL_POWER_BT_SCO_ENA; + } + + + slp_itrvl = le32_to_cpu(cmd->sleep_interval[IWL_POWER_VEC_SIZE - 1]); + if (slp_itrvl > IWL_CONN_MAX_LISTEN_INTERVAL) + cmd->sleep_interval[IWL_POWER_VEC_SIZE - 1] = + cpu_to_le32(IWL_CONN_MAX_LISTEN_INTERVAL); + + /* enforce max sleep interval */ + for (i = IWL_POWER_VEC_SIZE - 1; i >= 0 ; i--) { + if (le32_to_cpu(cmd->sleep_interval[i]) > + (max_sleep[i] * period)) + cmd->sleep_interval[i] = + cpu_to_le32(max_sleep[i] * period); + if (i != (IWL_POWER_VEC_SIZE - 1)) { + if (le32_to_cpu(cmd->sleep_interval[i]) > + le32_to_cpu(cmd->sleep_interval[i+1])) + cmd->sleep_interval[i] = + cmd->sleep_interval[i+1]; + } + } + + if (priv->power_data.bus_pm) + cmd->flags |= IWL_POWER_PCI_PM_MSK; + else + cmd->flags &= ~IWL_POWER_PCI_PM_MSK; + + IWL_DEBUG_POWER(priv, "numSkipDtim = %u, dtimPeriod = %d\n", + skip, period); + /* The power level here is 0-4 (used as array index), but user expects + to see 1-5 (according to spec). */ + IWL_DEBUG_POWER(priv, "Sleep command for index %d\n", lvl + 1); +} + +static void iwl_power_sleep_cam_cmd(struct iwl_priv *priv, + struct iwl_powertable_cmd *cmd) +{ + memset(cmd, 0, sizeof(*cmd)); + + if (priv->power_data.bus_pm) + cmd->flags |= IWL_POWER_PCI_PM_MSK; + + IWL_DEBUG_POWER(priv, "Sleep command for CAM\n"); +} + +static int iwl_set_power(struct iwl_priv *priv, struct iwl_powertable_cmd *cmd) +{ + IWL_DEBUG_POWER(priv, "Sending power/sleep command\n"); + IWL_DEBUG_POWER(priv, "Flags value = 0x%08X\n", cmd->flags); + IWL_DEBUG_POWER(priv, "Tx timeout = %u\n", le32_to_cpu(cmd->tx_data_timeout)); + IWL_DEBUG_POWER(priv, "Rx timeout = %u\n", le32_to_cpu(cmd->rx_data_timeout)); + IWL_DEBUG_POWER(priv, "Sleep interval vector = { %d , %d , %d , %d , %d }\n", + le32_to_cpu(cmd->sleep_interval[0]), + le32_to_cpu(cmd->sleep_interval[1]), + le32_to_cpu(cmd->sleep_interval[2]), + le32_to_cpu(cmd->sleep_interval[3]), + le32_to_cpu(cmd->sleep_interval[4])); + + return iwl_dvm_send_cmd_pdu(priv, POWER_TABLE_CMD, CMD_SYNC, + sizeof(struct iwl_powertable_cmd), cmd); +} + +static void iwl_power_build_cmd(struct iwl_priv *priv, + struct iwl_powertable_cmd *cmd) +{ + bool enabled = priv->hw->conf.flags & IEEE80211_CONF_PS; + int dtimper; + + dtimper = priv->hw->conf.ps_dtim_period ?: 1; + + if (priv->wowlan) + iwl_static_sleep_cmd(priv, cmd, IWL_POWER_INDEX_5, dtimper); + else if (!priv->cfg->base_params->no_idle_support && + priv->hw->conf.flags & IEEE80211_CONF_IDLE) + iwl_static_sleep_cmd(priv, cmd, IWL_POWER_INDEX_5, 20); + else if (iwl_tt_is_low_power_state(priv)) { + /* in thermal throttling low power state */ + iwl_static_sleep_cmd(priv, cmd, + iwl_tt_current_power_mode(priv), dtimper); + } else if (!enabled) + iwl_power_sleep_cam_cmd(priv, cmd); + else if (priv->power_data.debug_sleep_level_override >= 0) + iwl_static_sleep_cmd(priv, cmd, + priv->power_data.debug_sleep_level_override, + dtimper); + else { + /* Note that the user parameter is 1-5 (according to spec), + but we pass 0-4 because it acts as an array index. */ + if (iwlwifi_mod_params.power_level > IWL_POWER_INDEX_1 && + iwlwifi_mod_params.power_level <= IWL_POWER_NUM) + iwl_static_sleep_cmd(priv, cmd, + iwlwifi_mod_params.power_level - 1, dtimper); + else + iwl_static_sleep_cmd(priv, cmd, + IWL_POWER_INDEX_1, dtimper); + } +} + +int iwl_power_set_mode(struct iwl_priv *priv, struct iwl_powertable_cmd *cmd, + bool force) +{ + int ret; + bool update_chains; + + lockdep_assert_held(&priv->mutex); + + /* Don't update the RX chain when chain noise calibration is running */ + update_chains = priv->chain_noise_data.state == IWL_CHAIN_NOISE_DONE || + priv->chain_noise_data.state == IWL_CHAIN_NOISE_ALIVE; + + if (!memcmp(&priv->power_data.sleep_cmd, cmd, sizeof(*cmd)) && !force) + return 0; + + if (!iwl_is_ready_rf(priv)) + return -EIO; + + /* scan complete use sleep_power_next, need to be updated */ + memcpy(&priv->power_data.sleep_cmd_next, cmd, sizeof(*cmd)); + if (test_bit(STATUS_SCANNING, &priv->status) && !force) { + IWL_DEBUG_INFO(priv, "Defer power set mode while scanning\n"); + return 0; + } + + if (cmd->flags & IWL_POWER_DRIVER_ALLOW_SLEEP_MSK) + iwl_dvm_set_pmi(priv, true); + + ret = iwl_set_power(priv, cmd); + if (!ret) { + if (!(cmd->flags & IWL_POWER_DRIVER_ALLOW_SLEEP_MSK)) + iwl_dvm_set_pmi(priv, false); + + if (update_chains) + iwl_update_chain_flags(priv); + else + IWL_DEBUG_POWER(priv, + "Cannot update the power, chain noise " + "calibration running: %d\n", + priv->chain_noise_data.state); + + memcpy(&priv->power_data.sleep_cmd, cmd, sizeof(*cmd)); + } else + IWL_ERR(priv, "set power fail, ret = %d", ret); + + return ret; +} + +int iwl_power_update_mode(struct iwl_priv *priv, bool force) +{ + struct iwl_powertable_cmd cmd; + + iwl_power_build_cmd(priv, &cmd); + return iwl_power_set_mode(priv, &cmd, force); +} + +/* initialize to default */ +void iwl_power_initialize(struct iwl_priv *priv) +{ + priv->power_data.bus_pm = priv->trans->pm_support; + + priv->power_data.debug_sleep_level_override = -1; + + memset(&priv->power_data.sleep_cmd, 0, + sizeof(priv->power_data.sleep_cmd)); +} diff --git a/drivers/net/wireless/iwlwifi/dvm/power.h b/drivers/net/wireless/iwlwifi/dvm/power.h new file mode 100644 index 0000000..7b03e13 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/power.h @@ -0,0 +1,47 @@ +/****************************************************************************** + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + *****************************************************************************/ +#ifndef __iwl_power_setting_h__ +#define __iwl_power_setting_h__ + +#include "commands.h" + +struct iwl_power_mgr { + struct iwl_powertable_cmd sleep_cmd; + struct iwl_powertable_cmd sleep_cmd_next; + int debug_sleep_level_override; + bool bus_pm; +}; + +int iwl_power_set_mode(struct iwl_priv *priv, struct iwl_powertable_cmd *cmd, + bool force); +int iwl_power_update_mode(struct iwl_priv *priv, bool force); +void iwl_power_initialize(struct iwl_priv *priv); + +extern bool no_sleep_autoadjust; + +#endif /* __iwl_power_setting_h__ */ diff --git a/drivers/net/wireless/iwlwifi/dvm/rs.c b/drivers/net/wireless/iwlwifi/dvm/rs.c new file mode 100644 index 0000000..3239cae --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/rs.c @@ -0,0 +1,3369 @@ +/****************************************************************************** + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "dev.h" +#include "agn.h" + +#define RS_NAME "iwl-agn-rs" + +#define NUM_TRY_BEFORE_ANT_TOGGLE 1 +#define IWL_NUMBER_TRY 1 +#define IWL_HT_NUMBER_TRY 3 + +#define IWL_RATE_MAX_WINDOW 62 /* # tx in history window */ +#define IWL_RATE_MIN_FAILURE_TH 6 /* min failures to calc tpt */ +#define IWL_RATE_MIN_SUCCESS_TH 8 /* min successes to calc tpt */ + +/* max allowed rate miss before sync LQ cmd */ +#define IWL_MISSED_RATE_MAX 15 +/* max time to accum history 2 seconds */ +#define IWL_RATE_SCALE_FLUSH_INTVL (3*HZ) + +static u8 rs_ht_to_legacy[] = { + IWL_RATE_6M_INDEX, IWL_RATE_6M_INDEX, + IWL_RATE_6M_INDEX, IWL_RATE_6M_INDEX, + IWL_RATE_6M_INDEX, + IWL_RATE_6M_INDEX, IWL_RATE_9M_INDEX, + IWL_RATE_12M_INDEX, IWL_RATE_18M_INDEX, + IWL_RATE_24M_INDEX, IWL_RATE_36M_INDEX, + IWL_RATE_48M_INDEX, IWL_RATE_54M_INDEX +}; + +static const u8 ant_toggle_lookup[] = { + /*ANT_NONE -> */ ANT_NONE, + /*ANT_A -> */ ANT_B, + /*ANT_B -> */ ANT_C, + /*ANT_AB -> */ ANT_BC, + /*ANT_C -> */ ANT_A, + /*ANT_AC -> */ ANT_AB, + /*ANT_BC -> */ ANT_AC, + /*ANT_ABC -> */ ANT_ABC, +}; + +#define IWL_DECLARE_RATE_INFO(r, s, ip, in, rp, rn, pp, np) \ + [IWL_RATE_##r##M_INDEX] = { IWL_RATE_##r##M_PLCP, \ + IWL_RATE_SISO_##s##M_PLCP, \ + IWL_RATE_MIMO2_##s##M_PLCP,\ + IWL_RATE_MIMO3_##s##M_PLCP,\ + IWL_RATE_##r##M_IEEE, \ + IWL_RATE_##ip##M_INDEX, \ + IWL_RATE_##in##M_INDEX, \ + IWL_RATE_##rp##M_INDEX, \ + IWL_RATE_##rn##M_INDEX, \ + IWL_RATE_##pp##M_INDEX, \ + IWL_RATE_##np##M_INDEX } + +/* + * Parameter order: + * rate, ht rate, prev rate, next rate, prev tgg rate, next tgg rate + * + * If there isn't a valid next or previous rate then INV is used which + * maps to IWL_RATE_INVALID + * + */ +const struct iwl_rate_info iwl_rates[IWL_RATE_COUNT] = { + IWL_DECLARE_RATE_INFO(1, INV, INV, 2, INV, 2, INV, 2), /* 1mbps */ + IWL_DECLARE_RATE_INFO(2, INV, 1, 5, 1, 5, 1, 5), /* 2mbps */ + IWL_DECLARE_RATE_INFO(5, INV, 2, 6, 2, 11, 2, 11), /*5.5mbps */ + IWL_DECLARE_RATE_INFO(11, INV, 9, 12, 9, 12, 5, 18), /* 11mbps */ + IWL_DECLARE_RATE_INFO(6, 6, 5, 9, 5, 11, 5, 11), /* 6mbps */ + IWL_DECLARE_RATE_INFO(9, 6, 6, 11, 6, 11, 5, 11), /* 9mbps */ + IWL_DECLARE_RATE_INFO(12, 12, 11, 18, 11, 18, 11, 18), /* 12mbps */ + IWL_DECLARE_RATE_INFO(18, 18, 12, 24, 12, 24, 11, 24), /* 18mbps */ + IWL_DECLARE_RATE_INFO(24, 24, 18, 36, 18, 36, 18, 36), /* 24mbps */ + IWL_DECLARE_RATE_INFO(36, 36, 24, 48, 24, 48, 24, 48), /* 36mbps */ + IWL_DECLARE_RATE_INFO(48, 48, 36, 54, 36, 54, 36, 54), /* 48mbps */ + IWL_DECLARE_RATE_INFO(54, 54, 48, INV, 48, INV, 48, INV),/* 54mbps */ + IWL_DECLARE_RATE_INFO(60, 60, 48, INV, 48, INV, 48, INV),/* 60mbps */ + /* FIXME:RS: ^^ should be INV (legacy) */ +}; + +static inline u8 rs_extract_rate(u32 rate_n_flags) +{ + return (u8)(rate_n_flags & RATE_MCS_RATE_MSK); +} + +static int iwl_hwrate_to_plcp_idx(u32 rate_n_flags) +{ + int idx = 0; + + /* HT rate format */ + if (rate_n_flags & RATE_MCS_HT_MSK) { + idx = rs_extract_rate(rate_n_flags); + + if (idx >= IWL_RATE_MIMO3_6M_PLCP) + idx = idx - IWL_RATE_MIMO3_6M_PLCP; + else if (idx >= IWL_RATE_MIMO2_6M_PLCP) + idx = idx - IWL_RATE_MIMO2_6M_PLCP; + + idx += IWL_FIRST_OFDM_RATE; + /* skip 9M not supported in ht*/ + if (idx >= IWL_RATE_9M_INDEX) + idx += 1; + if ((idx >= IWL_FIRST_OFDM_RATE) && (idx <= IWL_LAST_OFDM_RATE)) + return idx; + + /* legacy rate format, search for match in table */ + } else { + for (idx = 0; idx < ARRAY_SIZE(iwl_rates); idx++) + if (iwl_rates[idx].plcp == + rs_extract_rate(rate_n_flags)) + return idx; + } + + return -1; +} + +static void rs_rate_scale_perform(struct iwl_priv *priv, + struct sk_buff *skb, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta); +static void rs_fill_link_cmd(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, u32 rate_n_flags); +static void rs_stay_in_table(struct iwl_lq_sta *lq_sta, bool force_search); + + +#ifdef CPTCFG_MAC80211_DEBUGFS +static void rs_dbgfs_set_mcs(struct iwl_lq_sta *lq_sta, + u32 *rate_n_flags, int index); +#else +static void rs_dbgfs_set_mcs(struct iwl_lq_sta *lq_sta, + u32 *rate_n_flags, int index) +{} +#endif + +/** + * The following tables contain the expected throughput metrics for all rates + * + * 1, 2, 5.5, 11, 6, 9, 12, 18, 24, 36, 48, 54, 60 MBits + * + * where invalid entries are zeros. + * + * CCK rates are only valid in legacy table and will only be used in G + * (2.4 GHz) band. + */ + +static s32 expected_tpt_legacy[IWL_RATE_COUNT] = { + 7, 13, 35, 58, 40, 57, 72, 98, 121, 154, 177, 186, 0 +}; + +static s32 expected_tpt_siso20MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 42, 0, 76, 102, 124, 159, 183, 193, 202}, /* Norm */ + {0, 0, 0, 0, 46, 0, 82, 110, 132, 168, 192, 202, 210}, /* SGI */ + {0, 0, 0, 0, 47, 0, 91, 133, 171, 242, 305, 334, 362}, /* AGG */ + {0, 0, 0, 0, 52, 0, 101, 145, 187, 264, 330, 361, 390}, /* AGG+SGI */ +}; + +static s32 expected_tpt_siso40MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 77, 0, 127, 160, 184, 220, 242, 250, 257}, /* Norm */ + {0, 0, 0, 0, 83, 0, 135, 169, 193, 229, 250, 257, 264}, /* SGI */ + {0, 0, 0, 0, 94, 0, 177, 249, 313, 423, 512, 550, 586}, /* AGG */ + {0, 0, 0, 0, 104, 0, 193, 270, 338, 454, 545, 584, 620}, /* AGG+SGI */ +}; + +static s32 expected_tpt_mimo2_20MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 74, 0, 123, 155, 179, 214, 236, 244, 251}, /* Norm */ + {0, 0, 0, 0, 81, 0, 131, 164, 188, 223, 243, 251, 257}, /* SGI */ + {0, 0, 0, 0, 89, 0, 167, 235, 296, 402, 488, 526, 560}, /* AGG */ + {0, 0, 0, 0, 97, 0, 182, 255, 320, 431, 520, 558, 593}, /* AGG+SGI*/ +}; + +static s32 expected_tpt_mimo2_40MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 123, 0, 182, 214, 235, 264, 279, 285, 289}, /* Norm */ + {0, 0, 0, 0, 131, 0, 191, 222, 242, 270, 284, 289, 293}, /* SGI */ + {0, 0, 0, 0, 171, 0, 305, 410, 496, 634, 731, 771, 805}, /* AGG */ + {0, 0, 0, 0, 186, 0, 329, 439, 527, 667, 764, 803, 838}, /* AGG+SGI */ +}; + +static s32 expected_tpt_mimo3_20MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 99, 0, 153, 186, 208, 239, 256, 263, 268}, /* Norm */ + {0, 0, 0, 0, 106, 0, 162, 194, 215, 246, 262, 268, 273}, /* SGI */ + {0, 0, 0, 0, 134, 0, 249, 346, 431, 574, 685, 732, 775}, /* AGG */ + {0, 0, 0, 0, 148, 0, 272, 376, 465, 614, 727, 775, 818}, /* AGG+SGI */ +}; + +static s32 expected_tpt_mimo3_40MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 152, 0, 211, 239, 255, 279, 290, 294, 297}, /* Norm */ + {0, 0, 0, 0, 160, 0, 219, 245, 261, 284, 294, 297, 300}, /* SGI */ + {0, 0, 0, 0, 254, 0, 443, 584, 695, 868, 984, 1030, 1070}, /* AGG */ + {0, 0, 0, 0, 277, 0, 478, 624, 737, 911, 1026, 1070, 1109}, /* AGG+SGI */ +}; + +/* mbps, mcs */ +static const struct iwl_rate_mcs_info iwl_rate_mcs[IWL_RATE_COUNT] = { + { "1", "BPSK DSSS"}, + { "2", "QPSK DSSS"}, + {"5.5", "BPSK CCK"}, + { "11", "QPSK CCK"}, + { "6", "BPSK 1/2"}, + { "9", "BPSK 1/2"}, + { "12", "QPSK 1/2"}, + { "18", "QPSK 3/4"}, + { "24", "16QAM 1/2"}, + { "36", "16QAM 3/4"}, + { "48", "64QAM 2/3"}, + { "54", "64QAM 3/4"}, + { "60", "64QAM 5/6"}, +}; + +#define MCS_INDEX_PER_STREAM (8) + +static void rs_rate_scale_clear_window(struct iwl_rate_scale_data *window) +{ + window->data = 0; + window->success_counter = 0; + window->success_ratio = IWL_INVALID_VALUE; + window->counter = 0; + window->average_tpt = IWL_INVALID_VALUE; + window->stamp = 0; +} + +static inline u8 rs_is_valid_ant(u8 valid_antenna, u8 ant_type) +{ + return (ant_type & valid_antenna) == ant_type; +} + +/* + * removes the old data from the statistics. All data that is older than + * TID_MAX_TIME_DIFF, will be deleted. + */ +static void rs_tl_rm_old_stats(struct iwl_traffic_load *tl, u32 curr_time) +{ + /* The oldest age we want to keep */ + u32 oldest_time = curr_time - TID_MAX_TIME_DIFF; + + while (tl->queue_count && + (tl->time_stamp < oldest_time)) { + tl->total -= tl->packet_count[tl->head]; + tl->packet_count[tl->head] = 0; + tl->time_stamp += TID_QUEUE_CELL_SPACING; + tl->queue_count--; + tl->head++; + if (tl->head >= TID_QUEUE_MAX_SIZE) + tl->head = 0; + } +} + +/* + * increment traffic load value for tid and also remove + * any old values if passed the certain time period + */ +static u8 rs_tl_add_packet(struct iwl_lq_sta *lq_data, + struct ieee80211_hdr *hdr) +{ + u32 curr_time = jiffies_to_msecs(jiffies); + u32 time_diff; + s32 index; + struct iwl_traffic_load *tl = NULL; + u8 tid; + + if (ieee80211_is_data_qos(hdr->frame_control)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + tid = qc[0] & 0xf; + } else + return IWL_MAX_TID_COUNT; + + if (unlikely(tid >= IWL_MAX_TID_COUNT)) + return IWL_MAX_TID_COUNT; + + tl = &lq_data->load[tid]; + + curr_time -= curr_time % TID_ROUND_VALUE; + + /* Happens only for the first packet. Initialize the data */ + if (!(tl->queue_count)) { + tl->total = 1; + tl->time_stamp = curr_time; + tl->queue_count = 1; + tl->head = 0; + tl->packet_count[0] = 1; + return IWL_MAX_TID_COUNT; + } + + time_diff = TIME_WRAP_AROUND(tl->time_stamp, curr_time); + index = time_diff / TID_QUEUE_CELL_SPACING; + + /* The history is too long: remove data that is older than */ + /* TID_MAX_TIME_DIFF */ + if (index >= TID_QUEUE_MAX_SIZE) + rs_tl_rm_old_stats(tl, curr_time); + + index = (tl->head + index) % TID_QUEUE_MAX_SIZE; + tl->packet_count[index] = tl->packet_count[index] + 1; + tl->total = tl->total + 1; + + if ((index + 1) > tl->queue_count) + tl->queue_count = index + 1; + + return tid; +} + +#ifdef CPTCFG_MAC80211_DEBUGFS +/** + * Program the device to use fixed rate for frame transmit + * This is for debugging/testing only + * once the device start use fixed rate, we need to reload the module + * to being back the normal operation. + */ +static void rs_program_fix_rate(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta) +{ + struct iwl_station_priv *sta_priv = + container_of(lq_sta, struct iwl_station_priv, lq_sta); + struct iwl_rxon_context *ctx = sta_priv->ctx; + + lq_sta->active_legacy_rate = 0x0FFF; /* 1 - 54 MBits, includes CCK */ + lq_sta->active_siso_rate = 0x1FD0; /* 6 - 60 MBits, no 9, no CCK */ + lq_sta->active_mimo2_rate = 0x1FD0; /* 6 - 60 MBits, no 9, no CCK */ + lq_sta->active_mimo3_rate = 0x1FD0; /* 6 - 60 MBits, no 9, no CCK */ + +#ifdef CPTCFG_IWLWIFI_DEVICE_TESTMODE + /* testmode has higher priority to overwirte the fixed rate */ + if (priv->tm_fixed_rate) + lq_sta->dbg_fixed_rate = priv->tm_fixed_rate; +#endif + + IWL_DEBUG_RATE(priv, "sta_id %d rate 0x%X\n", + lq_sta->lq.sta_id, lq_sta->dbg_fixed_rate); + + if (lq_sta->dbg_fixed_rate) { + rs_fill_link_cmd(NULL, lq_sta, lq_sta->dbg_fixed_rate); + iwl_send_lq_cmd(lq_sta->drv, ctx, &lq_sta->lq, CMD_ASYNC, + false); + } +} +#endif + +/* + get the traffic load value for tid +*/ +static u32 rs_tl_get_load(struct iwl_lq_sta *lq_data, u8 tid) +{ + u32 curr_time = jiffies_to_msecs(jiffies); + u32 time_diff; + s32 index; + struct iwl_traffic_load *tl = NULL; + + if (tid >= IWL_MAX_TID_COUNT) + return 0; + + tl = &(lq_data->load[tid]); + + curr_time -= curr_time % TID_ROUND_VALUE; + + if (!(tl->queue_count)) + return 0; + + time_diff = TIME_WRAP_AROUND(tl->time_stamp, curr_time); + index = time_diff / TID_QUEUE_CELL_SPACING; + + /* The history is too long: remove data that is older than */ + /* TID_MAX_TIME_DIFF */ + if (index >= TID_QUEUE_MAX_SIZE) + rs_tl_rm_old_stats(tl, curr_time); + + return tl->total; +} + +static int rs_tl_turn_on_agg_for_tid(struct iwl_priv *priv, + struct iwl_lq_sta *lq_data, u8 tid, + struct ieee80211_sta *sta) +{ + int ret = -EAGAIN; + u32 load; + + /* + * Don't create TX aggregation sessions when in high + * BT traffic, as they would just be disrupted by BT. + */ + if (priv->bt_traffic_load >= IWL_BT_COEX_TRAFFIC_LOAD_HIGH) { + IWL_DEBUG_COEX(priv, + "BT traffic (%d), no aggregation allowed\n", + priv->bt_traffic_load); + return ret; + } + + load = rs_tl_get_load(lq_data, tid); + + if ((iwlwifi_mod_params.auto_agg) || (load > IWL_AGG_LOAD_THRESHOLD)) { + IWL_DEBUG_HT(priv, "Starting Tx agg: STA: %pM tid: %d\n", + sta->addr, tid); + ret = ieee80211_start_tx_ba_session(sta, tid, 5000); + if (ret == -EAGAIN) { + /* + * driver and mac80211 is out of sync + * this might be cause by reloading firmware + * stop the tx ba session here + */ + IWL_ERR(priv, "Fail start Tx agg on tid: %d\n", + tid); + ieee80211_stop_tx_ba_session(sta, tid); + } + } else { + IWL_DEBUG_HT(priv, "Aggregation not enabled for tid %d " + "because load = %u\n", tid, load); + } + return ret; +} + +static void rs_tl_turn_on_agg(struct iwl_priv *priv, u8 tid, + struct iwl_lq_sta *lq_data, + struct ieee80211_sta *sta) +{ + if (tid < IWL_MAX_TID_COUNT) + rs_tl_turn_on_agg_for_tid(priv, lq_data, tid, sta); + else + IWL_ERR(priv, "tid exceeds max TID count: %d/%d\n", + tid, IWL_MAX_TID_COUNT); +} + +static inline int get_num_of_ant_from_rate(u32 rate_n_flags) +{ + return !!(rate_n_flags & RATE_MCS_ANT_A_MSK) + + !!(rate_n_flags & RATE_MCS_ANT_B_MSK) + + !!(rate_n_flags & RATE_MCS_ANT_C_MSK); +} + +/* + * Static function to get the expected throughput from an iwl_scale_tbl_info + * that wraps a NULL pointer check + */ +static s32 get_expected_tpt(struct iwl_scale_tbl_info *tbl, int rs_index) +{ + if (tbl->expected_tpt) + return tbl->expected_tpt[rs_index]; + return 0; +} + +/** + * rs_collect_tx_data - Update the success/failure sliding window + * + * We keep a sliding window of the last 62 packets transmitted + * at this rate. window->data contains the bitmask of successful + * packets. + */ +static int rs_collect_tx_data(struct iwl_scale_tbl_info *tbl, + int scale_index, int attempts, int successes) +{ + struct iwl_rate_scale_data *window = NULL; + static const u64 mask = (((u64)1) << (IWL_RATE_MAX_WINDOW - 1)); + s32 fail_count, tpt; + + if (scale_index < 0 || scale_index >= IWL_RATE_COUNT) + return -EINVAL; + + /* Select window for current tx bit rate */ + window = &(tbl->win[scale_index]); + + /* Get expected throughput */ + tpt = get_expected_tpt(tbl, scale_index); + + /* + * Keep track of only the latest 62 tx frame attempts in this rate's + * history window; anything older isn't really relevant any more. + * If we have filled up the sliding window, drop the oldest attempt; + * if the oldest attempt (highest bit in bitmap) shows "success", + * subtract "1" from the success counter (this is the main reason + * we keep these bitmaps!). + */ + while (attempts > 0) { + if (window->counter >= IWL_RATE_MAX_WINDOW) { + + /* remove earliest */ + window->counter = IWL_RATE_MAX_WINDOW - 1; + + if (window->data & mask) { + window->data &= ~mask; + window->success_counter--; + } + } + + /* Increment frames-attempted counter */ + window->counter++; + + /* Shift bitmap by one frame to throw away oldest history */ + window->data <<= 1; + + /* Mark the most recent #successes attempts as successful */ + if (successes > 0) { + window->success_counter++; + window->data |= 0x1; + successes--; + } + + attempts--; + } + + /* Calculate current success ratio, avoid divide-by-0! */ + if (window->counter > 0) + window->success_ratio = 128 * (100 * window->success_counter) + / window->counter; + else + window->success_ratio = IWL_INVALID_VALUE; + + fail_count = window->counter - window->success_counter; + + /* Calculate average throughput, if we have enough history. */ + if ((fail_count >= IWL_RATE_MIN_FAILURE_TH) || + (window->success_counter >= IWL_RATE_MIN_SUCCESS_TH)) + window->average_tpt = (window->success_ratio * tpt + 64) / 128; + else + window->average_tpt = IWL_INVALID_VALUE; + + /* Tag this window as having been updated */ + window->stamp = jiffies; + + return 0; +} + +/* + * Fill uCode API rate_n_flags field, based on "search" or "active" table. + */ +/* FIXME:RS:remove this function and put the flags statically in the table */ +static u32 rate_n_flags_from_tbl(struct iwl_priv *priv, + struct iwl_scale_tbl_info *tbl, + int index, u8 use_green) +{ + u32 rate_n_flags = 0; + + if (is_legacy(tbl->lq_type)) { + rate_n_flags = iwl_rates[index].plcp; + if (index >= IWL_FIRST_CCK_RATE && index <= IWL_LAST_CCK_RATE) + rate_n_flags |= RATE_MCS_CCK_MSK; + + } else if (is_Ht(tbl->lq_type)) { + if (index > IWL_LAST_OFDM_RATE) { + IWL_ERR(priv, "Invalid HT rate index %d\n", index); + index = IWL_LAST_OFDM_RATE; + } + rate_n_flags = RATE_MCS_HT_MSK; + + if (is_siso(tbl->lq_type)) + rate_n_flags |= iwl_rates[index].plcp_siso; + else if (is_mimo2(tbl->lq_type)) + rate_n_flags |= iwl_rates[index].plcp_mimo2; + else + rate_n_flags |= iwl_rates[index].plcp_mimo3; + } else { + IWL_ERR(priv, "Invalid tbl->lq_type %d\n", tbl->lq_type); + } + + rate_n_flags |= ((tbl->ant_type << RATE_MCS_ANT_POS) & + RATE_MCS_ANT_ABC_MSK); + + if (is_Ht(tbl->lq_type)) { + if (tbl->is_ht40) { + if (tbl->is_dup) + rate_n_flags |= RATE_MCS_DUP_MSK; + else + rate_n_flags |= RATE_MCS_HT40_MSK; + } + if (tbl->is_SGI) + rate_n_flags |= RATE_MCS_SGI_MSK; + + if (use_green) { + rate_n_flags |= RATE_MCS_GF_MSK; + if (is_siso(tbl->lq_type) && tbl->is_SGI) { + rate_n_flags &= ~RATE_MCS_SGI_MSK; + IWL_ERR(priv, "GF was set with SGI:SISO\n"); + } + } + } + return rate_n_flags; +} + +/* + * Interpret uCode API's rate_n_flags format, + * fill "search" or "active" tx mode table. + */ +static int rs_get_tbl_info_from_mcs(const u32 rate_n_flags, + enum ieee80211_band band, + struct iwl_scale_tbl_info *tbl, + int *rate_idx) +{ + u32 ant_msk = (rate_n_flags & RATE_MCS_ANT_ABC_MSK); + u8 num_of_ant = get_num_of_ant_from_rate(rate_n_flags); + u8 mcs; + + memset(tbl, 0, sizeof(struct iwl_scale_tbl_info)); + *rate_idx = iwl_hwrate_to_plcp_idx(rate_n_flags); + + if (*rate_idx == IWL_RATE_INVALID) { + *rate_idx = -1; + return -EINVAL; + } + tbl->is_SGI = 0; /* default legacy setup */ + tbl->is_ht40 = 0; + tbl->is_dup = 0; + tbl->ant_type = (ant_msk >> RATE_MCS_ANT_POS); + tbl->lq_type = LQ_NONE; + tbl->max_search = IWL_MAX_SEARCH; + + /* legacy rate format */ + if (!(rate_n_flags & RATE_MCS_HT_MSK)) { + if (num_of_ant == 1) { + if (band == IEEE80211_BAND_5GHZ) + tbl->lq_type = LQ_A; + else + tbl->lq_type = LQ_G; + } + /* HT rate format */ + } else { + if (rate_n_flags & RATE_MCS_SGI_MSK) + tbl->is_SGI = 1; + + if ((rate_n_flags & RATE_MCS_HT40_MSK) || + (rate_n_flags & RATE_MCS_DUP_MSK)) + tbl->is_ht40 = 1; + + if (rate_n_flags & RATE_MCS_DUP_MSK) + tbl->is_dup = 1; + + mcs = rs_extract_rate(rate_n_flags); + + /* SISO */ + if (mcs <= IWL_RATE_SISO_60M_PLCP) { + if (num_of_ant == 1) + tbl->lq_type = LQ_SISO; /*else NONE*/ + /* MIMO2 */ + } else if (mcs <= IWL_RATE_MIMO2_60M_PLCP) { + if (num_of_ant == 2) + tbl->lq_type = LQ_MIMO2; + /* MIMO3 */ + } else { + if (num_of_ant == 3) { + tbl->max_search = IWL_MAX_11N_MIMO3_SEARCH; + tbl->lq_type = LQ_MIMO3; + } + } + } + return 0; +} + +/* switch to another antenna/antennas and return 1 */ +/* if no other valid antenna found, return 0 */ +static int rs_toggle_antenna(u32 valid_ant, u32 *rate_n_flags, + struct iwl_scale_tbl_info *tbl) +{ + u8 new_ant_type; + + if (!tbl->ant_type || tbl->ant_type > ANT_ABC) + return 0; + + if (!rs_is_valid_ant(valid_ant, tbl->ant_type)) + return 0; + + new_ant_type = ant_toggle_lookup[tbl->ant_type]; + + while ((new_ant_type != tbl->ant_type) && + !rs_is_valid_ant(valid_ant, new_ant_type)) + new_ant_type = ant_toggle_lookup[new_ant_type]; + + if (new_ant_type == tbl->ant_type) + return 0; + + tbl->ant_type = new_ant_type; + *rate_n_flags &= ~RATE_MCS_ANT_ABC_MSK; + *rate_n_flags |= new_ant_type << RATE_MCS_ANT_POS; + return 1; +} + +/** + * Green-field mode is valid if the station supports it and + * there are no non-GF stations present in the BSS. + */ +static bool rs_use_green(struct ieee80211_sta *sta) +{ + /* + * There's a bug somewhere in this code that causes the + * scaling to get stuck because GF+SGI can't be combined + * in SISO rates. Until we find that bug, disable GF, it + * has only limited benefit and we still interoperate with + * GF APs since we can always receive GF transmissions. + */ + return false; +} + +/** + * rs_get_supported_rates - get the available rates + * + * if management frame or broadcast frame only return + * basic available rates. + * + */ +static u16 rs_get_supported_rates(struct iwl_lq_sta *lq_sta, + struct ieee80211_hdr *hdr, + enum iwl_table_type rate_type) +{ + if (is_legacy(rate_type)) { + return lq_sta->active_legacy_rate; + } else { + if (is_siso(rate_type)) + return lq_sta->active_siso_rate; + else if (is_mimo2(rate_type)) + return lq_sta->active_mimo2_rate; + else + return lq_sta->active_mimo3_rate; + } +} + +static u16 rs_get_adjacent_rate(struct iwl_priv *priv, u8 index, u16 rate_mask, + int rate_type) +{ + u8 high = IWL_RATE_INVALID; + u8 low = IWL_RATE_INVALID; + + /* 802.11A or ht walks to the next literal adjacent rate in + * the rate table */ + if (is_a_band(rate_type) || !is_legacy(rate_type)) { + int i; + u32 mask; + + /* Find the previous rate that is in the rate mask */ + i = index - 1; + for (mask = (1 << i); i >= 0; i--, mask >>= 1) { + if (rate_mask & mask) { + low = i; + break; + } + } + + /* Find the next rate that is in the rate mask */ + i = index + 1; + for (mask = (1 << i); i < IWL_RATE_COUNT; i++, mask <<= 1) { + if (rate_mask & mask) { + high = i; + break; + } + } + + return (high << 8) | low; + } + + low = index; + while (low != IWL_RATE_INVALID) { + low = iwl_rates[low].prev_rs; + if (low == IWL_RATE_INVALID) + break; + if (rate_mask & (1 << low)) + break; + IWL_DEBUG_RATE(priv, "Skipping masked lower rate: %d\n", low); + } + + high = index; + while (high != IWL_RATE_INVALID) { + high = iwl_rates[high].next_rs; + if (high == IWL_RATE_INVALID) + break; + if (rate_mask & (1 << high)) + break; + IWL_DEBUG_RATE(priv, "Skipping masked higher rate: %d\n", high); + } + + return (high << 8) | low; +} + +static u32 rs_get_lower_rate(struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl, + u8 scale_index, u8 ht_possible) +{ + s32 low; + u16 rate_mask; + u16 high_low; + u8 switch_to_legacy = 0; + u8 is_green = lq_sta->is_green; + struct iwl_priv *priv = lq_sta->drv; + + /* check if we need to switch from HT to legacy rates. + * assumption is that mandatory rates (1Mbps or 6Mbps) + * are always supported (spec demand) */ + if (!is_legacy(tbl->lq_type) && (!ht_possible || !scale_index)) { + switch_to_legacy = 1; + scale_index = rs_ht_to_legacy[scale_index]; + if (lq_sta->band == IEEE80211_BAND_5GHZ) + tbl->lq_type = LQ_A; + else + tbl->lq_type = LQ_G; + + if (num_of_ant(tbl->ant_type) > 1) + tbl->ant_type = + first_antenna(priv->nvm_data->valid_tx_ant); + + tbl->is_ht40 = 0; + tbl->is_SGI = 0; + tbl->max_search = IWL_MAX_SEARCH; + } + + rate_mask = rs_get_supported_rates(lq_sta, NULL, tbl->lq_type); + + /* Mask with station rate restriction */ + if (is_legacy(tbl->lq_type)) { + /* supp_rates has no CCK bits in A mode */ + if (lq_sta->band == IEEE80211_BAND_5GHZ) + rate_mask = (u16)(rate_mask & + (lq_sta->supp_rates << IWL_FIRST_OFDM_RATE)); + else + rate_mask = (u16)(rate_mask & lq_sta->supp_rates); + } + + /* If we switched from HT to legacy, check current rate */ + if (switch_to_legacy && (rate_mask & (1 << scale_index))) { + low = scale_index; + goto out; + } + + high_low = rs_get_adjacent_rate(lq_sta->drv, scale_index, rate_mask, + tbl->lq_type); + low = high_low & 0xff; + + if (low == IWL_RATE_INVALID) + low = scale_index; + +out: + return rate_n_flags_from_tbl(lq_sta->drv, tbl, low, is_green); +} + +/* + * Simple function to compare two rate scale table types + */ +static bool table_type_matches(struct iwl_scale_tbl_info *a, + struct iwl_scale_tbl_info *b) +{ + return (a->lq_type == b->lq_type) && (a->ant_type == b->ant_type) && + (a->is_SGI == b->is_SGI); +} + +static void rs_bt_update_lq(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + struct iwl_lq_sta *lq_sta) +{ + struct iwl_scale_tbl_info *tbl; + bool full_concurrent = priv->bt_full_concurrent; + + if (priv->bt_ant_couple_ok) { + /* + * Is there a need to switch between + * full concurrency and 3-wire? + */ + if (priv->bt_ci_compliance && priv->bt_ant_couple_ok) + full_concurrent = true; + else + full_concurrent = false; + } + if ((priv->bt_traffic_load != priv->last_bt_traffic_load) || + (priv->bt_full_concurrent != full_concurrent)) { + priv->bt_full_concurrent = full_concurrent; + priv->last_bt_traffic_load = priv->bt_traffic_load; + + /* Update uCode's rate table. */ + tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + rs_fill_link_cmd(priv, lq_sta, tbl->current_rate); + iwl_send_lq_cmd(priv, ctx, &lq_sta->lq, CMD_ASYNC, false); + + queue_work(priv->workqueue, &priv->bt_full_concurrency); + } +} + +/* + * mac80211 sends us Tx status + */ +static void rs_tx_status(void *priv_r, struct ieee80211_supported_band *sband, + struct ieee80211_sta *sta, void *priv_sta, + struct sk_buff *skb) +{ + int legacy_success; + int retries; + int rs_index, mac_index, i; + struct iwl_lq_sta *lq_sta = priv_sta; + struct iwl_link_quality_cmd *table; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct iwl_op_mode *op_mode = (struct iwl_op_mode *)priv_r; + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + enum mac80211_rate_control_flags mac_flags; + u32 tx_rate; + struct iwl_scale_tbl_info tbl_type; + struct iwl_scale_tbl_info *curr_tbl, *other_tbl, *tmp_tbl; + struct iwl_station_priv *sta_priv = (void *)sta->drv_priv; + struct iwl_rxon_context *ctx = sta_priv->ctx; + + IWL_DEBUG_RATE_LIMIT(priv, "get frame ack response, update rate scale window\n"); + + /* Treat uninitialized rate scaling data same as non-existing. */ + if (!lq_sta) { + IWL_DEBUG_RATE(priv, "Station rate scaling not created yet.\n"); + return; + } else if (!lq_sta->drv) { + IWL_DEBUG_RATE(priv, "Rate scaling not initialized yet.\n"); + return; + } + + if (!ieee80211_is_data(hdr->frame_control) || + info->flags & IEEE80211_TX_CTL_NO_ACK) + return; + + /* This packet was aggregated but doesn't carry status info */ + if ((info->flags & IEEE80211_TX_CTL_AMPDU) && + !(info->flags & IEEE80211_TX_STAT_AMPDU)) + return; + + /* + * Ignore this Tx frame response if its initial rate doesn't match + * that of latest Link Quality command. There may be stragglers + * from a previous Link Quality command, but we're no longer interested + * in those; they're either from the "active" mode while we're trying + * to check "search" mode, or a prior "search" mode after we've moved + * to a new "search" mode (which might become the new "active" mode). + */ + table = &lq_sta->lq; + tx_rate = le32_to_cpu(table->rs_table[0].rate_n_flags); + rs_get_tbl_info_from_mcs(tx_rate, priv->band, &tbl_type, &rs_index); + if (priv->band == IEEE80211_BAND_5GHZ) + rs_index -= IWL_FIRST_OFDM_RATE; + mac_flags = info->status.rates[0].flags; + mac_index = info->status.rates[0].idx; + /* For HT packets, map MCS to PLCP */ + if (mac_flags & IEEE80211_TX_RC_MCS) { + mac_index &= RATE_MCS_CODE_MSK; /* Remove # of streams */ + if (mac_index >= (IWL_RATE_9M_INDEX - IWL_FIRST_OFDM_RATE)) + mac_index++; + /* + * mac80211 HT index is always zero-indexed; we need to move + * HT OFDM rates after CCK rates in 2.4 GHz band + */ + if (priv->band == IEEE80211_BAND_2GHZ) + mac_index += IWL_FIRST_OFDM_RATE; + } + /* Here we actually compare this rate to the latest LQ command */ + if ((mac_index < 0) || + (tbl_type.is_SGI != !!(mac_flags & IEEE80211_TX_RC_SHORT_GI)) || + (tbl_type.is_ht40 != !!(mac_flags & IEEE80211_TX_RC_40_MHZ_WIDTH)) || + (tbl_type.is_dup != !!(mac_flags & IEEE80211_TX_RC_DUP_DATA)) || + (tbl_type.ant_type != info->status.antenna) || + (!!(tx_rate & RATE_MCS_HT_MSK) != !!(mac_flags & IEEE80211_TX_RC_MCS)) || + (!!(tx_rate & RATE_MCS_GF_MSK) != !!(mac_flags & IEEE80211_TX_RC_GREEN_FIELD)) || + (rs_index != mac_index)) { + IWL_DEBUG_RATE(priv, "initial rate %d does not match %d (0x%x)\n", mac_index, rs_index, tx_rate); + /* + * Since rates mis-match, the last LQ command may have failed. + * After IWL_MISSED_RATE_MAX mis-matches, resync the uCode with + * ... driver. + */ + lq_sta->missed_rate_counter++; + if (lq_sta->missed_rate_counter > IWL_MISSED_RATE_MAX) { + lq_sta->missed_rate_counter = 0; + iwl_send_lq_cmd(priv, ctx, &lq_sta->lq, CMD_ASYNC, false); + } + /* Regardless, ignore this status info for outdated rate */ + return; + } else + /* Rate did match, so reset the missed_rate_counter */ + lq_sta->missed_rate_counter = 0; + + /* Figure out if rate scale algorithm is in active or search table */ + if (table_type_matches(&tbl_type, + &(lq_sta->lq_info[lq_sta->active_tbl]))) { + curr_tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + other_tbl = &(lq_sta->lq_info[1 - lq_sta->active_tbl]); + } else if (table_type_matches(&tbl_type, + &lq_sta->lq_info[1 - lq_sta->active_tbl])) { + curr_tbl = &(lq_sta->lq_info[1 - lq_sta->active_tbl]); + other_tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + } else { + IWL_DEBUG_RATE(priv, "Neither active nor search matches tx rate\n"); + tmp_tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + IWL_DEBUG_RATE(priv, "active- lq:%x, ant:%x, SGI:%d\n", + tmp_tbl->lq_type, tmp_tbl->ant_type, tmp_tbl->is_SGI); + tmp_tbl = &(lq_sta->lq_info[1 - lq_sta->active_tbl]); + IWL_DEBUG_RATE(priv, "search- lq:%x, ant:%x, SGI:%d\n", + tmp_tbl->lq_type, tmp_tbl->ant_type, tmp_tbl->is_SGI); + IWL_DEBUG_RATE(priv, "actual- lq:%x, ant:%x, SGI:%d\n", + tbl_type.lq_type, tbl_type.ant_type, tbl_type.is_SGI); + /* + * no matching table found, let's by-pass the data collection + * and continue to perform rate scale to find the rate table + */ + rs_stay_in_table(lq_sta, true); + goto done; + } + + /* + * Updating the frame history depends on whether packets were + * aggregated. + * + * For aggregation, all packets were transmitted at the same rate, the + * first index into rate scale table. + */ + if (info->flags & IEEE80211_TX_STAT_AMPDU) { + tx_rate = le32_to_cpu(table->rs_table[0].rate_n_flags); + rs_get_tbl_info_from_mcs(tx_rate, priv->band, &tbl_type, + &rs_index); + rs_collect_tx_data(curr_tbl, rs_index, + info->status.ampdu_len, + info->status.ampdu_ack_len); + + /* Update success/fail counts if not searching for new mode */ + if (lq_sta->stay_in_tbl) { + lq_sta->total_success += info->status.ampdu_ack_len; + lq_sta->total_failed += (info->status.ampdu_len - + info->status.ampdu_ack_len); + } + } else { + /* + * For legacy, update frame history with for each Tx retry. + */ + retries = info->status.rates[0].count - 1; + /* HW doesn't send more than 15 retries */ + retries = min(retries, 15); + + /* The last transmission may have been successful */ + legacy_success = !!(info->flags & IEEE80211_TX_STAT_ACK); + /* Collect data for each rate used during failed TX attempts */ + for (i = 0; i <= retries; ++i) { + tx_rate = le32_to_cpu(table->rs_table[i].rate_n_flags); + rs_get_tbl_info_from_mcs(tx_rate, priv->band, + &tbl_type, &rs_index); + /* + * Only collect stats if retried rate is in the same RS + * table as active/search. + */ + if (table_type_matches(&tbl_type, curr_tbl)) + tmp_tbl = curr_tbl; + else if (table_type_matches(&tbl_type, other_tbl)) + tmp_tbl = other_tbl; + else + continue; + rs_collect_tx_data(tmp_tbl, rs_index, 1, + i < retries ? 0 : legacy_success); + } + + /* Update success/fail counts if not searching for new mode */ + if (lq_sta->stay_in_tbl) { + lq_sta->total_success += legacy_success; + lq_sta->total_failed += retries + (1 - legacy_success); + } + } + /* The last TX rate is cached in lq_sta; it's set in if/else above */ + lq_sta->last_rate_n_flags = tx_rate; +done: + /* See if there's a better rate or modulation mode to try. */ + if (sta && sta->supp_rates[sband->band]) + rs_rate_scale_perform(priv, skb, sta, lq_sta); + +#if defined(CPTCFG_MAC80211_DEBUGFS) && defined(CPTCFG_IWLWIFI_DEVICE_TESTMODE) + if ((priv->tm_fixed_rate) && + (priv->tm_fixed_rate != lq_sta->dbg_fixed_rate)) + rs_program_fix_rate(priv, lq_sta); +#endif + if (priv->cfg->bt_params && priv->cfg->bt_params->advanced_bt_coexist) + rs_bt_update_lq(priv, ctx, lq_sta); +} + +/* + * Begin a period of staying with a selected modulation mode. + * Set "stay_in_tbl" flag to prevent any mode switches. + * Set frame tx success limits according to legacy vs. high-throughput, + * and reset overall (spanning all rates) tx success history statistics. + * These control how long we stay using same modulation mode before + * searching for a new mode. + */ +static void rs_set_stay_in_table(struct iwl_priv *priv, u8 is_legacy, + struct iwl_lq_sta *lq_sta) +{ + IWL_DEBUG_RATE(priv, "we are staying in the same table\n"); + lq_sta->stay_in_tbl = 1; /* only place this gets set */ + if (is_legacy) { + lq_sta->table_count_limit = IWL_LEGACY_TABLE_COUNT; + lq_sta->max_failure_limit = IWL_LEGACY_FAILURE_LIMIT; + lq_sta->max_success_limit = IWL_LEGACY_SUCCESS_LIMIT; + } else { + lq_sta->table_count_limit = IWL_NONE_LEGACY_TABLE_COUNT; + lq_sta->max_failure_limit = IWL_NONE_LEGACY_FAILURE_LIMIT; + lq_sta->max_success_limit = IWL_NONE_LEGACY_SUCCESS_LIMIT; + } + lq_sta->table_count = 0; + lq_sta->total_failed = 0; + lq_sta->total_success = 0; + lq_sta->flush_timer = jiffies; + lq_sta->action_counter = 0; +} + +/* + * Find correct throughput table for given mode of modulation + */ +static void rs_set_expected_tpt_table(struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl) +{ + /* Used to choose among HT tables */ + s32 (*ht_tbl_pointer)[IWL_RATE_COUNT]; + + /* Check for invalid LQ type */ + if (WARN_ON_ONCE(!is_legacy(tbl->lq_type) && !is_Ht(tbl->lq_type))) { + tbl->expected_tpt = expected_tpt_legacy; + return; + } + + /* Legacy rates have only one table */ + if (is_legacy(tbl->lq_type)) { + tbl->expected_tpt = expected_tpt_legacy; + return; + } + + /* Choose among many HT tables depending on number of streams + * (SISO/MIMO2/MIMO3), channel width (20/40), SGI, and aggregation + * status */ + if (is_siso(tbl->lq_type) && (!tbl->is_ht40 || lq_sta->is_dup)) + ht_tbl_pointer = expected_tpt_siso20MHz; + else if (is_siso(tbl->lq_type)) + ht_tbl_pointer = expected_tpt_siso40MHz; + else if (is_mimo2(tbl->lq_type) && (!tbl->is_ht40 || lq_sta->is_dup)) + ht_tbl_pointer = expected_tpt_mimo2_20MHz; + else if (is_mimo2(tbl->lq_type)) + ht_tbl_pointer = expected_tpt_mimo2_40MHz; + else if (is_mimo3(tbl->lq_type) && (!tbl->is_ht40 || lq_sta->is_dup)) + ht_tbl_pointer = expected_tpt_mimo3_20MHz; + else /* if (is_mimo3(tbl->lq_type)) <-- must be true */ + ht_tbl_pointer = expected_tpt_mimo3_40MHz; + + if (!tbl->is_SGI && !lq_sta->is_agg) /* Normal */ + tbl->expected_tpt = ht_tbl_pointer[0]; + else if (tbl->is_SGI && !lq_sta->is_agg) /* SGI */ + tbl->expected_tpt = ht_tbl_pointer[1]; + else if (!tbl->is_SGI && lq_sta->is_agg) /* AGG */ + tbl->expected_tpt = ht_tbl_pointer[2]; + else /* AGG+SGI */ + tbl->expected_tpt = ht_tbl_pointer[3]; +} + +/* + * Find starting rate for new "search" high-throughput mode of modulation. + * Goal is to find lowest expected rate (under perfect conditions) that is + * above the current measured throughput of "active" mode, to give new mode + * a fair chance to prove itself without too many challenges. + * + * This gets called when transitioning to more aggressive modulation + * (i.e. legacy to SISO or MIMO, or SISO to MIMO), as well as less aggressive + * (i.e. MIMO to SISO). When moving to MIMO, bit rate will typically need + * to decrease to match "active" throughput. When moving from MIMO to SISO, + * bit rate will typically need to increase, but not if performance was bad. + */ +static s32 rs_get_best_rate(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl, /* "search" */ + u16 rate_mask, s8 index) +{ + /* "active" values */ + struct iwl_scale_tbl_info *active_tbl = + &(lq_sta->lq_info[lq_sta->active_tbl]); + s32 active_sr = active_tbl->win[index].success_ratio; + s32 active_tpt = active_tbl->expected_tpt[index]; + + /* expected "search" throughput */ + s32 *tpt_tbl = tbl->expected_tpt; + + s32 new_rate, high, low, start_hi; + u16 high_low; + s8 rate = index; + + new_rate = high = low = start_hi = IWL_RATE_INVALID; + + for (; ;) { + high_low = rs_get_adjacent_rate(priv, rate, rate_mask, + tbl->lq_type); + + low = high_low & 0xff; + high = (high_low >> 8) & 0xff; + + /* + * Lower the "search" bit rate, to give new "search" mode + * approximately the same throughput as "active" if: + * + * 1) "Active" mode has been working modestly well (but not + * great), and expected "search" throughput (under perfect + * conditions) at candidate rate is above the actual + * measured "active" throughput (but less than expected + * "active" throughput under perfect conditions). + * OR + * 2) "Active" mode has been working perfectly or very well + * and expected "search" throughput (under perfect + * conditions) at candidate rate is above expected + * "active" throughput (under perfect conditions). + */ + if ((((100 * tpt_tbl[rate]) > lq_sta->last_tpt) && + ((active_sr > IWL_RATE_DECREASE_TH) && + (active_sr <= IWL_RATE_HIGH_TH) && + (tpt_tbl[rate] <= active_tpt))) || + ((active_sr >= IWL_RATE_SCALE_SWITCH) && + (tpt_tbl[rate] > active_tpt))) { + + /* (2nd or later pass) + * If we've already tried to raise the rate, and are + * now trying to lower it, use the higher rate. */ + if (start_hi != IWL_RATE_INVALID) { + new_rate = start_hi; + break; + } + + new_rate = rate; + + /* Loop again with lower rate */ + if (low != IWL_RATE_INVALID) + rate = low; + + /* Lower rate not available, use the original */ + else + break; + + /* Else try to raise the "search" rate to match "active" */ + } else { + /* (2nd or later pass) + * If we've already tried to lower the rate, and are + * now trying to raise it, use the lower rate. */ + if (new_rate != IWL_RATE_INVALID) + break; + + /* Loop again with higher rate */ + else if (high != IWL_RATE_INVALID) { + start_hi = high; + rate = high; + + /* Higher rate not available, use the original */ + } else { + new_rate = rate; + break; + } + } + } + + return new_rate; +} + +/* + * Set up search table for MIMO2 + */ +static int rs_switch_to_mimo2(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, + struct iwl_scale_tbl_info *tbl, int index) +{ + u16 rate_mask; + s32 rate; + s8 is_green = lq_sta->is_green; + struct iwl_station_priv *sta_priv = (void *)sta->drv_priv; + struct iwl_rxon_context *ctx = sta_priv->ctx; + + if (!conf_is_ht(conf) || !sta->ht_cap.ht_supported) + return -1; + + if (sta->smps_mode == IEEE80211_SMPS_STATIC) + return -1; + + /* Need both Tx chains/antennas to support MIMO */ + if (priv->hw_params.tx_chains_num < 2) + return -1; + + IWL_DEBUG_RATE(priv, "LQ: try to switch to MIMO2\n"); + + tbl->lq_type = LQ_MIMO2; + tbl->is_dup = lq_sta->is_dup; + tbl->action = 0; + tbl->max_search = IWL_MAX_SEARCH; + rate_mask = lq_sta->active_mimo2_rate; + + if (iwl_is_ht40_tx_allowed(priv, ctx, sta)) + tbl->is_ht40 = 1; + else + tbl->is_ht40 = 0; + + rs_set_expected_tpt_table(lq_sta, tbl); + + rate = rs_get_best_rate(priv, lq_sta, tbl, rate_mask, index); + + IWL_DEBUG_RATE(priv, "LQ: MIMO2 best rate %d mask %X\n", rate, rate_mask); + if ((rate == IWL_RATE_INVALID) || !((1 << rate) & rate_mask)) { + IWL_DEBUG_RATE(priv, "Can't switch with index %d rate mask %x\n", + rate, rate_mask); + return -1; + } + tbl->current_rate = rate_n_flags_from_tbl(priv, tbl, rate, is_green); + + IWL_DEBUG_RATE(priv, "LQ: Switch to new mcs %X index is green %X\n", + tbl->current_rate, is_green); + return 0; +} + +/* + * Set up search table for MIMO3 + */ +static int rs_switch_to_mimo3(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, + struct iwl_scale_tbl_info *tbl, int index) +{ + u16 rate_mask; + s32 rate; + s8 is_green = lq_sta->is_green; + struct iwl_station_priv *sta_priv = (void *)sta->drv_priv; + struct iwl_rxon_context *ctx = sta_priv->ctx; + + if (!conf_is_ht(conf) || !sta->ht_cap.ht_supported) + return -1; + + if (sta->smps_mode == IEEE80211_SMPS_STATIC) + return -1; + + /* Need both Tx chains/antennas to support MIMO */ + if (priv->hw_params.tx_chains_num < 3) + return -1; + + IWL_DEBUG_RATE(priv, "LQ: try to switch to MIMO3\n"); + + tbl->lq_type = LQ_MIMO3; + tbl->is_dup = lq_sta->is_dup; + tbl->action = 0; + tbl->max_search = IWL_MAX_11N_MIMO3_SEARCH; + rate_mask = lq_sta->active_mimo3_rate; + + if (iwl_is_ht40_tx_allowed(priv, ctx, sta)) + tbl->is_ht40 = 1; + else + tbl->is_ht40 = 0; + + rs_set_expected_tpt_table(lq_sta, tbl); + + rate = rs_get_best_rate(priv, lq_sta, tbl, rate_mask, index); + + IWL_DEBUG_RATE(priv, "LQ: MIMO3 best rate %d mask %X\n", + rate, rate_mask); + if ((rate == IWL_RATE_INVALID) || !((1 << rate) & rate_mask)) { + IWL_DEBUG_RATE(priv, "Can't switch with index %d rate mask %x\n", + rate, rate_mask); + return -1; + } + tbl->current_rate = rate_n_flags_from_tbl(priv, tbl, rate, is_green); + + IWL_DEBUG_RATE(priv, "LQ: Switch to new mcs %X index is green %X\n", + tbl->current_rate, is_green); + return 0; +} + +/* + * Set up search table for SISO + */ +static int rs_switch_to_siso(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, + struct iwl_scale_tbl_info *tbl, int index) +{ + u16 rate_mask; + u8 is_green = lq_sta->is_green; + s32 rate; + struct iwl_station_priv *sta_priv = (void *)sta->drv_priv; + struct iwl_rxon_context *ctx = sta_priv->ctx; + + if (!conf_is_ht(conf) || !sta->ht_cap.ht_supported) + return -1; + + IWL_DEBUG_RATE(priv, "LQ: try to switch to SISO\n"); + + tbl->is_dup = lq_sta->is_dup; + tbl->lq_type = LQ_SISO; + tbl->action = 0; + tbl->max_search = IWL_MAX_SEARCH; + rate_mask = lq_sta->active_siso_rate; + + if (iwl_is_ht40_tx_allowed(priv, ctx, sta)) + tbl->is_ht40 = 1; + else + tbl->is_ht40 = 0; + + if (is_green) + tbl->is_SGI = 0; /*11n spec: no SGI in SISO+Greenfield*/ + + rs_set_expected_tpt_table(lq_sta, tbl); + rate = rs_get_best_rate(priv, lq_sta, tbl, rate_mask, index); + + IWL_DEBUG_RATE(priv, "LQ: get best rate %d mask %X\n", rate, rate_mask); + if ((rate == IWL_RATE_INVALID) || !((1 << rate) & rate_mask)) { + IWL_DEBUG_RATE(priv, "can not switch with index %d rate mask %x\n", + rate, rate_mask); + return -1; + } + tbl->current_rate = rate_n_flags_from_tbl(priv, tbl, rate, is_green); + IWL_DEBUG_RATE(priv, "LQ: Switch to new mcs %X index is green %X\n", + tbl->current_rate, is_green); + return 0; +} + +/* + * Try to switch to new modulation mode from legacy + */ +static int rs_move_legacy_other(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, + int index) +{ + struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + struct iwl_scale_tbl_info *search_tbl = + &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + struct iwl_rate_scale_data *window = &(tbl->win[index]); + u32 sz = (sizeof(struct iwl_scale_tbl_info) - + (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT)); + u8 start_action; + u8 valid_tx_ant = priv->nvm_data->valid_tx_ant; + u8 tx_chains_num = priv->hw_params.tx_chains_num; + int ret = 0; + u8 update_search_tbl_counter = 0; + + switch (priv->bt_traffic_load) { + case IWL_BT_COEX_TRAFFIC_LOAD_NONE: + /* nothing */ + break; + case IWL_BT_COEX_TRAFFIC_LOAD_LOW: + /* avoid antenna B unless MIMO */ + if (tbl->action == IWL_LEGACY_SWITCH_ANTENNA2) + tbl->action = IWL_LEGACY_SWITCH_SISO; + break; + case IWL_BT_COEX_TRAFFIC_LOAD_HIGH: + case IWL_BT_COEX_TRAFFIC_LOAD_CONTINUOUS: + /* avoid antenna B and MIMO */ + valid_tx_ant = + first_antenna(priv->nvm_data->valid_tx_ant); + if (tbl->action >= IWL_LEGACY_SWITCH_ANTENNA2 && + tbl->action != IWL_LEGACY_SWITCH_SISO) + tbl->action = IWL_LEGACY_SWITCH_SISO; + break; + default: + IWL_ERR(priv, "Invalid BT load %d", priv->bt_traffic_load); + break; + } + + if (!iwl_ht_enabled(priv)) + /* stay in Legacy */ + tbl->action = IWL_LEGACY_SWITCH_ANTENNA1; + else if (iwl_tx_ant_restriction(priv) == IWL_ANT_OK_SINGLE && + tbl->action > IWL_LEGACY_SWITCH_SISO) + tbl->action = IWL_LEGACY_SWITCH_SISO; + + /* configure as 1x1 if bt full concurrency */ + if (priv->bt_full_concurrent) { + if (!iwl_ht_enabled(priv)) + tbl->action = IWL_LEGACY_SWITCH_ANTENNA1; + else if (tbl->action >= IWL_LEGACY_SWITCH_ANTENNA2) + tbl->action = IWL_LEGACY_SWITCH_SISO; + valid_tx_ant = + first_antenna(priv->nvm_data->valid_tx_ant); + } + + start_action = tbl->action; + for (; ;) { + lq_sta->action_counter++; + switch (tbl->action) { + case IWL_LEGACY_SWITCH_ANTENNA1: + case IWL_LEGACY_SWITCH_ANTENNA2: + IWL_DEBUG_RATE(priv, "LQ: Legacy toggle Antenna\n"); + + if ((tbl->action == IWL_LEGACY_SWITCH_ANTENNA1 && + tx_chains_num <= 1) || + (tbl->action == IWL_LEGACY_SWITCH_ANTENNA2 && + tx_chains_num <= 2)) + break; + + /* Don't change antenna if success has been great */ + if (window->success_ratio >= IWL_RS_GOOD_RATIO && + !priv->bt_full_concurrent && + priv->bt_traffic_load == + IWL_BT_COEX_TRAFFIC_LOAD_NONE) + break; + + /* Set up search table to try other antenna */ + memcpy(search_tbl, tbl, sz); + + if (rs_toggle_antenna(valid_tx_ant, + &search_tbl->current_rate, search_tbl)) { + update_search_tbl_counter = 1; + rs_set_expected_tpt_table(lq_sta, search_tbl); + goto out; + } + break; + case IWL_LEGACY_SWITCH_SISO: + IWL_DEBUG_RATE(priv, "LQ: Legacy switch to SISO\n"); + + /* Set up search table to try SISO */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + ret = rs_switch_to_siso(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) { + lq_sta->action_counter = 0; + goto out; + } + + break; + case IWL_LEGACY_SWITCH_MIMO2_AB: + case IWL_LEGACY_SWITCH_MIMO2_AC: + case IWL_LEGACY_SWITCH_MIMO2_BC: + IWL_DEBUG_RATE(priv, "LQ: Legacy switch to MIMO2\n"); + + /* Set up search table to try MIMO */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + + if (tbl->action == IWL_LEGACY_SWITCH_MIMO2_AB) + search_tbl->ant_type = ANT_AB; + else if (tbl->action == IWL_LEGACY_SWITCH_MIMO2_AC) + search_tbl->ant_type = ANT_AC; + else + search_tbl->ant_type = ANT_BC; + + if (!rs_is_valid_ant(valid_tx_ant, search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo2(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) { + lq_sta->action_counter = 0; + goto out; + } + break; + + case IWL_LEGACY_SWITCH_MIMO3_ABC: + IWL_DEBUG_RATE(priv, "LQ: Legacy switch to MIMO3\n"); + + /* Set up search table to try MIMO3 */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + + search_tbl->ant_type = ANT_ABC; + + if (!rs_is_valid_ant(valid_tx_ant, search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo3(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) { + lq_sta->action_counter = 0; + goto out; + } + break; + } + tbl->action++; + if (tbl->action > IWL_LEGACY_SWITCH_MIMO3_ABC) + tbl->action = IWL_LEGACY_SWITCH_ANTENNA1; + + if (tbl->action == start_action) + break; + + } + search_tbl->lq_type = LQ_NONE; + return 0; + +out: + lq_sta->search_better_tbl = 1; + tbl->action++; + if (tbl->action > IWL_LEGACY_SWITCH_MIMO3_ABC) + tbl->action = IWL_LEGACY_SWITCH_ANTENNA1; + if (update_search_tbl_counter) + search_tbl->action = tbl->action; + return 0; + +} + +/* + * Try to switch to new modulation mode from SISO + */ +static int rs_move_siso_to_other(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, int index) +{ + u8 is_green = lq_sta->is_green; + struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + struct iwl_scale_tbl_info *search_tbl = + &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + struct iwl_rate_scale_data *window = &(tbl->win[index]); + struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + u32 sz = (sizeof(struct iwl_scale_tbl_info) - + (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT)); + u8 start_action; + u8 valid_tx_ant = priv->nvm_data->valid_tx_ant; + u8 tx_chains_num = priv->hw_params.tx_chains_num; + u8 update_search_tbl_counter = 0; + int ret; + + switch (priv->bt_traffic_load) { + case IWL_BT_COEX_TRAFFIC_LOAD_NONE: + /* nothing */ + break; + case IWL_BT_COEX_TRAFFIC_LOAD_LOW: + /* avoid antenna B unless MIMO */ + if (tbl->action == IWL_SISO_SWITCH_ANTENNA2) + tbl->action = IWL_SISO_SWITCH_MIMO2_AB; + break; + case IWL_BT_COEX_TRAFFIC_LOAD_HIGH: + case IWL_BT_COEX_TRAFFIC_LOAD_CONTINUOUS: + /* avoid antenna B and MIMO */ + valid_tx_ant = + first_antenna(priv->nvm_data->valid_tx_ant); + if (tbl->action != IWL_SISO_SWITCH_ANTENNA1) + tbl->action = IWL_SISO_SWITCH_ANTENNA1; + break; + default: + IWL_ERR(priv, "Invalid BT load %d", priv->bt_traffic_load); + break; + } + + if (iwl_tx_ant_restriction(priv) == IWL_ANT_OK_SINGLE && + tbl->action > IWL_SISO_SWITCH_ANTENNA2) { + /* stay in SISO */ + tbl->action = IWL_SISO_SWITCH_ANTENNA1; + } + + /* configure as 1x1 if bt full concurrency */ + if (priv->bt_full_concurrent) { + valid_tx_ant = + first_antenna(priv->nvm_data->valid_tx_ant); + if (tbl->action >= IWL_LEGACY_SWITCH_ANTENNA2) + tbl->action = IWL_SISO_SWITCH_ANTENNA1; + } + + start_action = tbl->action; + for (;;) { + lq_sta->action_counter++; + switch (tbl->action) { + case IWL_SISO_SWITCH_ANTENNA1: + case IWL_SISO_SWITCH_ANTENNA2: + IWL_DEBUG_RATE(priv, "LQ: SISO toggle Antenna\n"); + if ((tbl->action == IWL_SISO_SWITCH_ANTENNA1 && + tx_chains_num <= 1) || + (tbl->action == IWL_SISO_SWITCH_ANTENNA2 && + tx_chains_num <= 2)) + break; + + if (window->success_ratio >= IWL_RS_GOOD_RATIO && + !priv->bt_full_concurrent && + priv->bt_traffic_load == + IWL_BT_COEX_TRAFFIC_LOAD_NONE) + break; + + memcpy(search_tbl, tbl, sz); + if (rs_toggle_antenna(valid_tx_ant, + &search_tbl->current_rate, search_tbl)) { + update_search_tbl_counter = 1; + goto out; + } + break; + case IWL_SISO_SWITCH_MIMO2_AB: + case IWL_SISO_SWITCH_MIMO2_AC: + case IWL_SISO_SWITCH_MIMO2_BC: + IWL_DEBUG_RATE(priv, "LQ: SISO switch to MIMO2\n"); + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + + if (tbl->action == IWL_SISO_SWITCH_MIMO2_AB) + search_tbl->ant_type = ANT_AB; + else if (tbl->action == IWL_SISO_SWITCH_MIMO2_AC) + search_tbl->ant_type = ANT_AC; + else + search_tbl->ant_type = ANT_BC; + + if (!rs_is_valid_ant(valid_tx_ant, search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo2(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) + goto out; + break; + case IWL_SISO_SWITCH_GI: + if (!tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_20)) + break; + if (tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_40)) + break; + + IWL_DEBUG_RATE(priv, "LQ: SISO toggle SGI/NGI\n"); + + memcpy(search_tbl, tbl, sz); + if (is_green) { + if (!tbl->is_SGI) + break; + else + IWL_ERR(priv, + "SGI was set in GF+SISO\n"); + } + search_tbl->is_SGI = !tbl->is_SGI; + rs_set_expected_tpt_table(lq_sta, search_tbl); + if (tbl->is_SGI) { + s32 tpt = lq_sta->last_tpt / 100; + if (tpt >= search_tbl->expected_tpt[index]) + break; + } + search_tbl->current_rate = + rate_n_flags_from_tbl(priv, search_tbl, + index, is_green); + update_search_tbl_counter = 1; + goto out; + case IWL_SISO_SWITCH_MIMO3_ABC: + IWL_DEBUG_RATE(priv, "LQ: SISO switch to MIMO3\n"); + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + search_tbl->ant_type = ANT_ABC; + + if (!rs_is_valid_ant(valid_tx_ant, search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo3(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) + goto out; + break; + } + tbl->action++; + if (tbl->action > IWL_LEGACY_SWITCH_MIMO3_ABC) + tbl->action = IWL_SISO_SWITCH_ANTENNA1; + + if (tbl->action == start_action) + break; + } + search_tbl->lq_type = LQ_NONE; + return 0; + + out: + lq_sta->search_better_tbl = 1; + tbl->action++; + if (tbl->action > IWL_SISO_SWITCH_MIMO3_ABC) + tbl->action = IWL_SISO_SWITCH_ANTENNA1; + if (update_search_tbl_counter) + search_tbl->action = tbl->action; + + return 0; +} + +/* + * Try to switch to new modulation mode from MIMO2 + */ +static int rs_move_mimo2_to_other(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, int index) +{ + s8 is_green = lq_sta->is_green; + struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + struct iwl_scale_tbl_info *search_tbl = + &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + struct iwl_rate_scale_data *window = &(tbl->win[index]); + struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + u32 sz = (sizeof(struct iwl_scale_tbl_info) - + (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT)); + u8 start_action; + u8 valid_tx_ant = priv->nvm_data->valid_tx_ant; + u8 tx_chains_num = priv->hw_params.tx_chains_num; + u8 update_search_tbl_counter = 0; + int ret; + + switch (priv->bt_traffic_load) { + case IWL_BT_COEX_TRAFFIC_LOAD_NONE: + /* nothing */ + break; + case IWL_BT_COEX_TRAFFIC_LOAD_HIGH: + case IWL_BT_COEX_TRAFFIC_LOAD_CONTINUOUS: + /* avoid antenna B and MIMO */ + if (tbl->action != IWL_MIMO2_SWITCH_SISO_A) + tbl->action = IWL_MIMO2_SWITCH_SISO_A; + break; + case IWL_BT_COEX_TRAFFIC_LOAD_LOW: + /* avoid antenna B unless MIMO */ + if (tbl->action == IWL_MIMO2_SWITCH_SISO_B || + tbl->action == IWL_MIMO2_SWITCH_SISO_C) + tbl->action = IWL_MIMO2_SWITCH_SISO_A; + break; + default: + IWL_ERR(priv, "Invalid BT load %d", priv->bt_traffic_load); + break; + } + + if ((iwl_tx_ant_restriction(priv) == IWL_ANT_OK_SINGLE) && + (tbl->action < IWL_MIMO2_SWITCH_SISO_A || + tbl->action > IWL_MIMO2_SWITCH_SISO_C)) { + /* switch in SISO */ + tbl->action = IWL_MIMO2_SWITCH_SISO_A; + } + + /* configure as 1x1 if bt full concurrency */ + if (priv->bt_full_concurrent && + (tbl->action < IWL_MIMO2_SWITCH_SISO_A || + tbl->action > IWL_MIMO2_SWITCH_SISO_C)) + tbl->action = IWL_MIMO2_SWITCH_SISO_A; + + start_action = tbl->action; + for (;;) { + lq_sta->action_counter++; + switch (tbl->action) { + case IWL_MIMO2_SWITCH_ANTENNA1: + case IWL_MIMO2_SWITCH_ANTENNA2: + IWL_DEBUG_RATE(priv, "LQ: MIMO2 toggle Antennas\n"); + + if (tx_chains_num <= 2) + break; + + if (window->success_ratio >= IWL_RS_GOOD_RATIO) + break; + + memcpy(search_tbl, tbl, sz); + if (rs_toggle_antenna(valid_tx_ant, + &search_tbl->current_rate, search_tbl)) { + update_search_tbl_counter = 1; + goto out; + } + break; + case IWL_MIMO2_SWITCH_SISO_A: + case IWL_MIMO2_SWITCH_SISO_B: + case IWL_MIMO2_SWITCH_SISO_C: + IWL_DEBUG_RATE(priv, "LQ: MIMO2 switch to SISO\n"); + + /* Set up new search table for SISO */ + memcpy(search_tbl, tbl, sz); + + if (tbl->action == IWL_MIMO2_SWITCH_SISO_A) + search_tbl->ant_type = ANT_A; + else if (tbl->action == IWL_MIMO2_SWITCH_SISO_B) + search_tbl->ant_type = ANT_B; + else + search_tbl->ant_type = ANT_C; + + if (!rs_is_valid_ant(valid_tx_ant, search_tbl->ant_type)) + break; + + ret = rs_switch_to_siso(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) + goto out; + + break; + + case IWL_MIMO2_SWITCH_GI: + if (!tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_20)) + break; + if (tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_40)) + break; + + IWL_DEBUG_RATE(priv, "LQ: MIMO2 toggle SGI/NGI\n"); + + /* Set up new search table for MIMO2 */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = !tbl->is_SGI; + rs_set_expected_tpt_table(lq_sta, search_tbl); + /* + * If active table already uses the fastest possible + * modulation (dual stream with short guard interval), + * and it's working well, there's no need to look + * for a better type of modulation! + */ + if (tbl->is_SGI) { + s32 tpt = lq_sta->last_tpt / 100; + if (tpt >= search_tbl->expected_tpt[index]) + break; + } + search_tbl->current_rate = + rate_n_flags_from_tbl(priv, search_tbl, + index, is_green); + update_search_tbl_counter = 1; + goto out; + + case IWL_MIMO2_SWITCH_MIMO3_ABC: + IWL_DEBUG_RATE(priv, "LQ: MIMO2 switch to MIMO3\n"); + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + search_tbl->ant_type = ANT_ABC; + + if (!rs_is_valid_ant(valid_tx_ant, search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo3(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) + goto out; + + break; + } + tbl->action++; + if (tbl->action > IWL_MIMO2_SWITCH_MIMO3_ABC) + tbl->action = IWL_MIMO2_SWITCH_ANTENNA1; + + if (tbl->action == start_action) + break; + } + search_tbl->lq_type = LQ_NONE; + return 0; + out: + lq_sta->search_better_tbl = 1; + tbl->action++; + if (tbl->action > IWL_MIMO2_SWITCH_MIMO3_ABC) + tbl->action = IWL_MIMO2_SWITCH_ANTENNA1; + if (update_search_tbl_counter) + search_tbl->action = tbl->action; + + return 0; + +} + +/* + * Try to switch to new modulation mode from MIMO3 + */ +static int rs_move_mimo3_to_other(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, + struct ieee80211_conf *conf, + struct ieee80211_sta *sta, int index) +{ + s8 is_green = lq_sta->is_green; + struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + struct iwl_scale_tbl_info *search_tbl = + &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + struct iwl_rate_scale_data *window = &(tbl->win[index]); + struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + u32 sz = (sizeof(struct iwl_scale_tbl_info) - + (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT)); + u8 start_action; + u8 valid_tx_ant = priv->nvm_data->valid_tx_ant; + u8 tx_chains_num = priv->hw_params.tx_chains_num; + int ret; + u8 update_search_tbl_counter = 0; + + switch (priv->bt_traffic_load) { + case IWL_BT_COEX_TRAFFIC_LOAD_NONE: + /* nothing */ + break; + case IWL_BT_COEX_TRAFFIC_LOAD_HIGH: + case IWL_BT_COEX_TRAFFIC_LOAD_CONTINUOUS: + /* avoid antenna B and MIMO */ + if (tbl->action != IWL_MIMO3_SWITCH_SISO_A) + tbl->action = IWL_MIMO3_SWITCH_SISO_A; + break; + case IWL_BT_COEX_TRAFFIC_LOAD_LOW: + /* avoid antenna B unless MIMO */ + if (tbl->action == IWL_MIMO3_SWITCH_SISO_B || + tbl->action == IWL_MIMO3_SWITCH_SISO_C) + tbl->action = IWL_MIMO3_SWITCH_SISO_A; + break; + default: + IWL_ERR(priv, "Invalid BT load %d", priv->bt_traffic_load); + break; + } + + if ((iwl_tx_ant_restriction(priv) == IWL_ANT_OK_SINGLE) && + (tbl->action < IWL_MIMO3_SWITCH_SISO_A || + tbl->action > IWL_MIMO3_SWITCH_SISO_C)) { + /* switch in SISO */ + tbl->action = IWL_MIMO3_SWITCH_SISO_A; + } + + /* configure as 1x1 if bt full concurrency */ + if (priv->bt_full_concurrent && + (tbl->action < IWL_MIMO3_SWITCH_SISO_A || + tbl->action > IWL_MIMO3_SWITCH_SISO_C)) + tbl->action = IWL_MIMO3_SWITCH_SISO_A; + + start_action = tbl->action; + for (;;) { + lq_sta->action_counter++; + switch (tbl->action) { + case IWL_MIMO3_SWITCH_ANTENNA1: + case IWL_MIMO3_SWITCH_ANTENNA2: + IWL_DEBUG_RATE(priv, "LQ: MIMO3 toggle Antennas\n"); + + if (tx_chains_num <= 3) + break; + + if (window->success_ratio >= IWL_RS_GOOD_RATIO) + break; + + memcpy(search_tbl, tbl, sz); + if (rs_toggle_antenna(valid_tx_ant, + &search_tbl->current_rate, search_tbl)) + goto out; + break; + case IWL_MIMO3_SWITCH_SISO_A: + case IWL_MIMO3_SWITCH_SISO_B: + case IWL_MIMO3_SWITCH_SISO_C: + IWL_DEBUG_RATE(priv, "LQ: MIMO3 switch to SISO\n"); + + /* Set up new search table for SISO */ + memcpy(search_tbl, tbl, sz); + + if (tbl->action == IWL_MIMO3_SWITCH_SISO_A) + search_tbl->ant_type = ANT_A; + else if (tbl->action == IWL_MIMO3_SWITCH_SISO_B) + search_tbl->ant_type = ANT_B; + else + search_tbl->ant_type = ANT_C; + + if (!rs_is_valid_ant(valid_tx_ant, search_tbl->ant_type)) + break; + + ret = rs_switch_to_siso(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) + goto out; + + break; + + case IWL_MIMO3_SWITCH_MIMO2_AB: + case IWL_MIMO3_SWITCH_MIMO2_AC: + case IWL_MIMO3_SWITCH_MIMO2_BC: + IWL_DEBUG_RATE(priv, "LQ: MIMO3 switch to MIMO2\n"); + + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + if (tbl->action == IWL_MIMO3_SWITCH_MIMO2_AB) + search_tbl->ant_type = ANT_AB; + else if (tbl->action == IWL_MIMO3_SWITCH_MIMO2_AC) + search_tbl->ant_type = ANT_AC; + else + search_tbl->ant_type = ANT_BC; + + if (!rs_is_valid_ant(valid_tx_ant, search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo2(priv, lq_sta, conf, sta, + search_tbl, index); + if (!ret) + goto out; + + break; + + case IWL_MIMO3_SWITCH_GI: + if (!tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_20)) + break; + if (tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_40)) + break; + + IWL_DEBUG_RATE(priv, "LQ: MIMO3 toggle SGI/NGI\n"); + + /* Set up new search table for MIMO */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = !tbl->is_SGI; + rs_set_expected_tpt_table(lq_sta, search_tbl); + /* + * If active table already uses the fastest possible + * modulation (dual stream with short guard interval), + * and it's working well, there's no need to look + * for a better type of modulation! + */ + if (tbl->is_SGI) { + s32 tpt = lq_sta->last_tpt / 100; + if (tpt >= search_tbl->expected_tpt[index]) + break; + } + search_tbl->current_rate = + rate_n_flags_from_tbl(priv, search_tbl, + index, is_green); + update_search_tbl_counter = 1; + goto out; + } + tbl->action++; + if (tbl->action > IWL_MIMO3_SWITCH_GI) + tbl->action = IWL_MIMO3_SWITCH_ANTENNA1; + + if (tbl->action == start_action) + break; + } + search_tbl->lq_type = LQ_NONE; + return 0; + out: + lq_sta->search_better_tbl = 1; + tbl->action++; + if (tbl->action > IWL_MIMO3_SWITCH_GI) + tbl->action = IWL_MIMO3_SWITCH_ANTENNA1; + if (update_search_tbl_counter) + search_tbl->action = tbl->action; + + return 0; + +} + +/* + * Check whether we should continue using same modulation mode, or + * begin search for a new mode, based on: + * 1) # tx successes or failures while using this mode + * 2) # times calling this function + * 3) elapsed time in this mode (not used, for now) + */ +static void rs_stay_in_table(struct iwl_lq_sta *lq_sta, bool force_search) +{ + struct iwl_scale_tbl_info *tbl; + int i; + int active_tbl; + int flush_interval_passed = 0; + struct iwl_priv *priv; + + priv = lq_sta->drv; + active_tbl = lq_sta->active_tbl; + + tbl = &(lq_sta->lq_info[active_tbl]); + + /* If we've been disallowing search, see if we should now allow it */ + if (lq_sta->stay_in_tbl) { + + /* Elapsed time using current modulation mode */ + if (lq_sta->flush_timer) + flush_interval_passed = + time_after(jiffies, + (unsigned long)(lq_sta->flush_timer + + IWL_RATE_SCALE_FLUSH_INTVL)); + + /* + * Check if we should allow search for new modulation mode. + * If many frames have failed or succeeded, or we've used + * this same modulation for a long time, allow search, and + * reset history stats that keep track of whether we should + * allow a new search. Also (below) reset all bitmaps and + * stats in active history. + */ + if (force_search || + (lq_sta->total_failed > lq_sta->max_failure_limit) || + (lq_sta->total_success > lq_sta->max_success_limit) || + ((!lq_sta->search_better_tbl) && (lq_sta->flush_timer) + && (flush_interval_passed))) { + IWL_DEBUG_RATE(priv, "LQ: stay is expired %d %d %d\n", + lq_sta->total_failed, + lq_sta->total_success, + flush_interval_passed); + + /* Allow search for new mode */ + lq_sta->stay_in_tbl = 0; /* only place reset */ + lq_sta->total_failed = 0; + lq_sta->total_success = 0; + lq_sta->flush_timer = 0; + + /* + * Else if we've used this modulation mode enough repetitions + * (regardless of elapsed time or success/failure), reset + * history bitmaps and rate-specific stats for all rates in + * active table. + */ + } else { + lq_sta->table_count++; + if (lq_sta->table_count >= + lq_sta->table_count_limit) { + lq_sta->table_count = 0; + + IWL_DEBUG_RATE(priv, "LQ: stay in table clear win\n"); + for (i = 0; i < IWL_RATE_COUNT; i++) + rs_rate_scale_clear_window( + &(tbl->win[i])); + } + } + + /* If transitioning to allow "search", reset all history + * bitmaps and stats in active table (this will become the new + * "search" table). */ + if (!lq_sta->stay_in_tbl) { + for (i = 0; i < IWL_RATE_COUNT; i++) + rs_rate_scale_clear_window(&(tbl->win[i])); + } + } +} + +/* + * setup rate table in uCode + */ +static void rs_update_rate_tbl(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl, + int index, u8 is_green) +{ + u32 rate; + + /* Update uCode's rate table. */ + rate = rate_n_flags_from_tbl(priv, tbl, index, is_green); + rs_fill_link_cmd(priv, lq_sta, rate); + iwl_send_lq_cmd(priv, ctx, &lq_sta->lq, CMD_ASYNC, false); +} + +/* + * Do rate scaling and search for new modulation mode. + */ +static void rs_rate_scale_perform(struct iwl_priv *priv, + struct sk_buff *skb, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta) +{ + struct ieee80211_hw *hw = priv->hw; + struct ieee80211_conf *conf = &hw->conf; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + int low = IWL_RATE_INVALID; + int high = IWL_RATE_INVALID; + int index; + int i; + struct iwl_rate_scale_data *window = NULL; + int current_tpt = IWL_INVALID_VALUE; + int low_tpt = IWL_INVALID_VALUE; + int high_tpt = IWL_INVALID_VALUE; + u32 fail_count; + s8 scale_action = 0; + u16 rate_mask; + u8 update_lq = 0; + struct iwl_scale_tbl_info *tbl, *tbl1; + u16 rate_scale_index_msk = 0; + u8 is_green = 0; + u8 active_tbl = 0; + u8 done_search = 0; + u16 high_low; + s32 sr; + u8 tid = IWL_MAX_TID_COUNT; + struct iwl_tid_data *tid_data; + struct iwl_station_priv *sta_priv = (void *)sta->drv_priv; + struct iwl_rxon_context *ctx = sta_priv->ctx; + + IWL_DEBUG_RATE(priv, "rate scale calculate new rate for skb\n"); + + /* Send management frames and NO_ACK data using lowest rate. */ + /* TODO: this could probably be improved.. */ + if (!ieee80211_is_data(hdr->frame_control) || + info->flags & IEEE80211_TX_CTL_NO_ACK) + return; + + lq_sta->supp_rates = sta->supp_rates[lq_sta->band]; + + tid = rs_tl_add_packet(lq_sta, hdr); + if ((tid != IWL_MAX_TID_COUNT) && + (lq_sta->tx_agg_tid_en & (1 << tid))) { + tid_data = &priv->tid_data[lq_sta->lq.sta_id][tid]; + if (tid_data->agg.state == IWL_AGG_OFF) + lq_sta->is_agg = 0; + else + lq_sta->is_agg = 1; + } else + lq_sta->is_agg = 0; + + /* + * Select rate-scale / modulation-mode table to work with in + * the rest of this function: "search" if searching for better + * modulation mode, or "active" if doing rate scaling within a mode. + */ + if (!lq_sta->search_better_tbl) + active_tbl = lq_sta->active_tbl; + else + active_tbl = 1 - lq_sta->active_tbl; + + tbl = &(lq_sta->lq_info[active_tbl]); + if (is_legacy(tbl->lq_type)) + lq_sta->is_green = 0; + else + lq_sta->is_green = rs_use_green(sta); + is_green = lq_sta->is_green; + + /* current tx rate */ + index = lq_sta->last_txrate_idx; + + IWL_DEBUG_RATE(priv, "Rate scale index %d for type %d\n", index, + tbl->lq_type); + + /* rates available for this association, and for modulation mode */ + rate_mask = rs_get_supported_rates(lq_sta, hdr, tbl->lq_type); + + IWL_DEBUG_RATE(priv, "mask 0x%04X\n", rate_mask); + + /* mask with station rate restriction */ + if (is_legacy(tbl->lq_type)) { + if (lq_sta->band == IEEE80211_BAND_5GHZ) + /* supp_rates has no CCK bits in A mode */ + rate_scale_index_msk = (u16) (rate_mask & + (lq_sta->supp_rates << IWL_FIRST_OFDM_RATE)); + else + rate_scale_index_msk = (u16) (rate_mask & + lq_sta->supp_rates); + + } else + rate_scale_index_msk = rate_mask; + + if (!rate_scale_index_msk) + rate_scale_index_msk = rate_mask; + + if (!((1 << index) & rate_scale_index_msk)) { + IWL_ERR(priv, "Current Rate is not valid\n"); + if (lq_sta->search_better_tbl) { + /* revert to active table if search table is not valid*/ + tbl->lq_type = LQ_NONE; + lq_sta->search_better_tbl = 0; + tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + /* get "active" rate info */ + index = iwl_hwrate_to_plcp_idx(tbl->current_rate); + rs_update_rate_tbl(priv, ctx, lq_sta, tbl, + index, is_green); + } + return; + } + + /* Get expected throughput table and history window for current rate */ + if (!tbl->expected_tpt) { + IWL_ERR(priv, "tbl->expected_tpt is NULL\n"); + return; + } + + /* force user max rate if set by user */ + if ((lq_sta->max_rate_idx != -1) && + (lq_sta->max_rate_idx < index)) { + index = lq_sta->max_rate_idx; + update_lq = 1; + window = &(tbl->win[index]); + goto lq_update; + } + + window = &(tbl->win[index]); + + /* + * If there is not enough history to calculate actual average + * throughput, keep analyzing results of more tx frames, without + * changing rate or mode (bypass most of the rest of this function). + * Set up new rate table in uCode only if old rate is not supported + * in current association (use new rate found above). + */ + fail_count = window->counter - window->success_counter; + if ((fail_count < IWL_RATE_MIN_FAILURE_TH) && + (window->success_counter < IWL_RATE_MIN_SUCCESS_TH)) { + IWL_DEBUG_RATE(priv, "LQ: still below TH. succ=%d total=%d " + "for index %d\n", + window->success_counter, window->counter, index); + + /* Can't calculate this yet; not enough history */ + window->average_tpt = IWL_INVALID_VALUE; + + /* Should we stay with this modulation mode, + * or search for a new one? */ + rs_stay_in_table(lq_sta, false); + + goto out; + } + /* Else we have enough samples; calculate estimate of + * actual average throughput */ + if (window->average_tpt != ((window->success_ratio * + tbl->expected_tpt[index] + 64) / 128)) { + IWL_ERR(priv, "expected_tpt should have been calculated by now\n"); + window->average_tpt = ((window->success_ratio * + tbl->expected_tpt[index] + 64) / 128); + } + + /* If we are searching for better modulation mode, check success. */ + if (lq_sta->search_better_tbl && + (iwl_tx_ant_restriction(priv) == IWL_ANT_OK_MULTI)) { + /* If good success, continue using the "search" mode; + * no need to send new link quality command, since we're + * continuing to use the setup that we've been trying. */ + if (window->average_tpt > lq_sta->last_tpt) { + + IWL_DEBUG_RATE(priv, "LQ: SWITCHING TO NEW TABLE " + "suc=%d cur-tpt=%d old-tpt=%d\n", + window->success_ratio, + window->average_tpt, + lq_sta->last_tpt); + + if (!is_legacy(tbl->lq_type)) + lq_sta->enable_counter = 1; + + /* Swap tables; "search" becomes "active" */ + lq_sta->active_tbl = active_tbl; + current_tpt = window->average_tpt; + + /* Else poor success; go back to mode in "active" table */ + } else { + + IWL_DEBUG_RATE(priv, "LQ: GOING BACK TO THE OLD TABLE " + "suc=%d cur-tpt=%d old-tpt=%d\n", + window->success_ratio, + window->average_tpt, + lq_sta->last_tpt); + + /* Nullify "search" table */ + tbl->lq_type = LQ_NONE; + + /* Revert to "active" table */ + active_tbl = lq_sta->active_tbl; + tbl = &(lq_sta->lq_info[active_tbl]); + + /* Revert to "active" rate and throughput info */ + index = iwl_hwrate_to_plcp_idx(tbl->current_rate); + current_tpt = lq_sta->last_tpt; + + /* Need to set up a new rate table in uCode */ + update_lq = 1; + } + + /* Either way, we've made a decision; modulation mode + * search is done, allow rate adjustment next time. */ + lq_sta->search_better_tbl = 0; + done_search = 1; /* Don't switch modes below! */ + goto lq_update; + } + + /* (Else) not in search of better modulation mode, try for better + * starting rate, while staying in this mode. */ + high_low = rs_get_adjacent_rate(priv, index, rate_scale_index_msk, + tbl->lq_type); + low = high_low & 0xff; + high = (high_low >> 8) & 0xff; + + /* If user set max rate, dont allow higher than user constrain */ + if ((lq_sta->max_rate_idx != -1) && + (lq_sta->max_rate_idx < high)) + high = IWL_RATE_INVALID; + + sr = window->success_ratio; + + /* Collect measured throughputs for current and adjacent rates */ + current_tpt = window->average_tpt; + if (low != IWL_RATE_INVALID) + low_tpt = tbl->win[low].average_tpt; + if (high != IWL_RATE_INVALID) + high_tpt = tbl->win[high].average_tpt; + + scale_action = 0; + + /* Too many failures, decrease rate */ + if ((sr <= IWL_RATE_DECREASE_TH) || (current_tpt == 0)) { + IWL_DEBUG_RATE(priv, "decrease rate because of low success_ratio\n"); + scale_action = -1; + + /* No throughput measured yet for adjacent rates; try increase. */ + } else if ((low_tpt == IWL_INVALID_VALUE) && + (high_tpt == IWL_INVALID_VALUE)) { + + if (high != IWL_RATE_INVALID && sr >= IWL_RATE_INCREASE_TH) + scale_action = 1; + else if (low != IWL_RATE_INVALID) + scale_action = 0; + } + + /* Both adjacent throughputs are measured, but neither one has better + * throughput; we're using the best rate, don't change it! */ + else if ((low_tpt != IWL_INVALID_VALUE) && + (high_tpt != IWL_INVALID_VALUE) && + (low_tpt < current_tpt) && + (high_tpt < current_tpt)) + scale_action = 0; + + /* At least one adjacent rate's throughput is measured, + * and may have better performance. */ + else { + /* Higher adjacent rate's throughput is measured */ + if (high_tpt != IWL_INVALID_VALUE) { + /* Higher rate has better throughput */ + if (high_tpt > current_tpt && + sr >= IWL_RATE_INCREASE_TH) { + scale_action = 1; + } else { + scale_action = 0; + } + + /* Lower adjacent rate's throughput is measured */ + } else if (low_tpt != IWL_INVALID_VALUE) { + /* Lower rate has better throughput */ + if (low_tpt > current_tpt) { + IWL_DEBUG_RATE(priv, + "decrease rate because of low tpt\n"); + scale_action = -1; + } else if (sr >= IWL_RATE_INCREASE_TH) { + scale_action = 1; + } + } + } + + /* Sanity check; asked for decrease, but success rate or throughput + * has been good at old rate. Don't change it. */ + if ((scale_action == -1) && (low != IWL_RATE_INVALID) && + ((sr > IWL_RATE_HIGH_TH) || + (current_tpt > (100 * tbl->expected_tpt[low])))) + scale_action = 0; + if (!iwl_ht_enabled(priv) && !is_legacy(tbl->lq_type)) + scale_action = -1; + if (iwl_tx_ant_restriction(priv) != IWL_ANT_OK_MULTI && + (is_mimo2(tbl->lq_type) || is_mimo3(tbl->lq_type))) + scale_action = -1; + + if ((priv->bt_traffic_load >= IWL_BT_COEX_TRAFFIC_LOAD_HIGH) && + (is_mimo2(tbl->lq_type) || is_mimo3(tbl->lq_type))) { + if (lq_sta->last_bt_traffic > priv->bt_traffic_load) { + /* + * don't set scale_action, don't want to scale up if + * the rate scale doesn't otherwise think that is a + * good idea. + */ + } else if (lq_sta->last_bt_traffic <= priv->bt_traffic_load) { + scale_action = -1; + } + } + lq_sta->last_bt_traffic = priv->bt_traffic_load; + + if ((priv->bt_traffic_load >= IWL_BT_COEX_TRAFFIC_LOAD_HIGH) && + (is_mimo2(tbl->lq_type) || is_mimo3(tbl->lq_type))) { + /* search for a new modulation */ + rs_stay_in_table(lq_sta, true); + goto lq_update; + } + + switch (scale_action) { + case -1: + /* Decrease starting rate, update uCode's rate table */ + if (low != IWL_RATE_INVALID) { + update_lq = 1; + index = low; + } + + break; + case 1: + /* Increase starting rate, update uCode's rate table */ + if (high != IWL_RATE_INVALID) { + update_lq = 1; + index = high; + } + + break; + case 0: + /* No change */ + default: + break; + } + + IWL_DEBUG_RATE(priv, "choose rate scale index %d action %d low %d " + "high %d type %d\n", + index, scale_action, low, high, tbl->lq_type); + +lq_update: + /* Replace uCode's rate table for the destination station. */ + if (update_lq) + rs_update_rate_tbl(priv, ctx, lq_sta, tbl, index, is_green); + + if (iwl_tx_ant_restriction(priv) == IWL_ANT_OK_MULTI) { + /* Should we stay with this modulation mode, + * or search for a new one? */ + rs_stay_in_table(lq_sta, false); + } + /* + * Search for new modulation mode if we're: + * 1) Not changing rates right now + * 2) Not just finishing up a search + * 3) Allowing a new search + */ + if (!update_lq && !done_search && !lq_sta->stay_in_tbl && window->counter) { + /* Save current throughput to compare with "search" throughput*/ + lq_sta->last_tpt = current_tpt; + + /* Select a new "search" modulation mode to try. + * If one is found, set up the new "search" table. */ + if (is_legacy(tbl->lq_type)) + rs_move_legacy_other(priv, lq_sta, conf, sta, index); + else if (is_siso(tbl->lq_type)) + rs_move_siso_to_other(priv, lq_sta, conf, sta, index); + else if (is_mimo2(tbl->lq_type)) + rs_move_mimo2_to_other(priv, lq_sta, conf, sta, index); + else + rs_move_mimo3_to_other(priv, lq_sta, conf, sta, index); + + /* If new "search" mode was selected, set up in uCode table */ + if (lq_sta->search_better_tbl) { + /* Access the "search" table, clear its history. */ + tbl = &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + for (i = 0; i < IWL_RATE_COUNT; i++) + rs_rate_scale_clear_window(&(tbl->win[i])); + + /* Use new "search" start rate */ + index = iwl_hwrate_to_plcp_idx(tbl->current_rate); + + IWL_DEBUG_RATE(priv, "Switch current mcs: %X index: %d\n", + tbl->current_rate, index); + rs_fill_link_cmd(priv, lq_sta, tbl->current_rate); + iwl_send_lq_cmd(priv, ctx, &lq_sta->lq, CMD_ASYNC, false); + } else + done_search = 1; + } + + if (done_search && !lq_sta->stay_in_tbl) { + /* If the "active" (non-search) mode was legacy, + * and we've tried switching antennas, + * but we haven't been able to try HT modes (not available), + * stay with best antenna legacy modulation for a while + * before next round of mode comparisons. */ + tbl1 = &(lq_sta->lq_info[lq_sta->active_tbl]); + if (is_legacy(tbl1->lq_type) && !conf_is_ht(conf) && + lq_sta->action_counter > tbl1->max_search) { + IWL_DEBUG_RATE(priv, "LQ: STAY in legacy table\n"); + rs_set_stay_in_table(priv, 1, lq_sta); + } + + /* If we're in an HT mode, and all 3 mode switch actions + * have been tried and compared, stay in this best modulation + * mode for a while before next round of mode comparisons. */ + if (lq_sta->enable_counter && + (lq_sta->action_counter >= tbl1->max_search) && + iwl_ht_enabled(priv)) { + if ((lq_sta->last_tpt > IWL_AGG_TPT_THREHOLD) && + (lq_sta->tx_agg_tid_en & (1 << tid)) && + (tid != IWL_MAX_TID_COUNT)) { + u8 sta_id = lq_sta->lq.sta_id; + tid_data = &priv->tid_data[sta_id][tid]; + if (tid_data->agg.state == IWL_AGG_OFF) { + IWL_DEBUG_RATE(priv, + "try to aggregate tid %d\n", + tid); + rs_tl_turn_on_agg(priv, tid, + lq_sta, sta); + } + } + rs_set_stay_in_table(priv, 0, lq_sta); + } + } + +out: + tbl->current_rate = rate_n_flags_from_tbl(priv, tbl, index, is_green); + lq_sta->last_txrate_idx = index; +} + +/** + * rs_initialize_lq - Initialize a station's hardware rate table + * + * The uCode's station table contains a table of fallback rates + * for automatic fallback during transmission. + * + * NOTE: This sets up a default set of values. These will be replaced later + * if the driver's iwl-agn-rs rate scaling algorithm is used, instead of + * rc80211_simple. + * + * NOTE: Run REPLY_ADD_STA command to set up station table entry, before + * calling this function (which runs REPLY_TX_LINK_QUALITY_CMD, + * which requires station table entry to exist). + */ +static void rs_initialize_lq(struct iwl_priv *priv, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta) +{ + struct iwl_scale_tbl_info *tbl; + int rate_idx; + int i; + u32 rate; + u8 use_green = rs_use_green(sta); + u8 active_tbl = 0; + u8 valid_tx_ant; + struct iwl_station_priv *sta_priv; + struct iwl_rxon_context *ctx; + + if (!sta || !lq_sta) + return; + + sta_priv = (void *)sta->drv_priv; + ctx = sta_priv->ctx; + + i = lq_sta->last_txrate_idx; + + valid_tx_ant = priv->nvm_data->valid_tx_ant; + + if (!lq_sta->search_better_tbl) + active_tbl = lq_sta->active_tbl; + else + active_tbl = 1 - lq_sta->active_tbl; + + tbl = &(lq_sta->lq_info[active_tbl]); + + if ((i < 0) || (i >= IWL_RATE_COUNT)) + i = 0; + + rate = iwl_rates[i].plcp; + tbl->ant_type = first_antenna(valid_tx_ant); + rate |= tbl->ant_type << RATE_MCS_ANT_POS; + + if (i >= IWL_FIRST_CCK_RATE && i <= IWL_LAST_CCK_RATE) + rate |= RATE_MCS_CCK_MSK; + + rs_get_tbl_info_from_mcs(rate, priv->band, tbl, &rate_idx); + if (!rs_is_valid_ant(valid_tx_ant, tbl->ant_type)) + rs_toggle_antenna(valid_tx_ant, &rate, tbl); + + rate = rate_n_flags_from_tbl(priv, tbl, rate_idx, use_green); + tbl->current_rate = rate; + rs_set_expected_tpt_table(lq_sta, tbl); + rs_fill_link_cmd(NULL, lq_sta, rate); + priv->stations[lq_sta->lq.sta_id].lq = &lq_sta->lq; + iwl_send_lq_cmd(priv, ctx, &lq_sta->lq, CMD_SYNC, true); +} + +static void rs_get_rate(void *priv_r, struct ieee80211_sta *sta, void *priv_sta, + struct ieee80211_tx_rate_control *txrc) +{ + + struct sk_buff *skb = txrc->skb; + struct ieee80211_supported_band *sband = txrc->sband; + struct iwl_op_mode *op_mode __maybe_unused = + (struct iwl_op_mode *)priv_r; + struct iwl_priv *priv __maybe_unused = IWL_OP_MODE_GET_DVM(op_mode); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct iwl_lq_sta *lq_sta = priv_sta; + int rate_idx; + + IWL_DEBUG_RATE_LIMIT(priv, "rate scale calculate new rate for skb\n"); + + /* Get max rate if user set max rate */ + if (lq_sta) { + lq_sta->max_rate_idx = txrc->max_rate_idx; + if ((sband->band == IEEE80211_BAND_5GHZ) && + (lq_sta->max_rate_idx != -1)) + lq_sta->max_rate_idx += IWL_FIRST_OFDM_RATE; + if ((lq_sta->max_rate_idx < 0) || + (lq_sta->max_rate_idx >= IWL_RATE_COUNT)) + lq_sta->max_rate_idx = -1; + } + + /* Treat uninitialized rate scaling data same as non-existing. */ + if (lq_sta && !lq_sta->drv) { + IWL_DEBUG_RATE(priv, "Rate scaling not initialized yet.\n"); + priv_sta = NULL; + } + + /* Send management frames and NO_ACK data using lowest rate. */ + if (rate_control_send_low(sta, priv_sta, txrc)) + return; + + rate_idx = lq_sta->last_txrate_idx; + + if (lq_sta->last_rate_n_flags & RATE_MCS_HT_MSK) { + rate_idx -= IWL_FIRST_OFDM_RATE; + /* 6M and 9M shared same MCS index */ + rate_idx = (rate_idx > 0) ? (rate_idx - 1) : 0; + if (rs_extract_rate(lq_sta->last_rate_n_flags) >= + IWL_RATE_MIMO3_6M_PLCP) + rate_idx = rate_idx + (2 * MCS_INDEX_PER_STREAM); + else if (rs_extract_rate(lq_sta->last_rate_n_flags) >= + IWL_RATE_MIMO2_6M_PLCP) + rate_idx = rate_idx + MCS_INDEX_PER_STREAM; + info->control.rates[0].flags = IEEE80211_TX_RC_MCS; + if (lq_sta->last_rate_n_flags & RATE_MCS_SGI_MSK) + info->control.rates[0].flags |= IEEE80211_TX_RC_SHORT_GI; + if (lq_sta->last_rate_n_flags & RATE_MCS_DUP_MSK) + info->control.rates[0].flags |= IEEE80211_TX_RC_DUP_DATA; + if (lq_sta->last_rate_n_flags & RATE_MCS_HT40_MSK) + info->control.rates[0].flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; + if (lq_sta->last_rate_n_flags & RATE_MCS_GF_MSK) + info->control.rates[0].flags |= IEEE80211_TX_RC_GREEN_FIELD; + } else { + /* Check for invalid rates */ + if ((rate_idx < 0) || (rate_idx >= IWL_RATE_COUNT_LEGACY) || + ((sband->band == IEEE80211_BAND_5GHZ) && + (rate_idx < IWL_FIRST_OFDM_RATE))) + rate_idx = rate_lowest_index(sband, sta); + /* On valid 5 GHz rate, adjust index */ + else if (sband->band == IEEE80211_BAND_5GHZ) + rate_idx -= IWL_FIRST_OFDM_RATE; + info->control.rates[0].flags = 0; + } + info->control.rates[0].idx = rate_idx; + info->control.rates[0].count = 1; +} + +static void *rs_alloc_sta(void *priv_rate, struct ieee80211_sta *sta, + gfp_t gfp) +{ + struct iwl_station_priv *sta_priv = (struct iwl_station_priv *) sta->drv_priv; + struct iwl_op_mode *op_mode __maybe_unused = + (struct iwl_op_mode *)priv_rate; + struct iwl_priv *priv __maybe_unused = IWL_OP_MODE_GET_DVM(op_mode); + + IWL_DEBUG_RATE(priv, "create station rate scale window\n"); + + return &sta_priv->lq_sta; +} + +/* + * Called after adding a new station to initialize rate scaling + */ +void iwl_rs_rate_init(struct iwl_priv *priv, struct ieee80211_sta *sta, u8 sta_id) +{ + int i, j; + struct ieee80211_hw *hw = priv->hw; + struct ieee80211_conf *conf = &priv->hw->conf; + struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + struct iwl_station_priv *sta_priv; + struct iwl_lq_sta *lq_sta; + struct ieee80211_supported_band *sband; + unsigned long supp; /* must be unsigned long for for_each_set_bit */ + + sta_priv = (struct iwl_station_priv *) sta->drv_priv; + lq_sta = &sta_priv->lq_sta; + sband = hw->wiphy->bands[conf->chandef.chan->band]; + + + lq_sta->lq.sta_id = sta_id; + + for (j = 0; j < LQ_SIZE; j++) + for (i = 0; i < IWL_RATE_COUNT; i++) + rs_rate_scale_clear_window(&lq_sta->lq_info[j].win[i]); + + lq_sta->flush_timer = 0; + lq_sta->supp_rates = sta->supp_rates[sband->band]; + for (j = 0; j < LQ_SIZE; j++) + for (i = 0; i < IWL_RATE_COUNT; i++) + rs_rate_scale_clear_window(&lq_sta->lq_info[j].win[i]); + + IWL_DEBUG_RATE(priv, "LQ: *** rate scale station global init for station %d ***\n", + sta_id); + /* TODO: what is a good starting rate for STA? About middle? Maybe not + * the lowest or the highest rate.. Could consider using RSSI from + * previous packets? Need to have IEEE 802.1X auth succeed immediately + * after assoc.. */ + + lq_sta->is_dup = 0; + lq_sta->max_rate_idx = -1; + lq_sta->missed_rate_counter = IWL_MISSED_RATE_MAX; + lq_sta->is_green = rs_use_green(sta); + lq_sta->band = sband->band; + /* + * active legacy rates as per supported rates bitmap + */ + supp = sta->supp_rates[sband->band]; + lq_sta->active_legacy_rate = 0; + for_each_set_bit(i, &supp, BITS_PER_LONG) + lq_sta->active_legacy_rate |= BIT(sband->bitrates[i].hw_value); + + /* + * active_siso_rate mask includes 9 MBits (bit 5), and CCK (bits 0-3), + * supp_rates[] does not; shift to convert format, force 9 MBits off. + */ + lq_sta->active_siso_rate = ht_cap->mcs.rx_mask[0] << 1; + lq_sta->active_siso_rate |= ht_cap->mcs.rx_mask[0] & 0x1; + lq_sta->active_siso_rate &= ~((u16)0x2); + lq_sta->active_siso_rate <<= IWL_FIRST_OFDM_RATE; + + /* Same here */ + lq_sta->active_mimo2_rate = ht_cap->mcs.rx_mask[1] << 1; + lq_sta->active_mimo2_rate |= ht_cap->mcs.rx_mask[1] & 0x1; + lq_sta->active_mimo2_rate &= ~((u16)0x2); + lq_sta->active_mimo2_rate <<= IWL_FIRST_OFDM_RATE; + + lq_sta->active_mimo3_rate = ht_cap->mcs.rx_mask[2] << 1; + lq_sta->active_mimo3_rate |= ht_cap->mcs.rx_mask[2] & 0x1; + lq_sta->active_mimo3_rate &= ~((u16)0x2); + lq_sta->active_mimo3_rate <<= IWL_FIRST_OFDM_RATE; + + IWL_DEBUG_RATE(priv, "SISO-RATE=%X MIMO2-RATE=%X MIMO3-RATE=%X\n", + lq_sta->active_siso_rate, + lq_sta->active_mimo2_rate, + lq_sta->active_mimo3_rate); + + /* These values will be overridden later */ + lq_sta->lq.general_params.single_stream_ant_msk = + first_antenna(priv->nvm_data->valid_tx_ant); + lq_sta->lq.general_params.dual_stream_ant_msk = + priv->nvm_data->valid_tx_ant & + ~first_antenna(priv->nvm_data->valid_tx_ant); + if (!lq_sta->lq.general_params.dual_stream_ant_msk) { + lq_sta->lq.general_params.dual_stream_ant_msk = ANT_AB; + } else if (num_of_ant(priv->nvm_data->valid_tx_ant) == 2) { + lq_sta->lq.general_params.dual_stream_ant_msk = + priv->nvm_data->valid_tx_ant; + } + + /* as default allow aggregation for all tids */ + lq_sta->tx_agg_tid_en = IWL_AGG_ALL_TID; + lq_sta->drv = priv; + + /* Set last_txrate_idx to lowest rate */ + lq_sta->last_txrate_idx = rate_lowest_index(sband, sta); + if (sband->band == IEEE80211_BAND_5GHZ) + lq_sta->last_txrate_idx += IWL_FIRST_OFDM_RATE; + lq_sta->is_agg = 0; +#ifdef CPTCFG_IWLWIFI_DEVICE_TESTMODE + priv->tm_fixed_rate = 0; +#endif +#ifdef CPTCFG_MAC80211_DEBUGFS + lq_sta->dbg_fixed_rate = 0; +#endif + + rs_initialize_lq(priv, sta, lq_sta); +} + +static void rs_fill_link_cmd(struct iwl_priv *priv, + struct iwl_lq_sta *lq_sta, u32 new_rate) +{ + struct iwl_scale_tbl_info tbl_type; + int index = 0; + int rate_idx; + int repeat_rate = 0; + u8 ant_toggle_cnt = 0; + u8 use_ht_possible = 1; + u8 valid_tx_ant = 0; + struct iwl_station_priv *sta_priv = + container_of(lq_sta, struct iwl_station_priv, lq_sta); + struct iwl_link_quality_cmd *lq_cmd = &lq_sta->lq; + + /* Override starting rate (index 0) if needed for debug purposes */ + rs_dbgfs_set_mcs(lq_sta, &new_rate, index); + + /* Interpret new_rate (rate_n_flags) */ + rs_get_tbl_info_from_mcs(new_rate, lq_sta->band, + &tbl_type, &rate_idx); + + if (priv && priv->bt_full_concurrent) { + /* 1x1 only */ + tbl_type.ant_type = + first_antenna(priv->nvm_data->valid_tx_ant); + } + + /* How many times should we repeat the initial rate? */ + if (is_legacy(tbl_type.lq_type)) { + ant_toggle_cnt = 1; + repeat_rate = IWL_NUMBER_TRY; + } else { + repeat_rate = min(IWL_HT_NUMBER_TRY, + LINK_QUAL_AGG_DISABLE_START_DEF - 1); + } + + lq_cmd->general_params.mimo_delimiter = + is_mimo(tbl_type.lq_type) ? 1 : 0; + + /* Fill 1st table entry (index 0) */ + lq_cmd->rs_table[index].rate_n_flags = cpu_to_le32(new_rate); + + if (num_of_ant(tbl_type.ant_type) == 1) { + lq_cmd->general_params.single_stream_ant_msk = + tbl_type.ant_type; + } else if (num_of_ant(tbl_type.ant_type) == 2) { + lq_cmd->general_params.dual_stream_ant_msk = + tbl_type.ant_type; + } /* otherwise we don't modify the existing value */ + + index++; + repeat_rate--; + if (priv) { + if (priv->bt_full_concurrent) + valid_tx_ant = ANT_A; + else + valid_tx_ant = priv->nvm_data->valid_tx_ant; + } + + /* Fill rest of rate table */ + while (index < LINK_QUAL_MAX_RETRY_NUM) { + /* Repeat initial/next rate. + * For legacy IWL_NUMBER_TRY == 1, this loop will not execute. + * For HT IWL_HT_NUMBER_TRY == 3, this executes twice. */ + while (repeat_rate > 0 && (index < LINK_QUAL_MAX_RETRY_NUM)) { + if (is_legacy(tbl_type.lq_type)) { + if (ant_toggle_cnt < NUM_TRY_BEFORE_ANT_TOGGLE) + ant_toggle_cnt++; + else if (priv && + rs_toggle_antenna(valid_tx_ant, + &new_rate, &tbl_type)) + ant_toggle_cnt = 1; + } + + /* Override next rate if needed for debug purposes */ + rs_dbgfs_set_mcs(lq_sta, &new_rate, index); + + /* Fill next table entry */ + lq_cmd->rs_table[index].rate_n_flags = + cpu_to_le32(new_rate); + repeat_rate--; + index++; + } + + rs_get_tbl_info_from_mcs(new_rate, lq_sta->band, &tbl_type, + &rate_idx); + + if (priv && priv->bt_full_concurrent) { + /* 1x1 only */ + tbl_type.ant_type = + first_antenna(priv->nvm_data->valid_tx_ant); + } + + /* Indicate to uCode which entries might be MIMO. + * If initial rate was MIMO, this will finally end up + * as (IWL_HT_NUMBER_TRY * 2), after 2nd pass, otherwise 0. */ + if (is_mimo(tbl_type.lq_type)) + lq_cmd->general_params.mimo_delimiter = index; + + /* Get next rate */ + new_rate = rs_get_lower_rate(lq_sta, &tbl_type, rate_idx, + use_ht_possible); + + /* How many times should we repeat the next rate? */ + if (is_legacy(tbl_type.lq_type)) { + if (ant_toggle_cnt < NUM_TRY_BEFORE_ANT_TOGGLE) + ant_toggle_cnt++; + else if (priv && + rs_toggle_antenna(valid_tx_ant, + &new_rate, &tbl_type)) + ant_toggle_cnt = 1; + + repeat_rate = IWL_NUMBER_TRY; + } else { + repeat_rate = IWL_HT_NUMBER_TRY; + } + + /* Don't allow HT rates after next pass. + * rs_get_lower_rate() will change type to LQ_A or LQ_G. */ + use_ht_possible = 0; + + /* Override next rate if needed for debug purposes */ + rs_dbgfs_set_mcs(lq_sta, &new_rate, index); + + /* Fill next table entry */ + lq_cmd->rs_table[index].rate_n_flags = cpu_to_le32(new_rate); + + index++; + repeat_rate--; + } + + lq_cmd->agg_params.agg_frame_cnt_limit = + sta_priv->max_agg_bufsize ?: LINK_QUAL_AGG_FRAME_LIMIT_DEF; + lq_cmd->agg_params.agg_dis_start_th = LINK_QUAL_AGG_DISABLE_START_DEF; + + lq_cmd->agg_params.agg_time_limit = + cpu_to_le16(LINK_QUAL_AGG_TIME_LIMIT_DEF); + /* + * overwrite if needed, pass aggregation time limit + * to uCode in uSec + */ + if (priv && priv->cfg->bt_params && + priv->cfg->bt_params->agg_time_limit && + priv->bt_traffic_load >= IWL_BT_COEX_TRAFFIC_LOAD_HIGH) + lq_cmd->agg_params.agg_time_limit = + cpu_to_le16(priv->cfg->bt_params->agg_time_limit); +} + +static void *rs_alloc(struct ieee80211_hw *hw, struct dentry *debugfsdir) +{ + return hw->priv; +} +/* rate scale requires free function to be implemented */ +static void rs_free(void *priv_rate) +{ + return; +} + +static void rs_free_sta(void *priv_r, struct ieee80211_sta *sta, + void *priv_sta) +{ + struct iwl_op_mode *op_mode __maybe_unused = priv_r; + struct iwl_priv *priv __maybe_unused = IWL_OP_MODE_GET_DVM(op_mode); + + IWL_DEBUG_RATE(priv, "enter\n"); + IWL_DEBUG_RATE(priv, "leave\n"); +} + +#ifdef CPTCFG_MAC80211_DEBUGFS +static void rs_dbgfs_set_mcs(struct iwl_lq_sta *lq_sta, + u32 *rate_n_flags, int index) +{ + struct iwl_priv *priv; + u8 valid_tx_ant; + u8 ant_sel_tx; + + priv = lq_sta->drv; + valid_tx_ant = priv->nvm_data->valid_tx_ant; + if (lq_sta->dbg_fixed_rate) { + ant_sel_tx = + ((lq_sta->dbg_fixed_rate & RATE_MCS_ANT_ABC_MSK) + >> RATE_MCS_ANT_POS); + if ((valid_tx_ant & ant_sel_tx) == ant_sel_tx) { + *rate_n_flags = lq_sta->dbg_fixed_rate; + IWL_DEBUG_RATE(priv, "Fixed rate ON\n"); + } else { + lq_sta->dbg_fixed_rate = 0; + IWL_ERR(priv, + "Invalid antenna selection 0x%X, Valid is 0x%X\n", + ant_sel_tx, valid_tx_ant); + IWL_DEBUG_RATE(priv, "Fixed rate OFF\n"); + } + } else { + IWL_DEBUG_RATE(priv, "Fixed rate OFF\n"); + } +} + +static ssize_t rs_sta_dbgfs_scale_table_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct iwl_lq_sta *lq_sta = file->private_data; + struct iwl_priv *priv; + char buf[64]; + size_t buf_size; + u32 parsed_rate; + + + priv = lq_sta->drv; + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (sscanf(buf, "%x", &parsed_rate) == 1) + lq_sta->dbg_fixed_rate = parsed_rate; + else + lq_sta->dbg_fixed_rate = 0; + + rs_program_fix_rate(priv, lq_sta); + + return count; +} + +static ssize_t rs_sta_dbgfs_scale_table_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + char *buff; + int desc = 0; + int i = 0; + int index = 0; + ssize_t ret; + + struct iwl_lq_sta *lq_sta = file->private_data; + struct iwl_priv *priv; + struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + + priv = lq_sta->drv; + buff = kmalloc(1024, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + desc += sprintf(buff+desc, "sta_id %d\n", lq_sta->lq.sta_id); + desc += sprintf(buff+desc, "failed=%d success=%d rate=0%X\n", + lq_sta->total_failed, lq_sta->total_success, + lq_sta->active_legacy_rate); + desc += sprintf(buff+desc, "fixed rate 0x%X\n", + lq_sta->dbg_fixed_rate); + desc += sprintf(buff+desc, "valid_tx_ant %s%s%s\n", + (priv->nvm_data->valid_tx_ant & ANT_A) ? "ANT_A," : "", + (priv->nvm_data->valid_tx_ant & ANT_B) ? "ANT_B," : "", + (priv->nvm_data->valid_tx_ant & ANT_C) ? "ANT_C" : ""); + desc += sprintf(buff+desc, "lq type %s\n", + (is_legacy(tbl->lq_type)) ? "legacy" : "HT"); + if (is_Ht(tbl->lq_type)) { + desc += sprintf(buff+desc, " %s", + (is_siso(tbl->lq_type)) ? "SISO" : + ((is_mimo2(tbl->lq_type)) ? "MIMO2" : "MIMO3")); + desc += sprintf(buff+desc, " %s", + (tbl->is_ht40) ? "40MHz" : "20MHz"); + desc += sprintf(buff+desc, " %s %s %s\n", (tbl->is_SGI) ? "SGI" : "", + (lq_sta->is_green) ? "GF enabled" : "", + (lq_sta->is_agg) ? "AGG on" : ""); + } + desc += sprintf(buff+desc, "last tx rate=0x%X\n", + lq_sta->last_rate_n_flags); + desc += sprintf(buff+desc, "general:" + "flags=0x%X mimo-d=%d s-ant0x%x d-ant=0x%x\n", + lq_sta->lq.general_params.flags, + lq_sta->lq.general_params.mimo_delimiter, + lq_sta->lq.general_params.single_stream_ant_msk, + lq_sta->lq.general_params.dual_stream_ant_msk); + + desc += sprintf(buff+desc, "agg:" + "time_limit=%d dist_start_th=%d frame_cnt_limit=%d\n", + le16_to_cpu(lq_sta->lq.agg_params.agg_time_limit), + lq_sta->lq.agg_params.agg_dis_start_th, + lq_sta->lq.agg_params.agg_frame_cnt_limit); + + desc += sprintf(buff+desc, + "Start idx [0]=0x%x [1]=0x%x [2]=0x%x [3]=0x%x\n", + lq_sta->lq.general_params.start_rate_index[0], + lq_sta->lq.general_params.start_rate_index[1], + lq_sta->lq.general_params.start_rate_index[2], + lq_sta->lq.general_params.start_rate_index[3]); + + for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) { + index = iwl_hwrate_to_plcp_idx( + le32_to_cpu(lq_sta->lq.rs_table[i].rate_n_flags)); + if (is_legacy(tbl->lq_type)) { + desc += sprintf(buff+desc, " rate[%d] 0x%X %smbps\n", + i, le32_to_cpu(lq_sta->lq.rs_table[i].rate_n_flags), + iwl_rate_mcs[index].mbps); + } else { + desc += sprintf(buff+desc, " rate[%d] 0x%X %smbps (%s)\n", + i, le32_to_cpu(lq_sta->lq.rs_table[i].rate_n_flags), + iwl_rate_mcs[index].mbps, iwl_rate_mcs[index].mcs); + } + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buff, desc); + kfree(buff); + return ret; +} + +static const struct file_operations rs_sta_dbgfs_scale_table_ops = { + .write = rs_sta_dbgfs_scale_table_write, + .read = rs_sta_dbgfs_scale_table_read, + .open = simple_open, + .llseek = default_llseek, +}; +static ssize_t rs_sta_dbgfs_stats_table_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + char *buff; + int desc = 0; + int i, j; + ssize_t ret; + + struct iwl_lq_sta *lq_sta = file->private_data; + + buff = kmalloc(1024, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + for (i = 0; i < LQ_SIZE; i++) { + desc += sprintf(buff+desc, + "%s type=%d SGI=%d HT40=%d DUP=%d GF=%d\n" + "rate=0x%X\n", + lq_sta->active_tbl == i ? "*" : "x", + lq_sta->lq_info[i].lq_type, + lq_sta->lq_info[i].is_SGI, + lq_sta->lq_info[i].is_ht40, + lq_sta->lq_info[i].is_dup, + lq_sta->is_green, + lq_sta->lq_info[i].current_rate); + for (j = 0; j < IWL_RATE_COUNT; j++) { + desc += sprintf(buff+desc, + "counter=%d success=%d %%=%d\n", + lq_sta->lq_info[i].win[j].counter, + lq_sta->lq_info[i].win[j].success_counter, + lq_sta->lq_info[i].win[j].success_ratio); + } + } + ret = simple_read_from_buffer(user_buf, count, ppos, buff, desc); + kfree(buff); + return ret; +} + +static const struct file_operations rs_sta_dbgfs_stats_table_ops = { + .read = rs_sta_dbgfs_stats_table_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t rs_sta_dbgfs_rate_scale_data_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + struct iwl_lq_sta *lq_sta = file->private_data; + struct iwl_scale_tbl_info *tbl = &lq_sta->lq_info[lq_sta->active_tbl]; + char buff[120]; + int desc = 0; + + if (is_Ht(tbl->lq_type)) + desc += sprintf(buff+desc, + "Bit Rate= %d Mb/s\n", + tbl->expected_tpt[lq_sta->last_txrate_idx]); + else + desc += sprintf(buff+desc, + "Bit Rate= %d Mb/s\n", + iwl_rates[lq_sta->last_txrate_idx].ieee >> 1); + + return simple_read_from_buffer(user_buf, count, ppos, buff, desc); +} + +static const struct file_operations rs_sta_dbgfs_rate_scale_data_ops = { + .read = rs_sta_dbgfs_rate_scale_data_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static void rs_add_debugfs(void *priv, void *priv_sta, + struct dentry *dir) +{ + struct iwl_lq_sta *lq_sta = priv_sta; + lq_sta->rs_sta_dbgfs_scale_table_file = + debugfs_create_file("rate_scale_table", S_IRUSR | S_IWUSR, dir, + lq_sta, &rs_sta_dbgfs_scale_table_ops); + lq_sta->rs_sta_dbgfs_stats_table_file = + debugfs_create_file("rate_stats_table", S_IRUSR, dir, + lq_sta, &rs_sta_dbgfs_stats_table_ops); + lq_sta->rs_sta_dbgfs_rate_scale_data_file = + debugfs_create_file("rate_scale_data", S_IRUSR, dir, + lq_sta, &rs_sta_dbgfs_rate_scale_data_ops); + lq_sta->rs_sta_dbgfs_tx_agg_tid_en_file = + debugfs_create_u8("tx_agg_tid_enable", S_IRUSR | S_IWUSR, dir, + &lq_sta->tx_agg_tid_en); + +} + +static void rs_remove_debugfs(void *priv, void *priv_sta) +{ + struct iwl_lq_sta *lq_sta = priv_sta; + debugfs_remove(lq_sta->rs_sta_dbgfs_scale_table_file); + debugfs_remove(lq_sta->rs_sta_dbgfs_stats_table_file); + debugfs_remove(lq_sta->rs_sta_dbgfs_rate_scale_data_file); + debugfs_remove(lq_sta->rs_sta_dbgfs_tx_agg_tid_en_file); +} +#endif + +/* + * Initialization of rate scaling information is done by driver after + * the station is added. Since mac80211 calls this function before a + * station is added we ignore it. + */ +static void rs_rate_init_stub(void *priv_r, struct ieee80211_supported_band *sband, + struct ieee80211_sta *sta, void *priv_sta) +{ +} +static struct rate_control_ops rs_ops = { + .module = NULL, + .name = RS_NAME, + .tx_status = rs_tx_status, + .get_rate = rs_get_rate, + .rate_init = rs_rate_init_stub, + .alloc = rs_alloc, + .free = rs_free, + .alloc_sta = rs_alloc_sta, + .free_sta = rs_free_sta, +#ifdef CPTCFG_MAC80211_DEBUGFS + .add_sta_debugfs = rs_add_debugfs, + .remove_sta_debugfs = rs_remove_debugfs, +#endif +}; + +int iwlagn_rate_control_register(void) +{ + return ieee80211_rate_control_register(&rs_ops); +} + +void iwlagn_rate_control_unregister(void) +{ + ieee80211_rate_control_unregister(&rs_ops); +} + diff --git a/drivers/net/wireless/iwlwifi/dvm/rs.h b/drivers/net/wireless/iwlwifi/dvm/rs.h new file mode 100644 index 0000000..73a97f0 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/rs.h @@ -0,0 +1,433 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#ifndef __iwl_agn_rs_h__ +#define __iwl_agn_rs_h__ + +#include + +#include "iwl-config.h" + +#include "commands.h" + +struct iwl_rate_info { + u8 plcp; /* uCode API: IWL_RATE_6M_PLCP, etc. */ + u8 plcp_siso; /* uCode API: IWL_RATE_SISO_6M_PLCP, etc. */ + u8 plcp_mimo2; /* uCode API: IWL_RATE_MIMO2_6M_PLCP, etc. */ + u8 plcp_mimo3; /* uCode API: IWL_RATE_MIMO3_6M_PLCP, etc. */ + u8 ieee; /* MAC header: IWL_RATE_6M_IEEE, etc. */ + u8 prev_ieee; /* previous rate in IEEE speeds */ + u8 next_ieee; /* next rate in IEEE speeds */ + u8 prev_rs; /* previous rate used in rs algo */ + u8 next_rs; /* next rate used in rs algo */ + u8 prev_rs_tgg; /* previous rate used in TGG rs algo */ + u8 next_rs_tgg; /* next rate used in TGG rs algo */ +}; + +/* + * These serve as indexes into + * struct iwl_rate_info iwl_rates[IWL_RATE_COUNT]; + */ +enum { + IWL_RATE_1M_INDEX = 0, + IWL_RATE_2M_INDEX, + IWL_RATE_5M_INDEX, + IWL_RATE_11M_INDEX, + IWL_RATE_6M_INDEX, + IWL_RATE_9M_INDEX, + IWL_RATE_12M_INDEX, + IWL_RATE_18M_INDEX, + IWL_RATE_24M_INDEX, + IWL_RATE_36M_INDEX, + IWL_RATE_48M_INDEX, + IWL_RATE_54M_INDEX, + IWL_RATE_60M_INDEX, + IWL_RATE_COUNT, /*FIXME:RS:change to IWL_RATE_INDEX_COUNT,*/ + IWL_RATE_COUNT_LEGACY = IWL_RATE_COUNT - 1, /* Excluding 60M */ + IWL_RATE_INVM_INDEX = IWL_RATE_COUNT, + IWL_RATE_INVALID = IWL_RATE_COUNT, +}; + +enum { + IWL_RATE_6M_INDEX_TABLE = 0, + IWL_RATE_9M_INDEX_TABLE, + IWL_RATE_12M_INDEX_TABLE, + IWL_RATE_18M_INDEX_TABLE, + IWL_RATE_24M_INDEX_TABLE, + IWL_RATE_36M_INDEX_TABLE, + IWL_RATE_48M_INDEX_TABLE, + IWL_RATE_54M_INDEX_TABLE, + IWL_RATE_1M_INDEX_TABLE, + IWL_RATE_2M_INDEX_TABLE, + IWL_RATE_5M_INDEX_TABLE, + IWL_RATE_11M_INDEX_TABLE, + IWL_RATE_INVM_INDEX_TABLE = IWL_RATE_INVM_INDEX - 1, +}; + +enum { + IWL_FIRST_OFDM_RATE = IWL_RATE_6M_INDEX, + IWL_LAST_OFDM_RATE = IWL_RATE_60M_INDEX, + IWL_FIRST_CCK_RATE = IWL_RATE_1M_INDEX, + IWL_LAST_CCK_RATE = IWL_RATE_11M_INDEX, +}; + +/* #define vs. enum to keep from defaulting to 'large integer' */ +#define IWL_RATE_6M_MASK (1 << IWL_RATE_6M_INDEX) +#define IWL_RATE_9M_MASK (1 << IWL_RATE_9M_INDEX) +#define IWL_RATE_12M_MASK (1 << IWL_RATE_12M_INDEX) +#define IWL_RATE_18M_MASK (1 << IWL_RATE_18M_INDEX) +#define IWL_RATE_24M_MASK (1 << IWL_RATE_24M_INDEX) +#define IWL_RATE_36M_MASK (1 << IWL_RATE_36M_INDEX) +#define IWL_RATE_48M_MASK (1 << IWL_RATE_48M_INDEX) +#define IWL_RATE_54M_MASK (1 << IWL_RATE_54M_INDEX) +#define IWL_RATE_60M_MASK (1 << IWL_RATE_60M_INDEX) +#define IWL_RATE_1M_MASK (1 << IWL_RATE_1M_INDEX) +#define IWL_RATE_2M_MASK (1 << IWL_RATE_2M_INDEX) +#define IWL_RATE_5M_MASK (1 << IWL_RATE_5M_INDEX) +#define IWL_RATE_11M_MASK (1 << IWL_RATE_11M_INDEX) + +/* uCode API values for legacy bit rates, both OFDM and CCK */ +enum { + IWL_RATE_6M_PLCP = 13, + IWL_RATE_9M_PLCP = 15, + IWL_RATE_12M_PLCP = 5, + IWL_RATE_18M_PLCP = 7, + IWL_RATE_24M_PLCP = 9, + IWL_RATE_36M_PLCP = 11, + IWL_RATE_48M_PLCP = 1, + IWL_RATE_54M_PLCP = 3, + IWL_RATE_60M_PLCP = 3,/*FIXME:RS:should be removed*/ + IWL_RATE_1M_PLCP = 10, + IWL_RATE_2M_PLCP = 20, + IWL_RATE_5M_PLCP = 55, + IWL_RATE_11M_PLCP = 110, + /*FIXME:RS:change to IWL_RATE_LEGACY_??M_PLCP */ + /*FIXME:RS:add IWL_RATE_LEGACY_INVM_PLCP = 0,*/ +}; + +/* uCode API values for OFDM high-throughput (HT) bit rates */ +enum { + IWL_RATE_SISO_6M_PLCP = 0, + IWL_RATE_SISO_12M_PLCP = 1, + IWL_RATE_SISO_18M_PLCP = 2, + IWL_RATE_SISO_24M_PLCP = 3, + IWL_RATE_SISO_36M_PLCP = 4, + IWL_RATE_SISO_48M_PLCP = 5, + IWL_RATE_SISO_54M_PLCP = 6, + IWL_RATE_SISO_60M_PLCP = 7, + IWL_RATE_MIMO2_6M_PLCP = 0x8, + IWL_RATE_MIMO2_12M_PLCP = 0x9, + IWL_RATE_MIMO2_18M_PLCP = 0xa, + IWL_RATE_MIMO2_24M_PLCP = 0xb, + IWL_RATE_MIMO2_36M_PLCP = 0xc, + IWL_RATE_MIMO2_48M_PLCP = 0xd, + IWL_RATE_MIMO2_54M_PLCP = 0xe, + IWL_RATE_MIMO2_60M_PLCP = 0xf, + IWL_RATE_MIMO3_6M_PLCP = 0x10, + IWL_RATE_MIMO3_12M_PLCP = 0x11, + IWL_RATE_MIMO3_18M_PLCP = 0x12, + IWL_RATE_MIMO3_24M_PLCP = 0x13, + IWL_RATE_MIMO3_36M_PLCP = 0x14, + IWL_RATE_MIMO3_48M_PLCP = 0x15, + IWL_RATE_MIMO3_54M_PLCP = 0x16, + IWL_RATE_MIMO3_60M_PLCP = 0x17, + IWL_RATE_SISO_INVM_PLCP, + IWL_RATE_MIMO2_INVM_PLCP = IWL_RATE_SISO_INVM_PLCP, + IWL_RATE_MIMO3_INVM_PLCP = IWL_RATE_SISO_INVM_PLCP, +}; + +/* MAC header values for bit rates */ +enum { + IWL_RATE_6M_IEEE = 12, + IWL_RATE_9M_IEEE = 18, + IWL_RATE_12M_IEEE = 24, + IWL_RATE_18M_IEEE = 36, + IWL_RATE_24M_IEEE = 48, + IWL_RATE_36M_IEEE = 72, + IWL_RATE_48M_IEEE = 96, + IWL_RATE_54M_IEEE = 108, + IWL_RATE_60M_IEEE = 120, + IWL_RATE_1M_IEEE = 2, + IWL_RATE_2M_IEEE = 4, + IWL_RATE_5M_IEEE = 11, + IWL_RATE_11M_IEEE = 22, +}; + +#define IWL_RATES_MASK ((1 << IWL_RATE_COUNT) - 1) + +#define IWL_INVALID_VALUE -1 + +#define IWL_MIN_RSSI_VAL -100 +#define IWL_MAX_RSSI_VAL 0 + +/* These values specify how many Tx frame attempts before + * searching for a new modulation mode */ +#define IWL_LEGACY_FAILURE_LIMIT 160 +#define IWL_LEGACY_SUCCESS_LIMIT 480 +#define IWL_LEGACY_TABLE_COUNT 160 + +#define IWL_NONE_LEGACY_FAILURE_LIMIT 400 +#define IWL_NONE_LEGACY_SUCCESS_LIMIT 4500 +#define IWL_NONE_LEGACY_TABLE_COUNT 1500 + +/* Success ratio (ACKed / attempted tx frames) values (perfect is 128 * 100) */ +#define IWL_RS_GOOD_RATIO 12800 /* 100% */ +#define IWL_RATE_SCALE_SWITCH 10880 /* 85% */ +#define IWL_RATE_HIGH_TH 10880 /* 85% */ +#define IWL_RATE_INCREASE_TH 6400 /* 50% */ +#define IWL_RATE_DECREASE_TH 1920 /* 15% */ + +/* possible actions when in legacy mode */ +#define IWL_LEGACY_SWITCH_ANTENNA1 0 +#define IWL_LEGACY_SWITCH_ANTENNA2 1 +#define IWL_LEGACY_SWITCH_SISO 2 +#define IWL_LEGACY_SWITCH_MIMO2_AB 3 +#define IWL_LEGACY_SWITCH_MIMO2_AC 4 +#define IWL_LEGACY_SWITCH_MIMO2_BC 5 +#define IWL_LEGACY_SWITCH_MIMO3_ABC 6 + +/* possible actions when in siso mode */ +#define IWL_SISO_SWITCH_ANTENNA1 0 +#define IWL_SISO_SWITCH_ANTENNA2 1 +#define IWL_SISO_SWITCH_MIMO2_AB 2 +#define IWL_SISO_SWITCH_MIMO2_AC 3 +#define IWL_SISO_SWITCH_MIMO2_BC 4 +#define IWL_SISO_SWITCH_GI 5 +#define IWL_SISO_SWITCH_MIMO3_ABC 6 + + +/* possible actions when in mimo mode */ +#define IWL_MIMO2_SWITCH_ANTENNA1 0 +#define IWL_MIMO2_SWITCH_ANTENNA2 1 +#define IWL_MIMO2_SWITCH_SISO_A 2 +#define IWL_MIMO2_SWITCH_SISO_B 3 +#define IWL_MIMO2_SWITCH_SISO_C 4 +#define IWL_MIMO2_SWITCH_GI 5 +#define IWL_MIMO2_SWITCH_MIMO3_ABC 6 + + +/* possible actions when in mimo3 mode */ +#define IWL_MIMO3_SWITCH_ANTENNA1 0 +#define IWL_MIMO3_SWITCH_ANTENNA2 1 +#define IWL_MIMO3_SWITCH_SISO_A 2 +#define IWL_MIMO3_SWITCH_SISO_B 3 +#define IWL_MIMO3_SWITCH_SISO_C 4 +#define IWL_MIMO3_SWITCH_MIMO2_AB 5 +#define IWL_MIMO3_SWITCH_MIMO2_AC 6 +#define IWL_MIMO3_SWITCH_MIMO2_BC 7 +#define IWL_MIMO3_SWITCH_GI 8 + + +#define IWL_MAX_11N_MIMO3_SEARCH IWL_MIMO3_SWITCH_GI +#define IWL_MAX_SEARCH IWL_MIMO2_SWITCH_MIMO3_ABC + +/*FIXME:RS:add possible actions for MIMO3*/ + +#define IWL_ACTION_LIMIT 3 /* # possible actions */ + +#define LQ_SIZE 2 /* 2 mode tables: "Active" and "Search" */ + +/* load per tid defines for A-MPDU activation */ +#define IWL_AGG_TPT_THREHOLD 0 +#define IWL_AGG_LOAD_THRESHOLD 10 +#define IWL_AGG_ALL_TID 0xff +#define TID_QUEUE_CELL_SPACING 50 /*mS */ +#define TID_QUEUE_MAX_SIZE 20 +#define TID_ROUND_VALUE 5 /* mS */ + +#define TID_MAX_TIME_DIFF ((TID_QUEUE_MAX_SIZE - 1) * TID_QUEUE_CELL_SPACING) +#define TIME_WRAP_AROUND(x, y) (((y) > (x)) ? (y) - (x) : (0-(x)) + (y)) + +extern const struct iwl_rate_info iwl_rates[IWL_RATE_COUNT]; + +enum iwl_table_type { + LQ_NONE, + LQ_G, /* legacy types */ + LQ_A, + LQ_SISO, /* high-throughput types */ + LQ_MIMO2, + LQ_MIMO3, + LQ_MAX, +}; + +#define is_legacy(tbl) (((tbl) == LQ_G) || ((tbl) == LQ_A)) +#define is_siso(tbl) ((tbl) == LQ_SISO) +#define is_mimo2(tbl) ((tbl) == LQ_MIMO2) +#define is_mimo3(tbl) ((tbl) == LQ_MIMO3) +#define is_mimo(tbl) (is_mimo2(tbl) || is_mimo3(tbl)) +#define is_Ht(tbl) (is_siso(tbl) || is_mimo(tbl)) +#define is_a_band(tbl) ((tbl) == LQ_A) +#define is_g_and(tbl) ((tbl) == LQ_G) + +#define IWL_MAX_MCS_DISPLAY_SIZE 12 + +struct iwl_rate_mcs_info { + char mbps[IWL_MAX_MCS_DISPLAY_SIZE]; + char mcs[IWL_MAX_MCS_DISPLAY_SIZE]; +}; + +/** + * struct iwl_rate_scale_data -- tx success history for one rate + */ +struct iwl_rate_scale_data { + u64 data; /* bitmap of successful frames */ + s32 success_counter; /* number of frames successful */ + s32 success_ratio; /* per-cent * 128 */ + s32 counter; /* number of frames attempted */ + s32 average_tpt; /* success ratio * expected throughput */ + unsigned long stamp; +}; + +/** + * struct iwl_scale_tbl_info -- tx params and success history for all rates + * + * There are two of these in struct iwl_lq_sta, + * one for "active", and one for "search". + */ +struct iwl_scale_tbl_info { + enum iwl_table_type lq_type; + u8 ant_type; + u8 is_SGI; /* 1 = short guard interval */ + u8 is_ht40; /* 1 = 40 MHz channel width */ + u8 is_dup; /* 1 = duplicated data streams */ + u8 action; /* change modulation; IWL_[LEGACY/SISO/MIMO]_SWITCH_* */ + u8 max_search; /* maximun number of tables we can search */ + s32 *expected_tpt; /* throughput metrics; expected_tpt_G, etc. */ + u32 current_rate; /* rate_n_flags, uCode API format */ + struct iwl_rate_scale_data win[IWL_RATE_COUNT]; /* rate histories */ +}; + +struct iwl_traffic_load { + unsigned long time_stamp; /* age of the oldest statistics */ + u32 packet_count[TID_QUEUE_MAX_SIZE]; /* packet count in this time + * slice */ + u32 total; /* total num of packets during the + * last TID_MAX_TIME_DIFF */ + u8 queue_count; /* number of queues that has + * been used since the last cleanup */ + u8 head; /* start of the circular buffer */ +}; + +/** + * struct iwl_lq_sta -- driver's rate scaling private structure + * + * Pointer to this gets passed back and forth between driver and mac80211. + */ +struct iwl_lq_sta { + u8 active_tbl; /* index of active table, range 0-1 */ + u8 enable_counter; /* indicates HT mode */ + u8 stay_in_tbl; /* 1: disallow, 0: allow search for new mode */ + u8 search_better_tbl; /* 1: currently trying alternate mode */ + s32 last_tpt; + + /* The following determine when to search for a new mode */ + u32 table_count_limit; + u32 max_failure_limit; /* # failed frames before new search */ + u32 max_success_limit; /* # successful frames before new search */ + u32 table_count; + u32 total_failed; /* total failed frames, any/all rates */ + u32 total_success; /* total successful frames, any/all rates */ + u64 flush_timer; /* time staying in mode before new search */ + + u8 action_counter; /* # mode-switch actions tried */ + u8 is_green; + u8 is_dup; + enum ieee80211_band band; + + /* The following are bitmaps of rates; IWL_RATE_6M_MASK, etc. */ + u32 supp_rates; + u16 active_legacy_rate; + u16 active_siso_rate; + u16 active_mimo2_rate; + u16 active_mimo3_rate; + s8 max_rate_idx; /* Max rate set by user */ + u8 missed_rate_counter; + + struct iwl_link_quality_cmd lq; + struct iwl_scale_tbl_info lq_info[LQ_SIZE]; /* "active", "search" */ + struct iwl_traffic_load load[IWL_MAX_TID_COUNT]; + u8 tx_agg_tid_en; +#ifdef CPTCFG_MAC80211_DEBUGFS + struct dentry *rs_sta_dbgfs_scale_table_file; + struct dentry *rs_sta_dbgfs_stats_table_file; + struct dentry *rs_sta_dbgfs_rate_scale_data_file; + struct dentry *rs_sta_dbgfs_tx_agg_tid_en_file; + u32 dbg_fixed_rate; +#endif + struct iwl_priv *drv; + + /* used to be in sta_info */ + int last_txrate_idx; + /* last tx rate_n_flags */ + u32 last_rate_n_flags; + /* packets destined for this STA are aggregated */ + u8 is_agg; + /* BT traffic this sta was last updated in */ + u8 last_bt_traffic; +}; + +static inline u8 num_of_ant(u8 mask) +{ + return !!((mask) & ANT_A) + + !!((mask) & ANT_B) + + !!((mask) & ANT_C); +} + +static inline u8 first_antenna(u8 mask) +{ + if (mask & ANT_A) + return ANT_A; + if (mask & ANT_B) + return ANT_B; + return ANT_C; +} + + +/* Initialize station's rate scaling information after adding station */ +extern void iwl_rs_rate_init(struct iwl_priv *priv, + struct ieee80211_sta *sta, u8 sta_id); + +/** + * iwl_rate_control_register - Register the rate control algorithm callbacks + * + * Since the rate control algorithm is hardware specific, there is no need + * or reason to place it as a stand alone module. The driver can call + * iwl_rate_control_register in order to register the rate control callbacks + * with the mac80211 subsystem. This should be performed prior to calling + * ieee80211_register_hw + * + */ +extern int iwlagn_rate_control_register(void); + +/** + * iwl_rate_control_unregister - Unregister the rate control callbacks + * + * This should be called after calling ieee80211_unregister_hw, but before + * the driver is unloaded. + */ +extern void iwlagn_rate_control_unregister(void); + +#endif /* __iwl_agn__rs__ */ diff --git a/drivers/net/wireless/iwlwifi/dvm/rx.c b/drivers/net/wireless/iwlwifi/dvm/rx.c new file mode 100644 index 0000000..bb2371c --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/rx.c @@ -0,0 +1,1151 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portionhelp of the ieee80211 subsystem header files. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include "iwl-io.h" +#include "dev.h" +#include "calib.h" +#include "agn.h" + +#define IWL_CMD_ENTRY(x) [x] = #x + +const char *iwl_dvm_cmd_strings[REPLY_MAX] = { + IWL_CMD_ENTRY(REPLY_ALIVE), + IWL_CMD_ENTRY(REPLY_ERROR), + IWL_CMD_ENTRY(REPLY_ECHO), + IWL_CMD_ENTRY(REPLY_RXON), + IWL_CMD_ENTRY(REPLY_RXON_ASSOC), + IWL_CMD_ENTRY(REPLY_QOS_PARAM), + IWL_CMD_ENTRY(REPLY_RXON_TIMING), + IWL_CMD_ENTRY(REPLY_ADD_STA), + IWL_CMD_ENTRY(REPLY_REMOVE_STA), + IWL_CMD_ENTRY(REPLY_REMOVE_ALL_STA), + IWL_CMD_ENTRY(REPLY_TXFIFO_FLUSH), + IWL_CMD_ENTRY(REPLY_WEPKEY), + IWL_CMD_ENTRY(REPLY_TX), + IWL_CMD_ENTRY(REPLY_LEDS_CMD), + IWL_CMD_ENTRY(REPLY_TX_LINK_QUALITY_CMD), + IWL_CMD_ENTRY(COEX_PRIORITY_TABLE_CMD), + IWL_CMD_ENTRY(COEX_MEDIUM_NOTIFICATION), + IWL_CMD_ENTRY(COEX_EVENT_CMD), + IWL_CMD_ENTRY(REPLY_QUIET_CMD), + IWL_CMD_ENTRY(REPLY_CHANNEL_SWITCH), + IWL_CMD_ENTRY(CHANNEL_SWITCH_NOTIFICATION), + IWL_CMD_ENTRY(REPLY_SPECTRUM_MEASUREMENT_CMD), + IWL_CMD_ENTRY(SPECTRUM_MEASURE_NOTIFICATION), + IWL_CMD_ENTRY(POWER_TABLE_CMD), + IWL_CMD_ENTRY(PM_SLEEP_NOTIFICATION), + IWL_CMD_ENTRY(PM_DEBUG_STATISTIC_NOTIFIC), + IWL_CMD_ENTRY(REPLY_SCAN_CMD), + IWL_CMD_ENTRY(REPLY_SCAN_ABORT_CMD), + IWL_CMD_ENTRY(SCAN_START_NOTIFICATION), + IWL_CMD_ENTRY(SCAN_RESULTS_NOTIFICATION), + IWL_CMD_ENTRY(SCAN_COMPLETE_NOTIFICATION), + IWL_CMD_ENTRY(BEACON_NOTIFICATION), + IWL_CMD_ENTRY(REPLY_TX_BEACON), + IWL_CMD_ENTRY(WHO_IS_AWAKE_NOTIFICATION), + IWL_CMD_ENTRY(QUIET_NOTIFICATION), + IWL_CMD_ENTRY(REPLY_TX_PWR_TABLE_CMD), + IWL_CMD_ENTRY(MEASURE_ABORT_NOTIFICATION), + IWL_CMD_ENTRY(REPLY_BT_CONFIG), + IWL_CMD_ENTRY(REPLY_STATISTICS_CMD), + IWL_CMD_ENTRY(STATISTICS_NOTIFICATION), + IWL_CMD_ENTRY(REPLY_CARD_STATE_CMD), + IWL_CMD_ENTRY(CARD_STATE_NOTIFICATION), + IWL_CMD_ENTRY(MISSED_BEACONS_NOTIFICATION), + IWL_CMD_ENTRY(REPLY_CT_KILL_CONFIG_CMD), + IWL_CMD_ENTRY(SENSITIVITY_CMD), + IWL_CMD_ENTRY(REPLY_PHY_CALIBRATION_CMD), + IWL_CMD_ENTRY(REPLY_RX_PHY_CMD), + IWL_CMD_ENTRY(REPLY_RX_MPDU_CMD), + IWL_CMD_ENTRY(REPLY_COMPRESSED_BA), + IWL_CMD_ENTRY(CALIBRATION_CFG_CMD), + IWL_CMD_ENTRY(CALIBRATION_RES_NOTIFICATION), + IWL_CMD_ENTRY(CALIBRATION_COMPLETE_NOTIFICATION), + IWL_CMD_ENTRY(REPLY_TX_POWER_DBM_CMD), + IWL_CMD_ENTRY(TEMPERATURE_NOTIFICATION), + IWL_CMD_ENTRY(TX_ANT_CONFIGURATION_CMD), + IWL_CMD_ENTRY(REPLY_BT_COEX_PROFILE_NOTIF), + IWL_CMD_ENTRY(REPLY_BT_COEX_PRIO_TABLE), + IWL_CMD_ENTRY(REPLY_BT_COEX_PROT_ENV), + IWL_CMD_ENTRY(REPLY_WIPAN_PARAMS), + IWL_CMD_ENTRY(REPLY_WIPAN_RXON), + IWL_CMD_ENTRY(REPLY_WIPAN_RXON_TIMING), + IWL_CMD_ENTRY(REPLY_WIPAN_RXON_ASSOC), + IWL_CMD_ENTRY(REPLY_WIPAN_QOS_PARAM), + IWL_CMD_ENTRY(REPLY_WIPAN_WEPKEY), + IWL_CMD_ENTRY(REPLY_WIPAN_P2P_CHANNEL_SWITCH), + IWL_CMD_ENTRY(REPLY_WIPAN_NOA_NOTIFICATION), + IWL_CMD_ENTRY(REPLY_WIPAN_DEACTIVATION_COMPLETE), + IWL_CMD_ENTRY(REPLY_WOWLAN_PATTERNS), + IWL_CMD_ENTRY(REPLY_WOWLAN_WAKEUP_FILTER), + IWL_CMD_ENTRY(REPLY_WOWLAN_TSC_RSC_PARAMS), + IWL_CMD_ENTRY(REPLY_WOWLAN_TKIP_PARAMS), + IWL_CMD_ENTRY(REPLY_WOWLAN_KEK_KCK_MATERIAL), + IWL_CMD_ENTRY(REPLY_WOWLAN_GET_STATUS), + IWL_CMD_ENTRY(REPLY_D3_CONFIG), +}; +#undef IWL_CMD_ENTRY + +/****************************************************************************** + * + * Generic RX handler implementations + * + ******************************************************************************/ + +static int iwlagn_rx_reply_error(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_error_resp *err_resp = (void *)pkt->data; + + IWL_ERR(priv, "Error Reply type 0x%08X cmd REPLY_ERROR (0x%02X) " + "seq 0x%04X ser 0x%08X\n", + le32_to_cpu(err_resp->error_type), + err_resp->cmd_id, + le16_to_cpu(err_resp->bad_cmd_seq_num), + le32_to_cpu(err_resp->error_info)); + return 0; +} + +static int iwlagn_rx_csa(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_csa_notification *csa = (void *)pkt->data; + /* + * MULTI-FIXME + * See iwlagn_mac_channel_switch. + */ + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + struct iwl_rxon_cmd *rxon = (void *)&ctx->active; + + if (!test_bit(STATUS_CHANNEL_SWITCH_PENDING, &priv->status)) + return 0; + + if (!le32_to_cpu(csa->status) && csa->channel == priv->switch_channel) { + rxon->channel = csa->channel; + ctx->staging.channel = csa->channel; + IWL_DEBUG_11H(priv, "CSA notif: channel %d\n", + le16_to_cpu(csa->channel)); + iwl_chswitch_done(priv, true); + } else { + IWL_ERR(priv, "CSA notif (fail) : channel %d\n", + le16_to_cpu(csa->channel)); + iwl_chswitch_done(priv, false); + } + return 0; +} + + +static int iwlagn_rx_spectrum_measure_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_spectrum_notification *report = (void *)pkt->data; + + if (!report->state) { + IWL_DEBUG_11H(priv, + "Spectrum Measure Notification: Start\n"); + return 0; + } + + memcpy(&priv->measure_report, report, sizeof(*report)); + priv->measurement_status |= MEASUREMENT_READY; + return 0; +} + +static int iwlagn_rx_pm_sleep_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ +#ifdef CPTCFG_IWLWIFI_DEBUG + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_sleep_notification *sleep = (void *)pkt->data; + IWL_DEBUG_RX(priv, "sleep mode: %d, src: %d\n", + sleep->pm_sleep_mode, sleep->pm_wakeup_src); +#endif + return 0; +} + +static int iwlagn_rx_pm_debug_statistics_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + u32 __maybe_unused len = + le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; + IWL_DEBUG_RADIO(priv, "Dumping %d bytes of unhandled " + "notification for PM_DEBUG_STATISTIC_NOTIFIC:\n", len); + iwl_print_hex_dump(priv, IWL_DL_RADIO, pkt->data, len); + return 0; +} + +static int iwlagn_rx_beacon_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwlagn_beacon_notif *beacon = (void *)pkt->data; +#ifdef CPTCFG_IWLWIFI_DEBUG + u16 status = le16_to_cpu(beacon->beacon_notify_hdr.status.status); + u8 rate = iwl_hw_get_rate(beacon->beacon_notify_hdr.rate_n_flags); + + IWL_DEBUG_RX(priv, "beacon status %#x, retries:%d ibssmgr:%d " + "tsf:0x%.8x%.8x rate:%d\n", + status & TX_STATUS_MSK, + beacon->beacon_notify_hdr.failure_frame, + le32_to_cpu(beacon->ibss_mgr_status), + le32_to_cpu(beacon->high_tsf), + le32_to_cpu(beacon->low_tsf), rate); +#endif + + priv->ibss_manager = le32_to_cpu(beacon->ibss_mgr_status); + + return 0; +} + +/** + * iwl_good_plcp_health - checks for plcp error. + * + * When the plcp error is exceeding the thresholds, reset the radio + * to improve the throughput. + */ +static bool iwlagn_good_plcp_health(struct iwl_priv *priv, + struct statistics_rx_phy *cur_ofdm, + struct statistics_rx_ht_phy *cur_ofdm_ht, + unsigned int msecs) +{ + int delta; + int threshold = priv->plcp_delta_threshold; + + if (threshold == IWL_MAX_PLCP_ERR_THRESHOLD_DISABLE) { + IWL_DEBUG_RADIO(priv, "plcp_err check disabled\n"); + return true; + } + + delta = le32_to_cpu(cur_ofdm->plcp_err) - + le32_to_cpu(priv->statistics.rx_ofdm.plcp_err) + + le32_to_cpu(cur_ofdm_ht->plcp_err) - + le32_to_cpu(priv->statistics.rx_ofdm_ht.plcp_err); + + /* Can be negative if firmware reset statistics */ + if (delta <= 0) + return true; + + if ((delta * 100 / msecs) > threshold) { + IWL_DEBUG_RADIO(priv, + "plcp health threshold %u delta %d msecs %u\n", + threshold, delta, msecs); + return false; + } + + return true; +} + +int iwl_force_rf_reset(struct iwl_priv *priv, bool external) +{ + struct iwl_rf_reset *rf_reset; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return -EAGAIN; + + if (!iwl_is_any_associated(priv)) { + IWL_DEBUG_SCAN(priv, "force reset rejected: not associated\n"); + return -ENOLINK; + } + + rf_reset = &priv->rf_reset; + rf_reset->reset_request_count++; + if (!external && rf_reset->last_reset_jiffies && + time_after(rf_reset->last_reset_jiffies + + IWL_DELAY_NEXT_FORCE_RF_RESET, jiffies)) { + IWL_DEBUG_INFO(priv, "RF reset rejected\n"); + rf_reset->reset_reject_count++; + return -EAGAIN; + } + rf_reset->reset_success_count++; + rf_reset->last_reset_jiffies = jiffies; + + /* + * There is no easy and better way to force reset the radio, + * the only known method is switching channel which will force to + * reset and tune the radio. + * Use internal short scan (single channel) operation to should + * achieve this objective. + * Driver should reset the radio when number of consecutive missed + * beacon, or any other uCode error condition detected. + */ + IWL_DEBUG_INFO(priv, "perform radio reset.\n"); + iwl_internal_short_hw_scan(priv); + return 0; +} + + +static void iwlagn_recover_from_statistics(struct iwl_priv *priv, + struct statistics_rx_phy *cur_ofdm, + struct statistics_rx_ht_phy *cur_ofdm_ht, + struct statistics_tx *tx, + unsigned long stamp) +{ + unsigned int msecs; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + msecs = jiffies_to_msecs(stamp - priv->rx_statistics_jiffies); + + /* Only gather statistics and update time stamp when not associated */ + if (!iwl_is_any_associated(priv)) + return; + + /* Do not check/recover when do not have enough statistics data */ + if (msecs < 99) + return; + + if (iwlwifi_mod_params.plcp_check && + !iwlagn_good_plcp_health(priv, cur_ofdm, cur_ofdm_ht, msecs)) + iwl_force_rf_reset(priv, false); +} + +/* Calculate noise level, based on measurements during network silence just + * before arriving beacon. This measurement can be done only if we know + * exactly when to expect beacons, therefore only when we're associated. */ +static void iwlagn_rx_calc_noise(struct iwl_priv *priv) +{ + struct statistics_rx_non_phy *rx_info; + int num_active_rx = 0; + int total_silence = 0; + int bcn_silence_a, bcn_silence_b, bcn_silence_c; + int last_rx_noise; + + rx_info = &priv->statistics.rx_non_phy; + + bcn_silence_a = + le32_to_cpu(rx_info->beacon_silence_rssi_a) & IN_BAND_FILTER; + bcn_silence_b = + le32_to_cpu(rx_info->beacon_silence_rssi_b) & IN_BAND_FILTER; + bcn_silence_c = + le32_to_cpu(rx_info->beacon_silence_rssi_c) & IN_BAND_FILTER; + + if (bcn_silence_a) { + total_silence += bcn_silence_a; + num_active_rx++; + } + if (bcn_silence_b) { + total_silence += bcn_silence_b; + num_active_rx++; + } + if (bcn_silence_c) { + total_silence += bcn_silence_c; + num_active_rx++; + } + + /* Average among active antennas */ + if (num_active_rx) + last_rx_noise = (total_silence / num_active_rx) - 107; + else + last_rx_noise = IWL_NOISE_MEAS_NOT_AVAILABLE; + + IWL_DEBUG_CALIB(priv, "inband silence a %u, b %u, c %u, dBm %d\n", + bcn_silence_a, bcn_silence_b, bcn_silence_c, + last_rx_noise); +} + +#ifdef CPTCFG_IWLWIFI_DEBUGFS +/* + * based on the assumption of all statistics counter are in DWORD + * FIXME: This function is for debugging, do not deal with + * the case of counters roll-over. + */ +static void accum_stats(__le32 *prev, __le32 *cur, __le32 *delta, + __le32 *max_delta, __le32 *accum, int size) +{ + int i; + + for (i = 0; + i < size / sizeof(__le32); + i++, prev++, cur++, delta++, max_delta++, accum++) { + if (le32_to_cpu(*cur) > le32_to_cpu(*prev)) { + *delta = cpu_to_le32( + le32_to_cpu(*cur) - le32_to_cpu(*prev)); + le32_add_cpu(accum, le32_to_cpu(*delta)); + if (le32_to_cpu(*delta) > le32_to_cpu(*max_delta)) + *max_delta = *delta; + } + } +} + +static void +iwlagn_accumulative_statistics(struct iwl_priv *priv, + struct statistics_general_common *common, + struct statistics_rx_non_phy *rx_non_phy, + struct statistics_rx_phy *rx_ofdm, + struct statistics_rx_ht_phy *rx_ofdm_ht, + struct statistics_rx_phy *rx_cck, + struct statistics_tx *tx, + struct statistics_bt_activity *bt_activity) +{ +#define ACCUM(_name) \ + accum_stats((__le32 *)&priv->statistics._name, \ + (__le32 *)_name, \ + (__le32 *)&priv->delta_stats._name, \ + (__le32 *)&priv->max_delta_stats._name, \ + (__le32 *)&priv->accum_stats._name, \ + sizeof(*_name)); + + ACCUM(common); + ACCUM(rx_non_phy); + ACCUM(rx_ofdm); + ACCUM(rx_ofdm_ht); + ACCUM(rx_cck); + ACCUM(tx); + if (bt_activity) + ACCUM(bt_activity); +#undef ACCUM +} +#else +static inline void +iwlagn_accumulative_statistics(struct iwl_priv *priv, + struct statistics_general_common *common, + struct statistics_rx_non_phy *rx_non_phy, + struct statistics_rx_phy *rx_ofdm, + struct statistics_rx_ht_phy *rx_ofdm_ht, + struct statistics_rx_phy *rx_cck, + struct statistics_tx *tx, + struct statistics_bt_activity *bt_activity) +{ +} +#endif + +static int iwlagn_rx_statistics(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + unsigned long stamp = jiffies; + const int reg_recalib_period = 60; + int change; + struct iwl_rx_packet *pkt = rxb_addr(rxb); + u32 len = le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; + __le32 *flag; + struct statistics_general_common *common; + struct statistics_rx_non_phy *rx_non_phy; + struct statistics_rx_phy *rx_ofdm; + struct statistics_rx_ht_phy *rx_ofdm_ht; + struct statistics_rx_phy *rx_cck; + struct statistics_tx *tx; + struct statistics_bt_activity *bt_activity; + + len -= sizeof(struct iwl_cmd_header); /* skip header */ + + IWL_DEBUG_RX(priv, "Statistics notification received (%d bytes).\n", + len); + + spin_lock(&priv->statistics.lock); + + if (len == sizeof(struct iwl_bt_notif_statistics)) { + struct iwl_bt_notif_statistics *stats; + stats = (void *)&pkt->data; + flag = &stats->flag; + common = &stats->general.common; + rx_non_phy = &stats->rx.general.common; + rx_ofdm = &stats->rx.ofdm; + rx_ofdm_ht = &stats->rx.ofdm_ht; + rx_cck = &stats->rx.cck; + tx = &stats->tx; + bt_activity = &stats->general.activity; + +#ifdef CPTCFG_IWLWIFI_DEBUGFS + /* handle this exception directly */ + priv->statistics.num_bt_kills = stats->rx.general.num_bt_kills; + le32_add_cpu(&priv->statistics.accum_num_bt_kills, + le32_to_cpu(stats->rx.general.num_bt_kills)); +#endif + } else if (len == sizeof(struct iwl_notif_statistics)) { + struct iwl_notif_statistics *stats; + stats = (void *)&pkt->data; + flag = &stats->flag; + common = &stats->general.common; + rx_non_phy = &stats->rx.general; + rx_ofdm = &stats->rx.ofdm; + rx_ofdm_ht = &stats->rx.ofdm_ht; + rx_cck = &stats->rx.cck; + tx = &stats->tx; + bt_activity = NULL; + } else { + WARN_ONCE(1, "len %d doesn't match BT (%zu) or normal (%zu)\n", + len, sizeof(struct iwl_bt_notif_statistics), + sizeof(struct iwl_notif_statistics)); + spin_unlock(&priv->statistics.lock); + return 0; + } + + change = common->temperature != priv->statistics.common.temperature || + (*flag & STATISTICS_REPLY_FLG_HT40_MODE_MSK) != + (priv->statistics.flag & STATISTICS_REPLY_FLG_HT40_MODE_MSK); + + iwlagn_accumulative_statistics(priv, common, rx_non_phy, rx_ofdm, + rx_ofdm_ht, rx_cck, tx, bt_activity); + + iwlagn_recover_from_statistics(priv, rx_ofdm, rx_ofdm_ht, tx, stamp); + + priv->statistics.flag = *flag; + memcpy(&priv->statistics.common, common, sizeof(*common)); + memcpy(&priv->statistics.rx_non_phy, rx_non_phy, sizeof(*rx_non_phy)); + memcpy(&priv->statistics.rx_ofdm, rx_ofdm, sizeof(*rx_ofdm)); + memcpy(&priv->statistics.rx_ofdm_ht, rx_ofdm_ht, sizeof(*rx_ofdm_ht)); + memcpy(&priv->statistics.rx_cck, rx_cck, sizeof(*rx_cck)); + memcpy(&priv->statistics.tx, tx, sizeof(*tx)); +#ifdef CPTCFG_IWLWIFI_DEBUGFS + if (bt_activity) + memcpy(&priv->statistics.bt_activity, bt_activity, + sizeof(*bt_activity)); +#endif + + priv->rx_statistics_jiffies = stamp; + + set_bit(STATUS_STATISTICS, &priv->status); + + /* Reschedule the statistics timer to occur in + * reg_recalib_period seconds to ensure we get a + * thermal update even if the uCode doesn't give + * us one */ + mod_timer(&priv->statistics_periodic, jiffies + + msecs_to_jiffies(reg_recalib_period * 1000)); + + if (unlikely(!test_bit(STATUS_SCANNING, &priv->status)) && + (pkt->hdr.cmd == STATISTICS_NOTIFICATION)) { + iwlagn_rx_calc_noise(priv); + queue_work(priv->workqueue, &priv->run_time_calib_work); + } + if (priv->lib->temperature && change) + priv->lib->temperature(priv); + + spin_unlock(&priv->statistics.lock); + + return 0; +} + +static int iwlagn_rx_reply_statistics(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_notif_statistics *stats = (void *)pkt->data; + + if (le32_to_cpu(stats->flag) & UCODE_STATISTICS_CLEAR_MSK) { +#ifdef CPTCFG_IWLWIFI_DEBUGFS + memset(&priv->accum_stats, 0, + sizeof(priv->accum_stats)); + memset(&priv->delta_stats, 0, + sizeof(priv->delta_stats)); + memset(&priv->max_delta_stats, 0, + sizeof(priv->max_delta_stats)); +#endif + IWL_DEBUG_RX(priv, "Statistics have been cleared\n"); + } + iwlagn_rx_statistics(priv, rxb, cmd); + return 0; +} + +/* Handle notification from uCode that card's power state is changing + * due to software, hardware, or critical temperature RFKILL */ +static int iwlagn_rx_card_state_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_card_state_notif *card_state_notif = (void *)pkt->data; + u32 flags = le32_to_cpu(card_state_notif->flags); + unsigned long status = priv->status; + + IWL_DEBUG_RF_KILL(priv, "Card state received: HW:%s SW:%s CT:%s\n", + (flags & HW_CARD_DISABLED) ? "Kill" : "On", + (flags & SW_CARD_DISABLED) ? "Kill" : "On", + (flags & CT_CARD_DISABLED) ? + "Reached" : "Not reached"); + + if (flags & (SW_CARD_DISABLED | HW_CARD_DISABLED | + CT_CARD_DISABLED)) { + + iwl_write32(priv->trans, CSR_UCODE_DRV_GP1_SET, + CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); + + iwl_write_direct32(priv->trans, HBUS_TARG_MBX_C, + HBUS_TARG_MBX_C_REG_BIT_CMD_BLOCKED); + + if (!(flags & RXON_CARD_DISABLED)) { + iwl_write32(priv->trans, CSR_UCODE_DRV_GP1_CLR, + CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); + iwl_write_direct32(priv->trans, HBUS_TARG_MBX_C, + HBUS_TARG_MBX_C_REG_BIT_CMD_BLOCKED); + } + if (flags & CT_CARD_DISABLED) + iwl_tt_enter_ct_kill(priv); + } + if (!(flags & CT_CARD_DISABLED)) + iwl_tt_exit_ct_kill(priv); + + if (flags & HW_CARD_DISABLED) + set_bit(STATUS_RF_KILL_HW, &priv->status); + else + clear_bit(STATUS_RF_KILL_HW, &priv->status); + + + if (!(flags & RXON_CARD_DISABLED)) + iwl_scan_cancel(priv); + + if ((test_bit(STATUS_RF_KILL_HW, &status) != + test_bit(STATUS_RF_KILL_HW, &priv->status))) + wiphy_rfkill_set_hw_state(priv->hw->wiphy, + test_bit(STATUS_RF_KILL_HW, &priv->status)); + return 0; +} + +static int iwlagn_rx_missed_beacon_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) + +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_missed_beacon_notif *missed_beacon = (void *)pkt->data; + + if (le32_to_cpu(missed_beacon->consecutive_missed_beacons) > + priv->missed_beacon_threshold) { + IWL_DEBUG_CALIB(priv, + "missed bcn cnsq %d totl %d rcd %d expctd %d\n", + le32_to_cpu(missed_beacon->consecutive_missed_beacons), + le32_to_cpu(missed_beacon->total_missed_becons), + le32_to_cpu(missed_beacon->num_recvd_beacons), + le32_to_cpu(missed_beacon->num_expected_beacons)); + if (!test_bit(STATUS_SCANNING, &priv->status)) + iwl_init_sensitivity(priv); + } + return 0; +} + +/* Cache phy data (Rx signal strength, etc) for HT frame (REPLY_RX_PHY_CMD). + * This will be used later in iwl_rx_reply_rx() for REPLY_RX_MPDU_CMD. */ +static int iwlagn_rx_reply_rx_phy(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + + priv->last_phy_res_valid = true; + priv->ampdu_ref++; + memcpy(&priv->last_phy_res, pkt->data, + sizeof(struct iwl_rx_phy_res)); + return 0; +} + +/* + * returns non-zero if packet should be dropped + */ +static int iwlagn_set_decrypted_flag(struct iwl_priv *priv, + struct ieee80211_hdr *hdr, + u32 decrypt_res, + struct ieee80211_rx_status *stats) +{ + u16 fc = le16_to_cpu(hdr->frame_control); + + /* + * All contexts have the same setting here due to it being + * a module parameter, so OK to check any context. + */ + if (priv->contexts[IWL_RXON_CTX_BSS].active.filter_flags & + RXON_FILTER_DIS_DECRYPT_MSK) + return 0; + + if (!(fc & IEEE80211_FCTL_PROTECTED)) + return 0; + + IWL_DEBUG_RX(priv, "decrypt_res:0x%x\n", decrypt_res); + switch (decrypt_res & RX_RES_STATUS_SEC_TYPE_MSK) { + case RX_RES_STATUS_SEC_TYPE_TKIP: + /* The uCode has got a bad phase 1 Key, pushes the packet. + * Decryption will be done in SW. */ + if ((decrypt_res & RX_RES_STATUS_DECRYPT_TYPE_MSK) == + RX_RES_STATUS_BAD_KEY_TTAK) + break; + + case RX_RES_STATUS_SEC_TYPE_WEP: + if ((decrypt_res & RX_RES_STATUS_DECRYPT_TYPE_MSK) == + RX_RES_STATUS_BAD_ICV_MIC) { + /* bad ICV, the packet is destroyed since the + * decryption is inplace, drop it */ + IWL_DEBUG_RX(priv, "Packet destroyed\n"); + return -1; + } + case RX_RES_STATUS_SEC_TYPE_CCMP: + if ((decrypt_res & RX_RES_STATUS_DECRYPT_TYPE_MSK) == + RX_RES_STATUS_DECRYPT_OK) { + IWL_DEBUG_RX(priv, "hw decrypt successfully!!!\n"); + stats->flag |= RX_FLAG_DECRYPTED; + } + break; + + default: + break; + } + return 0; +} + +static void iwlagn_pass_packet_to_mac80211(struct iwl_priv *priv, + struct ieee80211_hdr *hdr, + u16 len, + u32 ampdu_status, + struct iwl_rx_cmd_buffer *rxb, + struct ieee80211_rx_status *stats) +{ + struct sk_buff *skb; + __le16 fc = hdr->frame_control; + struct iwl_rxon_context *ctx; + unsigned int hdrlen, fraglen; + + /* We only process data packets if the interface is open */ + if (unlikely(!priv->is_open)) { + IWL_DEBUG_DROP_LIMIT(priv, + "Dropping packet while interface is not open.\n"); + return; + } + + /* In case of HW accelerated crypto and bad decryption, drop */ + if (!iwlwifi_mod_params.sw_crypto && + iwlagn_set_decrypted_flag(priv, hdr, ampdu_status, stats)) + return; + + /* Dont use dev_alloc_skb(), we'll have enough headroom once + * ieee80211_hdr pulled. + */ + skb = alloc_skb(128, GFP_ATOMIC); + if (!skb) { + IWL_ERR(priv, "alloc_skb failed\n"); + return; + } + /* If frame is small enough to fit in skb->head, pull it completely. + * If not, only pull ieee80211_hdr so that splice() or TCP coalesce + * are more efficient. + */ + hdrlen = (len <= skb_tailroom(skb)) ? len : sizeof(*hdr); + + memcpy(skb_put(skb, hdrlen), hdr, hdrlen); + fraglen = len - hdrlen; + + if (fraglen) { + int offset = (void *)hdr + hdrlen - + rxb_addr(rxb) + rxb_offset(rxb); + + skb_add_rx_frag(skb, 0, rxb_steal_page(rxb), offset, + fraglen, rxb->truesize); + } + + /* + * Wake any queues that were stopped due to a passive channel tx + * failure. This can happen because the regulatory enforcement in + * the device waits for a beacon before allowing transmission, + * sometimes even after already having transmitted frames for the + * association because the new RXON may reset the information. + */ + if (unlikely(ieee80211_is_beacon(fc) && priv->passive_no_rx)) { + for_each_context(priv, ctx) { + if (!ether_addr_equal(hdr->addr3, + ctx->active.bssid_addr)) + continue; + iwlagn_lift_passive_no_rx(priv); + } + } + + memcpy(IEEE80211_SKB_RXCB(skb), stats, sizeof(*stats)); + + ieee80211_rx_ni(priv->hw, skb); +} + +static u32 iwlagn_translate_rx_status(struct iwl_priv *priv, u32 decrypt_in) +{ + u32 decrypt_out = 0; + + if ((decrypt_in & RX_RES_STATUS_STATION_FOUND) == + RX_RES_STATUS_STATION_FOUND) + decrypt_out |= (RX_RES_STATUS_STATION_FOUND | + RX_RES_STATUS_NO_STATION_INFO_MISMATCH); + + decrypt_out |= (decrypt_in & RX_RES_STATUS_SEC_TYPE_MSK); + + /* packet was not encrypted */ + if ((decrypt_in & RX_RES_STATUS_SEC_TYPE_MSK) == + RX_RES_STATUS_SEC_TYPE_NONE) + return decrypt_out; + + /* packet was encrypted with unknown alg */ + if ((decrypt_in & RX_RES_STATUS_SEC_TYPE_MSK) == + RX_RES_STATUS_SEC_TYPE_ERR) + return decrypt_out; + + /* decryption was not done in HW */ + if ((decrypt_in & RX_MPDU_RES_STATUS_DEC_DONE_MSK) != + RX_MPDU_RES_STATUS_DEC_DONE_MSK) + return decrypt_out; + + switch (decrypt_in & RX_RES_STATUS_SEC_TYPE_MSK) { + + case RX_RES_STATUS_SEC_TYPE_CCMP: + /* alg is CCM: check MIC only */ + if (!(decrypt_in & RX_MPDU_RES_STATUS_MIC_OK)) + /* Bad MIC */ + decrypt_out |= RX_RES_STATUS_BAD_ICV_MIC; + else + decrypt_out |= RX_RES_STATUS_DECRYPT_OK; + + break; + + case RX_RES_STATUS_SEC_TYPE_TKIP: + if (!(decrypt_in & RX_MPDU_RES_STATUS_TTAK_OK)) { + /* Bad TTAK */ + decrypt_out |= RX_RES_STATUS_BAD_KEY_TTAK; + break; + } + /* fall through if TTAK OK */ + default: + if (!(decrypt_in & RX_MPDU_RES_STATUS_ICV_OK)) + decrypt_out |= RX_RES_STATUS_BAD_ICV_MIC; + else + decrypt_out |= RX_RES_STATUS_DECRYPT_OK; + break; + } + + IWL_DEBUG_RX(priv, "decrypt_in:0x%x decrypt_out = 0x%x\n", + decrypt_in, decrypt_out); + + return decrypt_out; +} + +/* Calc max signal level (dBm) among 3 possible receivers */ +static int iwlagn_calc_rssi(struct iwl_priv *priv, + struct iwl_rx_phy_res *rx_resp) +{ + /* data from PHY/DSP regarding signal strength, etc., + * contents are always there, not configurable by host + */ + struct iwlagn_non_cfg_phy *ncphy = + (struct iwlagn_non_cfg_phy *)rx_resp->non_cfg_phy_buf; + u32 val, rssi_a, rssi_b, rssi_c, max_rssi; + u8 agc; + + val = le32_to_cpu(ncphy->non_cfg_phy[IWLAGN_RX_RES_AGC_IDX]); + agc = (val & IWLAGN_OFDM_AGC_MSK) >> IWLAGN_OFDM_AGC_BIT_POS; + + /* Find max rssi among 3 possible receivers. + * These values are measured by the digital signal processor (DSP). + * They should stay fairly constant even as the signal strength varies, + * if the radio's automatic gain control (AGC) is working right. + * AGC value (see below) will provide the "interesting" info. + */ + val = le32_to_cpu(ncphy->non_cfg_phy[IWLAGN_RX_RES_RSSI_AB_IDX]); + rssi_a = (val & IWLAGN_OFDM_RSSI_INBAND_A_BITMSK) >> + IWLAGN_OFDM_RSSI_A_BIT_POS; + rssi_b = (val & IWLAGN_OFDM_RSSI_INBAND_B_BITMSK) >> + IWLAGN_OFDM_RSSI_B_BIT_POS; + val = le32_to_cpu(ncphy->non_cfg_phy[IWLAGN_RX_RES_RSSI_C_IDX]); + rssi_c = (val & IWLAGN_OFDM_RSSI_INBAND_C_BITMSK) >> + IWLAGN_OFDM_RSSI_C_BIT_POS; + + max_rssi = max_t(u32, rssi_a, rssi_b); + max_rssi = max_t(u32, max_rssi, rssi_c); + + IWL_DEBUG_STATS(priv, "Rssi In A %d B %d C %d Max %d AGC dB %d\n", + rssi_a, rssi_b, rssi_c, max_rssi, agc); + + /* dBm = max_rssi dB - agc dB - constant. + * Higher AGC (higher radio gain) means lower signal. */ + return max_rssi - agc - IWLAGN_RSSI_OFFSET; +} + +/* Called for REPLY_RX_MPDU_CMD */ +static int iwlagn_rx_reply_rx(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct ieee80211_hdr *header; + struct ieee80211_rx_status rx_status = {}; + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_rx_phy_res *phy_res; + __le32 rx_pkt_status; + struct iwl_rx_mpdu_res_start *amsdu; + u32 len; + u32 ampdu_status; + u32 rate_n_flags; + + if (!priv->last_phy_res_valid) { + IWL_ERR(priv, "MPDU frame without cached PHY data\n"); + return 0; + } + phy_res = &priv->last_phy_res; + amsdu = (struct iwl_rx_mpdu_res_start *)pkt->data; + header = (struct ieee80211_hdr *)(pkt->data + sizeof(*amsdu)); + len = le16_to_cpu(amsdu->byte_count); + rx_pkt_status = *(__le32 *)(pkt->data + sizeof(*amsdu) + len); + ampdu_status = iwlagn_translate_rx_status(priv, + le32_to_cpu(rx_pkt_status)); + + if ((unlikely(phy_res->cfg_phy_cnt > 20))) { + IWL_DEBUG_DROP(priv, "dsp size out of range [0,20]: %d\n", + phy_res->cfg_phy_cnt); + return 0; + } + + if (!(rx_pkt_status & RX_RES_STATUS_NO_CRC32_ERROR) || + !(rx_pkt_status & RX_RES_STATUS_NO_RXE_OVERFLOW)) { + IWL_DEBUG_RX(priv, "Bad CRC or FIFO: 0x%08X.\n", + le32_to_cpu(rx_pkt_status)); + return 0; + } + + /* This will be used in several places later */ + rate_n_flags = le32_to_cpu(phy_res->rate_n_flags); + + /* rx_status carries information about the packet to mac80211 */ + rx_status.mactime = le64_to_cpu(phy_res->timestamp); + rx_status.band = (phy_res->phy_flags & RX_RES_PHY_FLAGS_BAND_24_MSK) ? + IEEE80211_BAND_2GHZ : IEEE80211_BAND_5GHZ; + rx_status.freq = + ieee80211_channel_to_frequency(le16_to_cpu(phy_res->channel), + rx_status.band); + rx_status.rate_idx = + iwlagn_hwrate_to_mac80211_idx(rate_n_flags, rx_status.band); + rx_status.flag = 0; + + /* TSF isn't reliable. In order to allow smooth user experience, + * this W/A doesn't propagate it to the mac80211 */ + /*rx_status.flag |= RX_FLAG_MACTIME_START;*/ + + priv->ucode_beacon_time = le32_to_cpu(phy_res->beacon_time_stamp); + + /* Find max signal strength (dBm) among 3 antenna/receiver chains */ + rx_status.signal = iwlagn_calc_rssi(priv, phy_res); + + IWL_DEBUG_STATS_LIMIT(priv, "Rssi %d, TSF %llu\n", + rx_status.signal, (unsigned long long)rx_status.mactime); + + /* + * "antenna number" + * + * It seems that the antenna field in the phy flags value + * is actually a bit field. This is undefined by radiotap, + * it wants an actual antenna number but I always get "7" + * for most legacy frames I receive indicating that the + * same frame was received on all three RX chains. + * + * I think this field should be removed in favor of a + * new 802.11n radiotap field "RX chains" that is defined + * as a bitmask. + */ + rx_status.antenna = + (le16_to_cpu(phy_res->phy_flags) & RX_RES_PHY_FLAGS_ANTENNA_MSK) + >> RX_RES_PHY_FLAGS_ANTENNA_POS; + + /* set the preamble flag if appropriate */ + if (phy_res->phy_flags & RX_RES_PHY_FLAGS_SHORT_PREAMBLE_MSK) + rx_status.flag |= RX_FLAG_SHORTPRE; + + if (phy_res->phy_flags & RX_RES_PHY_FLAGS_AGG_MSK) { + /* + * We know which subframes of an A-MPDU belong + * together since we get a single PHY response + * from the firmware for all of them + */ + rx_status.flag |= RX_FLAG_AMPDU_DETAILS; + rx_status.ampdu_reference = priv->ampdu_ref; + } + + /* Set up the HT phy flags */ + if (rate_n_flags & RATE_MCS_HT_MSK) + rx_status.flag |= RX_FLAG_HT; + if (rate_n_flags & RATE_MCS_HT40_MSK) + rx_status.flag |= RX_FLAG_40MHZ; + if (rate_n_flags & RATE_MCS_SGI_MSK) + rx_status.flag |= RX_FLAG_SHORT_GI; + if (rate_n_flags & RATE_MCS_GF_MSK) + rx_status.flag |= RX_FLAG_HT_GF; + + iwlagn_pass_packet_to_mac80211(priv, header, len, ampdu_status, + rxb, &rx_status); + return 0; +} + +static int iwlagn_rx_noa_notification(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_wipan_noa_data *new_data, *old_data; + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_wipan_noa_notification *noa_notif = (void *)pkt->data; + + /* no condition -- we're in softirq */ + old_data = rcu_dereference_protected(priv->noa_data, true); + + if (noa_notif->noa_active) { + u32 len = le16_to_cpu(noa_notif->noa_attribute.length); + u32 copylen = len; + + /* EID, len, OUI, subtype */ + len += 1 + 1 + 3 + 1; + /* P2P id, P2P length */ + len += 1 + 2; + copylen += 1 + 2; + + new_data = kmalloc(sizeof(*new_data) + len, GFP_ATOMIC); + if (new_data) { + new_data->length = len; + new_data->data[0] = WLAN_EID_VENDOR_SPECIFIC; + new_data->data[1] = len - 2; /* not counting EID, len */ + new_data->data[2] = (WLAN_OUI_WFA >> 16) & 0xff; + new_data->data[3] = (WLAN_OUI_WFA >> 8) & 0xff; + new_data->data[4] = (WLAN_OUI_WFA >> 0) & 0xff; + new_data->data[5] = WLAN_OUI_TYPE_WFA_P2P; + memcpy(&new_data->data[6], &noa_notif->noa_attribute, + copylen); + } + } else + new_data = NULL; + + rcu_assign_pointer(priv->noa_data, new_data); + + if (old_data) + kfree_rcu(old_data, rcu_head); + + return 0; +} + +/** + * iwl_setup_rx_handlers - Initialize Rx handler callbacks + * + * Setup the RX handlers for each of the reply types sent from the uCode + * to the host. + */ +void iwl_setup_rx_handlers(struct iwl_priv *priv) +{ + int (**handlers)(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); + + handlers = priv->rx_handlers; + + handlers[REPLY_ERROR] = iwlagn_rx_reply_error; + handlers[CHANNEL_SWITCH_NOTIFICATION] = iwlagn_rx_csa; + handlers[SPECTRUM_MEASURE_NOTIFICATION] = + iwlagn_rx_spectrum_measure_notif; + handlers[PM_SLEEP_NOTIFICATION] = iwlagn_rx_pm_sleep_notif; + handlers[PM_DEBUG_STATISTIC_NOTIFIC] = + iwlagn_rx_pm_debug_statistics_notif; + handlers[BEACON_NOTIFICATION] = iwlagn_rx_beacon_notif; + handlers[REPLY_ADD_STA] = iwl_add_sta_callback; + + handlers[REPLY_WIPAN_NOA_NOTIFICATION] = iwlagn_rx_noa_notification; + + /* + * The same handler is used for both the REPLY to a discrete + * statistics request from the host as well as for the periodic + * statistics notifications (after received beacons) from the uCode. + */ + handlers[REPLY_STATISTICS_CMD] = iwlagn_rx_reply_statistics; + handlers[STATISTICS_NOTIFICATION] = iwlagn_rx_statistics; + + iwl_setup_rx_scan_handlers(priv); + + handlers[CARD_STATE_NOTIFICATION] = iwlagn_rx_card_state_notif; + handlers[MISSED_BEACONS_NOTIFICATION] = + iwlagn_rx_missed_beacon_notif; + + /* Rx handlers */ + handlers[REPLY_RX_PHY_CMD] = iwlagn_rx_reply_rx_phy; + handlers[REPLY_RX_MPDU_CMD] = iwlagn_rx_reply_rx; + + /* block ack */ + handlers[REPLY_COMPRESSED_BA] = + iwlagn_rx_reply_compressed_ba; + + priv->rx_handlers[REPLY_TX] = iwlagn_rx_reply_tx; + + /* set up notification wait support */ + iwl_notification_wait_init(&priv->notif_wait); + + /* Set up BT Rx handlers */ + if (priv->cfg->bt_params) + iwlagn_bt_rx_handler_setup(priv); +} + +int iwl_rx_dispatch(struct iwl_op_mode *op_mode, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + int err = 0; + + /* + * Do the notification wait before RX handlers so + * even if the RX handler consumes the RXB we have + * access to it in the notification wait entry. + */ + iwl_notification_wait_notify(&priv->notif_wait, pkt); + +#ifdef CPTCFG_IWLWIFI_DEVICE_TESTMODE + /* + * RX data may be forwarded to userspace in one + * of two cases: the user owns the fw through testmode or when + * the user requested to monitor the rx w/o affecting the regular flow. + * In these cases the iwl_test object will handle forwarding the rx + * data to user space. + * Note that if the ownership flag != IWL_OWNERSHIP_TM the flow + * continues. + */ + iwl_test_rx(&priv->tst, rxb); +#endif + + if (priv->ucode_owner != IWL_OWNERSHIP_TM) { + /* Based on type of command response or notification, + * handle those that need handling via function in + * rx_handlers table. See iwl_setup_rx_handlers() */ + if (priv->rx_handlers[pkt->hdr.cmd]) { + priv->rx_handlers_stats[pkt->hdr.cmd]++; + err = priv->rx_handlers[pkt->hdr.cmd] (priv, rxb, cmd); + } else { + /* No handling needed */ + IWL_DEBUG_RX(priv, "No handler needed for %s, 0x%02x\n", + iwl_dvm_get_cmd_string(pkt->hdr.cmd), + pkt->hdr.cmd); + } + } + return err; +} diff --git a/drivers/net/wireless/iwlwifi/dvm/rxon.c b/drivers/net/wireless/iwlwifi/dvm/rxon.c new file mode 100644 index 0000000..4d2c45e --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/rxon.c @@ -0,0 +1,1575 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#include +#include "iwl-trans.h" +#include "iwl-modparams.h" +#include "dev.h" +#include "agn.h" +#include "calib.h" + +/* + * initialize rxon structure with default values from eeprom + */ +void iwl_connection_init_rx_config(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + memset(&ctx->staging, 0, sizeof(ctx->staging)); + + if (!ctx->vif) { + ctx->staging.dev_type = ctx->unused_devtype; + } else + switch (ctx->vif->type) { + case NL80211_IFTYPE_AP: + ctx->staging.dev_type = ctx->ap_devtype; + break; + + case NL80211_IFTYPE_STATION: + ctx->staging.dev_type = ctx->station_devtype; + ctx->staging.filter_flags = RXON_FILTER_ACCEPT_GRP_MSK; + break; + + case NL80211_IFTYPE_ADHOC: + ctx->staging.dev_type = ctx->ibss_devtype; + ctx->staging.flags = RXON_FLG_SHORT_PREAMBLE_MSK; + ctx->staging.filter_flags = RXON_FILTER_BCON_AWARE_MSK | + RXON_FILTER_ACCEPT_GRP_MSK; + break; + + case NL80211_IFTYPE_MONITOR: + ctx->staging.dev_type = RXON_DEV_TYPE_SNIFFER; + break; + + default: + IWL_ERR(priv, "Unsupported interface type %d\n", + ctx->vif->type); + break; + } + +#if 0 + /* TODO: Figure out when short_preamble would be set and cache from + * that */ + if (!hw_to_local(priv->hw)->short_preamble) + ctx->staging.flags &= ~RXON_FLG_SHORT_PREAMBLE_MSK; + else + ctx->staging.flags |= RXON_FLG_SHORT_PREAMBLE_MSK; +#endif + + ctx->staging.channel = + cpu_to_le16(priv->hw->conf.chandef.chan->hw_value); + priv->band = priv->hw->conf.chandef.chan->band; + + iwl_set_flags_for_band(priv, ctx, priv->band, ctx->vif); + + /* clear both MIX and PURE40 mode flag */ + ctx->staging.flags &= ~(RXON_FLG_CHANNEL_MODE_MIXED | + RXON_FLG_CHANNEL_MODE_PURE_40); + if (ctx->vif) + memcpy(ctx->staging.node_addr, ctx->vif->addr, ETH_ALEN); + + ctx->staging.ofdm_ht_single_stream_basic_rates = 0xff; + ctx->staging.ofdm_ht_dual_stream_basic_rates = 0xff; + ctx->staging.ofdm_ht_triple_stream_basic_rates = 0xff; +} + +static int iwlagn_disable_bss(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct iwl_rxon_cmd *send) +{ + __le32 old_filter = send->filter_flags; + int ret; + + send->filter_flags &= ~RXON_FILTER_ASSOC_MSK; + ret = iwl_dvm_send_cmd_pdu(priv, ctx->rxon_cmd, + CMD_SYNC, sizeof(*send), send); + + send->filter_flags = old_filter; + + if (ret) + IWL_DEBUG_QUIET_RFKILL(priv, + "Error clearing ASSOC_MSK on BSS (%d)\n", ret); + + return ret; +} + +static int iwlagn_disable_pan(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct iwl_rxon_cmd *send) +{ + struct iwl_notification_wait disable_wait; + __le32 old_filter = send->filter_flags; + u8 old_dev_type = send->dev_type; + int ret; + static const u8 deactivate_cmd[] = { + REPLY_WIPAN_DEACTIVATION_COMPLETE + }; + + iwl_init_notification_wait(&priv->notif_wait, &disable_wait, + deactivate_cmd, ARRAY_SIZE(deactivate_cmd), + NULL, NULL); + + send->filter_flags &= ~RXON_FILTER_ASSOC_MSK; + send->dev_type = RXON_DEV_TYPE_P2P; + ret = iwl_dvm_send_cmd_pdu(priv, ctx->rxon_cmd, + CMD_SYNC, sizeof(*send), send); + + send->filter_flags = old_filter; + send->dev_type = old_dev_type; + + if (ret) { + IWL_ERR(priv, "Error disabling PAN (%d)\n", ret); + iwl_remove_notification(&priv->notif_wait, &disable_wait); + } else { + ret = iwl_wait_notification(&priv->notif_wait, + &disable_wait, HZ); + if (ret) + IWL_ERR(priv, "Timed out waiting for PAN disable\n"); + } + + return ret; +} + +static int iwlagn_disconn_pan(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct iwl_rxon_cmd *send) +{ + __le32 old_filter = send->filter_flags; + int ret; + + send->filter_flags &= ~RXON_FILTER_ASSOC_MSK; + ret = iwl_dvm_send_cmd_pdu(priv, ctx->rxon_cmd, CMD_SYNC, + sizeof(*send), send); + + send->filter_flags = old_filter; + + return ret; +} + +static void iwlagn_update_qos(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + int ret; + + if (!ctx->is_active) + return; + + ctx->qos_data.def_qos_parm.qos_flags = 0; + + if (ctx->qos_data.qos_active) + ctx->qos_data.def_qos_parm.qos_flags |= + QOS_PARAM_FLG_UPDATE_EDCA_MSK; + + if (ctx->ht.enabled) + ctx->qos_data.def_qos_parm.qos_flags |= QOS_PARAM_FLG_TGN_MSK; + + IWL_DEBUG_INFO(priv, "send QoS cmd with Qos active=%d FLAGS=0x%X\n", + ctx->qos_data.qos_active, + ctx->qos_data.def_qos_parm.qos_flags); + + ret = iwl_dvm_send_cmd_pdu(priv, ctx->qos_cmd, CMD_SYNC, + sizeof(struct iwl_qosparam_cmd), + &ctx->qos_data.def_qos_parm); + if (ret) + IWL_DEBUG_QUIET_RFKILL(priv, "Failed to update QoS\n"); +} + +static int iwlagn_update_beacon(struct iwl_priv *priv, + struct ieee80211_vif *vif) +{ + lockdep_assert_held(&priv->mutex); + + dev_kfree_skb(priv->beacon_skb); + priv->beacon_skb = ieee80211_beacon_get(priv->hw, vif); + if (!priv->beacon_skb) + return -ENOMEM; + return iwlagn_send_beacon_cmd(priv); +} + +static int iwlagn_send_rxon_assoc(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + int ret = 0; + struct iwl_rxon_assoc_cmd rxon_assoc; + const struct iwl_rxon_cmd *rxon1 = &ctx->staging; + const struct iwl_rxon_cmd *rxon2 = &ctx->active; + + if ((rxon1->flags == rxon2->flags) && + (rxon1->filter_flags == rxon2->filter_flags) && + (rxon1->cck_basic_rates == rxon2->cck_basic_rates) && + (rxon1->ofdm_ht_single_stream_basic_rates == + rxon2->ofdm_ht_single_stream_basic_rates) && + (rxon1->ofdm_ht_dual_stream_basic_rates == + rxon2->ofdm_ht_dual_stream_basic_rates) && + (rxon1->ofdm_ht_triple_stream_basic_rates == + rxon2->ofdm_ht_triple_stream_basic_rates) && + (rxon1->acquisition_data == rxon2->acquisition_data) && + (rxon1->rx_chain == rxon2->rx_chain) && + (rxon1->ofdm_basic_rates == rxon2->ofdm_basic_rates)) { + IWL_DEBUG_INFO(priv, "Using current RXON_ASSOC. Not resending.\n"); + return 0; + } + + rxon_assoc.flags = ctx->staging.flags; + rxon_assoc.filter_flags = ctx->staging.filter_flags; + rxon_assoc.ofdm_basic_rates = ctx->staging.ofdm_basic_rates; + rxon_assoc.cck_basic_rates = ctx->staging.cck_basic_rates; + rxon_assoc.reserved1 = 0; + rxon_assoc.reserved2 = 0; + rxon_assoc.reserved3 = 0; + rxon_assoc.ofdm_ht_single_stream_basic_rates = + ctx->staging.ofdm_ht_single_stream_basic_rates; + rxon_assoc.ofdm_ht_dual_stream_basic_rates = + ctx->staging.ofdm_ht_dual_stream_basic_rates; + rxon_assoc.rx_chain_select_flags = ctx->staging.rx_chain; + rxon_assoc.ofdm_ht_triple_stream_basic_rates = + ctx->staging.ofdm_ht_triple_stream_basic_rates; + rxon_assoc.acquisition_data = ctx->staging.acquisition_data; + + ret = iwl_dvm_send_cmd_pdu(priv, ctx->rxon_assoc_cmd, + CMD_ASYNC, sizeof(rxon_assoc), &rxon_assoc); + return ret; +} + +static u16 iwl_adjust_beacon_interval(u16 beacon_val, u16 max_beacon_val) +{ + u16 new_val; + u16 beacon_factor; + + /* + * If mac80211 hasn't given us a beacon interval, program + * the default into the device (not checking this here + * would cause the adjustment below to return the maximum + * value, which may break PAN.) + */ + if (!beacon_val) + return DEFAULT_BEACON_INTERVAL; + + /* + * If the beacon interval we obtained from the peer + * is too large, we'll have to wake up more often + * (and in IBSS case, we'll beacon too much) + * + * For example, if max_beacon_val is 4096, and the + * requested beacon interval is 7000, we'll have to + * use 3500 to be able to wake up on the beacons. + * + * This could badly influence beacon detection stats. + */ + + beacon_factor = (beacon_val + max_beacon_val) / max_beacon_val; + new_val = beacon_val / beacon_factor; + + if (!new_val) + new_val = max_beacon_val; + + return new_val; +} + +static int iwl_send_rxon_timing(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + u64 tsf; + s32 interval_tm, rem; + struct ieee80211_conf *conf = NULL; + u16 beacon_int; + struct ieee80211_vif *vif = ctx->vif; + + conf = &priv->hw->conf; + + lockdep_assert_held(&priv->mutex); + + memset(&ctx->timing, 0, sizeof(struct iwl_rxon_time_cmd)); + + ctx->timing.timestamp = cpu_to_le64(priv->timestamp); + ctx->timing.listen_interval = cpu_to_le16(conf->listen_interval); + + beacon_int = vif ? vif->bss_conf.beacon_int : 0; + + /* + * TODO: For IBSS we need to get atim_window from mac80211, + * for now just always use 0 + */ + ctx->timing.atim_window = 0; + + if (ctx->ctxid == IWL_RXON_CTX_PAN && + (!ctx->vif || ctx->vif->type != NL80211_IFTYPE_STATION) && + iwl_is_associated(priv, IWL_RXON_CTX_BSS) && + priv->contexts[IWL_RXON_CTX_BSS].vif && + priv->contexts[IWL_RXON_CTX_BSS].vif->bss_conf.beacon_int) { + ctx->timing.beacon_interval = + priv->contexts[IWL_RXON_CTX_BSS].timing.beacon_interval; + beacon_int = le16_to_cpu(ctx->timing.beacon_interval); + } else if (ctx->ctxid == IWL_RXON_CTX_BSS && + iwl_is_associated(priv, IWL_RXON_CTX_PAN) && + priv->contexts[IWL_RXON_CTX_PAN].vif && + priv->contexts[IWL_RXON_CTX_PAN].vif->bss_conf.beacon_int && + (!iwl_is_associated_ctx(ctx) || !ctx->vif || + !ctx->vif->bss_conf.beacon_int)) { + ctx->timing.beacon_interval = + priv->contexts[IWL_RXON_CTX_PAN].timing.beacon_interval; + beacon_int = le16_to_cpu(ctx->timing.beacon_interval); + } else { + beacon_int = iwl_adjust_beacon_interval(beacon_int, + IWL_MAX_UCODE_BEACON_INTERVAL * TIME_UNIT); + ctx->timing.beacon_interval = cpu_to_le16(beacon_int); + } + + ctx->beacon_int = beacon_int; + + tsf = priv->timestamp; /* tsf is modifed by do_div: copy it */ + interval_tm = beacon_int * TIME_UNIT; + rem = do_div(tsf, interval_tm); + ctx->timing.beacon_init_val = cpu_to_le32(interval_tm - rem); + + ctx->timing.dtim_period = vif ? (vif->bss_conf.dtim_period ?: 1) : 1; + + IWL_DEBUG_ASSOC(priv, + "beacon interval %d beacon timer %d beacon tim %d\n", + le16_to_cpu(ctx->timing.beacon_interval), + le32_to_cpu(ctx->timing.beacon_init_val), + le16_to_cpu(ctx->timing.atim_window)); + + return iwl_dvm_send_cmd_pdu(priv, ctx->rxon_timing_cmd, + CMD_SYNC, sizeof(ctx->timing), &ctx->timing); +} + +static int iwlagn_rxon_disconn(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + int ret; + struct iwl_rxon_cmd *active = (void *)&ctx->active; + + if (ctx->ctxid == IWL_RXON_CTX_BSS) { + ret = iwlagn_disable_bss(priv, ctx, &ctx->staging); + } else { + ret = iwlagn_disable_pan(priv, ctx, &ctx->staging); + if (ret) + return ret; + if (ctx->vif) { + ret = iwl_send_rxon_timing(priv, ctx); + if (ret) { + IWL_ERR(priv, "Failed to send timing (%d)!\n", ret); + return ret; + } + ret = iwlagn_disconn_pan(priv, ctx, &ctx->staging); + } + } + if (ret) + return ret; + + /* + * Un-assoc RXON clears the station table and WEP + * keys, so we have to restore those afterwards. + */ + iwl_clear_ucode_stations(priv, ctx); + /* update -- might need P2P now */ + iwl_update_bcast_station(priv, ctx); + iwl_restore_stations(priv, ctx); + ret = iwl_restore_default_wep_keys(priv, ctx); + if (ret) { + IWL_ERR(priv, "Failed to restore WEP keys (%d)\n", ret); + return ret; + } + + memcpy(active, &ctx->staging, sizeof(*active)); + return 0; +} + +static int iwl_set_tx_power(struct iwl_priv *priv, s8 tx_power, bool force) +{ + int ret; + s8 prev_tx_power; + bool defer; + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + + if (priv->calib_disabled & IWL_TX_POWER_CALIB_DISABLED) + return 0; + + lockdep_assert_held(&priv->mutex); + + if (priv->tx_power_user_lmt == tx_power && !force) + return 0; + + if (tx_power < IWLAGN_TX_POWER_TARGET_POWER_MIN) { + IWL_WARN(priv, + "Requested user TXPOWER %d below lower limit %d.\n", + tx_power, + IWLAGN_TX_POWER_TARGET_POWER_MIN); + return -EINVAL; + } + + if (tx_power > DIV_ROUND_UP(priv->nvm_data->max_tx_pwr_half_dbm, 2)) { + IWL_WARN(priv, + "Requested user TXPOWER %d above upper limit %d.\n", + tx_power, priv->nvm_data->max_tx_pwr_half_dbm); + return -EINVAL; + } + + if (!iwl_is_ready_rf(priv)) + return -EIO; + + /* scan complete and commit_rxon use tx_power_next value, + * it always need to be updated for newest request */ + priv->tx_power_next = tx_power; + + /* do not set tx power when scanning or channel changing */ + defer = test_bit(STATUS_SCANNING, &priv->status) || + memcmp(&ctx->active, &ctx->staging, sizeof(ctx->staging)); + if (defer && !force) { + IWL_DEBUG_INFO(priv, "Deferring tx power set\n"); + return 0; + } + + prev_tx_power = priv->tx_power_user_lmt; + priv->tx_power_user_lmt = tx_power; + + ret = iwlagn_send_tx_power(priv); + + /* if fail to set tx_power, restore the orig. tx power */ + if (ret) { + priv->tx_power_user_lmt = prev_tx_power; + priv->tx_power_next = prev_tx_power; + } + return ret; +} + +static int iwlagn_rxon_connect(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + int ret; + struct iwl_rxon_cmd *active = (void *)&ctx->active; + + /* RXON timing must be before associated RXON */ + if (ctx->ctxid == IWL_RXON_CTX_BSS) { + ret = iwl_send_rxon_timing(priv, ctx); + if (ret) { + IWL_ERR(priv, "Failed to send timing (%d)!\n", ret); + return ret; + } + } + /* QoS info may be cleared by previous un-assoc RXON */ + iwlagn_update_qos(priv, ctx); + + /* + * We'll run into this code path when beaconing is + * enabled, but then we also need to send the beacon + * to the device. + */ + if (ctx->vif && (ctx->vif->type == NL80211_IFTYPE_AP)) { + ret = iwlagn_update_beacon(priv, ctx->vif); + if (ret) { + IWL_ERR(priv, + "Error sending required beacon (%d)!\n", + ret); + return ret; + } + } + + priv->start_calib = 0; + /* + * Apply the new configuration. + * + * Associated RXON doesn't clear the station table in uCode, + * so we don't need to restore stations etc. after this. + */ + ret = iwl_dvm_send_cmd_pdu(priv, ctx->rxon_cmd, CMD_SYNC, + sizeof(struct iwl_rxon_cmd), &ctx->staging); + if (ret) { + IWL_ERR(priv, "Error setting new RXON (%d)\n", ret); + return ret; + } + memcpy(active, &ctx->staging, sizeof(*active)); + + /* IBSS beacon needs to be sent after setting assoc */ + if (ctx->vif && (ctx->vif->type == NL80211_IFTYPE_ADHOC)) + if (iwlagn_update_beacon(priv, ctx->vif)) + IWL_ERR(priv, "Error sending IBSS beacon\n"); + iwl_init_sensitivity(priv); + + /* + * If we issue a new RXON command which required a tune then + * we must send a new TXPOWER command or we won't be able to + * Tx any frames. + * + * It's expected we set power here if channel is changing. + */ + ret = iwl_set_tx_power(priv, priv->tx_power_next, true); + if (ret) { + IWL_ERR(priv, "Error sending TX power (%d)\n", ret); + return ret; + } + + if (ctx->vif && ctx->vif->type == NL80211_IFTYPE_STATION && + priv->cfg->ht_params && priv->cfg->ht_params->smps_mode) + ieee80211_request_smps(ctx->vif, + priv->cfg->ht_params->smps_mode); + + return 0; +} + +int iwlagn_set_pan_params(struct iwl_priv *priv) +{ + struct iwl_wipan_params_cmd cmd; + struct iwl_rxon_context *ctx_bss, *ctx_pan; + int slot0 = 300, slot1 = 0; + int ret; + + if (priv->valid_contexts == BIT(IWL_RXON_CTX_BSS)) + return 0; + + BUILD_BUG_ON(NUM_IWL_RXON_CTX != 2); + + lockdep_assert_held(&priv->mutex); + + ctx_bss = &priv->contexts[IWL_RXON_CTX_BSS]; + ctx_pan = &priv->contexts[IWL_RXON_CTX_PAN]; + + /* + * If the PAN context is inactive, then we don't need + * to update the PAN parameters, the last thing we'll + * have done before it goes inactive is making the PAN + * parameters be WLAN-only. + */ + if (!ctx_pan->is_active) + return 0; + + memset(&cmd, 0, sizeof(cmd)); + + /* only 2 slots are currently allowed */ + cmd.num_slots = 2; + + cmd.slots[0].type = 0; /* BSS */ + cmd.slots[1].type = 1; /* PAN */ + + if (priv->hw_roc_setup) { + /* both contexts must be used for this to happen */ + slot1 = IWL_MIN_SLOT_TIME; + slot0 = 3000; + } else if (ctx_bss->vif && ctx_pan->vif) { + int bcnint = ctx_pan->beacon_int; + int dtim = ctx_pan->vif->bss_conf.dtim_period ?: 1; + + /* should be set, but seems unused?? */ + cmd.flags |= cpu_to_le16(IWL_WIPAN_PARAMS_FLG_SLOTTED_MODE); + + if (ctx_pan->vif->type == NL80211_IFTYPE_AP && + bcnint && + bcnint != ctx_bss->beacon_int) { + IWL_ERR(priv, + "beacon intervals don't match (%d, %d)\n", + ctx_bss->beacon_int, ctx_pan->beacon_int); + } else + bcnint = max_t(int, bcnint, + ctx_bss->beacon_int); + if (!bcnint) + bcnint = DEFAULT_BEACON_INTERVAL; + slot0 = bcnint / 2; + slot1 = bcnint - slot0; + + if (test_bit(STATUS_SCAN_HW, &priv->status) || + (!ctx_bss->vif->bss_conf.idle && + !ctx_bss->vif->bss_conf.assoc)) { + slot0 = dtim * bcnint * 3 - IWL_MIN_SLOT_TIME; + slot1 = IWL_MIN_SLOT_TIME; + } else if (!ctx_pan->vif->bss_conf.idle && + !ctx_pan->vif->bss_conf.assoc) { + slot1 = dtim * bcnint * 3 - IWL_MIN_SLOT_TIME; + slot0 = IWL_MIN_SLOT_TIME; + } + } else if (ctx_pan->vif) { + slot0 = 0; + slot1 = max_t(int, 1, ctx_pan->vif->bss_conf.dtim_period) * + ctx_pan->beacon_int; + slot1 = max_t(int, DEFAULT_BEACON_INTERVAL, slot1); + + if (test_bit(STATUS_SCAN_HW, &priv->status)) { + slot0 = slot1 * 3 - IWL_MIN_SLOT_TIME; + slot1 = IWL_MIN_SLOT_TIME; + } + } + + cmd.slots[0].width = cpu_to_le16(slot0); + cmd.slots[1].width = cpu_to_le16(slot1); + + ret = iwl_dvm_send_cmd_pdu(priv, REPLY_WIPAN_PARAMS, CMD_SYNC, + sizeof(cmd), &cmd); + if (ret) + IWL_ERR(priv, "Error setting PAN parameters (%d)\n", ret); + + return ret; +} + +static void _iwl_set_rxon_ht(struct iwl_priv *priv, + struct iwl_ht_config *ht_conf, + struct iwl_rxon_context *ctx) +{ + struct iwl_rxon_cmd *rxon = &ctx->staging; + + if (!ctx->ht.enabled) { + rxon->flags &= ~(RXON_FLG_CHANNEL_MODE_MSK | + RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK | + RXON_FLG_HT40_PROT_MSK | + RXON_FLG_HT_PROT_MSK); + return; + } + + /* FIXME: if the definition of ht.protection changed, the "translation" + * will be needed for rxon->flags + */ + rxon->flags |= cpu_to_le32(ctx->ht.protection << + RXON_FLG_HT_OPERATING_MODE_POS); + + /* Set up channel bandwidth: + * 20 MHz only, 20/40 mixed or pure 40 if ht40 ok */ + /* clear the HT channel mode before set the mode */ + rxon->flags &= ~(RXON_FLG_CHANNEL_MODE_MSK | + RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK); + if (iwl_is_ht40_tx_allowed(priv, ctx, NULL)) { + /* pure ht40 */ + if (ctx->ht.protection == + IEEE80211_HT_OP_MODE_PROTECTION_20MHZ) { + rxon->flags |= RXON_FLG_CHANNEL_MODE_PURE_40; + /* + * Note: control channel is opposite of extension + * channel + */ + switch (ctx->ht.extension_chan_offset) { + case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: + rxon->flags &= + ~RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK; + break; + case IEEE80211_HT_PARAM_CHA_SEC_BELOW: + rxon->flags |= + RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK; + break; + } + } else { + /* + * Note: control channel is opposite of extension + * channel + */ + switch (ctx->ht.extension_chan_offset) { + case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: + rxon->flags &= + ~(RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK); + rxon->flags |= RXON_FLG_CHANNEL_MODE_MIXED; + break; + case IEEE80211_HT_PARAM_CHA_SEC_BELOW: + rxon->flags |= RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK; + rxon->flags |= RXON_FLG_CHANNEL_MODE_MIXED; + break; + case IEEE80211_HT_PARAM_CHA_SEC_NONE: + default: + /* + * channel location only valid if in Mixed + * mode + */ + IWL_ERR(priv, + "invalid extension channel offset\n"); + break; + } + } + } else { + rxon->flags |= RXON_FLG_CHANNEL_MODE_LEGACY; + } + + iwlagn_set_rxon_chain(priv, ctx); + + IWL_DEBUG_ASSOC(priv, "rxon flags 0x%X operation mode :0x%X " + "extension channel offset 0x%x\n", + le32_to_cpu(rxon->flags), ctx->ht.protection, + ctx->ht.extension_chan_offset); +} + +void iwl_set_rxon_ht(struct iwl_priv *priv, struct iwl_ht_config *ht_conf) +{ + struct iwl_rxon_context *ctx; + + for_each_context(priv, ctx) + _iwl_set_rxon_ht(priv, ht_conf, ctx); +} + +/** + * iwl_set_rxon_channel - Set the band and channel values in staging RXON + * @ch: requested channel as a pointer to struct ieee80211_channel + + * NOTE: Does not commit to the hardware; it sets appropriate bit fields + * in the staging RXON flag structure based on the ch->band + */ +void iwl_set_rxon_channel(struct iwl_priv *priv, struct ieee80211_channel *ch, + struct iwl_rxon_context *ctx) +{ + enum ieee80211_band band = ch->band; + u16 channel = ch->hw_value; + + if ((le16_to_cpu(ctx->staging.channel) == channel) && + (priv->band == band)) + return; + + ctx->staging.channel = cpu_to_le16(channel); + if (band == IEEE80211_BAND_5GHZ) + ctx->staging.flags &= ~RXON_FLG_BAND_24G_MSK; + else + ctx->staging.flags |= RXON_FLG_BAND_24G_MSK; + + priv->band = band; + + IWL_DEBUG_INFO(priv, "Staging channel set to %d [%d]\n", channel, band); + +} + +void iwl_set_flags_for_band(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + enum ieee80211_band band, + struct ieee80211_vif *vif) +{ + if (band == IEEE80211_BAND_5GHZ) { + ctx->staging.flags &= + ~(RXON_FLG_BAND_24G_MSK | RXON_FLG_AUTO_DETECT_MSK + | RXON_FLG_CCK_MSK); + ctx->staging.flags |= RXON_FLG_SHORT_SLOT_MSK; + } else { + /* Copied from iwl_post_associate() */ + if (vif && vif->bss_conf.use_short_slot) + ctx->staging.flags |= RXON_FLG_SHORT_SLOT_MSK; + else + ctx->staging.flags &= ~RXON_FLG_SHORT_SLOT_MSK; + + ctx->staging.flags |= RXON_FLG_BAND_24G_MSK; + ctx->staging.flags |= RXON_FLG_AUTO_DETECT_MSK; + ctx->staging.flags &= ~RXON_FLG_CCK_MSK; + } +} + +static void iwl_set_rxon_hwcrypto(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, int hw_decrypt) +{ + struct iwl_rxon_cmd *rxon = &ctx->staging; + + if (hw_decrypt) + rxon->filter_flags &= ~RXON_FILTER_DIS_DECRYPT_MSK; + else + rxon->filter_flags |= RXON_FILTER_DIS_DECRYPT_MSK; + +} + +/* validate RXON structure is valid */ +static int iwl_check_rxon_cmd(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + struct iwl_rxon_cmd *rxon = &ctx->staging; + u32 errors = 0; + + if (rxon->flags & RXON_FLG_BAND_24G_MSK) { + if (rxon->flags & RXON_FLG_TGJ_NARROW_BAND_MSK) { + IWL_WARN(priv, "check 2.4G: wrong narrow\n"); + errors |= BIT(0); + } + if (rxon->flags & RXON_FLG_RADAR_DETECT_MSK) { + IWL_WARN(priv, "check 2.4G: wrong radar\n"); + errors |= BIT(1); + } + } else { + if (!(rxon->flags & RXON_FLG_SHORT_SLOT_MSK)) { + IWL_WARN(priv, "check 5.2G: not short slot!\n"); + errors |= BIT(2); + } + if (rxon->flags & RXON_FLG_CCK_MSK) { + IWL_WARN(priv, "check 5.2G: CCK!\n"); + errors |= BIT(3); + } + } + if ((rxon->node_addr[0] | rxon->bssid_addr[0]) & 0x1) { + IWL_WARN(priv, "mac/bssid mcast!\n"); + errors |= BIT(4); + } + + /* make sure basic rates 6Mbps and 1Mbps are supported */ + if ((rxon->ofdm_basic_rates & IWL_RATE_6M_MASK) == 0 && + (rxon->cck_basic_rates & IWL_RATE_1M_MASK) == 0) { + IWL_WARN(priv, "neither 1 nor 6 are basic\n"); + errors |= BIT(5); + } + + if (le16_to_cpu(rxon->assoc_id) > 2007) { + IWL_WARN(priv, "aid > 2007\n"); + errors |= BIT(6); + } + + if ((rxon->flags & (RXON_FLG_CCK_MSK | RXON_FLG_SHORT_SLOT_MSK)) + == (RXON_FLG_CCK_MSK | RXON_FLG_SHORT_SLOT_MSK)) { + IWL_WARN(priv, "CCK and short slot\n"); + errors |= BIT(7); + } + + if ((rxon->flags & (RXON_FLG_CCK_MSK | RXON_FLG_AUTO_DETECT_MSK)) + == (RXON_FLG_CCK_MSK | RXON_FLG_AUTO_DETECT_MSK)) { + IWL_WARN(priv, "CCK and auto detect"); + errors |= BIT(8); + } + + if ((rxon->flags & (RXON_FLG_AUTO_DETECT_MSK | + RXON_FLG_TGG_PROTECT_MSK)) == + RXON_FLG_TGG_PROTECT_MSK) { + IWL_WARN(priv, "TGg but no auto-detect\n"); + errors |= BIT(9); + } + + if (rxon->channel == 0) { + IWL_WARN(priv, "zero channel is invalid\n"); + errors |= BIT(10); + } + + WARN(errors, "Invalid RXON (%#x), channel %d", + errors, le16_to_cpu(rxon->channel)); + + return errors ? -EINVAL : 0; +} + +/** + * iwl_full_rxon_required - check if full RXON (vs RXON_ASSOC) cmd is needed + * @priv: staging_rxon is compared to active_rxon + * + * If the RXON structure is changing enough to require a new tune, + * or is clearing the RXON_FILTER_ASSOC_MSK, then return 1 to indicate that + * a new tune (full RXON command, rather than RXON_ASSOC cmd) is required. + */ +static int iwl_full_rxon_required(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + const struct iwl_rxon_cmd *staging = &ctx->staging; + const struct iwl_rxon_cmd *active = &ctx->active; + +#define CHK(cond) \ + if ((cond)) { \ + IWL_DEBUG_INFO(priv, "need full RXON - " #cond "\n"); \ + return 1; \ + } + +#define CHK_NEQ(c1, c2) \ + if ((c1) != (c2)) { \ + IWL_DEBUG_INFO(priv, "need full RXON - " \ + #c1 " != " #c2 " - %d != %d\n", \ + (c1), (c2)); \ + return 1; \ + } + + /* These items are only settable from the full RXON command */ + CHK(!iwl_is_associated_ctx(ctx)); + CHK(!ether_addr_equal(staging->bssid_addr, active->bssid_addr)); + CHK(!ether_addr_equal(staging->node_addr, active->node_addr)); + CHK(!ether_addr_equal(staging->wlap_bssid_addr, + active->wlap_bssid_addr)); + CHK_NEQ(staging->dev_type, active->dev_type); + CHK_NEQ(staging->channel, active->channel); + CHK_NEQ(staging->air_propagation, active->air_propagation); + CHK_NEQ(staging->ofdm_ht_single_stream_basic_rates, + active->ofdm_ht_single_stream_basic_rates); + CHK_NEQ(staging->ofdm_ht_dual_stream_basic_rates, + active->ofdm_ht_dual_stream_basic_rates); + CHK_NEQ(staging->ofdm_ht_triple_stream_basic_rates, + active->ofdm_ht_triple_stream_basic_rates); + CHK_NEQ(staging->assoc_id, active->assoc_id); + + /* flags, filter_flags, ofdm_basic_rates, and cck_basic_rates can + * be updated with the RXON_ASSOC command -- however only some + * flag transitions are allowed using RXON_ASSOC */ + + /* Check if we are not switching bands */ + CHK_NEQ(staging->flags & RXON_FLG_BAND_24G_MSK, + active->flags & RXON_FLG_BAND_24G_MSK); + + /* Check if we are switching association toggle */ + CHK_NEQ(staging->filter_flags & RXON_FILTER_ASSOC_MSK, + active->filter_flags & RXON_FILTER_ASSOC_MSK); + +#undef CHK +#undef CHK_NEQ + + return 0; +} + +#ifdef CPTCFG_IWLWIFI_DEBUG +void iwl_print_rx_config_cmd(struct iwl_priv *priv, + enum iwl_rxon_context_id ctxid) +{ + struct iwl_rxon_context *ctx = &priv->contexts[ctxid]; + struct iwl_rxon_cmd *rxon = &ctx->staging; + + IWL_DEBUG_RADIO(priv, "RX CONFIG:\n"); + iwl_print_hex_dump(priv, IWL_DL_RADIO, (u8 *) rxon, sizeof(*rxon)); + IWL_DEBUG_RADIO(priv, "u16 channel: 0x%x\n", + le16_to_cpu(rxon->channel)); + IWL_DEBUG_RADIO(priv, "u32 flags: 0x%08X\n", + le32_to_cpu(rxon->flags)); + IWL_DEBUG_RADIO(priv, "u32 filter_flags: 0x%08x\n", + le32_to_cpu(rxon->filter_flags)); + IWL_DEBUG_RADIO(priv, "u8 dev_type: 0x%x\n", rxon->dev_type); + IWL_DEBUG_RADIO(priv, "u8 ofdm_basic_rates: 0x%02x\n", + rxon->ofdm_basic_rates); + IWL_DEBUG_RADIO(priv, "u8 cck_basic_rates: 0x%02x\n", + rxon->cck_basic_rates); + IWL_DEBUG_RADIO(priv, "u8[6] node_addr: %pM\n", rxon->node_addr); + IWL_DEBUG_RADIO(priv, "u8[6] bssid_addr: %pM\n", rxon->bssid_addr); + IWL_DEBUG_RADIO(priv, "u16 assoc_id: 0x%x\n", + le16_to_cpu(rxon->assoc_id)); +} +#endif + +static void iwl_calc_basic_rates(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + int lowest_present_ofdm = 100; + int lowest_present_cck = 100; + u8 cck = 0; + u8 ofdm = 0; + + if (ctx->vif) { + struct ieee80211_supported_band *sband; + unsigned long basic = ctx->vif->bss_conf.basic_rates; + int i; + + sband = priv->hw->wiphy->bands[priv->hw->conf.chandef.chan->band]; + + for_each_set_bit(i, &basic, BITS_PER_LONG) { + int hw = sband->bitrates[i].hw_value; + if (hw >= IWL_FIRST_OFDM_RATE) { + ofdm |= BIT(hw - IWL_FIRST_OFDM_RATE); + if (lowest_present_ofdm > hw) + lowest_present_ofdm = hw; + } else { + BUILD_BUG_ON(IWL_FIRST_CCK_RATE != 0); + + cck |= BIT(hw); + if (lowest_present_cck > hw) + lowest_present_cck = hw; + } + } + } + + /* + * Now we've got the basic rates as bitmaps in the ofdm and cck + * variables. This isn't sufficient though, as there might not + * be all the right rates in the bitmap. E.g. if the only basic + * rates are 5.5 Mbps and 11 Mbps, we still need to add 1 Mbps + * and 6 Mbps because the 802.11-2007 standard says in 9.6: + * + * [...] a STA responding to a received frame shall transmit + * its Control Response frame [...] at the highest rate in the + * BSSBasicRateSet parameter that is less than or equal to the + * rate of the immediately previous frame in the frame exchange + * sequence ([...]) and that is of the same modulation class + * ([...]) as the received frame. If no rate contained in the + * BSSBasicRateSet parameter meets these conditions, then the + * control frame sent in response to a received frame shall be + * transmitted at the highest mandatory rate of the PHY that is + * less than or equal to the rate of the received frame, and + * that is of the same modulation class as the received frame. + * + * As a consequence, we need to add all mandatory rates that are + * lower than all of the basic rates to these bitmaps. + */ + + if (IWL_RATE_24M_INDEX < lowest_present_ofdm) + ofdm |= IWL_RATE_24M_MASK >> IWL_FIRST_OFDM_RATE; + if (IWL_RATE_12M_INDEX < lowest_present_ofdm) + ofdm |= IWL_RATE_12M_MASK >> IWL_FIRST_OFDM_RATE; + /* 6M already there or needed so always add */ + ofdm |= IWL_RATE_6M_MASK >> IWL_FIRST_OFDM_RATE; + + /* + * CCK is a bit more complex with DSSS vs. HR/DSSS vs. ERP. + * Note, however: + * - if no CCK rates are basic, it must be ERP since there must + * be some basic rates at all, so they're OFDM => ERP PHY + * (or we're in 5 GHz, and the cck bitmap will never be used) + * - if 11M is a basic rate, it must be ERP as well, so add 5.5M + * - if 5.5M is basic, 1M and 2M are mandatory + * - if 2M is basic, 1M is mandatory + * - if 1M is basic, that's the only valid ACK rate. + * As a consequence, it's not as complicated as it sounds, just add + * any lower rates to the ACK rate bitmap. + */ + if (IWL_RATE_11M_INDEX < lowest_present_cck) + cck |= IWL_RATE_11M_MASK >> IWL_FIRST_CCK_RATE; + if (IWL_RATE_5M_INDEX < lowest_present_cck) + cck |= IWL_RATE_5M_MASK >> IWL_FIRST_CCK_RATE; + if (IWL_RATE_2M_INDEX < lowest_present_cck) + cck |= IWL_RATE_2M_MASK >> IWL_FIRST_CCK_RATE; + /* 1M already there or needed so always add */ + cck |= IWL_RATE_1M_MASK >> IWL_FIRST_CCK_RATE; + + IWL_DEBUG_RATE(priv, "Set basic rates cck:0x%.2x ofdm:0x%.2x\n", + cck, ofdm); + + /* "basic_rates" is a misnomer here -- should be called ACK rates */ + ctx->staging.cck_basic_rates = cck; + ctx->staging.ofdm_basic_rates = ofdm; +} + +/** + * iwlagn_commit_rxon - commit staging_rxon to hardware + * + * The RXON command in staging_rxon is committed to the hardware and + * the active_rxon structure is updated with the new data. This + * function correctly transitions out of the RXON_ASSOC_MSK state if + * a HW tune is required based on the RXON structure changes. + * + * The connect/disconnect flow should be as the following: + * + * 1. make sure send RXON command with association bit unset if not connect + * this should include the channel and the band for the candidate + * to be connected to + * 2. Add Station before RXON association with the AP + * 3. RXON_timing has to send before RXON for connection + * 4. full RXON command - associated bit set + * 5. use RXON_ASSOC command to update any flags changes + */ +int iwlagn_commit_rxon(struct iwl_priv *priv, struct iwl_rxon_context *ctx) +{ + /* cast away the const for active_rxon in this function */ + struct iwl_rxon_cmd *active = (void *)&ctx->active; + bool new_assoc = !!(ctx->staging.filter_flags & RXON_FILTER_ASSOC_MSK); + int ret; + + lockdep_assert_held(&priv->mutex); + + if (!iwl_is_alive(priv)) + return -EBUSY; + + /* This function hardcodes a bunch of dual-mode assumptions */ + BUILD_BUG_ON(NUM_IWL_RXON_CTX != 2); + + if (!ctx->is_active) + return 0; + + /* always get timestamp with Rx frame */ + ctx->staging.flags |= RXON_FLG_TSF2HOST_MSK; + + /* recalculate basic rates */ + iwl_calc_basic_rates(priv, ctx); + + /* + * force CTS-to-self frames protection if RTS-CTS is not preferred + * one aggregation protection method + */ + if (!priv->hw_params.use_rts_for_aggregation) + ctx->staging.flags |= RXON_FLG_SELF_CTS_EN; + + if ((ctx->vif && ctx->vif->bss_conf.use_short_slot) || + !(ctx->staging.flags & RXON_FLG_BAND_24G_MSK)) + ctx->staging.flags |= RXON_FLG_SHORT_SLOT_MSK; + else + ctx->staging.flags &= ~RXON_FLG_SHORT_SLOT_MSK; + + iwl_print_rx_config_cmd(priv, ctx->ctxid); + ret = iwl_check_rxon_cmd(priv, ctx); + if (ret) { + IWL_ERR(priv, "Invalid RXON configuration. Not committing.\n"); + return -EINVAL; + } + + /* + * receive commit_rxon request + * abort any previous channel switch if still in process + */ + if (test_bit(STATUS_CHANNEL_SWITCH_PENDING, &priv->status) && + (priv->switch_channel != ctx->staging.channel)) { + IWL_DEBUG_11H(priv, "abort channel switch on %d\n", + le16_to_cpu(priv->switch_channel)); + iwl_chswitch_done(priv, false); + } + + /* + * If we don't need to send a full RXON, we can use + * iwl_rxon_assoc_cmd which is used to reconfigure filter + * and other flags for the current radio configuration. + */ + if (!iwl_full_rxon_required(priv, ctx)) { + ret = iwlagn_send_rxon_assoc(priv, ctx); + if (ret) { + IWL_ERR(priv, "Error setting RXON_ASSOC (%d)\n", ret); + return ret; + } + + memcpy(active, &ctx->staging, sizeof(*active)); + /* + * We do not commit tx power settings while channel changing, + * do it now if after settings changed. + */ + iwl_set_tx_power(priv, priv->tx_power_next, false); + + /* make sure we are in the right PS state */ + iwl_power_update_mode(priv, true); + + return 0; + } + + iwl_set_rxon_hwcrypto(priv, ctx, !iwlwifi_mod_params.sw_crypto); + + IWL_DEBUG_INFO(priv, + "Going to commit RXON\n" + " * with%s RXON_FILTER_ASSOC_MSK\n" + " * channel = %d\n" + " * bssid = %pM\n", + (new_assoc ? "" : "out"), + le16_to_cpu(ctx->staging.channel), + ctx->staging.bssid_addr); + + /* + * Always clear associated first, but with the correct config. + * This is required as for example station addition for the + * AP station must be done after the BSSID is set to correctly + * set up filters in the device. + */ + ret = iwlagn_rxon_disconn(priv, ctx); + if (ret) + return ret; + + ret = iwlagn_set_pan_params(priv); + if (ret) + return ret; + + if (new_assoc) + return iwlagn_rxon_connect(priv, ctx); + + return 0; +} + +void iwlagn_config_ht40(struct ieee80211_conf *conf, + struct iwl_rxon_context *ctx) +{ + if (conf_is_ht40_minus(conf)) { + ctx->ht.extension_chan_offset = + IEEE80211_HT_PARAM_CHA_SEC_BELOW; + ctx->ht.is_40mhz = true; + } else if (conf_is_ht40_plus(conf)) { + ctx->ht.extension_chan_offset = + IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + ctx->ht.is_40mhz = true; + } else { + ctx->ht.extension_chan_offset = + IEEE80211_HT_PARAM_CHA_SEC_NONE; + ctx->ht.is_40mhz = false; + } +} + +int iwlagn_mac_config(struct ieee80211_hw *hw, u32 changed) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_rxon_context *ctx; + struct ieee80211_conf *conf = &hw->conf; + struct ieee80211_channel *channel = conf->chandef.chan; + int ret = 0; + + IWL_DEBUG_MAC80211(priv, "enter: changed %#x\n", changed); + + mutex_lock(&priv->mutex); + + if (unlikely(test_bit(STATUS_SCANNING, &priv->status))) { + IWL_DEBUG_MAC80211(priv, "leave - scanning\n"); + goto out; + } + + if (!iwl_is_ready(priv)) { + IWL_DEBUG_MAC80211(priv, "leave - not ready\n"); + goto out; + } + + if (changed & (IEEE80211_CONF_CHANGE_SMPS | + IEEE80211_CONF_CHANGE_CHANNEL)) { + /* mac80211 uses static for non-HT which is what we want */ + priv->current_ht_config.smps = conf->smps_mode; + + /* + * Recalculate chain counts. + * + * If monitor mode is enabled then mac80211 will + * set up the SM PS mode to OFF if an HT channel is + * configured. + */ + for_each_context(priv, ctx) + iwlagn_set_rxon_chain(priv, ctx); + } + + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + for_each_context(priv, ctx) { + /* Configure HT40 channels */ + if (ctx->ht.enabled != conf_is_ht(conf)) + ctx->ht.enabled = conf_is_ht(conf); + + if (ctx->ht.enabled) { + /* if HT40 is used, it should not change + * after associated except channel switch */ + if (!ctx->ht.is_40mhz || + !iwl_is_associated_ctx(ctx)) + iwlagn_config_ht40(conf, ctx); + } else + ctx->ht.is_40mhz = false; + + /* + * Default to no protection. Protection mode will + * later be set from BSS config in iwl_ht_conf + */ + ctx->ht.protection = IEEE80211_HT_OP_MODE_PROTECTION_NONE; + + /* if we are switching from ht to 2.4 clear flags + * from any ht related info since 2.4 does not + * support ht */ + if (le16_to_cpu(ctx->staging.channel) != + channel->hw_value) + ctx->staging.flags = 0; + + iwl_set_rxon_channel(priv, channel, ctx); + iwl_set_rxon_ht(priv, &priv->current_ht_config); + + iwl_set_flags_for_band(priv, ctx, channel->band, + ctx->vif); + } + + iwl_update_bcast_stations(priv); + } + + if (changed & (IEEE80211_CONF_CHANGE_PS | + IEEE80211_CONF_CHANGE_IDLE)) { + ret = iwl_power_update_mode(priv, false); + if (ret) + IWL_DEBUG_MAC80211(priv, "Error setting sleep level\n"); + } + + if (changed & IEEE80211_CONF_CHANGE_POWER) { + IWL_DEBUG_MAC80211(priv, "TX Power old=%d new=%d\n", + priv->tx_power_user_lmt, conf->power_level); + + iwl_set_tx_power(priv, conf->power_level, false); + } + + for_each_context(priv, ctx) { + if (!memcmp(&ctx->staging, &ctx->active, sizeof(ctx->staging))) + continue; + iwlagn_commit_rxon(priv, ctx); + } + out: + mutex_unlock(&priv->mutex); + IWL_DEBUG_MAC80211(priv, "leave\n"); + + return ret; +} + +static void iwlagn_check_needed_chains(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_bss_conf *bss_conf) +{ + struct ieee80211_vif *vif = ctx->vif; + struct iwl_rxon_context *tmp; + struct ieee80211_sta *sta; + struct iwl_ht_config *ht_conf = &priv->current_ht_config; + struct ieee80211_sta_ht_cap *ht_cap; + bool need_multiple; + + lockdep_assert_held(&priv->mutex); + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + rcu_read_lock(); + sta = ieee80211_find_sta(vif, bss_conf->bssid); + if (!sta) { + /* + * If at all, this can only happen through a race + * when the AP disconnects us while we're still + * setting up the connection, in that case mac80211 + * will soon tell us about that. + */ + need_multiple = false; + rcu_read_unlock(); + break; + } + + ht_cap = &sta->ht_cap; + + need_multiple = true; + + /* + * If the peer advertises no support for receiving 2 and 3 + * stream MCS rates, it can't be transmitting them either. + */ + if (ht_cap->mcs.rx_mask[1] == 0 && + ht_cap->mcs.rx_mask[2] == 0) { + need_multiple = false; + } else if (!(ht_cap->mcs.tx_params & + IEEE80211_HT_MCS_TX_DEFINED)) { + /* If it can't TX MCS at all ... */ + need_multiple = false; + } else if (ht_cap->mcs.tx_params & + IEEE80211_HT_MCS_TX_RX_DIFF) { + int maxstreams; + + /* + * But if it can receive them, it might still not + * be able to transmit them, which is what we need + * to check here -- so check the number of streams + * it advertises for TX (if different from RX). + */ + + maxstreams = (ht_cap->mcs.tx_params & + IEEE80211_HT_MCS_TX_MAX_STREAMS_MASK); + maxstreams >>= + IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT; + maxstreams += 1; + + if (maxstreams <= 1) + need_multiple = false; + } + + rcu_read_unlock(); + break; + case NL80211_IFTYPE_ADHOC: + /* currently */ + need_multiple = false; + break; + default: + /* only AP really */ + need_multiple = true; + break; + } + + ctx->ht_need_multiple_chains = need_multiple; + + if (!need_multiple) { + /* check all contexts */ + for_each_context(priv, tmp) { + if (!tmp->vif) + continue; + if (tmp->ht_need_multiple_chains) { + need_multiple = true; + break; + } + } + } + + ht_conf->single_chain_sufficient = !need_multiple; +} + +static void iwlagn_chain_noise_reset(struct iwl_priv *priv) +{ + struct iwl_chain_noise_data *data = &priv->chain_noise_data; + int ret; + + if (priv->calib_disabled & IWL_CHAIN_NOISE_CALIB_DISABLED) + return; + + if ((data->state == IWL_CHAIN_NOISE_ALIVE) && + iwl_is_any_associated(priv)) { + struct iwl_calib_chain_noise_reset_cmd cmd; + + /* clear data for chain noise calibration algorithm */ + data->chain_noise_a = 0; + data->chain_noise_b = 0; + data->chain_noise_c = 0; + data->chain_signal_a = 0; + data->chain_signal_b = 0; + data->chain_signal_c = 0; + data->beacon_count = 0; + + memset(&cmd, 0, sizeof(cmd)); + iwl_set_calib_hdr(&cmd.hdr, + priv->phy_calib_chain_noise_reset_cmd); + ret = iwl_dvm_send_cmd_pdu(priv, + REPLY_PHY_CALIBRATION_CMD, + CMD_SYNC, sizeof(cmd), &cmd); + if (ret) + IWL_ERR(priv, + "Could not send REPLY_PHY_CALIBRATION_CMD\n"); + data->state = IWL_CHAIN_NOISE_ACCUMULATE; + IWL_DEBUG_CALIB(priv, "Run chain_noise_calibrate\n"); + } +} + +void iwlagn_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changes) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_rxon_context *ctx = iwl_rxon_ctx_from_vif(vif); + int ret; + bool force = false; + + mutex_lock(&priv->mutex); + + if (changes & BSS_CHANGED_IDLE && bss_conf->idle) { + /* + * If we go idle, then clearly no "passive-no-rx" + * workaround is needed any more, this is a reset. + */ + iwlagn_lift_passive_no_rx(priv); + } + + if (unlikely(!iwl_is_ready(priv))) { + IWL_DEBUG_MAC80211(priv, "leave - not ready\n"); + mutex_unlock(&priv->mutex); + return; + } + + if (unlikely(!ctx->vif)) { + IWL_DEBUG_MAC80211(priv, "leave - vif is NULL\n"); + mutex_unlock(&priv->mutex); + return; + } + + if (changes & BSS_CHANGED_BEACON_INT) + force = true; + + if (changes & BSS_CHANGED_QOS) { + ctx->qos_data.qos_active = bss_conf->qos; + iwlagn_update_qos(priv, ctx); + } + + ctx->staging.assoc_id = cpu_to_le16(vif->bss_conf.aid); + if (vif->bss_conf.use_short_preamble) + ctx->staging.flags |= RXON_FLG_SHORT_PREAMBLE_MSK; + else + ctx->staging.flags &= ~RXON_FLG_SHORT_PREAMBLE_MSK; + + if (changes & BSS_CHANGED_ASSOC) { + if (bss_conf->assoc) { + priv->timestamp = bss_conf->sync_tsf; + ctx->staging.filter_flags |= RXON_FILTER_ASSOC_MSK; + } else { + ctx->staging.filter_flags &= ~RXON_FILTER_ASSOC_MSK; + + if (ctx->ctxid == IWL_RXON_CTX_BSS) + priv->have_rekey_data = false; + } + + iwlagn_bt_coex_rssi_monitor(priv); + } + + if (ctx->ht.enabled) { + ctx->ht.protection = bss_conf->ht_operation_mode & + IEEE80211_HT_OP_MODE_PROTECTION; + ctx->ht.non_gf_sta_present = !!(bss_conf->ht_operation_mode & + IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT); + iwlagn_check_needed_chains(priv, ctx, bss_conf); + iwl_set_rxon_ht(priv, &priv->current_ht_config); + } + + iwlagn_set_rxon_chain(priv, ctx); + + if (bss_conf->use_cts_prot && (priv->band != IEEE80211_BAND_5GHZ)) + ctx->staging.flags |= RXON_FLG_TGG_PROTECT_MSK; + else + ctx->staging.flags &= ~RXON_FLG_TGG_PROTECT_MSK; + + if (bss_conf->use_cts_prot) + ctx->staging.flags |= RXON_FLG_SELF_CTS_EN; + else + ctx->staging.flags &= ~RXON_FLG_SELF_CTS_EN; + + memcpy(ctx->staging.bssid_addr, bss_conf->bssid, ETH_ALEN); + + if (vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_ADHOC) { + if (vif->bss_conf.enable_beacon) { + ctx->staging.filter_flags |= RXON_FILTER_ASSOC_MSK; + priv->beacon_ctx = ctx; + } else { + ctx->staging.filter_flags &= ~RXON_FILTER_ASSOC_MSK; + priv->beacon_ctx = NULL; + } + } + + /* + * If the ucode decides to do beacon filtering before + * association, it will lose beacons that are needed + * before sending frames out on passive channels. This + * causes association failures on those channels. Enable + * receiving beacons in such cases. + */ + + if (vif->type == NL80211_IFTYPE_STATION) { + if (!bss_conf->assoc) + ctx->staging.filter_flags |= RXON_FILTER_BCON_AWARE_MSK; + else + ctx->staging.filter_flags &= + ~RXON_FILTER_BCON_AWARE_MSK; + } + + if (force || memcmp(&ctx->staging, &ctx->active, sizeof(ctx->staging))) + iwlagn_commit_rxon(priv, ctx); + + if (changes & BSS_CHANGED_ASSOC && bss_conf->assoc) { + /* + * The chain noise calibration will enable PM upon + * completion. If calibration has already been run + * then we need to enable power management here. + */ + if (priv->chain_noise_data.state == IWL_CHAIN_NOISE_DONE) + iwl_power_update_mode(priv, false); + + /* Enable RX differential gain and sensitivity calibrations */ + iwlagn_chain_noise_reset(priv); + priv->start_calib = 1; + } + + if (changes & BSS_CHANGED_IBSS) { + ret = iwlagn_manage_ibss_station(priv, vif, + bss_conf->ibss_joined); + if (ret) + IWL_ERR(priv, "failed to %s IBSS station %pM\n", + bss_conf->ibss_joined ? "add" : "remove", + bss_conf->bssid); + } + + if (changes & BSS_CHANGED_BEACON && priv->beacon_ctx == ctx) { + if (iwlagn_update_beacon(priv, vif)) + IWL_ERR(priv, "Error updating beacon\n"); + } + + mutex_unlock(&priv->mutex); +} + +void iwlagn_post_scan(struct iwl_priv *priv) +{ + struct iwl_rxon_context *ctx; + + /* + * We do not commit power settings while scan is pending, + * do it now if the settings changed. + */ + iwl_power_set_mode(priv, &priv->power_data.sleep_cmd_next, false); + iwl_set_tx_power(priv, priv->tx_power_next, false); + + /* + * Since setting the RXON may have been deferred while + * performing the scan, fire one off if needed + */ + for_each_context(priv, ctx) + if (memcmp(&ctx->staging, &ctx->active, sizeof(ctx->staging))) + iwlagn_commit_rxon(priv, ctx); + + iwlagn_set_pan_params(priv); +} diff --git a/drivers/net/wireless/iwlwifi/dvm/scan.c b/drivers/net/wireless/iwlwifi/dvm/scan.c new file mode 100644 index 0000000..7537d51 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/scan.c @@ -0,0 +1,1188 @@ +/****************************************************************************** + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + *****************************************************************************/ +#include +#include +#include +#include + +#include "dev.h" +#include "agn.h" + +/* For active scan, listen ACTIVE_DWELL_TIME (msec) on each channel after + * sending probe req. This should be set long enough to hear probe responses + * from more than one AP. */ +#define IWL_ACTIVE_DWELL_TIME_24 (30) /* all times in msec */ +#define IWL_ACTIVE_DWELL_TIME_52 (20) + +#define IWL_ACTIVE_DWELL_FACTOR_24GHZ (3) +#define IWL_ACTIVE_DWELL_FACTOR_52GHZ (2) + +/* For passive scan, listen PASSIVE_DWELL_TIME (msec) on each channel. + * Must be set longer than active dwell time. + * For the most reliable scan, set > AP beacon interval (typically 100msec). */ +#define IWL_PASSIVE_DWELL_TIME_24 (20) /* all times in msec */ +#define IWL_PASSIVE_DWELL_TIME_52 (10) +#define IWL_PASSIVE_DWELL_BASE (100) +#define IWL_CHANNEL_TUNE_TIME 5 +#define MAX_SCAN_CHANNEL 50 + +/* For reset radio, need minimal dwell time only */ +#define IWL_RADIO_RESET_DWELL_TIME 5 + +static int iwl_send_scan_abort(struct iwl_priv *priv) +{ + int ret; + struct iwl_host_cmd cmd = { + .id = REPLY_SCAN_ABORT_CMD, + .flags = CMD_SYNC | CMD_WANT_SKB, + }; + __le32 *status; + + /* Exit instantly with error when device is not ready + * to receive scan abort command or it does not perform + * hardware scan currently */ + if (!test_bit(STATUS_READY, &priv->status) || + !test_bit(STATUS_SCAN_HW, &priv->status) || + test_bit(STATUS_FW_ERROR, &priv->status)) + return -EIO; + + ret = iwl_dvm_send_cmd(priv, &cmd); + if (ret) + return ret; + + status = (void *)cmd.resp_pkt->data; + if (*status != CAN_ABORT_STATUS) { + /* The scan abort will return 1 for success or + * 2 for "failure". A failure condition can be + * due to simply not being in an active scan which + * can occur if we send the scan abort before we + * the microcode has notified us that a scan is + * completed. */ + IWL_DEBUG_SCAN(priv, "SCAN_ABORT ret %d.\n", + le32_to_cpu(*status)); + ret = -EIO; + } + + iwl_free_resp(&cmd); + return ret; +} + +static void iwl_complete_scan(struct iwl_priv *priv, bool aborted) +{ + /* check if scan was requested from mac80211 */ + if (priv->scan_request) { + IWL_DEBUG_SCAN(priv, "Complete scan in mac80211\n"); + ieee80211_scan_completed(priv->hw, aborted); + } + + if (priv->scan_type == IWL_SCAN_ROC) + iwl_scan_roc_expired(priv); + + priv->scan_type = IWL_SCAN_NORMAL; + priv->scan_vif = NULL; + priv->scan_request = NULL; +} + +static void iwl_process_scan_complete(struct iwl_priv *priv) +{ + bool aborted; + + lockdep_assert_held(&priv->mutex); + + if (!test_and_clear_bit(STATUS_SCAN_COMPLETE, &priv->status)) + return; + + IWL_DEBUG_SCAN(priv, "Completed scan.\n"); + + cancel_delayed_work(&priv->scan_check); + + aborted = test_and_clear_bit(STATUS_SCAN_ABORTING, &priv->status); + if (aborted) + IWL_DEBUG_SCAN(priv, "Aborted scan completed.\n"); + + if (!test_and_clear_bit(STATUS_SCANNING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Scan already completed.\n"); + goto out_settings; + } + + if (priv->scan_type == IWL_SCAN_ROC) + iwl_scan_roc_expired(priv); + + if (priv->scan_type != IWL_SCAN_NORMAL && !aborted) { + int err; + + /* Check if mac80211 requested scan during our internal scan */ + if (priv->scan_request == NULL) + goto out_complete; + + /* If so request a new scan */ + err = iwl_scan_initiate(priv, priv->scan_vif, IWL_SCAN_NORMAL, + priv->scan_request->channels[0]->band); + if (err) { + IWL_DEBUG_SCAN(priv, + "failed to initiate pending scan: %d\n", err); + aborted = true; + goto out_complete; + } + + return; + } + +out_complete: + iwl_complete_scan(priv, aborted); + +out_settings: + /* Can we still talk to firmware ? */ + if (!iwl_is_ready_rf(priv)) + return; + + iwlagn_post_scan(priv); +} + +void iwl_force_scan_end(struct iwl_priv *priv) +{ + lockdep_assert_held(&priv->mutex); + + if (!test_bit(STATUS_SCANNING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Forcing scan end while not scanning\n"); + return; + } + + IWL_DEBUG_SCAN(priv, "Forcing scan end\n"); + clear_bit(STATUS_SCANNING, &priv->status); + clear_bit(STATUS_SCAN_HW, &priv->status); + clear_bit(STATUS_SCAN_ABORTING, &priv->status); + clear_bit(STATUS_SCAN_COMPLETE, &priv->status); + iwl_complete_scan(priv, true); +} + +static void iwl_do_scan_abort(struct iwl_priv *priv) +{ + int ret; + + lockdep_assert_held(&priv->mutex); + + if (!test_bit(STATUS_SCANNING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Not performing scan to abort\n"); + return; + } + + if (test_and_set_bit(STATUS_SCAN_ABORTING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Scan abort in progress\n"); + return; + } + + ret = iwl_send_scan_abort(priv); + if (ret) { + IWL_DEBUG_SCAN(priv, "Send scan abort failed %d\n", ret); + iwl_force_scan_end(priv); + } else + IWL_DEBUG_SCAN(priv, "Successfully send scan abort\n"); +} + +/** + * iwl_scan_cancel - Cancel any currently executing HW scan + */ +int iwl_scan_cancel(struct iwl_priv *priv) +{ + IWL_DEBUG_SCAN(priv, "Queuing abort scan\n"); + queue_work(priv->workqueue, &priv->abort_scan); + return 0; +} + +/** + * iwl_scan_cancel_timeout - Cancel any currently executing HW scan + * @ms: amount of time to wait (in milliseconds) for scan to abort + * + */ +void iwl_scan_cancel_timeout(struct iwl_priv *priv, unsigned long ms) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(ms); + + lockdep_assert_held(&priv->mutex); + + IWL_DEBUG_SCAN(priv, "Scan cancel timeout\n"); + + iwl_do_scan_abort(priv); + + while (time_before_eq(jiffies, timeout)) { + if (!test_bit(STATUS_SCAN_HW, &priv->status)) + goto finished; + msleep(20); + } + + return; + + finished: + /* + * Now STATUS_SCAN_HW is clear. This means that the + * device finished, but the background work is going + * to execute at best as soon as we release the mutex. + * Since we need to be able to issue a new scan right + * after this function returns, run the complete here. + * The STATUS_SCAN_COMPLETE bit will then be cleared + * and prevent the background work from "completing" + * a possible new scan. + */ + iwl_process_scan_complete(priv); +} + +/* Service response to REPLY_SCAN_CMD (0x80) */ +static int iwl_rx_reply_scan(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ +#ifdef CPTCFG_IWLWIFI_DEBUG + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_scanreq_notification *notif = (void *)pkt->data; + + IWL_DEBUG_SCAN(priv, "Scan request status = 0x%x\n", notif->status); +#endif + return 0; +} + +/* Service SCAN_START_NOTIFICATION (0x82) */ +static int iwl_rx_scan_start_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_scanstart_notification *notif = (void *)pkt->data; + + priv->scan_start_tsf = le32_to_cpu(notif->tsf_low); + IWL_DEBUG_SCAN(priv, "Scan start: " + "%d [802.11%s] " + "(TSF: 0x%08X:%08X) - %d (beacon timer %u)\n", + notif->channel, + notif->band ? "bg" : "a", + le32_to_cpu(notif->tsf_high), + le32_to_cpu(notif->tsf_low), + notif->status, notif->beacon_timer); + + if (priv->scan_type == IWL_SCAN_ROC && + !priv->hw_roc_start_notified) { + ieee80211_ready_on_channel(priv->hw); + priv->hw_roc_start_notified = true; + } + + return 0; +} + +/* Service SCAN_RESULTS_NOTIFICATION (0x83) */ +static int iwl_rx_scan_results_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ +#ifdef CPTCFG_IWLWIFI_DEBUG + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_scanresults_notification *notif = (void *)pkt->data; + + IWL_DEBUG_SCAN(priv, "Scan ch.res: " + "%d [802.11%s] " + "probe status: %u:%u " + "(TSF: 0x%08X:%08X) - %d " + "elapsed=%lu usec\n", + notif->channel, + notif->band ? "bg" : "a", + notif->probe_status, notif->num_probe_not_sent, + le32_to_cpu(notif->tsf_high), + le32_to_cpu(notif->tsf_low), + le32_to_cpu(notif->statistics[0]), + le32_to_cpu(notif->tsf_low) - priv->scan_start_tsf); +#endif + return 0; +} + +/* Service SCAN_COMPLETE_NOTIFICATION (0x84) */ +static int iwl_rx_scan_complete_notif(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_scancomplete_notification *scan_notif = (void *)pkt->data; + + IWL_DEBUG_SCAN(priv, "Scan complete: %d channels (TSF 0x%08X:%08X) - %d\n", + scan_notif->scanned_channels, + scan_notif->tsf_low, + scan_notif->tsf_high, scan_notif->status); + + IWL_DEBUG_SCAN(priv, "Scan on %sGHz took %dms\n", + (priv->scan_band == IEEE80211_BAND_2GHZ) ? "2.4" : "5.2", + jiffies_to_msecs(jiffies - priv->scan_start)); + + /* + * When aborting, we run the scan completed background work inline + * and the background work must then do nothing. The SCAN_COMPLETE + * bit helps implement that logic and thus needs to be set before + * queueing the work. Also, since the scan abort waits for SCAN_HW + * to clear, we need to set SCAN_COMPLETE before clearing SCAN_HW + * to avoid a race there. + */ + set_bit(STATUS_SCAN_COMPLETE, &priv->status); + clear_bit(STATUS_SCAN_HW, &priv->status); + queue_work(priv->workqueue, &priv->scan_completed); + + if (priv->iw_mode != NL80211_IFTYPE_ADHOC && + iwl_advanced_bt_coexist(priv) && + priv->bt_status != scan_notif->bt_status) { + if (scan_notif->bt_status) { + /* BT on */ + if (!priv->bt_ch_announce) + priv->bt_traffic_load = + IWL_BT_COEX_TRAFFIC_LOAD_HIGH; + /* + * otherwise, no traffic load information provided + * no changes made + */ + } else { + /* BT off */ + priv->bt_traffic_load = + IWL_BT_COEX_TRAFFIC_LOAD_NONE; + } + priv->bt_status = scan_notif->bt_status; + queue_work(priv->workqueue, + &priv->bt_traffic_change_work); + } + return 0; +} + +void iwl_setup_rx_scan_handlers(struct iwl_priv *priv) +{ + /* scan handlers */ + priv->rx_handlers[REPLY_SCAN_CMD] = iwl_rx_reply_scan; + priv->rx_handlers[SCAN_START_NOTIFICATION] = iwl_rx_scan_start_notif; + priv->rx_handlers[SCAN_RESULTS_NOTIFICATION] = + iwl_rx_scan_results_notif; + priv->rx_handlers[SCAN_COMPLETE_NOTIFICATION] = + iwl_rx_scan_complete_notif; +} + +static u16 iwl_get_active_dwell_time(struct iwl_priv *priv, + enum ieee80211_band band, u8 n_probes) +{ + if (band == IEEE80211_BAND_5GHZ) + return IWL_ACTIVE_DWELL_TIME_52 + + IWL_ACTIVE_DWELL_FACTOR_52GHZ * (n_probes + 1); + else + return IWL_ACTIVE_DWELL_TIME_24 + + IWL_ACTIVE_DWELL_FACTOR_24GHZ * (n_probes + 1); +} + +static u16 iwl_limit_dwell(struct iwl_priv *priv, u16 dwell_time) +{ + struct iwl_rxon_context *ctx; + int limits[NUM_IWL_RXON_CTX] = {}; + int n_active = 0; + u16 limit; + + BUILD_BUG_ON(NUM_IWL_RXON_CTX != 2); + + /* + * If we're associated, we clamp the dwell time 98% + * of the beacon interval (minus 2 * channel tune time) + * If both contexts are active, we have to restrict to + * 1/2 of the minimum of them, because they might be in + * lock-step with the time inbetween only half of what + * time we'd have in each of them. + */ + for_each_context(priv, ctx) { + switch (ctx->staging.dev_type) { + case RXON_DEV_TYPE_P2P: + /* no timing constraints */ + continue; + case RXON_DEV_TYPE_ESS: + default: + /* timing constraints if associated */ + if (!iwl_is_associated_ctx(ctx)) + continue; + break; + case RXON_DEV_TYPE_CP: + case RXON_DEV_TYPE_2STA: + /* + * These seem to always have timers for TBTT + * active in uCode even when not associated yet. + */ + break; + } + + limits[n_active++] = ctx->beacon_int ?: IWL_PASSIVE_DWELL_BASE; + } + + switch (n_active) { + case 0: + return dwell_time; + case 2: + limit = (limits[1] * 98) / 100 - IWL_CHANNEL_TUNE_TIME * 2; + limit /= 2; + dwell_time = min(limit, dwell_time); + /* fall through to limit further */ + case 1: + limit = (limits[0] * 98) / 100 - IWL_CHANNEL_TUNE_TIME * 2; + limit /= n_active; + return min(limit, dwell_time); + default: + WARN_ON_ONCE(1); + return dwell_time; + } +} + +static u16 iwl_get_passive_dwell_time(struct iwl_priv *priv, + enum ieee80211_band band) +{ + u16 passive = (band == IEEE80211_BAND_2GHZ) ? + IWL_PASSIVE_DWELL_BASE + IWL_PASSIVE_DWELL_TIME_24 : + IWL_PASSIVE_DWELL_BASE + IWL_PASSIVE_DWELL_TIME_52; + + return iwl_limit_dwell(priv, passive); +} + +/* Return valid, unused, channel for a passive scan to reset the RF */ +static u8 iwl_get_single_channel_number(struct iwl_priv *priv, + enum ieee80211_band band) +{ + struct ieee80211_supported_band *sband = priv->hw->wiphy->bands[band]; + struct iwl_rxon_context *ctx; + int i; + + for (i = 0; i < sband->n_channels; i++) { + bool busy = false; + + for_each_context(priv, ctx) { + busy = sband->channels[i].hw_value == + le16_to_cpu(ctx->staging.channel); + if (busy) + break; + } + + if (busy) + continue; + + if (!(sband->channels[i].flags & IEEE80211_CHAN_DISABLED)) + return sband->channels[i].hw_value; + } + + return 0; +} + +static int iwl_get_channel_for_reset_scan(struct iwl_priv *priv, + struct ieee80211_vif *vif, + enum ieee80211_band band, + struct iwl_scan_channel *scan_ch) +{ + const struct ieee80211_supported_band *sband; + u16 channel; + + sband = iwl_get_hw_mode(priv, band); + if (!sband) { + IWL_ERR(priv, "invalid band\n"); + return 0; + } + + channel = iwl_get_single_channel_number(priv, band); + if (channel) { + scan_ch->channel = cpu_to_le16(channel); + scan_ch->type = SCAN_CHANNEL_TYPE_PASSIVE; + scan_ch->active_dwell = + cpu_to_le16(IWL_RADIO_RESET_DWELL_TIME); + scan_ch->passive_dwell = + cpu_to_le16(IWL_RADIO_RESET_DWELL_TIME); + /* Set txpower levels to defaults */ + scan_ch->dsp_atten = 110; + if (band == IEEE80211_BAND_5GHZ) + scan_ch->tx_gain = ((1 << 5) | (3 << 3)) | 3; + else + scan_ch->tx_gain = ((1 << 5) | (5 << 3)); + return 1; + } + + IWL_ERR(priv, "no valid channel found\n"); + return 0; +} + +static int iwl_get_channels_for_scan(struct iwl_priv *priv, + struct ieee80211_vif *vif, + enum ieee80211_band band, + u8 is_active, u8 n_probes, + struct iwl_scan_channel *scan_ch) +{ + struct ieee80211_channel *chan; + const struct ieee80211_supported_band *sband; + u16 passive_dwell = 0; + u16 active_dwell = 0; + int added, i; + u16 channel; + + sband = iwl_get_hw_mode(priv, band); + if (!sband) + return 0; + + active_dwell = iwl_get_active_dwell_time(priv, band, n_probes); + passive_dwell = iwl_get_passive_dwell_time(priv, band); + + if (passive_dwell <= active_dwell) + passive_dwell = active_dwell + 1; + + for (i = 0, added = 0; i < priv->scan_request->n_channels; i++) { + chan = priv->scan_request->channels[i]; + + if (chan->band != band) + continue; + + channel = chan->hw_value; + scan_ch->channel = cpu_to_le16(channel); + + if (!is_active || (chan->flags & IEEE80211_CHAN_PASSIVE_SCAN)) + scan_ch->type = SCAN_CHANNEL_TYPE_PASSIVE; + else + scan_ch->type = SCAN_CHANNEL_TYPE_ACTIVE; + + if (n_probes) + scan_ch->type |= IWL_SCAN_PROBE_MASK(n_probes); + + scan_ch->active_dwell = cpu_to_le16(active_dwell); + scan_ch->passive_dwell = cpu_to_le16(passive_dwell); + + /* Set txpower levels to defaults */ + scan_ch->dsp_atten = 110; + + /* NOTE: if we were doing 6Mb OFDM for scans we'd use + * power level: + * scan_ch->tx_gain = ((1 << 5) | (2 << 3)) | 3; + */ + if (band == IEEE80211_BAND_5GHZ) + scan_ch->tx_gain = ((1 << 5) | (3 << 3)) | 3; + else + scan_ch->tx_gain = ((1 << 5) | (5 << 3)); + + IWL_DEBUG_SCAN(priv, "Scanning ch=%d prob=0x%X [%s %d]\n", + channel, le32_to_cpu(scan_ch->type), + (scan_ch->type & SCAN_CHANNEL_TYPE_ACTIVE) ? + "ACTIVE" : "PASSIVE", + (scan_ch->type & SCAN_CHANNEL_TYPE_ACTIVE) ? + active_dwell : passive_dwell); + + scan_ch++; + added++; + } + + IWL_DEBUG_SCAN(priv, "total channels to scan %d\n", added); + return added; +} + +/** + * iwl_fill_probe_req - fill in all required fields and IE for probe request + */ + +static u16 iwl_fill_probe_req(struct ieee80211_mgmt *frame, const u8 *ta, + const u8 *ies, int ie_len, const u8 *ssid, + u8 ssid_len, int left) +{ + int len = 0; + u8 *pos = NULL; + + /* Make sure there is enough space for the probe request, + * two mandatory IEs and the data */ + left -= 24; + if (left < 0) + return 0; + + frame->frame_control = cpu_to_le16(IEEE80211_STYPE_PROBE_REQ); + eth_broadcast_addr(frame->da); + memcpy(frame->sa, ta, ETH_ALEN); + eth_broadcast_addr(frame->bssid); + frame->seq_ctrl = 0; + + len += 24; + + /* ...next IE... */ + pos = &frame->u.probe_req.variable[0]; + + /* fill in our SSID IE */ + left -= ssid_len + 2; + if (left < 0) + return 0; + *pos++ = WLAN_EID_SSID; + *pos++ = ssid_len; + if (ssid && ssid_len) { + memcpy(pos, ssid, ssid_len); + pos += ssid_len; + } + + len += ssid_len + 2; + + if (WARN_ON(left < ie_len)) + return len; + + if (ies && ie_len) { + memcpy(pos, ies, ie_len); + len += ie_len; + } + + return (u16)len; +} + +static int iwlagn_request_scan(struct iwl_priv *priv, struct ieee80211_vif *vif) +{ + struct iwl_host_cmd cmd = { + .id = REPLY_SCAN_CMD, + .len = { sizeof(struct iwl_scan_cmd), }, + .flags = CMD_SYNC, + }; + struct iwl_scan_cmd *scan; + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + u32 rate_flags = 0; + u16 cmd_len = 0; + u16 rx_chain = 0; + enum ieee80211_band band; + u8 n_probes = 0; + u8 rx_ant = priv->nvm_data->valid_rx_ant; + u8 rate; + bool is_active = false; + int chan_mod; + u8 active_chains; + u8 scan_tx_antennas = priv->nvm_data->valid_tx_ant; + int ret; + int scan_cmd_size = sizeof(struct iwl_scan_cmd) + + MAX_SCAN_CHANNEL * sizeof(struct iwl_scan_channel) + + priv->fw->ucode_capa.max_probe_length; + const u8 *ssid = NULL; + u8 ssid_len = 0; + + if (WARN_ON(priv->scan_type == IWL_SCAN_NORMAL && + (!priv->scan_request || + priv->scan_request->n_channels > MAX_SCAN_CHANNEL))) + return -EINVAL; + + lockdep_assert_held(&priv->mutex); + + if (vif) + ctx = iwl_rxon_ctx_from_vif(vif); + + if (!priv->scan_cmd) { + priv->scan_cmd = kmalloc(scan_cmd_size, GFP_KERNEL); + if (!priv->scan_cmd) { + IWL_DEBUG_SCAN(priv, + "fail to allocate memory for scan\n"); + return -ENOMEM; + } + } + scan = priv->scan_cmd; + memset(scan, 0, scan_cmd_size); + + scan->quiet_plcp_th = IWL_PLCP_QUIET_THRESH; + scan->quiet_time = IWL_ACTIVE_QUIET_TIME; + + if (priv->scan_type != IWL_SCAN_ROC && + iwl_is_any_associated(priv)) { + u16 interval = 0; + u32 extra; + u32 suspend_time = 100; + u32 scan_suspend_time = 100; + + IWL_DEBUG_INFO(priv, "Scanning while associated...\n"); + switch (priv->scan_type) { + case IWL_SCAN_ROC: + WARN_ON(1); + break; + case IWL_SCAN_RADIO_RESET: + interval = 0; + break; + case IWL_SCAN_NORMAL: + interval = vif->bss_conf.beacon_int; + break; + } + + scan->suspend_time = 0; + scan->max_out_time = cpu_to_le32(200 * 1024); + if (!interval) + interval = suspend_time; + + extra = (suspend_time / interval) << 22; + scan_suspend_time = (extra | + ((suspend_time % interval) * 1024)); + scan->suspend_time = cpu_to_le32(scan_suspend_time); + IWL_DEBUG_SCAN(priv, "suspend_time 0x%X beacon interval %d\n", + scan_suspend_time, interval); + } else if (priv->scan_type == IWL_SCAN_ROC) { + scan->suspend_time = 0; + scan->max_out_time = 0; + scan->quiet_time = 0; + scan->quiet_plcp_th = 0; + } + + switch (priv->scan_type) { + case IWL_SCAN_RADIO_RESET: + IWL_DEBUG_SCAN(priv, "Start internal passive scan.\n"); + /* + * Override quiet time as firmware checks that active + * dwell is >= quiet; since we use passive scan it'll + * not actually be used. + */ + scan->quiet_time = cpu_to_le16(IWL_RADIO_RESET_DWELL_TIME); + break; + case IWL_SCAN_NORMAL: + if (priv->scan_request->n_ssids) { + int i, p = 0; + IWL_DEBUG_SCAN(priv, "Kicking off active scan\n"); + /* + * The highest priority SSID is inserted to the + * probe request template. + */ + ssid_len = priv->scan_request->ssids[0].ssid_len; + ssid = priv->scan_request->ssids[0].ssid; + + /* + * Invert the order of ssids, the firmware will invert + * it back. + */ + for (i = priv->scan_request->n_ssids - 1; i >= 1; i--) { + scan->direct_scan[p].id = WLAN_EID_SSID; + scan->direct_scan[p].len = + priv->scan_request->ssids[i].ssid_len; + memcpy(scan->direct_scan[p].ssid, + priv->scan_request->ssids[i].ssid, + priv->scan_request->ssids[i].ssid_len); + n_probes++; + p++; + } + is_active = true; + } else + IWL_DEBUG_SCAN(priv, "Start passive scan.\n"); + break; + case IWL_SCAN_ROC: + IWL_DEBUG_SCAN(priv, "Start ROC scan.\n"); + break; + } + + scan->tx_cmd.tx_flags = TX_CMD_FLG_SEQ_CTL_MSK; + scan->tx_cmd.sta_id = ctx->bcast_sta_id; + scan->tx_cmd.stop_time.life_time = TX_CMD_LIFE_TIME_INFINITE; + + switch (priv->scan_band) { + case IEEE80211_BAND_2GHZ: + scan->flags = RXON_FLG_BAND_24G_MSK | RXON_FLG_AUTO_DETECT_MSK; + chan_mod = le32_to_cpu( + priv->contexts[IWL_RXON_CTX_BSS].active.flags & + RXON_FLG_CHANNEL_MODE_MSK) + >> RXON_FLG_CHANNEL_MODE_POS; + if ((priv->scan_request && priv->scan_request->no_cck) || + chan_mod == CHANNEL_MODE_PURE_40) { + rate = IWL_RATE_6M_PLCP; + } else { + rate = IWL_RATE_1M_PLCP; + rate_flags = RATE_MCS_CCK_MSK; + } + /* + * Internal scans are passive, so we can indiscriminately set + * the BT ignore flag on 2.4 GHz since it applies to TX only. + */ + if (priv->cfg->bt_params && + priv->cfg->bt_params->advanced_bt_coexist) + scan->tx_cmd.tx_flags |= TX_CMD_FLG_IGNORE_BT; + break; + case IEEE80211_BAND_5GHZ: + rate = IWL_RATE_6M_PLCP; + break; + default: + IWL_WARN(priv, "Invalid scan band\n"); + return -EIO; + } + + /* + * If active scanning is requested but a certain channel is + * marked passive, we can do active scanning if we detect + * transmissions. + * + * There is an issue with some firmware versions that triggers + * a sysassert on a "good CRC threshold" of zero (== disabled), + * on a radar channel even though this means that we should NOT + * send probes. + * + * The "good CRC threshold" is the number of frames that we + * need to receive during our dwell time on a channel before + * sending out probes -- setting this to a huge value will + * mean we never reach it, but at the same time work around + * the aforementioned issue. Thus use IWL_GOOD_CRC_TH_NEVER + * here instead of IWL_GOOD_CRC_TH_DISABLED. + * + * This was fixed in later versions along with some other + * scan changes, and the threshold behaves as a flag in those + * versions. + */ + if (priv->new_scan_threshold_behaviour) + scan->good_CRC_th = is_active ? IWL_GOOD_CRC_TH_DEFAULT : + IWL_GOOD_CRC_TH_DISABLED; + else + scan->good_CRC_th = is_active ? IWL_GOOD_CRC_TH_DEFAULT : + IWL_GOOD_CRC_TH_NEVER; + + band = priv->scan_band; + + if (band == IEEE80211_BAND_2GHZ && + priv->cfg->bt_params && + priv->cfg->bt_params->advanced_bt_coexist) { + /* transmit 2.4 GHz probes only on first antenna */ + scan_tx_antennas = first_antenna(scan_tx_antennas); + } + + priv->scan_tx_ant[band] = iwl_toggle_tx_ant(priv, + priv->scan_tx_ant[band], + scan_tx_antennas); + rate_flags |= iwl_ant_idx_to_flags(priv->scan_tx_ant[band]); + scan->tx_cmd.rate_n_flags = iwl_hw_set_rate_n_flags(rate, rate_flags); + + /* + * In power save mode while associated use one chain, + * otherwise use all chains + */ + if (test_bit(STATUS_POWER_PMI, &priv->status) && + !(priv->hw->conf.flags & IEEE80211_CONF_IDLE)) { + /* rx_ant has been set to all valid chains previously */ + active_chains = rx_ant & + ((u8)(priv->chain_noise_data.active_chains)); + if (!active_chains) + active_chains = rx_ant; + + IWL_DEBUG_SCAN(priv, "chain_noise_data.active_chains: %u\n", + priv->chain_noise_data.active_chains); + + rx_ant = first_antenna(active_chains); + } + if (priv->cfg->bt_params && + priv->cfg->bt_params->advanced_bt_coexist && + priv->bt_full_concurrent) { + /* operated as 1x1 in full concurrency mode */ + rx_ant = first_antenna(rx_ant); + } + + /* MIMO is not used here, but value is required */ + rx_chain |= + priv->nvm_data->valid_rx_ant << RXON_RX_CHAIN_VALID_POS; + rx_chain |= rx_ant << RXON_RX_CHAIN_FORCE_MIMO_SEL_POS; + rx_chain |= rx_ant << RXON_RX_CHAIN_FORCE_SEL_POS; + rx_chain |= 0x1 << RXON_RX_CHAIN_DRIVER_FORCE_POS; + scan->rx_chain = cpu_to_le16(rx_chain); + switch (priv->scan_type) { + case IWL_SCAN_NORMAL: + cmd_len = iwl_fill_probe_req( + (struct ieee80211_mgmt *)scan->data, + vif->addr, + priv->scan_request->ie, + priv->scan_request->ie_len, + ssid, ssid_len, + scan_cmd_size - sizeof(*scan)); + break; + case IWL_SCAN_RADIO_RESET: + case IWL_SCAN_ROC: + /* use bcast addr, will not be transmitted but must be valid */ + cmd_len = iwl_fill_probe_req( + (struct ieee80211_mgmt *)scan->data, + iwl_bcast_addr, NULL, 0, + NULL, 0, + scan_cmd_size - sizeof(*scan)); + break; + default: + BUG(); + } + scan->tx_cmd.len = cpu_to_le16(cmd_len); + + scan->filter_flags |= (RXON_FILTER_ACCEPT_GRP_MSK | + RXON_FILTER_BCON_AWARE_MSK); + + switch (priv->scan_type) { + case IWL_SCAN_RADIO_RESET: + scan->channel_count = + iwl_get_channel_for_reset_scan(priv, vif, band, + (void *)&scan->data[cmd_len]); + break; + case IWL_SCAN_NORMAL: + scan->channel_count = + iwl_get_channels_for_scan(priv, vif, band, + is_active, n_probes, + (void *)&scan->data[cmd_len]); + break; + case IWL_SCAN_ROC: { + struct iwl_scan_channel *scan_ch; + int n_chan, i; + u16 dwell; + + dwell = iwl_limit_dwell(priv, priv->hw_roc_duration); + n_chan = DIV_ROUND_UP(priv->hw_roc_duration, dwell); + + scan->channel_count = n_chan; + + scan_ch = (void *)&scan->data[cmd_len]; + + for (i = 0; i < n_chan; i++) { + scan_ch->type = SCAN_CHANNEL_TYPE_PASSIVE; + scan_ch->channel = + cpu_to_le16(priv->hw_roc_channel->hw_value); + + if (i == n_chan - 1) + dwell = priv->hw_roc_duration - i * dwell; + + scan_ch->active_dwell = + scan_ch->passive_dwell = cpu_to_le16(dwell); + + /* Set txpower levels to defaults */ + scan_ch->dsp_atten = 110; + + /* NOTE: if we were doing 6Mb OFDM for scans we'd use + * power level: + * scan_ch->tx_gain = ((1 << 5) | (2 << 3)) | 3; + */ + if (priv->hw_roc_channel->band == IEEE80211_BAND_5GHZ) + scan_ch->tx_gain = ((1 << 5) | (3 << 3)) | 3; + else + scan_ch->tx_gain = ((1 << 5) | (5 << 3)); + + scan_ch++; + } + } + + break; + } + + if (scan->channel_count == 0) { + IWL_DEBUG_SCAN(priv, "channel count %d\n", scan->channel_count); + return -EIO; + } + + cmd.len[0] += le16_to_cpu(scan->tx_cmd.len) + + scan->channel_count * sizeof(struct iwl_scan_channel); + cmd.data[0] = scan; + cmd.dataflags[0] = IWL_HCMD_DFL_NOCOPY; + scan->len = cpu_to_le16(cmd.len[0]); + + /* set scan bit here for PAN params */ + set_bit(STATUS_SCAN_HW, &priv->status); + + ret = iwlagn_set_pan_params(priv); + if (ret) { + clear_bit(STATUS_SCAN_HW, &priv->status); + return ret; + } + + ret = iwl_dvm_send_cmd(priv, &cmd); + if (ret) { + clear_bit(STATUS_SCAN_HW, &priv->status); + iwlagn_set_pan_params(priv); + } + + return ret; +} + +void iwl_init_scan_params(struct iwl_priv *priv) +{ + u8 ant_idx = fls(priv->nvm_data->valid_tx_ant) - 1; + if (!priv->scan_tx_ant[IEEE80211_BAND_5GHZ]) + priv->scan_tx_ant[IEEE80211_BAND_5GHZ] = ant_idx; + if (!priv->scan_tx_ant[IEEE80211_BAND_2GHZ]) + priv->scan_tx_ant[IEEE80211_BAND_2GHZ] = ant_idx; +} + +int __must_check iwl_scan_initiate(struct iwl_priv *priv, + struct ieee80211_vif *vif, + enum iwl_scan_type scan_type, + enum ieee80211_band band) +{ + int ret; + + lockdep_assert_held(&priv->mutex); + + cancel_delayed_work(&priv->scan_check); + + if (!iwl_is_ready_rf(priv)) { + IWL_WARN(priv, "Request scan called when driver not ready.\n"); + return -EIO; + } + + if (test_bit(STATUS_SCAN_HW, &priv->status)) { + IWL_DEBUG_SCAN(priv, + "Multiple concurrent scan requests in parallel.\n"); + return -EBUSY; + } + + if (test_bit(STATUS_SCAN_ABORTING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Scan request while abort pending.\n"); + return -EBUSY; + } + + IWL_DEBUG_SCAN(priv, "Starting %sscan...\n", + scan_type == IWL_SCAN_NORMAL ? "" : + scan_type == IWL_SCAN_ROC ? "remain-on-channel " : + "internal short "); + + set_bit(STATUS_SCANNING, &priv->status); + priv->scan_type = scan_type; + priv->scan_start = jiffies; + priv->scan_band = band; + + ret = iwlagn_request_scan(priv, vif); + if (ret) { + clear_bit(STATUS_SCANNING, &priv->status); + priv->scan_type = IWL_SCAN_NORMAL; + return ret; + } + + queue_delayed_work(priv->workqueue, &priv->scan_check, + IWL_SCAN_CHECK_WATCHDOG); + + return 0; +} + + +/* + * internal short scan, this function should only been called while associated. + * It will reset and tune the radio to prevent possible RF related problem + */ +void iwl_internal_short_hw_scan(struct iwl_priv *priv) +{ + queue_work(priv->workqueue, &priv->start_internal_scan); +} + +static void iwl_bg_start_internal_scan(struct work_struct *work) +{ + struct iwl_priv *priv = + container_of(work, struct iwl_priv, start_internal_scan); + + IWL_DEBUG_SCAN(priv, "Start internal scan\n"); + + mutex_lock(&priv->mutex); + + if (priv->scan_type == IWL_SCAN_RADIO_RESET) { + IWL_DEBUG_SCAN(priv, "Internal scan already in progress\n"); + goto unlock; + } + + if (test_bit(STATUS_SCANNING, &priv->status)) { + IWL_DEBUG_SCAN(priv, "Scan already in progress.\n"); + goto unlock; + } + + if (iwl_scan_initiate(priv, NULL, IWL_SCAN_RADIO_RESET, priv->band)) + IWL_DEBUG_SCAN(priv, "failed to start internal short scan\n"); + unlock: + mutex_unlock(&priv->mutex); +} + +static void iwl_bg_scan_check(struct work_struct *data) +{ + struct iwl_priv *priv = + container_of(data, struct iwl_priv, scan_check.work); + + IWL_DEBUG_SCAN(priv, "Scan check work\n"); + + /* Since we are here firmware does not finish scan and + * most likely is in bad shape, so we don't bother to + * send abort command, just force scan complete to mac80211 */ + mutex_lock(&priv->mutex); + iwl_force_scan_end(priv); + mutex_unlock(&priv->mutex); +} + +static void iwl_bg_abort_scan(struct work_struct *work) +{ + struct iwl_priv *priv = container_of(work, struct iwl_priv, abort_scan); + + IWL_DEBUG_SCAN(priv, "Abort scan work\n"); + + /* We keep scan_check work queued in case when firmware will not + * report back scan completed notification */ + mutex_lock(&priv->mutex); + iwl_scan_cancel_timeout(priv, 200); + mutex_unlock(&priv->mutex); +} + +static void iwl_bg_scan_completed(struct work_struct *work) +{ + struct iwl_priv *priv = + container_of(work, struct iwl_priv, scan_completed); + + mutex_lock(&priv->mutex); + iwl_process_scan_complete(priv); + mutex_unlock(&priv->mutex); +} + +void iwl_setup_scan_deferred_work(struct iwl_priv *priv) +{ + INIT_WORK(&priv->scan_completed, iwl_bg_scan_completed); + INIT_WORK(&priv->abort_scan, iwl_bg_abort_scan); + INIT_WORK(&priv->start_internal_scan, iwl_bg_start_internal_scan); + INIT_DELAYED_WORK(&priv->scan_check, iwl_bg_scan_check); +} + +void iwl_cancel_scan_deferred_work(struct iwl_priv *priv) +{ + cancel_work_sync(&priv->start_internal_scan); + cancel_work_sync(&priv->abort_scan); + cancel_work_sync(&priv->scan_completed); + + if (cancel_delayed_work_sync(&priv->scan_check)) { + mutex_lock(&priv->mutex); + iwl_force_scan_end(priv); + mutex_unlock(&priv->mutex); + } +} + +void iwl_scan_roc_expired(struct iwl_priv *priv) +{ + /* + * The status bit should be set here, to prevent a race + * where the atomic_read returns 1, but before the execution continues + * iwl_scan_offchannel_skb_status() checks if the status bit is set + */ + set_bit(STATUS_SCAN_ROC_EXPIRED, &priv->status); + + if (atomic_read(&priv->num_aux_in_flight) == 0) { + ieee80211_remain_on_channel_expired(priv->hw); + priv->hw_roc_channel = NULL; + schedule_delayed_work(&priv->hw_roc_disable_work, + 10 * HZ); + + clear_bit(STATUS_SCAN_ROC_EXPIRED, &priv->status); + } else { + IWL_DEBUG_SCAN(priv, "ROC done with %d frames in aux\n", + atomic_read(&priv->num_aux_in_flight)); + } +} + +void iwl_scan_offchannel_skb(struct iwl_priv *priv) +{ + WARN_ON(!priv->hw_roc_start_notified); + atomic_inc(&priv->num_aux_in_flight); +} + +void iwl_scan_offchannel_skb_status(struct iwl_priv *priv) +{ + if (atomic_dec_return(&priv->num_aux_in_flight) == 0 && + test_bit(STATUS_SCAN_ROC_EXPIRED, &priv->status)) { + IWL_DEBUG_SCAN(priv, "0 aux frames. Calling ROC expired\n"); + iwl_scan_roc_expired(priv); + } +} diff --git a/drivers/net/wireless/iwlwifi/dvm/sta.c b/drivers/net/wireless/iwlwifi/dvm/sta.c new file mode 100644 index 0000000..8212097 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/sta.c @@ -0,0 +1,1477 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ +#include +#include +#include "iwl-trans.h" +#include "dev.h" +#include "agn.h" + +const u8 iwl_bcast_addr[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + +static int iwl_sta_ucode_activate(struct iwl_priv *priv, u8 sta_id) +{ + lockdep_assert_held(&priv->sta_lock); + + if (sta_id >= IWLAGN_STATION_COUNT) { + IWL_ERR(priv, "invalid sta_id %u", sta_id); + return -EINVAL; + } + if (!(priv->stations[sta_id].used & IWL_STA_DRIVER_ACTIVE)) + IWL_ERR(priv, "ACTIVATE a non DRIVER active station id %u " + "addr %pM\n", + sta_id, priv->stations[sta_id].sta.sta.addr); + + if (priv->stations[sta_id].used & IWL_STA_UCODE_ACTIVE) { + IWL_DEBUG_ASSOC(priv, + "STA id %u addr %pM already present in uCode " + "(according to driver)\n", + sta_id, priv->stations[sta_id].sta.sta.addr); + } else { + priv->stations[sta_id].used |= IWL_STA_UCODE_ACTIVE; + IWL_DEBUG_ASSOC(priv, "Added STA id %u addr %pM to uCode\n", + sta_id, priv->stations[sta_id].sta.sta.addr); + } + return 0; +} + +static int iwl_process_add_sta_resp(struct iwl_priv *priv, + struct iwl_addsta_cmd *addsta, + struct iwl_rx_packet *pkt) +{ + struct iwl_add_sta_resp *add_sta_resp = (void *)pkt->data; + u8 sta_id = addsta->sta.sta_id; + int ret = -EIO; + + if (pkt->hdr.flags & IWL_CMD_FAILED_MSK) { + IWL_ERR(priv, "Bad return from REPLY_ADD_STA (0x%08X)\n", + pkt->hdr.flags); + return ret; + } + + IWL_DEBUG_INFO(priv, "Processing response for adding station %u\n", + sta_id); + + spin_lock_bh(&priv->sta_lock); + + switch (add_sta_resp->status) { + case ADD_STA_SUCCESS_MSK: + IWL_DEBUG_INFO(priv, "REPLY_ADD_STA PASSED\n"); + ret = iwl_sta_ucode_activate(priv, sta_id); + break; + case ADD_STA_NO_ROOM_IN_TABLE: + IWL_ERR(priv, "Adding station %d failed, no room in table.\n", + sta_id); + break; + case ADD_STA_NO_BLOCK_ACK_RESOURCE: + IWL_ERR(priv, "Adding station %d failed, no block ack " + "resource.\n", sta_id); + break; + case ADD_STA_MODIFY_NON_EXIST_STA: + IWL_ERR(priv, "Attempting to modify non-existing station %d\n", + sta_id); + break; + default: + IWL_DEBUG_ASSOC(priv, "Received REPLY_ADD_STA:(0x%08X)\n", + add_sta_resp->status); + break; + } + + IWL_DEBUG_INFO(priv, "%s station id %u addr %pM\n", + priv->stations[sta_id].sta.mode == + STA_CONTROL_MODIFY_MSK ? "Modified" : "Added", + sta_id, priv->stations[sta_id].sta.sta.addr); + + /* + * XXX: The MAC address in the command buffer is often changed from + * the original sent to the device. That is, the MAC address + * written to the command buffer often is not the same MAC address + * read from the command buffer when the command returns. This + * issue has not yet been resolved and this debugging is left to + * observe the problem. + */ + IWL_DEBUG_INFO(priv, "%s station according to cmd buffer %pM\n", + priv->stations[sta_id].sta.mode == + STA_CONTROL_MODIFY_MSK ? "Modified" : "Added", + addsta->sta.addr); + spin_unlock_bh(&priv->sta_lock); + + return ret; +} + +int iwl_add_sta_callback(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + + if (!cmd) + return 0; + + return iwl_process_add_sta_resp(priv, (void *)cmd->payload, pkt); +} + +int iwl_send_add_sta(struct iwl_priv *priv, + struct iwl_addsta_cmd *sta, u8 flags) +{ + int ret = 0; + struct iwl_host_cmd cmd = { + .id = REPLY_ADD_STA, + .flags = flags, + .data = { sta, }, + .len = { sizeof(*sta), }, + }; + u8 sta_id __maybe_unused = sta->sta.sta_id; + + IWL_DEBUG_INFO(priv, "Adding sta %u (%pM) %ssynchronously\n", + sta_id, sta->sta.addr, flags & CMD_ASYNC ? "a" : ""); + + if (!(flags & CMD_ASYNC)) { + cmd.flags |= CMD_WANT_SKB; + might_sleep(); + } + + ret = iwl_dvm_send_cmd(priv, &cmd); + + if (ret || (flags & CMD_ASYNC)) + return ret; + /*else the command was successfully sent in SYNC mode, need to free + * the reply page */ + + iwl_free_resp(&cmd); + + if (cmd.handler_status) + IWL_ERR(priv, "%s - error in the CMD response %d", __func__, + cmd.handler_status); + + return cmd.handler_status; +} + +bool iwl_is_ht40_tx_allowed(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_sta *sta) +{ + if (!ctx->ht.enabled || !ctx->ht.is_40mhz) + return false; + +#ifdef CPTCFG_IWLWIFI_DEBUGFS + if (priv->disable_ht40) + return false; +#endif + + /* special case for RXON */ + if (!sta) + return true; + + return sta->bandwidth >= IEEE80211_STA_RX_BW_40; +} + +static void iwl_sta_calc_ht_flags(struct iwl_priv *priv, + struct ieee80211_sta *sta, + struct iwl_rxon_context *ctx, + __le32 *flags, __le32 *mask) +{ + struct ieee80211_sta_ht_cap *sta_ht_inf = &sta->ht_cap; + + *mask = STA_FLG_RTS_MIMO_PROT_MSK | + STA_FLG_MIMO_DIS_MSK | + STA_FLG_HT40_EN_MSK | + STA_FLG_MAX_AGG_SIZE_MSK | + STA_FLG_AGG_MPDU_DENSITY_MSK; + *flags = 0; + + if (!sta || !sta_ht_inf->ht_supported) + return; + + IWL_DEBUG_INFO(priv, "STA %pM SM PS mode: %s\n", + sta->addr, + (sta->smps_mode == IEEE80211_SMPS_STATIC) ? + "static" : + (sta->smps_mode == IEEE80211_SMPS_DYNAMIC) ? + "dynamic" : "disabled"); + + switch (sta->smps_mode) { + case IEEE80211_SMPS_STATIC: + *flags |= STA_FLG_MIMO_DIS_MSK; + break; + case IEEE80211_SMPS_DYNAMIC: + *flags |= STA_FLG_RTS_MIMO_PROT_MSK; + break; + case IEEE80211_SMPS_OFF: + break; + default: + IWL_WARN(priv, "Invalid MIMO PS mode %d\n", sta->smps_mode); + break; + } + + *flags |= cpu_to_le32( + (u32)sta_ht_inf->ampdu_factor << STA_FLG_MAX_AGG_SIZE_POS); + + *flags |= cpu_to_le32( + (u32)sta_ht_inf->ampdu_density << STA_FLG_AGG_MPDU_DENSITY_POS); + + if (iwl_is_ht40_tx_allowed(priv, ctx, sta)) + *flags |= STA_FLG_HT40_EN_MSK; +} + +int iwl_sta_update_ht(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + struct ieee80211_sta *sta) +{ + u8 sta_id = iwl_sta_id(sta); + __le32 flags, mask; + struct iwl_addsta_cmd cmd; + + if (WARN_ON_ONCE(sta_id == IWL_INVALID_STATION)) + return -EINVAL; + + iwl_sta_calc_ht_flags(priv, sta, ctx, &flags, &mask); + + spin_lock_bh(&priv->sta_lock); + priv->stations[sta_id].sta.station_flags &= ~mask; + priv->stations[sta_id].sta.station_flags |= flags; + spin_unlock_bh(&priv->sta_lock); + + memset(&cmd, 0, sizeof(cmd)); + cmd.mode = STA_CONTROL_MODIFY_MSK; + cmd.station_flags_msk = mask; + cmd.station_flags = flags; + cmd.sta.sta_id = sta_id; + + return iwl_send_add_sta(priv, &cmd, CMD_SYNC); +} + +static void iwl_set_ht_add_station(struct iwl_priv *priv, u8 index, + struct ieee80211_sta *sta, + struct iwl_rxon_context *ctx) +{ + __le32 flags, mask; + + iwl_sta_calc_ht_flags(priv, sta, ctx, &flags, &mask); + + lockdep_assert_held(&priv->sta_lock); + priv->stations[index].sta.station_flags &= ~mask; + priv->stations[index].sta.station_flags |= flags; +} + +/** + * iwl_prep_station - Prepare station information for addition + * + * should be called with sta_lock held + */ +u8 iwl_prep_station(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + const u8 *addr, bool is_ap, struct ieee80211_sta *sta) +{ + struct iwl_station_entry *station; + int i; + u8 sta_id = IWL_INVALID_STATION; + + if (is_ap) + sta_id = ctx->ap_sta_id; + else if (is_broadcast_ether_addr(addr)) + sta_id = ctx->bcast_sta_id; + else + for (i = IWL_STA_ID; i < IWLAGN_STATION_COUNT; i++) { + if (ether_addr_equal(priv->stations[i].sta.sta.addr, + addr)) { + sta_id = i; + break; + } + + if (!priv->stations[i].used && + sta_id == IWL_INVALID_STATION) + sta_id = i; + } + + /* + * These two conditions have the same outcome, but keep them + * separate + */ + if (unlikely(sta_id == IWL_INVALID_STATION)) + return sta_id; + + /* + * uCode is not able to deal with multiple requests to add a + * station. Keep track if one is in progress so that we do not send + * another. + */ + if (priv->stations[sta_id].used & IWL_STA_UCODE_INPROGRESS) { + IWL_DEBUG_INFO(priv, "STA %d already in process of being " + "added.\n", sta_id); + return sta_id; + } + + if ((priv->stations[sta_id].used & IWL_STA_DRIVER_ACTIVE) && + (priv->stations[sta_id].used & IWL_STA_UCODE_ACTIVE) && + ether_addr_equal(priv->stations[sta_id].sta.sta.addr, addr)) { + IWL_DEBUG_ASSOC(priv, "STA %d (%pM) already added, not " + "adding again.\n", sta_id, addr); + return sta_id; + } + + station = &priv->stations[sta_id]; + station->used = IWL_STA_DRIVER_ACTIVE; + IWL_DEBUG_ASSOC(priv, "Add STA to driver ID %d: %pM\n", + sta_id, addr); + priv->num_stations++; + + /* Set up the REPLY_ADD_STA command to send to device */ + memset(&station->sta, 0, sizeof(struct iwl_addsta_cmd)); + memcpy(station->sta.sta.addr, addr, ETH_ALEN); + station->sta.mode = 0; + station->sta.sta.sta_id = sta_id; + station->sta.station_flags = ctx->station_flags; + station->ctxid = ctx->ctxid; + + if (sta) { + struct iwl_station_priv *sta_priv; + + sta_priv = (void *)sta->drv_priv; + sta_priv->ctx = ctx; + } + + /* + * OK to call unconditionally, since local stations (IBSS BSSID + * STA and broadcast STA) pass in a NULL sta, and mac80211 + * doesn't allow HT IBSS. + */ + iwl_set_ht_add_station(priv, sta_id, sta, ctx); + + return sta_id; + +} + +#define STA_WAIT_TIMEOUT (HZ/2) + +/** + * iwl_add_station_common - + */ +int iwl_add_station_common(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + const u8 *addr, bool is_ap, + struct ieee80211_sta *sta, u8 *sta_id_r) +{ + int ret = 0; + u8 sta_id; + struct iwl_addsta_cmd sta_cmd; + + *sta_id_r = 0; + spin_lock_bh(&priv->sta_lock); + sta_id = iwl_prep_station(priv, ctx, addr, is_ap, sta); + if (sta_id == IWL_INVALID_STATION) { + IWL_ERR(priv, "Unable to prepare station %pM for addition\n", + addr); + spin_unlock_bh(&priv->sta_lock); + return -EINVAL; + } + + /* + * uCode is not able to deal with multiple requests to add a + * station. Keep track if one is in progress so that we do not send + * another. + */ + if (priv->stations[sta_id].used & IWL_STA_UCODE_INPROGRESS) { + IWL_DEBUG_INFO(priv, "STA %d already in process of being " + "added.\n", sta_id); + spin_unlock_bh(&priv->sta_lock); + return -EEXIST; + } + + if ((priv->stations[sta_id].used & IWL_STA_DRIVER_ACTIVE) && + (priv->stations[sta_id].used & IWL_STA_UCODE_ACTIVE)) { + IWL_DEBUG_ASSOC(priv, "STA %d (%pM) already added, not " + "adding again.\n", sta_id, addr); + spin_unlock_bh(&priv->sta_lock); + return -EEXIST; + } + + priv->stations[sta_id].used |= IWL_STA_UCODE_INPROGRESS; + memcpy(&sta_cmd, &priv->stations[sta_id].sta, + sizeof(struct iwl_addsta_cmd)); + spin_unlock_bh(&priv->sta_lock); + + /* Add station to device's station table */ + ret = iwl_send_add_sta(priv, &sta_cmd, CMD_SYNC); + if (ret) { + spin_lock_bh(&priv->sta_lock); + IWL_ERR(priv, "Adding station %pM failed.\n", + priv->stations[sta_id].sta.sta.addr); + priv->stations[sta_id].used &= ~IWL_STA_DRIVER_ACTIVE; + priv->stations[sta_id].used &= ~IWL_STA_UCODE_INPROGRESS; + spin_unlock_bh(&priv->sta_lock); + } + *sta_id_r = sta_id; + return ret; +} + +/** + * iwl_sta_ucode_deactivate - deactivate ucode status for a station + */ +static void iwl_sta_ucode_deactivate(struct iwl_priv *priv, u8 sta_id) +{ + lockdep_assert_held(&priv->sta_lock); + + /* Ucode must be active and driver must be non active */ + if ((priv->stations[sta_id].used & + (IWL_STA_UCODE_ACTIVE | IWL_STA_DRIVER_ACTIVE)) != + IWL_STA_UCODE_ACTIVE) + IWL_ERR(priv, "removed non active STA %u\n", sta_id); + + priv->stations[sta_id].used &= ~IWL_STA_UCODE_ACTIVE; + + memset(&priv->stations[sta_id], 0, sizeof(struct iwl_station_entry)); + IWL_DEBUG_ASSOC(priv, "Removed STA %u\n", sta_id); +} + +static int iwl_send_remove_station(struct iwl_priv *priv, + const u8 *addr, int sta_id, + bool temporary) +{ + struct iwl_rx_packet *pkt; + int ret; + struct iwl_rem_sta_cmd rm_sta_cmd; + + struct iwl_host_cmd cmd = { + .id = REPLY_REMOVE_STA, + .len = { sizeof(struct iwl_rem_sta_cmd), }, + .flags = CMD_SYNC, + .data = { &rm_sta_cmd, }, + }; + + memset(&rm_sta_cmd, 0, sizeof(rm_sta_cmd)); + rm_sta_cmd.num_sta = 1; + memcpy(&rm_sta_cmd.addr, addr, ETH_ALEN); + + cmd.flags |= CMD_WANT_SKB; + + ret = iwl_dvm_send_cmd(priv, &cmd); + + if (ret) + return ret; + + pkt = cmd.resp_pkt; + if (pkt->hdr.flags & IWL_CMD_FAILED_MSK) { + IWL_ERR(priv, "Bad return from REPLY_REMOVE_STA (0x%08X)\n", + pkt->hdr.flags); + ret = -EIO; + } + + if (!ret) { + struct iwl_rem_sta_resp *rem_sta_resp = (void *)pkt->data; + switch (rem_sta_resp->status) { + case REM_STA_SUCCESS_MSK: + if (!temporary) { + spin_lock_bh(&priv->sta_lock); + iwl_sta_ucode_deactivate(priv, sta_id); + spin_unlock_bh(&priv->sta_lock); + } + IWL_DEBUG_ASSOC(priv, "REPLY_REMOVE_STA PASSED\n"); + break; + default: + ret = -EIO; + IWL_ERR(priv, "REPLY_REMOVE_STA failed\n"); + break; + } + } + iwl_free_resp(&cmd); + + return ret; +} + +/** + * iwl_remove_station - Remove driver's knowledge of station. + */ +int iwl_remove_station(struct iwl_priv *priv, const u8 sta_id, + const u8 *addr) +{ + u8 tid; + + if (!iwl_is_ready(priv)) { + IWL_DEBUG_INFO(priv, + "Unable to remove station %pM, device not ready.\n", + addr); + /* + * It is typical for stations to be removed when we are + * going down. Return success since device will be down + * soon anyway + */ + return 0; + } + + IWL_DEBUG_ASSOC(priv, "Removing STA from driver:%d %pM\n", + sta_id, addr); + + if (WARN_ON(sta_id == IWL_INVALID_STATION)) + return -EINVAL; + + spin_lock_bh(&priv->sta_lock); + + if (!(priv->stations[sta_id].used & IWL_STA_DRIVER_ACTIVE)) { + IWL_DEBUG_INFO(priv, "Removing %pM but non DRIVER active\n", + addr); + goto out_err; + } + + if (!(priv->stations[sta_id].used & IWL_STA_UCODE_ACTIVE)) { + IWL_DEBUG_INFO(priv, "Removing %pM but non UCODE active\n", + addr); + goto out_err; + } + + if (priv->stations[sta_id].used & IWL_STA_LOCAL) { + kfree(priv->stations[sta_id].lq); + priv->stations[sta_id].lq = NULL; + } + + for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) + memset(&priv->tid_data[sta_id][tid], 0, + sizeof(priv->tid_data[sta_id][tid])); + + priv->stations[sta_id].used &= ~IWL_STA_DRIVER_ACTIVE; + + priv->num_stations--; + + if (WARN_ON(priv->num_stations < 0)) + priv->num_stations = 0; + + spin_unlock_bh(&priv->sta_lock); + + return iwl_send_remove_station(priv, addr, sta_id, false); +out_err: + spin_unlock_bh(&priv->sta_lock); + return -EINVAL; +} + +void iwl_deactivate_station(struct iwl_priv *priv, const u8 sta_id, + const u8 *addr) +{ + u8 tid; + + if (!iwl_is_ready(priv)) { + IWL_DEBUG_INFO(priv, + "Unable to remove station %pM, device not ready.\n", + addr); + return; + } + + IWL_DEBUG_ASSOC(priv, "Deactivating STA: %pM (%d)\n", addr, sta_id); + + if (WARN_ON_ONCE(sta_id == IWL_INVALID_STATION)) + return; + + spin_lock_bh(&priv->sta_lock); + + WARN_ON_ONCE(!(priv->stations[sta_id].used & IWL_STA_DRIVER_ACTIVE)); + + for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) + memset(&priv->tid_data[sta_id][tid], 0, + sizeof(priv->tid_data[sta_id][tid])); + + priv->stations[sta_id].used &= ~IWL_STA_DRIVER_ACTIVE; + + priv->num_stations--; + + if (WARN_ON_ONCE(priv->num_stations < 0)) + priv->num_stations = 0; + + spin_unlock_bh(&priv->sta_lock); +} + +static void iwl_sta_fill_lq(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + u8 sta_id, struct iwl_link_quality_cmd *link_cmd) +{ + int i, r; + u32 rate_flags = 0; + __le32 rate_n_flags; + + lockdep_assert_held(&priv->mutex); + + memset(link_cmd, 0, sizeof(*link_cmd)); + + /* Set up the rate scaling to start at selected rate, fall back + * all the way down to 1M in IEEE order, and then spin on 1M */ + if (priv->band == IEEE80211_BAND_5GHZ) + r = IWL_RATE_6M_INDEX; + else if (ctx && ctx->vif && ctx->vif->p2p) + r = IWL_RATE_6M_INDEX; + else + r = IWL_RATE_1M_INDEX; + + if (r >= IWL_FIRST_CCK_RATE && r <= IWL_LAST_CCK_RATE) + rate_flags |= RATE_MCS_CCK_MSK; + + rate_flags |= first_antenna(priv->nvm_data->valid_tx_ant) << + RATE_MCS_ANT_POS; + rate_n_flags = iwl_hw_set_rate_n_flags(iwl_rates[r].plcp, rate_flags); + for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) + link_cmd->rs_table[i].rate_n_flags = rate_n_flags; + + link_cmd->general_params.single_stream_ant_msk = + first_antenna(priv->nvm_data->valid_tx_ant); + + link_cmd->general_params.dual_stream_ant_msk = + priv->nvm_data->valid_tx_ant & + ~first_antenna(priv->nvm_data->valid_tx_ant); + if (!link_cmd->general_params.dual_stream_ant_msk) { + link_cmd->general_params.dual_stream_ant_msk = ANT_AB; + } else if (num_of_ant(priv->nvm_data->valid_tx_ant) == 2) { + link_cmd->general_params.dual_stream_ant_msk = + priv->nvm_data->valid_tx_ant; + } + + link_cmd->agg_params.agg_dis_start_th = + LINK_QUAL_AGG_DISABLE_START_DEF; + link_cmd->agg_params.agg_time_limit = + cpu_to_le16(LINK_QUAL_AGG_TIME_LIMIT_DEF); + + link_cmd->sta_id = sta_id; +} + +/** + * iwl_clear_ucode_stations - clear ucode station table bits + * + * This function clears all the bits in the driver indicating + * which stations are active in the ucode. Call when something + * other than explicit station management would cause this in + * the ucode, e.g. unassociated RXON. + */ +void iwl_clear_ucode_stations(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + int i; + bool cleared = false; + + IWL_DEBUG_INFO(priv, "Clearing ucode stations in driver\n"); + + spin_lock_bh(&priv->sta_lock); + for (i = 0; i < IWLAGN_STATION_COUNT; i++) { + if (ctx && ctx->ctxid != priv->stations[i].ctxid) + continue; + + if (priv->stations[i].used & IWL_STA_UCODE_ACTIVE) { + IWL_DEBUG_INFO(priv, + "Clearing ucode active for station %d\n", i); + priv->stations[i].used &= ~IWL_STA_UCODE_ACTIVE; + cleared = true; + } + } + spin_unlock_bh(&priv->sta_lock); + + if (!cleared) + IWL_DEBUG_INFO(priv, + "No active stations found to be cleared\n"); +} + +/** + * iwl_restore_stations() - Restore driver known stations to device + * + * All stations considered active by driver, but not present in ucode, is + * restored. + * + * Function sleeps. + */ +void iwl_restore_stations(struct iwl_priv *priv, struct iwl_rxon_context *ctx) +{ + struct iwl_addsta_cmd sta_cmd; + static const struct iwl_link_quality_cmd zero_lq = {}; + struct iwl_link_quality_cmd lq; + int i; + bool found = false; + int ret; + bool send_lq; + + if (!iwl_is_ready(priv)) { + IWL_DEBUG_INFO(priv, + "Not ready yet, not restoring any stations.\n"); + return; + } + + IWL_DEBUG_ASSOC(priv, "Restoring all known stations ... start.\n"); + spin_lock_bh(&priv->sta_lock); + for (i = 0; i < IWLAGN_STATION_COUNT; i++) { + if (ctx->ctxid != priv->stations[i].ctxid) + continue; + if ((priv->stations[i].used & IWL_STA_DRIVER_ACTIVE) && + !(priv->stations[i].used & IWL_STA_UCODE_ACTIVE)) { + IWL_DEBUG_ASSOC(priv, "Restoring sta %pM\n", + priv->stations[i].sta.sta.addr); + priv->stations[i].sta.mode = 0; + priv->stations[i].used |= IWL_STA_UCODE_INPROGRESS; + found = true; + } + } + + for (i = 0; i < IWLAGN_STATION_COUNT; i++) { + if ((priv->stations[i].used & IWL_STA_UCODE_INPROGRESS)) { + memcpy(&sta_cmd, &priv->stations[i].sta, + sizeof(struct iwl_addsta_cmd)); + send_lq = false; + if (priv->stations[i].lq) { + if (priv->wowlan) + iwl_sta_fill_lq(priv, ctx, i, &lq); + else + memcpy(&lq, priv->stations[i].lq, + sizeof(struct iwl_link_quality_cmd)); + + if (memcmp(&lq, &zero_lq, sizeof(lq))) + send_lq = true; + } + spin_unlock_bh(&priv->sta_lock); + ret = iwl_send_add_sta(priv, &sta_cmd, CMD_SYNC); + if (ret) { + spin_lock_bh(&priv->sta_lock); + IWL_ERR(priv, "Adding station %pM failed.\n", + priv->stations[i].sta.sta.addr); + priv->stations[i].used &= + ~IWL_STA_DRIVER_ACTIVE; + priv->stations[i].used &= + ~IWL_STA_UCODE_INPROGRESS; + continue; + } + /* + * Rate scaling has already been initialized, send + * current LQ command + */ + if (send_lq) + iwl_send_lq_cmd(priv, ctx, &lq, + CMD_SYNC, true); + spin_lock_bh(&priv->sta_lock); + priv->stations[i].used &= ~IWL_STA_UCODE_INPROGRESS; + } + } + + spin_unlock_bh(&priv->sta_lock); + if (!found) + IWL_DEBUG_INFO(priv, "Restoring all known stations .... " + "no stations to be restored.\n"); + else + IWL_DEBUG_INFO(priv, "Restoring all known stations .... " + "complete.\n"); +} + +int iwl_get_free_ucode_key_offset(struct iwl_priv *priv) +{ + int i; + + for (i = 0; i < priv->sta_key_max_num; i++) + if (!test_and_set_bit(i, &priv->ucode_key_table)) + return i; + + return WEP_INVALID_OFFSET; +} + +void iwl_dealloc_bcast_stations(struct iwl_priv *priv) +{ + int i; + + spin_lock_bh(&priv->sta_lock); + for (i = 0; i < IWLAGN_STATION_COUNT; i++) { + if (!(priv->stations[i].used & IWL_STA_BCAST)) + continue; + + priv->stations[i].used &= ~IWL_STA_UCODE_ACTIVE; + priv->num_stations--; + if (WARN_ON(priv->num_stations < 0)) + priv->num_stations = 0; + kfree(priv->stations[i].lq); + priv->stations[i].lq = NULL; + } + spin_unlock_bh(&priv->sta_lock); +} + +#ifdef CPTCFG_IWLWIFI_DEBUG +static void iwl_dump_lq_cmd(struct iwl_priv *priv, + struct iwl_link_quality_cmd *lq) +{ + int i; + IWL_DEBUG_RATE(priv, "lq station id 0x%x\n", lq->sta_id); + IWL_DEBUG_RATE(priv, "lq ant 0x%X 0x%X\n", + lq->general_params.single_stream_ant_msk, + lq->general_params.dual_stream_ant_msk); + + for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) + IWL_DEBUG_RATE(priv, "lq index %d 0x%X\n", + i, lq->rs_table[i].rate_n_flags); +} +#else +static inline void iwl_dump_lq_cmd(struct iwl_priv *priv, + struct iwl_link_quality_cmd *lq) +{ +} +#endif + +/** + * is_lq_table_valid() - Test one aspect of LQ cmd for validity + * + * It sometimes happens when a HT rate has been in use and we + * loose connectivity with AP then mac80211 will first tell us that the + * current channel is not HT anymore before removing the station. In such a + * scenario the RXON flags will be updated to indicate we are not + * communicating HT anymore, but the LQ command may still contain HT rates. + * Test for this to prevent driver from sending LQ command between the time + * RXON flags are updated and when LQ command is updated. + */ +static bool is_lq_table_valid(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct iwl_link_quality_cmd *lq) +{ + int i; + + if (ctx->ht.enabled) + return true; + + IWL_DEBUG_INFO(priv, "Channel %u is not an HT channel\n", + ctx->active.channel); + for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) { + if (le32_to_cpu(lq->rs_table[i].rate_n_flags) & + RATE_MCS_HT_MSK) { + IWL_DEBUG_INFO(priv, + "index %d of LQ expects HT channel\n", + i); + return false; + } + } + return true; +} + +/** + * iwl_send_lq_cmd() - Send link quality command + * @init: This command is sent as part of station initialization right + * after station has been added. + * + * The link quality command is sent as the last step of station creation. + * This is the special case in which init is set and we call a callback in + * this case to clear the state indicating that station creation is in + * progress. + */ +int iwl_send_lq_cmd(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + struct iwl_link_quality_cmd *lq, u8 flags, bool init) +{ + int ret = 0; + struct iwl_host_cmd cmd = { + .id = REPLY_TX_LINK_QUALITY_CMD, + .len = { sizeof(struct iwl_link_quality_cmd), }, + .flags = flags, + .data = { lq, }, + }; + + if (WARN_ON(lq->sta_id == IWL_INVALID_STATION)) + return -EINVAL; + + + spin_lock_bh(&priv->sta_lock); + if (!(priv->stations[lq->sta_id].used & IWL_STA_DRIVER_ACTIVE)) { + spin_unlock_bh(&priv->sta_lock); + return -EINVAL; + } + spin_unlock_bh(&priv->sta_lock); + + iwl_dump_lq_cmd(priv, lq); + if (WARN_ON(init && (cmd.flags & CMD_ASYNC))) + return -EINVAL; + + if (is_lq_table_valid(priv, ctx, lq)) + ret = iwl_dvm_send_cmd(priv, &cmd); + else + ret = -EINVAL; + + if (cmd.flags & CMD_ASYNC) + return ret; + + if (init) { + IWL_DEBUG_INFO(priv, "init LQ command complete, " + "clearing sta addition status for sta %d\n", + lq->sta_id); + spin_lock_bh(&priv->sta_lock); + priv->stations[lq->sta_id].used &= ~IWL_STA_UCODE_INPROGRESS; + spin_unlock_bh(&priv->sta_lock); + } + return ret; +} + + +static struct iwl_link_quality_cmd * +iwl_sta_alloc_lq(struct iwl_priv *priv, struct iwl_rxon_context *ctx, + u8 sta_id) +{ + struct iwl_link_quality_cmd *link_cmd; + + link_cmd = kzalloc(sizeof(struct iwl_link_quality_cmd), GFP_KERNEL); + if (!link_cmd) { + IWL_ERR(priv, "Unable to allocate memory for LQ cmd.\n"); + return NULL; + } + + iwl_sta_fill_lq(priv, ctx, sta_id, link_cmd); + + return link_cmd; +} + +/* + * iwlagn_add_bssid_station - Add the special IBSS BSSID station + * + * Function sleeps. + */ +int iwlagn_add_bssid_station(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + const u8 *addr, u8 *sta_id_r) +{ + int ret; + u8 sta_id; + struct iwl_link_quality_cmd *link_cmd; + + if (sta_id_r) + *sta_id_r = IWL_INVALID_STATION; + + ret = iwl_add_station_common(priv, ctx, addr, 0, NULL, &sta_id); + if (ret) { + IWL_ERR(priv, "Unable to add station %pM\n", addr); + return ret; + } + + if (sta_id_r) + *sta_id_r = sta_id; + + spin_lock_bh(&priv->sta_lock); + priv->stations[sta_id].used |= IWL_STA_LOCAL; + spin_unlock_bh(&priv->sta_lock); + + /* Set up default rate scaling table in device's station table */ + link_cmd = iwl_sta_alloc_lq(priv, ctx, sta_id); + if (!link_cmd) { + IWL_ERR(priv, + "Unable to initialize rate scaling for station %pM.\n", + addr); + return -ENOMEM; + } + + ret = iwl_send_lq_cmd(priv, ctx, link_cmd, CMD_SYNC, true); + if (ret) + IWL_ERR(priv, "Link quality command failed (%d)\n", ret); + + spin_lock_bh(&priv->sta_lock); + priv->stations[sta_id].lq = link_cmd; + spin_unlock_bh(&priv->sta_lock); + + return 0; +} + +/* + * static WEP keys + * + * For each context, the device has a table of 4 static WEP keys + * (one for each key index) that is updated with the following + * commands. + */ + +static int iwl_send_static_wepkey_cmd(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + bool send_if_empty) +{ + int i, not_empty = 0; + u8 buff[sizeof(struct iwl_wep_cmd) + + sizeof(struct iwl_wep_key) * WEP_KEYS_MAX]; + struct iwl_wep_cmd *wep_cmd = (struct iwl_wep_cmd *)buff; + size_t cmd_size = sizeof(struct iwl_wep_cmd); + struct iwl_host_cmd cmd = { + .id = ctx->wep_key_cmd, + .data = { wep_cmd, }, + .flags = CMD_SYNC, + }; + + might_sleep(); + + memset(wep_cmd, 0, cmd_size + + (sizeof(struct iwl_wep_key) * WEP_KEYS_MAX)); + + for (i = 0; i < WEP_KEYS_MAX ; i++) { + wep_cmd->key[i].key_index = i; + if (ctx->wep_keys[i].key_size) { + wep_cmd->key[i].key_offset = i; + not_empty = 1; + } else { + wep_cmd->key[i].key_offset = WEP_INVALID_OFFSET; + } + + wep_cmd->key[i].key_size = ctx->wep_keys[i].key_size; + memcpy(&wep_cmd->key[i].key[3], ctx->wep_keys[i].key, + ctx->wep_keys[i].key_size); + } + + wep_cmd->global_key_type = WEP_KEY_WEP_TYPE; + wep_cmd->num_keys = WEP_KEYS_MAX; + + cmd_size += sizeof(struct iwl_wep_key) * WEP_KEYS_MAX; + + cmd.len[0] = cmd_size; + + if (not_empty || send_if_empty) + return iwl_dvm_send_cmd(priv, &cmd); + else + return 0; +} + +int iwl_restore_default_wep_keys(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + lockdep_assert_held(&priv->mutex); + + return iwl_send_static_wepkey_cmd(priv, ctx, false); +} + +int iwl_remove_default_wep_key(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_key_conf *keyconf) +{ + int ret; + + lockdep_assert_held(&priv->mutex); + + IWL_DEBUG_WEP(priv, "Removing default WEP key: idx=%d\n", + keyconf->keyidx); + + memset(&ctx->wep_keys[keyconf->keyidx], 0, sizeof(ctx->wep_keys[0])); + if (iwl_is_rfkill(priv)) { + IWL_DEBUG_WEP(priv, + "Not sending REPLY_WEPKEY command due to RFKILL.\n"); + /* but keys in device are clear anyway so return success */ + return 0; + } + ret = iwl_send_static_wepkey_cmd(priv, ctx, 1); + IWL_DEBUG_WEP(priv, "Remove default WEP key: idx=%d ret=%d\n", + keyconf->keyidx, ret); + + return ret; +} + +int iwl_set_default_wep_key(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_key_conf *keyconf) +{ + int ret; + + lockdep_assert_held(&priv->mutex); + + if (keyconf->keylen != WEP_KEY_LEN_128 && + keyconf->keylen != WEP_KEY_LEN_64) { + IWL_DEBUG_WEP(priv, + "Bad WEP key length %d\n", keyconf->keylen); + return -EINVAL; + } + + keyconf->hw_key_idx = IWLAGN_HW_KEY_DEFAULT; + + ctx->wep_keys[keyconf->keyidx].key_size = keyconf->keylen; + memcpy(&ctx->wep_keys[keyconf->keyidx].key, &keyconf->key, + keyconf->keylen); + + ret = iwl_send_static_wepkey_cmd(priv, ctx, false); + IWL_DEBUG_WEP(priv, "Set default WEP key: len=%d idx=%d ret=%d\n", + keyconf->keylen, keyconf->keyidx, ret); + + return ret; +} + +/* + * dynamic (per-station) keys + * + * The dynamic keys are a little more complicated. The device has + * a key cache of up to STA_KEY_MAX_NUM/STA_KEY_MAX_NUM_PAN keys. + * These are linked to stations by a table that contains an index + * into the key table for each station/key index/{mcast,unicast}, + * i.e. it's basically an array of pointers like this: + * key_offset_t key_mapping[NUM_STATIONS][4][2]; + * (it really works differently, but you can think of it as such) + * + * The key uploading and linking happens in the same command, the + * add station command with STA_MODIFY_KEY_MASK. + */ + +static u8 iwlagn_key_sta_id(struct iwl_priv *priv, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_vif_priv *vif_priv = (void *)vif->drv_priv; + + if (sta) + return iwl_sta_id(sta); + + /* + * The device expects GTKs for station interfaces to be + * installed as GTKs for the AP station. If we have no + * station ID, then use the ap_sta_id in that case. + */ + if (vif->type == NL80211_IFTYPE_STATION && vif_priv->ctx) + return vif_priv->ctx->ap_sta_id; + + return IWL_INVALID_STATION; +} + +static int iwlagn_send_sta_key(struct iwl_priv *priv, + struct ieee80211_key_conf *keyconf, + u8 sta_id, u32 tkip_iv32, u16 *tkip_p1k, + u32 cmd_flags) +{ + __le16 key_flags; + struct iwl_addsta_cmd sta_cmd; + int i; + + spin_lock_bh(&priv->sta_lock); + memcpy(&sta_cmd, &priv->stations[sta_id].sta, sizeof(sta_cmd)); + spin_unlock_bh(&priv->sta_lock); + + key_flags = cpu_to_le16(keyconf->keyidx << STA_KEY_FLG_KEYID_POS); + key_flags |= STA_KEY_FLG_MAP_KEY_MSK; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + key_flags |= STA_KEY_FLG_CCMP; + memcpy(sta_cmd.key.key, keyconf->key, keyconf->keylen); + break; + case WLAN_CIPHER_SUITE_TKIP: + key_flags |= STA_KEY_FLG_TKIP; + sta_cmd.key.tkip_rx_tsc_byte2 = tkip_iv32; + for (i = 0; i < 5; i++) + sta_cmd.key.tkip_rx_ttak[i] = cpu_to_le16(tkip_p1k[i]); + memcpy(sta_cmd.key.key, keyconf->key, keyconf->keylen); + break; + case WLAN_CIPHER_SUITE_WEP104: + key_flags |= STA_KEY_FLG_KEY_SIZE_MSK; + /* fall through */ + case WLAN_CIPHER_SUITE_WEP40: + key_flags |= STA_KEY_FLG_WEP; + memcpy(&sta_cmd.key.key[3], keyconf->key, keyconf->keylen); + break; + default: + WARN_ON(1); + return -EINVAL; + } + + if (!(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE)) + key_flags |= STA_KEY_MULTICAST_MSK; + + /* key pointer (offset) */ + sta_cmd.key.key_offset = keyconf->hw_key_idx; + + sta_cmd.key.key_flags = key_flags; + sta_cmd.mode = STA_CONTROL_MODIFY_MSK; + sta_cmd.sta.modify_mask = STA_MODIFY_KEY_MASK; + + return iwl_send_add_sta(priv, &sta_cmd, cmd_flags); +} + +void iwl_update_tkip_key(struct iwl_priv *priv, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, u32 iv32, u16 *phase1key) +{ + u8 sta_id = iwlagn_key_sta_id(priv, vif, sta); + + if (sta_id == IWL_INVALID_STATION) + return; + + if (iwl_scan_cancel(priv)) { + /* cancel scan failed, just live w/ bad key and rely + briefly on SW decryption */ + return; + } + + iwlagn_send_sta_key(priv, keyconf, sta_id, + iv32, phase1key, CMD_ASYNC); +} + +int iwl_remove_dynamic_key(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta) +{ + struct iwl_addsta_cmd sta_cmd; + u8 sta_id = iwlagn_key_sta_id(priv, ctx->vif, sta); + __le16 key_flags; + + /* if station isn't there, neither is the key */ + if (sta_id == IWL_INVALID_STATION) + return -ENOENT; + + spin_lock_bh(&priv->sta_lock); + memcpy(&sta_cmd, &priv->stations[sta_id].sta, sizeof(sta_cmd)); + if (!(priv->stations[sta_id].used & IWL_STA_UCODE_ACTIVE)) + sta_id = IWL_INVALID_STATION; + spin_unlock_bh(&priv->sta_lock); + + if (sta_id == IWL_INVALID_STATION) + return 0; + + lockdep_assert_held(&priv->mutex); + + ctx->key_mapping_keys--; + + IWL_DEBUG_WEP(priv, "Remove dynamic key: idx=%d sta=%d\n", + keyconf->keyidx, sta_id); + + if (!test_and_clear_bit(keyconf->hw_key_idx, &priv->ucode_key_table)) + IWL_ERR(priv, "offset %d not used in uCode key table.\n", + keyconf->hw_key_idx); + + key_flags = cpu_to_le16(keyconf->keyidx << STA_KEY_FLG_KEYID_POS); + key_flags |= STA_KEY_FLG_MAP_KEY_MSK | STA_KEY_FLG_NO_ENC | + STA_KEY_FLG_INVALID; + + if (!(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE)) + key_flags |= STA_KEY_MULTICAST_MSK; + + sta_cmd.key.key_flags = key_flags; + sta_cmd.key.key_offset = keyconf->hw_key_idx; + sta_cmd.sta.modify_mask = STA_MODIFY_KEY_MASK; + sta_cmd.mode = STA_CONTROL_MODIFY_MSK; + + return iwl_send_add_sta(priv, &sta_cmd, CMD_SYNC); +} + +int iwl_set_dynamic_key(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta) +{ + struct ieee80211_key_seq seq; + u16 p1k[5]; + int ret; + u8 sta_id = iwlagn_key_sta_id(priv, ctx->vif, sta); + const u8 *addr; + + if (sta_id == IWL_INVALID_STATION) + return -EINVAL; + + lockdep_assert_held(&priv->mutex); + + keyconf->hw_key_idx = iwl_get_free_ucode_key_offset(priv); + if (keyconf->hw_key_idx == WEP_INVALID_OFFSET) + return -ENOSPC; + + ctx->key_mapping_keys++; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_TKIP: + if (sta) + addr = sta->addr; + else /* station mode case only */ + addr = ctx->active.bssid_addr; + + /* pre-fill phase 1 key into device cache */ + ieee80211_get_key_rx_seq(keyconf, 0, &seq); + ieee80211_get_tkip_rx_p1k(keyconf, addr, seq.tkip.iv32, p1k); + ret = iwlagn_send_sta_key(priv, keyconf, sta_id, + seq.tkip.iv32, p1k, CMD_SYNC); + break; + case WLAN_CIPHER_SUITE_CCMP: + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + ret = iwlagn_send_sta_key(priv, keyconf, sta_id, + 0, NULL, CMD_SYNC); + break; + default: + IWL_ERR(priv, "Unknown cipher %x\n", keyconf->cipher); + ret = -EINVAL; + } + + if (ret) { + ctx->key_mapping_keys--; + clear_bit(keyconf->hw_key_idx, &priv->ucode_key_table); + } + + IWL_DEBUG_WEP(priv, "Set dynamic key: cipher=%x len=%d idx=%d sta=%pM ret=%d\n", + keyconf->cipher, keyconf->keylen, keyconf->keyidx, + sta ? sta->addr : NULL, ret); + + return ret; +} + +/** + * iwlagn_alloc_bcast_station - add broadcast station into driver's station table. + * + * This adds the broadcast station into the driver's station table + * and marks it driver active, so that it will be restored to the + * device at the next best time. + */ +int iwlagn_alloc_bcast_station(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + struct iwl_link_quality_cmd *link_cmd; + u8 sta_id; + + spin_lock_bh(&priv->sta_lock); + sta_id = iwl_prep_station(priv, ctx, iwl_bcast_addr, false, NULL); + if (sta_id == IWL_INVALID_STATION) { + IWL_ERR(priv, "Unable to prepare broadcast station\n"); + spin_unlock_bh(&priv->sta_lock); + + return -EINVAL; + } + + priv->stations[sta_id].used |= IWL_STA_DRIVER_ACTIVE; + priv->stations[sta_id].used |= IWL_STA_BCAST; + spin_unlock_bh(&priv->sta_lock); + + link_cmd = iwl_sta_alloc_lq(priv, ctx, sta_id); + if (!link_cmd) { + IWL_ERR(priv, + "Unable to initialize rate scaling for bcast station.\n"); + return -ENOMEM; + } + + spin_lock_bh(&priv->sta_lock); + priv->stations[sta_id].lq = link_cmd; + spin_unlock_bh(&priv->sta_lock); + + return 0; +} + +/** + * iwl_update_bcast_station - update broadcast station's LQ command + * + * Only used by iwlagn. Placed here to have all bcast station management + * code together. + */ +int iwl_update_bcast_station(struct iwl_priv *priv, + struct iwl_rxon_context *ctx) +{ + struct iwl_link_quality_cmd *link_cmd; + u8 sta_id = ctx->bcast_sta_id; + + link_cmd = iwl_sta_alloc_lq(priv, ctx, sta_id); + if (!link_cmd) { + IWL_ERR(priv, "Unable to initialize rate scaling for bcast station.\n"); + return -ENOMEM; + } + + spin_lock_bh(&priv->sta_lock); + if (priv->stations[sta_id].lq) + kfree(priv->stations[sta_id].lq); + else + IWL_DEBUG_INFO(priv, "Bcast station rate scaling has not been initialized yet.\n"); + priv->stations[sta_id].lq = link_cmd; + spin_unlock_bh(&priv->sta_lock); + + return 0; +} + +int iwl_update_bcast_stations(struct iwl_priv *priv) +{ + struct iwl_rxon_context *ctx; + int ret = 0; + + for_each_context(priv, ctx) { + ret = iwl_update_bcast_station(priv, ctx); + if (ret) + break; + } + + return ret; +} + +/** + * iwl_sta_tx_modify_enable_tid - Enable Tx for this TID in station table + */ +int iwl_sta_tx_modify_enable_tid(struct iwl_priv *priv, int sta_id, int tid) +{ + struct iwl_addsta_cmd sta_cmd; + + lockdep_assert_held(&priv->mutex); + + /* Remove "disable" flag, to enable Tx for this TID */ + spin_lock_bh(&priv->sta_lock); + priv->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_TID_DISABLE_TX; + priv->stations[sta_id].sta.tid_disable_tx &= cpu_to_le16(~(1 << tid)); + priv->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + memcpy(&sta_cmd, &priv->stations[sta_id].sta, sizeof(struct iwl_addsta_cmd)); + spin_unlock_bh(&priv->sta_lock); + + return iwl_send_add_sta(priv, &sta_cmd, CMD_SYNC); +} + +int iwl_sta_rx_agg_start(struct iwl_priv *priv, struct ieee80211_sta *sta, + int tid, u16 ssn) +{ + int sta_id; + struct iwl_addsta_cmd sta_cmd; + + lockdep_assert_held(&priv->mutex); + + sta_id = iwl_sta_id(sta); + if (sta_id == IWL_INVALID_STATION) + return -ENXIO; + + spin_lock_bh(&priv->sta_lock); + priv->stations[sta_id].sta.station_flags_msk = 0; + priv->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_ADDBA_TID_MSK; + priv->stations[sta_id].sta.add_immediate_ba_tid = (u8)tid; + priv->stations[sta_id].sta.add_immediate_ba_ssn = cpu_to_le16(ssn); + priv->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + memcpy(&sta_cmd, &priv->stations[sta_id].sta, sizeof(struct iwl_addsta_cmd)); + spin_unlock_bh(&priv->sta_lock); + + return iwl_send_add_sta(priv, &sta_cmd, CMD_SYNC); +} + +int iwl_sta_rx_agg_stop(struct iwl_priv *priv, struct ieee80211_sta *sta, + int tid) +{ + int sta_id; + struct iwl_addsta_cmd sta_cmd; + + lockdep_assert_held(&priv->mutex); + + sta_id = iwl_sta_id(sta); + if (sta_id == IWL_INVALID_STATION) { + IWL_ERR(priv, "Invalid station for AGG tid %d\n", tid); + return -ENXIO; + } + + spin_lock_bh(&priv->sta_lock); + priv->stations[sta_id].sta.station_flags_msk = 0; + priv->stations[sta_id].sta.sta.modify_mask = STA_MODIFY_DELBA_TID_MSK; + priv->stations[sta_id].sta.remove_immediate_ba_tid = (u8)tid; + priv->stations[sta_id].sta.mode = STA_CONTROL_MODIFY_MSK; + memcpy(&sta_cmd, &priv->stations[sta_id].sta, sizeof(struct iwl_addsta_cmd)); + spin_unlock_bh(&priv->sta_lock); + + return iwl_send_add_sta(priv, &sta_cmd, CMD_SYNC); +} + + + +void iwl_sta_modify_sleep_tx_count(struct iwl_priv *priv, int sta_id, int cnt) +{ + struct iwl_addsta_cmd cmd = { + .mode = STA_CONTROL_MODIFY_MSK, + .station_flags = STA_FLG_PWR_SAVE_MSK, + .station_flags_msk = STA_FLG_PWR_SAVE_MSK, + .sta.sta_id = sta_id, + .sta.modify_mask = STA_MODIFY_SLEEP_TX_COUNT_MSK, + .sleep_tx_count = cpu_to_le16(cnt), + }; + + iwl_send_add_sta(priv, &cmd, CMD_ASYNC); +} diff --git a/drivers/net/wireless/iwlwifi/dvm/testmode.c b/drivers/net/wireless/iwlwifi/dvm/testmode.c new file mode 100644 index 0000000..b89b9d9 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/testmode.c @@ -0,0 +1,471 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2010 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2010 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "iwl-debug.h" +#include "iwl-trans.h" +#include "dev.h" +#include "agn.h" +#include "iwl-test.h" +#include "iwl-testmode.h" + +static int iwl_testmode_send_cmd(struct iwl_op_mode *op_mode, + struct iwl_host_cmd *cmd) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + return iwl_dvm_send_cmd(priv, cmd); +} + +static bool iwl_testmode_valid_hw_addr(u32 addr) +{ + if (iwlagn_hw_valid_rtc_data_addr(addr)) + return true; + + if (IWLAGN_RTC_INST_LOWER_BOUND <= addr && + addr < IWLAGN_RTC_INST_UPPER_BOUND) + return true; + + return false; +} + +static u32 iwl_testmode_get_fw_ver(struct iwl_op_mode *op_mode) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + return priv->fw->ucode_ver; +} + +static struct sk_buff* +iwl_testmode_alloc_reply(struct iwl_op_mode *op_mode, int len) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + return cfg80211_testmode_alloc_reply_skb(priv->hw->wiphy, len); +} + +static int iwl_testmode_reply(struct iwl_op_mode *op_mode, struct sk_buff *skb) +{ + return cfg80211_testmode_reply(skb); +} + +static struct sk_buff *iwl_testmode_alloc_event(struct iwl_op_mode *op_mode, + int len) +{ + struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode); + return cfg80211_testmode_alloc_event_skb(priv->hw->wiphy, len, + GFP_ATOMIC); +} + +static void iwl_testmode_event(struct iwl_op_mode *op_mode, struct sk_buff *skb) +{ + return cfg80211_testmode_event(skb, GFP_ATOMIC); +} + +static struct iwl_test_ops tst_ops = { + .send_cmd = iwl_testmode_send_cmd, + .valid_hw_addr = iwl_testmode_valid_hw_addr, + .get_fw_ver = iwl_testmode_get_fw_ver, + .alloc_reply = iwl_testmode_alloc_reply, + .reply = iwl_testmode_reply, + .alloc_event = iwl_testmode_alloc_event, + .event = iwl_testmode_event, +}; + +void iwl_testmode_init(struct iwl_priv *priv) +{ + iwl_test_init(&priv->tst, priv->trans, &tst_ops); +} + +void iwl_testmode_free(struct iwl_priv *priv) +{ + iwl_test_free(&priv->tst); +} + +static int iwl_testmode_cfg_init_calib(struct iwl_priv *priv) +{ + struct iwl_notification_wait calib_wait; + static const u8 calib_complete[] = { + CALIBRATION_COMPLETE_NOTIFICATION + }; + int ret; + + iwl_init_notification_wait(&priv->notif_wait, &calib_wait, + calib_complete, ARRAY_SIZE(calib_complete), + NULL, NULL); + ret = iwl_init_alive_start(priv); + if (ret) { + IWL_ERR(priv, "Fail init calibration: %d\n", ret); + goto cfg_init_calib_error; + } + + ret = iwl_wait_notification(&priv->notif_wait, &calib_wait, 2 * HZ); + if (ret) + IWL_ERR(priv, "Error detecting" + " CALIBRATION_COMPLETE_NOTIFICATION: %d\n", ret); + return ret; + +cfg_init_calib_error: + iwl_remove_notification(&priv->notif_wait, &calib_wait); + return ret; +} + +/* + * This function handles the user application commands for driver. + * + * It retrieves command ID carried with IWL_TM_ATTR_COMMAND and calls to the + * handlers respectively. + * + * If it's an unknown commdn ID, -ENOSYS is replied; otherwise, the returned + * value of the actual command execution is replied to the user application. + * + * If there's any message responding to the user space, IWL_TM_ATTR_SYNC_RSP + * is used for carry the message while IWL_TM_ATTR_COMMAND must set to + * IWL_TM_CMD_DEV2APP_SYNC_RSP. + * + * @hw: ieee80211_hw object that represents the device + * @tb: gnl message fields from the user space + */ +static int iwl_testmode_driver(struct ieee80211_hw *hw, struct nlattr **tb) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + struct iwl_trans *trans = priv->trans; + struct sk_buff *skb; + unsigned char *rsp_data_ptr = NULL; + int status = 0, rsp_data_len = 0; + u32 inst_size = 0, data_size = 0; + const struct fw_img *img; + + switch (nla_get_u32(tb[IWL_TM_ATTR_COMMAND])) { + case IWL_TM_CMD_APP2DEV_GET_DEVICENAME: + rsp_data_ptr = (unsigned char *)priv->cfg->name; + rsp_data_len = strlen(priv->cfg->name); + skb = cfg80211_testmode_alloc_reply_skb(hw->wiphy, + rsp_data_len + 20); + if (!skb) { + IWL_ERR(priv, "Memory allocation fail\n"); + return -ENOMEM; + } + if (nla_put_u32(skb, IWL_TM_ATTR_COMMAND, + IWL_TM_CMD_DEV2APP_SYNC_RSP) || + nla_put(skb, IWL_TM_ATTR_SYNC_RSP, + rsp_data_len, rsp_data_ptr)) + goto nla_put_failure; + status = cfg80211_testmode_reply(skb); + if (status < 0) + IWL_ERR(priv, "Error sending msg : %d\n", status); + break; + + case IWL_TM_CMD_APP2DEV_LOAD_INIT_FW: + status = iwl_load_ucode_wait_alive(priv, IWL_UCODE_INIT); + if (status) + IWL_ERR(priv, "Error loading init ucode: %d\n", status); + break; + + case IWL_TM_CMD_APP2DEV_CFG_INIT_CALIB: + iwl_testmode_cfg_init_calib(priv); + priv->ucode_loaded = false; + iwl_trans_stop_device(trans); + break; + + case IWL_TM_CMD_APP2DEV_LOAD_RUNTIME_FW: + status = iwl_load_ucode_wait_alive(priv, IWL_UCODE_REGULAR); + if (status) { + IWL_ERR(priv, + "Error loading runtime ucode: %d\n", status); + break; + } + status = iwl_alive_start(priv); + if (status) + IWL_ERR(priv, + "Error starting the device: %d\n", status); + break; + + case IWL_TM_CMD_APP2DEV_LOAD_WOWLAN_FW: + iwl_scan_cancel_timeout(priv, 200); + priv->ucode_loaded = false; + iwl_trans_stop_device(trans); + status = iwl_load_ucode_wait_alive(priv, IWL_UCODE_WOWLAN); + if (status) { + IWL_ERR(priv, + "Error loading WOWLAN ucode: %d\n", status); + break; + } + status = iwl_alive_start(priv); + if (status) + IWL_ERR(priv, + "Error starting the device: %d\n", status); + break; + + case IWL_TM_CMD_APP2DEV_GET_EEPROM: + if (priv->eeprom_blob) { + skb = cfg80211_testmode_alloc_reply_skb(hw->wiphy, + priv->eeprom_blob_size + 20); + if (!skb) { + IWL_ERR(priv, "Memory allocation fail\n"); + return -ENOMEM; + } + if (nla_put_u32(skb, IWL_TM_ATTR_COMMAND, + IWL_TM_CMD_DEV2APP_EEPROM_RSP) || + nla_put(skb, IWL_TM_ATTR_EEPROM, + priv->eeprom_blob_size, + priv->eeprom_blob)) + goto nla_put_failure; + status = cfg80211_testmode_reply(skb); + if (status < 0) + IWL_ERR(priv, "Error sending msg : %d\n", + status); + } else + return -ENODATA; + break; + + case IWL_TM_CMD_APP2DEV_FIXRATE_REQ: + if (!tb[IWL_TM_ATTR_FIXRATE]) { + IWL_ERR(priv, "Missing fixrate setting\n"); + return -ENOMSG; + } + priv->tm_fixed_rate = nla_get_u32(tb[IWL_TM_ATTR_FIXRATE]); + break; + + case IWL_TM_CMD_APP2DEV_GET_FW_INFO: + skb = cfg80211_testmode_alloc_reply_skb(hw->wiphy, 20 + 8); + if (!skb) { + IWL_ERR(priv, "Memory allocation fail\n"); + return -ENOMEM; + } + if (!priv->ucode_loaded) { + IWL_ERR(priv, "No uCode has not been loaded\n"); + return -EINVAL; + } else { + img = &priv->fw->img[priv->cur_ucode]; + inst_size = img->sec[IWL_UCODE_SECTION_INST].len; + data_size = img->sec[IWL_UCODE_SECTION_DATA].len; + } + if (nla_put_u32(skb, IWL_TM_ATTR_FW_TYPE, priv->cur_ucode) || + nla_put_u32(skb, IWL_TM_ATTR_FW_INST_SIZE, inst_size) || + nla_put_u32(skb, IWL_TM_ATTR_FW_DATA_SIZE, data_size)) + goto nla_put_failure; + status = cfg80211_testmode_reply(skb); + if (status < 0) + IWL_ERR(priv, "Error sending msg : %d\n", status); + break; + + default: + IWL_ERR(priv, "Unknown testmode driver command ID\n"); + return -ENOSYS; + } + return status; + +nla_put_failure: + kfree_skb(skb); + return -EMSGSIZE; +} + +/* + * This function handles the user application switch ucode ownership. + * + * It retrieves the mandatory fields IWL_TM_ATTR_UCODE_OWNER and + * decide who the current owner of the uCode + * + * If the current owner is OWNERSHIP_TM, then the only host command + * can deliver to uCode is from testmode, all the other host commands + * will dropped. + * + * default driver is the owner of uCode in normal operational mode + * + * @hw: ieee80211_hw object that represents the device + * @tb: gnl message fields from the user space + */ +static int iwl_testmode_ownership(struct ieee80211_hw *hw, struct nlattr **tb) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + u8 owner; + + if (!tb[IWL_TM_ATTR_UCODE_OWNER]) { + IWL_ERR(priv, "Missing ucode owner\n"); + return -ENOMSG; + } + + owner = nla_get_u8(tb[IWL_TM_ATTR_UCODE_OWNER]); + if (owner == IWL_OWNERSHIP_DRIVER) { + priv->ucode_owner = owner; + iwl_test_enable_notifications(&priv->tst, false); + } else if (owner == IWL_OWNERSHIP_TM) { + priv->ucode_owner = owner; + iwl_test_enable_notifications(&priv->tst, true); + } else { + IWL_ERR(priv, "Invalid owner\n"); + return -EINVAL; + } + return 0; +} + +/* The testmode gnl message handler that takes the gnl message from the + * user space and parses it per the policy iwl_testmode_gnl_msg_policy, then + * invoke the corresponding handlers. + * + * This function is invoked when there is user space application sending + * gnl message through the testmode tunnel NL80211_CMD_TESTMODE regulated + * by nl80211. + * + * It retrieves the mandatory field, IWL_TM_ATTR_COMMAND, before + * dispatching it to the corresponding handler. + * + * If IWL_TM_ATTR_COMMAND is missing, -ENOMSG is replied to user application; + * -ENOSYS is replied to the user application if the command is unknown; + * Otherwise, the command is dispatched to the respective handler. + * + * @hw: ieee80211_hw object that represents the device + * @data: pointer to user space message + * @len: length in byte of @data + */ +int iwlagn_mac_testmode_cmd(struct ieee80211_hw *hw, void *data, int len) +{ + struct nlattr *tb[IWL_TM_ATTR_MAX]; + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + int result; + + result = iwl_test_parse(&priv->tst, tb, data, len); + if (result) + return result; + + /* in case multiple accesses to the device happens */ + mutex_lock(&priv->mutex); + switch (nla_get_u32(tb[IWL_TM_ATTR_COMMAND])) { + case IWL_TM_CMD_APP2DEV_UCODE: + case IWL_TM_CMD_APP2DEV_DIRECT_REG_READ32: + case IWL_TM_CMD_APP2DEV_DIRECT_REG_WRITE32: + case IWL_TM_CMD_APP2DEV_DIRECT_REG_WRITE8: + case IWL_TM_CMD_APP2DEV_BEGIN_TRACE: + case IWL_TM_CMD_APP2DEV_END_TRACE: + case IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_READ: + case IWL_TM_CMD_APP2DEV_NOTIFICATIONS: + case IWL_TM_CMD_APP2DEV_GET_FW_VERSION: + case IWL_TM_CMD_APP2DEV_GET_DEVICE_ID: + case IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_WRITE: + result = iwl_test_handle_cmd(&priv->tst, tb); + break; + + case IWL_TM_CMD_APP2DEV_GET_DEVICENAME: + case IWL_TM_CMD_APP2DEV_LOAD_INIT_FW: + case IWL_TM_CMD_APP2DEV_CFG_INIT_CALIB: + case IWL_TM_CMD_APP2DEV_LOAD_RUNTIME_FW: + case IWL_TM_CMD_APP2DEV_GET_EEPROM: + case IWL_TM_CMD_APP2DEV_FIXRATE_REQ: + case IWL_TM_CMD_APP2DEV_LOAD_WOWLAN_FW: + case IWL_TM_CMD_APP2DEV_GET_FW_INFO: + IWL_DEBUG_INFO(priv, "testmode cmd to driver\n"); + result = iwl_testmode_driver(hw, tb); + break; + + case IWL_TM_CMD_APP2DEV_OWNERSHIP: + IWL_DEBUG_INFO(priv, "testmode change uCode ownership\n"); + result = iwl_testmode_ownership(hw, tb); + break; + + default: + IWL_ERR(priv, "Unknown testmode command\n"); + result = -ENOSYS; + break; + } + mutex_unlock(&priv->mutex); + + if (result) + IWL_ERR(priv, "Test cmd failed result=%d\n", result); + return result; +} + +int iwlagn_mac_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *skb, + struct netlink_callback *cb, + void *data, int len) +{ + struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); + int result; + u32 cmd; + + if (cb->args[3]) { + /* offset by 1 since commands start at 0 */ + cmd = cb->args[3] - 1; + } else { + struct nlattr *tb[IWL_TM_ATTR_MAX]; + + result = iwl_test_parse(&priv->tst, tb, data, len); + if (result) + return result; + + cmd = nla_get_u32(tb[IWL_TM_ATTR_COMMAND]); + cb->args[3] = cmd + 1; + } + + /* in case multiple accesses to the device happens */ + mutex_lock(&priv->mutex); + result = iwl_test_dump(&priv->tst, cmd, skb, cb); + mutex_unlock(&priv->mutex); + return result; +} diff --git a/drivers/net/wireless/iwlwifi/dvm/tt.c b/drivers/net/wireless/iwlwifi/dvm/tt.c new file mode 100644 index 0000000..a0256ea --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/tt.c @@ -0,0 +1,691 @@ +/****************************************************************************** + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + *****************************************************************************/ + + +#include +#include +#include +#include +#include +#include "iwl-io.h" +#include "iwl-modparams.h" +#include "iwl-debug.h" +#include "agn.h" +#include "dev.h" +#include "commands.h" +#include "tt.h" + +/* default Thermal Throttling transaction table + * Current state | Throttling Down | Throttling Up + *============================================================================= + * Condition Nxt State Condition Nxt State Condition Nxt State + *----------------------------------------------------------------------------- + * IWL_TI_0 T >= 114 CT_KILL 114>T>=105 TI_1 N/A N/A + * IWL_TI_1 T >= 114 CT_KILL 114>T>=110 TI_2 T<=95 TI_0 + * IWL_TI_2 T >= 114 CT_KILL T<=100 TI_1 + * IWL_CT_KILL N/A N/A N/A N/A T<=95 TI_0 + *============================================================================= + */ +static const struct iwl_tt_trans tt_range_0[IWL_TI_STATE_MAX - 1] = { + {IWL_TI_0, IWL_ABSOLUTE_ZERO, 104}, + {IWL_TI_1, 105, CT_KILL_THRESHOLD - 1}, + {IWL_TI_CT_KILL, CT_KILL_THRESHOLD, IWL_ABSOLUTE_MAX} +}; +static const struct iwl_tt_trans tt_range_1[IWL_TI_STATE_MAX - 1] = { + {IWL_TI_0, IWL_ABSOLUTE_ZERO, 95}, + {IWL_TI_2, 110, CT_KILL_THRESHOLD - 1}, + {IWL_TI_CT_KILL, CT_KILL_THRESHOLD, IWL_ABSOLUTE_MAX} +}; +static const struct iwl_tt_trans tt_range_2[IWL_TI_STATE_MAX - 1] = { + {IWL_TI_1, IWL_ABSOLUTE_ZERO, 100}, + {IWL_TI_CT_KILL, CT_KILL_THRESHOLD, IWL_ABSOLUTE_MAX}, + {IWL_TI_CT_KILL, CT_KILL_THRESHOLD, IWL_ABSOLUTE_MAX} +}; +static const struct iwl_tt_trans tt_range_3[IWL_TI_STATE_MAX - 1] = { + {IWL_TI_0, IWL_ABSOLUTE_ZERO, CT_KILL_EXIT_THRESHOLD}, + {IWL_TI_CT_KILL, CT_KILL_EXIT_THRESHOLD + 1, IWL_ABSOLUTE_MAX}, + {IWL_TI_CT_KILL, CT_KILL_EXIT_THRESHOLD + 1, IWL_ABSOLUTE_MAX} +}; + +/* Advance Thermal Throttling default restriction table */ +static const struct iwl_tt_restriction restriction_range[IWL_TI_STATE_MAX] = { + {IWL_ANT_OK_MULTI, IWL_ANT_OK_MULTI, true }, + {IWL_ANT_OK_SINGLE, IWL_ANT_OK_MULTI, true }, + {IWL_ANT_OK_SINGLE, IWL_ANT_OK_SINGLE, false }, + {IWL_ANT_OK_NONE, IWL_ANT_OK_NONE, false } +}; + +bool iwl_tt_is_low_power_state(struct iwl_priv *priv) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + + if (tt->state >= IWL_TI_1) + return true; + return false; +} + +u8 iwl_tt_current_power_mode(struct iwl_priv *priv) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + + return tt->tt_power_mode; +} + +bool iwl_ht_enabled(struct iwl_priv *priv) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + struct iwl_tt_restriction *restriction; + + if (!priv->thermal_throttle.advanced_tt) + return true; + restriction = tt->restriction + tt->state; + return restriction->is_ht; +} + +static bool iwl_within_ct_kill_margin(struct iwl_priv *priv) +{ + s32 temp = priv->temperature; /* degrees CELSIUS except specified */ + bool within_margin = false; + + if (!priv->thermal_throttle.advanced_tt) + within_margin = ((temp + IWL_TT_CT_KILL_MARGIN) >= + CT_KILL_THRESHOLD_LEGACY) ? true : false; + else + within_margin = ((temp + IWL_TT_CT_KILL_MARGIN) >= + CT_KILL_THRESHOLD) ? true : false; + return within_margin; +} + +bool iwl_check_for_ct_kill(struct iwl_priv *priv) +{ + bool is_ct_kill = false; + + if (iwl_within_ct_kill_margin(priv)) { + iwl_tt_enter_ct_kill(priv); + is_ct_kill = true; + } + return is_ct_kill; +} + +enum iwl_antenna_ok iwl_tx_ant_restriction(struct iwl_priv *priv) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + struct iwl_tt_restriction *restriction; + + if (!priv->thermal_throttle.advanced_tt) + return IWL_ANT_OK_MULTI; + restriction = tt->restriction + tt->state; + return restriction->tx_stream; +} + +enum iwl_antenna_ok iwl_rx_ant_restriction(struct iwl_priv *priv) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + struct iwl_tt_restriction *restriction; + + if (!priv->thermal_throttle.advanced_tt) + return IWL_ANT_OK_MULTI; + restriction = tt->restriction + tt->state; + return restriction->rx_stream; +} + +#define CT_KILL_EXIT_DURATION (5) /* 5 seconds duration */ +#define CT_KILL_WAITING_DURATION (300) /* 300ms duration */ + +/* + * toggle the bit to wake up uCode and check the temperature + * if the temperature is below CT, uCode will stay awake and send card + * state notification with CT_KILL bit clear to inform Thermal Throttling + * Management to change state. Otherwise, uCode will go back to sleep + * without doing anything, driver should continue the 5 seconds timer + * to wake up uCode for temperature check until temperature drop below CT + */ +static void iwl_tt_check_exit_ct_kill(unsigned long data) +{ + struct iwl_priv *priv = (struct iwl_priv *)data; + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + unsigned long flags; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + if (tt->state == IWL_TI_CT_KILL) { + if (priv->thermal_throttle.ct_kill_toggle) { + iwl_write32(priv->trans, CSR_UCODE_DRV_GP1_CLR, + CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT); + priv->thermal_throttle.ct_kill_toggle = false; + } else { + iwl_write32(priv->trans, CSR_UCODE_DRV_GP1_SET, + CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT); + priv->thermal_throttle.ct_kill_toggle = true; + } + iwl_read32(priv->trans, CSR_UCODE_DRV_GP1); + if (iwl_trans_grab_nic_access(priv->trans, false, &flags)) + iwl_trans_release_nic_access(priv->trans, &flags); + + /* Reschedule the ct_kill timer to occur in + * CT_KILL_EXIT_DURATION seconds to ensure we get a + * thermal update */ + IWL_DEBUG_TEMP(priv, "schedule ct_kill exit timer\n"); + mod_timer(&priv->thermal_throttle.ct_kill_exit_tm, + jiffies + CT_KILL_EXIT_DURATION * HZ); + } +} + +static void iwl_perform_ct_kill_task(struct iwl_priv *priv, + bool stop) +{ + if (stop) { + IWL_DEBUG_TEMP(priv, "Stop all queues\n"); + if (priv->mac80211_registered) + ieee80211_stop_queues(priv->hw); + IWL_DEBUG_TEMP(priv, + "Schedule 5 seconds CT_KILL Timer\n"); + mod_timer(&priv->thermal_throttle.ct_kill_exit_tm, + jiffies + CT_KILL_EXIT_DURATION * HZ); + } else { + IWL_DEBUG_TEMP(priv, "Wake all queues\n"); + if (priv->mac80211_registered) + ieee80211_wake_queues(priv->hw); + } +} + +static void iwl_tt_ready_for_ct_kill(unsigned long data) +{ + struct iwl_priv *priv = (struct iwl_priv *)data; + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + /* temperature timer expired, ready to go into CT_KILL state */ + if (tt->state != IWL_TI_CT_KILL) { + IWL_DEBUG_TEMP(priv, "entering CT_KILL state when " + "temperature timer expired\n"); + tt->state = IWL_TI_CT_KILL; + set_bit(STATUS_CT_KILL, &priv->status); + iwl_perform_ct_kill_task(priv, true); + } +} + +static void iwl_prepare_ct_kill_task(struct iwl_priv *priv) +{ + IWL_DEBUG_TEMP(priv, "Prepare to enter IWL_TI_CT_KILL\n"); + /* make request to retrieve statistics information */ + iwl_send_statistics_request(priv, CMD_SYNC, false); + /* Reschedule the ct_kill wait timer */ + mod_timer(&priv->thermal_throttle.ct_kill_waiting_tm, + jiffies + msecs_to_jiffies(CT_KILL_WAITING_DURATION)); +} + +#define IWL_MINIMAL_POWER_THRESHOLD (CT_KILL_THRESHOLD_LEGACY) +#define IWL_REDUCED_PERFORMANCE_THRESHOLD_2 (100) +#define IWL_REDUCED_PERFORMANCE_THRESHOLD_1 (90) + +/* + * Legacy thermal throttling + * 1) Avoid NIC destruction due to high temperatures + * Chip will identify dangerously high temperatures that can + * harm the device and will power down + * 2) Avoid the NIC power down due to high temperature + * Throttle early enough to lower the power consumption before + * drastic steps are needed + */ +static void iwl_legacy_tt_handler(struct iwl_priv *priv, s32 temp, bool force) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + enum iwl_tt_state old_state; + +#ifdef CPTCFG_IWLWIFI_DEBUG + if ((tt->tt_previous_temp) && + (temp > tt->tt_previous_temp) && + ((temp - tt->tt_previous_temp) > + IWL_TT_INCREASE_MARGIN)) { + IWL_DEBUG_TEMP(priv, + "Temperature increase %d degree Celsius\n", + (temp - tt->tt_previous_temp)); + } +#endif + old_state = tt->state; + /* in Celsius */ + if (temp >= IWL_MINIMAL_POWER_THRESHOLD) + tt->state = IWL_TI_CT_KILL; + else if (temp >= IWL_REDUCED_PERFORMANCE_THRESHOLD_2) + tt->state = IWL_TI_2; + else if (temp >= IWL_REDUCED_PERFORMANCE_THRESHOLD_1) + tt->state = IWL_TI_1; + else + tt->state = IWL_TI_0; + +#ifdef CPTCFG_IWLWIFI_DEBUG + tt->tt_previous_temp = temp; +#endif + /* stop ct_kill_waiting_tm timer */ + del_timer_sync(&priv->thermal_throttle.ct_kill_waiting_tm); + if (tt->state != old_state) { + switch (tt->state) { + case IWL_TI_0: + /* + * When the system is ready to go back to IWL_TI_0 + * we only have to call iwl_power_update_mode() to + * do so. + */ + break; + case IWL_TI_1: + tt->tt_power_mode = IWL_POWER_INDEX_3; + break; + case IWL_TI_2: + tt->tt_power_mode = IWL_POWER_INDEX_4; + break; + default: + tt->tt_power_mode = IWL_POWER_INDEX_5; + break; + } + mutex_lock(&priv->mutex); + if (old_state == IWL_TI_CT_KILL) + clear_bit(STATUS_CT_KILL, &priv->status); + if (tt->state != IWL_TI_CT_KILL && + iwl_power_update_mode(priv, true)) { + /* TT state not updated + * try again during next temperature read + */ + if (old_state == IWL_TI_CT_KILL) + set_bit(STATUS_CT_KILL, &priv->status); + tt->state = old_state; + IWL_ERR(priv, "Cannot update power mode, " + "TT state not updated\n"); + } else { + if (tt->state == IWL_TI_CT_KILL) { + if (force) { + set_bit(STATUS_CT_KILL, &priv->status); + iwl_perform_ct_kill_task(priv, true); + } else { + iwl_prepare_ct_kill_task(priv); + tt->state = old_state; + } + } else if (old_state == IWL_TI_CT_KILL && + tt->state != IWL_TI_CT_KILL) + iwl_perform_ct_kill_task(priv, false); + IWL_DEBUG_TEMP(priv, "Temperature state changed %u\n", + tt->state); + IWL_DEBUG_TEMP(priv, "Power Index change to %u\n", + tt->tt_power_mode); + } + mutex_unlock(&priv->mutex); + } +} + +/* + * Advance thermal throttling + * 1) Avoid NIC destruction due to high temperatures + * Chip will identify dangerously high temperatures that can + * harm the device and will power down + * 2) Avoid the NIC power down due to high temperature + * Throttle early enough to lower the power consumption before + * drastic steps are needed + * Actions include relaxing the power down sleep thresholds and + * decreasing the number of TX streams + * 3) Avoid throughput performance impact as much as possible + * + *============================================================================= + * Condition Nxt State Condition Nxt State Condition Nxt State + *----------------------------------------------------------------------------- + * IWL_TI_0 T >= 114 CT_KILL 114>T>=105 TI_1 N/A N/A + * IWL_TI_1 T >= 114 CT_KILL 114>T>=110 TI_2 T<=95 TI_0 + * IWL_TI_2 T >= 114 CT_KILL T<=100 TI_1 + * IWL_CT_KILL N/A N/A N/A N/A T<=95 TI_0 + *============================================================================= + */ +static void iwl_advance_tt_handler(struct iwl_priv *priv, s32 temp, bool force) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + int i; + bool changed = false; + enum iwl_tt_state old_state; + struct iwl_tt_trans *transaction; + + old_state = tt->state; + for (i = 0; i < IWL_TI_STATE_MAX - 1; i++) { + /* based on the current TT state, + * find the curresponding transaction table + * each table has (IWL_TI_STATE_MAX - 1) entries + * tt->transaction + ((old_state * (IWL_TI_STATE_MAX - 1)) + * will advance to the correct table. + * then based on the current temperature + * find the next state need to transaction to + * go through all the possible (IWL_TI_STATE_MAX - 1) entries + * in the current table to see if transaction is needed + */ + transaction = tt->transaction + + ((old_state * (IWL_TI_STATE_MAX - 1)) + i); + if (temp >= transaction->tt_low && + temp <= transaction->tt_high) { +#ifdef CPTCFG_IWLWIFI_DEBUG + if ((tt->tt_previous_temp) && + (temp > tt->tt_previous_temp) && + ((temp - tt->tt_previous_temp) > + IWL_TT_INCREASE_MARGIN)) { + IWL_DEBUG_TEMP(priv, + "Temperature increase %d " + "degree Celsius\n", + (temp - tt->tt_previous_temp)); + } + tt->tt_previous_temp = temp; +#endif + if (old_state != + transaction->next_state) { + changed = true; + tt->state = + transaction->next_state; + } + break; + } + } + /* stop ct_kill_waiting_tm timer */ + del_timer_sync(&priv->thermal_throttle.ct_kill_waiting_tm); + if (changed) { + if (tt->state >= IWL_TI_1) { + /* force PI = IWL_POWER_INDEX_5 in the case of TI > 0 */ + tt->tt_power_mode = IWL_POWER_INDEX_5; + + if (!iwl_ht_enabled(priv)) { + struct iwl_rxon_context *ctx; + + for_each_context(priv, ctx) { + struct iwl_rxon_cmd *rxon; + + rxon = &ctx->staging; + + /* disable HT */ + rxon->flags &= ~( + RXON_FLG_CHANNEL_MODE_MSK | + RXON_FLG_CTRL_CHANNEL_LOC_HI_MSK | + RXON_FLG_HT40_PROT_MSK | + RXON_FLG_HT_PROT_MSK); + } + } else { + /* check HT capability and set + * according to the system HT capability + * in case get disabled before */ + iwl_set_rxon_ht(priv, &priv->current_ht_config); + } + + } else { + /* + * restore system power setting -- it will be + * recalculated automatically. + */ + + /* check HT capability and set + * according to the system HT capability + * in case get disabled before */ + iwl_set_rxon_ht(priv, &priv->current_ht_config); + } + mutex_lock(&priv->mutex); + if (old_state == IWL_TI_CT_KILL) + clear_bit(STATUS_CT_KILL, &priv->status); + if (tt->state != IWL_TI_CT_KILL && + iwl_power_update_mode(priv, true)) { + /* TT state not updated + * try again during next temperature read + */ + IWL_ERR(priv, "Cannot update power mode, " + "TT state not updated\n"); + if (old_state == IWL_TI_CT_KILL) + set_bit(STATUS_CT_KILL, &priv->status); + tt->state = old_state; + } else { + IWL_DEBUG_TEMP(priv, + "Thermal Throttling to new state: %u\n", + tt->state); + if (old_state != IWL_TI_CT_KILL && + tt->state == IWL_TI_CT_KILL) { + if (force) { + IWL_DEBUG_TEMP(priv, + "Enter IWL_TI_CT_KILL\n"); + set_bit(STATUS_CT_KILL, &priv->status); + iwl_perform_ct_kill_task(priv, true); + } else { + tt->state = old_state; + iwl_prepare_ct_kill_task(priv); + } + } else if (old_state == IWL_TI_CT_KILL && + tt->state != IWL_TI_CT_KILL) { + IWL_DEBUG_TEMP(priv, "Exit IWL_TI_CT_KILL\n"); + iwl_perform_ct_kill_task(priv, false); + } + } + mutex_unlock(&priv->mutex); + } +} + +/* Card State Notification indicated reach critical temperature + * if PSP not enable, no Thermal Throttling function will be performed + * just set the GP1 bit to acknowledge the event + * otherwise, go into IWL_TI_CT_KILL state + * since Card State Notification will not provide any temperature reading + * for Legacy mode + * so just pass the CT_KILL temperature to iwl_legacy_tt_handler() + * for advance mode + * pass CT_KILL_THRESHOLD+1 to make sure move into IWL_TI_CT_KILL state + */ +static void iwl_bg_ct_enter(struct work_struct *work) +{ + struct iwl_priv *priv = container_of(work, struct iwl_priv, ct_enter); + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + if (!iwl_is_ready(priv)) + return; + + if (tt->state != IWL_TI_CT_KILL) { + IWL_ERR(priv, "Device reached critical temperature " + "- ucode going to sleep!\n"); + if (!priv->thermal_throttle.advanced_tt) + iwl_legacy_tt_handler(priv, + IWL_MINIMAL_POWER_THRESHOLD, + true); + else + iwl_advance_tt_handler(priv, + CT_KILL_THRESHOLD + 1, true); + } +} + +/* Card State Notification indicated out of critical temperature + * since Card State Notification will not provide any temperature reading + * so pass the IWL_REDUCED_PERFORMANCE_THRESHOLD_2 temperature + * to iwl_legacy_tt_handler() to get out of IWL_CT_KILL state + */ +static void iwl_bg_ct_exit(struct work_struct *work) +{ + struct iwl_priv *priv = container_of(work, struct iwl_priv, ct_exit); + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + if (!iwl_is_ready(priv)) + return; + + /* stop ct_kill_exit_tm timer */ + del_timer_sync(&priv->thermal_throttle.ct_kill_exit_tm); + + if (tt->state == IWL_TI_CT_KILL) { + IWL_ERR(priv, + "Device temperature below critical" + "- ucode awake!\n"); + /* + * exit from CT_KILL state + * reset the current temperature reading + */ + priv->temperature = 0; + if (!priv->thermal_throttle.advanced_tt) + iwl_legacy_tt_handler(priv, + IWL_REDUCED_PERFORMANCE_THRESHOLD_2, + true); + else + iwl_advance_tt_handler(priv, CT_KILL_EXIT_THRESHOLD, + true); + } +} + +void iwl_tt_enter_ct_kill(struct iwl_priv *priv) +{ + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + IWL_DEBUG_TEMP(priv, "Queueing critical temperature enter.\n"); + queue_work(priv->workqueue, &priv->ct_enter); +} + +void iwl_tt_exit_ct_kill(struct iwl_priv *priv) +{ + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + IWL_DEBUG_TEMP(priv, "Queueing critical temperature exit.\n"); + queue_work(priv->workqueue, &priv->ct_exit); +} + +static void iwl_bg_tt_work(struct work_struct *work) +{ + struct iwl_priv *priv = container_of(work, struct iwl_priv, tt_work); + s32 temp = priv->temperature; /* degrees CELSIUS except specified */ + + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + if (!priv->thermal_throttle.advanced_tt) + iwl_legacy_tt_handler(priv, temp, false); + else + iwl_advance_tt_handler(priv, temp, false); +} + +void iwl_tt_handler(struct iwl_priv *priv) +{ + if (test_bit(STATUS_EXIT_PENDING, &priv->status)) + return; + + IWL_DEBUG_TEMP(priv, "Queueing thermal throttling work.\n"); + queue_work(priv->workqueue, &priv->tt_work); +} + +/* Thermal throttling initialization + * For advance thermal throttling: + * Initialize Thermal Index and temperature threshold table + * Initialize thermal throttling restriction table + */ +void iwl_tt_initialize(struct iwl_priv *priv) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + int size = sizeof(struct iwl_tt_trans) * (IWL_TI_STATE_MAX - 1); + struct iwl_tt_trans *transaction; + + IWL_DEBUG_TEMP(priv, "Initialize Thermal Throttling\n"); + + memset(tt, 0, sizeof(struct iwl_tt_mgmt)); + + tt->state = IWL_TI_0; + init_timer(&priv->thermal_throttle.ct_kill_exit_tm); + priv->thermal_throttle.ct_kill_exit_tm.data = (unsigned long)priv; + priv->thermal_throttle.ct_kill_exit_tm.function = + iwl_tt_check_exit_ct_kill; + init_timer(&priv->thermal_throttle.ct_kill_waiting_tm); + priv->thermal_throttle.ct_kill_waiting_tm.data = + (unsigned long)priv; + priv->thermal_throttle.ct_kill_waiting_tm.function = + iwl_tt_ready_for_ct_kill; + /* setup deferred ct kill work */ + INIT_WORK(&priv->tt_work, iwl_bg_tt_work); + INIT_WORK(&priv->ct_enter, iwl_bg_ct_enter); + INIT_WORK(&priv->ct_exit, iwl_bg_ct_exit); + + if (priv->cfg->base_params->adv_thermal_throttle) { + IWL_DEBUG_TEMP(priv, "Advanced Thermal Throttling\n"); + tt->restriction = kcalloc(IWL_TI_STATE_MAX, + sizeof(struct iwl_tt_restriction), + GFP_KERNEL); + tt->transaction = kcalloc(IWL_TI_STATE_MAX * + (IWL_TI_STATE_MAX - 1), + sizeof(struct iwl_tt_trans), + GFP_KERNEL); + if (!tt->restriction || !tt->transaction) { + IWL_ERR(priv, "Fallback to Legacy Throttling\n"); + priv->thermal_throttle.advanced_tt = false; + kfree(tt->restriction); + tt->restriction = NULL; + kfree(tt->transaction); + tt->transaction = NULL; + } else { + transaction = tt->transaction + + (IWL_TI_0 * (IWL_TI_STATE_MAX - 1)); + memcpy(transaction, &tt_range_0[0], size); + transaction = tt->transaction + + (IWL_TI_1 * (IWL_TI_STATE_MAX - 1)); + memcpy(transaction, &tt_range_1[0], size); + transaction = tt->transaction + + (IWL_TI_2 * (IWL_TI_STATE_MAX - 1)); + memcpy(transaction, &tt_range_2[0], size); + transaction = tt->transaction + + (IWL_TI_CT_KILL * (IWL_TI_STATE_MAX - 1)); + memcpy(transaction, &tt_range_3[0], size); + size = sizeof(struct iwl_tt_restriction) * + IWL_TI_STATE_MAX; + memcpy(tt->restriction, + &restriction_range[0], size); + priv->thermal_throttle.advanced_tt = true; + } + } else { + IWL_DEBUG_TEMP(priv, "Legacy Thermal Throttling\n"); + priv->thermal_throttle.advanced_tt = false; + } +} + +/* cleanup thermal throttling management related memory and timer */ +void iwl_tt_exit(struct iwl_priv *priv) +{ + struct iwl_tt_mgmt *tt = &priv->thermal_throttle; + + /* stop ct_kill_exit_tm timer if activated */ + del_timer_sync(&priv->thermal_throttle.ct_kill_exit_tm); + /* stop ct_kill_waiting_tm timer if activated */ + del_timer_sync(&priv->thermal_throttle.ct_kill_waiting_tm); + cancel_work_sync(&priv->tt_work); + cancel_work_sync(&priv->ct_enter); + cancel_work_sync(&priv->ct_exit); + + if (priv->thermal_throttle.advanced_tt) { + /* free advance thermal throttling memory */ + kfree(tt->restriction); + tt->restriction = NULL; + kfree(tt->transaction); + tt->transaction = NULL; + } +} diff --git a/drivers/net/wireless/iwlwifi/dvm/tt.h b/drivers/net/wireless/iwlwifi/dvm/tt.h new file mode 100644 index 0000000..85ec9ed --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/tt.h @@ -0,0 +1,128 @@ +/****************************************************************************** + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + *****************************************************************************/ +#ifndef __iwl_tt_setting_h__ +#define __iwl_tt_setting_h__ + +#include "commands.h" + +#define IWL_ABSOLUTE_ZERO 0 +#define IWL_ABSOLUTE_MAX 0xFFFFFFFF +#define IWL_TT_INCREASE_MARGIN 5 +#define IWL_TT_CT_KILL_MARGIN 3 + +enum iwl_antenna_ok { + IWL_ANT_OK_NONE, + IWL_ANT_OK_SINGLE, + IWL_ANT_OK_MULTI, +}; + +/* Thermal Throttling State Machine states */ +enum iwl_tt_state { + IWL_TI_0, /* normal temperature, system power state */ + IWL_TI_1, /* high temperature detect, low power state */ + IWL_TI_2, /* higher temperature detected, lower power state */ + IWL_TI_CT_KILL, /* critical temperature detected, lowest power state */ + IWL_TI_STATE_MAX +}; + +/** + * struct iwl_tt_restriction - Thermal Throttling restriction table + * @tx_stream: number of tx stream allowed + * @is_ht: ht enable/disable + * @rx_stream: number of rx stream allowed + * + * This table is used by advance thermal throttling management + * based on the current thermal throttling state, and determines + * the number of tx/rx streams and the status of HT operation. + */ +struct iwl_tt_restriction { + enum iwl_antenna_ok tx_stream; + enum iwl_antenna_ok rx_stream; + bool is_ht; +}; + +/** + * struct iwl_tt_trans - Thermal Throttling transaction table + * @next_state: next thermal throttling mode + * @tt_low: low temperature threshold to change state + * @tt_high: high temperature threshold to change state + * + * This is used by the advanced thermal throttling algorithm + * to determine the next thermal state to go based on the + * current temperature. + */ +struct iwl_tt_trans { + enum iwl_tt_state next_state; + u32 tt_low; + u32 tt_high; +}; + +/** + * struct iwl_tt_mgnt - Thermal Throttling Management structure + * @advanced_tt: advanced thermal throttle required + * @state: current Thermal Throttling state + * @tt_power_mode: Thermal Throttling power mode index + * being used to set power level when + * when thermal throttling state != IWL_TI_0 + * the tt_power_mode should set to different + * power mode based on the current tt state + * @tt_previous_temperature: last measured temperature + * @iwl_tt_restriction: ptr to restriction tbl, used by advance + * thermal throttling to determine how many tx/rx streams + * should be used in tt state; and can HT be enabled or not + * @iwl_tt_trans: ptr to adv trans table, used by advance thermal throttling + * state transaction + * @ct_kill_toggle: used to toggle the CSR bit when checking uCode temperature + * @ct_kill_exit_tm: timer to exit thermal kill + */ +struct iwl_tt_mgmt { + enum iwl_tt_state state; + bool advanced_tt; + u8 tt_power_mode; + bool ct_kill_toggle; +#ifdef CPTCFG_IWLWIFI_DEBUG + s32 tt_previous_temp; +#endif + struct iwl_tt_restriction *restriction; + struct iwl_tt_trans *transaction; + struct timer_list ct_kill_exit_tm; + struct timer_list ct_kill_waiting_tm; +}; + +u8 iwl_tt_current_power_mode(struct iwl_priv *priv); +bool iwl_tt_is_low_power_state(struct iwl_priv *priv); +bool iwl_ht_enabled(struct iwl_priv *priv); +enum iwl_antenna_ok iwl_tx_ant_restriction(struct iwl_priv *priv); +enum iwl_antenna_ok iwl_rx_ant_restriction(struct iwl_priv *priv); +void iwl_tt_enter_ct_kill(struct iwl_priv *priv); +void iwl_tt_exit_ct_kill(struct iwl_priv *priv); +void iwl_tt_handler(struct iwl_priv *priv); +void iwl_tt_initialize(struct iwl_priv *priv); +void iwl_tt_exit(struct iwl_priv *priv); + +#endif /* __iwl_tt_setting_h__ */ diff --git a/drivers/net/wireless/iwlwifi/dvm/tx.c b/drivers/net/wireless/iwlwifi/dvm/tx.c new file mode 100644 index 0000000..88f8d69 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/tx.c @@ -0,0 +1,1453 @@ +/****************************************************************************** + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include "iwl-io.h" +#include "iwl-trans.h" +#include "iwl-agn-hw.h" +#include "dev.h" +#include "agn.h" + +static const u8 tid_to_ac[] = { + IEEE80211_AC_BE, + IEEE80211_AC_BK, + IEEE80211_AC_BK, + IEEE80211_AC_BE, + IEEE80211_AC_VI, + IEEE80211_AC_VI, + IEEE80211_AC_VO, + IEEE80211_AC_VO, +}; + +static void iwlagn_tx_cmd_protection(struct iwl_priv *priv, + struct ieee80211_tx_info *info, + __le16 fc, __le32 *tx_flags) +{ + if (info->control.rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS || + info->control.rates[0].flags & IEEE80211_TX_RC_USE_CTS_PROTECT || + info->flags & IEEE80211_TX_CTL_AMPDU) + *tx_flags |= TX_CMD_FLG_PROT_REQUIRE_MSK; +} + +/* + * handle build REPLY_TX command notification. + */ +static void iwlagn_tx_cmd_build_basic(struct iwl_priv *priv, + struct sk_buff *skb, + struct iwl_tx_cmd *tx_cmd, + struct ieee80211_tx_info *info, + struct ieee80211_hdr *hdr, u8 sta_id) +{ + __le16 fc = hdr->frame_control; + __le32 tx_flags = tx_cmd->tx_flags; + + tx_cmd->stop_time.life_time = TX_CMD_LIFE_TIME_INFINITE; + + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) + tx_flags |= TX_CMD_FLG_ACK_MSK; + else + tx_flags &= ~TX_CMD_FLG_ACK_MSK; + + if (ieee80211_is_probe_resp(fc)) + tx_flags |= TX_CMD_FLG_TSF_MSK; + else if (ieee80211_is_back_req(fc)) + tx_flags |= TX_CMD_FLG_ACK_MSK | TX_CMD_FLG_IMM_BA_RSP_MASK; + else if (info->band == IEEE80211_BAND_2GHZ && + priv->cfg->bt_params && + priv->cfg->bt_params->advanced_bt_coexist && + (ieee80211_is_auth(fc) || ieee80211_is_assoc_req(fc) || + ieee80211_is_reassoc_req(fc) || + skb->protocol == cpu_to_be16(ETH_P_PAE))) + tx_flags |= TX_CMD_FLG_IGNORE_BT; + + + tx_cmd->sta_id = sta_id; + if (ieee80211_has_morefrags(fc)) + tx_flags |= TX_CMD_FLG_MORE_FRAG_MSK; + + if (ieee80211_is_data_qos(fc)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + tx_cmd->tid_tspec = qc[0] & 0xf; + tx_flags &= ~TX_CMD_FLG_SEQ_CTL_MSK; + } else { + tx_cmd->tid_tspec = IWL_TID_NON_QOS; + if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) + tx_flags |= TX_CMD_FLG_SEQ_CTL_MSK; + else + tx_flags &= ~TX_CMD_FLG_SEQ_CTL_MSK; + } + + iwlagn_tx_cmd_protection(priv, info, fc, &tx_flags); + + tx_flags &= ~(TX_CMD_FLG_ANT_SEL_MSK); + if (ieee80211_is_mgmt(fc)) { + if (ieee80211_is_assoc_req(fc) || ieee80211_is_reassoc_req(fc)) + tx_cmd->timeout.pm_frame_timeout = cpu_to_le16(3); + else + tx_cmd->timeout.pm_frame_timeout = cpu_to_le16(2); + } else { + tx_cmd->timeout.pm_frame_timeout = 0; + } + + tx_cmd->driver_txop = 0; + tx_cmd->tx_flags = tx_flags; + tx_cmd->next_frame_len = 0; +} + +static void iwlagn_tx_cmd_build_rate(struct iwl_priv *priv, + struct iwl_tx_cmd *tx_cmd, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + __le16 fc) +{ + u32 rate_flags; + int rate_idx; + u8 rts_retry_limit; + u8 data_retry_limit; + u8 rate_plcp; + + if (priv->wowlan) { + rts_retry_limit = IWLAGN_LOW_RETRY_LIMIT; + data_retry_limit = IWLAGN_LOW_RETRY_LIMIT; + } else { + /* Set retry limit on RTS packets */ + rts_retry_limit = IWLAGN_RTS_DFAULT_RETRY_LIMIT; + + /* Set retry limit on DATA packets and Probe Responses*/ + if (ieee80211_is_probe_resp(fc)) { + data_retry_limit = IWLAGN_MGMT_DFAULT_RETRY_LIMIT; + rts_retry_limit = + min(data_retry_limit, rts_retry_limit); + } else if (ieee80211_is_back_req(fc)) + data_retry_limit = IWLAGN_BAR_DFAULT_RETRY_LIMIT; + else + data_retry_limit = IWLAGN_DEFAULT_TX_RETRY; + } + + tx_cmd->data_retry_limit = data_retry_limit; + tx_cmd->rts_retry_limit = rts_retry_limit; + + /* DATA packets will use the uCode station table for rate/antenna + * selection */ + if (ieee80211_is_data(fc)) { + tx_cmd->initial_rate_index = 0; + tx_cmd->tx_flags |= TX_CMD_FLG_STA_RATE_MSK; +#ifdef CPTCFG_IWLWIFI_DEVICE_TESTMODE + if (priv->tm_fixed_rate) { + /* + * rate overwrite by testmode + * we not only send lq command to change rate + * we also re-enforce per data pkt base. + */ + tx_cmd->tx_flags &= ~TX_CMD_FLG_STA_RATE_MSK; + memcpy(&tx_cmd->rate_n_flags, &priv->tm_fixed_rate, + sizeof(tx_cmd->rate_n_flags)); + } +#endif + return; + } else if (ieee80211_is_back_req(fc)) + tx_cmd->tx_flags |= TX_CMD_FLG_STA_RATE_MSK; + + /** + * If the current TX rate stored in mac80211 has the MCS bit set, it's + * not really a TX rate. Thus, we use the lowest supported rate for + * this band. Also use the lowest supported rate if the stored rate + * index is invalid. + */ + rate_idx = info->control.rates[0].idx; + if (info->control.rates[0].flags & IEEE80211_TX_RC_MCS || + (rate_idx < 0) || (rate_idx > IWL_RATE_COUNT_LEGACY)) + rate_idx = rate_lowest_index( + &priv->nvm_data->bands[info->band], sta); + /* For 5 GHZ band, remap mac80211 rate indices into driver indices */ + if (info->band == IEEE80211_BAND_5GHZ) + rate_idx += IWL_FIRST_OFDM_RATE; + /* Get PLCP rate for tx_cmd->rate_n_flags */ + rate_plcp = iwl_rates[rate_idx].plcp; + /* Zero out flags for this packet */ + rate_flags = 0; + + /* Set CCK flag as needed */ + if ((rate_idx >= IWL_FIRST_CCK_RATE) && (rate_idx <= IWL_LAST_CCK_RATE)) + rate_flags |= RATE_MCS_CCK_MSK; + + /* Set up antennas */ + if (priv->cfg->bt_params && + priv->cfg->bt_params->advanced_bt_coexist && + priv->bt_full_concurrent) { + /* operated as 1x1 in full concurrency mode */ + priv->mgmt_tx_ant = iwl_toggle_tx_ant(priv, priv->mgmt_tx_ant, + first_antenna(priv->nvm_data->valid_tx_ant)); + } else + priv->mgmt_tx_ant = iwl_toggle_tx_ant( + priv, priv->mgmt_tx_ant, + priv->nvm_data->valid_tx_ant); + rate_flags |= iwl_ant_idx_to_flags(priv->mgmt_tx_ant); + + /* Set the rate in the TX cmd */ + tx_cmd->rate_n_flags = iwl_hw_set_rate_n_flags(rate_plcp, rate_flags); +} + +static void iwlagn_tx_cmd_build_hwcrypto(struct iwl_priv *priv, + struct ieee80211_tx_info *info, + struct iwl_tx_cmd *tx_cmd, + struct sk_buff *skb_frag) +{ + struct ieee80211_key_conf *keyconf = info->control.hw_key; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + tx_cmd->sec_ctl = TX_CMD_SEC_CCM; + memcpy(tx_cmd->key, keyconf->key, keyconf->keylen); + if (info->flags & IEEE80211_TX_CTL_AMPDU) + tx_cmd->tx_flags |= TX_CMD_FLG_AGG_CCMP_MSK; + break; + + case WLAN_CIPHER_SUITE_TKIP: + tx_cmd->sec_ctl = TX_CMD_SEC_TKIP; + ieee80211_get_tkip_p2k(keyconf, skb_frag, tx_cmd->key); + break; + + case WLAN_CIPHER_SUITE_WEP104: + tx_cmd->sec_ctl |= TX_CMD_SEC_KEY128; + /* fall through */ + case WLAN_CIPHER_SUITE_WEP40: + tx_cmd->sec_ctl |= (TX_CMD_SEC_WEP | + (keyconf->keyidx & TX_CMD_SEC_MSK) << TX_CMD_SEC_SHIFT); + + memcpy(&tx_cmd->key[3], keyconf->key, keyconf->keylen); + + IWL_DEBUG_TX(priv, "Configuring packet for WEP encryption " + "with key %d\n", keyconf->keyidx); + break; + + default: + IWL_ERR(priv, "Unknown encode cipher %x\n", keyconf->cipher); + break; + } +} + +/** + * iwl_sta_id_or_broadcast - return sta_id or broadcast sta + * @context: the current context + * @sta: mac80211 station + * + * In certain circumstances mac80211 passes a station pointer + * that may be %NULL, for example during TX or key setup. In + * that case, we need to use the broadcast station, so this + * inline wraps that pattern. + */ +static int iwl_sta_id_or_broadcast(struct iwl_rxon_context *context, + struct ieee80211_sta *sta) +{ + int sta_id; + + if (!sta) + return context->bcast_sta_id; + + sta_id = iwl_sta_id(sta); + + /* + * mac80211 should not be passing a partially + * initialised station! + */ + WARN_ON(sta_id == IWL_INVALID_STATION); + + return sta_id; +} + +/* + * start REPLY_TX command process + */ +int iwlagn_tx_skb(struct iwl_priv *priv, + struct ieee80211_sta *sta, + struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct iwl_station_priv *sta_priv = NULL; + struct iwl_rxon_context *ctx = &priv->contexts[IWL_RXON_CTX_BSS]; + struct iwl_device_cmd *dev_cmd; + struct iwl_tx_cmd *tx_cmd; + __le16 fc; + u8 hdr_len; + u16 len, seq_number = 0; + u8 sta_id, tid = IWL_MAX_TID_COUNT; + bool is_agg = false, is_data_qos = false; + int txq_id; + + if (info->control.vif) + ctx = iwl_rxon_ctx_from_vif(info->control.vif); + + if (iwl_is_rfkill(priv)) { + IWL_DEBUG_DROP(priv, "Dropping - RF KILL\n"); + goto drop_unlock_priv; + } + + fc = hdr->frame_control; + +#ifdef CPTCFG_IWLWIFI_DEBUG + if (ieee80211_is_auth(fc)) + IWL_DEBUG_TX(priv, "Sending AUTH frame\n"); + else if (ieee80211_is_assoc_req(fc)) + IWL_DEBUG_TX(priv, "Sending ASSOC frame\n"); + else if (ieee80211_is_reassoc_req(fc)) + IWL_DEBUG_TX(priv, "Sending REASSOC frame\n"); +#endif + + if (unlikely(ieee80211_is_probe_resp(fc))) { + struct iwl_wipan_noa_data *noa_data = + rcu_dereference(priv->noa_data); + + if (noa_data && + pskb_expand_head(skb, 0, noa_data->length, + GFP_ATOMIC) == 0) { + memcpy(skb_put(skb, noa_data->length), + noa_data->data, noa_data->length); + hdr = (struct ieee80211_hdr *)skb->data; + } + } + + hdr_len = ieee80211_hdrlen(fc); + + /* For management frames use broadcast id to do not break aggregation */ + if (!ieee80211_is_data(fc)) + sta_id = ctx->bcast_sta_id; + else { + /* Find index into station table for destination station */ + sta_id = iwl_sta_id_or_broadcast(ctx, sta); + if (sta_id == IWL_INVALID_STATION) { + IWL_DEBUG_DROP(priv, "Dropping - INVALID STATION: %pM\n", + hdr->addr1); + goto drop_unlock_priv; + } + } + + if (sta) + sta_priv = (void *)sta->drv_priv; + + if (sta_priv && sta_priv->asleep && + (info->flags & IEEE80211_TX_CTL_NO_PS_BUFFER)) { + /* + * This sends an asynchronous command to the device, + * but we can rely on it being processed before the + * next frame is processed -- and the next frame to + * this station is the one that will consume this + * counter. + * For now set the counter to just 1 since we do not + * support uAPSD yet. + * + * FIXME: If we get two non-bufferable frames one + * after the other, we might only send out one of + * them because this is racy. + */ + iwl_sta_modify_sleep_tx_count(priv, sta_id, 1); + } + + dev_cmd = iwl_trans_alloc_tx_cmd(priv->trans); + + if (unlikely(!dev_cmd)) + goto drop_unlock_priv; + + memset(dev_cmd, 0, sizeof(*dev_cmd)); + tx_cmd = (struct iwl_tx_cmd *) dev_cmd->payload; + + /* Total # bytes to be transmitted */ + len = (u16)skb->len; + tx_cmd->len = cpu_to_le16(len); + + if (info->control.hw_key) + iwlagn_tx_cmd_build_hwcrypto(priv, info, tx_cmd, skb); + + /* TODO need this for burst mode later on */ + iwlagn_tx_cmd_build_basic(priv, skb, tx_cmd, info, hdr, sta_id); + + iwlagn_tx_cmd_build_rate(priv, tx_cmd, info, sta, fc); + + memset(&info->status, 0, sizeof(info->status)); + + info->driver_data[0] = ctx; + info->driver_data[1] = dev_cmd; + /* From now on, we cannot access info->control */ + + spin_lock(&priv->sta_lock); + + if (ieee80211_is_data_qos(fc) && !ieee80211_is_qos_nullfunc(fc)) { + u8 *qc = NULL; + struct iwl_tid_data *tid_data; + qc = ieee80211_get_qos_ctl(hdr); + tid = qc[0] & IEEE80211_QOS_CTL_TID_MASK; + if (WARN_ON_ONCE(tid >= IWL_MAX_TID_COUNT)) + goto drop_unlock_sta; + tid_data = &priv->tid_data[sta_id][tid]; + + /* aggregation is on for this */ + if (info->flags & IEEE80211_TX_CTL_AMPDU && + tid_data->agg.state != IWL_AGG_ON) { + IWL_ERR(priv, "TX_CTL_AMPDU while not in AGG:" + " Tx flags = 0x%08x, agg.state = %d", + info->flags, tid_data->agg.state); + IWL_ERR(priv, "sta_id = %d, tid = %d seq_num = %d", + sta_id, tid, + IEEE80211_SEQ_TO_SN(tid_data->seq_number)); + goto drop_unlock_sta; + } + + /* We can receive packets from the stack in IWL_AGG_{ON,OFF} + * only. Check this here. + */ + if (WARN_ONCE(tid_data->agg.state != IWL_AGG_ON && + tid_data->agg.state != IWL_AGG_OFF, + "Tx while agg.state = %d", tid_data->agg.state)) + goto drop_unlock_sta; + + seq_number = tid_data->seq_number; + seq_number &= IEEE80211_SCTL_SEQ; + hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); + hdr->seq_ctrl |= cpu_to_le16(seq_number); + seq_number += 0x10; + + if (info->flags & IEEE80211_TX_CTL_AMPDU) + is_agg = true; + is_data_qos = true; + } + + /* Copy MAC header from skb into command buffer */ + memcpy(tx_cmd->hdr, hdr, hdr_len); + + if (is_agg) + txq_id = priv->tid_data[sta_id][tid].agg.txq_id; + else if (info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM) { + /* + * Send this frame after DTIM -- there's a special queue + * reserved for this for contexts that support AP mode. + */ + txq_id = ctx->mcast_queue; + + /* + * The microcode will clear the more data + * bit in the last frame it transmits. + */ + hdr->frame_control |= + cpu_to_le16(IEEE80211_FCTL_MOREDATA); + } else if (info->flags & IEEE80211_TX_CTL_TX_OFFCHAN) + txq_id = IWL_AUX_QUEUE; + else + txq_id = ctx->ac_to_queue[skb_get_queue_mapping(skb)]; + + WARN_ON_ONCE(!is_agg && txq_id != info->hw_queue); + WARN_ON_ONCE(is_agg && + priv->queue_to_mac80211[txq_id] != info->hw_queue); + + IWL_DEBUG_TX(priv, "TX to [%d|%d] Q:%d - seq: 0x%x\n", sta_id, tid, + txq_id, seq_number); + + if (iwl_trans_tx(priv->trans, skb, dev_cmd, txq_id)) + goto drop_unlock_sta; + + if (is_data_qos && !ieee80211_has_morefrags(fc)) + priv->tid_data[sta_id][tid].seq_number = seq_number; + + spin_unlock(&priv->sta_lock); + + /* + * Avoid atomic ops if it isn't an associated client. + * Also, if this is a packet for aggregation, don't + * increase the counter because the ucode will stop + * aggregation queues when their respective station + * goes to sleep. + */ + if (sta_priv && sta_priv->client && !is_agg) + atomic_inc(&sta_priv->pending_frames); + + if (info->flags & IEEE80211_TX_CTL_TX_OFFCHAN) + iwl_scan_offchannel_skb(priv); + + return 0; + +drop_unlock_sta: + if (dev_cmd) + iwl_trans_free_tx_cmd(priv->trans, dev_cmd); + spin_unlock(&priv->sta_lock); +drop_unlock_priv: + return -1; +} + +static int iwlagn_alloc_agg_txq(struct iwl_priv *priv, int mq) +{ + int q; + + for (q = IWLAGN_FIRST_AMPDU_QUEUE; + q < priv->cfg->base_params->num_of_queues; q++) { + if (!test_and_set_bit(q, priv->agg_q_alloc)) { + priv->queue_to_mac80211[q] = mq; + return q; + } + } + + return -ENOSPC; +} + +static void iwlagn_dealloc_agg_txq(struct iwl_priv *priv, int q) +{ + clear_bit(q, priv->agg_q_alloc); + priv->queue_to_mac80211[q] = IWL_INVALID_MAC80211_QUEUE; +} + +int iwlagn_tx_agg_stop(struct iwl_priv *priv, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid) +{ + struct iwl_tid_data *tid_data; + int sta_id, txq_id; + enum iwl_agg_state agg_state; + + sta_id = iwl_sta_id(sta); + + if (sta_id == IWL_INVALID_STATION) { + IWL_ERR(priv, "Invalid station for AGG tid %d\n", tid); + return -ENXIO; + } + + spin_lock_bh(&priv->sta_lock); + + tid_data = &priv->tid_data[sta_id][tid]; + txq_id = tid_data->agg.txq_id; + + switch (tid_data->agg.state) { + case IWL_EMPTYING_HW_QUEUE_ADDBA: + /* + * This can happen if the peer stops aggregation + * again before we've had a chance to drain the + * queue we selected previously, i.e. before the + * session was really started completely. + */ + IWL_DEBUG_HT(priv, "AGG stop before setup done\n"); + goto turn_off; + case IWL_AGG_STARTING: + /* + * This can happen when the session is stopped before + * we receive ADDBA response + */ + IWL_DEBUG_HT(priv, "AGG stop before AGG became operational\n"); + goto turn_off; + case IWL_AGG_ON: + break; + default: + IWL_WARN(priv, + "Stopping AGG while state not ON or starting for %d on %d (%d)\n", + sta_id, tid, tid_data->agg.state); + spin_unlock_bh(&priv->sta_lock); + return 0; + } + + tid_data->agg.ssn = IEEE80211_SEQ_TO_SN(tid_data->seq_number); + + /* There are still packets for this RA / TID in the HW */ + if (!test_bit(txq_id, priv->agg_q_alloc)) { + IWL_DEBUG_TX_QUEUES(priv, + "stopping AGG on STA/TID %d/%d but hwq %d not used\n", + sta_id, tid, txq_id); + } else if (tid_data->agg.ssn != tid_data->next_reclaimed) { + IWL_DEBUG_TX_QUEUES(priv, + "Can't proceed: ssn %d, next_recl = %d\n", + tid_data->agg.ssn, + tid_data->next_reclaimed); + tid_data->agg.state = IWL_EMPTYING_HW_QUEUE_DELBA; + spin_unlock_bh(&priv->sta_lock); + return 0; + } + + IWL_DEBUG_TX_QUEUES(priv, "Can proceed: ssn = next_recl = %d\n", + tid_data->agg.ssn); +turn_off: + agg_state = tid_data->agg.state; + tid_data->agg.state = IWL_AGG_OFF; + + spin_unlock_bh(&priv->sta_lock); + + if (test_bit(txq_id, priv->agg_q_alloc)) { + /* + * If the transport didn't know that we wanted to start + * agreggation, don't tell it that we want to stop them. + * This can happen when we don't get the addBA response on + * time, or we hadn't time to drain the AC queues. + */ + if (agg_state == IWL_AGG_ON) + iwl_trans_txq_disable(priv->trans, txq_id); + else + IWL_DEBUG_TX_QUEUES(priv, "Don't disable tx agg: %d\n", + agg_state); + iwlagn_dealloc_agg_txq(priv, txq_id); + } + + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + + return 0; +} + +int iwlagn_tx_agg_start(struct iwl_priv *priv, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 *ssn) +{ + struct iwl_rxon_context *ctx = iwl_rxon_ctx_from_vif(vif); + struct iwl_tid_data *tid_data; + int sta_id, txq_id, ret; + + IWL_DEBUG_HT(priv, "TX AGG request on ra = %pM tid = %d\n", + sta->addr, tid); + + sta_id = iwl_sta_id(sta); + if (sta_id == IWL_INVALID_STATION) { + IWL_ERR(priv, "Start AGG on invalid station\n"); + return -ENXIO; + } + if (unlikely(tid >= IWL_MAX_TID_COUNT)) + return -EINVAL; + + if (priv->tid_data[sta_id][tid].agg.state != IWL_AGG_OFF) { + IWL_ERR(priv, "Start AGG when state is not IWL_AGG_OFF !\n"); + return -ENXIO; + } + + txq_id = iwlagn_alloc_agg_txq(priv, ctx->ac_to_queue[tid_to_ac[tid]]); + if (txq_id < 0) { + IWL_DEBUG_TX_QUEUES(priv, + "No free aggregation queue for %pM/%d\n", + sta->addr, tid); + return txq_id; + } + + ret = iwl_sta_tx_modify_enable_tid(priv, sta_id, tid); + if (ret) + return ret; + + spin_lock_bh(&priv->sta_lock); + tid_data = &priv->tid_data[sta_id][tid]; + tid_data->agg.ssn = IEEE80211_SEQ_TO_SN(tid_data->seq_number); + tid_data->agg.txq_id = txq_id; + + *ssn = tid_data->agg.ssn; + + if (*ssn == tid_data->next_reclaimed) { + IWL_DEBUG_TX_QUEUES(priv, "Can proceed: ssn = next_recl = %d\n", + tid_data->agg.ssn); + tid_data->agg.state = IWL_AGG_STARTING; + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + } else { + IWL_DEBUG_TX_QUEUES(priv, "Can't proceed: ssn %d, " + "next_reclaimed = %d\n", + tid_data->agg.ssn, + tid_data->next_reclaimed); + tid_data->agg.state = IWL_EMPTYING_HW_QUEUE_ADDBA; + } + spin_unlock_bh(&priv->sta_lock); + + return ret; +} + +int iwlagn_tx_agg_flush(struct iwl_priv *priv, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid) +{ + struct iwl_tid_data *tid_data; + enum iwl_agg_state agg_state; + int sta_id, txq_id; + sta_id = iwl_sta_id(sta); + + /* + * First set the agg state to OFF to avoid calling + * ieee80211_stop_tx_ba_cb in iwlagn_check_ratid_empty. + */ + spin_lock_bh(&priv->sta_lock); + + tid_data = &priv->tid_data[sta_id][tid]; + txq_id = tid_data->agg.txq_id; + agg_state = tid_data->agg.state; + IWL_DEBUG_TX_QUEUES(priv, "Flush AGG: sta %d tid %d q %d state %d\n", + sta_id, tid, txq_id, tid_data->agg.state); + + tid_data->agg.state = IWL_AGG_OFF; + + spin_unlock_bh(&priv->sta_lock); + + if (iwlagn_txfifo_flush(priv, BIT(txq_id))) + IWL_ERR(priv, "Couldn't flush the AGG queue\n"); + + if (test_bit(txq_id, priv->agg_q_alloc)) { + /* + * If the transport didn't know that we wanted to start + * agreggation, don't tell it that we want to stop them. + * This can happen when we don't get the addBA response on + * time, or we hadn't time to drain the AC queues. + */ + if (agg_state == IWL_AGG_ON) + iwl_trans_txq_disable(priv->trans, txq_id); + else + IWL_DEBUG_TX_QUEUES(priv, "Don't disable tx agg: %d\n", + agg_state); + iwlagn_dealloc_agg_txq(priv, txq_id); + } + + return 0; +} + +int iwlagn_tx_agg_oper(struct iwl_priv *priv, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u8 buf_size) +{ + struct iwl_station_priv *sta_priv = (void *) sta->drv_priv; + struct iwl_rxon_context *ctx = iwl_rxon_ctx_from_vif(vif); + int q, fifo; + u16 ssn; + + buf_size = min_t(int, buf_size, LINK_QUAL_AGG_FRAME_LIMIT_DEF); + + spin_lock_bh(&priv->sta_lock); + ssn = priv->tid_data[sta_priv->sta_id][tid].agg.ssn; + q = priv->tid_data[sta_priv->sta_id][tid].agg.txq_id; + priv->tid_data[sta_priv->sta_id][tid].agg.state = IWL_AGG_ON; + spin_unlock_bh(&priv->sta_lock); + + fifo = ctx->ac_to_fifo[tid_to_ac[tid]]; + + iwl_trans_txq_enable(priv->trans, q, fifo, sta_priv->sta_id, tid, + buf_size, ssn); + + /* + * If the limit is 0, then it wasn't initialised yet, + * use the default. We can do that since we take the + * minimum below, and we don't want to go above our + * default due to hardware restrictions. + */ + if (sta_priv->max_agg_bufsize == 0) + sta_priv->max_agg_bufsize = + LINK_QUAL_AGG_FRAME_LIMIT_DEF; + + /* + * Even though in theory the peer could have different + * aggregation reorder buffer sizes for different sessions, + * our ucode doesn't allow for that and has a global limit + * for each station. Therefore, use the minimum of all the + * aggregation sessions and our default value. + */ + sta_priv->max_agg_bufsize = + min(sta_priv->max_agg_bufsize, buf_size); + + if (priv->hw_params.use_rts_for_aggregation) { + /* + * switch to RTS/CTS if it is the prefer protection + * method for HT traffic + */ + + sta_priv->lq_sta.lq.general_params.flags |= + LINK_QUAL_FLAGS_SET_STA_TLC_RTS_MSK; + } + priv->agg_tids_count++; + IWL_DEBUG_HT(priv, "priv->agg_tids_count = %u\n", + priv->agg_tids_count); + + sta_priv->lq_sta.lq.agg_params.agg_frame_cnt_limit = + sta_priv->max_agg_bufsize; + + IWL_DEBUG_HT(priv, "Tx aggregation enabled on ra = %pM tid = %d\n", + sta->addr, tid); + + return iwl_send_lq_cmd(priv, ctx, + &sta_priv->lq_sta.lq, CMD_ASYNC, false); +} + +static void iwlagn_check_ratid_empty(struct iwl_priv *priv, int sta_id, u8 tid) +{ + struct iwl_tid_data *tid_data = &priv->tid_data[sta_id][tid]; + enum iwl_rxon_context_id ctx; + struct ieee80211_vif *vif; + u8 *addr; + + lockdep_assert_held(&priv->sta_lock); + + addr = priv->stations[sta_id].sta.sta.addr; + ctx = priv->stations[sta_id].ctxid; + vif = priv->contexts[ctx].vif; + + switch (priv->tid_data[sta_id][tid].agg.state) { + case IWL_EMPTYING_HW_QUEUE_DELBA: + /* There are no packets for this RA / TID in the HW any more */ + if (tid_data->agg.ssn == tid_data->next_reclaimed) { + IWL_DEBUG_TX_QUEUES(priv, + "Can continue DELBA flow ssn = next_recl =" + " %d", tid_data->next_reclaimed); + iwl_trans_txq_disable(priv->trans, + tid_data->agg.txq_id); + iwlagn_dealloc_agg_txq(priv, tid_data->agg.txq_id); + tid_data->agg.state = IWL_AGG_OFF; + ieee80211_stop_tx_ba_cb_irqsafe(vif, addr, tid); + } + break; + case IWL_EMPTYING_HW_QUEUE_ADDBA: + /* There are no packets for this RA / TID in the HW any more */ + if (tid_data->agg.ssn == tid_data->next_reclaimed) { + IWL_DEBUG_TX_QUEUES(priv, + "Can continue ADDBA flow ssn = next_recl =" + " %d", tid_data->next_reclaimed); + tid_data->agg.state = IWL_AGG_STARTING; + ieee80211_start_tx_ba_cb_irqsafe(vif, addr, tid); + } + break; + default: + break; + } +} + +static void iwlagn_non_agg_tx_status(struct iwl_priv *priv, + struct iwl_rxon_context *ctx, + const u8 *addr1) +{ + struct ieee80211_sta *sta; + struct iwl_station_priv *sta_priv; + + rcu_read_lock(); + sta = ieee80211_find_sta(ctx->vif, addr1); + if (sta) { + sta_priv = (void *)sta->drv_priv; + /* avoid atomic ops if this isn't a client */ + if (sta_priv->client && + atomic_dec_return(&sta_priv->pending_frames) == 0) + ieee80211_sta_block_awake(priv->hw, sta, false); + } + rcu_read_unlock(); +} + +/** + * translate ucode response to mac80211 tx status control values + */ +static void iwlagn_hwrate_to_tx_control(struct iwl_priv *priv, u32 rate_n_flags, + struct ieee80211_tx_info *info) +{ + struct ieee80211_tx_rate *r = &info->status.rates[0]; + + info->status.antenna = + ((rate_n_flags & RATE_MCS_ANT_ABC_MSK) >> RATE_MCS_ANT_POS); + if (rate_n_flags & RATE_MCS_HT_MSK) + r->flags |= IEEE80211_TX_RC_MCS; + if (rate_n_flags & RATE_MCS_GF_MSK) + r->flags |= IEEE80211_TX_RC_GREEN_FIELD; + if (rate_n_flags & RATE_MCS_HT40_MSK) + r->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; + if (rate_n_flags & RATE_MCS_DUP_MSK) + r->flags |= IEEE80211_TX_RC_DUP_DATA; + if (rate_n_flags & RATE_MCS_SGI_MSK) + r->flags |= IEEE80211_TX_RC_SHORT_GI; + r->idx = iwlagn_hwrate_to_mac80211_idx(rate_n_flags, info->band); +} + +#ifdef CPTCFG_IWLWIFI_DEBUG +const char *iwl_get_tx_fail_reason(u32 status) +{ +#define TX_STATUS_FAIL(x) case TX_STATUS_FAIL_ ## x: return #x +#define TX_STATUS_POSTPONE(x) case TX_STATUS_POSTPONE_ ## x: return #x + + switch (status & TX_STATUS_MSK) { + case TX_STATUS_SUCCESS: + return "SUCCESS"; + TX_STATUS_POSTPONE(DELAY); + TX_STATUS_POSTPONE(FEW_BYTES); + TX_STATUS_POSTPONE(BT_PRIO); + TX_STATUS_POSTPONE(QUIET_PERIOD); + TX_STATUS_POSTPONE(CALC_TTAK); + TX_STATUS_FAIL(INTERNAL_CROSSED_RETRY); + TX_STATUS_FAIL(SHORT_LIMIT); + TX_STATUS_FAIL(LONG_LIMIT); + TX_STATUS_FAIL(FIFO_UNDERRUN); + TX_STATUS_FAIL(DRAIN_FLOW); + TX_STATUS_FAIL(RFKILL_FLUSH); + TX_STATUS_FAIL(LIFE_EXPIRE); + TX_STATUS_FAIL(DEST_PS); + TX_STATUS_FAIL(HOST_ABORTED); + TX_STATUS_FAIL(BT_RETRY); + TX_STATUS_FAIL(STA_INVALID); + TX_STATUS_FAIL(FRAG_DROPPED); + TX_STATUS_FAIL(TID_DISABLE); + TX_STATUS_FAIL(FIFO_FLUSHED); + TX_STATUS_FAIL(INSUFFICIENT_CF_POLL); + TX_STATUS_FAIL(PASSIVE_NO_RX); + TX_STATUS_FAIL(NO_BEACON_ON_RADAR); + } + + return "UNKNOWN"; + +#undef TX_STATUS_FAIL +#undef TX_STATUS_POSTPONE +} +#endif /* CPTCFG_IWLWIFI_DEBUG */ + +static void iwlagn_count_agg_tx_err_status(struct iwl_priv *priv, u16 status) +{ + status &= AGG_TX_STATUS_MSK; + + switch (status) { + case AGG_TX_STATE_UNDERRUN_MSK: + priv->reply_agg_tx_stats.underrun++; + break; + case AGG_TX_STATE_BT_PRIO_MSK: + priv->reply_agg_tx_stats.bt_prio++; + break; + case AGG_TX_STATE_FEW_BYTES_MSK: + priv->reply_agg_tx_stats.few_bytes++; + break; + case AGG_TX_STATE_ABORT_MSK: + priv->reply_agg_tx_stats.abort++; + break; + case AGG_TX_STATE_LAST_SENT_TTL_MSK: + priv->reply_agg_tx_stats.last_sent_ttl++; + break; + case AGG_TX_STATE_LAST_SENT_TRY_CNT_MSK: + priv->reply_agg_tx_stats.last_sent_try++; + break; + case AGG_TX_STATE_LAST_SENT_BT_KILL_MSK: + priv->reply_agg_tx_stats.last_sent_bt_kill++; + break; + case AGG_TX_STATE_SCD_QUERY_MSK: + priv->reply_agg_tx_stats.scd_query++; + break; + case AGG_TX_STATE_TEST_BAD_CRC32_MSK: + priv->reply_agg_tx_stats.bad_crc32++; + break; + case AGG_TX_STATE_RESPONSE_MSK: + priv->reply_agg_tx_stats.response++; + break; + case AGG_TX_STATE_DUMP_TX_MSK: + priv->reply_agg_tx_stats.dump_tx++; + break; + case AGG_TX_STATE_DELAY_TX_MSK: + priv->reply_agg_tx_stats.delay_tx++; + break; + default: + priv->reply_agg_tx_stats.unknown++; + break; + } +} + +static inline u32 iwlagn_get_scd_ssn(struct iwlagn_tx_resp *tx_resp) +{ + return le32_to_cpup((__le32 *)&tx_resp->status + + tx_resp->frame_count) & IEEE80211_MAX_SN; +} + +static void iwl_rx_reply_tx_agg(struct iwl_priv *priv, + struct iwlagn_tx_resp *tx_resp) +{ + struct agg_tx_status *frame_status = &tx_resp->status; + int tid = (tx_resp->ra_tid & IWLAGN_TX_RES_TID_MSK) >> + IWLAGN_TX_RES_TID_POS; + int sta_id = (tx_resp->ra_tid & IWLAGN_TX_RES_RA_MSK) >> + IWLAGN_TX_RES_RA_POS; + struct iwl_ht_agg *agg = &priv->tid_data[sta_id][tid].agg; + u32 status = le16_to_cpu(tx_resp->status.status); + int i; + + WARN_ON(tid == IWL_TID_NON_QOS); + + if (agg->wait_for_ba) + IWL_DEBUG_TX_REPLY(priv, + "got tx response w/o block-ack\n"); + + agg->rate_n_flags = le32_to_cpu(tx_resp->rate_n_flags); + agg->wait_for_ba = (tx_resp->frame_count > 1); + + /* + * If the BT kill count is non-zero, we'll get this + * notification again. + */ + if (tx_resp->bt_kill_count && tx_resp->frame_count == 1 && + priv->cfg->bt_params && + priv->cfg->bt_params->advanced_bt_coexist) { + IWL_DEBUG_COEX(priv, "receive reply tx w/ bt_kill\n"); + } + + if (tx_resp->frame_count == 1) + return; + + IWL_DEBUG_TX_REPLY(priv, "TXQ %d initial_rate 0x%x ssn %d frm_cnt %d\n", + agg->txq_id, + le32_to_cpu(tx_resp->rate_n_flags), + iwlagn_get_scd_ssn(tx_resp), tx_resp->frame_count); + + /* Construct bit-map of pending frames within Tx window */ + for (i = 0; i < tx_resp->frame_count; i++) { + u16 fstatus = le16_to_cpu(frame_status[i].status); + u8 retry_cnt = (fstatus & AGG_TX_TRY_MSK) >> AGG_TX_TRY_POS; + + if (status & AGG_TX_STATUS_MSK) + iwlagn_count_agg_tx_err_status(priv, fstatus); + + if (status & (AGG_TX_STATE_FEW_BYTES_MSK | + AGG_TX_STATE_ABORT_MSK)) + continue; + + if (status & AGG_TX_STATUS_MSK || retry_cnt > 1) + IWL_DEBUG_TX_REPLY(priv, + "%d: status %s (0x%04x), try-count (0x%01x)\n", + i, + iwl_get_agg_tx_fail_reason(fstatus), + fstatus & AGG_TX_STATUS_MSK, + retry_cnt); + } +} + +#ifdef CPTCFG_IWLWIFI_DEBUG +#define AGG_TX_STATE_FAIL(x) case AGG_TX_STATE_ ## x: return #x + +const char *iwl_get_agg_tx_fail_reason(u16 status) +{ + status &= AGG_TX_STATUS_MSK; + switch (status) { + case AGG_TX_STATE_TRANSMITTED: + return "SUCCESS"; + AGG_TX_STATE_FAIL(UNDERRUN_MSK); + AGG_TX_STATE_FAIL(BT_PRIO_MSK); + AGG_TX_STATE_FAIL(FEW_BYTES_MSK); + AGG_TX_STATE_FAIL(ABORT_MSK); + AGG_TX_STATE_FAIL(LAST_SENT_TTL_MSK); + AGG_TX_STATE_FAIL(LAST_SENT_TRY_CNT_MSK); + AGG_TX_STATE_FAIL(LAST_SENT_BT_KILL_MSK); + AGG_TX_STATE_FAIL(SCD_QUERY_MSK); + AGG_TX_STATE_FAIL(TEST_BAD_CRC32_MSK); + AGG_TX_STATE_FAIL(RESPONSE_MSK); + AGG_TX_STATE_FAIL(DUMP_TX_MSK); + AGG_TX_STATE_FAIL(DELAY_TX_MSK); + } + + return "UNKNOWN"; +} +#endif /* CPTCFG_IWLWIFI_DEBUG */ + +static void iwlagn_count_tx_err_status(struct iwl_priv *priv, u16 status) +{ + status &= TX_STATUS_MSK; + + switch (status) { + case TX_STATUS_POSTPONE_DELAY: + priv->reply_tx_stats.pp_delay++; + break; + case TX_STATUS_POSTPONE_FEW_BYTES: + priv->reply_tx_stats.pp_few_bytes++; + break; + case TX_STATUS_POSTPONE_BT_PRIO: + priv->reply_tx_stats.pp_bt_prio++; + break; + case TX_STATUS_POSTPONE_QUIET_PERIOD: + priv->reply_tx_stats.pp_quiet_period++; + break; + case TX_STATUS_POSTPONE_CALC_TTAK: + priv->reply_tx_stats.pp_calc_ttak++; + break; + case TX_STATUS_FAIL_INTERNAL_CROSSED_RETRY: + priv->reply_tx_stats.int_crossed_retry++; + break; + case TX_STATUS_FAIL_SHORT_LIMIT: + priv->reply_tx_stats.short_limit++; + break; + case TX_STATUS_FAIL_LONG_LIMIT: + priv->reply_tx_stats.long_limit++; + break; + case TX_STATUS_FAIL_FIFO_UNDERRUN: + priv->reply_tx_stats.fifo_underrun++; + break; + case TX_STATUS_FAIL_DRAIN_FLOW: + priv->reply_tx_stats.drain_flow++; + break; + case TX_STATUS_FAIL_RFKILL_FLUSH: + priv->reply_tx_stats.rfkill_flush++; + break; + case TX_STATUS_FAIL_LIFE_EXPIRE: + priv->reply_tx_stats.life_expire++; + break; + case TX_STATUS_FAIL_DEST_PS: + priv->reply_tx_stats.dest_ps++; + break; + case TX_STATUS_FAIL_HOST_ABORTED: + priv->reply_tx_stats.host_abort++; + break; + case TX_STATUS_FAIL_BT_RETRY: + priv->reply_tx_stats.bt_retry++; + break; + case TX_STATUS_FAIL_STA_INVALID: + priv->reply_tx_stats.sta_invalid++; + break; + case TX_STATUS_FAIL_FRAG_DROPPED: + priv->reply_tx_stats.frag_drop++; + break; + case TX_STATUS_FAIL_TID_DISABLE: + priv->reply_tx_stats.tid_disable++; + break; + case TX_STATUS_FAIL_FIFO_FLUSHED: + priv->reply_tx_stats.fifo_flush++; + break; + case TX_STATUS_FAIL_INSUFFICIENT_CF_POLL: + priv->reply_tx_stats.insuff_cf_poll++; + break; + case TX_STATUS_FAIL_PASSIVE_NO_RX: + priv->reply_tx_stats.fail_hw_drop++; + break; + case TX_STATUS_FAIL_NO_BEACON_ON_RADAR: + priv->reply_tx_stats.sta_color_mismatch++; + break; + default: + priv->reply_tx_stats.unknown++; + break; + } +} + +static void iwlagn_set_tx_status(struct iwl_priv *priv, + struct ieee80211_tx_info *info, + struct iwlagn_tx_resp *tx_resp) +{ + u16 status = le16_to_cpu(tx_resp->status.status); + + info->flags &= ~IEEE80211_TX_CTL_AMPDU; + + info->status.rates[0].count = tx_resp->failure_frame + 1; + info->flags |= iwl_tx_status_to_mac80211(status); + iwlagn_hwrate_to_tx_control(priv, le32_to_cpu(tx_resp->rate_n_flags), + info); + if (!iwl_is_tx_success(status)) + iwlagn_count_tx_err_status(priv, status); +} + +static void iwl_check_abort_status(struct iwl_priv *priv, + u8 frame_count, u32 status) +{ + if (frame_count == 1 && status == TX_STATUS_FAIL_RFKILL_FLUSH) { + IWL_ERR(priv, "Tx flush command to flush out all frames\n"); + if (!test_bit(STATUS_EXIT_PENDING, &priv->status)) + queue_work(priv->workqueue, &priv->tx_flush); + } +} + +int iwlagn_rx_reply_tx(struct iwl_priv *priv, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + u16 sequence = le16_to_cpu(pkt->hdr.sequence); + int txq_id = SEQ_TO_QUEUE(sequence); + int cmd_index __maybe_unused = SEQ_TO_INDEX(sequence); + struct iwlagn_tx_resp *tx_resp = (void *)pkt->data; + struct ieee80211_hdr *hdr; + u32 status = le16_to_cpu(tx_resp->status.status); + u16 ssn = iwlagn_get_scd_ssn(tx_resp); + int tid; + int sta_id; + int freed; + struct ieee80211_tx_info *info; + struct sk_buff_head skbs; + struct sk_buff *skb; + struct iwl_rxon_context *ctx; + bool is_agg = (txq_id >= IWLAGN_FIRST_AMPDU_QUEUE); + bool is_offchannel_skb; + + tid = (tx_resp->ra_tid & IWLAGN_TX_RES_TID_MSK) >> + IWLAGN_TX_RES_TID_POS; + sta_id = (tx_resp->ra_tid & IWLAGN_TX_RES_RA_MSK) >> + IWLAGN_TX_RES_RA_POS; + + spin_lock_bh(&priv->sta_lock); + + if (is_agg) { + WARN_ON_ONCE(sta_id >= IWLAGN_STATION_COUNT || + tid >= IWL_MAX_TID_COUNT); + if (txq_id != priv->tid_data[sta_id][tid].agg.txq_id) + IWL_ERR(priv, "txq_id mismatch: %d %d\n", txq_id, + priv->tid_data[sta_id][tid].agg.txq_id); + iwl_rx_reply_tx_agg(priv, tx_resp); + } + + __skb_queue_head_init(&skbs); + + is_offchannel_skb = false; + + if (tx_resp->frame_count == 1) { + u16 next_reclaimed = le16_to_cpu(tx_resp->seq_ctl); + next_reclaimed = IEEE80211_SEQ_TO_SN(next_reclaimed + 0x10); + + if (is_agg) { + /* If this is an aggregation queue, we can rely on the + * ssn since the wifi sequence number corresponds to + * the index in the TFD ring (%256). + * The seq_ctl is the sequence control of the packet + * to which this Tx response relates. But if there is a + * hole in the bitmap of the BA we received, this Tx + * response may allow to reclaim the hole and all the + * subsequent packets that were already acked. + * In that case, seq_ctl != ssn, and the next packet + * to be reclaimed will be ssn and not seq_ctl. + */ + next_reclaimed = ssn; + } + + if (tid != IWL_TID_NON_QOS) { + priv->tid_data[sta_id][tid].next_reclaimed = + next_reclaimed; + IWL_DEBUG_TX_REPLY(priv, "Next reclaimed packet:%d\n", + next_reclaimed); + } + + iwl_trans_reclaim(priv->trans, txq_id, ssn, &skbs); + + iwlagn_check_ratid_empty(priv, sta_id, tid); + freed = 0; + + /* process frames */ + skb_queue_walk(&skbs, skb) { + hdr = (struct ieee80211_hdr *)skb->data; + + if (!ieee80211_is_data_qos(hdr->frame_control)) + priv->last_seq_ctl = tx_resp->seq_ctl; + + info = IEEE80211_SKB_CB(skb); + ctx = info->driver_data[0]; + iwl_trans_free_tx_cmd(priv->trans, + info->driver_data[1]); + + memset(&info->status, 0, sizeof(info->status)); + + if (status == TX_STATUS_FAIL_PASSIVE_NO_RX && + ctx->vif && + ctx->vif->type == NL80211_IFTYPE_STATION) { + /* block and stop all queues */ + priv->passive_no_rx = true; + IWL_DEBUG_TX_QUEUES(priv, "stop all queues: " + "passive channel"); + ieee80211_stop_queues(priv->hw); + + IWL_DEBUG_TX_REPLY(priv, + "TXQ %d status %s (0x%08x) " + "rate_n_flags 0x%x retries %d\n", + txq_id, + iwl_get_tx_fail_reason(status), + status, + le32_to_cpu(tx_resp->rate_n_flags), + tx_resp->failure_frame); + + IWL_DEBUG_TX_REPLY(priv, + "FrameCnt = %d, idx=%d\n", + tx_resp->frame_count, cmd_index); + } + + /* check if BAR is needed */ + if (is_agg && !iwl_is_tx_success(status)) + info->flags |= IEEE80211_TX_STAT_AMPDU_NO_BACK; + iwlagn_set_tx_status(priv, IEEE80211_SKB_CB(skb), + tx_resp); + if (!is_agg) + iwlagn_non_agg_tx_status(priv, ctx, hdr->addr1); + + is_offchannel_skb = + (info->flags & IEEE80211_TX_CTL_TX_OFFCHAN); + freed++; + } + + if (tid != IWL_TID_NON_QOS) { + priv->tid_data[sta_id][tid].next_reclaimed = + next_reclaimed; + IWL_DEBUG_TX_REPLY(priv, "Next reclaimed packet:%d\n", + next_reclaimed); + } + + if (!is_agg && freed != 1) + IWL_ERR(priv, "Q: %d, freed %d\n", txq_id, freed); + + /* + * An offchannel frame can be send only on the AUX queue, where + * there is no aggregation (and reordering) so it only is single + * skb is expected to be processed. + */ + if (is_offchannel_skb && freed != 1) + IWL_ERR(priv, "OFFCHANNEL SKB freed %d\n", freed); + + IWL_DEBUG_TX_REPLY(priv, "TXQ %d status %s (0x%08x)\n", txq_id, + iwl_get_tx_fail_reason(status), status); + + IWL_DEBUG_TX_REPLY(priv, + "\t\t\t\tinitial_rate 0x%x retries %d, idx=%d ssn=%d seq_ctl=0x%x\n", + le32_to_cpu(tx_resp->rate_n_flags), + tx_resp->failure_frame, + SEQ_TO_INDEX(sequence), ssn, + le16_to_cpu(tx_resp->seq_ctl)); + } + + iwl_check_abort_status(priv, tx_resp->frame_count, status); + spin_unlock_bh(&priv->sta_lock); + + while (!skb_queue_empty(&skbs)) { + skb = __skb_dequeue(&skbs); + ieee80211_tx_status_ni(priv->hw, skb); + } + + if (is_offchannel_skb) + iwl_scan_offchannel_skb_status(priv); + + return 0; +} + +/** + * iwlagn_rx_reply_compressed_ba - Handler for REPLY_COMPRESSED_BA + * + * Handles block-acknowledge notification from device, which reports success + * of frames sent via aggregation. + */ +int iwlagn_rx_reply_compressed_ba(struct iwl_priv *priv, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_compressed_ba_resp *ba_resp = (void *)pkt->data; + struct iwl_ht_agg *agg; + struct sk_buff_head reclaimed_skbs; + struct ieee80211_tx_info *info; + struct ieee80211_hdr *hdr; + struct sk_buff *skb; + int sta_id; + int tid; + int freed; + + /* "flow" corresponds to Tx queue */ + u16 scd_flow = le16_to_cpu(ba_resp->scd_flow); + + /* "ssn" is start of block-ack Tx window, corresponds to index + * (in Tx queue's circular buffer) of first TFD/frame in window */ + u16 ba_resp_scd_ssn = le16_to_cpu(ba_resp->scd_ssn); + + if (scd_flow >= priv->cfg->base_params->num_of_queues) { + IWL_ERR(priv, + "BUG_ON scd_flow is bigger than number of queues\n"); + return 0; + } + + sta_id = ba_resp->sta_id; + tid = ba_resp->tid; + agg = &priv->tid_data[sta_id][tid].agg; + + spin_lock_bh(&priv->sta_lock); + + if (unlikely(!agg->wait_for_ba)) { + if (unlikely(ba_resp->bitmap)) + IWL_ERR(priv, "Received BA when not expected\n"); + spin_unlock_bh(&priv->sta_lock); + return 0; + } + + if (unlikely(scd_flow != agg->txq_id)) { + /* + * FIXME: this is a uCode bug which need to be addressed, + * log the information and return for now. + * Since it is can possibly happen very often and in order + * not to fill the syslog, don't use IWL_ERR or IWL_WARN + */ + IWL_DEBUG_TX_QUEUES(priv, + "Bad queue mapping txq_id=%d, agg_txq[sta:%d,tid:%d]=%d\n", + scd_flow, sta_id, tid, agg->txq_id); + spin_unlock_bh(&priv->sta_lock); + return 0; + } + + __skb_queue_head_init(&reclaimed_skbs); + + /* Release all TFDs before the SSN, i.e. all TFDs in front of + * block-ack window (we assume that they've been successfully + * transmitted ... if not, it's too late anyway). */ + iwl_trans_reclaim(priv->trans, scd_flow, ba_resp_scd_ssn, + &reclaimed_skbs); + + IWL_DEBUG_TX_REPLY(priv, "REPLY_COMPRESSED_BA [%d] Received from %pM, " + "sta_id = %d\n", + agg->wait_for_ba, + (u8 *) &ba_resp->sta_addr_lo32, + ba_resp->sta_id); + IWL_DEBUG_TX_REPLY(priv, "TID = %d, SeqCtl = %d, bitmap = 0x%llx, " + "scd_flow = %d, scd_ssn = %d sent:%d, acked:%d\n", + ba_resp->tid, le16_to_cpu(ba_resp->seq_ctl), + (unsigned long long)le64_to_cpu(ba_resp->bitmap), + scd_flow, ba_resp_scd_ssn, ba_resp->txed, + ba_resp->txed_2_done); + + /* Mark that the expected block-ack response arrived */ + agg->wait_for_ba = false; + + /* Sanity check values reported by uCode */ + if (ba_resp->txed_2_done > ba_resp->txed) { + IWL_DEBUG_TX_REPLY(priv, + "bogus sent(%d) and ack(%d) count\n", + ba_resp->txed, ba_resp->txed_2_done); + /* + * set txed_2_done = txed, + * so it won't impact rate scale + */ + ba_resp->txed = ba_resp->txed_2_done; + } + + priv->tid_data[sta_id][tid].next_reclaimed = ba_resp_scd_ssn; + + iwlagn_check_ratid_empty(priv, sta_id, tid); + freed = 0; + + skb_queue_walk(&reclaimed_skbs, skb) { + hdr = (struct ieee80211_hdr *)skb->data; + + if (ieee80211_is_data_qos(hdr->frame_control)) + freed++; + else + WARN_ON_ONCE(1); + + info = IEEE80211_SKB_CB(skb); + iwl_trans_free_tx_cmd(priv->trans, info->driver_data[1]); + + if (freed == 1) { + /* this is the first skb we deliver in this batch */ + /* put the rate scaling data there */ + info = IEEE80211_SKB_CB(skb); + memset(&info->status, 0, sizeof(info->status)); + info->flags |= IEEE80211_TX_STAT_ACK; + info->flags |= IEEE80211_TX_STAT_AMPDU; + info->status.ampdu_ack_len = ba_resp->txed_2_done; + info->status.ampdu_len = ba_resp->txed; + iwlagn_hwrate_to_tx_control(priv, agg->rate_n_flags, + info); + } + } + + spin_unlock_bh(&priv->sta_lock); + + while (!skb_queue_empty(&reclaimed_skbs)) { + skb = __skb_dequeue(&reclaimed_skbs); + ieee80211_tx_status_ni(priv->hw, skb); + } + + return 0; +} diff --git a/drivers/net/wireless/iwlwifi/dvm/ucode.c b/drivers/net/wireless/iwlwifi/dvm/ucode.c new file mode 100644 index 0000000..0a1cdc5 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/dvm/ucode.c @@ -0,0 +1,463 @@ +/****************************************************************************** + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#include +#include + +#include "iwl-io.h" +#include "iwl-agn-hw.h" +#include "iwl-trans.h" +#include "iwl-fh.h" +#include "iwl-op-mode.h" + +#include "dev.h" +#include "agn.h" +#include "calib.h" + +/****************************************************************************** + * + * uCode download functions + * + ******************************************************************************/ + +static inline const struct fw_img * +iwl_get_ucode_image(struct iwl_priv *priv, enum iwl_ucode_type ucode_type) +{ + if (ucode_type >= IWL_UCODE_TYPE_MAX) + return NULL; + + return &priv->fw->img[ucode_type]; +} + +/* + * Calibration + */ +static int iwl_set_Xtal_calib(struct iwl_priv *priv) +{ + struct iwl_calib_xtal_freq_cmd cmd; + __le16 *xtal_calib = priv->nvm_data->xtal_calib; + + iwl_set_calib_hdr(&cmd.hdr, IWL_PHY_CALIBRATE_CRYSTAL_FRQ_CMD); + cmd.cap_pin1 = le16_to_cpu(xtal_calib[0]); + cmd.cap_pin2 = le16_to_cpu(xtal_calib[1]); + return iwl_calib_set(priv, (void *)&cmd, sizeof(cmd)); +} + +static int iwl_set_temperature_offset_calib(struct iwl_priv *priv) +{ + struct iwl_calib_temperature_offset_cmd cmd; + + memset(&cmd, 0, sizeof(cmd)); + iwl_set_calib_hdr(&cmd.hdr, IWL_PHY_CALIBRATE_TEMP_OFFSET_CMD); + cmd.radio_sensor_offset = priv->nvm_data->raw_temperature; + if (!(cmd.radio_sensor_offset)) + cmd.radio_sensor_offset = DEFAULT_RADIO_SENSOR_OFFSET; + + IWL_DEBUG_CALIB(priv, "Radio sensor offset: %d\n", + le16_to_cpu(cmd.radio_sensor_offset)); + return iwl_calib_set(priv, (void *)&cmd, sizeof(cmd)); +} + +static int iwl_set_temperature_offset_calib_v2(struct iwl_priv *priv) +{ + struct iwl_calib_temperature_offset_v2_cmd cmd; + + memset(&cmd, 0, sizeof(cmd)); + iwl_set_calib_hdr(&cmd.hdr, IWL_PHY_CALIBRATE_TEMP_OFFSET_CMD); + cmd.radio_sensor_offset_high = priv->nvm_data->kelvin_temperature; + cmd.radio_sensor_offset_low = priv->nvm_data->raw_temperature; + if (!cmd.radio_sensor_offset_low) { + IWL_DEBUG_CALIB(priv, "no info in EEPROM, use default\n"); + cmd.radio_sensor_offset_low = DEFAULT_RADIO_SENSOR_OFFSET; + cmd.radio_sensor_offset_high = DEFAULT_RADIO_SENSOR_OFFSET; + } + cmd.burntVoltageRef = priv->nvm_data->calib_voltage; + + IWL_DEBUG_CALIB(priv, "Radio sensor offset high: %d\n", + le16_to_cpu(cmd.radio_sensor_offset_high)); + IWL_DEBUG_CALIB(priv, "Radio sensor offset low: %d\n", + le16_to_cpu(cmd.radio_sensor_offset_low)); + IWL_DEBUG_CALIB(priv, "Voltage Ref: %d\n", + le16_to_cpu(cmd.burntVoltageRef)); + + return iwl_calib_set(priv, (void *)&cmd, sizeof(cmd)); +} + +static int iwl_send_calib_cfg(struct iwl_priv *priv) +{ + struct iwl_calib_cfg_cmd calib_cfg_cmd; + struct iwl_host_cmd cmd = { + .id = CALIBRATION_CFG_CMD, + .len = { sizeof(struct iwl_calib_cfg_cmd), }, + .data = { &calib_cfg_cmd, }, + }; + + memset(&calib_cfg_cmd, 0, sizeof(calib_cfg_cmd)); + calib_cfg_cmd.ucd_calib_cfg.once.is_enable = IWL_CALIB_INIT_CFG_ALL; + calib_cfg_cmd.ucd_calib_cfg.once.start = IWL_CALIB_INIT_CFG_ALL; + calib_cfg_cmd.ucd_calib_cfg.once.send_res = IWL_CALIB_INIT_CFG_ALL; + calib_cfg_cmd.ucd_calib_cfg.flags = + IWL_CALIB_CFG_FLAG_SEND_COMPLETE_NTFY_MSK; + + return iwl_dvm_send_cmd(priv, &cmd); +} + +int iwl_init_alive_start(struct iwl_priv *priv) +{ + int ret; + + if (priv->cfg->bt_params && + priv->cfg->bt_params->advanced_bt_coexist) { + /* + * Tell uCode we are ready to perform calibration + * need to perform this before any calibration + * no need to close the envlope since we are going + * to load the runtime uCode later. + */ + ret = iwl_send_bt_env(priv, IWL_BT_COEX_ENV_OPEN, + BT_COEX_PRIO_TBL_EVT_INIT_CALIB2); + if (ret) + return ret; + + } + + ret = iwl_send_calib_cfg(priv); + if (ret) + return ret; + + /** + * temperature offset calibration is only needed for runtime ucode, + * so prepare the value now. + */ + if (priv->cfg->need_temp_offset_calib) { + if (priv->cfg->temp_offset_v2) + return iwl_set_temperature_offset_calib_v2(priv); + else + return iwl_set_temperature_offset_calib(priv); + } + + return 0; +} + +static int iwl_send_wimax_coex(struct iwl_priv *priv) +{ + struct iwl_wimax_coex_cmd coex_cmd; + + /* coexistence is disabled */ + memset(&coex_cmd, 0, sizeof(coex_cmd)); + + return iwl_dvm_send_cmd_pdu(priv, + COEX_PRIORITY_TABLE_CMD, CMD_SYNC, + sizeof(coex_cmd), &coex_cmd); +} + +static const u8 iwl_bt_prio_tbl[BT_COEX_PRIO_TBL_EVT_MAX] = { + ((BT_COEX_PRIO_TBL_PRIO_BYPASS << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (0 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + ((BT_COEX_PRIO_TBL_PRIO_BYPASS << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (1 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + ((BT_COEX_PRIO_TBL_PRIO_LOW << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (0 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + ((BT_COEX_PRIO_TBL_PRIO_LOW << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (1 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + ((BT_COEX_PRIO_TBL_PRIO_HIGH << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (0 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + ((BT_COEX_PRIO_TBL_PRIO_HIGH << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (1 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + ((BT_COEX_PRIO_TBL_PRIO_BYPASS << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (0 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + ((BT_COEX_PRIO_TBL_PRIO_COEX_OFF << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (0 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + ((BT_COEX_PRIO_TBL_PRIO_COEX_ON << IWL_BT_COEX_PRIO_TBL_PRIO_POS) | + (0 << IWL_BT_COEX_PRIO_TBL_SHARED_ANTENNA_POS)), + 0, 0, 0, 0, 0, 0, 0 +}; + +void iwl_send_prio_tbl(struct iwl_priv *priv) +{ + struct iwl_bt_coex_prio_table_cmd prio_tbl_cmd; + + memcpy(prio_tbl_cmd.prio_tbl, iwl_bt_prio_tbl, + sizeof(iwl_bt_prio_tbl)); + if (iwl_dvm_send_cmd_pdu(priv, + REPLY_BT_COEX_PRIO_TABLE, CMD_SYNC, + sizeof(prio_tbl_cmd), &prio_tbl_cmd)) + IWL_ERR(priv, "failed to send BT prio tbl command\n"); +} + +int iwl_send_bt_env(struct iwl_priv *priv, u8 action, u8 type) +{ + struct iwl_bt_coex_prot_env_cmd env_cmd; + int ret; + + env_cmd.action = action; + env_cmd.type = type; + ret = iwl_dvm_send_cmd_pdu(priv, + REPLY_BT_COEX_PROT_ENV, CMD_SYNC, + sizeof(env_cmd), &env_cmd); + if (ret) + IWL_ERR(priv, "failed to send BT env command\n"); + return ret; +} + +static const u8 iwlagn_default_queue_to_tx_fifo[] = { + IWL_TX_FIFO_VO, + IWL_TX_FIFO_VI, + IWL_TX_FIFO_BE, + IWL_TX_FIFO_BK, +}; + +static const u8 iwlagn_ipan_queue_to_tx_fifo[] = { + IWL_TX_FIFO_VO, + IWL_TX_FIFO_VI, + IWL_TX_FIFO_BE, + IWL_TX_FIFO_BK, + IWL_TX_FIFO_BK_IPAN, + IWL_TX_FIFO_BE_IPAN, + IWL_TX_FIFO_VI_IPAN, + IWL_TX_FIFO_VO_IPAN, + IWL_TX_FIFO_BE_IPAN, + IWL_TX_FIFO_UNUSED, + IWL_TX_FIFO_AUX, +}; + +static int iwl_alive_notify(struct iwl_priv *priv) +{ + const u8 *queue_to_txf; + u8 n_queues; + int ret; + int i; + + iwl_trans_fw_alive(priv->trans, 0); + + if (priv->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_PAN && + priv->nvm_data->sku_cap_ipan_enable) { + n_queues = ARRAY_SIZE(iwlagn_ipan_queue_to_tx_fifo); + queue_to_txf = iwlagn_ipan_queue_to_tx_fifo; + } else { + n_queues = ARRAY_SIZE(iwlagn_default_queue_to_tx_fifo); + queue_to_txf = iwlagn_default_queue_to_tx_fifo; + } + + for (i = 0; i < n_queues; i++) + if (queue_to_txf[i] != IWL_TX_FIFO_UNUSED) + iwl_trans_ac_txq_enable(priv->trans, i, + queue_to_txf[i]); + + priv->passive_no_rx = false; + priv->transport_queue_stop = 0; + + ret = iwl_send_wimax_coex(priv); + if (ret) + return ret; + + if (!priv->cfg->no_xtal_calib) { + ret = iwl_set_Xtal_calib(priv); + if (ret) + return ret; + } + + return iwl_send_calib_results(priv); +} + +struct iwl_alive_data { + bool valid; + u8 subtype; +}; + +static bool iwl_alive_fn(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_priv *priv = + container_of(notif_wait, struct iwl_priv, notif_wait); + struct iwl_alive_data *alive_data = data; + struct iwl_alive_resp *palive; + + palive = (void *)pkt->data; + + IWL_DEBUG_FW(priv, "Alive ucode status 0x%08X revision " + "0x%01X 0x%01X\n", + palive->is_valid, palive->ver_type, + palive->ver_subtype); + + priv->device_pointers.error_event_table = + le32_to_cpu(palive->error_event_table_ptr); + priv->device_pointers.log_event_table = + le32_to_cpu(palive->log_event_table_ptr); + + alive_data->subtype = palive->ver_subtype; + alive_data->valid = palive->is_valid == UCODE_VALID_OK; + + return true; +} + +#define UCODE_ALIVE_TIMEOUT HZ +#define UCODE_CALIB_TIMEOUT (2*HZ) + +int iwl_load_ucode_wait_alive(struct iwl_priv *priv, + enum iwl_ucode_type ucode_type) +{ + struct iwl_notification_wait alive_wait; + struct iwl_alive_data alive_data; + const struct fw_img *fw; + int ret; + enum iwl_ucode_type old_type; + static const u8 alive_cmd[] = { REPLY_ALIVE }; + + old_type = priv->cur_ucode; + priv->cur_ucode = ucode_type; + fw = iwl_get_ucode_image(priv, ucode_type); + + priv->ucode_loaded = false; + + if (!fw) + return -EINVAL; + + iwl_init_notification_wait(&priv->notif_wait, &alive_wait, + alive_cmd, ARRAY_SIZE(alive_cmd), + iwl_alive_fn, &alive_data); + + ret = iwl_trans_start_fw(priv->trans, fw, false); + if (ret) { + priv->cur_ucode = old_type; + iwl_remove_notification(&priv->notif_wait, &alive_wait); + return ret; + } + + /* + * Some things may run in the background now, but we + * just wait for the ALIVE notification here. + */ + ret = iwl_wait_notification(&priv->notif_wait, &alive_wait, + UCODE_ALIVE_TIMEOUT); + if (ret) { + priv->cur_ucode = old_type; + return ret; + } + + if (!alive_data.valid) { + IWL_ERR(priv, "Loaded ucode is not valid!\n"); + priv->cur_ucode = old_type; + return -EIO; + } + + priv->ucode_loaded = true; + + if (ucode_type != IWL_UCODE_WOWLAN) { + /* delay a bit to give rfkill time to run */ + msleep(5); + } + + ret = iwl_alive_notify(priv); + if (ret) { + IWL_WARN(priv, + "Could not complete ALIVE transition: %d\n", ret); + priv->cur_ucode = old_type; + return ret; + } + + return 0; +} + +static bool iwlagn_wait_calib(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_priv *priv = data; + struct iwl_calib_hdr *hdr; + int len; + + if (pkt->hdr.cmd != CALIBRATION_RES_NOTIFICATION) { + WARN_ON(pkt->hdr.cmd != CALIBRATION_COMPLETE_NOTIFICATION); + return true; + } + + hdr = (struct iwl_calib_hdr *)pkt->data; + len = le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; + + /* reduce the size by the length field itself */ + len -= sizeof(__le32); + + if (iwl_calib_set(priv, hdr, len)) + IWL_ERR(priv, "Failed to record calibration data %d\n", + hdr->op_code); + + return false; +} + +int iwl_run_init_ucode(struct iwl_priv *priv) +{ + struct iwl_notification_wait calib_wait; + static const u8 calib_complete[] = { + CALIBRATION_RES_NOTIFICATION, + CALIBRATION_COMPLETE_NOTIFICATION + }; + int ret; + + lockdep_assert_held(&priv->mutex); + + /* No init ucode required? Curious, but maybe ok */ + if (!priv->fw->img[IWL_UCODE_INIT].sec[0].len) + return 0; + + if (priv->init_ucode_run) + return 0; + + iwl_init_notification_wait(&priv->notif_wait, &calib_wait, + calib_complete, ARRAY_SIZE(calib_complete), + iwlagn_wait_calib, priv); + + /* Will also start the device */ + ret = iwl_load_ucode_wait_alive(priv, IWL_UCODE_INIT); + if (ret) + goto error; + + ret = iwl_init_alive_start(priv); + if (ret) + goto error; + + /* + * Some things may run in the background now, but we + * just wait for the calibration complete notification. + */ + ret = iwl_wait_notification(&priv->notif_wait, &calib_wait, + UCODE_CALIB_TIMEOUT); + if (!ret) + priv->init_ucode_run = true; + + goto out; + + error: + iwl_remove_notification(&priv->notif_wait, &calib_wait); + out: + /* Whatever happened, stop the device */ + iwl_trans_stop_device(priv->trans); + priv->ucode_loaded = false; + + return ret; +} diff --git a/drivers/net/wireless/iwlwifi/iwl-1000.c b/drivers/net/wireless/iwlwifi/iwl-1000.c new file mode 100644 index 0000000..c080ae3 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-1000.c @@ -0,0 +1,140 @@ +/****************************************************************************** + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#include +#include +#include "iwl-config.h" +#include "iwl-csr.h" +#include "iwl-agn-hw.h" + +/* Highest firmware API version supported */ +#define IWL1000_UCODE_API_MAX 5 +#define IWL100_UCODE_API_MAX 5 + +/* Oldest version we won't warn about */ +#define IWL1000_UCODE_API_OK 5 +#define IWL100_UCODE_API_OK 5 + +/* Lowest firmware API version supported */ +#define IWL1000_UCODE_API_MIN 1 +#define IWL100_UCODE_API_MIN 5 + +/* EEPROM version */ +#define EEPROM_1000_TX_POWER_VERSION (4) +#define EEPROM_1000_EEPROM_VERSION (0x15C) + +#define IWL1000_FW_PRE "iwlwifi-1000-" +#define IWL1000_MODULE_FIRMWARE(api) IWL1000_FW_PRE __stringify(api) ".ucode" + +#define IWL100_FW_PRE "iwlwifi-100-" +#define IWL100_MODULE_FIRMWARE(api) IWL100_FW_PRE __stringify(api) ".ucode" + + +static const struct iwl_base_params iwl1000_base_params = { + .num_of_queues = IWLAGN_NUM_QUEUES, + .eeprom_size = OTP_LOW_IMAGE_SIZE, + .pll_cfg_val = CSR50_ANA_PLL_CFG_VAL, + .max_ll_items = OTP_MAX_LL_ITEMS_1000, + .shadow_ram_support = false, + .led_compensation = 51, + .support_ct_kill_exit = true, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_EXT_LONG_THRESHOLD_DEF, + .chain_noise_scale = 1000, + .wd_timeout = IWL_WATCHDOG_DISABLED, + .max_event_log_size = 128, +}; + +static const struct iwl_ht_params iwl1000_ht_params = { + .ht_greenfield_support = true, + .use_rts_for_aggregation = true, /* use rts/cts protection */ + .ht40_bands = BIT(IEEE80211_BAND_2GHZ), +}; + +static const struct iwl_eeprom_params iwl1000_eeprom_params = { + .regulatory_bands = { + EEPROM_REG_BAND_1_CHANNELS, + EEPROM_REG_BAND_2_CHANNELS, + EEPROM_REG_BAND_3_CHANNELS, + EEPROM_REG_BAND_4_CHANNELS, + EEPROM_REG_BAND_5_CHANNELS, + EEPROM_REG_BAND_24_HT40_CHANNELS, + EEPROM_REGULATORY_BAND_NO_HT40, + } +}; + +#define IWL_DEVICE_1000 \ + .fw_name_pre = IWL1000_FW_PRE, \ + .ucode_api_max = IWL1000_UCODE_API_MAX, \ + .ucode_api_ok = IWL1000_UCODE_API_OK, \ + .ucode_api_min = IWL1000_UCODE_API_MIN, \ + .device_family = IWL_DEVICE_FAMILY_1000, \ + .max_inst_size = IWLAGN_RTC_INST_SIZE, \ + .max_data_size = IWLAGN_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_1000_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_1000_TX_POWER_VERSION, \ + .base_params = &iwl1000_base_params, \ + .eeprom_params = &iwl1000_eeprom_params, \ + .led_mode = IWL_LED_BLINK + +const struct iwl_cfg iwl1000_bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 1000 BGN", + IWL_DEVICE_1000, + .ht_params = &iwl1000_ht_params, +}; + +const struct iwl_cfg iwl1000_bg_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 1000 BG", + IWL_DEVICE_1000, +}; + +#define IWL_DEVICE_100 \ + .fw_name_pre = IWL100_FW_PRE, \ + .ucode_api_max = IWL100_UCODE_API_MAX, \ + .ucode_api_ok = IWL100_UCODE_API_OK, \ + .ucode_api_min = IWL100_UCODE_API_MIN, \ + .device_family = IWL_DEVICE_FAMILY_100, \ + .max_inst_size = IWLAGN_RTC_INST_SIZE, \ + .max_data_size = IWLAGN_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_1000_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_1000_TX_POWER_VERSION, \ + .base_params = &iwl1000_base_params, \ + .eeprom_params = &iwl1000_eeprom_params, \ + .led_mode = IWL_LED_RF_STATE, \ + .rx_with_siso_diversity = true + +const struct iwl_cfg iwl100_bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 100 BGN", + IWL_DEVICE_100, + .ht_params = &iwl1000_ht_params, +}; + +const struct iwl_cfg iwl100_bg_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 100 BG", + IWL_DEVICE_100, +}; + +MODULE_FIRMWARE(IWL1000_MODULE_FIRMWARE(IWL1000_UCODE_API_OK)); +MODULE_FIRMWARE(IWL100_MODULE_FIRMWARE(IWL100_UCODE_API_OK)); diff --git a/drivers/net/wireless/iwlwifi/iwl-2000.c b/drivers/net/wireless/iwlwifi/iwl-2000.c new file mode 100644 index 0000000..a6ddd2f --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-2000.c @@ -0,0 +1,242 @@ +/****************************************************************************** + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#include +#include +#include "iwl-config.h" +#include "iwl-agn-hw.h" +#include "dvm/commands.h" /* needed for BT for now */ + +/* Highest firmware API version supported */ +#define IWL2030_UCODE_API_MAX 6 +#define IWL2000_UCODE_API_MAX 6 +#define IWL105_UCODE_API_MAX 6 +#define IWL135_UCODE_API_MAX 6 + +/* Oldest version we won't warn about */ +#define IWL2030_UCODE_API_OK 6 +#define IWL2000_UCODE_API_OK 6 +#define IWL105_UCODE_API_OK 6 +#define IWL135_UCODE_API_OK 6 + +/* Lowest firmware API version supported */ +#define IWL2030_UCODE_API_MIN 5 +#define IWL2000_UCODE_API_MIN 5 +#define IWL105_UCODE_API_MIN 5 +#define IWL135_UCODE_API_MIN 5 + +/* EEPROM version */ +#define EEPROM_2000_TX_POWER_VERSION (6) +#define EEPROM_2000_EEPROM_VERSION (0x805) + + +#define IWL2030_FW_PRE "iwlwifi-2030-" +#define IWL2030_MODULE_FIRMWARE(api) IWL2030_FW_PRE __stringify(api) ".ucode" + +#define IWL2000_FW_PRE "iwlwifi-2000-" +#define IWL2000_MODULE_FIRMWARE(api) IWL2000_FW_PRE __stringify(api) ".ucode" + +#define IWL105_FW_PRE "iwlwifi-105-" +#define IWL105_MODULE_FIRMWARE(api) IWL105_FW_PRE __stringify(api) ".ucode" + +#define IWL135_FW_PRE "iwlwifi-135-" +#define IWL135_MODULE_FIRMWARE(api) IWL135_FW_PRE __stringify(api) ".ucode" + +static const struct iwl_base_params iwl2000_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE, + .num_of_queues = IWLAGN_NUM_QUEUES, + .pll_cfg_val = 0, + .max_ll_items = OTP_MAX_LL_ITEMS_2x00, + .shadow_ram_support = true, + .led_compensation = 51, + .adv_thermal_throttle = true, + .support_ct_kill_exit = true, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF, + .chain_noise_scale = 1000, + .wd_timeout = IWL_DEF_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = false, /* TODO: fix bugs using this feature */ + .hd_v2 = true, +}; + + +static const struct iwl_base_params iwl2030_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE, + .num_of_queues = IWLAGN_NUM_QUEUES, + .pll_cfg_val = 0, + .max_ll_items = OTP_MAX_LL_ITEMS_2x00, + .shadow_ram_support = true, + .led_compensation = 57, + .adv_thermal_throttle = true, + .support_ct_kill_exit = true, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF, + .chain_noise_scale = 1000, + .wd_timeout = IWL_LONG_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = false, /* TODO: fix bugs using this feature */ + .hd_v2 = true, +}; + +static const struct iwl_ht_params iwl2000_ht_params = { + .ht_greenfield_support = true, + .use_rts_for_aggregation = true, /* use rts/cts protection */ + .ht40_bands = BIT(IEEE80211_BAND_2GHZ), +}; + +static const struct iwl_bt_params iwl2030_bt_params = { + /* Due to bluetooth, we transmit 2.4 GHz probes only on antenna A */ + .advanced_bt_coexist = true, + .agg_time_limit = BT_AGG_THRESHOLD_DEF, + .bt_init_traffic_load = IWL_BT_COEX_TRAFFIC_LOAD_NONE, + .bt_prio_boost = IWLAGN_BT_PRIO_BOOST_DEFAULT32, + .bt_sco_disable = true, + .bt_session_2 = true, +}; + +static const struct iwl_eeprom_params iwl20x0_eeprom_params = { + .regulatory_bands = { + EEPROM_REG_BAND_1_CHANNELS, + EEPROM_REG_BAND_2_CHANNELS, + EEPROM_REG_BAND_3_CHANNELS, + EEPROM_REG_BAND_4_CHANNELS, + EEPROM_REG_BAND_5_CHANNELS, + EEPROM_6000_REG_BAND_24_HT40_CHANNELS, + EEPROM_REGULATORY_BAND_NO_HT40, + }, + .enhanced_txpower = true, +}; + +#define IWL_DEVICE_2000 \ + .fw_name_pre = IWL2000_FW_PRE, \ + .ucode_api_max = IWL2000_UCODE_API_MAX, \ + .ucode_api_ok = IWL2000_UCODE_API_OK, \ + .ucode_api_min = IWL2000_UCODE_API_MIN, \ + .device_family = IWL_DEVICE_FAMILY_2000, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_2000_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_2000_TX_POWER_VERSION, \ + .base_params = &iwl2000_base_params, \ + .eeprom_params = &iwl20x0_eeprom_params, \ + .need_temp_offset_calib = true, \ + .temp_offset_v2 = true, \ + .led_mode = IWL_LED_RF_STATE + +const struct iwl_cfg iwl2000_2bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 2200 BGN", + IWL_DEVICE_2000, + .ht_params = &iwl2000_ht_params, +}; + +const struct iwl_cfg iwl2000_2bgn_d_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 2200D BGN", + IWL_DEVICE_2000, + .ht_params = &iwl2000_ht_params, +}; + +#define IWL_DEVICE_2030 \ + .fw_name_pre = IWL2030_FW_PRE, \ + .ucode_api_max = IWL2030_UCODE_API_MAX, \ + .ucode_api_ok = IWL2030_UCODE_API_OK, \ + .ucode_api_min = IWL2030_UCODE_API_MIN, \ + .device_family = IWL_DEVICE_FAMILY_2030, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_2000_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_2000_TX_POWER_VERSION, \ + .base_params = &iwl2030_base_params, \ + .bt_params = &iwl2030_bt_params, \ + .eeprom_params = &iwl20x0_eeprom_params, \ + .need_temp_offset_calib = true, \ + .temp_offset_v2 = true, \ + .led_mode = IWL_LED_RF_STATE, \ + .adv_pm = true + +const struct iwl_cfg iwl2030_2bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 2230 BGN", + IWL_DEVICE_2030, + .ht_params = &iwl2000_ht_params, +}; + +#define IWL_DEVICE_105 \ + .fw_name_pre = IWL105_FW_PRE, \ + .ucode_api_max = IWL105_UCODE_API_MAX, \ + .ucode_api_ok = IWL105_UCODE_API_OK, \ + .ucode_api_min = IWL105_UCODE_API_MIN, \ + .device_family = IWL_DEVICE_FAMILY_105, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_2000_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_2000_TX_POWER_VERSION, \ + .base_params = &iwl2000_base_params, \ + .eeprom_params = &iwl20x0_eeprom_params, \ + .need_temp_offset_calib = true, \ + .temp_offset_v2 = true, \ + .led_mode = IWL_LED_RF_STATE, \ + .adv_pm = true, \ + .rx_with_siso_diversity = true + +const struct iwl_cfg iwl105_bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 105 BGN", + IWL_DEVICE_105, + .ht_params = &iwl2000_ht_params, +}; + +const struct iwl_cfg iwl105_bgn_d_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 105D BGN", + IWL_DEVICE_105, + .ht_params = &iwl2000_ht_params, +}; + +#define IWL_DEVICE_135 \ + .fw_name_pre = IWL135_FW_PRE, \ + .ucode_api_max = IWL135_UCODE_API_MAX, \ + .ucode_api_ok = IWL135_UCODE_API_OK, \ + .ucode_api_min = IWL135_UCODE_API_MIN, \ + .device_family = IWL_DEVICE_FAMILY_135, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_2000_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_2000_TX_POWER_VERSION, \ + .base_params = &iwl2030_base_params, \ + .bt_params = &iwl2030_bt_params, \ + .eeprom_params = &iwl20x0_eeprom_params, \ + .need_temp_offset_calib = true, \ + .temp_offset_v2 = true, \ + .led_mode = IWL_LED_RF_STATE, \ + .adv_pm = true, \ + .rx_with_siso_diversity = true + +const struct iwl_cfg iwl135_bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 135 BGN", + IWL_DEVICE_135, + .ht_params = &iwl2000_ht_params, +}; + +MODULE_FIRMWARE(IWL2000_MODULE_FIRMWARE(IWL2000_UCODE_API_OK)); +MODULE_FIRMWARE(IWL2030_MODULE_FIRMWARE(IWL2030_UCODE_API_OK)); +MODULE_FIRMWARE(IWL105_MODULE_FIRMWARE(IWL105_UCODE_API_OK)); +MODULE_FIRMWARE(IWL135_MODULE_FIRMWARE(IWL135_UCODE_API_OK)); diff --git a/drivers/net/wireless/iwlwifi/iwl-5000.c b/drivers/net/wireless/iwlwifi/iwl-5000.c new file mode 100644 index 0000000..403f3f2 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-5000.c @@ -0,0 +1,179 @@ +/****************************************************************************** + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#include +#include +#include "iwl-config.h" +#include "iwl-agn-hw.h" +#include "iwl-csr.h" + +/* Highest firmware API version supported */ +#define IWL5000_UCODE_API_MAX 5 +#define IWL5150_UCODE_API_MAX 2 + +/* Oldest version we won't warn about */ +#define IWL5000_UCODE_API_OK 5 +#define IWL5150_UCODE_API_OK 2 + +/* Lowest firmware API version supported */ +#define IWL5000_UCODE_API_MIN 1 +#define IWL5150_UCODE_API_MIN 1 + +/* EEPROM versions */ +#define EEPROM_5000_TX_POWER_VERSION (4) +#define EEPROM_5000_EEPROM_VERSION (0x11A) +#define EEPROM_5050_TX_POWER_VERSION (4) +#define EEPROM_5050_EEPROM_VERSION (0x21E) + +#define IWL5000_FW_PRE "iwlwifi-5000-" +#define IWL5000_MODULE_FIRMWARE(api) IWL5000_FW_PRE __stringify(api) ".ucode" + +#define IWL5150_FW_PRE "iwlwifi-5150-" +#define IWL5150_MODULE_FIRMWARE(api) IWL5150_FW_PRE __stringify(api) ".ucode" + +static const struct iwl_base_params iwl5000_base_params = { + .eeprom_size = IWLAGN_EEPROM_IMG_SIZE, + .num_of_queues = IWLAGN_NUM_QUEUES, + .pll_cfg_val = CSR50_ANA_PLL_CFG_VAL, + .led_compensation = 51, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_LONG_THRESHOLD_DEF, + .chain_noise_scale = 1000, + .wd_timeout = IWL_WATCHDOG_DISABLED, + .max_event_log_size = 512, + .no_idle_support = true, +}; + +static const struct iwl_ht_params iwl5000_ht_params = { + .ht_greenfield_support = true, + .ht40_bands = BIT(IEEE80211_BAND_2GHZ) | BIT(IEEE80211_BAND_5GHZ), +}; + +static const struct iwl_eeprom_params iwl5000_eeprom_params = { + .regulatory_bands = { + EEPROM_REG_BAND_1_CHANNELS, + EEPROM_REG_BAND_2_CHANNELS, + EEPROM_REG_BAND_3_CHANNELS, + EEPROM_REG_BAND_4_CHANNELS, + EEPROM_REG_BAND_5_CHANNELS, + EEPROM_REG_BAND_24_HT40_CHANNELS, + EEPROM_REG_BAND_52_HT40_CHANNELS + }, +}; + +#define IWL_DEVICE_5000 \ + .fw_name_pre = IWL5000_FW_PRE, \ + .ucode_api_max = IWL5000_UCODE_API_MAX, \ + .ucode_api_ok = IWL5000_UCODE_API_OK, \ + .ucode_api_min = IWL5000_UCODE_API_MIN, \ + .device_family = IWL_DEVICE_FAMILY_5000, \ + .max_inst_size = IWLAGN_RTC_INST_SIZE, \ + .max_data_size = IWLAGN_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_5000_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_5000_TX_POWER_VERSION, \ + .base_params = &iwl5000_base_params, \ + .eeprom_params = &iwl5000_eeprom_params, \ + .led_mode = IWL_LED_BLINK + +const struct iwl_cfg iwl5300_agn_cfg = { + .name = "Intel(R) Ultimate N WiFi Link 5300 AGN", + IWL_DEVICE_5000, + /* at least EEPROM 0x11A has wrong info */ + .valid_tx_ant = ANT_ABC, /* .cfg overwrite */ + .valid_rx_ant = ANT_ABC, /* .cfg overwrite */ + .ht_params = &iwl5000_ht_params, +}; + +const struct iwl_cfg iwl5100_bgn_cfg = { + .name = "Intel(R) WiFi Link 5100 BGN", + IWL_DEVICE_5000, + .valid_tx_ant = ANT_B, /* .cfg overwrite */ + .valid_rx_ant = ANT_AB, /* .cfg overwrite */ + .ht_params = &iwl5000_ht_params, +}; + +const struct iwl_cfg iwl5100_abg_cfg = { + .name = "Intel(R) WiFi Link 5100 ABG", + IWL_DEVICE_5000, + .valid_tx_ant = ANT_B, /* .cfg overwrite */ + .valid_rx_ant = ANT_AB, /* .cfg overwrite */ +}; + +const struct iwl_cfg iwl5100_agn_cfg = { + .name = "Intel(R) WiFi Link 5100 AGN", + IWL_DEVICE_5000, + .valid_tx_ant = ANT_B, /* .cfg overwrite */ + .valid_rx_ant = ANT_AB, /* .cfg overwrite */ + .ht_params = &iwl5000_ht_params, +}; + +const struct iwl_cfg iwl5350_agn_cfg = { + .name = "Intel(R) WiMAX/WiFi Link 5350 AGN", + .fw_name_pre = IWL5000_FW_PRE, + .ucode_api_max = IWL5000_UCODE_API_MAX, + .ucode_api_ok = IWL5000_UCODE_API_OK, + .ucode_api_min = IWL5000_UCODE_API_MIN, + .device_family = IWL_DEVICE_FAMILY_5000, + .max_inst_size = IWLAGN_RTC_INST_SIZE, + .max_data_size = IWLAGN_RTC_DATA_SIZE, + .nvm_ver = EEPROM_5050_EEPROM_VERSION, + .nvm_calib_ver = EEPROM_5050_TX_POWER_VERSION, + .base_params = &iwl5000_base_params, + .eeprom_params = &iwl5000_eeprom_params, + .ht_params = &iwl5000_ht_params, + .led_mode = IWL_LED_BLINK, + .internal_wimax_coex = true, +}; + +#define IWL_DEVICE_5150 \ + .fw_name_pre = IWL5150_FW_PRE, \ + .ucode_api_max = IWL5150_UCODE_API_MAX, \ + .ucode_api_ok = IWL5150_UCODE_API_OK, \ + .ucode_api_min = IWL5150_UCODE_API_MIN, \ + .device_family = IWL_DEVICE_FAMILY_5150, \ + .max_inst_size = IWLAGN_RTC_INST_SIZE, \ + .max_data_size = IWLAGN_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_5050_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_5050_TX_POWER_VERSION, \ + .base_params = &iwl5000_base_params, \ + .eeprom_params = &iwl5000_eeprom_params, \ + .no_xtal_calib = true, \ + .led_mode = IWL_LED_BLINK, \ + .internal_wimax_coex = true + +const struct iwl_cfg iwl5150_agn_cfg = { + .name = "Intel(R) WiMAX/WiFi Link 5150 AGN", + IWL_DEVICE_5150, + .ht_params = &iwl5000_ht_params, + +}; + +const struct iwl_cfg iwl5150_abg_cfg = { + .name = "Intel(R) WiMAX/WiFi Link 5150 ABG", + IWL_DEVICE_5150, +}; + +MODULE_FIRMWARE(IWL5000_MODULE_FIRMWARE(IWL5000_UCODE_API_OK)); +MODULE_FIRMWARE(IWL5150_MODULE_FIRMWARE(IWL5150_UCODE_API_OK)); diff --git a/drivers/net/wireless/iwlwifi/iwl-6000.c b/drivers/net/wireless/iwlwifi/iwl-6000.c new file mode 100644 index 0000000..5282088 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-6000.c @@ -0,0 +1,408 @@ +/****************************************************************************** + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#include +#include +#include "iwl-config.h" +#include "iwl-agn-hw.h" +#include "dvm/commands.h" /* needed for BT for now */ + +/* Highest firmware API version supported */ +#define IWL6000_UCODE_API_MAX 6 +#define IWL6050_UCODE_API_MAX 5 +#define IWL6000G2_UCODE_API_MAX 6 +#define IWL6035_UCODE_API_MAX 6 + +/* Oldest version we won't warn about */ +#define IWL6000_UCODE_API_OK 4 +#define IWL6000G2_UCODE_API_OK 5 +#define IWL6050_UCODE_API_OK 5 +#define IWL6000G2B_UCODE_API_OK 6 +#define IWL6035_UCODE_API_OK 6 + +/* Lowest firmware API version supported */ +#define IWL6000_UCODE_API_MIN 4 +#define IWL6050_UCODE_API_MIN 4 +#define IWL6000G2_UCODE_API_MIN 5 +#define IWL6035_UCODE_API_MIN 6 + +/* EEPROM versions */ +#define EEPROM_6000_TX_POWER_VERSION (4) +#define EEPROM_6000_EEPROM_VERSION (0x423) +#define EEPROM_6050_TX_POWER_VERSION (4) +#define EEPROM_6050_EEPROM_VERSION (0x532) +#define EEPROM_6150_TX_POWER_VERSION (6) +#define EEPROM_6150_EEPROM_VERSION (0x553) +#define EEPROM_6005_TX_POWER_VERSION (6) +#define EEPROM_6005_EEPROM_VERSION (0x709) +#define EEPROM_6030_TX_POWER_VERSION (6) +#define EEPROM_6030_EEPROM_VERSION (0x709) +#define EEPROM_6035_TX_POWER_VERSION (6) +#define EEPROM_6035_EEPROM_VERSION (0x753) + +#define IWL6000_FW_PRE "iwlwifi-6000-" +#define IWL6000_MODULE_FIRMWARE(api) IWL6000_FW_PRE __stringify(api) ".ucode" + +#define IWL6050_FW_PRE "iwlwifi-6050-" +#define IWL6050_MODULE_FIRMWARE(api) IWL6050_FW_PRE __stringify(api) ".ucode" + +#define IWL6005_FW_PRE "iwlwifi-6000g2a-" +#define IWL6005_MODULE_FIRMWARE(api) IWL6005_FW_PRE __stringify(api) ".ucode" + +#define IWL6030_FW_PRE "iwlwifi-6000g2b-" +#define IWL6030_MODULE_FIRMWARE(api) IWL6030_FW_PRE __stringify(api) ".ucode" + +static const struct iwl_base_params iwl6000_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE, + .num_of_queues = IWLAGN_NUM_QUEUES, + .pll_cfg_val = 0, + .max_ll_items = OTP_MAX_LL_ITEMS_6x00, + .shadow_ram_support = true, + .led_compensation = 51, + .adv_thermal_throttle = true, + .support_ct_kill_exit = true, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF, + .chain_noise_scale = 1000, + .wd_timeout = IWL_DEF_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = false, /* TODO: fix bugs using this feature */ +}; + +static const struct iwl_base_params iwl6050_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE, + .num_of_queues = IWLAGN_NUM_QUEUES, + .pll_cfg_val = 0, + .max_ll_items = OTP_MAX_LL_ITEMS_6x50, + .shadow_ram_support = true, + .led_compensation = 51, + .adv_thermal_throttle = true, + .support_ct_kill_exit = true, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF, + .chain_noise_scale = 1500, + .wd_timeout = IWL_DEF_WD_TIMEOUT, + .max_event_log_size = 1024, + .shadow_reg_enable = false, /* TODO: fix bugs using this feature */ +}; + +static const struct iwl_base_params iwl6000_g2_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE, + .num_of_queues = IWLAGN_NUM_QUEUES, + .pll_cfg_val = 0, + .max_ll_items = OTP_MAX_LL_ITEMS_6x00, + .shadow_ram_support = true, + .led_compensation = 57, + .adv_thermal_throttle = true, + .support_ct_kill_exit = true, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF, + .chain_noise_scale = 1000, + .wd_timeout = IWL_LONG_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = false, /* TODO: fix bugs using this feature */ +}; + +static const struct iwl_ht_params iwl6000_ht_params = { + .ht_greenfield_support = true, + .use_rts_for_aggregation = true, /* use rts/cts protection */ + .ht40_bands = BIT(IEEE80211_BAND_2GHZ) | BIT(IEEE80211_BAND_5GHZ), +}; + +static const struct iwl_bt_params iwl6000_bt_params = { + /* Due to bluetooth, we transmit 2.4 GHz probes only on antenna A */ + .advanced_bt_coexist = true, + .agg_time_limit = BT_AGG_THRESHOLD_DEF, + .bt_init_traffic_load = IWL_BT_COEX_TRAFFIC_LOAD_NONE, + .bt_prio_boost = IWLAGN_BT_PRIO_BOOST_DEFAULT, + .bt_sco_disable = true, +}; + +static const struct iwl_eeprom_params iwl6000_eeprom_params = { + .regulatory_bands = { + EEPROM_REG_BAND_1_CHANNELS, + EEPROM_REG_BAND_2_CHANNELS, + EEPROM_REG_BAND_3_CHANNELS, + EEPROM_REG_BAND_4_CHANNELS, + EEPROM_REG_BAND_5_CHANNELS, + EEPROM_6000_REG_BAND_24_HT40_CHANNELS, + EEPROM_REG_BAND_52_HT40_CHANNELS + }, + .enhanced_txpower = true, +}; + +#define IWL_DEVICE_6005 \ + .fw_name_pre = IWL6005_FW_PRE, \ + .ucode_api_max = IWL6000G2_UCODE_API_MAX, \ + .ucode_api_ok = IWL6000G2_UCODE_API_OK, \ + .ucode_api_min = IWL6000G2_UCODE_API_MIN, \ + .device_family = IWL_DEVICE_FAMILY_6005, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_6005_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_6005_TX_POWER_VERSION, \ + .base_params = &iwl6000_g2_base_params, \ + .eeprom_params = &iwl6000_eeprom_params, \ + .need_temp_offset_calib = true, \ + .led_mode = IWL_LED_RF_STATE + +const struct iwl_cfg iwl6005_2agn_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6205 AGN", + IWL_DEVICE_6005, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6005_2abg_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6205 ABG", + IWL_DEVICE_6005, +}; + +const struct iwl_cfg iwl6005_2bg_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6205 BG", + IWL_DEVICE_6005, +}; + +const struct iwl_cfg iwl6005_2agn_sff_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6205S AGN", + IWL_DEVICE_6005, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6005_2agn_d_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6205D AGN", + IWL_DEVICE_6005, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6005_2agn_mow1_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6206 AGN", + IWL_DEVICE_6005, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6005_2agn_mow2_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6207 AGN", + IWL_DEVICE_6005, + .ht_params = &iwl6000_ht_params, +}; + +#define IWL_DEVICE_6030 \ + .fw_name_pre = IWL6030_FW_PRE, \ + .ucode_api_max = IWL6000G2_UCODE_API_MAX, \ + .ucode_api_ok = IWL6000G2B_UCODE_API_OK, \ + .ucode_api_min = IWL6000G2_UCODE_API_MIN, \ + .device_family = IWL_DEVICE_FAMILY_6030, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_6030_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_6030_TX_POWER_VERSION, \ + .base_params = &iwl6000_g2_base_params, \ + .bt_params = &iwl6000_bt_params, \ + .eeprom_params = &iwl6000_eeprom_params, \ + .need_temp_offset_calib = true, \ + .led_mode = IWL_LED_RF_STATE, \ + .adv_pm = true \ + +const struct iwl_cfg iwl6030_2agn_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6230 AGN", + IWL_DEVICE_6030, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6030_2abg_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6230 ABG", + IWL_DEVICE_6030, +}; + +const struct iwl_cfg iwl6030_2bgn_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6230 BGN", + IWL_DEVICE_6030, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6030_2bg_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6230 BG", + IWL_DEVICE_6030, +}; + +#define IWL_DEVICE_6035 \ + .fw_name_pre = IWL6030_FW_PRE, \ + .ucode_api_max = IWL6035_UCODE_API_MAX, \ + .ucode_api_ok = IWL6035_UCODE_API_OK, \ + .ucode_api_min = IWL6035_UCODE_API_MIN, \ + .device_family = IWL_DEVICE_FAMILY_6030, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_6030_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_6030_TX_POWER_VERSION, \ + .base_params = &iwl6000_g2_base_params, \ + .bt_params = &iwl6000_bt_params, \ + .eeprom_params = &iwl6000_eeprom_params, \ + .need_temp_offset_calib = true, \ + .led_mode = IWL_LED_RF_STATE, \ + .adv_pm = true + +const struct iwl_cfg iwl6035_2agn_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6235 AGN", + IWL_DEVICE_6035, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6035_2agn_sff_cfg = { + .name = "Intel(R) Centrino(R) Ultimate-N 6235 AGN", + IWL_DEVICE_6035, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl1030_bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 1030 BGN", + IWL_DEVICE_6030, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl1030_bg_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 1030 BG", + IWL_DEVICE_6030, +}; + +const struct iwl_cfg iwl130_bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 130 BGN", + IWL_DEVICE_6030, + .ht_params = &iwl6000_ht_params, + .rx_with_siso_diversity = true, +}; + +const struct iwl_cfg iwl130_bg_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N 130 BG", + IWL_DEVICE_6030, + .rx_with_siso_diversity = true, +}; + +/* + * "i": Internal configuration, use internal Power Amplifier + */ +#define IWL_DEVICE_6000i \ + .fw_name_pre = IWL6000_FW_PRE, \ + .ucode_api_max = IWL6000_UCODE_API_MAX, \ + .ucode_api_ok = IWL6000_UCODE_API_OK, \ + .ucode_api_min = IWL6000_UCODE_API_MIN, \ + .device_family = IWL_DEVICE_FAMILY_6000i, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .valid_tx_ant = ANT_BC, /* .cfg overwrite */ \ + .valid_rx_ant = ANT_BC, /* .cfg overwrite */ \ + .nvm_ver = EEPROM_6000_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_6000_TX_POWER_VERSION, \ + .base_params = &iwl6000_base_params, \ + .eeprom_params = &iwl6000_eeprom_params, \ + .led_mode = IWL_LED_BLINK + +const struct iwl_cfg iwl6000i_2agn_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6200 AGN", + IWL_DEVICE_6000i, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6000i_2abg_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6200 ABG", + IWL_DEVICE_6000i, +}; + +const struct iwl_cfg iwl6000i_2bg_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N 6200 BG", + IWL_DEVICE_6000i, +}; + +#define IWL_DEVICE_6050 \ + .fw_name_pre = IWL6050_FW_PRE, \ + .ucode_api_max = IWL6050_UCODE_API_MAX, \ + .ucode_api_min = IWL6050_UCODE_API_MIN, \ + .device_family = IWL_DEVICE_FAMILY_6050, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .valid_tx_ant = ANT_AB, /* .cfg overwrite */ \ + .valid_rx_ant = ANT_AB, /* .cfg overwrite */ \ + .nvm_ver = EEPROM_6050_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_6050_TX_POWER_VERSION, \ + .base_params = &iwl6050_base_params, \ + .eeprom_params = &iwl6000_eeprom_params, \ + .led_mode = IWL_LED_BLINK, \ + .internal_wimax_coex = true + +const struct iwl_cfg iwl6050_2agn_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N + WiMAX 6250 AGN", + IWL_DEVICE_6050, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6050_2abg_cfg = { + .name = "Intel(R) Centrino(R) Advanced-N + WiMAX 6250 ABG", + IWL_DEVICE_6050, +}; + +#define IWL_DEVICE_6150 \ + .fw_name_pre = IWL6050_FW_PRE, \ + .ucode_api_max = IWL6050_UCODE_API_MAX, \ + .ucode_api_min = IWL6050_UCODE_API_MIN, \ + .device_family = IWL_DEVICE_FAMILY_6150, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .nvm_ver = EEPROM_6150_EEPROM_VERSION, \ + .nvm_calib_ver = EEPROM_6150_TX_POWER_VERSION, \ + .base_params = &iwl6050_base_params, \ + .eeprom_params = &iwl6000_eeprom_params, \ + .led_mode = IWL_LED_BLINK, \ + .internal_wimax_coex = true + +const struct iwl_cfg iwl6150_bgn_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N + WiMAX 6150 BGN", + IWL_DEVICE_6150, + .ht_params = &iwl6000_ht_params, +}; + +const struct iwl_cfg iwl6150_bg_cfg = { + .name = "Intel(R) Centrino(R) Wireless-N + WiMAX 6150 BG", + IWL_DEVICE_6150, +}; + +const struct iwl_cfg iwl6000_3agn_cfg = { + .name = "Intel(R) Centrino(R) Ultimate-N 6300 AGN", + .fw_name_pre = IWL6000_FW_PRE, + .ucode_api_max = IWL6000_UCODE_API_MAX, + .ucode_api_ok = IWL6000_UCODE_API_OK, + .ucode_api_min = IWL6000_UCODE_API_MIN, + .device_family = IWL_DEVICE_FAMILY_6000, + .max_inst_size = IWL60_RTC_INST_SIZE, + .max_data_size = IWL60_RTC_DATA_SIZE, + .nvm_ver = EEPROM_6000_EEPROM_VERSION, + .nvm_calib_ver = EEPROM_6000_TX_POWER_VERSION, + .base_params = &iwl6000_base_params, + .eeprom_params = &iwl6000_eeprom_params, + .ht_params = &iwl6000_ht_params, + .led_mode = IWL_LED_BLINK, +}; + +MODULE_FIRMWARE(IWL6000_MODULE_FIRMWARE(IWL6000_UCODE_API_OK)); +MODULE_FIRMWARE(IWL6050_MODULE_FIRMWARE(IWL6050_UCODE_API_OK)); +MODULE_FIRMWARE(IWL6005_MODULE_FIRMWARE(IWL6000G2_UCODE_API_OK)); +MODULE_FIRMWARE(IWL6030_MODULE_FIRMWARE(IWL6000G2B_UCODE_API_OK)); diff --git a/drivers/net/wireless/iwlwifi/iwl-7000.c b/drivers/net/wireless/iwlwifi/iwl-7000.c new file mode 100644 index 0000000..dc94d44 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-7000.c @@ -0,0 +1,146 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#include +#include +#include "iwl-config.h" +#include "iwl-agn-hw.h" + +/* Highest firmware API version supported */ +#define IWL7260_UCODE_API_MAX 7 +#define IWL3160_UCODE_API_MAX 7 + +/* Oldest version we won't warn about */ +#define IWL7260_UCODE_API_OK 7 +#define IWL3160_UCODE_API_OK 7 + +/* Lowest firmware API version supported */ +#define IWL7260_UCODE_API_MIN 7 +#define IWL3160_UCODE_API_MIN 7 + +/* NVM versions */ +#define IWL7260_NVM_VERSION 0x0a1d +#define IWL7260_TX_POWER_VERSION 0xffff /* meaningless */ +#define IWL3160_NVM_VERSION 0x709 +#define IWL3160_TX_POWER_VERSION 0xffff /* meaningless */ + +#define IWL7260_FW_PRE "iwlwifi-7260-" +#define IWL7260_MODULE_FIRMWARE(api) IWL7260_FW_PRE __stringify(api) ".ucode" + +#define IWL3160_FW_PRE "iwlwifi-3160-" +#define IWL3160_MODULE_FIRMWARE(api) IWL3160_FW_PRE __stringify(api) ".ucode" + +static const struct iwl_base_params iwl7000_base_params = { + .eeprom_size = OTP_LOW_IMAGE_SIZE, + .num_of_queues = IWLAGN_NUM_QUEUES, + .pll_cfg_val = 0, + .shadow_ram_support = true, + .led_compensation = 57, + .adv_thermal_throttle = true, + .support_ct_kill_exit = true, + .plcp_delta_threshold = IWL_MAX_PLCP_ERR_THRESHOLD_DEF, + .chain_noise_scale = 1000, + .wd_timeout = IWL_LONG_WD_TIMEOUT, + .max_event_log_size = 512, + .shadow_reg_enable = false, /* TODO: fix bugs using this feature */ +}; + +static const struct iwl_ht_params iwl7000_ht_params = { + .use_rts_for_aggregation = true, /* use rts/cts protection */ + .ht40_bands = BIT(IEEE80211_BAND_2GHZ) | BIT(IEEE80211_BAND_5GHZ), +}; + +#define IWL_DEVICE_7000 \ + .ucode_api_max = IWL7260_UCODE_API_MAX, \ + .ucode_api_ok = IWL7260_UCODE_API_OK, \ + .ucode_api_min = IWL7260_UCODE_API_MIN, \ + .device_family = IWL_DEVICE_FAMILY_7000, \ + .max_inst_size = IWL60_RTC_INST_SIZE, \ + .max_data_size = IWL60_RTC_DATA_SIZE, \ + .base_params = &iwl7000_base_params, \ + /* TODO: .bt_params? */ \ + .need_temp_offset_calib = true, \ + .led_mode = IWL_LED_RF_STATE, \ + .adv_pm = true \ + + +const struct iwl_cfg iwl7260_2ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC7260", + .fw_name_pre = IWL7260_FW_PRE, + IWL_DEVICE_7000, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL7260_NVM_VERSION, + .nvm_calib_ver = IWL7260_TX_POWER_VERSION, +}; + +const struct iwl_cfg iwl3160_ac_cfg = { + .name = "Intel(R) Dual Band Wireless AC3160", + .fw_name_pre = IWL3160_FW_PRE, + IWL_DEVICE_7000, + .ht_params = &iwl7000_ht_params, + .nvm_ver = IWL3160_NVM_VERSION, + .nvm_calib_ver = IWL3160_TX_POWER_VERSION, +}; + +MODULE_FIRMWARE(IWL7260_MODULE_FIRMWARE(IWL7260_UCODE_API_OK)); +MODULE_FIRMWARE(IWL3160_MODULE_FIRMWARE(IWL3160_UCODE_API_OK)); diff --git a/drivers/net/wireless/iwlwifi/iwl-agn-hw.h b/drivers/net/wireless/iwlwifi/iwl-agn-hw.h new file mode 100644 index 0000000..6d73f94 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-agn-hw.h @@ -0,0 +1,119 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +/* + * Please use this file (iwl-agn-hw.h) only for hardware-related definitions. + */ + +#ifndef __iwl_agn_hw_h__ +#define __iwl_agn_hw_h__ + +#define IWLAGN_RTC_INST_LOWER_BOUND (0x000000) +#define IWLAGN_RTC_INST_UPPER_BOUND (0x020000) + +#define IWLAGN_RTC_DATA_LOWER_BOUND (0x800000) +#define IWLAGN_RTC_DATA_UPPER_BOUND (0x80C000) + +#define IWLAGN_RTC_INST_SIZE (IWLAGN_RTC_INST_UPPER_BOUND - \ + IWLAGN_RTC_INST_LOWER_BOUND) +#define IWLAGN_RTC_DATA_SIZE (IWLAGN_RTC_DATA_UPPER_BOUND - \ + IWLAGN_RTC_DATA_LOWER_BOUND) + +#define IWL60_RTC_INST_LOWER_BOUND (0x000000) +#define IWL60_RTC_INST_UPPER_BOUND (0x040000) +#define IWL60_RTC_DATA_LOWER_BOUND (0x800000) +#define IWL60_RTC_DATA_UPPER_BOUND (0x814000) +#define IWL60_RTC_INST_SIZE \ + (IWL60_RTC_INST_UPPER_BOUND - IWL60_RTC_INST_LOWER_BOUND) +#define IWL60_RTC_DATA_SIZE \ + (IWL60_RTC_DATA_UPPER_BOUND - IWL60_RTC_DATA_LOWER_BOUND) + +/* RSSI to dBm */ +#define IWLAGN_RSSI_OFFSET 44 + +#define IWLAGN_DEFAULT_TX_RETRY 15 +#define IWLAGN_MGMT_DFAULT_RETRY_LIMIT 3 +#define IWLAGN_RTS_DFAULT_RETRY_LIMIT 60 +#define IWLAGN_BAR_DFAULT_RETRY_LIMIT 60 +#define IWLAGN_LOW_RETRY_LIMIT 7 + +/* Limit range of txpower output target to be between these values */ +#define IWLAGN_TX_POWER_TARGET_POWER_MIN (0) /* 0 dBm: 1 milliwatt */ +#define IWLAGN_TX_POWER_TARGET_POWER_MAX (16) /* 16 dBm */ + +/* EEPROM */ +#define IWLAGN_EEPROM_IMG_SIZE 2048 +/* OTP */ +/* lower blocks contain EEPROM image and calibration data */ +#define OTP_LOW_IMAGE_SIZE (2 * 512 * sizeof(u16)) /* 2 KB */ +/* high blocks contain PAPD data */ +#define OTP_HIGH_IMAGE_SIZE_6x00 (6 * 512 * sizeof(u16)) /* 6 KB */ +#define OTP_HIGH_IMAGE_SIZE_1000 (0x200 * sizeof(u16)) /* 1024 bytes */ +#define OTP_MAX_LL_ITEMS_1000 (3) /* OTP blocks for 1000 */ +#define OTP_MAX_LL_ITEMS_6x00 (4) /* OTP blocks for 6x00 */ +#define OTP_MAX_LL_ITEMS_6x50 (7) /* OTP blocks for 6x50 */ +#define OTP_MAX_LL_ITEMS_2x00 (4) /* OTP blocks for 2x00 */ + + +#define IWLAGN_NUM_QUEUES 20 + +#endif /* __iwl_agn_hw_h__ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-config.h b/drivers/net/wireless/iwlwifi/iwl-config.h new file mode 100644 index 0000000..c67e296 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-config.h @@ -0,0 +1,326 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#ifndef __IWL_CONFIG_H__ +#define __IWL_CONFIG_H__ + +#include +#include + + +enum iwl_device_family { + IWL_DEVICE_FAMILY_UNDEFINED, + IWL_DEVICE_FAMILY_1000, + IWL_DEVICE_FAMILY_100, + IWL_DEVICE_FAMILY_2000, + IWL_DEVICE_FAMILY_2030, + IWL_DEVICE_FAMILY_105, + IWL_DEVICE_FAMILY_135, + IWL_DEVICE_FAMILY_5000, + IWL_DEVICE_FAMILY_5150, + IWL_DEVICE_FAMILY_6000, + IWL_DEVICE_FAMILY_6000i, + IWL_DEVICE_FAMILY_6005, + IWL_DEVICE_FAMILY_6030, + IWL_DEVICE_FAMILY_6050, + IWL_DEVICE_FAMILY_6150, + IWL_DEVICE_FAMILY_7000, +}; + +/* + * LED mode + * IWL_LED_DEFAULT: use device default + * IWL_LED_RF_STATE: turn LED on/off based on RF state + * LED ON = RF ON + * LED OFF = RF OFF + * IWL_LED_BLINK: adjust led blink rate based on blink table + * IWL_LED_DISABLE: led disabled + */ +enum iwl_led_mode { + IWL_LED_DEFAULT, + IWL_LED_RF_STATE, + IWL_LED_BLINK, + IWL_LED_DISABLE, +}; + +/* + * This is the threshold value of plcp error rate per 100mSecs. It is + * used to set and check for the validity of plcp_delta. + */ +#define IWL_MAX_PLCP_ERR_THRESHOLD_MIN 1 +#define IWL_MAX_PLCP_ERR_THRESHOLD_DEF 50 +#define IWL_MAX_PLCP_ERR_LONG_THRESHOLD_DEF 100 +#define IWL_MAX_PLCP_ERR_EXT_LONG_THRESHOLD_DEF 200 +#define IWL_MAX_PLCP_ERR_THRESHOLD_MAX 255 +#define IWL_MAX_PLCP_ERR_THRESHOLD_DISABLE 0 + +/* TX queue watchdog timeouts in mSecs */ +#define IWL_WATCHDOG_DISABLED 0 +#define IWL_DEF_WD_TIMEOUT 2000 +#define IWL_LONG_WD_TIMEOUT 10000 +#define IWL_MAX_WD_TIMEOUT 120000 + +/* Antenna presence definitions */ +#define ANT_NONE 0x0 +#define ANT_A BIT(0) +#define ANT_B BIT(1) +#define ANT_C BIT(2) +#define ANT_AB (ANT_A | ANT_B) +#define ANT_AC (ANT_A | ANT_C) +#define ANT_BC (ANT_B | ANT_C) +#define ANT_ABC (ANT_A | ANT_B | ANT_C) + + +/* + * @max_ll_items: max number of OTP blocks + * @shadow_ram_support: shadow support for OTP memory + * @led_compensation: compensate on the led on/off time per HW according + * to the deviation to achieve the desired led frequency. + * The detail algorithm is described in iwl-led.c + * @chain_noise_num_beacons: number of beacons used to compute chain noise + * @adv_thermal_throttle: support advance thermal throttle + * @support_ct_kill_exit: support ct kill exit condition + * @plcp_delta_threshold: plcp error rate threshold used to trigger + * radio tuning when there is a high receiving plcp error rate + * @chain_noise_scale: default chain noise scale used for gain computation + * @wd_timeout: TX queues watchdog timeout + * @max_event_log_size: size of event log buffer size for ucode event logging + * @shadow_reg_enable: HW shadow register support + * @hd_v2: v2 of enhanced sensitivity value, used for 2000 series and up + * @no_idle_support: do not support idle mode + */ +struct iwl_base_params { + int eeprom_size; + int num_of_queues; /* def: HW dependent */ + /* for iwl_pcie_apm_init() */ + u32 pll_cfg_val; + + const u16 max_ll_items; + const bool shadow_ram_support; + u16 led_compensation; + bool adv_thermal_throttle; + bool support_ct_kill_exit; + u8 plcp_delta_threshold; + s32 chain_noise_scale; + unsigned int wd_timeout; + u32 max_event_log_size; + const bool shadow_reg_enable; + const bool hd_v2; + const bool no_idle_support; +}; + +/* + * @advanced_bt_coexist: support advanced bt coexist + * @bt_init_traffic_load: specify initial bt traffic load + * @bt_prio_boost: default bt priority boost value + * @agg_time_limit: maximum number of uSec in aggregation + * @bt_sco_disable: uCode should not response to BT in SCO/ESCO mode + */ +struct iwl_bt_params { + bool advanced_bt_coexist; + u8 bt_init_traffic_load; + u32 bt_prio_boost; + u16 agg_time_limit; + bool bt_sco_disable; + bool bt_session_2; +}; + +/* + * @use_rts_for_aggregation: use rts/cts protection for HT traffic + * @ht40_bands: bitmap of bands (using %IEEE80211_BAND_*) that support HT40 + */ +struct iwl_ht_params { + enum ieee80211_smps_mode smps_mode; + const bool ht_greenfield_support; /* if used set to true */ + bool use_rts_for_aggregation; + u8 ht40_bands; +}; + +/* + * information on how to parse the EEPROM + */ +#define EEPROM_REG_BAND_1_CHANNELS 0x08 +#define EEPROM_REG_BAND_2_CHANNELS 0x26 +#define EEPROM_REG_BAND_3_CHANNELS 0x42 +#define EEPROM_REG_BAND_4_CHANNELS 0x5C +#define EEPROM_REG_BAND_5_CHANNELS 0x74 +#define EEPROM_REG_BAND_24_HT40_CHANNELS 0x82 +#define EEPROM_REG_BAND_52_HT40_CHANNELS 0x92 +#define EEPROM_6000_REG_BAND_24_HT40_CHANNELS 0x80 +#define EEPROM_REGULATORY_BAND_NO_HT40 0 + +struct iwl_eeprom_params { + const u8 regulatory_bands[7]; + bool enhanced_txpower; +}; + +/** + * struct iwl_cfg + * @name: Offical name of the device + * @fw_name_pre: Firmware filename prefix. The api version and extension + * (.ucode) will be added to filename before loading from disk. The + * filename is constructed as fw_name_pre.ucode. + * @ucode_api_max: Highest version of uCode API supported by driver. + * @ucode_api_ok: oldest version of the uCode API that is OK to load + * without a warning, for use in transitions + * @ucode_api_min: Lowest version of uCode API supported by driver. + * @max_inst_size: The maximal length of the fw inst section + * @max_data_size: The maximal length of the fw data section + * @valid_tx_ant: valid transmit antenna + * @valid_rx_ant: valid receive antenna + * @nvm_ver: NVM version + * @nvm_calib_ver: NVM calibration version + * @lib: pointer to the lib ops + * @base_params: pointer to basic parameters + * @ht_params: point to ht patameters + * @bt_params: pointer to bt parameters + * @need_temp_offset_calib: need to perform temperature offset calibration + * @no_xtal_calib: some devices do not need crystal calibration data, + * don't send it to those + * @led_mode: 0=blinking, 1=On(RF On)/Off(RF Off) + * @adv_pm: advance power management + * @rx_with_siso_diversity: 1x1 device with rx antenna diversity + * @internal_wimax_coex: internal wifi/wimax combo device + * @temp_offset_v2: support v2 of temperature offset calibration + * + * We enable the driver to be backward compatible wrt. hardware features. + * API differences in uCode shouldn't be handled here but through TLVs + * and/or the uCode API version instead. + */ +struct iwl_cfg { + /* params specific to an individual device within a device family */ + const char *name; + const char *fw_name_pre; + const unsigned int ucode_api_max; + const unsigned int ucode_api_ok; + const unsigned int ucode_api_min; + const enum iwl_device_family device_family; + const u32 max_data_size; + const u32 max_inst_size; + u8 valid_tx_ant; + u8 valid_rx_ant; + u16 nvm_ver; + u16 nvm_calib_ver; + /* params not likely to change within a device family */ + const struct iwl_base_params *base_params; + /* params likely to change within a device family */ + const struct iwl_ht_params *ht_params; + const struct iwl_bt_params *bt_params; + const struct iwl_eeprom_params *eeprom_params; + const bool need_temp_offset_calib; /* if used set to true */ + const bool no_xtal_calib; + enum iwl_led_mode led_mode; + const bool adv_pm; + const bool rx_with_siso_diversity; + const bool internal_wimax_coex; + const bool temp_offset_v2; +}; + +/* + * This list declares the config structures for all devices. + */ +extern const struct iwl_cfg iwl5300_agn_cfg; +extern const struct iwl_cfg iwl5100_agn_cfg; +extern const struct iwl_cfg iwl5350_agn_cfg; +extern const struct iwl_cfg iwl5100_bgn_cfg; +extern const struct iwl_cfg iwl5100_abg_cfg; +extern const struct iwl_cfg iwl5150_agn_cfg; +extern const struct iwl_cfg iwl5150_abg_cfg; +extern const struct iwl_cfg iwl6005_2agn_cfg; +extern const struct iwl_cfg iwl6005_2abg_cfg; +extern const struct iwl_cfg iwl6005_2bg_cfg; +extern const struct iwl_cfg iwl6005_2agn_sff_cfg; +extern const struct iwl_cfg iwl6005_2agn_d_cfg; +extern const struct iwl_cfg iwl6005_2agn_mow1_cfg; +extern const struct iwl_cfg iwl6005_2agn_mow2_cfg; +extern const struct iwl_cfg iwl1030_bgn_cfg; +extern const struct iwl_cfg iwl1030_bg_cfg; +extern const struct iwl_cfg iwl6030_2agn_cfg; +extern const struct iwl_cfg iwl6030_2abg_cfg; +extern const struct iwl_cfg iwl6030_2bgn_cfg; +extern const struct iwl_cfg iwl6030_2bg_cfg; +extern const struct iwl_cfg iwl6000i_2agn_cfg; +extern const struct iwl_cfg iwl6000i_2abg_cfg; +extern const struct iwl_cfg iwl6000i_2bg_cfg; +extern const struct iwl_cfg iwl6000_3agn_cfg; +extern const struct iwl_cfg iwl6050_2agn_cfg; +extern const struct iwl_cfg iwl6050_2abg_cfg; +extern const struct iwl_cfg iwl6150_bgn_cfg; +extern const struct iwl_cfg iwl6150_bg_cfg; +extern const struct iwl_cfg iwl1000_bgn_cfg; +extern const struct iwl_cfg iwl1000_bg_cfg; +extern const struct iwl_cfg iwl100_bgn_cfg; +extern const struct iwl_cfg iwl100_bg_cfg; +extern const struct iwl_cfg iwl130_bgn_cfg; +extern const struct iwl_cfg iwl130_bg_cfg; +extern const struct iwl_cfg iwl2000_2bgn_cfg; +extern const struct iwl_cfg iwl2000_2bgn_d_cfg; +extern const struct iwl_cfg iwl2030_2bgn_cfg; +extern const struct iwl_cfg iwl6035_2agn_cfg; +extern const struct iwl_cfg iwl6035_2agn_sff_cfg; +extern const struct iwl_cfg iwl105_bgn_cfg; +extern const struct iwl_cfg iwl105_bgn_d_cfg; +extern const struct iwl_cfg iwl135_bgn_cfg; +extern const struct iwl_cfg iwl7260_2ac_cfg; +extern const struct iwl_cfg iwl3160_ac_cfg; + +#endif /* __IWL_CONFIG_H__ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-csr.h b/drivers/net/wireless/iwlwifi/iwl-csr.h new file mode 100644 index 0000000..20e845d --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-csr.h @@ -0,0 +1,475 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#ifndef __iwl_csr_h__ +#define __iwl_csr_h__ +/* + * CSR (control and status registers) + * + * CSR registers are mapped directly into PCI bus space, and are accessible + * whenever platform supplies power to device, even when device is in + * low power states due to driver-invoked device resets + * (e.g. CSR_RESET_REG_FLAG_SW_RESET) or uCode-driven power-saving modes. + * + * Use iwl_write32() and iwl_read32() family to access these registers; + * these provide simple PCI bus access, without waking up the MAC. + * Do not use iwl_write_direct32() family for these registers; + * no need to "grab nic access" via CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ. + * The MAC (uCode processor, etc.) does not need to be powered up for accessing + * the CSR registers. + * + * NOTE: Device does need to be awake in order to read this memory + * via CSR_EEPROM and CSR_OTP registers + */ +#define CSR_BASE (0x000) + +#define CSR_HW_IF_CONFIG_REG (CSR_BASE+0x000) /* hardware interface config */ +#define CSR_INT_COALESCING (CSR_BASE+0x004) /* accum ints, 32-usec units */ +#define CSR_INT (CSR_BASE+0x008) /* host interrupt status/ack */ +#define CSR_INT_MASK (CSR_BASE+0x00c) /* host interrupt enable */ +#define CSR_FH_INT_STATUS (CSR_BASE+0x010) /* busmaster int status/ack*/ +#define CSR_GPIO_IN (CSR_BASE+0x018) /* read external chip pins */ +#define CSR_RESET (CSR_BASE+0x020) /* busmaster enable, NMI, etc*/ +#define CSR_GP_CNTRL (CSR_BASE+0x024) + +/* 2nd byte of CSR_INT_COALESCING, not accessible via iwl_write32()! */ +#define CSR_INT_PERIODIC_REG (CSR_BASE+0x005) + +/* + * Hardware revision info + * Bit fields: + * 31-16: Reserved + * 15-4: Type of device: see CSR_HW_REV_TYPE_xxx definitions + * 3-2: Revision step: 0 = A, 1 = B, 2 = C, 3 = D + * 1-0: "Dash" (-) value, as in A-1, etc. + */ +#define CSR_HW_REV (CSR_BASE+0x028) + +/* + * EEPROM and OTP (one-time-programmable) memory reads + * + * NOTE: Device must be awake, initialized via apm_ops.init(), + * in order to read. + */ +#define CSR_EEPROM_REG (CSR_BASE+0x02c) +#define CSR_EEPROM_GP (CSR_BASE+0x030) +#define CSR_OTP_GP_REG (CSR_BASE+0x034) + +#define CSR_GIO_REG (CSR_BASE+0x03C) +#define CSR_GP_UCODE_REG (CSR_BASE+0x048) +#define CSR_GP_DRIVER_REG (CSR_BASE+0x050) + +/* + * UCODE-DRIVER GP (general purpose) mailbox registers. + * SET/CLR registers set/clear bit(s) if "1" is written. + */ +#define CSR_UCODE_DRV_GP1 (CSR_BASE+0x054) +#define CSR_UCODE_DRV_GP1_SET (CSR_BASE+0x058) +#define CSR_UCODE_DRV_GP1_CLR (CSR_BASE+0x05c) +#define CSR_UCODE_DRV_GP2 (CSR_BASE+0x060) + +#define CSR_LED_REG (CSR_BASE+0x094) +#define CSR_DRAM_INT_TBL_REG (CSR_BASE+0x0A0) +#define CSR_MAC_SHADOW_REG_CTRL (CSR_BASE+0x0A8) /* 6000 and up */ + + +/* GIO Chicken Bits (PCI Express bus link power management) */ +#define CSR_GIO_CHICKEN_BITS (CSR_BASE+0x100) + +/* Analog phase-lock-loop configuration */ +#define CSR_ANA_PLL_CFG (CSR_BASE+0x20c) + +/* + * CSR Hardware Revision Workaround Register. Indicates hardware rev; + * "step" determines CCK backoff for txpower calculation. Used for 4965 only. + * See also CSR_HW_REV register. + * Bit fields: + * 3-2: 0 = A, 1 = B, 2 = C, 3 = D step + * 1-0: "Dash" (-) value, as in C-1, etc. + */ +#define CSR_HW_REV_WA_REG (CSR_BASE+0x22C) + +#define CSR_DBG_HPET_MEM_REG (CSR_BASE+0x240) +#define CSR_DBG_LINK_PWR_MGMT_REG (CSR_BASE+0x250) + +/* Bits for CSR_HW_IF_CONFIG_REG */ +#define CSR_HW_IF_CONFIG_REG_MSK_MAC_DASH (0x00000003) +#define CSR_HW_IF_CONFIG_REG_MSK_MAC_STEP (0x0000000C) +#define CSR_HW_IF_CONFIG_REG_MSK_BOARD_VER (0x000000C0) +#define CSR_HW_IF_CONFIG_REG_BIT_MAC_SI (0x00000100) +#define CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI (0x00000200) +#define CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE (0x00000C00) +#define CSR_HW_IF_CONFIG_REG_MSK_PHY_DASH (0x00003000) +#define CSR_HW_IF_CONFIG_REG_MSK_PHY_STEP (0x0000C000) + +#define CSR_HW_IF_CONFIG_REG_POS_MAC_DASH (0) +#define CSR_HW_IF_CONFIG_REG_POS_MAC_STEP (2) +#define CSR_HW_IF_CONFIG_REG_POS_BOARD_VER (6) +#define CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE (10) +#define CSR_HW_IF_CONFIG_REG_POS_PHY_DASH (12) +#define CSR_HW_IF_CONFIG_REG_POS_PHY_STEP (14) + +#define CSR_HW_IF_CONFIG_REG_BIT_HAP_WAKE_L1A (0x00080000) +#define CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM (0x00200000) +#define CSR_HW_IF_CONFIG_REG_BIT_NIC_READY (0x00400000) /* PCI_OWN_SEM */ +#define CSR_HW_IF_CONFIG_REG_BIT_NIC_PREPARE_DONE (0x02000000) /* ME_OWN */ +#define CSR_HW_IF_CONFIG_REG_PREPARE (0x08000000) /* WAKE_ME */ + +#define CSR_INT_PERIODIC_DIS (0x00) /* disable periodic int*/ +#define CSR_INT_PERIODIC_ENA (0xFF) /* 255*32 usec ~ 8 msec*/ + +/* interrupt flags in INTA, set by uCode or hardware (e.g. dma), + * acknowledged (reset) by host writing "1" to flagged bits. */ +#define CSR_INT_BIT_FH_RX (1 << 31) /* Rx DMA, cmd responses, FH_INT[17:16] */ +#define CSR_INT_BIT_HW_ERR (1 << 29) /* DMA hardware error FH_INT[31] */ +#define CSR_INT_BIT_RX_PERIODIC (1 << 28) /* Rx periodic */ +#define CSR_INT_BIT_FH_TX (1 << 27) /* Tx DMA FH_INT[1:0] */ +#define CSR_INT_BIT_SCD (1 << 26) /* TXQ pointer advanced */ +#define CSR_INT_BIT_SW_ERR (1 << 25) /* uCode error */ +#define CSR_INT_BIT_RF_KILL (1 << 7) /* HW RFKILL switch GP_CNTRL[27] toggled */ +#define CSR_INT_BIT_CT_KILL (1 << 6) /* Critical temp (chip too hot) rfkill */ +#define CSR_INT_BIT_SW_RX (1 << 3) /* Rx, command responses */ +#define CSR_INT_BIT_WAKEUP (1 << 1) /* NIC controller waking up (pwr mgmt) */ +#define CSR_INT_BIT_ALIVE (1 << 0) /* uCode interrupts once it initializes */ + +#define CSR_INI_SET_MASK (CSR_INT_BIT_FH_RX | \ + CSR_INT_BIT_HW_ERR | \ + CSR_INT_BIT_FH_TX | \ + CSR_INT_BIT_SW_ERR | \ + CSR_INT_BIT_RF_KILL | \ + CSR_INT_BIT_SW_RX | \ + CSR_INT_BIT_WAKEUP | \ + CSR_INT_BIT_ALIVE) + +/* interrupt flags in FH (flow handler) (PCI busmaster DMA) */ +#define CSR_FH_INT_BIT_ERR (1 << 31) /* Error */ +#define CSR_FH_INT_BIT_HI_PRIOR (1 << 30) /* High priority Rx, bypass coalescing */ +#define CSR_FH_INT_BIT_RX_CHNL1 (1 << 17) /* Rx channel 1 */ +#define CSR_FH_INT_BIT_RX_CHNL0 (1 << 16) /* Rx channel 0 */ +#define CSR_FH_INT_BIT_TX_CHNL1 (1 << 1) /* Tx channel 1 */ +#define CSR_FH_INT_BIT_TX_CHNL0 (1 << 0) /* Tx channel 0 */ + +#define CSR_FH_INT_RX_MASK (CSR_FH_INT_BIT_HI_PRIOR | \ + CSR_FH_INT_BIT_RX_CHNL1 | \ + CSR_FH_INT_BIT_RX_CHNL0) + +#define CSR_FH_INT_TX_MASK (CSR_FH_INT_BIT_TX_CHNL1 | \ + CSR_FH_INT_BIT_TX_CHNL0) + +/* GPIO */ +#define CSR_GPIO_IN_BIT_AUX_POWER (0x00000200) +#define CSR_GPIO_IN_VAL_VAUX_PWR_SRC (0x00000000) +#define CSR_GPIO_IN_VAL_VMAIN_PWR_SRC (0x00000200) + +/* RESET */ +#define CSR_RESET_REG_FLAG_NEVO_RESET (0x00000001) +#define CSR_RESET_REG_FLAG_FORCE_NMI (0x00000002) +#define CSR_RESET_REG_FLAG_SW_RESET (0x00000080) +#define CSR_RESET_REG_FLAG_MASTER_DISABLED (0x00000100) +#define CSR_RESET_REG_FLAG_STOP_MASTER (0x00000200) +#define CSR_RESET_LINK_PWR_MGMT_DISABLED (0x80000000) + +/* + * GP (general purpose) CONTROL REGISTER + * Bit fields: + * 27: HW_RF_KILL_SW + * Indicates state of (platform's) hardware RF-Kill switch + * 26-24: POWER_SAVE_TYPE + * Indicates current power-saving mode: + * 000 -- No power saving + * 001 -- MAC power-down + * 010 -- PHY (radio) power-down + * 011 -- Error + * 9-6: SYS_CONFIG + * Indicates current system configuration, reflecting pins on chip + * as forced high/low by device circuit board. + * 4: GOING_TO_SLEEP + * Indicates MAC is entering a power-saving sleep power-down. + * Not a good time to access device-internal resources. + * 3: MAC_ACCESS_REQ + * Host sets this to request and maintain MAC wakeup, to allow host + * access to device-internal resources. Host must wait for + * MAC_CLOCK_READY (and !GOING_TO_SLEEP) before accessing non-CSR + * device registers. + * 2: INIT_DONE + * Host sets this to put device into fully operational D0 power mode. + * Host resets this after SW_RESET to put device into low power mode. + * 0: MAC_CLOCK_READY + * Indicates MAC (ucode processor, etc.) is powered up and can run. + * Internal resources are accessible. + * NOTE: This does not indicate that the processor is actually running. + * NOTE: This does not indicate that device has completed + * init or post-power-down restore of internal SRAM memory. + * Use CSR_UCODE_DRV_GP1_BIT_MAC_SLEEP as indication that + * SRAM is restored and uCode is in normal operation mode. + * Later devices (5xxx/6xxx/1xxx) use non-volatile SRAM, and + * do not need to save/restore it. + * NOTE: After device reset, this bit remains "0" until host sets + * INIT_DONE + */ +#define CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY (0x00000001) +#define CSR_GP_CNTRL_REG_FLAG_INIT_DONE (0x00000004) +#define CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ (0x00000008) +#define CSR_GP_CNTRL_REG_FLAG_GOING_TO_SLEEP (0x00000010) + +#define CSR_GP_CNTRL_REG_VAL_MAC_ACCESS_EN (0x00000001) + +#define CSR_GP_CNTRL_REG_MSK_POWER_SAVE_TYPE (0x07000000) +#define CSR_GP_CNTRL_REG_FLAG_MAC_POWER_SAVE (0x04000000) +#define CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW (0x08000000) + + +/* HW REV */ +#define CSR_HW_REV_DASH(_val) (((_val) & 0x0000003) >> 0) +#define CSR_HW_REV_STEP(_val) (((_val) & 0x000000C) >> 2) + +#define CSR_HW_REV_TYPE_MSK (0x000FFF0) +#define CSR_HW_REV_TYPE_5300 (0x0000020) +#define CSR_HW_REV_TYPE_5350 (0x0000030) +#define CSR_HW_REV_TYPE_5100 (0x0000050) +#define CSR_HW_REV_TYPE_5150 (0x0000040) +#define CSR_HW_REV_TYPE_1000 (0x0000060) +#define CSR_HW_REV_TYPE_6x00 (0x0000070) +#define CSR_HW_REV_TYPE_6x50 (0x0000080) +#define CSR_HW_REV_TYPE_6150 (0x0000084) +#define CSR_HW_REV_TYPE_6x05 (0x00000B0) +#define CSR_HW_REV_TYPE_6x30 CSR_HW_REV_TYPE_6x05 +#define CSR_HW_REV_TYPE_6x35 CSR_HW_REV_TYPE_6x05 +#define CSR_HW_REV_TYPE_2x30 (0x00000C0) +#define CSR_HW_REV_TYPE_2x00 (0x0000100) +#define CSR_HW_REV_TYPE_105 (0x0000110) +#define CSR_HW_REV_TYPE_135 (0x0000120) +#define CSR_HW_REV_TYPE_NONE (0x00001F0) + +/* EEPROM REG */ +#define CSR_EEPROM_REG_READ_VALID_MSK (0x00000001) +#define CSR_EEPROM_REG_BIT_CMD (0x00000002) +#define CSR_EEPROM_REG_MSK_ADDR (0x0000FFFC) +#define CSR_EEPROM_REG_MSK_DATA (0xFFFF0000) + +/* EEPROM GP */ +#define CSR_EEPROM_GP_VALID_MSK (0x00000007) /* signature */ +#define CSR_EEPROM_GP_IF_OWNER_MSK (0x00000180) +#define CSR_EEPROM_GP_BAD_SIGNATURE_BOTH_EEP_AND_OTP (0x00000000) +#define CSR_EEPROM_GP_BAD_SIG_EEP_GOOD_SIG_OTP (0x00000001) +#define CSR_EEPROM_GP_GOOD_SIG_EEP_LESS_THAN_4K (0x00000002) +#define CSR_EEPROM_GP_GOOD_SIG_EEP_MORE_THAN_4K (0x00000004) + +/* One-time-programmable memory general purpose reg */ +#define CSR_OTP_GP_REG_DEVICE_SELECT (0x00010000) /* 0 - EEPROM, 1 - OTP */ +#define CSR_OTP_GP_REG_OTP_ACCESS_MODE (0x00020000) /* 0 - absolute, 1 - relative */ +#define CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK (0x00100000) /* bit 20 */ +#define CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK (0x00200000) /* bit 21 */ + +/* GP REG */ +#define CSR_GP_REG_POWER_SAVE_STATUS_MSK (0x03000000) /* bit 24/25 */ +#define CSR_GP_REG_NO_POWER_SAVE (0x00000000) +#define CSR_GP_REG_MAC_POWER_SAVE (0x01000000) +#define CSR_GP_REG_PHY_POWER_SAVE (0x02000000) +#define CSR_GP_REG_POWER_SAVE_ERROR (0x03000000) + + +/* CSR GIO */ +#define CSR_GIO_REG_VAL_L0S_ENABLED (0x00000002) + +/* + * UCODE-DRIVER GP (general purpose) mailbox register 1 + * Host driver and uCode write and/or read this register to communicate with + * each other. + * Bit fields: + * 4: UCODE_DISABLE + * Host sets this to request permanent halt of uCode, same as + * sending CARD_STATE command with "halt" bit set. + * 3: CT_KILL_EXIT + * Host sets this to request exit from CT_KILL state, i.e. host thinks + * device temperature is low enough to continue normal operation. + * 2: CMD_BLOCKED + * Host sets this during RF KILL power-down sequence (HW, SW, CT KILL) + * to release uCode to clear all Tx and command queues, enter + * unassociated mode, and power down. + * NOTE: Some devices also use HBUS_TARG_MBX_C register for this bit. + * 1: SW_BIT_RFKILL + * Host sets this when issuing CARD_STATE command to request + * device sleep. + * 0: MAC_SLEEP + * uCode sets this when preparing a power-saving power-down. + * uCode resets this when power-up is complete and SRAM is sane. + * NOTE: device saves internal SRAM data to host when powering down, + * and must restore this data after powering back up. + * MAC_SLEEP is the best indication that restore is complete. + * Later devices (5xxx/6xxx/1xxx) use non-volatile SRAM, and + * do not need to save/restore it. + */ +#define CSR_UCODE_DRV_GP1_BIT_MAC_SLEEP (0x00000001) +#define CSR_UCODE_SW_BIT_RFKILL (0x00000002) +#define CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED (0x00000004) +#define CSR_UCODE_DRV_GP1_REG_BIT_CT_KILL_EXIT (0x00000008) +#define CSR_UCODE_DRV_GP1_BIT_D3_CFG_COMPLETE (0x00000020) + +/* GP Driver */ +#define CSR_GP_DRIVER_REG_BIT_RADIO_SKU_MSK (0x00000003) +#define CSR_GP_DRIVER_REG_BIT_RADIO_SKU_3x3_HYB (0x00000000) +#define CSR_GP_DRIVER_REG_BIT_RADIO_SKU_2x2_HYB (0x00000001) +#define CSR_GP_DRIVER_REG_BIT_RADIO_SKU_2x2_IPA (0x00000002) +#define CSR_GP_DRIVER_REG_BIT_CALIB_VERSION6 (0x00000004) +#define CSR_GP_DRIVER_REG_BIT_6050_1x2 (0x00000008) + +#define CSR_GP_DRIVER_REG_BIT_RADIO_IQ_INVER (0x00000080) + +/* GIO Chicken Bits (PCI Express bus link power management) */ +#define CSR_GIO_CHICKEN_BITS_REG_BIT_L1A_NO_L0S_RX (0x00800000) +#define CSR_GIO_CHICKEN_BITS_REG_BIT_DIS_L0S_EXIT_TIMER (0x20000000) + +/* LED */ +#define CSR_LED_BSM_CTRL_MSK (0xFFFFFFDF) +#define CSR_LED_REG_TURN_ON (0x60) +#define CSR_LED_REG_TURN_OFF (0x20) + +/* ANA_PLL */ +#define CSR50_ANA_PLL_CFG_VAL (0x00880300) + +/* HPET MEM debug */ +#define CSR_DBG_HPET_MEM_REG_VAL (0xFFFF0000) + +/* DRAM INT TABLE */ +#define CSR_DRAM_INT_TBL_ENABLE (1 << 31) +#define CSR_DRAM_INIT_TBL_WRAP_CHECK (1 << 27) + +/* + * HBUS (Host-side Bus) + * + * HBUS registers are mapped directly into PCI bus space, but are used + * to indirectly access device's internal memory or registers that + * may be powered-down. + * + * Use iwl_write_direct32()/iwl_read_direct32() family for these registers; + * host must "grab nic access" via CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ + * to make sure the MAC (uCode processor, etc.) is powered up for accessing + * internal resources. + * + * Do not use iwl_write32()/iwl_read32() family to access these registers; + * these provide only simple PCI bus access, without waking up the MAC. + */ +#define HBUS_BASE (0x400) + +/* + * Registers for accessing device's internal SRAM memory (e.g. SCD SRAM + * structures, error log, event log, verifying uCode load). + * First write to address register, then read from or write to data register + * to complete the job. Once the address register is set up, accesses to + * data registers auto-increment the address by one dword. + * Bit usage for address registers (read or write): + * 0-31: memory address within device + */ +#define HBUS_TARG_MEM_RADDR (HBUS_BASE+0x00c) +#define HBUS_TARG_MEM_WADDR (HBUS_BASE+0x010) +#define HBUS_TARG_MEM_WDAT (HBUS_BASE+0x018) +#define HBUS_TARG_MEM_RDAT (HBUS_BASE+0x01c) + +/* Mailbox C, used as workaround alternative to CSR_UCODE_DRV_GP1 mailbox */ +#define HBUS_TARG_MBX_C (HBUS_BASE+0x030) +#define HBUS_TARG_MBX_C_REG_BIT_CMD_BLOCKED (0x00000004) + +/* + * Registers for accessing device's internal peripheral registers + * (e.g. SCD, BSM, etc.). First write to address register, + * then read from or write to data register to complete the job. + * Bit usage for address registers (read or write): + * 0-15: register address (offset) within device + * 24-25: (# bytes - 1) to read or write (e.g. 3 for dword) + */ +#define HBUS_TARG_PRPH_WADDR (HBUS_BASE+0x044) +#define HBUS_TARG_PRPH_RADDR (HBUS_BASE+0x048) +#define HBUS_TARG_PRPH_WDAT (HBUS_BASE+0x04c) +#define HBUS_TARG_PRPH_RDAT (HBUS_BASE+0x050) + +/* Used to enable DBGM */ +#define HBUS_TARG_TEST_REG (HBUS_BASE+0x05c) + +/* + * Per-Tx-queue write pointer (index, really!) + * Indicates index to next TFD that driver will fill (1 past latest filled). + * Bit usage: + * 0-7: queue write index + * 11-8: queue selector + */ +#define HBUS_TARG_WRPTR (HBUS_BASE+0x060) + +/********************************************************** + * CSR values + **********************************************************/ + /* + * host interrupt timeout value + * used with setting interrupt coalescing timer + * the CSR_INT_COALESCING is an 8 bit register in 32-usec unit + * + * default interrupt coalescing timer is 64 x 32 = 2048 usecs + * default interrupt coalescing calibration timer is 16 x 32 = 512 usecs + */ +#define IWL_HOST_INT_TIMEOUT_MAX (0xFF) +#define IWL_HOST_INT_TIMEOUT_DEF (0x40) +#define IWL_HOST_INT_TIMEOUT_MIN (0x0) +#define IWL_HOST_INT_CALIB_TIMEOUT_MAX (0xFF) +#define IWL_HOST_INT_CALIB_TIMEOUT_DEF (0x10) +#define IWL_HOST_INT_CALIB_TIMEOUT_MIN (0x0) + +#endif /* !__iwl_csr_h__ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-debug.c b/drivers/net/wireless/iwlwifi/iwl-debug.c new file mode 100644 index 0000000..e0916d8 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-debug.c @@ -0,0 +1,152 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2007 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2011 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#define DEBUG + +#include +#include +#include +#include "iwl-drv.h" +#include "iwl-debug.h" +#include "iwl-devtrace.h" + +#define __iwl_fn(fn) \ +void __iwl_ ##fn(struct device *dev, const char *fmt, ...) \ +{ \ + struct va_format vaf = { \ + .fmt = fmt, \ + }; \ + va_list args1, args2; \ + \ + va_start(args1, fmt); \ + va_copy(args2, args1); \ + vaf.va = &args2; \ + dev_ ##fn(dev, "%pV", &vaf); \ + va_end(args2); \ + vaf.va = &args1; \ + trace_iwlwifi_ ##fn(&vaf); \ + va_end(args1); \ +} + +__iwl_fn(warn) +IWL_EXPORT_SYMBOL(__iwl_warn); +__iwl_fn(info) +IWL_EXPORT_SYMBOL(__iwl_info); +__iwl_fn(crit) +IWL_EXPORT_SYMBOL(__iwl_crit); + +void __iwl_err(struct device *dev, bool rfkill_prefix, bool trace_only, + const char *fmt, ...) +{ + struct va_format vaf = { + .fmt = fmt, + }; + va_list args; + + va_start(args, fmt); + if (!trace_only) { + va_list args2; + + va_copy(args2, args); + vaf.va = &args2; + if (rfkill_prefix) + dev_err(dev, "(RFKILL) %pV", &vaf); + else + dev_err(dev, "%pV", &vaf); + va_end(args2); + } + vaf.va = &args; + trace_iwlwifi_err(&vaf); + va_end(args); +} +IWL_EXPORT_SYMBOL(__iwl_err); + +#if defined(CPTCFG_IWLWIFI_DEBUG) || defined(CPTCFG_IWLWIFI_DEVICE_TRACING) +void __iwl_dbg(struct device *dev, + u32 level, bool limit, const char *function, + const char *fmt, ...) +{ + struct va_format vaf = { + .fmt = fmt, + }; + va_list args; + + va_start(args, fmt); +#ifdef CPTCFG_IWLWIFI_DEBUG + if (iwl_have_debug_level(level) && + (!limit || net_ratelimit())) { + va_list args2; + + va_copy(args2, args); + vaf.va = &args2; + dev_dbg(dev, "%c %s %pV", in_interrupt() ? 'I' : 'U', + function, &vaf); + va_end(args2); + } +#endif + vaf.va = &args; + trace_iwlwifi_dbg(level, in_interrupt(), function, &vaf); + va_end(args); +} +IWL_EXPORT_SYMBOL(__iwl_dbg); +#endif diff --git a/drivers/net/wireless/iwlwifi/iwl-debug.h b/drivers/net/wireless/iwlwifi/iwl-debug.h new file mode 100644 index 0000000..b7aa247 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-debug.h @@ -0,0 +1,187 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#ifndef __iwl_debug_h__ +#define __iwl_debug_h__ + +#include "iwl-modparams.h" + + +static inline bool iwl_have_debug_level(u32 level) +{ + return iwlwifi_mod_params.debug_level & level; +} + +void __iwl_err(struct device *dev, bool rfkill_prefix, bool only_trace, + const char *fmt, ...) __printf(4, 5); +void __iwl_warn(struct device *dev, const char *fmt, ...) __printf(2, 3); +void __iwl_info(struct device *dev, const char *fmt, ...) __printf(2, 3); +void __iwl_crit(struct device *dev, const char *fmt, ...) __printf(2, 3); + +/* No matter what is m (priv, bus, trans), this will work */ +#define IWL_ERR(m, f, a...) __iwl_err((m)->dev, false, false, f, ## a) +#define IWL_ERR_DEV(d, f, a...) __iwl_err((d), false, false, f, ## a) +#define IWL_WARN(m, f, a...) __iwl_warn((m)->dev, f, ## a) +#define IWL_INFO(m, f, a...) __iwl_info((m)->dev, f, ## a) +#define IWL_CRIT(m, f, a...) __iwl_crit((m)->dev, f, ## a) + +#if defined(CPTCFG_IWLWIFI_DEBUG) || defined(CPTCFG_IWLWIFI_DEVICE_TRACING) +void __iwl_dbg(struct device *dev, + u32 level, bool limit, const char *function, + const char *fmt, ...) __printf(5, 6); +#else +__printf(5, 6) static inline void +__iwl_dbg(struct device *dev, + u32 level, bool limit, const char *function, + const char *fmt, ...) +{} +#endif + +#define iwl_print_hex_error(m, p, len) \ +do { \ + print_hex_dump(KERN_ERR, "iwl data: ", \ + DUMP_PREFIX_OFFSET, 16, 1, p, len, 1); \ +} while (0) + +#define IWL_DEBUG(m, level, fmt, args...) \ + __iwl_dbg((m)->dev, level, false, __func__, fmt, ##args) +#define IWL_DEBUG_DEV(dev, level, fmt, args...) \ + __iwl_dbg((dev), level, false, __func__, fmt, ##args) +#define IWL_DEBUG_LIMIT(m, level, fmt, args...) \ + __iwl_dbg((m)->dev, level, true, __func__, fmt, ##args) + +#ifdef CPTCFG_IWLWIFI_DEBUG +#define iwl_print_hex_dump(m, level, p, len) \ +do { \ + if (iwl_have_debug_level(level)) \ + print_hex_dump(KERN_DEBUG, "iwl data: ", \ + DUMP_PREFIX_OFFSET, 16, 1, p, len, 1); \ +} while (0) +#else +#define iwl_print_hex_dump(m, level, p, len) +#endif /* CPTCFG_IWLWIFI_DEBUG */ + +/* + * To use the debug system: + * + * If you are defining a new debug classification, simply add it to the #define + * list here in the form of + * + * #define IWL_DL_xxxx VALUE + * + * where xxxx should be the name of the classification (for example, WEP). + * + * You then need to either add a IWL_xxxx_DEBUG() macro definition for your + * classification, or use IWL_DEBUG(IWL_DL_xxxx, ...) whenever you want + * to send output to that classification. + * + * The active debug levels can be accessed via files + * + * /sys/module/iwlwifi/parameters/debug + * when CPTCFG_IWLWIFI_DEBUG=y. + * + * /sys/kernel/debug/phy0/iwlwifi/debug/debug_level + * when CPTCFG_IWLWIFI_DEBUGFS=y. + * + */ + +/* 0x0000000F - 0x00000001 */ +#define IWL_DL_INFO 0x00000001 +#define IWL_DL_MAC80211 0x00000002 +#define IWL_DL_HCMD 0x00000004 +#define IWL_DL_STATE 0x00000008 +/* 0x000000F0 - 0x00000010 */ +#define IWL_DL_TE 0x00000020 +#define IWL_DL_EEPROM 0x00000040 +#define IWL_DL_RADIO 0x00000080 +/* 0x00000F00 - 0x00000100 */ +#define IWL_DL_POWER 0x00000100 +#define IWL_DL_TEMP 0x00000200 +#define IWL_DL_SCAN 0x00000800 +/* 0x0000F000 - 0x00001000 */ +#define IWL_DL_ASSOC 0x00001000 +#define IWL_DL_DROP 0x00002000 +#define IWL_DL_COEX 0x00008000 +/* 0x000F0000 - 0x00010000 */ +#define IWL_DL_FW 0x00010000 +#define IWL_DL_RF_KILL 0x00020000 +#define IWL_DL_FW_ERRORS 0x00040000 +#define IWL_DL_LED 0x00080000 +/* 0x00F00000 - 0x00100000 */ +#define IWL_DL_RATE 0x00100000 +#define IWL_DL_CALIB 0x00200000 +#define IWL_DL_WEP 0x00400000 +#define IWL_DL_TX 0x00800000 +/* 0x0F000000 - 0x01000000 */ +#define IWL_DL_RX 0x01000000 +#define IWL_DL_ISR 0x02000000 +#define IWL_DL_HT 0x04000000 +/* 0xF0000000 - 0x10000000 */ +#define IWL_DL_11H 0x10000000 +#define IWL_DL_STATS 0x20000000 +#define IWL_DL_TX_REPLY 0x40000000 +#define IWL_DL_TX_QUEUES 0x80000000 + +#define IWL_DEBUG_INFO(p, f, a...) IWL_DEBUG(p, IWL_DL_INFO, f, ## a) +#define IWL_DEBUG_MAC80211(p, f, a...) IWL_DEBUG(p, IWL_DL_MAC80211, f, ## a) +#define IWL_DEBUG_TEMP(p, f, a...) IWL_DEBUG(p, IWL_DL_TEMP, f, ## a) +#define IWL_DEBUG_SCAN(p, f, a...) IWL_DEBUG(p, IWL_DL_SCAN, f, ## a) +#define IWL_DEBUG_RX(p, f, a...) IWL_DEBUG(p, IWL_DL_RX, f, ## a) +#define IWL_DEBUG_TX(p, f, a...) IWL_DEBUG(p, IWL_DL_TX, f, ## a) +#define IWL_DEBUG_ISR(p, f, a...) IWL_DEBUG(p, IWL_DL_ISR, f, ## a) +#define IWL_DEBUG_LED(p, f, a...) IWL_DEBUG(p, IWL_DL_LED, f, ## a) +#define IWL_DEBUG_WEP(p, f, a...) IWL_DEBUG(p, IWL_DL_WEP, f, ## a) +#define IWL_DEBUG_HC(p, f, a...) IWL_DEBUG(p, IWL_DL_HCMD, f, ## a) +#define IWL_DEBUG_TE(p, f, a...) IWL_DEBUG(p, IWL_DL_TE, f, ## a) +#define IWL_DEBUG_EEPROM(d, f, a...) IWL_DEBUG_DEV(d, IWL_DL_EEPROM, f, ## a) +#define IWL_DEBUG_CALIB(p, f, a...) IWL_DEBUG(p, IWL_DL_CALIB, f, ## a) +#define IWL_DEBUG_FW(p, f, a...) IWL_DEBUG(p, IWL_DL_FW, f, ## a) +#define IWL_DEBUG_RF_KILL(p, f, a...) IWL_DEBUG(p, IWL_DL_RF_KILL, f, ## a) +#define IWL_DEBUG_FW_ERRORS(p, f, a...) IWL_DEBUG(p, IWL_DL_FW_ERRORS, f, ## a) +#define IWL_DEBUG_DROP(p, f, a...) IWL_DEBUG(p, IWL_DL_DROP, f, ## a) +#define IWL_DEBUG_DROP_LIMIT(p, f, a...) \ + IWL_DEBUG_LIMIT(p, IWL_DL_DROP, f, ## a) +#define IWL_DEBUG_COEX(p, f, a...) IWL_DEBUG(p, IWL_DL_COEX, f, ## a) +#define IWL_DEBUG_RATE(p, f, a...) IWL_DEBUG(p, IWL_DL_RATE, f, ## a) +#define IWL_DEBUG_RATE_LIMIT(p, f, a...) \ + IWL_DEBUG_LIMIT(p, IWL_DL_RATE, f, ## a) +#define IWL_DEBUG_ASSOC(p, f, a...) \ + IWL_DEBUG(p, IWL_DL_ASSOC | IWL_DL_INFO, f, ## a) +#define IWL_DEBUG_ASSOC_LIMIT(p, f, a...) \ + IWL_DEBUG_LIMIT(p, IWL_DL_ASSOC | IWL_DL_INFO, f, ## a) +#define IWL_DEBUG_HT(p, f, a...) IWL_DEBUG(p, IWL_DL_HT, f, ## a) +#define IWL_DEBUG_STATS(p, f, a...) IWL_DEBUG(p, IWL_DL_STATS, f, ## a) +#define IWL_DEBUG_STATS_LIMIT(p, f, a...) \ + IWL_DEBUG_LIMIT(p, IWL_DL_STATS, f, ## a) +#define IWL_DEBUG_TX_REPLY(p, f, a...) IWL_DEBUG(p, IWL_DL_TX_REPLY, f, ## a) +#define IWL_DEBUG_TX_QUEUES(p, f, a...) IWL_DEBUG(p, IWL_DL_TX_QUEUES, f, ## a) +#define IWL_DEBUG_RADIO(p, f, a...) IWL_DEBUG(p, IWL_DL_RADIO, f, ## a) +#define IWL_DEBUG_POWER(p, f, a...) IWL_DEBUG(p, IWL_DL_POWER, f, ## a) +#define IWL_DEBUG_11H(p, f, a...) IWL_DEBUG(p, IWL_DL_11H, f, ## a) + +#endif diff --git a/drivers/net/wireless/iwlwifi/iwl-devtrace.c b/drivers/net/wireless/iwlwifi/iwl-devtrace.c new file mode 100644 index 0000000..8f61c71 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-devtrace.c @@ -0,0 +1,50 @@ +/****************************************************************************** + * + * Copyright(c) 2009 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#include + +/* sparse doesn't like tracepoint macros */ +#ifndef __CHECKER__ +#include "iwl-trans.h" + +#define CREATE_TRACE_POINTS +#include "iwl-devtrace.h" + +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_dev_iowrite8); +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_dev_ioread32); +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_dev_iowrite32); +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_dev_rx); +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_dev_tx); +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_dev_ucode_event); +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_dev_ucode_error); +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_dev_ucode_cont_event); +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_dev_ucode_wrap_event); +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_info); +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_warn); +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_crit); +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_err); +EXPORT_TRACEPOINT_SYMBOL(iwlwifi_dbg); +#endif diff --git a/drivers/net/wireless/iwlwifi/iwl-devtrace.h b/drivers/net/wireless/iwlwifi/iwl-devtrace.h new file mode 100644 index 0000000..19c7cdb --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-devtrace.h @@ -0,0 +1,514 @@ +/****************************************************************************** + * + * Copyright(c) 2009 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#if !defined(__IWLWIFI_DEVICE_TRACE) || defined(TRACE_HEADER_MULTI_READ) +#include +#include +#include +#include "iwl-trans.h" +#if !defined(__IWLWIFI_DEVICE_TRACE) +static inline bool iwl_trace_data(struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (void *)skb->data; + + if (ieee80211_is_data(hdr->frame_control)) + return skb->protocol != cpu_to_be16(ETH_P_PAE); + return false; +} + +static inline size_t iwl_rx_trace_len(const struct iwl_trans *trans, + void *rxbuf, size_t len) +{ + struct iwl_cmd_header *cmd = (void *)((u8 *)rxbuf + sizeof(__le32)); + struct ieee80211_hdr *hdr; + + if (cmd->cmd != trans->rx_mpdu_cmd) + return len; + + hdr = (void *)((u8 *)cmd + sizeof(struct iwl_cmd_header) + + trans->rx_mpdu_cmd_hdr_size); + if (!ieee80211_is_data(hdr->frame_control)) + return len; + /* maybe try to identify EAPOL frames? */ + return sizeof(__le32) + sizeof(*cmd) + trans->rx_mpdu_cmd_hdr_size + + ieee80211_hdrlen(hdr->frame_control); +} +#endif + +#define __IWLWIFI_DEVICE_TRACE + +#include +#include +#include "iwl-trans.h" + + +#if !defined(CPTCFG_IWLWIFI_DEVICE_TRACING) || defined(__CHECKER__) +#undef TRACE_EVENT +#define TRACE_EVENT(name, proto, ...) \ +static inline void trace_ ## name(proto) {} +#undef DECLARE_EVENT_CLASS +#define DECLARE_EVENT_CLASS(...) +#undef DEFINE_EVENT +#define DEFINE_EVENT(evt_class, name, proto, ...) \ +static inline void trace_ ## name(proto) {} +#endif + +#define DEV_ENTRY __string(dev, dev_name(dev)) +#define DEV_ASSIGN __assign_str(dev, dev_name(dev)) + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM iwlwifi_io + +TRACE_EVENT(iwlwifi_dev_ioread32, + TP_PROTO(const struct device *dev, u32 offs, u32 val), + TP_ARGS(dev, offs, val), + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, offs) + __field(u32, val) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->offs = offs; + __entry->val = val; + ), + TP_printk("[%s] read io[%#x] = %#x", + __get_str(dev), __entry->offs, __entry->val) +); + +TRACE_EVENT(iwlwifi_dev_iowrite8, + TP_PROTO(const struct device *dev, u32 offs, u8 val), + TP_ARGS(dev, offs, val), + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, offs) + __field(u8, val) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->offs = offs; + __entry->val = val; + ), + TP_printk("[%s] write io[%#x] = %#x)", + __get_str(dev), __entry->offs, __entry->val) +); + +TRACE_EVENT(iwlwifi_dev_iowrite32, + TP_PROTO(const struct device *dev, u32 offs, u32 val), + TP_ARGS(dev, offs, val), + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, offs) + __field(u32, val) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->offs = offs; + __entry->val = val; + ), + TP_printk("[%s] write io[%#x] = %#x)", + __get_str(dev), __entry->offs, __entry->val) +); + +TRACE_EVENT(iwlwifi_dev_iowrite_prph32, + TP_PROTO(const struct device *dev, u32 offs, u32 val), + TP_ARGS(dev, offs, val), + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, offs) + __field(u32, val) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->offs = offs; + __entry->val = val; + ), + TP_printk("[%s] write PRPH[%#x] = %#x)", + __get_str(dev), __entry->offs, __entry->val) +); + +TRACE_EVENT(iwlwifi_dev_ioread_prph32, + TP_PROTO(const struct device *dev, u32 offs, u32 val), + TP_ARGS(dev, offs, val), + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, offs) + __field(u32, val) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->offs = offs; + __entry->val = val; + ), + TP_printk("[%s] read PRPH[%#x] = %#x", + __get_str(dev), __entry->offs, __entry->val) +); + +TRACE_EVENT(iwlwifi_dev_irq, + TP_PROTO(const struct device *dev), + TP_ARGS(dev), + TP_STRUCT__entry( + DEV_ENTRY + ), + TP_fast_assign( + DEV_ASSIGN; + ), + /* TP_printk("") doesn't compile */ + TP_printk("%d", 0) +); + +TRACE_EVENT(iwlwifi_dev_ict_read, + TP_PROTO(const struct device *dev, u32 index, u32 value), + TP_ARGS(dev, index, value), + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, index) + __field(u32, value) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->index = index; + __entry->value = value; + ), + TP_printk("[%s] read ict[%d] = %#.8x", + __get_str(dev), __entry->index, __entry->value) +); + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM iwlwifi_ucode + +TRACE_EVENT(iwlwifi_dev_ucode_cont_event, + TP_PROTO(const struct device *dev, u32 time, u32 data, u32 ev), + TP_ARGS(dev, time, data, ev), + TP_STRUCT__entry( + DEV_ENTRY + + __field(u32, time) + __field(u32, data) + __field(u32, ev) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->time = time; + __entry->data = data; + __entry->ev = ev; + ), + TP_printk("[%s] EVT_LOGT:%010u:0x%08x:%04u", + __get_str(dev), __entry->time, __entry->data, __entry->ev) +); + +TRACE_EVENT(iwlwifi_dev_ucode_wrap_event, + TP_PROTO(const struct device *dev, u32 wraps, u32 n_entry, u32 p_entry), + TP_ARGS(dev, wraps, n_entry, p_entry), + TP_STRUCT__entry( + DEV_ENTRY + + __field(u32, wraps) + __field(u32, n_entry) + __field(u32, p_entry) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->wraps = wraps; + __entry->n_entry = n_entry; + __entry->p_entry = p_entry; + ), + TP_printk("[%s] wraps=#%02d n=0x%X p=0x%X", + __get_str(dev), __entry->wraps, __entry->n_entry, + __entry->p_entry) +); + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM iwlwifi_msg + +#define MAX_MSG_LEN 110 + +DECLARE_EVENT_CLASS(iwlwifi_msg_event, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf), + TP_STRUCT__entry( + __dynamic_array(char, msg, MAX_MSG_LEN) + ), + TP_fast_assign( + WARN_ON_ONCE(vsnprintf(__get_dynamic_array(msg), + MAX_MSG_LEN, vaf->fmt, + *vaf->va) >= MAX_MSG_LEN); + ), + TP_printk("%s", __get_str(msg)) +); + +DEFINE_EVENT(iwlwifi_msg_event, iwlwifi_err, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(iwlwifi_msg_event, iwlwifi_warn, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(iwlwifi_msg_event, iwlwifi_info, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(iwlwifi_msg_event, iwlwifi_crit, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +TRACE_EVENT(iwlwifi_dbg, + TP_PROTO(u32 level, bool in_interrupt, const char *function, + struct va_format *vaf), + TP_ARGS(level, in_interrupt, function, vaf), + TP_STRUCT__entry( + __field(u32, level) + __field(u8, in_interrupt) + __string(function, function) + __dynamic_array(char, msg, MAX_MSG_LEN) + ), + TP_fast_assign( + __entry->level = level; + __entry->in_interrupt = in_interrupt; + __assign_str(function, function); + WARN_ON_ONCE(vsnprintf(__get_dynamic_array(msg), + MAX_MSG_LEN, vaf->fmt, + *vaf->va) >= MAX_MSG_LEN); + ), + TP_printk("%s", __get_str(msg)) +); + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM iwlwifi_data + +TRACE_EVENT(iwlwifi_dev_tx_data, + TP_PROTO(const struct device *dev, + struct sk_buff *skb, + void *data, size_t data_len), + TP_ARGS(dev, skb, data, data_len), + TP_STRUCT__entry( + DEV_ENTRY + + __dynamic_array(u8, data, iwl_trace_data(skb) ? data_len : 0) + ), + TP_fast_assign( + DEV_ASSIGN; + if (iwl_trace_data(skb)) + memcpy(__get_dynamic_array(data), data, data_len); + ), + TP_printk("[%s] TX frame data", __get_str(dev)) +); + +TRACE_EVENT(iwlwifi_dev_rx_data, + TP_PROTO(const struct device *dev, + const struct iwl_trans *trans, + void *rxbuf, size_t len), + TP_ARGS(dev, trans, rxbuf, len), + TP_STRUCT__entry( + DEV_ENTRY + + __dynamic_array(u8, data, + len - iwl_rx_trace_len(trans, rxbuf, len)) + ), + TP_fast_assign( + size_t offs = iwl_rx_trace_len(trans, rxbuf, len); + DEV_ASSIGN; + if (offs < len) + memcpy(__get_dynamic_array(data), + ((u8 *)rxbuf) + offs, len - offs); + ), + TP_printk("[%s] RX frame data", __get_str(dev)) +); + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM iwlwifi + +TRACE_EVENT(iwlwifi_dev_hcmd, + TP_PROTO(const struct device *dev, + struct iwl_host_cmd *cmd, u16 total_size, + struct iwl_cmd_header *hdr), + TP_ARGS(dev, cmd, total_size, hdr), + TP_STRUCT__entry( + DEV_ENTRY + __dynamic_array(u8, hcmd, total_size) + __field(u32, flags) + ), + TP_fast_assign( + int i, offset = sizeof(*hdr); + + DEV_ASSIGN; + __entry->flags = cmd->flags; + memcpy(__get_dynamic_array(hcmd), hdr, sizeof(*hdr)); + + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + if (!cmd->len[i]) + continue; + memcpy((u8 *)__get_dynamic_array(hcmd) + offset, + cmd->data[i], cmd->len[i]); + offset += cmd->len[i]; + } + ), + TP_printk("[%s] hcmd %#.2x (%ssync)", + __get_str(dev), ((u8 *)__get_dynamic_array(hcmd))[0], + __entry->flags & CMD_ASYNC ? "a" : "") +); + +TRACE_EVENT(iwlwifi_dev_rx, + TP_PROTO(const struct device *dev, const struct iwl_trans *trans, + void *rxbuf, size_t len), + TP_ARGS(dev, trans, rxbuf, len), + TP_STRUCT__entry( + DEV_ENTRY + __dynamic_array(u8, rxbuf, iwl_rx_trace_len(trans, rxbuf, len)) + ), + TP_fast_assign( + DEV_ASSIGN; + memcpy(__get_dynamic_array(rxbuf), rxbuf, + iwl_rx_trace_len(trans, rxbuf, len)); + ), + TP_printk("[%s] RX cmd %#.2x", + __get_str(dev), ((u8 *)__get_dynamic_array(rxbuf))[4]) +); + +TRACE_EVENT(iwlwifi_dev_tx, + TP_PROTO(const struct device *dev, struct sk_buff *skb, + void *tfd, size_t tfdlen, + void *buf0, size_t buf0_len, + void *buf1, size_t buf1_len), + TP_ARGS(dev, skb, tfd, tfdlen, buf0, buf0_len, buf1, buf1_len), + TP_STRUCT__entry( + DEV_ENTRY + + __field(size_t, framelen) + __dynamic_array(u8, tfd, tfdlen) + + /* + * Do not insert between or below these items, + * we want to keep the frame together (except + * for the possible padding). + */ + __dynamic_array(u8, buf0, buf0_len) + __dynamic_array(u8, buf1, iwl_trace_data(skb) ? 0 : buf1_len) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->framelen = buf0_len + buf1_len; + memcpy(__get_dynamic_array(tfd), tfd, tfdlen); + memcpy(__get_dynamic_array(buf0), buf0, buf0_len); + if (!iwl_trace_data(skb)) + memcpy(__get_dynamic_array(buf1), buf1, buf1_len); + ), + TP_printk("[%s] TX %.2x (%zu bytes)", + __get_str(dev), ((u8 *)__get_dynamic_array(buf0))[0], + __entry->framelen) +); + +TRACE_EVENT(iwlwifi_dev_ucode_error, + TP_PROTO(const struct device *dev, u32 desc, u32 tsf_low, + u32 data1, u32 data2, u32 line, u32 blink1, + u32 blink2, u32 ilink1, u32 ilink2, u32 bcon_time, + u32 gp1, u32 gp2, u32 gp3, u32 ucode_ver, u32 hw_ver, + u32 brd_ver), + TP_ARGS(dev, desc, tsf_low, data1, data2, line, + blink1, blink2, ilink1, ilink2, bcon_time, gp1, gp2, + gp3, ucode_ver, hw_ver, brd_ver), + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, desc) + __field(u32, tsf_low) + __field(u32, data1) + __field(u32, data2) + __field(u32, line) + __field(u32, blink1) + __field(u32, blink2) + __field(u32, ilink1) + __field(u32, ilink2) + __field(u32, bcon_time) + __field(u32, gp1) + __field(u32, gp2) + __field(u32, gp3) + __field(u32, ucode_ver) + __field(u32, hw_ver) + __field(u32, brd_ver) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->desc = desc; + __entry->tsf_low = tsf_low; + __entry->data1 = data1; + __entry->data2 = data2; + __entry->line = line; + __entry->blink1 = blink1; + __entry->blink2 = blink2; + __entry->ilink1 = ilink1; + __entry->ilink2 = ilink2; + __entry->bcon_time = bcon_time; + __entry->gp1 = gp1; + __entry->gp2 = gp2; + __entry->gp3 = gp3; + __entry->ucode_ver = ucode_ver; + __entry->hw_ver = hw_ver; + __entry->brd_ver = brd_ver; + ), + TP_printk("[%s] #%02d %010u data 0x%08X 0x%08X line %u, " + "blink 0x%05X 0x%05X ilink 0x%05X 0x%05X " + "bcon_tm %010u gp 0x%08X 0x%08X 0x%08X uCode 0x%08X " + "hw 0x%08X brd 0x%08X", + __get_str(dev), __entry->desc, __entry->tsf_low, + __entry->data1, + __entry->data2, __entry->line, __entry->blink1, + __entry->blink2, __entry->ilink1, __entry->ilink2, + __entry->bcon_time, __entry->gp1, __entry->gp2, + __entry->gp3, __entry->ucode_ver, __entry->hw_ver, + __entry->brd_ver) +); + +TRACE_EVENT(iwlwifi_dev_ucode_event, + TP_PROTO(const struct device *dev, u32 time, u32 data, u32 ev), + TP_ARGS(dev, time, data, ev), + TP_STRUCT__entry( + DEV_ENTRY + + __field(u32, time) + __field(u32, data) + __field(u32, ev) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->time = time; + __entry->data = data; + __entry->ev = ev; + ), + TP_printk("[%s] EVT_LOGT:%010u:0x%08x:%04u", + __get_str(dev), __entry->time, __entry->data, __entry->ev) +); +#endif /* __IWLWIFI_DEVICE_TRACE */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE iwl-devtrace +#include diff --git a/drivers/net/wireless/iwlwifi/iwl-drv.c b/drivers/net/wireless/iwlwifi/iwl-drv.c new file mode 100644 index 0000000..17d8b6d --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-drv.c @@ -0,0 +1,1276 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#include +#include +#include +#include +#include + +#include "iwl-drv.h" +#include "iwl-debug.h" +#include "iwl-trans.h" +#include "iwl-op-mode.h" +#include "iwl-agn-hw.h" +#include "iwl-fw.h" +#include "iwl-config.h" +#include "iwl-modparams.h" + +/* private includes */ +#include "iwl-fw-file.h" + +/****************************************************************************** + * + * module boiler plate + * + ******************************************************************************/ + +/* + * module name, copyright, version, etc. + */ +#define DRV_DESCRIPTION "Intel(R) Wireless WiFi driver for Linux" + +#ifdef CPTCFG_IWLWIFI_DEBUG +#define VD "d" +#else +#define VD +#endif + +#define DRV_VERSION IWLWIFI_VERSION VD + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR(DRV_COPYRIGHT " " DRV_AUTHOR); +MODULE_LICENSE("GPL"); + +#ifdef CPTCFG_IWLWIFI_DEBUGFS +static struct dentry *iwl_dbgfs_root; +#endif + +/** + * struct iwl_drv - drv common data + * @list: list of drv structures using this opmode + * @fw: the iwl_fw structure + * @op_mode: the running op_mode + * @trans: transport layer + * @dev: for debug prints only + * @cfg: configuration struct + * @fw_index: firmware revision to try loading + * @firmware_name: composite filename of ucode file to load + * @request_firmware_complete: the firmware has been obtained from user space + */ +struct iwl_drv { + struct list_head list; + struct iwl_fw fw; + + struct iwl_op_mode *op_mode; + struct iwl_trans *trans; + struct device *dev; + const struct iwl_cfg *cfg; + + int fw_index; /* firmware we're trying to load */ + char firmware_name[25]; /* name of firmware file to load */ + + struct completion request_firmware_complete; + +#ifdef CPTCFG_IWLWIFI_DEBUGFS + struct dentry *dbgfs_drv; + struct dentry *dbgfs_trans; + struct dentry *dbgfs_op_mode; +#endif +}; + +enum { + DVM_OP_MODE = 0, + MVM_OP_MODE = 1, +}; + +/* Protects the table contents, i.e. the ops pointer & drv list */ +static struct mutex iwlwifi_opmode_table_mtx; +static struct iwlwifi_opmode_table { + const char *name; /* name: iwldvm, iwlmvm, etc */ + const struct iwl_op_mode_ops *ops; /* pointer to op_mode ops */ + struct list_head drv; /* list of devices using this op_mode */ +} iwlwifi_opmode_table[] = { /* ops set when driver is initialized */ + [DVM_OP_MODE] = { .name = "iwldvm", .ops = NULL }, + [MVM_OP_MODE] = { .name = "iwlmvm", .ops = NULL }, +}; + +/* + * struct fw_sec: Just for the image parsing proccess. + * For the fw storage we are using struct fw_desc. + */ +struct fw_sec { + const void *data; /* the sec data */ + size_t size; /* section size */ + u32 offset; /* offset of writing in the device */ +}; + +static void iwl_free_fw_desc(struct iwl_drv *drv, struct fw_desc *desc) +{ + vfree(desc->data); + desc->data = NULL; + desc->len = 0; +} + +static void iwl_free_fw_img(struct iwl_drv *drv, struct fw_img *img) +{ + int i; + for (i = 0; i < IWL_UCODE_SECTION_MAX; i++) + iwl_free_fw_desc(drv, &img->sec[i]); +} + +static void iwl_dealloc_ucode(struct iwl_drv *drv) +{ + int i; + for (i = 0; i < IWL_UCODE_TYPE_MAX; i++) + iwl_free_fw_img(drv, drv->fw.img + i); +} + +static int iwl_alloc_fw_desc(struct iwl_drv *drv, struct fw_desc *desc, + struct fw_sec *sec) +{ + void *data; + + desc->data = NULL; + + if (!sec || !sec->size) + return -EINVAL; + + data = vmalloc(sec->size); + if (!data) + return -ENOMEM; + + desc->len = sec->size; + desc->offset = sec->offset; + memcpy(data, sec->data, desc->len); + desc->data = data; + + return 0; +} + +static void iwl_req_fw_callback(const struct firmware *ucode_raw, + void *context); + +#define UCODE_EXPERIMENTAL_INDEX 100 +#define UCODE_EXPERIMENTAL_TAG "exp" + +static int iwl_request_firmware(struct iwl_drv *drv, bool first) +{ + const char *name_pre = drv->cfg->fw_name_pre; + char tag[8]; + + if (first) { +#ifdef CPTCFG_IWLWIFI_DEBUG_EXPERIMENTAL_UCODE + drv->fw_index = UCODE_EXPERIMENTAL_INDEX; + strcpy(tag, UCODE_EXPERIMENTAL_TAG); + } else if (drv->fw_index == UCODE_EXPERIMENTAL_INDEX) { +#endif + drv->fw_index = drv->cfg->ucode_api_max; + sprintf(tag, "%d", drv->fw_index); + } else { + drv->fw_index--; + sprintf(tag, "%d", drv->fw_index); + } + + if (drv->fw_index < drv->cfg->ucode_api_min) { + IWL_ERR(drv, "no suitable firmware found!\n"); + return -ENOENT; + } + + sprintf(drv->firmware_name, "%s%s%s", name_pre, tag, ".ucode"); + + IWL_DEBUG_INFO(drv, "attempting to load firmware %s'%s'\n", + (drv->fw_index == UCODE_EXPERIMENTAL_INDEX) + ? "EXPERIMENTAL " : "", + drv->firmware_name); + + return request_firmware_nowait(THIS_MODULE, 1, drv->firmware_name, + drv->trans->dev, + GFP_KERNEL, drv, iwl_req_fw_callback); +} + +struct fw_img_parsing { + struct fw_sec sec[IWL_UCODE_SECTION_MAX]; + int sec_counter; +}; + +/* + * struct fw_sec_parsing: to extract fw section and it's offset from tlv + */ +struct fw_sec_parsing { + __le32 offset; + const u8 data[]; +} __packed; + +/** + * struct iwl_tlv_calib_data - parse the default calib data from TLV + * + * @ucode_type: the uCode to which the following default calib relates. + * @calib: default calibrations. + */ +struct iwl_tlv_calib_data { + __le32 ucode_type; + struct iwl_tlv_calib_ctrl calib; +} __packed; + +struct iwl_firmware_pieces { + struct fw_img_parsing img[IWL_UCODE_TYPE_MAX]; + + u32 init_evtlog_ptr, init_evtlog_size, init_errlog_ptr; + u32 inst_evtlog_ptr, inst_evtlog_size, inst_errlog_ptr; +}; + +/* + * These functions are just to extract uCode section data from the pieces + * structure. + */ +static struct fw_sec *get_sec(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec) +{ + return &pieces->img[type].sec[sec]; +} + +static void set_sec_data(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec, + const void *data) +{ + pieces->img[type].sec[sec].data = data; +} + +static void set_sec_size(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec, + size_t size) +{ + pieces->img[type].sec[sec].size = size; +} + +static size_t get_sec_size(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec) +{ + return pieces->img[type].sec[sec].size; +} + +static void set_sec_offset(struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type, + int sec, + u32 offset) +{ + pieces->img[type].sec[sec].offset = offset; +} + +/* + * Gets uCode section from tlv. + */ +static int iwl_store_ucode_sec(struct iwl_firmware_pieces *pieces, + const void *data, enum iwl_ucode_type type, + int size) +{ + struct fw_img_parsing *img; + struct fw_sec *sec; + struct fw_sec_parsing *sec_parse; + + if (WARN_ON(!pieces || !data || type >= IWL_UCODE_TYPE_MAX)) + return -1; + + sec_parse = (struct fw_sec_parsing *)data; + + img = &pieces->img[type]; + sec = &img->sec[img->sec_counter]; + + sec->offset = le32_to_cpu(sec_parse->offset); + sec->data = sec_parse->data; + sec->size = size - sizeof(sec_parse->offset); + + ++img->sec_counter; + + return 0; +} + +static int iwl_set_default_calib(struct iwl_drv *drv, const u8 *data) +{ + struct iwl_tlv_calib_data *def_calib = + (struct iwl_tlv_calib_data *)data; + u32 ucode_type = le32_to_cpu(def_calib->ucode_type); + if (ucode_type >= IWL_UCODE_TYPE_MAX) { + IWL_ERR(drv, "Wrong ucode_type %u for default calibration.\n", + ucode_type); + return -EINVAL; + } + drv->fw.default_calib[ucode_type].flow_trigger = + def_calib->calib.flow_trigger; + drv->fw.default_calib[ucode_type].event_trigger = + def_calib->calib.event_trigger; + + return 0; +} + +static int iwl_parse_v1_v2_firmware(struct iwl_drv *drv, + const struct firmware *ucode_raw, + struct iwl_firmware_pieces *pieces) +{ + struct iwl_ucode_header *ucode = (void *)ucode_raw->data; + u32 api_ver, hdr_size, build; + char buildstr[25]; + const u8 *src; + + drv->fw.ucode_ver = le32_to_cpu(ucode->ver); + api_ver = IWL_UCODE_API(drv->fw.ucode_ver); + + switch (api_ver) { + default: + hdr_size = 28; + if (ucode_raw->size < hdr_size) { + IWL_ERR(drv, "File size too small!\n"); + return -EINVAL; + } + build = le32_to_cpu(ucode->u.v2.build); + set_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST, + le32_to_cpu(ucode->u.v2.inst_size)); + set_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA, + le32_to_cpu(ucode->u.v2.data_size)); + set_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST, + le32_to_cpu(ucode->u.v2.init_size)); + set_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA, + le32_to_cpu(ucode->u.v2.init_data_size)); + src = ucode->u.v2.data; + break; + case 0: + case 1: + case 2: + hdr_size = 24; + if (ucode_raw->size < hdr_size) { + IWL_ERR(drv, "File size too small!\n"); + return -EINVAL; + } + build = 0; + set_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST, + le32_to_cpu(ucode->u.v1.inst_size)); + set_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA, + le32_to_cpu(ucode->u.v1.data_size)); + set_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST, + le32_to_cpu(ucode->u.v1.init_size)); + set_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA, + le32_to_cpu(ucode->u.v1.init_data_size)); + src = ucode->u.v1.data; + break; + } + + if (build) + sprintf(buildstr, " build %u%s", build, + (drv->fw_index == UCODE_EXPERIMENTAL_INDEX) + ? " (EXP)" : ""); + else + buildstr[0] = '\0'; + + snprintf(drv->fw.fw_version, + sizeof(drv->fw.fw_version), + "%u.%u.%u.%u%s", + IWL_UCODE_MAJOR(drv->fw.ucode_ver), + IWL_UCODE_MINOR(drv->fw.ucode_ver), + IWL_UCODE_API(drv->fw.ucode_ver), + IWL_UCODE_SERIAL(drv->fw.ucode_ver), + buildstr); + + /* Verify size of file vs. image size info in file's header */ + + if (ucode_raw->size != hdr_size + + get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST) + + get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA) + + get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST) + + get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA)) { + + IWL_ERR(drv, + "uCode file size %d does not match expected size\n", + (int)ucode_raw->size); + return -EINVAL; + } + + + set_sec_data(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST, src); + src += get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST); + set_sec_offset(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST, + IWLAGN_RTC_INST_LOWER_BOUND); + set_sec_data(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA, src); + src += get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA); + set_sec_offset(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA, + IWLAGN_RTC_DATA_LOWER_BOUND); + set_sec_data(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST, src); + src += get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST); + set_sec_offset(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST, + IWLAGN_RTC_INST_LOWER_BOUND); + set_sec_data(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA, src); + src += get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA); + set_sec_offset(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA, + IWLAGN_RTC_DATA_LOWER_BOUND); + return 0; +} + +static int iwl_parse_tlv_firmware(struct iwl_drv *drv, + const struct firmware *ucode_raw, + struct iwl_firmware_pieces *pieces, + struct iwl_ucode_capabilities *capa) +{ + struct iwl_tlv_ucode_header *ucode = (void *)ucode_raw->data; + struct iwl_ucode_tlv *tlv; + size_t len = ucode_raw->size; + const u8 *data; + u32 tlv_len; + enum iwl_ucode_tlv_type tlv_type; + const u8 *tlv_data; + char buildstr[25]; + u32 build; + + if (len < sizeof(*ucode)) { + IWL_ERR(drv, "uCode has invalid length: %zd\n", len); + return -EINVAL; + } + + if (ucode->magic != cpu_to_le32(IWL_TLV_UCODE_MAGIC)) { + IWL_ERR(drv, "invalid uCode magic: 0X%x\n", + le32_to_cpu(ucode->magic)); + return -EINVAL; + } + + drv->fw.ucode_ver = le32_to_cpu(ucode->ver); + build = le32_to_cpu(ucode->build); + + if (build) + sprintf(buildstr, " build %u%s", build, + (drv->fw_index == UCODE_EXPERIMENTAL_INDEX) + ? " (EXP)" : ""); + else + buildstr[0] = '\0'; + + snprintf(drv->fw.fw_version, + sizeof(drv->fw.fw_version), + "%u.%u.%u.%u%s", + IWL_UCODE_MAJOR(drv->fw.ucode_ver), + IWL_UCODE_MINOR(drv->fw.ucode_ver), + IWL_UCODE_API(drv->fw.ucode_ver), + IWL_UCODE_SERIAL(drv->fw.ucode_ver), + buildstr); + + data = ucode->data; + + len -= sizeof(*ucode); + + while (len >= sizeof(*tlv)) { + len -= sizeof(*tlv); + tlv = (void *)data; + + tlv_len = le32_to_cpu(tlv->length); + tlv_type = le32_to_cpu(tlv->type); + tlv_data = tlv->data; + + if (len < tlv_len) { + IWL_ERR(drv, "invalid TLV len: %zd/%u\n", + len, tlv_len); + return -EINVAL; + } + len -= ALIGN(tlv_len, 4); + data += sizeof(*tlv) + ALIGN(tlv_len, 4); + + switch (tlv_type) { + case IWL_UCODE_TLV_INST: + set_sec_data(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_INST, tlv_data); + set_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_INST, tlv_len); + set_sec_offset(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_INST, + IWLAGN_RTC_INST_LOWER_BOUND); + break; + case IWL_UCODE_TLV_DATA: + set_sec_data(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA, tlv_data); + set_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA, tlv_len); + set_sec_offset(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA, + IWLAGN_RTC_DATA_LOWER_BOUND); + break; + case IWL_UCODE_TLV_INIT: + set_sec_data(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_INST, tlv_data); + set_sec_size(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_INST, tlv_len); + set_sec_offset(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_INST, + IWLAGN_RTC_INST_LOWER_BOUND); + break; + case IWL_UCODE_TLV_INIT_DATA: + set_sec_data(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_DATA, tlv_data); + set_sec_size(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_DATA, tlv_len); + set_sec_offset(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_DATA, + IWLAGN_RTC_DATA_LOWER_BOUND); + break; + case IWL_UCODE_TLV_BOOT: + IWL_ERR(drv, "Found unexpected BOOT ucode\n"); + break; + case IWL_UCODE_TLV_PROBE_MAX_LEN: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + capa->max_probe_length = + le32_to_cpup((__le32 *)tlv_data); + break; + case IWL_UCODE_TLV_PAN: + if (tlv_len) + goto invalid_tlv_len; + capa->flags |= IWL_UCODE_TLV_FLAGS_PAN; + break; + case IWL_UCODE_TLV_FLAGS: + /* must be at least one u32 */ + if (tlv_len < sizeof(u32)) + goto invalid_tlv_len; + /* and a proper number of u32s */ + if (tlv_len % sizeof(u32)) + goto invalid_tlv_len; + /* + * This driver only reads the first u32 as + * right now no more features are defined, + * if that changes then either the driver + * will not work with the new firmware, or + * it'll not take advantage of new features. + */ + capa->flags = le32_to_cpup((__le32 *)tlv_data); + break; + case IWL_UCODE_TLV_INIT_EVTLOG_PTR: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->init_evtlog_ptr = + le32_to_cpup((__le32 *)tlv_data); + break; + case IWL_UCODE_TLV_INIT_EVTLOG_SIZE: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->init_evtlog_size = + le32_to_cpup((__le32 *)tlv_data); + break; + case IWL_UCODE_TLV_INIT_ERRLOG_PTR: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->init_errlog_ptr = + le32_to_cpup((__le32 *)tlv_data); + break; + case IWL_UCODE_TLV_RUNT_EVTLOG_PTR: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->inst_evtlog_ptr = + le32_to_cpup((__le32 *)tlv_data); + break; + case IWL_UCODE_TLV_RUNT_EVTLOG_SIZE: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->inst_evtlog_size = + le32_to_cpup((__le32 *)tlv_data); + break; + case IWL_UCODE_TLV_RUNT_ERRLOG_PTR: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + pieces->inst_errlog_ptr = + le32_to_cpup((__le32 *)tlv_data); + break; + case IWL_UCODE_TLV_ENHANCE_SENS_TBL: + if (tlv_len) + goto invalid_tlv_len; + drv->fw.enhance_sensitivity_table = true; + break; + case IWL_UCODE_TLV_WOWLAN_INST: + set_sec_data(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_INST, tlv_data); + set_sec_size(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_INST, tlv_len); + set_sec_offset(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_INST, + IWLAGN_RTC_INST_LOWER_BOUND); + break; + case IWL_UCODE_TLV_WOWLAN_DATA: + set_sec_data(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_DATA, tlv_data); + set_sec_size(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_DATA, tlv_len); + set_sec_offset(pieces, IWL_UCODE_WOWLAN, + IWL_UCODE_SECTION_DATA, + IWLAGN_RTC_DATA_LOWER_BOUND); + break; + case IWL_UCODE_TLV_PHY_CALIBRATION_SIZE: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + capa->standard_phy_calibration_size = + le32_to_cpup((__le32 *)tlv_data); + break; + case IWL_UCODE_TLV_SEC_RT: + iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_REGULAR, + tlv_len); + drv->fw.mvm_fw = true; + break; + case IWL_UCODE_TLV_SEC_INIT: + iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_INIT, + tlv_len); + drv->fw.mvm_fw = true; + break; + case IWL_UCODE_TLV_SEC_WOWLAN: + iwl_store_ucode_sec(pieces, tlv_data, IWL_UCODE_WOWLAN, + tlv_len); + drv->fw.mvm_fw = true; + break; + case IWL_UCODE_TLV_DEF_CALIB: + if (tlv_len != sizeof(struct iwl_tlv_calib_data)) + goto invalid_tlv_len; + if (iwl_set_default_calib(drv, tlv_data)) + goto tlv_error; + break; + case IWL_UCODE_TLV_PHY_SKU: + if (tlv_len != sizeof(u32)) + goto invalid_tlv_len; + drv->fw.phy_config = le32_to_cpup((__le32 *)tlv_data); + break; + default: + IWL_DEBUG_INFO(drv, "unknown TLV: %d\n", tlv_type); + break; + } + } + + if (len) { + IWL_ERR(drv, "invalid TLV after parsing: %zd\n", len); + iwl_print_hex_dump(drv, IWL_DL_FW, (u8 *)data, len); + return -EINVAL; + } + + return 0; + + invalid_tlv_len: + IWL_ERR(drv, "TLV %d has invalid size: %u\n", tlv_type, tlv_len); + tlv_error: + iwl_print_hex_dump(drv, IWL_DL_FW, tlv_data, tlv_len); + + return -EINVAL; +} + +static int iwl_alloc_ucode(struct iwl_drv *drv, + struct iwl_firmware_pieces *pieces, + enum iwl_ucode_type type) +{ + int i; + for (i = 0; + i < IWL_UCODE_SECTION_MAX && get_sec_size(pieces, type, i); + i++) + if (iwl_alloc_fw_desc(drv, &(drv->fw.img[type].sec[i]), + get_sec(pieces, type, i))) + return -ENOMEM; + return 0; +} + +static int validate_sec_sizes(struct iwl_drv *drv, + struct iwl_firmware_pieces *pieces, + const struct iwl_cfg *cfg) +{ + IWL_DEBUG_INFO(drv, "f/w package hdr runtime inst size = %Zd\n", + get_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_INST)); + IWL_DEBUG_INFO(drv, "f/w package hdr runtime data size = %Zd\n", + get_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA)); + IWL_DEBUG_INFO(drv, "f/w package hdr init inst size = %Zd\n", + get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST)); + IWL_DEBUG_INFO(drv, "f/w package hdr init data size = %Zd\n", + get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA)); + + /* Verify that uCode images will fit in card's SRAM. */ + if (get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_INST) > + cfg->max_inst_size) { + IWL_ERR(drv, "uCode instr len %Zd too large to fit in\n", + get_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_INST)); + return -1; + } + + if (get_sec_size(pieces, IWL_UCODE_REGULAR, IWL_UCODE_SECTION_DATA) > + cfg->max_data_size) { + IWL_ERR(drv, "uCode data len %Zd too large to fit in\n", + get_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA)); + return -1; + } + + if (get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_INST) > + cfg->max_inst_size) { + IWL_ERR(drv, "uCode init instr len %Zd too large to fit in\n", + get_sec_size(pieces, IWL_UCODE_INIT, + IWL_UCODE_SECTION_INST)); + return -1; + } + + if (get_sec_size(pieces, IWL_UCODE_INIT, IWL_UCODE_SECTION_DATA) > + cfg->max_data_size) { + IWL_ERR(drv, "uCode init data len %Zd too large to fit in\n", + get_sec_size(pieces, IWL_UCODE_REGULAR, + IWL_UCODE_SECTION_DATA)); + return -1; + } + return 0; +} + +static struct iwl_op_mode * +_iwl_op_mode_start(struct iwl_drv *drv, struct iwlwifi_opmode_table *op) +{ + const struct iwl_op_mode_ops *ops = op->ops; + struct dentry *dbgfs_dir = NULL; + struct iwl_op_mode *op_mode = NULL; + +#ifdef CPTCFG_IWLWIFI_DEBUGFS + drv->dbgfs_op_mode = debugfs_create_dir(op->name, + drv->dbgfs_drv); + if (!drv->dbgfs_op_mode) { + IWL_ERR(drv, + "failed to create opmode debugfs directory\n"); + return op_mode; + } + dbgfs_dir = drv->dbgfs_op_mode; +#endif + + op_mode = ops->start(drv->trans, drv->cfg, &drv->fw, dbgfs_dir); + +#ifdef CPTCFG_IWLWIFI_DEBUGFS + if (!op_mode) { + debugfs_remove_recursive(drv->dbgfs_op_mode); + drv->dbgfs_op_mode = NULL; + } +#endif + + return op_mode; +} + +static void _iwl_op_mode_stop(struct iwl_drv *drv) +{ + /* op_mode can be NULL if its start failed */ + if (drv->op_mode) { + iwl_op_mode_stop(drv->op_mode); + drv->op_mode = NULL; + +#ifdef CPTCFG_IWLWIFI_DEBUGFS + debugfs_remove_recursive(drv->dbgfs_op_mode); + drv->dbgfs_op_mode = NULL; +#endif + } +} + +/** + * iwl_req_fw_callback - callback when firmware was loaded + * + * If loaded successfully, copies the firmware into buffers + * for the card to fetch (via DMA). + */ +static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context) +{ + struct iwl_drv *drv = context; + struct iwl_fw *fw = &drv->fw; + struct iwl_ucode_header *ucode; + struct iwlwifi_opmode_table *op; + int err; + struct iwl_firmware_pieces pieces; + const unsigned int api_max = drv->cfg->ucode_api_max; + unsigned int api_ok = drv->cfg->ucode_api_ok; + const unsigned int api_min = drv->cfg->ucode_api_min; + u32 api_ver; + int i; + bool load_module = false; + + fw->ucode_capa.max_probe_length = 200; + fw->ucode_capa.standard_phy_calibration_size = + IWL_DEFAULT_STANDARD_PHY_CALIBRATE_TBL_SIZE; + + if (!api_ok) + api_ok = api_max; + + memset(&pieces, 0, sizeof(pieces)); + + if (!ucode_raw) { + if (drv->fw_index <= api_ok) + IWL_ERR(drv, + "request for firmware file '%s' failed.\n", + drv->firmware_name); + goto try_again; + } + + IWL_DEBUG_INFO(drv, "Loaded firmware file '%s' (%zd bytes).\n", + drv->firmware_name, ucode_raw->size); + + /* Make sure that we got at least the API version number */ + if (ucode_raw->size < 4) { + IWL_ERR(drv, "File size way too small!\n"); + goto try_again; + } + + /* Data from ucode file: header followed by uCode images */ + ucode = (struct iwl_ucode_header *)ucode_raw->data; + + if (ucode->ver) + err = iwl_parse_v1_v2_firmware(drv, ucode_raw, &pieces); + else + err = iwl_parse_tlv_firmware(drv, ucode_raw, &pieces, + &fw->ucode_capa); + + if (err) + goto try_again; + + api_ver = IWL_UCODE_API(drv->fw.ucode_ver); + + /* + * api_ver should match the api version forming part of the + * firmware filename ... but we don't check for that and only rely + * on the API version read from firmware header from here on forward + */ + /* no api version check required for experimental uCode */ + if (drv->fw_index != UCODE_EXPERIMENTAL_INDEX) { + if (api_ver < api_min || api_ver > api_max) { + IWL_ERR(drv, + "Driver unable to support your firmware API. " + "Driver supports v%u, firmware is v%u.\n", + api_max, api_ver); + goto try_again; + } + + if (api_ver < api_ok) { + if (api_ok != api_max) + IWL_ERR(drv, "Firmware has old API version, " + "expected v%u through v%u, got v%u.\n", + api_ok, api_max, api_ver); + else + IWL_ERR(drv, "Firmware has old API version, " + "expected v%u, got v%u.\n", + api_max, api_ver); + IWL_ERR(drv, "New firmware can be obtained from " + "http://www.intellinuxwireless.org/.\n"); + } + } + + /* + * In mvm uCode there is no difference between data and instructions + * sections. + */ + if (!fw->mvm_fw && validate_sec_sizes(drv, &pieces, drv->cfg)) + goto try_again; + + /* Allocate ucode buffers for card's bus-master loading ... */ + + /* Runtime instructions and 2 copies of data: + * 1) unmodified from disk + * 2) backup cache for save/restore during power-downs */ + for (i = 0; i < IWL_UCODE_TYPE_MAX; i++) + if (iwl_alloc_ucode(drv, &pieces, i)) + goto out_free_fw; + + /* Now that we can no longer fail, copy information */ + + /* + * The (size - 16) / 12 formula is based on the information recorded + * for each event, which is of mode 1 (including timestamp) for all + * new microcodes that include this information. + */ + fw->init_evtlog_ptr = pieces.init_evtlog_ptr; + if (pieces.init_evtlog_size) + fw->init_evtlog_size = (pieces.init_evtlog_size - 16)/12; + else + fw->init_evtlog_size = + drv->cfg->base_params->max_event_log_size; + fw->init_errlog_ptr = pieces.init_errlog_ptr; + fw->inst_evtlog_ptr = pieces.inst_evtlog_ptr; + if (pieces.inst_evtlog_size) + fw->inst_evtlog_size = (pieces.inst_evtlog_size - 16)/12; + else + fw->inst_evtlog_size = + drv->cfg->base_params->max_event_log_size; + fw->inst_errlog_ptr = pieces.inst_errlog_ptr; + + /* + * figure out the offset of chain noise reset and gain commands + * base on the size of standard phy calibration commands table size + */ + if (fw->ucode_capa.standard_phy_calibration_size > + IWL_MAX_PHY_CALIBRATE_TBL_SIZE) + fw->ucode_capa.standard_phy_calibration_size = + IWL_MAX_STANDARD_PHY_CALIBRATE_TBL_SIZE; + + /* We have our copies now, allow OS release its copies */ + release_firmware(ucode_raw); + + mutex_lock(&iwlwifi_opmode_table_mtx); + if (fw->mvm_fw) + op = &iwlwifi_opmode_table[MVM_OP_MODE]; + else + op = &iwlwifi_opmode_table[DVM_OP_MODE]; + + IWL_INFO(drv, "loaded firmware version %s op_mode %s\n", + drv->fw.fw_version, op->name); + + /* add this device to the list of devices using this op_mode */ + list_add_tail(&drv->list, &op->drv); + + if (op->ops) { + drv->op_mode = _iwl_op_mode_start(drv, op); + + if (!drv->op_mode) { + mutex_unlock(&iwlwifi_opmode_table_mtx); + goto out_unbind; + } + } else { + load_module = true; + } + mutex_unlock(&iwlwifi_opmode_table_mtx); + + /* + * Complete the firmware request last so that + * a driver unbind (stop) doesn't run while we + * are doing the start() above. + */ + complete(&drv->request_firmware_complete); + + /* + * Load the module last so we don't block anything + * else from proceeding if the module fails to load + * or hangs loading. + */ + if (load_module) { + err = request_module("%s", op->name); +#ifdef CPTCFG_IWLWIFI_OPMODE_MODULAR + if (err) + IWL_ERR(drv, + "failed to load module %s (error %d), is dynamic loading enabled?\n", + op->name, err); +#endif + } + return; + + try_again: + /* try next, if any */ + release_firmware(ucode_raw); + if (iwl_request_firmware(drv, false)) + goto out_unbind; + return; + + out_free_fw: + IWL_ERR(drv, "failed to allocate pci memory\n"); + iwl_dealloc_ucode(drv); + release_firmware(ucode_raw); + out_unbind: + complete(&drv->request_firmware_complete); + device_release_driver(drv->trans->dev); +} + +struct iwl_drv *iwl_drv_start(struct iwl_trans *trans, + const struct iwl_cfg *cfg) +{ + struct iwl_drv *drv; + int ret; + + drv = kzalloc(sizeof(*drv), GFP_KERNEL); + if (!drv) + return NULL; + + drv->trans = trans; + drv->dev = trans->dev; + drv->cfg = cfg; + + init_completion(&drv->request_firmware_complete); + INIT_LIST_HEAD(&drv->list); + +#ifdef CPTCFG_IWLWIFI_DEBUGFS + /* Create the device debugfs entries. */ + drv->dbgfs_drv = debugfs_create_dir(dev_name(trans->dev), + iwl_dbgfs_root); + + if (!drv->dbgfs_drv) { + IWL_ERR(drv, "failed to create debugfs directory\n"); + ret = -ENOMEM; + goto err_free_drv; + } + + /* Create transport layer debugfs dir */ + drv->trans->dbgfs_dir = debugfs_create_dir("trans", drv->dbgfs_drv); + + if (!drv->trans->dbgfs_dir) { + IWL_ERR(drv, "failed to create transport debugfs directory\n"); + ret = -ENOMEM; + goto err_free_dbgfs; + } +#endif + + ret = iwl_request_firmware(drv, true); + if (ret) { + IWL_ERR(trans, "Couldn't request the fw\n"); + goto err_fw; + } + + return drv; + +err_fw: +#ifdef CPTCFG_IWLWIFI_DEBUGFS +err_free_dbgfs: + debugfs_remove_recursive(drv->dbgfs_drv); +err_free_drv: +#endif + kfree(drv); + + return ERR_PTR(ret); +} + +void iwl_drv_stop(struct iwl_drv *drv) +{ + wait_for_completion(&drv->request_firmware_complete); + + _iwl_op_mode_stop(drv); + + iwl_dealloc_ucode(drv); + + mutex_lock(&iwlwifi_opmode_table_mtx); + /* + * List is empty (this item wasn't added) + * when firmware loading failed -- in that + * case we can't remove it from any list. + */ + if (!list_empty(&drv->list)) + list_del(&drv->list); + mutex_unlock(&iwlwifi_opmode_table_mtx); + +#ifdef CPTCFG_IWLWIFI_DEBUGFS + debugfs_remove_recursive(drv->dbgfs_drv); +#endif + + kfree(drv); +} + + +/* shared module parameters */ +struct iwl_mod_params iwlwifi_mod_params = { + .restart_fw = true, + .plcp_check = true, + .bt_coex_active = true, + .power_level = IWL_POWER_INDEX_1, + .bt_ch_announce = true, + .auto_agg = true, + .wd_disable = true, + /* the rest are 0 by default */ +}; +IWL_EXPORT_SYMBOL(iwlwifi_mod_params); + +int iwl_opmode_register(const char *name, const struct iwl_op_mode_ops *ops) +{ + int i; + struct iwl_drv *drv; + struct iwlwifi_opmode_table *op; + + mutex_lock(&iwlwifi_opmode_table_mtx); + for (i = 0; i < ARRAY_SIZE(iwlwifi_opmode_table); i++) { + op = &iwlwifi_opmode_table[i]; + if (strcmp(op->name, name)) + continue; + op->ops = ops; + /* TODO: need to handle exceptional case */ + list_for_each_entry(drv, &op->drv, list) + drv->op_mode = _iwl_op_mode_start(drv, op); + + mutex_unlock(&iwlwifi_opmode_table_mtx); + return 0; + } + mutex_unlock(&iwlwifi_opmode_table_mtx); + return -EIO; +} +IWL_EXPORT_SYMBOL(iwl_opmode_register); + +void iwl_opmode_deregister(const char *name) +{ + int i; + struct iwl_drv *drv; + + mutex_lock(&iwlwifi_opmode_table_mtx); + for (i = 0; i < ARRAY_SIZE(iwlwifi_opmode_table); i++) { + if (strcmp(iwlwifi_opmode_table[i].name, name)) + continue; + iwlwifi_opmode_table[i].ops = NULL; + + /* call the stop routine for all devices */ + list_for_each_entry(drv, &iwlwifi_opmode_table[i].drv, list) + _iwl_op_mode_stop(drv); + + mutex_unlock(&iwlwifi_opmode_table_mtx); + return; + } + mutex_unlock(&iwlwifi_opmode_table_mtx); +} +IWL_EXPORT_SYMBOL(iwl_opmode_deregister); + +static int __init iwl_drv_init(void) +{ + int i; + + mutex_init(&iwlwifi_opmode_table_mtx); + + for (i = 0; i < ARRAY_SIZE(iwlwifi_opmode_table); i++) + INIT_LIST_HEAD(&iwlwifi_opmode_table[i].drv); + + pr_info(DRV_DESCRIPTION ", " DRV_VERSION "\n"); + pr_info(DRV_COPYRIGHT "\n"); + +#ifdef CPTCFG_IWLWIFI_DEBUGFS + /* Create the root of iwlwifi debugfs subsystem. */ + iwl_dbgfs_root = debugfs_create_dir(DRV_NAME, NULL); + + if (!iwl_dbgfs_root) + return -EFAULT; +#endif + + return iwl_pci_register_driver(); +} +module_init(iwl_drv_init); + +static void __exit iwl_drv_exit(void) +{ + iwl_pci_unregister_driver(); + +#ifdef CPTCFG_IWLWIFI_DEBUGFS + debugfs_remove_recursive(iwl_dbgfs_root); +#endif +} +module_exit(iwl_drv_exit); + +#ifdef CPTCFG_IWLWIFI_DEBUG +module_param_named(debug, iwlwifi_mod_params.debug_level, uint, + S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "debug output mask"); +#endif + +module_param_named(swcrypto, iwlwifi_mod_params.sw_crypto, int, S_IRUGO); +MODULE_PARM_DESC(swcrypto, "using crypto in software (default 0 [hardware])"); +module_param_named(11n_disable, iwlwifi_mod_params.disable_11n, uint, S_IRUGO); +MODULE_PARM_DESC(11n_disable, + "disable 11n functionality, bitmap: 1: full, 2: agg TX, 4: agg RX"); +module_param_named(amsdu_size_8K, iwlwifi_mod_params.amsdu_size_8K, + int, S_IRUGO); +MODULE_PARM_DESC(amsdu_size_8K, "enable 8K amsdu size (default 0)"); +module_param_named(fw_restart, iwlwifi_mod_params.restart_fw, bool, S_IRUGO); +MODULE_PARM_DESC(fw_restart, "restart firmware in case of error (default true)"); + +module_param_named(antenna_coupling, iwlwifi_mod_params.ant_coupling, + int, S_IRUGO); +MODULE_PARM_DESC(antenna_coupling, + "specify antenna coupling in dB (defualt: 0 dB)"); + +module_param_named(bt_ch_inhibition, iwlwifi_mod_params.bt_ch_announce, + bool, S_IRUGO); +MODULE_PARM_DESC(bt_ch_inhibition, + "Enable BT channel inhibition (default: enable)"); + +module_param_named(plcp_check, iwlwifi_mod_params.plcp_check, bool, S_IRUGO); +MODULE_PARM_DESC(plcp_check, "Check plcp health (default: 1 [enabled])"); + +module_param_named(wd_disable, iwlwifi_mod_params.wd_disable, int, S_IRUGO); +MODULE_PARM_DESC(wd_disable, + "Disable stuck queue watchdog timer 0=system default, " + "1=disable, 2=enable (default: 0)"); + +/* + * set bt_coex_active to true, uCode will do kill/defer + * every time the priority line is asserted (BT is sending signals on the + * priority line in the PCIx). + * set bt_coex_active to false, uCode will ignore the BT activity and + * perform the normal operation + * + * User might experience transmit issue on some platform due to WiFi/BT + * co-exist problem. The possible behaviors are: + * Able to scan and finding all the available AP + * Not able to associate with any AP + * On those platforms, WiFi communication can be restored by set + * "bt_coex_active" module parameter to "false" + * + * default: bt_coex_active = true (BT_COEX_ENABLE) + */ +module_param_named(bt_coex_active, iwlwifi_mod_params.bt_coex_active, + bool, S_IRUGO); +MODULE_PARM_DESC(bt_coex_active, "enable wifi/bt co-exist (default: enable)"); + +module_param_named(led_mode, iwlwifi_mod_params.led_mode, int, S_IRUGO); +MODULE_PARM_DESC(led_mode, "0=system default, " + "1=On(RF On)/Off(RF Off), 2=blinking, 3=Off (default: 0)"); + +module_param_named(power_save, iwlwifi_mod_params.power_save, + bool, S_IRUGO); +MODULE_PARM_DESC(power_save, + "enable WiFi power management (default: disable)"); + +module_param_named(power_level, iwlwifi_mod_params.power_level, + int, S_IRUGO); +MODULE_PARM_DESC(power_level, + "default power save level (range from 1 - 5, default: 1)"); + +module_param_named(auto_agg, iwlwifi_mod_params.auto_agg, + bool, S_IRUGO); +MODULE_PARM_DESC(auto_agg, + "enable agg w/o check traffic load (default: enable)"); diff --git a/drivers/net/wireless/iwlwifi/iwl-drv.h b/drivers/net/wireless/iwlwifi/iwl-drv.h new file mode 100644 index 0000000..6e25bee --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-drv.h @@ -0,0 +1,141 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef __iwl_drv_h__ +#define __iwl_drv_h__ + +#include + +/* for all modules */ +#define DRV_NAME "iwlwifi" +#define IWLWIFI_VERSION "in-tree:" +#define DRV_COPYRIGHT "Copyright(c) 2003-2013 Intel Corporation" +#define DRV_AUTHOR "" + + +/** + * DOC: Driver system flows - drv component + * + * This component implements the system flows such as bus enumeration, bus + * removal. Bus dependent parts of system flows (such as iwl_pci_probe) are in + * bus specific files (transport files). This is the code that is common among + * different buses. + * + * This component is also in charge of managing the several implementations of + * the wifi flows: it will allow to have several fw API implementation. These + * different implementations will differ in the way they implement mac80211's + * handlers too. + + * The init flow wrt to the drv component looks like this: + * 1) The bus specific component is called from module_init + * 2) The bus specific component registers the bus driver + * 3) The bus driver calls the probe function + * 4) The bus specific component configures the bus + * 5) The bus specific component calls to the drv bus agnostic part + * (iwl_drv_start) + * 6) iwl_drv_start fetches the fw ASYNC, iwl_req_fw_callback + * 7) iwl_req_fw_callback parses the fw file + * 8) iwl_req_fw_callback starts the wifi implementation to matches the fw + */ + +struct iwl_drv; +struct iwl_trans; +struct iwl_cfg; +/** + * iwl_drv_start - start the drv + * + * @trans_ops: the ops of the transport + * @cfg: device specific constants / virtual functions + * + * starts the driver: fetches the firmware. This should be called by bus + * specific system flows implementations. For example, the bus specific probe + * function should do bus related operations only, and then call to this + * function. It returns the driver object or %NULL if an error occured. + */ +struct iwl_drv *iwl_drv_start(struct iwl_trans *trans, + const struct iwl_cfg *cfg); + +/** + * iwl_drv_stop - stop the drv + * + * @drv: + * + * Stop the driver. This should be called by bus specific system flows + * implementations. For example, the bus specific remove function should first + * call this function and then do the bus related operations only. + */ +void iwl_drv_stop(struct iwl_drv *drv); + +/* + * exported symbol management + * + * The driver can be split into multiple modules, in which case some symbols + * must be exported for the sub-modules. However, if it's not split and + * everything is built-in, then we can avoid that. + */ +#ifdef CPTCFG_IWLWIFI_OPMODE_MODULAR +#define IWL_EXPORT_SYMBOL(sym) EXPORT_SYMBOL_GPL(sym) +#else +#define IWL_EXPORT_SYMBOL(sym) +#endif + +#endif /* __iwl_drv_h__ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-eeprom-parse.c b/drivers/net/wireless/iwlwifi/iwl-eeprom-parse.c new file mode 100644 index 0000000..600c9fd --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-eeprom-parse.c @@ -0,0 +1,932 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ +#include +#include +#include +#include "iwl-drv.h" +#include "iwl-modparams.h" +#include "iwl-eeprom-parse.h" + +/* EEPROM offset definitions */ + +/* indirect access definitions */ +#define ADDRESS_MSK 0x0000FFFF +#define INDIRECT_TYPE_MSK 0x000F0000 +#define INDIRECT_HOST 0x00010000 +#define INDIRECT_GENERAL 0x00020000 +#define INDIRECT_REGULATORY 0x00030000 +#define INDIRECT_CALIBRATION 0x00040000 +#define INDIRECT_PROCESS_ADJST 0x00050000 +#define INDIRECT_OTHERS 0x00060000 +#define INDIRECT_TXP_LIMIT 0x00070000 +#define INDIRECT_TXP_LIMIT_SIZE 0x00080000 +#define INDIRECT_ADDRESS 0x00100000 + +/* corresponding link offsets in EEPROM */ +#define EEPROM_LINK_HOST (2*0x64) +#define EEPROM_LINK_GENERAL (2*0x65) +#define EEPROM_LINK_REGULATORY (2*0x66) +#define EEPROM_LINK_CALIBRATION (2*0x67) +#define EEPROM_LINK_PROCESS_ADJST (2*0x68) +#define EEPROM_LINK_OTHERS (2*0x69) +#define EEPROM_LINK_TXP_LIMIT (2*0x6a) +#define EEPROM_LINK_TXP_LIMIT_SIZE (2*0x6b) + +/* General */ +#define EEPROM_DEVICE_ID (2*0x08) /* 2 bytes */ +#define EEPROM_SUBSYSTEM_ID (2*0x0A) /* 2 bytes */ +#define EEPROM_MAC_ADDRESS (2*0x15) /* 6 bytes */ +#define EEPROM_BOARD_REVISION (2*0x35) /* 2 bytes */ +#define EEPROM_BOARD_PBA_NUMBER (2*0x3B+1) /* 9 bytes */ +#define EEPROM_VERSION (2*0x44) /* 2 bytes */ +#define EEPROM_SKU_CAP (2*0x45) /* 2 bytes */ +#define EEPROM_OEM_MODE (2*0x46) /* 2 bytes */ +#define EEPROM_RADIO_CONFIG (2*0x48) /* 2 bytes */ +#define EEPROM_NUM_MAC_ADDRESS (2*0x4C) /* 2 bytes */ + +/* calibration */ +struct iwl_eeprom_calib_hdr { + u8 version; + u8 pa_type; + __le16 voltage; +} __packed; + +#define EEPROM_CALIB_ALL (INDIRECT_ADDRESS | INDIRECT_CALIBRATION) +#define EEPROM_XTAL ((2*0x128) | EEPROM_CALIB_ALL) + +/* temperature */ +#define EEPROM_KELVIN_TEMPERATURE ((2*0x12A) | EEPROM_CALIB_ALL) +#define EEPROM_RAW_TEMPERATURE ((2*0x12B) | EEPROM_CALIB_ALL) + +/* SKU Capabilities (actual values from EEPROM definition) */ +enum eeprom_sku_bits { + EEPROM_SKU_CAP_BAND_24GHZ = BIT(4), + EEPROM_SKU_CAP_BAND_52GHZ = BIT(5), + EEPROM_SKU_CAP_11N_ENABLE = BIT(6), + EEPROM_SKU_CAP_AMT_ENABLE = BIT(7), + EEPROM_SKU_CAP_IPAN_ENABLE = BIT(8) +}; + +/* radio config bits (actual values from EEPROM definition) */ +#define EEPROM_RF_CFG_TYPE_MSK(x) (x & 0x3) /* bits 0-1 */ +#define EEPROM_RF_CFG_STEP_MSK(x) ((x >> 2) & 0x3) /* bits 2-3 */ +#define EEPROM_RF_CFG_DASH_MSK(x) ((x >> 4) & 0x3) /* bits 4-5 */ +#define EEPROM_RF_CFG_PNUM_MSK(x) ((x >> 6) & 0x3) /* bits 6-7 */ +#define EEPROM_RF_CFG_TX_ANT_MSK(x) ((x >> 8) & 0xF) /* bits 8-11 */ +#define EEPROM_RF_CFG_RX_ANT_MSK(x) ((x >> 12) & 0xF) /* bits 12-15 */ + + +/* + * EEPROM bands + * These are the channel numbers from each band in the order + * that they are stored in the EEPROM band information. Note + * that EEPROM bands aren't the same as mac80211 bands, and + * there are even special "ht40 bands" in the EEPROM. + */ +static const u8 iwl_eeprom_band_1[14] = { /* 2.4 GHz */ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 +}; + +static const u8 iwl_eeprom_band_2[] = { /* 4915-5080MHz */ + 183, 184, 185, 187, 188, 189, 192, 196, 7, 8, 11, 12, 16 +}; + +static const u8 iwl_eeprom_band_3[] = { /* 5170-5320MHz */ + 34, 36, 38, 40, 42, 44, 46, 48, 52, 56, 60, 64 +}; + +static const u8 iwl_eeprom_band_4[] = { /* 5500-5700MHz */ + 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140 +}; + +static const u8 iwl_eeprom_band_5[] = { /* 5725-5825MHz */ + 145, 149, 153, 157, 161, 165 +}; + +static const u8 iwl_eeprom_band_6[] = { /* 2.4 ht40 channel */ + 1, 2, 3, 4, 5, 6, 7 +}; + +static const u8 iwl_eeprom_band_7[] = { /* 5.2 ht40 channel */ + 36, 44, 52, 60, 100, 108, 116, 124, 132, 149, 157 +}; + +#define IWL_NUM_CHANNELS (ARRAY_SIZE(iwl_eeprom_band_1) + \ + ARRAY_SIZE(iwl_eeprom_band_2) + \ + ARRAY_SIZE(iwl_eeprom_band_3) + \ + ARRAY_SIZE(iwl_eeprom_band_4) + \ + ARRAY_SIZE(iwl_eeprom_band_5)) + +/* rate data (static) */ +static struct ieee80211_rate iwl_cfg80211_rates[] = { + { .bitrate = 1 * 10, .hw_value = 0, .hw_value_short = 0, }, + { .bitrate = 2 * 10, .hw_value = 1, .hw_value_short = 1, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 5.5 * 10, .hw_value = 2, .hw_value_short = 2, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 11 * 10, .hw_value = 3, .hw_value_short = 3, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 6 * 10, .hw_value = 4, .hw_value_short = 4, }, + { .bitrate = 9 * 10, .hw_value = 5, .hw_value_short = 5, }, + { .bitrate = 12 * 10, .hw_value = 6, .hw_value_short = 6, }, + { .bitrate = 18 * 10, .hw_value = 7, .hw_value_short = 7, }, + { .bitrate = 24 * 10, .hw_value = 8, .hw_value_short = 8, }, + { .bitrate = 36 * 10, .hw_value = 9, .hw_value_short = 9, }, + { .bitrate = 48 * 10, .hw_value = 10, .hw_value_short = 10, }, + { .bitrate = 54 * 10, .hw_value = 11, .hw_value_short = 11, }, +}; +#define RATES_24_OFFS 0 +#define N_RATES_24 ARRAY_SIZE(iwl_cfg80211_rates) +#define RATES_52_OFFS 4 +#define N_RATES_52 (N_RATES_24 - RATES_52_OFFS) + +/* EEPROM reading functions */ + +static u16 iwl_eeprom_query16(const u8 *eeprom, size_t eeprom_size, int offset) +{ + if (WARN_ON(offset + sizeof(u16) > eeprom_size)) + return 0; + return le16_to_cpup((__le16 *)(eeprom + offset)); +} + +static u32 eeprom_indirect_address(const u8 *eeprom, size_t eeprom_size, + u32 address) +{ + u16 offset = 0; + + if ((address & INDIRECT_ADDRESS) == 0) + return address; + + switch (address & INDIRECT_TYPE_MSK) { + case INDIRECT_HOST: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_HOST); + break; + case INDIRECT_GENERAL: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_GENERAL); + break; + case INDIRECT_REGULATORY: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_REGULATORY); + break; + case INDIRECT_TXP_LIMIT: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_TXP_LIMIT); + break; + case INDIRECT_TXP_LIMIT_SIZE: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_TXP_LIMIT_SIZE); + break; + case INDIRECT_CALIBRATION: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_CALIBRATION); + break; + case INDIRECT_PROCESS_ADJST: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_PROCESS_ADJST); + break; + case INDIRECT_OTHERS: + offset = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_LINK_OTHERS); + break; + default: + WARN_ON(1); + break; + } + + /* translate the offset from words to byte */ + return (address & ADDRESS_MSK) + (offset << 1); +} + +static const u8 *iwl_eeprom_query_addr(const u8 *eeprom, size_t eeprom_size, + u32 offset) +{ + u32 address = eeprom_indirect_address(eeprom, eeprom_size, offset); + + if (WARN_ON(address >= eeprom_size)) + return NULL; + + return &eeprom[address]; +} + +static int iwl_eeprom_read_calib(const u8 *eeprom, size_t eeprom_size, + struct iwl_nvm_data *data) +{ + struct iwl_eeprom_calib_hdr *hdr; + + hdr = (void *)iwl_eeprom_query_addr(eeprom, eeprom_size, + EEPROM_CALIB_ALL); + if (!hdr) + return -ENODATA; + data->calib_version = hdr->version; + data->calib_voltage = hdr->voltage; + + return 0; +} + +/** + * enum iwl_eeprom_channel_flags - channel flags in EEPROM + * @EEPROM_CHANNEL_VALID: channel is usable for this SKU/geo + * @EEPROM_CHANNEL_IBSS: usable as an IBSS channel + * @EEPROM_CHANNEL_ACTIVE: active scanning allowed + * @EEPROM_CHANNEL_RADAR: radar detection required + * @EEPROM_CHANNEL_WIDE: 20 MHz channel okay (?) + * @EEPROM_CHANNEL_DFS: dynamic freq selection candidate + */ +enum iwl_eeprom_channel_flags { + EEPROM_CHANNEL_VALID = BIT(0), + EEPROM_CHANNEL_IBSS = BIT(1), + EEPROM_CHANNEL_ACTIVE = BIT(3), + EEPROM_CHANNEL_RADAR = BIT(4), + EEPROM_CHANNEL_WIDE = BIT(5), + EEPROM_CHANNEL_DFS = BIT(7), +}; + +/** + * struct iwl_eeprom_channel - EEPROM channel data + * @flags: %EEPROM_CHANNEL_* flags + * @max_power_avg: max power (in dBm) on this channel, at most 31 dBm + */ +struct iwl_eeprom_channel { + u8 flags; + s8 max_power_avg; +} __packed; + + +enum iwl_eeprom_enhanced_txpwr_flags { + IWL_EEPROM_ENH_TXP_FL_VALID = BIT(0), + IWL_EEPROM_ENH_TXP_FL_BAND_52G = BIT(1), + IWL_EEPROM_ENH_TXP_FL_OFDM = BIT(2), + IWL_EEPROM_ENH_TXP_FL_40MHZ = BIT(3), + IWL_EEPROM_ENH_TXP_FL_HT_AP = BIT(4), + IWL_EEPROM_ENH_TXP_FL_RES1 = BIT(5), + IWL_EEPROM_ENH_TXP_FL_RES2 = BIT(6), + IWL_EEPROM_ENH_TXP_FL_COMMON_TYPE = BIT(7), +}; + +/** + * iwl_eeprom_enhanced_txpwr structure + * @flags: entry flags + * @channel: channel number + * @chain_a_max_pwr: chain a max power in 1/2 dBm + * @chain_b_max_pwr: chain b max power in 1/2 dBm + * @chain_c_max_pwr: chain c max power in 1/2 dBm + * @delta_20_in_40: 20-in-40 deltas (hi/lo) + * @mimo2_max_pwr: mimo2 max power in 1/2 dBm + * @mimo3_max_pwr: mimo3 max power in 1/2 dBm + * + * This structure presents the enhanced regulatory tx power limit layout + * in an EEPROM image. + */ +struct iwl_eeprom_enhanced_txpwr { + u8 flags; + u8 channel; + s8 chain_a_max; + s8 chain_b_max; + s8 chain_c_max; + u8 delta_20_in_40; + s8 mimo2_max; + s8 mimo3_max; +} __packed; + +static s8 iwl_get_max_txpwr_half_dbm(const struct iwl_nvm_data *data, + struct iwl_eeprom_enhanced_txpwr *txp) +{ + s8 result = 0; /* (.5 dBm) */ + + /* Take the highest tx power from any valid chains */ + if (data->valid_tx_ant & ANT_A && txp->chain_a_max > result) + result = txp->chain_a_max; + + if (data->valid_tx_ant & ANT_B && txp->chain_b_max > result) + result = txp->chain_b_max; + + if (data->valid_tx_ant & ANT_C && txp->chain_c_max > result) + result = txp->chain_c_max; + + if ((data->valid_tx_ant == ANT_AB || + data->valid_tx_ant == ANT_BC || + data->valid_tx_ant == ANT_AC) && txp->mimo2_max > result) + result = txp->mimo2_max; + + if (data->valid_tx_ant == ANT_ABC && txp->mimo3_max > result) + result = txp->mimo3_max; + + return result; +} + +#define EEPROM_TXP_OFFS (0x00 | INDIRECT_ADDRESS | INDIRECT_TXP_LIMIT) +#define EEPROM_TXP_ENTRY_LEN sizeof(struct iwl_eeprom_enhanced_txpwr) +#define EEPROM_TXP_SZ_OFFS (0x00 | INDIRECT_ADDRESS | INDIRECT_TXP_LIMIT_SIZE) + +#define TXP_CHECK_AND_PRINT(x) \ + ((txp->flags & IWL_EEPROM_ENH_TXP_FL_##x) ? # x " " : "") + +static void +iwl_eeprom_enh_txp_read_element(struct iwl_nvm_data *data, + struct iwl_eeprom_enhanced_txpwr *txp, + int n_channels, s8 max_txpower_avg) +{ + int ch_idx; + enum ieee80211_band band; + + band = txp->flags & IWL_EEPROM_ENH_TXP_FL_BAND_52G ? + IEEE80211_BAND_5GHZ : IEEE80211_BAND_2GHZ; + + for (ch_idx = 0; ch_idx < n_channels; ch_idx++) { + struct ieee80211_channel *chan = &data->channels[ch_idx]; + + /* update matching channel or from common data only */ + if (txp->channel != 0 && chan->hw_value != txp->channel) + continue; + + /* update matching band only */ + if (band != chan->band) + continue; + + if (chan->max_power < max_txpower_avg && + !(txp->flags & IWL_EEPROM_ENH_TXP_FL_40MHZ)) + chan->max_power = max_txpower_avg; + } +} + +static void iwl_eeprom_enhanced_txpower(struct device *dev, + struct iwl_nvm_data *data, + const u8 *eeprom, size_t eeprom_size, + int n_channels) +{ + struct iwl_eeprom_enhanced_txpwr *txp_array, *txp; + int idx, entries; + __le16 *txp_len; + s8 max_txp_avg_halfdbm; + + BUILD_BUG_ON(sizeof(struct iwl_eeprom_enhanced_txpwr) != 8); + + /* the length is in 16-bit words, but we want entries */ + txp_len = (__le16 *)iwl_eeprom_query_addr(eeprom, eeprom_size, + EEPROM_TXP_SZ_OFFS); + entries = le16_to_cpup(txp_len) * 2 / EEPROM_TXP_ENTRY_LEN; + + txp_array = (void *)iwl_eeprom_query_addr(eeprom, eeprom_size, + EEPROM_TXP_OFFS); + + for (idx = 0; idx < entries; idx++) { + txp = &txp_array[idx]; + /* skip invalid entries */ + if (!(txp->flags & IWL_EEPROM_ENH_TXP_FL_VALID)) + continue; + + IWL_DEBUG_EEPROM(dev, "%s %d:\t %s%s%s%s%s%s%s%s (0x%02x)\n", + (txp->channel && (txp->flags & + IWL_EEPROM_ENH_TXP_FL_COMMON_TYPE)) ? + "Common " : (txp->channel) ? + "Channel" : "Common", + (txp->channel), + TXP_CHECK_AND_PRINT(VALID), + TXP_CHECK_AND_PRINT(BAND_52G), + TXP_CHECK_AND_PRINT(OFDM), + TXP_CHECK_AND_PRINT(40MHZ), + TXP_CHECK_AND_PRINT(HT_AP), + TXP_CHECK_AND_PRINT(RES1), + TXP_CHECK_AND_PRINT(RES2), + TXP_CHECK_AND_PRINT(COMMON_TYPE), + txp->flags); + IWL_DEBUG_EEPROM(dev, + "\t\t chain_A: 0x%02x chain_B: 0X%02x chain_C: 0X%02x\n", + txp->chain_a_max, txp->chain_b_max, + txp->chain_c_max); + IWL_DEBUG_EEPROM(dev, + "\t\t MIMO2: 0x%02x MIMO3: 0x%02x High 20_on_40: 0x%02x Low 20_on_40: 0x%02x\n", + txp->mimo2_max, txp->mimo3_max, + ((txp->delta_20_in_40 & 0xf0) >> 4), + (txp->delta_20_in_40 & 0x0f)); + + max_txp_avg_halfdbm = iwl_get_max_txpwr_half_dbm(data, txp); + + iwl_eeprom_enh_txp_read_element(data, txp, n_channels, + DIV_ROUND_UP(max_txp_avg_halfdbm, 2)); + + if (max_txp_avg_halfdbm > data->max_tx_pwr_half_dbm) + data->max_tx_pwr_half_dbm = max_txp_avg_halfdbm; + } +} + +static void iwl_init_band_reference(const struct iwl_cfg *cfg, + const u8 *eeprom, size_t eeprom_size, + int eeprom_band, int *eeprom_ch_count, + const struct iwl_eeprom_channel **ch_info, + const u8 **eeprom_ch_array) +{ + u32 offset = cfg->eeprom_params->regulatory_bands[eeprom_band - 1]; + + offset |= INDIRECT_ADDRESS | INDIRECT_REGULATORY; + + *ch_info = (void *)iwl_eeprom_query_addr(eeprom, eeprom_size, offset); + + switch (eeprom_band) { + case 1: /* 2.4GHz band */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_1); + *eeprom_ch_array = iwl_eeprom_band_1; + break; + case 2: /* 4.9GHz band */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_2); + *eeprom_ch_array = iwl_eeprom_band_2; + break; + case 3: /* 5.2GHz band */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_3); + *eeprom_ch_array = iwl_eeprom_band_3; + break; + case 4: /* 5.5GHz band */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_4); + *eeprom_ch_array = iwl_eeprom_band_4; + break; + case 5: /* 5.7GHz band */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_5); + *eeprom_ch_array = iwl_eeprom_band_5; + break; + case 6: /* 2.4GHz ht40 channels */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_6); + *eeprom_ch_array = iwl_eeprom_band_6; + break; + case 7: /* 5 GHz ht40 channels */ + *eeprom_ch_count = ARRAY_SIZE(iwl_eeprom_band_7); + *eeprom_ch_array = iwl_eeprom_band_7; + break; + default: + *eeprom_ch_count = 0; + *eeprom_ch_array = NULL; + WARN_ON(1); + } +} + +#define CHECK_AND_PRINT(x) \ + ((eeprom_ch->flags & EEPROM_CHANNEL_##x) ? # x " " : "") + +static void iwl_mod_ht40_chan_info(struct device *dev, + struct iwl_nvm_data *data, int n_channels, + enum ieee80211_band band, u16 channel, + const struct iwl_eeprom_channel *eeprom_ch, + u8 clear_ht40_extension_channel) +{ + struct ieee80211_channel *chan = NULL; + int i; + + for (i = 0; i < n_channels; i++) { + if (data->channels[i].band != band) + continue; + if (data->channels[i].hw_value != channel) + continue; + chan = &data->channels[i]; + break; + } + + if (!chan) + return; + + IWL_DEBUG_EEPROM(dev, + "HT40 Ch. %d [%sGHz] %s%s%s%s%s(0x%02x %ddBm): Ad-Hoc %ssupported\n", + channel, + band == IEEE80211_BAND_5GHZ ? "5.2" : "2.4", + CHECK_AND_PRINT(IBSS), + CHECK_AND_PRINT(ACTIVE), + CHECK_AND_PRINT(RADAR), + CHECK_AND_PRINT(WIDE), + CHECK_AND_PRINT(DFS), + eeprom_ch->flags, + eeprom_ch->max_power_avg, + ((eeprom_ch->flags & EEPROM_CHANNEL_IBSS) && + !(eeprom_ch->flags & EEPROM_CHANNEL_RADAR)) ? "" + : "not "); + + if (eeprom_ch->flags & EEPROM_CHANNEL_VALID) + chan->flags &= ~clear_ht40_extension_channel; +} + +#define CHECK_AND_PRINT_I(x) \ + ((eeprom_ch_info[ch_idx].flags & EEPROM_CHANNEL_##x) ? # x " " : "") + +static int iwl_init_channel_map(struct device *dev, const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, + const u8 *eeprom, size_t eeprom_size) +{ + int band, ch_idx; + const struct iwl_eeprom_channel *eeprom_ch_info; + const u8 *eeprom_ch_array; + int eeprom_ch_count; + int n_channels = 0; + + /* + * Loop through the 5 EEPROM bands and add them to the parse list + */ + for (band = 1; band <= 5; band++) { + struct ieee80211_channel *channel; + + iwl_init_band_reference(cfg, eeprom, eeprom_size, band, + &eeprom_ch_count, &eeprom_ch_info, + &eeprom_ch_array); + + /* Loop through each band adding each of the channels */ + for (ch_idx = 0; ch_idx < eeprom_ch_count; ch_idx++) { + const struct iwl_eeprom_channel *eeprom_ch; + + eeprom_ch = &eeprom_ch_info[ch_idx]; + + if (!(eeprom_ch->flags & EEPROM_CHANNEL_VALID)) { + IWL_DEBUG_EEPROM(dev, + "Ch. %d Flags %x [%sGHz] - No traffic\n", + eeprom_ch_array[ch_idx], + eeprom_ch_info[ch_idx].flags, + (band != 1) ? "5.2" : "2.4"); + continue; + } + + channel = &data->channels[n_channels]; + n_channels++; + + channel->hw_value = eeprom_ch_array[ch_idx]; + channel->band = (band == 1) ? IEEE80211_BAND_2GHZ + : IEEE80211_BAND_5GHZ; + channel->center_freq = + ieee80211_channel_to_frequency( + channel->hw_value, channel->band); + + /* set no-HT40, will enable as appropriate later */ + channel->flags = IEEE80211_CHAN_NO_HT40; + + if (!(eeprom_ch->flags & EEPROM_CHANNEL_IBSS)) + channel->flags |= IEEE80211_CHAN_NO_IBSS; + + if (!(eeprom_ch->flags & EEPROM_CHANNEL_ACTIVE)) + channel->flags |= IEEE80211_CHAN_PASSIVE_SCAN; + + if (eeprom_ch->flags & EEPROM_CHANNEL_RADAR) + channel->flags |= IEEE80211_CHAN_RADAR; + + /* Initialize regulatory-based run-time data */ + channel->max_power = + eeprom_ch_info[ch_idx].max_power_avg; + IWL_DEBUG_EEPROM(dev, + "Ch. %d [%sGHz] %s%s%s%s%s%s(0x%02x %ddBm): Ad-Hoc %ssupported\n", + channel->hw_value, + (band != 1) ? "5.2" : "2.4", + CHECK_AND_PRINT_I(VALID), + CHECK_AND_PRINT_I(IBSS), + CHECK_AND_PRINT_I(ACTIVE), + CHECK_AND_PRINT_I(RADAR), + CHECK_AND_PRINT_I(WIDE), + CHECK_AND_PRINT_I(DFS), + eeprom_ch_info[ch_idx].flags, + eeprom_ch_info[ch_idx].max_power_avg, + ((eeprom_ch_info[ch_idx].flags & + EEPROM_CHANNEL_IBSS) && + !(eeprom_ch_info[ch_idx].flags & + EEPROM_CHANNEL_RADAR)) + ? "" : "not "); + } + } + + if (cfg->eeprom_params->enhanced_txpower) { + /* + * for newer device (6000 series and up) + * EEPROM contain enhanced tx power information + * driver need to process addition information + * to determine the max channel tx power limits + */ + iwl_eeprom_enhanced_txpower(dev, data, eeprom, eeprom_size, + n_channels); + } else { + /* All others use data from channel map */ + int i; + + data->max_tx_pwr_half_dbm = -128; + + for (i = 0; i < n_channels; i++) + data->max_tx_pwr_half_dbm = + max_t(s8, data->max_tx_pwr_half_dbm, + data->channels[i].max_power * 2); + } + + /* Check if we do have HT40 channels */ + if (cfg->eeprom_params->regulatory_bands[5] == + EEPROM_REGULATORY_BAND_NO_HT40 && + cfg->eeprom_params->regulatory_bands[6] == + EEPROM_REGULATORY_BAND_NO_HT40) + return n_channels; + + /* Two additional EEPROM bands for 2.4 and 5 GHz HT40 channels */ + for (band = 6; band <= 7; band++) { + enum ieee80211_band ieeeband; + + iwl_init_band_reference(cfg, eeprom, eeprom_size, band, + &eeprom_ch_count, &eeprom_ch_info, + &eeprom_ch_array); + + /* EEPROM band 6 is 2.4, band 7 is 5 GHz */ + ieeeband = (band == 6) ? IEEE80211_BAND_2GHZ + : IEEE80211_BAND_5GHZ; + + /* Loop through each band adding each of the channels */ + for (ch_idx = 0; ch_idx < eeprom_ch_count; ch_idx++) { + /* Set up driver's info for lower half */ + iwl_mod_ht40_chan_info(dev, data, n_channels, ieeeband, + eeprom_ch_array[ch_idx], + &eeprom_ch_info[ch_idx], + IEEE80211_CHAN_NO_HT40PLUS); + + /* Set up driver's info for upper half */ + iwl_mod_ht40_chan_info(dev, data, n_channels, ieeeband, + eeprom_ch_array[ch_idx] + 4, + &eeprom_ch_info[ch_idx], + IEEE80211_CHAN_NO_HT40MINUS); + } + } + + return n_channels; +} + +int iwl_init_sband_channels(struct iwl_nvm_data *data, + struct ieee80211_supported_band *sband, + int n_channels, enum ieee80211_band band) +{ + struct ieee80211_channel *chan = &data->channels[0]; + int n = 0, idx = 0; + + while (chan->band != band && idx < n_channels) + chan = &data->channels[++idx]; + + sband->channels = &data->channels[idx]; + + while (chan->band == band && idx < n_channels) { + chan = &data->channels[++idx]; + n++; + } + + sband->n_channels = n; + + return n; +} + +#define MAX_BIT_RATE_40_MHZ 150 /* Mbps */ +#define MAX_BIT_RATE_20_MHZ 72 /* Mbps */ + +void iwl_init_ht_hw_capab(const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, + struct ieee80211_sta_ht_cap *ht_info, + enum ieee80211_band band) +{ + int max_bit_rate = 0; + u8 rx_chains; + u8 tx_chains; + + tx_chains = hweight8(data->valid_tx_ant); + if (cfg->rx_with_siso_diversity) + rx_chains = 1; + else + rx_chains = hweight8(data->valid_rx_ant); + + if (!(data->sku_cap_11n_enable) || !cfg->ht_params) { + ht_info->ht_supported = false; + return; + } + + ht_info->ht_supported = true; + ht_info->cap = IEEE80211_HT_CAP_DSSSCCK40; + + if (iwlwifi_mod_params.amsdu_size_8K) + ht_info->cap |= IEEE80211_HT_CAP_MAX_AMSDU; + + ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + ht_info->ampdu_density = IEEE80211_HT_MPDU_DENSITY_4; + + ht_info->mcs.rx_mask[0] = 0xFF; + if (rx_chains >= 2) + ht_info->mcs.rx_mask[1] = 0xFF; + if (rx_chains >= 3) + ht_info->mcs.rx_mask[2] = 0xFF; + + if (cfg->ht_params->ht_greenfield_support) + ht_info->cap |= IEEE80211_HT_CAP_GRN_FLD; + ht_info->cap |= IEEE80211_HT_CAP_SGI_20; + + max_bit_rate = MAX_BIT_RATE_20_MHZ; + + if (cfg->ht_params->ht40_bands & BIT(band)) { + ht_info->cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + ht_info->cap |= IEEE80211_HT_CAP_SGI_40; + ht_info->mcs.rx_mask[4] = 0x01; + max_bit_rate = MAX_BIT_RATE_40_MHZ; + } + + /* Highest supported Rx data rate */ + max_bit_rate *= rx_chains; + WARN_ON(max_bit_rate & ~IEEE80211_HT_MCS_RX_HIGHEST_MASK); + ht_info->mcs.rx_highest = cpu_to_le16(max_bit_rate); + + /* Tx MCS capabilities */ + ht_info->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; + if (tx_chains != rx_chains) { + ht_info->mcs.tx_params |= IEEE80211_HT_MCS_TX_RX_DIFF; + ht_info->mcs.tx_params |= ((tx_chains - 1) << + IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT); + } +} + +static void iwl_init_sbands(struct device *dev, const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, + const u8 *eeprom, size_t eeprom_size) +{ + int n_channels = iwl_init_channel_map(dev, cfg, data, + eeprom, eeprom_size); + int n_used = 0; + struct ieee80211_supported_band *sband; + + sband = &data->bands[IEEE80211_BAND_2GHZ]; + sband->band = IEEE80211_BAND_2GHZ; + sband->bitrates = &iwl_cfg80211_rates[RATES_24_OFFS]; + sband->n_bitrates = N_RATES_24; + n_used += iwl_init_sband_channels(data, sband, n_channels, + IEEE80211_BAND_2GHZ); + iwl_init_ht_hw_capab(cfg, data, &sband->ht_cap, IEEE80211_BAND_2GHZ); + + sband = &data->bands[IEEE80211_BAND_5GHZ]; + sband->band = IEEE80211_BAND_5GHZ; + sband->bitrates = &iwl_cfg80211_rates[RATES_52_OFFS]; + sband->n_bitrates = N_RATES_52; + n_used += iwl_init_sband_channels(data, sband, n_channels, + IEEE80211_BAND_5GHZ); + iwl_init_ht_hw_capab(cfg, data, &sband->ht_cap, IEEE80211_BAND_5GHZ); + + if (n_channels != n_used) + IWL_ERR_DEV(dev, "EEPROM: used only %d of %d channels\n", + n_used, n_channels); +} + +/* EEPROM data functions */ + +struct iwl_nvm_data * +iwl_parse_eeprom_data(struct device *dev, const struct iwl_cfg *cfg, + const u8 *eeprom, size_t eeprom_size) +{ + struct iwl_nvm_data *data; + const void *tmp; + u16 radio_cfg, sku; + + if (WARN_ON(!cfg || !cfg->eeprom_params)) + return NULL; + + data = kzalloc(sizeof(*data) + + sizeof(struct ieee80211_channel) * IWL_NUM_CHANNELS, + GFP_KERNEL); + if (!data) + return NULL; + + /* get MAC address(es) */ + tmp = iwl_eeprom_query_addr(eeprom, eeprom_size, EEPROM_MAC_ADDRESS); + if (!tmp) + goto err_free; + memcpy(data->hw_addr, tmp, ETH_ALEN); + data->n_hw_addrs = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_NUM_MAC_ADDRESS); + + if (iwl_eeprom_read_calib(eeprom, eeprom_size, data)) + goto err_free; + + tmp = iwl_eeprom_query_addr(eeprom, eeprom_size, EEPROM_XTAL); + if (!tmp) + goto err_free; + memcpy(data->xtal_calib, tmp, sizeof(data->xtal_calib)); + + tmp = iwl_eeprom_query_addr(eeprom, eeprom_size, + EEPROM_RAW_TEMPERATURE); + if (!tmp) + goto err_free; + data->raw_temperature = *(__le16 *)tmp; + + tmp = iwl_eeprom_query_addr(eeprom, eeprom_size, + EEPROM_KELVIN_TEMPERATURE); + if (!tmp) + goto err_free; + data->kelvin_temperature = *(__le16 *)tmp; + data->kelvin_voltage = *((__le16 *)tmp + 1); + + radio_cfg = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_RADIO_CONFIG); + data->radio_cfg_dash = EEPROM_RF_CFG_DASH_MSK(radio_cfg); + data->radio_cfg_pnum = EEPROM_RF_CFG_PNUM_MSK(radio_cfg); + data->radio_cfg_step = EEPROM_RF_CFG_STEP_MSK(radio_cfg); + data->radio_cfg_type = EEPROM_RF_CFG_TYPE_MSK(radio_cfg); + data->valid_rx_ant = EEPROM_RF_CFG_RX_ANT_MSK(radio_cfg); + data->valid_tx_ant = EEPROM_RF_CFG_TX_ANT_MSK(radio_cfg); + + sku = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_SKU_CAP); + data->sku_cap_11n_enable = sku & EEPROM_SKU_CAP_11N_ENABLE; + data->sku_cap_amt_enable = sku & EEPROM_SKU_CAP_AMT_ENABLE; + data->sku_cap_band_24GHz_enable = sku & EEPROM_SKU_CAP_BAND_24GHZ; + data->sku_cap_band_52GHz_enable = sku & EEPROM_SKU_CAP_BAND_52GHZ; + data->sku_cap_ipan_enable = sku & EEPROM_SKU_CAP_IPAN_ENABLE; + if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_ALL) + data->sku_cap_11n_enable = false; + + data->nvm_version = iwl_eeprom_query16(eeprom, eeprom_size, + EEPROM_VERSION); + + /* check overrides (some devices have wrong EEPROM) */ + if (cfg->valid_tx_ant) + data->valid_tx_ant = cfg->valid_tx_ant; + if (cfg->valid_rx_ant) + data->valid_rx_ant = cfg->valid_rx_ant; + + if (!data->valid_tx_ant || !data->valid_rx_ant) { + IWL_ERR_DEV(dev, "invalid antennas (0x%x, 0x%x)\n", + data->valid_tx_ant, data->valid_rx_ant); + goto err_free; + } + + iwl_init_sbands(dev, cfg, data, eeprom, eeprom_size); + + return data; + err_free: + kfree(data); + return NULL; +} +IWL_EXPORT_SYMBOL(iwl_parse_eeprom_data); + +/* helper functions */ +int iwl_nvm_check_version(struct iwl_nvm_data *data, + struct iwl_trans *trans) +{ + if (data->nvm_version >= trans->cfg->nvm_ver || + data->calib_version >= trans->cfg->nvm_calib_ver) { + IWL_DEBUG_INFO(trans, "device EEPROM VER=0x%x, CALIB=0x%x\n", + data->nvm_version, data->calib_version); + return 0; + } + + IWL_ERR(trans, + "Unsupported (too old) EEPROM VER=0x%x < 0x%x CALIB=0x%x < 0x%x\n", + data->nvm_version, trans->cfg->nvm_ver, + data->calib_version, trans->cfg->nvm_calib_ver); + return -EINVAL; +} +IWL_EXPORT_SYMBOL(iwl_nvm_check_version); diff --git a/drivers/net/wireless/iwlwifi/iwl-eeprom-parse.h b/drivers/net/wireless/iwlwifi/iwl-eeprom-parse.h new file mode 100644 index 0000000..37f1153 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-eeprom-parse.h @@ -0,0 +1,138 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ +#ifndef __iwl_eeprom_parse_h__ +#define __iwl_eeprom_parse_h__ + +#include +#include +#include "iwl-trans.h" + +struct iwl_nvm_data { + int n_hw_addrs; + u8 hw_addr[ETH_ALEN]; + + u8 calib_version; + __le16 calib_voltage; + + __le16 raw_temperature; + __le16 kelvin_temperature; + __le16 kelvin_voltage; + __le16 xtal_calib[2]; + + bool sku_cap_band_24GHz_enable; + bool sku_cap_band_52GHz_enable; + bool sku_cap_11n_enable; + bool sku_cap_amt_enable; + bool sku_cap_ipan_enable; + + u8 radio_cfg_type; + u8 radio_cfg_step; + u8 radio_cfg_dash; + u8 radio_cfg_pnum; + u8 valid_tx_ant, valid_rx_ant; + + u16 nvm_version; + s8 max_tx_pwr_half_dbm; + + struct ieee80211_supported_band bands[IEEE80211_NUM_BANDS]; + struct ieee80211_channel channels[]; +}; + +/** + * iwl_parse_eeprom_data - parse EEPROM data and return values + * + * @dev: device pointer we're parsing for, for debug only + * @cfg: device configuration for parsing and overrides + * @eeprom: the EEPROM data + * @eeprom_size: length of the EEPROM data + * + * This function parses all EEPROM values we need and then + * returns a (newly allocated) struct containing all the + * relevant values for driver use. The struct must be freed + * later with iwl_free_nvm_data(). + */ +struct iwl_nvm_data * +iwl_parse_eeprom_data(struct device *dev, const struct iwl_cfg *cfg, + const u8 *eeprom, size_t eeprom_size); + +/** + * iwl_free_nvm_data - free NVM data + * @data: the data to free + */ +static inline void iwl_free_nvm_data(struct iwl_nvm_data *data) +{ + kfree(data); +} + +int iwl_nvm_check_version(struct iwl_nvm_data *data, + struct iwl_trans *trans); + +int iwl_init_sband_channels(struct iwl_nvm_data *data, + struct ieee80211_supported_band *sband, + int n_channels, enum ieee80211_band band); + +void iwl_init_ht_hw_capab(const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, + struct ieee80211_sta_ht_cap *ht_info, + enum ieee80211_band band); + +#endif /* __iwl_eeprom_parse_h__ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-eeprom-read.c b/drivers/net/wireless/iwlwifi/iwl-eeprom-read.c new file mode 100644 index 0000000..e5f2e36 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-eeprom-read.c @@ -0,0 +1,464 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ +#include +#include +#include + +#include "iwl-drv.h" +#include "iwl-debug.h" +#include "iwl-eeprom-read.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "iwl-csr.h" + +/* + * EEPROM access time values: + * + * Driver initiates EEPROM read by writing byte address << 1 to CSR_EEPROM_REG. + * Driver then polls CSR_EEPROM_REG for CSR_EEPROM_REG_READ_VALID_MSK (0x1). + * When polling, wait 10 uSec between polling loops, up to a maximum 5000 uSec. + * Driver reads 16-bit value from bits 31-16 of CSR_EEPROM_REG. + */ +#define IWL_EEPROM_ACCESS_TIMEOUT 5000 /* uSec */ + +#define IWL_EEPROM_SEM_TIMEOUT 10 /* microseconds */ +#define IWL_EEPROM_SEM_RETRY_LIMIT 1000 /* number of attempts (not time) */ + + +/* + * The device's EEPROM semaphore prevents conflicts between driver and uCode + * when accessing the EEPROM; each access is a series of pulses to/from the + * EEPROM chip, not a single event, so even reads could conflict if they + * weren't arbitrated by the semaphore. + */ + +#define EEPROM_SEM_TIMEOUT 10 /* milliseconds */ +#define EEPROM_SEM_RETRY_LIMIT 1000 /* number of attempts (not time) */ + +static int iwl_eeprom_acquire_semaphore(struct iwl_trans *trans) +{ + u16 count; + int ret; + + for (count = 0; count < EEPROM_SEM_RETRY_LIMIT; count++) { + /* Request semaphore */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM); + + /* See if we got it */ + ret = iwl_poll_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM, + CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM, + EEPROM_SEM_TIMEOUT); + if (ret >= 0) { + IWL_DEBUG_EEPROM(trans->dev, + "Acquired semaphore after %d tries.\n", + count+1); + return ret; + } + } + + return ret; +} + +static void iwl_eeprom_release_semaphore(struct iwl_trans *trans) +{ + iwl_clear_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_EEPROM_OWN_SEM); +} + +static int iwl_eeprom_verify_signature(struct iwl_trans *trans, bool nvm_is_otp) +{ + u32 gp = iwl_read32(trans, CSR_EEPROM_GP) & CSR_EEPROM_GP_VALID_MSK; + + IWL_DEBUG_EEPROM(trans->dev, "EEPROM signature=0x%08x\n", gp); + + switch (gp) { + case CSR_EEPROM_GP_BAD_SIG_EEP_GOOD_SIG_OTP: + if (!nvm_is_otp) { + IWL_ERR(trans, "EEPROM with bad signature: 0x%08x\n", + gp); + return -ENOENT; + } + return 0; + case CSR_EEPROM_GP_GOOD_SIG_EEP_LESS_THAN_4K: + case CSR_EEPROM_GP_GOOD_SIG_EEP_MORE_THAN_4K: + if (nvm_is_otp) { + IWL_ERR(trans, "OTP with bad signature: 0x%08x\n", gp); + return -ENOENT; + } + return 0; + case CSR_EEPROM_GP_BAD_SIGNATURE_BOTH_EEP_AND_OTP: + default: + IWL_ERR(trans, + "bad EEPROM/OTP signature, type=%s, EEPROM_GP=0x%08x\n", + nvm_is_otp ? "OTP" : "EEPROM", gp); + return -ENOENT; + } +} + +/****************************************************************************** + * + * OTP related functions + * +******************************************************************************/ + +static void iwl_set_otp_access_absolute(struct iwl_trans *trans) +{ + iwl_read32(trans, CSR_OTP_GP_REG); + + iwl_clear_bit(trans, CSR_OTP_GP_REG, + CSR_OTP_GP_REG_OTP_ACCESS_MODE); +} + +static int iwl_nvm_is_otp(struct iwl_trans *trans) +{ + u32 otpgp; + + /* OTP only valid for CP/PP and after */ + switch (trans->hw_rev & CSR_HW_REV_TYPE_MSK) { + case CSR_HW_REV_TYPE_NONE: + IWL_ERR(trans, "Unknown hardware type\n"); + return -EIO; + case CSR_HW_REV_TYPE_5300: + case CSR_HW_REV_TYPE_5350: + case CSR_HW_REV_TYPE_5100: + case CSR_HW_REV_TYPE_5150: + return 0; + default: + otpgp = iwl_read32(trans, CSR_OTP_GP_REG); + if (otpgp & CSR_OTP_GP_REG_DEVICE_SELECT) + return 1; + return 0; + } +} + +static int iwl_init_otp_access(struct iwl_trans *trans) +{ + int ret; + + /* Enable 40MHz radio clock */ + iwl_write32(trans, CSR_GP_CNTRL, + iwl_read32(trans, CSR_GP_CNTRL) | + CSR_GP_CNTRL_REG_FLAG_INIT_DONE); + + /* wait for clock to be ready */ + ret = iwl_poll_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, + CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, + 25000); + if (ret < 0) { + IWL_ERR(trans, "Time out access OTP\n"); + } else { + iwl_set_bits_prph(trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_VAL_RESET_REQ); + udelay(5); + iwl_clear_bits_prph(trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_VAL_RESET_REQ); + + /* + * CSR auto clock gate disable bit - + * this is only applicable for HW with OTP shadow RAM + */ + if (trans->cfg->base_params->shadow_ram_support) + iwl_set_bit(trans, CSR_DBG_LINK_PWR_MGMT_REG, + CSR_RESET_LINK_PWR_MGMT_DISABLED); + } + return ret; +} + +static int iwl_read_otp_word(struct iwl_trans *trans, u16 addr, + __le16 *eeprom_data) +{ + int ret = 0; + u32 r; + u32 otpgp; + + iwl_write32(trans, CSR_EEPROM_REG, + CSR_EEPROM_REG_MSK_ADDR & (addr << 1)); + ret = iwl_poll_bit(trans, CSR_EEPROM_REG, + CSR_EEPROM_REG_READ_VALID_MSK, + CSR_EEPROM_REG_READ_VALID_MSK, + IWL_EEPROM_ACCESS_TIMEOUT); + if (ret < 0) { + IWL_ERR(trans, "Time out reading OTP[%d]\n", addr); + return ret; + } + r = iwl_read32(trans, CSR_EEPROM_REG); + /* check for ECC errors: */ + otpgp = iwl_read32(trans, CSR_OTP_GP_REG); + if (otpgp & CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK) { + /* stop in this case */ + /* set the uncorrectable OTP ECC bit for acknowledgement */ + iwl_set_bit(trans, CSR_OTP_GP_REG, + CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK); + IWL_ERR(trans, "Uncorrectable OTP ECC error, abort OTP read\n"); + return -EINVAL; + } + if (otpgp & CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK) { + /* continue in this case */ + /* set the correctable OTP ECC bit for acknowledgement */ + iwl_set_bit(trans, CSR_OTP_GP_REG, + CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK); + IWL_ERR(trans, "Correctable OTP ECC error, continue read\n"); + } + *eeprom_data = cpu_to_le16(r >> 16); + return 0; +} + +/* + * iwl_is_otp_empty: check for empty OTP + */ +static bool iwl_is_otp_empty(struct iwl_trans *trans) +{ + u16 next_link_addr = 0; + __le16 link_value; + bool is_empty = false; + + /* locate the beginning of OTP link list */ + if (!iwl_read_otp_word(trans, next_link_addr, &link_value)) { + if (!link_value) { + IWL_ERR(trans, "OTP is empty\n"); + is_empty = true; + } + } else { + IWL_ERR(trans, "Unable to read first block of OTP list.\n"); + is_empty = true; + } + + return is_empty; +} + + +/* + * iwl_find_otp_image: find EEPROM image in OTP + * finding the OTP block that contains the EEPROM image. + * the last valid block on the link list (the block _before_ the last block) + * is the block we should read and used to configure the device. + * If all the available OTP blocks are full, the last block will be the block + * we should read and used to configure the device. + * only perform this operation if shadow RAM is disabled + */ +static int iwl_find_otp_image(struct iwl_trans *trans, + u16 *validblockaddr) +{ + u16 next_link_addr = 0, valid_addr; + __le16 link_value = 0; + int usedblocks = 0; + + /* set addressing mode to absolute to traverse the link list */ + iwl_set_otp_access_absolute(trans); + + /* checking for empty OTP or error */ + if (iwl_is_otp_empty(trans)) + return -EINVAL; + + /* + * start traverse link list + * until reach the max number of OTP blocks + * different devices have different number of OTP blocks + */ + do { + /* save current valid block address + * check for more block on the link list + */ + valid_addr = next_link_addr; + next_link_addr = le16_to_cpu(link_value) * sizeof(u16); + IWL_DEBUG_EEPROM(trans->dev, "OTP blocks %d addr 0x%x\n", + usedblocks, next_link_addr); + if (iwl_read_otp_word(trans, next_link_addr, &link_value)) + return -EINVAL; + if (!link_value) { + /* + * reach the end of link list, return success and + * set address point to the starting address + * of the image + */ + *validblockaddr = valid_addr; + /* skip first 2 bytes (link list pointer) */ + *validblockaddr += 2; + return 0; + } + /* more in the link list, continue */ + usedblocks++; + } while (usedblocks <= trans->cfg->base_params->max_ll_items); + + /* OTP has no valid blocks */ + IWL_DEBUG_EEPROM(trans->dev, "OTP has no valid blocks\n"); + return -EINVAL; +} + +/** + * iwl_read_eeprom - read EEPROM contents + * + * Load the EEPROM contents from adapter and return it + * and its size. + * + * NOTE: This routine uses the non-debug IO access functions. + */ +int iwl_read_eeprom(struct iwl_trans *trans, u8 **eeprom, size_t *eeprom_size) +{ + __le16 *e; + u32 gp = iwl_read32(trans, CSR_EEPROM_GP); + int sz; + int ret; + u16 addr; + u16 validblockaddr = 0; + u16 cache_addr = 0; + int nvm_is_otp; + + if (!eeprom || !eeprom_size) + return -EINVAL; + + nvm_is_otp = iwl_nvm_is_otp(trans); + if (nvm_is_otp < 0) + return nvm_is_otp; + + sz = trans->cfg->base_params->eeprom_size; + IWL_DEBUG_EEPROM(trans->dev, "NVM size = %d\n", sz); + + e = kmalloc(sz, GFP_KERNEL); + if (!e) + return -ENOMEM; + + ret = iwl_eeprom_verify_signature(trans, nvm_is_otp); + if (ret < 0) { + IWL_ERR(trans, "EEPROM not found, EEPROM_GP=0x%08x\n", gp); + goto err_free; + } + + /* Make sure driver (instead of uCode) is allowed to read EEPROM */ + ret = iwl_eeprom_acquire_semaphore(trans); + if (ret < 0) { + IWL_ERR(trans, "Failed to acquire EEPROM semaphore.\n"); + goto err_free; + } + + if (nvm_is_otp) { + ret = iwl_init_otp_access(trans); + if (ret) { + IWL_ERR(trans, "Failed to initialize OTP access.\n"); + goto err_unlock; + } + + iwl_write32(trans, CSR_EEPROM_GP, + iwl_read32(trans, CSR_EEPROM_GP) & + ~CSR_EEPROM_GP_IF_OWNER_MSK); + + iwl_set_bit(trans, CSR_OTP_GP_REG, + CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK | + CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK); + /* traversing the linked list if no shadow ram supported */ + if (!trans->cfg->base_params->shadow_ram_support) { + ret = iwl_find_otp_image(trans, &validblockaddr); + if (ret) + goto err_unlock; + } + for (addr = validblockaddr; addr < validblockaddr + sz; + addr += sizeof(u16)) { + __le16 eeprom_data; + + ret = iwl_read_otp_word(trans, addr, &eeprom_data); + if (ret) + goto err_unlock; + e[cache_addr / 2] = eeprom_data; + cache_addr += sizeof(u16); + } + } else { + /* eeprom is an array of 16bit values */ + for (addr = 0; addr < sz; addr += sizeof(u16)) { + u32 r; + + iwl_write32(trans, CSR_EEPROM_REG, + CSR_EEPROM_REG_MSK_ADDR & (addr << 1)); + + ret = iwl_poll_bit(trans, CSR_EEPROM_REG, + CSR_EEPROM_REG_READ_VALID_MSK, + CSR_EEPROM_REG_READ_VALID_MSK, + IWL_EEPROM_ACCESS_TIMEOUT); + if (ret < 0) { + IWL_ERR(trans, + "Time out reading EEPROM[%d]\n", addr); + goto err_unlock; + } + r = iwl_read32(trans, CSR_EEPROM_REG); + e[addr / 2] = cpu_to_le16(r >> 16); + } + } + + IWL_DEBUG_EEPROM(trans->dev, "NVM Type: %s\n", + nvm_is_otp ? "OTP" : "EEPROM"); + + iwl_eeprom_release_semaphore(trans); + + *eeprom_size = sz; + *eeprom = (u8 *)e; + return 0; + + err_unlock: + iwl_eeprom_release_semaphore(trans); + err_free: + kfree(e); + + return ret; +} +IWL_EXPORT_SYMBOL(iwl_read_eeprom); diff --git a/drivers/net/wireless/iwlwifi/iwl-eeprom-read.h b/drivers/net/wireless/iwlwifi/iwl-eeprom-read.h new file mode 100644 index 0000000..8e941f8 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-eeprom-read.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef __iwl_eeprom_h__ +#define __iwl_eeprom_h__ + +#include "iwl-trans.h" + +int iwl_read_eeprom(struct iwl_trans *trans, u8 **eeprom, size_t *eeprom_size); + +#endif /* __iwl_eeprom_h__ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-fh.h b/drivers/net/wireless/iwlwifi/iwl-fh.h new file mode 100644 index 0000000..484d318 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-fh.h @@ -0,0 +1,540 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#ifndef __iwl_fh_h__ +#define __iwl_fh_h__ + +#include + +/****************************/ +/* Flow Handler Definitions */ +/****************************/ + +/** + * This I/O area is directly read/writable by driver (e.g. Linux uses writel()) + * Addresses are offsets from device's PCI hardware base address. + */ +#define FH_MEM_LOWER_BOUND (0x1000) +#define FH_MEM_UPPER_BOUND (0x2000) + +/** + * Keep-Warm (KW) buffer base address. + * + * Driver must allocate a 4KByte buffer that is for keeping the + * host DRAM powered on (via dummy accesses to DRAM) to maintain low-latency + * DRAM access when doing Txing or Rxing. The dummy accesses prevent host + * from going into a power-savings mode that would cause higher DRAM latency, + * and possible data over/under-runs, before all Tx/Rx is complete. + * + * Driver loads FH_KW_MEM_ADDR_REG with the physical address (bits 35:4) + * of the buffer, which must be 4K aligned. Once this is set up, the device + * automatically invokes keep-warm accesses when normal accesses might not + * be sufficient to maintain fast DRAM response. + * + * Bit fields: + * 31-0: Keep-warm buffer physical base address [35:4], must be 4K aligned + */ +#define FH_KW_MEM_ADDR_REG (FH_MEM_LOWER_BOUND + 0x97C) + + +/** + * TFD Circular Buffers Base (CBBC) addresses + * + * Device has 16 base pointer registers, one for each of 16 host-DRAM-resident + * circular buffers (CBs/queues) containing Transmit Frame Descriptors (TFDs) + * (see struct iwl_tfd_frame). These 16 pointer registers are offset by 0x04 + * bytes from one another. Each TFD circular buffer in DRAM must be 256-byte + * aligned (address bits 0-7 must be 0). + * Later devices have 20 (5000 series) or 30 (higher) queues, but the registers + * for them are in different places. + * + * Bit fields in each pointer register: + * 27-0: TFD CB physical base address [35:8], must be 256-byte aligned + */ +#define FH_MEM_CBBC_0_15_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0x9D0) +#define FH_MEM_CBBC_0_15_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xA10) +#define FH_MEM_CBBC_16_19_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xBF0) +#define FH_MEM_CBBC_16_19_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xC00) +#define FH_MEM_CBBC_20_31_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xB20) +#define FH_MEM_CBBC_20_31_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xB80) + +/* Find TFD CB base pointer for given queue */ +static inline unsigned int FH_MEM_CBBC_QUEUE(unsigned int chnl) +{ + if (chnl < 16) + return FH_MEM_CBBC_0_15_LOWER_BOUND + 4 * chnl; + if (chnl < 20) + return FH_MEM_CBBC_16_19_LOWER_BOUND + 4 * (chnl - 16); + WARN_ON_ONCE(chnl >= 32); + return FH_MEM_CBBC_20_31_LOWER_BOUND + 4 * (chnl - 20); +} + + +/** + * Rx SRAM Control and Status Registers (RSCSR) + * + * These registers provide handshake between driver and device for the Rx queue + * (this queue handles *all* command responses, notifications, Rx data, etc. + * sent from uCode to host driver). Unlike Tx, there is only one Rx + * queue, and only one Rx DMA/FIFO channel. Also unlike Tx, which can + * concatenate up to 20 DRAM buffers to form a Tx frame, each Receive Buffer + * Descriptor (RBD) points to only one Rx Buffer (RB); there is a 1:1 + * mapping between RBDs and RBs. + * + * Driver must allocate host DRAM memory for the following, and set the + * physical address of each into device registers: + * + * 1) Receive Buffer Descriptor (RBD) circular buffer (CB), typically with 256 + * entries (although any power of 2, up to 4096, is selectable by driver). + * Each entry (1 dword) points to a receive buffer (RB) of consistent size + * (typically 4K, although 8K or 16K are also selectable by driver). + * Driver sets up RB size and number of RBDs in the CB via Rx config + * register FH_MEM_RCSR_CHNL0_CONFIG_REG. + * + * Bit fields within one RBD: + * 27-0: Receive Buffer physical address bits [35:8], 256-byte aligned + * + * Driver sets physical address [35:8] of base of RBD circular buffer + * into FH_RSCSR_CHNL0_RBDCB_BASE_REG [27:0]. + * + * 2) Rx status buffer, 8 bytes, in which uCode indicates which Rx Buffers + * (RBs) have been filled, via a "write pointer", actually the index of + * the RB's corresponding RBD within the circular buffer. Driver sets + * physical address [35:4] into FH_RSCSR_CHNL0_STTS_WPTR_REG [31:0]. + * + * Bit fields in lower dword of Rx status buffer (upper dword not used + * by driver: + * 31-12: Not used by driver + * 11- 0: Index of last filled Rx buffer descriptor + * (device writes, driver reads this value) + * + * As the driver prepares Receive Buffers (RBs) for device to fill, driver must + * enter pointers to these RBs into contiguous RBD circular buffer entries, + * and update the device's "write" index register, + * FH_RSCSR_CHNL0_RBDCB_WPTR_REG. + * + * This "write" index corresponds to the *next* RBD that the driver will make + * available, i.e. one RBD past the tail of the ready-to-fill RBDs within + * the circular buffer. This value should initially be 0 (before preparing any + * RBs), should be 8 after preparing the first 8 RBs (for example), and must + * wrap back to 0 at the end of the circular buffer (but don't wrap before + * "read" index has advanced past 1! See below). + * NOTE: DEVICE EXPECTS THE WRITE INDEX TO BE INCREMENTED IN MULTIPLES OF 8. + * + * As the device fills RBs (referenced from contiguous RBDs within the circular + * buffer), it updates the Rx status buffer in host DRAM, 2) described above, + * to tell the driver the index of the latest filled RBD. The driver must + * read this "read" index from DRAM after receiving an Rx interrupt from device + * + * The driver must also internally keep track of a third index, which is the + * next RBD to process. When receiving an Rx interrupt, driver should process + * all filled but unprocessed RBs up to, but not including, the RB + * corresponding to the "read" index. For example, if "read" index becomes "1", + * driver may process the RB pointed to by RBD 0. Depending on volume of + * traffic, there may be many RBs to process. + * + * If read index == write index, device thinks there is no room to put new data. + * Due to this, the maximum number of filled RBs is 255, instead of 256. To + * be safe, make sure that there is a gap of at least 2 RBDs between "write" + * and "read" indexes; that is, make sure that there are no more than 254 + * buffers waiting to be filled. + */ +#define FH_MEM_RSCSR_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xBC0) +#define FH_MEM_RSCSR_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xC00) +#define FH_MEM_RSCSR_CHNL0 (FH_MEM_RSCSR_LOWER_BOUND) + +/** + * Physical base address of 8-byte Rx Status buffer. + * Bit fields: + * 31-0: Rx status buffer physical base address [35:4], must 16-byte aligned. + */ +#define FH_RSCSR_CHNL0_STTS_WPTR_REG (FH_MEM_RSCSR_CHNL0) + +/** + * Physical base address of Rx Buffer Descriptor Circular Buffer. + * Bit fields: + * 27-0: RBD CD physical base address [35:8], must be 256-byte aligned. + */ +#define FH_RSCSR_CHNL0_RBDCB_BASE_REG (FH_MEM_RSCSR_CHNL0 + 0x004) + +/** + * Rx write pointer (index, really!). + * Bit fields: + * 11-0: Index of driver's most recent prepared-to-be-filled RBD, + 1. + * NOTE: For 256-entry circular buffer, use only bits [7:0]. + */ +#define FH_RSCSR_CHNL0_RBDCB_WPTR_REG (FH_MEM_RSCSR_CHNL0 + 0x008) +#define FH_RSCSR_CHNL0_WPTR (FH_RSCSR_CHNL0_RBDCB_WPTR_REG) + +#define FW_RSCSR_CHNL0_RXDCB_RDPTR_REG (FH_MEM_RSCSR_CHNL0 + 0x00c) +#define FH_RSCSR_CHNL0_RDPTR FW_RSCSR_CHNL0_RXDCB_RDPTR_REG + +/** + * Rx Config/Status Registers (RCSR) + * Rx Config Reg for channel 0 (only channel used) + * + * Driver must initialize FH_MEM_RCSR_CHNL0_CONFIG_REG as follows for + * normal operation (see bit fields). + * + * Clearing FH_MEM_RCSR_CHNL0_CONFIG_REG to 0 turns off Rx DMA. + * Driver should poll FH_MEM_RSSR_RX_STATUS_REG for + * FH_RSSR_CHNL0_RX_STATUS_CHNL_IDLE (bit 24) before continuing. + * + * Bit fields: + * 31-30: Rx DMA channel enable: '00' off/pause, '01' pause at end of frame, + * '10' operate normally + * 29-24: reserved + * 23-20: # RBDs in circular buffer = 2^value; use "8" for 256 RBDs (normal), + * min "5" for 32 RBDs, max "12" for 4096 RBDs. + * 19-18: reserved + * 17-16: size of each receive buffer; '00' 4K (normal), '01' 8K, + * '10' 12K, '11' 16K. + * 15-14: reserved + * 13-12: IRQ destination; '00' none, '01' host driver (normal operation) + * 11- 4: timeout for closing Rx buffer and interrupting host (units 32 usec) + * typical value 0x10 (about 1/2 msec) + * 3- 0: reserved + */ +#define FH_MEM_RCSR_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xC00) +#define FH_MEM_RCSR_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xCC0) +#define FH_MEM_RCSR_CHNL0 (FH_MEM_RCSR_LOWER_BOUND) + +#define FH_MEM_RCSR_CHNL0_CONFIG_REG (FH_MEM_RCSR_CHNL0) +#define FH_MEM_RCSR_CHNL0_RBDCB_WPTR (FH_MEM_RCSR_CHNL0 + 0x8) +#define FH_MEM_RCSR_CHNL0_FLUSH_RB_REQ (FH_MEM_RCSR_CHNL0 + 0x10) + +#define FH_RCSR_CHNL0_RX_CONFIG_RB_TIMEOUT_MSK (0x00000FF0) /* bits 4-11 */ +#define FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_MSK (0x00001000) /* bits 12 */ +#define FH_RCSR_CHNL0_RX_CONFIG_SINGLE_FRAME_MSK (0x00008000) /* bit 15 */ +#define FH_RCSR_CHNL0_RX_CONFIG_RB_SIZE_MSK (0x00030000) /* bits 16-17 */ +#define FH_RCSR_CHNL0_RX_CONFIG_RBDBC_SIZE_MSK (0x00F00000) /* bits 20-23 */ +#define FH_RCSR_CHNL0_RX_CONFIG_DMA_CHNL_EN_MSK (0xC0000000) /* bits 30-31*/ + +#define FH_RCSR_RX_CONFIG_RBDCB_SIZE_POS (20) +#define FH_RCSR_RX_CONFIG_REG_IRQ_RBTH_POS (4) +#define RX_RB_TIMEOUT (0x11) + +#define FH_RCSR_RX_CONFIG_CHNL_EN_PAUSE_VAL (0x00000000) +#define FH_RCSR_RX_CONFIG_CHNL_EN_PAUSE_EOF_VAL (0x40000000) +#define FH_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL (0x80000000) + +#define FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K (0x00000000) +#define FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_8K (0x00010000) +#define FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_12K (0x00020000) +#define FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_16K (0x00030000) + +#define FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY (0x00000004) +#define FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_NO_INT_VAL (0x00000000) +#define FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL (0x00001000) + +/** + * Rx Shared Status Registers (RSSR) + * + * After stopping Rx DMA channel (writing 0 to + * FH_MEM_RCSR_CHNL0_CONFIG_REG), driver must poll + * FH_MEM_RSSR_RX_STATUS_REG until Rx channel is idle. + * + * Bit fields: + * 24: 1 = Channel 0 is idle + * + * FH_MEM_RSSR_SHARED_CTRL_REG and FH_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV + * contain default values that should not be altered by the driver. + */ +#define FH_MEM_RSSR_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xC40) +#define FH_MEM_RSSR_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xD00) + +#define FH_MEM_RSSR_SHARED_CTRL_REG (FH_MEM_RSSR_LOWER_BOUND) +#define FH_MEM_RSSR_RX_STATUS_REG (FH_MEM_RSSR_LOWER_BOUND + 0x004) +#define FH_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV\ + (FH_MEM_RSSR_LOWER_BOUND + 0x008) + +#define FH_RSSR_CHNL0_RX_STATUS_CHNL_IDLE (0x01000000) + +#define FH_MEM_TFDIB_REG1_ADDR_BITSHIFT 28 + +/* TFDB Area - TFDs buffer table */ +#define FH_MEM_TFDIB_DRAM_ADDR_LSB_MSK (0xFFFFFFFF) +#define FH_TFDIB_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0x900) +#define FH_TFDIB_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0x958) +#define FH_TFDIB_CTRL0_REG(_chnl) (FH_TFDIB_LOWER_BOUND + 0x8 * (_chnl)) +#define FH_TFDIB_CTRL1_REG(_chnl) (FH_TFDIB_LOWER_BOUND + 0x8 * (_chnl) + 0x4) + +/** + * Transmit DMA Channel Control/Status Registers (TCSR) + * + * Device has one configuration register for each of 8 Tx DMA/FIFO channels + * supported in hardware (don't confuse these with the 16 Tx queues in DRAM, + * which feed the DMA/FIFO channels); config regs are separated by 0x20 bytes. + * + * To use a Tx DMA channel, driver must initialize its + * FH_TCSR_CHNL_TX_CONFIG_REG(chnl) with: + * + * FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE | + * FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE_VAL + * + * All other bits should be 0. + * + * Bit fields: + * 31-30: Tx DMA channel enable: '00' off/pause, '01' pause at end of frame, + * '10' operate normally + * 29- 4: Reserved, set to "0" + * 3: Enable internal DMA requests (1, normal operation), disable (0) + * 2- 0: Reserved, set to "0" + */ +#define FH_TCSR_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xD00) +#define FH_TCSR_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xE60) + +/* Find Control/Status reg for given Tx DMA/FIFO channel */ +#define FH_TCSR_CHNL_NUM (8) + +/* TCSR: tx_config register values */ +#define FH_TCSR_CHNL_TX_CONFIG_REG(_chnl) \ + (FH_TCSR_LOWER_BOUND + 0x20 * (_chnl)) +#define FH_TCSR_CHNL_TX_CREDIT_REG(_chnl) \ + (FH_TCSR_LOWER_BOUND + 0x20 * (_chnl) + 0x4) +#define FH_TCSR_CHNL_TX_BUF_STS_REG(_chnl) \ + (FH_TCSR_LOWER_BOUND + 0x20 * (_chnl) + 0x8) + +#define FH_TCSR_TX_CONFIG_REG_VAL_MSG_MODE_TXF (0x00000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_MSG_MODE_DRV (0x00000001) + +#define FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_DISABLE (0x00000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE (0x00000008) + +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_NOINT (0x00000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_ENDTFD (0x00100000) +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_IFTFD (0x00200000) + +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_NOINT (0x00000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_ENDTFD (0x00400000) +#define FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_RTC_IFTFD (0x00800000) + +#define FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE (0x00000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE_EOF (0x40000000) +#define FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE (0x80000000) + +#define FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_EMPTY (0x00000000) +#define FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_WAIT (0x00002000) +#define FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_VALID (0x00000003) + +#define FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_NUM (20) +#define FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_IDX (12) + +/** + * Tx Shared Status Registers (TSSR) + * + * After stopping Tx DMA channel (writing 0 to + * FH_TCSR_CHNL_TX_CONFIG_REG(chnl)), driver must poll + * FH_TSSR_TX_STATUS_REG until selected Tx channel is idle + * (channel's buffers empty | no pending requests). + * + * Bit fields: + * 31-24: 1 = Channel buffers empty (channel 7:0) + * 23-16: 1 = No pending requests (channel 7:0) + */ +#define FH_TSSR_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0xEA0) +#define FH_TSSR_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0xEC0) + +#define FH_TSSR_TX_STATUS_REG (FH_TSSR_LOWER_BOUND + 0x010) + +/** + * Bit fields for TSSR(Tx Shared Status & Control) error status register: + * 31: Indicates an address error when accessed to internal memory + * uCode/driver must write "1" in order to clear this flag + * 30: Indicates that Host did not send the expected number of dwords to FH + * uCode/driver must write "1" in order to clear this flag + * 16-9:Each status bit is for one channel. Indicates that an (Error) ActDMA + * command was received from the scheduler while the TRB was already full + * with previous command + * uCode/driver must write "1" in order to clear this flag + * 7-0: Each status bit indicates a channel's TxCredit error. When an error + * bit is set, it indicates that the FH has received a full indication + * from the RTC TxFIFO and the current value of the TxCredit counter was + * not equal to zero. This mean that the credit mechanism was not + * synchronized to the TxFIFO status + * uCode/driver must write "1" in order to clear this flag + */ +#define FH_TSSR_TX_ERROR_REG (FH_TSSR_LOWER_BOUND + 0x018) +#define FH_TSSR_TX_MSG_CONFIG_REG (FH_TSSR_LOWER_BOUND + 0x008) + +#define FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(_chnl) ((1 << (_chnl)) << 16) + +/* Tx service channels */ +#define FH_SRVC_CHNL (9) +#define FH_SRVC_LOWER_BOUND (FH_MEM_LOWER_BOUND + 0x9C8) +#define FH_SRVC_UPPER_BOUND (FH_MEM_LOWER_BOUND + 0x9D0) +#define FH_SRVC_CHNL_SRAM_ADDR_REG(_chnl) \ + (FH_SRVC_LOWER_BOUND + ((_chnl) - 9) * 0x4) + +#define FH_TX_CHICKEN_BITS_REG (FH_MEM_LOWER_BOUND + 0xE98) +#define FH_TX_TRB_REG(_chan) (FH_MEM_LOWER_BOUND + 0x958 + (_chan) * 4) + +/* Instruct FH to increment the retry count of a packet when + * it is brought from the memory to TX-FIFO + */ +#define FH_TX_CHICKEN_BITS_SCD_AUTO_RETRY_EN (0x00000002) + +#define RX_QUEUE_SIZE 256 +#define RX_QUEUE_MASK 255 +#define RX_QUEUE_SIZE_LOG 8 + +/* + * RX related structures and functions + */ +#define RX_FREE_BUFFERS 64 +#define RX_LOW_WATERMARK 8 + +/** + * struct iwl_rb_status - reseve buffer status + * host memory mapped FH registers + * @closed_rb_num [0:11] - Indicates the index of the RB which was closed + * @closed_fr_num [0:11] - Indicates the index of the RX Frame which was closed + * @finished_rb_num [0:11] - Indicates the index of the current RB + * in which the last frame was written to + * @finished_fr_num [0:11] - Indicates the index of the RX Frame + * which was transferred + */ +struct iwl_rb_status { + __le16 closed_rb_num; + __le16 closed_fr_num; + __le16 finished_rb_num; + __le16 finished_fr_nam; + __le32 __unused; +} __packed; + + +#define TFD_QUEUE_SIZE_MAX (256) +#define TFD_QUEUE_SIZE_BC_DUP (64) +#define TFD_QUEUE_BC_SIZE (TFD_QUEUE_SIZE_MAX + TFD_QUEUE_SIZE_BC_DUP) +#define IWL_TX_DMA_MASK DMA_BIT_MASK(36) +#define IWL_NUM_OF_TBS 20 + +static inline u8 iwl_get_dma_hi_addr(dma_addr_t addr) +{ + return (sizeof(addr) > sizeof(u32) ? (addr >> 16) >> 16 : 0) & 0xF; +} +/** + * struct iwl_tfd_tb transmit buffer descriptor within transmit frame descriptor + * + * This structure contains dma address and length of transmission address + * + * @lo: low [31:0] portion of the dma address of TX buffer + * every even is unaligned on 16 bit boundary + * @hi_n_len 0-3 [35:32] portion of dma + * 4-15 length of the tx buffer + */ +struct iwl_tfd_tb { + __le32 lo; + __le16 hi_n_len; +} __packed; + +/** + * struct iwl_tfd + * + * Transmit Frame Descriptor (TFD) + * + * @ __reserved1[3] reserved + * @ num_tbs 0-4 number of active tbs + * 5 reserved + * 6-7 padding (not used) + * @ tbs[20] transmit frame buffer descriptors + * @ __pad padding + * + * Each Tx queue uses a circular buffer of 256 TFDs stored in host DRAM. + * Both driver and device share these circular buffers, each of which must be + * contiguous 256 TFDs x 128 bytes-per-TFD = 32 KBytes + * + * Driver must indicate the physical address of the base of each + * circular buffer via the FH_MEM_CBBC_QUEUE registers. + * + * Each TFD contains pointer/size information for up to 20 data buffers + * in host DRAM. These buffers collectively contain the (one) frame described + * by the TFD. Each buffer must be a single contiguous block of memory within + * itself, but buffers may be scattered in host DRAM. Each buffer has max size + * of (4K - 4). The concatenates all of a TFD's buffers into a single + * Tx frame, up to 8 KBytes in size. + * + * A maximum of 255 (not 256!) TFDs may be on a queue waiting for Tx. + */ +struct iwl_tfd { + u8 __reserved1[3]; + u8 num_tbs; + struct iwl_tfd_tb tbs[IWL_NUM_OF_TBS]; + __le32 __pad; +} __packed; + +/* Keep Warm Size */ +#define IWL_KW_SIZE 0x1000 /* 4k */ + +/* Fixed (non-configurable) rx data from phy */ + +/** + * struct iwlagn_schedq_bc_tbl scheduler byte count table + * base physical address provided by SCD_DRAM_BASE_ADDR + * @tfd_offset 0-12 - tx command byte count + * 12-16 - station index + */ +struct iwlagn_scd_bc_tbl { + __le16 tfd_offset[TFD_QUEUE_BC_SIZE]; +} __packed; + +#endif /* !__iwl_fh_h__ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-fw-file.h b/drivers/net/wireless/iwlwifi/iwl-fw-file.h new file mode 100644 index 0000000..8b6c6fd --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-fw-file.h @@ -0,0 +1,156 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef __iwl_fw_file_h__ +#define __iwl_fw_file_h__ + +#include + +/* v1/v2 uCode file layout */ +struct iwl_ucode_header { + __le32 ver; /* major/minor/API/serial */ + union { + struct { + __le32 inst_size; /* bytes of runtime code */ + __le32 data_size; /* bytes of runtime data */ + __le32 init_size; /* bytes of init code */ + __le32 init_data_size; /* bytes of init data */ + __le32 boot_size; /* bytes of bootstrap code */ + u8 data[0]; /* in same order as sizes */ + } v1; + struct { + __le32 build; /* build number */ + __le32 inst_size; /* bytes of runtime code */ + __le32 data_size; /* bytes of runtime data */ + __le32 init_size; /* bytes of init code */ + __le32 init_data_size; /* bytes of init data */ + __le32 boot_size; /* bytes of bootstrap code */ + u8 data[0]; /* in same order as sizes */ + } v2; + } u; +}; + +/* + * new TLV uCode file layout + * + * The new TLV file format contains TLVs, that each specify + * some piece of data. + */ + +enum iwl_ucode_tlv_type { + IWL_UCODE_TLV_INVALID = 0, /* unused */ + IWL_UCODE_TLV_INST = 1, + IWL_UCODE_TLV_DATA = 2, + IWL_UCODE_TLV_INIT = 3, + IWL_UCODE_TLV_INIT_DATA = 4, + IWL_UCODE_TLV_BOOT = 5, + IWL_UCODE_TLV_PROBE_MAX_LEN = 6, /* a u32 value */ + IWL_UCODE_TLV_PAN = 7, + IWL_UCODE_TLV_RUNT_EVTLOG_PTR = 8, + IWL_UCODE_TLV_RUNT_EVTLOG_SIZE = 9, + IWL_UCODE_TLV_RUNT_ERRLOG_PTR = 10, + IWL_UCODE_TLV_INIT_EVTLOG_PTR = 11, + IWL_UCODE_TLV_INIT_EVTLOG_SIZE = 12, + IWL_UCODE_TLV_INIT_ERRLOG_PTR = 13, + IWL_UCODE_TLV_ENHANCE_SENS_TBL = 14, + IWL_UCODE_TLV_PHY_CALIBRATION_SIZE = 15, + IWL_UCODE_TLV_WOWLAN_INST = 16, + IWL_UCODE_TLV_WOWLAN_DATA = 17, + IWL_UCODE_TLV_FLAGS = 18, + IWL_UCODE_TLV_SEC_RT = 19, + IWL_UCODE_TLV_SEC_INIT = 20, + IWL_UCODE_TLV_SEC_WOWLAN = 21, + IWL_UCODE_TLV_DEF_CALIB = 22, + IWL_UCODE_TLV_PHY_SKU = 23, +}; + +struct iwl_ucode_tlv { + __le32 type; /* see above */ + __le32 length; /* not including type/length fields */ + u8 data[0]; +}; + +#define IWL_TLV_UCODE_MAGIC 0x0a4c5749 + +struct iwl_tlv_ucode_header { + /* + * The TLV style ucode header is distinguished from + * the v1/v2 style header by first four bytes being + * zero, as such is an invalid combination of + * major/minor/API/serial versions. + */ + __le32 zero; + __le32 magic; + u8 human_readable[64]; + __le32 ver; /* major/minor/API/serial */ + __le32 build; + __le64 ignore; + /* + * The data contained herein has a TLV layout, + * see above for the TLV header and types. + * Note that each TLV is padded to a length + * that is a multiple of 4 for alignment. + */ + u8 data[0]; +}; + +#endif /* __iwl_fw_file_h__ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-fw.h b/drivers/net/wireless/iwlwifi/iwl-fw.h new file mode 100644 index 0000000..c4c446d --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-fw.h @@ -0,0 +1,218 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef __iwl_fw_h__ +#define __iwl_fw_h__ +#include +#include + +/** + * enum iwl_ucode_tlv_flag - ucode API flags + * @IWL_UCODE_TLV_FLAGS_PAN: This is PAN capable microcode; this previously + * was a separate TLV but moved here to save space. + * @IWL_UCODE_TLV_FLAGS_NEWSCAN: new uCode scan behaviour on hidden SSID, + * treats good CRC threshold as a boolean + * @IWL_UCODE_TLV_FLAGS_MFP: This uCode image supports MFP (802.11w). + * @IWL_UCODE_TLV_FLAGS_P2P: This uCode image supports P2P. + * @IWL_UCODE_TLV_FLAGS_DW_BC_TABLE: The SCD byte count table is in DWORDS + */ +enum iwl_ucode_tlv_flag { + IWL_UCODE_TLV_FLAGS_PAN = BIT(0), + IWL_UCODE_TLV_FLAGS_NEWSCAN = BIT(1), + IWL_UCODE_TLV_FLAGS_MFP = BIT(2), + IWL_UCODE_TLV_FLAGS_P2P = BIT(3), + IWL_UCODE_TLV_FLAGS_DW_BC_TABLE = BIT(4), +}; + +/* The default calibrate table size if not specified by firmware file */ +#define IWL_DEFAULT_STANDARD_PHY_CALIBRATE_TBL_SIZE 18 +#define IWL_MAX_STANDARD_PHY_CALIBRATE_TBL_SIZE 19 +#define IWL_MAX_PHY_CALIBRATE_TBL_SIZE 253 + +/** + * enum iwl_ucode_type + * + * The type of ucode. + * + * @IWL_UCODE_REGULAR: Normal runtime ucode + * @IWL_UCODE_INIT: Initial ucode + * @IWL_UCODE_WOWLAN: Wake on Wireless enabled ucode + */ +enum iwl_ucode_type { + IWL_UCODE_REGULAR, + IWL_UCODE_INIT, + IWL_UCODE_WOWLAN, + IWL_UCODE_TYPE_MAX, +}; + +/* + * enumeration of ucode section. + * This enumeration is used for legacy tlv style (before 16.0 uCode). + */ +enum iwl_ucode_sec { + IWL_UCODE_SECTION_INST, + IWL_UCODE_SECTION_DATA, +}; +/* + * For 16.0 uCode and above, there is no differentiation between sections, + * just an offset to the HW address. + */ +#define IWL_UCODE_SECTION_MAX 4 + +struct iwl_ucode_capabilities { + u32 max_probe_length; + u32 standard_phy_calibration_size; + u32 flags; +}; + +/* one for each uCode image (inst/data, init/runtime/wowlan) */ +struct fw_desc { + const void *data; /* vmalloc'ed data */ + u32 len; /* size in bytes */ + u32 offset; /* offset in the device */ +}; + +struct fw_img { + struct fw_desc sec[IWL_UCODE_SECTION_MAX]; +}; + +/* uCode version contains 4 values: Major/Minor/API/Serial */ +#define IWL_UCODE_MAJOR(ver) (((ver) & 0xFF000000) >> 24) +#define IWL_UCODE_MINOR(ver) (((ver) & 0x00FF0000) >> 16) +#define IWL_UCODE_API(ver) (((ver) & 0x0000FF00) >> 8) +#define IWL_UCODE_SERIAL(ver) ((ver) & 0x000000FF) + +/* + * Calibration control struct. + * Sent as part of the phy configuration command. + * @flow_trigger: bitmap for which calibrations to perform according to + * flow triggers. + * @event_trigger: bitmap for which calibrations to perform according to + * event triggers. + */ +struct iwl_tlv_calib_ctrl { + __le32 flow_trigger; + __le32 event_trigger; +} __packed; + +enum iwl_fw_phy_cfg { + FW_PHY_CFG_RADIO_TYPE_POS = 0, + FW_PHY_CFG_RADIO_TYPE = 0x3 << FW_PHY_CFG_RADIO_TYPE_POS, + FW_PHY_CFG_RADIO_STEP_POS = 2, + FW_PHY_CFG_RADIO_STEP = 0x3 << FW_PHY_CFG_RADIO_STEP_POS, + FW_PHY_CFG_RADIO_DASH_POS = 4, + FW_PHY_CFG_RADIO_DASH = 0x3 << FW_PHY_CFG_RADIO_DASH_POS, + FW_PHY_CFG_TX_CHAIN_POS = 16, + FW_PHY_CFG_TX_CHAIN = 0xf << FW_PHY_CFG_TX_CHAIN_POS, + FW_PHY_CFG_RX_CHAIN_POS = 20, + FW_PHY_CFG_RX_CHAIN = 0xf << FW_PHY_CFG_RX_CHAIN_POS, +}; + +/** + * struct iwl_fw - variables associated with the firmware + * + * @ucode_ver: ucode version from the ucode file + * @fw_version: firmware version string + * @img: ucode image like ucode_rt, ucode_init, ucode_wowlan. + * @ucode_capa: capabilities parsed from the ucode file. + * @enhance_sensitivity_table: device can do enhanced sensitivity. + * @init_evtlog_ptr: event log offset for init ucode. + * @init_evtlog_size: event log size for init ucode. + * @init_errlog_ptr: error log offfset for init ucode. + * @inst_evtlog_ptr: event log offset for runtime ucode. + * @inst_evtlog_size: event log size for runtime ucode. + * @inst_errlog_ptr: error log offfset for runtime ucode. + * @mvm_fw: indicates this is MVM firmware + */ +struct iwl_fw { + u32 ucode_ver; + + char fw_version[ETHTOOL_FWVERS_LEN]; + + /* ucode images */ + struct fw_img img[IWL_UCODE_TYPE_MAX]; + + struct iwl_ucode_capabilities ucode_capa; + bool enhance_sensitivity_table; + + u32 init_evtlog_ptr, init_evtlog_size, init_errlog_ptr; + u32 inst_evtlog_ptr, inst_evtlog_size, inst_errlog_ptr; + + struct iwl_tlv_calib_ctrl default_calib[IWL_UCODE_TYPE_MAX]; + u32 phy_config; + + bool mvm_fw; +}; + +static inline u8 iwl_fw_valid_tx_ant(const struct iwl_fw *fw) +{ + return (fw->phy_config & FW_PHY_CFG_TX_CHAIN) >> + FW_PHY_CFG_TX_CHAIN_POS; +} + +static inline u8 iwl_fw_valid_rx_ant(const struct iwl_fw *fw) +{ + return (fw->phy_config & FW_PHY_CFG_RX_CHAIN) >> + FW_PHY_CFG_RX_CHAIN_POS; +} + +#endif /* __iwl_fw_h__ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-io.c b/drivers/net/wireless/iwlwifi/iwl-io.c new file mode 100644 index 0000000..305c81f --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-io.c @@ -0,0 +1,168 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ +#include +#include +#include + +#include "iwl-drv.h" +#include "iwl-io.h" +#include "iwl-csr.h" +#include "iwl-debug.h" + +#define IWL_POLL_INTERVAL 10 /* microseconds */ + +int iwl_poll_bit(struct iwl_trans *trans, u32 addr, + u32 bits, u32 mask, int timeout) +{ + int t = 0; + + do { + if ((iwl_read32(trans, addr) & mask) == (bits & mask)) + return t; + udelay(IWL_POLL_INTERVAL); + t += IWL_POLL_INTERVAL; + } while (t < timeout); + + return -ETIMEDOUT; +} +IWL_EXPORT_SYMBOL(iwl_poll_bit); + +u32 iwl_read_direct32(struct iwl_trans *trans, u32 reg) +{ + u32 value = 0x5a5a5a5a; + unsigned long flags; + if (iwl_trans_grab_nic_access(trans, false, &flags)) { + value = iwl_read32(trans, reg); + iwl_trans_release_nic_access(trans, &flags); + } + + return value; +} +IWL_EXPORT_SYMBOL(iwl_read_direct32); + +void iwl_write_direct32(struct iwl_trans *trans, u32 reg, u32 value) +{ + unsigned long flags; + + if (iwl_trans_grab_nic_access(trans, false, &flags)) { + iwl_write32(trans, reg, value); + iwl_trans_release_nic_access(trans, &flags); + } +} +IWL_EXPORT_SYMBOL(iwl_write_direct32); + +int iwl_poll_direct_bit(struct iwl_trans *trans, u32 addr, u32 mask, + int timeout) +{ + int t = 0; + + do { + if ((iwl_read_direct32(trans, addr) & mask) == mask) + return t; + udelay(IWL_POLL_INTERVAL); + t += IWL_POLL_INTERVAL; + } while (t < timeout); + + return -ETIMEDOUT; +} +IWL_EXPORT_SYMBOL(iwl_poll_direct_bit); + +static inline u32 __iwl_read_prph(struct iwl_trans *trans, u32 ofs) +{ + u32 val = iwl_trans_read_prph(trans, ofs); + trace_iwlwifi_dev_ioread_prph32(trans->dev, ofs, val); + return val; +} + +static inline void __iwl_write_prph(struct iwl_trans *trans, u32 ofs, u32 val) +{ + trace_iwlwifi_dev_iowrite_prph32(trans->dev, ofs, val); + iwl_trans_write_prph(trans, ofs, val); +} + +u32 iwl_read_prph(struct iwl_trans *trans, u32 ofs) +{ + unsigned long flags; + u32 val = 0x5a5a5a5a; + + if (iwl_trans_grab_nic_access(trans, false, &flags)) { + val = __iwl_read_prph(trans, ofs); + iwl_trans_release_nic_access(trans, &flags); + } + return val; +} +IWL_EXPORT_SYMBOL(iwl_read_prph); + +void iwl_write_prph(struct iwl_trans *trans, u32 ofs, u32 val) +{ + unsigned long flags; + + if (iwl_trans_grab_nic_access(trans, false, &flags)) { + __iwl_write_prph(trans, ofs, val); + iwl_trans_release_nic_access(trans, &flags); + } +} +IWL_EXPORT_SYMBOL(iwl_write_prph); + +void iwl_set_bits_prph(struct iwl_trans *trans, u32 ofs, u32 mask) +{ + unsigned long flags; + + if (iwl_trans_grab_nic_access(trans, false, &flags)) { + __iwl_write_prph(trans, ofs, + __iwl_read_prph(trans, ofs) | mask); + iwl_trans_release_nic_access(trans, &flags); + } +} +IWL_EXPORT_SYMBOL(iwl_set_bits_prph); + +void iwl_set_bits_mask_prph(struct iwl_trans *trans, u32 ofs, + u32 bits, u32 mask) +{ + unsigned long flags; + + if (iwl_trans_grab_nic_access(trans, false, &flags)) { + __iwl_write_prph(trans, ofs, + (__iwl_read_prph(trans, ofs) & mask) | bits); + iwl_trans_release_nic_access(trans, &flags); + } +} +IWL_EXPORT_SYMBOL(iwl_set_bits_mask_prph); + +void iwl_clear_bits_prph(struct iwl_trans *trans, u32 ofs, u32 mask) +{ + unsigned long flags; + u32 val; + + if (iwl_trans_grab_nic_access(trans, false, &flags)) { + val = __iwl_read_prph(trans, ofs); + __iwl_write_prph(trans, ofs, (val & ~mask)); + iwl_trans_release_nic_access(trans, &flags); + } +} +IWL_EXPORT_SYMBOL(iwl_clear_bits_prph); diff --git a/drivers/net/wireless/iwlwifi/iwl-io.h b/drivers/net/wireless/iwlwifi/iwl-io.h new file mode 100644 index 0000000..fd9f5b9 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-io.h @@ -0,0 +1,80 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#ifndef __iwl_io_h__ +#define __iwl_io_h__ + +#include "iwl-devtrace.h" +#include "iwl-trans.h" + +static inline void iwl_write8(struct iwl_trans *trans, u32 ofs, u8 val) +{ + trace_iwlwifi_dev_iowrite8(trans->dev, ofs, val); + iwl_trans_write8(trans, ofs, val); +} + +static inline void iwl_write32(struct iwl_trans *trans, u32 ofs, u32 val) +{ + trace_iwlwifi_dev_iowrite32(trans->dev, ofs, val); + iwl_trans_write32(trans, ofs, val); +} + +static inline u32 iwl_read32(struct iwl_trans *trans, u32 ofs) +{ + u32 val = iwl_trans_read32(trans, ofs); + trace_iwlwifi_dev_ioread32(trans->dev, ofs, val); + return val; +} + +static inline void iwl_set_bit(struct iwl_trans *trans, u32 reg, u32 mask) +{ + iwl_trans_set_bits_mask(trans, reg, mask, mask); +} + +static inline void iwl_clear_bit(struct iwl_trans *trans, u32 reg, u32 mask) +{ + iwl_trans_set_bits_mask(trans, reg, mask, 0); +} + +int iwl_poll_bit(struct iwl_trans *trans, u32 addr, + u32 bits, u32 mask, int timeout); +int iwl_poll_direct_bit(struct iwl_trans *trans, u32 addr, u32 mask, + int timeout); + +u32 iwl_read_direct32(struct iwl_trans *trans, u32 reg); +void iwl_write_direct32(struct iwl_trans *trans, u32 reg, u32 value); + + +u32 iwl_read_prph(struct iwl_trans *trans, u32 ofs); +void iwl_write_prph(struct iwl_trans *trans, u32 ofs, u32 val); +void iwl_set_bits_prph(struct iwl_trans *trans, u32 ofs, u32 mask); +void iwl_set_bits_mask_prph(struct iwl_trans *trans, u32 ofs, + u32 bits, u32 mask); +void iwl_clear_bits_prph(struct iwl_trans *trans, u32 ofs, u32 mask); + +#endif diff --git a/drivers/net/wireless/iwlwifi/iwl-modparams.h b/drivers/net/wireless/iwlwifi/iwl-modparams.h new file mode 100644 index 0000000..d6f6c37 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-modparams.h @@ -0,0 +1,124 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#ifndef __iwl_modparams_h__ +#define __iwl_modparams_h__ + +#include +#include +#include +#include + +extern struct iwl_mod_params iwlwifi_mod_params; + +enum iwl_power_level { + IWL_POWER_INDEX_1, + IWL_POWER_INDEX_2, + IWL_POWER_INDEX_3, + IWL_POWER_INDEX_4, + IWL_POWER_INDEX_5, + IWL_POWER_NUM +}; + +#define IWL_DISABLE_HT_ALL BIT(0) +#define IWL_DISABLE_HT_TXAGG BIT(1) +#define IWL_DISABLE_HT_RXAGG BIT(2) + +/** + * struct iwl_mod_params + * + * Holds the module parameters + * + * @sw_crypto: using hardware encryption, default = 0 + * @disable_11n: disable 11n capabilities, default = 0, + * use IWL_DISABLE_HT_* constants + * @amsdu_size_8K: enable 8K amsdu size, default = 0 + * @restart_fw: restart firmware, default = 1 + * @plcp_check: enable plcp health check, default = true + * @wd_disable: enable stuck queue check, default = 0 + * @bt_coex_active: enable bt coex, default = true + * @led_mode: system default, default = 0 + * @power_save: disable power save, default = false + * @power_level: power level, default = 1 + * @debug_level: levels are IWL_DL_* + * @ant_coupling: antenna coupling in dB, default = 0 + * @bt_ch_announce: BT channel inhibition, default = enable + * @auto_agg: enable agg. without check, default = true + */ +struct iwl_mod_params { + int sw_crypto; + unsigned int disable_11n; + int amsdu_size_8K; + bool restart_fw; + bool plcp_check; + int wd_disable; + bool bt_coex_active; + int led_mode; + bool power_save; + int power_level; + u32 debug_level; + int ant_coupling; + bool bt_ch_announce; + bool auto_agg; +}; + +#endif /* #__iwl_modparams_h__ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-notif-wait.c b/drivers/net/wireless/iwlwifi/iwl-notif-wait.c new file mode 100644 index 0000000..940b8a9 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-notif-wait.c @@ -0,0 +1,191 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#include +#include + +#include "iwl-drv.h" +#include "iwl-notif-wait.h" + + +void iwl_notification_wait_init(struct iwl_notif_wait_data *notif_wait) +{ + spin_lock_init(¬if_wait->notif_wait_lock); + INIT_LIST_HEAD(¬if_wait->notif_waits); + init_waitqueue_head(¬if_wait->notif_waitq); +} +IWL_EXPORT_SYMBOL(iwl_notification_wait_init); + +void iwl_notification_wait_notify(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt) +{ + bool triggered = false; + + if (!list_empty(¬if_wait->notif_waits)) { + struct iwl_notification_wait *w; + + spin_lock(¬if_wait->notif_wait_lock); + list_for_each_entry(w, ¬if_wait->notif_waits, list) { + int i; + bool found = false; + + /* + * If it already finished (triggered) or has been + * aborted then don't evaluate it again to avoid races, + * Otherwise the function could be called again even + * though it returned true before + */ + if (w->triggered || w->aborted) + continue; + + for (i = 0; i < w->n_cmds; i++) { + if (w->cmds[i] == pkt->hdr.cmd) { + found = true; + break; + } + } + if (!found) + continue; + + if (!w->fn || w->fn(notif_wait, pkt, w->fn_data)) { + w->triggered = true; + triggered = true; + } + } + spin_unlock(¬if_wait->notif_wait_lock); + + } + + if (triggered) + wake_up_all(¬if_wait->notif_waitq); +} +IWL_EXPORT_SYMBOL(iwl_notification_wait_notify); + +void iwl_abort_notification_waits(struct iwl_notif_wait_data *notif_wait) +{ + struct iwl_notification_wait *wait_entry; + + spin_lock(¬if_wait->notif_wait_lock); + list_for_each_entry(wait_entry, ¬if_wait->notif_waits, list) + wait_entry->aborted = true; + spin_unlock(¬if_wait->notif_wait_lock); + + wake_up_all(¬if_wait->notif_waitq); +} +IWL_EXPORT_SYMBOL(iwl_abort_notification_waits); + +void +iwl_init_notification_wait(struct iwl_notif_wait_data *notif_wait, + struct iwl_notification_wait *wait_entry, + const u8 *cmds, int n_cmds, + bool (*fn)(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data), + void *fn_data) +{ + if (WARN_ON(n_cmds > MAX_NOTIF_CMDS)) + n_cmds = MAX_NOTIF_CMDS; + + wait_entry->fn = fn; + wait_entry->fn_data = fn_data; + wait_entry->n_cmds = n_cmds; + memcpy(wait_entry->cmds, cmds, n_cmds); + wait_entry->triggered = false; + wait_entry->aborted = false; + + spin_lock_bh(¬if_wait->notif_wait_lock); + list_add(&wait_entry->list, ¬if_wait->notif_waits); + spin_unlock_bh(¬if_wait->notif_wait_lock); +} +IWL_EXPORT_SYMBOL(iwl_init_notification_wait); + +int iwl_wait_notification(struct iwl_notif_wait_data *notif_wait, + struct iwl_notification_wait *wait_entry, + unsigned long timeout) +{ + int ret; + + ret = wait_event_timeout(notif_wait->notif_waitq, + wait_entry->triggered || wait_entry->aborted, + timeout); + + spin_lock_bh(¬if_wait->notif_wait_lock); + list_del(&wait_entry->list); + spin_unlock_bh(¬if_wait->notif_wait_lock); + + if (wait_entry->aborted) + return -EIO; + + /* return value is always >= 0 */ + if (ret <= 0) + return -ETIMEDOUT; + return 0; +} +IWL_EXPORT_SYMBOL(iwl_wait_notification); + +void iwl_remove_notification(struct iwl_notif_wait_data *notif_wait, + struct iwl_notification_wait *wait_entry) +{ + spin_lock_bh(¬if_wait->notif_wait_lock); + list_del(&wait_entry->list); + spin_unlock_bh(¬if_wait->notif_wait_lock); +} +IWL_EXPORT_SYMBOL(iwl_remove_notification); diff --git a/drivers/net/wireless/iwlwifi/iwl-notif-wait.h b/drivers/net/wireless/iwlwifi/iwl-notif-wait.h new file mode 100644 index 0000000..2e2f1c8 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-notif-wait.h @@ -0,0 +1,138 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#ifndef __iwl_notif_wait_h__ +#define __iwl_notif_wait_h__ + +#include + +#include "iwl-trans.h" + +struct iwl_notif_wait_data { + struct list_head notif_waits; + spinlock_t notif_wait_lock; + wait_queue_head_t notif_waitq; +}; + +#define MAX_NOTIF_CMDS 5 + +/** + * struct iwl_notification_wait - notification wait entry + * @list: list head for global list + * @fn: Function called with the notification. If the function + * returns true, the wait is over, if it returns false then + * the waiter stays blocked. If no function is given, any + * of the listed commands will unblock the waiter. + * @cmds: command IDs + * @n_cmds: number of command IDs + * @triggered: waiter should be woken up + * @aborted: wait was aborted + * + * This structure is not used directly, to wait for a + * notification declare it on the stack, and call + * iwlagn_init_notification_wait() with appropriate + * parameters. Then do whatever will cause the ucode + * to notify the driver, and to wait for that then + * call iwlagn_wait_notification(). + * + * Each notification is one-shot. If at some point we + * need to support multi-shot notifications (which + * can't be allocated on the stack) we need to modify + * the code for them. + */ +struct iwl_notification_wait { + struct list_head list; + + bool (*fn)(struct iwl_notif_wait_data *notif_data, + struct iwl_rx_packet *pkt, void *data); + void *fn_data; + + u8 cmds[MAX_NOTIF_CMDS]; + u8 n_cmds; + bool triggered, aborted; +}; + + +/* caller functions */ +void iwl_notification_wait_init(struct iwl_notif_wait_data *notif_data); +void iwl_notification_wait_notify(struct iwl_notif_wait_data *notif_data, + struct iwl_rx_packet *pkt); +void iwl_abort_notification_waits(struct iwl_notif_wait_data *notif_data); + +/* user functions */ +void __acquires(wait_entry) +iwl_init_notification_wait(struct iwl_notif_wait_data *notif_data, + struct iwl_notification_wait *wait_entry, + const u8 *cmds, int n_cmds, + bool (*fn)(struct iwl_notif_wait_data *notif_data, + struct iwl_rx_packet *pkt, void *data), + void *fn_data); + +int __must_check __releases(wait_entry) +iwl_wait_notification(struct iwl_notif_wait_data *notif_data, + struct iwl_notification_wait *wait_entry, + unsigned long timeout); + +void __releases(wait_entry) +iwl_remove_notification(struct iwl_notif_wait_data *notif_data, + struct iwl_notification_wait *wait_entry); + +#endif /* __iwl_notif_wait_h__ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/iwlwifi/iwl-nvm-parse.c new file mode 100644 index 0000000..6199a0a --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-nvm-parse.c @@ -0,0 +1,393 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ +#include +#include +#include +#include "iwl-drv.h" +#include "iwl-modparams.h" +#include "iwl-nvm-parse.h" + +/* NVM offsets (in words) definitions */ +enum wkp_nvm_offsets { + /* NVM HW-Section offset (in words) definitions */ + HW_ADDR = 0x15, + +/* NVM SW-Section offset (in words) definitions */ + NVM_SW_SECTION = 0x1C0, + NVM_VERSION = 0, + RADIO_CFG = 1, + SKU = 2, + N_HW_ADDRS = 3, + NVM_CHANNELS = 0x1E0 - NVM_SW_SECTION, + +/* NVM calibration section offset (in words) definitions */ + NVM_CALIB_SECTION = 0x2B8, + XTAL_CALIB = 0x316 - NVM_CALIB_SECTION +}; + +/* SKU Capabilities (actual values from NVM definition) */ +enum nvm_sku_bits { + NVM_SKU_CAP_BAND_24GHZ = BIT(0), + NVM_SKU_CAP_BAND_52GHZ = BIT(1), + NVM_SKU_CAP_11N_ENABLE = BIT(2), +}; + +/* radio config bits (actual values from NVM definition) */ +#define NVM_RF_CFG_DASH_MSK(x) (x & 0x3) /* bits 0-1 */ +#define NVM_RF_CFG_STEP_MSK(x) ((x >> 2) & 0x3) /* bits 2-3 */ +#define NVM_RF_CFG_TYPE_MSK(x) ((x >> 4) & 0x3) /* bits 4-5 */ +#define NVM_RF_CFG_PNUM_MSK(x) ((x >> 6) & 0x3) /* bits 6-7 */ +#define NVM_RF_CFG_TX_ANT_MSK(x) ((x >> 8) & 0xF) /* bits 8-11 */ +#define NVM_RF_CFG_RX_ANT_MSK(x) ((x >> 12) & 0xF) /* bits 12-15 */ + +/* + * These are the channel numbers in the order that they are stored in the NVM + */ +static const u8 iwl_nvm_channels[] = { + /* 2.4 GHz */ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + /* 5 GHz */ + 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 +}; + +#define IWL_NUM_CHANNELS ARRAY_SIZE(iwl_nvm_channels) +#define NUM_2GHZ_CHANNELS 14 +#define FIRST_2GHZ_HT_MINUS 5 +#define LAST_2GHZ_HT_PLUS 9 +#define LAST_5GHZ_HT 161 + + +/* rate data (static) */ +static struct ieee80211_rate iwl_cfg80211_rates[] = { + { .bitrate = 1 * 10, .hw_value = 0, .hw_value_short = 0, }, + { .bitrate = 2 * 10, .hw_value = 1, .hw_value_short = 1, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 5.5 * 10, .hw_value = 2, .hw_value_short = 2, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 11 * 10, .hw_value = 3, .hw_value_short = 3, + .flags = IEEE80211_RATE_SHORT_PREAMBLE, }, + { .bitrate = 6 * 10, .hw_value = 4, .hw_value_short = 4, }, + { .bitrate = 9 * 10, .hw_value = 5, .hw_value_short = 5, }, + { .bitrate = 12 * 10, .hw_value = 6, .hw_value_short = 6, }, + { .bitrate = 18 * 10, .hw_value = 7, .hw_value_short = 7, }, + { .bitrate = 24 * 10, .hw_value = 8, .hw_value_short = 8, }, + { .bitrate = 36 * 10, .hw_value = 9, .hw_value_short = 9, }, + { .bitrate = 48 * 10, .hw_value = 10, .hw_value_short = 10, }, + { .bitrate = 54 * 10, .hw_value = 11, .hw_value_short = 11, }, +}; +#define RATES_24_OFFS 0 +#define N_RATES_24 ARRAY_SIZE(iwl_cfg80211_rates) +#define RATES_52_OFFS 4 +#define N_RATES_52 (N_RATES_24 - RATES_52_OFFS) + +/** + * enum iwl_nvm_channel_flags - channel flags in NVM + * @NVM_CHANNEL_VALID: channel is usable for this SKU/geo + * @NVM_CHANNEL_IBSS: usable as an IBSS channel + * @NVM_CHANNEL_ACTIVE: active scanning allowed + * @NVM_CHANNEL_RADAR: radar detection required + * @NVM_CHANNEL_DFS: dynamic freq selection candidate + * @NVM_CHANNEL_WIDE: 20 MHz channel okay (?) + * @NVM_CHANNEL_40MHZ: 40 MHz channel okay (?) + * @NVM_CHANNEL_80MHZ: 80 MHz channel okay (?) + * @NVM_CHANNEL_160MHZ: 160 MHz channel okay (?) + */ +enum iwl_nvm_channel_flags { + NVM_CHANNEL_VALID = BIT(0), + NVM_CHANNEL_IBSS = BIT(1), + NVM_CHANNEL_ACTIVE = BIT(3), + NVM_CHANNEL_RADAR = BIT(4), + NVM_CHANNEL_DFS = BIT(7), + NVM_CHANNEL_WIDE = BIT(8), + NVM_CHANNEL_40MHZ = BIT(9), + NVM_CHANNEL_80MHZ = BIT(10), + NVM_CHANNEL_160MHZ = BIT(11), +}; + +#define CHECK_AND_PRINT_I(x) \ + ((ch_flags & NVM_CHANNEL_##x) ? # x " " : "") + +static int iwl_init_channel_map(struct device *dev, const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, + const __le16 * const nvm_ch_flags) +{ + int ch_idx; + int n_channels = 0; + struct ieee80211_channel *channel; + u16 ch_flags; + bool is_5ghz; + + for (ch_idx = 0; ch_idx < IWL_NUM_CHANNELS; ch_idx++) { + ch_flags = __le16_to_cpup(nvm_ch_flags + ch_idx); + if (!(ch_flags & NVM_CHANNEL_VALID)) { + IWL_DEBUG_EEPROM(dev, + "Ch. %d Flags %x [%sGHz] - No traffic\n", + iwl_nvm_channels[ch_idx], + ch_flags, + (ch_idx >= NUM_2GHZ_CHANNELS) ? + "5.2" : "2.4"); + continue; + } + + channel = &data->channels[n_channels]; + n_channels++; + + channel->hw_value = iwl_nvm_channels[ch_idx]; + channel->band = (ch_idx < NUM_2GHZ_CHANNELS) ? + IEEE80211_BAND_2GHZ : IEEE80211_BAND_5GHZ; + channel->center_freq = + ieee80211_channel_to_frequency( + channel->hw_value, channel->band); + + /* TODO: Need to be dependent to the NVM */ + channel->flags = IEEE80211_CHAN_NO_HT40; + if (ch_idx < NUM_2GHZ_CHANNELS && + (ch_flags & NVM_CHANNEL_40MHZ)) { + if (iwl_nvm_channels[ch_idx] <= LAST_2GHZ_HT_PLUS) + channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS; + if (iwl_nvm_channels[ch_idx] >= FIRST_2GHZ_HT_MINUS) + channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS; + } else if (iwl_nvm_channels[ch_idx] <= LAST_5GHZ_HT && + (ch_flags & NVM_CHANNEL_40MHZ)) { + if ((ch_idx - NUM_2GHZ_CHANNELS) % 2 == 0) + channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS; + else + channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS; + } + if (!(ch_flags & NVM_CHANNEL_80MHZ)) + channel->flags |= IEEE80211_CHAN_NO_80MHZ; + if (!(ch_flags & NVM_CHANNEL_160MHZ)) + channel->flags |= IEEE80211_CHAN_NO_160MHZ; + + if (!(ch_flags & NVM_CHANNEL_IBSS)) + channel->flags |= IEEE80211_CHAN_NO_IBSS; + + if (!(ch_flags & NVM_CHANNEL_ACTIVE)) + channel->flags |= IEEE80211_CHAN_PASSIVE_SCAN; + + if (ch_flags & NVM_CHANNEL_RADAR) + channel->flags |= IEEE80211_CHAN_RADAR; + + /* Initialize regulatory-based run-time data */ + + /* TODO: read the real value from the NVM */ + channel->max_power = 0; + is_5ghz = channel->band == IEEE80211_BAND_5GHZ; + IWL_DEBUG_EEPROM(dev, + "Ch. %d [%sGHz] %s%s%s%s%s%s(0x%02x %ddBm): Ad-Hoc %ssupported\n", + channel->hw_value, + is_5ghz ? "5.2" : "2.4", + CHECK_AND_PRINT_I(VALID), + CHECK_AND_PRINT_I(IBSS), + CHECK_AND_PRINT_I(ACTIVE), + CHECK_AND_PRINT_I(RADAR), + CHECK_AND_PRINT_I(WIDE), + CHECK_AND_PRINT_I(DFS), + ch_flags, + channel->max_power, + ((ch_flags & NVM_CHANNEL_IBSS) && + !(ch_flags & NVM_CHANNEL_RADAR)) + ? "" : "not "); + } + + return n_channels; +} + +static void iwl_init_vht_hw_capab(const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, + struct ieee80211_sta_vht_cap *vht_cap) +{ + /* For now, assume new devices with NVM are VHT capable */ + + vht_cap->vht_supported = true; + + vht_cap->cap = IEEE80211_VHT_CAP_SHORT_GI_80 | + IEEE80211_VHT_CAP_RXSTBC_1 | + IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE | + 7 << IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT; + + if (iwlwifi_mod_params.amsdu_size_8K) + vht_cap->cap |= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_7991; + + vht_cap->vht_mcs.rx_mcs_map = + cpu_to_le16(IEEE80211_VHT_MCS_SUPPORT_0_9 << 0 | + IEEE80211_VHT_MCS_SUPPORT_0_9 << 2 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 4 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 6 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 8 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 10 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 12 | + IEEE80211_VHT_MCS_NOT_SUPPORTED << 14); + + if (data->valid_rx_ant == 1 || cfg->rx_with_siso_diversity) { + vht_cap->cap |= IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN | + IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN; + /* this works because NOT_SUPPORTED == 3 */ + vht_cap->vht_mcs.rx_mcs_map |= + cpu_to_le16(IEEE80211_VHT_MCS_NOT_SUPPORTED << 2); + } + + vht_cap->vht_mcs.tx_mcs_map = vht_cap->vht_mcs.rx_mcs_map; +} + +static void iwl_init_sbands(struct device *dev, const struct iwl_cfg *cfg, + struct iwl_nvm_data *data, const __le16 *nvm_sw) +{ + int n_channels = iwl_init_channel_map(dev, cfg, data, + &nvm_sw[NVM_CHANNELS]); + int n_used = 0; + struct ieee80211_supported_band *sband; + + sband = &data->bands[IEEE80211_BAND_2GHZ]; + sband->band = IEEE80211_BAND_2GHZ; + sband->bitrates = &iwl_cfg80211_rates[RATES_24_OFFS]; + sband->n_bitrates = N_RATES_24; + n_used += iwl_init_sband_channels(data, sband, n_channels, + IEEE80211_BAND_2GHZ); + iwl_init_ht_hw_capab(cfg, data, &sband->ht_cap, IEEE80211_BAND_2GHZ); + + sband = &data->bands[IEEE80211_BAND_5GHZ]; + sband->band = IEEE80211_BAND_5GHZ; + sband->bitrates = &iwl_cfg80211_rates[RATES_52_OFFS]; + sband->n_bitrates = N_RATES_52; + n_used += iwl_init_sband_channels(data, sband, n_channels, + IEEE80211_BAND_5GHZ); + iwl_init_ht_hw_capab(cfg, data, &sband->ht_cap, IEEE80211_BAND_5GHZ); + iwl_init_vht_hw_capab(cfg, data, &sband->vht_cap); + + if (n_channels != n_used) + IWL_ERR_DEV(dev, "NVM: used only %d of %d channels\n", + n_used, n_channels); +} + +struct iwl_nvm_data * +iwl_parse_nvm_data(struct device *dev, const struct iwl_cfg *cfg, + const __le16 *nvm_hw, const __le16 *nvm_sw, + const __le16 *nvm_calib) +{ + struct iwl_nvm_data *data; + u8 hw_addr[ETH_ALEN]; + u16 radio_cfg, sku; + + data = kzalloc(sizeof(*data) + + sizeof(struct ieee80211_channel) * IWL_NUM_CHANNELS, + GFP_KERNEL); + if (!data) + return NULL; + + data->nvm_version = le16_to_cpup(nvm_sw + NVM_VERSION); + + radio_cfg = le16_to_cpup(nvm_sw + RADIO_CFG); + data->radio_cfg_type = NVM_RF_CFG_TYPE_MSK(radio_cfg); + data->radio_cfg_step = NVM_RF_CFG_STEP_MSK(radio_cfg); + data->radio_cfg_dash = NVM_RF_CFG_DASH_MSK(radio_cfg); + data->radio_cfg_pnum = NVM_RF_CFG_PNUM_MSK(radio_cfg); + data->valid_tx_ant = NVM_RF_CFG_TX_ANT_MSK(radio_cfg); + data->valid_rx_ant = NVM_RF_CFG_RX_ANT_MSK(radio_cfg); + + sku = le16_to_cpup(nvm_sw + SKU); + data->sku_cap_band_24GHz_enable = sku & NVM_SKU_CAP_BAND_24GHZ; + data->sku_cap_band_52GHz_enable = sku & NVM_SKU_CAP_BAND_52GHZ; + data->sku_cap_11n_enable = sku & NVM_SKU_CAP_11N_ENABLE; + if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_ALL) + data->sku_cap_11n_enable = false; + + /* check overrides (some devices have wrong NVM) */ + if (cfg->valid_tx_ant) + data->valid_tx_ant = cfg->valid_tx_ant; + if (cfg->valid_rx_ant) + data->valid_rx_ant = cfg->valid_rx_ant; + + if (!data->valid_tx_ant || !data->valid_rx_ant) { + IWL_ERR_DEV(dev, "invalid antennas (0x%x, 0x%x)\n", + data->valid_tx_ant, data->valid_rx_ant); + kfree(data); + return NULL; + } + + data->n_hw_addrs = le16_to_cpup(nvm_sw + N_HW_ADDRS); + + data->xtal_calib[0] = *(nvm_calib + XTAL_CALIB); + data->xtal_calib[1] = *(nvm_calib + XTAL_CALIB + 1); + + /* The byte order is little endian 16 bit, meaning 214365 */ + memcpy(hw_addr, nvm_hw + HW_ADDR, ETH_ALEN); + data->hw_addr[0] = hw_addr[1]; + data->hw_addr[1] = hw_addr[0]; + data->hw_addr[2] = hw_addr[3]; + data->hw_addr[3] = hw_addr[2]; + data->hw_addr[4] = hw_addr[5]; + data->hw_addr[5] = hw_addr[4]; + + iwl_init_sbands(dev, cfg, data, nvm_sw); + + data->calib_version = 255; /* TODO: + this value will prevent some checks from + failing, we need to check if this + field is still needed, and if it does, + where is it in the NVM*/ + + return data; +} +IWL_EXPORT_SYMBOL(iwl_parse_nvm_data); diff --git a/drivers/net/wireless/iwlwifi/iwl-nvm-parse.h b/drivers/net/wireless/iwlwifi/iwl-nvm-parse.h new file mode 100644 index 0000000..e57fb98 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-nvm-parse.h @@ -0,0 +1,80 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2008 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ +#ifndef __iwl_nvm_parse_h__ +#define __iwl_nvm_parse_h__ + +#include "iwl-eeprom-parse.h" + +/** + * iwl_parse_nvm_data - parse NVM data and return values + * + * This function parses all NVM values we need and then + * returns a (newly allocated) struct containing all the + * relevant values for driver use. The struct must be freed + * later with iwl_free_nvm_data(). + */ +struct iwl_nvm_data * +iwl_parse_nvm_data(struct device *dev, const struct iwl_cfg *cfg, + const __le16 *nvm_hw, const __le16 *nvm_sw, + const __le16 *nvm_calib); + +#endif /* __iwl_nvm_parse_h__ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-op-mode.h b/drivers/net/wireless/iwlwifi/iwl-op-mode.h new file mode 100644 index 0000000..98c7aa7 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-op-mode.h @@ -0,0 +1,231 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#ifndef __iwl_op_mode_h__ +#define __iwl_op_mode_h__ + +#include + +struct iwl_op_mode; +struct iwl_trans; +struct sk_buff; +struct iwl_device_cmd; +struct iwl_rx_cmd_buffer; +struct iwl_fw; +struct iwl_cfg; + +/** + * DOC: Operational mode - what is it ? + * + * The operational mode (a.k.a. op_mode) is the layer that implements + * mac80211's handlers. It knows two APIs: mac80211's and the fw's. It uses + * the transport API to access the HW. The op_mode doesn't need to know how the + * underlying HW works, since the transport layer takes care of that. + * + * There can be several op_mode: i.e. different fw APIs will require two + * different op_modes. This is why the op_mode is virtualized. + */ + +/** + * DOC: Life cycle of the Operational mode + * + * The operational mode has a very simple life cycle. + * + * 1) The driver layer (iwl-drv.c) chooses the op_mode based on the + * capabilities advertized by the fw file (in TLV format). + * 2) The driver layer starts the op_mode (ops->start) + * 3) The op_mode registers registers mac80211 + * 4) The op_mode is governed by mac80211 + * 5) The driver layer stops the op_mode + */ + +/** + * struct iwl_op_mode_ops - op_mode specific operations + * + * The op_mode exports its ops so that external components can start it and + * interact with it. The driver layer typically calls the start and stop + * handlers, the transport layer calls the others. + * + * All the handlers MUST be implemented + * + * @start: start the op_mode. The transport layer is already allocated. + * May sleep + * @stop: stop the op_mode. Must free all the memory allocated. + * May sleep + * @rx: Rx notification to the op_mode. rxb is the Rx buffer itself. Cmd is the + * HCMD the this Rx responds to. + * This callback may sleep, it is called from a threaded IRQ handler. + * @queue_full: notifies that a HW queue is full. + * Must be atomic and called with BH disabled. + * @queue_not_full: notifies that a HW queue is not full any more. + * Must be atomic and called with BH disabled. + * @hw_rf_kill:notifies of a change in the HW rf kill switch. True means that + * the radio is killed. May sleep. + * @free_skb: allows the transport layer to free skbs that haven't been + * reclaimed by the op_mode. This can happen when the driver is freed and + * there are Tx packets pending in the transport layer. + * Must be atomic + * @nic_error: error notification. Must be atomic and must be called with BH + * disabled. + * @cmd_queue_full: Called when the command queue gets full. Must be atomic and + * called with BH disabled. + * @nic_config: configure NIC, called before firmware is started. + * May sleep + * @wimax_active: invoked when WiMax becomes active. May sleep + */ +struct iwl_op_mode_ops { + struct iwl_op_mode *(*start)(struct iwl_trans *trans, + const struct iwl_cfg *cfg, + const struct iwl_fw *fw, + struct dentry *dbgfs_dir); + void (*stop)(struct iwl_op_mode *op_mode); + int (*rx)(struct iwl_op_mode *op_mode, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); + void (*queue_full)(struct iwl_op_mode *op_mode, int queue); + void (*queue_not_full)(struct iwl_op_mode *op_mode, int queue); + void (*hw_rf_kill)(struct iwl_op_mode *op_mode, bool state); + void (*free_skb)(struct iwl_op_mode *op_mode, struct sk_buff *skb); + void (*nic_error)(struct iwl_op_mode *op_mode); + void (*cmd_queue_full)(struct iwl_op_mode *op_mode); + void (*nic_config)(struct iwl_op_mode *op_mode); + void (*wimax_active)(struct iwl_op_mode *op_mode); +}; + +int iwl_opmode_register(const char *name, const struct iwl_op_mode_ops *ops); +void iwl_opmode_deregister(const char *name); + +/** + * struct iwl_op_mode - operational mode + * + * This holds an implementation of the mac80211 / fw API. + * + * @ops - pointer to its own ops + */ +struct iwl_op_mode { + const struct iwl_op_mode_ops *ops; + const struct iwl_trans *trans; + + char op_mode_specific[0] __aligned(sizeof(void *)); +}; + +static inline void iwl_op_mode_stop(struct iwl_op_mode *op_mode) +{ + might_sleep(); + op_mode->ops->stop(op_mode); +} + +static inline int iwl_op_mode_rx(struct iwl_op_mode *op_mode, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + might_sleep(); + return op_mode->ops->rx(op_mode, rxb, cmd); +} + +static inline void iwl_op_mode_queue_full(struct iwl_op_mode *op_mode, + int queue) +{ + op_mode->ops->queue_full(op_mode, queue); +} + +static inline void iwl_op_mode_queue_not_full(struct iwl_op_mode *op_mode, + int queue) +{ + op_mode->ops->queue_not_full(op_mode, queue); +} + +static inline void iwl_op_mode_hw_rf_kill(struct iwl_op_mode *op_mode, + bool state) +{ + might_sleep(); + op_mode->ops->hw_rf_kill(op_mode, state); +} + +static inline void iwl_op_mode_free_skb(struct iwl_op_mode *op_mode, + struct sk_buff *skb) +{ + op_mode->ops->free_skb(op_mode, skb); +} + +static inline void iwl_op_mode_nic_error(struct iwl_op_mode *op_mode) +{ + op_mode->ops->nic_error(op_mode); +} + +static inline void iwl_op_mode_cmd_queue_full(struct iwl_op_mode *op_mode) +{ + op_mode->ops->cmd_queue_full(op_mode); +} + +static inline void iwl_op_mode_nic_config(struct iwl_op_mode *op_mode) +{ + might_sleep(); + op_mode->ops->nic_config(op_mode); +} + +static inline void iwl_op_mode_wimax_active(struct iwl_op_mode *op_mode) +{ + might_sleep(); + op_mode->ops->wimax_active(op_mode); +} + +#endif /* __iwl_op_mode_h__ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-phy-db.c b/drivers/net/wireless/iwlwifi/iwl-phy-db.c new file mode 100644 index 0000000..25745da --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-phy-db.c @@ -0,0 +1,499 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#include +#include +#include + +#include "iwl-drv.h" +#include "iwl-phy-db.h" +#include "iwl-debug.h" +#include "iwl-op-mode.h" +#include "iwl-trans.h" + +#define CHANNEL_NUM_SIZE 4 /* num of channels in calib_ch size */ +#define IWL_NUM_PAPD_CH_GROUPS 4 +#define IWL_NUM_TXP_CH_GROUPS 9 + +struct iwl_phy_db_entry { + u16 size; + u8 *data; +}; + +/** + * struct iwl_phy_db - stores phy configuration and calibration data. + * + * @cfg: phy configuration. + * @calib_nch: non channel specific calibration data. + * @calib_ch: channel specific calibration data. + * @calib_ch_group_papd: calibration data related to papd channel group. + * @calib_ch_group_txp: calibration data related to tx power chanel group. + */ +struct iwl_phy_db { + struct iwl_phy_db_entry cfg; + struct iwl_phy_db_entry calib_nch; + struct iwl_phy_db_entry calib_ch; + struct iwl_phy_db_entry calib_ch_group_papd[IWL_NUM_PAPD_CH_GROUPS]; + struct iwl_phy_db_entry calib_ch_group_txp[IWL_NUM_TXP_CH_GROUPS]; + + u32 channel_num; + u32 channel_size; + + struct iwl_trans *trans; +}; + +enum iwl_phy_db_section_type { + IWL_PHY_DB_CFG = 1, + IWL_PHY_DB_CALIB_NCH, + IWL_PHY_DB_CALIB_CH, + IWL_PHY_DB_CALIB_CHG_PAPD, + IWL_PHY_DB_CALIB_CHG_TXP, + IWL_PHY_DB_MAX +}; + +#define PHY_DB_CMD 0x6c /* TEMP API - The actual is 0x8c */ + +/* + * phy db - configure operational ucode + */ +struct iwl_phy_db_cmd { + __le16 type; + __le16 length; + u8 data[]; +} __packed; + +/* for parsing of tx power channel group data that comes from the firmware*/ +struct iwl_phy_db_chg_txp { + __le32 space; + __le16 max_channel_idx; +} __packed; + +/* + * phy db - Receieve phy db chunk after calibrations + */ +struct iwl_calib_res_notif_phy_db { + __le16 type; + __le16 length; + u8 data[]; +} __packed; + +struct iwl_phy_db *iwl_phy_db_init(struct iwl_trans *trans) +{ + struct iwl_phy_db *phy_db = kzalloc(sizeof(struct iwl_phy_db), + GFP_KERNEL); + + if (!phy_db) + return phy_db; + + phy_db->trans = trans; + + /* TODO: add default values of the phy db. */ + return phy_db; +} +IWL_EXPORT_SYMBOL(iwl_phy_db_init); + +/* + * get phy db section: returns a pointer to a phy db section specified by + * type and channel group id. + */ +static struct iwl_phy_db_entry * +iwl_phy_db_get_section(struct iwl_phy_db *phy_db, + enum iwl_phy_db_section_type type, + u16 chg_id) +{ + if (!phy_db || type >= IWL_PHY_DB_MAX) + return NULL; + + switch (type) { + case IWL_PHY_DB_CFG: + return &phy_db->cfg; + case IWL_PHY_DB_CALIB_NCH: + return &phy_db->calib_nch; + case IWL_PHY_DB_CALIB_CH: + return &phy_db->calib_ch; + case IWL_PHY_DB_CALIB_CHG_PAPD: + if (chg_id >= IWL_NUM_PAPD_CH_GROUPS) + return NULL; + return &phy_db->calib_ch_group_papd[chg_id]; + case IWL_PHY_DB_CALIB_CHG_TXP: + if (chg_id >= IWL_NUM_TXP_CH_GROUPS) + return NULL; + return &phy_db->calib_ch_group_txp[chg_id]; + default: + return NULL; + } + return NULL; +} + +static void iwl_phy_db_free_section(struct iwl_phy_db *phy_db, + enum iwl_phy_db_section_type type, + u16 chg_id) +{ + struct iwl_phy_db_entry *entry = + iwl_phy_db_get_section(phy_db, type, chg_id); + if (!entry) + return; + + kfree(entry->data); + entry->data = NULL; + entry->size = 0; +} + +void iwl_phy_db_free(struct iwl_phy_db *phy_db) +{ + int i; + + if (!phy_db) + return; + + iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CFG, 0); + iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_NCH, 0); + iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_CH, 0); + for (i = 0; i < IWL_NUM_PAPD_CH_GROUPS; i++) + iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_CHG_PAPD, i); + for (i = 0; i < IWL_NUM_TXP_CH_GROUPS; i++) + iwl_phy_db_free_section(phy_db, IWL_PHY_DB_CALIB_CHG_TXP, i); + + kfree(phy_db); +} +IWL_EXPORT_SYMBOL(iwl_phy_db_free); + +int iwl_phy_db_set_section(struct iwl_phy_db *phy_db, struct iwl_rx_packet *pkt, + gfp_t alloc_ctx) +{ + struct iwl_calib_res_notif_phy_db *phy_db_notif = + (struct iwl_calib_res_notif_phy_db *)pkt->data; + enum iwl_phy_db_section_type type = le16_to_cpu(phy_db_notif->type); + u16 size = le16_to_cpu(phy_db_notif->length); + struct iwl_phy_db_entry *entry; + u16 chg_id = 0; + + if (!phy_db) + return -EINVAL; + + if (type == IWL_PHY_DB_CALIB_CHG_PAPD || + type == IWL_PHY_DB_CALIB_CHG_TXP) + chg_id = le16_to_cpup((__le16 *)phy_db_notif->data); + + entry = iwl_phy_db_get_section(phy_db, type, chg_id); + if (!entry) + return -EINVAL; + + kfree(entry->data); + entry->data = kmemdup(phy_db_notif->data, size, alloc_ctx); + if (!entry->data) { + entry->size = 0; + return -ENOMEM; + } + + entry->size = size; + + if (type == IWL_PHY_DB_CALIB_CH) { + phy_db->channel_num = + le32_to_cpup((__le32 *)phy_db_notif->data); + phy_db->channel_size = + (size - CHANNEL_NUM_SIZE) / phy_db->channel_num; + } + + IWL_DEBUG_INFO(phy_db->trans, + "%s(%d): [PHYDB]SET: Type %d , Size: %d\n", + __func__, __LINE__, type, size); + + return 0; +} +IWL_EXPORT_SYMBOL(iwl_phy_db_set_section); + +static int is_valid_channel(u16 ch_id) +{ + if (ch_id <= 14 || + (36 <= ch_id && ch_id <= 64 && ch_id % 4 == 0) || + (100 <= ch_id && ch_id <= 140 && ch_id % 4 == 0) || + (145 <= ch_id && ch_id <= 165 && ch_id % 4 == 1)) + return 1; + return 0; +} + +static u8 ch_id_to_ch_index(u16 ch_id) +{ + if (WARN_ON(!is_valid_channel(ch_id))) + return 0xff; + + if (ch_id <= 14) + return ch_id - 1; + if (ch_id <= 64) + return (ch_id + 20) / 4; + if (ch_id <= 140) + return (ch_id - 12) / 4; + return (ch_id - 13) / 4; +} + + +static u16 channel_id_to_papd(u16 ch_id) +{ + if (WARN_ON(!is_valid_channel(ch_id))) + return 0xff; + + if (1 <= ch_id && ch_id <= 14) + return 0; + if (36 <= ch_id && ch_id <= 64) + return 1; + if (100 <= ch_id && ch_id <= 140) + return 2; + return 3; +} + +static u16 channel_id_to_txp(struct iwl_phy_db *phy_db, u16 ch_id) +{ + struct iwl_phy_db_chg_txp *txp_chg; + int i; + u8 ch_index = ch_id_to_ch_index(ch_id); + if (ch_index == 0xff) + return 0xff; + + for (i = 0; i < IWL_NUM_TXP_CH_GROUPS; i++) { + txp_chg = (void *)phy_db->calib_ch_group_txp[i].data; + if (!txp_chg) + return 0xff; + /* + * Looking for the first channel group that its max channel is + * higher then wanted channel. + */ + if (le16_to_cpu(txp_chg->max_channel_idx) >= ch_index) + return i; + } + return 0xff; +} +static +int iwl_phy_db_get_section_data(struct iwl_phy_db *phy_db, + u32 type, u8 **data, u16 *size, u16 ch_id) +{ + struct iwl_phy_db_entry *entry; + u32 channel_num; + u32 channel_size; + u16 ch_group_id = 0; + u16 index; + + if (!phy_db) + return -EINVAL; + + /* find wanted channel group */ + if (type == IWL_PHY_DB_CALIB_CHG_PAPD) + ch_group_id = channel_id_to_papd(ch_id); + else if (type == IWL_PHY_DB_CALIB_CHG_TXP) + ch_group_id = channel_id_to_txp(phy_db, ch_id); + + entry = iwl_phy_db_get_section(phy_db, type, ch_group_id); + if (!entry) + return -EINVAL; + + if (type == IWL_PHY_DB_CALIB_CH) { + index = ch_id_to_ch_index(ch_id); + channel_num = phy_db->channel_num; + channel_size = phy_db->channel_size; + if (index >= channel_num) { + IWL_ERR(phy_db->trans, "Wrong channel number %d\n", + ch_id); + return -EINVAL; + } + *data = entry->data + CHANNEL_NUM_SIZE + index * channel_size; + *size = channel_size; + } else { + *data = entry->data; + *size = entry->size; + } + + IWL_DEBUG_INFO(phy_db->trans, + "%s(%d): [PHYDB] GET: Type %d , Size: %d\n", + __func__, __LINE__, type, *size); + + return 0; +} + +static int iwl_send_phy_db_cmd(struct iwl_phy_db *phy_db, u16 type, + u16 length, void *data) +{ + struct iwl_phy_db_cmd phy_db_cmd; + struct iwl_host_cmd cmd = { + .id = PHY_DB_CMD, + .flags = CMD_SYNC, + }; + + IWL_DEBUG_INFO(phy_db->trans, + "Sending PHY-DB hcmd of type %d, of length %d\n", + type, length); + + /* Set phy db cmd variables */ + phy_db_cmd.type = cpu_to_le16(type); + phy_db_cmd.length = cpu_to_le16(length); + + /* Set hcmd variables */ + cmd.data[0] = &phy_db_cmd; + cmd.len[0] = sizeof(struct iwl_phy_db_cmd); + cmd.data[1] = data; + cmd.len[1] = length; + cmd.dataflags[1] = IWL_HCMD_DFL_NOCOPY; + + return iwl_trans_send_cmd(phy_db->trans, &cmd); +} + +static int iwl_phy_db_send_all_channel_groups( + struct iwl_phy_db *phy_db, + enum iwl_phy_db_section_type type, + u8 max_ch_groups) +{ + u16 i; + int err; + struct iwl_phy_db_entry *entry; + + /* Send all the channel specific groups to operational fw */ + for (i = 0; i < max_ch_groups; i++) { + entry = iwl_phy_db_get_section(phy_db, + type, + i); + if (!entry) + return -EINVAL; + + /* Send the requested PHY DB section */ + err = iwl_send_phy_db_cmd(phy_db, + type, + entry->size, + entry->data); + if (err) { + IWL_ERR(phy_db->trans, + "Can't SEND phy_db section %d (%d), err %d", + type, i, err); + return err; + } + + IWL_DEBUG_INFO(phy_db->trans, + "Sent PHY_DB HCMD, type = %d num = %d", + type, i); + } + + return 0; +} + +int iwl_send_phy_db_data(struct iwl_phy_db *phy_db) +{ + u8 *data = NULL; + u16 size = 0; + int err; + + IWL_DEBUG_INFO(phy_db->trans, + "Sending phy db data and configuration to runtime image\n"); + + /* Send PHY DB CFG section */ + err = iwl_phy_db_get_section_data(phy_db, IWL_PHY_DB_CFG, + &data, &size, 0); + if (err) { + IWL_ERR(phy_db->trans, "Cannot get Phy DB cfg section\n"); + return err; + } + + err = iwl_send_phy_db_cmd(phy_db, IWL_PHY_DB_CFG, size, data); + if (err) { + IWL_ERR(phy_db->trans, + "Cannot send HCMD of Phy DB cfg section\n"); + return err; + } + + err = iwl_phy_db_get_section_data(phy_db, IWL_PHY_DB_CALIB_NCH, + &data, &size, 0); + if (err) { + IWL_ERR(phy_db->trans, + "Cannot get Phy DB non specific channel section\n"); + return err; + } + + err = iwl_send_phy_db_cmd(phy_db, IWL_PHY_DB_CALIB_NCH, size, data); + if (err) { + IWL_ERR(phy_db->trans, + "Cannot send HCMD of Phy DB non specific channel section\n"); + return err; + } + + /* Send all the TXP channel specific data */ + err = iwl_phy_db_send_all_channel_groups(phy_db, + IWL_PHY_DB_CALIB_CHG_PAPD, + IWL_NUM_PAPD_CH_GROUPS); + if (err) { + IWL_ERR(phy_db->trans, + "Cannot send channel specific PAPD groups"); + return err; + } + + /* Send all the TXP channel specific data */ + err = iwl_phy_db_send_all_channel_groups(phy_db, + IWL_PHY_DB_CALIB_CHG_TXP, + IWL_NUM_TXP_CH_GROUPS); + if (err) { + IWL_ERR(phy_db->trans, + "Cannot send channel specific TX power groups"); + return err; + } + + IWL_DEBUG_INFO(phy_db->trans, + "Finished sending phy db non channel data\n"); + return 0; +} +IWL_EXPORT_SYMBOL(iwl_send_phy_db_data); diff --git a/drivers/net/wireless/iwlwifi/iwl-phy-db.h b/drivers/net/wireless/iwlwifi/iwl-phy-db.h new file mode 100644 index 0000000..ce983af --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-phy-db.h @@ -0,0 +1,82 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#ifndef __IWL_PHYDB_H__ +#define __IWL_PHYDB_H__ + +#include + +#include "iwl-op-mode.h" +#include "iwl-trans.h" + +struct iwl_phy_db *iwl_phy_db_init(struct iwl_trans *trans); + +void iwl_phy_db_free(struct iwl_phy_db *phy_db); + +int iwl_phy_db_set_section(struct iwl_phy_db *phy_db, struct iwl_rx_packet *pkt, + gfp_t alloc_ctx); + + +int iwl_send_phy_db_data(struct iwl_phy_db *phy_db); + +#endif /* __IWL_PHYDB_H__ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-prph.h b/drivers/net/wireless/iwlwifi/iwl-prph.h new file mode 100644 index 0000000..386f2a7 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-prph.h @@ -0,0 +1,263 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef __iwl_prph_h__ +#define __iwl_prph_h__ + +/* + * Registers in this file are internal, not PCI bus memory mapped. + * Driver accesses these via HBUS_TARG_PRPH_* registers. + */ +#define PRPH_BASE (0x00000) +#define PRPH_END (0xFFFFF) + +/* APMG (power management) constants */ +#define APMG_BASE (PRPH_BASE + 0x3000) +#define APMG_CLK_CTRL_REG (APMG_BASE + 0x0000) +#define APMG_CLK_EN_REG (APMG_BASE + 0x0004) +#define APMG_CLK_DIS_REG (APMG_BASE + 0x0008) +#define APMG_PS_CTRL_REG (APMG_BASE + 0x000c) +#define APMG_PCIDEV_STT_REG (APMG_BASE + 0x0010) +#define APMG_RFKILL_REG (APMG_BASE + 0x0014) +#define APMG_RTC_INT_STT_REG (APMG_BASE + 0x001c) +#define APMG_RTC_INT_MSK_REG (APMG_BASE + 0x0020) +#define APMG_DIGITAL_SVR_REG (APMG_BASE + 0x0058) +#define APMG_ANALOG_SVR_REG (APMG_BASE + 0x006C) + +#define APMS_CLK_VAL_MRB_FUNC_MODE (0x00000001) +#define APMG_CLK_VAL_DMA_CLK_RQT (0x00000200) +#define APMG_CLK_VAL_BSM_CLK_RQT (0x00000800) + +#define APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS (0x00400000) +#define APMG_PS_CTRL_VAL_RESET_REQ (0x04000000) +#define APMG_PS_CTRL_MSK_PWR_SRC (0x03000000) +#define APMG_PS_CTRL_VAL_PWR_SRC_VMAIN (0x00000000) +#define APMG_PS_CTRL_VAL_PWR_SRC_VAUX (0x02000000) +#define APMG_SVR_VOLTAGE_CONFIG_BIT_MSK (0x000001E0) /* bit 8:5 */ +#define APMG_SVR_DIGITAL_VOLTAGE_1_32 (0x00000060) + +#define APMG_PCIDEV_STT_VAL_L1_ACT_DIS (0x00000800) + +/* Device system time */ +#define DEVICE_SYSTEM_TIME_REG 0xA0206C + +/** + * Tx Scheduler + * + * The Tx Scheduler selects the next frame to be transmitted, choosing TFDs + * (Transmit Frame Descriptors) from up to 16 circular Tx queues resident in + * host DRAM. It steers each frame's Tx command (which contains the frame + * data) into one of up to 7 prioritized Tx DMA FIFO channels within the + * device. A queue maps to only one (selectable by driver) Tx DMA channel, + * but one DMA channel may take input from several queues. + * + * Tx DMA FIFOs have dedicated purposes. + * + * For 5000 series and up, they are used differently + * (cf. iwl5000_default_queue_to_tx_fifo in iwl-5000.c): + * + * 0 -- EDCA BK (background) frames, lowest priority + * 1 -- EDCA BE (best effort) frames, normal priority + * 2 -- EDCA VI (video) frames, higher priority + * 3 -- EDCA VO (voice) and management frames, highest priority + * 4 -- unused + * 5 -- unused + * 6 -- unused + * 7 -- Commands + * + * Driver should normally map queues 0-6 to Tx DMA/FIFO channels 0-6. + * In addition, driver can map the remaining queues to Tx DMA/FIFO + * channels 0-3 to support 11n aggregation via EDCA DMA channels. + * + * The driver sets up each queue to work in one of two modes: + * + * 1) Scheduler-Ack, in which the scheduler automatically supports a + * block-ack (BA) window of up to 64 TFDs. In this mode, each queue + * contains TFDs for a unique combination of Recipient Address (RA) + * and Traffic Identifier (TID), that is, traffic of a given + * Quality-Of-Service (QOS) priority, destined for a single station. + * + * In scheduler-ack mode, the scheduler keeps track of the Tx status of + * each frame within the BA window, including whether it's been transmitted, + * and whether it's been acknowledged by the receiving station. The device + * automatically processes block-acks received from the receiving STA, + * and reschedules un-acked frames to be retransmitted (successful + * Tx completion may end up being out-of-order). + * + * The driver must maintain the queue's Byte Count table in host DRAM + * for this mode. + * This mode does not support fragmentation. + * + * 2) FIFO (a.k.a. non-Scheduler-ACK), in which each TFD is processed in order. + * The device may automatically retry Tx, but will retry only one frame + * at a time, until receiving ACK from receiving station, or reaching + * retry limit and giving up. + * + * The command queue (#4/#9) must use this mode! + * This mode does not require use of the Byte Count table in host DRAM. + * + * Driver controls scheduler operation via 3 means: + * 1) Scheduler registers + * 2) Shared scheduler data base in internal SRAM + * 3) Shared data in host DRAM + * + * Initialization: + * + * When loading, driver should allocate memory for: + * 1) 16 TFD circular buffers, each with space for (typically) 256 TFDs. + * 2) 16 Byte Count circular buffers in 16 KBytes contiguous memory + * (1024 bytes for each queue). + * + * After receiving "Alive" response from uCode, driver must initialize + * the scheduler (especially for queue #4/#9, the command queue, otherwise + * the driver can't issue commands!): + */ +#define SCD_MEM_LOWER_BOUND (0x0000) + +/** + * Max Tx window size is the max number of contiguous TFDs that the scheduler + * can keep track of at one time when creating block-ack chains of frames. + * Note that "64" matches the number of ack bits in a block-ack packet. + */ +#define SCD_WIN_SIZE 64 +#define SCD_FRAME_LIMIT 64 + +#define SCD_TXFIFO_POS_TID (0) +#define SCD_TXFIFO_POS_RA (4) +#define SCD_QUEUE_RA_TID_MAP_RATID_MSK (0x01FF) + +/* agn SCD */ +#define SCD_QUEUE_STTS_REG_POS_TXF (0) +#define SCD_QUEUE_STTS_REG_POS_ACTIVE (3) +#define SCD_QUEUE_STTS_REG_POS_WSL (4) +#define SCD_QUEUE_STTS_REG_POS_SCD_ACT_EN (19) +#define SCD_QUEUE_STTS_REG_MSK (0x017F0000) + +#define SCD_QUEUE_CTX_REG1_CREDIT_POS (8) +#define SCD_QUEUE_CTX_REG1_CREDIT_MSK (0x00FFFF00) +#define SCD_QUEUE_CTX_REG1_SUPER_CREDIT_POS (24) +#define SCD_QUEUE_CTX_REG1_SUPER_CREDIT_MSK (0xFF000000) +#define SCD_QUEUE_CTX_REG2_WIN_SIZE_POS (0) +#define SCD_QUEUE_CTX_REG2_WIN_SIZE_MSK (0x0000007F) +#define SCD_QUEUE_CTX_REG2_FRAME_LIMIT_POS (16) +#define SCD_QUEUE_CTX_REG2_FRAME_LIMIT_MSK (0x007F0000) + +/* Context Data */ +#define SCD_CONTEXT_MEM_LOWER_BOUND (SCD_MEM_LOWER_BOUND + 0x600) +#define SCD_CONTEXT_MEM_UPPER_BOUND (SCD_MEM_LOWER_BOUND + 0x6A0) + +/* Tx status */ +#define SCD_TX_STTS_MEM_LOWER_BOUND (SCD_MEM_LOWER_BOUND + 0x6A0) +#define SCD_TX_STTS_MEM_UPPER_BOUND (SCD_MEM_LOWER_BOUND + 0x7E0) + +/* Translation Data */ +#define SCD_TRANS_TBL_MEM_LOWER_BOUND (SCD_MEM_LOWER_BOUND + 0x7E0) +#define SCD_TRANS_TBL_MEM_UPPER_BOUND (SCD_MEM_LOWER_BOUND + 0x808) + +#define SCD_CONTEXT_QUEUE_OFFSET(x)\ + (SCD_CONTEXT_MEM_LOWER_BOUND + ((x) * 8)) + +#define SCD_TX_STTS_QUEUE_OFFSET(x)\ + (SCD_TX_STTS_MEM_LOWER_BOUND + ((x) * 16)) + +#define SCD_TRANS_TBL_OFFSET_QUEUE(x) \ + ((SCD_TRANS_TBL_MEM_LOWER_BOUND + ((x) * 2)) & 0xfffc) + +#define SCD_BASE (PRPH_BASE + 0xa02c00) + +#define SCD_SRAM_BASE_ADDR (SCD_BASE + 0x0) +#define SCD_DRAM_BASE_ADDR (SCD_BASE + 0x8) +#define SCD_AIT (SCD_BASE + 0x0c) +#define SCD_TXFACT (SCD_BASE + 0x10) +#define SCD_ACTIVE (SCD_BASE + 0x14) +#define SCD_QUEUECHAIN_SEL (SCD_BASE + 0xe8) +#define SCD_CHAINEXT_EN (SCD_BASE + 0x244) +#define SCD_AGGR_SEL (SCD_BASE + 0x248) +#define SCD_INTERRUPT_MASK (SCD_BASE + 0x108) + +static inline unsigned int SCD_QUEUE_WRPTR(unsigned int chnl) +{ + if (chnl < 20) + return SCD_BASE + 0x18 + chnl * 4; + WARN_ON_ONCE(chnl >= 32); + return SCD_BASE + 0x284 + (chnl - 20) * 4; +} + +static inline unsigned int SCD_QUEUE_RDPTR(unsigned int chnl) +{ + if (chnl < 20) + return SCD_BASE + 0x68 + chnl * 4; + WARN_ON_ONCE(chnl >= 32); + return SCD_BASE + 0x2B4 + (chnl - 20) * 4; +} + +static inline unsigned int SCD_QUEUE_STATUS_BITS(unsigned int chnl) +{ + if (chnl < 20) + return SCD_BASE + 0x10c + chnl * 4; + WARN_ON_ONCE(chnl >= 32); + return SCD_BASE + 0x384 + (chnl - 20) * 4; +} + +/*********************** END TX SCHEDULER *************************************/ + +#endif /* __iwl_prph_h__ */ diff --git a/drivers/net/wireless/iwlwifi/iwl-test.c b/drivers/net/wireless/iwlwifi/iwl-test.c new file mode 100644 index 0000000..5cfd55b --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-test.c @@ -0,0 +1,852 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2010 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2010 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#include +#include + +#include "iwl-drv.h" +#include "iwl-io.h" +#include "iwl-fh.h" +#include "iwl-prph.h" +#include "iwl-trans.h" +#include "iwl-test.h" +#include "iwl-csr.h" +#include "iwl-testmode.h" + +/* + * Periphery registers absolute lower bound. This is used in order to + * differentiate registery access through HBUS_TARG_PRPH_* and + * HBUS_TARG_MEM_* accesses. + */ +#define IWL_ABS_PRPH_START (0xA00000) + +/* + * The TLVs used in the gnl message policy between the kernel module and + * user space application. iwl_testmode_gnl_msg_policy is to be carried + * through the NL80211_CMD_TESTMODE channel regulated by nl80211. + * See iwl-testmode.h + */ +static +struct nla_policy iwl_testmode_gnl_msg_policy[IWL_TM_ATTR_MAX] = { + [IWL_TM_ATTR_COMMAND] = { .type = NLA_U32, }, + + [IWL_TM_ATTR_UCODE_CMD_ID] = { .type = NLA_U8, }, + [IWL_TM_ATTR_UCODE_CMD_DATA] = { .type = NLA_UNSPEC, }, + + [IWL_TM_ATTR_REG_OFFSET] = { .type = NLA_U32, }, + [IWL_TM_ATTR_REG_VALUE8] = { .type = NLA_U8, }, + [IWL_TM_ATTR_REG_VALUE32] = { .type = NLA_U32, }, + + [IWL_TM_ATTR_SYNC_RSP] = { .type = NLA_UNSPEC, }, + [IWL_TM_ATTR_UCODE_RX_PKT] = { .type = NLA_UNSPEC, }, + + [IWL_TM_ATTR_EEPROM] = { .type = NLA_UNSPEC, }, + + [IWL_TM_ATTR_TRACE_ADDR] = { .type = NLA_UNSPEC, }, + [IWL_TM_ATTR_TRACE_DUMP] = { .type = NLA_UNSPEC, }, + [IWL_TM_ATTR_TRACE_SIZE] = { .type = NLA_U32, }, + + [IWL_TM_ATTR_FIXRATE] = { .type = NLA_U32, }, + + [IWL_TM_ATTR_UCODE_OWNER] = { .type = NLA_U8, }, + + [IWL_TM_ATTR_MEM_ADDR] = { .type = NLA_U32, }, + [IWL_TM_ATTR_BUFFER_SIZE] = { .type = NLA_U32, }, + [IWL_TM_ATTR_BUFFER_DUMP] = { .type = NLA_UNSPEC, }, + + [IWL_TM_ATTR_FW_VERSION] = { .type = NLA_U32, }, + [IWL_TM_ATTR_DEVICE_ID] = { .type = NLA_U32, }, + [IWL_TM_ATTR_FW_TYPE] = { .type = NLA_U32, }, + [IWL_TM_ATTR_FW_INST_SIZE] = { .type = NLA_U32, }, + [IWL_TM_ATTR_FW_DATA_SIZE] = { .type = NLA_U32, }, + + [IWL_TM_ATTR_ENABLE_NOTIFICATION] = {.type = NLA_FLAG, }, +}; + +static inline void iwl_test_trace_clear(struct iwl_test *tst) +{ + memset(&tst->trace, 0, sizeof(struct iwl_test_trace)); +} + +static void iwl_test_trace_stop(struct iwl_test *tst) +{ + if (!tst->trace.enabled) + return; + + if (tst->trace.cpu_addr && tst->trace.dma_addr) + dma_free_coherent(tst->trans->dev, + tst->trace.tsize, + tst->trace.cpu_addr, + tst->trace.dma_addr); + + iwl_test_trace_clear(tst); +} + +static inline void iwl_test_mem_clear(struct iwl_test *tst) +{ + memset(&tst->mem, 0, sizeof(struct iwl_test_mem)); +} + +static inline void iwl_test_mem_stop(struct iwl_test *tst) +{ + if (!tst->mem.in_read) + return; + + iwl_test_mem_clear(tst); +} + +/* + * Initializes the test object + * During the lifetime of the test object it is assumed that the transport is + * started. The test object should be stopped before the transport is stopped. + */ +void iwl_test_init(struct iwl_test *tst, struct iwl_trans *trans, + struct iwl_test_ops *ops) +{ + tst->trans = trans; + tst->ops = ops; + + iwl_test_trace_clear(tst); + iwl_test_mem_clear(tst); +} +EXPORT_SYMBOL_GPL(iwl_test_init); + +/* + * Stop the test object + */ +void iwl_test_free(struct iwl_test *tst) +{ + iwl_test_mem_stop(tst); + iwl_test_trace_stop(tst); +} +EXPORT_SYMBOL_GPL(iwl_test_free); + +static inline int iwl_test_send_cmd(struct iwl_test *tst, + struct iwl_host_cmd *cmd) +{ + return tst->ops->send_cmd(tst->trans->op_mode, cmd); +} + +static inline bool iwl_test_valid_hw_addr(struct iwl_test *tst, u32 addr) +{ + return tst->ops->valid_hw_addr(addr); +} + +static inline u32 iwl_test_fw_ver(struct iwl_test *tst) +{ + return tst->ops->get_fw_ver(tst->trans->op_mode); +} + +static inline struct sk_buff* +iwl_test_alloc_reply(struct iwl_test *tst, int len) +{ + return tst->ops->alloc_reply(tst->trans->op_mode, len); +} + +static inline int iwl_test_reply(struct iwl_test *tst, struct sk_buff *skb) +{ + return tst->ops->reply(tst->trans->op_mode, skb); +} + +static inline struct sk_buff* +iwl_test_alloc_event(struct iwl_test *tst, int len) +{ + return tst->ops->alloc_event(tst->trans->op_mode, len); +} + +static inline void +iwl_test_event(struct iwl_test *tst, struct sk_buff *skb) +{ + return tst->ops->event(tst->trans->op_mode, skb); +} + +/* + * This function handles the user application commands to the fw. The fw + * commands are sent in a synchronuous manner. In case that the user requested + * to get commands response, it is send to the user. + */ +static int iwl_test_fw_cmd(struct iwl_test *tst, struct nlattr **tb) +{ + struct iwl_host_cmd cmd; + struct iwl_rx_packet *pkt; + struct sk_buff *skb; + void *reply_buf; + u32 reply_len; + int ret; + bool cmd_want_skb; + + memset(&cmd, 0, sizeof(struct iwl_host_cmd)); + + if (!tb[IWL_TM_ATTR_UCODE_CMD_ID] || + !tb[IWL_TM_ATTR_UCODE_CMD_DATA]) { + IWL_ERR(tst->trans, "Missing fw command mandatory fields\n"); + return -ENOMSG; + } + + cmd.flags = CMD_ON_DEMAND | CMD_SYNC; + cmd_want_skb = nla_get_flag(tb[IWL_TM_ATTR_UCODE_CMD_SKB]); + if (cmd_want_skb) + cmd.flags |= CMD_WANT_SKB; + + cmd.id = nla_get_u8(tb[IWL_TM_ATTR_UCODE_CMD_ID]); + cmd.data[0] = nla_data(tb[IWL_TM_ATTR_UCODE_CMD_DATA]); + cmd.len[0] = nla_len(tb[IWL_TM_ATTR_UCODE_CMD_DATA]); + cmd.dataflags[0] = IWL_HCMD_DFL_NOCOPY; + IWL_DEBUG_INFO(tst->trans, "test fw cmd=0x%x, flags 0x%x, len %d\n", + cmd.id, cmd.flags, cmd.len[0]); + + ret = iwl_test_send_cmd(tst, &cmd); + if (ret) { + IWL_ERR(tst->trans, "Failed to send hcmd\n"); + return ret; + } + if (!cmd_want_skb) + return ret; + + /* Handling return of SKB to the user */ + pkt = cmd.resp_pkt; + if (!pkt) { + IWL_ERR(tst->trans, "HCMD received a null response packet\n"); + return ret; + } + + reply_len = le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; + skb = iwl_test_alloc_reply(tst, reply_len + 20); + reply_buf = kmemdup(&pkt->hdr, reply_len, GFP_KERNEL); + if (!skb || !reply_buf) { + kfree_skb(skb); + kfree(reply_buf); + return -ENOMEM; + } + + /* The reply is in a page, that we cannot send to user space. */ + iwl_free_resp(&cmd); + + if (nla_put_u32(skb, IWL_TM_ATTR_COMMAND, + IWL_TM_CMD_DEV2APP_UCODE_RX_PKT) || + nla_put(skb, IWL_TM_ATTR_UCODE_RX_PKT, reply_len, reply_buf)) + goto nla_put_failure; + return iwl_test_reply(tst, skb); + +nla_put_failure: + IWL_DEBUG_INFO(tst->trans, "Failed creating NL attributes\n"); + kfree(reply_buf); + kfree_skb(skb); + return -ENOMSG; +} + +/* + * Handles the user application commands for register access. + */ +static int iwl_test_reg(struct iwl_test *tst, struct nlattr **tb) +{ + u32 ofs, val32, cmd; + u8 val8; + struct sk_buff *skb; + int status = 0; + struct iwl_trans *trans = tst->trans; + + if (!tb[IWL_TM_ATTR_REG_OFFSET]) { + IWL_ERR(trans, "Missing reg offset\n"); + return -ENOMSG; + } + + ofs = nla_get_u32(tb[IWL_TM_ATTR_REG_OFFSET]); + IWL_DEBUG_INFO(trans, "test reg access cmd offset=0x%x\n", ofs); + + cmd = nla_get_u32(tb[IWL_TM_ATTR_COMMAND]); + + /* + * Allow access only to FH/CSR/HBUS in direct mode. + * Since we don't have the upper bounds for the CSR and HBUS segments, + * we will use only the upper bound of FH for sanity check. + */ + if (ofs >= FH_MEM_UPPER_BOUND) { + IWL_ERR(trans, "offset out of segment (0x0 - 0x%x)\n", + FH_MEM_UPPER_BOUND); + return -EINVAL; + } + + switch (cmd) { + case IWL_TM_CMD_APP2DEV_DIRECT_REG_READ32: + val32 = iwl_read_direct32(tst->trans, ofs); + IWL_DEBUG_INFO(trans, "32 value to read 0x%x\n", val32); + + skb = iwl_test_alloc_reply(tst, 20); + if (!skb) { + IWL_ERR(trans, "Memory allocation fail\n"); + return -ENOMEM; + } + if (nla_put_u32(skb, IWL_TM_ATTR_REG_VALUE32, val32)) + goto nla_put_failure; + status = iwl_test_reply(tst, skb); + if (status < 0) + IWL_ERR(trans, "Error sending msg : %d\n", status); + break; + + case IWL_TM_CMD_APP2DEV_DIRECT_REG_WRITE32: + if (!tb[IWL_TM_ATTR_REG_VALUE32]) { + IWL_ERR(trans, "Missing value to write\n"); + return -ENOMSG; + } else { + val32 = nla_get_u32(tb[IWL_TM_ATTR_REG_VALUE32]); + IWL_DEBUG_INFO(trans, "32b write val=0x%x\n", val32); + iwl_write_direct32(tst->trans, ofs, val32); + } + break; + + case IWL_TM_CMD_APP2DEV_DIRECT_REG_WRITE8: + if (!tb[IWL_TM_ATTR_REG_VALUE8]) { + IWL_ERR(trans, "Missing value to write\n"); + return -ENOMSG; + } else { + val8 = nla_get_u8(tb[IWL_TM_ATTR_REG_VALUE8]); + IWL_DEBUG_INFO(trans, "8b write val=0x%x\n", val8); + iwl_write8(tst->trans, ofs, val8); + } + break; + + default: + IWL_ERR(trans, "Unknown test register cmd ID\n"); + return -ENOMSG; + } + + return status; + +nla_put_failure: + kfree_skb(skb); + return -EMSGSIZE; +} + +/* + * Handles the request to start FW tracing. Allocates of the trace buffer + * and sends a reply to user space with the address of the allocated buffer. + */ +static int iwl_test_trace_begin(struct iwl_test *tst, struct nlattr **tb) +{ + struct sk_buff *skb; + int status = 0; + + if (tst->trace.enabled) + return -EBUSY; + + if (!tb[IWL_TM_ATTR_TRACE_SIZE]) + tst->trace.size = TRACE_BUFF_SIZE_DEF; + else + tst->trace.size = + nla_get_u32(tb[IWL_TM_ATTR_TRACE_SIZE]); + + if (!tst->trace.size) + return -EINVAL; + + if (tst->trace.size < TRACE_BUFF_SIZE_MIN || + tst->trace.size > TRACE_BUFF_SIZE_MAX) + return -EINVAL; + + tst->trace.tsize = tst->trace.size + TRACE_BUFF_PADD; + tst->trace.cpu_addr = dma_alloc_coherent(tst->trans->dev, + tst->trace.tsize, + &tst->trace.dma_addr, + GFP_KERNEL); + if (!tst->trace.cpu_addr) + return -ENOMEM; + + tst->trace.enabled = true; + tst->trace.trace_addr = (u8 *)PTR_ALIGN(tst->trace.cpu_addr, 0x100); + + memset(tst->trace.trace_addr, 0x03B, tst->trace.size); + + skb = iwl_test_alloc_reply(tst, sizeof(tst->trace.dma_addr) + 20); + if (!skb) { + IWL_ERR(tst->trans, "Memory allocation fail\n"); + iwl_test_trace_stop(tst); + return -ENOMEM; + } + + if (nla_put(skb, IWL_TM_ATTR_TRACE_ADDR, + sizeof(tst->trace.dma_addr), + (u64 *)&tst->trace.dma_addr)) + goto nla_put_failure; + + status = iwl_test_reply(tst, skb); + if (status < 0) + IWL_ERR(tst->trans, "Error sending msg : %d\n", status); + + tst->trace.nchunks = DIV_ROUND_UP(tst->trace.size, + DUMP_CHUNK_SIZE); + + return status; + +nla_put_failure: + kfree_skb(skb); + if (nla_get_u32(tb[IWL_TM_ATTR_COMMAND]) == + IWL_TM_CMD_APP2DEV_BEGIN_TRACE) + iwl_test_trace_stop(tst); + return -EMSGSIZE; +} + +/* + * Handles indirect read from the periphery or the SRAM. The read is performed + * to a temporary buffer. The user space application should later issue a dump + */ +static int iwl_test_indirect_read(struct iwl_test *tst, u32 addr, u32 size) +{ + struct iwl_trans *trans = tst->trans; + unsigned long flags; + int i; + + if (size & 0x3) + return -EINVAL; + + tst->mem.size = size; + tst->mem.addr = kmalloc(tst->mem.size, GFP_KERNEL); + if (tst->mem.addr == NULL) + return -ENOMEM; + + /* Hard-coded periphery absolute address */ + if (IWL_ABS_PRPH_START <= addr && + addr < IWL_ABS_PRPH_START + PRPH_END) { + if (!iwl_trans_grab_nic_access(trans, false, &flags)) { + return -EIO; + } + iwl_write32(trans, HBUS_TARG_PRPH_RADDR, + addr | (3 << 24)); + for (i = 0; i < size; i += 4) + *(u32 *)(tst->mem.addr + i) = + iwl_read32(trans, HBUS_TARG_PRPH_RDAT); + iwl_trans_release_nic_access(trans, &flags); + } else { /* target memory (SRAM) */ + iwl_trans_read_mem(trans, addr, tst->mem.addr, + tst->mem.size / 4); + } + + tst->mem.nchunks = + DIV_ROUND_UP(tst->mem.size, DUMP_CHUNK_SIZE); + tst->mem.in_read = true; + return 0; + +} + +/* + * Handles indirect write to the periphery or SRAM. The is performed to a + * temporary buffer. + */ +static int iwl_test_indirect_write(struct iwl_test *tst, u32 addr, + u32 size, unsigned char *buf) +{ + struct iwl_trans *trans = tst->trans; + u32 val, i; + unsigned long flags; + + if (IWL_ABS_PRPH_START <= addr && + addr < IWL_ABS_PRPH_START + PRPH_END) { + /* Periphery writes can be 1-3 bytes long, or DWORDs */ + if (size < 4) { + memcpy(&val, buf, size); + if (!iwl_trans_grab_nic_access(trans, false, &flags)) + return -EIO; + iwl_write32(trans, HBUS_TARG_PRPH_WADDR, + (addr & 0x0000FFFF) | + ((size - 1) << 24)); + iwl_write32(trans, HBUS_TARG_PRPH_WDAT, val); + iwl_trans_release_nic_access(trans, &flags); + } else { + if (size % 4) + return -EINVAL; + for (i = 0; i < size; i += 4) + iwl_write_prph(trans, addr+i, + *(u32 *)(buf+i)); + } + } else if (iwl_test_valid_hw_addr(tst, addr)) { + iwl_trans_write_mem(trans, addr, buf, size / 4); + } else { + return -EINVAL; + } + return 0; +} + +/* + * Handles the user application commands for indirect read/write + * to/from the periphery or the SRAM. + */ +static int iwl_test_indirect_mem(struct iwl_test *tst, struct nlattr **tb) +{ + u32 addr, size, cmd; + unsigned char *buf; + + /* Both read and write should be blocked, for atomicity */ + if (tst->mem.in_read) + return -EBUSY; + + cmd = nla_get_u32(tb[IWL_TM_ATTR_COMMAND]); + if (!tb[IWL_TM_ATTR_MEM_ADDR]) { + IWL_ERR(tst->trans, "Error finding memory offset address\n"); + return -ENOMSG; + } + addr = nla_get_u32(tb[IWL_TM_ATTR_MEM_ADDR]); + if (!tb[IWL_TM_ATTR_BUFFER_SIZE]) { + IWL_ERR(tst->trans, "Error finding size for memory reading\n"); + return -ENOMSG; + } + size = nla_get_u32(tb[IWL_TM_ATTR_BUFFER_SIZE]); + + if (cmd == IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_READ) { + return iwl_test_indirect_read(tst, addr, size); + } else { + if (!tb[IWL_TM_ATTR_BUFFER_DUMP]) + return -EINVAL; + buf = (unsigned char *)nla_data(tb[IWL_TM_ATTR_BUFFER_DUMP]); + return iwl_test_indirect_write(tst, addr, size, buf); + } +} + +/* + * Enable notifications to user space + */ +static int iwl_test_notifications(struct iwl_test *tst, + struct nlattr **tb) +{ + tst->notify = nla_get_flag(tb[IWL_TM_ATTR_ENABLE_NOTIFICATION]); + return 0; +} + +/* + * Handles the request to get the device id + */ +static int iwl_test_get_dev_id(struct iwl_test *tst, struct nlattr **tb) +{ + u32 devid = tst->trans->hw_id; + struct sk_buff *skb; + int status; + + IWL_DEBUG_INFO(tst->trans, "hw version: 0x%x\n", devid); + + skb = iwl_test_alloc_reply(tst, 20); + if (!skb) { + IWL_ERR(tst->trans, "Memory allocation fail\n"); + return -ENOMEM; + } + + if (nla_put_u32(skb, IWL_TM_ATTR_DEVICE_ID, devid)) + goto nla_put_failure; + status = iwl_test_reply(tst, skb); + if (status < 0) + IWL_ERR(tst->trans, "Error sending msg : %d\n", status); + + return 0; + +nla_put_failure: + kfree_skb(skb); + return -EMSGSIZE; +} + +/* + * Handles the request to get the FW version + */ +static int iwl_test_get_fw_ver(struct iwl_test *tst, struct nlattr **tb) +{ + struct sk_buff *skb; + int status; + u32 ver = iwl_test_fw_ver(tst); + + IWL_DEBUG_INFO(tst->trans, "uCode version raw: 0x%x\n", ver); + + skb = iwl_test_alloc_reply(tst, 20); + if (!skb) { + IWL_ERR(tst->trans, "Memory allocation fail\n"); + return -ENOMEM; + } + + if (nla_put_u32(skb, IWL_TM_ATTR_FW_VERSION, ver)) + goto nla_put_failure; + + status = iwl_test_reply(tst, skb); + if (status < 0) + IWL_ERR(tst->trans, "Error sending msg : %d\n", status); + + return 0; + +nla_put_failure: + kfree_skb(skb); + return -EMSGSIZE; +} + +/* + * Parse the netlink message and validate that the IWL_TM_ATTR_CMD exists + */ +int iwl_test_parse(struct iwl_test *tst, struct nlattr **tb, + void *data, int len) +{ + int result; + + result = nla_parse(tb, IWL_TM_ATTR_MAX - 1, data, len, + iwl_testmode_gnl_msg_policy); + if (result) { + IWL_ERR(tst->trans, "Fail parse gnl msg: %d\n", result); + return result; + } + + /* IWL_TM_ATTR_COMMAND is absolutely mandatory */ + if (!tb[IWL_TM_ATTR_COMMAND]) { + IWL_ERR(tst->trans, "Missing testmode command type\n"); + return -ENOMSG; + } + return 0; +} +IWL_EXPORT_SYMBOL(iwl_test_parse); + +/* + * Handle test commands. + * Returns 1 for unknown commands (not handled by the test object); negative + * value in case of error. + */ +int iwl_test_handle_cmd(struct iwl_test *tst, struct nlattr **tb) +{ + int result; + + switch (nla_get_u32(tb[IWL_TM_ATTR_COMMAND])) { + case IWL_TM_CMD_APP2DEV_UCODE: + IWL_DEBUG_INFO(tst->trans, "test cmd to uCode\n"); + result = iwl_test_fw_cmd(tst, tb); + break; + + case IWL_TM_CMD_APP2DEV_DIRECT_REG_READ32: + case IWL_TM_CMD_APP2DEV_DIRECT_REG_WRITE32: + case IWL_TM_CMD_APP2DEV_DIRECT_REG_WRITE8: + IWL_DEBUG_INFO(tst->trans, "test cmd to register\n"); + result = iwl_test_reg(tst, tb); + break; + + case IWL_TM_CMD_APP2DEV_BEGIN_TRACE: + IWL_DEBUG_INFO(tst->trans, "test uCode trace cmd to driver\n"); + result = iwl_test_trace_begin(tst, tb); + break; + + case IWL_TM_CMD_APP2DEV_END_TRACE: + iwl_test_trace_stop(tst); + result = 0; + break; + + case IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_READ: + case IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_WRITE: + IWL_DEBUG_INFO(tst->trans, "test indirect memory cmd\n"); + result = iwl_test_indirect_mem(tst, tb); + break; + + case IWL_TM_CMD_APP2DEV_NOTIFICATIONS: + IWL_DEBUG_INFO(tst->trans, "test notifications cmd\n"); + result = iwl_test_notifications(tst, tb); + break; + + case IWL_TM_CMD_APP2DEV_GET_FW_VERSION: + IWL_DEBUG_INFO(tst->trans, "test get FW ver cmd\n"); + result = iwl_test_get_fw_ver(tst, tb); + break; + + case IWL_TM_CMD_APP2DEV_GET_DEVICE_ID: + IWL_DEBUG_INFO(tst->trans, "test Get device ID cmd\n"); + result = iwl_test_get_dev_id(tst, tb); + break; + + default: + IWL_DEBUG_INFO(tst->trans, "Unknown test command\n"); + result = 1; + break; + } + return result; +} +IWL_EXPORT_SYMBOL(iwl_test_handle_cmd); + +static int iwl_test_trace_dump(struct iwl_test *tst, struct sk_buff *skb, + struct netlink_callback *cb) +{ + int idx, length; + + if (!tst->trace.enabled || !tst->trace.trace_addr) + return -EFAULT; + + idx = cb->args[4]; + if (idx >= tst->trace.nchunks) + return -ENOENT; + + length = DUMP_CHUNK_SIZE; + if (((idx + 1) == tst->trace.nchunks) && + (tst->trace.size % DUMP_CHUNK_SIZE)) + length = tst->trace.size % + DUMP_CHUNK_SIZE; + + if (nla_put(skb, IWL_TM_ATTR_TRACE_DUMP, length, + tst->trace.trace_addr + (DUMP_CHUNK_SIZE * idx))) + goto nla_put_failure; + + cb->args[4] = ++idx; + return 0; + + nla_put_failure: + return -ENOBUFS; +} + +static int iwl_test_buffer_dump(struct iwl_test *tst, struct sk_buff *skb, + struct netlink_callback *cb) +{ + int idx, length; + + if (!tst->mem.in_read) + return -EFAULT; + + idx = cb->args[4]; + if (idx >= tst->mem.nchunks) { + iwl_test_mem_stop(tst); + return -ENOENT; + } + + length = DUMP_CHUNK_SIZE; + if (((idx + 1) == tst->mem.nchunks) && + (tst->mem.size % DUMP_CHUNK_SIZE)) + length = tst->mem.size % DUMP_CHUNK_SIZE; + + if (nla_put(skb, IWL_TM_ATTR_BUFFER_DUMP, length, + tst->mem.addr + (DUMP_CHUNK_SIZE * idx))) + goto nla_put_failure; + + cb->args[4] = ++idx; + return 0; + + nla_put_failure: + return -ENOBUFS; +} + +/* + * Handle dump commands. + * Returns 1 for unknown commands (not handled by the test object); negative + * value in case of error. + */ +int iwl_test_dump(struct iwl_test *tst, u32 cmd, struct sk_buff *skb, + struct netlink_callback *cb) +{ + int result; + + switch (cmd) { + case IWL_TM_CMD_APP2DEV_READ_TRACE: + IWL_DEBUG_INFO(tst->trans, "uCode trace cmd\n"); + result = iwl_test_trace_dump(tst, skb, cb); + break; + + case IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_DUMP: + IWL_DEBUG_INFO(tst->trans, "testmode sram dump cmd\n"); + result = iwl_test_buffer_dump(tst, skb, cb); + break; + + default: + result = 1; + break; + } + return result; +} +IWL_EXPORT_SYMBOL(iwl_test_dump); + +/* + * Multicast a spontaneous messages from the device to the user space. + */ +static void iwl_test_send_rx(struct iwl_test *tst, + struct iwl_rx_cmd_buffer *rxb) +{ + struct sk_buff *skb; + struct iwl_rx_packet *data; + int length; + + data = rxb_addr(rxb); + length = le32_to_cpu(data->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; + + /* the length doesn't include len_n_flags field, so add it manually */ + length += sizeof(__le32); + + skb = iwl_test_alloc_event(tst, length + 20); + if (skb == NULL) { + IWL_ERR(tst->trans, "Out of memory for message to user\n"); + return; + } + + if (nla_put_u32(skb, IWL_TM_ATTR_COMMAND, + IWL_TM_CMD_DEV2APP_UCODE_RX_PKT) || + nla_put(skb, IWL_TM_ATTR_UCODE_RX_PKT, length, data)) + goto nla_put_failure; + + iwl_test_event(tst, skb); + return; + +nla_put_failure: + kfree_skb(skb); + IWL_ERR(tst->trans, "Ouch, overran buffer, check allocation!\n"); +} + +/* + * Called whenever a Rx frames is recevied from the device. If notifications to + * the user space are requested, sends the frames to the user. + */ +void iwl_test_rx(struct iwl_test *tst, struct iwl_rx_cmd_buffer *rxb) +{ + if (tst->notify) + iwl_test_send_rx(tst, rxb); +} +IWL_EXPORT_SYMBOL(iwl_test_rx); diff --git a/drivers/net/wireless/iwlwifi/iwl-test.h b/drivers/net/wireless/iwlwifi/iwl-test.h new file mode 100644 index 0000000..8fbd217 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-test.h @@ -0,0 +1,161 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2010 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2010 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#ifndef __IWL_TEST_H__ +#define __IWL_TEST_H__ + +#include +#include "iwl-trans.h" + +struct iwl_test_trace { + u32 size; + u32 tsize; + u32 nchunks; + u8 *cpu_addr; + u8 *trace_addr; + dma_addr_t dma_addr; + bool enabled; +}; + +struct iwl_test_mem { + u32 size; + u32 nchunks; + u8 *addr; + bool in_read; +}; + +/* + * struct iwl_test_ops: callback to the op mode + * + * The structure defines the callbacks that the op_mode should handle, + * inorder to handle logic that is out of the scope of iwl_test. The + * op_mode must set all the callbacks. + + * @send_cmd: handler that is used by the test object to request the + * op_mode to send a command to the fw. + * + * @valid_hw_addr: handler that is used by the test object to request the + * op_mode to check if the given address is a valid address. + * + * @get_fw_ver: handler used to get the FW version. + * + * @alloc_reply: handler used by the test object to request the op_mode + * to allocate an skb for sending a reply to the user, and initialize + * the skb. It is assumed that the test object only fills the required + * attributes. + * + * @reply: handler used by the test object to request the op_mode to reply + * to a request. The skb is an skb previously allocated by the the + * alloc_reply callback. + I + * @alloc_event: handler used by the test object to request the op_mode + * to allocate an skb for sending an event, and initialize + * the skb. It is assumed that the test object only fills the required + * attributes. + * + * @reply: handler used by the test object to request the op_mode to send + * an event. The skb is an skb previously allocated by the the + * alloc_event callback. + */ +struct iwl_test_ops { + int (*send_cmd)(struct iwl_op_mode *op_modes, + struct iwl_host_cmd *cmd); + bool (*valid_hw_addr)(u32 addr); + u32 (*get_fw_ver)(struct iwl_op_mode *op_mode); + + struct sk_buff *(*alloc_reply)(struct iwl_op_mode *op_mode, int len); + int (*reply)(struct iwl_op_mode *op_mode, struct sk_buff *skb); + struct sk_buff* (*alloc_event)(struct iwl_op_mode *op_mode, int len); + void (*event)(struct iwl_op_mode *op_mode, struct sk_buff *skb); +}; + +struct iwl_test { + struct iwl_trans *trans; + struct iwl_test_ops *ops; + struct iwl_test_trace trace; + struct iwl_test_mem mem; + bool notify; +}; + +void iwl_test_init(struct iwl_test *tst, struct iwl_trans *trans, + struct iwl_test_ops *ops); + +void iwl_test_free(struct iwl_test *tst); + +int iwl_test_parse(struct iwl_test *tst, struct nlattr **tb, + void *data, int len); + +int iwl_test_handle_cmd(struct iwl_test *tst, struct nlattr **tb); + +int iwl_test_dump(struct iwl_test *tst, u32 cmd, struct sk_buff *skb, + struct netlink_callback *cb); + +void iwl_test_rx(struct iwl_test *tst, struct iwl_rx_cmd_buffer *rxb); + +static inline void iwl_test_enable_notifications(struct iwl_test *tst, + bool enable) +{ + tst->notify = enable; +} + +#endif diff --git a/drivers/net/wireless/iwlwifi/iwl-testmode.h b/drivers/net/wireless/iwlwifi/iwl-testmode.h new file mode 100644 index 0000000..98f48a9 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-testmode.h @@ -0,0 +1,309 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2010 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2010 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#ifndef __IWL_TESTMODE_H__ +#define __IWL_TESTMODE_H__ + +#include + + +/* + * Commands from user space to kernel space(IWL_TM_CMD_ID_APP2DEV_XX) and + * from and kernel space to user space(IWL_TM_CMD_ID_DEV2APP_XX). + * The command ID is carried with IWL_TM_ATTR_COMMAND. + * + * @IWL_TM_CMD_APP2DEV_UCODE: + * commands from user application to the uCode, + * the actual uCode host command ID is carried with + * IWL_TM_ATTR_UCODE_CMD_ID + * + * @IWL_TM_CMD_APP2DEV_DIRECT_REG_READ32: + * @IWL_TM_CMD_APP2DEV_DIRECT_REG_WRITE32: + * @IWL_TM_CMD_APP2DEV_DIRECT_REG_WRITE8: + * commands from user applicaiton to access register + * + * @IWL_TM_CMD_APP2DEV_GET_DEVICENAME: retrieve device name + * @IWL_TM_CMD_APP2DEV_LOAD_INIT_FW: load initial uCode image + * @IWL_TM_CMD_APP2DEV_CFG_INIT_CALIB: perform calibration + * @IWL_TM_CMD_APP2DEV_LOAD_RUNTIME_FW: load runtime uCode image + * @IWL_TM_CMD_APP2DEV_GET_EEPROM: request EEPROM data + * @IWL_TM_CMD_APP2DEV_FIXRATE_REQ: set fix MCS + * commands fom user space for pure driver level operations + * + * @IWL_TM_CMD_APP2DEV_BEGIN_TRACE: + * @IWL_TM_CMD_APP2DEV_END_TRACE: + * @IWL_TM_CMD_APP2DEV_READ_TRACE: + * commands fom user space for uCode trace operations + * + * @IWL_TM_CMD_DEV2APP_SYNC_RSP: + * commands from kernel space to carry the synchronous response + * to user application + * @IWL_TM_CMD_DEV2APP_UCODE_RX_PKT: + * commands from kernel space to multicast the spontaneous messages + * to user application, or reply of host commands + * @IWL_TM_CMD_DEV2APP_EEPROM_RSP: + * commands from kernel space to carry the eeprom response + * to user application + * + * @IWL_TM_CMD_APP2DEV_OWNERSHIP: + * commands from user application to own change the ownership of the uCode + * if application has the ownership, the only host command from + * testmode will deliver to uCode. Default owner is driver + * + * @IWL_TM_CMD_APP2DEV_LOAD_WOWLAN_FW: load Wake On Wireless LAN uCode image + * @IWL_TM_CMD_APP2DEV_GET_FW_VERSION: retrieve uCode version + * @IWL_TM_CMD_APP2DEV_GET_DEVICE_ID: retrieve ID information in device + * @IWL_TM_CMD_APP2DEV_GET_FW_INFO: + * retrieve information of existing loaded uCode image + * + * @IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_READ: + * @IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_DUMP: + * @IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_WRITE: + * Commands to read/write data from periphery or SRAM memory ranges. + * Fore reading, a READ command is sent from the userspace and the data + * is returned when the user calls a DUMP command. + * For writing, only a WRITE command is used. + * @IWL_TM_CMD_APP2DEV_NOTIFICATIONS: + * Command to enable/disable notifications (currently RX packets) from the + * driver to userspace. + */ +enum iwl_tm_cmd_t { + IWL_TM_CMD_APP2DEV_UCODE = 1, + IWL_TM_CMD_APP2DEV_DIRECT_REG_READ32 = 2, + IWL_TM_CMD_APP2DEV_DIRECT_REG_WRITE32 = 3, + IWL_TM_CMD_APP2DEV_DIRECT_REG_WRITE8 = 4, + IWL_TM_CMD_APP2DEV_GET_DEVICENAME = 5, + IWL_TM_CMD_APP2DEV_LOAD_INIT_FW = 6, + IWL_TM_CMD_APP2DEV_CFG_INIT_CALIB = 7, + IWL_TM_CMD_APP2DEV_LOAD_RUNTIME_FW = 8, + IWL_TM_CMD_APP2DEV_GET_EEPROM = 9, + IWL_TM_CMD_APP2DEV_FIXRATE_REQ = 10, + IWL_TM_CMD_APP2DEV_BEGIN_TRACE = 11, + IWL_TM_CMD_APP2DEV_END_TRACE = 12, + IWL_TM_CMD_APP2DEV_READ_TRACE = 13, + IWL_TM_CMD_DEV2APP_SYNC_RSP = 14, + IWL_TM_CMD_DEV2APP_UCODE_RX_PKT = 15, + IWL_TM_CMD_DEV2APP_EEPROM_RSP = 16, + IWL_TM_CMD_APP2DEV_OWNERSHIP = 17, + RESERVED_18 = 18, + RESERVED_19 = 19, + RESERVED_20 = 20, + RESERVED_21 = 21, + IWL_TM_CMD_APP2DEV_LOAD_WOWLAN_FW = 22, + IWL_TM_CMD_APP2DEV_GET_FW_VERSION = 23, + IWL_TM_CMD_APP2DEV_GET_DEVICE_ID = 24, + IWL_TM_CMD_APP2DEV_GET_FW_INFO = 25, + IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_READ = 26, + IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_DUMP = 27, + IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_WRITE = 28, + IWL_TM_CMD_APP2DEV_NOTIFICATIONS = 29, + IWL_TM_CMD_MAX = 30, +}; + +/* + * Atrribute filed in testmode command + * See enum iwl_tm_cmd_t. + * + * @IWL_TM_ATTR_NOT_APPLICABLE: + * The attribute is not applicable or invalid + * @IWL_TM_ATTR_COMMAND: + * From user space to kernel space: + * the command either destines to ucode, driver, or register; + * From kernel space to user space: + * the command either carries synchronous response, + * or the spontaneous message multicast from the device; + * + * @IWL_TM_ATTR_UCODE_CMD_ID: + * @IWL_TM_ATTR_UCODE_CMD_DATA: + * When IWL_TM_ATTR_COMMAND is IWL_TM_CMD_APP2DEV_UCODE, + * The mandatory fields are : + * IWL_TM_ATTR_UCODE_CMD_ID for recognizable command ID; + * IWL_TM_ATTR_UCODE_CMD_DATA for the actual command payload + * to the ucode + * + * @IWL_TM_ATTR_REG_OFFSET: + * @IWL_TM_ATTR_REG_VALUE8: + * @IWL_TM_ATTR_REG_VALUE32: + * When IWL_TM_ATTR_COMMAND is IWL_TM_CMD_APP2DEV_REG_XXX, + * The mandatory fields are: + * IWL_TM_ATTR_REG_OFFSET for the offset of the target register; + * IWL_TM_ATTR_REG_VALUE8 or IWL_TM_ATTR_REG_VALUE32 for value + * + * @IWL_TM_ATTR_SYNC_RSP: + * When IWL_TM_ATTR_COMMAND is IWL_TM_CMD_DEV2APP_SYNC_RSP, + * The mandatory fields are: + * IWL_TM_ATTR_SYNC_RSP for the data content responding to the user + * application command + * + * @IWL_TM_ATTR_UCODE_RX_PKT: + * When IWL_TM_ATTR_COMMAND is IWL_TM_CMD_DEV2APP_UCODE_RX_PKT, + * The mandatory fields are: + * IWL_TM_ATTR_UCODE_RX_PKT for the data content multicast to the user + * application + * + * @IWL_TM_ATTR_EEPROM: + * When IWL_TM_ATTR_COMMAND is IWL_TM_CMD_DEV2APP_EEPROM, + * The mandatory fields are: + * IWL_TM_ATTR_EEPROM for the data content responging to the user + * application + * + * @IWL_TM_ATTR_TRACE_ADDR: + * @IWL_TM_ATTR_TRACE_SIZE: + * @IWL_TM_ATTR_TRACE_DUMP: + * When IWL_TM_ATTR_COMMAND is IWL_TM_CMD_APP2DEV_XXX_TRACE, + * The mandatory fields are: + * IWL_TM_ATTR_MEM_TRACE_ADDR for the trace address + * IWL_TM_ATTR_MEM_TRACE_SIZE for the trace buffer size + * IWL_TM_ATTR_MEM_TRACE_DUMP for the trace dump + * + * @IWL_TM_ATTR_FIXRATE: + * When IWL_TM_ATTR_COMMAND is IWL_TM_CMD_APP2DEV_FIXRATE_REQ, + * The mandatory fields are: + * IWL_TM_ATTR_FIXRATE for the fixed rate + * + * @IWL_TM_ATTR_UCODE_OWNER: + * When IWL_TM_ATTR_COMMAND is IWL_TM_CMD_APP2DEV_OWNERSHIP, + * The mandatory fields are: + * IWL_TM_ATTR_UCODE_OWNER for the new owner + * + * @IWL_TM_ATTR_MEM_ADDR: + * @IWL_TM_ATTR_BUFFER_SIZE: + * When IWL_TM_ATTR_COMMAND is IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_READ + * or IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_WRITE. + * The mandatory fields are: + * IWL_TM_ATTR_MEM_ADDR for the address in SRAM/periphery to read/write + * IWL_TM_ATTR_BUFFER_SIZE for the buffer size of data to read/write. + * + * @IWL_TM_ATTR_BUFFER_DUMP: + * When IWL_TM_ATTR_COMMAND is IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_DUMP, + * IWL_TM_ATTR_BUFFER_DUMP is used for the data that was read. + * When IWL_TM_ATTR_COMMAND is IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_WRITE, + * this attribute contains the data to write. + * + * @IWL_TM_ATTR_FW_VERSION: + * When IWL_TM_ATTR_COMMAND is IWL_TM_CMD_APP2DEV_GET_FW_VERSION, + * IWL_TM_ATTR_FW_VERSION for the uCode version + * + * @IWL_TM_ATTR_DEVICE_ID: + * When IWL_TM_ATTR_COMMAND is IWL_TM_CMD_APP2DEV_GET_DEVICE_ID, + * IWL_TM_ATTR_DEVICE_ID for the device ID information + * + * @IWL_TM_ATTR_FW_TYPE: + * @IWL_TM_ATTR_FW_INST_SIZE: + * @IWL_TM_ATTR_FW_DATA_SIZE: + * When IWL_TM_ATTR_COMMAND is IWL_TM_CMD_APP2DEV_GET_FW_INFO, + * The mandatory fields are: + * IWL_TM_ATTR_FW_TYPE for the uCode type (INIT/RUNTIME/...) + * IWL_TM_ATTR_FW_INST_SIZE for the size of instruction section + * IWL_TM_ATTR_FW_DATA_SIZE for the size of data section + * + * @IWL_TM_ATTR_UCODE_CMD_SKB: + * When IWL_TM_ATTR_COMMAND is IWL_TM_CMD_APP2DEV_UCODE this flag + * indicates that the user wants to receive the response of the command + * in a reply SKB. If it's not present, the response is not returned. + * @IWL_TM_ATTR_ENABLE_NOTIFICATIONS: + * When IWL_TM_ATTR_COMMAND is IWL_TM_CMD_APP2DEV_NOTIFICATIONS, this + * flag enables (if present) or disables (if not) the forwarding + * to userspace. + */ +enum iwl_tm_attr_t { + IWL_TM_ATTR_NOT_APPLICABLE = 0, + IWL_TM_ATTR_COMMAND = 1, + IWL_TM_ATTR_UCODE_CMD_ID = 2, + IWL_TM_ATTR_UCODE_CMD_DATA = 3, + IWL_TM_ATTR_REG_OFFSET = 4, + IWL_TM_ATTR_REG_VALUE8 = 5, + IWL_TM_ATTR_REG_VALUE32 = 6, + IWL_TM_ATTR_SYNC_RSP = 7, + IWL_TM_ATTR_UCODE_RX_PKT = 8, + IWL_TM_ATTR_EEPROM = 9, + IWL_TM_ATTR_TRACE_ADDR = 10, + IWL_TM_ATTR_TRACE_SIZE = 11, + IWL_TM_ATTR_TRACE_DUMP = 12, + IWL_TM_ATTR_FIXRATE = 13, + IWL_TM_ATTR_UCODE_OWNER = 14, + IWL_TM_ATTR_MEM_ADDR = 15, + IWL_TM_ATTR_BUFFER_SIZE = 16, + IWL_TM_ATTR_BUFFER_DUMP = 17, + IWL_TM_ATTR_FW_VERSION = 18, + IWL_TM_ATTR_DEVICE_ID = 19, + IWL_TM_ATTR_FW_TYPE = 20, + IWL_TM_ATTR_FW_INST_SIZE = 21, + IWL_TM_ATTR_FW_DATA_SIZE = 22, + IWL_TM_ATTR_UCODE_CMD_SKB = 23, + IWL_TM_ATTR_ENABLE_NOTIFICATION = 24, + IWL_TM_ATTR_MAX = 25, +}; + +/* uCode trace buffer */ +#define TRACE_BUFF_SIZE_MAX 0x200000 +#define TRACE_BUFF_SIZE_MIN 0x20000 +#define TRACE_BUFF_SIZE_DEF TRACE_BUFF_SIZE_MIN +#define TRACE_BUFF_PADD 0x2000 + +/* Maximum data size of each dump it packet */ +#define DUMP_CHUNK_SIZE (PAGE_SIZE - 1024) + +/* Address offset of data segment in SRAM */ +#define SRAM_DATA_SEG_OFFSET 0x800000 + +#endif diff --git a/drivers/net/wireless/iwlwifi/iwl-trans.h b/drivers/net/wireless/iwlwifi/iwl-trans.h new file mode 100644 index 0000000..7a13790 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/iwl-trans.h @@ -0,0 +1,813 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#ifndef __iwl_trans_h__ +#define __iwl_trans_h__ + +#include +#include /* for page_address */ +#include + +#include "iwl-debug.h" +#include "iwl-config.h" +#include "iwl-fw.h" + +/** + * DOC: Transport layer - what is it ? + * + * The tranport layer is the layer that deals with the HW directly. It provides + * an abstraction of the underlying HW to the upper layer. The transport layer + * doesn't provide any policy, algorithm or anything of this kind, but only + * mechanisms to make the HW do something.It is not completely stateless but + * close to it. + * We will have an implementation for each different supported bus. + */ + +/** + * DOC: Life cycle of the transport layer + * + * The transport layer has a very precise life cycle. + * + * 1) A helper function is called during the module initialization and + * registers the bus driver's ops with the transport's alloc function. + * 2) Bus's probe calls to the transport layer's allocation functions. + * Of course this function is bus specific. + * 3) This allocation functions will spawn the upper layer which will + * register mac80211. + * + * 4) At some point (i.e. mac80211's start call), the op_mode will call + * the following sequence: + * start_hw + * start_fw + * + * 5) Then when finished (or reset): + * stop_fw (a.k.a. stop device for the moment) + * stop_hw + * + * 6) Eventually, the free function will be called. + */ + +/** + * DOC: Host command section + * + * A host command is a commaned issued by the upper layer to the fw. There are + * several versions of fw that have several APIs. The transport layer is + * completely agnostic to these differences. + * The transport does provide helper functionnality (i.e. SYNC / ASYNC mode), + */ +#define SEQ_TO_QUEUE(s) (((s) >> 8) & 0x1f) +#define QUEUE_TO_SEQ(q) (((q) & 0x1f) << 8) +#define SEQ_TO_INDEX(s) ((s) & 0xff) +#define INDEX_TO_SEQ(i) ((i) & 0xff) +#define SEQ_RX_FRAME cpu_to_le16(0x8000) + +/** + * struct iwl_cmd_header + * + * This header format appears in the beginning of each command sent from the + * driver, and each response/notification received from uCode. + */ +struct iwl_cmd_header { + u8 cmd; /* Command ID: REPLY_RXON, etc. */ + u8 flags; /* 0:5 reserved, 6 abort, 7 internal */ + /* + * The driver sets up the sequence number to values of its choosing. + * uCode does not use this value, but passes it back to the driver + * when sending the response to each driver-originated command, so + * the driver can match the response to the command. Since the values + * don't get used by uCode, the driver may set up an arbitrary format. + * + * There is one exception: uCode sets bit 15 when it originates + * the response/notification, i.e. when the response/notification + * is not a direct response to a command sent by the driver. For + * example, uCode issues REPLY_RX when it sends a received frame + * to the driver; it is not a direct response to any driver command. + * + * The Linux driver uses the following format: + * + * 0:7 tfd index - position within TX queue + * 8:12 TX queue id + * 13:14 reserved + * 15 unsolicited RX or uCode-originated notification + */ + __le16 sequence; +} __packed; + +/* iwl_cmd_header flags value */ +#define IWL_CMD_FAILED_MSK 0x40 + + +#define FH_RSCSR_FRAME_SIZE_MSK 0x00003FFF /* bits 0-13 */ +#define FH_RSCSR_FRAME_INVALID 0x55550000 +#define FH_RSCSR_FRAME_ALIGN 0x40 + +struct iwl_rx_packet { + /* + * The first 4 bytes of the RX frame header contain both the RX frame + * size and some flags. + * Bit fields: + * 31: flag flush RB request + * 30: flag ignore TC (terminal counter) request + * 29: flag fast IRQ request + * 28-14: Reserved + * 13-00: RX frame size + */ + __le32 len_n_flags; + struct iwl_cmd_header hdr; + u8 data[]; +} __packed; + +/** + * enum CMD_MODE - how to send the host commands ? + * + * @CMD_SYNC: The caller will be stalled until the fw responds to the command + * @CMD_ASYNC: Return right away and don't want for the response + * @CMD_WANT_SKB: valid only with CMD_SYNC. The caller needs the buffer of the + * response. The caller needs to call iwl_free_resp when done. + * @CMD_ON_DEMAND: This command is sent by the test mode pipe. + */ +enum CMD_MODE { + CMD_SYNC = 0, + CMD_ASYNC = BIT(0), + CMD_WANT_SKB = BIT(1), + CMD_ON_DEMAND = BIT(2), +}; + +#define DEF_CMD_PAYLOAD_SIZE 320 + +/** + * struct iwl_device_cmd + * + * For allocation of the command and tx queues, this establishes the overall + * size of the largest command we send to uCode, except for commands that + * aren't fully copied and use other TFD space. + */ +struct iwl_device_cmd { + struct iwl_cmd_header hdr; /* uCode API */ + u8 payload[DEF_CMD_PAYLOAD_SIZE]; +} __packed; + +#define TFD_MAX_PAYLOAD_SIZE (sizeof(struct iwl_device_cmd)) + +/* + * number of transfer buffers (fragments) per transmit frame descriptor; + * this is just the driver's idea, the hardware supports 20 + */ +#define IWL_MAX_CMD_TBS_PER_TFD 2 + +/** + * struct iwl_hcmd_dataflag - flag for each one of the chunks of the command + * + * @IWL_HCMD_DFL_NOCOPY: By default, the command is copied to the host command's + * ring. The transport layer doesn't map the command's buffer to DMA, but + * rather copies it to an previously allocated DMA buffer. This flag tells + * the transport layer not to copy the command, but to map the existing + * buffer (that is passed in) instead. This saves the memcpy and allows + * commands that are bigger than the fixed buffer to be submitted. + * Note that a TFD entry after a NOCOPY one cannot be a normal copied one. + * @IWL_HCMD_DFL_DUP: Only valid without NOCOPY, duplicate the memory for this + * chunk internally and free it again after the command completes. This + * can (currently) be used only once per command. + * Note that a TFD entry after a DUP one cannot be a normal copied one. + */ +enum iwl_hcmd_dataflag { + IWL_HCMD_DFL_NOCOPY = BIT(0), + IWL_HCMD_DFL_DUP = BIT(1), +}; + +/** + * struct iwl_host_cmd - Host command to the uCode + * + * @data: array of chunks that composes the data of the host command + * @resp_pkt: response packet, if %CMD_WANT_SKB was set + * @_rx_page_order: (internally used to free response packet) + * @_rx_page_addr: (internally used to free response packet) + * @handler_status: return value of the handler of the command + * (put in setup_rx_handlers) - valid for SYNC mode only + * @flags: can be CMD_* + * @len: array of the lenths of the chunks in data + * @dataflags: IWL_HCMD_DFL_* + * @id: id of the host command + */ +struct iwl_host_cmd { + const void *data[IWL_MAX_CMD_TBS_PER_TFD]; + struct iwl_rx_packet *resp_pkt; + unsigned long _rx_page_addr; + u32 _rx_page_order; + int handler_status; + + u32 flags; + u16 len[IWL_MAX_CMD_TBS_PER_TFD]; + u8 dataflags[IWL_MAX_CMD_TBS_PER_TFD]; + u8 id; +}; + +static inline void iwl_free_resp(struct iwl_host_cmd *cmd) +{ + free_pages(cmd->_rx_page_addr, cmd->_rx_page_order); +} + +struct iwl_rx_cmd_buffer { + struct page *_page; + int _offset; + bool _page_stolen; + u32 _rx_page_order; + unsigned int truesize; +}; + +static inline void *rxb_addr(struct iwl_rx_cmd_buffer *r) +{ + return (void *)((unsigned long)page_address(r->_page) + r->_offset); +} + +static inline int rxb_offset(struct iwl_rx_cmd_buffer *r) +{ + return r->_offset; +} + +static inline struct page *rxb_steal_page(struct iwl_rx_cmd_buffer *r) +{ + r->_page_stolen = true; + get_page(r->_page); + return r->_page; +} + +static inline void iwl_free_rxb(struct iwl_rx_cmd_buffer *r) +{ + __free_pages(r->_page, r->_rx_page_order); +} + +#define MAX_NO_RECLAIM_CMDS 6 + +#define IWL_MASK(lo, hi) ((1 << (hi)) | ((1 << (hi)) - (1 << (lo)))) + +/* + * Maximum number of HW queues the transport layer + * currently supports + */ +#define IWL_MAX_HW_QUEUES 32 +#define IWL_MAX_TID_COUNT 8 +#define IWL_FRAME_LIMIT 64 + +/** + * enum iwl_wowlan_status - WoWLAN image/device status + * @IWL_D3_STATUS_ALIVE: firmware is still running after resume + * @IWL_D3_STATUS_RESET: device was reset while suspended + */ +enum iwl_d3_status { + IWL_D3_STATUS_ALIVE, + IWL_D3_STATUS_RESET, +}; + +/** + * struct iwl_trans_config - transport configuration + * + * @op_mode: pointer to the upper layer. + * @cmd_queue: the index of the command queue. + * Must be set before start_fw. + * @cmd_fifo: the fifo for host commands + * @no_reclaim_cmds: Some devices erroneously don't set the + * SEQ_RX_FRAME bit on some notifications, this is the + * list of such notifications to filter. Max length is + * %MAX_NO_RECLAIM_CMDS. + * @n_no_reclaim_cmds: # of commands in list + * @rx_buf_size_8k: 8 kB RX buffer size needed for A-MSDUs, + * if unset 4k will be the RX buffer size + * @bc_table_dword: set to true if the BC table expects the byte count to be + * in DWORD (as opposed to bytes) + * @queue_watchdog_timeout: time (in ms) after which queues + * are considered stuck and will trigger device restart + * @command_names: array of command names, must be 256 entries + * (one for each command); for debugging only + */ +struct iwl_trans_config { + struct iwl_op_mode *op_mode; + + u8 cmd_queue; + u8 cmd_fifo; + const u8 *no_reclaim_cmds; + int n_no_reclaim_cmds; + + bool rx_buf_size_8k; + bool bc_table_dword; + unsigned int queue_watchdog_timeout; + const char **command_names; +}; + +struct iwl_trans; + +/** + * struct iwl_trans_ops - transport specific operations + * + * All the handlers MUST be implemented + * + * @start_hw: starts the HW- from that point on, the HW can send interrupts + * May sleep + * @stop_hw: stops the HW- from that point on, the HW will be in low power but + * will still issue interrupt if the HW RF kill is triggered unless + * op_mode_leaving is true. + * May sleep + * @start_fw: allocates and inits all the resources for the transport + * layer. Also kick a fw image. + * May sleep + * @fw_alive: called when the fw sends alive notification. If the fw provides + * the SCD base address in SRAM, then provide it here, or 0 otherwise. + * May sleep + * @stop_device:stops the whole device (embedded CPU put to reset) + * May sleep + * @d3_suspend: put the device into the correct mode for WoWLAN during + * suspend. This is optional, if not implemented WoWLAN will not be + * supported. This callback may sleep. + * @d3_resume: resume the device after WoWLAN, enabling the opmode to + * talk to the WoWLAN image to get its status. This is optional, if not + * implemented WoWLAN will not be supported. This callback may sleep. + * @send_cmd:send a host command. Must return -ERFKILL if RFkill is asserted. + * If RFkill is asserted in the middle of a SYNC host command, it must + * return -ERFKILL straight away. + * May sleep only if CMD_SYNC is set + * @tx: send an skb + * Must be atomic + * @reclaim: free packet until ssn. Returns a list of freed packets. + * Must be atomic + * @txq_enable: setup a queue. To setup an AC queue, use the + * iwl_trans_ac_txq_enable wrapper. fw_alive must have been called before + * this one. The op_mode must not configure the HCMD queue. May sleep. + * @txq_disable: de-configure a Tx queue to send AMPDUs + * Must be atomic + * @wait_tx_queue_empty: wait until all tx queues are empty + * May sleep + * @dbgfs_register: add the dbgfs files under this directory. Files will be + * automatically deleted. + * @suspend: stop the device unless WoWLAN is configured + * @resume: resume activity of the device + * @write8: write a u8 to a register at offset ofs from the BAR + * @write32: write a u32 to a register at offset ofs from the BAR + * @read32: read a u32 register at offset ofs from the BAR + * @read_prph: read a DWORD from a periphery register + * @write_prph: write a DWORD to a periphery register + * @read_mem: read device's SRAM in DWORD + * @write_mem: write device's SRAM in DWORD. If %buf is %NULL, then the memory + * will be zeroed. + * @configure: configure parameters required by the transport layer from + * the op_mode. May be called several times before start_fw, can't be + * called after that. + * @set_pmi: set the power pmi state + * @grab_nic_access: wake the NIC to be able to access non-HBUS regs. + * Sleeping is not allowed between grab_nic_access and + * release_nic_access. + * @release_nic_access: let the NIC go to sleep. The "flags" parameter + * must be the same one that was sent before to the grab_nic_access. + * @set_bits_mask - set SRAM register according to value and mask. + */ +struct iwl_trans_ops { + + int (*start_hw)(struct iwl_trans *iwl_trans); + void (*stop_hw)(struct iwl_trans *iwl_trans, bool op_mode_leaving); + int (*start_fw)(struct iwl_trans *trans, const struct fw_img *fw, + bool run_in_rfkill); + void (*fw_alive)(struct iwl_trans *trans, u32 scd_addr); + void (*stop_device)(struct iwl_trans *trans); + + void (*d3_suspend)(struct iwl_trans *trans); + int (*d3_resume)(struct iwl_trans *trans, enum iwl_d3_status *status); + + int (*send_cmd)(struct iwl_trans *trans, struct iwl_host_cmd *cmd); + + int (*tx)(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_device_cmd *dev_cmd, int queue); + void (*reclaim)(struct iwl_trans *trans, int queue, int ssn, + struct sk_buff_head *skbs); + + void (*txq_enable)(struct iwl_trans *trans, int queue, int fifo, + int sta_id, int tid, int frame_limit, u16 ssn); + void (*txq_disable)(struct iwl_trans *trans, int queue); + + int (*dbgfs_register)(struct iwl_trans *trans, struct dentry* dir); + int (*wait_tx_queue_empty)(struct iwl_trans *trans); +#ifdef CONFIG_PM_SLEEP + int (*suspend)(struct iwl_trans *trans); + int (*resume)(struct iwl_trans *trans); +#endif + void (*write8)(struct iwl_trans *trans, u32 ofs, u8 val); + void (*write32)(struct iwl_trans *trans, u32 ofs, u32 val); + u32 (*read32)(struct iwl_trans *trans, u32 ofs); + u32 (*read_prph)(struct iwl_trans *trans, u32 ofs); + void (*write_prph)(struct iwl_trans *trans, u32 ofs, u32 val); + int (*read_mem)(struct iwl_trans *trans, u32 addr, + void *buf, int dwords); + int (*write_mem)(struct iwl_trans *trans, u32 addr, + void *buf, int dwords); + void (*configure)(struct iwl_trans *trans, + const struct iwl_trans_config *trans_cfg); + void (*set_pmi)(struct iwl_trans *trans, bool state); + bool (*grab_nic_access)(struct iwl_trans *trans, bool silent, + unsigned long *flags); + void (*release_nic_access)(struct iwl_trans *trans, + unsigned long *flags); + void (*set_bits_mask)(struct iwl_trans *trans, u32 reg, u32 mask, + u32 value); +}; + +/** + * enum iwl_trans_state - state of the transport layer + * + * @IWL_TRANS_NO_FW: no fw has sent an alive response + * @IWL_TRANS_FW_ALIVE: a fw has sent an alive response + */ +enum iwl_trans_state { + IWL_TRANS_NO_FW = 0, + IWL_TRANS_FW_ALIVE = 1, +}; + +/** + * struct iwl_trans - transport common data + * + * @ops - pointer to iwl_trans_ops + * @op_mode - pointer to the op_mode + * @cfg - pointer to the configuration + * @dev - pointer to struct device * that represents the device + * @hw_id: a u32 with the ID of the device / subdevice. + * Set during transport allocation. + * @hw_id_str: a string with info about HW ID. Set during transport allocation. + * @pm_support: set to true in start_hw if link pm is supported + * @dev_cmd_pool: pool for Tx cmd allocation - for internal use only. + * The user should use iwl_trans_{alloc,free}_tx_cmd. + * @dev_cmd_headroom: room needed for the transport's private use before the + * device_cmd for Tx - for internal use only + * The user should use iwl_trans_{alloc,free}_tx_cmd. + * @rx_mpdu_cmd: MPDU RX command ID, must be assigned by opmode before + * starting the firmware, used for tracing + * @rx_mpdu_cmd_hdr_size: used for tracing, amount of data before the + * start of the 802.11 header in the @rx_mpdu_cmd + */ +struct iwl_trans { + const struct iwl_trans_ops *ops; + struct iwl_op_mode *op_mode; + const struct iwl_cfg *cfg; + enum iwl_trans_state state; + + struct device *dev; + u32 hw_rev; + u32 hw_id; + char hw_id_str[52]; + + u8 rx_mpdu_cmd, rx_mpdu_cmd_hdr_size; + + bool pm_support; + + /* The following fields are internal only */ + struct kmem_cache *dev_cmd_pool; + size_t dev_cmd_headroom; + char dev_cmd_pool_name[50]; + + struct dentry *dbgfs_dir; + +#ifdef CONFIG_LOCKDEP + struct lockdep_map sync_cmd_lockdep_map; +#endif + + /* pointer to trans specific struct */ + /*Ensure that this pointer will always be aligned to sizeof pointer */ + char trans_specific[0] __aligned(sizeof(void *)); +}; + +static inline void iwl_trans_configure(struct iwl_trans *trans, + const struct iwl_trans_config *trans_cfg) +{ + trans->op_mode = trans_cfg->op_mode; + + trans->ops->configure(trans, trans_cfg); +} + +static inline int iwl_trans_start_hw(struct iwl_trans *trans) +{ + might_sleep(); + + return trans->ops->start_hw(trans); +} + +static inline void iwl_trans_stop_hw(struct iwl_trans *trans, + bool op_mode_leaving) +{ + might_sleep(); + + trans->ops->stop_hw(trans, op_mode_leaving); + + if (op_mode_leaving) + trans->op_mode = NULL; + + trans->state = IWL_TRANS_NO_FW; +} + +static inline void iwl_trans_fw_alive(struct iwl_trans *trans, u32 scd_addr) +{ + might_sleep(); + + trans->state = IWL_TRANS_FW_ALIVE; + + trans->ops->fw_alive(trans, scd_addr); +} + +static inline int iwl_trans_start_fw(struct iwl_trans *trans, + const struct fw_img *fw, + bool run_in_rfkill) +{ + might_sleep(); + + WARN_ON_ONCE(!trans->rx_mpdu_cmd); + + return trans->ops->start_fw(trans, fw, run_in_rfkill); +} + +static inline void iwl_trans_stop_device(struct iwl_trans *trans) +{ + might_sleep(); + + trans->ops->stop_device(trans); + + trans->state = IWL_TRANS_NO_FW; +} + +static inline void iwl_trans_d3_suspend(struct iwl_trans *trans) +{ + might_sleep(); + trans->ops->d3_suspend(trans); +} + +static inline int iwl_trans_d3_resume(struct iwl_trans *trans, + enum iwl_d3_status *status) +{ + might_sleep(); + return trans->ops->d3_resume(trans, status); +} + +static inline int iwl_trans_send_cmd(struct iwl_trans *trans, + struct iwl_host_cmd *cmd) +{ + int ret; + + WARN_ONCE(trans->state != IWL_TRANS_FW_ALIVE, + "%s bad state = %d", __func__, trans->state); + + if (!(cmd->flags & CMD_ASYNC)) + lock_map_acquire_read(&trans->sync_cmd_lockdep_map); + + ret = trans->ops->send_cmd(trans, cmd); + + if (!(cmd->flags & CMD_ASYNC)) + lock_map_release(&trans->sync_cmd_lockdep_map); + + return ret; +} + +static inline struct iwl_device_cmd * +iwl_trans_alloc_tx_cmd(struct iwl_trans *trans) +{ + u8 *dev_cmd_ptr = kmem_cache_alloc(trans->dev_cmd_pool, GFP_ATOMIC); + + if (unlikely(dev_cmd_ptr == NULL)) + return NULL; + + return (struct iwl_device_cmd *) + (dev_cmd_ptr + trans->dev_cmd_headroom); +} + +static inline void iwl_trans_free_tx_cmd(struct iwl_trans *trans, + struct iwl_device_cmd *dev_cmd) +{ + u8 *dev_cmd_ptr = (u8 *)dev_cmd - trans->dev_cmd_headroom; + + kmem_cache_free(trans->dev_cmd_pool, dev_cmd_ptr); +} + +static inline int iwl_trans_tx(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_device_cmd *dev_cmd, int queue) +{ + WARN_ONCE(trans->state != IWL_TRANS_FW_ALIVE, + "%s bad state = %d", __func__, trans->state); + + return trans->ops->tx(trans, skb, dev_cmd, queue); +} + +static inline void iwl_trans_reclaim(struct iwl_trans *trans, int queue, + int ssn, struct sk_buff_head *skbs) +{ + WARN_ONCE(trans->state != IWL_TRANS_FW_ALIVE, + "%s bad state = %d", __func__, trans->state); + + trans->ops->reclaim(trans, queue, ssn, skbs); +} + +static inline void iwl_trans_txq_disable(struct iwl_trans *trans, int queue) +{ + WARN_ONCE(trans->state != IWL_TRANS_FW_ALIVE, + "%s bad state = %d", __func__, trans->state); + + trans->ops->txq_disable(trans, queue); +} + +static inline void iwl_trans_txq_enable(struct iwl_trans *trans, int queue, + int fifo, int sta_id, int tid, + int frame_limit, u16 ssn) +{ + might_sleep(); + + WARN_ONCE(trans->state != IWL_TRANS_FW_ALIVE, + "%s bad state = %d", __func__, trans->state); + + trans->ops->txq_enable(trans, queue, fifo, sta_id, tid, + frame_limit, ssn); +} + +static inline void iwl_trans_ac_txq_enable(struct iwl_trans *trans, int queue, + int fifo) +{ + iwl_trans_txq_enable(trans, queue, fifo, -1, + IWL_MAX_TID_COUNT, IWL_FRAME_LIMIT, 0); +} + +static inline int iwl_trans_wait_tx_queue_empty(struct iwl_trans *trans) +{ + WARN_ONCE(trans->state != IWL_TRANS_FW_ALIVE, + "%s bad state = %d", __func__, trans->state); + + return trans->ops->wait_tx_queue_empty(trans); +} + +static inline int iwl_trans_dbgfs_register(struct iwl_trans *trans, + struct dentry *dir) +{ + return trans->ops->dbgfs_register(trans, dir); +} + +#ifdef CONFIG_PM_SLEEP +static inline int iwl_trans_suspend(struct iwl_trans *trans) +{ + return trans->ops->suspend(trans); +} + +static inline int iwl_trans_resume(struct iwl_trans *trans) +{ + return trans->ops->resume(trans); +} +#endif + +static inline void iwl_trans_write8(struct iwl_trans *trans, u32 ofs, u8 val) +{ + trans->ops->write8(trans, ofs, val); +} + +static inline void iwl_trans_write32(struct iwl_trans *trans, u32 ofs, u32 val) +{ + trans->ops->write32(trans, ofs, val); +} + +static inline u32 iwl_trans_read32(struct iwl_trans *trans, u32 ofs) +{ + return trans->ops->read32(trans, ofs); +} + +static inline u32 iwl_trans_read_prph(struct iwl_trans *trans, u32 ofs) +{ + return trans->ops->read_prph(trans, ofs); +} + +static inline void iwl_trans_write_prph(struct iwl_trans *trans, u32 ofs, + u32 val) +{ + return trans->ops->write_prph(trans, ofs, val); +} + +static inline int iwl_trans_read_mem(struct iwl_trans *trans, u32 addr, + void *buf, int dwords) +{ + return trans->ops->read_mem(trans, addr, buf, dwords); +} + +#define iwl_trans_read_mem_bytes(trans, addr, buf, bufsize) \ + do { \ + if (__builtin_constant_p(bufsize)) \ + BUILD_BUG_ON((bufsize) % sizeof(u32)); \ + iwl_trans_read_mem(trans, addr, buf, (bufsize) / sizeof(u32));\ + } while (0) + +static inline u32 iwl_trans_read_mem32(struct iwl_trans *trans, u32 addr) +{ + u32 value; + + if (WARN_ON(iwl_trans_read_mem(trans, addr, &value, 1))) + return 0xa5a5a5a5; + + return value; +} + +static inline int iwl_trans_write_mem(struct iwl_trans *trans, u32 addr, + void *buf, int dwords) +{ + return trans->ops->write_mem(trans, addr, buf, dwords); +} + +static inline u32 iwl_trans_write_mem32(struct iwl_trans *trans, u32 addr, + u32 val) +{ + return iwl_trans_write_mem(trans, addr, &val, 1); +} + +static inline void iwl_trans_set_pmi(struct iwl_trans *trans, bool state) +{ + trans->ops->set_pmi(trans, state); +} + +static inline void +iwl_trans_set_bits_mask(struct iwl_trans *trans, u32 reg, u32 mask, u32 value) +{ + trans->ops->set_bits_mask(trans, reg, mask, value); +} + +#define iwl_trans_grab_nic_access(trans, silent, flags) \ + __cond_lock(nic_access, \ + likely((trans)->ops->grab_nic_access(trans, silent, flags))) + +static inline void __releases(nic_access) +iwl_trans_release_nic_access(struct iwl_trans *trans, unsigned long *flags) +{ + trans->ops->release_nic_access(trans, flags); + __release(nic_access); +} + +/***************************************************** +* driver (transport) register/unregister functions +******************************************************/ +int __must_check iwl_pci_register_driver(void); +void iwl_pci_unregister_driver(void); + +static inline void trans_lockdep_init(struct iwl_trans *trans) +{ +#ifdef CONFIG_LOCKDEP + static struct lock_class_key __key; + + lockdep_init_map(&trans->sync_cmd_lockdep_map, "sync_cmd_lockdep_map", + &__key, 0); +#endif +} + +#endif /* __iwl_trans_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/Makefile b/drivers/net/wireless/iwlwifi/mvm/Makefile new file mode 100644 index 0000000..b54d1d8 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/Makefile @@ -0,0 +1,10 @@ +obj-$(CPTCFG_IWLMVM) += iwlmvm.o +iwlmvm-y += fw.o mac80211.o nvm.o ops.o phy-ctxt.o mac-ctxt.o +iwlmvm-y += utils.o rx.o tx.o binding.o quota.o sta.o +iwlmvm-y += scan.o time-event.o rs.o +iwlmvm-y += power.o bt-coex.o +iwlmvm-y += led.o +iwlmvm-$(CPTCFG_IWLWIFI_DEBUGFS) += debugfs.o +iwlmvm-$(CONFIG_PM_SLEEP) += d3.o + +ccflags-y += -D__CHECK_ENDIAN__ -I$(src)/../ diff --git a/drivers/net/wireless/iwlwifi/mvm/binding.c b/drivers/net/wireless/iwlwifi/mvm/binding.c new file mode 100644 index 0000000..93fd145 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/binding.c @@ -0,0 +1,197 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#include +#include "fw-api.h" +#include "mvm.h" + +struct iwl_mvm_iface_iterator_data { + struct ieee80211_vif *ignore_vif; + int idx; + + struct iwl_mvm_phy_ctxt *phyctxt; + + u16 ids[MAX_MACS_IN_BINDING]; + u16 colors[MAX_MACS_IN_BINDING]; +}; + +static int iwl_mvm_binding_cmd(struct iwl_mvm *mvm, u32 action, + struct iwl_mvm_iface_iterator_data *data) +{ + struct iwl_binding_cmd cmd; + struct iwl_mvm_phy_ctxt *phyctxt = data->phyctxt; + int i, ret; + u32 status; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(phyctxt->id, + phyctxt->color)); + cmd.action = cpu_to_le32(action); + cmd.phy = cpu_to_le32(FW_CMD_ID_AND_COLOR(phyctxt->id, + phyctxt->color)); + + for (i = 0; i < MAX_MACS_IN_BINDING; i++) + cmd.macs[i] = cpu_to_le32(FW_CTXT_INVALID); + for (i = 0; i < data->idx; i++) + cmd.macs[i] = cpu_to_le32(FW_CMD_ID_AND_COLOR(data->ids[i], + data->colors[i])); + + status = 0; + ret = iwl_mvm_send_cmd_pdu_status(mvm, BINDING_CONTEXT_CMD, + sizeof(cmd), &cmd, &status); + if (ret) { + IWL_ERR(mvm, "Failed to send binding (action:%d): %d\n", + action, ret); + return ret; + } + + if (status) { + IWL_ERR(mvm, "Binding command failed: %u\n", status); + ret = -EIO; + } + + return ret; +} + +static void iwl_mvm_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_iface_iterator_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif == data->ignore_vif) + return; + + if (mvmvif->phy_ctxt != data->phyctxt) + return; + + if (WARN_ON_ONCE(data->idx >= MAX_MACS_IN_BINDING)) + return; + + data->ids[data->idx] = mvmvif->id; + data->colors[data->idx] = mvmvif->color; + data->idx++; +} + +static int iwl_mvm_binding_update(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mvm_phy_ctxt *phyctxt, + bool add) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_iface_iterator_data data = { + .ignore_vif = vif, + .phyctxt = phyctxt, + }; + u32 action = FW_CTXT_ACTION_MODIFY; + + lockdep_assert_held(&mvm->mutex); + + ieee80211_iterate_active_interfaces_atomic(mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_iface_iterator, + &data); + + /* + * If there are no other interfaces yet we + * need to create a new binding. + */ + if (data.idx == 0) { + if (add) + action = FW_CTXT_ACTION_ADD; + else + action = FW_CTXT_ACTION_REMOVE; + } + + if (add) { + if (WARN_ON_ONCE(data.idx >= MAX_MACS_IN_BINDING)) + return -EINVAL; + + data.ids[data.idx] = mvmvif->id; + data.colors[data.idx] = mvmvif->color; + data.idx++; + } + + return iwl_mvm_binding_cmd(mvm, action, &data); +} + +int iwl_mvm_binding_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (WARN_ON_ONCE(!mvmvif->phy_ctxt)) + return -EINVAL; + + return iwl_mvm_binding_update(mvm, vif, mvmvif->phy_ctxt, true); +} + +int iwl_mvm_binding_remove_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (WARN_ON_ONCE(!mvmvif->phy_ctxt)) + return -EINVAL; + + return iwl_mvm_binding_update(mvm, vif, mvmvif->phy_ctxt, false); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/bt-coex.c b/drivers/net/wireless/iwlwifi/mvm/bt-coex.c new file mode 100644 index 0000000..810bfa5 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/bt-coex.c @@ -0,0 +1,589 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#include + +#include "fw-api-bt-coex.h" +#include "iwl-modparams.h" +#include "mvm.h" +#include "iwl-debug.h" + +#define EVENT_PRIO_ANT(_evt, _prio, _shrd_ant) \ + [(_evt)] = (((_prio) << BT_COEX_PRIO_TBL_PRIO_POS) | \ + ((_shrd_ant) << BT_COEX_PRIO_TBL_SHRD_ANT_POS)) + +static const u8 iwl_bt_prio_tbl[BT_COEX_PRIO_TBL_EVT_MAX] = { + EVENT_PRIO_ANT(BT_COEX_PRIO_TBL_EVT_INIT_CALIB1, + BT_COEX_PRIO_TBL_PRIO_BYPASS, 0), + EVENT_PRIO_ANT(BT_COEX_PRIO_TBL_EVT_INIT_CALIB2, + BT_COEX_PRIO_TBL_PRIO_BYPASS, 1), + EVENT_PRIO_ANT(BT_COEX_PRIO_TBL_EVT_PERIODIC_CALIB_LOW1, + BT_COEX_PRIO_TBL_PRIO_LOW, 0), + EVENT_PRIO_ANT(BT_COEX_PRIO_TBL_EVT_PERIODIC_CALIB_LOW2, + BT_COEX_PRIO_TBL_PRIO_LOW, 1), + EVENT_PRIO_ANT(BT_COEX_PRIO_TBL_EVT_PERIODIC_CALIB_HIGH1, + BT_COEX_PRIO_TBL_PRIO_HIGH, 0), + EVENT_PRIO_ANT(BT_COEX_PRIO_TBL_EVT_PERIODIC_CALIB_HIGH2, + BT_COEX_PRIO_TBL_PRIO_HIGH, 1), + EVENT_PRIO_ANT(BT_COEX_PRIO_TBL_EVT_DTIM, + BT_COEX_PRIO_TBL_DISABLED, 0), + EVENT_PRIO_ANT(BT_COEX_PRIO_TBL_EVT_SCAN52, + BT_COEX_PRIO_TBL_PRIO_COEX_OFF, 0), + EVENT_PRIO_ANT(BT_COEX_PRIO_TBL_EVT_SCAN24, + BT_COEX_PRIO_TBL_PRIO_COEX_ON, 0), + EVENT_PRIO_ANT(BT_COEX_PRIO_TBL_EVT_IDLE, + BT_COEX_PRIO_TBL_PRIO_COEX_IDLE, 0), + 0, 0, 0, 0, 0, 0, +}; + +#undef EVENT_PRIO_ANT + +/* BT Antenna Coupling Threshold (dB) */ +#define IWL_BT_ANTENNA_COUPLING_THRESHOLD (35) +#define IWL_BT_LOAD_FORCE_SISO_THRESHOLD (3) + +#define BT_ENABLE_REDUCED_TXPOWER_THRESHOLD (-62) +#define BT_DISABLE_REDUCED_TXPOWER_THRESHOLD (-65) +#define BT_REDUCED_TX_POWER_BIT BIT(7) + +static inline bool is_loose_coex(void) +{ + return iwlwifi_mod_params.ant_coupling > + IWL_BT_ANTENNA_COUPLING_THRESHOLD; +} + +int iwl_send_bt_prio_tbl(struct iwl_mvm *mvm) +{ + return iwl_mvm_send_cmd_pdu(mvm, BT_COEX_PRIO_TABLE, CMD_SYNC, + sizeof(struct iwl_bt_coex_prio_tbl_cmd), + &iwl_bt_prio_tbl); +} + +static int iwl_send_bt_env(struct iwl_mvm *mvm, u8 action, u8 type) +{ + struct iwl_bt_coex_prot_env_cmd env_cmd; + int ret; + + env_cmd.action = action; + env_cmd.type = type; + ret = iwl_mvm_send_cmd_pdu(mvm, BT_COEX_PROT_ENV, CMD_SYNC, + sizeof(env_cmd), &env_cmd); + if (ret) + IWL_ERR(mvm, "failed to send BT env command\n"); + return ret; +} + +enum iwl_bt_kill_msk { + BT_KILL_MSK_DEFAULT, + BT_KILL_MSK_SCO_HID_A2DP, + BT_KILL_MSK_REDUCED_TXPOW, + BT_KILL_MSK_MAX, +}; + +static const u32 iwl_bt_ack_kill_msk[BT_KILL_MSK_MAX] = { + [BT_KILL_MSK_DEFAULT] = 0xffff0000, + [BT_KILL_MSK_SCO_HID_A2DP] = 0xffffffff, + [BT_KILL_MSK_REDUCED_TXPOW] = 0, +}; + +static const u32 iwl_bt_cts_kill_msk[BT_KILL_MSK_MAX] = { + [BT_KILL_MSK_DEFAULT] = 0xffff0000, + [BT_KILL_MSK_SCO_HID_A2DP] = 0xffffffff, + [BT_KILL_MSK_REDUCED_TXPOW] = 0, +}; + +#define IWL_BT_DEFAULT_BOOST (0xf0f0f0f0) + +/* Tight Coex */ +static const __le32 iwl_tight_lookup[BT_COEX_LUT_SIZE] = { + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaeaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xcc00ff28), + cpu_to_le32(0x0000aaaa), + cpu_to_le32(0xcc00aaaa), + cpu_to_le32(0x0000aaaa), + cpu_to_le32(0xc0004000), + cpu_to_le32(0x00000000), + cpu_to_le32(0xf0005000), + cpu_to_le32(0xf0005000), +}; + +/* Loose Coex */ +static const __le32 iwl_loose_lookup[BT_COEX_LUT_SIZE] = { + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaeaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xcc00ff28), + cpu_to_le32(0x0000aaaa), + cpu_to_le32(0xcc00aaaa), + cpu_to_le32(0x0000aaaa), + cpu_to_le32(0x00000000), + cpu_to_le32(0x00000000), + cpu_to_le32(0xf0005000), + cpu_to_le32(0xf0005000), +}; + +/* Full concurrency */ +static const __le32 iwl_concurrent_lookup[BT_COEX_LUT_SIZE] = { + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0xaaaaaaaa), + cpu_to_le32(0x00000000), + cpu_to_le32(0x00000000), + cpu_to_le32(0x00000000), + cpu_to_le32(0x00000000), +}; + +int iwl_send_bt_init_conf(struct iwl_mvm *mvm) +{ + struct iwl_bt_coex_cmd cmd = { + .max_kill = 5, + .bt3_time_t7_value = 1, + .bt3_prio_sample_time = 2, + .bt3_timer_t2_value = 0xc, + }; + int ret; + + cmd.flags = iwlwifi_mod_params.bt_coex_active ? + BT_COEX_NW : BT_COEX_DISABLE; + cmd.flags |= BT_CH_PRIMARY_EN | BT_SYNC_2_BT_DISABLE; + + cmd.valid_bit_msk = cpu_to_le16(BT_VALID_ENABLE | + BT_VALID_BT_PRIO_BOOST | + BT_VALID_MAX_KILL | + BT_VALID_3W_TMRS | + BT_VALID_KILL_ACK | + BT_VALID_KILL_CTS | + BT_VALID_REDUCED_TX_POWER | + BT_VALID_LUT); + + if (is_loose_coex()) + memcpy(&cmd.decision_lut, iwl_loose_lookup, + sizeof(iwl_tight_lookup)); + else + memcpy(&cmd.decision_lut, iwl_tight_lookup, + sizeof(iwl_tight_lookup)); + + cmd.bt_prio_boost = cpu_to_le32(IWL_BT_DEFAULT_BOOST); + cmd.kill_ack_msk = + cpu_to_le32(iwl_bt_ack_kill_msk[BT_KILL_MSK_DEFAULT]); + cmd.kill_cts_msk = + cpu_to_le32(iwl_bt_cts_kill_msk[BT_KILL_MSK_DEFAULT]); + + memset(&mvm->last_bt_notif, 0, sizeof(mvm->last_bt_notif)); + + /* go to CALIB state in internal BT-Coex state machine */ + ret = iwl_send_bt_env(mvm, BT_COEX_ENV_OPEN, + BT_COEX_PRIO_TBL_EVT_INIT_CALIB2); + if (ret) + return ret; + + ret = iwl_send_bt_env(mvm, BT_COEX_ENV_CLOSE, + BT_COEX_PRIO_TBL_EVT_INIT_CALIB2); + if (ret) + return ret; + + return iwl_mvm_send_cmd_pdu(mvm, BT_CONFIG, CMD_SYNC, + sizeof(cmd), &cmd); +} + +static int iwl_mvm_bt_udpate_ctrl_kill_msk(struct iwl_mvm *mvm, + bool reduced_tx_power) +{ + enum iwl_bt_kill_msk bt_kill_msk; + struct iwl_bt_coex_cmd cmd = {}; + struct iwl_bt_coex_profile_notif *notif = &mvm->last_bt_notif; + + lockdep_assert_held(&mvm->mutex); + + if (reduced_tx_power) { + /* Reduced Tx power has precedence on the type of the profile */ + bt_kill_msk = BT_KILL_MSK_REDUCED_TXPOW; + } else { + /* Low latency BT profile is active: give higher prio to BT */ + if (BT_MBOX_MSG(notif, 3, SCO_STATE) || + BT_MBOX_MSG(notif, 3, A2DP_STATE) || + BT_MBOX_MSG(notif, 3, SNIFF_STATE)) + bt_kill_msk = BT_KILL_MSK_SCO_HID_A2DP; + else + bt_kill_msk = BT_KILL_MSK_DEFAULT; + } + + IWL_DEBUG_COEX(mvm, + "Update kill_msk: %d - SCO %sactive A2DP %sactive SNIFF %sactive\n", + bt_kill_msk, + BT_MBOX_MSG(notif, 3, SCO_STATE) ? "" : "in", + BT_MBOX_MSG(notif, 3, A2DP_STATE) ? "" : "in", + BT_MBOX_MSG(notif, 3, SNIFF_STATE) ? "" : "in"); + + /* Don't send HCMD if there is no update */ + if (bt_kill_msk == mvm->bt_kill_msk) + return 0; + + mvm->bt_kill_msk = bt_kill_msk; + cmd.kill_ack_msk = cpu_to_le32(iwl_bt_ack_kill_msk[bt_kill_msk]); + cmd.kill_cts_msk = cpu_to_le32(iwl_bt_cts_kill_msk[bt_kill_msk]); + cmd.valid_bit_msk = cpu_to_le16(BT_VALID_KILL_ACK | BT_VALID_KILL_CTS); + + IWL_DEBUG_COEX(mvm, "bt_kill_msk = %d\n", bt_kill_msk); + return iwl_mvm_send_cmd_pdu(mvm, BT_CONFIG, CMD_SYNC, + sizeof(cmd), &cmd); +} + +static int iwl_mvm_bt_coex_reduced_txp(struct iwl_mvm *mvm, u8 sta_id, + bool enable) +{ + struct iwl_bt_coex_cmd cmd = { + .valid_bit_msk = cpu_to_le16(BT_VALID_REDUCED_TX_POWER), + .bt_reduced_tx_power = sta_id, + }; + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + + /* This can happen if the station has been removed right now */ + if (sta_id == IWL_MVM_STATION_COUNT) + return 0; + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + mvmsta = (void *)sta->drv_priv; + + /* nothing to do */ + if (mvmsta->bt_reduced_txpower == enable) + return 0; + + if (enable) + cmd.bt_reduced_tx_power |= BT_REDUCED_TX_POWER_BIT; + + IWL_DEBUG_COEX(mvm, "%sable reduced Tx Power for sta %d\n", + enable ? "en" : "dis", sta_id); + + mvmsta->bt_reduced_txpower = enable; + + /* Send ASYNC since this can be sent from an atomic context */ + return iwl_mvm_send_cmd_pdu(mvm, BT_CONFIG, CMD_ASYNC, + sizeof(cmd), &cmd); +} + +struct iwl_bt_iterator_data { + struct iwl_bt_coex_profile_notif *notif; + struct iwl_mvm *mvm; + u32 num_bss_ifaces; + bool reduced_tx_power; +}; + +static void iwl_mvm_bt_notif_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_bt_iterator_data *data = _data; + struct iwl_mvm *mvm = data->mvm; + struct ieee80211_chanctx_conf *chanctx_conf; + enum ieee80211_smps_mode smps_mode; + enum ieee80211_band band; + int ave_rssi; + + if (vif->type != NL80211_IFTYPE_STATION) + return; + + rcu_read_lock(); + chanctx_conf = rcu_dereference(vif->chanctx_conf); + if (chanctx_conf && chanctx_conf->def.chan) + band = chanctx_conf->def.chan->band; + else + band = -1; + rcu_read_unlock(); + + smps_mode = IEEE80211_SMPS_AUTOMATIC; + + if (band != IEEE80211_BAND_2GHZ) { + ieee80211_request_smps(vif, smps_mode); + return; + } + + if (data->notif->bt_status) + smps_mode = IEEE80211_SMPS_DYNAMIC; + + if (data->notif->bt_traffic_load >= IWL_BT_LOAD_FORCE_SISO_THRESHOLD) + smps_mode = IEEE80211_SMPS_STATIC; + + IWL_DEBUG_COEX(data->mvm, + "mac %d: bt_status %d traffic_load %d smps_req %d\n", + mvmvif->id, data->notif->bt_status, + data->notif->bt_traffic_load, smps_mode); + + ieee80211_request_smps(vif, smps_mode); + + /* don't reduce the Tx power if in loose scheme */ + if (is_loose_coex()) + return; + + data->num_bss_ifaces++; + + /* reduced Txpower only if there are open BT connections, so ...*/ + if (!BT_MBOX_MSG(data->notif, 3, OPEN_CON_2)) { + /* ... cancel reduced Tx power ... */ + if (iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, false)) + IWL_ERR(mvm, "Couldn't send BT_CONFIG cmd\n"); + data->reduced_tx_power = false; + + /* ... and there is no need to get reports on RSSI any more. */ + ieee80211_disable_rssi_reports(vif); + return; + } + + ave_rssi = ieee80211_ave_rssi(vif); + + /* if the RSSI isn't valid, fake it is very low */ + if (!ave_rssi) + ave_rssi = -100; + if (ave_rssi > BT_ENABLE_REDUCED_TXPOWER_THRESHOLD) { + if (iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, true)) + IWL_ERR(mvm, "Couldn't send BT_CONFIG cmd\n"); + + /* + * bt_kill_msk can be BT_KILL_MSK_REDUCED_TXPOW only if all the + * BSS / P2P clients have rssi above threshold. + * We set the bt_kill_msk to BT_KILL_MSK_REDUCED_TXPOW before + * the iteration, if one interface's rssi isn't good enough, + * bt_kill_msk will be set to default values. + */ + } else if (ave_rssi < BT_DISABLE_REDUCED_TXPOWER_THRESHOLD) { + if (iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, false)) + IWL_ERR(mvm, "Couldn't send BT_CONFIG cmd\n"); + + /* + * One interface hasn't rssi above threshold, bt_kill_msk must + * be set to default values. + */ + data->reduced_tx_power = false; + } + + /* Begin to monitor the RSSI: it may influence the reduced Tx power */ + ieee80211_enable_rssi_reports(vif, BT_DISABLE_REDUCED_TXPOWER_THRESHOLD, + BT_ENABLE_REDUCED_TXPOWER_THRESHOLD); +} + +static void iwl_mvm_bt_coex_notif_handle(struct iwl_mvm *mvm) +{ + struct iwl_bt_iterator_data data = { + .mvm = mvm, + .notif = &mvm->last_bt_notif, + .reduced_tx_power = true, + }; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_bt_notif_iterator, &data); + + /* + * If there are no BSS / P2P client interfaces, reduced Tx Power is + * irrelevant since it is based on the RSSI coming from the beacon. + * Use BT_KILL_MSK_DEFAULT in that case. + */ + data.reduced_tx_power = data.reduced_tx_power && data.num_bss_ifaces; + + if (iwl_mvm_bt_udpate_ctrl_kill_msk(mvm, data.reduced_tx_power)) + IWL_ERR(mvm, "Failed to update the ctrl_kill_msk\n"); +} + +/* upon association, the fw will send in BT Coex notification */ +int iwl_mvm_rx_bt_coex_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *dev_cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_bt_coex_profile_notif *notif = (void *)pkt->data; + + + IWL_DEBUG_COEX(mvm, "BT Coex Notification received\n"); + IWL_DEBUG_COEX(mvm, "\tBT %salive\n", notif->bt_status ? "" : "not "); + IWL_DEBUG_COEX(mvm, "\tBT open conn %d\n", notif->bt_open_conn); + IWL_DEBUG_COEX(mvm, "\tBT traffic load %d\n", notif->bt_traffic_load); + IWL_DEBUG_COEX(mvm, "\tBT agg traffic load %d\n", + notif->bt_agg_traffic_load); + IWL_DEBUG_COEX(mvm, "\tBT ci compliance %d\n", notif->bt_ci_compliance); + + /* remember this notification for future use: rssi fluctuations */ + memcpy(&mvm->last_bt_notif, notif, sizeof(mvm->last_bt_notif)); + + iwl_mvm_bt_coex_notif_handle(mvm); + + /* + * This is an async handler for a notification, returning anything other + * than 0 doesn't make sense even if HCMD failed. + */ + return 0; +} + +static void iwl_mvm_bt_rssi_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = (void *)vif->drv_priv; + struct iwl_bt_iterator_data *data = _data; + struct iwl_mvm *mvm = data->mvm; + + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + + if (vif->type != NL80211_IFTYPE_STATION || + mvmvif->ap_sta_id == IWL_MVM_STATION_COUNT) + return; + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[mvmvif->ap_sta_id], + lockdep_is_held(&mvm->mutex)); + mvmsta = (void *)sta->drv_priv; + + /* + * This interface doesn't support reduced Tx power (because of low + * RSSI probably), then set bt_kill_msk to default values. + */ + if (!mvmsta->bt_reduced_txpower) + data->reduced_tx_power = false; + /* else - possibly leave it to BT_KILL_MSK_REDUCED_TXPOW */ +} + +void iwl_mvm_bt_rssi_event(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + enum ieee80211_rssi_event rssi_event) +{ + struct iwl_mvm_vif *mvmvif = (void *)vif->drv_priv; + struct iwl_bt_iterator_data data = { + .mvm = mvm, + .reduced_tx_power = true, + }; + int ret; + + mutex_lock(&mvm->mutex); + + /* Rssi update while not associated ?! */ + if (WARN_ON_ONCE(mvmvif->ap_sta_id == IWL_MVM_STATION_COUNT)) + goto out_unlock; + + /* No open connection - reports should be disabled */ + if (!BT_MBOX_MSG(&mvm->last_bt_notif, 3, OPEN_CON_2)) + goto out_unlock; + + IWL_DEBUG_COEX(mvm, "RSSI for %pM is now %s\n", vif->bss_conf.bssid, + rssi_event == RSSI_EVENT_HIGH ? "HIGH" : "LOW"); + + /* + * Check if rssi is good enough for reduced Tx power, but not in loose + * scheme. + */ + if (rssi_event == RSSI_EVENT_LOW || is_loose_coex()) + ret = iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, + false); + else + ret = iwl_mvm_bt_coex_reduced_txp(mvm, mvmvif->ap_sta_id, true); + + if (ret) + IWL_ERR(mvm, "couldn't send BT_CONFIG HCMD upon RSSI event\n"); + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_bt_rssi_iterator, &data); + + /* + * If there are no BSS / P2P client interfaces, reduced Tx Power is + * irrelevant since it is based on the RSSI coming from the beacon. + * Use BT_KILL_MSK_DEFAULT in that case. + */ + data.reduced_tx_power = data.reduced_tx_power && data.num_bss_ifaces; + + if (iwl_mvm_bt_udpate_ctrl_kill_msk(mvm, data.reduced_tx_power)) + IWL_ERR(mvm, "Failed to update the ctrl_kill_msk\n"); + + out_unlock: + mutex_unlock(&mvm->mutex); +} + +void iwl_mvm_bt_coex_vif_assoc(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct ieee80211_chanctx_conf *chanctx_conf; + enum ieee80211_band band; + + rcu_read_lock(); + chanctx_conf = rcu_dereference(vif->chanctx_conf); + if (chanctx_conf && chanctx_conf->def.chan) + band = chanctx_conf->def.chan->band; + else + band = -1; + rcu_read_unlock(); + + /* if we are in 2GHz we will get a notification from the fw */ + if (band == IEEE80211_BAND_2GHZ) + return; + + /* else, we can remove all the constraints */ + memset(&mvm->last_bt_notif, 0, sizeof(mvm->last_bt_notif)); + + iwl_mvm_bt_coex_notif_handle(mvm); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/d3.c b/drivers/net/wireless/iwlwifi/mvm/d3.c new file mode 100644 index 0000000..a1b2cee --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/d3.c @@ -0,0 +1,1270 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include "iwl-modparams.h" +#include "fw-api.h" +#include "mvm.h" + +void iwl_mvm_set_rekey_data(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_gtk_rekey_data *data) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (iwlwifi_mod_params.sw_crypto) + return; + + mutex_lock(&mvm->mutex); + + memcpy(mvmvif->rekey_data.kek, data->kek, NL80211_KEK_LEN); + memcpy(mvmvif->rekey_data.kck, data->kck, NL80211_KCK_LEN); + mvmvif->rekey_data.replay_ctr = + cpu_to_le64(be64_to_cpup((__be64 *)&data->replay_ctr)); + mvmvif->rekey_data.valid = true; + + mutex_unlock(&mvm->mutex); +} + +#if IS_ENABLED(CONFIG_IPV6) +void iwl_mvm_ipv6_addr_change(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct inet6_dev *idev) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct inet6_ifaddr *ifa; + int idx = 0; + + read_lock_bh(&idev->lock); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + list_for_each_entry(ifa, &idev->addr_list, if_list) { +#else + for (ifa = idev->addr_list; ifa; ifa = ifa->if_next) { +#endif + mvmvif->target_ipv6_addrs[idx] = ifa->addr; + idx++; + if (idx >= IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS) + break; + } + read_unlock_bh(&idev->lock); + + mvmvif->num_target_ipv6_addrs = idx; +} +#endif + +void iwl_mvm_set_default_unicast_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, int idx) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + mvmvif->tx_key_idx = idx; +} + +static void iwl_mvm_convert_p1k(u16 *p1k, __le16 *out) +{ + int i; + + for (i = 0; i < IWL_P1K_SIZE; i++) + out[i] = cpu_to_le16(p1k[i]); +} + +struct wowlan_key_data { + struct iwl_wowlan_rsc_tsc_params_cmd *rsc_tsc; + struct iwl_wowlan_tkip_params_cmd *tkip; + bool error, use_rsc_tsc, use_tkip; + int gtk_key_idx; +}; + +static void iwl_mvm_wowlan_program_keys(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, + void *_data) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct wowlan_key_data *data = _data; + struct aes_sc *aes_sc, *aes_tx_sc = NULL; + struct tkip_sc *tkip_sc, *tkip_tx_sc = NULL; + struct iwl_p1k_cache *rx_p1ks; + u8 *rx_mic_key; + struct ieee80211_key_seq seq; + u32 cur_rx_iv32 = 0; + u16 p1k[IWL_P1K_SIZE]; + int ret, i; + + mutex_lock(&mvm->mutex); + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: { /* hack it for now */ + struct { + struct iwl_mvm_wep_key_cmd wep_key_cmd; + struct iwl_mvm_wep_key wep_key; + } __packed wkc = { + .wep_key_cmd.mac_id_n_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)), + .wep_key_cmd.num_keys = 1, + /* firmware sets STA_KEY_FLG_WEP_13BYTES */ + .wep_key_cmd.decryption_type = STA_KEY_FLG_WEP, + .wep_key.key_index = key->keyidx, + .wep_key.key_size = key->keylen, + }; + + /* + * This will fail -- the key functions don't set support + * pairwise WEP keys. However, that's better than silently + * failing WoWLAN. Or maybe not? + */ + if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) + break; + + memcpy(&wkc.wep_key.key[3], key->key, key->keylen); + if (key->keyidx == mvmvif->tx_key_idx) { + /* TX key must be at offset 0 */ + wkc.wep_key.key_offset = 0; + } else { + /* others start at 1 */ + data->gtk_key_idx++; + wkc.wep_key.key_offset = data->gtk_key_idx; + } + + ret = iwl_mvm_send_cmd_pdu(mvm, WEP_KEY, CMD_SYNC, + sizeof(wkc), &wkc); + data->error = ret != 0; + + mvm->ptk_ivlen = key->iv_len; + mvm->ptk_icvlen = key->icv_len; + mvm->gtk_ivlen = key->iv_len; + mvm->gtk_icvlen = key->icv_len; + + /* don't upload key again */ + goto out_unlock; + } + default: + data->error = true; + goto out_unlock; + case WLAN_CIPHER_SUITE_AES_CMAC: + /* + * Ignore CMAC keys -- the WoWLAN firmware doesn't support them + * but we also shouldn't abort suspend due to that. It does have + * support for the IGTK key renewal, but doesn't really use the + * IGTK for anything. This means we could spuriously wake up or + * be deauthenticated, but that was considered acceptable. + */ + goto out_unlock; + case WLAN_CIPHER_SUITE_TKIP: + if (sta) { + tkip_sc = data->rsc_tsc->all_tsc_rsc.tkip.unicast_rsc; + tkip_tx_sc = &data->rsc_tsc->all_tsc_rsc.tkip.tsc; + + rx_p1ks = data->tkip->rx_uni; + + ieee80211_get_key_tx_seq(key, &seq); + tkip_tx_sc->iv16 = cpu_to_le16(seq.tkip.iv16); + tkip_tx_sc->iv32 = cpu_to_le32(seq.tkip.iv32); + + ieee80211_get_tkip_p1k_iv(key, seq.tkip.iv32, p1k); + iwl_mvm_convert_p1k(p1k, data->tkip->tx.p1k); + + memcpy(data->tkip->mic_keys.tx, + &key->key[NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY], + IWL_MIC_KEY_SIZE); + + rx_mic_key = data->tkip->mic_keys.rx_unicast; + } else { + tkip_sc = + data->rsc_tsc->all_tsc_rsc.tkip.multicast_rsc; + rx_p1ks = data->tkip->rx_multi; + rx_mic_key = data->tkip->mic_keys.rx_mcast; + } + + /* + * For non-QoS this relies on the fact that both the uCode and + * mac80211 use TID 0 (as they need to to avoid replay attacks) + * for checking the IV in the frames. + */ + for (i = 0; i < IWL_NUM_RSC; i++) { + ieee80211_get_key_rx_seq(key, i, &seq); + tkip_sc[i].iv16 = cpu_to_le16(seq.tkip.iv16); + tkip_sc[i].iv32 = cpu_to_le32(seq.tkip.iv32); + /* wrapping isn't allowed, AP must rekey */ + if (seq.tkip.iv32 > cur_rx_iv32) + cur_rx_iv32 = seq.tkip.iv32; + } + + ieee80211_get_tkip_rx_p1k(key, vif->bss_conf.bssid, + cur_rx_iv32, p1k); + iwl_mvm_convert_p1k(p1k, rx_p1ks[0].p1k); + ieee80211_get_tkip_rx_p1k(key, vif->bss_conf.bssid, + cur_rx_iv32 + 1, p1k); + iwl_mvm_convert_p1k(p1k, rx_p1ks[1].p1k); + + memcpy(rx_mic_key, + &key->key[NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY], + IWL_MIC_KEY_SIZE); + + data->use_tkip = true; + data->use_rsc_tsc = true; + break; + case WLAN_CIPHER_SUITE_CCMP: + if (sta) { + u8 *pn = seq.ccmp.pn; + + aes_sc = data->rsc_tsc->all_tsc_rsc.aes.unicast_rsc; + aes_tx_sc = &data->rsc_tsc->all_tsc_rsc.aes.tsc; + + ieee80211_get_key_tx_seq(key, &seq); + aes_tx_sc->pn = cpu_to_le64((u64)pn[5] | + ((u64)pn[4] << 8) | + ((u64)pn[3] << 16) | + ((u64)pn[2] << 24) | + ((u64)pn[1] << 32) | + ((u64)pn[0] << 40)); + } else { + aes_sc = data->rsc_tsc->all_tsc_rsc.aes.multicast_rsc; + } + + /* + * For non-QoS this relies on the fact that both the uCode and + * mac80211 use TID 0 for checking the IV in the frames. + */ + for (i = 0; i < IWL_NUM_RSC; i++) { + u8 *pn = seq.ccmp.pn; + + ieee80211_get_key_rx_seq(key, i, &seq); + aes_sc->pn = cpu_to_le64((u64)pn[5] | + ((u64)pn[4] << 8) | + ((u64)pn[3] << 16) | + ((u64)pn[2] << 24) | + ((u64)pn[1] << 32) | + ((u64)pn[0] << 40)); + } + data->use_rsc_tsc = true; + break; + } + + /* + * The D3 firmware hardcodes the key offset 0 as the key it uses + * to transmit packets to the AP, i.e. the PTK. + */ + if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) { + key->hw_key_idx = 0; + mvm->ptk_ivlen = key->iv_len; + mvm->ptk_icvlen = key->icv_len; + } else { + data->gtk_key_idx++; + key->hw_key_idx = data->gtk_key_idx; + mvm->gtk_ivlen = key->iv_len; + mvm->gtk_icvlen = key->icv_len; + } + + ret = iwl_mvm_set_sta_key(mvm, vif, sta, key, true); + data->error = ret != 0; +out_unlock: + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_send_patterns(struct iwl_mvm *mvm, + struct cfg80211_wowlan *wowlan) +{ + struct iwl_wowlan_patterns_cmd *pattern_cmd; + struct iwl_host_cmd cmd = { + .id = WOWLAN_PATTERNS, + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + .flags = CMD_SYNC, + }; + int i, err; + + if (!wowlan->n_patterns) + return 0; + + cmd.len[0] = sizeof(*pattern_cmd) + + wowlan->n_patterns * sizeof(struct iwl_wowlan_pattern); + + pattern_cmd = kmalloc(cmd.len[0], GFP_KERNEL); + if (!pattern_cmd) + return -ENOMEM; + + pattern_cmd->n_patterns = cpu_to_le32(wowlan->n_patterns); + + for (i = 0; i < wowlan->n_patterns; i++) { + int mask_len = DIV_ROUND_UP(wowlan->patterns[i].pattern_len, 8); + + memcpy(&pattern_cmd->patterns[i].mask, + wowlan->patterns[i].mask, mask_len); + memcpy(&pattern_cmd->patterns[i].pattern, + wowlan->patterns[i].pattern, + wowlan->patterns[i].pattern_len); + pattern_cmd->patterns[i].mask_size = mask_len; + pattern_cmd->patterns[i].pattern_size = + wowlan->patterns[i].pattern_len; + } + + cmd.data[0] = pattern_cmd; + err = iwl_mvm_send_cmd(mvm, &cmd); + kfree(pattern_cmd); + return err; +} + +static int iwl_mvm_send_proto_offload(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_proto_offload_cmd cmd = {}; +#if IS_ENABLED(CONFIG_IPV6) + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int i; + + if (mvmvif->num_target_ipv6_addrs) { + cmd.enabled |= cpu_to_le32(IWL_D3_PROTO_OFFLOAD_NS); + memcpy(cmd.ndp_mac_addr, vif->addr, ETH_ALEN); + } + + BUILD_BUG_ON(sizeof(cmd.target_ipv6_addr[i]) != + sizeof(mvmvif->target_ipv6_addrs[i])); + + for (i = 0; i < mvmvif->num_target_ipv6_addrs; i++) + memcpy(cmd.target_ipv6_addr[i], + &mvmvif->target_ipv6_addrs[i], + sizeof(cmd.target_ipv6_addr[i])); +#endif + + if (vif->bss_conf.arp_addr_cnt) { + cmd.enabled |= cpu_to_le32(IWL_D3_PROTO_OFFLOAD_ARP); + cmd.host_ipv4_addr = vif->bss_conf.arp_addr_list[0]; + memcpy(cmd.arp_mac_addr, vif->addr, ETH_ALEN); + } + + if (!cmd.enabled) + return 0; + + return iwl_mvm_send_cmd_pdu(mvm, PROT_OFFLOAD_CONFIG_CMD, CMD_SYNC, + sizeof(cmd), &cmd); +} + +enum iwl_mvm_tcp_packet_type { + MVM_TCP_TX_SYN, + MVM_TCP_RX_SYNACK, + MVM_TCP_TX_DATA, + MVM_TCP_RX_ACK, + MVM_TCP_RX_WAKE, + MVM_TCP_TX_FIN, +}; + +static __le16 pseudo_hdr_check(int len, __be32 saddr, __be32 daddr) +{ + __sum16 check = tcp_v4_check(len, saddr, daddr, 0); + return cpu_to_le16(be16_to_cpu((__force __be16)check)); +} + +static void iwl_mvm_build_tcp_packet(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_wowlan_tcp *tcp, + void *_pkt, u8 *mask, + __le16 *pseudo_hdr_csum, + enum iwl_mvm_tcp_packet_type ptype) +{ + struct { + struct ethhdr eth; + struct iphdr ip; + struct tcphdr tcp; + u8 data[]; + } __packed *pkt = _pkt; + u16 ip_tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr); + int i; + + pkt->eth.h_proto = cpu_to_be16(ETH_P_IP), + pkt->ip.version = 4; + pkt->ip.ihl = 5; + pkt->ip.protocol = IPPROTO_TCP; + + switch (ptype) { + case MVM_TCP_TX_SYN: + case MVM_TCP_TX_DATA: + case MVM_TCP_TX_FIN: + memcpy(pkt->eth.h_dest, tcp->dst_mac, ETH_ALEN); + memcpy(pkt->eth.h_source, vif->addr, ETH_ALEN); + pkt->ip.ttl = 128; + pkt->ip.saddr = tcp->src; + pkt->ip.daddr = tcp->dst; + pkt->tcp.source = cpu_to_be16(tcp->src_port); + pkt->tcp.dest = cpu_to_be16(tcp->dst_port); + /* overwritten for TX SYN later */ + pkt->tcp.doff = sizeof(struct tcphdr) / 4; + pkt->tcp.window = cpu_to_be16(65000); + break; + case MVM_TCP_RX_SYNACK: + case MVM_TCP_RX_ACK: + case MVM_TCP_RX_WAKE: + memcpy(pkt->eth.h_dest, vif->addr, ETH_ALEN); + memcpy(pkt->eth.h_source, tcp->dst_mac, ETH_ALEN); + pkt->ip.saddr = tcp->dst; + pkt->ip.daddr = tcp->src; + pkt->tcp.source = cpu_to_be16(tcp->dst_port); + pkt->tcp.dest = cpu_to_be16(tcp->src_port); + break; + default: + WARN_ON(1); + return; + } + + switch (ptype) { + case MVM_TCP_TX_SYN: + /* firmware assumes 8 option bytes - 8 NOPs for now */ + memset(pkt->data, 0x01, 8); + ip_tot_len += 8; + pkt->tcp.doff = (sizeof(struct tcphdr) + 8) / 4; + pkt->tcp.syn = 1; + break; + case MVM_TCP_TX_DATA: + ip_tot_len += tcp->payload_len; + memcpy(pkt->data, tcp->payload, tcp->payload_len); + pkt->tcp.psh = 1; + pkt->tcp.ack = 1; + break; + case MVM_TCP_TX_FIN: + pkt->tcp.fin = 1; + pkt->tcp.ack = 1; + break; + case MVM_TCP_RX_SYNACK: + pkt->tcp.syn = 1; + pkt->tcp.ack = 1; + break; + case MVM_TCP_RX_ACK: + pkt->tcp.ack = 1; + break; + case MVM_TCP_RX_WAKE: + ip_tot_len += tcp->wake_len; + pkt->tcp.psh = 1; + pkt->tcp.ack = 1; + memcpy(pkt->data, tcp->wake_data, tcp->wake_len); + break; + } + + switch (ptype) { + case MVM_TCP_TX_SYN: + case MVM_TCP_TX_DATA: + case MVM_TCP_TX_FIN: + pkt->ip.tot_len = cpu_to_be16(ip_tot_len); + pkt->ip.check = ip_fast_csum(&pkt->ip, pkt->ip.ihl); + break; + case MVM_TCP_RX_WAKE: + for (i = 0; i < DIV_ROUND_UP(tcp->wake_len, 8); i++) { + u8 tmp = tcp->wake_mask[i]; + mask[i + 6] |= tmp << 6; + if (i + 1 < DIV_ROUND_UP(tcp->wake_len, 8)) + mask[i + 7] = tmp >> 2; + } + /* fall through for ethernet/IP/TCP headers mask */ + case MVM_TCP_RX_SYNACK: + case MVM_TCP_RX_ACK: + mask[0] = 0xff; /* match ethernet */ + /* + * match ethernet, ip.version, ip.ihl + * the ip.ihl half byte is really masked out by firmware + */ + mask[1] = 0x7f; + mask[2] = 0x80; /* match ip.protocol */ + mask[3] = 0xfc; /* match ip.saddr, ip.daddr */ + mask[4] = 0x3f; /* match ip.daddr, tcp.source, tcp.dest */ + mask[5] = 0x80; /* match tcp flags */ + /* leave rest (0 or set for MVM_TCP_RX_WAKE) */ + break; + }; + + *pseudo_hdr_csum = pseudo_hdr_check(ip_tot_len - sizeof(struct iphdr), + pkt->ip.saddr, pkt->ip.daddr); +} + +static int iwl_mvm_send_remote_wake_cfg(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_wowlan_tcp *tcp) +{ + struct iwl_wowlan_remote_wake_config *cfg; + struct iwl_host_cmd cmd = { + .id = REMOTE_WAKE_CONFIG_CMD, + .len = { sizeof(*cfg), }, + .dataflags = { IWL_HCMD_DFL_NOCOPY, }, + .flags = CMD_SYNC, + }; + int ret; + + if (!tcp) + return 0; + + cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + cmd.data[0] = cfg; + + cfg->max_syn_retries = 10; + cfg->max_data_retries = 10; + cfg->tcp_syn_ack_timeout = 1; /* seconds */ + cfg->tcp_ack_timeout = 1; /* seconds */ + + /* SYN (TX) */ + iwl_mvm_build_tcp_packet( + mvm, vif, tcp, cfg->syn_tx.data, NULL, + &cfg->syn_tx.info.tcp_pseudo_header_checksum, + MVM_TCP_TX_SYN); + cfg->syn_tx.info.tcp_payload_length = 0; + + /* SYN/ACK (RX) */ + iwl_mvm_build_tcp_packet( + mvm, vif, tcp, cfg->synack_rx.data, cfg->synack_rx.rx_mask, + &cfg->synack_rx.info.tcp_pseudo_header_checksum, + MVM_TCP_RX_SYNACK); + cfg->synack_rx.info.tcp_payload_length = 0; + + /* KEEPALIVE/ACK (TX) */ + iwl_mvm_build_tcp_packet( + mvm, vif, tcp, cfg->keepalive_tx.data, NULL, + &cfg->keepalive_tx.info.tcp_pseudo_header_checksum, + MVM_TCP_TX_DATA); + cfg->keepalive_tx.info.tcp_payload_length = + cpu_to_le16(tcp->payload_len); + cfg->sequence_number_offset = tcp->payload_seq.offset; + /* length must be 0..4, the field is little endian */ + cfg->sequence_number_length = tcp->payload_seq.len; + cfg->initial_sequence_number = cpu_to_le32(tcp->payload_seq.start); + cfg->keepalive_interval = cpu_to_le16(tcp->data_interval); + if (tcp->payload_tok.len) { + cfg->token_offset = tcp->payload_tok.offset; + cfg->token_length = tcp->payload_tok.len; + cfg->num_tokens = + cpu_to_le16(tcp->tokens_size % tcp->payload_tok.len); + memcpy(cfg->tokens, tcp->payload_tok.token_stream, + tcp->tokens_size); + } else { + /* set tokens to max value to almost never run out */ + cfg->num_tokens = cpu_to_le16(65535); + } + + /* ACK (RX) */ + iwl_mvm_build_tcp_packet( + mvm, vif, tcp, cfg->keepalive_ack_rx.data, + cfg->keepalive_ack_rx.rx_mask, + &cfg->keepalive_ack_rx.info.tcp_pseudo_header_checksum, + MVM_TCP_RX_ACK); + cfg->keepalive_ack_rx.info.tcp_payload_length = 0; + + /* WAKEUP (RX) */ + iwl_mvm_build_tcp_packet( + mvm, vif, tcp, cfg->wake_rx.data, cfg->wake_rx.rx_mask, + &cfg->wake_rx.info.tcp_pseudo_header_checksum, + MVM_TCP_RX_WAKE); + cfg->wake_rx.info.tcp_payload_length = + cpu_to_le16(tcp->wake_len); + + /* FIN */ + iwl_mvm_build_tcp_packet( + mvm, vif, tcp, cfg->fin_tx.data, NULL, + &cfg->fin_tx.info.tcp_pseudo_header_checksum, + MVM_TCP_TX_FIN); + cfg->fin_tx.info.tcp_payload_length = 0; + + ret = iwl_mvm_send_cmd(mvm, &cmd); + kfree(cfg); + + return ret; +} + +struct iwl_d3_iter_data { + struct iwl_mvm *mvm; + struct ieee80211_vif *vif; + bool error; +}; + +static void iwl_mvm_d3_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_d3_iter_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif->type != NL80211_IFTYPE_STATION || vif->p2p) + return; + + if (mvmvif->ap_sta_id == IWL_MVM_STATION_COUNT) + return; + + if (data->vif) { + IWL_ERR(data->mvm, "More than one managed interface active!\n"); + data->error = true; + return; + } + + data->vif = vif; +} + +static int iwl_mvm_d3_reprogram(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *ap_sta) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_chanctx_conf *ctx; + u8 chains_static, chains_dynamic; + struct cfg80211_chan_def chandef; + int ret, i; + struct iwl_binding_cmd binding_cmd = {}; + struct iwl_time_quota_cmd quota_cmd = {}; + u32 status; + + /* add back the PHY */ + if (WARN_ON(!mvmvif->phy_ctxt)) + return -EINVAL; + + rcu_read_lock(); + ctx = rcu_dereference(vif->chanctx_conf); + if (WARN_ON(!ctx)) { + rcu_read_unlock(); + return -EINVAL; + } + chandef = ctx->def; + chains_static = ctx->rx_chains_static; + chains_dynamic = ctx->rx_chains_dynamic; + rcu_read_unlock(); + + ret = iwl_mvm_phy_ctxt_add(mvm, mvmvif->phy_ctxt, &chandef, + chains_static, chains_dynamic); + if (ret) + return ret; + + /* add back the MAC */ + mvmvif->uploaded = false; + + if (WARN_ON(!vif->bss_conf.assoc)) + return -EINVAL; + /* hack */ + vif->bss_conf.assoc = false; + ret = iwl_mvm_mac_ctxt_add(mvm, vif); + vif->bss_conf.assoc = true; + if (ret) + return ret; + + /* add back binding - XXX refactor? */ + binding_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->phy_ctxt->id, + mvmvif->phy_ctxt->color)); + binding_cmd.action = cpu_to_le32(FW_CTXT_ACTION_ADD); + binding_cmd.phy = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->phy_ctxt->id, + mvmvif->phy_ctxt->color)); + binding_cmd.macs[0] = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)); + for (i = 1; i < MAX_MACS_IN_BINDING; i++) + binding_cmd.macs[i] = cpu_to_le32(FW_CTXT_INVALID); + + status = 0; + ret = iwl_mvm_send_cmd_pdu_status(mvm, BINDING_CONTEXT_CMD, + sizeof(binding_cmd), &binding_cmd, + &status); + if (ret) { + IWL_ERR(mvm, "Failed to add binding: %d\n", ret); + return ret; + } + + if (status) { + IWL_ERR(mvm, "Binding command failed: %u\n", status); + return -EIO; + } + + ret = iwl_mvm_sta_send_to_fw(mvm, ap_sta, false); + if (ret) + return ret; + rcu_assign_pointer(mvm->fw_id_to_mac_id[mvmvif->ap_sta_id], ap_sta); + + ret = iwl_mvm_mac_ctxt_changed(mvm, vif); + if (ret) + return ret; + + /* and some quota */ + quota_cmd.quotas[0].id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->phy_ctxt->id, + mvmvif->phy_ctxt->color)); + quota_cmd.quotas[0].quota = cpu_to_le32(100); + quota_cmd.quotas[0].max_duration = cpu_to_le32(1000); + + for (i = 1; i < MAX_BINDINGS; i++) + quota_cmd.quotas[i].id_and_color = cpu_to_le32(FW_CTXT_INVALID); + + ret = iwl_mvm_send_cmd_pdu(mvm, TIME_QUOTA_CMD, CMD_SYNC, + sizeof(quota_cmd), "a_cmd); + if (ret) + IWL_ERR(mvm, "Failed to send quota: %d\n", ret); + + return 0; +} + +int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_d3_iter_data suspend_iter_data = { + .mvm = mvm, + }; + struct ieee80211_vif *vif; + struct iwl_mvm_vif *mvmvif; + struct ieee80211_sta *ap_sta; + struct iwl_mvm_sta *mvm_ap_sta; + struct iwl_wowlan_config_cmd wowlan_config_cmd = {}; + struct iwl_wowlan_kek_kck_material_cmd kek_kck_cmd = {}; + struct iwl_wowlan_tkip_params_cmd tkip_cmd = {}; + struct iwl_d3_manager_config d3_cfg_cmd = { + /* + * Program the minimum sleep time to 10 seconds, as many + * platforms have issues processing a wakeup signal while + * still being in the process of suspending. + */ + .min_sleep_time = cpu_to_le32(10 * 1000 * 1000), + }; + struct wowlan_key_data key_data = { + .use_rsc_tsc = false, + .tkip = &tkip_cmd, + .use_tkip = false, + }; + int ret, i; + u16 seq; + u8 old_aux_sta_id, old_ap_sta_id = IWL_MVM_STATION_COUNT; + + if (WARN_ON(!wowlan)) + return -EINVAL; + + key_data.rsc_tsc = kzalloc(sizeof(*key_data.rsc_tsc), GFP_KERNEL); + if (!key_data.rsc_tsc) + return -ENOMEM; + + mutex_lock(&mvm->mutex); + + old_aux_sta_id = mvm->aux_sta.sta_id; + + /* see if there's only a single BSS vif and it's associated */ + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_d3_iface_iterator, &suspend_iter_data); + + if (suspend_iter_data.error || !suspend_iter_data.vif) { + ret = 1; + goto out_noreset; + } + + vif = suspend_iter_data.vif; + mvmvif = iwl_mvm_vif_from_mac80211(vif); + + ap_sta = rcu_dereference_protected( + mvm->fw_id_to_mac_id[mvmvif->ap_sta_id], + lockdep_is_held(&mvm->mutex)); + if (IS_ERR_OR_NULL(ap_sta)) { + ret = -EINVAL; + goto out_noreset; + } + + mvm_ap_sta = (struct iwl_mvm_sta *)ap_sta->drv_priv; + + /* + * The D3 firmware still hardcodes the AP station ID for the + * BSS we're associated with as 0. Store the real STA ID here + * and assign 0. When we leave this function, we'll restore + * the original value for the resume code. + */ + old_ap_sta_id = mvm_ap_sta->sta_id; + mvm_ap_sta->sta_id = 0; + mvmvif->ap_sta_id = 0; + + /* TODO: wowlan_config_cmd.wowlan_ba_teardown_tids */ + + wowlan_config_cmd.is_11n_connection = ap_sta->ht_cap.ht_supported; + + /* + * We know the last used seqno, and the uCode expects to know that + * one, it will increment before TX. + */ + seq = mvm_ap_sta->last_seq_ctl & IEEE80211_SCTL_SEQ; + wowlan_config_cmd.non_qos_seq = cpu_to_le16(seq); + + /* + * For QoS counters, we store the one to use next, so subtract 0x10 + * since the uCode will add 0x10 *before* using the value while we + * increment after using the value (i.e. store the next value to use). + */ + for (i = 0; i < IWL_MAX_TID_COUNT; i++) { + seq = mvm_ap_sta->tid_data[i].seq_number; + seq -= 0x10; + wowlan_config_cmd.qos_seq[i] = cpu_to_le16(seq); + } + + if (wowlan->disconnect) + wowlan_config_cmd.wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_BEACON_MISS | + IWL_WOWLAN_WAKEUP_LINK_CHANGE); + if (wowlan->magic_pkt) + wowlan_config_cmd.wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_MAGIC_PACKET); + if (wowlan->gtk_rekey_failure) + wowlan_config_cmd.wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_GTK_REKEY_FAIL); + if (wowlan->eap_identity_req) + wowlan_config_cmd.wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_EAP_IDENT_REQ); + if (wowlan->four_way_handshake) + wowlan_config_cmd.wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_4WAY_HANDSHAKE); + if (wowlan->n_patterns) + wowlan_config_cmd.wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_PATTERN_MATCH); + + if (wowlan->rfkill_release) + wowlan_config_cmd.wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_RF_KILL_DEASSERT); + + if (wowlan->tcp) { + /* + * Set the "link change" (really "link lost") flag as well + * since that implies losing the TCP connection. + */ + wowlan_config_cmd.wakeup_filter |= + cpu_to_le32(IWL_WOWLAN_WAKEUP_REMOTE_LINK_LOSS | + IWL_WOWLAN_WAKEUP_REMOTE_SIGNATURE_TABLE | + IWL_WOWLAN_WAKEUP_REMOTE_WAKEUP_PACKET | + IWL_WOWLAN_WAKEUP_LINK_CHANGE); + } + + iwl_mvm_cancel_scan(mvm); + + iwl_trans_stop_device(mvm->trans); + + /* + * Set the HW restart bit -- this is mostly true as we're + * going to load new firmware and reprogram that, though + * the reprogramming is going to be manual to avoid adding + * all the MACs that aren't support. + * We don't have to clear up everything though because the + * reprogramming is manual. When we resume, we'll actually + * go through a proper restart sequence again to switch + * back to the runtime firmware image. + */ + set_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status); + + /* We reprogram keys and shouldn't allocate new key indices */ + memset(mvm->fw_key_table, 0, sizeof(mvm->fw_key_table)); + + mvm->ptk_ivlen = 0; + mvm->ptk_icvlen = 0; + mvm->ptk_ivlen = 0; + mvm->ptk_icvlen = 0; + + /* + * The D3 firmware still hardcodes the AP station ID for the + * BSS we're associated with as 0. As a result, we have to move + * the auxiliary station to ID 1 so the ID 0 remains free for + * the AP station for later. + * We set the sta_id to 1 here, and reset it to its previous + * value (that we stored above) later. + */ + mvm->aux_sta.sta_id = 1; + + ret = iwl_mvm_load_d3_fw(mvm); + if (ret) + goto out; + + ret = iwl_mvm_d3_reprogram(mvm, vif, ap_sta); + if (ret) + goto out; + + if (!iwlwifi_mod_params.sw_crypto) { + /* + * This needs to be unlocked due to lock ordering + * constraints. Since we're in the suspend path + * that isn't really a problem though. + */ + mutex_unlock(&mvm->mutex); + ieee80211_iter_keys(mvm->hw, vif, + iwl_mvm_wowlan_program_keys, + &key_data); + mutex_lock(&mvm->mutex); + if (key_data.error) { + ret = -EIO; + goto out; + } + + if (key_data.use_rsc_tsc) { + struct iwl_host_cmd rsc_tsc_cmd = { + .id = WOWLAN_TSC_RSC_PARAM, + .flags = CMD_SYNC, + .data[0] = key_data.rsc_tsc, + .dataflags[0] = IWL_HCMD_DFL_NOCOPY, + .len[0] = sizeof(*key_data.rsc_tsc), + }; + + ret = iwl_mvm_send_cmd(mvm, &rsc_tsc_cmd); + if (ret) + goto out; + } + + if (key_data.use_tkip) { + ret = iwl_mvm_send_cmd_pdu(mvm, + WOWLAN_TKIP_PARAM, + CMD_SYNC, sizeof(tkip_cmd), + &tkip_cmd); + if (ret) + goto out; + } + + if (mvmvif->rekey_data.valid) { + memset(&kek_kck_cmd, 0, sizeof(kek_kck_cmd)); + memcpy(kek_kck_cmd.kck, mvmvif->rekey_data.kck, + NL80211_KCK_LEN); + kek_kck_cmd.kck_len = cpu_to_le16(NL80211_KCK_LEN); + memcpy(kek_kck_cmd.kek, mvmvif->rekey_data.kek, + NL80211_KEK_LEN); + kek_kck_cmd.kek_len = cpu_to_le16(NL80211_KEK_LEN); + kek_kck_cmd.replay_ctr = mvmvif->rekey_data.replay_ctr; + + ret = iwl_mvm_send_cmd_pdu(mvm, + WOWLAN_KEK_KCK_MATERIAL, + CMD_SYNC, + sizeof(kek_kck_cmd), + &kek_kck_cmd); + if (ret) + goto out; + } + } + + ret = iwl_mvm_send_cmd_pdu(mvm, WOWLAN_CONFIGURATION, + CMD_SYNC, sizeof(wowlan_config_cmd), + &wowlan_config_cmd); + if (ret) + goto out; + + ret = iwl_mvm_send_patterns(mvm, wowlan); + if (ret) + goto out; + + ret = iwl_mvm_send_proto_offload(mvm, vif); + if (ret) + goto out; + + ret = iwl_mvm_send_remote_wake_cfg(mvm, vif, wowlan->tcp); + if (ret) + goto out; + + /* must be last -- this switches firmware state */ + ret = iwl_mvm_send_cmd_pdu(mvm, D3_CONFIG_CMD, CMD_SYNC, + sizeof(d3_cfg_cmd), &d3_cfg_cmd); + if (ret) + goto out; + + clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status); + + iwl_trans_d3_suspend(mvm->trans); + out: + mvm->aux_sta.sta_id = old_aux_sta_id; + mvm_ap_sta->sta_id = old_ap_sta_id; + mvmvif->ap_sta_id = old_ap_sta_id; + out_noreset: + kfree(key_data.rsc_tsc); + if (ret < 0) + ieee80211_restart_hw(mvm->hw); + + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_query_wakeup_reasons(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + u32 base = mvm->error_event_table; + struct error_table_start { + /* cf. struct iwl_error_event_table */ + u32 valid; + u32 error_id; + } err_info; + struct cfg80211_wowlan_wakeup wakeup = { + .pattern_idx = -1, + }; + struct cfg80211_wowlan_wakeup *wakeup_report = &wakeup; + struct iwl_host_cmd cmd = { + .id = WOWLAN_GET_STATUSES, + .flags = CMD_SYNC | CMD_WANT_SKB, + }; + struct iwl_wowlan_status *status; + u32 reasons; + int ret, len; + struct sk_buff *pkt = NULL; + + iwl_trans_read_mem_bytes(mvm->trans, base, + &err_info, sizeof(err_info)); + + if (err_info.valid) { + IWL_INFO(mvm, "error table is valid (%d)\n", + err_info.valid); + if (err_info.error_id == RF_KILL_INDICATOR_FOR_WOWLAN) { + wakeup.rfkill_release = true; + ieee80211_report_wowlan_wakeup(vif, &wakeup, + GFP_KERNEL); + } + return; + } + + /* only for tracing for now */ + ret = iwl_mvm_send_cmd_pdu(mvm, OFFLOADS_QUERY_CMD, CMD_SYNC, 0, NULL); + if (ret) + IWL_ERR(mvm, "failed to query offload statistics (%d)\n", ret); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret) { + IWL_ERR(mvm, "failed to query status (%d)\n", ret); + return; + } + + /* RF-kill already asserted again... */ + if (!cmd.resp_pkt) + return; + + len = le32_to_cpu(cmd.resp_pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; + if (len - sizeof(struct iwl_cmd_header) < sizeof(*status)) { + IWL_ERR(mvm, "Invalid WoWLAN status response!\n"); + goto out; + } + + status = (void *)cmd.resp_pkt->data; + + if (len - sizeof(struct iwl_cmd_header) != + sizeof(*status) + + ALIGN(le32_to_cpu(status->wake_packet_bufsize), 4)) { + IWL_ERR(mvm, "Invalid WoWLAN status response!\n"); + goto out; + } + + reasons = le32_to_cpu(status->wakeup_reasons); + + if (reasons == IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS) { + wakeup_report = NULL; + goto report; + } + + if (reasons & IWL_WOWLAN_WAKEUP_BY_MAGIC_PACKET) + wakeup.magic_pkt = true; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_PATTERN) + wakeup.pattern_idx = + le16_to_cpu(status->pattern_number); + + if (reasons & (IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_MISSED_BEACON | + IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_DEAUTH)) + wakeup.disconnect = true; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_GTK_REKEY_FAILURE) + wakeup.gtk_rekey_failure = true; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_RFKILL_DEASSERTED) + wakeup.rfkill_release = true; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_EAPOL_REQUEST) + wakeup.eap_identity_req = true; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_FOUR_WAY_HANDSHAKE) + wakeup.four_way_handshake = true; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_LINK_LOSS) + wakeup.tcp_connlost = true; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_SIGNATURE_TABLE) + wakeup.tcp_nomoretokens = true; + + if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_WAKEUP_PACKET) + wakeup.tcp_match = true; + + if (status->wake_packet_bufsize) { + int pktsize = le32_to_cpu(status->wake_packet_bufsize); + int pktlen = le32_to_cpu(status->wake_packet_length); + const u8 *pktdata = status->wake_packet; + struct ieee80211_hdr *hdr = (void *)pktdata; + int truncated = pktlen - pktsize; + + /* this would be a firmware bug */ + if (WARN_ON_ONCE(truncated < 0)) + truncated = 0; + + if (ieee80211_is_data(hdr->frame_control)) { + int hdrlen = ieee80211_hdrlen(hdr->frame_control); + int ivlen = 0, icvlen = 4; /* also FCS */ + + pkt = alloc_skb(pktsize, GFP_KERNEL); + if (!pkt) + goto report; + + memcpy(skb_put(pkt, hdrlen), pktdata, hdrlen); + pktdata += hdrlen; + pktsize -= hdrlen; + + if (ieee80211_has_protected(hdr->frame_control)) { + if (is_multicast_ether_addr(hdr->addr1)) { + ivlen = mvm->gtk_ivlen; + icvlen += mvm->gtk_icvlen; + } else { + ivlen = mvm->ptk_ivlen; + icvlen += mvm->ptk_icvlen; + } + } + + /* if truncated, FCS/ICV is (partially) gone */ + if (truncated >= icvlen) { + icvlen = 0; + truncated -= icvlen; + } else { + icvlen -= truncated; + truncated = 0; + } + + pktsize -= ivlen + icvlen; + pktdata += ivlen; + + memcpy(skb_put(pkt, pktsize), pktdata, pktsize); + + if (ieee80211_data_to_8023(pkt, vif->addr, vif->type)) + goto report; + wakeup.packet = pkt->data; + wakeup.packet_present_len = pkt->len; + wakeup.packet_len = pkt->len - truncated; + wakeup.packet_80211 = false; + } else { + int fcslen = 4; + + if (truncated >= 4) { + truncated -= 4; + fcslen = 0; + } else { + fcslen -= truncated; + truncated = 0; + } + pktsize -= fcslen; + wakeup.packet = status->wake_packet; + wakeup.packet_present_len = pktsize; + wakeup.packet_len = pktlen - truncated; + wakeup.packet_80211 = true; + } + } + + report: + ieee80211_report_wowlan_wakeup(vif, wakeup_report, GFP_KERNEL); + kfree_skb(pkt); + + out: + iwl_free_resp(&cmd); +} + +int iwl_mvm_resume(struct ieee80211_hw *hw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_d3_iter_data resume_iter_data = { + .mvm = mvm, + }; + struct ieee80211_vif *vif = NULL; + int ret; + enum iwl_d3_status d3_status; + + mutex_lock(&mvm->mutex); + + /* get the BSS vif pointer again */ + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_d3_iface_iterator, &resume_iter_data); + + if (WARN_ON(resume_iter_data.error || !resume_iter_data.vif)) + goto out_unlock; + + vif = resume_iter_data.vif; + + ret = iwl_trans_d3_resume(mvm->trans, &d3_status); + if (ret) + goto out_unlock; + + if (d3_status != IWL_D3_STATUS_ALIVE) { + IWL_INFO(mvm, "Device was reset during suspend\n"); + goto out_unlock; + } + + iwl_mvm_query_wakeup_reasons(mvm, vif); + + out_unlock: + mutex_unlock(&mvm->mutex); + + if (vif) + ieee80211_resume_disconnect(vif); + + /* return 1 to reconfigure the device */ + set_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status); + return 1; +} + +void iwl_mvm_set_wakeup(struct ieee80211_hw *hw, bool enabled) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + device_set_wakeup_enable(mvm->trans->dev, enabled); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/debugfs.c b/drivers/net/wireless/iwlwifi/mvm/debugfs.c new file mode 100644 index 0000000..2053dcc --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/debugfs.c @@ -0,0 +1,615 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#include "mvm.h" +#include "sta.h" +#include "iwl-io.h" + +struct iwl_dbgfs_mvm_ctx { + struct iwl_mvm *mvm; + struct ieee80211_vif *vif; +}; + +static ssize_t iwl_dbgfs_tx_flush_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + + char buf[16]; + int buf_size, ret; + u32 scd_q_msk; + + if (!mvm->ucode_loaded || mvm->cur_ucode != IWL_UCODE_REGULAR) + return -EIO; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (sscanf(buf, "%x", &scd_q_msk) != 1) + return -EINVAL; + + IWL_ERR(mvm, "FLUSHING queues: scd_q_msk = 0x%x\n", scd_q_msk); + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_flush_tx_path(mvm, scd_q_msk, true) ? : count; + mutex_unlock(&mvm->mutex); + + return ret; +} + +static ssize_t iwl_dbgfs_sta_drain_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + struct ieee80211_sta *sta; + + char buf[8]; + int buf_size, sta_id, drain, ret; + + if (!mvm->ucode_loaded || mvm->cur_ucode != IWL_UCODE_REGULAR) + return -EIO; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (sscanf(buf, "%d %d", &sta_id, &drain) != 2) + return -EINVAL; + + mutex_lock(&mvm->mutex); + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + if (IS_ERR_OR_NULL(sta)) + ret = -ENOENT; + else + ret = iwl_mvm_drain_sta(mvm, (void *)sta->drv_priv, drain) ? : + count; + + mutex_unlock(&mvm->mutex); + + return ret; +} + +static ssize_t iwl_dbgfs_sram_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + const struct fw_img *img; + int ofs, len, pos = 0; + size_t bufsz, ret; + char *buf; + u8 *ptr; + + /* default is to dump the entire data segment */ + if (!mvm->dbgfs_sram_offset && !mvm->dbgfs_sram_len) { + mvm->dbgfs_sram_offset = 0x800000; + if (!mvm->ucode_loaded) + return -EINVAL; + img = &mvm->fw->img[mvm->cur_ucode]; + mvm->dbgfs_sram_len = img->sec[IWL_UCODE_SECTION_DATA].len; + } + len = mvm->dbgfs_sram_len; + + bufsz = len * 4 + 256; + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ptr = kzalloc(len, GFP_KERNEL); + if (!ptr) { + kfree(buf); + return -ENOMEM; + } + + pos += scnprintf(buf + pos, bufsz - pos, "sram_len: 0x%x\n", len); + pos += scnprintf(buf + pos, bufsz - pos, "sram_offset: 0x%x\n", + mvm->dbgfs_sram_offset); + + iwl_trans_read_mem_bytes(mvm->trans, + mvm->dbgfs_sram_offset, + ptr, len); + for (ofs = 0; ofs < len; ofs += 16) { + pos += scnprintf(buf + pos, bufsz - pos, "0x%.4x ", ofs); + hex_dump_to_buffer(ptr + ofs, 16, 16, 1, buf + pos, + bufsz - pos, false); + pos += strlen(buf + pos); + if (bufsz - pos > 0) + buf[pos++] = '\n'; + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + + kfree(buf); + kfree(ptr); + + return ret; +} + +static ssize_t iwl_dbgfs_sram_write(struct file *file, + const char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + char buf[64]; + int buf_size; + u32 offset, len; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (sscanf(buf, "%x,%x", &offset, &len) == 2) { + if ((offset & 0x3) || (len & 0x3)) + return -EINVAL; + mvm->dbgfs_sram_offset = offset; + mvm->dbgfs_sram_len = len; + } else { + mvm->dbgfs_sram_offset = 0; + mvm->dbgfs_sram_len = 0; + } + + return count; +} + +static ssize_t iwl_dbgfs_stations_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + struct ieee80211_sta *sta; + char buf[400]; + int i, pos = 0, bufsz = sizeof(buf); + + mutex_lock(&mvm->mutex); + + for (i = 0; i < IWL_MVM_STATION_COUNT; i++) { + pos += scnprintf(buf + pos, bufsz - pos, "%.2d: ", i); + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i], + lockdep_is_held(&mvm->mutex)); + if (!sta) + pos += scnprintf(buf + pos, bufsz - pos, "N/A\n"); + else if (IS_ERR(sta)) + pos += scnprintf(buf + pos, bufsz - pos, "%ld\n", + PTR_ERR(sta)); + else + pos += scnprintf(buf + pos, bufsz - pos, "%pM\n", + sta->addr); + } + + mutex_unlock(&mvm->mutex); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_power_down_allow_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + char buf[8] = {}; + int allow; + + if (!mvm->ucode_loaded) + return -EIO; + + if (copy_from_user(buf, user_buf, sizeof(buf))) + return -EFAULT; + + if (sscanf(buf, "%d", &allow) != 1) + return -EINVAL; + + IWL_DEBUG_POWER(mvm, "%s device power down\n", + allow ? "allow" : "prevent"); + + /* + * TODO: Send REPLY_DEBUG_CMD (0xf0) when FW support it + */ + + return count; +} + +static ssize_t iwl_dbgfs_power_down_d3_allow_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + char buf[8] = {}; + int allow; + + if (copy_from_user(buf, user_buf, sizeof(buf))) + return -EFAULT; + + if (sscanf(buf, "%d", &allow) != 1) + return -EINVAL; + + IWL_DEBUG_POWER(mvm, "%s device power down in d3\n", + allow ? "allow" : "prevent"); + + /* + * TODO: When WoWLAN FW alive notification happens, driver will send + * REPLY_DEBUG_CMD setting power_down_allow flag according to + * mvm->prevent_power_down_d3 + */ + mvm->prevent_power_down_d3 = !allow; + + return count; +} + +static ssize_t iwl_dbgfs_mac_params_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ieee80211_vif *vif = file->private_data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm *mvm = mvmvif->dbgfs_data; + u8 ap_sta_id; + struct ieee80211_chanctx_conf *chanctx_conf; + char buf[512]; + int bufsz = sizeof(buf); + int pos = 0; + int i; + + mutex_lock(&mvm->mutex); + + ap_sta_id = mvmvif->ap_sta_id; + + pos += scnprintf(buf+pos, bufsz-pos, "mac id/color: %d / %d\n", + mvmvif->id, mvmvif->color); + pos += scnprintf(buf+pos, bufsz-pos, "bssid: %pM\n", + vif->bss_conf.bssid); + pos += scnprintf(buf+pos, bufsz-pos, "QoS:\n"); + for (i = 0; i < ARRAY_SIZE(mvmvif->queue_params); i++) { + pos += scnprintf(buf+pos, bufsz-pos, + "\t%d: txop:%d - cw_min:%d - cw_max = %d - aifs = %d upasd = %d\n", + i, mvmvif->queue_params[i].txop, + mvmvif->queue_params[i].cw_min, + mvmvif->queue_params[i].cw_max, + mvmvif->queue_params[i].aifs, + mvmvif->queue_params[i].uapsd); + } + + if (vif->type == NL80211_IFTYPE_STATION && + ap_sta_id != IWL_MVM_STATION_COUNT) { + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvm_sta; + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[ap_sta_id], + lockdep_is_held(&mvm->mutex)); + mvm_sta = (void *)sta->drv_priv; + pos += scnprintf(buf+pos, bufsz-pos, + "ap_sta_id %d - reduced Tx power %d\n", + ap_sta_id, mvm_sta->bt_reduced_txpower); + } + + rcu_read_lock(); + chanctx_conf = rcu_dereference(vif->chanctx_conf); + if (chanctx_conf) { + pos += scnprintf(buf+pos, bufsz-pos, + "idle rx chains %d, active rx chains: %d\n", + chanctx_conf->rx_chains_static, + chanctx_conf->rx_chains_dynamic); + } + rcu_read_unlock(); + + mutex_unlock(&mvm->mutex); + + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +#define BT_MBOX_MSG(_notif, _num, _field) \ + ((le32_to_cpu((_notif)->mbox_msg[(_num)]) & BT_MBOX##_num##_##_field)\ + >> BT_MBOX##_num##_##_field##_POS) + + +#define BT_MBOX_PRINT(_num, _field, _end) \ + pos += scnprintf(buf + pos, bufsz - pos, \ + "\t%s: %d%s", \ + #_field, \ + BT_MBOX_MSG(notif, _num, _field), \ + true ? "\n" : ", "); + +static ssize_t iwl_dbgfs_bt_notif_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + struct iwl_bt_coex_profile_notif *notif = &mvm->last_bt_notif; + char *buf; + int ret, pos = 0, bufsz = sizeof(char) * 1024; + + buf = kmalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + mutex_lock(&mvm->mutex); + + pos += scnprintf(buf+pos, bufsz-pos, "MBOX dw0:\n"); + + BT_MBOX_PRINT(0, LE_SLAVE_LAT, false); + BT_MBOX_PRINT(0, LE_PROF1, false); + BT_MBOX_PRINT(0, LE_PROF2, false); + BT_MBOX_PRINT(0, LE_PROF_OTHER, false); + BT_MBOX_PRINT(0, CHL_SEQ_N, false); + BT_MBOX_PRINT(0, INBAND_S, false); + BT_MBOX_PRINT(0, LE_MIN_RSSI, false); + BT_MBOX_PRINT(0, LE_SCAN, false); + BT_MBOX_PRINT(0, LE_ADV, false); + BT_MBOX_PRINT(0, LE_MAX_TX_POWER, false); + BT_MBOX_PRINT(0, OPEN_CON_1, true); + + pos += scnprintf(buf+pos, bufsz-pos, "MBOX dw1:\n"); + + BT_MBOX_PRINT(1, BR_MAX_TX_POWER, false); + BT_MBOX_PRINT(1, IP_SR, false); + BT_MBOX_PRINT(1, LE_MSTR, false); + BT_MBOX_PRINT(1, AGGR_TRFC_LD, false); + BT_MBOX_PRINT(1, MSG_TYPE, false); + BT_MBOX_PRINT(1, SSN, true); + + pos += scnprintf(buf+pos, bufsz-pos, "MBOX dw2:\n"); + + BT_MBOX_PRINT(2, SNIFF_ACT, false); + BT_MBOX_PRINT(2, PAG, false); + BT_MBOX_PRINT(2, INQUIRY, false); + BT_MBOX_PRINT(2, CONN, false); + BT_MBOX_PRINT(2, SNIFF_INTERVAL, false); + BT_MBOX_PRINT(2, DISC, false); + BT_MBOX_PRINT(2, SCO_TX_ACT, false); + BT_MBOX_PRINT(2, SCO_RX_ACT, false); + BT_MBOX_PRINT(2, ESCO_RE_TX, false); + BT_MBOX_PRINT(2, SCO_DURATION, true); + + pos += scnprintf(buf+pos, bufsz-pos, "MBOX dw3:\n"); + + BT_MBOX_PRINT(3, SCO_STATE, false); + BT_MBOX_PRINT(3, SNIFF_STATE, false); + BT_MBOX_PRINT(3, A2DP_STATE, false); + BT_MBOX_PRINT(3, ACL_STATE, false); + BT_MBOX_PRINT(3, MSTR_STATE, false); + BT_MBOX_PRINT(3, OBX_STATE, false); + BT_MBOX_PRINT(3, OPEN_CON_2, false); + BT_MBOX_PRINT(3, TRAFFIC_LOAD, false); + BT_MBOX_PRINT(3, CHL_SEQN_LSB, false); + BT_MBOX_PRINT(3, INBAND_P, false); + BT_MBOX_PRINT(3, MSG_TYPE_2, false); + BT_MBOX_PRINT(3, SSN_2, false); + BT_MBOX_PRINT(3, UPDATE_REQUEST, true); + + pos += scnprintf(buf+pos, bufsz-pos, "bt_status = %d\n", + notif->bt_status); + pos += scnprintf(buf+pos, bufsz-pos, "bt_open_conn = %d\n", + notif->bt_open_conn); + pos += scnprintf(buf+pos, bufsz-pos, "bt_traffic_load = %d\n", + notif->bt_traffic_load); + pos += scnprintf(buf+pos, bufsz-pos, "bt_agg_traffic_load = %d\n", + notif->bt_agg_traffic_load); + pos += scnprintf(buf+pos, bufsz-pos, "bt_ci_compliance = %d\n", + notif->bt_ci_compliance); + + mutex_unlock(&mvm->mutex); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + + return ret; +} +#undef BT_MBOX_PRINT + +static ssize_t iwl_dbgfs_fw_restart_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_mvm *mvm = file->private_data; + bool restart_fw = iwlwifi_mod_params.restart_fw; + int ret; + + iwlwifi_mod_params.restart_fw = true; + + mutex_lock(&mvm->mutex); + + /* take the return value to make compiler happy - it will fail anyway */ + ret = iwl_mvm_send_cmd_pdu(mvm, REPLY_ERROR, CMD_SYNC, 0, NULL); + + mutex_unlock(&mvm->mutex); + + iwlwifi_mod_params.restart_fw = restart_fw; + + return count; +} + +#define MVM_DEBUGFS_READ_FILE_OPS(name) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .read = iwl_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +} + +#define MVM_DEBUGFS_READ_WRITE_FILE_OPS(name) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .write = iwl_dbgfs_##name##_write, \ + .read = iwl_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define MVM_DEBUGFS_WRITE_FILE_OPS(name) \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .write = iwl_dbgfs_##name##_write, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define MVM_DEBUGFS_ADD_FILE(name, parent, mode) do { \ + if (!debugfs_create_file(#name, mode, parent, mvm, \ + &iwl_dbgfs_##name##_ops)) \ + goto err; \ + } while (0) + +#define MVM_DEBUGFS_ADD_FILE_VIF(name, parent, mode) do { \ + if (!debugfs_create_file(#name, mode, parent, vif, \ + &iwl_dbgfs_##name##_ops)) \ + goto err; \ + } while (0) + +/* Device wide debugfs entries */ +MVM_DEBUGFS_WRITE_FILE_OPS(tx_flush); +MVM_DEBUGFS_WRITE_FILE_OPS(sta_drain); +MVM_DEBUGFS_READ_WRITE_FILE_OPS(sram); +MVM_DEBUGFS_READ_FILE_OPS(stations); +MVM_DEBUGFS_READ_FILE_OPS(bt_notif); +MVM_DEBUGFS_WRITE_FILE_OPS(power_down_allow); +MVM_DEBUGFS_WRITE_FILE_OPS(power_down_d3_allow); +MVM_DEBUGFS_WRITE_FILE_OPS(fw_restart); + +/* Interface specific debugfs entries */ +MVM_DEBUGFS_READ_FILE_OPS(mac_params); + +int iwl_mvm_dbgfs_register(struct iwl_mvm *mvm, struct dentry *dbgfs_dir) +{ + char buf[100]; + + mvm->debugfs_dir = dbgfs_dir; + + MVM_DEBUGFS_ADD_FILE(tx_flush, mvm->debugfs_dir, S_IWUSR); + MVM_DEBUGFS_ADD_FILE(sta_drain, mvm->debugfs_dir, S_IWUSR); + MVM_DEBUGFS_ADD_FILE(sram, mvm->debugfs_dir, S_IWUSR | S_IRUSR); + MVM_DEBUGFS_ADD_FILE(stations, dbgfs_dir, S_IRUSR); + MVM_DEBUGFS_ADD_FILE(bt_notif, dbgfs_dir, S_IRUSR); + MVM_DEBUGFS_ADD_FILE(power_down_allow, mvm->debugfs_dir, S_IWUSR); + MVM_DEBUGFS_ADD_FILE(power_down_d3_allow, mvm->debugfs_dir, S_IWUSR); + MVM_DEBUGFS_ADD_FILE(fw_restart, mvm->debugfs_dir, S_IWUSR); + + /* + * Create a symlink with mac80211. It will be removed when mac80211 + * exists (before the opmode exists which removes the target.) + */ + snprintf(buf, 100, "../../%s/%s", + dbgfs_dir->d_parent->d_parent->d_name.name, + dbgfs_dir->d_parent->d_name.name); + if (!debugfs_create_symlink("iwlwifi", mvm->hw->wiphy->debugfsdir, buf)) + goto err; + + return 0; +err: + IWL_ERR(mvm, "Can't create the mvm debugfs directory\n"); + return -ENOMEM; +} + +void iwl_mvm_vif_dbgfs_register(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct dentry *dbgfs_dir = vif->debugfs_dir; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + char buf[100]; + + if (!dbgfs_dir) + return; + + mvmvif->dbgfs_dir = debugfs_create_dir("iwlmvm", dbgfs_dir); + mvmvif->dbgfs_data = mvm; + + if (!mvmvif->dbgfs_dir) { + IWL_ERR(mvm, "Failed to create debugfs directory under %s\n", + dbgfs_dir->d_name.name); + return; + } + + MVM_DEBUGFS_ADD_FILE_VIF(mac_params, mvmvif->dbgfs_dir, + S_IRUSR); + + /* + * Create symlink for convenience pointing to interface specific + * debugfs entries for the driver. For example, under + * /sys/kernel/debug/iwlwifi/0000\:02\:00.0/iwlmvm/ + * find + * netdev:wlan0 -> ../../../ieee80211/phy0/netdev:wlan0/iwlmvm/ + */ + snprintf(buf, 100, "../../../%s/%s/%s/%s", + dbgfs_dir->d_parent->d_parent->d_name.name, + dbgfs_dir->d_parent->d_name.name, + dbgfs_dir->d_name.name, + mvmvif->dbgfs_dir->d_name.name); + + mvmvif->dbgfs_slink = debugfs_create_symlink(dbgfs_dir->d_name.name, + mvm->debugfs_dir, buf); + if (!mvmvif->dbgfs_slink) + IWL_ERR(mvm, "Can't create debugfs symbolic link under %s\n", + dbgfs_dir->d_name.name); + return; +err: + IWL_ERR(mvm, "Can't create debugfs entity\n"); +} + +void iwl_mvm_vif_dbgfs_clean(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + debugfs_remove(mvmvif->dbgfs_slink); + mvmvif->dbgfs_slink = NULL; + + debugfs_remove_recursive(mvmvif->dbgfs_dir); + mvmvif->dbgfs_dir = NULL; +} diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api-bt-coex.h b/drivers/net/wireless/iwlwifi/mvm/fw-api-bt-coex.h new file mode 100644 index 0000000..05c61d6 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api-bt-coex.h @@ -0,0 +1,319 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef __fw_api_bt_coex_h__ +#define __fw_api_bt_coex_h__ + +#include +#include + +#define BITS(nb) (BIT(nb) - 1) + +/** + * enum iwl_bt_coex_flags - flags for BT_COEX command + * @BT_CH_PRIMARY_EN: + * @BT_CH_SECONDARY_EN: + * @BT_NOTIF_COEX_OFF: + * @BT_COEX_MODE_POS: + * @BT_COEX_MODE_MSK: + * @BT_COEX_DISABLE: + * @BT_COEX_2W: + * @BT_COEX_3W: + * @BT_COEX_NW: + * @BT_USE_DEFAULTS: + * @BT_SYNC_2_BT_DISABLE: + * @BT_COEX_CORUNNING_TBL_EN: + */ +enum iwl_bt_coex_flags { + BT_CH_PRIMARY_EN = BIT(0), + BT_CH_SECONDARY_EN = BIT(1), + BT_NOTIF_COEX_OFF = BIT(2), + BT_COEX_MODE_POS = 3, + BT_COEX_MODE_MSK = BITS(3) << BT_COEX_MODE_POS, + BT_COEX_DISABLE = 0x0 << BT_COEX_MODE_POS, + BT_COEX_2W = 0x1 << BT_COEX_MODE_POS, + BT_COEX_3W = 0x2 << BT_COEX_MODE_POS, + BT_COEX_NW = 0x3 << BT_COEX_MODE_POS, + BT_USE_DEFAULTS = BIT(6), + BT_SYNC_2_BT_DISABLE = BIT(7), + /* + * For future use - when the flags will be enlarged + * BT_COEX_CORUNNING_TBL_EN = BIT(8), + */ +}; + +/* + * indicates what has changed in the BT_COEX command. + */ +enum iwl_bt_coex_valid_bit_msk { + BT_VALID_ENABLE = BIT(0), + BT_VALID_BT_PRIO_BOOST = BIT(1), + BT_VALID_MAX_KILL = BIT(2), + BT_VALID_3W_TMRS = BIT(3), + BT_VALID_KILL_ACK = BIT(4), + BT_VALID_KILL_CTS = BIT(5), + BT_VALID_REDUCED_TX_POWER = BIT(6), + BT_VALID_LUT = BIT(7), + BT_VALID_WIFI_RX_SW_PRIO_BOOST = BIT(8), + BT_VALID_WIFI_TX_SW_PRIO_BOOST = BIT(9), + BT_VALID_MULTI_PRIO_LUT = BIT(10), + BT_VALID_TRM_KICK_FILTER = BIT(11), + BT_VALID_CORUN_LUT_20 = BIT(12), + BT_VALID_CORUN_LUT_40 = BIT(13), + BT_VALID_ANT_ISOLATION = BIT(14), + BT_VALID_ANT_ISOLATION_THRS = BIT(15), + /* + * For future use - when the valid flags will be enlarged + * BT_VALID_TXTX_DELTA_FREQ_THRS = BIT(16), + * BT_VALID_TXRX_MAX_FREQ_0 = BIT(17), + */ +}; + +/** + * enum iwl_bt_reduced_tx_power - allows to reduce txpower for WiFi frames. + * @BT_REDUCED_TX_POWER_CTL: reduce Tx power for control frames + * @BT_REDUCED_TX_POWER_DATA: reduce Tx power for data frames + * + * This mechanism allows to have BT and WiFi run concurrently. Since WiFi + * reduces its Tx power, it can work along with BT, hence reducing the amount + * of WiFi frames being killed by BT. + */ +enum iwl_bt_reduced_tx_power { + BT_REDUCED_TX_POWER_CTL = BIT(0), + BT_REDUCED_TX_POWER_DATA = BIT(1), +}; + +#define BT_COEX_LUT_SIZE (12) + +/** + * struct iwl_bt_coex_cmd - bt coex configuration command + * @flags:&enum iwl_bt_coex_flags + * @lead_time: + * @max_kill: + * @bt3_time_t7_value: + * @kill_ack_msk: + * @kill_cts_msk: + * @bt3_prio_sample_time: + * @bt3_timer_t2_value: + * @bt4_reaction_time: + * @decision_lut[12]: + * @bt_reduced_tx_power: enum %iwl_bt_reduced_tx_power + * @valid_bit_msk: enum %iwl_bt_coex_valid_bit_msk + * @bt_prio_boost: values for PTA boost register + * @wifi_tx_prio_boost: SW boost of wifi tx priority + * @wifi_rx_prio_boost: SW boost of wifi rx priority + * + * The structure is used for the BT_COEX command. + */ +struct iwl_bt_coex_cmd { + u8 flags; + u8 lead_time; + u8 max_kill; + u8 bt3_time_t7_value; + __le32 kill_ack_msk; + __le32 kill_cts_msk; + u8 bt3_prio_sample_time; + u8 bt3_timer_t2_value; + __le16 bt4_reaction_time; + __le32 decision_lut[BT_COEX_LUT_SIZE]; + u8 bt_reduced_tx_power; + u8 reserved; + __le16 valid_bit_msk; + __le32 bt_prio_boost; + u8 reserved2; + u8 wifi_tx_prio_boost; + __le16 wifi_rx_prio_boost; +} __packed; /* BT_COEX_CMD_API_S_VER_3 */ + +#define BT_MBOX(n_dw, _msg, _pos, _nbits) \ + BT_MBOX##n_dw##_##_msg##_POS = (_pos), \ + BT_MBOX##n_dw##_##_msg = BITS(_nbits) << BT_MBOX##n_dw##_##_msg##_POS + +enum iwl_bt_mxbox_dw0 { + BT_MBOX(0, LE_SLAVE_LAT, 0, 3), + BT_MBOX(0, LE_PROF1, 3, 1), + BT_MBOX(0, LE_PROF2, 4, 1), + BT_MBOX(0, LE_PROF_OTHER, 5, 1), + BT_MBOX(0, CHL_SEQ_N, 8, 4), + BT_MBOX(0, INBAND_S, 13, 1), + BT_MBOX(0, LE_MIN_RSSI, 16, 4), + BT_MBOX(0, LE_SCAN, 20, 1), + BT_MBOX(0, LE_ADV, 21, 1), + BT_MBOX(0, LE_MAX_TX_POWER, 24, 4), + BT_MBOX(0, OPEN_CON_1, 28, 2), +}; + +enum iwl_bt_mxbox_dw1 { + BT_MBOX(1, BR_MAX_TX_POWER, 0, 4), + BT_MBOX(1, IP_SR, 4, 1), + BT_MBOX(1, LE_MSTR, 5, 1), + BT_MBOX(1, AGGR_TRFC_LD, 8, 6), + BT_MBOX(1, MSG_TYPE, 16, 3), + BT_MBOX(1, SSN, 19, 2), +}; + +enum iwl_bt_mxbox_dw2 { + BT_MBOX(2, SNIFF_ACT, 0, 3), + BT_MBOX(2, PAG, 3, 1), + BT_MBOX(2, INQUIRY, 4, 1), + BT_MBOX(2, CONN, 5, 1), + BT_MBOX(2, SNIFF_INTERVAL, 8, 5), + BT_MBOX(2, DISC, 13, 1), + BT_MBOX(2, SCO_TX_ACT, 16, 2), + BT_MBOX(2, SCO_RX_ACT, 18, 2), + BT_MBOX(2, ESCO_RE_TX, 20, 2), + BT_MBOX(2, SCO_DURATION, 24, 6), +}; + +enum iwl_bt_mxbox_dw3 { + BT_MBOX(3, SCO_STATE, 0, 1), + BT_MBOX(3, SNIFF_STATE, 1, 1), + BT_MBOX(3, A2DP_STATE, 2, 1), + BT_MBOX(3, ACL_STATE, 3, 1), + BT_MBOX(3, MSTR_STATE, 4, 1), + BT_MBOX(3, OBX_STATE, 5, 1), + BT_MBOX(3, OPEN_CON_2, 8, 2), + BT_MBOX(3, TRAFFIC_LOAD, 10, 2), + BT_MBOX(3, CHL_SEQN_LSB, 12, 1), + BT_MBOX(3, INBAND_P, 13, 1), + BT_MBOX(3, MSG_TYPE_2, 16, 3), + BT_MBOX(3, SSN_2, 19, 2), + BT_MBOX(3, UPDATE_REQUEST, 21, 1), +}; + +#define BT_MBOX_MSG(_notif, _num, _field) \ + ((le32_to_cpu((_notif)->mbox_msg[(_num)]) & BT_MBOX##_num##_##_field)\ + >> BT_MBOX##_num##_##_field##_POS) + +/** + * struct iwl_bt_coex_profile_notif - notification about BT coex + * @mbox_msg: message from BT to WiFi + * @:bt_status: 0 - off, 1 - on + * @:bt_open_conn: number of BT connections open + * @:bt_traffic_load: load of BT traffic + * @:bt_agg_traffic_load: aggregated load of BT traffic + * @:bt_ci_compliance: 0 - no CI compliance, 1 - CI compliant + */ +struct iwl_bt_coex_profile_notif { + __le32 mbox_msg[4]; + u8 bt_status; + u8 bt_open_conn; + u8 bt_traffic_load; + u8 bt_agg_traffic_load; + u8 bt_ci_compliance; + u8 reserved[3]; +} __packed; /* BT_COEX_PROFILE_NTFY_API_S_VER_2 */ + +enum iwl_bt_coex_prio_table_event { + BT_COEX_PRIO_TBL_EVT_INIT_CALIB1 = 0, + BT_COEX_PRIO_TBL_EVT_INIT_CALIB2 = 1, + BT_COEX_PRIO_TBL_EVT_PERIODIC_CALIB_LOW1 = 2, + BT_COEX_PRIO_TBL_EVT_PERIODIC_CALIB_LOW2 = 3, + BT_COEX_PRIO_TBL_EVT_PERIODIC_CALIB_HIGH1 = 4, + BT_COEX_PRIO_TBL_EVT_PERIODIC_CALIB_HIGH2 = 5, + BT_COEX_PRIO_TBL_EVT_DTIM = 6, + BT_COEX_PRIO_TBL_EVT_SCAN52 = 7, + BT_COEX_PRIO_TBL_EVT_SCAN24 = 8, + BT_COEX_PRIO_TBL_EVT_IDLE = 9, + BT_COEX_PRIO_TBL_EVT_MAX = 16, +}; /* BT_COEX_PRIO_TABLE_EVENTS_API_E_VER_1 */ + +enum iwl_bt_coex_prio_table_prio { + BT_COEX_PRIO_TBL_DISABLED = 0, + BT_COEX_PRIO_TBL_PRIO_LOW = 1, + BT_COEX_PRIO_TBL_PRIO_HIGH = 2, + BT_COEX_PRIO_TBL_PRIO_BYPASS = 3, + BT_COEX_PRIO_TBL_PRIO_COEX_OFF = 4, + BT_COEX_PRIO_TBL_PRIO_COEX_ON = 5, + BT_COEX_PRIO_TBL_PRIO_COEX_IDLE = 6, + BT_COEX_PRIO_TBL_MAX = 8, +}; /* BT_COEX_PRIO_TABLE_PRIORITIES_API_E_VER_1 */ + +#define BT_COEX_PRIO_TBL_SHRD_ANT_POS (0) +#define BT_COEX_PRIO_TBL_PRIO_POS (1) +#define BT_COEX_PRIO_TBL_RESERVED_POS (4) + +/** + * struct iwl_bt_coex_prio_tbl_cmd - priority table for BT coex + * @prio_tbl: + */ +struct iwl_bt_coex_prio_tbl_cmd { + u8 prio_tbl[BT_COEX_PRIO_TBL_EVT_MAX]; +} __packed; + +enum iwl_bt_coex_env_action { + BT_COEX_ENV_CLOSE = 0, + BT_COEX_ENV_OPEN = 1, +}; /* BT_COEX_PROT_ENV_ACTION_API_E_VER_1 */ + +/** + * struct iwl_bt_coex_prot_env_cmd - BT Protection Envelope + * @action: enum %iwl_bt_coex_env_action + * @type: enum %iwl_bt_coex_prio_table_event + */ +struct iwl_bt_coex_prot_env_cmd { + u8 action; /* 0 = closed, 1 = open */ + u8 type; /* 0 .. 15 */ + u8 reserved[2]; +} __packed; + +#endif /* __fw_api_bt_coex_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api-d3.h b/drivers/net/wireless/iwlwifi/mvm/fw-api-d3.h new file mode 100644 index 0000000..6f8b2c1 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api-d3.h @@ -0,0 +1,333 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef __fw_api_d3_h__ +#define __fw_api_d3_h__ + +/** + * enum iwl_d3_wakeup_flags - D3 manager wakeup flags + * @IWL_WAKEUP_D3_CONFIG_FW_ERROR: wake up on firmware sysassert + */ +enum iwl_d3_wakeup_flags { + IWL_WAKEUP_D3_CONFIG_FW_ERROR = BIT(0), +}; /* D3_MANAGER_WAKEUP_CONFIG_API_E_VER_3 */ + +/** + * struct iwl_d3_manager_config - D3 manager configuration command + * @min_sleep_time: minimum sleep time (in usec) + * @wakeup_flags: wakeup flags, see &enum iwl_d3_wakeup_flags + * @wakeup_host_timer: force wakeup after this many seconds + * + * The structure is used for the D3_CONFIG_CMD command. + */ +struct iwl_d3_manager_config { + __le32 min_sleep_time; + __le32 wakeup_flags; + __le32 wakeup_host_timer; +} __packed; /* D3_MANAGER_CONFIG_CMD_S_VER_4 */ + + +/* TODO: OFFLOADS_QUERY_API_S_VER_1 */ + +/** + * enum iwl_d3_proto_offloads - enabled protocol offloads + * @IWL_D3_PROTO_OFFLOAD_ARP: ARP data is enabled + * @IWL_D3_PROTO_OFFLOAD_NS: NS (Neighbor Solicitation) is enabled + */ +enum iwl_proto_offloads { + IWL_D3_PROTO_OFFLOAD_ARP = BIT(0), + IWL_D3_PROTO_OFFLOAD_NS = BIT(1), +}; + +#define IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS 2 + +/** + * struct iwl_proto_offload_cmd - ARP/NS offload configuration + * @enabled: enable flags + * @remote_ipv4_addr: remote address to answer to (or zero if all) + * @host_ipv4_addr: our IPv4 address to respond to queries for + * @arp_mac_addr: our MAC address for ARP responses + * @remote_ipv6_addr: remote address to answer to (or zero if all) + * @solicited_node_ipv6_addr: broken -- solicited node address exists + * for each target address + * @target_ipv6_addr: our target addresses + * @ndp_mac_addr: neighbor soliciation response MAC address + */ +struct iwl_proto_offload_cmd { + __le32 enabled; + __be32 remote_ipv4_addr; + __be32 host_ipv4_addr; + u8 arp_mac_addr[ETH_ALEN]; + __le16 reserved1; + + u8 remote_ipv6_addr[16]; + u8 solicited_node_ipv6_addr[16]; + u8 target_ipv6_addr[IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS][16]; + u8 ndp_mac_addr[ETH_ALEN]; + __le16 reserved2; +} __packed; /* PROT_OFFLOAD_CONFIG_CMD_DB_S_VER_1 */ + + +/* + * WOWLAN_PATTERNS + */ +#define IWL_WOWLAN_MIN_PATTERN_LEN 16 +#define IWL_WOWLAN_MAX_PATTERN_LEN 128 + +struct iwl_wowlan_pattern { + u8 mask[IWL_WOWLAN_MAX_PATTERN_LEN / 8]; + u8 pattern[IWL_WOWLAN_MAX_PATTERN_LEN]; + u8 mask_size; + u8 pattern_size; + __le16 reserved; +} __packed; /* WOWLAN_PATTERN_API_S_VER_1 */ + +#define IWL_WOWLAN_MAX_PATTERNS 20 + +struct iwl_wowlan_patterns_cmd { + __le32 n_patterns; + struct iwl_wowlan_pattern patterns[]; +} __packed; /* WOWLAN_PATTERN_ARRAY_API_S_VER_1 */ + +enum iwl_wowlan_wakeup_filters { + IWL_WOWLAN_WAKEUP_MAGIC_PACKET = BIT(0), + IWL_WOWLAN_WAKEUP_PATTERN_MATCH = BIT(1), + IWL_WOWLAN_WAKEUP_BEACON_MISS = BIT(2), + IWL_WOWLAN_WAKEUP_LINK_CHANGE = BIT(3), + IWL_WOWLAN_WAKEUP_GTK_REKEY_FAIL = BIT(4), + IWL_WOWLAN_WAKEUP_EAP_IDENT_REQ = BIT(5), + IWL_WOWLAN_WAKEUP_4WAY_HANDSHAKE = BIT(6), + IWL_WOWLAN_WAKEUP_ENABLE_NET_DETECT = BIT(7), + IWL_WOWLAN_WAKEUP_RF_KILL_DEASSERT = BIT(8), + IWL_WOWLAN_WAKEUP_REMOTE_LINK_LOSS = BIT(9), + IWL_WOWLAN_WAKEUP_REMOTE_SIGNATURE_TABLE = BIT(10), + /* BIT(11) reserved */ + IWL_WOWLAN_WAKEUP_REMOTE_WAKEUP_PACKET = BIT(12), +}; /* WOWLAN_WAKEUP_FILTER_API_E_VER_4 */ + +struct iwl_wowlan_config_cmd { + __le32 wakeup_filter; + __le16 non_qos_seq; + __le16 qos_seq[8]; + u8 wowlan_ba_teardown_tids; + u8 is_11n_connection; +} __packed; /* WOWLAN_CONFIG_API_S_VER_2 */ + +/* + * WOWLAN_TSC_RSC_PARAMS + */ +#define IWL_NUM_RSC 16 + +struct tkip_sc { + __le16 iv16; + __le16 pad; + __le32 iv32; +} __packed; /* TKIP_SC_API_U_VER_1 */ + +struct iwl_tkip_rsc_tsc { + struct tkip_sc unicast_rsc[IWL_NUM_RSC]; + struct tkip_sc multicast_rsc[IWL_NUM_RSC]; + struct tkip_sc tsc; +} __packed; /* TKIP_TSC_RSC_API_S_VER_1 */ + +struct aes_sc { + __le64 pn; +} __packed; /* TKIP_AES_SC_API_U_VER_1 */ + +struct iwl_aes_rsc_tsc { + struct aes_sc unicast_rsc[IWL_NUM_RSC]; + struct aes_sc multicast_rsc[IWL_NUM_RSC]; + struct aes_sc tsc; +} __packed; /* AES_TSC_RSC_API_S_VER_1 */ + +union iwl_all_tsc_rsc { + struct iwl_tkip_rsc_tsc tkip; + struct iwl_aes_rsc_tsc aes; +}; /* ALL_TSC_RSC_API_S_VER_2 */ + +struct iwl_wowlan_rsc_tsc_params_cmd { + union iwl_all_tsc_rsc all_tsc_rsc; +} __packed; /* ALL_TSC_RSC_API_S_VER_2 */ + +#define IWL_MIC_KEY_SIZE 8 +struct iwl_mic_keys { + u8 tx[IWL_MIC_KEY_SIZE]; + u8 rx_unicast[IWL_MIC_KEY_SIZE]; + u8 rx_mcast[IWL_MIC_KEY_SIZE]; +} __packed; /* MIC_KEYS_API_S_VER_1 */ + +#define IWL_P1K_SIZE 5 +struct iwl_p1k_cache { + __le16 p1k[IWL_P1K_SIZE]; +} __packed; + +#define IWL_NUM_RX_P1K_CACHE 2 + +struct iwl_wowlan_tkip_params_cmd { + struct iwl_mic_keys mic_keys; + struct iwl_p1k_cache tx; + struct iwl_p1k_cache rx_uni[IWL_NUM_RX_P1K_CACHE]; + struct iwl_p1k_cache rx_multi[IWL_NUM_RX_P1K_CACHE]; +} __packed; /* WOWLAN_TKIP_SETTING_API_S_VER_1 */ + +#define IWL_KCK_MAX_SIZE 32 +#define IWL_KEK_MAX_SIZE 32 + +struct iwl_wowlan_kek_kck_material_cmd { + u8 kck[IWL_KCK_MAX_SIZE]; + u8 kek[IWL_KEK_MAX_SIZE]; + __le16 kck_len; + __le16 kek_len; + __le64 replay_ctr; +} __packed; /* KEK_KCK_MATERIAL_API_S_VER_2 */ + +#define RF_KILL_INDICATOR_FOR_WOWLAN 0x87 + +enum iwl_wowlan_rekey_status { + IWL_WOWLAN_REKEY_POST_REKEY = 0, + IWL_WOWLAN_REKEY_WHILE_REKEY = 1, +}; /* WOWLAN_REKEY_STATUS_API_E_VER_1 */ + +enum iwl_wowlan_wakeup_reason { + IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS = 0, + IWL_WOWLAN_WAKEUP_BY_MAGIC_PACKET = BIT(0), + IWL_WOWLAN_WAKEUP_BY_PATTERN = BIT(1), + IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_MISSED_BEACON = BIT(2), + IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_DEAUTH = BIT(3), + IWL_WOWLAN_WAKEUP_BY_GTK_REKEY_FAILURE = BIT(4), + IWL_WOWLAN_WAKEUP_BY_RFKILL_DEASSERTED = BIT(5), + IWL_WOWLAN_WAKEUP_BY_UCODE_ERROR = BIT(6), + IWL_WOWLAN_WAKEUP_BY_EAPOL_REQUEST = BIT(7), + IWL_WOWLAN_WAKEUP_BY_FOUR_WAY_HANDSHAKE = BIT(8), + IWL_WOWLAN_WAKEUP_BY_REM_WAKE_LINK_LOSS = BIT(9), + IWL_WOWLAN_WAKEUP_BY_REM_WAKE_SIGNATURE_TABLE = BIT(10), + /* BIT(11) reserved */ + IWL_WOWLAN_WAKEUP_BY_REM_WAKE_WAKEUP_PACKET = BIT(12), +}; /* WOWLAN_WAKE_UP_REASON_API_E_VER_2 */ + +struct iwl_wowlan_status { + __le64 replay_ctr; + __le16 pattern_number; + __le16 non_qos_seq_ctr; + __le16 qos_seq_ctr[8]; + __le32 wakeup_reasons; + __le32 rekey_status; + __le32 num_of_gtk_rekeys; + __le32 transmitted_ndps; + __le32 received_beacons; + __le32 wake_packet_length; + __le32 wake_packet_bufsize; + u8 wake_packet[]; /* can be truncated from _length to _bufsize */ +} __packed; /* WOWLAN_STATUSES_API_S_VER_4 */ + +#define IWL_WOWLAN_TCP_MAX_PACKET_LEN 64 +#define IWL_WOWLAN_REMOTE_WAKE_MAX_PACKET_LEN 128 +#define IWL_WOWLAN_REMOTE_WAKE_MAX_TOKENS 2048 + +struct iwl_tcp_packet_info { + __le16 tcp_pseudo_header_checksum; + __le16 tcp_payload_length; +} __packed; /* TCP_PACKET_INFO_API_S_VER_2 */ + +struct iwl_tcp_packet { + struct iwl_tcp_packet_info info; + u8 rx_mask[IWL_WOWLAN_MAX_PATTERN_LEN / 8]; + u8 data[IWL_WOWLAN_TCP_MAX_PACKET_LEN]; +} __packed; /* TCP_PROTOCOL_PACKET_API_S_VER_1 */ + +struct iwl_remote_wake_packet { + struct iwl_tcp_packet_info info; + u8 rx_mask[IWL_WOWLAN_MAX_PATTERN_LEN / 8]; + u8 data[IWL_WOWLAN_REMOTE_WAKE_MAX_PACKET_LEN]; +} __packed; /* TCP_PROTOCOL_PACKET_API_S_VER_1 */ + +struct iwl_wowlan_remote_wake_config { + __le32 connection_max_time; /* unused */ + /* TCP_PROTOCOL_CONFIG_API_S_VER_1 */ + u8 max_syn_retries; + u8 max_data_retries; + u8 tcp_syn_ack_timeout; + u8 tcp_ack_timeout; + + struct iwl_tcp_packet syn_tx; + struct iwl_tcp_packet synack_rx; + struct iwl_tcp_packet keepalive_ack_rx; + struct iwl_tcp_packet fin_tx; + + struct iwl_remote_wake_packet keepalive_tx; + struct iwl_remote_wake_packet wake_rx; + + /* REMOTE_WAKE_OFFSET_INFO_API_S_VER_1 */ + u8 sequence_number_offset; + u8 sequence_number_length; + u8 token_offset; + u8 token_length; + /* REMOTE_WAKE_PROTOCOL_PARAMS_API_S_VER_1 */ + __le32 initial_sequence_number; + __le16 keepalive_interval; + __le16 num_tokens; + u8 tokens[IWL_WOWLAN_REMOTE_WAKE_MAX_TOKENS]; +} __packed; /* REMOTE_WAKE_CONFIG_API_S_VER_2 */ + +/* TODO: NetDetect API */ + +#endif /* __fw_api_d3_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api-mac.h b/drivers/net/wireless/iwlwifi/mvm/fw-api-mac.h new file mode 100644 index 0000000..98b1feb --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api-mac.h @@ -0,0 +1,375 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef __fw_api_mac_h__ +#define __fw_api_mac_h__ + +/* + * The first MAC indices (starting from 0) + * are available to the driver, AUX follows + */ +#define MAC_INDEX_AUX 4 +#define MAC_INDEX_MIN_DRIVER 0 +#define NUM_MAC_INDEX_DRIVER MAC_INDEX_AUX + +enum iwl_ac { + AC_BK, + AC_BE, + AC_VI, + AC_VO, + AC_NUM, +}; + +/** + * enum iwl_mac_protection_flags - MAC context flags + * @MAC_PROT_FLG_TGG_PROTECT: 11g protection when transmitting OFDM frames, + * this will require CCK RTS/CTS2self. + * RTS/CTS will protect full burst time. + * @MAC_PROT_FLG_HT_PROT: enable HT protection + * @MAC_PROT_FLG_FAT_PROT: protect 40 MHz transmissions + * @MAC_PROT_FLG_SELF_CTS_EN: allow CTS2self + */ +enum iwl_mac_protection_flags { + MAC_PROT_FLG_TGG_PROTECT = BIT(3), + MAC_PROT_FLG_HT_PROT = BIT(23), + MAC_PROT_FLG_FAT_PROT = BIT(24), + MAC_PROT_FLG_SELF_CTS_EN = BIT(30), +}; + +#define MAC_FLG_SHORT_SLOT BIT(4) +#define MAC_FLG_SHORT_PREAMBLE BIT(5) + +/** + * enum iwl_mac_types - Supported MAC types + * @FW_MAC_TYPE_FIRST: lowest supported MAC type + * @FW_MAC_TYPE_AUX: Auxiliary MAC (internal) + * @FW_MAC_TYPE_LISTENER: monitor MAC type (?) + * @FW_MAC_TYPE_PIBSS: Pseudo-IBSS + * @FW_MAC_TYPE_IBSS: IBSS + * @FW_MAC_TYPE_BSS_STA: BSS (managed) station + * @FW_MAC_TYPE_P2P_DEVICE: P2P Device + * @FW_MAC_TYPE_P2P_STA: P2P client + * @FW_MAC_TYPE_GO: P2P GO + * @FW_MAC_TYPE_TEST: ? + * @FW_MAC_TYPE_MAX: highest support MAC type + */ +enum iwl_mac_types { + FW_MAC_TYPE_FIRST = 1, + FW_MAC_TYPE_AUX = FW_MAC_TYPE_FIRST, + FW_MAC_TYPE_LISTENER, + FW_MAC_TYPE_PIBSS, + FW_MAC_TYPE_IBSS, + FW_MAC_TYPE_BSS_STA, + FW_MAC_TYPE_P2P_DEVICE, + FW_MAC_TYPE_P2P_STA, + FW_MAC_TYPE_GO, + FW_MAC_TYPE_TEST, + FW_MAC_TYPE_MAX = FW_MAC_TYPE_TEST +}; /* MAC_CONTEXT_TYPE_API_E_VER_1 */ + +/** + * enum iwl_tsf_id - TSF hw timer ID + * @TSF_ID_A: use TSF A + * @TSF_ID_B: use TSF B + * @TSF_ID_C: use TSF C + * @TSF_ID_D: use TSF D + * @NUM_TSF_IDS: number of TSF timers available + */ +enum iwl_tsf_id { + TSF_ID_A = 0, + TSF_ID_B = 1, + TSF_ID_C = 2, + TSF_ID_D = 3, + NUM_TSF_IDS = 4, +}; /* TSF_ID_API_E_VER_1 */ + +/** + * struct iwl_mac_data_ap - configuration data for AP MAC context + * @beacon_time: beacon transmit time in system time + * @beacon_tsf: beacon transmit time in TSF + * @bi: beacon interval in TU + * @bi_reciprocal: 2^32 / bi + * @dtim_interval: dtim transmit time in TU + * @dtim_reciprocal: 2^32 / dtim_interval + * @mcast_qid: queue ID for multicast traffic + * @beacon_template: beacon template ID + */ +struct iwl_mac_data_ap { + __le32 beacon_time; + __le64 beacon_tsf; + __le32 bi; + __le32 bi_reciprocal; + __le32 dtim_interval; + __le32 dtim_reciprocal; + __le32 mcast_qid; + __le32 beacon_template; +} __packed; /* AP_MAC_DATA_API_S_VER_1 */ + +/** + * struct iwl_mac_data_ibss - configuration data for IBSS MAC context + * @beacon_time: beacon transmit time in system time + * @beacon_tsf: beacon transmit time in TSF + * @bi: beacon interval in TU + * @bi_reciprocal: 2^32 / bi + */ +struct iwl_mac_data_ibss { + __le32 beacon_time; + __le64 beacon_tsf; + __le32 bi; + __le32 bi_reciprocal; +} __packed; /* IBSS_MAC_DATA_API_S_VER_1 */ + +/** + * struct iwl_mac_data_sta - configuration data for station MAC context + * @is_assoc: 1 for associated state, 0 otherwise + * @dtim_time: DTIM arrival time in system time + * @dtim_tsf: DTIM arrival time in TSF + * @bi: beacon interval in TU, applicable only when associated + * @bi_reciprocal: 2^32 / bi , applicable only when associated + * @dtim_interval: DTIM interval in TU, applicable only when associated + * @dtim_reciprocal: 2^32 / dtim_interval , applicable only when associated + * @listen_interval: in beacon intervals, applicable only when associated + * @assoc_id: unique ID assigned by the AP during association + */ +struct iwl_mac_data_sta { + __le32 is_assoc; + __le32 dtim_time; + __le64 dtim_tsf; + __le32 bi; + __le32 bi_reciprocal; + __le32 dtim_interval; + __le32 dtim_reciprocal; + __le32 listen_interval; + __le32 assoc_id; + __le32 assoc_beacon_arrive_time; +} __packed; /* STA_MAC_DATA_API_S_VER_1 */ + +/** + * struct iwl_mac_data_go - configuration data for P2P GO MAC context + * @ap: iwl_mac_data_ap struct with most config data + * @ctwin: client traffic window in TU (period after TBTT when GO is present). + * 0 indicates that there is no CT window. + * @opp_ps_enabled: indicate that opportunistic PS allowed + */ +struct iwl_mac_data_go { + struct iwl_mac_data_ap ap; + __le32 ctwin; + __le32 opp_ps_enabled; +} __packed; /* GO_MAC_DATA_API_S_VER_1 */ + +/** + * struct iwl_mac_data_p2p_sta - configuration data for P2P client MAC context + * @sta: iwl_mac_data_sta struct with most config data + * @ctwin: client traffic window in TU (period after TBTT when GO is present). + * 0 indicates that there is no CT window. + */ +struct iwl_mac_data_p2p_sta { + struct iwl_mac_data_sta sta; + __le32 ctwin; +} __packed; /* P2P_STA_MAC_DATA_API_S_VER_1 */ + +/** + * struct iwl_mac_data_pibss - Pseudo IBSS config data + * @stats_interval: interval in TU between statistics notifications to host. + */ +struct iwl_mac_data_pibss { + __le32 stats_interval; +} __packed; /* PIBSS_MAC_DATA_API_S_VER_1 */ + +/* + * struct iwl_mac_data_p2p_dev - configuration data for the P2P Device MAC + * context. + * @is_disc_extended: if set to true, P2P Device discoverability is enabled on + * other channels as well. This should be to true only in case that the + * device is discoverable and there is an active GO. Note that setting this + * field when not needed, will increase the number of interrupts and have + * effect on the platform power, as this setting opens the Rx filters on + * all macs. + */ +struct iwl_mac_data_p2p_dev { + __le32 is_disc_extended; +} __packed; /* _P2P_DEV_MAC_DATA_API_S_VER_1 */ + +/** + * enum iwl_mac_filter_flags - MAC context filter flags + * @MAC_FILTER_IN_PROMISC: accept all data frames + * @MAC_FILTER_IN_CONTROL_AND_MGMT: pass all mangement and + * control frames to the host + * @MAC_FILTER_ACCEPT_GRP: accept multicast frames + * @MAC_FILTER_DIS_DECRYPT: don't decrypt unicast frames + * @MAC_FILTER_DIS_GRP_DECRYPT: don't decrypt multicast frames + * @MAC_FILTER_IN_BEACON: transfer foreign BSS's beacons to host + * (in station mode when associated) + * @MAC_FILTER_OUT_BCAST: filter out all broadcast frames + * @MAC_FILTER_IN_CRC32: extract FCS and append it to frames + * @MAC_FILTER_IN_PROBE_REQUEST: pass probe requests to host + */ +enum iwl_mac_filter_flags { + MAC_FILTER_IN_PROMISC = BIT(0), + MAC_FILTER_IN_CONTROL_AND_MGMT = BIT(1), + MAC_FILTER_ACCEPT_GRP = BIT(2), + MAC_FILTER_DIS_DECRYPT = BIT(3), + MAC_FILTER_DIS_GRP_DECRYPT = BIT(4), + MAC_FILTER_IN_BEACON = BIT(6), + MAC_FILTER_OUT_BCAST = BIT(8), + MAC_FILTER_IN_CRC32 = BIT(11), + MAC_FILTER_IN_PROBE_REQUEST = BIT(12), +}; + +/** + * enum iwl_mac_qos_flags - QoS flags + * @MAC_QOS_FLG_UPDATE_EDCA: ? + * @MAC_QOS_FLG_TGN: HT is enabled + * @MAC_QOS_FLG_TXOP_TYPE: ? + * + */ +enum iwl_mac_qos_flags { + MAC_QOS_FLG_UPDATE_EDCA = BIT(0), + MAC_QOS_FLG_TGN = BIT(1), + MAC_QOS_FLG_TXOP_TYPE = BIT(4), +}; + +/** + * struct iwl_ac_qos - QOS timing params for MAC_CONTEXT_CMD + * @cw_min: Contention window, start value in numbers of slots. + * Should be a power-of-2, minus 1. Device's default is 0x0f. + * @cw_max: Contention window, max value in numbers of slots. + * Should be a power-of-2, minus 1. Device's default is 0x3f. + * @aifsn: Number of slots in Arbitration Interframe Space (before + * performing random backoff timing prior to Tx). Device default 1. + * @fifos_mask: FIFOs used by this MAC for this AC + * @edca_txop: Length of Tx opportunity, in uSecs. Device default is 0. + * + * One instance of this config struct for each of 4 EDCA access categories + * in struct iwl_qosparam_cmd. + * + * Device will automatically increase contention window by (2*CW) + 1 for each + * transmission retry. Device uses cw_max as a bit mask, ANDed with new CW + * value, to cap the CW value. + */ +struct iwl_ac_qos { + __le16 cw_min; + __le16 cw_max; + u8 aifsn; + u8 fifos_mask; + __le16 edca_txop; +} __packed; /* AC_QOS_API_S_VER_2 */ + +/** + * struct iwl_mac_ctx_cmd - command structure to configure MAC contexts + * ( MAC_CONTEXT_CMD = 0x28 ) + * @id_and_color: ID and color of the MAC + * @action: action to perform, one of FW_CTXT_ACTION_* + * @mac_type: one of FW_MAC_TYPE_* + * @tsd_id: TSF HW timer, one of TSF_ID_* + * @node_addr: MAC address + * @bssid_addr: BSSID + * @cck_rates: basic rates available for CCK + * @ofdm_rates: basic rates available for OFDM + * @protection_flags: combination of MAC_PROT_FLG_FLAG_* + * @cck_short_preamble: 0x20 for enabling short preamble, 0 otherwise + * @short_slot: 0x10 for enabling short slots, 0 otherwise + * @filter_flags: combination of MAC_FILTER_* + * @qos_flags: from MAC_QOS_FLG_* + * @ac: one iwl_mac_qos configuration for each AC + * @mac_specific: one of struct iwl_mac_data_*, according to mac_type + */ +struct iwl_mac_ctx_cmd { + /* COMMON_INDEX_HDR_API_S_VER_1 */ + __le32 id_and_color; + __le32 action; + /* MAC_CONTEXT_COMMON_DATA_API_S_VER_1 */ + __le32 mac_type; + __le32 tsf_id; + u8 node_addr[6]; + __le16 reserved_for_node_addr; + u8 bssid_addr[6]; + __le16 reserved_for_bssid_addr; + __le32 cck_rates; + __le32 ofdm_rates; + __le32 protection_flags; + __le32 cck_short_preamble; + __le32 short_slot; + __le32 filter_flags; + /* MAC_QOS_PARAM_API_S_VER_1 */ + __le32 qos_flags; + struct iwl_ac_qos ac[AC_NUM+1]; + /* MAC_CONTEXT_COMMON_DATA_API_S */ + union { + struct iwl_mac_data_ap ap; + struct iwl_mac_data_go go; + struct iwl_mac_data_sta sta; + struct iwl_mac_data_p2p_sta p2p_sta; + struct iwl_mac_data_p2p_dev p2p_dev; + struct iwl_mac_data_pibss pibss; + struct iwl_mac_data_ibss ibss; + }; +} __packed; /* MAC_CONTEXT_CMD_API_S_VER_1 */ + +static inline u32 iwl_mvm_reciprocal(u32 v) +{ + if (!v) + return 0; + return 0xFFFFFFFF / v; +} + +#endif /* __fw_api_mac_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api-power.h b/drivers/net/wireless/iwlwifi/mvm/fw-api-power.h new file mode 100644 index 0000000..81fe45f --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api-power.h @@ -0,0 +1,120 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#ifndef __fw_api_power_h__ +#define __fw_api_power_h__ + +/* Power Management Commands, Responses, Notifications */ + +/** + * enum iwl_scan_flags - masks for power table command flags + * @POWER_FLAGS_POWER_SAVE_ENA_MSK: '1' Allow to save power by turning off + * receiver and transmitter. '0' - does not allow. + * @POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK: '0' Driver disables power management, + * '1' Driver enables PM (use rest of parameters) + * @POWER_FLAGS_SKIP_OVER_DTIM_MSK: '0' PM have to walk up every DTIM, + * '1' PM could sleep over DTIM till listen Interval. + * @POWER_FLAGS_ADVANCE_PM_ENA_MSK: Advanced PM (uAPSD) enable mask + * @POWER_FLAGS_LPRX_ENA_MSK: Low Power RX enable. +*/ +enum iwl_power_flags { + POWER_FLAGS_POWER_SAVE_ENA_MSK = BIT(0), + POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK = BIT(1), + POWER_FLAGS_SKIP_OVER_DTIM_MSK = BIT(2), + POWER_FLAGS_ADVANCE_PM_ENA_MSK = BIT(9), + POWER_FLAGS_LPRX_ENA_MSK = BIT(11), +}; + +#define IWL_POWER_VEC_SIZE 5 + +/** + * struct iwl_powertable_cmd - Power Table Command + * POWER_TABLE_CMD = 0x77 (command, has simple generic response) + * + * @flags: Power table command flags from POWER_FLAGS_* + * @keep_alive_seconds: Keep alive period in seconds. Default - 25 sec. + * Minimum allowed:- 3 * DTIM. Keep alive period must be + * set regardless of power scheme or current power state. + * FW use this value also when PM is disabled. + * @rx_data_timeout: Minimum time (usec) from last Rx packet for AM to + * PSM transition - legacy PM + * @tx_data_timeout: Minimum time (usec) from last Tx packet for AM to + * PSM transition - legacy PM + * @sleep_interval: not in use + * @keep_alive_beacons: not in use + * @lprx_rssi_threshold: Signal strength up to which LP RX can be enabled. + * Default: 80dbm + */ +struct iwl_powertable_cmd { + /* PM_POWER_TABLE_CMD_API_S_VER_5 */ + __le16 flags; + u8 keep_alive_seconds; + u8 debug_flags; + __le32 rx_data_timeout; + __le32 tx_data_timeout; + __le32 sleep_interval[IWL_POWER_VEC_SIZE]; + __le32 keep_alive_beacons; + __le32 lprx_rssi_threshold; +} __packed; + +#endif diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api-rs.h b/drivers/net/wireless/iwlwifi/mvm/fw-api-rs.h new file mode 100644 index 0000000..fdd33bc --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api-rs.h @@ -0,0 +1,312 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef __fw_api_rs_h__ +#define __fw_api_rs_h__ + +#include "fw-api-mac.h" + +/* + * These serve as indexes into + * struct iwl_rate_info fw_rate_idx_to_plcp[IWL_RATE_COUNT]; + */ +enum { + IWL_RATE_1M_INDEX = 0, + IWL_FIRST_CCK_RATE = IWL_RATE_1M_INDEX, + IWL_RATE_2M_INDEX, + IWL_RATE_5M_INDEX, + IWL_RATE_11M_INDEX, + IWL_LAST_CCK_RATE = IWL_RATE_11M_INDEX, + IWL_RATE_6M_INDEX, + IWL_FIRST_OFDM_RATE = IWL_RATE_6M_INDEX, + IWL_RATE_9M_INDEX, + IWL_RATE_12M_INDEX, + IWL_RATE_18M_INDEX, + IWL_RATE_24M_INDEX, + IWL_RATE_36M_INDEX, + IWL_RATE_48M_INDEX, + IWL_RATE_54M_INDEX, + IWL_LAST_NON_HT_RATE = IWL_RATE_54M_INDEX, + IWL_RATE_60M_INDEX, + IWL_LAST_OFDM_RATE = IWL_RATE_60M_INDEX, + IWL_RATE_COUNT_LEGACY = IWL_LAST_NON_HT_RATE + 1, + IWL_RATE_COUNT, +}; + +#define IWL_RATE_BIT_MSK(r) BIT(IWL_RATE_##r##M_INDEX) + +/* fw API values for legacy bit rates, both OFDM and CCK */ +enum { + IWL_RATE_6M_PLCP = 13, + IWL_RATE_9M_PLCP = 15, + IWL_RATE_12M_PLCP = 5, + IWL_RATE_18M_PLCP = 7, + IWL_RATE_24M_PLCP = 9, + IWL_RATE_36M_PLCP = 11, + IWL_RATE_48M_PLCP = 1, + IWL_RATE_54M_PLCP = 3, + IWL_RATE_1M_PLCP = 10, + IWL_RATE_2M_PLCP = 20, + IWL_RATE_5M_PLCP = 55, + IWL_RATE_11M_PLCP = 110, +}; + +/* + * rate_n_flags bit fields + * + * The 32-bit value has different layouts in the low 8 bites depending on the + * format. There are three formats, HT, VHT and legacy (11abg, with subformats + * for CCK and OFDM). + * + * High-throughput (HT) rate format + * bit 8 is 1, bit 26 is 0, bit 9 is 0 (OFDM) + * Very High-throughput (VHT) rate format + * bit 8 is 0, bit 26 is 1, bit 9 is 0 (OFDM) + * Legacy OFDM rate format for bits 7:0 + * bit 8 is 0, bit 26 is 0, bit 9 is 0 (OFDM) + * Legacy CCK rate format for bits 7:0: + * bit 8 is 0, bit 26 is 0, bit 9 is 1 (CCK) + */ + +/* Bit 8: (1) HT format, (0) legacy or VHT format */ +#define RATE_MCS_HT_POS 8 +#define RATE_MCS_HT_MSK (1 << RATE_MCS_HT_POS) + +/* Bit 9: (1) CCK, (0) OFDM. HT (bit 8) must be "0" for this bit to be valid */ +#define RATE_MCS_CCK_POS 9 +#define RATE_MCS_CCK_MSK (1 << RATE_MCS_CCK_POS) + +/* Bit 26: (1) VHT format, (0) legacy format in bits 8:0 */ +#define RATE_MCS_VHT_POS 26 +#define RATE_MCS_VHT_MSK (1 << RATE_MCS_VHT_POS) + + +/* + * High-throughput (HT) rate format for bits 7:0 + * + * 2-0: MCS rate base + * 0) 6 Mbps + * 1) 12 Mbps + * 2) 18 Mbps + * 3) 24 Mbps + * 4) 36 Mbps + * 5) 48 Mbps + * 6) 54 Mbps + * 7) 60 Mbps + * 4-3: 0) Single stream (SISO) + * 1) Dual stream (MIMO) + * 2) Triple stream (MIMO) + * 5: Value of 0x20 in bits 7:0 indicates 6 Mbps HT40 duplicate data + * (bits 7-6 are zero) + * + * Together the low 5 bits work out to the MCS index because we don't + * support MCSes above 15/23, and 0-7 have one stream, 8-15 have two + * streams and 16-23 have three streams. We could also support MCS 32 + * which is the duplicate 20 MHz MCS (bit 5 set, all others zero.) + */ +#define RATE_HT_MCS_RATE_CODE_MSK 0x7 + +/* Bit 10: (1) Use Green Field preamble */ +#define RATE_HT_MCS_GF_POS 10 +#define RATE_HT_MCS_GF_MSK (1 << RATE_HT_MCS_GF_POS) + +#define RATE_HT_MCS_INDEX_MSK 0x3f + +/* + * Very High-throughput (VHT) rate format for bits 7:0 + * + * 3-0: VHT MCS (0-9) + * 5-4: number of streams - 1: + * 0) Single stream (SISO) + * 1) Dual stream (MIMO) + * 2) Triple stream (MIMO) + */ + +/* Bit 4-5: (0) SISO, (1) MIMO2 (2) MIMO3 */ +#define RATE_VHT_MCS_RATE_CODE_MSK 0xf +#define RATE_VHT_MCS_NSS_POS 4 +#define RATE_VHT_MCS_NSS_MSK (3 << RATE_VHT_MCS_NSS_POS) + +/* + * Legacy OFDM rate format for bits 7:0 + * + * 3-0: 0xD) 6 Mbps + * 0xF) 9 Mbps + * 0x5) 12 Mbps + * 0x7) 18 Mbps + * 0x9) 24 Mbps + * 0xB) 36 Mbps + * 0x1) 48 Mbps + * 0x3) 54 Mbps + * (bits 7-4 are 0) + * + * Legacy CCK rate format for bits 7:0: + * bit 8 is 0, bit 26 is 0, bit 9 is 1 (CCK): + * + * 6-0: 10) 1 Mbps + * 20) 2 Mbps + * 55) 5.5 Mbps + * 110) 11 Mbps + * (bit 7 is 0) + */ +#define RATE_LEGACY_RATE_MSK 0xff + + +/* + * Bit 11-12: (0) 20MHz, (1) 40MHz, (2) 80MHz, (3) 160MHz + * 0 and 1 are valid for HT and VHT, 2 and 3 only for VHT + */ +#define RATE_MCS_CHAN_WIDTH_POS 11 +#define RATE_MCS_CHAN_WIDTH_MSK (3 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_20 (0 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_40 (1 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_80 (2 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_160 (3 << RATE_MCS_CHAN_WIDTH_POS) + +/* Bit 13: (1) Short guard interval (0.4 usec), (0) normal GI (0.8 usec) */ +#define RATE_MCS_SGI_POS 13 +#define RATE_MCS_SGI_MSK (1 << RATE_MCS_SGI_POS) + +/* Bit 14-16: Antenna selection (1) Ant A, (2) Ant B, (4) Ant C */ +#define RATE_MCS_ANT_POS 14 +#define RATE_MCS_ANT_A_MSK (1 << RATE_MCS_ANT_POS) +#define RATE_MCS_ANT_B_MSK (2 << RATE_MCS_ANT_POS) +#define RATE_MCS_ANT_C_MSK (4 << RATE_MCS_ANT_POS) +#define RATE_MCS_ANT_AB_MSK (RATE_MCS_ANT_A_MSK | \ + RATE_MCS_ANT_B_MSK) +#define RATE_MCS_ANT_ABC_MSK (RATE_MCS_ANT_AB_MSK | \ + RATE_MCS_ANT_C_MSK) +#define RATE_MCS_ANT_MSK RATE_MCS_ANT_ABC_MSK +#define RATE_MCS_ANT_NUM 3 + +/* Bit 17-18: (0) SS, (1) SS*2 */ +#define RATE_MCS_STBC_POS 17 +#define RATE_MCS_STBC_MSK (1 << RATE_MCS_STBC_POS) + +/* Bit 19: (0) Beamforming is off, (1) Beamforming is on */ +#define RATE_MCS_BF_POS 19 +#define RATE_MCS_BF_MSK (1 << RATE_MCS_BF_POS) + +/* Bit 20: (0) ZLF is off, (1) ZLF is on */ +#define RATE_MCS_ZLF_POS 20 +#define RATE_MCS_ZLF_MSK (1 << RATE_MCS_ZLF_POS) + +/* Bit 24-25: (0) 20MHz (no dup), (1) 2x20MHz, (2) 4x20MHz, 3 8x20MHz */ +#define RATE_MCS_DUP_POS 24 +#define RATE_MCS_DUP_MSK (3 << RATE_MCS_DUP_POS) + +/* Bit 27: (1) LDPC enabled, (0) LDPC disabled */ +#define RATE_MCS_LDPC_POS 27 +#define RATE_MCS_LDPC_MSK (1 << RATE_MCS_LDPC_POS) + + +/* Link Quality definitions */ + +/* # entries in rate scale table to support Tx retries */ +#define LQ_MAX_RETRY_NUM 16 + +/* Link quality command flags, only this one is available */ +#define LQ_FLAG_SET_STA_TLC_RTS_MSK BIT(0) + +/** + * struct iwl_lq_cmd - link quality command + * @sta_id: station to update + * @control: not used + * @flags: combination of LQ_FLAG_* + * @mimo_delim: the first SISO index in rs_table, which separates MIMO + * and SISO rates + * @single_stream_ant_msk: best antenna for SISO (can be dual in CDD). + * Should be ANT_[ABC] + * @dual_stream_ant_msk: best antennas for MIMO, combination of ANT_[ABC] + * @initial_rate_index: first index from rs_table per AC category + * @agg_time_limit: aggregation max time threshold in usec/100, meaning + * value of 100 is one usec. Range is 100 to 8000 + * @agg_disable_start_th: try-count threshold for starting aggregation. + * If a frame has higher try-count, it should not be selected for + * starting an aggregation sequence. + * @agg_frame_cnt_limit: max frame count in an aggregation. + * 0: no limit + * 1: no aggregation (one frame per aggregation) + * 2 - 0x3f: maximal number of frames (up to 3f == 63) + * @rs_table: array of rates for each TX try, each is rate_n_flags, + * meaning it is a combination of RATE_MCS_* and IWL_RATE_*_PLCP + * @bf_params: beam forming params, currently not used + */ +struct iwl_lq_cmd { + u8 sta_id; + u8 reserved1; + u16 control; + /* LINK_QUAL_GENERAL_PARAMS_API_S_VER_1 */ + u8 flags; + u8 mimo_delim; + u8 single_stream_ant_msk; + u8 dual_stream_ant_msk; + u8 initial_rate_index[AC_NUM]; + /* LINK_QUAL_AGG_PARAMS_API_S_VER_1 */ + __le16 agg_time_limit; + u8 agg_disable_start_th; + u8 agg_frame_cnt_limit; + __le32 reserved2; + __le32 rs_table[LQ_MAX_RETRY_NUM]; + __le32 bf_params; +}; /* LINK_QUALITY_CMD_API_S_VER_1 */ +#endif /* __fw_api_rs_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api-scan.h b/drivers/net/wireless/iwlwifi/mvm/fw-api-scan.h new file mode 100644 index 0000000..365095a --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api-scan.h @@ -0,0 +1,560 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#ifndef __fw_api_scan_h__ +#define __fw_api_scan_h__ + +#include "fw-api.h" + +/* Scan Commands, Responses, Notifications */ + +/* Masks for iwl_scan_channel.type flags */ +#define SCAN_CHANNEL_TYPE_ACTIVE BIT(0) +#define SCAN_CHANNEL_NARROW_BAND BIT(22) + +/* Max number of IEs for direct SSID scans in a command */ +#define PROBE_OPTION_MAX 20 + +/** + * struct iwl_scan_channel - entry in REPLY_SCAN_CMD channel table + * @channel: band is selected by iwl_scan_cmd "flags" field + * @tx_gain: gain for analog radio + * @dsp_atten: gain for DSP + * @active_dwell: dwell time for active scan in TU, typically 5-50 + * @passive_dwell: dwell time for passive scan in TU, typically 20-500 + * @type: type is broken down to these bits: + * bit 0: 0 = passive, 1 = active + * bits 1-20: SSID direct bit map. If any of these bits is set then + * the corresponding SSID IE is transmitted in probe request + * (bit i adds IE in position i to the probe request) + * bit 22: channel width, 0 = regular, 1 = TGj narrow channel + * + * @iteration_count: + * @iteration_interval: + * This struct is used once for each channel in the scan list. + * Each channel can independently select: + * 1) SSID for directed active scans + * 2) Txpower setting (for rate specified within Tx command) + * 3) How long to stay on-channel (behavior may be modified by quiet_time, + * quiet_plcp_th, good_CRC_th) + * + * To avoid uCode errors, make sure the following are true (see comments + * under struct iwl_scan_cmd about max_out_time and quiet_time): + * 1) If using passive_dwell (i.e. passive_dwell != 0): + * active_dwell <= passive_dwell (< max_out_time if max_out_time != 0) + * 2) quiet_time <= active_dwell + * 3) If restricting off-channel time (i.e. max_out_time !=0): + * passive_dwell < max_out_time + * active_dwell < max_out_time + */ +struct iwl_scan_channel { + __le32 type; + __le16 channel; + __le16 iteration_count; + __le32 iteration_interval; + __le16 active_dwell; + __le16 passive_dwell; +} __packed; /* SCAN_CHANNEL_CONTROL_API_S_VER_1 */ + +/** + * struct iwl_ssid_ie - directed scan network information element + * + * Up to 20 of these may appear in REPLY_SCAN_CMD, + * selected by "type" bit field in struct iwl_scan_channel; + * each channel may select different ssids from among the 20 entries. + * SSID IEs get transmitted in reverse order of entry. + */ +struct iwl_ssid_ie { + u8 id; + u8 len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; +} __packed; /* SCAN_DIRECT_SSID_IE_API_S_VER_1 */ + +/** + * iwl_scan_flags - masks for scan command flags + *@SCAN_FLAGS_PERIODIC_SCAN: + *@SCAN_FLAGS_P2P_PUBLIC_ACTION_FRAME_TX: + *@SCAN_FLAGS_DELAYED_SCAN_LOWBAND: + *@SCAN_FLAGS_DELAYED_SCAN_HIGHBAND: + *@SCAN_FLAGS_FRAGMENTED_SCAN: + */ +enum iwl_scan_flags { + SCAN_FLAGS_PERIODIC_SCAN = BIT(0), + SCAN_FLAGS_P2P_PUBLIC_ACTION_FRAME_TX = BIT(1), + SCAN_FLAGS_DELAYED_SCAN_LOWBAND = BIT(2), + SCAN_FLAGS_DELAYED_SCAN_HIGHBAND = BIT(3), + SCAN_FLAGS_FRAGMENTED_SCAN = BIT(4), +}; + +/** + * enum iwl_scan_type - Scan types for scan command + * @SCAN_TYPE_FORCED: + * @SCAN_TYPE_BACKGROUND: + * @SCAN_TYPE_OS: + * @SCAN_TYPE_ROAMING: + * @SCAN_TYPE_ACTION: + * @SCAN_TYPE_DISCOVERY: + * @SCAN_TYPE_DISCOVERY_FORCED: + */ +enum iwl_scan_type { + SCAN_TYPE_FORCED = 0, + SCAN_TYPE_BACKGROUND = 1, + SCAN_TYPE_OS = 2, + SCAN_TYPE_ROAMING = 3, + SCAN_TYPE_ACTION = 4, + SCAN_TYPE_DISCOVERY = 5, + SCAN_TYPE_DISCOVERY_FORCED = 6, +}; /* SCAN_ACTIVITY_TYPE_E_VER_1 */ + +/* Maximal number of channels to scan */ +#define MAX_NUM_SCAN_CHANNELS 0x24 + +/** + * struct iwl_scan_cmd - scan request command + * ( SCAN_REQUEST_CMD = 0x80 ) + * @len: command length in bytes + * @scan_flags: scan flags from SCAN_FLAGS_* + * @channel_count: num of channels in channel list (1 - MAX_NUM_SCAN_CHANNELS) + * @quiet_time: in msecs, dwell this time for active scan on quiet channels + * @quiet_plcp_th: quiet PLCP threshold (channel is quiet if less than + * this number of packets were received (typically 1) + * @passive2active: is auto switching from passive to active allowed (0 or 1) + * @rxchain_sel_flags: RXON_RX_CHAIN_* + * @max_out_time: in usecs, max out of serving channel time + * @suspend_time: how long to pause scan when returning to service channel: + * bits 0-19: beacon interal in usecs (suspend before executing) + * bits 20-23: reserved + * bits 24-31: number of beacons (suspend between channels) + * @rxon_flags: RXON_FLG_* + * @filter_flags: RXON_FILTER_* + * @tx_cmd: for active scans (zero for passive), w/o payload, + * no RS so specify TX rate + * @direct_scan: direct scan SSIDs + * @type: one of SCAN_TYPE_* + * @repeats: how many time to repeat the scan + */ +struct iwl_scan_cmd { + __le16 len; + u8 scan_flags; + u8 channel_count; + __le16 quiet_time; + __le16 quiet_plcp_th; + __le16 passive2active; + __le16 rxchain_sel_flags; + __le32 max_out_time; + __le32 suspend_time; + /* RX_ON_FLAGS_API_S_VER_1 */ + __le32 rxon_flags; + __le32 filter_flags; + struct iwl_tx_cmd tx_cmd; + struct iwl_ssid_ie direct_scan[PROBE_OPTION_MAX]; + __le32 type; + __le32 repeats; + + /* + * Probe request frame, followed by channel list. + * + * Size of probe request frame is specified by byte count in tx_cmd. + * Channel list follows immediately after probe request frame. + * Number of channels in list is specified by channel_count. + * Each channel in list is of type: + * + * struct iwl_scan_channel channels[0]; + * + * NOTE: Only one band of channels can be scanned per pass. You + * must not mix 2.4GHz channels and 5.2GHz channels, and you must wait + * for one scan to complete (i.e. receive SCAN_COMPLETE_NOTIFICATION) + * before requesting another scan. + */ + u8 data[0]; +} __packed; /* SCAN_REQUEST_FIXED_PART_API_S_VER_5 */ + +/* Response to scan request contains only status with one of these values */ +#define SCAN_RESPONSE_OK 0x1 +#define SCAN_RESPONSE_ERROR 0x2 + +/* + * SCAN_ABORT_CMD = 0x81 + * When scan abort is requested, the command has no fields except the common + * header. The response contains only a status with one of these values. + */ +#define SCAN_ABORT_POSSIBLE 0x1 +#define SCAN_ABORT_IGNORED 0x2 /* no pending scans */ + +/* TODO: complete documentation */ +#define SCAN_OWNER_STATUS 0x1 +#define MEASURE_OWNER_STATUS 0x2 + +/** + * struct iwl_scan_start_notif - notifies start of scan in the device + * ( SCAN_START_NOTIFICATION = 0x82 ) + * @tsf_low: TSF timer (lower half) in usecs + * @tsf_high: TSF timer (higher half) in usecs + * @beacon_timer: structured as follows: + * bits 0:19 - beacon interval in usecs + * bits 20:23 - reserved (0) + * bits 24:31 - number of beacons + * @channel: which channel is scanned + * @band: 0 for 5.2 GHz, 1 for 2.4 GHz + * @status: one of *_OWNER_STATUS + */ +struct iwl_scan_start_notif { + __le32 tsf_low; + __le32 tsf_high; + __le32 beacon_timer; + u8 channel; + u8 band; + u8 reserved[2]; + __le32 status; +} __packed; /* SCAN_START_NTF_API_S_VER_1 */ + +/* scan results probe_status first bit indicates success */ +#define SCAN_PROBE_STATUS_OK 0 +#define SCAN_PROBE_STATUS_TX_FAILED BIT(0) +/* error statuses combined with TX_FAILED */ +#define SCAN_PROBE_STATUS_FAIL_TTL BIT(1) +#define SCAN_PROBE_STATUS_FAIL_BT BIT(2) + +/* How many statistics are gathered for each channel */ +#define SCAN_RESULTS_STATISTICS 1 + +/** + * enum iwl_scan_complete_status - status codes for scan complete notifications + * @SCAN_COMP_STATUS_OK: scan completed successfully + * @SCAN_COMP_STATUS_ABORT: scan was aborted by user + * @SCAN_COMP_STATUS_ERR_SLEEP: sending null sleep packet failed + * @SCAN_COMP_STATUS_ERR_CHAN_TIMEOUT: timeout before channel is ready + * @SCAN_COMP_STATUS_ERR_PROBE: sending probe request failed + * @SCAN_COMP_STATUS_ERR_WAKEUP: sending null wakeup packet failed + * @SCAN_COMP_STATUS_ERR_ANTENNAS: invalid antennas chosen at scan command + * @SCAN_COMP_STATUS_ERR_INTERNAL: internal error caused scan abort + * @SCAN_COMP_STATUS_ERR_COEX: medium was lost ot WiMax + * @SCAN_COMP_STATUS_P2P_ACTION_OK: P2P public action frame TX was successful + * (not an error!) + * @SCAN_COMP_STATUS_ITERATION_END: indicates end of one repeatition the driver + * asked for + * @SCAN_COMP_STATUS_ERR_ALLOC_TE: scan could not allocate time events +*/ +enum iwl_scan_complete_status { + SCAN_COMP_STATUS_OK = 0x1, + SCAN_COMP_STATUS_ABORT = 0x2, + SCAN_COMP_STATUS_ERR_SLEEP = 0x3, + SCAN_COMP_STATUS_ERR_CHAN_TIMEOUT = 0x4, + SCAN_COMP_STATUS_ERR_PROBE = 0x5, + SCAN_COMP_STATUS_ERR_WAKEUP = 0x6, + SCAN_COMP_STATUS_ERR_ANTENNAS = 0x7, + SCAN_COMP_STATUS_ERR_INTERNAL = 0x8, + SCAN_COMP_STATUS_ERR_COEX = 0x9, + SCAN_COMP_STATUS_P2P_ACTION_OK = 0xA, + SCAN_COMP_STATUS_ITERATION_END = 0x0B, + SCAN_COMP_STATUS_ERR_ALLOC_TE = 0x0C, +}; + +/** + * struct iwl_scan_results_notif - scan results for one channel + * ( SCAN_RESULTS_NOTIFICATION = 0x83 ) + * @channel: which channel the results are from + * @band: 0 for 5.2 GHz, 1 for 2.4 GHz + * @probe_status: SCAN_PROBE_STATUS_*, indicates success of probe request + * @num_probe_not_sent: # of request that weren't sent due to not enough time + * @duration: duration spent in channel, in usecs + * @statistics: statistics gathered for this channel + */ +struct iwl_scan_results_notif { + u8 channel; + u8 band; + u8 probe_status; + u8 num_probe_not_sent; + __le32 duration; + __le32 statistics[SCAN_RESULTS_STATISTICS]; +} __packed; /* SCAN_RESULT_NTF_API_S_VER_2 */ + +/** + * struct iwl_scan_complete_notif - notifies end of scanning (all channels) + * ( SCAN_COMPLETE_NOTIFICATION = 0x84 ) + * @scanned_channels: number of channels scanned (and number of valid results) + * @status: one of SCAN_COMP_STATUS_* + * @bt_status: BT on/off status + * @last_channel: last channel that was scanned + * @tsf_low: TSF timer (lower half) in usecs + * @tsf_high: TSF timer (higher half) in usecs + * @results: all scan results, only "scanned_channels" of them are valid + */ +struct iwl_scan_complete_notif { + u8 scanned_channels; + u8 status; + u8 bt_status; + u8 last_channel; + __le32 tsf_low; + __le32 tsf_high; + struct iwl_scan_results_notif results[MAX_NUM_SCAN_CHANNELS]; +} __packed; /* SCAN_COMPLETE_NTF_API_S_VER_2 */ + +/* scan offload */ +#define IWL_MAX_SCAN_CHANNELS 40 +#define IWL_SCAN_MAX_BLACKLIST_LEN 64 +#define IWL_SCAN_MAX_PROFILES 11 +#define SCAN_OFFLOAD_PROBE_REQ_SIZE 512 + +/* Default watchdog (in MS) for scheduled scan iteration */ +#define IWL_SCHED_SCAN_WATCHDOG cpu_to_le16(15000) + +#define IWL_GOOD_CRC_TH_DEFAULT cpu_to_le16(1) +#define CAN_ABORT_STATUS 1 + +#define IWL_FULL_SCAN_MULTIPLIER 5 +#define IWL_FAST_SCHED_SCAN_ITERATIONS 3 + +/** + * struct iwl_scan_offload_cmd - SCAN_REQUEST_FIXED_PART_API_S_VER_6 + * @scan_flags: see enum iwl_scan_flags + * @channel_count: channels in channel list + * @quiet_time: dwell time, in milisiconds, on quiet channel + * @quiet_plcp_th: quiet channel num of packets threshold + * @good_CRC_th: passive to active promotion threshold + * @rx_chain: RXON rx chain. + * @max_out_time: max uSec to be out of assoceated channel + * @suspend_time: pause scan this long when returning to service channel + * @flags: RXON flags + * @filter_flags: RXONfilter + * @tx_cmd: tx command for active scan; for 2GHz and for 5GHz. + * @direct_scan: list of SSIDs for directed active scan + * @scan_type: see enum iwl_scan_type. + * @rep_count: repetition count for each scheduled scan iteration. + */ +struct iwl_scan_offload_cmd { + __le16 len; + u8 scan_flags; + u8 channel_count; + __le16 quiet_time; + __le16 quiet_plcp_th; + __le16 good_CRC_th; + __le16 rx_chain; + __le32 max_out_time; + __le32 suspend_time; + /* RX_ON_FLAGS_API_S_VER_1 */ + __le32 flags; + __le32 filter_flags; + struct iwl_tx_cmd tx_cmd[2]; + /* SCAN_DIRECT_SSID_IE_API_S_VER_1 */ + struct iwl_ssid_ie direct_scan[PROBE_OPTION_MAX]; + __le32 scan_type; + __le32 rep_count; +} __packed; + +enum iwl_scan_offload_channel_flags { + IWL_SCAN_OFFLOAD_CHANNEL_ACTIVE = BIT(0), + IWL_SCAN_OFFLOAD_CHANNEL_NARROW = BIT(22), + IWL_SCAN_OFFLOAD_CHANNEL_FULL = BIT(24), + IWL_SCAN_OFFLOAD_CHANNEL_PARTIAL = BIT(25), +}; + +/** + * iwl_scan_channel_cfg - SCAN_CHANNEL_CFG_S + * @type: bitmap - see enum iwl_scan_offload_channel_flags. + * 0: passive (0) or active (1) scan. + * 1-20: directed scan to i'th ssid. + * 22: channel width configuation - 1 for narrow. + * 24: full scan. + * 25: partial scan. + * @channel_number: channel number 1-13 etc. + * @iter_count: repetition count for the channel. + * @iter_interval: interval between two innteration on one channel. + * @dwell_time: entry 0 - active scan, entry 1 - passive scan. + */ +struct iwl_scan_channel_cfg { + __le32 type[IWL_MAX_SCAN_CHANNELS]; + __le16 channel_number[IWL_MAX_SCAN_CHANNELS]; + __le16 iter_count[IWL_MAX_SCAN_CHANNELS]; + __le32 iter_interval[IWL_MAX_SCAN_CHANNELS]; + u8 dwell_time[IWL_MAX_SCAN_CHANNELS][2]; +} __packed; + +/** + * iwl_scan_offload_cfg - SCAN_OFFLOAD_CONFIG_API_S + * @scan_cmd: scan command fixed part + * @channel_cfg: scan channel configuration + * @data: probe request frames (one per band) + */ +struct iwl_scan_offload_cfg { + struct iwl_scan_offload_cmd scan_cmd; + struct iwl_scan_channel_cfg channel_cfg; + u8 data[0]; +} __packed; + +/** + * iwl_scan_offload_blacklist - SCAN_OFFLOAD_BLACKLIST_S + * @ssid: MAC address to filter out + * @reported_rssi: AP rssi reported to the host + */ +struct iwl_scan_offload_blacklist { + u8 ssid[ETH_ALEN]; + u8 reported_rssi; + u8 reserved; +} __packed; + +enum iwl_scan_offload_network_type { + IWL_NETWORK_TYPE_BSS = 1, + IWL_NETWORK_TYPE_IBSS = 2, + IWL_NETWORK_TYPE_ANY = 3, +}; + +enum iwl_scan_offload_band_selection { + IWL_SCAN_OFFLOAD_SELECT_2_4 = 0x4, + IWL_SCAN_OFFLOAD_SELECT_5_2 = 0x8, + IWL_SCAN_OFFLOAD_SELECT_ANY = 0xc, +}; + +/** + * iwl_scan_offload_profile - SCAN_OFFLOAD_PROFILE_S + * @ssid_index: index to ssid list in fixed part + * @unicast_cipher: encryption olgorithm to match - bitmap + * @aut_alg: authentication olgorithm to match - bitmap + * @network_type: enum iwl_scan_offload_network_type + * @band_selection: enum iwl_scan_offload_band_selection + */ +struct iwl_scan_offload_profile { + u8 ssid_index; + u8 unicast_cipher; + u8 auth_alg; + u8 network_type; + u8 band_selection; + u8 reserved[3]; +} __packed; + +/** + * iwl_scan_offload_profile_cfg - SCAN_OFFLOAD_PROFILES_CFG_API_S_VER_1 + * @blaclist: AP list to filter off from scan results + * @profiles: profiles to search for match + * @blacklist_len: length of blacklist + * @num_profiles: num of profiles in the list + */ +struct iwl_scan_offload_profile_cfg { + struct iwl_scan_offload_blacklist blacklist[IWL_SCAN_MAX_BLACKLIST_LEN]; + struct iwl_scan_offload_profile profiles[IWL_SCAN_MAX_PROFILES]; + u8 blacklist_len; + u8 num_profiles; + u8 reserved[2]; +} __packed; + +/** + * iwl_scan_offload_schedule - schedule of scan offload + * @delay: delay between iterations, in seconds. + * @iterations: num of scan iterations + * @full_scan_mul: number of partial scans before each full scan + */ +struct iwl_scan_offload_schedule { + u16 delay; + u8 iterations; + u8 full_scan_mul; +} __packed; + +/* + * iwl_scan_offload_flags + * + * IWL_SCAN_OFFLOAD_FLAG_FILTER_SSID: filter mode - upload every beacon or match + * ssid list. + * IWL_SCAN_OFFLOAD_FLAG_CACHED_CHANNEL: add cached channels to partial scan. + * IWL_SCAN_OFFLOAD_FLAG_ENERGY_SCAN: use energy based scan before partial scan + * on A band. + */ +enum iwl_scan_offload_flags { + IWL_SCAN_OFFLOAD_FLAG_FILTER_SSID = BIT(0), + IWL_SCAN_OFFLOAD_FLAG_CACHED_CHANNEL = BIT(2), + IWL_SCAN_OFFLOAD_FLAG_ENERGY_SCAN = BIT(3), +}; + +/** + * iwl_scan_offload_req - scan offload request command + * @flags: bitmap - enum iwl_scan_offload_flags. + * @watchdog: maximum scan duration in TU. + * @delay: delay in seconds before first iteration. + * @schedule_line: scan offload schedule, for fast and regular scan. + */ +struct iwl_scan_offload_req { + __le16 flags; + __le16 watchdog; + __le16 delay; + __le16 reserved; + struct iwl_scan_offload_schedule schedule_line[2]; +} __packed; + +enum iwl_scan_offload_compleate_status { + IWL_SCAN_OFFLOAD_COMPLETED = 1, + IWL_SCAN_OFFLOAD_ABORTED = 2, +}; + +/** + * iwl_scan_offload_complete - SCAN_OFFLOAD_COMPLETE_NTF_API_S_VER_1 + * @last_schedule_line: last schedule line executed (fast or regular) + * @last_schedule_iteration: last scan iteration executed before scan abort + * @status: enum iwl_scan_offload_compleate_status + */ +struct iwl_scan_offload_complete { + u8 last_schedule_line; + u8 last_schedule_iteration; + u8 status; + u8 reserved; +} __packed; + +#endif diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api-sta.h b/drivers/net/wireless/iwlwifi/mvm/fw-api-sta.h new file mode 100644 index 0000000..a30691a --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api-sta.h @@ -0,0 +1,380 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef __fw_api_sta_h__ +#define __fw_api_sta_h__ + +/** + * enum iwl_sta_flags - flags for the ADD_STA host command + * @STA_FLG_REDUCED_TX_PWR_CTRL: + * @STA_FLG_REDUCED_TX_PWR_DATA: + * @STA_FLG_FLG_ANT_MSK: Antenna selection + * @STA_FLG_PS: set if STA is in Power Save + * @STA_FLG_INVALID: set if STA is invalid + * @STA_FLG_DLP_EN: Direct Link Protocol is enabled + * @STA_FLG_SET_ALL_KEYS: the current key applies to all key IDs + * @STA_FLG_DRAIN_FLOW: drain flow + * @STA_FLG_PAN: STA is for PAN interface + * @STA_FLG_CLASS_AUTH: + * @STA_FLG_CLASS_ASSOC: + * @STA_FLG_CLASS_MIMO_PROT: + * @STA_FLG_MAX_AGG_SIZE_MSK: maximal size for A-MPDU + * @STA_FLG_AGG_MPDU_DENS_MSK: maximal MPDU density for Tx aggregation + * @STA_FLG_FAT_EN_MSK: support for channel width (for Tx). This flag is + * initialised by driver and can be updated by fw upon reception of + * action frames that can change the channel width. When cleared the fw + * will send all the frames in 20MHz even when FAT channel is requested. + * @STA_FLG_MIMO_EN_MSK: support for MIMO. This flag is initialised by the + * driver and can be updated by fw upon reception of action frames. + * @STA_FLG_MFP_EN: Management Frame Protection + */ +enum iwl_sta_flags { + STA_FLG_REDUCED_TX_PWR_CTRL = BIT(3), + STA_FLG_REDUCED_TX_PWR_DATA = BIT(6), + + STA_FLG_FLG_ANT_A = (1 << 4), + STA_FLG_FLG_ANT_B = (2 << 4), + STA_FLG_FLG_ANT_MSK = (STA_FLG_FLG_ANT_A | + STA_FLG_FLG_ANT_B), + + STA_FLG_PS = BIT(8), + STA_FLG_INVALID = BIT(9), + STA_FLG_DLP_EN = BIT(10), + STA_FLG_SET_ALL_KEYS = BIT(11), + STA_FLG_DRAIN_FLOW = BIT(12), + STA_FLG_PAN = BIT(13), + STA_FLG_CLASS_AUTH = BIT(14), + STA_FLG_CLASS_ASSOC = BIT(15), + STA_FLG_RTS_MIMO_PROT = BIT(17), + + STA_FLG_MAX_AGG_SIZE_SHIFT = 19, + STA_FLG_MAX_AGG_SIZE_8K = (0 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_16K = (1 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_32K = (2 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_64K = (3 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_128K = (4 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_256K = (5 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_512K = (6 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_1024K = (7 << STA_FLG_MAX_AGG_SIZE_SHIFT), + STA_FLG_MAX_AGG_SIZE_MSK = (7 << STA_FLG_MAX_AGG_SIZE_SHIFT), + + STA_FLG_AGG_MPDU_DENS_SHIFT = 23, + STA_FLG_AGG_MPDU_DENS_2US = (4 << STA_FLG_AGG_MPDU_DENS_SHIFT), + STA_FLG_AGG_MPDU_DENS_4US = (5 << STA_FLG_AGG_MPDU_DENS_SHIFT), + STA_FLG_AGG_MPDU_DENS_8US = (6 << STA_FLG_AGG_MPDU_DENS_SHIFT), + STA_FLG_AGG_MPDU_DENS_16US = (7 << STA_FLG_AGG_MPDU_DENS_SHIFT), + STA_FLG_AGG_MPDU_DENS_MSK = (7 << STA_FLG_AGG_MPDU_DENS_SHIFT), + + STA_FLG_FAT_EN_20MHZ = (0 << 26), + STA_FLG_FAT_EN_40MHZ = (1 << 26), + STA_FLG_FAT_EN_80MHZ = (2 << 26), + STA_FLG_FAT_EN_160MHZ = (3 << 26), + STA_FLG_FAT_EN_MSK = (3 << 26), + + STA_FLG_MIMO_EN_SISO = (0 << 28), + STA_FLG_MIMO_EN_MIMO2 = (1 << 28), + STA_FLG_MIMO_EN_MIMO3 = (2 << 28), + STA_FLG_MIMO_EN_MSK = (3 << 28), +}; + +/** + * enum iwl_sta_key_flag - key flags for the ADD_STA host command + * @STA_KEY_FLG_EN_MSK: mask for encryption algorithm + * @STA_KEY_FLG_WEP_KEY_MAP: wep is either a group key (0 - legacy WEP) or from + * station info array (1 - n 1X mode) + * @STA_KEY_FLG_KEYID_MSK: the index of the key + * @STA_KEY_NOT_VALID: key is invalid + * @STA_KEY_FLG_WEP_13BYTES: set for 13 bytes WEP key + * @STA_KEY_MULTICAST: set for multical key + * @STA_KEY_MFP: key is used for Management Frame Protection + */ +enum iwl_sta_key_flag { + STA_KEY_FLG_NO_ENC = (0 << 0), + STA_KEY_FLG_WEP = (1 << 0), + STA_KEY_FLG_CCM = (2 << 0), + STA_KEY_FLG_TKIP = (3 << 0), + STA_KEY_FLG_CMAC = (6 << 0), + STA_KEY_FLG_ENC_UNKNOWN = (7 << 0), + STA_KEY_FLG_EN_MSK = (7 << 0), + + STA_KEY_FLG_WEP_KEY_MAP = BIT(3), + STA_KEY_FLG_KEYID_POS = 8, + STA_KEY_FLG_KEYID_MSK = (3 << STA_KEY_FLG_KEYID_POS), + STA_KEY_NOT_VALID = BIT(11), + STA_KEY_FLG_WEP_13BYTES = BIT(12), + STA_KEY_MULTICAST = BIT(14), + STA_KEY_MFP = BIT(15), +}; + +/** + * enum iwl_sta_modify_flag - indicate to the fw what flag are being changed + * @STA_MODIFY_KEY: this command modifies %key + * @STA_MODIFY_TID_DISABLE_TX: this command modifies %tid_disable_tx + * @STA_MODIFY_TX_RATE: unused + * @STA_MODIFY_ADD_BA_TID: this command modifies %add_immediate_ba_tid + * @STA_MODIFY_REMOVE_BA_TID: this command modifies %remove_immediate_ba_tid + * @STA_MODIFY_SLEEPING_STA_TX_COUNT: this command modifies %sleep_tx_count + * @STA_MODIFY_PROT_TH: + * @STA_MODIFY_QUEUES: modify the queues used by this station + */ +enum iwl_sta_modify_flag { + STA_MODIFY_KEY = BIT(0), + STA_MODIFY_TID_DISABLE_TX = BIT(1), + STA_MODIFY_TX_RATE = BIT(2), + STA_MODIFY_ADD_BA_TID = BIT(3), + STA_MODIFY_REMOVE_BA_TID = BIT(4), + STA_MODIFY_SLEEPING_STA_TX_COUNT = BIT(5), + STA_MODIFY_PROT_TH = BIT(6), + STA_MODIFY_QUEUES = BIT(7), +}; + +#define STA_MODE_MODIFY 1 + +/** + * enum iwl_sta_sleep_flag - type of sleep of the station + * @STA_SLEEP_STATE_AWAKE: + * @STA_SLEEP_STATE_PS_POLL: + * @STA_SLEEP_STATE_UAPSD: + */ +enum iwl_sta_sleep_flag { + STA_SLEEP_STATE_AWAKE = 0, + STA_SLEEP_STATE_PS_POLL = BIT(0), + STA_SLEEP_STATE_UAPSD = BIT(1), +}; + +/* STA ID and color bits definitions */ +#define STA_ID_SEED (0x0f) +#define STA_ID_POS (0) +#define STA_ID_MSK (STA_ID_SEED << STA_ID_POS) + +#define STA_COLOR_SEED (0x7) +#define STA_COLOR_POS (4) +#define STA_COLOR_MSK (STA_COLOR_SEED << STA_COLOR_POS) + +#define STA_ID_N_COLOR_GET_COLOR(id_n_color) \ + (((id_n_color) & STA_COLOR_MSK) >> STA_COLOR_POS) +#define STA_ID_N_COLOR_GET_ID(id_n_color) \ + (((id_n_color) & STA_ID_MSK) >> STA_ID_POS) + +#define STA_KEY_MAX_NUM (16) +#define STA_KEY_IDX_INVALID (0xff) +#define STA_KEY_MAX_DATA_KEY_NUM (4) +#define IWL_MAX_GLOBAL_KEYS (4) +#define STA_KEY_LEN_WEP40 (5) +#define STA_KEY_LEN_WEP104 (13) + +/** + * struct iwl_mvm_keyinfo - key information + * @key_flags: type %iwl_sta_key_flag + * @tkip_rx_tsc_byte2: TSC[2] for key mix ph1 detection + * @tkip_rx_ttak: 10-byte unicast TKIP TTAK for Rx + * @key_offset: key offset in the fw's key table + * @key: 16-byte unicast decryption key + * @tx_secur_seq_cnt: initial RSC / PN needed for replay check + * @hw_tkip_mic_rx_key: byte: MIC Rx Key - used for TKIP only + * @hw_tkip_mic_tx_key: byte: MIC Tx Key - used for TKIP only + */ +struct iwl_mvm_keyinfo { + __le16 key_flags; + u8 tkip_rx_tsc_byte2; + u8 reserved1; + __le16 tkip_rx_ttak[5]; + u8 key_offset; + u8 reserved2; + u8 key[16]; + __le64 tx_secur_seq_cnt; + __le64 hw_tkip_mic_rx_key; + __le64 hw_tkip_mic_tx_key; +} __packed; + +/** + * struct iwl_mvm_add_sta_cmd - Add / modify a station in the fw's station table + * ( REPLY_ADD_STA = 0x18 ) + * @add_modify: 1: modify existing, 0: add new station + * @unicast_tx_key_id: unicast tx key id. Relevant only when unicast key sent + * @multicast_tx_key_id: multicast tx key id. Relevant only when multicast key + * sent + * @mac_id_n_color: the Mac context this station belongs to + * @addr[ETH_ALEN]: station's MAC address + * @sta_id: index of station in uCode's station table + * @modify_mask: STA_MODIFY_*, selects which parameters to modify vs. leave + * alone. 1 - modify, 0 - don't change. + * @key: look at %iwl_mvm_keyinfo + * @station_flags: look at %iwl_sta_flags + * @station_flags_msk: what of %station_flags have changed + * @tid_disable_tx: is tid BIT(tid) enabled for Tx. Clear BIT(x) to enable + * AMPDU for tid x. Set %STA_MODIFY_TID_DISABLE_TX to change this field. + * @add_immediate_ba_tid: tid for which to add block-ack support (Rx) + * Set %STA_MODIFY_ADD_BA_TID to use this field, and also set + * add_immediate_ba_ssn. + * @remove_immediate_ba_tid: tid for which to remove block-ack support (Rx) + * Set %STA_MODIFY_REMOVE_BA_TID to use this field + * @add_immediate_ba_ssn: ssn for the Rx block-ack session. Used together with + * add_immediate_ba_tid. + * @sleep_tx_count: number of packets to transmit to station even though it is + * asleep. Used to synchronise PS-poll and u-APSD responses while ucode + * keeps track of STA sleep state. + * @sleep_state_flags: Look at %iwl_sta_sleep_flag. + * @assoc_id: assoc_id to be sent in VHT PLCP (9-bit), for grp use 0, for AP + * mac-addr. + * @beamform_flags: beam forming controls + * @tfd_queue_msk: tfd queues used by this station + * + * The device contains an internal table of per-station information, with info + * on security keys, aggregation parameters, and Tx rates for initial Tx + * attempt and any retries (set by REPLY_TX_LINK_QUALITY_CMD). + * + * ADD_STA sets up the table entry for one station, either creating a new + * entry, or modifying a pre-existing one. + */ +struct iwl_mvm_add_sta_cmd { + u8 add_modify; + u8 unicast_tx_key_id; + u8 multicast_tx_key_id; + u8 reserved1; + __le32 mac_id_n_color; + u8 addr[ETH_ALEN]; + __le16 reserved2; + u8 sta_id; + u8 modify_mask; + __le16 reserved3; + struct iwl_mvm_keyinfo key; + __le32 station_flags; + __le32 station_flags_msk; + __le16 tid_disable_tx; + __le16 reserved4; + u8 add_immediate_ba_tid; + u8 remove_immediate_ba_tid; + __le16 add_immediate_ba_ssn; + __le16 sleep_tx_count; + __le16 sleep_state_flags; + __le16 assoc_id; + __le16 beamform_flags; + __le32 tfd_queue_msk; +} __packed; /* ADD_STA_CMD_API_S_VER_5 */ + +/** + * enum iwl_mvm_add_sta_rsp_status - status in the response to ADD_STA command + * @ADD_STA_SUCCESS: operation was executed successfully + * @ADD_STA_STATIONS_OVERLOAD: no room left in the fw's station table + * @ADD_STA_IMMEDIATE_BA_FAILURE: can't add Rx block ack session + * @ADD_STA_MODIFY_NON_EXISTING_STA: driver requested to modify a station that + * doesn't exist. + */ +enum iwl_mvm_add_sta_rsp_status { + ADD_STA_SUCCESS = 0x1, + ADD_STA_STATIONS_OVERLOAD = 0x2, + ADD_STA_IMMEDIATE_BA_FAILURE = 0x4, + ADD_STA_MODIFY_NON_EXISTING_STA = 0x8, +}; + +/** + * struct iwl_mvm_rm_sta_cmd - Add / modify a station in the fw's station table + * ( REMOVE_STA = 0x19 ) + * @sta_id: the station id of the station to be removed + */ +struct iwl_mvm_rm_sta_cmd { + u8 sta_id; + u8 reserved[3]; +} __packed; /* REMOVE_STA_CMD_API_S_VER_2 */ + +/** + * struct iwl_mvm_mgmt_mcast_key_cmd + * ( MGMT_MCAST_KEY = 0x1f ) + * @ctrl_flags: %iwl_sta_key_flag + * @IGTK: + * @K1: IGTK master key + * @K2: IGTK sub key + * @sta_id: station ID that support IGTK + * @key_id: + * @receive_seq_cnt: initial RSC/PN needed for replay check + */ +struct iwl_mvm_mgmt_mcast_key_cmd { + __le32 ctrl_flags; + u8 IGTK[16]; + u8 K1[16]; + u8 K2[16]; + __le32 key_id; + __le32 sta_id; + __le64 receive_seq_cnt; +} __packed; /* SEC_MGMT_MULTICAST_KEY_CMD_API_S_VER_1 */ + +struct iwl_mvm_wep_key { + u8 key_index; + u8 key_offset; + __le16 reserved1; + u8 key_size; + u8 reserved2[3]; + u8 key[16]; +} __packed; + +struct iwl_mvm_wep_key_cmd { + __le32 mac_id_n_color; + u8 num_keys; + u8 decryption_type; + u8 flags; + u8 reserved; + struct iwl_mvm_wep_key wep_key[0]; +} __packed; /* SEC_CURR_WEP_KEY_CMD_API_S_VER_2 */ + + +#endif /* __fw_api_sta_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api-tx.h b/drivers/net/wireless/iwlwifi/mvm/fw-api-tx.h new file mode 100644 index 0000000..007a93b --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api-tx.h @@ -0,0 +1,586 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef __fw_api_tx_h__ +#define __fw_api_tx_h__ + +/** + * enum iwl_tx_flags - bitmasks for tx_flags in TX command + * @TX_CMD_FLG_PROT_REQUIRE: use RTS or CTS-to-self to protect the frame + * @TX_CMD_FLG_ACK: expect ACK from receiving station + * @TX_CMD_FLG_STA_RATE: use RS table with initial index from the TX command. + * Otherwise, use rate_n_flags from the TX command + * @TX_CMD_FLG_BA: this frame is a block ack + * @TX_CMD_FLG_BAR: this frame is a BA request, immediate BAR is expected + * Must set TX_CMD_FLG_ACK with this flag. + * @TX_CMD_FLG_TXOP_PROT: protect frame with full TXOP protection + * @TX_CMD_FLG_VHT_NDPA: mark frame is NDPA for VHT beamformer sequence + * @TX_CMD_FLG_HT_NDPA: mark frame is NDPA for HT beamformer sequence + * @TX_CMD_FLG_CSI_FDBK2HOST: mark to send feedback to host (only if good CRC) + * @TX_CMD_FLG_BT_DIS: disable BT priority for this frame + * @TX_CMD_FLG_SEQ_CTL: set if FW should override the sequence control. + * Should be set for mgmt, non-QOS data, mcast, bcast and in scan command + * @TX_CMD_FLG_MORE_FRAG: this frame is non-last MPDU + * @TX_CMD_FLG_NEXT_FRAME: this frame includes information of the next frame + * @TX_CMD_FLG_TSF: FW should calculate and insert TSF in the frame + * Should be set for beacons and probe responses + * @TX_CMD_FLG_CALIB: activate PA TX power calibrations + * @TX_CMD_FLG_KEEP_SEQ_CTL: if seq_ctl is set, don't increase inner seq count + * @TX_CMD_FLG_AGG_START: allow this frame to start aggregation + * @TX_CMD_FLG_MH_PAD: driver inserted 2 byte padding after MAC header. + * Should be set for 26/30 length MAC headers + * @TX_CMD_FLG_RESP_TO_DRV: zero this if the response should go only to FW + * @TX_CMD_FLG_CCMP_AGG: this frame uses CCMP for aggregation acceleration + * @TX_CMD_FLG_TKIP_MIC_DONE: FW already performed TKIP MIC calculation + * @TX_CMD_FLG_CTS_ONLY: send CTS only, no data after that + * @TX_CMD_FLG_DUR: disable duration overwriting used in PS-Poll Assoc-id + * @TX_CMD_FLG_FW_DROP: FW should mark frame to be dropped + * @TX_CMD_FLG_EXEC_PAPD: execute PAPD + * @TX_CMD_FLG_PAPD_TYPE: 0 for reference power, 1 for nominal power + * @TX_CMD_FLG_HCCA_CHUNK: mark start of TSPEC chunk + */ +enum iwl_tx_flags { + TX_CMD_FLG_PROT_REQUIRE = BIT(0), + TX_CMD_FLG_ACK = BIT(3), + TX_CMD_FLG_STA_RATE = BIT(4), + TX_CMD_FLG_BA = BIT(5), + TX_CMD_FLG_BAR = BIT(6), + TX_CMD_FLG_TXOP_PROT = BIT(7), + TX_CMD_FLG_VHT_NDPA = BIT(8), + TX_CMD_FLG_HT_NDPA = BIT(9), + TX_CMD_FLG_CSI_FDBK2HOST = BIT(10), + TX_CMD_FLG_BT_DIS = BIT(12), + TX_CMD_FLG_SEQ_CTL = BIT(13), + TX_CMD_FLG_MORE_FRAG = BIT(14), + TX_CMD_FLG_NEXT_FRAME = BIT(15), + TX_CMD_FLG_TSF = BIT(16), + TX_CMD_FLG_CALIB = BIT(17), + TX_CMD_FLG_KEEP_SEQ_CTL = BIT(18), + TX_CMD_FLG_AGG_START = BIT(19), + TX_CMD_FLG_MH_PAD = BIT(20), + TX_CMD_FLG_RESP_TO_DRV = BIT(21), + TX_CMD_FLG_CCMP_AGG = BIT(22), + TX_CMD_FLG_TKIP_MIC_DONE = BIT(23), + TX_CMD_FLG_CTS_ONLY = BIT(24), + TX_CMD_FLG_DUR = BIT(25), + TX_CMD_FLG_FW_DROP = BIT(26), + TX_CMD_FLG_EXEC_PAPD = BIT(27), + TX_CMD_FLG_PAPD_TYPE = BIT(28), + TX_CMD_FLG_HCCA_CHUNK = BIT(31) +}; /* TX_FLAGS_BITS_API_S_VER_1 */ + +/* + * TX command security control + */ +#define TX_CMD_SEC_WEP 0x01 +#define TX_CMD_SEC_CCM 0x02 +#define TX_CMD_SEC_TKIP 0x03 +#define TX_CMD_SEC_WEP_KEY_IDX_POS 6 +#define TX_CMD_SEC_WEP_KEY_IDX_MSK 0xc0 +#define TX_CMD_SEC_KEY128 0x08 + +/* TODO: how does these values are OK with only 16 bit variable??? */ +/* + * TX command next frame info + * + * bits 0:2 - security control (TX_CMD_SEC_*) + * bit 3 - immediate ACK required + * bit 4 - rate is taken from STA table + * bit 5 - frame belongs to BA stream + * bit 6 - immediate BA response expected + * bit 7 - unused + * bits 8:15 - Station ID + * bits 16:31 - rate + */ +#define TX_CMD_NEXT_FRAME_ACK_MSK (0x8) +#define TX_CMD_NEXT_FRAME_STA_RATE_MSK (0x10) +#define TX_CMD_NEXT_FRAME_BA_MSK (0x20) +#define TX_CMD_NEXT_FRAME_IMM_BA_RSP_MSK (0x40) +#define TX_CMD_NEXT_FRAME_FLAGS_MSK (0xf8) +#define TX_CMD_NEXT_FRAME_STA_ID_MSK (0xff00) +#define TX_CMD_NEXT_FRAME_STA_ID_POS (8) +#define TX_CMD_NEXT_FRAME_RATE_MSK (0xffff0000) +#define TX_CMD_NEXT_FRAME_RATE_POS (16) + +/* + * TX command Frame life time in us - to be written in pm_frame_timeout + */ +#define TX_CMD_LIFE_TIME_INFINITE 0xFFFFFFFF +#define TX_CMD_LIFE_TIME_DEFAULT 2000000 /* 2000 ms*/ +#define TX_CMD_LIFE_TIME_PROBE_RESP 40000 /* 40 ms */ +#define TX_CMD_LIFE_TIME_EXPIRED_FRAME 0 + +/* + * TID for non QoS frames - to be written in tid_tspec + */ +#define IWL_TID_NON_QOS IWL_MAX_TID_COUNT + +/* + * Limits on the retransmissions - to be written in {data,rts}_retry_limit + */ +#define IWL_DEFAULT_TX_RETRY 15 +#define IWL_MGMT_DFAULT_RETRY_LIMIT 3 +#define IWL_RTS_DFAULT_RETRY_LIMIT 60 +#define IWL_BAR_DFAULT_RETRY_LIMIT 60 +#define IWL_LOW_RETRY_LIMIT 7 + +/* TODO: complete documentation for try_cnt and btkill_cnt */ +/** + * struct iwl_tx_cmd - TX command struct to FW + * ( TX_CMD = 0x1c ) + * @len: in bytes of the payload, see below for details + * @next_frame_len: same as len, but for next frame (0 if not applicable) + * Used for fragmentation and bursting, but not in 11n aggregation. + * @tx_flags: combination of TX_CMD_FLG_* + * @rate_n_flags: rate for *all* Tx attempts, if TX_CMD_FLG_STA_RATE_MSK is + * cleared. Combination of RATE_MCS_* + * @sta_id: index of destination station in FW station table + * @sec_ctl: security control, TX_CMD_SEC_* + * @initial_rate_index: index into the the rate table for initial TX attempt. + * Applied if TX_CMD_FLG_STA_RATE_MSK is set, normally 0 for data frames. + * @key: security key + * @next_frame_flags: TX_CMD_SEC_* and TX_CMD_NEXT_FRAME_* + * @life_time: frame life time (usecs??) + * @dram_lsb_ptr: Physical address of scratch area in the command (try_cnt + + * btkill_cnd + reserved), first 32 bits. "0" disables usage. + * @dram_msb_ptr: upper bits of the scratch physical address + * @rts_retry_limit: max attempts for RTS + * @data_retry_limit: max attempts to send the data packet + * @tid_spec: TID/tspec + * @pm_frame_timeout: PM TX frame timeout + * @driver_txop: duration od EDCA TXOP, in 32-usec units. Set this if not + * specified by HCCA protocol + * + * The byte count (both len and next_frame_len) includes MAC header + * (24/26/30/32 bytes) + * + 2 bytes pad if 26/30 header size + * + 8 byte IV for CCM or TKIP (not used for WEP) + * + Data payload + * + 8-byte MIC (not used for CCM/WEP) + * It does not include post-MAC padding, i.e., + * MIC (CCM) 8 bytes, ICV (WEP/TKIP/CKIP) 4 bytes, CRC 4 bytes. + * Range of len: 14-2342 bytes. + * + * After the struct fields the MAC header is placed, plus any padding, + * and then the actial payload. + */ +struct iwl_tx_cmd { + __le16 len; + __le16 next_frame_len; + __le32 tx_flags; + /* DRAM_SCRATCH_API_U_VER_1 */ + u8 try_cnt; + u8 btkill_cnt; + __le16 reserved; + __le32 rate_n_flags; + u8 sta_id; + u8 sec_ctl; + u8 initial_rate_index; + u8 reserved2; + u8 key[16]; + __le16 next_frame_flags; + __le16 reserved3; + __le32 life_time; + __le32 dram_lsb_ptr; + u8 dram_msb_ptr; + u8 rts_retry_limit; + u8 data_retry_limit; + u8 tid_tspec; + __le16 pm_frame_timeout; + __le16 driver_txop; + u8 payload[0]; + struct ieee80211_hdr hdr[0]; +} __packed; /* TX_CMD_API_S_VER_3 */ + +/* + * TX response related data + */ + +/* + * enum iwl_tx_status - status that is returned by the fw after attempts to Tx + * @TX_STATUS_SUCCESS: + * @TX_STATUS_DIRECT_DONE: + * @TX_STATUS_POSTPONE_DELAY: + * @TX_STATUS_POSTPONE_FEW_BYTES: + * @TX_STATUS_POSTPONE_BT_PRIO: + * @TX_STATUS_POSTPONE_QUIET_PERIOD: + * @TX_STATUS_POSTPONE_CALC_TTAK: + * @TX_STATUS_FAIL_INTERNAL_CROSSED_RETRY: + * @TX_STATUS_FAIL_SHORT_LIMIT: + * @TX_STATUS_FAIL_LONG_LIMIT: + * @TX_STATUS_FAIL_UNDERRUN: + * @TX_STATUS_FAIL_DRAIN_FLOW: + * @TX_STATUS_FAIL_RFKILL_FLUSH: + * @TX_STATUS_FAIL_LIFE_EXPIRE: + * @TX_STATUS_FAIL_DEST_PS: + * @TX_STATUS_FAIL_HOST_ABORTED: + * @TX_STATUS_FAIL_BT_RETRY: + * @TX_STATUS_FAIL_STA_INVALID: + * @TX_TATUS_FAIL_FRAG_DROPPED: + * @TX_STATUS_FAIL_TID_DISABLE: + * @TX_STATUS_FAIL_FIFO_FLUSHED: + * @TX_STATUS_FAIL_SMALL_CF_POLL: + * @TX_STATUS_FAIL_FW_DROP: + * @TX_STATUS_FAIL_STA_COLOR_MISMATCH: mismatch between color of Tx cmd and + * STA table + * @TX_FRAME_STATUS_INTERNAL_ABORT: + * @TX_MODE_MSK: + * @TX_MODE_NO_BURST: + * @TX_MODE_IN_BURST_SEQ: + * @TX_MODE_FIRST_IN_BURST: + * @TX_QUEUE_NUM_MSK: + * + * Valid only if frame_count =1 + * TODO: complete documentation + */ +enum iwl_tx_status { + TX_STATUS_MSK = 0x000000ff, + TX_STATUS_SUCCESS = 0x01, + TX_STATUS_DIRECT_DONE = 0x02, + /* postpone TX */ + TX_STATUS_POSTPONE_DELAY = 0x40, + TX_STATUS_POSTPONE_FEW_BYTES = 0x41, + TX_STATUS_POSTPONE_BT_PRIO = 0x42, + TX_STATUS_POSTPONE_QUIET_PERIOD = 0x43, + TX_STATUS_POSTPONE_CALC_TTAK = 0x44, + /* abort TX */ + TX_STATUS_FAIL_INTERNAL_CROSSED_RETRY = 0x81, + TX_STATUS_FAIL_SHORT_LIMIT = 0x82, + TX_STATUS_FAIL_LONG_LIMIT = 0x83, + TX_STATUS_FAIL_UNDERRUN = 0x84, + TX_STATUS_FAIL_DRAIN_FLOW = 0x85, + TX_STATUS_FAIL_RFKILL_FLUSH = 0x86, + TX_STATUS_FAIL_LIFE_EXPIRE = 0x87, + TX_STATUS_FAIL_DEST_PS = 0x88, + TX_STATUS_FAIL_HOST_ABORTED = 0x89, + TX_STATUS_FAIL_BT_RETRY = 0x8a, + TX_STATUS_FAIL_STA_INVALID = 0x8b, + TX_STATUS_FAIL_FRAG_DROPPED = 0x8c, + TX_STATUS_FAIL_TID_DISABLE = 0x8d, + TX_STATUS_FAIL_FIFO_FLUSHED = 0x8e, + TX_STATUS_FAIL_SMALL_CF_POLL = 0x8f, + TX_STATUS_FAIL_FW_DROP = 0x90, + TX_STATUS_FAIL_STA_COLOR_MISMATCH = 0x91, + TX_STATUS_INTERNAL_ABORT = 0x92, + TX_MODE_MSK = 0x00000f00, + TX_MODE_NO_BURST = 0x00000000, + TX_MODE_IN_BURST_SEQ = 0x00000100, + TX_MODE_FIRST_IN_BURST = 0x00000200, + TX_QUEUE_NUM_MSK = 0x0001f000, + TX_NARROW_BW_MSK = 0x00060000, + TX_NARROW_BW_1DIV2 = 0x00020000, + TX_NARROW_BW_1DIV4 = 0x00040000, + TX_NARROW_BW_1DIV8 = 0x00060000, +}; + +/* + * enum iwl_tx_agg_status - TX aggregation status + * @AGG_TX_STATE_STATUS_MSK: + * @AGG_TX_STATE_TRANSMITTED: + * @AGG_TX_STATE_UNDERRUN: + * @AGG_TX_STATE_BT_PRIO: + * @AGG_TX_STATE_FEW_BYTES: + * @AGG_TX_STATE_ABORT: + * @AGG_TX_STATE_LAST_SENT_TTL: + * @AGG_TX_STATE_LAST_SENT_TRY_CNT: + * @AGG_TX_STATE_LAST_SENT_BT_KILL: + * @AGG_TX_STATE_SCD_QUERY: + * @AGG_TX_STATE_TEST_BAD_CRC32: + * @AGG_TX_STATE_RESPONSE: + * @AGG_TX_STATE_DUMP_TX: + * @AGG_TX_STATE_DELAY_TX: + * @AGG_TX_STATE_TRY_CNT_MSK: Retry count for 1st frame in aggregation (retries + * occur if tx failed for this frame when it was a member of a previous + * aggregation block). If rate scaling is used, retry count indicates the + * rate table entry used for all frames in the new agg. + *@ AGG_TX_STATE_SEQ_NUM_MSK: Command ID and sequence number of Tx command for + * this frame + * + * TODO: complete documentation + */ +enum iwl_tx_agg_status { + AGG_TX_STATE_STATUS_MSK = 0x00fff, + AGG_TX_STATE_TRANSMITTED = 0x000, + AGG_TX_STATE_UNDERRUN = 0x001, + AGG_TX_STATE_BT_PRIO = 0x002, + AGG_TX_STATE_FEW_BYTES = 0x004, + AGG_TX_STATE_ABORT = 0x008, + AGG_TX_STATE_LAST_SENT_TTL = 0x010, + AGG_TX_STATE_LAST_SENT_TRY_CNT = 0x020, + AGG_TX_STATE_LAST_SENT_BT_KILL = 0x040, + AGG_TX_STATE_SCD_QUERY = 0x080, + AGG_TX_STATE_TEST_BAD_CRC32 = 0x0100, + AGG_TX_STATE_RESPONSE = 0x1ff, + AGG_TX_STATE_DUMP_TX = 0x200, + AGG_TX_STATE_DELAY_TX = 0x400, + AGG_TX_STATE_TRY_CNT_POS = 12, + AGG_TX_STATE_TRY_CNT_MSK = 0xf << AGG_TX_STATE_TRY_CNT_POS, +}; + +#define AGG_TX_STATE_LAST_SENT_MSK (AGG_TX_STATE_LAST_SENT_TTL| \ + AGG_TX_STATE_LAST_SENT_TRY_CNT| \ + AGG_TX_STATE_LAST_SENT_BT_KILL) + +/* + * The mask below describes a status where we are absolutely sure that the MPDU + * wasn't sent. For BA/Underrun we cannot be that sure. All we know that we've + * written the bytes to the TXE, but we know nothing about what the DSP did. + */ +#define AGG_TX_STAT_FRAME_NOT_SENT (AGG_TX_STATE_FEW_BYTES | \ + AGG_TX_STATE_ABORT | \ + AGG_TX_STATE_SCD_QUERY) + +/* + * REPLY_TX = 0x1c (response) + * + * This response may be in one of two slightly different formats, indicated + * by the frame_count field: + * + * 1) No aggregation (frame_count == 1). This reports Tx results for a single + * frame. Multiple attempts, at various bit rates, may have been made for + * this frame. + * + * 2) Aggregation (frame_count > 1). This reports Tx results for two or more + * frames that used block-acknowledge. All frames were transmitted at + * same rate. Rate scaling may have been used if first frame in this new + * agg block failed in previous agg block(s). + * + * Note that, for aggregation, ACK (block-ack) status is not delivered + * here; block-ack has not been received by the time the device records + * this status. + * This status relates to reasons the tx might have been blocked or aborted + * within the device, rather than whether it was received successfully by + * the destination station. + */ + +/** + * struct agg_tx_status - per packet TX aggregation status + * @status: enum iwl_tx_agg_status + * @sequence: Sequence # for this frame's Tx cmd (not SSN!) + */ +struct agg_tx_status { + __le16 status; + __le16 sequence; +} __packed; + +/* + * definitions for initial rate index field + * bits [3:0] initial rate index + * bits [6:4] rate table color, used for the initial rate + * bit-7 invalid rate indication + */ +#define TX_RES_INIT_RATE_INDEX_MSK 0x0f +#define TX_RES_RATE_TABLE_COLOR_MSK 0x70 +#define TX_RES_INV_RATE_INDEX_MSK 0x80 + +#define IWL_MVM_TX_RES_GET_TID(_ra_tid) ((_ra_tid) & 0x0f) +#define IWL_MVM_TX_RES_GET_RA(_ra_tid) ((_ra_tid) >> 4) + +/** + * struct iwl_mvm_tx_resp - notifies that fw is TXing a packet + * ( REPLY_TX = 0x1c ) + * @frame_count: 1 no aggregation, >1 aggregation + * @bt_kill_count: num of times blocked by bluetooth (unused for agg) + * @failure_rts: num of failures due to unsuccessful RTS + * @failure_frame: num failures due to no ACK (unused for agg) + * @initial_rate: for non-agg: rate of the successful Tx. For agg: rate of the + * Tx of all the batch. RATE_MCS_* + * @wireless_media_time: for non-agg: RTS + CTS + frame tx attempts time + ACK. + * for agg: RTS + CTS + aggregation tx time + block-ack time. + * in usec. + * @pa_status: tx power info + * @pa_integ_res_a: tx power info + * @pa_integ_res_b: tx power info + * @pa_integ_res_c: tx power info + * @measurement_req_id: tx power info + * @tfd_info: TFD information set by the FH + * @seq_ctl: sequence control from the Tx cmd + * @byte_cnt: byte count from the Tx cmd + * @tlc_info: TLC rate info + * @ra_tid: bits [3:0] = ra, bits [7:4] = tid + * @frame_ctrl: frame control + * @status: for non-agg: frame status TX_STATUS_* + * for agg: status of 1st frame, AGG_TX_STATE_*; other frame status fields + * follow this one, up to frame_count. + * + * After the array of statuses comes the SSN of the SCD. Look at + * %iwl_mvm_get_scd_ssn for more details. + */ +struct iwl_mvm_tx_resp { + u8 frame_count; + u8 bt_kill_count; + u8 failure_rts; + u8 failure_frame; + __le32 initial_rate; + __le16 wireless_media_time; + + u8 pa_status; + u8 pa_integ_res_a[3]; + u8 pa_integ_res_b[3]; + u8 pa_integ_res_c[3]; + __le16 measurement_req_id; + __le16 reserved; + + __le32 tfd_info; + __le16 seq_ctl; + __le16 byte_cnt; + u8 tlc_info; + u8 ra_tid; + __le16 frame_ctrl; + + struct agg_tx_status status; +} __packed; /* TX_RSP_API_S_VER_3 */ + +/** + * struct iwl_mvm_ba_notif - notifies about reception of BA + * ( BA_NOTIF = 0xc5 ) + * @sta_addr_lo32: lower 32 bits of the MAC address + * @sta_addr_hi16: upper 16 bits of the MAC address + * @sta_id: Index of recipient (BA-sending) station in fw's station table + * @tid: tid of the session + * @seq_ctl: + * @bitmap: the bitmap of the BA notification as seen in the air + * @scd_flow: the tx queue this BA relates to + * @scd_ssn: the index of the last contiguously sent packet + * @txed: number of Txed frames in this batch + * @txed_2_done: number of Acked frames in this batch + */ +struct iwl_mvm_ba_notif { + __le32 sta_addr_lo32; + __le16 sta_addr_hi16; + __le16 reserved; + + u8 sta_id; + u8 tid; + __le16 seq_ctl; + __le64 bitmap; + __le16 scd_flow; + __le16 scd_ssn; + u8 txed; + u8 txed_2_done; + __le16 reserved1; +} __packed; + +/* + * struct iwl_mac_beacon_cmd - beacon template command + * @tx: the tx commands associated with the beacon frame + * @template_id: currently equal to the mac context id of the coresponding + * mac. + * @tim_idx: the offset of the tim IE in the beacon + * @tim_size: the length of the tim IE + * @frame: the template of the beacon frame + */ +struct iwl_mac_beacon_cmd { + struct iwl_tx_cmd tx; + __le32 template_id; + __le32 tim_idx; + __le32 tim_size; + struct ieee80211_hdr frame[0]; +} __packed; + +struct iwl_beacon_notif { + struct iwl_mvm_tx_resp beacon_notify_hdr; + __le64 tsf; + __le32 ibss_mgr_status; +} __packed; + +/** + * enum iwl_dump_control - dump (flush) control flags + * @DUMP_TX_FIFO_FLUSH: Dump MSDUs until the the FIFO is empty + * and the TFD queues are empty. + */ +enum iwl_dump_control { + DUMP_TX_FIFO_FLUSH = BIT(1), +}; + +/** + * struct iwl_tx_path_flush_cmd -- queue/FIFO flush command + * @queues_ctl: bitmap of queues to flush + * @flush_ctl: control flags + * @reserved: reserved + */ +struct iwl_tx_path_flush_cmd { + __le32 queues_ctl; + __le16 flush_ctl; + __le16 reserved; +} __packed; /* TX_PATH_FLUSH_CMD_API_S_VER_1 */ + +/** + * iwl_mvm_get_scd_ssn - returns the SSN of the SCD + * @tx_resp: the Tx response from the fw (agg or non-agg) + * + * When the fw sends an AMPDU, it fetches the MPDUs one after the other. Since + * it can't know that everything will go well until the end of the AMPDU, it + * can't know in advance the number of MPDUs that will be sent in the current + * batch. This is why it writes the agg Tx response while it fetches the MPDUs. + * Hence, it can't know in advance what the SSN of the SCD will be at the end + * of the batch. This is why the SSN of the SCD is written at the end of the + * whole struct at a variable offset. This function knows how to cope with the + * variable offset and returns the SSN of the SCD. + */ +static inline u32 iwl_mvm_get_scd_ssn(struct iwl_mvm_tx_resp *tx_resp) +{ + return le32_to_cpup((__le32 *)&tx_resp->status + + tx_resp->frame_count) & 0xfff; +} + +#endif /* __fw_api_tx_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api.h b/drivers/net/wireless/iwlwifi/mvm/fw-api.h new file mode 100644 index 0000000..c638455 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api.h @@ -0,0 +1,978 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#ifndef __fw_api_h__ +#define __fw_api_h__ + +#include "fw-api-rs.h" +#include "fw-api-tx.h" +#include "fw-api-sta.h" +#include "fw-api-mac.h" +#include "fw-api-power.h" +#include "fw-api-d3.h" +#include "fw-api-bt-coex.h" + +/* queue and FIFO numbers by usage */ +enum { + IWL_MVM_OFFCHANNEL_QUEUE = 8, + IWL_MVM_CMD_QUEUE = 9, + IWL_MVM_AUX_QUEUE = 15, + IWL_MVM_FIRST_AGG_QUEUE = 16, + IWL_MVM_NUM_QUEUES = 20, + IWL_MVM_LAST_AGG_QUEUE = IWL_MVM_NUM_QUEUES - 1, + IWL_MVM_CMD_FIFO = 7 +}; + +#define IWL_MVM_STATION_COUNT 16 + +/* commands */ +enum { + MVM_ALIVE = 0x1, + REPLY_ERROR = 0x2, + + INIT_COMPLETE_NOTIF = 0x4, + + /* PHY context commands */ + PHY_CONTEXT_CMD = 0x8, + DBG_CFG = 0x9, + + /* station table */ + ADD_STA = 0x18, + REMOVE_STA = 0x19, + + /* TX */ + TX_CMD = 0x1c, + TXPATH_FLUSH = 0x1e, + MGMT_MCAST_KEY = 0x1f, + + /* global key */ + WEP_KEY = 0x20, + + /* MAC and Binding commands */ + MAC_CONTEXT_CMD = 0x28, + TIME_EVENT_CMD = 0x29, /* both CMD and response */ + TIME_EVENT_NOTIFICATION = 0x2a, + BINDING_CONTEXT_CMD = 0x2b, + TIME_QUOTA_CMD = 0x2c, + + LQ_CMD = 0x4e, + + /* Calibration */ + TEMPERATURE_NOTIFICATION = 0x62, + CALIBRATION_CFG_CMD = 0x65, + CALIBRATION_RES_NOTIFICATION = 0x66, + CALIBRATION_COMPLETE_NOTIFICATION = 0x67, + RADIO_VERSION_NOTIFICATION = 0x68, + + /* Scan offload */ + SCAN_OFFLOAD_REQUEST_CMD = 0x51, + SCAN_OFFLOAD_ABORT_CMD = 0x52, + SCAN_OFFLOAD_COMPLETE = 0x6D, + SCAN_OFFLOAD_UPDATE_PROFILES_CMD = 0x6E, + SCAN_OFFLOAD_CONFIG_CMD = 0x6f, + + /* Phy */ + PHY_CONFIGURATION_CMD = 0x6a, + CALIB_RES_NOTIF_PHY_DB = 0x6b, + /* PHY_DB_CMD = 0x6c, */ + + /* Power */ + POWER_TABLE_CMD = 0x77, + + /* Scanning */ + SCAN_REQUEST_CMD = 0x80, + SCAN_ABORT_CMD = 0x81, + SCAN_START_NOTIFICATION = 0x82, + SCAN_RESULTS_NOTIFICATION = 0x83, + SCAN_COMPLETE_NOTIFICATION = 0x84, + + /* NVM */ + NVM_ACCESS_CMD = 0x88, + + SET_CALIB_DEFAULT_CMD = 0x8e, + + BEACON_NOTIFICATION = 0x90, + BEACON_TEMPLATE_CMD = 0x91, + TX_ANT_CONFIGURATION_CMD = 0x98, + BT_CONFIG = 0x9b, + STATISTICS_NOTIFICATION = 0x9d, + + /* RF-KILL commands and notifications */ + CARD_STATE_CMD = 0xa0, + CARD_STATE_NOTIFICATION = 0xa1, + + REPLY_RX_PHY_CMD = 0xc0, + REPLY_RX_MPDU_CMD = 0xc1, + BA_NOTIF = 0xc5, + + /* BT Coex */ + BT_COEX_PRIO_TABLE = 0xcc, + BT_COEX_PROT_ENV = 0xcd, + BT_PROFILE_NOTIFICATION = 0xce, + + REPLY_DEBUG_CMD = 0xf0, + DEBUG_LOG_MSG = 0xf7, + + MCAST_FILTER_CMD = 0xd0, + + /* D3 commands/notifications */ + D3_CONFIG_CMD = 0xd3, + PROT_OFFLOAD_CONFIG_CMD = 0xd4, + OFFLOADS_QUERY_CMD = 0xd5, + REMOTE_WAKE_CONFIG_CMD = 0xd6, + + /* for WoWLAN in particular */ + WOWLAN_PATTERNS = 0xe0, + WOWLAN_CONFIGURATION = 0xe1, + WOWLAN_TSC_RSC_PARAM = 0xe2, + WOWLAN_TKIP_PARAM = 0xe3, + WOWLAN_KEK_KCK_MATERIAL = 0xe4, + WOWLAN_GET_STATUSES = 0xe5, + WOWLAN_TX_POWER_PER_DB = 0xe6, + + /* and for NetDetect */ + NET_DETECT_CONFIG_CMD = 0x54, + NET_DETECT_PROFILES_QUERY_CMD = 0x56, + NET_DETECT_PROFILES_CMD = 0x57, + NET_DETECT_HOTSPOTS_CMD = 0x58, + NET_DETECT_HOTSPOTS_QUERY_CMD = 0x59, + + REPLY_MAX = 0xff, +}; + +/** + * struct iwl_cmd_response - generic response struct for most commands + * @status: status of the command asked, changes for each one + */ +struct iwl_cmd_response { + __le32 status; +}; + +/* + * struct iwl_tx_ant_cfg_cmd + * @valid: valid antenna configuration + */ +struct iwl_tx_ant_cfg_cmd { + __le32 valid; +} __packed; + +/* + * Calibration control struct. + * Sent as part of the phy configuration command. + * @flow_trigger: bitmap for which calibrations to perform according to + * flow triggers. + * @event_trigger: bitmap for which calibrations to perform according to + * event triggers. + */ +struct iwl_calib_ctrl { + __le32 flow_trigger; + __le32 event_trigger; +} __packed; + +/* This enum defines the bitmap of various calibrations to enable in both + * init ucode and runtime ucode through CALIBRATION_CFG_CMD. + */ +enum iwl_calib_cfg { + IWL_CALIB_CFG_XTAL_IDX = BIT(0), + IWL_CALIB_CFG_TEMPERATURE_IDX = BIT(1), + IWL_CALIB_CFG_VOLTAGE_READ_IDX = BIT(2), + IWL_CALIB_CFG_PAPD_IDX = BIT(3), + IWL_CALIB_CFG_TX_PWR_IDX = BIT(4), + IWL_CALIB_CFG_DC_IDX = BIT(5), + IWL_CALIB_CFG_BB_FILTER_IDX = BIT(6), + IWL_CALIB_CFG_LO_LEAKAGE_IDX = BIT(7), + IWL_CALIB_CFG_TX_IQ_IDX = BIT(8), + IWL_CALIB_CFG_TX_IQ_SKEW_IDX = BIT(9), + IWL_CALIB_CFG_RX_IQ_IDX = BIT(10), + IWL_CALIB_CFG_RX_IQ_SKEW_IDX = BIT(11), + IWL_CALIB_CFG_SENSITIVITY_IDX = BIT(12), + IWL_CALIB_CFG_CHAIN_NOISE_IDX = BIT(13), + IWL_CALIB_CFG_DISCONNECTED_ANT_IDX = BIT(14), + IWL_CALIB_CFG_ANT_COUPLING_IDX = BIT(15), + IWL_CALIB_CFG_DAC_IDX = BIT(16), + IWL_CALIB_CFG_ABS_IDX = BIT(17), + IWL_CALIB_CFG_AGC_IDX = BIT(18), +}; + +/* + * Phy configuration command. + */ +struct iwl_phy_cfg_cmd { + __le32 phy_cfg; + struct iwl_calib_ctrl calib_control; +} __packed; + +#define PHY_CFG_RADIO_TYPE (BIT(0) | BIT(1)) +#define PHY_CFG_RADIO_STEP (BIT(2) | BIT(3)) +#define PHY_CFG_RADIO_DASH (BIT(4) | BIT(5)) +#define PHY_CFG_PRODUCT_NUMBER (BIT(6) | BIT(7)) +#define PHY_CFG_TX_CHAIN_A BIT(8) +#define PHY_CFG_TX_CHAIN_B BIT(9) +#define PHY_CFG_TX_CHAIN_C BIT(10) +#define PHY_CFG_RX_CHAIN_A BIT(12) +#define PHY_CFG_RX_CHAIN_B BIT(13) +#define PHY_CFG_RX_CHAIN_C BIT(14) + + +/* Target of the NVM_ACCESS_CMD */ +enum { + NVM_ACCESS_TARGET_CACHE = 0, + NVM_ACCESS_TARGET_OTP = 1, + NVM_ACCESS_TARGET_EEPROM = 2, +}; + +/* Section types for NVM_ACCESS_CMD */ +enum { + NVM_SECTION_TYPE_HW = 0, + NVM_SECTION_TYPE_SW, + NVM_SECTION_TYPE_PAPD, + NVM_SECTION_TYPE_BT, + NVM_SECTION_TYPE_CALIBRATION, + NVM_SECTION_TYPE_PRODUCTION, + NVM_SECTION_TYPE_POST_FCS_CALIB, + NVM_NUM_OF_SECTIONS, +}; + +/** + * struct iwl_nvm_access_cmd_ver2 - Request the device to send an NVM section + * @op_code: 0 - read, 1 - write + * @target: NVM_ACCESS_TARGET_* + * @type: NVM_SECTION_TYPE_* + * @offset: offset in bytes into the section + * @length: in bytes, to read/write + * @data: if write operation, the data to write. On read its empty + */ +struct iwl_nvm_access_cmd { + u8 op_code; + u8 target; + __le16 type; + __le16 offset; + __le16 length; + u8 data[]; +} __packed; /* NVM_ACCESS_CMD_API_S_VER_2 */ + +/** + * struct iwl_nvm_access_resp_ver2 - response to NVM_ACCESS_CMD + * @offset: offset in bytes into the section + * @length: in bytes, either how much was written or read + * @type: NVM_SECTION_TYPE_* + * @status: 0 for success, fail otherwise + * @data: if read operation, the data returned. Empty on write. + */ +struct iwl_nvm_access_resp { + __le16 offset; + __le16 length; + __le16 type; + __le16 status; + u8 data[]; +} __packed; /* NVM_ACCESS_CMD_RESP_API_S_VER_2 */ + +/* MVM_ALIVE 0x1 */ + +/* alive response is_valid values */ +#define ALIVE_RESP_UCODE_OK BIT(0) +#define ALIVE_RESP_RFKILL BIT(1) + +/* alive response ver_type values */ +enum { + FW_TYPE_HW = 0, + FW_TYPE_PROT = 1, + FW_TYPE_AP = 2, + FW_TYPE_WOWLAN = 3, + FW_TYPE_TIMING = 4, + FW_TYPE_WIPAN = 5 +}; + +/* alive response ver_subtype values */ +enum { + FW_SUBTYPE_FULL_FEATURE = 0, + FW_SUBTYPE_BOOTSRAP = 1, /* Not valid */ + FW_SUBTYPE_REDUCED = 2, + FW_SUBTYPE_ALIVE_ONLY = 3, + FW_SUBTYPE_WOWLAN = 4, + FW_SUBTYPE_AP_SUBTYPE = 5, + FW_SUBTYPE_WIPAN = 6, + FW_SUBTYPE_INITIALIZE = 9 +}; + +#define IWL_ALIVE_STATUS_ERR 0xDEAD +#define IWL_ALIVE_STATUS_OK 0xCAFE + +#define IWL_ALIVE_FLG_RFKILL BIT(0) + +struct mvm_alive_resp { + __le16 status; + __le16 flags; + u8 ucode_minor; + u8 ucode_major; + __le16 id; + u8 api_minor; + u8 api_major; + u8 ver_subtype; + u8 ver_type; + u8 mac; + u8 opt; + __le16 reserved2; + __le32 timestamp; + __le32 error_event_table_ptr; /* SRAM address for error log */ + __le32 log_event_table_ptr; /* SRAM address for event log */ + __le32 cpu_register_ptr; + __le32 dbgm_config_ptr; + __le32 alive_counter_ptr; + __le32 scd_base_ptr; /* SRAM address for SCD */ +} __packed; /* ALIVE_RES_API_S_VER_1 */ + +/* Error response/notification */ +enum { + FW_ERR_UNKNOWN_CMD = 0x0, + FW_ERR_INVALID_CMD_PARAM = 0x1, + FW_ERR_SERVICE = 0x2, + FW_ERR_ARC_MEMORY = 0x3, + FW_ERR_ARC_CODE = 0x4, + FW_ERR_WATCH_DOG = 0x5, + FW_ERR_WEP_GRP_KEY_INDX = 0x10, + FW_ERR_WEP_KEY_SIZE = 0x11, + FW_ERR_OBSOLETE_FUNC = 0x12, + FW_ERR_UNEXPECTED = 0xFE, + FW_ERR_FATAL = 0xFF +}; + +/** + * struct iwl_error_resp - FW error indication + * ( REPLY_ERROR = 0x2 ) + * @error_type: one of FW_ERR_* + * @cmd_id: the command ID for which the error occured + * @bad_cmd_seq_num: sequence number of the erroneous command + * @error_service: which service created the error, applicable only if + * error_type = 2, otherwise 0 + * @timestamp: TSF in usecs. + */ +struct iwl_error_resp { + __le32 error_type; + u8 cmd_id; + u8 reserved1; + __le16 bad_cmd_seq_num; + __le32 error_service; + __le64 timestamp; +} __packed; + + +/* Common PHY, MAC and Bindings definitions */ + +#define MAX_MACS_IN_BINDING (3) +#define MAX_BINDINGS (4) +#define AUX_BINDING_INDEX (3) +#define MAX_PHYS (4) + +/* Used to extract ID and color from the context dword */ +#define FW_CTXT_ID_POS (0) +#define FW_CTXT_ID_MSK (0xff << FW_CTXT_ID_POS) +#define FW_CTXT_COLOR_POS (8) +#define FW_CTXT_COLOR_MSK (0xff << FW_CTXT_COLOR_POS) +#define FW_CTXT_INVALID (0xffffffff) + +#define FW_CMD_ID_AND_COLOR(_id, _color) ((_id << FW_CTXT_ID_POS) |\ + (_color << FW_CTXT_COLOR_POS)) + +/* Possible actions on PHYs, MACs and Bindings */ +enum { + FW_CTXT_ACTION_STUB = 0, + FW_CTXT_ACTION_ADD, + FW_CTXT_ACTION_MODIFY, + FW_CTXT_ACTION_REMOVE, + FW_CTXT_ACTION_NUM +}; /* COMMON_CONTEXT_ACTION_API_E_VER_1 */ + +/* Time Events */ + +/* Time Event types, according to MAC type */ +enum iwl_time_event_type { + /* BSS Station Events */ + TE_BSS_STA_AGGRESSIVE_ASSOC, + TE_BSS_STA_ASSOC, + TE_BSS_EAP_DHCP_PROT, + TE_BSS_QUIET_PERIOD, + + /* P2P Device Events */ + TE_P2P_DEVICE_DISCOVERABLE, + TE_P2P_DEVICE_LISTEN, + TE_P2P_DEVICE_ACTION_SCAN, + TE_P2P_DEVICE_FULL_SCAN, + + /* P2P Client Events */ + TE_P2P_CLIENT_AGGRESSIVE_ASSOC, + TE_P2P_CLIENT_ASSOC, + TE_P2P_CLIENT_QUIET_PERIOD, + + /* P2P GO Events */ + TE_P2P_GO_ASSOC_PROT, + TE_P2P_GO_REPETITIVE_NOA, + TE_P2P_GO_CT_WINDOW, + + /* WiDi Sync Events */ + TE_WIDI_TX_SYNC, + + TE_MAX +}; /* MAC_EVENT_TYPE_API_E_VER_1 */ + +/* Time Event dependencies: none, on another TE, or in a specific time */ +enum { + TE_INDEPENDENT = 0, + TE_DEP_OTHER = 1, + TE_DEP_TSF = 2, + TE_EVENT_SOCIOPATHIC = 4, +}; /* MAC_EVENT_DEPENDENCY_POLICY_API_E_VER_2 */ +/* + * Supported Time event notifications configuration. + * A notification (both event and fragment) includes a status indicating weather + * the FW was able to schedule the event or not. For fragment start/end + * notification the status is always success. There is no start/end fragment + * notification for monolithic events. + * + * @TE_NOTIF_NONE: no notifications + * @TE_NOTIF_HOST_EVENT_START: request/receive notification on event start + * @TE_NOTIF_HOST_EVENT_END:request/receive notification on event end + * @TE_NOTIF_INTERNAL_EVENT_START: internal FW use + * @TE_NOTIF_INTERNAL_EVENT_END: internal FW use. + * @TE_NOTIF_HOST_FRAG_START: request/receive notification on frag start + * @TE_NOTIF_HOST_FRAG_END:request/receive notification on frag end + * @TE_NOTIF_INTERNAL_FRAG_START: internal FW use. + * @TE_NOTIF_INTERNAL_FRAG_END: internal FW use. + */ +enum { + TE_NOTIF_NONE = 0, + TE_NOTIF_HOST_EVENT_START = 0x1, + TE_NOTIF_HOST_EVENT_END = 0x2, + TE_NOTIF_INTERNAL_EVENT_START = 0x4, + TE_NOTIF_INTERNAL_EVENT_END = 0x8, + TE_NOTIF_HOST_FRAG_START = 0x10, + TE_NOTIF_HOST_FRAG_END = 0x20, + TE_NOTIF_INTERNAL_FRAG_START = 0x40, + TE_NOTIF_INTERNAL_FRAG_END = 0x80 +}; /* MAC_EVENT_ACTION_API_E_VER_2 */ + +/* + * @TE_FRAG_NONE: fragmentation of the time event is NOT allowed. + * @TE_FRAG_SINGLE: fragmentation of the time event is allowed, but only + * the first fragment is scheduled. + * @TE_FRAG_DUAL: fragmentation of the time event is allowed, but only + * the first 2 fragments are scheduled. + * @TE_FRAG_ENDLESS: fragmentation of the time event is allowed, and any number + * of fragments are valid. + * + * Other than the constant defined above, specifying a fragmentation value 'x' + * means that the event can be fragmented but only the first 'x' will be + * scheduled. + */ +enum { + TE_FRAG_NONE = 0, + TE_FRAG_SINGLE = 1, + TE_FRAG_DUAL = 2, + TE_FRAG_ENDLESS = 0xffffffff +}; + +/* Repeat the time event endlessly (until removed) */ +#define TE_REPEAT_ENDLESS (0xffffffff) +/* If a Time Event has bounded repetitions, this is the maximal value */ +#define TE_REPEAT_MAX_MSK (0x0fffffff) +/* If a Time Event can be fragmented, this is the max number of fragments */ +#define TE_FRAG_MAX_MSK (0x0fffffff) + +/** + * struct iwl_time_event_cmd - configuring Time Events + * ( TIME_EVENT_CMD = 0x29 ) + * @id_and_color: ID and color of the relevant MAC + * @action: action to perform, one of FW_CTXT_ACTION_* + * @id: this field has two meanings, depending on the action: + * If the action is ADD, then it means the type of event to add. + * For all other actions it is the unique event ID assigned when the + * event was added by the FW. + * @apply_time: When to start the Time Event (in GP2) + * @max_delay: maximum delay to event's start (apply time), in TU + * @depends_on: the unique ID of the event we depend on (if any) + * @interval: interval between repetitions, in TU + * @interval_reciprocal: 2^32 / interval + * @duration: duration of event in TU + * @repeat: how many repetitions to do, can be TE_REPEAT_ENDLESS + * @dep_policy: one of TE_INDEPENDENT, TE_DEP_OTHER, TE_DEP_TSF + * @is_present: 0 or 1, are we present or absent during the Time Event + * @max_frags: maximal number of fragments the Time Event can be divided to + * @notify: notifications using TE_NOTIF_* (whom to notify when) + */ +struct iwl_time_event_cmd { + /* COMMON_INDEX_HDR_API_S_VER_1 */ + __le32 id_and_color; + __le32 action; + __le32 id; + /* MAC_TIME_EVENT_DATA_API_S_VER_1 */ + __le32 apply_time; + __le32 max_delay; + __le32 dep_policy; + __le32 depends_on; + __le32 is_present; + __le32 max_frags; + __le32 interval; + __le32 interval_reciprocal; + __le32 duration; + __le32 repeat; + __le32 notify; +} __packed; /* MAC_TIME_EVENT_CMD_API_S_VER_1 */ + +/** + * struct iwl_time_event_resp - response structure to iwl_time_event_cmd + * @status: bit 0 indicates success, all others specify errors + * @id: the Time Event type + * @unique_id: the unique ID assigned (in ADD) or given (others) to the TE + * @id_and_color: ID and color of the relevant MAC + */ +struct iwl_time_event_resp { + __le32 status; + __le32 id; + __le32 unique_id; + __le32 id_and_color; +} __packed; /* MAC_TIME_EVENT_RSP_API_S_VER_1 */ + +/** + * struct iwl_time_event_notif - notifications of time event start/stop + * ( TIME_EVENT_NOTIFICATION = 0x2a ) + * @timestamp: action timestamp in GP2 + * @session_id: session's unique id + * @unique_id: unique id of the Time Event itself + * @id_and_color: ID and color of the relevant MAC + * @action: one of TE_NOTIF_START or TE_NOTIF_END + * @status: true if scheduled, false otherwise (not executed) + */ +struct iwl_time_event_notif { + __le32 timestamp; + __le32 session_id; + __le32 unique_id; + __le32 id_and_color; + __le32 action; + __le32 status; +} __packed; /* MAC_TIME_EVENT_NTFY_API_S_VER_1 */ + + +/* Bindings and Time Quota */ + +/** + * struct iwl_binding_cmd - configuring bindings + * ( BINDING_CONTEXT_CMD = 0x2b ) + * @id_and_color: ID and color of the relevant Binding + * @action: action to perform, one of FW_CTXT_ACTION_* + * @macs: array of MAC id and colors which belong to the binding + * @phy: PHY id and color which belongs to the binding + */ +struct iwl_binding_cmd { + /* COMMON_INDEX_HDR_API_S_VER_1 */ + __le32 id_and_color; + __le32 action; + /* BINDING_DATA_API_S_VER_1 */ + __le32 macs[MAX_MACS_IN_BINDING]; + __le32 phy; +} __packed; /* BINDING_CMD_API_S_VER_1 */ + +/* The maximal number of fragments in the FW's schedule session */ +#define IWL_MVM_MAX_QUOTA 128 + +/** + * struct iwl_time_quota_data - configuration of time quota per binding + * @id_and_color: ID and color of the relevant Binding + * @quota: absolute time quota in TU. The scheduler will try to divide the + * remainig quota (after Time Events) according to this quota. + * @max_duration: max uninterrupted context duration in TU + */ +struct iwl_time_quota_data { + __le32 id_and_color; + __le32 quota; + __le32 max_duration; +} __packed; /* TIME_QUOTA_DATA_API_S_VER_1 */ + +/** + * struct iwl_time_quota_cmd - configuration of time quota between bindings + * ( TIME_QUOTA_CMD = 0x2c ) + * @quotas: allocations per binding + */ +struct iwl_time_quota_cmd { + struct iwl_time_quota_data quotas[MAX_BINDINGS]; +} __packed; /* TIME_QUOTA_ALLOCATION_CMD_API_S_VER_1 */ + + +/* PHY context */ + +/* Supported bands */ +#define PHY_BAND_5 (0) +#define PHY_BAND_24 (1) + +/* Supported channel width, vary if there is VHT support */ +#define PHY_VHT_CHANNEL_MODE20 (0x0) +#define PHY_VHT_CHANNEL_MODE40 (0x1) +#define PHY_VHT_CHANNEL_MODE80 (0x2) +#define PHY_VHT_CHANNEL_MODE160 (0x3) + +/* + * Control channel position: + * For legacy set bit means upper channel, otherwise lower. + * For VHT - bit-2 marks if the control is lower/upper relative to center-freq + * bits-1:0 mark the distance from the center freq. for 20Mhz, offset is 0. + * center_freq + * | + * 40Mhz |_______|_______| + * 80Mhz |_______|_______|_______|_______| + * 160Mhz |_______|_______|_______|_______|_______|_______|_______|_______| + * code 011 010 001 000 | 100 101 110 111 + */ +#define PHY_VHT_CTRL_POS_1_BELOW (0x0) +#define PHY_VHT_CTRL_POS_2_BELOW (0x1) +#define PHY_VHT_CTRL_POS_3_BELOW (0x2) +#define PHY_VHT_CTRL_POS_4_BELOW (0x3) +#define PHY_VHT_CTRL_POS_1_ABOVE (0x4) +#define PHY_VHT_CTRL_POS_2_ABOVE (0x5) +#define PHY_VHT_CTRL_POS_3_ABOVE (0x6) +#define PHY_VHT_CTRL_POS_4_ABOVE (0x7) + +/* + * @band: PHY_BAND_* + * @channel: channel number + * @width: PHY_[VHT|LEGACY]_CHANNEL_* + * @ctrl channel: PHY_[VHT|LEGACY]_CTRL_* + */ +struct iwl_fw_channel_info { + u8 band; + u8 channel; + u8 width; + u8 ctrl_pos; +} __packed; + +#define PHY_RX_CHAIN_DRIVER_FORCE_POS (0) +#define PHY_RX_CHAIN_DRIVER_FORCE_MSK \ + (0x1 << PHY_RX_CHAIN_DRIVER_FORCE_POS) +#define PHY_RX_CHAIN_VALID_POS (1) +#define PHY_RX_CHAIN_VALID_MSK \ + (0x7 << PHY_RX_CHAIN_VALID_POS) +#define PHY_RX_CHAIN_FORCE_SEL_POS (4) +#define PHY_RX_CHAIN_FORCE_SEL_MSK \ + (0x7 << PHY_RX_CHAIN_FORCE_SEL_POS) +#define PHY_RX_CHAIN_FORCE_MIMO_SEL_POS (7) +#define PHY_RX_CHAIN_FORCE_MIMO_SEL_MSK \ + (0x7 << PHY_RX_CHAIN_FORCE_MIMO_SEL_POS) +#define PHY_RX_CHAIN_CNT_POS (10) +#define PHY_RX_CHAIN_CNT_MSK \ + (0x3 << PHY_RX_CHAIN_CNT_POS) +#define PHY_RX_CHAIN_MIMO_CNT_POS (12) +#define PHY_RX_CHAIN_MIMO_CNT_MSK \ + (0x3 << PHY_RX_CHAIN_MIMO_CNT_POS) +#define PHY_RX_CHAIN_MIMO_FORCE_POS (14) +#define PHY_RX_CHAIN_MIMO_FORCE_MSK \ + (0x1 << PHY_RX_CHAIN_MIMO_FORCE_POS) + +/* TODO: fix the value, make it depend on firmware at runtime? */ +#define NUM_PHY_CTX 3 + +/* TODO: complete missing documentation */ +/** + * struct iwl_phy_context_cmd - config of the PHY context + * ( PHY_CONTEXT_CMD = 0x8 ) + * @id_and_color: ID and color of the relevant Binding + * @action: action to perform, one of FW_CTXT_ACTION_* + * @apply_time: 0 means immediate apply and context switch. + * other value means apply new params after X usecs + * @tx_param_color: ??? + * @channel_info: + * @txchain_info: ??? + * @rxchain_info: ??? + * @acquisition_data: ??? + * @dsp_cfg_flags: set to 0 + */ +struct iwl_phy_context_cmd { + /* COMMON_INDEX_HDR_API_S_VER_1 */ + __le32 id_and_color; + __le32 action; + /* PHY_CONTEXT_DATA_API_S_VER_1 */ + __le32 apply_time; + __le32 tx_param_color; + struct iwl_fw_channel_info ci; + __le32 txchain_info; + __le32 rxchain_info; + __le32 acquisition_data; + __le32 dsp_cfg_flags; +} __packed; /* PHY_CONTEXT_CMD_API_VER_1 */ + +#define IWL_RX_INFO_PHY_CNT 8 +#define IWL_RX_INFO_AGC_IDX 1 +#define IWL_RX_INFO_RSSI_AB_IDX 2 +#define IWL_OFDM_AGC_A_MSK 0x0000007f +#define IWL_OFDM_AGC_A_POS 0 +#define IWL_OFDM_AGC_B_MSK 0x00003f80 +#define IWL_OFDM_AGC_B_POS 7 +#define IWL_OFDM_AGC_CODE_MSK 0x3fe00000 +#define IWL_OFDM_AGC_CODE_POS 20 +#define IWL_OFDM_RSSI_INBAND_A_MSK 0x00ff +#define IWL_OFDM_RSSI_A_POS 0 +#define IWL_OFDM_RSSI_ALLBAND_A_MSK 0xff00 +#define IWL_OFDM_RSSI_ALLBAND_A_POS 8 +#define IWL_OFDM_RSSI_INBAND_B_MSK 0xff0000 +#define IWL_OFDM_RSSI_B_POS 16 +#define IWL_OFDM_RSSI_ALLBAND_B_MSK 0xff000000 +#define IWL_OFDM_RSSI_ALLBAND_B_POS 24 + +/** + * struct iwl_rx_phy_info - phy info + * (REPLY_RX_PHY_CMD = 0xc0) + * @non_cfg_phy_cnt: non configurable DSP phy data byte count + * @cfg_phy_cnt: configurable DSP phy data byte count + * @stat_id: configurable DSP phy data set ID + * @reserved1: + * @system_timestamp: GP2 at on air rise + * @timestamp: TSF at on air rise + * @beacon_time_stamp: beacon at on-air rise + * @phy_flags: general phy flags: band, modulation, ... + * @channel: channel number + * @non_cfg_phy_buf: for various implementations of non_cfg_phy + * @rate_n_flags: RATE_MCS_* + * @byte_count: frame's byte-count + * @frame_time: frame's time on the air, based on byte count and frame rate + * calculation + * @mac_active_msk: what MACs were active when the frame was received + * + * Before each Rx, the device sends this data. It contains PHY information + * about the reception of the packet. + */ +struct iwl_rx_phy_info { + u8 non_cfg_phy_cnt; + u8 cfg_phy_cnt; + u8 stat_id; + u8 reserved1; + __le32 system_timestamp; + __le64 timestamp; + __le32 beacon_time_stamp; + __le16 phy_flags; + __le16 channel; + __le32 non_cfg_phy[IWL_RX_INFO_PHY_CNT]; + __le32 rate_n_flags; + __le32 byte_count; + __le16 mac_active_msk; + __le16 frame_time; +} __packed; + +struct iwl_rx_mpdu_res_start { + __le16 byte_count; + __le16 reserved; +} __packed; + +/** + * enum iwl_rx_phy_flags - to parse %iwl_rx_phy_info phy_flags + * @RX_RES_PHY_FLAGS_BAND_24: true if the packet was received on 2.4 band + * @RX_RES_PHY_FLAGS_MOD_CCK: + * @RX_RES_PHY_FLAGS_SHORT_PREAMBLE: true if packet's preamble was short + * @RX_RES_PHY_FLAGS_NARROW_BAND: + * @RX_RES_PHY_FLAGS_ANTENNA: antenna on which the packet was received + * @RX_RES_PHY_FLAGS_AGG: set if the packet was part of an A-MPDU + * @RX_RES_PHY_FLAGS_OFDM_HT: The frame was an HT frame + * @RX_RES_PHY_FLAGS_OFDM_GF: The frame used GF preamble + * @RX_RES_PHY_FLAGS_OFDM_VHT: The frame was a VHT frame + */ +enum iwl_rx_phy_flags { + RX_RES_PHY_FLAGS_BAND_24 = BIT(0), + RX_RES_PHY_FLAGS_MOD_CCK = BIT(1), + RX_RES_PHY_FLAGS_SHORT_PREAMBLE = BIT(2), + RX_RES_PHY_FLAGS_NARROW_BAND = BIT(3), + RX_RES_PHY_FLAGS_ANTENNA = (0x7 << 4), + RX_RES_PHY_FLAGS_ANTENNA_POS = 4, + RX_RES_PHY_FLAGS_AGG = BIT(7), + RX_RES_PHY_FLAGS_OFDM_HT = BIT(8), + RX_RES_PHY_FLAGS_OFDM_GF = BIT(9), + RX_RES_PHY_FLAGS_OFDM_VHT = BIT(10), +}; + +/** + * enum iwl_mvm_rx_status - written by fw for each Rx packet + * @RX_MPDU_RES_STATUS_CRC_OK: CRC is fine + * @RX_MPDU_RES_STATUS_OVERRUN_OK: there was no RXE overflow + * @RX_MPDU_RES_STATUS_SRC_STA_FOUND: + * @RX_MPDU_RES_STATUS_KEY_VALID: + * @RX_MPDU_RES_STATUS_KEY_PARAM_OK: + * @RX_MPDU_RES_STATUS_ICV_OK: ICV is fine, if not, the packet is destroyed + * @RX_MPDU_RES_STATUS_MIC_OK: used for CCM alg only. TKIP MIC is checked + * in the driver. + * @RX_MPDU_RES_STATUS_TTAK_OK: TTAK is fine + * @RX_MPDU_RES_STATUS_MNG_FRAME_REPLAY_ERR: valid for alg = CCM_CMAC or + * alg = CCM only. Checks replay attack for 11w frames. Relevant only if + * %RX_MPDU_RES_STATUS_ROBUST_MNG_FRAME is set. + * @RX_MPDU_RES_STATUS_SEC_NO_ENC: this frame is not encrypted + * @RX_MPDU_RES_STATUS_SEC_WEP_ENC: this frame is encrypted using WEP + * @RX_MPDU_RES_STATUS_SEC_CCM_ENC: this frame is encrypted using CCM + * @RX_MPDU_RES_STATUS_SEC_TKIP_ENC: this frame is encrypted using TKIP + * @RX_MPDU_RES_STATUS_SEC_CCM_CMAC_ENC: this frame is encrypted using CCM_CMAC + * @RX_MPDU_RES_STATUS_SEC_ENC_ERR: this frame couldn't be decrypted + * @RX_MPDU_RES_STATUS_SEC_ENC_MSK: bitmask of the encryption algorithm + * @RX_MPDU_RES_STATUS_DEC_DONE: this frame has been successfully decrypted + * @RX_MPDU_RES_STATUS_PROTECT_FRAME_BIT_CMP: + * @RX_MPDU_RES_STATUS_EXT_IV_BIT_CMP: + * @RX_MPDU_RES_STATUS_KEY_ID_CMP_BIT: + * @RX_MPDU_RES_STATUS_ROBUST_MNG_FRAME: this frame is an 11w management frame + * @RX_MPDU_RES_STATUS_HASH_INDEX_MSK: + * @RX_MPDU_RES_STATUS_STA_ID_MSK: + * @RX_MPDU_RES_STATUS_RRF_KILL: + * @RX_MPDU_RES_STATUS_FILTERING_MSK: + * @RX_MPDU_RES_STATUS2_FILTERING_MSK: + */ +enum iwl_mvm_rx_status { + RX_MPDU_RES_STATUS_CRC_OK = BIT(0), + RX_MPDU_RES_STATUS_OVERRUN_OK = BIT(1), + RX_MPDU_RES_STATUS_SRC_STA_FOUND = BIT(2), + RX_MPDU_RES_STATUS_KEY_VALID = BIT(3), + RX_MPDU_RES_STATUS_KEY_PARAM_OK = BIT(4), + RX_MPDU_RES_STATUS_ICV_OK = BIT(5), + RX_MPDU_RES_STATUS_MIC_OK = BIT(6), + RX_MPDU_RES_STATUS_TTAK_OK = BIT(7), + RX_MPDU_RES_STATUS_MNG_FRAME_REPLAY_ERR = BIT(7), + RX_MPDU_RES_STATUS_SEC_NO_ENC = (0 << 8), + RX_MPDU_RES_STATUS_SEC_WEP_ENC = (1 << 8), + RX_MPDU_RES_STATUS_SEC_CCM_ENC = (2 << 8), + RX_MPDU_RES_STATUS_SEC_TKIP_ENC = (3 << 8), + RX_MPDU_RES_STATUS_SEC_CCM_CMAC_ENC = (6 << 8), + RX_MPDU_RES_STATUS_SEC_ENC_ERR = (7 << 8), + RX_MPDU_RES_STATUS_SEC_ENC_MSK = (7 << 8), + RX_MPDU_RES_STATUS_DEC_DONE = BIT(11), + RX_MPDU_RES_STATUS_PROTECT_FRAME_BIT_CMP = BIT(12), + RX_MPDU_RES_STATUS_EXT_IV_BIT_CMP = BIT(13), + RX_MPDU_RES_STATUS_KEY_ID_CMP_BIT = BIT(14), + RX_MPDU_RES_STATUS_ROBUST_MNG_FRAME = BIT(15), + RX_MPDU_RES_STATUS_HASH_INDEX_MSK = (0x3F0000), + RX_MPDU_RES_STATUS_STA_ID_MSK = (0x1f000000), + RX_MPDU_RES_STATUS_RRF_KILL = BIT(29), + RX_MPDU_RES_STATUS_FILTERING_MSK = (0xc00000), + RX_MPDU_RES_STATUS2_FILTERING_MSK = (0xc0000000), +}; + +/** + * struct iwl_radio_version_notif - information on the radio version + * ( RADIO_VERSION_NOTIFICATION = 0x68 ) + * @radio_flavor: + * @radio_step: + * @radio_dash: + */ +struct iwl_radio_version_notif { + __le32 radio_flavor; + __le32 radio_step; + __le32 radio_dash; +} __packed; /* RADIO_VERSION_NOTOFICATION_S_VER_1 */ + +enum iwl_card_state_flags { + CARD_ENABLED = 0x00, + HW_CARD_DISABLED = 0x01, + SW_CARD_DISABLED = 0x02, + CT_KILL_CARD_DISABLED = 0x04, + HALT_CARD_DISABLED = 0x08, + CARD_DISABLED_MSK = 0x0f, + CARD_IS_RX_ON = 0x10, +}; + +/** + * struct iwl_radio_version_notif - information on the radio version + * ( CARD_STATE_NOTIFICATION = 0xa1 ) + * @flags: %iwl_card_state_flags + */ +struct iwl_card_state_notif { + __le32 flags; +} __packed; /* CARD_STATE_NTFY_API_S_VER_1 */ + +/** + * struct iwl_set_calib_default_cmd - set default value for calibration. + * ( SET_CALIB_DEFAULT_CMD = 0x8e ) + * @calib_index: the calibration to set value for + * @length: of data + * @data: the value to set for the calibration result + */ +struct iwl_set_calib_default_cmd { + __le16 calib_index; + __le16 length; + u8 data[0]; +} __packed; /* PHY_CALIB_OVERRIDE_VALUES_S */ + +#define MAX_PORT_ID_NUM 2 + +/** + * struct iwl_mcast_filter_cmd - configure multicast filter. + * @filter_own: Set 1 to filter out multicast packets sent by station itself + * @port_id: Multicast MAC addresses array specifier. This is a strange way + * to identify network interface adopted in host-device IF. + * It is used by FW as index in array of addresses. This array has + * MAX_PORT_ID_NUM members. + * @count: Number of MAC addresses in the array + * @pass_all: Set 1 to pass all multicast packets. + * @bssid: current association BSSID. + * @addr_list: Place holder for array of MAC addresses. + * IMPORTANT: add padding if necessary to ensure DWORD alignment. + */ +struct iwl_mcast_filter_cmd { + u8 filter_own; + u8 port_id; + u8 count; + u8 pass_all; + u8 bssid[6]; + u8 reserved[2]; + u8 addr_list[0]; +} __packed; /* MCAST_FILTERING_CMD_API_S_VER_1 */ + +#endif /* __fw_api_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/fw.c b/drivers/net/wireless/iwlwifi/mvm/fw.c new file mode 100644 index 0000000..e18c92d --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw.c @@ -0,0 +1,528 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#include + +#include "iwl-trans.h" +#include "iwl-op-mode.h" +#include "iwl-fw.h" +#include "iwl-debug.h" +#include "iwl-csr.h" /* for iwl_mvm_rx_card_state_notif */ +#include "iwl-io.h" /* for iwl_mvm_rx_card_state_notif */ +#include "iwl-eeprom-parse.h" + +#include "mvm.h" +#include "iwl-phy-db.h" + +#define MVM_UCODE_ALIVE_TIMEOUT HZ +#define MVM_UCODE_CALIB_TIMEOUT (2*HZ) + +#define UCODE_VALID_OK cpu_to_le32(0x1) + +/* Default calibration values for WkP - set to INIT image w/o running */ +static const u8 wkp_calib_values_rx_iq_skew[] = { 0x00, 0x00, 0x01, 0x00 }; +static const u8 wkp_calib_values_tx_iq_skew[] = { 0x01, 0x00, 0x00, 0x00 }; + +struct iwl_calib_default_data { + u16 size; + void *data; +}; + +#define CALIB_SIZE_N_DATA(_buf) {.size = sizeof(_buf), .data = &_buf} + +static const struct iwl_calib_default_data wkp_calib_default_data[12] = { + [9] = CALIB_SIZE_N_DATA(wkp_calib_values_tx_iq_skew), + [11] = CALIB_SIZE_N_DATA(wkp_calib_values_rx_iq_skew), +}; + +struct iwl_mvm_alive_data { + bool valid; + u32 scd_base_addr; +}; + +static inline const struct fw_img * +iwl_get_ucode_image(struct iwl_mvm *mvm, enum iwl_ucode_type ucode_type) +{ + if (ucode_type >= IWL_UCODE_TYPE_MAX) + return NULL; + + return &mvm->fw->img[ucode_type]; +} + +static int iwl_send_tx_ant_cfg(struct iwl_mvm *mvm, u8 valid_tx_ant) +{ + struct iwl_tx_ant_cfg_cmd tx_ant_cmd = { + .valid = cpu_to_le32(valid_tx_ant), + }; + + IWL_DEBUG_FW(mvm, "select valid tx ant: %u\n", valid_tx_ant); + return iwl_mvm_send_cmd_pdu(mvm, TX_ANT_CONFIGURATION_CMD, CMD_SYNC, + sizeof(tx_ant_cmd), &tx_ant_cmd); +} + +static bool iwl_alive_fn(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_mvm *mvm = + container_of(notif_wait, struct iwl_mvm, notif_wait); + struct iwl_mvm_alive_data *alive_data = data; + struct mvm_alive_resp *palive; + + palive = (void *)pkt->data; + + mvm->error_event_table = le32_to_cpu(palive->error_event_table_ptr); + mvm->log_event_table = le32_to_cpu(palive->log_event_table_ptr); + alive_data->scd_base_addr = le32_to_cpu(palive->scd_base_ptr); + + alive_data->valid = le16_to_cpu(palive->status) == IWL_ALIVE_STATUS_OK; + IWL_DEBUG_FW(mvm, + "Alive ucode status 0x%04x revision 0x%01X 0x%01X flags 0x%01X\n", + le16_to_cpu(palive->status), palive->ver_type, + palive->ver_subtype, palive->flags); + + return true; +} + +static bool iwl_wait_phy_db_entry(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_phy_db *phy_db = data; + + if (pkt->hdr.cmd != CALIB_RES_NOTIF_PHY_DB) { + WARN_ON(pkt->hdr.cmd != INIT_COMPLETE_NOTIF); + return true; + } + + WARN_ON(iwl_phy_db_set_section(phy_db, pkt, GFP_ATOMIC)); + + return false; +} + +static int iwl_mvm_load_ucode_wait_alive(struct iwl_mvm *mvm, + enum iwl_ucode_type ucode_type) +{ + struct iwl_notification_wait alive_wait; + struct iwl_mvm_alive_data alive_data; + const struct fw_img *fw; + int ret, i; + enum iwl_ucode_type old_type = mvm->cur_ucode; + static const u8 alive_cmd[] = { MVM_ALIVE }; + + mvm->cur_ucode = ucode_type; + fw = iwl_get_ucode_image(mvm, ucode_type); + + mvm->ucode_loaded = false; + + if (!fw) + return -EINVAL; + + iwl_init_notification_wait(&mvm->notif_wait, &alive_wait, + alive_cmd, ARRAY_SIZE(alive_cmd), + iwl_alive_fn, &alive_data); + + ret = iwl_trans_start_fw(mvm->trans, fw, ucode_type == IWL_UCODE_INIT); + if (ret) { + mvm->cur_ucode = old_type; + iwl_remove_notification(&mvm->notif_wait, &alive_wait); + return ret; + } + + /* + * Some things may run in the background now, but we + * just wait for the ALIVE notification here. + */ + ret = iwl_wait_notification(&mvm->notif_wait, &alive_wait, + MVM_UCODE_ALIVE_TIMEOUT); + if (ret) { + mvm->cur_ucode = old_type; + return ret; + } + + if (!alive_data.valid) { + IWL_ERR(mvm, "Loaded ucode is not valid!\n"); + mvm->cur_ucode = old_type; + return -EIO; + } + + iwl_trans_fw_alive(mvm->trans, alive_data.scd_base_addr); + + /* + * Note: all the queues are enabled as part of the interface + * initialization, but in firmware restart scenarios they + * could be stopped, so wake them up. In firmware restart, + * mac80211 will have the queues stopped as well until the + * reconfiguration completes. During normal startup, they + * will be empty. + */ + + for (i = 0; i < IWL_MAX_HW_QUEUES; i++) { + if (i < IWL_MVM_FIRST_AGG_QUEUE && i != IWL_MVM_CMD_QUEUE) + mvm->queue_to_mac80211[i] = i; + else + mvm->queue_to_mac80211[i] = IWL_INVALID_MAC80211_QUEUE; + atomic_set(&mvm->queue_stop_count[i], 0); + } + + mvm->transport_queue_stop = 0; + + mvm->ucode_loaded = true; + + return 0; +} + +static int iwl_send_phy_cfg_cmd(struct iwl_mvm *mvm) +{ + struct iwl_phy_cfg_cmd phy_cfg_cmd; + enum iwl_ucode_type ucode_type = mvm->cur_ucode; + + /* Set parameters */ + phy_cfg_cmd.phy_cfg = cpu_to_le32(mvm->fw->phy_config); + phy_cfg_cmd.calib_control.event_trigger = + mvm->fw->default_calib[ucode_type].event_trigger; + phy_cfg_cmd.calib_control.flow_trigger = + mvm->fw->default_calib[ucode_type].flow_trigger; + + IWL_DEBUG_INFO(mvm, "Sending Phy CFG command: 0x%x\n", + phy_cfg_cmd.phy_cfg); + + return iwl_mvm_send_cmd_pdu(mvm, PHY_CONFIGURATION_CMD, CMD_SYNC, + sizeof(phy_cfg_cmd), &phy_cfg_cmd); +} + +static int iwl_set_default_calibrations(struct iwl_mvm *mvm) +{ + u8 cmd_raw[16]; /* holds the variable size commands */ + struct iwl_set_calib_default_cmd *cmd = + (struct iwl_set_calib_default_cmd *)cmd_raw; + int ret, i; + + /* Setting default values for calibrations we don't run */ + for (i = 0; i < ARRAY_SIZE(wkp_calib_default_data); i++) { + u16 cmd_len; + + if (wkp_calib_default_data[i].size == 0) + continue; + + memset(cmd_raw, 0, sizeof(cmd_raw)); + cmd_len = wkp_calib_default_data[i].size + sizeof(cmd); + cmd->calib_index = cpu_to_le16(i); + cmd->length = cpu_to_le16(wkp_calib_default_data[i].size); + if (WARN_ONCE(cmd_len > sizeof(cmd_raw), + "Need to enlarge cmd_raw to %d\n", cmd_len)) + break; + memcpy(cmd->data, wkp_calib_default_data[i].data, + wkp_calib_default_data[i].size); + ret = iwl_mvm_send_cmd_pdu(mvm, SET_CALIB_DEFAULT_CMD, 0, + sizeof(*cmd) + + wkp_calib_default_data[i].size, + cmd); + if (ret) + return ret; + } + + return 0; +} + +int iwl_run_init_mvm_ucode(struct iwl_mvm *mvm, bool read_nvm) +{ + struct iwl_notification_wait calib_wait; + static const u8 init_complete[] = { + INIT_COMPLETE_NOTIF, + CALIB_RES_NOTIF_PHY_DB + }; + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (mvm->init_ucode_run) + return 0; + + iwl_init_notification_wait(&mvm->notif_wait, + &calib_wait, + init_complete, + ARRAY_SIZE(init_complete), + iwl_wait_phy_db_entry, + mvm->phy_db); + + /* Will also start the device */ + ret = iwl_mvm_load_ucode_wait_alive(mvm, IWL_UCODE_INIT); + if (ret) { + IWL_ERR(mvm, "Failed to start INIT ucode: %d\n", ret); + goto error; + } + + ret = iwl_send_bt_prio_tbl(mvm); + if (ret) + goto error; + + if (read_nvm) { + /* Read nvm */ + ret = iwl_nvm_init(mvm); + if (ret) { + IWL_ERR(mvm, "Failed to read NVM: %d\n", ret); + goto error; + } + } + + ret = iwl_nvm_check_version(mvm->nvm_data, mvm->trans); + WARN_ON(ret); + + /* Send TX valid antennas before triggering calibrations */ + ret = iwl_send_tx_ant_cfg(mvm, iwl_fw_valid_tx_ant(mvm->fw)); + if (ret) + goto error; + + /* need to set default values */ + ret = iwl_set_default_calibrations(mvm); + if (ret) + goto error; + + /* + * Send phy configurations command to init uCode + * to start the 16.0 uCode init image internal calibrations. + */ + ret = iwl_send_phy_cfg_cmd(mvm); + if (ret) { + IWL_ERR(mvm, "Failed to run INIT calibrations: %d\n", + ret); + goto error; + } + + /* + * Some things may run in the background now, but we + * just wait for the calibration complete notification. + */ + ret = iwl_wait_notification(&mvm->notif_wait, &calib_wait, + MVM_UCODE_CALIB_TIMEOUT); + if (!ret) + mvm->init_ucode_run = true; + goto out; + +error: + iwl_remove_notification(&mvm->notif_wait, &calib_wait); +out: + if (!iwlmvm_mod_params.init_dbg) { + iwl_trans_stop_device(mvm->trans); + } else if (!mvm->nvm_data) { + /* we want to debug INIT and we have no NVM - fake */ + mvm->nvm_data = kzalloc(sizeof(struct iwl_nvm_data) + + sizeof(struct ieee80211_channel) + + sizeof(struct ieee80211_rate), + GFP_KERNEL); + if (!mvm->nvm_data) + return -ENOMEM; + mvm->nvm_data->valid_rx_ant = 1; + mvm->nvm_data->valid_tx_ant = 1; + mvm->nvm_data->bands[0].channels = mvm->nvm_data->channels; + mvm->nvm_data->bands[0].n_channels = 1; + mvm->nvm_data->bands[0].n_bitrates = 1; + mvm->nvm_data->bands[0].bitrates = + (void *)mvm->nvm_data->channels + 1; + mvm->nvm_data->bands[0].bitrates->hw_value = 10; + } + + return ret; +} + +#define UCODE_CALIB_TIMEOUT (2*HZ) + +int iwl_mvm_up(struct iwl_mvm *mvm) +{ + int ret, i; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_trans_start_hw(mvm->trans); + if (ret) + return ret; + + /* If we were in RFKILL during module loading, load init ucode now */ + if (!mvm->init_ucode_run) { + ret = iwl_run_init_mvm_ucode(mvm, false); + if (ret && !iwlmvm_mod_params.init_dbg) { + IWL_ERR(mvm, "Failed to run INIT ucode: %d\n", ret); + goto error; + } + } + + if (iwlmvm_mod_params.init_dbg) + return 0; + + ret = iwl_mvm_load_ucode_wait_alive(mvm, IWL_UCODE_REGULAR); + if (ret) { + IWL_ERR(mvm, "Failed to start RT ucode: %d\n", ret); + goto error; + } + + ret = iwl_send_tx_ant_cfg(mvm, iwl_fw_valid_tx_ant(mvm->fw)); + if (ret) + goto error; + + ret = iwl_send_bt_prio_tbl(mvm); + if (ret) + goto error; + + ret = iwl_send_bt_init_conf(mvm); + if (ret) + goto error; + + /* Send phy db control command and then phy db calibration*/ + ret = iwl_send_phy_db_data(mvm->phy_db); + if (ret) + goto error; + + ret = iwl_send_phy_cfg_cmd(mvm); + if (ret) + goto error; + + /* init the fw <-> mac80211 STA mapping */ + for (i = 0; i < IWL_MVM_STATION_COUNT; i++) + RCU_INIT_POINTER(mvm->fw_id_to_mac_id[i], NULL); + + /* Add auxiliary station for scanning */ + ret = iwl_mvm_add_aux_sta(mvm); + if (ret) + goto error; + + IWL_DEBUG_INFO(mvm, "RT uCode started.\n"); + + return 0; + error: + iwl_trans_stop_device(mvm->trans); + return ret; +} + +int iwl_mvm_load_d3_fw(struct iwl_mvm *mvm) +{ + int ret, i; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_trans_start_hw(mvm->trans); + if (ret) + return ret; + + ret = iwl_mvm_load_ucode_wait_alive(mvm, IWL_UCODE_WOWLAN); + if (ret) { + IWL_ERR(mvm, "Failed to start WoWLAN firmware: %d\n", ret); + goto error; + } + + ret = iwl_send_tx_ant_cfg(mvm, iwl_fw_valid_tx_ant(mvm->fw)); + if (ret) + goto error; + + /* Send phy db control command and then phy db calibration*/ + ret = iwl_send_phy_db_data(mvm->phy_db); + if (ret) + goto error; + + ret = iwl_send_phy_cfg_cmd(mvm); + if (ret) + goto error; + + /* init the fw <-> mac80211 STA mapping */ + for (i = 0; i < IWL_MVM_STATION_COUNT; i++) + RCU_INIT_POINTER(mvm->fw_id_to_mac_id[i], NULL); + + /* Add auxiliary station for scanning */ + ret = iwl_mvm_add_aux_sta(mvm); + if (ret) + goto error; + + return 0; + error: + iwl_trans_stop_device(mvm->trans); + return ret; +} + +int iwl_mvm_rx_card_state_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_card_state_notif *card_state_notif = (void *)pkt->data; + u32 flags = le32_to_cpu(card_state_notif->flags); + + IWL_DEBUG_RF_KILL(mvm, "Card state received: HW:%s SW:%s CT:%s\n", + (flags & HW_CARD_DISABLED) ? "Kill" : "On", + (flags & SW_CARD_DISABLED) ? "Kill" : "On", + (flags & CT_KILL_CARD_DISABLED) ? + "Reached" : "Not reached"); + + return 0; +} + +int iwl_mvm_rx_radio_ver(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_radio_version_notif *radio_version = (void *)pkt->data; + + /* TODO: what to do with that? */ + IWL_DEBUG_INFO(mvm, + "Radio version: flavor: 0x%08x, step 0x%08x, dash 0x%08x\n", + le32_to_cpu(radio_version->radio_flavor), + le32_to_cpu(radio_version->radio_step), + le32_to_cpu(radio_version->radio_dash)); + return 0; +} diff --git a/drivers/net/wireless/iwlwifi/mvm/led.c b/drivers/net/wireless/iwlwifi/mvm/led.c new file mode 100644 index 0000000..e0e7b7c --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/led.c @@ -0,0 +1,136 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#include +#include "iwl-io.h" +#include "iwl-csr.h" +#include "mvm.h" + +/* Set led register on */ +static void iwl_mvm_led_enable(struct iwl_mvm *mvm) +{ + iwl_write32(mvm->trans, CSR_LED_REG, CSR_LED_REG_TURN_ON); +} + +/* Set led register off */ +static void iwl_mvm_led_disable(struct iwl_mvm *mvm) +{ + iwl_write32(mvm->trans, CSR_LED_REG, CSR_LED_REG_TURN_OFF); +} + +static void iwl_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct iwl_mvm *mvm = container_of(led_cdev, struct iwl_mvm, led); + if (brightness > 0) + iwl_mvm_led_enable(mvm); + else + iwl_mvm_led_disable(mvm); +} + +int iwl_mvm_leds_init(struct iwl_mvm *mvm) +{ + int mode = iwlwifi_mod_params.led_mode; + int ret; + + switch (mode) { + case IWL_LED_DEFAULT: + case IWL_LED_RF_STATE: + mode = IWL_LED_RF_STATE; + break; + case IWL_LED_DISABLE: + IWL_INFO(mvm, "Led disabled\n"); + return 0; + default: + return -EINVAL; + }; + + mvm->led.name = kasprintf(GFP_KERNEL, "%s-led", + wiphy_name(mvm->hw->wiphy)); + mvm->led.brightness_set = iwl_led_brightness_set; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) + mvm->led.max_brightness = 1; +#endif + + if (mode == IWL_LED_RF_STATE) + mvm->led.default_trigger = + ieee80211_get_radio_led_name(mvm->hw); + + ret = led_classdev_register(mvm->trans->dev, &mvm->led); + if (ret) { + kfree(mvm->led.name); + IWL_INFO(mvm, "Failed to enable led\n"); + return ret; + } + + return 0; +} + +void iwl_mvm_leds_exit(struct iwl_mvm *mvm) +{ + if (iwlwifi_mod_params.led_mode == IWL_LED_DISABLE) + return; + + led_classdev_unregister(&mvm->led); + kfree(mvm->led.name); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/mac-ctxt.c b/drivers/net/wireless/iwlwifi/mvm/mac-ctxt.c new file mode 100644 index 0000000..d8e858c --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/mac-ctxt.c @@ -0,0 +1,1050 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#include +#include +#include "iwl-io.h" +#include "iwl-prph.h" +#include "fw-api.h" +#include "mvm.h" + +const u8 iwl_mvm_ac_to_tx_fifo[] = { + IWL_MVM_TX_FIFO_BK, + IWL_MVM_TX_FIFO_BE, + IWL_MVM_TX_FIFO_VI, + IWL_MVM_TX_FIFO_VO, +}; + +struct iwl_mvm_mac_iface_iterator_data { + struct iwl_mvm *mvm; + struct ieee80211_vif *vif; + unsigned long available_mac_ids[BITS_TO_LONGS(NUM_MAC_INDEX_DRIVER)]; + unsigned long available_tsf_ids[BITS_TO_LONGS(NUM_TSF_IDS)]; + unsigned long used_hw_queues[BITS_TO_LONGS(IWL_MVM_FIRST_AGG_QUEUE)]; + enum iwl_tsf_id preferred_tsf; + bool found_vif; +}; + +static void iwl_mvm_mac_iface_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_mac_iface_iterator_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u32 ac; + + /* Iterator may already find the interface being added -- skip it */ + if (vif == data->vif) { + data->found_vif = true; + return; + } + + /* Mark the queues used by the vif */ + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) + if (vif->hw_queue[ac] != IEEE80211_INVAL_HW_QUEUE) + __set_bit(vif->hw_queue[ac], data->used_hw_queues); + + if (vif->cab_queue != IEEE80211_INVAL_HW_QUEUE) + __set_bit(vif->cab_queue, data->used_hw_queues); + + /* + * Mark MAC IDs as used by clearing the available bit, and + * (below) mark TSFs as used if their existing use is not + * compatible with the new interface type. + * No locking or atomic bit operations are needed since the + * data is on the stack of the caller function. + */ + __clear_bit(mvmvif->id, data->available_mac_ids); + + /* + * The TSF is a hardware/firmware resource, there are 4 and + * the driver should assign and free them as needed. However, + * there are cases where 2 MACs should share the same TSF ID + * for the purpose of clock sync, an optimization to avoid + * clock drift causing overlapping TBTTs/DTIMs for a GO and + * client in the system. + * + * The firmware will decide according to the MAC type which + * will be the master and slave. Clients that need to sync + * with a remote station will be the master, and an AP or GO + * will be the slave. + * + * Depending on the new interface type it can be slaved to + * or become the master of an existing interface. + */ + switch (data->vif->type) { + case NL80211_IFTYPE_STATION: + /* + * The new interface is client, so if the existing one + * we're iterating is an AP, the TSF should be used to + * avoid drift between the new client and existing AP, + * the existing AP will get drift updates from the new + * client context in this case + */ + if (vif->type == NL80211_IFTYPE_AP) { + if (data->preferred_tsf == NUM_TSF_IDS && + test_bit(mvmvif->tsf_id, data->available_tsf_ids)) + data->preferred_tsf = mvmvif->tsf_id; + return; + } + break; + case NL80211_IFTYPE_AP: + /* + * The new interface is AP/GO, so should get drift + * updates from an existing client or use the same + * TSF as an existing GO. There's no drift between + * TSFs internally but if they used different TSFs + * then a new client MAC could update one of them + * and cause drift that way. + */ + if (vif->type == NL80211_IFTYPE_STATION || + vif->type == NL80211_IFTYPE_AP) { + if (data->preferred_tsf == NUM_TSF_IDS && + test_bit(mvmvif->tsf_id, data->available_tsf_ids)) + data->preferred_tsf = mvmvif->tsf_id; + return; + } + break; + default: + /* + * For all other interface types there's no need to + * take drift into account. Either they're exclusive + * like IBSS and monitor, or we don't care much about + * their TSF (like P2P Device), but we won't be able + * to share the TSF resource. + */ + break; + } + + /* + * Unless we exited above, we can't share the TSF resource + * that the virtual interface we're iterating over is using + * with the new one, so clear the available bit and if this + * was the preferred one, reset that as well. + */ + __clear_bit(mvmvif->tsf_id, data->available_tsf_ids); + + if (data->preferred_tsf == mvmvif->tsf_id) + data->preferred_tsf = NUM_TSF_IDS; +} + +/* + * Get the mask of the queus used by the vif + */ +u32 iwl_mvm_mac_get_queues_mask(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + u32 qmask = 0, ac; + + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) + return BIT(IWL_MVM_OFFCHANNEL_QUEUE); + + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) + if (vif->hw_queue[ac] != IEEE80211_INVAL_HW_QUEUE) + qmask |= BIT(vif->hw_queue[ac]); + + return qmask; +} + +static int iwl_mvm_mac_ctxt_allocate_resources(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_mac_iface_iterator_data data = { + .mvm = mvm, + .vif = vif, + .available_mac_ids = { (1 << NUM_MAC_INDEX_DRIVER) - 1 }, + .available_tsf_ids = { (1 << NUM_TSF_IDS) - 1 }, + /* no preference yet */ + .preferred_tsf = NUM_TSF_IDS, + .used_hw_queues = { + BIT(IWL_MVM_OFFCHANNEL_QUEUE) | + BIT(IWL_MVM_AUX_QUEUE) | + BIT(IWL_MVM_CMD_QUEUE) + }, + .found_vif = false, + }; + u32 ac; + int ret; + + /* + * Allocate a MAC ID and a TSF for this MAC, along with the queues + * and other resources. + */ + + /* + * Before the iterator, we start with all MAC IDs and TSFs available. + * + * During iteration, all MAC IDs are cleared that are in use by other + * virtual interfaces, and all TSF IDs are cleared that can't be used + * by this new virtual interface because they're used by an interface + * that can't share it with the new one. + * At the same time, we check if there's a preferred TSF in the case + * that we should share it with another interface. + */ + + /* Currently, MAC ID 0 should be used only for the managed vif */ + if (vif->type != NL80211_IFTYPE_STATION || vif->p2p) + __clear_bit(0, data.available_mac_ids); + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + iwl_mvm_mac_iface_iterator, &data); + + /* + * In the case we're getting here during resume, it's similar to + * firmware restart, and with RESUME_ALL the iterator will find + * the vif being added already. + * We don't want to reassign any IDs in either case since doing + * so would probably assign different IDs (as interfaces aren't + * necessarily added in the same order), but the old IDs were + * preserved anyway, so skip ID assignment for both resume and + * recovery. + */ + if (data.found_vif) + return 0; + + /* Therefore, in recovery, we can't get here */ + WARN_ON_ONCE(test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)); + + mvmvif->id = find_first_bit(data.available_mac_ids, + NUM_MAC_INDEX_DRIVER); + if (mvmvif->id == NUM_MAC_INDEX_DRIVER) { + IWL_ERR(mvm, "Failed to init MAC context - no free ID!\n"); + ret = -EIO; + goto exit_fail; + } + + if (data.preferred_tsf != NUM_TSF_IDS) + mvmvif->tsf_id = data.preferred_tsf; + else + mvmvif->tsf_id = find_first_bit(data.available_tsf_ids, + NUM_TSF_IDS); + if (mvmvif->tsf_id == NUM_TSF_IDS) { + IWL_ERR(mvm, "Failed to init MAC context - no free TSF!\n"); + ret = -EIO; + goto exit_fail; + } + + mvmvif->color = 0; + + INIT_LIST_HEAD(&mvmvif->time_event_data.list); + mvmvif->time_event_data.id = TE_MAX; + + /* No need to allocate data queues to P2P Device MAC.*/ + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) + vif->hw_queue[ac] = IEEE80211_INVAL_HW_QUEUE; + + return 0; + } + + /* Find available queues, and allocate them to the ACs */ + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { + u8 queue = find_first_zero_bit(data.used_hw_queues, + IWL_MVM_FIRST_AGG_QUEUE); + + if (queue >= IWL_MVM_FIRST_AGG_QUEUE) { + IWL_ERR(mvm, "Failed to allocate queue\n"); + ret = -EIO; + goto exit_fail; + } + + __set_bit(queue, data.used_hw_queues); + vif->hw_queue[ac] = queue; + } + + /* Allocate the CAB queue for softAP and GO interfaces */ + if (vif->type == NL80211_IFTYPE_AP) { + u8 queue = find_first_zero_bit(data.used_hw_queues, + IWL_MVM_FIRST_AGG_QUEUE); + + if (queue >= IWL_MVM_FIRST_AGG_QUEUE) { + IWL_ERR(mvm, "Failed to allocate cab queue\n"); + ret = -EIO; + goto exit_fail; + } + + vif->cab_queue = queue; + } else { + vif->cab_queue = IEEE80211_INVAL_HW_QUEUE; + } + + mvmvif->bcast_sta.sta_id = IWL_MVM_STATION_COUNT; + mvmvif->ap_sta_id = IWL_MVM_STATION_COUNT; + + return 0; + +exit_fail: + memset(mvmvif, 0, sizeof(struct iwl_mvm_vif)); + memset(vif->hw_queue, IEEE80211_INVAL_HW_QUEUE, sizeof(vif->hw_queue)); + vif->cab_queue = IEEE80211_INVAL_HW_QUEUE; + return ret; +} + +int iwl_mvm_mac_ctxt_init(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + u32 ac; + int ret; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_mvm_mac_ctxt_allocate_resources(mvm, vif); + if (ret) + return ret; + + switch (vif->type) { + case NL80211_IFTYPE_P2P_DEVICE: + iwl_trans_ac_txq_enable(mvm->trans, IWL_MVM_OFFCHANNEL_QUEUE, + IWL_MVM_TX_FIFO_VO); + break; + case NL80211_IFTYPE_AP: + iwl_trans_ac_txq_enable(mvm->trans, vif->cab_queue, + IWL_MVM_TX_FIFO_MCAST); + /* fall through */ + default: + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) + iwl_trans_ac_txq_enable(mvm->trans, vif->hw_queue[ac], + iwl_mvm_ac_to_tx_fifo[ac]); + break; + } + + return 0; +} + +void iwl_mvm_mac_ctxt_release(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + int ac; + + lockdep_assert_held(&mvm->mutex); + + switch (vif->type) { + case NL80211_IFTYPE_P2P_DEVICE: + iwl_trans_txq_disable(mvm->trans, IWL_MVM_OFFCHANNEL_QUEUE); + break; + case NL80211_IFTYPE_AP: + iwl_trans_txq_disable(mvm->trans, vif->cab_queue); + /* fall through */ + default: + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) + iwl_trans_txq_disable(mvm->trans, vif->hw_queue[ac]); + } +} + +static void iwl_mvm_ack_rates(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + enum ieee80211_band band, + u8 *cck_rates, u8 *ofdm_rates) +{ + struct ieee80211_supported_band *sband; + unsigned long basic = vif->bss_conf.basic_rates; + int lowest_present_ofdm = 100; + int lowest_present_cck = 100; + u8 cck = 0; + u8 ofdm = 0; + int i; + + sband = mvm->hw->wiphy->bands[band]; + + for_each_set_bit(i, &basic, BITS_PER_LONG) { + int hw = sband->bitrates[i].hw_value; + if (hw >= IWL_FIRST_OFDM_RATE) { + ofdm |= BIT(hw - IWL_FIRST_OFDM_RATE); + if (lowest_present_ofdm > hw) + lowest_present_ofdm = hw; + } else { + BUILD_BUG_ON(IWL_FIRST_CCK_RATE != 0); + + cck |= BIT(hw); + if (lowest_present_cck > hw) + lowest_present_cck = hw; + } + } + + /* + * Now we've got the basic rates as bitmaps in the ofdm and cck + * variables. This isn't sufficient though, as there might not + * be all the right rates in the bitmap. E.g. if the only basic + * rates are 5.5 Mbps and 11 Mbps, we still need to add 1 Mbps + * and 6 Mbps because the 802.11-2007 standard says in 9.6: + * + * [...] a STA responding to a received frame shall transmit + * its Control Response frame [...] at the highest rate in the + * BSSBasicRateSet parameter that is less than or equal to the + * rate of the immediately previous frame in the frame exchange + * sequence ([...]) and that is of the same modulation class + * ([...]) as the received frame. If no rate contained in the + * BSSBasicRateSet parameter meets these conditions, then the + * control frame sent in response to a received frame shall be + * transmitted at the highest mandatory rate of the PHY that is + * less than or equal to the rate of the received frame, and + * that is of the same modulation class as the received frame. + * + * As a consequence, we need to add all mandatory rates that are + * lower than all of the basic rates to these bitmaps. + */ + + if (IWL_RATE_24M_INDEX < lowest_present_ofdm) + ofdm |= IWL_RATE_BIT_MSK(24) >> IWL_FIRST_OFDM_RATE; + if (IWL_RATE_12M_INDEX < lowest_present_ofdm) + ofdm |= IWL_RATE_BIT_MSK(12) >> IWL_FIRST_OFDM_RATE; + /* 6M already there or needed so always add */ + ofdm |= IWL_RATE_BIT_MSK(6) >> IWL_FIRST_OFDM_RATE; + + /* + * CCK is a bit more complex with DSSS vs. HR/DSSS vs. ERP. + * Note, however: + * - if no CCK rates are basic, it must be ERP since there must + * be some basic rates at all, so they're OFDM => ERP PHY + * (or we're in 5 GHz, and the cck bitmap will never be used) + * - if 11M is a basic rate, it must be ERP as well, so add 5.5M + * - if 5.5M is basic, 1M and 2M are mandatory + * - if 2M is basic, 1M is mandatory + * - if 1M is basic, that's the only valid ACK rate. + * As a consequence, it's not as complicated as it sounds, just add + * any lower rates to the ACK rate bitmap. + */ + if (IWL_RATE_11M_INDEX < lowest_present_cck) + cck |= IWL_RATE_BIT_MSK(11) >> IWL_FIRST_CCK_RATE; + if (IWL_RATE_5M_INDEX < lowest_present_cck) + cck |= IWL_RATE_BIT_MSK(5) >> IWL_FIRST_CCK_RATE; + if (IWL_RATE_2M_INDEX < lowest_present_cck) + cck |= IWL_RATE_BIT_MSK(2) >> IWL_FIRST_CCK_RATE; + /* 1M already there or needed so always add */ + cck |= IWL_RATE_BIT_MSK(1) >> IWL_FIRST_CCK_RATE; + + *cck_rates = cck; + *ofdm_rates = ofdm; +} + +static void iwl_mvm_mac_ctxt_cmd_common(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mac_ctx_cmd *cmd, + u32 action) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct ieee80211_chanctx_conf *chanctx; + u8 cck_ack_rates, ofdm_ack_rates; + int i; + + cmd->id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)); + cmd->action = cpu_to_le32(action); + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + if (vif->p2p) + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_P2P_STA); + else + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_BSS_STA); + break; + case NL80211_IFTYPE_AP: + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_GO); + break; + case NL80211_IFTYPE_MONITOR: + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_LISTENER); + break; + case NL80211_IFTYPE_P2P_DEVICE: + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_P2P_DEVICE); + break; + case NL80211_IFTYPE_ADHOC: + cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_IBSS); + break; + default: + WARN_ON_ONCE(1); + } + + cmd->tsf_id = cpu_to_le32(mvmvif->tsf_id); + + memcpy(cmd->node_addr, vif->addr, ETH_ALEN); + if (vif->bss_conf.bssid) + memcpy(cmd->bssid_addr, vif->bss_conf.bssid, ETH_ALEN); + else + eth_broadcast_addr(cmd->bssid_addr); + + rcu_read_lock(); + chanctx = rcu_dereference(vif->chanctx_conf); + iwl_mvm_ack_rates(mvm, vif, chanctx ? chanctx->def.chan->band + : IEEE80211_BAND_2GHZ, + &cck_ack_rates, &ofdm_ack_rates); + rcu_read_unlock(); + + cmd->cck_rates = cpu_to_le32((u32)cck_ack_rates); + cmd->ofdm_rates = cpu_to_le32((u32)ofdm_ack_rates); + + cmd->cck_short_preamble = + cpu_to_le32(vif->bss_conf.use_short_preamble ? + MAC_FLG_SHORT_PREAMBLE : 0); + cmd->short_slot = + cpu_to_le32(vif->bss_conf.use_short_slot ? + MAC_FLG_SHORT_SLOT : 0); + + for (i = 0; i < AC_NUM; i++) { + cmd->ac[i].cw_min = cpu_to_le16(mvmvif->queue_params[i].cw_min); + cmd->ac[i].cw_max = cpu_to_le16(mvmvif->queue_params[i].cw_max); + cmd->ac[i].aifsn = mvmvif->queue_params[i].aifs; + cmd->ac[i].edca_txop = + cpu_to_le16(mvmvif->queue_params[i].txop * 32); + cmd->ac[i].fifos_mask = BIT(iwl_mvm_ac_to_tx_fifo[i]); + } + + /* in AP mode, the MCAST FIFO takes the EDCA params from VO */ + if (vif->type == NL80211_IFTYPE_AP) + cmd->ac[AC_VO].fifos_mask |= BIT(IWL_MVM_TX_FIFO_MCAST); + + if (vif->bss_conf.qos) + cmd->qos_flags |= cpu_to_le32(MAC_QOS_FLG_UPDATE_EDCA); + + /* Don't use cts to self as the fw doesn't support it currently. */ + if (vif->bss_conf.use_cts_prot) + cmd->protection_flags |= cpu_to_le32(MAC_PROT_FLG_TGG_PROTECT); + + /* + * I think that we should enable these 2 flags regardless the HT PROT + * fields in the HT IE, but I am not sure. Someone knows whom to ask?... + */ + if (vif->bss_conf.chandef.width != NL80211_CHAN_WIDTH_20_NOHT) { + cmd->qos_flags |= cpu_to_le32(MAC_QOS_FLG_TGN); + cmd->protection_flags |= cpu_to_le32(MAC_PROT_FLG_HT_PROT | + MAC_PROT_FLG_FAT_PROT); + } + + cmd->filter_flags = cpu_to_le32(MAC_FILTER_ACCEPT_GRP); +} + +static int iwl_mvm_mac_ctxt_send_cmd(struct iwl_mvm *mvm, + struct iwl_mac_ctx_cmd *cmd) +{ + int ret = iwl_mvm_send_cmd_pdu(mvm, MAC_CONTEXT_CMD, CMD_SYNC, + sizeof(*cmd), cmd); + if (ret) + IWL_ERR(mvm, "Failed to send MAC context (action:%d): %d\n", + le32_to_cpu(cmd->action), ret); + return ret; +} + +/* + * Fill the specific data for mac context of type station or p2p client + */ +static void iwl_mvm_mac_ctxt_cmd_fill_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mac_data_sta *ctxt_sta, + bool force_assoc_off) +{ + /* We need the dtim_period to set the MAC as associated */ + if (vif->bss_conf.assoc && vif->bss_conf.dtim_period && + !force_assoc_off) { + u32 dtim_offs; + + /* + * The DTIM count counts down, so when it is N that means N + * more beacon intervals happen until the DTIM TBTT. Therefore + * add this to the current time. If that ends up being in the + * future, the firmware will handle it. + * + * Also note that the system_timestamp (which we get here as + * "sync_device_ts") and TSF timestamp aren't at exactly the + * same offset in the frame -- the TSF is at the first symbol + * of the TSF, the system timestamp is at signal acquisition + * time. This means there's an offset between them of at most + * a few hundred microseconds (24 * 8 bits + PLCP time gives + * 384us in the longest case), this is currently not relevant + * as the firmware wakes up around 2ms before the TBTT. + */ + dtim_offs = vif->bss_conf.sync_dtim_count * + vif->bss_conf.beacon_int; + /* convert TU to usecs */ + dtim_offs *= 1024; + + ctxt_sta->dtim_tsf = + cpu_to_le64(vif->bss_conf.sync_tsf + dtim_offs); + ctxt_sta->dtim_time = + cpu_to_le32(vif->bss_conf.sync_device_ts + dtim_offs); + + IWL_DEBUG_INFO(mvm, "DTIM TBTT is 0x%llx/0x%x, offset %d\n", + le64_to_cpu(ctxt_sta->dtim_tsf), + le32_to_cpu(ctxt_sta->dtim_time), + dtim_offs); + + ctxt_sta->is_assoc = cpu_to_le32(1); + } else { + ctxt_sta->is_assoc = cpu_to_le32(0); + } + + ctxt_sta->bi = cpu_to_le32(vif->bss_conf.beacon_int); + ctxt_sta->bi_reciprocal = + cpu_to_le32(iwl_mvm_reciprocal(vif->bss_conf.beacon_int)); + ctxt_sta->dtim_interval = cpu_to_le32(vif->bss_conf.beacon_int * + vif->bss_conf.dtim_period); + ctxt_sta->dtim_reciprocal = + cpu_to_le32(iwl_mvm_reciprocal(vif->bss_conf.beacon_int * + vif->bss_conf.dtim_period)); + + ctxt_sta->listen_interval = cpu_to_le32(mvm->hw->conf.listen_interval); + ctxt_sta->assoc_id = cpu_to_le32(vif->bss_conf.aid); +} + +static int iwl_mvm_mac_ctxt_cmd_station(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action) +{ + struct iwl_mac_ctx_cmd cmd = {}; + + WARN_ON(vif->type != NL80211_IFTYPE_STATION || vif->p2p); + + /* Fill the common data for all mac context types */ + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, action); + + /* Allow beacons to pass through as long as we are not associated,or we + * do not have dtim period information */ + if (!vif->bss_conf.assoc || !vif->bss_conf.dtim_period) + cmd.filter_flags |= cpu_to_le32(MAC_FILTER_IN_BEACON); + else + cmd.filter_flags &= ~cpu_to_le32(MAC_FILTER_IN_BEACON); + + /* Fill the data specific for station mode */ + iwl_mvm_mac_ctxt_cmd_fill_sta(mvm, vif, &cmd.sta, + action == FW_CTXT_ACTION_ADD); + + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctxt_cmd_p2p_client(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action) +{ + struct iwl_mac_ctx_cmd cmd = {}; + struct ieee80211_p2p_noa_attr *noa = &vif->bss_conf.p2p_noa_attr; + + WARN_ON(vif->type != NL80211_IFTYPE_STATION || !vif->p2p); + + /* Fill the common data for all mac context types */ + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, action); + + /* Fill the data specific for station mode */ + iwl_mvm_mac_ctxt_cmd_fill_sta(mvm, vif, &cmd.p2p_sta.sta, + action == FW_CTXT_ACTION_ADD); + + cmd.p2p_sta.ctwin = cpu_to_le32(noa->oppps_ctwindow & + IEEE80211_P2P_OPPPS_CTWINDOW_MASK); + + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctxt_cmd_listener(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action) +{ + struct iwl_mac_ctx_cmd cmd = {}; + + WARN_ON(vif->type != NL80211_IFTYPE_MONITOR); + + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, action); + + cmd.filter_flags = cpu_to_le32(MAC_FILTER_IN_PROMISC | + MAC_FILTER_IN_CONTROL_AND_MGMT | + MAC_FILTER_IN_BEACON | + MAC_FILTER_IN_PROBE_REQUEST); + + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +struct iwl_mvm_go_iterator_data { + bool go_active; +}; + +static void iwl_mvm_go_iterator(void *_data, u8 *mac, struct ieee80211_vif *vif) +{ + struct iwl_mvm_go_iterator_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (vif->type == NL80211_IFTYPE_AP && vif->p2p && mvmvif->ap_active) + data->go_active = true; +} + +static int iwl_mvm_mac_ctxt_cmd_p2p_device(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action) +{ + struct iwl_mac_ctx_cmd cmd = {}; + struct iwl_mvm_go_iterator_data data = {}; + + WARN_ON(vif->type != NL80211_IFTYPE_P2P_DEVICE); + + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, action); + + cmd.protection_flags |= cpu_to_le32(MAC_PROT_FLG_TGG_PROTECT); + + /* Override the filter flags to accept only probe requests */ + cmd.filter_flags = cpu_to_le32(MAC_FILTER_IN_PROBE_REQUEST); + + /* + * This flag should be set to true when the P2P Device is + * discoverable and there is at least another active P2P GO. Settings + * this flag will allow the P2P Device to be discoverable on other + * channels in addition to its listen channel. + * Note that this flag should not be set in other cases as it opens the + * Rx filters on all MAC and increases the number of interrupts. + */ + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + iwl_mvm_go_iterator, &data); + + cmd.p2p_dev.is_disc_extended = cpu_to_le32(data.go_active ? 1 : 0); + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static void iwl_mvm_mac_ctxt_set_tim(struct iwl_mvm *mvm, + struct iwl_mac_beacon_cmd *beacon_cmd, + u8 *beacon, u32 frame_size) +{ + u32 tim_idx; + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)beacon; + + /* The index is relative to frame start but we start looking at the + * variable-length part of the beacon. */ + tim_idx = mgmt->u.beacon.variable - beacon; + + /* Parse variable-length elements of beacon to find WLAN_EID_TIM */ + while ((tim_idx < (frame_size - 2)) && + (beacon[tim_idx] != WLAN_EID_TIM)) + tim_idx += beacon[tim_idx+1] + 2; + + /* If TIM field was found, set variables */ + if ((tim_idx < (frame_size - 1)) && (beacon[tim_idx] == WLAN_EID_TIM)) { + beacon_cmd->tim_idx = cpu_to_le32(tim_idx); + beacon_cmd->tim_size = cpu_to_le32((u32)beacon[tim_idx+1]); + } else { + IWL_WARN(mvm, "Unable to find TIM Element in beacon\n"); + } +} + +static int iwl_mvm_mac_ctxt_send_beacon(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct sk_buff *beacon) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_host_cmd cmd = { + .id = BEACON_TEMPLATE_CMD, + .flags = CMD_ASYNC, + }; + struct iwl_mac_beacon_cmd beacon_cmd = {}; + struct ieee80211_tx_info *info; + u32 beacon_skb_len; + u32 rate; + + if (WARN_ON(!beacon)) + return -EINVAL; + + beacon_skb_len = beacon->len; + + /* TODO: for now the beacon template id is set to be the mac context id. + * Might be better to handle it as another resource ... */ + beacon_cmd.template_id = cpu_to_le32((u32)mvmvif->id); + + /* Set up TX command fields */ + beacon_cmd.tx.len = cpu_to_le16((u16)beacon_skb_len); + beacon_cmd.tx.sta_id = mvmvif->bcast_sta.sta_id; + beacon_cmd.tx.life_time = cpu_to_le32(TX_CMD_LIFE_TIME_INFINITE); + beacon_cmd.tx.tx_flags = cpu_to_le32(TX_CMD_FLG_SEQ_CTL | + TX_CMD_FLG_BT_DIS | + TX_CMD_FLG_TSF); + + mvm->mgmt_last_antenna_idx = + iwl_mvm_next_antenna(mvm, iwl_fw_valid_tx_ant(mvm->fw), + mvm->mgmt_last_antenna_idx); + + beacon_cmd.tx.rate_n_flags = + cpu_to_le32(BIT(mvm->mgmt_last_antenna_idx) << + RATE_MCS_ANT_POS); + + info = IEEE80211_SKB_CB(beacon); + + if (info->band == IEEE80211_BAND_5GHZ || vif->p2p) { + rate = IWL_FIRST_OFDM_RATE; + } else { + rate = IWL_FIRST_CCK_RATE; + beacon_cmd.tx.rate_n_flags |= cpu_to_le32(RATE_MCS_CCK_MSK); + } + beacon_cmd.tx.rate_n_flags |= + cpu_to_le32(iwl_mvm_mac80211_idx_to_hwrate(rate)); + + /* Set up TX beacon command fields */ + iwl_mvm_mac_ctxt_set_tim(mvm, &beacon_cmd, + beacon->data, + beacon_skb_len); + + /* Submit command */ + cmd.len[0] = sizeof(beacon_cmd); + cmd.data[0] = &beacon_cmd; + cmd.dataflags[0] = 0; + cmd.len[1] = beacon_skb_len; + cmd.data[1] = beacon->data; + cmd.dataflags[1] = IWL_HCMD_DFL_DUP; + + return iwl_mvm_send_cmd(mvm, &cmd); +} + +/* The beacon template for the AP/GO context has changed and needs update */ +int iwl_mvm_mac_ctxt_beacon_changed(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct sk_buff *beacon; + int ret; + + WARN_ON(vif->type != NL80211_IFTYPE_AP); + + beacon = ieee80211_beacon_get(mvm->hw, vif); + if (!beacon) + return -ENOMEM; + + ret = iwl_mvm_mac_ctxt_send_beacon(mvm, vif, beacon); + dev_kfree_skb(beacon); + return ret; +} + +/* + * Fill the specific data for mac context of type AP of P2P GO + */ +static void iwl_mvm_mac_ctxt_cmd_fill_ap(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mac_data_ap *ctxt_ap, + bool add) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + ctxt_ap->bi = cpu_to_le32(vif->bss_conf.beacon_int); + ctxt_ap->bi_reciprocal = + cpu_to_le32(iwl_mvm_reciprocal(vif->bss_conf.beacon_int)); + ctxt_ap->dtim_interval = cpu_to_le32(vif->bss_conf.beacon_int * + vif->bss_conf.dtim_period); + ctxt_ap->dtim_reciprocal = + cpu_to_le32(iwl_mvm_reciprocal(vif->bss_conf.beacon_int * + vif->bss_conf.dtim_period)); + + ctxt_ap->mcast_qid = cpu_to_le32(vif->cab_queue); + + /* + * Only read the system time when the MAC is being added, when we + * just modify the MAC then we should keep the time -- the firmware + * can otherwise have a "jumping" TBTT. + */ + if (add) + mvmvif->ap_beacon_time = + iwl_read_prph(mvm->trans, DEVICE_SYSTEM_TIME_REG); + + ctxt_ap->beacon_time = cpu_to_le32(mvmvif->ap_beacon_time); + + ctxt_ap->beacon_tsf = 0; /* unused */ + + /* TODO: Assume that the beacon id == mac context id */ + ctxt_ap->beacon_template = cpu_to_le32(mvmvif->id); +} + +static int iwl_mvm_mac_ctxt_cmd_ap(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action) +{ + struct iwl_mac_ctx_cmd cmd = {}; + + WARN_ON(vif->type != NL80211_IFTYPE_AP || vif->p2p); + + /* Fill the common data for all mac context types */ + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, action); + + /* Also enable probe requests to pass */ + cmd.filter_flags |= cpu_to_le32(MAC_FILTER_IN_PROBE_REQUEST); + + /* Fill the data specific for ap mode */ + iwl_mvm_mac_ctxt_cmd_fill_ap(mvm, vif, &cmd.ap, + action == FW_CTXT_ACTION_ADD); + + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctxt_cmd_go(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 action) +{ + struct iwl_mac_ctx_cmd cmd = {}; + struct ieee80211_p2p_noa_attr *noa = &vif->bss_conf.p2p_noa_attr; + + WARN_ON(vif->type != NL80211_IFTYPE_AP || !vif->p2p); + + /* Fill the common data for all mac context types */ + iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, action); + + /* Fill the data specific for GO mode */ + iwl_mvm_mac_ctxt_cmd_fill_ap(mvm, vif, &cmd.go.ap, + action == FW_CTXT_ACTION_ADD); + + cmd.go.ctwin = cpu_to_le32(noa->oppps_ctwindow & + IEEE80211_P2P_OPPPS_CTWINDOW_MASK); + cmd.go.opp_ps_enabled = + cpu_to_le32(!!(noa->oppps_ctwindow & + IEEE80211_P2P_OPPPS_ENABLE_BIT)); + + return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctx_send(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + u32 action) +{ + switch (vif->type) { + case NL80211_IFTYPE_STATION: + if (!vif->p2p) + return iwl_mvm_mac_ctxt_cmd_station(mvm, vif, + action); + else + return iwl_mvm_mac_ctxt_cmd_p2p_client(mvm, vif, + action); + break; + case NL80211_IFTYPE_AP: + if (!vif->p2p) + return iwl_mvm_mac_ctxt_cmd_ap(mvm, vif, action); + else + return iwl_mvm_mac_ctxt_cmd_go(mvm, vif, action); + break; + case NL80211_IFTYPE_MONITOR: + return iwl_mvm_mac_ctxt_cmd_listener(mvm, vif, action); + case NL80211_IFTYPE_P2P_DEVICE: + return iwl_mvm_mac_ctxt_cmd_p2p_device(mvm, vif, action); + default: + break; + } + + return -EOPNOTSUPP; +} + +int iwl_mvm_mac_ctxt_add(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + if (WARN_ONCE(mvmvif->uploaded, "Adding active MAC %pM/%d\n", + vif->addr, ieee80211_vif_type_p2p(vif))) + return -EIO; + + ret = iwl_mvm_mac_ctx_send(mvm, vif, FW_CTXT_ACTION_ADD); + if (ret) + return ret; + + mvmvif->uploaded = true; + return 0; +} + +int iwl_mvm_mac_ctxt_changed(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + if (WARN_ONCE(!mvmvif->uploaded, "Changing inactive MAC %pM/%d\n", + vif->addr, ieee80211_vif_type_p2p(vif))) + return -EIO; + + return iwl_mvm_mac_ctx_send(mvm, vif, FW_CTXT_ACTION_MODIFY); +} + +int iwl_mvm_mac_ctxt_remove(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mac_ctx_cmd cmd; + int ret; + + if (WARN_ONCE(!mvmvif->uploaded, "Removing inactive MAC %pM/%d\n", + vif->addr, ieee80211_vif_type_p2p(vif))) + return -EIO; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color)); + cmd.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE); + + ret = iwl_mvm_send_cmd_pdu(mvm, MAC_CONTEXT_CMD, CMD_SYNC, + sizeof(cmd), &cmd); + if (ret) { + IWL_ERR(mvm, "Failed to remove MAC context: %d\n", ret); + return ret; + } + + mvmvif->uploaded = false; + return 0; +} + +int iwl_mvm_rx_beacon_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_beacon_notif *beacon = (void *)pkt->data; + u16 status __maybe_unused = + le16_to_cpu(beacon->beacon_notify_hdr.status.status); + u32 rate __maybe_unused = + le32_to_cpu(beacon->beacon_notify_hdr.initial_rate); + + IWL_DEBUG_RX(mvm, "beacon status %#x retries:%d tsf:0x%16llX rate:%d\n", + status & TX_STATUS_MSK, + beacon->beacon_notify_hdr.failure_frame, + le64_to_cpu(beacon->tsf), + rate); + return 0; +} diff --git a/drivers/net/wireless/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/iwlwifi/mvm/mac80211.c new file mode 100644 index 0000000..f7545e0 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/mac80211.c @@ -0,0 +1,1429 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "iwl-op-mode.h" +#include "iwl-io.h" +#include "mvm.h" +#include "sta.h" +#include "time-event.h" +#include "iwl-eeprom-parse.h" +#include "fw-api-scan.h" +#include "iwl-phy-db.h" + +static const struct ieee80211_iface_limit iwl_mvm_limits[] = { + { + .max = 1, + .types = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_AP), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_P2P_DEVICE), + }, +}; + +static const struct ieee80211_iface_combination iwl_mvm_iface_combinations[] = { + { + .num_different_channels = 1, + .max_interfaces = 3, + .limits = iwl_mvm_limits, + .n_limits = ARRAY_SIZE(iwl_mvm_limits), + }, +}; + +#ifdef CONFIG_PM_SLEEP +static const struct nl80211_wowlan_tcp_data_token_feature +iwl_mvm_wowlan_tcp_token_feature = { + .min_len = 0, + .max_len = 255, + .bufsize = IWL_WOWLAN_REMOTE_WAKE_MAX_TOKENS, +}; + +static const struct wiphy_wowlan_tcp_support iwl_mvm_wowlan_tcp_support = { + .tok = &iwl_mvm_wowlan_tcp_token_feature, + .data_payload_max = IWL_WOWLAN_TCP_MAX_PACKET_LEN - + sizeof(struct ethhdr) - + sizeof(struct iphdr) - + sizeof(struct tcphdr), + .data_interval_max = 65535, /* __le16 in API */ + .wake_payload_max = IWL_WOWLAN_REMOTE_WAKE_MAX_PACKET_LEN - + sizeof(struct ethhdr) - + sizeof(struct iphdr) - + sizeof(struct tcphdr), + .seq = true, +}; +#endif + +int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm) +{ + struct ieee80211_hw *hw = mvm->hw; + int num_mac, ret, i; + + /* Tell mac80211 our characteristics */ + hw->flags = IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_SPECTRUM_MGMT | + IEEE80211_HW_REPORTS_TX_ACK_STATUS | + IEEE80211_HW_QUEUE_CONTROL | + IEEE80211_HW_WANT_MONITOR_VIF | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_SUPPORTS_DYNAMIC_PS | + IEEE80211_HW_AMPDU_AGGREGATION | + IEEE80211_HW_TIMING_BEACON_ONLY; + + hw->queues = IWL_MVM_FIRST_AGG_QUEUE; + hw->offchannel_tx_hw_queue = IWL_MVM_OFFCHANNEL_QUEUE; + hw->rate_control_algorithm = "iwl-mvm-rs"; + + /* + * Enable 11w if advertised by firmware and software crypto + * is not enabled (as the firmware will interpret some mgmt + * packets, so enabling it with software crypto isn't safe) + */ + if (mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_MFP && + !iwlwifi_mod_params.sw_crypto) + hw->flags |= IEEE80211_HW_MFP_CAPABLE; + + hw->sta_data_size = sizeof(struct iwl_mvm_sta); + hw->vif_data_size = sizeof(struct iwl_mvm_vif); + hw->chanctx_data_size = sizeof(struct iwl_mvm_phy_ctxt); + + hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_P2P_DEVICE); + + hw->wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY | + WIPHY_FLAG_DISABLE_BEACON_HINTS | + WIPHY_FLAG_IBSS_RSN; + + hw->wiphy->iface_combinations = iwl_mvm_iface_combinations; + hw->wiphy->n_iface_combinations = + ARRAY_SIZE(iwl_mvm_iface_combinations); + + hw->wiphy->max_remain_on_channel_duration = 10000; + hw->max_listen_interval = IWL_CONN_MAX_LISTEN_INTERVAL; + + /* Extract MAC address */ + memcpy(mvm->addresses[0].addr, mvm->nvm_data->hw_addr, ETH_ALEN); + hw->wiphy->addresses = mvm->addresses; + hw->wiphy->n_addresses = 1; + + /* Extract additional MAC addresses if available */ + num_mac = (mvm->nvm_data->n_hw_addrs > 1) ? + min(IWL_MVM_MAX_ADDRESSES, mvm->nvm_data->n_hw_addrs) : 1; + + for (i = 1; i < num_mac; i++) { + memcpy(mvm->addresses[i].addr, mvm->addresses[i-1].addr, + ETH_ALEN); + mvm->addresses[i].addr[5]++; + hw->wiphy->n_addresses++; + } + + /* we create the 802.11 header and a max-length SSID element */ + hw->wiphy->max_scan_ie_len = + mvm->fw->ucode_capa.max_probe_length - 24 - 34; + hw->wiphy->max_scan_ssids = PROBE_OPTION_MAX; + + if (mvm->nvm_data->bands[IEEE80211_BAND_2GHZ].n_channels) + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = + &mvm->nvm_data->bands[IEEE80211_BAND_2GHZ]; + if (mvm->nvm_data->bands[IEEE80211_BAND_5GHZ].n_channels) + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = + &mvm->nvm_data->bands[IEEE80211_BAND_5GHZ]; + + hw->wiphy->hw_version = mvm->trans->hw_id; + + if (iwlmvm_mod_params.power_scheme != IWL_POWER_SCHEME_CAM) + hw->wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT; + else + hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + + hw->wiphy->features |= NL80211_FEATURE_P2P_GO_CTWIN | + NL80211_FEATURE_P2P_GO_OPPPS; + + mvm->rts_threshold = IEEE80211_MAX_RTS_THRESHOLD; + +#ifdef CONFIG_PM_SLEEP + if (mvm->fw->img[IWL_UCODE_WOWLAN].sec[0].len && + mvm->trans->ops->d3_suspend && + mvm->trans->ops->d3_resume && + device_can_wakeup(mvm->trans->dev)) { + hw->wiphy->wowlan.flags = WIPHY_WOWLAN_MAGIC_PKT | + WIPHY_WOWLAN_DISCONNECT | + WIPHY_WOWLAN_EAP_IDENTITY_REQ | + WIPHY_WOWLAN_RFKILL_RELEASE; + if (!iwlwifi_mod_params.sw_crypto) + hw->wiphy->wowlan.flags |= + WIPHY_WOWLAN_SUPPORTS_GTK_REKEY | + WIPHY_WOWLAN_GTK_REKEY_FAILURE | + WIPHY_WOWLAN_4WAY_HANDSHAKE; + + hw->wiphy->wowlan.n_patterns = IWL_WOWLAN_MAX_PATTERNS; + hw->wiphy->wowlan.pattern_min_len = IWL_WOWLAN_MIN_PATTERN_LEN; + hw->wiphy->wowlan.pattern_max_len = IWL_WOWLAN_MAX_PATTERN_LEN; + hw->wiphy->wowlan.tcp = &iwl_mvm_wowlan_tcp_support; + } +#endif + + ret = iwl_mvm_leds_init(mvm); + if (ret) + return ret; + + ret = ieee80211_register_hw(mvm->hw); + if (ret) + iwl_mvm_leds_exit(mvm); + + return ret; +} + +static void iwl_mvm_mac_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + if (test_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status)) { + IWL_DEBUG_DROP(mvm, "Dropping - RF KILL\n"); + goto drop; + } + + if (IEEE80211_SKB_CB(skb)->hw_queue == IWL_MVM_OFFCHANNEL_QUEUE && + !test_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status)) + goto drop; + + if (control->sta) { + if (iwl_mvm_tx_skb(mvm, skb, control->sta)) + goto drop; + return; + } + + if (iwl_mvm_tx_skb_non_sta(mvm, skb)) + goto drop; + return; + drop: + ieee80211_free_txskb(hw, skb); +} + +static int iwl_mvm_mac_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, + u16 *ssn, u8 buf_size) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + IWL_DEBUG_HT(mvm, "A-MPDU action on addr %pM tid %d: action %d\n", + sta->addr, tid, action); + + if (!(mvm->nvm_data->sku_cap_11n_enable)) + return -EACCES; + + mutex_lock(&mvm->mutex); + + switch (action) { + case IEEE80211_AMPDU_RX_START: + if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_RXAGG) { + ret = -EINVAL; + break; + } + ret = iwl_mvm_sta_rx_agg(mvm, sta, tid, *ssn, true); + break; + case IEEE80211_AMPDU_RX_STOP: + ret = iwl_mvm_sta_rx_agg(mvm, sta, tid, 0, false); + break; + case IEEE80211_AMPDU_TX_START: + if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_TXAGG) { + ret = -EINVAL; + break; + } + ret = iwl_mvm_sta_tx_agg_start(mvm, vif, sta, tid, ssn); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + ret = iwl_mvm_sta_tx_agg_stop(mvm, vif, sta, tid); + break; + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + ret = iwl_mvm_sta_tx_agg_flush(mvm, vif, sta, tid); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + ret = iwl_mvm_sta_tx_agg_oper(mvm, vif, sta, tid, buf_size); + break; + default: + WARN_ON_ONCE(1); + ret = -EINVAL; + break; + } + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_cleanup_iterator(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + mvmvif->uploaded = false; + mvmvif->ap_sta_id = IWL_MVM_STATION_COUNT; + + /* does this make sense at all? */ + mvmvif->color++; + + spin_lock_bh(&mvm->time_event_lock); + iwl_mvm_te_clear_data(mvm, &mvmvif->time_event_data); + spin_unlock_bh(&mvm->time_event_lock); + + if (vif->type != NL80211_IFTYPE_P2P_DEVICE) + mvmvif->phy_ctxt = NULL; +} + +static void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm) +{ + iwl_trans_stop_device(mvm->trans); + iwl_trans_stop_hw(mvm->trans, false); + + mvm->scan_status = IWL_MVM_SCAN_NONE; + + /* just in case one was running */ + ieee80211_remain_on_channel_expired(mvm->hw); + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + iwl_mvm_cleanup_iterator, mvm); + + memset(mvm->fw_key_table, 0, sizeof(mvm->fw_key_table)); + memset(mvm->sta_drained, 0, sizeof(mvm->sta_drained)); + + ieee80211_wake_queues(mvm->hw); + + mvm->vif_count = 0; +} + +static int iwl_mvm_mac_start(struct ieee80211_hw *hw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + mutex_lock(&mvm->mutex); + + /* Clean up some internal and mac80211 state on restart */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) + iwl_mvm_restart_cleanup(mvm); + + ret = iwl_mvm_up(mvm); + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_mac_restart_complete(struct ieee80211_hw *hw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + mutex_lock(&mvm->mutex); + + clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status); + ret = iwl_mvm_update_quotas(mvm, NULL); + if (ret) + IWL_ERR(mvm, "Failed to update quotas after restart (%d)\n", + ret); + + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_mac_stop(struct ieee80211_hw *hw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + flush_work(&mvm->async_handlers_wk); + + mutex_lock(&mvm->mutex); + /* async_handlers_wk is now blocked */ + + /* + * The work item could be running or queued if the + * ROC time event stops just as we get here. + */ + cancel_work_sync(&mvm->roc_done_wk); + + iwl_trans_stop_device(mvm->trans); + iwl_trans_stop_hw(mvm->trans, false); + + iwl_mvm_async_handlers_purge(mvm); + /* async_handlers_list is empty and will stay empty: HW is stopped */ + + /* the fw is stopped, the aux sta is dead: clean up driver state */ + iwl_mvm_dealloc_int_sta(mvm, &mvm->aux_sta); + + mutex_unlock(&mvm->mutex); + + /* + * The worker might have been waiting for the mutex, let it run and + * discover that its list is now empty. + */ + cancel_work_sync(&mvm->async_handlers_wk); +} + +static void iwl_mvm_pm_disable_iterator(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = data; + int ret; + + ret = iwl_mvm_power_disable(mvm, vif); + if (ret) + IWL_ERR(mvm, "failed to disable power management\n"); +} + +static void iwl_mvm_power_update_iterator(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = data; + + iwl_mvm_power_update_mode(mvm, vif); +} + +static int iwl_mvm_mac_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + /* + * Not much to do here. The stack will not allow interface + * types or combinations that we didn't advertise, so we + * don't really have to check the types. + */ + + mutex_lock(&mvm->mutex); + + /* Allocate resources for the MAC context, and add it the the fw */ + ret = iwl_mvm_mac_ctxt_init(mvm, vif); + if (ret) + goto out_unlock; + + /* + * The AP binding flow can be done only after the beacon + * template is configured (which happens only in the mac80211 + * start_ap() flow), and adding the broadcast station can happen + * only after the binding. + * In addition, since modifying the MAC before adding a bcast + * station is not allowed by the FW, delay the adding of MAC context to + * the point where we can also add the bcast station. + * In short: there's not much we can do at this point, other than + * allocating resources :) + */ + if (vif->type == NL80211_IFTYPE_AP) { + u32 qmask = iwl_mvm_mac_get_queues_mask(mvm, vif); + ret = iwl_mvm_allocate_int_sta(mvm, &mvmvif->bcast_sta, + qmask); + if (ret) { + IWL_ERR(mvm, "Failed to allocate bcast sta\n"); + goto out_release; + } + + goto out_unlock; + } + + /* + * TODO: remove this temporary code. + * Currently MVM FW supports power management only on single MAC. + * If new interface added, disable PM on existing interface. + * P2P device is a special case, since it is handled by FW similary to + * scan. If P2P deviced is added, PM remains enabled on existing + * interface. + * Note: the method below does not count the new interface being added + * at this moment. + */ + if (vif->type != NL80211_IFTYPE_P2P_DEVICE) + mvm->vif_count++; + if (mvm->vif_count > 1) { + IWL_DEBUG_MAC80211(mvm, + "Disable power on existing interfaces\n"); + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, + IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_pm_disable_iterator, mvm); + } + + ret = iwl_mvm_mac_ctxt_add(mvm, vif); + if (ret) + goto out_release; + + /* + * Update power state on the new interface. Admittedly, based on + * mac80211 logics this power update will disable power management + */ + iwl_mvm_power_update_mode(mvm, vif); + + /* + * P2P_DEVICE interface does not have a channel context assigned to it, + * so a dedicated PHY context is allocated to it and the corresponding + * MAC context is bound to it at this stage. + */ + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + struct ieee80211_channel *chan; + struct cfg80211_chan_def chandef; + + mvmvif->phy_ctxt = &mvm->phy_ctxt_roc; + + /* + * The channel used here isn't relevant as it's + * going to be overwritten as part of the ROC flow. + * For now use the first channel we have. + */ + chan = &mvm->hw->wiphy->bands[IEEE80211_BAND_2GHZ]->channels[0]; + cfg80211_chandef_create(&chandef, chan, NL80211_CHAN_NO_HT); + ret = iwl_mvm_phy_ctxt_add(mvm, mvmvif->phy_ctxt, + &chandef, 1, 1); + if (ret) + goto out_remove_mac; + + ret = iwl_mvm_binding_add_vif(mvm, vif); + if (ret) + goto out_remove_phy; + + ret = iwl_mvm_add_bcast_sta(mvm, vif, &mvmvif->bcast_sta); + if (ret) + goto out_unbind; + + /* Save a pointer to p2p device vif, so it can later be used to + * update the p2p device MAC when a GO is started/stopped */ + mvm->p2p_device_vif = vif; + } + + iwl_mvm_vif_dbgfs_register(mvm, vif); + goto out_unlock; + + out_unbind: + iwl_mvm_binding_remove_vif(mvm, vif); + out_remove_phy: + iwl_mvm_phy_ctxt_remove(mvm, mvmvif->phy_ctxt); + out_remove_mac: + mvmvif->phy_ctxt = NULL; + iwl_mvm_mac_ctxt_remove(mvm, vif); + out_release: + /* + * TODO: remove this temporary code. + * Currently MVM FW supports power management only on single MAC. + * Check if only one additional interface remains after releasing + * current one. Update power mode on the remaining interface. + */ + if (vif->type != NL80211_IFTYPE_P2P_DEVICE) + mvm->vif_count--; + IWL_DEBUG_MAC80211(mvm, "Currently %d interfaces active\n", + mvm->vif_count); + if (mvm->vif_count == 1) { + ieee80211_iterate_active_interfaces( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_power_update_iterator, mvm); + } + iwl_mvm_mac_ctxt_release(mvm, vif); + out_unlock: + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_prepare_mac_removal(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + u32 tfd_msk = 0, ac; + + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) + if (vif->hw_queue[ac] != IEEE80211_INVAL_HW_QUEUE) + tfd_msk |= BIT(vif->hw_queue[ac]); + + if (vif->cab_queue != IEEE80211_INVAL_HW_QUEUE) + tfd_msk |= BIT(vif->cab_queue); + + if (tfd_msk) { + mutex_lock(&mvm->mutex); + iwl_mvm_flush_tx_path(mvm, tfd_msk, true); + mutex_unlock(&mvm->mutex); + } + + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + /* + * Flush the ROC worker which will flush the OFFCHANNEL queue. + * We assume here that all the packets sent to the OFFCHANNEL + * queue are sent in ROC session. + */ + flush_work(&mvm->roc_done_wk); + } else { + /* + * By now, all the AC queues are empty. The AGG queues are + * empty too. We already got all the Tx responses for all the + * packets in the queues. The drain work can have been + * triggered. Flush it. This work item takes the mutex, so kill + * it before we take it. + */ + flush_work(&mvm->sta_drained_wk); + } +} + +static void iwl_mvm_mac_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + iwl_mvm_prepare_mac_removal(mvm, vif); + + mutex_lock(&mvm->mutex); + + iwl_mvm_vif_dbgfs_clean(mvm, vif); + + /* + * For AP/GO interface, the tear down of the resources allocated to the + * interface is be handled as part of the stop_ap flow. + */ + if (vif->type == NL80211_IFTYPE_AP) { + iwl_mvm_dealloc_int_sta(mvm, &mvmvif->bcast_sta); + goto out_release; + } + + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + mvm->p2p_device_vif = NULL; + iwl_mvm_rm_bcast_sta(mvm, &mvmvif->bcast_sta); + iwl_mvm_binding_remove_vif(mvm, vif); + iwl_mvm_phy_ctxt_remove(mvm, mvmvif->phy_ctxt); + mvmvif->phy_ctxt = NULL; + } + + /* + * TODO: remove this temporary code. + * Currently MVM FW supports power management only on single MAC. + * Check if only one additional interface remains after removing + * current one. Update power mode on the remaining interface. + */ + if (mvm->vif_count && vif->type != NL80211_IFTYPE_P2P_DEVICE) + mvm->vif_count--; + IWL_DEBUG_MAC80211(mvm, "Currently %d interfaces active\n", + mvm->vif_count); + if (mvm->vif_count == 1) { + ieee80211_iterate_active_interfaces( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_power_update_iterator, mvm); + } + + iwl_mvm_mac_ctxt_remove(mvm, vif); + +out_release: + iwl_mvm_mac_ctxt_release(mvm, vif); + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_mac_config(struct ieee80211_hw *hw, u32 changed) +{ + return 0; +} + +static void iwl_mvm_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + *total_flags = 0; +} + +static int iwl_mvm_configure_mcast_filter(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mcast_filter_cmd mcast_filter_cmd = { + .pass_all = 1, + }; + + memcpy(mcast_filter_cmd.bssid, vif->bss_conf.bssid, ETH_ALEN); + + return iwl_mvm_send_cmd_pdu(mvm, MCAST_FILTER_CMD, CMD_SYNC, + sizeof(mcast_filter_cmd), + &mcast_filter_cmd); +} + +static void iwl_mvm_bss_info_changed_station(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changes) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + ret = iwl_mvm_mac_ctxt_changed(mvm, vif); + if (ret) + IWL_ERR(mvm, "failed to update MAC %pM\n", vif->addr); + + if (changes & BSS_CHANGED_ASSOC) { + if (bss_conf->assoc) { + /* add quota for this interface */ + ret = iwl_mvm_update_quotas(mvm, vif); + if (ret) { + IWL_ERR(mvm, "failed to update quotas\n"); + return; + } + iwl_mvm_bt_coex_vif_assoc(mvm, vif); + iwl_mvm_configure_mcast_filter(mvm, vif); + } else if (mvmvif->ap_sta_id != IWL_MVM_STATION_COUNT) { + /* remove AP station now that the MAC is unassoc */ + ret = iwl_mvm_rm_sta_id(mvm, vif, mvmvif->ap_sta_id); + if (ret) + IWL_ERR(mvm, "failed to remove AP station\n"); + mvmvif->ap_sta_id = IWL_MVM_STATION_COUNT; + /* remove quota for this interface */ + ret = iwl_mvm_update_quotas(mvm, NULL); + if (ret) + IWL_ERR(mvm, "failed to update quotas\n"); + } + } else if (changes & BSS_CHANGED_DTIM_PERIOD) { + /* + * We received a beacon _after_ association so + * remove the session protection. + */ + iwl_mvm_remove_time_event(mvm, mvmvif, + &mvmvif->time_event_data); + } else if (changes & BSS_CHANGED_PS) { + /* + * TODO: remove this temporary code. + * Currently MVM FW supports power management only on single + * MAC. Avoid power mode update if more than one interface + * is active. + */ + IWL_DEBUG_MAC80211(mvm, "Currently %d interfaces active\n", + mvm->vif_count); + if (mvm->vif_count == 1) { + ret = iwl_mvm_power_update_mode(mvm, vif); + if (ret) + IWL_ERR(mvm, "failed to update power mode\n"); + } + } +} + +static int iwl_mvm_start_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + mutex_lock(&mvm->mutex); + + /* Send the beacon template */ + ret = iwl_mvm_mac_ctxt_beacon_changed(mvm, vif); + if (ret) + goto out_unlock; + + /* Add the mac context */ + ret = iwl_mvm_mac_ctxt_add(mvm, vif); + if (ret) + goto out_unlock; + + /* Perform the binding */ + ret = iwl_mvm_binding_add_vif(mvm, vif); + if (ret) + goto out_remove; + + mvmvif->ap_active = true; + + /* Send the bcast station. At this stage the TBTT and DTIM time events + * are added and applied to the scheduler */ + ret = iwl_mvm_send_bcast_sta(mvm, vif, &mvmvif->bcast_sta); + if (ret) + goto out_unbind; + + ret = iwl_mvm_update_quotas(mvm, vif); + if (ret) + goto out_rm_bcast; + + /* Need to update the P2P Device MAC */ + if (vif->p2p && mvm->p2p_device_vif) + iwl_mvm_mac_ctxt_changed(mvm, mvm->p2p_device_vif); + + mutex_unlock(&mvm->mutex); + return 0; + +out_rm_bcast: + iwl_mvm_send_rm_bcast_sta(mvm, &mvmvif->bcast_sta); +out_unbind: + iwl_mvm_binding_remove_vif(mvm, vif); +out_remove: + iwl_mvm_mac_ctxt_remove(mvm, vif); +out_unlock: + mutex_unlock(&mvm->mutex); + return ret; +} + +static void iwl_mvm_stop_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + iwl_mvm_prepare_mac_removal(mvm, vif); + + mutex_lock(&mvm->mutex); + + mvmvif->ap_active = false; + + /* Need to update the P2P Device MAC */ + if (vif->p2p && mvm->p2p_device_vif) + iwl_mvm_mac_ctxt_changed(mvm, mvm->p2p_device_vif); + + iwl_mvm_update_quotas(mvm, NULL); + iwl_mvm_send_rm_bcast_sta(mvm, &mvmvif->bcast_sta); + iwl_mvm_binding_remove_vif(mvm, vif); + iwl_mvm_mac_ctxt_remove(mvm, vif); + + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_bss_info_changed_ap(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changes) +{ + /* Need to send a new beacon template to the FW */ + if (changes & BSS_CHANGED_BEACON) { + if (iwl_mvm_mac_ctxt_beacon_changed(mvm, vif)) + IWL_WARN(mvm, "Failed updating beacon data\n"); + } +} + +static void iwl_mvm_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changes) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mutex_lock(&mvm->mutex); + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + iwl_mvm_bss_info_changed_station(mvm, vif, bss_conf, changes); + break; + case NL80211_IFTYPE_AP: + iwl_mvm_bss_info_changed_ap(mvm, vif, bss_conf, changes); + break; + default: + /* shouldn't happen */ + WARN_ON_ONCE(1); + } + + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_mac_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_scan_request *req) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + if (req->n_channels == 0 || req->n_channels > MAX_NUM_SCAN_CHANNELS) + return -EINVAL; + + mutex_lock(&mvm->mutex); + + if (mvm->scan_status == IWL_MVM_SCAN_NONE) + ret = iwl_mvm_scan_request(mvm, vif, req); + else + ret = -EBUSY; + + mutex_unlock(&mvm->mutex); + + return ret; +} + +static void iwl_mvm_mac_cancel_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mutex_lock(&mvm->mutex); + + iwl_mvm_cancel_scan(mvm); + + mutex_unlock(&mvm->mutex); +} + +static void +iwl_mvm_mac_allow_buffered_frames(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u16 tid, + int num_frames, + enum ieee80211_frame_release_type reason, + bool more_data) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + /* TODO: how do we tell the fw to send frames for a specific TID */ + + /* + * The fw will send EOSP notification when the last frame will be + * transmitted. + */ + iwl_mvm_sta_modify_sleep_tx_count(mvm, sta, reason, num_frames); +} + +static void iwl_mvm_mac_sta_notify(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum sta_notify_cmd cmd, + struct ieee80211_sta *sta) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_sta *mvmsta = (void *)sta->drv_priv; + + switch (cmd) { + case STA_NOTIFY_SLEEP: + if (atomic_read(&mvm->pending_frames[mvmsta->sta_id]) > 0) + ieee80211_sta_block_awake(hw, sta, true); + /* + * The fw updates the STA to be asleep. Tx packets on the Tx + * queues to this station will not be transmitted. The fw will + * send a Tx response with TX_STATUS_FAIL_DEST_PS. + */ + break; + case STA_NOTIFY_AWAKE: + if (WARN_ON(mvmsta->sta_id == IWL_MVM_STATION_COUNT)) + break; + iwl_mvm_sta_modify_ps_wake(mvm, sta); + break; + default: + break; + } +} + +static int iwl_mvm_mac_sta_state(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + enum ieee80211_sta_state old_state, + enum ieee80211_sta_state new_state) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + IWL_DEBUG_MAC80211(mvm, "station %pM state change %d->%d\n", + sta->addr, old_state, new_state); + + /* this would be a mac80211 bug ... but don't crash */ + if (WARN_ON_ONCE(!mvmvif->phy_ctxt)) + return -EINVAL; + + /* if a STA is being removed, reuse its ID */ + flush_work(&mvm->sta_drained_wk); + + mutex_lock(&mvm->mutex); + if (old_state == IEEE80211_STA_NOTEXIST && + new_state == IEEE80211_STA_NONE) { + /* + * Firmware bug - it'll crash if the beacon interval is less + * than 16. We can't avoid connecting at all, so refuse the + * station state change, this will cause mac80211 to abandon + * attempts to connect to this AP, and eventually wpa_s will + * blacklist the AP... + */ + if (vif->type == NL80211_IFTYPE_STATION && + vif->bss_conf.beacon_int < 16) { + IWL_ERR(mvm, + "AP %pM beacon interval is %d, refusing due to firmware bug!\n", + sta->addr, vif->bss_conf.beacon_int); + ret = -EINVAL; + goto out_unlock; + } + ret = iwl_mvm_add_sta(mvm, vif, sta); + } else if (old_state == IEEE80211_STA_NONE && + new_state == IEEE80211_STA_AUTH) { + ret = 0; + } else if (old_state == IEEE80211_STA_AUTH && + new_state == IEEE80211_STA_ASSOC) { + ret = iwl_mvm_update_sta(mvm, vif, sta); + if (ret == 0) + iwl_mvm_rs_rate_init(mvm, sta, + mvmvif->phy_ctxt->channel->band); + } else if (old_state == IEEE80211_STA_ASSOC && + new_state == IEEE80211_STA_AUTHORIZED) { + ret = 0; + } else if (old_state == IEEE80211_STA_AUTHORIZED && + new_state == IEEE80211_STA_ASSOC) { + ret = 0; + } else if (old_state == IEEE80211_STA_ASSOC && + new_state == IEEE80211_STA_AUTH) { + ret = 0; + } else if (old_state == IEEE80211_STA_AUTH && + new_state == IEEE80211_STA_NONE) { + ret = 0; + } else if (old_state == IEEE80211_STA_NONE && + new_state == IEEE80211_STA_NOTEXIST) { + ret = iwl_mvm_rm_sta(mvm, vif, sta); + } else { + ret = -EIO; + } + out_unlock: + mutex_unlock(&mvm->mutex); + + return ret; +} + +static int iwl_mvm_mac_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + mvm->rts_threshold = value; + + return 0; +} + +static int iwl_mvm_mac_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 ac, + const struct ieee80211_tx_queue_params *params) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + mvmvif->queue_params[ac] = *params; + + /* + * No need to update right away, we'll get BSS_CHANGED_QOS + * The exception is P2P_DEVICE interface which needs immediate update. + */ + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { + int ret; + + mutex_lock(&mvm->mutex); + ret = iwl_mvm_mac_ctxt_changed(mvm, vif); + mutex_unlock(&mvm->mutex); + return ret; + } + return 0; +} + +static void iwl_mvm_mac_mgd_prepare_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + u32 duration = min(IWL_MVM_TE_SESSION_PROTECTION_MAX_TIME_MS, + 200 + vif->bss_conf.beacon_int); + u32 min_duration = min(IWL_MVM_TE_SESSION_PROTECTION_MIN_TIME_MS, + 100 + vif->bss_conf.beacon_int); + + if (WARN_ON_ONCE(vif->bss_conf.assoc)) + return; + + mutex_lock(&mvm->mutex); + /* Try really hard to protect the session and hear a beacon */ + iwl_mvm_protect_session(mvm, vif, duration, min_duration); + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_mac_set_key(struct ieee80211_hw *hw, + enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + int ret; + + if (iwlwifi_mod_params.sw_crypto) { + IWL_DEBUG_MAC80211(mvm, "leave - hwcrypto disabled\n"); + return -EOPNOTSUPP; + } + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_TKIP: + key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC; + /* fall-through */ + case WLAN_CIPHER_SUITE_CCMP: + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + WARN_ON_ONCE(!(hw->flags & IEEE80211_HW_MFP_CAPABLE)); + break; + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + /* + * Support for TX only, at least for now, so accept + * the key and do nothing else. Then mac80211 will + * pass it for TX but we don't have to use it for RX. + */ + return 0; + default: + return -EOPNOTSUPP; + } + + mutex_lock(&mvm->mutex); + + switch (cmd) { + case SET_KEY: + if (vif->type == NL80211_IFTYPE_AP && !sta) { + /* GTK on AP interface is a TX-only key, return 0 */ + ret = 0; + key->hw_key_idx = STA_KEY_IDX_INVALID; + break; + } + + IWL_DEBUG_MAC80211(mvm, "set hwcrypto key\n"); + ret = iwl_mvm_set_sta_key(mvm, vif, sta, key, false); + if (ret) { + IWL_WARN(mvm, "set key failed\n"); + /* + * can't add key for RX, but we don't need it + * in the device for TX so still return 0 + */ + key->hw_key_idx = STA_KEY_IDX_INVALID; + ret = 0; + } + + break; + case DISABLE_KEY: + if (key->hw_key_idx == STA_KEY_IDX_INVALID) { + ret = 0; + break; + } + + IWL_DEBUG_MAC80211(mvm, "disable hwcrypto key\n"); + ret = iwl_mvm_remove_sta_key(mvm, vif, sta, key); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&mvm->mutex); + return ret; +} + +static void iwl_mvm_mac_update_tkip_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, + u32 iv32, u16 *phase1key) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + iwl_mvm_update_tkip_key(mvm, vif, keyconf, sta, iv32, phase1key); +} + + +static int iwl_mvm_roc(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel *channel, + int duration, + enum ieee80211_roc_type type) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct cfg80211_chan_def chandef; + int ret; + + if (vif->type != NL80211_IFTYPE_P2P_DEVICE) { + IWL_ERR(mvm, "vif isn't a P2P_DEVICE: %d\n", vif->type); + return -EINVAL; + } + + IWL_DEBUG_MAC80211(mvm, "enter (%d, %d, %d)\n", channel->hw_value, + duration, type); + + mutex_lock(&mvm->mutex); + + cfg80211_chandef_create(&chandef, channel, NL80211_CHAN_NO_HT); + ret = iwl_mvm_phy_ctxt_changed(mvm, &mvm->phy_ctxt_roc, + &chandef, 1, 1); + + /* Schedule the time events */ + ret = iwl_mvm_start_p2p_roc(mvm, vif, duration, type); + + mutex_unlock(&mvm->mutex); + IWL_DEBUG_MAC80211(mvm, "leave\n"); + + return ret; +} + +static int iwl_mvm_cancel_roc(struct ieee80211_hw *hw) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + IWL_DEBUG_MAC80211(mvm, "enter\n"); + + mutex_lock(&mvm->mutex); + iwl_mvm_stop_p2p_roc(mvm); + mutex_unlock(&mvm->mutex); + + IWL_DEBUG_MAC80211(mvm, "leave\n"); + return 0; +} + +static int iwl_mvm_add_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_phy_ctxt *phy_ctxt = (void *)ctx->drv_priv; + int ret; + + mutex_lock(&mvm->mutex); + + IWL_DEBUG_MAC80211(mvm, "Add PHY context\n"); + ret = iwl_mvm_phy_ctxt_add(mvm, phy_ctxt, &ctx->def, + ctx->rx_chains_static, + ctx->rx_chains_dynamic); + mutex_unlock(&mvm->mutex); + return ret; +} + +static void iwl_mvm_remove_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_phy_ctxt *phy_ctxt = (void *)ctx->drv_priv; + + mutex_lock(&mvm->mutex); + iwl_mvm_phy_ctxt_remove(mvm, phy_ctxt); + mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_change_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx, + u32 changed) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_phy_ctxt *phy_ctxt = (void *)ctx->drv_priv; + + mutex_lock(&mvm->mutex); + iwl_mvm_phy_ctxt_changed(mvm, phy_ctxt, &ctx->def, + ctx->rx_chains_static, + ctx->rx_chains_dynamic); + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_assign_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *ctx) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_phy_ctxt *phyctx = (void *)ctx->drv_priv; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int ret; + + mutex_lock(&mvm->mutex); + + mvmvif->phy_ctxt = phyctx; + + switch (vif->type) { + case NL80211_IFTYPE_AP: + /* + * The AP binding flow is handled as part of the start_ap flow + * (in bss_info_changed). + */ + ret = 0; + goto out_unlock; + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_MONITOR: + break; + default: + ret = -EINVAL; + goto out_unlock; + } + + ret = iwl_mvm_binding_add_vif(mvm, vif); + if (ret) + goto out_unlock; + + /* + * Setting the quota at this stage is only required for monitor + * interfaces. For the other types, the bss_info changed flow + * will handle quota settings. + */ + if (vif->type == NL80211_IFTYPE_MONITOR) { + mvmvif->monitor_active = true; + ret = iwl_mvm_update_quotas(mvm, vif); + if (ret) + goto out_remove_binding; + } + + goto out_unlock; + + out_remove_binding: + iwl_mvm_binding_remove_vif(mvm, vif); + out_unlock: + mutex_unlock(&mvm->mutex); + if (ret) + mvmvif->phy_ctxt = NULL; + return ret; +} + +static void iwl_mvm_unassign_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *ctx) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + + mutex_lock(&mvm->mutex); + + iwl_mvm_remove_time_event(mvm, mvmvif, &mvmvif->time_event_data); + + if (vif->type == NL80211_IFTYPE_AP) + goto out_unlock; + + switch (vif->type) { + case NL80211_IFTYPE_MONITOR: + mvmvif->monitor_active = false; + iwl_mvm_update_quotas(mvm, NULL); + break; + default: + break; + } + + iwl_mvm_binding_remove_vif(mvm, vif); +out_unlock: + mvmvif->phy_ctxt = NULL; + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_set_tim(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + bool set) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + struct iwl_mvm_sta *mvm_sta = (void *)sta->drv_priv; + + if (!mvm_sta || !mvm_sta->vif) { + IWL_ERR(mvm, "Station is not associated to a vif\n"); + return -EINVAL; + } + + return iwl_mvm_mac_ctxt_beacon_changed(mvm, mvm_sta->vif); +} + +static void iwl_mvm_mac_rssi_callback(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_rssi_event rssi_event) +{ + struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + + iwl_mvm_bt_rssi_event(mvm, vif, rssi_event); +} + +struct ieee80211_ops iwl_mvm_hw_ops = { + .tx = iwl_mvm_mac_tx, + .ampdu_action = iwl_mvm_mac_ampdu_action, + .start = iwl_mvm_mac_start, + .restart_complete = iwl_mvm_mac_restart_complete, + .stop = iwl_mvm_mac_stop, + .add_interface = iwl_mvm_mac_add_interface, + .remove_interface = iwl_mvm_mac_remove_interface, + .config = iwl_mvm_mac_config, + .configure_filter = iwl_mvm_configure_filter, + .bss_info_changed = iwl_mvm_bss_info_changed, + .hw_scan = iwl_mvm_mac_hw_scan, + .cancel_hw_scan = iwl_mvm_mac_cancel_hw_scan, + .sta_state = iwl_mvm_mac_sta_state, + .sta_notify = iwl_mvm_mac_sta_notify, + .allow_buffered_frames = iwl_mvm_mac_allow_buffered_frames, + .set_rts_threshold = iwl_mvm_mac_set_rts_threshold, + .conf_tx = iwl_mvm_mac_conf_tx, + .mgd_prepare_tx = iwl_mvm_mac_mgd_prepare_tx, + .set_key = iwl_mvm_mac_set_key, + .update_tkip_key = iwl_mvm_mac_update_tkip_key, + .remain_on_channel = iwl_mvm_roc, + .cancel_remain_on_channel = iwl_mvm_cancel_roc, + .rssi_callback = iwl_mvm_mac_rssi_callback, + + .add_chanctx = iwl_mvm_add_chanctx, + .remove_chanctx = iwl_mvm_remove_chanctx, + .change_chanctx = iwl_mvm_change_chanctx, + .assign_vif_chanctx = iwl_mvm_assign_vif_chanctx, + .unassign_vif_chanctx = iwl_mvm_unassign_vif_chanctx, + + .start_ap = iwl_mvm_start_ap, + .stop_ap = iwl_mvm_stop_ap, + + .set_tim = iwl_mvm_set_tim, + +#ifdef CONFIG_PM_SLEEP + /* look at d3.c */ + .suspend = iwl_mvm_suspend, + .resume = iwl_mvm_resume, + .set_wakeup = iwl_mvm_set_wakeup, + .set_rekey_data = iwl_mvm_set_rekey_data, +#if IS_ENABLED(CONFIG_IPV6) + .ipv6_addr_change = iwl_mvm_ipv6_addr_change, +#endif + .set_default_unicast_key = iwl_mvm_set_default_unicast_key, +#endif +}; diff --git a/drivers/net/wireless/iwlwifi/mvm/mvm.h b/drivers/net/wireless/iwlwifi/mvm/mvm.h new file mode 100644 index 0000000..c76d11e --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/mvm.h @@ -0,0 +1,538 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#ifndef __IWL_MVM_H__ +#define __IWL_MVM_H__ + +#include +#include +#include +#include + +#include "iwl-op-mode.h" +#include "iwl-trans.h" +#include "iwl-notif-wait.h" +#include "iwl-eeprom-parse.h" +#include "iwl-test.h" +#include "iwl-trans.h" +#include "sta.h" +#include "fw-api.h" + +#define IWL_INVALID_MAC80211_QUEUE 0xff +#define IWL_MVM_MAX_ADDRESSES 5 +/* RSSI offset for WkP */ +#define IWL_RSSI_OFFSET 50 + +enum iwl_mvm_tx_fifo { + IWL_MVM_TX_FIFO_BK = 0, + IWL_MVM_TX_FIFO_BE, + IWL_MVM_TX_FIFO_VI, + IWL_MVM_TX_FIFO_VO, + IWL_MVM_TX_FIFO_MCAST = 5, +}; + +extern struct ieee80211_ops iwl_mvm_hw_ops; +/** + * struct iwl_mvm_mod_params - module parameters for iwlmvm + * @init_dbg: if true, then the NIC won't be stopped if the INIT fw asserted. + * We will register to mac80211 to have testmode working. The NIC must not + * be up'ed after the INIT fw asserted. This is useful to be able to use + * proprietary tools over testmode to debug the INIT fw. + * @power_scheme: CAM(Continuous Active Mode)-1, BPS(Balanced Power + * Save)-2(default), LP(Low Power)-3 + */ +struct iwl_mvm_mod_params { + bool init_dbg; + int power_scheme; +}; +extern struct iwl_mvm_mod_params iwlmvm_mod_params; + +struct iwl_mvm_phy_ctxt { + u16 id; + u16 color; + + /* + * TODO: This should probably be removed. Currently here only for rate + * scaling algorithm + */ + struct ieee80211_channel *channel; +}; + +struct iwl_mvm_time_event_data { + struct ieee80211_vif *vif; + struct list_head list; + unsigned long end_jiffies; + u32 duration; + bool running; + u32 uid; + + /* + * The access to the 'id' field must be done when the + * mvm->time_event_lock is held, as it value is used to indicate + * if the te is in the time event list or not (when id == TE_MAX) + */ + u32 id; +}; + + /* Power management */ + +/** + * enum iwl_power_scheme + * @IWL_POWER_LEVEL_CAM - Continuously Active Mode + * @IWL_POWER_LEVEL_BPS - Balanced Power Save (default) + * @IWL_POWER_LEVEL_LP - Low Power + */ +enum iwl_power_scheme { + IWL_POWER_SCHEME_CAM = 1, + IWL_POWER_SCHEME_BPS, + IWL_POWER_SCHEME_LP +}; + +#define IWL_CONN_MAX_LISTEN_INTERVAL 70 + +/** + * struct iwl_mvm_vif - data per Virtual Interface, it is a MAC context + * @id: between 0 and 3 + * @color: to solve races upon MAC addition and removal + * @ap_sta_id: the sta_id of the AP - valid only if VIF type is STA + * @uploaded: indicates the MAC context has been added to the device + * @ap_active: indicates that ap context is configured, and that the interface + * should get quota etc. + * @monitor_active: indicates that monitor context is configured, and that the + * interface should get quota etc. + * @queue_params: QoS params for this MAC + * @bcast_sta: station used for broadcast packets. Used by the following + * vifs: P2P_DEVICE, GO and AP. + * @beacon_skb: the skb used to hold the AP/GO beacon template + */ +struct iwl_mvm_vif { + u16 id; + u16 color; + u8 ap_sta_id; + + bool uploaded; + bool ap_active; + bool monitor_active; + + u32 ap_beacon_time; + + enum iwl_tsf_id tsf_id; + + /* + * QoS data from mac80211, need to store this here + * as mac80211 has a separate callback but we need + * to have the data for the MAC context + */ + struct ieee80211_tx_queue_params queue_params[IEEE80211_NUM_ACS]; + struct iwl_mvm_time_event_data time_event_data; + + struct iwl_mvm_int_sta bcast_sta; + + /* + * Assigned while mac80211 has the interface in a channel context, + * or, for P2P Device, while it exists. + */ + struct iwl_mvm_phy_ctxt *phy_ctxt; + +#ifdef CONFIG_PM_SLEEP + /* WoWLAN GTK rekey data */ + struct { + u8 kck[NL80211_KCK_LEN], kek[NL80211_KEK_LEN]; + __le64 replay_ctr; + bool valid; + } rekey_data; + + int tx_key_idx; + +#if IS_ENABLED(CONFIG_IPV6) + /* IPv6 addresses for WoWLAN */ + struct in6_addr target_ipv6_addrs[IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS]; + int num_target_ipv6_addrs; +#endif +#endif + +#ifdef CPTCFG_IWLWIFI_DEBUGFS + struct dentry *dbgfs_dir; + struct dentry *dbgfs_slink; + void *dbgfs_data; +#endif +}; + +static inline struct iwl_mvm_vif * +iwl_mvm_vif_from_mac80211(struct ieee80211_vif *vif) +{ + return (void *)vif->drv_priv; +} + +enum iwl_mvm_status { + IWL_MVM_STATUS_HW_RFKILL, + IWL_MVM_STATUS_ROC_RUNNING, + IWL_MVM_STATUS_IN_HW_RESTART, +}; + +enum iwl_scan_status { + IWL_MVM_SCAN_NONE, + IWL_MVM_SCAN_OS, +}; + +/** + * struct iwl_nvm_section - describes an NVM section in memory. + * + * This struct holds an NVM section read from the NIC using NVM_ACCESS_CMD, + * and saved for later use by the driver. Not all NVM sections are saved + * this way, only the needed ones. + */ +struct iwl_nvm_section { + u16 length; + const u8 *data; +}; + +struct iwl_mvm { + /* for logger access */ + struct device *dev; + + struct iwl_trans *trans; + const struct iwl_fw *fw; + const struct iwl_cfg *cfg; + struct iwl_phy_db *phy_db; + struct ieee80211_hw *hw; + + /* for protecting access to iwl_mvm */ + struct mutex mutex; + struct list_head async_handlers_list; + spinlock_t async_handlers_lock; + struct work_struct async_handlers_wk; + + struct work_struct roc_done_wk; + + unsigned long status; + + enum iwl_ucode_type cur_ucode; + bool ucode_loaded; + bool init_ucode_run; + u32 error_event_table; + u32 log_event_table; + + u32 ampdu_ref; + + struct iwl_notif_wait_data notif_wait; + + unsigned long transport_queue_stop; + u8 queue_to_mac80211[IWL_MAX_HW_QUEUES]; + atomic_t queue_stop_count[IWL_MAX_HW_QUEUES]; + + struct iwl_nvm_data *nvm_data; + /* NVM sections */ + struct iwl_nvm_section nvm_sections[NVM_NUM_OF_SECTIONS]; + + /* EEPROM MAC addresses */ + struct mac_address addresses[IWL_MVM_MAX_ADDRESSES]; + + /* data related to data path */ + struct iwl_rx_phy_info last_phy_info; + struct ieee80211_sta __rcu *fw_id_to_mac_id[IWL_MVM_STATION_COUNT]; + struct work_struct sta_drained_wk; + unsigned long sta_drained[BITS_TO_LONGS(IWL_MVM_STATION_COUNT)]; + atomic_t pending_frames[IWL_MVM_STATION_COUNT]; + + /* configured by mac80211 */ + u32 rts_threshold; + + /* Scan status, cmd (pre-allocated) and auxiliary station */ + enum iwl_scan_status scan_status; + struct iwl_scan_cmd *scan_cmd; + + /* Internal station */ + struct iwl_mvm_int_sta aux_sta; + + u8 scan_last_antenna_idx; /* to toggle TX between antennas */ + u8 mgmt_last_antenna_idx; + +#ifdef CPTCFG_IWLWIFI_DEBUGFS + struct dentry *debugfs_dir; + u32 dbgfs_sram_offset, dbgfs_sram_len; + bool prevent_power_down_d3; +#endif + + struct iwl_mvm_phy_ctxt phy_ctxt_roc; + + struct list_head time_event_list; + spinlock_t time_event_lock; + + /* + * A bitmap indicating the index of the key in use. The firmware + * can hold 16 keys at most. Reflect this fact. + */ + unsigned long fw_key_table[BITS_TO_LONGS(STA_KEY_MAX_NUM)]; + + /* + * This counter of created interfaces is referenced only in conjunction + * with FW limitation related to power management. Currently PM is + * supported only on a single interface. + * IMPORTANT: this variable counts all interfaces except P2P device. + */ + u8 vif_count; + + struct led_classdev led; + + struct ieee80211_vif *p2p_device_vif; + +#ifdef CONFIG_PM_SLEEP + int gtk_ivlen, gtk_icvlen, ptk_ivlen, ptk_icvlen; +#endif + + /* BT-Coex */ + u8 bt_kill_msk; + struct iwl_bt_coex_profile_notif last_bt_notif; +}; + +/* Extract MVM priv from op_mode and _hw */ +#define IWL_OP_MODE_GET_MVM(_iwl_op_mode) \ + ((struct iwl_mvm *)(_iwl_op_mode)->op_mode_specific) + +#define IWL_MAC80211_GET_MVM(_hw) \ + IWL_OP_MODE_GET_MVM((struct iwl_op_mode *)((_hw)->priv)) + +extern const u8 iwl_mvm_ac_to_tx_fifo[]; + +struct iwl_rate_info { + u8 plcp; /* uCode API: IWL_RATE_6M_PLCP, etc. */ + u8 plcp_siso; /* uCode API: IWL_RATE_SISO_6M_PLCP, etc. */ + u8 plcp_mimo2; /* uCode API: IWL_RATE_MIMO2_6M_PLCP, etc. */ + u8 plcp_mimo3; /* uCode API: IWL_RATE_MIMO3_6M_PLCP, etc. */ + u8 ieee; /* MAC header: IWL_RATE_6M_IEEE, etc. */ +}; + +/****************** + * MVM Methods + ******************/ +/* uCode */ +int iwl_run_init_mvm_ucode(struct iwl_mvm *mvm, bool read_nvm); + +/* Utils */ +int iwl_mvm_legacy_rate_to_mac80211_idx(u32 rate_n_flags, + enum ieee80211_band band); +u8 iwl_mvm_mac80211_idx_to_hwrate(int rate_idx); +void iwl_mvm_dump_nic_error_log(struct iwl_mvm *mvm); +u8 first_antenna(u8 mask); +u8 iwl_mvm_next_antenna(struct iwl_mvm *mvm, u8 valid, u8 last_idx); + +/* Tx / Host Commands */ +int __must_check iwl_mvm_send_cmd(struct iwl_mvm *mvm, + struct iwl_host_cmd *cmd); +int __must_check iwl_mvm_send_cmd_pdu(struct iwl_mvm *mvm, u8 id, + u32 flags, u16 len, const void *data); +int __must_check iwl_mvm_send_cmd_status(struct iwl_mvm *mvm, + struct iwl_host_cmd *cmd, + u32 *status); +int __must_check iwl_mvm_send_cmd_pdu_status(struct iwl_mvm *mvm, u8 id, + u16 len, const void *data, + u32 *status); +int iwl_mvm_tx_skb(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_sta *sta); +int iwl_mvm_tx_skb_non_sta(struct iwl_mvm *mvm, struct sk_buff *skb); +#ifdef CPTCFG_IWLWIFI_DEBUG +const char *iwl_mvm_get_tx_fail_reason(u32 status); +#else +static inline const char *iwl_mvm_get_tx_fail_reason(u32 status) { return ""; } +#endif +int iwl_mvm_flush_tx_path(struct iwl_mvm *mvm, u32 tfd_msk, bool sync); +void iwl_mvm_async_handlers_purge(struct iwl_mvm *mvm); + +/* Statistics */ +int iwl_mvm_rx_reply_statistics(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); +int iwl_mvm_rx_statistics(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); + +/* NVM */ +int iwl_nvm_init(struct iwl_mvm *mvm); + +int iwl_mvm_up(struct iwl_mvm *mvm); +int iwl_mvm_load_d3_fw(struct iwl_mvm *mvm); + +int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm); + +/* + * FW notifications / CMD responses handlers + * Convention: iwl_mvm_rx_ + */ +int iwl_mvm_rx_rx_phy_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); +int iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); +int iwl_mvm_rx_tx_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); +int iwl_mvm_rx_ba_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); +int iwl_mvm_rx_radio_ver(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); +int iwl_mvm_rx_fw_error(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); +int iwl_mvm_rx_card_state_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); +int iwl_mvm_rx_radio_ver(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); + +/* MVM PHY */ +int iwl_mvm_phy_ctxt_add(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic); +int iwl_mvm_phy_ctxt_changed(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic); +void iwl_mvm_phy_ctxt_remove(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt); + +/* MAC (virtual interface) programming */ +int iwl_mvm_mac_ctxt_init(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_mac_ctxt_release(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_mac_ctxt_add(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_mac_ctxt_changed(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_mac_ctxt_remove(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +u32 iwl_mvm_mac_get_queues_mask(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); +int iwl_mvm_mac_ctxt_beacon_changed(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); +int iwl_mvm_rx_beacon_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); + +/* Bindings */ +int iwl_mvm_binding_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_binding_remove_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif); + +/* Quota management */ +int iwl_mvm_update_quotas(struct iwl_mvm *mvm, struct ieee80211_vif *newvif); + +/* Scanning */ +int iwl_mvm_scan_request(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_scan_request *req); +int iwl_mvm_rx_scan_response(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); +int iwl_mvm_rx_scan_complete(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); +void iwl_mvm_cancel_scan(struct iwl_mvm *mvm); + +/* MVM debugfs */ +#ifdef CPTCFG_IWLWIFI_DEBUGFS +int iwl_mvm_dbgfs_register(struct iwl_mvm *mvm, struct dentry *dbgfs_dir); +void iwl_mvm_vif_dbgfs_register(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_vif_dbgfs_clean(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +#else +static inline int iwl_mvm_dbgfs_register(struct iwl_mvm *mvm, + struct dentry *dbgfs_dir) +{ + return 0; +} +static inline void +iwl_mvm_vif_dbgfs_register(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +} +static inline void +iwl_mvm_vif_dbgfs_clean(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +} +#endif /* CPTCFG_IWLWIFI_DEBUGFS */ + +/* rate scaling */ +int iwl_mvm_send_lq_cmd(struct iwl_mvm *mvm, struct iwl_lq_cmd *lq, + u8 flags, bool init); + +/* power managment */ +int iwl_mvm_power_update_mode(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_power_disable(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_power_build_cmd(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_powertable_cmd *cmd); + +int iwl_mvm_leds_init(struct iwl_mvm *mvm); +void iwl_mvm_leds_exit(struct iwl_mvm *mvm); + +/* D3 (WoWLAN, NetDetect) */ +int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan); +int iwl_mvm_resume(struct ieee80211_hw *hw); +void iwl_mvm_set_wakeup(struct ieee80211_hw *hw, bool enabled); +void iwl_mvm_set_rekey_data(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_gtk_rekey_data *data); +void iwl_mvm_ipv6_addr_change(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct inet6_dev *idev); +void iwl_mvm_set_default_unicast_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, int idx); + +/* BT Coex */ +int iwl_send_bt_prio_tbl(struct iwl_mvm *mvm); +int iwl_send_bt_init_conf(struct iwl_mvm *mvm); +int iwl_mvm_rx_bt_coex_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); +void iwl_mvm_bt_rssi_event(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + enum ieee80211_rssi_event rssi_event); +void iwl_mvm_bt_coex_vif_assoc(struct iwl_mvm *mvm, struct ieee80211_vif *vif); + +#endif /* __IWL_MVM_H__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/nvm.c b/drivers/net/wireless/iwlwifi/mvm/nvm.c new file mode 100644 index 0000000..b8ec02f --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/nvm.c @@ -0,0 +1,238 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#include "iwl-trans.h" +#include "mvm.h" +#include "iwl-eeprom-parse.h" +#include "iwl-eeprom-read.h" +#include "iwl-nvm-parse.h" + +/* list of NVM sections we are allowed/need to read */ +static const int nvm_to_read[] = { + NVM_SECTION_TYPE_HW, + NVM_SECTION_TYPE_SW, + NVM_SECTION_TYPE_CALIBRATION, + NVM_SECTION_TYPE_PRODUCTION, +}; + +/* Default NVM size to read */ +#define IWL_NVM_DEFAULT_CHUNK_SIZE (2*1024); + +static inline void iwl_nvm_fill_read(struct iwl_nvm_access_cmd *cmd, + u16 offset, u16 length, u16 section) +{ + cmd->offset = cpu_to_le16(offset); + cmd->length = cpu_to_le16(length); + cmd->type = cpu_to_le16(section); +} + +static int iwl_nvm_read_chunk(struct iwl_mvm *mvm, u16 section, + u16 offset, u16 length, u8 *data) +{ + struct iwl_nvm_access_cmd nvm_access_cmd = {}; + struct iwl_nvm_access_resp *nvm_resp; + struct iwl_rx_packet *pkt; + struct iwl_host_cmd cmd = { + .id = NVM_ACCESS_CMD, + .flags = CMD_SYNC | CMD_WANT_SKB, + .data = { &nvm_access_cmd, }, + }; + int ret, bytes_read, offset_read; + u8 *resp_data; + + iwl_nvm_fill_read(&nvm_access_cmd, offset, length, section); + cmd.len[0] = sizeof(struct iwl_nvm_access_cmd); + + ret = iwl_mvm_send_cmd(mvm, &cmd); + if (ret) + return ret; + + pkt = cmd.resp_pkt; + if (pkt->hdr.flags & IWL_CMD_FAILED_MSK) { + IWL_ERR(mvm, "Bad return from NVM_ACCES_COMMAND (0x%08X)\n", + pkt->hdr.flags); + ret = -EIO; + goto exit; + } + + /* Extract NVM response */ + nvm_resp = (void *)pkt->data; + ret = le16_to_cpu(nvm_resp->status); + bytes_read = le16_to_cpu(nvm_resp->length); + offset_read = le16_to_cpu(nvm_resp->offset); + resp_data = nvm_resp->data; + if (ret) { + IWL_ERR(mvm, + "NVM access command failed with status %d (device: %s)\n", + ret, mvm->cfg->name); + ret = -EINVAL; + goto exit; + } + + if (offset_read != offset) { + IWL_ERR(mvm, "NVM ACCESS response with invalid offset %d\n", + offset_read); + ret = -EINVAL; + goto exit; + } + + /* Write data to NVM */ + memcpy(data + offset, resp_data, bytes_read); + ret = bytes_read; + +exit: + iwl_free_resp(&cmd); + return ret; +} + +/* + * Reads an NVM section completely. + * NICs prior to 7000 family doesn't have a real NVM, but just read + * section 0 which is the EEPROM. Because the EEPROM reading is unlimited + * by uCode, we need to manually check in this case that we don't + * overflow and try to read more than the EEPROM size. + * For 7000 family NICs, we supply the maximal size we can read, and + * the uCode fills the response with as much data as we can, + * without overflowing, so no check is needed. + */ +static int iwl_nvm_read_section(struct iwl_mvm *mvm, u16 section, + u8 *data) +{ + u16 length, offset = 0; + int ret; + + /* Set nvm section read length */ + length = IWL_NVM_DEFAULT_CHUNK_SIZE; + + ret = length; + + /* Read the NVM until exhausted (reading less than requested) */ + while (ret == length) { + ret = iwl_nvm_read_chunk(mvm, section, offset, length, data); + if (ret < 0) { + IWL_ERR(mvm, + "Cannot read NVM from section %d offset %d, length %d\n", + section, offset, length); + return ret; + } + offset += ret; + } + + IWL_INFO(mvm, "NVM section %d read completed\n", section); + return offset; +} + +static struct iwl_nvm_data * +iwl_parse_nvm_sections(struct iwl_mvm *mvm) +{ + struct iwl_nvm_section *sections = mvm->nvm_sections; + const __le16 *hw, *sw, *calib; + + /* Checking for required sections */ + if (!mvm->nvm_sections[NVM_SECTION_TYPE_SW].data || + !mvm->nvm_sections[NVM_SECTION_TYPE_HW].data) { + IWL_ERR(mvm, "Can't parse empty NVM sections\n"); + return NULL; + } + + if (WARN_ON(!mvm->cfg)) + return NULL; + + hw = (const __le16 *)sections[NVM_SECTION_TYPE_HW].data; + sw = (const __le16 *)sections[NVM_SECTION_TYPE_SW].data; + calib = (const __le16 *)sections[NVM_SECTION_TYPE_CALIBRATION].data; + return iwl_parse_nvm_data(mvm->trans->dev, mvm->cfg, hw, sw, calib); +} + +int iwl_nvm_init(struct iwl_mvm *mvm) +{ + int ret, i, section; + u8 *nvm_buffer, *temp; + + /* TODO: find correct NVM max size for a section */ + nvm_buffer = kmalloc(mvm->cfg->base_params->eeprom_size, + GFP_KERNEL); + if (!nvm_buffer) + return -ENOMEM; + for (i = 0; i < ARRAY_SIZE(nvm_to_read); i++) { + section = nvm_to_read[i]; + /* we override the constness for initial read */ + ret = iwl_nvm_read_section(mvm, section, nvm_buffer); + if (ret < 0) + break; + temp = kmemdup(nvm_buffer, ret, GFP_KERNEL); + if (!temp) { + ret = -ENOMEM; + break; + } + mvm->nvm_sections[section].data = temp; + mvm->nvm_sections[section].length = ret; + } + kfree(nvm_buffer); + if (ret < 0) + return ret; + + ret = 0; + mvm->nvm_data = iwl_parse_nvm_sections(mvm); + + return ret; +} diff --git a/drivers/net/wireless/iwlwifi/mvm/ops.c b/drivers/net/wireless/iwlwifi/mvm/ops.c new file mode 100644 index 0000000..b29c31a --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/ops.c @@ -0,0 +1,677 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#include +#include + +#include "iwl-notif-wait.h" +#include "iwl-trans.h" +#include "iwl-op-mode.h" +#include "iwl-fw.h" +#include "iwl-debug.h" +#include "iwl-drv.h" +#include "iwl-modparams.h" +#include "mvm.h" +#include "iwl-phy-db.h" +#include "iwl-eeprom-parse.h" +#include "iwl-csr.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "rs.h" +#include "fw-api-scan.h" +#include "time-event.h" + +/* + * module name, copyright, version, etc. + */ +#define DRV_DESCRIPTION "The new Intel(R) wireless AGN driver for Linux" + +#define DRV_VERSION IWLWIFI_VERSION + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR(DRV_COPYRIGHT " " DRV_AUTHOR); +MODULE_LICENSE("GPL"); + +static const struct iwl_op_mode_ops iwl_mvm_ops; + +struct iwl_mvm_mod_params iwlmvm_mod_params = { + .power_scheme = IWL_POWER_SCHEME_BPS, + /* rest of fields are 0 by default */ +}; + +module_param_named(init_dbg, iwlmvm_mod_params.init_dbg, bool, S_IRUGO); +MODULE_PARM_DESC(init_dbg, + "set to true to debug an ASSERT in INIT fw (default: false"); +module_param_named(power_scheme, iwlmvm_mod_params.power_scheme, int, S_IRUGO); +MODULE_PARM_DESC(power_scheme, + "power management scheme: 1-active, 2-balanced, 3-low power, default: 2"); + +/* + * module init and exit functions + */ +static int __init iwl_mvm_init(void) +{ + int ret; + + ret = iwl_mvm_rate_control_register(); + if (ret) { + pr_err("Unable to register rate control algorithm: %d\n", ret); + return ret; + } + + ret = iwl_opmode_register("iwlmvm", &iwl_mvm_ops); + + if (ret) { + pr_err("Unable to register MVM op_mode: %d\n", ret); + iwl_mvm_rate_control_unregister(); + } + + return ret; +} +module_init(iwl_mvm_init); + +static void __exit iwl_mvm_exit(void) +{ + iwl_opmode_deregister("iwlmvm"); + iwl_mvm_rate_control_unregister(); +} +module_exit(iwl_mvm_exit); + +static void iwl_mvm_nic_config(struct iwl_op_mode *op_mode) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + u8 radio_cfg_type, radio_cfg_step, radio_cfg_dash; + u32 reg_val = 0; + + radio_cfg_type = (mvm->fw->phy_config & FW_PHY_CFG_RADIO_TYPE) >> + FW_PHY_CFG_RADIO_TYPE_POS; + radio_cfg_step = (mvm->fw->phy_config & FW_PHY_CFG_RADIO_STEP) >> + FW_PHY_CFG_RADIO_STEP_POS; + radio_cfg_dash = (mvm->fw->phy_config & FW_PHY_CFG_RADIO_DASH) >> + FW_PHY_CFG_RADIO_DASH_POS; + + /* SKU control */ + reg_val |= CSR_HW_REV_STEP(mvm->trans->hw_rev) << + CSR_HW_IF_CONFIG_REG_POS_MAC_STEP; + reg_val |= CSR_HW_REV_DASH(mvm->trans->hw_rev) << + CSR_HW_IF_CONFIG_REG_POS_MAC_DASH; + + /* radio configuration */ + reg_val |= radio_cfg_type << CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE; + reg_val |= radio_cfg_step << CSR_HW_IF_CONFIG_REG_POS_PHY_STEP; + reg_val |= radio_cfg_dash << CSR_HW_IF_CONFIG_REG_POS_PHY_DASH; + + WARN_ON((radio_cfg_type << CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE) & + ~CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE); + + /* silicon bits */ + reg_val |= CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI; + + iwl_trans_set_bits_mask(mvm->trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_MSK_MAC_DASH | + CSR_HW_IF_CONFIG_REG_MSK_MAC_STEP | + CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE | + CSR_HW_IF_CONFIG_REG_MSK_PHY_STEP | + CSR_HW_IF_CONFIG_REG_MSK_PHY_DASH | + CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI | + CSR_HW_IF_CONFIG_REG_BIT_MAC_SI, + reg_val); + + IWL_DEBUG_INFO(mvm, "Radio type=0x%x-0x%x-0x%x\n", radio_cfg_type, + radio_cfg_step, radio_cfg_dash); + + /* + * W/A : NIC is stuck in a reset state after Early PCIe power off + * (PCIe power is lost before PERST# is asserted), causing ME FW + * to lose ownership and not being able to obtain it back. + */ + iwl_set_bits_mask_prph(mvm->trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS, + ~APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS); +} + +struct iwl_rx_handlers { + u8 cmd_id; + bool async; + int (*fn)(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); +}; + +#define RX_HANDLER(_cmd_id, _fn, _async) \ + { .cmd_id = _cmd_id , .fn = _fn , .async = _async } + +/* + * Handlers for fw notifications + * Convention: RX_HANDLER(CMD_NAME, iwl_mvm_rx_CMD_NAME + * This list should be in order of frequency for performance purposes. + * + * The handler can be SYNC - this means that it will be called in the Rx path + * which can't acquire mvm->mutex. If the handler needs to hold mvm->mutex (and + * only in this case!), it should be set as ASYNC. In that case, it will be + * called from a worker with mvm->mutex held. + */ +static const struct iwl_rx_handlers iwl_mvm_rx_handlers[] = { + RX_HANDLER(REPLY_RX_MPDU_CMD, iwl_mvm_rx_rx_mpdu, false), + RX_HANDLER(REPLY_RX_PHY_CMD, iwl_mvm_rx_rx_phy_cmd, false), + RX_HANDLER(TX_CMD, iwl_mvm_rx_tx_cmd, false), + RX_HANDLER(BA_NOTIF, iwl_mvm_rx_ba_notif, false), + RX_HANDLER(TIME_EVENT_NOTIFICATION, iwl_mvm_rx_time_event_notif, false), + + RX_HANDLER(SCAN_REQUEST_CMD, iwl_mvm_rx_scan_response, false), + RX_HANDLER(SCAN_COMPLETE_NOTIFICATION, iwl_mvm_rx_scan_complete, false), + + RX_HANDLER(BT_PROFILE_NOTIFICATION, iwl_mvm_rx_bt_coex_notif, true), + RX_HANDLER(BEACON_NOTIFICATION, iwl_mvm_rx_beacon_notif, false), + + RX_HANDLER(RADIO_VERSION_NOTIFICATION, iwl_mvm_rx_radio_ver, false), + RX_HANDLER(CARD_STATE_NOTIFICATION, iwl_mvm_rx_card_state_notif, false), + + RX_HANDLER(REPLY_ERROR, iwl_mvm_rx_fw_error, false), +}; +#undef RX_HANDLER +#define CMD(x) [x] = #x + +static const char *iwl_mvm_cmd_strings[REPLY_MAX] = { + CMD(MVM_ALIVE), + CMD(REPLY_ERROR), + CMD(INIT_COMPLETE_NOTIF), + CMD(PHY_CONTEXT_CMD), + CMD(MGMT_MCAST_KEY), + CMD(TX_CMD), + CMD(TXPATH_FLUSH), + CMD(MAC_CONTEXT_CMD), + CMD(TIME_EVENT_CMD), + CMD(TIME_EVENT_NOTIFICATION), + CMD(BINDING_CONTEXT_CMD), + CMD(TIME_QUOTA_CMD), + CMD(RADIO_VERSION_NOTIFICATION), + CMD(SCAN_REQUEST_CMD), + CMD(SCAN_ABORT_CMD), + CMD(SCAN_START_NOTIFICATION), + CMD(SCAN_RESULTS_NOTIFICATION), + CMD(SCAN_COMPLETE_NOTIFICATION), + CMD(NVM_ACCESS_CMD), + CMD(PHY_CONFIGURATION_CMD), + CMD(CALIB_RES_NOTIF_PHY_DB), + CMD(SET_CALIB_DEFAULT_CMD), + CMD(CALIBRATION_COMPLETE_NOTIFICATION), + CMD(ADD_STA), + CMD(REMOVE_STA), + CMD(LQ_CMD), + CMD(SCAN_OFFLOAD_CONFIG_CMD), + CMD(SCAN_OFFLOAD_REQUEST_CMD), + CMD(SCAN_OFFLOAD_ABORT_CMD), + CMD(SCAN_OFFLOAD_COMPLETE), + CMD(SCAN_OFFLOAD_UPDATE_PROFILES_CMD), + CMD(POWER_TABLE_CMD), + CMD(WEP_KEY), + CMD(REPLY_RX_PHY_CMD), + CMD(REPLY_RX_MPDU_CMD), + CMD(BEACON_NOTIFICATION), + CMD(BEACON_TEMPLATE_CMD), + CMD(STATISTICS_NOTIFICATION), + CMD(TX_ANT_CONFIGURATION_CMD), + CMD(D3_CONFIG_CMD), + CMD(PROT_OFFLOAD_CONFIG_CMD), + CMD(OFFLOADS_QUERY_CMD), + CMD(REMOTE_WAKE_CONFIG_CMD), + CMD(WOWLAN_PATTERNS), + CMD(WOWLAN_CONFIGURATION), + CMD(WOWLAN_TSC_RSC_PARAM), + CMD(WOWLAN_TKIP_PARAM), + CMD(WOWLAN_KEK_KCK_MATERIAL), + CMD(WOWLAN_GET_STATUSES), + CMD(WOWLAN_TX_POWER_PER_DB), + CMD(NET_DETECT_CONFIG_CMD), + CMD(NET_DETECT_PROFILES_QUERY_CMD), + CMD(NET_DETECT_PROFILES_CMD), + CMD(NET_DETECT_HOTSPOTS_CMD), + CMD(NET_DETECT_HOTSPOTS_QUERY_CMD), + CMD(CARD_STATE_NOTIFICATION), + CMD(BT_COEX_PRIO_TABLE), + CMD(BT_COEX_PROT_ENV), + CMD(BT_PROFILE_NOTIFICATION), + CMD(BT_CONFIG), + CMD(MCAST_FILTER_CMD), +}; +#undef CMD + +/* this forward declaration can avoid to export the function */ +static void iwl_mvm_async_handlers_wk(struct work_struct *wk); + +static struct iwl_op_mode * +iwl_op_mode_mvm_start(struct iwl_trans *trans, const struct iwl_cfg *cfg, + const struct iwl_fw *fw, struct dentry *dbgfs_dir) +{ + struct ieee80211_hw *hw; + struct iwl_op_mode *op_mode; + struct iwl_mvm *mvm; + struct iwl_trans_config trans_cfg = {}; + static const u8 no_reclaim_cmds[] = { + TX_CMD, + }; + int err, scan_size; + + /******************************** + * 1. Allocating and configuring HW data + ********************************/ + hw = ieee80211_alloc_hw(sizeof(struct iwl_op_mode) + + sizeof(struct iwl_mvm), + &iwl_mvm_hw_ops); + if (!hw) + return NULL; + + op_mode = hw->priv; + op_mode->ops = &iwl_mvm_ops; + op_mode->trans = trans; + + mvm = IWL_OP_MODE_GET_MVM(op_mode); + mvm->dev = trans->dev; + mvm->trans = trans; + mvm->cfg = cfg; + mvm->fw = fw; + mvm->hw = hw; + + mutex_init(&mvm->mutex); + spin_lock_init(&mvm->async_handlers_lock); + INIT_LIST_HEAD(&mvm->time_event_list); + INIT_LIST_HEAD(&mvm->async_handlers_list); + spin_lock_init(&mvm->time_event_lock); + + INIT_WORK(&mvm->async_handlers_wk, iwl_mvm_async_handlers_wk); + INIT_WORK(&mvm->roc_done_wk, iwl_mvm_roc_done_wk); + INIT_WORK(&mvm->sta_drained_wk, iwl_mvm_sta_drained_wk); + + SET_IEEE80211_DEV(mvm->hw, mvm->trans->dev); + + /* + * Populate the state variables that the transport layer needs + * to know about. + */ + trans_cfg.op_mode = op_mode; + trans_cfg.no_reclaim_cmds = no_reclaim_cmds; + trans_cfg.n_no_reclaim_cmds = ARRAY_SIZE(no_reclaim_cmds); + trans_cfg.rx_buf_size_8k = iwlwifi_mod_params.amsdu_size_8K; + + if (mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_DW_BC_TABLE) + trans_cfg.bc_table_dword = true; + + if (!iwlwifi_mod_params.wd_disable) + trans_cfg.queue_watchdog_timeout = cfg->base_params->wd_timeout; + else + trans_cfg.queue_watchdog_timeout = IWL_WATCHDOG_DISABLED; + + trans_cfg.command_names = iwl_mvm_cmd_strings; + + trans_cfg.cmd_queue = IWL_MVM_CMD_QUEUE; + trans_cfg.cmd_fifo = IWL_MVM_CMD_FIFO; + + snprintf(mvm->hw->wiphy->fw_version, + sizeof(mvm->hw->wiphy->fw_version), + "%s", fw->fw_version); + + /* Configure transport layer */ + iwl_trans_configure(mvm->trans, &trans_cfg); + + trans->rx_mpdu_cmd = REPLY_RX_MPDU_CMD; + trans->rx_mpdu_cmd_hdr_size = sizeof(struct iwl_rx_mpdu_res_start); + + /* set up notification wait support */ + iwl_notification_wait_init(&mvm->notif_wait); + + /* Init phy db */ + mvm->phy_db = iwl_phy_db_init(trans); + if (!mvm->phy_db) { + IWL_ERR(mvm, "Cannot init phy_db\n"); + goto out_free; + } + + IWL_INFO(mvm, "Detected %s, REV=0x%X\n", + mvm->cfg->name, mvm->trans->hw_rev); + + err = iwl_trans_start_hw(mvm->trans); + if (err) + goto out_free; + + mutex_lock(&mvm->mutex); + err = iwl_run_init_mvm_ucode(mvm, true); + mutex_unlock(&mvm->mutex); + if (err && !iwlmvm_mod_params.init_dbg) { + IWL_ERR(mvm, "Failed to run INIT ucode: %d\n", err); + goto out_free; + } + + /* Stop the hw after the ALIVE and NVM has been read */ + if (!iwlmvm_mod_params.init_dbg) + iwl_trans_stop_hw(mvm->trans, false); + + scan_size = sizeof(struct iwl_scan_cmd) + + mvm->fw->ucode_capa.max_probe_length + + (MAX_NUM_SCAN_CHANNELS * sizeof(struct iwl_scan_channel)); + mvm->scan_cmd = kmalloc(scan_size, GFP_KERNEL); + if (!mvm->scan_cmd) + goto out_free; + + err = iwl_mvm_mac_setup_register(mvm); + if (err) + goto out_free; + + err = iwl_mvm_dbgfs_register(mvm, dbgfs_dir); + if (err) + goto out_unregister; + + return op_mode; + + out_unregister: + ieee80211_unregister_hw(mvm->hw); + out_free: + iwl_phy_db_free(mvm->phy_db); + kfree(mvm->scan_cmd); + iwl_trans_stop_hw(trans, true); + ieee80211_free_hw(mvm->hw); + return NULL; +} + +static void iwl_op_mode_mvm_stop(struct iwl_op_mode *op_mode) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + int i; + + iwl_mvm_leds_exit(mvm); + + ieee80211_unregister_hw(mvm->hw); + + kfree(mvm->scan_cmd); + + iwl_trans_stop_hw(mvm->trans, true); + + iwl_phy_db_free(mvm->phy_db); + mvm->phy_db = NULL; + + iwl_free_nvm_data(mvm->nvm_data); + for (i = 0; i < NVM_NUM_OF_SECTIONS; i++) + kfree(mvm->nvm_sections[i].data); + + ieee80211_free_hw(mvm->hw); +} + +struct iwl_async_handler_entry { + struct list_head list; + struct iwl_rx_cmd_buffer rxb; + int (*fn)(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); +}; + +void iwl_mvm_async_handlers_purge(struct iwl_mvm *mvm) +{ + struct iwl_async_handler_entry *entry, *tmp; + + spin_lock_bh(&mvm->async_handlers_lock); + list_for_each_entry_safe(entry, tmp, &mvm->async_handlers_list, list) { + iwl_free_rxb(&entry->rxb); + list_del(&entry->list); + kfree(entry); + } + spin_unlock_bh(&mvm->async_handlers_lock); +} + +static void iwl_mvm_async_handlers_wk(struct work_struct *wk) +{ + struct iwl_mvm *mvm = + container_of(wk, struct iwl_mvm, async_handlers_wk); + struct iwl_async_handler_entry *entry, *tmp; + struct list_head local_list; + + INIT_LIST_HEAD(&local_list); + + /* Ensure that we are not in stop flow (check iwl_mvm_mac_stop) */ + mutex_lock(&mvm->mutex); + + /* + * Sync with Rx path with a lock. Remove all the entries from this list, + * add them to a local one (lock free), and then handle them. + */ + spin_lock_bh(&mvm->async_handlers_lock); + list_splice_init(&mvm->async_handlers_list, &local_list); + spin_unlock_bh(&mvm->async_handlers_lock); + + list_for_each_entry_safe(entry, tmp, &local_list, list) { + if (entry->fn(mvm, &entry->rxb, NULL)) + IWL_WARN(mvm, + "returned value from ASYNC handlers are ignored\n"); + iwl_free_rxb(&entry->rxb); + list_del(&entry->list); + kfree(entry); + } + mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_rx_dispatch(struct iwl_op_mode *op_mode, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + u8 i; + + /* + * Do the notification wait before RX handlers so + * even if the RX handler consumes the RXB we have + * access to it in the notification wait entry. + */ + iwl_notification_wait_notify(&mvm->notif_wait, pkt); + + for (i = 0; i < ARRAY_SIZE(iwl_mvm_rx_handlers); i++) { + const struct iwl_rx_handlers *rx_h = &iwl_mvm_rx_handlers[i]; + struct iwl_async_handler_entry *entry; + + if (rx_h->cmd_id != pkt->hdr.cmd) + continue; + + if (!rx_h->async) + return rx_h->fn(mvm, rxb, cmd); + + entry = kzalloc(sizeof(*entry), GFP_ATOMIC); + /* we can't do much... */ + if (!entry) + return 0; + + entry->rxb._page = rxb_steal_page(rxb); + entry->rxb._offset = rxb->_offset; + entry->rxb._rx_page_order = rxb->_rx_page_order; + entry->fn = rx_h->fn; + spin_lock(&mvm->async_handlers_lock); + list_add_tail(&entry->list, &mvm->async_handlers_list); + spin_unlock(&mvm->async_handlers_lock); + schedule_work(&mvm->async_handlers_wk); + break; + } + + return 0; +} + +static void iwl_mvm_stop_sw_queue(struct iwl_op_mode *op_mode, int queue) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + int mq = mvm->queue_to_mac80211[queue]; + + if (WARN_ON_ONCE(mq == IWL_INVALID_MAC80211_QUEUE)) + return; + + if (atomic_inc_return(&mvm->queue_stop_count[mq]) > 1) { + IWL_DEBUG_TX_QUEUES(mvm, + "queue %d (mac80211 %d) already stopped\n", + queue, mq); + return; + } + + set_bit(mq, &mvm->transport_queue_stop); + ieee80211_stop_queue(mvm->hw, mq); +} + +static void iwl_mvm_wake_sw_queue(struct iwl_op_mode *op_mode, int queue) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + int mq = mvm->queue_to_mac80211[queue]; + + if (WARN_ON_ONCE(mq == IWL_INVALID_MAC80211_QUEUE)) + return; + + if (atomic_dec_return(&mvm->queue_stop_count[mq]) > 0) { + IWL_DEBUG_TX_QUEUES(mvm, + "queue %d (mac80211 %d) already awake\n", + queue, mq); + return; + } + + clear_bit(mq, &mvm->transport_queue_stop); + + ieee80211_wake_queue(mvm->hw, mq); +} + +static void iwl_mvm_set_hw_rfkill_state(struct iwl_op_mode *op_mode, bool state) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + + if (state) + set_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status); + else + clear_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status); + + wiphy_rfkill_set_hw_state(mvm->hw->wiphy, state); +} + +static void iwl_mvm_free_skb(struct iwl_op_mode *op_mode, struct sk_buff *skb) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + struct ieee80211_tx_info *info; + + info = IEEE80211_SKB_CB(skb); + iwl_trans_free_tx_cmd(mvm->trans, info->driver_data[1]); + ieee80211_free_txskb(mvm->hw, skb); +} + +static void iwl_mvm_nic_restart(struct iwl_mvm *mvm) +{ + iwl_abort_notification_waits(&mvm->notif_wait); + + /* + * If we're restarting already, don't cycle restarts. + * If INIT fw asserted, it will likely fail again. + * If WoWLAN fw asserted, don't restart either, mac80211 + * can't recover this since we're already half suspended. + */ + if (test_and_set_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { + IWL_ERR(mvm, "Firmware error during reconfiguration! Abort.\n"); + } else if (mvm->cur_ucode == IWL_UCODE_REGULAR && + iwlwifi_mod_params.restart_fw) { + /* + * This is a bit racy, but worst case we tell mac80211 about + * a stopped/aborted (sched) scan when that was already done + * which is not a problem. It is necessary to abort any scan + * here because mac80211 requires having the scan cleared + * before restarting. + * We'll reset the scan_status to NONE in restart cleanup in + * the next start() call from mac80211. + */ + switch (mvm->scan_status) { + case IWL_MVM_SCAN_NONE: + break; + case IWL_MVM_SCAN_OS: + ieee80211_scan_completed(mvm->hw, true); + break; + } + + ieee80211_restart_hw(mvm->hw); + } +} + +static void iwl_mvm_nic_error(struct iwl_op_mode *op_mode) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + + iwl_mvm_dump_nic_error_log(mvm); + + iwl_mvm_nic_restart(mvm); +} + +static void iwl_mvm_cmd_queue_full(struct iwl_op_mode *op_mode) +{ + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + + WARN_ON(1); + iwl_mvm_nic_restart(mvm); +} + +static const struct iwl_op_mode_ops iwl_mvm_ops = { + .start = iwl_op_mode_mvm_start, + .stop = iwl_op_mode_mvm_stop, + .rx = iwl_mvm_rx_dispatch, + .queue_full = iwl_mvm_stop_sw_queue, + .queue_not_full = iwl_mvm_wake_sw_queue, + .hw_rf_kill = iwl_mvm_set_hw_rfkill_state, + .free_skb = iwl_mvm_free_skb, + .nic_error = iwl_mvm_nic_error, + .cmd_queue_full = iwl_mvm_cmd_queue_full, + .nic_config = iwl_mvm_nic_config, +}; diff --git a/drivers/net/wireless/iwlwifi/mvm/phy-ctxt.c b/drivers/net/wireless/iwlwifi/mvm/phy-ctxt.c new file mode 100644 index 0000000..a28a1d1 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/phy-ctxt.c @@ -0,0 +1,286 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#include +#include "fw-api.h" +#include "mvm.h" + +/* Maps the driver specific channel width definition to the the fw values */ +static inline u8 iwl_mvm_get_channel_width(struct cfg80211_chan_def *chandef) +{ + switch (chandef->width) { + case NL80211_CHAN_WIDTH_20_NOHT: + case NL80211_CHAN_WIDTH_20: + return PHY_VHT_CHANNEL_MODE20; + case NL80211_CHAN_WIDTH_40: + return PHY_VHT_CHANNEL_MODE40; + case NL80211_CHAN_WIDTH_80: + return PHY_VHT_CHANNEL_MODE80; + case NL80211_CHAN_WIDTH_160: + return PHY_VHT_CHANNEL_MODE160; + default: + WARN(1, "Invalid channel width=%u", chandef->width); + return PHY_VHT_CHANNEL_MODE20; + } +} + +/* + * Maps the driver specific control channel position (relative to the center + * freq) definitions to the the fw values + */ +static inline u8 iwl_mvm_get_ctrl_pos(struct cfg80211_chan_def *chandef) +{ + switch (chandef->chan->center_freq - chandef->center_freq1) { + case -70: + return PHY_VHT_CTRL_POS_4_BELOW; + case -50: + return PHY_VHT_CTRL_POS_3_BELOW; + case -30: + return PHY_VHT_CTRL_POS_2_BELOW; + case -10: + return PHY_VHT_CTRL_POS_1_BELOW; + case 10: + return PHY_VHT_CTRL_POS_1_ABOVE; + case 30: + return PHY_VHT_CTRL_POS_2_ABOVE; + case 50: + return PHY_VHT_CTRL_POS_3_ABOVE; + case 70: + return PHY_VHT_CTRL_POS_4_ABOVE; + default: + WARN(1, "Invalid channel definition"); + case 0: + /* + * The FW is expected to check the control channel position only + * when in HT/VHT and the channel width is not 20MHz. Return + * this value as the default one. + */ + return PHY_VHT_CTRL_POS_1_BELOW; + } +} + +/* + * Construct the generic fields of the PHY context command + */ +static void iwl_mvm_phy_ctxt_cmd_hdr(struct iwl_mvm_phy_ctxt *ctxt, + struct iwl_phy_context_cmd *cmd, + u32 action, u32 apply_time) +{ + memset(cmd, 0, sizeof(struct iwl_phy_context_cmd)); + + cmd->id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(ctxt->id, + ctxt->color)); + cmd->action = cpu_to_le32(action); + cmd->apply_time = cpu_to_le32(apply_time); +} + +/* + * Add the phy configuration to the PHY context command + */ +static void iwl_mvm_phy_ctxt_cmd_data(struct iwl_mvm *mvm, + struct iwl_phy_context_cmd *cmd, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic) +{ + u8 active_cnt, idle_cnt; + + /* Set the channel info data */ + cmd->ci.band = (chandef->chan->band == IEEE80211_BAND_2GHZ ? + PHY_BAND_24 : PHY_BAND_5); + + cmd->ci.channel = chandef->chan->hw_value; + cmd->ci.width = iwl_mvm_get_channel_width(chandef); + cmd->ci.ctrl_pos = iwl_mvm_get_ctrl_pos(chandef); + + /* Set rx the chains */ + idle_cnt = chains_static; + active_cnt = chains_dynamic; + + cmd->rxchain_info = cpu_to_le32(iwl_fw_valid_rx_ant(mvm->fw) << + PHY_RX_CHAIN_VALID_POS); + cmd->rxchain_info |= cpu_to_le32(idle_cnt << PHY_RX_CHAIN_CNT_POS); + cmd->rxchain_info |= cpu_to_le32(active_cnt << + PHY_RX_CHAIN_MIMO_CNT_POS); + + cmd->txchain_info = cpu_to_le32(iwl_fw_valid_tx_ant(mvm->fw)); +} + +/* + * Send a command to apply the current phy configuration. The command is send + * only if something in the configuration changed: in case that this is the + * first time that the phy configuration is applied or in case that the phy + * configuration changed from the previous apply. + */ +static int iwl_mvm_phy_ctxt_apply(struct iwl_mvm *mvm, + struct iwl_mvm_phy_ctxt *ctxt, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic, + u32 action, u32 apply_time) +{ + struct iwl_phy_context_cmd cmd; + int ret; + + /* Set the command header fields */ + iwl_mvm_phy_ctxt_cmd_hdr(ctxt, &cmd, action, apply_time); + + /* Set the command data */ + iwl_mvm_phy_ctxt_cmd_data(mvm, &cmd, chandef, + chains_static, chains_dynamic); + + ret = iwl_mvm_send_cmd_pdu(mvm, PHY_CONTEXT_CMD, CMD_SYNC, + sizeof(struct iwl_phy_context_cmd), + &cmd); + if (ret) + IWL_ERR(mvm, "PHY ctxt cmd error. ret=%d\n", ret); + return ret; +} + + +struct phy_ctx_used_data { + unsigned long used[BITS_TO_LONGS(NUM_PHY_CTX)]; +}; + +static void iwl_mvm_phy_ctx_used_iter(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx, + void *_data) +{ + struct phy_ctx_used_data *data = _data; + struct iwl_mvm_phy_ctxt *phy_ctxt = (void *)ctx->drv_priv; + + __set_bit(phy_ctxt->id, data->used); +} + +/* + * Send a command to add a PHY context based on the current HW configuration. + */ +int iwl_mvm_phy_ctxt_add(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic) +{ + struct phy_ctx_used_data data = { + .used = { }, + }; + + /* + * If this is a regular PHY context (not the ROC one) + * skip the ROC PHY context's ID. + */ + if (ctxt != &mvm->phy_ctxt_roc) + __set_bit(mvm->phy_ctxt_roc.id, data.used); + + lockdep_assert_held(&mvm->mutex); + ctxt->color++; + + if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { + ieee80211_iter_chan_contexts_atomic( + mvm->hw, iwl_mvm_phy_ctx_used_iter, &data); + + ctxt->id = find_first_zero_bit(data.used, NUM_PHY_CTX); + if (WARN_ONCE(ctxt->id == NUM_PHY_CTX, + "Failed to init PHY context - no free ID!\n")) + return -EIO; + } + + ctxt->channel = chandef->chan; + return iwl_mvm_phy_ctxt_apply(mvm, ctxt, chandef, + chains_static, chains_dynamic, + FW_CTXT_ACTION_ADD, 0); +} + +/* + * Send a command to modify the PHY context based on the current HW + * configuration. Note that the function does not check that the configuration + * changed. + */ +int iwl_mvm_phy_ctxt_changed(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, + struct cfg80211_chan_def *chandef, + u8 chains_static, u8 chains_dynamic) +{ + lockdep_assert_held(&mvm->mutex); + + ctxt->channel = chandef->chan; + return iwl_mvm_phy_ctxt_apply(mvm, ctxt, chandef, + chains_static, chains_dynamic, + FW_CTXT_ACTION_MODIFY, 0); +} + +/* + * Send a command to the FW to remove the given phy context. + * Once the command is sent, regardless of success or failure, the context is + * marked as invalid + */ +void iwl_mvm_phy_ctxt_remove(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt) +{ + struct iwl_phy_context_cmd cmd; + int ret; + + lockdep_assert_held(&mvm->mutex); + + iwl_mvm_phy_ctxt_cmd_hdr(ctxt, &cmd, FW_CTXT_ACTION_REMOVE, 0); + ret = iwl_mvm_send_cmd_pdu(mvm, PHY_CONTEXT_CMD, CMD_SYNC, + sizeof(struct iwl_phy_context_cmd), + &cmd); + if (ret) + IWL_ERR(mvm, "Failed to send PHY remove: ctxt id=%d\n", + ctxt->id); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/power.c b/drivers/net/wireless/iwlwifi/mvm/power.c new file mode 100644 index 0000000..ed77e43 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/power.c @@ -0,0 +1,180 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#include +#include +#include +#include + +#include + +#include "iwl-debug.h" +#include "mvm.h" +#include "iwl-modparams.h" +#include "fw-api-power.h" + +#define POWER_KEEP_ALIVE_PERIOD_SEC 25 + +static void iwl_mvm_power_log(struct iwl_mvm *mvm, + struct iwl_powertable_cmd *cmd) +{ + IWL_DEBUG_POWER(mvm, + "Sending power table command for power level %d, flags = 0x%X\n", + iwlmvm_mod_params.power_scheme, + le16_to_cpu(cmd->flags)); + IWL_DEBUG_POWER(mvm, "Keep alive = %u sec\n", cmd->keep_alive_seconds); + + if (cmd->flags & cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK)) { + IWL_DEBUG_POWER(mvm, "Rx timeout = %u usec\n", + le32_to_cpu(cmd->rx_data_timeout)); + IWL_DEBUG_POWER(mvm, "Tx timeout = %u usec\n", + le32_to_cpu(cmd->tx_data_timeout)); + IWL_DEBUG_POWER(mvm, "LP RX RSSI threshold = %u\n", + cmd->lprx_rssi_threshold); + } +} + +void iwl_mvm_power_build_cmd(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_powertable_cmd *cmd) +{ + struct ieee80211_hw *hw = mvm->hw; + struct ieee80211_chanctx_conf *chanctx_conf; + struct ieee80211_channel *chan; + int dtimper, dtimper_msec; + int keep_alive; + bool radar_detect = false; + + /* + * Regardless of power management state the driver must set + * keep alive period. FW will use it for sending keep alive NDPs + * immediately after association. + */ + cmd->keep_alive_seconds = POWER_KEEP_ALIVE_PERIOD_SEC; + + if (iwlmvm_mod_params.power_scheme == IWL_POWER_SCHEME_CAM) + return; + + cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_SAVE_ENA_MSK); + + if (!vif->bss_conf.ps) + return; + + cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK); + + dtimper = hw->conf.ps_dtim_period ?: 1; + + /* Check if radar detection is required on current channel */ + rcu_read_lock(); + chanctx_conf = rcu_dereference(vif->chanctx_conf); + WARN_ON(!chanctx_conf); + if (chanctx_conf) { + chan = chanctx_conf->def.chan; + radar_detect = chan->flags & IEEE80211_CHAN_RADAR; + } + rcu_read_unlock(); + + /* Check skip over DTIM conditions */ + if (!radar_detect && (dtimper <= 10) && + (iwlmvm_mod_params.power_scheme == IWL_POWER_SCHEME_LP)) + cmd->flags |= cpu_to_le16(POWER_FLAGS_SKIP_OVER_DTIM_MSK); + + /* Check that keep alive period is at least 3 * DTIM */ + dtimper_msec = dtimper * vif->bss_conf.beacon_int; + keep_alive = max_t(int, 3 * dtimper_msec, + MSEC_PER_SEC * cmd->keep_alive_seconds); + keep_alive = DIV_ROUND_UP(keep_alive, MSEC_PER_SEC); + cmd->keep_alive_seconds = keep_alive; + + cmd->rx_data_timeout = cpu_to_le32(100 * USEC_PER_MSEC); + cmd->tx_data_timeout = cpu_to_le32(100 * USEC_PER_MSEC); +} + +int iwl_mvm_power_update_mode(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_powertable_cmd cmd = {}; + + if (vif->type != NL80211_IFTYPE_STATION || vif->p2p) + return 0; + + iwl_mvm_power_build_cmd(mvm, vif, &cmd); + iwl_mvm_power_log(mvm, &cmd); + + return iwl_mvm_send_cmd_pdu(mvm, POWER_TABLE_CMD, CMD_SYNC, + sizeof(cmd), &cmd); +} + +int iwl_mvm_power_disable(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ + struct iwl_powertable_cmd cmd = {}; + + if (vif->type != NL80211_IFTYPE_STATION || vif->p2p) + return 0; + + if (iwlmvm_mod_params.power_scheme != IWL_POWER_SCHEME_CAM) + cmd.flags |= cpu_to_le16(POWER_FLAGS_POWER_SAVE_ENA_MSK); + + iwl_mvm_power_log(mvm, &cmd); + + return iwl_mvm_send_cmd_pdu(mvm, POWER_TABLE_CMD, CMD_ASYNC, + sizeof(cmd), &cmd); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/quota.c b/drivers/net/wireless/iwlwifi/mvm/quota.c new file mode 100644 index 0000000..a1e3e92 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/quota.c @@ -0,0 +1,198 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#include +#include "fw-api.h" +#include "mvm.h" + +struct iwl_mvm_quota_iterator_data { + int n_interfaces[MAX_BINDINGS]; + int colors[MAX_BINDINGS]; + struct ieee80211_vif *new_vif; +}; + +static void iwl_mvm_quota_iterator(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_quota_iterator_data *data = _data; + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + u16 id; + + /* + * We'll account for the new interface (if any) below, + * skip it here in case we're not called from within + * the add_interface callback (otherwise it won't show + * up in iteration) + */ + if (vif == data->new_vif) + return; + + if (!mvmvif->phy_ctxt) + return; + + /* currently, PHY ID == binding ID */ + id = mvmvif->phy_ctxt->id; + + /* need at least one binding per PHY */ + BUILD_BUG_ON(NUM_PHY_CTX > MAX_BINDINGS); + + if (WARN_ON_ONCE(id >= MAX_BINDINGS)) + return; + + if (data->colors[id] < 0) + data->colors[id] = mvmvif->phy_ctxt->color; + else + WARN_ON_ONCE(data->colors[id] != mvmvif->phy_ctxt->color); + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + if (vif->bss_conf.assoc) + data->n_interfaces[id]++; + break; + case NL80211_IFTYPE_AP: + if (mvmvif->ap_active) + data->n_interfaces[id]++; + break; + case NL80211_IFTYPE_MONITOR: + if (mvmvif->monitor_active) + data->n_interfaces[id]++; + break; + case NL80211_IFTYPE_P2P_DEVICE: + break; + case NL80211_IFTYPE_ADHOC: + if (vif->bss_conf.ibss_joined) + data->n_interfaces[id]++; + break; + default: + WARN_ON_ONCE(1); + break; + } +} + +int iwl_mvm_update_quotas(struct iwl_mvm *mvm, struct ieee80211_vif *newvif) +{ + struct iwl_time_quota_cmd cmd; + int i, idx, ret, num_active_bindings, quota, quota_rem; + struct iwl_mvm_quota_iterator_data data = { + .n_interfaces = {}, + .colors = { -1, -1, -1, -1 }, + .new_vif = newvif, + }; + + /* update all upon completion */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) + return 0; + + BUILD_BUG_ON(data.colors[MAX_BINDINGS - 1] != -1); + + lockdep_assert_held(&mvm->mutex); + + memset(&cmd, 0, sizeof(cmd)); + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_quota_iterator, &data); + if (newvif) { + data.new_vif = NULL; + iwl_mvm_quota_iterator(&data, newvif->addr, newvif); + } + + /* + * The FW's scheduling session consists of + * IWL_MVM_MAX_QUOTA fragments. Divide these fragments + * equally between all the bindings that require quota + */ + num_active_bindings = 0; + for (i = 0; i < MAX_BINDINGS; i++) { + cmd.quotas[i].id_and_color = cpu_to_le32(FW_CTXT_INVALID); + if (data.n_interfaces[i] > 0) + num_active_bindings++; + } + + if (!num_active_bindings) + goto send_cmd; + + quota = IWL_MVM_MAX_QUOTA / num_active_bindings; + quota_rem = IWL_MVM_MAX_QUOTA % num_active_bindings; + + for (idx = 0, i = 0; i < MAX_BINDINGS; i++) { + if (data.n_interfaces[i] <= 0) + continue; + + cmd.quotas[idx].id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(i, data.colors[i])); + cmd.quotas[idx].quota = cpu_to_le32(quota); + cmd.quotas[idx].max_duration = cpu_to_le32(IWL_MVM_MAX_QUOTA); + idx++; + } + + /* Give the remainder of the session to the first binding */ + le32_add_cpu(&cmd.quotas[0].quota, quota_rem); + +send_cmd: + ret = iwl_mvm_send_cmd_pdu(mvm, TIME_QUOTA_CMD, CMD_SYNC, + sizeof(cmd), &cmd); + if (ret) + IWL_ERR(mvm, "Failed to send quota: %d\n", ret); + return ret; +} diff --git a/drivers/net/wireless/iwlwifi/mvm/rs.c b/drivers/net/wireless/iwlwifi/mvm/rs.c new file mode 100644 index 0000000..2b09a37 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/rs.c @@ -0,0 +1,3083 @@ +/****************************************************************************** + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include "rs.h" +#include "fw-api.h" +#include "sta.h" +#include "iwl-op-mode.h" +#include "mvm.h" + +#define RS_NAME "iwl-mvm-rs" + +#define NUM_TRY_BEFORE_ANT_TOGGLE 1 +#define IWL_NUMBER_TRY 1 +#define IWL_HT_NUMBER_TRY 3 + +#define IWL_RATE_MAX_WINDOW 62 /* # tx in history window */ +#define IWL_RATE_MIN_FAILURE_TH 6 /* min failures to calc tpt */ +#define IWL_RATE_MIN_SUCCESS_TH 8 /* min successes to calc tpt */ + +/* max allowed rate miss before sync LQ cmd */ +#define IWL_MISSED_RATE_MAX 15 +/* max time to accum history 2 seconds */ +#define IWL_RATE_SCALE_FLUSH_INTVL (3*HZ) + +static u8 rs_ht_to_legacy[] = { + IWL_RATE_6M_INDEX, IWL_RATE_6M_INDEX, + IWL_RATE_6M_INDEX, IWL_RATE_6M_INDEX, + IWL_RATE_6M_INDEX, + IWL_RATE_6M_INDEX, IWL_RATE_9M_INDEX, + IWL_RATE_12M_INDEX, IWL_RATE_18M_INDEX, + IWL_RATE_24M_INDEX, IWL_RATE_36M_INDEX, + IWL_RATE_48M_INDEX, IWL_RATE_54M_INDEX +}; + +static const u8 ant_toggle_lookup[] = { + /*ANT_NONE -> */ ANT_NONE, + /*ANT_A -> */ ANT_B, + /*ANT_B -> */ ANT_C, + /*ANT_AB -> */ ANT_BC, + /*ANT_C -> */ ANT_A, + /*ANT_AC -> */ ANT_AB, + /*ANT_BC -> */ ANT_AC, + /*ANT_ABC -> */ ANT_ABC, +}; + +#define IWL_DECLARE_RATE_INFO(r, s, ip, in, rp, rn, pp, np) \ + [IWL_RATE_##r##M_INDEX] = { IWL_RATE_##r##M_PLCP, \ + IWL_RATE_SISO_##s##M_PLCP, \ + IWL_RATE_MIMO2_##s##M_PLCP,\ + IWL_RATE_MIMO3_##s##M_PLCP,\ + IWL_RATE_##r##M_IEEE, \ + IWL_RATE_##ip##M_INDEX, \ + IWL_RATE_##in##M_INDEX, \ + IWL_RATE_##rp##M_INDEX, \ + IWL_RATE_##rn##M_INDEX, \ + IWL_RATE_##pp##M_INDEX, \ + IWL_RATE_##np##M_INDEX } + +/* + * Parameter order: + * rate, ht rate, prev rate, next rate, prev tgg rate, next tgg rate + * + * If there isn't a valid next or previous rate then INV is used which + * maps to IWL_RATE_INVALID + * + */ +static const struct iwl_rs_rate_info iwl_rates[IWL_RATE_COUNT] = { + IWL_DECLARE_RATE_INFO(1, INV, INV, 2, INV, 2, INV, 2), /* 1mbps */ + IWL_DECLARE_RATE_INFO(2, INV, 1, 5, 1, 5, 1, 5), /* 2mbps */ + IWL_DECLARE_RATE_INFO(5, INV, 2, 6, 2, 11, 2, 11), /*5.5mbps */ + IWL_DECLARE_RATE_INFO(11, INV, 9, 12, 9, 12, 5, 18), /* 11mbps */ + IWL_DECLARE_RATE_INFO(6, 6, 5, 9, 5, 11, 5, 11), /* 6mbps */ + IWL_DECLARE_RATE_INFO(9, 6, 6, 11, 6, 11, 5, 11), /* 9mbps */ + IWL_DECLARE_RATE_INFO(12, 12, 11, 18, 11, 18, 11, 18), /* 12mbps */ + IWL_DECLARE_RATE_INFO(18, 18, 12, 24, 12, 24, 11, 24), /* 18mbps */ + IWL_DECLARE_RATE_INFO(24, 24, 18, 36, 18, 36, 18, 36), /* 24mbps */ + IWL_DECLARE_RATE_INFO(36, 36, 24, 48, 24, 48, 24, 48), /* 36mbps */ + IWL_DECLARE_RATE_INFO(48, 48, 36, 54, 36, 54, 36, 54), /* 48mbps */ + IWL_DECLARE_RATE_INFO(54, 54, 48, INV, 48, INV, 48, INV),/* 54mbps */ + IWL_DECLARE_RATE_INFO(60, 60, 48, INV, 48, INV, 48, INV),/* 60mbps */ + /* FIXME:RS: ^^ should be INV (legacy) */ +}; + +static inline u8 rs_extract_rate(u32 rate_n_flags) +{ + /* also works for HT because bits 7:6 are zero there */ + return (u8)(rate_n_flags & RATE_LEGACY_RATE_MSK); +} + +static int iwl_hwrate_to_plcp_idx(u32 rate_n_flags) +{ + int idx = 0; + + /* HT rate format */ + if (rate_n_flags & RATE_MCS_HT_MSK) { + idx = rs_extract_rate(rate_n_flags); + + if (idx >= IWL_RATE_MIMO3_6M_PLCP) + idx = idx - IWL_RATE_MIMO3_6M_PLCP; + else if (idx >= IWL_RATE_MIMO2_6M_PLCP) + idx = idx - IWL_RATE_MIMO2_6M_PLCP; + + idx += IWL_FIRST_OFDM_RATE; + /* skip 9M not supported in ht*/ + if (idx >= IWL_RATE_9M_INDEX) + idx += 1; + if ((idx >= IWL_FIRST_OFDM_RATE) && (idx <= IWL_LAST_OFDM_RATE)) + return idx; + + /* legacy rate format, search for match in table */ + } else { + for (idx = 0; idx < ARRAY_SIZE(iwl_rates); idx++) + if (iwl_rates[idx].plcp == + rs_extract_rate(rate_n_flags)) + return idx; + } + + return -1; +} + +static void rs_rate_scale_perform(struct iwl_mvm *mvm, + struct sk_buff *skb, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta); +static void rs_fill_link_cmd(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta, u32 rate_n_flags); +static void rs_stay_in_table(struct iwl_lq_sta *lq_sta, bool force_search); + + +#ifdef CPTCFG_MAC80211_DEBUGFS +static void rs_dbgfs_set_mcs(struct iwl_lq_sta *lq_sta, + u32 *rate_n_flags, int index); +#else +static void rs_dbgfs_set_mcs(struct iwl_lq_sta *lq_sta, + u32 *rate_n_flags, int index) +{} +#endif + +/** + * The following tables contain the expected throughput metrics for all rates + * + * 1, 2, 5.5, 11, 6, 9, 12, 18, 24, 36, 48, 54, 60 MBits + * + * where invalid entries are zeros. + * + * CCK rates are only valid in legacy table and will only be used in G + * (2.4 GHz) band. + */ + +static s32 expected_tpt_legacy[IWL_RATE_COUNT] = { + 7, 13, 35, 58, 40, 57, 72, 98, 121, 154, 177, 186, 0 +}; + +static s32 expected_tpt_siso20MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 42, 0, 76, 102, 124, 159, 183, 193, 202}, /* Norm */ + {0, 0, 0, 0, 46, 0, 82, 110, 132, 168, 192, 202, 210}, /* SGI */ + {0, 0, 0, 0, 47, 0, 91, 133, 171, 242, 305, 334, 362}, /* AGG */ + {0, 0, 0, 0, 52, 0, 101, 145, 187, 264, 330, 361, 390}, /* AGG+SGI */ +}; + +static s32 expected_tpt_siso40MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 77, 0, 127, 160, 184, 220, 242, 250, 257}, /* Norm */ + {0, 0, 0, 0, 83, 0, 135, 169, 193, 229, 250, 257, 264}, /* SGI */ + {0, 0, 0, 0, 94, 0, 177, 249, 313, 423, 512, 550, 586}, /* AGG */ + {0, 0, 0, 0, 104, 0, 193, 270, 338, 454, 545, 584, 620}, /* AGG+SGI */ +}; + +static s32 expected_tpt_mimo2_20MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 74, 0, 123, 155, 179, 214, 236, 244, 251}, /* Norm */ + {0, 0, 0, 0, 81, 0, 131, 164, 188, 223, 243, 251, 257}, /* SGI */ + {0, 0, 0, 0, 89, 0, 167, 235, 296, 402, 488, 526, 560}, /* AGG */ + {0, 0, 0, 0, 97, 0, 182, 255, 320, 431, 520, 558, 593}, /* AGG+SGI*/ +}; + +static s32 expected_tpt_mimo2_40MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 123, 0, 182, 214, 235, 264, 279, 285, 289}, /* Norm */ + {0, 0, 0, 0, 131, 0, 191, 222, 242, 270, 284, 289, 293}, /* SGI */ + {0, 0, 0, 0, 171, 0, 305, 410, 496, 634, 731, 771, 805}, /* AGG */ + {0, 0, 0, 0, 186, 0, 329, 439, 527, 667, 764, 803, 838}, /* AGG+SGI */ +}; + +static s32 expected_tpt_mimo3_20MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 99, 0, 153, 186, 208, 239, 256, 263, 268}, /* Norm */ + {0, 0, 0, 0, 106, 0, 162, 194, 215, 246, 262, 268, 273}, /* SGI */ + {0, 0, 0, 0, 134, 0, 249, 346, 431, 574, 685, 732, 775}, /* AGG */ + {0, 0, 0, 0, 148, 0, 272, 376, 465, 614, 727, 775, 818}, /* AGG+SGI */ +}; + +static s32 expected_tpt_mimo3_40MHz[4][IWL_RATE_COUNT] = { + {0, 0, 0, 0, 152, 0, 211, 239, 255, 279, 290, 294, 297}, /* Norm */ + {0, 0, 0, 0, 160, 0, 219, 245, 261, 284, 294, 297, 300}, /* SGI */ + {0, 0, 0, 0, 254, 0, 443, 584, 695, 868, 984, 1030, 1070}, /* AGG */ + {0, 0, 0, 0, 277, 0, 478, 624, 737, 911, 1026, 1070, 1109}, /* AGG+SGI */ +}; + +/* mbps, mcs */ +static const struct iwl_rate_mcs_info iwl_rate_mcs[IWL_RATE_COUNT] = { + { "1", "BPSK DSSS"}, + { "2", "QPSK DSSS"}, + {"5.5", "BPSK CCK"}, + { "11", "QPSK CCK"}, + { "6", "BPSK 1/2"}, + { "9", "BPSK 1/2"}, + { "12", "QPSK 1/2"}, + { "18", "QPSK 3/4"}, + { "24", "16QAM 1/2"}, + { "36", "16QAM 3/4"}, + { "48", "64QAM 2/3"}, + { "54", "64QAM 3/4"}, + { "60", "64QAM 5/6"}, +}; + +#define MCS_INDEX_PER_STREAM (8) + +static void rs_rate_scale_clear_window(struct iwl_rate_scale_data *window) +{ + window->data = 0; + window->success_counter = 0; + window->success_ratio = IWL_INVALID_VALUE; + window->counter = 0; + window->average_tpt = IWL_INVALID_VALUE; + window->stamp = 0; +} + +static inline u8 rs_is_valid_ant(u8 valid_antenna, u8 ant_type) +{ + return (ant_type & valid_antenna) == ant_type; +} + +/* + * removes the old data from the statistics. All data that is older than + * TID_MAX_TIME_DIFF, will be deleted. + */ +static void rs_tl_rm_old_stats(struct iwl_traffic_load *tl, u32 curr_time) +{ + /* The oldest age we want to keep */ + u32 oldest_time = curr_time - TID_MAX_TIME_DIFF; + + while (tl->queue_count && + (tl->time_stamp < oldest_time)) { + tl->total -= tl->packet_count[tl->head]; + tl->packet_count[tl->head] = 0; + tl->time_stamp += TID_QUEUE_CELL_SPACING; + tl->queue_count--; + tl->head++; + if (tl->head >= TID_QUEUE_MAX_SIZE) + tl->head = 0; + } +} + +/* + * increment traffic load value for tid and also remove + * any old values if passed the certain time period + */ +static u8 rs_tl_add_packet(struct iwl_lq_sta *lq_data, + struct ieee80211_hdr *hdr) +{ + u32 curr_time = jiffies_to_msecs(jiffies); + u32 time_diff; + s32 index; + struct iwl_traffic_load *tl = NULL; + u8 tid; + + if (ieee80211_is_data_qos(hdr->frame_control)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + tid = qc[0] & 0xf; + } else { + return IWL_MAX_TID_COUNT; + } + + if (unlikely(tid >= IWL_MAX_TID_COUNT)) + return IWL_MAX_TID_COUNT; + + tl = &lq_data->load[tid]; + + curr_time -= curr_time % TID_ROUND_VALUE; + + /* Happens only for the first packet. Initialize the data */ + if (!(tl->queue_count)) { + tl->total = 1; + tl->time_stamp = curr_time; + tl->queue_count = 1; + tl->head = 0; + tl->packet_count[0] = 1; + return IWL_MAX_TID_COUNT; + } + + time_diff = TIME_WRAP_AROUND(tl->time_stamp, curr_time); + index = time_diff / TID_QUEUE_CELL_SPACING; + + /* The history is too long: remove data that is older than */ + /* TID_MAX_TIME_DIFF */ + if (index >= TID_QUEUE_MAX_SIZE) + rs_tl_rm_old_stats(tl, curr_time); + + index = (tl->head + index) % TID_QUEUE_MAX_SIZE; + tl->packet_count[index] = tl->packet_count[index] + 1; + tl->total = tl->total + 1; + + if ((index + 1) > tl->queue_count) + tl->queue_count = index + 1; + + return tid; +} + +#ifdef CPTCFG_MAC80211_DEBUGFS +/** + * Program the device to use fixed rate for frame transmit + * This is for debugging/testing only + * once the device start use fixed rate, we need to reload the module + * to being back the normal operation. + */ +static void rs_program_fix_rate(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta) +{ + lq_sta->active_legacy_rate = 0x0FFF; /* 1 - 54 MBits, includes CCK */ + lq_sta->active_siso_rate = 0x1FD0; /* 6 - 60 MBits, no 9, no CCK */ + lq_sta->active_mimo2_rate = 0x1FD0; /* 6 - 60 MBits, no 9, no CCK */ + lq_sta->active_mimo3_rate = 0x1FD0; /* 6 - 60 MBits, no 9, no CCK */ + + IWL_DEBUG_RATE(mvm, "sta_id %d rate 0x%X\n", + lq_sta->lq.sta_id, lq_sta->dbg_fixed_rate); + + if (lq_sta->dbg_fixed_rate) { + rs_fill_link_cmd(NULL, lq_sta, lq_sta->dbg_fixed_rate); + iwl_mvm_send_lq_cmd(lq_sta->drv, &lq_sta->lq, CMD_ASYNC, false); + } +} +#endif + +/* + get the traffic load value for tid +*/ +static u32 rs_tl_get_load(struct iwl_lq_sta *lq_data, u8 tid) +{ + u32 curr_time = jiffies_to_msecs(jiffies); + u32 time_diff; + s32 index; + struct iwl_traffic_load *tl = NULL; + + if (tid >= IWL_MAX_TID_COUNT) + return 0; + + tl = &(lq_data->load[tid]); + + curr_time -= curr_time % TID_ROUND_VALUE; + + if (!(tl->queue_count)) + return 0; + + time_diff = TIME_WRAP_AROUND(tl->time_stamp, curr_time); + index = time_diff / TID_QUEUE_CELL_SPACING; + + /* The history is too long: remove data that is older than */ + /* TID_MAX_TIME_DIFF */ + if (index >= TID_QUEUE_MAX_SIZE) + rs_tl_rm_old_stats(tl, curr_time); + + return tl->total; +} + +static int rs_tl_turn_on_agg_for_tid(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_data, u8 tid, + struct ieee80211_sta *sta) +{ + int ret = -EAGAIN; + u32 load; + + load = rs_tl_get_load(lq_data, tid); + + if ((iwlwifi_mod_params.auto_agg) || (load > IWL_AGG_LOAD_THRESHOLD)) { + IWL_DEBUG_HT(mvm, "Starting Tx agg: STA: %pM tid: %d\n", + sta->addr, tid); + ret = ieee80211_start_tx_ba_session(sta, tid, 5000); + if (ret == -EAGAIN) { + /* + * driver and mac80211 is out of sync + * this might be cause by reloading firmware + * stop the tx ba session here + */ + IWL_ERR(mvm, "Fail start Tx agg on tid: %d\n", + tid); + ieee80211_stop_tx_ba_session(sta, tid); + } + } else { + IWL_DEBUG_HT(mvm, + "Aggregation not enabled for tid %d because load = %u\n", + tid, load); + } + return ret; +} + +static void rs_tl_turn_on_agg(struct iwl_mvm *mvm, u8 tid, + struct iwl_lq_sta *lq_data, + struct ieee80211_sta *sta) +{ + if (tid < IWL_MAX_TID_COUNT) + rs_tl_turn_on_agg_for_tid(mvm, lq_data, tid, sta); + else + IWL_ERR(mvm, "tid exceeds max TID count: %d/%d\n", + tid, IWL_MAX_TID_COUNT); +} + +static inline int get_num_of_ant_from_rate(u32 rate_n_flags) +{ + return !!(rate_n_flags & RATE_MCS_ANT_A_MSK) + + !!(rate_n_flags & RATE_MCS_ANT_B_MSK) + + !!(rate_n_flags & RATE_MCS_ANT_C_MSK); +} + +/* + * Static function to get the expected throughput from an iwl_scale_tbl_info + * that wraps a NULL pointer check + */ +static s32 get_expected_tpt(struct iwl_scale_tbl_info *tbl, int rs_index) +{ + if (tbl->expected_tpt) + return tbl->expected_tpt[rs_index]; + return 0; +} + +/** + * rs_collect_tx_data - Update the success/failure sliding window + * + * We keep a sliding window of the last 62 packets transmitted + * at this rate. window->data contains the bitmask of successful + * packets. + */ +static int rs_collect_tx_data(struct iwl_scale_tbl_info *tbl, + int scale_index, int attempts, int successes) +{ + struct iwl_rate_scale_data *window = NULL; + static const u64 mask = (((u64)1) << (IWL_RATE_MAX_WINDOW - 1)); + s32 fail_count, tpt; + + if (scale_index < 0 || scale_index >= IWL_RATE_COUNT) + return -EINVAL; + + /* Select window for current tx bit rate */ + window = &(tbl->win[scale_index]); + + /* Get expected throughput */ + tpt = get_expected_tpt(tbl, scale_index); + + /* + * Keep track of only the latest 62 tx frame attempts in this rate's + * history window; anything older isn't really relevant any more. + * If we have filled up the sliding window, drop the oldest attempt; + * if the oldest attempt (highest bit in bitmap) shows "success", + * subtract "1" from the success counter (this is the main reason + * we keep these bitmaps!). + */ + while (attempts > 0) { + if (window->counter >= IWL_RATE_MAX_WINDOW) { + /* remove earliest */ + window->counter = IWL_RATE_MAX_WINDOW - 1; + + if (window->data & mask) { + window->data &= ~mask; + window->success_counter--; + } + } + + /* Increment frames-attempted counter */ + window->counter++; + + /* Shift bitmap by one frame to throw away oldest history */ + window->data <<= 1; + + /* Mark the most recent #successes attempts as successful */ + if (successes > 0) { + window->success_counter++; + window->data |= 0x1; + successes--; + } + + attempts--; + } + + /* Calculate current success ratio, avoid divide-by-0! */ + if (window->counter > 0) + window->success_ratio = 128 * (100 * window->success_counter) + / window->counter; + else + window->success_ratio = IWL_INVALID_VALUE; + + fail_count = window->counter - window->success_counter; + + /* Calculate average throughput, if we have enough history. */ + if ((fail_count >= IWL_RATE_MIN_FAILURE_TH) || + (window->success_counter >= IWL_RATE_MIN_SUCCESS_TH)) + window->average_tpt = (window->success_ratio * tpt + 64) / 128; + else + window->average_tpt = IWL_INVALID_VALUE; + + /* Tag this window as having been updated */ + window->stamp = jiffies; + + return 0; +} + +/* + * Fill uCode API rate_n_flags field, based on "search" or "active" table. + */ +/* FIXME:RS:remove this function and put the flags statically in the table */ +static u32 rate_n_flags_from_tbl(struct iwl_mvm *mvm, + struct iwl_scale_tbl_info *tbl, + int index, u8 use_green) +{ + u32 rate_n_flags = 0; + + if (is_legacy(tbl->lq_type)) { + rate_n_flags = iwl_rates[index].plcp; + if (index >= IWL_FIRST_CCK_RATE && index <= IWL_LAST_CCK_RATE) + rate_n_flags |= RATE_MCS_CCK_MSK; + } else if (is_Ht(tbl->lq_type)) { + if (index > IWL_LAST_OFDM_RATE) { + IWL_ERR(mvm, "Invalid HT rate index %d\n", index); + index = IWL_LAST_OFDM_RATE; + } + rate_n_flags = RATE_MCS_HT_MSK; + + if (is_siso(tbl->lq_type)) + rate_n_flags |= iwl_rates[index].plcp_siso; + else if (is_mimo2(tbl->lq_type)) + rate_n_flags |= iwl_rates[index].plcp_mimo2; + else + rate_n_flags |= iwl_rates[index].plcp_mimo3; + } else { + IWL_ERR(mvm, "Invalid tbl->lq_type %d\n", tbl->lq_type); + } + + rate_n_flags |= ((tbl->ant_type << RATE_MCS_ANT_POS) & + RATE_MCS_ANT_ABC_MSK); + + if (is_Ht(tbl->lq_type)) { + if (tbl->is_ht40) + rate_n_flags |= RATE_MCS_CHAN_WIDTH_40; + if (tbl->is_SGI) + rate_n_flags |= RATE_MCS_SGI_MSK; + + if (use_green) { + rate_n_flags |= RATE_HT_MCS_GF_MSK; + if (is_siso(tbl->lq_type) && tbl->is_SGI) { + rate_n_flags &= ~RATE_MCS_SGI_MSK; + IWL_ERR(mvm, "GF was set with SGI:SISO\n"); + } + } + } + return rate_n_flags; +} + +/* + * Interpret uCode API's rate_n_flags format, + * fill "search" or "active" tx mode table. + */ +static int rs_get_tbl_info_from_mcs(const u32 rate_n_flags, + enum ieee80211_band band, + struct iwl_scale_tbl_info *tbl, + int *rate_idx) +{ + u32 ant_msk = (rate_n_flags & RATE_MCS_ANT_ABC_MSK); + u8 num_of_ant = get_num_of_ant_from_rate(rate_n_flags); + u8 mcs; + + memset(tbl, 0, sizeof(struct iwl_scale_tbl_info)); + *rate_idx = iwl_hwrate_to_plcp_idx(rate_n_flags); + + if (*rate_idx == IWL_RATE_INVALID) { + *rate_idx = -1; + return -EINVAL; + } + tbl->is_SGI = 0; /* default legacy setup */ + tbl->is_ht40 = 0; + tbl->ant_type = (ant_msk >> RATE_MCS_ANT_POS); + tbl->lq_type = LQ_NONE; + tbl->max_search = IWL_MAX_SEARCH; + + /* legacy rate format */ + if (!(rate_n_flags & RATE_MCS_HT_MSK)) { + if (num_of_ant == 1) { + if (band == IEEE80211_BAND_5GHZ) + tbl->lq_type = LQ_A; + else + tbl->lq_type = LQ_G; + } + /* HT rate format */ + } else { + if (rate_n_flags & RATE_MCS_SGI_MSK) + tbl->is_SGI = 1; + + if (rate_n_flags & RATE_MCS_CHAN_WIDTH_40) /* TODO */ + tbl->is_ht40 = 1; + + mcs = rs_extract_rate(rate_n_flags); + + /* SISO */ + if (mcs <= IWL_RATE_SISO_60M_PLCP) { + if (num_of_ant == 1) + tbl->lq_type = LQ_SISO; /*else NONE*/ + /* MIMO2 */ + } else if (mcs <= IWL_RATE_MIMO2_60M_PLCP) { + if (num_of_ant == 2) + tbl->lq_type = LQ_MIMO2; + /* MIMO3 */ + } else { + if (num_of_ant == 3) { + tbl->max_search = IWL_MAX_11N_MIMO3_SEARCH; + tbl->lq_type = LQ_MIMO3; + } + } + } + return 0; +} + +/* switch to another antenna/antennas and return 1 */ +/* if no other valid antenna found, return 0 */ +static int rs_toggle_antenna(u32 valid_ant, u32 *rate_n_flags, + struct iwl_scale_tbl_info *tbl) +{ + u8 new_ant_type; + + if (!tbl->ant_type || tbl->ant_type > ANT_ABC) + return 0; + + if (!rs_is_valid_ant(valid_ant, tbl->ant_type)) + return 0; + + new_ant_type = ant_toggle_lookup[tbl->ant_type]; + + while ((new_ant_type != tbl->ant_type) && + !rs_is_valid_ant(valid_ant, new_ant_type)) + new_ant_type = ant_toggle_lookup[new_ant_type]; + + if (new_ant_type == tbl->ant_type) + return 0; + + tbl->ant_type = new_ant_type; + *rate_n_flags &= ~RATE_MCS_ANT_ABC_MSK; + *rate_n_flags |= new_ant_type << RATE_MCS_ANT_POS; + return 1; +} + +/** + * Green-field mode is valid if the station supports it and + * there are no non-GF stations present in the BSS. + */ +static bool rs_use_green(struct ieee80211_sta *sta) +{ + /* + * There's a bug somewhere in this code that causes the + * scaling to get stuck because GF+SGI can't be combined + * in SISO rates. Until we find that bug, disable GF, it + * has only limited benefit and we still interoperate with + * GF APs since we can always receive GF transmissions. + */ + return false; +} + +/** + * rs_get_supported_rates - get the available rates + * + * if management frame or broadcast frame only return + * basic available rates. + * + */ +static u16 rs_get_supported_rates(struct iwl_lq_sta *lq_sta, + struct ieee80211_hdr *hdr, + enum iwl_table_type rate_type) +{ + if (is_legacy(rate_type)) { + return lq_sta->active_legacy_rate; + } else { + if (is_siso(rate_type)) + return lq_sta->active_siso_rate; + else if (is_mimo2(rate_type)) + return lq_sta->active_mimo2_rate; + else + return lq_sta->active_mimo3_rate; + } +} + +static u16 rs_get_adjacent_rate(struct iwl_mvm *mvm, u8 index, u16 rate_mask, + int rate_type) +{ + u8 high = IWL_RATE_INVALID; + u8 low = IWL_RATE_INVALID; + + /* 802.11A or ht walks to the next literal adjacent rate in + * the rate table */ + if (is_a_band(rate_type) || !is_legacy(rate_type)) { + int i; + u32 mask; + + /* Find the previous rate that is in the rate mask */ + i = index - 1; + for (mask = (1 << i); i >= 0; i--, mask >>= 1) { + if (rate_mask & mask) { + low = i; + break; + } + } + + /* Find the next rate that is in the rate mask */ + i = index + 1; + for (mask = (1 << i); i < IWL_RATE_COUNT; i++, mask <<= 1) { + if (rate_mask & mask) { + high = i; + break; + } + } + + return (high << 8) | low; + } + + low = index; + while (low != IWL_RATE_INVALID) { + low = iwl_rates[low].prev_rs; + if (low == IWL_RATE_INVALID) + break; + if (rate_mask & (1 << low)) + break; + IWL_DEBUG_RATE(mvm, "Skipping masked lower rate: %d\n", low); + } + + high = index; + while (high != IWL_RATE_INVALID) { + high = iwl_rates[high].next_rs; + if (high == IWL_RATE_INVALID) + break; + if (rate_mask & (1 << high)) + break; + IWL_DEBUG_RATE(mvm, "Skipping masked higher rate: %d\n", high); + } + + return (high << 8) | low; +} + +static u32 rs_get_lower_rate(struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl, + u8 scale_index, u8 ht_possible) +{ + s32 low; + u16 rate_mask; + u16 high_low; + u8 switch_to_legacy = 0; + u8 is_green = lq_sta->is_green; + struct iwl_mvm *mvm = lq_sta->drv; + + /* check if we need to switch from HT to legacy rates. + * assumption is that mandatory rates (1Mbps or 6Mbps) + * are always supported (spec demand) */ + if (!is_legacy(tbl->lq_type) && (!ht_possible || !scale_index)) { + switch_to_legacy = 1; + scale_index = rs_ht_to_legacy[scale_index]; + if (lq_sta->band == IEEE80211_BAND_5GHZ) + tbl->lq_type = LQ_A; + else + tbl->lq_type = LQ_G; + + if (num_of_ant(tbl->ant_type) > 1) + tbl->ant_type = + first_antenna(iwl_fw_valid_tx_ant(mvm->fw)); + + tbl->is_ht40 = 0; + tbl->is_SGI = 0; + tbl->max_search = IWL_MAX_SEARCH; + } + + rate_mask = rs_get_supported_rates(lq_sta, NULL, tbl->lq_type); + + /* Mask with station rate restriction */ + if (is_legacy(tbl->lq_type)) { + /* supp_rates has no CCK bits in A mode */ + if (lq_sta->band == IEEE80211_BAND_5GHZ) + rate_mask = (u16)(rate_mask & + (lq_sta->supp_rates << IWL_FIRST_OFDM_RATE)); + else + rate_mask = (u16)(rate_mask & lq_sta->supp_rates); + } + + /* If we switched from HT to legacy, check current rate */ + if (switch_to_legacy && (rate_mask & (1 << scale_index))) { + low = scale_index; + goto out; + } + + high_low = rs_get_adjacent_rate(lq_sta->drv, scale_index, rate_mask, + tbl->lq_type); + low = high_low & 0xff; + + if (low == IWL_RATE_INVALID) + low = scale_index; + +out: + return rate_n_flags_from_tbl(lq_sta->drv, tbl, low, is_green); +} + +/* + * Simple function to compare two rate scale table types + */ +static bool table_type_matches(struct iwl_scale_tbl_info *a, + struct iwl_scale_tbl_info *b) +{ + return (a->lq_type == b->lq_type) && (a->ant_type == b->ant_type) && + (a->is_SGI == b->is_SGI); +} + +/* + * mac80211 sends us Tx status + */ +static void rs_tx_status(void *mvm_r, struct ieee80211_supported_band *sband, + struct ieee80211_sta *sta, void *priv_sta, + struct sk_buff *skb) +{ + int legacy_success; + int retries; + int rs_index, mac_index, i; + struct iwl_lq_sta *lq_sta = priv_sta; + struct iwl_lq_cmd *table; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct iwl_op_mode *op_mode = (struct iwl_op_mode *)mvm_r; + struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + enum mac80211_rate_control_flags mac_flags; + u32 tx_rate; + struct iwl_scale_tbl_info tbl_type; + struct iwl_scale_tbl_info *curr_tbl, *other_tbl, *tmp_tbl; + + IWL_DEBUG_RATE_LIMIT(mvm, + "get frame ack response, update rate scale window\n"); + + /* Treat uninitialized rate scaling data same as non-existing. */ + if (!lq_sta) { + IWL_DEBUG_RATE(mvm, "Station rate scaling not created yet.\n"); + return; + } else if (!lq_sta->drv) { + IWL_DEBUG_RATE(mvm, "Rate scaling not initialized yet.\n"); + return; + } + + if (!ieee80211_is_data(hdr->frame_control) || + info->flags & IEEE80211_TX_CTL_NO_ACK) + return; + + /* This packet was aggregated but doesn't carry status info */ + if ((info->flags & IEEE80211_TX_CTL_AMPDU) && + !(info->flags & IEEE80211_TX_STAT_AMPDU)) + return; + + /* + * Ignore this Tx frame response if its initial rate doesn't match + * that of latest Link Quality command. There may be stragglers + * from a previous Link Quality command, but we're no longer interested + * in those; they're either from the "active" mode while we're trying + * to check "search" mode, or a prior "search" mode after we've moved + * to a new "search" mode (which might become the new "active" mode). + */ + table = &lq_sta->lq; + tx_rate = le32_to_cpu(table->rs_table[0]); + rs_get_tbl_info_from_mcs(tx_rate, info->band, &tbl_type, &rs_index); + if (info->band == IEEE80211_BAND_5GHZ) + rs_index -= IWL_FIRST_OFDM_RATE; + mac_flags = info->status.rates[0].flags; + mac_index = info->status.rates[0].idx; + /* For HT packets, map MCS to PLCP */ + if (mac_flags & IEEE80211_TX_RC_MCS) { + /* Remove # of streams */ + mac_index &= RATE_HT_MCS_RATE_CODE_MSK; + if (mac_index >= (IWL_RATE_9M_INDEX - IWL_FIRST_OFDM_RATE)) + mac_index++; + /* + * mac80211 HT index is always zero-indexed; we need to move + * HT OFDM rates after CCK rates in 2.4 GHz band + */ + if (info->band == IEEE80211_BAND_2GHZ) + mac_index += IWL_FIRST_OFDM_RATE; + } + /* Here we actually compare this rate to the latest LQ command */ + if ((mac_index < 0) || + (tbl_type.is_SGI != !!(mac_flags & IEEE80211_TX_RC_SHORT_GI)) || + (tbl_type.is_ht40 != !!(mac_flags & IEEE80211_TX_RC_40_MHZ_WIDTH)) || + (tbl_type.ant_type != info->status.antenna) || + (!!(tx_rate & RATE_MCS_HT_MSK) != + !!(mac_flags & IEEE80211_TX_RC_MCS)) || + (!!(tx_rate & RATE_HT_MCS_GF_MSK) != + !!(mac_flags & IEEE80211_TX_RC_GREEN_FIELD)) || + (rs_index != mac_index)) { + IWL_DEBUG_RATE(mvm, + "initial rate %d does not match %d (0x%x)\n", + mac_index, rs_index, tx_rate); + /* + * Since rates mis-match, the last LQ command may have failed. + * After IWL_MISSED_RATE_MAX mis-matches, resync the uCode with + * ... driver. + */ + lq_sta->missed_rate_counter++; + if (lq_sta->missed_rate_counter > IWL_MISSED_RATE_MAX) { + lq_sta->missed_rate_counter = 0; + iwl_mvm_send_lq_cmd(mvm, &lq_sta->lq, CMD_ASYNC, false); + } + /* Regardless, ignore this status info for outdated rate */ + return; + } else + /* Rate did match, so reset the missed_rate_counter */ + lq_sta->missed_rate_counter = 0; + + /* Figure out if rate scale algorithm is in active or search table */ + if (table_type_matches(&tbl_type, + &(lq_sta->lq_info[lq_sta->active_tbl]))) { + curr_tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + other_tbl = &(lq_sta->lq_info[1 - lq_sta->active_tbl]); + } else if (table_type_matches( + &tbl_type, &lq_sta->lq_info[1 - lq_sta->active_tbl])) { + curr_tbl = &(lq_sta->lq_info[1 - lq_sta->active_tbl]); + other_tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + } else { + IWL_DEBUG_RATE(mvm, + "Neither active nor search matches tx rate\n"); + tmp_tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + IWL_DEBUG_RATE(mvm, "active- lq:%x, ant:%x, SGI:%d\n", + tmp_tbl->lq_type, tmp_tbl->ant_type, + tmp_tbl->is_SGI); + tmp_tbl = &(lq_sta->lq_info[1 - lq_sta->active_tbl]); + IWL_DEBUG_RATE(mvm, "search- lq:%x, ant:%x, SGI:%d\n", + tmp_tbl->lq_type, tmp_tbl->ant_type, + tmp_tbl->is_SGI); + IWL_DEBUG_RATE(mvm, "actual- lq:%x, ant:%x, SGI:%d\n", + tbl_type.lq_type, tbl_type.ant_type, + tbl_type.is_SGI); + /* + * no matching table found, let's by-pass the data collection + * and continue to perform rate scale to find the rate table + */ + rs_stay_in_table(lq_sta, true); + goto done; + } + + /* + * Updating the frame history depends on whether packets were + * aggregated. + * + * For aggregation, all packets were transmitted at the same rate, the + * first index into rate scale table. + */ + if (info->flags & IEEE80211_TX_STAT_AMPDU) { + tx_rate = le32_to_cpu(table->rs_table[0]); + rs_get_tbl_info_from_mcs(tx_rate, info->band, &tbl_type, + &rs_index); + rs_collect_tx_data(curr_tbl, rs_index, + info->status.ampdu_len, + info->status.ampdu_ack_len); + + /* Update success/fail counts if not searching for new mode */ + if (lq_sta->stay_in_tbl) { + lq_sta->total_success += info->status.ampdu_ack_len; + lq_sta->total_failed += (info->status.ampdu_len - + info->status.ampdu_ack_len); + } + } else { + /* + * For legacy, update frame history with for each Tx retry. + */ + retries = info->status.rates[0].count - 1; + /* HW doesn't send more than 15 retries */ + retries = min(retries, 15); + + /* The last transmission may have been successful */ + legacy_success = !!(info->flags & IEEE80211_TX_STAT_ACK); + /* Collect data for each rate used during failed TX attempts */ + for (i = 0; i <= retries; ++i) { + tx_rate = le32_to_cpu(table->rs_table[i]); + rs_get_tbl_info_from_mcs(tx_rate, info->band, + &tbl_type, &rs_index); + /* + * Only collect stats if retried rate is in the same RS + * table as active/search. + */ + if (table_type_matches(&tbl_type, curr_tbl)) + tmp_tbl = curr_tbl; + else if (table_type_matches(&tbl_type, other_tbl)) + tmp_tbl = other_tbl; + else + continue; + rs_collect_tx_data(tmp_tbl, rs_index, 1, + i < retries ? 0 : legacy_success); + } + + /* Update success/fail counts if not searching for new mode */ + if (lq_sta->stay_in_tbl) { + lq_sta->total_success += legacy_success; + lq_sta->total_failed += retries + (1 - legacy_success); + } + } + /* The last TX rate is cached in lq_sta; it's set in if/else above */ + lq_sta->last_rate_n_flags = tx_rate; +done: + /* See if there's a better rate or modulation mode to try. */ + if (sta && sta->supp_rates[sband->band]) + rs_rate_scale_perform(mvm, skb, sta, lq_sta); +} + +/* + * Begin a period of staying with a selected modulation mode. + * Set "stay_in_tbl" flag to prevent any mode switches. + * Set frame tx success limits according to legacy vs. high-throughput, + * and reset overall (spanning all rates) tx success history statistics. + * These control how long we stay using same modulation mode before + * searching for a new mode. + */ +static void rs_set_stay_in_table(struct iwl_mvm *mvm, u8 is_legacy, + struct iwl_lq_sta *lq_sta) +{ + IWL_DEBUG_RATE(mvm, "we are staying in the same table\n"); + lq_sta->stay_in_tbl = 1; /* only place this gets set */ + if (is_legacy) { + lq_sta->table_count_limit = IWL_LEGACY_TABLE_COUNT; + lq_sta->max_failure_limit = IWL_LEGACY_FAILURE_LIMIT; + lq_sta->max_success_limit = IWL_LEGACY_SUCCESS_LIMIT; + } else { + lq_sta->table_count_limit = IWL_NONE_LEGACY_TABLE_COUNT; + lq_sta->max_failure_limit = IWL_NONE_LEGACY_FAILURE_LIMIT; + lq_sta->max_success_limit = IWL_NONE_LEGACY_SUCCESS_LIMIT; + } + lq_sta->table_count = 0; + lq_sta->total_failed = 0; + lq_sta->total_success = 0; + lq_sta->flush_timer = jiffies; + lq_sta->action_counter = 0; +} + +/* + * Find correct throughput table for given mode of modulation + */ +static void rs_set_expected_tpt_table(struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl) +{ + /* Used to choose among HT tables */ + s32 (*ht_tbl_pointer)[IWL_RATE_COUNT]; + + /* Check for invalid LQ type */ + if (WARN_ON_ONCE(!is_legacy(tbl->lq_type) && !is_Ht(tbl->lq_type))) { + tbl->expected_tpt = expected_tpt_legacy; + return; + } + + /* Legacy rates have only one table */ + if (is_legacy(tbl->lq_type)) { + tbl->expected_tpt = expected_tpt_legacy; + return; + } + + /* Choose among many HT tables depending on number of streams + * (SISO/MIMO2/MIMO3), channel width (20/40), SGI, and aggregation + * status */ + if (is_siso(tbl->lq_type) && !tbl->is_ht40) + ht_tbl_pointer = expected_tpt_siso20MHz; + else if (is_siso(tbl->lq_type)) + ht_tbl_pointer = expected_tpt_siso40MHz; + else if (is_mimo2(tbl->lq_type) && !tbl->is_ht40) + ht_tbl_pointer = expected_tpt_mimo2_20MHz; + else if (is_mimo2(tbl->lq_type)) + ht_tbl_pointer = expected_tpt_mimo2_40MHz; + else if (is_mimo3(tbl->lq_type) && !tbl->is_ht40) + ht_tbl_pointer = expected_tpt_mimo3_20MHz; + else /* if (is_mimo3(tbl->lq_type)) <-- must be true */ + ht_tbl_pointer = expected_tpt_mimo3_40MHz; + + if (!tbl->is_SGI && !lq_sta->is_agg) /* Normal */ + tbl->expected_tpt = ht_tbl_pointer[0]; + else if (tbl->is_SGI && !lq_sta->is_agg) /* SGI */ + tbl->expected_tpt = ht_tbl_pointer[1]; + else if (!tbl->is_SGI && lq_sta->is_agg) /* AGG */ + tbl->expected_tpt = ht_tbl_pointer[2]; + else /* AGG+SGI */ + tbl->expected_tpt = ht_tbl_pointer[3]; +} + +/* + * Find starting rate for new "search" high-throughput mode of modulation. + * Goal is to find lowest expected rate (under perfect conditions) that is + * above the current measured throughput of "active" mode, to give new mode + * a fair chance to prove itself without too many challenges. + * + * This gets called when transitioning to more aggressive modulation + * (i.e. legacy to SISO or MIMO, or SISO to MIMO), as well as less aggressive + * (i.e. MIMO to SISO). When moving to MIMO, bit rate will typically need + * to decrease to match "active" throughput. When moving from MIMO to SISO, + * bit rate will typically need to increase, but not if performance was bad. + */ +static s32 rs_get_best_rate(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl, /* "search" */ + u16 rate_mask, s8 index) +{ + /* "active" values */ + struct iwl_scale_tbl_info *active_tbl = + &(lq_sta->lq_info[lq_sta->active_tbl]); + s32 active_sr = active_tbl->win[index].success_ratio; + s32 active_tpt = active_tbl->expected_tpt[index]; + + /* expected "search" throughput */ + s32 *tpt_tbl = tbl->expected_tpt; + + s32 new_rate, high, low, start_hi; + u16 high_low; + s8 rate = index; + + new_rate = high = low = start_hi = IWL_RATE_INVALID; + + while (1) { + high_low = rs_get_adjacent_rate(mvm, rate, rate_mask, + tbl->lq_type); + + low = high_low & 0xff; + high = (high_low >> 8) & 0xff; + + /* + * Lower the "search" bit rate, to give new "search" mode + * approximately the same throughput as "active" if: + * + * 1) "Active" mode has been working modestly well (but not + * great), and expected "search" throughput (under perfect + * conditions) at candidate rate is above the actual + * measured "active" throughput (but less than expected + * "active" throughput under perfect conditions). + * OR + * 2) "Active" mode has been working perfectly or very well + * and expected "search" throughput (under perfect + * conditions) at candidate rate is above expected + * "active" throughput (under perfect conditions). + */ + if ((((100 * tpt_tbl[rate]) > lq_sta->last_tpt) && + ((active_sr > IWL_RATE_DECREASE_TH) && + (active_sr <= IWL_RATE_HIGH_TH) && + (tpt_tbl[rate] <= active_tpt))) || + ((active_sr >= IWL_RATE_SCALE_SWITCH) && + (tpt_tbl[rate] > active_tpt))) { + /* (2nd or later pass) + * If we've already tried to raise the rate, and are + * now trying to lower it, use the higher rate. */ + if (start_hi != IWL_RATE_INVALID) { + new_rate = start_hi; + break; + } + + new_rate = rate; + + /* Loop again with lower rate */ + if (low != IWL_RATE_INVALID) + rate = low; + + /* Lower rate not available, use the original */ + else + break; + + /* Else try to raise the "search" rate to match "active" */ + } else { + /* (2nd or later pass) + * If we've already tried to lower the rate, and are + * now trying to raise it, use the lower rate. */ + if (new_rate != IWL_RATE_INVALID) + break; + + /* Loop again with higher rate */ + else if (high != IWL_RATE_INVALID) { + start_hi = high; + rate = high; + + /* Higher rate not available, use the original */ + } else { + new_rate = rate; + break; + } + } + } + + return new_rate; +} + +static bool iwl_is_ht40_tx_allowed(struct ieee80211_sta *sta) +{ + return sta->bandwidth >= IEEE80211_STA_RX_BW_40; +} + +/* + * Set up search table for MIMO2 + */ +static int rs_switch_to_mimo2(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta, + struct ieee80211_sta *sta, + struct iwl_scale_tbl_info *tbl, int index) +{ + u16 rate_mask; + s32 rate; + s8 is_green = lq_sta->is_green; + + if (!sta->ht_cap.ht_supported) + return -1; + + if (sta->smps_mode == IEEE80211_SMPS_STATIC) + return -1; + + /* Need both Tx chains/antennas to support MIMO */ + if (num_of_ant(iwl_fw_valid_tx_ant(mvm->fw)) < 2) + return -1; + + IWL_DEBUG_RATE(mvm, "LQ: try to switch to MIMO2\n"); + + tbl->lq_type = LQ_MIMO2; + tbl->action = 0; + tbl->max_search = IWL_MAX_SEARCH; + rate_mask = lq_sta->active_mimo2_rate; + + if (iwl_is_ht40_tx_allowed(sta)) + tbl->is_ht40 = 1; + else + tbl->is_ht40 = 0; + + rs_set_expected_tpt_table(lq_sta, tbl); + + rate = rs_get_best_rate(mvm, lq_sta, tbl, rate_mask, index); + + IWL_DEBUG_RATE(mvm, "LQ: MIMO2 best rate %d mask %X\n", + rate, rate_mask); + if ((rate == IWL_RATE_INVALID) || !((1 << rate) & rate_mask)) { + IWL_DEBUG_RATE(mvm, "Can't switch with index %d rate mask %x\n", + rate, rate_mask); + return -1; + } + tbl->current_rate = rate_n_flags_from_tbl(mvm, tbl, rate, is_green); + + IWL_DEBUG_RATE(mvm, "LQ: Switch to new mcs %X index is green %X\n", + tbl->current_rate, is_green); + return 0; +} + +/* + * Set up search table for MIMO3 + */ +static int rs_switch_to_mimo3(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta, + struct ieee80211_sta *sta, + struct iwl_scale_tbl_info *tbl, int index) +{ + u16 rate_mask; + s32 rate; + s8 is_green = lq_sta->is_green; + + if (!sta->ht_cap.ht_supported) + return -1; + + if (sta->smps_mode == IEEE80211_SMPS_STATIC) + return -1; + + /* Need both Tx chains/antennas to support MIMO */ + if (num_of_ant(iwl_fw_valid_tx_ant(mvm->fw)) < 3) + return -1; + + IWL_DEBUG_RATE(mvm, "LQ: try to switch to MIMO3\n"); + + tbl->lq_type = LQ_MIMO3; + tbl->action = 0; + tbl->max_search = IWL_MAX_11N_MIMO3_SEARCH; + rate_mask = lq_sta->active_mimo3_rate; + + if (iwl_is_ht40_tx_allowed(sta)) + tbl->is_ht40 = 1; + else + tbl->is_ht40 = 0; + + rs_set_expected_tpt_table(lq_sta, tbl); + + rate = rs_get_best_rate(mvm, lq_sta, tbl, rate_mask, index); + + IWL_DEBUG_RATE(mvm, "LQ: MIMO3 best rate %d mask %X\n", + rate, rate_mask); + if ((rate == IWL_RATE_INVALID) || !((1 << rate) & rate_mask)) { + IWL_DEBUG_RATE(mvm, "Can't switch with index %d rate mask %x\n", + rate, rate_mask); + return -1; + } + tbl->current_rate = rate_n_flags_from_tbl(mvm, tbl, rate, is_green); + + IWL_DEBUG_RATE(mvm, "LQ: Switch to new mcs %X index is green %X\n", + tbl->current_rate, is_green); + return 0; +} + +/* + * Set up search table for SISO + */ +static int rs_switch_to_siso(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta, + struct ieee80211_sta *sta, + struct iwl_scale_tbl_info *tbl, int index) +{ + u16 rate_mask; + u8 is_green = lq_sta->is_green; + s32 rate; + + if (!sta->ht_cap.ht_supported) + return -1; + + IWL_DEBUG_RATE(mvm, "LQ: try to switch to SISO\n"); + + tbl->lq_type = LQ_SISO; + tbl->action = 0; + tbl->max_search = IWL_MAX_SEARCH; + rate_mask = lq_sta->active_siso_rate; + + if (iwl_is_ht40_tx_allowed(sta)) + tbl->is_ht40 = 1; + else + tbl->is_ht40 = 0; + + if (is_green) + tbl->is_SGI = 0; /*11n spec: no SGI in SISO+Greenfield*/ + + rs_set_expected_tpt_table(lq_sta, tbl); + rate = rs_get_best_rate(mvm, lq_sta, tbl, rate_mask, index); + + IWL_DEBUG_RATE(mvm, "LQ: get best rate %d mask %X\n", rate, rate_mask); + if ((rate == IWL_RATE_INVALID) || !((1 << rate) & rate_mask)) { + IWL_DEBUG_RATE(mvm, + "can not switch with index %d rate mask %x\n", + rate, rate_mask); + return -1; + } + tbl->current_rate = rate_n_flags_from_tbl(mvm, tbl, rate, is_green); + IWL_DEBUG_RATE(mvm, "LQ: Switch to new mcs %X index is green %X\n", + tbl->current_rate, is_green); + return 0; +} + +/* + * Try to switch to new modulation mode from legacy + */ +static int rs_move_legacy_other(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta, + struct ieee80211_sta *sta, + int index) +{ + struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + struct iwl_scale_tbl_info *search_tbl = + &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + struct iwl_rate_scale_data *window = &(tbl->win[index]); + u32 sz = (sizeof(struct iwl_scale_tbl_info) - + (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT)); + u8 start_action; + u8 valid_tx_ant = iwl_fw_valid_tx_ant(mvm->fw); + u8 tx_chains_num = num_of_ant(valid_tx_ant); + int ret; + u8 update_search_tbl_counter = 0; + + start_action = tbl->action; + while (1) { + lq_sta->action_counter++; + switch (tbl->action) { + case IWL_LEGACY_SWITCH_ANTENNA1: + case IWL_LEGACY_SWITCH_ANTENNA2: + IWL_DEBUG_RATE(mvm, "LQ: Legacy toggle Antenna\n"); + + if ((tbl->action == IWL_LEGACY_SWITCH_ANTENNA1 && + tx_chains_num <= 1) || + (tbl->action == IWL_LEGACY_SWITCH_ANTENNA2 && + tx_chains_num <= 2)) + break; + + /* Don't change antenna if success has been great */ + if (window->success_ratio >= IWL_RS_GOOD_RATIO) + break; + + /* Set up search table to try other antenna */ + memcpy(search_tbl, tbl, sz); + + if (rs_toggle_antenna(valid_tx_ant, + &search_tbl->current_rate, + search_tbl)) { + update_search_tbl_counter = 1; + rs_set_expected_tpt_table(lq_sta, search_tbl); + goto out; + } + break; + case IWL_LEGACY_SWITCH_SISO: + IWL_DEBUG_RATE(mvm, "LQ: Legacy switch to SISO\n"); + + /* Set up search table to try SISO */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + ret = rs_switch_to_siso(mvm, lq_sta, sta, + search_tbl, index); + if (!ret) { + lq_sta->action_counter = 0; + goto out; + } + + break; + case IWL_LEGACY_SWITCH_MIMO2_AB: + case IWL_LEGACY_SWITCH_MIMO2_AC: + case IWL_LEGACY_SWITCH_MIMO2_BC: + IWL_DEBUG_RATE(mvm, "LQ: Legacy switch to MIMO2\n"); + + /* Set up search table to try MIMO */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + + if (tbl->action == IWL_LEGACY_SWITCH_MIMO2_AB) + search_tbl->ant_type = ANT_AB; + else if (tbl->action == IWL_LEGACY_SWITCH_MIMO2_AC) + search_tbl->ant_type = ANT_AC; + else + search_tbl->ant_type = ANT_BC; + + if (!rs_is_valid_ant(valid_tx_ant, + search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo2(mvm, lq_sta, sta, + search_tbl, index); + if (!ret) { + lq_sta->action_counter = 0; + goto out; + } + break; + + case IWL_LEGACY_SWITCH_MIMO3_ABC: + IWL_DEBUG_RATE(mvm, "LQ: Legacy switch to MIMO3\n"); + + /* Set up search table to try MIMO3 */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + + search_tbl->ant_type = ANT_ABC; + + if (!rs_is_valid_ant(valid_tx_ant, + search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo3(mvm, lq_sta, sta, + search_tbl, index); + if (!ret) { + lq_sta->action_counter = 0; + goto out; + } + break; + } + tbl->action++; + if (tbl->action > IWL_LEGACY_SWITCH_MIMO3_ABC) + tbl->action = IWL_LEGACY_SWITCH_ANTENNA1; + + if (tbl->action == start_action) + break; + } + search_tbl->lq_type = LQ_NONE; + return 0; + +out: + lq_sta->search_better_tbl = 1; + tbl->action++; + if (tbl->action > IWL_LEGACY_SWITCH_MIMO3_ABC) + tbl->action = IWL_LEGACY_SWITCH_ANTENNA1; + if (update_search_tbl_counter) + search_tbl->action = tbl->action; + return 0; +} + +/* + * Try to switch to new modulation mode from SISO + */ +static int rs_move_siso_to_other(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta, + struct ieee80211_sta *sta, int index) +{ + u8 is_green = lq_sta->is_green; + struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + struct iwl_scale_tbl_info *search_tbl = + &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + struct iwl_rate_scale_data *window = &(tbl->win[index]); + struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + u32 sz = (sizeof(struct iwl_scale_tbl_info) - + (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT)); + u8 start_action; + u8 valid_tx_ant = iwl_fw_valid_tx_ant(mvm->fw); + u8 tx_chains_num = num_of_ant(valid_tx_ant); + u8 update_search_tbl_counter = 0; + int ret; + + start_action = tbl->action; + while (1) { + lq_sta->action_counter++; + switch (tbl->action) { + case IWL_SISO_SWITCH_ANTENNA1: + case IWL_SISO_SWITCH_ANTENNA2: + IWL_DEBUG_RATE(mvm, "LQ: SISO toggle Antenna\n"); + if ((tbl->action == IWL_SISO_SWITCH_ANTENNA1 && + tx_chains_num <= 1) || + (tbl->action == IWL_SISO_SWITCH_ANTENNA2 && + tx_chains_num <= 2)) + break; + + if (window->success_ratio >= IWL_RS_GOOD_RATIO) + break; + + memcpy(search_tbl, tbl, sz); + if (rs_toggle_antenna(valid_tx_ant, + &search_tbl->current_rate, + search_tbl)) { + update_search_tbl_counter = 1; + goto out; + } + break; + case IWL_SISO_SWITCH_MIMO2_AB: + case IWL_SISO_SWITCH_MIMO2_AC: + case IWL_SISO_SWITCH_MIMO2_BC: + IWL_DEBUG_RATE(mvm, "LQ: SISO switch to MIMO2\n"); + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + + if (tbl->action == IWL_SISO_SWITCH_MIMO2_AB) + search_tbl->ant_type = ANT_AB; + else if (tbl->action == IWL_SISO_SWITCH_MIMO2_AC) + search_tbl->ant_type = ANT_AC; + else + search_tbl->ant_type = ANT_BC; + + if (!rs_is_valid_ant(valid_tx_ant, + search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo2(mvm, lq_sta, sta, + search_tbl, index); + if (!ret) + goto out; + break; + case IWL_SISO_SWITCH_GI: + if (!tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_20)) + break; + if (tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_40)) + break; + + IWL_DEBUG_RATE(mvm, "LQ: SISO toggle SGI/NGI\n"); + + memcpy(search_tbl, tbl, sz); + if (is_green) { + if (!tbl->is_SGI) + break; + else + IWL_ERR(mvm, + "SGI was set in GF+SISO\n"); + } + search_tbl->is_SGI = !tbl->is_SGI; + rs_set_expected_tpt_table(lq_sta, search_tbl); + if (tbl->is_SGI) { + s32 tpt = lq_sta->last_tpt / 100; + if (tpt >= search_tbl->expected_tpt[index]) + break; + } + search_tbl->current_rate = + rate_n_flags_from_tbl(mvm, search_tbl, + index, is_green); + update_search_tbl_counter = 1; + goto out; + case IWL_SISO_SWITCH_MIMO3_ABC: + IWL_DEBUG_RATE(mvm, "LQ: SISO switch to MIMO3\n"); + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + search_tbl->ant_type = ANT_ABC; + + if (!rs_is_valid_ant(valid_tx_ant, + search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo3(mvm, lq_sta, sta, + search_tbl, index); + if (!ret) + goto out; + break; + } + tbl->action++; + if (tbl->action > IWL_LEGACY_SWITCH_MIMO3_ABC) + tbl->action = IWL_SISO_SWITCH_ANTENNA1; + + if (tbl->action == start_action) + break; + } + search_tbl->lq_type = LQ_NONE; + return 0; + + out: + lq_sta->search_better_tbl = 1; + tbl->action++; + if (tbl->action > IWL_SISO_SWITCH_MIMO3_ABC) + tbl->action = IWL_SISO_SWITCH_ANTENNA1; + if (update_search_tbl_counter) + search_tbl->action = tbl->action; + + return 0; +} + +/* + * Try to switch to new modulation mode from MIMO2 + */ +static int rs_move_mimo2_to_other(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta, + struct ieee80211_sta *sta, int index) +{ + s8 is_green = lq_sta->is_green; + struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + struct iwl_scale_tbl_info *search_tbl = + &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + struct iwl_rate_scale_data *window = &(tbl->win[index]); + struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + u32 sz = (sizeof(struct iwl_scale_tbl_info) - + (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT)); + u8 start_action; + u8 valid_tx_ant = iwl_fw_valid_tx_ant(mvm->fw); + u8 tx_chains_num = num_of_ant(valid_tx_ant); + u8 update_search_tbl_counter = 0; + int ret; + + start_action = tbl->action; + while (1) { + lq_sta->action_counter++; + switch (tbl->action) { + case IWL_MIMO2_SWITCH_ANTENNA1: + case IWL_MIMO2_SWITCH_ANTENNA2: + IWL_DEBUG_RATE(mvm, "LQ: MIMO2 toggle Antennas\n"); + + if (tx_chains_num <= 2) + break; + + if (window->success_ratio >= IWL_RS_GOOD_RATIO) + break; + + memcpy(search_tbl, tbl, sz); + if (rs_toggle_antenna(valid_tx_ant, + &search_tbl->current_rate, + search_tbl)) { + update_search_tbl_counter = 1; + goto out; + } + break; + case IWL_MIMO2_SWITCH_SISO_A: + case IWL_MIMO2_SWITCH_SISO_B: + case IWL_MIMO2_SWITCH_SISO_C: + IWL_DEBUG_RATE(mvm, "LQ: MIMO2 switch to SISO\n"); + + /* Set up new search table for SISO */ + memcpy(search_tbl, tbl, sz); + + if (tbl->action == IWL_MIMO2_SWITCH_SISO_A) + search_tbl->ant_type = ANT_A; + else if (tbl->action == IWL_MIMO2_SWITCH_SISO_B) + search_tbl->ant_type = ANT_B; + else + search_tbl->ant_type = ANT_C; + + if (!rs_is_valid_ant(valid_tx_ant, + search_tbl->ant_type)) + break; + + ret = rs_switch_to_siso(mvm, lq_sta, sta, + search_tbl, index); + if (!ret) + goto out; + + break; + + case IWL_MIMO2_SWITCH_GI: + if (!tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_20)) + break; + if (tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_40)) + break; + + IWL_DEBUG_RATE(mvm, "LQ: MIMO2 toggle SGI/NGI\n"); + + /* Set up new search table for MIMO2 */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = !tbl->is_SGI; + rs_set_expected_tpt_table(lq_sta, search_tbl); + /* + * If active table already uses the fastest possible + * modulation (dual stream with short guard interval), + * and it's working well, there's no need to look + * for a better type of modulation! + */ + if (tbl->is_SGI) { + s32 tpt = lq_sta->last_tpt / 100; + if (tpt >= search_tbl->expected_tpt[index]) + break; + } + search_tbl->current_rate = + rate_n_flags_from_tbl(mvm, search_tbl, + index, is_green); + update_search_tbl_counter = 1; + goto out; + + case IWL_MIMO2_SWITCH_MIMO3_ABC: + IWL_DEBUG_RATE(mvm, "LQ: MIMO2 switch to MIMO3\n"); + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + search_tbl->ant_type = ANT_ABC; + + if (!rs_is_valid_ant(valid_tx_ant, + search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo3(mvm, lq_sta, sta, + search_tbl, index); + if (!ret) + goto out; + + break; + } + tbl->action++; + if (tbl->action > IWL_MIMO2_SWITCH_MIMO3_ABC) + tbl->action = IWL_MIMO2_SWITCH_ANTENNA1; + + if (tbl->action == start_action) + break; + } + search_tbl->lq_type = LQ_NONE; + return 0; + out: + lq_sta->search_better_tbl = 1; + tbl->action++; + if (tbl->action > IWL_MIMO2_SWITCH_MIMO3_ABC) + tbl->action = IWL_MIMO2_SWITCH_ANTENNA1; + if (update_search_tbl_counter) + search_tbl->action = tbl->action; + + return 0; +} + +/* + * Try to switch to new modulation mode from MIMO3 + */ +static int rs_move_mimo3_to_other(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta, + struct ieee80211_sta *sta, int index) +{ + s8 is_green = lq_sta->is_green; + struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + struct iwl_scale_tbl_info *search_tbl = + &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + struct iwl_rate_scale_data *window = &(tbl->win[index]); + struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + u32 sz = (sizeof(struct iwl_scale_tbl_info) - + (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT)); + u8 start_action; + u8 valid_tx_ant = iwl_fw_valid_tx_ant(mvm->fw); + u8 tx_chains_num = num_of_ant(valid_tx_ant); + int ret; + u8 update_search_tbl_counter = 0; + + start_action = tbl->action; + while (1) { + lq_sta->action_counter++; + switch (tbl->action) { + case IWL_MIMO3_SWITCH_ANTENNA1: + case IWL_MIMO3_SWITCH_ANTENNA2: + IWL_DEBUG_RATE(mvm, "LQ: MIMO3 toggle Antennas\n"); + + if (tx_chains_num <= 3) + break; + + if (window->success_ratio >= IWL_RS_GOOD_RATIO) + break; + + memcpy(search_tbl, tbl, sz); + if (rs_toggle_antenna(valid_tx_ant, + &search_tbl->current_rate, + search_tbl)) + goto out; + break; + case IWL_MIMO3_SWITCH_SISO_A: + case IWL_MIMO3_SWITCH_SISO_B: + case IWL_MIMO3_SWITCH_SISO_C: + IWL_DEBUG_RATE(mvm, "LQ: MIMO3 switch to SISO\n"); + + /* Set up new search table for SISO */ + memcpy(search_tbl, tbl, sz); + + if (tbl->action == IWL_MIMO3_SWITCH_SISO_A) + search_tbl->ant_type = ANT_A; + else if (tbl->action == IWL_MIMO3_SWITCH_SISO_B) + search_tbl->ant_type = ANT_B; + else + search_tbl->ant_type = ANT_C; + + if (!rs_is_valid_ant(valid_tx_ant, + search_tbl->ant_type)) + break; + + ret = rs_switch_to_siso(mvm, lq_sta, sta, + search_tbl, index); + if (!ret) + goto out; + + break; + + case IWL_MIMO3_SWITCH_MIMO2_AB: + case IWL_MIMO3_SWITCH_MIMO2_AC: + case IWL_MIMO3_SWITCH_MIMO2_BC: + IWL_DEBUG_RATE(mvm, "LQ: MIMO3 switch to MIMO2\n"); + + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = 0; + if (tbl->action == IWL_MIMO3_SWITCH_MIMO2_AB) + search_tbl->ant_type = ANT_AB; + else if (tbl->action == IWL_MIMO3_SWITCH_MIMO2_AC) + search_tbl->ant_type = ANT_AC; + else + search_tbl->ant_type = ANT_BC; + + if (!rs_is_valid_ant(valid_tx_ant, + search_tbl->ant_type)) + break; + + ret = rs_switch_to_mimo2(mvm, lq_sta, sta, + search_tbl, index); + if (!ret) + goto out; + + break; + + case IWL_MIMO3_SWITCH_GI: + if (!tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_20)) + break; + if (tbl->is_ht40 && !(ht_cap->cap & + IEEE80211_HT_CAP_SGI_40)) + break; + + IWL_DEBUG_RATE(mvm, "LQ: MIMO3 toggle SGI/NGI\n"); + + /* Set up new search table for MIMO */ + memcpy(search_tbl, tbl, sz); + search_tbl->is_SGI = !tbl->is_SGI; + rs_set_expected_tpt_table(lq_sta, search_tbl); + /* + * If active table already uses the fastest possible + * modulation (dual stream with short guard interval), + * and it's working well, there's no need to look + * for a better type of modulation! + */ + if (tbl->is_SGI) { + s32 tpt = lq_sta->last_tpt / 100; + if (tpt >= search_tbl->expected_tpt[index]) + break; + } + search_tbl->current_rate = + rate_n_flags_from_tbl(mvm, search_tbl, + index, is_green); + update_search_tbl_counter = 1; + goto out; + } + tbl->action++; + if (tbl->action > IWL_MIMO3_SWITCH_GI) + tbl->action = IWL_MIMO3_SWITCH_ANTENNA1; + + if (tbl->action == start_action) + break; + } + search_tbl->lq_type = LQ_NONE; + return 0; + out: + lq_sta->search_better_tbl = 1; + tbl->action++; + if (tbl->action > IWL_MIMO3_SWITCH_GI) + tbl->action = IWL_MIMO3_SWITCH_ANTENNA1; + if (update_search_tbl_counter) + search_tbl->action = tbl->action; + + return 0; +} + +/* + * Check whether we should continue using same modulation mode, or + * begin search for a new mode, based on: + * 1) # tx successes or failures while using this mode + * 2) # times calling this function + * 3) elapsed time in this mode (not used, for now) + */ +static void rs_stay_in_table(struct iwl_lq_sta *lq_sta, bool force_search) +{ + struct iwl_scale_tbl_info *tbl; + int i; + int active_tbl; + int flush_interval_passed = 0; + struct iwl_mvm *mvm; + + mvm = lq_sta->drv; + active_tbl = lq_sta->active_tbl; + + tbl = &(lq_sta->lq_info[active_tbl]); + + /* If we've been disallowing search, see if we should now allow it */ + if (lq_sta->stay_in_tbl) { + /* Elapsed time using current modulation mode */ + if (lq_sta->flush_timer) + flush_interval_passed = + time_after(jiffies, + (unsigned long)(lq_sta->flush_timer + + IWL_RATE_SCALE_FLUSH_INTVL)); + + /* + * Check if we should allow search for new modulation mode. + * If many frames have failed or succeeded, or we've used + * this same modulation for a long time, allow search, and + * reset history stats that keep track of whether we should + * allow a new search. Also (below) reset all bitmaps and + * stats in active history. + */ + if (force_search || + (lq_sta->total_failed > lq_sta->max_failure_limit) || + (lq_sta->total_success > lq_sta->max_success_limit) || + ((!lq_sta->search_better_tbl) && + (lq_sta->flush_timer) && (flush_interval_passed))) { + IWL_DEBUG_RATE(mvm, + "LQ: stay is expired %d %d %d\n", + lq_sta->total_failed, + lq_sta->total_success, + flush_interval_passed); + + /* Allow search for new mode */ + lq_sta->stay_in_tbl = 0; /* only place reset */ + lq_sta->total_failed = 0; + lq_sta->total_success = 0; + lq_sta->flush_timer = 0; + /* + * Else if we've used this modulation mode enough repetitions + * (regardless of elapsed time or success/failure), reset + * history bitmaps and rate-specific stats for all rates in + * active table. + */ + } else { + lq_sta->table_count++; + if (lq_sta->table_count >= + lq_sta->table_count_limit) { + lq_sta->table_count = 0; + + IWL_DEBUG_RATE(mvm, + "LQ: stay in table clear win\n"); + for (i = 0; i < IWL_RATE_COUNT; i++) + rs_rate_scale_clear_window( + &(tbl->win[i])); + } + } + + /* If transitioning to allow "search", reset all history + * bitmaps and stats in active table (this will become the new + * "search" table). */ + if (!lq_sta->stay_in_tbl) { + for (i = 0; i < IWL_RATE_COUNT; i++) + rs_rate_scale_clear_window(&(tbl->win[i])); + } + } +} + +/* + * setup rate table in uCode + */ +static void rs_update_rate_tbl(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta, + struct iwl_scale_tbl_info *tbl, + int index, u8 is_green) +{ + u32 rate; + + /* Update uCode's rate table. */ + rate = rate_n_flags_from_tbl(mvm, tbl, index, is_green); + rs_fill_link_cmd(mvm, lq_sta, rate); + iwl_mvm_send_lq_cmd(mvm, &lq_sta->lq, CMD_ASYNC, false); +} + +/* + * Do rate scaling and search for new modulation mode. + */ +static void rs_rate_scale_perform(struct iwl_mvm *mvm, + struct sk_buff *skb, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + int low = IWL_RATE_INVALID; + int high = IWL_RATE_INVALID; + int index; + int i; + struct iwl_rate_scale_data *window = NULL; + int current_tpt = IWL_INVALID_VALUE; + int low_tpt = IWL_INVALID_VALUE; + int high_tpt = IWL_INVALID_VALUE; + u32 fail_count; + s8 scale_action = 0; + u16 rate_mask; + u8 update_lq = 0; + struct iwl_scale_tbl_info *tbl, *tbl1; + u16 rate_scale_index_msk = 0; + u8 is_green = 0; + u8 active_tbl = 0; + u8 done_search = 0; + u16 high_low; + s32 sr; + u8 tid = IWL_MAX_TID_COUNT; + struct iwl_mvm_sta *sta_priv = (void *)sta->drv_priv; + struct iwl_mvm_tid_data *tid_data; + + IWL_DEBUG_RATE(mvm, "rate scale calculate new rate for skb\n"); + + /* Send management frames and NO_ACK data using lowest rate. */ + /* TODO: this could probably be improved.. */ + if (!ieee80211_is_data(hdr->frame_control) || + info->flags & IEEE80211_TX_CTL_NO_ACK) + return; + + lq_sta->supp_rates = sta->supp_rates[lq_sta->band]; + + tid = rs_tl_add_packet(lq_sta, hdr); + if ((tid != IWL_MAX_TID_COUNT) && + (lq_sta->tx_agg_tid_en & (1 << tid))) { + tid_data = &sta_priv->tid_data[tid]; + if (tid_data->state == IWL_AGG_OFF) + lq_sta->is_agg = 0; + else + lq_sta->is_agg = 1; + } else { + lq_sta->is_agg = 0; + } + + /* + * Select rate-scale / modulation-mode table to work with in + * the rest of this function: "search" if searching for better + * modulation mode, or "active" if doing rate scaling within a mode. + */ + if (!lq_sta->search_better_tbl) + active_tbl = lq_sta->active_tbl; + else + active_tbl = 1 - lq_sta->active_tbl; + + tbl = &(lq_sta->lq_info[active_tbl]); + if (is_legacy(tbl->lq_type)) + lq_sta->is_green = 0; + else + lq_sta->is_green = rs_use_green(sta); + is_green = lq_sta->is_green; + + /* current tx rate */ + index = lq_sta->last_txrate_idx; + + IWL_DEBUG_RATE(mvm, "Rate scale index %d for type %d\n", index, + tbl->lq_type); + + /* rates available for this association, and for modulation mode */ + rate_mask = rs_get_supported_rates(lq_sta, hdr, tbl->lq_type); + + IWL_DEBUG_RATE(mvm, "mask 0x%04X\n", rate_mask); + + /* mask with station rate restriction */ + if (is_legacy(tbl->lq_type)) { + if (lq_sta->band == IEEE80211_BAND_5GHZ) + /* supp_rates has no CCK bits in A mode */ + rate_scale_index_msk = (u16) (rate_mask & + (lq_sta->supp_rates << IWL_FIRST_OFDM_RATE)); + else + rate_scale_index_msk = (u16) (rate_mask & + lq_sta->supp_rates); + + } else { + rate_scale_index_msk = rate_mask; + } + + if (!rate_scale_index_msk) + rate_scale_index_msk = rate_mask; + + if (!((1 << index) & rate_scale_index_msk)) { + IWL_ERR(mvm, "Current Rate is not valid\n"); + if (lq_sta->search_better_tbl) { + /* revert to active table if search table is not valid*/ + tbl->lq_type = LQ_NONE; + lq_sta->search_better_tbl = 0; + tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + /* get "active" rate info */ + index = iwl_hwrate_to_plcp_idx(tbl->current_rate); + rs_update_rate_tbl(mvm, lq_sta, tbl, index, is_green); + } + return; + } + + /* Get expected throughput table and history window for current rate */ + if (!tbl->expected_tpt) { + IWL_ERR(mvm, "tbl->expected_tpt is NULL\n"); + return; + } + + /* force user max rate if set by user */ + if ((lq_sta->max_rate_idx != -1) && + (lq_sta->max_rate_idx < index)) { + index = lq_sta->max_rate_idx; + update_lq = 1; + window = &(tbl->win[index]); + goto lq_update; + } + + window = &(tbl->win[index]); + + /* + * If there is not enough history to calculate actual average + * throughput, keep analyzing results of more tx frames, without + * changing rate or mode (bypass most of the rest of this function). + * Set up new rate table in uCode only if old rate is not supported + * in current association (use new rate found above). + */ + fail_count = window->counter - window->success_counter; + if ((fail_count < IWL_RATE_MIN_FAILURE_TH) && + (window->success_counter < IWL_RATE_MIN_SUCCESS_TH)) { + IWL_DEBUG_RATE(mvm, + "LQ: still below TH. succ=%d total=%d for index %d\n", + window->success_counter, window->counter, index); + + /* Can't calculate this yet; not enough history */ + window->average_tpt = IWL_INVALID_VALUE; + + /* Should we stay with this modulation mode, + * or search for a new one? */ + rs_stay_in_table(lq_sta, false); + + goto out; + } + /* Else we have enough samples; calculate estimate of + * actual average throughput */ + if (window->average_tpt != ((window->success_ratio * + tbl->expected_tpt[index] + 64) / 128)) { + IWL_ERR(mvm, + "expected_tpt should have been calculated by now\n"); + window->average_tpt = ((window->success_ratio * + tbl->expected_tpt[index] + 64) / 128); + } + + /* If we are searching for better modulation mode, check success. */ + if (lq_sta->search_better_tbl) { + /* If good success, continue using the "search" mode; + * no need to send new link quality command, since we're + * continuing to use the setup that we've been trying. */ + if (window->average_tpt > lq_sta->last_tpt) { + IWL_DEBUG_RATE(mvm, + "LQ: SWITCHING TO NEW TABLE suc=%d cur-tpt=%d old-tpt=%d\n", + window->success_ratio, + window->average_tpt, + lq_sta->last_tpt); + + if (!is_legacy(tbl->lq_type)) + lq_sta->enable_counter = 1; + + /* Swap tables; "search" becomes "active" */ + lq_sta->active_tbl = active_tbl; + current_tpt = window->average_tpt; + /* Else poor success; go back to mode in "active" table */ + } else { + IWL_DEBUG_RATE(mvm, + "LQ: GOING BACK TO THE OLD TABLE suc=%d cur-tpt=%d old-tpt=%d\n", + window->success_ratio, + window->average_tpt, + lq_sta->last_tpt); + + /* Nullify "search" table */ + tbl->lq_type = LQ_NONE; + + /* Revert to "active" table */ + active_tbl = lq_sta->active_tbl; + tbl = &(lq_sta->lq_info[active_tbl]); + + /* Revert to "active" rate and throughput info */ + index = iwl_hwrate_to_plcp_idx(tbl->current_rate); + current_tpt = lq_sta->last_tpt; + + /* Need to set up a new rate table in uCode */ + update_lq = 1; + } + + /* Either way, we've made a decision; modulation mode + * search is done, allow rate adjustment next time. */ + lq_sta->search_better_tbl = 0; + done_search = 1; /* Don't switch modes below! */ + goto lq_update; + } + + /* (Else) not in search of better modulation mode, try for better + * starting rate, while staying in this mode. */ + high_low = rs_get_adjacent_rate(mvm, index, rate_scale_index_msk, + tbl->lq_type); + low = high_low & 0xff; + high = (high_low >> 8) & 0xff; + + /* If user set max rate, dont allow higher than user constrain */ + if ((lq_sta->max_rate_idx != -1) && + (lq_sta->max_rate_idx < high)) + high = IWL_RATE_INVALID; + + sr = window->success_ratio; + + /* Collect measured throughputs for current and adjacent rates */ + current_tpt = window->average_tpt; + if (low != IWL_RATE_INVALID) + low_tpt = tbl->win[low].average_tpt; + if (high != IWL_RATE_INVALID) + high_tpt = tbl->win[high].average_tpt; + + scale_action = 0; + + /* Too many failures, decrease rate */ + if ((sr <= IWL_RATE_DECREASE_TH) || (current_tpt == 0)) { + IWL_DEBUG_RATE(mvm, + "decrease rate because of low success_ratio\n"); + scale_action = -1; + /* No throughput measured yet for adjacent rates; try increase. */ + } else if ((low_tpt == IWL_INVALID_VALUE) && + (high_tpt == IWL_INVALID_VALUE)) { + if (high != IWL_RATE_INVALID && sr >= IWL_RATE_INCREASE_TH) + scale_action = 1; + else if (low != IWL_RATE_INVALID) + scale_action = 0; + } + + /* Both adjacent throughputs are measured, but neither one has better + * throughput; we're using the best rate, don't change it! */ + else if ((low_tpt != IWL_INVALID_VALUE) && + (high_tpt != IWL_INVALID_VALUE) && + (low_tpt < current_tpt) && + (high_tpt < current_tpt)) + scale_action = 0; + + /* At least one adjacent rate's throughput is measured, + * and may have better performance. */ + else { + /* Higher adjacent rate's throughput is measured */ + if (high_tpt != IWL_INVALID_VALUE) { + /* Higher rate has better throughput */ + if (high_tpt > current_tpt && + sr >= IWL_RATE_INCREASE_TH) { + scale_action = 1; + } else { + scale_action = 0; + } + + /* Lower adjacent rate's throughput is measured */ + } else if (low_tpt != IWL_INVALID_VALUE) { + /* Lower rate has better throughput */ + if (low_tpt > current_tpt) { + IWL_DEBUG_RATE(mvm, + "decrease rate because of low tpt\n"); + scale_action = -1; + } else if (sr >= IWL_RATE_INCREASE_TH) { + scale_action = 1; + } + } + } + + /* Sanity check; asked for decrease, but success rate or throughput + * has been good at old rate. Don't change it. */ + if ((scale_action == -1) && (low != IWL_RATE_INVALID) && + ((sr > IWL_RATE_HIGH_TH) || + (current_tpt > (100 * tbl->expected_tpt[low])))) + scale_action = 0; + + switch (scale_action) { + case -1: + /* Decrease starting rate, update uCode's rate table */ + if (low != IWL_RATE_INVALID) { + update_lq = 1; + index = low; + } + + break; + case 1: + /* Increase starting rate, update uCode's rate table */ + if (high != IWL_RATE_INVALID) { + update_lq = 1; + index = high; + } + + break; + case 0: + /* No change */ + default: + break; + } + + IWL_DEBUG_RATE(mvm, + "choose rate scale index %d action %d low %d high %d type %d\n", + index, scale_action, low, high, tbl->lq_type); + +lq_update: + /* Replace uCode's rate table for the destination station. */ + if (update_lq) + rs_update_rate_tbl(mvm, lq_sta, tbl, index, is_green); + + rs_stay_in_table(lq_sta, false); + + /* + * Search for new modulation mode if we're: + * 1) Not changing rates right now + * 2) Not just finishing up a search + * 3) Allowing a new search + */ + if (!update_lq && !done_search && + !lq_sta->stay_in_tbl && window->counter) { + /* Save current throughput to compare with "search" throughput*/ + lq_sta->last_tpt = current_tpt; + + /* Select a new "search" modulation mode to try. + * If one is found, set up the new "search" table. */ + if (is_legacy(tbl->lq_type)) + rs_move_legacy_other(mvm, lq_sta, sta, index); + else if (is_siso(tbl->lq_type)) + rs_move_siso_to_other(mvm, lq_sta, sta, index); + else if (is_mimo2(tbl->lq_type)) + rs_move_mimo2_to_other(mvm, lq_sta, sta, index); + else + rs_move_mimo3_to_other(mvm, lq_sta, sta, index); + + /* If new "search" mode was selected, set up in uCode table */ + if (lq_sta->search_better_tbl) { + /* Access the "search" table, clear its history. */ + tbl = &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); + for (i = 0; i < IWL_RATE_COUNT; i++) + rs_rate_scale_clear_window(&(tbl->win[i])); + + /* Use new "search" start rate */ + index = iwl_hwrate_to_plcp_idx(tbl->current_rate); + + IWL_DEBUG_RATE(mvm, + "Switch current mcs: %X index: %d\n", + tbl->current_rate, index); + rs_fill_link_cmd(mvm, lq_sta, tbl->current_rate); + iwl_mvm_send_lq_cmd(mvm, &lq_sta->lq, CMD_ASYNC, false); + } else { + done_search = 1; + } + } + + if (done_search && !lq_sta->stay_in_tbl) { + /* If the "active" (non-search) mode was legacy, + * and we've tried switching antennas, + * but we haven't been able to try HT modes (not available), + * stay with best antenna legacy modulation for a while + * before next round of mode comparisons. */ + tbl1 = &(lq_sta->lq_info[lq_sta->active_tbl]); + if (is_legacy(tbl1->lq_type) && !sta->ht_cap.ht_supported && + lq_sta->action_counter > tbl1->max_search) { + IWL_DEBUG_RATE(mvm, "LQ: STAY in legacy table\n"); + rs_set_stay_in_table(mvm, 1, lq_sta); + } + + /* If we're in an HT mode, and all 3 mode switch actions + * have been tried and compared, stay in this best modulation + * mode for a while before next round of mode comparisons. */ + if (lq_sta->enable_counter && + (lq_sta->action_counter >= tbl1->max_search)) { + if ((lq_sta->last_tpt > IWL_AGG_TPT_THREHOLD) && + (lq_sta->tx_agg_tid_en & (1 << tid)) && + (tid != IWL_MAX_TID_COUNT)) { + tid_data = &sta_priv->tid_data[tid]; + if (tid_data->state == IWL_AGG_OFF) { + IWL_DEBUG_RATE(mvm, + "try to aggregate tid %d\n", + tid); + rs_tl_turn_on_agg(mvm, tid, + lq_sta, sta); + } + } + rs_set_stay_in_table(mvm, 0, lq_sta); + } + } + +out: + tbl->current_rate = rate_n_flags_from_tbl(mvm, tbl, index, is_green); + lq_sta->last_txrate_idx = index; +} + +/** + * rs_initialize_lq - Initialize a station's hardware rate table + * + * The uCode's station table contains a table of fallback rates + * for automatic fallback during transmission. + * + * NOTE: This sets up a default set of values. These will be replaced later + * if the driver's iwl-agn-rs rate scaling algorithm is used, instead of + * rc80211_simple. + * + * NOTE: Run REPLY_ADD_STA command to set up station table entry, before + * calling this function (which runs REPLY_TX_LINK_QUALITY_CMD, + * which requires station table entry to exist). + */ +static void rs_initialize_lq(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + struct iwl_lq_sta *lq_sta, + enum ieee80211_band band) +{ + struct iwl_scale_tbl_info *tbl; + int rate_idx; + int i; + u32 rate; + u8 use_green = rs_use_green(sta); + u8 active_tbl = 0; + u8 valid_tx_ant; + + if (!sta || !lq_sta) + return; + + i = lq_sta->last_txrate_idx; + + valid_tx_ant = iwl_fw_valid_tx_ant(mvm->fw); + + if (!lq_sta->search_better_tbl) + active_tbl = lq_sta->active_tbl; + else + active_tbl = 1 - lq_sta->active_tbl; + + tbl = &(lq_sta->lq_info[active_tbl]); + + if ((i < 0) || (i >= IWL_RATE_COUNT)) + i = 0; + + rate = iwl_rates[i].plcp; + tbl->ant_type = first_antenna(valid_tx_ant); + rate |= tbl->ant_type << RATE_MCS_ANT_POS; + + if (i >= IWL_FIRST_CCK_RATE && i <= IWL_LAST_CCK_RATE) + rate |= RATE_MCS_CCK_MSK; + + rs_get_tbl_info_from_mcs(rate, band, tbl, &rate_idx); + if (!rs_is_valid_ant(valid_tx_ant, tbl->ant_type)) + rs_toggle_antenna(valid_tx_ant, &rate, tbl); + + rate = rate_n_flags_from_tbl(mvm, tbl, rate_idx, use_green); + tbl->current_rate = rate; + rs_set_expected_tpt_table(lq_sta, tbl); + rs_fill_link_cmd(NULL, lq_sta, rate); + /* TODO restore station should remember the lq cmd */ + iwl_mvm_send_lq_cmd(mvm, &lq_sta->lq, CMD_SYNC, true); +} + +static void rs_get_rate(void *mvm_r, struct ieee80211_sta *sta, void *mvm_sta, + struct ieee80211_tx_rate_control *txrc) +{ + struct sk_buff *skb = txrc->skb; + struct ieee80211_supported_band *sband = txrc->sband; + struct iwl_op_mode *op_mode __maybe_unused = + (struct iwl_op_mode *)mvm_r; + struct iwl_mvm *mvm __maybe_unused = IWL_OP_MODE_GET_MVM(op_mode); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct iwl_lq_sta *lq_sta = mvm_sta; + int rate_idx; + + IWL_DEBUG_RATE_LIMIT(mvm, "rate scale calculate new rate for skb\n"); + + /* Get max rate if user set max rate */ + if (lq_sta) { + lq_sta->max_rate_idx = txrc->max_rate_idx; + if ((sband->band == IEEE80211_BAND_5GHZ) && + (lq_sta->max_rate_idx != -1)) + lq_sta->max_rate_idx += IWL_FIRST_OFDM_RATE; + if ((lq_sta->max_rate_idx < 0) || + (lq_sta->max_rate_idx >= IWL_RATE_COUNT)) + lq_sta->max_rate_idx = -1; + } + + /* Treat uninitialized rate scaling data same as non-existing. */ + if (lq_sta && !lq_sta->drv) { + IWL_DEBUG_RATE(mvm, "Rate scaling not initialized yet.\n"); + mvm_sta = NULL; + } + + /* Send management frames and NO_ACK data using lowest rate. */ + if (rate_control_send_low(sta, mvm_sta, txrc)) + return; + + rate_idx = lq_sta->last_txrate_idx; + + if (lq_sta->last_rate_n_flags & RATE_MCS_HT_MSK) { + rate_idx -= IWL_FIRST_OFDM_RATE; + /* 6M and 9M shared same MCS index */ + rate_idx = (rate_idx > 0) ? (rate_idx - 1) : 0; + if (rs_extract_rate(lq_sta->last_rate_n_flags) >= + IWL_RATE_MIMO3_6M_PLCP) + rate_idx = rate_idx + (2 * MCS_INDEX_PER_STREAM); + else if (rs_extract_rate(lq_sta->last_rate_n_flags) >= + IWL_RATE_MIMO2_6M_PLCP) + rate_idx = rate_idx + MCS_INDEX_PER_STREAM; + info->control.rates[0].flags = IEEE80211_TX_RC_MCS; + if (lq_sta->last_rate_n_flags & RATE_MCS_SGI_MSK) + info->control.rates[0].flags |= IEEE80211_TX_RC_SHORT_GI; + if (lq_sta->last_rate_n_flags & RATE_MCS_CHAN_WIDTH_40) /* TODO */ + info->control.rates[0].flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; + if (lq_sta->last_rate_n_flags & RATE_HT_MCS_GF_MSK) + info->control.rates[0].flags |= IEEE80211_TX_RC_GREEN_FIELD; + } else { + /* Check for invalid rates */ + if ((rate_idx < 0) || (rate_idx >= IWL_RATE_COUNT_LEGACY) || + ((sband->band == IEEE80211_BAND_5GHZ) && + (rate_idx < IWL_FIRST_OFDM_RATE))) + rate_idx = rate_lowest_index(sband, sta); + /* On valid 5 GHz rate, adjust index */ + else if (sband->band == IEEE80211_BAND_5GHZ) + rate_idx -= IWL_FIRST_OFDM_RATE; + info->control.rates[0].flags = 0; + } + info->control.rates[0].idx = rate_idx; + info->control.rates[0].count = 1; +} + +static void *rs_alloc_sta(void *mvm_rate, struct ieee80211_sta *sta, + gfp_t gfp) +{ + struct iwl_mvm_sta *sta_priv = (struct iwl_mvm_sta *)sta->drv_priv; + struct iwl_op_mode *op_mode __maybe_unused = + (struct iwl_op_mode *)mvm_rate; + struct iwl_mvm *mvm __maybe_unused = IWL_OP_MODE_GET_MVM(op_mode); + + IWL_DEBUG_RATE(mvm, "create station rate scale window\n"); + + return &sta_priv->lq_sta; +} + +/* + * Called after adding a new station to initialize rate scaling + */ +void iwl_mvm_rs_rate_init(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + enum ieee80211_band band) +{ + int i, j; + struct ieee80211_hw *hw = mvm->hw; + struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; + struct iwl_mvm_sta *sta_priv; + struct iwl_lq_sta *lq_sta; + struct ieee80211_supported_band *sband; + unsigned long supp; /* must be unsigned long for for_each_set_bit */ + + sta_priv = (struct iwl_mvm_sta *)sta->drv_priv; + lq_sta = &sta_priv->lq_sta; + sband = hw->wiphy->bands[band]; + + lq_sta->lq.sta_id = sta_priv->sta_id; + + for (j = 0; j < LQ_SIZE; j++) + for (i = 0; i < IWL_RATE_COUNT; i++) + rs_rate_scale_clear_window(&lq_sta->lq_info[j].win[i]); + + lq_sta->flush_timer = 0; + lq_sta->supp_rates = sta->supp_rates[sband->band]; + for (j = 0; j < LQ_SIZE; j++) + for (i = 0; i < IWL_RATE_COUNT; i++) + rs_rate_scale_clear_window(&lq_sta->lq_info[j].win[i]); + + IWL_DEBUG_RATE(mvm, + "LQ: *** rate scale station global init for station %d ***\n", + sta_priv->sta_id); + /* TODO: what is a good starting rate for STA? About middle? Maybe not + * the lowest or the highest rate.. Could consider using RSSI from + * previous packets? Need to have IEEE 802.1X auth succeed immediately + * after assoc.. */ + + lq_sta->max_rate_idx = -1; + lq_sta->missed_rate_counter = IWL_MISSED_RATE_MAX; + lq_sta->is_green = rs_use_green(sta); + lq_sta->band = sband->band; + /* + * active legacy rates as per supported rates bitmap + */ + supp = sta->supp_rates[sband->band]; + lq_sta->active_legacy_rate = 0; + for_each_set_bit(i, &supp, BITS_PER_LONG) + lq_sta->active_legacy_rate |= BIT(sband->bitrates[i].hw_value); + + /* + * active_siso_rate mask includes 9 MBits (bit 5), and CCK (bits 0-3), + * supp_rates[] does not; shift to convert format, force 9 MBits off. + */ + lq_sta->active_siso_rate = ht_cap->mcs.rx_mask[0] << 1; + lq_sta->active_siso_rate |= ht_cap->mcs.rx_mask[0] & 0x1; + lq_sta->active_siso_rate &= ~((u16)0x2); + lq_sta->active_siso_rate <<= IWL_FIRST_OFDM_RATE; + + /* Same here */ + lq_sta->active_mimo2_rate = ht_cap->mcs.rx_mask[1] << 1; + lq_sta->active_mimo2_rate |= ht_cap->mcs.rx_mask[1] & 0x1; + lq_sta->active_mimo2_rate &= ~((u16)0x2); + lq_sta->active_mimo2_rate <<= IWL_FIRST_OFDM_RATE; + + lq_sta->active_mimo3_rate = ht_cap->mcs.rx_mask[2] << 1; + lq_sta->active_mimo3_rate |= ht_cap->mcs.rx_mask[2] & 0x1; + lq_sta->active_mimo3_rate &= ~((u16)0x2); + lq_sta->active_mimo3_rate <<= IWL_FIRST_OFDM_RATE; + + IWL_DEBUG_RATE(mvm, + "SISO-RATE=%X MIMO2-RATE=%X MIMO3-RATE=%X\n", + lq_sta->active_siso_rate, + lq_sta->active_mimo2_rate, + lq_sta->active_mimo3_rate); + + /* These values will be overridden later */ + lq_sta->lq.single_stream_ant_msk = + first_antenna(iwl_fw_valid_tx_ant(mvm->fw)); + lq_sta->lq.dual_stream_ant_msk = + iwl_fw_valid_tx_ant(mvm->fw) & + ~first_antenna(iwl_fw_valid_tx_ant(mvm->fw)); + if (!lq_sta->lq.dual_stream_ant_msk) { + lq_sta->lq.dual_stream_ant_msk = ANT_AB; + } else if (num_of_ant(iwl_fw_valid_tx_ant(mvm->fw)) == 2) { + lq_sta->lq.dual_stream_ant_msk = + iwl_fw_valid_tx_ant(mvm->fw); + } + + /* as default allow aggregation for all tids */ + lq_sta->tx_agg_tid_en = IWL_AGG_ALL_TID; + lq_sta->drv = mvm; + + /* Set last_txrate_idx to lowest rate */ + lq_sta->last_txrate_idx = rate_lowest_index(sband, sta); + if (sband->band == IEEE80211_BAND_5GHZ) + lq_sta->last_txrate_idx += IWL_FIRST_OFDM_RATE; + lq_sta->is_agg = 0; +#ifdef CPTCFG_MAC80211_DEBUGFS + lq_sta->dbg_fixed_rate = 0; +#endif + + rs_initialize_lq(mvm, sta, lq_sta, band); +} + +static void rs_fill_link_cmd(struct iwl_mvm *mvm, + struct iwl_lq_sta *lq_sta, u32 new_rate) +{ + struct iwl_scale_tbl_info tbl_type; + int index = 0; + int rate_idx; + int repeat_rate = 0; + u8 ant_toggle_cnt = 0; + u8 use_ht_possible = 1; + u8 valid_tx_ant = 0; + struct iwl_lq_cmd *lq_cmd = &lq_sta->lq; + + /* Override starting rate (index 0) if needed for debug purposes */ + rs_dbgfs_set_mcs(lq_sta, &new_rate, index); + + /* Interpret new_rate (rate_n_flags) */ + rs_get_tbl_info_from_mcs(new_rate, lq_sta->band, + &tbl_type, &rate_idx); + + /* How many times should we repeat the initial rate? */ + if (is_legacy(tbl_type.lq_type)) { + ant_toggle_cnt = 1; + repeat_rate = IWL_NUMBER_TRY; + } else { + repeat_rate = min(IWL_HT_NUMBER_TRY, + LINK_QUAL_AGG_DISABLE_START_DEF - 1); + } + + lq_cmd->mimo_delim = is_mimo(tbl_type.lq_type) ? 1 : 0; + + /* Fill 1st table entry (index 0) */ + lq_cmd->rs_table[index] = cpu_to_le32(new_rate); + + if (num_of_ant(tbl_type.ant_type) == 1) + lq_cmd->single_stream_ant_msk = tbl_type.ant_type; + else if (num_of_ant(tbl_type.ant_type) == 2) + lq_cmd->dual_stream_ant_msk = tbl_type.ant_type; + /* otherwise we don't modify the existing value */ + + index++; + repeat_rate--; + if (mvm) + valid_tx_ant = iwl_fw_valid_tx_ant(mvm->fw); + + /* Fill rest of rate table */ + while (index < LINK_QUAL_MAX_RETRY_NUM) { + /* Repeat initial/next rate. + * For legacy IWL_NUMBER_TRY == 1, this loop will not execute. + * For HT IWL_HT_NUMBER_TRY == 3, this executes twice. */ + while (repeat_rate > 0 && (index < LINK_QUAL_MAX_RETRY_NUM)) { + if (is_legacy(tbl_type.lq_type)) { + if (ant_toggle_cnt < NUM_TRY_BEFORE_ANT_TOGGLE) + ant_toggle_cnt++; + else if (mvm && + rs_toggle_antenna(valid_tx_ant, + &new_rate, &tbl_type)) + ant_toggle_cnt = 1; + } + + /* Override next rate if needed for debug purposes */ + rs_dbgfs_set_mcs(lq_sta, &new_rate, index); + + /* Fill next table entry */ + lq_cmd->rs_table[index] = + cpu_to_le32(new_rate); + repeat_rate--; + index++; + } + + rs_get_tbl_info_from_mcs(new_rate, lq_sta->band, &tbl_type, + &rate_idx); + + + /* Indicate to uCode which entries might be MIMO. + * If initial rate was MIMO, this will finally end up + * as (IWL_HT_NUMBER_TRY * 2), after 2nd pass, otherwise 0. */ + if (is_mimo(tbl_type.lq_type)) + lq_cmd->mimo_delim = index; + + /* Get next rate */ + new_rate = rs_get_lower_rate(lq_sta, &tbl_type, rate_idx, + use_ht_possible); + + /* How many times should we repeat the next rate? */ + if (is_legacy(tbl_type.lq_type)) { + if (ant_toggle_cnt < NUM_TRY_BEFORE_ANT_TOGGLE) + ant_toggle_cnt++; + else if (mvm && + rs_toggle_antenna(valid_tx_ant, + &new_rate, &tbl_type)) + ant_toggle_cnt = 1; + + repeat_rate = IWL_NUMBER_TRY; + } else { + repeat_rate = IWL_HT_NUMBER_TRY; + } + + /* Don't allow HT rates after next pass. + * rs_get_lower_rate() will change type to LQ_A or LQ_G. */ + use_ht_possible = 0; + + /* Override next rate if needed for debug purposes */ + rs_dbgfs_set_mcs(lq_sta, &new_rate, index); + + /* Fill next table entry */ + lq_cmd->rs_table[index] = cpu_to_le32(new_rate); + + index++; + repeat_rate--; + } + + lq_cmd->agg_frame_cnt_limit = LINK_QUAL_AGG_FRAME_LIMIT_DEF; + lq_cmd->agg_disable_start_th = LINK_QUAL_AGG_DISABLE_START_DEF; + + lq_cmd->agg_time_limit = + cpu_to_le16(LINK_QUAL_AGG_TIME_LIMIT_DEF); +} + +static void *rs_alloc(struct ieee80211_hw *hw, struct dentry *debugfsdir) +{ + return hw->priv; +} +/* rate scale requires free function to be implemented */ +static void rs_free(void *mvm_rate) +{ + return; +} + +static void rs_free_sta(void *mvm_r, struct ieee80211_sta *sta, + void *mvm_sta) +{ + struct iwl_op_mode *op_mode __maybe_unused = mvm_r; + struct iwl_mvm *mvm __maybe_unused = IWL_OP_MODE_GET_MVM(op_mode); + + IWL_DEBUG_RATE(mvm, "enter\n"); + IWL_DEBUG_RATE(mvm, "leave\n"); +} + +#ifdef CPTCFG_MAC80211_DEBUGFS +static void rs_dbgfs_set_mcs(struct iwl_lq_sta *lq_sta, + u32 *rate_n_flags, int index) +{ + struct iwl_mvm *mvm; + u8 valid_tx_ant; + u8 ant_sel_tx; + + mvm = lq_sta->drv; + valid_tx_ant = iwl_fw_valid_tx_ant(mvm->fw); + if (lq_sta->dbg_fixed_rate) { + ant_sel_tx = + ((lq_sta->dbg_fixed_rate & RATE_MCS_ANT_ABC_MSK) + >> RATE_MCS_ANT_POS); + if ((valid_tx_ant & ant_sel_tx) == ant_sel_tx) { + *rate_n_flags = lq_sta->dbg_fixed_rate; + IWL_DEBUG_RATE(mvm, "Fixed rate ON\n"); + } else { + lq_sta->dbg_fixed_rate = 0; + IWL_ERR(mvm, + "Invalid antenna selection 0x%X, Valid is 0x%X\n", + ant_sel_tx, valid_tx_ant); + IWL_DEBUG_RATE(mvm, "Fixed rate OFF\n"); + } + } else { + IWL_DEBUG_RATE(mvm, "Fixed rate OFF\n"); + } +} + +static ssize_t rs_sta_dbgfs_scale_table_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct iwl_lq_sta *lq_sta = file->private_data; + struct iwl_mvm *mvm; + char buf[64]; + size_t buf_size; + u32 parsed_rate; + + + mvm = lq_sta->drv; + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (sscanf(buf, "%x", &parsed_rate) == 1) + lq_sta->dbg_fixed_rate = parsed_rate; + else + lq_sta->dbg_fixed_rate = 0; + + rs_program_fix_rate(mvm, lq_sta); + + return count; +} + +static ssize_t rs_sta_dbgfs_scale_table_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + char *buff; + int desc = 0; + int i = 0; + int index = 0; + ssize_t ret; + + struct iwl_lq_sta *lq_sta = file->private_data; + struct iwl_mvm *mvm; + struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + + mvm = lq_sta->drv; + buff = kmalloc(1024, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + desc += sprintf(buff+desc, "sta_id %d\n", lq_sta->lq.sta_id); + desc += sprintf(buff+desc, "failed=%d success=%d rate=0%X\n", + lq_sta->total_failed, lq_sta->total_success, + lq_sta->active_legacy_rate); + desc += sprintf(buff+desc, "fixed rate 0x%X\n", + lq_sta->dbg_fixed_rate); + desc += sprintf(buff+desc, "valid_tx_ant %s%s%s\n", + (iwl_fw_valid_tx_ant(mvm->fw) & ANT_A) ? "ANT_A," : "", + (iwl_fw_valid_tx_ant(mvm->fw) & ANT_B) ? "ANT_B," : "", + (iwl_fw_valid_tx_ant(mvm->fw) & ANT_C) ? "ANT_C" : ""); + desc += sprintf(buff+desc, "lq type %s\n", + (is_legacy(tbl->lq_type)) ? "legacy" : "HT"); + if (is_Ht(tbl->lq_type)) { + desc += sprintf(buff+desc, " %s", + (is_siso(tbl->lq_type)) ? "SISO" : + ((is_mimo2(tbl->lq_type)) ? "MIMO2" : "MIMO3")); + desc += sprintf(buff+desc, " %s", + (tbl->is_ht40) ? "40MHz" : "20MHz"); + desc += sprintf(buff+desc, " %s %s %s\n", + (tbl->is_SGI) ? "SGI" : "", + (lq_sta->is_green) ? "GF enabled" : "", + (lq_sta->is_agg) ? "AGG on" : ""); + } + desc += sprintf(buff+desc, "last tx rate=0x%X\n", + lq_sta->last_rate_n_flags); + desc += sprintf(buff+desc, + "general: flags=0x%X mimo-d=%d s-ant0x%x d-ant=0x%x\n", + lq_sta->lq.flags, + lq_sta->lq.mimo_delim, + lq_sta->lq.single_stream_ant_msk, + lq_sta->lq.dual_stream_ant_msk); + + desc += sprintf(buff+desc, + "agg: time_limit=%d dist_start_th=%d frame_cnt_limit=%d\n", + le16_to_cpu(lq_sta->lq.agg_time_limit), + lq_sta->lq.agg_disable_start_th, + lq_sta->lq.agg_frame_cnt_limit); + + desc += sprintf(buff+desc, + "Start idx [0]=0x%x [1]=0x%x [2]=0x%x [3]=0x%x\n", + lq_sta->lq.initial_rate_index[0], + lq_sta->lq.initial_rate_index[1], + lq_sta->lq.initial_rate_index[2], + lq_sta->lq.initial_rate_index[3]); + + for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) { + index = iwl_hwrate_to_plcp_idx( + le32_to_cpu(lq_sta->lq.rs_table[i])); + if (is_legacy(tbl->lq_type)) { + desc += sprintf(buff+desc, " rate[%d] 0x%X %smbps\n", + i, le32_to_cpu(lq_sta->lq.rs_table[i]), + iwl_rate_mcs[index].mbps); + } else { + desc += sprintf(buff+desc, + " rate[%d] 0x%X %smbps (%s)\n", + i, le32_to_cpu(lq_sta->lq.rs_table[i]), + iwl_rate_mcs[index].mbps, + iwl_rate_mcs[index].mcs); + } + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buff, desc); + kfree(buff); + return ret; +} + +static const struct file_operations rs_sta_dbgfs_scale_table_ops = { + .write = rs_sta_dbgfs_scale_table_write, + .read = rs_sta_dbgfs_scale_table_read, + .open = simple_open, + .llseek = default_llseek, +}; +static ssize_t rs_sta_dbgfs_stats_table_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + char *buff; + int desc = 0; + int i, j; + ssize_t ret; + + struct iwl_lq_sta *lq_sta = file->private_data; + + buff = kmalloc(1024, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + for (i = 0; i < LQ_SIZE; i++) { + desc += sprintf(buff+desc, + "%s type=%d SGI=%d HT40=%d DUP=0 GF=%d\n" + "rate=0x%X\n", + lq_sta->active_tbl == i ? "*" : "x", + lq_sta->lq_info[i].lq_type, + lq_sta->lq_info[i].is_SGI, + lq_sta->lq_info[i].is_ht40, + lq_sta->is_green, + lq_sta->lq_info[i].current_rate); + for (j = 0; j < IWL_RATE_COUNT; j++) { + desc += sprintf(buff+desc, + "counter=%d success=%d %%=%d\n", + lq_sta->lq_info[i].win[j].counter, + lq_sta->lq_info[i].win[j].success_counter, + lq_sta->lq_info[i].win[j].success_ratio); + } + } + ret = simple_read_from_buffer(user_buf, count, ppos, buff, desc); + kfree(buff); + return ret; +} + +static const struct file_operations rs_sta_dbgfs_stats_table_ops = { + .read = rs_sta_dbgfs_stats_table_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t rs_sta_dbgfs_rate_scale_data_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + struct iwl_lq_sta *lq_sta = file->private_data; + struct iwl_scale_tbl_info *tbl = &lq_sta->lq_info[lq_sta->active_tbl]; + char buff[120]; + int desc = 0; + + if (is_Ht(tbl->lq_type)) + desc += sprintf(buff+desc, + "Bit Rate= %d Mb/s\n", + tbl->expected_tpt[lq_sta->last_txrate_idx]); + else + desc += sprintf(buff+desc, + "Bit Rate= %d Mb/s\n", + iwl_rates[lq_sta->last_txrate_idx].ieee >> 1); + + return simple_read_from_buffer(user_buf, count, ppos, buff, desc); +} + +static const struct file_operations rs_sta_dbgfs_rate_scale_data_ops = { + .read = rs_sta_dbgfs_rate_scale_data_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static void rs_add_debugfs(void *mvm, void *mvm_sta, struct dentry *dir) +{ + struct iwl_lq_sta *lq_sta = mvm_sta; + lq_sta->rs_sta_dbgfs_scale_table_file = + debugfs_create_file("rate_scale_table", S_IRUSR | S_IWUSR, dir, + lq_sta, &rs_sta_dbgfs_scale_table_ops); + lq_sta->rs_sta_dbgfs_stats_table_file = + debugfs_create_file("rate_stats_table", S_IRUSR, dir, + lq_sta, &rs_sta_dbgfs_stats_table_ops); + lq_sta->rs_sta_dbgfs_rate_scale_data_file = + debugfs_create_file("rate_scale_data", S_IRUSR, dir, + lq_sta, &rs_sta_dbgfs_rate_scale_data_ops); + lq_sta->rs_sta_dbgfs_tx_agg_tid_en_file = + debugfs_create_u8("tx_agg_tid_enable", S_IRUSR | S_IWUSR, dir, + &lq_sta->tx_agg_tid_en); +} + +static void rs_remove_debugfs(void *mvm, void *mvm_sta) +{ + struct iwl_lq_sta *lq_sta = mvm_sta; + debugfs_remove(lq_sta->rs_sta_dbgfs_scale_table_file); + debugfs_remove(lq_sta->rs_sta_dbgfs_stats_table_file); + debugfs_remove(lq_sta->rs_sta_dbgfs_rate_scale_data_file); + debugfs_remove(lq_sta->rs_sta_dbgfs_tx_agg_tid_en_file); +} +#endif + +/* + * Initialization of rate scaling information is done by driver after + * the station is added. Since mac80211 calls this function before a + * station is added we ignore it. + */ +static void rs_rate_init_stub(void *mvm_r, + struct ieee80211_supported_band *sband, + struct ieee80211_sta *sta, void *mvm_sta) +{ +} +static struct rate_control_ops rs_mvm_ops = { + .module = NULL, + .name = RS_NAME, + .tx_status = rs_tx_status, + .get_rate = rs_get_rate, + .rate_init = rs_rate_init_stub, + .alloc = rs_alloc, + .free = rs_free, + .alloc_sta = rs_alloc_sta, + .free_sta = rs_free_sta, +#ifdef CPTCFG_MAC80211_DEBUGFS + .add_sta_debugfs = rs_add_debugfs, + .remove_sta_debugfs = rs_remove_debugfs, +#endif +}; + +int iwl_mvm_rate_control_register(void) +{ + return ieee80211_rate_control_register(&rs_mvm_ops); +} + +void iwl_mvm_rate_control_unregister(void) +{ + ieee80211_rate_control_unregister(&rs_mvm_ops); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/rs.h b/drivers/net/wireless/iwlwifi/mvm/rs.h new file mode 100644 index 0000000..a3d4803 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/rs.h @@ -0,0 +1,393 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#ifndef __rs_h__ +#define __rs_h__ + +#include + +#include "iwl-config.h" + +#include "fw-api.h" +#include "iwl-trans.h" + +struct iwl_rs_rate_info { + u8 plcp; /* uCode API: IWL_RATE_6M_PLCP, etc. */ + u8 plcp_siso; /* uCode API: IWL_RATE_SISO_6M_PLCP, etc. */ + u8 plcp_mimo2; /* uCode API: IWL_RATE_MIMO2_6M_PLCP, etc. */ + u8 plcp_mimo3; /* uCode API: IWL_RATE_MIMO3_6M_PLCP, etc. */ + u8 ieee; /* MAC header: IWL_RATE_6M_IEEE, etc. */ + u8 prev_ieee; /* previous rate in IEEE speeds */ + u8 next_ieee; /* next rate in IEEE speeds */ + u8 prev_rs; /* previous rate used in rs algo */ + u8 next_rs; /* next rate used in rs algo */ + u8 prev_rs_tgg; /* previous rate used in TGG rs algo */ + u8 next_rs_tgg; /* next rate used in TGG rs algo */ +}; + +#define IWL_RATE_60M_PLCP 3 + +enum { + IWL_RATE_INVM_INDEX = IWL_RATE_COUNT, + IWL_RATE_INVALID = IWL_RATE_COUNT, +}; + +#define LINK_QUAL_MAX_RETRY_NUM 16 + +enum { + IWL_RATE_6M_INDEX_TABLE = 0, + IWL_RATE_9M_INDEX_TABLE, + IWL_RATE_12M_INDEX_TABLE, + IWL_RATE_18M_INDEX_TABLE, + IWL_RATE_24M_INDEX_TABLE, + IWL_RATE_36M_INDEX_TABLE, + IWL_RATE_48M_INDEX_TABLE, + IWL_RATE_54M_INDEX_TABLE, + IWL_RATE_1M_INDEX_TABLE, + IWL_RATE_2M_INDEX_TABLE, + IWL_RATE_5M_INDEX_TABLE, + IWL_RATE_11M_INDEX_TABLE, + IWL_RATE_INVM_INDEX_TABLE = IWL_RATE_INVM_INDEX - 1, +}; + +/* #define vs. enum to keep from defaulting to 'large integer' */ +#define IWL_RATE_6M_MASK (1 << IWL_RATE_6M_INDEX) +#define IWL_RATE_9M_MASK (1 << IWL_RATE_9M_INDEX) +#define IWL_RATE_12M_MASK (1 << IWL_RATE_12M_INDEX) +#define IWL_RATE_18M_MASK (1 << IWL_RATE_18M_INDEX) +#define IWL_RATE_24M_MASK (1 << IWL_RATE_24M_INDEX) +#define IWL_RATE_36M_MASK (1 << IWL_RATE_36M_INDEX) +#define IWL_RATE_48M_MASK (1 << IWL_RATE_48M_INDEX) +#define IWL_RATE_54M_MASK (1 << IWL_RATE_54M_INDEX) +#define IWL_RATE_60M_MASK (1 << IWL_RATE_60M_INDEX) +#define IWL_RATE_1M_MASK (1 << IWL_RATE_1M_INDEX) +#define IWL_RATE_2M_MASK (1 << IWL_RATE_2M_INDEX) +#define IWL_RATE_5M_MASK (1 << IWL_RATE_5M_INDEX) +#define IWL_RATE_11M_MASK (1 << IWL_RATE_11M_INDEX) + + +/* uCode API values for OFDM high-throughput (HT) bit rates */ +enum { + IWL_RATE_SISO_6M_PLCP = 0, + IWL_RATE_SISO_12M_PLCP = 1, + IWL_RATE_SISO_18M_PLCP = 2, + IWL_RATE_SISO_24M_PLCP = 3, + IWL_RATE_SISO_36M_PLCP = 4, + IWL_RATE_SISO_48M_PLCP = 5, + IWL_RATE_SISO_54M_PLCP = 6, + IWL_RATE_SISO_60M_PLCP = 7, + IWL_RATE_MIMO2_6M_PLCP = 0x8, + IWL_RATE_MIMO2_12M_PLCP = 0x9, + IWL_RATE_MIMO2_18M_PLCP = 0xa, + IWL_RATE_MIMO2_24M_PLCP = 0xb, + IWL_RATE_MIMO2_36M_PLCP = 0xc, + IWL_RATE_MIMO2_48M_PLCP = 0xd, + IWL_RATE_MIMO2_54M_PLCP = 0xe, + IWL_RATE_MIMO2_60M_PLCP = 0xf, + IWL_RATE_MIMO3_6M_PLCP = 0x10, + IWL_RATE_MIMO3_12M_PLCP = 0x11, + IWL_RATE_MIMO3_18M_PLCP = 0x12, + IWL_RATE_MIMO3_24M_PLCP = 0x13, + IWL_RATE_MIMO3_36M_PLCP = 0x14, + IWL_RATE_MIMO3_48M_PLCP = 0x15, + IWL_RATE_MIMO3_54M_PLCP = 0x16, + IWL_RATE_MIMO3_60M_PLCP = 0x17, + IWL_RATE_SISO_INVM_PLCP, + IWL_RATE_MIMO2_INVM_PLCP = IWL_RATE_SISO_INVM_PLCP, + IWL_RATE_MIMO3_INVM_PLCP = IWL_RATE_SISO_INVM_PLCP, +}; + +/* MAC header values for bit rates */ +enum { + IWL_RATE_6M_IEEE = 12, + IWL_RATE_9M_IEEE = 18, + IWL_RATE_12M_IEEE = 24, + IWL_RATE_18M_IEEE = 36, + IWL_RATE_24M_IEEE = 48, + IWL_RATE_36M_IEEE = 72, + IWL_RATE_48M_IEEE = 96, + IWL_RATE_54M_IEEE = 108, + IWL_RATE_60M_IEEE = 120, + IWL_RATE_1M_IEEE = 2, + IWL_RATE_2M_IEEE = 4, + IWL_RATE_5M_IEEE = 11, + IWL_RATE_11M_IEEE = 22, +}; + +#define IWL_RATES_MASK ((1 << IWL_RATE_COUNT) - 1) + +#define IWL_INVALID_VALUE -1 + +#define IWL_MIN_RSSI_VAL -100 +#define IWL_MAX_RSSI_VAL 0 + +/* These values specify how many Tx frame attempts before + * searching for a new modulation mode */ +#define IWL_LEGACY_FAILURE_LIMIT 160 +#define IWL_LEGACY_SUCCESS_LIMIT 480 +#define IWL_LEGACY_TABLE_COUNT 160 + +#define IWL_NONE_LEGACY_FAILURE_LIMIT 400 +#define IWL_NONE_LEGACY_SUCCESS_LIMIT 4500 +#define IWL_NONE_LEGACY_TABLE_COUNT 1500 + +/* Success ratio (ACKed / attempted tx frames) values (perfect is 128 * 100) */ +#define IWL_RS_GOOD_RATIO 12800 /* 100% */ +#define IWL_RATE_SCALE_SWITCH 10880 /* 85% */ +#define IWL_RATE_HIGH_TH 10880 /* 85% */ +#define IWL_RATE_INCREASE_TH 6400 /* 50% */ +#define IWL_RATE_DECREASE_TH 1920 /* 15% */ + +/* possible actions when in legacy mode */ +#define IWL_LEGACY_SWITCH_ANTENNA1 0 +#define IWL_LEGACY_SWITCH_ANTENNA2 1 +#define IWL_LEGACY_SWITCH_SISO 2 +#define IWL_LEGACY_SWITCH_MIMO2_AB 3 +#define IWL_LEGACY_SWITCH_MIMO2_AC 4 +#define IWL_LEGACY_SWITCH_MIMO2_BC 5 +#define IWL_LEGACY_SWITCH_MIMO3_ABC 6 + +/* possible actions when in siso mode */ +#define IWL_SISO_SWITCH_ANTENNA1 0 +#define IWL_SISO_SWITCH_ANTENNA2 1 +#define IWL_SISO_SWITCH_MIMO2_AB 2 +#define IWL_SISO_SWITCH_MIMO2_AC 3 +#define IWL_SISO_SWITCH_MIMO2_BC 4 +#define IWL_SISO_SWITCH_GI 5 +#define IWL_SISO_SWITCH_MIMO3_ABC 6 + + +/* possible actions when in mimo mode */ +#define IWL_MIMO2_SWITCH_ANTENNA1 0 +#define IWL_MIMO2_SWITCH_ANTENNA2 1 +#define IWL_MIMO2_SWITCH_SISO_A 2 +#define IWL_MIMO2_SWITCH_SISO_B 3 +#define IWL_MIMO2_SWITCH_SISO_C 4 +#define IWL_MIMO2_SWITCH_GI 5 +#define IWL_MIMO2_SWITCH_MIMO3_ABC 6 + + +/* possible actions when in mimo3 mode */ +#define IWL_MIMO3_SWITCH_ANTENNA1 0 +#define IWL_MIMO3_SWITCH_ANTENNA2 1 +#define IWL_MIMO3_SWITCH_SISO_A 2 +#define IWL_MIMO3_SWITCH_SISO_B 3 +#define IWL_MIMO3_SWITCH_SISO_C 4 +#define IWL_MIMO3_SWITCH_MIMO2_AB 5 +#define IWL_MIMO3_SWITCH_MIMO2_AC 6 +#define IWL_MIMO3_SWITCH_MIMO2_BC 7 +#define IWL_MIMO3_SWITCH_GI 8 + + +#define IWL_MAX_11N_MIMO3_SEARCH IWL_MIMO3_SWITCH_GI +#define IWL_MAX_SEARCH IWL_MIMO2_SWITCH_MIMO3_ABC + +/*FIXME:RS:add possible actions for MIMO3*/ + +#define IWL_ACTION_LIMIT 3 /* # possible actions */ + +#define LINK_QUAL_AGG_TIME_LIMIT_DEF (4000) /* 4 milliseconds */ +#define LINK_QUAL_AGG_TIME_LIMIT_MAX (8000) +#define LINK_QUAL_AGG_TIME_LIMIT_MIN (100) + +#define LINK_QUAL_AGG_DISABLE_START_DEF (3) +#define LINK_QUAL_AGG_DISABLE_START_MAX (255) +#define LINK_QUAL_AGG_DISABLE_START_MIN (0) + +#define LINK_QUAL_AGG_FRAME_LIMIT_DEF (63) +#define LINK_QUAL_AGG_FRAME_LIMIT_MAX (63) +#define LINK_QUAL_AGG_FRAME_LIMIT_MIN (0) + +#define LQ_SIZE 2 /* 2 mode tables: "Active" and "Search" */ + +/* load per tid defines for A-MPDU activation */ +#define IWL_AGG_TPT_THREHOLD 0 +#define IWL_AGG_LOAD_THRESHOLD 10 +#define IWL_AGG_ALL_TID 0xff +#define TID_QUEUE_CELL_SPACING 50 /*mS */ +#define TID_QUEUE_MAX_SIZE 20 +#define TID_ROUND_VALUE 5 /* mS */ + +#define TID_MAX_TIME_DIFF ((TID_QUEUE_MAX_SIZE - 1) * TID_QUEUE_CELL_SPACING) +#define TIME_WRAP_AROUND(x, y) (((y) > (x)) ? (y) - (x) : (0-(x)) + (y)) + +enum iwl_table_type { + LQ_NONE, + LQ_G, /* legacy types */ + LQ_A, + LQ_SISO, /* high-throughput types */ + LQ_MIMO2, + LQ_MIMO3, + LQ_MAX, +}; + +#define is_legacy(tbl) (((tbl) == LQ_G) || ((tbl) == LQ_A)) +#define is_siso(tbl) ((tbl) == LQ_SISO) +#define is_mimo2(tbl) ((tbl) == LQ_MIMO2) +#define is_mimo3(tbl) ((tbl) == LQ_MIMO3) +#define is_mimo(tbl) (is_mimo2(tbl) || is_mimo3(tbl)) +#define is_Ht(tbl) (is_siso(tbl) || is_mimo(tbl)) +#define is_a_band(tbl) ((tbl) == LQ_A) +#define is_g_and(tbl) ((tbl) == LQ_G) + +#define IWL_MAX_MCS_DISPLAY_SIZE 12 + +struct iwl_rate_mcs_info { + char mbps[IWL_MAX_MCS_DISPLAY_SIZE]; + char mcs[IWL_MAX_MCS_DISPLAY_SIZE]; +}; + +/** + * struct iwl_rate_scale_data -- tx success history for one rate + */ +struct iwl_rate_scale_data { + u64 data; /* bitmap of successful frames */ + s32 success_counter; /* number of frames successful */ + s32 success_ratio; /* per-cent * 128 */ + s32 counter; /* number of frames attempted */ + s32 average_tpt; /* success ratio * expected throughput */ + unsigned long stamp; +}; + +/** + * struct iwl_scale_tbl_info -- tx params and success history for all rates + * + * There are two of these in struct iwl_lq_sta, + * one for "active", and one for "search". + */ +struct iwl_scale_tbl_info { + enum iwl_table_type lq_type; + u8 ant_type; + u8 is_SGI; /* 1 = short guard interval */ + u8 is_ht40; /* 1 = 40 MHz channel width */ + u8 action; /* change modulation; IWL_[LEGACY/SISO/MIMO]_SWITCH_* */ + u8 max_search; /* maximun number of tables we can search */ + s32 *expected_tpt; /* throughput metrics; expected_tpt_G, etc. */ + u32 current_rate; /* rate_n_flags, uCode API format */ + struct iwl_rate_scale_data win[IWL_RATE_COUNT]; /* rate histories */ +}; + +struct iwl_traffic_load { + unsigned long time_stamp; /* age of the oldest statistics */ + u32 packet_count[TID_QUEUE_MAX_SIZE]; /* packet count in this time + * slice */ + u32 total; /* total num of packets during the + * last TID_MAX_TIME_DIFF */ + u8 queue_count; /* number of queues that has + * been used since the last cleanup */ + u8 head; /* start of the circular buffer */ +}; + +/** + * struct iwl_lq_sta -- driver's rate scaling private structure + * + * Pointer to this gets passed back and forth between driver and mac80211. + */ +struct iwl_lq_sta { + u8 active_tbl; /* index of active table, range 0-1 */ + u8 enable_counter; /* indicates HT mode */ + u8 stay_in_tbl; /* 1: disallow, 0: allow search for new mode */ + u8 search_better_tbl; /* 1: currently trying alternate mode */ + s32 last_tpt; + + /* The following determine when to search for a new mode */ + u32 table_count_limit; + u32 max_failure_limit; /* # failed frames before new search */ + u32 max_success_limit; /* # successful frames before new search */ + u32 table_count; + u32 total_failed; /* total failed frames, any/all rates */ + u32 total_success; /* total successful frames, any/all rates */ + u64 flush_timer; /* time staying in mode before new search */ + + u8 action_counter; /* # mode-switch actions tried */ + u8 is_green; + enum ieee80211_band band; + + /* The following are bitmaps of rates; IWL_RATE_6M_MASK, etc. */ + u32 supp_rates; + u16 active_legacy_rate; + u16 active_siso_rate; + u16 active_mimo2_rate; + u16 active_mimo3_rate; + s8 max_rate_idx; /* Max rate set by user */ + u8 missed_rate_counter; + + struct iwl_lq_cmd lq; + struct iwl_scale_tbl_info lq_info[LQ_SIZE]; /* "active", "search" */ + struct iwl_traffic_load load[IWL_MAX_TID_COUNT]; + u8 tx_agg_tid_en; +#ifdef CPTCFG_MAC80211_DEBUGFS + struct dentry *rs_sta_dbgfs_scale_table_file; + struct dentry *rs_sta_dbgfs_stats_table_file; + struct dentry *rs_sta_dbgfs_rate_scale_data_file; + struct dentry *rs_sta_dbgfs_tx_agg_tid_en_file; + u32 dbg_fixed_rate; +#endif + struct iwl_mvm *drv; + + /* used to be in sta_info */ + int last_txrate_idx; + /* last tx rate_n_flags */ + u32 last_rate_n_flags; + /* packets destined for this STA are aggregated */ + u8 is_agg; + /* BT traffic this sta was last updated in */ + u8 last_bt_traffic; +}; + +static inline u8 num_of_ant(u8 mask) +{ + return !!((mask) & ANT_A) + + !!((mask) & ANT_B) + + !!((mask) & ANT_C); +} + +/* Initialize station's rate scaling information after adding station */ +extern void iwl_mvm_rs_rate_init(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + enum ieee80211_band band); + +/** + * iwl_rate_control_register - Register the rate control algorithm callbacks + * + * Since the rate control algorithm is hardware specific, there is no need + * or reason to place it as a stand alone module. The driver can call + * iwl_rate_control_register in order to register the rate control callbacks + * with the mac80211 subsystem. This should be performed prior to calling + * ieee80211_register_hw + * + */ +extern int iwl_mvm_rate_control_register(void); + +/** + * iwl_rate_control_unregister - Unregister the rate control callbacks + * + * This should be called after calling ieee80211_unregister_hw, but before + * the driver is unloaded. + */ +extern void iwl_mvm_rate_control_unregister(void); + +#endif /* __rs__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/rx.c b/drivers/net/wireless/iwlwifi/mvm/rx.c new file mode 100644 index 0000000..4dfc21a --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/rx.c @@ -0,0 +1,365 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ +#include "iwl-trans.h" + +#include "mvm.h" +#include "fw-api.h" + +/* + * iwl_mvm_rx_rx_phy_cmd - REPLY_RX_PHY_CMD handler + * + * Copies the phy information in mvm->last_phy_info, it will be used when the + * actual data will come from the fw in the next packet. + */ +int iwl_mvm_rx_rx_phy_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + + memcpy(&mvm->last_phy_info, pkt->data, sizeof(mvm->last_phy_info)); + mvm->ampdu_ref++; + return 0; +} + +/* + * iwl_mvm_pass_packet_to_mac80211 - builds the packet for mac80211 + * + * Adds the rxb to a new skb and give it to mac80211 + */ +static void iwl_mvm_pass_packet_to_mac80211(struct iwl_mvm *mvm, + struct ieee80211_hdr *hdr, u16 len, + u32 ampdu_status, + struct iwl_rx_cmd_buffer *rxb, + struct ieee80211_rx_status *stats) +{ + struct sk_buff *skb; + unsigned int hdrlen, fraglen; + + /* Dont use dev_alloc_skb(), we'll have enough headroom once + * ieee80211_hdr pulled. + */ + skb = alloc_skb(128, GFP_ATOMIC); + if (!skb) { + IWL_ERR(mvm, "alloc_skb failed\n"); + return; + } + /* If frame is small enough to fit in skb->head, pull it completely. + * If not, only pull ieee80211_hdr so that splice() or TCP coalesce + * are more efficient. + */ + hdrlen = (len <= skb_tailroom(skb)) ? len : sizeof(*hdr); + + memcpy(skb_put(skb, hdrlen), hdr, hdrlen); + fraglen = len - hdrlen; + + if (fraglen) { + int offset = (void *)hdr + hdrlen - + rxb_addr(rxb) + rxb_offset(rxb); + + skb_add_rx_frag(skb, 0, rxb_steal_page(rxb), offset, + fraglen, rxb->truesize); + } + + memcpy(IEEE80211_SKB_RXCB(skb), stats, sizeof(*stats)); + + ieee80211_rx_ni(mvm->hw, skb); +} + +/* + * iwl_mvm_calc_rssi - calculate the rssi in dBm + * @phy_info: the phy information for the coming packet + */ +static int iwl_mvm_calc_rssi(struct iwl_mvm *mvm, + struct iwl_rx_phy_info *phy_info) +{ + int rssi_a, rssi_b, rssi_a_dbm, rssi_b_dbm, max_rssi_dbm; + int rssi_all_band_a, rssi_all_band_b; + u32 agc_a, agc_b, max_agc; + u32 val; + + /* Find max rssi among 2 possible receivers. + * These values are measured by the Digital Signal Processor (DSP). + * They should stay fairly constant even as the signal strength varies, + * if the radio's Automatic Gain Control (AGC) is working right. + * AGC value (see below) will provide the "interesting" info. + */ + val = le32_to_cpu(phy_info->non_cfg_phy[IWL_RX_INFO_AGC_IDX]); + agc_a = (val & IWL_OFDM_AGC_A_MSK) >> IWL_OFDM_AGC_A_POS; + agc_b = (val & IWL_OFDM_AGC_B_MSK) >> IWL_OFDM_AGC_B_POS; + max_agc = max_t(u32, agc_a, agc_b); + + val = le32_to_cpu(phy_info->non_cfg_phy[IWL_RX_INFO_RSSI_AB_IDX]); + rssi_a = (val & IWL_OFDM_RSSI_INBAND_A_MSK) >> IWL_OFDM_RSSI_A_POS; + rssi_b = (val & IWL_OFDM_RSSI_INBAND_B_MSK) >> IWL_OFDM_RSSI_B_POS; + rssi_all_band_a = (val & IWL_OFDM_RSSI_ALLBAND_A_MSK) >> + IWL_OFDM_RSSI_ALLBAND_A_POS; + rssi_all_band_b = (val & IWL_OFDM_RSSI_ALLBAND_B_MSK) >> + IWL_OFDM_RSSI_ALLBAND_B_POS; + + /* + * dBm = rssi dB - agc dB - constant. + * Higher AGC (higher radio gain) means lower signal. + */ + rssi_a_dbm = rssi_a - IWL_RSSI_OFFSET - agc_a; + rssi_b_dbm = rssi_b - IWL_RSSI_OFFSET - agc_b; + max_rssi_dbm = max_t(int, rssi_a_dbm, rssi_b_dbm); + + IWL_DEBUG_STATS(mvm, "Rssi In A %d B %d Max %d AGCA %d AGCB %d\n", + rssi_a_dbm, rssi_b_dbm, max_rssi_dbm, agc_a, agc_b); + + return max_rssi_dbm; +} + +/* + * iwl_mvm_set_mac80211_rx_flag - translate fw status to mac80211 format + * @mvm: the mvm object + * @hdr: 80211 header + * @stats: status in mac80211's format + * @rx_pkt_status: status coming from fw + * + * returns non 0 value if the packet should be dropped + */ +static u32 iwl_mvm_set_mac80211_rx_flag(struct iwl_mvm *mvm, + struct ieee80211_hdr *hdr, + struct ieee80211_rx_status *stats, + u32 rx_pkt_status) +{ + if (!ieee80211_has_protected(hdr->frame_control) || + (rx_pkt_status & RX_MPDU_RES_STATUS_SEC_ENC_MSK) == + RX_MPDU_RES_STATUS_SEC_NO_ENC) + return 0; + + /* packet was encrypted with unknown alg */ + if ((rx_pkt_status & RX_MPDU_RES_STATUS_SEC_ENC_MSK) == + RX_MPDU_RES_STATUS_SEC_ENC_ERR) + return 0; + + switch (rx_pkt_status & RX_MPDU_RES_STATUS_SEC_ENC_MSK) { + case RX_MPDU_RES_STATUS_SEC_CCM_ENC: + /* alg is CCM: check MIC only */ + if (!(rx_pkt_status & RX_MPDU_RES_STATUS_MIC_OK)) + return -1; + + stats->flag |= RX_FLAG_DECRYPTED; + IWL_DEBUG_WEP(mvm, "hw decrypted CCMP successfully\n"); + return 0; + + case RX_MPDU_RES_STATUS_SEC_TKIP_ENC: + /* Don't drop the frame and decrypt it in SW */ + if (!(rx_pkt_status & RX_MPDU_RES_STATUS_TTAK_OK)) + return 0; + /* fall through if TTAK OK */ + + case RX_MPDU_RES_STATUS_SEC_WEP_ENC: + if (!(rx_pkt_status & RX_MPDU_RES_STATUS_ICV_OK)) + return -1; + + stats->flag |= RX_FLAG_DECRYPTED; + return 0; + + default: + IWL_ERR(mvm, "Unhandled alg: 0x%x\n", rx_pkt_status); + } + + return 0; +} + +/* + * iwl_mvm_rx_rx_mpdu - REPLY_RX_MPDU_CMD handler + * + * Handles the actual data of the Rx packet from the fw + */ +int iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct ieee80211_hdr *hdr; + struct ieee80211_rx_status rx_status = {}; + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_rx_phy_info *phy_info; + struct iwl_rx_mpdu_res_start *rx_res; + u32 len; + u32 ampdu_status; + u32 rate_n_flags; + u32 rx_pkt_status; + + phy_info = &mvm->last_phy_info; + rx_res = (struct iwl_rx_mpdu_res_start *)pkt->data; + hdr = (struct ieee80211_hdr *)(pkt->data + sizeof(*rx_res)); + len = le16_to_cpu(rx_res->byte_count); + rx_pkt_status = le32_to_cpup((__le32 *) + (pkt->data + sizeof(*rx_res) + len)); + + memset(&rx_status, 0, sizeof(rx_status)); + + /* + * drop the packet if it has failed being decrypted by HW + */ + if (iwl_mvm_set_mac80211_rx_flag(mvm, hdr, &rx_status, rx_pkt_status)) { + IWL_DEBUG_DROP(mvm, "Bad decryption results 0x%08x\n", + rx_pkt_status); + return 0; + } + + if ((unlikely(phy_info->cfg_phy_cnt > 20))) { + IWL_DEBUG_DROP(mvm, "dsp size out of range [0,20]: %d\n", + phy_info->cfg_phy_cnt); + return 0; + } + + if (!(rx_pkt_status & RX_MPDU_RES_STATUS_CRC_OK) || + !(rx_pkt_status & RX_MPDU_RES_STATUS_OVERRUN_OK)) { + IWL_DEBUG_RX(mvm, "Bad CRC or FIFO: 0x%08X.\n", rx_pkt_status); + return 0; + } + + /* This will be used in several places later */ + rate_n_flags = le32_to_cpu(phy_info->rate_n_flags); + + /* rx_status carries information about the packet to mac80211 */ + rx_status.mactime = le64_to_cpu(phy_info->timestamp); + rx_status.device_timestamp = le32_to_cpu(phy_info->system_timestamp); + rx_status.band = + (phy_info->phy_flags & cpu_to_le16(RX_RES_PHY_FLAGS_BAND_24)) ? + IEEE80211_BAND_2GHZ : IEEE80211_BAND_5GHZ; + rx_status.freq = + ieee80211_channel_to_frequency(le16_to_cpu(phy_info->channel), + rx_status.band); + /* + * TSF as indicated by the fw is at INA time, but mac80211 expects the + * TSF at the beginning of the MPDU. + */ + /*rx_status.flag |= RX_FLAG_MACTIME_MPDU;*/ + + /* Find max signal strength (dBm) among 3 antenna/receiver chains */ + rx_status.signal = iwl_mvm_calc_rssi(mvm, phy_info); + + IWL_DEBUG_STATS_LIMIT(mvm, "Rssi %d, TSF %llu\n", rx_status.signal, + (unsigned long long)rx_status.mactime); + + /* + * "antenna number" + * + * It seems that the antenna field in the phy flags value + * is actually a bit field. This is undefined by radiotap, + * it wants an actual antenna number but I always get "7" + * for most legacy frames I receive indicating that the + * same frame was received on all three RX chains. + * + * I think this field should be removed in favor of a + * new 802.11n radiotap field "RX chains" that is defined + * as a bitmask. + */ + rx_status.antenna = (le16_to_cpu(phy_info->phy_flags) & + RX_RES_PHY_FLAGS_ANTENNA) + >> RX_RES_PHY_FLAGS_ANTENNA_POS; + + /* set the preamble flag if appropriate */ + if (phy_info->phy_flags & cpu_to_le16(RX_RES_PHY_FLAGS_SHORT_PREAMBLE)) + rx_status.flag |= RX_FLAG_SHORTPRE; + + if (phy_info->phy_flags & cpu_to_le16(RX_RES_PHY_FLAGS_AGG)) { + /* + * We know which subframes of an A-MPDU belong + * together since we get a single PHY response + * from the firmware for all of them + */ + rx_status.flag |= RX_FLAG_AMPDU_DETAILS; + rx_status.ampdu_reference = mvm->ampdu_ref; + } + + /* Set up the HT phy flags */ + switch (rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK) { + case RATE_MCS_CHAN_WIDTH_20: + break; + case RATE_MCS_CHAN_WIDTH_40: + rx_status.flag |= RX_FLAG_40MHZ; + break; + case RATE_MCS_CHAN_WIDTH_80: + rx_status.flag |= RX_FLAG_80MHZ; + break; + case RATE_MCS_CHAN_WIDTH_160: + rx_status.flag |= RX_FLAG_160MHZ; + break; + } + if (rate_n_flags & RATE_MCS_SGI_MSK) + rx_status.flag |= RX_FLAG_SHORT_GI; + if (rate_n_flags & RATE_HT_MCS_GF_MSK) + rx_status.flag |= RX_FLAG_HT_GF; + if (rate_n_flags & RATE_MCS_HT_MSK) { + rx_status.flag |= RX_FLAG_HT; + rx_status.rate_idx = rate_n_flags & RATE_HT_MCS_INDEX_MSK; + } else if (rate_n_flags & RATE_MCS_VHT_MSK) { + rx_status.vht_nss = + ((rate_n_flags & RATE_VHT_MCS_NSS_MSK) >> + RATE_VHT_MCS_NSS_POS) + 1; + rx_status.rate_idx = rate_n_flags & RATE_VHT_MCS_RATE_CODE_MSK; + rx_status.flag |= RX_FLAG_VHT; + } else { + rx_status.rate_idx = + iwl_mvm_legacy_rate_to_mac80211_idx(rate_n_flags, + rx_status.band); + } + + iwl_mvm_pass_packet_to_mac80211(mvm, hdr, len, ampdu_status, + rxb, &rx_status); + return 0; +} diff --git a/drivers/net/wireless/iwlwifi/mvm/scan.c b/drivers/net/wireless/iwlwifi/mvm/scan.c new file mode 100644 index 0000000..8e1f6c0 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/scan.c @@ -0,0 +1,441 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#include +#include + +#include "mvm.h" +#include "iwl-eeprom-parse.h" +#include "fw-api-scan.h" + +#define IWL_PLCP_QUIET_THRESH 1 +#define IWL_ACTIVE_QUIET_TIME 10 + +static inline __le16 iwl_mvm_scan_rx_chain(struct iwl_mvm *mvm) +{ + u16 rx_chain; + u8 rx_ant = iwl_fw_valid_rx_ant(mvm->fw); + + rx_chain = rx_ant << PHY_RX_CHAIN_VALID_POS; + rx_chain |= rx_ant << PHY_RX_CHAIN_FORCE_MIMO_SEL_POS; + rx_chain |= rx_ant << PHY_RX_CHAIN_FORCE_SEL_POS; + rx_chain |= 0x1 << PHY_RX_CHAIN_DRIVER_FORCE_POS; + return cpu_to_le16(rx_chain); +} + +static inline __le32 iwl_mvm_scan_max_out_time(struct ieee80211_vif *vif) +{ + if (vif->bss_conf.assoc) + return cpu_to_le32(200 * 1024); + else + return 0; +} + +static inline __le32 iwl_mvm_scan_suspend_time(struct ieee80211_vif *vif) +{ + if (vif->bss_conf.assoc) + return cpu_to_le32(vif->bss_conf.beacon_int); + else + return 0; +} + +static inline __le32 +iwl_mvm_scan_rxon_flags(struct cfg80211_scan_request *req) +{ + if (req->channels[0]->band == IEEE80211_BAND_2GHZ) + return cpu_to_le32(PHY_BAND_24); + else + return cpu_to_le32(PHY_BAND_5); +} + +static inline __le32 +iwl_mvm_scan_rate_n_flags(struct iwl_mvm *mvm, enum ieee80211_band band, + bool no_cck) +{ + u32 tx_ant; + + mvm->scan_last_antenna_idx = + iwl_mvm_next_antenna(mvm, iwl_fw_valid_tx_ant(mvm->fw), + mvm->scan_last_antenna_idx); + tx_ant = BIT(mvm->scan_last_antenna_idx) << RATE_MCS_ANT_POS; + + if (band == IEEE80211_BAND_2GHZ && !no_cck) + return cpu_to_le32(IWL_RATE_1M_PLCP | RATE_MCS_CCK_MSK | + tx_ant); + else + return cpu_to_le32(IWL_RATE_6M_PLCP | tx_ant); +} + +/* + * We insert the SSIDs in an inverted order, because the FW will + * invert it back. The most prioritized SSID, which is first in the + * request list, is not copied here, but inserted directly to the probe + * request. + */ +static void iwl_mvm_scan_fill_ssids(struct iwl_scan_cmd *cmd, + struct cfg80211_scan_request *req) +{ + int fw_idx, req_idx; + + for (req_idx = req->n_ssids - 1, fw_idx = 0; req_idx > 0; + req_idx--, fw_idx++) { + cmd->direct_scan[fw_idx].id = WLAN_EID_SSID; + cmd->direct_scan[fw_idx].len = req->ssids[req_idx].ssid_len; + memcpy(cmd->direct_scan[fw_idx].ssid, + req->ssids[req_idx].ssid, + req->ssids[req_idx].ssid_len); + } +} + +/* + * If req->n_ssids > 0, it means we should do an active scan. + * In case of active scan w/o directed scan, we receive a zero-length SSID + * just to notify that this scan is active and not passive. + * In order to notify the FW of the number of SSIDs we wish to scan (including + * the zero-length one), we need to set the corresponding bits in chan->type, + * one for each SSID, and set the active bit (first). + */ +static u16 iwl_mvm_get_active_dwell(enum ieee80211_band band, int n_ssids) +{ + if (band == IEEE80211_BAND_2GHZ) + return 30 + 3 * (n_ssids + 1); + return 20 + 2 * (n_ssids + 1); +} + +static u16 iwl_mvm_get_passive_dwell(enum ieee80211_band band) +{ + return band == IEEE80211_BAND_2GHZ ? 100 + 20 : 100 + 10; +} + +static void iwl_mvm_scan_fill_channels(struct iwl_scan_cmd *cmd, + struct cfg80211_scan_request *req) +{ + u16 passive_dwell = iwl_mvm_get_passive_dwell(req->channels[0]->band); + u16 active_dwell = iwl_mvm_get_active_dwell(req->channels[0]->band, + req->n_ssids); + struct iwl_scan_channel *chan = (struct iwl_scan_channel *) + (cmd->data + le16_to_cpu(cmd->tx_cmd.len)); + int i; + + for (i = 0; i < cmd->channel_count; i++) { + chan->channel = cpu_to_le16(req->channels[i]->hw_value); + chan->type = cpu_to_le32(BIT(req->n_ssids) - 1); + if (req->channels[i]->flags & IEEE80211_CHAN_PASSIVE_SCAN) + chan->type &= cpu_to_le32(~SCAN_CHANNEL_TYPE_ACTIVE); + chan->active_dwell = cpu_to_le16(active_dwell); + chan->passive_dwell = cpu_to_le16(passive_dwell); + chan->iteration_count = cpu_to_le16(1); + chan++; + } +} + +/* + * Fill in probe request with the following parameters: + * TA is our vif HW address, which mac80211 ensures we have. + * Packet is broadcasted, so this is both SA and DA. + * The probe request IE is made out of two: first comes the most prioritized + * SSID if a directed scan is requested. Second comes whatever extra + * information was given to us as the scan request IE. + */ +static u16 iwl_mvm_fill_probe_req(struct ieee80211_mgmt *frame, const u8 *ta, + int n_ssids, const u8 *ssid, int ssid_len, + const u8 *ie, int ie_len, + int left) +{ + int len = 0; + u8 *pos = NULL; + + /* Make sure there is enough space for the probe request, + * two mandatory IEs and the data */ + left -= 24; + if (left < 0) + return 0; + + frame->frame_control = cpu_to_le16(IEEE80211_STYPE_PROBE_REQ); + eth_broadcast_addr(frame->da); + memcpy(frame->sa, ta, ETH_ALEN); + eth_broadcast_addr(frame->bssid); + frame->seq_ctrl = 0; + + len += 24; + + /* for passive scans, no need to fill anything */ + if (n_ssids == 0) + return (u16)len; + + /* points to the payload of the request */ + pos = &frame->u.probe_req.variable[0]; + + /* fill in our SSID IE */ + left -= ssid_len + 2; + if (left < 0) + return 0; + *pos++ = WLAN_EID_SSID; + *pos++ = ssid_len; + if (ssid && ssid_len) { /* ssid_len may be == 0 even if ssid is valid */ + memcpy(pos, ssid, ssid_len); + pos += ssid_len; + } + + len += ssid_len + 2; + + if (WARN_ON(left < ie_len)) + return len; + + if (ie && ie_len) { + memcpy(pos, ie, ie_len); + len += ie_len; + } + + return (u16)len; +} + +int iwl_mvm_scan_request(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct cfg80211_scan_request *req) +{ + struct iwl_host_cmd hcmd = { + .id = SCAN_REQUEST_CMD, + .len = { 0, }, + .data = { mvm->scan_cmd, }, + .flags = CMD_SYNC, + .dataflags = { IWL_HCMD_DFL_NOCOPY, }, + }; + struct iwl_scan_cmd *cmd = mvm->scan_cmd; + int ret; + u32 status; + int ssid_len = 0; + u8 *ssid = NULL; + + lockdep_assert_held(&mvm->mutex); + BUG_ON(mvm->scan_cmd == NULL); + + IWL_DEBUG_SCAN(mvm, "Handling mac80211 scan request\n"); + mvm->scan_status = IWL_MVM_SCAN_OS; + memset(cmd, 0, sizeof(struct iwl_scan_cmd) + + mvm->fw->ucode_capa.max_probe_length + + (MAX_NUM_SCAN_CHANNELS * sizeof(struct iwl_scan_channel))); + + cmd->channel_count = (u8)req->n_channels; + cmd->quiet_time = cpu_to_le16(IWL_ACTIVE_QUIET_TIME); + cmd->quiet_plcp_th = cpu_to_le16(IWL_PLCP_QUIET_THRESH); + cmd->rxchain_sel_flags = iwl_mvm_scan_rx_chain(mvm); + cmd->max_out_time = iwl_mvm_scan_max_out_time(vif); + cmd->suspend_time = iwl_mvm_scan_suspend_time(vif); + cmd->rxon_flags = iwl_mvm_scan_rxon_flags(req); + cmd->filter_flags = cpu_to_le32(MAC_FILTER_ACCEPT_GRP | + MAC_FILTER_IN_BEACON); + + if (vif->type == NL80211_IFTYPE_P2P_DEVICE) + cmd->type = cpu_to_le32(SCAN_TYPE_DISCOVERY_FORCED); + else + cmd->type = cpu_to_le32(SCAN_TYPE_FORCED); + + /* + * TODO: This is a WA due to a bug in the FW AUX framework that does not + * properly handle time events that fail to be scheduled + */ + cmd->type = cpu_to_le32(SCAN_TYPE_FORCED); + + cmd->repeats = cpu_to_le32(1); + + /* + * If the user asked for passive scan, don't change to active scan if + * you see any activity on the channel - remain passive. + */ + if (req->n_ssids > 0) { + cmd->passive2active = cpu_to_le16(1); + ssid = req->ssids[0].ssid; + ssid_len = req->ssids[0].ssid_len; + } else { + cmd->passive2active = 0; + } + + iwl_mvm_scan_fill_ssids(cmd, req); + + cmd->tx_cmd.tx_flags = cpu_to_le32(TX_CMD_FLG_SEQ_CTL); + cmd->tx_cmd.sta_id = mvm->aux_sta.sta_id; + cmd->tx_cmd.life_time = cpu_to_le32(TX_CMD_LIFE_TIME_INFINITE); + cmd->tx_cmd.rate_n_flags = + iwl_mvm_scan_rate_n_flags(mvm, req->channels[0]->band, + req->no_cck); + + cmd->tx_cmd.len = + cpu_to_le16(iwl_mvm_fill_probe_req( + (struct ieee80211_mgmt *)cmd->data, + vif->addr, + req->n_ssids, ssid, ssid_len, + req->ie, req->ie_len, + mvm->fw->ucode_capa.max_probe_length)); + + iwl_mvm_scan_fill_channels(cmd, req); + + cmd->len = cpu_to_le16(sizeof(struct iwl_scan_cmd) + + le16_to_cpu(cmd->tx_cmd.len) + + (cmd->channel_count * sizeof(struct iwl_scan_channel))); + hcmd.len[0] = le16_to_cpu(cmd->len); + + status = SCAN_RESPONSE_OK; + ret = iwl_mvm_send_cmd_status(mvm, &hcmd, &status); + if (!ret && status == SCAN_RESPONSE_OK) { + IWL_DEBUG_SCAN(mvm, "Scan request was sent successfully\n"); + } else { + /* + * If the scan failed, it usually means that the FW was unable + * to allocate the time events. Warn on it, but maybe we + * should try to send the command again with different params. + */ + IWL_ERR(mvm, "Scan failed! status 0x%x ret %d\n", + status, ret); + mvm->scan_status = IWL_MVM_SCAN_NONE; + ret = -EIO; + } + return ret; +} + +int iwl_mvm_rx_scan_response(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_cmd_response *resp = (void *)pkt->data; + + IWL_DEBUG_SCAN(mvm, "Scan response received. status 0x%x\n", + le32_to_cpu(resp->status)); + return 0; +} + +int iwl_mvm_rx_scan_complete(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_scan_complete_notif *notif = (void *)pkt->data; + + IWL_DEBUG_SCAN(mvm, "Scan complete: status=0x%x scanned channels=%d\n", + notif->status, notif->scanned_channels); + + mvm->scan_status = IWL_MVM_SCAN_NONE; + ieee80211_scan_completed(mvm->hw, notif->status != SCAN_COMP_STATUS_OK); + + return 0; +} + +static bool iwl_mvm_scan_abort_notif(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_mvm *mvm = + container_of(notif_wait, struct iwl_mvm, notif_wait); + struct iwl_scan_complete_notif *notif; + u32 *resp; + + switch (pkt->hdr.cmd) { + case SCAN_ABORT_CMD: + resp = (void *)pkt->data; + if (*resp == CAN_ABORT_STATUS) { + IWL_DEBUG_SCAN(mvm, + "Scan can be aborted, wait until completion\n"); + return false; + } + + IWL_DEBUG_SCAN(mvm, "Scan cannot be aborted, exit now: %d\n", + *resp); + return true; + + case SCAN_COMPLETE_NOTIFICATION: + notif = (void *)pkt->data; + IWL_DEBUG_SCAN(mvm, "Scan aborted: status 0x%x\n", + notif->status); + return true; + + default: + WARN_ON(1); + return false; + }; +} + +void iwl_mvm_cancel_scan(struct iwl_mvm *mvm) +{ + struct iwl_notification_wait wait_scan_abort; + static const u8 scan_abort_notif[] = { SCAN_ABORT_CMD, + SCAN_COMPLETE_NOTIFICATION }; + int ret; + + iwl_init_notification_wait(&mvm->notif_wait, &wait_scan_abort, + scan_abort_notif, + ARRAY_SIZE(scan_abort_notif), + iwl_mvm_scan_abort_notif, NULL); + + ret = iwl_mvm_send_cmd_pdu(mvm, SCAN_ABORT_CMD, CMD_SYNC, 0, NULL); + if (ret) { + IWL_ERR(mvm, "Couldn't send SCAN_ABORT_CMD: %d\n", ret); + goto out_remove_notif; + } + + ret = iwl_wait_notification(&mvm->notif_wait, &wait_scan_abort, 1 * HZ); + if (ret) + IWL_ERR(mvm, "%s - failed on timeout\n", __func__); + + return; + +out_remove_notif: + iwl_remove_notification(&mvm->notif_wait, &wait_scan_abort); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/sta.c b/drivers/net/wireless/iwlwifi/mvm/sta.c new file mode 100644 index 0000000..68f0bbe --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/sta.c @@ -0,0 +1,1333 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#include + +#include "mvm.h" +#include "sta.h" + +static int iwl_mvm_find_free_sta_id(struct iwl_mvm *mvm) +{ + int sta_id; + + WARN_ON_ONCE(test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)); + + lockdep_assert_held(&mvm->mutex); + + /* Don't take rcu_read_lock() since we are protected by mvm->mutex */ + for (sta_id = 0; sta_id < IWL_MVM_STATION_COUNT; sta_id++) + if (!rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex))) + return sta_id; + return IWL_MVM_STATION_COUNT; +} + +/* send station add/update command to firmware */ +int iwl_mvm_sta_send_to_fw(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + bool update) +{ + struct iwl_mvm_sta *mvm_sta = (void *)sta->drv_priv; + struct iwl_mvm_add_sta_cmd add_sta_cmd; + int ret; + u32 status; + u32 agg_size = 0, mpdu_dens = 0; + + memset(&add_sta_cmd, 0, sizeof(add_sta_cmd)); + + add_sta_cmd.sta_id = mvm_sta->sta_id; + add_sta_cmd.mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color); + if (!update) { + add_sta_cmd.tfd_queue_msk = cpu_to_le32(mvm_sta->tfd_queue_msk); + memcpy(&add_sta_cmd.addr, sta->addr, ETH_ALEN); + } + add_sta_cmd.add_modify = update ? 1 : 0; + + add_sta_cmd.station_flags_msk |= cpu_to_le32(STA_FLG_FAT_EN_MSK | + STA_FLG_MIMO_EN_MSK); + + switch (sta->bandwidth) { + case IEEE80211_STA_RX_BW_160: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_160MHZ); + /* fall through */ + case IEEE80211_STA_RX_BW_80: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_80MHZ); + /* fall through */ + case IEEE80211_STA_RX_BW_40: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_40MHZ); + /* fall through */ + case IEEE80211_STA_RX_BW_20: + if (sta->ht_cap.ht_supported) + add_sta_cmd.station_flags |= + cpu_to_le32(STA_FLG_FAT_EN_20MHZ); + break; + } + + switch (sta->rx_nss) { + case 1: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_SISO); + break; + case 2: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_MIMO2); + break; + case 3 ... 8: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_MIMO3); + break; + } + + switch (sta->smps_mode) { + case IEEE80211_SMPS_AUTOMATIC: + case IEEE80211_SMPS_NUM_MODES: + WARN_ON(1); + break; + case IEEE80211_SMPS_STATIC: + /* override NSS */ + add_sta_cmd.station_flags &= ~cpu_to_le32(STA_FLG_MIMO_EN_MSK); + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_SISO); + break; + case IEEE80211_SMPS_DYNAMIC: + add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_RTS_MIMO_PROT); + break; + case IEEE80211_SMPS_OFF: + /* nothing */ + break; + } + + if (sta->ht_cap.ht_supported) { + add_sta_cmd.station_flags_msk |= + cpu_to_le32(STA_FLG_MAX_AGG_SIZE_MSK | + STA_FLG_AGG_MPDU_DENS_MSK); + + mpdu_dens = sta->ht_cap.ampdu_density; + } + + if (sta->vht_cap.vht_supported) { + agg_size = sta->vht_cap.cap & + IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK; + agg_size >>= + IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT; + } else if (sta->ht_cap.ht_supported) { + agg_size = sta->ht_cap.ampdu_factor; + } + + add_sta_cmd.station_flags |= + cpu_to_le32(agg_size << STA_FLG_MAX_AGG_SIZE_SHIFT); + add_sta_cmd.station_flags |= + cpu_to_le32(mpdu_dens << STA_FLG_AGG_MPDU_DENS_SHIFT); + + status = ADD_STA_SUCCESS; + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, sizeof(add_sta_cmd), + &add_sta_cmd, &status); + if (ret) + return ret; + + switch (status) { + case ADD_STA_SUCCESS: + IWL_DEBUG_ASSOC(mvm, "ADD_STA PASSED\n"); + break; + default: + ret = -EIO; + IWL_ERR(mvm, "ADD_STA failed\n"); + break; + } + + return ret; +} + +int iwl_mvm_add_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_sta *mvm_sta = (void *)sta->drv_priv; + int i, ret, sta_id; + + lockdep_assert_held(&mvm->mutex); + + if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) + sta_id = iwl_mvm_find_free_sta_id(mvm); + else + sta_id = mvm_sta->sta_id; + + if (WARN_ON_ONCE(sta_id == IWL_MVM_STATION_COUNT)) + return -ENOSPC; + + spin_lock_init(&mvm_sta->lock); + + mvm_sta->sta_id = sta_id; + mvm_sta->mac_id_n_color = FW_CMD_ID_AND_COLOR(mvmvif->id, + mvmvif->color); + mvm_sta->vif = vif; + mvm_sta->max_agg_bufsize = LINK_QUAL_AGG_FRAME_LIMIT_DEF; + + /* HW restart, don't assume the memory has been zeroed */ + atomic_set(&mvm->pending_frames[sta_id], 0); + mvm_sta->tid_disable_agg = 0; + mvm_sta->tfd_queue_msk = 0; + for (i = 0; i < IEEE80211_NUM_ACS; i++) + if (vif->hw_queue[i] != IEEE80211_INVAL_HW_QUEUE) + mvm_sta->tfd_queue_msk |= BIT(vif->hw_queue[i]); + + /* for HW restart - need to reset the seq_number etc... */ + memset(mvm_sta->tid_data, 0, sizeof(mvm_sta->tid_data)); + + ret = iwl_mvm_sta_send_to_fw(mvm, sta, false); + if (ret) + return ret; + + /* The first station added is the AP, the others are TDLS STAs */ + if (vif->type == NL80211_IFTYPE_STATION && + mvmvif->ap_sta_id == IWL_MVM_STATION_COUNT) + mvmvif->ap_sta_id = sta_id; + + rcu_assign_pointer(mvm->fw_id_to_mac_id[sta_id], sta); + + return 0; +} + +int iwl_mvm_update_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + return iwl_mvm_sta_send_to_fw(mvm, sta, true); +} + +int iwl_mvm_drain_sta(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta, + bool drain) +{ + struct iwl_mvm_add_sta_cmd cmd = {}; + int ret; + u32 status; + + lockdep_assert_held(&mvm->mutex); + + cmd.mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color); + cmd.sta_id = mvmsta->sta_id; + cmd.add_modify = STA_MODE_MODIFY; + cmd.station_flags = drain ? cpu_to_le32(STA_FLG_DRAIN_FLOW) : 0; + cmd.station_flags_msk = cpu_to_le32(STA_FLG_DRAIN_FLOW); + + status = ADD_STA_SUCCESS; + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, sizeof(cmd), + &cmd, &status); + if (ret) + return ret; + + switch (status) { + case ADD_STA_SUCCESS: + IWL_DEBUG_INFO(mvm, "Frames for staid %d will drained in fw\n", + mvmsta->sta_id); + break; + default: + ret = -EIO; + IWL_ERR(mvm, "Couldn't drain frames for staid %d\n", + mvmsta->sta_id); + break; + } + + return ret; +} + +/* + * Remove a station from the FW table. Before sending the command to remove + * the station validate that the station is indeed known to the driver (sanity + * only). + */ +static int iwl_mvm_rm_sta_common(struct iwl_mvm *mvm, u8 sta_id) +{ + struct ieee80211_sta *sta; + struct iwl_mvm_rm_sta_cmd rm_sta_cmd = { + .sta_id = sta_id, + }; + int ret; + + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + + /* Note: internal stations are marked as error values */ + if (!sta) { + IWL_ERR(mvm, "Invalid station id\n"); + return -EINVAL; + } + + ret = iwl_mvm_send_cmd_pdu(mvm, REMOVE_STA, CMD_SYNC, + sizeof(rm_sta_cmd), &rm_sta_cmd); + if (ret) { + IWL_ERR(mvm, "Failed to remove station. Id=%d\n", sta_id); + return ret; + } + + return 0; +} + +void iwl_mvm_sta_drained_wk(struct work_struct *wk) +{ + struct iwl_mvm *mvm = container_of(wk, struct iwl_mvm, sta_drained_wk); + u8 sta_id; + + /* + * The mutex is needed because of the SYNC cmd, but not only: if the + * work would run concurrently with iwl_mvm_rm_sta, it would run before + * iwl_mvm_rm_sta sets the station as busy, and exit. Then + * iwl_mvm_rm_sta would set the station as busy, and nobody will clean + * that later. + */ + mutex_lock(&mvm->mutex); + + for_each_set_bit(sta_id, mvm->sta_drained, IWL_MVM_STATION_COUNT) { + int ret; + struct ieee80211_sta *sta = + rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + + /* This station is in use */ + if (!IS_ERR(sta)) + continue; + + if (PTR_ERR(sta) == -EINVAL) { + IWL_ERR(mvm, "Drained sta %d, but it is internal?\n", + sta_id); + continue; + } + + if (!sta) { + IWL_ERR(mvm, "Drained sta %d, but it was NULL?\n", + sta_id); + continue; + } + + WARN_ON(PTR_ERR(sta) != -EBUSY); + /* This station was removed and we waited until it got drained, + * we can now proceed and remove it. + */ + ret = iwl_mvm_rm_sta_common(mvm, sta_id); + if (ret) { + IWL_ERR(mvm, + "Couldn't remove sta %d after it was drained\n", + sta_id); + continue; + } + rcu_assign_pointer(mvm->fw_id_to_mac_id[sta_id], NULL); + clear_bit(sta_id, mvm->sta_drained); + } + + mutex_unlock(&mvm->mutex); +} + +int iwl_mvm_rm_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_sta *mvm_sta = (void *)sta->drv_priv; + int ret; + + lockdep_assert_held(&mvm->mutex); + + if (vif->type == NL80211_IFTYPE_STATION && + mvmvif->ap_sta_id == mvm_sta->sta_id) { + /* flush its queues here since we are freeing mvm_sta */ + ret = iwl_mvm_flush_tx_path(mvm, mvm_sta->tfd_queue_msk, true); + + /* + * Put a non-NULL since the fw station isn't removed. + * It will be removed after the MAC will be set as + * unassoc. + */ + rcu_assign_pointer(mvm->fw_id_to_mac_id[mvm_sta->sta_id], + ERR_PTR(-EINVAL)); + + /* if we are associated - we can't remove the AP STA now */ + if (vif->bss_conf.assoc) + return ret; + + /* unassoc - go ahead - remove the AP STA now */ + mvmvif->ap_sta_id = IWL_MVM_STATION_COUNT; + } + + /* + * Make sure that the tx response code sees the station as -EBUSY and + * calls the drain worker. + */ + spin_lock_bh(&mvm_sta->lock); + /* + * There are frames pending on the AC queues for this station. + * We need to wait until all the frames are drained... + */ + if (atomic_read(&mvm->pending_frames[mvm_sta->sta_id])) { + rcu_assign_pointer(mvm->fw_id_to_mac_id[mvm_sta->sta_id], + ERR_PTR(-EBUSY)); + spin_unlock_bh(&mvm_sta->lock); + ret = iwl_mvm_drain_sta(mvm, mvm_sta, true); + } else { + spin_unlock_bh(&mvm_sta->lock); + ret = iwl_mvm_rm_sta_common(mvm, mvm_sta->sta_id); + rcu_assign_pointer(mvm->fw_id_to_mac_id[mvm_sta->sta_id], NULL); + } + + return ret; +} + +int iwl_mvm_rm_sta_id(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u8 sta_id) +{ + int ret = iwl_mvm_rm_sta_common(mvm, sta_id); + + lockdep_assert_held(&mvm->mutex); + + rcu_assign_pointer(mvm->fw_id_to_mac_id[sta_id], NULL); + return ret; +} + +int iwl_mvm_allocate_int_sta(struct iwl_mvm *mvm, struct iwl_mvm_int_sta *sta, + u32 qmask) +{ + if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { + sta->sta_id = iwl_mvm_find_free_sta_id(mvm); + if (WARN_ON_ONCE(sta->sta_id == IWL_MVM_STATION_COUNT)) + return -ENOSPC; + } + + sta->tfd_queue_msk = qmask; + + /* put a non-NULL value so iterating over the stations won't stop */ + rcu_assign_pointer(mvm->fw_id_to_mac_id[sta->sta_id], ERR_PTR(-EINVAL)); + return 0; +} + +void iwl_mvm_dealloc_int_sta(struct iwl_mvm *mvm, struct iwl_mvm_int_sta *sta) +{ + rcu_assign_pointer(mvm->fw_id_to_mac_id[sta->sta_id], NULL); + memset(sta, 0, sizeof(struct iwl_mvm_int_sta)); + sta->sta_id = IWL_MVM_STATION_COUNT; +} + +static int iwl_mvm_add_int_sta_common(struct iwl_mvm *mvm, + struct iwl_mvm_int_sta *sta, + const u8 *addr, + u16 mac_id, u16 color) +{ + struct iwl_mvm_add_sta_cmd cmd; + int ret; + u32 status; + + lockdep_assert_held(&mvm->mutex); + + memset(&cmd, 0, sizeof(struct iwl_mvm_add_sta_cmd)); + cmd.sta_id = sta->sta_id; + cmd.mac_id_n_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mac_id, + color)); + + cmd.tfd_queue_msk = cpu_to_le32(sta->tfd_queue_msk); + + if (addr) + memcpy(cmd.addr, addr, ETH_ALEN); + + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, sizeof(cmd), + &cmd, &status); + if (ret) + return ret; + + switch (status) { + case ADD_STA_SUCCESS: + IWL_DEBUG_INFO(mvm, "Internal station added.\n"); + return 0; + default: + ret = -EIO; + IWL_ERR(mvm, "Add internal station failed, status=0x%x\n", + status); + break; + } + return ret; +} + +int iwl_mvm_add_aux_sta(struct iwl_mvm *mvm) +{ + int ret; + + lockdep_assert_held(&mvm->mutex); + + /* Add the aux station, but without any queues */ + ret = iwl_mvm_allocate_int_sta(mvm, &mvm->aux_sta, 0); + if (ret) + return ret; + + ret = iwl_mvm_add_int_sta_common(mvm, &mvm->aux_sta, NULL, + MAC_INDEX_AUX, 0); + + if (ret) + iwl_mvm_dealloc_int_sta(mvm, &mvm->aux_sta); + return ret; +} + +/* + * Send the add station command for the vif's broadcast station. + * Assumes that the station was already allocated. + * + * @mvm: the mvm component + * @vif: the interface to which the broadcast station is added + * @bsta: the broadcast station to add. + */ +int iwl_mvm_send_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_int_sta *bsta) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + static const u8 baddr[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + lockdep_assert_held(&mvm->mutex); + + if (WARN_ON_ONCE(bsta->sta_id == IWL_MVM_STATION_COUNT)) + return -ENOSPC; + + return iwl_mvm_add_int_sta_common(mvm, bsta, baddr, + mvmvif->id, mvmvif->color); +} + +/* Send the FW a request to remove the station from it's internal data + * structures, but DO NOT remove the entry from the local data structures. */ +int iwl_mvm_send_rm_bcast_sta(struct iwl_mvm *mvm, + struct iwl_mvm_int_sta *bsta) +{ + int ret; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_mvm_rm_sta_common(mvm, bsta->sta_id); + if (ret) + IWL_WARN(mvm, "Failed sending remove station\n"); + return ret; +} + +/* Allocate a new station entry for the broadcast station to the given vif, + * and send it to the FW. + * Note that each P2P mac should have its own broadcast station. + * + * @mvm: the mvm component + * @vif: the interface to which the broadcast station is added + * @bsta: the broadcast station to add. */ +int iwl_mvm_add_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_int_sta *bsta) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + static const u8 baddr[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + u32 qmask; + int ret; + + lockdep_assert_held(&mvm->mutex); + + qmask = iwl_mvm_mac_get_queues_mask(mvm, vif); + ret = iwl_mvm_allocate_int_sta(mvm, bsta, qmask); + if (ret) + return ret; + + ret = iwl_mvm_add_int_sta_common(mvm, bsta, baddr, + mvmvif->id, mvmvif->color); + + if (ret) + iwl_mvm_dealloc_int_sta(mvm, bsta); + return ret; +} + +/* + * Send the FW a request to remove the station from it's internal data + * structures, and in addition remove it from the local data structure. + */ +int iwl_mvm_rm_bcast_sta(struct iwl_mvm *mvm, struct iwl_mvm_int_sta *bsta) +{ + int ret; + + lockdep_assert_held(&mvm->mutex); + + ret = iwl_mvm_rm_sta_common(mvm, bsta->sta_id); + if (ret) + return ret; + + iwl_mvm_dealloc_int_sta(mvm, bsta); + return ret; +} + +int iwl_mvm_sta_rx_agg(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + int tid, u16 ssn, bool start) +{ + struct iwl_mvm_sta *mvm_sta = (void *)sta->drv_priv; + struct iwl_mvm_add_sta_cmd cmd = {}; + int ret; + u32 status; + + lockdep_assert_held(&mvm->mutex); + + cmd.mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color); + cmd.sta_id = mvm_sta->sta_id; + cmd.add_modify = STA_MODE_MODIFY; + if (start) { + cmd.add_immediate_ba_tid = (u8) tid; + cmd.add_immediate_ba_ssn = cpu_to_le16(ssn); + } else { + cmd.remove_immediate_ba_tid = (u8) tid; + } + cmd.modify_mask = start ? STA_MODIFY_ADD_BA_TID : + STA_MODIFY_REMOVE_BA_TID; + + status = ADD_STA_SUCCESS; + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, sizeof(cmd), + &cmd, &status); + if (ret) + return ret; + + switch (status) { + case ADD_STA_SUCCESS: + IWL_DEBUG_INFO(mvm, "RX BA Session %sed in fw\n", + start ? "start" : "stopp"); + break; + case ADD_STA_IMMEDIATE_BA_FAILURE: + IWL_WARN(mvm, "RX BA Session refused by fw\n"); + ret = -ENOSPC; + break; + default: + ret = -EIO; + IWL_ERR(mvm, "RX BA Session failed %sing, status 0x%x\n", + start ? "start" : "stopp", status); + break; + } + + return ret; +} + +static int iwl_mvm_sta_tx_agg(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + int tid, u8 queue, bool start) +{ + struct iwl_mvm_sta *mvm_sta = (void *)sta->drv_priv; + struct iwl_mvm_add_sta_cmd cmd = {}; + int ret; + u32 status; + + lockdep_assert_held(&mvm->mutex); + + if (start) { + mvm_sta->tfd_queue_msk |= BIT(queue); + mvm_sta->tid_disable_agg &= ~BIT(tid); + } else { + mvm_sta->tfd_queue_msk &= ~BIT(queue); + mvm_sta->tid_disable_agg |= BIT(tid); + } + + cmd.mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color); + cmd.sta_id = mvm_sta->sta_id; + cmd.add_modify = STA_MODE_MODIFY; + cmd.modify_mask = STA_MODIFY_QUEUES | STA_MODIFY_TID_DISABLE_TX; + cmd.tfd_queue_msk = cpu_to_le32(mvm_sta->tfd_queue_msk); + cmd.tid_disable_tx = cpu_to_le16(mvm_sta->tid_disable_agg); + + status = ADD_STA_SUCCESS; + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, sizeof(cmd), + &cmd, &status); + if (ret) + return ret; + + switch (status) { + case ADD_STA_SUCCESS: + break; + default: + ret = -EIO; + IWL_ERR(mvm, "TX BA Session failed %sing, status 0x%x\n", + start ? "start" : "stopp", status); + break; + } + + return ret; +} + +static const u8 tid_to_ac[] = { + IEEE80211_AC_BE, + IEEE80211_AC_BK, + IEEE80211_AC_BK, + IEEE80211_AC_BE, + IEEE80211_AC_VI, + IEEE80211_AC_VI, + IEEE80211_AC_VO, + IEEE80211_AC_VO, +}; + +int iwl_mvm_sta_tx_agg_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 *ssn) +{ + struct iwl_mvm_sta *mvmsta = (void *)sta->drv_priv; + struct iwl_mvm_tid_data *tid_data; + int txq_id; + + if (WARN_ON_ONCE(tid >= IWL_MAX_TID_COUNT)) + return -EINVAL; + + if (mvmsta->tid_data[tid].state != IWL_AGG_OFF) { + IWL_ERR(mvm, "Start AGG when state is not IWL_AGG_OFF %d!\n", + mvmsta->tid_data[tid].state); + return -ENXIO; + } + + lockdep_assert_held(&mvm->mutex); + + for (txq_id = IWL_MVM_FIRST_AGG_QUEUE; + txq_id <= IWL_MVM_LAST_AGG_QUEUE; txq_id++) + if (mvm->queue_to_mac80211[txq_id] == + IWL_INVALID_MAC80211_QUEUE) + break; + + if (txq_id > IWL_MVM_LAST_AGG_QUEUE) { + IWL_ERR(mvm, "Failed to allocate agg queue\n"); + return -EIO; + } + + /* the new tx queue is still connected to the same mac80211 queue */ + mvm->queue_to_mac80211[txq_id] = vif->hw_queue[tid_to_ac[tid]]; + + spin_lock_bh(&mvmsta->lock); + tid_data = &mvmsta->tid_data[tid]; + tid_data->ssn = IEEE80211_SEQ_TO_SN(tid_data->seq_number); + tid_data->txq_id = txq_id; + *ssn = tid_data->ssn; + + IWL_DEBUG_TX_QUEUES(mvm, + "Start AGG: sta %d tid %d queue %d - ssn = %d, next_recl = %d\n", + mvmsta->sta_id, tid, txq_id, tid_data->ssn, + tid_data->next_reclaimed); + + if (tid_data->ssn == tid_data->next_reclaimed) { + tid_data->state = IWL_AGG_STARTING; + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + } else { + tid_data->state = IWL_EMPTYING_HW_QUEUE_ADDBA; + } + + spin_unlock_bh(&mvmsta->lock); + + return 0; +} + +int iwl_mvm_sta_tx_agg_oper(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u8 buf_size) +{ + struct iwl_mvm_sta *mvmsta = (void *)sta->drv_priv; + struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; + int queue, fifo, ret; + u16 ssn; + + buf_size = min_t(int, buf_size, LINK_QUAL_AGG_FRAME_LIMIT_DEF); + + spin_lock_bh(&mvmsta->lock); + ssn = tid_data->ssn; + queue = tid_data->txq_id; + tid_data->state = IWL_AGG_ON; + tid_data->ssn = 0xffff; + spin_unlock_bh(&mvmsta->lock); + + fifo = iwl_mvm_ac_to_tx_fifo[tid_to_ac[tid]]; + + ret = iwl_mvm_sta_tx_agg(mvm, sta, tid, queue, true); + if (ret) + return -EIO; + + iwl_trans_txq_enable(mvm->trans, queue, fifo, mvmsta->sta_id, tid, + buf_size, ssn); + + /* + * Even though in theory the peer could have different + * aggregation reorder buffer sizes for different sessions, + * our ucode doesn't allow for that and has a global limit + * for each station. Therefore, use the minimum of all the + * aggregation sessions and our default value. + */ + mvmsta->max_agg_bufsize = + min(mvmsta->max_agg_bufsize, buf_size); + mvmsta->lq_sta.lq.agg_frame_cnt_limit = mvmsta->max_agg_bufsize; + + if (mvm->cfg->ht_params->use_rts_for_aggregation) { + /* + * switch to RTS/CTS if it is the prefer protection + * method for HT traffic + */ + mvmsta->lq_sta.lq.flags |= LQ_FLAG_SET_STA_TLC_RTS_MSK; + /* + * TODO: remove the TLC_RTS flag when we tear down the last + * AGG session (agg_tids_count in DVM) + */ + } + + IWL_DEBUG_HT(mvm, "Tx aggregation enabled on ra = %pM tid = %d\n", + sta->addr, tid); + + return iwl_mvm_send_lq_cmd(mvm, &mvmsta->lq_sta.lq, CMD_ASYNC, false); +} + +int iwl_mvm_sta_tx_agg_stop(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid) +{ + struct iwl_mvm_sta *mvmsta = (void *)sta->drv_priv; + struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; + u16 txq_id; + int err; + + + /* + * If mac80211 is cleaning its state, then say that we finished since + * our state has been cleared anyway. + */ + if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + return 0; + } + + spin_lock_bh(&mvmsta->lock); + + txq_id = tid_data->txq_id; + + IWL_DEBUG_TX_QUEUES(mvm, "Stop AGG: sta %d tid %d q %d state %d\n", + mvmsta->sta_id, tid, txq_id, tid_data->state); + + switch (tid_data->state) { + case IWL_AGG_ON: + tid_data->ssn = IEEE80211_SEQ_TO_SN(tid_data->seq_number); + + IWL_DEBUG_TX_QUEUES(mvm, + "ssn = %d, next_recl = %d\n", + tid_data->ssn, tid_data->next_reclaimed); + + /* There are still packets for this RA / TID in the HW */ + if (tid_data->ssn != tid_data->next_reclaimed) { + tid_data->state = IWL_EMPTYING_HW_QUEUE_DELBA; + err = 0; + break; + } + + tid_data->ssn = 0xffff; + iwl_trans_txq_disable(mvm->trans, txq_id); + /* fall through */ + case IWL_AGG_STARTING: + case IWL_EMPTYING_HW_QUEUE_ADDBA: + /* + * The agg session has been stopped before it was set up. This + * can happen when the AddBA timer times out for example. + */ + + /* No barriers since we are under mutex */ + lockdep_assert_held(&mvm->mutex); + mvm->queue_to_mac80211[txq_id] = IWL_INVALID_MAC80211_QUEUE; + + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + tid_data->state = IWL_AGG_OFF; + err = 0; + break; + default: + IWL_ERR(mvm, + "Stopping AGG while state not ON or starting for %d on %d (%d)\n", + mvmsta->sta_id, tid, tid_data->state); + IWL_ERR(mvm, + "\ttid_data->txq_id = %d\n", tid_data->txq_id); + err = -EINVAL; + } + + spin_unlock_bh(&mvmsta->lock); + + return err; +} + +int iwl_mvm_sta_tx_agg_flush(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid) +{ + struct iwl_mvm_sta *mvmsta = (void *)sta->drv_priv; + struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; + u16 txq_id; + enum iwl_mvm_agg_state old_state; + + /* + * First set the agg state to OFF to avoid calling + * ieee80211_stop_tx_ba_cb in iwl_mvm_check_ratid_empty. + */ + spin_lock_bh(&mvmsta->lock); + txq_id = tid_data->txq_id; + IWL_DEBUG_TX_QUEUES(mvm, "Flush AGG: sta %d tid %d q %d state %d\n", + mvmsta->sta_id, tid, txq_id, tid_data->state); + old_state = tid_data->state; + tid_data->state = IWL_AGG_OFF; + spin_unlock_bh(&mvmsta->lock); + + if (old_state >= IWL_AGG_ON) { + if (iwl_mvm_flush_tx_path(mvm, BIT(txq_id), true)) + IWL_ERR(mvm, "Couldn't flush the AGG queue\n"); + + iwl_trans_txq_disable(mvm->trans, tid_data->txq_id); + } + + mvm->queue_to_mac80211[tid_data->txq_id] = + IWL_INVALID_MAC80211_QUEUE; + + return 0; +} + +static int iwl_mvm_set_fw_key_idx(struct iwl_mvm *mvm) +{ + int i; + + lockdep_assert_held(&mvm->mutex); + + i = find_first_zero_bit(mvm->fw_key_table, STA_KEY_MAX_NUM); + + if (i == STA_KEY_MAX_NUM) + return STA_KEY_IDX_INVALID; + + __set_bit(i, mvm->fw_key_table); + + return i; +} + +static u8 iwl_mvm_get_key_sta_id(struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_vif *mvmvif = (void *)vif->drv_priv; + + if (sta) { + struct iwl_mvm_sta *mvm_sta = (void *)sta->drv_priv; + + return mvm_sta->sta_id; + } + + /* + * The device expects GTKs for station interfaces to be + * installed as GTKs for the AP station. If we have no + * station ID, then use AP's station ID. + */ + if (vif->type == NL80211_IFTYPE_STATION && + mvmvif->ap_sta_id != IWL_MVM_STATION_COUNT) + return mvmvif->ap_sta_id; + + return IWL_MVM_STATION_COUNT; +} + +static int iwl_mvm_send_sta_key(struct iwl_mvm *mvm, + struct iwl_mvm_sta *mvm_sta, + struct ieee80211_key_conf *keyconf, + u8 sta_id, u32 tkip_iv32, u16 *tkip_p1k, + u32 cmd_flags) +{ + __le16 key_flags; + struct iwl_mvm_add_sta_cmd cmd = {}; + int ret, status; + u16 keyidx; + int i; + + keyidx = (keyconf->keyidx << STA_KEY_FLG_KEYID_POS) & + STA_KEY_FLG_KEYID_MSK; + key_flags = cpu_to_le16(keyidx); + key_flags |= cpu_to_le16(STA_KEY_FLG_WEP_KEY_MAP); + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_TKIP: + key_flags |= cpu_to_le16(STA_KEY_FLG_TKIP); + cmd.key.tkip_rx_tsc_byte2 = tkip_iv32; + for (i = 0; i < 5; i++) + cmd.key.tkip_rx_ttak[i] = cpu_to_le16(tkip_p1k[i]); + memcpy(cmd.key.key, keyconf->key, keyconf->keylen); + break; + case WLAN_CIPHER_SUITE_CCMP: + key_flags |= cpu_to_le16(STA_KEY_FLG_CCM); + memcpy(cmd.key.key, keyconf->key, keyconf->keylen); + break; + default: + WARN_ON(1); + return -EINVAL; + } + + if (!(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE)) + key_flags |= cpu_to_le16(STA_KEY_MULTICAST); + + cmd.mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color); + cmd.key.key_offset = keyconf->hw_key_idx; + cmd.key.key_flags = key_flags; + cmd.add_modify = STA_MODE_MODIFY; + cmd.modify_mask = STA_MODIFY_KEY; + cmd.sta_id = sta_id; + + status = ADD_STA_SUCCESS; + if (cmd_flags == CMD_SYNC) + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, sizeof(cmd), + &cmd, &status); + else + ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, CMD_ASYNC, + sizeof(cmd), &cmd); + + switch (status) { + case ADD_STA_SUCCESS: + IWL_DEBUG_WEP(mvm, "MODIFY_STA: set dynamic key passed\n"); + break; + default: + ret = -EIO; + IWL_ERR(mvm, "MODIFY_STA: set dynamic key failed\n"); + break; + } + + return ret; +} + +static int iwl_mvm_send_sta_igtk(struct iwl_mvm *mvm, + struct ieee80211_key_conf *keyconf, + u8 sta_id, bool remove_key) +{ + struct iwl_mvm_mgmt_mcast_key_cmd igtk_cmd = {}; + + /* verify the key details match the required command's expectations */ + if (WARN_ON((keyconf->cipher != WLAN_CIPHER_SUITE_AES_CMAC) || + (keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE) || + (keyconf->keyidx != 4 && keyconf->keyidx != 5))) + return -EINVAL; + + igtk_cmd.key_id = cpu_to_le32(keyconf->keyidx); + igtk_cmd.sta_id = cpu_to_le32(sta_id); + + if (remove_key) { + igtk_cmd.ctrl_flags |= cpu_to_le32(STA_KEY_NOT_VALID); + } else { + struct ieee80211_key_seq seq; + const u8 *pn; + + memcpy(igtk_cmd.IGTK, keyconf->key, keyconf->keylen); + ieee80211_aes_cmac_calculate_k1_k2(keyconf, + igtk_cmd.K1, igtk_cmd.K2); + ieee80211_get_key_rx_seq(keyconf, 0, &seq); + pn = seq.aes_cmac.pn; + igtk_cmd.receive_seq_cnt = cpu_to_le64(((u64) pn[5] << 0) | + ((u64) pn[4] << 8) | + ((u64) pn[3] << 16) | + ((u64) pn[2] << 24) | + ((u64) pn[1] << 32) | + ((u64) pn[0] << 40)); + } + + IWL_DEBUG_INFO(mvm, "%s igtk for sta %u\n", + remove_key ? "removing" : "installing", + igtk_cmd.sta_id); + + return iwl_mvm_send_cmd_pdu(mvm, MGMT_MCAST_KEY, CMD_SYNC, + sizeof(igtk_cmd), &igtk_cmd); +} + + +static inline u8 *iwl_mvm_get_mac_addr(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_vif *mvmvif = (void *)vif->drv_priv; + + if (sta) + return sta->addr; + + if (vif->type == NL80211_IFTYPE_STATION && + mvmvif->ap_sta_id != IWL_MVM_STATION_COUNT) { + u8 sta_id = mvmvif->ap_sta_id; + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + return sta->addr; + } + + + return NULL; +} + +int iwl_mvm_set_sta_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *keyconf, + bool have_key_offset) +{ + struct iwl_mvm_sta *mvm_sta; + int ret; + u8 *addr, sta_id; + struct ieee80211_key_seq seq; + u16 p1k[5]; + + lockdep_assert_held(&mvm->mutex); + + /* Get the station id from the mvm local station table */ + sta_id = iwl_mvm_get_key_sta_id(vif, sta); + if (sta_id == IWL_MVM_STATION_COUNT) { + IWL_ERR(mvm, "Failed to find station id\n"); + return -EINVAL; + } + + if (keyconf->cipher == WLAN_CIPHER_SUITE_AES_CMAC) { + ret = iwl_mvm_send_sta_igtk(mvm, keyconf, sta_id, false); + goto end; + } + + /* + * It is possible that the 'sta' parameter is NULL, and thus + * there is a need to retrieve the sta from the local station table. + */ + if (!sta) { + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + if (IS_ERR_OR_NULL(sta)) { + IWL_ERR(mvm, "Invalid station id\n"); + return -EINVAL; + } + } + + mvm_sta = (struct iwl_mvm_sta *)sta->drv_priv; + if (WARN_ON_ONCE(mvm_sta->vif != vif)) + return -EINVAL; + + if (!have_key_offset) { + /* + * The D3 firmware hardcodes the PTK offset to 0, so we have to + * configure it there. As a result, this workaround exists to + * let the caller set the key offset (hw_key_idx), see d3.c. + */ + keyconf->hw_key_idx = iwl_mvm_set_fw_key_idx(mvm); + if (keyconf->hw_key_idx == STA_KEY_IDX_INVALID) + return -ENOSPC; + } + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_TKIP: + addr = iwl_mvm_get_mac_addr(mvm, vif, sta); + /* get phase 1 key from mac80211 */ + ieee80211_get_key_rx_seq(keyconf, 0, &seq); + ieee80211_get_tkip_rx_p1k(keyconf, addr, seq.tkip.iv32, p1k); + ret = iwl_mvm_send_sta_key(mvm, mvm_sta, keyconf, sta_id, + seq.tkip.iv32, p1k, CMD_SYNC); + break; + case WLAN_CIPHER_SUITE_CCMP: + ret = iwl_mvm_send_sta_key(mvm, mvm_sta, keyconf, sta_id, + 0, NULL, CMD_SYNC); + break; + default: + IWL_ERR(mvm, "Unknown cipher %x\n", keyconf->cipher); + ret = -EINVAL; + } + + if (ret) + __clear_bit(keyconf->hw_key_idx, mvm->fw_key_table); + +end: + IWL_DEBUG_WEP(mvm, "key: cipher=%x len=%d idx=%d sta=%pM ret=%d\n", + keyconf->cipher, keyconf->keylen, keyconf->keyidx, + sta->addr, ret); + return ret; +} + +int iwl_mvm_remove_sta_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *keyconf) +{ + struct iwl_mvm_sta *mvm_sta; + struct iwl_mvm_add_sta_cmd cmd = {}; + __le16 key_flags; + int ret, status; + u8 sta_id; + + lockdep_assert_held(&mvm->mutex); + + /* Get the station id from the mvm local station table */ + sta_id = iwl_mvm_get_key_sta_id(vif, sta); + + IWL_DEBUG_WEP(mvm, "mvm remove dynamic key: idx=%d sta=%d\n", + keyconf->keyidx, sta_id); + + if (keyconf->cipher == WLAN_CIPHER_SUITE_AES_CMAC) + return iwl_mvm_send_sta_igtk(mvm, keyconf, sta_id, true); + + ret = __test_and_clear_bit(keyconf->hw_key_idx, mvm->fw_key_table); + if (!ret) { + IWL_ERR(mvm, "offset %d not used in fw key table.\n", + keyconf->hw_key_idx); + return -ENOENT; + } + + if (sta_id == IWL_MVM_STATION_COUNT) { + IWL_DEBUG_WEP(mvm, "station non-existent, early return.\n"); + return 0; + } + + /* + * It is possible that the 'sta' parameter is NULL, and thus + * there is a need to retrieve the sta from the local station table, + * for example when a GTK is removed (where the sta_id will then be + * the AP ID, and no station was passed by mac80211.) + */ + if (!sta) { + sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], + lockdep_is_held(&mvm->mutex)); + if (!sta) { + IWL_ERR(mvm, "Invalid station id\n"); + return -EINVAL; + } + } + + mvm_sta = (struct iwl_mvm_sta *)sta->drv_priv; + if (WARN_ON_ONCE(mvm_sta->vif != vif)) + return -EINVAL; + + key_flags = cpu_to_le16((keyconf->keyidx << STA_KEY_FLG_KEYID_POS) & + STA_KEY_FLG_KEYID_MSK); + key_flags |= cpu_to_le16(STA_KEY_FLG_NO_ENC | STA_KEY_FLG_WEP_KEY_MAP); + key_flags |= cpu_to_le16(STA_KEY_NOT_VALID); + + if (!(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE)) + key_flags |= cpu_to_le16(STA_KEY_MULTICAST); + + cmd.mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color); + cmd.key.key_flags = key_flags; + cmd.key.key_offset = keyconf->hw_key_idx; + cmd.sta_id = sta_id; + + cmd.modify_mask = STA_MODIFY_KEY; + cmd.add_modify = STA_MODE_MODIFY; + + status = ADD_STA_SUCCESS; + ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, sizeof(cmd), + &cmd, &status); + + switch (status) { + case ADD_STA_SUCCESS: + IWL_DEBUG_WEP(mvm, "MODIFY_STA: remove sta key passed\n"); + break; + default: + ret = -EIO; + IWL_ERR(mvm, "MODIFY_STA: remove sta key failed\n"); + break; + } + + return ret; +} + +void iwl_mvm_update_tkip_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, u32 iv32, + u16 *phase1key) +{ + struct iwl_mvm_sta *mvm_sta; + u8 sta_id = iwl_mvm_get_key_sta_id(vif, sta); + + if (WARN_ON_ONCE(sta_id == IWL_MVM_STATION_COUNT)) + return; + + rcu_read_lock(); + + if (!sta) { + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + if (WARN_ON(IS_ERR_OR_NULL(sta))) { + rcu_read_unlock(); + return; + } + } + + mvm_sta = (void *)sta->drv_priv; + iwl_mvm_send_sta_key(mvm, mvm_sta, keyconf, sta_id, + iv32, phase1key, CMD_ASYNC); + rcu_read_unlock(); +} + +void iwl_mvm_sta_modify_ps_wake(struct iwl_mvm *mvm, + struct ieee80211_sta *sta) +{ + struct iwl_mvm_sta *mvmsta = (void *)sta->drv_priv; + struct iwl_mvm_add_sta_cmd cmd = { + .add_modify = STA_MODE_MODIFY, + .sta_id = mvmsta->sta_id, + .station_flags_msk = cpu_to_le32(STA_FLG_PS), + .mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color), + }; + int ret; + + ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, CMD_ASYNC, sizeof(cmd), &cmd); + if (ret) + IWL_ERR(mvm, "Failed to send ADD_STA command (%d)\n", ret); +} + +void iwl_mvm_sta_modify_sleep_tx_count(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + enum ieee80211_frame_release_type reason, + u16 cnt) +{ + u16 sleep_state_flags = + (reason == IEEE80211_FRAME_RELEASE_UAPSD) ? + STA_SLEEP_STATE_UAPSD : STA_SLEEP_STATE_PS_POLL; + struct iwl_mvm_sta *mvmsta = (void *)sta->drv_priv; + struct iwl_mvm_add_sta_cmd cmd = { + .add_modify = STA_MODE_MODIFY, + .sta_id = mvmsta->sta_id, + .modify_mask = STA_MODIFY_SLEEPING_STA_TX_COUNT, + .sleep_tx_count = cpu_to_le16(cnt), + .mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color), + /* + * Same modify mask for sleep_tx_count and sleep_state_flags so + * we must set the sleep_state_flags too. + */ + .sleep_state_flags = cpu_to_le16(sleep_state_flags), + }; + int ret; + + /* TODO: somehow the fw doesn't seem to take PS_POLL into account */ + ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, CMD_ASYNC, sizeof(cmd), &cmd); + if (ret) + IWL_ERR(mvm, "Failed to send ADD_STA command (%d)\n", ret); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/sta.h b/drivers/net/wireless/iwlwifi/mvm/sta.h new file mode 100644 index 0000000..a4ddce7 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/sta.h @@ -0,0 +1,376 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#ifndef __sta_h__ +#define __sta_h__ + +#include +#include +#include + +#include "iwl-trans.h" /* for IWL_MAX_TID_COUNT */ +#include "fw-api.h" /* IWL_MVM_STATION_COUNT */ +#include "rs.h" + +struct iwl_mvm; + +/** + * DOC: station table - introduction + * + * The station table is a list of data structure that reprensent the stations. + * In STA/P2P client mode, the driver will hold one station for the AP/ GO. + * In GO/AP mode, the driver will have as many stations as associated clients. + * All these stations are reflected in the fw's station table. The driver + * keeps the fw's station table up to date with the ADD_STA command. Stations + * can be removed by the REMOVE_STA command. + * + * All the data related to a station is held in the structure %iwl_mvm_sta + * which is embed in the mac80211's %ieee80211_sta (in the drv_priv) area. + * This data includes the index of the station in the fw, per tid information + * (sequence numbers, Block-ack state machine, etc...). The stations are + * created and deleted by the %sta_state callback from %ieee80211_ops. + * + * The driver holds a map: %fw_id_to_mac_id that allows to fetch a + * %ieee80211_sta (and the %iwl_mvm_sta embedded into it) based on a fw + * station index. That way, the driver is able to get the tid related data in + * O(1) in time sensitive paths (Tx / Tx response / BA notification). These + * paths are triggered by the fw, and the driver needs to get a pointer to the + * %ieee80211 structure. This map helps to get that pointer quickly. + */ + +/** + * DOC: station table - locking + * + * As stated before, the station is created / deleted by mac80211's %sta_state + * callback from %ieee80211_ops which can sleep. The next paragraph explains + * the locking of a single stations, the next ones relates to the station + * table. + * + * The station holds the sequence number per tid. So this data needs to be + * accessed in the Tx path (which is softIRQ). It also holds the Block-Ack + * information (the state machine / and the logic that checks if the queues + * were drained), so it also needs to be accessible from the Tx response flow. + * In short, the station needs to be access from sleepable context as well as + * from tasklets, so the station itself needs a spinlock. + * + * The writers of %fw_id_to_mac_id map are serialized by the global mutex of + * the mvm op_mode. This is possible since %sta_state can sleep. + * The pointers in this map are RCU protected, hence we won't replace the + * station while we have Tx / Tx response / BA notification running. + * + * If a station is deleted while it still has packets in its A-MPDU queues, + * then the reclaim flow will notice that there is no station in the map for + * sta_id and it will dump the responses. + */ + +/** + * DOC: station table - internal stations + * + * The FW needs a few internal stations that are not reflected in + * mac80211, such as broadcast station in AP / GO mode, or AUX sta for + * scanning and P2P device (during the GO negotiation). + * For these kind of stations we have %iwl_mvm_int_sta struct which holds the + * data relevant for them from both %iwl_mvm_sta and %ieee80211_sta. + * Usually the data for these stations is static, so no locking is required, + * and no TID data as this is also not needed. + * One thing to note, is that these stations have an ID in the fw, but not + * in mac80211. In order to "reserve" them a sta_id in %fw_id_to_mac_id + * we fill ERR_PTR(EINVAL) in this mapping and all other dereferencing of + * pointers from this mapping need to check that the value is not error + * or NULL. + * + * Currently there is only one auxiliary station for scanning, initialized + * on init. + */ + +/** + * DOC: station table - AP Station in STA mode + * + * %iwl_mvm_vif includes the index of the AP station in the fw's STA table: + * %ap_sta_id. To get the point to the coresponsding %ieee80211_sta, + * &fw_id_to_mac_id can be used. Due to the way the fw works, we must not remove + * the AP station from the fw before setting the MAC context as unassociated. + * Hence, %fw_id_to_mac_id[%ap_sta_id] will be NULLed when the AP station is + * removed by mac80211, but the station won't be removed in the fw until the + * VIF is set as unassociated. Then, %ap_sta_id will be invalidated. + */ + +/** + * DOC: station table - Drain vs. Flush + * + * Flush means that all the frames in the SCD queue are dumped regardless the + * station to which they were sent. We do that when we disassociate and before + * we remove the STA of the AP. The flush can be done synchronously against the + * fw. + * Drain means that the fw will drop all the frames sent to a specific station. + * This is useful when a client (if we are IBSS / GO or AP) disassociates. In + * that case, we need to drain all the frames for that client from the AC queues + * that are shared with the other clients. Only then, we can remove the STA in + * the fw. In order to do so, we track the non-AMPDU packets for each station. + * If mac80211 removes a STA and if it still has non-AMPDU packets pending in + * the queues, we mark this station as %EBUSY in %fw_id_to_mac_id, and drop all + * the frames for this STA (%iwl_mvm_rm_sta). When the last frame is dropped + * (we know about it with its Tx response), we remove the station in fw and set + * it as %NULL in %fw_id_to_mac_id: this is the purpose of + * %iwl_mvm_sta_drained_wk. + */ + +/** + * DOC: station table - fw restart + * + * When the fw asserts, or we have any other issue that requires to reset the + * driver, we require mac80211 to reconfigure the driver. Since the private + * data of the stations is embed in mac80211's %ieee80211_sta, that data will + * not be zeroed and needs to be reinitialized manually. + * %IWL_MVM_STATUS_IN_HW_RESTART is set during restart and that will hint us + * that we must not allocate a new sta_id but reuse the previous one. This + * means that the stations being re-added after the reset will have the same + * place in the fw as before the reset. We do need to zero the %fw_id_to_mac_id + * map, since the stations aren't in the fw any more. Internal stations that + * are not added by mac80211 will be re-added in the init flow that is called + * after the restart: mac80211 call's %iwl_mvm_mac_start which calls to + * %iwl_mvm_up. + */ + +/** + * DOC: AP mode - PS + * + * When a station is asleep, the fw will set it as "asleep". All the + * non-aggregation frames to that station will be dropped by the fw + * (%TX_STATUS_FAIL_DEST_PS failure code). + * AMPDUs are in a separate queue that is stopped by the fw. We just need to + * let mac80211 know how many frames we have in these queues so that it can + * properly handle trigger frames. + * When the a trigger frame is received, mac80211 tells the driver to send + * frames from the AMPDU queues or AC queue depending on which queue are + * delivery-enabled and what TID has frames to transmit (Note that mac80211 has + * all the knowledege since all the non-agg frames are buffered / filtered, and + * the driver tells mac80211 about agg frames). The driver needs to tell the fw + * to let frames out even if the station is asleep. This is done by + * %iwl_mvm_sta_modify_sleep_tx_count. + * When we receive a frame from that station with PM bit unset, the + * driver needs to let the fw know that this station isn't alseep any more. + * This is done by %iwl_mvm_sta_modify_ps_wake. + * + * TODO - EOSP handling + */ + +/** + * enum iwl_mvm_agg_state + * + * The state machine of the BA agreement establishment / tear down. + * These states relate to a specific RA / TID. + * + * @IWL_AGG_OFF: aggregation is not used + * @IWL_AGG_STARTING: aggregation are starting (between start and oper) + * @IWL_AGG_ON: aggregation session is up + * @IWL_EMPTYING_HW_QUEUE_ADDBA: establishing a BA session - waiting for the + * HW queue to be empty from packets for this RA /TID. + * @IWL_EMPTYING_HW_QUEUE_DELBA: tearing down a BA session - waiting for the + * HW queue to be empty from packets for this RA /TID. + */ +enum iwl_mvm_agg_state { + IWL_AGG_OFF = 0, + IWL_AGG_STARTING, + IWL_AGG_ON, + IWL_EMPTYING_HW_QUEUE_ADDBA, + IWL_EMPTYING_HW_QUEUE_DELBA, +}; + +/** + * struct iwl_mvm_tid_data - holds the states for each RA / TID + * @seq_number: the next WiFi sequence number to use + * @next_reclaimed: the WiFi sequence number of the next packet to be acked. + * This is basically (last acked packet++). + * @rate_n_flags: Rate at which Tx was attempted. Holds the data between the + * Tx response (TX_CMD), and the block ack notification (COMPRESSED_BA). + * @state: state of the BA agreement establishment / tear down. + * @txq_id: Tx queue used by the BA session + * @ssn: the first packet to be sent in AGG HW queue in Tx AGG start flow, or + * the first packet to be sent in legacy HW queue in Tx AGG stop flow. + * Basically when next_reclaimed reaches ssn, we can tell mac80211 that + * we are ready to finish the Tx AGG stop / start flow. + * @wait_for_ba: Expect block-ack before next Tx reply + */ +struct iwl_mvm_tid_data { + u16 seq_number; + u16 next_reclaimed; + /* The rest is Tx AGG related */ + u32 rate_n_flags; + enum iwl_mvm_agg_state state; + u16 txq_id; + u16 ssn; + bool wait_for_ba; +}; + +/** + * struct iwl_mvm_sta - representation of a station in the driver + * @sta_id: the index of the station in the fw (will be replaced by id_n_color) + * @tfd_queue_msk: the tfd queues used by the station + * @mac_id_n_color: the MAC context this station is linked to + * @tid_disable_agg: bitmap: if bit(tid) is set, the fw won't send ampdus for + * tid. + * @max_agg_bufsize: the maximal size of the AGG buffer for this station + * @bt_reduced_txpower: is reduced tx power enabled for this station + * @lock: lock to protect the whole struct. Since %tid_data is access from Tx + * and from Tx response flow, it needs a spinlock. + * @tid_data: per tid data. Look at %iwl_mvm_tid_data. + * + * When mac80211 creates a station it reserves some space (hw->sta_data_size) + * in the structure for use by driver. This structure is placed in that + * space. + * + */ +struct iwl_mvm_sta { + u32 sta_id; + u32 tfd_queue_msk; + u32 mac_id_n_color; + u16 tid_disable_agg; + u8 max_agg_bufsize; + bool bt_reduced_txpower; + spinlock_t lock; + struct iwl_mvm_tid_data tid_data[IWL_MAX_TID_COUNT]; + struct iwl_lq_sta lq_sta; + struct ieee80211_vif *vif; + +#ifdef CONFIG_PM_SLEEP + u16 last_seq_ctl; +#endif +}; + +/** + * struct iwl_mvm_int_sta - representation of an internal station (auxiliary or + * broadcast) + * @sta_id: the index of the station in the fw (will be replaced by id_n_color) + * @tfd_queue_msk: the tfd queues used by the station + */ +struct iwl_mvm_int_sta { + u32 sta_id; + u32 tfd_queue_msk; +}; + +int iwl_mvm_sta_send_to_fw(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + bool update); +int iwl_mvm_add_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +int iwl_mvm_update_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +int iwl_mvm_rm_sta(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +int iwl_mvm_rm_sta_id(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u8 sta_id); +int iwl_mvm_set_sta_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key, + bool have_key_offset); +int iwl_mvm_remove_sta_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *keyconf); + +void iwl_mvm_update_tkip_key(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct ieee80211_key_conf *keyconf, + struct ieee80211_sta *sta, u32 iv32, + u16 *phase1key); + +/* AMPDU */ +int iwl_mvm_sta_rx_agg(struct iwl_mvm *mvm, struct ieee80211_sta *sta, + int tid, u16 ssn, bool start); +int iwl_mvm_sta_tx_agg_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u16 *ssn); +int iwl_mvm_sta_tx_agg_oper(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid, u8 buf_size); +int iwl_mvm_sta_tx_agg_stop(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid); +int iwl_mvm_sta_tx_agg_flush(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 tid); + +int iwl_mvm_add_aux_sta(struct iwl_mvm *mvm); +int iwl_mvm_allocate_int_sta(struct iwl_mvm *mvm, struct iwl_mvm_int_sta *sta, + u32 qmask); +void iwl_mvm_dealloc_int_sta(struct iwl_mvm *mvm, + struct iwl_mvm_int_sta *sta); +int iwl_mvm_send_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_int_sta *bsta); +int iwl_mvm_send_rm_bcast_sta(struct iwl_mvm *mvm, + struct iwl_mvm_int_sta *bsta); +int iwl_mvm_add_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + struct iwl_mvm_int_sta *bsta); +int iwl_mvm_rm_bcast_sta(struct iwl_mvm *mvm, struct iwl_mvm_int_sta *bsta); +void iwl_mvm_sta_drained_wk(struct work_struct *wk); +void iwl_mvm_sta_modify_ps_wake(struct iwl_mvm *mvm, + struct ieee80211_sta *sta); +void iwl_mvm_sta_modify_sleep_tx_count(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, + enum ieee80211_frame_release_type reason, + u16 cnt); +int iwl_mvm_drain_sta(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta, + bool drain); + +#endif /* __sta_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/time-event.c b/drivers/net/wireless/iwlwifi/mvm/time-event.c new file mode 100644 index 0000000..ad9bbca --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/time-event.c @@ -0,0 +1,527 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#include +#include + +#include "iwl-notif-wait.h" +#include "iwl-trans.h" +#include "fw-api.h" +#include "time-event.h" +#include "mvm.h" +#include "iwl-io.h" +#include "iwl-prph.h" + +/* A TimeUnit is 1024 microsecond */ +#define TU_TO_JIFFIES(_tu) (usecs_to_jiffies((_tu) * 1024)) +#define MSEC_TO_TU(_msec) (_msec*1000/1024) + +/* + * For the high priority TE use a time event type that has similar priority to + * the FW's action scan priority. + */ +#define IWL_MVM_ROC_TE_TYPE_NORMAL TE_P2P_DEVICE_DISCOVERABLE +#define IWL_MVM_ROC_TE_TYPE_MGMT_TX TE_P2P_CLIENT_ASSOC + +void iwl_mvm_te_clear_data(struct iwl_mvm *mvm, + struct iwl_mvm_time_event_data *te_data) +{ + lockdep_assert_held(&mvm->time_event_lock); + + if (te_data->id == TE_MAX) + return; + + list_del(&te_data->list); + te_data->running = false; + te_data->uid = 0; + te_data->id = TE_MAX; + te_data->vif = NULL; +} + +void iwl_mvm_roc_done_wk(struct work_struct *wk) +{ + struct iwl_mvm *mvm = container_of(wk, struct iwl_mvm, roc_done_wk); + + synchronize_net(); + + /* + * Flush the offchannel queue -- this is called when the time + * event finishes or is cancelled, so that frames queued for it + * won't get stuck on the queue and be transmitted in the next + * time event. + * We have to send the command asynchronously since this cannot + * be under the mutex for locking reasons, but that's not an + * issue as it will have to complete before the next command is + * executed, and a new time event means a new command. + */ + iwl_mvm_flush_tx_path(mvm, BIT(IWL_MVM_OFFCHANNEL_QUEUE), false); +} + +static void iwl_mvm_roc_finished(struct iwl_mvm *mvm) +{ + /* + * First, clear the ROC_RUNNING status bit. This will cause the TX + * path to drop offchannel transmissions. That would also be done + * by mac80211, but it is racy, in particular in the case that the + * time event actually completed in the firmware (which is handled + * in iwl_mvm_te_handle_notif). + */ + clear_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status); + + /* + * Of course, our status bit is just as racy as mac80211, so in + * addition, fire off the work struct which will drop all frames + * from the hardware queues that made it through the race. First + * it will of course synchronize the TX path to make sure that + * any *new* TX will be rejected. + */ + schedule_work(&mvm->roc_done_wk); +} + +/* + * Handles a FW notification for an event that is known to the driver. + * + * @mvm: the mvm component + * @te_data: the time event data + * @notif: the notification data corresponding the time event data. + */ +static void iwl_mvm_te_handle_notif(struct iwl_mvm *mvm, + struct iwl_mvm_time_event_data *te_data, + struct iwl_time_event_notif *notif) +{ + lockdep_assert_held(&mvm->time_event_lock); + + IWL_DEBUG_TE(mvm, "Handle time event notif - UID = 0x%x action %d\n", + le32_to_cpu(notif->unique_id), + le32_to_cpu(notif->action)); + + /* + * The FW sends the start/end time event notifications even for events + * that it fails to schedule. This is indicated in the status field of + * the notification. This happens in cases that the scheduler cannot + * find a schedule that can handle the event (for example requesting a + * P2P Device discoveribility, while there are other higher priority + * events in the system). + */ + WARN_ONCE(!le32_to_cpu(notif->status), + "Failed to schedule time event\n"); + + if (le32_to_cpu(notif->action) & TE_NOTIF_HOST_EVENT_END) { + IWL_DEBUG_TE(mvm, + "TE ended - current time %lu, estimated end %lu\n", + jiffies, te_data->end_jiffies); + + if (te_data->vif->type == NL80211_IFTYPE_P2P_DEVICE) { + ieee80211_remain_on_channel_expired(mvm->hw); + iwl_mvm_roc_finished(mvm); + } + + /* + * By now, we should have finished association + * and know the dtim period. + */ + if (te_data->vif->type == NL80211_IFTYPE_STATION && + (!te_data->vif->bss_conf.assoc || + !te_data->vif->bss_conf.dtim_period)) { + IWL_ERR(mvm, + "No assocation and the time event is over already...\n"); + ieee80211_connection_loss(te_data->vif); + } + + iwl_mvm_te_clear_data(mvm, te_data); + } else if (le32_to_cpu(notif->action) & TE_NOTIF_HOST_EVENT_START) { + te_data->running = true; + te_data->end_jiffies = jiffies + + TU_TO_JIFFIES(te_data->duration); + + if (te_data->vif->type == NL80211_IFTYPE_P2P_DEVICE) { + set_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status); + ieee80211_ready_on_channel(mvm->hw); + } + } else { + IWL_WARN(mvm, "Got TE with unknown action\n"); + } +} + +/* + * The Rx handler for time event notifications + */ +int iwl_mvm_rx_time_event_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_time_event_notif *notif = (void *)pkt->data; + struct iwl_mvm_time_event_data *te_data, *tmp; + + IWL_DEBUG_TE(mvm, "Time event notification - UID = 0x%x action %d\n", + le32_to_cpu(notif->unique_id), + le32_to_cpu(notif->action)); + + spin_lock_bh(&mvm->time_event_lock); + list_for_each_entry_safe(te_data, tmp, &mvm->time_event_list, list) { + if (le32_to_cpu(notif->unique_id) == te_data->uid) + iwl_mvm_te_handle_notif(mvm, te_data, notif); + } + spin_unlock_bh(&mvm->time_event_lock); + + return 0; +} + +static bool iwl_mvm_time_event_response(struct iwl_notif_wait_data *notif_wait, + struct iwl_rx_packet *pkt, void *data) +{ + struct iwl_mvm *mvm = + container_of(notif_wait, struct iwl_mvm, notif_wait); + struct iwl_mvm_time_event_data *te_data = data; + struct iwl_time_event_resp *resp; + int resp_len = le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; + + if (WARN_ON(pkt->hdr.cmd != TIME_EVENT_CMD)) + return true; + + if (WARN_ON_ONCE(resp_len != sizeof(pkt->hdr) + sizeof(*resp))) { + IWL_ERR(mvm, "Invalid TIME_EVENT_CMD response\n"); + return true; + } + + resp = (void *)pkt->data; + + /* we should never get a response to another TIME_EVENT_CMD here */ + if (WARN_ON_ONCE(le32_to_cpu(resp->id) != te_data->id)) + return false; + + te_data->uid = le32_to_cpu(resp->unique_id); + IWL_DEBUG_TE(mvm, "TIME_EVENT_CMD response - UID = 0x%x\n", + te_data->uid); + return true; +} + +static int iwl_mvm_time_event_send_add(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + struct iwl_mvm_time_event_data *te_data, + struct iwl_time_event_cmd *te_cmd) +{ + static const u8 time_event_response[] = { TIME_EVENT_CMD }; + struct iwl_notification_wait wait_time_event; + int ret; + + lockdep_assert_held(&mvm->mutex); + + IWL_DEBUG_TE(mvm, "Add new TE, duration %d TU\n", + le32_to_cpu(te_cmd->duration)); + + spin_lock_bh(&mvm->time_event_lock); + if (WARN_ON(te_data->id != TE_MAX)) { + spin_unlock_bh(&mvm->time_event_lock); + return -EIO; + } + te_data->vif = vif; + te_data->duration = le32_to_cpu(te_cmd->duration); + te_data->id = le32_to_cpu(te_cmd->id); + list_add_tail(&te_data->list, &mvm->time_event_list); + spin_unlock_bh(&mvm->time_event_lock); + + /* + * Use a notification wait, which really just processes the + * command response and doesn't wait for anything, in order + * to be able to process the response and get the UID inside + * the RX path. Using CMD_WANT_SKB doesn't work because it + * stores the buffer and then wakes up this thread, by which + * time another notification (that the time event started) + * might already be processed unsuccessfully. + */ + iwl_init_notification_wait(&mvm->notif_wait, &wait_time_event, + time_event_response, + ARRAY_SIZE(time_event_response), + iwl_mvm_time_event_response, te_data); + + ret = iwl_mvm_send_cmd_pdu(mvm, TIME_EVENT_CMD, CMD_SYNC, + sizeof(*te_cmd), te_cmd); + if (ret) { + IWL_ERR(mvm, "Couldn't send TIME_EVENT_CMD: %d\n", ret); + iwl_remove_notification(&mvm->notif_wait, &wait_time_event); + goto out_clear_te; + } + + /* No need to wait for anything, so just pass 1 (0 isn't valid) */ + ret = iwl_wait_notification(&mvm->notif_wait, &wait_time_event, 1); + /* should never fail */ + WARN_ON_ONCE(ret); + + if (ret) { + out_clear_te: + spin_lock_bh(&mvm->time_event_lock); + iwl_mvm_te_clear_data(mvm, te_data); + spin_unlock_bh(&mvm->time_event_lock); + } + return ret; +} + +void iwl_mvm_protect_session(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 duration, u32 min_duration) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; + struct iwl_time_event_cmd time_cmd = {}; + + lockdep_assert_held(&mvm->mutex); + + if (te_data->running && + time_after(te_data->end_jiffies, + jiffies + TU_TO_JIFFIES(min_duration))) { + IWL_DEBUG_TE(mvm, "We have enough time in the current TE: %u\n", + jiffies_to_msecs(te_data->end_jiffies - jiffies)); + return; + } + + if (te_data->running) { + IWL_DEBUG_TE(mvm, "extend 0x%x: only %u ms left\n", + te_data->uid, + jiffies_to_msecs(te_data->end_jiffies - jiffies)); + /* + * we don't have enough time + * cancel the current TE and issue a new one + * Of course it would be better to remove the old one only + * when the new one is added, but we don't care if we are off + * channel for a bit. All we need to do, is not to return + * before we actually begin to be on the channel. + */ + iwl_mvm_stop_session_protection(mvm, vif); + } + + time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_ADD); + time_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); + time_cmd.id = cpu_to_le32(TE_BSS_STA_AGGRESSIVE_ASSOC); + + time_cmd.apply_time = + cpu_to_le32(iwl_read_prph(mvm->trans, DEVICE_SYSTEM_TIME_REG)); + + time_cmd.dep_policy = TE_INDEPENDENT; + time_cmd.is_present = cpu_to_le32(1); + time_cmd.max_frags = cpu_to_le32(TE_FRAG_NONE); + time_cmd.max_delay = cpu_to_le32(500); + /* TODO: why do we need to interval = bi if it is not periodic? */ + time_cmd.interval = cpu_to_le32(1); + time_cmd.interval_reciprocal = cpu_to_le32(iwl_mvm_reciprocal(1)); + time_cmd.duration = cpu_to_le32(duration); + time_cmd.repeat = cpu_to_le32(1); + time_cmd.notify = cpu_to_le32(TE_NOTIF_HOST_EVENT_START | + TE_NOTIF_HOST_EVENT_END); + + iwl_mvm_time_event_send_add(mvm, vif, te_data, &time_cmd); +} + +/* + * Explicit request to remove a time event. The removal of a time event needs to + * be synchronized with the flow of a time event's end notification, which also + * removes the time event from the op mode data structures. + */ +void iwl_mvm_remove_time_event(struct iwl_mvm *mvm, + struct iwl_mvm_vif *mvmvif, + struct iwl_mvm_time_event_data *te_data) +{ + struct iwl_time_event_cmd time_cmd = {}; + u32 id, uid; + int ret; + + /* + * It is possible that by the time we got to this point the time + * event was already removed. + */ + spin_lock_bh(&mvm->time_event_lock); + + /* Save time event uid before clearing its data */ + uid = te_data->uid; + id = te_data->id; + + /* + * The clear_data function handles time events that were already removed + */ + iwl_mvm_te_clear_data(mvm, te_data); + spin_unlock_bh(&mvm->time_event_lock); + + /* + * It is possible that by the time we try to remove it, the time event + * has already ended and removed. In such a case there is no need to + * send a removal command. + */ + if (id == TE_MAX) { + IWL_DEBUG_TE(mvm, "TE 0x%x has already ended\n", uid); + return; + } + + /* When we remove a TE, the UID is to be set in the id field */ + time_cmd.id = cpu_to_le32(uid); + time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE); + time_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); + + IWL_DEBUG_TE(mvm, "Removing TE 0x%x\n", le32_to_cpu(time_cmd.id)); + ret = iwl_mvm_send_cmd_pdu(mvm, TIME_EVENT_CMD, CMD_SYNC, + sizeof(time_cmd), &time_cmd); + if (WARN_ON(ret)) + return; +} + +void iwl_mvm_stop_session_protection(struct iwl_mvm *mvm, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; + + lockdep_assert_held(&mvm->mutex); + iwl_mvm_remove_time_event(mvm, mvmvif, te_data); +} + +int iwl_mvm_start_p2p_roc(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + int duration, enum ieee80211_roc_type type) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; + struct iwl_time_event_cmd time_cmd = {}; + + lockdep_assert_held(&mvm->mutex); + if (te_data->running) { + IWL_WARN(mvm, "P2P_DEVICE remain on channel already running\n"); + return -EBUSY; + } + + /* + * Flush the done work, just in case it's still pending, so that + * the work it does can complete and we can accept new frames. + */ + flush_work(&mvm->roc_done_wk); + + time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_ADD); + time_cmd.id_and_color = + cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); + + switch (type) { + case IEEE80211_ROC_TYPE_NORMAL: + time_cmd.id = cpu_to_le32(IWL_MVM_ROC_TE_TYPE_NORMAL); + break; + case IEEE80211_ROC_TYPE_MGMT_TX: + time_cmd.id = cpu_to_le32(IWL_MVM_ROC_TE_TYPE_MGMT_TX); + break; + default: + WARN_ONCE(1, "Got an invalid ROC type\n"); + return -EINVAL; + } + + time_cmd.apply_time = cpu_to_le32(0); + time_cmd.dep_policy = cpu_to_le32(TE_INDEPENDENT); + time_cmd.is_present = cpu_to_le32(1); + time_cmd.interval = cpu_to_le32(1); + + /* + * The P2P Device TEs can have lower priority than other events + * that are being scheduled by the driver/fw, and thus it might not be + * scheduled. To improve the chances of it being scheduled, allow them + * to be fragmented, and in addition allow them to be delayed. + */ + time_cmd.max_frags = cpu_to_le32(MSEC_TO_TU(duration)/20); + time_cmd.max_delay = cpu_to_le32(MSEC_TO_TU(duration/2)); + time_cmd.duration = cpu_to_le32(MSEC_TO_TU(duration)); + time_cmd.repeat = cpu_to_le32(1); + time_cmd.notify = cpu_to_le32(TE_NOTIF_HOST_EVENT_START | + TE_NOTIF_HOST_EVENT_END); + + return iwl_mvm_time_event_send_add(mvm, vif, te_data, &time_cmd); +} + +void iwl_mvm_stop_p2p_roc(struct iwl_mvm *mvm) +{ + struct iwl_mvm_vif *mvmvif; + struct iwl_mvm_time_event_data *te_data; + + lockdep_assert_held(&mvm->mutex); + + /* + * Iterate over the list of time events and find the time event that is + * associated with a P2P_DEVICE interface. + * This assumes that a P2P_DEVICE interface can have only a single time + * event at any given time and this time event coresponds to a ROC + * request + */ + mvmvif = NULL; + spin_lock_bh(&mvm->time_event_lock); + list_for_each_entry(te_data, &mvm->time_event_list, list) { + if (te_data->vif->type == NL80211_IFTYPE_P2P_DEVICE) { + mvmvif = iwl_mvm_vif_from_mac80211(te_data->vif); + break; + } + } + spin_unlock_bh(&mvm->time_event_lock); + + if (!mvmvif) { + IWL_WARN(mvm, "P2P_DEVICE no remain on channel event\n"); + return; + } + + iwl_mvm_remove_time_event(mvm, mvmvif, te_data); + + iwl_mvm_roc_finished(mvm); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/time-event.h b/drivers/net/wireless/iwlwifi/mvm/time-event.h new file mode 100644 index 0000000..f86c510 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/time-event.h @@ -0,0 +1,215 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#ifndef __time_event_h__ +#define __time_event_h__ + +#include "fw-api.h" + +#include "mvm.h" + +/** + * DOC: Time Events - what is it? + * + * Time Events are a fw feature that allows the driver to control the presence + * of the device on the channel. Since the fw supports multiple channels + * concurrently, the fw may choose to jump to another channel at any time. + * In order to make sure that the fw is on a specific channel at a certain time + * and for a certain duration, the driver needs to issue a time event. + * + * The simplest example is for BSS association. The driver issues a time event, + * waits for it to start, and only then tells mac80211 that we can start the + * association. This way, we make sure that the association will be done + * smoothly and won't be interrupted by channel switch decided within the fw. + */ + + /** + * DOC: The flow against the fw + * + * When the driver needs to make sure we are in a certain channel, at a certain + * time and for a certain duration, it sends a Time Event. The flow against the + * fw goes like this: + * 1) Driver sends a TIME_EVENT_CMD to the fw + * 2) Driver gets the response for that command. This response contains the + * Unique ID (UID) of the event. + * 3) The fw sends notification when the event starts. + * + * Of course the API provides various options that allow to cover parameters + * of the flow. + * What is the duration of the event? + * What is the start time of the event? + * Is there an end-time for the event? + * How much can the event be delayed? + * Can the event be split? + * If yes what is the maximal number of chunks? + * etc... + */ + +/** + * DOC: Abstraction to the driver + * + * In order to simplify the use of time events to the rest of the driver, + * we abstract the use of time events. This component provides the functions + * needed by the driver. + */ + +#define IWL_MVM_TE_SESSION_PROTECTION_MAX_TIME_MS 500 +#define IWL_MVM_TE_SESSION_PROTECTION_MIN_TIME_MS 400 + +/** + * iwl_mvm_protect_session - start / extend the session protection. + * @mvm: the mvm component + * @vif: the virtual interface for which the session is issued + * @duration: the duration of the session in TU. + * @min_duration: will start a new session if the current session will end + * in less than min_duration. + * + * This function can be used to start a session protection which means that the + * fw will stay on the channel for %duration_ms milliseconds. This function + * will block (sleep) until the session starts. This function can also be used + * to extend a currently running session. + * This function is meant to be used for BSS association for example, where we + * want to make sure that the fw stays on the channel during the association. + */ +void iwl_mvm_protect_session(struct iwl_mvm *mvm, + struct ieee80211_vif *vif, + u32 duration, u32 min_duration); + +/** + * iwl_mvm_stop_session_protection - cancel the session protection. + * @mvm: the mvm component + * @vif: the virtual interface for which the session is issued + * + * This functions cancels the session protection which is an act of good + * citizenship. If it is not needed any more it should be cancelled because + * the other bindings wait for the medium during that time. + * This funtions doesn't sleep. + */ +void iwl_mvm_stop_session_protection(struct iwl_mvm *mvm, + struct ieee80211_vif *vif); + +/* + * iwl_mvm_rx_time_event_notif - handles %TIME_EVENT_NOTIFICATION. + */ +int iwl_mvm_rx_time_event_notif(struct iwl_mvm *mvm, + struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd); + +/** + * iwl_mvm_start_p2p_roc - start remain on channel for p2p device functionlity + * @mvm: the mvm component + * @vif: the virtual interface for which the roc is requested. It is assumed + * that the vif type is NL80211_IFTYPE_P2P_DEVICE + * @duration: the requested duration in millisecond for the fw to be on the + * channel that is bound to the vif. + * @type: the remain on channel request type + * + * This function can be used to issue a remain on channel session, + * which means that the fw will stay in the channel for the request %duration + * milliseconds. The function is async, meaning that it only issues the ROC + * request but does not wait for it to start. Once the FW is ready to serve the + * ROC request, it will issue a notification to the driver that it is on the + * requested channel. Once the FW completes the ROC request it will issue + * another notification to the driver. + */ +int iwl_mvm_start_p2p_roc(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + int duration, enum ieee80211_roc_type type); + +/** + * iwl_mvm_stop_p2p_roc - stop remain on channel for p2p device functionlity + * @mvm: the mvm component + * + * This function can be used to cancel an ongoing ROC session. + * The function is async, it will instruct the FW to stop serving the ROC + * session, but will not wait for the actual stopping of the session. + */ +void iwl_mvm_stop_p2p_roc(struct iwl_mvm *mvm); + +/** + * iwl_mvm_remove_time_event - general function to clean up of time event + * @mvm: the mvm component + * @vif: the vif to which the time event belongs + * @te_data: the time event data that corresponds to that time event + * + * This function can be used to cancel a time event regardless its type. + * It is useful for cleaning up time events running before removing an + * interface. + */ +void iwl_mvm_remove_time_event(struct iwl_mvm *mvm, + struct iwl_mvm_vif *mvmvif, + struct iwl_mvm_time_event_data *te_data); + +/** + * iwl_mvm_te_clear_data - remove time event from list + * @mvm: the mvm component + * @te_data: the time event data to remove + * + * This function is mostly internal, it is made available here only + * for firmware restart purposes. + */ +void iwl_mvm_te_clear_data(struct iwl_mvm *mvm, + struct iwl_mvm_time_event_data *te_data); + +void iwl_mvm_roc_done_wk(struct work_struct *wk); + +#endif /* __time_event_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/tx.c b/drivers/net/wireless/iwlwifi/mvm/tx.c new file mode 100644 index 0000000..fee2db8 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/tx.c @@ -0,0 +1,939 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#include +#include + +#include "iwl-trans.h" +#include "iwl-eeprom-parse.h" +#include "mvm.h" +#include "sta.h" + +/* + * Sets most of the Tx cmd's fields + */ +static void iwl_mvm_set_tx_cmd(struct iwl_mvm *mvm, struct sk_buff *skb, + struct iwl_tx_cmd *tx_cmd, + struct ieee80211_tx_info *info, u8 sta_id) +{ + struct ieee80211_hdr *hdr = (void *)skb->data; + __le16 fc = hdr->frame_control; + u32 tx_flags = le32_to_cpu(tx_cmd->tx_flags); + u32 len = skb->len + FCS_LEN; + + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) + tx_flags |= TX_CMD_FLG_ACK; + else + tx_flags &= ~TX_CMD_FLG_ACK; + + if (ieee80211_is_probe_resp(fc)) + tx_flags |= TX_CMD_FLG_TSF; + else if (ieee80211_is_back_req(fc)) + tx_flags |= TX_CMD_FLG_ACK | TX_CMD_FLG_BAR; + + /* High prio packet (wrt. BT coex) if it is EAPOL, MCAST or MGMT */ + if (info->band == IEEE80211_BAND_2GHZ && + (skb->protocol == cpu_to_be16(ETH_P_PAE) || + is_multicast_ether_addr(hdr->addr1) || + ieee80211_is_back_req(fc) || + ieee80211_is_mgmt(fc))) + tx_flags |= TX_CMD_FLG_BT_DIS; + + if (ieee80211_has_morefrags(fc)) + tx_flags |= TX_CMD_FLG_MORE_FRAG; + + if (ieee80211_is_data_qos(fc)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + tx_cmd->tid_tspec = qc[0] & 0xf; + tx_flags &= ~TX_CMD_FLG_SEQ_CTL; + } else { + tx_cmd->tid_tspec = IWL_TID_NON_QOS; + if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) + tx_flags |= TX_CMD_FLG_SEQ_CTL; + else + tx_flags &= ~TX_CMD_FLG_SEQ_CTL; + } + + if (ieee80211_is_mgmt(fc)) { + if (ieee80211_is_assoc_req(fc) || ieee80211_is_reassoc_req(fc)) + tx_cmd->pm_frame_timeout = cpu_to_le16(3); + else + tx_cmd->pm_frame_timeout = cpu_to_le16(2); + + /* The spec allows Action frames in A-MPDU, we don't support + * it + */ + WARN_ON_ONCE(info->flags & IEEE80211_TX_CTL_AMPDU); + } else { + tx_cmd->pm_frame_timeout = 0; + } + + if (info->flags & IEEE80211_TX_CTL_AMPDU) + tx_flags |= TX_CMD_FLG_PROT_REQUIRE; + + if (ieee80211_is_data(fc) && len > mvm->rts_threshold && + !is_multicast_ether_addr(ieee80211_get_DA(hdr))) + tx_flags |= TX_CMD_FLG_PROT_REQUIRE; + + tx_cmd->driver_txop = 0; + tx_cmd->tx_flags = cpu_to_le32(tx_flags); + /* Total # bytes to be transmitted */ + tx_cmd->len = cpu_to_le16((u16)skb->len); + tx_cmd->next_frame_len = 0; + tx_cmd->life_time = cpu_to_le32(TX_CMD_LIFE_TIME_INFINITE); + tx_cmd->sta_id = sta_id; +} + +/* + * Sets the fields in the Tx cmd that are rate related + */ +static void iwl_mvm_set_tx_cmd_rate(struct iwl_mvm *mvm, + struct iwl_tx_cmd *tx_cmd, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + __le16 fc) +{ + u32 rate_flags; + int rate_idx; + u8 rate_plcp; + + /* Set retry limit on RTS packets */ + tx_cmd->rts_retry_limit = IWL_RTS_DFAULT_RETRY_LIMIT; + + /* Set retry limit on DATA packets and Probe Responses*/ + if (ieee80211_is_probe_resp(fc)) { + tx_cmd->data_retry_limit = IWL_MGMT_DFAULT_RETRY_LIMIT; + tx_cmd->rts_retry_limit = + min(tx_cmd->data_retry_limit, tx_cmd->rts_retry_limit); + } else if (ieee80211_is_back_req(fc)) { + tx_cmd->data_retry_limit = IWL_BAR_DFAULT_RETRY_LIMIT; + } else { + tx_cmd->data_retry_limit = IWL_DEFAULT_TX_RETRY; + } + + /* + * for data packets, rate info comes from the table inside he fw. This + * table is controlled by LINK_QUALITY commands + */ + + if (ieee80211_is_data(fc) && sta) { + tx_cmd->initial_rate_index = 0; + tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_STA_RATE); + return; + } else if (ieee80211_is_back_req(fc)) { + tx_cmd->tx_flags |= + cpu_to_le32(TX_CMD_FLG_ACK | TX_CMD_FLG_BAR); + } + + /* HT rate doesn't make sense for a non data frame */ + WARN_ONCE(info->control.rates[0].flags & IEEE80211_TX_RC_MCS, + "Got an HT rate for a non data frame 0x%x\n", + info->control.rates[0].flags); + + rate_idx = info->control.rates[0].idx; + /* if the rate isn't a well known legacy rate, take the lowest one */ + if (rate_idx < 0 || rate_idx > IWL_RATE_COUNT_LEGACY) + rate_idx = rate_lowest_index( + &mvm->nvm_data->bands[info->band], sta); + + /* For 5 GHZ band, remap mac80211 rate indices into driver indices */ + if (info->band == IEEE80211_BAND_5GHZ) + rate_idx += IWL_FIRST_OFDM_RATE; + + /* For 2.4 GHZ band, check that there is no need to remap */ + BUILD_BUG_ON(IWL_FIRST_CCK_RATE != 0); + + /* Get PLCP rate for tx_cmd->rate_n_flags */ + rate_plcp = iwl_mvm_mac80211_idx_to_hwrate(rate_idx); + + mvm->mgmt_last_antenna_idx = + iwl_mvm_next_antenna(mvm, iwl_fw_valid_tx_ant(mvm->fw), + mvm->mgmt_last_antenna_idx); + rate_flags = BIT(mvm->mgmt_last_antenna_idx) << RATE_MCS_ANT_POS; + + /* Set CCK flag as needed */ + if ((rate_idx >= IWL_FIRST_CCK_RATE) && (rate_idx <= IWL_LAST_CCK_RATE)) + rate_flags |= RATE_MCS_CCK_MSK; + + /* Set the rate in the TX cmd */ + tx_cmd->rate_n_flags = cpu_to_le32((u32)rate_plcp | rate_flags); +} + +/* + * Sets the fields in the Tx cmd that are crypto related + */ +static void iwl_mvm_set_tx_cmd_crypto(struct iwl_mvm *mvm, + struct ieee80211_tx_info *info, + struct iwl_tx_cmd *tx_cmd, + struct sk_buff *skb_frag) +{ + struct ieee80211_key_conf *keyconf = info->control.hw_key; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_CCMP: + tx_cmd->sec_ctl = TX_CMD_SEC_CCM; + memcpy(tx_cmd->key, keyconf->key, keyconf->keylen); + if (info->flags & IEEE80211_TX_CTL_AMPDU) + tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_CCMP_AGG); + break; + + case WLAN_CIPHER_SUITE_TKIP: + tx_cmd->sec_ctl = TX_CMD_SEC_TKIP; + ieee80211_get_tkip_p2k(keyconf, skb_frag, tx_cmd->key); + break; + + case WLAN_CIPHER_SUITE_WEP104: + tx_cmd->sec_ctl |= TX_CMD_SEC_KEY128; + /* fall through */ + case WLAN_CIPHER_SUITE_WEP40: + tx_cmd->sec_ctl |= TX_CMD_SEC_WEP | + ((keyconf->keyidx << TX_CMD_SEC_WEP_KEY_IDX_POS) & + TX_CMD_SEC_WEP_KEY_IDX_MSK); + + memcpy(&tx_cmd->key[3], keyconf->key, keyconf->keylen); + break; + default: + IWL_ERR(mvm, "Unknown encode cipher %x\n", keyconf->cipher); + break; + } +} + +/* + * Allocates and sets the Tx cmd the driver data pointers in the skb + */ +static struct iwl_device_cmd * +iwl_mvm_set_tx_params(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_sta *sta, u8 sta_id) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct iwl_device_cmd *dev_cmd; + struct iwl_tx_cmd *tx_cmd; + + dev_cmd = iwl_trans_alloc_tx_cmd(mvm->trans); + + if (unlikely(!dev_cmd)) + return NULL; + + memset(dev_cmd, 0, sizeof(*dev_cmd)); + tx_cmd = (struct iwl_tx_cmd *)dev_cmd->payload; + + if (info->control.hw_key) + iwl_mvm_set_tx_cmd_crypto(mvm, info, tx_cmd, skb); + + iwl_mvm_set_tx_cmd(mvm, skb, tx_cmd, info, sta_id); + + iwl_mvm_set_tx_cmd_rate(mvm, tx_cmd, info, sta, hdr->frame_control); + + memset(&info->status, 0, sizeof(info->status)); + + info->driver_data[0] = NULL; + info->driver_data[1] = dev_cmd; + + return dev_cmd; +} + +int iwl_mvm_tx_skb_non_sta(struct iwl_mvm *mvm, struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct iwl_device_cmd *dev_cmd; + struct iwl_tx_cmd *tx_cmd; + u8 sta_id; + + if (WARN_ON_ONCE(info->flags & IEEE80211_TX_CTL_AMPDU)) + return -1; + + if (WARN_ON_ONCE(info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM && + (!info->control.vif || + info->hw_queue != info->control.vif->cab_queue))) + return -1; + + /* + * If the interface on which frame is sent is the P2P_DEVICE + * or an AP/GO interface use the broadcast station associated + * with it; otherwise use the AUX station. + */ + if (info->control.vif && + (info->control.vif->type == NL80211_IFTYPE_P2P_DEVICE || + info->control.vif->type == NL80211_IFTYPE_AP)) { + struct iwl_mvm_vif *mvmvif = + iwl_mvm_vif_from_mac80211(info->control.vif); + sta_id = mvmvif->bcast_sta.sta_id; + } else { + sta_id = mvm->aux_sta.sta_id; + } + + IWL_DEBUG_TX(mvm, "station Id %d, queue=%d\n", sta_id, info->hw_queue); + + dev_cmd = iwl_mvm_set_tx_params(mvm, skb, NULL, sta_id); + if (!dev_cmd) + return -1; + + /* From now on, we cannot access info->control */ + tx_cmd = (struct iwl_tx_cmd *)dev_cmd->payload; + + /* Copy MAC header from skb into command buffer */ + memcpy(tx_cmd->hdr, hdr, ieee80211_hdrlen(hdr->frame_control)); + + if (iwl_trans_tx(mvm->trans, skb, dev_cmd, info->hw_queue)) { + iwl_trans_free_tx_cmd(mvm->trans, dev_cmd); + return -1; + } + + return 0; +} + +/* + * Sets the fields in the Tx cmd that are crypto related + */ +int iwl_mvm_tx_skb(struct iwl_mvm *mvm, struct sk_buff *skb, + struct ieee80211_sta *sta) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct iwl_mvm_sta *mvmsta; + struct iwl_device_cmd *dev_cmd; + struct iwl_tx_cmd *tx_cmd; + __le16 fc; + u16 seq_number = 0; + u8 tid = IWL_MAX_TID_COUNT; + u8 txq_id = info->hw_queue; + bool is_data_qos = false, is_ampdu = false; + + mvmsta = (void *)sta->drv_priv; + fc = hdr->frame_control; + + if (WARN_ON_ONCE(!mvmsta)) + return -1; + + if (WARN_ON_ONCE(mvmsta->sta_id == IWL_MVM_STATION_COUNT)) + return -1; + + dev_cmd = iwl_mvm_set_tx_params(mvm, skb, sta, mvmsta->sta_id); + if (!dev_cmd) + goto drop; + + tx_cmd = (struct iwl_tx_cmd *)dev_cmd->payload; + /* From now on, we cannot access info->control */ + + spin_lock(&mvmsta->lock); + + if (ieee80211_is_data_qos(fc) && !ieee80211_is_qos_nullfunc(fc)) { + u8 *qc = NULL; + qc = ieee80211_get_qos_ctl(hdr); + tid = qc[0] & IEEE80211_QOS_CTL_TID_MASK; + if (WARN_ON_ONCE(tid >= IWL_MAX_TID_COUNT)) + goto drop_unlock_sta; + + seq_number = mvmsta->tid_data[tid].seq_number; + seq_number &= IEEE80211_SCTL_SEQ; + hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); + hdr->seq_ctrl |= cpu_to_le16(seq_number); + seq_number += 0x10; + is_data_qos = true; + is_ampdu = info->flags & IEEE80211_TX_CTL_AMPDU; + } + + /* Copy MAC header from skb into command buffer */ + memcpy(tx_cmd->hdr, hdr, ieee80211_hdrlen(fc)); + + WARN_ON_ONCE(info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM); + + if (is_ampdu) { + if (WARN_ON_ONCE(mvmsta->tid_data[tid].state != IWL_AGG_ON)) + goto drop_unlock_sta; + txq_id = mvmsta->tid_data[tid].txq_id; + } + + IWL_DEBUG_TX(mvm, "TX to [%d|%d] Q:%d - seq: 0x%x\n", mvmsta->sta_id, + tid, txq_id, seq_number); + + /* NOTE: aggregation will need changes here (for txq id) */ + if (iwl_trans_tx(mvm->trans, skb, dev_cmd, txq_id)) + goto drop_unlock_sta; + + if (is_data_qos && !ieee80211_has_morefrags(fc)) + mvmsta->tid_data[tid].seq_number = seq_number; + + spin_unlock(&mvmsta->lock); + + if (txq_id < IWL_MVM_FIRST_AGG_QUEUE) + atomic_inc(&mvm->pending_frames[mvmsta->sta_id]); + + return 0; + +drop_unlock_sta: + iwl_trans_free_tx_cmd(mvm->trans, dev_cmd); + spin_unlock(&mvmsta->lock); +drop: + return -1; +} + +static void iwl_mvm_check_ratid_empty(struct iwl_mvm *mvm, + struct ieee80211_sta *sta, u8 tid) +{ + struct iwl_mvm_sta *mvmsta = (void *)sta->drv_priv; + struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; + struct ieee80211_vif *vif = mvmsta->vif; + + lockdep_assert_held(&mvmsta->lock); + + if (tid_data->ssn != tid_data->next_reclaimed) + return; + + switch (tid_data->state) { + case IWL_EMPTYING_HW_QUEUE_ADDBA: + IWL_DEBUG_TX_QUEUES(mvm, + "Can continue addBA flow ssn = next_recl = %d\n", + tid_data->next_reclaimed); + tid_data->state = IWL_AGG_STARTING; + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + + case IWL_EMPTYING_HW_QUEUE_DELBA: + IWL_DEBUG_TX_QUEUES(mvm, + "Can continue DELBA flow ssn = next_recl = %d\n", + tid_data->next_reclaimed); + iwl_trans_txq_disable(mvm->trans, tid_data->txq_id); + tid_data->state = IWL_AGG_OFF; + /* + * we can't hold the mutex - but since we are after a sequence + * point (call to iwl_trans_txq_disable), so we don't even need + * a memory barrier. + */ + mvm->queue_to_mac80211[tid_data->txq_id] = + IWL_INVALID_MAC80211_QUEUE; + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + + default: + break; + } +} + +#ifdef CPTCFG_IWLWIFI_DEBUG +const char *iwl_mvm_get_tx_fail_reason(u32 status) +{ +#define TX_STATUS_FAIL(x) case TX_STATUS_FAIL_ ## x: return #x +#define TX_STATUS_POSTPONE(x) case TX_STATUS_POSTPONE_ ## x: return #x + + switch (status & TX_STATUS_MSK) { + case TX_STATUS_SUCCESS: + return "SUCCESS"; + TX_STATUS_POSTPONE(DELAY); + TX_STATUS_POSTPONE(FEW_BYTES); + TX_STATUS_POSTPONE(BT_PRIO); + TX_STATUS_POSTPONE(QUIET_PERIOD); + TX_STATUS_POSTPONE(CALC_TTAK); + TX_STATUS_FAIL(INTERNAL_CROSSED_RETRY); + TX_STATUS_FAIL(SHORT_LIMIT); + TX_STATUS_FAIL(LONG_LIMIT); + TX_STATUS_FAIL(UNDERRUN); + TX_STATUS_FAIL(DRAIN_FLOW); + TX_STATUS_FAIL(RFKILL_FLUSH); + TX_STATUS_FAIL(LIFE_EXPIRE); + TX_STATUS_FAIL(DEST_PS); + TX_STATUS_FAIL(HOST_ABORTED); + TX_STATUS_FAIL(BT_RETRY); + TX_STATUS_FAIL(STA_INVALID); + TX_STATUS_FAIL(FRAG_DROPPED); + TX_STATUS_FAIL(TID_DISABLE); + TX_STATUS_FAIL(FIFO_FLUSHED); + TX_STATUS_FAIL(SMALL_CF_POLL); + TX_STATUS_FAIL(FW_DROP); + TX_STATUS_FAIL(STA_COLOR_MISMATCH); + } + + return "UNKNOWN"; + +#undef TX_STATUS_FAIL +#undef TX_STATUS_POSTPONE +} +#endif /* CPTCFG_IWLWIFI_DEBUG */ + +/** + * translate ucode response to mac80211 tx status control values + */ +static void iwl_mvm_hwrate_to_tx_control(u32 rate_n_flags, + struct ieee80211_tx_info *info) +{ + struct ieee80211_tx_rate *r = &info->status.rates[0]; + + info->status.antenna = + ((rate_n_flags & RATE_MCS_ANT_ABC_MSK) >> RATE_MCS_ANT_POS); + if (rate_n_flags & RATE_HT_MCS_GF_MSK) + r->flags |= IEEE80211_TX_RC_GREEN_FIELD; + switch (rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK) { + case RATE_MCS_CHAN_WIDTH_20: + break; + case RATE_MCS_CHAN_WIDTH_40: + r->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; + break; + case RATE_MCS_CHAN_WIDTH_80: + r->flags |= IEEE80211_TX_RC_80_MHZ_WIDTH; + break; + case RATE_MCS_CHAN_WIDTH_160: + r->flags |= IEEE80211_TX_RC_160_MHZ_WIDTH; + break; + } + if (rate_n_flags & RATE_MCS_SGI_MSK) + r->flags |= IEEE80211_TX_RC_SHORT_GI; + if (rate_n_flags & RATE_MCS_HT_MSK) { + r->flags |= IEEE80211_TX_RC_MCS; + r->idx = rate_n_flags & RATE_HT_MCS_INDEX_MSK; + } else if (rate_n_flags & RATE_MCS_VHT_MSK) { + ieee80211_rate_set_vht( + r, rate_n_flags & RATE_VHT_MCS_RATE_CODE_MSK, + ((rate_n_flags & RATE_VHT_MCS_NSS_MSK) >> + RATE_VHT_MCS_NSS_POS) + 1); + r->flags |= IEEE80211_TX_RC_VHT_MCS; + } else { + r->idx = iwl_mvm_legacy_rate_to_mac80211_idx(rate_n_flags, + info->band); + } +} + +static void iwl_mvm_rx_tx_cmd_single(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + struct ieee80211_sta *sta; + u16 sequence = le16_to_cpu(pkt->hdr.sequence); + int txq_id = SEQ_TO_QUEUE(sequence); + struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data; + int sta_id = IWL_MVM_TX_RES_GET_RA(tx_resp->ra_tid); + int tid = IWL_MVM_TX_RES_GET_TID(tx_resp->ra_tid); + u32 status = le16_to_cpu(tx_resp->status.status); + u16 ssn = iwl_mvm_get_scd_ssn(tx_resp); + struct iwl_mvm_sta *mvmsta; + struct sk_buff_head skbs; + u8 skb_freed = 0; + u16 next_reclaimed, seq_ctl; + + __skb_queue_head_init(&skbs); + + seq_ctl = le16_to_cpu(tx_resp->seq_ctl); + + /* we can free until ssn % q.n_bd not inclusive */ + iwl_trans_reclaim(mvm->trans, txq_id, ssn, &skbs); + + while (!skb_queue_empty(&skbs)) { + struct sk_buff *skb = __skb_dequeue(&skbs); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + skb_freed++; + + iwl_trans_free_tx_cmd(mvm->trans, info->driver_data[1]); + + memset(&info->status, 0, sizeof(info->status)); + + info->flags &= ~IEEE80211_TX_CTL_AMPDU; + + /* inform mac80211 about what happened with the frame */ + switch (status & TX_STATUS_MSK) { + case TX_STATUS_SUCCESS: + case TX_STATUS_DIRECT_DONE: + info->flags |= IEEE80211_TX_STAT_ACK; + break; + case TX_STATUS_FAIL_DEST_PS: + info->flags |= IEEE80211_TX_STAT_TX_FILTERED; + break; + default: + break; + } + + info->status.rates[0].count = tx_resp->failure_frame + 1; + iwl_mvm_hwrate_to_tx_control(le32_to_cpu(tx_resp->initial_rate), + info); + + /* Single frame failure in an AMPDU queue => send BAR */ + if (txq_id >= IWL_MVM_FIRST_AGG_QUEUE && + !(info->flags & IEEE80211_TX_STAT_ACK)) + info->flags |= IEEE80211_TX_STAT_AMPDU_NO_BACK; + + /* W/A FW bug: seq_ctl is wrong when the status isn't success */ + if (status != TX_STATUS_SUCCESS) { + struct ieee80211_hdr *hdr = (void *)skb->data; + seq_ctl = le16_to_cpu(hdr->seq_ctrl); + } + + ieee80211_tx_status_ni(mvm->hw, skb); + } + + if (txq_id >= IWL_MVM_FIRST_AGG_QUEUE) { + /* If this is an aggregation queue, we use the ssn since: + * ssn = wifi seq_num % 256. + * The seq_ctl is the sequence control of the packet to which + * this Tx response relates. But if there is a hole in the + * bitmap of the BA we received, this Tx response may allow to + * reclaim the hole and all the subsequent packets that were + * already acked. In that case, seq_ctl != ssn, and the next + * packet to be reclaimed will be ssn and not seq_ctl. In that + * case, several packets will be reclaimed even if + * frame_count = 1. + * + * The ssn is the index (% 256) of the latest packet that has + * treated (acked / dropped) + 1. + */ + next_reclaimed = ssn; + } else { + /* The next packet to be reclaimed is the one after this one */ + next_reclaimed = IEEE80211_SEQ_TO_SN(seq_ctl + 0x10); + } + + IWL_DEBUG_TX_REPLY(mvm, + "TXQ %d status %s (0x%08x)\n", + txq_id, iwl_mvm_get_tx_fail_reason(status), status); + + IWL_DEBUG_TX_REPLY(mvm, + "\t\t\t\tinitial_rate 0x%x retries %d, idx=%d ssn=%d next_reclaimed=0x%x seq_ctl=0x%x\n", + le32_to_cpu(tx_resp->initial_rate), + tx_resp->failure_frame, SEQ_TO_INDEX(sequence), + ssn, next_reclaimed, seq_ctl); + + rcu_read_lock(); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + + if (!IS_ERR_OR_NULL(sta)) { + mvmsta = (void *)sta->drv_priv; + + if (tid != IWL_TID_NON_QOS) { + struct iwl_mvm_tid_data *tid_data = + &mvmsta->tid_data[tid]; + + spin_lock_bh(&mvmsta->lock); + tid_data->next_reclaimed = next_reclaimed; + IWL_DEBUG_TX_REPLY(mvm, "Next reclaimed packet:%d\n", + next_reclaimed); + iwl_mvm_check_ratid_empty(mvm, sta, tid); + spin_unlock_bh(&mvmsta->lock); + } + +#ifdef CONFIG_PM_SLEEP + mvmsta->last_seq_ctl = seq_ctl; +#endif + } else { + sta = NULL; + mvmsta = NULL; + } + + /* + * If the txq is not an AMPDU queue, there is no chance we freed + * several skbs. Check that out... + */ + if (txq_id < IWL_MVM_FIRST_AGG_QUEUE && !WARN_ON(skb_freed > 1) && + atomic_sub_and_test(skb_freed, &mvm->pending_frames[sta_id])) { + if (mvmsta) { + /* + * If there are no pending frames for this STA, notify + * mac80211 that this station can go to sleep in its + * STA table. + */ + if (mvmsta->vif->type == NL80211_IFTYPE_AP) + ieee80211_sta_block_awake(mvm->hw, sta, false); + /* + * We might very well have taken mvmsta pointer while + * the station was being removed. The remove flow might + * have seen a pending_frame (because we didn't take + * the lock) even if now the queues are drained. So make + * really sure now that this the station is not being + * removed. If it is, run the drain worker to remove it. + */ + spin_lock_bh(&mvmsta->lock); + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + if (IS_ERR_OR_NULL(sta)) { + /* + * Station disappeared in the meantime: + * so we are draining. + */ + set_bit(sta_id, mvm->sta_drained); + schedule_work(&mvm->sta_drained_wk); + } + spin_unlock_bh(&mvmsta->lock); + } else if (!mvmsta) { + /* Tx response without STA, so we are draining */ + set_bit(sta_id, mvm->sta_drained); + schedule_work(&mvm->sta_drained_wk); + } + } + + rcu_read_unlock(); +} + +#ifdef CPTCFG_IWLWIFI_DEBUG +#define AGG_TX_STATE_(x) case AGG_TX_STATE_ ## x: return #x +static const char *iwl_get_agg_tx_status(u16 status) +{ + switch (status & AGG_TX_STATE_STATUS_MSK) { + AGG_TX_STATE_(TRANSMITTED); + AGG_TX_STATE_(UNDERRUN); + AGG_TX_STATE_(BT_PRIO); + AGG_TX_STATE_(FEW_BYTES); + AGG_TX_STATE_(ABORT); + AGG_TX_STATE_(LAST_SENT_TTL); + AGG_TX_STATE_(LAST_SENT_TRY_CNT); + AGG_TX_STATE_(LAST_SENT_BT_KILL); + AGG_TX_STATE_(SCD_QUERY); + AGG_TX_STATE_(TEST_BAD_CRC32); + AGG_TX_STATE_(RESPONSE); + AGG_TX_STATE_(DUMP_TX); + AGG_TX_STATE_(DELAY_TX); + } + + return "UNKNOWN"; +} + +static void iwl_mvm_rx_tx_cmd_agg_dbg(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data; + struct agg_tx_status *frame_status = &tx_resp->status; + int i; + + for (i = 0; i < tx_resp->frame_count; i++) { + u16 fstatus = le16_to_cpu(frame_status[i].status); + + IWL_DEBUG_TX_REPLY(mvm, + "status %s (0x%04x), try-count (%d) seq (0x%x)\n", + iwl_get_agg_tx_status(fstatus), + fstatus & AGG_TX_STATE_STATUS_MSK, + (fstatus & AGG_TX_STATE_TRY_CNT_MSK) >> + AGG_TX_STATE_TRY_CNT_POS, + le16_to_cpu(frame_status[i].sequence)); + } +} +#else +static void iwl_mvm_rx_tx_cmd_agg_dbg(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{} +#endif /* CPTCFG_IWLWIFI_DEBUG */ + +static void iwl_mvm_rx_tx_cmd_agg(struct iwl_mvm *mvm, + struct iwl_rx_packet *pkt) +{ + struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data; + int sta_id = IWL_MVM_TX_RES_GET_RA(tx_resp->ra_tid); + int tid = IWL_MVM_TX_RES_GET_TID(tx_resp->ra_tid); + u16 sequence = le16_to_cpu(pkt->hdr.sequence); + struct ieee80211_sta *sta; + + if (WARN_ON_ONCE(SEQ_TO_QUEUE(sequence) < IWL_MVM_FIRST_AGG_QUEUE)) + return; + + if (WARN_ON_ONCE(tid == IWL_TID_NON_QOS)) + return; + + iwl_mvm_rx_tx_cmd_agg_dbg(mvm, pkt); + + rcu_read_lock(); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + + if (!WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) { + struct iwl_mvm_sta *mvmsta = (void *)sta->drv_priv; + mvmsta->tid_data[tid].rate_n_flags = + le32_to_cpu(tx_resp->initial_rate); + } + + rcu_read_unlock(); +} + +int iwl_mvm_rx_tx_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data; + + if (tx_resp->frame_count == 1) + iwl_mvm_rx_tx_cmd_single(mvm, pkt); + else + iwl_mvm_rx_tx_cmd_agg(mvm, pkt); + + return 0; +} + +int iwl_mvm_rx_ba_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_mvm_ba_notif *ba_notif = (void *)pkt->data; + struct sk_buff_head reclaimed_skbs; + struct iwl_mvm_tid_data *tid_data; + struct ieee80211_tx_info *info; + struct ieee80211_sta *sta; + struct iwl_mvm_sta *mvmsta; + struct ieee80211_hdr *hdr; + struct sk_buff *skb; + int sta_id, tid, freed; + + /* "flow" corresponds to Tx queue */ + u16 scd_flow = le16_to_cpu(ba_notif->scd_flow); + + /* "ssn" is start of block-ack Tx window, corresponds to index + * (in Tx queue's circular buffer) of first TFD/frame in window */ + u16 ba_resp_scd_ssn = le16_to_cpu(ba_notif->scd_ssn); + + sta_id = ba_notif->sta_id; + tid = ba_notif->tid; + + rcu_read_lock(); + + sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + + /* Reclaiming frames for a station that has been deleted ? */ + if (WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) { + rcu_read_unlock(); + return 0; + } + + mvmsta = (void *)sta->drv_priv; + tid_data = &mvmsta->tid_data[tid]; + + if (WARN_ONCE(tid_data->txq_id != scd_flow, "Q %d, tid %d, flow %d", + tid_data->txq_id, tid, scd_flow)) { + rcu_read_unlock(); + return 0; + } + + spin_lock_bh(&mvmsta->lock); + + __skb_queue_head_init(&reclaimed_skbs); + + /* + * Release all TFDs before the SSN, i.e. all TFDs in front of + * block-ack window (we assume that they've been successfully + * transmitted ... if not, it's too late anyway). + */ + iwl_trans_reclaim(mvm->trans, scd_flow, ba_resp_scd_ssn, + &reclaimed_skbs); + + IWL_DEBUG_TX_REPLY(mvm, + "BA_NOTIFICATION Received from %pM, sta_id = %d\n", + (u8 *)&ba_notif->sta_addr_lo32, + ba_notif->sta_id); + IWL_DEBUG_TX_REPLY(mvm, + "TID = %d, SeqCtl = %d, bitmap = 0x%llx, scd_flow = %d, scd_ssn = %d sent:%d, acked:%d\n", + ba_notif->tid, le16_to_cpu(ba_notif->seq_ctl), + (unsigned long long)le64_to_cpu(ba_notif->bitmap), + scd_flow, ba_resp_scd_ssn, ba_notif->txed, + ba_notif->txed_2_done); + + tid_data->next_reclaimed = ba_resp_scd_ssn; + + iwl_mvm_check_ratid_empty(mvm, sta, tid); + + freed = 0; + + skb_queue_walk(&reclaimed_skbs, skb) { + hdr = (struct ieee80211_hdr *)skb->data; + + if (ieee80211_is_data_qos(hdr->frame_control)) + freed++; + else + WARN_ON_ONCE(1); + + info = IEEE80211_SKB_CB(skb); + iwl_trans_free_tx_cmd(mvm->trans, info->driver_data[1]); + + if (freed == 1) { + /* this is the first skb we deliver in this batch */ + /* put the rate scaling data there */ + info = IEEE80211_SKB_CB(skb); + memset(&info->status, 0, sizeof(info->status)); + info->flags |= IEEE80211_TX_STAT_ACK; + info->flags |= IEEE80211_TX_STAT_AMPDU; + info->status.ampdu_ack_len = ba_notif->txed_2_done; + info->status.ampdu_len = ba_notif->txed; + iwl_mvm_hwrate_to_tx_control(tid_data->rate_n_flags, + info); + } + } + + spin_unlock_bh(&mvmsta->lock); + + rcu_read_unlock(); + + while (!skb_queue_empty(&reclaimed_skbs)) { + skb = __skb_dequeue(&reclaimed_skbs); + ieee80211_tx_status_ni(mvm->hw, skb); + } + + return 0; +} + +int iwl_mvm_flush_tx_path(struct iwl_mvm *mvm, u32 tfd_msk, bool sync) +{ + int ret; + struct iwl_tx_path_flush_cmd flush_cmd = { + .queues_ctl = cpu_to_le32(tfd_msk), + .flush_ctl = cpu_to_le16(DUMP_TX_FIFO_FLUSH), + }; + + u32 flags = sync ? CMD_SYNC : CMD_ASYNC; + + ret = iwl_mvm_send_cmd_pdu(mvm, TXPATH_FLUSH, flags, + sizeof(flush_cmd), &flush_cmd); + if (ret) + IWL_ERR(mvm, "Failed to send flush command (%d)\n", ret); + return ret; +} diff --git a/drivers/net/wireless/iwlwifi/mvm/utils.c b/drivers/net/wireless/iwlwifi/mvm/utils.c new file mode 100644 index 0000000..687b34e --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/utils.c @@ -0,0 +1,473 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#include + +#include "iwl-debug.h" +#include "iwl-io.h" + +#include "mvm.h" +#include "fw-api-rs.h" + +/* + * Will return 0 even if the cmd failed when RFKILL is asserted unless + * CMD_WANT_SKB is set in cmd->flags. + */ +int iwl_mvm_send_cmd(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd) +{ + int ret; + + /* + * Synchronous commands from this op-mode must hold + * the mutex, this ensures we don't try to send two + * (or more) synchronous commands at a time. + */ + if (!(cmd->flags & CMD_ASYNC)) + lockdep_assert_held(&mvm->mutex); + + ret = iwl_trans_send_cmd(mvm->trans, cmd); + + /* + * If the caller wants the SKB, then don't hide any problems, the + * caller might access the response buffer which will be NULL if + * the command failed. + */ + if (cmd->flags & CMD_WANT_SKB) + return ret; + + /* Silently ignore failures if RFKILL is asserted */ + if (!ret || ret == -ERFKILL) + return 0; + return ret; +} + +int iwl_mvm_send_cmd_pdu(struct iwl_mvm *mvm, u8 id, + u32 flags, u16 len, const void *data) +{ + struct iwl_host_cmd cmd = { + .id = id, + .len = { len, }, + .data = { data, }, + .flags = flags, + }; + + return iwl_mvm_send_cmd(mvm, &cmd); +} + +/* + * We assume that the caller set the status to the sucess value + */ +int iwl_mvm_send_cmd_status(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd, + u32 *status) +{ + struct iwl_rx_packet *pkt; + struct iwl_cmd_response *resp; + int ret, resp_len; + + lockdep_assert_held(&mvm->mutex); + + /* + * Only synchronous commands can wait for status, + * we use WANT_SKB so the caller can't. + */ + if (WARN_ONCE(cmd->flags & (CMD_ASYNC | CMD_WANT_SKB), + "cmd flags %x", cmd->flags)) + return -EINVAL; + + cmd->flags |= CMD_SYNC | CMD_WANT_SKB; + + ret = iwl_trans_send_cmd(mvm->trans, cmd); + if (ret == -ERFKILL) { + /* + * The command failed because of RFKILL, don't update + * the status, leave it as success and return 0. + */ + return 0; + } else if (ret) { + return ret; + } + + pkt = cmd->resp_pkt; + /* Can happen if RFKILL is asserted */ + if (!pkt) { + ret = 0; + goto out_free_resp; + } + + if (pkt->hdr.flags & IWL_CMD_FAILED_MSK) { + ret = -EIO; + goto out_free_resp; + } + + resp_len = le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; + if (WARN_ON_ONCE(resp_len != sizeof(pkt->hdr) + sizeof(*resp))) { + ret = -EIO; + goto out_free_resp; + } + + resp = (void *)pkt->data; + *status = le32_to_cpu(resp->status); + out_free_resp: + iwl_free_resp(cmd); + return ret; +} + +/* + * We assume that the caller set the status to the sucess value + */ +int iwl_mvm_send_cmd_pdu_status(struct iwl_mvm *mvm, u8 id, u16 len, + const void *data, u32 *status) +{ + struct iwl_host_cmd cmd = { + .id = id, + .len = { len, }, + .data = { data, }, + }; + + return iwl_mvm_send_cmd_status(mvm, &cmd, status); +} + +#define IWL_DECLARE_RATE_INFO(r) \ + [IWL_RATE_##r##M_INDEX] = IWL_RATE_##r##M_PLCP + +/* + * Translate from fw_rate_index (IWL_RATE_XXM_INDEX) to PLCP + */ +static const u8 fw_rate_idx_to_plcp[IWL_RATE_COUNT] = { + IWL_DECLARE_RATE_INFO(1), + IWL_DECLARE_RATE_INFO(2), + IWL_DECLARE_RATE_INFO(5), + IWL_DECLARE_RATE_INFO(11), + IWL_DECLARE_RATE_INFO(6), + IWL_DECLARE_RATE_INFO(9), + IWL_DECLARE_RATE_INFO(12), + IWL_DECLARE_RATE_INFO(18), + IWL_DECLARE_RATE_INFO(24), + IWL_DECLARE_RATE_INFO(36), + IWL_DECLARE_RATE_INFO(48), + IWL_DECLARE_RATE_INFO(54), +}; + +int iwl_mvm_legacy_rate_to_mac80211_idx(u32 rate_n_flags, + enum ieee80211_band band) +{ + int rate = rate_n_flags & RATE_LEGACY_RATE_MSK; + int idx; + int band_offset = 0; + + /* Legacy rate format, search for match in table */ + if (band == IEEE80211_BAND_5GHZ) + band_offset = IWL_FIRST_OFDM_RATE; + for (idx = band_offset; idx < IWL_RATE_COUNT_LEGACY; idx++) + if (fw_rate_idx_to_plcp[idx] == rate) + return idx - band_offset; + + return -1; +} + +u8 iwl_mvm_mac80211_idx_to_hwrate(int rate_idx) +{ + /* Get PLCP rate for tx_cmd->rate_n_flags */ + return fw_rate_idx_to_plcp[rate_idx]; +} + +int iwl_mvm_rx_fw_error(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, + struct iwl_device_cmd *cmd) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + struct iwl_error_resp *err_resp = (void *)pkt->data; + + IWL_ERR(mvm, "FW Error notification: type 0x%08X cmd_id 0x%02X\n", + le32_to_cpu(err_resp->error_type), err_resp->cmd_id); + IWL_ERR(mvm, "FW Error notification: seq 0x%04X service 0x%08X\n", + le16_to_cpu(err_resp->bad_cmd_seq_num), + le32_to_cpu(err_resp->error_service)); + IWL_ERR(mvm, "FW Error notification: timestamp 0x%16llX\n", + le64_to_cpu(err_resp->timestamp)); + return 0; +} + +/* + * Returns the first antenna as ANT_[ABC], as defined in iwl-config.h. + * The parameter should also be a combination of ANT_[ABC]. + */ +u8 first_antenna(u8 mask) +{ + BUILD_BUG_ON(ANT_A != BIT(0)); /* using ffs is wrong if not */ + if (WARN_ON_ONCE(!mask)) /* ffs will return 0 if mask is zeroed */ + return BIT(0); + return BIT(ffs(mask) - 1); +} + +/* + * Toggles between TX antennas to send the probe request on. + * Receives the bitmask of valid TX antennas and the *index* used + * for the last TX, and returns the next valid *index* to use. + * In order to set it in the tx_cmd, must do BIT(idx). + */ +u8 iwl_mvm_next_antenna(struct iwl_mvm *mvm, u8 valid, u8 last_idx) +{ + u8 ind = last_idx; + int i; + + for (i = 0; i < RATE_MCS_ANT_NUM; i++) { + ind = (ind + 1) % RATE_MCS_ANT_NUM; + if (valid & BIT(ind)) + return ind; + } + + WARN_ONCE(1, "Failed to toggle between antennas 0x%x", valid); + return last_idx; +} + +static struct { + char *name; + u8 num; +} advanced_lookup[] = { + { "NMI_INTERRUPT_WDG", 0x34 }, + { "SYSASSERT", 0x35 }, + { "UCODE_VERSION_MISMATCH", 0x37 }, + { "BAD_COMMAND", 0x38 }, + { "NMI_INTERRUPT_DATA_ACTION_PT", 0x3C }, + { "FATAL_ERROR", 0x3D }, + { "NMI_TRM_HW_ERR", 0x46 }, + { "NMI_INTERRUPT_TRM", 0x4C }, + { "NMI_INTERRUPT_BREAK_POINT", 0x54 }, + { "NMI_INTERRUPT_WDG_RXF_FULL", 0x5C }, + { "NMI_INTERRUPT_WDG_NO_RBD_RXF_FULL", 0x64 }, + { "NMI_INTERRUPT_HOST", 0x66 }, + { "NMI_INTERRUPT_ACTION_PT", 0x7C }, + { "NMI_INTERRUPT_UNKNOWN", 0x84 }, + { "NMI_INTERRUPT_INST_ACTION_PT", 0x86 }, + { "ADVANCED_SYSASSERT", 0 }, +}; + +static const char *desc_lookup(u32 num) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(advanced_lookup) - 1; i++) + if (advanced_lookup[i].num == num) + return advanced_lookup[i].name; + + /* No entry matches 'num', so it is the last: ADVANCED_SYSASSERT */ + return advanced_lookup[i].name; +} + +/* + * Note: This structure is read from the device with IO accesses, + * and the reading already does the endian conversion. As it is + * read with u32-sized accesses, any members with a different size + * need to be ordered correctly though! + */ +struct iwl_error_event_table { + u32 valid; /* (nonzero) valid, (0) log is empty */ + u32 error_id; /* type of error */ + u32 pc; /* program counter */ + u32 blink1; /* branch link */ + u32 blink2; /* branch link */ + u32 ilink1; /* interrupt link */ + u32 ilink2; /* interrupt link */ + u32 data1; /* error-specific data */ + u32 data2; /* error-specific data */ + u32 data3; /* error-specific data */ + u32 bcon_time; /* beacon timer */ + u32 tsf_low; /* network timestamp function timer */ + u32 tsf_hi; /* network timestamp function timer */ + u32 gp1; /* GP1 timer register */ + u32 gp2; /* GP2 timer register */ + u32 gp3; /* GP3 timer register */ + u32 ucode_ver; /* uCode version */ + u32 hw_ver; /* HW Silicon version */ + u32 brd_ver; /* HW board version */ + u32 log_pc; /* log program counter */ + u32 frame_ptr; /* frame pointer */ + u32 stack_ptr; /* stack pointer */ + u32 hcmd; /* last host command header */ + u32 isr0; /* isr status register LMPM_NIC_ISR0: + * rxtx_flag */ + u32 isr1; /* isr status register LMPM_NIC_ISR1: + * host_flag */ + u32 isr2; /* isr status register LMPM_NIC_ISR2: + * enc_flag */ + u32 isr3; /* isr status register LMPM_NIC_ISR3: + * time_flag */ + u32 isr4; /* isr status register LMPM_NIC_ISR4: + * wico interrupt */ + u32 isr_pref; /* isr status register LMPM_NIC_PREF_STAT */ + u32 wait_event; /* wait event() caller address */ + u32 l2p_control; /* L2pControlField */ + u32 l2p_duration; /* L2pDurationField */ + u32 l2p_mhvalid; /* L2pMhValidBits */ + u32 l2p_addr_match; /* L2pAddrMatchStat */ + u32 lmpm_pmg_sel; /* indicate which clocks are turned on + * (LMPM_PMG_SEL) */ + u32 u_timestamp; /* indicate when the date and time of the + * compilation */ + u32 flow_handler; /* FH read/write pointers, RX credit */ +} __packed; + +#define ERROR_START_OFFSET (1 * sizeof(u32)) +#define ERROR_ELEM_SIZE (7 * sizeof(u32)) + +void iwl_mvm_dump_nic_error_log(struct iwl_mvm *mvm) +{ + struct iwl_trans *trans = mvm->trans; + struct iwl_error_event_table table; + u32 base; + + base = mvm->error_event_table; + if (mvm->cur_ucode == IWL_UCODE_INIT) { + if (!base) + base = mvm->fw->init_errlog_ptr; + } else { + if (!base) + base = mvm->fw->inst_errlog_ptr; + } + + if (base < 0x800000 || base >= 0x80C000) { + IWL_ERR(mvm, + "Not valid error log pointer 0x%08X for %s uCode\n", + base, + (mvm->cur_ucode == IWL_UCODE_INIT) + ? "Init" : "RT"); + return; + } + + iwl_trans_read_mem_bytes(trans, base, &table, sizeof(table)); + + if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) { + IWL_ERR(trans, "Start IWL Error Log Dump:\n"); + IWL_ERR(trans, "Status: 0x%08lX, count: %d\n", + mvm->status, table.valid); + } + + trace_iwlwifi_dev_ucode_error(trans->dev, table.error_id, table.tsf_low, + table.data1, table.data2, table.data3, + table.blink1, table.blink2, table.ilink1, + table.ilink2, table.bcon_time, table.gp1, + table.gp2, table.gp3, table.ucode_ver, + table.hw_ver, table.brd_ver); + IWL_ERR(mvm, "0x%08X | %-28s\n", table.error_id, + desc_lookup(table.error_id)); + IWL_ERR(mvm, "0x%08X | uPc\n", table.pc); + IWL_ERR(mvm, "0x%08X | branchlink1\n", table.blink1); + IWL_ERR(mvm, "0x%08X | branchlink2\n", table.blink2); + IWL_ERR(mvm, "0x%08X | interruptlink1\n", table.ilink1); + IWL_ERR(mvm, "0x%08X | interruptlink2\n", table.ilink2); + IWL_ERR(mvm, "0x%08X | data1\n", table.data1); + IWL_ERR(mvm, "0x%08X | data2\n", table.data2); + IWL_ERR(mvm, "0x%08X | data3\n", table.data3); + IWL_ERR(mvm, "0x%08X | beacon time\n", table.bcon_time); + IWL_ERR(mvm, "0x%08X | tsf low\n", table.tsf_low); + IWL_ERR(mvm, "0x%08X | tsf hi\n", table.tsf_hi); + IWL_ERR(mvm, "0x%08X | time gp1\n", table.gp1); + IWL_ERR(mvm, "0x%08X | time gp2\n", table.gp2); + IWL_ERR(mvm, "0x%08X | time gp3\n", table.gp3); + IWL_ERR(mvm, "0x%08X | uCode version\n", table.ucode_ver); + IWL_ERR(mvm, "0x%08X | hw version\n", table.hw_ver); + IWL_ERR(mvm, "0x%08X | board version\n", table.brd_ver); + IWL_ERR(mvm, "0x%08X | hcmd\n", table.hcmd); + IWL_ERR(mvm, "0x%08X | isr0\n", table.isr0); + IWL_ERR(mvm, "0x%08X | isr1\n", table.isr1); + IWL_ERR(mvm, "0x%08X | isr2\n", table.isr2); + IWL_ERR(mvm, "0x%08X | isr3\n", table.isr3); + IWL_ERR(mvm, "0x%08X | isr4\n", table.isr4); + IWL_ERR(mvm, "0x%08X | isr_pref\n", table.isr_pref); + IWL_ERR(mvm, "0x%08X | wait_event\n", table.wait_event); + IWL_ERR(mvm, "0x%08X | l2p_control\n", table.l2p_control); + IWL_ERR(mvm, "0x%08X | l2p_duration\n", table.l2p_duration); + IWL_ERR(mvm, "0x%08X | l2p_mhvalid\n", table.l2p_mhvalid); + IWL_ERR(mvm, "0x%08X | l2p_addr_match\n", table.l2p_addr_match); + IWL_ERR(mvm, "0x%08X | lmpm_pmg_sel\n", table.lmpm_pmg_sel); + IWL_ERR(mvm, "0x%08X | timestamp\n", table.u_timestamp); + IWL_ERR(mvm, "0x%08X | flow_handler\n", table.flow_handler); +} + +/** + * iwl_mvm_send_lq_cmd() - Send link quality command + * @init: This command is sent as part of station initialization right + * after station has been added. + * + * The link quality command is sent as the last step of station creation. + * This is the special case in which init is set and we call a callback in + * this case to clear the state indicating that station creation is in + * progress. + */ +int iwl_mvm_send_lq_cmd(struct iwl_mvm *mvm, struct iwl_lq_cmd *lq, + u8 flags, bool init) +{ + struct iwl_host_cmd cmd = { + .id = LQ_CMD, + .len = { sizeof(struct iwl_lq_cmd), }, + .flags = flags, + .data = { lq, }, + }; + + if (WARN_ON(lq->sta_id == IWL_MVM_STATION_COUNT)) + return -EINVAL; + + if (WARN_ON(init && (cmd.flags & CMD_ASYNC))) + return -EINVAL; + + return iwl_mvm_send_cmd(mvm, &cmd); +} diff --git a/drivers/net/wireless/iwlwifi/pcie/drv.c b/drivers/net/wireless/iwlwifi/pcie/drv.c new file mode 100644 index 0000000..aa619ec --- /dev/null +++ b/drivers/net/wireless/iwlwifi/pcie/drv.c @@ -0,0 +1,402 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include + +#include "iwl-trans.h" +#include "iwl-drv.h" +#include "internal.h" + +#define IWL_PCI_DEVICE(dev, subdev, cfg) \ + .vendor = PCI_VENDOR_ID_INTEL, .device = (dev), \ + .subvendor = PCI_ANY_ID, .subdevice = (subdev), \ + .driver_data = (kernel_ulong_t)&(cfg) + +/* Hardware specific file defines the PCI IDs table for that hardware module */ +static DEFINE_PCI_DEVICE_TABLE(iwl_hw_card_ids) = { + {IWL_PCI_DEVICE(0x4232, 0x1201, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1301, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1204, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1304, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1205, iwl5100_bgn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1305, iwl5100_bgn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1206, iwl5100_abg_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1306, iwl5100_abg_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1221, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1321, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1224, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1324, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1225, iwl5100_bgn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1325, iwl5100_bgn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1226, iwl5100_abg_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4232, 0x1326, iwl5100_abg_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1211, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1311, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1214, iwl5100_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1314, iwl5100_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1215, iwl5100_bgn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1315, iwl5100_bgn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1216, iwl5100_abg_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4237, 0x1316, iwl5100_abg_cfg)}, /* Half Mini Card */ + +/* 5300 Series WiFi */ + {IWL_PCI_DEVICE(0x4235, 0x1021, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1121, iwl5300_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1024, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1124, iwl5300_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1001, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1101, iwl5300_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1004, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4235, 0x1104, iwl5300_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4236, 0x1011, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4236, 0x1111, iwl5300_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x4236, 0x1014, iwl5300_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x4236, 0x1114, iwl5300_agn_cfg)}, /* Half Mini Card */ + +/* 5350 Series WiFi/WiMax */ + {IWL_PCI_DEVICE(0x423A, 0x1001, iwl5350_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423A, 0x1021, iwl5350_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423B, 0x1011, iwl5350_agn_cfg)}, /* Mini Card */ + +/* 5150 Series Wifi/WiMax */ + {IWL_PCI_DEVICE(0x423C, 0x1201, iwl5150_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1301, iwl5150_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1206, iwl5150_abg_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1306, iwl5150_abg_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1221, iwl5150_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1321, iwl5150_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x423C, 0x1326, iwl5150_abg_cfg)}, /* Half Mini Card */ + + {IWL_PCI_DEVICE(0x423D, 0x1211, iwl5150_agn_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423D, 0x1311, iwl5150_agn_cfg)}, /* Half Mini Card */ + {IWL_PCI_DEVICE(0x423D, 0x1216, iwl5150_abg_cfg)}, /* Mini Card */ + {IWL_PCI_DEVICE(0x423D, 0x1316, iwl5150_abg_cfg)}, /* Half Mini Card */ + +/* 6x00 Series */ + {IWL_PCI_DEVICE(0x422B, 0x1101, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x422B, 0x1108, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x422B, 0x1121, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x422B, 0x1128, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x422C, 0x1301, iwl6000i_2agn_cfg)}, + {IWL_PCI_DEVICE(0x422C, 0x1306, iwl6000i_2abg_cfg)}, + {IWL_PCI_DEVICE(0x422C, 0x1307, iwl6000i_2bg_cfg)}, + {IWL_PCI_DEVICE(0x422C, 0x1321, iwl6000i_2agn_cfg)}, + {IWL_PCI_DEVICE(0x422C, 0x1326, iwl6000i_2abg_cfg)}, + {IWL_PCI_DEVICE(0x4238, 0x1111, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x4238, 0x1118, iwl6000_3agn_cfg)}, + {IWL_PCI_DEVICE(0x4239, 0x1311, iwl6000i_2agn_cfg)}, + {IWL_PCI_DEVICE(0x4239, 0x1316, iwl6000i_2abg_cfg)}, + +/* 6x05 Series */ + {IWL_PCI_DEVICE(0x0082, 0x1301, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1306, iwl6005_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1307, iwl6005_2bg_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1308, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1321, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1326, iwl6005_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1328, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0085, 0x1311, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0085, 0x1318, iwl6005_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0085, 0x1316, iwl6005_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0xC020, iwl6005_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x0085, 0xC220, iwl6005_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x0085, 0xC228, iwl6005_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x4820, iwl6005_2agn_d_cfg)}, + {IWL_PCI_DEVICE(0x0082, 0x1304, iwl6005_2agn_mow1_cfg)},/* low 5GHz active */ + {IWL_PCI_DEVICE(0x0082, 0x1305, iwl6005_2agn_mow2_cfg)},/* high 5GHz active */ + +/* 6x30 Series */ + {IWL_PCI_DEVICE(0x008A, 0x5305, iwl1030_bgn_cfg)}, + {IWL_PCI_DEVICE(0x008A, 0x5307, iwl1030_bg_cfg)}, + {IWL_PCI_DEVICE(0x008A, 0x5325, iwl1030_bgn_cfg)}, + {IWL_PCI_DEVICE(0x008A, 0x5327, iwl1030_bg_cfg)}, + {IWL_PCI_DEVICE(0x008B, 0x5315, iwl1030_bgn_cfg)}, + {IWL_PCI_DEVICE(0x008B, 0x5317, iwl1030_bg_cfg)}, + {IWL_PCI_DEVICE(0x0090, 0x5211, iwl6030_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0090, 0x5215, iwl6030_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0090, 0x5216, iwl6030_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5201, iwl6030_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5205, iwl6030_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5206, iwl6030_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5207, iwl6030_2bg_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5221, iwl6030_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5225, iwl6030_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0091, 0x5226, iwl6030_2abg_cfg)}, + +/* 6x50 WiFi/WiMax Series */ + {IWL_PCI_DEVICE(0x0087, 0x1301, iwl6050_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0087, 0x1306, iwl6050_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0087, 0x1321, iwl6050_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0087, 0x1326, iwl6050_2abg_cfg)}, + {IWL_PCI_DEVICE(0x0089, 0x1311, iwl6050_2agn_cfg)}, + {IWL_PCI_DEVICE(0x0089, 0x1316, iwl6050_2abg_cfg)}, + +/* 6150 WiFi/WiMax Series */ + {IWL_PCI_DEVICE(0x0885, 0x1305, iwl6150_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0885, 0x1307, iwl6150_bg_cfg)}, + {IWL_PCI_DEVICE(0x0885, 0x1325, iwl6150_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0885, 0x1327, iwl6150_bg_cfg)}, + {IWL_PCI_DEVICE(0x0886, 0x1315, iwl6150_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0886, 0x1317, iwl6150_bg_cfg)}, + +/* 1000 Series WiFi */ + {IWL_PCI_DEVICE(0x0083, 0x1205, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1305, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1225, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1325, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0084, 0x1215, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0084, 0x1315, iwl1000_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1206, iwl1000_bg_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1306, iwl1000_bg_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1226, iwl1000_bg_cfg)}, + {IWL_PCI_DEVICE(0x0083, 0x1326, iwl1000_bg_cfg)}, + {IWL_PCI_DEVICE(0x0084, 0x1216, iwl1000_bg_cfg)}, + {IWL_PCI_DEVICE(0x0084, 0x1316, iwl1000_bg_cfg)}, + +/* 100 Series WiFi */ + {IWL_PCI_DEVICE(0x08AE, 0x1005, iwl100_bgn_cfg)}, + {IWL_PCI_DEVICE(0x08AE, 0x1007, iwl100_bg_cfg)}, + {IWL_PCI_DEVICE(0x08AF, 0x1015, iwl100_bgn_cfg)}, + {IWL_PCI_DEVICE(0x08AF, 0x1017, iwl100_bg_cfg)}, + {IWL_PCI_DEVICE(0x08AE, 0x1025, iwl100_bgn_cfg)}, + {IWL_PCI_DEVICE(0x08AE, 0x1027, iwl100_bg_cfg)}, + +/* 130 Series WiFi */ + {IWL_PCI_DEVICE(0x0896, 0x5005, iwl130_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0896, 0x5007, iwl130_bg_cfg)}, + {IWL_PCI_DEVICE(0x0897, 0x5015, iwl130_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0897, 0x5017, iwl130_bg_cfg)}, + {IWL_PCI_DEVICE(0x0896, 0x5025, iwl130_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0896, 0x5027, iwl130_bg_cfg)}, + +/* 2x00 Series */ + {IWL_PCI_DEVICE(0x0890, 0x4022, iwl2000_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0891, 0x4222, iwl2000_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0890, 0x4422, iwl2000_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0890, 0x4822, iwl2000_2bgn_d_cfg)}, + +/* 2x30 Series */ + {IWL_PCI_DEVICE(0x0887, 0x4062, iwl2030_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0888, 0x4262, iwl2030_2bgn_cfg)}, + {IWL_PCI_DEVICE(0x0887, 0x4462, iwl2030_2bgn_cfg)}, + +/* 6x35 Series */ + {IWL_PCI_DEVICE(0x088E, 0x4060, iwl6035_2agn_cfg)}, + {IWL_PCI_DEVICE(0x088E, 0x406A, iwl6035_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x088F, 0x4260, iwl6035_2agn_cfg)}, + {IWL_PCI_DEVICE(0x088F, 0x426A, iwl6035_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x088E, 0x4460, iwl6035_2agn_cfg)}, + {IWL_PCI_DEVICE(0x088E, 0x446A, iwl6035_2agn_sff_cfg)}, + {IWL_PCI_DEVICE(0x088E, 0x4860, iwl6035_2agn_cfg)}, + {IWL_PCI_DEVICE(0x088F, 0x5260, iwl6035_2agn_cfg)}, + +/* 105 Series */ + {IWL_PCI_DEVICE(0x0894, 0x0022, iwl105_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0895, 0x0222, iwl105_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0894, 0x0422, iwl105_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0894, 0x0822, iwl105_bgn_d_cfg)}, + +/* 135 Series */ + {IWL_PCI_DEVICE(0x0892, 0x0062, iwl135_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0893, 0x0262, iwl135_bgn_cfg)}, + {IWL_PCI_DEVICE(0x0892, 0x0462, iwl135_bgn_cfg)}, + +/* 7000 Series */ + {IWL_PCI_DEVICE(0x08B1, 0x4070, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0x4062, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B1, 0xC070, iwl7260_2ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x0070, iwl3160_ac_cfg)}, + {IWL_PCI_DEVICE(0x08B3, 0x8070, iwl3160_ac_cfg)}, + + {0} +}; +MODULE_DEVICE_TABLE(pci, iwl_hw_card_ids); + +/* PCI registers */ +#define PCI_CFG_RETRY_TIMEOUT 0x041 + +static int iwl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + const struct iwl_cfg *cfg = (struct iwl_cfg *)(ent->driver_data); + struct iwl_trans *iwl_trans; + struct iwl_trans_pcie *trans_pcie; + int ret; + + iwl_trans = iwl_trans_pcie_alloc(pdev, ent, cfg); + if (iwl_trans == NULL) + return -ENOMEM; + + pci_set_drvdata(pdev, iwl_trans); + + trans_pcie = IWL_TRANS_GET_PCIE_TRANS(iwl_trans); + trans_pcie->drv = iwl_drv_start(iwl_trans, cfg); + + if (IS_ERR_OR_NULL(trans_pcie->drv)) { + ret = PTR_ERR(trans_pcie->drv); + goto out_free_trans; + } + + /* register transport layer debugfs here */ + ret = iwl_trans_dbgfs_register(iwl_trans, iwl_trans->dbgfs_dir); + if (ret) + goto out_free_drv; + + return 0; + +out_free_drv: + iwl_drv_stop(trans_pcie->drv); +out_free_trans: + iwl_trans_pcie_free(iwl_trans); + pci_set_drvdata(pdev, NULL); + return ret; +} + +static void iwl_pci_remove(struct pci_dev *pdev) +{ + struct iwl_trans *trans = pci_get_drvdata(pdev); + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + iwl_drv_stop(trans_pcie->drv); + iwl_trans_pcie_free(trans); + + pci_set_drvdata(pdev, NULL); +} + +#ifdef CONFIG_PM_SLEEP + +static int iwl_pci_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct iwl_trans *iwl_trans = pci_get_drvdata(pdev); + + /* Before you put code here, think about WoWLAN. You cannot check here + * whether WoWLAN is enabled or not, and your code will run even if + * WoWLAN is enabled - don't kill the NIC, someone may need it in Sx. + */ + + return iwl_trans_suspend(iwl_trans); +} + +static int iwl_pci_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct iwl_trans *iwl_trans = pci_get_drvdata(pdev); + + /* Before you put code here, think about WoWLAN. You cannot check here + * whether WoWLAN is enabled or not, and your code will run even if + * WoWLAN is enabled - the NIC may be alive. + */ + + /* + * We disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state. + */ + pci_write_config_byte(pdev, PCI_CFG_RETRY_TIMEOUT, 0x00); + + return iwl_trans_resume(iwl_trans); +} + +compat_pci_suspend(iwl_pci_suspend) +compat_pci_resume(iwl_pci_resume) + +static SIMPLE_DEV_PM_OPS(iwl_dev_pm_ops, iwl_pci_suspend, iwl_pci_resume); + +#define IWL_PM_OPS (&iwl_dev_pm_ops) + +#else + +#define IWL_PM_OPS NULL + +#endif + +static struct pci_driver iwl_pci_driver = { + .name = DRV_NAME, + .id_table = iwl_hw_card_ids, + .probe = iwl_pci_probe, + .remove = iwl_pci_remove, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) + .driver.pm = IWL_PM_OPS, +#elif defined(CONFIG_PM_SLEEP) + .suspend = iwl_pci_suspend_compat, + .resume = iwl_pci_resume_compat, +#endif +}; + +int __must_check iwl_pci_register_driver(void) +{ + int ret; + ret = pci_register_driver(&iwl_pci_driver); + if (ret) + pr_err("Unable to initialize PCI module\n"); + + return ret; +} + +void iwl_pci_unregister_driver(void) +{ + pci_unregister_driver(&iwl_pci_driver); +} diff --git a/drivers/net/wireless/iwlwifi/pcie/internal.h b/drivers/net/wireless/iwlwifi/pcie/internal.h new file mode 100644 index 0000000..7a6732c --- /dev/null +++ b/drivers/net/wireless/iwlwifi/pcie/internal.h @@ -0,0 +1,482 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ +#ifndef __iwl_trans_int_pcie_h__ +#define __iwl_trans_int_pcie_h__ + +#include +#include +#include +#include +#include +#include + +#include "iwl-fh.h" +#include "iwl-csr.h" +#include "iwl-trans.h" +#include "iwl-debug.h" +#include "iwl-io.h" +#include "iwl-op-mode.h" + +struct iwl_host_cmd; + +/*This file includes the declaration that are internal to the + * trans_pcie layer */ + +struct iwl_rx_mem_buffer { + dma_addr_t page_dma; + struct page *page; + struct list_head list; +}; + +/** + * struct isr_statistics - interrupt statistics + * + */ +struct isr_statistics { + u32 hw; + u32 sw; + u32 err_code; + u32 sch; + u32 alive; + u32 rfkill; + u32 ctkill; + u32 wakeup; + u32 rx; + u32 tx; + u32 unhandled; +}; + +/** + * struct iwl_rxq - Rx queue + * @bd: driver's pointer to buffer of receive buffer descriptors (rbd) + * @bd_dma: bus address of buffer of receive buffer descriptors (rbd) + * @pool: + * @queue: + * @read: Shared index to newest available Rx buffer + * @write: Shared index to oldest written Rx packet + * @free_count: Number of pre-allocated buffers in rx_free + * @write_actual: + * @rx_free: list of free SKBs for use + * @rx_used: List of Rx buffers with no SKB + * @need_update: flag to indicate we need to update read/write index + * @rb_stts: driver's pointer to receive buffer status + * @rb_stts_dma: bus address of receive buffer status + * @lock: + * + * NOTE: rx_free and rx_used are used as a FIFO for iwl_rx_mem_buffers + */ +struct iwl_rxq { + __le32 *bd; + dma_addr_t bd_dma; + struct iwl_rx_mem_buffer pool[RX_QUEUE_SIZE + RX_FREE_BUFFERS]; + struct iwl_rx_mem_buffer *queue[RX_QUEUE_SIZE]; + u32 read; + u32 write; + u32 free_count; + u32 write_actual; + struct list_head rx_free; + struct list_head rx_used; + int need_update; + struct iwl_rb_status *rb_stts; + dma_addr_t rb_stts_dma; + spinlock_t lock; +}; + +struct iwl_dma_ptr { + dma_addr_t dma; + void *addr; + size_t size; +}; + +/** + * iwl_queue_inc_wrap - increment queue index, wrap back to beginning + * @index -- current index + * @n_bd -- total number of entries in queue (must be power of 2) + */ +static inline int iwl_queue_inc_wrap(int index, int n_bd) +{ + return ++index & (n_bd - 1); +} + +/** + * iwl_queue_dec_wrap - decrement queue index, wrap back to end + * @index -- current index + * @n_bd -- total number of entries in queue (must be power of 2) + */ +static inline int iwl_queue_dec_wrap(int index, int n_bd) +{ + return --index & (n_bd - 1); +} + +struct iwl_cmd_meta { + /* only for SYNC commands, iff the reply skb is wanted */ + struct iwl_host_cmd *source; + u32 flags; +}; + +/* + * Generic queue structure + * + * Contains common data for Rx and Tx queues. + * + * Note the difference between n_bd and n_window: the hardware + * always assumes 256 descriptors, so n_bd is always 256 (unless + * there might be HW changes in the future). For the normal TX + * queues, n_window, which is the size of the software queue data + * is also 256; however, for the command queue, n_window is only + * 32 since we don't need so many commands pending. Since the HW + * still uses 256 BDs for DMA though, n_bd stays 256. As a result, + * the software buffers (in the variables @meta, @txb in struct + * iwl_txq) only have 32 entries, while the HW buffers (@tfds in + * the same struct) have 256. + * This means that we end up with the following: + * HW entries: | 0 | ... | N * 32 | ... | N * 32 + 31 | ... | 255 | + * SW entries: | 0 | ... | 31 | + * where N is a number between 0 and 7. This means that the SW + * data is a window overlayed over the HW queue. + */ +struct iwl_queue { + int n_bd; /* number of BDs in this queue */ + int write_ptr; /* 1-st empty entry (index) host_w*/ + int read_ptr; /* last used entry (index) host_r*/ + /* use for monitoring and recovering the stuck queue */ + dma_addr_t dma_addr; /* physical addr for BD's */ + int n_window; /* safe queue window */ + u32 id; + int low_mark; /* low watermark, resume queue if free + * space more than this */ + int high_mark; /* high watermark, stop queue if free + * space less than this */ +}; + +#define TFD_TX_CMD_SLOTS 256 +#define TFD_CMD_SLOTS 32 + +/* + * The FH will write back to the first TB only, so we need + * to copy some data into the buffer regardless of whether + * it should be mapped or not. This indicates how big the + * first TB must be to include the scratch buffer. Since + * the scratch is 4 bytes at offset 12, it's 16 now. If we + * make it bigger then allocations will be bigger and copy + * slower, so that's probably not useful. + */ +#define IWL_HCMD_SCRATCHBUF_SIZE 16 + +struct iwl_pcie_txq_entry { + struct iwl_device_cmd *cmd; + struct sk_buff *skb; + /* buffer to free after command completes */ + const void *free_buf; + struct iwl_cmd_meta meta; +}; + +struct iwl_pcie_txq_scratch_buf { + struct iwl_cmd_header hdr; + u8 buf[8]; + __le32 scratch; +}; + +/** + * struct iwl_txq - Tx Queue for DMA + * @q: generic Rx/Tx queue descriptor + * @tfds: transmit frame descriptors (DMA memory) + * @scratchbufs: start of command headers, including scratch buffers, for + * the writeback -- this is DMA memory and an array holding one buffer + * for each command on the queue + * @scratchbufs_dma: DMA address for the scratchbufs start + * @entries: transmit entries (driver state) + * @lock: queue lock + * @stuck_timer: timer that fires if queue gets stuck + * @trans_pcie: pointer back to transport (for timer) + * @need_update: indicates need to update read/write index + * @active: stores if queue is active + * + * A Tx queue consists of circular buffer of BDs (a.k.a. TFDs, transmit frame + * descriptors) and required locking structures. + */ +struct iwl_txq { + struct iwl_queue q; + struct iwl_tfd *tfds; + struct iwl_pcie_txq_scratch_buf *scratchbufs; + dma_addr_t scratchbufs_dma; + struct iwl_pcie_txq_entry *entries; + spinlock_t lock; + struct timer_list stuck_timer; + struct iwl_trans_pcie *trans_pcie; + u8 need_update; + u8 active; +}; + +static inline dma_addr_t +iwl_pcie_get_scratchbuf_dma(struct iwl_txq *txq, int idx) +{ + return txq->scratchbufs_dma + + sizeof(struct iwl_pcie_txq_scratch_buf) * idx; +} + +/** + * struct iwl_trans_pcie - PCIe transport specific data + * @rxq: all the RX queue data + * @rx_replenish: work that will be called when buffers need to be allocated + * @drv - pointer to iwl_drv + * @trans: pointer to the generic transport area + * @scd_base_addr: scheduler sram base address in SRAM + * @scd_bc_tbls: pointer to the byte count table of the scheduler + * @kw: keep warm address + * @pci_dev: basic pci-network driver stuff + * @hw_base: pci hardware address support + * @ucode_write_complete: indicates that the ucode has been copied. + * @ucode_write_waitq: wait queue for uCode load + * @status - transport specific status flags + * @cmd_queue - command queue number + * @rx_buf_size_8k: 8 kB RX buffer size + * @bc_table_dword: true if the BC table expects DWORD (as opposed to bytes) + * @rx_page_order: page order for receive buffer size + * @wd_timeout: queue watchdog timeout (jiffies) + * @reg_lock: protect hw register access + */ +struct iwl_trans_pcie { + struct iwl_rxq rxq; + struct work_struct rx_replenish; + struct iwl_trans *trans; + struct iwl_drv *drv; + + /* INT ICT Table */ + __le32 *ict_tbl; + dma_addr_t ict_tbl_dma; + int ict_index; + u32 inta; + bool use_ict; + struct isr_statistics isr_stats; + + spinlock_t irq_lock; + u32 inta_mask; + u32 scd_base_addr; + struct iwl_dma_ptr scd_bc_tbls; + struct iwl_dma_ptr kw; + + struct iwl_txq *txq; + unsigned long queue_used[BITS_TO_LONGS(IWL_MAX_HW_QUEUES)]; + unsigned long queue_stopped[BITS_TO_LONGS(IWL_MAX_HW_QUEUES)]; + + /* PCI bus related data */ + struct pci_dev *pci_dev; + void __iomem *hw_base; + + bool ucode_write_complete; + wait_queue_head_t ucode_write_waitq; + wait_queue_head_t wait_command_queue; + + unsigned long status; + u8 cmd_queue; + u8 cmd_fifo; + u8 n_no_reclaim_cmds; + u8 no_reclaim_cmds[MAX_NO_RECLAIM_CMDS]; + + bool rx_buf_size_8k; + bool bc_table_dword; + u32 rx_page_order; + + const char **command_names; + + /* queue watchdog */ + unsigned long wd_timeout; + + /*protect hw register */ + spinlock_t reg_lock; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31) + struct compat_threaded_irq irq_compat; +#endif +}; + +/** + * enum iwl_pcie_status: status of the PCIe transport + * @STATUS_HCMD_ACTIVE: a SYNC command is being processed + * @STATUS_DEVICE_ENABLED: APM is enabled + * @STATUS_TPOWER_PMI: the device might be asleep (need to wake it up) + * @STATUS_INT_ENABLED: interrupts are enabled + * @STATUS_RFKILL: the HW RFkill switch is in KILL position + * @STATUS_FW_ERROR: the fw is in error state + */ +enum iwl_pcie_status { + STATUS_HCMD_ACTIVE, + STATUS_DEVICE_ENABLED, + STATUS_TPOWER_PMI, + STATUS_INT_ENABLED, + STATUS_RFKILL, + STATUS_FW_ERROR, +}; + +#define IWL_TRANS_GET_PCIE_TRANS(_iwl_trans) \ + ((struct iwl_trans_pcie *) ((_iwl_trans)->trans_specific)) + +static inline struct iwl_trans * +iwl_trans_pcie_get_trans(struct iwl_trans_pcie *trans_pcie) +{ + return container_of((void *)trans_pcie, struct iwl_trans, + trans_specific); +} + +/* + * Convention: trans API functions: iwl_trans_pcie_XXX + * Other functions: iwl_pcie_XXX + */ +struct iwl_trans *iwl_trans_pcie_alloc(struct pci_dev *pdev, + const struct pci_device_id *ent, + const struct iwl_cfg *cfg); +void iwl_trans_pcie_free(struct iwl_trans *trans); + +/***************************************************** +* RX +******************************************************/ +int iwl_pcie_rx_init(struct iwl_trans *trans); +irqreturn_t iwl_pcie_irq_handler(int irq, void *dev_id); +int iwl_pcie_rx_stop(struct iwl_trans *trans); +void iwl_pcie_rx_free(struct iwl_trans *trans); + +/***************************************************** +* ICT - interrupt handling +******************************************************/ +irqreturn_t iwl_pcie_isr_ict(int irq, void *data); +int iwl_pcie_alloc_ict(struct iwl_trans *trans); +void iwl_pcie_free_ict(struct iwl_trans *trans); +void iwl_pcie_reset_ict(struct iwl_trans *trans); +void iwl_pcie_disable_ict(struct iwl_trans *trans); + +/***************************************************** +* TX / HCMD +******************************************************/ +int iwl_pcie_tx_init(struct iwl_trans *trans); +void iwl_pcie_tx_start(struct iwl_trans *trans, u32 scd_base_addr); +int iwl_pcie_tx_stop(struct iwl_trans *trans); +void iwl_pcie_tx_free(struct iwl_trans *trans); +void iwl_trans_pcie_txq_enable(struct iwl_trans *trans, int txq_id, int fifo, + int sta_id, int tid, int frame_limit, u16 ssn); +void iwl_trans_pcie_txq_disable(struct iwl_trans *trans, int queue); +int iwl_trans_pcie_tx(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_device_cmd *dev_cmd, int txq_id); +void iwl_pcie_txq_inc_wr_ptr(struct iwl_trans *trans, struct iwl_txq *txq); +int iwl_trans_pcie_send_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd); +void iwl_pcie_hcmd_complete(struct iwl_trans *trans, + struct iwl_rx_cmd_buffer *rxb, int handler_status); +void iwl_trans_pcie_reclaim(struct iwl_trans *trans, int txq_id, int ssn, + struct sk_buff_head *skbs); +void iwl_trans_pcie_tx_reset(struct iwl_trans *trans); + +/***************************************************** +* Error handling +******************************************************/ +int iwl_pcie_dump_fh(struct iwl_trans *trans, char **buf); +void iwl_pcie_dump_csr(struct iwl_trans *trans); + +/***************************************************** +* Helpers +******************************************************/ +static inline void iwl_disable_interrupts(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + clear_bit(STATUS_INT_ENABLED, &trans_pcie->status); + + /* disable interrupts from uCode/NIC to host */ + iwl_write32(trans, CSR_INT_MASK, 0x00000000); + + /* acknowledge/clear/reset any interrupts still pending + * from uCode or flow handler (Rx/Tx DMA) */ + iwl_write32(trans, CSR_INT, 0xffffffff); + iwl_write32(trans, CSR_FH_INT_STATUS, 0xffffffff); + IWL_DEBUG_ISR(trans, "Disabled interrupts\n"); +} + +static inline void iwl_enable_interrupts(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + IWL_DEBUG_ISR(trans, "Enabling interrupts\n"); + set_bit(STATUS_INT_ENABLED, &trans_pcie->status); + iwl_write32(trans, CSR_INT_MASK, trans_pcie->inta_mask); +} + +static inline void iwl_enable_rfkill_int(struct iwl_trans *trans) +{ + IWL_DEBUG_ISR(trans, "Enabling rfkill interrupt\n"); + iwl_write32(trans, CSR_INT_MASK, CSR_INT_BIT_RF_KILL); +} + +static inline void iwl_wake_queue(struct iwl_trans *trans, + struct iwl_txq *txq) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (test_and_clear_bit(txq->q.id, trans_pcie->queue_stopped)) { + IWL_DEBUG_TX_QUEUES(trans, "Wake hwq %d\n", txq->q.id); + iwl_op_mode_queue_not_full(trans->op_mode, txq->q.id); + } +} + +static inline void iwl_stop_queue(struct iwl_trans *trans, + struct iwl_txq *txq) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (!test_and_set_bit(txq->q.id, trans_pcie->queue_stopped)) { + iwl_op_mode_queue_full(trans->op_mode, txq->q.id); + IWL_DEBUG_TX_QUEUES(trans, "Stop hwq %d\n", txq->q.id); + } else + IWL_DEBUG_TX_QUEUES(trans, "hwq %d already stopped\n", + txq->q.id); +} + +static inline bool iwl_queue_used(const struct iwl_queue *q, int i) +{ + return q->write_ptr >= q->read_ptr ? + (i >= q->read_ptr && i < q->write_ptr) : + !(i < q->read_ptr && i >= q->write_ptr); +} + +static inline u8 get_cmd_index(struct iwl_queue *q, u32 index) +{ + return index & (q->n_window - 1); +} + +static inline const char *get_cmd_string(struct iwl_trans_pcie *trans_pcie, + u8 cmd) +{ + if (!trans_pcie->command_names || !trans_pcie->command_names[cmd]) + return "UNKNOWN"; + return trans_pcie->command_names[cmd]; +} + +static inline bool iwl_is_rfkill_set(struct iwl_trans *trans) +{ + return !(iwl_read32(trans, CSR_GP_CNTRL) & + CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW); +} + +#endif /* __iwl_trans_int_pcie_h__ */ diff --git a/drivers/net/wireless/iwlwifi/pcie/rx.c b/drivers/net/wireless/iwlwifi/pcie/rx.c new file mode 100644 index 0000000..ed42794 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/pcie/rx.c @@ -0,0 +1,1306 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ +#include +#include +#include + +#include "iwl-prph.h" +#include "iwl-io.h" +#include "internal.h" +#include "iwl-op-mode.h" + +/****************************************************************************** + * + * RX path functions + * + ******************************************************************************/ + +/* + * Rx theory of operation + * + * Driver allocates a circular buffer of Receive Buffer Descriptors (RBDs), + * each of which point to Receive Buffers to be filled by the NIC. These get + * used not only for Rx frames, but for any command response or notification + * from the NIC. The driver and NIC manage the Rx buffers by means + * of indexes into the circular buffer. + * + * Rx Queue Indexes + * The host/firmware share two index registers for managing the Rx buffers. + * + * The READ index maps to the first position that the firmware may be writing + * to -- the driver can read up to (but not including) this position and get + * good data. + * The READ index is managed by the firmware once the card is enabled. + * + * The WRITE index maps to the last position the driver has read from -- the + * position preceding WRITE is the last slot the firmware can place a packet. + * + * The queue is empty (no good data) if WRITE = READ - 1, and is full if + * WRITE = READ. + * + * During initialization, the host sets up the READ queue position to the first + * INDEX position, and WRITE to the last (READ - 1 wrapped) + * + * When the firmware places a packet in a buffer, it will advance the READ index + * and fire the RX interrupt. The driver can then query the READ index and + * process as many packets as possible, moving the WRITE index forward as it + * resets the Rx queue buffers with new memory. + * + * The management in the driver is as follows: + * + A list of pre-allocated SKBs is stored in iwl->rxq->rx_free. When + * iwl->rxq->free_count drops to or below RX_LOW_WATERMARK, work is scheduled + * to replenish the iwl->rxq->rx_free. + * + In iwl_pcie_rx_replenish (scheduled) if 'processed' != 'read' then the + * iwl->rxq is replenished and the READ INDEX is updated (updating the + * 'processed' and 'read' driver indexes as well) + * + A received packet is processed and handed to the kernel network stack, + * detached from the iwl->rxq. The driver 'processed' index is updated. + * + The Host/Firmware iwl->rxq is replenished at irq thread time from the + * rx_free list. If there are no allocated buffers in iwl->rxq->rx_free, + * the READ INDEX is not incremented and iwl->status(RX_STALLED) is set. + * If there were enough free buffers and RX_STALLED is set it is cleared. + * + * + * Driver sequence: + * + * iwl_rxq_alloc() Allocates rx_free + * iwl_pcie_rx_replenish() Replenishes rx_free list from rx_used, and calls + * iwl_pcie_rxq_restock + * iwl_pcie_rxq_restock() Moves available buffers from rx_free into Rx + * queue, updates firmware pointers, and updates + * the WRITE index. If insufficient rx_free buffers + * are available, schedules iwl_pcie_rx_replenish + * + * -- enable interrupts -- + * ISR - iwl_rx() Detach iwl_rx_mem_buffers from pool up to the + * READ INDEX, detaching the SKB from the pool. + * Moves the packet buffer from queue to rx_used. + * Calls iwl_pcie_rxq_restock to refill any empty + * slots. + * ... + * + */ + +/* + * iwl_rxq_space - Return number of free slots available in queue. + */ +static int iwl_rxq_space(const struct iwl_rxq *q) +{ + int s = q->read - q->write; + if (s <= 0) + s += RX_QUEUE_SIZE; + /* keep some buffer to not confuse full and empty queue */ + s -= 2; + if (s < 0) + s = 0; + return s; +} + +/* + * iwl_dma_addr2rbd_ptr - convert a DMA address to a uCode read buffer ptr + */ +static inline __le32 iwl_pcie_dma_addr2rbd_ptr(dma_addr_t dma_addr) +{ + return cpu_to_le32((u32)(dma_addr >> 8)); +} + +/* + * iwl_pcie_rx_stop - stops the Rx DMA + */ +int iwl_pcie_rx_stop(struct iwl_trans *trans) +{ + iwl_write_direct32(trans, FH_MEM_RCSR_CHNL0_CONFIG_REG, 0); + return iwl_poll_direct_bit(trans, FH_MEM_RSSR_RX_STATUS_REG, + FH_RSSR_CHNL0_RX_STATUS_CHNL_IDLE, 1000); +} + +/* + * iwl_pcie_rxq_inc_wr_ptr - Update the write pointer for the RX queue + */ +static void iwl_pcie_rxq_inc_wr_ptr(struct iwl_trans *trans, struct iwl_rxq *q) +{ + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&q->lock, flags); + + if (q->need_update == 0) + goto exit_unlock; + + if (trans->cfg->base_params->shadow_reg_enable) { + /* shadow register enabled */ + /* Device expects a multiple of 8 */ + q->write_actual = (q->write & ~0x7); + iwl_write32(trans, FH_RSCSR_CHNL0_WPTR, q->write_actual); + } else { + struct iwl_trans_pcie *trans_pcie = + IWL_TRANS_GET_PCIE_TRANS(trans); + + /* If power-saving is in use, make sure device is awake */ + if (test_bit(STATUS_TPOWER_PMI, &trans_pcie->status)) { + reg = iwl_read32(trans, CSR_UCODE_DRV_GP1); + + if (reg & CSR_UCODE_DRV_GP1_BIT_MAC_SLEEP) { + IWL_DEBUG_INFO(trans, + "Rx queue requesting wakeup," + " GP1 = 0x%x\n", reg); + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + goto exit_unlock; + } + + q->write_actual = (q->write & ~0x7); + iwl_write_direct32(trans, FH_RSCSR_CHNL0_WPTR, + q->write_actual); + + /* Else device is assumed to be awake */ + } else { + /* Device expects a multiple of 8 */ + q->write_actual = (q->write & ~0x7); + iwl_write_direct32(trans, FH_RSCSR_CHNL0_WPTR, + q->write_actual); + } + } + q->need_update = 0; + + exit_unlock: + spin_unlock_irqrestore(&q->lock, flags); +} + +/* + * iwl_pcie_rxq_restock - refill RX queue from pre-allocated pool + * + * If there are slots in the RX queue that need to be restocked, + * and we have free pre-allocated buffers, fill the ranks as much + * as we can, pulling from rx_free. + * + * This moves the 'write' index forward to catch up with 'processed', and + * also updates the memory address in the firmware to reference the new + * target buffer. + */ +static void iwl_pcie_rxq_restock(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rxq *rxq = &trans_pcie->rxq; + struct iwl_rx_mem_buffer *rxb; + unsigned long flags; + + /* + * If the device isn't enabled - not need to try to add buffers... + * This can happen when we stop the device and still have an interrupt + * pending. We stop the APM before we sync the interrupts because we + * have to (see comment there). On the other hand, since the APM is + * stopped, we cannot access the HW (in particular not prph). + * So don't try to restock if the APM has been already stopped. + */ + if (!test_bit(STATUS_DEVICE_ENABLED, &trans_pcie->status)) + return; + + spin_lock_irqsave(&rxq->lock, flags); + while ((iwl_rxq_space(rxq) > 0) && (rxq->free_count)) { + /* The overwritten rxb must be a used one */ + rxb = rxq->queue[rxq->write]; + BUG_ON(rxb && rxb->page); + + /* Get next free Rx buffer, remove from free list */ + rxb = list_first_entry(&rxq->rx_free, struct iwl_rx_mem_buffer, + list); + list_del(&rxb->list); + + /* Point to Rx buffer via next RBD in circular buffer */ + rxq->bd[rxq->write] = iwl_pcie_dma_addr2rbd_ptr(rxb->page_dma); + rxq->queue[rxq->write] = rxb; + rxq->write = (rxq->write + 1) & RX_QUEUE_MASK; + rxq->free_count--; + } + spin_unlock_irqrestore(&rxq->lock, flags); + /* If the pre-allocated buffer pool is dropping low, schedule to + * refill it */ + if (rxq->free_count <= RX_LOW_WATERMARK) + schedule_work(&trans_pcie->rx_replenish); + + /* If we've added more space for the firmware to place data, tell it. + * Increment device's write pointer in multiples of 8. */ + if (rxq->write_actual != (rxq->write & ~0x7)) { + spin_lock_irqsave(&rxq->lock, flags); + rxq->need_update = 1; + spin_unlock_irqrestore(&rxq->lock, flags); + iwl_pcie_rxq_inc_wr_ptr(trans, rxq); + } +} + +/* + * iwl_pcie_rxq_alloc_rbs - allocate a page for each used RBD + * + * A used RBD is an Rx buffer that has been given to the stack. To use it again + * a page must be allocated and the RBD must point to the page. This function + * doesn't change the HW pointer but handles the list of pages that is used by + * iwl_pcie_rxq_restock. The latter function will update the HW to use the newly + * allocated buffers. + */ +static void iwl_pcie_rxq_alloc_rbs(struct iwl_trans *trans, gfp_t priority) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rxq *rxq = &trans_pcie->rxq; + struct iwl_rx_mem_buffer *rxb; + struct page *page; + unsigned long flags; + gfp_t gfp_mask = priority; + + while (1) { + spin_lock_irqsave(&rxq->lock, flags); + if (list_empty(&rxq->rx_used)) { + spin_unlock_irqrestore(&rxq->lock, flags); + return; + } + spin_unlock_irqrestore(&rxq->lock, flags); + + if (rxq->free_count > RX_LOW_WATERMARK) + gfp_mask |= __GFP_NOWARN; + + if (trans_pcie->rx_page_order > 0) + gfp_mask |= __GFP_COMP; + + /* Alloc a new receive buffer */ + page = alloc_pages(gfp_mask, trans_pcie->rx_page_order); + if (!page) { + if (net_ratelimit()) + IWL_DEBUG_INFO(trans, "alloc_pages failed, " + "order: %d\n", + trans_pcie->rx_page_order); + + if ((rxq->free_count <= RX_LOW_WATERMARK) && + net_ratelimit()) + IWL_CRIT(trans, "Failed to alloc_pages with %s." + "Only %u free buffers remaining.\n", + priority == GFP_ATOMIC ? + "GFP_ATOMIC" : "GFP_KERNEL", + rxq->free_count); + /* We don't reschedule replenish work here -- we will + * call the restock method and if it still needs + * more buffers it will schedule replenish */ + return; + } + + spin_lock_irqsave(&rxq->lock, flags); + + if (list_empty(&rxq->rx_used)) { + spin_unlock_irqrestore(&rxq->lock, flags); + __free_pages(page, trans_pcie->rx_page_order); + return; + } + rxb = list_first_entry(&rxq->rx_used, struct iwl_rx_mem_buffer, + list); + list_del(&rxb->list); + spin_unlock_irqrestore(&rxq->lock, flags); + + BUG_ON(rxb->page); + rxb->page = page; + /* Get physical address of the RB */ + rxb->page_dma = + dma_map_page(trans->dev, page, 0, + PAGE_SIZE << trans_pcie->rx_page_order, + DMA_FROM_DEVICE); + if (dma_mapping_error(trans->dev, rxb->page_dma)) { + rxb->page = NULL; + spin_lock_irqsave(&rxq->lock, flags); + list_add(&rxb->list, &rxq->rx_used); + spin_unlock_irqrestore(&rxq->lock, flags); + __free_pages(page, trans_pcie->rx_page_order); + return; + } + /* dma address must be no more than 36 bits */ + BUG_ON(rxb->page_dma & ~DMA_BIT_MASK(36)); + /* and also 256 byte aligned! */ + BUG_ON(rxb->page_dma & DMA_BIT_MASK(8)); + + spin_lock_irqsave(&rxq->lock, flags); + + list_add_tail(&rxb->list, &rxq->rx_free); + rxq->free_count++; + + spin_unlock_irqrestore(&rxq->lock, flags); + } +} + +static void iwl_pcie_rxq_free_rbs(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rxq *rxq = &trans_pcie->rxq; + int i; + + /* Fill the rx_used queue with _all_ of the Rx buffers */ + for (i = 0; i < RX_FREE_BUFFERS + RX_QUEUE_SIZE; i++) { + /* In the reset function, these buffers may have been allocated + * to an SKB, so we need to unmap and free potential storage */ + if (rxq->pool[i].page != NULL) { + dma_unmap_page(trans->dev, rxq->pool[i].page_dma, + PAGE_SIZE << trans_pcie->rx_page_order, + DMA_FROM_DEVICE); + __free_pages(rxq->pool[i].page, + trans_pcie->rx_page_order); + rxq->pool[i].page = NULL; + } + list_add_tail(&rxq->pool[i].list, &rxq->rx_used); + } +} + +/* + * iwl_pcie_rx_replenish - Move all used buffers from rx_used to rx_free + * + * When moving to rx_free an page is allocated for the slot. + * + * Also restock the Rx queue via iwl_pcie_rxq_restock. + * This is called as a scheduled work item (except for during initialization) + */ +static void iwl_pcie_rx_replenish(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + unsigned long flags; + + iwl_pcie_rxq_alloc_rbs(trans, GFP_KERNEL); + + spin_lock_irqsave(&trans_pcie->irq_lock, flags); + iwl_pcie_rxq_restock(trans); + spin_unlock_irqrestore(&trans_pcie->irq_lock, flags); +} + +static void iwl_pcie_rx_replenish_now(struct iwl_trans *trans) +{ + iwl_pcie_rxq_alloc_rbs(trans, GFP_ATOMIC); + + iwl_pcie_rxq_restock(trans); +} + +static void iwl_pcie_rx_replenish_work(struct work_struct *data) +{ + struct iwl_trans_pcie *trans_pcie = + container_of(data, struct iwl_trans_pcie, rx_replenish); + + iwl_pcie_rx_replenish(trans_pcie->trans); +} + +static int iwl_pcie_rx_alloc(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rxq *rxq = &trans_pcie->rxq; + struct device *dev = trans->dev; + + memset(&trans_pcie->rxq, 0, sizeof(trans_pcie->rxq)); + + spin_lock_init(&rxq->lock); + + if (WARN_ON(rxq->bd || rxq->rb_stts)) + return -EINVAL; + + /* Allocate the circular buffer of Read Buffer Descriptors (RBDs) */ + rxq->bd = dma_zalloc_coherent(dev, sizeof(__le32) * RX_QUEUE_SIZE, + &rxq->bd_dma, GFP_KERNEL); + if (!rxq->bd) + goto err_bd; + + /*Allocate the driver's pointer to receive buffer status */ + rxq->rb_stts = dma_zalloc_coherent(dev, sizeof(*rxq->rb_stts), + &rxq->rb_stts_dma, GFP_KERNEL); + if (!rxq->rb_stts) + goto err_rb_stts; + + return 0; + +err_rb_stts: + dma_free_coherent(dev, sizeof(__le32) * RX_QUEUE_SIZE, + rxq->bd, rxq->bd_dma); + rxq->bd_dma = 0; + rxq->bd = NULL; +err_bd: + return -ENOMEM; +} + +static void iwl_pcie_rx_hw_init(struct iwl_trans *trans, struct iwl_rxq *rxq) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 rb_size; + const u32 rfdnlog = RX_QUEUE_SIZE_LOG; /* 256 RBDs */ + + if (trans_pcie->rx_buf_size_8k) + rb_size = FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_8K; + else + rb_size = FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K; + + /* Stop Rx DMA */ + iwl_write_direct32(trans, FH_MEM_RCSR_CHNL0_CONFIG_REG, 0); + /* reset and flush pointers */ + iwl_write_direct32(trans, FH_MEM_RCSR_CHNL0_RBDCB_WPTR, 0); + iwl_write_direct32(trans, FH_MEM_RCSR_CHNL0_FLUSH_RB_REQ, 0); + iwl_write_direct32(trans, FH_RSCSR_CHNL0_RDPTR, 0); + + /* Reset driver's Rx queue write index */ + iwl_write_direct32(trans, FH_RSCSR_CHNL0_RBDCB_WPTR_REG, 0); + + /* Tell device where to find RBD circular buffer in DRAM */ + iwl_write_direct32(trans, FH_RSCSR_CHNL0_RBDCB_BASE_REG, + (u32)(rxq->bd_dma >> 8)); + + /* Tell device where in DRAM to update its Rx status */ + iwl_write_direct32(trans, FH_RSCSR_CHNL0_STTS_WPTR_REG, + rxq->rb_stts_dma >> 4); + + /* Enable Rx DMA + * FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY is set because of HW bug in + * the credit mechanism in 5000 HW RX FIFO + * Direct rx interrupts to hosts + * Rx buffer size 4 or 8k + * RB timeout 0x10 + * 256 RBDs + */ + iwl_write_direct32(trans, FH_MEM_RCSR_CHNL0_CONFIG_REG, + FH_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL | + FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY | + FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL | + rb_size| + (RX_RB_TIMEOUT << FH_RCSR_RX_CONFIG_REG_IRQ_RBTH_POS)| + (rfdnlog << FH_RCSR_RX_CONFIG_RBDCB_SIZE_POS)); + + /* Set interrupt coalescing timer to default (2048 usecs) */ + iwl_write8(trans, CSR_INT_COALESCING, IWL_HOST_INT_TIMEOUT_DEF); +} + +int iwl_pcie_rx_init(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rxq *rxq = &trans_pcie->rxq; + int i, err; + unsigned long flags; + + if (!rxq->bd) { + err = iwl_pcie_rx_alloc(trans); + if (err) + return err; + } + + spin_lock_irqsave(&rxq->lock, flags); + INIT_LIST_HEAD(&rxq->rx_free); + INIT_LIST_HEAD(&rxq->rx_used); + + INIT_WORK(&trans_pcie->rx_replenish, + iwl_pcie_rx_replenish_work); + + iwl_pcie_rxq_free_rbs(trans); + + for (i = 0; i < RX_QUEUE_SIZE; i++) + rxq->queue[i] = NULL; + + /* Set us so that we have processed and used all buffers, but have + * not restocked the Rx queue with fresh buffers */ + rxq->read = rxq->write = 0; + rxq->write_actual = 0; + rxq->free_count = 0; + memset(rxq->rb_stts, 0, sizeof(*rxq->rb_stts)); + spin_unlock_irqrestore(&rxq->lock, flags); + + iwl_pcie_rx_replenish(trans); + + iwl_pcie_rx_hw_init(trans, rxq); + + spin_lock_irqsave(&trans_pcie->irq_lock, flags); + rxq->need_update = 1; + iwl_pcie_rxq_inc_wr_ptr(trans, rxq); + spin_unlock_irqrestore(&trans_pcie->irq_lock, flags); + + return 0; +} + +void iwl_pcie_rx_free(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rxq *rxq = &trans_pcie->rxq; + unsigned long flags; + + /*if rxq->bd is NULL, it means that nothing has been allocated, + * exit now */ + if (!rxq->bd) { + IWL_DEBUG_INFO(trans, "Free NULL rx context\n"); + return; + } + + cancel_work_sync(&trans_pcie->rx_replenish); + + spin_lock_irqsave(&rxq->lock, flags); + iwl_pcie_rxq_free_rbs(trans); + spin_unlock_irqrestore(&rxq->lock, flags); + + dma_free_coherent(trans->dev, sizeof(__le32) * RX_QUEUE_SIZE, + rxq->bd, rxq->bd_dma); + rxq->bd_dma = 0; + rxq->bd = NULL; + + if (rxq->rb_stts) + dma_free_coherent(trans->dev, + sizeof(struct iwl_rb_status), + rxq->rb_stts, rxq->rb_stts_dma); + else + IWL_DEBUG_INFO(trans, "Free rxq->rb_stts which is NULL\n"); + rxq->rb_stts_dma = 0; + rxq->rb_stts = NULL; +} + +static void iwl_pcie_rx_handle_rb(struct iwl_trans *trans, + struct iwl_rx_mem_buffer *rxb) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rxq *rxq = &trans_pcie->rxq; + struct iwl_txq *txq = &trans_pcie->txq[trans_pcie->cmd_queue]; + unsigned long flags; + bool page_stolen = false; + int max_len = PAGE_SIZE << trans_pcie->rx_page_order; + u32 offset = 0; + + if (WARN_ON(!rxb)) + return; + + dma_unmap_page(trans->dev, rxb->page_dma, max_len, DMA_FROM_DEVICE); + + while (offset + sizeof(u32) + sizeof(struct iwl_cmd_header) < max_len) { + struct iwl_rx_packet *pkt; + struct iwl_device_cmd *cmd; + u16 sequence; + bool reclaim; + int index, cmd_index, err, len; + struct iwl_rx_cmd_buffer rxcb = { + ._offset = offset, + ._rx_page_order = trans_pcie->rx_page_order, + ._page = rxb->page, + ._page_stolen = false, + .truesize = max_len, + }; + + pkt = rxb_addr(&rxcb); + + if (pkt->len_n_flags == cpu_to_le32(FH_RSCSR_FRAME_INVALID)) + break; + + IWL_DEBUG_RX(trans, "cmd at offset %d: %s (0x%.2x)\n", + rxcb._offset, get_cmd_string(trans_pcie, pkt->hdr.cmd), + pkt->hdr.cmd); + + len = le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; + len += sizeof(u32); /* account for status word */ + trace_iwlwifi_dev_rx(trans->dev, trans, pkt, len); + trace_iwlwifi_dev_rx_data(trans->dev, trans, pkt, len); + + /* Reclaim a command buffer only if this packet is a response + * to a (driver-originated) command. + * If the packet (e.g. Rx frame) originated from uCode, + * there is no command buffer to reclaim. + * Ucode should set SEQ_RX_FRAME bit if ucode-originated, + * but apparently a few don't get set; catch them here. */ + reclaim = !(pkt->hdr.sequence & SEQ_RX_FRAME); + if (reclaim) { + int i; + + for (i = 0; i < trans_pcie->n_no_reclaim_cmds; i++) { + if (trans_pcie->no_reclaim_cmds[i] == + pkt->hdr.cmd) { + reclaim = false; + break; + } + } + } + + sequence = le16_to_cpu(pkt->hdr.sequence); + index = SEQ_TO_INDEX(sequence); + cmd_index = get_cmd_index(&txq->q, index); + + if (reclaim) + cmd = txq->entries[cmd_index].cmd; + else + cmd = NULL; + + err = iwl_op_mode_rx(trans->op_mode, &rxcb, cmd); + + if (reclaim) { + kfree(txq->entries[cmd_index].free_buf); + txq->entries[cmd_index].free_buf = NULL; + } + + /* + * After here, we should always check rxcb._page_stolen, + * if it is true then one of the handlers took the page. + */ + + if (reclaim) { + /* Invoke any callbacks, transfer the buffer to caller, + * and fire off the (possibly) blocking + * iwl_trans_send_cmd() + * as we reclaim the driver command queue */ + if (!rxcb._page_stolen) + iwl_pcie_hcmd_complete(trans, &rxcb, err); + else + IWL_WARN(trans, "Claim null rxb?\n"); + } + + page_stolen |= rxcb._page_stolen; + offset += ALIGN(len, FH_RSCSR_FRAME_ALIGN); + } + + /* page was stolen from us -- free our reference */ + if (page_stolen) { + __free_pages(rxb->page, trans_pcie->rx_page_order); + rxb->page = NULL; + } + + /* Reuse the page if possible. For notification packets and + * SKBs that fail to Rx correctly, add them back into the + * rx_free list for reuse later. */ + spin_lock_irqsave(&rxq->lock, flags); + if (rxb->page != NULL) { + rxb->page_dma = + dma_map_page(trans->dev, rxb->page, 0, + PAGE_SIZE << trans_pcie->rx_page_order, + DMA_FROM_DEVICE); + if (dma_mapping_error(trans->dev, rxb->page_dma)) { + /* + * free the page(s) as well to not break + * the invariant that the items on the used + * list have no page(s) + */ + __free_pages(rxb->page, trans_pcie->rx_page_order); + rxb->page = NULL; + list_add_tail(&rxb->list, &rxq->rx_used); + } else { + list_add_tail(&rxb->list, &rxq->rx_free); + rxq->free_count++; + } + } else + list_add_tail(&rxb->list, &rxq->rx_used); + spin_unlock_irqrestore(&rxq->lock, flags); +} + +/* + * iwl_pcie_rx_handle - Main entry function for receiving responses from fw + */ +static void iwl_pcie_rx_handle(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rxq *rxq = &trans_pcie->rxq; + u32 r, i; + u8 fill_rx = 0; + u32 count = 8; + int total_empty; + + /* uCode's read index (stored in shared DRAM) indicates the last Rx + * buffer that the driver may process (last buffer filled by ucode). */ + r = le16_to_cpu(ACCESS_ONCE(rxq->rb_stts->closed_rb_num)) & 0x0FFF; + i = rxq->read; + + /* Rx interrupt, but nothing sent from uCode */ + if (i == r) + IWL_DEBUG_RX(trans, "HW = SW = %d\n", r); + + /* calculate total frames need to be restock after handling RX */ + total_empty = r - rxq->write_actual; + if (total_empty < 0) + total_empty += RX_QUEUE_SIZE; + + if (total_empty > (RX_QUEUE_SIZE / 2)) + fill_rx = 1; + + while (i != r) { + struct iwl_rx_mem_buffer *rxb; + + rxb = rxq->queue[i]; + rxq->queue[i] = NULL; + + IWL_DEBUG_RX(trans, "rxbuf: HW = %d, SW = %d (%p)\n", + r, i, rxb); + iwl_pcie_rx_handle_rb(trans, rxb); + + i = (i + 1) & RX_QUEUE_MASK; + /* If there are a lot of unused frames, + * restock the Rx queue so ucode wont assert. */ + if (fill_rx) { + count++; + if (count >= 8) { + rxq->read = i; + iwl_pcie_rx_replenish_now(trans); + count = 0; + } + } + } + + /* Backtrack one entry */ + rxq->read = i; + if (fill_rx) + iwl_pcie_rx_replenish_now(trans); + else + iwl_pcie_rxq_restock(trans); +} + +/* + * iwl_pcie_irq_handle_error - called for HW or SW error interrupt from card + */ +static void iwl_pcie_irq_handle_error(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + /* W/A for WiFi/WiMAX coex and WiMAX own the RF */ + if (trans->cfg->internal_wimax_coex && + (!(iwl_read_prph(trans, APMG_CLK_CTRL_REG) & + APMS_CLK_VAL_MRB_FUNC_MODE) || + (iwl_read_prph(trans, APMG_PS_CTRL_REG) & + APMG_PS_CTRL_VAL_RESET_REQ))) { + clear_bit(STATUS_HCMD_ACTIVE, &trans_pcie->status); + iwl_op_mode_wimax_active(trans->op_mode); + wake_up(&trans_pcie->wait_command_queue); + return; + } + + iwl_pcie_dump_csr(trans); + iwl_pcie_dump_fh(trans, NULL); + + set_bit(STATUS_FW_ERROR, &trans_pcie->status); + clear_bit(STATUS_HCMD_ACTIVE, &trans_pcie->status); + wake_up(&trans_pcie->wait_command_queue); + + local_bh_disable(); + iwl_op_mode_nic_error(trans->op_mode); + local_bh_enable(); +} + +irqreturn_t iwl_pcie_irq_handler(int irq, void *dev_id) +{ + struct iwl_trans *trans = dev_id; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct isr_statistics *isr_stats = &trans_pcie->isr_stats; + u32 inta = 0; + u32 handled = 0; + unsigned long flags; + u32 i; +#ifdef CPTCFG_IWLWIFI_DEBUG + u32 inta_mask; +#endif + + lock_map_acquire(&trans->sync_cmd_lockdep_map); + + spin_lock_irqsave(&trans_pcie->irq_lock, flags); + + /* Ack/clear/reset pending uCode interrupts. + * Note: Some bits in CSR_INT are "OR" of bits in CSR_FH_INT_STATUS, + */ + /* There is a hardware bug in the interrupt mask function that some + * interrupts (i.e. CSR_INT_BIT_SCD) can still be generated even if + * they are disabled in the CSR_INT_MASK register. Furthermore the + * ICT interrupt handling mechanism has another bug that might cause + * these unmasked interrupts fail to be detected. We workaround the + * hardware bugs here by ACKing all the possible interrupts so that + * interrupt coalescing can still be achieved. + */ + iwl_write32(trans, CSR_INT, + trans_pcie->inta | ~trans_pcie->inta_mask); + + inta = trans_pcie->inta; + +#ifdef CPTCFG_IWLWIFI_DEBUG + if (iwl_have_debug_level(IWL_DL_ISR)) { + /* just for debug */ + inta_mask = iwl_read32(trans, CSR_INT_MASK); + IWL_DEBUG_ISR(trans, "inta 0x%08x, enabled 0x%08x\n", + inta, inta_mask); + } +#endif + + /* saved interrupt in inta variable now we can reset trans_pcie->inta */ + trans_pcie->inta = 0; + + spin_unlock_irqrestore(&trans_pcie->irq_lock, flags); + + /* Now service all interrupt bits discovered above. */ + if (inta & CSR_INT_BIT_HW_ERR) { + IWL_ERR(trans, "Hardware error detected. Restarting.\n"); + + /* Tell the device to stop sending interrupts */ + iwl_disable_interrupts(trans); + + isr_stats->hw++; + iwl_pcie_irq_handle_error(trans); + + handled |= CSR_INT_BIT_HW_ERR; + + goto out; + } + +#ifdef CPTCFG_IWLWIFI_DEBUG + if (iwl_have_debug_level(IWL_DL_ISR)) { + /* NIC fires this, but we don't use it, redundant with WAKEUP */ + if (inta & CSR_INT_BIT_SCD) { + IWL_DEBUG_ISR(trans, "Scheduler finished to transmit " + "the frame/frames.\n"); + isr_stats->sch++; + } + + /* Alive notification via Rx interrupt will do the real work */ + if (inta & CSR_INT_BIT_ALIVE) { + IWL_DEBUG_ISR(trans, "Alive interrupt\n"); + isr_stats->alive++; + } + } +#endif + /* Safely ignore these bits for debug checks below */ + inta &= ~(CSR_INT_BIT_SCD | CSR_INT_BIT_ALIVE); + + /* HW RF KILL switch toggled */ + if (inta & CSR_INT_BIT_RF_KILL) { + bool hw_rfkill; + + hw_rfkill = iwl_is_rfkill_set(trans); + IWL_WARN(trans, "RF_KILL bit toggled to %s.\n", + hw_rfkill ? "disable radio" : "enable radio"); + + isr_stats->rfkill++; + + iwl_op_mode_hw_rf_kill(trans->op_mode, hw_rfkill); + if (hw_rfkill) { + set_bit(STATUS_RFKILL, &trans_pcie->status); + if (test_and_clear_bit(STATUS_HCMD_ACTIVE, + &trans_pcie->status)) + IWL_DEBUG_RF_KILL(trans, + "Rfkill while SYNC HCMD in flight\n"); + wake_up(&trans_pcie->wait_command_queue); + } else { + clear_bit(STATUS_RFKILL, &trans_pcie->status); + } + + handled |= CSR_INT_BIT_RF_KILL; + } + + /* Chip got too hot and stopped itself */ + if (inta & CSR_INT_BIT_CT_KILL) { + IWL_ERR(trans, "Microcode CT kill error detected.\n"); + isr_stats->ctkill++; + handled |= CSR_INT_BIT_CT_KILL; + } + + /* Error detected by uCode */ + if (inta & CSR_INT_BIT_SW_ERR) { + IWL_ERR(trans, "Microcode SW error detected. " + " Restarting 0x%X.\n", inta); + isr_stats->sw++; + iwl_pcie_irq_handle_error(trans); + handled |= CSR_INT_BIT_SW_ERR; + } + + /* uCode wakes up after power-down sleep */ + if (inta & CSR_INT_BIT_WAKEUP) { + IWL_DEBUG_ISR(trans, "Wakeup interrupt\n"); + iwl_pcie_rxq_inc_wr_ptr(trans, &trans_pcie->rxq); + for (i = 0; i < trans->cfg->base_params->num_of_queues; i++) + iwl_pcie_txq_inc_wr_ptr(trans, &trans_pcie->txq[i]); + + isr_stats->wakeup++; + + handled |= CSR_INT_BIT_WAKEUP; + } + + /* All uCode command responses, including Tx command responses, + * Rx "responses" (frame-received notification), and other + * notifications from uCode come through here*/ + if (inta & (CSR_INT_BIT_FH_RX | CSR_INT_BIT_SW_RX | + CSR_INT_BIT_RX_PERIODIC)) { + IWL_DEBUG_ISR(trans, "Rx interrupt\n"); + if (inta & (CSR_INT_BIT_FH_RX | CSR_INT_BIT_SW_RX)) { + handled |= (CSR_INT_BIT_FH_RX | CSR_INT_BIT_SW_RX); + iwl_write32(trans, CSR_FH_INT_STATUS, + CSR_FH_INT_RX_MASK); + } + if (inta & CSR_INT_BIT_RX_PERIODIC) { + handled |= CSR_INT_BIT_RX_PERIODIC; + iwl_write32(trans, + CSR_INT, CSR_INT_BIT_RX_PERIODIC); + } + /* Sending RX interrupt require many steps to be done in the + * the device: + * 1- write interrupt to current index in ICT table. + * 2- dma RX frame. + * 3- update RX shared data to indicate last write index. + * 4- send interrupt. + * This could lead to RX race, driver could receive RX interrupt + * but the shared data changes does not reflect this; + * periodic interrupt will detect any dangling Rx activity. + */ + + /* Disable periodic interrupt; we use it as just a one-shot. */ + iwl_write8(trans, CSR_INT_PERIODIC_REG, + CSR_INT_PERIODIC_DIS); + + iwl_pcie_rx_handle(trans); + + /* + * Enable periodic interrupt in 8 msec only if we received + * real RX interrupt (instead of just periodic int), to catch + * any dangling Rx interrupt. If it was just the periodic + * interrupt, there was no dangling Rx activity, and no need + * to extend the periodic interrupt; one-shot is enough. + */ + if (inta & (CSR_INT_BIT_FH_RX | CSR_INT_BIT_SW_RX)) + iwl_write8(trans, CSR_INT_PERIODIC_REG, + CSR_INT_PERIODIC_ENA); + + isr_stats->rx++; + } + + /* This "Tx" DMA channel is used only for loading uCode */ + if (inta & CSR_INT_BIT_FH_TX) { + iwl_write32(trans, CSR_FH_INT_STATUS, CSR_FH_INT_TX_MASK); + IWL_DEBUG_ISR(trans, "uCode load interrupt\n"); + isr_stats->tx++; + handled |= CSR_INT_BIT_FH_TX; + /* Wake up uCode load routine, now that load is complete */ + trans_pcie->ucode_write_complete = true; + wake_up(&trans_pcie->ucode_write_waitq); + } + + if (inta & ~handled) { + IWL_ERR(trans, "Unhandled INTA bits 0x%08x\n", inta & ~handled); + isr_stats->unhandled++; + } + + if (inta & ~(trans_pcie->inta_mask)) { + IWL_WARN(trans, "Disabled INTA bits 0x%08x were pending\n", + inta & ~trans_pcie->inta_mask); + } + + /* Re-enable all interrupts */ + /* only Re-enable if disabled by irq */ + if (test_bit(STATUS_INT_ENABLED, &trans_pcie->status)) + iwl_enable_interrupts(trans); + /* Re-enable RF_KILL if it occurred */ + else if (handled & CSR_INT_BIT_RF_KILL) + iwl_enable_rfkill_int(trans); + +out: + lock_map_release(&trans->sync_cmd_lockdep_map); + return IRQ_HANDLED; +} + +/****************************************************************************** + * + * ICT functions + * + ******************************************************************************/ + +/* a device (PCI-E) page is 4096 bytes long */ +#define ICT_SHIFT 12 +#define ICT_SIZE (1 << ICT_SHIFT) +#define ICT_COUNT (ICT_SIZE / sizeof(u32)) + +/* Free dram table */ +void iwl_pcie_free_ict(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (trans_pcie->ict_tbl) { + dma_free_coherent(trans->dev, ICT_SIZE, + trans_pcie->ict_tbl, + trans_pcie->ict_tbl_dma); + trans_pcie->ict_tbl = NULL; + trans_pcie->ict_tbl_dma = 0; + } +} + +/* + * allocate dram shared table, it is an aligned memory + * block of ICT_SIZE. + * also reset all data related to ICT table interrupt. + */ +int iwl_pcie_alloc_ict(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + trans_pcie->ict_tbl = + dma_alloc_coherent(trans->dev, ICT_SIZE, + &trans_pcie->ict_tbl_dma, + GFP_KERNEL); + if (!trans_pcie->ict_tbl) + return -ENOMEM; + + /* just an API sanity check ... it is guaranteed to be aligned */ + if (WARN_ON(trans_pcie->ict_tbl_dma & (ICT_SIZE - 1))) { + iwl_pcie_free_ict(trans); + return -EINVAL; + } + + IWL_DEBUG_ISR(trans, "ict dma addr %Lx\n", + (unsigned long long)trans_pcie->ict_tbl_dma); + + IWL_DEBUG_ISR(trans, "ict vir addr %p\n", trans_pcie->ict_tbl); + + /* reset table and index to all 0 */ + memset(trans_pcie->ict_tbl, 0, ICT_SIZE); + trans_pcie->ict_index = 0; + + /* add periodic RX interrupt */ + trans_pcie->inta_mask |= CSR_INT_BIT_RX_PERIODIC; + return 0; +} + +/* Device is going up inform it about using ICT interrupt table, + * also we need to tell the driver to start using ICT interrupt. + */ +void iwl_pcie_reset_ict(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 val; + unsigned long flags; + + if (!trans_pcie->ict_tbl) + return; + + spin_lock_irqsave(&trans_pcie->irq_lock, flags); + iwl_disable_interrupts(trans); + + memset(trans_pcie->ict_tbl, 0, ICT_SIZE); + + val = trans_pcie->ict_tbl_dma >> ICT_SHIFT; + + val |= CSR_DRAM_INT_TBL_ENABLE; + val |= CSR_DRAM_INIT_TBL_WRAP_CHECK; + + IWL_DEBUG_ISR(trans, "CSR_DRAM_INT_TBL_REG =0x%x\n", val); + + iwl_write32(trans, CSR_DRAM_INT_TBL_REG, val); + trans_pcie->use_ict = true; + trans_pcie->ict_index = 0; + iwl_write32(trans, CSR_INT, trans_pcie->inta_mask); + iwl_enable_interrupts(trans); + spin_unlock_irqrestore(&trans_pcie->irq_lock, flags); +} + +/* Device is going down disable ict interrupt usage */ +void iwl_pcie_disable_ict(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + unsigned long flags; + + spin_lock_irqsave(&trans_pcie->irq_lock, flags); + trans_pcie->use_ict = false; + spin_unlock_irqrestore(&trans_pcie->irq_lock, flags); +} + +/* legacy (non-ICT) ISR. Assumes that trans_pcie->irq_lock is held */ +static irqreturn_t iwl_pcie_isr(int irq, void *data) +{ + struct iwl_trans *trans = data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 inta, inta_mask; +#ifdef CPTCFG_IWLWIFI_DEBUG + u32 inta_fh; +#endif + + lockdep_assert_held(&trans_pcie->irq_lock); + + trace_iwlwifi_dev_irq(trans->dev); + + /* Disable (but don't clear!) interrupts here to avoid + * back-to-back ISRs and sporadic interrupts from our NIC. + * If we have something to service, the irq thread will re-enable ints. + * If we *don't* have something, we'll re-enable before leaving here. */ + inta_mask = iwl_read32(trans, CSR_INT_MASK); + iwl_write32(trans, CSR_INT_MASK, 0x00000000); + + /* Discover which interrupts are active/pending */ + inta = iwl_read32(trans, CSR_INT); + + if (inta & (~inta_mask)) { + IWL_DEBUG_ISR(trans, + "We got a masked interrupt (0x%08x)...Ack and ignore\n", + inta & (~inta_mask)); + iwl_write32(trans, CSR_INT, inta & (~inta_mask)); + inta &= inta_mask; + } + + /* Ignore interrupt if there's nothing in NIC to service. + * This may be due to IRQ shared with another device, + * or due to sporadic interrupts thrown from our NIC. */ + if (!inta) { + IWL_DEBUG_ISR(trans, "Ignore interrupt, inta == 0\n"); + goto none; + } + + if ((inta == 0xFFFFFFFF) || ((inta & 0xFFFFFFF0) == 0xa5a5a5a0)) { + /* Hardware disappeared. It might have already raised + * an interrupt */ + IWL_WARN(trans, "HARDWARE GONE?? INTA == 0x%08x\n", inta); + return IRQ_HANDLED; + } + +#ifdef CPTCFG_IWLWIFI_DEBUG + if (iwl_have_debug_level(IWL_DL_ISR)) { + inta_fh = iwl_read32(trans, CSR_FH_INT_STATUS); + IWL_DEBUG_ISR(trans, "ISR inta 0x%08x, enabled 0x%08x, " + "fh 0x%08x\n", inta, inta_mask, inta_fh); + } +#endif + + trans_pcie->inta |= inta; + /* the thread will service interrupts and re-enable them */ + if (likely(inta)) + return IRQ_WAKE_THREAD; + else if (test_bit(STATUS_INT_ENABLED, &trans_pcie->status) && + !trans_pcie->inta) + iwl_enable_interrupts(trans); + return IRQ_HANDLED; + +none: + /* re-enable interrupts here since we don't have anything to service. */ + /* only Re-enable if disabled by irq and no schedules tasklet. */ + if (test_bit(STATUS_INT_ENABLED, &trans_pcie->status) && + !trans_pcie->inta) + iwl_enable_interrupts(trans); + + return IRQ_NONE; +} + +/* interrupt handler using ict table, with this interrupt driver will + * stop using INTA register to get device's interrupt, reading this register + * is expensive, device will write interrupts in ICT dram table, increment + * index then will fire interrupt to driver, driver will OR all ICT table + * entries from current index up to table entry with 0 value. the result is + * the interrupt we need to service, driver will set the entries back to 0 and + * set index. + */ +irqreturn_t iwl_pcie_isr_ict(int irq, void *data) +{ + struct iwl_trans *trans = data; + struct iwl_trans_pcie *trans_pcie; + u32 inta, inta_mask; + u32 val = 0; + u32 read; + unsigned long flags; + + if (!trans) + return IRQ_NONE; + + trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + spin_lock_irqsave(&trans_pcie->irq_lock, flags); + + /* dram interrupt table not set yet, + * use legacy interrupt. + */ + if (unlikely(!trans_pcie->use_ict)) { + irqreturn_t ret = iwl_pcie_isr(irq, data); + spin_unlock_irqrestore(&trans_pcie->irq_lock, flags); + return ret; + } + + trace_iwlwifi_dev_irq(trans->dev); + + /* Disable (but don't clear!) interrupts here to avoid + * back-to-back ISRs and sporadic interrupts from our NIC. + * If we have something to service, the tasklet will re-enable ints. + * If we *don't* have something, we'll re-enable before leaving here. + */ + inta_mask = iwl_read32(trans, CSR_INT_MASK); + iwl_write32(trans, CSR_INT_MASK, 0x00000000); + + /* Ignore interrupt if there's nothing in NIC to service. + * This may be due to IRQ shared with another device, + * or due to sporadic interrupts thrown from our NIC. */ + read = le32_to_cpu(trans_pcie->ict_tbl[trans_pcie->ict_index]); + trace_iwlwifi_dev_ict_read(trans->dev, trans_pcie->ict_index, read); + if (!read) { + IWL_DEBUG_ISR(trans, "Ignore interrupt, inta == 0\n"); + goto none; + } + + /* + * Collect all entries up to the first 0, starting from ict_index; + * note we already read at ict_index. + */ + do { + val |= read; + IWL_DEBUG_ISR(trans, "ICT index %d value 0x%08X\n", + trans_pcie->ict_index, read); + trans_pcie->ict_tbl[trans_pcie->ict_index] = 0; + trans_pcie->ict_index = + iwl_queue_inc_wrap(trans_pcie->ict_index, ICT_COUNT); + + read = le32_to_cpu(trans_pcie->ict_tbl[trans_pcie->ict_index]); + trace_iwlwifi_dev_ict_read(trans->dev, trans_pcie->ict_index, + read); + } while (read); + + /* We should not get this value, just ignore it. */ + if (val == 0xffffffff) + val = 0; + + /* + * this is a w/a for a h/w bug. the h/w bug may cause the Rx bit + * (bit 15 before shifting it to 31) to clear when using interrupt + * coalescing. fortunately, bits 18 and 19 stay set when this happens + * so we use them to decide on the real state of the Rx bit. + * In order words, bit 15 is set if bit 18 or bit 19 are set. + */ + if (val & 0xC0000) + val |= 0x8000; + + inta = (0xff & val) | ((0xff00 & val) << 16); + IWL_DEBUG_ISR(trans, "ISR inta 0x%08x, enabled 0x%08x ict 0x%08x\n", + inta, inta_mask, val); + + inta &= trans_pcie->inta_mask; + trans_pcie->inta |= inta; + + /* iwl_pcie_tasklet() will service interrupts and re-enable them */ + if (likely(inta)) { + spin_unlock_irqrestore(&trans_pcie->irq_lock, flags); + return IRQ_WAKE_THREAD; + } else if (test_bit(STATUS_INT_ENABLED, &trans_pcie->status) && + !trans_pcie->inta) { + /* Allow interrupt if was disabled by this handler and + * no tasklet was schedules, We should not enable interrupt, + * tasklet will enable it. + */ + iwl_enable_interrupts(trans); + } + + spin_unlock_irqrestore(&trans_pcie->irq_lock, flags); + return IRQ_HANDLED; + + none: + /* re-enable interrupts here since we don't have anything to service. + * only Re-enable if disabled by irq. + */ + if (test_bit(STATUS_INT_ENABLED, &trans_pcie->status) && + !trans_pcie->inta) + iwl_enable_interrupts(trans); + + spin_unlock_irqrestore(&trans_pcie->irq_lock, flags); + return IRQ_NONE; +} diff --git a/drivers/net/wireless/iwlwifi/pcie/trans.c b/drivers/net/wireless/iwlwifi/pcie/trans.c new file mode 100644 index 0000000..4bb60b0 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/pcie/trans.c @@ -0,0 +1,1607 @@ +/****************************************************************************** + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * BSD LICENSE + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include + +#include "iwl-drv.h" +#include "iwl-trans.h" +#include "iwl-csr.h" +#include "iwl-prph.h" +#include "iwl-agn-hw.h" +#include "internal.h" + +static void __iwl_trans_pcie_set_bits_mask(struct iwl_trans *trans, + u32 reg, u32 mask, u32 value) +{ + u32 v; + +#ifdef CPTCFG_IWLWIFI_DEBUG + WARN_ON_ONCE(value & ~mask); +#endif + + v = iwl_read32(trans, reg); + v &= ~mask; + v |= value; + iwl_write32(trans, reg, v); +} + +static inline void __iwl_trans_pcie_clear_bit(struct iwl_trans *trans, + u32 reg, u32 mask) +{ + __iwl_trans_pcie_set_bits_mask(trans, reg, mask, 0); +} + +static inline void __iwl_trans_pcie_set_bit(struct iwl_trans *trans, + u32 reg, u32 mask) +{ + __iwl_trans_pcie_set_bits_mask(trans, reg, mask, mask); +} + +static void iwl_pcie_set_pwr(struct iwl_trans *trans, bool vaux) +{ + if (vaux && pci_pme_capable(to_pci_dev(trans->dev), PCI_D3cold)) + iwl_set_bits_mask_prph(trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_VAL_PWR_SRC_VAUX, + ~APMG_PS_CTRL_MSK_PWR_SRC); + else + iwl_set_bits_mask_prph(trans, APMG_PS_CTRL_REG, + APMG_PS_CTRL_VAL_PWR_SRC_VMAIN, + ~APMG_PS_CTRL_MSK_PWR_SRC); +} + +/* PCI registers */ +#define PCI_CFG_RETRY_TIMEOUT 0x041 + +static void iwl_pcie_apm_config(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u16 lctl; + + /* + * HW bug W/A for instability in PCIe bus L0S->L1 transition. + * Check if BIOS (or OS) enabled L1-ASPM on this device. + * If so (likely), disable L0S, so device moves directly L0->L1; + * costs negligible amount of power savings. + * If not (unlikely), enable L0S, so there is at least some + * power savings, even without L1. + */ + pcie_capability_read_word(trans_pcie->pci_dev, PCI_EXP_LNKCTL, &lctl); + if (lctl & PCI_EXP_LNKCTL_ASPM_L1) { + /* L1-ASPM enabled; disable(!) L0S */ + iwl_set_bit(trans, CSR_GIO_REG, CSR_GIO_REG_VAL_L0S_ENABLED); + dev_info(trans->dev, "L1 Enabled; Disabling L0S\n"); + } else { + /* L1-ASPM disabled; enable(!) L0S */ + iwl_clear_bit(trans, CSR_GIO_REG, CSR_GIO_REG_VAL_L0S_ENABLED); + dev_info(trans->dev, "L1 Disabled; Enabling L0S\n"); + } + trans->pm_support = !(lctl & PCI_EXP_LNKCTL_ASPM_L0S); +} + +/* + * Start up NIC's basic functionality after it has been reset + * (e.g. after platform boot, or shutdown via iwl_pcie_apm_stop()) + * NOTE: This does not load uCode nor start the embedded processor + */ +static int iwl_pcie_apm_init(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret = 0; + IWL_DEBUG_INFO(trans, "Init card's basic functions\n"); + + /* + * Use "set_bit" below rather than "write", to preserve any hardware + * bits already set by default after reset. + */ + + /* Disable L0S exit timer (platform NMI Work/Around) */ + iwl_set_bit(trans, CSR_GIO_CHICKEN_BITS, + CSR_GIO_CHICKEN_BITS_REG_BIT_DIS_L0S_EXIT_TIMER); + + /* + * Disable L0s without affecting L1; + * don't wait for ICH L0s (ICH bug W/A) + */ + iwl_set_bit(trans, CSR_GIO_CHICKEN_BITS, + CSR_GIO_CHICKEN_BITS_REG_BIT_L1A_NO_L0S_RX); + + /* Set FH wait threshold to maximum (HW error during stress W/A) */ + iwl_set_bit(trans, CSR_DBG_HPET_MEM_REG, CSR_DBG_HPET_MEM_REG_VAL); + + /* + * Enable HAP INTA (interrupt from management bus) to + * wake device's PCI Express link L1a -> L0s + */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_HAP_WAKE_L1A); + + iwl_pcie_apm_config(trans); + + /* Configure analog phase-lock-loop before activating to D0A */ + if (trans->cfg->base_params->pll_cfg_val) + iwl_set_bit(trans, CSR_ANA_PLL_CFG, + trans->cfg->base_params->pll_cfg_val); + + /* + * Set "initialization complete" bit to move adapter from + * D0U* --> D0A* (powered-up active) state. + */ + iwl_set_bit(trans, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE); + + /* + * Wait for clock stabilization; once stabilized, access to + * device-internal resources is supported, e.g. iwl_write_prph() + * and accesses to uCode SRAM. + */ + ret = iwl_poll_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, + CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, 25000); + if (ret < 0) { + IWL_DEBUG_INFO(trans, "Failed to init the card\n"); + goto out; + } + + /* + * Enable DMA clock and wait for it to stabilize. + * + * Write to "CLK_EN_REG"; "1" bits enable clocks, while "0" bits + * do not disable clocks. This preserves any hardware bits already + * set by default in "CLK_CTRL_REG" after reset. + */ + iwl_write_prph(trans, APMG_CLK_EN_REG, APMG_CLK_VAL_DMA_CLK_RQT); + udelay(20); + + /* Disable L1-Active */ + iwl_set_bits_prph(trans, APMG_PCIDEV_STT_REG, + APMG_PCIDEV_STT_VAL_L1_ACT_DIS); + + set_bit(STATUS_DEVICE_ENABLED, &trans_pcie->status); + +out: + return ret; +} + +static int iwl_pcie_apm_stop_master(struct iwl_trans *trans) +{ + int ret = 0; + + /* stop device's busmaster DMA activity */ + iwl_set_bit(trans, CSR_RESET, CSR_RESET_REG_FLAG_STOP_MASTER); + + ret = iwl_poll_bit(trans, CSR_RESET, + CSR_RESET_REG_FLAG_MASTER_DISABLED, + CSR_RESET_REG_FLAG_MASTER_DISABLED, 100); + if (ret) + IWL_WARN(trans, "Master Disable Timed Out, 100 usec\n"); + + IWL_DEBUG_INFO(trans, "stop master\n"); + + return ret; +} + +static void iwl_pcie_apm_stop(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + IWL_DEBUG_INFO(trans, "Stop card, put in low power state\n"); + + clear_bit(STATUS_DEVICE_ENABLED, &trans_pcie->status); + + /* Stop device's DMA activity */ + iwl_pcie_apm_stop_master(trans); + + /* Reset the entire device */ + iwl_set_bit(trans, CSR_RESET, CSR_RESET_REG_FLAG_SW_RESET); + + udelay(10); + + /* + * Clear "initialization complete" bit to move adapter from + * D0A* (powered-up Active) --> D0U* (Uninitialized) state. + */ + iwl_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_INIT_DONE); +} + +static int iwl_pcie_nic_init(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + unsigned long flags; + + /* nic_init */ + spin_lock_irqsave(&trans_pcie->irq_lock, flags); + iwl_pcie_apm_init(trans); + + /* Set interrupt coalescing calibration timer to default (512 usecs) */ + iwl_write8(trans, CSR_INT_COALESCING, IWL_HOST_INT_CALIB_TIMEOUT_DEF); + + spin_unlock_irqrestore(&trans_pcie->irq_lock, flags); + + iwl_pcie_set_pwr(trans, false); + + iwl_op_mode_nic_config(trans->op_mode); + + /* Allocate the RX queue, or reset if it is already allocated */ + iwl_pcie_rx_init(trans); + + /* Allocate or reset and init all Tx and Command queues */ + if (iwl_pcie_tx_init(trans)) + return -ENOMEM; + + if (trans->cfg->base_params->shadow_reg_enable) { + /* enable shadow regs in HW */ + iwl_set_bit(trans, CSR_MAC_SHADOW_REG_CTRL, 0x800FFFFF); + IWL_DEBUG_INFO(trans, "Enabling shadow registers in device\n"); + } + + return 0; +} + +#define HW_READY_TIMEOUT (50) + +/* Note: returns poll_bit return value, which is >= 0 if success */ +static int iwl_pcie_set_hw_ready(struct iwl_trans *trans) +{ + int ret; + + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_NIC_READY); + + /* See if we got it */ + ret = iwl_poll_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_BIT_NIC_READY, + CSR_HW_IF_CONFIG_REG_BIT_NIC_READY, + HW_READY_TIMEOUT); + + IWL_DEBUG_INFO(trans, "hardware%s ready\n", ret < 0 ? " not" : ""); + return ret; +} + +/* Note: returns standard 0/-ERROR code */ +static int iwl_pcie_prepare_card_hw(struct iwl_trans *trans) +{ + int ret; + int t = 0; + + IWL_DEBUG_INFO(trans, "iwl_trans_prepare_card_hw enter\n"); + + ret = iwl_pcie_set_hw_ready(trans); + /* If the card is ready, exit 0 */ + if (ret >= 0) + return 0; + + /* If HW is not ready, prepare the conditions to check again */ + iwl_set_bit(trans, CSR_HW_IF_CONFIG_REG, + CSR_HW_IF_CONFIG_REG_PREPARE); + + do { + ret = iwl_pcie_set_hw_ready(trans); + if (ret >= 0) + return 0; + + usleep_range(200, 1000); + t += 200; + } while (t < 150000); + + return ret; +} + +/* + * ucode + */ +static int iwl_pcie_load_firmware_chunk(struct iwl_trans *trans, u32 dst_addr, + dma_addr_t phy_addr, u32 byte_cnt) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret; + + trans_pcie->ucode_write_complete = false; + + iwl_write_direct32(trans, + FH_TCSR_CHNL_TX_CONFIG_REG(FH_SRVC_CHNL), + FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE); + + iwl_write_direct32(trans, + FH_SRVC_CHNL_SRAM_ADDR_REG(FH_SRVC_CHNL), + dst_addr); + + iwl_write_direct32(trans, + FH_TFDIB_CTRL0_REG(FH_SRVC_CHNL), + phy_addr & FH_MEM_TFDIB_DRAM_ADDR_LSB_MSK); + + iwl_write_direct32(trans, + FH_TFDIB_CTRL1_REG(FH_SRVC_CHNL), + (iwl_get_dma_hi_addr(phy_addr) + << FH_MEM_TFDIB_REG1_ADDR_BITSHIFT) | byte_cnt); + + iwl_write_direct32(trans, + FH_TCSR_CHNL_TX_BUF_STS_REG(FH_SRVC_CHNL), + 1 << FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_NUM | + 1 << FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_IDX | + FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_VALID); + + iwl_write_direct32(trans, + FH_TCSR_CHNL_TX_CONFIG_REG(FH_SRVC_CHNL), + FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE | + FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_DISABLE | + FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_ENDTFD); + + ret = wait_event_timeout(trans_pcie->ucode_write_waitq, + trans_pcie->ucode_write_complete, 5 * HZ); + if (!ret) { + IWL_ERR(trans, "Failed to load firmware chunk!\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int iwl_pcie_load_section(struct iwl_trans *trans, u8 section_num, + const struct fw_desc *section) +{ + u8 *v_addr; + dma_addr_t p_addr; + u32 offset; + int ret = 0; + + IWL_DEBUG_FW(trans, "[%d] uCode section being loaded...\n", + section_num); + + v_addr = dma_alloc_coherent(trans->dev, PAGE_SIZE, &p_addr, GFP_KERNEL); + if (!v_addr) + return -ENOMEM; + + for (offset = 0; offset < section->len; offset += PAGE_SIZE) { + u32 copy_size; + + copy_size = min_t(u32, PAGE_SIZE, section->len - offset); + + memcpy(v_addr, (u8 *)section->data + offset, copy_size); + ret = iwl_pcie_load_firmware_chunk(trans, + section->offset + offset, + p_addr, copy_size); + if (ret) { + IWL_ERR(trans, + "Could not load the [%d] uCode section\n", + section_num); + break; + } + } + + dma_free_coherent(trans->dev, PAGE_SIZE, v_addr, p_addr); + return ret; +} + +static int iwl_pcie_load_given_ucode(struct iwl_trans *trans, + const struct fw_img *image) +{ + int i, ret = 0; + + for (i = 0; i < IWL_UCODE_SECTION_MAX; i++) { + if (!image->sec[i].data) + break; + + ret = iwl_pcie_load_section(trans, i, &image->sec[i]); + if (ret) + return ret; + } + + /* Remove all resets to allow NIC to operate */ + iwl_write32(trans, CSR_RESET, 0); + + return 0; +} + +static int iwl_trans_pcie_start_fw(struct iwl_trans *trans, + const struct fw_img *fw, bool run_in_rfkill) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret; + bool hw_rfkill; + + /* This may fail if AMT took ownership of the device */ + if (iwl_pcie_prepare_card_hw(trans)) { + IWL_WARN(trans, "Exit HW not ready\n"); + return -EIO; + } + + clear_bit(STATUS_FW_ERROR, &trans_pcie->status); + + iwl_enable_rfkill_int(trans); + + /* If platform's RF_KILL switch is NOT set to KILL */ + hw_rfkill = iwl_is_rfkill_set(trans); + if (hw_rfkill) + set_bit(STATUS_RFKILL, &trans_pcie->status); + else + clear_bit(STATUS_RFKILL, &trans_pcie->status); + iwl_op_mode_hw_rf_kill(trans->op_mode, hw_rfkill); + if (hw_rfkill && !run_in_rfkill) + return -ERFKILL; + + iwl_write32(trans, CSR_INT, 0xFFFFFFFF); + + ret = iwl_pcie_nic_init(trans); + if (ret) { + IWL_ERR(trans, "Unable to init nic\n"); + return ret; + } + + /* make sure rfkill handshake bits are cleared */ + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, + CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); + + /* clear (again), then enable host interrupts */ + iwl_write32(trans, CSR_INT, 0xFFFFFFFF); + iwl_enable_interrupts(trans); + + /* really make sure rfkill handshake bits are cleared */ + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, CSR_UCODE_SW_BIT_RFKILL); + + /* Load the given image to the HW */ + return iwl_pcie_load_given_ucode(trans, fw); +} + +static void iwl_trans_pcie_fw_alive(struct iwl_trans *trans, u32 scd_addr) +{ + iwl_pcie_reset_ict(trans); + iwl_pcie_tx_start(trans, scd_addr); +} + +static void iwl_trans_pcie_stop_device(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + unsigned long flags; + + /* tell the device to stop sending interrupts */ + spin_lock_irqsave(&trans_pcie->irq_lock, flags); + iwl_disable_interrupts(trans); + spin_unlock_irqrestore(&trans_pcie->irq_lock, flags); + + /* device going down, Stop using ICT table */ + iwl_pcie_disable_ict(trans); + + /* + * If a HW restart happens during firmware loading, + * then the firmware loading might call this function + * and later it might be called again due to the + * restart. So don't process again if the device is + * already dead. + */ + if (test_bit(STATUS_DEVICE_ENABLED, &trans_pcie->status)) { + iwl_pcie_tx_stop(trans); + iwl_pcie_rx_stop(trans); + + /* Power-down device's busmaster DMA clocks */ + iwl_write_prph(trans, APMG_CLK_DIS_REG, + APMG_CLK_VAL_DMA_CLK_RQT); + udelay(5); + } + + /* Make sure (redundant) we've released our request to stay awake */ + iwl_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + + /* Stop the device, and put it in low power state */ + iwl_pcie_apm_stop(trans); + + /* Upon stop, the APM issues an interrupt if HW RF kill is set. + * Clean again the interrupt here + */ + spin_lock_irqsave(&trans_pcie->irq_lock, flags); + iwl_disable_interrupts(trans); + spin_unlock_irqrestore(&trans_pcie->irq_lock, flags); + + iwl_enable_rfkill_int(trans); + + /* stop and reset the on-board processor */ + iwl_write32(trans, CSR_RESET, CSR_RESET_REG_FLAG_NEVO_RESET); + + /* clear all status bits */ + clear_bit(STATUS_HCMD_ACTIVE, &trans_pcie->status); + clear_bit(STATUS_INT_ENABLED, &trans_pcie->status); + clear_bit(STATUS_DEVICE_ENABLED, &trans_pcie->status); + clear_bit(STATUS_TPOWER_PMI, &trans_pcie->status); + clear_bit(STATUS_RFKILL, &trans_pcie->status); +} + +static void iwl_trans_pcie_d3_suspend(struct iwl_trans *trans) +{ + /* let the ucode operate on its own */ + iwl_write32(trans, CSR_UCODE_DRV_GP1_SET, + CSR_UCODE_DRV_GP1_BIT_D3_CFG_COMPLETE); + + iwl_disable_interrupts(trans); + iwl_pcie_disable_ict(trans); + + iwl_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + iwl_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_INIT_DONE); + + /* + * reset TX queues -- some of their registers reset during S3 + * so if we don't reset everything here the D3 image would try + * to execute some invalid memory upon resume + */ + iwl_trans_pcie_tx_reset(trans); + + iwl_pcie_set_pwr(trans, true); +} + +static int iwl_trans_pcie_d3_resume(struct iwl_trans *trans, + enum iwl_d3_status *status) +{ + u32 val; + int ret; + + iwl_pcie_set_pwr(trans, false); + + val = iwl_read32(trans, CSR_RESET); + if (val & CSR_RESET_REG_FLAG_NEVO_RESET) { + *status = IWL_D3_STATUS_RESET; + return 0; + } + + /* + * Also enables interrupts - none will happen as the device doesn't + * know we're waking it up, only when the opmode actually tells it + * after this call. + */ + iwl_pcie_reset_ict(trans); + + iwl_set_bit(trans, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + iwl_set_bit(trans, CSR_GP_CNTRL, CSR_GP_CNTRL_REG_FLAG_INIT_DONE); + + ret = iwl_poll_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, + CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, + 25000); + if (ret) { + IWL_ERR(trans, "Failed to resume the device (mac ready)\n"); + return ret; + } + + iwl_trans_pcie_tx_reset(trans); + + ret = iwl_pcie_rx_init(trans); + if (ret) { + IWL_ERR(trans, "Failed to resume the device (RX reset)\n"); + return ret; + } + + iwl_write32(trans, CSR_UCODE_DRV_GP1_CLR, + CSR_UCODE_DRV_GP1_BIT_D3_CFG_COMPLETE); + + *status = IWL_D3_STATUS_ALIVE; + return 0; +} + +static int iwl_trans_pcie_start_hw(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + bool hw_rfkill; + int err; + + err = iwl_pcie_prepare_card_hw(trans); + if (err) { + IWL_ERR(trans, "Error while preparing HW: %d\n", err); + return err; + } + + iwl_pcie_apm_init(trans); + + /* From now on, the op_mode will be kept updated about RF kill state */ + iwl_enable_rfkill_int(trans); + + hw_rfkill = iwl_is_rfkill_set(trans); + if (hw_rfkill) + set_bit(STATUS_RFKILL, &trans_pcie->status); + else + clear_bit(STATUS_RFKILL, &trans_pcie->status); + iwl_op_mode_hw_rf_kill(trans->op_mode, hw_rfkill); + + return 0; +} + +static void iwl_trans_pcie_stop_hw(struct iwl_trans *trans, + bool op_mode_leaving) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + bool hw_rfkill; + unsigned long flags; + + spin_lock_irqsave(&trans_pcie->irq_lock, flags); + iwl_disable_interrupts(trans); + spin_unlock_irqrestore(&trans_pcie->irq_lock, flags); + + iwl_pcie_apm_stop(trans); + + spin_lock_irqsave(&trans_pcie->irq_lock, flags); + iwl_disable_interrupts(trans); + spin_unlock_irqrestore(&trans_pcie->irq_lock, flags); + + iwl_pcie_disable_ict(trans); + + if (!op_mode_leaving) { + /* + * Even if we stop the HW, we still want the RF kill + * interrupt + */ + iwl_enable_rfkill_int(trans); + + /* + * Check again since the RF kill state may have changed while + * all the interrupts were disabled, in this case we couldn't + * receive the RF kill interrupt and update the state in the + * op_mode. + */ + hw_rfkill = iwl_is_rfkill_set(trans); + if (hw_rfkill) + set_bit(STATUS_RFKILL, &trans_pcie->status); + else + clear_bit(STATUS_RFKILL, &trans_pcie->status); + iwl_op_mode_hw_rf_kill(trans->op_mode, hw_rfkill); + } +} + +static void iwl_trans_pcie_write8(struct iwl_trans *trans, u32 ofs, u8 val) +{ + writeb(val, IWL_TRANS_GET_PCIE_TRANS(trans)->hw_base + ofs); +} + +static void iwl_trans_pcie_write32(struct iwl_trans *trans, u32 ofs, u32 val) +{ + writel(val, IWL_TRANS_GET_PCIE_TRANS(trans)->hw_base + ofs); +} + +static u32 iwl_trans_pcie_read32(struct iwl_trans *trans, u32 ofs) +{ + return readl(IWL_TRANS_GET_PCIE_TRANS(trans)->hw_base + ofs); +} + +static u32 iwl_trans_pcie_read_prph(struct iwl_trans *trans, u32 reg) +{ + iwl_trans_pcie_write32(trans, HBUS_TARG_PRPH_RADDR, + ((reg & 0x000FFFFF) | (3 << 24))); + return iwl_trans_pcie_read32(trans, HBUS_TARG_PRPH_RDAT); +} + +static void iwl_trans_pcie_write_prph(struct iwl_trans *trans, u32 addr, + u32 val) +{ + iwl_trans_pcie_write32(trans, HBUS_TARG_PRPH_WADDR, + ((addr & 0x000FFFFF) | (3 << 24))); + iwl_trans_pcie_write32(trans, HBUS_TARG_PRPH_WDAT, val); +} + +static void iwl_trans_pcie_configure(struct iwl_trans *trans, + const struct iwl_trans_config *trans_cfg) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + trans_pcie->cmd_queue = trans_cfg->cmd_queue; + trans_pcie->cmd_fifo = trans_cfg->cmd_fifo; + if (WARN_ON(trans_cfg->n_no_reclaim_cmds > MAX_NO_RECLAIM_CMDS)) + trans_pcie->n_no_reclaim_cmds = 0; + else + trans_pcie->n_no_reclaim_cmds = trans_cfg->n_no_reclaim_cmds; + if (trans_pcie->n_no_reclaim_cmds) + memcpy(trans_pcie->no_reclaim_cmds, trans_cfg->no_reclaim_cmds, + trans_pcie->n_no_reclaim_cmds * sizeof(u8)); + + trans_pcie->rx_buf_size_8k = trans_cfg->rx_buf_size_8k; + if (trans_pcie->rx_buf_size_8k) + trans_pcie->rx_page_order = get_order(8 * 1024); + else + trans_pcie->rx_page_order = get_order(4 * 1024); + + trans_pcie->wd_timeout = + msecs_to_jiffies(trans_cfg->queue_watchdog_timeout); + + trans_pcie->command_names = trans_cfg->command_names; + trans_pcie->bc_table_dword = trans_cfg->bc_table_dword; +} + +void iwl_trans_pcie_free(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31) + compat_synchronize_threaded_irq(&trans_pcie->irq_compat); +#else + synchronize_irq(trans_pcie->pci_dev->irq); +#endif + + iwl_pcie_tx_free(trans); + iwl_pcie_rx_free(trans); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31) + compat_free_threaded_irq(&trans_pcie->irq_compat); + compat_destroy_threaded_irq(&trans_pcie->irq_compat); +#else + free_irq(trans_pcie->pci_dev->irq, trans); +#endif + iwl_pcie_free_ict(trans); + + pci_disable_msi(trans_pcie->pci_dev); + iounmap(trans_pcie->hw_base); + pci_release_regions(trans_pcie->pci_dev); + pci_disable_device(trans_pcie->pci_dev); + kmem_cache_destroy(trans->dev_cmd_pool); + + kfree(trans); +} + +static void iwl_trans_pcie_set_pmi(struct iwl_trans *trans, bool state) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (state) + set_bit(STATUS_TPOWER_PMI, &trans_pcie->status); + else + clear_bit(STATUS_TPOWER_PMI, &trans_pcie->status); +} + +#ifdef CONFIG_PM_SLEEP +static int iwl_trans_pcie_suspend(struct iwl_trans *trans) +{ + return 0; +} + +static int iwl_trans_pcie_resume(struct iwl_trans *trans) +{ + bool hw_rfkill; + + iwl_enable_rfkill_int(trans); + + hw_rfkill = iwl_is_rfkill_set(trans); + iwl_op_mode_hw_rf_kill(trans->op_mode, hw_rfkill); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static bool iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans, bool silent, + unsigned long *flags) +{ + int ret; + struct iwl_trans_pcie *pcie_trans = IWL_TRANS_GET_PCIE_TRANS(trans); + spin_lock_irqsave(&pcie_trans->reg_lock, *flags); + + /* this bit wakes up the NIC */ + __iwl_trans_pcie_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + + /* + * These bits say the device is running, and should keep running for + * at least a short while (at least as long as MAC_ACCESS_REQ stays 1), + * but they do not indicate that embedded SRAM is restored yet; + * 3945 and 4965 have volatile SRAM, and must save/restore contents + * to/from host DRAM when sleeping/waking for power-saving. + * Each direction takes approximately 1/4 millisecond; with this + * overhead, it's a good idea to grab and hold MAC_ACCESS_REQUEST if a + * series of register accesses are expected (e.g. reading Event Log), + * to keep device from sleeping. + * + * CSR_UCODE_DRV_GP1 register bit MAC_SLEEP == 0 indicates that + * SRAM is okay/restored. We don't check that here because this call + * is just for hardware register access; but GP1 MAC_SLEEP check is a + * good idea before accessing 3945/4965 SRAM (e.g. reading Event Log). + * + * 5000 series and later (including 1000 series) have non-volatile SRAM, + * and do not save/restore SRAM when power cycling. + */ + ret = iwl_poll_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_VAL_MAC_ACCESS_EN, + (CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY | + CSR_GP_CNTRL_REG_FLAG_GOING_TO_SLEEP), 15000); + if (unlikely(ret < 0)) { + iwl_write32(trans, CSR_RESET, CSR_RESET_REG_FLAG_FORCE_NMI); + if (!silent) { + u32 val = iwl_read32(trans, CSR_GP_CNTRL); + WARN_ONCE(1, + "Timeout waiting for hardware access (CSR_GP_CNTRL 0x%08x)\n", + val); + spin_unlock_irqrestore(&pcie_trans->reg_lock, *flags); + return false; + } + } + + /* + * Fool sparse by faking we release the lock - sparse will + * track nic_access anyway. + */ + __release(&pcie_trans->reg_lock); + return true; +} + +static void iwl_trans_pcie_release_nic_access(struct iwl_trans *trans, + unsigned long *flags) +{ + struct iwl_trans_pcie *pcie_trans = IWL_TRANS_GET_PCIE_TRANS(trans); + + lockdep_assert_held(&pcie_trans->reg_lock); + + /* + * Fool sparse by faking we acquiring the lock - sparse will + * track nic_access anyway. + */ + __acquire(&pcie_trans->reg_lock); + + __iwl_trans_pcie_clear_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + /* + * Above we read the CSR_GP_CNTRL register, which will flush + * any previous writes, but we need the write that clears the + * MAC_ACCESS_REQ bit to be performed before any other writes + * scheduled on different CPUs (after we drop reg_lock). + */ + mmiowb(); + spin_unlock_irqrestore(&pcie_trans->reg_lock, *flags); +} + +static int iwl_trans_pcie_read_mem(struct iwl_trans *trans, u32 addr, + void *buf, int dwords) +{ + unsigned long flags; + int offs, ret = 0; + u32 *vals = buf; + + if (iwl_trans_grab_nic_access(trans, false, &flags)) { + iwl_write32(trans, HBUS_TARG_MEM_RADDR, addr); + for (offs = 0; offs < dwords; offs++) + vals[offs] = iwl_read32(trans, HBUS_TARG_MEM_RDAT); + iwl_trans_release_nic_access(trans, &flags); + } else { + ret = -EBUSY; + } + return ret; +} + +static int iwl_trans_pcie_write_mem(struct iwl_trans *trans, u32 addr, + void *buf, int dwords) +{ + unsigned long flags; + int offs, ret = 0; + u32 *vals = buf; + + if (iwl_trans_grab_nic_access(trans, false, &flags)) { + iwl_write32(trans, HBUS_TARG_MEM_WADDR, addr); + for (offs = 0; offs < dwords; offs++) + iwl_write32(trans, HBUS_TARG_MEM_WDAT, + vals ? vals[offs] : 0); + iwl_trans_release_nic_access(trans, &flags); + } else { + ret = -EBUSY; + } + return ret; +} + +#define IWL_FLUSH_WAIT_MS 2000 + +static int iwl_trans_pcie_wait_txq_empty(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq; + struct iwl_queue *q; + int cnt; + unsigned long now = jiffies; + u32 scd_sram_addr; + u8 buf[16]; + int ret = 0; + + /* waiting for all the tx frames complete might take a while */ + for (cnt = 0; cnt < trans->cfg->base_params->num_of_queues; cnt++) { + if (cnt == trans_pcie->cmd_queue) + continue; + txq = &trans_pcie->txq[cnt]; + q = &txq->q; + while (q->read_ptr != q->write_ptr && !time_after(jiffies, + now + msecs_to_jiffies(IWL_FLUSH_WAIT_MS))) + msleep(1); + + if (q->read_ptr != q->write_ptr) { + IWL_ERR(trans, + "fail to flush all tx fifo queues Q %d\n", cnt); + ret = -ETIMEDOUT; + break; + } + } + + if (!ret) + return 0; + + IWL_ERR(trans, "Current SW read_ptr %d write_ptr %d\n", + txq->q.read_ptr, txq->q.write_ptr); + + scd_sram_addr = trans_pcie->scd_base_addr + + SCD_TX_STTS_QUEUE_OFFSET(txq->q.id); + iwl_trans_read_mem_bytes(trans, scd_sram_addr, buf, sizeof(buf)); + + iwl_print_hex_error(trans, buf, sizeof(buf)); + + for (cnt = 0; cnt < FH_TCSR_CHNL_NUM; cnt++) + IWL_ERR(trans, "FH TRBs(%d) = 0x%08x\n", cnt, + iwl_read_direct32(trans, FH_TX_TRB_REG(cnt))); + + for (cnt = 0; cnt < trans->cfg->base_params->num_of_queues; cnt++) { + u32 status = iwl_read_prph(trans, SCD_QUEUE_STATUS_BITS(cnt)); + u8 fifo = (status >> SCD_QUEUE_STTS_REG_POS_TXF) & 0x7; + bool active = !!(status & BIT(SCD_QUEUE_STTS_REG_POS_ACTIVE)); + u32 tbl_dw = + iwl_trans_read_mem32(trans, trans_pcie->scd_base_addr + + SCD_TRANS_TBL_OFFSET_QUEUE(cnt)); + + if (cnt & 0x1) + tbl_dw = (tbl_dw & 0xFFFF0000) >> 16; + else + tbl_dw = tbl_dw & 0x0000FFFF; + + IWL_ERR(trans, + "Q %d is %sactive and mapped to fifo %d ra_tid 0x%04x [%d,%d]\n", + cnt, active ? "" : "in", fifo, tbl_dw, + iwl_read_prph(trans, + SCD_QUEUE_RDPTR(cnt)) & (txq->q.n_bd - 1), + iwl_read_prph(trans, SCD_QUEUE_WRPTR(cnt))); + } + + return ret; +} + +static void iwl_trans_pcie_set_bits_mask(struct iwl_trans *trans, u32 reg, + u32 mask, u32 value) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + unsigned long flags; + + spin_lock_irqsave(&trans_pcie->reg_lock, flags); + __iwl_trans_pcie_set_bits_mask(trans, reg, mask, value); + spin_unlock_irqrestore(&trans_pcie->reg_lock, flags); +} + +static const char *get_fh_string(int cmd) +{ +#define IWL_CMD(x) case x: return #x + switch (cmd) { + IWL_CMD(FH_RSCSR_CHNL0_STTS_WPTR_REG); + IWL_CMD(FH_RSCSR_CHNL0_RBDCB_BASE_REG); + IWL_CMD(FH_RSCSR_CHNL0_WPTR); + IWL_CMD(FH_MEM_RCSR_CHNL0_CONFIG_REG); + IWL_CMD(FH_MEM_RSSR_SHARED_CTRL_REG); + IWL_CMD(FH_MEM_RSSR_RX_STATUS_REG); + IWL_CMD(FH_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV); + IWL_CMD(FH_TSSR_TX_STATUS_REG); + IWL_CMD(FH_TSSR_TX_ERROR_REG); + default: + return "UNKNOWN"; + } +#undef IWL_CMD +} + +int iwl_pcie_dump_fh(struct iwl_trans *trans, char **buf) +{ + int i; + static const u32 fh_tbl[] = { + FH_RSCSR_CHNL0_STTS_WPTR_REG, + FH_RSCSR_CHNL0_RBDCB_BASE_REG, + FH_RSCSR_CHNL0_WPTR, + FH_MEM_RCSR_CHNL0_CONFIG_REG, + FH_MEM_RSSR_SHARED_CTRL_REG, + FH_MEM_RSSR_RX_STATUS_REG, + FH_MEM_RSSR_RX_ENABLE_ERR_IRQ2DRV, + FH_TSSR_TX_STATUS_REG, + FH_TSSR_TX_ERROR_REG + }; + +#ifdef CPTCFG_IWLWIFI_DEBUGFS + if (buf) { + int pos = 0; + size_t bufsz = ARRAY_SIZE(fh_tbl) * 48 + 40; + + *buf = kmalloc(bufsz, GFP_KERNEL); + if (!*buf) + return -ENOMEM; + + pos += scnprintf(*buf + pos, bufsz - pos, + "FH register values:\n"); + + for (i = 0; i < ARRAY_SIZE(fh_tbl); i++) + pos += scnprintf(*buf + pos, bufsz - pos, + " %34s: 0X%08x\n", + get_fh_string(fh_tbl[i]), + iwl_read_direct32(trans, fh_tbl[i])); + + return pos; + } +#endif + + IWL_ERR(trans, "FH register values:\n"); + for (i = 0; i < ARRAY_SIZE(fh_tbl); i++) + IWL_ERR(trans, " %34s: 0X%08x\n", + get_fh_string(fh_tbl[i]), + iwl_read_direct32(trans, fh_tbl[i])); + + return 0; +} + +static const char *get_csr_string(int cmd) +{ +#define IWL_CMD(x) case x: return #x + switch (cmd) { + IWL_CMD(CSR_HW_IF_CONFIG_REG); + IWL_CMD(CSR_INT_COALESCING); + IWL_CMD(CSR_INT); + IWL_CMD(CSR_INT_MASK); + IWL_CMD(CSR_FH_INT_STATUS); + IWL_CMD(CSR_GPIO_IN); + IWL_CMD(CSR_RESET); + IWL_CMD(CSR_GP_CNTRL); + IWL_CMD(CSR_HW_REV); + IWL_CMD(CSR_EEPROM_REG); + IWL_CMD(CSR_EEPROM_GP); + IWL_CMD(CSR_OTP_GP_REG); + IWL_CMD(CSR_GIO_REG); + IWL_CMD(CSR_GP_UCODE_REG); + IWL_CMD(CSR_GP_DRIVER_REG); + IWL_CMD(CSR_UCODE_DRV_GP1); + IWL_CMD(CSR_UCODE_DRV_GP2); + IWL_CMD(CSR_LED_REG); + IWL_CMD(CSR_DRAM_INT_TBL_REG); + IWL_CMD(CSR_GIO_CHICKEN_BITS); + IWL_CMD(CSR_ANA_PLL_CFG); + IWL_CMD(CSR_HW_REV_WA_REG); + IWL_CMD(CSR_DBG_HPET_MEM_REG); + default: + return "UNKNOWN"; + } +#undef IWL_CMD +} + +void iwl_pcie_dump_csr(struct iwl_trans *trans) +{ + int i; + static const u32 csr_tbl[] = { + CSR_HW_IF_CONFIG_REG, + CSR_INT_COALESCING, + CSR_INT, + CSR_INT_MASK, + CSR_FH_INT_STATUS, + CSR_GPIO_IN, + CSR_RESET, + CSR_GP_CNTRL, + CSR_HW_REV, + CSR_EEPROM_REG, + CSR_EEPROM_GP, + CSR_OTP_GP_REG, + CSR_GIO_REG, + CSR_GP_UCODE_REG, + CSR_GP_DRIVER_REG, + CSR_UCODE_DRV_GP1, + CSR_UCODE_DRV_GP2, + CSR_LED_REG, + CSR_DRAM_INT_TBL_REG, + CSR_GIO_CHICKEN_BITS, + CSR_ANA_PLL_CFG, + CSR_HW_REV_WA_REG, + CSR_DBG_HPET_MEM_REG + }; + IWL_ERR(trans, "CSR values:\n"); + IWL_ERR(trans, "(2nd byte of CSR_INT_COALESCING is " + "CSR_INT_PERIODIC_REG)\n"); + for (i = 0; i < ARRAY_SIZE(csr_tbl); i++) { + IWL_ERR(trans, " %25s: 0X%08x\n", + get_csr_string(csr_tbl[i]), + iwl_read32(trans, csr_tbl[i])); + } +} + +#ifdef CPTCFG_IWLWIFI_DEBUGFS +/* create and remove of files */ +#define DEBUGFS_ADD_FILE(name, parent, mode) do { \ + if (!debugfs_create_file(#name, mode, parent, trans, \ + &iwl_dbgfs_##name##_ops)) \ + goto err; \ +} while (0) + +/* file operation */ +#define DEBUGFS_READ_FUNC(name) \ +static ssize_t iwl_dbgfs_##name##_read(struct file *file, \ + char __user *user_buf, \ + size_t count, loff_t *ppos); + +#define DEBUGFS_WRITE_FUNC(name) \ +static ssize_t iwl_dbgfs_##name##_write(struct file *file, \ + const char __user *user_buf, \ + size_t count, loff_t *ppos); + +#define DEBUGFS_READ_FILE_OPS(name) \ + DEBUGFS_READ_FUNC(name); \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .read = iwl_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_WRITE_FILE_OPS(name) \ + DEBUGFS_WRITE_FUNC(name); \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .write = iwl_dbgfs_##name##_write, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_READ_WRITE_FILE_OPS(name) \ + DEBUGFS_READ_FUNC(name); \ + DEBUGFS_WRITE_FUNC(name); \ +static const struct file_operations iwl_dbgfs_##name##_ops = { \ + .write = iwl_dbgfs_##name##_write, \ + .read = iwl_dbgfs_##name##_read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +static ssize_t iwl_dbgfs_tx_queue_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq; + struct iwl_queue *q; + char *buf; + int pos = 0; + int cnt; + int ret; + size_t bufsz; + + bufsz = sizeof(char) * 64 * trans->cfg->base_params->num_of_queues; + + if (!trans_pcie->txq) + return -EAGAIN; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (cnt = 0; cnt < trans->cfg->base_params->num_of_queues; cnt++) { + txq = &trans_pcie->txq[cnt]; + q = &txq->q; + pos += scnprintf(buf + pos, bufsz - pos, + "hwq %.2d: read=%u write=%u use=%d stop=%d\n", + cnt, q->read_ptr, q->write_ptr, + !!test_bit(cnt, trans_pcie->queue_used), + !!test_bit(cnt, trans_pcie->queue_stopped)); + } + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_rx_queue_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_rxq *rxq = &trans_pcie->rxq; + char buf[256]; + int pos = 0; + const size_t bufsz = sizeof(buf); + + pos += scnprintf(buf + pos, bufsz - pos, "read: %u\n", + rxq->read); + pos += scnprintf(buf + pos, bufsz - pos, "write: %u\n", + rxq->write); + pos += scnprintf(buf + pos, bufsz - pos, "free_count: %u\n", + rxq->free_count); + if (rxq->rb_stts) { + pos += scnprintf(buf + pos, bufsz - pos, "closed_rb_num: %u\n", + le16_to_cpu(rxq->rb_stts->closed_rb_num) & 0x0FFF); + } else { + pos += scnprintf(buf + pos, bufsz - pos, + "closed_rb_num: Not Allocated\n"); + } + return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_interrupt_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct isr_statistics *isr_stats = &trans_pcie->isr_stats; + + int pos = 0; + char *buf; + int bufsz = 24 * 64; /* 24 items * 64 char per item */ + ssize_t ret; + + buf = kzalloc(bufsz, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + pos += scnprintf(buf + pos, bufsz - pos, + "Interrupt Statistics Report:\n"); + + pos += scnprintf(buf + pos, bufsz - pos, "HW Error:\t\t\t %u\n", + isr_stats->hw); + pos += scnprintf(buf + pos, bufsz - pos, "SW Error:\t\t\t %u\n", + isr_stats->sw); + if (isr_stats->sw || isr_stats->hw) { + pos += scnprintf(buf + pos, bufsz - pos, + "\tLast Restarting Code: 0x%X\n", + isr_stats->err_code); + } +#ifdef CPTCFG_IWLWIFI_DEBUG + pos += scnprintf(buf + pos, bufsz - pos, "Frame transmitted:\t\t %u\n", + isr_stats->sch); + pos += scnprintf(buf + pos, bufsz - pos, "Alive interrupt:\t\t %u\n", + isr_stats->alive); +#endif + pos += scnprintf(buf + pos, bufsz - pos, + "HW RF KILL switch toggled:\t %u\n", isr_stats->rfkill); + + pos += scnprintf(buf + pos, bufsz - pos, "CT KILL:\t\t\t %u\n", + isr_stats->ctkill); + + pos += scnprintf(buf + pos, bufsz - pos, "Wakeup Interrupt:\t\t %u\n", + isr_stats->wakeup); + + pos += scnprintf(buf + pos, bufsz - pos, + "Rx command responses:\t\t %u\n", isr_stats->rx); + + pos += scnprintf(buf + pos, bufsz - pos, "Tx/FH interrupt:\t\t %u\n", + isr_stats->tx); + + pos += scnprintf(buf + pos, bufsz - pos, "Unexpected INTA:\t\t %u\n", + isr_stats->unhandled); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + kfree(buf); + return ret; +} + +static ssize_t iwl_dbgfs_interrupt_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct isr_statistics *isr_stats = &trans_pcie->isr_stats; + + char buf[8]; + int buf_size; + u32 reset_flag; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%x", &reset_flag) != 1) + return -EFAULT; + if (reset_flag == 0) + memset(isr_stats, 0, sizeof(*isr_stats)); + + return count; +} + +static ssize_t iwl_dbgfs_csr_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + char buf[8]; + int buf_size; + int csr; + + memset(buf, 0, sizeof(buf)); + buf_size = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + if (sscanf(buf, "%d", &csr) != 1) + return -EFAULT; + + iwl_pcie_dump_csr(trans); + + return count; +} + +static ssize_t iwl_dbgfs_fh_reg_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct iwl_trans *trans = file->private_data; + char *buf = NULL; + int pos = 0; + ssize_t ret = -EFAULT; + + ret = pos = iwl_pcie_dump_fh(trans, &buf); + if (buf) { + ret = simple_read_from_buffer(user_buf, + count, ppos, buf, pos); + kfree(buf); + } + + return ret; +} + +DEBUGFS_READ_WRITE_FILE_OPS(interrupt); +DEBUGFS_READ_FILE_OPS(fh_reg); +DEBUGFS_READ_FILE_OPS(rx_queue); +DEBUGFS_READ_FILE_OPS(tx_queue); +DEBUGFS_WRITE_FILE_OPS(csr); + +/* + * Create the debugfs files and directories + * + */ +static int iwl_trans_pcie_dbgfs_register(struct iwl_trans *trans, + struct dentry *dir) +{ + DEBUGFS_ADD_FILE(rx_queue, dir, S_IRUSR); + DEBUGFS_ADD_FILE(tx_queue, dir, S_IRUSR); + DEBUGFS_ADD_FILE(interrupt, dir, S_IWUSR | S_IRUSR); + DEBUGFS_ADD_FILE(csr, dir, S_IWUSR); + DEBUGFS_ADD_FILE(fh_reg, dir, S_IRUSR); + return 0; + +err: + IWL_ERR(trans, "failed to create the trans debugfs entry\n"); + return -ENOMEM; +} +#else +static int iwl_trans_pcie_dbgfs_register(struct iwl_trans *trans, + struct dentry *dir) +{ + return 0; +} +#endif /*CPTCFG_IWLWIFI_DEBUGFS */ + +static const struct iwl_trans_ops trans_ops_pcie = { + .start_hw = iwl_trans_pcie_start_hw, + .stop_hw = iwl_trans_pcie_stop_hw, + .fw_alive = iwl_trans_pcie_fw_alive, + .start_fw = iwl_trans_pcie_start_fw, + .stop_device = iwl_trans_pcie_stop_device, + + .d3_suspend = iwl_trans_pcie_d3_suspend, + .d3_resume = iwl_trans_pcie_d3_resume, + + .send_cmd = iwl_trans_pcie_send_hcmd, + + .tx = iwl_trans_pcie_tx, + .reclaim = iwl_trans_pcie_reclaim, + + .txq_disable = iwl_trans_pcie_txq_disable, + .txq_enable = iwl_trans_pcie_txq_enable, + + .dbgfs_register = iwl_trans_pcie_dbgfs_register, + + .wait_tx_queue_empty = iwl_trans_pcie_wait_txq_empty, + +#ifdef CONFIG_PM_SLEEP + .suspend = iwl_trans_pcie_suspend, + .resume = iwl_trans_pcie_resume, +#endif + .write8 = iwl_trans_pcie_write8, + .write32 = iwl_trans_pcie_write32, + .read32 = iwl_trans_pcie_read32, + .read_prph = iwl_trans_pcie_read_prph, + .write_prph = iwl_trans_pcie_write_prph, + .read_mem = iwl_trans_pcie_read_mem, + .write_mem = iwl_trans_pcie_write_mem, + .configure = iwl_trans_pcie_configure, + .set_pmi = iwl_trans_pcie_set_pmi, + .grab_nic_access = iwl_trans_pcie_grab_nic_access, + .release_nic_access = iwl_trans_pcie_release_nic_access, + .set_bits_mask = iwl_trans_pcie_set_bits_mask, +}; + +struct iwl_trans *iwl_trans_pcie_alloc(struct pci_dev *pdev, + const struct pci_device_id *ent, + const struct iwl_cfg *cfg) +{ + struct iwl_trans_pcie *trans_pcie; + struct iwl_trans *trans; + u16 pci_cmd; + int err; + + trans = kzalloc(sizeof(struct iwl_trans) + + sizeof(struct iwl_trans_pcie), GFP_KERNEL); + + if (!trans) + return NULL; + + trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + trans->ops = &trans_ops_pcie; + trans->cfg = cfg; + trans_lockdep_init(trans); + trans_pcie->trans = trans; + spin_lock_init(&trans_pcie->irq_lock); + spin_lock_init(&trans_pcie->reg_lock); + init_waitqueue_head(&trans_pcie->ucode_write_waitq); + + if (pci_enable_device(pdev)) { + err = -ENODEV; + goto out_no_pci; + } + + /* W/A - seems to solve weird behavior. We need to remove this if we + * don't want to stay in L1 all the time. This wastes a lot of power */ + pci_disable_link_state(pdev, PCIE_LINK_STATE_L0S | PCIE_LINK_STATE_L1 | + PCIE_LINK_STATE_CLKPM); + + pci_set_master(pdev); + + err = pci_set_dma_mask(pdev, DMA_BIT_MASK(36)); + if (!err) + err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(36)); + if (err) { + err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + if (!err) + err = pci_set_consistent_dma_mask(pdev, + DMA_BIT_MASK(32)); + /* both attempts failed: */ + if (err) { + dev_err(&pdev->dev, "No suitable DMA available\n"); + goto out_pci_disable_device; + } + } + + err = pci_request_regions(pdev, DRV_NAME); + if (err) { + dev_err(&pdev->dev, "pci_request_regions failed\n"); + goto out_pci_disable_device; + } + + trans_pcie->hw_base = pci_ioremap_bar(pdev, 0); + if (!trans_pcie->hw_base) { + dev_err(&pdev->dev, "pci_ioremap_bar failed\n"); + err = -ENODEV; + goto out_pci_release_regions; + } + + /* We disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state */ + pci_write_config_byte(pdev, PCI_CFG_RETRY_TIMEOUT, 0x00); + + err = pci_enable_msi(pdev); + if (err) { + dev_err(&pdev->dev, "pci_enable_msi failed(0X%x)\n", err); + /* enable rfkill interrupt: hw bug w/a */ + pci_read_config_word(pdev, PCI_COMMAND, &pci_cmd); + if (pci_cmd & PCI_COMMAND_INTX_DISABLE) { + pci_cmd &= ~PCI_COMMAND_INTX_DISABLE; + pci_write_config_word(pdev, PCI_COMMAND, pci_cmd); + } + } + + trans->dev = &pdev->dev; + trans_pcie->pci_dev = pdev; + trans->hw_rev = iwl_read32(trans, CSR_HW_REV); + trans->hw_id = (pdev->device << 16) + pdev->subsystem_device; + snprintf(trans->hw_id_str, sizeof(trans->hw_id_str), + "PCI ID: 0x%04X:0x%04X", pdev->device, pdev->subsystem_device); + + /* Initialize the wait queue for commands */ + init_waitqueue_head(&trans_pcie->wait_command_queue); + + snprintf(trans->dev_cmd_pool_name, sizeof(trans->dev_cmd_pool_name), + "iwl_cmd_pool:%s", dev_name(trans->dev)); + + trans->dev_cmd_headroom = 0; + trans->dev_cmd_pool = + kmem_cache_create(trans->dev_cmd_pool_name, + sizeof(struct iwl_device_cmd) + + trans->dev_cmd_headroom, + sizeof(void *), + SLAB_HWCACHE_ALIGN, + NULL); + + if (!trans->dev_cmd_pool) + goto out_pci_disable_msi; + + trans_pcie->inta_mask = CSR_INI_SET_MASK; + + if (iwl_pcie_alloc_ict(trans)) + goto out_free_cmd_pool; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31) + if (compat_request_threaded_irq(&trans_pcie->irq_compat, + pdev->irq, iwl_pcie_isr_ict, + iwl_pcie_irq_handler, + IRQF_SHARED, DRV_NAME, trans)) { +#else + if (request_threaded_irq(pdev->irq, iwl_pcie_isr_ict, + iwl_pcie_irq_handler, + IRQF_SHARED, DRV_NAME, trans)) { +#endif + IWL_ERR(trans, "Error allocating IRQ %d\n", pdev->irq); + goto out_free_ict; + } + + return trans; + +out_free_ict: + iwl_pcie_free_ict(trans); +out_free_cmd_pool: + kmem_cache_destroy(trans->dev_cmd_pool); +out_pci_disable_msi: + pci_disable_msi(pdev); +out_pci_release_regions: + pci_release_regions(pdev); +out_pci_disable_device: + pci_disable_device(pdev); +out_no_pci: + kfree(trans); + return NULL; +} diff --git a/drivers/net/wireless/iwlwifi/pcie/tx.c b/drivers/net/wireless/iwlwifi/pcie/tx.c new file mode 100644 index 0000000..5f5165c --- /dev/null +++ b/drivers/net/wireless/iwlwifi/pcie/tx.c @@ -0,0 +1,1745 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * Portions of this file are derived from the ipw3945 project, as well + * as portions of the ieee80211 subsystem header files. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ +#include +#include +#include + +#include "iwl-debug.h" +#include "iwl-csr.h" +#include "iwl-prph.h" +#include "iwl-io.h" +#include "iwl-op-mode.h" +#include "internal.h" +/* FIXME: need to abstract out TX command (once we know what it looks like) */ +#include "dvm/commands.h" + +#define IWL_TX_CRC_SIZE 4 +#define IWL_TX_DELIMITER_SIZE 4 + +/*************** DMA-QUEUE-GENERAL-FUNCTIONS ***** + * DMA services + * + * Theory of operation + * + * A Tx or Rx queue resides in host DRAM, and is comprised of a circular buffer + * of buffer descriptors, each of which points to one or more data buffers for + * the device to read from or fill. Driver and device exchange status of each + * queue via "read" and "write" pointers. Driver keeps minimum of 2 empty + * entries in each circular buffer, to protect against confusing empty and full + * queue states. + * + * The device reads or writes the data in the queues via the device's several + * DMA/FIFO channels. Each queue is mapped to a single DMA channel. + * + * For Tx queue, there are low mark and high mark limits. If, after queuing + * the packet for Tx, free space become < low mark, Tx queue stopped. When + * reclaiming packets (on 'tx done IRQ), if free space become > high mark, + * Tx queue resumed. + * + ***************************************************/ +static int iwl_queue_space(const struct iwl_queue *q) +{ + int s = q->read_ptr - q->write_ptr; + + if (q->read_ptr > q->write_ptr) + s -= q->n_bd; + + if (s <= 0) + s += q->n_window; + /* keep some reserve to not confuse empty and full situations */ + s -= 2; + if (s < 0) + s = 0; + return s; +} + +/* + * iwl_queue_init - Initialize queue's high/low-water and read/write indexes + */ +static int iwl_queue_init(struct iwl_queue *q, int count, int slots_num, u32 id) +{ + q->n_bd = count; + q->n_window = slots_num; + q->id = id; + + /* count must be power-of-two size, otherwise iwl_queue_inc_wrap + * and iwl_queue_dec_wrap are broken. */ + if (WARN_ON(!is_power_of_2(count))) + return -EINVAL; + + /* slots_num must be power-of-two size, otherwise + * get_cmd_index is broken. */ + if (WARN_ON(!is_power_of_2(slots_num))) + return -EINVAL; + + q->low_mark = q->n_window / 4; + if (q->low_mark < 4) + q->low_mark = 4; + + q->high_mark = q->n_window / 8; + if (q->high_mark < 2) + q->high_mark = 2; + + q->write_ptr = 0; + q->read_ptr = 0; + + return 0; +} + +static int iwl_pcie_alloc_dma_ptr(struct iwl_trans *trans, + struct iwl_dma_ptr *ptr, size_t size) +{ + if (WARN_ON(ptr->addr)) + return -EINVAL; + + ptr->addr = dma_alloc_coherent(trans->dev, size, + &ptr->dma, GFP_KERNEL); + if (!ptr->addr) + return -ENOMEM; + ptr->size = size; + return 0; +} + +static void iwl_pcie_free_dma_ptr(struct iwl_trans *trans, + struct iwl_dma_ptr *ptr) +{ + if (unlikely(!ptr->addr)) + return; + + dma_free_coherent(trans->dev, ptr->size, ptr->addr, ptr->dma); + memset(ptr, 0, sizeof(*ptr)); +} + +static void iwl_pcie_txq_stuck_timer(unsigned long data) +{ + struct iwl_txq *txq = (void *)data; + struct iwl_queue *q = &txq->q; + struct iwl_trans_pcie *trans_pcie = txq->trans_pcie; + struct iwl_trans *trans = iwl_trans_pcie_get_trans(trans_pcie); + u32 scd_sram_addr = trans_pcie->scd_base_addr + + SCD_TX_STTS_QUEUE_OFFSET(txq->q.id); + u8 buf[16]; + int i; + + spin_lock(&txq->lock); + /* check if triggered erroneously */ + if (txq->q.read_ptr == txq->q.write_ptr) { + spin_unlock(&txq->lock); + return; + } + spin_unlock(&txq->lock); + + IWL_ERR(trans, "Queue %d stuck for %u ms.\n", txq->q.id, + jiffies_to_msecs(trans_pcie->wd_timeout)); + IWL_ERR(trans, "Current SW read_ptr %d write_ptr %d\n", + txq->q.read_ptr, txq->q.write_ptr); + + iwl_trans_read_mem_bytes(trans, scd_sram_addr, buf, sizeof(buf)); + + iwl_print_hex_error(trans, buf, sizeof(buf)); + + for (i = 0; i < FH_TCSR_CHNL_NUM; i++) + IWL_ERR(trans, "FH TRBs(%d) = 0x%08x\n", i, + iwl_read_direct32(trans, FH_TX_TRB_REG(i))); + + for (i = 0; i < trans->cfg->base_params->num_of_queues; i++) { + u32 status = iwl_read_prph(trans, SCD_QUEUE_STATUS_BITS(i)); + u8 fifo = (status >> SCD_QUEUE_STTS_REG_POS_TXF) & 0x7; + bool active = !!(status & BIT(SCD_QUEUE_STTS_REG_POS_ACTIVE)); + u32 tbl_dw = + iwl_trans_read_mem32(trans, + trans_pcie->scd_base_addr + + SCD_TRANS_TBL_OFFSET_QUEUE(i)); + + if (i & 0x1) + tbl_dw = (tbl_dw & 0xFFFF0000) >> 16; + else + tbl_dw = tbl_dw & 0x0000FFFF; + + IWL_ERR(trans, + "Q %d is %sactive and mapped to fifo %d ra_tid 0x%04x [%d,%d]\n", + i, active ? "" : "in", fifo, tbl_dw, + iwl_read_prph(trans, + SCD_QUEUE_RDPTR(i)) & (txq->q.n_bd - 1), + iwl_read_prph(trans, SCD_QUEUE_WRPTR(i))); + } + + for (i = q->read_ptr; i != q->write_ptr; + i = iwl_queue_inc_wrap(i, q->n_bd)) + IWL_ERR(trans, "scratch %d = 0x%08x\n", i, + le32_to_cpu(txq->scratchbufs[i].scratch)); + + iwl_op_mode_nic_error(trans->op_mode); +} + +/* + * iwl_pcie_txq_update_byte_cnt_tbl - Set up entry in Tx byte-count array + */ +static void iwl_pcie_txq_update_byte_cnt_tbl(struct iwl_trans *trans, + struct iwl_txq *txq, u16 byte_cnt) +{ + struct iwlagn_scd_bc_tbl *scd_bc_tbl; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int write_ptr = txq->q.write_ptr; + int txq_id = txq->q.id; + u8 sec_ctl = 0; + u8 sta_id = 0; + u16 len = byte_cnt + IWL_TX_CRC_SIZE + IWL_TX_DELIMITER_SIZE; + __le16 bc_ent; + struct iwl_tx_cmd *tx_cmd = + (void *) txq->entries[txq->q.write_ptr].cmd->payload; + + scd_bc_tbl = trans_pcie->scd_bc_tbls.addr; + + WARN_ON(len > 0xFFF || write_ptr >= TFD_QUEUE_SIZE_MAX); + + sta_id = tx_cmd->sta_id; + sec_ctl = tx_cmd->sec_ctl; + + switch (sec_ctl & TX_CMD_SEC_MSK) { + case TX_CMD_SEC_CCM: + len += CCMP_MIC_LEN; + break; + case TX_CMD_SEC_TKIP: + len += TKIP_ICV_LEN; + break; + case TX_CMD_SEC_WEP: + len += WEP_IV_LEN + WEP_ICV_LEN; + break; + } + + if (trans_pcie->bc_table_dword) + len = DIV_ROUND_UP(len, 4); + + bc_ent = cpu_to_le16(len | (sta_id << 12)); + + scd_bc_tbl[txq_id].tfd_offset[write_ptr] = bc_ent; + + if (write_ptr < TFD_QUEUE_SIZE_BC_DUP) + scd_bc_tbl[txq_id]. + tfd_offset[TFD_QUEUE_SIZE_MAX + write_ptr] = bc_ent; +} + +static void iwl_pcie_txq_inval_byte_cnt_tbl(struct iwl_trans *trans, + struct iwl_txq *txq) +{ + struct iwl_trans_pcie *trans_pcie = + IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwlagn_scd_bc_tbl *scd_bc_tbl = trans_pcie->scd_bc_tbls.addr; + int txq_id = txq->q.id; + int read_ptr = txq->q.read_ptr; + u8 sta_id = 0; + __le16 bc_ent; + struct iwl_tx_cmd *tx_cmd = + (void *)txq->entries[txq->q.read_ptr].cmd->payload; + + WARN_ON(read_ptr >= TFD_QUEUE_SIZE_MAX); + + if (txq_id != trans_pcie->cmd_queue) + sta_id = tx_cmd->sta_id; + + bc_ent = cpu_to_le16(1 | (sta_id << 12)); + scd_bc_tbl[txq_id].tfd_offset[read_ptr] = bc_ent; + + if (read_ptr < TFD_QUEUE_SIZE_BC_DUP) + scd_bc_tbl[txq_id]. + tfd_offset[TFD_QUEUE_SIZE_MAX + read_ptr] = bc_ent; +} + +/* + * iwl_pcie_txq_inc_wr_ptr - Send new write index to hardware + */ +void iwl_pcie_txq_inc_wr_ptr(struct iwl_trans *trans, struct iwl_txq *txq) +{ + u32 reg = 0; + int txq_id = txq->q.id; + + if (txq->need_update == 0) + return; + + if (trans->cfg->base_params->shadow_reg_enable) { + /* shadow register enabled */ + iwl_write32(trans, HBUS_TARG_WRPTR, + txq->q.write_ptr | (txq_id << 8)); + } else { + struct iwl_trans_pcie *trans_pcie = + IWL_TRANS_GET_PCIE_TRANS(trans); + /* if we're trying to save power */ + if (test_bit(STATUS_TPOWER_PMI, &trans_pcie->status)) { + /* wake up nic if it's powered down ... + * uCode will wake up, and interrupt us again, so next + * time we'll skip this part. */ + reg = iwl_read32(trans, CSR_UCODE_DRV_GP1); + + if (reg & CSR_UCODE_DRV_GP1_BIT_MAC_SLEEP) { + IWL_DEBUG_INFO(trans, + "Tx queue %d requesting wakeup," + " GP1 = 0x%x\n", txq_id, reg); + iwl_set_bit(trans, CSR_GP_CNTRL, + CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); + return; + } + + IWL_DEBUG_TX(trans, "Q:%d WR: 0x%x\n", txq_id, + txq->q.write_ptr); + + iwl_write_direct32(trans, HBUS_TARG_WRPTR, + txq->q.write_ptr | (txq_id << 8)); + + /* + * else not in power-save mode, + * uCode will never sleep when we're + * trying to tx (during RFKILL, we're not trying to tx). + */ + } else + iwl_write32(trans, HBUS_TARG_WRPTR, + txq->q.write_ptr | (txq_id << 8)); + } + txq->need_update = 0; +} + +static inline dma_addr_t iwl_pcie_tfd_tb_get_addr(struct iwl_tfd *tfd, u8 idx) +{ + struct iwl_tfd_tb *tb = &tfd->tbs[idx]; + + dma_addr_t addr = get_unaligned_le32(&tb->lo); + if (sizeof(dma_addr_t) > sizeof(u32)) + addr |= + ((dma_addr_t)(le16_to_cpu(tb->hi_n_len) & 0xF) << 16) << 16; + + return addr; +} + +static inline u16 iwl_pcie_tfd_tb_get_len(struct iwl_tfd *tfd, u8 idx) +{ + struct iwl_tfd_tb *tb = &tfd->tbs[idx]; + + return le16_to_cpu(tb->hi_n_len) >> 4; +} + +static inline void iwl_pcie_tfd_set_tb(struct iwl_tfd *tfd, u8 idx, + dma_addr_t addr, u16 len) +{ + struct iwl_tfd_tb *tb = &tfd->tbs[idx]; + u16 hi_n_len = len << 4; + + put_unaligned_le32(addr, &tb->lo); + if (sizeof(dma_addr_t) > sizeof(u32)) + hi_n_len |= ((addr >> 16) >> 16) & 0xF; + + tb->hi_n_len = cpu_to_le16(hi_n_len); + + tfd->num_tbs = idx + 1; +} + +static inline u8 iwl_pcie_tfd_get_num_tbs(struct iwl_tfd *tfd) +{ + return tfd->num_tbs & 0x1f; +} + +static void iwl_pcie_tfd_unmap(struct iwl_trans *trans, + struct iwl_cmd_meta *meta, + struct iwl_tfd *tfd) +{ + int i; + int num_tbs; + + /* Sanity check on number of chunks */ + num_tbs = iwl_pcie_tfd_get_num_tbs(tfd); + + if (num_tbs >= IWL_NUM_OF_TBS) { + IWL_ERR(trans, "Too many chunks: %i\n", num_tbs); + /* @todo issue fatal error, it is quite serious situation */ + return; + } + + /* first TB is never freed - it's the scratchbuf data */ + + for (i = 1; i < num_tbs; i++) + dma_unmap_single(trans->dev, iwl_pcie_tfd_tb_get_addr(tfd, i), + iwl_pcie_tfd_tb_get_len(tfd, i), + DMA_TO_DEVICE); + + tfd->num_tbs = 0; +} + +/* + * iwl_pcie_txq_free_tfd - Free all chunks referenced by TFD [txq->q.read_ptr] + * @trans - transport private data + * @txq - tx queue + * @dma_dir - the direction of the DMA mapping + * + * Does NOT advance any TFD circular buffer read/write indexes + * Does NOT free the TFD itself (which is within circular buffer) + */ +static void iwl_pcie_txq_free_tfd(struct iwl_trans *trans, struct iwl_txq *txq) +{ + struct iwl_tfd *tfd_tmp = txq->tfds; + + /* rd_ptr is bounded by n_bd and idx is bounded by n_window */ + int rd_ptr = txq->q.read_ptr; + int idx = get_cmd_index(&txq->q, rd_ptr); + + lockdep_assert_held(&txq->lock); + + /* We have only q->n_window txq->entries, but we use q->n_bd tfds */ + iwl_pcie_tfd_unmap(trans, &txq->entries[idx].meta, &tfd_tmp[rd_ptr]); + + /* free SKB */ + if (txq->entries) { + struct sk_buff *skb; + + skb = txq->entries[idx].skb; + + /* Can be called from irqs-disabled context + * If skb is not NULL, it means that the whole queue is being + * freed and that the queue is not empty - free the skb + */ + if (skb) { + iwl_op_mode_free_skb(trans->op_mode, skb); + txq->entries[idx].skb = NULL; + } + } +} + +static int iwl_pcie_txq_build_tfd(struct iwl_trans *trans, struct iwl_txq *txq, + dma_addr_t addr, u16 len, u8 reset) +{ + struct iwl_queue *q; + struct iwl_tfd *tfd, *tfd_tmp; + u32 num_tbs; + + q = &txq->q; + tfd_tmp = txq->tfds; + tfd = &tfd_tmp[q->write_ptr]; + + if (reset) + memset(tfd, 0, sizeof(*tfd)); + + num_tbs = iwl_pcie_tfd_get_num_tbs(tfd); + + /* Each TFD can point to a maximum 20 Tx buffers */ + if (num_tbs >= IWL_NUM_OF_TBS) { + IWL_ERR(trans, "Error can not send more than %d chunks\n", + IWL_NUM_OF_TBS); + return -EINVAL; + } + + if (WARN_ON(addr & ~DMA_BIT_MASK(36))) + return -EINVAL; + + if (unlikely(addr & ~IWL_TX_DMA_MASK)) + IWL_ERR(trans, "Unaligned address = %llx\n", + (unsigned long long)addr); + + iwl_pcie_tfd_set_tb(tfd, num_tbs, addr, len); + + return 0; +} + +static int iwl_pcie_txq_alloc(struct iwl_trans *trans, + struct iwl_txq *txq, int slots_num, + u32 txq_id) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + size_t tfd_sz = sizeof(struct iwl_tfd) * TFD_QUEUE_SIZE_MAX; + size_t scratchbuf_sz; + int i; + + if (WARN_ON(txq->entries || txq->tfds)) + return -EINVAL; + + setup_timer(&txq->stuck_timer, iwl_pcie_txq_stuck_timer, + (unsigned long)txq); + txq->trans_pcie = trans_pcie; + + txq->q.n_window = slots_num; + + txq->entries = kcalloc(slots_num, + sizeof(struct iwl_pcie_txq_entry), + GFP_KERNEL); + + if (!txq->entries) + goto error; + + if (txq_id == trans_pcie->cmd_queue) + for (i = 0; i < slots_num; i++) { + txq->entries[i].cmd = + kmalloc(sizeof(struct iwl_device_cmd), + GFP_KERNEL); + if (!txq->entries[i].cmd) + goto error; + } + + /* Circular buffer of transmit frame descriptors (TFDs), + * shared with device */ + txq->tfds = dma_alloc_coherent(trans->dev, tfd_sz, + &txq->q.dma_addr, GFP_KERNEL); + if (!txq->tfds) + goto error; + + BUILD_BUG_ON(IWL_HCMD_SCRATCHBUF_SIZE != sizeof(*txq->scratchbufs)); + BUILD_BUG_ON(offsetof(struct iwl_pcie_txq_scratch_buf, scratch) != + sizeof(struct iwl_cmd_header) + + offsetof(struct iwl_tx_cmd, scratch)); + + scratchbuf_sz = sizeof(*txq->scratchbufs) * slots_num; + + txq->scratchbufs = dma_alloc_coherent(trans->dev, scratchbuf_sz, + &txq->scratchbufs_dma, + GFP_KERNEL); + if (!txq->scratchbufs) + goto err_free_tfds; + + txq->q.id = txq_id; + + return 0; +err_free_tfds: + dma_free_coherent(trans->dev, tfd_sz, txq->tfds, txq->q.dma_addr); +error: + if (txq->entries && txq_id == trans_pcie->cmd_queue) + for (i = 0; i < slots_num; i++) + kfree(txq->entries[i].cmd); + kfree(txq->entries); + txq->entries = NULL; + + return -ENOMEM; + +} + +static int iwl_pcie_txq_init(struct iwl_trans *trans, struct iwl_txq *txq, + int slots_num, u32 txq_id) +{ + int ret; + + txq->need_update = 0; + + /* TFD_QUEUE_SIZE_MAX must be power-of-two size, otherwise + * iwl_queue_inc_wrap and iwl_queue_dec_wrap are broken. */ + BUILD_BUG_ON(TFD_QUEUE_SIZE_MAX & (TFD_QUEUE_SIZE_MAX - 1)); + + /* Initialize queue's high/low-water marks, and head/tail indexes */ + ret = iwl_queue_init(&txq->q, TFD_QUEUE_SIZE_MAX, slots_num, + txq_id); + if (ret) + return ret; + + spin_lock_init(&txq->lock); + + /* + * Tell nic where to find circular buffer of Tx Frame Descriptors for + * given Tx queue, and enable the DMA channel used for that queue. + * Circular buffer (TFD queue in DRAM) physical base address */ + iwl_write_direct32(trans, FH_MEM_CBBC_QUEUE(txq_id), + txq->q.dma_addr >> 8); + + return 0; +} + +/* + * iwl_pcie_txq_unmap - Unmap any remaining DMA mappings and free skb's + */ +static void iwl_pcie_txq_unmap(struct iwl_trans *trans, int txq_id) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq = &trans_pcie->txq[txq_id]; + struct iwl_queue *q = &txq->q; + + if (!q->n_bd) + return; + + spin_lock_bh(&txq->lock); + while (q->write_ptr != q->read_ptr) { + IWL_DEBUG_TX_REPLY(trans, "Q %d Free %d\n", + txq_id, q->read_ptr); + iwl_pcie_txq_free_tfd(trans, txq); + q->read_ptr = iwl_queue_inc_wrap(q->read_ptr, q->n_bd); + } + txq->active = false; + spin_unlock_bh(&txq->lock); + + /* just in case - this queue may have been stopped */ + iwl_wake_queue(trans, txq); +} + +/* + * iwl_pcie_txq_free - Deallocate DMA queue. + * @txq: Transmit queue to deallocate. + * + * Empty queue by removing and destroying all BD's. + * Free all buffers. + * 0-fill, but do not free "txq" descriptor structure. + */ +static void iwl_pcie_txq_free(struct iwl_trans *trans, int txq_id) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq = &trans_pcie->txq[txq_id]; + struct device *dev = trans->dev; + int i; + + if (WARN_ON(!txq)) + return; + + iwl_pcie_txq_unmap(trans, txq_id); + + /* De-alloc array of command/tx buffers */ + if (txq_id == trans_pcie->cmd_queue) + for (i = 0; i < txq->q.n_window; i++) { + kfree(txq->entries[i].cmd); + kfree(txq->entries[i].free_buf); + } + + /* De-alloc circular buffer of TFDs */ + if (txq->q.n_bd) { + dma_free_coherent(dev, sizeof(struct iwl_tfd) * + txq->q.n_bd, txq->tfds, txq->q.dma_addr); + txq->q.dma_addr = 0; + + dma_free_coherent(dev, + sizeof(*txq->scratchbufs) * txq->q.n_window, + txq->scratchbufs, txq->scratchbufs_dma); + } + + kfree(txq->entries); + txq->entries = NULL; + + del_timer_sync(&txq->stuck_timer); + + /* 0-fill queue descriptor structure */ + memset(txq, 0, sizeof(*txq)); +} + +/* + * Activate/Deactivate Tx DMA/FIFO channels according tx fifos mask + */ +static void iwl_pcie_txq_set_sched(struct iwl_trans *trans, u32 mask) +{ + struct iwl_trans_pcie __maybe_unused *trans_pcie = + IWL_TRANS_GET_PCIE_TRANS(trans); + + iwl_write_prph(trans, SCD_TXFACT, mask); +} + +void iwl_pcie_tx_start(struct iwl_trans *trans, u32 scd_base_addr) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int nq = trans->cfg->base_params->num_of_queues; + int chan; + u32 reg_val; + int clear_dwords = (SCD_TRANS_TBL_OFFSET_QUEUE(nq) - + SCD_CONTEXT_MEM_LOWER_BOUND) / sizeof(u32); + + /* make sure all queue are not stopped/used */ + memset(trans_pcie->queue_stopped, 0, sizeof(trans_pcie->queue_stopped)); + memset(trans_pcie->queue_used, 0, sizeof(trans_pcie->queue_used)); + + trans_pcie->scd_base_addr = + iwl_read_prph(trans, SCD_SRAM_BASE_ADDR); + + WARN_ON(scd_base_addr != 0 && + scd_base_addr != trans_pcie->scd_base_addr); + + /* reset context data, TX status and translation data */ + iwl_trans_write_mem(trans, trans_pcie->scd_base_addr + + SCD_CONTEXT_MEM_LOWER_BOUND, + NULL, clear_dwords); + + iwl_write_prph(trans, SCD_DRAM_BASE_ADDR, + trans_pcie->scd_bc_tbls.dma >> 10); + + /* The chain extension of the SCD doesn't work well. This feature is + * enabled by default by the HW, so we need to disable it manually. + */ + iwl_write_prph(trans, SCD_CHAINEXT_EN, 0); + + iwl_trans_ac_txq_enable(trans, trans_pcie->cmd_queue, + trans_pcie->cmd_fifo); + + /* Activate all Tx DMA/FIFO channels */ + iwl_pcie_txq_set_sched(trans, IWL_MASK(0, 7)); + + /* Enable DMA channel */ + for (chan = 0; chan < FH_TCSR_CHNL_NUM; chan++) + iwl_write_direct32(trans, FH_TCSR_CHNL_TX_CONFIG_REG(chan), + FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE | + FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE); + + /* Update FH chicken bits */ + reg_val = iwl_read_direct32(trans, FH_TX_CHICKEN_BITS_REG); + iwl_write_direct32(trans, FH_TX_CHICKEN_BITS_REG, + reg_val | FH_TX_CHICKEN_BITS_SCD_AUTO_RETRY_EN); + + /* Enable L1-Active */ + iwl_clear_bits_prph(trans, APMG_PCIDEV_STT_REG, + APMG_PCIDEV_STT_VAL_L1_ACT_DIS); +} + +void iwl_trans_pcie_tx_reset(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int txq_id; + + for (txq_id = 0; txq_id < trans->cfg->base_params->num_of_queues; + txq_id++) { + struct iwl_txq *txq = &trans_pcie->txq[txq_id]; + + iwl_write_direct32(trans, FH_MEM_CBBC_QUEUE(txq_id), + txq->q.dma_addr >> 8); + iwl_pcie_txq_unmap(trans, txq_id); + txq->q.read_ptr = 0; + txq->q.write_ptr = 0; + } + + /* Tell NIC where to find the "keep warm" buffer */ + iwl_write_direct32(trans, FH_KW_MEM_ADDR_REG, + trans_pcie->kw.dma >> 4); + + iwl_pcie_tx_start(trans, trans_pcie->scd_base_addr); +} + +/* + * iwl_pcie_tx_stop - Stop all Tx DMA channels + */ +int iwl_pcie_tx_stop(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ch, txq_id, ret; + unsigned long flags; + + /* Turn off all Tx DMA fifos */ + spin_lock_irqsave(&trans_pcie->irq_lock, flags); + + iwl_pcie_txq_set_sched(trans, 0); + + /* Stop each Tx DMA channel, and wait for it to be idle */ + for (ch = 0; ch < FH_TCSR_CHNL_NUM; ch++) { + iwl_write_direct32(trans, + FH_TCSR_CHNL_TX_CONFIG_REG(ch), 0x0); + ret = iwl_poll_direct_bit(trans, FH_TSSR_TX_STATUS_REG, + FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(ch), 1000); + if (ret < 0) + IWL_ERR(trans, + "Failing on timeout while stopping DMA channel %d [0x%08x]\n", + ch, + iwl_read_direct32(trans, + FH_TSSR_TX_STATUS_REG)); + } + spin_unlock_irqrestore(&trans_pcie->irq_lock, flags); + + if (!trans_pcie->txq) { + IWL_WARN(trans, + "Stopping tx queues that aren't allocated...\n"); + return 0; + } + + /* Unmap DMA from host system and free skb's */ + for (txq_id = 0; txq_id < trans->cfg->base_params->num_of_queues; + txq_id++) + iwl_pcie_txq_unmap(trans, txq_id); + + return 0; +} + +/* + * iwl_trans_tx_free - Free TXQ Context + * + * Destroy all TX DMA queues and structures + */ +void iwl_pcie_tx_free(struct iwl_trans *trans) +{ + int txq_id; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + /* Tx queues */ + if (trans_pcie->txq) { + for (txq_id = 0; + txq_id < trans->cfg->base_params->num_of_queues; txq_id++) + iwl_pcie_txq_free(trans, txq_id); + } + + kfree(trans_pcie->txq); + trans_pcie->txq = NULL; + + iwl_pcie_free_dma_ptr(trans, &trans_pcie->kw); + + iwl_pcie_free_dma_ptr(trans, &trans_pcie->scd_bc_tbls); +} + +/* + * iwl_pcie_tx_alloc - allocate TX context + * Allocate all Tx DMA structures and initialize them + */ +static int iwl_pcie_tx_alloc(struct iwl_trans *trans) +{ + int ret; + int txq_id, slots_num; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + u16 scd_bc_tbls_size = trans->cfg->base_params->num_of_queues * + sizeof(struct iwlagn_scd_bc_tbl); + + /*It is not allowed to alloc twice, so warn when this happens. + * We cannot rely on the previous allocation, so free and fail */ + if (WARN_ON(trans_pcie->txq)) { + ret = -EINVAL; + goto error; + } + + ret = iwl_pcie_alloc_dma_ptr(trans, &trans_pcie->scd_bc_tbls, + scd_bc_tbls_size); + if (ret) { + IWL_ERR(trans, "Scheduler BC Table allocation failed\n"); + goto error; + } + + /* Alloc keep-warm buffer */ + ret = iwl_pcie_alloc_dma_ptr(trans, &trans_pcie->kw, IWL_KW_SIZE); + if (ret) { + IWL_ERR(trans, "Keep Warm allocation failed\n"); + goto error; + } + + trans_pcie->txq = kcalloc(trans->cfg->base_params->num_of_queues, + sizeof(struct iwl_txq), GFP_KERNEL); + if (!trans_pcie->txq) { + IWL_ERR(trans, "Not enough memory for txq\n"); + ret = ENOMEM; + goto error; + } + + /* Alloc and init all Tx queues, including the command queue (#4/#9) */ + for (txq_id = 0; txq_id < trans->cfg->base_params->num_of_queues; + txq_id++) { + slots_num = (txq_id == trans_pcie->cmd_queue) ? + TFD_CMD_SLOTS : TFD_TX_CMD_SLOTS; + ret = iwl_pcie_txq_alloc(trans, &trans_pcie->txq[txq_id], + slots_num, txq_id); + if (ret) { + IWL_ERR(trans, "Tx %d queue alloc failed\n", txq_id); + goto error; + } + } + + return 0; + +error: + iwl_pcie_tx_free(trans); + + return ret; +} +int iwl_pcie_tx_init(struct iwl_trans *trans) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret; + int txq_id, slots_num; + unsigned long flags; + bool alloc = false; + + if (!trans_pcie->txq) { + ret = iwl_pcie_tx_alloc(trans); + if (ret) + goto error; + alloc = true; + } + + spin_lock_irqsave(&trans_pcie->irq_lock, flags); + + /* Turn off all Tx DMA fifos */ + iwl_write_prph(trans, SCD_TXFACT, 0); + + /* Tell NIC where to find the "keep warm" buffer */ + iwl_write_direct32(trans, FH_KW_MEM_ADDR_REG, + trans_pcie->kw.dma >> 4); + + spin_unlock_irqrestore(&trans_pcie->irq_lock, flags); + + /* Alloc and init all Tx queues, including the command queue (#4/#9) */ + for (txq_id = 0; txq_id < trans->cfg->base_params->num_of_queues; + txq_id++) { + slots_num = (txq_id == trans_pcie->cmd_queue) ? + TFD_CMD_SLOTS : TFD_TX_CMD_SLOTS; + ret = iwl_pcie_txq_init(trans, &trans_pcie->txq[txq_id], + slots_num, txq_id); + if (ret) { + IWL_ERR(trans, "Tx %d queue init failed\n", txq_id); + goto error; + } + } + + return 0; +error: + /*Upon error, free only if we allocated something */ + if (alloc) + iwl_pcie_tx_free(trans); + return ret; +} + +static inline void iwl_pcie_txq_progress(struct iwl_trans_pcie *trans_pcie, + struct iwl_txq *txq) +{ + if (!trans_pcie->wd_timeout) + return; + + /* + * if empty delete timer, otherwise move timer forward + * since we're making progress on this queue + */ + if (txq->q.read_ptr == txq->q.write_ptr) + del_timer(&txq->stuck_timer); + else + mod_timer(&txq->stuck_timer, jiffies + trans_pcie->wd_timeout); +} + +/* Frees buffers until index _not_ inclusive */ +void iwl_trans_pcie_reclaim(struct iwl_trans *trans, int txq_id, int ssn, + struct sk_buff_head *skbs) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq = &trans_pcie->txq[txq_id]; + /* n_bd is usually 256 => n_bd - 1 = 0xff */ + int tfd_num = ssn & (txq->q.n_bd - 1); + struct iwl_queue *q = &txq->q; + int last_to_free; + + /* This function is not meant to release cmd queue*/ + if (WARN_ON(txq_id == trans_pcie->cmd_queue)) + return; + + spin_lock_bh(&txq->lock); + + if (!txq->active) { + IWL_DEBUG_TX_QUEUES(trans, "Q %d inactive - ignoring idx %d\n", + txq_id, ssn); + goto out; + } + + if (txq->q.read_ptr == tfd_num) + goto out; + + IWL_DEBUG_TX_REPLY(trans, "[Q %d] %d -> %d (%d)\n", + txq_id, txq->q.read_ptr, tfd_num, ssn); + + /*Since we free until index _not_ inclusive, the one before index is + * the last we will free. This one must be used */ + last_to_free = iwl_queue_dec_wrap(tfd_num, q->n_bd); + + if (!iwl_queue_used(q, last_to_free)) { + IWL_ERR(trans, + "%s: Read index for DMA queue txq id (%d), last_to_free %d is out of range [0-%d] %d %d.\n", + __func__, txq_id, last_to_free, q->n_bd, + q->write_ptr, q->read_ptr); + goto out; + } + + if (WARN_ON(!skb_queue_empty(skbs))) + goto out; + + for (; + q->read_ptr != tfd_num; + q->read_ptr = iwl_queue_inc_wrap(q->read_ptr, q->n_bd)) { + + if (WARN_ON_ONCE(txq->entries[txq->q.read_ptr].skb == NULL)) + continue; + + __skb_queue_tail(skbs, txq->entries[txq->q.read_ptr].skb); + + txq->entries[txq->q.read_ptr].skb = NULL; + + iwl_pcie_txq_inval_byte_cnt_tbl(trans, txq); + + iwl_pcie_txq_free_tfd(trans, txq); + } + + iwl_pcie_txq_progress(trans_pcie, txq); + + if (iwl_queue_space(&txq->q) > txq->q.low_mark) + iwl_wake_queue(trans, txq); +out: + spin_unlock_bh(&txq->lock); +} + +/* + * iwl_pcie_cmdq_reclaim - Reclaim TX command queue entries already Tx'd + * + * When FW advances 'R' index, all entries between old and new 'R' index + * need to be reclaimed. As result, some free space forms. If there is + * enough free space (> low mark), wake the stack that feeds us. + */ +static void iwl_pcie_cmdq_reclaim(struct iwl_trans *trans, int txq_id, int idx) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq = &trans_pcie->txq[txq_id]; + struct iwl_queue *q = &txq->q; + int nfreed = 0; + + lockdep_assert_held(&txq->lock); + + if ((idx >= q->n_bd) || (!iwl_queue_used(q, idx))) { + IWL_ERR(trans, + "%s: Read index for DMA queue txq id (%d), index %d is out of range [0-%d] %d %d.\n", + __func__, txq_id, idx, q->n_bd, + q->write_ptr, q->read_ptr); + return; + } + + for (idx = iwl_queue_inc_wrap(idx, q->n_bd); q->read_ptr != idx; + q->read_ptr = iwl_queue_inc_wrap(q->read_ptr, q->n_bd)) { + + if (nfreed++ > 0) { + IWL_ERR(trans, "HCMD skipped: index (%d) %d %d\n", + idx, q->write_ptr, q->read_ptr); + iwl_op_mode_nic_error(trans->op_mode); + } + } + + iwl_pcie_txq_progress(trans_pcie, txq); +} + +static int iwl_pcie_txq_set_ratid_map(struct iwl_trans *trans, u16 ra_tid, + u16 txq_id) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 tbl_dw_addr; + u32 tbl_dw; + u16 scd_q2ratid; + + scd_q2ratid = ra_tid & SCD_QUEUE_RA_TID_MAP_RATID_MSK; + + tbl_dw_addr = trans_pcie->scd_base_addr + + SCD_TRANS_TBL_OFFSET_QUEUE(txq_id); + + tbl_dw = iwl_trans_read_mem32(trans, tbl_dw_addr); + + if (txq_id & 0x1) + tbl_dw = (scd_q2ratid << 16) | (tbl_dw & 0x0000FFFF); + else + tbl_dw = scd_q2ratid | (tbl_dw & 0xFFFF0000); + + iwl_trans_write_mem32(trans, tbl_dw_addr, tbl_dw); + + return 0; +} + +static inline void iwl_pcie_txq_set_inactive(struct iwl_trans *trans, + u16 txq_id) +{ + /* Simply stop the queue, but don't change any configuration; + * the SCD_ACT_EN bit is the write-enable mask for the ACTIVE bit. */ + iwl_write_prph(trans, + SCD_QUEUE_STATUS_BITS(txq_id), + (0 << SCD_QUEUE_STTS_REG_POS_ACTIVE)| + (1 << SCD_QUEUE_STTS_REG_POS_SCD_ACT_EN)); +} + +void iwl_trans_pcie_txq_enable(struct iwl_trans *trans, int txq_id, int fifo, + int sta_id, int tid, int frame_limit, u16 ssn) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (test_and_set_bit(txq_id, trans_pcie->queue_used)) + WARN_ONCE(1, "queue %d already used - expect issues", txq_id); + + /* Stop this Tx queue before configuring it */ + iwl_pcie_txq_set_inactive(trans, txq_id); + + /* Set this queue as a chain-building queue unless it is CMD queue */ + if (txq_id != trans_pcie->cmd_queue) + iwl_set_bits_prph(trans, SCD_QUEUECHAIN_SEL, BIT(txq_id)); + + /* If this queue is mapped to a certain station: it is an AGG queue */ + if (sta_id >= 0) { + u16 ra_tid = BUILD_RAxTID(sta_id, tid); + + /* Map receiver-address / traffic-ID to this queue */ + iwl_pcie_txq_set_ratid_map(trans, ra_tid, txq_id); + + /* enable aggregations for the queue */ + iwl_set_bits_prph(trans, SCD_AGGR_SEL, BIT(txq_id)); + } else { + /* + * disable aggregations for the queue, this will also make the + * ra_tid mapping configuration irrelevant since it is now a + * non-AGG queue. + */ + iwl_clear_bits_prph(trans, SCD_AGGR_SEL, BIT(txq_id)); + } + + /* Place first TFD at index corresponding to start sequence number. + * Assumes that ssn_idx is valid (!= 0xFFF) */ + trans_pcie->txq[txq_id].q.read_ptr = (ssn & 0xff); + trans_pcie->txq[txq_id].q.write_ptr = (ssn & 0xff); + + iwl_write_direct32(trans, HBUS_TARG_WRPTR, + (ssn & 0xff) | (txq_id << 8)); + iwl_write_prph(trans, SCD_QUEUE_RDPTR(txq_id), ssn); + + /* Set up Tx window size and frame limit for this queue */ + iwl_trans_write_mem32(trans, trans_pcie->scd_base_addr + + SCD_CONTEXT_QUEUE_OFFSET(txq_id), 0); + iwl_trans_write_mem32(trans, trans_pcie->scd_base_addr + + SCD_CONTEXT_QUEUE_OFFSET(txq_id) + sizeof(u32), + ((frame_limit << SCD_QUEUE_CTX_REG2_WIN_SIZE_POS) & + SCD_QUEUE_CTX_REG2_WIN_SIZE_MSK) | + ((frame_limit << SCD_QUEUE_CTX_REG2_FRAME_LIMIT_POS) & + SCD_QUEUE_CTX_REG2_FRAME_LIMIT_MSK)); + + /* Set up Status area in SRAM, map to Tx DMA/FIFO, activate the queue */ + iwl_write_prph(trans, SCD_QUEUE_STATUS_BITS(txq_id), + (1 << SCD_QUEUE_STTS_REG_POS_ACTIVE) | + (fifo << SCD_QUEUE_STTS_REG_POS_TXF) | + (1 << SCD_QUEUE_STTS_REG_POS_WSL) | + SCD_QUEUE_STTS_REG_MSK); + trans_pcie->txq[txq_id].active = true; + IWL_DEBUG_TX_QUEUES(trans, "Activate queue %d on FIFO %d WrPtr: %d\n", + txq_id, fifo, ssn & 0xff); +} + +void iwl_trans_pcie_txq_disable(struct iwl_trans *trans, int txq_id) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + u32 stts_addr = trans_pcie->scd_base_addr + + SCD_TX_STTS_QUEUE_OFFSET(txq_id); + static const u32 zero_val[4] = {}; + + if (!test_and_clear_bit(txq_id, trans_pcie->queue_used)) { + WARN_ONCE(1, "queue %d not used", txq_id); + return; + } + + iwl_pcie_txq_set_inactive(trans, txq_id); + + iwl_trans_write_mem(trans, stts_addr, (void *)zero_val, + ARRAY_SIZE(zero_val)); + + iwl_pcie_txq_unmap(trans, txq_id); + + IWL_DEBUG_TX_QUEUES(trans, "Deactivate queue %d\n", txq_id); +} + +/*************** HOST COMMAND QUEUE FUNCTIONS *****/ + +/* + * iwl_pcie_enqueue_hcmd - enqueue a uCode command + * @priv: device private data point + * @cmd: a point to the ucode command structure + * + * The function returns < 0 values to indicate the operation is + * failed. On success, it turns the index (> 0) of command in the + * command queue. + */ +static int iwl_pcie_enqueue_hcmd(struct iwl_trans *trans, + struct iwl_host_cmd *cmd) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq = &trans_pcie->txq[trans_pcie->cmd_queue]; + struct iwl_queue *q = &txq->q; + struct iwl_device_cmd *out_cmd; + struct iwl_cmd_meta *out_meta; + void *dup_buf = NULL; + dma_addr_t phys_addr; + int idx; + u16 copy_size, cmd_size, scratch_size; + bool had_nocopy = false; + int i; + u32 cmd_pos; + const u8 *cmddata[IWL_MAX_CMD_TBS_PER_TFD]; + u16 cmdlen[IWL_MAX_CMD_TBS_PER_TFD]; + + copy_size = sizeof(out_cmd->hdr); + cmd_size = sizeof(out_cmd->hdr); + + /* need one for the header if the first is NOCOPY */ + BUILD_BUG_ON(IWL_MAX_CMD_TBS_PER_TFD > IWL_NUM_OF_TBS - 1); + + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + cmddata[i] = cmd->data[i]; + cmdlen[i] = cmd->len[i]; + + if (!cmd->len[i]) + continue; + + /* need at least IWL_HCMD_SCRATCHBUF_SIZE copied */ + if (copy_size < IWL_HCMD_SCRATCHBUF_SIZE) { + int copy = IWL_HCMD_SCRATCHBUF_SIZE - copy_size; + + if (copy > cmdlen[i]) + copy = cmdlen[i]; + cmdlen[i] -= copy; + cmddata[i] += copy; + copy_size += copy; + } + + if (cmd->dataflags[i] & IWL_HCMD_DFL_NOCOPY) { + had_nocopy = true; + if (WARN_ON(cmd->dataflags[i] & IWL_HCMD_DFL_DUP)) { + idx = -EINVAL; + goto free_dup_buf; + } + } else if (cmd->dataflags[i] & IWL_HCMD_DFL_DUP) { + /* + * This is also a chunk that isn't copied + * to the static buffer so set had_nocopy. + */ + had_nocopy = true; + + /* only allowed once */ + if (WARN_ON(dup_buf)) { + idx = -EINVAL; + goto free_dup_buf; + } + + dup_buf = kmemdup(cmddata[i], cmdlen[i], + GFP_ATOMIC); + if (!dup_buf) + return -ENOMEM; + } else { + /* NOCOPY must not be followed by normal! */ + if (WARN_ON(had_nocopy)) { + idx = -EINVAL; + goto free_dup_buf; + } + copy_size += cmdlen[i]; + } + cmd_size += cmd->len[i]; + } + + /* + * If any of the command structures end up being larger than + * the TFD_MAX_PAYLOAD_SIZE and they aren't dynamically + * allocated into separate TFDs, then we will need to + * increase the size of the buffers. + */ + if (WARN(copy_size > TFD_MAX_PAYLOAD_SIZE, + "Command %s (%#x) is too large (%d bytes)\n", + get_cmd_string(trans_pcie, cmd->id), cmd->id, copy_size)) { + idx = -EINVAL; + goto free_dup_buf; + } + + spin_lock_bh(&txq->lock); + + if (iwl_queue_space(q) < ((cmd->flags & CMD_ASYNC) ? 2 : 1)) { + spin_unlock_bh(&txq->lock); + + IWL_ERR(trans, "No space in command queue\n"); + iwl_op_mode_cmd_queue_full(trans->op_mode); + idx = -ENOSPC; + goto free_dup_buf; + } + + idx = get_cmd_index(q, q->write_ptr); + out_cmd = txq->entries[idx].cmd; + out_meta = &txq->entries[idx].meta; + + memset(out_meta, 0, sizeof(*out_meta)); /* re-initialize to NULL */ + if (cmd->flags & CMD_WANT_SKB) + out_meta->source = cmd; + + /* set up the header */ + + out_cmd->hdr.cmd = cmd->id; + out_cmd->hdr.flags = 0; + out_cmd->hdr.sequence = + cpu_to_le16(QUEUE_TO_SEQ(trans_pcie->cmd_queue) | + INDEX_TO_SEQ(q->write_ptr)); + + /* and copy the data that needs to be copied */ + cmd_pos = offsetof(struct iwl_device_cmd, payload); + copy_size = sizeof(out_cmd->hdr); + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + int copy = 0; + + if (!cmd->len[i]) + continue; + + /* need at least IWL_HCMD_SCRATCHBUF_SIZE copied */ + if (copy_size < IWL_HCMD_SCRATCHBUF_SIZE) { + copy = IWL_HCMD_SCRATCHBUF_SIZE - copy_size; + + if (copy > cmd->len[i]) + copy = cmd->len[i]; + } + + /* copy everything if not nocopy/dup */ + if (!(cmd->dataflags[i] & (IWL_HCMD_DFL_NOCOPY | + IWL_HCMD_DFL_DUP))) + copy = cmd->len[i]; + + if (copy) { + memcpy((u8 *)out_cmd + cmd_pos, cmd->data[i], copy); + cmd_pos += copy; + copy_size += copy; + } + } + + IWL_DEBUG_HC(trans, + "Sending command %s (#%x), seq: 0x%04X, %d bytes at %d[%d]:%d\n", + get_cmd_string(trans_pcie, out_cmd->hdr.cmd), + out_cmd->hdr.cmd, le16_to_cpu(out_cmd->hdr.sequence), + cmd_size, q->write_ptr, idx, trans_pcie->cmd_queue); + + /* start the TFD with the scratchbuf */ + scratch_size = min_t(int, copy_size, IWL_HCMD_SCRATCHBUF_SIZE); + memcpy(&txq->scratchbufs[q->write_ptr], &out_cmd->hdr, scratch_size); + iwl_pcie_txq_build_tfd(trans, txq, + iwl_pcie_get_scratchbuf_dma(txq, q->write_ptr), + scratch_size, 1); + + /* map first command fragment, if any remains */ + if (copy_size > scratch_size) { + phys_addr = dma_map_single(trans->dev, + ((u8 *)&out_cmd->hdr) + scratch_size, + copy_size - scratch_size, + DMA_TO_DEVICE); + if (dma_mapping_error(trans->dev, phys_addr)) { + iwl_pcie_tfd_unmap(trans, out_meta, + &txq->tfds[q->write_ptr]); + idx = -ENOMEM; + goto out; + } + + iwl_pcie_txq_build_tfd(trans, txq, phys_addr, + copy_size - scratch_size, 0); + } + + /* map the remaining (adjusted) nocopy/dup fragments */ + for (i = 0; i < IWL_MAX_CMD_TBS_PER_TFD; i++) { + const void *data = cmddata[i]; + + if (!cmdlen[i]) + continue; + if (!(cmd->dataflags[i] & (IWL_HCMD_DFL_NOCOPY | + IWL_HCMD_DFL_DUP))) + continue; + if (cmd->dataflags[i] & IWL_HCMD_DFL_DUP) + data = dup_buf; + phys_addr = dma_map_single(trans->dev, (void *)data, + cmdlen[i], DMA_TO_DEVICE); + if (dma_mapping_error(trans->dev, phys_addr)) { + iwl_pcie_tfd_unmap(trans, out_meta, + &txq->tfds[q->write_ptr]); + idx = -ENOMEM; + goto out; + } + + iwl_pcie_txq_build_tfd(trans, txq, phys_addr, cmdlen[i], 0); + } + + out_meta->flags = cmd->flags; + if (WARN_ON_ONCE(txq->entries[idx].free_buf)) + kfree(txq->entries[idx].free_buf); + txq->entries[idx].free_buf = dup_buf; + + txq->need_update = 1; + + trace_iwlwifi_dev_hcmd(trans->dev, cmd, cmd_size, &out_cmd->hdr); + + /* start timer if queue currently empty */ + if (q->read_ptr == q->write_ptr && trans_pcie->wd_timeout) + mod_timer(&txq->stuck_timer, jiffies + trans_pcie->wd_timeout); + + /* Increment and update queue's write index */ + q->write_ptr = iwl_queue_inc_wrap(q->write_ptr, q->n_bd); + iwl_pcie_txq_inc_wr_ptr(trans, txq); + + out: + spin_unlock_bh(&txq->lock); + free_dup_buf: + if (idx < 0) + kfree(dup_buf); + return idx; +} + +/* + * iwl_pcie_hcmd_complete - Pull unused buffers off the queue and reclaim them + * @rxb: Rx buffer to reclaim + * @handler_status: return value of the handler of the command + * (put in setup_rx_handlers) + * + * If an Rx buffer has an async callback associated with it the callback + * will be executed. The attached skb (if present) will only be freed + * if the callback returns 1 + */ +void iwl_pcie_hcmd_complete(struct iwl_trans *trans, + struct iwl_rx_cmd_buffer *rxb, int handler_status) +{ + struct iwl_rx_packet *pkt = rxb_addr(rxb); + u16 sequence = le16_to_cpu(pkt->hdr.sequence); + int txq_id = SEQ_TO_QUEUE(sequence); + int index = SEQ_TO_INDEX(sequence); + int cmd_index; + struct iwl_device_cmd *cmd; + struct iwl_cmd_meta *meta; + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct iwl_txq *txq = &trans_pcie->txq[trans_pcie->cmd_queue]; + + /* If a Tx command is being handled and it isn't in the actual + * command queue then there a command routing bug has been introduced + * in the queue management code. */ + if (WARN(txq_id != trans_pcie->cmd_queue, + "wrong command queue %d (should be %d), sequence 0x%X readp=%d writep=%d\n", + txq_id, trans_pcie->cmd_queue, sequence, + trans_pcie->txq[trans_pcie->cmd_queue].q.read_ptr, + trans_pcie->txq[trans_pcie->cmd_queue].q.write_ptr)) { + iwl_print_hex_error(trans, pkt, 32); + return; + } + + spin_lock_bh(&txq->lock); + + cmd_index = get_cmd_index(&txq->q, index); + cmd = txq->entries[cmd_index].cmd; + meta = &txq->entries[cmd_index].meta; + + iwl_pcie_tfd_unmap(trans, meta, &txq->tfds[index]); + + /* Input error checking is done when commands are added to queue. */ + if (meta->flags & CMD_WANT_SKB) { + struct page *p = rxb_steal_page(rxb); + + meta->source->resp_pkt = pkt; + meta->source->_rx_page_addr = (unsigned long)page_address(p); + meta->source->_rx_page_order = trans_pcie->rx_page_order; + meta->source->handler_status = handler_status; + } + + iwl_pcie_cmdq_reclaim(trans, txq_id, index); + + if (!(meta->flags & CMD_ASYNC)) { + if (!test_bit(STATUS_HCMD_ACTIVE, &trans_pcie->status)) { + IWL_WARN(trans, + "HCMD_ACTIVE already clear for command %s\n", + get_cmd_string(trans_pcie, cmd->hdr.cmd)); + } + clear_bit(STATUS_HCMD_ACTIVE, &trans_pcie->status); + IWL_DEBUG_INFO(trans, "Clearing HCMD_ACTIVE for command %s\n", + get_cmd_string(trans_pcie, cmd->hdr.cmd)); + wake_up(&trans_pcie->wait_command_queue); + } + + meta->flags = 0; + + spin_unlock_bh(&txq->lock); +} + +#define HOST_COMPLETE_TIMEOUT (2 * HZ) + +static int iwl_pcie_send_hcmd_async(struct iwl_trans *trans, + struct iwl_host_cmd *cmd) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int ret; + + /* An asynchronous command can not expect an SKB to be set. */ + if (WARN_ON(cmd->flags & CMD_WANT_SKB)) + return -EINVAL; + + ret = iwl_pcie_enqueue_hcmd(trans, cmd); + if (ret < 0) { + IWL_ERR(trans, + "Error sending %s: enqueue_hcmd failed: %d\n", + get_cmd_string(trans_pcie, cmd->id), ret); + return ret; + } + return 0; +} + +static int iwl_pcie_send_hcmd_sync(struct iwl_trans *trans, + struct iwl_host_cmd *cmd) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + int cmd_idx; + int ret; + + IWL_DEBUG_INFO(trans, "Attempting to send sync command %s\n", + get_cmd_string(trans_pcie, cmd->id)); + + if (WARN_ON(test_and_set_bit(STATUS_HCMD_ACTIVE, + &trans_pcie->status))) { + IWL_ERR(trans, "Command %s: a command is already active!\n", + get_cmd_string(trans_pcie, cmd->id)); + return -EIO; + } + + IWL_DEBUG_INFO(trans, "Setting HCMD_ACTIVE for command %s\n", + get_cmd_string(trans_pcie, cmd->id)); + + cmd_idx = iwl_pcie_enqueue_hcmd(trans, cmd); + if (cmd_idx < 0) { + ret = cmd_idx; + clear_bit(STATUS_HCMD_ACTIVE, &trans_pcie->status); + IWL_ERR(trans, + "Error sending %s: enqueue_hcmd failed: %d\n", + get_cmd_string(trans_pcie, cmd->id), ret); + return ret; + } + + ret = wait_event_timeout(trans_pcie->wait_command_queue, + !test_bit(STATUS_HCMD_ACTIVE, + &trans_pcie->status), + HOST_COMPLETE_TIMEOUT); + if (!ret) { + if (test_bit(STATUS_HCMD_ACTIVE, &trans_pcie->status)) { + struct iwl_txq *txq = + &trans_pcie->txq[trans_pcie->cmd_queue]; + struct iwl_queue *q = &txq->q; + + IWL_ERR(trans, + "Error sending %s: time out after %dms.\n", + get_cmd_string(trans_pcie, cmd->id), + jiffies_to_msecs(HOST_COMPLETE_TIMEOUT)); + + IWL_ERR(trans, + "Current CMD queue read_ptr %d write_ptr %d\n", + q->read_ptr, q->write_ptr); + + clear_bit(STATUS_HCMD_ACTIVE, &trans_pcie->status); + IWL_DEBUG_INFO(trans, + "Clearing HCMD_ACTIVE for command %s\n", + get_cmd_string(trans_pcie, cmd->id)); + ret = -ETIMEDOUT; + goto cancel; + } + } + + if (test_bit(STATUS_FW_ERROR, &trans_pcie->status)) { + IWL_ERR(trans, "FW error in SYNC CMD %s\n", + get_cmd_string(trans_pcie, cmd->id)); + ret = -EIO; + goto cancel; + } + + if (test_bit(STATUS_RFKILL, &trans_pcie->status)) { + IWL_DEBUG_RF_KILL(trans, "RFKILL in SYNC CMD... no rsp\n"); + ret = -ERFKILL; + goto cancel; + } + + if ((cmd->flags & CMD_WANT_SKB) && !cmd->resp_pkt) { + IWL_ERR(trans, "Error: Response NULL in '%s'\n", + get_cmd_string(trans_pcie, cmd->id)); + ret = -EIO; + goto cancel; + } + + return 0; + +cancel: + if (cmd->flags & CMD_WANT_SKB) { + /* + * Cancel the CMD_WANT_SKB flag for the cmd in the + * TX cmd queue. Otherwise in case the cmd comes + * in later, it will possibly set an invalid + * address (cmd->meta.source). + */ + trans_pcie->txq[trans_pcie->cmd_queue]. + entries[cmd_idx].meta.flags &= ~CMD_WANT_SKB; + } + + if (cmd->resp_pkt) { + iwl_free_resp(cmd); + cmd->resp_pkt = NULL; + } + + return ret; +} + +int iwl_trans_pcie_send_hcmd(struct iwl_trans *trans, struct iwl_host_cmd *cmd) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + + if (test_bit(STATUS_FW_ERROR, &trans_pcie->status)) + return -EIO; + + if (test_bit(STATUS_RFKILL, &trans_pcie->status)) { + IWL_DEBUG_RF_KILL(trans, "Dropping CMD 0x%x: RF KILL\n", + cmd->id); + return -ERFKILL; + } + + if (cmd->flags & CMD_ASYNC) + return iwl_pcie_send_hcmd_async(trans, cmd); + + /* We still can fail on RFKILL that can be asserted while we wait */ + return iwl_pcie_send_hcmd_sync(trans, cmd); +} + +int iwl_trans_pcie_tx(struct iwl_trans *trans, struct sk_buff *skb, + struct iwl_device_cmd *dev_cmd, int txq_id) +{ + struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct iwl_tx_cmd *tx_cmd = (struct iwl_tx_cmd *)dev_cmd->payload; + struct iwl_cmd_meta *out_meta; + struct iwl_txq *txq; + struct iwl_queue *q; + dma_addr_t tb0_phys, tb1_phys, scratch_phys; + void *tb1_addr; + u16 len, tb1_len, tb2_len; + u8 wait_write_ptr = 0; + __le16 fc = hdr->frame_control; + u8 hdr_len = ieee80211_hdrlen(fc); + u16 __maybe_unused wifi_seq; + + txq = &trans_pcie->txq[txq_id]; + q = &txq->q; + + if (unlikely(!test_bit(txq_id, trans_pcie->queue_used))) { + WARN_ON_ONCE(1); + return -EINVAL; + } + + spin_lock(&txq->lock); + + /* In AGG mode, the index in the ring must correspond to the WiFi + * sequence number. This is a HW requirements to help the SCD to parse + * the BA. + * Check here that the packets are in the right place on the ring. + */ +#ifdef CPTCFG_IWLWIFI_DEBUG + wifi_seq = IEEE80211_SEQ_TO_SN(le16_to_cpu(hdr->seq_ctrl)); + WARN_ONCE((iwl_read_prph(trans, SCD_AGGR_SEL) & BIT(txq_id)) && + ((wifi_seq & 0xff) != q->write_ptr), + "Q: %d WiFi Seq %d tfdNum %d", + txq_id, wifi_seq, q->write_ptr); +#endif + + /* Set up driver data for this TFD */ + txq->entries[q->write_ptr].skb = skb; + txq->entries[q->write_ptr].cmd = dev_cmd; + + dev_cmd->hdr.cmd = REPLY_TX; + dev_cmd->hdr.sequence = + cpu_to_le16((u16)(QUEUE_TO_SEQ(txq_id) | + INDEX_TO_SEQ(q->write_ptr))); + + tb0_phys = iwl_pcie_get_scratchbuf_dma(txq, q->write_ptr); + scratch_phys = tb0_phys + sizeof(struct iwl_cmd_header) + + offsetof(struct iwl_tx_cmd, scratch); + + tx_cmd->dram_lsb_ptr = cpu_to_le32(scratch_phys); + tx_cmd->dram_msb_ptr = iwl_get_dma_hi_addr(scratch_phys); + + /* Set up first empty entry in queue's array of Tx/cmd buffers */ + out_meta = &txq->entries[q->write_ptr].meta; + + /* + * The second TB (tb1) points to the remainder of the TX command + * and the 802.11 header - dword aligned size + * (This calculation modifies the TX command, so do it before the + * setup of the first TB) + */ + len = sizeof(struct iwl_tx_cmd) + sizeof(struct iwl_cmd_header) + + hdr_len - IWL_HCMD_SCRATCHBUF_SIZE; + tb1_len = (len + 3) & ~3; + + /* Tell NIC about any 2-byte padding after MAC header */ + if (tb1_len != len) + tx_cmd->tx_flags |= TX_CMD_FLG_MH_PAD_MSK; + + /* The first TB points to the scratchbuf data - min_copy bytes */ + memcpy(&txq->scratchbufs[q->write_ptr], &dev_cmd->hdr, + IWL_HCMD_SCRATCHBUF_SIZE); + iwl_pcie_txq_build_tfd(trans, txq, tb0_phys, + IWL_HCMD_SCRATCHBUF_SIZE, 1); + + /* there must be data left over for TB1 or this code must be changed */ + BUILD_BUG_ON(sizeof(struct iwl_tx_cmd) < IWL_HCMD_SCRATCHBUF_SIZE); + + /* map the data for TB1 */ + tb1_addr = ((u8 *)&dev_cmd->hdr) + IWL_HCMD_SCRATCHBUF_SIZE; + tb1_phys = dma_map_single(trans->dev, tb1_addr, tb1_len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, tb1_phys))) + goto out_err; + iwl_pcie_txq_build_tfd(trans, txq, tb1_phys, tb1_len, 0); + + /* + * Set up TFD's third entry to point directly to remainder + * of skb, if any (802.11 null frames have no payload). + */ + tb2_len = skb->len - hdr_len; + if (tb2_len > 0) { + dma_addr_t tb2_phys = dma_map_single(trans->dev, + skb->data + hdr_len, + tb2_len, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(trans->dev, tb2_phys))) { + iwl_pcie_tfd_unmap(trans, out_meta, + &txq->tfds[q->write_ptr]); + goto out_err; + } + iwl_pcie_txq_build_tfd(trans, txq, tb2_phys, tb2_len, 0); + } + + /* Set up entry for this TFD in Tx byte-count array */ + iwl_pcie_txq_update_byte_cnt_tbl(trans, txq, le16_to_cpu(tx_cmd->len)); + + trace_iwlwifi_dev_tx(trans->dev, skb, + &txq->tfds[txq->q.write_ptr], + sizeof(struct iwl_tfd), + &dev_cmd->hdr, IWL_HCMD_SCRATCHBUF_SIZE + tb1_len, + skb->data + hdr_len, tb2_len); + trace_iwlwifi_dev_tx_data(trans->dev, skb, + skb->data + hdr_len, tb2_len); + + if (!ieee80211_has_morefrags(fc)) { + txq->need_update = 1; + } else { + wait_write_ptr = 1; + txq->need_update = 0; + } + + /* start timer if queue currently empty */ + if (txq->need_update && q->read_ptr == q->write_ptr && + trans_pcie->wd_timeout) + mod_timer(&txq->stuck_timer, jiffies + trans_pcie->wd_timeout); + + /* Tell device the write index *just past* this latest filled TFD */ + q->write_ptr = iwl_queue_inc_wrap(q->write_ptr, q->n_bd); + iwl_pcie_txq_inc_wr_ptr(trans, txq); + + /* + * At this point the frame is "transmitted" successfully + * and we will get a TX status notification eventually, + * regardless of the value of ret. "ret" only indicates + * whether or not we should update the write pointer. + */ + if (iwl_queue_space(q) < q->high_mark) { + if (wait_write_ptr) { + txq->need_update = 1; + iwl_pcie_txq_inc_wr_ptr(trans, txq); + } else { + iwl_stop_queue(trans, txq); + } + } + spin_unlock(&txq->lock); + return 0; +out_err: + spin_unlock(&txq->lock); + return -1; +} diff --git a/drivers/net/wireless/libertas/Kconfig b/drivers/net/wireless/libertas/Kconfig new file mode 100644 index 0000000..8f89d9f --- /dev/null +++ b/drivers/net/wireless/libertas/Kconfig @@ -0,0 +1,52 @@ +config LIBERTAS + depends on !BACKPORT_KERNEL_2_6_27 + tristate "Marvell 8xxx Libertas WLAN driver support" + depends on m + depends on CFG80211 + depends on WIRELESS_EXT + depends on WEXT_SPY + select LIB80211 + select BACKPORT_FW_LOADER + ---help--- + A library for Marvell Libertas 8xxx devices. + +config LIBERTAS_USB + tristate "Marvell Libertas 8388 USB 802.11b/g cards" + depends on m + depends on LIBERTAS && USB + ---help--- + A driver for Marvell Libertas 8388 USB devices. + +config LIBERTAS_CS + tristate "Marvell Libertas 8385 CompactFlash 802.11b/g cards" + depends on m + depends on LIBERTAS && PCMCIA + ---help--- + A driver for Marvell Libertas 8385 CompactFlash devices. + +config LIBERTAS_SDIO + depends on !BACKPORT_KERNEL_2_6_32 + tristate "Marvell Libertas 8385/8686/8688 SDIO 802.11b/g cards" + depends on m + depends on LIBERTAS && MMC + ---help--- + A driver for Marvell Libertas 8385/8686/8688 SDIO devices. + +config LIBERTAS_SPI + tristate "Marvell Libertas 8686 SPI 802.11b/g cards" + depends on m + depends on LIBERTAS && SPI + ---help--- + A driver for Marvell Libertas 8686 SPI devices. + +config LIBERTAS_DEBUG + bool "Enable full debugging output in the Libertas module." + depends on LIBERTAS + ---help--- + Debugging support. + +config LIBERTAS_MESH + bool "Enable mesh support" + depends on LIBERTAS + help + This enables Libertas' MESH support, used by e.g. the OLPC people. diff --git a/drivers/net/wireless/libertas/LICENSE b/drivers/net/wireless/libertas/LICENSE new file mode 100644 index 0000000..8862742 --- /dev/null +++ b/drivers/net/wireless/libertas/LICENSE @@ -0,0 +1,16 @@ + Copyright (c) 2003-2006, Marvell International Ltd. + All Rights Reserved + + This program is free software; you can redistribute it and/or modify it + under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + diff --git a/drivers/net/wireless/libertas/Makefile b/drivers/net/wireless/libertas/Makefile new file mode 100644 index 0000000..28b3bea --- /dev/null +++ b/drivers/net/wireless/libertas/Makefile @@ -0,0 +1,21 @@ +libertas-y += cfg.o +libertas-y += cmd.o +libertas-y += cmdresp.o +libertas-y += debugfs.o +libertas-y += ethtool.o +libertas-y += main.o +libertas-y += rx.o +libertas-y += tx.o +libertas-y += firmware.o +libertas-$(CPTCFG_LIBERTAS_MESH) += mesh.o + +usb8xxx-objs += if_usb.o +libertas_cs-objs += if_cs.o +libertas_sdio-objs += if_sdio.o +libertas_spi-objs += if_spi.o + +obj-$(CPTCFG_LIBERTAS) += libertas.o +obj-$(CPTCFG_LIBERTAS_USB) += usb8xxx.o +obj-$(CPTCFG_LIBERTAS_CS) += libertas_cs.o +obj-$(CPTCFG_LIBERTAS_SDIO) += libertas_sdio.o +obj-$(CPTCFG_LIBERTAS_SPI) += libertas_spi.o diff --git a/drivers/net/wireless/libertas/README b/drivers/net/wireless/libertas/README new file mode 100644 index 0000000..91f2ca9 --- /dev/null +++ b/drivers/net/wireless/libertas/README @@ -0,0 +1,240 @@ +================================================================================ + README for Libertas + + (c) Copyright © 2003-2006, Marvell International Ltd. + All Rights Reserved + + This software file (the "File") is distributed by Marvell International + Ltd. under the terms of the GNU General Public License Version 2, June 1991 + (the "License"). You may use, redistribute and/or modify this File in + accordance with the terms and conditions of the License, a copy of which + is available along with the File in the license.txt file or by writing to + the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 or on the worldwide web at http://www.gnu.org/licenses/gpl.txt. + + THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + ARE EXPRESSLY DISCLAIMED. The License provides additional details about + this warranty disclaimer. +================================================================================ + +===================== +DRIVER LOADING +===================== + + o. Copy the firmware image (e.g. usb8388.bin) to /lib/firmware/ + + o. Load driver by using the following command: + + insmod usb8388.ko [fw_name=usb8388.bin] + +========================= +ETHTOOL +========================= + + +Use the -i option to retrieve version information from the driver. + +# ethtool -i eth0 +driver: libertas +version: COMM-USB8388-318.p4 +firmware-version: 5.110.7 +bus-info: + +Use the -e option to read the EEPROM contents of the card. + + Usage: + ethtool -e ethX [raw on|off] [offset N] [length N] + + -e retrieves and prints an EEPROM dump for the specified ethernet + device. When raw is enabled, then it dumps the raw EEPROM data + to stdout. The length and offset parameters allow dumping cer- + tain portions of the EEPROM. Default is to dump the entire EEP- + ROM. + +# ethtool -e eth0 offset 0 length 16 +Offset Values +------ ------ +0x0000 38 33 30 58 00 00 34 f4 00 00 10 00 00 c4 17 00 + +======================== +DEBUGFS COMMANDS +======================== + +those commands are used via debugfs interface + +=========== +rdmac +rdbbp +rdrf + These commands are used to read the MAC, BBP and RF registers from the + card. These commands take one parameter that specifies the offset + location that is to be read. This parameter must be specified in + hexadecimal (its possible to precede preceding the number with a "0x"). + + Path: /sys/kernel/debug/libertas_wireless/ethX/registers/ + + Usage: + echo "0xa123" > rdmac ; cat rdmac + echo "0xa123" > rdbbp ; cat rdbbp + echo "0xa123" > rdrf ; cat rdrf +wrmac +wrbbp +wrrf + These commands are used to write the MAC, BBP and RF registers in the + card. These commands take two parameters that specify the offset + location and the value that is to be written. This parameters must + be specified in hexadecimal (its possible to precede the number + with a "0x"). + + Usage: + echo "0xa123 0xaa" > wrmac + echo "0xa123 0xaa" > wrbbp + echo "0xa123 0xaa" > wrrf + +sleepparams + This command is used to set the sleepclock configurations + + Path: /sys/kernel/debug/libertas_wireless/ethX/ + + Usage: + cat sleepparams: reads the current sleepclock configuration + + echo "p1 p2 p3 p4 p5 p6" > sleepparams: writes the sleepclock configuration. + + where: + p1 is Sleep clock error in ppm (0-65535) + p2 is Wakeup offset in usec (0-65535) + p3 is Clock stabilization time in usec (0-65535) + p4 is Control periodic calibration (0-2) + p5 is Control the use of external sleep clock (0-2) + p6 is reserved for debug (0-65535) + +subscribed_events + + The subscribed_events directory contains the interface for the + subscribed events API. + + Path: /sys/kernel/debug/libertas_wireless/ethX/subscribed_events/ + + Each event is represented by a filename. Each filename consists of the + following three fields: + Value Frequency Subscribed + + To read the current values for a given event, do: + cat event + To set the current values, do: + echo "60 2 1" > event + + Frequency field specifies the reporting frequency for this event. + If it is set to 0, then the event is reported only once, and then + automatically unsubscribed. If it is set to 1, then the event is + reported every time it occurs. If it is set to N, then the event is + reported every Nth time it occurs. + + beacon_missed + Value field specifies the number of consecutive missing beacons which + triggers the LINK_LOSS event. This event is generated only once after + which the firmware resets its state. At initialization, the LINK_LOSS + event is subscribed by default. The default value of MissedBeacons is + 60. + + failure_count + Value field specifies the consecutive failure count threshold which + triggers the generation of the MAX_FAIL event. Once this event is + generated, the consecutive failure count is reset to 0. + At initialization, the MAX_FAIL event is NOT subscribed by + default. + + high_rssi + This event is generated when the average received RSSI in beacons goes + above a threshold, specified by Value. + + low_rssi + This event is generated when the average received RSSI in beacons goes + below a threshold, specified by Value. + + high_snr + This event is generated when the average received SNR in beacons goes + above a threshold, specified by Value. + + low_snr + This event is generated when the average received SNR in beacons goes + below a threshold, specified by Value. + +extscan + This command is used to do a specific scan. + + Path: /sys/kernel/debug/libertas_wireless/ethX/ + + Usage: echo "SSID" > extscan + + Example: + echo "LINKSYS-AP" > extscan + + To see the results of use getscantable command. + +getscantable + + Display the current contents of the driver scan table (ie. get the + scan results). + + Path: /sys/kernel/debug/libertas_wireless/ethX/ + + Usage: + cat getscantable + +setuserscan + Initiate a customized scan and retrieve the results + + + Path: /sys/kernel/debug/libertas_wireless/ethX/ + + Usage: + echo "[ARGS]" > setuserscan + + where [ARGS]: + + bssid=xx:xx:xx:xx:xx:xx specify a BSSID filter for the scan + ssid="[SSID]" specify a SSID filter for the scan + keep=[0 or 1] keep the previous scan results (1), discard (0) + dur=[scan time] time to scan for each channel in milliseconds + type=[1,2,3] BSS type: 1 (Infra), 2(Adhoc), 3(Any) + + Any combination of the above arguments can be supplied on the command + line. If dur tokens are absent, the driver default setting will be used. + The bssid and ssid fields, if blank, will produce an unfiltered scan. + The type field will default to 3 (Any) and the keep field will default + to 0 (Discard). + + Examples: + 1) Perform a passive scan on all channels for 20 ms per channel: + echo "dur=20" > setuserscan + + 2) Perform an active scan for a specific SSID: + echo "ssid="TestAP"" > setuserscan + + 3) Scan all available channels (B/G, A bands) for a specific BSSID, keep + the current scan table intact, update existing or append new scan data: + echo "bssid=00:50:43:20:12:82 keep=1" > setuserscan + + 4) Scan for all infrastructure networks. + Keep the previous scan table intact. Update any duplicate BSSID/SSID + matches with the new scan data: + echo "type=1 keep=1" > setuserscan + + All entries in the scan table (not just the new scan data when keep=1) + will be displayed upon completion by use of the getscantable ioctl. + +hostsleep + This command is used to enable/disable host sleep. + Note: Host sleep parameters should be configured using + "ethtool -s ethX wol X" command before enabling host sleep. + + Path: /sys/kernel/debug/libertas_wireless/ethX/ + + Usage: + cat hostsleep: reads the current hostsleep state + echo "1" > hostsleep : enable host sleep. + echo "0" > hostsleep : disable host sleep + diff --git a/drivers/net/wireless/libertas/cfg.c b/drivers/net/wireless/libertas/cfg.c new file mode 100644 index 0000000..116f4ab --- /dev/null +++ b/drivers/net/wireless/libertas/cfg.c @@ -0,0 +1,2223 @@ +/* + * Implement cfg80211 ("iw") support. + * + * Copyright (C) 2009 M&N Solutions GmbH, 61191 Rosbach, Germany + * Holger Schurig + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +#include "decl.h" +#include "cfg.h" +#include "cmd.h" +#include "mesh.h" + + +#define CHAN2G(_channel, _freq, _flags) { \ + .band = IEEE80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_channel), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +static struct ieee80211_channel lbs_2ghz_channels[] = { + CHAN2G(1, 2412, 0), + CHAN2G(2, 2417, 0), + CHAN2G(3, 2422, 0), + CHAN2G(4, 2427, 0), + CHAN2G(5, 2432, 0), + CHAN2G(6, 2437, 0), + CHAN2G(7, 2442, 0), + CHAN2G(8, 2447, 0), + CHAN2G(9, 2452, 0), + CHAN2G(10, 2457, 0), + CHAN2G(11, 2462, 0), + CHAN2G(12, 2467, 0), + CHAN2G(13, 2472, 0), + CHAN2G(14, 2484, 0), +}; + +#define RATETAB_ENT(_rate, _hw_value, _flags) { \ + .bitrate = (_rate), \ + .hw_value = (_hw_value), \ + .flags = (_flags), \ +} + + +/* Table 6 in section 3.2.1.1 */ +static struct ieee80211_rate lbs_rates[] = { + RATETAB_ENT(10, 0, 0), + RATETAB_ENT(20, 1, 0), + RATETAB_ENT(55, 2, 0), + RATETAB_ENT(110, 3, 0), + RATETAB_ENT(60, 9, 0), + RATETAB_ENT(90, 6, 0), + RATETAB_ENT(120, 7, 0), + RATETAB_ENT(180, 8, 0), + RATETAB_ENT(240, 9, 0), + RATETAB_ENT(360, 10, 0), + RATETAB_ENT(480, 11, 0), + RATETAB_ENT(540, 12, 0), +}; + +static struct ieee80211_supported_band lbs_band_2ghz = { + .channels = lbs_2ghz_channels, + .n_channels = ARRAY_SIZE(lbs_2ghz_channels), + .bitrates = lbs_rates, + .n_bitrates = ARRAY_SIZE(lbs_rates), +}; + + +static const u32 cipher_suites[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, +}; + +/* Time to stay on the channel */ +#define LBS_DWELL_PASSIVE 100 +#define LBS_DWELL_ACTIVE 40 + + +/*************************************************************************** + * Misc utility functions + * + * TLVs are Marvell specific. They are very similar to IEs, they have the + * same structure: type, length, data*. The only difference: for IEs, the + * type and length are u8, but for TLVs they're __le16. + */ + +/* + * Convert NL80211's auth_type to the one from Libertas, see chapter 5.9.1 + * in the firmware spec + */ +static int lbs_auth_to_authtype(enum nl80211_auth_type auth_type) +{ + int ret = -ENOTSUPP; + + switch (auth_type) { + case NL80211_AUTHTYPE_OPEN_SYSTEM: + case NL80211_AUTHTYPE_SHARED_KEY: + ret = auth_type; + break; + case NL80211_AUTHTYPE_AUTOMATIC: + ret = NL80211_AUTHTYPE_OPEN_SYSTEM; + break; + case NL80211_AUTHTYPE_NETWORK_EAP: + ret = 0x80; + break; + default: + /* silence compiler */ + break; + } + return ret; +} + + +/* + * Various firmware commands need the list of supported rates, but with + * the hight-bit set for basic rates + */ +static int lbs_add_rates(u8 *rates) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(lbs_rates); i++) { + u8 rate = lbs_rates[i].bitrate / 5; + if (rate == 0x02 || rate == 0x04 || + rate == 0x0b || rate == 0x16) + rate |= 0x80; + rates[i] = rate; + } + return ARRAY_SIZE(lbs_rates); +} + + +/*************************************************************************** + * TLV utility functions + * + * TLVs are Marvell specific. They are very similar to IEs, they have the + * same structure: type, length, data*. The only difference: for IEs, the + * type and length are u8, but for TLVs they're __le16. + */ + + +/* + * Add ssid TLV + */ +#define LBS_MAX_SSID_TLV_SIZE \ + (sizeof(struct mrvl_ie_header) \ + + IEEE80211_MAX_SSID_LEN) + +static int lbs_add_ssid_tlv(u8 *tlv, const u8 *ssid, int ssid_len) +{ + struct mrvl_ie_ssid_param_set *ssid_tlv = (void *)tlv; + + /* + * TLV-ID SSID 00 00 + * length 06 00 + * ssid 4d 4e 54 45 53 54 + */ + ssid_tlv->header.type = cpu_to_le16(TLV_TYPE_SSID); + ssid_tlv->header.len = cpu_to_le16(ssid_len); + memcpy(ssid_tlv->ssid, ssid, ssid_len); + return sizeof(ssid_tlv->header) + ssid_len; +} + + +/* + * Add channel list TLV (section 8.4.2) + * + * Actual channel data comes from priv->wdev->wiphy->channels. + */ +#define LBS_MAX_CHANNEL_LIST_TLV_SIZE \ + (sizeof(struct mrvl_ie_header) \ + + (LBS_SCAN_BEFORE_NAP * sizeof(struct chanscanparamset))) + +static int lbs_add_channel_list_tlv(struct lbs_private *priv, u8 *tlv, + int last_channel, int active_scan) +{ + int chanscanparamsize = sizeof(struct chanscanparamset) * + (last_channel - priv->scan_channel); + + struct mrvl_ie_header *header = (void *) tlv; + + /* + * TLV-ID CHANLIST 01 01 + * length 0e 00 + * channel 00 01 00 00 00 64 00 + * radio type 00 + * channel 01 + * scan type 00 + * min scan time 00 00 + * max scan time 64 00 + * channel 2 00 02 00 00 00 64 00 + * + */ + + header->type = cpu_to_le16(TLV_TYPE_CHANLIST); + header->len = cpu_to_le16(chanscanparamsize); + tlv += sizeof(struct mrvl_ie_header); + + /* lbs_deb_scan("scan: channels %d to %d\n", priv->scan_channel, + last_channel); */ + memset(tlv, 0, chanscanparamsize); + + while (priv->scan_channel < last_channel) { + struct chanscanparamset *param = (void *) tlv; + + param->radiotype = CMD_SCAN_RADIO_TYPE_BG; + param->channumber = + priv->scan_req->channels[priv->scan_channel]->hw_value; + if (active_scan) { + param->maxscantime = cpu_to_le16(LBS_DWELL_ACTIVE); + } else { + param->chanscanmode.passivescan = 1; + param->maxscantime = cpu_to_le16(LBS_DWELL_PASSIVE); + } + tlv += sizeof(struct chanscanparamset); + priv->scan_channel++; + } + return sizeof(struct mrvl_ie_header) + chanscanparamsize; +} + + +/* + * Add rates TLV + * + * The rates are in lbs_bg_rates[], but for the 802.11b + * rates the high bit is set. We add this TLV only because + * there's a firmware which otherwise doesn't report all + * APs in range. + */ +#define LBS_MAX_RATES_TLV_SIZE \ + (sizeof(struct mrvl_ie_header) \ + + (ARRAY_SIZE(lbs_rates))) + +/* Adds a TLV with all rates the hardware supports */ +static int lbs_add_supported_rates_tlv(u8 *tlv) +{ + size_t i; + struct mrvl_ie_rates_param_set *rate_tlv = (void *)tlv; + + /* + * TLV-ID RATES 01 00 + * length 0e 00 + * rates 82 84 8b 96 0c 12 18 24 30 48 60 6c + */ + rate_tlv->header.type = cpu_to_le16(TLV_TYPE_RATES); + tlv += sizeof(rate_tlv->header); + i = lbs_add_rates(tlv); + tlv += i; + rate_tlv->header.len = cpu_to_le16(i); + return sizeof(rate_tlv->header) + i; +} + +/* Add common rates from a TLV and return the new end of the TLV */ +static u8 * +add_ie_rates(u8 *tlv, const u8 *ie, int *nrates) +{ + int hw, ap, ap_max = ie[1]; + u8 hw_rate; + + /* Advance past IE header */ + ie += 2; + + lbs_deb_hex(LBS_DEB_ASSOC, "AP IE Rates", (u8 *) ie, ap_max); + + for (hw = 0; hw < ARRAY_SIZE(lbs_rates); hw++) { + hw_rate = lbs_rates[hw].bitrate / 5; + for (ap = 0; ap < ap_max; ap++) { + if (hw_rate == (ie[ap] & 0x7f)) { + *tlv++ = ie[ap]; + *nrates = *nrates + 1; + } + } + } + return tlv; +} + +/* + * Adds a TLV with all rates the hardware *and* BSS supports. + */ +static int lbs_add_common_rates_tlv(u8 *tlv, struct cfg80211_bss *bss) +{ + struct mrvl_ie_rates_param_set *rate_tlv = (void *)tlv; + const u8 *rates_eid, *ext_rates_eid; + int n = 0; + + rcu_read_lock(); + rates_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SUPP_RATES); + ext_rates_eid = ieee80211_bss_get_ie(bss, WLAN_EID_EXT_SUPP_RATES); + + /* + * 01 00 TLV_TYPE_RATES + * 04 00 len + * 82 84 8b 96 rates + */ + rate_tlv->header.type = cpu_to_le16(TLV_TYPE_RATES); + tlv += sizeof(rate_tlv->header); + + /* Add basic rates */ + if (rates_eid) { + tlv = add_ie_rates(tlv, rates_eid, &n); + + /* Add extended rates, if any */ + if (ext_rates_eid) + tlv = add_ie_rates(tlv, ext_rates_eid, &n); + } else { + lbs_deb_assoc("assoc: bss had no basic rate IE\n"); + /* Fallback: add basic 802.11b rates */ + *tlv++ = 0x82; + *tlv++ = 0x84; + *tlv++ = 0x8b; + *tlv++ = 0x96; + n = 4; + } + rcu_read_unlock(); + + rate_tlv->header.len = cpu_to_le16(n); + return sizeof(rate_tlv->header) + n; +} + + +/* + * Add auth type TLV. + * + * This is only needed for newer firmware (V9 and up). + */ +#define LBS_MAX_AUTH_TYPE_TLV_SIZE \ + sizeof(struct mrvl_ie_auth_type) + +static int lbs_add_auth_type_tlv(u8 *tlv, enum nl80211_auth_type auth_type) +{ + struct mrvl_ie_auth_type *auth = (void *) tlv; + + /* + * 1f 01 TLV_TYPE_AUTH_TYPE + * 01 00 len + * 01 auth type + */ + auth->header.type = cpu_to_le16(TLV_TYPE_AUTH_TYPE); + auth->header.len = cpu_to_le16(sizeof(*auth)-sizeof(auth->header)); + auth->auth = cpu_to_le16(lbs_auth_to_authtype(auth_type)); + return sizeof(*auth); +} + + +/* + * Add channel (phy ds) TLV + */ +#define LBS_MAX_CHANNEL_TLV_SIZE \ + sizeof(struct mrvl_ie_header) + +static int lbs_add_channel_tlv(u8 *tlv, u8 channel) +{ + struct mrvl_ie_ds_param_set *ds = (void *) tlv; + + /* + * 03 00 TLV_TYPE_PHY_DS + * 01 00 len + * 06 channel + */ + ds->header.type = cpu_to_le16(TLV_TYPE_PHY_DS); + ds->header.len = cpu_to_le16(sizeof(*ds)-sizeof(ds->header)); + ds->channel = channel; + return sizeof(*ds); +} + + +/* + * Add (empty) CF param TLV of the form: + */ +#define LBS_MAX_CF_PARAM_TLV_SIZE \ + sizeof(struct mrvl_ie_header) + +static int lbs_add_cf_param_tlv(u8 *tlv) +{ + struct mrvl_ie_cf_param_set *cf = (void *)tlv; + + /* + * 04 00 TLV_TYPE_CF + * 06 00 len + * 00 cfpcnt + * 00 cfpperiod + * 00 00 cfpmaxduration + * 00 00 cfpdurationremaining + */ + cf->header.type = cpu_to_le16(TLV_TYPE_CF); + cf->header.len = cpu_to_le16(sizeof(*cf)-sizeof(cf->header)); + return sizeof(*cf); +} + +/* + * Add WPA TLV + */ +#define LBS_MAX_WPA_TLV_SIZE \ + (sizeof(struct mrvl_ie_header) \ + + 128 /* TODO: I guessed the size */) + +static int lbs_add_wpa_tlv(u8 *tlv, const u8 *ie, u8 ie_len) +{ + size_t tlv_len; + + /* + * We need just convert an IE to an TLV. IEs use u8 for the header, + * u8 type + * u8 len + * u8[] data + * but TLVs use __le16 instead: + * __le16 type + * __le16 len + * u8[] data + */ + *tlv++ = *ie++; + *tlv++ = 0; + tlv_len = *tlv++ = *ie++; + *tlv++ = 0; + while (tlv_len--) + *tlv++ = *ie++; + /* the TLV is two bytes larger than the IE */ + return ie_len + 2; +} + +/* + * Set Channel + */ + +static int lbs_cfg_set_monitor_channel(struct wiphy *wiphy, + struct cfg80211_chan_def *chandef) +{ + struct lbs_private *priv = wiphy_priv(wiphy); + int ret = -ENOTSUPP; + + lbs_deb_enter_args(LBS_DEB_CFG80211, "freq %d, type %d", + chandef->chan->center_freq, + cfg80211_get_chandef_type(chandef)); + + if (cfg80211_get_chandef_type(chandef) != NL80211_CHAN_NO_HT) + goto out; + + ret = lbs_set_channel(priv, chandef->chan->hw_value); + + out: + lbs_deb_leave_args(LBS_DEB_CFG80211, "ret %d", ret); + return ret; +} + +static int lbs_cfg_set_mesh_channel(struct wiphy *wiphy, + struct net_device *netdev, + struct ieee80211_channel *channel) +{ + struct lbs_private *priv = wiphy_priv(wiphy); + int ret = -ENOTSUPP; + + lbs_deb_enter_args(LBS_DEB_CFG80211, "iface %s freq %d", + netdev_name(netdev), channel->center_freq); + + if (netdev != priv->mesh_dev) + goto out; + + ret = lbs_mesh_set_channel(priv, channel->hw_value); + + out: + lbs_deb_leave_args(LBS_DEB_CFG80211, "ret %d", ret); + return ret; +} + + + +/* + * Scanning + */ + +/* + * When scanning, the firmware doesn't send a nul packet with the power-safe + * bit to the AP. So we cannot stay away from our current channel too long, + * otherwise we loose data. So take a "nap" while scanning every other + * while. + */ +#define LBS_SCAN_BEFORE_NAP 4 + + +/* + * When the firmware reports back a scan-result, it gives us an "u8 rssi", + * which isn't really an RSSI, as it becomes larger when moving away from + * the AP. Anyway, we need to convert that into mBm. + */ +#define LBS_SCAN_RSSI_TO_MBM(rssi) \ + ((-(int)rssi + 3)*100) + +static int lbs_ret_scan(struct lbs_private *priv, unsigned long dummy, + struct cmd_header *resp) +{ + struct cfg80211_bss *bss; + struct cmd_ds_802_11_scan_rsp *scanresp = (void *)resp; + int bsssize; + const u8 *pos; + const u8 *tsfdesc; + int tsfsize; + int i; + int ret = -EILSEQ; + + lbs_deb_enter(LBS_DEB_CFG80211); + + bsssize = get_unaligned_le16(&scanresp->bssdescriptsize); + + lbs_deb_scan("scan response: %d BSSs (%d bytes); resp size %d bytes\n", + scanresp->nr_sets, bsssize, le16_to_cpu(resp->size)); + + if (scanresp->nr_sets == 0) { + ret = 0; + goto done; + } + + /* + * The general layout of the scan response is described in chapter + * 5.7.1. Basically we have a common part, then any number of BSS + * descriptor sections. Finally we have section with the same number + * of TSFs. + * + * cmd_ds_802_11_scan_rsp + * cmd_header + * pos_size + * nr_sets + * bssdesc 1 + * bssid + * rssi + * timestamp + * intvl + * capa + * IEs + * bssdesc 2 + * bssdesc n + * MrvlIEtypes_TsfFimestamp_t + * TSF for BSS 1 + * TSF for BSS 2 + * TSF for BSS n + */ + + pos = scanresp->bssdesc_and_tlvbuffer; + + lbs_deb_hex(LBS_DEB_SCAN, "SCAN_RSP", scanresp->bssdesc_and_tlvbuffer, + scanresp->bssdescriptsize); + + tsfdesc = pos + bsssize; + tsfsize = 4 + 8 * scanresp->nr_sets; + lbs_deb_hex(LBS_DEB_SCAN, "SCAN_TSF", (u8 *) tsfdesc, tsfsize); + + /* Validity check: we expect a Marvell-Local TLV */ + i = get_unaligned_le16(tsfdesc); + tsfdesc += 2; + if (i != TLV_TYPE_TSFTIMESTAMP) { + lbs_deb_scan("scan response: invalid TSF Timestamp %d\n", i); + goto done; + } + + /* + * Validity check: the TLV holds TSF values with 8 bytes each, so + * the size in the TLV must match the nr_sets value + */ + i = get_unaligned_le16(tsfdesc); + tsfdesc += 2; + if (i / 8 != scanresp->nr_sets) { + lbs_deb_scan("scan response: invalid number of TSF timestamp " + "sets (expected %d got %d)\n", scanresp->nr_sets, + i / 8); + goto done; + } + + for (i = 0; i < scanresp->nr_sets; i++) { + const u8 *bssid; + const u8 *ie; + int left; + int ielen; + int rssi; + u16 intvl; + u16 capa; + int chan_no = -1; + const u8 *ssid = NULL; + u8 ssid_len = 0; + DECLARE_SSID_BUF(ssid_buf); + + int len = get_unaligned_le16(pos); + pos += 2; + + /* BSSID */ + bssid = pos; + pos += ETH_ALEN; + /* RSSI */ + rssi = *pos++; + /* Packet time stamp */ + pos += 8; + /* Beacon interval */ + intvl = get_unaligned_le16(pos); + pos += 2; + /* Capabilities */ + capa = get_unaligned_le16(pos); + pos += 2; + + /* To find out the channel, we must parse the IEs */ + ie = pos; + /* + * 6+1+8+2+2: size of BSSID, RSSI, time stamp, beacon + * interval, capabilities + */ + ielen = left = len - (6 + 1 + 8 + 2 + 2); + while (left >= 2) { + u8 id, elen; + id = *pos++; + elen = *pos++; + left -= 2; + if (elen > left || elen == 0) { + lbs_deb_scan("scan response: invalid IE fmt\n"); + goto done; + } + + if (id == WLAN_EID_DS_PARAMS) + chan_no = *pos; + if (id == WLAN_EID_SSID) { + ssid = pos; + ssid_len = elen; + } + left -= elen; + pos += elen; + } + + /* No channel, no luck */ + if (chan_no != -1) { + struct wiphy *wiphy = priv->wdev->wiphy; + int freq = ieee80211_channel_to_frequency(chan_no, + IEEE80211_BAND_2GHZ); + struct ieee80211_channel *channel = + ieee80211_get_channel(wiphy, freq); + + lbs_deb_scan("scan: %pM, capa %04x, chan %2d, %s, " + "%d dBm\n", + bssid, capa, chan_no, + print_ssid(ssid_buf, ssid, ssid_len), + LBS_SCAN_RSSI_TO_MBM(rssi)/100); + + if (channel && + !(channel->flags & IEEE80211_CHAN_DISABLED)) { + bss = cfg80211_inform_bss(wiphy, channel, + bssid, get_unaligned_le64(tsfdesc), + capa, intvl, ie, ielen, + LBS_SCAN_RSSI_TO_MBM(rssi), + GFP_KERNEL); + cfg80211_put_bss(wiphy, bss); + } + } else + lbs_deb_scan("scan response: missing BSS channel IE\n"); + + tsfdesc += 8; + } + ret = 0; + + done: + lbs_deb_leave_args(LBS_DEB_SCAN, "ret %d", ret); + return ret; +} + + +/* + * Our scan command contains a TLV, consting of a SSID TLV, a channel list + * TLV and a rates TLV. Determine the maximum size of them: + */ +#define LBS_SCAN_MAX_CMD_SIZE \ + (sizeof(struct cmd_ds_802_11_scan) \ + + LBS_MAX_SSID_TLV_SIZE \ + + LBS_MAX_CHANNEL_LIST_TLV_SIZE \ + + LBS_MAX_RATES_TLV_SIZE) + +/* + * Assumes priv->scan_req is initialized and valid + * Assumes priv->scan_channel is initialized + */ +static void lbs_scan_worker(struct work_struct *work) +{ + struct lbs_private *priv = + container_of(work, struct lbs_private, scan_work.work); + struct cmd_ds_802_11_scan *scan_cmd; + u8 *tlv; /* pointer into our current, growing TLV storage area */ + int last_channel; + int running, carrier; + + lbs_deb_enter(LBS_DEB_SCAN); + + scan_cmd = kzalloc(LBS_SCAN_MAX_CMD_SIZE, GFP_KERNEL); + if (scan_cmd == NULL) + goto out_no_scan_cmd; + + /* prepare fixed part of scan command */ + scan_cmd->bsstype = CMD_BSS_TYPE_ANY; + + /* stop network while we're away from our main channel */ + running = !netif_queue_stopped(priv->dev); + carrier = netif_carrier_ok(priv->dev); + if (running) + netif_stop_queue(priv->dev); + if (carrier) + netif_carrier_off(priv->dev); + + /* prepare fixed part of scan command */ + tlv = scan_cmd->tlvbuffer; + + /* add SSID TLV */ + if (priv->scan_req->n_ssids && priv->scan_req->ssids[0].ssid_len > 0) + tlv += lbs_add_ssid_tlv(tlv, + priv->scan_req->ssids[0].ssid, + priv->scan_req->ssids[0].ssid_len); + + /* add channel TLVs */ + last_channel = priv->scan_channel + LBS_SCAN_BEFORE_NAP; + if (last_channel > priv->scan_req->n_channels) + last_channel = priv->scan_req->n_channels; + tlv += lbs_add_channel_list_tlv(priv, tlv, last_channel, + priv->scan_req->n_ssids); + + /* add rates TLV */ + tlv += lbs_add_supported_rates_tlv(tlv); + + if (priv->scan_channel < priv->scan_req->n_channels) { + cancel_delayed_work(&priv->scan_work); + if (netif_running(priv->dev)) + queue_delayed_work(priv->work_thread, &priv->scan_work, + msecs_to_jiffies(300)); + } + + /* This is the final data we are about to send */ + scan_cmd->hdr.size = cpu_to_le16(tlv - (u8 *)scan_cmd); + lbs_deb_hex(LBS_DEB_SCAN, "SCAN_CMD", (void *)scan_cmd, + sizeof(*scan_cmd)); + lbs_deb_hex(LBS_DEB_SCAN, "SCAN_TLV", scan_cmd->tlvbuffer, + tlv - scan_cmd->tlvbuffer); + + __lbs_cmd(priv, CMD_802_11_SCAN, &scan_cmd->hdr, + le16_to_cpu(scan_cmd->hdr.size), + lbs_ret_scan, 0); + + if (priv->scan_channel >= priv->scan_req->n_channels) { + /* Mark scan done */ + cancel_delayed_work(&priv->scan_work); + lbs_scan_done(priv); + } + + /* Restart network */ + if (carrier) + netif_carrier_on(priv->dev); + if (running && !priv->tx_pending_len) + netif_wake_queue(priv->dev); + + kfree(scan_cmd); + + /* Wake up anything waiting on scan completion */ + if (priv->scan_req == NULL) { + lbs_deb_scan("scan: waking up waiters\n"); + wake_up_all(&priv->scan_q); + } + + out_no_scan_cmd: + lbs_deb_leave(LBS_DEB_SCAN); +} + +static void _internal_start_scan(struct lbs_private *priv, bool internal, + struct cfg80211_scan_request *request) +{ + lbs_deb_enter(LBS_DEB_CFG80211); + + lbs_deb_scan("scan: ssids %d, channels %d, ie_len %zd\n", + request->n_ssids, request->n_channels, request->ie_len); + + priv->scan_channel = 0; + priv->scan_req = request; + priv->internal_scan = internal; + + queue_delayed_work(priv->work_thread, &priv->scan_work, + msecs_to_jiffies(50)); + + lbs_deb_leave(LBS_DEB_CFG80211); +} + +/* + * Clean up priv->scan_req. Should be used to handle the allocation details. + */ +void lbs_scan_done(struct lbs_private *priv) +{ + WARN_ON(!priv->scan_req); + + if (priv->internal_scan) + kfree(priv->scan_req); + else + cfg80211_scan_done(priv->scan_req, false); + + priv->scan_req = NULL; +} + +static int lbs_cfg_scan(struct wiphy *wiphy, + struct cfg80211_scan_request *request) +{ + struct lbs_private *priv = wiphy_priv(wiphy); + int ret = 0; + + lbs_deb_enter(LBS_DEB_CFG80211); + + if (priv->scan_req || delayed_work_pending(&priv->scan_work)) { + /* old scan request not yet processed */ + ret = -EAGAIN; + goto out; + } + + _internal_start_scan(priv, false, request); + + if (priv->surpriseremoved) + ret = -EIO; + + out: + lbs_deb_leave_args(LBS_DEB_CFG80211, "ret %d", ret); + return ret; +} + + + + +/* + * Events + */ + +void lbs_send_disconnect_notification(struct lbs_private *priv) +{ + lbs_deb_enter(LBS_DEB_CFG80211); + + cfg80211_disconnected(priv->dev, + 0, + NULL, 0, + GFP_KERNEL); + + lbs_deb_leave(LBS_DEB_CFG80211); +} + +void lbs_send_mic_failureevent(struct lbs_private *priv, u32 event) +{ + lbs_deb_enter(LBS_DEB_CFG80211); + + cfg80211_michael_mic_failure(priv->dev, + priv->assoc_bss, + event == MACREG_INT_CODE_MIC_ERR_MULTICAST ? + NL80211_KEYTYPE_GROUP : + NL80211_KEYTYPE_PAIRWISE, + -1, + NULL, + GFP_KERNEL); + + lbs_deb_leave(LBS_DEB_CFG80211); +} + + + + +/* + * Connect/disconnect + */ + + +/* + * This removes all WEP keys + */ +static int lbs_remove_wep_keys(struct lbs_private *priv) +{ + struct cmd_ds_802_11_set_wep cmd; + int ret; + + lbs_deb_enter(LBS_DEB_CFG80211); + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.keyindex = cpu_to_le16(priv->wep_tx_key); + cmd.action = cpu_to_le16(CMD_ACT_REMOVE); + + ret = lbs_cmd_with_response(priv, CMD_802_11_SET_WEP, &cmd); + + lbs_deb_leave(LBS_DEB_CFG80211); + return ret; +} + +/* + * Set WEP keys + */ +static int lbs_set_wep_keys(struct lbs_private *priv) +{ + struct cmd_ds_802_11_set_wep cmd; + int i; + int ret; + + lbs_deb_enter(LBS_DEB_CFG80211); + + /* + * command 13 00 + * size 50 00 + * sequence xx xx + * result 00 00 + * action 02 00 ACT_ADD + * transmit key 00 00 + * type for key 1 01 WEP40 + * type for key 2 00 + * type for key 3 00 + * type for key 4 00 + * key 1 39 39 39 39 39 00 00 00 + * 00 00 00 00 00 00 00 00 + * key 2 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 + * key 3 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 + * key 4 00 00 00 00 00 00 00 00 + */ + if (priv->wep_key_len[0] || priv->wep_key_len[1] || + priv->wep_key_len[2] || priv->wep_key_len[3]) { + /* Only set wep keys if we have at least one of them */ + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.keyindex = cpu_to_le16(priv->wep_tx_key); + cmd.action = cpu_to_le16(CMD_ACT_ADD); + + for (i = 0; i < 4; i++) { + switch (priv->wep_key_len[i]) { + case WLAN_KEY_LEN_WEP40: + cmd.keytype[i] = CMD_TYPE_WEP_40_BIT; + break; + case WLAN_KEY_LEN_WEP104: + cmd.keytype[i] = CMD_TYPE_WEP_104_BIT; + break; + default: + cmd.keytype[i] = 0; + break; + } + memcpy(cmd.keymaterial[i], priv->wep_key[i], + priv->wep_key_len[i]); + } + + ret = lbs_cmd_with_response(priv, CMD_802_11_SET_WEP, &cmd); + } else { + /* Otherwise remove all wep keys */ + ret = lbs_remove_wep_keys(priv); + } + + lbs_deb_leave(LBS_DEB_CFG80211); + return ret; +} + + +/* + * Enable/Disable RSN status + */ +static int lbs_enable_rsn(struct lbs_private *priv, int enable) +{ + struct cmd_ds_802_11_enable_rsn cmd; + int ret; + + lbs_deb_enter_args(LBS_DEB_CFG80211, "%d", enable); + + /* + * cmd 2f 00 + * size 0c 00 + * sequence xx xx + * result 00 00 + * action 01 00 ACT_SET + * enable 01 00 + */ + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_SET); + cmd.enable = cpu_to_le16(enable); + + ret = lbs_cmd_with_response(priv, CMD_802_11_ENABLE_RSN, &cmd); + + lbs_deb_leave(LBS_DEB_CFG80211); + return ret; +} + + +/* + * Set WPA/WPA key material + */ + +/* + * like "struct cmd_ds_802_11_key_material", but with cmd_header. Once we + * get rid of WEXT, this should go into host.h + */ + +struct cmd_key_material { + struct cmd_header hdr; + + __le16 action; + struct MrvlIEtype_keyParamSet param; +} __packed; + +static int lbs_set_key_material(struct lbs_private *priv, + int key_type, + int key_info, + u8 *key, u16 key_len) +{ + struct cmd_key_material cmd; + int ret; + + lbs_deb_enter(LBS_DEB_CFG80211); + + /* + * Example for WPA (TKIP): + * + * cmd 5e 00 + * size 34 00 + * sequence xx xx + * result 00 00 + * action 01 00 + * TLV type 00 01 key param + * length 00 26 + * key type 01 00 TKIP + * key info 06 00 UNICAST | ENABLED + * key len 20 00 + * key 32 bytes + */ + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_SET); + cmd.param.type = cpu_to_le16(TLV_TYPE_KEY_MATERIAL); + cmd.param.length = cpu_to_le16(sizeof(cmd.param) - 4); + cmd.param.keytypeid = cpu_to_le16(key_type); + cmd.param.keyinfo = cpu_to_le16(key_info); + cmd.param.keylen = cpu_to_le16(key_len); + if (key && key_len) + memcpy(cmd.param.key, key, key_len); + + ret = lbs_cmd_with_response(priv, CMD_802_11_KEY_MATERIAL, &cmd); + + lbs_deb_leave(LBS_DEB_CFG80211); + return ret; +} + + +/* + * Sets the auth type (open, shared, etc) in the firmware. That + * we use CMD_802_11_AUTHENTICATE is misleading, this firmware + * command doesn't send an authentication frame at all, it just + * stores the auth_type. + */ +static int lbs_set_authtype(struct lbs_private *priv, + struct cfg80211_connect_params *sme) +{ + struct cmd_ds_802_11_authenticate cmd; + int ret; + + lbs_deb_enter_args(LBS_DEB_CFG80211, "%d", sme->auth_type); + + /* + * cmd 11 00 + * size 19 00 + * sequence xx xx + * result 00 00 + * BSS id 00 13 19 80 da 30 + * auth type 00 + * reserved 00 00 00 00 00 00 00 00 00 00 + */ + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + if (sme->bssid) + memcpy(cmd.bssid, sme->bssid, ETH_ALEN); + /* convert auth_type */ + ret = lbs_auth_to_authtype(sme->auth_type); + if (ret < 0) + goto done; + + cmd.authtype = ret; + ret = lbs_cmd_with_response(priv, CMD_802_11_AUTHENTICATE, &cmd); + + done: + lbs_deb_leave_args(LBS_DEB_CFG80211, "ret %d", ret); + return ret; +} + + +/* + * Create association request + */ +#define LBS_ASSOC_MAX_CMD_SIZE \ + (sizeof(struct cmd_ds_802_11_associate) \ + - 512 /* cmd_ds_802_11_associate.iebuf */ \ + + LBS_MAX_SSID_TLV_SIZE \ + + LBS_MAX_CHANNEL_TLV_SIZE \ + + LBS_MAX_CF_PARAM_TLV_SIZE \ + + LBS_MAX_AUTH_TYPE_TLV_SIZE \ + + LBS_MAX_WPA_TLV_SIZE) + +static int lbs_associate(struct lbs_private *priv, + struct cfg80211_bss *bss, + struct cfg80211_connect_params *sme) +{ + struct cmd_ds_802_11_associate_response *resp; + struct cmd_ds_802_11_associate *cmd = kzalloc(LBS_ASSOC_MAX_CMD_SIZE, + GFP_KERNEL); + const u8 *ssid_eid; + size_t len, resp_ie_len; + int status; + int ret; + u8 *pos = &(cmd->iebuf[0]); + u8 *tmp; + + lbs_deb_enter(LBS_DEB_CFG80211); + + if (!cmd) { + ret = -ENOMEM; + goto done; + } + + /* + * cmd 50 00 + * length 34 00 + * sequence xx xx + * result 00 00 + * BSS id 00 13 19 80 da 30 + * capabilities 11 00 + * listen interval 0a 00 + * beacon interval 00 00 + * DTIM period 00 + * TLVs xx (up to 512 bytes) + */ + cmd->hdr.command = cpu_to_le16(CMD_802_11_ASSOCIATE); + + /* Fill in static fields */ + memcpy(cmd->bssid, bss->bssid, ETH_ALEN); + cmd->listeninterval = cpu_to_le16(MRVDRV_DEFAULT_LISTEN_INTERVAL); + cmd->capability = cpu_to_le16(bss->capability); + + /* add SSID TLV */ + rcu_read_lock(); + ssid_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SSID); + if (ssid_eid) + pos += lbs_add_ssid_tlv(pos, ssid_eid + 2, ssid_eid[1]); + else + lbs_deb_assoc("no SSID\n"); + rcu_read_unlock(); + + /* add DS param TLV */ + if (bss->channel) + pos += lbs_add_channel_tlv(pos, bss->channel->hw_value); + else + lbs_deb_assoc("no channel\n"); + + /* add (empty) CF param TLV */ + pos += lbs_add_cf_param_tlv(pos); + + /* add rates TLV */ + tmp = pos + 4; /* skip Marvell IE header */ + pos += lbs_add_common_rates_tlv(pos, bss); + lbs_deb_hex(LBS_DEB_ASSOC, "Common Rates", tmp, pos - tmp); + + /* add auth type TLV */ + if (MRVL_FW_MAJOR_REV(priv->fwrelease) >= 9) + pos += lbs_add_auth_type_tlv(pos, sme->auth_type); + + /* add WPA/WPA2 TLV */ + if (sme->ie && sme->ie_len) + pos += lbs_add_wpa_tlv(pos, sme->ie, sme->ie_len); + + len = (sizeof(*cmd) - sizeof(cmd->iebuf)) + + (u16)(pos - (u8 *) &cmd->iebuf); + cmd->hdr.size = cpu_to_le16(len); + + lbs_deb_hex(LBS_DEB_ASSOC, "ASSOC_CMD", (u8 *) cmd, + le16_to_cpu(cmd->hdr.size)); + + /* store for later use */ + memcpy(priv->assoc_bss, bss->bssid, ETH_ALEN); + + ret = lbs_cmd_with_response(priv, CMD_802_11_ASSOCIATE, cmd); + if (ret) + goto done; + + /* generate connect message to cfg80211 */ + + resp = (void *) cmd; /* recast for easier field access */ + status = le16_to_cpu(resp->statuscode); + + /* Older FW versions map the IEEE 802.11 Status Code in the association + * response to the following values returned in resp->statuscode: + * + * IEEE Status Code Marvell Status Code + * 0 -> 0x0000 ASSOC_RESULT_SUCCESS + * 13 -> 0x0004 ASSOC_RESULT_AUTH_REFUSED + * 14 -> 0x0004 ASSOC_RESULT_AUTH_REFUSED + * 15 -> 0x0004 ASSOC_RESULT_AUTH_REFUSED + * 16 -> 0x0004 ASSOC_RESULT_AUTH_REFUSED + * others -> 0x0003 ASSOC_RESULT_REFUSED + * + * Other response codes: + * 0x0001 -> ASSOC_RESULT_INVALID_PARAMETERS (unused) + * 0x0002 -> ASSOC_RESULT_TIMEOUT (internal timer expired waiting for + * association response from the AP) + */ + if (MRVL_FW_MAJOR_REV(priv->fwrelease) <= 8) { + switch (status) { + case 0: + break; + case 1: + lbs_deb_assoc("invalid association parameters\n"); + status = WLAN_STATUS_CAPS_UNSUPPORTED; + break; + case 2: + lbs_deb_assoc("timer expired while waiting for AP\n"); + status = WLAN_STATUS_AUTH_TIMEOUT; + break; + case 3: + lbs_deb_assoc("association refused by AP\n"); + status = WLAN_STATUS_ASSOC_DENIED_UNSPEC; + break; + case 4: + lbs_deb_assoc("authentication refused by AP\n"); + status = WLAN_STATUS_UNKNOWN_AUTH_TRANSACTION; + break; + default: + lbs_deb_assoc("association failure %d\n", status); + /* v5 OLPC firmware does return the AP status code if + * it's not one of the values above. Let that through. + */ + break; + } + } + + lbs_deb_assoc("status %d, statuscode 0x%04x, capability 0x%04x, " + "aid 0x%04x\n", status, le16_to_cpu(resp->statuscode), + le16_to_cpu(resp->capability), le16_to_cpu(resp->aid)); + + resp_ie_len = le16_to_cpu(resp->hdr.size) + - sizeof(resp->hdr) + - 6; + cfg80211_connect_result(priv->dev, + priv->assoc_bss, + sme->ie, sme->ie_len, + resp->iebuf, resp_ie_len, + status, + GFP_KERNEL); + + if (status == 0) { + /* TODO: get rid of priv->connect_status */ + priv->connect_status = LBS_CONNECTED; + netif_carrier_on(priv->dev); + if (!priv->tx_pending_len) + netif_tx_wake_all_queues(priv->dev); + } + + kfree(cmd); +done: + lbs_deb_leave_args(LBS_DEB_CFG80211, "ret %d", ret); + return ret; +} + +static struct cfg80211_scan_request * +_new_connect_scan_req(struct wiphy *wiphy, struct cfg80211_connect_params *sme) +{ + struct cfg80211_scan_request *creq = NULL; + int i, n_channels = 0; + enum ieee80211_band band; + + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { + if (wiphy->bands[band]) + n_channels += wiphy->bands[band]->n_channels; + } + + creq = kzalloc(sizeof(*creq) + sizeof(struct cfg80211_ssid) + + n_channels * sizeof(void *), + GFP_ATOMIC); + if (!creq) + return NULL; + + /* SSIDs come after channels */ + creq->ssids = (void *)&creq->channels[n_channels]; + creq->n_channels = n_channels; + creq->n_ssids = 1; + + /* Scan all available channels */ + i = 0; + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { + int j; + + if (!wiphy->bands[band]) + continue; + + for (j = 0; j < wiphy->bands[band]->n_channels; j++) { + /* ignore disabled channels */ + if (wiphy->bands[band]->channels[j].flags & + IEEE80211_CHAN_DISABLED) + continue; + + creq->channels[i] = &wiphy->bands[band]->channels[j]; + i++; + } + } + if (i) { + /* Set real number of channels specified in creq->channels[] */ + creq->n_channels = i; + + /* Scan for the SSID we're going to connect to */ + memcpy(creq->ssids[0].ssid, sme->ssid, sme->ssid_len); + creq->ssids[0].ssid_len = sme->ssid_len; + } else { + /* No channels found... */ + kfree(creq); + creq = NULL; + } + + return creq; +} + +static int lbs_cfg_connect(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_connect_params *sme) +{ + struct lbs_private *priv = wiphy_priv(wiphy); + struct cfg80211_bss *bss = NULL; + int ret = 0; + u8 preamble = RADIO_PREAMBLE_SHORT; + + if (dev == priv->mesh_dev) + return -EOPNOTSUPP; + + lbs_deb_enter(LBS_DEB_CFG80211); + + if (!sme->bssid) { + struct cfg80211_scan_request *creq; + + /* + * Scan for the requested network after waiting for existing + * scans to finish. + */ + lbs_deb_assoc("assoc: waiting for existing scans\n"); + wait_event_interruptible_timeout(priv->scan_q, + (priv->scan_req == NULL), + (15 * HZ)); + + creq = _new_connect_scan_req(wiphy, sme); + if (!creq) { + ret = -EINVAL; + goto done; + } + + lbs_deb_assoc("assoc: scanning for compatible AP\n"); + _internal_start_scan(priv, true, creq); + + lbs_deb_assoc("assoc: waiting for scan to complete\n"); + wait_event_interruptible_timeout(priv->scan_q, + (priv->scan_req == NULL), + (15 * HZ)); + lbs_deb_assoc("assoc: scanning competed\n"); + } + + /* Find the BSS we want using available scan results */ + bss = cfg80211_get_bss(wiphy, sme->channel, sme->bssid, + sme->ssid, sme->ssid_len, + WLAN_CAPABILITY_ESS, WLAN_CAPABILITY_ESS); + if (!bss) { + wiphy_err(wiphy, "assoc: bss %pM not in scan results\n", + sme->bssid); + ret = -ENOENT; + goto done; + } + lbs_deb_assoc("trying %pM\n", bss->bssid); + lbs_deb_assoc("cipher 0x%x, key index %d, key len %d\n", + sme->crypto.cipher_group, + sme->key_idx, sme->key_len); + + /* As this is a new connection, clear locally stored WEP keys */ + priv->wep_tx_key = 0; + memset(priv->wep_key, 0, sizeof(priv->wep_key)); + memset(priv->wep_key_len, 0, sizeof(priv->wep_key_len)); + + /* set/remove WEP keys */ + switch (sme->crypto.cipher_group) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + /* Store provided WEP keys in priv-> */ + priv->wep_tx_key = sme->key_idx; + priv->wep_key_len[sme->key_idx] = sme->key_len; + memcpy(priv->wep_key[sme->key_idx], sme->key, sme->key_len); + /* Set WEP keys and WEP mode */ + lbs_set_wep_keys(priv); + priv->mac_control |= CMD_ACT_MAC_WEP_ENABLE; + lbs_set_mac_control(priv); + /* No RSN mode for WEP */ + lbs_enable_rsn(priv, 0); + break; + case 0: /* there's no WLAN_CIPHER_SUITE_NONE definition */ + /* + * If we don't have no WEP, no WPA and no WPA2, + * we remove all keys like in the WPA/WPA2 setup, + * we just don't set RSN. + * + * Therefore: fall-through + */ + case WLAN_CIPHER_SUITE_TKIP: + case WLAN_CIPHER_SUITE_CCMP: + /* Remove WEP keys and WEP mode */ + lbs_remove_wep_keys(priv); + priv->mac_control &= ~CMD_ACT_MAC_WEP_ENABLE; + lbs_set_mac_control(priv); + + /* clear the WPA/WPA2 keys */ + lbs_set_key_material(priv, + KEY_TYPE_ID_WEP, /* doesn't matter */ + KEY_INFO_WPA_UNICAST, + NULL, 0); + lbs_set_key_material(priv, + KEY_TYPE_ID_WEP, /* doesn't matter */ + KEY_INFO_WPA_MCAST, + NULL, 0); + /* RSN mode for WPA/WPA2 */ + lbs_enable_rsn(priv, sme->crypto.cipher_group != 0); + break; + default: + wiphy_err(wiphy, "unsupported cipher group 0x%x\n", + sme->crypto.cipher_group); + ret = -ENOTSUPP; + goto done; + } + + ret = lbs_set_authtype(priv, sme); + if (ret == -ENOTSUPP) { + wiphy_err(wiphy, "unsupported authtype 0x%x\n", sme->auth_type); + goto done; + } + + lbs_set_radio(priv, preamble, 1); + + /* Do the actual association */ + ret = lbs_associate(priv, bss, sme); + + done: + if (bss) + cfg80211_put_bss(wiphy, bss); + lbs_deb_leave_args(LBS_DEB_CFG80211, "ret %d", ret); + return ret; +} + +int lbs_disconnect(struct lbs_private *priv, u16 reason) +{ + struct cmd_ds_802_11_deauthenticate cmd; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + /* Mildly ugly to use a locally store my own BSSID ... */ + memcpy(cmd.macaddr, &priv->assoc_bss, ETH_ALEN); + cmd.reasoncode = cpu_to_le16(reason); + + ret = lbs_cmd_with_response(priv, CMD_802_11_DEAUTHENTICATE, &cmd); + if (ret) + return ret; + + cfg80211_disconnected(priv->dev, + reason, + NULL, 0, + GFP_KERNEL); + priv->connect_status = LBS_DISCONNECTED; + + return 0; +} + +static int lbs_cfg_disconnect(struct wiphy *wiphy, struct net_device *dev, + u16 reason_code) +{ + struct lbs_private *priv = wiphy_priv(wiphy); + + if (dev == priv->mesh_dev) + return -EOPNOTSUPP; + + lbs_deb_enter_args(LBS_DEB_CFG80211, "reason_code %d", reason_code); + + /* store for lbs_cfg_ret_disconnect() */ + priv->disassoc_reason = reason_code; + + return lbs_disconnect(priv, reason_code); +} + +static int lbs_cfg_set_default_key(struct wiphy *wiphy, + struct net_device *netdev, + u8 key_index, bool unicast, + bool multicast) +{ + struct lbs_private *priv = wiphy_priv(wiphy); + + if (netdev == priv->mesh_dev) + return -EOPNOTSUPP; + + lbs_deb_enter(LBS_DEB_CFG80211); + + if (key_index != priv->wep_tx_key) { + lbs_deb_assoc("set_default_key: to %d\n", key_index); + priv->wep_tx_key = key_index; + lbs_set_wep_keys(priv); + } + + return 0; +} + + +static int lbs_cfg_add_key(struct wiphy *wiphy, struct net_device *netdev, + u8 idx, bool pairwise, const u8 *mac_addr, + struct key_params *params) +{ + struct lbs_private *priv = wiphy_priv(wiphy); + u16 key_info; + u16 key_type; + int ret = 0; + + if (netdev == priv->mesh_dev) + return -EOPNOTSUPP; + + lbs_deb_enter(LBS_DEB_CFG80211); + + lbs_deb_assoc("add_key: cipher 0x%x, mac_addr %pM\n", + params->cipher, mac_addr); + lbs_deb_assoc("add_key: key index %d, key len %d\n", + idx, params->key_len); + if (params->key_len) + lbs_deb_hex(LBS_DEB_CFG80211, "KEY", + params->key, params->key_len); + + lbs_deb_assoc("add_key: seq len %d\n", params->seq_len); + if (params->seq_len) + lbs_deb_hex(LBS_DEB_CFG80211, "SEQ", + params->seq, params->seq_len); + + switch (params->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + /* actually compare if something has changed ... */ + if ((priv->wep_key_len[idx] != params->key_len) || + memcmp(priv->wep_key[idx], + params->key, params->key_len) != 0) { + priv->wep_key_len[idx] = params->key_len; + memcpy(priv->wep_key[idx], + params->key, params->key_len); + lbs_set_wep_keys(priv); + } + break; + case WLAN_CIPHER_SUITE_TKIP: + case WLAN_CIPHER_SUITE_CCMP: + key_info = KEY_INFO_WPA_ENABLED | ((idx == 0) + ? KEY_INFO_WPA_UNICAST + : KEY_INFO_WPA_MCAST); + key_type = (params->cipher == WLAN_CIPHER_SUITE_TKIP) + ? KEY_TYPE_ID_TKIP + : KEY_TYPE_ID_AES; + lbs_set_key_material(priv, + key_type, + key_info, + params->key, params->key_len); + break; + default: + wiphy_err(wiphy, "unhandled cipher 0x%x\n", params->cipher); + ret = -ENOTSUPP; + break; + } + + return ret; +} + + +static int lbs_cfg_del_key(struct wiphy *wiphy, struct net_device *netdev, + u8 key_index, bool pairwise, const u8 *mac_addr) +{ + + lbs_deb_enter(LBS_DEB_CFG80211); + + lbs_deb_assoc("del_key: key_idx %d, mac_addr %pM\n", + key_index, mac_addr); + +#ifdef TODO + struct lbs_private *priv = wiphy_priv(wiphy); + /* + * I think can keep this a NO-OP, because: + + * - we clear all keys whenever we do lbs_cfg_connect() anyway + * - neither "iw" nor "wpa_supplicant" won't call this during + * an ongoing connection + * - TODO: but I have to check if this is still true when + * I set the AP to periodic re-keying + * - we've not kzallec() something when we've added a key at + * lbs_cfg_connect() or lbs_cfg_add_key(). + * + * This causes lbs_cfg_del_key() only called at disconnect time, + * where we'd just waste time deleting a key that is not going + * to be used anyway. + */ + if (key_index < 3 && priv->wep_key_len[key_index]) { + priv->wep_key_len[key_index] = 0; + lbs_set_wep_keys(priv); + } +#endif + + return 0; +} + + +/* + * Get station + */ + +static int lbs_cfg_get_station(struct wiphy *wiphy, struct net_device *dev, + u8 *mac, struct station_info *sinfo) +{ + struct lbs_private *priv = wiphy_priv(wiphy); + s8 signal, noise; + int ret; + size_t i; + + lbs_deb_enter(LBS_DEB_CFG80211); + + sinfo->filled |= STATION_INFO_TX_BYTES | + STATION_INFO_TX_PACKETS | + STATION_INFO_RX_BYTES | + STATION_INFO_RX_PACKETS; + sinfo->tx_bytes = priv->dev->stats.tx_bytes; + sinfo->tx_packets = priv->dev->stats.tx_packets; + sinfo->rx_bytes = priv->dev->stats.rx_bytes; + sinfo->rx_packets = priv->dev->stats.rx_packets; + + /* Get current RSSI */ + ret = lbs_get_rssi(priv, &signal, &noise); + if (ret == 0) { + sinfo->signal = signal; + sinfo->filled |= STATION_INFO_SIGNAL; + } + + /* Convert priv->cur_rate from hw_value to NL80211 value */ + for (i = 0; i < ARRAY_SIZE(lbs_rates); i++) { + if (priv->cur_rate == lbs_rates[i].hw_value) { + sinfo->txrate.legacy = lbs_rates[i].bitrate; + sinfo->filled |= STATION_INFO_TX_BITRATE; + break; + } + } + + return 0; +} + + + + +/* + * Change interface + */ + +static int lbs_change_intf(struct wiphy *wiphy, struct net_device *dev, + enum nl80211_iftype type, u32 *flags, + struct vif_params *params) +{ + struct lbs_private *priv = wiphy_priv(wiphy); + int ret = 0; + + if (dev == priv->mesh_dev) + return -EOPNOTSUPP; + + switch (type) { + case NL80211_IFTYPE_MONITOR: + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: + break; + default: + return -EOPNOTSUPP; + } + + lbs_deb_enter(LBS_DEB_CFG80211); + + if (priv->iface_running) + ret = lbs_set_iface_type(priv, type); + + if (!ret) + priv->wdev->iftype = type; + + lbs_deb_leave_args(LBS_DEB_CFG80211, "ret %d", ret); + return ret; +} + + + +/* + * IBSS (Ad-Hoc) + */ + +/* + * The firmware needs the following bits masked out of the beacon-derived + * capability field when associating/joining to a BSS: + * 9 (QoS), 11 (APSD), 12 (unused), 14 (unused), 15 (unused) + */ +#define CAPINFO_MASK (~(0xda00)) + + +static void lbs_join_post(struct lbs_private *priv, + struct cfg80211_ibss_params *params, + u8 *bssid, u16 capability) +{ + u8 fake_ie[2 + IEEE80211_MAX_SSID_LEN + /* ssid */ + 2 + 4 + /* basic rates */ + 2 + 1 + /* DS parameter */ + 2 + 2 + /* atim */ + 2 + 8]; /* extended rates */ + u8 *fake = fake_ie; + struct cfg80211_bss *bss; + + lbs_deb_enter(LBS_DEB_CFG80211); + + /* + * For cfg80211_inform_bss, we'll need a fake IE, as we can't get + * the real IE from the firmware. So we fabricate a fake IE based on + * what the firmware actually sends (sniffed with wireshark). + */ + /* Fake SSID IE */ + *fake++ = WLAN_EID_SSID; + *fake++ = params->ssid_len; + memcpy(fake, params->ssid, params->ssid_len); + fake += params->ssid_len; + /* Fake supported basic rates IE */ + *fake++ = WLAN_EID_SUPP_RATES; + *fake++ = 4; + *fake++ = 0x82; + *fake++ = 0x84; + *fake++ = 0x8b; + *fake++ = 0x96; + /* Fake DS channel IE */ + *fake++ = WLAN_EID_DS_PARAMS; + *fake++ = 1; + *fake++ = params->chandef.chan->hw_value; + /* Fake IBSS params IE */ + *fake++ = WLAN_EID_IBSS_PARAMS; + *fake++ = 2; + *fake++ = 0; /* ATIM=0 */ + *fake++ = 0; + /* Fake extended rates IE, TODO: don't add this for 802.11b only, + * but I don't know how this could be checked */ + *fake++ = WLAN_EID_EXT_SUPP_RATES; + *fake++ = 8; + *fake++ = 0x0c; + *fake++ = 0x12; + *fake++ = 0x18; + *fake++ = 0x24; + *fake++ = 0x30; + *fake++ = 0x48; + *fake++ = 0x60; + *fake++ = 0x6c; + lbs_deb_hex(LBS_DEB_CFG80211, "IE", fake_ie, fake - fake_ie); + + bss = cfg80211_inform_bss(priv->wdev->wiphy, + params->chandef.chan, + bssid, + 0, + capability, + params->beacon_interval, + fake_ie, fake - fake_ie, + 0, GFP_KERNEL); + cfg80211_put_bss(priv->wdev->wiphy, bss); + + memcpy(priv->wdev->ssid, params->ssid, params->ssid_len); + priv->wdev->ssid_len = params->ssid_len; + + cfg80211_ibss_joined(priv->dev, bssid, GFP_KERNEL); + + /* TODO: consider doing this at MACREG_INT_CODE_LINK_SENSED time */ + priv->connect_status = LBS_CONNECTED; + netif_carrier_on(priv->dev); + if (!priv->tx_pending_len) + netif_wake_queue(priv->dev); + + lbs_deb_leave(LBS_DEB_CFG80211); +} + +static int lbs_ibss_join_existing(struct lbs_private *priv, + struct cfg80211_ibss_params *params, + struct cfg80211_bss *bss) +{ + const u8 *rates_eid; + struct cmd_ds_802_11_ad_hoc_join cmd; + u8 preamble = RADIO_PREAMBLE_SHORT; + int ret = 0; + + lbs_deb_enter(LBS_DEB_CFG80211); + + /* TODO: set preamble based on scan result */ + ret = lbs_set_radio(priv, preamble, 1); + if (ret) + goto out; + + /* + * Example CMD_802_11_AD_HOC_JOIN command: + * + * command 2c 00 CMD_802_11_AD_HOC_JOIN + * size 65 00 + * sequence xx xx + * result 00 00 + * bssid 02 27 27 97 2f 96 + * ssid 49 42 53 53 00 00 00 00 + * 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 + * type 02 CMD_BSS_TYPE_IBSS + * beacon period 64 00 + * dtim period 00 + * timestamp 00 00 00 00 00 00 00 00 + * localtime 00 00 00 00 00 00 00 00 + * IE DS 03 + * IE DS len 01 + * IE DS channel 01 + * reserveed 00 00 00 00 + * IE IBSS 06 + * IE IBSS len 02 + * IE IBSS atim 00 00 + * reserved 00 00 00 00 + * capability 02 00 + * rates 82 84 8b 96 0c 12 18 24 30 48 60 6c 00 + * fail timeout ff 00 + * probe delay 00 00 + */ + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + + memcpy(cmd.bss.bssid, bss->bssid, ETH_ALEN); + memcpy(cmd.bss.ssid, params->ssid, params->ssid_len); + cmd.bss.type = CMD_BSS_TYPE_IBSS; + cmd.bss.beaconperiod = cpu_to_le16(params->beacon_interval); + cmd.bss.ds.header.id = WLAN_EID_DS_PARAMS; + cmd.bss.ds.header.len = 1; + cmd.bss.ds.channel = params->chandef.chan->hw_value; + cmd.bss.ibss.header.id = WLAN_EID_IBSS_PARAMS; + cmd.bss.ibss.header.len = 2; + cmd.bss.ibss.atimwindow = 0; + cmd.bss.capability = cpu_to_le16(bss->capability & CAPINFO_MASK); + + /* set rates to the intersection of our rates and the rates in the + bss */ + rcu_read_lock(); + rates_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SUPP_RATES); + if (!rates_eid) { + lbs_add_rates(cmd.bss.rates); + } else { + int hw, i; + u8 rates_max = rates_eid[1]; + u8 *rates = cmd.bss.rates; + for (hw = 0; hw < ARRAY_SIZE(lbs_rates); hw++) { + u8 hw_rate = lbs_rates[hw].bitrate / 5; + for (i = 0; i < rates_max; i++) { + if (hw_rate == (rates_eid[i+2] & 0x7f)) { + u8 rate = rates_eid[i+2]; + if (rate == 0x02 || rate == 0x04 || + rate == 0x0b || rate == 0x16) + rate |= 0x80; + *rates++ = rate; + } + } + } + } + rcu_read_unlock(); + + /* Only v8 and below support setting this */ + if (MRVL_FW_MAJOR_REV(priv->fwrelease) <= 8) { + cmd.failtimeout = cpu_to_le16(MRVDRV_ASSOCIATION_TIME_OUT); + cmd.probedelay = cpu_to_le16(CMD_SCAN_PROBE_DELAY_TIME); + } + ret = lbs_cmd_with_response(priv, CMD_802_11_AD_HOC_JOIN, &cmd); + if (ret) + goto out; + + /* + * This is a sample response to CMD_802_11_AD_HOC_JOIN: + * + * response 2c 80 + * size 09 00 + * sequence xx xx + * result 00 00 + * reserved 00 + */ + lbs_join_post(priv, params, bss->bssid, bss->capability); + + out: + lbs_deb_leave_args(LBS_DEB_CFG80211, "ret %d", ret); + return ret; +} + + + +static int lbs_ibss_start_new(struct lbs_private *priv, + struct cfg80211_ibss_params *params) +{ + struct cmd_ds_802_11_ad_hoc_start cmd; + struct cmd_ds_802_11_ad_hoc_result *resp = + (struct cmd_ds_802_11_ad_hoc_result *) &cmd; + u8 preamble = RADIO_PREAMBLE_SHORT; + int ret = 0; + u16 capability; + + lbs_deb_enter(LBS_DEB_CFG80211); + + ret = lbs_set_radio(priv, preamble, 1); + if (ret) + goto out; + + /* + * Example CMD_802_11_AD_HOC_START command: + * + * command 2b 00 CMD_802_11_AD_HOC_START + * size b1 00 + * sequence xx xx + * result 00 00 + * ssid 54 45 53 54 00 00 00 00 + * 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 + * 00 00 00 00 00 00 00 00 + * bss type 02 + * beacon period 64 00 + * dtim period 00 + * IE IBSS 06 + * IE IBSS len 02 + * IE IBSS atim 00 00 + * reserved 00 00 00 00 + * IE DS 03 + * IE DS len 01 + * IE DS channel 01 + * reserved 00 00 00 00 + * probe delay 00 00 + * capability 02 00 + * rates 82 84 8b 96 (basic rates with have bit 7 set) + * 0c 12 18 24 30 48 60 6c + * padding 100 bytes + */ + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + memcpy(cmd.ssid, params->ssid, params->ssid_len); + cmd.bsstype = CMD_BSS_TYPE_IBSS; + cmd.beaconperiod = cpu_to_le16(params->beacon_interval); + cmd.ibss.header.id = WLAN_EID_IBSS_PARAMS; + cmd.ibss.header.len = 2; + cmd.ibss.atimwindow = 0; + cmd.ds.header.id = WLAN_EID_DS_PARAMS; + cmd.ds.header.len = 1; + cmd.ds.channel = params->chandef.chan->hw_value; + /* Only v8 and below support setting probe delay */ + if (MRVL_FW_MAJOR_REV(priv->fwrelease) <= 8) + cmd.probedelay = cpu_to_le16(CMD_SCAN_PROBE_DELAY_TIME); + /* TODO: mix in WLAN_CAPABILITY_PRIVACY */ + capability = WLAN_CAPABILITY_IBSS; + cmd.capability = cpu_to_le16(capability); + lbs_add_rates(cmd.rates); + + + ret = lbs_cmd_with_response(priv, CMD_802_11_AD_HOC_START, &cmd); + if (ret) + goto out; + + /* + * This is a sample response to CMD_802_11_AD_HOC_JOIN: + * + * response 2b 80 + * size 14 00 + * sequence xx xx + * result 00 00 + * reserved 00 + * bssid 02 2b 7b 0f 86 0e + */ + lbs_join_post(priv, params, resp->bssid, capability); + + out: + lbs_deb_leave_args(LBS_DEB_CFG80211, "ret %d", ret); + return ret; +} + + +static int lbs_join_ibss(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_ibss_params *params) +{ + struct lbs_private *priv = wiphy_priv(wiphy); + int ret = 0; + struct cfg80211_bss *bss; + DECLARE_SSID_BUF(ssid_buf); + + if (dev == priv->mesh_dev) + return -EOPNOTSUPP; + + lbs_deb_enter(LBS_DEB_CFG80211); + + if (!params->chandef.chan) { + ret = -ENOTSUPP; + goto out; + } + + ret = lbs_set_channel(priv, params->chandef.chan->hw_value); + if (ret) + goto out; + + /* Search if someone is beaconing. This assumes that the + * bss list is populated already */ + bss = cfg80211_get_bss(wiphy, params->chandef.chan, params->bssid, + params->ssid, params->ssid_len, + WLAN_CAPABILITY_IBSS, WLAN_CAPABILITY_IBSS); + + if (bss) { + ret = lbs_ibss_join_existing(priv, params, bss); + cfg80211_put_bss(wiphy, bss); + } else + ret = lbs_ibss_start_new(priv, params); + + + out: + lbs_deb_leave_args(LBS_DEB_CFG80211, "ret %d", ret); + return ret; +} + + +static int lbs_leave_ibss(struct wiphy *wiphy, struct net_device *dev) +{ + struct lbs_private *priv = wiphy_priv(wiphy); + struct cmd_ds_802_11_ad_hoc_stop cmd; + int ret = 0; + + if (dev == priv->mesh_dev) + return -EOPNOTSUPP; + + lbs_deb_enter(LBS_DEB_CFG80211); + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + ret = lbs_cmd_with_response(priv, CMD_802_11_AD_HOC_STOP, &cmd); + + /* TODO: consider doing this at MACREG_INT_CODE_ADHOC_BCN_LOST time */ + lbs_mac_event_disconnected(priv); + + lbs_deb_leave_args(LBS_DEB_CFG80211, "ret %d", ret); + return ret; +} + + + + +/* + * Initialization + */ + +static struct cfg80211_ops lbs_cfg80211_ops = { + .set_monitor_channel = lbs_cfg_set_monitor_channel, + .libertas_set_mesh_channel = lbs_cfg_set_mesh_channel, + .scan = lbs_cfg_scan, + .connect = lbs_cfg_connect, + .disconnect = lbs_cfg_disconnect, + .add_key = lbs_cfg_add_key, + .del_key = lbs_cfg_del_key, + .set_default_key = lbs_cfg_set_default_key, + .get_station = lbs_cfg_get_station, + .change_virtual_intf = lbs_change_intf, + .join_ibss = lbs_join_ibss, + .leave_ibss = lbs_leave_ibss, +}; + + +/* + * At this time lbs_private *priv doesn't even exist, so we just allocate + * memory and don't initialize the wiphy further. This is postponed until we + * can talk to the firmware and happens at registration time in + * lbs_cfg_wiphy_register(). + */ +struct wireless_dev *lbs_cfg_alloc(struct device *dev) +{ + int ret = 0; + struct wireless_dev *wdev; + + lbs_deb_enter(LBS_DEB_CFG80211); + + wdev = kzalloc(sizeof(struct wireless_dev), GFP_KERNEL); + if (!wdev) + return ERR_PTR(-ENOMEM); + + wdev->wiphy = wiphy_new(&lbs_cfg80211_ops, sizeof(struct lbs_private)); + if (!wdev->wiphy) { + dev_err(dev, "cannot allocate wiphy\n"); + ret = -ENOMEM; + goto err_wiphy_new; + } + + lbs_deb_leave(LBS_DEB_CFG80211); + return wdev; + + err_wiphy_new: + kfree(wdev); + lbs_deb_leave_args(LBS_DEB_CFG80211, "ret %d", ret); + return ERR_PTR(ret); +} + + +static void lbs_cfg_set_regulatory_hint(struct lbs_private *priv) +{ + struct region_code_mapping { + const char *cn; + int code; + }; + + /* Section 5.17.2 */ + static const struct region_code_mapping regmap[] = { + {"US ", 0x10}, /* US FCC */ + {"CA ", 0x20}, /* Canada */ + {"EU ", 0x30}, /* ETSI */ + {"ES ", 0x31}, /* Spain */ + {"FR ", 0x32}, /* France */ + {"JP ", 0x40}, /* Japan */ + }; + size_t i; + + lbs_deb_enter(LBS_DEB_CFG80211); + + for (i = 0; i < ARRAY_SIZE(regmap); i++) + if (regmap[i].code == priv->regioncode) { + regulatory_hint(priv->wdev->wiphy, regmap[i].cn); + break; + } + + lbs_deb_leave(LBS_DEB_CFG80211); +} + +static void lbs_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct lbs_private *priv = wiphy_priv(wiphy); + + lbs_deb_enter_args(LBS_DEB_CFG80211, "cfg80211 regulatory domain " + "callback for domain %c%c\n", request->alpha2[0], + request->alpha2[1]); + + memcpy(priv->country_code, request->alpha2, sizeof(request->alpha2)); + if (lbs_iface_active(priv)) + lbs_set_11d_domain_info(priv); + + lbs_deb_leave(LBS_DEB_CFG80211); +} + +/* + * This function get's called after lbs_setup_firmware() determined the + * firmware capabities. So we can setup the wiphy according to our + * hardware/firmware. + */ +int lbs_cfg_register(struct lbs_private *priv) +{ + struct wireless_dev *wdev = priv->wdev; + int ret; + + lbs_deb_enter(LBS_DEB_CFG80211); + + wdev->wiphy->max_scan_ssids = 1; + wdev->wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + + wdev->wiphy->interface_modes = + BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC); + if (lbs_rtap_supported(priv)) + wdev->wiphy->interface_modes |= BIT(NL80211_IFTYPE_MONITOR); + if (lbs_mesh_activated(priv)) + wdev->wiphy->interface_modes |= BIT(NL80211_IFTYPE_MESH_POINT); + + wdev->wiphy->bands[IEEE80211_BAND_2GHZ] = &lbs_band_2ghz; + + /* + * We could check priv->fwcapinfo && FW_CAPINFO_WPA, but I have + * never seen a firmware without WPA + */ + wdev->wiphy->cipher_suites = cipher_suites; + wdev->wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites); + wdev->wiphy->reg_notifier = lbs_reg_notifier; + + ret = wiphy_register(wdev->wiphy); + if (ret < 0) + pr_err("cannot register wiphy device\n"); + + priv->wiphy_registered = true; + + ret = register_netdev(priv->dev); + if (ret) + pr_err("cannot register network device\n"); + + INIT_DELAYED_WORK(&priv->scan_work, lbs_scan_worker); + + lbs_cfg_set_regulatory_hint(priv); + + lbs_deb_leave_args(LBS_DEB_CFG80211, "ret %d", ret); + return ret; +} + +void lbs_scan_deinit(struct lbs_private *priv) +{ + lbs_deb_enter(LBS_DEB_CFG80211); + cancel_delayed_work_sync(&priv->scan_work); +} + + +void lbs_cfg_free(struct lbs_private *priv) +{ + struct wireless_dev *wdev = priv->wdev; + + lbs_deb_enter(LBS_DEB_CFG80211); + + if (!wdev) + return; + + if (priv->wiphy_registered) + wiphy_unregister(wdev->wiphy); + + if (wdev->wiphy) + wiphy_free(wdev->wiphy); + + kfree(wdev); +} diff --git a/drivers/net/wireless/libertas/cfg.h b/drivers/net/wireless/libertas/cfg.h new file mode 100644 index 0000000..10995f5 --- /dev/null +++ b/drivers/net/wireless/libertas/cfg.h @@ -0,0 +1,20 @@ +#ifndef __LBS_CFG80211_H__ +#define __LBS_CFG80211_H__ + +struct device; +struct lbs_private; +struct regulatory_request; +struct wiphy; + +struct wireless_dev *lbs_cfg_alloc(struct device *dev); +int lbs_cfg_register(struct lbs_private *priv); +void lbs_cfg_free(struct lbs_private *priv); + +void lbs_send_disconnect_notification(struct lbs_private *priv); +void lbs_send_mic_failureevent(struct lbs_private *priv, u32 event); + +void lbs_scan_done(struct lbs_private *priv); +void lbs_scan_deinit(struct lbs_private *priv); +int lbs_disconnect(struct lbs_private *priv, u16 reason); + +#endif diff --git a/drivers/net/wireless/libertas/cmd.c b/drivers/net/wireless/libertas/cmd.c new file mode 100644 index 0000000..aaa2973 --- /dev/null +++ b/drivers/net/wireless/libertas/cmd.c @@ -0,0 +1,1724 @@ +/* + * This file contains the handling of command. + * It prepares command and sends it to firmware when it is ready. + */ + +#include +#include +#include +#include +#include +#include + +#include "decl.h" +#include "cfg.h" +#include "cmd.h" + +#define CAL_NF(nf) ((s32)(-(s32)(nf))) +#define CAL_RSSI(snr, nf) ((s32)((s32)(snr) + CAL_NF(nf))) + +/** + * lbs_cmd_copyback - Simple callback that copies response back into command + * + * @priv: A pointer to &struct lbs_private structure + * @extra: A pointer to the original command structure for which + * 'resp' is a response + * @resp: A pointer to the command response + * + * returns: 0 on success, error on failure + */ +int lbs_cmd_copyback(struct lbs_private *priv, unsigned long extra, + struct cmd_header *resp) +{ + struct cmd_header *buf = (void *)extra; + uint16_t copy_len; + + copy_len = min(le16_to_cpu(buf->size), le16_to_cpu(resp->size)); + memcpy(buf, resp, copy_len); + return 0; +} +EXPORT_SYMBOL_GPL(lbs_cmd_copyback); + +/** + * lbs_cmd_async_callback - Simple callback that ignores the result. + * Use this if you just want to send a command to the hardware, but don't + * care for the result. + * + * @priv: ignored + * @extra: ignored + * @resp: ignored + * + * returns: 0 for success + */ +static int lbs_cmd_async_callback(struct lbs_private *priv, unsigned long extra, + struct cmd_header *resp) +{ + return 0; +} + + +/** + * is_command_allowed_in_ps - tests if a command is allowed in Power Save mode + * + * @cmd: the command ID + * + * returns: 1 if allowed, 0 if not allowed + */ +static u8 is_command_allowed_in_ps(u16 cmd) +{ + switch (cmd) { + case CMD_802_11_RSSI: + return 1; + case CMD_802_11_HOST_SLEEP_CFG: + return 1; + default: + break; + } + return 0; +} + +/** + * lbs_update_hw_spec - Updates the hardware details like MAC address + * and regulatory region + * + * @priv: A pointer to &struct lbs_private structure + * + * returns: 0 on success, error on failure + */ +int lbs_update_hw_spec(struct lbs_private *priv) +{ + struct cmd_ds_get_hw_spec cmd; + int ret = -1; + u32 i; + + lbs_deb_enter(LBS_DEB_CMD); + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + memcpy(cmd.permanentaddr, priv->current_addr, ETH_ALEN); + ret = lbs_cmd_with_response(priv, CMD_GET_HW_SPEC, &cmd); + if (ret) + goto out; + + priv->fwcapinfo = le32_to_cpu(cmd.fwcapinfo); + + /* The firmware release is in an interesting format: the patch + * level is in the most significant nibble ... so fix that: */ + priv->fwrelease = le32_to_cpu(cmd.fwrelease); + priv->fwrelease = (priv->fwrelease << 8) | + (priv->fwrelease >> 24 & 0xff); + + /* Some firmware capabilities: + * CF card firmware 5.0.16p0: cap 0x00000303 + * USB dongle firmware 5.110.17p2: cap 0x00000303 + */ + netdev_info(priv->dev, "%pM, fw %u.%u.%up%u, cap 0x%08x\n", + cmd.permanentaddr, + priv->fwrelease >> 24 & 0xff, + priv->fwrelease >> 16 & 0xff, + priv->fwrelease >> 8 & 0xff, + priv->fwrelease & 0xff, + priv->fwcapinfo); + lbs_deb_cmd("GET_HW_SPEC: hardware interface 0x%x, hardware spec 0x%04x\n", + cmd.hwifversion, cmd.version); + + /* Clamp region code to 8-bit since FW spec indicates that it should + * only ever be 8-bit, even though the field size is 16-bit. Some firmware + * returns non-zero high 8 bits here. + * + * Firmware version 4.0.102 used in CF8381 has region code shifted. We + * need to check for this problem and handle it properly. + */ + if (MRVL_FW_MAJOR_REV(priv->fwrelease) == MRVL_FW_V4) + priv->regioncode = (le16_to_cpu(cmd.regioncode) >> 8) & 0xFF; + else + priv->regioncode = le16_to_cpu(cmd.regioncode) & 0xFF; + + for (i = 0; i < MRVDRV_MAX_REGION_CODE; i++) { + /* use the region code to search for the index */ + if (priv->regioncode == lbs_region_code_to_index[i]) + break; + } + + /* if it's unidentified region code, use the default (USA) */ + if (i >= MRVDRV_MAX_REGION_CODE) { + priv->regioncode = 0x10; + netdev_info(priv->dev, + "unidentified region code; using the default (USA)\n"); + } + + if (priv->current_addr[0] == 0xff) + memmove(priv->current_addr, cmd.permanentaddr, ETH_ALEN); + + if (!priv->copied_hwaddr) { + memcpy(priv->dev->dev_addr, priv->current_addr, ETH_ALEN); + if (priv->mesh_dev) + memcpy(priv->mesh_dev->dev_addr, + priv->current_addr, ETH_ALEN); + priv->copied_hwaddr = 1; + } + +out: + lbs_deb_leave(LBS_DEB_CMD); + return ret; +} + +static int lbs_ret_host_sleep_cfg(struct lbs_private *priv, unsigned long dummy, + struct cmd_header *resp) +{ + lbs_deb_enter(LBS_DEB_CMD); + if (priv->is_host_sleep_activated) { + priv->is_host_sleep_configured = 0; + if (priv->psstate == PS_STATE_FULL_POWER) { + priv->is_host_sleep_activated = 0; + wake_up_interruptible(&priv->host_sleep_q); + } + } else { + priv->is_host_sleep_configured = 1; + } + lbs_deb_leave(LBS_DEB_CMD); + return 0; +} + +int lbs_host_sleep_cfg(struct lbs_private *priv, uint32_t criteria, + struct wol_config *p_wol_config) +{ + struct cmd_ds_host_sleep cmd_config; + int ret; + + /* + * Certain firmware versions do not support EHS_REMOVE_WAKEUP command + * and the card will return a failure. Since we need to be + * able to reset the mask, in those cases we set a 0 mask instead. + */ + if (criteria == EHS_REMOVE_WAKEUP && !priv->ehs_remove_supported) + criteria = 0; + + cmd_config.hdr.size = cpu_to_le16(sizeof(cmd_config)); + cmd_config.criteria = cpu_to_le32(criteria); + cmd_config.gpio = priv->wol_gpio; + cmd_config.gap = priv->wol_gap; + + if (p_wol_config != NULL) + memcpy((uint8_t *)&cmd_config.wol_conf, (uint8_t *)p_wol_config, + sizeof(struct wol_config)); + else + cmd_config.wol_conf.action = CMD_ACT_ACTION_NONE; + + ret = __lbs_cmd(priv, CMD_802_11_HOST_SLEEP_CFG, &cmd_config.hdr, + le16_to_cpu(cmd_config.hdr.size), + lbs_ret_host_sleep_cfg, 0); + if (!ret) { + if (p_wol_config) + memcpy((uint8_t *) p_wol_config, + (uint8_t *)&cmd_config.wol_conf, + sizeof(struct wol_config)); + } else { + netdev_info(priv->dev, "HOST_SLEEP_CFG failed %d\n", ret); + } + + return ret; +} +EXPORT_SYMBOL_GPL(lbs_host_sleep_cfg); + +/** + * lbs_set_ps_mode - Sets the Power Save mode + * + * @priv: A pointer to &struct lbs_private structure + * @cmd_action: The Power Save operation (PS_MODE_ACTION_ENTER_PS or + * PS_MODE_ACTION_EXIT_PS) + * @block: Whether to block on a response or not + * + * returns: 0 on success, error on failure + */ +int lbs_set_ps_mode(struct lbs_private *priv, u16 cmd_action, bool block) +{ + struct cmd_ds_802_11_ps_mode cmd; + int ret = 0; + + lbs_deb_enter(LBS_DEB_CMD); + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(cmd_action); + + if (cmd_action == PS_MODE_ACTION_ENTER_PS) { + lbs_deb_cmd("PS_MODE: action ENTER_PS\n"); + cmd.multipledtim = cpu_to_le16(1); /* Default DTIM multiple */ + } else if (cmd_action == PS_MODE_ACTION_EXIT_PS) { + lbs_deb_cmd("PS_MODE: action EXIT_PS\n"); + } else { + /* We don't handle CONFIRM_SLEEP here because it needs to + * be fastpathed to the firmware. + */ + lbs_deb_cmd("PS_MODE: unknown action 0x%X\n", cmd_action); + ret = -EOPNOTSUPP; + goto out; + } + + if (block) + ret = lbs_cmd_with_response(priv, CMD_802_11_PS_MODE, &cmd); + else + lbs_cmd_async(priv, CMD_802_11_PS_MODE, &cmd.hdr, sizeof (cmd)); + +out: + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +int lbs_cmd_802_11_sleep_params(struct lbs_private *priv, uint16_t cmd_action, + struct sleep_params *sp) +{ + struct cmd_ds_802_11_sleep_params cmd; + int ret; + + lbs_deb_enter(LBS_DEB_CMD); + + if (cmd_action == CMD_ACT_GET) { + memset(&cmd, 0, sizeof(cmd)); + } else { + cmd.error = cpu_to_le16(sp->sp_error); + cmd.offset = cpu_to_le16(sp->sp_offset); + cmd.stabletime = cpu_to_le16(sp->sp_stabletime); + cmd.calcontrol = sp->sp_calcontrol; + cmd.externalsleepclk = sp->sp_extsleepclk; + cmd.reserved = cpu_to_le16(sp->sp_reserved); + } + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(cmd_action); + + ret = lbs_cmd_with_response(priv, CMD_802_11_SLEEP_PARAMS, &cmd); + + if (!ret) { + lbs_deb_cmd("error 0x%x, offset 0x%x, stabletime 0x%x, " + "calcontrol 0x%x extsleepclk 0x%x\n", + le16_to_cpu(cmd.error), le16_to_cpu(cmd.offset), + le16_to_cpu(cmd.stabletime), cmd.calcontrol, + cmd.externalsleepclk); + + sp->sp_error = le16_to_cpu(cmd.error); + sp->sp_offset = le16_to_cpu(cmd.offset); + sp->sp_stabletime = le16_to_cpu(cmd.stabletime); + sp->sp_calcontrol = cmd.calcontrol; + sp->sp_extsleepclk = cmd.externalsleepclk; + sp->sp_reserved = le16_to_cpu(cmd.reserved); + } + + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return 0; +} + +static int lbs_wait_for_ds_awake(struct lbs_private *priv) +{ + int ret = 0; + + lbs_deb_enter(LBS_DEB_CMD); + + if (priv->is_deep_sleep) { + if (!wait_event_interruptible_timeout(priv->ds_awake_q, + !priv->is_deep_sleep, (10 * HZ))) { + netdev_err(priv->dev, "ds_awake_q: timer expired\n"); + ret = -1; + } + } + + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +int lbs_set_deep_sleep(struct lbs_private *priv, int deep_sleep) +{ + int ret = 0; + + lbs_deb_enter(LBS_DEB_CMD); + + if (deep_sleep) { + if (priv->is_deep_sleep != 1) { + lbs_deb_cmd("deep sleep: sleep\n"); + BUG_ON(!priv->enter_deep_sleep); + ret = priv->enter_deep_sleep(priv); + if (!ret) { + netif_stop_queue(priv->dev); + netif_carrier_off(priv->dev); + } + } else { + netdev_err(priv->dev, "deep sleep: already enabled\n"); + } + } else { + if (priv->is_deep_sleep) { + lbs_deb_cmd("deep sleep: wakeup\n"); + BUG_ON(!priv->exit_deep_sleep); + ret = priv->exit_deep_sleep(priv); + if (!ret) { + ret = lbs_wait_for_ds_awake(priv); + if (ret) + netdev_err(priv->dev, + "deep sleep: wakeup failed\n"); + } + } + } + + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +static int lbs_ret_host_sleep_activate(struct lbs_private *priv, + unsigned long dummy, + struct cmd_header *cmd) +{ + lbs_deb_enter(LBS_DEB_FW); + priv->is_host_sleep_activated = 1; + wake_up_interruptible(&priv->host_sleep_q); + lbs_deb_leave(LBS_DEB_FW); + return 0; +} + +int lbs_set_host_sleep(struct lbs_private *priv, int host_sleep) +{ + struct cmd_header cmd; + int ret = 0; + uint32_t criteria = EHS_REMOVE_WAKEUP; + + lbs_deb_enter(LBS_DEB_CMD); + + if (host_sleep) { + if (priv->is_host_sleep_activated != 1) { + memset(&cmd, 0, sizeof(cmd)); + ret = lbs_host_sleep_cfg(priv, priv->wol_criteria, + (struct wol_config *)NULL); + if (ret) { + netdev_info(priv->dev, + "Host sleep configuration failed: %d\n", + ret); + return ret; + } + if (priv->psstate == PS_STATE_FULL_POWER) { + ret = __lbs_cmd(priv, + CMD_802_11_HOST_SLEEP_ACTIVATE, + &cmd, + sizeof(cmd), + lbs_ret_host_sleep_activate, 0); + if (ret) + netdev_info(priv->dev, + "HOST_SLEEP_ACTIVATE failed: %d\n", + ret); + } + + if (!wait_event_interruptible_timeout( + priv->host_sleep_q, + priv->is_host_sleep_activated, + (10 * HZ))) { + netdev_err(priv->dev, + "host_sleep_q: timer expired\n"); + ret = -1; + } + } else { + netdev_err(priv->dev, "host sleep: already enabled\n"); + } + } else { + if (priv->is_host_sleep_activated) + ret = lbs_host_sleep_cfg(priv, criteria, + (struct wol_config *)NULL); + } + + return ret; +} + +/** + * lbs_set_snmp_mib - Set an SNMP MIB value + * + * @priv: A pointer to &struct lbs_private structure + * @oid: The OID to set in the firmware + * @val: Value to set the OID to + * + * returns: 0 on success, error on failure + */ +int lbs_set_snmp_mib(struct lbs_private *priv, u32 oid, u16 val) +{ + struct cmd_ds_802_11_snmp_mib cmd; + int ret; + + lbs_deb_enter(LBS_DEB_CMD); + + memset(&cmd, 0, sizeof (cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_SET); + cmd.oid = cpu_to_le16((u16) oid); + + switch (oid) { + case SNMP_MIB_OID_BSS_TYPE: + cmd.bufsize = cpu_to_le16(sizeof(u8)); + cmd.value[0] = val; + break; + case SNMP_MIB_OID_11D_ENABLE: + case SNMP_MIB_OID_FRAG_THRESHOLD: + case SNMP_MIB_OID_RTS_THRESHOLD: + case SNMP_MIB_OID_SHORT_RETRY_LIMIT: + case SNMP_MIB_OID_LONG_RETRY_LIMIT: + cmd.bufsize = cpu_to_le16(sizeof(u16)); + *((__le16 *)(&cmd.value)) = cpu_to_le16(val); + break; + default: + lbs_deb_cmd("SNMP_CMD: (set) unhandled OID 0x%x\n", oid); + ret = -EINVAL; + goto out; + } + + lbs_deb_cmd("SNMP_CMD: (set) oid 0x%x, oid size 0x%x, value 0x%x\n", + le16_to_cpu(cmd.oid), le16_to_cpu(cmd.bufsize), val); + + ret = lbs_cmd_with_response(priv, CMD_802_11_SNMP_MIB, &cmd); + +out: + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +/** + * lbs_get_snmp_mib - Get an SNMP MIB value + * + * @priv: A pointer to &struct lbs_private structure + * @oid: The OID to retrieve from the firmware + * @out_val: Location for the returned value + * + * returns: 0 on success, error on failure + */ +int lbs_get_snmp_mib(struct lbs_private *priv, u32 oid, u16 *out_val) +{ + struct cmd_ds_802_11_snmp_mib cmd; + int ret; + + lbs_deb_enter(LBS_DEB_CMD); + + memset(&cmd, 0, sizeof (cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_GET); + cmd.oid = cpu_to_le16(oid); + + ret = lbs_cmd_with_response(priv, CMD_802_11_SNMP_MIB, &cmd); + if (ret) + goto out; + + switch (le16_to_cpu(cmd.bufsize)) { + case sizeof(u8): + *out_val = cmd.value[0]; + break; + case sizeof(u16): + *out_val = le16_to_cpu(*((__le16 *)(&cmd.value))); + break; + default: + lbs_deb_cmd("SNMP_CMD: (get) unhandled OID 0x%x size %d\n", + oid, le16_to_cpu(cmd.bufsize)); + break; + } + +out: + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +/** + * lbs_get_tx_power - Get the min, max, and current TX power + * + * @priv: A pointer to &struct lbs_private structure + * @curlevel: Current power level in dBm + * @minlevel: Minimum supported power level in dBm (optional) + * @maxlevel: Maximum supported power level in dBm (optional) + * + * returns: 0 on success, error on failure + */ +int lbs_get_tx_power(struct lbs_private *priv, s16 *curlevel, s16 *minlevel, + s16 *maxlevel) +{ + struct cmd_ds_802_11_rf_tx_power cmd; + int ret; + + lbs_deb_enter(LBS_DEB_CMD); + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_GET); + + ret = lbs_cmd_with_response(priv, CMD_802_11_RF_TX_POWER, &cmd); + if (ret == 0) { + *curlevel = le16_to_cpu(cmd.curlevel); + if (minlevel) + *minlevel = cmd.minlevel; + if (maxlevel) + *maxlevel = cmd.maxlevel; + } + + lbs_deb_leave(LBS_DEB_CMD); + return ret; +} + +/** + * lbs_set_tx_power - Set the TX power + * + * @priv: A pointer to &struct lbs_private structure + * @dbm: The desired power level in dBm + * + * returns: 0 on success, error on failure + */ +int lbs_set_tx_power(struct lbs_private *priv, s16 dbm) +{ + struct cmd_ds_802_11_rf_tx_power cmd; + int ret; + + lbs_deb_enter(LBS_DEB_CMD); + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_SET); + cmd.curlevel = cpu_to_le16(dbm); + + lbs_deb_cmd("SET_RF_TX_POWER: %d dBm\n", dbm); + + ret = lbs_cmd_with_response(priv, CMD_802_11_RF_TX_POWER, &cmd); + + lbs_deb_leave(LBS_DEB_CMD); + return ret; +} + +/** + * lbs_set_monitor_mode - Enable or disable monitor mode + * (only implemented on OLPC usb8388 FW) + * + * @priv: A pointer to &struct lbs_private structure + * @enable: 1 to enable monitor mode, 0 to disable + * + * returns: 0 on success, error on failure + */ +int lbs_set_monitor_mode(struct lbs_private *priv, int enable) +{ + struct cmd_ds_802_11_monitor_mode cmd; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_SET); + if (enable) + cmd.mode = cpu_to_le16(0x1); + + lbs_deb_cmd("SET_MONITOR_MODE: %d\n", enable); + + ret = lbs_cmd_with_response(priv, CMD_802_11_MONITOR_MODE, &cmd); + if (ret == 0) { + priv->dev->type = enable ? ARPHRD_IEEE80211_RADIOTAP : + ARPHRD_ETHER; + } + + lbs_deb_leave(LBS_DEB_CMD); + return ret; +} + +/** + * lbs_get_channel - Get the radio channel + * + * @priv: A pointer to &struct lbs_private structure + * + * returns: The channel on success, error on failure + */ +static int lbs_get_channel(struct lbs_private *priv) +{ + struct cmd_ds_802_11_rf_channel cmd; + int ret = 0; + + lbs_deb_enter(LBS_DEB_CMD); + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_OPT_802_11_RF_CHANNEL_GET); + + ret = lbs_cmd_with_response(priv, CMD_802_11_RF_CHANNEL, &cmd); + if (ret) + goto out; + + ret = le16_to_cpu(cmd.channel); + lbs_deb_cmd("current radio channel is %d\n", ret); + +out: + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +int lbs_update_channel(struct lbs_private *priv) +{ + int ret; + + /* the channel in f/w could be out of sync; get the current channel */ + lbs_deb_enter(LBS_DEB_ASSOC); + + ret = lbs_get_channel(priv); + if (ret > 0) { + priv->channel = ret; + ret = 0; + } + lbs_deb_leave_args(LBS_DEB_ASSOC, "ret %d", ret); + return ret; +} + +/** + * lbs_set_channel - Set the radio channel + * + * @priv: A pointer to &struct lbs_private structure + * @channel: The desired channel, or 0 to clear a locked channel + * + * returns: 0 on success, error on failure + */ +int lbs_set_channel(struct lbs_private *priv, u8 channel) +{ + struct cmd_ds_802_11_rf_channel cmd; +#ifdef DEBUG + u8 old_channel = priv->channel; +#endif + int ret = 0; + + lbs_deb_enter(LBS_DEB_CMD); + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_OPT_802_11_RF_CHANNEL_SET); + cmd.channel = cpu_to_le16(channel); + + ret = lbs_cmd_with_response(priv, CMD_802_11_RF_CHANNEL, &cmd); + if (ret) + goto out; + + priv->channel = (uint8_t) le16_to_cpu(cmd.channel); + lbs_deb_cmd("channel switch from %d to %d\n", old_channel, + priv->channel); + +out: + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +/** + * lbs_get_rssi - Get current RSSI and noise floor + * + * @priv: A pointer to &struct lbs_private structure + * @rssi: On successful return, signal level in mBm + * @nf: On successful return, Noise floor + * + * returns: The channel on success, error on failure + */ +int lbs_get_rssi(struct lbs_private *priv, s8 *rssi, s8 *nf) +{ + struct cmd_ds_802_11_rssi cmd; + int ret = 0; + + lbs_deb_enter(LBS_DEB_CMD); + + BUG_ON(rssi == NULL); + BUG_ON(nf == NULL); + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + /* Average SNR over last 8 beacons */ + cmd.n_or_snr = cpu_to_le16(8); + + ret = lbs_cmd_with_response(priv, CMD_802_11_RSSI, &cmd); + if (ret == 0) { + *nf = CAL_NF(le16_to_cpu(cmd.nf)); + *rssi = CAL_RSSI(le16_to_cpu(cmd.n_or_snr), le16_to_cpu(cmd.nf)); + } + + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +/** + * lbs_set_11d_domain_info - Send regulatory and 802.11d domain information + * to the firmware + * + * @priv: pointer to &struct lbs_private + * + * returns: 0 on success, error code on failure +*/ +int lbs_set_11d_domain_info(struct lbs_private *priv) +{ + struct wiphy *wiphy = priv->wdev->wiphy; + struct ieee80211_supported_band **bands = wiphy->bands; + struct cmd_ds_802_11d_domain_info cmd; + struct mrvl_ie_domain_param_set *domain = &cmd.domain; + struct ieee80211_country_ie_triplet *t; + enum ieee80211_band band; + struct ieee80211_channel *ch; + u8 num_triplet = 0; + u8 num_parsed_chan = 0; + u8 first_channel = 0, next_chan = 0, max_pwr = 0; + u8 i, flag = 0; + size_t triplet_size; + int ret = 0; + + lbs_deb_enter(LBS_DEB_11D); + if (!priv->country_code[0]) + goto out; + + memset(&cmd, 0, sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_SET); + + lbs_deb_11d("Setting country code '%c%c'\n", + priv->country_code[0], priv->country_code[1]); + + domain->header.type = cpu_to_le16(TLV_TYPE_DOMAIN); + + /* Set country code */ + domain->country_code[0] = priv->country_code[0]; + domain->country_code[1] = priv->country_code[1]; + domain->country_code[2] = ' '; + + /* Now set up the channel triplets; firmware is somewhat picky here + * and doesn't validate channel numbers and spans; hence it would + * interpret a triplet of (36, 4, 20) as channels 36, 37, 38, 39. Since + * the last 3 aren't valid channels, the driver is responsible for + * splitting that up into 4 triplet pairs of (36, 1, 20) + (40, 1, 20) + * etc. + */ + for (band = 0; + (band < IEEE80211_NUM_BANDS) && (num_triplet < MAX_11D_TRIPLETS); + band++) { + + if (!bands[band]) + continue; + + for (i = 0; + (i < bands[band]->n_channels) && (num_triplet < MAX_11D_TRIPLETS); + i++) { + ch = &bands[band]->channels[i]; + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + + if (!flag) { + flag = 1; + next_chan = first_channel = (u32) ch->hw_value; + max_pwr = ch->max_power; + num_parsed_chan = 1; + continue; + } + + if ((ch->hw_value == next_chan + 1) && + (ch->max_power == max_pwr)) { + /* Consolidate adjacent channels */ + next_chan++; + num_parsed_chan++; + } else { + /* Add this triplet */ + lbs_deb_11d("11D triplet (%d, %d, %d)\n", + first_channel, num_parsed_chan, + max_pwr); + t = &domain->triplet[num_triplet]; + t->chans.first_channel = first_channel; + t->chans.num_channels = num_parsed_chan; + t->chans.max_power = max_pwr; + num_triplet++; + flag = 0; + } + } + + if (flag) { + /* Add last triplet */ + lbs_deb_11d("11D triplet (%d, %d, %d)\n", first_channel, + num_parsed_chan, max_pwr); + t = &domain->triplet[num_triplet]; + t->chans.first_channel = first_channel; + t->chans.num_channels = num_parsed_chan; + t->chans.max_power = max_pwr; + num_triplet++; + } + } + + lbs_deb_11d("# triplets %d\n", num_triplet); + + /* Set command header sizes */ + triplet_size = num_triplet * sizeof(struct ieee80211_country_ie_triplet); + domain->header.len = cpu_to_le16(sizeof(domain->country_code) + + triplet_size); + + lbs_deb_hex(LBS_DEB_11D, "802.11D domain param set", + (u8 *) &cmd.domain.country_code, + le16_to_cpu(domain->header.len)); + + cmd.hdr.size = cpu_to_le16(sizeof(cmd.hdr) + + sizeof(cmd.action) + + sizeof(cmd.domain.header) + + sizeof(cmd.domain.country_code) + + triplet_size); + + ret = lbs_cmd_with_response(priv, CMD_802_11D_DOMAIN_INFO, &cmd); + +out: + lbs_deb_leave_args(LBS_DEB_11D, "ret %d", ret); + return ret; +} + +/** + * lbs_get_reg - Read a MAC, Baseband, or RF register + * + * @priv: pointer to &struct lbs_private + * @reg: register command, one of CMD_MAC_REG_ACCESS, + * CMD_BBP_REG_ACCESS, or CMD_RF_REG_ACCESS + * @offset: byte offset of the register to get + * @value: on success, the value of the register at 'offset' + * + * returns: 0 on success, error code on failure +*/ +int lbs_get_reg(struct lbs_private *priv, u16 reg, u16 offset, u32 *value) +{ + struct cmd_ds_reg_access cmd; + int ret = 0; + + lbs_deb_enter(LBS_DEB_CMD); + + BUG_ON(value == NULL); + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_GET); + cmd.offset = cpu_to_le16(offset); + + if (reg != CMD_MAC_REG_ACCESS && + reg != CMD_BBP_REG_ACCESS && + reg != CMD_RF_REG_ACCESS) { + ret = -EINVAL; + goto out; + } + + ret = lbs_cmd_with_response(priv, reg, &cmd); + if (!ret) { + if (reg == CMD_BBP_REG_ACCESS || reg == CMD_RF_REG_ACCESS) + *value = cmd.value.bbp_rf; + else if (reg == CMD_MAC_REG_ACCESS) + *value = le32_to_cpu(cmd.value.mac); + } + +out: + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +/** + * lbs_set_reg - Write a MAC, Baseband, or RF register + * + * @priv: pointer to &struct lbs_private + * @reg: register command, one of CMD_MAC_REG_ACCESS, + * CMD_BBP_REG_ACCESS, or CMD_RF_REG_ACCESS + * @offset: byte offset of the register to set + * @value: the value to write to the register at 'offset' + * + * returns: 0 on success, error code on failure +*/ +int lbs_set_reg(struct lbs_private *priv, u16 reg, u16 offset, u32 value) +{ + struct cmd_ds_reg_access cmd; + int ret = 0; + + lbs_deb_enter(LBS_DEB_CMD); + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_SET); + cmd.offset = cpu_to_le16(offset); + + if (reg == CMD_BBP_REG_ACCESS || reg == CMD_RF_REG_ACCESS) + cmd.value.bbp_rf = (u8) (value & 0xFF); + else if (reg == CMD_MAC_REG_ACCESS) + cmd.value.mac = cpu_to_le32(value); + else { + ret = -EINVAL; + goto out; + } + + ret = lbs_cmd_with_response(priv, reg, &cmd); + +out: + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +static void lbs_queue_cmd(struct lbs_private *priv, + struct cmd_ctrl_node *cmdnode) +{ + unsigned long flags; + int addtail = 1; + + lbs_deb_enter(LBS_DEB_HOST); + + if (!cmdnode) { + lbs_deb_host("QUEUE_CMD: cmdnode is NULL\n"); + goto done; + } + if (!cmdnode->cmdbuf->size) { + lbs_deb_host("DNLD_CMD: cmd size is zero\n"); + goto done; + } + cmdnode->result = 0; + + /* Exit_PS command needs to be queued in the header always. */ + if (le16_to_cpu(cmdnode->cmdbuf->command) == CMD_802_11_PS_MODE) { + struct cmd_ds_802_11_ps_mode *psm = (void *) &cmdnode->cmdbuf; + + if (psm->action == cpu_to_le16(PS_MODE_ACTION_EXIT_PS)) { + if (priv->psstate != PS_STATE_FULL_POWER) + addtail = 0; + } + } + + if (le16_to_cpu(cmdnode->cmdbuf->command) == CMD_802_11_WAKEUP_CONFIRM) + addtail = 0; + + spin_lock_irqsave(&priv->driver_lock, flags); + + if (addtail) + list_add_tail(&cmdnode->list, &priv->cmdpendingq); + else + list_add(&cmdnode->list, &priv->cmdpendingq); + + spin_unlock_irqrestore(&priv->driver_lock, flags); + + lbs_deb_host("QUEUE_CMD: inserted command 0x%04x into cmdpendingq\n", + le16_to_cpu(cmdnode->cmdbuf->command)); + +done: + lbs_deb_leave(LBS_DEB_HOST); +} + +static void lbs_submit_command(struct lbs_private *priv, + struct cmd_ctrl_node *cmdnode) +{ + unsigned long flags; + struct cmd_header *cmd; + uint16_t cmdsize; + uint16_t command; + int timeo = 3 * HZ; + int ret; + + lbs_deb_enter(LBS_DEB_HOST); + + cmd = cmdnode->cmdbuf; + + spin_lock_irqsave(&priv->driver_lock, flags); + priv->seqnum++; + cmd->seqnum = cpu_to_le16(priv->seqnum); + priv->cur_cmd = cmdnode; + spin_unlock_irqrestore(&priv->driver_lock, flags); + + cmdsize = le16_to_cpu(cmd->size); + command = le16_to_cpu(cmd->command); + + /* These commands take longer */ + if (command == CMD_802_11_SCAN || command == CMD_802_11_ASSOCIATE) + timeo = 5 * HZ; + + lbs_deb_cmd("DNLD_CMD: command 0x%04x, seq %d, size %d\n", + command, le16_to_cpu(cmd->seqnum), cmdsize); + lbs_deb_hex(LBS_DEB_CMD, "DNLD_CMD", (void *) cmdnode->cmdbuf, cmdsize); + + ret = priv->hw_host_to_card(priv, MVMS_CMD, (u8 *) cmd, cmdsize); + + if (ret) { + netdev_info(priv->dev, "DNLD_CMD: hw_host_to_card failed: %d\n", + ret); + /* Reset dnld state machine, report failure */ + priv->dnld_sent = DNLD_RES_RECEIVED; + lbs_complete_command(priv, cmdnode, ret); + } + + if (command == CMD_802_11_DEEP_SLEEP) { + if (priv->is_auto_deep_sleep_enabled) { + priv->wakeup_dev_required = 1; + priv->dnld_sent = 0; + } + priv->is_deep_sleep = 1; + lbs_complete_command(priv, cmdnode, 0); + } else { + /* Setup the timer after transmit command */ + mod_timer(&priv->command_timer, jiffies + timeo); + } + + lbs_deb_leave(LBS_DEB_HOST); +} + +/* + * This function inserts command node to cmdfreeq + * after cleans it. Requires priv->driver_lock held. + */ +static void __lbs_cleanup_and_insert_cmd(struct lbs_private *priv, + struct cmd_ctrl_node *cmdnode) +{ + lbs_deb_enter(LBS_DEB_HOST); + + if (!cmdnode) + goto out; + + cmdnode->callback = NULL; + cmdnode->callback_arg = 0; + + memset(cmdnode->cmdbuf, 0, LBS_CMD_BUFFER_SIZE); + + list_add_tail(&cmdnode->list, &priv->cmdfreeq); + out: + lbs_deb_leave(LBS_DEB_HOST); +} + +static void lbs_cleanup_and_insert_cmd(struct lbs_private *priv, + struct cmd_ctrl_node *ptempcmd) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->driver_lock, flags); + __lbs_cleanup_and_insert_cmd(priv, ptempcmd); + spin_unlock_irqrestore(&priv->driver_lock, flags); +} + +void __lbs_complete_command(struct lbs_private *priv, struct cmd_ctrl_node *cmd, + int result) +{ + /* + * Normally, commands are removed from cmdpendingq before being + * submitted. However, we can arrive here on alternative codepaths + * where the command is still pending. Make sure the command really + * isn't part of a list at this point. + */ + list_del_init(&cmd->list); + + cmd->result = result; + cmd->cmdwaitqwoken = 1; + wake_up(&cmd->cmdwait_q); + + if (!cmd->callback || cmd->callback == lbs_cmd_async_callback) + __lbs_cleanup_and_insert_cmd(priv, cmd); + priv->cur_cmd = NULL; + wake_up(&priv->waitq); +} + +void lbs_complete_command(struct lbs_private *priv, struct cmd_ctrl_node *cmd, + int result) +{ + unsigned long flags; + spin_lock_irqsave(&priv->driver_lock, flags); + __lbs_complete_command(priv, cmd, result); + spin_unlock_irqrestore(&priv->driver_lock, flags); +} + +int lbs_set_radio(struct lbs_private *priv, u8 preamble, u8 radio_on) +{ + struct cmd_ds_802_11_radio_control cmd; + int ret = -EINVAL; + + lbs_deb_enter(LBS_DEB_CMD); + + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_SET); + + /* Only v8 and below support setting the preamble */ + if (priv->fwrelease < 0x09000000) { + switch (preamble) { + case RADIO_PREAMBLE_SHORT: + case RADIO_PREAMBLE_AUTO: + case RADIO_PREAMBLE_LONG: + cmd.control = cpu_to_le16(preamble); + break; + default: + goto out; + } + } + + if (radio_on) + cmd.control |= cpu_to_le16(0x1); + else { + cmd.control &= cpu_to_le16(~0x1); + priv->txpower_cur = 0; + } + + lbs_deb_cmd("RADIO_CONTROL: radio %s, preamble %d\n", + radio_on ? "ON" : "OFF", preamble); + + priv->radio_on = radio_on; + + ret = lbs_cmd_with_response(priv, CMD_802_11_RADIO_CONTROL, &cmd); + +out: + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} + +void lbs_set_mac_control(struct lbs_private *priv) +{ + struct cmd_ds_mac_control cmd; + + lbs_deb_enter(LBS_DEB_CMD); + + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(priv->mac_control); + cmd.reserved = 0; + + lbs_cmd_async(priv, CMD_MAC_CONTROL, &cmd.hdr, sizeof(cmd)); + + lbs_deb_leave(LBS_DEB_CMD); +} + +int lbs_set_mac_control_sync(struct lbs_private *priv) +{ + struct cmd_ds_mac_control cmd; + int ret = 0; + + lbs_deb_enter(LBS_DEB_CMD); + + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(priv->mac_control); + cmd.reserved = 0; + ret = lbs_cmd_with_response(priv, CMD_MAC_CONTROL, &cmd); + + lbs_deb_leave(LBS_DEB_CMD); + return ret; +} + +/** + * lbs_allocate_cmd_buffer - allocates the command buffer and links + * it to command free queue + * + * @priv: A pointer to &struct lbs_private structure + * + * returns: 0 for success or -1 on error + */ +int lbs_allocate_cmd_buffer(struct lbs_private *priv) +{ + int ret = 0; + u32 bufsize; + u32 i; + struct cmd_ctrl_node *cmdarray; + + lbs_deb_enter(LBS_DEB_HOST); + + /* Allocate and initialize the command array */ + bufsize = sizeof(struct cmd_ctrl_node) * LBS_NUM_CMD_BUFFERS; + if (!(cmdarray = kzalloc(bufsize, GFP_KERNEL))) { + lbs_deb_host("ALLOC_CMD_BUF: tempcmd_array is NULL\n"); + ret = -1; + goto done; + } + priv->cmd_array = cmdarray; + + /* Allocate and initialize each command buffer in the command array */ + for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) { + cmdarray[i].cmdbuf = kzalloc(LBS_CMD_BUFFER_SIZE, GFP_KERNEL); + if (!cmdarray[i].cmdbuf) { + lbs_deb_host("ALLOC_CMD_BUF: ptempvirtualaddr is NULL\n"); + ret = -1; + goto done; + } + } + + for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) { + init_waitqueue_head(&cmdarray[i].cmdwait_q); + lbs_cleanup_and_insert_cmd(priv, &cmdarray[i]); + } + ret = 0; + +done: + lbs_deb_leave_args(LBS_DEB_HOST, "ret %d", ret); + return ret; +} + +/** + * lbs_free_cmd_buffer - free the command buffer + * + * @priv: A pointer to &struct lbs_private structure + * + * returns: 0 for success + */ +int lbs_free_cmd_buffer(struct lbs_private *priv) +{ + struct cmd_ctrl_node *cmdarray; + unsigned int i; + + lbs_deb_enter(LBS_DEB_HOST); + + /* need to check if cmd array is allocated or not */ + if (priv->cmd_array == NULL) { + lbs_deb_host("FREE_CMD_BUF: cmd_array is NULL\n"); + goto done; + } + + cmdarray = priv->cmd_array; + + /* Release shared memory buffers */ + for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) { + if (cmdarray[i].cmdbuf) { + kfree(cmdarray[i].cmdbuf); + cmdarray[i].cmdbuf = NULL; + } + } + + /* Release cmd_ctrl_node */ + if (priv->cmd_array) { + kfree(priv->cmd_array); + priv->cmd_array = NULL; + } + +done: + lbs_deb_leave(LBS_DEB_HOST); + return 0; +} + +/** + * lbs_get_free_cmd_node - gets a free command node if available in + * command free queue + * + * @priv: A pointer to &struct lbs_private structure + * + * returns: A pointer to &cmd_ctrl_node structure on success + * or %NULL on error + */ +static struct cmd_ctrl_node *lbs_get_free_cmd_node(struct lbs_private *priv) +{ + struct cmd_ctrl_node *tempnode; + unsigned long flags; + + lbs_deb_enter(LBS_DEB_HOST); + + if (!priv) + return NULL; + + spin_lock_irqsave(&priv->driver_lock, flags); + + if (!list_empty(&priv->cmdfreeq)) { + tempnode = list_first_entry(&priv->cmdfreeq, + struct cmd_ctrl_node, list); + list_del_init(&tempnode->list); + } else { + lbs_deb_host("GET_CMD_NODE: cmd_ctrl_node is not available\n"); + tempnode = NULL; + } + + spin_unlock_irqrestore(&priv->driver_lock, flags); + + lbs_deb_leave(LBS_DEB_HOST); + return tempnode; +} + +/** + * lbs_execute_next_command - execute next command in command + * pending queue. Will put firmware back to PS mode if applicable. + * + * @priv: A pointer to &struct lbs_private structure + * + * returns: 0 on success or -1 on error + */ +int lbs_execute_next_command(struct lbs_private *priv) +{ + struct cmd_ctrl_node *cmdnode = NULL; + struct cmd_header *cmd; + unsigned long flags; + int ret = 0; + + /* Debug group is LBS_DEB_THREAD and not LBS_DEB_HOST, because the + * only caller to us is lbs_thread() and we get even when a + * data packet is received */ + lbs_deb_enter(LBS_DEB_THREAD); + + spin_lock_irqsave(&priv->driver_lock, flags); + + if (priv->cur_cmd) { + netdev_alert(priv->dev, + "EXEC_NEXT_CMD: already processing command!\n"); + spin_unlock_irqrestore(&priv->driver_lock, flags); + ret = -1; + goto done; + } + + if (!list_empty(&priv->cmdpendingq)) { + cmdnode = list_first_entry(&priv->cmdpendingq, + struct cmd_ctrl_node, list); + } + + spin_unlock_irqrestore(&priv->driver_lock, flags); + + if (cmdnode) { + cmd = cmdnode->cmdbuf; + + if (is_command_allowed_in_ps(le16_to_cpu(cmd->command))) { + if ((priv->psstate == PS_STATE_SLEEP) || + (priv->psstate == PS_STATE_PRE_SLEEP)) { + lbs_deb_host( + "EXEC_NEXT_CMD: cannot send cmd 0x%04x in psstate %d\n", + le16_to_cpu(cmd->command), + priv->psstate); + ret = -1; + goto done; + } + lbs_deb_host("EXEC_NEXT_CMD: OK to send command " + "0x%04x in psstate %d\n", + le16_to_cpu(cmd->command), priv->psstate); + } else if (priv->psstate != PS_STATE_FULL_POWER) { + /* + * 1. Non-PS command: + * Queue it. set needtowakeup to TRUE if current state + * is SLEEP, otherwise call send EXIT_PS. + * 2. PS command but not EXIT_PS: + * Ignore it. + * 3. PS command EXIT_PS: + * Set needtowakeup to TRUE if current state is SLEEP, + * otherwise send this command down to firmware + * immediately. + */ + if (cmd->command != cpu_to_le16(CMD_802_11_PS_MODE)) { + /* Prepare to send Exit PS, + * this non PS command will be sent later */ + if ((priv->psstate == PS_STATE_SLEEP) + || (priv->psstate == PS_STATE_PRE_SLEEP) + ) { + /* w/ new scheme, it will not reach here. + since it is blocked in main_thread. */ + priv->needtowakeup = 1; + } else { + lbs_set_ps_mode(priv, + PS_MODE_ACTION_EXIT_PS, + false); + } + + ret = 0; + goto done; + } else { + /* + * PS command. Ignore it if it is not Exit_PS. + * otherwise send it down immediately. + */ + struct cmd_ds_802_11_ps_mode *psm = (void *)&cmd[1]; + + lbs_deb_host( + "EXEC_NEXT_CMD: PS cmd, action 0x%02x\n", + psm->action); + if (psm->action != + cpu_to_le16(PS_MODE_ACTION_EXIT_PS)) { + lbs_deb_host( + "EXEC_NEXT_CMD: ignore ENTER_PS cmd\n"); + lbs_complete_command(priv, cmdnode, 0); + + ret = 0; + goto done; + } + + if ((priv->psstate == PS_STATE_SLEEP) || + (priv->psstate == PS_STATE_PRE_SLEEP)) { + lbs_deb_host( + "EXEC_NEXT_CMD: ignore EXIT_PS cmd in sleep\n"); + lbs_complete_command(priv, cmdnode, 0); + priv->needtowakeup = 1; + + ret = 0; + goto done; + } + + lbs_deb_host( + "EXEC_NEXT_CMD: sending EXIT_PS\n"); + } + } + spin_lock_irqsave(&priv->driver_lock, flags); + list_del_init(&cmdnode->list); + spin_unlock_irqrestore(&priv->driver_lock, flags); + lbs_deb_host("EXEC_NEXT_CMD: sending command 0x%04x\n", + le16_to_cpu(cmd->command)); + lbs_submit_command(priv, cmdnode); + } else { + /* + * check if in power save mode, if yes, put the device back + * to PS mode + */ +#ifdef TODO + /* + * This was the old code for libertas+wext. Someone that + * understands this beast should re-code it in a sane way. + * + * I actually don't understand why this is related to WPA + * and to connection status, shouldn't powering should be + * independ of such things? + */ + if ((priv->psmode != LBS802_11POWERMODECAM) && + (priv->psstate == PS_STATE_FULL_POWER) && + ((priv->connect_status == LBS_CONNECTED) || + lbs_mesh_connected(priv))) { + if (priv->secinfo.WPAenabled || + priv->secinfo.WPA2enabled) { + /* check for valid WPA group keys */ + if (priv->wpa_mcast_key.len || + priv->wpa_unicast_key.len) { + lbs_deb_host( + "EXEC_NEXT_CMD: WPA enabled and GTK_SET" + " go back to PS_SLEEP"); + lbs_set_ps_mode(priv, + PS_MODE_ACTION_ENTER_PS, + false); + } + } else { + lbs_deb_host( + "EXEC_NEXT_CMD: cmdpendingq empty, " + "go back to PS_SLEEP"); + lbs_set_ps_mode(priv, PS_MODE_ACTION_ENTER_PS, + false); + } + } +#endif + } + + ret = 0; +done: + lbs_deb_leave(LBS_DEB_THREAD); + return ret; +} + +static void lbs_send_confirmsleep(struct lbs_private *priv) +{ + unsigned long flags; + int ret; + + lbs_deb_enter(LBS_DEB_HOST); + lbs_deb_hex(LBS_DEB_HOST, "sleep confirm", (u8 *) &confirm_sleep, + sizeof(confirm_sleep)); + + ret = priv->hw_host_to_card(priv, MVMS_CMD, (u8 *) &confirm_sleep, + sizeof(confirm_sleep)); + if (ret) { + netdev_alert(priv->dev, "confirm_sleep failed\n"); + goto out; + } + + spin_lock_irqsave(&priv->driver_lock, flags); + + /* We don't get a response on the sleep-confirmation */ + priv->dnld_sent = DNLD_RES_RECEIVED; + + if (priv->is_host_sleep_configured) { + priv->is_host_sleep_activated = 1; + wake_up_interruptible(&priv->host_sleep_q); + } + + /* If nothing to do, go back to sleep (?) */ + if (!kfifo_len(&priv->event_fifo) && !priv->resp_len[priv->resp_idx]) + priv->psstate = PS_STATE_SLEEP; + + spin_unlock_irqrestore(&priv->driver_lock, flags); + +out: + lbs_deb_leave(LBS_DEB_HOST); +} + +/** + * lbs_ps_confirm_sleep - checks condition and prepares to + * send sleep confirm command to firmware if ok + * + * @priv: A pointer to &struct lbs_private structure + * + * returns: n/a + */ +void lbs_ps_confirm_sleep(struct lbs_private *priv) +{ + unsigned long flags =0; + int allowed = 1; + + lbs_deb_enter(LBS_DEB_HOST); + + spin_lock_irqsave(&priv->driver_lock, flags); + if (priv->dnld_sent) { + allowed = 0; + lbs_deb_host("dnld_sent was set\n"); + } + + /* In-progress command? */ + if (priv->cur_cmd) { + allowed = 0; + lbs_deb_host("cur_cmd was set\n"); + } + + /* Pending events or command responses? */ + if (kfifo_len(&priv->event_fifo) || priv->resp_len[priv->resp_idx]) { + allowed = 0; + lbs_deb_host("pending events or command responses\n"); + } + spin_unlock_irqrestore(&priv->driver_lock, flags); + + if (allowed) { + lbs_deb_host("sending lbs_ps_confirm_sleep\n"); + lbs_send_confirmsleep(priv); + } else { + lbs_deb_host("sleep confirm has been delayed\n"); + } + + lbs_deb_leave(LBS_DEB_HOST); +} + + +/** + * lbs_set_tpc_cfg - Configures the transmission power control functionality + * + * @priv: A pointer to &struct lbs_private structure + * @enable: Transmission power control enable + * @p0: Power level when link quality is good (dBm). + * @p1: Power level when link quality is fair (dBm). + * @p2: Power level when link quality is poor (dBm). + * @usesnr: Use Signal to Noise Ratio in TPC + * + * returns: 0 on success + */ +int lbs_set_tpc_cfg(struct lbs_private *priv, int enable, int8_t p0, int8_t p1, + int8_t p2, int usesnr) +{ + struct cmd_ds_802_11_tpc_cfg cmd; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_SET); + cmd.enable = !!enable; + cmd.usesnr = !!usesnr; + cmd.P0 = p0; + cmd.P1 = p1; + cmd.P2 = p2; + + ret = lbs_cmd_with_response(priv, CMD_802_11_TPC_CFG, &cmd); + + return ret; +} + +/** + * lbs_set_power_adapt_cfg - Configures the power adaptation settings + * + * @priv: A pointer to &struct lbs_private structure + * @enable: Power adaptation enable + * @p0: Power level for 1, 2, 5.5 and 11 Mbps (dBm). + * @p1: Power level for 6, 9, 12, 18, 22, 24 and 36 Mbps (dBm). + * @p2: Power level for 48 and 54 Mbps (dBm). + * + * returns: 0 on Success + */ + +int lbs_set_power_adapt_cfg(struct lbs_private *priv, int enable, int8_t p0, + int8_t p1, int8_t p2) +{ + struct cmd_ds_802_11_pa_cfg cmd; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_SET); + cmd.enable = !!enable; + cmd.P0 = p0; + cmd.P1 = p1; + cmd.P2 = p2; + + ret = lbs_cmd_with_response(priv, CMD_802_11_PA_CFG , &cmd); + + return ret; +} + + +struct cmd_ctrl_node *__lbs_cmd_async(struct lbs_private *priv, + uint16_t command, struct cmd_header *in_cmd, int in_cmd_size, + int (*callback)(struct lbs_private *, unsigned long, struct cmd_header *), + unsigned long callback_arg) +{ + struct cmd_ctrl_node *cmdnode; + + lbs_deb_enter(LBS_DEB_HOST); + + if (priv->surpriseremoved) { + lbs_deb_host("PREP_CMD: card removed\n"); + cmdnode = ERR_PTR(-ENOENT); + goto done; + } + + /* No commands are allowed in Deep Sleep until we toggle the GPIO + * to wake up the card and it has signaled that it's ready. + */ + if (!priv->is_auto_deep_sleep_enabled) { + if (priv->is_deep_sleep) { + lbs_deb_cmd("command not allowed in deep sleep\n"); + cmdnode = ERR_PTR(-EBUSY); + goto done; + } + } + + cmdnode = lbs_get_free_cmd_node(priv); + if (cmdnode == NULL) { + lbs_deb_host("PREP_CMD: cmdnode is NULL\n"); + + /* Wake up main thread to execute next command */ + wake_up(&priv->waitq); + cmdnode = ERR_PTR(-ENOBUFS); + goto done; + } + + cmdnode->callback = callback; + cmdnode->callback_arg = callback_arg; + + /* Copy the incoming command to the buffer */ + memcpy(cmdnode->cmdbuf, in_cmd, in_cmd_size); + + /* Set command, clean result, move to buffer */ + cmdnode->cmdbuf->command = cpu_to_le16(command); + cmdnode->cmdbuf->size = cpu_to_le16(in_cmd_size); + cmdnode->cmdbuf->result = 0; + + lbs_deb_host("PREP_CMD: command 0x%04x\n", command); + + cmdnode->cmdwaitqwoken = 0; + lbs_queue_cmd(priv, cmdnode); + wake_up(&priv->waitq); + + done: + lbs_deb_leave_args(LBS_DEB_HOST, "ret %p", cmdnode); + return cmdnode; +} + +void lbs_cmd_async(struct lbs_private *priv, uint16_t command, + struct cmd_header *in_cmd, int in_cmd_size) +{ + lbs_deb_enter(LBS_DEB_CMD); + __lbs_cmd_async(priv, command, in_cmd, in_cmd_size, + lbs_cmd_async_callback, 0); + lbs_deb_leave(LBS_DEB_CMD); +} + +int __lbs_cmd(struct lbs_private *priv, uint16_t command, + struct cmd_header *in_cmd, int in_cmd_size, + int (*callback)(struct lbs_private *, unsigned long, struct cmd_header *), + unsigned long callback_arg) +{ + struct cmd_ctrl_node *cmdnode; + unsigned long flags; + int ret = 0; + + lbs_deb_enter(LBS_DEB_HOST); + + cmdnode = __lbs_cmd_async(priv, command, in_cmd, in_cmd_size, + callback, callback_arg); + if (IS_ERR(cmdnode)) { + ret = PTR_ERR(cmdnode); + goto done; + } + + might_sleep(); + + /* + * Be careful with signals here. A signal may be received as the system + * goes into suspend or resume. We do not want this to interrupt the + * command, so we perform an uninterruptible sleep. + */ + wait_event(cmdnode->cmdwait_q, cmdnode->cmdwaitqwoken); + + spin_lock_irqsave(&priv->driver_lock, flags); + ret = cmdnode->result; + if (ret) + netdev_info(priv->dev, "PREP_CMD: command 0x%04x failed: %d\n", + command, ret); + + __lbs_cleanup_and_insert_cmd(priv, cmdnode); + spin_unlock_irqrestore(&priv->driver_lock, flags); + +done: + lbs_deb_leave_args(LBS_DEB_HOST, "ret %d", ret); + return ret; +} +EXPORT_SYMBOL_GPL(__lbs_cmd); diff --git a/drivers/net/wireless/libertas/cmd.h b/drivers/net/wireless/libertas/cmd.h new file mode 100644 index 0000000..4279e8a --- /dev/null +++ b/drivers/net/wireless/libertas/cmd.h @@ -0,0 +1,140 @@ +/* Copyright (C) 2007, Red Hat, Inc. */ + +#ifndef _LBS_CMD_H_ +#define _LBS_CMD_H_ + +#include + +#include "host.h" +#include "dev.h" + + +/* Command & response transfer between host and card */ + +struct cmd_ctrl_node { + struct list_head list; + int result; + /* command response */ + int (*callback)(struct lbs_private *, + unsigned long, + struct cmd_header *); + unsigned long callback_arg; + /* command data */ + struct cmd_header *cmdbuf; + /* wait queue */ + u16 cmdwaitqwoken; + wait_queue_head_t cmdwait_q; +}; + + +/* lbs_cmd() infers the size of the buffer to copy data back into, from + the size of the target of the pointer. Since the command to be sent + may often be smaller, that size is set in cmd->size by the caller.*/ +#define lbs_cmd(priv, cmdnr, cmd, cb, cb_arg) ({ \ + uint16_t __sz = le16_to_cpu((cmd)->hdr.size); \ + (cmd)->hdr.size = cpu_to_le16(sizeof(*(cmd))); \ + __lbs_cmd(priv, cmdnr, &(cmd)->hdr, __sz, cb, cb_arg); \ +}) + +#define lbs_cmd_with_response(priv, cmdnr, cmd) \ + lbs_cmd(priv, cmdnr, cmd, lbs_cmd_copyback, (unsigned long) (cmd)) + +void lbs_cmd_async(struct lbs_private *priv, uint16_t command, + struct cmd_header *in_cmd, int in_cmd_size); + +int __lbs_cmd(struct lbs_private *priv, uint16_t command, + struct cmd_header *in_cmd, int in_cmd_size, + int (*callback)(struct lbs_private *, unsigned long, struct cmd_header *), + unsigned long callback_arg); + +struct cmd_ctrl_node *__lbs_cmd_async(struct lbs_private *priv, + uint16_t command, struct cmd_header *in_cmd, int in_cmd_size, + int (*callback)(struct lbs_private *, unsigned long, struct cmd_header *), + unsigned long callback_arg); + +int lbs_cmd_copyback(struct lbs_private *priv, unsigned long extra, + struct cmd_header *resp); + +int lbs_allocate_cmd_buffer(struct lbs_private *priv); +int lbs_free_cmd_buffer(struct lbs_private *priv); + +int lbs_execute_next_command(struct lbs_private *priv); +void __lbs_complete_command(struct lbs_private *priv, struct cmd_ctrl_node *cmd, + int result); +void lbs_complete_command(struct lbs_private *priv, struct cmd_ctrl_node *cmd, + int result); +int lbs_process_command_response(struct lbs_private *priv, u8 *data, u32 len); + + +/* From cmdresp.c */ + +void lbs_mac_event_disconnected(struct lbs_private *priv); + + + +/* Events */ + +int lbs_process_event(struct lbs_private *priv, u32 event); + + +/* Actual commands */ + +int lbs_update_hw_spec(struct lbs_private *priv); + +int lbs_set_channel(struct lbs_private *priv, u8 channel); + +int lbs_update_channel(struct lbs_private *priv); + +int lbs_host_sleep_cfg(struct lbs_private *priv, uint32_t criteria, + struct wol_config *p_wol_config); + +int lbs_cmd_802_11_sleep_params(struct lbs_private *priv, uint16_t cmd_action, + struct sleep_params *sp); + +void lbs_ps_confirm_sleep(struct lbs_private *priv); + +int lbs_set_radio(struct lbs_private *priv, u8 preamble, u8 radio_on); + +void lbs_set_mac_control(struct lbs_private *priv); +int lbs_set_mac_control_sync(struct lbs_private *priv); + +int lbs_get_tx_power(struct lbs_private *priv, s16 *curlevel, s16 *minlevel, + s16 *maxlevel); + +int lbs_set_snmp_mib(struct lbs_private *priv, u32 oid, u16 val); + +int lbs_get_snmp_mib(struct lbs_private *priv, u32 oid, u16 *out_val); + + +/* Commands only used in wext.c, assoc. and scan.c */ + +int lbs_set_power_adapt_cfg(struct lbs_private *priv, int enable, int8_t p0, + int8_t p1, int8_t p2); + +int lbs_set_tpc_cfg(struct lbs_private *priv, int enable, int8_t p0, int8_t p1, + int8_t p2, int usesnr); + +int lbs_set_data_rate(struct lbs_private *priv, u8 rate); + +int lbs_cmd_802_11_rate_adapt_rateset(struct lbs_private *priv, + uint16_t cmd_action); + +int lbs_set_tx_power(struct lbs_private *priv, s16 dbm); + +int lbs_set_deep_sleep(struct lbs_private *priv, int deep_sleep); + +int lbs_set_host_sleep(struct lbs_private *priv, int host_sleep); + +int lbs_set_monitor_mode(struct lbs_private *priv, int enable); + +int lbs_get_rssi(struct lbs_private *priv, s8 *snr, s8 *nf); + +int lbs_set_11d_domain_info(struct lbs_private *priv); + +int lbs_get_reg(struct lbs_private *priv, u16 reg, u16 offset, u32 *value); + +int lbs_set_reg(struct lbs_private *priv, u16 reg, u16 offset, u32 value); + +int lbs_set_ps_mode(struct lbs_private *priv, u16 cmd_action, bool block); + +#endif /* _LBS_CMD_H */ diff --git a/drivers/net/wireless/libertas/cmdresp.c b/drivers/net/wireless/libertas/cmdresp.c new file mode 100644 index 0000000..178b222 --- /dev/null +++ b/drivers/net/wireless/libertas/cmdresp.c @@ -0,0 +1,350 @@ +/* + * This file contains the handling of command + * responses as well as events generated by firmware. + */ + +#include +#include +#include +#include +#include +#include + +#include "cfg.h" +#include "cmd.h" + +/** + * lbs_mac_event_disconnected - handles disconnect event. It + * reports disconnect to upper layer, clean tx/rx packets, + * reset link state etc. + * + * @priv: A pointer to struct lbs_private structure + * + * returns: n/a + */ +void lbs_mac_event_disconnected(struct lbs_private *priv) +{ + if (priv->connect_status != LBS_CONNECTED) + return; + + lbs_deb_enter(LBS_DEB_ASSOC); + + /* + * Cisco AP sends EAP failure and de-auth in less than 0.5 ms. + * It causes problem in the Supplicant + */ + msleep_interruptible(1000); + + if (priv->wdev->iftype == NL80211_IFTYPE_STATION) + lbs_send_disconnect_notification(priv); + + /* report disconnect to upper layer */ + netif_stop_queue(priv->dev); + netif_carrier_off(priv->dev); + + /* Free Tx and Rx packets */ + kfree_skb(priv->currenttxskb); + priv->currenttxskb = NULL; + priv->tx_pending_len = 0; + + priv->connect_status = LBS_DISCONNECTED; + + if (priv->psstate != PS_STATE_FULL_POWER) { + /* make firmware to exit PS mode */ + lbs_deb_cmd("disconnected, so exit PS mode\n"); + lbs_set_ps_mode(priv, PS_MODE_ACTION_EXIT_PS, false); + } + lbs_deb_leave(LBS_DEB_ASSOC); +} + +int lbs_process_command_response(struct lbs_private *priv, u8 *data, u32 len) +{ + uint16_t respcmd, curcmd; + struct cmd_header *resp; + int ret = 0; + unsigned long flags; + uint16_t result; + + lbs_deb_enter(LBS_DEB_HOST); + + mutex_lock(&priv->lock); + spin_lock_irqsave(&priv->driver_lock, flags); + + if (!priv->cur_cmd) { + lbs_deb_host("CMD_RESP: cur_cmd is NULL\n"); + ret = -1; + spin_unlock_irqrestore(&priv->driver_lock, flags); + goto done; + } + + resp = (void *)data; + curcmd = le16_to_cpu(priv->cur_cmd->cmdbuf->command); + respcmd = le16_to_cpu(resp->command); + result = le16_to_cpu(resp->result); + + lbs_deb_cmd("CMD_RESP: response 0x%04x, seq %d, size %d\n", + respcmd, le16_to_cpu(resp->seqnum), len); + lbs_deb_hex(LBS_DEB_CMD, "CMD_RESP", (void *) resp, len); + + if (resp->seqnum != priv->cur_cmd->cmdbuf->seqnum) { + netdev_info(priv->dev, + "Received CMD_RESP with invalid sequence %d (expected %d)\n", + le16_to_cpu(resp->seqnum), + le16_to_cpu(priv->cur_cmd->cmdbuf->seqnum)); + spin_unlock_irqrestore(&priv->driver_lock, flags); + ret = -1; + goto done; + } + if (respcmd != CMD_RET(curcmd) && + respcmd != CMD_RET_802_11_ASSOCIATE && curcmd != CMD_802_11_ASSOCIATE) { + netdev_info(priv->dev, "Invalid CMD_RESP %x to command %x!\n", + respcmd, curcmd); + spin_unlock_irqrestore(&priv->driver_lock, flags); + ret = -1; + goto done; + } + + if (resp->result == cpu_to_le16(0x0004)) { + /* 0x0004 means -EAGAIN. Drop the response, let it time out + and be resubmitted */ + netdev_info(priv->dev, + "Firmware returns DEFER to command %x. Will let it time out...\n", + le16_to_cpu(resp->command)); + spin_unlock_irqrestore(&priv->driver_lock, flags); + ret = -1; + goto done; + } + + /* Now we got response from FW, cancel the command timer */ + del_timer(&priv->command_timer); + priv->cmd_timed_out = 0; + + if (respcmd == CMD_RET(CMD_802_11_PS_MODE)) { + struct cmd_ds_802_11_ps_mode *psmode = (void *) &resp[1]; + u16 action = le16_to_cpu(psmode->action); + + lbs_deb_host( + "CMD_RESP: PS_MODE cmd reply result 0x%x, action 0x%x\n", + result, action); + + if (result) { + lbs_deb_host("CMD_RESP: PS command failed with 0x%x\n", + result); + /* + * We should not re-try enter-ps command in + * ad-hoc mode. It takes place in + * lbs_execute_next_command(). + */ + if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR && + action == PS_MODE_ACTION_ENTER_PS) + priv->psmode = LBS802_11POWERMODECAM; + } else if (action == PS_MODE_ACTION_ENTER_PS) { + priv->needtowakeup = 0; + priv->psstate = PS_STATE_AWAKE; + + lbs_deb_host("CMD_RESP: ENTER_PS command response\n"); + if (priv->connect_status != LBS_CONNECTED) { + /* + * When Deauth Event received before Enter_PS command + * response, We need to wake up the firmware. + */ + lbs_deb_host( + "disconnected, invoking lbs_ps_wakeup\n"); + + spin_unlock_irqrestore(&priv->driver_lock, flags); + mutex_unlock(&priv->lock); + lbs_set_ps_mode(priv, PS_MODE_ACTION_EXIT_PS, + false); + mutex_lock(&priv->lock); + spin_lock_irqsave(&priv->driver_lock, flags); + } + } else if (action == PS_MODE_ACTION_EXIT_PS) { + priv->needtowakeup = 0; + priv->psstate = PS_STATE_FULL_POWER; + lbs_deb_host("CMD_RESP: EXIT_PS command response\n"); + } else { + lbs_deb_host("CMD_RESP: PS action 0x%X\n", action); + } + + __lbs_complete_command(priv, priv->cur_cmd, result); + spin_unlock_irqrestore(&priv->driver_lock, flags); + + ret = 0; + goto done; + } + + /* If the command is not successful, cleanup and return failure */ + if ((result != 0 || !(respcmd & 0x8000))) { + lbs_deb_host("CMD_RESP: error 0x%04x in command reply 0x%04x\n", + result, respcmd); + /* + * Handling errors here + */ + switch (respcmd) { + case CMD_RET(CMD_GET_HW_SPEC): + case CMD_RET(CMD_802_11_RESET): + lbs_deb_host("CMD_RESP: reset failed\n"); + break; + + } + __lbs_complete_command(priv, priv->cur_cmd, result); + spin_unlock_irqrestore(&priv->driver_lock, flags); + + ret = -1; + goto done; + } + + spin_unlock_irqrestore(&priv->driver_lock, flags); + + if (priv->cur_cmd && priv->cur_cmd->callback) { + ret = priv->cur_cmd->callback(priv, priv->cur_cmd->callback_arg, + resp); + } + + spin_lock_irqsave(&priv->driver_lock, flags); + + if (priv->cur_cmd) { + /* Clean up and Put current command back to cmdfreeq */ + __lbs_complete_command(priv, priv->cur_cmd, result); + } + spin_unlock_irqrestore(&priv->driver_lock, flags); + +done: + mutex_unlock(&priv->lock); + lbs_deb_leave_args(LBS_DEB_HOST, "ret %d", ret); + return ret; +} + +int lbs_process_event(struct lbs_private *priv, u32 event) +{ + int ret = 0; + struct cmd_header cmd; + + lbs_deb_enter(LBS_DEB_CMD); + + switch (event) { + case MACREG_INT_CODE_LINK_SENSED: + lbs_deb_cmd("EVENT: link sensed\n"); + break; + + case MACREG_INT_CODE_DEAUTHENTICATED: + lbs_deb_cmd("EVENT: deauthenticated\n"); + lbs_mac_event_disconnected(priv); + break; + + case MACREG_INT_CODE_DISASSOCIATED: + lbs_deb_cmd("EVENT: disassociated\n"); + lbs_mac_event_disconnected(priv); + break; + + case MACREG_INT_CODE_LINK_LOST_NO_SCAN: + lbs_deb_cmd("EVENT: link lost\n"); + lbs_mac_event_disconnected(priv); + break; + + case MACREG_INT_CODE_PS_SLEEP: + lbs_deb_cmd("EVENT: ps sleep\n"); + + /* handle unexpected PS SLEEP event */ + if (priv->psstate == PS_STATE_FULL_POWER) { + lbs_deb_cmd( + "EVENT: in FULL POWER mode, ignoreing PS_SLEEP\n"); + break; + } + priv->psstate = PS_STATE_PRE_SLEEP; + + lbs_ps_confirm_sleep(priv); + + break; + + case MACREG_INT_CODE_HOST_AWAKE: + lbs_deb_cmd("EVENT: host awake\n"); + if (priv->reset_deep_sleep_wakeup) + priv->reset_deep_sleep_wakeup(priv); + priv->is_deep_sleep = 0; + lbs_cmd_async(priv, CMD_802_11_WAKEUP_CONFIRM, &cmd, + sizeof(cmd)); + priv->is_host_sleep_activated = 0; + wake_up_interruptible(&priv->host_sleep_q); + break; + + case MACREG_INT_CODE_DEEP_SLEEP_AWAKE: + if (priv->reset_deep_sleep_wakeup) + priv->reset_deep_sleep_wakeup(priv); + lbs_deb_cmd("EVENT: ds awake\n"); + priv->is_deep_sleep = 0; + priv->wakeup_dev_required = 0; + wake_up_interruptible(&priv->ds_awake_q); + break; + + case MACREG_INT_CODE_PS_AWAKE: + lbs_deb_cmd("EVENT: ps awake\n"); + /* handle unexpected PS AWAKE event */ + if (priv->psstate == PS_STATE_FULL_POWER) { + lbs_deb_cmd( + "EVENT: In FULL POWER mode - ignore PS AWAKE\n"); + break; + } + + priv->psstate = PS_STATE_AWAKE; + + if (priv->needtowakeup) { + /* + * wait for the command processing to finish + * before resuming sending + * priv->needtowakeup will be set to FALSE + * in lbs_ps_wakeup() + */ + lbs_deb_cmd("waking up ...\n"); + lbs_set_ps_mode(priv, PS_MODE_ACTION_EXIT_PS, false); + } + break; + + case MACREG_INT_CODE_MIC_ERR_UNICAST: + lbs_deb_cmd("EVENT: UNICAST MIC ERROR\n"); + lbs_send_mic_failureevent(priv, event); + break; + + case MACREG_INT_CODE_MIC_ERR_MULTICAST: + lbs_deb_cmd("EVENT: MULTICAST MIC ERROR\n"); + lbs_send_mic_failureevent(priv, event); + break; + + case MACREG_INT_CODE_MIB_CHANGED: + lbs_deb_cmd("EVENT: MIB CHANGED\n"); + break; + case MACREG_INT_CODE_INIT_DONE: + lbs_deb_cmd("EVENT: INIT DONE\n"); + break; + case MACREG_INT_CODE_ADHOC_BCN_LOST: + lbs_deb_cmd("EVENT: ADHOC beacon lost\n"); + break; + case MACREG_INT_CODE_RSSI_LOW: + netdev_alert(priv->dev, "EVENT: rssi low\n"); + break; + case MACREG_INT_CODE_SNR_LOW: + netdev_alert(priv->dev, "EVENT: snr low\n"); + break; + case MACREG_INT_CODE_MAX_FAIL: + netdev_alert(priv->dev, "EVENT: max fail\n"); + break; + case MACREG_INT_CODE_RSSI_HIGH: + netdev_alert(priv->dev, "EVENT: rssi high\n"); + break; + case MACREG_INT_CODE_SNR_HIGH: + netdev_alert(priv->dev, "EVENT: snr high\n"); + break; + + case MACREG_INT_CODE_MESH_AUTO_STARTED: + /* Ignore spurious autostart events */ + netdev_info(priv->dev, "EVENT: MESH_AUTO_STARTED (ignoring)\n"); + break; + + default: + netdev_alert(priv->dev, "EVENT: unknown event id %d\n", event); + break; + } + + lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); + return ret; +} diff --git a/drivers/net/wireless/libertas/debugfs.c b/drivers/net/wireless/libertas/debugfs.c new file mode 100644 index 0000000..668dd27 --- /dev/null +++ b/drivers/net/wireless/libertas/debugfs.c @@ -0,0 +1,986 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "decl.h" +#include "cmd.h" +#include "debugfs.h" + +static struct dentry *lbs_dir; +static char *szStates[] = { + "Connected", + "Disconnected" +}; + +#ifdef PROC_DEBUG +static void lbs_debug_init(struct lbs_private *priv); +#endif + +static ssize_t write_file_dummy(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return -EINVAL; +} + +static const size_t len = PAGE_SIZE; + +static ssize_t lbs_dev_info(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + size_t pos = 0; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + ssize_t res; + if (!buf) + return -ENOMEM; + + pos += snprintf(buf+pos, len-pos, "state = %s\n", + szStates[priv->connect_status]); + pos += snprintf(buf+pos, len-pos, "region_code = %02x\n", + (u32) priv->regioncode); + + res = simple_read_from_buffer(userbuf, count, ppos, buf, pos); + + free_page(addr); + return res; +} + +static ssize_t lbs_sleepparams_write(struct file *file, + const char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t buf_size, ret; + struct sleep_params sp; + int p1, p2, p3, p4, p5, p6; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + if (!buf) + return -ENOMEM; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, user_buf, buf_size)) { + ret = -EFAULT; + goto out_unlock; + } + ret = sscanf(buf, "%d %d %d %d %d %d", &p1, &p2, &p3, &p4, &p5, &p6); + if (ret != 6) { + ret = -EINVAL; + goto out_unlock; + } + sp.sp_error = p1; + sp.sp_offset = p2; + sp.sp_stabletime = p3; + sp.sp_calcontrol = p4; + sp.sp_extsleepclk = p5; + sp.sp_reserved = p6; + + ret = lbs_cmd_802_11_sleep_params(priv, CMD_ACT_SET, &sp); + if (!ret) + ret = count; + else if (ret > 0) + ret = -EINVAL; + +out_unlock: + free_page(addr); + return ret; +} + +static ssize_t lbs_sleepparams_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t ret; + size_t pos = 0; + struct sleep_params sp; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + if (!buf) + return -ENOMEM; + + ret = lbs_cmd_802_11_sleep_params(priv, CMD_ACT_GET, &sp); + if (ret) + goto out_unlock; + + pos += snprintf(buf, len, "%d %d %d %d %d %d\n", sp.sp_error, + sp.sp_offset, sp.sp_stabletime, + sp.sp_calcontrol, sp.sp_extsleepclk, + sp.sp_reserved); + + ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); + +out_unlock: + free_page(addr); + return ret; +} + +static ssize_t lbs_host_sleep_write(struct file *file, + const char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t buf_size, ret; + int host_sleep; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + if (!buf) + return -ENOMEM; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, user_buf, buf_size)) { + ret = -EFAULT; + goto out_unlock; + } + ret = sscanf(buf, "%d", &host_sleep); + if (ret != 1) { + ret = -EINVAL; + goto out_unlock; + } + + if (host_sleep == 0) + ret = lbs_set_host_sleep(priv, 0); + else if (host_sleep == 1) { + if (priv->wol_criteria == EHS_REMOVE_WAKEUP) { + netdev_info(priv->dev, + "wake parameters not configured\n"); + ret = -EINVAL; + goto out_unlock; + } + ret = lbs_set_host_sleep(priv, 1); + } else { + netdev_err(priv->dev, "invalid option\n"); + ret = -EINVAL; + } + + if (!ret) + ret = count; + +out_unlock: + free_page(addr); + return ret; +} + +static ssize_t lbs_host_sleep_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t ret; + size_t pos = 0; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + if (!buf) + return -ENOMEM; + + pos += snprintf(buf, len, "%d\n", priv->is_host_sleep_activated); + + ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); + + free_page(addr); + return ret; +} + +/* + * When calling CMD_802_11_SUBSCRIBE_EVENT with CMD_ACT_GET, me might + * get a bunch of vendor-specific TLVs (a.k.a. IEs) back from the + * firmware. Here's an example: + * 04 01 02 00 00 00 05 01 02 00 00 00 06 01 02 00 + * 00 00 07 01 02 00 3c 00 00 00 00 00 00 00 03 03 + * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + * + * The 04 01 is the TLV type (here TLV_TYPE_RSSI_LOW), 02 00 is the length, + * 00 00 are the data bytes of this TLV. For this TLV, their meaning is + * defined in mrvlietypes_thresholds + * + * This function searches in this TLV data chunk for a given TLV type + * and returns a pointer to the first data byte of the TLV, or to NULL + * if the TLV hasn't been found. + */ +static void *lbs_tlv_find(uint16_t tlv_type, const uint8_t *tlv, uint16_t size) +{ + struct mrvl_ie_header *tlv_h; + uint16_t length; + ssize_t pos = 0; + + while (pos < size) { + tlv_h = (struct mrvl_ie_header *) tlv; + if (!tlv_h->len) + return NULL; + if (tlv_h->type == cpu_to_le16(tlv_type)) + return tlv_h; + length = le16_to_cpu(tlv_h->len) + sizeof(*tlv_h); + pos += length; + tlv += length; + } + return NULL; +} + + +static ssize_t lbs_threshold_read(uint16_t tlv_type, uint16_t event_mask, + struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct cmd_ds_802_11_subscribe_event *subscribed; + struct mrvl_ie_thresholds *got; + struct lbs_private *priv = file->private_data; + ssize_t ret = 0; + size_t pos = 0; + char *buf; + u8 value; + u8 freq; + int events = 0; + + buf = (char *)get_zeroed_page(GFP_KERNEL); + if (!buf) + return -ENOMEM; + + subscribed = kzalloc(sizeof(*subscribed), GFP_KERNEL); + if (!subscribed) { + ret = -ENOMEM; + goto out_page; + } + + subscribed->hdr.size = cpu_to_le16(sizeof(*subscribed)); + subscribed->action = cpu_to_le16(CMD_ACT_GET); + + ret = lbs_cmd_with_response(priv, CMD_802_11_SUBSCRIBE_EVENT, subscribed); + if (ret) + goto out_cmd; + + got = lbs_tlv_find(tlv_type, subscribed->tlv, sizeof(subscribed->tlv)); + if (got) { + value = got->value; + freq = got->freq; + events = le16_to_cpu(subscribed->events); + + pos += snprintf(buf, len, "%d %d %d\n", value, freq, + !!(events & event_mask)); + } + + ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); + + out_cmd: + kfree(subscribed); + + out_page: + free_page((unsigned long)buf); + return ret; +} + + +static ssize_t lbs_threshold_write(uint16_t tlv_type, uint16_t event_mask, + struct file *file, + const char __user *userbuf, size_t count, + loff_t *ppos) +{ + struct cmd_ds_802_11_subscribe_event *events; + struct mrvl_ie_thresholds *tlv; + struct lbs_private *priv = file->private_data; + ssize_t buf_size; + int value, freq, new_mask; + uint16_t curr_mask; + char *buf; + int ret; + + buf = (char *)get_zeroed_page(GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, userbuf, buf_size)) { + ret = -EFAULT; + goto out_page; + } + ret = sscanf(buf, "%d %d %d", &value, &freq, &new_mask); + if (ret != 3) { + ret = -EINVAL; + goto out_page; + } + events = kzalloc(sizeof(*events), GFP_KERNEL); + if (!events) { + ret = -ENOMEM; + goto out_page; + } + + events->hdr.size = cpu_to_le16(sizeof(*events)); + events->action = cpu_to_le16(CMD_ACT_GET); + + ret = lbs_cmd_with_response(priv, CMD_802_11_SUBSCRIBE_EVENT, events); + if (ret) + goto out_events; + + curr_mask = le16_to_cpu(events->events); + + if (new_mask) + new_mask = curr_mask | event_mask; + else + new_mask = curr_mask & ~event_mask; + + /* Now everything is set and we can send stuff down to the firmware */ + + tlv = (void *)events->tlv; + + events->action = cpu_to_le16(CMD_ACT_SET); + events->events = cpu_to_le16(new_mask); + tlv->header.type = cpu_to_le16(tlv_type); + tlv->header.len = cpu_to_le16(sizeof(*tlv) - sizeof(tlv->header)); + tlv->value = value; + if (tlv_type != TLV_TYPE_BCNMISS) + tlv->freq = freq; + + /* The command header, the action, the event mask, and one TLV */ + events->hdr.size = cpu_to_le16(sizeof(events->hdr) + 4 + sizeof(*tlv)); + + ret = lbs_cmd_with_response(priv, CMD_802_11_SUBSCRIBE_EVENT, events); + + if (!ret) + ret = count; + out_events: + kfree(events); + out_page: + free_page((unsigned long)buf); + return ret; +} + + +static ssize_t lbs_lowrssi_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_read(TLV_TYPE_RSSI_LOW, CMD_SUBSCRIBE_RSSI_LOW, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_lowrssi_write(struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_write(TLV_TYPE_RSSI_LOW, CMD_SUBSCRIBE_RSSI_LOW, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_lowsnr_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_read(TLV_TYPE_SNR_LOW, CMD_SUBSCRIBE_SNR_LOW, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_lowsnr_write(struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_write(TLV_TYPE_SNR_LOW, CMD_SUBSCRIBE_SNR_LOW, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_failcount_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_read(TLV_TYPE_FAILCOUNT, CMD_SUBSCRIBE_FAILCOUNT, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_failcount_write(struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_write(TLV_TYPE_FAILCOUNT, CMD_SUBSCRIBE_FAILCOUNT, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_highrssi_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_read(TLV_TYPE_RSSI_HIGH, CMD_SUBSCRIBE_RSSI_HIGH, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_highrssi_write(struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_write(TLV_TYPE_RSSI_HIGH, CMD_SUBSCRIBE_RSSI_HIGH, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_highsnr_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_read(TLV_TYPE_SNR_HIGH, CMD_SUBSCRIBE_SNR_HIGH, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_highsnr_write(struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_write(TLV_TYPE_SNR_HIGH, CMD_SUBSCRIBE_SNR_HIGH, + file, userbuf, count, ppos); +} + +static ssize_t lbs_bcnmiss_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_read(TLV_TYPE_BCNMISS, CMD_SUBSCRIBE_BCNMISS, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_bcnmiss_write(struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + return lbs_threshold_write(TLV_TYPE_BCNMISS, CMD_SUBSCRIBE_BCNMISS, + file, userbuf, count, ppos); +} + + +static ssize_t lbs_rdmac_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t pos = 0; + int ret; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + u32 val = 0; + + if (!buf) + return -ENOMEM; + + ret = lbs_get_reg(priv, CMD_MAC_REG_ACCESS, priv->mac_offset, &val); + mdelay(10); + if (!ret) { + pos = snprintf(buf, len, "MAC[0x%x] = 0x%08x\n", + priv->mac_offset, val); + ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); + } + free_page(addr); + return ret; +} + +static ssize_t lbs_rdmac_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t res, buf_size; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + if (!buf) + return -ENOMEM; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, userbuf, buf_size)) { + res = -EFAULT; + goto out_unlock; + } + priv->mac_offset = simple_strtoul(buf, NULL, 16); + res = count; +out_unlock: + free_page(addr); + return res; +} + +static ssize_t lbs_wrmac_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + + struct lbs_private *priv = file->private_data; + ssize_t res, buf_size; + u32 offset, value; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + if (!buf) + return -ENOMEM; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, userbuf, buf_size)) { + res = -EFAULT; + goto out_unlock; + } + res = sscanf(buf, "%x %x", &offset, &value); + if (res != 2) { + res = -EFAULT; + goto out_unlock; + } + + res = lbs_set_reg(priv, CMD_MAC_REG_ACCESS, offset, value); + mdelay(10); + + if (!res) + res = count; +out_unlock: + free_page(addr); + return res; +} + +static ssize_t lbs_rdbbp_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t pos = 0; + int ret; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + u32 val; + + if (!buf) + return -ENOMEM; + + ret = lbs_get_reg(priv, CMD_BBP_REG_ACCESS, priv->bbp_offset, &val); + mdelay(10); + if (!ret) { + pos = snprintf(buf, len, "BBP[0x%x] = 0x%08x\n", + priv->bbp_offset, val); + ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); + } + free_page(addr); + + return ret; +} + +static ssize_t lbs_rdbbp_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t res, buf_size; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + if (!buf) + return -ENOMEM; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, userbuf, buf_size)) { + res = -EFAULT; + goto out_unlock; + } + priv->bbp_offset = simple_strtoul(buf, NULL, 16); + res = count; +out_unlock: + free_page(addr); + return res; +} + +static ssize_t lbs_wrbbp_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + + struct lbs_private *priv = file->private_data; + ssize_t res, buf_size; + u32 offset, value; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + if (!buf) + return -ENOMEM; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, userbuf, buf_size)) { + res = -EFAULT; + goto out_unlock; + } + res = sscanf(buf, "%x %x", &offset, &value); + if (res != 2) { + res = -EFAULT; + goto out_unlock; + } + + res = lbs_set_reg(priv, CMD_BBP_REG_ACCESS, offset, value); + mdelay(10); + + if (!res) + res = count; +out_unlock: + free_page(addr); + return res; +} + +static ssize_t lbs_rdrf_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t pos = 0; + int ret; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + u32 val; + + if (!buf) + return -ENOMEM; + + ret = lbs_get_reg(priv, CMD_RF_REG_ACCESS, priv->rf_offset, &val); + mdelay(10); + if (!ret) { + pos = snprintf(buf, len, "RF[0x%x] = 0x%08x\n", + priv->rf_offset, val); + ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos); + } + free_page(addr); + + return ret; +} + +static ssize_t lbs_rdrf_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lbs_private *priv = file->private_data; + ssize_t res, buf_size; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + if (!buf) + return -ENOMEM; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, userbuf, buf_size)) { + res = -EFAULT; + goto out_unlock; + } + priv->rf_offset = simple_strtoul(buf, NULL, 16); + res = count; +out_unlock: + free_page(addr); + return res; +} + +static ssize_t lbs_wrrf_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + + struct lbs_private *priv = file->private_data; + ssize_t res, buf_size; + u32 offset, value; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + if (!buf) + return -ENOMEM; + + buf_size = min(count, len - 1); + if (copy_from_user(buf, userbuf, buf_size)) { + res = -EFAULT; + goto out_unlock; + } + res = sscanf(buf, "%x %x", &offset, &value); + if (res != 2) { + res = -EFAULT; + goto out_unlock; + } + + res = lbs_set_reg(priv, CMD_RF_REG_ACCESS, offset, value); + mdelay(10); + + if (!res) + res = count; +out_unlock: + free_page(addr); + return res; +} + +#define FOPS(fread, fwrite) { \ + .owner = THIS_MODULE, \ + .open = simple_open, \ + .read = (fread), \ + .write = (fwrite), \ + .llseek = generic_file_llseek, \ +} + +struct lbs_debugfs_files { + const char *name; + umode_t perm; + struct file_operations fops; +}; + +static const struct lbs_debugfs_files debugfs_files[] = { + { "info", 0444, FOPS(lbs_dev_info, write_file_dummy), }, + { "sleepparams", 0644, FOPS(lbs_sleepparams_read, + lbs_sleepparams_write), }, + { "hostsleep", 0644, FOPS(lbs_host_sleep_read, + lbs_host_sleep_write), }, +}; + +static const struct lbs_debugfs_files debugfs_events_files[] = { + {"low_rssi", 0644, FOPS(lbs_lowrssi_read, + lbs_lowrssi_write), }, + {"low_snr", 0644, FOPS(lbs_lowsnr_read, + lbs_lowsnr_write), }, + {"failure_count", 0644, FOPS(lbs_failcount_read, + lbs_failcount_write), }, + {"beacon_missed", 0644, FOPS(lbs_bcnmiss_read, + lbs_bcnmiss_write), }, + {"high_rssi", 0644, FOPS(lbs_highrssi_read, + lbs_highrssi_write), }, + {"high_snr", 0644, FOPS(lbs_highsnr_read, + lbs_highsnr_write), }, +}; + +static const struct lbs_debugfs_files debugfs_regs_files[] = { + {"rdmac", 0644, FOPS(lbs_rdmac_read, lbs_rdmac_write), }, + {"wrmac", 0600, FOPS(NULL, lbs_wrmac_write), }, + {"rdbbp", 0644, FOPS(lbs_rdbbp_read, lbs_rdbbp_write), }, + {"wrbbp", 0600, FOPS(NULL, lbs_wrbbp_write), }, + {"rdrf", 0644, FOPS(lbs_rdrf_read, lbs_rdrf_write), }, + {"wrrf", 0600, FOPS(NULL, lbs_wrrf_write), }, +}; + +void lbs_debugfs_init(void) +{ + if (!lbs_dir) + lbs_dir = debugfs_create_dir("lbs_wireless", NULL); +} + +void lbs_debugfs_remove(void) +{ + if (lbs_dir) + debugfs_remove(lbs_dir); +} + +void lbs_debugfs_init_one(struct lbs_private *priv, struct net_device *dev) +{ + int i; + const struct lbs_debugfs_files *files; + if (!lbs_dir) + goto exit; + + priv->debugfs_dir = debugfs_create_dir(dev->name, lbs_dir); + if (!priv->debugfs_dir) + goto exit; + + for (i=0; idebugfs_files[i] = debugfs_create_file(files->name, + files->perm, + priv->debugfs_dir, + priv, + &files->fops); + } + + priv->events_dir = debugfs_create_dir("subscribed_events", priv->debugfs_dir); + if (!priv->events_dir) + goto exit; + + for (i=0; idebugfs_events_files[i] = debugfs_create_file(files->name, + files->perm, + priv->events_dir, + priv, + &files->fops); + } + + priv->regs_dir = debugfs_create_dir("registers", priv->debugfs_dir); + if (!priv->regs_dir) + goto exit; + + for (i=0; idebugfs_regs_files[i] = debugfs_create_file(files->name, + files->perm, + priv->regs_dir, + priv, + &files->fops); + } + +#ifdef PROC_DEBUG + lbs_debug_init(priv); +#endif +exit: + return; +} + +void lbs_debugfs_remove_one(struct lbs_private *priv) +{ + int i; + + for(i=0; idebugfs_regs_files[i]); + + debugfs_remove(priv->regs_dir); + + for(i=0; idebugfs_events_files[i]); + + debugfs_remove(priv->events_dir); +#ifdef PROC_DEBUG + debugfs_remove(priv->debugfs_debug); +#endif + for(i=0; idebugfs_files[i]); + debugfs_remove(priv->debugfs_dir); +} + + + +/* debug entry */ + +#ifdef PROC_DEBUG + +#define item_size(n) (FIELD_SIZEOF(struct lbs_private, n)) +#define item_addr(n) (offsetof(struct lbs_private, n)) + + +struct debug_data { + char name[32]; + u32 size; + size_t addr; +}; + +/* To debug any member of struct lbs_private, simply add one line here. + */ +static struct debug_data items[] = { + {"psmode", item_size(psmode), item_addr(psmode)}, + {"psstate", item_size(psstate), item_addr(psstate)}, +}; + +static int num_of_items = ARRAY_SIZE(items); + +/** + * lbs_debugfs_read - proc read function + * + * @file: file to read + * @userbuf: pointer to buffer + * @count: number of bytes to read + * @ppos: read data starting position + * + * returns: amount of data read or negative error code + */ +static ssize_t lbs_debugfs_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + int val = 0; + size_t pos = 0; + ssize_t res; + char *p; + int i; + struct debug_data *d; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *)addr; + if (!buf) + return -ENOMEM; + + p = buf; + + d = file->private_data; + + for (i = 0; i < num_of_items; i++) { + if (d[i].size == 1) + val = *((u8 *) d[i].addr); + else if (d[i].size == 2) + val = *((u16 *) d[i].addr); + else if (d[i].size == 4) + val = *((u32 *) d[i].addr); + else if (d[i].size == 8) + val = *((u64 *) d[i].addr); + + pos += sprintf(p + pos, "%s=%d\n", d[i].name, val); + } + + res = simple_read_from_buffer(userbuf, count, ppos, p, pos); + + free_page(addr); + return res; +} + +/** + * lbs_debugfs_write - proc write function + * + * @f: file pointer + * @buf: pointer to data buffer + * @cnt: data number to write + * @ppos: file position + * + * returns: amount of data written + */ +static ssize_t lbs_debugfs_write(struct file *f, const char __user *buf, + size_t cnt, loff_t *ppos) +{ + int r, i; + char *pdata; + char *p; + char *p0; + char *p1; + char *p2; + struct debug_data *d = f->private_data; + + pdata = kmalloc(cnt, GFP_KERNEL); + if (pdata == NULL) + return 0; + + if (copy_from_user(pdata, buf, cnt)) { + lbs_deb_debugfs("Copy from user failed\n"); + kfree(pdata); + return 0; + } + + p0 = pdata; + for (i = 0; i < num_of_items; i++) { + do { + p = strstr(p0, d[i].name); + if (p == NULL) + break; + p1 = strchr(p, '\n'); + if (p1 == NULL) + break; + p0 = p1++; + p2 = strchr(p, '='); + if (!p2) + break; + p2++; + r = simple_strtoul(p2, NULL, 0); + if (d[i].size == 1) + *((u8 *) d[i].addr) = (u8) r; + else if (d[i].size == 2) + *((u16 *) d[i].addr) = (u16) r; + else if (d[i].size == 4) + *((u32 *) d[i].addr) = (u32) r; + else if (d[i].size == 8) + *((u64 *) d[i].addr) = (u64) r; + break; + } while (1); + } + kfree(pdata); + + return (ssize_t)cnt; +} + +static const struct file_operations lbs_debug_fops = { + .owner = THIS_MODULE, + .open = simple_open, + .write = lbs_debugfs_write, + .read = lbs_debugfs_read, + .llseek = default_llseek, +}; + +/** + * lbs_debug_init - create debug proc file + * + * @priv: pointer to &struct lbs_private + * + * returns: N/A + */ +static void lbs_debug_init(struct lbs_private *priv) +{ + int i; + + if (!priv->debugfs_dir) + return; + + for (i = 0; i < num_of_items; i++) + items[i].addr += (size_t) priv; + + priv->debugfs_debug = debugfs_create_file("debug", 0644, + priv->debugfs_dir, &items[0], + &lbs_debug_fops); +} +#endif diff --git a/drivers/net/wireless/libertas/debugfs.h b/drivers/net/wireless/libertas/debugfs.h new file mode 100644 index 0000000..f2b9c7f --- /dev/null +++ b/drivers/net/wireless/libertas/debugfs.h @@ -0,0 +1,10 @@ +#ifndef _LBS_DEBUGFS_H_ +#define _LBS_DEBUGFS_H_ + +void lbs_debugfs_init(void); +void lbs_debugfs_remove(void); + +void lbs_debugfs_init_one(struct lbs_private *priv, struct net_device *dev); +void lbs_debugfs_remove_one(struct lbs_private *priv); + +#endif diff --git a/drivers/net/wireless/libertas/decl.h b/drivers/net/wireless/libertas/decl.h new file mode 100644 index 0000000..84a3aa7 --- /dev/null +++ b/drivers/net/wireless/libertas/decl.h @@ -0,0 +1,82 @@ + +/* + * This file contains declaration referring to + * functions defined in other source files + */ + +#ifndef _LBS_DECL_H_ +#define _LBS_DECL_H_ + +#include +#include +#include + +/* Should be terminated by a NULL entry */ +struct lbs_fw_table { + int model; + const char *helper; + const char *fwname; +}; + +struct lbs_private; +typedef void (*lbs_fw_cb)(struct lbs_private *priv, int ret, + const struct firmware *helper, const struct firmware *mainfw); + +struct lbs_private; +struct sk_buff; +struct net_device; +struct cmd_ds_command; + + +/* ethtool.c */ +extern const struct ethtool_ops lbs_ethtool_ops; + + +/* tx.c */ +void lbs_send_tx_feedback(struct lbs_private *priv, u32 try_count); +netdev_tx_t lbs_hard_start_xmit(struct sk_buff *skb, + struct net_device *dev); + +/* rx.c */ +int lbs_process_rxed_packet(struct lbs_private *priv, struct sk_buff *); + + +/* main.c */ +struct lbs_private *lbs_add_card(void *card, struct device *dmdev); +void lbs_remove_card(struct lbs_private *priv); +int lbs_start_card(struct lbs_private *priv); +void lbs_stop_card(struct lbs_private *priv); +void lbs_host_to_card_done(struct lbs_private *priv); + +int lbs_start_iface(struct lbs_private *priv); +int lbs_stop_iface(struct lbs_private *priv); +int lbs_set_iface_type(struct lbs_private *priv, enum nl80211_iftype type); + +int lbs_rtap_supported(struct lbs_private *priv); + +int lbs_set_mac_address(struct net_device *dev, void *addr); +void lbs_set_multicast_list(struct net_device *dev); +void lbs_update_mcast(struct lbs_private *priv); + +int lbs_suspend(struct lbs_private *priv); +int lbs_resume(struct lbs_private *priv); + +void lbs_queue_event(struct lbs_private *priv, u32 event); +void lbs_notify_command_response(struct lbs_private *priv, u8 resp_idx); + +int lbs_enter_auto_deep_sleep(struct lbs_private *priv); +int lbs_exit_auto_deep_sleep(struct lbs_private *priv); + +u32 lbs_fw_index_to_data_rate(u8 index); +u8 lbs_data_rate_to_fw_index(u32 rate); + +int lbs_get_firmware(struct device *dev, u32 card_model, + const struct lbs_fw_table *fw_table, + const struct firmware **helper, + const struct firmware **mainfw); +int lbs_get_firmware_async(struct lbs_private *priv, struct device *device, + u32 card_model, const struct lbs_fw_table *fw_table, + lbs_fw_cb callback); +void lbs_wait_for_firmware_load(struct lbs_private *priv); + +#endif diff --git a/drivers/net/wireless/libertas/defs.h b/drivers/net/wireless/libertas/defs.h new file mode 100644 index 0000000..c280ad9 --- /dev/null +++ b/drivers/net/wireless/libertas/defs.h @@ -0,0 +1,401 @@ +/* + * This header file contains global constant/enum definitions, + * global variable declaration. + */ +#ifndef _LBS_DEFS_H_ +#define _LBS_DEFS_H_ + +#include + +#ifdef CPTCFG_LIBERTAS_DEBUG +#define DEBUG +#define PROC_DEBUG +#endif + +#ifndef DRV_NAME +#define DRV_NAME "libertas" +#endif + +/* + * Really nasty hack to avoid stuffing compat.diff with tons of ifdefs, + * we could add this to a compat header file but too lazy to check ml_priv + * is not used anywhere else + */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)) +#define ml_priv priv +#endif + +#define LBS_DEB_ENTER 0x00000001 +#define LBS_DEB_LEAVE 0x00000002 +#define LBS_DEB_MAIN 0x00000004 +#define LBS_DEB_NET 0x00000008 +#define LBS_DEB_MESH 0x00000010 +#define LBS_DEB_WEXT 0x00000020 +#define LBS_DEB_IOCTL 0x00000040 +#define LBS_DEB_SCAN 0x00000080 +#define LBS_DEB_ASSOC 0x00000100 +#define LBS_DEB_JOIN 0x00000200 +#define LBS_DEB_11D 0x00000400 +#define LBS_DEB_DEBUGFS 0x00000800 +#define LBS_DEB_ETHTOOL 0x00001000 +#define LBS_DEB_HOST 0x00002000 +#define LBS_DEB_CMD 0x00004000 +#define LBS_DEB_RX 0x00008000 +#define LBS_DEB_TX 0x00010000 +#define LBS_DEB_USB 0x00020000 +#define LBS_DEB_CS 0x00040000 +#define LBS_DEB_FW 0x00080000 +#define LBS_DEB_THREAD 0x00100000 +#define LBS_DEB_HEX 0x00200000 +#define LBS_DEB_SDIO 0x00400000 +#define LBS_DEB_SYSFS 0x00800000 +#define LBS_DEB_SPI 0x01000000 +#define LBS_DEB_CFG80211 0x02000000 + +extern unsigned int lbs_debug; + +#ifdef DEBUG +#define LBS_DEB_LL(grp, grpnam, fmt, args...) \ +do { if ((lbs_debug & (grp)) == (grp)) \ + printk(KERN_DEBUG DRV_NAME grpnam "%s: " fmt, \ + in_interrupt() ? " (INT)" : "", ## args); } while (0) +#else +#define LBS_DEB_LL(grp, grpnam, fmt, args...) do {} while (0) +#endif + +#define lbs_deb_enter(grp) \ + LBS_DEB_LL(grp | LBS_DEB_ENTER, " enter", "%s()\n", __func__); +#define lbs_deb_enter_args(grp, fmt, args...) \ + LBS_DEB_LL(grp | LBS_DEB_ENTER, " enter", "%s(" fmt ")\n", __func__, ## args); +#define lbs_deb_leave(grp) \ + LBS_DEB_LL(grp | LBS_DEB_LEAVE, " leave", "%s()\n", __func__); +#define lbs_deb_leave_args(grp, fmt, args...) \ + LBS_DEB_LL(grp | LBS_DEB_LEAVE, " leave", "%s(), " fmt "\n", \ + __func__, ##args); +#define lbs_deb_main(fmt, args...) LBS_DEB_LL(LBS_DEB_MAIN, " main", fmt, ##args) +#define lbs_deb_net(fmt, args...) LBS_DEB_LL(LBS_DEB_NET, " net", fmt, ##args) +#define lbs_deb_mesh(fmt, args...) LBS_DEB_LL(LBS_DEB_MESH, " mesh", fmt, ##args) +#define lbs_deb_wext(fmt, args...) LBS_DEB_LL(LBS_DEB_WEXT, " wext", fmt, ##args) +#define lbs_deb_ioctl(fmt, args...) LBS_DEB_LL(LBS_DEB_IOCTL, " ioctl", fmt, ##args) +#define lbs_deb_scan(fmt, args...) LBS_DEB_LL(LBS_DEB_SCAN, " scan", fmt, ##args) +#define lbs_deb_assoc(fmt, args...) LBS_DEB_LL(LBS_DEB_ASSOC, " assoc", fmt, ##args) +#define lbs_deb_join(fmt, args...) LBS_DEB_LL(LBS_DEB_JOIN, " join", fmt, ##args) +#define lbs_deb_11d(fmt, args...) LBS_DEB_LL(LBS_DEB_11D, " 11d", fmt, ##args) +#define lbs_deb_debugfs(fmt, args...) LBS_DEB_LL(LBS_DEB_DEBUGFS, " debugfs", fmt, ##args) +#define lbs_deb_ethtool(fmt, args...) LBS_DEB_LL(LBS_DEB_ETHTOOL, " ethtool", fmt, ##args) +#define lbs_deb_host(fmt, args...) LBS_DEB_LL(LBS_DEB_HOST, " host", fmt, ##args) +#define lbs_deb_cmd(fmt, args...) LBS_DEB_LL(LBS_DEB_CMD, " cmd", fmt, ##args) +#define lbs_deb_rx(fmt, args...) LBS_DEB_LL(LBS_DEB_RX, " rx", fmt, ##args) +#define lbs_deb_tx(fmt, args...) LBS_DEB_LL(LBS_DEB_TX, " tx", fmt, ##args) +#define lbs_deb_fw(fmt, args...) LBS_DEB_LL(LBS_DEB_FW, " fw", fmt, ##args) +#define lbs_deb_usb(fmt, args...) LBS_DEB_LL(LBS_DEB_USB, " usb", fmt, ##args) +#define lbs_deb_usbd(dev, fmt, args...) LBS_DEB_LL(LBS_DEB_USB, " usbd", "%s:" fmt, dev_name(dev), ##args) +#define lbs_deb_cs(fmt, args...) LBS_DEB_LL(LBS_DEB_CS, " cs", fmt, ##args) +#define lbs_deb_thread(fmt, args...) LBS_DEB_LL(LBS_DEB_THREAD, " thread", fmt, ##args) +#define lbs_deb_sdio(fmt, args...) LBS_DEB_LL(LBS_DEB_SDIO, " sdio", fmt, ##args) +#define lbs_deb_sysfs(fmt, args...) LBS_DEB_LL(LBS_DEB_SYSFS, " sysfs", fmt, ##args) +#define lbs_deb_spi(fmt, args...) LBS_DEB_LL(LBS_DEB_SPI, " spi", fmt, ##args) +#define lbs_deb_cfg80211(fmt, args...) LBS_DEB_LL(LBS_DEB_CFG80211, " cfg80211", fmt, ##args) + +#ifdef DEBUG +static inline void lbs_deb_hex(unsigned int grp, const char *prompt, u8 *buf, int len) +{ + int i = 0; + + if (len && + (lbs_debug & LBS_DEB_HEX) && + (lbs_debug & grp)) + { + for (i = 1; i <= len; i++) { + if ((i & 0xf) == 1) { + if (i != 1) + printk("\n"); + printk(DRV_NAME " %s: ", prompt); + } + printk("%02x ", (u8) * buf); + buf++; + } + printk("\n"); + } +} +#else +#define lbs_deb_hex(grp,prompt,buf,len) do {} while (0) +#endif + + + +/* Buffer Constants */ + +/* The size of SQ memory PPA, DPA are 8 DWORDs, that keep the physical + * addresses of TxPD buffers. Station has only 8 TxPD available, Whereas + * driver has more local TxPDs. Each TxPD on the host memory is associated + * with a Tx control node. The driver maintains 8 RxPD descriptors for + * station firmware to store Rx packet information. + * + * Current version of MAC has a 32x6 multicast address buffer. + * + * 802.11b can have up to 14 channels, the driver keeps the + * BSSID(MAC address) of each APs or Ad hoc stations it has sensed. + */ + +#define MRVDRV_MAX_MULTICAST_LIST_SIZE 32 +#define LBS_NUM_CMD_BUFFERS 10 +#define LBS_CMD_BUFFER_SIZE (2 * 1024) +#define MRVDRV_MAX_CHANNEL_SIZE 14 +#define MRVDRV_ASSOCIATION_TIME_OUT 255 +#define MRVDRV_SNAP_HEADER_LEN 8 + +#define LBS_UPLD_SIZE 2312 +#define DEV_NAME_LEN 32 + +/* Wake criteria for HOST_SLEEP_CFG command */ +#define EHS_WAKE_ON_BROADCAST_DATA 0x0001 +#define EHS_WAKE_ON_UNICAST_DATA 0x0002 +#define EHS_WAKE_ON_MAC_EVENT 0x0004 +#define EHS_WAKE_ON_MULTICAST_DATA 0x0008 +#define EHS_REMOVE_WAKEUP 0xFFFFFFFF +/* Wake rules for Host_Sleep_CFG command */ +#define WOL_RULE_NET_TYPE_INFRA_OR_IBSS 0x00 +#define WOL_RULE_NET_TYPE_MESH 0x10 +#define WOL_RULE_ADDR_TYPE_BCAST 0x01 +#define WOL_RULE_ADDR_TYPE_MCAST 0x08 +#define WOL_RULE_ADDR_TYPE_UCAST 0x02 +#define WOL_RULE_OP_AND 0x01 +#define WOL_RULE_OP_OR 0x02 +#define WOL_RULE_OP_INVALID 0xFF +#define WOL_RESULT_VALID_CMD 0 +#define WOL_RESULT_NOSPC_ERR 1 +#define WOL_RESULT_EEXIST_ERR 2 + +/* Misc constants */ +/* This section defines 802.11 specific contants */ + +#define MRVDRV_MAX_BSS_DESCRIPTS 16 +#define MRVDRV_MAX_REGION_CODE 6 + +#define MRVDRV_DEFAULT_LISTEN_INTERVAL 10 + +#define MRVDRV_CHANNELS_PER_SCAN 4 +#define MRVDRV_MAX_CHANNELS_PER_SCAN 14 + +#define MRVDRV_MIN_BEACON_INTERVAL 20 +#define MRVDRV_MAX_BEACON_INTERVAL 1000 +#define MRVDRV_BEACON_INTERVAL 100 + +#define MARVELL_MESH_IE_LENGTH 9 + +/* + * Values used to populate the struct mrvl_mesh_ie. The only time you need this + * is when enabling the mesh using CMD_MESH_CONFIG. + */ +#define MARVELL_MESH_IE_TYPE 4 +#define MARVELL_MESH_IE_SUBTYPE 0 +#define MARVELL_MESH_IE_VERSION 0 +#define MARVELL_MESH_PROTO_ID_HWMP 0 +#define MARVELL_MESH_METRIC_ID 0 +#define MARVELL_MESH_CAPABILITY 0 + +/* INT status Bit Definition */ +#define MRVDRV_TX_DNLD_RDY 0x0001 +#define MRVDRV_RX_UPLD_RDY 0x0002 +#define MRVDRV_CMD_DNLD_RDY 0x0004 +#define MRVDRV_CMD_UPLD_RDY 0x0008 +#define MRVDRV_CARDEVENT 0x0010 + +/* Automatic TX control default levels */ +#define POW_ADAPT_DEFAULT_P0 13 +#define POW_ADAPT_DEFAULT_P1 15 +#define POW_ADAPT_DEFAULT_P2 18 +#define TPC_DEFAULT_P0 5 +#define TPC_DEFAULT_P1 10 +#define TPC_DEFAULT_P2 13 + +/* TxPD status */ + +/* + * Station firmware use TxPD status field to report final Tx transmit + * result, Bit masks are used to present combined situations. + */ + +#define MRVDRV_TxPD_POWER_MGMT_NULL_PACKET 0x01 +#define MRVDRV_TxPD_POWER_MGMT_LAST_PACKET 0x08 + +/* Tx mesh flag */ +/* + * Currently we are using normal WDS flag as mesh flag. + * TODO: change to proper mesh flag when MAC understands it. + */ +#define TxPD_CONTROL_WDS_FRAME (1<<17) +#define TxPD_MESH_FRAME TxPD_CONTROL_WDS_FRAME + +/* Mesh interface ID */ +#define MESH_IFACE_ID 0x0001 +/* Mesh id should be in bits 14-13-12 */ +#define MESH_IFACE_BIT_OFFSET 0x000c +/* Mesh enable bit in FW capability */ +#define MESH_CAPINFO_ENABLE_MASK (1<<16) + +/* FW definition from Marvell v4 */ +#define MRVL_FW_V4 (0x04) +/* FW definition from Marvell v5 */ +#define MRVL_FW_V5 (0x05) +/* FW definition from Marvell v10 */ +#define MRVL_FW_V10 (0x0a) +/* FW major revision definition */ +#define MRVL_FW_MAJOR_REV(x) ((x)>>24) + +/* RxPD status */ + +#define MRVDRV_RXPD_STATUS_OK 0x0001 + +/* RxPD status - Received packet types */ +/* Rx mesh flag */ +/* + * Currently we are using normal WDS flag as mesh flag. + * TODO: change to proper mesh flag when MAC understands it. + */ +#define RxPD_CONTROL_WDS_FRAME (0x40) +#define RxPD_MESH_FRAME RxPD_CONTROL_WDS_FRAME + +/* RSSI-related defines */ +/* + * RSSI constants are used to implement 802.11 RSSI threshold + * indication. if the Rx packet signal got too weak for 5 consecutive + * times, miniport driver (driver) will report this event to wrapper + */ + +#define MRVDRV_NF_DEFAULT_SCAN_VALUE (-96) + +/* RTS/FRAG related defines */ +#define MRVDRV_RTS_MIN_VALUE 0 +#define MRVDRV_RTS_MAX_VALUE 2347 +#define MRVDRV_FRAG_MIN_VALUE 256 +#define MRVDRV_FRAG_MAX_VALUE 2346 + +/* This is for firmware specific length */ +#define EXTRA_LEN 36 + +#define MRVDRV_ETH_TX_PACKET_BUFFER_SIZE \ + (ETH_FRAME_LEN + sizeof(struct txpd) + EXTRA_LEN) + +#define MRVDRV_ETH_RX_PACKET_BUFFER_SIZE \ + (ETH_FRAME_LEN + sizeof(struct rxpd) \ + + MRVDRV_SNAP_HEADER_LEN + EXTRA_LEN) + +#define CMD_F_HOSTCMD (1 << 0) +#define FW_CAPINFO_WPA (1 << 0) +#define FW_CAPINFO_PS (1 << 1) +#define FW_CAPINFO_FIRMWARE_UPGRADE (1 << 13) +#define FW_CAPINFO_BOOT2_UPGRADE (1<<14) +#define FW_CAPINFO_PERSISTENT_CONFIG (1<<15) + +#define KEY_LEN_WPA_AES 16 +#define KEY_LEN_WPA_TKIP 32 +#define KEY_LEN_WEP_104 13 +#define KEY_LEN_WEP_40 5 + +#define RF_ANTENNA_1 0x1 +#define RF_ANTENNA_2 0x2 +#define RF_ANTENNA_AUTO 0xFFFF + +#define BAND_B (0x01) +#define BAND_G (0x02) +#define ALL_802_11_BANDS (BAND_B | BAND_G) + +#define MAX_RATES 14 + +#define MAX_LEDS 8 + +/* Global Variable Declaration */ +extern const char lbs_driver_version[]; +extern u16 lbs_region_code_to_index[MRVDRV_MAX_REGION_CODE]; + + +/* ENUM definition */ +/* SNRNF_TYPE */ +enum SNRNF_TYPE { + TYPE_BEACON = 0, + TYPE_RXPD, + MAX_TYPE_B +}; + +/* SNRNF_DATA */ +enum SNRNF_DATA { + TYPE_NOAVG = 0, + TYPE_AVG, + MAX_TYPE_AVG +}; + +/* LBS_802_11_POWER_MODE */ +enum LBS_802_11_POWER_MODE { + LBS802_11POWERMODECAM, + LBS802_11POWERMODEMAX_PSP, + LBS802_11POWERMODEFAST_PSP, + /* not a real mode, defined as an upper bound */ + LBS802_11POWEMODEMAX +}; + +/* PS_STATE */ +enum PS_STATE { + PS_STATE_FULL_POWER, + PS_STATE_AWAKE, + PS_STATE_PRE_SLEEP, + PS_STATE_SLEEP +}; + +/* DNLD_STATE */ +enum DNLD_STATE { + DNLD_RES_RECEIVED, + DNLD_DATA_SENT, + DNLD_CMD_SENT, + DNLD_BOOTCMD_SENT, +}; + +/* LBS_MEDIA_STATE */ +enum LBS_MEDIA_STATE { + LBS_CONNECTED, + LBS_DISCONNECTED +}; + +/* LBS_802_11_PRIVACY_FILTER */ +enum LBS_802_11_PRIVACY_FILTER { + LBS802_11PRIVFILTERACCEPTALL, + LBS802_11PRIVFILTER8021XWEP +}; + +/* mv_ms_type */ +enum mv_ms_type { + MVMS_DAT = 0, + MVMS_CMD = 1, + MVMS_TXDONE = 2, + MVMS_EVENT +}; + +/* KEY_TYPE_ID */ +enum KEY_TYPE_ID { + KEY_TYPE_ID_WEP = 0, + KEY_TYPE_ID_TKIP, + KEY_TYPE_ID_AES +}; + +/* KEY_INFO_WPA (applies to both TKIP and AES/CCMP) */ +enum KEY_INFO_WPA { + KEY_INFO_WPA_MCAST = 0x01, + KEY_INFO_WPA_UNICAST = 0x02, + KEY_INFO_WPA_ENABLED = 0x04 +}; + +/* Default values for fwt commands. */ +#define FWT_DEFAULT_METRIC 0 +#define FWT_DEFAULT_DIR 1 +/* Default Rate, 11Mbps */ +#define FWT_DEFAULT_RATE 3 +#define FWT_DEFAULT_SSN 0xffffffff +#define FWT_DEFAULT_DSN 0 +#define FWT_DEFAULT_HOPCOUNT 0 +#define FWT_DEFAULT_TTL 0 +#define FWT_DEFAULT_EXPIRATION 0 +#define FWT_DEFAULT_SLEEPMODE 0 +#define FWT_DEFAULT_SNR 0 + +#endif diff --git a/drivers/net/wireless/libertas/dev.h b/drivers/net/wireless/libertas/dev.h new file mode 100644 index 0000000..721ef85 --- /dev/null +++ b/drivers/net/wireless/libertas/dev.h @@ -0,0 +1,211 @@ +/* + * This file contains definitions and data structures specific + * to Marvell 802.11 NIC. It contains the Device Information + * structure struct lbs_private.. + */ +#ifndef _LBS_DEV_H_ +#define _LBS_DEV_H_ + +#include "defs.h" +#include "decl.h" +#include "host.h" + +#include + +/* sleep_params */ +struct sleep_params { + uint16_t sp_error; + uint16_t sp_offset; + uint16_t sp_stabletime; + uint8_t sp_calcontrol; + uint8_t sp_extsleepclk; + uint16_t sp_reserved; +}; + +/* Mesh statistics */ +struct lbs_mesh_stats { + u32 fwd_bcast_cnt; /* Fwd: Broadcast counter */ + u32 fwd_unicast_cnt; /* Fwd: Unicast counter */ + u32 fwd_drop_ttl; /* Fwd: TTL zero */ + u32 fwd_drop_rbt; /* Fwd: Recently Broadcasted */ + u32 fwd_drop_noroute; /* Fwd: No route to Destination */ + u32 fwd_drop_nobuf; /* Fwd: Run out of internal buffers */ + u32 drop_blind; /* Rx: Dropped by blinding table */ + u32 tx_failed_cnt; /* Tx: Failed transmissions */ +}; + +/* Private structure for the MV device */ +struct lbs_private { + + /* Basic networking */ + struct net_device *dev; + u32 connect_status; + struct work_struct mcast_work; + u32 nr_of_multicastmacaddr; + u8 multicastlist[MRVDRV_MAX_MULTICAST_LIST_SIZE][ETH_ALEN]; + + /* CFG80211 */ + struct wireless_dev *wdev; + bool wiphy_registered; + struct cfg80211_scan_request *scan_req; + u8 assoc_bss[ETH_ALEN]; + u8 country_code[IEEE80211_COUNTRY_STRING_LEN]; + u8 disassoc_reason; + + /* Mesh */ + struct net_device *mesh_dev; /* Virtual device */ +#ifdef CPTCFG_LIBERTAS_MESH + struct lbs_mesh_stats mstats; + uint16_t mesh_tlv; + u8 mesh_ssid[IEEE80211_MAX_SSID_LEN + 1]; + u8 mesh_ssid_len; + u8 mesh_channel; +#endif + + /* Debugfs */ + struct dentry *debugfs_dir; + struct dentry *debugfs_debug; + struct dentry *debugfs_files[6]; + struct dentry *events_dir; + struct dentry *debugfs_events_files[6]; + struct dentry *regs_dir; + struct dentry *debugfs_regs_files[6]; + + /* Hardware debugging */ + u32 mac_offset; + u32 bbp_offset; + u32 rf_offset; + + /* Power management */ + u16 psmode; + u32 psstate; + u8 needtowakeup; + + /* Deep sleep */ + int is_deep_sleep; + int deep_sleep_required; + int is_auto_deep_sleep_enabled; + int wakeup_dev_required; + int is_activity_detected; + int auto_deep_sleep_timeout; /* in ms */ + wait_queue_head_t ds_awake_q; + struct timer_list auto_deepsleep_timer; + + /* Host sleep*/ + int is_host_sleep_configured; + int is_host_sleep_activated; + wait_queue_head_t host_sleep_q; + + /* Hardware access */ + void *card; + bool iface_running; + u8 fw_ready; + u8 surpriseremoved; + u8 setup_fw_on_resume; + int (*hw_host_to_card) (struct lbs_private *priv, u8 type, u8 *payload, u16 nb); + void (*reset_card) (struct lbs_private *priv); + int (*power_save) (struct lbs_private *priv); + int (*power_restore) (struct lbs_private *priv); + int (*enter_deep_sleep) (struct lbs_private *priv); + int (*exit_deep_sleep) (struct lbs_private *priv); + int (*reset_deep_sleep_wakeup) (struct lbs_private *priv); + + /* Adapter info (from EEPROM) */ + u32 fwrelease; + u32 fwcapinfo; + u16 regioncode; + u8 current_addr[ETH_ALEN]; + u8 copied_hwaddr; + + /* Command download */ + u8 dnld_sent; + /* bit0 1/0=data_sent/data_tx_done, + bit1 1/0=cmd_sent/cmd_tx_done, + all other bits reserved 0 */ + u16 seqnum; + struct cmd_ctrl_node *cmd_array; + struct cmd_ctrl_node *cur_cmd; + struct list_head cmdfreeq; /* free command buffers */ + struct list_head cmdpendingq; /* pending command buffers */ + struct timer_list command_timer; + int cmd_timed_out; + + /* Command responses sent from the hardware to the driver */ + u8 resp_idx; + u8 resp_buf[2][LBS_UPLD_SIZE]; + u32 resp_len[2]; + + /* Events sent from hardware to driver */ + struct kfifo event_fifo; + + /* thread to service interrupts */ + struct task_struct *main_thread; + wait_queue_head_t waitq; + struct workqueue_struct *work_thread; + + /* Encryption stuff */ + u8 authtype_auto; + u8 wep_tx_key; + u8 wep_key[4][WLAN_KEY_LEN_WEP104]; + u8 wep_key_len[4]; + + /* Wake On LAN */ + uint32_t wol_criteria; + uint8_t wol_gpio; + uint8_t wol_gap; + bool ehs_remove_supported; + + /* Transmitting */ + int tx_pending_len; /* -1 while building packet */ + u8 tx_pending_buf[LBS_UPLD_SIZE]; + /* protected by hard_start_xmit serialization */ + u8 txretrycount; + struct sk_buff *currenttxskb; + struct timer_list tx_lockup_timer; + + /* Locks */ + struct mutex lock; + spinlock_t driver_lock; + + /* NIC/link operation characteristics */ + u16 mac_control; + u8 radio_on; + u8 cur_rate; + u8 channel; + s16 txpower_cur; + s16 txpower_min; + s16 txpower_max; + + /* Scanning */ + struct delayed_work scan_work; + int scan_channel; + /* Queue of things waiting for scan completion */ + wait_queue_head_t scan_q; + /* Whether the scan was initiated internally and not by cfg80211 */ + bool internal_scan; + + /* Firmware load */ + u32 fw_model; + wait_queue_head_t fw_waitq; + struct device *fw_device; + const struct firmware *helper_fw; + const struct lbs_fw_table *fw_table; + const struct lbs_fw_table *fw_iter; + lbs_fw_cb fw_callback; +}; + +extern struct cmd_confirm_sleep confirm_sleep; + +/* Check if there is an interface active. */ +static inline int lbs_iface_active(struct lbs_private *priv) +{ + int r; + + r = netif_running(priv->dev); + if (priv->mesh_dev) + r |= netif_running(priv->mesh_dev); + + return r; +} + +#endif diff --git a/drivers/net/wireless/libertas/ethtool.c b/drivers/net/wireless/libertas/ethtool.c new file mode 100644 index 0000000..dec1089 --- /dev/null +++ b/drivers/net/wireless/libertas/ethtool.c @@ -0,0 +1,120 @@ +#include +#include +#include +#include + +#include "decl.h" +#include "cmd.h" +#include "mesh.h" + + +static void lbs_ethtool_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + struct lbs_private *priv = dev->ml_priv; + + snprintf(info->fw_version, sizeof(info->fw_version), + "%u.%u.%u.p%u", + priv->fwrelease >> 24 & 0xff, + priv->fwrelease >> 16 & 0xff, + priv->fwrelease >> 8 & 0xff, + priv->fwrelease & 0xff); + strlcpy(info->driver, "libertas", sizeof(info->driver)); + strlcpy(info->version, lbs_driver_version, sizeof(info->version)); +} + +/* + * All 8388 parts have 16KiB EEPROM size at the time of writing. + * In case that changes this needs fixing. + */ +#define LBS_EEPROM_LEN 16384 + +static int lbs_ethtool_get_eeprom_len(struct net_device *dev) +{ + return LBS_EEPROM_LEN; +} + +static int lbs_ethtool_get_eeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 * bytes) +{ + struct lbs_private *priv = dev->ml_priv; + struct cmd_ds_802_11_eeprom_access cmd; + int ret; + + lbs_deb_enter(LBS_DEB_ETHTOOL); + + if (eeprom->offset + eeprom->len > LBS_EEPROM_LEN || + eeprom->len > LBS_EEPROM_READ_LEN) { + ret = -EINVAL; + goto out; + } + + cmd.hdr.size = cpu_to_le16(sizeof(struct cmd_ds_802_11_eeprom_access) - + LBS_EEPROM_READ_LEN + eeprom->len); + cmd.action = cpu_to_le16(CMD_ACT_GET); + cmd.offset = cpu_to_le16(eeprom->offset); + cmd.len = cpu_to_le16(eeprom->len); + ret = lbs_cmd_with_response(priv, CMD_802_11_EEPROM_ACCESS, &cmd); + if (!ret) + memcpy(bytes, cmd.value, eeprom->len); + +out: + lbs_deb_leave_args(LBS_DEB_ETHTOOL, "ret %d", ret); + return ret; +} + +static void lbs_ethtool_get_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct lbs_private *priv = dev->ml_priv; + + wol->supported = WAKE_UCAST|WAKE_MCAST|WAKE_BCAST|WAKE_PHY; + + if (priv->wol_criteria == EHS_REMOVE_WAKEUP) + return; + + if (priv->wol_criteria & EHS_WAKE_ON_UNICAST_DATA) + wol->wolopts |= WAKE_UCAST; + if (priv->wol_criteria & EHS_WAKE_ON_MULTICAST_DATA) + wol->wolopts |= WAKE_MCAST; + if (priv->wol_criteria & EHS_WAKE_ON_BROADCAST_DATA) + wol->wolopts |= WAKE_BCAST; + if (priv->wol_criteria & EHS_WAKE_ON_MAC_EVENT) + wol->wolopts |= WAKE_PHY; +} + +static int lbs_ethtool_set_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct lbs_private *priv = dev->ml_priv; + + if (wol->wolopts & ~(WAKE_UCAST|WAKE_MCAST|WAKE_BCAST|WAKE_PHY)) + return -EOPNOTSUPP; + + priv->wol_criteria = 0; + if (wol->wolopts & WAKE_UCAST) + priv->wol_criteria |= EHS_WAKE_ON_UNICAST_DATA; + if (wol->wolopts & WAKE_MCAST) + priv->wol_criteria |= EHS_WAKE_ON_MULTICAST_DATA; + if (wol->wolopts & WAKE_BCAST) + priv->wol_criteria |= EHS_WAKE_ON_BROADCAST_DATA; + if (wol->wolopts & WAKE_PHY) + priv->wol_criteria |= EHS_WAKE_ON_MAC_EVENT; + if (wol->wolopts == 0) + priv->wol_criteria |= EHS_REMOVE_WAKEUP; + return 0; +} + +const struct ethtool_ops lbs_ethtool_ops = { + .get_drvinfo = lbs_ethtool_get_drvinfo, + .get_eeprom = lbs_ethtool_get_eeprom, + .get_eeprom_len = lbs_ethtool_get_eeprom_len, +#ifdef CPTCFG_LIBERTAS_MESH + .get_sset_count = lbs_mesh_ethtool_get_sset_count, + .get_ethtool_stats = lbs_mesh_ethtool_get_stats, + .get_strings = lbs_mesh_ethtool_get_strings, +#endif + .get_wol = lbs_ethtool_get_wol, + .set_wol = lbs_ethtool_set_wol, +}; + diff --git a/drivers/net/wireless/libertas/firmware.c b/drivers/net/wireless/libertas/firmware.c new file mode 100644 index 0000000..c0f9e7e --- /dev/null +++ b/drivers/net/wireless/libertas/firmware.c @@ -0,0 +1,222 @@ +/* + * Firmware loading and handling functions. + */ + +#include +#include +#include + +#include "dev.h" +#include "decl.h" + +static void load_next_firmware_from_table(struct lbs_private *private); + +static void lbs_fw_loaded(struct lbs_private *priv, int ret, + const struct firmware *helper, const struct firmware *mainfw) +{ + unsigned long flags; + + lbs_deb_fw("firmware load complete, code %d\n", ret); + + /* User must free helper/mainfw */ + priv->fw_callback(priv, ret, helper, mainfw); + + spin_lock_irqsave(&priv->driver_lock, flags); + priv->fw_callback = NULL; + wake_up(&priv->fw_waitq); + spin_unlock_irqrestore(&priv->driver_lock, flags); +} + +static void do_load_firmware(struct lbs_private *priv, const char *name, + void (*cb)(const struct firmware *fw, void *context)) +{ + int ret; + + lbs_deb_fw("Requesting %s\n", name); + ret = request_firmware_nowait(THIS_MODULE, true, name, + priv->fw_device, GFP_KERNEL, priv, cb); + if (ret) { + lbs_deb_fw("request_firmware_nowait error %d\n", ret); + lbs_fw_loaded(priv, ret, NULL, NULL); + } +} + +static void main_firmware_cb(const struct firmware *firmware, void *context) +{ + struct lbs_private *priv = context; + + if (!firmware) { + /* Failed to find firmware: try next table entry */ + load_next_firmware_from_table(priv); + return; + } + + /* Firmware found! */ + lbs_fw_loaded(priv, 0, priv->helper_fw, firmware); +} + +static void helper_firmware_cb(const struct firmware *firmware, void *context) +{ + struct lbs_private *priv = context; + + if (!firmware) { + /* Failed to find firmware: try next table entry */ + load_next_firmware_from_table(priv); + return; + } + + /* Firmware found! */ + if (priv->fw_iter->fwname) { + priv->helper_fw = firmware; + do_load_firmware(priv, priv->fw_iter->fwname, main_firmware_cb); + } else { + /* No main firmware needed for this helper --> success! */ + lbs_fw_loaded(priv, 0, firmware, NULL); + } +} + +static void load_next_firmware_from_table(struct lbs_private *priv) +{ + const struct lbs_fw_table *iter; + + if (!priv->fw_iter) + iter = priv->fw_table; + else + iter = ++priv->fw_iter; + + if (priv->helper_fw) { + release_firmware(priv->helper_fw); + priv->helper_fw = NULL; + } + +next: + if (!iter->helper) { + /* End of table hit. */ + lbs_fw_loaded(priv, -ENOENT, NULL, NULL); + return; + } + + if (iter->model != priv->fw_model) { + iter++; + goto next; + } + + priv->fw_iter = iter; + do_load_firmware(priv, iter->helper, helper_firmware_cb); +} + +void lbs_wait_for_firmware_load(struct lbs_private *priv) +{ + wait_event(priv->fw_waitq, priv->fw_callback == NULL); +} + +/** + * lbs_get_firmware_async - Retrieves firmware asynchronously. Can load + * either a helper firmware and a main firmware (2-stage), or just the helper. + * + * @priv: Pointer to lbs_private instance + * @dev: A pointer to &device structure + * @card_model: Bus-specific card model ID used to filter firmware table + * elements + * @fw_table: Table of firmware file names and device model numbers + * terminated by an entry with a NULL helper name + * @callback: User callback to invoke when firmware load succeeds or fails. + */ +int lbs_get_firmware_async(struct lbs_private *priv, struct device *device, + u32 card_model, const struct lbs_fw_table *fw_table, + lbs_fw_cb callback) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->driver_lock, flags); + if (priv->fw_callback) { + lbs_deb_fw("firmware load already in progress\n"); + spin_unlock_irqrestore(&priv->driver_lock, flags); + return -EBUSY; + } + + priv->fw_device = device; + priv->fw_callback = callback; + priv->fw_table = fw_table; + priv->fw_iter = NULL; + priv->fw_model = card_model; + spin_unlock_irqrestore(&priv->driver_lock, flags); + + lbs_deb_fw("Starting async firmware load\n"); + load_next_firmware_from_table(priv); + return 0; +} +EXPORT_SYMBOL_GPL(lbs_get_firmware_async); + +/** + * lbs_get_firmware - Retrieves two-stage firmware + * + * @dev: A pointer to &device structure + * @card_model: Bus-specific card model ID used to filter firmware table + * elements + * @fw_table: Table of firmware file names and device model numbers + * terminated by an entry with a NULL helper name + * @helper: On success, the helper firmware; caller must free + * @mainfw: On success, the main firmware; caller must free + * + * Deprecated: use lbs_get_firmware_async() instead. + * + * returns: 0 on success, non-zero on failure + */ +int lbs_get_firmware(struct device *dev, u32 card_model, + const struct lbs_fw_table *fw_table, + const struct firmware **helper, + const struct firmware **mainfw) +{ + const struct lbs_fw_table *iter; + int ret; + + BUG_ON(helper == NULL); + BUG_ON(mainfw == NULL); + + /* Search for firmware to use from the table. */ + iter = fw_table; + while (iter && iter->helper) { + if (iter->model != card_model) + goto next; + + if (*helper == NULL) { + ret = request_firmware(helper, iter->helper, dev); + if (ret) + goto next; + + /* If the device has one-stage firmware (ie cf8305) and + * we've got it then we don't need to bother with the + * main firmware. + */ + if (iter->fwname == NULL) + return 0; + } + + if (*mainfw == NULL) { + ret = request_firmware(mainfw, iter->fwname, dev); + if (ret) { + /* Clear the helper to ensure we don't have + * mismatched firmware pairs. + */ + release_firmware(*helper); + *helper = NULL; + } + } + + if (*helper && *mainfw) + return 0; + + next: + iter++; + } + + /* Failed */ + release_firmware(*helper); + *helper = NULL; + release_firmware(*mainfw); + *mainfw = NULL; + + return -ENOENT; +} +EXPORT_SYMBOL_GPL(lbs_get_firmware); diff --git a/drivers/net/wireless/libertas/host.h b/drivers/net/wireless/libertas/host.h new file mode 100644 index 0000000..96726f7 --- /dev/null +++ b/drivers/net/wireless/libertas/host.h @@ -0,0 +1,978 @@ +/* + * This file function prototypes, data structure + * and definitions for all the host/station commands + */ + +#ifndef _LBS_HOST_H_ +#define _LBS_HOST_H_ + +#include "types.h" +#include "defs.h" + +#define DEFAULT_AD_HOC_CHANNEL 6 + +#define CMD_OPTION_WAITFORRSP 0x0002 + +/* Host command IDs */ + +/* + * Return command are almost always the same as the host command, but with + * bit 15 set high. There are a few exceptions, though... + */ +#define CMD_RET(cmd) (0x8000 | cmd) + +/* Return command convention exceptions: */ +#define CMD_RET_802_11_ASSOCIATE 0x8012 + +/* Command codes */ +#define CMD_GET_HW_SPEC 0x0003 +#define CMD_EEPROM_UPDATE 0x0004 +#define CMD_802_11_RESET 0x0005 +#define CMD_802_11_SCAN 0x0006 +#define CMD_802_11_GET_LOG 0x000b +#define CMD_MAC_MULTICAST_ADR 0x0010 +#define CMD_802_11_AUTHENTICATE 0x0011 +#define CMD_802_11_EEPROM_ACCESS 0x0059 +#define CMD_802_11_ASSOCIATE 0x0050 +#define CMD_802_11_SET_WEP 0x0013 +#define CMD_802_11_GET_STAT 0x0014 +#define CMD_802_3_GET_STAT 0x0015 +#define CMD_802_11_SNMP_MIB 0x0016 +#define CMD_MAC_REG_MAP 0x0017 +#define CMD_BBP_REG_MAP 0x0018 +#define CMD_MAC_REG_ACCESS 0x0019 +#define CMD_BBP_REG_ACCESS 0x001a +#define CMD_RF_REG_ACCESS 0x001b +#define CMD_802_11_RADIO_CONTROL 0x001c +#define CMD_802_11_RF_CHANNEL 0x001d +#define CMD_802_11_RF_TX_POWER 0x001e +#define CMD_802_11_RSSI 0x001f +#define CMD_802_11_RF_ANTENNA 0x0020 +#define CMD_802_11_PS_MODE 0x0021 +#define CMD_802_11_DATA_RATE 0x0022 +#define CMD_RF_REG_MAP 0x0023 +#define CMD_802_11_DEAUTHENTICATE 0x0024 +#define CMD_802_11_REASSOCIATE 0x0025 +#define CMD_MAC_CONTROL 0x0028 +#define CMD_802_11_AD_HOC_START 0x002b +#define CMD_802_11_AD_HOC_JOIN 0x002c +#define CMD_802_11_QUERY_TKIP_REPLY_CNTRS 0x002e +#define CMD_802_11_ENABLE_RSN 0x002f +#define CMD_802_11_SET_AFC 0x003c +#define CMD_802_11_GET_AFC 0x003d +#define CMD_802_11_DEEP_SLEEP 0x003e +#define CMD_802_11_AD_HOC_STOP 0x0040 +#define CMD_802_11_HOST_SLEEP_CFG 0x0043 +#define CMD_802_11_WAKEUP_CONFIRM 0x0044 +#define CMD_802_11_HOST_SLEEP_ACTIVATE 0x0045 +#define CMD_802_11_BEACON_STOP 0x0049 +#define CMD_802_11_MAC_ADDRESS 0x004d +#define CMD_802_11_LED_GPIO_CTRL 0x004e +#define CMD_802_11_BAND_CONFIG 0x0058 +#define CMD_GSPI_BUS_CONFIG 0x005a +#define CMD_802_11D_DOMAIN_INFO 0x005b +#define CMD_802_11_KEY_MATERIAL 0x005e +#define CMD_802_11_SLEEP_PARAMS 0x0066 +#define CMD_802_11_INACTIVITY_TIMEOUT 0x0067 +#define CMD_802_11_SLEEP_PERIOD 0x0068 +#define CMD_802_11_TPC_CFG 0x0072 +#define CMD_802_11_PA_CFG 0x0073 +#define CMD_802_11_FW_WAKE_METHOD 0x0074 +#define CMD_802_11_SUBSCRIBE_EVENT 0x0075 +#define CMD_802_11_RATE_ADAPT_RATESET 0x0076 +#define CMD_802_11_TX_RATE_QUERY 0x007f +#define CMD_GET_TSF 0x0080 +#define CMD_BT_ACCESS 0x0087 +#define CMD_FWT_ACCESS 0x0095 +#define CMD_802_11_MONITOR_MODE 0x0098 +#define CMD_MESH_ACCESS 0x009b +#define CMD_MESH_CONFIG_OLD 0x00a3 +#define CMD_MESH_CONFIG 0x00ac +#define CMD_SET_BOOT2_VER 0x00a5 +#define CMD_FUNC_INIT 0x00a9 +#define CMD_FUNC_SHUTDOWN 0x00aa +#define CMD_802_11_BEACON_CTRL 0x00b0 + +/* For the IEEE Power Save */ +#define PS_MODE_ACTION_ENTER_PS 0x0030 +#define PS_MODE_ACTION_EXIT_PS 0x0031 +#define PS_MODE_ACTION_SLEEP_CONFIRMED 0x0034 + +#define CMD_ENABLE_RSN 0x0001 +#define CMD_DISABLE_RSN 0x0000 + +#define CMD_ACT_GET 0x0000 +#define CMD_ACT_SET 0x0001 + +/* Define action or option for CMD_802_11_SET_WEP */ +#define CMD_ACT_ADD 0x0002 +#define CMD_ACT_REMOVE 0x0004 + +#define CMD_TYPE_WEP_40_BIT 0x01 +#define CMD_TYPE_WEP_104_BIT 0x02 + +#define CMD_NUM_OF_WEP_KEYS 4 + +#define CMD_WEP_KEY_INDEX_MASK 0x3fff + +/* Define action or option for CMD_802_11_SCAN */ +#define CMD_BSS_TYPE_BSS 0x0001 +#define CMD_BSS_TYPE_IBSS 0x0002 +#define CMD_BSS_TYPE_ANY 0x0003 + +/* Define action or option for CMD_802_11_SCAN */ +#define CMD_SCAN_TYPE_ACTIVE 0x0000 +#define CMD_SCAN_TYPE_PASSIVE 0x0001 + +#define CMD_SCAN_RADIO_TYPE_BG 0 + +#define CMD_SCAN_PROBE_DELAY_TIME 0 + +/* Define action or option for CMD_MAC_CONTROL */ +#define CMD_ACT_MAC_RX_ON 0x0001 +#define CMD_ACT_MAC_TX_ON 0x0002 +#define CMD_ACT_MAC_LOOPBACK_ON 0x0004 +#define CMD_ACT_MAC_WEP_ENABLE 0x0008 +#define CMD_ACT_MAC_INT_ENABLE 0x0010 +#define CMD_ACT_MAC_MULTICAST_ENABLE 0x0020 +#define CMD_ACT_MAC_BROADCAST_ENABLE 0x0040 +#define CMD_ACT_MAC_PROMISCUOUS_ENABLE 0x0080 +#define CMD_ACT_MAC_ALL_MULTICAST_ENABLE 0x0100 +#define CMD_ACT_MAC_STRICT_PROTECTION_ENABLE 0x0400 + +/* Event flags for CMD_802_11_SUBSCRIBE_EVENT */ +#define CMD_SUBSCRIBE_RSSI_LOW 0x0001 +#define CMD_SUBSCRIBE_SNR_LOW 0x0002 +#define CMD_SUBSCRIBE_FAILCOUNT 0x0004 +#define CMD_SUBSCRIBE_BCNMISS 0x0008 +#define CMD_SUBSCRIBE_RSSI_HIGH 0x0010 +#define CMD_SUBSCRIBE_SNR_HIGH 0x0020 + +#define RADIO_PREAMBLE_LONG 0x00 +#define RADIO_PREAMBLE_SHORT 0x02 +#define RADIO_PREAMBLE_AUTO 0x04 + +/* Define action or option for CMD_802_11_RF_CHANNEL */ +#define CMD_OPT_802_11_RF_CHANNEL_GET 0x00 +#define CMD_OPT_802_11_RF_CHANNEL_SET 0x01 + +/* Define action or option for CMD_802_11_DATA_RATE */ +#define CMD_ACT_SET_TX_AUTO 0x0000 +#define CMD_ACT_SET_TX_FIX_RATE 0x0001 +#define CMD_ACT_GET_TX_RATE 0x0002 + +/* Options for CMD_802_11_FW_WAKE_METHOD */ +#define CMD_WAKE_METHOD_UNCHANGED 0x0000 +#define CMD_WAKE_METHOD_COMMAND_INT 0x0001 +#define CMD_WAKE_METHOD_GPIO 0x0002 + +/* Object IDs for CMD_802_11_SNMP_MIB */ +#define SNMP_MIB_OID_BSS_TYPE 0x0000 +#define SNMP_MIB_OID_OP_RATE_SET 0x0001 +#define SNMP_MIB_OID_BEACON_PERIOD 0x0002 /* Reserved on v9+ */ +#define SNMP_MIB_OID_DTIM_PERIOD 0x0003 /* Reserved on v9+ */ +#define SNMP_MIB_OID_ASSOC_TIMEOUT 0x0004 /* Reserved on v9+ */ +#define SNMP_MIB_OID_RTS_THRESHOLD 0x0005 +#define SNMP_MIB_OID_SHORT_RETRY_LIMIT 0x0006 +#define SNMP_MIB_OID_LONG_RETRY_LIMIT 0x0007 +#define SNMP_MIB_OID_FRAG_THRESHOLD 0x0008 +#define SNMP_MIB_OID_11D_ENABLE 0x0009 +#define SNMP_MIB_OID_11H_ENABLE 0x000A + +/* Define action or option for CMD_BT_ACCESS */ +enum cmd_bt_access_opts { + /* The bt commands start at 5 instead of 1 because the old dft commands + * are mapped to 1-4. These old commands are no longer maintained and + * should not be called. + */ + CMD_ACT_BT_ACCESS_ADD = 5, + CMD_ACT_BT_ACCESS_DEL, + CMD_ACT_BT_ACCESS_LIST, + CMD_ACT_BT_ACCESS_RESET, + CMD_ACT_BT_ACCESS_SET_INVERT, + CMD_ACT_BT_ACCESS_GET_INVERT +}; + +/* Define action or option for CMD_FWT_ACCESS */ +enum cmd_fwt_access_opts { + CMD_ACT_FWT_ACCESS_ADD = 1, + CMD_ACT_FWT_ACCESS_DEL, + CMD_ACT_FWT_ACCESS_LOOKUP, + CMD_ACT_FWT_ACCESS_LIST, + CMD_ACT_FWT_ACCESS_LIST_ROUTE, + CMD_ACT_FWT_ACCESS_LIST_NEIGHBOR, + CMD_ACT_FWT_ACCESS_RESET, + CMD_ACT_FWT_ACCESS_CLEANUP, + CMD_ACT_FWT_ACCESS_TIME, +}; + +/* Define action or option for CMD_802_11_HOST_SLEEP_CFG */ +enum cmd_wol_cfg_opts { + CMD_ACT_ACTION_NONE = 0, + CMD_ACT_SET_WOL_RULE, + CMD_ACT_GET_WOL_RULE, + CMD_ACT_RESET_WOL_RULE, +}; + +/* Define action or option for CMD_MESH_ACCESS */ +enum cmd_mesh_access_opts { + CMD_ACT_MESH_GET_TTL = 1, + CMD_ACT_MESH_SET_TTL, + CMD_ACT_MESH_GET_STATS, + CMD_ACT_MESH_GET_ANYCAST, + CMD_ACT_MESH_SET_ANYCAST, + CMD_ACT_MESH_SET_LINK_COSTS, + CMD_ACT_MESH_GET_LINK_COSTS, + CMD_ACT_MESH_SET_BCAST_RATE, + CMD_ACT_MESH_GET_BCAST_RATE, + CMD_ACT_MESH_SET_RREQ_DELAY, + CMD_ACT_MESH_GET_RREQ_DELAY, + CMD_ACT_MESH_SET_ROUTE_EXP, + CMD_ACT_MESH_GET_ROUTE_EXP, + CMD_ACT_MESH_SET_AUTOSTART_ENABLED, + CMD_ACT_MESH_GET_AUTOSTART_ENABLED, + CMD_ACT_MESH_SET_GET_PRB_RSP_LIMIT = 17, +}; + +/* Define actions and types for CMD_MESH_CONFIG */ +enum cmd_mesh_config_actions { + CMD_ACT_MESH_CONFIG_STOP = 0, + CMD_ACT_MESH_CONFIG_START, + CMD_ACT_MESH_CONFIG_SET, + CMD_ACT_MESH_CONFIG_GET, +}; + +enum cmd_mesh_config_types { + CMD_TYPE_MESH_SET_BOOTFLAG = 1, + CMD_TYPE_MESH_SET_BOOTTIME, + CMD_TYPE_MESH_SET_DEF_CHANNEL, + CMD_TYPE_MESH_SET_MESH_IE, + CMD_TYPE_MESH_GET_DEFAULTS, + CMD_TYPE_MESH_GET_MESH_IE, /* GET_DEFAULTS is superset of GET_MESHIE */ +}; + +/* Card Event definition */ +#define MACREG_INT_CODE_TX_PPA_FREE 0 +#define MACREG_INT_CODE_TX_DMA_DONE 1 +#define MACREG_INT_CODE_LINK_LOST_W_SCAN 2 +#define MACREG_INT_CODE_LINK_LOST_NO_SCAN 3 +#define MACREG_INT_CODE_LINK_SENSED 4 +#define MACREG_INT_CODE_CMD_FINISHED 5 +#define MACREG_INT_CODE_MIB_CHANGED 6 +#define MACREG_INT_CODE_INIT_DONE 7 +#define MACREG_INT_CODE_DEAUTHENTICATED 8 +#define MACREG_INT_CODE_DISASSOCIATED 9 +#define MACREG_INT_CODE_PS_AWAKE 10 +#define MACREG_INT_CODE_PS_SLEEP 11 +#define MACREG_INT_CODE_MIC_ERR_MULTICAST 13 +#define MACREG_INT_CODE_MIC_ERR_UNICAST 14 +#define MACREG_INT_CODE_WM_AWAKE 15 +#define MACREG_INT_CODE_DEEP_SLEEP_AWAKE 16 +#define MACREG_INT_CODE_ADHOC_BCN_LOST 17 +#define MACREG_INT_CODE_HOST_AWAKE 18 +#define MACREG_INT_CODE_STOP_TX 19 +#define MACREG_INT_CODE_START_TX 20 +#define MACREG_INT_CODE_CHANNEL_SWITCH 21 +#define MACREG_INT_CODE_MEASUREMENT_RDY 22 +#define MACREG_INT_CODE_WMM_CHANGE 23 +#define MACREG_INT_CODE_BG_SCAN_REPORT 24 +#define MACREG_INT_CODE_RSSI_LOW 25 +#define MACREG_INT_CODE_SNR_LOW 26 +#define MACREG_INT_CODE_MAX_FAIL 27 +#define MACREG_INT_CODE_RSSI_HIGH 28 +#define MACREG_INT_CODE_SNR_HIGH 29 +#define MACREG_INT_CODE_MESH_AUTO_STARTED 35 +#define MACREG_INT_CODE_FIRMWARE_READY 48 + + +/* 802.11-related definitions */ + +/* TxPD descriptor */ +struct txpd { + /* union to cope up with later FW revisions */ + union { + /* Current Tx packet status */ + __le32 tx_status; + struct { + /* BSS type: client, AP, etc. */ + u8 bss_type; + /* BSS number */ + u8 bss_num; + /* Reserved */ + __le16 reserved; + } bss; + } u; + /* Tx control */ + __le32 tx_control; + __le32 tx_packet_location; + /* Tx packet length */ + __le16 tx_packet_length; + /* First 2 byte of destination MAC address */ + u8 tx_dest_addr_high[2]; + /* Last 4 byte of destination MAC address */ + u8 tx_dest_addr_low[4]; + /* Pkt Priority */ + u8 priority; + /* Pkt Trasnit Power control */ + u8 powermgmt; + /* Amount of time the packet has been queued (units = 2ms) */ + u8 pktdelay_2ms; + /* reserved */ + u8 reserved1; +} __packed; + +/* RxPD Descriptor */ +struct rxpd { + /* union to cope up with later FW revisions */ + union { + /* Current Rx packet status */ + __le16 status; + struct { + /* BSS type: client, AP, etc. */ + u8 bss_type; + /* BSS number */ + u8 bss_num; + } __packed bss; + } __packed u; + + /* SNR */ + u8 snr; + + /* Tx control */ + u8 rx_control; + + /* Pkt length */ + __le16 pkt_len; + + /* Noise Floor */ + u8 nf; + + /* Rx Packet Rate */ + u8 rx_rate; + + /* Pkt addr */ + __le32 pkt_ptr; + + /* Next Rx RxPD addr */ + __le32 next_rxpd_ptr; + + /* Pkt Priority */ + u8 priority; + u8 reserved[3]; +} __packed; + +struct cmd_header { + __le16 command; + __le16 size; + __le16 seqnum; + __le16 result; +} __packed; + +/* Generic structure to hold all key types. */ +struct enc_key { + u16 len; + u16 flags; /* KEY_INFO_* from defs.h */ + u16 type; /* KEY_TYPE_* from defs.h */ + u8 key[32]; +}; + +/* lbs_offset_value */ +struct lbs_offset_value { + u32 offset; + u32 value; +} __packed; + +#define MAX_11D_TRIPLETS 83 + +struct mrvl_ie_domain_param_set { + struct mrvl_ie_header header; + + u8 country_code[IEEE80211_COUNTRY_STRING_LEN]; + struct ieee80211_country_ie_triplet triplet[MAX_11D_TRIPLETS]; +} __packed; + +struct cmd_ds_802_11d_domain_info { + struct cmd_header hdr; + + __le16 action; + struct mrvl_ie_domain_param_set domain; +} __packed; + +/* + * Define data structure for CMD_GET_HW_SPEC + * This structure defines the response for the GET_HW_SPEC command + */ +struct cmd_ds_get_hw_spec { + struct cmd_header hdr; + + /* HW Interface version number */ + __le16 hwifversion; + /* HW version number */ + __le16 version; + /* Max number of TxPD FW can handle */ + __le16 nr_txpd; + /* Max no of Multicast address */ + __le16 nr_mcast_adr; + /* MAC address */ + u8 permanentaddr[6]; + + /* region Code */ + __le16 regioncode; + + /* Number of antenna used */ + __le16 nr_antenna; + + /* FW release number, example 0x01030304 = 2.3.4p1 */ + __le32 fwrelease; + + /* Base Address of TxPD queue */ + __le32 wcb_base; + /* Read Pointer of RxPd queue */ + __le32 rxpd_rdptr; + + /* Write Pointer of RxPd queue */ + __le32 rxpd_wrptr; + + /*FW/HW capability */ + __le32 fwcapinfo; +} __packed; + +struct cmd_ds_802_11_subscribe_event { + struct cmd_header hdr; + + __le16 action; + __le16 events; + + /* A TLV to the CMD_802_11_SUBSCRIBE_EVENT command can contain a + * number of TLVs. From the v5.1 manual, those TLVs would add up to + * 40 bytes. However, future firmware might add additional TLVs, so I + * bump this up a bit. + */ + uint8_t tlv[128]; +} __packed; + +/* + * This scan handle Country Information IE(802.11d compliant) + * Define data structure for CMD_802_11_SCAN + */ +struct cmd_ds_802_11_scan { + struct cmd_header hdr; + + uint8_t bsstype; + uint8_t bssid[ETH_ALEN]; + uint8_t tlvbuffer[0]; +} __packed; + +struct cmd_ds_802_11_scan_rsp { + struct cmd_header hdr; + + __le16 bssdescriptsize; + uint8_t nr_sets; + uint8_t bssdesc_and_tlvbuffer[0]; +} __packed; + +struct cmd_ds_802_11_get_log { + struct cmd_header hdr; + + __le32 mcasttxframe; + __le32 failed; + __le32 retry; + __le32 multiretry; + __le32 framedup; + __le32 rtssuccess; + __le32 rtsfailure; + __le32 ackfailure; + __le32 rxfrag; + __le32 mcastrxframe; + __le32 fcserror; + __le32 txframe; + __le32 wepundecryptable; +} __packed; + +struct cmd_ds_mac_control { + struct cmd_header hdr; + __le16 action; + u16 reserved; +} __packed; + +struct cmd_ds_mac_multicast_adr { + struct cmd_header hdr; + __le16 action; + __le16 nr_of_adrs; + u8 maclist[ETH_ALEN * MRVDRV_MAX_MULTICAST_LIST_SIZE]; +} __packed; + +struct cmd_ds_802_11_authenticate { + struct cmd_header hdr; + + u8 bssid[ETH_ALEN]; + u8 authtype; + u8 reserved[10]; +} __packed; + +struct cmd_ds_802_11_deauthenticate { + struct cmd_header hdr; + + u8 macaddr[ETH_ALEN]; + __le16 reasoncode; +} __packed; + +struct cmd_ds_802_11_associate { + struct cmd_header hdr; + + u8 bssid[6]; + __le16 capability; + __le16 listeninterval; + __le16 bcnperiod; + u8 dtimperiod; + u8 iebuf[512]; /* Enough for required and most optional IEs */ +} __packed; + +struct cmd_ds_802_11_associate_response { + struct cmd_header hdr; + + __le16 capability; + __le16 statuscode; + __le16 aid; + u8 iebuf[512]; +} __packed; + +struct cmd_ds_802_11_set_wep { + struct cmd_header hdr; + + /* ACT_ADD, ACT_REMOVE or ACT_ENABLE */ + __le16 action; + + /* key Index selected for Tx */ + __le16 keyindex; + + /* 40, 128bit or TXWEP */ + uint8_t keytype[4]; + uint8_t keymaterial[4][16]; +} __packed; + +struct cmd_ds_802_11_snmp_mib { + struct cmd_header hdr; + + __le16 action; + __le16 oid; + __le16 bufsize; + u8 value[128]; +} __packed; + +struct cmd_ds_reg_access { + struct cmd_header hdr; + + __le16 action; + __le16 offset; + union { + u8 bbp_rf; /* for BBP and RF registers */ + __le32 mac; /* for MAC registers */ + } value; +} __packed; + +struct cmd_ds_802_11_radio_control { + struct cmd_header hdr; + + __le16 action; + __le16 control; +} __packed; + +struct cmd_ds_802_11_beacon_control { + struct cmd_header hdr; + + __le16 action; + __le16 beacon_enable; + __le16 beacon_period; +} __packed; + +struct cmd_ds_802_11_sleep_params { + struct cmd_header hdr; + + /* ACT_GET/ACT_SET */ + __le16 action; + + /* Sleep clock error in ppm */ + __le16 error; + + /* Wakeup offset in usec */ + __le16 offset; + + /* Clock stabilization time in usec */ + __le16 stabletime; + + /* control periodic calibration */ + uint8_t calcontrol; + + /* control the use of external sleep clock */ + uint8_t externalsleepclk; + + /* reserved field, should be set to zero */ + __le16 reserved; +} __packed; + +struct cmd_ds_802_11_rf_channel { + struct cmd_header hdr; + + __le16 action; + __le16 channel; + __le16 rftype; /* unused */ + __le16 reserved; /* unused */ + u8 channellist[32]; /* unused */ +} __packed; + +struct cmd_ds_802_11_rssi { + struct cmd_header hdr; + + /* + * request: number of beacons (N) to average the SNR and NF over + * response: SNR of most recent beacon + */ + __le16 n_or_snr; + + /* + * The following fields are only set in the response. + * In the request these are reserved and should be set to 0. + */ + __le16 nf; /* most recent beacon noise floor */ + __le16 avg_snr; /* average SNR weighted by N from request */ + __le16 avg_nf; /* average noise floor weighted by N from request */ +} __packed; + +struct cmd_ds_802_11_mac_address { + struct cmd_header hdr; + + __le16 action; + u8 macadd[ETH_ALEN]; +} __packed; + +struct cmd_ds_802_11_rf_tx_power { + struct cmd_header hdr; + + __le16 action; + __le16 curlevel; + s8 maxlevel; + s8 minlevel; +} __packed; + +/* MONITOR_MODE only exists in OLPC v5 firmware */ +struct cmd_ds_802_11_monitor_mode { + struct cmd_header hdr; + + __le16 action; + __le16 mode; +} __packed; + +struct cmd_ds_set_boot2_ver { + struct cmd_header hdr; + + __le16 action; + __le16 version; +} __packed; + +struct cmd_ds_802_11_fw_wake_method { + struct cmd_header hdr; + + __le16 action; + __le16 method; +} __packed; + +struct cmd_ds_802_11_ps_mode { + struct cmd_header hdr; + + __le16 action; + + /* + * Interval for keepalive in PS mode: + * 0x0000 = don't change + * 0x001E = firmware default + * 0xFFFF = disable + */ + __le16 nullpktinterval; + + /* + * Number of DTIM intervals to wake up for: + * 0 = don't change + * 1 = firmware default + * 5 = max + */ + __le16 multipledtim; + + __le16 reserved; + __le16 locallisteninterval; + + /* + * AdHoc awake period (FW v9+ only): + * 0 = don't change + * 1 = always awake (IEEE standard behavior) + * 2 - 31 = sleep for (n - 1) periods and awake for 1 period + * 32 - 254 = invalid + * 255 = sleep at each ATIM + */ + __le16 adhoc_awake_period; +} __packed; + +struct cmd_confirm_sleep { + struct cmd_header hdr; + + __le16 action; + __le16 nullpktinterval; + __le16 multipledtim; + __le16 reserved; + __le16 locallisteninterval; +} __packed; + +struct cmd_ds_802_11_data_rate { + struct cmd_header hdr; + + __le16 action; + __le16 reserved; + u8 rates[MAX_RATES]; +} __packed; + +struct cmd_ds_802_11_rate_adapt_rateset { + struct cmd_header hdr; + __le16 action; + __le16 enablehwauto; + __le16 bitmap; +} __packed; + +struct cmd_ds_802_11_ad_hoc_start { + struct cmd_header hdr; + + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 bsstype; + __le16 beaconperiod; + u8 dtimperiod; /* Reserved on v9 and later */ + struct ieee_ie_ibss_param_set ibss; + u8 reserved1[4]; + struct ieee_ie_ds_param_set ds; + u8 reserved2[4]; + __le16 probedelay; /* Reserved on v9 and later */ + __le16 capability; + u8 rates[MAX_RATES]; + u8 tlv_memory_size_pad[100]; +} __packed; + +struct cmd_ds_802_11_ad_hoc_result { + struct cmd_header hdr; + + u8 pad[3]; + u8 bssid[ETH_ALEN]; +} __packed; + +struct adhoc_bssdesc { + u8 bssid[ETH_ALEN]; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 type; + __le16 beaconperiod; + u8 dtimperiod; + __le64 timestamp; + __le64 localtime; + struct ieee_ie_ds_param_set ds; + u8 reserved1[4]; + struct ieee_ie_ibss_param_set ibss; + u8 reserved2[4]; + __le16 capability; + u8 rates[MAX_RATES]; + + /* + * DO NOT ADD ANY FIELDS TO THIS STRUCTURE. It is used below in the + * Adhoc join command and will cause a binary layout mismatch with + * the firmware + */ +} __packed; + +struct cmd_ds_802_11_ad_hoc_join { + struct cmd_header hdr; + + struct adhoc_bssdesc bss; + __le16 failtimeout; /* Reserved on v9 and later */ + __le16 probedelay; /* Reserved on v9 and later */ +} __packed; + +struct cmd_ds_802_11_ad_hoc_stop { + struct cmd_header hdr; +} __packed; + +struct cmd_ds_802_11_enable_rsn { + struct cmd_header hdr; + + __le16 action; + __le16 enable; +} __packed; + +struct MrvlIEtype_keyParamSet { + /* type ID */ + __le16 type; + + /* length of Payload */ + __le16 length; + + /* type of key: WEP=0, TKIP=1, AES=2 */ + __le16 keytypeid; + + /* key control Info specific to a keytypeid */ + __le16 keyinfo; + + /* length of key */ + __le16 keylen; + + /* key material of size keylen */ + u8 key[32]; +} __packed; + +#define MAX_WOL_RULES 16 + +struct host_wol_rule { + uint8_t rule_no; + uint8_t rule_ops; + __le16 sig_offset; + __le16 sig_length; + __le16 reserve; + __be32 sig_mask; + __be32 signature; +} __packed; + +struct wol_config { + uint8_t action; + uint8_t pattern; + uint8_t no_rules_in_cmd; + uint8_t result; + struct host_wol_rule rule[MAX_WOL_RULES]; +} __packed; + +struct cmd_ds_host_sleep { + struct cmd_header hdr; + __le32 criteria; + uint8_t gpio; + uint16_t gap; + struct wol_config wol_conf; +} __packed; + + + +struct cmd_ds_802_11_key_material { + struct cmd_header hdr; + + __le16 action; + struct MrvlIEtype_keyParamSet keyParamSet[2]; +} __packed; + +struct cmd_ds_802_11_eeprom_access { + struct cmd_header hdr; + __le16 action; + __le16 offset; + __le16 len; + /* firmware says it returns a maximum of 20 bytes */ +#define LBS_EEPROM_READ_LEN 20 + u8 value[LBS_EEPROM_READ_LEN]; +} __packed; + +struct cmd_ds_802_11_tpc_cfg { + struct cmd_header hdr; + + __le16 action; + uint8_t enable; + int8_t P0; + int8_t P1; + int8_t P2; + uint8_t usesnr; +} __packed; + + +struct cmd_ds_802_11_pa_cfg { + struct cmd_header hdr; + + __le16 action; + uint8_t enable; + int8_t P0; + int8_t P1; + int8_t P2; +} __packed; + + +struct cmd_ds_802_11_led_ctrl { + struct cmd_header hdr; + + __le16 action; + __le16 numled; + u8 data[256]; +} __packed; + +/* Automatic Frequency Control */ +struct cmd_ds_802_11_afc { + struct cmd_header hdr; + + __le16 afc_auto; + union { + struct { + __le16 threshold; + __le16 period; + }; + struct { + __le16 timing_offset; /* signed */ + __le16 carrier_offset; /* signed */ + }; + }; +} __packed; + +struct cmd_tx_rate_query { + __le16 txrate; +} __packed; + +struct cmd_ds_get_tsf { + __le64 tsfvalue; +} __packed; + +struct cmd_ds_bt_access { + struct cmd_header hdr; + + __le16 action; + __le32 id; + u8 addr1[ETH_ALEN]; + u8 addr2[ETH_ALEN]; +} __packed; + +struct cmd_ds_fwt_access { + struct cmd_header hdr; + + __le16 action; + __le32 id; + u8 valid; + u8 da[ETH_ALEN]; + u8 dir; + u8 ra[ETH_ALEN]; + __le32 ssn; + __le32 dsn; + __le32 metric; + u8 rate; + u8 hopcount; + u8 ttl; + __le32 expiration; + u8 sleepmode; + __le32 snr; + __le32 references; + u8 prec[ETH_ALEN]; +} __packed; + +struct cmd_ds_mesh_config { + struct cmd_header hdr; + + __le16 action; + __le16 channel; + __le16 type; + __le16 length; + u8 data[128]; /* last position reserved */ +} __packed; + +struct cmd_ds_mesh_access { + struct cmd_header hdr; + + __le16 action; + __le32 data[32]; /* last position reserved */ +} __packed; + +/* Number of stats counters returned by the firmware */ +#define MESH_STATS_NUM 8 +#endif diff --git a/drivers/net/wireless/libertas/if_cs.c b/drivers/net/wireless/libertas/if_cs.c new file mode 100644 index 0000000..ce9e973 --- /dev/null +++ b/drivers/net/wireless/libertas/if_cs.c @@ -0,0 +1,1090 @@ +/* + + Driver for the Marvell 8385 based compact flash WLAN cards. + + (C) 2007 by Holger Schurig + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define DRV_NAME "libertas_cs" + +#include "decl.h" +#include "defs.h" +#include "dev.h" + + +/********************************************************************/ +/* Module stuff */ +/********************************************************************/ + +MODULE_AUTHOR("Holger Schurig "); +MODULE_DESCRIPTION("Driver for Marvell 83xx compact flash WLAN cards"); +MODULE_LICENSE("GPL"); + + + +/********************************************************************/ +/* Data structures */ +/********************************************************************/ + +struct if_cs_card { + struct pcmcia_device *p_dev; + struct lbs_private *priv; + void __iomem *iobase; + bool align_regs; + u32 model; +}; + + +enum { + MODEL_UNKNOWN = 0x00, + MODEL_8305 = 0x01, + MODEL_8381 = 0x02, + MODEL_8385 = 0x03 +}; + +static const struct lbs_fw_table fw_table[] = { + { MODEL_8305, "libertas/cf8305.bin", NULL }, + { MODEL_8305, "libertas_cs_helper.fw", NULL }, + { MODEL_8381, "libertas/cf8381_helper.bin", "libertas/cf8381.bin" }, + { MODEL_8381, "libertas_cs_helper.fw", "libertas_cs.fw" }, + { MODEL_8385, "libertas/cf8385_helper.bin", "libertas/cf8385.bin" }, + { MODEL_8385, "libertas_cs_helper.fw", "libertas_cs.fw" }, + { 0, NULL, NULL } +}; +MODULE_FIRMWARE("libertas/cf8305.bin"); +MODULE_FIRMWARE("libertas/cf8381_helper.bin"); +MODULE_FIRMWARE("libertas/cf8381.bin"); +MODULE_FIRMWARE("libertas/cf8385_helper.bin"); +MODULE_FIRMWARE("libertas/cf8385.bin"); +MODULE_FIRMWARE("libertas_cs_helper.fw"); +MODULE_FIRMWARE("libertas_cs.fw"); + + +/********************************************************************/ +/* Hardware access */ +/********************************************************************/ + +/* This define enables wrapper functions which allow you + to dump all register accesses. You normally won't this, + except for development */ +/* #define DEBUG_IO */ + +#ifdef DEBUG_IO +static int debug_output = 0; +#else +/* This way the compiler optimizes the printk's away */ +#define debug_output 0 +#endif + +static inline unsigned int if_cs_read8(struct if_cs_card *card, uint reg) +{ + unsigned int val = ioread8(card->iobase + reg); + if (debug_output) + printk(KERN_INFO "inb %08x<%02x\n", reg, val); + return val; +} +static inline unsigned int if_cs_read16(struct if_cs_card *card, uint reg) +{ + unsigned int val = ioread16(card->iobase + reg); + if (debug_output) + printk(KERN_INFO "inw %08x<%04x\n", reg, val); + return val; +} +static inline void if_cs_read16_rep( + struct if_cs_card *card, + uint reg, + void *buf, + unsigned long count) +{ + if (debug_output) + printk(KERN_INFO "insw %08x<(0x%lx words)\n", + reg, count); + ioread16_rep(card->iobase + reg, buf, count); +} + +static inline void if_cs_write8(struct if_cs_card *card, uint reg, u8 val) +{ + if (debug_output) + printk(KERN_INFO "outb %08x>%02x\n", reg, val); + iowrite8(val, card->iobase + reg); +} + +static inline void if_cs_write16(struct if_cs_card *card, uint reg, u16 val) +{ + if (debug_output) + printk(KERN_INFO "outw %08x>%04x\n", reg, val); + iowrite16(val, card->iobase + reg); +} + +static inline void if_cs_write16_rep( + struct if_cs_card *card, + uint reg, + const void *buf, + unsigned long count) +{ + if (debug_output) + printk(KERN_INFO "outsw %08x>(0x%lx words)\n", + reg, count); + iowrite16_rep(card->iobase + reg, buf, count); +} + + +/* + * I know that polling/delaying is frowned upon. However, this procedure + * with polling is needed while downloading the firmware. At this stage, + * the hardware does unfortunately not create any interrupts. + * + * Fortunately, this function is never used once the firmware is in + * the card. :-) + * + * As a reference, see the "Firmware Specification v5.1", page 18 + * and 19. I did not follow their suggested timing to the word, + * but this works nice & fast anyway. + */ +static int if_cs_poll_while_fw_download(struct if_cs_card *card, uint addr, u8 reg) +{ + int i; + + for (i = 0; i < 100000; i++) { + u8 val = if_cs_read8(card, addr); + if (val == reg) + return 0; + udelay(5); + } + return -ETIME; +} + + + +/* + * First the bitmasks for the host/card interrupt/status registers: + */ +#define IF_CS_BIT_TX 0x0001 +#define IF_CS_BIT_RX 0x0002 +#define IF_CS_BIT_COMMAND 0x0004 +#define IF_CS_BIT_RESP 0x0008 +#define IF_CS_BIT_EVENT 0x0010 +#define IF_CS_BIT_MASK 0x001f + + + +/* + * It's not really clear to me what the host status register is for. It + * needs to be set almost in union with "host int cause". The following + * bits from above are used: + * + * IF_CS_BIT_TX driver downloaded a data packet + * IF_CS_BIT_RX driver got a data packet + * IF_CS_BIT_COMMAND driver downloaded a command + * IF_CS_BIT_RESP not used (has some meaning with powerdown) + * IF_CS_BIT_EVENT driver read a host event + */ +#define IF_CS_HOST_STATUS 0x00000000 + +/* + * With the host int cause register can the host (that is, Linux) cause + * an interrupt in the firmware, to tell the firmware about those events: + * + * IF_CS_BIT_TX a data packet has been downloaded + * IF_CS_BIT_RX a received data packet has retrieved + * IF_CS_BIT_COMMAND a firmware block or a command has been downloaded + * IF_CS_BIT_RESP not used (has some meaning with powerdown) + * IF_CS_BIT_EVENT a host event (link lost etc) has been retrieved + */ +#define IF_CS_HOST_INT_CAUSE 0x00000002 + +/* + * The host int mask register is used to enable/disable interrupt. However, + * I have the suspicion that disabled interrupts are lost. + */ +#define IF_CS_HOST_INT_MASK 0x00000004 + +/* + * Used to send or receive data packets: + */ +#define IF_CS_WRITE 0x00000016 +#define IF_CS_WRITE_LEN 0x00000014 +#define IF_CS_READ 0x00000010 +#define IF_CS_READ_LEN 0x00000024 + +/* + * Used to send commands (and to send firmware block) and to + * receive command responses: + */ +#define IF_CS_CMD 0x0000001A +#define IF_CS_CMD_LEN 0x00000018 +#define IF_CS_RESP 0x00000012 +#define IF_CS_RESP_LEN 0x00000030 + +/* + * The card status registers shows what the card/firmware actually + * accepts: + * + * IF_CS_BIT_TX you may send a data packet + * IF_CS_BIT_RX you may retrieve a data packet + * IF_CS_BIT_COMMAND you may send a command + * IF_CS_BIT_RESP you may retrieve a command response + * IF_CS_BIT_EVENT the card has a event for use (link lost, snr low etc) + * + * When reading this register several times, you will get back the same + * results --- with one exception: the IF_CS_BIT_EVENT clear itself + * automatically. + * + * Not that we don't rely on BIT_RX,_BIT_RESP or BIT_EVENT because + * we handle this via the card int cause register. + */ +#define IF_CS_CARD_STATUS 0x00000020 +#define IF_CS_CARD_STATUS_MASK 0x7f00 + +/* + * The card int cause register is used by the card/firmware to notify us + * about the following events: + * + * IF_CS_BIT_TX a data packet has successfully been sentx + * IF_CS_BIT_RX a data packet has been received and can be retrieved + * IF_CS_BIT_COMMAND not used + * IF_CS_BIT_RESP the firmware has a command response for us + * IF_CS_BIT_EVENT the card has a event for use (link lost, snr low etc) + */ +#define IF_CS_CARD_INT_CAUSE 0x00000022 + +/* + * This is used to for handshaking with the card's bootloader/helper image + * to synchronize downloading of firmware blocks. + */ +#define IF_CS_SQ_READ_LOW 0x00000028 +#define IF_CS_SQ_HELPER_OK 0x10 + +/* + * The scratch register tells us ... + * + * IF_CS_SCRATCH_BOOT_OK the bootloader runs + * IF_CS_SCRATCH_HELPER_OK the helper firmware already runs + */ +#define IF_CS_SCRATCH 0x0000003F +#define IF_CS_SCRATCH_BOOT_OK 0x00 +#define IF_CS_SCRATCH_HELPER_OK 0x5a + +/* + * Used to detect ancient chips: + */ +#define IF_CS_PRODUCT_ID 0x0000001C +#define IF_CS_CF8385_B1_REV 0x12 +#define IF_CS_CF8381_B3_REV 0x04 +#define IF_CS_CF8305_B1_REV 0x03 + +/* + * Used to detect other cards than CF8385 since their revisions of silicon + * doesn't match those from CF8385, eg. CF8381 B3 works with this driver. + */ +#define CF8305_MANFID 0x02db +#define CF8305_CARDID 0x8103 +#define CF8381_MANFID 0x02db +#define CF8381_CARDID 0x6064 +#define CF8385_MANFID 0x02df +#define CF8385_CARDID 0x8103 + +/* + * FIXME: just use the 'driver_info' field of 'struct pcmcia_device_id' when + * that gets fixed. Currently there's no way to access it from the probe hook. + */ +static inline u32 get_model(u16 manf_id, u16 card_id) +{ + /* NOTE: keep in sync with if_cs_ids */ + if (manf_id == CF8305_MANFID && card_id == CF8305_CARDID) + return MODEL_8305; + else if (manf_id == CF8381_MANFID && card_id == CF8381_CARDID) + return MODEL_8381; + else if (manf_id == CF8385_MANFID && card_id == CF8385_CARDID) + return MODEL_8385; + return MODEL_UNKNOWN; +} + +/********************************************************************/ +/* I/O and interrupt handling */ +/********************************************************************/ + +static inline void if_cs_enable_ints(struct if_cs_card *card) +{ + lbs_deb_enter(LBS_DEB_CS); + if_cs_write16(card, IF_CS_HOST_INT_MASK, 0); +} + +static inline void if_cs_disable_ints(struct if_cs_card *card) +{ + lbs_deb_enter(LBS_DEB_CS); + if_cs_write16(card, IF_CS_HOST_INT_MASK, IF_CS_BIT_MASK); +} + +/* + * Called from if_cs_host_to_card to send a command to the hardware + */ +static int if_cs_send_cmd(struct lbs_private *priv, u8 *buf, u16 nb) +{ + struct if_cs_card *card = (struct if_cs_card *)priv->card; + int ret = -1; + int loops = 0; + + lbs_deb_enter(LBS_DEB_CS); + if_cs_disable_ints(card); + + /* Is hardware ready? */ + while (1) { + u16 status = if_cs_read16(card, IF_CS_CARD_STATUS); + if (status & IF_CS_BIT_COMMAND) + break; + if (++loops > 100) { + netdev_err(priv->dev, "card not ready for commands\n"); + goto done; + } + mdelay(1); + } + + if_cs_write16(card, IF_CS_CMD_LEN, nb); + + if_cs_write16_rep(card, IF_CS_CMD, buf, nb / 2); + /* Are we supposed to transfer an odd amount of bytes? */ + if (nb & 1) + if_cs_write8(card, IF_CS_CMD, buf[nb-1]); + + /* "Assert the download over interrupt command in the Host + * status register" */ + if_cs_write16(card, IF_CS_HOST_STATUS, IF_CS_BIT_COMMAND); + + /* "Assert the download over interrupt command in the Card + * interrupt case register" */ + if_cs_write16(card, IF_CS_HOST_INT_CAUSE, IF_CS_BIT_COMMAND); + ret = 0; + +done: + if_cs_enable_ints(card); + lbs_deb_leave_args(LBS_DEB_CS, "ret %d", ret); + return ret; +} + +/* + * Called from if_cs_host_to_card to send a data to the hardware + */ +static void if_cs_send_data(struct lbs_private *priv, u8 *buf, u16 nb) +{ + struct if_cs_card *card = (struct if_cs_card *)priv->card; + u16 status; + + lbs_deb_enter(LBS_DEB_CS); + if_cs_disable_ints(card); + + status = if_cs_read16(card, IF_CS_CARD_STATUS); + BUG_ON((status & IF_CS_BIT_TX) == 0); + + if_cs_write16(card, IF_CS_WRITE_LEN, nb); + + /* write even number of bytes, then odd byte if necessary */ + if_cs_write16_rep(card, IF_CS_WRITE, buf, nb / 2); + if (nb & 1) + if_cs_write8(card, IF_CS_WRITE, buf[nb-1]); + + if_cs_write16(card, IF_CS_HOST_STATUS, IF_CS_BIT_TX); + if_cs_write16(card, IF_CS_HOST_INT_CAUSE, IF_CS_BIT_TX); + if_cs_enable_ints(card); + + lbs_deb_leave(LBS_DEB_CS); +} + +/* + * Get the command result out of the card. + */ +static int if_cs_receive_cmdres(struct lbs_private *priv, u8 *data, u32 *len) +{ + unsigned long flags; + int ret = -1; + u16 status; + + lbs_deb_enter(LBS_DEB_CS); + + /* is hardware ready? */ + status = if_cs_read16(priv->card, IF_CS_CARD_STATUS); + if ((status & IF_CS_BIT_RESP) == 0) { + netdev_err(priv->dev, "no cmd response in card\n"); + *len = 0; + goto out; + } + + *len = if_cs_read16(priv->card, IF_CS_RESP_LEN); + if ((*len == 0) || (*len > LBS_CMD_BUFFER_SIZE)) { + netdev_err(priv->dev, + "card cmd buffer has invalid # of bytes (%d)\n", + *len); + goto out; + } + + /* read even number of bytes, then odd byte if necessary */ + if_cs_read16_rep(priv->card, IF_CS_RESP, data, *len/sizeof(u16)); + if (*len & 1) + data[*len-1] = if_cs_read8(priv->card, IF_CS_RESP); + + /* This is a workaround for a firmware that reports too much + * bytes */ + *len -= 8; + ret = 0; + + /* Clear this flag again */ + spin_lock_irqsave(&priv->driver_lock, flags); + priv->dnld_sent = DNLD_RES_RECEIVED; + spin_unlock_irqrestore(&priv->driver_lock, flags); + +out: + lbs_deb_leave_args(LBS_DEB_CS, "ret %d, len %d", ret, *len); + return ret; +} + +static struct sk_buff *if_cs_receive_data(struct lbs_private *priv) +{ + struct sk_buff *skb = NULL; + u16 len; + u8 *data; + + lbs_deb_enter(LBS_DEB_CS); + + len = if_cs_read16(priv->card, IF_CS_READ_LEN); + if (len == 0 || len > MRVDRV_ETH_RX_PACKET_BUFFER_SIZE) { + netdev_err(priv->dev, + "card data buffer has invalid # of bytes (%d)\n", + len); + priv->dev->stats.rx_dropped++; + goto dat_err; + } + + skb = dev_alloc_skb(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE + 2); + if (!skb) + goto out; + skb_put(skb, len); + skb_reserve(skb, 2);/* 16 byte align */ + data = skb->data; + + /* read even number of bytes, then odd byte if necessary */ + if_cs_read16_rep(priv->card, IF_CS_READ, data, len/sizeof(u16)); + if (len & 1) + data[len-1] = if_cs_read8(priv->card, IF_CS_READ); + +dat_err: + if_cs_write16(priv->card, IF_CS_HOST_STATUS, IF_CS_BIT_RX); + if_cs_write16(priv->card, IF_CS_HOST_INT_CAUSE, IF_CS_BIT_RX); + +out: + lbs_deb_leave_args(LBS_DEB_CS, "ret %p", skb); + return skb; +} + +static irqreturn_t if_cs_interrupt(int irq, void *data) +{ + struct if_cs_card *card = data; + struct lbs_private *priv = card->priv; + u16 cause; + + lbs_deb_enter(LBS_DEB_CS); + + /* Ask card interrupt cause register if there is something for us */ + cause = if_cs_read16(card, IF_CS_CARD_INT_CAUSE); + lbs_deb_cs("cause 0x%04x\n", cause); + + if (cause == 0) { + /* Not for us */ + return IRQ_NONE; + } + + if (cause == 0xffff) { + /* Read in junk, the card has probably been removed */ + card->priv->surpriseremoved = 1; + return IRQ_HANDLED; + } + + if (cause & IF_CS_BIT_RX) { + struct sk_buff *skb; + lbs_deb_cs("rx packet\n"); + skb = if_cs_receive_data(priv); + if (skb) + lbs_process_rxed_packet(priv, skb); + } + + if (cause & IF_CS_BIT_TX) { + lbs_deb_cs("tx done\n"); + lbs_host_to_card_done(priv); + } + + if (cause & IF_CS_BIT_RESP) { + unsigned long flags; + u8 i; + + lbs_deb_cs("cmd resp\n"); + spin_lock_irqsave(&priv->driver_lock, flags); + i = (priv->resp_idx == 0) ? 1 : 0; + spin_unlock_irqrestore(&priv->driver_lock, flags); + + BUG_ON(priv->resp_len[i]); + if_cs_receive_cmdres(priv, priv->resp_buf[i], + &priv->resp_len[i]); + + spin_lock_irqsave(&priv->driver_lock, flags); + lbs_notify_command_response(priv, i); + spin_unlock_irqrestore(&priv->driver_lock, flags); + } + + if (cause & IF_CS_BIT_EVENT) { + u16 status = if_cs_read16(priv->card, IF_CS_CARD_STATUS); + if_cs_write16(priv->card, IF_CS_HOST_INT_CAUSE, + IF_CS_BIT_EVENT); + lbs_queue_event(priv, (status & IF_CS_CARD_STATUS_MASK) >> 8); + } + + /* Clear interrupt cause */ + if_cs_write16(card, IF_CS_CARD_INT_CAUSE, cause & IF_CS_BIT_MASK); + + lbs_deb_leave(LBS_DEB_CS); + return IRQ_HANDLED; +} + + + + +/********************************************************************/ +/* Firmware */ +/********************************************************************/ + +/* + * Tries to program the helper firmware. + * + * Return 0 on success + */ +static int if_cs_prog_helper(struct if_cs_card *card, const struct firmware *fw) +{ + int ret = 0; + int sent = 0; + u8 scratch; + + lbs_deb_enter(LBS_DEB_CS); + + /* + * This is the only place where an unaligned register access happens on + * the CF8305 card, therefore for the sake of speed of the driver, we do + * the alignment correction here. + */ + if (card->align_regs) + scratch = if_cs_read16(card, IF_CS_SCRATCH) >> 8; + else + scratch = if_cs_read8(card, IF_CS_SCRATCH); + + /* "If the value is 0x5a, the firmware is already + * downloaded successfully" + */ + if (scratch == IF_CS_SCRATCH_HELPER_OK) + goto done; + + /* "If the value is != 00, it is invalid value of register */ + if (scratch != IF_CS_SCRATCH_BOOT_OK) { + ret = -ENODEV; + goto done; + } + + lbs_deb_cs("helper size %td\n", fw->size); + + /* "Set the 5 bytes of the helper image to 0" */ + /* Not needed, this contains an ARM branch instruction */ + + for (;;) { + /* "the number of bytes to send is 256" */ + int count = 256; + int remain = fw->size - sent; + + if (remain < count) + count = remain; + + /* + * "write the number of bytes to be sent to the I/O Command + * write length register" + */ + if_cs_write16(card, IF_CS_CMD_LEN, count); + + /* "write this to I/O Command port register as 16 bit writes */ + if (count) + if_cs_write16_rep(card, IF_CS_CMD, + &fw->data[sent], + count >> 1); + + /* + * "Assert the download over interrupt command in the Host + * status register" + */ + if_cs_write8(card, IF_CS_HOST_STATUS, IF_CS_BIT_COMMAND); + + /* + * "Assert the download over interrupt command in the Card + * interrupt case register" + */ + if_cs_write16(card, IF_CS_HOST_INT_CAUSE, IF_CS_BIT_COMMAND); + + /* + * "The host polls the Card Status register ... for 50 ms before + * declaring a failure" + */ + ret = if_cs_poll_while_fw_download(card, IF_CS_CARD_STATUS, + IF_CS_BIT_COMMAND); + if (ret < 0) { + pr_err("can't download helper at 0x%x, ret %d\n", + sent, ret); + goto done; + } + + if (count == 0) + break; + + sent += count; + } + +done: + lbs_deb_leave_args(LBS_DEB_CS, "ret %d", ret); + return ret; +} + + +static int if_cs_prog_real(struct if_cs_card *card, const struct firmware *fw) +{ + int ret = 0; + int retry = 0; + int len = 0; + int sent; + + lbs_deb_enter(LBS_DEB_CS); + + lbs_deb_cs("fw size %td\n", fw->size); + + ret = if_cs_poll_while_fw_download(card, IF_CS_SQ_READ_LOW, + IF_CS_SQ_HELPER_OK); + if (ret < 0) { + pr_err("helper firmware doesn't answer\n"); + goto done; + } + + for (sent = 0; sent < fw->size; sent += len) { + len = if_cs_read16(card, IF_CS_SQ_READ_LOW); + if (len & 1) { + retry++; + pr_info("odd, need to retry this firmware block\n"); + } else { + retry = 0; + } + + if (retry > 20) { + pr_err("could not download firmware\n"); + ret = -ENODEV; + goto done; + } + if (retry) { + sent -= len; + } + + + if_cs_write16(card, IF_CS_CMD_LEN, len); + + if_cs_write16_rep(card, IF_CS_CMD, + &fw->data[sent], + (len+1) >> 1); + if_cs_write8(card, IF_CS_HOST_STATUS, IF_CS_BIT_COMMAND); + if_cs_write16(card, IF_CS_HOST_INT_CAUSE, IF_CS_BIT_COMMAND); + + ret = if_cs_poll_while_fw_download(card, IF_CS_CARD_STATUS, + IF_CS_BIT_COMMAND); + if (ret < 0) { + pr_err("can't download firmware at 0x%x\n", sent); + goto done; + } + } + + ret = if_cs_poll_while_fw_download(card, IF_CS_SCRATCH, 0x5a); + if (ret < 0) + pr_err("firmware download failed\n"); + +done: + lbs_deb_leave_args(LBS_DEB_CS, "ret %d", ret); + return ret; +} + +static void if_cs_prog_firmware(struct lbs_private *priv, int ret, + const struct firmware *helper, + const struct firmware *mainfw) +{ + struct if_cs_card *card = priv->card; + + if (ret) { + pr_err("failed to find firmware (%d)\n", ret); + return; + } + + /* Load the firmware */ + ret = if_cs_prog_helper(card, helper); + if (ret == 0 && (card->model != MODEL_8305)) + ret = if_cs_prog_real(card, mainfw); + if (ret) + goto out; + + /* Now actually get the IRQ */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + ret = request_irq(card->p_dev->irq, if_cs_interrupt, +#else + ret = request_irq(card->p_dev->irq.AssignedIRQ, if_cs_interrupt, +#endif + IRQF_SHARED, DRV_NAME, card); + if (ret) { + pr_err("error in request_irq\n"); + goto out; + } + + /* + * Clear any interrupt cause that happened while sending + * firmware/initializing card + */ + if_cs_write16(card, IF_CS_CARD_INT_CAUSE, IF_CS_BIT_MASK); + if_cs_enable_ints(card); + + /* And finally bring the card up */ + priv->fw_ready = 1; + if (lbs_start_card(priv) != 0) { + pr_err("could not activate card\n"); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + free_irq(card->p_dev->irq, card); +#else + free_irq(card->p_dev->irq.AssignedIRQ, card); +#endif + } + +out: + release_firmware(helper); + release_firmware(mainfw); +} + + +/********************************************************************/ +/* Callback functions for libertas.ko */ +/********************************************************************/ + +/* Send commands or data packets to the card */ +static int if_cs_host_to_card(struct lbs_private *priv, + u8 type, + u8 *buf, + u16 nb) +{ + int ret = -1; + + lbs_deb_enter_args(LBS_DEB_CS, "type %d, bytes %d", type, nb); + + switch (type) { + case MVMS_DAT: + priv->dnld_sent = DNLD_DATA_SENT; + if_cs_send_data(priv, buf, nb); + ret = 0; + break; + case MVMS_CMD: + priv->dnld_sent = DNLD_CMD_SENT; + ret = if_cs_send_cmd(priv, buf, nb); + break; + default: + netdev_err(priv->dev, "%s: unsupported type %d\n", + __func__, type); + } + + lbs_deb_leave_args(LBS_DEB_CS, "ret %d", ret); + return ret; +} + + +static void if_cs_release(struct pcmcia_device *p_dev) +{ + struct if_cs_card *card = p_dev->priv; + + lbs_deb_enter(LBS_DEB_CS); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + free_irq(p_dev->irq, card); +#else + free_irq(p_dev->irq.AssignedIRQ, card); +#endif + pcmcia_disable_device(p_dev); + if (card->iobase) + ioport_unmap(card->iobase); + + lbs_deb_leave(LBS_DEB_CS); +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)) +static int if_cs_ioprobe(struct pcmcia_device *p_dev, void *priv_data) +{ + p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; + p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO; + + if (p_dev->resource[1]->end) { + pr_err("wrong CIS (check number of IO windows)\n"); + return -ENODEV; + } +#else +static int if_cs_ioprobe(struct pcmcia_device *p_dev, + cistpl_cftable_entry_t *cfg, + cistpl_cftable_entry_t *dflt, + unsigned int vcc, + void *priv_data) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)) + p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO; + p_dev->resource[0]->start = cfg->io.win[0].base; + p_dev->resource[0]->end = cfg->io.win[0].len; +#else + p_dev->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + p_dev->io.BasePort1 = cfg->io.win[0].base; + p_dev->io.NumPorts1 = cfg->io.win[0].len; +#endif + + /* Do we need to allocate an interrupt? */ + p_dev->conf.Attributes |= CONF_ENABLE_IRQ; + + /* IO window settings */ + if (cfg->io.nwin != 1) { + pr_err("wrong CIS (check number of IO windows)\n"); + return -ENODEV; + } +#endif + + /* This reserves IO space but doesn't actually enable it */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)) + return pcmcia_request_io(p_dev); +#else + return pcmcia_request_io(p_dev, &p_dev->io); +#endif +} + +static int if_cs_probe(struct pcmcia_device *p_dev) +{ + int ret = -ENOMEM; + unsigned int prod_id; + struct lbs_private *priv; + struct if_cs_card *card; + + lbs_deb_enter(LBS_DEB_CS); + + card = kzalloc(sizeof(struct if_cs_card), GFP_KERNEL); + if (!card) + goto out; + + card->p_dev = p_dev; + p_dev->priv = card; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)) + p_dev->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; +#else +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)) + p_dev->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING; + p_dev->irq.Handler = NULL; +#endif + p_dev->conf.Attributes = 0; + p_dev->conf.IntType = INT_MEMORY_AND_IO; +#endif + + if (pcmcia_loop_config(p_dev, if_cs_ioprobe, NULL)) { + pr_err("error in pcmcia_loop_config\n"); + goto out1; + } + + /* + * Allocate an interrupt line. Note that this does not assign + * a handler to the interrupt, unless the 'Handler' member of + * the irq structure is initialized. + */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + if (!p_dev->irq) + goto out1; +#else + if (p_dev->conf.Attributes & CONF_ENABLE_IRQ) { + ret = pcmcia_request_irq(p_dev, &p_dev->irq); + if (ret) { + pr_err("error in pcmcia_request_irq\n"); + goto out1; + } + } +#endif + + /* Initialize io access */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)) + card->iobase = ioport_map(p_dev->resource[0]->start, + resource_size(p_dev->resource[0])); +#else + card->iobase = ioport_map(p_dev->io.BasePort1, p_dev->io.NumPorts1); +#endif + if (!card->iobase) { + pr_err("error in ioport_map\n"); + ret = -EIO; + goto out1; + } + + ret = pcmcia_enable_device(p_dev); + if (ret) { + pr_err("error in pcmcia_enable_device\n"); + goto out2; + } + + /* Finally, report what we've done */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)) + lbs_deb_cs("irq %d, io %pR", p_dev->irq, p_dev->resource[0]); +#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + lbs_deb_cs("irq %d, io 0x%04x-0x%04x\n", + p_dev->irq, p_dev->io.BasePort1, + p_dev->io.BasePort1 + p_dev->io.NumPorts1 - 1); +#else + lbs_deb_cs("irq %d, io 0x%04x-0x%04x\n", + p_dev->irq.AssignedIRQ, p_dev->io.BasePort1, + p_dev->io.BasePort1 + p_dev->io.NumPorts1 - 1); +#endif + + /* + * Most of the libertas cards can do unaligned register access, but some + * weird ones cannot. That's especially true for the CF8305 card. + */ + card->align_regs = false; + + card->model = get_model(p_dev->manf_id, p_dev->card_id); + if (card->model == MODEL_UNKNOWN) { + pr_err("unsupported manf_id 0x%04x / card_id 0x%04x\n", + p_dev->manf_id, p_dev->card_id); + goto out2; + } + + /* Check if we have a current silicon */ + prod_id = if_cs_read8(card, IF_CS_PRODUCT_ID); + if (card->model == MODEL_8305) { + card->align_regs = true; + if (prod_id < IF_CS_CF8305_B1_REV) { + pr_err("8305 rev B0 and older are not supported\n"); + ret = -ENODEV; + goto out2; + } + } + + if ((card->model == MODEL_8381) && prod_id < IF_CS_CF8381_B3_REV) { + pr_err("8381 rev B2 and older are not supported\n"); + ret = -ENODEV; + goto out2; + } + + if ((card->model == MODEL_8385) && prod_id < IF_CS_CF8385_B1_REV) { + pr_err("8385 rev B0 and older are not supported\n"); + ret = -ENODEV; + goto out2; + } + + /* Make this card known to the libertas driver */ + priv = lbs_add_card(card, &p_dev->dev); + if (!priv) { + ret = -ENOMEM; + goto out2; + } + + /* Set up fields in lbs_private */ + card->priv = priv; + priv->card = card; + priv->hw_host_to_card = if_cs_host_to_card; + priv->enter_deep_sleep = NULL; + priv->exit_deep_sleep = NULL; + priv->reset_deep_sleep_wakeup = NULL; + + /* Get firmware */ + ret = lbs_get_firmware_async(priv, &p_dev->dev, card->model, fw_table, + if_cs_prog_firmware); + if (ret) { + pr_err("failed to find firmware (%d)\n", ret); + goto out3; + } + + goto out; + +out3: + lbs_remove_card(priv); +out2: + ioport_unmap(card->iobase); +out1: + pcmcia_disable_device(p_dev); +out: + lbs_deb_leave_args(LBS_DEB_CS, "ret %d", ret); + return ret; +} + + +static void if_cs_detach(struct pcmcia_device *p_dev) +{ + struct if_cs_card *card = p_dev->priv; + + lbs_deb_enter(LBS_DEB_CS); + + lbs_stop_card(card->priv); + lbs_remove_card(card->priv); + if_cs_disable_ints(card); + if_cs_release(p_dev); + kfree(card); + + lbs_deb_leave(LBS_DEB_CS); +} + + + +/********************************************************************/ +/* Module initialization */ +/********************************************************************/ + +static const struct pcmcia_device_id if_cs_ids[] = { + PCMCIA_DEVICE_MANF_CARD(CF8305_MANFID, CF8305_CARDID), + PCMCIA_DEVICE_MANF_CARD(CF8381_MANFID, CF8381_CARDID), + PCMCIA_DEVICE_MANF_CARD(CF8385_MANFID, CF8385_CARDID), + /* NOTE: keep in sync with get_model() */ + PCMCIA_DEVICE_NULL, +}; +MODULE_DEVICE_TABLE(pcmcia, if_cs_ids); + +static struct pcmcia_driver lbs_driver = { + .owner = THIS_MODULE, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)) + .name = DRV_NAME, +#else + .drv = { + .name = DRV_NAME, + }, +#endif + .probe = if_cs_probe, + .remove = if_cs_detach, + .id_table = if_cs_ids, +}; +module_pcmcia_driver(lbs_driver); diff --git a/drivers/net/wireless/libertas/if_sdio.c b/drivers/net/wireless/libertas/if_sdio.c new file mode 100644 index 0000000..0e8eb62 --- /dev/null +++ b/drivers/net/wireless/libertas/if_sdio.c @@ -0,0 +1,1463 @@ +/* + * linux/drivers/net/wireless/libertas/if_sdio.c + * + * Copyright 2007-2008 Pierre Ossman + * + * Inspired by if_cs.c, Copyright 2007 Holger Schurig + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This hardware has more or less no CMD53 support, so all registers + * must be accessed using sdio_readb()/sdio_writeb(). + * + * Transfers must be in one transaction or the firmware goes bonkers. + * This means that the transfer must either be small enough to do a + * byte based transfer or it must be padded to a multiple of the + * current block size. + * + * As SDIO is still new to the kernel, it is unfortunately common with + * bugs in the host controllers related to that. One such bug is that + * controllers cannot do transfers that aren't a multiple of 4 bytes. + * If you don't have time to fix the host controller driver, you can + * work around the problem by modifying if_sdio_host_to_card() and + * if_sdio_card_to_host() to pad the data. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "host.h" +#include "decl.h" +#include "defs.h" +#include "dev.h" +#include "cmd.h" +#include "if_sdio.h" + +static void if_sdio_interrupt(struct sdio_func *func); + +/* The if_sdio_remove() callback function is called when + * user removes this module from kernel space or ejects + * the card from the slot. The driver handles these 2 cases + * differently for SD8688 combo chip. + * If the user is removing the module, the FUNC_SHUTDOWN + * command for SD8688 is sent to the firmware. + * If the card is removed, there is no need to send this command. + * + * The variable 'user_rmmod' is used to distinguish these two + * scenarios. This flag is initialized as FALSE in case the card + * is removed, and will be set to TRUE for module removal when + * module_exit function is called. + */ +static u8 user_rmmod; + +static const struct sdio_device_id if_sdio_ids[] = { + { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, + SDIO_DEVICE_ID_MARVELL_LIBERTAS) }, + { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, + SDIO_DEVICE_ID_MARVELL_8688WLAN) }, + { /* end: all zeroes */ }, +}; + +MODULE_DEVICE_TABLE(sdio, if_sdio_ids); + +#define MODEL_8385 0x04 +#define MODEL_8686 0x0b +#define MODEL_8688 0x10 + +static const struct lbs_fw_table fw_table[] = { + { MODEL_8385, "libertas/sd8385_helper.bin", "libertas/sd8385.bin" }, + { MODEL_8385, "sd8385_helper.bin", "sd8385.bin" }, + { MODEL_8686, "libertas/sd8686_v9_helper.bin", "libertas/sd8686_v9.bin" }, + { MODEL_8686, "libertas/sd8686_v8_helper.bin", "libertas/sd8686_v8.bin" }, + { MODEL_8686, "sd8686_helper.bin", "sd8686.bin" }, + { MODEL_8688, "libertas/sd8688_helper.bin", "libertas/sd8688.bin" }, + { MODEL_8688, "sd8688_helper.bin", "sd8688.bin" }, + { 0, NULL, NULL } +}; +MODULE_FIRMWARE("libertas/sd8385_helper.bin"); +MODULE_FIRMWARE("libertas/sd8385.bin"); +MODULE_FIRMWARE("sd8385_helper.bin"); +MODULE_FIRMWARE("sd8385.bin"); +MODULE_FIRMWARE("libertas/sd8686_v9_helper.bin"); +MODULE_FIRMWARE("libertas/sd8686_v9.bin"); +MODULE_FIRMWARE("libertas/sd8686_v8_helper.bin"); +MODULE_FIRMWARE("libertas/sd8686_v8.bin"); +MODULE_FIRMWARE("sd8686_helper.bin"); +MODULE_FIRMWARE("sd8686.bin"); +MODULE_FIRMWARE("libertas/sd8688_helper.bin"); +MODULE_FIRMWARE("libertas/sd8688.bin"); +MODULE_FIRMWARE("sd8688_helper.bin"); +MODULE_FIRMWARE("sd8688.bin"); + +struct if_sdio_packet { + struct if_sdio_packet *next; + u16 nb; + u8 buffer[0] __attribute__((aligned(4))); +}; + +struct if_sdio_card { + struct sdio_func *func; + struct lbs_private *priv; + + int model; + unsigned long ioport; + unsigned int scratch_reg; + bool started; + wait_queue_head_t pwron_waitq; + + u8 buffer[65536] __attribute__((aligned(4))); + + spinlock_t lock; + struct if_sdio_packet *packets; + + struct workqueue_struct *workqueue; + struct work_struct packet_worker; + + u8 rx_unit; +}; + +static void if_sdio_finish_power_on(struct if_sdio_card *card); +static int if_sdio_power_off(struct if_sdio_card *card); + +/********************************************************************/ +/* I/O */ +/********************************************************************/ + +/* + * For SD8385/SD8686, this function reads firmware status after + * the image is downloaded, or reads RX packet length when + * interrupt (with IF_SDIO_H_INT_UPLD bit set) is received. + * For SD8688, this function reads firmware status only. + */ +static u16 if_sdio_read_scratch(struct if_sdio_card *card, int *err) +{ + int ret; + u16 scratch; + + scratch = sdio_readb(card->func, card->scratch_reg, &ret); + if (!ret) + scratch |= sdio_readb(card->func, card->scratch_reg + 1, + &ret) << 8; + + if (err) + *err = ret; + + if (ret) + return 0xffff; + + return scratch; +} + +static u8 if_sdio_read_rx_unit(struct if_sdio_card *card) +{ + int ret; + u8 rx_unit; + + rx_unit = sdio_readb(card->func, IF_SDIO_RX_UNIT, &ret); + + if (ret) + rx_unit = 0; + + return rx_unit; +} + +static u16 if_sdio_read_rx_len(struct if_sdio_card *card, int *err) +{ + int ret; + u16 rx_len; + + switch (card->model) { + case MODEL_8385: + case MODEL_8686: + rx_len = if_sdio_read_scratch(card, &ret); + break; + case MODEL_8688: + default: /* for newer chipsets */ + rx_len = sdio_readb(card->func, IF_SDIO_RX_LEN, &ret); + if (!ret) + rx_len <<= card->rx_unit; + else + rx_len = 0xffff; /* invalid length */ + + break; + } + + if (err) + *err = ret; + + return rx_len; +} + +static int if_sdio_handle_cmd(struct if_sdio_card *card, + u8 *buffer, unsigned size) +{ + struct lbs_private *priv = card->priv; + int ret; + unsigned long flags; + u8 i; + + lbs_deb_enter(LBS_DEB_SDIO); + + if (size > LBS_CMD_BUFFER_SIZE) { + lbs_deb_sdio("response packet too large (%d bytes)\n", + (int)size); + ret = -E2BIG; + goto out; + } + + spin_lock_irqsave(&priv->driver_lock, flags); + + i = (priv->resp_idx == 0) ? 1 : 0; + BUG_ON(priv->resp_len[i]); + priv->resp_len[i] = size; + memcpy(priv->resp_buf[i], buffer, size); + lbs_notify_command_response(priv, i); + + spin_unlock_irqrestore(&card->priv->driver_lock, flags); + + ret = 0; + +out: + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + return ret; +} + +static int if_sdio_handle_data(struct if_sdio_card *card, + u8 *buffer, unsigned size) +{ + int ret; + struct sk_buff *skb; + char *data; + + lbs_deb_enter(LBS_DEB_SDIO); + + if (size > MRVDRV_ETH_RX_PACKET_BUFFER_SIZE) { + lbs_deb_sdio("response packet too large (%d bytes)\n", + (int)size); + ret = -E2BIG; + goto out; + } + + skb = dev_alloc_skb(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE + NET_IP_ALIGN); + if (!skb) { + ret = -ENOMEM; + goto out; + } + + skb_reserve(skb, NET_IP_ALIGN); + + data = skb_put(skb, size); + + memcpy(data, buffer, size); + + lbs_process_rxed_packet(card->priv, skb); + + ret = 0; + +out: + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + + return ret; +} + +static int if_sdio_handle_event(struct if_sdio_card *card, + u8 *buffer, unsigned size) +{ + int ret; + u32 event; + + lbs_deb_enter(LBS_DEB_SDIO); + + if (card->model == MODEL_8385) { + event = sdio_readb(card->func, IF_SDIO_EVENT, &ret); + if (ret) + goto out; + + /* right shift 3 bits to get the event id */ + event >>= 3; + } else { + if (size < 4) { + lbs_deb_sdio("event packet too small (%d bytes)\n", + (int)size); + ret = -EINVAL; + goto out; + } + event = buffer[3] << 24; + event |= buffer[2] << 16; + event |= buffer[1] << 8; + event |= buffer[0] << 0; + } + + lbs_queue_event(card->priv, event & 0xFF); + ret = 0; + +out: + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + + return ret; +} + +static int if_sdio_wait_status(struct if_sdio_card *card, const u8 condition) +{ + u8 status; + unsigned long timeout; + int ret = 0; + + timeout = jiffies + HZ; + while (1) { + status = sdio_readb(card->func, IF_SDIO_STATUS, &ret); + if (ret) + return ret; + if ((status & condition) == condition) + break; + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + mdelay(1); + } + return ret; +} + +static int if_sdio_card_to_host(struct if_sdio_card *card) +{ + int ret; + u16 size, type, chunk; + + lbs_deb_enter(LBS_DEB_SDIO); + + size = if_sdio_read_rx_len(card, &ret); + if (ret) + goto out; + + if (size < 4) { + lbs_deb_sdio("invalid packet size (%d bytes) from firmware\n", + (int)size); + ret = -EINVAL; + goto out; + } + + ret = if_sdio_wait_status(card, IF_SDIO_IO_RDY); + if (ret) + goto out; + + /* + * The transfer must be in one transaction or the firmware + * goes suicidal. There's no way to guarantee that for all + * controllers, but we can at least try. + */ + chunk = sdio_align_size(card->func, size); + + ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk); + if (ret) + goto out; + + chunk = card->buffer[0] | (card->buffer[1] << 8); + type = card->buffer[2] | (card->buffer[3] << 8); + + lbs_deb_sdio("packet of type %d and size %d bytes\n", + (int)type, (int)chunk); + + if (chunk > size) { + lbs_deb_sdio("packet fragment (%d > %d)\n", + (int)chunk, (int)size); + ret = -EINVAL; + goto out; + } + + if (chunk < size) { + lbs_deb_sdio("packet fragment (%d < %d)\n", + (int)chunk, (int)size); + } + + switch (type) { + case MVMS_CMD: + ret = if_sdio_handle_cmd(card, card->buffer + 4, chunk - 4); + if (ret) + goto out; + break; + case MVMS_DAT: + ret = if_sdio_handle_data(card, card->buffer + 4, chunk - 4); + if (ret) + goto out; + break; + case MVMS_EVENT: + ret = if_sdio_handle_event(card, card->buffer + 4, chunk - 4); + if (ret) + goto out; + break; + default: + lbs_deb_sdio("invalid type (%d) from firmware\n", + (int)type); + ret = -EINVAL; + goto out; + } + +out: + if (ret) + pr_err("problem fetching packet from firmware\n"); + + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + + return ret; +} + +static void if_sdio_host_to_card_worker(struct work_struct *work) +{ + struct if_sdio_card *card; + struct if_sdio_packet *packet; + int ret; + unsigned long flags; + + lbs_deb_enter(LBS_DEB_SDIO); + + card = container_of(work, struct if_sdio_card, packet_worker); + + while (1) { + spin_lock_irqsave(&card->lock, flags); + packet = card->packets; + if (packet) + card->packets = packet->next; + spin_unlock_irqrestore(&card->lock, flags); + + if (!packet) + break; + + sdio_claim_host(card->func); + + ret = if_sdio_wait_status(card, IF_SDIO_IO_RDY); + if (ret == 0) { + ret = sdio_writesb(card->func, card->ioport, + packet->buffer, packet->nb); + } + + if (ret) + pr_err("error %d sending packet to firmware\n", ret); + + sdio_release_host(card->func); + + kfree(packet); + } + + lbs_deb_leave(LBS_DEB_SDIO); +} + +/********************************************************************/ +/* Firmware */ +/********************************************************************/ + +#define FW_DL_READY_STATUS (IF_SDIO_IO_RDY | IF_SDIO_DL_RDY) + +static int if_sdio_prog_helper(struct if_sdio_card *card, + const struct firmware *fw) +{ + int ret; + unsigned long timeout; + u8 *chunk_buffer; + u32 chunk_size; + const u8 *firmware; + size_t size; + + lbs_deb_enter(LBS_DEB_SDIO); + + chunk_buffer = kzalloc(64, GFP_KERNEL); + if (!chunk_buffer) { + ret = -ENOMEM; + goto out; + } + + sdio_claim_host(card->func); + + ret = sdio_set_block_size(card->func, 32); + if (ret) + goto release; + + firmware = fw->data; + size = fw->size; + + while (size) { + ret = if_sdio_wait_status(card, FW_DL_READY_STATUS); + if (ret) + goto release; + + /* On some platforms (like Davinci) the chip needs more time + * between helper blocks. + */ + mdelay(2); + + chunk_size = min(size, (size_t)60); + + *((__le32*)chunk_buffer) = cpu_to_le32(chunk_size); + memcpy(chunk_buffer + 4, firmware, chunk_size); +/* + lbs_deb_sdio("sending %d bytes chunk\n", chunk_size); +*/ + ret = sdio_writesb(card->func, card->ioport, + chunk_buffer, 64); + if (ret) + goto release; + + firmware += chunk_size; + size -= chunk_size; + } + + /* an empty block marks the end of the transfer */ + memset(chunk_buffer, 0, 4); + ret = sdio_writesb(card->func, card->ioport, chunk_buffer, 64); + if (ret) + goto release; + + lbs_deb_sdio("waiting for helper to boot...\n"); + + /* wait for the helper to boot by looking at the size register */ + timeout = jiffies + HZ; + while (1) { + u16 req_size; + + req_size = sdio_readb(card->func, IF_SDIO_RD_BASE, &ret); + if (ret) + goto release; + + req_size |= sdio_readb(card->func, IF_SDIO_RD_BASE + 1, &ret) << 8; + if (ret) + goto release; + + if (req_size != 0) + break; + + if (time_after(jiffies, timeout)) { + ret = -ETIMEDOUT; + goto release; + } + + msleep(10); + } + + ret = 0; + +release: + sdio_release_host(card->func); + kfree(chunk_buffer); + +out: + if (ret) + pr_err("failed to load helper firmware\n"); + + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + return ret; +} + +static int if_sdio_prog_real(struct if_sdio_card *card, + const struct firmware *fw) +{ + int ret; + unsigned long timeout; + u8 *chunk_buffer; + u32 chunk_size; + const u8 *firmware; + size_t size, req_size; + + lbs_deb_enter(LBS_DEB_SDIO); + + chunk_buffer = kzalloc(512, GFP_KERNEL); + if (!chunk_buffer) { + ret = -ENOMEM; + goto out; + } + + sdio_claim_host(card->func); + + ret = sdio_set_block_size(card->func, 32); + if (ret) + goto release; + + firmware = fw->data; + size = fw->size; + + while (size) { + timeout = jiffies + HZ; + while (1) { + ret = if_sdio_wait_status(card, FW_DL_READY_STATUS); + if (ret) + goto release; + + req_size = sdio_readb(card->func, IF_SDIO_RD_BASE, + &ret); + if (ret) + goto release; + + req_size |= sdio_readb(card->func, IF_SDIO_RD_BASE + 1, + &ret) << 8; + if (ret) + goto release; + + /* + * For SD8688 wait until the length is not 0, 1 or 2 + * before downloading the first FW block, + * since BOOT code writes the register to indicate the + * helper/FW download winner, + * the value could be 1 or 2 (Func1 or Func2). + */ + if ((size != fw->size) || (req_size > 2)) + break; + if (time_after(jiffies, timeout)) { + ret = -ETIMEDOUT; + goto release; + } + mdelay(1); + } + +/* + lbs_deb_sdio("firmware wants %d bytes\n", (int)req_size); +*/ + if (req_size == 0) { + lbs_deb_sdio("firmware helper gave up early\n"); + ret = -EIO; + goto release; + } + + if (req_size & 0x01) { + lbs_deb_sdio("firmware helper signalled error\n"); + ret = -EIO; + goto release; + } + + if (req_size > size) + req_size = size; + + while (req_size) { + chunk_size = min(req_size, (size_t)512); + + memcpy(chunk_buffer, firmware, chunk_size); +/* + lbs_deb_sdio("sending %d bytes (%d bytes) chunk\n", + chunk_size, (chunk_size + 31) / 32 * 32); +*/ + ret = sdio_writesb(card->func, card->ioport, + chunk_buffer, roundup(chunk_size, 32)); + if (ret) + goto release; + + firmware += chunk_size; + size -= chunk_size; + req_size -= chunk_size; + } + } + + ret = 0; + + lbs_deb_sdio("waiting for firmware to boot...\n"); + + /* wait for the firmware to boot */ + timeout = jiffies + HZ; + while (1) { + u16 scratch; + + scratch = if_sdio_read_scratch(card, &ret); + if (ret) + goto release; + + if (scratch == IF_SDIO_FIRMWARE_OK) + break; + + if (time_after(jiffies, timeout)) { + ret = -ETIMEDOUT; + goto release; + } + + msleep(10); + } + + ret = 0; + +release: + sdio_release_host(card->func); + kfree(chunk_buffer); + +out: + if (ret) + pr_err("failed to load firmware\n"); + + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + return ret; +} + +static void if_sdio_do_prog_firmware(struct lbs_private *priv, int ret, + const struct firmware *helper, + const struct firmware *mainfw) +{ + struct if_sdio_card *card = priv->card; + + if (ret) { + pr_err("failed to find firmware (%d)\n", ret); + return; + } + + ret = if_sdio_prog_helper(card, helper); + if (ret) + goto out; + + lbs_deb_sdio("Helper firmware loaded\n"); + + ret = if_sdio_prog_real(card, mainfw); + if (ret) + goto out; + + lbs_deb_sdio("Firmware loaded\n"); + if_sdio_finish_power_on(card); + +out: + release_firmware(helper); + release_firmware(mainfw); +} + +static int if_sdio_prog_firmware(struct if_sdio_card *card) +{ + int ret; + u16 scratch; + + lbs_deb_enter(LBS_DEB_SDIO); + + /* + * Disable interrupts + */ + sdio_claim_host(card->func); + sdio_writeb(card->func, 0x00, IF_SDIO_H_INT_MASK, &ret); + sdio_release_host(card->func); + + sdio_claim_host(card->func); + scratch = if_sdio_read_scratch(card, &ret); + sdio_release_host(card->func); + + lbs_deb_sdio("firmware status = %#x\n", scratch); + lbs_deb_sdio("scratch ret = %d\n", ret); + + if (ret) + goto out; + + + /* + * The manual clearly describes that FEDC is the right code to use + * to detect firmware presence, but for SD8686 it is not that simple. + * Scratch is also used to store the RX packet length, so we lose + * the FEDC value early on. So we use a non-zero check in order + * to validate firmware presence. + * Additionally, the SD8686 in the Gumstix always has the high scratch + * bit set, even when the firmware is not loaded. So we have to + * exclude that from the test. + */ + if (scratch == IF_SDIO_FIRMWARE_OK) { + lbs_deb_sdio("firmware already loaded\n"); + if_sdio_finish_power_on(card); + return 0; + } else if ((card->model == MODEL_8686) && (scratch & 0x7fff)) { + lbs_deb_sdio("firmware may be running\n"); + if_sdio_finish_power_on(card); + return 0; + } + + ret = lbs_get_firmware_async(card->priv, &card->func->dev, card->model, + fw_table, if_sdio_do_prog_firmware); + +out: + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + return ret; +} + +/********************************************************************/ +/* Power management */ +/********************************************************************/ + +/* Finish power on sequence (after firmware is loaded) */ +static void if_sdio_finish_power_on(struct if_sdio_card *card) +{ + struct sdio_func *func = card->func; + struct lbs_private *priv = card->priv; + int ret; + + sdio_claim_host(func); + sdio_set_block_size(card->func, IF_SDIO_BLOCK_SIZE); + + /* + * Get rx_unit if the chip is SD8688 or newer. + * SD8385 & SD8686 do not have rx_unit. + */ + if ((card->model != MODEL_8385) + && (card->model != MODEL_8686)) + card->rx_unit = if_sdio_read_rx_unit(card); + else + card->rx_unit = 0; + + /* + * Set up the interrupt handler late. + * + * If we set it up earlier, the (buggy) hardware generates a spurious + * interrupt, even before the interrupt has been enabled, with + * CCCR_INTx = 0. + * + * We register the interrupt handler late so that we can handle any + * spurious interrupts, and also to avoid generation of that known + * spurious interrupt in the first place. + */ + ret = sdio_claim_irq(func, if_sdio_interrupt); + if (ret) + goto release; + + /* + * Enable interrupts now that everything is set up + */ + sdio_writeb(func, 0x0f, IF_SDIO_H_INT_MASK, &ret); + if (ret) + goto release_irq; + + sdio_release_host(func); + + /* Set fw_ready before queuing any commands so that + * lbs_thread won't block from sending them to firmware. + */ + priv->fw_ready = 1; + + /* + * FUNC_INIT is required for SD8688 WLAN/BT multiple functions + */ + if (card->model == MODEL_8688) { + struct cmd_header cmd; + + memset(&cmd, 0, sizeof(cmd)); + + lbs_deb_sdio("send function INIT command\n"); + if (__lbs_cmd(priv, CMD_FUNC_INIT, &cmd, sizeof(cmd), + lbs_cmd_copyback, (unsigned long) &cmd)) + netdev_alert(priv->dev, "CMD_FUNC_INIT cmd failed\n"); + } + + wake_up(&card->pwron_waitq); + + if (!card->started) { + ret = lbs_start_card(priv); + if_sdio_power_off(card); + if (ret == 0) { + card->started = true; + /* Tell PM core that we don't need the card to be + * powered now */ + pm_runtime_put_noidle(&func->dev); + } + } + + return; + +release_irq: + sdio_release_irq(func); +release: + sdio_release_host(func); +} + +static int if_sdio_power_on(struct if_sdio_card *card) +{ + struct sdio_func *func = card->func; + struct mmc_host *host = func->card->host; + int ret; + + sdio_claim_host(func); + + ret = sdio_enable_func(func); + if (ret) + goto release; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)) + /* For 1-bit transfers to the 8686 model, we need to enable the + * interrupt flag in the CCCR register. Set the MMC_QUIRK_LENIENT_FN0 + * bit to allow access to non-vendor registers. */ + if ((card->model == MODEL_8686) && + (host->caps & MMC_CAP_SDIO_IRQ) && + (host->ios.bus_width == MMC_BUS_WIDTH_1)) { + u8 reg; + + func->card->quirks |= MMC_QUIRK_LENIENT_FN0; + reg = sdio_f0_readb(func, SDIO_CCCR_IF, &ret); + if (ret) + goto disable; + + reg |= SDIO_BUS_ECSI; + sdio_f0_writeb(func, reg, SDIO_CCCR_IF, &ret); + if (ret) + goto disable; + } +#endif + + card->ioport = sdio_readb(func, IF_SDIO_IOPORT, &ret); + if (ret) + goto disable; + + card->ioport |= sdio_readb(func, IF_SDIO_IOPORT + 1, &ret) << 8; + if (ret) + goto disable; + + card->ioport |= sdio_readb(func, IF_SDIO_IOPORT + 2, &ret) << 16; + if (ret) + goto disable; + + sdio_release_host(func); + ret = if_sdio_prog_firmware(card); + if (ret) { + sdio_disable_func(func); + return ret; + } + + return 0; + +disable: + sdio_disable_func(func); +release: + sdio_release_host(func); + return ret; +} + +static int if_sdio_power_off(struct if_sdio_card *card) +{ + struct sdio_func *func = card->func; + struct lbs_private *priv = card->priv; + + priv->fw_ready = 0; + + sdio_claim_host(func); + sdio_release_irq(func); + sdio_disable_func(func); + sdio_release_host(func); + return 0; +} + + +/*******************************************************************/ +/* Libertas callbacks */ +/*******************************************************************/ + +static int if_sdio_host_to_card(struct lbs_private *priv, + u8 type, u8 *buf, u16 nb) +{ + int ret; + struct if_sdio_card *card; + struct if_sdio_packet *packet, *cur; + u16 size; + unsigned long flags; + + lbs_deb_enter_args(LBS_DEB_SDIO, "type %d, bytes %d", type, nb); + + card = priv->card; + + if (nb > (65536 - sizeof(struct if_sdio_packet) - 4)) { + ret = -EINVAL; + goto out; + } + + /* + * The transfer must be in one transaction or the firmware + * goes suicidal. There's no way to guarantee that for all + * controllers, but we can at least try. + */ + size = sdio_align_size(card->func, nb + 4); + + packet = kzalloc(sizeof(struct if_sdio_packet) + size, + GFP_ATOMIC); + if (!packet) { + ret = -ENOMEM; + goto out; + } + + packet->next = NULL; + packet->nb = size; + + /* + * SDIO specific header. + */ + packet->buffer[0] = (nb + 4) & 0xff; + packet->buffer[1] = ((nb + 4) >> 8) & 0xff; + packet->buffer[2] = type; + packet->buffer[3] = 0; + + memcpy(packet->buffer + 4, buf, nb); + + spin_lock_irqsave(&card->lock, flags); + + if (!card->packets) + card->packets = packet; + else { + cur = card->packets; + while (cur->next) + cur = cur->next; + cur->next = packet; + } + + switch (type) { + case MVMS_CMD: + priv->dnld_sent = DNLD_CMD_SENT; + break; + case MVMS_DAT: + priv->dnld_sent = DNLD_DATA_SENT; + break; + default: + lbs_deb_sdio("unknown packet type %d\n", (int)type); + } + + spin_unlock_irqrestore(&card->lock, flags); + + queue_work(card->workqueue, &card->packet_worker); + + ret = 0; + +out: + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + + return ret; +} + +static int if_sdio_enter_deep_sleep(struct lbs_private *priv) +{ + int ret = -1; + struct cmd_header cmd; + + memset(&cmd, 0, sizeof(cmd)); + + lbs_deb_sdio("send DEEP_SLEEP command\n"); + ret = __lbs_cmd(priv, CMD_802_11_DEEP_SLEEP, &cmd, sizeof(cmd), + lbs_cmd_copyback, (unsigned long) &cmd); + if (ret) + netdev_err(priv->dev, "DEEP_SLEEP cmd failed\n"); + + mdelay(200); + return ret; +} + +static int if_sdio_exit_deep_sleep(struct lbs_private *priv) +{ + struct if_sdio_card *card = priv->card; + int ret = -1; + + lbs_deb_enter(LBS_DEB_SDIO); + sdio_claim_host(card->func); + + sdio_writeb(card->func, HOST_POWER_UP, CONFIGURATION_REG, &ret); + if (ret) + netdev_err(priv->dev, "sdio_writeb failed!\n"); + + sdio_release_host(card->func); + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + return ret; +} + +static int if_sdio_reset_deep_sleep_wakeup(struct lbs_private *priv) +{ + struct if_sdio_card *card = priv->card; + int ret = -1; + + lbs_deb_enter(LBS_DEB_SDIO); + sdio_claim_host(card->func); + + sdio_writeb(card->func, 0, CONFIGURATION_REG, &ret); + if (ret) + netdev_err(priv->dev, "sdio_writeb failed!\n"); + + sdio_release_host(card->func); + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + return ret; + +} + +static struct mmc_host *reset_host; + +static void if_sdio_reset_card_worker(struct work_struct *work) +{ + /* + * The actual reset operation must be run outside of lbs_thread. This + * is because mmc_remove_host() will cause the device to be instantly + * destroyed, and the libertas driver then needs to end lbs_thread, + * leading to a deadlock. + * + * We run it in a workqueue totally independent from the if_sdio_card + * instance for that reason. + */ + + pr_info("Resetting card..."); + mmc_remove_host(reset_host); + mmc_add_host(reset_host); +} +static DECLARE_WORK(card_reset_work, if_sdio_reset_card_worker); + +static void if_sdio_reset_card(struct lbs_private *priv) +{ + struct if_sdio_card *card = priv->card; + + if (work_pending(&card_reset_work)) + return; + + reset_host = card->func->card->host; + schedule_work(&card_reset_work); +} + +static int if_sdio_power_save(struct lbs_private *priv) +{ + struct if_sdio_card *card = priv->card; + int ret; + + flush_workqueue(card->workqueue); + + ret = if_sdio_power_off(card); + + /* Let runtime PM know the card is powered off */ + pm_runtime_put_sync(&card->func->dev); + + return ret; +} + +static int if_sdio_power_restore(struct lbs_private *priv) +{ + struct if_sdio_card *card = priv->card; + int r; + + /* Make sure the card will not be powered off by runtime PM */ + pm_runtime_get_sync(&card->func->dev); + + r = if_sdio_power_on(card); + if (r) + return r; + + wait_event(card->pwron_waitq, priv->fw_ready); + return 0; +} + + +/*******************************************************************/ +/* SDIO callbacks */ +/*******************************************************************/ + +static void if_sdio_interrupt(struct sdio_func *func) +{ + int ret; + struct if_sdio_card *card; + u8 cause; + + lbs_deb_enter(LBS_DEB_SDIO); + + card = sdio_get_drvdata(func); + + cause = sdio_readb(card->func, IF_SDIO_H_INT_STATUS, &ret); + if (ret || !cause) + goto out; + + lbs_deb_sdio("interrupt: 0x%X\n", (unsigned)cause); + + sdio_writeb(card->func, ~cause, IF_SDIO_H_INT_STATUS, &ret); + if (ret) + goto out; + + /* + * Ignore the define name, this really means the card has + * successfully received the command. + */ + card->priv->is_activity_detected = 1; + if (cause & IF_SDIO_H_INT_DNLD) + lbs_host_to_card_done(card->priv); + + + if (cause & IF_SDIO_H_INT_UPLD) { + ret = if_sdio_card_to_host(card); + if (ret) + goto out; + } + + ret = 0; + +out: + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); +} + +static int if_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct if_sdio_card *card; + struct lbs_private *priv; + int ret, i; + unsigned int model; + struct if_sdio_packet *packet; + + lbs_deb_enter(LBS_DEB_SDIO); + + for (i = 0;i < func->card->num_info;i++) { + if (sscanf(func->card->info[i], + "802.11 SDIO ID: %x", &model) == 1) + break; + if (sscanf(func->card->info[i], + "ID: %x", &model) == 1) + break; + if (!strcmp(func->card->info[i], "IBIS Wireless SDIO Card")) { + model = MODEL_8385; + break; + } + } + + if (i == func->card->num_info) { + pr_err("unable to identify card model\n"); + return -ENODEV; + } + + card = kzalloc(sizeof(struct if_sdio_card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->func = func; + card->model = model; + + switch (card->model) { + case MODEL_8385: + card->scratch_reg = IF_SDIO_SCRATCH_OLD; + break; + case MODEL_8686: + card->scratch_reg = IF_SDIO_SCRATCH; + break; + case MODEL_8688: + default: /* for newer chipsets */ + card->scratch_reg = IF_SDIO_FW_STATUS; + break; + } + + spin_lock_init(&card->lock); + card->workqueue = create_workqueue("libertas_sdio"); + INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker); + init_waitqueue_head(&card->pwron_waitq); + + /* Check if we support this card */ + for (i = 0; i < ARRAY_SIZE(fw_table); i++) { + if (card->model == fw_table[i].model) + break; + } + if (i == ARRAY_SIZE(fw_table)) { + pr_err("unknown card model 0x%x\n", card->model); + ret = -ENODEV; + goto free; + } + + sdio_set_drvdata(func, card); + + lbs_deb_sdio("class = 0x%X, vendor = 0x%X, " + "device = 0x%X, model = 0x%X, ioport = 0x%X\n", + func->class, func->vendor, func->device, + model, (unsigned)card->ioport); + + + priv = lbs_add_card(card, &func->dev); + if (!priv) { + ret = -ENOMEM; + goto free; + } + + card->priv = priv; + + priv->card = card; + priv->hw_host_to_card = if_sdio_host_to_card; + priv->enter_deep_sleep = if_sdio_enter_deep_sleep; + priv->exit_deep_sleep = if_sdio_exit_deep_sleep; + priv->reset_deep_sleep_wakeup = if_sdio_reset_deep_sleep_wakeup; + priv->reset_card = if_sdio_reset_card; + priv->power_save = if_sdio_power_save; + priv->power_restore = if_sdio_power_restore; + + ret = if_sdio_power_on(card); + if (ret) + goto err_activate_card; + +out: + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + + return ret; + +err_activate_card: + flush_workqueue(card->workqueue); + lbs_remove_card(priv); +free: + destroy_workqueue(card->workqueue); + while (card->packets) { + packet = card->packets; + card->packets = card->packets->next; + kfree(packet); + } + + kfree(card); + + goto out; +} + +static void if_sdio_remove(struct sdio_func *func) +{ + struct if_sdio_card *card; + struct if_sdio_packet *packet; + + lbs_deb_enter(LBS_DEB_SDIO); + + card = sdio_get_drvdata(func); + + /* Undo decrement done above in if_sdio_probe */ + pm_runtime_get_noresume(&func->dev); + + if (user_rmmod && (card->model == MODEL_8688)) { + /* + * FUNC_SHUTDOWN is required for SD8688 WLAN/BT + * multiple functions + */ + struct cmd_header cmd; + + memset(&cmd, 0, sizeof(cmd)); + + lbs_deb_sdio("send function SHUTDOWN command\n"); + if (__lbs_cmd(card->priv, CMD_FUNC_SHUTDOWN, + &cmd, sizeof(cmd), lbs_cmd_copyback, + (unsigned long) &cmd)) + pr_alert("CMD_FUNC_SHUTDOWN cmd failed\n"); + } + + + lbs_deb_sdio("call remove card\n"); + lbs_stop_card(card->priv); + lbs_remove_card(card->priv); + + flush_workqueue(card->workqueue); + destroy_workqueue(card->workqueue); + + while (card->packets) { + packet = card->packets; + card->packets = card->packets->next; + kfree(packet); + } + + kfree(card); + lbs_deb_leave(LBS_DEB_SDIO); +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) +static int if_sdio_suspend(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + int ret; + struct if_sdio_card *card = sdio_get_drvdata(func); + + mmc_pm_flag_t flags = sdio_get_host_pm_caps(func); + + /* If we're powered off anyway, just let the mmc layer remove the + * card. */ + if (!lbs_iface_active(card->priv)) + return -ENOSYS; + + dev_info(dev, "%s: suspend: PM flags = 0x%x\n", + sdio_func_id(func), flags); + + /* If we aren't being asked to wake on anything, we should bail out + * and let the SD stack power down the card. + */ + if (card->priv->wol_criteria == EHS_REMOVE_WAKEUP) { + dev_info(dev, "Suspend without wake params -- powering down card\n"); + return -ENOSYS; + } + + if (!(flags & MMC_PM_KEEP_POWER)) { + dev_err(dev, "%s: cannot remain alive while host is suspended\n", + sdio_func_id(func)); + return -ENOSYS; + } + + ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); + if (ret) + return ret; + + ret = lbs_suspend(card->priv); + if (ret) + return ret; + + return sdio_set_host_pm_flags(func, MMC_PM_WAKE_SDIO_IRQ); +} + +static int if_sdio_resume(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + struct if_sdio_card *card = sdio_get_drvdata(func); + int ret; + + dev_info(dev, "%s: resume: we're back\n", sdio_func_id(func)); + + ret = lbs_resume(card->priv); + + return ret; +} + +static const struct dev_pm_ops if_sdio_pm_ops = { + .suspend = if_sdio_suspend, + .resume = if_sdio_resume, +}; +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) */ + +static struct sdio_driver if_sdio_driver = { + .name = "libertas_sdio", + .id_table = if_sdio_ids, + .probe = if_sdio_probe, + .remove = if_sdio_remove, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) + .drv = { + .pm = &if_sdio_pm_ops, + }, +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) */ +}; + +/*******************************************************************/ +/* Module functions */ +/*******************************************************************/ + +static int __init if_sdio_init_module(void) +{ + int ret = 0; + + lbs_deb_enter(LBS_DEB_SDIO); + + printk(KERN_INFO "libertas_sdio: Libertas SDIO driver\n"); + printk(KERN_INFO "libertas_sdio: Copyright Pierre Ossman\n"); + + ret = sdio_register_driver(&if_sdio_driver); + + /* Clear the flag in case user removes the card. */ + user_rmmod = 0; + + lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); + + return ret; +} + +static void __exit if_sdio_exit_module(void) +{ + lbs_deb_enter(LBS_DEB_SDIO); + + /* Set the flag as user is removing this module. */ + user_rmmod = 1; + + cancel_work_sync(&card_reset_work); + + sdio_unregister_driver(&if_sdio_driver); + + lbs_deb_leave(LBS_DEB_SDIO); +} + +module_init(if_sdio_init_module); +module_exit(if_sdio_exit_module); + +MODULE_DESCRIPTION("Libertas SDIO WLAN Driver"); +MODULE_AUTHOR("Pierre Ossman"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/libertas/if_sdio.h b/drivers/net/wireless/libertas/if_sdio.h new file mode 100644 index 0000000..62fda35 --- /dev/null +++ b/drivers/net/wireless/libertas/if_sdio.h @@ -0,0 +1,52 @@ +/* + * linux/drivers/net/wireless/libertas/if_sdio.h + * + * Copyright 2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#ifndef _LBS_IF_SDIO_H +#define _LBS_IF_SDIO_H + +#define IF_SDIO_IOPORT 0x00 + +#define IF_SDIO_H_INT_MASK 0x04 +#define IF_SDIO_H_INT_OFLOW 0x08 +#define IF_SDIO_H_INT_UFLOW 0x04 +#define IF_SDIO_H_INT_DNLD 0x02 +#define IF_SDIO_H_INT_UPLD 0x01 + +#define IF_SDIO_H_INT_STATUS 0x05 +#define IF_SDIO_H_INT_RSR 0x06 +#define IF_SDIO_H_INT_STATUS2 0x07 + +#define IF_SDIO_RD_BASE 0x10 + +#define IF_SDIO_STATUS 0x20 +#define IF_SDIO_IO_RDY 0x08 +#define IF_SDIO_CIS_RDY 0x04 +#define IF_SDIO_UL_RDY 0x02 +#define IF_SDIO_DL_RDY 0x01 + +#define IF_SDIO_C_INT_MASK 0x24 +#define IF_SDIO_C_INT_STATUS 0x28 +#define IF_SDIO_C_INT_RSR 0x2C + +#define IF_SDIO_SCRATCH 0x34 +#define IF_SDIO_SCRATCH_OLD 0x80fe +#define IF_SDIO_FW_STATUS 0x40 +#define IF_SDIO_FIRMWARE_OK 0xfedc + +#define IF_SDIO_RX_LEN 0x42 +#define IF_SDIO_RX_UNIT 0x43 + +#define IF_SDIO_EVENT 0x80fc + +#define IF_SDIO_BLOCK_SIZE 256 +#define CONFIGURATION_REG 0x03 +#define HOST_POWER_UP (0x1U << 1) +#endif diff --git a/drivers/net/wireless/libertas/if_spi.c b/drivers/net/wireless/libertas/if_spi.c new file mode 100644 index 0000000..671f38a --- /dev/null +++ b/drivers/net/wireless/libertas/if_spi.c @@ -0,0 +1,1328 @@ +/* + * linux/drivers/net/wireless/libertas/if_spi.c + * + * Driver for Marvell SPI WLAN cards. + * + * Copyright 2008 Analog Devices Inc. + * + * Authors: + * Andrey Yurovsky + * Colin McCabe + * + * Inspired by if_sdio.c, Copyright 2007-2008 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "host.h" +#include "decl.h" +#include "defs.h" +#include "dev.h" +#include "if_spi.h" + +struct if_spi_packet { + struct list_head list; + u16 blen; + u8 buffer[0] __attribute__((aligned(4))); +}; + +struct if_spi_card { + struct spi_device *spi; + struct lbs_private *priv; + struct libertas_spi_platform_data *pdata; + + /* The card ID and card revision, as reported by the hardware. */ + u16 card_id; + u8 card_rev; + + /* The last time that we initiated an SPU operation */ + unsigned long prev_xfer_time; + + int use_dummy_writes; + unsigned long spu_port_delay; + unsigned long spu_reg_delay; + + /* Handles all SPI communication (except for FW load) */ + struct workqueue_struct *workqueue; + struct work_struct packet_work; + struct work_struct resume_work; + + u8 cmd_buffer[IF_SPI_CMD_BUF_SIZE]; + + /* A buffer of incoming packets from libertas core. + * Since we can't sleep in hw_host_to_card, we have to buffer + * them. */ + struct list_head cmd_packet_list; + struct list_head data_packet_list; + + /* Protects cmd_packet_list and data_packet_list */ + spinlock_t buffer_lock; + + /* True is card suspended */ + u8 suspended; +}; + +static void free_if_spi_card(struct if_spi_card *card) +{ + struct list_head *cursor, *next; + struct if_spi_packet *packet; + + list_for_each_safe(cursor, next, &card->cmd_packet_list) { + packet = container_of(cursor, struct if_spi_packet, list); + list_del(&packet->list); + kfree(packet); + } + list_for_each_safe(cursor, next, &card->data_packet_list) { + packet = container_of(cursor, struct if_spi_packet, list); + list_del(&packet->list); + kfree(packet); + } + spi_set_drvdata(card->spi, NULL); + kfree(card); +} + +#define MODEL_8385 0x04 +#define MODEL_8686 0x0b +#define MODEL_8688 0x10 + +static const struct lbs_fw_table fw_table[] = { + { MODEL_8385, "libertas/gspi8385_helper.bin", "libertas/gspi8385.bin" }, + { MODEL_8385, "libertas/gspi8385_hlp.bin", "libertas/gspi8385.bin" }, + { MODEL_8686, "libertas/gspi8686_v9_helper.bin", "libertas/gspi8686_v9.bin" }, + { MODEL_8686, "libertas/gspi8686_hlp.bin", "libertas/gspi8686.bin" }, + { MODEL_8688, "libertas/gspi8688_helper.bin", "libertas/gspi8688.bin" }, + { 0, NULL, NULL } +}; +MODULE_FIRMWARE("libertas/gspi8385_helper.bin"); +MODULE_FIRMWARE("libertas/gspi8385_hlp.bin"); +MODULE_FIRMWARE("libertas/gspi8385.bin"); +MODULE_FIRMWARE("libertas/gspi8686_v9_helper.bin"); +MODULE_FIRMWARE("libertas/gspi8686_v9.bin"); +MODULE_FIRMWARE("libertas/gspi8686_hlp.bin"); +MODULE_FIRMWARE("libertas/gspi8686.bin"); +MODULE_FIRMWARE("libertas/gspi8688_helper.bin"); +MODULE_FIRMWARE("libertas/gspi8688.bin"); + + +/* + * SPI Interface Unit Routines + * + * The SPU sits between the host and the WLAN module. + * All communication with the firmware is through SPU transactions. + * + * First we have to put a SPU register name on the bus. Then we can + * either read from or write to that register. + * + */ + +static void spu_transaction_init(struct if_spi_card *card) +{ + if (!time_after(jiffies, card->prev_xfer_time + 1)) { + /* Unfortunately, the SPU requires a delay between successive + * transactions. If our last transaction was more than a jiffy + * ago, we have obviously already delayed enough. + * If not, we have to busy-wait to be on the safe side. */ + ndelay(400); + } +} + +static void spu_transaction_finish(struct if_spi_card *card) +{ + card->prev_xfer_time = jiffies; +} + +/* + * Write out a byte buffer to an SPI register, + * using a series of 16-bit transfers. + */ +static int spu_write(struct if_spi_card *card, u16 reg, const u8 *buf, int len) +{ + int err = 0; + __le16 reg_out = cpu_to_le16(reg | IF_SPI_WRITE_OPERATION_MASK); + struct spi_message m; + struct spi_transfer reg_trans; + struct spi_transfer data_trans; + + spi_message_init(&m); + memset(®_trans, 0, sizeof(reg_trans)); + memset(&data_trans, 0, sizeof(data_trans)); + + /* You must give an even number of bytes to the SPU, even if it + * doesn't care about the last one. */ + BUG_ON(len & 0x1); + + spu_transaction_init(card); + + /* write SPU register index */ + reg_trans.tx_buf = ®_out; + reg_trans.len = sizeof(reg_out); + + data_trans.tx_buf = buf; + data_trans.len = len; + + spi_message_add_tail(®_trans, &m); + spi_message_add_tail(&data_trans, &m); + + err = spi_sync(card->spi, &m); + spu_transaction_finish(card); + return err; +} + +static inline int spu_write_u16(struct if_spi_card *card, u16 reg, u16 val) +{ + __le16 buff; + + buff = cpu_to_le16(val); + return spu_write(card, reg, (u8 *)&buff, sizeof(u16)); +} + +static inline int spu_reg_is_port_reg(u16 reg) +{ + switch (reg) { + case IF_SPI_IO_RDWRPORT_REG: + case IF_SPI_CMD_RDWRPORT_REG: + case IF_SPI_DATA_RDWRPORT_REG: + return 1; + default: + return 0; + } +} + +static int spu_read(struct if_spi_card *card, u16 reg, u8 *buf, int len) +{ + unsigned int delay; + int err = 0; + __le16 reg_out = cpu_to_le16(reg | IF_SPI_READ_OPERATION_MASK); + struct spi_message m; + struct spi_transfer reg_trans; + struct spi_transfer dummy_trans; + struct spi_transfer data_trans; + + /* + * You must take an even number of bytes from the SPU, even if you + * don't care about the last one. + */ + BUG_ON(len & 0x1); + + spu_transaction_init(card); + + spi_message_init(&m); + memset(®_trans, 0, sizeof(reg_trans)); + memset(&dummy_trans, 0, sizeof(dummy_trans)); + memset(&data_trans, 0, sizeof(data_trans)); + + /* write SPU register index */ + reg_trans.tx_buf = ®_out; + reg_trans.len = sizeof(reg_out); + spi_message_add_tail(®_trans, &m); + + delay = spu_reg_is_port_reg(reg) ? card->spu_port_delay : + card->spu_reg_delay; + if (card->use_dummy_writes) { + /* Clock in dummy cycles while the SPU fills the FIFO */ + dummy_trans.len = delay / 8; + spi_message_add_tail(&dummy_trans, &m); + } else { + /* Busy-wait while the SPU fills the FIFO */ + reg_trans.delay_usecs = + DIV_ROUND_UP((100 + (delay * 10)), 1000); + } + + /* read in data */ + data_trans.rx_buf = buf; + data_trans.len = len; + spi_message_add_tail(&data_trans, &m); + + err = spi_sync(card->spi, &m); + spu_transaction_finish(card); + return err; +} + +/* Read 16 bits from an SPI register */ +static inline int spu_read_u16(struct if_spi_card *card, u16 reg, u16 *val) +{ + __le16 buf; + int ret; + + ret = spu_read(card, reg, (u8 *)&buf, sizeof(buf)); + if (ret == 0) + *val = le16_to_cpup(&buf); + return ret; +} + +/* + * Read 32 bits from an SPI register. + * The low 16 bits are read first. + */ +static int spu_read_u32(struct if_spi_card *card, u16 reg, u32 *val) +{ + __le32 buf; + int err; + + err = spu_read(card, reg, (u8 *)&buf, sizeof(buf)); + if (!err) + *val = le32_to_cpup(&buf); + return err; +} + +/* + * Keep reading 16 bits from an SPI register until you get the correct result. + * + * If mask = 0, the correct result is any non-zero number. + * If mask != 0, the correct result is any number where + * number & target_mask == target + * + * Returns -ETIMEDOUT if a second passes without the correct result. + */ +static int spu_wait_for_u16(struct if_spi_card *card, u16 reg, + u16 target_mask, u16 target) +{ + int err; + unsigned long timeout = jiffies + 5*HZ; + while (1) { + u16 val; + err = spu_read_u16(card, reg, &val); + if (err) + return err; + if (target_mask) { + if ((val & target_mask) == target) + return 0; + } else { + if (val) + return 0; + } + udelay(100); + if (time_after(jiffies, timeout)) { + pr_err("%s: timeout with val=%02x, target_mask=%02x, target=%02x\n", + __func__, val, target_mask, target); + return -ETIMEDOUT; + } + } +} + +/* + * Read 16 bits from an SPI register until you receive a specific value. + * Returns -ETIMEDOUT if a 4 tries pass without success. + */ +static int spu_wait_for_u32(struct if_spi_card *card, u32 reg, u32 target) +{ + int err, try; + for (try = 0; try < 4; ++try) { + u32 val = 0; + err = spu_read_u32(card, reg, &val); + if (err) + return err; + if (val == target) + return 0; + mdelay(100); + } + return -ETIMEDOUT; +} + +static int spu_set_interrupt_mode(struct if_spi_card *card, + int suppress_host_int, + int auto_int) +{ + int err = 0; + + /* + * We can suppress a host interrupt by clearing the appropriate + * bit in the "host interrupt status mask" register + */ + if (suppress_host_int) { + err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_MASK_REG, 0); + if (err) + return err; + } else { + err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_MASK_REG, + IF_SPI_HISM_TX_DOWNLOAD_RDY | + IF_SPI_HISM_RX_UPLOAD_RDY | + IF_SPI_HISM_CMD_DOWNLOAD_RDY | + IF_SPI_HISM_CARDEVENT | + IF_SPI_HISM_CMD_UPLOAD_RDY); + if (err) + return err; + } + + /* + * If auto-interrupts are on, the completion of certain transactions + * will trigger an interrupt automatically. If auto-interrupts + * are off, we need to set the "Card Interrupt Cause" register to + * trigger a card interrupt. + */ + if (auto_int) { + err = spu_write_u16(card, IF_SPI_HOST_INT_CTRL_REG, + IF_SPI_HICT_TX_DOWNLOAD_OVER_AUTO | + IF_SPI_HICT_RX_UPLOAD_OVER_AUTO | + IF_SPI_HICT_CMD_DOWNLOAD_OVER_AUTO | + IF_SPI_HICT_CMD_UPLOAD_OVER_AUTO); + if (err) + return err; + } else { + err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_MASK_REG, 0); + if (err) + return err; + } + return err; +} + +static int spu_get_chip_revision(struct if_spi_card *card, + u16 *card_id, u8 *card_rev) +{ + int err = 0; + u32 dev_ctrl; + err = spu_read_u32(card, IF_SPI_DEVICEID_CTRL_REG, &dev_ctrl); + if (err) + return err; + *card_id = IF_SPI_DEVICEID_CTRL_REG_TO_CARD_ID(dev_ctrl); + *card_rev = IF_SPI_DEVICEID_CTRL_REG_TO_CARD_REV(dev_ctrl); + return err; +} + +static int spu_set_bus_mode(struct if_spi_card *card, u16 mode) +{ + int err = 0; + u16 rval; + /* set bus mode */ + err = spu_write_u16(card, IF_SPI_SPU_BUS_MODE_REG, mode); + if (err) + return err; + /* Check that we were able to read back what we just wrote. */ + err = spu_read_u16(card, IF_SPI_SPU_BUS_MODE_REG, &rval); + if (err) + return err; + if ((rval & 0xF) != mode) { + pr_err("Can't read bus mode register\n"); + return -EIO; + } + return 0; +} + +static int spu_init(struct if_spi_card *card, int use_dummy_writes) +{ + int err = 0; + u32 delay; + + /* + * We have to start up in timed delay mode so that we can safely + * read the Delay Read Register. + */ + card->use_dummy_writes = 0; + err = spu_set_bus_mode(card, + IF_SPI_BUS_MODE_SPI_CLOCK_PHASE_RISING | + IF_SPI_BUS_MODE_DELAY_METHOD_TIMED | + IF_SPI_BUS_MODE_16_BIT_ADDRESS_16_BIT_DATA); + if (err) + return err; + card->spu_port_delay = 1000; + card->spu_reg_delay = 1000; + err = spu_read_u32(card, IF_SPI_DELAY_READ_REG, &delay); + if (err) + return err; + card->spu_port_delay = delay & 0x0000ffff; + card->spu_reg_delay = (delay & 0xffff0000) >> 16; + + /* If dummy clock delay mode has been requested, switch to it now */ + if (use_dummy_writes) { + card->use_dummy_writes = 1; + err = spu_set_bus_mode(card, + IF_SPI_BUS_MODE_SPI_CLOCK_PHASE_RISING | + IF_SPI_BUS_MODE_DELAY_METHOD_DUMMY_CLOCK | + IF_SPI_BUS_MODE_16_BIT_ADDRESS_16_BIT_DATA); + if (err) + return err; + } + + lbs_deb_spi("Initialized SPU unit. " + "spu_port_delay=0x%04lx, spu_reg_delay=0x%04lx\n", + card->spu_port_delay, card->spu_reg_delay); + return err; +} + +/* + * Firmware Loading + */ + +static int if_spi_prog_helper_firmware(struct if_spi_card *card, + const struct firmware *firmware) +{ + int err = 0; + int bytes_remaining; + const u8 *fw; + u8 temp[HELPER_FW_LOAD_CHUNK_SZ]; + + lbs_deb_enter(LBS_DEB_SPI); + + err = spu_set_interrupt_mode(card, 1, 0); + if (err) + goto out; + + bytes_remaining = firmware->size; + fw = firmware->data; + + /* Load helper firmware image */ + while (bytes_remaining > 0) { + /* + * Scratch pad 1 should contain the number of bytes we + * want to download to the firmware + */ + err = spu_write_u16(card, IF_SPI_SCRATCH_1_REG, + HELPER_FW_LOAD_CHUNK_SZ); + if (err) + goto out; + + err = spu_wait_for_u16(card, IF_SPI_HOST_INT_STATUS_REG, + IF_SPI_HIST_CMD_DOWNLOAD_RDY, + IF_SPI_HIST_CMD_DOWNLOAD_RDY); + if (err) + goto out; + + /* + * Feed the data into the command read/write port reg + * in chunks of 64 bytes + */ + memset(temp, 0, sizeof(temp)); + memcpy(temp, fw, + min(bytes_remaining, HELPER_FW_LOAD_CHUNK_SZ)); + mdelay(10); + err = spu_write(card, IF_SPI_CMD_RDWRPORT_REG, + temp, HELPER_FW_LOAD_CHUNK_SZ); + if (err) + goto out; + + /* Interrupt the boot code */ + err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_REG, 0); + if (err) + goto out; + err = spu_write_u16(card, IF_SPI_CARD_INT_CAUSE_REG, + IF_SPI_CIC_CMD_DOWNLOAD_OVER); + if (err) + goto out; + bytes_remaining -= HELPER_FW_LOAD_CHUNK_SZ; + fw += HELPER_FW_LOAD_CHUNK_SZ; + } + + /* + * Once the helper / single stage firmware download is complete, + * write 0 to scratch pad 1 and interrupt the + * bootloader. This completes the helper download. + */ + err = spu_write_u16(card, IF_SPI_SCRATCH_1_REG, FIRMWARE_DNLD_OK); + if (err) + goto out; + err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_REG, 0); + if (err) + goto out; + err = spu_write_u16(card, IF_SPI_CARD_INT_CAUSE_REG, + IF_SPI_CIC_CMD_DOWNLOAD_OVER); +out: + if (err) + pr_err("failed to load helper firmware (err=%d)\n", err); + lbs_deb_leave_args(LBS_DEB_SPI, "err %d", err); + return err; +} + +/* + * Returns the length of the next packet the firmware expects us to send. + * Sets crc_err if the previous transfer had a CRC error. + */ +static int if_spi_prog_main_firmware_check_len(struct if_spi_card *card, + int *crc_err) +{ + u16 len; + int err = 0; + + /* + * wait until the host interrupt status register indicates + * that we are ready to download + */ + err = spu_wait_for_u16(card, IF_SPI_HOST_INT_STATUS_REG, + IF_SPI_HIST_CMD_DOWNLOAD_RDY, + IF_SPI_HIST_CMD_DOWNLOAD_RDY); + if (err) { + pr_err("timed out waiting for host_int_status\n"); + return err; + } + + /* Ask the device how many bytes of firmware it wants. */ + err = spu_read_u16(card, IF_SPI_SCRATCH_1_REG, &len); + if (err) + return err; + + if (len > IF_SPI_CMD_BUF_SIZE) { + pr_err("firmware load device requested a larger transfer than we are prepared to handle (len = %d)\n", + len); + return -EIO; + } + if (len & 0x1) { + lbs_deb_spi("%s: crc error\n", __func__); + len &= ~0x1; + *crc_err = 1; + } else + *crc_err = 0; + + return len; +} + +static int if_spi_prog_main_firmware(struct if_spi_card *card, + const struct firmware *firmware) +{ + struct lbs_private *priv = card->priv; + int len, prev_len; + int bytes, crc_err = 0, err = 0; + const u8 *fw; + u16 num_crc_errs; + + lbs_deb_enter(LBS_DEB_SPI); + + err = spu_set_interrupt_mode(card, 1, 0); + if (err) + goto out; + + err = spu_wait_for_u16(card, IF_SPI_SCRATCH_1_REG, 0, 0); + if (err) { + netdev_err(priv->dev, + "%s: timed out waiting for initial scratch reg = 0\n", + __func__); + goto out; + } + + num_crc_errs = 0; + prev_len = 0; + bytes = firmware->size; + fw = firmware->data; + while ((len = if_spi_prog_main_firmware_check_len(card, &crc_err))) { + if (len < 0) { + err = len; + goto out; + } + if (bytes < 0) { + /* + * If there are no more bytes left, we would normally + * expect to have terminated with len = 0 + */ + netdev_err(priv->dev, + "Firmware load wants more bytes than we have to offer.\n"); + break; + } + if (crc_err) { + /* Previous transfer failed. */ + if (++num_crc_errs > MAX_MAIN_FW_LOAD_CRC_ERR) { + pr_err("Too many CRC errors encountered in firmware load.\n"); + err = -EIO; + goto out; + } + } else { + /* Previous transfer succeeded. Advance counters. */ + bytes -= prev_len; + fw += prev_len; + } + if (bytes < len) { + memset(card->cmd_buffer, 0, len); + memcpy(card->cmd_buffer, fw, bytes); + } else + memcpy(card->cmd_buffer, fw, len); + + err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_REG, 0); + if (err) + goto out; + err = spu_write(card, IF_SPI_CMD_RDWRPORT_REG, + card->cmd_buffer, len); + if (err) + goto out; + err = spu_write_u16(card, IF_SPI_CARD_INT_CAUSE_REG , + IF_SPI_CIC_CMD_DOWNLOAD_OVER); + if (err) + goto out; + prev_len = len; + } + if (bytes > prev_len) { + pr_err("firmware load wants fewer bytes than we have to offer\n"); + } + + /* Confirm firmware download */ + err = spu_wait_for_u32(card, IF_SPI_SCRATCH_4_REG, + SUCCESSFUL_FW_DOWNLOAD_MAGIC); + if (err) { + pr_err("failed to confirm the firmware download\n"); + goto out; + } + +out: + if (err) + pr_err("failed to load firmware (err=%d)\n", err); + lbs_deb_leave_args(LBS_DEB_SPI, "err %d", err); + return err; +} + +/* + * SPI Transfer Thread + * + * The SPI worker handles all SPI transfers, so there is no need for a lock. + */ + +/* Move a command from the card to the host */ +static int if_spi_c2h_cmd(struct if_spi_card *card) +{ + struct lbs_private *priv = card->priv; + unsigned long flags; + int err = 0; + u16 len; + u8 i; + + /* + * We need a buffer big enough to handle whatever people send to + * hw_host_to_card + */ + BUILD_BUG_ON(IF_SPI_CMD_BUF_SIZE < LBS_CMD_BUFFER_SIZE); + BUILD_BUG_ON(IF_SPI_CMD_BUF_SIZE < LBS_UPLD_SIZE); + + /* + * It's just annoying if the buffer size isn't a multiple of 4, because + * then we might have len < IF_SPI_CMD_BUF_SIZE but + * ALIGN(len, 4) > IF_SPI_CMD_BUF_SIZE + */ + BUILD_BUG_ON(IF_SPI_CMD_BUF_SIZE % 4 != 0); + + lbs_deb_enter(LBS_DEB_SPI); + + /* How many bytes are there to read? */ + err = spu_read_u16(card, IF_SPI_SCRATCH_2_REG, &len); + if (err) + goto out; + if (!len) { + netdev_err(priv->dev, "%s: error: card has no data for host\n", + __func__); + err = -EINVAL; + goto out; + } else if (len > IF_SPI_CMD_BUF_SIZE) { + netdev_err(priv->dev, + "%s: error: response packet too large: %d bytes, but maximum is %d\n", + __func__, len, IF_SPI_CMD_BUF_SIZE); + err = -EINVAL; + goto out; + } + + /* Read the data from the WLAN module into our command buffer */ + err = spu_read(card, IF_SPI_CMD_RDWRPORT_REG, + card->cmd_buffer, ALIGN(len, 4)); + if (err) + goto out; + + spin_lock_irqsave(&priv->driver_lock, flags); + i = (priv->resp_idx == 0) ? 1 : 0; + BUG_ON(priv->resp_len[i]); + priv->resp_len[i] = len; + memcpy(priv->resp_buf[i], card->cmd_buffer, len); + lbs_notify_command_response(priv, i); + spin_unlock_irqrestore(&priv->driver_lock, flags); + +out: + if (err) + netdev_err(priv->dev, "%s: err=%d\n", __func__, err); + lbs_deb_leave(LBS_DEB_SPI); + return err; +} + +/* Move data from the card to the host */ +static int if_spi_c2h_data(struct if_spi_card *card) +{ + struct lbs_private *priv = card->priv; + struct sk_buff *skb; + char *data; + u16 len; + int err = 0; + + lbs_deb_enter(LBS_DEB_SPI); + + /* How many bytes are there to read? */ + err = spu_read_u16(card, IF_SPI_SCRATCH_1_REG, &len); + if (err) + goto out; + if (!len) { + netdev_err(priv->dev, "%s: error: card has no data for host\n", + __func__); + err = -EINVAL; + goto out; + } else if (len > MRVDRV_ETH_RX_PACKET_BUFFER_SIZE) { + netdev_err(priv->dev, + "%s: error: card has %d bytes of data, but our maximum skb size is %zu\n", + __func__, len, MRVDRV_ETH_RX_PACKET_BUFFER_SIZE); + err = -EINVAL; + goto out; + } + + /* TODO: should we allocate a smaller skb if we have less data? */ + skb = dev_alloc_skb(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE); + if (!skb) { + err = -ENOBUFS; + goto out; + } + skb_reserve(skb, IPFIELD_ALIGN_OFFSET); + data = skb_put(skb, len); + + /* Read the data from the WLAN module into our skb... */ + err = spu_read(card, IF_SPI_DATA_RDWRPORT_REG, data, ALIGN(len, 4)); + if (err) + goto free_skb; + + /* pass the SKB to libertas */ + err = lbs_process_rxed_packet(card->priv, skb); + if (err) + goto free_skb; + + /* success */ + goto out; + +free_skb: + dev_kfree_skb(skb); +out: + if (err) + netdev_err(priv->dev, "%s: err=%d\n", __func__, err); + lbs_deb_leave(LBS_DEB_SPI); + return err; +} + +/* Move data or a command from the host to the card. */ +static void if_spi_h2c(struct if_spi_card *card, + struct if_spi_packet *packet, int type) +{ + struct lbs_private *priv = card->priv; + int err = 0; + u16 int_type, port_reg; + + switch (type) { + case MVMS_DAT: + int_type = IF_SPI_CIC_TX_DOWNLOAD_OVER; + port_reg = IF_SPI_DATA_RDWRPORT_REG; + break; + case MVMS_CMD: + int_type = IF_SPI_CIC_CMD_DOWNLOAD_OVER; + port_reg = IF_SPI_CMD_RDWRPORT_REG; + break; + default: + netdev_err(priv->dev, "can't transfer buffer of type %d\n", + type); + err = -EINVAL; + goto out; + } + + /* Write the data to the card */ + err = spu_write(card, port_reg, packet->buffer, packet->blen); + if (err) + goto out; + +out: + kfree(packet); + + if (err) + netdev_err(priv->dev, "%s: error %d\n", __func__, err); +} + +/* Inform the host about a card event */ +static void if_spi_e2h(struct if_spi_card *card) +{ + int err = 0; + u32 cause; + struct lbs_private *priv = card->priv; + + err = spu_read_u32(card, IF_SPI_SCRATCH_3_REG, &cause); + if (err) + goto out; + + /* re-enable the card event interrupt */ + spu_write_u16(card, IF_SPI_HOST_INT_STATUS_REG, + ~IF_SPI_HICU_CARD_EVENT); + + /* generate a card interrupt */ + spu_write_u16(card, IF_SPI_CARD_INT_CAUSE_REG, IF_SPI_CIC_HOST_EVENT); + + lbs_queue_event(priv, cause & 0xff); +out: + if (err) + netdev_err(priv->dev, "%s: error %d\n", __func__, err); +} + +static void if_spi_host_to_card_worker(struct work_struct *work) +{ + int err; + struct if_spi_card *card; + u16 hiStatus; + unsigned long flags; + struct if_spi_packet *packet; + struct lbs_private *priv; + + card = container_of(work, struct if_spi_card, packet_work); + priv = card->priv; + + lbs_deb_enter(LBS_DEB_SPI); + + /* + * Read the host interrupt status register to see what we + * can do. + */ + err = spu_read_u16(card, IF_SPI_HOST_INT_STATUS_REG, + &hiStatus); + if (err) { + netdev_err(priv->dev, "I/O error\n"); + goto err; + } + + if (hiStatus & IF_SPI_HIST_CMD_UPLOAD_RDY) { + err = if_spi_c2h_cmd(card); + if (err) + goto err; + } + if (hiStatus & IF_SPI_HIST_RX_UPLOAD_RDY) { + err = if_spi_c2h_data(card); + if (err) + goto err; + } + + /* + * workaround: in PS mode, the card does not set the Command + * Download Ready bit, but it sets TX Download Ready. + */ + if (hiStatus & IF_SPI_HIST_CMD_DOWNLOAD_RDY || + (card->priv->psstate != PS_STATE_FULL_POWER && + (hiStatus & IF_SPI_HIST_TX_DOWNLOAD_RDY))) { + /* + * This means two things. First of all, + * if there was a previous command sent, the card has + * successfully received it. + * Secondly, it is now ready to download another + * command. + */ + lbs_host_to_card_done(card->priv); + + /* Do we have any command packets from the host to send? */ + packet = NULL; + spin_lock_irqsave(&card->buffer_lock, flags); + if (!list_empty(&card->cmd_packet_list)) { + packet = (struct if_spi_packet *)(card-> + cmd_packet_list.next); + list_del(&packet->list); + } + spin_unlock_irqrestore(&card->buffer_lock, flags); + + if (packet) + if_spi_h2c(card, packet, MVMS_CMD); + } + if (hiStatus & IF_SPI_HIST_TX_DOWNLOAD_RDY) { + /* Do we have any data packets from the host to send? */ + packet = NULL; + spin_lock_irqsave(&card->buffer_lock, flags); + if (!list_empty(&card->data_packet_list)) { + packet = (struct if_spi_packet *)(card-> + data_packet_list.next); + list_del(&packet->list); + } + spin_unlock_irqrestore(&card->buffer_lock, flags); + + if (packet) + if_spi_h2c(card, packet, MVMS_DAT); + } + if (hiStatus & IF_SPI_HIST_CARD_EVENT) + if_spi_e2h(card); + +err: + if (err) + netdev_err(priv->dev, "%s: got error %d\n", __func__, err); + + lbs_deb_leave(LBS_DEB_SPI); +} + +/* + * Host to Card + * + * Called from Libertas to transfer some data to the WLAN device + * We can't sleep here. + */ +static int if_spi_host_to_card(struct lbs_private *priv, + u8 type, u8 *buf, u16 nb) +{ + int err = 0; + unsigned long flags; + struct if_spi_card *card = priv->card; + struct if_spi_packet *packet; + u16 blen; + + lbs_deb_enter_args(LBS_DEB_SPI, "type %d, bytes %d", type, nb); + + if (nb == 0) { + netdev_err(priv->dev, "%s: invalid size requested: %d\n", + __func__, nb); + err = -EINVAL; + goto out; + } + blen = ALIGN(nb, 4); + packet = kzalloc(sizeof(struct if_spi_packet) + blen, GFP_ATOMIC); + if (!packet) { + err = -ENOMEM; + goto out; + } + packet->blen = blen; + memcpy(packet->buffer, buf, nb); + memset(packet->buffer + nb, 0, blen - nb); + + switch (type) { + case MVMS_CMD: + priv->dnld_sent = DNLD_CMD_SENT; + spin_lock_irqsave(&card->buffer_lock, flags); + list_add_tail(&packet->list, &card->cmd_packet_list); + spin_unlock_irqrestore(&card->buffer_lock, flags); + break; + case MVMS_DAT: + priv->dnld_sent = DNLD_DATA_SENT; + spin_lock_irqsave(&card->buffer_lock, flags); + list_add_tail(&packet->list, &card->data_packet_list); + spin_unlock_irqrestore(&card->buffer_lock, flags); + break; + default: + kfree(packet); + netdev_err(priv->dev, "can't transfer buffer of type %d\n", + type); + err = -EINVAL; + break; + } + + /* Queue spi xfer work */ + queue_work(card->workqueue, &card->packet_work); +out: + lbs_deb_leave_args(LBS_DEB_SPI, "err=%d", err); + return err; +} + +/* + * Host Interrupts + * + * Service incoming interrupts from the WLAN device. We can't sleep here, so + * don't try to talk on the SPI bus, just queue the SPI xfer work. + */ +static irqreturn_t if_spi_host_interrupt(int irq, void *dev_id) +{ + struct if_spi_card *card = dev_id; + + queue_work(card->workqueue, &card->packet_work); + + return IRQ_HANDLED; +} + +/* + * SPI callbacks + */ + +static int if_spi_init_card(struct if_spi_card *card) +{ + struct lbs_private *priv = card->priv; + int err, i; + u32 scratch; + const struct firmware *helper = NULL; + const struct firmware *mainfw = NULL; + + lbs_deb_enter(LBS_DEB_SPI); + + err = spu_init(card, card->pdata->use_dummy_writes); + if (err) + goto out; + err = spu_get_chip_revision(card, &card->card_id, &card->card_rev); + if (err) + goto out; + + err = spu_read_u32(card, IF_SPI_SCRATCH_4_REG, &scratch); + if (err) + goto out; + if (scratch == SUCCESSFUL_FW_DOWNLOAD_MAGIC) + lbs_deb_spi("Firmware is already loaded for " + "Marvell WLAN 802.11 adapter\n"); + else { + /* Check if we support this card */ + for (i = 0; i < ARRAY_SIZE(fw_table); i++) { + if (card->card_id == fw_table[i].model) + break; + } + if (i == ARRAY_SIZE(fw_table)) { + netdev_err(priv->dev, "Unsupported chip_id: 0x%02x\n", + card->card_id); + err = -ENODEV; + goto out; + } + + err = lbs_get_firmware(&card->spi->dev, card->card_id, + &fw_table[0], &helper, &mainfw); + if (err) { + netdev_err(priv->dev, "failed to find firmware (%d)\n", + err); + goto out; + } + + lbs_deb_spi("Initializing FW for Marvell WLAN 802.11 adapter " + "(chip_id = 0x%04x, chip_rev = 0x%02x) " + "attached to SPI bus_num %d, chip_select %d. " + "spi->max_speed_hz=%d\n", + card->card_id, card->card_rev, + card->spi->master->bus_num, + card->spi->chip_select, + card->spi->max_speed_hz); + err = if_spi_prog_helper_firmware(card, helper); + if (err) + goto out; + err = if_spi_prog_main_firmware(card, mainfw); + if (err) + goto out; + lbs_deb_spi("loaded FW for Marvell WLAN 802.11 adapter\n"); + } + + err = spu_set_interrupt_mode(card, 0, 1); + if (err) + goto out; + +out: + release_firmware(helper); + release_firmware(mainfw); + + lbs_deb_leave_args(LBS_DEB_SPI, "err %d\n", err); + + return err; +} + +static void if_spi_resume_worker(struct work_struct *work) +{ + struct if_spi_card *card; + + card = container_of(work, struct if_spi_card, resume_work); + + if (card->suspended) { + if (card->pdata->setup) + card->pdata->setup(card->spi); + + /* Init card ... */ + if_spi_init_card(card); + + enable_irq(card->spi->irq); + + /* And resume it ... */ + lbs_resume(card->priv); + + card->suspended = 0; + } +} + +static int if_spi_probe(struct spi_device *spi) +{ + struct if_spi_card *card; + struct lbs_private *priv = NULL; + struct libertas_spi_platform_data *pdata = spi->dev.platform_data; + int err = 0; + + lbs_deb_enter(LBS_DEB_SPI); + + if (!pdata) { + err = -EINVAL; + goto out; + } + + if (pdata->setup) { + err = pdata->setup(spi); + if (err) + goto out; + } + + /* Allocate card structure to represent this specific device */ + card = kzalloc(sizeof(struct if_spi_card), GFP_KERNEL); + if (!card) { + err = -ENOMEM; + goto teardown; + } + spi_set_drvdata(spi, card); + card->pdata = pdata; + card->spi = spi; + card->prev_xfer_time = jiffies; + + INIT_LIST_HEAD(&card->cmd_packet_list); + INIT_LIST_HEAD(&card->data_packet_list); + spin_lock_init(&card->buffer_lock); + + /* Initialize the SPI Interface Unit */ + + /* Firmware load */ + err = if_spi_init_card(card); + if (err) + goto free_card; + + /* + * Register our card with libertas. + * This will call alloc_etherdev. + */ + priv = lbs_add_card(card, &spi->dev); + if (!priv) { + err = -ENOMEM; + goto free_card; + } + card->priv = priv; + priv->setup_fw_on_resume = 1; + priv->card = card; + priv->hw_host_to_card = if_spi_host_to_card; + priv->enter_deep_sleep = NULL; + priv->exit_deep_sleep = NULL; + priv->reset_deep_sleep_wakeup = NULL; + priv->fw_ready = 1; + + /* Initialize interrupt handling stuff. */ + card->workqueue = create_workqueue("libertas_spi"); + INIT_WORK(&card->packet_work, if_spi_host_to_card_worker); + INIT_WORK(&card->resume_work, if_spi_resume_worker); + + err = request_irq(spi->irq, if_spi_host_interrupt, + IRQF_TRIGGER_FALLING, "libertas_spi", card); + if (err) { + pr_err("can't get host irq line-- request_irq failed\n"); + goto terminate_workqueue; + } + + /* + * Start the card. + * This will call register_netdev, and we'll start + * getting interrupts... + */ + err = lbs_start_card(priv); + if (err) + goto release_irq; + + lbs_deb_spi("Finished initializing WLAN module.\n"); + + /* successful exit */ + goto out; + +release_irq: + free_irq(spi->irq, card); +terminate_workqueue: + flush_workqueue(card->workqueue); + destroy_workqueue(card->workqueue); + lbs_remove_card(priv); /* will call free_netdev */ +free_card: + free_if_spi_card(card); +teardown: + if (pdata->teardown) + pdata->teardown(spi); +out: + lbs_deb_leave_args(LBS_DEB_SPI, "err %d\n", err); + return err; +} + +static int libertas_spi_remove(struct spi_device *spi) +{ + struct if_spi_card *card = spi_get_drvdata(spi); + struct lbs_private *priv = card->priv; + + lbs_deb_spi("libertas_spi_remove\n"); + lbs_deb_enter(LBS_DEB_SPI); + + cancel_work_sync(&card->resume_work); + + lbs_stop_card(priv); + lbs_remove_card(priv); /* will call free_netdev */ + + free_irq(spi->irq, card); + flush_workqueue(card->workqueue); + destroy_workqueue(card->workqueue); + if (card->pdata->teardown) + card->pdata->teardown(spi); + free_if_spi_card(card); + lbs_deb_leave(LBS_DEB_SPI); + return 0; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) +static int if_spi_suspend(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct if_spi_card *card = spi_get_drvdata(spi); + + if (!card->suspended) { + lbs_suspend(card->priv); + flush_workqueue(card->workqueue); + disable_irq(spi->irq); + + if (card->pdata->teardown) + card->pdata->teardown(spi); + card->suspended = 1; + } + + return 0; +} + +static int if_spi_resume(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct if_spi_card *card = spi_get_drvdata(spi); + + /* Schedule delayed work */ + schedule_work(&card->resume_work); + + return 0; +} + +static const struct dev_pm_ops if_spi_pm_ops = { + .suspend = if_spi_suspend, + .resume = if_spi_resume, +}; +#endif + +static struct spi_driver libertas_spi_driver = { + .probe = if_spi_probe, + .remove = libertas_spi_remove, + .driver = { + .name = "libertas_spi", + .owner = THIS_MODULE, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) + .pm = &if_spi_pm_ops, +#endif + }, +}; + +/* + * Module functions + */ + +static int __init if_spi_init_module(void) +{ + int ret = 0; + lbs_deb_enter(LBS_DEB_SPI); + printk(KERN_INFO "libertas_spi: Libertas SPI driver\n"); + ret = spi_register_driver(&libertas_spi_driver); + lbs_deb_leave(LBS_DEB_SPI); + return ret; +} + +static void __exit if_spi_exit_module(void) +{ + lbs_deb_enter(LBS_DEB_SPI); + spi_unregister_driver(&libertas_spi_driver); + lbs_deb_leave(LBS_DEB_SPI); +} + +module_init(if_spi_init_module); +module_exit(if_spi_exit_module); + +MODULE_DESCRIPTION("Libertas SPI WLAN Driver"); +MODULE_AUTHOR("Andrey Yurovsky , " + "Colin McCabe "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:libertas_spi"); diff --git a/drivers/net/wireless/libertas/if_spi.h b/drivers/net/wireless/libertas/if_spi.h new file mode 100644 index 0000000..e450e31 --- /dev/null +++ b/drivers/net/wireless/libertas/if_spi.h @@ -0,0 +1,206 @@ +/* + * linux/drivers/net/wireless/libertas/if_spi.c + * + * Driver for Marvell SPI WLAN cards. + * + * Copyright 2008 Analog Devices Inc. + * + * Authors: + * Andrey Yurovsky + * Colin McCabe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#ifndef _LBS_IF_SPI_H_ +#define _LBS_IF_SPI_H_ + +#define IPFIELD_ALIGN_OFFSET 2 +#define IF_SPI_CMD_BUF_SIZE 2400 + +/***************** Firmware *****************/ + +#define IF_SPI_FW_NAME_MAX 30 + +#define MAX_MAIN_FW_LOAD_CRC_ERR 10 + +/* Chunk size when loading the helper firmware */ +#define HELPER_FW_LOAD_CHUNK_SZ 64 + +/* Value to write to indicate end of helper firmware dnld */ +#define FIRMWARE_DNLD_OK 0x0000 + +/* Value to check once the main firmware is downloaded */ +#define SUCCESSFUL_FW_DOWNLOAD_MAGIC 0x88888888 + +/***************** SPI Interface Unit *****************/ +/* Masks used in SPI register read/write operations */ +#define IF_SPI_READ_OPERATION_MASK 0x0 +#define IF_SPI_WRITE_OPERATION_MASK 0x8000 + +/* SPI register offsets. 4-byte aligned. */ +#define IF_SPI_DEVICEID_CTRL_REG 0x00 /* DeviceID controller reg */ +#define IF_SPI_IO_READBASE_REG 0x04 /* Read I/O base reg */ +#define IF_SPI_IO_WRITEBASE_REG 0x08 /* Write I/O base reg */ +#define IF_SPI_IO_RDWRPORT_REG 0x0C /* Read/Write I/O port reg */ + +#define IF_SPI_CMD_READBASE_REG 0x10 /* Read command base reg */ +#define IF_SPI_CMD_WRITEBASE_REG 0x14 /* Write command base reg */ +#define IF_SPI_CMD_RDWRPORT_REG 0x18 /* Read/Write command port reg */ + +#define IF_SPI_DATA_READBASE_REG 0x1C /* Read data base reg */ +#define IF_SPI_DATA_WRITEBASE_REG 0x20 /* Write data base reg */ +#define IF_SPI_DATA_RDWRPORT_REG 0x24 /* Read/Write data port reg */ + +#define IF_SPI_SCRATCH_1_REG 0x28 /* Scratch reg 1 */ +#define IF_SPI_SCRATCH_2_REG 0x2C /* Scratch reg 2 */ +#define IF_SPI_SCRATCH_3_REG 0x30 /* Scratch reg 3 */ +#define IF_SPI_SCRATCH_4_REG 0x34 /* Scratch reg 4 */ + +#define IF_SPI_TX_FRAME_SEQ_NUM_REG 0x38 /* Tx frame sequence number reg */ +#define IF_SPI_TX_FRAME_STATUS_REG 0x3C /* Tx frame status reg */ + +#define IF_SPI_HOST_INT_CTRL_REG 0x40 /* Host interrupt controller reg */ + +#define IF_SPI_CARD_INT_CAUSE_REG 0x44 /* Card interrupt cause reg */ +#define IF_SPI_CARD_INT_STATUS_REG 0x48 /* Card interrupt status reg */ +#define IF_SPI_CARD_INT_EVENT_MASK_REG 0x4C /* Card interrupt event mask */ +#define IF_SPI_CARD_INT_STATUS_MASK_REG 0x50 /* Card interrupt status mask */ + +#define IF_SPI_CARD_INT_RESET_SELECT_REG 0x54 /* Card interrupt reset select */ + +#define IF_SPI_HOST_INT_CAUSE_REG 0x58 /* Host interrupt cause reg */ +#define IF_SPI_HOST_INT_STATUS_REG 0x5C /* Host interrupt status reg */ +#define IF_SPI_HOST_INT_EVENT_MASK_REG 0x60 /* Host interrupt event mask */ +#define IF_SPI_HOST_INT_STATUS_MASK_REG 0x64 /* Host interrupt status mask */ +#define IF_SPI_HOST_INT_RESET_SELECT_REG 0x68 /* Host interrupt reset select */ + +#define IF_SPI_DELAY_READ_REG 0x6C /* Delay read reg */ +#define IF_SPI_SPU_BUS_MODE_REG 0x70 /* SPU BUS mode reg */ + +/***************** IF_SPI_DEVICEID_CTRL_REG *****************/ +#define IF_SPI_DEVICEID_CTRL_REG_TO_CARD_ID(dc) ((dc & 0xffff0000)>>16) +#define IF_SPI_DEVICEID_CTRL_REG_TO_CARD_REV(dc) (dc & 0x000000ff) + +/***************** IF_SPI_HOST_INT_CTRL_REG *****************/ +/* Host Interrupt Control bit : Wake up */ +#define IF_SPI_HICT_WAKE_UP (1<<0) +/* Host Interrupt Control bit : WLAN ready */ +#define IF_SPI_HICT_WLAN_READY (1<<1) +/*#define IF_SPI_HICT_FIFO_FIRST_HALF_EMPTY (1<<2) */ +/*#define IF_SPI_HICT_FIFO_SECOND_HALF_EMPTY (1<<3) */ +/*#define IF_SPI_HICT_IRQSRC_WLAN (1<<4) */ +/* Host Interrupt Control bit : Tx auto download */ +#define IF_SPI_HICT_TX_DOWNLOAD_OVER_AUTO (1<<5) +/* Host Interrupt Control bit : Rx auto upload */ +#define IF_SPI_HICT_RX_UPLOAD_OVER_AUTO (1<<6) +/* Host Interrupt Control bit : Command auto download */ +#define IF_SPI_HICT_CMD_DOWNLOAD_OVER_AUTO (1<<7) +/* Host Interrupt Control bit : Command auto upload */ +#define IF_SPI_HICT_CMD_UPLOAD_OVER_AUTO (1<<8) + +/***************** IF_SPI_CARD_INT_CAUSE_REG *****************/ +/* Card Interrupt Case bit : Tx download over */ +#define IF_SPI_CIC_TX_DOWNLOAD_OVER (1<<0) +/* Card Interrupt Case bit : Rx upload over */ +#define IF_SPI_CIC_RX_UPLOAD_OVER (1<<1) +/* Card Interrupt Case bit : Command download over */ +#define IF_SPI_CIC_CMD_DOWNLOAD_OVER (1<<2) +/* Card Interrupt Case bit : Host event */ +#define IF_SPI_CIC_HOST_EVENT (1<<3) +/* Card Interrupt Case bit : Command upload over */ +#define IF_SPI_CIC_CMD_UPLOAD_OVER (1<<4) +/* Card Interrupt Case bit : Power down */ +#define IF_SPI_CIC_POWER_DOWN (1<<5) + +/***************** IF_SPI_CARD_INT_STATUS_REG *****************/ +#define IF_SPI_CIS_TX_DOWNLOAD_OVER (1<<0) +#define IF_SPI_CIS_RX_UPLOAD_OVER (1<<1) +#define IF_SPI_CIS_CMD_DOWNLOAD_OVER (1<<2) +#define IF_SPI_CIS_HOST_EVENT (1<<3) +#define IF_SPI_CIS_CMD_UPLOAD_OVER (1<<4) +#define IF_SPI_CIS_POWER_DOWN (1<<5) + +/***************** IF_SPI_HOST_INT_CAUSE_REG *****************/ +#define IF_SPI_HICU_TX_DOWNLOAD_RDY (1<<0) +#define IF_SPI_HICU_RX_UPLOAD_RDY (1<<1) +#define IF_SPI_HICU_CMD_DOWNLOAD_RDY (1<<2) +#define IF_SPI_HICU_CARD_EVENT (1<<3) +#define IF_SPI_HICU_CMD_UPLOAD_RDY (1<<4) +#define IF_SPI_HICU_IO_WR_FIFO_OVERFLOW (1<<5) +#define IF_SPI_HICU_IO_RD_FIFO_UNDERFLOW (1<<6) +#define IF_SPI_HICU_DATA_WR_FIFO_OVERFLOW (1<<7) +#define IF_SPI_HICU_DATA_RD_FIFO_UNDERFLOW (1<<8) +#define IF_SPI_HICU_CMD_WR_FIFO_OVERFLOW (1<<9) +#define IF_SPI_HICU_CMD_RD_FIFO_UNDERFLOW (1<<10) + +/***************** IF_SPI_HOST_INT_STATUS_REG *****************/ +/* Host Interrupt Status bit : Tx download ready */ +#define IF_SPI_HIST_TX_DOWNLOAD_RDY (1<<0) +/* Host Interrupt Status bit : Rx upload ready */ +#define IF_SPI_HIST_RX_UPLOAD_RDY (1<<1) +/* Host Interrupt Status bit : Command download ready */ +#define IF_SPI_HIST_CMD_DOWNLOAD_RDY (1<<2) +/* Host Interrupt Status bit : Card event */ +#define IF_SPI_HIST_CARD_EVENT (1<<3) +/* Host Interrupt Status bit : Command upload ready */ +#define IF_SPI_HIST_CMD_UPLOAD_RDY (1<<4) +/* Host Interrupt Status bit : I/O write FIFO overflow */ +#define IF_SPI_HIST_IO_WR_FIFO_OVERFLOW (1<<5) +/* Host Interrupt Status bit : I/O read FIFO underflow */ +#define IF_SPI_HIST_IO_RD_FIFO_UNDRFLOW (1<<6) +/* Host Interrupt Status bit : Data write FIFO overflow */ +#define IF_SPI_HIST_DATA_WR_FIFO_OVERFLOW (1<<7) +/* Host Interrupt Status bit : Data read FIFO underflow */ +#define IF_SPI_HIST_DATA_RD_FIFO_UNDERFLOW (1<<8) +/* Host Interrupt Status bit : Command write FIFO overflow */ +#define IF_SPI_HIST_CMD_WR_FIFO_OVERFLOW (1<<9) +/* Host Interrupt Status bit : Command read FIFO underflow */ +#define IF_SPI_HIST_CMD_RD_FIFO_UNDERFLOW (1<<10) + +/***************** IF_SPI_HOST_INT_STATUS_MASK_REG *****************/ +/* Host Interrupt Status Mask bit : Tx download ready */ +#define IF_SPI_HISM_TX_DOWNLOAD_RDY (1<<0) +/* Host Interrupt Status Mask bit : Rx upload ready */ +#define IF_SPI_HISM_RX_UPLOAD_RDY (1<<1) +/* Host Interrupt Status Mask bit : Command download ready */ +#define IF_SPI_HISM_CMD_DOWNLOAD_RDY (1<<2) +/* Host Interrupt Status Mask bit : Card event */ +#define IF_SPI_HISM_CARDEVENT (1<<3) +/* Host Interrupt Status Mask bit : Command upload ready */ +#define IF_SPI_HISM_CMD_UPLOAD_RDY (1<<4) +/* Host Interrupt Status Mask bit : I/O write FIFO overflow */ +#define IF_SPI_HISM_IO_WR_FIFO_OVERFLOW (1<<5) +/* Host Interrupt Status Mask bit : I/O read FIFO underflow */ +#define IF_SPI_HISM_IO_RD_FIFO_UNDERFLOW (1<<6) +/* Host Interrupt Status Mask bit : Data write FIFO overflow */ +#define IF_SPI_HISM_DATA_WR_FIFO_OVERFLOW (1<<7) +/* Host Interrupt Status Mask bit : Data write FIFO underflow */ +#define IF_SPI_HISM_DATA_RD_FIFO_UNDERFLOW (1<<8) +/* Host Interrupt Status Mask bit : Command write FIFO overflow */ +#define IF_SPI_HISM_CMD_WR_FIFO_OVERFLOW (1<<9) +/* Host Interrupt Status Mask bit : Command write FIFO underflow */ +#define IF_SPI_HISM_CMD_RD_FIFO_UNDERFLOW (1<<10) + +/***************** IF_SPI_SPU_BUS_MODE_REG *****************/ +/* SCK edge on which the WLAN module outputs data on MISO */ +#define IF_SPI_BUS_MODE_SPI_CLOCK_PHASE_FALLING 0x8 +#define IF_SPI_BUS_MODE_SPI_CLOCK_PHASE_RISING 0x0 + +/* In a SPU read operation, there is a delay between writing the SPU + * register name and getting back data from the WLAN module. + * This can be specified in terms of nanoseconds or in terms of dummy + * clock cycles which the master must output before receiving a response. */ +#define IF_SPI_BUS_MODE_DELAY_METHOD_DUMMY_CLOCK 0x4 +#define IF_SPI_BUS_MODE_DELAY_METHOD_TIMED 0x0 + +/* Some different modes of SPI operation */ +#define IF_SPI_BUS_MODE_8_BIT_ADDRESS_16_BIT_DATA 0x00 +#define IF_SPI_BUS_MODE_8_BIT_ADDRESS_32_BIT_DATA 0x01 +#define IF_SPI_BUS_MODE_16_BIT_ADDRESS_16_BIT_DATA 0x02 +#define IF_SPI_BUS_MODE_16_BIT_ADDRESS_32_BIT_DATA 0x03 + +#endif diff --git a/drivers/net/wireless/libertas/if_usb.c b/drivers/net/wireless/libertas/if_usb.c new file mode 100644 index 0000000..dddb6eb --- /dev/null +++ b/drivers/net/wireless/libertas/if_usb.c @@ -0,0 +1,1025 @@ +/* + * This file contains functions used in USB interface module. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_OLPC +#include +#endif + +#define DRV_NAME "usb8xxx" + +#include "host.h" +#include "decl.h" +#include "defs.h" +#include "dev.h" +#include "cmd.h" +#include "if_usb.h" + +#define INSANEDEBUG 0 +#define lbs_deb_usb2(...) do { if (INSANEDEBUG) lbs_deb_usbd(__VA_ARGS__); } while (0) + +#define MESSAGE_HEADER_LEN 4 + +MODULE_FIRMWARE("libertas/usb8388_v9.bin"); +MODULE_FIRMWARE("libertas/usb8388_v5.bin"); +MODULE_FIRMWARE("libertas/usb8388.bin"); +MODULE_FIRMWARE("libertas/usb8682.bin"); +MODULE_FIRMWARE("usb8388.bin"); + +enum { + MODEL_UNKNOWN = 0x0, + MODEL_8388 = 0x1, + MODEL_8682 = 0x2 +}; + +/* table of firmware file names */ +static const struct lbs_fw_table fw_table[] = { + { MODEL_8388, "libertas/usb8388_olpc.bin", NULL }, + { MODEL_8388, "libertas/usb8388_v9.bin", NULL }, + { MODEL_8388, "libertas/usb8388_v5.bin", NULL }, + { MODEL_8388, "libertas/usb8388.bin", NULL }, + { MODEL_8388, "usb8388.bin", NULL }, + { MODEL_8682, "libertas/usb8682.bin", NULL } +}; + +static struct usb_device_id if_usb_table[] = { + /* Enter the device signature inside */ + { USB_DEVICE(0x1286, 0x2001), .driver_info = MODEL_8388 }, + { USB_DEVICE(0x05a3, 0x8388), .driver_info = MODEL_8388 }, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, if_usb_table); + +static void if_usb_receive(struct urb *urb); +static void if_usb_receive_fwload(struct urb *urb); +static void if_usb_prog_firmware(struct lbs_private *priv, int ret, + const struct firmware *fw, + const struct firmware *unused); +static int if_usb_host_to_card(struct lbs_private *priv, uint8_t type, + uint8_t *payload, uint16_t nb); +static int usb_tx_block(struct if_usb_card *cardp, uint8_t *payload, + uint16_t nb); +static void if_usb_free(struct if_usb_card *cardp); +static int if_usb_submit_rx_urb(struct if_usb_card *cardp); +static int if_usb_reset_device(struct if_usb_card *cardp); + +/** + * if_usb_write_bulk_callback - callback function to handle the status + * of the URB + * @urb: pointer to &urb structure + * returns: N/A + */ +static void if_usb_write_bulk_callback(struct urb *urb) +{ + struct if_usb_card *cardp = (struct if_usb_card *) urb->context; + + /* handle the transmission complete validations */ + + if (urb->status == 0) { + struct lbs_private *priv = cardp->priv; + + lbs_deb_usb2(&urb->dev->dev, "URB status is successful\n"); + lbs_deb_usb2(&urb->dev->dev, "Actual length transmitted %d\n", + urb->actual_length); + + /* Boot commands such as UPDATE_FW and UPDATE_BOOT2 are not + * passed up to the lbs level. + */ + if (priv && priv->dnld_sent != DNLD_BOOTCMD_SENT) + lbs_host_to_card_done(priv); + } else { + /* print the failure status number for debug */ + pr_info("URB in failure status: %d\n", urb->status); + } +} + +/** + * if_usb_free - free tx/rx urb, skb and rx buffer + * @cardp: pointer to &if_usb_card + * returns: N/A + */ +static void if_usb_free(struct if_usb_card *cardp) +{ + lbs_deb_enter(LBS_DEB_USB); + + /* Unlink tx & rx urb */ + usb_kill_urb(cardp->tx_urb); + usb_kill_urb(cardp->rx_urb); + + usb_free_urb(cardp->tx_urb); + cardp->tx_urb = NULL; + + usb_free_urb(cardp->rx_urb); + cardp->rx_urb = NULL; + + kfree(cardp->ep_out_buf); + cardp->ep_out_buf = NULL; + + lbs_deb_leave(LBS_DEB_USB); +} + +static void if_usb_setup_firmware(struct lbs_private *priv) +{ + struct if_usb_card *cardp = priv->card; + struct cmd_ds_set_boot2_ver b2_cmd; + struct cmd_ds_802_11_fw_wake_method wake_method; + + b2_cmd.hdr.size = cpu_to_le16(sizeof(b2_cmd)); + b2_cmd.action = 0; + b2_cmd.version = cardp->boot2_version; + + if (lbs_cmd_with_response(priv, CMD_SET_BOOT2_VER, &b2_cmd)) + lbs_deb_usb("Setting boot2 version failed\n"); + + priv->wol_gpio = 2; /* Wake via GPIO2... */ + priv->wol_gap = 20; /* ... after 20ms */ + lbs_host_sleep_cfg(priv, EHS_WAKE_ON_UNICAST_DATA, + (struct wol_config *) NULL); + + wake_method.hdr.size = cpu_to_le16(sizeof(wake_method)); + wake_method.action = cpu_to_le16(CMD_ACT_GET); + if (lbs_cmd_with_response(priv, CMD_802_11_FW_WAKE_METHOD, &wake_method)) { + netdev_info(priv->dev, "Firmware does not seem to support PS mode\n"); + priv->fwcapinfo &= ~FW_CAPINFO_PS; + } else { + if (le16_to_cpu(wake_method.method) == CMD_WAKE_METHOD_COMMAND_INT) { + lbs_deb_usb("Firmware seems to support PS with wake-via-command\n"); + } else { + /* The versions which boot up this way don't seem to + work even if we set it to the command interrupt */ + priv->fwcapinfo &= ~FW_CAPINFO_PS; + netdev_info(priv->dev, + "Firmware doesn't wake via command interrupt; disabling PS mode\n"); + } + } +} + +static void if_usb_fw_timeo(unsigned long priv) +{ + struct if_usb_card *cardp = (void *)priv; + + if (cardp->fwdnldover) { + lbs_deb_usb("Download complete, no event. Assuming success\n"); + } else { + pr_err("Download timed out\n"); + cardp->surprise_removed = 1; + } + wake_up(&cardp->fw_wq); +} + +#ifdef CONFIG_OLPC +static void if_usb_reset_olpc_card(struct lbs_private *priv) +{ + printk(KERN_CRIT "Resetting OLPC wireless via EC...\n"); + olpc_ec_cmd(0x25, NULL, 0, NULL, 0); +} +#endif + +/** + * if_usb_probe - sets the configuration values + * @intf: &usb_interface pointer + * @id: pointer to usb_device_id + * returns: 0 on success, error code on failure + */ +static int if_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + struct lbs_private *priv; + struct if_usb_card *cardp; + int r = -ENOMEM; + int i; + + udev = interface_to_usbdev(intf); + + cardp = kzalloc(sizeof(struct if_usb_card), GFP_KERNEL); + if (!cardp) + goto error; + + setup_timer(&cardp->fw_timeout, if_usb_fw_timeo, (unsigned long)cardp); + init_waitqueue_head(&cardp->fw_wq); + + cardp->udev = udev; + cardp->model = (uint32_t) id->driver_info; + iface_desc = intf->cur_altsetting; + + lbs_deb_usbd(&udev->dev, "bcdUSB = 0x%X bDeviceClass = 0x%X" + " bDeviceSubClass = 0x%X, bDeviceProtocol = 0x%X\n", + le16_to_cpu(udev->descriptor.bcdUSB), + udev->descriptor.bDeviceClass, + udev->descriptor.bDeviceSubClass, + udev->descriptor.bDeviceProtocol); + + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + if (usb_endpoint_is_bulk_in(endpoint)) { + cardp->ep_in_size = le16_to_cpu(endpoint->wMaxPacketSize); + cardp->ep_in = usb_endpoint_num(endpoint); + + lbs_deb_usbd(&udev->dev, "in_endpoint = %d\n", cardp->ep_in); + lbs_deb_usbd(&udev->dev, "Bulk in size is %d\n", cardp->ep_in_size); + + } else if (usb_endpoint_is_bulk_out(endpoint)) { + cardp->ep_out_size = le16_to_cpu(endpoint->wMaxPacketSize); + cardp->ep_out = usb_endpoint_num(endpoint); + + lbs_deb_usbd(&udev->dev, "out_endpoint = %d\n", cardp->ep_out); + lbs_deb_usbd(&udev->dev, "Bulk out size is %d\n", cardp->ep_out_size); + } + } + if (!cardp->ep_out_size || !cardp->ep_in_size) { + lbs_deb_usbd(&udev->dev, "Endpoints not found\n"); + goto dealloc; + } + if (!(cardp->rx_urb = usb_alloc_urb(0, GFP_KERNEL))) { + lbs_deb_usbd(&udev->dev, "Rx URB allocation failed\n"); + goto dealloc; + } + if (!(cardp->tx_urb = usb_alloc_urb(0, GFP_KERNEL))) { + lbs_deb_usbd(&udev->dev, "Tx URB allocation failed\n"); + goto dealloc; + } + cardp->ep_out_buf = kmalloc(MRVDRV_ETH_TX_PACKET_BUFFER_SIZE, GFP_KERNEL); + if (!cardp->ep_out_buf) { + lbs_deb_usbd(&udev->dev, "Could not allocate buffer\n"); + goto dealloc; + } + + if (!(priv = lbs_add_card(cardp, &intf->dev))) + goto err_add_card; + + cardp->priv = priv; + + priv->hw_host_to_card = if_usb_host_to_card; + priv->enter_deep_sleep = NULL; + priv->exit_deep_sleep = NULL; + priv->reset_deep_sleep_wakeup = NULL; +#ifdef CONFIG_OLPC + if (machine_is_olpc()) + priv->reset_card = if_usb_reset_olpc_card; +#endif + + cardp->boot2_version = udev->descriptor.bcdDevice; + + usb_get_dev(udev); + usb_set_intfdata(intf, cardp); + + r = lbs_get_firmware_async(priv, &udev->dev, cardp->model, + fw_table, if_usb_prog_firmware); + if (r) + goto err_get_fw; + + return 0; + +err_get_fw: + lbs_remove_card(priv); +err_add_card: + if_usb_reset_device(cardp); +dealloc: + if_usb_free(cardp); + +error: + return r; +} + +/** + * if_usb_disconnect - free resource and cleanup + * @intf: USB interface structure + * returns: N/A + */ +static void if_usb_disconnect(struct usb_interface *intf) +{ + struct if_usb_card *cardp = usb_get_intfdata(intf); + struct lbs_private *priv = cardp->priv; + + lbs_deb_enter(LBS_DEB_MAIN); + + cardp->surprise_removed = 1; + + if (priv) { + lbs_stop_card(priv); + lbs_remove_card(priv); + } + + /* Unlink and free urb */ + if_usb_free(cardp); + + usb_set_intfdata(intf, NULL); + usb_put_dev(interface_to_usbdev(intf)); + + lbs_deb_leave(LBS_DEB_MAIN); +} + +/** + * if_usb_send_fw_pkt - download FW + * @cardp: pointer to &struct if_usb_card + * returns: 0 + */ +static int if_usb_send_fw_pkt(struct if_usb_card *cardp) +{ + struct fwdata *fwdata = cardp->ep_out_buf; + const uint8_t *firmware = cardp->fw->data; + + /* If we got a CRC failure on the last block, back + up and retry it */ + if (!cardp->CRC_OK) { + cardp->totalbytes = cardp->fwlastblksent; + cardp->fwseqnum--; + } + + lbs_deb_usb2(&cardp->udev->dev, "totalbytes = %d\n", + cardp->totalbytes); + + /* struct fwdata (which we sent to the card) has an + extra __le32 field in between the header and the data, + which is not in the struct fwheader in the actual + firmware binary. Insert the seqnum in the middle... */ + memcpy(&fwdata->hdr, &firmware[cardp->totalbytes], + sizeof(struct fwheader)); + + cardp->fwlastblksent = cardp->totalbytes; + cardp->totalbytes += sizeof(struct fwheader); + + memcpy(fwdata->data, &firmware[cardp->totalbytes], + le32_to_cpu(fwdata->hdr.datalength)); + + lbs_deb_usb2(&cardp->udev->dev, "Data length = %d\n", + le32_to_cpu(fwdata->hdr.datalength)); + + fwdata->seqnum = cpu_to_le32(++cardp->fwseqnum); + cardp->totalbytes += le32_to_cpu(fwdata->hdr.datalength); + + usb_tx_block(cardp, cardp->ep_out_buf, sizeof(struct fwdata) + + le32_to_cpu(fwdata->hdr.datalength)); + + if (fwdata->hdr.dnldcmd == cpu_to_le32(FW_HAS_DATA_TO_RECV)) { + lbs_deb_usb2(&cardp->udev->dev, "There are data to follow\n"); + lbs_deb_usb2(&cardp->udev->dev, "seqnum = %d totalbytes = %d\n", + cardp->fwseqnum, cardp->totalbytes); + } else if (fwdata->hdr.dnldcmd == cpu_to_le32(FW_HAS_LAST_BLOCK)) { + lbs_deb_usb2(&cardp->udev->dev, "Host has finished FW downloading\n"); + lbs_deb_usb2(&cardp->udev->dev, "Donwloading FW JUMP BLOCK\n"); + + cardp->fwfinalblk = 1; + } + + lbs_deb_usb2(&cardp->udev->dev, "Firmware download done; size %d\n", + cardp->totalbytes); + + return 0; +} + +static int if_usb_reset_device(struct if_usb_card *cardp) +{ + struct cmd_header *cmd = cardp->ep_out_buf + 4; + int ret; + + lbs_deb_enter(LBS_DEB_USB); + + *(__le32 *)cardp->ep_out_buf = cpu_to_le32(CMD_TYPE_REQUEST); + + cmd->command = cpu_to_le16(CMD_802_11_RESET); + cmd->size = cpu_to_le16(sizeof(cmd)); + cmd->result = cpu_to_le16(0); + cmd->seqnum = cpu_to_le16(0x5a5a); + usb_tx_block(cardp, cardp->ep_out_buf, 4 + sizeof(struct cmd_header)); + + msleep(100); + ret = usb_reset_device(cardp->udev); + msleep(100); + +#ifdef CONFIG_OLPC + if (ret && machine_is_olpc()) + if_usb_reset_olpc_card(NULL); +#endif + + lbs_deb_leave_args(LBS_DEB_USB, "ret %d", ret); + + return ret; +} + +/** + * usb_tx_block - transfer the data to the device + * @cardp: pointer to &struct if_usb_card + * @payload: pointer to payload data + * @nb: data length + * returns: 0 for success or negative error code + */ +static int usb_tx_block(struct if_usb_card *cardp, uint8_t *payload, uint16_t nb) +{ + int ret; + + /* check if device is removed */ + if (cardp->surprise_removed) { + lbs_deb_usbd(&cardp->udev->dev, "Device removed\n"); + ret = -ENODEV; + goto tx_ret; + } + + usb_fill_bulk_urb(cardp->tx_urb, cardp->udev, + usb_sndbulkpipe(cardp->udev, + cardp->ep_out), + payload, nb, if_usb_write_bulk_callback, cardp); + + cardp->tx_urb->transfer_flags |= URB_ZERO_PACKET; + + if ((ret = usb_submit_urb(cardp->tx_urb, GFP_ATOMIC))) { + lbs_deb_usbd(&cardp->udev->dev, "usb_submit_urb failed: %d\n", ret); + } else { + lbs_deb_usb2(&cardp->udev->dev, "usb_submit_urb success\n"); + ret = 0; + } + +tx_ret: + return ret; +} + +static int __if_usb_submit_rx_urb(struct if_usb_card *cardp, + void (*callbackfn)(struct urb *urb)) +{ + struct sk_buff *skb; + int ret = -1; + + if (!(skb = dev_alloc_skb(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE))) { + pr_err("No free skb\n"); + goto rx_ret; + } + + cardp->rx_skb = skb; + + /* Fill the receive configuration URB and initialise the Rx call back */ + usb_fill_bulk_urb(cardp->rx_urb, cardp->udev, + usb_rcvbulkpipe(cardp->udev, cardp->ep_in), + skb->data + IPFIELD_ALIGN_OFFSET, + MRVDRV_ETH_RX_PACKET_BUFFER_SIZE, callbackfn, + cardp); + + cardp->rx_urb->transfer_flags |= URB_ZERO_PACKET; + + lbs_deb_usb2(&cardp->udev->dev, "Pointer for rx_urb %p\n", cardp->rx_urb); + if ((ret = usb_submit_urb(cardp->rx_urb, GFP_ATOMIC))) { + lbs_deb_usbd(&cardp->udev->dev, "Submit Rx URB failed: %d\n", ret); + kfree_skb(skb); + cardp->rx_skb = NULL; + ret = -1; + } else { + lbs_deb_usb2(&cardp->udev->dev, "Submit Rx URB success\n"); + ret = 0; + } + +rx_ret: + return ret; +} + +static int if_usb_submit_rx_urb_fwload(struct if_usb_card *cardp) +{ + return __if_usb_submit_rx_urb(cardp, &if_usb_receive_fwload); +} + +static int if_usb_submit_rx_urb(struct if_usb_card *cardp) +{ + return __if_usb_submit_rx_urb(cardp, &if_usb_receive); +} + +static void if_usb_receive_fwload(struct urb *urb) +{ + struct if_usb_card *cardp = urb->context; + struct sk_buff *skb = cardp->rx_skb; + struct fwsyncheader *syncfwheader; + struct bootcmdresp bootcmdresp; + + if (urb->status) { + lbs_deb_usbd(&cardp->udev->dev, + "URB status is failed during fw load\n"); + kfree_skb(skb); + return; + } + + if (cardp->fwdnldover) { + __le32 *tmp = (__le32 *)(skb->data + IPFIELD_ALIGN_OFFSET); + + if (tmp[0] == cpu_to_le32(CMD_TYPE_INDICATION) && + tmp[1] == cpu_to_le32(MACREG_INT_CODE_FIRMWARE_READY)) { + pr_info("Firmware ready event received\n"); + wake_up(&cardp->fw_wq); + } else { + lbs_deb_usb("Waiting for confirmation; got %x %x\n", + le32_to_cpu(tmp[0]), le32_to_cpu(tmp[1])); + if_usb_submit_rx_urb_fwload(cardp); + } + kfree_skb(skb); + return; + } + if (cardp->bootcmdresp <= 0) { + memcpy (&bootcmdresp, skb->data + IPFIELD_ALIGN_OFFSET, + sizeof(bootcmdresp)); + + if (le16_to_cpu(cardp->udev->descriptor.bcdDevice) < 0x3106) { + kfree_skb(skb); + if_usb_submit_rx_urb_fwload(cardp); + cardp->bootcmdresp = BOOT_CMD_RESP_OK; + lbs_deb_usbd(&cardp->udev->dev, + "Received valid boot command response\n"); + return; + } + if (bootcmdresp.magic != cpu_to_le32(BOOT_CMD_MAGIC_NUMBER)) { + if (bootcmdresp.magic == cpu_to_le32(CMD_TYPE_REQUEST) || + bootcmdresp.magic == cpu_to_le32(CMD_TYPE_DATA) || + bootcmdresp.magic == cpu_to_le32(CMD_TYPE_INDICATION)) { + if (!cardp->bootcmdresp) + pr_info("Firmware already seems alive; resetting\n"); + cardp->bootcmdresp = -1; + } else { + pr_info("boot cmd response wrong magic number (0x%x)\n", + le32_to_cpu(bootcmdresp.magic)); + } + } else if ((bootcmdresp.cmd != BOOT_CMD_FW_BY_USB) && + (bootcmdresp.cmd != BOOT_CMD_UPDATE_FW) && + (bootcmdresp.cmd != BOOT_CMD_UPDATE_BOOT2)) { + pr_info("boot cmd response cmd_tag error (%d)\n", + bootcmdresp.cmd); + } else if (bootcmdresp.result != BOOT_CMD_RESP_OK) { + pr_info("boot cmd response result error (%d)\n", + bootcmdresp.result); + } else { + cardp->bootcmdresp = 1; + lbs_deb_usbd(&cardp->udev->dev, + "Received valid boot command response\n"); + } + kfree_skb(skb); + if_usb_submit_rx_urb_fwload(cardp); + return; + } + + syncfwheader = kmemdup(skb->data + IPFIELD_ALIGN_OFFSET, + sizeof(struct fwsyncheader), GFP_ATOMIC); + if (!syncfwheader) { + lbs_deb_usbd(&cardp->udev->dev, "Failure to allocate syncfwheader\n"); + kfree_skb(skb); + return; + } + + if (!syncfwheader->cmd) { + lbs_deb_usb2(&cardp->udev->dev, "FW received Blk with correct CRC\n"); + lbs_deb_usb2(&cardp->udev->dev, "FW received Blk seqnum = %d\n", + le32_to_cpu(syncfwheader->seqnum)); + cardp->CRC_OK = 1; + } else { + lbs_deb_usbd(&cardp->udev->dev, "FW received Blk with CRC error\n"); + cardp->CRC_OK = 0; + } + + kfree_skb(skb); + + /* Give device 5s to either write firmware to its RAM or eeprom */ + mod_timer(&cardp->fw_timeout, jiffies + (HZ*5)); + + if (cardp->fwfinalblk) { + cardp->fwdnldover = 1; + goto exit; + } + + if_usb_send_fw_pkt(cardp); + + exit: + if_usb_submit_rx_urb_fwload(cardp); + + kfree(syncfwheader); +} + +#define MRVDRV_MIN_PKT_LEN 30 + +static inline void process_cmdtypedata(int recvlength, struct sk_buff *skb, + struct if_usb_card *cardp, + struct lbs_private *priv) +{ + if (recvlength > MRVDRV_ETH_RX_PACKET_BUFFER_SIZE + MESSAGE_HEADER_LEN + || recvlength < MRVDRV_MIN_PKT_LEN) { + lbs_deb_usbd(&cardp->udev->dev, "Packet length is Invalid\n"); + kfree_skb(skb); + return; + } + + skb_reserve(skb, IPFIELD_ALIGN_OFFSET); + skb_put(skb, recvlength); + skb_pull(skb, MESSAGE_HEADER_LEN); + + lbs_process_rxed_packet(priv, skb); +} + +static inline void process_cmdrequest(int recvlength, uint8_t *recvbuff, + struct sk_buff *skb, + struct if_usb_card *cardp, + struct lbs_private *priv) +{ + u8 i; + + if (recvlength > LBS_CMD_BUFFER_SIZE) { + lbs_deb_usbd(&cardp->udev->dev, + "The receive buffer is too large\n"); + kfree_skb(skb); + return; + } + + BUG_ON(!in_interrupt()); + + spin_lock(&priv->driver_lock); + + i = (priv->resp_idx == 0) ? 1 : 0; + BUG_ON(priv->resp_len[i]); + priv->resp_len[i] = (recvlength - MESSAGE_HEADER_LEN); + memcpy(priv->resp_buf[i], recvbuff + MESSAGE_HEADER_LEN, + priv->resp_len[i]); + kfree_skb(skb); + lbs_notify_command_response(priv, i); + + spin_unlock(&priv->driver_lock); + + lbs_deb_usbd(&cardp->udev->dev, + "Wake up main thread to handle cmd response\n"); +} + +/** + * if_usb_receive - read the packet into the upload buffer, + * wake up the main thread and initialise the Rx callack + * + * @urb: pointer to &struct urb + * returns: N/A + */ +static void if_usb_receive(struct urb *urb) +{ + struct if_usb_card *cardp = urb->context; + struct sk_buff *skb = cardp->rx_skb; + struct lbs_private *priv = cardp->priv; + int recvlength = urb->actual_length; + uint8_t *recvbuff = NULL; + uint32_t recvtype = 0; + __le32 *pkt = (__le32 *)(skb->data + IPFIELD_ALIGN_OFFSET); + uint32_t event; + + lbs_deb_enter(LBS_DEB_USB); + + if (recvlength) { + if (urb->status) { + lbs_deb_usbd(&cardp->udev->dev, "RX URB failed: %d\n", + urb->status); + kfree_skb(skb); + goto setup_for_next; + } + + recvbuff = skb->data + IPFIELD_ALIGN_OFFSET; + recvtype = le32_to_cpu(pkt[0]); + lbs_deb_usbd(&cardp->udev->dev, + "Recv length = 0x%x, Recv type = 0x%X\n", + recvlength, recvtype); + } else if (urb->status) { + kfree_skb(skb); + goto rx_exit; + } + + switch (recvtype) { + case CMD_TYPE_DATA: + process_cmdtypedata(recvlength, skb, cardp, priv); + break; + + case CMD_TYPE_REQUEST: + process_cmdrequest(recvlength, recvbuff, skb, cardp, priv); + break; + + case CMD_TYPE_INDICATION: + /* Event handling */ + event = le32_to_cpu(pkt[1]); + lbs_deb_usbd(&cardp->udev->dev, "**EVENT** 0x%X\n", event); + kfree_skb(skb); + + /* Icky undocumented magic special case */ + if (event & 0xffff0000) { + u32 trycount = (event & 0xffff0000) >> 16; + + lbs_send_tx_feedback(priv, trycount); + } else + lbs_queue_event(priv, event & 0xFF); + break; + + default: + lbs_deb_usbd(&cardp->udev->dev, "Unknown command type 0x%X\n", + recvtype); + kfree_skb(skb); + break; + } + +setup_for_next: + if_usb_submit_rx_urb(cardp); +rx_exit: + lbs_deb_leave(LBS_DEB_USB); +} + +/** + * if_usb_host_to_card - downloads data to FW + * @priv: pointer to &struct lbs_private structure + * @type: type of data + * @payload: pointer to data buffer + * @nb: number of bytes + * returns: 0 for success or negative error code + */ +static int if_usb_host_to_card(struct lbs_private *priv, uint8_t type, + uint8_t *payload, uint16_t nb) +{ + struct if_usb_card *cardp = priv->card; + + lbs_deb_usbd(&cardp->udev->dev,"*** type = %u\n", type); + lbs_deb_usbd(&cardp->udev->dev,"size after = %d\n", nb); + + if (type == MVMS_CMD) { + *(__le32 *)cardp->ep_out_buf = cpu_to_le32(CMD_TYPE_REQUEST); + priv->dnld_sent = DNLD_CMD_SENT; + } else { + *(__le32 *)cardp->ep_out_buf = cpu_to_le32(CMD_TYPE_DATA); + priv->dnld_sent = DNLD_DATA_SENT; + } + + memcpy((cardp->ep_out_buf + MESSAGE_HEADER_LEN), payload, nb); + + return usb_tx_block(cardp, cardp->ep_out_buf, nb + MESSAGE_HEADER_LEN); +} + +/** + * if_usb_issue_boot_command - issues Boot command to the Boot2 code + * @cardp: pointer to &if_usb_card + * @ivalue: 1:Boot from FW by USB-Download + * 2:Boot from FW in EEPROM + * returns: 0 for success or negative error code + */ +static int if_usb_issue_boot_command(struct if_usb_card *cardp, int ivalue) +{ + struct bootcmd *bootcmd = cardp->ep_out_buf; + + /* Prepare command */ + bootcmd->magic = cpu_to_le32(BOOT_CMD_MAGIC_NUMBER); + bootcmd->cmd = ivalue; + memset(bootcmd->pad, 0, sizeof(bootcmd->pad)); + + /* Issue command */ + usb_tx_block(cardp, cardp->ep_out_buf, sizeof(*bootcmd)); + + return 0; +} + + +/** + * check_fwfile_format - check the validity of Boot2/FW image + * + * @data: pointer to image + * @totlen: image length + * returns: 0 (good) or 1 (failure) + */ +static int check_fwfile_format(const uint8_t *data, uint32_t totlen) +{ + uint32_t bincmd, exit; + uint32_t blksize, offset, len; + int ret; + + ret = 1; + exit = len = 0; + + do { + struct fwheader *fwh = (void *)data; + + bincmd = le32_to_cpu(fwh->dnldcmd); + blksize = le32_to_cpu(fwh->datalength); + switch (bincmd) { + case FW_HAS_DATA_TO_RECV: + offset = sizeof(struct fwheader) + blksize; + data += offset; + len += offset; + if (len >= totlen) + exit = 1; + break; + case FW_HAS_LAST_BLOCK: + exit = 1; + ret = 0; + break; + default: + exit = 1; + break; + } + } while (!exit); + + if (ret) + pr_err("firmware file format check FAIL\n"); + else + lbs_deb_fw("firmware file format check PASS\n"); + + return ret; +} + +static void if_usb_prog_firmware(struct lbs_private *priv, int ret, + const struct firmware *fw, + const struct firmware *unused) +{ + struct if_usb_card *cardp = priv->card; + int i = 0; + static int reset_count = 10; + + lbs_deb_enter(LBS_DEB_USB); + + if (ret) { + pr_err("failed to find firmware (%d)\n", ret); + goto done; + } + + cardp->fw = fw; + if (check_fwfile_format(cardp->fw->data, cardp->fw->size)) { + ret = -EINVAL; + goto release_fw; + } + + /* Cancel any pending usb business */ + usb_kill_urb(cardp->rx_urb); + usb_kill_urb(cardp->tx_urb); + + cardp->fwlastblksent = 0; + cardp->fwdnldover = 0; + cardp->totalbytes = 0; + cardp->fwfinalblk = 0; + cardp->bootcmdresp = 0; + +restart: + if (if_usb_submit_rx_urb_fwload(cardp) < 0) { + lbs_deb_usbd(&cardp->udev->dev, "URB submission is failed\n"); + ret = -EIO; + goto release_fw; + } + + cardp->bootcmdresp = 0; + do { + int j = 0; + i++; + if_usb_issue_boot_command(cardp, BOOT_CMD_FW_BY_USB); + /* wait for command response */ + do { + j++; + msleep_interruptible(100); + } while (cardp->bootcmdresp == 0 && j < 10); + } while (cardp->bootcmdresp == 0 && i < 5); + + if (cardp->bootcmdresp == BOOT_CMD_RESP_NOT_SUPPORTED) { + /* Return to normal operation */ + ret = -EOPNOTSUPP; + usb_kill_urb(cardp->rx_urb); + usb_kill_urb(cardp->tx_urb); + if (if_usb_submit_rx_urb(cardp) < 0) + ret = -EIO; + goto release_fw; + } else if (cardp->bootcmdresp <= 0) { + if (--reset_count >= 0) { + if_usb_reset_device(cardp); + goto restart; + } + ret = -EIO; + goto release_fw; + } + + i = 0; + + cardp->totalbytes = 0; + cardp->fwlastblksent = 0; + cardp->CRC_OK = 1; + cardp->fwdnldover = 0; + cardp->fwseqnum = -1; + cardp->totalbytes = 0; + cardp->fwfinalblk = 0; + + /* Send the first firmware packet... */ + if_usb_send_fw_pkt(cardp); + + /* ... and wait for the process to complete */ + wait_event_interruptible(cardp->fw_wq, cardp->surprise_removed || cardp->fwdnldover); + + del_timer_sync(&cardp->fw_timeout); + usb_kill_urb(cardp->rx_urb); + + if (!cardp->fwdnldover) { + pr_info("failed to load fw, resetting device!\n"); + if (--reset_count >= 0) { + if_usb_reset_device(cardp); + goto restart; + } + + pr_info("FW download failure, time = %d ms\n", i * 100); + ret = -EIO; + goto release_fw; + } + + cardp->priv->fw_ready = 1; + if_usb_submit_rx_urb(cardp); + + if (lbs_start_card(priv)) + goto release_fw; + + if_usb_setup_firmware(priv); + + /* + * EHS_REMOVE_WAKEUP is not supported on all versions of the firmware. + */ + priv->wol_criteria = EHS_REMOVE_WAKEUP; + if (lbs_host_sleep_cfg(priv, priv->wol_criteria, NULL)) + priv->ehs_remove_supported = false; + + release_fw: + release_firmware(cardp->fw); + cardp->fw = NULL; + + done: + lbs_deb_leave(LBS_DEB_USB); +} + + +#ifdef CONFIG_PM +static int if_usb_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct if_usb_card *cardp = usb_get_intfdata(intf); + struct lbs_private *priv = cardp->priv; + int ret; + + lbs_deb_enter(LBS_DEB_USB); + + if (priv->psstate != PS_STATE_FULL_POWER) { + ret = -1; + goto out; + } + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,1,0)) +#ifdef CONFIG_OLPC + if (machine_is_olpc()) { + if (priv->wol_criteria == EHS_REMOVE_WAKEUP) + olpc_ec_wakeup_clear(EC_SCI_SRC_WLAN); + else + olpc_ec_wakeup_set(EC_SCI_SRC_WLAN); + } +#endif +#endif + + ret = lbs_suspend(priv); + if (ret) + goto out; + + /* Unlink tx & rx urb */ + usb_kill_urb(cardp->tx_urb); + usb_kill_urb(cardp->rx_urb); + + out: + lbs_deb_leave(LBS_DEB_USB); + return ret; +} + +static int if_usb_resume(struct usb_interface *intf) +{ + struct if_usb_card *cardp = usb_get_intfdata(intf); + struct lbs_private *priv = cardp->priv; + + lbs_deb_enter(LBS_DEB_USB); + + if_usb_submit_rx_urb(cardp); + + lbs_resume(priv); + + lbs_deb_leave(LBS_DEB_USB); + return 0; +} +#else +#define if_usb_suspend NULL +#define if_usb_resume NULL +#endif + +static struct usb_driver if_usb_driver = { + .name = DRV_NAME, + .probe = if_usb_probe, + .disconnect = if_usb_disconnect, + .id_table = if_usb_table, + .suspend = if_usb_suspend, + .resume = if_usb_resume, + .reset_resume = if_usb_resume, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +module_usb_driver(if_usb_driver); + +MODULE_DESCRIPTION("8388 USB WLAN Driver"); +MODULE_AUTHOR("Marvell International Ltd. and Red Hat, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/libertas/if_usb.h b/drivers/net/wireless/libertas/if_usb.h new file mode 100644 index 0000000..6e42eac --- /dev/null +++ b/drivers/net/wireless/libertas/if_usb.h @@ -0,0 +1,106 @@ +#ifndef _LBS_IF_USB_H +#define _LBS_IF_USB_H + +#include +#include + +struct lbs_private; + +/* + * This file contains definition for USB interface. + */ +#define CMD_TYPE_REQUEST 0xF00DFACE +#define CMD_TYPE_DATA 0xBEADC0DE +#define CMD_TYPE_INDICATION 0xBEEFFACE + +#define IPFIELD_ALIGN_OFFSET 2 + +#define BOOT_CMD_FW_BY_USB 0x01 +#define BOOT_CMD_FW_IN_EEPROM 0x02 +#define BOOT_CMD_UPDATE_BOOT2 0x03 +#define BOOT_CMD_UPDATE_FW 0x04 +#define BOOT_CMD_MAGIC_NUMBER 0x4C56524D /* LVRM */ + +struct bootcmd +{ + __le32 magic; + uint8_t cmd; + uint8_t pad[11]; +}; + +#define BOOT_CMD_RESP_OK 0x0001 +#define BOOT_CMD_RESP_FAIL 0x0000 +#define BOOT_CMD_RESP_NOT_SUPPORTED 0x0002 + +struct bootcmdresp +{ + __le32 magic; + uint8_t cmd; + uint8_t result; + uint8_t pad[2]; +}; + +/* USB card description structure*/ +struct if_usb_card { + struct usb_device *udev; + uint32_t model; /* MODEL_* */ + struct urb *rx_urb, *tx_urb; + struct lbs_private *priv; + + struct sk_buff *rx_skb; + + uint8_t ep_in; + uint8_t ep_out; + + /* bootcmdresp == 0 means command is pending + * bootcmdresp < 0 means error + * bootcmdresp > 0 is a BOOT_CMD_RESP_* from firmware + */ + int8_t bootcmdresp; + + int ep_in_size; + + void *ep_out_buf; + int ep_out_size; + + const struct firmware *fw; + struct timer_list fw_timeout; + wait_queue_head_t fw_wq; + uint32_t fwseqnum; + uint32_t totalbytes; + uint32_t fwlastblksent; + uint8_t CRC_OK; + uint8_t fwdnldover; + uint8_t fwfinalblk; + uint8_t surprise_removed; + + __le16 boot2_version; +}; + +/* fwheader */ +struct fwheader { + __le32 dnldcmd; + __le32 baseaddr; + __le32 datalength; + __le32 CRC; +}; + +#define FW_MAX_DATA_BLK_SIZE 600 +/* FWData */ +struct fwdata { + struct fwheader hdr; + __le32 seqnum; + uint8_t data[0]; +}; + +/* fwsyncheader */ +struct fwsyncheader { + __le32 cmd; + __le32 seqnum; +}; + +#define FW_HAS_DATA_TO_RECV 0x00000001 +#define FW_HAS_LAST_BLOCK 0x00000004 + + +#endif diff --git a/drivers/net/wireless/libertas/main.c b/drivers/net/wireless/libertas/main.c new file mode 100644 index 0000000..e3e95a0 --- /dev/null +++ b/drivers/net/wireless/libertas/main.c @@ -0,0 +1,1245 @@ +/* + * This file contains the major functions in WLAN + * driver. It includes init, exit, open, close and main + * thread etc.. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "host.h" +#include "decl.h" +#include "dev.h" +#include "cfg.h" +#include "debugfs.h" +#include "cmd.h" +#include "mesh.h" + +#define DRIVER_RELEASE_VERSION "323.p0" +const char lbs_driver_version[] = "COMM-USB8388-" DRIVER_RELEASE_VERSION +#ifdef DEBUG + "-dbg" +#endif + ""; + + +/* Module parameters */ +unsigned int lbs_debug; +EXPORT_SYMBOL_GPL(lbs_debug); +module_param_named(libertas_debug, lbs_debug, int, 0644); + +unsigned int lbs_disablemesh; +EXPORT_SYMBOL_GPL(lbs_disablemesh); +module_param_named(libertas_disablemesh, lbs_disablemesh, int, 0644); + + +/* + * This global structure is used to send the confirm_sleep command as + * fast as possible down to the firmware. + */ +struct cmd_confirm_sleep confirm_sleep; + + +/* + * the table to keep region code + */ +u16 lbs_region_code_to_index[MRVDRV_MAX_REGION_CODE] = + { 0x10, 0x20, 0x30, 0x31, 0x32, 0x40 }; + +/* + * FW rate table. FW refers to rates by their index in this table, not by the + * rate value itself. Values of 0x00 are + * reserved positions. + */ +static u8 fw_data_rates[MAX_RATES] = + { 0x02, 0x04, 0x0B, 0x16, 0x00, 0x0C, 0x12, + 0x18, 0x24, 0x30, 0x48, 0x60, 0x6C, 0x00 +}; + +/** + * lbs_fw_index_to_data_rate - use index to get the data rate + * + * @idx: The index of data rate + * returns: data rate or 0 + */ +u32 lbs_fw_index_to_data_rate(u8 idx) +{ + if (idx >= sizeof(fw_data_rates)) + idx = 0; + return fw_data_rates[idx]; +} + +/** + * lbs_data_rate_to_fw_index - use rate to get the index + * + * @rate: data rate + * returns: index or 0 + */ +u8 lbs_data_rate_to_fw_index(u32 rate) +{ + u8 i; + + if (!rate) + return 0; + + for (i = 0; i < sizeof(fw_data_rates); i++) { + if (rate == fw_data_rates[i]) + return i; + } + return 0; +} + +int lbs_set_iface_type(struct lbs_private *priv, enum nl80211_iftype type) +{ + int ret = 0; + + switch (type) { + case NL80211_IFTYPE_MONITOR: + ret = lbs_set_monitor_mode(priv, 1); + break; + case NL80211_IFTYPE_STATION: + if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) + ret = lbs_set_monitor_mode(priv, 0); + if (!ret) + ret = lbs_set_snmp_mib(priv, SNMP_MIB_OID_BSS_TYPE, 1); + break; + case NL80211_IFTYPE_ADHOC: + if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) + ret = lbs_set_monitor_mode(priv, 0); + if (!ret) + ret = lbs_set_snmp_mib(priv, SNMP_MIB_OID_BSS_TYPE, 2); + break; + default: + ret = -ENOTSUPP; + } + return ret; +} + +int lbs_start_iface(struct lbs_private *priv) +{ + struct cmd_ds_802_11_mac_address cmd; + int ret; + + if (priv->power_restore) { + ret = priv->power_restore(priv); + if (ret) + return ret; + } + + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_SET); + memcpy(cmd.macadd, priv->current_addr, ETH_ALEN); + + ret = lbs_cmd_with_response(priv, CMD_802_11_MAC_ADDRESS, &cmd); + if (ret) { + lbs_deb_net("set MAC address failed\n"); + goto err; + } + + ret = lbs_set_iface_type(priv, priv->wdev->iftype); + if (ret) { + lbs_deb_net("set iface type failed\n"); + goto err; + } + + ret = lbs_set_11d_domain_info(priv); + if (ret) { + lbs_deb_net("set 11d domain info failed\n"); + goto err; + } + + lbs_update_channel(priv); + + priv->iface_running = true; + return 0; + +err: + if (priv->power_save) + priv->power_save(priv); + return ret; +} + +/** + * lbs_dev_open - open the ethX interface + * + * @dev: A pointer to &net_device structure + * returns: 0 or -EBUSY if monitor mode active + */ +static int lbs_dev_open(struct net_device *dev) +{ + struct lbs_private *priv = dev->ml_priv; + int ret = 0; + + lbs_deb_enter(LBS_DEB_NET); + if (!priv->iface_running) { + ret = lbs_start_iface(priv); + if (ret) + goto out; + } + + spin_lock_irq(&priv->driver_lock); + + netif_carrier_off(dev); + + if (!priv->tx_pending_len) + netif_wake_queue(dev); + + spin_unlock_irq(&priv->driver_lock); + +out: + lbs_deb_leave_args(LBS_DEB_NET, "ret %d", ret); + return ret; +} + +static bool lbs_command_queue_empty(struct lbs_private *priv) +{ + unsigned long flags; + bool ret; + spin_lock_irqsave(&priv->driver_lock, flags); + ret = priv->cur_cmd == NULL && list_empty(&priv->cmdpendingq); + spin_unlock_irqrestore(&priv->driver_lock, flags); + return ret; +} + +int lbs_stop_iface(struct lbs_private *priv) +{ + unsigned long flags; + int ret = 0; + + lbs_deb_enter(LBS_DEB_MAIN); + + spin_lock_irqsave(&priv->driver_lock, flags); + priv->iface_running = false; + kfree_skb(priv->currenttxskb); + priv->currenttxskb = NULL; + priv->tx_pending_len = 0; + spin_unlock_irqrestore(&priv->driver_lock, flags); + + cancel_work_sync(&priv->mcast_work); + del_timer_sync(&priv->tx_lockup_timer); + + /* Disable command processing, and wait for all commands to complete */ + lbs_deb_main("waiting for commands to complete\n"); + wait_event(priv->waitq, lbs_command_queue_empty(priv)); + lbs_deb_main("all commands completed\n"); + + if (priv->power_save) + ret = priv->power_save(priv); + + lbs_deb_leave(LBS_DEB_MAIN); + return ret; +} + +/** + * lbs_eth_stop - close the ethX interface + * + * @dev: A pointer to &net_device structure + * returns: 0 + */ +static int lbs_eth_stop(struct net_device *dev) +{ + struct lbs_private *priv = dev->ml_priv; + + lbs_deb_enter(LBS_DEB_NET); + + if (priv->connect_status == LBS_CONNECTED) + lbs_disconnect(priv, WLAN_REASON_DEAUTH_LEAVING); + + spin_lock_irq(&priv->driver_lock); + netif_stop_queue(dev); + spin_unlock_irq(&priv->driver_lock); + + lbs_update_mcast(priv); + cancel_delayed_work_sync(&priv->scan_work); + if (priv->scan_req) + lbs_scan_done(priv); + + netif_carrier_off(priv->dev); + + if (!lbs_iface_active(priv)) + lbs_stop_iface(priv); + + lbs_deb_leave(LBS_DEB_NET); + return 0; +} + +void lbs_host_to_card_done(struct lbs_private *priv) +{ + unsigned long flags; + + lbs_deb_enter(LBS_DEB_THREAD); + + spin_lock_irqsave(&priv->driver_lock, flags); + del_timer(&priv->tx_lockup_timer); + + priv->dnld_sent = DNLD_RES_RECEIVED; + + /* Wake main thread if commands are pending */ + if (!priv->cur_cmd || priv->tx_pending_len > 0) { + if (!priv->wakeup_dev_required) + wake_up(&priv->waitq); + } + + spin_unlock_irqrestore(&priv->driver_lock, flags); + lbs_deb_leave(LBS_DEB_THREAD); +} +EXPORT_SYMBOL_GPL(lbs_host_to_card_done); + +int lbs_set_mac_address(struct net_device *dev, void *addr) +{ + int ret = 0; + struct lbs_private *priv = dev->ml_priv; + struct sockaddr *phwaddr = addr; + + lbs_deb_enter(LBS_DEB_NET); + + /* + * Can only set MAC address when all interfaces are down, to be written + * to the hardware when one of them is brought up. + */ + if (lbs_iface_active(priv)) + return -EBUSY; + + /* In case it was called from the mesh device */ + dev = priv->dev; + + memcpy(priv->current_addr, phwaddr->sa_data, ETH_ALEN); + memcpy(dev->dev_addr, phwaddr->sa_data, ETH_ALEN); + if (priv->mesh_dev) + memcpy(priv->mesh_dev->dev_addr, phwaddr->sa_data, ETH_ALEN); + + lbs_deb_leave_args(LBS_DEB_NET, "ret %d", ret); + return ret; +} + + +static inline int mac_in_list(unsigned char *list, int list_len, + unsigned char *mac) +{ + while (list_len) { + if (!memcmp(list, mac, ETH_ALEN)) + return 1; + list += ETH_ALEN; + list_len--; + } + return 0; +} + + +static int lbs_add_mcast_addrs(struct cmd_ds_mac_multicast_adr *cmd, + struct net_device *dev, int nr_addrs) +{ + int i = nr_addrs; + struct netdev_hw_addr *ha; + int cnt; + + if ((dev->flags & (IFF_UP|IFF_MULTICAST)) != (IFF_UP|IFF_MULTICAST)) + return nr_addrs; + + netif_addr_lock_bh(dev); + cnt = netdev_mc_count(dev); + netdev_for_each_mc_addr(ha, dev) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + if (mac_in_list(cmd->maclist, nr_addrs, ha->addr)) { +#else + if (mac_in_list(cmd->maclist, nr_addrs, ha->dmi_addr)) { +#endif + lbs_deb_net("mcast address %s:%pM skipped\n", dev->name, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + ha->addr); +#else + ha->dmi_addr); +#endif + cnt--; + continue; + } + + if (i == MRVDRV_MAX_MULTICAST_LIST_SIZE) + break; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + memcpy(&cmd->maclist[6*i], ha->addr, ETH_ALEN); +#else + memcpy(&cmd->maclist[6*i], ha->dmi_addr, ETH_ALEN); +#endif + lbs_deb_net("mcast address %s:%pM added to filter\n", dev->name, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + ha->addr); +#else + ha->dmi_addr); +#endif + i++; + cnt--; + } + netif_addr_unlock_bh(dev); + if (cnt) + return -EOVERFLOW; + + return i; +} + +void lbs_update_mcast(struct lbs_private *priv) +{ + struct cmd_ds_mac_multicast_adr mcast_cmd; + int dev_flags = 0; + int nr_addrs; + int old_mac_control = priv->mac_control; + + lbs_deb_enter(LBS_DEB_NET); + + if (netif_running(priv->dev)) + dev_flags |= priv->dev->flags; + if (priv->mesh_dev && netif_running(priv->mesh_dev)) + dev_flags |= priv->mesh_dev->flags; + + if (dev_flags & IFF_PROMISC) { + priv->mac_control |= CMD_ACT_MAC_PROMISCUOUS_ENABLE; + priv->mac_control &= ~(CMD_ACT_MAC_ALL_MULTICAST_ENABLE | + CMD_ACT_MAC_MULTICAST_ENABLE); + goto out_set_mac_control; + } else if (dev_flags & IFF_ALLMULTI) { + do_allmulti: + priv->mac_control |= CMD_ACT_MAC_ALL_MULTICAST_ENABLE; + priv->mac_control &= ~(CMD_ACT_MAC_PROMISCUOUS_ENABLE | + CMD_ACT_MAC_MULTICAST_ENABLE); + goto out_set_mac_control; + } + + /* Once for priv->dev, again for priv->mesh_dev if it exists */ + nr_addrs = lbs_add_mcast_addrs(&mcast_cmd, priv->dev, 0); + if (nr_addrs >= 0 && priv->mesh_dev) + nr_addrs = lbs_add_mcast_addrs(&mcast_cmd, priv->mesh_dev, nr_addrs); + if (nr_addrs < 0) + goto do_allmulti; + + if (nr_addrs) { + int size = offsetof(struct cmd_ds_mac_multicast_adr, + maclist[6*nr_addrs]); + + mcast_cmd.action = cpu_to_le16(CMD_ACT_SET); + mcast_cmd.hdr.size = cpu_to_le16(size); + mcast_cmd.nr_of_adrs = cpu_to_le16(nr_addrs); + + lbs_cmd_async(priv, CMD_MAC_MULTICAST_ADR, &mcast_cmd.hdr, size); + + priv->mac_control |= CMD_ACT_MAC_MULTICAST_ENABLE; + } else + priv->mac_control &= ~CMD_ACT_MAC_MULTICAST_ENABLE; + + priv->mac_control &= ~(CMD_ACT_MAC_PROMISCUOUS_ENABLE | + CMD_ACT_MAC_ALL_MULTICAST_ENABLE); + out_set_mac_control: + if (priv->mac_control != old_mac_control) + lbs_set_mac_control(priv); + + lbs_deb_leave(LBS_DEB_NET); +} + +static void lbs_set_mcast_worker(struct work_struct *work) +{ + struct lbs_private *priv = container_of(work, struct lbs_private, mcast_work); + lbs_update_mcast(priv); +} + +void lbs_set_multicast_list(struct net_device *dev) +{ + struct lbs_private *priv = dev->ml_priv; + + schedule_work(&priv->mcast_work); +} + +/** + * lbs_thread - handles the major jobs in the LBS driver. + * It handles all events generated by firmware, RX data received + * from firmware and TX data sent from kernel. + * + * @data: A pointer to &lbs_thread structure + * returns: 0 + */ +static int lbs_thread(void *data) +{ + struct net_device *dev = data; + struct lbs_private *priv = dev->ml_priv; + wait_queue_t wait; + + lbs_deb_enter(LBS_DEB_THREAD); + + init_waitqueue_entry(&wait, current); + + for (;;) { + int shouldsleep; + u8 resp_idx; + + lbs_deb_thread("1: currenttxskb %p, dnld_sent %d\n", + priv->currenttxskb, priv->dnld_sent); + + add_wait_queue(&priv->waitq, &wait); + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irq(&priv->driver_lock); + + if (kthread_should_stop()) + shouldsleep = 0; /* Bye */ + else if (priv->surpriseremoved) + shouldsleep = 1; /* We need to wait until we're _told_ to die */ + else if (priv->psstate == PS_STATE_SLEEP) + shouldsleep = 1; /* Sleep mode. Nothing we can do till it wakes */ + else if (priv->cmd_timed_out) + shouldsleep = 0; /* Command timed out. Recover */ + else if (!priv->fw_ready) + shouldsleep = 1; /* Firmware not ready. We're waiting for it */ + else if (priv->dnld_sent) + shouldsleep = 1; /* Something is en route to the device already */ + else if (priv->tx_pending_len > 0) + shouldsleep = 0; /* We've a packet to send */ + else if (priv->resp_len[priv->resp_idx]) + shouldsleep = 0; /* We have a command response */ + else if (priv->cur_cmd) + shouldsleep = 1; /* Can't send a command; one already running */ + else if (!list_empty(&priv->cmdpendingq) && + !(priv->wakeup_dev_required)) + shouldsleep = 0; /* We have a command to send */ + else if (kfifo_len(&priv->event_fifo)) + shouldsleep = 0; /* We have an event to process */ + else + shouldsleep = 1; /* No command */ + + if (shouldsleep) { + lbs_deb_thread("sleeping, connect_status %d, " + "psmode %d, psstate %d\n", + priv->connect_status, + priv->psmode, priv->psstate); + spin_unlock_irq(&priv->driver_lock); + schedule(); + } else + spin_unlock_irq(&priv->driver_lock); + + lbs_deb_thread("2: currenttxskb %p, dnld_send %d\n", + priv->currenttxskb, priv->dnld_sent); + + set_current_state(TASK_RUNNING); + remove_wait_queue(&priv->waitq, &wait); + + lbs_deb_thread("3: currenttxskb %p, dnld_sent %d\n", + priv->currenttxskb, priv->dnld_sent); + + if (kthread_should_stop()) { + lbs_deb_thread("break from main thread\n"); + break; + } + + if (priv->surpriseremoved) { + lbs_deb_thread("adapter removed; waiting to die...\n"); + continue; + } + + lbs_deb_thread("4: currenttxskb %p, dnld_sent %d\n", + priv->currenttxskb, priv->dnld_sent); + + /* Process any pending command response */ + spin_lock_irq(&priv->driver_lock); + resp_idx = priv->resp_idx; + if (priv->resp_len[resp_idx]) { + spin_unlock_irq(&priv->driver_lock); + lbs_process_command_response(priv, + priv->resp_buf[resp_idx], + priv->resp_len[resp_idx]); + spin_lock_irq(&priv->driver_lock); + priv->resp_len[resp_idx] = 0; + } + spin_unlock_irq(&priv->driver_lock); + + /* Process hardware events, e.g. card removed, link lost */ + spin_lock_irq(&priv->driver_lock); + while (kfifo_len(&priv->event_fifo)) { + u32 event; + + if (kfifo_out(&priv->event_fifo, + (unsigned char *) &event, sizeof(event)) != + sizeof(event)) + break; + spin_unlock_irq(&priv->driver_lock); + lbs_process_event(priv, event); + spin_lock_irq(&priv->driver_lock); + } + spin_unlock_irq(&priv->driver_lock); + + if (priv->wakeup_dev_required) { + lbs_deb_thread("Waking up device...\n"); + /* Wake up device */ + if (priv->exit_deep_sleep(priv)) + lbs_deb_thread("Wakeup device failed\n"); + continue; + } + + /* command timeout stuff */ + if (priv->cmd_timed_out && priv->cur_cmd) { + struct cmd_ctrl_node *cmdnode = priv->cur_cmd; + + netdev_info(dev, "Timeout submitting command 0x%04x\n", + le16_to_cpu(cmdnode->cmdbuf->command)); + lbs_complete_command(priv, cmdnode, -ETIMEDOUT); + + /* Reset card, but only when it isn't in the process + * of being shutdown anyway. */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)) + if (!dev->dismantle && priv->reset_card) +#else + if (priv->reset_card) +#endif + priv->reset_card(priv); + } + priv->cmd_timed_out = 0; + + if (!priv->fw_ready) + continue; + + /* Check if we need to confirm Sleep Request received previously */ + if (priv->psstate == PS_STATE_PRE_SLEEP && + !priv->dnld_sent && !priv->cur_cmd) { + if (priv->connect_status == LBS_CONNECTED) { + lbs_deb_thread("pre-sleep, currenttxskb %p, " + "dnld_sent %d, cur_cmd %p\n", + priv->currenttxskb, priv->dnld_sent, + priv->cur_cmd); + + lbs_ps_confirm_sleep(priv); + } else { + /* workaround for firmware sending + * deauth/linkloss event immediately + * after sleep request; remove this + * after firmware fixes it + */ + priv->psstate = PS_STATE_AWAKE; + netdev_alert(dev, + "ignore PS_SleepConfirm in non-connected state\n"); + } + } + + /* The PS state is changed during processing of Sleep Request + * event above + */ + if ((priv->psstate == PS_STATE_SLEEP) || + (priv->psstate == PS_STATE_PRE_SLEEP)) + continue; + + if (priv->is_deep_sleep) + continue; + + /* Execute the next command */ + if (!priv->dnld_sent && !priv->cur_cmd) + lbs_execute_next_command(priv); + + spin_lock_irq(&priv->driver_lock); + if (!priv->dnld_sent && priv->tx_pending_len > 0) { + int ret = priv->hw_host_to_card(priv, MVMS_DAT, + priv->tx_pending_buf, + priv->tx_pending_len); + if (ret) { + lbs_deb_tx("host_to_card failed %d\n", ret); + priv->dnld_sent = DNLD_RES_RECEIVED; + } else { + mod_timer(&priv->tx_lockup_timer, + jiffies + (HZ * 5)); + } + priv->tx_pending_len = 0; + if (!priv->currenttxskb) { + /* We can wake the queues immediately if we aren't + waiting for TX feedback */ + if (priv->connect_status == LBS_CONNECTED) + netif_wake_queue(priv->dev); + if (priv->mesh_dev && + netif_running(priv->mesh_dev)) + netif_wake_queue(priv->mesh_dev); + } + } + spin_unlock_irq(&priv->driver_lock); + } + + del_timer(&priv->command_timer); + del_timer(&priv->tx_lockup_timer); + del_timer(&priv->auto_deepsleep_timer); + + lbs_deb_leave(LBS_DEB_THREAD); + return 0; +} + +/** + * lbs_setup_firmware - gets the HW spec from the firmware and sets + * some basic parameters + * + * @priv: A pointer to &struct lbs_private structure + * returns: 0 or -1 + */ +static int lbs_setup_firmware(struct lbs_private *priv) +{ + int ret = -1; + s16 curlevel = 0, minlevel = 0, maxlevel = 0; + + lbs_deb_enter(LBS_DEB_FW); + + /* Read MAC address from firmware */ + memset(priv->current_addr, 0xff, ETH_ALEN); + ret = lbs_update_hw_spec(priv); + if (ret) + goto done; + + /* Read power levels if available */ + ret = lbs_get_tx_power(priv, &curlevel, &minlevel, &maxlevel); + if (ret == 0) { + priv->txpower_cur = curlevel; + priv->txpower_min = minlevel; + priv->txpower_max = maxlevel; + } + + /* Send cmd to FW to enable 11D function */ + ret = lbs_set_snmp_mib(priv, SNMP_MIB_OID_11D_ENABLE, 1); + if (ret) + goto done; + + ret = lbs_set_mac_control_sync(priv); +done: + lbs_deb_leave_args(LBS_DEB_FW, "ret %d", ret); + return ret; +} + +int lbs_suspend(struct lbs_private *priv) +{ + int ret; + + lbs_deb_enter(LBS_DEB_FW); + + if (priv->is_deep_sleep) { + ret = lbs_set_deep_sleep(priv, 0); + if (ret) { + netdev_err(priv->dev, + "deep sleep cancellation failed: %d\n", ret); + return ret; + } + priv->deep_sleep_required = 1; + } + + ret = lbs_set_host_sleep(priv, 1); + + netif_device_detach(priv->dev); + if (priv->mesh_dev) + netif_device_detach(priv->mesh_dev); + + lbs_deb_leave_args(LBS_DEB_FW, "ret %d", ret); + return ret; +} +EXPORT_SYMBOL_GPL(lbs_suspend); + +int lbs_resume(struct lbs_private *priv) +{ + int ret; + + lbs_deb_enter(LBS_DEB_FW); + + ret = lbs_set_host_sleep(priv, 0); + + netif_device_attach(priv->dev); + if (priv->mesh_dev) + netif_device_attach(priv->mesh_dev); + + if (priv->deep_sleep_required) { + priv->deep_sleep_required = 0; + ret = lbs_set_deep_sleep(priv, 1); + if (ret) + netdev_err(priv->dev, + "deep sleep activation failed: %d\n", ret); + } + + if (priv->setup_fw_on_resume) + ret = lbs_setup_firmware(priv); + + lbs_deb_leave_args(LBS_DEB_FW, "ret %d", ret); + return ret; +} +EXPORT_SYMBOL_GPL(lbs_resume); + +/** + * lbs_cmd_timeout_handler - handles the timeout of command sending. + * It will re-send the same command again. + * + * @data: &struct lbs_private pointer + */ +static void lbs_cmd_timeout_handler(unsigned long data) +{ + struct lbs_private *priv = (struct lbs_private *)data; + unsigned long flags; + + lbs_deb_enter(LBS_DEB_CMD); + spin_lock_irqsave(&priv->driver_lock, flags); + + if (!priv->cur_cmd) + goto out; + + netdev_info(priv->dev, "command 0x%04x timed out\n", + le16_to_cpu(priv->cur_cmd->cmdbuf->command)); + + priv->cmd_timed_out = 1; + + /* + * If the device didn't even acknowledge the command, reset the state + * so that we don't block all future commands due to this one timeout. + */ + if (priv->dnld_sent == DNLD_CMD_SENT) + priv->dnld_sent = DNLD_RES_RECEIVED; + + wake_up(&priv->waitq); +out: + spin_unlock_irqrestore(&priv->driver_lock, flags); + lbs_deb_leave(LBS_DEB_CMD); +} + +/** + * lbs_tx_lockup_handler - handles the timeout of the passing of TX frames + * to the hardware. This is known to frequently happen with SD8686 when + * waking up after a Wake-on-WLAN-triggered resume. + * + * @data: &struct lbs_private pointer + */ +static void lbs_tx_lockup_handler(unsigned long data) +{ + struct lbs_private *priv = (struct lbs_private *)data; + unsigned long flags; + + lbs_deb_enter(LBS_DEB_TX); + spin_lock_irqsave(&priv->driver_lock, flags); + + netdev_info(priv->dev, "TX lockup detected\n"); + if (priv->reset_card) + priv->reset_card(priv); + + priv->dnld_sent = DNLD_RES_RECEIVED; + wake_up_interruptible(&priv->waitq); + + spin_unlock_irqrestore(&priv->driver_lock, flags); + lbs_deb_leave(LBS_DEB_TX); +} + +/** + * auto_deepsleep_timer_fn - put the device back to deep sleep mode when + * timer expires and no activity (command, event, data etc.) is detected. + * @data: &struct lbs_private pointer + * returns: N/A + */ +static void auto_deepsleep_timer_fn(unsigned long data) +{ + struct lbs_private *priv = (struct lbs_private *)data; + + lbs_deb_enter(LBS_DEB_CMD); + + if (priv->is_activity_detected) { + priv->is_activity_detected = 0; + } else { + if (priv->is_auto_deep_sleep_enabled && + (!priv->wakeup_dev_required) && + (priv->connect_status != LBS_CONNECTED)) { + struct cmd_header cmd; + + lbs_deb_main("Entering auto deep sleep mode...\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.size = cpu_to_le16(sizeof(cmd)); + lbs_cmd_async(priv, CMD_802_11_DEEP_SLEEP, &cmd, + sizeof(cmd)); + } + } + mod_timer(&priv->auto_deepsleep_timer , jiffies + + (priv->auto_deep_sleep_timeout * HZ)/1000); + lbs_deb_leave(LBS_DEB_CMD); +} + +int lbs_enter_auto_deep_sleep(struct lbs_private *priv) +{ + lbs_deb_enter(LBS_DEB_SDIO); + + priv->is_auto_deep_sleep_enabled = 1; + if (priv->is_deep_sleep) + priv->wakeup_dev_required = 1; + mod_timer(&priv->auto_deepsleep_timer , + jiffies + (priv->auto_deep_sleep_timeout * HZ)/1000); + + lbs_deb_leave(LBS_DEB_SDIO); + return 0; +} + +int lbs_exit_auto_deep_sleep(struct lbs_private *priv) +{ + lbs_deb_enter(LBS_DEB_SDIO); + + priv->is_auto_deep_sleep_enabled = 0; + priv->auto_deep_sleep_timeout = 0; + del_timer(&priv->auto_deepsleep_timer); + + lbs_deb_leave(LBS_DEB_SDIO); + return 0; +} + +static int lbs_init_adapter(struct lbs_private *priv) +{ + int ret; + + lbs_deb_enter(LBS_DEB_MAIN); + + memset(priv->current_addr, 0xff, ETH_ALEN); + + priv->connect_status = LBS_DISCONNECTED; + priv->channel = DEFAULT_AD_HOC_CHANNEL; + priv->mac_control = CMD_ACT_MAC_RX_ON | CMD_ACT_MAC_TX_ON; + priv->radio_on = 1; + priv->psmode = LBS802_11POWERMODECAM; + priv->psstate = PS_STATE_FULL_POWER; + priv->is_deep_sleep = 0; + priv->is_auto_deep_sleep_enabled = 0; + priv->deep_sleep_required = 0; + priv->wakeup_dev_required = 0; + init_waitqueue_head(&priv->ds_awake_q); + init_waitqueue_head(&priv->scan_q); + priv->authtype_auto = 1; + priv->is_host_sleep_configured = 0; + priv->is_host_sleep_activated = 0; + init_waitqueue_head(&priv->host_sleep_q); + init_waitqueue_head(&priv->fw_waitq); + mutex_init(&priv->lock); + + setup_timer(&priv->command_timer, lbs_cmd_timeout_handler, + (unsigned long)priv); + setup_timer(&priv->tx_lockup_timer, lbs_tx_lockup_handler, + (unsigned long)priv); + setup_timer(&priv->auto_deepsleep_timer, auto_deepsleep_timer_fn, + (unsigned long)priv); + + INIT_LIST_HEAD(&priv->cmdfreeq); + INIT_LIST_HEAD(&priv->cmdpendingq); + + spin_lock_init(&priv->driver_lock); + + /* Allocate the command buffers */ + if (lbs_allocate_cmd_buffer(priv)) { + pr_err("Out of memory allocating command buffers\n"); + ret = -ENOMEM; + goto out; + } + priv->resp_idx = 0; + priv->resp_len[0] = priv->resp_len[1] = 0; + + /* Create the event FIFO */ + ret = kfifo_alloc(&priv->event_fifo, sizeof(u32) * 16, GFP_KERNEL); + if (ret) { + pr_err("Out of memory allocating event FIFO buffer\n"); + goto out; + } + +out: + lbs_deb_leave_args(LBS_DEB_MAIN, "ret %d", ret); + + return ret; +} + +static void lbs_free_adapter(struct lbs_private *priv) +{ + lbs_deb_enter(LBS_DEB_MAIN); + + lbs_free_cmd_buffer(priv); + kfifo_free(&priv->event_fifo); + del_timer(&priv->command_timer); + del_timer(&priv->tx_lockup_timer); + del_timer(&priv->auto_deepsleep_timer); + + lbs_deb_leave(LBS_DEB_MAIN); +} + +static const struct net_device_ops lbs_netdev_ops = { + .ndo_open = lbs_dev_open, + .ndo_stop = lbs_eth_stop, + .ndo_start_xmit = lbs_hard_start_xmit, + .ndo_set_mac_address = lbs_set_mac_address, + .ndo_set_rx_mode = lbs_set_multicast_list, + .ndo_change_mtu = eth_change_mtu, + .ndo_validate_addr = eth_validate_addr, +}; + +/** + * lbs_add_card - adds the card. It will probe the + * card, allocate the lbs_priv and initialize the device. + * + * @card: A pointer to card + * @dmdev: A pointer to &struct device + * returns: A pointer to &struct lbs_private structure + */ +struct lbs_private *lbs_add_card(void *card, struct device *dmdev) +{ + struct net_device *dev; + struct wireless_dev *wdev; + struct lbs_private *priv = NULL; + + lbs_deb_enter(LBS_DEB_MAIN); + + /* Allocate an Ethernet device and register it */ + wdev = lbs_cfg_alloc(dmdev); + if (IS_ERR(wdev)) { + pr_err("cfg80211 init failed\n"); + goto done; + } + + wdev->iftype = NL80211_IFTYPE_STATION; + priv = wdev_priv(wdev); + priv->wdev = wdev; + + if (lbs_init_adapter(priv)) { + pr_err("failed to initialize adapter structure\n"); + goto err_wdev; + } + + dev = alloc_netdev(0, "wlan%d", ether_setup); + if (!dev) { + dev_err(dmdev, "no memory for network device instance\n"); + goto err_adapter; + } + + dev->ieee80211_ptr = wdev; + dev->ml_priv = priv; + SET_NETDEV_DEV(dev, dmdev); + wdev->netdev = dev; + priv->dev = dev; + + netdev_attach_ops(dev, &lbs_netdev_ops); + dev->watchdog_timeo = 5 * HZ; + dev->ethtool_ops = &lbs_ethtool_ops; + dev->flags |= IFF_BROADCAST | IFF_MULTICAST; + + priv->card = card; + + strcpy(dev->name, "wlan%d"); + + lbs_deb_thread("Starting main thread...\n"); + init_waitqueue_head(&priv->waitq); + priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main"); + if (IS_ERR(priv->main_thread)) { + lbs_deb_thread("Error creating main thread.\n"); + goto err_ndev; + } + + priv->work_thread = create_singlethread_workqueue("lbs_worker"); + INIT_WORK(&priv->mcast_work, lbs_set_mcast_worker); + + priv->wol_criteria = EHS_REMOVE_WAKEUP; + priv->wol_gpio = 0xff; + priv->wol_gap = 20; + priv->ehs_remove_supported = true; + + goto done; + + err_ndev: + free_netdev(dev); + + err_adapter: + lbs_free_adapter(priv); + + err_wdev: + lbs_cfg_free(priv); + + priv = NULL; + +done: + lbs_deb_leave_args(LBS_DEB_MAIN, "priv %p", priv); + return priv; +} +EXPORT_SYMBOL_GPL(lbs_add_card); + + +void lbs_remove_card(struct lbs_private *priv) +{ + struct net_device *dev = priv->dev; + + lbs_deb_enter(LBS_DEB_MAIN); + + lbs_remove_mesh(priv); + + if (priv->wiphy_registered) + lbs_scan_deinit(priv); + + lbs_wait_for_firmware_load(priv); + + /* worker thread destruction blocks on the in-flight command which + * should have been cleared already in lbs_stop_card(). + */ + lbs_deb_main("destroying worker thread\n"); + destroy_workqueue(priv->work_thread); + lbs_deb_main("done destroying worker thread\n"); + + if (priv->psmode == LBS802_11POWERMODEMAX_PSP) { + priv->psmode = LBS802_11POWERMODECAM; + lbs_set_ps_mode(priv, PS_MODE_ACTION_EXIT_PS, true); + } + + if (priv->is_deep_sleep) { + priv->is_deep_sleep = 0; + wake_up_interruptible(&priv->ds_awake_q); + } + + priv->is_host_sleep_configured = 0; + priv->is_host_sleep_activated = 0; + wake_up_interruptible(&priv->host_sleep_q); + + /* Stop the thread servicing the interrupts */ + priv->surpriseremoved = 1; + kthread_stop(priv->main_thread); + + lbs_free_adapter(priv); + lbs_cfg_free(priv); + free_netdev(dev); + + lbs_deb_leave(LBS_DEB_MAIN); +} +EXPORT_SYMBOL_GPL(lbs_remove_card); + + +int lbs_rtap_supported(struct lbs_private *priv) +{ + if (MRVL_FW_MAJOR_REV(priv->fwrelease) == MRVL_FW_V5) + return 1; + + /* newer firmware use a capability mask */ + return ((MRVL_FW_MAJOR_REV(priv->fwrelease) >= MRVL_FW_V10) && + (priv->fwcapinfo & MESH_CAPINFO_ENABLE_MASK)); +} + + +int lbs_start_card(struct lbs_private *priv) +{ + struct net_device *dev = priv->dev; + int ret = -1; + + lbs_deb_enter(LBS_DEB_MAIN); + + /* poke the firmware */ + ret = lbs_setup_firmware(priv); + if (ret) + goto done; + + if (!lbs_disablemesh) + lbs_init_mesh(priv); + else + pr_info("%s: mesh disabled\n", dev->name); + + if (lbs_cfg_register(priv)) { + pr_err("cannot register device\n"); + goto done; + } + + if (lbs_mesh_activated(priv)) + lbs_start_mesh(priv); + + lbs_debugfs_init_one(priv, dev); + + netdev_info(dev, "Marvell WLAN 802.11 adapter\n"); + + ret = 0; + +done: + lbs_deb_leave_args(LBS_DEB_MAIN, "ret %d", ret); + return ret; +} +EXPORT_SYMBOL_GPL(lbs_start_card); + + +void lbs_stop_card(struct lbs_private *priv) +{ + struct net_device *dev; + + lbs_deb_enter(LBS_DEB_MAIN); + + if (!priv) + goto out; + dev = priv->dev; + + /* If the netdev isn't registered, it means that lbs_start_card() was + * never called so we have nothing to do here. */ + if (dev->reg_state != NETREG_REGISTERED) + goto out; + + netif_stop_queue(dev); + netif_carrier_off(dev); + + lbs_debugfs_remove_one(priv); + lbs_deinit_mesh(priv); + unregister_netdev(dev); + +out: + lbs_deb_leave(LBS_DEB_MAIN); +} +EXPORT_SYMBOL_GPL(lbs_stop_card); + + +void lbs_queue_event(struct lbs_private *priv, u32 event) +{ + unsigned long flags; + + lbs_deb_enter(LBS_DEB_THREAD); + spin_lock_irqsave(&priv->driver_lock, flags); + + if (priv->psstate == PS_STATE_SLEEP) + priv->psstate = PS_STATE_AWAKE; + + kfifo_in(&priv->event_fifo, (unsigned char *) &event, sizeof(u32)); + + wake_up(&priv->waitq); + + spin_unlock_irqrestore(&priv->driver_lock, flags); + lbs_deb_leave(LBS_DEB_THREAD); +} +EXPORT_SYMBOL_GPL(lbs_queue_event); + +void lbs_notify_command_response(struct lbs_private *priv, u8 resp_idx) +{ + lbs_deb_enter(LBS_DEB_THREAD); + + if (priv->psstate == PS_STATE_SLEEP) + priv->psstate = PS_STATE_AWAKE; + + /* Swap buffers by flipping the response index */ + BUG_ON(resp_idx > 1); + priv->resp_idx = resp_idx; + + wake_up(&priv->waitq); + + lbs_deb_leave(LBS_DEB_THREAD); +} +EXPORT_SYMBOL_GPL(lbs_notify_command_response); + +static int __init lbs_init_module(void) +{ + lbs_deb_enter(LBS_DEB_MAIN); + memset(&confirm_sleep, 0, sizeof(confirm_sleep)); + confirm_sleep.hdr.command = cpu_to_le16(CMD_802_11_PS_MODE); + confirm_sleep.hdr.size = cpu_to_le16(sizeof(confirm_sleep)); + confirm_sleep.action = cpu_to_le16(PS_MODE_ACTION_SLEEP_CONFIRMED); + lbs_debugfs_init(); + lbs_deb_leave(LBS_DEB_MAIN); + return 0; +} + +static void __exit lbs_exit_module(void) +{ + lbs_deb_enter(LBS_DEB_MAIN); + lbs_debugfs_remove(); + lbs_deb_leave(LBS_DEB_MAIN); +} + +module_init(lbs_init_module); +module_exit(lbs_exit_module); + +MODULE_DESCRIPTION("Libertas WLAN Driver Library"); +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/libertas/mesh.c b/drivers/net/wireless/libertas/mesh.c new file mode 100644 index 0000000..b5120b5 --- /dev/null +++ b/drivers/net/wireless/libertas/mesh.c @@ -0,0 +1,1188 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mesh.h" +#include "decl.h" +#include "cmd.h" + + +static int lbs_add_mesh(struct lbs_private *priv); + +/*************************************************************************** + * Mesh command handling + */ + +static int lbs_mesh_access(struct lbs_private *priv, uint16_t cmd_action, + struct cmd_ds_mesh_access *cmd) +{ + int ret; + + lbs_deb_enter_args(LBS_DEB_CMD, "action %d", cmd_action); + + cmd->hdr.command = cpu_to_le16(CMD_MESH_ACCESS); + cmd->hdr.size = cpu_to_le16(sizeof(*cmd)); + cmd->hdr.result = 0; + + cmd->action = cpu_to_le16(cmd_action); + + ret = lbs_cmd_with_response(priv, CMD_MESH_ACCESS, cmd); + + lbs_deb_leave(LBS_DEB_CMD); + return ret; +} + +static int __lbs_mesh_config_send(struct lbs_private *priv, + struct cmd_ds_mesh_config *cmd, + uint16_t action, uint16_t type) +{ + int ret; + u16 command = CMD_MESH_CONFIG_OLD; + + lbs_deb_enter(LBS_DEB_CMD); + + /* + * Command id is 0xac for v10 FW along with mesh interface + * id in bits 14-13-12. + */ + if (priv->mesh_tlv == TLV_TYPE_MESH_ID) + command = CMD_MESH_CONFIG | + (MESH_IFACE_ID << MESH_IFACE_BIT_OFFSET); + + cmd->hdr.command = cpu_to_le16(command); + cmd->hdr.size = cpu_to_le16(sizeof(struct cmd_ds_mesh_config)); + cmd->hdr.result = 0; + + cmd->type = cpu_to_le16(type); + cmd->action = cpu_to_le16(action); + + ret = lbs_cmd_with_response(priv, command, cmd); + + lbs_deb_leave(LBS_DEB_CMD); + return ret; +} + +static int lbs_mesh_config_send(struct lbs_private *priv, + struct cmd_ds_mesh_config *cmd, + uint16_t action, uint16_t type) +{ + int ret; + + if (!(priv->fwcapinfo & FW_CAPINFO_PERSISTENT_CONFIG)) + return -EOPNOTSUPP; + + ret = __lbs_mesh_config_send(priv, cmd, action, type); + return ret; +} + +/* This function is the CMD_MESH_CONFIG legacy function. It only handles the + * START and STOP actions. The extended actions supported by CMD_MESH_CONFIG + * are all handled by preparing a struct cmd_ds_mesh_config and passing it to + * lbs_mesh_config_send. + */ +static int lbs_mesh_config(struct lbs_private *priv, uint16_t action, + uint16_t chan) +{ + struct cmd_ds_mesh_config cmd; + struct mrvl_meshie *ie; + DECLARE_SSID_BUF(ssid); + + memset(&cmd, 0, sizeof(cmd)); + cmd.channel = cpu_to_le16(chan); + ie = (struct mrvl_meshie *)cmd.data; + + switch (action) { + case CMD_ACT_MESH_CONFIG_START: + ie->id = WLAN_EID_VENDOR_SPECIFIC; + ie->val.oui[0] = 0x00; + ie->val.oui[1] = 0x50; + ie->val.oui[2] = 0x43; + ie->val.type = MARVELL_MESH_IE_TYPE; + ie->val.subtype = MARVELL_MESH_IE_SUBTYPE; + ie->val.version = MARVELL_MESH_IE_VERSION; + ie->val.active_protocol_id = MARVELL_MESH_PROTO_ID_HWMP; + ie->val.active_metric_id = MARVELL_MESH_METRIC_ID; + ie->val.mesh_capability = MARVELL_MESH_CAPABILITY; + ie->val.mesh_id_len = priv->mesh_ssid_len; + memcpy(ie->val.mesh_id, priv->mesh_ssid, priv->mesh_ssid_len); + ie->len = sizeof(struct mrvl_meshie_val) - + IEEE80211_MAX_SSID_LEN + priv->mesh_ssid_len; + cmd.length = cpu_to_le16(sizeof(struct mrvl_meshie_val)); + break; + case CMD_ACT_MESH_CONFIG_STOP: + break; + default: + return -1; + } + lbs_deb_cmd("mesh config action %d type %x channel %d SSID %s\n", + action, priv->mesh_tlv, chan, + print_ssid(ssid, priv->mesh_ssid, priv->mesh_ssid_len)); + + return __lbs_mesh_config_send(priv, &cmd, action, priv->mesh_tlv); +} + +int lbs_mesh_set_channel(struct lbs_private *priv, u8 channel) +{ + priv->mesh_channel = channel; + return lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, channel); +} + +static uint16_t lbs_mesh_get_channel(struct lbs_private *priv) +{ + return priv->mesh_channel ?: 1; +} + +/*************************************************************************** + * Mesh sysfs support + */ + +/* + * Attributes exported through sysfs + */ + +/** + * lbs_anycast_get - Get function for sysfs attribute anycast_mask + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned + */ +static ssize_t lbs_anycast_get(struct device *dev, + struct device_attribute *attr, char * buf) +{ + struct lbs_private *priv = to_net_dev(dev)->ml_priv; + struct cmd_ds_mesh_access mesh_access; + int ret; + + memset(&mesh_access, 0, sizeof(mesh_access)); + + ret = lbs_mesh_access(priv, CMD_ACT_MESH_GET_ANYCAST, &mesh_access); + if (ret) + return ret; + + return snprintf(buf, 12, "0x%X\n", le32_to_cpu(mesh_access.data[0])); +} + +/** + * lbs_anycast_set - Set function for sysfs attribute anycast_mask + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer + */ +static ssize_t lbs_anycast_set(struct device *dev, + struct device_attribute *attr, const char * buf, size_t count) +{ + struct lbs_private *priv = to_net_dev(dev)->ml_priv; + struct cmd_ds_mesh_access mesh_access; + uint32_t datum; + int ret; + + memset(&mesh_access, 0, sizeof(mesh_access)); + sscanf(buf, "%x", &datum); + mesh_access.data[0] = cpu_to_le32(datum); + + ret = lbs_mesh_access(priv, CMD_ACT_MESH_SET_ANYCAST, &mesh_access); + if (ret) + return ret; + + return strlen(buf); +} + +/** + * lbs_prb_rsp_limit_get - Get function for sysfs attribute prb_rsp_limit + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned + */ +static ssize_t lbs_prb_rsp_limit_get(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lbs_private *priv = to_net_dev(dev)->ml_priv; + struct cmd_ds_mesh_access mesh_access; + int ret; + u32 retry_limit; + + memset(&mesh_access, 0, sizeof(mesh_access)); + mesh_access.data[0] = cpu_to_le32(CMD_ACT_GET); + + ret = lbs_mesh_access(priv, CMD_ACT_MESH_SET_GET_PRB_RSP_LIMIT, + &mesh_access); + if (ret) + return ret; + + retry_limit = le32_to_cpu(mesh_access.data[1]); + return snprintf(buf, 10, "%d\n", retry_limit); +} + +/** + * lbs_prb_rsp_limit_set - Set function for sysfs attribute prb_rsp_limit + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer + */ +static ssize_t lbs_prb_rsp_limit_set(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct lbs_private *priv = to_net_dev(dev)->ml_priv; + struct cmd_ds_mesh_access mesh_access; + int ret; + unsigned long retry_limit; + + memset(&mesh_access, 0, sizeof(mesh_access)); + mesh_access.data[0] = cpu_to_le32(CMD_ACT_SET); + + if (!strict_strtoul(buf, 10, &retry_limit)) + return -ENOTSUPP; + if (retry_limit > 15) + return -ENOTSUPP; + + mesh_access.data[1] = cpu_to_le32(retry_limit); + + ret = lbs_mesh_access(priv, CMD_ACT_MESH_SET_GET_PRB_RSP_LIMIT, + &mesh_access); + if (ret) + return ret; + + return strlen(buf); +} + +/** + * lbs_mesh_get - Get function for sysfs attribute mesh + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned + */ +static ssize_t lbs_mesh_get(struct device *dev, + struct device_attribute *attr, char * buf) +{ + struct lbs_private *priv = to_net_dev(dev)->ml_priv; + return snprintf(buf, 5, "0x%X\n", !!priv->mesh_dev); +} + +/** + * lbs_mesh_set - Set function for sysfs attribute mesh + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer + */ +static ssize_t lbs_mesh_set(struct device *dev, + struct device_attribute *attr, const char * buf, size_t count) +{ + struct lbs_private *priv = to_net_dev(dev)->ml_priv; + int enable; + + sscanf(buf, "%x", &enable); + enable = !!enable; + if (enable == !!priv->mesh_dev) + return count; + + if (enable) + lbs_add_mesh(priv); + else + lbs_remove_mesh(priv); + + return count; +} + +/* + * lbs_mesh attribute to be exported per ethX interface + * through sysfs (/sys/class/net/ethX/lbs_mesh) + */ +static DEVICE_ATTR(lbs_mesh, 0644, lbs_mesh_get, lbs_mesh_set); + +/* + * anycast_mask attribute to be exported per mshX interface + * through sysfs (/sys/class/net/mshX/anycast_mask) + */ +static DEVICE_ATTR(anycast_mask, 0644, lbs_anycast_get, lbs_anycast_set); + +/* + * prb_rsp_limit attribute to be exported per mshX interface + * through sysfs (/sys/class/net/mshX/prb_rsp_limit) + */ +static DEVICE_ATTR(prb_rsp_limit, 0644, lbs_prb_rsp_limit_get, + lbs_prb_rsp_limit_set); + +static struct attribute *lbs_mesh_sysfs_entries[] = { + &dev_attr_anycast_mask.attr, + &dev_attr_prb_rsp_limit.attr, + NULL, +}; + +static const struct attribute_group lbs_mesh_attr_group = { + .attrs = lbs_mesh_sysfs_entries, +}; + + +/*************************************************************************** + * Persistent configuration support + */ + +static int mesh_get_default_parameters(struct device *dev, + struct mrvl_mesh_defaults *defs) +{ + struct lbs_private *priv = to_net_dev(dev)->ml_priv; + struct cmd_ds_mesh_config cmd; + int ret; + + memset(&cmd, 0, sizeof(struct cmd_ds_mesh_config)); + ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_GET, + CMD_TYPE_MESH_GET_DEFAULTS); + + if (ret) + return -EOPNOTSUPP; + + memcpy(defs, &cmd.data[0], sizeof(struct mrvl_mesh_defaults)); + + return 0; +} + +/** + * bootflag_get - Get function for sysfs attribute bootflag + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned + */ +static ssize_t bootflag_get(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mrvl_mesh_defaults defs; + int ret; + + ret = mesh_get_default_parameters(dev, &defs); + + if (ret) + return ret; + + return snprintf(buf, 12, "%d\n", le32_to_cpu(defs.bootflag)); +} + +/** + * bootflag_set - Set function for sysfs attribute bootflag + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer + */ +static ssize_t bootflag_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lbs_private *priv = to_net_dev(dev)->ml_priv; + struct cmd_ds_mesh_config cmd; + uint32_t datum; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + ret = sscanf(buf, "%d", &datum); + if ((ret != 1) || (datum > 1)) + return -EINVAL; + + *((__le32 *)&cmd.data[0]) = cpu_to_le32(!!datum); + cmd.length = cpu_to_le16(sizeof(uint32_t)); + ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET, + CMD_TYPE_MESH_SET_BOOTFLAG); + if (ret) + return ret; + + return strlen(buf); +} + +/** + * boottime_get - Get function for sysfs attribute boottime + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned + */ +static ssize_t boottime_get(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mrvl_mesh_defaults defs; + int ret; + + ret = mesh_get_default_parameters(dev, &defs); + + if (ret) + return ret; + + return snprintf(buf, 12, "%d\n", defs.boottime); +} + +/** + * boottime_set - Set function for sysfs attribute boottime + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer + */ +static ssize_t boottime_set(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct lbs_private *priv = to_net_dev(dev)->ml_priv; + struct cmd_ds_mesh_config cmd; + uint32_t datum; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + ret = sscanf(buf, "%d", &datum); + if ((ret != 1) || (datum > 255)) + return -EINVAL; + + /* A too small boot time will result in the device booting into + * standalone (no-host) mode before the host can take control of it, + * so the change will be hard to revert. This may be a desired + * feature (e.g to configure a very fast boot time for devices that + * will not be attached to a host), but dangerous. So I'm enforcing a + * lower limit of 20 seconds: remove and recompile the driver if this + * does not work for you. + */ + datum = (datum < 20) ? 20 : datum; + cmd.data[0] = datum; + cmd.length = cpu_to_le16(sizeof(uint8_t)); + ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET, + CMD_TYPE_MESH_SET_BOOTTIME); + if (ret) + return ret; + + return strlen(buf); +} + +/** + * channel_get - Get function for sysfs attribute channel + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned + */ +static ssize_t channel_get(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mrvl_mesh_defaults defs; + int ret; + + ret = mesh_get_default_parameters(dev, &defs); + + if (ret) + return ret; + + return snprintf(buf, 12, "%d\n", le16_to_cpu(defs.channel)); +} + +/** + * channel_set - Set function for sysfs attribute channel + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer + */ +static ssize_t channel_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lbs_private *priv = to_net_dev(dev)->ml_priv; + struct cmd_ds_mesh_config cmd; + uint32_t datum; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + ret = sscanf(buf, "%d", &datum); + if (ret != 1 || datum < 1 || datum > 11) + return -EINVAL; + + *((__le16 *)&cmd.data[0]) = cpu_to_le16(datum); + cmd.length = cpu_to_le16(sizeof(uint16_t)); + ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET, + CMD_TYPE_MESH_SET_DEF_CHANNEL); + if (ret) + return ret; + + return strlen(buf); +} + +/** + * mesh_id_get - Get function for sysfs attribute mesh_id + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned + */ +static ssize_t mesh_id_get(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct mrvl_mesh_defaults defs; + int ret; + + ret = mesh_get_default_parameters(dev, &defs); + + if (ret) + return ret; + + if (defs.meshie.val.mesh_id_len > IEEE80211_MAX_SSID_LEN) { + dev_err(dev, "inconsistent mesh ID length\n"); + defs.meshie.val.mesh_id_len = IEEE80211_MAX_SSID_LEN; + } + + memcpy(buf, defs.meshie.val.mesh_id, defs.meshie.val.mesh_id_len); + buf[defs.meshie.val.mesh_id_len] = '\n'; + buf[defs.meshie.val.mesh_id_len + 1] = '\0'; + + return defs.meshie.val.mesh_id_len + 1; +} + +/** + * mesh_id_set - Set function for sysfs attribute mesh_id + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer + */ +static ssize_t mesh_id_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cmd_ds_mesh_config cmd; + struct mrvl_mesh_defaults defs; + struct mrvl_meshie *ie; + struct lbs_private *priv = to_net_dev(dev)->ml_priv; + int len; + int ret; + + if (count < 2 || count > IEEE80211_MAX_SSID_LEN + 1) + return -EINVAL; + + memset(&cmd, 0, sizeof(struct cmd_ds_mesh_config)); + ie = (struct mrvl_meshie *) &cmd.data[0]; + + /* fetch all other Information Element parameters */ + ret = mesh_get_default_parameters(dev, &defs); + + cmd.length = cpu_to_le16(sizeof(struct mrvl_meshie)); + + /* transfer IE elements */ + memcpy(ie, &defs.meshie, sizeof(struct mrvl_meshie)); + + len = count - 1; + memcpy(ie->val.mesh_id, buf, len); + /* SSID len */ + ie->val.mesh_id_len = len; + /* IE len */ + ie->len = sizeof(struct mrvl_meshie_val) - IEEE80211_MAX_SSID_LEN + len; + + ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET, + CMD_TYPE_MESH_SET_MESH_IE); + if (ret) + return ret; + + return strlen(buf); +} + +/** + * protocol_id_get - Get function for sysfs attribute protocol_id + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned + */ +static ssize_t protocol_id_get(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mrvl_mesh_defaults defs; + int ret; + + ret = mesh_get_default_parameters(dev, &defs); + + if (ret) + return ret; + + return snprintf(buf, 5, "%d\n", defs.meshie.val.active_protocol_id); +} + +/** + * protocol_id_set - Set function for sysfs attribute protocol_id + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer + */ +static ssize_t protocol_id_set(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct cmd_ds_mesh_config cmd; + struct mrvl_mesh_defaults defs; + struct mrvl_meshie *ie; + struct lbs_private *priv = to_net_dev(dev)->ml_priv; + uint32_t datum; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + ret = sscanf(buf, "%d", &datum); + if ((ret != 1) || (datum > 255)) + return -EINVAL; + + /* fetch all other Information Element parameters */ + ret = mesh_get_default_parameters(dev, &defs); + + cmd.length = cpu_to_le16(sizeof(struct mrvl_meshie)); + + /* transfer IE elements */ + ie = (struct mrvl_meshie *) &cmd.data[0]; + memcpy(ie, &defs.meshie, sizeof(struct mrvl_meshie)); + /* update protocol id */ + ie->val.active_protocol_id = datum; + + ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET, + CMD_TYPE_MESH_SET_MESH_IE); + if (ret) + return ret; + + return strlen(buf); +} + +/** + * metric_id_get - Get function for sysfs attribute metric_id + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned + */ +static ssize_t metric_id_get(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mrvl_mesh_defaults defs; + int ret; + + ret = mesh_get_default_parameters(dev, &defs); + + if (ret) + return ret; + + return snprintf(buf, 5, "%d\n", defs.meshie.val.active_metric_id); +} + +/** + * metric_id_set - Set function for sysfs attribute metric_id + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer + */ +static ssize_t metric_id_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cmd_ds_mesh_config cmd; + struct mrvl_mesh_defaults defs; + struct mrvl_meshie *ie; + struct lbs_private *priv = to_net_dev(dev)->ml_priv; + uint32_t datum; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + ret = sscanf(buf, "%d", &datum); + if ((ret != 1) || (datum > 255)) + return -EINVAL; + + /* fetch all other Information Element parameters */ + ret = mesh_get_default_parameters(dev, &defs); + + cmd.length = cpu_to_le16(sizeof(struct mrvl_meshie)); + + /* transfer IE elements */ + ie = (struct mrvl_meshie *) &cmd.data[0]; + memcpy(ie, &defs.meshie, sizeof(struct mrvl_meshie)); + /* update metric id */ + ie->val.active_metric_id = datum; + + ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET, + CMD_TYPE_MESH_SET_MESH_IE); + if (ret) + return ret; + + return strlen(buf); +} + +/** + * capability_get - Get function for sysfs attribute capability + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned + */ +static ssize_t capability_get(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mrvl_mesh_defaults defs; + int ret; + + ret = mesh_get_default_parameters(dev, &defs); + + if (ret) + return ret; + + return snprintf(buf, 5, "%d\n", defs.meshie.val.mesh_capability); +} + +/** + * capability_set - Set function for sysfs attribute capability + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer + */ +static ssize_t capability_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cmd_ds_mesh_config cmd; + struct mrvl_mesh_defaults defs; + struct mrvl_meshie *ie; + struct lbs_private *priv = to_net_dev(dev)->ml_priv; + uint32_t datum; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + ret = sscanf(buf, "%d", &datum); + if ((ret != 1) || (datum > 255)) + return -EINVAL; + + /* fetch all other Information Element parameters */ + ret = mesh_get_default_parameters(dev, &defs); + + cmd.length = cpu_to_le16(sizeof(struct mrvl_meshie)); + + /* transfer IE elements */ + ie = (struct mrvl_meshie *) &cmd.data[0]; + memcpy(ie, &defs.meshie, sizeof(struct mrvl_meshie)); + /* update value */ + ie->val.mesh_capability = datum; + + ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET, + CMD_TYPE_MESH_SET_MESH_IE); + if (ret) + return ret; + + return strlen(buf); +} + + +static DEVICE_ATTR(bootflag, 0644, bootflag_get, bootflag_set); +static DEVICE_ATTR(boottime, 0644, boottime_get, boottime_set); +static DEVICE_ATTR(channel, 0644, channel_get, channel_set); +static DEVICE_ATTR(mesh_id, 0644, mesh_id_get, mesh_id_set); +static DEVICE_ATTR(protocol_id, 0644, protocol_id_get, protocol_id_set); +static DEVICE_ATTR(metric_id, 0644, metric_id_get, metric_id_set); +static DEVICE_ATTR(capability, 0644, capability_get, capability_set); + +static struct attribute *boot_opts_attrs[] = { + &dev_attr_bootflag.attr, + &dev_attr_boottime.attr, + &dev_attr_channel.attr, + NULL +}; + +static const struct attribute_group boot_opts_group = { + .name = "boot_options", + .attrs = boot_opts_attrs, +}; + +static struct attribute *mesh_ie_attrs[] = { + &dev_attr_mesh_id.attr, + &dev_attr_protocol_id.attr, + &dev_attr_metric_id.attr, + &dev_attr_capability.attr, + NULL +}; + +static const struct attribute_group mesh_ie_group = { + .name = "mesh_ie", + .attrs = mesh_ie_attrs, +}; + +static void lbs_persist_config_init(struct net_device *dev) +{ + int ret; + ret = sysfs_create_group(&(dev->dev.kobj), &boot_opts_group); + ret = sysfs_create_group(&(dev->dev.kobj), &mesh_ie_group); +} + +static void lbs_persist_config_remove(struct net_device *dev) +{ + sysfs_remove_group(&(dev->dev.kobj), &boot_opts_group); + sysfs_remove_group(&(dev->dev.kobj), &mesh_ie_group); +} + + +/*************************************************************************** + * Initializing and starting, stopping mesh + */ + +/* + * Check mesh FW version and appropriately send the mesh start + * command + */ +int lbs_init_mesh(struct lbs_private *priv) +{ + int ret = 0; + + lbs_deb_enter(LBS_DEB_MESH); + + /* Determine mesh_fw_ver from fwrelease and fwcapinfo */ + /* 5.0.16p0 9.0.0.p0 is known to NOT support any mesh */ + /* 5.110.22 have mesh command with 0xa3 command id */ + /* 10.0.0.p0 FW brings in mesh config command with different id */ + /* Check FW version MSB and initialize mesh_fw_ver */ + if (MRVL_FW_MAJOR_REV(priv->fwrelease) == MRVL_FW_V5) { + /* Enable mesh, if supported, and work out which TLV it uses. + 0x100 + 291 is an unofficial value used in 5.110.20.pXX + 0x100 + 37 is the official value used in 5.110.21.pXX + but we check them in that order because 20.pXX doesn't + give an error -- it just silently fails. */ + + /* 5.110.20.pXX firmware will fail the command if the channel + doesn't match the existing channel. But only if the TLV + is correct. If the channel is wrong, _BOTH_ versions will + give an error to 0x100+291, and allow 0x100+37 to succeed. + It's just that 5.110.20.pXX will not have done anything + useful */ + + priv->mesh_tlv = TLV_TYPE_OLD_MESH_ID; + if (lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, 1)) { + priv->mesh_tlv = TLV_TYPE_MESH_ID; + if (lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, 1)) + priv->mesh_tlv = 0; + } + } else + if ((MRVL_FW_MAJOR_REV(priv->fwrelease) >= MRVL_FW_V10) && + (priv->fwcapinfo & MESH_CAPINFO_ENABLE_MASK)) { + /* 10.0.0.pXX new firmwares should succeed with TLV + * 0x100+37; Do not invoke command with old TLV. + */ + priv->mesh_tlv = TLV_TYPE_MESH_ID; + if (lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, 1)) + priv->mesh_tlv = 0; + } + + /* Stop meshing until interface is brought up */ + lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_STOP, 1); + + if (priv->mesh_tlv) { + sprintf(priv->mesh_ssid, "mesh"); + priv->mesh_ssid_len = 4; + ret = 1; + } + + lbs_deb_leave_args(LBS_DEB_MESH, "ret %d", ret); + return ret; +} + +void lbs_start_mesh(struct lbs_private *priv) +{ + lbs_add_mesh(priv); + + if (device_create_file(&priv->dev->dev, &dev_attr_lbs_mesh)) + netdev_err(priv->dev, "cannot register lbs_mesh attribute\n"); +} + +int lbs_deinit_mesh(struct lbs_private *priv) +{ + struct net_device *dev = priv->dev; + int ret = 0; + + lbs_deb_enter(LBS_DEB_MESH); + + if (priv->mesh_tlv) { + device_remove_file(&dev->dev, &dev_attr_lbs_mesh); + ret = 1; + } + + lbs_deb_leave_args(LBS_DEB_MESH, "ret %d", ret); + return ret; +} + + +/** + * lbs_mesh_stop - close the mshX interface + * + * @dev: A pointer to &net_device structure + * returns: 0 + */ +static int lbs_mesh_stop(struct net_device *dev) +{ + struct lbs_private *priv = dev->ml_priv; + + lbs_deb_enter(LBS_DEB_MESH); + lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_STOP, + lbs_mesh_get_channel(priv)); + + spin_lock_irq(&priv->driver_lock); + + netif_stop_queue(dev); + netif_carrier_off(dev); + + spin_unlock_irq(&priv->driver_lock); + + lbs_update_mcast(priv); + if (!lbs_iface_active(priv)) + lbs_stop_iface(priv); + + lbs_deb_leave(LBS_DEB_MESH); + return 0; +} + +/** + * lbs_mesh_dev_open - open the mshX interface + * + * @dev: A pointer to &net_device structure + * returns: 0 or -EBUSY if monitor mode active + */ +static int lbs_mesh_dev_open(struct net_device *dev) +{ + struct lbs_private *priv = dev->ml_priv; + int ret = 0; + + lbs_deb_enter(LBS_DEB_NET); + if (!priv->iface_running) { + ret = lbs_start_iface(priv); + if (ret) + goto out; + } + + spin_lock_irq(&priv->driver_lock); + + if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) { + ret = -EBUSY; + spin_unlock_irq(&priv->driver_lock); + goto out; + } + + netif_carrier_on(dev); + + if (!priv->tx_pending_len) + netif_wake_queue(dev); + + spin_unlock_irq(&priv->driver_lock); + + ret = lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, + lbs_mesh_get_channel(priv)); + +out: + lbs_deb_leave_args(LBS_DEB_NET, "ret %d", ret); + return ret; +} + +static const struct net_device_ops mesh_netdev_ops = { + .ndo_open = lbs_mesh_dev_open, + .ndo_stop = lbs_mesh_stop, + .ndo_start_xmit = lbs_hard_start_xmit, + .ndo_set_mac_address = lbs_set_mac_address, + .ndo_set_rx_mode = lbs_set_multicast_list, +}; + +/** + * lbs_add_mesh - add mshX interface + * + * @priv: A pointer to the &struct lbs_private structure + * returns: 0 if successful, -X otherwise + */ +static int lbs_add_mesh(struct lbs_private *priv) +{ + struct net_device *mesh_dev = NULL; + struct wireless_dev *mesh_wdev; + int ret = 0; + + lbs_deb_enter(LBS_DEB_MESH); + + /* Allocate a virtual mesh device */ + mesh_wdev = kzalloc(sizeof(struct wireless_dev), GFP_KERNEL); + if (!mesh_wdev) { + lbs_deb_mesh("init mshX wireless device failed\n"); + ret = -ENOMEM; + goto done; + } + + mesh_dev = alloc_netdev(0, "msh%d", ether_setup); + if (!mesh_dev) { + lbs_deb_mesh("init mshX device failed\n"); + ret = -ENOMEM; + goto err_free_wdev; + } + + mesh_wdev->iftype = NL80211_IFTYPE_MESH_POINT; + mesh_wdev->wiphy = priv->wdev->wiphy; + mesh_wdev->netdev = mesh_dev; + + mesh_dev->ml_priv = priv; + mesh_dev->ieee80211_ptr = mesh_wdev; + priv->mesh_dev = mesh_dev; + + netdev_attach_ops(mesh_dev, &mesh_netdev_ops); + mesh_dev->ethtool_ops = &lbs_ethtool_ops; + memcpy(mesh_dev->dev_addr, priv->dev->dev_addr, ETH_ALEN); + + SET_NETDEV_DEV(priv->mesh_dev, priv->dev->dev.parent); + + mesh_dev->flags |= IFF_BROADCAST | IFF_MULTICAST; + /* Register virtual mesh interface */ + ret = register_netdev(mesh_dev); + if (ret) { + pr_err("cannot register mshX virtual interface\n"); + goto err_free_netdev; + } + + ret = sysfs_create_group(&(mesh_dev->dev.kobj), &lbs_mesh_attr_group); + if (ret) + goto err_unregister; + + lbs_persist_config_init(mesh_dev); + + /* Everything successful */ + ret = 0; + goto done; + +err_unregister: + unregister_netdev(mesh_dev); + +err_free_netdev: + free_netdev(mesh_dev); + +err_free_wdev: + kfree(mesh_wdev); + +done: + lbs_deb_leave_args(LBS_DEB_MESH, "ret %d", ret); + return ret; +} + +void lbs_remove_mesh(struct lbs_private *priv) +{ + struct net_device *mesh_dev; + + mesh_dev = priv->mesh_dev; + if (!mesh_dev) + return; + + lbs_deb_enter(LBS_DEB_MESH); + netif_stop_queue(mesh_dev); + netif_carrier_off(mesh_dev); + sysfs_remove_group(&(mesh_dev->dev.kobj), &lbs_mesh_attr_group); + lbs_persist_config_remove(mesh_dev); + unregister_netdev(mesh_dev); + priv->mesh_dev = NULL; + kfree(mesh_dev->ieee80211_ptr); + free_netdev(mesh_dev); + lbs_deb_leave(LBS_DEB_MESH); +} + + +/*************************************************************************** + * Sending and receiving + */ +struct net_device *lbs_mesh_set_dev(struct lbs_private *priv, + struct net_device *dev, struct rxpd *rxpd) +{ + if (priv->mesh_dev) { + if (priv->mesh_tlv == TLV_TYPE_OLD_MESH_ID) { + if (rxpd->rx_control & RxPD_MESH_FRAME) + dev = priv->mesh_dev; + } else if (priv->mesh_tlv == TLV_TYPE_MESH_ID) { + if (rxpd->u.bss.bss_num == MESH_IFACE_ID) + dev = priv->mesh_dev; + } + } + return dev; +} + + +void lbs_mesh_set_txpd(struct lbs_private *priv, + struct net_device *dev, struct txpd *txpd) +{ + if (dev == priv->mesh_dev) { + if (priv->mesh_tlv == TLV_TYPE_OLD_MESH_ID) + txpd->tx_control |= cpu_to_le32(TxPD_MESH_FRAME); + else if (priv->mesh_tlv == TLV_TYPE_MESH_ID) + txpd->u.bss.bss_num = MESH_IFACE_ID; + } +} + + +/*************************************************************************** + * Ethtool related + */ + +static const char * const mesh_stat_strings[] = { + "drop_duplicate_bcast", + "drop_ttl_zero", + "drop_no_fwd_route", + "drop_no_buffers", + "fwded_unicast_cnt", + "fwded_bcast_cnt", + "drop_blind_table", + "tx_failed_cnt" +}; + +void lbs_mesh_ethtool_get_stats(struct net_device *dev, + struct ethtool_stats *stats, uint64_t *data) +{ + struct lbs_private *priv = dev->ml_priv; + struct cmd_ds_mesh_access mesh_access; + int ret; + + lbs_deb_enter(LBS_DEB_ETHTOOL); + + /* Get Mesh Statistics */ + ret = lbs_mesh_access(priv, CMD_ACT_MESH_GET_STATS, &mesh_access); + + if (ret) { + memset(data, 0, MESH_STATS_NUM*(sizeof(uint64_t))); + return; + } + + priv->mstats.fwd_drop_rbt = le32_to_cpu(mesh_access.data[0]); + priv->mstats.fwd_drop_ttl = le32_to_cpu(mesh_access.data[1]); + priv->mstats.fwd_drop_noroute = le32_to_cpu(mesh_access.data[2]); + priv->mstats.fwd_drop_nobuf = le32_to_cpu(mesh_access.data[3]); + priv->mstats.fwd_unicast_cnt = le32_to_cpu(mesh_access.data[4]); + priv->mstats.fwd_bcast_cnt = le32_to_cpu(mesh_access.data[5]); + priv->mstats.drop_blind = le32_to_cpu(mesh_access.data[6]); + priv->mstats.tx_failed_cnt = le32_to_cpu(mesh_access.data[7]); + + data[0] = priv->mstats.fwd_drop_rbt; + data[1] = priv->mstats.fwd_drop_ttl; + data[2] = priv->mstats.fwd_drop_noroute; + data[3] = priv->mstats.fwd_drop_nobuf; + data[4] = priv->mstats.fwd_unicast_cnt; + data[5] = priv->mstats.fwd_bcast_cnt; + data[6] = priv->mstats.drop_blind; + data[7] = priv->mstats.tx_failed_cnt; + + lbs_deb_enter(LBS_DEB_ETHTOOL); +} + +int lbs_mesh_ethtool_get_sset_count(struct net_device *dev, int sset) +{ + struct lbs_private *priv = dev->ml_priv; + + if (sset == ETH_SS_STATS && dev == priv->mesh_dev) + return MESH_STATS_NUM; + + return -EOPNOTSUPP; +} + +void lbs_mesh_ethtool_get_strings(struct net_device *dev, + uint32_t stringset, uint8_t *s) +{ + int i; + + lbs_deb_enter(LBS_DEB_ETHTOOL); + + switch (stringset) { + case ETH_SS_STATS: + for (i = 0; i < MESH_STATS_NUM; i++) { + memcpy(s + i * ETH_GSTRING_LEN, + mesh_stat_strings[i], + ETH_GSTRING_LEN); + } + break; + } + lbs_deb_enter(LBS_DEB_ETHTOOL); +} diff --git a/drivers/net/wireless/libertas/mesh.h b/drivers/net/wireless/libertas/mesh.h new file mode 100644 index 0000000..4e4f44f --- /dev/null +++ b/drivers/net/wireless/libertas/mesh.h @@ -0,0 +1,77 @@ +/* + * Contains all definitions needed for the Libertas' MESH implementation. + */ +#ifndef _LBS_MESH_H_ +#define _LBS_MESH_H_ + + +#include +#include + +#include "host.h" +#include "dev.h" + +#ifdef CPTCFG_LIBERTAS_MESH + +struct net_device; + +int lbs_init_mesh(struct lbs_private *priv); +void lbs_start_mesh(struct lbs_private *priv); +int lbs_deinit_mesh(struct lbs_private *priv); + +void lbs_remove_mesh(struct lbs_private *priv); + +static inline bool lbs_mesh_activated(struct lbs_private *priv) +{ + /* Mesh SSID is only programmed after successful init */ + return priv->mesh_ssid_len != 0; +} + +int lbs_mesh_set_channel(struct lbs_private *priv, u8 channel); + +/* Sending / Receiving */ + +struct rxpd; +struct txpd; + +struct net_device *lbs_mesh_set_dev(struct lbs_private *priv, + struct net_device *dev, struct rxpd *rxpd); +void lbs_mesh_set_txpd(struct lbs_private *priv, + struct net_device *dev, struct txpd *txpd); + + +/* Command handling */ + +struct cmd_ds_command; +struct cmd_ds_mesh_access; +struct cmd_ds_mesh_config; + + +/* Ethtool statistics */ + +struct ethtool_stats; + +void lbs_mesh_ethtool_get_stats(struct net_device *dev, + struct ethtool_stats *stats, uint64_t *data); +int lbs_mesh_ethtool_get_sset_count(struct net_device *dev, int sset); +void lbs_mesh_ethtool_get_strings(struct net_device *dev, + uint32_t stringset, uint8_t *s); + + +#else + +#define lbs_init_mesh(priv) +#define lbs_deinit_mesh(priv) +#define lbs_start_mesh(priv) +#define lbs_add_mesh(priv) +#define lbs_remove_mesh(priv) +#define lbs_mesh_set_dev(priv, dev, rxpd) (dev) +#define lbs_mesh_set_txpd(priv, dev, txpd) +#define lbs_mesh_set_channel(priv, channel) (0) +#define lbs_mesh_activated(priv) (false) + +#endif + + + +#endif diff --git a/drivers/net/wireless/libertas/radiotap.h b/drivers/net/wireless/libertas/radiotap.h new file mode 100644 index 0000000..b3c8ea6 --- /dev/null +++ b/drivers/net/wireless/libertas/radiotap.h @@ -0,0 +1,44 @@ +#include + +struct tx_radiotap_hdr { + struct ieee80211_radiotap_header hdr; + u8 rate; + u8 txpower; + u8 rts_retries; + u8 data_retries; +} __packed; + +#define TX_RADIOTAP_PRESENT ( \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_DBM_TX_POWER) | \ + (1 << IEEE80211_RADIOTAP_RTS_RETRIES) | \ + (1 << IEEE80211_RADIOTAP_DATA_RETRIES) | \ + 0) + +#define IEEE80211_FC_VERSION_MASK 0x0003 +#define IEEE80211_FC_TYPE_MASK 0x000c +#define IEEE80211_FC_TYPE_MGT 0x0000 +#define IEEE80211_FC_TYPE_CTL 0x0004 +#define IEEE80211_FC_TYPE_DATA 0x0008 +#define IEEE80211_FC_SUBTYPE_MASK 0x00f0 +#define IEEE80211_FC_TOFROMDS_MASK 0x0300 +#define IEEE80211_FC_TODS_MASK 0x0100 +#define IEEE80211_FC_FROMDS_MASK 0x0200 +#define IEEE80211_FC_NODS 0x0000 +#define IEEE80211_FC_TODS 0x0100 +#define IEEE80211_FC_FROMDS 0x0200 +#define IEEE80211_FC_DSTODS 0x0300 + +struct rx_radiotap_hdr { + struct ieee80211_radiotap_header hdr; + u8 flags; + u8 rate; + u8 antsignal; +} __packed; + +#define RX_RADIOTAP_PRESENT ( \ + (1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_DB_ANTSIGNAL) |\ + 0) + diff --git a/drivers/net/wireless/libertas/rx.c b/drivers/net/wireless/libertas/rx.c new file mode 100644 index 0000000..c7366b0 --- /dev/null +++ b/drivers/net/wireless/libertas/rx.c @@ -0,0 +1,284 @@ +/* + * This file contains the handling of RX in wlan driver. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +#include "defs.h" +#include "host.h" +#include "radiotap.h" +#include "decl.h" +#include "dev.h" +#include "mesh.h" + +struct eth803hdr { + u8 dest_addr[6]; + u8 src_addr[6]; + u16 h803_len; +} __packed; + +struct rfc1042hdr { + u8 llc_dsap; + u8 llc_ssap; + u8 llc_ctrl; + u8 snap_oui[3]; + u16 snap_type; +} __packed; + +struct rxpackethdr { + struct eth803hdr eth803_hdr; + struct rfc1042hdr rfc1042_hdr; +} __packed; + +struct rx80211packethdr { + struct rxpd rx_pd; + void *eth80211_hdr; +} __packed; + +static int process_rxed_802_11_packet(struct lbs_private *priv, + struct sk_buff *skb); + +/** + * lbs_process_rxed_packet - processes received packet and forwards it + * to kernel/upper layer + * + * @priv: A pointer to &struct lbs_private + * @skb: A pointer to skb which includes the received packet + * returns: 0 or -1 + */ +int lbs_process_rxed_packet(struct lbs_private *priv, struct sk_buff *skb) +{ + int ret = 0; + struct net_device *dev = priv->dev; + struct rxpackethdr *p_rx_pkt; + struct rxpd *p_rx_pd; + int hdrchop; + struct ethhdr *p_ethhdr; + static const u8 rfc1042_eth_hdr[] = { + 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00 + }; + + lbs_deb_enter(LBS_DEB_RX); + + BUG_ON(!skb); + + skb->ip_summed = CHECKSUM_NONE; + + if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) + return process_rxed_802_11_packet(priv, skb); + + p_rx_pd = (struct rxpd *) skb->data; + p_rx_pkt = (struct rxpackethdr *) ((u8 *)p_rx_pd + + le32_to_cpu(p_rx_pd->pkt_ptr)); + + dev = lbs_mesh_set_dev(priv, dev, p_rx_pd); + + lbs_deb_hex(LBS_DEB_RX, "RX Data: Before chop rxpd", skb->data, + min_t(unsigned int, skb->len, 100)); + + if (skb->len < (ETH_HLEN + 8 + sizeof(struct rxpd))) { + lbs_deb_rx("rx err: frame received with bad length\n"); + dev->stats.rx_length_errors++; + ret = 0; + dev_kfree_skb(skb); + goto done; + } + + lbs_deb_rx("rx data: skb->len - pkt_ptr = %d-%zd = %zd\n", + skb->len, (size_t)le32_to_cpu(p_rx_pd->pkt_ptr), + skb->len - (size_t)le32_to_cpu(p_rx_pd->pkt_ptr)); + + lbs_deb_hex(LBS_DEB_RX, "RX Data: Dest", p_rx_pkt->eth803_hdr.dest_addr, + sizeof(p_rx_pkt->eth803_hdr.dest_addr)); + lbs_deb_hex(LBS_DEB_RX, "RX Data: Src", p_rx_pkt->eth803_hdr.src_addr, + sizeof(p_rx_pkt->eth803_hdr.src_addr)); + + if (memcmp(&p_rx_pkt->rfc1042_hdr, + rfc1042_eth_hdr, sizeof(rfc1042_eth_hdr)) == 0) { + /* + * Replace the 803 header and rfc1042 header (llc/snap) with an + * EthernetII header, keep the src/dst and snap_type (ethertype) + * + * The firmware only passes up SNAP frames converting + * all RX Data from 802.11 to 802.2/LLC/SNAP frames. + * + * To create the Ethernet II, just move the src, dst address right + * before the snap_type. + */ + p_ethhdr = (struct ethhdr *) + ((u8 *) &p_rx_pkt->eth803_hdr + + sizeof(p_rx_pkt->eth803_hdr) + sizeof(p_rx_pkt->rfc1042_hdr) + - sizeof(p_rx_pkt->eth803_hdr.dest_addr) + - sizeof(p_rx_pkt->eth803_hdr.src_addr) + - sizeof(p_rx_pkt->rfc1042_hdr.snap_type)); + + memcpy(p_ethhdr->h_source, p_rx_pkt->eth803_hdr.src_addr, + sizeof(p_ethhdr->h_source)); + memcpy(p_ethhdr->h_dest, p_rx_pkt->eth803_hdr.dest_addr, + sizeof(p_ethhdr->h_dest)); + + /* Chop off the rxpd + the excess memory from the 802.2/llc/snap header + * that was removed + */ + hdrchop = (u8 *)p_ethhdr - (u8 *)p_rx_pd; + } else { + lbs_deb_hex(LBS_DEB_RX, "RX Data: LLC/SNAP", + (u8 *) &p_rx_pkt->rfc1042_hdr, + sizeof(p_rx_pkt->rfc1042_hdr)); + + /* Chop off the rxpd */ + hdrchop = (u8 *)&p_rx_pkt->eth803_hdr - (u8 *)p_rx_pd; + } + + /* Chop off the leading header bytes so the skb points to the start of + * either the reconstructed EthII frame or the 802.2/llc/snap frame + */ + skb_pull(skb, hdrchop); + + priv->cur_rate = lbs_fw_index_to_data_rate(p_rx_pd->rx_rate); + + lbs_deb_rx("rx data: size of actual packet %d\n", skb->len); + dev->stats.rx_bytes += skb->len; + dev->stats.rx_packets++; + + skb->protocol = eth_type_trans(skb, dev); + if (in_interrupt()) + netif_rx(skb); + else + netif_rx_ni(skb); + + ret = 0; +done: + lbs_deb_leave_args(LBS_DEB_RX, "ret %d", ret); + return ret; +} +EXPORT_SYMBOL_GPL(lbs_process_rxed_packet); + +/** + * convert_mv_rate_to_radiotap - converts Tx/Rx rates from Marvell WLAN format + * (see Table 2 in Section 3.1) to IEEE80211_RADIOTAP_RATE units (500 Kb/s) + * + * @rate: Input rate + * returns: Output Rate (0 if invalid) + */ +static u8 convert_mv_rate_to_radiotap(u8 rate) +{ + switch (rate) { + case 0: /* 1 Mbps */ + return 2; + case 1: /* 2 Mbps */ + return 4; + case 2: /* 5.5 Mbps */ + return 11; + case 3: /* 11 Mbps */ + return 22; + /* case 4: reserved */ + case 5: /* 6 Mbps */ + return 12; + case 6: /* 9 Mbps */ + return 18; + case 7: /* 12 Mbps */ + return 24; + case 8: /* 18 Mbps */ + return 36; + case 9: /* 24 Mbps */ + return 48; + case 10: /* 36 Mbps */ + return 72; + case 11: /* 48 Mbps */ + return 96; + case 12: /* 54 Mbps */ + return 108; + } + pr_alert("Invalid Marvell WLAN rate %i\n", rate); + return 0; +} + +/** + * process_rxed_802_11_packet - processes a received 802.11 packet and forwards + * it to kernel/upper layer + * + * @priv: A pointer to &struct lbs_private + * @skb: A pointer to skb which includes the received packet + * returns: 0 or -1 + */ +static int process_rxed_802_11_packet(struct lbs_private *priv, + struct sk_buff *skb) +{ + int ret = 0; + struct net_device *dev = priv->dev; + struct rx80211packethdr *p_rx_pkt; + struct rxpd *prxpd; + struct rx_radiotap_hdr radiotap_hdr; + struct rx_radiotap_hdr *pradiotap_hdr; + + lbs_deb_enter(LBS_DEB_RX); + + p_rx_pkt = (struct rx80211packethdr *) skb->data; + prxpd = &p_rx_pkt->rx_pd; + + /* lbs_deb_hex(LBS_DEB_RX, "RX Data: Before chop rxpd", skb->data, min(skb->len, 100)); */ + + if (skb->len < (ETH_HLEN + 8 + sizeof(struct rxpd))) { + lbs_deb_rx("rx err: frame received with bad length\n"); + dev->stats.rx_length_errors++; + ret = -EINVAL; + kfree_skb(skb); + goto done; + } + + lbs_deb_rx("rx data: skb->len-sizeof(RxPd) = %d-%zd = %zd\n", + skb->len, sizeof(struct rxpd), skb->len - sizeof(struct rxpd)); + + /* create the exported radio header */ + + /* radiotap header */ + memset(&radiotap_hdr, 0, sizeof(radiotap_hdr)); + /* XXX must check radiotap_hdr.hdr.it_pad for pad */ + radiotap_hdr.hdr.it_len = cpu_to_le16 (sizeof(struct rx_radiotap_hdr)); + radiotap_hdr.hdr.it_present = cpu_to_le32 (RX_RADIOTAP_PRESENT); + radiotap_hdr.rate = convert_mv_rate_to_radiotap(prxpd->rx_rate); + /* XXX must check no carryout */ + radiotap_hdr.antsignal = prxpd->snr + prxpd->nf; + + /* chop the rxpd */ + skb_pull(skb, sizeof(struct rxpd)); + + /* add space for the new radio header */ + if ((skb_headroom(skb) < sizeof(struct rx_radiotap_hdr)) && + pskb_expand_head(skb, sizeof(struct rx_radiotap_hdr), 0, GFP_ATOMIC)) { + netdev_alert(dev, "%s: couldn't pskb_expand_head\n", __func__); + ret = -ENOMEM; + kfree_skb(skb); + goto done; + } + + pradiotap_hdr = (void *)skb_push(skb, sizeof(struct rx_radiotap_hdr)); + memcpy(pradiotap_hdr, &radiotap_hdr, sizeof(struct rx_radiotap_hdr)); + + priv->cur_rate = lbs_fw_index_to_data_rate(prxpd->rx_rate); + + lbs_deb_rx("rx data: size of actual packet %d\n", skb->len); + dev->stats.rx_bytes += skb->len; + dev->stats.rx_packets++; + + skb->protocol = eth_type_trans(skb, priv->dev); + + if (in_interrupt()) + netif_rx(skb); + else + netif_rx_ni(skb); + + ret = 0; + +done: + lbs_deb_leave_args(LBS_DEB_RX, "ret %d", ret); + return ret; +} diff --git a/drivers/net/wireless/libertas/tx.c b/drivers/net/wireless/libertas/tx.c new file mode 100644 index 0000000..c025f9c --- /dev/null +++ b/drivers/net/wireless/libertas/tx.c @@ -0,0 +1,207 @@ +/* + * This file contains the handling of TX in wlan driver. + */ +#include +#include +#include +#include +#include +#include + +#include "host.h" +#include "radiotap.h" +#include "decl.h" +#include "defs.h" +#include "dev.h" +#include "mesh.h" + +/** + * convert_radiotap_rate_to_mv - converts Tx/Rx rates from IEEE80211_RADIOTAP_RATE + * units (500 Kb/s) into Marvell WLAN format (see Table 8 in Section 3.2.1) + * + * @rate: Input rate + * returns: Output Rate (0 if invalid) + */ +static u32 convert_radiotap_rate_to_mv(u8 rate) +{ + switch (rate) { + case 2: /* 1 Mbps */ + return 0 | (1 << 4); + case 4: /* 2 Mbps */ + return 1 | (1 << 4); + case 11: /* 5.5 Mbps */ + return 2 | (1 << 4); + case 22: /* 11 Mbps */ + return 3 | (1 << 4); + case 12: /* 6 Mbps */ + return 4 | (1 << 4); + case 18: /* 9 Mbps */ + return 5 | (1 << 4); + case 24: /* 12 Mbps */ + return 6 | (1 << 4); + case 36: /* 18 Mbps */ + return 7 | (1 << 4); + case 48: /* 24 Mbps */ + return 8 | (1 << 4); + case 72: /* 36 Mbps */ + return 9 | (1 << 4); + case 96: /* 48 Mbps */ + return 10 | (1 << 4); + case 108: /* 54 Mbps */ + return 11 | (1 << 4); + } + return 0; +} + +/** + * lbs_hard_start_xmit - checks the conditions and sends packet to IF + * layer if everything is ok + * + * @skb: A pointer to skb which includes TX packet + * @dev: A pointer to the &struct net_device + * returns: 0 or -1 + */ +netdev_tx_t lbs_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + unsigned long flags; + struct lbs_private *priv = dev->ml_priv; + struct txpd *txpd; + char *p802x_hdr; + uint16_t pkt_len; + netdev_tx_t ret = NETDEV_TX_OK; + + lbs_deb_enter(LBS_DEB_TX); + + /* We need to protect against the queues being restarted before + we get round to stopping them */ + spin_lock_irqsave(&priv->driver_lock, flags); + + if (priv->surpriseremoved) + goto free; + + if (!skb->len || (skb->len > MRVDRV_ETH_TX_PACKET_BUFFER_SIZE)) { + lbs_deb_tx("tx err: skb length %d 0 or > %zd\n", + skb->len, MRVDRV_ETH_TX_PACKET_BUFFER_SIZE); + /* We'll never manage to send this one; drop it and return 'OK' */ + + dev->stats.tx_dropped++; + dev->stats.tx_errors++; + goto free; + } + + + netif_stop_queue(priv->dev); + if (priv->mesh_dev) + netif_stop_queue(priv->mesh_dev); + + if (priv->tx_pending_len) { + /* This can happen if packets come in on the mesh and eth + device simultaneously -- there's no mutual exclusion on + hard_start_xmit() calls between devices. */ + lbs_deb_tx("Packet on %s while busy\n", dev->name); + ret = NETDEV_TX_BUSY; + goto unlock; + } + + priv->tx_pending_len = -1; + spin_unlock_irqrestore(&priv->driver_lock, flags); + + lbs_deb_hex(LBS_DEB_TX, "TX Data", skb->data, min_t(unsigned int, skb->len, 100)); + + txpd = (void *)priv->tx_pending_buf; + memset(txpd, 0, sizeof(struct txpd)); + + p802x_hdr = skb->data; + pkt_len = skb->len; + + if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) { + struct tx_radiotap_hdr *rtap_hdr = (void *)skb->data; + + /* set txpd fields from the radiotap header */ + txpd->tx_control = cpu_to_le32(convert_radiotap_rate_to_mv(rtap_hdr->rate)); + + /* skip the radiotap header */ + p802x_hdr += sizeof(*rtap_hdr); + pkt_len -= sizeof(*rtap_hdr); + + /* copy destination address from 802.11 header */ + memcpy(txpd->tx_dest_addr_high, p802x_hdr + 4, ETH_ALEN); + } else { + /* copy destination address from 802.3 header */ + memcpy(txpd->tx_dest_addr_high, p802x_hdr, ETH_ALEN); + } + + txpd->tx_packet_length = cpu_to_le16(pkt_len); + txpd->tx_packet_location = cpu_to_le32(sizeof(struct txpd)); + + lbs_mesh_set_txpd(priv, dev, txpd); + + lbs_deb_hex(LBS_DEB_TX, "txpd", (u8 *) &txpd, sizeof(struct txpd)); + + lbs_deb_hex(LBS_DEB_TX, "Tx Data", (u8 *) p802x_hdr, le16_to_cpu(txpd->tx_packet_length)); + + memcpy(&txpd[1], p802x_hdr, le16_to_cpu(txpd->tx_packet_length)); + + spin_lock_irqsave(&priv->driver_lock, flags); + priv->tx_pending_len = pkt_len + sizeof(struct txpd); + + lbs_deb_tx("%s lined up packet\n", __func__); + + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; + + if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) { + /* Keep the skb to echo it back once Tx feedback is + received from FW */ + skb_orphan(skb); + + /* Keep the skb around for when we get feedback */ + priv->currenttxskb = skb; + } else { + free: + dev_kfree_skb_any(skb); + } + + unlock: + spin_unlock_irqrestore(&priv->driver_lock, flags); + wake_up(&priv->waitq); + + lbs_deb_leave_args(LBS_DEB_TX, "ret %d", ret); + return ret; +} + +/** + * lbs_send_tx_feedback - sends to the host the last transmitted packet, + * filling the radiotap headers with transmission information. + * + * @priv: A pointer to &struct lbs_private structure + * @try_count: A 32-bit value containing transmission retry status. + * + * returns: void + */ +void lbs_send_tx_feedback(struct lbs_private *priv, u32 try_count) +{ + struct tx_radiotap_hdr *radiotap_hdr; + + if (priv->wdev->iftype != NL80211_IFTYPE_MONITOR || + priv->currenttxskb == NULL) + return; + + radiotap_hdr = (struct tx_radiotap_hdr *)priv->currenttxskb->data; + + radiotap_hdr->data_retries = try_count ? + (1 + priv->txretrycount - try_count) : 0; + + priv->currenttxskb->protocol = eth_type_trans(priv->currenttxskb, + priv->dev); + netif_rx(priv->currenttxskb); + + priv->currenttxskb = NULL; + + if (priv->connect_status == LBS_CONNECTED) + netif_wake_queue(priv->dev); + + if (priv->mesh_dev && netif_running(priv->mesh_dev)) + netif_wake_queue(priv->mesh_dev); +} +EXPORT_SYMBOL_GPL(lbs_send_tx_feedback); diff --git a/drivers/net/wireless/libertas/types.h b/drivers/net/wireless/libertas/types.h new file mode 100644 index 0000000..cf1d9b0 --- /dev/null +++ b/drivers/net/wireless/libertas/types.h @@ -0,0 +1,268 @@ +/* + * This header file contains definition for global types + */ +#ifndef _LBS_TYPES_H_ +#define _LBS_TYPES_H_ + +#include +#include +#include + +struct ieee_ie_header { + u8 id; + u8 len; +} __packed; + +struct ieee_ie_cf_param_set { + struct ieee_ie_header header; + + u8 cfpcnt; + u8 cfpperiod; + __le16 cfpmaxduration; + __le16 cfpdurationremaining; +} __packed; + + +struct ieee_ie_ibss_param_set { + struct ieee_ie_header header; + + __le16 atimwindow; +} __packed; + +union ieee_ss_param_set { + struct ieee_ie_cf_param_set cf; + struct ieee_ie_ibss_param_set ibss; +} __packed; + +struct ieee_ie_fh_param_set { + struct ieee_ie_header header; + + __le16 dwelltime; + u8 hopset; + u8 hoppattern; + u8 hopindex; +} __packed; + +struct ieee_ie_ds_param_set { + struct ieee_ie_header header; + + u8 channel; +} __packed; + +union ieee_phy_param_set { + struct ieee_ie_fh_param_set fh; + struct ieee_ie_ds_param_set ds; +} __packed; + +/* TLV type ID definition */ +#define PROPRIETARY_TLV_BASE_ID 0x0100 + +/* Terminating TLV type */ +#define MRVL_TERMINATE_TLV_ID 0xffff + +#define TLV_TYPE_SSID 0x0000 +#define TLV_TYPE_RATES 0x0001 +#define TLV_TYPE_PHY_FH 0x0002 +#define TLV_TYPE_PHY_DS 0x0003 +#define TLV_TYPE_CF 0x0004 +#define TLV_TYPE_IBSS 0x0006 + +#define TLV_TYPE_DOMAIN 0x0007 + +#define TLV_TYPE_POWER_CAPABILITY 0x0021 + +#define TLV_TYPE_KEY_MATERIAL (PROPRIETARY_TLV_BASE_ID + 0) +#define TLV_TYPE_CHANLIST (PROPRIETARY_TLV_BASE_ID + 1) +#define TLV_TYPE_NUMPROBES (PROPRIETARY_TLV_BASE_ID + 2) +#define TLV_TYPE_RSSI_LOW (PROPRIETARY_TLV_BASE_ID + 4) +#define TLV_TYPE_SNR_LOW (PROPRIETARY_TLV_BASE_ID + 5) +#define TLV_TYPE_FAILCOUNT (PROPRIETARY_TLV_BASE_ID + 6) +#define TLV_TYPE_BCNMISS (PROPRIETARY_TLV_BASE_ID + 7) +#define TLV_TYPE_LED_GPIO (PROPRIETARY_TLV_BASE_ID + 8) +#define TLV_TYPE_LEDBEHAVIOR (PROPRIETARY_TLV_BASE_ID + 9) +#define TLV_TYPE_PASSTHROUGH (PROPRIETARY_TLV_BASE_ID + 10) +#define TLV_TYPE_REASSOCAP (PROPRIETARY_TLV_BASE_ID + 11) +#define TLV_TYPE_POWER_TBL_2_4GHZ (PROPRIETARY_TLV_BASE_ID + 12) +#define TLV_TYPE_POWER_TBL_5GHZ (PROPRIETARY_TLV_BASE_ID + 13) +#define TLV_TYPE_BCASTPROBE (PROPRIETARY_TLV_BASE_ID + 14) +#define TLV_TYPE_NUMSSID_PROBE (PROPRIETARY_TLV_BASE_ID + 15) +#define TLV_TYPE_WMMQSTATUS (PROPRIETARY_TLV_BASE_ID + 16) +#define TLV_TYPE_CRYPTO_DATA (PROPRIETARY_TLV_BASE_ID + 17) +#define TLV_TYPE_WILDCARDSSID (PROPRIETARY_TLV_BASE_ID + 18) +#define TLV_TYPE_TSFTIMESTAMP (PROPRIETARY_TLV_BASE_ID + 19) +#define TLV_TYPE_RSSI_HIGH (PROPRIETARY_TLV_BASE_ID + 22) +#define TLV_TYPE_SNR_HIGH (PROPRIETARY_TLV_BASE_ID + 23) +#define TLV_TYPE_AUTH_TYPE (PROPRIETARY_TLV_BASE_ID + 31) +#define TLV_TYPE_MESH_ID (PROPRIETARY_TLV_BASE_ID + 37) +#define TLV_TYPE_OLD_MESH_ID (PROPRIETARY_TLV_BASE_ID + 291) + +/* TLV related data structures */ +struct mrvl_ie_header { + __le16 type; + __le16 len; +} __packed; + +struct mrvl_ie_data { + struct mrvl_ie_header header; + u8 Data[1]; +} __packed; + +struct mrvl_ie_rates_param_set { + struct mrvl_ie_header header; + u8 rates[1]; +} __packed; + +struct mrvl_ie_ssid_param_set { + struct mrvl_ie_header header; + u8 ssid[1]; +} __packed; + +struct mrvl_ie_wildcard_ssid_param_set { + struct mrvl_ie_header header; + u8 MaxSsidlength; + u8 ssid[1]; +} __packed; + +struct chanscanmode { +#ifdef __BIG_ENDIAN_BITFIELD + u8 reserved_2_7:6; + u8 disablechanfilt:1; + u8 passivescan:1; +#else + u8 passivescan:1; + u8 disablechanfilt:1; + u8 reserved_2_7:6; +#endif +} __packed; + +struct chanscanparamset { + u8 radiotype; + u8 channumber; + struct chanscanmode chanscanmode; + __le16 minscantime; + __le16 maxscantime; +} __packed; + +struct mrvl_ie_chanlist_param_set { + struct mrvl_ie_header header; + struct chanscanparamset chanscanparam[1]; +} __packed; + +struct mrvl_ie_cf_param_set { + struct mrvl_ie_header header; + u8 cfpcnt; + u8 cfpperiod; + __le16 cfpmaxduration; + __le16 cfpdurationremaining; +} __packed; + +struct mrvl_ie_ds_param_set { + struct mrvl_ie_header header; + u8 channel; +} __packed; + +struct mrvl_ie_rsn_param_set { + struct mrvl_ie_header header; + u8 rsnie[1]; +} __packed; + +struct mrvl_ie_tsf_timestamp { + struct mrvl_ie_header header; + __le64 tsftable[1]; +} __packed; + +/* v9 and later firmware only */ +struct mrvl_ie_auth_type { + struct mrvl_ie_header header; + __le16 auth; +} __packed; + +/* Local Power capability */ +struct mrvl_ie_power_capability { + struct mrvl_ie_header header; + s8 minpower; + s8 maxpower; +} __packed; + +/* used in CMD_802_11_SUBSCRIBE_EVENT for SNR, RSSI and Failure */ +struct mrvl_ie_thresholds { + struct mrvl_ie_header header; + u8 value; + u8 freq; +} __packed; + +struct mrvl_ie_beacons_missed { + struct mrvl_ie_header header; + u8 beaconmissed; + u8 reserved; +} __packed; + +struct mrvl_ie_num_probes { + struct mrvl_ie_header header; + __le16 numprobes; +} __packed; + +struct mrvl_ie_bcast_probe { + struct mrvl_ie_header header; + __le16 bcastprobe; +} __packed; + +struct mrvl_ie_num_ssid_probe { + struct mrvl_ie_header header; + __le16 numssidprobe; +} __packed; + +struct led_pin { + u8 led; + u8 pin; +} __packed; + +struct mrvl_ie_ledgpio { + struct mrvl_ie_header header; + struct led_pin ledpin[1]; +} __packed; + +struct led_bhv { + uint8_t firmwarestate; + uint8_t led; + uint8_t ledstate; + uint8_t ledarg; +} __packed; + + +struct mrvl_ie_ledbhv { + struct mrvl_ie_header header; + struct led_bhv ledbhv[1]; +} __packed; + +/* + * Meant to be packed as the value member of a struct ieee80211_info_element. + * Note that the len member of the ieee80211_info_element varies depending on + * the mesh_id_len + */ +struct mrvl_meshie_val { + uint8_t oui[3]; + uint8_t type; + uint8_t subtype; + uint8_t version; + uint8_t active_protocol_id; + uint8_t active_metric_id; + uint8_t mesh_capability; + uint8_t mesh_id_len; + uint8_t mesh_id[IEEE80211_MAX_SSID_LEN]; +} __packed; + +struct mrvl_meshie { + u8 id, len; + struct mrvl_meshie_val val; +} __packed; + +struct mrvl_mesh_defaults { + __le32 bootflag; + uint8_t boottime; + uint8_t reserved; + __le16 channel; + struct mrvl_meshie meshie; +} __packed; + +#endif diff --git a/drivers/net/wireless/libertas_tf/Makefile b/drivers/net/wireless/libertas_tf/Makefile new file mode 100644 index 0000000..b1845d5 --- /dev/null +++ b/drivers/net/wireless/libertas_tf/Makefile @@ -0,0 +1,6 @@ +libertas_tf-objs := main.o cmd.o + +libertas_tf_usb-objs += if_usb.o + +obj-$(CPTCFG_LIBERTAS_THINFIRM) += libertas_tf.o +obj-$(CPTCFG_LIBERTAS_THINFIRM_USB) += libertas_tf_usb.o diff --git a/drivers/net/wireless/libertas_tf/cmd.c b/drivers/net/wireless/libertas_tf/cmd.c new file mode 100644 index 0000000..909ac36 --- /dev/null +++ b/drivers/net/wireless/libertas_tf/cmd.c @@ -0,0 +1,807 @@ +/* + * Copyright (C) 2008, cozybit Inc. + * Copyright (C) 2003-2006, Marvell International Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include + +#include "libertas_tf.h" + +static const struct channel_range channel_ranges[] = { + { LBTF_REGDOMAIN_US, 1, 12 }, + { LBTF_REGDOMAIN_CA, 1, 12 }, + { LBTF_REGDOMAIN_EU, 1, 14 }, + { LBTF_REGDOMAIN_JP, 1, 14 }, + { LBTF_REGDOMAIN_SP, 1, 14 }, + { LBTF_REGDOMAIN_FR, 1, 14 }, +}; + +static u16 lbtf_region_code_to_index[MRVDRV_MAX_REGION_CODE] = +{ + LBTF_REGDOMAIN_US, LBTF_REGDOMAIN_CA, LBTF_REGDOMAIN_EU, + LBTF_REGDOMAIN_SP, LBTF_REGDOMAIN_FR, LBTF_REGDOMAIN_JP, +}; + +static struct cmd_ctrl_node *lbtf_get_cmd_ctrl_node(struct lbtf_private *priv); + + +/** + * lbtf_cmd_copyback - Simple callback that copies response back into command + * + * @priv A pointer to struct lbtf_private structure + * @extra A pointer to the original command structure for which + * 'resp' is a response + * @resp A pointer to the command response + * + * Returns: 0 on success, error on failure + */ +int lbtf_cmd_copyback(struct lbtf_private *priv, unsigned long extra, + struct cmd_header *resp) +{ + struct cmd_header *buf = (void *)extra; + uint16_t copy_len; + + copy_len = min(le16_to_cpu(buf->size), le16_to_cpu(resp->size)); + memcpy(buf, resp, copy_len); + return 0; +} +EXPORT_SYMBOL_GPL(lbtf_cmd_copyback); + +#define CHAN_TO_IDX(chan) ((chan) - 1) + +static void lbtf_geo_init(struct lbtf_private *priv) +{ + const struct channel_range *range = channel_ranges; + u8 ch; + int i; + + for (i = 0; i < ARRAY_SIZE(channel_ranges); i++) + if (channel_ranges[i].regdomain == priv->regioncode) { + range = &channel_ranges[i]; + break; + } + + for (ch = priv->range.start; ch < priv->range.end; ch++) + priv->channels[CHAN_TO_IDX(ch)].flags = 0; +} + +/** + * lbtf_update_hw_spec: Updates the hardware details. + * + * @priv A pointer to struct lbtf_private structure + * + * Returns: 0 on success, error on failure + */ +int lbtf_update_hw_spec(struct lbtf_private *priv) +{ + struct cmd_ds_get_hw_spec cmd; + int ret = -1; + u32 i; + + lbtf_deb_enter(LBTF_DEB_CMD); + + memset(&cmd, 0, sizeof(cmd)); + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + memcpy(cmd.permanentaddr, priv->current_addr, ETH_ALEN); + ret = lbtf_cmd_with_response(priv, CMD_GET_HW_SPEC, &cmd); + if (ret) + goto out; + + priv->fwcapinfo = le32_to_cpu(cmd.fwcapinfo); + + /* The firmware release is in an interesting format: the patch + * level is in the most significant nibble ... so fix that: */ + priv->fwrelease = le32_to_cpu(cmd.fwrelease); + priv->fwrelease = (priv->fwrelease << 8) | + (priv->fwrelease >> 24 & 0xff); + + printk(KERN_INFO "libertastf: %pM, fw %u.%u.%up%u, cap 0x%08x\n", + cmd.permanentaddr, + priv->fwrelease >> 24 & 0xff, + priv->fwrelease >> 16 & 0xff, + priv->fwrelease >> 8 & 0xff, + priv->fwrelease & 0xff, + priv->fwcapinfo); + lbtf_deb_cmd("GET_HW_SPEC: hardware interface 0x%x, hardware spec 0x%04x\n", + cmd.hwifversion, cmd.version); + + /* Clamp region code to 8-bit since FW spec indicates that it should + * only ever be 8-bit, even though the field size is 16-bit. Some + * firmware returns non-zero high 8 bits here. + */ + priv->regioncode = le16_to_cpu(cmd.regioncode) & 0xFF; + + for (i = 0; i < MRVDRV_MAX_REGION_CODE; i++) { + /* use the region code to search for the index */ + if (priv->regioncode == lbtf_region_code_to_index[i]) + break; + } + + /* if it's unidentified region code, use the default (USA) */ + if (i >= MRVDRV_MAX_REGION_CODE) { + priv->regioncode = 0x10; + pr_info("unidentified region code; using the default (USA)\n"); + } + + if (priv->current_addr[0] == 0xff) + memmove(priv->current_addr, cmd.permanentaddr, ETH_ALEN); + + SET_IEEE80211_PERM_ADDR(priv->hw, priv->current_addr); + + lbtf_geo_init(priv); +out: + lbtf_deb_leave(LBTF_DEB_CMD); + return ret; +} + +/** + * lbtf_set_channel: Set the radio channel + * + * @priv A pointer to struct lbtf_private structure + * @channel The desired channel, or 0 to clear a locked channel + * + * Returns: 0 on success, error on failure + */ +int lbtf_set_channel(struct lbtf_private *priv, u8 channel) +{ + int ret = 0; + struct cmd_ds_802_11_rf_channel cmd; + + lbtf_deb_enter(LBTF_DEB_CMD); + + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_OPT_802_11_RF_CHANNEL_SET); + cmd.channel = cpu_to_le16(channel); + + ret = lbtf_cmd_with_response(priv, CMD_802_11_RF_CHANNEL, &cmd); + lbtf_deb_leave_args(LBTF_DEB_CMD, "ret %d", ret); + return ret; +} + +int lbtf_beacon_set(struct lbtf_private *priv, struct sk_buff *beacon) +{ + struct cmd_ds_802_11_beacon_set cmd; + int size; + + lbtf_deb_enter(LBTF_DEB_CMD); + + if (beacon->len > MRVL_MAX_BCN_SIZE) { + lbtf_deb_leave_args(LBTF_DEB_CMD, "ret %d", -1); + return -1; + } + size = sizeof(cmd) - sizeof(cmd.beacon) + beacon->len; + cmd.hdr.size = cpu_to_le16(size); + cmd.len = cpu_to_le16(beacon->len); + memcpy(cmd.beacon, (u8 *) beacon->data, beacon->len); + + lbtf_cmd_async(priv, CMD_802_11_BEACON_SET, &cmd.hdr, size); + + lbtf_deb_leave_args(LBTF_DEB_CMD, "ret %d", 0); + return 0; +} + +int lbtf_beacon_ctrl(struct lbtf_private *priv, bool beacon_enable, + int beacon_int) +{ + struct cmd_ds_802_11_beacon_control cmd; + lbtf_deb_enter(LBTF_DEB_CMD); + + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_SET); + cmd.beacon_enable = cpu_to_le16(beacon_enable); + cmd.beacon_period = cpu_to_le16(beacon_int); + + lbtf_cmd_async(priv, CMD_802_11_BEACON_CTRL, &cmd.hdr, sizeof(cmd)); + + lbtf_deb_leave(LBTF_DEB_CMD); + return 0; +} + +static void lbtf_queue_cmd(struct lbtf_private *priv, + struct cmd_ctrl_node *cmdnode) +{ + unsigned long flags; + lbtf_deb_enter(LBTF_DEB_HOST); + + if (!cmdnode) { + lbtf_deb_host("QUEUE_CMD: cmdnode is NULL\n"); + goto qcmd_done; + } + + if (!cmdnode->cmdbuf->size) { + lbtf_deb_host("DNLD_CMD: cmd size is zero\n"); + goto qcmd_done; + } + + cmdnode->result = 0; + spin_lock_irqsave(&priv->driver_lock, flags); + list_add_tail(&cmdnode->list, &priv->cmdpendingq); + spin_unlock_irqrestore(&priv->driver_lock, flags); + + lbtf_deb_host("QUEUE_CMD: inserted command 0x%04x into cmdpendingq\n", + le16_to_cpu(cmdnode->cmdbuf->command)); + +qcmd_done: + lbtf_deb_leave(LBTF_DEB_HOST); +} + +static void lbtf_submit_command(struct lbtf_private *priv, + struct cmd_ctrl_node *cmdnode) +{ + unsigned long flags; + struct cmd_header *cmd; + uint16_t cmdsize; + uint16_t command; + int timeo = 5 * HZ; + int ret; + + lbtf_deb_enter(LBTF_DEB_HOST); + + cmd = cmdnode->cmdbuf; + + spin_lock_irqsave(&priv->driver_lock, flags); + priv->cur_cmd = cmdnode; + cmdsize = le16_to_cpu(cmd->size); + command = le16_to_cpu(cmd->command); + + lbtf_deb_cmd("DNLD_CMD: command 0x%04x, seq %d, size %d\n", + command, le16_to_cpu(cmd->seqnum), cmdsize); + lbtf_deb_hex(LBTF_DEB_CMD, "DNLD_CMD", (void *) cmdnode->cmdbuf, cmdsize); + + ret = priv->hw_host_to_card(priv, MVMS_CMD, (u8 *) cmd, cmdsize); + spin_unlock_irqrestore(&priv->driver_lock, flags); + + if (ret) { + pr_info("DNLD_CMD: hw_host_to_card failed: %d\n", ret); + /* Let the timer kick in and retry, and potentially reset + the whole thing if the condition persists */ + timeo = HZ; + } + + /* Setup the timer after transmit command */ + mod_timer(&priv->command_timer, jiffies + timeo); + + lbtf_deb_leave(LBTF_DEB_HOST); +} + +/** + * This function inserts command node to cmdfreeq + * after cleans it. Requires priv->driver_lock held. + */ +static void __lbtf_cleanup_and_insert_cmd(struct lbtf_private *priv, + struct cmd_ctrl_node *cmdnode) +{ + lbtf_deb_enter(LBTF_DEB_HOST); + + if (!cmdnode) + goto cl_ins_out; + + cmdnode->callback = NULL; + cmdnode->callback_arg = 0; + + memset(cmdnode->cmdbuf, 0, LBS_CMD_BUFFER_SIZE); + + list_add_tail(&cmdnode->list, &priv->cmdfreeq); + +cl_ins_out: + lbtf_deb_leave(LBTF_DEB_HOST); +} + +static void lbtf_cleanup_and_insert_cmd(struct lbtf_private *priv, + struct cmd_ctrl_node *ptempcmd) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->driver_lock, flags); + __lbtf_cleanup_and_insert_cmd(priv, ptempcmd); + spin_unlock_irqrestore(&priv->driver_lock, flags); +} + +void lbtf_complete_command(struct lbtf_private *priv, struct cmd_ctrl_node *cmd, + int result) +{ + cmd->result = result; + cmd->cmdwaitqwoken = 1; + wake_up_interruptible(&cmd->cmdwait_q); + + if (!cmd->callback) + __lbtf_cleanup_and_insert_cmd(priv, cmd); + priv->cur_cmd = NULL; +} + +int lbtf_cmd_set_mac_multicast_addr(struct lbtf_private *priv) +{ + struct cmd_ds_mac_multicast_addr cmd; + + lbtf_deb_enter(LBTF_DEB_CMD); + + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_SET); + + cmd.nr_of_adrs = cpu_to_le16((u16) priv->nr_of_multicastmacaddr); + + lbtf_deb_cmd("MULTICAST_ADR: setting %d addresses\n", cmd.nr_of_adrs); + + memcpy(cmd.maclist, priv->multicastlist, + priv->nr_of_multicastmacaddr * ETH_ALEN); + + lbtf_cmd_async(priv, CMD_MAC_MULTICAST_ADR, &cmd.hdr, sizeof(cmd)); + + lbtf_deb_leave(LBTF_DEB_CMD); + return 0; +} + +void lbtf_set_mode(struct lbtf_private *priv, enum lbtf_mode mode) +{ + struct cmd_ds_set_mode cmd; + lbtf_deb_enter(LBTF_DEB_WEXT); + + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.mode = cpu_to_le16(mode); + lbtf_deb_wext("Switching to mode: 0x%x\n", mode); + lbtf_cmd_async(priv, CMD_802_11_SET_MODE, &cmd.hdr, sizeof(cmd)); + + lbtf_deb_leave(LBTF_DEB_WEXT); +} + +void lbtf_set_bssid(struct lbtf_private *priv, bool activate, const u8 *bssid) +{ + struct cmd_ds_set_bssid cmd; + lbtf_deb_enter(LBTF_DEB_CMD); + + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.activate = activate ? 1 : 0; + if (activate) + memcpy(cmd.bssid, bssid, ETH_ALEN); + + lbtf_cmd_async(priv, CMD_802_11_SET_BSSID, &cmd.hdr, sizeof(cmd)); + lbtf_deb_leave(LBTF_DEB_CMD); +} + +int lbtf_set_mac_address(struct lbtf_private *priv, uint8_t *mac_addr) +{ + struct cmd_ds_802_11_mac_address cmd; + lbtf_deb_enter(LBTF_DEB_CMD); + + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_SET); + + memcpy(cmd.macadd, mac_addr, ETH_ALEN); + + lbtf_cmd_async(priv, CMD_802_11_MAC_ADDRESS, &cmd.hdr, sizeof(cmd)); + lbtf_deb_leave(LBTF_DEB_CMD); + return 0; +} + +int lbtf_set_radio_control(struct lbtf_private *priv) +{ + int ret = 0; + struct cmd_ds_802_11_radio_control cmd; + + lbtf_deb_enter(LBTF_DEB_CMD); + + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(CMD_ACT_SET); + + switch (priv->preamble) { + case CMD_TYPE_SHORT_PREAMBLE: + cmd.control = cpu_to_le16(SET_SHORT_PREAMBLE); + break; + + case CMD_TYPE_LONG_PREAMBLE: + cmd.control = cpu_to_le16(SET_LONG_PREAMBLE); + break; + + case CMD_TYPE_AUTO_PREAMBLE: + default: + cmd.control = cpu_to_le16(SET_AUTO_PREAMBLE); + break; + } + + if (priv->radioon) + cmd.control |= cpu_to_le16(TURN_ON_RF); + else + cmd.control &= cpu_to_le16(~TURN_ON_RF); + + lbtf_deb_cmd("RADIO_SET: radio %d, preamble %d\n", priv->radioon, + priv->preamble); + + ret = lbtf_cmd_with_response(priv, CMD_802_11_RADIO_CONTROL, &cmd); + + lbtf_deb_leave_args(LBTF_DEB_CMD, "ret %d", ret); + return ret; +} + +void lbtf_set_mac_control(struct lbtf_private *priv) +{ + struct cmd_ds_mac_control cmd; + lbtf_deb_enter(LBTF_DEB_CMD); + + cmd.hdr.size = cpu_to_le16(sizeof(cmd)); + cmd.action = cpu_to_le16(priv->mac_control); + cmd.reserved = 0; + + lbtf_cmd_async(priv, CMD_MAC_CONTROL, + &cmd.hdr, sizeof(cmd)); + + lbtf_deb_leave(LBTF_DEB_CMD); +} + +/** + * lbtf_allocate_cmd_buffer - Allocates cmd buffer, links it to free cmd queue + * + * @priv A pointer to struct lbtf_private structure + * + * Returns: 0 on success. + */ +int lbtf_allocate_cmd_buffer(struct lbtf_private *priv) +{ + int ret = 0; + u32 bufsize; + u32 i; + struct cmd_ctrl_node *cmdarray; + + lbtf_deb_enter(LBTF_DEB_HOST); + + /* Allocate and initialize the command array */ + bufsize = sizeof(struct cmd_ctrl_node) * LBS_NUM_CMD_BUFFERS; + cmdarray = kzalloc(bufsize, GFP_KERNEL); + if (!cmdarray) { + lbtf_deb_host("ALLOC_CMD_BUF: tempcmd_array is NULL\n"); + ret = -1; + goto done; + } + priv->cmd_array = cmdarray; + + /* Allocate and initialize each command buffer in the command array */ + for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) { + cmdarray[i].cmdbuf = kzalloc(LBS_CMD_BUFFER_SIZE, GFP_KERNEL); + if (!cmdarray[i].cmdbuf) { + lbtf_deb_host("ALLOC_CMD_BUF: ptempvirtualaddr is NULL\n"); + ret = -1; + goto done; + } + } + + for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) { + init_waitqueue_head(&cmdarray[i].cmdwait_q); + lbtf_cleanup_and_insert_cmd(priv, &cmdarray[i]); + } + + ret = 0; + +done: + lbtf_deb_leave_args(LBTF_DEB_HOST, "ret %d", ret); + return ret; +} + +/** + * lbtf_free_cmd_buffer - Frees the cmd buffer. + * + * @priv A pointer to struct lbtf_private structure + * + * Returns: 0 + */ +int lbtf_free_cmd_buffer(struct lbtf_private *priv) +{ + struct cmd_ctrl_node *cmdarray; + unsigned int i; + + lbtf_deb_enter(LBTF_DEB_HOST); + + /* need to check if cmd array is allocated or not */ + if (priv->cmd_array == NULL) { + lbtf_deb_host("FREE_CMD_BUF: cmd_array is NULL\n"); + goto done; + } + + cmdarray = priv->cmd_array; + + /* Release shared memory buffers */ + for (i = 0; i < LBS_NUM_CMD_BUFFERS; i++) { + kfree(cmdarray[i].cmdbuf); + cmdarray[i].cmdbuf = NULL; + } + + /* Release cmd_ctrl_node */ + kfree(priv->cmd_array); + priv->cmd_array = NULL; + +done: + lbtf_deb_leave(LBTF_DEB_HOST); + return 0; +} + +/** + * lbtf_get_cmd_ctrl_node - Gets free cmd node from free cmd queue. + * + * @priv A pointer to struct lbtf_private structure + * + * Returns: pointer to a struct cmd_ctrl_node or NULL if none available. + */ +static struct cmd_ctrl_node *lbtf_get_cmd_ctrl_node(struct lbtf_private *priv) +{ + struct cmd_ctrl_node *tempnode; + unsigned long flags; + + lbtf_deb_enter(LBTF_DEB_HOST); + + if (!priv) + return NULL; + + spin_lock_irqsave(&priv->driver_lock, flags); + + if (!list_empty(&priv->cmdfreeq)) { + tempnode = list_first_entry(&priv->cmdfreeq, + struct cmd_ctrl_node, list); + list_del(&tempnode->list); + } else { + lbtf_deb_host("GET_CMD_NODE: cmd_ctrl_node is not available\n"); + tempnode = NULL; + } + + spin_unlock_irqrestore(&priv->driver_lock, flags); + + lbtf_deb_leave(LBTF_DEB_HOST); + return tempnode; +} + +/** + * lbtf_execute_next_command: execute next command in cmd pending queue. + * + * @priv A pointer to struct lbtf_private structure + * + * Returns: 0 on success. + */ +int lbtf_execute_next_command(struct lbtf_private *priv) +{ + struct cmd_ctrl_node *cmdnode = NULL; + struct cmd_header *cmd; + unsigned long flags; + int ret = 0; + + /* Debug group is lbtf_deb_THREAD and not lbtf_deb_HOST, because the + * only caller to us is lbtf_thread() and we get even when a + * data packet is received */ + lbtf_deb_enter(LBTF_DEB_THREAD); + + spin_lock_irqsave(&priv->driver_lock, flags); + + if (priv->cur_cmd) { + pr_alert("EXEC_NEXT_CMD: already processing command!\n"); + spin_unlock_irqrestore(&priv->driver_lock, flags); + ret = -1; + goto done; + } + + if (!list_empty(&priv->cmdpendingq)) { + cmdnode = list_first_entry(&priv->cmdpendingq, + struct cmd_ctrl_node, list); + } + + if (cmdnode) { + cmd = cmdnode->cmdbuf; + + list_del(&cmdnode->list); + lbtf_deb_host("EXEC_NEXT_CMD: sending command 0x%04x\n", + le16_to_cpu(cmd->command)); + spin_unlock_irqrestore(&priv->driver_lock, flags); + lbtf_submit_command(priv, cmdnode); + } else + spin_unlock_irqrestore(&priv->driver_lock, flags); + + ret = 0; +done: + lbtf_deb_leave(LBTF_DEB_THREAD); + return ret; +} + +static struct cmd_ctrl_node *__lbtf_cmd_async(struct lbtf_private *priv, + uint16_t command, struct cmd_header *in_cmd, int in_cmd_size, + int (*callback)(struct lbtf_private *, unsigned long, + struct cmd_header *), + unsigned long callback_arg) +{ + struct cmd_ctrl_node *cmdnode; + + lbtf_deb_enter(LBTF_DEB_HOST); + + if (priv->surpriseremoved) { + lbtf_deb_host("PREP_CMD: card removed\n"); + cmdnode = ERR_PTR(-ENOENT); + goto done; + } + + cmdnode = lbtf_get_cmd_ctrl_node(priv); + if (cmdnode == NULL) { + lbtf_deb_host("PREP_CMD: cmdnode is NULL\n"); + + /* Wake up main thread to execute next command */ + queue_work(lbtf_wq, &priv->cmd_work); + cmdnode = ERR_PTR(-ENOBUFS); + goto done; + } + + cmdnode->callback = callback; + cmdnode->callback_arg = callback_arg; + + /* Copy the incoming command to the buffer */ + memcpy(cmdnode->cmdbuf, in_cmd, in_cmd_size); + + /* Set sequence number, clean result, move to buffer */ + priv->seqnum++; + cmdnode->cmdbuf->command = cpu_to_le16(command); + cmdnode->cmdbuf->size = cpu_to_le16(in_cmd_size); + cmdnode->cmdbuf->seqnum = cpu_to_le16(priv->seqnum); + cmdnode->cmdbuf->result = 0; + + lbtf_deb_host("PREP_CMD: command 0x%04x\n", command); + + cmdnode->cmdwaitqwoken = 0; + lbtf_queue_cmd(priv, cmdnode); + queue_work(lbtf_wq, &priv->cmd_work); + + done: + lbtf_deb_leave_args(LBTF_DEB_HOST, "ret %p", cmdnode); + return cmdnode; +} + +void lbtf_cmd_async(struct lbtf_private *priv, uint16_t command, + struct cmd_header *in_cmd, int in_cmd_size) +{ + lbtf_deb_enter(LBTF_DEB_CMD); + __lbtf_cmd_async(priv, command, in_cmd, in_cmd_size, NULL, 0); + lbtf_deb_leave(LBTF_DEB_CMD); +} + +int __lbtf_cmd(struct lbtf_private *priv, uint16_t command, + struct cmd_header *in_cmd, int in_cmd_size, + int (*callback)(struct lbtf_private *, + unsigned long, struct cmd_header *), + unsigned long callback_arg) +{ + struct cmd_ctrl_node *cmdnode; + unsigned long flags; + int ret = 0; + + lbtf_deb_enter(LBTF_DEB_HOST); + + cmdnode = __lbtf_cmd_async(priv, command, in_cmd, in_cmd_size, + callback, callback_arg); + if (IS_ERR(cmdnode)) { + ret = PTR_ERR(cmdnode); + goto done; + } + + might_sleep(); + ret = wait_event_interruptible(cmdnode->cmdwait_q, + cmdnode->cmdwaitqwoken); + if (ret) { + pr_info("PREP_CMD: command 0x%04x interrupted by signal: %d\n", + command, ret); + goto done; + } + + spin_lock_irqsave(&priv->driver_lock, flags); + ret = cmdnode->result; + if (ret) + pr_info("PREP_CMD: command 0x%04x failed: %d\n", + command, ret); + + __lbtf_cleanup_and_insert_cmd(priv, cmdnode); + spin_unlock_irqrestore(&priv->driver_lock, flags); + +done: + lbtf_deb_leave_args(LBTF_DEB_HOST, "ret %d", ret); + return ret; +} +EXPORT_SYMBOL_GPL(__lbtf_cmd); + +/* Call holding driver_lock */ +void lbtf_cmd_response_rx(struct lbtf_private *priv) +{ + priv->cmd_response_rxed = 1; + queue_work(lbtf_wq, &priv->cmd_work); +} +EXPORT_SYMBOL_GPL(lbtf_cmd_response_rx); + +int lbtf_process_rx_command(struct lbtf_private *priv) +{ + uint16_t respcmd, curcmd; + struct cmd_header *resp; + int ret = 0; + unsigned long flags; + uint16_t result; + + lbtf_deb_enter(LBTF_DEB_CMD); + + mutex_lock(&priv->lock); + spin_lock_irqsave(&priv->driver_lock, flags); + + if (!priv->cur_cmd) { + ret = -1; + spin_unlock_irqrestore(&priv->driver_lock, flags); + goto done; + } + + resp = (void *)priv->cmd_resp_buff; + curcmd = le16_to_cpu(priv->cur_cmd->cmdbuf->command); + respcmd = le16_to_cpu(resp->command); + result = le16_to_cpu(resp->result); + + if (net_ratelimit()) + pr_info("libertastf: cmd response 0x%04x, seq %d, size %d\n", + respcmd, le16_to_cpu(resp->seqnum), + le16_to_cpu(resp->size)); + + if (resp->seqnum != priv->cur_cmd->cmdbuf->seqnum) { + spin_unlock_irqrestore(&priv->driver_lock, flags); + ret = -1; + goto done; + } + if (respcmd != CMD_RET(curcmd)) { + spin_unlock_irqrestore(&priv->driver_lock, flags); + ret = -1; + goto done; + } + + if (resp->result == cpu_to_le16(0x0004)) { + /* 0x0004 means -EAGAIN. Drop the response, let it time out + and be resubmitted */ + spin_unlock_irqrestore(&priv->driver_lock, flags); + ret = -1; + goto done; + } + + /* Now we got response from FW, cancel the command timer */ + del_timer(&priv->command_timer); + priv->cmd_timed_out = 0; + if (priv->nr_retries) + priv->nr_retries = 0; + + /* If the command is not successful, cleanup and return failure */ + if ((result != 0 || !(respcmd & 0x8000))) { + /* + * Handling errors here + */ + switch (respcmd) { + case CMD_RET(CMD_GET_HW_SPEC): + case CMD_RET(CMD_802_11_RESET): + pr_info("libertastf: reset failed\n"); + break; + + } + lbtf_complete_command(priv, priv->cur_cmd, result); + spin_unlock_irqrestore(&priv->driver_lock, flags); + + ret = -1; + goto done; + } + + spin_unlock_irqrestore(&priv->driver_lock, flags); + + if (priv->cur_cmd && priv->cur_cmd->callback) { + ret = priv->cur_cmd->callback(priv, priv->cur_cmd->callback_arg, + resp); + } + spin_lock_irqsave(&priv->driver_lock, flags); + + if (priv->cur_cmd) { + /* Clean up and Put current command back to cmdfreeq */ + lbtf_complete_command(priv, priv->cur_cmd, result); + } + spin_unlock_irqrestore(&priv->driver_lock, flags); + +done: + mutex_unlock(&priv->lock); + lbtf_deb_leave_args(LBTF_DEB_CMD, "ret %d", ret); + return ret; +} diff --git a/drivers/net/wireless/libertas_tf/deb_defs.h b/drivers/net/wireless/libertas_tf/deb_defs.h new file mode 100644 index 0000000..0793c8a --- /dev/null +++ b/drivers/net/wireless/libertas_tf/deb_defs.h @@ -0,0 +1,104 @@ +/** + * This header file contains global constant/enum definitions, + * global variable declaration. + */ +#ifndef _LBS_DEB_DEFS_H_ +#define _LBS_DEB_DEFS_H_ + +#ifndef DRV_NAME +#define DRV_NAME "libertas_tf" +#endif + +#include + +#ifdef CPTCFG_LIBERTAS_THINFIRM_DEBUG +#define DEBUG +#define PROC_DEBUG +#endif + +#define LBTF_DEB_ENTER 0x00000001 +#define LBTF_DEB_LEAVE 0x00000002 +#define LBTF_DEB_MAIN 0x00000004 +#define LBTF_DEB_NET 0x00000008 +#define LBTF_DEB_MESH 0x00000010 +#define LBTF_DEB_WEXT 0x00000020 +#define LBTF_DEB_IOCTL 0x00000040 +#define LBTF_DEB_SCAN 0x00000080 +#define LBTF_DEB_ASSOC 0x00000100 +#define LBTF_DEB_JOIN 0x00000200 +#define LBTF_DEB_11D 0x00000400 +#define LBTF_DEB_DEBUGFS 0x00000800 +#define LBTF_DEB_ETHTOOL 0x00001000 +#define LBTF_DEB_HOST 0x00002000 +#define LBTF_DEB_CMD 0x00004000 +#define LBTF_DEB_RX 0x00008000 +#define LBTF_DEB_TX 0x00010000 +#define LBTF_DEB_USB 0x00020000 +#define LBTF_DEB_CS 0x00040000 +#define LBTF_DEB_FW 0x00080000 +#define LBTF_DEB_THREAD 0x00100000 +#define LBTF_DEB_HEX 0x00200000 +#define LBTF_DEB_SDIO 0x00400000 +#define LBTF_DEB_MACOPS 0x00800000 + +extern unsigned int lbtf_debug; + + +#ifdef DEBUG +#define LBTF_DEB_LL(grp, grpnam, fmt, args...) \ +do { if ((lbtf_debug & (grp)) == (grp)) \ + printk(KERN_DEBUG DRV_NAME grpnam "%s: " fmt, \ + in_interrupt() ? " (INT)" : "", ## args); } while (0) +#else +#define LBTF_DEB_LL(grp, grpnam, fmt, args...) do {} while (0) +#endif + +#define lbtf_deb_enter(grp) \ + LBTF_DEB_LL(grp | LBTF_DEB_ENTER, " enter", "%s()\n", __func__); +#define lbtf_deb_enter_args(grp, fmt, args...) \ + LBTF_DEB_LL(grp | LBTF_DEB_ENTER, " enter", "%s(" fmt ")\n", __func__, ## args); +#define lbtf_deb_leave(grp) \ + LBTF_DEB_LL(grp | LBTF_DEB_LEAVE, " leave", "%s()\n", __func__); +#define lbtf_deb_leave_args(grp, fmt, args...) \ + LBTF_DEB_LL(grp | LBTF_DEB_LEAVE, " leave", "%s(), " fmt "\n", \ + __func__, ##args); +#define lbtf_deb_main(fmt, args...) LBTF_DEB_LL(LBTF_DEB_MAIN, " main", fmt, ##args) +#define lbtf_deb_net(fmt, args...) LBTF_DEB_LL(LBTF_DEB_NET, " net", fmt, ##args) +#define lbtf_deb_mesh(fmt, args...) LBTF_DEB_LL(LBTF_DEB_MESH, " mesh", fmt, ##args) +#define lbtf_deb_wext(fmt, args...) LBTF_DEB_LL(LBTF_DEB_WEXT, " wext", fmt, ##args) +#define lbtf_deb_ioctl(fmt, args...) LBTF_DEB_LL(LBTF_DEB_IOCTL, " ioctl", fmt, ##args) +#define lbtf_deb_scan(fmt, args...) LBTF_DEB_LL(LBTF_DEB_SCAN, " scan", fmt, ##args) +#define lbtf_deb_assoc(fmt, args...) LBTF_DEB_LL(LBTF_DEB_ASSOC, " assoc", fmt, ##args) +#define lbtf_deb_join(fmt, args...) LBTF_DEB_LL(LBTF_DEB_JOIN, " join", fmt, ##args) +#define lbtf_deb_11d(fmt, args...) LBTF_DEB_LL(LBTF_DEB_11D, " 11d", fmt, ##args) +#define lbtf_deb_debugfs(fmt, args...) LBTF_DEB_LL(LBTF_DEB_DEBUGFS, " debugfs", fmt, ##args) +#define lbtf_deb_ethtool(fmt, args...) LBTF_DEB_LL(LBTF_DEB_ETHTOOL, " ethtool", fmt, ##args) +#define lbtf_deb_host(fmt, args...) LBTF_DEB_LL(LBTF_DEB_HOST, " host", fmt, ##args) +#define lbtf_deb_cmd(fmt, args...) LBTF_DEB_LL(LBTF_DEB_CMD, " cmd", fmt, ##args) +#define lbtf_deb_rx(fmt, args...) LBTF_DEB_LL(LBTF_DEB_RX, " rx", fmt, ##args) +#define lbtf_deb_tx(fmt, args...) LBTF_DEB_LL(LBTF_DEB_TX, " tx", fmt, ##args) +#define lbtf_deb_fw(fmt, args...) LBTF_DEB_LL(LBTF_DEB_FW, " fw", fmt, ##args) +#define lbtf_deb_usb(fmt, args...) LBTF_DEB_LL(LBTF_DEB_USB, " usb", fmt, ##args) +#define lbtf_deb_usbd(dev, fmt, args...) LBTF_DEB_LL(LBTF_DEB_USB, " usbd", "%s:" fmt, dev_name(dev), ##args) +#define lbtf_deb_cs(fmt, args...) LBTF_DEB_LL(LBTF_DEB_CS, " cs", fmt, ##args) +#define lbtf_deb_thread(fmt, args...) LBTF_DEB_LL(LBTF_DEB_THREAD, " thread", fmt, ##args) +#define lbtf_deb_sdio(fmt, args...) LBTF_DEB_LL(LBTF_DEB_SDIO, " thread", fmt, ##args) +#define lbtf_deb_macops(fmt, args...) LBTF_DEB_LL(LBTF_DEB_MACOPS, " thread", fmt, ##args) + +#ifdef DEBUG +static inline void lbtf_deb_hex(unsigned int grp, const char *prompt, u8 *buf, int len) +{ + char newprompt[32]; + + if (len && + (lbtf_debug & LBTF_DEB_HEX) && + (lbtf_debug & grp)) { + snprintf(newprompt, sizeof(newprompt), DRV_NAME " %s: ", prompt); + print_hex_dump_bytes(prompt, DUMP_PREFIX_NONE, buf, len); + } +} +#else +#define lbtf_deb_hex(grp, prompt, buf, len) do {} while (0) +#endif + +#endif diff --git a/drivers/net/wireless/libertas_tf/if_usb.c b/drivers/net/wireless/libertas_tf/if_usb.c new file mode 100644 index 0000000..dd12cc3 --- /dev/null +++ b/drivers/net/wireless/libertas_tf/if_usb.c @@ -0,0 +1,932 @@ +/* + * Copyright (C) 2008, cozybit Inc. + * Copyright (C) 2003-2006, Marvell International Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ +#define DRV_NAME "lbtf_usb" + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "libertas_tf.h" +#include "if_usb.h" + +#include +#include +#include +#include +#include +#include + +#define INSANEDEBUG 0 +#define lbtf_deb_usb2(...) do { if (INSANEDEBUG) lbtf_deb_usbd(__VA_ARGS__); } while (0) + +#define MESSAGE_HEADER_LEN 4 + +static char *lbtf_fw_name = "lbtf_usb.bin"; +module_param_named(fw_name, lbtf_fw_name, charp, 0644); + +MODULE_FIRMWARE("lbtf_usb.bin"); + +static struct usb_device_id if_usb_table[] = { + /* Enter the device signature inside */ + { USB_DEVICE(0x1286, 0x2001) }, + { USB_DEVICE(0x05a3, 0x8388) }, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, if_usb_table); + +static void if_usb_receive(struct urb *urb); +static void if_usb_receive_fwload(struct urb *urb); +static int if_usb_prog_firmware(struct if_usb_card *cardp); +static int if_usb_host_to_card(struct lbtf_private *priv, uint8_t type, + uint8_t *payload, uint16_t nb); +static int usb_tx_block(struct if_usb_card *cardp, uint8_t *payload, + uint16_t nb, u8 data); +static void if_usb_free(struct if_usb_card *cardp); +static int if_usb_submit_rx_urb(struct if_usb_card *cardp); +static int if_usb_reset_device(struct if_usb_card *cardp); + +/** + * if_usb_wrike_bulk_callback - call back to handle URB status + * + * @param urb pointer to urb structure + */ +static void if_usb_write_bulk_callback(struct urb *urb) +{ + if (urb->status != 0) { + /* print the failure status number for debug */ + pr_info("URB in failure status: %d\n", urb->status); + } else { + lbtf_deb_usb2(&urb->dev->dev, "URB status is successful\n"); + lbtf_deb_usb2(&urb->dev->dev, "Actual length transmitted %d\n", + urb->actual_length); + } +} + +/** + * if_usb_free - free tx/rx urb, skb and rx buffer + * + * @param cardp pointer if_usb_card + */ +static void if_usb_free(struct if_usb_card *cardp) +{ + lbtf_deb_enter(LBTF_DEB_USB); + + /* Unlink tx & rx urb */ + usb_kill_urb(cardp->tx_urb); + usb_kill_urb(cardp->rx_urb); + usb_kill_urb(cardp->cmd_urb); + + usb_free_urb(cardp->tx_urb); + cardp->tx_urb = NULL; + + usb_free_urb(cardp->rx_urb); + cardp->rx_urb = NULL; + + usb_free_urb(cardp->cmd_urb); + cardp->cmd_urb = NULL; + + kfree(cardp->ep_out_buf); + cardp->ep_out_buf = NULL; + + lbtf_deb_leave(LBTF_DEB_USB); +} + +static void if_usb_setup_firmware(struct lbtf_private *priv) +{ + struct if_usb_card *cardp = priv->card; + struct cmd_ds_set_boot2_ver b2_cmd; + + lbtf_deb_enter(LBTF_DEB_USB); + + if_usb_submit_rx_urb(cardp); + b2_cmd.hdr.size = cpu_to_le16(sizeof(b2_cmd)); + b2_cmd.action = 0; + b2_cmd.version = cardp->boot2_version; + + if (lbtf_cmd_with_response(priv, CMD_SET_BOOT2_VER, &b2_cmd)) + lbtf_deb_usb("Setting boot2 version failed\n"); + + lbtf_deb_leave(LBTF_DEB_USB); +} + +static void if_usb_fw_timeo(unsigned long priv) +{ + struct if_usb_card *cardp = (void *)priv; + + lbtf_deb_enter(LBTF_DEB_USB); + if (!cardp->fwdnldover) { + /* Download timed out */ + cardp->priv->surpriseremoved = 1; + pr_err("Download timed out\n"); + } else { + lbtf_deb_usb("Download complete, no event. Assuming success\n"); + } + wake_up(&cardp->fw_wq); + lbtf_deb_leave(LBTF_DEB_USB); +} + +/** + * if_usb_probe - sets the configuration values + * + * @ifnum interface number + * @id pointer to usb_device_id + * + * Returns: 0 on success, error code on failure + */ +static int if_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + struct lbtf_private *priv; + struct if_usb_card *cardp; + int i; + + lbtf_deb_enter(LBTF_DEB_USB); + udev = interface_to_usbdev(intf); + + cardp = kzalloc(sizeof(struct if_usb_card), GFP_KERNEL); + if (!cardp) + goto error; + + setup_timer(&cardp->fw_timeout, if_usb_fw_timeo, (unsigned long)cardp); + init_waitqueue_head(&cardp->fw_wq); + + cardp->udev = udev; + iface_desc = intf->cur_altsetting; + + lbtf_deb_usbd(&udev->dev, "bcdUSB = 0x%X bDeviceClass = 0x%X" + " bDeviceSubClass = 0x%X, bDeviceProtocol = 0x%X\n", + le16_to_cpu(udev->descriptor.bcdUSB), + udev->descriptor.bDeviceClass, + udev->descriptor.bDeviceSubClass, + udev->descriptor.bDeviceProtocol); + + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + if (usb_endpoint_is_bulk_in(endpoint)) { + cardp->ep_in_size = + le16_to_cpu(endpoint->wMaxPacketSize); + cardp->ep_in = usb_endpoint_num(endpoint); + + lbtf_deb_usbd(&udev->dev, "in_endpoint = %d\n", + cardp->ep_in); + lbtf_deb_usbd(&udev->dev, "Bulk in size is %d\n", + cardp->ep_in_size); + } else if (usb_endpoint_is_bulk_out(endpoint)) { + cardp->ep_out_size = + le16_to_cpu(endpoint->wMaxPacketSize); + cardp->ep_out = usb_endpoint_num(endpoint); + + lbtf_deb_usbd(&udev->dev, "out_endpoint = %d\n", + cardp->ep_out); + lbtf_deb_usbd(&udev->dev, "Bulk out size is %d\n", + cardp->ep_out_size); + } + } + if (!cardp->ep_out_size || !cardp->ep_in_size) { + lbtf_deb_usbd(&udev->dev, "Endpoints not found\n"); + /* Endpoints not found */ + goto dealloc; + } + + cardp->rx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!cardp->rx_urb) { + lbtf_deb_usbd(&udev->dev, "Rx URB allocation failed\n"); + goto dealloc; + } + + cardp->tx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!cardp->tx_urb) { + lbtf_deb_usbd(&udev->dev, "Tx URB allocation failed\n"); + goto dealloc; + } + + cardp->cmd_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!cardp->cmd_urb) { + lbtf_deb_usbd(&udev->dev, "Cmd URB allocation failed\n"); + goto dealloc; + } + + cardp->ep_out_buf = kmalloc(MRVDRV_ETH_TX_PACKET_BUFFER_SIZE, + GFP_KERNEL); + if (!cardp->ep_out_buf) { + lbtf_deb_usbd(&udev->dev, "Could not allocate buffer\n"); + goto dealloc; + } + + priv = lbtf_add_card(cardp, &udev->dev); + if (!priv) + goto dealloc; + + cardp->priv = priv; + + priv->hw_host_to_card = if_usb_host_to_card; + priv->hw_prog_firmware = if_usb_prog_firmware; + priv->hw_reset_device = if_usb_reset_device; + cardp->boot2_version = udev->descriptor.bcdDevice; + + usb_get_dev(udev); + usb_set_intfdata(intf, cardp); + + return 0; + +dealloc: + if_usb_free(cardp); +error: +lbtf_deb_leave(LBTF_DEB_MAIN); + return -ENOMEM; +} + +/** + * if_usb_disconnect - free resource and cleanup + * + * @intf USB interface structure + */ +static void if_usb_disconnect(struct usb_interface *intf) +{ + struct if_usb_card *cardp = usb_get_intfdata(intf); + struct lbtf_private *priv = cardp->priv; + + lbtf_deb_enter(LBTF_DEB_MAIN); + + if_usb_reset_device(cardp); + + if (priv) + lbtf_remove_card(priv); + + /* Unlink and free urb */ + if_usb_free(cardp); + + usb_set_intfdata(intf, NULL); + usb_put_dev(interface_to_usbdev(intf)); + + lbtf_deb_leave(LBTF_DEB_MAIN); +} + +/** + * if_usb_send_fw_pkt - This function downloads the FW + * + * @priv pointer to struct lbtf_private + * + * Returns: 0 + */ +static int if_usb_send_fw_pkt(struct if_usb_card *cardp) +{ + struct fwdata *fwdata = cardp->ep_out_buf; + u8 *firmware = (u8 *) cardp->fw->data; + + lbtf_deb_enter(LBTF_DEB_FW); + + /* If we got a CRC failure on the last block, back + up and retry it */ + if (!cardp->CRC_OK) { + cardp->totalbytes = cardp->fwlastblksent; + cardp->fwseqnum--; + } + + lbtf_deb_usb2(&cardp->udev->dev, "totalbytes = %d\n", + cardp->totalbytes); + + /* struct fwdata (which we sent to the card) has an + extra __le32 field in between the header and the data, + which is not in the struct fwheader in the actual + firmware binary. Insert the seqnum in the middle... */ + memcpy(&fwdata->hdr, &firmware[cardp->totalbytes], + sizeof(struct fwheader)); + + cardp->fwlastblksent = cardp->totalbytes; + cardp->totalbytes += sizeof(struct fwheader); + + memcpy(fwdata->data, &firmware[cardp->totalbytes], + le32_to_cpu(fwdata->hdr.datalength)); + + lbtf_deb_usb2(&cardp->udev->dev, "Data length = %d\n", + le32_to_cpu(fwdata->hdr.datalength)); + + fwdata->seqnum = cpu_to_le32(++cardp->fwseqnum); + cardp->totalbytes += le32_to_cpu(fwdata->hdr.datalength); + + usb_tx_block(cardp, cardp->ep_out_buf, sizeof(struct fwdata) + + le32_to_cpu(fwdata->hdr.datalength), 0); + + if (fwdata->hdr.dnldcmd == cpu_to_le32(FW_HAS_DATA_TO_RECV)) { + lbtf_deb_usb2(&cardp->udev->dev, "There are data to follow\n"); + lbtf_deb_usb2(&cardp->udev->dev, + "seqnum = %d totalbytes = %d\n", + cardp->fwseqnum, cardp->totalbytes); + } else if (fwdata->hdr.dnldcmd == cpu_to_le32(FW_HAS_LAST_BLOCK)) { + lbtf_deb_usb2(&cardp->udev->dev, + "Host has finished FW downloading\n"); + lbtf_deb_usb2(&cardp->udev->dev, "Donwloading FW JUMP BLOCK\n"); + + /* Host has finished FW downloading + * Donwloading FW JUMP BLOCK + */ + cardp->fwfinalblk = 1; + } + + lbtf_deb_usb2(&cardp->udev->dev, "Firmware download done; size %d\n", + cardp->totalbytes); + + lbtf_deb_leave(LBTF_DEB_FW); + return 0; +} + +static int if_usb_reset_device(struct if_usb_card *cardp) +{ + struct cmd_ds_802_11_reset *cmd = cardp->ep_out_buf + 4; + int ret; + + lbtf_deb_enter(LBTF_DEB_USB); + + *(__le32 *)cardp->ep_out_buf = cpu_to_le32(CMD_TYPE_REQUEST); + + cmd->hdr.command = cpu_to_le16(CMD_802_11_RESET); + cmd->hdr.size = cpu_to_le16(sizeof(struct cmd_ds_802_11_reset)); + cmd->hdr.result = cpu_to_le16(0); + cmd->hdr.seqnum = cpu_to_le16(0x5a5a); + cmd->action = cpu_to_le16(CMD_ACT_HALT); + usb_tx_block(cardp, cardp->ep_out_buf, + 4 + sizeof(struct cmd_ds_802_11_reset), 0); + + msleep(100); + ret = usb_reset_device(cardp->udev); + msleep(100); + + lbtf_deb_leave_args(LBTF_DEB_USB, "ret %d", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(if_usb_reset_device); + +/** + * usb_tx_block - transfer data to the device + * + * @priv pointer to struct lbtf_private + * @payload pointer to payload data + * @nb data length + * @data non-zero for data, zero for commands + * + * Returns: 0 on success, nonzero otherwise. + */ +static int usb_tx_block(struct if_usb_card *cardp, uint8_t *payload, + uint16_t nb, u8 data) +{ + int ret = -1; + struct urb *urb; + + lbtf_deb_enter(LBTF_DEB_USB); + /* check if device is removed */ + if (cardp->priv->surpriseremoved) { + lbtf_deb_usbd(&cardp->udev->dev, "Device removed\n"); + goto tx_ret; + } + + if (data) + urb = cardp->tx_urb; + else + urb = cardp->cmd_urb; + + usb_fill_bulk_urb(urb, cardp->udev, + usb_sndbulkpipe(cardp->udev, + cardp->ep_out), + payload, nb, if_usb_write_bulk_callback, cardp); + + urb->transfer_flags |= URB_ZERO_PACKET; + + if (usb_submit_urb(urb, GFP_ATOMIC)) { + lbtf_deb_usbd(&cardp->udev->dev, + "usb_submit_urb failed: %d\n", ret); + goto tx_ret; + } + + lbtf_deb_usb2(&cardp->udev->dev, "usb_submit_urb success\n"); + + ret = 0; + +tx_ret: + lbtf_deb_leave(LBTF_DEB_USB); + return ret; +} + +static int __if_usb_submit_rx_urb(struct if_usb_card *cardp, + void (*callbackfn)(struct urb *urb)) +{ + struct sk_buff *skb; + int ret = -1; + + lbtf_deb_enter(LBTF_DEB_USB); + + skb = dev_alloc_skb(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE); + if (!skb) { + pr_err("No free skb\n"); + lbtf_deb_leave(LBTF_DEB_USB); + return -1; + } + + cardp->rx_skb = skb; + + /* Fill the receive configuration URB and initialise the Rx call back */ + usb_fill_bulk_urb(cardp->rx_urb, cardp->udev, + usb_rcvbulkpipe(cardp->udev, cardp->ep_in), + skb_tail_pointer(skb), + MRVDRV_ETH_RX_PACKET_BUFFER_SIZE, callbackfn, cardp); + + cardp->rx_urb->transfer_flags |= URB_ZERO_PACKET; + + lbtf_deb_usb2(&cardp->udev->dev, "Pointer for rx_urb %p\n", + cardp->rx_urb); + ret = usb_submit_urb(cardp->rx_urb, GFP_ATOMIC); + if (ret) { + lbtf_deb_usbd(&cardp->udev->dev, + "Submit Rx URB failed: %d\n", ret); + kfree_skb(skb); + cardp->rx_skb = NULL; + lbtf_deb_leave(LBTF_DEB_USB); + return -1; + } else { + lbtf_deb_usb2(&cardp->udev->dev, "Submit Rx URB success\n"); + lbtf_deb_leave(LBTF_DEB_USB); + return 0; + } +} + +static int if_usb_submit_rx_urb_fwload(struct if_usb_card *cardp) +{ + return __if_usb_submit_rx_urb(cardp, &if_usb_receive_fwload); +} + +static int if_usb_submit_rx_urb(struct if_usb_card *cardp) +{ + return __if_usb_submit_rx_urb(cardp, &if_usb_receive); +} + +static void if_usb_receive_fwload(struct urb *urb) +{ + struct if_usb_card *cardp = urb->context; + struct sk_buff *skb = cardp->rx_skb; + struct fwsyncheader *syncfwheader; + struct bootcmdresp bcmdresp; + + lbtf_deb_enter(LBTF_DEB_USB); + if (urb->status) { + lbtf_deb_usbd(&cardp->udev->dev, + "URB status is failed during fw load\n"); + kfree_skb(skb); + lbtf_deb_leave(LBTF_DEB_USB); + return; + } + + if (cardp->fwdnldover) { + __le32 *tmp = (__le32 *)(skb->data); + + if (tmp[0] == cpu_to_le32(CMD_TYPE_INDICATION) && + tmp[1] == cpu_to_le32(MACREG_INT_CODE_FIRMWARE_READY)) { + /* Firmware ready event received */ + pr_info("Firmware ready event received\n"); + wake_up(&cardp->fw_wq); + } else { + lbtf_deb_usb("Waiting for confirmation; got %x %x\n", + le32_to_cpu(tmp[0]), le32_to_cpu(tmp[1])); + if_usb_submit_rx_urb_fwload(cardp); + } + kfree_skb(skb); + lbtf_deb_leave(LBTF_DEB_USB); + return; + } + if (cardp->bootcmdresp <= 0) { + memcpy(&bcmdresp, skb->data, sizeof(bcmdresp)); + + if (le16_to_cpu(cardp->udev->descriptor.bcdDevice) < 0x3106) { + kfree_skb(skb); + if_usb_submit_rx_urb_fwload(cardp); + cardp->bootcmdresp = 1; + /* Received valid boot command response */ + lbtf_deb_usbd(&cardp->udev->dev, + "Received valid boot command response\n"); + lbtf_deb_leave(LBTF_DEB_USB); + return; + } + if (bcmdresp.magic != cpu_to_le32(BOOT_CMD_MAGIC_NUMBER)) { + if (bcmdresp.magic == cpu_to_le32(CMD_TYPE_REQUEST) || + bcmdresp.magic == cpu_to_le32(CMD_TYPE_DATA) || + bcmdresp.magic == cpu_to_le32(CMD_TYPE_INDICATION)) { + if (!cardp->bootcmdresp) + pr_info("Firmware already seems alive; resetting\n"); + cardp->bootcmdresp = -1; + } else { + pr_info("boot cmd response wrong magic number (0x%x)\n", + le32_to_cpu(bcmdresp.magic)); + } + } else if (bcmdresp.cmd != BOOT_CMD_FW_BY_USB) { + pr_info("boot cmd response cmd_tag error (%d)\n", + bcmdresp.cmd); + } else if (bcmdresp.result != BOOT_CMD_RESP_OK) { + pr_info("boot cmd response result error (%d)\n", + bcmdresp.result); + } else { + cardp->bootcmdresp = 1; + lbtf_deb_usbd(&cardp->udev->dev, + "Received valid boot command response\n"); + } + + kfree_skb(skb); + if_usb_submit_rx_urb_fwload(cardp); + lbtf_deb_leave(LBTF_DEB_USB); + return; + } + + syncfwheader = kmemdup(skb->data, sizeof(struct fwsyncheader), + GFP_ATOMIC); + if (!syncfwheader) { + lbtf_deb_usbd(&cardp->udev->dev, + "Failure to allocate syncfwheader\n"); + kfree_skb(skb); + lbtf_deb_leave(LBTF_DEB_USB); + return; + } + + if (!syncfwheader->cmd) { + lbtf_deb_usb2(&cardp->udev->dev, + "FW received Blk with correct CRC\n"); + lbtf_deb_usb2(&cardp->udev->dev, + "FW received Blk seqnum = %d\n", + le32_to_cpu(syncfwheader->seqnum)); + cardp->CRC_OK = 1; + } else { + lbtf_deb_usbd(&cardp->udev->dev, + "FW received Blk with CRC error\n"); + cardp->CRC_OK = 0; + } + + kfree_skb(skb); + + /* reschedule timer for 200ms hence */ + mod_timer(&cardp->fw_timeout, jiffies + (HZ/5)); + + if (cardp->fwfinalblk) { + cardp->fwdnldover = 1; + goto exit; + } + + if_usb_send_fw_pkt(cardp); + + exit: + if_usb_submit_rx_urb_fwload(cardp); + + kfree(syncfwheader); + + lbtf_deb_leave(LBTF_DEB_USB); +} + +#define MRVDRV_MIN_PKT_LEN 30 + +static inline void process_cmdtypedata(int recvlength, struct sk_buff *skb, + struct if_usb_card *cardp, + struct lbtf_private *priv) +{ + if (recvlength > MRVDRV_ETH_RX_PACKET_BUFFER_SIZE + MESSAGE_HEADER_LEN + || recvlength < MRVDRV_MIN_PKT_LEN) { + lbtf_deb_usbd(&cardp->udev->dev, "Packet length is Invalid\n"); + kfree_skb(skb); + return; + } + + skb_put(skb, recvlength); + skb_pull(skb, MESSAGE_HEADER_LEN); + lbtf_rx(priv, skb); +} + +static inline void process_cmdrequest(int recvlength, uint8_t *recvbuff, + struct sk_buff *skb, + struct if_usb_card *cardp, + struct lbtf_private *priv) +{ + if (recvlength > LBS_CMD_BUFFER_SIZE) { + lbtf_deb_usbd(&cardp->udev->dev, + "The receive buffer is too large\n"); + kfree_skb(skb); + return; + } + + BUG_ON(!in_interrupt()); + + spin_lock(&priv->driver_lock); + memcpy(priv->cmd_resp_buff, recvbuff + MESSAGE_HEADER_LEN, + recvlength - MESSAGE_HEADER_LEN); + kfree_skb(skb); + lbtf_cmd_response_rx(priv); + spin_unlock(&priv->driver_lock); +} + +/** + * if_usb_receive - read data received from the device. + * + * @urb pointer to struct urb + */ +static void if_usb_receive(struct urb *urb) +{ + struct if_usb_card *cardp = urb->context; + struct sk_buff *skb = cardp->rx_skb; + struct lbtf_private *priv = cardp->priv; + int recvlength = urb->actual_length; + uint8_t *recvbuff = NULL; + uint32_t recvtype = 0; + __le32 *pkt = (__le32 *) skb->data; + + lbtf_deb_enter(LBTF_DEB_USB); + + if (recvlength) { + if (urb->status) { + lbtf_deb_usbd(&cardp->udev->dev, "RX URB failed: %d\n", + urb->status); + kfree_skb(skb); + goto setup_for_next; + } + + recvbuff = skb->data; + recvtype = le32_to_cpu(pkt[0]); + lbtf_deb_usbd(&cardp->udev->dev, + "Recv length = 0x%x, Recv type = 0x%X\n", + recvlength, recvtype); + } else if (urb->status) { + kfree_skb(skb); + lbtf_deb_leave(LBTF_DEB_USB); + return; + } + + switch (recvtype) { + case CMD_TYPE_DATA: + process_cmdtypedata(recvlength, skb, cardp, priv); + break; + + case CMD_TYPE_REQUEST: + process_cmdrequest(recvlength, recvbuff, skb, cardp, priv); + break; + + case CMD_TYPE_INDICATION: + { + /* Event cause handling */ + u32 event_cause = le32_to_cpu(pkt[1]); + lbtf_deb_usbd(&cardp->udev->dev, "**EVENT** 0x%X\n", + event_cause); + + /* Icky undocumented magic special case */ + if (event_cause & 0xffff0000) { + u16 tmp; + u8 retrycnt; + u8 failure; + + tmp = event_cause >> 16; + retrycnt = tmp & 0x00ff; + failure = (tmp & 0xff00) >> 8; + lbtf_send_tx_feedback(priv, retrycnt, failure); + } else if (event_cause == LBTF_EVENT_BCN_SENT) + lbtf_bcn_sent(priv); + else + lbtf_deb_usbd(&cardp->udev->dev, + "Unsupported notification %d received\n", + event_cause); + kfree_skb(skb); + break; + } + default: + lbtf_deb_usbd(&cardp->udev->dev, + "libertastf: unknown command type 0x%X\n", recvtype); + kfree_skb(skb); + break; + } + +setup_for_next: + if_usb_submit_rx_urb(cardp); + lbtf_deb_leave(LBTF_DEB_USB); +} + +/** + * if_usb_host_to_card - Download data to the device + * + * @priv pointer to struct lbtf_private structure + * @type type of data + * @buf pointer to data buffer + * @len number of bytes + * + * Returns: 0 on success, nonzero otherwise + */ +static int if_usb_host_to_card(struct lbtf_private *priv, uint8_t type, + uint8_t *payload, uint16_t nb) +{ + struct if_usb_card *cardp = priv->card; + u8 data = 0; + + lbtf_deb_usbd(&cardp->udev->dev, "*** type = %u\n", type); + lbtf_deb_usbd(&cardp->udev->dev, "size after = %d\n", nb); + + if (type == MVMS_CMD) { + *(__le32 *)cardp->ep_out_buf = cpu_to_le32(CMD_TYPE_REQUEST); + } else { + *(__le32 *)cardp->ep_out_buf = cpu_to_le32(CMD_TYPE_DATA); + data = 1; + } + + memcpy((cardp->ep_out_buf + MESSAGE_HEADER_LEN), payload, nb); + + return usb_tx_block(cardp, cardp->ep_out_buf, nb + MESSAGE_HEADER_LEN, + data); +} + +/** + * if_usb_issue_boot_command - Issue boot command to Boot2. + * + * @ivalue 1 boots from FW by USB-Download, 2 boots from FW in EEPROM. + * + * Returns: 0 + */ +static int if_usb_issue_boot_command(struct if_usb_card *cardp, int ivalue) +{ + struct bootcmd *bootcmd = cardp->ep_out_buf; + + /* Prepare command */ + bootcmd->magic = cpu_to_le32(BOOT_CMD_MAGIC_NUMBER); + bootcmd->cmd = ivalue; + memset(bootcmd->pad, 0, sizeof(bootcmd->pad)); + + /* Issue command */ + usb_tx_block(cardp, cardp->ep_out_buf, sizeof(*bootcmd), 0); + + return 0; +} + + +/** + * check_fwfile_format - Check the validity of Boot2/FW image. + * + * @data pointer to image + * @totlen image length + * + * Returns: 0 if the image is valid, nonzero otherwise. + */ +static int check_fwfile_format(const u8 *data, u32 totlen) +{ + u32 bincmd, exit; + u32 blksize, offset, len; + int ret; + + ret = 1; + exit = len = 0; + + do { + struct fwheader *fwh = (void *) data; + + bincmd = le32_to_cpu(fwh->dnldcmd); + blksize = le32_to_cpu(fwh->datalength); + switch (bincmd) { + case FW_HAS_DATA_TO_RECV: + offset = sizeof(struct fwheader) + blksize; + data += offset; + len += offset; + if (len >= totlen) + exit = 1; + break; + case FW_HAS_LAST_BLOCK: + exit = 1; + ret = 0; + break; + default: + exit = 1; + break; + } + } while (!exit); + + if (ret) + pr_err("firmware file format check FAIL\n"); + else + lbtf_deb_fw("firmware file format check PASS\n"); + + return ret; +} + + +static int if_usb_prog_firmware(struct if_usb_card *cardp) +{ + int i = 0; + static int reset_count = 10; + int ret = 0; + + lbtf_deb_enter(LBTF_DEB_USB); + + kparam_block_sysfs_write(fw_name); + ret = request_firmware(&cardp->fw, lbtf_fw_name, &cardp->udev->dev); + if (ret < 0) { + pr_err("request_firmware() failed with %#x\n", ret); + pr_err("firmware %s not found\n", lbtf_fw_name); + kparam_unblock_sysfs_write(fw_name); + goto done; + } + kparam_unblock_sysfs_write(fw_name); + + if (check_fwfile_format(cardp->fw->data, cardp->fw->size)) + goto release_fw; + +restart: + if (if_usb_submit_rx_urb_fwload(cardp) < 0) { + lbtf_deb_usbd(&cardp->udev->dev, "URB submission is failed\n"); + ret = -1; + goto release_fw; + } + + cardp->bootcmdresp = 0; + do { + int j = 0; + i++; + /* Issue Boot command = 1, Boot from Download-FW */ + if_usb_issue_boot_command(cardp, BOOT_CMD_FW_BY_USB); + /* wait for command response */ + do { + j++; + msleep_interruptible(100); + } while (cardp->bootcmdresp == 0 && j < 10); + } while (cardp->bootcmdresp == 0 && i < 5); + + if (cardp->bootcmdresp <= 0) { + if (--reset_count >= 0) { + if_usb_reset_device(cardp); + goto restart; + } + return -1; + } + + i = 0; + + cardp->totalbytes = 0; + cardp->fwlastblksent = 0; + cardp->CRC_OK = 1; + cardp->fwdnldover = 0; + cardp->fwseqnum = -1; + cardp->totalbytes = 0; + cardp->fwfinalblk = 0; + + /* Send the first firmware packet... */ + if_usb_send_fw_pkt(cardp); + + /* ... and wait for the process to complete */ + wait_event_interruptible(cardp->fw_wq, cardp->priv->surpriseremoved || + cardp->fwdnldover); + + del_timer_sync(&cardp->fw_timeout); + usb_kill_urb(cardp->rx_urb); + + if (!cardp->fwdnldover) { + pr_info("failed to load fw, resetting device!\n"); + if (--reset_count >= 0) { + if_usb_reset_device(cardp); + goto restart; + } + + pr_info("FW download failure, time = %d ms\n", i * 100); + ret = -1; + goto release_fw; + } + + cardp->priv->fw_ready = 1; + + release_fw: + release_firmware(cardp->fw); + cardp->fw = NULL; + + if_usb_setup_firmware(cardp->priv); + + done: + lbtf_deb_leave_args(LBTF_DEB_USB, "ret %d", ret); + return ret; +} +EXPORT_SYMBOL_GPL(if_usb_prog_firmware); + + +#define if_usb_suspend NULL +#define if_usb_resume NULL + +static struct usb_driver if_usb_driver = { + .name = DRV_NAME, + .probe = if_usb_probe, + .disconnect = if_usb_disconnect, + .id_table = if_usb_table, + .suspend = if_usb_suspend, + .resume = if_usb_resume, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +module_usb_driver(if_usb_driver); + +MODULE_DESCRIPTION("8388 USB WLAN Thinfirm Driver"); +MODULE_AUTHOR("Cozybit Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/libertas_tf/if_usb.h b/drivers/net/wireless/libertas_tf/if_usb.h new file mode 100644 index 0000000..6fa5b3f --- /dev/null +++ b/drivers/net/wireless/libertas_tf/if_usb.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2008, cozybit Inc. + * Copyright (C) 2003-2006, Marvell International Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ +#include +#include + +struct lbtf_private; + +/** + * This file contains definition for USB interface. + */ +#define CMD_TYPE_REQUEST 0xF00DFACE +#define CMD_TYPE_DATA 0xBEADC0DE +#define CMD_TYPE_INDICATION 0xBEEFFACE + +#define BOOT_CMD_FW_BY_USB 0x01 +#define BOOT_CMD_FW_IN_EEPROM 0x02 +#define BOOT_CMD_UPDATE_BOOT2 0x03 +#define BOOT_CMD_UPDATE_FW 0x04 +#define BOOT_CMD_MAGIC_NUMBER 0x4C56524D /* LVRM */ + +struct bootcmd { + __le32 magic; + uint8_t cmd; + uint8_t pad[11]; +}; + +#define BOOT_CMD_RESP_OK 0x0001 +#define BOOT_CMD_RESP_FAIL 0x0000 + +struct bootcmdresp { + __le32 magic; + uint8_t cmd; + uint8_t result; + uint8_t pad[2]; +}; + +/** USB card description structure*/ +struct if_usb_card { + struct usb_device *udev; + struct urb *rx_urb, *tx_urb, *cmd_urb; + struct lbtf_private *priv; + + struct sk_buff *rx_skb; + + uint8_t ep_in; + uint8_t ep_out; + + int8_t bootcmdresp; + + int ep_in_size; + + void *ep_out_buf; + int ep_out_size; + + const struct firmware *fw; + struct timer_list fw_timeout; + wait_queue_head_t fw_wq; + uint32_t fwseqnum; + uint32_t totalbytes; + uint32_t fwlastblksent; + uint8_t CRC_OK; + uint8_t fwdnldover; + uint8_t fwfinalblk; + + __le16 boot2_version; +}; + +/** fwheader */ +struct fwheader { + __le32 dnldcmd; + __le32 baseaddr; + __le32 datalength; + __le32 CRC; +}; + +#define FW_MAX_DATA_BLK_SIZE 600 +/** FWData */ +struct fwdata { + struct fwheader hdr; + __le32 seqnum; + uint8_t data[0]; +}; + +/** fwsyncheader */ +struct fwsyncheader { + __le32 cmd; + __le32 seqnum; +}; + +#define FW_HAS_DATA_TO_RECV 0x00000001 +#define FW_HAS_LAST_BLOCK 0x00000004 diff --git a/drivers/net/wireless/libertas_tf/libertas_tf.h b/drivers/net/wireless/libertas_tf/libertas_tf.h new file mode 100644 index 0000000..ad77b92 --- /dev/null +++ b/drivers/net/wireless/libertas_tf/libertas_tf.h @@ -0,0 +1,519 @@ +/* + * Copyright (C) 2008, cozybit Inc. + * Copyright (C) 2007, Red Hat, Inc. + * Copyright (C) 2003-2006, Marvell International Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ +#include +#include +#include +#include + +#include "deb_defs.h" + +#ifndef DRV_NAME +#define DRV_NAME "libertas_tf" +#endif + +#define MRVL_DEFAULT_RETRIES 9 +#define MRVL_PER_PACKET_RATE 0x10 +#define MRVL_MAX_BCN_SIZE 440 +#define CMD_OPTION_WAITFORRSP 0x0002 + +/* Return command are almost always the same as the host command, but with + * bit 15 set high. There are a few exceptions, though... + */ +#define CMD_RET(cmd) (0x8000 | cmd) + +/* Command codes */ +#define CMD_GET_HW_SPEC 0x0003 +#define CMD_802_11_RESET 0x0005 +#define CMD_MAC_MULTICAST_ADR 0x0010 +#define CMD_802_11_RADIO_CONTROL 0x001c +#define CMD_802_11_RF_CHANNEL 0x001d +#define CMD_802_11_RF_TX_POWER 0x001e +#define CMD_MAC_CONTROL 0x0028 +#define CMD_802_11_MAC_ADDRESS 0x004d +#define CMD_SET_BOOT2_VER 0x00a5 +#define CMD_802_11_BEACON_CTRL 0x00b0 +#define CMD_802_11_BEACON_SET 0x00cb +#define CMD_802_11_SET_MODE 0x00cc +#define CMD_802_11_SET_BSSID 0x00cd + +#define CMD_ACT_GET 0x0000 +#define CMD_ACT_SET 0x0001 + +/* Define action or option for CMD_802_11_RESET */ +#define CMD_ACT_HALT 0x0003 + +/* Define action or option for CMD_MAC_CONTROL */ +#define CMD_ACT_MAC_RX_ON 0x0001 +#define CMD_ACT_MAC_TX_ON 0x0002 +#define CMD_ACT_MAC_MULTICAST_ENABLE 0x0020 +#define CMD_ACT_MAC_BROADCAST_ENABLE 0x0040 +#define CMD_ACT_MAC_PROMISCUOUS_ENABLE 0x0080 +#define CMD_ACT_MAC_ALL_MULTICAST_ENABLE 0x0100 + +/* Define action or option for CMD_802_11_RADIO_CONTROL */ +#define CMD_TYPE_AUTO_PREAMBLE 0x0001 +#define CMD_TYPE_SHORT_PREAMBLE 0x0002 +#define CMD_TYPE_LONG_PREAMBLE 0x0003 + +#define TURN_ON_RF 0x01 +#define RADIO_ON 0x01 +#define RADIO_OFF 0x00 + +#define SET_AUTO_PREAMBLE 0x05 +#define SET_SHORT_PREAMBLE 0x03 +#define SET_LONG_PREAMBLE 0x01 + +/* Define action or option for CMD_802_11_RF_CHANNEL */ +#define CMD_OPT_802_11_RF_CHANNEL_GET 0x00 +#define CMD_OPT_802_11_RF_CHANNEL_SET 0x01 + +/* Codes for CMD_802_11_SET_MODE */ +enum lbtf_mode { + LBTF_PASSIVE_MODE, + LBTF_STA_MODE, + LBTF_AP_MODE, +}; + +/** Card Event definition */ +#define MACREG_INT_CODE_FIRMWARE_READY 48 +/** Buffer Constants */ + +/* The size of SQ memory PPA, DPA are 8 DWORDs, that keep the physical +* addresses of TxPD buffers. Station has only 8 TxPD available, Whereas +* driver has more local TxPDs. Each TxPD on the host memory is associated +* with a Tx control node. The driver maintains 8 RxPD descriptors for +* station firmware to store Rx packet information. +* +* Current version of MAC has a 32x6 multicast address buffer. +* +* 802.11b can have up to 14 channels, the driver keeps the +* BSSID(MAC address) of each APs or Ad hoc stations it has sensed. +*/ + +#define MRVDRV_MAX_MULTICAST_LIST_SIZE 32 +#define LBS_NUM_CMD_BUFFERS 10 +#define LBS_CMD_BUFFER_SIZE (2 * 1024) +#define MRVDRV_MAX_CHANNEL_SIZE 14 +#define MRVDRV_SNAP_HEADER_LEN 8 + +#define LBS_UPLD_SIZE 2312 +#define DEV_NAME_LEN 32 + +/** Misc constants */ +/* This section defines 802.11 specific contants */ + +#define MRVDRV_MAX_REGION_CODE 6 +/** + * the table to keep region code + */ +#define LBTF_REGDOMAIN_US 0x10 +#define LBTF_REGDOMAIN_CA 0x20 +#define LBTF_REGDOMAIN_EU 0x30 +#define LBTF_REGDOMAIN_SP 0x31 +#define LBTF_REGDOMAIN_FR 0x32 +#define LBTF_REGDOMAIN_JP 0x40 + +#define SBI_EVENT_CAUSE_SHIFT 3 + +/** RxPD status */ + +#define MRVDRV_RXPD_STATUS_OK 0x0001 + + +/* This is for firmware specific length */ +#define EXTRA_LEN 36 + +#define MRVDRV_ETH_TX_PACKET_BUFFER_SIZE \ + (ETH_FRAME_LEN + sizeof(struct txpd) + EXTRA_LEN) + +#define MRVDRV_ETH_RX_PACKET_BUFFER_SIZE \ + (ETH_FRAME_LEN + sizeof(struct rxpd) \ + + MRVDRV_SNAP_HEADER_LEN + EXTRA_LEN) + +#define CMD_F_HOSTCMD (1 << 0) +#define FW_CAPINFO_WPA (1 << 0) + +#define RF_ANTENNA_1 0x1 +#define RF_ANTENNA_2 0x2 +#define RF_ANTENNA_AUTO 0xFFFF + +#define LBTF_EVENT_BCN_SENT 55 + +/** Global Variable Declaration */ +/** mv_ms_type */ +enum mv_ms_type { + MVMS_DAT = 0, + MVMS_CMD = 1, + MVMS_TXDONE = 2, + MVMS_EVENT +}; + +extern struct workqueue_struct *lbtf_wq; + +struct lbtf_private; + +struct lbtf_offset_value { + u32 offset; + u32 value; +}; + +struct channel_range { + u8 regdomain; + u8 start; + u8 end; /* exclusive (channel must be less than end) */ +}; + +struct if_usb_card; + +/** Private structure for the MV device */ +struct lbtf_private { + void *card; + struct ieee80211_hw *hw; + + /* Command response buffer */ + u8 cmd_resp_buff[LBS_UPLD_SIZE]; + /* Download sent: + bit0 1/0=data_sent/data_tx_done, + bit1 1/0=cmd_sent/cmd_tx_done, + all other bits reserved 0 */ + struct ieee80211_vif *vif; + + struct work_struct cmd_work; + struct work_struct tx_work; + /** Hardware access */ + int (*hw_host_to_card) (struct lbtf_private *priv, u8 type, u8 *payload, u16 nb); + int (*hw_prog_firmware) (struct if_usb_card *cardp); + int (*hw_reset_device) (struct if_usb_card *cardp); + + + /** Wlan adapter data structure*/ + /** STATUS variables */ + u32 fwrelease; + u32 fwcapinfo; + /* protected with big lock */ + + struct mutex lock; + + /** command-related variables */ + u16 seqnum; + /* protected by big lock */ + + struct cmd_ctrl_node *cmd_array; + /** Current command */ + struct cmd_ctrl_node *cur_cmd; + /** command Queues */ + /** Free command buffers */ + struct list_head cmdfreeq; + /** Pending command buffers */ + struct list_head cmdpendingq; + + /** spin locks */ + spinlock_t driver_lock; + + /** Timers */ + struct timer_list command_timer; + int nr_retries; + int cmd_timed_out; + + u8 cmd_response_rxed; + + /** capability Info used in Association, start, join */ + u16 capability; + + /** MAC address information */ + u8 current_addr[ETH_ALEN]; + u8 multicastlist[MRVDRV_MAX_MULTICAST_LIST_SIZE][ETH_ALEN]; + u32 nr_of_multicastmacaddr; + int cur_freq; + + struct sk_buff *skb_to_tx; + struct sk_buff *tx_skb; + + /** NIC Operation characteristics */ + u16 mac_control; + u16 regioncode; + struct channel_range range; + + u8 radioon; + u32 preamble; + + struct ieee80211_channel channels[14]; + struct ieee80211_rate rates[12]; + struct ieee80211_supported_band band; + struct lbtf_offset_value offsetvalue; + + u8 fw_ready; + u8 surpriseremoved; + struct sk_buff_head bc_ps_buf; + + /* Most recently reported noise in dBm */ + s8 noise; +}; + +/* 802.11-related definitions */ + +/* TxPD descriptor */ +struct txpd { + /* Current Tx packet status */ + __le32 tx_status; + /* Tx control */ + __le32 tx_control; + __le32 tx_packet_location; + /* Tx packet length */ + __le16 tx_packet_length; + /* First 2 byte of destination MAC address */ + u8 tx_dest_addr_high[2]; + /* Last 4 byte of destination MAC address */ + u8 tx_dest_addr_low[4]; + /* Pkt Priority */ + u8 priority; + /* Pkt Trasnit Power control */ + u8 powermgmt; + /* Time the packet has been queued in the driver (units = 2ms) */ + u8 pktdelay_2ms; + /* reserved */ + u8 reserved1; +}; + +/* RxPD Descriptor */ +struct rxpd { + /* Current Rx packet status */ + __le16 status; + + /* SNR */ + u8 snr; + + /* Tx control */ + u8 rx_control; + + /* Pkt length */ + __le16 pkt_len; + + /* Noise Floor */ + u8 nf; + + /* Rx Packet Rate */ + u8 rx_rate; + + /* Pkt addr */ + __le32 pkt_ptr; + + /* Next Rx RxPD addr */ + __le32 next_rxpd_ptr; + + /* Pkt Priority */ + u8 priority; + u8 reserved[3]; +}; + +struct cmd_header { + __le16 command; + __le16 size; + __le16 seqnum; + __le16 result; +} __packed; + +struct cmd_ctrl_node { + struct list_head list; + int result; + /* command response */ + int (*callback)(struct lbtf_private *, + unsigned long, struct cmd_header *); + unsigned long callback_arg; + /* command data */ + struct cmd_header *cmdbuf; + /* wait queue */ + u16 cmdwaitqwoken; + wait_queue_head_t cmdwait_q; +}; + +/* + * Define data structure for CMD_GET_HW_SPEC + * This structure defines the response for the GET_HW_SPEC command + */ +struct cmd_ds_get_hw_spec { + struct cmd_header hdr; + + /* HW Interface version number */ + __le16 hwifversion; + /* HW version number */ + __le16 version; + /* Max number of TxPD FW can handle */ + __le16 nr_txpd; + /* Max no of Multicast address */ + __le16 nr_mcast_adr; + /* MAC address */ + u8 permanentaddr[6]; + + /* region Code */ + __le16 regioncode; + + /* Number of antenna used */ + __le16 nr_antenna; + + /* FW release number, example 0x01030304 = 2.3.4p1 */ + __le32 fwrelease; + + /* Base Address of TxPD queue */ + __le32 wcb_base; + /* Read Pointer of RxPd queue */ + __le32 rxpd_rdptr; + + /* Write Pointer of RxPd queue */ + __le32 rxpd_wrptr; + + /*FW/HW capability */ + __le32 fwcapinfo; +} __packed; + +struct cmd_ds_mac_control { + struct cmd_header hdr; + __le16 action; + u16 reserved; +}; + +struct cmd_ds_802_11_mac_address { + struct cmd_header hdr; + + __le16 action; + uint8_t macadd[ETH_ALEN]; +}; + +struct cmd_ds_mac_multicast_addr { + struct cmd_header hdr; + + __le16 action; + __le16 nr_of_adrs; + u8 maclist[ETH_ALEN * MRVDRV_MAX_MULTICAST_LIST_SIZE]; +}; + +struct cmd_ds_set_mode { + struct cmd_header hdr; + + __le16 mode; +}; + +struct cmd_ds_set_bssid { + struct cmd_header hdr; + + u8 bssid[6]; + u8 activate; +}; + +struct cmd_ds_802_11_radio_control { + struct cmd_header hdr; + + __le16 action; + __le16 control; +}; + + +struct cmd_ds_802_11_rf_channel { + struct cmd_header hdr; + + __le16 action; + __le16 channel; + __le16 rftype; /* unused */ + __le16 reserved; /* unused */ + u8 channellist[32]; /* unused */ +}; + +struct cmd_ds_set_boot2_ver { + struct cmd_header hdr; + + __le16 action; + __le16 version; +}; + +struct cmd_ds_802_11_reset { + struct cmd_header hdr; + + __le16 action; +}; + +struct cmd_ds_802_11_beacon_control { + struct cmd_header hdr; + + __le16 action; + __le16 beacon_enable; + __le16 beacon_period; +}; + +struct cmd_ds_802_11_beacon_set { + struct cmd_header hdr; + + __le16 len; + u8 beacon[MRVL_MAX_BCN_SIZE]; +}; + +struct lbtf_private; +struct cmd_ctrl_node; + +/** Function Prototype Declaration */ +void lbtf_set_mac_control(struct lbtf_private *priv); + +int lbtf_free_cmd_buffer(struct lbtf_private *priv); + +int lbtf_allocate_cmd_buffer(struct lbtf_private *priv); +int lbtf_execute_next_command(struct lbtf_private *priv); +int lbtf_set_radio_control(struct lbtf_private *priv); +int lbtf_update_hw_spec(struct lbtf_private *priv); +int lbtf_cmd_set_mac_multicast_addr(struct lbtf_private *priv); +void lbtf_set_mode(struct lbtf_private *priv, enum lbtf_mode mode); +void lbtf_set_bssid(struct lbtf_private *priv, bool activate, const u8 *bssid); +int lbtf_set_mac_address(struct lbtf_private *priv, uint8_t *mac_addr); + +int lbtf_set_channel(struct lbtf_private *priv, u8 channel); + +int lbtf_beacon_set(struct lbtf_private *priv, struct sk_buff *beacon); +int lbtf_beacon_ctrl(struct lbtf_private *priv, bool beacon_enable, + int beacon_int); + + +int lbtf_process_rx_command(struct lbtf_private *priv); +void lbtf_complete_command(struct lbtf_private *priv, struct cmd_ctrl_node *cmd, + int result); +void lbtf_cmd_response_rx(struct lbtf_private *priv); + +/* main.c */ +struct chan_freq_power *lbtf_get_region_cfp_table(u8 region, + int *cfp_no); +struct lbtf_private *lbtf_add_card(void *card, struct device *dmdev); +int lbtf_remove_card(struct lbtf_private *priv); +int lbtf_start_card(struct lbtf_private *priv); +int lbtf_rx(struct lbtf_private *priv, struct sk_buff *skb); +void lbtf_send_tx_feedback(struct lbtf_private *priv, u8 retrycnt, u8 fail); +void lbtf_bcn_sent(struct lbtf_private *priv); + +/* support functions for cmd.c */ +/* lbtf_cmd() infers the size of the buffer to copy data back into, from + the size of the target of the pointer. Since the command to be sent + may often be smaller, that size is set in cmd->size by the caller.*/ +#define lbtf_cmd(priv, cmdnr, cmd, cb, cb_arg) ({ \ + uint16_t __sz = le16_to_cpu((cmd)->hdr.size); \ + (cmd)->hdr.size = cpu_to_le16(sizeof(*(cmd))); \ + __lbtf_cmd(priv, cmdnr, &(cmd)->hdr, __sz, cb, cb_arg); \ +}) + +#define lbtf_cmd_with_response(priv, cmdnr, cmd) \ + lbtf_cmd(priv, cmdnr, cmd, lbtf_cmd_copyback, (unsigned long) (cmd)) + +void lbtf_cmd_async(struct lbtf_private *priv, uint16_t command, + struct cmd_header *in_cmd, int in_cmd_size); + +int __lbtf_cmd(struct lbtf_private *priv, uint16_t command, + struct cmd_header *in_cmd, int in_cmd_size, + int (*callback)(struct lbtf_private *, unsigned long, + struct cmd_header *), + unsigned long callback_arg); + +int lbtf_cmd_copyback(struct lbtf_private *priv, unsigned long extra, + struct cmd_header *resp); diff --git a/drivers/net/wireless/libertas_tf/main.c b/drivers/net/wireless/libertas_tf/main.c new file mode 100644 index 0000000..74ed98d --- /dev/null +++ b/drivers/net/wireless/libertas_tf/main.c @@ -0,0 +1,785 @@ +/* + * Copyright (C) 2008, cozybit Inc. + * Copyright (C) 2003-2006, Marvell International Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include + +#include +#include +#include "libertas_tf.h" + +#define DRIVER_RELEASE_VERSION "004.p0" +/* thinfirm version: 5.132.X.pX */ +#define LBTF_FW_VER_MIN 0x05840300 +#define LBTF_FW_VER_MAX 0x0584ffff +#define QOS_CONTROL_LEN 2 + +/* Module parameters */ +unsigned int lbtf_debug; +EXPORT_SYMBOL_GPL(lbtf_debug); +module_param_named(libertas_tf_debug, lbtf_debug, int, 0644); + +static const char lbtf_driver_version[] = "THINFIRM-USB8388-" DRIVER_RELEASE_VERSION +#ifdef DEBUG + "-dbg" +#endif + ""; + +struct workqueue_struct *lbtf_wq; + +static const struct ieee80211_channel lbtf_channels[] = { + { .center_freq = 2412, .hw_value = 1 }, + { .center_freq = 2417, .hw_value = 2 }, + { .center_freq = 2422, .hw_value = 3 }, + { .center_freq = 2427, .hw_value = 4 }, + { .center_freq = 2432, .hw_value = 5 }, + { .center_freq = 2437, .hw_value = 6 }, + { .center_freq = 2442, .hw_value = 7 }, + { .center_freq = 2447, .hw_value = 8 }, + { .center_freq = 2452, .hw_value = 9 }, + { .center_freq = 2457, .hw_value = 10 }, + { .center_freq = 2462, .hw_value = 11 }, + { .center_freq = 2467, .hw_value = 12 }, + { .center_freq = 2472, .hw_value = 13 }, + { .center_freq = 2484, .hw_value = 14 }, +}; + +/* This table contains the hardware specific values for the modulation rates. */ +static const struct ieee80211_rate lbtf_rates[] = { + { .bitrate = 10, + .hw_value = 0, }, + { .bitrate = 20, + .hw_value = 1, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 55, + .hw_value = 2, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 110, + .hw_value = 3, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 60, + .hw_value = 5, + .flags = 0 }, + { .bitrate = 90, + .hw_value = 6, + .flags = 0 }, + { .bitrate = 120, + .hw_value = 7, + .flags = 0 }, + { .bitrate = 180, + .hw_value = 8, + .flags = 0 }, + { .bitrate = 240, + .hw_value = 9, + .flags = 0 }, + { .bitrate = 360, + .hw_value = 10, + .flags = 0 }, + { .bitrate = 480, + .hw_value = 11, + .flags = 0 }, + { .bitrate = 540, + .hw_value = 12, + .flags = 0 }, +}; + +static void lbtf_cmd_work(struct work_struct *work) +{ + struct lbtf_private *priv = container_of(work, struct lbtf_private, + cmd_work); + + lbtf_deb_enter(LBTF_DEB_CMD); + + spin_lock_irq(&priv->driver_lock); + /* command response? */ + if (priv->cmd_response_rxed) { + priv->cmd_response_rxed = 0; + spin_unlock_irq(&priv->driver_lock); + lbtf_process_rx_command(priv); + spin_lock_irq(&priv->driver_lock); + } + + if (priv->cmd_timed_out && priv->cur_cmd) { + struct cmd_ctrl_node *cmdnode = priv->cur_cmd; + + if (++priv->nr_retries > 10) { + lbtf_complete_command(priv, cmdnode, + -ETIMEDOUT); + priv->nr_retries = 0; + } else { + priv->cur_cmd = NULL; + + /* Stick it back at the _top_ of the pending + * queue for immediate resubmission */ + list_add(&cmdnode->list, &priv->cmdpendingq); + } + } + priv->cmd_timed_out = 0; + spin_unlock_irq(&priv->driver_lock); + + if (!priv->fw_ready) { + lbtf_deb_leave_args(LBTF_DEB_CMD, "fw not ready"); + return; + } + + /* Execute the next command */ + if (!priv->cur_cmd) + lbtf_execute_next_command(priv); + + lbtf_deb_leave(LBTF_DEB_CMD); +} + +/** + * lbtf_setup_firmware: initialize firmware. + * + * @priv A pointer to struct lbtf_private structure + * + * Returns: 0 on success. + */ +static int lbtf_setup_firmware(struct lbtf_private *priv) +{ + int ret = -1; + + lbtf_deb_enter(LBTF_DEB_FW); + /* + * Read priv address from HW + */ + memset(priv->current_addr, 0xff, ETH_ALEN); + ret = lbtf_update_hw_spec(priv); + if (ret) { + ret = -1; + goto done; + } + + lbtf_set_mac_control(priv); + lbtf_set_radio_control(priv); + + ret = 0; +done: + lbtf_deb_leave_args(LBTF_DEB_FW, "ret: %d", ret); + return ret; +} + +/** + * This function handles the timeout of command sending. + * It will re-send the same command again. + */ +static void command_timer_fn(unsigned long data) +{ + struct lbtf_private *priv = (struct lbtf_private *)data; + unsigned long flags; + lbtf_deb_enter(LBTF_DEB_CMD); + + spin_lock_irqsave(&priv->driver_lock, flags); + + if (!priv->cur_cmd) { + printk(KERN_DEBUG "libertastf: command timer expired; " + "no pending command\n"); + goto out; + } + + printk(KERN_DEBUG "libertas: command %x timed out\n", + le16_to_cpu(priv->cur_cmd->cmdbuf->command)); + + priv->cmd_timed_out = 1; + queue_work(lbtf_wq, &priv->cmd_work); +out: + spin_unlock_irqrestore(&priv->driver_lock, flags); + lbtf_deb_leave(LBTF_DEB_CMD); +} + +static int lbtf_init_adapter(struct lbtf_private *priv) +{ + lbtf_deb_enter(LBTF_DEB_MAIN); + memset(priv->current_addr, 0xff, ETH_ALEN); + mutex_init(&priv->lock); + + priv->vif = NULL; + setup_timer(&priv->command_timer, command_timer_fn, + (unsigned long)priv); + + INIT_LIST_HEAD(&priv->cmdfreeq); + INIT_LIST_HEAD(&priv->cmdpendingq); + + spin_lock_init(&priv->driver_lock); + + /* Allocate the command buffers */ + if (lbtf_allocate_cmd_buffer(priv)) + return -1; + + lbtf_deb_leave(LBTF_DEB_MAIN); + return 0; +} + +static void lbtf_free_adapter(struct lbtf_private *priv) +{ + lbtf_deb_enter(LBTF_DEB_MAIN); + lbtf_free_cmd_buffer(priv); + del_timer(&priv->command_timer); + lbtf_deb_leave(LBTF_DEB_MAIN); +} + +static void lbtf_op_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct lbtf_private *priv = hw->priv; + + priv->skb_to_tx = skb; + queue_work(lbtf_wq, &priv->tx_work); + /* + * queue will be restarted when we receive transmission feedback if + * there are no buffered multicast frames to send + */ + ieee80211_stop_queues(priv->hw); +} + +static void lbtf_tx_work(struct work_struct *work) +{ + struct lbtf_private *priv = container_of(work, struct lbtf_private, + tx_work); + unsigned int len; + struct ieee80211_tx_info *info; + struct txpd *txpd; + struct sk_buff *skb = NULL; + int err; + + lbtf_deb_enter(LBTF_DEB_MACOPS | LBTF_DEB_TX); + + if ((priv->vif->type == NL80211_IFTYPE_AP) && + (!skb_queue_empty(&priv->bc_ps_buf))) + skb = skb_dequeue(&priv->bc_ps_buf); + else if (priv->skb_to_tx) { + skb = priv->skb_to_tx; + priv->skb_to_tx = NULL; + } else { + lbtf_deb_leave(LBTF_DEB_MACOPS | LBTF_DEB_TX); + return; + } + + len = skb->len; + info = IEEE80211_SKB_CB(skb); + txpd = (struct txpd *) skb_push(skb, sizeof(struct txpd)); + + if (priv->surpriseremoved) { + dev_kfree_skb_any(skb); + lbtf_deb_leave(LBTF_DEB_MACOPS | LBTF_DEB_TX); + return; + } + + memset(txpd, 0, sizeof(struct txpd)); + /* Activate per-packet rate selection */ + txpd->tx_control |= cpu_to_le32(MRVL_PER_PACKET_RATE | + ieee80211_get_tx_rate(priv->hw, info)->hw_value); + + /* copy destination address from 802.11 header */ + memcpy(txpd->tx_dest_addr_high, skb->data + sizeof(struct txpd) + 4, + ETH_ALEN); + txpd->tx_packet_length = cpu_to_le16(len); + txpd->tx_packet_location = cpu_to_le32(sizeof(struct txpd)); + lbtf_deb_hex(LBTF_DEB_TX, "TX Data", skb->data, min_t(unsigned int, skb->len, 100)); + BUG_ON(priv->tx_skb); + spin_lock_irq(&priv->driver_lock); + priv->tx_skb = skb; + err = priv->hw_host_to_card(priv, MVMS_DAT, skb->data, skb->len); + spin_unlock_irq(&priv->driver_lock); + if (err) { + dev_kfree_skb_any(skb); + priv->tx_skb = NULL; + pr_err("TX error: %d", err); + } + lbtf_deb_leave(LBTF_DEB_MACOPS | LBTF_DEB_TX); +} + +static int lbtf_op_start(struct ieee80211_hw *hw) +{ + struct lbtf_private *priv = hw->priv; + void *card = priv->card; + int ret = -1; + + lbtf_deb_enter(LBTF_DEB_MACOPS); + + if (!priv->fw_ready) + /* Upload firmware */ + if (priv->hw_prog_firmware(card)) + goto err_prog_firmware; + + /* poke the firmware */ + priv->capability = WLAN_CAPABILITY_SHORT_PREAMBLE; + priv->radioon = RADIO_ON; + priv->mac_control = CMD_ACT_MAC_RX_ON | CMD_ACT_MAC_TX_ON; + ret = lbtf_setup_firmware(priv); + if (ret) + goto err_prog_firmware; + + if ((priv->fwrelease < LBTF_FW_VER_MIN) || + (priv->fwrelease > LBTF_FW_VER_MAX)) { + ret = -1; + goto err_prog_firmware; + } + + printk(KERN_INFO "libertastf: Marvell WLAN 802.11 thinfirm adapter\n"); + lbtf_deb_leave(LBTF_DEB_MACOPS); + return 0; + +err_prog_firmware: + priv->hw_reset_device(card); + lbtf_deb_leave_args(LBTF_DEB_MACOPS, "error programing fw; ret=%d", ret); + return ret; +} + +static void lbtf_op_stop(struct ieee80211_hw *hw) +{ + struct lbtf_private *priv = hw->priv; + unsigned long flags; + struct sk_buff *skb; + + struct cmd_ctrl_node *cmdnode; + + lbtf_deb_enter(LBTF_DEB_MACOPS); + + /* Flush pending command nodes */ + spin_lock_irqsave(&priv->driver_lock, flags); + list_for_each_entry(cmdnode, &priv->cmdpendingq, list) { + cmdnode->result = -ENOENT; + cmdnode->cmdwaitqwoken = 1; + wake_up_interruptible(&cmdnode->cmdwait_q); + } + + spin_unlock_irqrestore(&priv->driver_lock, flags); + cancel_work_sync(&priv->cmd_work); + cancel_work_sync(&priv->tx_work); + while ((skb = skb_dequeue(&priv->bc_ps_buf))) + dev_kfree_skb_any(skb); + priv->radioon = RADIO_OFF; + lbtf_set_radio_control(priv); + + lbtf_deb_leave(LBTF_DEB_MACOPS); +} + +static int lbtf_op_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct lbtf_private *priv = hw->priv; + lbtf_deb_enter(LBTF_DEB_MACOPS); + if (priv->vif != NULL) + return -EOPNOTSUPP; + + priv->vif = vif; + switch (vif->type) { + case NL80211_IFTYPE_MESH_POINT: + case NL80211_IFTYPE_AP: + lbtf_set_mode(priv, LBTF_AP_MODE); + break; + case NL80211_IFTYPE_STATION: + lbtf_set_mode(priv, LBTF_STA_MODE); + break; + default: + priv->vif = NULL; + return -EOPNOTSUPP; + } + lbtf_set_mac_address(priv, (u8 *) vif->addr); + lbtf_deb_leave(LBTF_DEB_MACOPS); + return 0; +} + +static void lbtf_op_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct lbtf_private *priv = hw->priv; + lbtf_deb_enter(LBTF_DEB_MACOPS); + + if (priv->vif->type == NL80211_IFTYPE_AP || + priv->vif->type == NL80211_IFTYPE_MESH_POINT) + lbtf_beacon_ctrl(priv, 0, 0); + lbtf_set_mode(priv, LBTF_PASSIVE_MODE); + lbtf_set_bssid(priv, 0, NULL); + priv->vif = NULL; + lbtf_deb_leave(LBTF_DEB_MACOPS); +} + +static int lbtf_op_config(struct ieee80211_hw *hw, u32 changed) +{ + struct lbtf_private *priv = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + lbtf_deb_enter(LBTF_DEB_MACOPS); + + if (conf->chandef.chan->center_freq != priv->cur_freq) { + priv->cur_freq = conf->chandef.chan->center_freq; + lbtf_set_channel(priv, conf->chandef.chan->hw_value); + } + lbtf_deb_leave(LBTF_DEB_MACOPS); + return 0; +} + +static u64 lbtf_op_prepare_multicast(struct ieee80211_hw *hw, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + struct netdev_hw_addr_list *mc_list) +#else + int mc_count, struct dev_addr_list *ha) +#endif +{ + struct lbtf_private *priv = hw->priv; + int i; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + struct netdev_hw_addr *ha; + int mc_count = netdev_hw_addr_list_count(mc_list); +#endif + + if (!mc_count || mc_count > MRVDRV_MAX_MULTICAST_LIST_SIZE) + return mc_count; + + priv->nr_of_multicastmacaddr = mc_count; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + i = 0; + netdev_hw_addr_list_for_each(ha, mc_list) + memcpy(&priv->multicastlist[i++], ha->addr, ETH_ALEN); +#else + for (i = 0; i < mc_count; i++) { + if (!ha) + break; + memcpy(&priv->multicastlist[i], ha->da_addr, + ETH_ALEN); + ha = ha->next; + } +#endif + + return mc_count; +} + +#define SUPPORTED_FIF_FLAGS (FIF_PROMISC_IN_BSS | FIF_ALLMULTI) +static void lbtf_op_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *new_flags, + u64 multicast) +{ + struct lbtf_private *priv = hw->priv; + int old_mac_control = priv->mac_control; + + lbtf_deb_enter(LBTF_DEB_MACOPS); + + changed_flags &= SUPPORTED_FIF_FLAGS; + *new_flags &= SUPPORTED_FIF_FLAGS; + + if (!changed_flags) { + lbtf_deb_leave(LBTF_DEB_MACOPS); + return; + } + + if (*new_flags & (FIF_PROMISC_IN_BSS)) + priv->mac_control |= CMD_ACT_MAC_PROMISCUOUS_ENABLE; + else + priv->mac_control &= ~CMD_ACT_MAC_PROMISCUOUS_ENABLE; + if (*new_flags & (FIF_ALLMULTI) || + multicast > MRVDRV_MAX_MULTICAST_LIST_SIZE) { + priv->mac_control |= CMD_ACT_MAC_ALL_MULTICAST_ENABLE; + priv->mac_control &= ~CMD_ACT_MAC_MULTICAST_ENABLE; + } else if (multicast) { + priv->mac_control |= CMD_ACT_MAC_MULTICAST_ENABLE; + priv->mac_control &= ~CMD_ACT_MAC_ALL_MULTICAST_ENABLE; + lbtf_cmd_set_mac_multicast_addr(priv); + } else { + priv->mac_control &= ~(CMD_ACT_MAC_MULTICAST_ENABLE | + CMD_ACT_MAC_ALL_MULTICAST_ENABLE); + if (priv->nr_of_multicastmacaddr) { + priv->nr_of_multicastmacaddr = 0; + lbtf_cmd_set_mac_multicast_addr(priv); + } + } + + + if (priv->mac_control != old_mac_control) + lbtf_set_mac_control(priv); + + lbtf_deb_leave(LBTF_DEB_MACOPS); +} + +static void lbtf_op_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changes) +{ + struct lbtf_private *priv = hw->priv; + struct sk_buff *beacon; + lbtf_deb_enter(LBTF_DEB_MACOPS); + + if (changes & (BSS_CHANGED_BEACON | BSS_CHANGED_BEACON_INT)) { + switch (priv->vif->type) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_MESH_POINT: + beacon = ieee80211_beacon_get(hw, vif); + if (beacon) { + lbtf_beacon_set(priv, beacon); + kfree_skb(beacon); + lbtf_beacon_ctrl(priv, 1, + bss_conf->beacon_int); + } + break; + default: + break; + } + } + + if (changes & BSS_CHANGED_BSSID) { + bool activate = !is_zero_ether_addr(bss_conf->bssid); + lbtf_set_bssid(priv, activate, bss_conf->bssid); + } + + if (changes & BSS_CHANGED_ERP_PREAMBLE) { + if (bss_conf->use_short_preamble) + priv->preamble = CMD_TYPE_SHORT_PREAMBLE; + else + priv->preamble = CMD_TYPE_LONG_PREAMBLE; + lbtf_set_radio_control(priv); + } + + lbtf_deb_leave(LBTF_DEB_MACOPS); +} + +static int lbtf_op_get_survey(struct ieee80211_hw *hw, int idx, + struct survey_info *survey) +{ + struct lbtf_private *priv = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + + if (idx != 0) + return -ENOENT; + + survey->channel = conf->chandef.chan; + survey->filled = SURVEY_INFO_NOISE_DBM; + survey->noise = priv->noise; + + return 0; +} + +static const struct ieee80211_ops lbtf_ops = { + .tx = lbtf_op_tx, + .start = lbtf_op_start, + .stop = lbtf_op_stop, + .add_interface = lbtf_op_add_interface, + .remove_interface = lbtf_op_remove_interface, + .config = lbtf_op_config, + .prepare_multicast = lbtf_op_prepare_multicast, + .configure_filter = lbtf_op_configure_filter, + .bss_info_changed = lbtf_op_bss_info_changed, + .get_survey = lbtf_op_get_survey, +}; + +int lbtf_rx(struct lbtf_private *priv, struct sk_buff *skb) +{ + struct ieee80211_rx_status stats; + struct rxpd *prxpd; + int need_padding; + unsigned int flags; + struct ieee80211_hdr *hdr; + + lbtf_deb_enter(LBTF_DEB_RX); + + prxpd = (struct rxpd *) skb->data; + + memset(&stats, 0, sizeof(stats)); + if (!(prxpd->status & cpu_to_le16(MRVDRV_RXPD_STATUS_OK))) + stats.flag |= RX_FLAG_FAILED_FCS_CRC; + stats.freq = priv->cur_freq; + stats.band = IEEE80211_BAND_2GHZ; + stats.signal = prxpd->snr; + priv->noise = prxpd->nf; + /* Marvell rate index has a hole at value 4 */ + if (prxpd->rx_rate > 4) + --prxpd->rx_rate; + stats.rate_idx = prxpd->rx_rate; + skb_pull(skb, sizeof(struct rxpd)); + + hdr = (struct ieee80211_hdr *)skb->data; + flags = le32_to_cpu(*(__le32 *)(skb->data + 4)); + + need_padding = ieee80211_is_data_qos(hdr->frame_control); + need_padding ^= ieee80211_has_a4(hdr->frame_control); + need_padding ^= ieee80211_is_data_qos(hdr->frame_control) && + (*ieee80211_get_qos_ctl(hdr) & + IEEE80211_QOS_CTL_A_MSDU_PRESENT); + + if (need_padding) { + memmove(skb->data + 2, skb->data, skb->len); + skb_reserve(skb, 2); + } + + memcpy(IEEE80211_SKB_RXCB(skb), &stats, sizeof(stats)); + + lbtf_deb_rx("rx data: skb->len-sizeof(RxPd) = %d-%zd = %zd\n", + skb->len, sizeof(struct rxpd), skb->len - sizeof(struct rxpd)); + lbtf_deb_hex(LBTF_DEB_RX, "RX Data", skb->data, + min_t(unsigned int, skb->len, 100)); + + ieee80211_rx_irqsafe(priv->hw, skb); + + lbtf_deb_leave(LBTF_DEB_RX); + return 0; +} +EXPORT_SYMBOL_GPL(lbtf_rx); + +/** + * lbtf_add_card: Add and initialize the card, no fw upload yet. + * + * @card A pointer to card + * + * Returns: pointer to struct lbtf_priv. + */ +struct lbtf_private *lbtf_add_card(void *card, struct device *dmdev) +{ + struct ieee80211_hw *hw; + struct lbtf_private *priv = NULL; + + lbtf_deb_enter(LBTF_DEB_MAIN); + + hw = ieee80211_alloc_hw(sizeof(struct lbtf_private), &lbtf_ops); + if (!hw) + goto done; + + priv = hw->priv; + if (lbtf_init_adapter(priv)) + goto err_init_adapter; + + priv->hw = hw; + priv->card = card; + priv->tx_skb = NULL; + + hw->queues = 1; + hw->flags = IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING; + hw->extra_tx_headroom = sizeof(struct txpd); + memcpy(priv->channels, lbtf_channels, sizeof(lbtf_channels)); + memcpy(priv->rates, lbtf_rates, sizeof(lbtf_rates)); + priv->band.n_bitrates = ARRAY_SIZE(lbtf_rates); + priv->band.bitrates = priv->rates; + priv->band.n_channels = ARRAY_SIZE(lbtf_channels); + priv->band.channels = priv->channels; + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &priv->band; + hw->wiphy->interface_modes = + BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC); + skb_queue_head_init(&priv->bc_ps_buf); + + SET_IEEE80211_DEV(hw, dmdev); + + INIT_WORK(&priv->cmd_work, lbtf_cmd_work); + INIT_WORK(&priv->tx_work, lbtf_tx_work); + if (ieee80211_register_hw(hw)) + goto err_init_adapter; + + goto done; + +err_init_adapter: + lbtf_free_adapter(priv); + ieee80211_free_hw(hw); + priv = NULL; + +done: + lbtf_deb_leave_args(LBTF_DEB_MAIN, "priv %p", priv); + return priv; +} +EXPORT_SYMBOL_GPL(lbtf_add_card); + + +int lbtf_remove_card(struct lbtf_private *priv) +{ + struct ieee80211_hw *hw = priv->hw; + + lbtf_deb_enter(LBTF_DEB_MAIN); + + priv->surpriseremoved = 1; + del_timer(&priv->command_timer); + lbtf_free_adapter(priv); + priv->hw = NULL; + ieee80211_unregister_hw(hw); + ieee80211_free_hw(hw); + + lbtf_deb_leave(LBTF_DEB_MAIN); + return 0; +} +EXPORT_SYMBOL_GPL(lbtf_remove_card); + +void lbtf_send_tx_feedback(struct lbtf_private *priv, u8 retrycnt, u8 fail) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(priv->tx_skb); + + ieee80211_tx_info_clear_status(info); + /* + * Commented out, otherwise we never go beyond 1Mbit/s using mac80211 + * default pid rc algorithm. + * + * info->status.retry_count = MRVL_DEFAULT_RETRIES - retrycnt; + */ + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK) && !fail) + info->flags |= IEEE80211_TX_STAT_ACK; + skb_pull(priv->tx_skb, sizeof(struct txpd)); + ieee80211_tx_status_irqsafe(priv->hw, priv->tx_skb); + priv->tx_skb = NULL; + if (!priv->skb_to_tx && skb_queue_empty(&priv->bc_ps_buf)) + ieee80211_wake_queues(priv->hw); + else + queue_work(lbtf_wq, &priv->tx_work); +} +EXPORT_SYMBOL_GPL(lbtf_send_tx_feedback); + +void lbtf_bcn_sent(struct lbtf_private *priv) +{ + struct sk_buff *skb = NULL; + + if (priv->vif->type != NL80211_IFTYPE_AP) + return; + + if (skb_queue_empty(&priv->bc_ps_buf)) { + bool tx_buff_bc = false; + + while ((skb = ieee80211_get_buffered_bc(priv->hw, priv->vif))) { + skb_queue_tail(&priv->bc_ps_buf, skb); + tx_buff_bc = true; + } + if (tx_buff_bc) { + ieee80211_stop_queues(priv->hw); + queue_work(lbtf_wq, &priv->tx_work); + } + } + + skb = ieee80211_beacon_get(priv->hw, priv->vif); + + if (skb) { + lbtf_beacon_set(priv, skb); + kfree_skb(skb); + } +} +EXPORT_SYMBOL_GPL(lbtf_bcn_sent); + +static int __init lbtf_init_module(void) +{ + lbtf_deb_enter(LBTF_DEB_MAIN); + lbtf_wq = create_workqueue("libertastf"); + if (lbtf_wq == NULL) { + printk(KERN_ERR "libertastf: couldn't create workqueue\n"); + return -ENOMEM; + } + lbtf_deb_leave(LBTF_DEB_MAIN); + return 0; +} + +static void __exit lbtf_exit_module(void) +{ + lbtf_deb_enter(LBTF_DEB_MAIN); + destroy_workqueue(lbtf_wq); + lbtf_deb_leave(LBTF_DEB_MAIN); +} + +module_init(lbtf_init_module); +module_exit(lbtf_exit_module); + +MODULE_DESCRIPTION("Libertas WLAN Thinfirm Driver Library"); +MODULE_AUTHOR("Cozybit Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/mac80211_hwsim.c b/drivers/net/wireless/mac80211_hwsim.c new file mode 100644 index 0000000..8452ba8 --- /dev/null +++ b/drivers/net/wireless/mac80211_hwsim.c @@ -0,0 +1,2582 @@ +/* + * mac80211_hwsim - software simulator of 802.11 radio(s) for mac80211 + * Copyright (c) 2008, Jouni Malinen + * Copyright (c) 2011, Javier Lopez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * TODO: + * - Add TSF sync and fix IBSS beacon transmission by adding + * competition for "air time" at TBTT + * - RX filtering based on filter configuration (data->rx_filter) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mac80211_hwsim.h" + +#define WARN_QUEUE 100 +#define MAX_QUEUE 200 + +MODULE_AUTHOR("Jouni Malinen"); +MODULE_DESCRIPTION("Software simulator of 802.11 radio(s) for mac80211"); +MODULE_LICENSE("GPL"); + +static u32 wmediumd_portid; + +static int radios = 2; +module_param(radios, int, 0444); +MODULE_PARM_DESC(radios, "Number of simulated radios"); + +static int channels = 1; +module_param(channels, int, 0444); +MODULE_PARM_DESC(channels, "Number of concurrent channels"); + +static bool paged_rx = false; +module_param(paged_rx, bool, 0644); +MODULE_PARM_DESC(paged_rx, "Use paged SKBs for RX instead of linear ones"); + +static bool rctbl = false; +module_param(rctbl, bool, 0444); +MODULE_PARM_DESC(rctbl, "Handle rate control table"); + +/** + * enum hwsim_regtest - the type of regulatory tests we offer + * + * These are the different values you can use for the regtest + * module parameter. This is useful to help test world roaming + * and the driver regulatory_hint() call and combinations of these. + * If you want to do specific alpha2 regulatory domain tests simply + * use the userspace regulatory request as that will be respected as + * well without the need of this module parameter. This is designed + * only for testing the driver regulatory request, world roaming + * and all possible combinations. + * + * @HWSIM_REGTEST_DISABLED: No regulatory tests are performed, + * this is the default value. + * @HWSIM_REGTEST_DRIVER_REG_FOLLOW: Used for testing the driver regulatory + * hint, only one driver regulatory hint will be sent as such the + * secondary radios are expected to follow. + * @HWSIM_REGTEST_DRIVER_REG_ALL: Used for testing the driver regulatory + * request with all radios reporting the same regulatory domain. + * @HWSIM_REGTEST_DIFF_COUNTRY: Used for testing the drivers calling + * different regulatory domains requests. Expected behaviour is for + * an intersection to occur but each device will still use their + * respective regulatory requested domains. Subsequent radios will + * use the resulting intersection. + * @HWSIM_REGTEST_WORLD_ROAM: Used for testing the world roaming. We accomplish + * this by using a custom beacon-capable regulatory domain for the first + * radio. All other device world roam. + * @HWSIM_REGTEST_CUSTOM_WORLD: Used for testing the custom world regulatory + * domain requests. All radios will adhere to this custom world regulatory + * domain. + * @HWSIM_REGTEST_CUSTOM_WORLD_2: Used for testing 2 custom world regulatory + * domain requests. The first radio will adhere to the first custom world + * regulatory domain, the second one to the second custom world regulatory + * domain. All other devices will world roam. + * @HWSIM_REGTEST_STRICT_FOLLOW_: Used for testing strict regulatory domain + * settings, only the first radio will send a regulatory domain request + * and use strict settings. The rest of the radios are expected to follow. + * @HWSIM_REGTEST_STRICT_ALL: Used for testing strict regulatory domain + * settings. All radios will adhere to this. + * @HWSIM_REGTEST_STRICT_AND_DRIVER_REG: Used for testing strict regulatory + * domain settings, combined with secondary driver regulatory domain + * settings. The first radio will get a strict regulatory domain setting + * using the first driver regulatory request and the second radio will use + * non-strict settings using the second driver regulatory request. All + * other devices should follow the intersection created between the + * first two. + * @HWSIM_REGTEST_ALL: Used for testing every possible mix. You will need + * at least 6 radios for a complete test. We will test in this order: + * 1 - driver custom world regulatory domain + * 2 - second custom world regulatory domain + * 3 - first driver regulatory domain request + * 4 - second driver regulatory domain request + * 5 - strict regulatory domain settings using the third driver regulatory + * domain request + * 6 and on - should follow the intersection of the 3rd, 4rth and 5th radio + * regulatory requests. + */ +enum hwsim_regtest { + HWSIM_REGTEST_DISABLED = 0, + HWSIM_REGTEST_DRIVER_REG_FOLLOW = 1, + HWSIM_REGTEST_DRIVER_REG_ALL = 2, + HWSIM_REGTEST_DIFF_COUNTRY = 3, + HWSIM_REGTEST_WORLD_ROAM = 4, + HWSIM_REGTEST_CUSTOM_WORLD = 5, + HWSIM_REGTEST_CUSTOM_WORLD_2 = 6, + HWSIM_REGTEST_STRICT_FOLLOW = 7, + HWSIM_REGTEST_STRICT_ALL = 8, + HWSIM_REGTEST_STRICT_AND_DRIVER_REG = 9, + HWSIM_REGTEST_ALL = 10, +}; + +/* Set to one of the HWSIM_REGTEST_* values above */ +static int regtest = HWSIM_REGTEST_DISABLED; +module_param(regtest, int, 0444); +MODULE_PARM_DESC(regtest, "The type of regulatory test we want to run"); + +static const char *hwsim_alpha2s[] = { + "FI", + "AL", + "US", + "DE", + "JP", + "AL", +}; + +static const struct ieee80211_regdomain hwsim_world_regdom_custom_01 = { + .n_reg_rules = 4, + .alpha2 = "99", + .reg_rules = { + REG_RULE(2412-10, 2462+10, 40, 0, 20, 0), + REG_RULE(2484-10, 2484+10, 40, 0, 20, 0), + REG_RULE(5150-10, 5240+10, 40, 0, 30, 0), + REG_RULE(5745-10, 5825+10, 40, 0, 30, 0), + } +}; + +static const struct ieee80211_regdomain hwsim_world_regdom_custom_02 = { + .n_reg_rules = 2, + .alpha2 = "99", + .reg_rules = { + REG_RULE(2412-10, 2462+10, 40, 0, 20, 0), + REG_RULE(5725-10, 5850+10, 40, 0, 30, + NL80211_RRF_PASSIVE_SCAN | NL80211_RRF_NO_IBSS), + } +}; + +struct hwsim_vif_priv { + u32 magic; + u8 bssid[ETH_ALEN]; + bool assoc; + u16 aid; +}; + +#define HWSIM_VIF_MAGIC 0x69537748 + +static inline void hwsim_check_magic(struct ieee80211_vif *vif) +{ + struct hwsim_vif_priv *vp = (void *)vif->drv_priv; + WARN(vp->magic != HWSIM_VIF_MAGIC, + "Invalid VIF (%p) magic %#x, %pM, %d/%d\n", + vif, vp->magic, vif->addr, vif->type, vif->p2p); +} + +static inline void hwsim_set_magic(struct ieee80211_vif *vif) +{ + struct hwsim_vif_priv *vp = (void *)vif->drv_priv; + vp->magic = HWSIM_VIF_MAGIC; +} + +static inline void hwsim_clear_magic(struct ieee80211_vif *vif) +{ + struct hwsim_vif_priv *vp = (void *)vif->drv_priv; + vp->magic = 0; +} + +struct hwsim_sta_priv { + u32 magic; +}; + +#define HWSIM_STA_MAGIC 0x6d537749 + +static inline void hwsim_check_sta_magic(struct ieee80211_sta *sta) +{ + struct hwsim_sta_priv *sp = (void *)sta->drv_priv; + WARN_ON(sp->magic != HWSIM_STA_MAGIC); +} + +static inline void hwsim_set_sta_magic(struct ieee80211_sta *sta) +{ + struct hwsim_sta_priv *sp = (void *)sta->drv_priv; + sp->magic = HWSIM_STA_MAGIC; +} + +static inline void hwsim_clear_sta_magic(struct ieee80211_sta *sta) +{ + struct hwsim_sta_priv *sp = (void *)sta->drv_priv; + sp->magic = 0; +} + +struct hwsim_chanctx_priv { + u32 magic; +}; + +#define HWSIM_CHANCTX_MAGIC 0x6d53774a + +static inline void hwsim_check_chanctx_magic(struct ieee80211_chanctx_conf *c) +{ + struct hwsim_chanctx_priv *cp = (void *)c->drv_priv; + WARN_ON(cp->magic != HWSIM_CHANCTX_MAGIC); +} + +static inline void hwsim_set_chanctx_magic(struct ieee80211_chanctx_conf *c) +{ + struct hwsim_chanctx_priv *cp = (void *)c->drv_priv; + cp->magic = HWSIM_CHANCTX_MAGIC; +} + +static inline void hwsim_clear_chanctx_magic(struct ieee80211_chanctx_conf *c) +{ + struct hwsim_chanctx_priv *cp = (void *)c->drv_priv; + cp->magic = 0; +} + +static struct class *hwsim_class; + +static struct net_device *hwsim_mon; /* global monitor netdev */ + +#define CHAN2G(_freq) { \ + .band = IEEE80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_freq), \ + .max_power = 20, \ +} + +#define CHAN5G(_freq) { \ + .band = IEEE80211_BAND_5GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_freq), \ + .max_power = 20, \ +} + +static const struct ieee80211_channel hwsim_channels_2ghz[] = { + CHAN2G(2412), /* Channel 1 */ + CHAN2G(2417), /* Channel 2 */ + CHAN2G(2422), /* Channel 3 */ + CHAN2G(2427), /* Channel 4 */ + CHAN2G(2432), /* Channel 5 */ + CHAN2G(2437), /* Channel 6 */ + CHAN2G(2442), /* Channel 7 */ + CHAN2G(2447), /* Channel 8 */ + CHAN2G(2452), /* Channel 9 */ + CHAN2G(2457), /* Channel 10 */ + CHAN2G(2462), /* Channel 11 */ + CHAN2G(2467), /* Channel 12 */ + CHAN2G(2472), /* Channel 13 */ + CHAN2G(2484), /* Channel 14 */ +}; + +static const struct ieee80211_channel hwsim_channels_5ghz[] = { + CHAN5G(5180), /* Channel 36 */ + CHAN5G(5200), /* Channel 40 */ + CHAN5G(5220), /* Channel 44 */ + CHAN5G(5240), /* Channel 48 */ + + CHAN5G(5260), /* Channel 52 */ + CHAN5G(5280), /* Channel 56 */ + CHAN5G(5300), /* Channel 60 */ + CHAN5G(5320), /* Channel 64 */ + + CHAN5G(5500), /* Channel 100 */ + CHAN5G(5520), /* Channel 104 */ + CHAN5G(5540), /* Channel 108 */ + CHAN5G(5560), /* Channel 112 */ + CHAN5G(5580), /* Channel 116 */ + CHAN5G(5600), /* Channel 120 */ + CHAN5G(5620), /* Channel 124 */ + CHAN5G(5640), /* Channel 128 */ + CHAN5G(5660), /* Channel 132 */ + CHAN5G(5680), /* Channel 136 */ + CHAN5G(5700), /* Channel 140 */ + + CHAN5G(5745), /* Channel 149 */ + CHAN5G(5765), /* Channel 153 */ + CHAN5G(5785), /* Channel 157 */ + CHAN5G(5805), /* Channel 161 */ + CHAN5G(5825), /* Channel 165 */ +}; + +static const struct ieee80211_rate hwsim_rates[] = { + { .bitrate = 10 }, + { .bitrate = 20, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 55, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 110, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 60 }, + { .bitrate = 90 }, + { .bitrate = 120 }, + { .bitrate = 180 }, + { .bitrate = 240 }, + { .bitrate = 360 }, + { .bitrate = 480 }, + { .bitrate = 540 } +}; + +static spinlock_t hwsim_radio_lock; +static struct list_head hwsim_radios; + +struct mac80211_hwsim_data { + struct list_head list; + struct ieee80211_hw *hw; + struct device *dev; + struct ieee80211_supported_band bands[IEEE80211_NUM_BANDS]; + struct ieee80211_channel channels_2ghz[ARRAY_SIZE(hwsim_channels_2ghz)]; + struct ieee80211_channel channels_5ghz[ARRAY_SIZE(hwsim_channels_5ghz)]; + struct ieee80211_rate rates[ARRAY_SIZE(hwsim_rates)]; + + struct mac_address addresses[2]; + + struct ieee80211_channel *tmp_chan; + struct delayed_work roc_done; + struct delayed_work hw_scan; + struct cfg80211_scan_request *hw_scan_request; + struct ieee80211_vif *hw_scan_vif; + int scan_chan_idx; + + struct ieee80211_channel *channel; + u64 beacon_int /* beacon interval in us */; + unsigned int rx_filter; + bool started, idle, scanning; + struct mutex mutex; + struct tasklet_hrtimer beacon_timer; + enum ps_mode { + PS_DISABLED, PS_ENABLED, PS_AUTO_POLL, PS_MANUAL_POLL + } ps; + bool ps_poll_pending; + struct dentry *debugfs; + struct dentry *debugfs_ps; + + struct sk_buff_head pending; /* packets pending */ + /* + * Only radios in the same group can communicate together (the + * channel has to match too). Each bit represents a group. A + * radio can be in more then one group. + */ + u64 group; + struct dentry *debugfs_group; + + int power_level; + + /* difference between this hw's clock and the real clock, in usecs */ + s64 tsf_offset; + s64 bcn_delta; + /* absolute beacon transmission time. Used to cover up "tx" delay. */ + u64 abs_bcn_ts; +}; + + +struct hwsim_radiotap_hdr { + struct ieee80211_radiotap_header hdr; + __le64 rt_tsft; + u8 rt_flags; + u8 rt_rate; + __le16 rt_channel; + __le16 rt_chbitmask; +} __packed; + +/* MAC80211_HWSIM netlinf family */ +static struct genl_family hwsim_genl_family = { + .id = GENL_ID_GENERATE, + .hdrsize = 0, + .name = "MAC80211_HWSIM", + .version = 1, + .maxattr = HWSIM_ATTR_MAX, +}; + +/* MAC80211_HWSIM netlink policy */ + +static struct nla_policy hwsim_genl_policy[HWSIM_ATTR_MAX + 1] = { + [HWSIM_ATTR_ADDR_RECEIVER] = { .type = NLA_UNSPEC, + .len = 6*sizeof(u8) }, + [HWSIM_ATTR_ADDR_TRANSMITTER] = { .type = NLA_UNSPEC, + .len = 6*sizeof(u8) }, + [HWSIM_ATTR_FRAME] = { .type = NLA_BINARY, + .len = IEEE80211_MAX_DATA_LEN }, + [HWSIM_ATTR_FLAGS] = { .type = NLA_U32 }, + [HWSIM_ATTR_RX_RATE] = { .type = NLA_U32 }, + [HWSIM_ATTR_SIGNAL] = { .type = NLA_U32 }, + [HWSIM_ATTR_TX_INFO] = { .type = NLA_UNSPEC, + .len = IEEE80211_TX_MAX_RATES*sizeof( + struct hwsim_tx_rate)}, + [HWSIM_ATTR_COOKIE] = { .type = NLA_U64 }, +}; + +static netdev_tx_t hwsim_mon_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + /* TODO: allow packet injection */ + dev_kfree_skb(skb); + return NETDEV_TX_OK; +} + +static inline u64 mac80211_hwsim_get_tsf_raw(void) +{ + return ktime_to_us(ktime_get_real()); +} + +static __le64 __mac80211_hwsim_get_tsf(struct mac80211_hwsim_data *data) +{ + u64 now = mac80211_hwsim_get_tsf_raw(); + return cpu_to_le64(now + data->tsf_offset); +} + +static u64 mac80211_hwsim_get_tsf(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mac80211_hwsim_data *data = hw->priv; + return le64_to_cpu(__mac80211_hwsim_get_tsf(data)); +} + +static void mac80211_hwsim_set_tsf(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u64 tsf) +{ + struct mac80211_hwsim_data *data = hw->priv; + u64 now = mac80211_hwsim_get_tsf(hw, vif); + u32 bcn_int = data->beacon_int; + s64 delta = tsf - now; + + data->tsf_offset += delta; + /* adjust after beaconing with new timestamp at old TBTT */ + data->bcn_delta = do_div(delta, bcn_int); +} + +static void mac80211_hwsim_monitor_rx(struct ieee80211_hw *hw, + struct sk_buff *tx_skb, + struct ieee80211_channel *chan) +{ + struct mac80211_hwsim_data *data = hw->priv; + struct sk_buff *skb; + struct hwsim_radiotap_hdr *hdr; + u16 flags; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx_skb); + struct ieee80211_rate *txrate = ieee80211_get_tx_rate(hw, info); + + if (!netif_running(hwsim_mon)) + return; + + skb = skb_copy_expand(tx_skb, sizeof(*hdr), 0, GFP_ATOMIC); + if (skb == NULL) + return; + + hdr = (struct hwsim_radiotap_hdr *) skb_push(skb, sizeof(*hdr)); + hdr->hdr.it_version = PKTHDR_RADIOTAP_VERSION; + hdr->hdr.it_pad = 0; + hdr->hdr.it_len = cpu_to_le16(sizeof(*hdr)); + hdr->hdr.it_present = cpu_to_le32((1 << IEEE80211_RADIOTAP_FLAGS) | + (1 << IEEE80211_RADIOTAP_RATE) | + (1 << IEEE80211_RADIOTAP_TSFT) | + (1 << IEEE80211_RADIOTAP_CHANNEL)); + hdr->rt_tsft = __mac80211_hwsim_get_tsf(data); + hdr->rt_flags = 0; + hdr->rt_rate = txrate->bitrate / 5; + hdr->rt_channel = cpu_to_le16(chan->center_freq); + flags = IEEE80211_CHAN_2GHZ; + if (txrate->flags & IEEE80211_RATE_ERP_G) + flags |= IEEE80211_CHAN_OFDM; + else + flags |= IEEE80211_CHAN_CCK; + hdr->rt_chbitmask = cpu_to_le16(flags); + + skb->dev = hwsim_mon; + skb_set_mac_header(skb, 0); + skb->ip_summed = CHECKSUM_UNNECESSARY; + skb->pkt_type = PACKET_OTHERHOST; + skb->protocol = htons(ETH_P_802_2); + memset(skb->cb, 0, sizeof(skb->cb)); + netif_rx(skb); +} + + +static void mac80211_hwsim_monitor_ack(struct ieee80211_channel *chan, + const u8 *addr) +{ + struct sk_buff *skb; + struct hwsim_radiotap_hdr *hdr; + u16 flags; + struct ieee80211_hdr *hdr11; + + if (!netif_running(hwsim_mon)) + return; + + skb = dev_alloc_skb(100); + if (skb == NULL) + return; + + hdr = (struct hwsim_radiotap_hdr *) skb_put(skb, sizeof(*hdr)); + hdr->hdr.it_version = PKTHDR_RADIOTAP_VERSION; + hdr->hdr.it_pad = 0; + hdr->hdr.it_len = cpu_to_le16(sizeof(*hdr)); + hdr->hdr.it_present = cpu_to_le32((1 << IEEE80211_RADIOTAP_FLAGS) | + (1 << IEEE80211_RADIOTAP_CHANNEL)); + hdr->rt_flags = 0; + hdr->rt_rate = 0; + hdr->rt_channel = cpu_to_le16(chan->center_freq); + flags = IEEE80211_CHAN_2GHZ; + hdr->rt_chbitmask = cpu_to_le16(flags); + + hdr11 = (struct ieee80211_hdr *) skb_put(skb, 10); + hdr11->frame_control = cpu_to_le16(IEEE80211_FTYPE_CTL | + IEEE80211_STYPE_ACK); + hdr11->duration_id = cpu_to_le16(0); + memcpy(hdr11->addr1, addr, ETH_ALEN); + + skb->dev = hwsim_mon; + skb_set_mac_header(skb, 0); + skb->ip_summed = CHECKSUM_UNNECESSARY; + skb->pkt_type = PACKET_OTHERHOST; + skb->protocol = htons(ETH_P_802_2); + memset(skb->cb, 0, sizeof(skb->cb)); + netif_rx(skb); +} + + +static bool hwsim_ps_rx_ok(struct mac80211_hwsim_data *data, + struct sk_buff *skb) +{ + switch (data->ps) { + case PS_DISABLED: + return true; + case PS_ENABLED: + return false; + case PS_AUTO_POLL: + /* TODO: accept (some) Beacons by default and other frames only + * if pending PS-Poll has been sent */ + return true; + case PS_MANUAL_POLL: + /* Allow unicast frames to own address if there is a pending + * PS-Poll */ + if (data->ps_poll_pending && + memcmp(data->hw->wiphy->perm_addr, skb->data + 4, + ETH_ALEN) == 0) { + data->ps_poll_pending = false; + return true; + } + return false; + } + + return true; +} + + +struct mac80211_hwsim_addr_match_data { + bool ret; + const u8 *addr; +}; + +static void mac80211_hwsim_addr_iter(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct mac80211_hwsim_addr_match_data *md = data; + if (memcmp(mac, md->addr, ETH_ALEN) == 0) + md->ret = true; +} + + +static bool mac80211_hwsim_addr_match(struct mac80211_hwsim_data *data, + const u8 *addr) +{ + struct mac80211_hwsim_addr_match_data md; + + if (memcmp(addr, data->hw->wiphy->perm_addr, ETH_ALEN) == 0) + return true; + + md.ret = false; + md.addr = addr; + ieee80211_iterate_active_interfaces_atomic(data->hw, + IEEE80211_IFACE_ITER_NORMAL, + mac80211_hwsim_addr_iter, + &md); + + return md.ret; +} + +static void mac80211_hwsim_tx_frame_nl(struct ieee80211_hw *hw, + struct sk_buff *my_skb, + int dst_portid) +{ + struct sk_buff *skb; + struct mac80211_hwsim_data *data = hw->priv; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) my_skb->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(my_skb); + void *msg_head; + unsigned int hwsim_flags = 0; + int i; + struct hwsim_tx_rate tx_attempts[IEEE80211_TX_MAX_RATES]; + + if (data->ps != PS_DISABLED) + hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PM); + /* If the queue contains MAX_QUEUE skb's drop some */ + if (skb_queue_len(&data->pending) >= MAX_QUEUE) { + /* Droping until WARN_QUEUE level */ + while (skb_queue_len(&data->pending) >= WARN_QUEUE) + skb_dequeue(&data->pending); + } + + skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_ATOMIC); + if (skb == NULL) + goto nla_put_failure; + + msg_head = genlmsg_put(skb, 0, 0, &hwsim_genl_family, 0, + HWSIM_CMD_FRAME); + if (msg_head == NULL) { + printk(KERN_DEBUG "mac80211_hwsim: problem with msg_head\n"); + goto nla_put_failure; + } + + if (nla_put(skb, HWSIM_ATTR_ADDR_TRANSMITTER, + sizeof(struct mac_address), data->addresses[1].addr)) + goto nla_put_failure; + + /* We get the skb->data */ + if (nla_put(skb, HWSIM_ATTR_FRAME, my_skb->len, my_skb->data)) + goto nla_put_failure; + + /* We get the flags for this transmission, and we translate them to + wmediumd flags */ + + if (info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS) + hwsim_flags |= HWSIM_TX_CTL_REQ_TX_STATUS; + + if (info->flags & IEEE80211_TX_CTL_NO_ACK) + hwsim_flags |= HWSIM_TX_CTL_NO_ACK; + + if (nla_put_u32(skb, HWSIM_ATTR_FLAGS, hwsim_flags)) + goto nla_put_failure; + + /* We get the tx control (rate and retries) info*/ + + for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { + tx_attempts[i].idx = info->status.rates[i].idx; + tx_attempts[i].count = info->status.rates[i].count; + } + + if (nla_put(skb, HWSIM_ATTR_TX_INFO, + sizeof(struct hwsim_tx_rate)*IEEE80211_TX_MAX_RATES, + tx_attempts)) + goto nla_put_failure; + + /* We create a cookie to identify this skb */ + if (nla_put_u64(skb, HWSIM_ATTR_COOKIE, (unsigned long) my_skb)) + goto nla_put_failure; + + genlmsg_end(skb, msg_head); + genlmsg_unicast(&init_net, skb, dst_portid); + + /* Enqueue the packet */ + skb_queue_tail(&data->pending, my_skb); + return; + +nla_put_failure: + printk(KERN_DEBUG "mac80211_hwsim: error occurred in %s\n", __func__); +} + +static bool hwsim_chans_compat(struct ieee80211_channel *c1, + struct ieee80211_channel *c2) +{ + if (!c1 || !c2) + return false; + + return c1->center_freq == c2->center_freq; +} + +struct tx_iter_data { + struct ieee80211_channel *channel; + bool receive; +}; + +static void mac80211_hwsim_tx_iter(void *_data, u8 *addr, + struct ieee80211_vif *vif) +{ + struct tx_iter_data *data = _data; + + if (!vif->chanctx_conf) + return; + + if (!hwsim_chans_compat(data->channel, + rcu_dereference(vif->chanctx_conf)->def.chan)) + return; + + data->receive = true; +} + +static bool mac80211_hwsim_tx_frame_no_nl(struct ieee80211_hw *hw, + struct sk_buff *skb, + struct ieee80211_channel *chan) +{ + struct mac80211_hwsim_data *data = hw->priv, *data2; + bool ack = false; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_rx_status rx_status; + u64 now; + + memset(&rx_status, 0, sizeof(rx_status)); + rx_status.flag |= RX_FLAG_MACTIME_START; + rx_status.freq = chan->center_freq; + rx_status.band = chan->band; + if (info->control.rates[0].flags & IEEE80211_TX_RC_VHT_MCS) { + rx_status.rate_idx = + ieee80211_rate_get_vht_mcs(&info->control.rates[0]); + rx_status.vht_nss = + ieee80211_rate_get_vht_nss(&info->control.rates[0]); + rx_status.flag |= RX_FLAG_VHT; + } else { + rx_status.rate_idx = info->control.rates[0].idx; + if (info->control.rates[0].flags & IEEE80211_TX_RC_MCS) + rx_status.flag |= RX_FLAG_HT; + } + if (info->control.rates[0].flags & IEEE80211_TX_RC_40_MHZ_WIDTH) + rx_status.flag |= RX_FLAG_40MHZ; + if (info->control.rates[0].flags & IEEE80211_TX_RC_SHORT_GI) + rx_status.flag |= RX_FLAG_SHORT_GI; + /* TODO: simulate real signal strength (and optional packet loss) */ + rx_status.signal = data->power_level - 50; + + if (data->ps != PS_DISABLED) + hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PM); + + /* release the skb's source info */ + skb_orphan(skb); + skb_dst_drop(skb); + skb->mark = 0; + secpath_reset(skb); + nf_reset(skb); + + /* + * Get absolute mactime here so all HWs RX at the "same time", and + * absolute TX time for beacon mactime so the timestamp matches. + * Giving beacons a different mactime than non-beacons looks messy, but + * it helps the Toffset be exact and a ~10us mactime discrepancy + * probably doesn't really matter. + */ + if (ieee80211_is_beacon(hdr->frame_control) || + ieee80211_is_probe_resp(hdr->frame_control)) + now = data->abs_bcn_ts; + else + now = mac80211_hwsim_get_tsf_raw(); + + /* Copy skb to all enabled radios that are on the current frequency */ + spin_lock(&hwsim_radio_lock); + list_for_each_entry(data2, &hwsim_radios, list) { + struct sk_buff *nskb; + struct tx_iter_data tx_iter_data = { + .receive = false, + .channel = chan, + }; + + if (data == data2) + continue; + + if (!data2->started || (data2->idle && !data2->tmp_chan) || + !hwsim_ps_rx_ok(data2, skb)) + continue; + + if (!(data->group & data2->group)) + continue; + + if (!hwsim_chans_compat(chan, data2->tmp_chan) && + !hwsim_chans_compat(chan, data2->channel)) { + ieee80211_iterate_active_interfaces_atomic( + data2->hw, IEEE80211_IFACE_ITER_NORMAL, + mac80211_hwsim_tx_iter, &tx_iter_data); + if (!tx_iter_data.receive) + continue; + } + + /* + * reserve some space for our vendor and the normal + * radiotap header, since we're copying anyway + */ + if (skb->len < PAGE_SIZE && paged_rx) { + struct page *page = alloc_page(GFP_ATOMIC); + + if (!page) + continue; + + nskb = dev_alloc_skb(128); + if (!nskb) { + __free_page(page); + continue; + } + + memcpy(page_address(page), skb->data, skb->len); + skb_add_rx_frag(nskb, 0, page, 0, skb->len, skb->len); + } else { + nskb = skb_copy(skb, GFP_ATOMIC); + if (!nskb) + continue; + } + + if (mac80211_hwsim_addr_match(data2, hdr->addr1)) + ack = true; + + rx_status.mactime = now + data2->tsf_offset; +#if 0 + /* + * Don't enable this code by default as the OUI 00:00:00 + * is registered to Xerox so we shouldn't use it here, it + * might find its way into pcap files. + * Note that this code requires the headroom in the SKB + * that was allocated earlier. + */ + rx_status.vendor_radiotap_oui[0] = 0x00; + rx_status.vendor_radiotap_oui[1] = 0x00; + rx_status.vendor_radiotap_oui[2] = 0x00; + rx_status.vendor_radiotap_subns = 127; + /* + * Radiotap vendor namespaces can (and should) also be + * split into fields by using the standard radiotap + * presence bitmap mechanism. Use just BIT(0) here for + * the presence bitmap. + */ + rx_status.vendor_radiotap_bitmap = BIT(0); + /* We have 8 bytes of (dummy) data */ + rx_status.vendor_radiotap_len = 8; + /* For testing, also require it to be aligned */ + rx_status.vendor_radiotap_align = 8; + /* push the data */ + memcpy(skb_push(nskb, 8), "ABCDEFGH", 8); +#endif + + memcpy(IEEE80211_SKB_RXCB(nskb), &rx_status, sizeof(rx_status)); + ieee80211_rx_irqsafe(data2->hw, nskb); + } + spin_unlock(&hwsim_radio_lock); + + return ack; +} + +static void mac80211_hwsim_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct mac80211_hwsim_data *data = hw->priv; + struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb); + struct ieee80211_chanctx_conf *chanctx_conf; + struct ieee80211_channel *channel; + bool ack; + u32 _portid; + + if (WARN_ON(skb->len < 10)) { + /* Should not happen; just a sanity check for addr1 use */ + dev_kfree_skb(skb); + return; + } + + if (channels == 1) { + channel = data->channel; + } else if (txi->hw_queue == 4) { + channel = data->tmp_chan; + } else { + chanctx_conf = rcu_dereference(txi->control.vif->chanctx_conf); + if (chanctx_conf) + channel = chanctx_conf->def.chan; + else + channel = NULL; + } + + if (WARN(!channel, "TX w/o channel - queue = %d\n", txi->hw_queue)) { + dev_kfree_skb(skb); + return; + } + + if (data->idle && !data->tmp_chan) { + wiphy_debug(hw->wiphy, "Trying to TX when idle - reject\n"); + dev_kfree_skb(skb); + return; + } + + if (txi->control.vif) + hwsim_check_magic(txi->control.vif); + if (control->sta) + hwsim_check_sta_magic(control->sta); + + if (rctbl) + ieee80211_get_tx_rates(txi->control.vif, control->sta, skb, + txi->control.rates, + ARRAY_SIZE(txi->control.rates)); + + txi->rate_driver_data[0] = channel; + mac80211_hwsim_monitor_rx(hw, skb, channel); + + /* wmediumd mode check */ + _portid = ACCESS_ONCE(wmediumd_portid); + + if (_portid) + return mac80211_hwsim_tx_frame_nl(hw, skb, _portid); + + /* NO wmediumd detected, perfect medium simulation */ + ack = mac80211_hwsim_tx_frame_no_nl(hw, skb, channel); + + if (ack && skb->len >= 16) { + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; + mac80211_hwsim_monitor_ack(channel, hdr->addr2); + } + + ieee80211_tx_info_clear_status(txi); + + /* frame was transmitted at most favorable rate at first attempt */ + txi->control.rates[0].count = 1; + txi->control.rates[1].idx = -1; + + if (!(txi->flags & IEEE80211_TX_CTL_NO_ACK) && ack) + txi->flags |= IEEE80211_TX_STAT_ACK; + ieee80211_tx_status_irqsafe(hw, skb); +} + + +static int mac80211_hwsim_start(struct ieee80211_hw *hw) +{ + struct mac80211_hwsim_data *data = hw->priv; + wiphy_debug(hw->wiphy, "%s\n", __func__); + data->started = true; + return 0; +} + + +static void mac80211_hwsim_stop(struct ieee80211_hw *hw) +{ + struct mac80211_hwsim_data *data = hw->priv; + data->started = false; + tasklet_hrtimer_cancel(&data->beacon_timer); + wiphy_debug(hw->wiphy, "%s\n", __func__); +} + + +static int mac80211_hwsim_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + wiphy_debug(hw->wiphy, "%s (type=%d mac_addr=%pM)\n", + __func__, ieee80211_vif_type_p2p(vif), + vif->addr); + hwsim_set_magic(vif); + + vif->cab_queue = 0; + vif->hw_queue[IEEE80211_AC_VO] = 0; + vif->hw_queue[IEEE80211_AC_VI] = 1; + vif->hw_queue[IEEE80211_AC_BE] = 2; + vif->hw_queue[IEEE80211_AC_BK] = 3; + + return 0; +} + + +static int mac80211_hwsim_change_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum nl80211_iftype newtype, + bool newp2p) +{ + newtype = ieee80211_iftype_p2p(newtype, newp2p); + wiphy_debug(hw->wiphy, + "%s (old type=%d, new type=%d, mac_addr=%pM)\n", + __func__, ieee80211_vif_type_p2p(vif), + newtype, vif->addr); + hwsim_check_magic(vif); + + /* + * interface may change from non-AP to AP in + * which case this needs to be set up again + */ + vif->cab_queue = 0; + + return 0; +} + +static void mac80211_hwsim_remove_interface( + struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + wiphy_debug(hw->wiphy, "%s (type=%d mac_addr=%pM)\n", + __func__, ieee80211_vif_type_p2p(vif), + vif->addr); + hwsim_check_magic(vif); + hwsim_clear_magic(vif); +} + +static void mac80211_hwsim_tx_frame(struct ieee80211_hw *hw, + struct sk_buff *skb, + struct ieee80211_channel *chan) +{ + u32 _pid = ACCESS_ONCE(wmediumd_portid); + + if (rctbl) { + struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb); + ieee80211_get_tx_rates(txi->control.vif, NULL, skb, + txi->control.rates, + ARRAY_SIZE(txi->control.rates)); + } + + mac80211_hwsim_monitor_rx(hw, skb, chan); + + if (_pid) + return mac80211_hwsim_tx_frame_nl(hw, skb, _pid); + + mac80211_hwsim_tx_frame_no_nl(hw, skb, chan); + dev_kfree_skb(skb); +} + +static void mac80211_hwsim_beacon_tx(void *arg, u8 *mac, + struct ieee80211_vif *vif) +{ + struct mac80211_hwsim_data *data = arg; + struct ieee80211_hw *hw = data->hw; + struct ieee80211_tx_info *info; + struct ieee80211_rate *txrate; + struct ieee80211_mgmt *mgmt; + struct sk_buff *skb; + + hwsim_check_magic(vif); + + if (vif->type != NL80211_IFTYPE_AP && + vif->type != NL80211_IFTYPE_MESH_POINT && + vif->type != NL80211_IFTYPE_ADHOC) + return; + + skb = ieee80211_beacon_get(hw, vif); + if (skb == NULL) + return; + info = IEEE80211_SKB_CB(skb); + if (rctbl) + ieee80211_get_tx_rates(vif, NULL, skb, + info->control.rates, + ARRAY_SIZE(info->control.rates)); + + txrate = ieee80211_get_tx_rate(hw, info); + + mgmt = (struct ieee80211_mgmt *) skb->data; + /* fake header transmission time */ + data->abs_bcn_ts = mac80211_hwsim_get_tsf_raw(); + mgmt->u.beacon.timestamp = cpu_to_le64(data->abs_bcn_ts + + data->tsf_offset + + 24 * 8 * 10 / txrate->bitrate); + + mac80211_hwsim_tx_frame(hw, skb, + rcu_dereference(vif->chanctx_conf)->def.chan); +} + +static enum hrtimer_restart +mac80211_hwsim_beacon(struct hrtimer *timer) +{ + struct mac80211_hwsim_data *data = + container_of(timer, struct mac80211_hwsim_data, + beacon_timer.timer); + struct ieee80211_hw *hw = data->hw; + u64 bcn_int = data->beacon_int; + ktime_t next_bcn; + + if (!data->started) + goto out; + + ieee80211_iterate_active_interfaces_atomic( + hw, IEEE80211_IFACE_ITER_NORMAL, + mac80211_hwsim_beacon_tx, data); + + /* beacon at new TBTT + beacon interval */ + if (data->bcn_delta) { + bcn_int -= data->bcn_delta; + data->bcn_delta = 0; + } + + next_bcn = ktime_add(hrtimer_get_expires(timer), + ns_to_ktime(bcn_int * 1000)); + tasklet_hrtimer_start(&data->beacon_timer, next_bcn, HRTIMER_MODE_ABS); +out: + return HRTIMER_NORESTART; +} + +static const char * const hwsim_chanwidths[] = { + [NL80211_CHAN_WIDTH_20_NOHT] = "noht", + [NL80211_CHAN_WIDTH_20] = "ht20", + [NL80211_CHAN_WIDTH_40] = "ht40", + [NL80211_CHAN_WIDTH_80] = "vht80", + [NL80211_CHAN_WIDTH_80P80] = "vht80p80", + [NL80211_CHAN_WIDTH_160] = "vht160", +}; + +static int mac80211_hwsim_config(struct ieee80211_hw *hw, u32 changed) +{ + struct mac80211_hwsim_data *data = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + static const char *smps_modes[IEEE80211_SMPS_NUM_MODES] = { + [IEEE80211_SMPS_AUTOMATIC] = "auto", + [IEEE80211_SMPS_OFF] = "off", + [IEEE80211_SMPS_STATIC] = "static", + [IEEE80211_SMPS_DYNAMIC] = "dynamic", + }; + + if (conf->chandef.chan) + wiphy_debug(hw->wiphy, + "%s (freq=%d(%d - %d)/%s idle=%d ps=%d smps=%s)\n", + __func__, + conf->chandef.chan->center_freq, + conf->chandef.center_freq1, + conf->chandef.center_freq2, + hwsim_chanwidths[conf->chandef.width], + !!(conf->flags & IEEE80211_CONF_IDLE), + !!(conf->flags & IEEE80211_CONF_PS), + smps_modes[conf->smps_mode]); + else + wiphy_debug(hw->wiphy, + "%s (freq=0 idle=%d ps=%d smps=%s)\n", + __func__, + !!(conf->flags & IEEE80211_CONF_IDLE), + !!(conf->flags & IEEE80211_CONF_PS), + smps_modes[conf->smps_mode]); + + data->idle = !!(conf->flags & IEEE80211_CONF_IDLE); + + data->channel = conf->chandef.chan; + + WARN_ON(data->channel && channels > 1); + + data->power_level = conf->power_level; + if (!data->started || !data->beacon_int) + tasklet_hrtimer_cancel(&data->beacon_timer); + else if (!hrtimer_is_queued(&data->beacon_timer.timer)) { + u64 tsf = mac80211_hwsim_get_tsf(hw, NULL); + u32 bcn_int = data->beacon_int; + u64 until_tbtt = bcn_int - do_div(tsf, bcn_int); + + tasklet_hrtimer_start(&data->beacon_timer, + ns_to_ktime(until_tbtt * 1000), + HRTIMER_MODE_REL); + } + + return 0; +} + + +static void mac80211_hwsim_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags,u64 multicast) +{ + struct mac80211_hwsim_data *data = hw->priv; + + wiphy_debug(hw->wiphy, "%s\n", __func__); + + data->rx_filter = 0; + if (*total_flags & FIF_PROMISC_IN_BSS) + data->rx_filter |= FIF_PROMISC_IN_BSS; + if (*total_flags & FIF_ALLMULTI) + data->rx_filter |= FIF_ALLMULTI; + + *total_flags = data->rx_filter; +} + +static void mac80211_hwsim_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u32 changed) +{ + struct hwsim_vif_priv *vp = (void *)vif->drv_priv; + struct mac80211_hwsim_data *data = hw->priv; + + hwsim_check_magic(vif); + + wiphy_debug(hw->wiphy, "%s(changed=0x%x)\n", __func__, changed); + + if (changed & BSS_CHANGED_BSSID) { + wiphy_debug(hw->wiphy, "%s: BSSID changed: %pM\n", + __func__, info->bssid); + memcpy(vp->bssid, info->bssid, ETH_ALEN); + } + + if (changed & BSS_CHANGED_ASSOC) { + wiphy_debug(hw->wiphy, " ASSOC: assoc=%d aid=%d\n", + info->assoc, info->aid); + vp->assoc = info->assoc; + vp->aid = info->aid; + } + + if (changed & BSS_CHANGED_BEACON_INT) { + wiphy_debug(hw->wiphy, " BCNINT: %d\n", info->beacon_int); + data->beacon_int = info->beacon_int * 1024; + } + + if (changed & BSS_CHANGED_BEACON_ENABLED) { + wiphy_debug(hw->wiphy, " BCN EN: %d\n", info->enable_beacon); + if (data->started && + !hrtimer_is_queued(&data->beacon_timer.timer) && + info->enable_beacon) { + u64 tsf, until_tbtt; + u32 bcn_int; + if (WARN_ON(!data->beacon_int)) + data->beacon_int = 1000 * 1024; + tsf = mac80211_hwsim_get_tsf(hw, vif); + bcn_int = data->beacon_int; + until_tbtt = bcn_int - do_div(tsf, bcn_int); + tasklet_hrtimer_start(&data->beacon_timer, + ns_to_ktime(until_tbtt * 1000), + HRTIMER_MODE_REL); + } else if (!info->enable_beacon) + tasklet_hrtimer_cancel(&data->beacon_timer); + } + + if (changed & BSS_CHANGED_ERP_CTS_PROT) { + wiphy_debug(hw->wiphy, " ERP_CTS_PROT: %d\n", + info->use_cts_prot); + } + + if (changed & BSS_CHANGED_ERP_PREAMBLE) { + wiphy_debug(hw->wiphy, " ERP_PREAMBLE: %d\n", + info->use_short_preamble); + } + + if (changed & BSS_CHANGED_ERP_SLOT) { + wiphy_debug(hw->wiphy, " ERP_SLOT: %d\n", info->use_short_slot); + } + + if (changed & BSS_CHANGED_HT) { + wiphy_debug(hw->wiphy, " HT: op_mode=0x%x\n", + info->ht_operation_mode); + } + + if (changed & BSS_CHANGED_BASIC_RATES) { + wiphy_debug(hw->wiphy, " BASIC_RATES: 0x%llx\n", + (unsigned long long) info->basic_rates); + } + + if (changed & BSS_CHANGED_TXPOWER) + wiphy_debug(hw->wiphy, " TX Power: %d dBm\n", info->txpower); +} + +static int mac80211_hwsim_sta_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + hwsim_check_magic(vif); + hwsim_set_sta_magic(sta); + + return 0; +} + +static int mac80211_hwsim_sta_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + hwsim_check_magic(vif); + hwsim_clear_sta_magic(sta); + + return 0; +} + +static void mac80211_hwsim_sta_notify(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum sta_notify_cmd cmd, + struct ieee80211_sta *sta) +{ + hwsim_check_magic(vif); + + switch (cmd) { + case STA_NOTIFY_SLEEP: + case STA_NOTIFY_AWAKE: + /* TODO: make good use of these flags */ + break; + default: + WARN(1, "Invalid sta notify: %d\n", cmd); + break; + } +} + +static int mac80211_hwsim_set_tim(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + bool set) +{ + hwsim_check_sta_magic(sta); + return 0; +} + +static int mac80211_hwsim_conf_tx( + struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + wiphy_debug(hw->wiphy, + "%s (queue=%d txop=%d cw_min=%d cw_max=%d aifs=%d)\n", + __func__, queue, + params->txop, params->cw_min, + params->cw_max, params->aifs); + return 0; +} + +static int mac80211_hwsim_get_survey( + struct ieee80211_hw *hw, int idx, + struct survey_info *survey) +{ + struct ieee80211_conf *conf = &hw->conf; + + wiphy_debug(hw->wiphy, "%s (idx=%d)\n", __func__, idx); + + if (idx != 0) + return -ENOENT; + + /* Current channel */ + survey->channel = conf->chandef.chan; + + /* + * Magically conjured noise level --- this is only ok for simulated hardware. + * + * A real driver which cannot determine the real channel noise MUST NOT + * report any noise, especially not a magically conjured one :-) + */ + survey->filled = SURVEY_INFO_NOISE_DBM; + survey->noise = -92; + + return 0; +} + +#ifdef CPTCFG_NL80211_TESTMODE +/* + * This section contains example code for using netlink + * attributes with the testmode command in nl80211. + */ + +/* These enums need to be kept in sync with userspace */ +enum hwsim_testmode_attr { + __HWSIM_TM_ATTR_INVALID = 0, + HWSIM_TM_ATTR_CMD = 1, + HWSIM_TM_ATTR_PS = 2, + + /* keep last */ + __HWSIM_TM_ATTR_AFTER_LAST, + HWSIM_TM_ATTR_MAX = __HWSIM_TM_ATTR_AFTER_LAST - 1 +}; + +enum hwsim_testmode_cmd { + HWSIM_TM_CMD_SET_PS = 0, + HWSIM_TM_CMD_GET_PS = 1, + HWSIM_TM_CMD_STOP_QUEUES = 2, + HWSIM_TM_CMD_WAKE_QUEUES = 3, +}; + +static const struct nla_policy hwsim_testmode_policy[HWSIM_TM_ATTR_MAX + 1] = { + [HWSIM_TM_ATTR_CMD] = { .type = NLA_U32 }, + [HWSIM_TM_ATTR_PS] = { .type = NLA_U32 }, +}; + +static int hwsim_fops_ps_write(void *dat, u64 val); + +static int mac80211_hwsim_testmode_cmd(struct ieee80211_hw *hw, + void *data, int len) +{ + struct mac80211_hwsim_data *hwsim = hw->priv; + struct nlattr *tb[HWSIM_TM_ATTR_MAX + 1]; + struct sk_buff *skb; + int err, ps; + + err = nla_parse(tb, HWSIM_TM_ATTR_MAX, data, len, + hwsim_testmode_policy); + if (err) + return err; + + if (!tb[HWSIM_TM_ATTR_CMD]) + return -EINVAL; + + switch (nla_get_u32(tb[HWSIM_TM_ATTR_CMD])) { + case HWSIM_TM_CMD_SET_PS: + if (!tb[HWSIM_TM_ATTR_PS]) + return -EINVAL; + ps = nla_get_u32(tb[HWSIM_TM_ATTR_PS]); + return hwsim_fops_ps_write(hwsim, ps); + case HWSIM_TM_CMD_GET_PS: + skb = cfg80211_testmode_alloc_reply_skb(hw->wiphy, + nla_total_size(sizeof(u32))); + if (!skb) + return -ENOMEM; + if (nla_put_u32(skb, HWSIM_TM_ATTR_PS, hwsim->ps)) + goto nla_put_failure; + return cfg80211_testmode_reply(skb); + case HWSIM_TM_CMD_STOP_QUEUES: + ieee80211_stop_queues(hw); + return 0; + case HWSIM_TM_CMD_WAKE_QUEUES: + ieee80211_wake_queues(hw); + return 0; + default: + return -EOPNOTSUPP; + } + + nla_put_failure: + kfree_skb(skb); + return -ENOBUFS; +} +#endif + +static int mac80211_hwsim_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, u16 *ssn, + u8 buf_size) +{ + switch (action) { + case IEEE80211_AMPDU_TX_START: + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + break; + case IEEE80211_AMPDU_RX_START: + case IEEE80211_AMPDU_RX_STOP: + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static void mac80211_hwsim_flush(struct ieee80211_hw *hw, u32 queues, bool drop) +{ + /* Not implemented, queues only on kernel side */ +} + +static void hw_scan_work(struct work_struct *work) +{ + struct mac80211_hwsim_data *hwsim = + container_of(work, struct mac80211_hwsim_data, hw_scan.work); + struct cfg80211_scan_request *req = hwsim->hw_scan_request; + int dwell, i; + + mutex_lock(&hwsim->mutex); + if (hwsim->scan_chan_idx >= req->n_channels) { + wiphy_debug(hwsim->hw->wiphy, "hw scan complete\n"); + ieee80211_scan_completed(hwsim->hw, false); + hwsim->hw_scan_request = NULL; + hwsim->hw_scan_vif = NULL; + hwsim->tmp_chan = NULL; + mutex_unlock(&hwsim->mutex); + return; + } + + wiphy_debug(hwsim->hw->wiphy, "hw scan %d MHz\n", + req->channels[hwsim->scan_chan_idx]->center_freq); + + hwsim->tmp_chan = req->channels[hwsim->scan_chan_idx]; + if (hwsim->tmp_chan->flags & IEEE80211_CHAN_PASSIVE_SCAN || + !req->n_ssids) { + dwell = 120; + } else { + dwell = 30; + /* send probes */ + for (i = 0; i < req->n_ssids; i++) { + struct sk_buff *probe; + + probe = ieee80211_probereq_get(hwsim->hw, + hwsim->hw_scan_vif, + req->ssids[i].ssid, + req->ssids[i].ssid_len, + req->ie_len); + if (!probe) + continue; + + if (req->ie_len) + memcpy(skb_put(probe, req->ie_len), req->ie, + req->ie_len); + + local_bh_disable(); + mac80211_hwsim_tx_frame(hwsim->hw, probe, + hwsim->tmp_chan); + local_bh_enable(); + } + } + ieee80211_queue_delayed_work(hwsim->hw, &hwsim->hw_scan, + msecs_to_jiffies(dwell)); + hwsim->scan_chan_idx++; + mutex_unlock(&hwsim->mutex); +} + +static int mac80211_hwsim_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_scan_request *req) +{ + struct mac80211_hwsim_data *hwsim = hw->priv; + + mutex_lock(&hwsim->mutex); + if (WARN_ON(hwsim->tmp_chan || hwsim->hw_scan_request)) { + mutex_unlock(&hwsim->mutex); + return -EBUSY; + } + hwsim->hw_scan_request = req; + hwsim->hw_scan_vif = vif; + hwsim->scan_chan_idx = 0; + mutex_unlock(&hwsim->mutex); + + wiphy_debug(hw->wiphy, "hwsim hw_scan request\n"); + + ieee80211_queue_delayed_work(hwsim->hw, &hwsim->hw_scan, 0); + + return 0; +} + +static void mac80211_hwsim_cancel_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mac80211_hwsim_data *hwsim = hw->priv; + + wiphy_debug(hw->wiphy, "hwsim cancel_hw_scan\n"); + + cancel_delayed_work_sync(&hwsim->hw_scan); + + mutex_lock(&hwsim->mutex); + ieee80211_scan_completed(hwsim->hw, true); + hwsim->tmp_chan = NULL; + hwsim->hw_scan_request = NULL; + hwsim->hw_scan_vif = NULL; + mutex_unlock(&hwsim->mutex); +} + +static void mac80211_hwsim_sw_scan(struct ieee80211_hw *hw) +{ + struct mac80211_hwsim_data *hwsim = hw->priv; + + mutex_lock(&hwsim->mutex); + + if (hwsim->scanning) { + printk(KERN_DEBUG "two hwsim sw_scans detected!\n"); + goto out; + } + + printk(KERN_DEBUG "hwsim sw_scan request, prepping stuff\n"); + hwsim->scanning = true; + +out: + mutex_unlock(&hwsim->mutex); +} + +static void mac80211_hwsim_sw_scan_complete(struct ieee80211_hw *hw) +{ + struct mac80211_hwsim_data *hwsim = hw->priv; + + mutex_lock(&hwsim->mutex); + + printk(KERN_DEBUG "hwsim sw_scan_complete\n"); + hwsim->scanning = false; + + mutex_unlock(&hwsim->mutex); +} + +static void hw_roc_done(struct work_struct *work) +{ + struct mac80211_hwsim_data *hwsim = + container_of(work, struct mac80211_hwsim_data, roc_done.work); + + mutex_lock(&hwsim->mutex); + ieee80211_remain_on_channel_expired(hwsim->hw); + hwsim->tmp_chan = NULL; + mutex_unlock(&hwsim->mutex); + + wiphy_debug(hwsim->hw->wiphy, "hwsim ROC expired\n"); +} + +static int mac80211_hwsim_roc(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel *chan, + int duration, + enum ieee80211_roc_type type) +{ + struct mac80211_hwsim_data *hwsim = hw->priv; + + mutex_lock(&hwsim->mutex); + if (WARN_ON(hwsim->tmp_chan || hwsim->hw_scan_request)) { + mutex_unlock(&hwsim->mutex); + return -EBUSY; + } + + hwsim->tmp_chan = chan; + mutex_unlock(&hwsim->mutex); + + wiphy_debug(hw->wiphy, "hwsim ROC (%d MHz, %d ms)\n", + chan->center_freq, duration); + + ieee80211_ready_on_channel(hw); + + ieee80211_queue_delayed_work(hw, &hwsim->roc_done, + msecs_to_jiffies(duration)); + return 0; +} + +static int mac80211_hwsim_croc(struct ieee80211_hw *hw) +{ + struct mac80211_hwsim_data *hwsim = hw->priv; + + cancel_delayed_work_sync(&hwsim->roc_done); + + mutex_lock(&hwsim->mutex); + hwsim->tmp_chan = NULL; + mutex_unlock(&hwsim->mutex); + + wiphy_debug(hw->wiphy, "hwsim ROC canceled\n"); + + return 0; +} + +static int mac80211_hwsim_add_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx) +{ + hwsim_set_chanctx_magic(ctx); + wiphy_debug(hw->wiphy, + "add channel context control: %d MHz/width: %d/cfreqs:%d/%d MHz\n", + ctx->def.chan->center_freq, ctx->def.width, + ctx->def.center_freq1, ctx->def.center_freq2); + return 0; +} + +static void mac80211_hwsim_remove_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx) +{ + wiphy_debug(hw->wiphy, + "remove channel context control: %d MHz/width: %d/cfreqs:%d/%d MHz\n", + ctx->def.chan->center_freq, ctx->def.width, + ctx->def.center_freq1, ctx->def.center_freq2); + hwsim_check_chanctx_magic(ctx); + hwsim_clear_chanctx_magic(ctx); +} + +static void mac80211_hwsim_change_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx, + u32 changed) +{ + hwsim_check_chanctx_magic(ctx); + wiphy_debug(hw->wiphy, + "change channel context control: %d MHz/width: %d/cfreqs:%d/%d MHz\n", + ctx->def.chan->center_freq, ctx->def.width, + ctx->def.center_freq1, ctx->def.center_freq2); +} + +static int mac80211_hwsim_assign_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *ctx) +{ + hwsim_check_magic(vif); + hwsim_check_chanctx_magic(ctx); + + return 0; +} + +static void mac80211_hwsim_unassign_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *ctx) +{ + hwsim_check_magic(vif); + hwsim_check_chanctx_magic(ctx); +} + +static struct ieee80211_ops mac80211_hwsim_ops = +{ + .tx = mac80211_hwsim_tx, + .start = mac80211_hwsim_start, + .stop = mac80211_hwsim_stop, + .add_interface = mac80211_hwsim_add_interface, + .change_interface = mac80211_hwsim_change_interface, + .remove_interface = mac80211_hwsim_remove_interface, + .config = mac80211_hwsim_config, + .configure_filter = mac80211_hwsim_configure_filter, + .bss_info_changed = mac80211_hwsim_bss_info_changed, + .sta_add = mac80211_hwsim_sta_add, + .sta_remove = mac80211_hwsim_sta_remove, + .sta_notify = mac80211_hwsim_sta_notify, + .set_tim = mac80211_hwsim_set_tim, + .conf_tx = mac80211_hwsim_conf_tx, + .get_survey = mac80211_hwsim_get_survey, + CFG80211_TESTMODE_CMD(mac80211_hwsim_testmode_cmd) + .ampdu_action = mac80211_hwsim_ampdu_action, + .sw_scan_start = mac80211_hwsim_sw_scan, + .sw_scan_complete = mac80211_hwsim_sw_scan_complete, + .flush = mac80211_hwsim_flush, + .get_tsf = mac80211_hwsim_get_tsf, + .set_tsf = mac80211_hwsim_set_tsf, +}; + + +static void mac80211_hwsim_free(void) +{ + struct list_head tmplist, *i, *tmp; + struct mac80211_hwsim_data *data, *tmpdata; + + INIT_LIST_HEAD(&tmplist); + + spin_lock_bh(&hwsim_radio_lock); + list_for_each_safe(i, tmp, &hwsim_radios) + list_move(i, &tmplist); + spin_unlock_bh(&hwsim_radio_lock); + + list_for_each_entry_safe(data, tmpdata, &tmplist, list) { + debugfs_remove(data->debugfs_group); + debugfs_remove(data->debugfs_ps); + debugfs_remove(data->debugfs); + ieee80211_unregister_hw(data->hw); + device_release_driver(data->dev); + device_unregister(data->dev); + ieee80211_free_hw(data->hw); + } + class_destroy(hwsim_class); +} + +static struct platform_driver mac80211_hwsim_driver = { + .driver = { + .name = "mac80211_hwsim", + .owner = THIS_MODULE, + }, +}; + +static const struct net_device_ops hwsim_netdev_ops = { + .ndo_start_xmit = hwsim_mon_xmit, + .ndo_change_mtu = eth_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static void hwsim_mon_setup(struct net_device *dev) +{ + netdev_attach_ops(dev, &hwsim_netdev_ops); + dev->destructor = free_netdev; + ether_setup(dev); + dev->tx_queue_len = 0; + dev->type = ARPHRD_IEEE80211_RADIOTAP; + memset(dev->dev_addr, 0, ETH_ALEN); + dev->dev_addr[0] = 0x12; +} + + +static void hwsim_send_ps_poll(void *dat, u8 *mac, struct ieee80211_vif *vif) +{ + struct mac80211_hwsim_data *data = dat; + struct hwsim_vif_priv *vp = (void *)vif->drv_priv; + struct sk_buff *skb; + struct ieee80211_pspoll *pspoll; + + if (!vp->assoc) + return; + + wiphy_debug(data->hw->wiphy, + "%s: send PS-Poll to %pM for aid %d\n", + __func__, vp->bssid, vp->aid); + + skb = dev_alloc_skb(sizeof(*pspoll)); + if (!skb) + return; + pspoll = (void *) skb_put(skb, sizeof(*pspoll)); + pspoll->frame_control = cpu_to_le16(IEEE80211_FTYPE_CTL | + IEEE80211_STYPE_PSPOLL | + IEEE80211_FCTL_PM); + pspoll->aid = cpu_to_le16(0xc000 | vp->aid); + memcpy(pspoll->bssid, vp->bssid, ETH_ALEN); + memcpy(pspoll->ta, mac, ETH_ALEN); + + rcu_read_lock(); + mac80211_hwsim_tx_frame(data->hw, skb, + rcu_dereference(vif->chanctx_conf)->def.chan); + rcu_read_unlock(); +} + +static void hwsim_send_nullfunc(struct mac80211_hwsim_data *data, u8 *mac, + struct ieee80211_vif *vif, int ps) +{ + struct hwsim_vif_priv *vp = (void *)vif->drv_priv; + struct sk_buff *skb; + struct ieee80211_hdr *hdr; + + if (!vp->assoc) + return; + + wiphy_debug(data->hw->wiphy, + "%s: send data::nullfunc to %pM ps=%d\n", + __func__, vp->bssid, ps); + + skb = dev_alloc_skb(sizeof(*hdr)); + if (!skb) + return; + hdr = (void *) skb_put(skb, sizeof(*hdr) - ETH_ALEN); + hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | + IEEE80211_STYPE_NULLFUNC | + (ps ? IEEE80211_FCTL_PM : 0)); + hdr->duration_id = cpu_to_le16(0); + memcpy(hdr->addr1, vp->bssid, ETH_ALEN); + memcpy(hdr->addr2, mac, ETH_ALEN); + memcpy(hdr->addr3, vp->bssid, ETH_ALEN); + + rcu_read_lock(); + mac80211_hwsim_tx_frame(data->hw, skb, + rcu_dereference(vif->chanctx_conf)->def.chan); + rcu_read_unlock(); +} + + +static void hwsim_send_nullfunc_ps(void *dat, u8 *mac, + struct ieee80211_vif *vif) +{ + struct mac80211_hwsim_data *data = dat; + hwsim_send_nullfunc(data, mac, vif, 1); +} + + +static void hwsim_send_nullfunc_no_ps(void *dat, u8 *mac, + struct ieee80211_vif *vif) +{ + struct mac80211_hwsim_data *data = dat; + hwsim_send_nullfunc(data, mac, vif, 0); +} + + +static int hwsim_fops_ps_read(void *dat, u64 *val) +{ + struct mac80211_hwsim_data *data = dat; + *val = data->ps; + return 0; +} + +static int hwsim_fops_ps_write(void *dat, u64 val) +{ + struct mac80211_hwsim_data *data = dat; + enum ps_mode old_ps; + + if (val != PS_DISABLED && val != PS_ENABLED && val != PS_AUTO_POLL && + val != PS_MANUAL_POLL) + return -EINVAL; + + old_ps = data->ps; + data->ps = val; + + if (val == PS_MANUAL_POLL) { + ieee80211_iterate_active_interfaces(data->hw, + IEEE80211_IFACE_ITER_NORMAL, + hwsim_send_ps_poll, data); + data->ps_poll_pending = true; + } else if (old_ps == PS_DISABLED && val != PS_DISABLED) { + ieee80211_iterate_active_interfaces(data->hw, + IEEE80211_IFACE_ITER_NORMAL, + hwsim_send_nullfunc_ps, + data); + } else if (old_ps != PS_DISABLED && val == PS_DISABLED) { + ieee80211_iterate_active_interfaces(data->hw, + IEEE80211_IFACE_ITER_NORMAL, + hwsim_send_nullfunc_no_ps, + data); + } + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(hwsim_fops_ps, hwsim_fops_ps_read, hwsim_fops_ps_write, + "%llu\n"); + + +static int hwsim_fops_group_read(void *dat, u64 *val) +{ + struct mac80211_hwsim_data *data = dat; + *val = data->group; + return 0; +} + +static int hwsim_fops_group_write(void *dat, u64 val) +{ + struct mac80211_hwsim_data *data = dat; + data->group = val; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(hwsim_fops_group, + hwsim_fops_group_read, hwsim_fops_group_write, + "%llx\n"); + +static struct mac80211_hwsim_data *get_hwsim_data_ref_from_addr( + struct mac_address *addr) +{ + struct mac80211_hwsim_data *data; + bool _found = false; + + spin_lock_bh(&hwsim_radio_lock); + list_for_each_entry(data, &hwsim_radios, list) { + if (memcmp(data->addresses[1].addr, addr, + sizeof(struct mac_address)) == 0) { + _found = true; + break; + } + } + spin_unlock_bh(&hwsim_radio_lock); + + if (!_found) + return NULL; + + return data; +} + +static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2, + struct genl_info *info) +{ + + struct ieee80211_hdr *hdr; + struct mac80211_hwsim_data *data2; + struct ieee80211_tx_info *txi; + struct hwsim_tx_rate *tx_attempts; + unsigned long ret_skb_ptr; + struct sk_buff *skb, *tmp; + struct mac_address *src; + unsigned int hwsim_flags; + + int i; + bool found = false; + + if (!info->attrs[HWSIM_ATTR_ADDR_TRANSMITTER] || + !info->attrs[HWSIM_ATTR_FLAGS] || + !info->attrs[HWSIM_ATTR_COOKIE] || + !info->attrs[HWSIM_ATTR_TX_INFO]) + goto out; + + src = (struct mac_address *)nla_data( + info->attrs[HWSIM_ATTR_ADDR_TRANSMITTER]); + hwsim_flags = nla_get_u32(info->attrs[HWSIM_ATTR_FLAGS]); + + ret_skb_ptr = nla_get_u64(info->attrs[HWSIM_ATTR_COOKIE]); + + data2 = get_hwsim_data_ref_from_addr(src); + + if (data2 == NULL) + goto out; + + /* look for the skb matching the cookie passed back from user */ + skb_queue_walk_safe(&data2->pending, skb, tmp) { + if ((unsigned long)skb == ret_skb_ptr) { + skb_unlink(skb, &data2->pending); + found = true; + break; + } + } + + /* not found */ + if (!found) + goto out; + + /* Tx info received because the frame was broadcasted on user space, + so we get all the necessary info: tx attempts and skb control buff */ + + tx_attempts = (struct hwsim_tx_rate *)nla_data( + info->attrs[HWSIM_ATTR_TX_INFO]); + + /* now send back TX status */ + txi = IEEE80211_SKB_CB(skb); + + ieee80211_tx_info_clear_status(txi); + + for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { + txi->status.rates[i].idx = tx_attempts[i].idx; + txi->status.rates[i].count = tx_attempts[i].count; + /*txi->status.rates[i].flags = 0;*/ + } + + txi->status.ack_signal = nla_get_u32(info->attrs[HWSIM_ATTR_SIGNAL]); + + if (!(hwsim_flags & HWSIM_TX_CTL_NO_ACK) && + (hwsim_flags & HWSIM_TX_STAT_ACK)) { + if (skb->len >= 16) { + hdr = (struct ieee80211_hdr *) skb->data; + mac80211_hwsim_monitor_ack(txi->rate_driver_data[0], + hdr->addr2); + } + txi->flags |= IEEE80211_TX_STAT_ACK; + } + ieee80211_tx_status_irqsafe(data2->hw, skb); + return 0; +out: + return -EINVAL; + +} + +static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, + struct genl_info *info) +{ + + struct mac80211_hwsim_data *data2; + struct ieee80211_rx_status rx_status; + struct mac_address *dst; + int frame_data_len; + char *frame_data; + struct sk_buff *skb = NULL; + + if (!info->attrs[HWSIM_ATTR_ADDR_RECEIVER] || + !info->attrs[HWSIM_ATTR_FRAME] || + !info->attrs[HWSIM_ATTR_RX_RATE] || + !info->attrs[HWSIM_ATTR_SIGNAL]) + goto out; + + dst = (struct mac_address *)nla_data( + info->attrs[HWSIM_ATTR_ADDR_RECEIVER]); + + frame_data_len = nla_len(info->attrs[HWSIM_ATTR_FRAME]); + frame_data = (char *)nla_data(info->attrs[HWSIM_ATTR_FRAME]); + + /* Allocate new skb here */ + skb = alloc_skb(frame_data_len, GFP_KERNEL); + if (skb == NULL) + goto err; + + if (frame_data_len <= IEEE80211_MAX_DATA_LEN) { + /* Copy the data */ + memcpy(skb_put(skb, frame_data_len), frame_data, + frame_data_len); + } else + goto err; + + data2 = get_hwsim_data_ref_from_addr(dst); + + if (data2 == NULL) + goto out; + + /* check if radio is configured properly */ + + if (data2->idle || !data2->started) + goto out; + + /*A frame is received from user space*/ + memset(&rx_status, 0, sizeof(rx_status)); + rx_status.freq = data2->channel->center_freq; + rx_status.band = data2->channel->band; + rx_status.rate_idx = nla_get_u32(info->attrs[HWSIM_ATTR_RX_RATE]); + rx_status.signal = nla_get_u32(info->attrs[HWSIM_ATTR_SIGNAL]); + + memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); + ieee80211_rx_irqsafe(data2->hw, skb); + + return 0; +err: + printk(KERN_DEBUG "mac80211_hwsim: error occurred in %s\n", __func__); + goto out; +out: + dev_kfree_skb(skb); + return -EINVAL; +} + +static int hwsim_register_received_nl(struct sk_buff *skb_2, + struct genl_info *info) +{ + if (info == NULL) + goto out; + + wmediumd_portid = genl_info_snd_portid(info); + + printk(KERN_DEBUG "mac80211_hwsim: received a REGISTER, " + "switching to wmediumd mode with pid %d\n", genl_info_snd_portid(info)); + + return 0; +out: + printk(KERN_DEBUG "mac80211_hwsim: error occurred in %s\n", __func__); + return -EINVAL; +} + +/* Generic Netlink operations array */ +static struct genl_ops hwsim_ops[] = { + { + .cmd = HWSIM_CMD_REGISTER, + .policy = hwsim_genl_policy, + .doit = hwsim_register_received_nl, + .flags = GENL_ADMIN_PERM, + }, + { + .cmd = HWSIM_CMD_FRAME, + .policy = hwsim_genl_policy, + .doit = hwsim_cloned_frame_received_nl, + }, + { + .cmd = HWSIM_CMD_TX_INFO_FRAME, + .policy = hwsim_genl_policy, + .doit = hwsim_tx_info_frame_received_nl, + }, +}; + +static int mac80211_hwsim_netlink_notify(struct notifier_block *nb, + unsigned long state, + void *_notify) +{ + struct netlink_notify *notify = _notify; + + if (state != NETLINK_URELEASE) + return NOTIFY_DONE; + + if (netlink_notify_portid(notify) == wmediumd_portid) { + printk(KERN_INFO "mac80211_hwsim: wmediumd released netlink" + " socket, switching to perfect channel medium\n"); + wmediumd_portid = 0; + } + return NOTIFY_DONE; + +} + +static struct notifier_block hwsim_netlink_notifier = { + .notifier_call = mac80211_hwsim_netlink_notify, +}; + +static int hwsim_init_netlink(void) +{ + int rc; + + /* userspace test API hasn't been adjusted for multi-channel */ + if (channels > 1) + return 0; + + printk(KERN_INFO "mac80211_hwsim: initializing netlink\n"); + + rc = genl_register_family_with_ops(&hwsim_genl_family, + hwsim_ops, ARRAY_SIZE(hwsim_ops)); + if (rc) + goto failure; + + rc = netlink_register_notifier(&hwsim_netlink_notifier); + if (rc) + goto failure; + + return 0; + +failure: + printk(KERN_DEBUG "mac80211_hwsim: error occurred in %s\n", __func__); + return -EINVAL; +} + +static void hwsim_exit_netlink(void) +{ + int ret; + + /* userspace test API hasn't been adjusted for multi-channel */ + if (channels > 1) + return; + + printk(KERN_INFO "mac80211_hwsim: closing netlink\n"); + /* unregister the notifier */ + netlink_unregister_notifier(&hwsim_netlink_notifier); + /* unregister the family */ + ret = genl_unregister_family(&hwsim_genl_family); + if (ret) + printk(KERN_DEBUG "mac80211_hwsim: " + "unregister family %i\n", ret); +} + +static const struct ieee80211_iface_limit hwsim_if_limits[] = { + { .max = 1, .types = BIT(NL80211_IFTYPE_ADHOC) }, + { .max = 2048, .types = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | +#ifdef CPTCFG_MAC80211_MESH + BIT(NL80211_IFTYPE_MESH_POINT) | +#endif + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_GO) }, + { .max = 1, .types = BIT(NL80211_IFTYPE_P2P_DEVICE) }, +}; + +static struct ieee80211_iface_combination hwsim_if_comb = { + .limits = hwsim_if_limits, + .n_limits = ARRAY_SIZE(hwsim_if_limits), + .max_interfaces = 2048, + .num_different_channels = 1, +}; + +static int __init init_mac80211_hwsim(void) +{ + int i, err = 0; + u8 addr[ETH_ALEN]; + struct mac80211_hwsim_data *data; + struct ieee80211_hw *hw; + enum ieee80211_band band; + + if (radios < 1 || radios > 100) + return -EINVAL; + + if (channels < 1) + return -EINVAL; + + if (channels > 1) { + hwsim_if_comb.num_different_channels = channels; + mac80211_hwsim_ops.hw_scan = mac80211_hwsim_hw_scan; + mac80211_hwsim_ops.cancel_hw_scan = + mac80211_hwsim_cancel_hw_scan; + mac80211_hwsim_ops.sw_scan_start = NULL; + mac80211_hwsim_ops.sw_scan_complete = NULL; + mac80211_hwsim_ops.remain_on_channel = + mac80211_hwsim_roc; + mac80211_hwsim_ops.cancel_remain_on_channel = + mac80211_hwsim_croc; + mac80211_hwsim_ops.add_chanctx = + mac80211_hwsim_add_chanctx; + mac80211_hwsim_ops.remove_chanctx = + mac80211_hwsim_remove_chanctx; + mac80211_hwsim_ops.change_chanctx = + mac80211_hwsim_change_chanctx; + mac80211_hwsim_ops.assign_vif_chanctx = + mac80211_hwsim_assign_vif_chanctx; + mac80211_hwsim_ops.unassign_vif_chanctx = + mac80211_hwsim_unassign_vif_chanctx; + } + + spin_lock_init(&hwsim_radio_lock); + INIT_LIST_HEAD(&hwsim_radios); + + err = platform_driver_register(&mac80211_hwsim_driver); + if (err) + return err; + + hwsim_class = class_create(THIS_MODULE, "mac80211_hwsim"); + if (IS_ERR(hwsim_class)) { + err = PTR_ERR(hwsim_class); + goto failed_unregister_driver; + } + + memset(addr, 0, ETH_ALEN); + addr[0] = 0x02; + + for (i = 0; i < radios; i++) { + printk(KERN_DEBUG "mac80211_hwsim: Initializing radio %d\n", + i); + hw = ieee80211_alloc_hw(sizeof(*data), &mac80211_hwsim_ops); + if (!hw) { + printk(KERN_DEBUG "mac80211_hwsim: ieee80211_alloc_hw " + "failed\n"); + err = -ENOMEM; + goto failed; + } + data = hw->priv; + data->hw = hw; + + data->dev = device_create(hwsim_class, NULL, 0, hw, + "hwsim%d", i); + if (IS_ERR(data->dev)) { + printk(KERN_DEBUG + "mac80211_hwsim: device_create failed (%ld)\n", + PTR_ERR(data->dev)); + err = -ENOMEM; + goto failed_drvdata; + } + data->dev->driver = &mac80211_hwsim_driver.driver; + err = device_bind_driver(data->dev); + if (err != 0) { + printk(KERN_DEBUG + "mac80211_hwsim: device_bind_driver failed (%d)\n", + err); + goto failed_hw; + } + + skb_queue_head_init(&data->pending); + + SET_IEEE80211_DEV(hw, data->dev); + addr[3] = i >> 8; + addr[4] = i; + memcpy(data->addresses[0].addr, addr, ETH_ALEN); + memcpy(data->addresses[1].addr, addr, ETH_ALEN); + data->addresses[1].addr[0] |= 0x40; + hw->wiphy->n_addresses = 2; + hw->wiphy->addresses = data->addresses; + + hw->wiphy->iface_combinations = &hwsim_if_comb; + hw->wiphy->n_iface_combinations = 1; + + if (channels > 1) { + hw->wiphy->max_scan_ssids = 255; + hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN; + hw->wiphy->max_remain_on_channel_duration = 1000; + } + + INIT_DELAYED_WORK(&data->roc_done, hw_roc_done); + INIT_DELAYED_WORK(&data->hw_scan, hw_scan_work); + + hw->channel_change_time = 1; + hw->queues = 5; + hw->offchannel_tx_hw_queue = 4; + hw->wiphy->interface_modes = + BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_MESH_POINT) | + BIT(NL80211_IFTYPE_P2P_DEVICE); + + hw->flags = IEEE80211_HW_MFP_CAPABLE | + IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_SUPPORTS_STATIC_SMPS | + IEEE80211_HW_SUPPORTS_DYNAMIC_SMPS | + IEEE80211_HW_AMPDU_AGGREGATION | + IEEE80211_HW_WANT_MONITOR_VIF | + IEEE80211_HW_QUEUE_CONTROL; + if (rctbl) + hw->flags |= IEEE80211_HW_SUPPORTS_RC_TABLE; + + hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS | + WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; + + /* ask mac80211 to reserve space for magic */ + hw->vif_data_size = sizeof(struct hwsim_vif_priv); + hw->sta_data_size = sizeof(struct hwsim_sta_priv); + hw->chanctx_data_size = sizeof(struct hwsim_chanctx_priv); + + memcpy(data->channels_2ghz, hwsim_channels_2ghz, + sizeof(hwsim_channels_2ghz)); + memcpy(data->channels_5ghz, hwsim_channels_5ghz, + sizeof(hwsim_channels_5ghz)); + memcpy(data->rates, hwsim_rates, sizeof(hwsim_rates)); + + for (band = IEEE80211_BAND_2GHZ; band < IEEE80211_NUM_BANDS; band++) { + struct ieee80211_supported_band *sband = &data->bands[band]; + switch (band) { + case IEEE80211_BAND_2GHZ: + sband->channels = data->channels_2ghz; + sband->n_channels = + ARRAY_SIZE(hwsim_channels_2ghz); + sband->bitrates = data->rates; + sband->n_bitrates = ARRAY_SIZE(hwsim_rates); + break; + case IEEE80211_BAND_5GHZ: + sband->channels = data->channels_5ghz; + sband->n_channels = + ARRAY_SIZE(hwsim_channels_5ghz); + sband->bitrates = data->rates + 4; + sband->n_bitrates = ARRAY_SIZE(hwsim_rates) - 4; + break; + default: + continue; + } + + sband->ht_cap.ht_supported = true; + sband->ht_cap.cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 | + IEEE80211_HT_CAP_GRN_FLD | + IEEE80211_HT_CAP_SGI_40 | + IEEE80211_HT_CAP_DSSSCCK40; + sband->ht_cap.ampdu_factor = 0x3; + sband->ht_cap.ampdu_density = 0x6; + memset(&sband->ht_cap.mcs, 0, + sizeof(sband->ht_cap.mcs)); + sband->ht_cap.mcs.rx_mask[0] = 0xff; + sband->ht_cap.mcs.rx_mask[1] = 0xff; + sband->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; + + hw->wiphy->bands[band] = sband; + + sband->vht_cap.vht_supported = true; + sband->vht_cap.cap = + IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454 | + IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ | + IEEE80211_VHT_CAP_RXLDPC | + IEEE80211_VHT_CAP_SHORT_GI_80 | + IEEE80211_VHT_CAP_SHORT_GI_160 | + IEEE80211_VHT_CAP_TXSTBC | + IEEE80211_VHT_CAP_RXSTBC_1 | + IEEE80211_VHT_CAP_RXSTBC_2 | + IEEE80211_VHT_CAP_RXSTBC_3 | + IEEE80211_VHT_CAP_RXSTBC_4 | + IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK; + sband->vht_cap.vht_mcs.rx_mcs_map = + cpu_to_le16(IEEE80211_VHT_MCS_SUPPORT_0_8 << 0 | + IEEE80211_VHT_MCS_SUPPORT_0_8 << 2 | + IEEE80211_VHT_MCS_SUPPORT_0_9 << 4 | + IEEE80211_VHT_MCS_SUPPORT_0_8 << 6 | + IEEE80211_VHT_MCS_SUPPORT_0_8 << 8 | + IEEE80211_VHT_MCS_SUPPORT_0_9 << 10 | + IEEE80211_VHT_MCS_SUPPORT_0_9 << 12 | + IEEE80211_VHT_MCS_SUPPORT_0_8 << 14); + sband->vht_cap.vht_mcs.tx_mcs_map = + sband->vht_cap.vht_mcs.rx_mcs_map; + } + /* By default all radios are belonging to the first group */ + data->group = 1; + mutex_init(&data->mutex); + + /* Enable frame retransmissions for lossy channels */ + hw->max_rates = 4; + hw->max_rate_tries = 11; + + /* Work to be done prior to ieee80211_register_hw() */ + switch (regtest) { + case HWSIM_REGTEST_DISABLED: + case HWSIM_REGTEST_DRIVER_REG_FOLLOW: + case HWSIM_REGTEST_DRIVER_REG_ALL: + case HWSIM_REGTEST_DIFF_COUNTRY: + /* + * Nothing to be done for driver regulatory domain + * hints prior to ieee80211_register_hw() + */ + break; + case HWSIM_REGTEST_WORLD_ROAM: + if (i == 0) { + hw->wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY; + wiphy_apply_custom_regulatory(hw->wiphy, + &hwsim_world_regdom_custom_01); + } + break; + case HWSIM_REGTEST_CUSTOM_WORLD: + hw->wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY; + wiphy_apply_custom_regulatory(hw->wiphy, + &hwsim_world_regdom_custom_01); + break; + case HWSIM_REGTEST_CUSTOM_WORLD_2: + if (i == 0) { + hw->wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY; + wiphy_apply_custom_regulatory(hw->wiphy, + &hwsim_world_regdom_custom_01); + } else if (i == 1) { + hw->wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY; + wiphy_apply_custom_regulatory(hw->wiphy, + &hwsim_world_regdom_custom_02); + } + break; + case HWSIM_REGTEST_STRICT_ALL: + hw->wiphy->flags |= WIPHY_FLAG_STRICT_REGULATORY; + break; + case HWSIM_REGTEST_STRICT_FOLLOW: + case HWSIM_REGTEST_STRICT_AND_DRIVER_REG: + if (i == 0) + hw->wiphy->flags |= WIPHY_FLAG_STRICT_REGULATORY; + break; + case HWSIM_REGTEST_ALL: + if (i == 0) { + hw->wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY; + wiphy_apply_custom_regulatory(hw->wiphy, + &hwsim_world_regdom_custom_01); + } else if (i == 1) { + hw->wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY; + wiphy_apply_custom_regulatory(hw->wiphy, + &hwsim_world_regdom_custom_02); + } else if (i == 4) + hw->wiphy->flags |= WIPHY_FLAG_STRICT_REGULATORY; + break; + default: + break; + } + + /* give the regulatory workqueue a chance to run */ + if (regtest) + schedule_timeout_interruptible(1); + err = ieee80211_register_hw(hw); + if (err < 0) { + printk(KERN_DEBUG "mac80211_hwsim: " + "ieee80211_register_hw failed (%d)\n", err); + goto failed_hw; + } + + /* Work to be done after to ieee80211_register_hw() */ + switch (regtest) { + case HWSIM_REGTEST_WORLD_ROAM: + case HWSIM_REGTEST_DISABLED: + break; + case HWSIM_REGTEST_DRIVER_REG_FOLLOW: + if (!i) + regulatory_hint(hw->wiphy, hwsim_alpha2s[0]); + break; + case HWSIM_REGTEST_DRIVER_REG_ALL: + case HWSIM_REGTEST_STRICT_ALL: + regulatory_hint(hw->wiphy, hwsim_alpha2s[0]); + break; + case HWSIM_REGTEST_DIFF_COUNTRY: + if (i < ARRAY_SIZE(hwsim_alpha2s)) + regulatory_hint(hw->wiphy, hwsim_alpha2s[i]); + break; + case HWSIM_REGTEST_CUSTOM_WORLD: + case HWSIM_REGTEST_CUSTOM_WORLD_2: + /* + * Nothing to be done for custom world regulatory + * domains after to ieee80211_register_hw + */ + break; + case HWSIM_REGTEST_STRICT_FOLLOW: + if (i == 0) + regulatory_hint(hw->wiphy, hwsim_alpha2s[0]); + break; + case HWSIM_REGTEST_STRICT_AND_DRIVER_REG: + if (i == 0) + regulatory_hint(hw->wiphy, hwsim_alpha2s[0]); + else if (i == 1) + regulatory_hint(hw->wiphy, hwsim_alpha2s[1]); + break; + case HWSIM_REGTEST_ALL: + if (i == 2) + regulatory_hint(hw->wiphy, hwsim_alpha2s[0]); + else if (i == 3) + regulatory_hint(hw->wiphy, hwsim_alpha2s[1]); + else if (i == 4) + regulatory_hint(hw->wiphy, hwsim_alpha2s[2]); + break; + default: + break; + } + + wiphy_debug(hw->wiphy, "hwaddr %pm registered\n", + hw->wiphy->perm_addr); + + data->debugfs = debugfs_create_dir("hwsim", + hw->wiphy->debugfsdir); + data->debugfs_ps = debugfs_create_file("ps", 0666, + data->debugfs, data, + &hwsim_fops_ps); + data->debugfs_group = debugfs_create_file("group", 0666, + data->debugfs, data, + &hwsim_fops_group); + + tasklet_hrtimer_init(&data->beacon_timer, + mac80211_hwsim_beacon, + CLOCK_REALTIME, HRTIMER_MODE_ABS); + + list_add_tail(&data->list, &hwsim_radios); + } + + hwsim_mon = alloc_netdev(0, "hwsim%d", hwsim_mon_setup); + if (hwsim_mon == NULL) + goto failed; + + rtnl_lock(); + + err = dev_alloc_name(hwsim_mon, hwsim_mon->name); + if (err < 0) + goto failed_mon; + + + err = register_netdevice(hwsim_mon); + if (err < 0) + goto failed_mon; + + rtnl_unlock(); + + err = hwsim_init_netlink(); + if (err < 0) + goto failed_nl; + + return 0; + +failed_nl: + printk(KERN_DEBUG "mac_80211_hwsim: failed initializing netlink\n"); + return err; + +failed_mon: + rtnl_unlock(); + free_netdev(hwsim_mon); + mac80211_hwsim_free(); + return err; + +failed_hw: + device_unregister(data->dev); +failed_drvdata: + ieee80211_free_hw(hw); +failed: + mac80211_hwsim_free(); +failed_unregister_driver: + platform_driver_unregister(&mac80211_hwsim_driver); + return err; +} +module_init(init_mac80211_hwsim); + +static void __exit exit_mac80211_hwsim(void) +{ + printk(KERN_DEBUG "mac80211_hwsim: unregister radios\n"); + + hwsim_exit_netlink(); + + mac80211_hwsim_free(); + unregister_netdev(hwsim_mon); + platform_driver_unregister(&mac80211_hwsim_driver); +} +module_exit(exit_mac80211_hwsim); diff --git a/drivers/net/wireless/mac80211_hwsim.h b/drivers/net/wireless/mac80211_hwsim.h new file mode 100644 index 0000000..afaad5a --- /dev/null +++ b/drivers/net/wireless/mac80211_hwsim.h @@ -0,0 +1,133 @@ +/* + * mac80211_hwsim - software simulator of 802.11 radio(s) for mac80211 + * Copyright (c) 2008, Jouni Malinen + * Copyright (c) 2011, Javier Lopez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __MAC80211_HWSIM_H +#define __MAC80211_HWSIM_H + +/** + * enum hwsim_tx_control_flags - flags to describe transmission info/status + * + * These flags are used to give the wmediumd extra information in order to + * modify its behavior for each frame + * + * @HWSIM_TX_CTL_REQ_TX_STATUS: require TX status callback for this frame. + * @HWSIM_TX_CTL_NO_ACK: tell the wmediumd not to wait for an ack + * @HWSIM_TX_STAT_ACK: Frame was acknowledged + * + */ +enum hwsim_tx_control_flags { + HWSIM_TX_CTL_REQ_TX_STATUS = BIT(0), + HWSIM_TX_CTL_NO_ACK = BIT(1), + HWSIM_TX_STAT_ACK = BIT(2), +}; + +/** + * DOC: Frame transmission/registration support + * + * Frame transmission and registration support exists to allow userspace + * entities such as wmediumd to receive and process all broadcasted + * frames from a mac80211_hwsim radio device. + * + * This allow user space applications to decide if the frame should be + * dropped or not and implement a wireless medium simulator at user space. + * + * Registration is done by sending a register message to the driver and + * will be automatically unregistered if the user application doesn't + * responds to sent frames. + * Once registered the user application has to take responsibility of + * broadcasting the frames to all listening mac80211_hwsim radio + * interfaces. + * + * For more technical details, see the corresponding command descriptions + * below. + */ + +/** + * enum hwsim_commands - supported hwsim commands + * + * @HWSIM_CMD_UNSPEC: unspecified command to catch errors + * + * @HWSIM_CMD_REGISTER: request to register and received all broadcasted + * frames by any mac80211_hwsim radio device. + * @HWSIM_CMD_FRAME: send/receive a broadcasted frame from/to kernel/user + * space, uses: + * %HWSIM_ATTR_ADDR_TRANSMITTER, %HWSIM_ATTR_ADDR_RECEIVER, + * %HWSIM_ATTR_FRAME, %HWSIM_ATTR_FLAGS, %HWSIM_ATTR_RX_RATE, + * %HWSIM_ATTR_SIGNAL, %HWSIM_ATTR_COOKIE + * @HWSIM_CMD_TX_INFO_FRAME: Transmission info report from user space to + * kernel, uses: + * %HWSIM_ATTR_ADDR_TRANSMITTER, %HWSIM_ATTR_FLAGS, + * %HWSIM_ATTR_TX_INFO, %HWSIM_ATTR_SIGNAL, %HWSIM_ATTR_COOKIE + * @__HWSIM_CMD_MAX: enum limit + */ +enum { + HWSIM_CMD_UNSPEC, + HWSIM_CMD_REGISTER, + HWSIM_CMD_FRAME, + HWSIM_CMD_TX_INFO_FRAME, + __HWSIM_CMD_MAX, +}; +#define HWSIM_CMD_MAX (_HWSIM_CMD_MAX - 1) + +/** + * enum hwsim_attrs - hwsim netlink attributes + * + * @HWSIM_ATTR_UNSPEC: unspecified attribute to catch errors + * + * @HWSIM_ATTR_ADDR_RECEIVER: MAC address of the radio device that + * the frame is broadcasted to + * @HWSIM_ATTR_ADDR_TRANSMITTER: MAC address of the radio device that + * the frame was broadcasted from + * @HWSIM_ATTR_FRAME: Data array + * @HWSIM_ATTR_FLAGS: mac80211 transmission flags, used to process + properly the frame at user space + * @HWSIM_ATTR_RX_RATE: estimated rx rate index for this frame at user + space + * @HWSIM_ATTR_SIGNAL: estimated RX signal for this frame at user + space + * @HWSIM_ATTR_TX_INFO: ieee80211_tx_rate array + * @HWSIM_ATTR_COOKIE: sk_buff cookie to identify the frame + * @__HWSIM_ATTR_MAX: enum limit + */ + + +enum { + HWSIM_ATTR_UNSPEC, + HWSIM_ATTR_ADDR_RECEIVER, + HWSIM_ATTR_ADDR_TRANSMITTER, + HWSIM_ATTR_FRAME, + HWSIM_ATTR_FLAGS, + HWSIM_ATTR_RX_RATE, + HWSIM_ATTR_SIGNAL, + HWSIM_ATTR_TX_INFO, + HWSIM_ATTR_COOKIE, + __HWSIM_ATTR_MAX, +}; +#define HWSIM_ATTR_MAX (__HWSIM_ATTR_MAX - 1) + +/** + * struct hwsim_tx_rate - rate selection/status + * + * @idx: rate index to attempt to send with + * @count: number of tries in this rate before going to the next rate + * + * A value of -1 for @idx indicates an invalid rate and, if used + * in an array of retry rates, that no more rates should be tried. + * + * When used for transmit status reporting, the driver should + * always report the rate and number of retries used. + * + */ +struct hwsim_tx_rate { + s8 idx; + u8 count; +} __packed; + +#endif /* __MAC80211_HWSIM_H */ diff --git a/drivers/net/wireless/mwifiex/11ac.c b/drivers/net/wireless/mwifiex/11ac.c new file mode 100644 index 0000000..5e0eec4 --- /dev/null +++ b/drivers/net/wireless/mwifiex/11ac.c @@ -0,0 +1,302 @@ +/* + * Marvell Wireless LAN device driver: 802.11ac + * + * Copyright (C) 2013, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "fw.h" +#include "main.h" +#include "11ac.h" + +/* This function converts the 2-bit MCS map to the highest long GI + * VHT data rate. + */ +static u16 +mwifiex_convert_mcsmap_to_maxrate(struct mwifiex_private *priv, + u8 bands, u16 mcs_map) +{ + u8 i, nss, max_mcs; + u16 max_rate = 0; + u32 usr_vht_cap_info = 0; + struct mwifiex_adapter *adapter = priv->adapter; + /* tables of the MCS map to the highest data rate (in Mbps) + * supported for long GI + */ + u16 max_rate_lgi_80MHZ[8][3] = { + {0x124, 0x15F, 0x186}, /* NSS = 1 */ + {0x249, 0x2BE, 0x30C}, /* NSS = 2 */ + {0x36D, 0x41D, 0x492}, /* NSS = 3 */ + {0x492, 0x57C, 0x618}, /* NSS = 4 */ + {0x5B6, 0x6DB, 0x79E}, /* NSS = 5 */ + {0x6DB, 0x83A, 0x0}, /* NSS = 6 */ + {0x7FF, 0x999, 0xAAA}, /* NSS = 7 */ + {0x924, 0xAF8, 0xC30} /* NSS = 8 */ + }; + u16 max_rate_lgi_160MHZ[8][3] = { + {0x249, 0x2BE, 0x30C}, /* NSS = 1 */ + {0x492, 0x57C, 0x618}, /* NSS = 2 */ + {0x6DB, 0x83A, 0x0}, /* NSS = 3 */ + {0x924, 0xAF8, 0xC30}, /* NSS = 4 */ + {0xB6D, 0xDB6, 0xF3C}, /* NSS = 5 */ + {0xDB6, 0x1074, 0x1248}, /* NSS = 6 */ + {0xFFF, 0x1332, 0x1554}, /* NSS = 7 */ + {0x1248, 0x15F0, 0x1860} /* NSS = 8 */ + }; + + if (bands & BAND_AAC) + usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_a; + else + usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_bg; + + /* find the max NSS supported */ + nss = 0; + for (i = 0; i < 8; i++) { + max_mcs = (mcs_map >> (2 * i)) & 0x3; + if (max_mcs < 3) + nss = i; + } + max_mcs = (mcs_map >> (2 * nss)) & 0x3; + + /* if max_mcs is 3, nss must be 0 (SS = 1). Thus, max mcs is MCS 9 */ + if (max_mcs >= 3) + max_mcs = 2; + + if (GET_VHTCAP_CHWDSET(usr_vht_cap_info)) { + /* support 160 MHz */ + max_rate = max_rate_lgi_160MHZ[nss][max_mcs]; + if (!max_rate) + /* MCS9 is not supported in NSS6 */ + max_rate = max_rate_lgi_160MHZ[nss][max_mcs - 1]; + } else { + max_rate = max_rate_lgi_80MHZ[nss][max_mcs]; + if (!max_rate) + /* MCS9 is not supported in NSS3 */ + max_rate = max_rate_lgi_80MHZ[nss][max_mcs - 1]; + } + + return max_rate; +} + +static void +mwifiex_fill_vht_cap_info(struct mwifiex_private *priv, + struct mwifiex_ie_types_vhtcap *vht_cap, u8 bands) +{ + struct mwifiex_adapter *adapter = priv->adapter; + + if (bands & BAND_A) + vht_cap->vht_cap.vht_cap_info = + cpu_to_le32(adapter->usr_dot_11ac_dev_cap_a); + else + vht_cap->vht_cap.vht_cap_info = + cpu_to_le32(adapter->usr_dot_11ac_dev_cap_bg); +} + +static void +mwifiex_fill_vht_cap_tlv(struct mwifiex_private *priv, + struct mwifiex_ie_types_vhtcap *vht_cap, u8 bands) +{ + struct mwifiex_adapter *adapter = priv->adapter; + u16 mcs_map_user, mcs_map_resp, mcs_map_result; + u16 mcs_user, mcs_resp, nss, tmp; + + /* Fill VHT cap info */ + mwifiex_fill_vht_cap_info(priv, vht_cap, bands); + + /* rx MCS Set: find the minimum of the user rx mcs and ap rx mcs */ + mcs_map_user = GET_DEVRXMCSMAP(adapter->usr_dot_11ac_mcs_support); + mcs_map_resp = le16_to_cpu(vht_cap->vht_cap.supp_mcs.rx_mcs_map); + mcs_map_result = 0; + + for (nss = 1; nss <= 8; nss++) { + mcs_user = GET_VHTNSSMCS(mcs_map_user, nss); + mcs_resp = GET_VHTNSSMCS(mcs_map_resp, nss); + + if ((mcs_user == NO_NSS_SUPPORT) || + (mcs_resp == NO_NSS_SUPPORT)) + SET_VHTNSSMCS(mcs_map_result, nss, NO_NSS_SUPPORT); + else + SET_VHTNSSMCS(mcs_map_result, nss, + min(mcs_user, mcs_resp)); + } + + vht_cap->vht_cap.supp_mcs.rx_mcs_map = cpu_to_le16(mcs_map_result); + + tmp = mwifiex_convert_mcsmap_to_maxrate(priv, bands, mcs_map_result); + vht_cap->vht_cap.supp_mcs.rx_highest = cpu_to_le16(tmp); + + /* tx MCS Set: find the minimum of the user tx mcs and ap tx mcs */ + mcs_map_user = GET_DEVTXMCSMAP(adapter->usr_dot_11ac_mcs_support); + mcs_map_resp = le16_to_cpu(vht_cap->vht_cap.supp_mcs.tx_mcs_map); + mcs_map_result = 0; + + for (nss = 1; nss <= 8; nss++) { + mcs_user = GET_VHTNSSMCS(mcs_map_user, nss); + mcs_resp = GET_VHTNSSMCS(mcs_map_resp, nss); + if ((mcs_user == NO_NSS_SUPPORT) || + (mcs_resp == NO_NSS_SUPPORT)) + SET_VHTNSSMCS(mcs_map_result, nss, NO_NSS_SUPPORT); + else + SET_VHTNSSMCS(mcs_map_result, nss, + min(mcs_user, mcs_resp)); + } + + vht_cap->vht_cap.supp_mcs.tx_mcs_map = cpu_to_le16(mcs_map_result); + + tmp = mwifiex_convert_mcsmap_to_maxrate(priv, bands, mcs_map_result); + vht_cap->vht_cap.supp_mcs.tx_highest = cpu_to_le16(tmp); + + return; +} + +int mwifiex_cmd_append_11ac_tlv(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc, + u8 **buffer) +{ + struct mwifiex_ie_types_vhtcap *vht_cap; + struct mwifiex_ie_types_oper_mode_ntf *oper_ntf; + struct ieee_types_oper_mode_ntf *ieee_oper_ntf; + struct mwifiex_ie_types_vht_oper *vht_op; + struct mwifiex_adapter *adapter = priv->adapter; + u8 supp_chwd_set; + u32 usr_vht_cap_info; + int ret_len = 0; + + if (bss_desc->bss_band & BAND_A) + usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_a; + else + usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_bg; + + /* VHT Capabilities IE */ + if (bss_desc->bcn_vht_cap) { + vht_cap = (struct mwifiex_ie_types_vhtcap *)*buffer; + memset(vht_cap, 0, sizeof(*vht_cap)); + vht_cap->header.type = cpu_to_le16(WLAN_EID_VHT_CAPABILITY); + vht_cap->header.len = + cpu_to_le16(sizeof(struct ieee80211_vht_cap)); + memcpy((u8 *)vht_cap + sizeof(struct mwifiex_ie_types_header), + (u8 *)bss_desc->bcn_vht_cap + + sizeof(struct ieee_types_header), + le16_to_cpu(vht_cap->header.len)); + + mwifiex_fill_vht_cap_tlv(priv, vht_cap, bss_desc->bss_band); + *buffer += sizeof(*vht_cap); + ret_len += sizeof(*vht_cap); + } + + /* VHT Operation IE */ + if (bss_desc->bcn_vht_oper) { + if (priv->bss_mode == NL80211_IFTYPE_STATION) { + vht_op = (struct mwifiex_ie_types_vht_oper *)*buffer; + memset(vht_op, 0, sizeof(*vht_op)); + vht_op->header.type = + cpu_to_le16(WLAN_EID_VHT_OPERATION); + vht_op->header.len = cpu_to_le16(sizeof(*vht_op) - + sizeof(struct mwifiex_ie_types_header)); + memcpy((u8 *)vht_op + + sizeof(struct mwifiex_ie_types_header), + (u8 *)bss_desc->bcn_vht_oper + + sizeof(struct ieee_types_header), + le16_to_cpu(vht_op->header.len)); + + /* negotiate the channel width and central freq + * and keep the central freq as the peer suggests + */ + supp_chwd_set = GET_VHTCAP_CHWDSET(usr_vht_cap_info); + + switch (supp_chwd_set) { + case 0: + vht_op->chan_width = + min_t(u8, IEEE80211_VHT_CHANWIDTH_80MHZ, + bss_desc->bcn_vht_oper->chan_width); + break; + case 1: + vht_op->chan_width = + min_t(u8, IEEE80211_VHT_CHANWIDTH_160MHZ, + bss_desc->bcn_vht_oper->chan_width); + break; + case 2: + vht_op->chan_width = + min_t(u8, IEEE80211_VHT_CHANWIDTH_80P80MHZ, + bss_desc->bcn_vht_oper->chan_width); + break; + default: + vht_op->chan_width = + IEEE80211_VHT_CHANWIDTH_USE_HT; + break; + } + + *buffer += sizeof(*vht_op); + ret_len += sizeof(*vht_op); + } + } + + /* Operating Mode Notification IE */ + if (bss_desc->oper_mode) { + ieee_oper_ntf = bss_desc->oper_mode; + oper_ntf = (void *)*buffer; + memset(oper_ntf, 0, sizeof(*oper_ntf)); + oper_ntf->header.type = cpu_to_le16(WLAN_EID_OPMODE_NOTIF); + oper_ntf->header.len = cpu_to_le16(sizeof(u8)); + oper_ntf->oper_mode = ieee_oper_ntf->oper_mode; + *buffer += sizeof(*oper_ntf); + ret_len += sizeof(*oper_ntf); + } + + return ret_len; +} + +int mwifiex_cmd_11ac_cfg(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct mwifiex_11ac_vht_cfg *cfg) +{ + struct host_cmd_11ac_vht_cfg *vhtcfg = &cmd->params.vht_cfg; + + cmd->command = cpu_to_le16(HostCmd_CMD_11AC_CFG); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_11ac_vht_cfg) + + S_DS_GEN); + vhtcfg->action = cpu_to_le16(cmd_action); + vhtcfg->band_config = cfg->band_config; + vhtcfg->misc_config = cfg->misc_config; + vhtcfg->cap_info = cpu_to_le32(cfg->cap_info); + vhtcfg->mcs_tx_set = cpu_to_le32(cfg->mcs_tx_set); + vhtcfg->mcs_rx_set = cpu_to_le32(cfg->mcs_rx_set); + + return 0; +} + +/* This function initializes the BlockACK setup information for given + * mwifiex_private structure for 11ac enabled networks. + */ +void mwifiex_set_11ac_ba_params(struct mwifiex_private *priv) +{ + priv->add_ba_param.timeout = MWIFIEX_DEFAULT_BLOCK_ACK_TIMEOUT; + + if (GET_BSS_ROLE(priv) == MWIFIEX_BSS_ROLE_UAP) { + priv->add_ba_param.tx_win_size = + MWIFIEX_11AC_UAP_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + MWIFIEX_11AC_UAP_AMPDU_DEF_RXWINSIZE; + } else { + priv->add_ba_param.tx_win_size = + MWIFIEX_11AC_STA_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + MWIFIEX_11AC_STA_AMPDU_DEF_RXWINSIZE; + } + + return; +} diff --git a/drivers/net/wireless/mwifiex/11ac.h b/drivers/net/wireless/mwifiex/11ac.h new file mode 100644 index 0000000..7c2c69b --- /dev/null +++ b/drivers/net/wireless/mwifiex/11ac.h @@ -0,0 +1,43 @@ +/* + * Marvell Wireless LAN device driver: 802.11ac + * + * Copyright (C) 2013, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _MWIFIEX_11AC_H_ +#define _MWIFIEX_11AC_H_ + +#define VHT_CFG_2GHZ BIT(0) +#define VHT_CFG_5GHZ BIT(1) + +enum vht_cfg_misc_config { + VHT_CAP_TX_OPERATION = 1, + VHT_CAP_ASSOCIATION, + VHT_CAP_UAP_ONLY +}; + +#define DEFAULT_VHT_MCS_SET 0xfffa +#define DISABLE_VHT_MCS_SET 0xffff + +#define VHT_BW_80_160_80P80 BIT(2) + +int mwifiex_cmd_append_11ac_tlv(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc, + u8 **buffer); +int mwifiex_cmd_11ac_cfg(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct mwifiex_11ac_vht_cfg *cfg); +#endif /* _MWIFIEX_11AC_H_ */ diff --git a/drivers/net/wireless/mwifiex/11n.c b/drivers/net/wireless/mwifiex/11n.c new file mode 100644 index 0000000..41e9d25 --- /dev/null +++ b/drivers/net/wireless/mwifiex/11n.c @@ -0,0 +1,703 @@ +/* + * Marvell Wireless LAN device driver: 802.11n + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" + +/* + * Fills HT capability information field, AMPDU Parameters field, HT extended + * capability field, and supported MCS set fields. + * + * HT capability information field, AMPDU Parameters field, supported MCS set + * fields are retrieved from cfg80211 stack + * + * RD responder bit to set to clear in the extended capability header. + */ +void +mwifiex_fill_cap_info(struct mwifiex_private *priv, u8 radio_type, + struct mwifiex_ie_types_htcap *ht_cap) +{ + uint16_t ht_ext_cap = le16_to_cpu(ht_cap->ht_cap.extended_ht_cap_info); + struct ieee80211_supported_band *sband = + priv->wdev->wiphy->bands[radio_type]; + + ht_cap->ht_cap.ampdu_params_info = + (sband->ht_cap.ampdu_factor & + IEEE80211_HT_AMPDU_PARM_FACTOR) | + ((sband->ht_cap.ampdu_density << + IEEE80211_HT_AMPDU_PARM_DENSITY_SHIFT) & + IEEE80211_HT_AMPDU_PARM_DENSITY); + + memcpy((u8 *) &ht_cap->ht_cap.mcs, &sband->ht_cap.mcs, + sizeof(sband->ht_cap.mcs)); + + if (priv->bss_mode == NL80211_IFTYPE_STATION || + (sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 && + (priv->adapter->sec_chan_offset != + IEEE80211_HT_PARAM_CHA_SEC_NONE))) + /* Set MCS32 for infra mode or ad-hoc mode with 40MHz support */ + SETHT_MCS32(ht_cap->ht_cap.mcs.rx_mask); + + /* Clear RD responder bit */ + ht_ext_cap &= ~IEEE80211_HT_EXT_CAP_RD_RESPONDER; + + ht_cap->ht_cap.cap_info = cpu_to_le16(sband->ht_cap.cap); + ht_cap->ht_cap.extended_ht_cap_info = cpu_to_le16(ht_ext_cap); +} + +/* + * This function returns the pointer to an entry in BA Stream + * table which matches the requested BA status. + */ +static struct mwifiex_tx_ba_stream_tbl * +mwifiex_get_ba_status(struct mwifiex_private *priv, + enum mwifiex_ba_status ba_status) +{ + struct mwifiex_tx_ba_stream_tbl *tx_ba_tsr_tbl; + unsigned long flags; + + spin_lock_irqsave(&priv->tx_ba_stream_tbl_lock, flags); + list_for_each_entry(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr, list) { + if (tx_ba_tsr_tbl->ba_status == ba_status) { + spin_unlock_irqrestore(&priv->tx_ba_stream_tbl_lock, + flags); + return tx_ba_tsr_tbl; + } + } + spin_unlock_irqrestore(&priv->tx_ba_stream_tbl_lock, flags); + return NULL; +} + +/* + * This function handles the command response of delete a block + * ack request. + * + * The function checks the response success status and takes action + * accordingly (send an add BA request in case of success, or recreate + * the deleted stream in case of failure, if the add BA was also + * initiated by us). + */ +int mwifiex_ret_11n_delba(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + int tid; + struct mwifiex_tx_ba_stream_tbl *tx_ba_tbl; + struct host_cmd_ds_11n_delba *del_ba = &resp->params.del_ba; + uint16_t del_ba_param_set = le16_to_cpu(del_ba->del_ba_param_set); + + tid = del_ba_param_set >> DELBA_TID_POS; + if (del_ba->del_result == BA_RESULT_SUCCESS) { + mwifiex_del_ba_tbl(priv, tid, del_ba->peer_mac_addr, + TYPE_DELBA_SENT, + INITIATOR_BIT(del_ba_param_set)); + + tx_ba_tbl = mwifiex_get_ba_status(priv, BA_SETUP_INPROGRESS); + if (tx_ba_tbl) + mwifiex_send_addba(priv, tx_ba_tbl->tid, + tx_ba_tbl->ra); + } else { /* + * In case of failure, recreate the deleted stream in case + * we initiated the ADDBA + */ + if (!INITIATOR_BIT(del_ba_param_set)) + return 0; + + mwifiex_create_ba_tbl(priv, del_ba->peer_mac_addr, tid, + BA_SETUP_INPROGRESS); + + tx_ba_tbl = mwifiex_get_ba_status(priv, BA_SETUP_INPROGRESS); + + if (tx_ba_tbl) + mwifiex_del_ba_tbl(priv, tx_ba_tbl->tid, tx_ba_tbl->ra, + TYPE_DELBA_SENT, true); + } + + return 0; +} + +/* + * This function handles the command response of add a block + * ack request. + * + * Handling includes changing the header fields to CPU formats, checking + * the response success status and taking actions accordingly (delete the + * BA stream table in case of failure). + */ +int mwifiex_ret_11n_addba_req(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + int tid; + struct host_cmd_ds_11n_addba_rsp *add_ba_rsp = &resp->params.add_ba_rsp; + struct mwifiex_tx_ba_stream_tbl *tx_ba_tbl; + + add_ba_rsp->ssn = cpu_to_le16((le16_to_cpu(add_ba_rsp->ssn)) + & SSN_MASK); + + tid = (le16_to_cpu(add_ba_rsp->block_ack_param_set) + & IEEE80211_ADDBA_PARAM_TID_MASK) + >> BLOCKACKPARAM_TID_POS; + if (le16_to_cpu(add_ba_rsp->status_code) == BA_RESULT_SUCCESS) { + tx_ba_tbl = mwifiex_get_ba_tbl(priv, tid, + add_ba_rsp->peer_mac_addr); + if (tx_ba_tbl) { + dev_dbg(priv->adapter->dev, "info: BA stream complete\n"); + tx_ba_tbl->ba_status = BA_SETUP_COMPLETE; + } else { + dev_err(priv->adapter->dev, "BA stream not created\n"); + } + } else { + mwifiex_del_ba_tbl(priv, tid, add_ba_rsp->peer_mac_addr, + TYPE_DELBA_SENT, true); + if (add_ba_rsp->add_rsp_result != BA_RESULT_TIMEOUT) + priv->aggr_prio_tbl[tid].ampdu_ap = + BA_STREAM_NOT_ALLOWED; + } + + return 0; +} + +/* + * This function prepares command of reconfigure Tx buffer. + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Setting Tx buffer size (for SET only) + * - Ensuring correct endian-ness + */ +int mwifiex_cmd_recfg_tx_buf(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, int cmd_action, + u16 *buf_size) +{ + struct host_cmd_ds_txbuf_cfg *tx_buf = &cmd->params.tx_buf; + u16 action = (u16) cmd_action; + + cmd->command = cpu_to_le16(HostCmd_CMD_RECONFIGURE_TX_BUFF); + cmd->size = + cpu_to_le16(sizeof(struct host_cmd_ds_txbuf_cfg) + S_DS_GEN); + tx_buf->action = cpu_to_le16(action); + switch (action) { + case HostCmd_ACT_GEN_SET: + dev_dbg(priv->adapter->dev, "cmd: set tx_buf=%d\n", *buf_size); + tx_buf->buff_size = cpu_to_le16(*buf_size); + break; + case HostCmd_ACT_GEN_GET: + default: + tx_buf->buff_size = 0; + break; + } + return 0; +} + +/* + * This function prepares command of AMSDU aggregation control. + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Setting AMSDU control parameters (for SET only) + * - Ensuring correct endian-ness + */ +int mwifiex_cmd_amsdu_aggr_ctrl(struct host_cmd_ds_command *cmd, + int cmd_action, + struct mwifiex_ds_11n_amsdu_aggr_ctrl *aa_ctrl) +{ + struct host_cmd_ds_amsdu_aggr_ctrl *amsdu_ctrl = + &cmd->params.amsdu_aggr_ctrl; + u16 action = (u16) cmd_action; + + cmd->command = cpu_to_le16(HostCmd_CMD_AMSDU_AGGR_CTRL); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_amsdu_aggr_ctrl) + + S_DS_GEN); + amsdu_ctrl->action = cpu_to_le16(action); + switch (action) { + case HostCmd_ACT_GEN_SET: + amsdu_ctrl->enable = cpu_to_le16(aa_ctrl->enable); + amsdu_ctrl->curr_buf_size = 0; + break; + case HostCmd_ACT_GEN_GET: + default: + amsdu_ctrl->curr_buf_size = 0; + break; + } + return 0; +} + +/* + * This function prepares 11n configuration command. + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Setting HT Tx capability and HT Tx information fields + * - Ensuring correct endian-ness + */ +int mwifiex_cmd_11n_cfg(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct mwifiex_ds_11n_tx_cfg *txcfg) +{ + struct host_cmd_ds_11n_cfg *htcfg = &cmd->params.htcfg; + + cmd->command = cpu_to_le16(HostCmd_CMD_11N_CFG); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_11n_cfg) + S_DS_GEN); + htcfg->action = cpu_to_le16(cmd_action); + htcfg->ht_tx_cap = cpu_to_le16(txcfg->tx_htcap); + htcfg->ht_tx_info = cpu_to_le16(txcfg->tx_htinfo); + + if (priv->adapter->is_hw_11ac_capable) + htcfg->misc_config = cpu_to_le16(txcfg->misc_config); + + return 0; +} + +/* + * This function appends an 11n TLV to a buffer. + * + * Buffer allocation is responsibility of the calling + * function. No size validation is made here. + * + * The function fills up the following sections, if applicable - + * - HT capability IE + * - HT information IE (with channel list) + * - 20/40 BSS Coexistence IE + * - HT Extended Capabilities IE + */ +int +mwifiex_cmd_append_11n_tlv(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc, + u8 **buffer) +{ + struct mwifiex_ie_types_htcap *ht_cap; + struct mwifiex_ie_types_htinfo *ht_info; + struct mwifiex_ie_types_chan_list_param_set *chan_list; + struct mwifiex_ie_types_2040bssco *bss_co_2040; + struct mwifiex_ie_types_extcap *ext_cap; + int ret_len = 0; + struct ieee80211_supported_band *sband; + u8 radio_type; + + if (!buffer || !*buffer) + return ret_len; + + radio_type = mwifiex_band_to_radio_type((u8) bss_desc->bss_band); + sband = priv->wdev->wiphy->bands[radio_type]; + + if (bss_desc->bcn_ht_cap) { + ht_cap = (struct mwifiex_ie_types_htcap *) *buffer; + memset(ht_cap, 0, sizeof(struct mwifiex_ie_types_htcap)); + ht_cap->header.type = cpu_to_le16(WLAN_EID_HT_CAPABILITY); + ht_cap->header.len = + cpu_to_le16(sizeof(struct ieee80211_ht_cap)); + memcpy((u8 *) ht_cap + sizeof(struct mwifiex_ie_types_header), + (u8 *) bss_desc->bcn_ht_cap + + sizeof(struct ieee_types_header), + le16_to_cpu(ht_cap->header.len)); + + mwifiex_fill_cap_info(priv, radio_type, ht_cap); + + *buffer += sizeof(struct mwifiex_ie_types_htcap); + ret_len += sizeof(struct mwifiex_ie_types_htcap); + } + + if (bss_desc->bcn_ht_oper) { + if (priv->bss_mode == NL80211_IFTYPE_ADHOC) { + ht_info = (struct mwifiex_ie_types_htinfo *) *buffer; + memset(ht_info, 0, + sizeof(struct mwifiex_ie_types_htinfo)); + ht_info->header.type = + cpu_to_le16(WLAN_EID_HT_OPERATION); + ht_info->header.len = + cpu_to_le16( + sizeof(struct ieee80211_ht_operation)); + + memcpy((u8 *) ht_info + + sizeof(struct mwifiex_ie_types_header), + (u8 *) bss_desc->bcn_ht_oper + + sizeof(struct ieee_types_header), + le16_to_cpu(ht_info->header.len)); + + if (!(sband->ht_cap.cap & + IEEE80211_HT_CAP_SUP_WIDTH_20_40)) + ht_info->ht_oper.ht_param &= + ~(IEEE80211_HT_PARAM_CHAN_WIDTH_ANY | + IEEE80211_HT_PARAM_CHA_SEC_OFFSET); + + *buffer += sizeof(struct mwifiex_ie_types_htinfo); + ret_len += sizeof(struct mwifiex_ie_types_htinfo); + } + + chan_list = + (struct mwifiex_ie_types_chan_list_param_set *) *buffer; + memset(chan_list, 0, + sizeof(struct mwifiex_ie_types_chan_list_param_set)); + chan_list->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + chan_list->header.len = cpu_to_le16( + sizeof(struct mwifiex_ie_types_chan_list_param_set) - + sizeof(struct mwifiex_ie_types_header)); + chan_list->chan_scan_param[0].chan_number = + bss_desc->bcn_ht_oper->primary_chan; + chan_list->chan_scan_param[0].radio_type = + mwifiex_band_to_radio_type((u8) bss_desc->bss_band); + + if (sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 && + bss_desc->bcn_ht_oper->ht_param & + IEEE80211_HT_PARAM_CHAN_WIDTH_ANY) + SET_SECONDARYCHAN(chan_list->chan_scan_param[0]. + radio_type, + (bss_desc->bcn_ht_oper->ht_param & + IEEE80211_HT_PARAM_CHA_SEC_OFFSET)); + + *buffer += sizeof(struct mwifiex_ie_types_chan_list_param_set); + ret_len += sizeof(struct mwifiex_ie_types_chan_list_param_set); + } + + if (bss_desc->bcn_bss_co_2040) { + bss_co_2040 = (struct mwifiex_ie_types_2040bssco *) *buffer; + memset(bss_co_2040, 0, + sizeof(struct mwifiex_ie_types_2040bssco)); + bss_co_2040->header.type = cpu_to_le16(WLAN_EID_BSS_COEX_2040); + bss_co_2040->header.len = + cpu_to_le16(sizeof(bss_co_2040->bss_co_2040)); + + memcpy((u8 *) bss_co_2040 + + sizeof(struct mwifiex_ie_types_header), + bss_desc->bcn_bss_co_2040 + + sizeof(struct ieee_types_header), + le16_to_cpu(bss_co_2040->header.len)); + + *buffer += sizeof(struct mwifiex_ie_types_2040bssco); + ret_len += sizeof(struct mwifiex_ie_types_2040bssco); + } + + if (bss_desc->bcn_ext_cap) { + ext_cap = (struct mwifiex_ie_types_extcap *) *buffer; + memset(ext_cap, 0, sizeof(struct mwifiex_ie_types_extcap)); + ext_cap->header.type = cpu_to_le16(WLAN_EID_EXT_CAPABILITY); + ext_cap->header.len = cpu_to_le16(sizeof(ext_cap->ext_cap)); + + memcpy((u8 *)ext_cap + sizeof(struct mwifiex_ie_types_header), + bss_desc->bcn_ext_cap + sizeof(struct ieee_types_header), + le16_to_cpu(ext_cap->header.len)); + + *buffer += sizeof(struct mwifiex_ie_types_extcap); + ret_len += sizeof(struct mwifiex_ie_types_extcap); + } + + return ret_len; +} + +/* + * This function checks if the given pointer is valid entry of + * Tx BA Stream table. + */ +static int mwifiex_is_tx_ba_stream_ptr_valid(struct mwifiex_private *priv, + struct mwifiex_tx_ba_stream_tbl *tx_tbl_ptr) +{ + struct mwifiex_tx_ba_stream_tbl *tx_ba_tsr_tbl; + + list_for_each_entry(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr, list) { + if (tx_ba_tsr_tbl == tx_tbl_ptr) + return true; + } + + return false; +} + +/* + * This function deletes the given entry in Tx BA Stream table. + * + * The function also performs a validity check on the supplied + * pointer before trying to delete. + */ +void mwifiex_11n_delete_tx_ba_stream_tbl_entry(struct mwifiex_private *priv, + struct mwifiex_tx_ba_stream_tbl *tx_ba_tsr_tbl) +{ + if (!tx_ba_tsr_tbl && + mwifiex_is_tx_ba_stream_ptr_valid(priv, tx_ba_tsr_tbl)) + return; + + dev_dbg(priv->adapter->dev, "info: tx_ba_tsr_tbl %p\n", tx_ba_tsr_tbl); + + list_del(&tx_ba_tsr_tbl->list); + + kfree(tx_ba_tsr_tbl); +} + +/* + * This function deletes all the entries in Tx BA Stream table. + */ +void mwifiex_11n_delete_all_tx_ba_stream_tbl(struct mwifiex_private *priv) +{ + int i; + struct mwifiex_tx_ba_stream_tbl *del_tbl_ptr, *tmp_node; + unsigned long flags; + + spin_lock_irqsave(&priv->tx_ba_stream_tbl_lock, flags); + list_for_each_entry_safe(del_tbl_ptr, tmp_node, + &priv->tx_ba_stream_tbl_ptr, list) + mwifiex_11n_delete_tx_ba_stream_tbl_entry(priv, del_tbl_ptr); + spin_unlock_irqrestore(&priv->tx_ba_stream_tbl_lock, flags); + + INIT_LIST_HEAD(&priv->tx_ba_stream_tbl_ptr); + + for (i = 0; i < MAX_NUM_TID; ++i) + priv->aggr_prio_tbl[i].ampdu_ap = + priv->aggr_prio_tbl[i].ampdu_user; +} + +/* + * This function returns the pointer to an entry in BA Stream + * table which matches the given RA/TID pair. + */ +struct mwifiex_tx_ba_stream_tbl * +mwifiex_get_ba_tbl(struct mwifiex_private *priv, int tid, u8 *ra) +{ + struct mwifiex_tx_ba_stream_tbl *tx_ba_tsr_tbl; + unsigned long flags; + + spin_lock_irqsave(&priv->tx_ba_stream_tbl_lock, flags); + list_for_each_entry(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr, list) { + if (!memcmp(tx_ba_tsr_tbl->ra, ra, ETH_ALEN) && + tx_ba_tsr_tbl->tid == tid) { + spin_unlock_irqrestore(&priv->tx_ba_stream_tbl_lock, + flags); + return tx_ba_tsr_tbl; + } + } + spin_unlock_irqrestore(&priv->tx_ba_stream_tbl_lock, flags); + return NULL; +} + +/* + * This function creates an entry in Tx BA stream table for the + * given RA/TID pair. + */ +void mwifiex_create_ba_tbl(struct mwifiex_private *priv, u8 *ra, int tid, + enum mwifiex_ba_status ba_status) +{ + struct mwifiex_tx_ba_stream_tbl *new_node; + unsigned long flags; + + if (!mwifiex_get_ba_tbl(priv, tid, ra)) { + new_node = kzalloc(sizeof(struct mwifiex_tx_ba_stream_tbl), + GFP_ATOMIC); + if (!new_node) + return; + + INIT_LIST_HEAD(&new_node->list); + + new_node->tid = tid; + new_node->ba_status = ba_status; + memcpy(new_node->ra, ra, ETH_ALEN); + + spin_lock_irqsave(&priv->tx_ba_stream_tbl_lock, flags); + list_add_tail(&new_node->list, &priv->tx_ba_stream_tbl_ptr); + spin_unlock_irqrestore(&priv->tx_ba_stream_tbl_lock, flags); + } +} + +/* + * This function sends an add BA request to the given TID/RA pair. + */ +int mwifiex_send_addba(struct mwifiex_private *priv, int tid, u8 *peer_mac) +{ + struct host_cmd_ds_11n_addba_req add_ba_req; + static u8 dialog_tok; + int ret; + + dev_dbg(priv->adapter->dev, "cmd: %s: tid %d\n", __func__, tid); + + add_ba_req.block_ack_param_set = cpu_to_le16( + (u16) ((tid << BLOCKACKPARAM_TID_POS) | + (priv->add_ba_param. + tx_win_size << BLOCKACKPARAM_WINSIZE_POS) | + IMMEDIATE_BLOCK_ACK)); + add_ba_req.block_ack_tmo = cpu_to_le16((u16)priv->add_ba_param.timeout); + + ++dialog_tok; + + if (dialog_tok == 0) + dialog_tok = 1; + + add_ba_req.dialog_token = dialog_tok; + memcpy(&add_ba_req.peer_mac_addr, peer_mac, ETH_ALEN); + + /* We don't wait for the response of this command */ + ret = mwifiex_send_cmd_async(priv, HostCmd_CMD_11N_ADDBA_REQ, + 0, 0, &add_ba_req); + + return ret; +} + +/* + * This function sends a delete BA request to the given TID/RA pair. + */ +int mwifiex_send_delba(struct mwifiex_private *priv, int tid, u8 *peer_mac, + int initiator) +{ + struct host_cmd_ds_11n_delba delba; + int ret; + uint16_t del_ba_param_set; + + memset(&delba, 0, sizeof(delba)); + delba.del_ba_param_set = cpu_to_le16(tid << DELBA_TID_POS); + + del_ba_param_set = le16_to_cpu(delba.del_ba_param_set); + if (initiator) + del_ba_param_set |= IEEE80211_DELBA_PARAM_INITIATOR_MASK; + else + del_ba_param_set &= ~IEEE80211_DELBA_PARAM_INITIATOR_MASK; + + memcpy(&delba.peer_mac_addr, peer_mac, ETH_ALEN); + + /* We don't wait for the response of this command */ + ret = mwifiex_send_cmd_async(priv, HostCmd_CMD_11N_DELBA, + HostCmd_ACT_GEN_SET, 0, &delba); + + return ret; +} + +/* + * This function handles the command response of a delete BA request. + */ +void mwifiex_11n_delete_ba_stream(struct mwifiex_private *priv, u8 *del_ba) +{ + struct host_cmd_ds_11n_delba *cmd_del_ba = + (struct host_cmd_ds_11n_delba *) del_ba; + uint16_t del_ba_param_set = le16_to_cpu(cmd_del_ba->del_ba_param_set); + int tid; + + tid = del_ba_param_set >> DELBA_TID_POS; + + mwifiex_del_ba_tbl(priv, tid, cmd_del_ba->peer_mac_addr, + TYPE_DELBA_RECEIVE, INITIATOR_BIT(del_ba_param_set)); +} + +/* + * This function retrieves the Rx reordering table. + */ +int mwifiex_get_rx_reorder_tbl(struct mwifiex_private *priv, + struct mwifiex_ds_rx_reorder_tbl *buf) +{ + int i; + struct mwifiex_ds_rx_reorder_tbl *rx_reo_tbl = buf; + struct mwifiex_rx_reorder_tbl *rx_reorder_tbl_ptr; + int count = 0; + unsigned long flags; + + spin_lock_irqsave(&priv->rx_reorder_tbl_lock, flags); + list_for_each_entry(rx_reorder_tbl_ptr, &priv->rx_reorder_tbl_ptr, + list) { + rx_reo_tbl->tid = (u16) rx_reorder_tbl_ptr->tid; + memcpy(rx_reo_tbl->ta, rx_reorder_tbl_ptr->ta, ETH_ALEN); + rx_reo_tbl->start_win = rx_reorder_tbl_ptr->start_win; + rx_reo_tbl->win_size = rx_reorder_tbl_ptr->win_size; + for (i = 0; i < rx_reorder_tbl_ptr->win_size; ++i) { + if (rx_reorder_tbl_ptr->rx_reorder_ptr[i]) + rx_reo_tbl->buffer[i] = true; + else + rx_reo_tbl->buffer[i] = false; + } + rx_reo_tbl++; + count++; + + if (count >= MWIFIEX_MAX_RX_BASTREAM_SUPPORTED) + break; + } + spin_unlock_irqrestore(&priv->rx_reorder_tbl_lock, flags); + + return count; +} + +/* + * This function retrieves the Tx BA stream table. + */ +int mwifiex_get_tx_ba_stream_tbl(struct mwifiex_private *priv, + struct mwifiex_ds_tx_ba_stream_tbl *buf) +{ + struct mwifiex_tx_ba_stream_tbl *tx_ba_tsr_tbl; + struct mwifiex_ds_tx_ba_stream_tbl *rx_reo_tbl = buf; + int count = 0; + unsigned long flags; + + spin_lock_irqsave(&priv->tx_ba_stream_tbl_lock, flags); + list_for_each_entry(tx_ba_tsr_tbl, &priv->tx_ba_stream_tbl_ptr, list) { + rx_reo_tbl->tid = (u16) tx_ba_tsr_tbl->tid; + dev_dbg(priv->adapter->dev, "data: %s tid=%d\n", + __func__, rx_reo_tbl->tid); + memcpy(rx_reo_tbl->ra, tx_ba_tsr_tbl->ra, ETH_ALEN); + rx_reo_tbl++; + count++; + if (count >= MWIFIEX_MAX_TX_BASTREAM_SUPPORTED) + break; + } + spin_unlock_irqrestore(&priv->tx_ba_stream_tbl_lock, flags); + + return count; +} + +/* + * This function retrieves the entry for specific tx BA stream table by RA and + * deletes it. + */ +void mwifiex_del_tx_ba_stream_tbl_by_ra(struct mwifiex_private *priv, u8 *ra) +{ + struct mwifiex_tx_ba_stream_tbl *tbl, *tmp; + unsigned long flags; + + if (!ra) + return; + + spin_lock_irqsave(&priv->tx_ba_stream_tbl_lock, flags); + list_for_each_entry_safe(tbl, tmp, &priv->tx_ba_stream_tbl_ptr, list) { + if (!memcmp(tbl->ra, ra, ETH_ALEN)) { + spin_unlock_irqrestore(&priv->tx_ba_stream_tbl_lock, + flags); + mwifiex_11n_delete_tx_ba_stream_tbl_entry(priv, tbl); + spin_lock_irqsave(&priv->tx_ba_stream_tbl_lock, flags); + } + } + spin_unlock_irqrestore(&priv->tx_ba_stream_tbl_lock, flags); + + return; +} + +/* This function initializes the BlockACK setup information for given + * mwifiex_private structure. + */ +void mwifiex_set_ba_params(struct mwifiex_private *priv) +{ + priv->add_ba_param.timeout = MWIFIEX_DEFAULT_BLOCK_ACK_TIMEOUT; + + if (GET_BSS_ROLE(priv) == MWIFIEX_BSS_ROLE_UAP) { + priv->add_ba_param.tx_win_size = + MWIFIEX_UAP_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + MWIFIEX_UAP_AMPDU_DEF_RXWINSIZE; + } else { + priv->add_ba_param.tx_win_size = + MWIFIEX_STA_AMPDU_DEF_TXWINSIZE; + priv->add_ba_param.rx_win_size = + MWIFIEX_STA_AMPDU_DEF_RXWINSIZE; + } + + return; +} diff --git a/drivers/net/wireless/mwifiex/11n.h b/drivers/net/wireless/mwifiex/11n.h new file mode 100644 index 0000000..375db01 --- /dev/null +++ b/drivers/net/wireless/mwifiex/11n.h @@ -0,0 +1,168 @@ +/* + * Marvell Wireless LAN device driver: 802.11n + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _MWIFIEX_11N_H_ +#define _MWIFIEX_11N_H_ + +#include "11n_aggr.h" +#include "11n_rxreorder.h" +#include "wmm.h" + +int mwifiex_ret_11n_delba(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp); +int mwifiex_ret_11n_addba_req(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp); +int mwifiex_cmd_11n_cfg(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action, + struct mwifiex_ds_11n_tx_cfg *txcfg); +int mwifiex_cmd_append_11n_tlv(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc, + u8 **buffer); +void mwifiex_fill_cap_info(struct mwifiex_private *, u8 radio_type, + struct mwifiex_ie_types_htcap *); +int mwifiex_set_get_11n_htcap_cfg(struct mwifiex_private *priv, + u16 action, int *htcap_cfg); +void mwifiex_11n_delete_tx_ba_stream_tbl_entry(struct mwifiex_private *priv, + struct mwifiex_tx_ba_stream_tbl + *tx_tbl); +void mwifiex_11n_delete_all_tx_ba_stream_tbl(struct mwifiex_private *priv); +struct mwifiex_tx_ba_stream_tbl *mwifiex_get_ba_tbl(struct + mwifiex_private + *priv, int tid, + u8 *ra); +void mwifiex_create_ba_tbl(struct mwifiex_private *priv, u8 *ra, int tid, + enum mwifiex_ba_status ba_status); +int mwifiex_send_addba(struct mwifiex_private *priv, int tid, u8 *peer_mac); +int mwifiex_send_delba(struct mwifiex_private *priv, int tid, u8 *peer_mac, + int initiator); +void mwifiex_11n_delete_ba_stream(struct mwifiex_private *priv, u8 *del_ba); +int mwifiex_get_rx_reorder_tbl(struct mwifiex_private *priv, + struct mwifiex_ds_rx_reorder_tbl *buf); +int mwifiex_get_tx_ba_stream_tbl(struct mwifiex_private *priv, + struct mwifiex_ds_tx_ba_stream_tbl *buf); +int mwifiex_cmd_recfg_tx_buf(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + int cmd_action, u16 *buf_size); +int mwifiex_cmd_amsdu_aggr_ctrl(struct host_cmd_ds_command *cmd, + int cmd_action, + struct mwifiex_ds_11n_amsdu_aggr_ctrl *aa_ctrl); +void mwifiex_del_tx_ba_stream_tbl_by_ra(struct mwifiex_private *priv, u8 *ra); + +/* + * This function checks whether AMPDU is allowed or not for a particular TID. + */ +static inline u8 +mwifiex_is_ampdu_allowed(struct mwifiex_private *priv, int tid) +{ + return ((priv->aggr_prio_tbl[tid].ampdu_ap != BA_STREAM_NOT_ALLOWED) + ? true : false); +} + +/* + * This function checks whether AMSDU is allowed or not for a particular TID. + */ +static inline u8 +mwifiex_is_amsdu_allowed(struct mwifiex_private *priv, int tid) +{ + return (((priv->aggr_prio_tbl[tid].amsdu != BA_STREAM_NOT_ALLOWED) && + (priv->is_data_rate_auto || !(priv->bitmap_rates[2] & 0x03))) + ? true : false); +} + +/* + * This function checks whether a space is available for new BA stream or not. + */ +static inline u8 mwifiex_space_avail_for_new_ba_stream( + struct mwifiex_adapter *adapter) +{ + struct mwifiex_private *priv; + u8 i; + u32 ba_stream_num = 0; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (priv) + ba_stream_num += mwifiex_wmm_list_len( + &priv->tx_ba_stream_tbl_ptr); + } + + return ((ba_stream_num < + MWIFIEX_MAX_TX_BASTREAM_SUPPORTED) ? true : false); +} + +/* + * This function finds the correct Tx BA stream to delete. + * + * Upon successfully locating, both the TID and the RA are returned. + */ +static inline u8 +mwifiex_find_stream_to_delete(struct mwifiex_private *priv, int ptr_tid, + int *ptid, u8 *ra) +{ + int tid; + u8 ret = false; + struct mwifiex_tx_ba_stream_tbl *tx_tbl; + unsigned long flags; + + tid = priv->aggr_prio_tbl[ptr_tid].ampdu_user; + + spin_lock_irqsave(&priv->tx_ba_stream_tbl_lock, flags); + list_for_each_entry(tx_tbl, &priv->tx_ba_stream_tbl_ptr, list) { + if (tid > priv->aggr_prio_tbl[tx_tbl->tid].ampdu_user) { + tid = priv->aggr_prio_tbl[tx_tbl->tid].ampdu_user; + *ptid = tx_tbl->tid; + memcpy(ra, tx_tbl->ra, ETH_ALEN); + ret = true; + } + } + spin_unlock_irqrestore(&priv->tx_ba_stream_tbl_lock, flags); + + return ret; +} + +/* + * This function checks whether BA stream is set up or not. + */ +static inline int +mwifiex_is_ba_stream_setup(struct mwifiex_private *priv, + struct mwifiex_ra_list_tbl *ptr, int tid) +{ + struct mwifiex_tx_ba_stream_tbl *tx_tbl; + + tx_tbl = mwifiex_get_ba_tbl(priv, tid, ptr->ra); + if (tx_tbl && IS_BASTREAM_SETUP(tx_tbl)) + return true; + + return false; +} + +/* + * This function checks whether associated station is 11n enabled + */ +static inline int mwifiex_is_sta_11n_enabled(struct mwifiex_private *priv, + struct mwifiex_sta_node *node) +{ + + if (!node || (priv->bss_role != MWIFIEX_BSS_ROLE_UAP) || + !priv->ap_11n_enabled) + return 0; + + return node->is_11n_enabled; +} +#endif /* !_MWIFIEX_11N_H_ */ diff --git a/drivers/net/wireless/mwifiex/11n_aggr.c b/drivers/net/wireless/mwifiex/11n_aggr.c new file mode 100644 index 0000000..d69d024 --- /dev/null +++ b/drivers/net/wireless/mwifiex/11n_aggr.c @@ -0,0 +1,304 @@ +/* + * Marvell Wireless LAN device driver: 802.11n Aggregation + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" +#include "11n_aggr.h" + +/* + * Creates an AMSDU subframe for aggregation into one AMSDU packet. + * + * The resultant AMSDU subframe format is - + * + * +---- ~ -----+---- ~ ------+---- ~ -----+----- ~ -----+---- ~ -----+ + * | DA | SA | Length | SNAP header | MSDU | + * | data[0..5] | data[6..11] | | | data[14..] | + * +---- ~ -----+---- ~ ------+---- ~ -----+----- ~ -----+---- ~ -----+ + * <--6-bytes--> <--6-bytes--> <--2-bytes--><--8-bytes--> <--n-bytes--> + * + * This function also computes the amount of padding required to make the + * buffer length multiple of 4 bytes. + * + * Data => |DA|SA|SNAP-TYPE|........ .| + * MSDU => |DA|SA|Length|SNAP|...... ..| + */ +static int +mwifiex_11n_form_amsdu_pkt(struct sk_buff *skb_aggr, + struct sk_buff *skb_src, int *pad) + +{ + int dt_offset; + struct rfc_1042_hdr snap = { + 0xaa, /* LLC DSAP */ + 0xaa, /* LLC SSAP */ + 0x03, /* LLC CTRL */ + {0x00, 0x00, 0x00}, /* SNAP OUI */ + 0x0000 /* SNAP type */ + /* + * This field will be overwritten + * later with ethertype + */ + }; + struct tx_packet_hdr *tx_header; + + tx_header = (void *)skb_put(skb_aggr, sizeof(*tx_header)); + + /* Copy DA and SA */ + dt_offset = 2 * ETH_ALEN; + memcpy(&tx_header->eth803_hdr, skb_src->data, dt_offset); + + /* Copy SNAP header */ + snap.snap_type = *(u16 *) ((u8 *)skb_src->data + dt_offset); + dt_offset += sizeof(u16); + + memcpy(&tx_header->rfc1042_hdr, &snap, sizeof(struct rfc_1042_hdr)); + + skb_pull(skb_src, dt_offset); + + /* Update Length field */ + tx_header->eth803_hdr.h_proto = htons(skb_src->len + LLC_SNAP_LEN); + + /* Add payload */ + memcpy(skb_put(skb_aggr, skb_src->len), skb_src->data, skb_src->len); + + /* Add padding for new MSDU to start from 4 byte boundary */ + *pad = (4 - ((unsigned long)skb_aggr->tail & 0x3)) % 4; + + return skb_aggr->len + *pad; +} + +/* + * Adds TxPD to AMSDU header. + * + * Each AMSDU packet will contain one TxPD at the beginning, + * followed by multiple AMSDU subframes. + */ +static void +mwifiex_11n_form_amsdu_txpd(struct mwifiex_private *priv, + struct sk_buff *skb) +{ + struct txpd *local_tx_pd; + + skb_push(skb, sizeof(*local_tx_pd)); + + local_tx_pd = (struct txpd *) skb->data; + memset(local_tx_pd, 0, sizeof(struct txpd)); + + /* Original priority has been overwritten */ + local_tx_pd->priority = (u8) skb->priority; + local_tx_pd->pkt_delay_2ms = + mwifiex_wmm_compute_drv_pkt_delay(priv, skb); + local_tx_pd->bss_num = priv->bss_num; + local_tx_pd->bss_type = priv->bss_type; + /* Always zero as the data is followed by struct txpd */ + local_tx_pd->tx_pkt_offset = cpu_to_le16(sizeof(struct txpd)); + local_tx_pd->tx_pkt_type = cpu_to_le16(PKT_TYPE_AMSDU); + local_tx_pd->tx_pkt_length = cpu_to_le16(skb->len - + sizeof(*local_tx_pd)); + + if (local_tx_pd->tx_control == 0) + /* TxCtrl set by user or default */ + local_tx_pd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl); + + if (GET_BSS_ROLE(priv) == MWIFIEX_BSS_ROLE_STA && + priv->adapter->pps_uapsd_mode) { + if (true == mwifiex_check_last_packet_indication(priv)) { + priv->adapter->tx_lock_flag = true; + local_tx_pd->flags = + MWIFIEX_TxPD_POWER_MGMT_LAST_PACKET; + } + } +} + +/* + * Create aggregated packet. + * + * This function creates an aggregated MSDU packet, by combining buffers + * from the RA list. Each individual buffer is encapsulated as an AMSDU + * subframe and all such subframes are concatenated together to form the + * AMSDU packet. + * + * A TxPD is also added to the front of the resultant AMSDU packets for + * transmission. The resultant packets format is - + * + * +---- ~ ----+------ ~ ------+------ ~ ------+-..-+------ ~ ------+ + * | TxPD |AMSDU sub-frame|AMSDU sub-frame| .. |AMSDU sub-frame| + * | | 1 | 2 | .. | n | + * +---- ~ ----+------ ~ ------+------ ~ ------+ .. +------ ~ ------+ + */ +int +mwifiex_11n_aggregate_pkt(struct mwifiex_private *priv, + struct mwifiex_ra_list_tbl *pra_list, + int ptrindex, unsigned long ra_list_flags) + __releases(&priv->wmm.ra_list_spinlock) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct sk_buff *skb_aggr, *skb_src; + struct mwifiex_txinfo *tx_info_aggr, *tx_info_src; + int pad = 0, ret; + struct mwifiex_tx_param tx_param; + struct txpd *ptx_pd = NULL; + int headroom = adapter->iface_type == MWIFIEX_USB ? 0 : INTF_HEADER_LEN; + + skb_src = skb_peek(&pra_list->skb_head); + if (!skb_src) { + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, + ra_list_flags); + return 0; + } + + tx_info_src = MWIFIEX_SKB_TXCB(skb_src); + skb_aggr = dev_alloc_skb(adapter->tx_buf_size); + if (!skb_aggr) { + dev_err(adapter->dev, "%s: alloc skb_aggr\n", __func__); + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, + ra_list_flags); + return -1; + } + skb_reserve(skb_aggr, headroom + sizeof(struct txpd)); + tx_info_aggr = MWIFIEX_SKB_TXCB(skb_aggr); + + tx_info_aggr->bss_type = tx_info_src->bss_type; + tx_info_aggr->bss_num = tx_info_src->bss_num; + skb_aggr->priority = skb_src->priority; + + do { + /* Check if AMSDU can accommodate this MSDU */ + if (skb_tailroom(skb_aggr) < (skb_src->len + LLC_SNAP_LEN)) + break; + + skb_src = skb_dequeue(&pra_list->skb_head); + + pra_list->total_pkts_size -= skb_src->len; + + atomic_dec(&priv->wmm.tx_pkts_queued); + + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, + ra_list_flags); + mwifiex_11n_form_amsdu_pkt(skb_aggr, skb_src, &pad); + + mwifiex_write_data_complete(adapter, skb_src, 0, 0); + + spin_lock_irqsave(&priv->wmm.ra_list_spinlock, ra_list_flags); + + if (!mwifiex_is_ralist_valid(priv, pra_list, ptrindex)) { + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, + ra_list_flags); + return -1; + } + + if (skb_tailroom(skb_aggr) < pad) { + pad = 0; + break; + } + skb_put(skb_aggr, pad); + + skb_src = skb_peek(&pra_list->skb_head); + + } while (skb_src); + + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, ra_list_flags); + + /* Last AMSDU packet does not need padding */ + skb_trim(skb_aggr, skb_aggr->len - pad); + + /* Form AMSDU */ + mwifiex_11n_form_amsdu_txpd(priv, skb_aggr); + if (GET_BSS_ROLE(priv) == MWIFIEX_BSS_ROLE_STA) + ptx_pd = (struct txpd *)skb_aggr->data; + + skb_push(skb_aggr, headroom); + + if (adapter->iface_type == MWIFIEX_USB) { + adapter->data_sent = true; + ret = adapter->if_ops.host_to_card(adapter, MWIFIEX_USB_EP_DATA, + skb_aggr, NULL); + } else { + /* + * Padding per MSDU will affect the length of next + * packet and hence the exact length of next packet + * is uncertain here. + * + * Also, aggregation of transmission buffer, while + * downloading the data to the card, wont gain much + * on the AMSDU packets as the AMSDU packets utilizes + * the transmission buffer space to the maximum + * (adapter->tx_buf_size). + */ + tx_param.next_pkt_len = 0; + + ret = adapter->if_ops.host_to_card(adapter, MWIFIEX_TYPE_DATA, + skb_aggr, &tx_param); + } + switch (ret) { + case -EBUSY: + spin_lock_irqsave(&priv->wmm.ra_list_spinlock, ra_list_flags); + if (!mwifiex_is_ralist_valid(priv, pra_list, ptrindex)) { + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, + ra_list_flags); + mwifiex_write_data_complete(adapter, skb_aggr, 1, -1); + return -1; + } + if (GET_BSS_ROLE(priv) == MWIFIEX_BSS_ROLE_STA && + adapter->pps_uapsd_mode && adapter->tx_lock_flag) { + priv->adapter->tx_lock_flag = false; + if (ptx_pd) + ptx_pd->flags = 0; + } + + skb_queue_tail(&pra_list->skb_head, skb_aggr); + + pra_list->total_pkts_size += skb_aggr->len; + + atomic_inc(&priv->wmm.tx_pkts_queued); + + tx_info_aggr->flags |= MWIFIEX_BUF_FLAG_REQUEUED_PKT; + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, + ra_list_flags); + dev_dbg(adapter->dev, "data: -EBUSY is returned\n"); + break; + case -1: + if (adapter->iface_type != MWIFIEX_PCIE) + adapter->data_sent = false; + dev_err(adapter->dev, "%s: host_to_card failed: %#x\n", + __func__, ret); + adapter->dbg.num_tx_host_to_card_failure++; + mwifiex_write_data_complete(adapter, skb_aggr, 1, ret); + return 0; + case -EINPROGRESS: + if (adapter->iface_type != MWIFIEX_PCIE) + adapter->data_sent = false; + break; + case 0: + mwifiex_write_data_complete(adapter, skb_aggr, 1, ret); + break; + default: + break; + } + if (ret != -EBUSY) { + mwifiex_rotate_priolists(priv, pra_list, ptrindex); + } + + return 0; +} diff --git a/drivers/net/wireless/mwifiex/11n_aggr.h b/drivers/net/wireless/mwifiex/11n_aggr.h new file mode 100644 index 0000000..892098d --- /dev/null +++ b/drivers/net/wireless/mwifiex/11n_aggr.h @@ -0,0 +1,33 @@ +/* + * Marvell Wireless LAN device driver: 802.11n Aggregation + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _MWIFIEX_11N_AGGR_H_ +#define _MWIFIEX_11N_AGGR_H_ + +#define PKT_TYPE_AMSDU 0xE6 +#define MIN_NUM_AMSDU 2 + +int mwifiex_11n_deaggregate_pkt(struct mwifiex_private *priv, + struct sk_buff *skb); +int mwifiex_11n_aggregate_pkt(struct mwifiex_private *priv, + struct mwifiex_ra_list_tbl *ptr, + int ptr_index, unsigned long flags) + __releases(&priv->wmm.ra_list_spinlock); + +#endif /* !_MWIFIEX_11N_AGGR_H_ */ diff --git a/drivers/net/wireless/mwifiex/11n_rxreorder.c b/drivers/net/wireless/mwifiex/11n_rxreorder.c new file mode 100644 index 0000000..ada809f --- /dev/null +++ b/drivers/net/wireless/mwifiex/11n_rxreorder.c @@ -0,0 +1,667 @@ +/* + * Marvell Wireless LAN device driver: 802.11n RX Re-ordering + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" +#include "11n_rxreorder.h" + +/* + * This function dispatches all packets in the Rx reorder table until the + * start window. + * + * There could be holes in the buffer, which are skipped by the function. + * Since the buffer is linear, the function uses rotation to simulate + * circular buffer. + */ +static void +mwifiex_11n_dispatch_pkt(struct mwifiex_private *priv, + struct mwifiex_rx_reorder_tbl *tbl, int start_win) +{ + int pkt_to_send, i; + void *rx_tmp_ptr; + unsigned long flags; + + pkt_to_send = (start_win > tbl->start_win) ? + min((start_win - tbl->start_win), tbl->win_size) : + tbl->win_size; + + for (i = 0; i < pkt_to_send; ++i) { + spin_lock_irqsave(&priv->rx_pkt_lock, flags); + rx_tmp_ptr = NULL; + if (tbl->rx_reorder_ptr[i]) { + rx_tmp_ptr = tbl->rx_reorder_ptr[i]; + tbl->rx_reorder_ptr[i] = NULL; + } + spin_unlock_irqrestore(&priv->rx_pkt_lock, flags); + if (rx_tmp_ptr) { + if (priv->bss_role == MWIFIEX_BSS_ROLE_UAP) + mwifiex_handle_uap_rx_forward(priv, rx_tmp_ptr); + else + mwifiex_process_rx_packet(priv, rx_tmp_ptr); + } + } + + spin_lock_irqsave(&priv->rx_pkt_lock, flags); + /* + * We don't have a circular buffer, hence use rotation to simulate + * circular buffer + */ + for (i = 0; i < tbl->win_size - pkt_to_send; ++i) { + tbl->rx_reorder_ptr[i] = tbl->rx_reorder_ptr[pkt_to_send + i]; + tbl->rx_reorder_ptr[pkt_to_send + i] = NULL; + } + + tbl->start_win = start_win; + spin_unlock_irqrestore(&priv->rx_pkt_lock, flags); +} + +/* + * This function dispatches all packets in the Rx reorder table until + * a hole is found. + * + * The start window is adjusted automatically when a hole is located. + * Since the buffer is linear, the function uses rotation to simulate + * circular buffer. + */ +static void +mwifiex_11n_scan_and_dispatch(struct mwifiex_private *priv, + struct mwifiex_rx_reorder_tbl *tbl) +{ + int i, j, xchg; + void *rx_tmp_ptr; + unsigned long flags; + + for (i = 0; i < tbl->win_size; ++i) { + spin_lock_irqsave(&priv->rx_pkt_lock, flags); + if (!tbl->rx_reorder_ptr[i]) { + spin_unlock_irqrestore(&priv->rx_pkt_lock, flags); + break; + } + rx_tmp_ptr = tbl->rx_reorder_ptr[i]; + tbl->rx_reorder_ptr[i] = NULL; + spin_unlock_irqrestore(&priv->rx_pkt_lock, flags); + + if (priv->bss_role == MWIFIEX_BSS_ROLE_UAP) + mwifiex_handle_uap_rx_forward(priv, rx_tmp_ptr); + else + mwifiex_process_rx_packet(priv, rx_tmp_ptr); + } + + spin_lock_irqsave(&priv->rx_pkt_lock, flags); + /* + * We don't have a circular buffer, hence use rotation to simulate + * circular buffer + */ + if (i > 0) { + xchg = tbl->win_size - i; + for (j = 0; j < xchg; ++j) { + tbl->rx_reorder_ptr[j] = tbl->rx_reorder_ptr[i + j]; + tbl->rx_reorder_ptr[i + j] = NULL; + } + } + tbl->start_win = (tbl->start_win + i) & (MAX_TID_VALUE - 1); + spin_unlock_irqrestore(&priv->rx_pkt_lock, flags); +} + +/* + * This function deletes the Rx reorder table and frees the memory. + * + * The function stops the associated timer and dispatches all the + * pending packets in the Rx reorder table before deletion. + */ +static void +mwifiex_del_rx_reorder_entry(struct mwifiex_private *priv, + struct mwifiex_rx_reorder_tbl *tbl) +{ + unsigned long flags; + + if (!tbl) + return; + + mwifiex_11n_dispatch_pkt(priv, tbl, (tbl->start_win + tbl->win_size) & + (MAX_TID_VALUE - 1)); + + del_timer(&tbl->timer_context.timer); + + spin_lock_irqsave(&priv->rx_reorder_tbl_lock, flags); + list_del(&tbl->list); + spin_unlock_irqrestore(&priv->rx_reorder_tbl_lock, flags); + + kfree(tbl->rx_reorder_ptr); + kfree(tbl); +} + +/* + * This function returns the pointer to an entry in Rx reordering + * table which matches the given TA/TID pair. + */ +struct mwifiex_rx_reorder_tbl * +mwifiex_11n_get_rx_reorder_tbl(struct mwifiex_private *priv, int tid, u8 *ta) +{ + struct mwifiex_rx_reorder_tbl *tbl; + unsigned long flags; + + spin_lock_irqsave(&priv->rx_reorder_tbl_lock, flags); + list_for_each_entry(tbl, &priv->rx_reorder_tbl_ptr, list) { + if (!memcmp(tbl->ta, ta, ETH_ALEN) && tbl->tid == tid) { + spin_unlock_irqrestore(&priv->rx_reorder_tbl_lock, + flags); + return tbl; + } + } + spin_unlock_irqrestore(&priv->rx_reorder_tbl_lock, flags); + + return NULL; +} + +/* This function retrieves the pointer to an entry in Rx reordering + * table which matches the given TA and deletes it. + */ +void mwifiex_11n_del_rx_reorder_tbl_by_ta(struct mwifiex_private *priv, u8 *ta) +{ + struct mwifiex_rx_reorder_tbl *tbl, *tmp; + unsigned long flags; + + if (!ta) + return; + + spin_lock_irqsave(&priv->rx_reorder_tbl_lock, flags); + list_for_each_entry_safe(tbl, tmp, &priv->rx_reorder_tbl_ptr, list) { + if (!memcmp(tbl->ta, ta, ETH_ALEN)) { + spin_unlock_irqrestore(&priv->rx_reorder_tbl_lock, + flags); + mwifiex_del_rx_reorder_entry(priv, tbl); + spin_lock_irqsave(&priv->rx_reorder_tbl_lock, flags); + } + } + spin_unlock_irqrestore(&priv->rx_reorder_tbl_lock, flags); + + return; +} + +/* + * This function finds the last sequence number used in the packets + * buffered in Rx reordering table. + */ +static int +mwifiex_11n_find_last_seq_num(struct mwifiex_rx_reorder_tbl *rx_reorder_tbl_ptr) +{ + int i; + + for (i = (rx_reorder_tbl_ptr->win_size - 1); i >= 0; --i) + if (rx_reorder_tbl_ptr->rx_reorder_ptr[i]) + return i; + + return -1; +} + +/* + * This function flushes all the packets in Rx reordering table. + * + * The function checks if any packets are currently buffered in the + * table or not. In case there are packets available, it dispatches + * them and then dumps the Rx reordering table. + */ +static void +mwifiex_flush_data(unsigned long context) +{ + struct reorder_tmr_cnxt *ctx = + (struct reorder_tmr_cnxt *) context; + int start_win; + + start_win = mwifiex_11n_find_last_seq_num(ctx->ptr); + + if (start_win < 0) + return; + + dev_dbg(ctx->priv->adapter->dev, "info: flush data %d\n", start_win); + mwifiex_11n_dispatch_pkt(ctx->priv, ctx->ptr, + (ctx->ptr->start_win + start_win + 1) & + (MAX_TID_VALUE - 1)); +} + +/* + * This function creates an entry in Rx reordering table for the + * given TA/TID. + * + * The function also initializes the entry with sequence number, window + * size as well as initializes the timer. + * + * If the received TA/TID pair is already present, all the packets are + * dispatched and the window size is moved until the SSN. + */ +static void +mwifiex_11n_create_rx_reorder_tbl(struct mwifiex_private *priv, u8 *ta, + int tid, int win_size, int seq_num) +{ + int i; + struct mwifiex_rx_reorder_tbl *tbl, *new_node; + u16 last_seq = 0; + unsigned long flags; + struct mwifiex_sta_node *node; + + /* + * If we get a TID, ta pair which is already present dispatch all the + * the packets and move the window size until the ssn + */ + tbl = mwifiex_11n_get_rx_reorder_tbl(priv, tid, ta); + if (tbl) { + mwifiex_11n_dispatch_pkt(priv, tbl, seq_num); + return; + } + /* if !tbl then create one */ + new_node = kzalloc(sizeof(struct mwifiex_rx_reorder_tbl), GFP_KERNEL); + if (!new_node) + return; + + INIT_LIST_HEAD(&new_node->list); + new_node->tid = tid; + memcpy(new_node->ta, ta, ETH_ALEN); + new_node->start_win = seq_num; + + if (mwifiex_queuing_ra_based(priv)) { + dev_dbg(priv->adapter->dev, + "info: AP/ADHOC:last_seq=%d start_win=%d\n", + last_seq, new_node->start_win); + if (priv->bss_role == MWIFIEX_BSS_ROLE_UAP) { + node = mwifiex_get_sta_entry(priv, ta); + if (node) + last_seq = node->rx_seq[tid]; + } + } else { + last_seq = priv->rx_seq[tid]; + } + + if (last_seq != MWIFIEX_DEF_11N_RX_SEQ_NUM && + last_seq >= new_node->start_win) + new_node->start_win = last_seq + 1; + + new_node->win_size = win_size; + new_node->flags = 0; + + new_node->rx_reorder_ptr = kzalloc(sizeof(void *) * win_size, + GFP_KERNEL); + if (!new_node->rx_reorder_ptr) { + kfree((u8 *) new_node); + dev_err(priv->adapter->dev, + "%s: failed to alloc reorder_ptr\n", __func__); + return; + } + + new_node->timer_context.ptr = new_node; + new_node->timer_context.priv = priv; + + init_timer(&new_node->timer_context.timer); + new_node->timer_context.timer.function = mwifiex_flush_data; + new_node->timer_context.timer.data = + (unsigned long) &new_node->timer_context; + + for (i = 0; i < win_size; ++i) + new_node->rx_reorder_ptr[i] = NULL; + + spin_lock_irqsave(&priv->rx_reorder_tbl_lock, flags); + list_add_tail(&new_node->list, &priv->rx_reorder_tbl_ptr); + spin_unlock_irqrestore(&priv->rx_reorder_tbl_lock, flags); +} + +/* + * This function prepares command for adding a BA request. + * + * Preparation includes - + * - Setting command ID and proper size + * - Setting add BA request buffer + * - Ensuring correct endian-ness + */ +int mwifiex_cmd_11n_addba_req(struct host_cmd_ds_command *cmd, void *data_buf) +{ + struct host_cmd_ds_11n_addba_req *add_ba_req = &cmd->params.add_ba_req; + + cmd->command = cpu_to_le16(HostCmd_CMD_11N_ADDBA_REQ); + cmd->size = cpu_to_le16(sizeof(*add_ba_req) + S_DS_GEN); + memcpy(add_ba_req, data_buf, sizeof(*add_ba_req)); + + return 0; +} + +/* + * This function prepares command for adding a BA response. + * + * Preparation includes - + * - Setting command ID and proper size + * - Setting add BA response buffer + * - Ensuring correct endian-ness + */ +int mwifiex_cmd_11n_addba_rsp_gen(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + struct host_cmd_ds_11n_addba_req + *cmd_addba_req) +{ + struct host_cmd_ds_11n_addba_rsp *add_ba_rsp = &cmd->params.add_ba_rsp; + u8 tid; + int win_size; + uint16_t block_ack_param_set; + + cmd->command = cpu_to_le16(HostCmd_CMD_11N_ADDBA_RSP); + cmd->size = cpu_to_le16(sizeof(*add_ba_rsp) + S_DS_GEN); + + memcpy(add_ba_rsp->peer_mac_addr, cmd_addba_req->peer_mac_addr, + ETH_ALEN); + add_ba_rsp->dialog_token = cmd_addba_req->dialog_token; + add_ba_rsp->block_ack_tmo = cmd_addba_req->block_ack_tmo; + add_ba_rsp->ssn = cmd_addba_req->ssn; + + block_ack_param_set = le16_to_cpu(cmd_addba_req->block_ack_param_set); + tid = (block_ack_param_set & IEEE80211_ADDBA_PARAM_TID_MASK) + >> BLOCKACKPARAM_TID_POS; + add_ba_rsp->status_code = cpu_to_le16(ADDBA_RSP_STATUS_ACCEPT); + block_ack_param_set &= ~IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK; + /* We donot support AMSDU inside AMPDU, hence reset the bit */ + block_ack_param_set &= ~BLOCKACKPARAM_AMSDU_SUPP_MASK; + block_ack_param_set |= (priv->add_ba_param.rx_win_size << + BLOCKACKPARAM_WINSIZE_POS); + add_ba_rsp->block_ack_param_set = cpu_to_le16(block_ack_param_set); + win_size = (le16_to_cpu(add_ba_rsp->block_ack_param_set) + & IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK) + >> BLOCKACKPARAM_WINSIZE_POS; + cmd_addba_req->block_ack_param_set = cpu_to_le16(block_ack_param_set); + + mwifiex_11n_create_rx_reorder_tbl(priv, cmd_addba_req->peer_mac_addr, + tid, win_size, + le16_to_cpu(cmd_addba_req->ssn)); + return 0; +} + +/* + * This function prepares command for deleting a BA request. + * + * Preparation includes - + * - Setting command ID and proper size + * - Setting del BA request buffer + * - Ensuring correct endian-ness + */ +int mwifiex_cmd_11n_delba(struct host_cmd_ds_command *cmd, void *data_buf) +{ + struct host_cmd_ds_11n_delba *del_ba = &cmd->params.del_ba; + + cmd->command = cpu_to_le16(HostCmd_CMD_11N_DELBA); + cmd->size = cpu_to_le16(sizeof(*del_ba) + S_DS_GEN); + memcpy(del_ba, data_buf, sizeof(*del_ba)); + + return 0; +} + +/* + * This function identifies if Rx reordering is needed for a received packet. + * + * In case reordering is required, the function will do the reordering + * before sending it to kernel. + * + * The Rx reorder table is checked first with the received TID/TA pair. If + * not found, the received packet is dispatched immediately. But if found, + * the packet is reordered and all the packets in the updated Rx reordering + * table is dispatched until a hole is found. + * + * For sequence number less than the starting window, the packet is dropped. + */ +int mwifiex_11n_rx_reorder_pkt(struct mwifiex_private *priv, + u16 seq_num, u16 tid, + u8 *ta, u8 pkt_type, void *payload) +{ + struct mwifiex_rx_reorder_tbl *tbl; + int start_win, end_win, win_size; + u16 pkt_index; + + tbl = mwifiex_11n_get_rx_reorder_tbl(priv, tid, ta); + if (!tbl) { + if (pkt_type != PKT_TYPE_BAR) { + if (priv->bss_role == MWIFIEX_BSS_ROLE_UAP) + mwifiex_handle_uap_rx_forward(priv, payload); + else + mwifiex_process_rx_packet(priv, payload); + } + return 0; + } + start_win = tbl->start_win; + win_size = tbl->win_size; + end_win = ((start_win + win_size) - 1) & (MAX_TID_VALUE - 1); + del_timer(&tbl->timer_context.timer); + mod_timer(&tbl->timer_context.timer, + jiffies + msecs_to_jiffies(MIN_FLUSH_TIMER_MS * win_size)); + + /* + * If seq_num is less then starting win then ignore and drop the + * packet + */ + if (tbl->flags & RXREOR_FORCE_NO_DROP) { + dev_dbg(priv->adapter->dev, + "RXREOR_FORCE_NO_DROP when HS is activated\n"); + tbl->flags &= ~RXREOR_FORCE_NO_DROP; + } else { + if ((start_win + TWOPOW11) > (MAX_TID_VALUE - 1)) { + if (seq_num >= ((start_win + TWOPOW11) & + (MAX_TID_VALUE - 1)) && + seq_num < start_win) + return -1; + } else if ((seq_num < start_win) || + (seq_num > (start_win + TWOPOW11))) { + return -1; + } + } + + /* + * If this packet is a BAR we adjust seq_num as + * WinStart = seq_num + */ + if (pkt_type == PKT_TYPE_BAR) + seq_num = ((seq_num + win_size) - 1) & (MAX_TID_VALUE - 1); + + if (((end_win < start_win) && + (seq_num < start_win) && (seq_num > end_win)) || + ((end_win > start_win) && ((seq_num > end_win) || + (seq_num < start_win)))) { + end_win = seq_num; + if (((seq_num - win_size) + 1) >= 0) + start_win = (end_win - win_size) + 1; + else + start_win = (MAX_TID_VALUE - (win_size - seq_num)) + 1; + mwifiex_11n_dispatch_pkt(priv, tbl, start_win); + } + + if (pkt_type != PKT_TYPE_BAR) { + if (seq_num >= start_win) + pkt_index = seq_num - start_win; + else + pkt_index = (seq_num+MAX_TID_VALUE) - start_win; + + if (tbl->rx_reorder_ptr[pkt_index]) + return -1; + + tbl->rx_reorder_ptr[pkt_index] = payload; + } + + /* + * Dispatch all packets sequentially from start_win until a + * hole is found and adjust the start_win appropriately + */ + mwifiex_11n_scan_and_dispatch(priv, tbl); + + return 0; +} + +/* + * This function deletes an entry for a given TID/TA pair. + * + * The TID/TA are taken from del BA event body. + */ +void +mwifiex_del_ba_tbl(struct mwifiex_private *priv, int tid, u8 *peer_mac, + u8 type, int initiator) +{ + struct mwifiex_rx_reorder_tbl *tbl; + struct mwifiex_tx_ba_stream_tbl *ptx_tbl; + u8 cleanup_rx_reorder_tbl; + unsigned long flags; + + if (type == TYPE_DELBA_RECEIVE) + cleanup_rx_reorder_tbl = (initiator) ? true : false; + else + cleanup_rx_reorder_tbl = (initiator) ? false : true; + + dev_dbg(priv->adapter->dev, "event: DELBA: %pM tid=%d initiator=%d\n", + peer_mac, tid, initiator); + + if (cleanup_rx_reorder_tbl) { + tbl = mwifiex_11n_get_rx_reorder_tbl(priv, tid, + peer_mac); + if (!tbl) { + dev_dbg(priv->adapter->dev, + "event: TID, TA not found in table\n"); + return; + } + mwifiex_del_rx_reorder_entry(priv, tbl); + } else { + ptx_tbl = mwifiex_get_ba_tbl(priv, tid, peer_mac); + if (!ptx_tbl) { + dev_dbg(priv->adapter->dev, + "event: TID, RA not found in table\n"); + return; + } + + spin_lock_irqsave(&priv->tx_ba_stream_tbl_lock, flags); + mwifiex_11n_delete_tx_ba_stream_tbl_entry(priv, ptx_tbl); + spin_unlock_irqrestore(&priv->tx_ba_stream_tbl_lock, flags); + } +} + +/* + * This function handles the command response of an add BA response. + * + * Handling includes changing the header fields into CPU format and + * creating the stream, provided the add BA is accepted. + */ +int mwifiex_ret_11n_addba_resp(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + struct host_cmd_ds_11n_addba_rsp *add_ba_rsp = &resp->params.add_ba_rsp; + int tid, win_size; + struct mwifiex_rx_reorder_tbl *tbl; + uint16_t block_ack_param_set; + + block_ack_param_set = le16_to_cpu(add_ba_rsp->block_ack_param_set); + + tid = (block_ack_param_set & IEEE80211_ADDBA_PARAM_TID_MASK) + >> BLOCKACKPARAM_TID_POS; + /* + * Check if we had rejected the ADDBA, if yes then do not create + * the stream + */ + if (le16_to_cpu(add_ba_rsp->status_code) == BA_RESULT_SUCCESS) { + win_size = (block_ack_param_set & + IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK) + >> BLOCKACKPARAM_WINSIZE_POS; + + dev_dbg(priv->adapter->dev, + "cmd: ADDBA RSP: %pM tid=%d ssn=%d win_size=%d\n", + add_ba_rsp->peer_mac_addr, tid, + add_ba_rsp->ssn, win_size); + } else { + dev_err(priv->adapter->dev, "ADDBA RSP: failed %pM tid=%d)\n", + add_ba_rsp->peer_mac_addr, tid); + + tbl = mwifiex_11n_get_rx_reorder_tbl(priv, tid, + add_ba_rsp->peer_mac_addr); + if (tbl) + mwifiex_del_rx_reorder_entry(priv, tbl); + } + + return 0; +} + +/* + * This function handles BA stream timeout event by preparing and sending + * a command to the firmware. + */ +void mwifiex_11n_ba_stream_timeout(struct mwifiex_private *priv, + struct host_cmd_ds_11n_batimeout *event) +{ + struct host_cmd_ds_11n_delba delba; + + memset(&delba, 0, sizeof(struct host_cmd_ds_11n_delba)); + memcpy(delba.peer_mac_addr, event->peer_mac_addr, ETH_ALEN); + + delba.del_ba_param_set |= + cpu_to_le16((u16) event->tid << DELBA_TID_POS); + delba.del_ba_param_set |= cpu_to_le16( + (u16) event->origninator << DELBA_INITIATOR_POS); + delba.reason_code = cpu_to_le16(WLAN_REASON_QSTA_TIMEOUT); + mwifiex_send_cmd_async(priv, HostCmd_CMD_11N_DELBA, 0, 0, &delba); +} + +/* + * This function cleans up the Rx reorder table by deleting all the entries + * and re-initializing. + */ +void mwifiex_11n_cleanup_reorder_tbl(struct mwifiex_private *priv) +{ + struct mwifiex_rx_reorder_tbl *del_tbl_ptr, *tmp_node; + unsigned long flags; + + spin_lock_irqsave(&priv->rx_reorder_tbl_lock, flags); + list_for_each_entry_safe(del_tbl_ptr, tmp_node, + &priv->rx_reorder_tbl_ptr, list) { + spin_unlock_irqrestore(&priv->rx_reorder_tbl_lock, flags); + mwifiex_del_rx_reorder_entry(priv, del_tbl_ptr); + spin_lock_irqsave(&priv->rx_reorder_tbl_lock, flags); + } + spin_unlock_irqrestore(&priv->rx_reorder_tbl_lock, flags); + + INIT_LIST_HEAD(&priv->rx_reorder_tbl_ptr); + mwifiex_reset_11n_rx_seq_num(priv); +} + +/* + * This function updates all rx_reorder_tbl's flags. + */ +void mwifiex_update_rxreor_flags(struct mwifiex_adapter *adapter, u8 flags) +{ + struct mwifiex_private *priv; + struct mwifiex_rx_reorder_tbl *tbl; + unsigned long lock_flags; + int i; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (!priv) + continue; + if (list_empty(&priv->rx_reorder_tbl_ptr)) + continue; + + spin_lock_irqsave(&priv->rx_reorder_tbl_lock, lock_flags); + list_for_each_entry(tbl, &priv->rx_reorder_tbl_ptr, list) + tbl->flags = flags; + spin_unlock_irqrestore(&priv->rx_reorder_tbl_lock, lock_flags); + } + + return; +} diff --git a/drivers/net/wireless/mwifiex/11n_rxreorder.h b/drivers/net/wireless/mwifiex/11n_rxreorder.h new file mode 100644 index 0000000..4064041 --- /dev/null +++ b/drivers/net/wireless/mwifiex/11n_rxreorder.h @@ -0,0 +1,82 @@ +/* + * Marvell Wireless LAN device driver: 802.11n RX Re-ordering + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _MWIFIEX_11N_RXREORDER_H_ +#define _MWIFIEX_11N_RXREORDER_H_ + +#define MIN_FLUSH_TIMER_MS 50 + +#define PKT_TYPE_BAR 0xE7 +#define MAX_TID_VALUE (2 << 11) +#define TWOPOW11 (2 << 10) + +#define BLOCKACKPARAM_TID_POS 2 +#define BLOCKACKPARAM_AMSDU_SUPP_MASK 0x1 +#define BLOCKACKPARAM_WINSIZE_POS 6 +#define DELBA_TID_POS 12 +#define DELBA_INITIATOR_POS 11 +#define TYPE_DELBA_SENT 1 +#define TYPE_DELBA_RECEIVE 2 +#define IMMEDIATE_BLOCK_ACK 0x2 + +#define ADDBA_RSP_STATUS_ACCEPT 0 + +#define MWIFIEX_DEF_11N_RX_SEQ_NUM 0xffff +#define BA_SETUP_MAX_PACKET_THRESHOLD 16 +#define BA_SETUP_PACKET_OFFSET 16 + +enum mwifiex_rxreor_flags { + RXREOR_FORCE_NO_DROP = 1<<0, +}; + +static inline void mwifiex_reset_11n_rx_seq_num(struct mwifiex_private *priv) +{ + memset(priv->rx_seq, 0xff, sizeof(priv->rx_seq)); +} + +int mwifiex_11n_rx_reorder_pkt(struct mwifiex_private *, + u16 seqNum, + u16 tid, u8 *ta, + u8 pkttype, void *payload); +void mwifiex_del_ba_tbl(struct mwifiex_private *priv, int Tid, + u8 *PeerMACAddr, u8 type, int initiator); +void mwifiex_11n_ba_stream_timeout(struct mwifiex_private *priv, + struct host_cmd_ds_11n_batimeout *event); +int mwifiex_ret_11n_addba_resp(struct mwifiex_private *priv, + struct host_cmd_ds_command + *resp); +int mwifiex_cmd_11n_delba(struct host_cmd_ds_command *cmd, + void *data_buf); +int mwifiex_cmd_11n_addba_rsp_gen(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + struct host_cmd_ds_11n_addba_req + *cmd_addba_req); +int mwifiex_cmd_11n_addba_req(struct host_cmd_ds_command *cmd, + void *data_buf); +void mwifiex_11n_cleanup_reorder_tbl(struct mwifiex_private *priv); +struct mwifiex_rx_reorder_tbl *mwifiex_11n_get_rxreorder_tbl(struct + mwifiex_private + *priv, int tid, + u8 *ta); +struct mwifiex_rx_reorder_tbl * +mwifiex_11n_get_rx_reorder_tbl(struct mwifiex_private *priv, int tid, u8 *ta); +void mwifiex_11n_del_rx_reorder_tbl_by_ta(struct mwifiex_private *priv, u8 *ta); +void mwifiex_update_rxreor_flags(struct mwifiex_adapter *adapter, u8 flags); + +#endif /* _MWIFIEX_11N_RXREORDER_H_ */ diff --git a/drivers/net/wireless/mwifiex/Kconfig b/drivers/net/wireless/mwifiex/Kconfig new file mode 100644 index 0000000..d287945 --- /dev/null +++ b/drivers/net/wireless/mwifiex/Kconfig @@ -0,0 +1,47 @@ +config MWIFIEX + depends on !BACKPORT_KERNEL_2_6_27 + tristate "Marvell WiFi-Ex Driver" + depends on m + depends on CFG80211 + ---help--- + This adds support for wireless adapters based on Marvell + 802.11n chipsets. + + If you choose to build it as a module, it will be called + mwifiex. + +config MWIFIEX_SDIO + tristate "Marvell WiFi-Ex Driver for SD8786/SD8787/SD8797" + depends on m + depends on MWIFIEX && MMC + select BACKPORT_FW_LOADER + ---help--- + This adds support for wireless adapters based on Marvell + 8786/8787/8797 chipsets with SDIO interface. + + If you choose to build it as a module, it will be called + mwifiex_sdio. + +config MWIFIEX_PCIE + tristate "Marvell WiFi-Ex Driver for PCIE 8766/8897" + depends on m + depends on MWIFIEX && PCI + select BACKPORT_FW_LOADER + ---help--- + This adds support for wireless adapters based on Marvell + 8766/8897 chipsets with PCIe interface. + + If you choose to build it as a module, it will be called + mwifiex_pcie. + +config MWIFIEX_USB + tristate "Marvell WiFi-Ex Driver for USB8797" + depends on m + depends on MWIFIEX && USB + select BACKPORT_FW_LOADER + ---help--- + This adds support for wireless adapters based on Marvell + Avastar 88W8797 chipset with USB interface. + + If you choose to build it as a module, it will be called + mwifiex_usb. diff --git a/drivers/net/wireless/mwifiex/Makefile b/drivers/net/wireless/mwifiex/Makefile new file mode 100644 index 0000000..1aa0b9a --- /dev/null +++ b/drivers/net/wireless/mwifiex/Makefile @@ -0,0 +1,53 @@ +# +# Copyright (C) 2011, Marvell International Ltd. +# +# This software file (the "File") is distributed by Marvell International +# Ltd. under the terms of the GNU General Public License Version 2, June 1991 +# (the "License"). You may use, redistribute and/or modify this File in +# accordance with the terms and conditions of the License, a copy of which +# is available by writing to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the +# worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +# +# THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE +# IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE +# ARE EXPRESSLY DISCLAIMED. The License provides additional details about +# this warranty disclaimer. + + +mwifiex-y += main.o +mwifiex-y += init.o +mwifiex-y += cfp.o +mwifiex-y += cmdevt.o +mwifiex-y += util.o +mwifiex-y += txrx.o +mwifiex-y += wmm.o +mwifiex-y += 11n.o +mwifiex-y += 11ac.o +mwifiex-y += 11n_aggr.o +mwifiex-y += 11n_rxreorder.o +mwifiex-y += scan.o +mwifiex-y += join.o +mwifiex-y += sta_ioctl.o +mwifiex-y += sta_cmd.o +mwifiex-y += uap_cmd.o +mwifiex-y += ie.o +mwifiex-y += sta_cmdresp.o +mwifiex-y += sta_event.o +mwifiex-y += uap_event.o +mwifiex-y += sta_tx.o +mwifiex-y += sta_rx.o +mwifiex-y += uap_txrx.o +mwifiex-y += cfg80211.o +mwifiex-y += ethtool.o +mwifiex-$(CONFIG_DEBUG_FS) += debugfs.o +obj-$(CPTCFG_MWIFIEX) += mwifiex.o + +mwifiex_sdio-y += sdio.o +obj-$(CPTCFG_MWIFIEX_SDIO) += mwifiex_sdio.o + +mwifiex_pcie-y += pcie.o +obj-$(CPTCFG_MWIFIEX_PCIE) += mwifiex_pcie.o + +mwifiex_usb-y += usb.o +obj-$(CPTCFG_MWIFIEX_USB) += mwifiex_usb.o diff --git a/drivers/net/wireless/mwifiex/README b/drivers/net/wireless/mwifiex/README new file mode 100644 index 0000000..3d64613 --- /dev/null +++ b/drivers/net/wireless/mwifiex/README @@ -0,0 +1,203 @@ +# Copyright (C) 2011, Marvell International Ltd. +# +# This software file (the "File") is distributed by Marvell International +# Ltd. under the terms of the GNU General Public License Version 2, June 1991 +# (the "License"). You may use, redistribute and/or modify this File in +# accordance with the terms and conditions of the License, a copy of which +# is available by writing to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the +# worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +# +# THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE +# IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE +# ARE EXPRESSLY DISCLAIMED. The License provides additional details about +# this warranty disclaimer. + + +=============================================================================== + U S E R M A N U A L + +1) FOR DRIVER INSTALL + + a) Copy sd8787.bin to /lib/firmware/mrvl/ directory, + create the directory if it doesn't exist. + b) Install WLAN driver, + insmod mwifiex.ko + c) Uninstall WLAN driver, + ifconfig mlanX down + rmmod mwifiex + + +2) FOR DRIVER CONFIGURATION AND INFO + The configurations can be done either using the 'iw' user space + utility or debugfs. + + a) 'iw' utility commands + + Following are some useful iw commands:- + +iw dev mlan0 scan + + This command will trigger a scan. + The command will then display the scan table entries + +iw dev mlan0 connect -w [] [] [key 0:abcde d:1123456789a] + The above command can be used to connect to an AP with a particular SSID. + Ap's operating frequency can be specified or even the bssid. If the AP is using + WEP encryption, wep keys can be specified in the command. + Note: Every time before connecting to an AP scan command (iw dev mlan0 scan) should be used by user. + +iw dev mlan0 disconnect + This command will be used to disconnect from an AP. + + +iw dev mlan0 ibss join [fixed-freq] [fixed-bssid] [key 0:abcde] + The command will be used to join or create an ibss. Optionally, operating frequency, + bssid and the security related parameters can be specified while joining/creating + and ibss. + +iw dev mlan0 ibss leave + The command will be used to leave an ibss network. + +iw dev mlan0 link + The command will be used to get the connection status. The command will return parameters + such as SSID, operating frequency, rx/tx packets, signal strength, tx bitrate. + + Apart from the iw utility all standard configurations using the 'iwconfig' utility are also supported. + + b) Debugfs interface + + The debugfs interface can be used for configurations and for getting + some useful information from the driver. + The section below explains the configurations that can be + done. + + Mount debugfs to /debugfs mount point: + + mkdir /debugfs + mount -t debugfs debugfs /debugfs + + The information is provided in /debugfs/mwifiex/mlanX/: + +iw reg set + The command will be used to change the regulatory domain. + +iw reg get + The command will be used to get current regulatory domain. + +info + This command is used to get driver info. + + Usage: + cat info + + driver_name = "mwifiex" + driver_version = + interface_name = "mlanX" + bss_mode = "Ad-hoc" | "Managed" | "Auto" | "Unknown" + media_state = "Disconnected" | "Connected" + mac_address = <6-byte adapter MAC address> + multicase_count = + essid = + bssid = + channel = + region_code = + multicasr_address[n] = + num_tx_bytes = + num_rx_bytes = + num_tx_pkts = + num_rx_pkts = + num_tx_pkts_dropped = + num_rx_pkts_dropped = + num_tx_pkts_err = + num_rx_pkts_err = + carrier "on" | "off" + tx queue "stopped" | "started" + + The following debug info are provided in /debugfs/mwifiex/mlanX/debug: + + int_counter = + wmm_ac_vo = + wmm_ac_vi = + wmm_ac_be = + wmm_ac_bk = + tx_buf_size = + curr_tx_buf_size = + ps_mode = <0/1, CAM mode/PS mode> + ps_state = <0/1/2/3, full power state/awake state/pre-sleep state/sleep state> + is_deep_sleep = <0/1, not deep sleep state/deep sleep state> + wakeup_dev_req = <0/1, wakeup device not required/required> + wakeup_tries = + hs_configured = <0/1, host sleep not configured/configured> + hs_activated = <0/1, extended host sleep not activated/activated> + num_tx_timeout = + num_cmd_timeout = + timeout_cmd_id = + timeout_cmd_act = + last_cmd_id = + last_cmd_act = + last_cmd_index = <0 based last command index> + last_cmd_resp_id = + last_cmd_resp_index = <0 based last command response index> + last_event = + last_event_index = <0 based last event index> + num_cmd_h2c_fail = + num_cmd_sleep_cfm_fail = + num_tx_h2c_fail = + num_evt_deauth = + num_evt_disassoc = + num_evt_link_lost = + num_cmd_deauth = + num_cmd_assoc_ok = + num_cmd_assoc_fail = + cmd_sent = <0/1, send command resources available/sending command to device> + data_sent = <0/1, send data resources available/sending data to device> + mp_rd_bitmap = + mp_wr_bitmap = + cmd_resp_received = <0/1, no cmd response to process/response received and yet to process> + event_received = <0/1, no event to process/event received and yet to process> + cmd_pending = + tx_pending = + rx_pending = + + +3) FOR DRIVER CONFIGURATION + +regrdwr + This command is used to read/write the adapter register. + + Usage: + echo " [value]" > regrdwr + cat regrdwr + + where the parameters are, + : 1:MAC/SOC, 2:BBP, 3:RF, 4:PMIC, 5:CAU + : offset of register + [value]: value to be written + + Examples: + echo "1 0xa060" > regrdwr : Read the MAC register + echo "1 0xa060 0x12" > regrdwr : Write the MAC register + echo "1 0xa794 0x80000000" > regrdwr + : Write 0x80000000 to MAC register +rdeeprom + This command is used to read the EEPROM contents of the card. + + Usage: + echo " " > rdeeprom + cat rdeeprom + + where the parameters are, + : multiples of 4 + : 4-20, multiples of 4 + + Example: + echo "0 20" > rdeeprom : Read 20 bytes of EEPROM data from offset 0 + +getlog + This command is used to get the statistics available in the station. + Usage: + + cat getlog + +=============================================================================== diff --git a/drivers/net/wireless/mwifiex/cfg80211.c b/drivers/net/wireless/mwifiex/cfg80211.c new file mode 100644 index 0000000..e7f7cdf --- /dev/null +++ b/drivers/net/wireless/mwifiex/cfg80211.c @@ -0,0 +1,2529 @@ +/* + * Marvell Wireless LAN device driver: CFG80211 + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "cfg80211.h" +#include "main.h" + +static const struct ieee80211_iface_limit mwifiex_ap_sta_limits[] = { + { + .max = 2, .types = BIT(NL80211_IFTYPE_STATION), + }, + { + .max = 1, .types = BIT(NL80211_IFTYPE_AP), + }, +}; + +static const struct ieee80211_iface_combination mwifiex_iface_comb_ap_sta = { + .limits = mwifiex_ap_sta_limits, + .num_different_channels = 1, + .n_limits = ARRAY_SIZE(mwifiex_ap_sta_limits), + .max_interfaces = MWIFIEX_MAX_BSS_NUM, + .beacon_int_infra_match = true, +}; + +static const struct ieee80211_regdomain mwifiex_world_regdom_custom = { + .n_reg_rules = 7, + .alpha2 = "99", + .reg_rules = { + /* Channel 1 - 11 */ + REG_RULE(2412-10, 2462+10, 40, 3, 20, 0), + /* Channel 12 - 13 */ + REG_RULE(2467-10, 2472+10, 20, 3, 20, + NL80211_RRF_PASSIVE_SCAN | NL80211_RRF_NO_IBSS), + /* Channel 14 */ + REG_RULE(2484-10, 2484+10, 20, 3, 20, + NL80211_RRF_PASSIVE_SCAN | NL80211_RRF_NO_IBSS | + NL80211_RRF_NO_OFDM), + /* Channel 36 - 48 */ + REG_RULE(5180-10, 5240+10, 40, 3, 20, + NL80211_RRF_PASSIVE_SCAN | NL80211_RRF_NO_IBSS), + /* Channel 149 - 165 */ + REG_RULE(5745-10, 5825+10, 40, 3, 20, + NL80211_RRF_PASSIVE_SCAN | NL80211_RRF_NO_IBSS), + /* Channel 52 - 64 */ + REG_RULE(5260-10, 5320+10, 40, 3, 30, + NL80211_RRF_PASSIVE_SCAN | NL80211_RRF_NO_IBSS | + NL80211_RRF_DFS), + /* Channel 100 - 140 */ + REG_RULE(5500-10, 5700+10, 40, 3, 30, + NL80211_RRF_PASSIVE_SCAN | NL80211_RRF_NO_IBSS | + NL80211_RRF_DFS), + } +}; + +/* + * This function maps the nl802.11 channel type into driver channel type. + * + * The mapping is as follows - + * NL80211_CHAN_NO_HT -> IEEE80211_HT_PARAM_CHA_SEC_NONE + * NL80211_CHAN_HT20 -> IEEE80211_HT_PARAM_CHA_SEC_NONE + * NL80211_CHAN_HT40PLUS -> IEEE80211_HT_PARAM_CHA_SEC_ABOVE + * NL80211_CHAN_HT40MINUS -> IEEE80211_HT_PARAM_CHA_SEC_BELOW + * Others -> IEEE80211_HT_PARAM_CHA_SEC_NONE + */ +u8 mwifiex_chan_type_to_sec_chan_offset(enum nl80211_channel_type chan_type) +{ + switch (chan_type) { + case NL80211_CHAN_NO_HT: + case NL80211_CHAN_HT20: + return IEEE80211_HT_PARAM_CHA_SEC_NONE; + case NL80211_CHAN_HT40PLUS: + return IEEE80211_HT_PARAM_CHA_SEC_ABOVE; + case NL80211_CHAN_HT40MINUS: + return IEEE80211_HT_PARAM_CHA_SEC_BELOW; + default: + return IEEE80211_HT_PARAM_CHA_SEC_NONE; + } +} + +/* + * This function checks whether WEP is set. + */ +static int +mwifiex_is_alg_wep(u32 cipher) +{ + switch (cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + return 1; + default: + break; + } + + return 0; +} + +/* + * This function retrieves the private structure from kernel wiphy structure. + */ +static void *mwifiex_cfg80211_get_adapter(struct wiphy *wiphy) +{ + return (void *) (*(unsigned long *) wiphy_priv(wiphy)); +} + +/* + * CFG802.11 operation handler to delete a network key. + */ +static int +mwifiex_cfg80211_del_key(struct wiphy *wiphy, struct net_device *netdev, + u8 key_index, bool pairwise, const u8 *mac_addr) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(netdev); + const u8 bc_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + const u8 *peer_mac = pairwise ? mac_addr : bc_mac; + + if (mwifiex_set_encode(priv, NULL, NULL, 0, key_index, peer_mac, 1)) { + wiphy_err(wiphy, "deleting the crypto keys\n"); + return -EFAULT; + } + + wiphy_dbg(wiphy, "info: crypto keys deleted\n"); + return 0; +} + +/* + * This function forms an skb for management frame. + */ +static int +mwifiex_form_mgmt_frame(struct sk_buff *skb, const u8 *buf, size_t len) +{ + u8 addr[ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + u16 pkt_len; + u32 tx_control = 0, pkt_type = PKT_TYPE_MGMT; + struct timeval tv; + + pkt_len = len + ETH_ALEN; + + skb_reserve(skb, MWIFIEX_MIN_DATA_HEADER_LEN + + MWIFIEX_MGMT_FRAME_HEADER_SIZE + sizeof(pkt_len)); + memcpy(skb_push(skb, sizeof(pkt_len)), &pkt_len, sizeof(pkt_len)); + + memcpy(skb_push(skb, sizeof(tx_control)), + &tx_control, sizeof(tx_control)); + + memcpy(skb_push(skb, sizeof(pkt_type)), &pkt_type, sizeof(pkt_type)); + + /* Add packet data and address4 */ + memcpy(skb_put(skb, sizeof(struct ieee80211_hdr_3addr)), buf, + sizeof(struct ieee80211_hdr_3addr)); + memcpy(skb_put(skb, ETH_ALEN), addr, ETH_ALEN); + memcpy(skb_put(skb, len - sizeof(struct ieee80211_hdr_3addr)), + buf + sizeof(struct ieee80211_hdr_3addr), + len - sizeof(struct ieee80211_hdr_3addr)); + + skb->priority = LOW_PRIO_TID; + do_gettimeofday(&tv); + skb->tstamp = timeval_to_ktime(tv); + + return 0; +} + +/* + * CFG802.11 operation handler to transmit a management frame. + */ +static int +mwifiex_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, + struct ieee80211_channel *chan, bool offchan, + unsigned int wait, const u8 *buf, size_t len, + bool no_cck, bool dont_wait_for_ack, u64 *cookie) +{ + struct sk_buff *skb; + u16 pkt_len; + const struct ieee80211_mgmt *mgmt; + struct mwifiex_private *priv = mwifiex_netdev_get_priv(wdev->netdev); + + if (!buf || !len) { + wiphy_err(wiphy, "invalid buffer and length\n"); + return -EFAULT; + } + + mgmt = (const struct ieee80211_mgmt *)buf; + if (GET_BSS_ROLE(priv) != MWIFIEX_BSS_ROLE_STA && + ieee80211_is_probe_resp(mgmt->frame_control)) { + /* Since we support offload probe resp, we need to skip probe + * resp in AP or GO mode */ + wiphy_dbg(wiphy, + "info: skip to send probe resp in AP or GO mode\n"); + return 0; + } + + pkt_len = len + ETH_ALEN; + skb = dev_alloc_skb(MWIFIEX_MIN_DATA_HEADER_LEN + + MWIFIEX_MGMT_FRAME_HEADER_SIZE + + pkt_len + sizeof(pkt_len)); + + if (!skb) { + wiphy_err(wiphy, "allocate skb failed for management frame\n"); + return -ENOMEM; + } + + mwifiex_form_mgmt_frame(skb, buf, len); + mwifiex_queue_tx_pkt(priv, skb); + + *cookie = prandom_u32() | 1; + cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, true, GFP_ATOMIC); + + wiphy_dbg(wiphy, "info: management frame transmitted\n"); + return 0; +} + +/* + * CFG802.11 operation handler to register a mgmt frame. + */ +static void +mwifiex_cfg80211_mgmt_frame_register(struct wiphy *wiphy, + struct wireless_dev *wdev, + u16 frame_type, bool reg) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(wdev->netdev); + + if (reg) + priv->mgmt_frame_mask |= BIT(frame_type >> 4); + else + priv->mgmt_frame_mask &= ~BIT(frame_type >> 4); + + mwifiex_send_cmd_async(priv, HostCmd_CMD_MGMT_FRAME_REG, + HostCmd_ACT_GEN_SET, 0, &priv->mgmt_frame_mask); + + wiphy_dbg(wiphy, "info: mgmt frame registered\n"); +} + +/* + * CFG802.11 operation handler to remain on channel. + */ +static int +mwifiex_cfg80211_remain_on_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, + struct ieee80211_channel *chan, + unsigned int duration, u64 *cookie) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(wdev->netdev); + int ret; + + if (!chan || !cookie) { + wiphy_err(wiphy, "Invalid parameter for ROC\n"); + return -EINVAL; + } + + if (priv->roc_cfg.cookie) { + wiphy_dbg(wiphy, "info: ongoing ROC, cookie = 0x%llu\n", + priv->roc_cfg.cookie); + return -EBUSY; + } + + ret = mwifiex_remain_on_chan_cfg(priv, HostCmd_ACT_GEN_SET, chan, + duration); + + if (!ret) { + *cookie = prandom_u32() | 1; + priv->roc_cfg.cookie = *cookie; + priv->roc_cfg.chan = *chan; + + cfg80211_ready_on_channel(wdev, *cookie, chan, + duration, GFP_ATOMIC); + + wiphy_dbg(wiphy, "info: ROC, cookie = 0x%llx\n", *cookie); + } + + return ret; +} + +/* + * CFG802.11 operation handler to cancel remain on channel. + */ +static int +mwifiex_cfg80211_cancel_remain_on_channel(struct wiphy *wiphy, + struct wireless_dev *wdev, u64 cookie) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(wdev->netdev); + int ret; + + if (cookie != priv->roc_cfg.cookie) + return -ENOENT; + + ret = mwifiex_remain_on_chan_cfg(priv, HostCmd_ACT_GEN_REMOVE, + &priv->roc_cfg.chan, 0); + + if (!ret) { + cfg80211_remain_on_channel_expired(wdev, cookie, + &priv->roc_cfg.chan, + GFP_ATOMIC); + + memset(&priv->roc_cfg, 0, sizeof(struct mwifiex_roc_cfg)); + + wiphy_dbg(wiphy, "info: cancel ROC, cookie = 0x%llx\n", cookie); + } + + return ret; +} + +/* + * CFG802.11 operation handler to set Tx power. + */ +static int +mwifiex_cfg80211_set_tx_power(struct wiphy *wiphy, + struct wireless_dev *wdev, + enum nl80211_tx_power_setting type, + int mbm) +{ + struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy); + struct mwifiex_private *priv; + struct mwifiex_power_cfg power_cfg; + int dbm = MBM_TO_DBM(mbm); + + if (type == NL80211_TX_POWER_FIXED) { + power_cfg.is_power_auto = 0; + power_cfg.power_level = dbm; + } else { + power_cfg.is_power_auto = 1; + } + + priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); + + return mwifiex_set_tx_power(priv, &power_cfg); +} + +/* + * CFG802.11 operation handler to set Power Save option. + * + * The timeout value, if provided, is currently ignored. + */ +static int +mwifiex_cfg80211_set_power_mgmt(struct wiphy *wiphy, + struct net_device *dev, + bool enabled, int timeout) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + u32 ps_mode; + + if (timeout) + wiphy_dbg(wiphy, + "info: ignore timeout value for IEEE Power Save\n"); + + ps_mode = enabled; + + return mwifiex_drv_set_power(priv, &ps_mode); +} + +/* + * CFG802.11 operation handler to set the default network key. + */ +static int +mwifiex_cfg80211_set_default_key(struct wiphy *wiphy, struct net_device *netdev, + u8 key_index, bool unicast, + bool multicast) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(netdev); + + /* Return if WEP key not configured */ + if (!priv->sec_info.wep_enabled) + return 0; + + if (priv->bss_type == MWIFIEX_BSS_TYPE_UAP) { + priv->wep_key_curr_index = key_index; + } else if (mwifiex_set_encode(priv, NULL, NULL, 0, key_index, + NULL, 0)) { + wiphy_err(wiphy, "set default Tx key index\n"); + return -EFAULT; + } + + return 0; +} + +/* + * CFG802.11 operation handler to add a network key. + */ +static int +mwifiex_cfg80211_add_key(struct wiphy *wiphy, struct net_device *netdev, + u8 key_index, bool pairwise, const u8 *mac_addr, + struct key_params *params) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(netdev); + struct mwifiex_wep_key *wep_key; + const u8 bc_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + const u8 *peer_mac = pairwise ? mac_addr : bc_mac; + + if (GET_BSS_ROLE(priv) == MWIFIEX_BSS_ROLE_UAP && + (params->cipher == WLAN_CIPHER_SUITE_WEP40 || + params->cipher == WLAN_CIPHER_SUITE_WEP104)) { + if (params->key && params->key_len) { + wep_key = &priv->wep_key[key_index]; + memset(wep_key, 0, sizeof(struct mwifiex_wep_key)); + memcpy(wep_key->key_material, params->key, + params->key_len); + wep_key->key_index = key_index; + wep_key->key_length = params->key_len; + priv->sec_info.wep_enabled = 1; + } + return 0; + } + + if (mwifiex_set_encode(priv, params, params->key, params->key_len, + key_index, peer_mac, 0)) { + wiphy_err(wiphy, "crypto keys added\n"); + return -EFAULT; + } + + return 0; +} + +/* + * This function sends domain information to the firmware. + * + * The following information are passed to the firmware - + * - Country codes + * - Sub bands (first channel, number of channels, maximum Tx power) + */ +static int mwifiex_send_domain_info_cmd_fw(struct wiphy *wiphy) +{ + u8 no_of_triplet = 0; + struct ieee80211_country_ie_triplet *t; + u8 no_of_parsed_chan = 0; + u8 first_chan = 0, next_chan = 0, max_pwr = 0; + u8 i, flag = 0; + enum ieee80211_band band; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy); + struct mwifiex_private *priv; + struct mwifiex_802_11d_domain_reg *domain_info = &adapter->domain_reg; + + /* Set country code */ + domain_info->country_code[0] = adapter->country_code[0]; + domain_info->country_code[1] = adapter->country_code[1]; + domain_info->country_code[2] = ' '; + + band = mwifiex_band_to_radio_type(adapter->config_bands); + if (!wiphy->bands[band]) { + wiphy_err(wiphy, "11D: setting domain info in FW\n"); + return -1; + } + + sband = wiphy->bands[band]; + + for (i = 0; i < sband->n_channels ; i++) { + ch = &sband->channels[i]; + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + + if (!flag) { + flag = 1; + first_chan = (u32) ch->hw_value; + next_chan = first_chan; + max_pwr = ch->max_power; + no_of_parsed_chan = 1; + continue; + } + + if (ch->hw_value == next_chan + 1 && + ch->max_power == max_pwr) { + next_chan++; + no_of_parsed_chan++; + } else { + t = &domain_info->triplet[no_of_triplet]; + t->chans.first_channel = first_chan; + t->chans.num_channels = no_of_parsed_chan; + t->chans.max_power = max_pwr; + no_of_triplet++; + first_chan = (u32) ch->hw_value; + next_chan = first_chan; + max_pwr = ch->max_power; + no_of_parsed_chan = 1; + } + } + + if (flag) { + t = &domain_info->triplet[no_of_triplet]; + t->chans.first_channel = first_chan; + t->chans.num_channels = no_of_parsed_chan; + t->chans.max_power = max_pwr; + no_of_triplet++; + } + + domain_info->no_of_triplet = no_of_triplet; + + priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); + + if (mwifiex_send_cmd_async(priv, HostCmd_CMD_802_11D_DOMAIN_INFO, + HostCmd_ACT_GEN_SET, 0, NULL)) { + wiphy_err(wiphy, "11D: setting domain info in FW\n"); + return -1; + } + + return 0; +} + +/* + * CFG802.11 regulatory domain callback function. + * + * This function is called when the regulatory domain is changed due to the + * following reasons - + * - Set by driver + * - Set by system core + * - Set by user + * - Set bt Country IE + */ +static void mwifiex_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy); + + wiphy_dbg(wiphy, "info: cfg80211 regulatory domain callback for %c%c\n", + request->alpha2[0], request->alpha2[1]); + + memcpy(adapter->country_code, request->alpha2, sizeof(request->alpha2)); + + switch (request->initiator) { + case NL80211_REGDOM_SET_BY_DRIVER: + case NL80211_REGDOM_SET_BY_CORE: + case NL80211_REGDOM_SET_BY_USER: + break; + /* Todo: apply driver specific changes in channel flags based + on the request initiator if necessary. */ + case NL80211_REGDOM_SET_BY_COUNTRY_IE: + break; + } + mwifiex_send_domain_info_cmd_fw(wiphy); +} + +/* + * This function sets the fragmentation threshold. + * + * The fragmentation threshold value must lie between MWIFIEX_FRAG_MIN_VALUE + * and MWIFIEX_FRAG_MAX_VALUE. + */ +static int +mwifiex_set_frag(struct mwifiex_private *priv, u32 frag_thr) +{ + if (frag_thr < MWIFIEX_FRAG_MIN_VALUE || + frag_thr > MWIFIEX_FRAG_MAX_VALUE) + frag_thr = MWIFIEX_FRAG_MAX_VALUE; + + return mwifiex_send_cmd_sync(priv, HostCmd_CMD_802_11_SNMP_MIB, + HostCmd_ACT_GEN_SET, FRAG_THRESH_I, + &frag_thr); +} + +/* + * This function sets the RTS threshold. + + * The rts value must lie between MWIFIEX_RTS_MIN_VALUE + * and MWIFIEX_RTS_MAX_VALUE. + */ +static int +mwifiex_set_rts(struct mwifiex_private *priv, u32 rts_thr) +{ + if (rts_thr < MWIFIEX_RTS_MIN_VALUE || rts_thr > MWIFIEX_RTS_MAX_VALUE) + rts_thr = MWIFIEX_RTS_MAX_VALUE; + + return mwifiex_send_cmd_sync(priv, HostCmd_CMD_802_11_SNMP_MIB, + HostCmd_ACT_GEN_SET, RTS_THRESH_I, + &rts_thr); +} + +/* + * CFG802.11 operation handler to set wiphy parameters. + * + * This function can be used to set the RTS threshold and the + * Fragmentation threshold of the driver. + */ +static int +mwifiex_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) +{ + struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy); + struct mwifiex_private *priv; + struct mwifiex_uap_bss_param *bss_cfg; + int ret, bss_started, i; + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + + switch (priv->bss_role) { + case MWIFIEX_BSS_ROLE_UAP: + bss_cfg = kzalloc(sizeof(struct mwifiex_uap_bss_param), + GFP_KERNEL); + if (!bss_cfg) + return -ENOMEM; + + mwifiex_set_sys_config_invalid_data(bss_cfg); + + if (changed & WIPHY_PARAM_RTS_THRESHOLD) + bss_cfg->rts_threshold = wiphy->rts_threshold; + if (changed & WIPHY_PARAM_FRAG_THRESHOLD) + bss_cfg->frag_threshold = wiphy->frag_threshold; + if (changed & WIPHY_PARAM_RETRY_LONG) + bss_cfg->retry_limit = wiphy->retry_long; + + bss_started = priv->bss_started; + + ret = mwifiex_send_cmd_sync(priv, + HostCmd_CMD_UAP_BSS_STOP, + HostCmd_ACT_GEN_SET, 0, + NULL); + if (ret) { + wiphy_err(wiphy, "Failed to stop the BSS\n"); + kfree(bss_cfg); + return ret; + } + + ret = mwifiex_send_cmd_async(priv, + HostCmd_CMD_UAP_SYS_CONFIG, + HostCmd_ACT_GEN_SET, + UAP_BSS_PARAMS_I, bss_cfg); + + kfree(bss_cfg); + + if (ret) { + wiphy_err(wiphy, "Failed to set bss config\n"); + return ret; + } + + if (!bss_started) + break; + + ret = mwifiex_send_cmd_async(priv, + HostCmd_CMD_UAP_BSS_START, + HostCmd_ACT_GEN_SET, 0, + NULL); + if (ret) { + wiphy_err(wiphy, "Failed to start BSS\n"); + return ret; + } + + break; + case MWIFIEX_BSS_ROLE_STA: + if (changed & WIPHY_PARAM_RTS_THRESHOLD) { + ret = mwifiex_set_rts(priv, + wiphy->rts_threshold); + if (ret) + return ret; + } + if (changed & WIPHY_PARAM_FRAG_THRESHOLD) { + ret = mwifiex_set_frag(priv, + wiphy->frag_threshold); + if (ret) + return ret; + } + break; + } + } + + return 0; +} + +static int +mwifiex_cfg80211_deinit_p2p(struct mwifiex_private *priv) +{ + u16 mode = P2P_MODE_DISABLE; + + if (GET_BSS_ROLE(priv) != MWIFIEX_BSS_ROLE_STA) + mwifiex_set_bss_role(priv, MWIFIEX_BSS_ROLE_STA); + + if (mwifiex_send_cmd_sync(priv, HostCmd_CMD_P2P_MODE_CFG, + HostCmd_ACT_GEN_SET, 0, &mode)) + return -1; + + return 0; +} + +/* + * This function initializes the functionalities for P2P client. + * The P2P client initialization sequence is: + * disable -> device -> client + */ +static int +mwifiex_cfg80211_init_p2p_client(struct mwifiex_private *priv) +{ + u16 mode; + + if (mwifiex_cfg80211_deinit_p2p(priv)) + return -1; + + mode = P2P_MODE_DEVICE; + if (mwifiex_send_cmd_sync(priv, HostCmd_CMD_P2P_MODE_CFG, + HostCmd_ACT_GEN_SET, 0, &mode)) + return -1; + + mode = P2P_MODE_CLIENT; + if (mwifiex_send_cmd_sync(priv, HostCmd_CMD_P2P_MODE_CFG, + HostCmd_ACT_GEN_SET, 0, &mode)) + return -1; + + return 0; +} + +/* + * This function initializes the functionalities for P2P GO. + * The P2P GO initialization sequence is: + * disable -> device -> GO + */ +static int +mwifiex_cfg80211_init_p2p_go(struct mwifiex_private *priv) +{ + u16 mode; + + if (mwifiex_cfg80211_deinit_p2p(priv)) + return -1; + + mode = P2P_MODE_DEVICE; + if (mwifiex_send_cmd_sync(priv, HostCmd_CMD_P2P_MODE_CFG, + HostCmd_ACT_GEN_SET, 0, &mode)) + return -1; + + mode = P2P_MODE_GO; + if (mwifiex_send_cmd_sync(priv, HostCmd_CMD_P2P_MODE_CFG, + HostCmd_ACT_GEN_SET, 0, &mode)) + return -1; + + if (GET_BSS_ROLE(priv) != MWIFIEX_BSS_ROLE_UAP) + mwifiex_set_bss_role(priv, MWIFIEX_BSS_ROLE_UAP); + + return 0; +} + +/* + * CFG802.11 operation handler to change interface type. + */ +static int +mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + struct net_device *dev, + enum nl80211_iftype type, u32 *flags, + struct vif_params *params) +{ + int ret; + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + + switch (dev->ieee80211_ptr->iftype) { + case NL80211_IFTYPE_ADHOC: + switch (type) { + case NL80211_IFTYPE_STATION: + break; + case NL80211_IFTYPE_UNSPECIFIED: + wiphy_warn(wiphy, "%s: kept type as IBSS\n", dev->name); + case NL80211_IFTYPE_ADHOC: /* This shouldn't happen */ + return 0; + case NL80211_IFTYPE_AP: + default: + wiphy_err(wiphy, "%s: changing to %d not supported\n", + dev->name, type); + return -EOPNOTSUPP; + } + break; + case NL80211_IFTYPE_STATION: + switch (type) { + case NL80211_IFTYPE_ADHOC: + break; + case NL80211_IFTYPE_P2P_CLIENT: + if (mwifiex_cfg80211_init_p2p_client(priv)) + return -EFAULT; + dev->ieee80211_ptr->iftype = type; + return 0; + case NL80211_IFTYPE_P2P_GO: + if (mwifiex_cfg80211_init_p2p_go(priv)) + return -EFAULT; + dev->ieee80211_ptr->iftype = type; + return 0; + case NL80211_IFTYPE_UNSPECIFIED: + wiphy_warn(wiphy, "%s: kept type as STA\n", dev->name); + case NL80211_IFTYPE_STATION: /* This shouldn't happen */ + return 0; + case NL80211_IFTYPE_AP: + default: + wiphy_err(wiphy, "%s: changing to %d not supported\n", + dev->name, type); + return -EOPNOTSUPP; + } + break; + case NL80211_IFTYPE_AP: + switch (type) { + case NL80211_IFTYPE_UNSPECIFIED: + wiphy_warn(wiphy, "%s: kept type as AP\n", dev->name); + case NL80211_IFTYPE_AP: /* This shouldn't happen */ + return 0; + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_STATION: + default: + wiphy_err(wiphy, "%s: changing to %d not supported\n", + dev->name, type); + return -EOPNOTSUPP; + } + break; + case NL80211_IFTYPE_P2P_CLIENT: + case NL80211_IFTYPE_P2P_GO: + switch (type) { + case NL80211_IFTYPE_STATION: + if (mwifiex_cfg80211_deinit_p2p(priv)) + return -EFAULT; + dev->ieee80211_ptr->iftype = type; + return 0; + default: + return -EOPNOTSUPP; + } + break; + default: + wiphy_err(wiphy, "%s: unknown iftype: %d\n", + dev->name, dev->ieee80211_ptr->iftype); + return -EOPNOTSUPP; + } + + dev->ieee80211_ptr->iftype = type; + priv->bss_mode = type; + mwifiex_deauthenticate(priv, NULL); + + priv->sec_info.authentication_mode = NL80211_AUTHTYPE_OPEN_SYSTEM; + + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_SET_BSS_MODE, + HostCmd_ACT_GEN_SET, 0, NULL); + + return ret; +} + +static void +mwifiex_parse_htinfo(struct mwifiex_private *priv, u8 tx_htinfo, + struct rate_info *rate) +{ + struct mwifiex_adapter *adapter = priv->adapter; + + if (adapter->is_hw_11ac_capable) { + /* bit[1-0]: 00=LG 01=HT 10=VHT */ + if (tx_htinfo & BIT(0)) { + /* HT */ + rate->mcs = priv->tx_rate; + rate->flags |= RATE_INFO_FLAGS_MCS; + } + if (tx_htinfo & BIT(1)) { + /* VHT */ + rate->mcs = priv->tx_rate & 0x0F; + rate->flags |= RATE_INFO_FLAGS_VHT_MCS; + } + + if (tx_htinfo & (BIT(1) | BIT(0))) { + /* HT or VHT */ + switch (tx_htinfo & (BIT(3) | BIT(2))) { + case 0: + /* This will be 20MHz */ + break; + case (BIT(2)): + rate->flags |= RATE_INFO_FLAGS_40_MHZ_WIDTH; + break; + case (BIT(3)): + rate->flags |= RATE_INFO_FLAGS_80_MHZ_WIDTH; + break; + case (BIT(3) | BIT(2)): + rate->flags |= RATE_INFO_FLAGS_160_MHZ_WIDTH; + break; + } + + if (tx_htinfo & BIT(4)) + rate->flags |= RATE_INFO_FLAGS_SHORT_GI; + + if ((priv->tx_rate >> 4) == 1) + rate->nss = 2; + else + rate->nss = 1; + } + } else { + /* + * Bit 0 in tx_htinfo indicates that current Tx rate + * is 11n rate. Valid MCS index values for us are 0 to 15. + */ + if ((tx_htinfo & BIT(0)) && (priv->tx_rate < 16)) { + rate->mcs = priv->tx_rate; + rate->flags |= RATE_INFO_FLAGS_MCS; + if (tx_htinfo & BIT(1)) + rate->flags |= RATE_INFO_FLAGS_40_MHZ_WIDTH; + if (tx_htinfo & BIT(2)) + rate->flags |= RATE_INFO_FLAGS_SHORT_GI; + } + } +} + +/* + * This function dumps the station information on a buffer. + * + * The following information are shown - + * - Total bytes transmitted + * - Total bytes received + * - Total packets transmitted + * - Total packets received + * - Signal quality level + * - Transmission rate + */ +static int +mwifiex_dump_station_info(struct mwifiex_private *priv, + struct station_info *sinfo) +{ + u32 rate; + + sinfo->filled = STATION_INFO_RX_BYTES | STATION_INFO_TX_BYTES | + STATION_INFO_RX_PACKETS | STATION_INFO_TX_PACKETS | + STATION_INFO_TX_BITRATE | + STATION_INFO_SIGNAL | STATION_INFO_SIGNAL_AVG; + + /* Get signal information from the firmware */ + if (mwifiex_send_cmd_sync(priv, HostCmd_CMD_RSSI_INFO, + HostCmd_ACT_GEN_GET, 0, NULL)) { + dev_err(priv->adapter->dev, "failed to get signal information\n"); + return -EFAULT; + } + + if (mwifiex_drv_get_data_rate(priv, &rate)) { + dev_err(priv->adapter->dev, "getting data rate\n"); + return -EFAULT; + } + + /* Get DTIM period information from firmware */ + mwifiex_send_cmd_sync(priv, HostCmd_CMD_802_11_SNMP_MIB, + HostCmd_ACT_GEN_GET, DTIM_PERIOD_I, + &priv->dtim_period); + + mwifiex_parse_htinfo(priv, priv->tx_htinfo, &sinfo->txrate); + + sinfo->signal_avg = priv->bcn_rssi_avg; + sinfo->rx_bytes = priv->stats.rx_bytes; + sinfo->tx_bytes = priv->stats.tx_bytes; + sinfo->rx_packets = priv->stats.rx_packets; + sinfo->tx_packets = priv->stats.tx_packets; + sinfo->signal = priv->bcn_rssi_avg; + /* bit rate is in 500 kb/s units. Convert it to 100kb/s units */ + sinfo->txrate.legacy = rate * 5; + + if (priv->bss_mode == NL80211_IFTYPE_STATION) { + sinfo->filled |= STATION_INFO_BSS_PARAM; + sinfo->bss_param.flags = 0; + if (priv->curr_bss_params.bss_descriptor.cap_info_bitmap & + WLAN_CAPABILITY_SHORT_PREAMBLE) + sinfo->bss_param.flags |= + BSS_PARAM_FLAGS_SHORT_PREAMBLE; + if (priv->curr_bss_params.bss_descriptor.cap_info_bitmap & + WLAN_CAPABILITY_SHORT_SLOT_TIME) + sinfo->bss_param.flags |= + BSS_PARAM_FLAGS_SHORT_SLOT_TIME; + sinfo->bss_param.dtim_period = priv->dtim_period; + sinfo->bss_param.beacon_interval = + priv->curr_bss_params.bss_descriptor.beacon_period; + } + + return 0; +} + +/* + * CFG802.11 operation handler to get station information. + * + * This function only works in connected mode, and dumps the + * requested station information, if available. + */ +static int +mwifiex_cfg80211_get_station(struct wiphy *wiphy, struct net_device *dev, + u8 *mac, struct station_info *sinfo) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + + if (!priv->media_connected) + return -ENOENT; + if (memcmp(mac, priv->cfg_bssid, ETH_ALEN)) + return -ENOENT; + + return mwifiex_dump_station_info(priv, sinfo); +} + +/* + * CFG802.11 operation handler to dump station information. + */ +static int +mwifiex_cfg80211_dump_station(struct wiphy *wiphy, struct net_device *dev, + int idx, u8 *mac, struct station_info *sinfo) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + + if (!priv->media_connected || idx) + return -ENOENT; + + memcpy(mac, priv->cfg_bssid, ETH_ALEN); + + return mwifiex_dump_station_info(priv, sinfo); +} + +/* Supported rates to be advertised to the cfg80211 */ +static struct ieee80211_rate mwifiex_rates[] = { + {.bitrate = 10, .hw_value = 2, }, + {.bitrate = 20, .hw_value = 4, }, + {.bitrate = 55, .hw_value = 11, }, + {.bitrate = 110, .hw_value = 22, }, + {.bitrate = 60, .hw_value = 12, }, + {.bitrate = 90, .hw_value = 18, }, + {.bitrate = 120, .hw_value = 24, }, + {.bitrate = 180, .hw_value = 36, }, + {.bitrate = 240, .hw_value = 48, }, + {.bitrate = 360, .hw_value = 72, }, + {.bitrate = 480, .hw_value = 96, }, + {.bitrate = 540, .hw_value = 108, }, +}; + +/* Channel definitions to be advertised to cfg80211 */ +static struct ieee80211_channel mwifiex_channels_2ghz[] = { + {.center_freq = 2412, .hw_value = 1, }, + {.center_freq = 2417, .hw_value = 2, }, + {.center_freq = 2422, .hw_value = 3, }, + {.center_freq = 2427, .hw_value = 4, }, + {.center_freq = 2432, .hw_value = 5, }, + {.center_freq = 2437, .hw_value = 6, }, + {.center_freq = 2442, .hw_value = 7, }, + {.center_freq = 2447, .hw_value = 8, }, + {.center_freq = 2452, .hw_value = 9, }, + {.center_freq = 2457, .hw_value = 10, }, + {.center_freq = 2462, .hw_value = 11, }, + {.center_freq = 2467, .hw_value = 12, }, + {.center_freq = 2472, .hw_value = 13, }, + {.center_freq = 2484, .hw_value = 14, }, +}; + +static struct ieee80211_supported_band mwifiex_band_2ghz = { + .channels = mwifiex_channels_2ghz, + .n_channels = ARRAY_SIZE(mwifiex_channels_2ghz), + .bitrates = mwifiex_rates, + .n_bitrates = ARRAY_SIZE(mwifiex_rates), +}; + +static struct ieee80211_channel mwifiex_channels_5ghz[] = { + {.center_freq = 5040, .hw_value = 8, }, + {.center_freq = 5060, .hw_value = 12, }, + {.center_freq = 5080, .hw_value = 16, }, + {.center_freq = 5170, .hw_value = 34, }, + {.center_freq = 5190, .hw_value = 38, }, + {.center_freq = 5210, .hw_value = 42, }, + {.center_freq = 5230, .hw_value = 46, }, + {.center_freq = 5180, .hw_value = 36, }, + {.center_freq = 5200, .hw_value = 40, }, + {.center_freq = 5220, .hw_value = 44, }, + {.center_freq = 5240, .hw_value = 48, }, + {.center_freq = 5260, .hw_value = 52, }, + {.center_freq = 5280, .hw_value = 56, }, + {.center_freq = 5300, .hw_value = 60, }, + {.center_freq = 5320, .hw_value = 64, }, + {.center_freq = 5500, .hw_value = 100, }, + {.center_freq = 5520, .hw_value = 104, }, + {.center_freq = 5540, .hw_value = 108, }, + {.center_freq = 5560, .hw_value = 112, }, + {.center_freq = 5580, .hw_value = 116, }, + {.center_freq = 5600, .hw_value = 120, }, + {.center_freq = 5620, .hw_value = 124, }, + {.center_freq = 5640, .hw_value = 128, }, + {.center_freq = 5660, .hw_value = 132, }, + {.center_freq = 5680, .hw_value = 136, }, + {.center_freq = 5700, .hw_value = 140, }, + {.center_freq = 5745, .hw_value = 149, }, + {.center_freq = 5765, .hw_value = 153, }, + {.center_freq = 5785, .hw_value = 157, }, + {.center_freq = 5805, .hw_value = 161, }, + {.center_freq = 5825, .hw_value = 165, }, +}; + +static struct ieee80211_supported_band mwifiex_band_5ghz = { + .channels = mwifiex_channels_5ghz, + .n_channels = ARRAY_SIZE(mwifiex_channels_5ghz), + .bitrates = mwifiex_rates + 4, + .n_bitrates = ARRAY_SIZE(mwifiex_rates) - 4, +}; + + +/* Supported crypto cipher suits to be advertised to cfg80211 */ +static const u32 mwifiex_cipher_suites[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, + WLAN_CIPHER_SUITE_AES_CMAC, +}; + +/* Supported mgmt frame types to be advertised to cfg80211 */ +static const struct ieee80211_txrx_stypes +mwifiex_mgmt_stypes[NUM_NL80211_IFTYPES] = { + [NL80211_IFTYPE_STATION] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4), + }, + [NL80211_IFTYPE_AP] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4), + }, + [NL80211_IFTYPE_P2P_CLIENT] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4), + }, + [NL80211_IFTYPE_P2P_GO] = { + .tx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_RESP >> 4), + .rx = BIT(IEEE80211_STYPE_ACTION >> 4) | + BIT(IEEE80211_STYPE_PROBE_REQ >> 4), + }, +}; + +/* + * CFG802.11 operation handler for setting bit rates. + * + * Function configures data rates to firmware using bitrate mask + * provided by cfg80211. + */ +static int mwifiex_cfg80211_set_bitrate_mask(struct wiphy *wiphy, + struct net_device *dev, + const u8 *peer, + const struct cfg80211_bitrate_mask *mask) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + u16 bitmap_rates[MAX_BITMAP_RATES_SIZE]; + enum ieee80211_band band; + + if (!priv->media_connected) { + dev_err(priv->adapter->dev, + "Can not set Tx data rate in disconnected state\n"); + return -EINVAL; + } + + band = mwifiex_band_to_radio_type(priv->curr_bss_params.band); + + memset(bitmap_rates, 0, sizeof(bitmap_rates)); + + /* Fill HR/DSSS rates. */ + if (band == IEEE80211_BAND_2GHZ) + bitmap_rates[0] = mask->control[band].legacy & 0x000f; + + /* Fill OFDM rates */ + if (band == IEEE80211_BAND_2GHZ) + bitmap_rates[1] = (mask->control[band].legacy & 0x0ff0) >> 4; + else + bitmap_rates[1] = mask->control[band].legacy; + + /* Fill MCS rates */ + bitmap_rates[2] = mask->control[band].mcs[0]; + if (priv->adapter->hw_dev_mcs_support == HT_STREAM_2X2) + bitmap_rates[2] |= mask->control[band].mcs[1] << 8; + + return mwifiex_send_cmd_sync(priv, HostCmd_CMD_TX_RATE_CFG, + HostCmd_ACT_GEN_SET, 0, bitmap_rates); +} + +/* + * CFG802.11 operation handler for connection quality monitoring. + * + * This function subscribes/unsubscribes HIGH_RSSI and LOW_RSSI + * events to FW. + */ +static int mwifiex_cfg80211_set_cqm_rssi_config(struct wiphy *wiphy, + struct net_device *dev, + s32 rssi_thold, u32 rssi_hyst) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + struct mwifiex_ds_misc_subsc_evt subsc_evt; + + priv->cqm_rssi_thold = rssi_thold; + priv->cqm_rssi_hyst = rssi_hyst; + + memset(&subsc_evt, 0x00, sizeof(struct mwifiex_ds_misc_subsc_evt)); + subsc_evt.events = BITMASK_BCN_RSSI_LOW | BITMASK_BCN_RSSI_HIGH; + + /* Subscribe/unsubscribe low and high rssi events */ + if (rssi_thold && rssi_hyst) { + subsc_evt.action = HostCmd_ACT_BITWISE_SET; + subsc_evt.bcn_l_rssi_cfg.abs_value = abs(rssi_thold); + subsc_evt.bcn_h_rssi_cfg.abs_value = abs(rssi_thold); + subsc_evt.bcn_l_rssi_cfg.evt_freq = 1; + subsc_evt.bcn_h_rssi_cfg.evt_freq = 1; + return mwifiex_send_cmd_sync(priv, + HostCmd_CMD_802_11_SUBSCRIBE_EVENT, + 0, 0, &subsc_evt); + } else { + subsc_evt.action = HostCmd_ACT_BITWISE_CLR; + return mwifiex_send_cmd_sync(priv, + HostCmd_CMD_802_11_SUBSCRIBE_EVENT, + 0, 0, &subsc_evt); + } + + return 0; +} + +/* cfg80211 operation handler for change_beacon. + * Function retrieves and sets modified management IEs to FW. + */ +static int mwifiex_cfg80211_change_beacon(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_beacon_data *data) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + + if (GET_BSS_ROLE(priv) != MWIFIEX_BSS_ROLE_UAP) { + wiphy_err(wiphy, "%s: bss_type mismatched\n", __func__); + return -EINVAL; + } + + if (!priv->bss_started) { + wiphy_err(wiphy, "%s: bss not started\n", __func__); + return -EINVAL; + } + + if (mwifiex_set_mgmt_ies(priv, data)) { + wiphy_err(wiphy, "%s: setting mgmt ies failed\n", __func__); + return -EFAULT; + } + + return 0; +} + +static int +mwifiex_cfg80211_set_antenna(struct wiphy *wiphy, u32 tx_ant, u32 rx_ant) +{ + struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy); + struct mwifiex_private *priv = mwifiex_get_priv(adapter, + MWIFIEX_BSS_ROLE_ANY); + struct mwifiex_ds_ant_cfg ant_cfg; + + if (!tx_ant || !rx_ant) + return -EOPNOTSUPP; + + if (adapter->hw_dev_mcs_support != HT_STREAM_2X2) { + /* Not a MIMO chip. User should provide specific antenna number + * for Tx/Rx path or enable all antennas for diversity + */ + if (tx_ant != rx_ant) + return -EOPNOTSUPP; + + if ((tx_ant & (tx_ant - 1)) && + (tx_ant != BIT(adapter->number_of_antenna) - 1)) + return -EOPNOTSUPP; + + if ((tx_ant == BIT(adapter->number_of_antenna) - 1) && + (priv->adapter->number_of_antenna > 1)) { + tx_ant = RF_ANTENNA_AUTO; + rx_ant = RF_ANTENNA_AUTO; + } + } + + ant_cfg.tx_ant = tx_ant; + ant_cfg.rx_ant = rx_ant; + + return mwifiex_send_cmd_sync(priv, HostCmd_CMD_RF_ANTENNA, + HostCmd_ACT_GEN_SET, 0, &ant_cfg); +} + +/* cfg80211 operation handler for stop ap. + * Function stops BSS running at uAP interface. + */ +static int mwifiex_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + + if (mwifiex_del_mgmt_ies(priv)) + wiphy_err(wiphy, "Failed to delete mgmt IEs!\n"); + + priv->ap_11n_enabled = 0; + + if (mwifiex_send_cmd_sync(priv, HostCmd_CMD_UAP_BSS_STOP, + HostCmd_ACT_GEN_SET, 0, NULL)) { + wiphy_err(wiphy, "Failed to stop the BSS\n"); + return -1; + } + + return 0; +} + +/* cfg80211 operation handler for start_ap. + * Function sets beacon period, DTIM period, SSID and security into + * AP config structure. + * AP is configured with these settings and BSS is started. + */ +static int mwifiex_cfg80211_start_ap(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_ap_settings *params) +{ + struct mwifiex_uap_bss_param *bss_cfg; + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + u8 config_bands = 0; + + if (GET_BSS_ROLE(priv) != MWIFIEX_BSS_ROLE_UAP) + return -1; + if (mwifiex_set_mgmt_ies(priv, ¶ms->beacon)) + return -1; + + bss_cfg = kzalloc(sizeof(struct mwifiex_uap_bss_param), GFP_KERNEL); + if (!bss_cfg) + return -ENOMEM; + + mwifiex_set_sys_config_invalid_data(bss_cfg); + + if (params->beacon_interval) + bss_cfg->beacon_period = params->beacon_interval; + if (params->dtim_period) + bss_cfg->dtim_period = params->dtim_period; + + if (params->ssid && params->ssid_len) { + memcpy(bss_cfg->ssid.ssid, params->ssid, params->ssid_len); + bss_cfg->ssid.ssid_len = params->ssid_len; + } + + switch (params->hidden_ssid) { + case NL80211_HIDDEN_SSID_NOT_IN_USE: + bss_cfg->bcast_ssid_ctl = 1; + break; + case NL80211_HIDDEN_SSID_ZERO_LEN: + bss_cfg->bcast_ssid_ctl = 0; + break; + case NL80211_HIDDEN_SSID_ZERO_CONTENTS: + /* firmware doesn't support this type of hidden SSID */ + default: + kfree(bss_cfg); + return -EINVAL; + } + + bss_cfg->channel = ieee80211_frequency_to_channel( + params->chandef.chan->center_freq); + + /* Set appropriate bands */ + if (params->chandef.chan->band == IEEE80211_BAND_2GHZ) { + bss_cfg->band_cfg = BAND_CONFIG_BG; + config_bands = BAND_B | BAND_G; + + if (params->chandef.width > NL80211_CHAN_WIDTH_20_NOHT) + config_bands |= BAND_GN; + + if (params->chandef.width > NL80211_CHAN_WIDTH_40) + config_bands |= BAND_GAC; + } else { + bss_cfg->band_cfg = BAND_CONFIG_A; + config_bands = BAND_A; + + if (params->chandef.width > NL80211_CHAN_WIDTH_20_NOHT) + config_bands |= BAND_AN; + + if (params->chandef.width > NL80211_CHAN_WIDTH_40) + config_bands |= BAND_AAC; + } + + if (!((config_bands | priv->adapter->fw_bands) & + ~priv->adapter->fw_bands)) + priv->adapter->config_bands = config_bands; + + mwifiex_set_uap_rates(bss_cfg, params); + mwifiex_send_domain_info_cmd_fw(wiphy); + + if (mwifiex_set_secure_params(priv, bss_cfg, params)) { + kfree(bss_cfg); + wiphy_err(wiphy, "Failed to parse secuirty parameters!\n"); + return -1; + } + + mwifiex_set_ht_params(priv, bss_cfg, params); + + if (priv->adapter->is_hw_11ac_capable) { + mwifiex_set_vht_params(priv, bss_cfg, params); + mwifiex_set_vht_width(priv, params->chandef.width, + priv->ap_11ac_enabled); + } + + if (priv->ap_11ac_enabled) + mwifiex_set_11ac_ba_params(priv); + else + mwifiex_set_ba_params(priv); + + mwifiex_set_wmm_params(priv, bss_cfg, params); + + if (params->inactivity_timeout > 0) { + /* sta_ao_timer/ps_sta_ao_timer is in unit of 100ms */ + bss_cfg->sta_ao_timer = 10 * params->inactivity_timeout; + bss_cfg->ps_sta_ao_timer = 10 * params->inactivity_timeout; + } + + if (mwifiex_send_cmd_sync(priv, HostCmd_CMD_UAP_BSS_STOP, + HostCmd_ACT_GEN_SET, 0, NULL)) { + wiphy_err(wiphy, "Failed to stop the BSS\n"); + kfree(bss_cfg); + return -1; + } + + if (mwifiex_send_cmd_async(priv, HostCmd_CMD_UAP_SYS_CONFIG, + HostCmd_ACT_GEN_SET, + UAP_BSS_PARAMS_I, bss_cfg)) { + wiphy_err(wiphy, "Failed to set the SSID\n"); + kfree(bss_cfg); + return -1; + } + + kfree(bss_cfg); + + if (mwifiex_send_cmd_async(priv, HostCmd_CMD_UAP_BSS_START, + HostCmd_ACT_GEN_SET, 0, NULL)) { + wiphy_err(wiphy, "Failed to start the BSS\n"); + return -1; + } + + if (priv->sec_info.wep_enabled) + priv->curr_pkt_filter |= HostCmd_ACT_MAC_WEP_ENABLE; + else + priv->curr_pkt_filter &= ~HostCmd_ACT_MAC_WEP_ENABLE; + + if (mwifiex_send_cmd_sync(priv, HostCmd_CMD_MAC_CONTROL, + HostCmd_ACT_GEN_SET, 0, + &priv->curr_pkt_filter)) + return -1; + + return 0; +} + +/* + * CFG802.11 operation handler for disconnection request. + * + * This function does not work when there is already a disconnection + * procedure going on. + */ +static int +mwifiex_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev, + u16 reason_code) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + + if (mwifiex_deauthenticate(priv, NULL)) + return -EFAULT; + + wiphy_dbg(wiphy, "info: successfully disconnected from %pM:" + " reason code %d\n", priv->cfg_bssid, reason_code); + + memset(priv->cfg_bssid, 0, ETH_ALEN); + + return 0; +} + +/* + * This function informs the CFG802.11 subsystem of a new IBSS. + * + * The following information are sent to the CFG802.11 subsystem + * to register the new IBSS. If we do not register the new IBSS, + * a kernel panic will result. + * - SSID + * - SSID length + * - BSSID + * - Channel + */ +static int mwifiex_cfg80211_inform_ibss_bss(struct mwifiex_private *priv) +{ + struct ieee80211_channel *chan; + struct mwifiex_bss_info bss_info; + struct cfg80211_bss *bss; + int ie_len; + u8 ie_buf[IEEE80211_MAX_SSID_LEN + sizeof(struct ieee_types_header)]; + enum ieee80211_band band; + + if (mwifiex_get_bss_info(priv, &bss_info)) + return -1; + + ie_buf[0] = WLAN_EID_SSID; + ie_buf[1] = bss_info.ssid.ssid_len; + + memcpy(&ie_buf[sizeof(struct ieee_types_header)], + &bss_info.ssid.ssid, bss_info.ssid.ssid_len); + ie_len = ie_buf[1] + sizeof(struct ieee_types_header); + + band = mwifiex_band_to_radio_type(priv->curr_bss_params.band); + chan = __ieee80211_get_channel(priv->wdev->wiphy, + ieee80211_channel_to_frequency(bss_info.bss_chan, + band)); + + bss = cfg80211_inform_bss(priv->wdev->wiphy, chan, + bss_info.bssid, 0, WLAN_CAPABILITY_IBSS, + 0, ie_buf, ie_len, 0, GFP_KERNEL); + cfg80211_put_bss(priv->wdev->wiphy, bss); + memcpy(priv->cfg_bssid, bss_info.bssid, ETH_ALEN); + + return 0; +} + +/* + * This function connects with a BSS. + * + * This function handles both Infra and Ad-Hoc modes. It also performs + * validity checking on the provided parameters, disconnects from the + * current BSS (if any), sets up the association/scan parameters, + * including security settings, and performs specific SSID scan before + * trying to connect. + * + * For Infra mode, the function returns failure if the specified SSID + * is not found in scan table. However, for Ad-Hoc mode, it can create + * the IBSS if it does not exist. On successful completion in either case, + * the function notifies the CFG802.11 subsystem of the new BSS connection. + */ +static int +mwifiex_cfg80211_assoc(struct mwifiex_private *priv, size_t ssid_len, u8 *ssid, + u8 *bssid, int mode, struct ieee80211_channel *channel, + struct cfg80211_connect_params *sme, bool privacy) +{ + struct cfg80211_ssid req_ssid; + int ret, auth_type = 0; + struct cfg80211_bss *bss = NULL; + u8 is_scanning_required = 0; + + memset(&req_ssid, 0, sizeof(struct cfg80211_ssid)); + + req_ssid.ssid_len = ssid_len; + if (ssid_len > IEEE80211_MAX_SSID_LEN) { + dev_err(priv->adapter->dev, "invalid SSID - aborting\n"); + return -EINVAL; + } + + memcpy(req_ssid.ssid, ssid, ssid_len); + if (!req_ssid.ssid_len || req_ssid.ssid[0] < 0x20) { + dev_err(priv->adapter->dev, "invalid SSID - aborting\n"); + return -EINVAL; + } + + /* disconnect before try to associate */ + mwifiex_deauthenticate(priv, NULL); + + /* As this is new association, clear locally stored + * keys and security related flags */ + priv->sec_info.wpa_enabled = false; + priv->sec_info.wpa2_enabled = false; + priv->wep_key_curr_index = 0; + priv->sec_info.encryption_mode = 0; + priv->sec_info.is_authtype_auto = 0; + ret = mwifiex_set_encode(priv, NULL, NULL, 0, 0, NULL, 1); + + if (mode == NL80211_IFTYPE_ADHOC) { + /* "privacy" is set only for ad-hoc mode */ + if (privacy) { + /* + * Keep WLAN_CIPHER_SUITE_WEP104 for now so that + * the firmware can find a matching network from the + * scan. The cfg80211 does not give us the encryption + * mode at this stage so just setting it to WEP here. + */ + priv->sec_info.encryption_mode = + WLAN_CIPHER_SUITE_WEP104; + priv->sec_info.authentication_mode = + NL80211_AUTHTYPE_OPEN_SYSTEM; + } + + goto done; + } + + /* Now handle infra mode. "sme" is valid for infra mode only */ + if (sme->auth_type == NL80211_AUTHTYPE_AUTOMATIC) { + auth_type = NL80211_AUTHTYPE_OPEN_SYSTEM; + priv->sec_info.is_authtype_auto = 1; + } else { + auth_type = sme->auth_type; + } + + if (sme->crypto.n_ciphers_pairwise) { + priv->sec_info.encryption_mode = + sme->crypto.ciphers_pairwise[0]; + priv->sec_info.authentication_mode = auth_type; + } + + if (sme->crypto.cipher_group) { + priv->sec_info.encryption_mode = sme->crypto.cipher_group; + priv->sec_info.authentication_mode = auth_type; + } + if (sme->ie) + ret = mwifiex_set_gen_ie(priv, sme->ie, sme->ie_len); + + if (sme->key) { + if (mwifiex_is_alg_wep(priv->sec_info.encryption_mode)) { + dev_dbg(priv->adapter->dev, + "info: setting wep encryption" + " with key len %d\n", sme->key_len); + priv->wep_key_curr_index = sme->key_idx; + ret = mwifiex_set_encode(priv, NULL, sme->key, + sme->key_len, sme->key_idx, + NULL, 0); + } + } +done: + /* + * Scan entries are valid for some time (15 sec). So we can save one + * active scan time if we just try cfg80211_get_bss first. If it fails + * then request scan and cfg80211_get_bss() again for final output. + */ + while (1) { + if (is_scanning_required) { + /* Do specific SSID scanning */ + if (mwifiex_request_scan(priv, &req_ssid)) { + dev_err(priv->adapter->dev, "scan error\n"); + return -EFAULT; + } + } + + /* Find the BSS we want using available scan results */ + if (mode == NL80211_IFTYPE_ADHOC) + bss = cfg80211_get_bss(priv->wdev->wiphy, channel, + bssid, ssid, ssid_len, + WLAN_CAPABILITY_IBSS, + WLAN_CAPABILITY_IBSS); + else + bss = cfg80211_get_bss(priv->wdev->wiphy, channel, + bssid, ssid, ssid_len, + WLAN_CAPABILITY_ESS, + WLAN_CAPABILITY_ESS); + + if (!bss) { + if (is_scanning_required) { + dev_warn(priv->adapter->dev, + "assoc: requested bss not found in scan results\n"); + break; + } + is_scanning_required = 1; + } else { + dev_dbg(priv->adapter->dev, + "info: trying to associate to '%s' bssid %pM\n", + (char *) req_ssid.ssid, bss->bssid); + memcpy(&priv->cfg_bssid, bss->bssid, ETH_ALEN); + break; + } + } + + ret = mwifiex_bss_start(priv, bss, &req_ssid); + if (ret) + return ret; + + if (mode == NL80211_IFTYPE_ADHOC) { + /* Inform the BSS information to kernel, otherwise + * kernel will give a panic after successful assoc */ + if (mwifiex_cfg80211_inform_ibss_bss(priv)) + return -EFAULT; + } + + return ret; +} + +/* + * CFG802.11 operation handler for association request. + * + * This function does not work when the current mode is set to Ad-Hoc, or + * when there is already an association procedure going on. The given BSS + * information is used to associate. + */ +static int +mwifiex_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_connect_params *sme) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + int ret; + + if (GET_BSS_ROLE(priv) != MWIFIEX_BSS_ROLE_STA) { + wiphy_err(wiphy, + "%s: reject infra assoc request in non-STA role\n", + dev->name); + return -EINVAL; + } + + wiphy_dbg(wiphy, "info: Trying to associate to %s and bssid %pM\n", + (char *) sme->ssid, sme->bssid); + + ret = mwifiex_cfg80211_assoc(priv, sme->ssid_len, sme->ssid, sme->bssid, + priv->bss_mode, sme->channel, sme, 0); + if (!ret) { + cfg80211_connect_result(priv->netdev, priv->cfg_bssid, NULL, 0, + NULL, 0, WLAN_STATUS_SUCCESS, + GFP_KERNEL); + dev_dbg(priv->adapter->dev, + "info: associated to bssid %pM successfully\n", + priv->cfg_bssid); + } else { + dev_dbg(priv->adapter->dev, + "info: association to bssid %pM failed\n", + priv->cfg_bssid); + memset(priv->cfg_bssid, 0, ETH_ALEN); + + if (ret > 0) + cfg80211_connect_result(priv->netdev, priv->cfg_bssid, + NULL, 0, NULL, 0, ret, + GFP_KERNEL); + else + cfg80211_connect_result(priv->netdev, priv->cfg_bssid, + NULL, 0, NULL, 0, + WLAN_STATUS_UNSPECIFIED_FAILURE, + GFP_KERNEL); + } + + return 0; +} + +/* + * This function sets following parameters for ibss network. + * - channel + * - start band + * - 11n flag + * - secondary channel offset + */ +static int mwifiex_set_ibss_params(struct mwifiex_private *priv, + struct cfg80211_ibss_params *params) +{ + struct wiphy *wiphy = priv->wdev->wiphy; + struct mwifiex_adapter *adapter = priv->adapter; + int index = 0, i; + u8 config_bands = 0; + + if (params->chandef.chan->band == IEEE80211_BAND_2GHZ) { + if (!params->basic_rates) { + config_bands = BAND_B | BAND_G; + } else { + for (i = 0; i < mwifiex_band_2ghz.n_bitrates; i++) { + /* + * Rates below 6 Mbps in the table are CCK + * rates; 802.11b and from 6 they are OFDM; + * 802.11G + */ + if (mwifiex_rates[i].bitrate == 60) { + index = 1 << i; + break; + } + } + + if (params->basic_rates < index) { + config_bands = BAND_B; + } else { + config_bands = BAND_G; + if (params->basic_rates % index) + config_bands |= BAND_B; + } + } + + if (cfg80211_get_chandef_type(¶ms->chandef) != + NL80211_CHAN_NO_HT) + config_bands |= BAND_G | BAND_GN; + } else { + if (cfg80211_get_chandef_type(¶ms->chandef) == + NL80211_CHAN_NO_HT) + config_bands = BAND_A; + else + config_bands = BAND_AN | BAND_A; + } + + if (!((config_bands | adapter->fw_bands) & ~adapter->fw_bands)) { + adapter->config_bands = config_bands; + adapter->adhoc_start_band = config_bands; + + if ((config_bands & BAND_GN) || (config_bands & BAND_AN)) + adapter->adhoc_11n_enabled = true; + else + adapter->adhoc_11n_enabled = false; + } + + adapter->sec_chan_offset = + mwifiex_chan_type_to_sec_chan_offset( + cfg80211_get_chandef_type(¶ms->chandef)); + priv->adhoc_channel = ieee80211_frequency_to_channel( + params->chandef.chan->center_freq); + + wiphy_dbg(wiphy, "info: set ibss band %d, chan %d, chan offset %d\n", + config_bands, priv->adhoc_channel, adapter->sec_chan_offset); + + return 0; +} + +/* + * CFG802.11 operation handler to join an IBSS. + * + * This function does not work in any mode other than Ad-Hoc, or if + * a join operation is already in progress. + */ +static int +mwifiex_cfg80211_join_ibss(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_ibss_params *params) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + int ret = 0; + + if (priv->bss_mode != NL80211_IFTYPE_ADHOC) { + wiphy_err(wiphy, "request to join ibss received " + "when station is not in ibss mode\n"); + goto done; + } + + wiphy_dbg(wiphy, "info: trying to join to %s and bssid %pM\n", + (char *) params->ssid, params->bssid); + + mwifiex_set_ibss_params(priv, params); + + ret = mwifiex_cfg80211_assoc(priv, params->ssid_len, params->ssid, + params->bssid, priv->bss_mode, + params->chandef.chan, NULL, + params->privacy); +done: + if (!ret) { + cfg80211_ibss_joined(priv->netdev, priv->cfg_bssid, GFP_KERNEL); + dev_dbg(priv->adapter->dev, + "info: joined/created adhoc network with bssid" + " %pM successfully\n", priv->cfg_bssid); + } else { + dev_dbg(priv->adapter->dev, + "info: failed creating/joining adhoc network\n"); + } + + return ret; +} + +/* + * CFG802.11 operation handler to leave an IBSS. + * + * This function does not work if a leave operation is + * already in progress. + */ +static int +mwifiex_cfg80211_leave_ibss(struct wiphy *wiphy, struct net_device *dev) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + + wiphy_dbg(wiphy, "info: disconnecting from essid %pM\n", + priv->cfg_bssid); + if (mwifiex_deauthenticate(priv, NULL)) + return -EFAULT; + + memset(priv->cfg_bssid, 0, ETH_ALEN); + + return 0; +} + +/* + * CFG802.11 operation handler for scan request. + * + * This function issues a scan request to the firmware based upon + * the user specified scan configuration. On successfull completion, + * it also informs the results. + */ +static int +mwifiex_cfg80211_scan(struct wiphy *wiphy, + struct cfg80211_scan_request *request) +{ + struct net_device *dev = request->wdev->netdev; + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + int i, offset, ret; + struct ieee80211_channel *chan; + struct ieee_types_header *ie; + + wiphy_dbg(wiphy, "info: received scan request on %s\n", dev->name); + + if ((request->flags & NL80211_SCAN_FLAG_LOW_PRIORITY) && + atomic_read(&priv->wmm.tx_pkts_queued) >= + MWIFIEX_MIN_TX_PENDING_TO_CANCEL_SCAN) { + dev_dbg(priv->adapter->dev, "scan rejected due to traffic\n"); + return -EBUSY; + } + + if (priv->user_scan_cfg) { + dev_err(priv->adapter->dev, "cmd: Scan already in process..\n"); + return -EBUSY; + } + + priv->user_scan_cfg = kzalloc(sizeof(struct mwifiex_user_scan_cfg), + GFP_KERNEL); + if (!priv->user_scan_cfg) + return -ENOMEM; + + priv->scan_request = request; + + priv->user_scan_cfg->num_ssids = request->n_ssids; + priv->user_scan_cfg->ssid_list = request->ssids; + + if (request->ie && request->ie_len) { + offset = 0; + for (i = 0; i < MWIFIEX_MAX_VSIE_NUM; i++) { + if (priv->vs_ie[i].mask != MWIFIEX_VSIE_MASK_CLEAR) + continue; + priv->vs_ie[i].mask = MWIFIEX_VSIE_MASK_SCAN; + ie = (struct ieee_types_header *)(request->ie + offset); + memcpy(&priv->vs_ie[i].ie, ie, sizeof(*ie) + ie->len); + offset += sizeof(*ie) + ie->len; + + if (offset >= request->ie_len) + break; + } + } + + for (i = 0; i < min_t(u32, request->n_channels, + MWIFIEX_USER_SCAN_CHAN_MAX); i++) { + chan = request->channels[i]; + priv->user_scan_cfg->chan_list[i].chan_number = chan->hw_value; + priv->user_scan_cfg->chan_list[i].radio_type = chan->band; + + if (chan->flags & IEEE80211_CHAN_PASSIVE_SCAN) + priv->user_scan_cfg->chan_list[i].scan_type = + MWIFIEX_SCAN_TYPE_PASSIVE; + else + priv->user_scan_cfg->chan_list[i].scan_type = + MWIFIEX_SCAN_TYPE_ACTIVE; + + priv->user_scan_cfg->chan_list[i].scan_time = 0; + } + + ret = mwifiex_scan_networks(priv, priv->user_scan_cfg); + if (ret) { + dev_err(priv->adapter->dev, "scan failed: %d\n", ret); + priv->scan_request = NULL; + kfree(priv->user_scan_cfg); + priv->user_scan_cfg = NULL; + return ret; + } + + if (request->ie && request->ie_len) { + for (i = 0; i < MWIFIEX_MAX_VSIE_NUM; i++) { + if (priv->vs_ie[i].mask == MWIFIEX_VSIE_MASK_SCAN) { + priv->vs_ie[i].mask = MWIFIEX_VSIE_MASK_CLEAR; + memset(&priv->vs_ie[i].ie, 0, + MWIFIEX_MAX_VSIE_LEN); + } + } + } + return 0; +} + +static void mwifiex_setup_vht_caps(struct ieee80211_sta_vht_cap *vht_info, + struct mwifiex_private *priv) +{ + struct mwifiex_adapter *adapter = priv->adapter; + + vht_info->vht_supported = true; + + vht_info->cap = adapter->hw_dot_11ac_dev_cap; + /* Update MCS support for VHT */ + vht_info->vht_mcs.rx_mcs_map = cpu_to_le16( + adapter->hw_dot_11ac_mcs_support & 0xFFFF); + vht_info->vht_mcs.rx_highest = 0; + vht_info->vht_mcs.tx_mcs_map = cpu_to_le16( + adapter->hw_dot_11ac_mcs_support >> 16); + vht_info->vht_mcs.tx_highest = 0; +} + +/* + * This function sets up the CFG802.11 specific HT capability fields + * with default values. + * + * The following default values are set - + * - HT Supported = True + * - Maximum AMPDU length factor = IEEE80211_HT_MAX_AMPDU_64K + * - Minimum AMPDU spacing = IEEE80211_HT_MPDU_DENSITY_NONE + * - HT Capabilities supported by firmware + * - MCS information, Rx mask = 0xff + * - MCD information, Tx parameters = IEEE80211_HT_MCS_TX_DEFINED (0x01) + */ +static void +mwifiex_setup_ht_caps(struct ieee80211_sta_ht_cap *ht_info, + struct mwifiex_private *priv) +{ + int rx_mcs_supp; + struct ieee80211_mcs_info mcs_set; + u8 *mcs = (u8 *)&mcs_set; + struct mwifiex_adapter *adapter = priv->adapter; + + ht_info->ht_supported = true; + ht_info->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + ht_info->ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE; + + memset(&ht_info->mcs, 0, sizeof(ht_info->mcs)); + + /* Fill HT capability information */ + if (ISSUPP_CHANWIDTH40(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + else + ht_info->cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40; + + if (ISSUPP_SHORTGI20(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_SGI_20; + else + ht_info->cap &= ~IEEE80211_HT_CAP_SGI_20; + + if (ISSUPP_SHORTGI40(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_SGI_40; + else + ht_info->cap &= ~IEEE80211_HT_CAP_SGI_40; + + if (ISSUPP_RXSTBC(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= 1 << IEEE80211_HT_CAP_RX_STBC_SHIFT; + else + ht_info->cap &= ~(3 << IEEE80211_HT_CAP_RX_STBC_SHIFT); + + if (ISSUPP_TXSTBC(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_TX_STBC; + else + ht_info->cap &= ~IEEE80211_HT_CAP_TX_STBC; + + if (ISSUPP_GREENFIELD(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_GRN_FLD; + else + ht_info->cap &= ~IEEE80211_HT_CAP_GRN_FLD; + + if (ISENABLED_40MHZ_INTOLERANT(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_40MHZ_INTOLERANT; + else + ht_info->cap &= ~IEEE80211_HT_CAP_40MHZ_INTOLERANT; + + if (ISSUPP_RXLDPC(adapter->hw_dot_11n_dev_cap)) + ht_info->cap |= IEEE80211_HT_CAP_LDPC_CODING; + else + ht_info->cap &= ~IEEE80211_HT_CAP_LDPC_CODING; + + ht_info->cap &= ~IEEE80211_HT_CAP_MAX_AMSDU; + ht_info->cap |= IEEE80211_HT_CAP_SM_PS; + + rx_mcs_supp = GET_RXMCSSUPP(adapter->hw_dev_mcs_support); + /* Set MCS for 1x1 */ + memset(mcs, 0xff, rx_mcs_supp); + /* Clear all the other values */ + memset(&mcs[rx_mcs_supp], 0, + sizeof(struct ieee80211_mcs_info) - rx_mcs_supp); + if (priv->bss_mode == NL80211_IFTYPE_STATION || + ISSUPP_CHANWIDTH40(adapter->hw_dot_11n_dev_cap)) + /* Set MCS32 for infra mode or ad-hoc mode with 40MHz support */ + SETHT_MCS32(mcs_set.rx_mask); + + memcpy((u8 *) &ht_info->mcs, mcs, sizeof(struct ieee80211_mcs_info)); + + ht_info->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; +} + +/* + * create a new virtual interface with the given name + */ +struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, + const char *name, + enum nl80211_iftype type, + u32 *flags, + struct vif_params *params) +{ + struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy); + struct mwifiex_private *priv; + struct net_device *dev; + void *mdev_priv; + struct wireless_dev *wdev; + + if (!adapter) + return ERR_PTR(-EFAULT); + + switch (type) { + case NL80211_IFTYPE_UNSPECIFIED: + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: + priv = adapter->priv[MWIFIEX_BSS_TYPE_STA]; + if (priv->bss_mode) { + wiphy_err(wiphy, + "cannot create multiple sta/adhoc ifaces\n"); + return ERR_PTR(-EINVAL); + } + + wdev = kzalloc(sizeof(struct wireless_dev), GFP_KERNEL); + if (!wdev) + return ERR_PTR(-ENOMEM); + + wdev->wiphy = wiphy; + priv->wdev = wdev; + wdev->iftype = NL80211_IFTYPE_STATION; + + if (type == NL80211_IFTYPE_UNSPECIFIED) + priv->bss_mode = NL80211_IFTYPE_STATION; + else + priv->bss_mode = type; + + priv->bss_type = MWIFIEX_BSS_TYPE_STA; + priv->frame_type = MWIFIEX_DATA_FRAME_TYPE_ETH_II; + priv->bss_priority = 0; + priv->bss_role = MWIFIEX_BSS_ROLE_STA; + priv->bss_num = 0; + + break; + case NL80211_IFTYPE_AP: + priv = adapter->priv[MWIFIEX_BSS_TYPE_UAP]; + + if (priv->bss_mode) { + wiphy_err(wiphy, "Can't create multiple AP interfaces"); + return ERR_PTR(-EINVAL); + } + + wdev = kzalloc(sizeof(struct wireless_dev), GFP_KERNEL); + if (!wdev) + return ERR_PTR(-ENOMEM); + + priv->wdev = wdev; + wdev->wiphy = wiphy; + wdev->iftype = NL80211_IFTYPE_AP; + + priv->bss_type = MWIFIEX_BSS_TYPE_UAP; + priv->frame_type = MWIFIEX_DATA_FRAME_TYPE_ETH_II; + priv->bss_priority = 0; + priv->bss_role = MWIFIEX_BSS_ROLE_UAP; + priv->bss_started = 0; + priv->bss_num = 0; + priv->bss_mode = type; + + break; + case NL80211_IFTYPE_P2P_CLIENT: + priv = adapter->priv[MWIFIEX_BSS_TYPE_P2P]; + + if (priv->bss_mode) { + wiphy_err(wiphy, "Can't create multiple P2P ifaces"); + return ERR_PTR(-EINVAL); + } + + wdev = kzalloc(sizeof(struct wireless_dev), GFP_KERNEL); + if (!wdev) + return ERR_PTR(-ENOMEM); + + priv->wdev = wdev; + wdev->wiphy = wiphy; + + /* At start-up, wpa_supplicant tries to change the interface + * to NL80211_IFTYPE_STATION if it is not managed mode. + */ + wdev->iftype = NL80211_IFTYPE_P2P_CLIENT; + priv->bss_mode = NL80211_IFTYPE_P2P_CLIENT; + + /* Setting bss_type to P2P tells firmware that this interface + * is receiving P2P peers found during find phase and doing + * action frame handshake. + */ + priv->bss_type = MWIFIEX_BSS_TYPE_P2P; + + priv->frame_type = MWIFIEX_DATA_FRAME_TYPE_ETH_II; + priv->bss_priority = MWIFIEX_BSS_ROLE_STA; + priv->bss_role = MWIFIEX_BSS_ROLE_STA; + priv->bss_started = 0; + priv->bss_num = 0; + + if (mwifiex_cfg80211_init_p2p_client(priv)) + return ERR_PTR(-EFAULT); + + break; + default: + wiphy_err(wiphy, "type not supported\n"); + return ERR_PTR(-EINVAL); + } + + dev = alloc_netdev_mqs(sizeof(struct mwifiex_private *), name, + ether_setup, IEEE80211_NUM_ACS, 1); + if (!dev) { + wiphy_err(wiphy, "no memory available for netdevice\n"); + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + return ERR_PTR(-ENOMEM); + } + + mwifiex_init_priv_params(priv, dev); + priv->netdev = dev; + + mwifiex_setup_ht_caps(&wiphy->bands[IEEE80211_BAND_2GHZ]->ht_cap, priv); + if (adapter->is_hw_11ac_capable) + mwifiex_setup_vht_caps( + &wiphy->bands[IEEE80211_BAND_2GHZ]->vht_cap, priv); + + if (adapter->config_bands & BAND_A) + mwifiex_setup_ht_caps( + &wiphy->bands[IEEE80211_BAND_5GHZ]->ht_cap, priv); + + if ((adapter->config_bands & BAND_A) && adapter->is_hw_11ac_capable) + mwifiex_setup_vht_caps( + &wiphy->bands[IEEE80211_BAND_5GHZ]->vht_cap, priv); + + dev_net_set(dev, wiphy_net(wiphy)); + dev->ieee80211_ptr = priv->wdev; + dev->ieee80211_ptr->iftype = priv->bss_mode; + memcpy(dev->dev_addr, wiphy->perm_addr, ETH_ALEN); + SET_NETDEV_DEV(dev, wiphy_dev(wiphy)); + + dev->flags |= IFF_BROADCAST | IFF_MULTICAST; + dev->watchdog_timeo = MWIFIEX_DEFAULT_WATCHDOG_TIMEOUT; + dev->hard_header_len += MWIFIEX_MIN_DATA_HEADER_LEN; + dev->ethtool_ops = &mwifiex_ethtool_ops; + + mdev_priv = netdev_priv(dev); + *((unsigned long *) mdev_priv) = (unsigned long) priv; + + SET_NETDEV_DEV(dev, adapter->dev); + + /* Register network device */ + if (register_netdevice(dev)) { + wiphy_err(wiphy, "cannot register virtual network device\n"); + free_netdev(dev); + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + return ERR_PTR(-EFAULT); + } + + sema_init(&priv->async_sem, 1); + + dev_dbg(adapter->dev, "info: %s: Marvell 802.11 Adapter\n", dev->name); + +#ifdef CONFIG_DEBUG_FS + mwifiex_dev_debugfs_init(priv); +#endif + return wdev; +} +EXPORT_SYMBOL_GPL(mwifiex_add_virtual_intf); + +/* + * del_virtual_intf: remove the virtual interface determined by dev + */ +int mwifiex_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(wdev->netdev); + +#ifdef CONFIG_DEBUG_FS + mwifiex_dev_debugfs_remove(priv); +#endif + + mwifiex_stop_net_dev_queue(priv->netdev, priv->adapter); + + if (netif_carrier_ok(priv->netdev)) + netif_carrier_off(priv->netdev); + + if (wdev->netdev->reg_state == NETREG_REGISTERED) + unregister_netdevice(wdev->netdev); + + /* Clear the priv in adapter */ + priv->netdev = NULL; + + priv->media_connected = false; + + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + + return 0; +} +EXPORT_SYMBOL_GPL(mwifiex_del_virtual_intf); + +#ifdef CONFIG_PM +static bool +mwifiex_is_pattern_supported(struct cfg80211_wowlan_trig_pkt_pattern *pat, + s8 *byte_seq) +{ + int j, k, valid_byte_cnt = 0; + bool dont_care_byte = false; + + for (j = 0; j < DIV_ROUND_UP(pat->pattern_len, 8); j++) { + for (k = 0; k < 8; k++) { + if (pat->mask[j] & 1 << k) { + memcpy(byte_seq + valid_byte_cnt, + &pat->pattern[j * 8 + k], 1); + valid_byte_cnt++; + if (dont_care_byte) + return false; + } else { + if (valid_byte_cnt) + dont_care_byte = true; + } + + if (valid_byte_cnt > MAX_BYTESEQ) + return false; + } + } + + byte_seq[MAX_BYTESEQ] = valid_byte_cnt; + + return true; +} + +static int mwifiex_cfg80211_suspend(struct wiphy *wiphy, + struct cfg80211_wowlan *wowlan) +{ + struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy); + struct mwifiex_ds_mef_cfg mef_cfg; + struct mwifiex_mef_entry *mef_entry; + int i, filt_num = 0, ret; + bool first_pat = true; + u8 byte_seq[MAX_BYTESEQ + 1]; + const u8 ipv4_mc_mac[] = {0x33, 0x33}; + const u8 ipv6_mc_mac[] = {0x01, 0x00, 0x5e}; + struct mwifiex_private *priv = + mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA); + + if (!wowlan) { + dev_warn(adapter->dev, "None of the WOWLAN triggers enabled\n"); + return 0; + } + + if (!priv->media_connected) { + dev_warn(adapter->dev, + "Can not configure WOWLAN in disconnected state\n"); + return 0; + } + + mef_entry = kzalloc(sizeof(*mef_entry), GFP_KERNEL); + if (!mef_entry) + return -ENOMEM; + + memset(&mef_cfg, 0, sizeof(mef_cfg)); + mef_cfg.num_entries = 1; + mef_cfg.mef_entry = mef_entry; + mef_entry->mode = MEF_MODE_HOST_SLEEP; + mef_entry->action = MEF_ACTION_ALLOW_AND_WAKEUP_HOST; + + for (i = 0; i < wowlan->n_patterns; i++) { + memset(byte_seq, 0, sizeof(byte_seq)); + if (!mwifiex_is_pattern_supported(&wowlan->patterns[i], + byte_seq)) { + wiphy_err(wiphy, "Pattern not supported\n"); + kfree(mef_entry); + return -EOPNOTSUPP; + } + + if (!wowlan->patterns[i].pkt_offset) { + if (!(byte_seq[0] & 0x01) && + (byte_seq[MAX_BYTESEQ] == 1)) { + mef_cfg.criteria |= MWIFIEX_CRITERIA_UNICAST; + continue; + } else if (is_broadcast_ether_addr(byte_seq)) { + mef_cfg.criteria |= MWIFIEX_CRITERIA_BROADCAST; + continue; + } else if ((!memcmp(byte_seq, ipv4_mc_mac, 2) && + (byte_seq[MAX_BYTESEQ] == 2)) || + (!memcmp(byte_seq, ipv6_mc_mac, 3) && + (byte_seq[MAX_BYTESEQ] == 3))) { + mef_cfg.criteria |= MWIFIEX_CRITERIA_MULTICAST; + continue; + } + } + + mef_entry->filter[filt_num].repeat = 1; + mef_entry->filter[filt_num].offset = + wowlan->patterns[i].pkt_offset; + memcpy(mef_entry->filter[filt_num].byte_seq, byte_seq, + sizeof(byte_seq)); + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + + if (first_pat) + first_pat = false; + else + mef_entry->filter[filt_num].filt_action = TYPE_AND; + + filt_num++; + } + + if (wowlan->magic_pkt) { + mef_cfg.criteria |= MWIFIEX_CRITERIA_UNICAST; + mef_entry->filter[filt_num].repeat = 16; + memcpy(mef_entry->filter[filt_num].byte_seq, priv->curr_addr, + ETH_ALEN); + mef_entry->filter[filt_num].byte_seq[MAX_BYTESEQ] = ETH_ALEN; + mef_entry->filter[filt_num].offset = 14; + mef_entry->filter[filt_num].filt_type = TYPE_EQ; + if (filt_num) + mef_entry->filter[filt_num].filt_action = TYPE_OR; + } + + if (!mef_cfg.criteria) + mef_cfg.criteria = MWIFIEX_CRITERIA_BROADCAST | + MWIFIEX_CRITERIA_UNICAST | + MWIFIEX_CRITERIA_MULTICAST; + + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_MEF_CFG, + HostCmd_ACT_GEN_SET, 0, + &mef_cfg); + + kfree(mef_entry); + return ret; +} + +static int mwifiex_cfg80211_resume(struct wiphy *wiphy) +{ + return 0; +} + +static void mwifiex_cfg80211_set_wakeup(struct wiphy *wiphy, + bool enabled) +{ + struct mwifiex_adapter *adapter = mwifiex_cfg80211_get_adapter(wiphy); + + device_set_wakeup_enable(adapter->dev, enabled); +} +#endif + +/* station cfg80211 operations */ +static struct cfg80211_ops mwifiex_cfg80211_ops = { + .add_virtual_intf = mwifiex_add_virtual_intf, + .del_virtual_intf = mwifiex_del_virtual_intf, + .change_virtual_intf = mwifiex_cfg80211_change_virtual_intf, + .scan = mwifiex_cfg80211_scan, + .connect = mwifiex_cfg80211_connect, + .disconnect = mwifiex_cfg80211_disconnect, + .get_station = mwifiex_cfg80211_get_station, + .dump_station = mwifiex_cfg80211_dump_station, + .set_wiphy_params = mwifiex_cfg80211_set_wiphy_params, + .join_ibss = mwifiex_cfg80211_join_ibss, + .leave_ibss = mwifiex_cfg80211_leave_ibss, + .add_key = mwifiex_cfg80211_add_key, + .del_key = mwifiex_cfg80211_del_key, + .mgmt_tx = mwifiex_cfg80211_mgmt_tx, + .mgmt_frame_register = mwifiex_cfg80211_mgmt_frame_register, + .remain_on_channel = mwifiex_cfg80211_remain_on_channel, + .cancel_remain_on_channel = mwifiex_cfg80211_cancel_remain_on_channel, + .set_default_key = mwifiex_cfg80211_set_default_key, + .set_power_mgmt = mwifiex_cfg80211_set_power_mgmt, + .set_tx_power = mwifiex_cfg80211_set_tx_power, + .set_bitrate_mask = mwifiex_cfg80211_set_bitrate_mask, + .start_ap = mwifiex_cfg80211_start_ap, + .stop_ap = mwifiex_cfg80211_stop_ap, + .change_beacon = mwifiex_cfg80211_change_beacon, + .set_cqm_rssi_config = mwifiex_cfg80211_set_cqm_rssi_config, + .set_antenna = mwifiex_cfg80211_set_antenna, +#ifdef CONFIG_PM + .suspend = mwifiex_cfg80211_suspend, + .resume = mwifiex_cfg80211_resume, + .set_wakeup = mwifiex_cfg80211_set_wakeup, +#endif +}; + +/* + * This function registers the device with CFG802.11 subsystem. + * + * The function creates the wireless device/wiphy, populates it with + * default parameters and handler function pointers, and finally + * registers the device. + */ + +int mwifiex_register_cfg80211(struct mwifiex_adapter *adapter) +{ + int ret; + void *wdev_priv; + struct wiphy *wiphy; + struct mwifiex_private *priv = adapter->priv[MWIFIEX_BSS_TYPE_STA]; + u8 *country_code; + + /* create a new wiphy for use with cfg80211 */ + wiphy = wiphy_new(&mwifiex_cfg80211_ops, + sizeof(struct mwifiex_adapter *)); + if (!wiphy) { + dev_err(adapter->dev, "%s: creating new wiphy\n", __func__); + return -ENOMEM; + } + wiphy->max_scan_ssids = MWIFIEX_MAX_SSID_LIST_LENGTH; + wiphy->max_scan_ie_len = MWIFIEX_MAX_VSIE_LEN; + wiphy->mgmt_stypes = mwifiex_mgmt_stypes; + wiphy->max_remain_on_channel_duration = 5000; + wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_AP); + + wiphy->bands[IEEE80211_BAND_2GHZ] = &mwifiex_band_2ghz; + if (adapter->config_bands & BAND_A) + wiphy->bands[IEEE80211_BAND_5GHZ] = &mwifiex_band_5ghz; + else + wiphy->bands[IEEE80211_BAND_5GHZ] = NULL; + + wiphy->iface_combinations = &mwifiex_iface_comb_ap_sta; + wiphy->n_iface_combinations = 1; + + /* Initialize cipher suits */ + wiphy->cipher_suites = mwifiex_cipher_suites; + wiphy->n_cipher_suites = ARRAY_SIZE(mwifiex_cipher_suites); + + memcpy(wiphy->perm_addr, priv->curr_addr, ETH_ALEN); + wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + wiphy->flags |= WIPHY_FLAG_HAVE_AP_SME | + WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD | + WIPHY_FLAG_AP_UAPSD | + WIPHY_FLAG_CUSTOM_REGULATORY | + WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; + + wiphy_apply_custom_regulatory(wiphy, &mwifiex_world_regdom_custom); + +#ifdef CONFIG_PM + wiphy->wowlan.flags = WIPHY_WOWLAN_MAGIC_PKT; + wiphy->wowlan.n_patterns = MWIFIEX_MAX_FILTERS; + wiphy->wowlan.pattern_min_len = 1; + wiphy->wowlan.pattern_max_len = MWIFIEX_MAX_PATTERN_LEN; + wiphy->wowlan.max_pkt_offset = MWIFIEX_MAX_OFFSET_LEN; +#endif + + wiphy->probe_resp_offload = NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS | + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 | + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P; + + wiphy->available_antennas_tx = BIT(adapter->number_of_antenna) - 1; + wiphy->available_antennas_rx = BIT(adapter->number_of_antenna) - 1; + + wiphy->features |= NL80211_FEATURE_HT_IBSS | + NL80211_FEATURE_INACTIVITY_TIMER | + NL80211_FEATURE_LOW_PRIORITY_SCAN; + + /* Reserve space for mwifiex specific private data for BSS */ + wiphy->bss_priv_size = sizeof(struct mwifiex_bss_priv); + + wiphy->reg_notifier = mwifiex_reg_notifier; + + /* Set struct mwifiex_adapter pointer in wiphy_priv */ + wdev_priv = wiphy_priv(wiphy); + *(unsigned long *)wdev_priv = (unsigned long)adapter; + + set_wiphy_dev(wiphy, priv->adapter->dev); + + ret = wiphy_register(wiphy); + if (ret < 0) { + dev_err(adapter->dev, + "%s: wiphy_register failed: %d\n", __func__, ret); + wiphy_free(wiphy); + return ret; + } + country_code = mwifiex_11d_code_2_region(priv->adapter->region_code); + if (country_code) + dev_info(adapter->dev, + "ignoring F/W country code %2.2s\n", country_code); + + adapter->wiphy = wiphy; + return ret; +} diff --git a/drivers/net/wireless/mwifiex/cfg80211.h b/drivers/net/wireless/mwifiex/cfg80211.h new file mode 100644 index 0000000..c584893 --- /dev/null +++ b/drivers/net/wireless/mwifiex/cfg80211.h @@ -0,0 +1,29 @@ +/* + * Marvell Wireless LAN device driver: CFG80211 + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef __MWIFIEX_CFG80211__ +#define __MWIFIEX_CFG80211__ + +#include + +#include "main.h" + +int mwifiex_register_cfg80211(struct mwifiex_adapter *); + +#endif diff --git a/drivers/net/wireless/mwifiex/cfp.c b/drivers/net/wireless/mwifiex/cfp.c new file mode 100644 index 0000000..5178c46 --- /dev/null +++ b/drivers/net/wireless/mwifiex/cfp.c @@ -0,0 +1,504 @@ +/* + * Marvell Wireless LAN device driver: Channel, Frequence and Power + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "cfg80211.h" + +/* 100mW */ +#define MWIFIEX_TX_PWR_DEFAULT 20 +/* 100mW */ +#define MWIFIEX_TX_PWR_US_DEFAULT 20 +/* 50mW */ +#define MWIFIEX_TX_PWR_JP_DEFAULT 16 +/* 100mW */ +#define MWIFIEX_TX_PWR_FR_100MW 20 +/* 10mW */ +#define MWIFIEX_TX_PWR_FR_10MW 10 +/* 100mW */ +#define MWIFIEX_TX_PWR_EMEA_DEFAULT 20 + +static u8 adhoc_rates_b[B_SUPPORTED_RATES] = { 0x82, 0x84, 0x8b, 0x96, 0 }; + +static u8 adhoc_rates_g[G_SUPPORTED_RATES] = { 0x8c, 0x12, 0x98, 0x24, + 0xb0, 0x48, 0x60, 0x6c, 0 }; + +static u8 adhoc_rates_bg[BG_SUPPORTED_RATES] = { 0x82, 0x84, 0x8b, 0x96, + 0x0c, 0x12, 0x18, 0x24, + 0x30, 0x48, 0x60, 0x6c, 0 }; + +static u8 adhoc_rates_a[A_SUPPORTED_RATES] = { 0x8c, 0x12, 0x98, 0x24, + 0xb0, 0x48, 0x60, 0x6c, 0 }; +static u8 supported_rates_a[A_SUPPORTED_RATES] = { 0x0c, 0x12, 0x18, 0x24, + 0xb0, 0x48, 0x60, 0x6c, 0 }; +static u16 mwifiex_data_rates[MWIFIEX_SUPPORTED_RATES_EXT] = { 0x02, 0x04, + 0x0B, 0x16, 0x00, 0x0C, 0x12, 0x18, + 0x24, 0x30, 0x48, 0x60, 0x6C, 0x90, + 0x0D, 0x1A, 0x27, 0x34, 0x4E, 0x68, + 0x75, 0x82, 0x0C, 0x1B, 0x36, 0x51, + 0x6C, 0xA2, 0xD8, 0xF3, 0x10E, 0x00 }; + +static u8 supported_rates_b[B_SUPPORTED_RATES] = { 0x02, 0x04, 0x0b, 0x16, 0 }; + +static u8 supported_rates_g[G_SUPPORTED_RATES] = { 0x0c, 0x12, 0x18, 0x24, + 0x30, 0x48, 0x60, 0x6c, 0 }; + +static u8 supported_rates_bg[BG_SUPPORTED_RATES] = { 0x02, 0x04, 0x0b, 0x0c, + 0x12, 0x16, 0x18, 0x24, 0x30, 0x48, + 0x60, 0x6c, 0 }; + +u16 region_code_index[MWIFIEX_MAX_REGION_CODE] = { 0x10, 0x20, 0x30, + 0x32, 0x40, 0x41, 0xff }; + +static u8 supported_rates_n[N_SUPPORTED_RATES] = { 0x02, 0x04, 0 }; + +struct region_code_mapping { + u8 code; + u8 region[IEEE80211_COUNTRY_STRING_LEN]; +}; + +static struct region_code_mapping region_code_mapping_t[] = { + { 0x10, "US " }, /* US FCC */ + { 0x20, "CA " }, /* IC Canada */ + { 0x30, "EU " }, /* ETSI */ + { 0x31, "ES " }, /* Spain */ + { 0x32, "FR " }, /* France */ + { 0x40, "JP " }, /* Japan */ + { 0x41, "JP " }, /* Japan */ + { 0x50, "CN " }, /* China */ +}; + +/* This function converts integer code to region string */ +u8 *mwifiex_11d_code_2_region(u8 code) +{ + u8 i; + u8 size = sizeof(region_code_mapping_t)/ + sizeof(struct region_code_mapping); + + /* Look for code in mapping table */ + for (i = 0; i < size; i++) + if (region_code_mapping_t[i].code == code) + return region_code_mapping_t[i].region; + + return NULL; +} + +/* + * This function maps an index in supported rates table into + * the corresponding data rate. + */ +u32 mwifiex_index_to_acs_data_rate(struct mwifiex_private *priv, + u8 index, u8 ht_info) +{ + /* + * For every mcs_rate line, the first 8 bytes are for stream 1x1, + * and all 16 bytes are for stream 2x2. + */ + u16 mcs_rate[4][16] = { + /* LGI 40M */ + { 0x1b, 0x36, 0x51, 0x6c, 0xa2, 0xd8, 0xf3, 0x10e, + 0x36, 0x6c, 0xa2, 0xd8, 0x144, 0x1b0, 0x1e6, 0x21c }, + + /* SGI 40M */ + { 0x1e, 0x3c, 0x5a, 0x78, 0xb4, 0xf0, 0x10e, 0x12c, + 0x3c, 0x78, 0xb4, 0xf0, 0x168, 0x1e0, 0x21c, 0x258 }, + + /* LGI 20M */ + { 0x0d, 0x1a, 0x27, 0x34, 0x4e, 0x68, 0x75, 0x82, + 0x1a, 0x34, 0x4e, 0x68, 0x9c, 0xd0, 0xea, 0x104 }, + + /* SGI 20M */ + { 0x0e, 0x1c, 0x2b, 0x39, 0x56, 0x73, 0x82, 0x90, + 0x1c, 0x39, 0x56, 0x73, 0xad, 0xe7, 0x104, 0x120 } + }; + /* AC rates */ + u16 ac_mcs_rate_nss1[8][10] = { + /* LG 160M */ + { 0x75, 0xEA, 0x15F, 0x1D4, 0x2BE, 0x3A8, 0x41D, + 0x492, 0x57C, 0x618 }, + + /* SG 160M */ + { 0x82, 0x104, 0x186, 0x208, 0x30C, 0x410, 0x492, + 0x514, 0x618, 0x6C6 }, + + /* LG 80M */ + { 0x3B, 0x75, 0xB0, 0xEA, 0x15F, 0x1D4, 0x20F, + 0x249, 0x2BE, 0x30C }, + + /* SG 80M */ + { 0x41, 0x82, 0xC3, 0x104, 0x186, 0x208, 0x249, + 0x28A, 0x30C, 0x363 }, + + /* LG 40M */ + { 0x1B, 0x36, 0x51, 0x6C, 0xA2, 0xD8, 0xF3, + 0x10E, 0x144, 0x168 }, + + /* SG 40M */ + { 0x1E, 0x3C, 0x5A, 0x78, 0xB4, 0xF0, 0x10E, + 0x12C, 0x168, 0x190 }, + + /* LG 20M */ + { 0xD, 0x1A, 0x27, 0x34, 0x4E, 0x68, 0x75, 0x82, 0x9C, 0x00 }, + + /* SG 20M */ + { 0xF, 0x1D, 0x2C, 0x3A, 0x57, 0x74, 0x82, 0x91, 0xAE, 0x00 }, + }; + /* NSS2 note: the value in the table is 2 multiplier of the actual + * rate + */ + u16 ac_mcs_rate_nss2[8][10] = { + /* LG 160M */ + { 0xEA, 0x1D4, 0x2BE, 0x3A8, 0x57C, 0x750, 0x83A, + 0x924, 0xAF8, 0xC30 }, + + /* SG 160M */ + { 0x104, 0x208, 0x30C, 0x410, 0x618, 0x820, 0x924, + 0xA28, 0xC30, 0xD8B }, + + /* LG 80M */ + { 0x75, 0xEA, 0x15F, 0x1D4, 0x2BE, 0x3A8, 0x41D, + 0x492, 0x57C, 0x618 }, + + /* SG 80M */ + { 0x82, 0x104, 0x186, 0x208, 0x30C, 0x410, 0x492, + 0x514, 0x618, 0x6C6 }, + + /* LG 40M */ + { 0x36, 0x6C, 0xA2, 0xD8, 0x144, 0x1B0, 0x1E6, + 0x21C, 0x288, 0x2D0 }, + + /* SG 40M */ + { 0x3C, 0x78, 0xB4, 0xF0, 0x168, 0x1E0, 0x21C, + 0x258, 0x2D0, 0x320 }, + + /* LG 20M */ + { 0x1A, 0x34, 0x4A, 0x68, 0x9C, 0xD0, 0xEA, 0x104, + 0x138, 0x00 }, + + /* SG 20M */ + { 0x1D, 0x3A, 0x57, 0x74, 0xAE, 0xE6, 0x104, 0x121, + 0x15B, 0x00 }, + }; + u32 rate = 0; + u8 mcs_index = 0; + u8 bw = 0; + u8 gi = 0; + + if ((ht_info & 0x3) == MWIFIEX_RATE_FORMAT_VHT) { + mcs_index = min(index & 0xF, 9); + + /* 20M: bw=0, 40M: bw=1, 80M: bw=2, 160M: bw=3 */ + bw = (ht_info & 0xC) >> 2; + + /* LGI: gi =0, SGI: gi = 1 */ + gi = (ht_info & 0x10) >> 4; + + if ((index >> 4) == 1) /* NSS = 2 */ + rate = ac_mcs_rate_nss2[2 * (3 - bw) + gi][mcs_index]; + else /* NSS = 1 */ + rate = ac_mcs_rate_nss1[2 * (3 - bw) + gi][mcs_index]; + } else if ((ht_info & 0x3) == MWIFIEX_RATE_FORMAT_HT) { + /* 20M: bw=0, 40M: bw=1 */ + bw = (ht_info & 0xC) >> 2; + + /* LGI: gi =0, SGI: gi = 1 */ + gi = (ht_info & 0x10) >> 4; + + if (index == MWIFIEX_RATE_BITMAP_MCS0) { + if (gi == 1) + rate = 0x0D; /* MCS 32 SGI rate */ + else + rate = 0x0C; /* MCS 32 LGI rate */ + } else if (index < 16) { + if ((bw == 1) || (bw == 0)) + rate = mcs_rate[2 * (1 - bw) + gi][index]; + else + rate = mwifiex_data_rates[0]; + } else { + rate = mwifiex_data_rates[0]; + } + } else { + /* 11n non-HT rates */ + if (index >= MWIFIEX_SUPPORTED_RATES_EXT) + index = 0; + rate = mwifiex_data_rates[index]; + } + + return rate; +} + +/* This function maps an index in supported rates table into + * the corresponding data rate. + */ +u32 mwifiex_index_to_data_rate(struct mwifiex_private *priv, + u8 index, u8 ht_info) +{ + /* For every mcs_rate line, the first 8 bytes are for stream 1x1, + * and all 16 bytes are for stream 2x2. + */ + u16 mcs_rate[4][16] = { + /* LGI 40M */ + { 0x1b, 0x36, 0x51, 0x6c, 0xa2, 0xd8, 0xf3, 0x10e, + 0x36, 0x6c, 0xa2, 0xd8, 0x144, 0x1b0, 0x1e6, 0x21c }, + + /* SGI 40M */ + { 0x1e, 0x3c, 0x5a, 0x78, 0xb4, 0xf0, 0x10e, 0x12c, + 0x3c, 0x78, 0xb4, 0xf0, 0x168, 0x1e0, 0x21c, 0x258 }, + + /* LGI 20M */ + { 0x0d, 0x1a, 0x27, 0x34, 0x4e, 0x68, 0x75, 0x82, + 0x1a, 0x34, 0x4e, 0x68, 0x9c, 0xd0, 0xea, 0x104 }, + + /* SGI 20M */ + { 0x0e, 0x1c, 0x2b, 0x39, 0x56, 0x73, 0x82, 0x90, + 0x1c, 0x39, 0x56, 0x73, 0xad, 0xe7, 0x104, 0x120 } + }; + u32 mcs_num_supp = + (priv->adapter->hw_dev_mcs_support == HT_STREAM_2X2) ? 16 : 8; + u32 rate; + + if (priv->adapter->is_hw_11ac_capable) + return mwifiex_index_to_acs_data_rate(priv, index, ht_info); + + if (ht_info & BIT(0)) { + if (index == MWIFIEX_RATE_BITMAP_MCS0) { + if (ht_info & BIT(2)) + rate = 0x0D; /* MCS 32 SGI rate */ + else + rate = 0x0C; /* MCS 32 LGI rate */ + } else if (index < mcs_num_supp) { + if (ht_info & BIT(1)) { + if (ht_info & BIT(2)) + /* SGI, 40M */ + rate = mcs_rate[1][index]; + else + /* LGI, 40M */ + rate = mcs_rate[0][index]; + } else { + if (ht_info & BIT(2)) + /* SGI, 20M */ + rate = mcs_rate[3][index]; + else + /* LGI, 20M */ + rate = mcs_rate[2][index]; + } + } else + rate = mwifiex_data_rates[0]; + } else { + if (index >= MWIFIEX_SUPPORTED_RATES_EXT) + index = 0; + rate = mwifiex_data_rates[index]; + } + return rate; +} + +/* + * This function returns the current active data rates. + * + * The result may vary depending upon connection status. + */ +u32 mwifiex_get_active_data_rates(struct mwifiex_private *priv, u8 *rates) +{ + if (!priv->media_connected) + return mwifiex_get_supported_rates(priv, rates); + else + return mwifiex_copy_rates(rates, 0, + priv->curr_bss_params.data_rates, + priv->curr_bss_params.num_of_rates); +} + +/* + * This function locates the Channel-Frequency-Power triplet based upon + * band and channel/frequency parameters. + */ +struct mwifiex_chan_freq_power * +mwifiex_get_cfp(struct mwifiex_private *priv, u8 band, u16 channel, u32 freq) +{ + struct mwifiex_chan_freq_power *cfp = NULL; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch = NULL; + int i; + + if (!channel && !freq) + return cfp; + + if (mwifiex_band_to_radio_type(band) == HostCmd_SCAN_RADIO_TYPE_BG) + sband = priv->wdev->wiphy->bands[IEEE80211_BAND_2GHZ]; + else + sband = priv->wdev->wiphy->bands[IEEE80211_BAND_5GHZ]; + + if (!sband) { + dev_err(priv->adapter->dev, "%s: cannot find cfp by band %d\n", + __func__, band); + return cfp; + } + + for (i = 0; i < sband->n_channels; i++) { + ch = &sband->channels[i]; + + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + + if (freq) { + if (ch->center_freq == freq) + break; + } else { + /* find by valid channel*/ + if (ch->hw_value == channel || + channel == FIRST_VALID_CHANNEL) + break; + } + } + if (i == sband->n_channels) { + dev_err(priv->adapter->dev, "%s: cannot find cfp by band %d" + " & channel=%d freq=%d\n", __func__, band, channel, + freq); + } else { + if (!ch) + return cfp; + + priv->cfp.channel = ch->hw_value; + priv->cfp.freq = ch->center_freq; + priv->cfp.max_tx_power = ch->max_power; + cfp = &priv->cfp; + } + + return cfp; +} + +/* + * This function checks if the data rate is set to auto. + */ +u8 +mwifiex_is_rate_auto(struct mwifiex_private *priv) +{ + u32 i; + int rate_num = 0; + + for (i = 0; i < ARRAY_SIZE(priv->bitmap_rates); i++) + if (priv->bitmap_rates[i]) + rate_num++; + + if (rate_num > 1) + return true; + else + return false; +} + +/* + * This function gets the supported data rates. + * + * The function works in both Ad-Hoc and infra mode by printing the + * band and returning the data rates. + */ +u32 mwifiex_get_supported_rates(struct mwifiex_private *priv, u8 *rates) +{ + u32 k = 0; + struct mwifiex_adapter *adapter = priv->adapter; + + if (priv->bss_mode == NL80211_IFTYPE_STATION || + priv->bss_mode == NL80211_IFTYPE_P2P_CLIENT) { + switch (adapter->config_bands) { + case BAND_B: + dev_dbg(adapter->dev, "info: infra band=%d " + "supported_rates_b\n", adapter->config_bands); + k = mwifiex_copy_rates(rates, k, supported_rates_b, + sizeof(supported_rates_b)); + break; + case BAND_G: + case BAND_G | BAND_GN: + case BAND_G | BAND_GN | BAND_GAC: + dev_dbg(adapter->dev, "info: infra band=%d " + "supported_rates_g\n", adapter->config_bands); + k = mwifiex_copy_rates(rates, k, supported_rates_g, + sizeof(supported_rates_g)); + break; + case BAND_B | BAND_G: + case BAND_A | BAND_B | BAND_G: + case BAND_A | BAND_B: + case BAND_A | BAND_B | BAND_G | BAND_GN | BAND_AN: + case BAND_A | BAND_B | BAND_G | BAND_GN | BAND_AN | BAND_AAC: + case BAND_A | BAND_B | BAND_G | BAND_GN | BAND_AN | + BAND_AAC | BAND_GAC: + case BAND_B | BAND_G | BAND_GN: + case BAND_B | BAND_G | BAND_GN | BAND_GAC: + dev_dbg(adapter->dev, "info: infra band=%d " + "supported_rates_bg\n", adapter->config_bands); + k = mwifiex_copy_rates(rates, k, supported_rates_bg, + sizeof(supported_rates_bg)); + break; + case BAND_A: + case BAND_A | BAND_G: + dev_dbg(adapter->dev, "info: infra band=%d " + "supported_rates_a\n", adapter->config_bands); + k = mwifiex_copy_rates(rates, k, supported_rates_a, + sizeof(supported_rates_a)); + break; + case BAND_AN: + case BAND_A | BAND_AN: + case BAND_A | BAND_AN | BAND_AAC: + case BAND_A | BAND_G | BAND_AN | BAND_GN: + case BAND_A | BAND_G | BAND_AN | BAND_GN | BAND_AAC: + dev_dbg(adapter->dev, "info: infra band=%d " + "supported_rates_a\n", adapter->config_bands); + k = mwifiex_copy_rates(rates, k, supported_rates_a, + sizeof(supported_rates_a)); + break; + case BAND_GN: + case BAND_GN | BAND_GAC: + dev_dbg(adapter->dev, "info: infra band=%d " + "supported_rates_n\n", adapter->config_bands); + k = mwifiex_copy_rates(rates, k, supported_rates_n, + sizeof(supported_rates_n)); + break; + } + } else { + /* Ad-hoc mode */ + switch (adapter->adhoc_start_band) { + case BAND_B: + dev_dbg(adapter->dev, "info: adhoc B\n"); + k = mwifiex_copy_rates(rates, k, adhoc_rates_b, + sizeof(adhoc_rates_b)); + break; + case BAND_G: + case BAND_G | BAND_GN: + dev_dbg(adapter->dev, "info: adhoc G only\n"); + k = mwifiex_copy_rates(rates, k, adhoc_rates_g, + sizeof(adhoc_rates_g)); + break; + case BAND_B | BAND_G: + case BAND_B | BAND_G | BAND_GN: + dev_dbg(adapter->dev, "info: adhoc BG\n"); + k = mwifiex_copy_rates(rates, k, adhoc_rates_bg, + sizeof(adhoc_rates_bg)); + break; + case BAND_A: + case BAND_A | BAND_AN: + dev_dbg(adapter->dev, "info: adhoc A\n"); + k = mwifiex_copy_rates(rates, k, adhoc_rates_a, + sizeof(adhoc_rates_a)); + break; + } + } + + return k; +} diff --git a/drivers/net/wireless/mwifiex/cmdevt.c b/drivers/net/wireless/mwifiex/cmdevt.c new file mode 100644 index 0000000..7a97f6c --- /dev/null +++ b/drivers/net/wireless/mwifiex/cmdevt.c @@ -0,0 +1,1546 @@ +/* + * Marvell Wireless LAN device driver: commands and events + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" +#include "11ac.h" + +/* + * This function initializes a command node. + * + * The actual allocation of the node is not done by this function. It only + * initiates a node by filling it with default parameters. Similarly, + * allocation of the different buffers used (IOCTL buffer, data buffer) are + * not done by this function either. + */ +static void +mwifiex_init_cmd_node(struct mwifiex_private *priv, + struct cmd_ctrl_node *cmd_node, + u32 cmd_oid, void *data_buf) +{ + cmd_node->priv = priv; + cmd_node->cmd_oid = cmd_oid; + if (priv->adapter->cmd_wait_q_required) { + cmd_node->wait_q_enabled = priv->adapter->cmd_wait_q_required; + priv->adapter->cmd_wait_q_required = false; + cmd_node->cmd_wait_q_woken = false; + cmd_node->condition = &cmd_node->cmd_wait_q_woken; + } + cmd_node->data_buf = data_buf; + cmd_node->cmd_skb = cmd_node->skb; +} + +/* + * This function returns a command node from the free queue depending upon + * availability. + */ +static struct cmd_ctrl_node * +mwifiex_get_cmd_node(struct mwifiex_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_node; + unsigned long flags; + + spin_lock_irqsave(&adapter->cmd_free_q_lock, flags); + if (list_empty(&adapter->cmd_free_q)) { + dev_err(adapter->dev, "GET_CMD_NODE: cmd node not available\n"); + spin_unlock_irqrestore(&adapter->cmd_free_q_lock, flags); + return NULL; + } + cmd_node = list_first_entry(&adapter->cmd_free_q, + struct cmd_ctrl_node, list); + list_del(&cmd_node->list); + spin_unlock_irqrestore(&adapter->cmd_free_q_lock, flags); + + return cmd_node; +} + +/* + * This function cleans up a command node. + * + * The function resets the fields including the buffer pointers. + * This function does not try to free the buffers. They must be + * freed before calling this function. + * + * This function will however call the receive completion callback + * in case a response buffer is still available before resetting + * the pointer. + */ +static void +mwifiex_clean_cmd_node(struct mwifiex_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + cmd_node->cmd_oid = 0; + cmd_node->cmd_flag = 0; + cmd_node->data_buf = NULL; + cmd_node->wait_q_enabled = false; + + if (cmd_node->cmd_skb) + skb_trim(cmd_node->cmd_skb, 0); + + if (cmd_node->resp_skb) { + adapter->if_ops.cmdrsp_complete(adapter, cmd_node->resp_skb); + cmd_node->resp_skb = NULL; + } +} + +/* + * This function sends a host command to the firmware. + * + * The function copies the host command into the driver command + * buffer, which will be transferred to the firmware later by the + * main thread. + */ +static int mwifiex_cmd_host_cmd(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + struct mwifiex_ds_misc_cmd *pcmd_ptr) +{ + /* Copy the HOST command to command buffer */ + memcpy(cmd, pcmd_ptr->cmd, pcmd_ptr->len); + dev_dbg(priv->adapter->dev, "cmd: host cmd size = %d\n", pcmd_ptr->len); + return 0; +} + +/* + * This function downloads a command to the firmware. + * + * The function performs sanity tests, sets the command sequence + * number and size, converts the header fields to CPU format before + * sending. Afterwards, it logs the command ID and action for debugging + * and sets up the command timeout timer. + */ +static int mwifiex_dnld_cmd_to_fw(struct mwifiex_private *priv, + struct cmd_ctrl_node *cmd_node) +{ + + struct mwifiex_adapter *adapter = priv->adapter; + int ret; + struct host_cmd_ds_command *host_cmd; + uint16_t cmd_code; + uint16_t cmd_size; + struct timeval tstamp; + unsigned long flags; + __le32 tmp; + + if (!adapter || !cmd_node) + return -1; + + host_cmd = (struct host_cmd_ds_command *) (cmd_node->cmd_skb->data); + + /* Sanity test */ + if (host_cmd == NULL || host_cmd->size == 0) { + dev_err(adapter->dev, "DNLD_CMD: host_cmd is null" + " or cmd size is 0, not sending\n"); + if (cmd_node->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + mwifiex_recycle_cmd_node(adapter, cmd_node); + return -1; + } + + cmd_code = le16_to_cpu(host_cmd->command); + cmd_size = le16_to_cpu(host_cmd->size); + + if (adapter->hw_status == MWIFIEX_HW_STATUS_RESET && + cmd_code != HostCmd_CMD_FUNC_SHUTDOWN && + cmd_code != HostCmd_CMD_FUNC_INIT) { + dev_err(adapter->dev, + "DNLD_CMD: FW in reset state, ignore cmd %#x\n", + cmd_code); + mwifiex_complete_cmd(adapter, cmd_node); + mwifiex_recycle_cmd_node(adapter, cmd_node); + return -1; + } + + /* Set command sequence number */ + adapter->seq_num++; + host_cmd->seq_num = cpu_to_le16(HostCmd_SET_SEQ_NO_BSS_INFO + (adapter->seq_num, + cmd_node->priv->bss_num, + cmd_node->priv->bss_type)); + + spin_lock_irqsave(&adapter->mwifiex_cmd_lock, flags); + adapter->curr_cmd = cmd_node; + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, flags); + + /* Adjust skb length */ + if (cmd_node->cmd_skb->len > cmd_size) + /* + * cmd_size is less than sizeof(struct host_cmd_ds_command). + * Trim off the unused portion. + */ + skb_trim(cmd_node->cmd_skb, cmd_size); + else if (cmd_node->cmd_skb->len < cmd_size) + /* + * cmd_size is larger than sizeof(struct host_cmd_ds_command) + * because we have appended custom IE TLV. Increase skb length + * accordingly. + */ + skb_put(cmd_node->cmd_skb, cmd_size - cmd_node->cmd_skb->len); + + do_gettimeofday(&tstamp); + dev_dbg(adapter->dev, "cmd: DNLD_CMD: (%lu.%lu): %#x, act %#x, len %d," + " seqno %#x\n", + tstamp.tv_sec, tstamp.tv_usec, cmd_code, + le16_to_cpu(*(__le16 *) ((u8 *) host_cmd + S_DS_GEN)), cmd_size, + le16_to_cpu(host_cmd->seq_num)); + + if (adapter->iface_type == MWIFIEX_USB) { + tmp = cpu_to_le32(MWIFIEX_USB_TYPE_CMD); + skb_push(cmd_node->cmd_skb, MWIFIEX_TYPE_LEN); + memcpy(cmd_node->cmd_skb->data, &tmp, MWIFIEX_TYPE_LEN); + adapter->cmd_sent = true; + ret = adapter->if_ops.host_to_card(adapter, + MWIFIEX_USB_EP_CMD_EVENT, + cmd_node->cmd_skb, NULL); + skb_pull(cmd_node->cmd_skb, MWIFIEX_TYPE_LEN); + if (ret == -EBUSY) + cmd_node->cmd_skb = NULL; + } else { + skb_push(cmd_node->cmd_skb, INTF_HEADER_LEN); + ret = adapter->if_ops.host_to_card(adapter, MWIFIEX_TYPE_CMD, + cmd_node->cmd_skb, NULL); + skb_pull(cmd_node->cmd_skb, INTF_HEADER_LEN); + } + + if (ret == -1) { + dev_err(adapter->dev, "DNLD_CMD: host to card failed\n"); + if (adapter->iface_type == MWIFIEX_USB) + adapter->cmd_sent = false; + if (cmd_node->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + mwifiex_recycle_cmd_node(adapter, adapter->curr_cmd); + + spin_lock_irqsave(&adapter->mwifiex_cmd_lock, flags); + adapter->curr_cmd = NULL; + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, flags); + + adapter->dbg.num_cmd_host_to_card_failure++; + return -1; + } + + /* Save the last command id and action to debug log */ + adapter->dbg.last_cmd_index = + (adapter->dbg.last_cmd_index + 1) % DBG_CMD_NUM; + adapter->dbg.last_cmd_id[adapter->dbg.last_cmd_index] = cmd_code; + adapter->dbg.last_cmd_act[adapter->dbg.last_cmd_index] = + le16_to_cpu(*(__le16 *) ((u8 *) host_cmd + S_DS_GEN)); + + /* Clear BSS_NO_BITS from HostCmd */ + cmd_code &= HostCmd_CMD_ID_MASK; + + /* Setup the timer after transmit command */ + mod_timer(&adapter->cmd_timer, + jiffies + msecs_to_jiffies(MWIFIEX_TIMER_10S)); + + return 0; +} + +/* + * This function downloads a sleep confirm command to the firmware. + * + * The function performs sanity tests, sets the command sequence + * number and size, converts the header fields to CPU format before + * sending. + * + * No responses are needed for sleep confirm command. + */ +static int mwifiex_dnld_sleep_confirm_cmd(struct mwifiex_adapter *adapter) +{ + int ret; + struct mwifiex_private *priv; + struct mwifiex_opt_sleep_confirm *sleep_cfm_buf = + (struct mwifiex_opt_sleep_confirm *) + adapter->sleep_cfm->data; + struct sk_buff *sleep_cfm_tmp; + __le32 tmp; + + priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); + + sleep_cfm_buf->seq_num = + cpu_to_le16((HostCmd_SET_SEQ_NO_BSS_INFO + (adapter->seq_num, priv->bss_num, + priv->bss_type))); + adapter->seq_num++; + + if (adapter->iface_type == MWIFIEX_USB) { + sleep_cfm_tmp = + dev_alloc_skb(sizeof(struct mwifiex_opt_sleep_confirm) + + MWIFIEX_TYPE_LEN); + skb_put(sleep_cfm_tmp, sizeof(struct mwifiex_opt_sleep_confirm) + + MWIFIEX_TYPE_LEN); + tmp = cpu_to_le32(MWIFIEX_USB_TYPE_CMD); + memcpy(sleep_cfm_tmp->data, &tmp, MWIFIEX_TYPE_LEN); + memcpy(sleep_cfm_tmp->data + MWIFIEX_TYPE_LEN, + adapter->sleep_cfm->data, + sizeof(struct mwifiex_opt_sleep_confirm)); + ret = adapter->if_ops.host_to_card(adapter, + MWIFIEX_USB_EP_CMD_EVENT, + sleep_cfm_tmp, NULL); + if (ret != -EBUSY) + dev_kfree_skb_any(sleep_cfm_tmp); + } else { + skb_push(adapter->sleep_cfm, INTF_HEADER_LEN); + ret = adapter->if_ops.host_to_card(adapter, MWIFIEX_TYPE_CMD, + adapter->sleep_cfm, NULL); + skb_pull(adapter->sleep_cfm, INTF_HEADER_LEN); + } + + if (ret == -1) { + dev_err(adapter->dev, "SLEEP_CFM: failed\n"); + adapter->dbg.num_cmd_sleep_cfm_host_to_card_failure++; + return -1; + } + if (GET_BSS_ROLE(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY)) + == MWIFIEX_BSS_ROLE_STA) { + if (!sleep_cfm_buf->resp_ctrl) + /* Response is not needed for sleep + confirm command */ + adapter->ps_state = PS_STATE_SLEEP; + else + adapter->ps_state = PS_STATE_SLEEP_CFM; + + if (!sleep_cfm_buf->resp_ctrl && + (adapter->is_hs_configured && + !adapter->sleep_period.period)) { + adapter->pm_wakeup_card_req = true; + mwifiex_hs_activated_event(mwifiex_get_priv + (adapter, MWIFIEX_BSS_ROLE_STA), true); + } + } + + return ret; +} + +/* + * This function allocates the command buffers and links them to + * the command free queue. + * + * The driver uses a pre allocated number of command buffers, which + * are created at driver initializations and freed at driver cleanup. + * Every command needs to obtain a command buffer from this pool before + * it can be issued. The command free queue lists the command buffers + * currently free to use, while the command pending queue lists the + * command buffers already in use and awaiting handling. Command buffers + * are returned to the free queue after use. + */ +int mwifiex_alloc_cmd_buffer(struct mwifiex_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_array; + u32 i; + + /* Allocate and initialize struct cmd_ctrl_node */ + cmd_array = kcalloc(MWIFIEX_NUM_OF_CMD_BUFFER, + sizeof(struct cmd_ctrl_node), GFP_KERNEL); + if (!cmd_array) + return -ENOMEM; + + adapter->cmd_pool = cmd_array; + + /* Allocate and initialize command buffers */ + for (i = 0; i < MWIFIEX_NUM_OF_CMD_BUFFER; i++) { + cmd_array[i].skb = dev_alloc_skb(MWIFIEX_SIZE_OF_CMD_BUFFER); + if (!cmd_array[i].skb) { + dev_err(adapter->dev, "ALLOC_CMD_BUF: out of memory\n"); + return -1; + } + } + + for (i = 0; i < MWIFIEX_NUM_OF_CMD_BUFFER; i++) + mwifiex_insert_cmd_to_free_q(adapter, &cmd_array[i]); + + return 0; +} + +/* + * This function frees the command buffers. + * + * The function calls the completion callback for all the command + * buffers that still have response buffers associated with them. + */ +int mwifiex_free_cmd_buffer(struct mwifiex_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_array; + u32 i; + + /* Need to check if cmd pool is allocated or not */ + if (!adapter->cmd_pool) { + dev_dbg(adapter->dev, "info: FREE_CMD_BUF: cmd_pool is null\n"); + return 0; + } + + cmd_array = adapter->cmd_pool; + + /* Release shared memory buffers */ + for (i = 0; i < MWIFIEX_NUM_OF_CMD_BUFFER; i++) { + if (cmd_array[i].skb) { + dev_dbg(adapter->dev, "cmd: free cmd buffer %d\n", i); + dev_kfree_skb_any(cmd_array[i].skb); + } + if (!cmd_array[i].resp_skb) + continue; + + if (adapter->iface_type == MWIFIEX_USB) + adapter->if_ops.cmdrsp_complete(adapter, + cmd_array[i].resp_skb); + else + dev_kfree_skb_any(cmd_array[i].resp_skb); + } + /* Release struct cmd_ctrl_node */ + if (adapter->cmd_pool) { + dev_dbg(adapter->dev, "cmd: free cmd pool\n"); + kfree(adapter->cmd_pool); + adapter->cmd_pool = NULL; + } + + return 0; +} + +/* + * This function handles events generated by firmware. + * + * Event body of events received from firmware are not used (though they are + * saved), only the event ID is used. Some events are re-invoked by + * the driver, with a new event body. + * + * After processing, the function calls the completion callback + * for cleanup. + */ +int mwifiex_process_event(struct mwifiex_adapter *adapter) +{ + int ret; + struct mwifiex_private *priv = + mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); + struct sk_buff *skb = adapter->event_skb; + u32 eventcause = adapter->event_cause; + struct timeval tstamp; + struct mwifiex_rxinfo *rx_info; + + /* Save the last event to debug log */ + adapter->dbg.last_event_index = + (adapter->dbg.last_event_index + 1) % DBG_CMD_NUM; + adapter->dbg.last_event[adapter->dbg.last_event_index] = + (u16) eventcause; + + /* Get BSS number and corresponding priv */ + priv = mwifiex_get_priv_by_id(adapter, EVENT_GET_BSS_NUM(eventcause), + EVENT_GET_BSS_TYPE(eventcause)); + if (!priv) + priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); + /* Clear BSS_NO_BITS from event */ + eventcause &= EVENT_ID_MASK; + adapter->event_cause = eventcause; + + if (skb) { + rx_info = MWIFIEX_SKB_RXCB(skb); + rx_info->bss_num = priv->bss_num; + rx_info->bss_type = priv->bss_type; + } + + if (eventcause != EVENT_PS_SLEEP && eventcause != EVENT_PS_AWAKE) { + do_gettimeofday(&tstamp); + dev_dbg(adapter->dev, "event: %lu.%lu: cause: %#x\n", + tstamp.tv_sec, tstamp.tv_usec, eventcause); + } else { + /* Handle PS_SLEEP/AWAKE events on STA */ + priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA); + if (!priv) + priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); + } + + if (priv->bss_role == MWIFIEX_BSS_ROLE_UAP) + ret = mwifiex_process_uap_event(priv); + else + ret = mwifiex_process_sta_event(priv); + + adapter->event_cause = 0; + adapter->event_skb = NULL; + adapter->if_ops.event_complete(adapter, skb); + + return ret; +} + +/* + * This function is used to send synchronous command to the firmware. + * + * it allocates a wait queue for the command and wait for the command + * response. + */ +int mwifiex_send_cmd_sync(struct mwifiex_private *priv, uint16_t cmd_no, + u16 cmd_action, u32 cmd_oid, void *data_buf) +{ + int ret = 0; + struct mwifiex_adapter *adapter = priv->adapter; + + adapter->cmd_wait_q_required = true; + + ret = mwifiex_send_cmd_async(priv, cmd_no, cmd_action, cmd_oid, + data_buf); + + return ret; +} + + +/* + * This function prepares a command and asynchronously send it to the firmware. + * + * Preparation includes - + * - Sanity tests to make sure the card is still present or the FW + * is not reset + * - Getting a new command node from the command free queue + * - Initializing the command node for default parameters + * - Fill up the non-default parameters and buffer pointers + * - Add the command to pending queue + */ +int mwifiex_send_cmd_async(struct mwifiex_private *priv, uint16_t cmd_no, + u16 cmd_action, u32 cmd_oid, void *data_buf) +{ + int ret; + struct mwifiex_adapter *adapter = priv->adapter; + struct cmd_ctrl_node *cmd_node; + struct host_cmd_ds_command *cmd_ptr; + + if (!adapter) { + pr_err("PREP_CMD: adapter is NULL\n"); + return -1; + } + + if (adapter->is_suspended) { + dev_err(adapter->dev, "PREP_CMD: device in suspended state\n"); + return -1; + } + + if (adapter->surprise_removed) { + dev_err(adapter->dev, "PREP_CMD: card is removed\n"); + return -1; + } + + if (adapter->hw_status == MWIFIEX_HW_STATUS_RESET) { + if (cmd_no != HostCmd_CMD_FUNC_INIT) { + dev_err(adapter->dev, "PREP_CMD: FW in reset state\n"); + return -1; + } + } + + /* Get a new command node */ + cmd_node = mwifiex_get_cmd_node(adapter); + + if (!cmd_node) { + dev_err(adapter->dev, "PREP_CMD: no free cmd node\n"); + return -1; + } + + /* Initialize the command node */ + mwifiex_init_cmd_node(priv, cmd_node, cmd_oid, data_buf); + + if (!cmd_node->cmd_skb) { + dev_err(adapter->dev, "PREP_CMD: no free cmd buf\n"); + return -1; + } + + memset(skb_put(cmd_node->cmd_skb, sizeof(struct host_cmd_ds_command)), + 0, sizeof(struct host_cmd_ds_command)); + + cmd_ptr = (struct host_cmd_ds_command *) (cmd_node->cmd_skb->data); + cmd_ptr->command = cpu_to_le16(cmd_no); + cmd_ptr->result = 0; + + /* Prepare command */ + if (cmd_no) { + switch (cmd_no) { + case HostCmd_CMD_UAP_SYS_CONFIG: + case HostCmd_CMD_UAP_BSS_START: + case HostCmd_CMD_UAP_BSS_STOP: + ret = mwifiex_uap_prepare_cmd(priv, cmd_no, cmd_action, + cmd_oid, data_buf, + cmd_ptr); + break; + default: + ret = mwifiex_sta_prepare_cmd(priv, cmd_no, cmd_action, + cmd_oid, data_buf, + cmd_ptr); + break; + } + } else { + ret = mwifiex_cmd_host_cmd(priv, cmd_ptr, data_buf); + cmd_node->cmd_flag |= CMD_F_HOSTCMD; + } + + /* Return error, since the command preparation failed */ + if (ret) { + dev_err(adapter->dev, "PREP_CMD: cmd %#x preparation failed\n", + cmd_no); + mwifiex_insert_cmd_to_free_q(adapter, cmd_node); + return -1; + } + + /* Send command */ + if (cmd_no == HostCmd_CMD_802_11_SCAN) { + mwifiex_queue_scan_cmd(priv, cmd_node); + } else { + mwifiex_insert_cmd_to_pending_q(adapter, cmd_node, true); + queue_work(adapter->workqueue, &adapter->main_work); + if (cmd_node->wait_q_enabled) + ret = mwifiex_wait_queue_complete(adapter, cmd_node); + } + + return ret; +} + +/* + * This function returns a command to the command free queue. + * + * The function also calls the completion callback if required, before + * cleaning the command node and re-inserting it into the free queue. + */ +void +mwifiex_insert_cmd_to_free_q(struct mwifiex_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + unsigned long flags; + + if (!cmd_node) + return; + + if (cmd_node->wait_q_enabled) + mwifiex_complete_cmd(adapter, cmd_node); + /* Clean the node */ + mwifiex_clean_cmd_node(adapter, cmd_node); + + /* Insert node into cmd_free_q */ + spin_lock_irqsave(&adapter->cmd_free_q_lock, flags); + list_add_tail(&cmd_node->list, &adapter->cmd_free_q); + spin_unlock_irqrestore(&adapter->cmd_free_q_lock, flags); +} + +/* This function reuses a command node. */ +void mwifiex_recycle_cmd_node(struct mwifiex_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + struct host_cmd_ds_command *host_cmd = (void *)cmd_node->cmd_skb->data; + + mwifiex_insert_cmd_to_free_q(adapter, cmd_node); + + atomic_dec(&adapter->cmd_pending); + dev_dbg(adapter->dev, "cmd: FREE_CMD: cmd=%#x, cmd_pending=%d\n", + le16_to_cpu(host_cmd->command), + atomic_read(&adapter->cmd_pending)); +} + +/* + * This function queues a command to the command pending queue. + * + * This in effect adds the command to the command list to be executed. + * Exit PS command is handled specially, by placing it always to the + * front of the command queue. + */ +void +mwifiex_insert_cmd_to_pending_q(struct mwifiex_adapter *adapter, + struct cmd_ctrl_node *cmd_node, u32 add_tail) +{ + struct host_cmd_ds_command *host_cmd = NULL; + u16 command; + unsigned long flags; + + host_cmd = (struct host_cmd_ds_command *) (cmd_node->cmd_skb->data); + if (!host_cmd) { + dev_err(adapter->dev, "QUEUE_CMD: host_cmd is NULL\n"); + return; + } + + command = le16_to_cpu(host_cmd->command); + + /* Exit_PS command needs to be queued in the header always. */ + if (command == HostCmd_CMD_802_11_PS_MODE_ENH) { + struct host_cmd_ds_802_11_ps_mode_enh *pm = + &host_cmd->params.psmode_enh; + if ((le16_to_cpu(pm->action) == DIS_PS) || + (le16_to_cpu(pm->action) == DIS_AUTO_PS)) { + if (adapter->ps_state != PS_STATE_AWAKE) + add_tail = false; + } + } + + spin_lock_irqsave(&adapter->cmd_pending_q_lock, flags); + if (add_tail) + list_add_tail(&cmd_node->list, &adapter->cmd_pending_q); + else + list_add(&cmd_node->list, &adapter->cmd_pending_q); + spin_unlock_irqrestore(&adapter->cmd_pending_q_lock, flags); + + atomic_inc(&adapter->cmd_pending); + dev_dbg(adapter->dev, "cmd: QUEUE_CMD: cmd=%#x, cmd_pending=%d\n", + command, atomic_read(&adapter->cmd_pending)); +} + +/* + * This function executes the next command in command pending queue. + * + * This function will fail if a command is already in processing stage, + * otherwise it will dequeue the first command from the command pending + * queue and send to the firmware. + * + * If the device is currently in host sleep mode, any commands, except the + * host sleep configuration command will de-activate the host sleep. For PS + * mode, the function will put the firmware back to sleep if applicable. + */ +int mwifiex_exec_next_cmd(struct mwifiex_adapter *adapter) +{ + struct mwifiex_private *priv; + struct cmd_ctrl_node *cmd_node; + int ret = 0; + struct host_cmd_ds_command *host_cmd; + unsigned long cmd_flags; + unsigned long cmd_pending_q_flags; + + /* Check if already in processing */ + if (adapter->curr_cmd) { + dev_err(adapter->dev, "EXEC_NEXT_CMD: cmd in processing\n"); + return -1; + } + + spin_lock_irqsave(&adapter->mwifiex_cmd_lock, cmd_flags); + /* Check if any command is pending */ + spin_lock_irqsave(&adapter->cmd_pending_q_lock, cmd_pending_q_flags); + if (list_empty(&adapter->cmd_pending_q)) { + spin_unlock_irqrestore(&adapter->cmd_pending_q_lock, + cmd_pending_q_flags); + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, cmd_flags); + return 0; + } + cmd_node = list_first_entry(&adapter->cmd_pending_q, + struct cmd_ctrl_node, list); + spin_unlock_irqrestore(&adapter->cmd_pending_q_lock, + cmd_pending_q_flags); + + host_cmd = (struct host_cmd_ds_command *) (cmd_node->cmd_skb->data); + priv = cmd_node->priv; + + if (adapter->ps_state != PS_STATE_AWAKE) { + dev_err(adapter->dev, "%s: cannot send cmd in sleep state," + " this should not happen\n", __func__); + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, cmd_flags); + return ret; + } + + spin_lock_irqsave(&adapter->cmd_pending_q_lock, cmd_pending_q_flags); + list_del(&cmd_node->list); + spin_unlock_irqrestore(&adapter->cmd_pending_q_lock, + cmd_pending_q_flags); + + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, cmd_flags); + ret = mwifiex_dnld_cmd_to_fw(priv, cmd_node); + priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); + /* Any command sent to the firmware when host is in sleep + * mode should de-configure host sleep. We should skip the + * host sleep configuration command itself though + */ + if (priv && (host_cmd->command != + cpu_to_le16(HostCmd_CMD_802_11_HS_CFG_ENH))) { + if (adapter->hs_activated) { + adapter->is_hs_configured = false; + mwifiex_hs_activated_event(priv, false); + } + } + + return ret; +} + +/* + * This function handles the command response. + * + * After processing, the function cleans the command node and puts + * it back to the command free queue. + */ +int mwifiex_process_cmdresp(struct mwifiex_adapter *adapter) +{ + struct host_cmd_ds_command *resp; + struct mwifiex_private *priv = + mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); + int ret = 0; + uint16_t orig_cmdresp_no; + uint16_t cmdresp_no; + uint16_t cmdresp_result; + struct timeval tstamp; + unsigned long flags; + + /* Now we got response from FW, cancel the command timer */ + del_timer(&adapter->cmd_timer); + + if (!adapter->curr_cmd || !adapter->curr_cmd->resp_skb) { + resp = (struct host_cmd_ds_command *) adapter->upld_buf; + dev_err(adapter->dev, "CMD_RESP: NULL curr_cmd, %#x\n", + le16_to_cpu(resp->command)); + return -1; + } + + adapter->num_cmd_timeout = 0; + + resp = (struct host_cmd_ds_command *) adapter->curr_cmd->resp_skb->data; + if (adapter->curr_cmd->cmd_flag & CMD_F_CANCELED) { + dev_err(adapter->dev, "CMD_RESP: %#x been canceled\n", + le16_to_cpu(resp->command)); + mwifiex_recycle_cmd_node(adapter, adapter->curr_cmd); + spin_lock_irqsave(&adapter->mwifiex_cmd_lock, flags); + adapter->curr_cmd = NULL; + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, flags); + return -1; + } + + if (adapter->curr_cmd->cmd_flag & CMD_F_HOSTCMD) { + /* Copy original response back to response buffer */ + struct mwifiex_ds_misc_cmd *hostcmd; + uint16_t size = le16_to_cpu(resp->size); + dev_dbg(adapter->dev, "info: host cmd resp size = %d\n", size); + size = min_t(u16, size, MWIFIEX_SIZE_OF_CMD_BUFFER); + if (adapter->curr_cmd->data_buf) { + hostcmd = adapter->curr_cmd->data_buf; + hostcmd->len = size; + memcpy(hostcmd->cmd, resp, size); + } + } + orig_cmdresp_no = le16_to_cpu(resp->command); + + /* Get BSS number and corresponding priv */ + priv = mwifiex_get_priv_by_id(adapter, + HostCmd_GET_BSS_NO(le16_to_cpu(resp->seq_num)), + HostCmd_GET_BSS_TYPE(le16_to_cpu(resp->seq_num))); + if (!priv) + priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); + /* Clear RET_BIT from HostCmd */ + resp->command = cpu_to_le16(orig_cmdresp_no & HostCmd_CMD_ID_MASK); + + cmdresp_no = le16_to_cpu(resp->command); + cmdresp_result = le16_to_cpu(resp->result); + + /* Save the last command response to debug log */ + adapter->dbg.last_cmd_resp_index = + (adapter->dbg.last_cmd_resp_index + 1) % DBG_CMD_NUM; + adapter->dbg.last_cmd_resp_id[adapter->dbg.last_cmd_resp_index] = + orig_cmdresp_no; + + do_gettimeofday(&tstamp); + dev_dbg(adapter->dev, "cmd: CMD_RESP: (%lu.%lu): 0x%x, result %d," + " len %d, seqno 0x%x\n", + tstamp.tv_sec, tstamp.tv_usec, orig_cmdresp_no, cmdresp_result, + le16_to_cpu(resp->size), le16_to_cpu(resp->seq_num)); + + if (!(orig_cmdresp_no & HostCmd_RET_BIT)) { + dev_err(adapter->dev, "CMD_RESP: invalid cmd resp\n"); + if (adapter->curr_cmd->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + + mwifiex_recycle_cmd_node(adapter, adapter->curr_cmd); + spin_lock_irqsave(&adapter->mwifiex_cmd_lock, flags); + adapter->curr_cmd = NULL; + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, flags); + return -1; + } + + if (adapter->curr_cmd->cmd_flag & CMD_F_HOSTCMD) { + adapter->curr_cmd->cmd_flag &= ~CMD_F_HOSTCMD; + if ((cmdresp_result == HostCmd_RESULT_OK) && + (cmdresp_no == HostCmd_CMD_802_11_HS_CFG_ENH)) + ret = mwifiex_ret_802_11_hs_cfg(priv, resp); + } else { + /* handle response */ + ret = mwifiex_process_sta_cmdresp(priv, cmdresp_no, resp); + } + + /* Check init command response */ + if (adapter->hw_status == MWIFIEX_HW_STATUS_INITIALIZING) { + if (ret) { + dev_err(adapter->dev, "%s: cmd %#x failed during " + "initialization\n", __func__, cmdresp_no); + mwifiex_init_fw_complete(adapter); + return -1; + } else if (adapter->last_init_cmd == cmdresp_no) + adapter->hw_status = MWIFIEX_HW_STATUS_INIT_DONE; + } + + if (adapter->curr_cmd) { + if (adapter->curr_cmd->wait_q_enabled) + adapter->cmd_wait_q.status = ret; + + mwifiex_recycle_cmd_node(adapter, adapter->curr_cmd); + + spin_lock_irqsave(&adapter->mwifiex_cmd_lock, flags); + adapter->curr_cmd = NULL; + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, flags); + } + + return ret; +} + +/* + * This function handles the timeout of command sending. + * + * It will re-send the same command again. + */ +void +mwifiex_cmd_timeout_func(unsigned long function_context) +{ + struct mwifiex_adapter *adapter = + (struct mwifiex_adapter *) function_context; + struct cmd_ctrl_node *cmd_node; + struct timeval tstamp; + + adapter->num_cmd_timeout++; + adapter->dbg.num_cmd_timeout++; + if (!adapter->curr_cmd) { + dev_dbg(adapter->dev, "cmd: empty curr_cmd\n"); + return; + } + cmd_node = adapter->curr_cmd; + if (cmd_node) { + adapter->dbg.timeout_cmd_id = + adapter->dbg.last_cmd_id[adapter->dbg.last_cmd_index]; + adapter->dbg.timeout_cmd_act = + adapter->dbg.last_cmd_act[adapter->dbg.last_cmd_index]; + do_gettimeofday(&tstamp); + dev_err(adapter->dev, + "%s: Timeout cmd id (%lu.%lu) = %#x, act = %#x\n", + __func__, tstamp.tv_sec, tstamp.tv_usec, + adapter->dbg.timeout_cmd_id, + adapter->dbg.timeout_cmd_act); + + dev_err(adapter->dev, "num_data_h2c_failure = %d\n", + adapter->dbg.num_tx_host_to_card_failure); + dev_err(adapter->dev, "num_cmd_h2c_failure = %d\n", + adapter->dbg.num_cmd_host_to_card_failure); + + dev_err(adapter->dev, "num_cmd_timeout = %d\n", + adapter->dbg.num_cmd_timeout); + dev_err(adapter->dev, "num_tx_timeout = %d\n", + adapter->dbg.num_tx_timeout); + + dev_err(adapter->dev, "last_cmd_index = %d\n", + adapter->dbg.last_cmd_index); + dev_err(adapter->dev, "last_cmd_id: %*ph\n", + (int)sizeof(adapter->dbg.last_cmd_id), + adapter->dbg.last_cmd_id); + dev_err(adapter->dev, "last_cmd_act: %*ph\n", + (int)sizeof(adapter->dbg.last_cmd_act), + adapter->dbg.last_cmd_act); + + dev_err(adapter->dev, "last_cmd_resp_index = %d\n", + adapter->dbg.last_cmd_resp_index); + dev_err(adapter->dev, "last_cmd_resp_id: %*ph\n", + (int)sizeof(adapter->dbg.last_cmd_resp_id), + adapter->dbg.last_cmd_resp_id); + + dev_err(adapter->dev, "last_event_index = %d\n", + adapter->dbg.last_event_index); + dev_err(adapter->dev, "last_event: %*ph\n", + (int)sizeof(adapter->dbg.last_event), + adapter->dbg.last_event); + + dev_err(adapter->dev, "data_sent=%d cmd_sent=%d\n", + adapter->data_sent, adapter->cmd_sent); + + dev_err(adapter->dev, "ps_mode=%d ps_state=%d\n", + adapter->ps_mode, adapter->ps_state); + + if (cmd_node->wait_q_enabled) { + adapter->cmd_wait_q.status = -ETIMEDOUT; + wake_up_interruptible(&adapter->cmd_wait_q.wait); + mwifiex_cancel_pending_ioctl(adapter); + /* reset cmd_sent flag to unblock new commands */ + adapter->cmd_sent = false; + } + } + if (adapter->hw_status == MWIFIEX_HW_STATUS_INITIALIZING) + mwifiex_init_fw_complete(adapter); + + if (adapter->if_ops.card_reset) + adapter->if_ops.card_reset(adapter); +} + +/* + * This function cancels all the pending commands. + * + * The current command, all commands in command pending queue and all scan + * commands in scan pending queue are cancelled. All the completion callbacks + * are called with failure status to ensure cleanup. + */ +void +mwifiex_cancel_all_pending_cmd(struct mwifiex_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_node = NULL, *tmp_node; + unsigned long flags; + + /* Cancel current cmd */ + if ((adapter->curr_cmd) && (adapter->curr_cmd->wait_q_enabled)) { + spin_lock_irqsave(&adapter->mwifiex_cmd_lock, flags); + adapter->curr_cmd->wait_q_enabled = false; + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, flags); + adapter->cmd_wait_q.status = -1; + mwifiex_complete_cmd(adapter, adapter->curr_cmd); + } + /* Cancel all pending command */ + spin_lock_irqsave(&adapter->cmd_pending_q_lock, flags); + list_for_each_entry_safe(cmd_node, tmp_node, + &adapter->cmd_pending_q, list) { + list_del(&cmd_node->list); + spin_unlock_irqrestore(&adapter->cmd_pending_q_lock, flags); + + if (cmd_node->wait_q_enabled) { + adapter->cmd_wait_q.status = -1; + mwifiex_complete_cmd(adapter, cmd_node); + cmd_node->wait_q_enabled = false; + } + mwifiex_recycle_cmd_node(adapter, cmd_node); + spin_lock_irqsave(&adapter->cmd_pending_q_lock, flags); + } + spin_unlock_irqrestore(&adapter->cmd_pending_q_lock, flags); + + /* Cancel all pending scan command */ + spin_lock_irqsave(&adapter->scan_pending_q_lock, flags); + list_for_each_entry_safe(cmd_node, tmp_node, + &adapter->scan_pending_q, list) { + list_del(&cmd_node->list); + spin_unlock_irqrestore(&adapter->scan_pending_q_lock, flags); + + cmd_node->wait_q_enabled = false; + mwifiex_insert_cmd_to_free_q(adapter, cmd_node); + spin_lock_irqsave(&adapter->scan_pending_q_lock, flags); + } + spin_unlock_irqrestore(&adapter->scan_pending_q_lock, flags); + + spin_lock_irqsave(&adapter->mwifiex_cmd_lock, flags); + adapter->scan_processing = false; + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, flags); +} + +/* + * This function cancels all pending commands that matches with + * the given IOCTL request. + * + * Both the current command buffer and the pending command queue are + * searched for matching IOCTL request. The completion callback of + * the matched command is called with failure status to ensure cleanup. + * In case of scan commands, all pending commands in scan pending queue + * are cancelled. + */ +void +mwifiex_cancel_pending_ioctl(struct mwifiex_adapter *adapter) +{ + struct cmd_ctrl_node *cmd_node = NULL, *tmp_node = NULL; + unsigned long cmd_flags; + unsigned long scan_pending_q_flags; + uint16_t cancel_scan_cmd = false; + + if ((adapter->curr_cmd) && + (adapter->curr_cmd->wait_q_enabled)) { + spin_lock_irqsave(&adapter->mwifiex_cmd_lock, cmd_flags); + cmd_node = adapter->curr_cmd; + cmd_node->wait_q_enabled = false; + cmd_node->cmd_flag |= CMD_F_CANCELED; + mwifiex_recycle_cmd_node(adapter, cmd_node); + mwifiex_complete_cmd(adapter, adapter->curr_cmd); + adapter->curr_cmd = NULL; + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, cmd_flags); + } + + /* Cancel all pending scan command */ + spin_lock_irqsave(&adapter->scan_pending_q_lock, + scan_pending_q_flags); + list_for_each_entry_safe(cmd_node, tmp_node, + &adapter->scan_pending_q, list) { + list_del(&cmd_node->list); + spin_unlock_irqrestore(&adapter->scan_pending_q_lock, + scan_pending_q_flags); + cmd_node->wait_q_enabled = false; + mwifiex_insert_cmd_to_free_q(adapter, cmd_node); + spin_lock_irqsave(&adapter->scan_pending_q_lock, + scan_pending_q_flags); + cancel_scan_cmd = true; + } + spin_unlock_irqrestore(&adapter->scan_pending_q_lock, + scan_pending_q_flags); + + if (cancel_scan_cmd) { + spin_lock_irqsave(&adapter->mwifiex_cmd_lock, cmd_flags); + adapter->scan_processing = false; + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, cmd_flags); + } + adapter->cmd_wait_q.status = -1; +} + +/* + * This function sends the sleep confirm command to firmware, if + * possible. + * + * The sleep confirm command cannot be issued if command response, + * data response or event response is awaiting handling, or if we + * are in the middle of sending a command, or expecting a command + * response. + */ +void +mwifiex_check_ps_cond(struct mwifiex_adapter *adapter) +{ + if (!adapter->cmd_sent && + !adapter->curr_cmd && !IS_CARD_RX_RCVD(adapter)) + mwifiex_dnld_sleep_confirm_cmd(adapter); + else + dev_dbg(adapter->dev, + "cmd: Delay Sleep Confirm (%s%s%s)\n", + (adapter->cmd_sent) ? "D" : "", + (adapter->curr_cmd) ? "C" : "", + (IS_CARD_RX_RCVD(adapter)) ? "R" : ""); +} + +/* + * This function sends a Host Sleep activated event to applications. + * + * This event is generated by the driver, with a blank event body. + */ +void +mwifiex_hs_activated_event(struct mwifiex_private *priv, u8 activated) +{ + if (activated) { + if (priv->adapter->is_hs_configured) { + priv->adapter->hs_activated = true; + mwifiex_update_rxreor_flags(priv->adapter, + RXREOR_FORCE_NO_DROP); + dev_dbg(priv->adapter->dev, "event: hs_activated\n"); + priv->adapter->hs_activate_wait_q_woken = true; + wake_up_interruptible( + &priv->adapter->hs_activate_wait_q); + } else { + dev_dbg(priv->adapter->dev, "event: HS not configured\n"); + } + } else { + dev_dbg(priv->adapter->dev, "event: hs_deactivated\n"); + priv->adapter->hs_activated = false; + } +} + +/* + * This function handles the command response of a Host Sleep configuration + * command. + * + * Handling includes changing the header fields into CPU format + * and setting the current host sleep activation status in driver. + * + * In case host sleep status change, the function generates an event to + * notify the applications. + */ +int mwifiex_ret_802_11_hs_cfg(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_hs_cfg_enh *phs_cfg = + &resp->params.opt_hs_cfg; + uint32_t conditions = le32_to_cpu(phs_cfg->params.hs_config.conditions); + + if (phs_cfg->action == cpu_to_le16(HS_ACTIVATE) && + adapter->iface_type != MWIFIEX_USB) { + mwifiex_hs_activated_event(priv, true); + return 0; + } else { + dev_dbg(adapter->dev, "cmd: CMD_RESP: HS_CFG cmd reply" + " result=%#x, conditions=0x%x gpio=0x%x gap=0x%x\n", + resp->result, conditions, + phs_cfg->params.hs_config.gpio, + phs_cfg->params.hs_config.gap); + } + if (conditions != HS_CFG_CANCEL) { + adapter->is_hs_configured = true; + if (adapter->iface_type == MWIFIEX_USB) + mwifiex_hs_activated_event(priv, true); + } else { + adapter->is_hs_configured = false; + if (adapter->hs_activated) + mwifiex_hs_activated_event(priv, false); + } + + return 0; +} + +/* + * This function wakes up the adapter and generates a Host Sleep + * cancel event on receiving the power up interrupt. + */ +void +mwifiex_process_hs_config(struct mwifiex_adapter *adapter) +{ + dev_dbg(adapter->dev, "info: %s: auto cancelling host sleep" + " since there is interrupt from the firmware\n", __func__); + + adapter->if_ops.wakeup(adapter); + adapter->hs_activated = false; + adapter->is_hs_configured = false; + adapter->is_suspended = false; + mwifiex_hs_activated_event(mwifiex_get_priv(adapter, + MWIFIEX_BSS_ROLE_ANY), + false); +} +EXPORT_SYMBOL_GPL(mwifiex_process_hs_config); + +/* + * This function handles the command response of a sleep confirm command. + * + * The function sets the card state to SLEEP if the response indicates success. + */ +void +mwifiex_process_sleep_confirm_resp(struct mwifiex_adapter *adapter, + u8 *pbuf, u32 upld_len) +{ + struct host_cmd_ds_command *cmd = (struct host_cmd_ds_command *) pbuf; + struct mwifiex_private *priv = + mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); + uint16_t result = le16_to_cpu(cmd->result); + uint16_t command = le16_to_cpu(cmd->command); + uint16_t seq_num = le16_to_cpu(cmd->seq_num); + + if (!upld_len) { + dev_err(adapter->dev, "%s: cmd size is 0\n", __func__); + return; + } + + /* Get BSS number and corresponding priv */ + priv = mwifiex_get_priv_by_id(adapter, HostCmd_GET_BSS_NO(seq_num), + HostCmd_GET_BSS_TYPE(seq_num)); + if (!priv) + priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); + + /* Update sequence number */ + seq_num = HostCmd_GET_SEQ_NO(seq_num); + /* Clear RET_BIT from HostCmd */ + command &= HostCmd_CMD_ID_MASK; + + if (command != HostCmd_CMD_802_11_PS_MODE_ENH) { + dev_err(adapter->dev, + "%s: rcvd unexpected resp for cmd %#x, result = %x\n", + __func__, command, result); + return; + } + + if (result) { + dev_err(adapter->dev, "%s: sleep confirm cmd failed\n", + __func__); + adapter->pm_wakeup_card_req = false; + adapter->ps_state = PS_STATE_AWAKE; + return; + } + adapter->pm_wakeup_card_req = true; + if (adapter->is_hs_configured) + mwifiex_hs_activated_event(mwifiex_get_priv + (adapter, MWIFIEX_BSS_ROLE_ANY), + true); + adapter->ps_state = PS_STATE_SLEEP; + cmd->command = cpu_to_le16(command); + cmd->seq_num = cpu_to_le16(seq_num); +} +EXPORT_SYMBOL_GPL(mwifiex_process_sleep_confirm_resp); + +/* + * This function prepares an enhanced power mode command. + * + * This function can be used to disable power save or to configure + * power save with auto PS or STA PS or auto deep sleep. + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Setting Power Save bitmap, PS parameters TLV, PS mode TLV, + * auto deep sleep TLV (as required) + * - Ensuring correct endian-ness + */ +int mwifiex_cmd_enh_power_mode(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_action, uint16_t ps_bitmap, + struct mwifiex_ds_auto_ds *auto_ds) +{ + struct host_cmd_ds_802_11_ps_mode_enh *psmode_enh = + &cmd->params.psmode_enh; + u8 *tlv; + u16 cmd_size = 0; + + cmd->command = cpu_to_le16(HostCmd_CMD_802_11_PS_MODE_ENH); + if (cmd_action == DIS_AUTO_PS) { + psmode_enh->action = cpu_to_le16(DIS_AUTO_PS); + psmode_enh->params.ps_bitmap = cpu_to_le16(ps_bitmap); + cmd->size = cpu_to_le16(S_DS_GEN + sizeof(psmode_enh->action) + + sizeof(psmode_enh->params.ps_bitmap)); + } else if (cmd_action == GET_PS) { + psmode_enh->action = cpu_to_le16(GET_PS); + psmode_enh->params.ps_bitmap = cpu_to_le16(ps_bitmap); + cmd->size = cpu_to_le16(S_DS_GEN + sizeof(psmode_enh->action) + + sizeof(psmode_enh->params.ps_bitmap)); + } else if (cmd_action == EN_AUTO_PS) { + psmode_enh->action = cpu_to_le16(EN_AUTO_PS); + psmode_enh->params.ps_bitmap = cpu_to_le16(ps_bitmap); + cmd_size = S_DS_GEN + sizeof(psmode_enh->action) + + sizeof(psmode_enh->params.ps_bitmap); + tlv = (u8 *) cmd + cmd_size; + if (ps_bitmap & BITMAP_STA_PS) { + struct mwifiex_adapter *adapter = priv->adapter; + struct mwifiex_ie_types_ps_param *ps_tlv = + (struct mwifiex_ie_types_ps_param *) tlv; + struct mwifiex_ps_param *ps_mode = &ps_tlv->param; + ps_tlv->header.type = cpu_to_le16(TLV_TYPE_PS_PARAM); + ps_tlv->header.len = cpu_to_le16(sizeof(*ps_tlv) - + sizeof(struct mwifiex_ie_types_header)); + cmd_size += sizeof(*ps_tlv); + tlv += sizeof(*ps_tlv); + dev_dbg(adapter->dev, "cmd: PS Command: Enter PS\n"); + ps_mode->null_pkt_interval = + cpu_to_le16(adapter->null_pkt_interval); + ps_mode->multiple_dtims = + cpu_to_le16(adapter->multiple_dtim); + ps_mode->bcn_miss_timeout = + cpu_to_le16(adapter->bcn_miss_time_out); + ps_mode->local_listen_interval = + cpu_to_le16(adapter->local_listen_interval); + ps_mode->adhoc_wake_period = + cpu_to_le16(adapter->adhoc_awake_period); + ps_mode->delay_to_ps = + cpu_to_le16(adapter->delay_to_ps); + ps_mode->mode = cpu_to_le16(adapter->enhanced_ps_mode); + + } + if (ps_bitmap & BITMAP_AUTO_DS) { + struct mwifiex_ie_types_auto_ds_param *auto_ds_tlv = + (struct mwifiex_ie_types_auto_ds_param *) tlv; + u16 idletime = 0; + + auto_ds_tlv->header.type = + cpu_to_le16(TLV_TYPE_AUTO_DS_PARAM); + auto_ds_tlv->header.len = + cpu_to_le16(sizeof(*auto_ds_tlv) - + sizeof(struct mwifiex_ie_types_header)); + cmd_size += sizeof(*auto_ds_tlv); + tlv += sizeof(*auto_ds_tlv); + if (auto_ds) + idletime = auto_ds->idle_time; + dev_dbg(priv->adapter->dev, + "cmd: PS Command: Enter Auto Deep Sleep\n"); + auto_ds_tlv->deep_sleep_timeout = cpu_to_le16(idletime); + } + cmd->size = cpu_to_le16(cmd_size); + } + return 0; +} + +/* + * This function handles the command response of an enhanced power mode + * command. + * + * Handling includes changing the header fields into CPU format + * and setting the current enhanced power mode in driver. + */ +int mwifiex_ret_enh_power_mode(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp, + struct mwifiex_ds_pm_cfg *pm_cfg) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_ps_mode_enh *ps_mode = + &resp->params.psmode_enh; + uint16_t action = le16_to_cpu(ps_mode->action); + uint16_t ps_bitmap = le16_to_cpu(ps_mode->params.ps_bitmap); + uint16_t auto_ps_bitmap = + le16_to_cpu(ps_mode->params.ps_bitmap); + + dev_dbg(adapter->dev, + "info: %s: PS_MODE cmd reply result=%#x action=%#X\n", + __func__, resp->result, action); + if (action == EN_AUTO_PS) { + if (auto_ps_bitmap & BITMAP_AUTO_DS) { + dev_dbg(adapter->dev, "cmd: Enabled auto deep sleep\n"); + priv->adapter->is_deep_sleep = true; + } + if (auto_ps_bitmap & BITMAP_STA_PS) { + dev_dbg(adapter->dev, "cmd: Enabled STA power save\n"); + if (adapter->sleep_period.period) + dev_dbg(adapter->dev, + "cmd: set to uapsd/pps mode\n"); + } + } else if (action == DIS_AUTO_PS) { + if (ps_bitmap & BITMAP_AUTO_DS) { + priv->adapter->is_deep_sleep = false; + dev_dbg(adapter->dev, "cmd: Disabled auto deep sleep\n"); + } + if (ps_bitmap & BITMAP_STA_PS) { + dev_dbg(adapter->dev, "cmd: Disabled STA power save\n"); + if (adapter->sleep_period.period) { + adapter->delay_null_pkt = false; + adapter->tx_lock_flag = false; + adapter->pps_uapsd_mode = false; + } + } + } else if (action == GET_PS) { + if (ps_bitmap & BITMAP_STA_PS) + adapter->ps_mode = MWIFIEX_802_11_POWER_MODE_PSP; + else + adapter->ps_mode = MWIFIEX_802_11_POWER_MODE_CAM; + + dev_dbg(adapter->dev, "cmd: ps_bitmap=%#x\n", ps_bitmap); + + if (pm_cfg) { + /* This section is for get power save mode */ + if (ps_bitmap & BITMAP_STA_PS) + pm_cfg->param.ps_mode = 1; + else + pm_cfg->param.ps_mode = 0; + } + } + return 0; +} + +/* + * This function prepares command to get hardware specifications. + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Setting permanent address parameter + * - Ensuring correct endian-ness + */ +int mwifiex_cmd_get_hw_spec(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd) +{ + struct host_cmd_ds_get_hw_spec *hw_spec = &cmd->params.hw_spec; + + cmd->command = cpu_to_le16(HostCmd_CMD_GET_HW_SPEC); + cmd->size = + cpu_to_le16(sizeof(struct host_cmd_ds_get_hw_spec) + S_DS_GEN); + memcpy(hw_spec->permanent_addr, priv->curr_addr, ETH_ALEN); + + return 0; +} + +/* + * This function handles the command response of get hardware + * specifications. + * + * Handling includes changing the header fields into CPU format + * and saving/updating the following parameters in driver - + * - Firmware capability information + * - Firmware band settings + * - Ad-hoc start band and channel + * - Ad-hoc 11n activation status + * - Firmware release number + * - Number of antennas + * - Hardware address + * - Hardware interface version + * - Firmware version + * - Region code + * - 11n capabilities + * - MCS support fields + * - MP end port + */ +int mwifiex_ret_get_hw_spec(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + struct host_cmd_ds_get_hw_spec *hw_spec = &resp->params.hw_spec; + struct mwifiex_adapter *adapter = priv->adapter; + int i; + + adapter->fw_cap_info = le32_to_cpu(hw_spec->fw_cap_info); + + if (IS_SUPPORT_MULTI_BANDS(adapter)) + adapter->fw_bands = (u8) GET_FW_DEFAULT_BANDS(adapter); + else + adapter->fw_bands = BAND_B; + + adapter->config_bands = adapter->fw_bands; + + if (adapter->fw_bands & BAND_A) { + if (adapter->fw_bands & BAND_GN) { + adapter->config_bands |= BAND_AN; + adapter->fw_bands |= BAND_AN; + } + if (adapter->fw_bands & BAND_AN) { + adapter->adhoc_start_band = BAND_A | BAND_AN; + adapter->adhoc_11n_enabled = true; + } else { + adapter->adhoc_start_band = BAND_A; + } + priv->adhoc_channel = DEFAULT_AD_HOC_CHANNEL_A; + } else if (adapter->fw_bands & BAND_GN) { + adapter->adhoc_start_band = BAND_G | BAND_B | BAND_GN; + priv->adhoc_channel = DEFAULT_AD_HOC_CHANNEL; + adapter->adhoc_11n_enabled = true; + } else if (adapter->fw_bands & BAND_G) { + adapter->adhoc_start_band = BAND_G | BAND_B; + priv->adhoc_channel = DEFAULT_AD_HOC_CHANNEL; + } else if (adapter->fw_bands & BAND_B) { + adapter->adhoc_start_band = BAND_B; + priv->adhoc_channel = DEFAULT_AD_HOC_CHANNEL; + } + + adapter->fw_release_number = le32_to_cpu(hw_spec->fw_release_number); + adapter->number_of_antenna = le16_to_cpu(hw_spec->number_of_antenna); + + if (le32_to_cpu(hw_spec->dot_11ac_dev_cap)) { + adapter->is_hw_11ac_capable = true; + + /* Copy 11AC cap */ + adapter->hw_dot_11ac_dev_cap = + le32_to_cpu(hw_spec->dot_11ac_dev_cap); + adapter->usr_dot_11ac_dev_cap_bg = adapter->hw_dot_11ac_dev_cap; + adapter->usr_dot_11ac_dev_cap_a = adapter->hw_dot_11ac_dev_cap; + + /* Copy 11AC mcs */ + adapter->hw_dot_11ac_mcs_support = + le32_to_cpu(hw_spec->dot_11ac_mcs_support); + adapter->usr_dot_11ac_mcs_support = + adapter->hw_dot_11ac_mcs_support; + } else { + adapter->is_hw_11ac_capable = false; + } + + dev_dbg(adapter->dev, "info: GET_HW_SPEC: fw_release_number- %#x\n", + adapter->fw_release_number); + dev_dbg(adapter->dev, "info: GET_HW_SPEC: permanent addr: %pM\n", + hw_spec->permanent_addr); + dev_dbg(adapter->dev, + "info: GET_HW_SPEC: hw_if_version=%#x version=%#x\n", + le16_to_cpu(hw_spec->hw_if_version), + le16_to_cpu(hw_spec->version)); + + if (priv->curr_addr[0] == 0xff) + memmove(priv->curr_addr, hw_spec->permanent_addr, ETH_ALEN); + + adapter->region_code = le16_to_cpu(hw_spec->region_code); + + for (i = 0; i < MWIFIEX_MAX_REGION_CODE; i++) + /* Use the region code to search for the index */ + if (adapter->region_code == region_code_index[i]) + break; + + /* If it's unidentified region code, use the default (USA) */ + if (i >= MWIFIEX_MAX_REGION_CODE) { + adapter->region_code = 0x10; + dev_dbg(adapter->dev, + "cmd: unknown region code, use default (USA)\n"); + } + + adapter->hw_dot_11n_dev_cap = le32_to_cpu(hw_spec->dot_11n_dev_cap); + adapter->hw_dev_mcs_support = hw_spec->dev_mcs_support; + + if (adapter->if_ops.update_mp_end_port) + adapter->if_ops.update_mp_end_port(adapter, + le16_to_cpu(hw_spec->mp_end_port)); + + return 0; +} diff --git a/drivers/net/wireless/mwifiex/debugfs.c b/drivers/net/wireless/mwifiex/debugfs.c new file mode 100644 index 0000000..7939970 --- /dev/null +++ b/drivers/net/wireless/mwifiex/debugfs.c @@ -0,0 +1,761 @@ +/* + * Marvell Wireless LAN device driver: debugfs + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include + +#include "main.h" +#include "11n.h" + + +static struct dentry *mwifiex_dfs_dir; + +static char *bss_modes[] = { + "UNSPECIFIED", + "ADHOC", + "STATION", + "AP", + "AP_VLAN", + "WDS", + "MONITOR", + "MESH_POINT", + "P2P_CLIENT", + "P2P_GO", + "P2P_DEVICE", +}; + +/* size/addr for mwifiex_debug_info */ +#define item_size(n) (FIELD_SIZEOF(struct mwifiex_debug_info, n)) +#define item_addr(n) (offsetof(struct mwifiex_debug_info, n)) + +/* size/addr for struct mwifiex_adapter */ +#define adapter_item_size(n) (FIELD_SIZEOF(struct mwifiex_adapter, n)) +#define adapter_item_addr(n) (offsetof(struct mwifiex_adapter, n)) + +struct mwifiex_debug_data { + char name[32]; /* variable/array name */ + u32 size; /* size of the variable/array */ + size_t addr; /* address of the variable/array */ + int num; /* number of variables in an array */ +}; + +static struct mwifiex_debug_data items[] = { + {"int_counter", item_size(int_counter), + item_addr(int_counter), 1}, + {"wmm_ac_vo", item_size(packets_out[WMM_AC_VO]), + item_addr(packets_out[WMM_AC_VO]), 1}, + {"wmm_ac_vi", item_size(packets_out[WMM_AC_VI]), + item_addr(packets_out[WMM_AC_VI]), 1}, + {"wmm_ac_be", item_size(packets_out[WMM_AC_BE]), + item_addr(packets_out[WMM_AC_BE]), 1}, + {"wmm_ac_bk", item_size(packets_out[WMM_AC_BK]), + item_addr(packets_out[WMM_AC_BK]), 1}, + {"tx_buf_size", item_size(tx_buf_size), + item_addr(tx_buf_size), 1}, + {"curr_tx_buf_size", item_size(curr_tx_buf_size), + item_addr(curr_tx_buf_size), 1}, + {"ps_mode", item_size(ps_mode), + item_addr(ps_mode), 1}, + {"ps_state", item_size(ps_state), + item_addr(ps_state), 1}, + {"is_deep_sleep", item_size(is_deep_sleep), + item_addr(is_deep_sleep), 1}, + {"wakeup_dev_req", item_size(pm_wakeup_card_req), + item_addr(pm_wakeup_card_req), 1}, + {"wakeup_tries", item_size(pm_wakeup_fw_try), + item_addr(pm_wakeup_fw_try), 1}, + {"hs_configured", item_size(is_hs_configured), + item_addr(is_hs_configured), 1}, + {"hs_activated", item_size(hs_activated), + item_addr(hs_activated), 1}, + {"num_tx_timeout", item_size(num_tx_timeout), + item_addr(num_tx_timeout), 1}, + {"num_cmd_timeout", item_size(num_cmd_timeout), + item_addr(num_cmd_timeout), 1}, + {"timeout_cmd_id", item_size(timeout_cmd_id), + item_addr(timeout_cmd_id), 1}, + {"timeout_cmd_act", item_size(timeout_cmd_act), + item_addr(timeout_cmd_act), 1}, + {"last_cmd_id", item_size(last_cmd_id), + item_addr(last_cmd_id), DBG_CMD_NUM}, + {"last_cmd_act", item_size(last_cmd_act), + item_addr(last_cmd_act), DBG_CMD_NUM}, + {"last_cmd_index", item_size(last_cmd_index), + item_addr(last_cmd_index), 1}, + {"last_cmd_resp_id", item_size(last_cmd_resp_id), + item_addr(last_cmd_resp_id), DBG_CMD_NUM}, + {"last_cmd_resp_index", item_size(last_cmd_resp_index), + item_addr(last_cmd_resp_index), 1}, + {"last_event", item_size(last_event), + item_addr(last_event), DBG_CMD_NUM}, + {"last_event_index", item_size(last_event_index), + item_addr(last_event_index), 1}, + {"num_cmd_h2c_fail", item_size(num_cmd_host_to_card_failure), + item_addr(num_cmd_host_to_card_failure), 1}, + {"num_cmd_sleep_cfm_fail", + item_size(num_cmd_sleep_cfm_host_to_card_failure), + item_addr(num_cmd_sleep_cfm_host_to_card_failure), 1}, + {"num_tx_h2c_fail", item_size(num_tx_host_to_card_failure), + item_addr(num_tx_host_to_card_failure), 1}, + {"num_evt_deauth", item_size(num_event_deauth), + item_addr(num_event_deauth), 1}, + {"num_evt_disassoc", item_size(num_event_disassoc), + item_addr(num_event_disassoc), 1}, + {"num_evt_link_lost", item_size(num_event_link_lost), + item_addr(num_event_link_lost), 1}, + {"num_cmd_deauth", item_size(num_cmd_deauth), + item_addr(num_cmd_deauth), 1}, + {"num_cmd_assoc_ok", item_size(num_cmd_assoc_success), + item_addr(num_cmd_assoc_success), 1}, + {"num_cmd_assoc_fail", item_size(num_cmd_assoc_failure), + item_addr(num_cmd_assoc_failure), 1}, + {"cmd_sent", item_size(cmd_sent), + item_addr(cmd_sent), 1}, + {"data_sent", item_size(data_sent), + item_addr(data_sent), 1}, + {"cmd_resp_received", item_size(cmd_resp_received), + item_addr(cmd_resp_received), 1}, + {"event_received", item_size(event_received), + item_addr(event_received), 1}, + + /* variables defined in struct mwifiex_adapter */ + {"cmd_pending", adapter_item_size(cmd_pending), + adapter_item_addr(cmd_pending), 1}, + {"tx_pending", adapter_item_size(tx_pending), + adapter_item_addr(tx_pending), 1}, + {"rx_pending", adapter_item_size(rx_pending), + adapter_item_addr(rx_pending), 1}, +}; + +static int num_of_items = ARRAY_SIZE(items); + +/* + * Proc info file read handler. + * + * This function is called when the 'info' file is opened for reading. + * It prints the following driver related information - + * - Driver name + * - Driver version + * - Driver extended version + * - Interface name + * - BSS mode + * - Media state (connected or disconnected) + * - MAC address + * - Total number of Tx bytes + * - Total number of Rx bytes + * - Total number of Tx packets + * - Total number of Rx packets + * - Total number of dropped Tx packets + * - Total number of dropped Rx packets + * - Total number of corrupted Tx packets + * - Total number of corrupted Rx packets + * - Carrier status (on or off) + * - Tx queue status (started or stopped) + * + * For STA mode drivers, it also prints the following extra - + * - ESSID + * - BSSID + * - Channel + * - Region code + * - Multicast count + * - Multicast addresses + */ +static ssize_t +mwifiex_info_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwifiex_private *priv = + (struct mwifiex_private *) file->private_data; + struct net_device *netdev = priv->netdev; + struct netdev_hw_addr *ha; + struct netdev_queue *txq; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *) page, fmt[64]; + struct mwifiex_bss_info info; + ssize_t ret; + int i = 0; + + if (!p) + return -ENOMEM; + + memset(&info, 0, sizeof(info)); + ret = mwifiex_get_bss_info(priv, &info); + if (ret) + goto free_and_exit; + + mwifiex_drv_get_driver_version(priv->adapter, fmt, sizeof(fmt) - 1); + + if (!priv->version_str[0]) + mwifiex_get_ver_ext(priv); + + p += sprintf(p, "driver_name = " "\"mwifiex\"\n"); + p += sprintf(p, "driver_version = %s", fmt); + p += sprintf(p, "\nverext = %s", priv->version_str); + p += sprintf(p, "\ninterface_name=\"%s\"\n", netdev->name); + + if (info.bss_mode >= ARRAY_SIZE(bss_modes)) + p += sprintf(p, "bss_mode=\"%d\"\n", info.bss_mode); + else + p += sprintf(p, "bss_mode=\"%s\"\n", bss_modes[info.bss_mode]); + + p += sprintf(p, "media_state=\"%s\"\n", + (!priv->media_connected ? "Disconnected" : "Connected")); + p += sprintf(p, "mac_address=\"%pM\"\n", netdev->dev_addr); + + if (GET_BSS_ROLE(priv) == MWIFIEX_BSS_ROLE_STA) { + p += sprintf(p, "multicast_count=\"%d\"\n", + netdev_mc_count(netdev)); + p += sprintf(p, "essid=\"%s\"\n", info.ssid.ssid); + p += sprintf(p, "bssid=\"%pM\"\n", info.bssid); + p += sprintf(p, "channel=\"%d\"\n", (int) info.bss_chan); + p += sprintf(p, "country_code = \"%s\"\n", info.country_code); + + netdev_for_each_mc_addr(ha, netdev) + p += sprintf(p, "multicast_address[%d]=\"%pM\"\n", +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + i++, ha->addr); +#else + i++, ha->dmi_addr); +#endif + } + + p += sprintf(p, "num_tx_bytes = %lu\n", priv->stats.tx_bytes); + p += sprintf(p, "num_rx_bytes = %lu\n", priv->stats.rx_bytes); + p += sprintf(p, "num_tx_pkts = %lu\n", priv->stats.tx_packets); + p += sprintf(p, "num_rx_pkts = %lu\n", priv->stats.rx_packets); + p += sprintf(p, "num_tx_pkts_dropped = %lu\n", priv->stats.tx_dropped); + p += sprintf(p, "num_rx_pkts_dropped = %lu\n", priv->stats.rx_dropped); + p += sprintf(p, "num_tx_pkts_err = %lu\n", priv->stats.tx_errors); + p += sprintf(p, "num_rx_pkts_err = %lu\n", priv->stats.rx_errors); + p += sprintf(p, "carrier %s\n", ((netif_carrier_ok(priv->netdev)) + ? "on" : "off")); + p += sprintf(p, "tx queue"); + for (i = 0; i < netdev->num_tx_queues; i++) { + txq = netdev_get_tx_queue(netdev, i); + p += sprintf(p, " %d:%s", i, netif_tx_queue_stopped(txq) ? + "stopped" : "started"); + } + p += sprintf(p, "\n"); + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *) page, + (unsigned long) p - page); + +free_and_exit: + free_page(page); + return ret; +} + +/* + * Proc getlog file read handler. + * + * This function is called when the 'getlog' file is opened for reading + * It prints the following log information - + * - Number of multicast Tx frames + * - Number of failed packets + * - Number of Tx retries + * - Number of multicast Tx retries + * - Number of duplicate frames + * - Number of RTS successes + * - Number of RTS failures + * - Number of ACK failures + * - Number of fragmented Rx frames + * - Number of multicast Rx frames + * - Number of FCS errors + * - Number of Tx frames + * - WEP ICV error counts + */ +static ssize_t +mwifiex_getlog_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwifiex_private *priv = + (struct mwifiex_private *) file->private_data; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *) page; + ssize_t ret; + struct mwifiex_ds_get_stats stats; + + if (!p) + return -ENOMEM; + + memset(&stats, 0, sizeof(stats)); + ret = mwifiex_get_stats_info(priv, &stats); + if (ret) + goto free_and_exit; + + p += sprintf(p, "\n" + "mcasttxframe %u\n" + "failed %u\n" + "retry %u\n" + "multiretry %u\n" + "framedup %u\n" + "rtssuccess %u\n" + "rtsfailure %u\n" + "ackfailure %u\n" + "rxfrag %u\n" + "mcastrxframe %u\n" + "fcserror %u\n" + "txframe %u\n" + "wepicverrcnt-1 %u\n" + "wepicverrcnt-2 %u\n" + "wepicverrcnt-3 %u\n" + "wepicverrcnt-4 %u\n", + stats.mcast_tx_frame, + stats.failed, + stats.retry, + stats.multi_retry, + stats.frame_dup, + stats.rts_success, + stats.rts_failure, + stats.ack_failure, + stats.rx_frag, + stats.mcast_rx_frame, + stats.fcs_error, + stats.tx_frame, + stats.wep_icv_error[0], + stats.wep_icv_error[1], + stats.wep_icv_error[2], + stats.wep_icv_error[3]); + + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *) page, + (unsigned long) p - page); + +free_and_exit: + free_page(page); + return ret; +} + +static struct mwifiex_debug_info info; + +/* + * Proc debug file read handler. + * + * This function is called when the 'debug' file is opened for reading + * It prints the following log information - + * - Interrupt count + * - WMM AC VO packets count + * - WMM AC VI packets count + * - WMM AC BE packets count + * - WMM AC BK packets count + * - Maximum Tx buffer size + * - Tx buffer size + * - Current Tx buffer size + * - Power Save mode + * - Power Save state + * - Deep Sleep status + * - Device wakeup required status + * - Number of wakeup tries + * - Host Sleep configured status + * - Host Sleep activated status + * - Number of Tx timeouts + * - Number of command timeouts + * - Last timed out command ID + * - Last timed out command action + * - Last command ID + * - Last command action + * - Last command index + * - Last command response ID + * - Last command response index + * - Last event + * - Last event index + * - Number of host to card command failures + * - Number of sleep confirm command failures + * - Number of host to card data failure + * - Number of deauthentication events + * - Number of disassociation events + * - Number of link lost events + * - Number of deauthentication commands + * - Number of association success commands + * - Number of association failure commands + * - Number of commands sent + * - Number of data packets sent + * - Number of command responses received + * - Number of events received + * - Tx BA stream table (TID, RA) + * - Rx reorder table (TID, TA, Start window, Window size, Buffer) + */ +static ssize_t +mwifiex_debug_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwifiex_private *priv = + (struct mwifiex_private *) file->private_data; + struct mwifiex_debug_data *d = &items[0]; + unsigned long page = get_zeroed_page(GFP_KERNEL); + char *p = (char *) page; + ssize_t ret; + size_t size, addr; + long val; + int i, j; + + if (!p) + return -ENOMEM; + + ret = mwifiex_get_debug_info(priv, &info); + if (ret) + goto free_and_exit; + + for (i = 0; i < num_of_items; i++) { + p += sprintf(p, "%s=", d[i].name); + + size = d[i].size / d[i].num; + + if (i < (num_of_items - 3)) + addr = d[i].addr + (size_t) &info; + else /* The last 3 items are struct mwifiex_adapter variables */ + addr = d[i].addr + (size_t) priv->adapter; + + for (j = 0; j < d[i].num; j++) { + switch (size) { + case 1: + val = *((u8 *) addr); + break; + case 2: + val = *((u16 *) addr); + break; + case 4: + val = *((u32 *) addr); + break; + case 8: + val = *((long long *) addr); + break; + default: + val = -1; + break; + } + + p += sprintf(p, "%#lx ", val); + addr += size; + } + + p += sprintf(p, "\n"); + } + + if (info.tx_tbl_num) { + p += sprintf(p, "Tx BA stream table:\n"); + for (i = 0; i < info.tx_tbl_num; i++) + p += sprintf(p, "tid = %d, ra = %pM\n", + info.tx_tbl[i].tid, info.tx_tbl[i].ra); + } + + if (info.rx_tbl_num) { + p += sprintf(p, "Rx reorder table:\n"); + for (i = 0; i < info.rx_tbl_num; i++) { + p += sprintf(p, "tid = %d, ta = %pM, " + "start_win = %d, " + "win_size = %d, buffer: ", + info.rx_tbl[i].tid, + info.rx_tbl[i].ta, + info.rx_tbl[i].start_win, + info.rx_tbl[i].win_size); + + for (j = 0; j < info.rx_tbl[i].win_size; j++) + p += sprintf(p, "%c ", + info.rx_tbl[i].buffer[j] ? + '1' : '0'); + + p += sprintf(p, "\n"); + } + } + + ret = simple_read_from_buffer(ubuf, count, ppos, (char *) page, + (unsigned long) p - page); + +free_and_exit: + free_page(page); + return ret; +} + +static u32 saved_reg_type, saved_reg_offset, saved_reg_value; + +/* + * Proc regrdwr file write handler. + * + * This function is called when the 'regrdwr' file is opened for writing + * + * This function can be used to write to a register. + */ +static ssize_t +mwifiex_regrdwr_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *) addr; + size_t buf_size = min(count, (size_t) (PAGE_SIZE - 1)); + int ret; + u32 reg_type = 0, reg_offset = 0, reg_value = UINT_MAX; + + if (!buf) + return -ENOMEM; + + + if (copy_from_user(buf, ubuf, buf_size)) { + ret = -EFAULT; + goto done; + } + + sscanf(buf, "%u %x %x", ®_type, ®_offset, ®_value); + + if (reg_type == 0 || reg_offset == 0) { + ret = -EINVAL; + goto done; + } else { + saved_reg_type = reg_type; + saved_reg_offset = reg_offset; + saved_reg_value = reg_value; + ret = count; + } +done: + free_page(addr); + return ret; +} + +/* + * Proc regrdwr file read handler. + * + * This function is called when the 'regrdwr' file is opened for reading + * + * This function can be used to read from a register. + */ +static ssize_t +mwifiex_regrdwr_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwifiex_private *priv = + (struct mwifiex_private *) file->private_data; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *) addr; + int pos = 0, ret = 0; + u32 reg_value; + + if (!buf) + return -ENOMEM; + + if (!saved_reg_type) { + /* No command has been given */ + pos += snprintf(buf, PAGE_SIZE, "0"); + goto done; + } + /* Set command has been given */ + if (saved_reg_value != UINT_MAX) { + ret = mwifiex_reg_write(priv, saved_reg_type, saved_reg_offset, + saved_reg_value); + + pos += snprintf(buf, PAGE_SIZE, "%u 0x%x 0x%x\n", + saved_reg_type, saved_reg_offset, + saved_reg_value); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + + goto done; + } + /* Get command has been given */ + ret = mwifiex_reg_read(priv, saved_reg_type, + saved_reg_offset, ®_value); + if (ret) { + ret = -EINVAL; + goto done; + } + + pos += snprintf(buf, PAGE_SIZE, "%u 0x%x 0x%x\n", saved_reg_type, + saved_reg_offset, reg_value); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + +done: + free_page(addr); + return ret; +} + +static u32 saved_offset = -1, saved_bytes = -1; + +/* + * Proc rdeeprom file write handler. + * + * This function is called when the 'rdeeprom' file is opened for writing + * + * This function can be used to write to a RDEEPROM location. + */ +static ssize_t +mwifiex_rdeeprom_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *) addr; + size_t buf_size = min(count, (size_t) (PAGE_SIZE - 1)); + int ret = 0; + int offset = -1, bytes = -1; + + if (!buf) + return -ENOMEM; + + + if (copy_from_user(buf, ubuf, buf_size)) { + ret = -EFAULT; + goto done; + } + + sscanf(buf, "%d %d", &offset, &bytes); + + if (offset == -1 || bytes == -1) { + ret = -EINVAL; + goto done; + } else { + saved_offset = offset; + saved_bytes = bytes; + ret = count; + } +done: + free_page(addr); + return ret; +} + +/* + * Proc rdeeprom read write handler. + * + * This function is called when the 'rdeeprom' file is opened for reading + * + * This function can be used to read from a RDEEPROM location. + */ +static ssize_t +mwifiex_rdeeprom_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct mwifiex_private *priv = + (struct mwifiex_private *) file->private_data; + unsigned long addr = get_zeroed_page(GFP_KERNEL); + char *buf = (char *) addr; + int pos = 0, ret = 0, i; + u8 value[MAX_EEPROM_DATA]; + + if (!buf) + return -ENOMEM; + + if (saved_offset == -1) { + /* No command has been given */ + pos += snprintf(buf, PAGE_SIZE, "0"); + goto done; + } + + /* Get command has been given */ + ret = mwifiex_eeprom_read(priv, (u16) saved_offset, + (u16) saved_bytes, value); + if (ret) { + ret = -EINVAL; + goto done; + } + + pos += snprintf(buf, PAGE_SIZE, "%d %d ", saved_offset, saved_bytes); + + for (i = 0; i < saved_bytes; i++) + pos += snprintf(buf + strlen(buf), PAGE_SIZE, "%d ", value[i]); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos); + +done: + free_page(addr); + return ret; +} + + +#define MWIFIEX_DFS_ADD_FILE(name) do { \ + if (!debugfs_create_file(#name, 0644, priv->dfs_dev_dir, \ + priv, &mwifiex_dfs_##name##_fops)) \ + return; \ +} while (0); + +#define MWIFIEX_DFS_FILE_OPS(name) \ +static const struct file_operations mwifiex_dfs_##name##_fops = { \ + .read = mwifiex_##name##_read, \ + .write = mwifiex_##name##_write, \ + .open = simple_open, \ +}; + +#define MWIFIEX_DFS_FILE_READ_OPS(name) \ +static const struct file_operations mwifiex_dfs_##name##_fops = { \ + .read = mwifiex_##name##_read, \ + .open = simple_open, \ +}; + +#define MWIFIEX_DFS_FILE_WRITE_OPS(name) \ +static const struct file_operations mwifiex_dfs_##name##_fops = { \ + .write = mwifiex_##name##_write, \ + .open = simple_open, \ +}; + + +MWIFIEX_DFS_FILE_READ_OPS(info); +MWIFIEX_DFS_FILE_READ_OPS(debug); +MWIFIEX_DFS_FILE_READ_OPS(getlog); +MWIFIEX_DFS_FILE_OPS(regrdwr); +MWIFIEX_DFS_FILE_OPS(rdeeprom); + +/* + * This function creates the debug FS directory structure and the files. + */ +void +mwifiex_dev_debugfs_init(struct mwifiex_private *priv) +{ + if (!mwifiex_dfs_dir || !priv) + return; + + priv->dfs_dev_dir = debugfs_create_dir(priv->netdev->name, + mwifiex_dfs_dir); + + if (!priv->dfs_dev_dir) + return; + + MWIFIEX_DFS_ADD_FILE(info); + MWIFIEX_DFS_ADD_FILE(debug); + MWIFIEX_DFS_ADD_FILE(getlog); + MWIFIEX_DFS_ADD_FILE(regrdwr); + MWIFIEX_DFS_ADD_FILE(rdeeprom); +} + +/* + * This function removes the debug FS directory structure and the files. + */ +void +mwifiex_dev_debugfs_remove(struct mwifiex_private *priv) +{ + if (!priv) + return; + + debugfs_remove_recursive(priv->dfs_dev_dir); +} + +/* + * This function creates the top level proc directory. + */ +void +mwifiex_debugfs_init(void) +{ + if (!mwifiex_dfs_dir) + mwifiex_dfs_dir = debugfs_create_dir("mwifiex", NULL); +} + +/* + * This function removes the top level proc directory. + */ +void +mwifiex_debugfs_remove(void) +{ + if (mwifiex_dfs_dir) + debugfs_remove(mwifiex_dfs_dir); +} diff --git a/drivers/net/wireless/mwifiex/decl.h b/drivers/net/wireless/mwifiex/decl.h new file mode 100644 index 0000000..94cc09d --- /dev/null +++ b/drivers/net/wireless/mwifiex/decl.h @@ -0,0 +1,154 @@ +/* + * Marvell Wireless LAN device driver: generic data structures and APIs + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _MWIFIEX_DECL_H_ +#define _MWIFIEX_DECL_H_ + +#undef pr_fmt +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include + + +#define MWIFIEX_MAX_BSS_NUM (3) + +#define MWIFIEX_MIN_DATA_HEADER_LEN 36 /* sizeof(mwifiex_txpd) + * + 4 byte alignment + */ +#define MWIFIEX_MGMT_FRAME_HEADER_SIZE 8 /* sizeof(pkt_type) + * + sizeof(tx_control) + */ + +#define MWIFIEX_MAX_TX_BASTREAM_SUPPORTED 2 +#define MWIFIEX_MAX_RX_BASTREAM_SUPPORTED 16 + +#define MWIFIEX_STA_AMPDU_DEF_TXWINSIZE 16 +#define MWIFIEX_STA_AMPDU_DEF_RXWINSIZE 32 +#define MWIFIEX_UAP_AMPDU_DEF_TXWINSIZE 32 +#define MWIFIEX_UAP_AMPDU_DEF_RXWINSIZE 16 +#define MWIFIEX_11AC_STA_AMPDU_DEF_TXWINSIZE 32 +#define MWIFIEX_11AC_STA_AMPDU_DEF_RXWINSIZE 48 +#define MWIFIEX_11AC_UAP_AMPDU_DEF_TXWINSIZE 48 +#define MWIFIEX_11AC_UAP_AMPDU_DEF_RXWINSIZE 32 + +#define MWIFIEX_DEFAULT_BLOCK_ACK_TIMEOUT 0xffff + +#define MWIFIEX_RATE_BITMAP_MCS0 32 + +#define MWIFIEX_RX_DATA_BUF_SIZE (4 * 1024) +#define MWIFIEX_RX_CMD_BUF_SIZE (2 * 1024) + +#define MAX_BEACON_PERIOD (4000) +#define MIN_BEACON_PERIOD (50) +#define MAX_DTIM_PERIOD (100) +#define MIN_DTIM_PERIOD (1) + +#define MWIFIEX_RTS_MIN_VALUE (0) +#define MWIFIEX_RTS_MAX_VALUE (2347) +#define MWIFIEX_FRAG_MIN_VALUE (256) +#define MWIFIEX_FRAG_MAX_VALUE (2346) +#define MWIFIEX_WMM_VERSION 0x01 +#define MWIFIEX_WMM_SUBTYPE 0x01 + +#define MWIFIEX_RETRY_LIMIT 14 +#define MWIFIEX_SDIO_BLOCK_SIZE 256 + +#define MWIFIEX_BUF_FLAG_REQUEUED_PKT BIT(0) +#define MWIFIEX_BUF_FLAG_BRIDGED_PKT BIT(1) + +#define MWIFIEX_BRIDGED_PKTS_THRESHOLD 1024 + +enum mwifiex_bss_type { + MWIFIEX_BSS_TYPE_STA = 0, + MWIFIEX_BSS_TYPE_UAP = 1, + MWIFIEX_BSS_TYPE_P2P = 2, + MWIFIEX_BSS_TYPE_ANY = 0xff, +}; + +enum mwifiex_bss_role { + MWIFIEX_BSS_ROLE_STA = 0, + MWIFIEX_BSS_ROLE_UAP = 1, + MWIFIEX_BSS_ROLE_ANY = 0xff, +}; + +#define BSS_ROLE_BIT_MASK BIT(0) + +#define GET_BSS_ROLE(priv) ((priv)->bss_role & BSS_ROLE_BIT_MASK) + +enum mwifiex_data_frame_type { + MWIFIEX_DATA_FRAME_TYPE_ETH_II = 0, + MWIFIEX_DATA_FRAME_TYPE_802_11, +}; + +struct mwifiex_fw_image { + u8 *helper_buf; + u32 helper_len; + u8 *fw_buf; + u32 fw_len; +}; + +struct mwifiex_802_11_ssid { + u32 ssid_len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; +}; + +struct mwifiex_wait_queue { + wait_queue_head_t wait; + int status; +}; + +struct mwifiex_rxinfo { + u8 bss_num; + u8 bss_type; + struct sk_buff *parent; + u8 use_count; +}; + +struct mwifiex_txinfo { + u32 status_code; + u8 flags; + u8 bss_num; + u8 bss_type; +}; + +enum mwifiex_wmm_ac_e { + WMM_AC_BK, + WMM_AC_BE, + WMM_AC_VI, + WMM_AC_VO +} __packed; + +struct ieee_types_wmm_ac_parameters { + u8 aci_aifsn_bitmap; + u8 ecw_bitmap; + __le16 tx_op_limit; +} __packed; + +struct mwifiex_types_wmm_info { + u8 oui[4]; + u8 subtype; + u8 version; + u8 qos_info; + u8 reserved; + struct ieee_types_wmm_ac_parameters ac_params[IEEE80211_NUM_ACS]; +} __packed; +#endif /* !_MWIFIEX_DECL_H_ */ diff --git a/drivers/net/wireless/mwifiex/ethtool.c b/drivers/net/wireless/mwifiex/ethtool.c new file mode 100644 index 0000000..bfb3990 --- /dev/null +++ b/drivers/net/wireless/mwifiex/ethtool.c @@ -0,0 +1,70 @@ +/* + * Marvell Wireless LAN device driver: ethtool + * + * Copyright (C) 2013, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "main.h" + +static void mwifiex_ethtool_get_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + u32 conditions = le32_to_cpu(priv->adapter->hs_cfg.conditions); + + wol->supported = WAKE_UCAST|WAKE_MCAST|WAKE_BCAST|WAKE_PHY; + + if (conditions == HS_CFG_COND_DEF) + return; + + if (conditions & HS_CFG_COND_UNICAST_DATA) + wol->wolopts |= WAKE_UCAST; + if (conditions & HS_CFG_COND_MULTICAST_DATA) + wol->wolopts |= WAKE_MCAST; + if (conditions & HS_CFG_COND_BROADCAST_DATA) + wol->wolopts |= WAKE_BCAST; + if (conditions & HS_CFG_COND_MAC_EVENT) + wol->wolopts |= WAKE_PHY; +} + +static int mwifiex_ethtool_set_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + u32 conditions = 0; + + if (wol->wolopts & ~(WAKE_UCAST|WAKE_MCAST|WAKE_BCAST|WAKE_PHY)) + return -EOPNOTSUPP; + + if (wol->wolopts & WAKE_UCAST) + conditions |= HS_CFG_COND_UNICAST_DATA; + if (wol->wolopts & WAKE_MCAST) + conditions |= HS_CFG_COND_MULTICAST_DATA; + if (wol->wolopts & WAKE_BCAST) + conditions |= HS_CFG_COND_BROADCAST_DATA; + if (wol->wolopts & WAKE_PHY) + conditions |= HS_CFG_COND_MAC_EVENT; + if (wol->wolopts == 0) + conditions |= HS_CFG_COND_DEF; + priv->adapter->hs_cfg.conditions = cpu_to_le32(conditions); + + return 0; +} + +const struct ethtool_ops mwifiex_ethtool_ops = { + .get_wol = mwifiex_ethtool_get_wol, + .set_wol = mwifiex_ethtool_set_wol, +}; diff --git a/drivers/net/wireless/mwifiex/fw.h b/drivers/net/wireless/mwifiex/fw.h new file mode 100644 index 0000000..1f7578d --- /dev/null +++ b/drivers/net/wireless/mwifiex/fw.h @@ -0,0 +1,1645 @@ +/* + * Marvell Wireless LAN device driver: Firmware specific macros & structures + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _MWIFIEX_FW_H_ +#define _MWIFIEX_FW_H_ + +#include + + +#define INTF_HEADER_LEN 4 + +struct rfc_1042_hdr { + u8 llc_dsap; + u8 llc_ssap; + u8 llc_ctrl; + u8 snap_oui[3]; + u16 snap_type; +}; + +struct rx_packet_hdr { + struct ethhdr eth803_hdr; + struct rfc_1042_hdr rfc1042_hdr; +}; + +struct tx_packet_hdr { + struct ethhdr eth803_hdr; + struct rfc_1042_hdr rfc1042_hdr; +}; + +#define B_SUPPORTED_RATES 5 +#define G_SUPPORTED_RATES 9 +#define BG_SUPPORTED_RATES 13 +#define A_SUPPORTED_RATES 9 +#define HOSTCMD_SUPPORTED_RATES 14 +#define N_SUPPORTED_RATES 3 +#define ALL_802_11_BANDS (BAND_A | BAND_B | BAND_G | BAND_GN | \ + BAND_AN | BAND_GAC | BAND_AAC) + +#define FW_MULTI_BANDS_SUPPORT (BIT(8) | BIT(9) | BIT(10) | BIT(11) | \ + BIT(12) | BIT(13)) +#define IS_SUPPORT_MULTI_BANDS(adapter) \ + (adapter->fw_cap_info & FW_MULTI_BANDS_SUPPORT) + +/* shift bit 12 and bit 13 in fw_cap_info from the firmware to bit 13 and 14 + * for 11ac so that bit 11 is for GN, bit 12 for AN, bit 13 for GAC, and bit + * bit 14 for AAC, in order to be compatible with the band capability + * defined in the driver after right shift of 8 bits. + */ +#define GET_FW_DEFAULT_BANDS(adapter) \ + (((((adapter->fw_cap_info & 0x3000) << 1) | \ + (adapter->fw_cap_info & ~0xF000)) >> 8) & \ + ALL_802_11_BANDS) + +#define HostCmd_WEP_KEY_INDEX_MASK 0x3fff + +#define KEY_INFO_ENABLED 0x01 +enum KEY_TYPE_ID { + KEY_TYPE_ID_WEP = 0, + KEY_TYPE_ID_TKIP, + KEY_TYPE_ID_AES, + KEY_TYPE_ID_WAPI, + KEY_TYPE_ID_AES_CMAC, +}; +#define KEY_MCAST BIT(0) +#define KEY_UNICAST BIT(1) +#define KEY_ENABLED BIT(2) +#define KEY_IGTK BIT(10) + +#define WAPI_KEY_LEN 50 + +#define MAX_POLL_TRIES 100 + +#define MAX_MULTI_INTERFACE_POLL_TRIES 1000 + +#define MAX_FIRMWARE_POLL_TRIES 100 + +#define FIRMWARE_READY_SDIO 0xfedc +#define FIRMWARE_READY_PCIE 0xfedcba00 + +enum mwifiex_usb_ep { + MWIFIEX_USB_EP_CMD_EVENT = 1, + MWIFIEX_USB_EP_DATA = 2, +}; + +enum MWIFIEX_802_11_PRIVACY_FILTER { + MWIFIEX_802_11_PRIV_FILTER_ACCEPT_ALL, + MWIFIEX_802_11_PRIV_FILTER_8021X_WEP +}; + +#define CAL_SNR(RSSI, NF) ((s16)((s16)(RSSI)-(s16)(NF))) +#define CAL_RSSI(SNR, NF) ((s16)((s16)(SNR)+(s16)(NF))) + +#define UAP_BSS_PARAMS_I 0 +#define UAP_CUSTOM_IE_I 1 +#define MWIFIEX_AUTO_IDX_MASK 0xffff +#define MWIFIEX_DELETE_MASK 0x0000 +#define MGMT_MASK_ASSOC_REQ 0x01 +#define MGMT_MASK_REASSOC_REQ 0x04 +#define MGMT_MASK_ASSOC_RESP 0x02 +#define MGMT_MASK_REASSOC_RESP 0x08 +#define MGMT_MASK_PROBE_REQ 0x10 +#define MGMT_MASK_PROBE_RESP 0x20 +#define MGMT_MASK_BEACON 0x100 + +#define TLV_TYPE_UAP_SSID 0x0000 +#define TLV_TYPE_UAP_RATES 0x0001 + +#define PROPRIETARY_TLV_BASE_ID 0x0100 +#define TLV_TYPE_KEY_MATERIAL (PROPRIETARY_TLV_BASE_ID + 0) +#define TLV_TYPE_CHANLIST (PROPRIETARY_TLV_BASE_ID + 1) +#define TLV_TYPE_NUMPROBES (PROPRIETARY_TLV_BASE_ID + 2) +#define TLV_TYPE_RSSI_LOW (PROPRIETARY_TLV_BASE_ID + 4) +#define TLV_TYPE_PASSTHROUGH (PROPRIETARY_TLV_BASE_ID + 10) +#define TLV_TYPE_WMMQSTATUS (PROPRIETARY_TLV_BASE_ID + 16) +#define TLV_TYPE_WILDCARDSSID (PROPRIETARY_TLV_BASE_ID + 18) +#define TLV_TYPE_TSFTIMESTAMP (PROPRIETARY_TLV_BASE_ID + 19) +#define TLV_TYPE_RSSI_HIGH (PROPRIETARY_TLV_BASE_ID + 22) +#define TLV_TYPE_AUTH_TYPE (PROPRIETARY_TLV_BASE_ID + 31) +#define TLV_TYPE_STA_MAC_ADDR (PROPRIETARY_TLV_BASE_ID + 32) +#define TLV_TYPE_CHANNELBANDLIST (PROPRIETARY_TLV_BASE_ID + 42) +#define TLV_TYPE_UAP_BEACON_PERIOD (PROPRIETARY_TLV_BASE_ID + 44) +#define TLV_TYPE_UAP_DTIM_PERIOD (PROPRIETARY_TLV_BASE_ID + 45) +#define TLV_TYPE_UAP_BCAST_SSID (PROPRIETARY_TLV_BASE_ID + 48) +#define TLV_TYPE_UAP_RTS_THRESHOLD (PROPRIETARY_TLV_BASE_ID + 51) +#define TLV_TYPE_UAP_AO_TIMER (PROPRIETARY_TLV_BASE_ID + 57) +#define TLV_TYPE_UAP_WEP_KEY (PROPRIETARY_TLV_BASE_ID + 59) +#define TLV_TYPE_UAP_WPA_PASSPHRASE (PROPRIETARY_TLV_BASE_ID + 60) +#define TLV_TYPE_UAP_ENCRY_PROTOCOL (PROPRIETARY_TLV_BASE_ID + 64) +#define TLV_TYPE_UAP_AKMP (PROPRIETARY_TLV_BASE_ID + 65) +#define TLV_TYPE_UAP_FRAG_THRESHOLD (PROPRIETARY_TLV_BASE_ID + 70) +#define TLV_TYPE_RATE_DROP_CONTROL (PROPRIETARY_TLV_BASE_ID + 82) +#define TLV_TYPE_RATE_SCOPE (PROPRIETARY_TLV_BASE_ID + 83) +#define TLV_TYPE_POWER_GROUP (PROPRIETARY_TLV_BASE_ID + 84) +#define TLV_TYPE_UAP_RETRY_LIMIT (PROPRIETARY_TLV_BASE_ID + 93) +#define TLV_TYPE_WAPI_IE (PROPRIETARY_TLV_BASE_ID + 94) +#define TLV_TYPE_UAP_MGMT_FRAME (PROPRIETARY_TLV_BASE_ID + 104) +#define TLV_TYPE_MGMT_IE (PROPRIETARY_TLV_BASE_ID + 105) +#define TLV_TYPE_AUTO_DS_PARAM (PROPRIETARY_TLV_BASE_ID + 113) +#define TLV_TYPE_PS_PARAM (PROPRIETARY_TLV_BASE_ID + 114) +#define TLV_TYPE_UAP_PS_AO_TIMER (PROPRIETARY_TLV_BASE_ID + 123) +#define TLV_TYPE_PWK_CIPHER (PROPRIETARY_TLV_BASE_ID + 145) +#define TLV_TYPE_GWK_CIPHER (PROPRIETARY_TLV_BASE_ID + 146) + +#define MWIFIEX_TX_DATA_BUF_SIZE_2K 2048 + +#define SSN_MASK 0xfff0 + +#define BA_RESULT_SUCCESS 0x0 +#define BA_RESULT_TIMEOUT 0x2 + +#define IS_BASTREAM_SETUP(ptr) (ptr->ba_status) + +#define BA_STREAM_NOT_ALLOWED 0xff + +#define IS_11N_ENABLED(priv) ((priv->adapter->config_bands & BAND_GN || \ + priv->adapter->config_bands & BAND_AN) && \ + priv->curr_bss_params.bss_descriptor.bcn_ht_cap) +#define INITIATOR_BIT(DelBAParamSet) (((DelBAParamSet) &\ + BIT(DELBA_INITIATOR_POS)) >> DELBA_INITIATOR_POS) + +#define MWIFIEX_TX_DATA_BUF_SIZE_4K 4096 +#define MWIFIEX_TX_DATA_BUF_SIZE_8K 8192 + +#define ISSUPP_11NENABLED(FwCapInfo) (FwCapInfo & BIT(11)) + +#define MWIFIEX_DEF_HT_CAP (IEEE80211_HT_CAP_DSSSCCK40 | \ + (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT) | \ + IEEE80211_HT_CAP_SM_PS) + +#define MWIFIEX_DEF_AMPDU IEEE80211_HT_AMPDU_PARM_FACTOR + +/* dev_cap bitmap + * BIT + * 0-16 reserved + * 17 IEEE80211_HT_CAP_SUP_WIDTH_20_40 + * 18-22 reserved + * 23 IEEE80211_HT_CAP_SGI_20 + * 24 IEEE80211_HT_CAP_SGI_40 + * 25 IEEE80211_HT_CAP_TX_STBC + * 26 IEEE80211_HT_CAP_RX_STBC + * 27-28 reserved + * 29 IEEE80211_HT_CAP_GRN_FLD + * 30-31 reserved + */ +#define ISSUPP_CHANWIDTH40(Dot11nDevCap) (Dot11nDevCap & BIT(17)) +#define ISSUPP_SHORTGI20(Dot11nDevCap) (Dot11nDevCap & BIT(23)) +#define ISSUPP_SHORTGI40(Dot11nDevCap) (Dot11nDevCap & BIT(24)) +#define ISSUPP_TXSTBC(Dot11nDevCap) (Dot11nDevCap & BIT(25)) +#define ISSUPP_RXSTBC(Dot11nDevCap) (Dot11nDevCap & BIT(26)) +#define ISSUPP_GREENFIELD(Dot11nDevCap) (Dot11nDevCap & BIT(29)) +#define ISENABLED_40MHZ_INTOLERANT(Dot11nDevCap) (Dot11nDevCap & BIT(8)) +#define ISSUPP_RXLDPC(Dot11nDevCap) (Dot11nDevCap & BIT(22)) + +/* httxcfg bitmap + * 0 reserved + * 1 20/40 Mhz enable(1)/disable(0) + * 2-3 reserved + * 4 green field enable(1)/disable(0) + * 5 short GI in 20 Mhz enable(1)/disable(0) + * 6 short GI in 40 Mhz enable(1)/disable(0) + * 7-15 reserved + */ +#define MWIFIEX_FW_DEF_HTTXCFG (BIT(1) | BIT(4) | BIT(5) | BIT(6)) + +#define GET_RXMCSSUPP(DevMCSSupported) (DevMCSSupported & 0x0f) +#define SETHT_MCS32(x) (x[4] |= 1) +#define HT_STREAM_2X2 0x22 + +#define SET_SECONDARYCHAN(RadioType, SECCHAN) (RadioType |= (SECCHAN << 4)) + +#define LLC_SNAP_LEN 8 + +/* HW_SPEC fw_cap_info */ + +#define ISSUPP_11ACENABLED(fw_cap_info) (fw_cap_info & (BIT(13)|BIT(14))) + +#define GET_VHTCAP_CHWDSET(vht_cap_info) ((vht_cap_info >> 2) & 0x3) +#define GET_VHTNSSMCS(mcs_mapset, nss) ((mcs_mapset >> (2 * (nss - 1))) & 0x3) +#define SET_VHTNSSMCS(mcs_mapset, nss, value) (mcs_mapset |= (value & 0x3) << \ + (2 * (nss - 1))) +#define NO_NSS_SUPPORT 0x3 + +#define GET_DEVTXMCSMAP(dev_mcs_map) (dev_mcs_map >> 16) +#define GET_DEVRXMCSMAP(dev_mcs_map) (dev_mcs_map & 0xFFFF) + +#define MOD_CLASS_HR_DSSS 0x03 +#define MOD_CLASS_OFDM 0x07 +#define MOD_CLASS_HT 0x08 +#define HT_BW_20 0 +#define HT_BW_40 1 + +#define HostCmd_CMD_GET_HW_SPEC 0x0003 +#define HostCmd_CMD_802_11_SCAN 0x0006 +#define HostCmd_CMD_802_11_GET_LOG 0x000b +#define HostCmd_CMD_MAC_MULTICAST_ADR 0x0010 +#define HostCmd_CMD_802_11_EEPROM_ACCESS 0x0059 +#define HostCmd_CMD_802_11_ASSOCIATE 0x0012 +#define HostCmd_CMD_802_11_SNMP_MIB 0x0016 +#define HostCmd_CMD_MAC_REG_ACCESS 0x0019 +#define HostCmd_CMD_BBP_REG_ACCESS 0x001a +#define HostCmd_CMD_RF_REG_ACCESS 0x001b +#define HostCmd_CMD_PMIC_REG_ACCESS 0x00ad +#define HostCmd_CMD_RF_TX_PWR 0x001e +#define HostCmd_CMD_RF_ANTENNA 0x0020 +#define HostCmd_CMD_802_11_DEAUTHENTICATE 0x0024 +#define HostCmd_CMD_MAC_CONTROL 0x0028 +#define HostCmd_CMD_802_11_AD_HOC_START 0x002b +#define HostCmd_CMD_802_11_AD_HOC_JOIN 0x002c +#define HostCmd_CMD_802_11_AD_HOC_STOP 0x0040 +#define HostCmd_CMD_802_11_MAC_ADDRESS 0x004D +#define HostCmd_CMD_802_11D_DOMAIN_INFO 0x005b +#define HostCmd_CMD_802_11_KEY_MATERIAL 0x005e +#define HostCmd_CMD_802_11_BG_SCAN_QUERY 0x006c +#define HostCmd_CMD_WMM_GET_STATUS 0x0071 +#define HostCmd_CMD_802_11_SUBSCRIBE_EVENT 0x0075 +#define HostCmd_CMD_802_11_TX_RATE_QUERY 0x007f +#define HostCmd_CMD_802_11_IBSS_COALESCING_STATUS 0x0083 +#define HostCmd_CMD_VERSION_EXT 0x0097 +#define HostCmd_CMD_MEF_CFG 0x009a +#define HostCmd_CMD_RSSI_INFO 0x00a4 +#define HostCmd_CMD_FUNC_INIT 0x00a9 +#define HostCmd_CMD_FUNC_SHUTDOWN 0x00aa +#define HostCmd_CMD_UAP_SYS_CONFIG 0x00b0 +#define HostCmd_CMD_UAP_BSS_START 0x00b1 +#define HostCmd_CMD_UAP_BSS_STOP 0x00b2 +#define HostCmd_CMD_11N_CFG 0x00cd +#define HostCmd_CMD_11N_ADDBA_REQ 0x00ce +#define HostCmd_CMD_11N_ADDBA_RSP 0x00cf +#define HostCmd_CMD_11N_DELBA 0x00d0 +#define HostCmd_CMD_RECONFIGURE_TX_BUFF 0x00d9 +#define HostCmd_CMD_AMSDU_AGGR_CTRL 0x00df +#define HostCmd_CMD_TXPWR_CFG 0x00d1 +#define HostCmd_CMD_TX_RATE_CFG 0x00d6 +#define HostCmd_CMD_802_11_PS_MODE_ENH 0x00e4 +#define HostCmd_CMD_802_11_HS_CFG_ENH 0x00e5 +#define HostCmd_CMD_P2P_MODE_CFG 0x00eb +#define HostCmd_CMD_CAU_REG_ACCESS 0x00ed +#define HostCmd_CMD_SET_BSS_MODE 0x00f7 +#define HostCmd_CMD_PCIE_DESC_DETAILS 0x00fa +#define HostCmd_CMD_MGMT_FRAME_REG 0x010c +#define HostCmd_CMD_REMAIN_ON_CHAN 0x010d +#define HostCmd_CMD_11AC_CFG 0x0112 + +#define PROTOCOL_NO_SECURITY 0x01 +#define PROTOCOL_STATIC_WEP 0x02 +#define PROTOCOL_WPA 0x08 +#define PROTOCOL_WPA2 0x20 +#define PROTOCOL_WPA2_MIXED 0x28 +#define PROTOCOL_EAP 0x40 +#define KEY_MGMT_NONE 0x04 +#define KEY_MGMT_PSK 0x02 +#define KEY_MGMT_EAP 0x01 +#define CIPHER_TKIP 0x04 +#define CIPHER_AES_CCMP 0x08 +#define VALID_CIPHER_BITMAP 0x0c + +enum ENH_PS_MODES { + EN_PS = 1, + DIS_PS = 2, + EN_AUTO_DS = 3, + DIS_AUTO_DS = 4, + SLEEP_CONFIRM = 5, + GET_PS = 0, + EN_AUTO_PS = 0xff, + DIS_AUTO_PS = 0xfe, +}; + +enum P2P_MODES { + P2P_MODE_DISABLE = 0, + P2P_MODE_DEVICE = 1, + P2P_MODE_GO = 2, + P2P_MODE_CLIENT = 3, +}; + +#define HostCmd_RET_BIT 0x8000 +#define HostCmd_ACT_GEN_GET 0x0000 +#define HostCmd_ACT_GEN_SET 0x0001 +#define HostCmd_ACT_GEN_REMOVE 0x0004 +#define HostCmd_ACT_BITWISE_SET 0x0002 +#define HostCmd_ACT_BITWISE_CLR 0x0003 +#define HostCmd_RESULT_OK 0x0000 + +#define HostCmd_ACT_MAC_RX_ON 0x0001 +#define HostCmd_ACT_MAC_TX_ON 0x0002 +#define HostCmd_ACT_MAC_WEP_ENABLE 0x0008 +#define HostCmd_ACT_MAC_ETHERNETII_ENABLE 0x0010 +#define HostCmd_ACT_MAC_PROMISCUOUS_ENABLE 0x0080 +#define HostCmd_ACT_MAC_ALL_MULTICAST_ENABLE 0x0100 +#define HostCmd_ACT_MAC_ADHOC_G_PROTECTION_ON 0x2000 + +#define HostCmd_BSS_MODE_IBSS 0x0002 +#define HostCmd_BSS_MODE_ANY 0x0003 + +#define HostCmd_SCAN_RADIO_TYPE_BG 0 +#define HostCmd_SCAN_RADIO_TYPE_A 1 + +#define HS_CFG_CANCEL 0xffffffff +#define HS_CFG_COND_DEF 0x00000000 +#define HS_CFG_GPIO_DEF 0xff +#define HS_CFG_GAP_DEF 0 +#define HS_CFG_COND_BROADCAST_DATA 0x00000001 +#define HS_CFG_COND_UNICAST_DATA 0x00000002 +#define HS_CFG_COND_MAC_EVENT 0x00000004 +#define HS_CFG_COND_MULTICAST_DATA 0x00000008 + +#define MWIFIEX_TIMEOUT_FOR_AP_RESP 0xfffc +#define MWIFIEX_STATUS_CODE_AUTH_TIMEOUT 2 + +#define CMD_F_HOSTCMD (1 << 0) +#define CMD_F_CANCELED (1 << 1) + +#define HostCmd_CMD_ID_MASK 0x0fff + +#define HostCmd_SEQ_NUM_MASK 0x00ff + +#define HostCmd_BSS_NUM_MASK 0x0f00 + +#define HostCmd_BSS_TYPE_MASK 0xf000 + +#define HostCmd_ACT_SET_RX 0x0001 +#define HostCmd_ACT_SET_TX 0x0002 +#define HostCmd_ACT_SET_BOTH 0x0003 + +#define RF_ANTENNA_AUTO 0xFFFF + +#define HostCmd_SET_SEQ_NO_BSS_INFO(seq, num, type) { \ + (((seq) & 0x00ff) | \ + (((num) & 0x000f) << 8)) | \ + (((type) & 0x000f) << 12); } + +#define HostCmd_GET_SEQ_NO(seq) \ + ((seq) & HostCmd_SEQ_NUM_MASK) + +#define HostCmd_GET_BSS_NO(seq) \ + (((seq) & HostCmd_BSS_NUM_MASK) >> 8) + +#define HostCmd_GET_BSS_TYPE(seq) \ + (((seq) & HostCmd_BSS_TYPE_MASK) >> 12) + +#define EVENT_DUMMY_HOST_WAKEUP_SIGNAL 0x00000001 +#define EVENT_LINK_LOST 0x00000003 +#define EVENT_LINK_SENSED 0x00000004 +#define EVENT_MIB_CHANGED 0x00000006 +#define EVENT_INIT_DONE 0x00000007 +#define EVENT_DEAUTHENTICATED 0x00000008 +#define EVENT_DISASSOCIATED 0x00000009 +#define EVENT_PS_AWAKE 0x0000000a +#define EVENT_PS_SLEEP 0x0000000b +#define EVENT_MIC_ERR_MULTICAST 0x0000000d +#define EVENT_MIC_ERR_UNICAST 0x0000000e +#define EVENT_DEEP_SLEEP_AWAKE 0x00000010 +#define EVENT_ADHOC_BCN_LOST 0x00000011 + +#define EVENT_WMM_STATUS_CHANGE 0x00000017 +#define EVENT_BG_SCAN_REPORT 0x00000018 +#define EVENT_RSSI_LOW 0x00000019 +#define EVENT_SNR_LOW 0x0000001a +#define EVENT_MAX_FAIL 0x0000001b +#define EVENT_RSSI_HIGH 0x0000001c +#define EVENT_SNR_HIGH 0x0000001d +#define EVENT_IBSS_COALESCED 0x0000001e +#define EVENT_DATA_RSSI_LOW 0x00000024 +#define EVENT_DATA_SNR_LOW 0x00000025 +#define EVENT_DATA_RSSI_HIGH 0x00000026 +#define EVENT_DATA_SNR_HIGH 0x00000027 +#define EVENT_LINK_QUALITY 0x00000028 +#define EVENT_PORT_RELEASE 0x0000002b +#define EVENT_UAP_STA_DEAUTH 0x0000002c +#define EVENT_UAP_STA_ASSOC 0x0000002d +#define EVENT_UAP_BSS_START 0x0000002e +#define EVENT_PRE_BEACON_LOST 0x00000031 +#define EVENT_ADDBA 0x00000033 +#define EVENT_DELBA 0x00000034 +#define EVENT_BA_STREAM_TIEMOUT 0x00000037 +#define EVENT_AMSDU_AGGR_CTRL 0x00000042 +#define EVENT_UAP_BSS_IDLE 0x00000043 +#define EVENT_UAP_BSS_ACTIVE 0x00000044 +#define EVENT_WEP_ICV_ERR 0x00000046 +#define EVENT_HS_ACT_REQ 0x00000047 +#define EVENT_BW_CHANGE 0x00000048 +#define EVENT_UAP_MIC_COUNTERMEASURES 0x0000004c +#define EVENT_HOSTWAKE_STAIE 0x0000004d +#define EVENT_REMAIN_ON_CHAN_EXPIRED 0x0000005f + +#define EVENT_ID_MASK 0xffff +#define BSS_NUM_MASK 0xf + +#define EVENT_GET_BSS_NUM(event_cause) \ + (((event_cause) >> 16) & BSS_NUM_MASK) + +#define EVENT_GET_BSS_TYPE(event_cause) \ + (((event_cause) >> 24) & 0x00ff) + +#define MWIFIEX_MAX_PATTERN_LEN 20 +#define MWIFIEX_MAX_OFFSET_LEN 50 +#define STACK_NBYTES 100 +#define TYPE_DNUM 1 +#define TYPE_BYTESEQ 2 +#define MAX_OPERAND 0x40 +#define TYPE_EQ (MAX_OPERAND+1) +#define TYPE_EQ_DNUM (MAX_OPERAND+2) +#define TYPE_EQ_BIT (MAX_OPERAND+3) +#define TYPE_AND (MAX_OPERAND+4) +#define TYPE_OR (MAX_OPERAND+5) +#define MEF_MODE_HOST_SLEEP 1 +#define MEF_ACTION_ALLOW_AND_WAKEUP_HOST 3 +#define MWIFIEX_CRITERIA_BROADCAST BIT(0) +#define MWIFIEX_CRITERIA_UNICAST BIT(1) +#define MWIFIEX_CRITERIA_MULTICAST BIT(3) + +struct mwifiex_ie_types_header { + __le16 type; + __le16 len; +} __packed; + +struct mwifiex_ie_types_data { + struct mwifiex_ie_types_header header; + u8 data[1]; +} __packed; + +#define MWIFIEX_TxPD_POWER_MGMT_NULL_PACKET 0x01 +#define MWIFIEX_TxPD_POWER_MGMT_LAST_PACKET 0x08 + +struct txpd { + u8 bss_type; + u8 bss_num; + __le16 tx_pkt_length; + __le16 tx_pkt_offset; + __le16 tx_pkt_type; + __le32 tx_control; + u8 priority; + u8 flags; + u8 pkt_delay_2ms; + u8 reserved1; +} __packed; + +struct rxpd { + u8 bss_type; + u8 bss_num; + __le16 rx_pkt_length; + __le16 rx_pkt_offset; + __le16 rx_pkt_type; + __le16 seq_num; + u8 priority; + u8 rx_rate; + s8 snr; + s8 nf; + + /* For: Non-802.11 AC cards + * + * Ht Info [Bit 0] RxRate format: LG=0, HT=1 + * [Bit 1] HT Bandwidth: BW20 = 0, BW40 = 1 + * [Bit 2] HT Guard Interval: LGI = 0, SGI = 1 + * + * For: 802.11 AC cards + * [Bit 1] [Bit 0] RxRate format: legacy rate = 00 HT = 01 VHT = 10 + * [Bit 3] [Bit 2] HT/VHT Bandwidth BW20 = 00 BW40 = 01 + * BW80 = 10 BW160 = 11 + * [Bit 4] HT/VHT Guard interval LGI = 0 SGI = 1 + * [Bit 5] STBC support Enabled = 1 + * [Bit 6] LDPC support Enabled = 1 + * [Bit 7] Reserved + */ + u8 ht_info; + u8 reserved; +} __packed; + +struct uap_txpd { + u8 bss_type; + u8 bss_num; + __le16 tx_pkt_length; + __le16 tx_pkt_offset; + __le16 tx_pkt_type; + __le32 tx_control; + u8 priority; + u8 flags; + u8 pkt_delay_2ms; + u8 reserved1; + __le32 reserved2; +}; + +struct uap_rxpd { + u8 bss_type; + u8 bss_num; + __le16 rx_pkt_length; + __le16 rx_pkt_offset; + __le16 rx_pkt_type; + __le16 seq_num; + u8 priority; + u8 reserved1; +}; + +enum mwifiex_chan_scan_mode_bitmasks { + MWIFIEX_PASSIVE_SCAN = BIT(0), + MWIFIEX_DISABLE_CHAN_FILT = BIT(1), +}; + +struct mwifiex_chan_scan_param_set { + u8 radio_type; + u8 chan_number; + u8 chan_scan_mode_bitmap; + __le16 min_scan_time; + __le16 max_scan_time; +} __packed; + +struct mwifiex_ie_types_chan_list_param_set { + struct mwifiex_ie_types_header header; + struct mwifiex_chan_scan_param_set chan_scan_param[1]; +} __packed; + +struct chan_band_param_set { + u8 radio_type; + u8 chan_number; +}; + +struct mwifiex_ie_types_chan_band_list_param_set { + struct mwifiex_ie_types_header header; + struct chan_band_param_set chan_band_param[1]; +} __packed; + +struct mwifiex_ie_types_rates_param_set { + struct mwifiex_ie_types_header header; + u8 rates[1]; +} __packed; + +struct mwifiex_ie_types_ssid_param_set { + struct mwifiex_ie_types_header header; + u8 ssid[1]; +} __packed; + +struct mwifiex_ie_types_num_probes { + struct mwifiex_ie_types_header header; + __le16 num_probes; +} __packed; + +struct mwifiex_ie_types_wildcard_ssid_params { + struct mwifiex_ie_types_header header; + u8 max_ssid_length; + u8 ssid[1]; +} __packed; + +#define TSF_DATA_SIZE 8 +struct mwifiex_ie_types_tsf_timestamp { + struct mwifiex_ie_types_header header; + u8 tsf_data[1]; +} __packed; + +struct mwifiex_cf_param_set { + u8 cfp_cnt; + u8 cfp_period; + u16 cfp_max_duration; + u16 cfp_duration_remaining; +} __packed; + +struct mwifiex_ibss_param_set { + u16 atim_window; +} __packed; + +struct mwifiex_ie_types_ss_param_set { + struct mwifiex_ie_types_header header; + union { + struct mwifiex_cf_param_set cf_param_set[1]; + struct mwifiex_ibss_param_set ibss_param_set[1]; + } cf_ibss; +} __packed; + +struct mwifiex_fh_param_set { + u16 dwell_time; + u8 hop_set; + u8 hop_pattern; + u8 hop_index; +} __packed; + +struct mwifiex_ds_param_set { + u8 current_chan; +} __packed; + +struct mwifiex_ie_types_phy_param_set { + struct mwifiex_ie_types_header header; + union { + struct mwifiex_fh_param_set fh_param_set[1]; + struct mwifiex_ds_param_set ds_param_set[1]; + } fh_ds; +} __packed; + +struct mwifiex_ie_types_auth_type { + struct mwifiex_ie_types_header header; + __le16 auth_type; +} __packed; + +struct mwifiex_ie_types_vendor_param_set { + struct mwifiex_ie_types_header header; + u8 ie[MWIFIEX_MAX_VSIE_LEN]; +}; + +struct mwifiex_ie_types_rsn_param_set { + struct mwifiex_ie_types_header header; + u8 rsn_ie[1]; +} __packed; + +#define KEYPARAMSET_FIXED_LEN 6 + +struct mwifiex_ie_type_key_param_set { + __le16 type; + __le16 length; + __le16 key_type_id; + __le16 key_info; + __le16 key_len; + u8 key[50]; +} __packed; + +#define IGTK_PN_LEN 8 + +struct mwifiex_cmac_param { + u8 ipn[IGTK_PN_LEN]; + u8 key[WLAN_KEY_LEN_AES_CMAC]; +} __packed; + +struct host_cmd_ds_802_11_key_material { + __le16 action; + struct mwifiex_ie_type_key_param_set key_param_set; +} __packed; + +struct host_cmd_ds_gen { + u16 command; + u16 size; + u16 seq_num; + u16 result; +}; + +#define S_DS_GEN sizeof(struct host_cmd_ds_gen) + +enum sleep_resp_ctrl { + RESP_NOT_NEEDED = 0, + RESP_NEEDED, +}; + +struct mwifiex_ps_param { + __le16 null_pkt_interval; + __le16 multiple_dtims; + __le16 bcn_miss_timeout; + __le16 local_listen_interval; + __le16 adhoc_wake_period; + __le16 mode; + __le16 delay_to_ps; +}; + +#define BITMAP_AUTO_DS 0x01 +#define BITMAP_STA_PS 0x10 + +struct mwifiex_ie_types_auto_ds_param { + struct mwifiex_ie_types_header header; + __le16 deep_sleep_timeout; +} __packed; + +struct mwifiex_ie_types_ps_param { + struct mwifiex_ie_types_header header; + struct mwifiex_ps_param param; +} __packed; + +struct host_cmd_ds_802_11_ps_mode_enh { + __le16 action; + + union { + struct mwifiex_ps_param opt_ps; + __le16 ps_bitmap; + } params; +} __packed; + +struct host_cmd_ds_get_hw_spec { + __le16 hw_if_version; + __le16 version; + __le16 reserved; + __le16 num_of_mcast_adr; + u8 permanent_addr[ETH_ALEN]; + __le16 region_code; + __le16 number_of_antenna; + __le32 fw_release_number; + __le32 reserved_1; + __le32 reserved_2; + __le32 reserved_3; + __le32 fw_cap_info; + __le32 dot_11n_dev_cap; + u8 dev_mcs_support; + __le16 mp_end_port; /* SDIO only, reserved for other interfacces */ + __le16 mgmt_buf_count; /* mgmt IE buffer count */ + __le32 reserved_5; + __le32 reserved_6; + __le32 dot_11ac_dev_cap; + __le32 dot_11ac_mcs_support; +} __packed; + +struct host_cmd_ds_802_11_rssi_info { + __le16 action; + __le16 ndata; + __le16 nbcn; + __le16 reserved[9]; + long long reserved_1; +}; + +struct host_cmd_ds_802_11_rssi_info_rsp { + __le16 action; + __le16 ndata; + __le16 nbcn; + __le16 data_rssi_last; + __le16 data_nf_last; + __le16 data_rssi_avg; + __le16 data_nf_avg; + __le16 bcn_rssi_last; + __le16 bcn_nf_last; + __le16 bcn_rssi_avg; + __le16 bcn_nf_avg; + long long tsf_bcn; +}; + +struct host_cmd_ds_802_11_mac_address { + __le16 action; + u8 mac_addr[ETH_ALEN]; +}; + +struct host_cmd_ds_mac_control { + __le16 action; + __le16 reserved; +}; + +struct host_cmd_ds_mac_multicast_adr { + __le16 action; + __le16 num_of_adrs; + u8 mac_list[MWIFIEX_MAX_MULTICAST_LIST_SIZE][ETH_ALEN]; +} __packed; + +struct host_cmd_ds_802_11_deauthenticate { + u8 mac_addr[ETH_ALEN]; + __le16 reason_code; +} __packed; + +struct host_cmd_ds_802_11_associate { + u8 peer_sta_addr[ETH_ALEN]; + __le16 cap_info_bitmap; + __le16 listen_interval; + __le16 beacon_period; + u8 dtim_period; +} __packed; + +struct ieee_types_assoc_rsp { + __le16 cap_info_bitmap; + __le16 status_code; + __le16 a_id; + u8 ie_buffer[1]; +} __packed; + +struct host_cmd_ds_802_11_associate_rsp { + struct ieee_types_assoc_rsp assoc_rsp; +} __packed; + +struct ieee_types_cf_param_set { + u8 element_id; + u8 len; + u8 cfp_cnt; + u8 cfp_period; + u16 cfp_max_duration; + u16 cfp_duration_remaining; +} __packed; + +struct ieee_types_ibss_param_set { + u8 element_id; + u8 len; + __le16 atim_window; +} __packed; + +union ieee_types_ss_param_set { + struct ieee_types_cf_param_set cf_param_set; + struct ieee_types_ibss_param_set ibss_param_set; +} __packed; + +struct ieee_types_fh_param_set { + u8 element_id; + u8 len; + __le16 dwell_time; + u8 hop_set; + u8 hop_pattern; + u8 hop_index; +} __packed; + +struct ieee_types_ds_param_set { + u8 element_id; + u8 len; + u8 current_chan; +} __packed; + +union ieee_types_phy_param_set { + struct ieee_types_fh_param_set fh_param_set; + struct ieee_types_ds_param_set ds_param_set; +} __packed; + +struct ieee_types_oper_mode_ntf { + u8 element_id; + u8 len; + u8 oper_mode; +} __packed; + +struct host_cmd_ds_802_11_ad_hoc_start { + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 bss_mode; + __le16 beacon_period; + u8 dtim_period; + union ieee_types_ss_param_set ss_param_set; + union ieee_types_phy_param_set phy_param_set; + u16 reserved1; + __le16 cap_info_bitmap; + u8 data_rate[HOSTCMD_SUPPORTED_RATES]; +} __packed; + +struct host_cmd_ds_802_11_ad_hoc_result { + u8 pad[3]; + u8 bssid[ETH_ALEN]; +} __packed; + +struct adhoc_bss_desc { + u8 bssid[ETH_ALEN]; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 bss_mode; + __le16 beacon_period; + u8 dtim_period; + u8 time_stamp[8]; + u8 local_time[8]; + union ieee_types_phy_param_set phy_param_set; + union ieee_types_ss_param_set ss_param_set; + __le16 cap_info_bitmap; + u8 data_rates[HOSTCMD_SUPPORTED_RATES]; + + /* + * DO NOT ADD ANY FIELDS TO THIS STRUCTURE. + * It is used in the Adhoc join command and will cause a + * binary layout mismatch with the firmware + */ +} __packed; + +struct host_cmd_ds_802_11_ad_hoc_join { + struct adhoc_bss_desc bss_descriptor; + u16 reserved1; + u16 reserved2; +} __packed; + +struct host_cmd_ds_802_11_get_log { + __le32 mcast_tx_frame; + __le32 failed; + __le32 retry; + __le32 multi_retry; + __le32 frame_dup; + __le32 rts_success; + __le32 rts_failure; + __le32 ack_failure; + __le32 rx_frag; + __le32 mcast_rx_frame; + __le32 fcs_error; + __le32 tx_frame; + __le32 reserved; + __le32 wep_icv_err_cnt[4]; +}; + +/* Enumeration for rate format */ +enum _mwifiex_rate_format { + MWIFIEX_RATE_FORMAT_LG = 0, + MWIFIEX_RATE_FORMAT_HT, + MWIFIEX_RATE_FORMAT_VHT, + MWIFIEX_RATE_FORMAT_AUTO = 0xFF, +}; + +struct host_cmd_ds_tx_rate_query { + u8 tx_rate; + /* Tx Rate Info: For 802.11 AC cards + * + * [Bit 0-1] tx rate formate: LG = 0, HT = 1, VHT = 2 + * [Bit 2-3] HT/VHT Bandwidth: BW20 = 0, BW40 = 1, BW80 = 2, BW160 = 3 + * [Bit 4] HT/VHT Guard Interval: LGI = 0, SGI = 1 + * + * For non-802.11 AC cards + * Ht Info [Bit 0] RxRate format: LG=0, HT=1 + * [Bit 1] HT Bandwidth: BW20 = 0, BW40 = 1 + * [Bit 2] HT Guard Interval: LGI = 0, SGI = 1 + */ + u8 ht_info; +} __packed; + +enum Host_Sleep_Action { + HS_CONFIGURE = 0x0001, + HS_ACTIVATE = 0x0002, +}; + +struct mwifiex_hs_config_param { + __le32 conditions; + u8 gpio; + u8 gap; +} __packed; + +struct hs_activate_param { + u16 resp_ctrl; +} __packed; + +struct host_cmd_ds_802_11_hs_cfg_enh { + __le16 action; + + union { + struct mwifiex_hs_config_param hs_config; + struct hs_activate_param hs_activate; + } params; +} __packed; + +enum SNMP_MIB_INDEX { + OP_RATE_SET_I = 1, + DTIM_PERIOD_I = 3, + RTS_THRESH_I = 5, + SHORT_RETRY_LIM_I = 6, + LONG_RETRY_LIM_I = 7, + FRAG_THRESH_I = 8, + DOT11D_I = 9, +}; + +#define MAX_SNMP_BUF_SIZE 128 + +struct host_cmd_ds_802_11_snmp_mib { + __le16 query_type; + __le16 oid; + __le16 buf_size; + u8 value[1]; +} __packed; + +struct mwifiex_rate_scope { + __le16 type; + __le16 length; + __le16 hr_dsss_rate_bitmap; + __le16 ofdm_rate_bitmap; + __le16 ht_mcs_rate_bitmap[8]; +} __packed; + +struct mwifiex_rate_drop_pattern { + __le16 type; + __le16 length; + __le32 rate_drop_mode; +} __packed; + +struct host_cmd_ds_tx_rate_cfg { + __le16 action; + __le16 cfg_index; +} __packed; + +struct mwifiex_power_group { + u8 modulation_class; + u8 first_rate_code; + u8 last_rate_code; + s8 power_step; + s8 power_min; + s8 power_max; + u8 ht_bandwidth; + u8 reserved; +} __packed; + +struct mwifiex_types_power_group { + u16 type; + u16 length; +} __packed; + +struct host_cmd_ds_txpwr_cfg { + __le16 action; + __le16 cfg_index; + __le32 mode; +} __packed; + +struct host_cmd_ds_rf_tx_pwr { + __le16 action; + __le16 cur_level; + u8 max_power; + u8 min_power; +} __packed; + +struct host_cmd_ds_rf_ant_mimo { + __le16 action_tx; + __le16 tx_ant_mode; + __le16 action_rx; + __le16 rx_ant_mode; +}; + +struct host_cmd_ds_rf_ant_siso { + __le16 action; + __le16 ant_mode; +}; + +struct mwifiex_bcn_param { + u8 bssid[ETH_ALEN]; + u8 rssi; + __le64 timestamp; + __le16 beacon_period; + __le16 cap_info_bitmap; +} __packed; + +#define MWIFIEX_USER_SCAN_CHAN_MAX 50 + +#define MWIFIEX_MAX_SSID_LIST_LENGTH 10 + +struct mwifiex_scan_cmd_config { + /* + * BSS mode to be sent in the firmware command + */ + u8 bss_mode; + + /* Specific BSSID used to filter scan results in the firmware */ + u8 specific_bssid[ETH_ALEN]; + + /* Length of TLVs sent in command starting at tlvBuffer */ + u32 tlv_buf_len; + + /* + * SSID TLV(s) and ChanList TLVs to be sent in the firmware command + * + * TLV_TYPE_CHANLIST, mwifiex_ie_types_chan_list_param_set + * WLAN_EID_SSID, mwifiex_ie_types_ssid_param_set + */ + u8 tlv_buf[1]; /* SSID TLV(s) and ChanList TLVs are stored + here */ +} __packed; + +struct mwifiex_user_scan_chan { + u8 chan_number; + u8 radio_type; + u8 scan_type; + u8 reserved; + u32 scan_time; +} __packed; + +struct mwifiex_user_scan_cfg { + /* + * BSS mode to be sent in the firmware command + */ + u8 bss_mode; + /* Configure the number of probe requests for active chan scans */ + u8 num_probes; + u8 reserved; + /* BSSID filter sent in the firmware command to limit the results */ + u8 specific_bssid[ETH_ALEN]; + /* SSID filter list used in the firmware to limit the scan results */ + struct cfg80211_ssid *ssid_list; + u8 num_ssids; + /* Variable number (fixed maximum) of channels to scan up */ + struct mwifiex_user_scan_chan chan_list[MWIFIEX_USER_SCAN_CHAN_MAX]; +} __packed; + +struct ie_body { + u8 grp_key_oui[4]; + u8 ptk_cnt[2]; + u8 ptk_body[4]; +} __packed; + +struct host_cmd_ds_802_11_scan { + u8 bss_mode; + u8 bssid[ETH_ALEN]; + u8 tlv_buffer[1]; +} __packed; + +struct host_cmd_ds_802_11_scan_rsp { + __le16 bss_descript_size; + u8 number_of_sets; + u8 bss_desc_and_tlv_buffer[1]; +} __packed; + +struct host_cmd_ds_802_11_bg_scan_query { + u8 flush; +} __packed; + +struct host_cmd_ds_802_11_bg_scan_query_rsp { + u32 report_condition; + struct host_cmd_ds_802_11_scan_rsp scan_resp; +} __packed; + +struct mwifiex_ietypes_domain_param_set { + struct mwifiex_ie_types_header header; + u8 country_code[IEEE80211_COUNTRY_STRING_LEN]; + struct ieee80211_country_ie_triplet triplet[1]; +} __packed; + +struct host_cmd_ds_802_11d_domain_info { + __le16 action; + struct mwifiex_ietypes_domain_param_set domain; +} __packed; + +struct host_cmd_ds_802_11d_domain_info_rsp { + __le16 action; + struct mwifiex_ietypes_domain_param_set domain; +} __packed; + +struct host_cmd_ds_11n_addba_req { + u8 add_req_result; + u8 peer_mac_addr[ETH_ALEN]; + u8 dialog_token; + __le16 block_ack_param_set; + __le16 block_ack_tmo; + __le16 ssn; +} __packed; + +struct host_cmd_ds_11n_addba_rsp { + u8 add_rsp_result; + u8 peer_mac_addr[ETH_ALEN]; + u8 dialog_token; + __le16 status_code; + __le16 block_ack_param_set; + __le16 block_ack_tmo; + __le16 ssn; +} __packed; + +struct host_cmd_ds_11n_delba { + u8 del_result; + u8 peer_mac_addr[ETH_ALEN]; + __le16 del_ba_param_set; + __le16 reason_code; + u8 reserved; +} __packed; + +struct host_cmd_ds_11n_batimeout { + u8 tid; + u8 peer_mac_addr[ETH_ALEN]; + u8 origninator; +} __packed; + +struct host_cmd_ds_11n_cfg { + __le16 action; + __le16 ht_tx_cap; + __le16 ht_tx_info; + __le16 misc_config; /* Needed for 802.11AC cards only */ +} __packed; + +struct host_cmd_ds_txbuf_cfg { + __le16 action; + __le16 buff_size; + __le16 mp_end_port; /* SDIO only, reserved for other interfacces */ + __le16 reserved3; +} __packed; + +struct host_cmd_ds_amsdu_aggr_ctrl { + __le16 action; + __le16 enable; + __le16 curr_buf_size; +} __packed; + +struct mwifiex_ie_types_wmm_param_set { + struct mwifiex_ie_types_header header; + u8 wmm_ie[1]; +}; + +struct mwifiex_ie_types_wmm_queue_status { + struct mwifiex_ie_types_header header; + u8 queue_index; + u8 disabled; + u16 medium_time; + u8 flow_required; + u8 flow_created; + u32 reserved; +}; + +struct ieee_types_vendor_header { + u8 element_id; + u8 len; + u8 oui[4]; /* 0~2: oui, 3: oui_type */ + u8 oui_subtype; + u8 version; +} __packed; + +struct ieee_types_wmm_parameter { + /* + * WMM Parameter IE - Vendor Specific Header: + * element_id [221/0xdd] + * Len [24] + * Oui [00:50:f2] + * OuiType [2] + * OuiSubType [1] + * Version [1] + */ + struct ieee_types_vendor_header vend_hdr; + u8 qos_info_bitmap; + u8 reserved; + struct ieee_types_wmm_ac_parameters ac_params[IEEE80211_NUM_ACS]; +} __packed; + +struct ieee_types_wmm_info { + + /* + * WMM Info IE - Vendor Specific Header: + * element_id [221/0xdd] + * Len [7] + * Oui [00:50:f2] + * OuiType [2] + * OuiSubType [0] + * Version [1] + */ + struct ieee_types_vendor_header vend_hdr; + + u8 qos_info_bitmap; +} __packed; + +struct host_cmd_ds_wmm_get_status { + u8 queue_status_tlv[sizeof(struct mwifiex_ie_types_wmm_queue_status) * + IEEE80211_NUM_ACS]; + u8 wmm_param_tlv[sizeof(struct ieee_types_wmm_parameter) + 2]; +} __packed; + +struct mwifiex_wmm_ac_status { + u8 disabled; + u8 flow_required; + u8 flow_created; +}; + +struct mwifiex_ie_types_htcap { + struct mwifiex_ie_types_header header; + struct ieee80211_ht_cap ht_cap; +} __packed; + +struct mwifiex_ie_types_vhtcap { + struct mwifiex_ie_types_header header; + struct ieee80211_vht_cap vht_cap; +} __packed; + +struct mwifiex_ie_types_oper_mode_ntf { + struct mwifiex_ie_types_header header; + u8 oper_mode; +} __packed; + +/* VHT Operations IE */ +struct mwifiex_ie_types_vht_oper { + struct mwifiex_ie_types_header header; + u8 chan_width; + u8 chan_center_freq_1; + u8 chan_center_freq_2; + /* Basic MCS set map, each 2 bits stands for a NSS */ + u16 basic_mcs_map; +} __packed; + +struct mwifiex_ie_types_wmmcap { + struct mwifiex_ie_types_header header; + struct mwifiex_types_wmm_info wmm_info; +} __packed; + +struct mwifiex_ie_types_htinfo { + struct mwifiex_ie_types_header header; + struct ieee80211_ht_operation ht_oper; +} __packed; + +struct mwifiex_ie_types_2040bssco { + struct mwifiex_ie_types_header header; + u8 bss_co_2040; +} __packed; + +struct mwifiex_ie_types_extcap { + struct mwifiex_ie_types_header header; + u8 ext_cap; +} __packed; + +struct host_cmd_ds_mac_reg_access { + __le16 action; + __le16 offset; + __le32 value; +} __packed; + +struct host_cmd_ds_bbp_reg_access { + __le16 action; + __le16 offset; + u8 value; + u8 reserved[3]; +} __packed; + +struct host_cmd_ds_rf_reg_access { + __le16 action; + __le16 offset; + u8 value; + u8 reserved[3]; +} __packed; + +struct host_cmd_ds_pmic_reg_access { + __le16 action; + __le16 offset; + u8 value; + u8 reserved[3]; +} __packed; + +struct host_cmd_ds_802_11_eeprom_access { + __le16 action; + + __le16 offset; + __le16 byte_count; + u8 value; +} __packed; + +struct host_cmd_tlv { + __le16 type; + __le16 len; +} __packed; + +struct mwifiex_assoc_event { + u8 sta_addr[ETH_ALEN]; + __le16 type; + __le16 len; + __le16 frame_control; + __le16 cap_info; + __le16 listen_interval; + u8 data[0]; +} __packed; + +struct host_cmd_ds_sys_config { + __le16 action; + u8 tlv[0]; +}; + +struct host_cmd_11ac_vht_cfg { + __le16 action; + u8 band_config; + u8 misc_config; + __le32 cap_info; + __le32 mcs_tx_set; + __le32 mcs_rx_set; +} __packed; + +struct host_cmd_tlv_akmp { + struct host_cmd_tlv tlv; + __le16 key_mgmt; + __le16 key_mgmt_operation; +} __packed; + +struct host_cmd_tlv_pwk_cipher { + struct host_cmd_tlv tlv; + __le16 proto; + u8 cipher; + u8 reserved; +} __packed; + +struct host_cmd_tlv_gwk_cipher { + struct host_cmd_tlv tlv; + u8 cipher; + u8 reserved; +} __packed; + +struct host_cmd_tlv_passphrase { + struct host_cmd_tlv tlv; + u8 passphrase[0]; +} __packed; + +struct host_cmd_tlv_wep_key { + struct host_cmd_tlv tlv; + u8 key_index; + u8 is_default; + u8 key[1]; +}; + +struct host_cmd_tlv_auth_type { + struct host_cmd_tlv tlv; + u8 auth_type; +} __packed; + +struct host_cmd_tlv_encrypt_protocol { + struct host_cmd_tlv tlv; + __le16 proto; +} __packed; + +struct host_cmd_tlv_ssid { + struct host_cmd_tlv tlv; + u8 ssid[0]; +} __packed; + +struct host_cmd_tlv_rates { + struct host_cmd_tlv tlv; + u8 rates[0]; +} __packed; + +struct host_cmd_tlv_bcast_ssid { + struct host_cmd_tlv tlv; + u8 bcast_ctl; +} __packed; + +struct host_cmd_tlv_beacon_period { + struct host_cmd_tlv tlv; + __le16 period; +} __packed; + +struct host_cmd_tlv_dtim_period { + struct host_cmd_tlv tlv; + u8 period; +} __packed; + +struct host_cmd_tlv_frag_threshold { + struct host_cmd_tlv tlv; + __le16 frag_thr; +} __packed; + +struct host_cmd_tlv_rts_threshold { + struct host_cmd_tlv tlv; + __le16 rts_thr; +} __packed; + +struct host_cmd_tlv_retry_limit { + struct host_cmd_tlv tlv; + u8 limit; +} __packed; + +struct host_cmd_tlv_mac_addr { + struct host_cmd_tlv tlv; + u8 mac_addr[ETH_ALEN]; +} __packed; + +struct host_cmd_tlv_channel_band { + struct host_cmd_tlv tlv; + u8 band_config; + u8 channel; +} __packed; + +struct host_cmd_tlv_ageout_timer { + struct host_cmd_tlv tlv; + __le32 sta_ao_timer; +} __packed; + +struct host_cmd_ds_version_ext { + u8 version_str_sel; + char version_str[128]; +} __packed; + +struct host_cmd_ds_mgmt_frame_reg { + __le16 action; + __le32 mask; +} __packed; + +struct host_cmd_ds_p2p_mode_cfg { + __le16 action; + __le16 mode; +} __packed; + +struct host_cmd_ds_remain_on_chan { + __le16 action; + u8 status; + u8 reserved; + u8 band_cfg; + u8 channel; + __le32 duration; +} __packed; + +struct host_cmd_ds_802_11_ibss_status { + __le16 action; + __le16 enable; + u8 bssid[ETH_ALEN]; + __le16 beacon_interval; + __le16 atim_window; + __le16 use_g_rate_protect; +} __packed; + +struct mwifiex_fw_mef_entry { + u8 mode; + u8 action; + __le16 exprsize; + u8 expr[0]; +} __packed; + +struct host_cmd_ds_mef_cfg { + __le32 criteria; + __le16 num_entries; + struct mwifiex_fw_mef_entry mef_entry[0]; +} __packed; + +#define CONNECTION_TYPE_INFRA 0 +#define CONNECTION_TYPE_ADHOC 1 +#define CONNECTION_TYPE_AP 2 + +struct host_cmd_ds_set_bss_mode { + u8 con_type; +} __packed; + +struct host_cmd_ds_pcie_details { + /* TX buffer descriptor ring address */ + u32 txbd_addr_lo; + u32 txbd_addr_hi; + /* TX buffer descriptor ring count */ + u32 txbd_count; + + /* RX buffer descriptor ring address */ + u32 rxbd_addr_lo; + u32 rxbd_addr_hi; + /* RX buffer descriptor ring count */ + u32 rxbd_count; + + /* Event buffer descriptor ring address */ + u32 evtbd_addr_lo; + u32 evtbd_addr_hi; + /* Event buffer descriptor ring count */ + u32 evtbd_count; + + /* Sleep cookie buffer physical address */ + u32 sleep_cookie_addr_lo; + u32 sleep_cookie_addr_hi; +} __packed; + +struct mwifiex_ie_types_rssi_threshold { + struct mwifiex_ie_types_header header; + u8 abs_value; + u8 evt_freq; +} __packed; + +struct host_cmd_ds_802_11_subsc_evt { + __le16 action; + __le16 events; +} __packed; + +struct mwifiex_ie { + __le16 ie_index; + __le16 mgmt_subtype_mask; + __le16 ie_length; + u8 ie_buffer[IEEE_MAX_IE_SIZE]; +} __packed; + +#define MAX_MGMT_IE_INDEX 16 +struct mwifiex_ie_list { + __le16 type; + __le16 len; + struct mwifiex_ie ie_list[MAX_MGMT_IE_INDEX]; +} __packed; + +struct host_cmd_ds_command { + __le16 command; + __le16 size; + __le16 seq_num; + __le16 result; + union { + struct host_cmd_ds_get_hw_spec hw_spec; + struct host_cmd_ds_mac_control mac_ctrl; + struct host_cmd_ds_802_11_mac_address mac_addr; + struct host_cmd_ds_mac_multicast_adr mc_addr; + struct host_cmd_ds_802_11_get_log get_log; + struct host_cmd_ds_802_11_rssi_info rssi_info; + struct host_cmd_ds_802_11_rssi_info_rsp rssi_info_rsp; + struct host_cmd_ds_802_11_snmp_mib smib; + struct host_cmd_ds_tx_rate_query tx_rate; + struct host_cmd_ds_tx_rate_cfg tx_rate_cfg; + struct host_cmd_ds_txpwr_cfg txp_cfg; + struct host_cmd_ds_rf_tx_pwr txp; + struct host_cmd_ds_rf_ant_mimo ant_mimo; + struct host_cmd_ds_rf_ant_siso ant_siso; + struct host_cmd_ds_802_11_ps_mode_enh psmode_enh; + struct host_cmd_ds_802_11_hs_cfg_enh opt_hs_cfg; + struct host_cmd_ds_802_11_scan scan; + struct host_cmd_ds_802_11_scan_rsp scan_resp; + struct host_cmd_ds_802_11_bg_scan_query bg_scan_query; + struct host_cmd_ds_802_11_bg_scan_query_rsp bg_scan_query_resp; + struct host_cmd_ds_802_11_associate associate; + struct host_cmd_ds_802_11_associate_rsp associate_rsp; + struct host_cmd_ds_802_11_deauthenticate deauth; + struct host_cmd_ds_802_11_ad_hoc_start adhoc_start; + struct host_cmd_ds_802_11_ad_hoc_result adhoc_result; + struct host_cmd_ds_802_11_ad_hoc_join adhoc_join; + struct host_cmd_ds_802_11d_domain_info domain_info; + struct host_cmd_ds_802_11d_domain_info_rsp domain_info_resp; + struct host_cmd_ds_11n_addba_req add_ba_req; + struct host_cmd_ds_11n_addba_rsp add_ba_rsp; + struct host_cmd_ds_11n_delba del_ba; + struct host_cmd_ds_txbuf_cfg tx_buf; + struct host_cmd_ds_amsdu_aggr_ctrl amsdu_aggr_ctrl; + struct host_cmd_ds_11n_cfg htcfg; + struct host_cmd_ds_wmm_get_status get_wmm_status; + struct host_cmd_ds_802_11_key_material key_material; + struct host_cmd_ds_version_ext verext; + struct host_cmd_ds_mgmt_frame_reg reg_mask; + struct host_cmd_ds_remain_on_chan roc_cfg; + struct host_cmd_ds_p2p_mode_cfg mode_cfg; + struct host_cmd_ds_802_11_ibss_status ibss_coalescing; + struct host_cmd_ds_mef_cfg mef_cfg; + struct host_cmd_ds_mac_reg_access mac_reg; + struct host_cmd_ds_bbp_reg_access bbp_reg; + struct host_cmd_ds_rf_reg_access rf_reg; + struct host_cmd_ds_pmic_reg_access pmic_reg; + struct host_cmd_ds_set_bss_mode bss_mode; + struct host_cmd_ds_pcie_details pcie_host_spec; + struct host_cmd_ds_802_11_eeprom_access eeprom; + struct host_cmd_ds_802_11_subsc_evt subsc_evt; + struct host_cmd_ds_sys_config uap_sys_config; + struct host_cmd_11ac_vht_cfg vht_cfg; + } params; +} __packed; + +struct mwifiex_opt_sleep_confirm { + __le16 command; + __le16 size; + __le16 seq_num; + __le16 result; + __le16 action; + __le16 resp_ctrl; +} __packed; +#endif /* !_MWIFIEX_FW_H_ */ diff --git a/drivers/net/wireless/mwifiex/ie.c b/drivers/net/wireless/mwifiex/ie.c new file mode 100644 index 0000000..e38342f --- /dev/null +++ b/drivers/net/wireless/mwifiex/ie.c @@ -0,0 +1,443 @@ +/* + * Marvell Wireless LAN device driver: management IE handling- setting and + * deleting IE. + * + * Copyright (C) 2012, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "main.h" + +/* This function checks if current IE index is used by any on other interface. + * Return: -1: yes, current IE index is used by someone else. + * 0: no, current IE index is NOT used by other interface. + */ +static int +mwifiex_ie_index_used_by_other_intf(struct mwifiex_private *priv, u16 idx) +{ + int i; + struct mwifiex_adapter *adapter = priv->adapter; + struct mwifiex_ie *ie; + + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i] != priv) { + ie = &adapter->priv[i]->mgmt_ie[idx]; + if (ie->mgmt_subtype_mask && ie->ie_length) + return -1; + } + } + + return 0; +} + +/* Get unused IE index. This index will be used for setting new IE */ +static int +mwifiex_ie_get_autoidx(struct mwifiex_private *priv, u16 subtype_mask, + struct mwifiex_ie *ie, u16 *index) +{ + u16 mask, len, i; + + for (i = 0; i < priv->adapter->max_mgmt_ie_index; i++) { + mask = le16_to_cpu(priv->mgmt_ie[i].mgmt_subtype_mask); + len = le16_to_cpu(ie->ie_length); + + if (mask == MWIFIEX_AUTO_IDX_MASK) + continue; + + if (mask == subtype_mask) { + if (len > IEEE_MAX_IE_SIZE) + continue; + + *index = i; + return 0; + } + + if (!priv->mgmt_ie[i].ie_length) { + if (mwifiex_ie_index_used_by_other_intf(priv, i)) + continue; + + *index = i; + return 0; + } + } + + return -1; +} + +/* This function prepares IE data buffer for command to be sent to FW */ +static int +mwifiex_update_autoindex_ies(struct mwifiex_private *priv, + struct mwifiex_ie_list *ie_list) +{ + u16 travel_len, index, mask; + s16 input_len; + struct mwifiex_ie *ie; + u8 *tmp; + + input_len = le16_to_cpu(ie_list->len); + travel_len = sizeof(struct host_cmd_tlv); + + ie_list->len = 0; + + while (input_len > 0) { + ie = (struct mwifiex_ie *)(((u8 *)ie_list) + travel_len); + input_len -= le16_to_cpu(ie->ie_length) + MWIFIEX_IE_HDR_SIZE; + travel_len += le16_to_cpu(ie->ie_length) + MWIFIEX_IE_HDR_SIZE; + + index = le16_to_cpu(ie->ie_index); + mask = le16_to_cpu(ie->mgmt_subtype_mask); + + if (index == MWIFIEX_AUTO_IDX_MASK) { + /* automatic addition */ + if (mwifiex_ie_get_autoidx(priv, mask, ie, &index)) + return -1; + if (index == MWIFIEX_AUTO_IDX_MASK) + return -1; + + tmp = (u8 *)&priv->mgmt_ie[index].ie_buffer; + memcpy(tmp, &ie->ie_buffer, le16_to_cpu(ie->ie_length)); + priv->mgmt_ie[index].ie_length = ie->ie_length; + priv->mgmt_ie[index].ie_index = cpu_to_le16(index); + priv->mgmt_ie[index].mgmt_subtype_mask = + cpu_to_le16(mask); + + ie->ie_index = cpu_to_le16(index); + } else { + if (mask != MWIFIEX_DELETE_MASK) + return -1; + /* + * Check if this index is being used on any + * other interface. + */ + if (mwifiex_ie_index_used_by_other_intf(priv, index)) + return -1; + + ie->ie_length = 0; + memcpy(&priv->mgmt_ie[index], ie, + sizeof(struct mwifiex_ie)); + } + + le16_add_cpu(&ie_list->len, + le16_to_cpu(priv->mgmt_ie[index].ie_length) + + MWIFIEX_IE_HDR_SIZE); + } + + if (GET_BSS_ROLE(priv) == MWIFIEX_BSS_ROLE_UAP) + return mwifiex_send_cmd_async(priv, HostCmd_CMD_UAP_SYS_CONFIG, + HostCmd_ACT_GEN_SET, + UAP_CUSTOM_IE_I, ie_list); + + return 0; +} + +/* Copy individual custom IEs for beacon, probe response and assoc response + * and prepare single structure for IE setting. + * This function also updates allocated IE indices from driver. + */ +static int +mwifiex_update_uap_custom_ie(struct mwifiex_private *priv, + struct mwifiex_ie *beacon_ie, u16 *beacon_idx, + struct mwifiex_ie *pr_ie, u16 *probe_idx, + struct mwifiex_ie *ar_ie, u16 *assoc_idx) +{ + struct mwifiex_ie_list *ap_custom_ie; + u8 *pos; + u16 len; + int ret; + + ap_custom_ie = kzalloc(sizeof(*ap_custom_ie), GFP_KERNEL); + if (!ap_custom_ie) + return -ENOMEM; + + ap_custom_ie->type = cpu_to_le16(TLV_TYPE_MGMT_IE); + pos = (u8 *)ap_custom_ie->ie_list; + + if (beacon_ie) { + len = sizeof(struct mwifiex_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(beacon_ie->ie_length); + memcpy(pos, beacon_ie, len); + pos += len; + le16_add_cpu(&ap_custom_ie->len, len); + } + if (pr_ie) { + len = sizeof(struct mwifiex_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(pr_ie->ie_length); + memcpy(pos, pr_ie, len); + pos += len; + le16_add_cpu(&ap_custom_ie->len, len); + } + if (ar_ie) { + len = sizeof(struct mwifiex_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(ar_ie->ie_length); + memcpy(pos, ar_ie, len); + pos += len; + le16_add_cpu(&ap_custom_ie->len, len); + } + + ret = mwifiex_update_autoindex_ies(priv, ap_custom_ie); + + pos = (u8 *)(&ap_custom_ie->ie_list[0].ie_index); + if (beacon_ie && *beacon_idx == MWIFIEX_AUTO_IDX_MASK) { + /* save beacon ie index after auto-indexing */ + *beacon_idx = le16_to_cpu(ap_custom_ie->ie_list[0].ie_index); + len = sizeof(*beacon_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(beacon_ie->ie_length); + pos += len; + } + if (pr_ie && le16_to_cpu(pr_ie->ie_index) == MWIFIEX_AUTO_IDX_MASK) { + /* save probe resp ie index after auto-indexing */ + *probe_idx = *((u16 *)pos); + len = sizeof(*pr_ie) - IEEE_MAX_IE_SIZE + + le16_to_cpu(pr_ie->ie_length); + pos += len; + } + if (ar_ie && le16_to_cpu(ar_ie->ie_index) == MWIFIEX_AUTO_IDX_MASK) + /* save assoc resp ie index after auto-indexing */ + *assoc_idx = *((u16 *)pos); + + kfree(ap_custom_ie); + return ret; +} + +/* This function checks if the vendor specified IE is present in passed buffer + * and copies it to mwifiex_ie structure. + * Function takes pointer to struct mwifiex_ie pointer as argument. + * If the vendor specified IE is present then memory is allocated for + * mwifiex_ie pointer and filled in with IE. Caller should take care of freeing + * this memory. + */ +static int mwifiex_update_vs_ie(const u8 *ies, int ies_len, + struct mwifiex_ie **ie_ptr, u16 mask, + unsigned int oui, u8 oui_type) +{ + struct ieee_types_header *vs_ie; + struct mwifiex_ie *ie = *ie_ptr; + const u8 *vendor_ie; + + vendor_ie = cfg80211_find_vendor_ie(oui, oui_type, ies, ies_len); + if (vendor_ie) { + if (!*ie_ptr) { + *ie_ptr = kzalloc(sizeof(struct mwifiex_ie), + GFP_KERNEL); + if (!*ie_ptr) + return -ENOMEM; + ie = *ie_ptr; + } + + vs_ie = (struct ieee_types_header *)vendor_ie; + memcpy(ie->ie_buffer + le16_to_cpu(ie->ie_length), + vs_ie, vs_ie->len + 2); + le16_add_cpu(&ie->ie_length, vs_ie->len + 2); + ie->mgmt_subtype_mask = cpu_to_le16(mask); + ie->ie_index = cpu_to_le16(MWIFIEX_AUTO_IDX_MASK); + } + + *ie_ptr = ie; + return 0; +} + +/* This function parses beacon IEs, probe response IEs, association response IEs + * from cfg80211_ap_settings->beacon and sets these IE to FW. + */ +static int mwifiex_set_mgmt_beacon_data_ies(struct mwifiex_private *priv, + struct cfg80211_beacon_data *data) +{ + struct mwifiex_ie *beacon_ie = NULL, *pr_ie = NULL, *ar_ie = NULL; + u16 beacon_idx = MWIFIEX_AUTO_IDX_MASK, pr_idx = MWIFIEX_AUTO_IDX_MASK; + u16 ar_idx = MWIFIEX_AUTO_IDX_MASK; + int ret = 0; + + if (data->beacon_ies && data->beacon_ies_len) { + mwifiex_update_vs_ie(data->beacon_ies, data->beacon_ies_len, + &beacon_ie, MGMT_MASK_BEACON, + WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPS); + mwifiex_update_vs_ie(data->beacon_ies, data->beacon_ies_len, + &beacon_ie, MGMT_MASK_BEACON, + WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P); + } + + if (data->proberesp_ies && data->proberesp_ies_len) { + mwifiex_update_vs_ie(data->proberesp_ies, + data->proberesp_ies_len, &pr_ie, + MGMT_MASK_PROBE_RESP, WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPS); + mwifiex_update_vs_ie(data->proberesp_ies, + data->proberesp_ies_len, &pr_ie, + MGMT_MASK_PROBE_RESP, + WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P); + } + + if (data->assocresp_ies && data->assocresp_ies_len) { + mwifiex_update_vs_ie(data->assocresp_ies, + data->assocresp_ies_len, &ar_ie, + MGMT_MASK_ASSOC_RESP | + MGMT_MASK_REASSOC_RESP, + WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPS); + mwifiex_update_vs_ie(data->assocresp_ies, + data->assocresp_ies_len, &ar_ie, + MGMT_MASK_ASSOC_RESP | + MGMT_MASK_REASSOC_RESP, WLAN_OUI_WFA, + WLAN_OUI_TYPE_WFA_P2P); + } + + if (beacon_ie || pr_ie || ar_ie) { + ret = mwifiex_update_uap_custom_ie(priv, beacon_ie, + &beacon_idx, pr_ie, + &pr_idx, ar_ie, &ar_idx); + if (ret) + goto done; + } + + priv->beacon_idx = beacon_idx; + priv->proberesp_idx = pr_idx; + priv->assocresp_idx = ar_idx; + +done: + kfree(beacon_ie); + kfree(pr_ie); + kfree(ar_ie); + + return ret; +} + +/* This function parses different IEs-tail IEs, beacon IEs, probe response IEs, + * association response IEs from cfg80211_ap_settings function and sets these IE + * to FW. + */ +int mwifiex_set_mgmt_ies(struct mwifiex_private *priv, + struct cfg80211_beacon_data *info) +{ + struct mwifiex_ie *gen_ie; + struct ieee_types_header *rsn_ie, *wpa_ie = NULL; + u16 rsn_idx = MWIFIEX_AUTO_IDX_MASK, ie_len = 0; + const u8 *vendor_ie; + + if (info->tail && info->tail_len) { + gen_ie = kzalloc(sizeof(struct mwifiex_ie), GFP_KERNEL); + if (!gen_ie) + return -ENOMEM; + gen_ie->ie_index = cpu_to_le16(rsn_idx); + gen_ie->mgmt_subtype_mask = cpu_to_le16(MGMT_MASK_BEACON | + MGMT_MASK_PROBE_RESP | + MGMT_MASK_ASSOC_RESP); + + rsn_ie = (void *)cfg80211_find_ie(WLAN_EID_RSN, + info->tail, info->tail_len); + if (rsn_ie) { + memcpy(gen_ie->ie_buffer, rsn_ie, rsn_ie->len + 2); + ie_len = rsn_ie->len + 2; + gen_ie->ie_length = cpu_to_le16(ie_len); + } + + vendor_ie = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WPA, + info->tail, + info->tail_len); + if (vendor_ie) { + wpa_ie = (struct ieee_types_header *)vendor_ie; + memcpy(gen_ie->ie_buffer + ie_len, + wpa_ie, wpa_ie->len + 2); + ie_len += wpa_ie->len + 2; + gen_ie->ie_length = cpu_to_le16(ie_len); + } + + if (rsn_ie || wpa_ie) { + if (mwifiex_update_uap_custom_ie(priv, gen_ie, &rsn_idx, + NULL, NULL, + NULL, NULL)) { + kfree(gen_ie); + return -1; + } + priv->rsn_idx = rsn_idx; + } + + kfree(gen_ie); + } + + return mwifiex_set_mgmt_beacon_data_ies(priv, info); +} + +/* This function removes management IE set */ +int mwifiex_del_mgmt_ies(struct mwifiex_private *priv) +{ + struct mwifiex_ie *beacon_ie = NULL, *pr_ie = NULL; + struct mwifiex_ie *ar_ie = NULL, *rsn_ie = NULL; + int ret = 0; + + if (priv->rsn_idx != MWIFIEX_AUTO_IDX_MASK) { + rsn_ie = kmalloc(sizeof(struct mwifiex_ie), GFP_KERNEL); + if (!rsn_ie) + return -ENOMEM; + + rsn_ie->ie_index = cpu_to_le16(priv->rsn_idx); + rsn_ie->mgmt_subtype_mask = cpu_to_le16(MWIFIEX_DELETE_MASK); + rsn_ie->ie_length = 0; + if (mwifiex_update_uap_custom_ie(priv, rsn_ie, &priv->rsn_idx, + NULL, &priv->proberesp_idx, + NULL, &priv->assocresp_idx)) { + ret = -1; + goto done; + } + + priv->rsn_idx = MWIFIEX_AUTO_IDX_MASK; + } + + if (priv->beacon_idx != MWIFIEX_AUTO_IDX_MASK) { + beacon_ie = kmalloc(sizeof(struct mwifiex_ie), GFP_KERNEL); + if (!beacon_ie) { + ret = -ENOMEM; + goto done; + } + beacon_ie->ie_index = cpu_to_le16(priv->beacon_idx); + beacon_ie->mgmt_subtype_mask = cpu_to_le16(MWIFIEX_DELETE_MASK); + beacon_ie->ie_length = 0; + } + if (priv->proberesp_idx != MWIFIEX_AUTO_IDX_MASK) { + pr_ie = kmalloc(sizeof(struct mwifiex_ie), GFP_KERNEL); + if (!pr_ie) { + ret = -ENOMEM; + goto done; + } + pr_ie->ie_index = cpu_to_le16(priv->proberesp_idx); + pr_ie->mgmt_subtype_mask = cpu_to_le16(MWIFIEX_DELETE_MASK); + pr_ie->ie_length = 0; + } + if (priv->assocresp_idx != MWIFIEX_AUTO_IDX_MASK) { + ar_ie = kmalloc(sizeof(struct mwifiex_ie), GFP_KERNEL); + if (!ar_ie) { + ret = -ENOMEM; + goto done; + } + ar_ie->ie_index = cpu_to_le16(priv->assocresp_idx); + ar_ie->mgmt_subtype_mask = cpu_to_le16(MWIFIEX_DELETE_MASK); + ar_ie->ie_length = 0; + } + + if (beacon_ie || pr_ie || ar_ie) + ret = mwifiex_update_uap_custom_ie(priv, + beacon_ie, &priv->beacon_idx, + pr_ie, &priv->proberesp_idx, + ar_ie, &priv->assocresp_idx); + +done: + kfree(beacon_ie); + kfree(pr_ie); + kfree(ar_ie); + kfree(rsn_ie); + + return ret; +} diff --git a/drivers/net/wireless/mwifiex/init.c b/drivers/net/wireless/mwifiex/init.c new file mode 100644 index 0000000..5bd1feb --- /dev/null +++ b/drivers/net/wireless/mwifiex/init.c @@ -0,0 +1,811 @@ +/* + * Marvell Wireless LAN device driver: HW/FW Initialization + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" + +/* + * This function adds a BSS priority table to the table list. + * + * The function allocates a new BSS priority table node and adds it to + * the end of BSS priority table list, kept in driver memory. + */ +static int mwifiex_add_bss_prio_tbl(struct mwifiex_private *priv) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct mwifiex_bss_prio_node *bss_prio; + struct mwifiex_bss_prio_tbl *tbl = adapter->bss_prio_tbl; + unsigned long flags; + + bss_prio = kzalloc(sizeof(struct mwifiex_bss_prio_node), GFP_KERNEL); + if (!bss_prio) + return -ENOMEM; + + bss_prio->priv = priv; + INIT_LIST_HEAD(&bss_prio->list); + + spin_lock_irqsave(&tbl[priv->bss_priority].bss_prio_lock, flags); + list_add_tail(&bss_prio->list, &tbl[priv->bss_priority].bss_prio_head); + spin_unlock_irqrestore(&tbl[priv->bss_priority].bss_prio_lock, flags); + + return 0; +} + +static void scan_delay_timer_fn(unsigned long data) +{ + struct mwifiex_private *priv = (struct mwifiex_private *)data; + struct mwifiex_adapter *adapter = priv->adapter; + struct cmd_ctrl_node *cmd_node, *tmp_node; + unsigned long flags; + + if (adapter->scan_delay_cnt == MWIFIEX_MAX_SCAN_DELAY_CNT) { + /* + * Abort scan operation by cancelling all pending scan + * commands + */ + spin_lock_irqsave(&adapter->scan_pending_q_lock, flags); + list_for_each_entry_safe(cmd_node, tmp_node, + &adapter->scan_pending_q, list) { + list_del(&cmd_node->list); + mwifiex_insert_cmd_to_free_q(adapter, cmd_node); + } + spin_unlock_irqrestore(&adapter->scan_pending_q_lock, flags); + + spin_lock_irqsave(&adapter->mwifiex_cmd_lock, flags); + adapter->scan_processing = false; + adapter->scan_delay_cnt = 0; + adapter->empty_tx_q_cnt = 0; + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, flags); + + if (priv->user_scan_cfg) { + if (priv->scan_request) { + dev_dbg(priv->adapter->dev, + "info: aborting scan\n"); + cfg80211_scan_done(priv->scan_request, 1); + priv->scan_request = NULL; + } else { + dev_dbg(priv->adapter->dev, + "info: scan already aborted\n"); + } + + kfree(priv->user_scan_cfg); + priv->user_scan_cfg = NULL; + } + goto done; + } + + if (!atomic_read(&priv->adapter->is_tx_received)) { + adapter->empty_tx_q_cnt++; + if (adapter->empty_tx_q_cnt == MWIFIEX_MAX_EMPTY_TX_Q_CNT) { + /* + * No Tx traffic for 200msec. Get scan command from + * scan pending queue and put to cmd pending queue to + * resume scan operation + */ + adapter->scan_delay_cnt = 0; + adapter->empty_tx_q_cnt = 0; + spin_lock_irqsave(&adapter->scan_pending_q_lock, flags); + cmd_node = list_first_entry(&adapter->scan_pending_q, + struct cmd_ctrl_node, list); + list_del(&cmd_node->list); + spin_unlock_irqrestore(&adapter->scan_pending_q_lock, + flags); + + mwifiex_insert_cmd_to_pending_q(adapter, cmd_node, + true); + queue_work(adapter->workqueue, &adapter->main_work); + goto done; + } + } else { + adapter->empty_tx_q_cnt = 0; + } + + /* Delay scan operation further by 20msec */ + mod_timer(&priv->scan_delay_timer, jiffies + + msecs_to_jiffies(MWIFIEX_SCAN_DELAY_MSEC)); + adapter->scan_delay_cnt++; + +done: + if (atomic_read(&priv->adapter->is_tx_received)) + atomic_set(&priv->adapter->is_tx_received, false); + + return; +} + +/* + * This function initializes the private structure and sets default + * values to the members. + * + * Additionally, it also initializes all the locks and sets up all the + * lists. + */ +int mwifiex_init_priv(struct mwifiex_private *priv) +{ + u32 i; + + priv->media_connected = false; + memset(priv->curr_addr, 0xff, ETH_ALEN); + + priv->pkt_tx_ctrl = 0; + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + priv->data_rate = 0; /* Initially indicate the rate as auto */ + priv->is_data_rate_auto = true; + priv->bcn_avg_factor = DEFAULT_BCN_AVG_FACTOR; + priv->data_avg_factor = DEFAULT_DATA_AVG_FACTOR; + + priv->sec_info.wep_enabled = 0; + priv->sec_info.authentication_mode = NL80211_AUTHTYPE_OPEN_SYSTEM; + priv->sec_info.encryption_mode = 0; + for (i = 0; i < ARRAY_SIZE(priv->wep_key); i++) + memset(&priv->wep_key[i], 0, sizeof(struct mwifiex_wep_key)); + priv->wep_key_curr_index = 0; + priv->curr_pkt_filter = HostCmd_ACT_MAC_RX_ON | HostCmd_ACT_MAC_TX_ON | + HostCmd_ACT_MAC_ETHERNETII_ENABLE; + + priv->beacon_period = 100; /* beacon interval */ ; + priv->attempted_bss_desc = NULL; + memset(&priv->curr_bss_params, 0, sizeof(priv->curr_bss_params)); + priv->listen_interval = MWIFIEX_DEFAULT_LISTEN_INTERVAL; + + memset(&priv->prev_ssid, 0, sizeof(priv->prev_ssid)); + memset(&priv->prev_bssid, 0, sizeof(priv->prev_bssid)); + memset(&priv->assoc_rsp_buf, 0, sizeof(priv->assoc_rsp_buf)); + priv->assoc_rsp_size = 0; + priv->adhoc_channel = DEFAULT_AD_HOC_CHANNEL; + priv->atim_window = 0; + priv->adhoc_state = ADHOC_IDLE; + priv->tx_power_level = 0; + priv->max_tx_power_level = 0; + priv->min_tx_power_level = 0; + priv->tx_rate = 0; + priv->rxpd_htinfo = 0; + priv->rxpd_rate = 0; + priv->rate_bitmap = 0; + priv->data_rssi_last = 0; + priv->data_rssi_avg = 0; + priv->data_nf_avg = 0; + priv->data_nf_last = 0; + priv->bcn_rssi_last = 0; + priv->bcn_rssi_avg = 0; + priv->bcn_nf_avg = 0; + priv->bcn_nf_last = 0; + memset(&priv->wpa_ie, 0, sizeof(priv->wpa_ie)); + memset(&priv->aes_key, 0, sizeof(priv->aes_key)); + priv->wpa_ie_len = 0; + priv->wpa_is_gtk_set = false; + + memset(&priv->assoc_tlv_buf, 0, sizeof(priv->assoc_tlv_buf)); + priv->assoc_tlv_buf_len = 0; + memset(&priv->wps, 0, sizeof(priv->wps)); + memset(&priv->gen_ie_buf, 0, sizeof(priv->gen_ie_buf)); + priv->gen_ie_buf_len = 0; + memset(priv->vs_ie, 0, sizeof(priv->vs_ie)); + + priv->wmm_required = true; + priv->wmm_enabled = false; + priv->wmm_qosinfo = 0; + priv->curr_bcn_buf = NULL; + priv->curr_bcn_size = 0; + priv->wps_ie = NULL; + priv->wps_ie_len = 0; + priv->ap_11n_enabled = 0; + memset(&priv->roc_cfg, 0, sizeof(priv->roc_cfg)); + + priv->scan_block = false; + + setup_timer(&priv->scan_delay_timer, scan_delay_timer_fn, + (unsigned long)priv); + + return mwifiex_add_bss_prio_tbl(priv); +} + +/* + * This function allocates buffers for members of the adapter + * structure. + * + * The memory allocated includes scan table, command buffers, and + * sleep confirm command buffer. In addition, the queues are + * also initialized. + */ +static int mwifiex_allocate_adapter(struct mwifiex_adapter *adapter) +{ + int ret; + + /* Allocate command buffer */ + ret = mwifiex_alloc_cmd_buffer(adapter); + if (ret) { + dev_err(adapter->dev, "%s: failed to alloc cmd buffer\n", + __func__); + return -1; + } + + adapter->sleep_cfm = + dev_alloc_skb(sizeof(struct mwifiex_opt_sleep_confirm) + + INTF_HEADER_LEN); + + if (!adapter->sleep_cfm) { + dev_err(adapter->dev, "%s: failed to alloc sleep cfm" + " cmd buffer\n", __func__); + return -1; + } + skb_reserve(adapter->sleep_cfm, INTF_HEADER_LEN); + + return 0; +} + +/* + * This function initializes the adapter structure and sets default + * values to the members of adapter. + * + * This also initializes the WMM related parameters in the driver private + * structures. + */ +static void mwifiex_init_adapter(struct mwifiex_adapter *adapter) +{ + struct mwifiex_opt_sleep_confirm *sleep_cfm_buf = NULL; + + skb_put(adapter->sleep_cfm, sizeof(struct mwifiex_opt_sleep_confirm)); + + adapter->cmd_sent = false; + + if (adapter->iface_type == MWIFIEX_SDIO) + adapter->data_sent = true; + else + adapter->data_sent = false; + + adapter->cmd_resp_received = false; + adapter->event_received = false; + adapter->data_received = false; + + adapter->surprise_removed = false; + + adapter->hw_status = MWIFIEX_HW_STATUS_INITIALIZING; + + adapter->ps_mode = MWIFIEX_802_11_POWER_MODE_CAM; + adapter->ps_state = PS_STATE_AWAKE; + adapter->need_to_wakeup = false; + + adapter->scan_mode = HostCmd_BSS_MODE_ANY; + adapter->specific_scan_time = MWIFIEX_SPECIFIC_SCAN_CHAN_TIME; + adapter->active_scan_time = MWIFIEX_ACTIVE_SCAN_CHAN_TIME; + adapter->passive_scan_time = MWIFIEX_PASSIVE_SCAN_CHAN_TIME; + + adapter->scan_probes = 1; + + adapter->multiple_dtim = 1; + + adapter->local_listen_interval = 0; /* default value in firmware + will be used */ + + adapter->is_deep_sleep = false; + + adapter->delay_null_pkt = false; + adapter->delay_to_ps = 1000; + adapter->enhanced_ps_mode = PS_MODE_AUTO; + + adapter->gen_null_pkt = false; /* Disable NULL Pkg generation by + default */ + adapter->pps_uapsd_mode = false; /* Disable pps/uapsd mode by + default */ + adapter->pm_wakeup_card_req = false; + + adapter->pm_wakeup_fw_try = false; + + adapter->tx_buf_size = MWIFIEX_TX_DATA_BUF_SIZE_2K; + adapter->curr_tx_buf_size = MWIFIEX_TX_DATA_BUF_SIZE_2K; + + adapter->is_hs_configured = false; + adapter->hs_cfg.conditions = cpu_to_le32(HS_CFG_COND_DEF); + adapter->hs_cfg.gpio = HS_CFG_GPIO_DEF; + adapter->hs_cfg.gap = HS_CFG_GAP_DEF; + adapter->hs_activated = false; + + memset(adapter->event_body, 0, sizeof(adapter->event_body)); + adapter->hw_dot_11n_dev_cap = 0; + adapter->hw_dev_mcs_support = 0; + adapter->sec_chan_offset = 0; + adapter->adhoc_11n_enabled = false; + + mwifiex_wmm_init(adapter); + + if (adapter->sleep_cfm) { + sleep_cfm_buf = (struct mwifiex_opt_sleep_confirm *) + adapter->sleep_cfm->data; + memset(sleep_cfm_buf, 0, adapter->sleep_cfm->len); + sleep_cfm_buf->command = + cpu_to_le16(HostCmd_CMD_802_11_PS_MODE_ENH); + sleep_cfm_buf->size = + cpu_to_le16(adapter->sleep_cfm->len); + sleep_cfm_buf->result = 0; + sleep_cfm_buf->action = cpu_to_le16(SLEEP_CONFIRM); + sleep_cfm_buf->resp_ctrl = cpu_to_le16(RESP_NEEDED); + } + memset(&adapter->sleep_params, 0, sizeof(adapter->sleep_params)); + memset(&adapter->sleep_period, 0, sizeof(adapter->sleep_period)); + adapter->tx_lock_flag = false; + adapter->null_pkt_interval = 0; + adapter->fw_bands = 0; + adapter->config_bands = 0; + adapter->adhoc_start_band = 0; + adapter->scan_channels = NULL; + adapter->fw_release_number = 0; + adapter->fw_cap_info = 0; + memset(&adapter->upld_buf, 0, sizeof(adapter->upld_buf)); + adapter->event_cause = 0; + adapter->region_code = 0; + adapter->bcn_miss_time_out = DEFAULT_BCN_MISS_TIMEOUT; + adapter->adhoc_awake_period = 0; + memset(&adapter->arp_filter, 0, sizeof(adapter->arp_filter)); + adapter->arp_filter_size = 0; + adapter->max_mgmt_ie_index = MAX_MGMT_IE_INDEX; + adapter->empty_tx_q_cnt = 0; +} + +/* + * This function sets trans_start per tx_queue + */ +void mwifiex_set_trans_start(struct net_device *dev) +{ + int i; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)) + for (i = 0; i < dev->num_tx_queues; i++) + netdev_get_tx_queue(dev, i)->trans_start = jiffies; +#endif + + dev->trans_start = jiffies; +} + +/* + * This function wakes up all queues in net_device + */ +void mwifiex_wake_up_net_dev_queue(struct net_device *netdev, + struct mwifiex_adapter *adapter) +{ + unsigned long dev_queue_flags; + unsigned int i; + + spin_lock_irqsave(&adapter->queue_lock, dev_queue_flags); + + for (i = 0; i < netdev->num_tx_queues; i++) { + struct netdev_queue *txq = netdev_get_tx_queue(netdev, i); + + if (netif_tx_queue_stopped(txq)) + netif_tx_wake_queue(txq); + } + + spin_unlock_irqrestore(&adapter->queue_lock, dev_queue_flags); +} + +/* + * This function stops all queues in net_device + */ +void mwifiex_stop_net_dev_queue(struct net_device *netdev, + struct mwifiex_adapter *adapter) +{ + unsigned long dev_queue_flags; + unsigned int i; + + spin_lock_irqsave(&adapter->queue_lock, dev_queue_flags); + + for (i = 0; i < netdev->num_tx_queues; i++) { + struct netdev_queue *txq = netdev_get_tx_queue(netdev, i); + + if (!netif_tx_queue_stopped(txq)) + netif_tx_stop_queue(txq); + } + + spin_unlock_irqrestore(&adapter->queue_lock, dev_queue_flags); +} + +/* + * This function releases the lock variables and frees the locks and + * associated locks. + */ +static void mwifiex_free_lock_list(struct mwifiex_adapter *adapter) +{ + struct mwifiex_private *priv; + s32 i, j; + + /* Free lists */ + list_del(&adapter->cmd_free_q); + list_del(&adapter->cmd_pending_q); + list_del(&adapter->scan_pending_q); + + for (i = 0; i < adapter->priv_num; i++) + list_del(&adapter->bss_prio_tbl[i].bss_prio_head); + + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i]) { + priv = adapter->priv[i]; + for (j = 0; j < MAX_NUM_TID; ++j) + list_del(&priv->wmm.tid_tbl_ptr[j].ra_list); + list_del(&priv->tx_ba_stream_tbl_ptr); + list_del(&priv->rx_reorder_tbl_ptr); + list_del(&priv->sta_list); + } + } +} + +/* + * This function frees the adapter structure. + * + * The freeing operation is done recursively, by canceling all + * pending commands, freeing the member buffers previously + * allocated (command buffers, scan table buffer, sleep confirm + * command buffer), stopping the timers and calling the cleanup + * routines for every interface, before the actual adapter + * structure is freed. + */ +static void +mwifiex_free_adapter(struct mwifiex_adapter *adapter) +{ + if (!adapter) { + pr_err("%s: adapter is NULL\n", __func__); + return; + } + + mwifiex_cancel_all_pending_cmd(adapter); + + /* Free lock variables */ + mwifiex_free_lock_list(adapter); + + /* Free command buffer */ + dev_dbg(adapter->dev, "info: free cmd buffer\n"); + mwifiex_free_cmd_buffer(adapter); + + del_timer(&adapter->cmd_timer); + + dev_dbg(adapter->dev, "info: free scan table\n"); + + if (adapter->if_ops.cleanup_if) + adapter->if_ops.cleanup_if(adapter); + + if (adapter->sleep_cfm) + dev_kfree_skb_any(adapter->sleep_cfm); +} + +/* + * This function intializes the lock variables and + * the list heads. + */ +int mwifiex_init_lock_list(struct mwifiex_adapter *adapter) +{ + struct mwifiex_private *priv; + s32 i, j; + + spin_lock_init(&adapter->mwifiex_lock); + spin_lock_init(&adapter->int_lock); + spin_lock_init(&adapter->main_proc_lock); + spin_lock_init(&adapter->mwifiex_cmd_lock); + spin_lock_init(&adapter->queue_lock); + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i]) { + priv = adapter->priv[i]; + spin_lock_init(&priv->rx_pkt_lock); + spin_lock_init(&priv->wmm.ra_list_spinlock); + spin_lock_init(&priv->curr_bcn_buf_lock); + spin_lock_init(&priv->sta_list_spinlock); + } + } + + /* Initialize cmd_free_q */ + INIT_LIST_HEAD(&adapter->cmd_free_q); + /* Initialize cmd_pending_q */ + INIT_LIST_HEAD(&adapter->cmd_pending_q); + /* Initialize scan_pending_q */ + INIT_LIST_HEAD(&adapter->scan_pending_q); + + spin_lock_init(&adapter->cmd_free_q_lock); + spin_lock_init(&adapter->cmd_pending_q_lock); + spin_lock_init(&adapter->scan_pending_q_lock); + + skb_queue_head_init(&adapter->usb_rx_data_q); + + for (i = 0; i < adapter->priv_num; ++i) { + INIT_LIST_HEAD(&adapter->bss_prio_tbl[i].bss_prio_head); + spin_lock_init(&adapter->bss_prio_tbl[i].bss_prio_lock); + } + + for (i = 0; i < adapter->priv_num; i++) { + if (!adapter->priv[i]) + continue; + priv = adapter->priv[i]; + for (j = 0; j < MAX_NUM_TID; ++j) + INIT_LIST_HEAD(&priv->wmm.tid_tbl_ptr[j].ra_list); + INIT_LIST_HEAD(&priv->tx_ba_stream_tbl_ptr); + INIT_LIST_HEAD(&priv->rx_reorder_tbl_ptr); + INIT_LIST_HEAD(&priv->sta_list); + + spin_lock_init(&priv->tx_ba_stream_tbl_lock); + spin_lock_init(&priv->rx_reorder_tbl_lock); + } + + return 0; +} + +/* + * This function initializes the firmware. + * + * The following operations are performed sequentially - + * - Allocate adapter structure + * - Initialize the adapter structure + * - Initialize the private structure + * - Add BSS priority tables to the adapter structure + * - For each interface, send the init commands to firmware + * - Send the first command in command pending queue, if available + */ +int mwifiex_init_fw(struct mwifiex_adapter *adapter) +{ + int ret; + struct mwifiex_private *priv; + u8 i, first_sta = true; + int is_cmd_pend_q_empty; + unsigned long flags; + + adapter->hw_status = MWIFIEX_HW_STATUS_INITIALIZING; + + /* Allocate memory for member of adapter structure */ + ret = mwifiex_allocate_adapter(adapter); + if (ret) + return -1; + + /* Initialize adapter structure */ + mwifiex_init_adapter(adapter); + + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i]) { + priv = adapter->priv[i]; + + /* Initialize private structure */ + ret = mwifiex_init_priv(priv); + if (ret) + return -1; + } + } + + if (adapter->if_ops.init_fw_port) { + if (adapter->if_ops.init_fw_port(adapter)) + return -1; + } + + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i]) { + ret = mwifiex_sta_init_cmd(adapter->priv[i], first_sta); + if (ret == -1) + return -1; + + first_sta = false; + } + } + + spin_lock_irqsave(&adapter->cmd_pending_q_lock, flags); + is_cmd_pend_q_empty = list_empty(&adapter->cmd_pending_q); + spin_unlock_irqrestore(&adapter->cmd_pending_q_lock, flags); + if (!is_cmd_pend_q_empty) { + /* Send the first command in queue and return */ + if (mwifiex_main_process(adapter) != -1) + ret = -EINPROGRESS; + } else { + adapter->hw_status = MWIFIEX_HW_STATUS_READY; + } + + return ret; +} + +/* + * This function deletes the BSS priority tables. + * + * The function traverses through all the allocated BSS priority nodes + * in every BSS priority table and frees them. + */ +static void mwifiex_delete_bss_prio_tbl(struct mwifiex_private *priv) +{ + int i; + struct mwifiex_adapter *adapter = priv->adapter; + struct mwifiex_bss_prio_node *bssprio_node, *tmp_node; + struct list_head *head; + spinlock_t *lock; /* bss priority lock */ + unsigned long flags; + + for (i = 0; i < adapter->priv_num; ++i) { + head = &adapter->bss_prio_tbl[i].bss_prio_head; + lock = &adapter->bss_prio_tbl[i].bss_prio_lock; + dev_dbg(adapter->dev, "info: delete BSS priority table," + " bss_type = %d, bss_num = %d, i = %d," + " head = %p\n", + priv->bss_type, priv->bss_num, i, head); + + { + spin_lock_irqsave(lock, flags); + if (list_empty(head)) { + spin_unlock_irqrestore(lock, flags); + continue; + } + list_for_each_entry_safe(bssprio_node, tmp_node, head, + list) { + if (bssprio_node->priv == priv) { + dev_dbg(adapter->dev, "info: Delete " + "node %p, next = %p\n", + bssprio_node, tmp_node); + list_del(&bssprio_node->list); + kfree(bssprio_node); + } + } + spin_unlock_irqrestore(lock, flags); + } + } +} + +/* + * This function frees the private structure, including cleans + * up the TX and RX queues and frees the BSS priority tables. + */ +void mwifiex_free_priv(struct mwifiex_private *priv) +{ + mwifiex_clean_txrx(priv); + mwifiex_delete_bss_prio_tbl(priv); + mwifiex_free_curr_bcn(priv); +} + +/* + * This function is used to shutdown the driver. + * + * The following operations are performed sequentially - + * - Check if already shut down + * - Make sure the main process has stopped + * - Clean up the Tx and Rx queues + * - Delete BSS priority tables + * - Free the adapter + * - Notify completion + */ +int +mwifiex_shutdown_drv(struct mwifiex_adapter *adapter) +{ + int ret = -EINPROGRESS; + struct mwifiex_private *priv; + s32 i; + unsigned long flags; + struct sk_buff *skb; + + /* mwifiex already shutdown */ + if (adapter->hw_status == MWIFIEX_HW_STATUS_NOT_READY) + return 0; + + adapter->hw_status = MWIFIEX_HW_STATUS_CLOSING; + /* wait for mwifiex_process to complete */ + if (adapter->mwifiex_processing) { + dev_warn(adapter->dev, "main process is still running\n"); + return ret; + } + + /* cancel current command */ + if (adapter->curr_cmd) { + dev_warn(adapter->dev, "curr_cmd is still in processing\n"); + del_timer(&adapter->cmd_timer); + mwifiex_recycle_cmd_node(adapter, adapter->curr_cmd); + adapter->curr_cmd = NULL; + } + + /* shut down mwifiex */ + dev_dbg(adapter->dev, "info: shutdown mwifiex...\n"); + + /* Clean up Tx/Rx queues and delete BSS priority table */ + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i]) { + priv = adapter->priv[i]; + + mwifiex_clean_txrx(priv); + mwifiex_delete_bss_prio_tbl(priv); + } + } + + spin_lock_irqsave(&adapter->mwifiex_lock, flags); + + if (adapter->if_ops.data_complete) { + while ((skb = skb_dequeue(&adapter->usb_rx_data_q))) { + struct mwifiex_rxinfo *rx_info = MWIFIEX_SKB_RXCB(skb); + + priv = adapter->priv[rx_info->bss_num]; + if (priv) + priv->stats.rx_dropped++; + + adapter->if_ops.data_complete(adapter, skb); + } + } + + /* Free adapter structure */ + mwifiex_free_adapter(adapter); + + spin_unlock_irqrestore(&adapter->mwifiex_lock, flags); + + /* Notify completion */ + ret = mwifiex_shutdown_fw_complete(adapter); + + return ret; +} + +/* + * This function downloads the firmware to the card. + * + * The actual download is preceded by two sanity checks - + * - Check if firmware is already running + * - Check if the interface is the winner to download the firmware + * + * ...and followed by another - + * - Check if the firmware is downloaded successfully + * + * After download is successfully completed, the host interrupts are enabled. + */ +int mwifiex_dnld_fw(struct mwifiex_adapter *adapter, + struct mwifiex_fw_image *pmfw) +{ + int ret; + u32 poll_num = 1; + + if (adapter->if_ops.check_fw_status) { + adapter->winner = 0; + + /* check if firmware is already running */ + ret = adapter->if_ops.check_fw_status(adapter, poll_num); + if (!ret) { + dev_notice(adapter->dev, + "WLAN FW already running! Skip FW dnld\n"); + goto done; + } + + poll_num = MAX_FIRMWARE_POLL_TRIES; + + /* check if we are the winner for downloading FW */ + if (!adapter->winner) { + dev_notice(adapter->dev, + "FW already running! Skip FW dnld\n"); + poll_num = MAX_MULTI_INTERFACE_POLL_TRIES; + goto poll_fw; + } + } + + if (pmfw) { + /* Download firmware with helper */ + ret = adapter->if_ops.prog_fw(adapter, pmfw); + if (ret) { + dev_err(adapter->dev, "prog_fw failed ret=%#x\n", ret); + return ret; + } + } + +poll_fw: + /* Check if the firmware is downloaded successfully or not */ + ret = adapter->if_ops.check_fw_status(adapter, poll_num); + if (ret) { + dev_err(adapter->dev, "FW failed to be active in time\n"); + return -1; + } +done: + /* re-enable host interrupt for mwifiex after fw dnld is successful */ + if (adapter->if_ops.enable_int) + adapter->if_ops.enable_int(adapter); + + return ret; +} diff --git a/drivers/net/wireless/mwifiex/ioctl.h b/drivers/net/wireless/mwifiex/ioctl.h new file mode 100644 index 0000000..7f27e45 --- /dev/null +++ b/drivers/net/wireless/mwifiex/ioctl.h @@ -0,0 +1,400 @@ +/* + * Marvell Wireless LAN device driver: ioctl data structures & APIs + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _MWIFIEX_IOCTL_H_ +#define _MWIFIEX_IOCTL_H_ + +#include + +enum { + MWIFIEX_SCAN_TYPE_UNCHANGED = 0, + MWIFIEX_SCAN_TYPE_ACTIVE, + MWIFIEX_SCAN_TYPE_PASSIVE +}; + +struct mwifiex_user_scan { + u32 scan_cfg_len; + u8 scan_cfg_buf[1]; +}; + +#define MWIFIEX_PROMISC_MODE 1 +#define MWIFIEX_MULTICAST_MODE 2 +#define MWIFIEX_ALL_MULTI_MODE 4 +#define MWIFIEX_MAX_MULTICAST_LIST_SIZE 32 + +struct mwifiex_multicast_list { + u32 mode; + u32 num_multicast_addr; + u8 mac_list[MWIFIEX_MAX_MULTICAST_LIST_SIZE][ETH_ALEN]; +}; + +struct mwifiex_chan_freq { + u32 channel; + u32 freq; +}; + +struct mwifiex_ssid_bssid { + struct cfg80211_ssid ssid; + u8 bssid[ETH_ALEN]; +}; + +enum { + BAND_B = 1, + BAND_G = 2, + BAND_A = 4, + BAND_GN = 8, + BAND_AN = 16, + BAND_GAC = 32, + BAND_AAC = 64, +}; + +#define MWIFIEX_WPA_PASSHPHRASE_LEN 64 +struct wpa_param { + u8 pairwise_cipher_wpa; + u8 pairwise_cipher_wpa2; + u8 group_cipher; + u32 length; + u8 passphrase[MWIFIEX_WPA_PASSHPHRASE_LEN]; +}; + +struct wep_key { + u8 key_index; + u8 is_default; + u16 length; + u8 key[WLAN_KEY_LEN_WEP104]; +}; + +#define KEY_MGMT_ON_HOST 0x03 +#define MWIFIEX_AUTH_MODE_AUTO 0xFF +#define BAND_CONFIG_BG 0x00 +#define BAND_CONFIG_A 0x01 +#define MWIFIEX_SUPPORTED_RATES 14 +#define MWIFIEX_SUPPORTED_RATES_EXT 32 + +struct mwifiex_uap_bss_param { + u8 channel; + u8 band_cfg; + u16 rts_threshold; + u16 frag_threshold; + u8 retry_limit; + struct mwifiex_802_11_ssid ssid; + u8 bcast_ssid_ctl; + u8 radio_ctl; + u8 dtim_period; + u16 beacon_period; + u16 auth_mode; + u16 protocol; + u16 key_mgmt; + u16 key_mgmt_operation; + struct wpa_param wpa_cfg; + struct wep_key wep_cfg[NUM_WEP_KEYS]; + struct ieee80211_ht_cap ht_cap; + struct ieee80211_vht_cap vht_cap; + u8 rates[MWIFIEX_SUPPORTED_RATES]; + u32 sta_ao_timer; + u32 ps_sta_ao_timer; + u8 qos_info; + struct mwifiex_types_wmm_info wmm_info; +}; + +enum { + ADHOC_IDLE, + ADHOC_STARTED, + ADHOC_JOINED, + ADHOC_COALESCED +}; + +struct mwifiex_ds_get_stats { + u32 mcast_tx_frame; + u32 failed; + u32 retry; + u32 multi_retry; + u32 frame_dup; + u32 rts_success; + u32 rts_failure; + u32 ack_failure; + u32 rx_frag; + u32 mcast_rx_frame; + u32 fcs_error; + u32 tx_frame; + u32 wep_icv_error[4]; +}; + +#define MWIFIEX_MAX_VER_STR_LEN 128 + +struct mwifiex_ver_ext { + u32 version_str_sel; + char version_str[MWIFIEX_MAX_VER_STR_LEN]; +}; + +struct mwifiex_bss_info { + u32 bss_mode; + struct cfg80211_ssid ssid; + u32 bss_chan; + u8 country_code[3]; + u32 media_connected; + u32 max_power_level; + u32 min_power_level; + u32 adhoc_state; + signed int bcn_nf_last; + u32 wep_status; + u32 is_hs_configured; + u32 is_deep_sleep; + u8 bssid[ETH_ALEN]; +}; + +#define MAX_NUM_TID 8 + +#define MAX_RX_WINSIZE 64 + +struct mwifiex_ds_rx_reorder_tbl { + u16 tid; + u8 ta[ETH_ALEN]; + u32 start_win; + u32 win_size; + u32 buffer[MAX_RX_WINSIZE]; +}; + +struct mwifiex_ds_tx_ba_stream_tbl { + u16 tid; + u8 ra[ETH_ALEN]; +}; + +#define DBG_CMD_NUM 5 + +struct mwifiex_debug_info { + u32 int_counter; + u32 packets_out[MAX_NUM_TID]; + u32 tx_buf_size; + u32 curr_tx_buf_size; + u32 tx_tbl_num; + struct mwifiex_ds_tx_ba_stream_tbl + tx_tbl[MWIFIEX_MAX_TX_BASTREAM_SUPPORTED]; + u32 rx_tbl_num; + struct mwifiex_ds_rx_reorder_tbl rx_tbl + [MWIFIEX_MAX_RX_BASTREAM_SUPPORTED]; + u16 ps_mode; + u32 ps_state; + u8 is_deep_sleep; + u8 pm_wakeup_card_req; + u32 pm_wakeup_fw_try; + u8 is_hs_configured; + u8 hs_activated; + u32 num_cmd_host_to_card_failure; + u32 num_cmd_sleep_cfm_host_to_card_failure; + u32 num_tx_host_to_card_failure; + u32 num_event_deauth; + u32 num_event_disassoc; + u32 num_event_link_lost; + u32 num_cmd_deauth; + u32 num_cmd_assoc_success; + u32 num_cmd_assoc_failure; + u32 num_tx_timeout; + u32 num_cmd_timeout; + u16 timeout_cmd_id; + u16 timeout_cmd_act; + u16 last_cmd_id[DBG_CMD_NUM]; + u16 last_cmd_act[DBG_CMD_NUM]; + u16 last_cmd_index; + u16 last_cmd_resp_id[DBG_CMD_NUM]; + u16 last_cmd_resp_index; + u16 last_event[DBG_CMD_NUM]; + u16 last_event_index; + u8 data_sent; + u8 cmd_sent; + u8 cmd_resp_received; + u8 event_received; +}; + +#define MWIFIEX_KEY_INDEX_UNICAST 0x40000000 +#define PN_LEN 16 + +struct mwifiex_ds_encrypt_key { + u32 key_disable; + u32 key_index; + u32 key_len; + u8 key_material[WLAN_MAX_KEY_LEN]; + u8 mac_addr[ETH_ALEN]; + u32 is_wapi_key; + u8 pn[PN_LEN]; /* packet number */ + u8 is_igtk_key; +}; + +struct mwifiex_power_cfg { + u32 is_power_auto; + u32 power_level; +}; + +struct mwifiex_ds_hs_cfg { + u32 is_invoke_hostcmd; + /* Bit0: non-unicast data + * Bit1: unicast data + * Bit2: mac events + * Bit3: magic packet + */ + u32 conditions; + u32 gpio; + u32 gap; +}; + +#define DEEP_SLEEP_ON 1 +#define DEEP_SLEEP_OFF 0 +#define DEEP_SLEEP_IDLE_TIME 100 +#define PS_MODE_AUTO 1 + +struct mwifiex_ds_auto_ds { + u16 auto_ds; + u16 idle_time; +}; + +struct mwifiex_ds_pm_cfg { + union { + u32 ps_mode; + struct mwifiex_ds_hs_cfg hs_cfg; + struct mwifiex_ds_auto_ds auto_deep_sleep; + u32 sleep_period; + } param; +}; + +struct mwifiex_11ac_vht_cfg { + u8 band_config; + u8 misc_config; + u32 cap_info; + u32 mcs_tx_set; + u32 mcs_rx_set; +}; + +struct mwifiex_ds_11n_tx_cfg { + u16 tx_htcap; + u16 tx_htinfo; + u16 misc_config; /* Needed for 802.11AC cards only */ +}; + +struct mwifiex_ds_11n_amsdu_aggr_ctrl { + u16 enable; + u16 curr_buf_size; +}; + +struct mwifiex_ds_ant_cfg { + u32 tx_ant; + u32 rx_ant; +}; + +#define MWIFIEX_NUM_OF_CMD_BUFFER 20 +#define MWIFIEX_SIZE_OF_CMD_BUFFER 2048 + +enum { + MWIFIEX_IE_TYPE_GEN_IE = 0, + MWIFIEX_IE_TYPE_ARP_FILTER, +}; + +enum { + MWIFIEX_REG_MAC = 1, + MWIFIEX_REG_BBP, + MWIFIEX_REG_RF, + MWIFIEX_REG_PMIC, + MWIFIEX_REG_CAU, +}; + +struct mwifiex_ds_reg_rw { + __le32 type; + __le32 offset; + __le32 value; +}; + +#define MAX_EEPROM_DATA 256 + +struct mwifiex_ds_read_eeprom { + __le16 offset; + __le16 byte_count; + u8 value[MAX_EEPROM_DATA]; +}; + +#define IEEE_MAX_IE_SIZE 256 + +#define MWIFIEX_IE_HDR_SIZE (sizeof(struct mwifiex_ie) - IEEE_MAX_IE_SIZE) + +struct mwifiex_ds_misc_gen_ie { + u32 type; + u32 len; + u8 ie_data[IEEE_MAX_IE_SIZE]; +}; + +struct mwifiex_ds_misc_cmd { + u32 len; + u8 cmd[MWIFIEX_SIZE_OF_CMD_BUFFER]; +}; + +#define BITMASK_BCN_RSSI_LOW BIT(0) +#define BITMASK_BCN_RSSI_HIGH BIT(4) + +enum subsc_evt_rssi_state { + EVENT_HANDLED, + RSSI_LOW_RECVD, + RSSI_HIGH_RECVD +}; + +struct subsc_evt_cfg { + u8 abs_value; + u8 evt_freq; +}; + +struct mwifiex_ds_misc_subsc_evt { + u16 action; + u16 events; + struct subsc_evt_cfg bcn_l_rssi_cfg; + struct subsc_evt_cfg bcn_h_rssi_cfg; +}; + +#define MAX_BYTESEQ 6 /* non-adjustable */ +#define MWIFIEX_MAX_FILTERS 10 + +struct mwifiex_mef_filter { + u16 repeat; + u16 offset; + s8 byte_seq[MAX_BYTESEQ + 1]; + u8 filt_type; + u8 filt_action; +}; + +struct mwifiex_mef_entry { + u8 mode; + u8 action; + struct mwifiex_mef_filter filter[MWIFIEX_MAX_FILTERS]; +}; + +struct mwifiex_ds_mef_cfg { + u32 criteria; + u16 num_entries; + struct mwifiex_mef_entry *mef_entry; +}; + +#define MWIFIEX_MAX_VSIE_LEN (256) +#define MWIFIEX_MAX_VSIE_NUM (8) +#define MWIFIEX_VSIE_MASK_CLEAR 0x00 +#define MWIFIEX_VSIE_MASK_SCAN 0x01 +#define MWIFIEX_VSIE_MASK_ASSOC 0x02 +#define MWIFIEX_VSIE_MASK_ADHOC 0x04 + +enum { + MWIFIEX_FUNC_INIT = 1, + MWIFIEX_FUNC_SHUTDOWN, +}; + +#endif /* !_MWIFIEX_IOCTL_H_ */ diff --git a/drivers/net/wireless/mwifiex/join.c b/drivers/net/wireless/mwifiex/join.c new file mode 100644 index 0000000..96bda6c --- /dev/null +++ b/drivers/net/wireless/mwifiex/join.c @@ -0,0 +1,1463 @@ +/* + * Marvell Wireless LAN device driver: association and ad-hoc start/join + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" +#include "11ac.h" + +#define CAPINFO_MASK (~(BIT(15) | BIT(14) | BIT(12) | BIT(11) | BIT(9))) + +/* + * Append a generic IE as a pass through TLV to a TLV buffer. + * + * This function is called from the network join command preparation routine. + * + * If the IE buffer has been setup by the application, this routine appends + * the buffer as a pass through TLV type to the request. + */ +static int +mwifiex_cmd_append_generic_ie(struct mwifiex_private *priv, u8 **buffer) +{ + int ret_len = 0; + struct mwifiex_ie_types_header ie_header; + + /* Null Checks */ + if (!buffer) + return 0; + if (!(*buffer)) + return 0; + + /* + * If there is a generic ie buffer setup, append it to the return + * parameter buffer pointer. + */ + if (priv->gen_ie_buf_len) { + dev_dbg(priv->adapter->dev, + "info: %s: append generic ie len %d to %p\n", + __func__, priv->gen_ie_buf_len, *buffer); + + /* Wrap the generic IE buffer with a pass through TLV type */ + ie_header.type = cpu_to_le16(TLV_TYPE_PASSTHROUGH); + ie_header.len = cpu_to_le16(priv->gen_ie_buf_len); + memcpy(*buffer, &ie_header, sizeof(ie_header)); + + /* Increment the return size and the return buffer pointer + param */ + *buffer += sizeof(ie_header); + ret_len += sizeof(ie_header); + + /* Copy the generic IE buffer to the output buffer, advance + pointer */ + memcpy(*buffer, priv->gen_ie_buf, priv->gen_ie_buf_len); + + /* Increment the return size and the return buffer pointer + param */ + *buffer += priv->gen_ie_buf_len; + ret_len += priv->gen_ie_buf_len; + + /* Reset the generic IE buffer */ + priv->gen_ie_buf_len = 0; + } + + /* return the length appended to the buffer */ + return ret_len; +} + +/* + * Append TSF tracking info from the scan table for the target AP. + * + * This function is called from the network join command preparation routine. + * + * The TSF table TSF sent to the firmware contains two TSF values: + * - The TSF of the target AP from its previous beacon/probe response + * - The TSF timestamp of our local MAC at the time we observed the + * beacon/probe response. + * + * The firmware uses the timestamp values to set an initial TSF value + * in the MAC for the new association after a reassociation attempt. + */ +static int +mwifiex_cmd_append_tsf_tlv(struct mwifiex_private *priv, u8 **buffer, + struct mwifiex_bssdescriptor *bss_desc) +{ + struct mwifiex_ie_types_tsf_timestamp tsf_tlv; + __le64 tsf_val; + + /* Null Checks */ + if (buffer == NULL) + return 0; + if (*buffer == NULL) + return 0; + + memset(&tsf_tlv, 0x00, sizeof(struct mwifiex_ie_types_tsf_timestamp)); + + tsf_tlv.header.type = cpu_to_le16(TLV_TYPE_TSFTIMESTAMP); + tsf_tlv.header.len = cpu_to_le16(2 * sizeof(tsf_val)); + + memcpy(*buffer, &tsf_tlv, sizeof(tsf_tlv.header)); + *buffer += sizeof(tsf_tlv.header); + + /* TSF at the time when beacon/probe_response was received */ + tsf_val = cpu_to_le64(bss_desc->fw_tsf); + memcpy(*buffer, &tsf_val, sizeof(tsf_val)); + *buffer += sizeof(tsf_val); + + tsf_val = cpu_to_le64(bss_desc->timestamp); + + dev_dbg(priv->adapter->dev, + "info: %s: TSF offset calc: %016llx - %016llx\n", + __func__, bss_desc->timestamp, bss_desc->fw_tsf); + + memcpy(*buffer, &tsf_val, sizeof(tsf_val)); + *buffer += sizeof(tsf_val); + + return sizeof(tsf_tlv.header) + (2 * sizeof(tsf_val)); +} + +/* + * This function finds out the common rates between rate1 and rate2. + * + * It will fill common rates in rate1 as output if found. + * + * NOTE: Setting the MSB of the basic rates needs to be taken + * care of, either before or after calling this function. + */ +static int mwifiex_get_common_rates(struct mwifiex_private *priv, u8 *rate1, + u32 rate1_size, u8 *rate2, u32 rate2_size) +{ + int ret; + u8 *ptr = rate1, *tmp; + u32 i, j; + + tmp = kmemdup(rate1, rate1_size, GFP_KERNEL); + if (!tmp) { + dev_err(priv->adapter->dev, "failed to alloc tmp buf\n"); + return -ENOMEM; + } + + memset(rate1, 0, rate1_size); + + for (i = 0; i < rate2_size && rate2[i]; i++) { + for (j = 0; j < rate1_size && tmp[j]; j++) { + /* Check common rate, excluding the bit for + basic rate */ + if ((rate2[i] & 0x7F) == (tmp[j] & 0x7F)) { + *rate1++ = tmp[j]; + break; + } + } + } + + dev_dbg(priv->adapter->dev, "info: Tx data rate set to %#x\n", + priv->data_rate); + + if (!priv->is_data_rate_auto) { + while (*ptr) { + if ((*ptr & 0x7f) == priv->data_rate) { + ret = 0; + goto done; + } + ptr++; + } + dev_err(priv->adapter->dev, "previously set fixed data rate %#x" + " is not compatible with the network\n", + priv->data_rate); + + ret = -1; + goto done; + } + + ret = 0; +done: + kfree(tmp); + return ret; +} + +/* + * This function creates the intersection of the rates supported by a + * target BSS and our adapter settings for use in an assoc/join command. + */ +static int +mwifiex_setup_rates_from_bssdesc(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc, + u8 *out_rates, u32 *out_rates_size) +{ + u8 card_rates[MWIFIEX_SUPPORTED_RATES]; + u32 card_rates_size; + + /* Copy AP supported rates */ + memcpy(out_rates, bss_desc->supported_rates, MWIFIEX_SUPPORTED_RATES); + /* Get the STA supported rates */ + card_rates_size = mwifiex_get_active_data_rates(priv, card_rates); + /* Get the common rates between AP and STA supported rates */ + if (mwifiex_get_common_rates(priv, out_rates, MWIFIEX_SUPPORTED_RATES, + card_rates, card_rates_size)) { + *out_rates_size = 0; + dev_err(priv->adapter->dev, "%s: cannot get common rates\n", + __func__); + return -1; + } + + *out_rates_size = + min_t(size_t, strlen(out_rates), MWIFIEX_SUPPORTED_RATES); + + return 0; +} + +/* + * This function appends a WPS IE. It is called from the network join command + * preparation routine. + * + * If the IE buffer has been setup by the application, this routine appends + * the buffer as a WPS TLV type to the request. + */ +static int +mwifiex_cmd_append_wps_ie(struct mwifiex_private *priv, u8 **buffer) +{ + int retLen = 0; + struct mwifiex_ie_types_header ie_header; + + if (!buffer || !*buffer) + return 0; + + /* + * If there is a wps ie buffer setup, append it to the return + * parameter buffer pointer. + */ + if (priv->wps_ie_len) { + dev_dbg(priv->adapter->dev, "cmd: append wps ie %d to %p\n", + priv->wps_ie_len, *buffer); + + /* Wrap the generic IE buffer with a pass through TLV type */ + ie_header.type = cpu_to_le16(TLV_TYPE_MGMT_IE); + ie_header.len = cpu_to_le16(priv->wps_ie_len); + memcpy(*buffer, &ie_header, sizeof(ie_header)); + *buffer += sizeof(ie_header); + retLen += sizeof(ie_header); + + memcpy(*buffer, priv->wps_ie, priv->wps_ie_len); + *buffer += priv->wps_ie_len; + retLen += priv->wps_ie_len; + + } + + kfree(priv->wps_ie); + priv->wps_ie_len = 0; + return retLen; +} + +/* + * This function appends a WAPI IE. + * + * This function is called from the network join command preparation routine. + * + * If the IE buffer has been setup by the application, this routine appends + * the buffer as a WAPI TLV type to the request. + */ +static int +mwifiex_cmd_append_wapi_ie(struct mwifiex_private *priv, u8 **buffer) +{ + int retLen = 0; + struct mwifiex_ie_types_header ie_header; + + /* Null Checks */ + if (buffer == NULL) + return 0; + if (*buffer == NULL) + return 0; + + /* + * If there is a wapi ie buffer setup, append it to the return + * parameter buffer pointer. + */ + if (priv->wapi_ie_len) { + dev_dbg(priv->adapter->dev, "cmd: append wapi ie %d to %p\n", + priv->wapi_ie_len, *buffer); + + /* Wrap the generic IE buffer with a pass through TLV type */ + ie_header.type = cpu_to_le16(TLV_TYPE_WAPI_IE); + ie_header.len = cpu_to_le16(priv->wapi_ie_len); + memcpy(*buffer, &ie_header, sizeof(ie_header)); + + /* Increment the return size and the return buffer pointer + param */ + *buffer += sizeof(ie_header); + retLen += sizeof(ie_header); + + /* Copy the wapi IE buffer to the output buffer, advance + pointer */ + memcpy(*buffer, priv->wapi_ie, priv->wapi_ie_len); + + /* Increment the return size and the return buffer pointer + param */ + *buffer += priv->wapi_ie_len; + retLen += priv->wapi_ie_len; + + } + /* return the length appended to the buffer */ + return retLen; +} + +/* + * This function appends rsn ie tlv for wpa/wpa2 security modes. + * It is called from the network join command preparation routine. + */ +static int mwifiex_append_rsn_ie_wpa_wpa2(struct mwifiex_private *priv, + u8 **buffer) +{ + struct mwifiex_ie_types_rsn_param_set *rsn_ie_tlv; + int rsn_ie_len; + + if (!buffer || !(*buffer)) + return 0; + + rsn_ie_tlv = (struct mwifiex_ie_types_rsn_param_set *) (*buffer); + rsn_ie_tlv->header.type = cpu_to_le16((u16) priv->wpa_ie[0]); + rsn_ie_tlv->header.type = cpu_to_le16( + le16_to_cpu(rsn_ie_tlv->header.type) & 0x00FF); + rsn_ie_tlv->header.len = cpu_to_le16((u16) priv->wpa_ie[1]); + rsn_ie_tlv->header.len = cpu_to_le16(le16_to_cpu(rsn_ie_tlv->header.len) + & 0x00FF); + if (le16_to_cpu(rsn_ie_tlv->header.len) <= (sizeof(priv->wpa_ie) - 2)) + memcpy(rsn_ie_tlv->rsn_ie, &priv->wpa_ie[2], + le16_to_cpu(rsn_ie_tlv->header.len)); + else + return -1; + + rsn_ie_len = sizeof(rsn_ie_tlv->header) + + le16_to_cpu(rsn_ie_tlv->header.len); + *buffer += rsn_ie_len; + + return rsn_ie_len; +} + +/* + * This function prepares command for association. + * + * This sets the following parameters - + * - Peer MAC address + * - Listen interval + * - Beacon interval + * - Capability information + * + * ...and the following TLVs, as required - + * - SSID TLV + * - PHY TLV + * - SS TLV + * - Rates TLV + * - Authentication TLV + * - Channel TLV + * - WPA/WPA2 IE + * - 11n TLV + * - Vendor specific TLV + * - WMM TLV + * - WAPI IE + * - Generic IE + * - TSF TLV + * + * Preparation also includes - + * - Setting command ID and proper size + * - Ensuring correct endian-ness + */ +int mwifiex_cmd_802_11_associate(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + struct mwifiex_bssdescriptor *bss_desc) +{ + struct host_cmd_ds_802_11_associate *assoc = &cmd->params.associate; + struct mwifiex_ie_types_ssid_param_set *ssid_tlv; + struct mwifiex_ie_types_phy_param_set *phy_tlv; + struct mwifiex_ie_types_ss_param_set *ss_tlv; + struct mwifiex_ie_types_rates_param_set *rates_tlv; + struct mwifiex_ie_types_auth_type *auth_tlv; + struct mwifiex_ie_types_chan_list_param_set *chan_tlv; + u8 rates[MWIFIEX_SUPPORTED_RATES]; + u32 rates_size; + u16 tmp_cap; + u8 *pos; + int rsn_ie_len = 0; + + pos = (u8 *) assoc; + + cmd->command = cpu_to_le16(HostCmd_CMD_802_11_ASSOCIATE); + + /* Save so we know which BSS Desc to use in the response handler */ + priv->attempted_bss_desc = bss_desc; + + memcpy(assoc->peer_sta_addr, + bss_desc->mac_address, sizeof(assoc->peer_sta_addr)); + pos += sizeof(assoc->peer_sta_addr); + + /* Set the listen interval */ + assoc->listen_interval = cpu_to_le16(priv->listen_interval); + /* Set the beacon period */ + assoc->beacon_period = cpu_to_le16(bss_desc->beacon_period); + + pos += sizeof(assoc->cap_info_bitmap); + pos += sizeof(assoc->listen_interval); + pos += sizeof(assoc->beacon_period); + pos += sizeof(assoc->dtim_period); + + ssid_tlv = (struct mwifiex_ie_types_ssid_param_set *) pos; + ssid_tlv->header.type = cpu_to_le16(WLAN_EID_SSID); + ssid_tlv->header.len = cpu_to_le16((u16) bss_desc->ssid.ssid_len); + memcpy(ssid_tlv->ssid, bss_desc->ssid.ssid, + le16_to_cpu(ssid_tlv->header.len)); + pos += sizeof(ssid_tlv->header) + le16_to_cpu(ssid_tlv->header.len); + + phy_tlv = (struct mwifiex_ie_types_phy_param_set *) pos; + phy_tlv->header.type = cpu_to_le16(WLAN_EID_DS_PARAMS); + phy_tlv->header.len = cpu_to_le16(sizeof(phy_tlv->fh_ds.ds_param_set)); + memcpy(&phy_tlv->fh_ds.ds_param_set, + &bss_desc->phy_param_set.ds_param_set.current_chan, + sizeof(phy_tlv->fh_ds.ds_param_set)); + pos += sizeof(phy_tlv->header) + le16_to_cpu(phy_tlv->header.len); + + ss_tlv = (struct mwifiex_ie_types_ss_param_set *) pos; + ss_tlv->header.type = cpu_to_le16(WLAN_EID_CF_PARAMS); + ss_tlv->header.len = cpu_to_le16(sizeof(ss_tlv->cf_ibss.cf_param_set)); + pos += sizeof(ss_tlv->header) + le16_to_cpu(ss_tlv->header.len); + + /* Get the common rates supported between the driver and the BSS Desc */ + if (mwifiex_setup_rates_from_bssdesc + (priv, bss_desc, rates, &rates_size)) + return -1; + + /* Save the data rates into Current BSS state structure */ + priv->curr_bss_params.num_of_rates = rates_size; + memcpy(&priv->curr_bss_params.data_rates, rates, rates_size); + + /* Setup the Rates TLV in the association command */ + rates_tlv = (struct mwifiex_ie_types_rates_param_set *) pos; + rates_tlv->header.type = cpu_to_le16(WLAN_EID_SUPP_RATES); + rates_tlv->header.len = cpu_to_le16((u16) rates_size); + memcpy(rates_tlv->rates, rates, rates_size); + pos += sizeof(rates_tlv->header) + rates_size; + dev_dbg(priv->adapter->dev, "info: ASSOC_CMD: rates size = %d\n", + rates_size); + + /* Add the Authentication type to be used for Auth frames */ + auth_tlv = (struct mwifiex_ie_types_auth_type *) pos; + auth_tlv->header.type = cpu_to_le16(TLV_TYPE_AUTH_TYPE); + auth_tlv->header.len = cpu_to_le16(sizeof(auth_tlv->auth_type)); + if (priv->sec_info.wep_enabled) + auth_tlv->auth_type = cpu_to_le16( + (u16) priv->sec_info.authentication_mode); + else + auth_tlv->auth_type = cpu_to_le16(NL80211_AUTHTYPE_OPEN_SYSTEM); + + pos += sizeof(auth_tlv->header) + le16_to_cpu(auth_tlv->header.len); + + if (IS_SUPPORT_MULTI_BANDS(priv->adapter) && + !(ISSUPP_11NENABLED(priv->adapter->fw_cap_info) && + (!bss_desc->disable_11n) && + (priv->adapter->config_bands & BAND_GN || + priv->adapter->config_bands & BAND_AN) && + (bss_desc->bcn_ht_cap) + ) + ) { + /* Append a channel TLV for the channel the attempted AP was + found on */ + chan_tlv = (struct mwifiex_ie_types_chan_list_param_set *) pos; + chan_tlv->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + chan_tlv->header.len = + cpu_to_le16(sizeof(struct mwifiex_chan_scan_param_set)); + + memset(chan_tlv->chan_scan_param, 0x00, + sizeof(struct mwifiex_chan_scan_param_set)); + chan_tlv->chan_scan_param[0].chan_number = + (bss_desc->phy_param_set.ds_param_set.current_chan); + dev_dbg(priv->adapter->dev, "info: Assoc: TLV Chan = %d\n", + chan_tlv->chan_scan_param[0].chan_number); + + chan_tlv->chan_scan_param[0].radio_type = + mwifiex_band_to_radio_type((u8) bss_desc->bss_band); + + dev_dbg(priv->adapter->dev, "info: Assoc: TLV Band = %d\n", + chan_tlv->chan_scan_param[0].radio_type); + pos += sizeof(chan_tlv->header) + + sizeof(struct mwifiex_chan_scan_param_set); + } + + if (!priv->wps.session_enable) { + if (priv->sec_info.wpa_enabled || priv->sec_info.wpa2_enabled) + rsn_ie_len = mwifiex_append_rsn_ie_wpa_wpa2(priv, &pos); + + if (rsn_ie_len == -1) + return -1; + } + + if (ISSUPP_11NENABLED(priv->adapter->fw_cap_info) && + (!bss_desc->disable_11n) && + (priv->adapter->config_bands & BAND_GN || + priv->adapter->config_bands & BAND_AN)) + mwifiex_cmd_append_11n_tlv(priv, bss_desc, &pos); + + if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info) && + !bss_desc->disable_11n && !bss_desc->disable_11ac && + (priv->adapter->config_bands & BAND_GAC || + priv->adapter->config_bands & BAND_AAC)) + mwifiex_cmd_append_11ac_tlv(priv, bss_desc, &pos); + + /* Append vendor specific IE TLV */ + mwifiex_cmd_append_vsie_tlv(priv, MWIFIEX_VSIE_MASK_ASSOC, &pos); + + mwifiex_wmm_process_association_req(priv, &pos, &bss_desc->wmm_ie, + bss_desc->bcn_ht_cap); + if (priv->sec_info.wapi_enabled && priv->wapi_ie_len) + mwifiex_cmd_append_wapi_ie(priv, &pos); + + if (priv->wps.session_enable && priv->wps_ie_len) + mwifiex_cmd_append_wps_ie(priv, &pos); + + mwifiex_cmd_append_generic_ie(priv, &pos); + + mwifiex_cmd_append_tsf_tlv(priv, &pos, bss_desc); + + cmd->size = cpu_to_le16((u16) (pos - (u8 *) assoc) + S_DS_GEN); + + /* Set the Capability info at last */ + tmp_cap = bss_desc->cap_info_bitmap; + + if (priv->adapter->config_bands == BAND_B) + tmp_cap &= ~WLAN_CAPABILITY_SHORT_SLOT_TIME; + + tmp_cap &= CAPINFO_MASK; + dev_dbg(priv->adapter->dev, "info: ASSOC_CMD: tmp_cap=%4X CAPINFO_MASK=%4lX\n", + tmp_cap, CAPINFO_MASK); + assoc->cap_info_bitmap = cpu_to_le16(tmp_cap); + + return 0; +} + +/* + * Association firmware command response handler + * + * The response buffer for the association command has the following + * memory layout. + * + * For cases where an association response was not received (indicated + * by the CapInfo and AId field): + * + * .------------------------------------------------------------. + * | Header(4 * sizeof(t_u16)): Standard command response hdr | + * .------------------------------------------------------------. + * | cap_info/Error Return(t_u16): | + * | 0xFFFF(-1): Internal error | + * | 0xFFFE(-2): Authentication unhandled message | + * | 0xFFFD(-3): Authentication refused | + * | 0xFFFC(-4): Timeout waiting for AP response | + * .------------------------------------------------------------. + * | status_code(t_u16): | + * | If cap_info is -1: | + * | An internal firmware failure prevented the | + * | command from being processed. The status_code | + * | will be set to 1. | + * | | + * | If cap_info is -2: | + * | An authentication frame was received but was | + * | not handled by the firmware. IEEE Status | + * | code for the failure is returned. | + * | | + * | If cap_info is -3: | + * | An authentication frame was received and the | + * | status_code is the IEEE Status reported in the | + * | response. | + * | | + * | If cap_info is -4: | + * | (1) Association response timeout | + * | (2) Authentication response timeout | + * .------------------------------------------------------------. + * | a_id(t_u16): 0xFFFF | + * .------------------------------------------------------------. + * + * + * For cases where an association response was received, the IEEE + * standard association response frame is returned: + * + * .------------------------------------------------------------. + * | Header(4 * sizeof(t_u16)): Standard command response hdr | + * .------------------------------------------------------------. + * | cap_info(t_u16): IEEE Capability | + * .------------------------------------------------------------. + * | status_code(t_u16): IEEE Status Code | + * .------------------------------------------------------------. + * | a_id(t_u16): IEEE Association ID | + * .------------------------------------------------------------. + * | IEEE IEs(variable): Any received IEs comprising the | + * | remaining portion of a received | + * | association response frame. | + * .------------------------------------------------------------. + * + * For simplistic handling, the status_code field can be used to determine + * an association success (0) or failure (non-zero). + */ +int mwifiex_ret_802_11_associate(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + struct mwifiex_adapter *adapter = priv->adapter; + int ret = 0; + struct ieee_types_assoc_rsp *assoc_rsp; + struct mwifiex_bssdescriptor *bss_desc; + u8 enable_data = true; + u16 cap_info, status_code; + + assoc_rsp = (struct ieee_types_assoc_rsp *) &resp->params; + + cap_info = le16_to_cpu(assoc_rsp->cap_info_bitmap); + status_code = le16_to_cpu(assoc_rsp->status_code); + + priv->assoc_rsp_size = min(le16_to_cpu(resp->size) - S_DS_GEN, + sizeof(priv->assoc_rsp_buf)); + + memcpy(priv->assoc_rsp_buf, &resp->params, priv->assoc_rsp_size); + + if (status_code) { + priv->adapter->dbg.num_cmd_assoc_failure++; + dev_err(priv->adapter->dev, + "ASSOC_RESP: failed, status code=%d err=%#x a_id=%#x\n", + status_code, cap_info, le16_to_cpu(assoc_rsp->a_id)); + + if (cap_info == MWIFIEX_TIMEOUT_FOR_AP_RESP) { + if (status_code == MWIFIEX_STATUS_CODE_AUTH_TIMEOUT) + ret = WLAN_STATUS_AUTH_TIMEOUT; + else + ret = WLAN_STATUS_UNSPECIFIED_FAILURE; + } else { + ret = status_code; + } + + goto done; + } + + /* Send a Media Connected event, according to the Spec */ + priv->media_connected = true; + + priv->adapter->ps_state = PS_STATE_AWAKE; + priv->adapter->pps_uapsd_mode = false; + priv->adapter->tx_lock_flag = false; + + /* Set the attempted BSSID Index to current */ + bss_desc = priv->attempted_bss_desc; + + dev_dbg(priv->adapter->dev, "info: ASSOC_RESP: %s\n", + bss_desc->ssid.ssid); + + /* Make a copy of current BSSID descriptor */ + memcpy(&priv->curr_bss_params.bss_descriptor, + bss_desc, sizeof(struct mwifiex_bssdescriptor)); + + /* Update curr_bss_params */ + priv->curr_bss_params.bss_descriptor.channel + = bss_desc->phy_param_set.ds_param_set.current_chan; + + priv->curr_bss_params.band = (u8) bss_desc->bss_band; + + if (bss_desc->wmm_ie.vend_hdr.element_id == WLAN_EID_VENDOR_SPECIFIC) + priv->curr_bss_params.wmm_enabled = true; + else + priv->curr_bss_params.wmm_enabled = false; + + if ((priv->wmm_required || bss_desc->bcn_ht_cap) && + priv->curr_bss_params.wmm_enabled) + priv->wmm_enabled = true; + else + priv->wmm_enabled = false; + + priv->curr_bss_params.wmm_uapsd_enabled = false; + + if (priv->wmm_enabled) + priv->curr_bss_params.wmm_uapsd_enabled + = ((bss_desc->wmm_ie.qos_info_bitmap & + IEEE80211_WMM_IE_AP_QOSINFO_UAPSD) ? 1 : 0); + + dev_dbg(priv->adapter->dev, "info: ASSOC_RESP: curr_pkt_filter is %#x\n", + priv->curr_pkt_filter); + if (priv->sec_info.wpa_enabled || priv->sec_info.wpa2_enabled) + priv->wpa_is_gtk_set = false; + + if (priv->wmm_enabled) { + /* Don't re-enable carrier until we get the WMM_GET_STATUS + event */ + enable_data = false; + } else { + /* Since WMM is not enabled, setup the queues with the + defaults */ + mwifiex_wmm_setup_queue_priorities(priv, NULL); + mwifiex_wmm_setup_ac_downgrade(priv); + } + + if (enable_data) + dev_dbg(priv->adapter->dev, + "info: post association, re-enabling data flow\n"); + + /* Reset SNR/NF/RSSI values */ + priv->data_rssi_last = 0; + priv->data_nf_last = 0; + priv->data_rssi_avg = 0; + priv->data_nf_avg = 0; + priv->bcn_rssi_last = 0; + priv->bcn_nf_last = 0; + priv->bcn_rssi_avg = 0; + priv->bcn_nf_avg = 0; + priv->rxpd_rate = 0; + priv->rxpd_htinfo = 0; + + mwifiex_save_curr_bcn(priv); + + priv->adapter->dbg.num_cmd_assoc_success++; + + dev_dbg(priv->adapter->dev, "info: ASSOC_RESP: associated\n"); + + /* Add the ra_list here for infra mode as there will be only 1 ra + always */ + mwifiex_ralist_add(priv, + priv->curr_bss_params.bss_descriptor.mac_address); + + if (!netif_carrier_ok(priv->netdev)) + netif_carrier_on(priv->netdev); + mwifiex_wake_up_net_dev_queue(priv->netdev, adapter); + + if (priv->sec_info.wpa_enabled || priv->sec_info.wpa2_enabled) + priv->scan_block = true; + +done: + /* Need to indicate IOCTL complete */ + if (adapter->curr_cmd->wait_q_enabled) { + if (ret) + adapter->cmd_wait_q.status = -1; + else + adapter->cmd_wait_q.status = 0; + } + + return ret; +} + +/* + * This function prepares command for ad-hoc start. + * + * Driver will fill up SSID, BSS mode, IBSS parameters, physical + * parameters, probe delay, and capability information. Firmware + * will fill up beacon period, basic rates and operational rates. + * + * In addition, the following TLVs are added - + * - Channel TLV + * - Vendor specific IE + * - WPA/WPA2 IE + * - HT Capabilities IE + * - HT Information IE + * + * Preparation also includes - + * - Setting command ID and proper size + * - Ensuring correct endian-ness + */ +int +mwifiex_cmd_802_11_ad_hoc_start(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + struct cfg80211_ssid *req_ssid) +{ + int rsn_ie_len = 0; + struct mwifiex_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_ad_hoc_start *adhoc_start = + &cmd->params.adhoc_start; + struct mwifiex_bssdescriptor *bss_desc; + u32 cmd_append_size = 0; + u32 i; + u16 tmp_cap; + struct mwifiex_ie_types_chan_list_param_set *chan_tlv; + u8 radio_type; + + struct mwifiex_ie_types_htcap *ht_cap; + struct mwifiex_ie_types_htinfo *ht_info; + u8 *pos = (u8 *) adhoc_start + + sizeof(struct host_cmd_ds_802_11_ad_hoc_start); + + if (!adapter) + return -1; + + cmd->command = cpu_to_le16(HostCmd_CMD_802_11_AD_HOC_START); + + bss_desc = &priv->curr_bss_params.bss_descriptor; + priv->attempted_bss_desc = bss_desc; + + /* + * Fill in the parameters for 2 data structures: + * 1. struct host_cmd_ds_802_11_ad_hoc_start command + * 2. bss_desc + * Driver will fill up SSID, bss_mode,IBSS param, Physical Param, + * probe delay, and Cap info. + * Firmware will fill up beacon period, Basic rates + * and operational rates. + */ + + memset(adhoc_start->ssid, 0, IEEE80211_MAX_SSID_LEN); + + memcpy(adhoc_start->ssid, req_ssid->ssid, req_ssid->ssid_len); + + dev_dbg(adapter->dev, "info: ADHOC_S_CMD: SSID = %s\n", + adhoc_start->ssid); + + memset(bss_desc->ssid.ssid, 0, IEEE80211_MAX_SSID_LEN); + memcpy(bss_desc->ssid.ssid, req_ssid->ssid, req_ssid->ssid_len); + + bss_desc->ssid.ssid_len = req_ssid->ssid_len; + + /* Set the BSS mode */ + adhoc_start->bss_mode = HostCmd_BSS_MODE_IBSS; + bss_desc->bss_mode = NL80211_IFTYPE_ADHOC; + adhoc_start->beacon_period = cpu_to_le16(priv->beacon_period); + bss_desc->beacon_period = priv->beacon_period; + + /* Set Physical param set */ +/* Parameter IE Id */ +#define DS_PARA_IE_ID 3 +/* Parameter IE length */ +#define DS_PARA_IE_LEN 1 + + adhoc_start->phy_param_set.ds_param_set.element_id = DS_PARA_IE_ID; + adhoc_start->phy_param_set.ds_param_set.len = DS_PARA_IE_LEN; + + if (!mwifiex_get_cfp(priv, adapter->adhoc_start_band, + (u16) priv->adhoc_channel, 0)) { + struct mwifiex_chan_freq_power *cfp; + cfp = mwifiex_get_cfp(priv, adapter->adhoc_start_band, + FIRST_VALID_CHANNEL, 0); + if (cfp) + priv->adhoc_channel = (u8) cfp->channel; + } + + if (!priv->adhoc_channel) { + dev_err(adapter->dev, "ADHOC_S_CMD: adhoc_channel cannot be 0\n"); + return -1; + } + + dev_dbg(adapter->dev, "info: ADHOC_S_CMD: creating ADHOC on channel %d\n", + priv->adhoc_channel); + + priv->curr_bss_params.bss_descriptor.channel = priv->adhoc_channel; + priv->curr_bss_params.band = adapter->adhoc_start_band; + + bss_desc->channel = priv->adhoc_channel; + adhoc_start->phy_param_set.ds_param_set.current_chan = + priv->adhoc_channel; + + memcpy(&bss_desc->phy_param_set, &adhoc_start->phy_param_set, + sizeof(union ieee_types_phy_param_set)); + + /* Set IBSS param set */ +/* IBSS parameter IE Id */ +#define IBSS_PARA_IE_ID 6 +/* IBSS parameter IE length */ +#define IBSS_PARA_IE_LEN 2 + + adhoc_start->ss_param_set.ibss_param_set.element_id = IBSS_PARA_IE_ID; + adhoc_start->ss_param_set.ibss_param_set.len = IBSS_PARA_IE_LEN; + adhoc_start->ss_param_set.ibss_param_set.atim_window + = cpu_to_le16(priv->atim_window); + memcpy(&bss_desc->ss_param_set, &adhoc_start->ss_param_set, + sizeof(union ieee_types_ss_param_set)); + + /* Set Capability info */ + bss_desc->cap_info_bitmap |= WLAN_CAPABILITY_IBSS; + tmp_cap = le16_to_cpu(adhoc_start->cap_info_bitmap); + tmp_cap &= ~WLAN_CAPABILITY_ESS; + tmp_cap |= WLAN_CAPABILITY_IBSS; + + /* Set up privacy in bss_desc */ + if (priv->sec_info.encryption_mode) { + /* Ad-Hoc capability privacy on */ + dev_dbg(adapter->dev, + "info: ADHOC_S_CMD: wep_status set privacy to WEP\n"); + bss_desc->privacy = MWIFIEX_802_11_PRIV_FILTER_8021X_WEP; + tmp_cap |= WLAN_CAPABILITY_PRIVACY; + } else { + dev_dbg(adapter->dev, "info: ADHOC_S_CMD: wep_status NOT set," + " setting privacy to ACCEPT ALL\n"); + bss_desc->privacy = MWIFIEX_802_11_PRIV_FILTER_ACCEPT_ALL; + } + + memset(adhoc_start->data_rate, 0, sizeof(adhoc_start->data_rate)); + mwifiex_get_active_data_rates(priv, adhoc_start->data_rate); + if ((adapter->adhoc_start_band & BAND_G) && + (priv->curr_pkt_filter & HostCmd_ACT_MAC_ADHOC_G_PROTECTION_ON)) { + if (mwifiex_send_cmd_async(priv, HostCmd_CMD_MAC_CONTROL, + HostCmd_ACT_GEN_SET, 0, + &priv->curr_pkt_filter)) { + dev_err(adapter->dev, + "ADHOC_S_CMD: G Protection config failed\n"); + return -1; + } + } + /* Find the last non zero */ + for (i = 0; i < sizeof(adhoc_start->data_rate); i++) + if (!adhoc_start->data_rate[i]) + break; + + priv->curr_bss_params.num_of_rates = i; + + /* Copy the ad-hoc creating rates into Current BSS rate structure */ + memcpy(&priv->curr_bss_params.data_rates, + &adhoc_start->data_rate, priv->curr_bss_params.num_of_rates); + + dev_dbg(adapter->dev, "info: ADHOC_S_CMD: rates=%02x %02x %02x %02x\n", + adhoc_start->data_rate[0], adhoc_start->data_rate[1], + adhoc_start->data_rate[2], adhoc_start->data_rate[3]); + + dev_dbg(adapter->dev, "info: ADHOC_S_CMD: AD-HOC Start command is ready\n"); + + if (IS_SUPPORT_MULTI_BANDS(adapter)) { + /* Append a channel TLV */ + chan_tlv = (struct mwifiex_ie_types_chan_list_param_set *) pos; + chan_tlv->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + chan_tlv->header.len = + cpu_to_le16(sizeof(struct mwifiex_chan_scan_param_set)); + + memset(chan_tlv->chan_scan_param, 0x00, + sizeof(struct mwifiex_chan_scan_param_set)); + chan_tlv->chan_scan_param[0].chan_number = + (u8) priv->curr_bss_params.bss_descriptor.channel; + + dev_dbg(adapter->dev, "info: ADHOC_S_CMD: TLV Chan = %d\n", + chan_tlv->chan_scan_param[0].chan_number); + + chan_tlv->chan_scan_param[0].radio_type + = mwifiex_band_to_radio_type(priv->curr_bss_params.band); + if (adapter->adhoc_start_band & BAND_GN || + adapter->adhoc_start_band & BAND_AN) { + if (adapter->sec_chan_offset == + IEEE80211_HT_PARAM_CHA_SEC_ABOVE) + chan_tlv->chan_scan_param[0].radio_type |= + (IEEE80211_HT_PARAM_CHA_SEC_ABOVE << 4); + else if (adapter->sec_chan_offset == + IEEE80211_HT_PARAM_CHA_SEC_ABOVE) + chan_tlv->chan_scan_param[0].radio_type |= + (IEEE80211_HT_PARAM_CHA_SEC_BELOW << 4); + } + dev_dbg(adapter->dev, "info: ADHOC_S_CMD: TLV Band = %d\n", + chan_tlv->chan_scan_param[0].radio_type); + pos += sizeof(chan_tlv->header) + + sizeof(struct mwifiex_chan_scan_param_set); + cmd_append_size += + sizeof(chan_tlv->header) + + sizeof(struct mwifiex_chan_scan_param_set); + } + + /* Append vendor specific IE TLV */ + cmd_append_size += mwifiex_cmd_append_vsie_tlv(priv, + MWIFIEX_VSIE_MASK_ADHOC, &pos); + + if (priv->sec_info.wpa_enabled) { + rsn_ie_len = mwifiex_append_rsn_ie_wpa_wpa2(priv, &pos); + if (rsn_ie_len == -1) + return -1; + cmd_append_size += rsn_ie_len; + } + + if (adapter->adhoc_11n_enabled) { + /* Fill HT CAPABILITY */ + ht_cap = (struct mwifiex_ie_types_htcap *) pos; + memset(ht_cap, 0, sizeof(struct mwifiex_ie_types_htcap)); + ht_cap->header.type = cpu_to_le16(WLAN_EID_HT_CAPABILITY); + ht_cap->header.len = + cpu_to_le16(sizeof(struct ieee80211_ht_cap)); + radio_type = mwifiex_band_to_radio_type( + priv->adapter->config_bands); + mwifiex_fill_cap_info(priv, radio_type, ht_cap); + + if (adapter->sec_chan_offset == + IEEE80211_HT_PARAM_CHA_SEC_NONE) { + u16 tmp_ht_cap; + + tmp_ht_cap = le16_to_cpu(ht_cap->ht_cap.cap_info); + tmp_ht_cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40; + tmp_ht_cap &= ~IEEE80211_HT_CAP_SGI_40; + ht_cap->ht_cap.cap_info = cpu_to_le16(tmp_ht_cap); + } + + pos += sizeof(struct mwifiex_ie_types_htcap); + cmd_append_size += sizeof(struct mwifiex_ie_types_htcap); + + /* Fill HT INFORMATION */ + ht_info = (struct mwifiex_ie_types_htinfo *) pos; + memset(ht_info, 0, sizeof(struct mwifiex_ie_types_htinfo)); + ht_info->header.type = cpu_to_le16(WLAN_EID_HT_OPERATION); + ht_info->header.len = + cpu_to_le16(sizeof(struct ieee80211_ht_operation)); + + ht_info->ht_oper.primary_chan = + (u8) priv->curr_bss_params.bss_descriptor.channel; + if (adapter->sec_chan_offset) { + ht_info->ht_oper.ht_param = adapter->sec_chan_offset; + ht_info->ht_oper.ht_param |= + IEEE80211_HT_PARAM_CHAN_WIDTH_ANY; + } + ht_info->ht_oper.operation_mode = + cpu_to_le16(IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT); + ht_info->ht_oper.basic_set[0] = 0xff; + pos += sizeof(struct mwifiex_ie_types_htinfo); + cmd_append_size += + sizeof(struct mwifiex_ie_types_htinfo); + } + + cmd->size = + cpu_to_le16((u16)(sizeof(struct host_cmd_ds_802_11_ad_hoc_start) + + S_DS_GEN + cmd_append_size)); + + if (adapter->adhoc_start_band == BAND_B) + tmp_cap &= ~WLAN_CAPABILITY_SHORT_SLOT_TIME; + else + tmp_cap |= WLAN_CAPABILITY_SHORT_SLOT_TIME; + + adhoc_start->cap_info_bitmap = cpu_to_le16(tmp_cap); + + return 0; +} + +/* + * This function prepares command for ad-hoc join. + * + * Most of the parameters are set up by copying from the target BSS descriptor + * from the scan response. + * + * In addition, the following TLVs are added - + * - Channel TLV + * - Vendor specific IE + * - WPA/WPA2 IE + * - 11n IE + * + * Preparation also includes - + * - Setting command ID and proper size + * - Ensuring correct endian-ness + */ +int +mwifiex_cmd_802_11_ad_hoc_join(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + struct mwifiex_bssdescriptor *bss_desc) +{ + int rsn_ie_len = 0; + struct host_cmd_ds_802_11_ad_hoc_join *adhoc_join = + &cmd->params.adhoc_join; + struct mwifiex_ie_types_chan_list_param_set *chan_tlv; + u32 cmd_append_size = 0; + u16 tmp_cap; + u32 i, rates_size = 0; + u16 curr_pkt_filter; + u8 *pos = + (u8 *) adhoc_join + + sizeof(struct host_cmd_ds_802_11_ad_hoc_join); + +/* Use G protection */ +#define USE_G_PROTECTION 0x02 + if (bss_desc->erp_flags & USE_G_PROTECTION) { + curr_pkt_filter = + priv-> + curr_pkt_filter | HostCmd_ACT_MAC_ADHOC_G_PROTECTION_ON; + + if (mwifiex_send_cmd_async(priv, HostCmd_CMD_MAC_CONTROL, + HostCmd_ACT_GEN_SET, 0, + &curr_pkt_filter)) { + dev_err(priv->adapter->dev, + "ADHOC_J_CMD: G Protection config failed\n"); + return -1; + } + } + + priv->attempted_bss_desc = bss_desc; + + cmd->command = cpu_to_le16(HostCmd_CMD_802_11_AD_HOC_JOIN); + + adhoc_join->bss_descriptor.bss_mode = HostCmd_BSS_MODE_IBSS; + + adhoc_join->bss_descriptor.beacon_period + = cpu_to_le16(bss_desc->beacon_period); + + memcpy(&adhoc_join->bss_descriptor.bssid, + &bss_desc->mac_address, ETH_ALEN); + + memcpy(&adhoc_join->bss_descriptor.ssid, + &bss_desc->ssid.ssid, bss_desc->ssid.ssid_len); + + memcpy(&adhoc_join->bss_descriptor.phy_param_set, + &bss_desc->phy_param_set, + sizeof(union ieee_types_phy_param_set)); + + memcpy(&adhoc_join->bss_descriptor.ss_param_set, + &bss_desc->ss_param_set, sizeof(union ieee_types_ss_param_set)); + + tmp_cap = bss_desc->cap_info_bitmap; + + tmp_cap &= CAPINFO_MASK; + + dev_dbg(priv->adapter->dev, + "info: ADHOC_J_CMD: tmp_cap=%4X CAPINFO_MASK=%4lX\n", + tmp_cap, CAPINFO_MASK); + + /* Information on BSSID descriptor passed to FW */ + dev_dbg(priv->adapter->dev, "info: ADHOC_J_CMD: BSSID=%pM, SSID='%s'\n", + adhoc_join->bss_descriptor.bssid, + adhoc_join->bss_descriptor.ssid); + + for (i = 0; i < MWIFIEX_SUPPORTED_RATES && + bss_desc->supported_rates[i]; i++) + ; + rates_size = i; + + /* Copy Data Rates from the Rates recorded in scan response */ + memset(adhoc_join->bss_descriptor.data_rates, 0, + sizeof(adhoc_join->bss_descriptor.data_rates)); + memcpy(adhoc_join->bss_descriptor.data_rates, + bss_desc->supported_rates, rates_size); + + /* Copy the adhoc join rates into Current BSS state structure */ + priv->curr_bss_params.num_of_rates = rates_size; + memcpy(&priv->curr_bss_params.data_rates, bss_desc->supported_rates, + rates_size); + + /* Copy the channel information */ + priv->curr_bss_params.bss_descriptor.channel = bss_desc->channel; + priv->curr_bss_params.band = (u8) bss_desc->bss_band; + + if (priv->sec_info.wep_enabled || priv->sec_info.wpa_enabled) + tmp_cap |= WLAN_CAPABILITY_PRIVACY; + + if (IS_SUPPORT_MULTI_BANDS(priv->adapter)) { + /* Append a channel TLV */ + chan_tlv = (struct mwifiex_ie_types_chan_list_param_set *) pos; + chan_tlv->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + chan_tlv->header.len = + cpu_to_le16(sizeof(struct mwifiex_chan_scan_param_set)); + + memset(chan_tlv->chan_scan_param, 0x00, + sizeof(struct mwifiex_chan_scan_param_set)); + chan_tlv->chan_scan_param[0].chan_number = + (bss_desc->phy_param_set.ds_param_set.current_chan); + dev_dbg(priv->adapter->dev, "info: ADHOC_J_CMD: TLV Chan=%d\n", + chan_tlv->chan_scan_param[0].chan_number); + + chan_tlv->chan_scan_param[0].radio_type = + mwifiex_band_to_radio_type((u8) bss_desc->bss_band); + + dev_dbg(priv->adapter->dev, "info: ADHOC_J_CMD: TLV Band=%d\n", + chan_tlv->chan_scan_param[0].radio_type); + pos += sizeof(chan_tlv->header) + + sizeof(struct mwifiex_chan_scan_param_set); + cmd_append_size += sizeof(chan_tlv->header) + + sizeof(struct mwifiex_chan_scan_param_set); + } + + if (priv->sec_info.wpa_enabled) + rsn_ie_len = mwifiex_append_rsn_ie_wpa_wpa2(priv, &pos); + if (rsn_ie_len == -1) + return -1; + cmd_append_size += rsn_ie_len; + + if (ISSUPP_11NENABLED(priv->adapter->fw_cap_info)) + cmd_append_size += mwifiex_cmd_append_11n_tlv(priv, + bss_desc, &pos); + + /* Append vendor specific IE TLV */ + cmd_append_size += mwifiex_cmd_append_vsie_tlv(priv, + MWIFIEX_VSIE_MASK_ADHOC, &pos); + + cmd->size = cpu_to_le16 + ((u16) (sizeof(struct host_cmd_ds_802_11_ad_hoc_join) + + S_DS_GEN + cmd_append_size)); + + adhoc_join->bss_descriptor.cap_info_bitmap = cpu_to_le16(tmp_cap); + + return 0; +} + +/* + * This function handles the command response of ad-hoc start and + * ad-hoc join. + * + * The function generates a device-connected event to notify + * the applications, in case of successful ad-hoc start/join, and + * saves the beacon buffer. + */ +int mwifiex_ret_802_11_ad_hoc(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + int ret = 0; + struct mwifiex_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_ad_hoc_result *adhoc_result; + struct mwifiex_bssdescriptor *bss_desc; + u16 reason_code; + + adhoc_result = &resp->params.adhoc_result; + + bss_desc = priv->attempted_bss_desc; + + /* Join result code 0 --> SUCCESS */ + reason_code = le16_to_cpu(resp->result); + if (reason_code) { + dev_err(priv->adapter->dev, "ADHOC_RESP: failed\n"); + if (priv->media_connected) + mwifiex_reset_connect_state(priv, reason_code); + + memset(&priv->curr_bss_params.bss_descriptor, + 0x00, sizeof(struct mwifiex_bssdescriptor)); + + ret = -1; + goto done; + } + + /* Send a Media Connected event, according to the Spec */ + priv->media_connected = true; + + if (le16_to_cpu(resp->command) == HostCmd_CMD_802_11_AD_HOC_START) { + dev_dbg(priv->adapter->dev, "info: ADHOC_S_RESP %s\n", + bss_desc->ssid.ssid); + + /* Update the created network descriptor with the new BSSID */ + memcpy(bss_desc->mac_address, + adhoc_result->bssid, ETH_ALEN); + + priv->adhoc_state = ADHOC_STARTED; + } else { + /* + * Now the join cmd should be successful. + * If BSSID has changed use SSID to compare instead of BSSID + */ + dev_dbg(priv->adapter->dev, "info: ADHOC_J_RESP %s\n", + bss_desc->ssid.ssid); + + /* + * Make a copy of current BSSID descriptor, only needed for + * join since the current descriptor is already being used + * for adhoc start + */ + memcpy(&priv->curr_bss_params.bss_descriptor, + bss_desc, sizeof(struct mwifiex_bssdescriptor)); + + priv->adhoc_state = ADHOC_JOINED; + } + + dev_dbg(priv->adapter->dev, "info: ADHOC_RESP: channel = %d\n", + priv->adhoc_channel); + dev_dbg(priv->adapter->dev, "info: ADHOC_RESP: BSSID = %pM\n", + priv->curr_bss_params.bss_descriptor.mac_address); + + if (!netif_carrier_ok(priv->netdev)) + netif_carrier_on(priv->netdev); + mwifiex_wake_up_net_dev_queue(priv->netdev, adapter); + + mwifiex_save_curr_bcn(priv); + +done: + /* Need to indicate IOCTL complete */ + if (adapter->curr_cmd->wait_q_enabled) { + if (ret) + adapter->cmd_wait_q.status = -1; + else + adapter->cmd_wait_q.status = 0; + + } + + return ret; +} + +/* + * This function associates to a specific BSS discovered in a scan. + * + * It clears any past association response stored for application + * retrieval and calls the command preparation routine to send the + * command to firmware. + */ +int mwifiex_associate(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc) +{ + u8 current_bssid[ETH_ALEN]; + + /* Return error if the adapter is not STA role or table entry + * is not marked as infra. + */ + if ((GET_BSS_ROLE(priv) != MWIFIEX_BSS_ROLE_STA) || + (bss_desc->bss_mode != NL80211_IFTYPE_STATION)) + return -1; + + if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info) && + !bss_desc->disable_11n && !bss_desc->disable_11ac && + (priv->adapter->config_bands & BAND_GAC || + priv->adapter->config_bands & BAND_AAC)) + mwifiex_set_11ac_ba_params(priv); + else + mwifiex_set_ba_params(priv); + + memcpy(¤t_bssid, + &priv->curr_bss_params.bss_descriptor.mac_address, + sizeof(current_bssid)); + + /* Clear any past association response stored for application + retrieval */ + priv->assoc_rsp_size = 0; + + return mwifiex_send_cmd_sync(priv, HostCmd_CMD_802_11_ASSOCIATE, + HostCmd_ACT_GEN_SET, 0, bss_desc); +} + +/* + * This function starts an ad-hoc network. + * + * It calls the command preparation routine to send the command to firmware. + */ +int +mwifiex_adhoc_start(struct mwifiex_private *priv, + struct cfg80211_ssid *adhoc_ssid) +{ + dev_dbg(priv->adapter->dev, "info: Adhoc Channel = %d\n", + priv->adhoc_channel); + dev_dbg(priv->adapter->dev, "info: curr_bss_params.channel = %d\n", + priv->curr_bss_params.bss_descriptor.channel); + dev_dbg(priv->adapter->dev, "info: curr_bss_params.band = %d\n", + priv->curr_bss_params.band); + + if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info) && + (priv->adapter->config_bands & BAND_GAC || + priv->adapter->config_bands & BAND_AAC)) + mwifiex_set_11ac_ba_params(priv); + else + mwifiex_set_ba_params(priv); + + return mwifiex_send_cmd_sync(priv, HostCmd_CMD_802_11_AD_HOC_START, + HostCmd_ACT_GEN_SET, 0, adhoc_ssid); +} + +/* + * This function joins an ad-hoc network found in a previous scan. + * + * It calls the command preparation routine to send the command to firmware, + * if already not connected to the requested SSID. + */ +int mwifiex_adhoc_join(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc) +{ + dev_dbg(priv->adapter->dev, "info: adhoc join: curr_bss ssid =%s\n", + priv->curr_bss_params.bss_descriptor.ssid.ssid); + dev_dbg(priv->adapter->dev, "info: adhoc join: curr_bss ssid_len =%u\n", + priv->curr_bss_params.bss_descriptor.ssid.ssid_len); + dev_dbg(priv->adapter->dev, "info: adhoc join: ssid =%s\n", + bss_desc->ssid.ssid); + dev_dbg(priv->adapter->dev, "info: adhoc join: ssid_len =%u\n", + bss_desc->ssid.ssid_len); + + /* Check if the requested SSID is already joined */ + if (priv->curr_bss_params.bss_descriptor.ssid.ssid_len && + !mwifiex_ssid_cmp(&bss_desc->ssid, + &priv->curr_bss_params.bss_descriptor.ssid) && + (priv->curr_bss_params.bss_descriptor.bss_mode == + NL80211_IFTYPE_ADHOC)) { + dev_dbg(priv->adapter->dev, "info: ADHOC_J_CMD: new ad-hoc SSID" + " is the same as current; not attempting to re-join\n"); + return -1; + } + + if (ISSUPP_11ACENABLED(priv->adapter->fw_cap_info) && + !bss_desc->disable_11n && !bss_desc->disable_11ac && + (priv->adapter->config_bands & BAND_GAC || + priv->adapter->config_bands & BAND_AAC)) + mwifiex_set_11ac_ba_params(priv); + else + mwifiex_set_ba_params(priv); + + dev_dbg(priv->adapter->dev, "info: curr_bss_params.channel = %d\n", + priv->curr_bss_params.bss_descriptor.channel); + dev_dbg(priv->adapter->dev, "info: curr_bss_params.band = %c\n", + priv->curr_bss_params.band); + + return mwifiex_send_cmd_sync(priv, HostCmd_CMD_802_11_AD_HOC_JOIN, + HostCmd_ACT_GEN_SET, 0, bss_desc); +} + +/* + * This function deauthenticates/disconnects from infra network by sending + * deauthentication request. + */ +static int mwifiex_deauthenticate_infra(struct mwifiex_private *priv, u8 *mac) +{ + u8 mac_address[ETH_ALEN]; + int ret; + + if (!mac || is_zero_ether_addr(mac)) + memcpy(mac_address, + priv->curr_bss_params.bss_descriptor.mac_address, + ETH_ALEN); + else + memcpy(mac_address, mac, ETH_ALEN); + + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_802_11_DEAUTHENTICATE, + HostCmd_ACT_GEN_SET, 0, mac_address); + + return ret; +} + +/* + * This function deauthenticates/disconnects from a BSS. + * + * In case of infra made, it sends deauthentication request, and + * in case of ad-hoc mode, a stop network request is sent to the firmware. + * In AP mode, a command to stop bss is sent to firmware. + */ +int mwifiex_deauthenticate(struct mwifiex_private *priv, u8 *mac) +{ + if (!priv->media_connected) + return 0; + + switch (priv->bss_mode) { + case NL80211_IFTYPE_STATION: + return mwifiex_deauthenticate_infra(priv, mac); + case NL80211_IFTYPE_ADHOC: + return mwifiex_send_cmd_sync(priv, + HostCmd_CMD_802_11_AD_HOC_STOP, + HostCmd_ACT_GEN_SET, 0, NULL); + case NL80211_IFTYPE_AP: + return mwifiex_send_cmd_sync(priv, HostCmd_CMD_UAP_BSS_STOP, + HostCmd_ACT_GEN_SET, 0, NULL); + default: + break; + } + + return 0; +} +EXPORT_SYMBOL_GPL(mwifiex_deauthenticate); + +/* + * This function converts band to radio type used in channel TLV. + */ +u8 +mwifiex_band_to_radio_type(u8 band) +{ + switch (band) { + case BAND_A: + case BAND_AN: + case BAND_A | BAND_AN: + case BAND_A | BAND_AN | BAND_AAC: + return HostCmd_SCAN_RADIO_TYPE_A; + case BAND_B: + case BAND_G: + case BAND_B | BAND_G: + default: + return HostCmd_SCAN_RADIO_TYPE_BG; + } +} diff --git a/drivers/net/wireless/mwifiex/main.c b/drivers/net/wireless/mwifiex/main.c new file mode 100644 index 0000000..9794165 --- /dev/null +++ b/drivers/net/wireless/mwifiex/main.c @@ -0,0 +1,918 @@ +/* + * Marvell Wireless LAN device driver: major functions + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "main.h" +#include "wmm.h" +#include "cfg80211.h" +#include "11n.h" + +#define VERSION "1.0" + +const char driver_version[] = "mwifiex " VERSION " (%s) "; + +/* + * This function registers the device and performs all the necessary + * initializations. + * + * The following initialization operations are performed - + * - Allocate adapter structure + * - Save interface specific operations table in adapter + * - Call interface specific initialization routine + * - Allocate private structures + * - Set default adapter structure parameters + * - Initialize locks + * + * In case of any errors during inittialization, this function also ensures + * proper cleanup before exiting. + */ +static int mwifiex_register(void *card, struct mwifiex_if_ops *if_ops, + void **padapter) +{ + struct mwifiex_adapter *adapter; + int i; + + adapter = kzalloc(sizeof(struct mwifiex_adapter), GFP_KERNEL); + if (!adapter) + return -ENOMEM; + + *padapter = adapter; + adapter->card = card; + + /* Save interface specific operations in adapter */ + memmove(&adapter->if_ops, if_ops, sizeof(struct mwifiex_if_ops)); + + /* card specific initialization has been deferred until now .. */ + if (adapter->if_ops.init_if) + if (adapter->if_ops.init_if(adapter)) + goto error; + + adapter->priv_num = 0; + + for (i = 0; i < MWIFIEX_MAX_BSS_NUM; i++) { + /* Allocate memory for private structure */ + adapter->priv[i] = + kzalloc(sizeof(struct mwifiex_private), GFP_KERNEL); + if (!adapter->priv[i]) + goto error; + + adapter->priv[i]->adapter = adapter; + adapter->priv_num++; + } + mwifiex_init_lock_list(adapter); + + init_timer(&adapter->cmd_timer); + adapter->cmd_timer.function = mwifiex_cmd_timeout_func; + adapter->cmd_timer.data = (unsigned long) adapter; + + return 0; + +error: + dev_dbg(adapter->dev, "info: leave mwifiex_register with error\n"); + + for (i = 0; i < adapter->priv_num; i++) + kfree(adapter->priv[i]); + + kfree(adapter); + + return -1; +} + +/* + * This function unregisters the device and performs all the necessary + * cleanups. + * + * The following cleanup operations are performed - + * - Free the timers + * - Free beacon buffers + * - Free private structures + * - Free adapter structure + */ +static int mwifiex_unregister(struct mwifiex_adapter *adapter) +{ + s32 i; + + del_timer(&adapter->cmd_timer); + + /* Free private structures */ + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i]) { + mwifiex_free_curr_bcn(adapter->priv[i]); + kfree(adapter->priv[i]); + } + } + + kfree(adapter); + return 0; +} + +/* + * The main process. + * + * This function is the main procedure of the driver and handles various driver + * operations. It runs in a loop and provides the core functionalities. + * + * The main responsibilities of this function are - + * - Ensure concurrency control + * - Handle pending interrupts and call interrupt handlers + * - Wake up the card if required + * - Handle command responses and call response handlers + * - Handle events and call event handlers + * - Execute pending commands + * - Transmit pending data packets + */ +int mwifiex_main_process(struct mwifiex_adapter *adapter) +{ + int ret = 0; + unsigned long flags; + struct sk_buff *skb; + + spin_lock_irqsave(&adapter->main_proc_lock, flags); + + /* Check if already processing */ + if (adapter->mwifiex_processing) { + spin_unlock_irqrestore(&adapter->main_proc_lock, flags); + goto exit_main_proc; + } else { + adapter->mwifiex_processing = true; + spin_unlock_irqrestore(&adapter->main_proc_lock, flags); + } +process_start: + do { + if ((adapter->hw_status == MWIFIEX_HW_STATUS_CLOSING) || + (adapter->hw_status == MWIFIEX_HW_STATUS_NOT_READY)) + break; + + /* Handle pending interrupt if any */ + if (adapter->int_status) { + if (adapter->hs_activated) + mwifiex_process_hs_config(adapter); + if (adapter->if_ops.process_int_status) + adapter->if_ops.process_int_status(adapter); + } + + /* Need to wake up the card ? */ + if ((adapter->ps_state == PS_STATE_SLEEP) && + (adapter->pm_wakeup_card_req && + !adapter->pm_wakeup_fw_try) && + (is_command_pending(adapter) || + !mwifiex_wmm_lists_empty(adapter))) { + adapter->pm_wakeup_fw_try = true; + adapter->if_ops.wakeup(adapter); + continue; + } + + if (IS_CARD_RX_RCVD(adapter)) { + adapter->pm_wakeup_fw_try = false; + if (adapter->ps_state == PS_STATE_SLEEP) + adapter->ps_state = PS_STATE_AWAKE; + } else { + /* We have tried to wakeup the card already */ + if (adapter->pm_wakeup_fw_try) + break; + if (adapter->ps_state != PS_STATE_AWAKE || + adapter->tx_lock_flag) + break; + + if ((adapter->scan_processing && + !adapter->scan_delay_cnt) || adapter->data_sent || + mwifiex_wmm_lists_empty(adapter)) { + if (adapter->cmd_sent || adapter->curr_cmd || + (!is_command_pending(adapter))) + break; + } + } + + /* Check Rx data for USB */ + if (adapter->iface_type == MWIFIEX_USB) + while ((skb = skb_dequeue(&adapter->usb_rx_data_q))) + mwifiex_handle_rx_packet(adapter, skb); + + /* Check for Cmd Resp */ + if (adapter->cmd_resp_received) { + adapter->cmd_resp_received = false; + mwifiex_process_cmdresp(adapter); + + /* call mwifiex back when init_fw is done */ + if (adapter->hw_status == MWIFIEX_HW_STATUS_INIT_DONE) { + adapter->hw_status = MWIFIEX_HW_STATUS_READY; + mwifiex_init_fw_complete(adapter); + } + } + + /* Check for event */ + if (adapter->event_received) { + adapter->event_received = false; + mwifiex_process_event(adapter); + } + + /* Check if we need to confirm Sleep Request + received previously */ + if (adapter->ps_state == PS_STATE_PRE_SLEEP) { + if (!adapter->cmd_sent && !adapter->curr_cmd) + mwifiex_check_ps_cond(adapter); + } + + /* * The ps_state may have been changed during processing of + * Sleep Request event. + */ + if ((adapter->ps_state == PS_STATE_SLEEP) || + (adapter->ps_state == PS_STATE_PRE_SLEEP) || + (adapter->ps_state == PS_STATE_SLEEP_CFM) || + adapter->tx_lock_flag) + continue; + + if (!adapter->cmd_sent && !adapter->curr_cmd) { + if (mwifiex_exec_next_cmd(adapter) == -1) { + ret = -1; + break; + } + } + + if ((!adapter->scan_processing || adapter->scan_delay_cnt) && + !adapter->data_sent && !mwifiex_wmm_lists_empty(adapter)) { + mwifiex_wmm_process_tx(adapter); + if (adapter->hs_activated) { + adapter->is_hs_configured = false; + mwifiex_hs_activated_event + (mwifiex_get_priv + (adapter, MWIFIEX_BSS_ROLE_ANY), + false); + } + } + + if (adapter->delay_null_pkt && !adapter->cmd_sent && + !adapter->curr_cmd && !is_command_pending(adapter) && + mwifiex_wmm_lists_empty(adapter)) { + if (!mwifiex_send_null_packet + (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA), + MWIFIEX_TxPD_POWER_MGMT_NULL_PACKET | + MWIFIEX_TxPD_POWER_MGMT_LAST_PACKET)) { + adapter->delay_null_pkt = false; + adapter->ps_state = PS_STATE_SLEEP; + } + break; + } + } while (true); + + spin_lock_irqsave(&adapter->main_proc_lock, flags); + if ((adapter->int_status) || IS_CARD_RX_RCVD(adapter)) { + spin_unlock_irqrestore(&adapter->main_proc_lock, flags); + goto process_start; + } + + adapter->mwifiex_processing = false; + spin_unlock_irqrestore(&adapter->main_proc_lock, flags); + +exit_main_proc: + if (adapter->hw_status == MWIFIEX_HW_STATUS_CLOSING) + mwifiex_shutdown_drv(adapter); + return ret; +} +EXPORT_SYMBOL_GPL(mwifiex_main_process); + +/* + * This function frees the adapter structure. + * + * Additionally, this closes the netlink socket, frees the timers + * and private structures. + */ +static void mwifiex_free_adapter(struct mwifiex_adapter *adapter) +{ + if (!adapter) { + pr_err("%s: adapter is NULL\n", __func__); + return; + } + + mwifiex_unregister(adapter); + pr_debug("info: %s: free adapter\n", __func__); +} + +/* + * This function gets firmware and initializes it. + * + * The main initialization steps followed are - + * - Download the correct firmware to card + * - Issue the init commands to firmware + */ +static void mwifiex_fw_dpc(const struct firmware *firmware, void *context) +{ + int ret; + char fmt[64]; + struct mwifiex_private *priv; + struct mwifiex_adapter *adapter = context; + struct mwifiex_fw_image fw; + + if (!firmware) { + dev_err(adapter->dev, + "Failed to get firmware %s\n", adapter->fw_name); + goto done; + } + + memset(&fw, 0, sizeof(struct mwifiex_fw_image)); + adapter->firmware = firmware; + fw.fw_buf = (u8 *) adapter->firmware->data; + fw.fw_len = adapter->firmware->size; + + if (adapter->if_ops.dnld_fw) + ret = adapter->if_ops.dnld_fw(adapter, &fw); + else + ret = mwifiex_dnld_fw(adapter, &fw); + if (ret == -1) + goto done; + + dev_notice(adapter->dev, "WLAN FW is active\n"); + + adapter->init_wait_q_woken = false; + ret = mwifiex_init_fw(adapter); + if (ret == -1) { + goto done; + } else if (!ret) { + adapter->hw_status = MWIFIEX_HW_STATUS_READY; + goto done; + } + /* Wait for mwifiex_init to complete */ + wait_event_interruptible(adapter->init_wait_q, + adapter->init_wait_q_woken); + if (adapter->hw_status != MWIFIEX_HW_STATUS_READY) + goto done; + + priv = adapter->priv[MWIFIEX_BSS_ROLE_STA]; + if (mwifiex_register_cfg80211(adapter)) { + dev_err(adapter->dev, "cannot register with cfg80211\n"); + goto err_init_fw; + } + + rtnl_lock(); + /* Create station interface by default */ + if (!mwifiex_add_virtual_intf(adapter->wiphy, "mlan%d", + NL80211_IFTYPE_STATION, NULL, NULL)) { + dev_err(adapter->dev, "cannot create default STA interface\n"); + goto err_add_intf; + } + rtnl_unlock(); + + mwifiex_drv_get_driver_version(adapter, fmt, sizeof(fmt) - 1); + dev_notice(adapter->dev, "driver_version = %s\n", fmt); + goto done; + +err_add_intf: + mwifiex_del_virtual_intf(adapter->wiphy, priv->wdev); + rtnl_unlock(); +err_init_fw: + pr_debug("info: %s: unregister device\n", __func__); + adapter->if_ops.unregister_dev(adapter); +done: + release_firmware(adapter->firmware); + complete(&adapter->fw_load); + return; +} + +/* + * This function initializes the hardware and gets firmware. + */ +static int mwifiex_init_hw_fw(struct mwifiex_adapter *adapter) +{ + int ret; + + init_completion(&adapter->fw_load); + ret = request_firmware_nowait(THIS_MODULE, 1, adapter->fw_name, + adapter->dev, GFP_KERNEL, adapter, + mwifiex_fw_dpc); + if (ret < 0) + dev_err(adapter->dev, + "request_firmware_nowait() returned error %d\n", ret); + return ret; +} + +/* + * CFG802.11 network device handler for open. + * + * Starts the data queue. + */ +static int +mwifiex_open(struct net_device *dev) +{ + netif_tx_start_all_queues(dev); + return 0; +} + +/* + * CFG802.11 network device handler for close. + */ +static int +mwifiex_close(struct net_device *dev) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + + if (priv->scan_request) { + dev_dbg(priv->adapter->dev, "aborting scan on ndo_stop\n"); + cfg80211_scan_done(priv->scan_request, 1); + priv->scan_request = NULL; + } + + return 0; +} + +/* + * Add buffer into wmm tx queue and queue work to transmit it. + */ +int mwifiex_queue_tx_pkt(struct mwifiex_private *priv, struct sk_buff *skb) +{ + struct netdev_queue *txq; + int index = mwifiex_1d_to_wmm_queue[skb->priority]; + + if (atomic_inc_return(&priv->wmm_tx_pending[index]) >= MAX_TX_PENDING) { + txq = netdev_get_tx_queue(priv->netdev, index); + if (!netif_tx_queue_stopped(txq)) { + netif_tx_stop_queue(txq); + dev_dbg(priv->adapter->dev, "stop queue: %d\n", index); + } + } + + atomic_inc(&priv->adapter->tx_pending); + mwifiex_wmm_add_buf_txqueue(priv, skb); + + if (priv->adapter->scan_delay_cnt) + atomic_set(&priv->adapter->is_tx_received, true); + + queue_work(priv->adapter->workqueue, &priv->adapter->main_work); + + return 0; +} + +/* + * CFG802.11 network device handler for data transmission. + */ +static int +mwifiex_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + struct sk_buff *new_skb; + struct mwifiex_txinfo *tx_info; + struct timeval tv; + + dev_dbg(priv->adapter->dev, "data: %lu BSS(%d-%d): Data <= kernel\n", + jiffies, priv->bss_type, priv->bss_num); + + if (priv->adapter->surprise_removed) { + kfree_skb(skb); + priv->stats.tx_dropped++; + return 0; + } + if (!skb->len || (skb->len > ETH_FRAME_LEN)) { + dev_err(priv->adapter->dev, "Tx: bad skb len %d\n", skb->len); + kfree_skb(skb); + priv->stats.tx_dropped++; + return 0; + } + if (skb_headroom(skb) < MWIFIEX_MIN_DATA_HEADER_LEN) { + dev_dbg(priv->adapter->dev, + "data: Tx: insufficient skb headroom %d\n", + skb_headroom(skb)); + /* Insufficient skb headroom - allocate a new skb */ + new_skb = + skb_realloc_headroom(skb, MWIFIEX_MIN_DATA_HEADER_LEN); + if (unlikely(!new_skb)) { + dev_err(priv->adapter->dev, "Tx: cannot alloca new_skb\n"); + kfree_skb(skb); + priv->stats.tx_dropped++; + return 0; + } + kfree_skb(skb); + skb = new_skb; + dev_dbg(priv->adapter->dev, "info: new skb headroomd %d\n", + skb_headroom(skb)); + } + + tx_info = MWIFIEX_SKB_TXCB(skb); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + + /* Record the current time the packet was queued; used to + * determine the amount of time the packet was queued in + * the driver before it was sent to the firmware. + * The delay is then sent along with the packet to the + * firmware for aggregate delay calculation for stats and + * MSDU lifetime expiry. + */ + do_gettimeofday(&tv); + skb->tstamp = timeval_to_ktime(tv); + + mwifiex_queue_tx_pkt(priv, skb); + + return 0; +} + +/* + * CFG802.11 network device handler for setting MAC address. + */ +static int +mwifiex_set_mac_address(struct net_device *dev, void *addr) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + struct sockaddr *hw_addr = addr; + int ret; + + memcpy(priv->curr_addr, hw_addr->sa_data, ETH_ALEN); + + /* Send request to firmware */ + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_802_11_MAC_ADDRESS, + HostCmd_ACT_GEN_SET, 0, NULL); + + if (!ret) + memcpy(priv->netdev->dev_addr, priv->curr_addr, ETH_ALEN); + else + dev_err(priv->adapter->dev, + "set mac address failed: ret=%d\n", ret); + + memcpy(dev->dev_addr, priv->curr_addr, ETH_ALEN); + + return ret; +} + +/* + * CFG802.11 network device handler for setting multicast list. + */ +static void mwifiex_set_multicast_list(struct net_device *dev) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + struct mwifiex_multicast_list mcast_list; + + if (dev->flags & IFF_PROMISC) { + mcast_list.mode = MWIFIEX_PROMISC_MODE; + } else if (dev->flags & IFF_ALLMULTI || + netdev_mc_count(dev) > MWIFIEX_MAX_MULTICAST_LIST_SIZE) { + mcast_list.mode = MWIFIEX_ALL_MULTI_MODE; + } else { + mcast_list.mode = MWIFIEX_MULTICAST_MODE; + mcast_list.num_multicast_addr = + mwifiex_copy_mcast_addr(&mcast_list, dev); + } + mwifiex_request_set_multicast_list(priv, &mcast_list); +} + +/* + * CFG802.11 network device handler for transmission timeout. + */ +static void +mwifiex_tx_timeout(struct net_device *dev) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + + priv->num_tx_timeout++; + priv->tx_timeout_cnt++; + dev_err(priv->adapter->dev, + "%lu : Tx timeout(#%d), bss_type-num = %d-%d\n", + jiffies, priv->tx_timeout_cnt, priv->bss_type, priv->bss_num); + mwifiex_set_trans_start(dev); + + if (priv->tx_timeout_cnt > TX_TIMEOUT_THRESHOLD && + priv->adapter->if_ops.card_reset) { + dev_err(priv->adapter->dev, + "tx_timeout_cnt exceeds threshold. Triggering card reset!\n"); + priv->adapter->if_ops.card_reset(priv->adapter); + } +} + +/* + * CFG802.11 network device handler for statistics retrieval. + */ +static struct net_device_stats *mwifiex_get_stats(struct net_device *dev) +{ + struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev); + + return &priv->stats; +} + +static u16 +mwifiex_netdev_select_wmm_queue(struct net_device *dev, struct sk_buff *skb) +{ + skb->priority = cfg80211_classify8021d(skb); + return mwifiex_1d_to_wmm_queue[skb->priority]; +} + +/* Network device handlers */ +static const struct net_device_ops mwifiex_netdev_ops = { + .ndo_open = mwifiex_open, + .ndo_stop = mwifiex_close, + .ndo_start_xmit = mwifiex_hard_start_xmit, + .ndo_set_mac_address = mwifiex_set_mac_address, + .ndo_tx_timeout = mwifiex_tx_timeout, + .ndo_get_stats = mwifiex_get_stats, + .ndo_set_rx_mode = mwifiex_set_multicast_list, + .ndo_select_queue = mwifiex_netdev_select_wmm_queue, +}; + +/* + * This function initializes the private structure parameters. + * + * The following wait queues are initialized - + * - IOCTL wait queue + * - Command wait queue + * - Statistics wait queue + * + * ...and the following default parameters are set - + * - Current key index : Set to 0 + * - Rate index : Set to auto + * - Media connected : Set to disconnected + * - Adhoc link sensed : Set to false + * - Nick name : Set to null + * - Number of Tx timeout : Set to 0 + * - Device address : Set to current address + * + * In addition, the CFG80211 work queue is also created. + */ +void mwifiex_init_priv_params(struct mwifiex_private *priv, + struct net_device *dev) +{ + netdev_attach_ops(dev, &mwifiex_netdev_ops); + dev->destructor = free_netdev; + /* Initialize private structure */ + priv->current_key_index = 0; + priv->media_connected = false; + memset(&priv->nick_name, 0, sizeof(priv->nick_name)); + memset(priv->mgmt_ie, 0, + sizeof(struct mwifiex_ie) * MAX_MGMT_IE_INDEX); + priv->beacon_idx = MWIFIEX_AUTO_IDX_MASK; + priv->proberesp_idx = MWIFIEX_AUTO_IDX_MASK; + priv->assocresp_idx = MWIFIEX_AUTO_IDX_MASK; + priv->rsn_idx = MWIFIEX_AUTO_IDX_MASK; + priv->num_tx_timeout = 0; + memcpy(dev->dev_addr, priv->curr_addr, ETH_ALEN); +} + +/* + * This function check if command is pending. + */ +int is_command_pending(struct mwifiex_adapter *adapter) +{ + unsigned long flags; + int is_cmd_pend_q_empty; + + spin_lock_irqsave(&adapter->cmd_pending_q_lock, flags); + is_cmd_pend_q_empty = list_empty(&adapter->cmd_pending_q); + spin_unlock_irqrestore(&adapter->cmd_pending_q_lock, flags); + + return !is_cmd_pend_q_empty; +} + +/* + * This is the main work queue function. + * + * It handles the main process, which in turn handles the complete + * driver operations. + */ +static void mwifiex_main_work_queue(struct work_struct *work) +{ + struct mwifiex_adapter *adapter = + container_of(work, struct mwifiex_adapter, main_work); + + if (adapter->surprise_removed) + return; + mwifiex_main_process(adapter); +} + +/* + * This function cancels all works in the queue and destroys + * the main workqueue. + */ +static void +mwifiex_terminate_workqueue(struct mwifiex_adapter *adapter) +{ + flush_workqueue(adapter->workqueue); + destroy_workqueue(adapter->workqueue); + adapter->workqueue = NULL; +} + +/* + * This function adds the card. + * + * This function follows the following major steps to set up the device - + * - Initialize software. This includes probing the card, registering + * the interface operations table, and allocating/initializing the + * adapter structure + * - Set up the netlink socket + * - Create and start the main work queue + * - Register the device + * - Initialize firmware and hardware + * - Add logical interfaces + */ +int +mwifiex_add_card(void *card, struct semaphore *sem, + struct mwifiex_if_ops *if_ops, u8 iface_type) +{ + struct mwifiex_adapter *adapter; + + if (down_interruptible(sem)) + goto exit_sem_err; + + if (mwifiex_register(card, if_ops, (void **)&adapter)) { + pr_err("%s: software init failed\n", __func__); + goto err_init_sw; + } + + adapter->iface_type = iface_type; + + adapter->hw_status = MWIFIEX_HW_STATUS_INITIALIZING; + adapter->surprise_removed = false; + init_waitqueue_head(&adapter->init_wait_q); + adapter->is_suspended = false; + adapter->hs_activated = false; + init_waitqueue_head(&adapter->hs_activate_wait_q); + adapter->cmd_wait_q_required = false; + init_waitqueue_head(&adapter->cmd_wait_q.wait); + adapter->cmd_wait_q.status = 0; + adapter->scan_wait_q_woken = false; + + adapter->workqueue = create_workqueue("MWIFIEX_WORK_QUEUE"); + if (!adapter->workqueue) + goto err_kmalloc; + + INIT_WORK(&adapter->main_work, mwifiex_main_work_queue); + + /* Register the device. Fill up the private data structure with relevant + information from the card and request for the required IRQ. */ + if (adapter->if_ops.register_dev(adapter)) { + pr_err("%s: failed to register mwifiex device\n", __func__); + goto err_registerdev; + } + + if (mwifiex_init_hw_fw(adapter)) { + pr_err("%s: firmware init failed\n", __func__); + goto err_init_fw; + } + + up(sem); + return 0; + +err_init_fw: + pr_debug("info: %s: unregister device\n", __func__); + if (adapter->if_ops.unregister_dev) + adapter->if_ops.unregister_dev(adapter); +err_registerdev: + adapter->surprise_removed = true; + mwifiex_terminate_workqueue(adapter); +err_kmalloc: + if ((adapter->hw_status == MWIFIEX_HW_STATUS_FW_READY) || + (adapter->hw_status == MWIFIEX_HW_STATUS_READY)) { + pr_debug("info: %s: shutdown mwifiex\n", __func__); + adapter->init_wait_q_woken = false; + + if (mwifiex_shutdown_drv(adapter) == -EINPROGRESS) + wait_event_interruptible(adapter->init_wait_q, + adapter->init_wait_q_woken); + } + + mwifiex_free_adapter(adapter); + +err_init_sw: + up(sem); + +exit_sem_err: + return -1; +} +EXPORT_SYMBOL_GPL(mwifiex_add_card); + +/* + * This function removes the card. + * + * This function follows the following major steps to remove the device - + * - Stop data traffic + * - Shutdown firmware + * - Remove the logical interfaces + * - Terminate the work queue + * - Unregister the device + * - Free the adapter structure + */ +int mwifiex_remove_card(struct mwifiex_adapter *adapter, struct semaphore *sem) +{ + struct mwifiex_private *priv = NULL; + int i; + + if (down_interruptible(sem)) + goto exit_sem_err; + + if (!adapter) + goto exit_remove; + + adapter->surprise_removed = true; + + /* Stop data */ + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (priv && priv->netdev) { + mwifiex_stop_net_dev_queue(priv->netdev, adapter); + if (netif_carrier_ok(priv->netdev)) + netif_carrier_off(priv->netdev); + } + } + + dev_dbg(adapter->dev, "cmd: calling mwifiex_shutdown_drv...\n"); + adapter->init_wait_q_woken = false; + + if (mwifiex_shutdown_drv(adapter) == -EINPROGRESS) + wait_event_interruptible(adapter->init_wait_q, + adapter->init_wait_q_woken); + dev_dbg(adapter->dev, "cmd: mwifiex_shutdown_drv done\n"); + if (atomic_read(&adapter->rx_pending) || + atomic_read(&adapter->tx_pending) || + atomic_read(&adapter->cmd_pending)) { + dev_err(adapter->dev, "rx_pending=%d, tx_pending=%d, " + "cmd_pending=%d\n", + atomic_read(&adapter->rx_pending), + atomic_read(&adapter->tx_pending), + atomic_read(&adapter->cmd_pending)); + } + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + + if (!priv) + continue; + + rtnl_lock(); + if (priv->wdev && priv->netdev) + mwifiex_del_virtual_intf(adapter->wiphy, priv->wdev); + rtnl_unlock(); + } + + priv = adapter->priv[0]; + if (!priv || !priv->wdev) + goto exit_remove; + + wiphy_unregister(priv->wdev->wiphy); + wiphy_free(priv->wdev->wiphy); + + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (priv) + kfree(priv->wdev); + } + + mwifiex_terminate_workqueue(adapter); + + /* Unregister device */ + dev_dbg(adapter->dev, "info: unregister device\n"); + if (adapter->if_ops.unregister_dev) + adapter->if_ops.unregister_dev(adapter); + /* Free adapter structure */ + dev_dbg(adapter->dev, "info: free adapter\n"); + mwifiex_free_adapter(adapter); + +exit_remove: + up(sem); +exit_sem_err: + return 0; +} +EXPORT_SYMBOL_GPL(mwifiex_remove_card); + +/* + * This function initializes the module. + * + * The debug FS is also initialized if configured. + */ +static int +mwifiex_init_module(void) +{ +#ifdef CONFIG_DEBUG_FS + mwifiex_debugfs_init(); +#endif + return 0; +} + +/* + * This function cleans up the module. + * + * The debug FS is removed if available. + */ +static void +mwifiex_cleanup_module(void) +{ +#ifdef CONFIG_DEBUG_FS + mwifiex_debugfs_remove(); +#endif +} + +module_init(mwifiex_init_module); +module_exit(mwifiex_cleanup_module); + +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_DESCRIPTION("Marvell WiFi-Ex Driver version " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/wireless/mwifiex/main.h b/drivers/net/wireless/mwifiex/main.h new file mode 100644 index 0000000..4ef67fc --- /dev/null +++ b/drivers/net/wireless/mwifiex/main.h @@ -0,0 +1,1128 @@ +/* + * Marvell Wireless LAN device driver: major data structures and prototypes + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _MWIFIEX_MAIN_H_ +#define _MWIFIEX_MAIN_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "pcie.h" + +extern const char driver_version[]; + +enum { + MWIFIEX_ASYNC_CMD, + MWIFIEX_SYNC_CMD +}; + +#define MWIFIEX_MAX_AP 64 + +#define MWIFIEX_DEFAULT_WATCHDOG_TIMEOUT (5 * HZ) + +#define MWIFIEX_TIMER_10S 10000 +#define MWIFIEX_TIMER_1S 1000 + +#define MAX_TX_PENDING 100 +#define LOW_TX_PENDING 80 + +#define MWIFIEX_UPLD_SIZE (2312) + +#define MAX_EVENT_SIZE 1024 + +#define ARP_FILTER_MAX_BUF_SIZE 68 + +#define MWIFIEX_KEY_BUFFER_SIZE 16 +#define MWIFIEX_DEFAULT_LISTEN_INTERVAL 10 +#define MWIFIEX_MAX_REGION_CODE 7 + +#define DEFAULT_BCN_AVG_FACTOR 8 +#define DEFAULT_DATA_AVG_FACTOR 8 + +#define FIRST_VALID_CHANNEL 0xff +#define DEFAULT_AD_HOC_CHANNEL 6 +#define DEFAULT_AD_HOC_CHANNEL_A 36 + +#define DEFAULT_BCN_MISS_TIMEOUT 5 + +#define MAX_SCAN_BEACON_BUFFER 8000 + +#define SCAN_BEACON_ENTRY_PAD 6 + +#define MWIFIEX_PASSIVE_SCAN_CHAN_TIME 110 +#define MWIFIEX_ACTIVE_SCAN_CHAN_TIME 30 +#define MWIFIEX_SPECIFIC_SCAN_CHAN_TIME 30 + +#define SCAN_RSSI(RSSI) (0x100 - ((u8)(RSSI))) + +#define MWIFIEX_MAX_TOTAL_SCAN_TIME (MWIFIEX_TIMER_10S - MWIFIEX_TIMER_1S) + +#define MWIFIEX_MAX_SCAN_DELAY_CNT 50 +#define MWIFIEX_MAX_EMPTY_TX_Q_CNT 10 +#define MWIFIEX_SCAN_DELAY_MSEC 20 + +#define MWIFIEX_MIN_TX_PENDING_TO_CANCEL_SCAN 2 + +#define RSN_GTK_OUI_OFFSET 2 + +#define MWIFIEX_OUI_NOT_PRESENT 0 +#define MWIFIEX_OUI_PRESENT 1 + +#define PKT_TYPE_MGMT 0xE5 + +/* + * Do not check for data_received for USB, as data_received + * is handled in mwifiex_usb_recv for USB + */ +#define IS_CARD_RX_RCVD(adapter) (adapter->cmd_resp_received || \ + adapter->event_received || \ + ((adapter->iface_type != MWIFIEX_USB) && \ + adapter->data_received) || \ + ((adapter->iface_type == MWIFIEX_USB) && \ + !skb_queue_empty(&adapter->usb_rx_data_q))) + +#define MWIFIEX_TYPE_CMD 1 +#define MWIFIEX_TYPE_DATA 0 +#define MWIFIEX_TYPE_EVENT 3 + +#define MAX_BITMAP_RATES_SIZE 10 + +#define MAX_CHANNEL_BAND_BG 14 +#define MAX_CHANNEL_BAND_A 165 + +#define MAX_FREQUENCY_BAND_BG 2484 + +#define MWIFIEX_EVENT_HEADER_LEN 4 +#define MWIFIEX_UAP_EVENT_EXTRA_HEADER 2 + +#define MWIFIEX_TYPE_LEN 4 +#define MWIFIEX_USB_TYPE_CMD 0xF00DFACE +#define MWIFIEX_USB_TYPE_DATA 0xBEADC0DE +#define MWIFIEX_USB_TYPE_EVENT 0xBEEFFACE + +/* Threshold for tx_timeout_cnt before we trigger a card reset */ +#define TX_TIMEOUT_THRESHOLD 6 + +struct mwifiex_dbg { + u32 num_cmd_host_to_card_failure; + u32 num_cmd_sleep_cfm_host_to_card_failure; + u32 num_tx_host_to_card_failure; + u32 num_event_deauth; + u32 num_event_disassoc; + u32 num_event_link_lost; + u32 num_cmd_deauth; + u32 num_cmd_assoc_success; + u32 num_cmd_assoc_failure; + u32 num_tx_timeout; + u32 num_cmd_timeout; + u16 timeout_cmd_id; + u16 timeout_cmd_act; + u16 last_cmd_id[DBG_CMD_NUM]; + u16 last_cmd_act[DBG_CMD_NUM]; + u16 last_cmd_index; + u16 last_cmd_resp_id[DBG_CMD_NUM]; + u16 last_cmd_resp_index; + u16 last_event[DBG_CMD_NUM]; + u16 last_event_index; +}; + +enum MWIFIEX_HARDWARE_STATUS { + MWIFIEX_HW_STATUS_READY, + MWIFIEX_HW_STATUS_INITIALIZING, + MWIFIEX_HW_STATUS_FW_READY, + MWIFIEX_HW_STATUS_INIT_DONE, + MWIFIEX_HW_STATUS_RESET, + MWIFIEX_HW_STATUS_CLOSING, + MWIFIEX_HW_STATUS_NOT_READY +}; + +enum MWIFIEX_802_11_POWER_MODE { + MWIFIEX_802_11_POWER_MODE_CAM, + MWIFIEX_802_11_POWER_MODE_PSP +}; + +struct mwifiex_tx_param { + u32 next_pkt_len; +}; + +enum MWIFIEX_PS_STATE { + PS_STATE_AWAKE, + PS_STATE_PRE_SLEEP, + PS_STATE_SLEEP_CFM, + PS_STATE_SLEEP +}; + +enum mwifiex_iface_type { + MWIFIEX_SDIO, + MWIFIEX_PCIE, + MWIFIEX_USB +}; + +struct mwifiex_add_ba_param { + u32 tx_win_size; + u32 rx_win_size; + u32 timeout; +}; + +struct mwifiex_tx_aggr { + u8 ampdu_user; + u8 ampdu_ap; + u8 amsdu; +}; + +struct mwifiex_ra_list_tbl { + struct list_head list; + struct sk_buff_head skb_head; + u8 ra[ETH_ALEN]; + u32 total_pkts_size; + u32 is_11n_enabled; + u16 max_amsdu; + u16 pkt_count; + u8 ba_packet_thr; +}; + +struct mwifiex_tid_tbl { + struct list_head ra_list; +}; + +#define WMM_HIGHEST_PRIORITY 7 +#define HIGH_PRIO_TID 7 +#define LOW_PRIO_TID 0 + +struct mwifiex_wmm_desc { + struct mwifiex_tid_tbl tid_tbl_ptr[MAX_NUM_TID]; + u32 packets_out[MAX_NUM_TID]; + /* spin lock to protect ra_list */ + spinlock_t ra_list_spinlock; + struct mwifiex_wmm_ac_status ac_status[IEEE80211_NUM_ACS]; + enum mwifiex_wmm_ac_e ac_down_graded_vals[IEEE80211_NUM_ACS]; + u32 drv_pkt_delay_max; + u8 queue_priority[IEEE80211_NUM_ACS]; + u32 user_pri_pkt_tx_ctrl[WMM_HIGHEST_PRIORITY + 1]; /* UP: 0 to 7 */ + /* Number of transmit packets queued */ + atomic_t tx_pkts_queued; + /* Tracks highest priority with a packet queued */ + atomic_t highest_queued_prio; +}; + +struct mwifiex_802_11_security { + u8 wpa_enabled; + u8 wpa2_enabled; + u8 wapi_enabled; + u8 wapi_key_on; + u8 wep_enabled; + u32 authentication_mode; + u8 is_authtype_auto; + u32 encryption_mode; +}; + +struct ieee_types_header { + u8 element_id; + u8 len; +} __packed; + +struct ieee_types_vendor_specific { + struct ieee_types_vendor_header vend_hdr; + u8 data[IEEE_MAX_IE_SIZE - sizeof(struct ieee_types_vendor_header)]; +} __packed; + +struct ieee_types_generic { + struct ieee_types_header ieee_hdr; + u8 data[IEEE_MAX_IE_SIZE - sizeof(struct ieee_types_header)]; +} __packed; + +struct mwifiex_bssdescriptor { + u8 mac_address[ETH_ALEN]; + struct cfg80211_ssid ssid; + u32 privacy; + s32 rssi; + u32 channel; + u32 freq; + u16 beacon_period; + u8 erp_flags; + u32 bss_mode; + u8 supported_rates[MWIFIEX_SUPPORTED_RATES]; + u8 data_rates[MWIFIEX_SUPPORTED_RATES]; + /* Network band. + * BAND_B(0x01): 'b' band + * BAND_G(0x02): 'g' band + * BAND_A(0X04): 'a' band + */ + u16 bss_band; + u64 fw_tsf; + u64 timestamp; + union ieee_types_phy_param_set phy_param_set; + union ieee_types_ss_param_set ss_param_set; + u16 cap_info_bitmap; + struct ieee_types_wmm_parameter wmm_ie; + u8 disable_11n; + struct ieee80211_ht_cap *bcn_ht_cap; + u16 ht_cap_offset; + struct ieee80211_ht_operation *bcn_ht_oper; + u16 ht_info_offset; + u8 *bcn_bss_co_2040; + u16 bss_co_2040_offset; + u8 *bcn_ext_cap; + u16 ext_cap_offset; + struct ieee80211_vht_cap *bcn_vht_cap; + u16 vht_cap_offset; + struct ieee80211_vht_operation *bcn_vht_oper; + u16 vht_info_offset; + struct ieee_types_oper_mode_ntf *oper_mode; + u16 oper_mode_offset; + u8 disable_11ac; + struct ieee_types_vendor_specific *bcn_wpa_ie; + u16 wpa_offset; + struct ieee_types_generic *bcn_rsn_ie; + u16 rsn_offset; + struct ieee_types_generic *bcn_wapi_ie; + u16 wapi_offset; + u8 *beacon_buf; + u32 beacon_buf_size; +}; + +struct mwifiex_current_bss_params { + struct mwifiex_bssdescriptor bss_descriptor; + u8 wmm_enabled; + u8 wmm_uapsd_enabled; + u8 band; + u32 num_of_rates; + u8 data_rates[MWIFIEX_SUPPORTED_RATES]; +}; + +struct mwifiex_sleep_params { + u16 sp_error; + u16 sp_offset; + u16 sp_stable_time; + u8 sp_cal_control; + u8 sp_ext_sleep_clk; + u16 sp_reserved; +}; + +struct mwifiex_sleep_period { + u16 period; + u16 reserved; +}; + +struct mwifiex_wep_key { + u32 length; + u32 key_index; + u32 key_length; + u8 key_material[MWIFIEX_KEY_BUFFER_SIZE]; +}; + +#define MAX_REGION_CHANNEL_NUM 2 + +struct mwifiex_chan_freq_power { + u16 channel; + u32 freq; + u16 max_tx_power; + u8 unsupported; +}; + +enum state_11d_t { + DISABLE_11D = 0, + ENABLE_11D = 1, +}; + +#define MWIFIEX_MAX_TRIPLET_802_11D 83 + +struct mwifiex_802_11d_domain_reg { + u8 country_code[IEEE80211_COUNTRY_STRING_LEN]; + u8 no_of_triplet; + struct ieee80211_country_ie_triplet + triplet[MWIFIEX_MAX_TRIPLET_802_11D]; +}; + +struct mwifiex_vendor_spec_cfg_ie { + u16 mask; + u16 flag; + u8 ie[MWIFIEX_MAX_VSIE_LEN]; +}; + +struct wps { + u8 session_enable; +}; + +struct mwifiex_roc_cfg { + u64 cookie; + struct ieee80211_channel chan; +}; + +struct mwifiex_adapter; +struct mwifiex_private; + +struct mwifiex_private { + struct mwifiex_adapter *adapter; + u8 bss_type; + u8 bss_role; + u8 bss_priority; + u8 bss_num; + u8 bss_started; + u8 frame_type; + u8 curr_addr[ETH_ALEN]; + u8 media_connected; + u32 num_tx_timeout; + /* track consecutive timeout */ + u8 tx_timeout_cnt; + struct net_device *netdev; + struct net_device_stats stats; + u16 curr_pkt_filter; + u32 bss_mode; + u32 pkt_tx_ctrl; + u16 tx_power_level; + u8 max_tx_power_level; + u8 min_tx_power_level; + u8 tx_rate; + u8 tx_htinfo; + u8 rxpd_htinfo; + u8 rxpd_rate; + u16 rate_bitmap; + u16 bitmap_rates[MAX_BITMAP_RATES_SIZE]; + u32 data_rate; + u8 is_data_rate_auto; + u16 bcn_avg_factor; + u16 data_avg_factor; + s16 data_rssi_last; + s16 data_nf_last; + s16 data_rssi_avg; + s16 data_nf_avg; + s16 bcn_rssi_last; + s16 bcn_nf_last; + s16 bcn_rssi_avg; + s16 bcn_nf_avg; + struct mwifiex_bssdescriptor *attempted_bss_desc; + struct cfg80211_ssid prev_ssid; + u8 prev_bssid[ETH_ALEN]; + struct mwifiex_current_bss_params curr_bss_params; + u16 beacon_period; + u8 dtim_period; + u16 listen_interval; + u16 atim_window; + u8 adhoc_channel; + u8 adhoc_is_link_sensed; + u8 adhoc_state; + struct mwifiex_802_11_security sec_info; + struct mwifiex_wep_key wep_key[NUM_WEP_KEYS]; + u16 wep_key_curr_index; + u8 wpa_ie[256]; + u8 wpa_ie_len; + u8 wpa_is_gtk_set; + struct host_cmd_ds_802_11_key_material aes_key; + u8 wapi_ie[256]; + u8 wapi_ie_len; + u8 *wps_ie; + u8 wps_ie_len; + u8 wmm_required; + u8 wmm_enabled; + u8 wmm_qosinfo; + struct mwifiex_wmm_desc wmm; + atomic_t wmm_tx_pending[IEEE80211_NUM_ACS]; + struct list_head sta_list; + /* spin lock for associated station list */ + spinlock_t sta_list_spinlock; + struct list_head tx_ba_stream_tbl_ptr; + /* spin lock for tx_ba_stream_tbl_ptr queue */ + spinlock_t tx_ba_stream_tbl_lock; + struct mwifiex_tx_aggr aggr_prio_tbl[MAX_NUM_TID]; + struct mwifiex_add_ba_param add_ba_param; + u16 rx_seq[MAX_NUM_TID]; + struct list_head rx_reorder_tbl_ptr; + /* spin lock for rx_reorder_tbl_ptr queue */ + spinlock_t rx_reorder_tbl_lock; + /* spin lock for Rx packets */ + spinlock_t rx_pkt_lock; + +#define MWIFIEX_ASSOC_RSP_BUF_SIZE 500 + u8 assoc_rsp_buf[MWIFIEX_ASSOC_RSP_BUF_SIZE]; + u32 assoc_rsp_size; + +#define MWIFIEX_GENIE_BUF_SIZE 256 + u8 gen_ie_buf[MWIFIEX_GENIE_BUF_SIZE]; + u8 gen_ie_buf_len; + + struct mwifiex_vendor_spec_cfg_ie vs_ie[MWIFIEX_MAX_VSIE_NUM]; + +#define MWIFIEX_ASSOC_TLV_BUF_SIZE 256 + u8 assoc_tlv_buf[MWIFIEX_ASSOC_TLV_BUF_SIZE]; + u8 assoc_tlv_buf_len; + + u8 *curr_bcn_buf; + u32 curr_bcn_size; + /* spin lock for beacon buffer */ + spinlock_t curr_bcn_buf_lock; + struct wireless_dev *wdev; + struct mwifiex_chan_freq_power cfp; + char version_str[128]; +#ifdef CONFIG_DEBUG_FS + struct dentry *dfs_dev_dir; +#endif + u8 nick_name[16]; + u16 current_key_index; + struct semaphore async_sem; + u8 report_scan_result; + struct cfg80211_scan_request *scan_request; + struct mwifiex_user_scan_cfg *user_scan_cfg; + u8 cfg_bssid[6]; + struct wps wps; + u8 scan_block; + s32 cqm_rssi_thold; + u32 cqm_rssi_hyst; + u8 subsc_evt_rssi_state; + struct mwifiex_ds_misc_subsc_evt async_subsc_evt_storage; + struct mwifiex_ie mgmt_ie[MAX_MGMT_IE_INDEX]; + u16 beacon_idx; + u16 proberesp_idx; + u16 assocresp_idx; + u16 rsn_idx; + struct timer_list scan_delay_timer; + u8 ap_11n_enabled; + u8 ap_11ac_enabled; + u32 mgmt_frame_mask; + struct mwifiex_roc_cfg roc_cfg; +}; + +enum mwifiex_ba_status { + BA_SETUP_NONE = 0, + BA_SETUP_INPROGRESS, + BA_SETUP_COMPLETE +}; + +struct mwifiex_tx_ba_stream_tbl { + struct list_head list; + int tid; + u8 ra[ETH_ALEN]; + enum mwifiex_ba_status ba_status; +}; + +struct mwifiex_rx_reorder_tbl; + +struct reorder_tmr_cnxt { + struct timer_list timer; + struct mwifiex_rx_reorder_tbl *ptr; + struct mwifiex_private *priv; +}; + +struct mwifiex_rx_reorder_tbl { + struct list_head list; + int tid; + u8 ta[ETH_ALEN]; + int start_win; + int win_size; + void **rx_reorder_ptr; + struct reorder_tmr_cnxt timer_context; + u8 flags; +}; + +struct mwifiex_bss_prio_node { + struct list_head list; + struct mwifiex_private *priv; +}; + +struct mwifiex_bss_prio_tbl { + struct list_head bss_prio_head; + /* spin lock for bss priority */ + spinlock_t bss_prio_lock; + struct mwifiex_bss_prio_node *bss_prio_cur; +}; + +struct cmd_ctrl_node { + struct list_head list; + struct mwifiex_private *priv; + u32 cmd_oid; + u32 cmd_flag; + struct sk_buff *cmd_skb; + struct sk_buff *resp_skb; + void *data_buf; + u32 wait_q_enabled; + struct sk_buff *skb; + u8 *condition; + u8 cmd_wait_q_woken; +}; + +struct mwifiex_bss_priv { + u8 band; + u64 fw_tsf; +}; + +/* This is AP specific structure which stores information + * about associated STA + */ +struct mwifiex_sta_node { + struct list_head list; + u8 mac_addr[ETH_ALEN]; + u8 is_wmm_enabled; + u8 is_11n_enabled; + u8 ampdu_sta[MAX_NUM_TID]; + u16 rx_seq[MAX_NUM_TID]; + u16 max_amsdu; +}; + +struct mwifiex_if_ops { + int (*init_if) (struct mwifiex_adapter *); + void (*cleanup_if) (struct mwifiex_adapter *); + int (*check_fw_status) (struct mwifiex_adapter *, u32); + int (*prog_fw) (struct mwifiex_adapter *, struct mwifiex_fw_image *); + int (*register_dev) (struct mwifiex_adapter *); + void (*unregister_dev) (struct mwifiex_adapter *); + int (*enable_int) (struct mwifiex_adapter *); + int (*process_int_status) (struct mwifiex_adapter *); + int (*host_to_card) (struct mwifiex_adapter *, u8, struct sk_buff *, + struct mwifiex_tx_param *); + int (*wakeup) (struct mwifiex_adapter *); + int (*wakeup_complete) (struct mwifiex_adapter *); + + /* Interface specific functions */ + void (*update_mp_end_port) (struct mwifiex_adapter *, u16); + void (*cleanup_mpa_buf) (struct mwifiex_adapter *); + int (*cmdrsp_complete) (struct mwifiex_adapter *, struct sk_buff *); + int (*event_complete) (struct mwifiex_adapter *, struct sk_buff *); + int (*data_complete) (struct mwifiex_adapter *, struct sk_buff *); + int (*init_fw_port) (struct mwifiex_adapter *); + int (*dnld_fw) (struct mwifiex_adapter *, struct mwifiex_fw_image *); + void (*card_reset) (struct mwifiex_adapter *); + int (*clean_pcie_ring) (struct mwifiex_adapter *adapter); +}; + +struct mwifiex_adapter { + u8 iface_type; + struct mwifiex_private *priv[MWIFIEX_MAX_BSS_NUM]; + u8 priv_num; + const struct firmware *firmware; + char fw_name[32]; + int winner; + struct device *dev; + struct wiphy *wiphy; + bool surprise_removed; + u32 fw_release_number; + u16 init_wait_q_woken; + wait_queue_head_t init_wait_q; + void *card; + struct mwifiex_if_ops if_ops; + atomic_t rx_pending; + atomic_t tx_pending; + atomic_t cmd_pending; + struct workqueue_struct *workqueue; + struct work_struct main_work; + struct mwifiex_bss_prio_tbl bss_prio_tbl[MWIFIEX_MAX_BSS_NUM]; + /* spin lock for init/shutdown */ + spinlock_t mwifiex_lock; + /* spin lock for main process */ + spinlock_t main_proc_lock; + u32 mwifiex_processing; + u16 tx_buf_size; + u16 curr_tx_buf_size; + u32 ioport; + enum MWIFIEX_HARDWARE_STATUS hw_status; + u16 number_of_antenna; + u32 fw_cap_info; + /* spin lock for interrupt handling */ + spinlock_t int_lock; + u8 int_status; + u32 event_cause; + struct sk_buff *event_skb; + u8 upld_buf[MWIFIEX_UPLD_SIZE]; + u8 data_sent; + u8 cmd_sent; + u8 cmd_resp_received; + u8 event_received; + u8 data_received; + u16 seq_num; + struct cmd_ctrl_node *cmd_pool; + struct cmd_ctrl_node *curr_cmd; + /* spin lock for command */ + spinlock_t mwifiex_cmd_lock; + u32 num_cmd_timeout; + u16 last_init_cmd; + struct timer_list cmd_timer; + struct list_head cmd_free_q; + /* spin lock for cmd_free_q */ + spinlock_t cmd_free_q_lock; + struct list_head cmd_pending_q; + /* spin lock for cmd_pending_q */ + spinlock_t cmd_pending_q_lock; + struct list_head scan_pending_q; + /* spin lock for scan_pending_q */ + spinlock_t scan_pending_q_lock; + struct sk_buff_head usb_rx_data_q; + u32 scan_processing; + u16 region_code; + struct mwifiex_802_11d_domain_reg domain_reg; + u16 scan_probes; + u32 scan_mode; + u16 specific_scan_time; + u16 active_scan_time; + u16 passive_scan_time; + u8 fw_bands; + u8 adhoc_start_band; + u8 config_bands; + struct mwifiex_chan_scan_param_set *scan_channels; + u8 tx_lock_flag; + struct mwifiex_sleep_params sleep_params; + struct mwifiex_sleep_period sleep_period; + u16 ps_mode; + u32 ps_state; + u8 need_to_wakeup; + u16 multiple_dtim; + u16 local_listen_interval; + u16 null_pkt_interval; + struct sk_buff *sleep_cfm; + u16 bcn_miss_time_out; + u16 adhoc_awake_period; + u8 is_deep_sleep; + u8 delay_null_pkt; + u16 delay_to_ps; + u16 enhanced_ps_mode; + u8 pm_wakeup_card_req; + u16 gen_null_pkt; + u16 pps_uapsd_mode; + u32 pm_wakeup_fw_try; + u8 is_hs_configured; + struct mwifiex_hs_config_param hs_cfg; + u8 hs_activated; + u16 hs_activate_wait_q_woken; + wait_queue_head_t hs_activate_wait_q; + bool is_suspended; + u8 event_body[MAX_EVENT_SIZE]; + u32 hw_dot_11n_dev_cap; + u8 hw_dev_mcs_support; + u8 adhoc_11n_enabled; + u8 sec_chan_offset; + struct mwifiex_dbg dbg; + u8 arp_filter[ARP_FILTER_MAX_BUF_SIZE]; + u32 arp_filter_size; + u16 cmd_wait_q_required; + struct mwifiex_wait_queue cmd_wait_q; + u8 scan_wait_q_woken; + spinlock_t queue_lock; /* lock for tx queues */ + struct completion fw_load; + u8 country_code[IEEE80211_COUNTRY_STRING_LEN]; + u16 max_mgmt_ie_index; + u8 scan_delay_cnt; + u8 empty_tx_q_cnt; + + /* 11AC */ + u32 is_hw_11ac_capable; + u32 hw_dot_11ac_dev_cap; + u32 hw_dot_11ac_mcs_support; + u32 usr_dot_11ac_dev_cap_bg; + u32 usr_dot_11ac_dev_cap_a; + u32 usr_dot_11ac_mcs_support; + + atomic_t is_tx_received; + atomic_t pending_bridged_pkts; +}; + +int mwifiex_init_lock_list(struct mwifiex_adapter *adapter); + +void mwifiex_set_trans_start(struct net_device *dev); + +void mwifiex_stop_net_dev_queue(struct net_device *netdev, + struct mwifiex_adapter *adapter); + +void mwifiex_wake_up_net_dev_queue(struct net_device *netdev, + struct mwifiex_adapter *adapter); + +int mwifiex_init_priv(struct mwifiex_private *priv); +void mwifiex_free_priv(struct mwifiex_private *priv); + +int mwifiex_init_fw(struct mwifiex_adapter *adapter); + +int mwifiex_init_fw_complete(struct mwifiex_adapter *adapter); + +int mwifiex_shutdown_drv(struct mwifiex_adapter *adapter); + +int mwifiex_shutdown_fw_complete(struct mwifiex_adapter *adapter); + +int mwifiex_dnld_fw(struct mwifiex_adapter *, struct mwifiex_fw_image *); + +int mwifiex_recv_packet(struct mwifiex_private *priv, struct sk_buff *skb); + +int mwifiex_process_mgmt_packet(struct mwifiex_private *priv, + struct sk_buff *skb); + +int mwifiex_process_event(struct mwifiex_adapter *adapter); + +int mwifiex_complete_cmd(struct mwifiex_adapter *adapter, + struct cmd_ctrl_node *cmd_node); + +int mwifiex_send_cmd_async(struct mwifiex_private *priv, uint16_t cmd_no, + u16 cmd_action, u32 cmd_oid, void *data_buf); + +int mwifiex_send_cmd_sync(struct mwifiex_private *priv, uint16_t cmd_no, + u16 cmd_action, u32 cmd_oid, void *data_buf); + +void mwifiex_cmd_timeout_func(unsigned long function_context); + +int mwifiex_get_debug_info(struct mwifiex_private *, + struct mwifiex_debug_info *); + +int mwifiex_alloc_cmd_buffer(struct mwifiex_adapter *adapter); +int mwifiex_free_cmd_buffer(struct mwifiex_adapter *adapter); +void mwifiex_cancel_all_pending_cmd(struct mwifiex_adapter *adapter); +void mwifiex_cancel_pending_ioctl(struct mwifiex_adapter *adapter); + +void mwifiex_insert_cmd_to_free_q(struct mwifiex_adapter *adapter, + struct cmd_ctrl_node *cmd_node); +void mwifiex_recycle_cmd_node(struct mwifiex_adapter *adapter, + struct cmd_ctrl_node *cmd_node); + +void mwifiex_insert_cmd_to_pending_q(struct mwifiex_adapter *adapter, + struct cmd_ctrl_node *cmd_node, + u32 addtail); + +int mwifiex_exec_next_cmd(struct mwifiex_adapter *adapter); +int mwifiex_process_cmdresp(struct mwifiex_adapter *adapter); +int mwifiex_handle_rx_packet(struct mwifiex_adapter *adapter, + struct sk_buff *skb); +int mwifiex_process_tx(struct mwifiex_private *priv, struct sk_buff *skb, + struct mwifiex_tx_param *tx_param); +int mwifiex_send_null_packet(struct mwifiex_private *priv, u8 flags); +int mwifiex_write_data_complete(struct mwifiex_adapter *adapter, + struct sk_buff *skb, int aggr, int status); +void mwifiex_clean_txrx(struct mwifiex_private *priv); +u8 mwifiex_check_last_packet_indication(struct mwifiex_private *priv); +void mwifiex_check_ps_cond(struct mwifiex_adapter *adapter); +void mwifiex_process_sleep_confirm_resp(struct mwifiex_adapter *, u8 *, + u32); +int mwifiex_cmd_enh_power_mode(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_action, uint16_t ps_bitmap, + struct mwifiex_ds_auto_ds *auto_ds); +int mwifiex_ret_enh_power_mode(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp, + struct mwifiex_ds_pm_cfg *pm_cfg); +void mwifiex_process_hs_config(struct mwifiex_adapter *adapter); +void mwifiex_hs_activated_event(struct mwifiex_private *priv, + u8 activated); +int mwifiex_ret_802_11_hs_cfg(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp); +int mwifiex_process_rx_packet(struct mwifiex_private *priv, + struct sk_buff *skb); +int mwifiex_sta_prepare_cmd(struct mwifiex_private *, uint16_t cmd_no, + u16 cmd_action, u32 cmd_oid, + void *data_buf, void *cmd_buf); +int mwifiex_uap_prepare_cmd(struct mwifiex_private *priv, uint16_t cmd_no, + u16 cmd_action, u32 cmd_oid, + void *data_buf, void *cmd_buf); +int mwifiex_process_sta_cmdresp(struct mwifiex_private *, u16 cmdresp_no, + struct host_cmd_ds_command *resp); +int mwifiex_process_sta_rx_packet(struct mwifiex_private *, + struct sk_buff *skb); +int mwifiex_process_uap_rx_packet(struct mwifiex_private *priv, + struct sk_buff *skb); +int mwifiex_handle_uap_rx_forward(struct mwifiex_private *priv, + struct sk_buff *skb); +int mwifiex_process_sta_event(struct mwifiex_private *); +int mwifiex_process_uap_event(struct mwifiex_private *); +struct mwifiex_sta_node * +mwifiex_get_sta_entry(struct mwifiex_private *priv, u8 *mac); +void mwifiex_delete_all_station_list(struct mwifiex_private *priv); +void *mwifiex_process_sta_txpd(struct mwifiex_private *, struct sk_buff *skb); +void *mwifiex_process_uap_txpd(struct mwifiex_private *, struct sk_buff *skb); +int mwifiex_sta_init_cmd(struct mwifiex_private *, u8 first_sta); +int mwifiex_cmd_802_11_scan(struct host_cmd_ds_command *cmd, + struct mwifiex_scan_cmd_config *scan_cfg); +void mwifiex_queue_scan_cmd(struct mwifiex_private *priv, + struct cmd_ctrl_node *cmd_node); +int mwifiex_ret_802_11_scan(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp); +s32 mwifiex_ssid_cmp(struct cfg80211_ssid *ssid1, struct cfg80211_ssid *ssid2); +int mwifiex_associate(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc); +int mwifiex_cmd_802_11_associate(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + struct mwifiex_bssdescriptor *bss_desc); +int mwifiex_ret_802_11_associate(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp); +void mwifiex_reset_connect_state(struct mwifiex_private *priv, u16 reason); +u8 mwifiex_band_to_radio_type(u8 band); +int mwifiex_deauthenticate(struct mwifiex_private *priv, u8 *mac); +int mwifiex_adhoc_start(struct mwifiex_private *priv, + struct cfg80211_ssid *adhoc_ssid); +int mwifiex_adhoc_join(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc); +int mwifiex_cmd_802_11_ad_hoc_start(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + struct cfg80211_ssid *req_ssid); +int mwifiex_cmd_802_11_ad_hoc_join(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + struct mwifiex_bssdescriptor *bss_desc); +int mwifiex_ret_802_11_ad_hoc(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp); +int mwifiex_cmd_802_11_bg_scan_query(struct host_cmd_ds_command *cmd); +struct mwifiex_chan_freq_power *mwifiex_get_cfp(struct mwifiex_private *priv, + u8 band, u16 channel, u32 freq); +u32 mwifiex_index_to_data_rate(struct mwifiex_private *priv, + u8 index, u8 ht_info); +u32 mwifiex_index_to_acs_data_rate(struct mwifiex_private *priv, + u8 index, u8 ht_info); +u32 mwifiex_find_freq_from_band_chan(u8, u8); +int mwifiex_cmd_append_vsie_tlv(struct mwifiex_private *priv, u16 vsie_mask, + u8 **buffer); +u32 mwifiex_get_active_data_rates(struct mwifiex_private *priv, + u8 *rates); +u32 mwifiex_get_supported_rates(struct mwifiex_private *priv, u8 *rates); +u8 mwifiex_is_rate_auto(struct mwifiex_private *priv); +extern u16 region_code_index[MWIFIEX_MAX_REGION_CODE]; +void mwifiex_save_curr_bcn(struct mwifiex_private *priv); +void mwifiex_free_curr_bcn(struct mwifiex_private *priv); +int mwifiex_cmd_get_hw_spec(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd); +int mwifiex_ret_get_hw_spec(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp); +int is_command_pending(struct mwifiex_adapter *adapter); +void mwifiex_init_priv_params(struct mwifiex_private *priv, + struct net_device *dev); +int mwifiex_set_secure_params(struct mwifiex_private *priv, + struct mwifiex_uap_bss_param *bss_config, + struct cfg80211_ap_settings *params); +void mwifiex_set_ht_params(struct mwifiex_private *priv, + struct mwifiex_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void mwifiex_set_vht_params(struct mwifiex_private *priv, + struct mwifiex_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void mwifiex_set_uap_rates(struct mwifiex_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void mwifiex_set_vht_width(struct mwifiex_private *priv, + enum nl80211_chan_width width, + bool ap_11ac_disable); +void +mwifiex_set_wmm_params(struct mwifiex_private *priv, + struct mwifiex_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params); +void mwifiex_set_ba_params(struct mwifiex_private *priv); +void mwifiex_set_11ac_ba_params(struct mwifiex_private *priv); + +/* + * This function checks if the queuing is RA based or not. + */ +static inline u8 +mwifiex_queuing_ra_based(struct mwifiex_private *priv) +{ + /* + * Currently we assume if we are in Infra, then DA=RA. This might not be + * true in the future + */ + if ((priv->bss_mode == NL80211_IFTYPE_STATION) && + (GET_BSS_ROLE(priv) == MWIFIEX_BSS_ROLE_STA)) + return false; + + return true; +} + +/* + * This function copies rates. + */ +static inline u32 +mwifiex_copy_rates(u8 *dest, u32 pos, u8 *src, int len) +{ + int i; + + for (i = 0; i < len && src[i]; i++, pos++) { + if (pos >= MWIFIEX_SUPPORTED_RATES) + break; + dest[pos] = src[i]; + } + + return pos; +} + +/* + * This function returns the correct private structure pointer based + * upon the BSS type and BSS number. + */ +static inline struct mwifiex_private * +mwifiex_get_priv_by_id(struct mwifiex_adapter *adapter, + u8 bss_num, u8 bss_type) +{ + int i; + + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i]) { + if ((adapter->priv[i]->bss_num == bss_num) && + (adapter->priv[i]->bss_type == bss_type)) + break; + } + } + return ((i < adapter->priv_num) ? adapter->priv[i] : NULL); +} + +/* + * This function returns the first available private structure pointer + * based upon the BSS role. + */ +static inline struct mwifiex_private * +mwifiex_get_priv(struct mwifiex_adapter *adapter, + enum mwifiex_bss_role bss_role) +{ + int i; + + for (i = 0; i < adapter->priv_num; i++) { + if (adapter->priv[i]) { + if (bss_role == MWIFIEX_BSS_ROLE_ANY || + GET_BSS_ROLE(adapter->priv[i]) == bss_role) + break; + } + } + + return ((i < adapter->priv_num) ? adapter->priv[i] : NULL); +} + +/* + * This function returns the driver private structure of a network device. + */ +static inline struct mwifiex_private * +mwifiex_netdev_get_priv(struct net_device *dev) +{ + return (struct mwifiex_private *) (*(unsigned long *) netdev_priv(dev)); +} + +/* + * This function checks if a skb holds a management frame. + */ +static inline bool mwifiex_is_skb_mgmt_frame(struct sk_buff *skb) +{ + return (*(u32 *)skb->data == PKT_TYPE_MGMT); +} + +int mwifiex_init_shutdown_fw(struct mwifiex_private *priv, + u32 func_init_shutdown); +int mwifiex_add_card(void *, struct semaphore *, struct mwifiex_if_ops *, u8); +int mwifiex_remove_card(struct mwifiex_adapter *, struct semaphore *); + +void mwifiex_get_version(struct mwifiex_adapter *adapter, char *version, + int maxlen); +int mwifiex_request_set_multicast_list(struct mwifiex_private *priv, + struct mwifiex_multicast_list *mcast_list); +int mwifiex_copy_mcast_addr(struct mwifiex_multicast_list *mlist, + struct net_device *dev); +int mwifiex_wait_queue_complete(struct mwifiex_adapter *adapter, + struct cmd_ctrl_node *cmd_queued); +int mwifiex_bss_start(struct mwifiex_private *priv, struct cfg80211_bss *bss, + struct cfg80211_ssid *req_ssid); +int mwifiex_cancel_hs(struct mwifiex_private *priv, int cmd_type); +int mwifiex_enable_hs(struct mwifiex_adapter *adapter); +int mwifiex_disable_auto_ds(struct mwifiex_private *priv); +int mwifiex_drv_get_data_rate(struct mwifiex_private *priv, u32 *rate); +int mwifiex_request_scan(struct mwifiex_private *priv, + struct cfg80211_ssid *req_ssid); +int mwifiex_scan_networks(struct mwifiex_private *priv, + const struct mwifiex_user_scan_cfg *user_scan_in); +int mwifiex_set_radio(struct mwifiex_private *priv, u8 option); + +int mwifiex_set_encode(struct mwifiex_private *priv, struct key_params *kp, + const u8 *key, int key_len, u8 key_index, + const u8 *mac_addr, int disable); + +int mwifiex_set_gen_ie(struct mwifiex_private *priv, u8 *ie, int ie_len); + +int mwifiex_get_ver_ext(struct mwifiex_private *priv); + +int mwifiex_remain_on_chan_cfg(struct mwifiex_private *priv, u16 action, + struct ieee80211_channel *chan, + unsigned int duration); + +int mwifiex_set_bss_role(struct mwifiex_private *priv, u8 bss_role); + +int mwifiex_get_stats_info(struct mwifiex_private *priv, + struct mwifiex_ds_get_stats *log); + +int mwifiex_reg_write(struct mwifiex_private *priv, u32 reg_type, + u32 reg_offset, u32 reg_value); + +int mwifiex_reg_read(struct mwifiex_private *priv, u32 reg_type, + u32 reg_offset, u32 *value); + +int mwifiex_eeprom_read(struct mwifiex_private *priv, u16 offset, u16 bytes, + u8 *value); + +int mwifiex_set_11n_httx_cfg(struct mwifiex_private *priv, int data); + +int mwifiex_get_11n_httx_cfg(struct mwifiex_private *priv, int *data); + +int mwifiex_set_tx_rate_cfg(struct mwifiex_private *priv, int tx_rate_index); + +int mwifiex_get_tx_rate_cfg(struct mwifiex_private *priv, int *tx_rate_index); + +int mwifiex_drv_set_power(struct mwifiex_private *priv, u32 *ps_mode); + +int mwifiex_drv_get_driver_version(struct mwifiex_adapter *adapter, + char *version, int max_len); + +int mwifiex_set_tx_power(struct mwifiex_private *priv, + struct mwifiex_power_cfg *power_cfg); + +int mwifiex_main_process(struct mwifiex_adapter *); + +int mwifiex_queue_tx_pkt(struct mwifiex_private *priv, struct sk_buff *skb); + +int mwifiex_get_bss_info(struct mwifiex_private *, + struct mwifiex_bss_info *); +int mwifiex_fill_new_bss_desc(struct mwifiex_private *priv, + struct cfg80211_bss *bss, + struct mwifiex_bssdescriptor *bss_desc); +int mwifiex_update_bss_desc_with_ie(struct mwifiex_adapter *adapter, + struct mwifiex_bssdescriptor *bss_entry); +int mwifiex_check_network_compatibility(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc); + +u8 mwifiex_chan_type_to_sec_chan_offset(enum nl80211_channel_type chan_type); + +struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, + const char *name, + enum nl80211_iftype type, + u32 *flags, + struct vif_params *params); +int mwifiex_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev); + +void mwifiex_set_sys_config_invalid_data(struct mwifiex_uap_bss_param *config); + +int mwifiex_add_wowlan_magic_pkt_filter(struct mwifiex_adapter *adapter); + +int mwifiex_set_mgmt_ies(struct mwifiex_private *priv, + struct cfg80211_beacon_data *data); +int mwifiex_del_mgmt_ies(struct mwifiex_private *priv); +u8 *mwifiex_11d_code_2_region(u8 code); + +extern const struct ethtool_ops mwifiex_ethtool_ops; + +#ifdef CONFIG_DEBUG_FS +void mwifiex_debugfs_init(void); +void mwifiex_debugfs_remove(void); + +void mwifiex_dev_debugfs_init(struct mwifiex_private *priv); +void mwifiex_dev_debugfs_remove(struct mwifiex_private *priv); +#endif +#endif /* !_MWIFIEX_MAIN_H_ */ diff --git a/drivers/net/wireless/mwifiex/pcie.c b/drivers/net/wireless/mwifiex/pcie.c new file mode 100644 index 0000000..20c9c4c --- /dev/null +++ b/drivers/net/wireless/mwifiex/pcie.c @@ -0,0 +1,2434 @@ +/* + * Marvell Wireless LAN device driver: PCIE specific handling + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" +#include "pcie.h" + +#define PCIE_VERSION "1.0" +#define DRV_NAME "Marvell mwifiex PCIe" + +static u8 user_rmmod; + +static struct mwifiex_if_ops pcie_ops; + +static struct semaphore add_remove_card_sem; + +static int +mwifiex_map_pci_memory(struct mwifiex_adapter *adapter, struct sk_buff *skb, + int size, int flags) +{ + struct pcie_service_card *card = adapter->card; + dma_addr_t buf_pa; + + buf_pa = pci_map_single(card->dev, skb->data, size, flags); + if (pci_dma_mapping_error(card->dev, buf_pa)) { + dev_err(adapter->dev, "failed to map pci memory!\n"); + return -1; + } + memcpy(skb->cb, &buf_pa, sizeof(dma_addr_t)); + return 0; +} + +/* + * This function reads sleep cookie and checks if FW is ready + */ +static bool mwifiex_pcie_ok_to_access_hw(struct mwifiex_adapter *adapter) +{ + u32 *cookie_addr; + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + + if (!reg->sleep_cookie) + return true; + + if (card->sleep_cookie_vbase) { + cookie_addr = (u32 *)card->sleep_cookie_vbase; + dev_dbg(adapter->dev, "info: ACCESS_HW: sleep cookie=0x%x\n", + *cookie_addr); + if (*cookie_addr == FW_AWAKE_COOKIE) + return true; + } + + return false; +} + +#ifdef CONFIG_PM +/* + * Kernel needs to suspend all functions separately. Therefore all + * registered functions must have drivers with suspend and resume + * methods. Failing that the kernel simply removes the whole card. + * + * If already not suspended, this function allocates and sends a host + * sleep activate request to the firmware and turns off the traffic. + */ +static int mwifiex_pcie_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct mwifiex_adapter *adapter; + struct pcie_service_card *card; + int hs_actived; + + if (pdev) { + card = (struct pcie_service_card *) pci_get_drvdata(pdev); + if (!card || !card->adapter) { + pr_err("Card or adapter structure is not valid\n"); + return 0; + } + } else { + pr_err("PCIE device is not specified\n"); + return 0; + } + + adapter = card->adapter; + + hs_actived = mwifiex_enable_hs(adapter); + + /* Indicate device suspended */ + adapter->is_suspended = true; + + return 0; +} + +/* + * Kernel needs to suspend all functions separately. Therefore all + * registered functions must have drivers with suspend and resume + * methods. Failing that the kernel simply removes the whole card. + * + * If already not resumed, this function turns on the traffic and + * sends a host sleep cancel request to the firmware. + */ +static int mwifiex_pcie_resume(struct pci_dev *pdev) +{ + struct mwifiex_adapter *adapter; + struct pcie_service_card *card; + + if (pdev) { + card = (struct pcie_service_card *) pci_get_drvdata(pdev); + if (!card || !card->adapter) { + pr_err("Card or adapter structure is not valid\n"); + return 0; + } + } else { + pr_err("PCIE device is not specified\n"); + return 0; + } + + adapter = card->adapter; + + if (!adapter->is_suspended) { + dev_warn(adapter->dev, "Device already resumed\n"); + return 0; + } + + adapter->is_suspended = false; + + mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA), + MWIFIEX_ASYNC_CMD); + + return 0; +} +#endif + +/* + * This function probes an mwifiex device and registers it. It allocates + * the card structure, enables PCIE function number and initiates the + * device registration and initialization procedure by adding a logical + * interface. + */ +static int mwifiex_pcie_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct pcie_service_card *card; + + pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", + pdev->vendor, pdev->device, pdev->revision); + + card = kzalloc(sizeof(struct pcie_service_card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->dev = pdev; + + if (ent->driver_data) { + struct mwifiex_pcie_device *data = (void *)ent->driver_data; + card->pcie.firmware = data->firmware; + card->pcie.reg = data->reg; + card->pcie.blksz_fw_dl = data->blksz_fw_dl; + } + + if (mwifiex_add_card(card, &add_remove_card_sem, &pcie_ops, + MWIFIEX_PCIE)) { + pr_err("%s failed\n", __func__); + kfree(card); + return -1; + } + + return 0; +} + +/* + * This function removes the interface and frees up the card structure. + */ +static void mwifiex_pcie_remove(struct pci_dev *pdev) +{ + struct pcie_service_card *card; + struct mwifiex_adapter *adapter; + struct mwifiex_private *priv; + int i; + + card = pci_get_drvdata(pdev); + if (!card) + return; + + adapter = card->adapter; + if (!adapter || !adapter->priv_num) + return; + + /* In case driver is removed when asynchronous FW load is in progress */ + wait_for_completion(&adapter->fw_load); + + if (user_rmmod) { +#ifdef CONFIG_PM + if (adapter->is_suspended) + mwifiex_pcie_resume(pdev); +#endif + + for (i = 0; i < adapter->priv_num; i++) + if ((GET_BSS_ROLE(adapter->priv[i]) == + MWIFIEX_BSS_ROLE_STA) && + adapter->priv[i]->media_connected) + mwifiex_deauthenticate(adapter->priv[i], NULL); + + priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); + + mwifiex_disable_auto_ds(priv); + + mwifiex_init_shutdown_fw(priv, MWIFIEX_FUNC_SHUTDOWN); + } + + mwifiex_remove_card(card->adapter, &add_remove_card_sem); + kfree(card); +} + +static DEFINE_PCI_DEVICE_TABLE(mwifiex_ids) = { + { + PCIE_VENDOR_ID_MARVELL, PCIE_DEVICE_ID_MARVELL_88W8766P, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, + .driver_data = (unsigned long) &mwifiex_pcie8766, + }, + { + PCIE_VENDOR_ID_MARVELL, PCIE_DEVICE_ID_MARVELL_88W8897, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, + .driver_data = (unsigned long) &mwifiex_pcie8897, + }, + {}, +}; + +MODULE_DEVICE_TABLE(pci, mwifiex_ids); + +/* PCI Device Driver */ +static struct pci_driver __refdata mwifiex_pcie = { + .name = "mwifiex_pcie", + .id_table = mwifiex_ids, + .probe = mwifiex_pcie_probe, + .remove = mwifiex_pcie_remove, +#ifdef CONFIG_PM + /* Power Management Hooks */ + .suspend = mwifiex_pcie_suspend, + .resume = mwifiex_pcie_resume, +#endif +}; + +/* + * This function writes data into PCIE card register. + */ +static int mwifiex_write_reg(struct mwifiex_adapter *adapter, int reg, u32 data) +{ + struct pcie_service_card *card = adapter->card; + + iowrite32(data, card->pci_mmap1 + reg); + + return 0; +} + +/* + * This function reads data from PCIE card register. + */ +static int mwifiex_read_reg(struct mwifiex_adapter *adapter, int reg, u32 *data) +{ + struct pcie_service_card *card = adapter->card; + + *data = ioread32(card->pci_mmap1 + reg); + + return 0; +} + +/* + * This function adds delay loop to ensure FW is awake before proceeding. + */ +static void mwifiex_pcie_dev_wakeup_delay(struct mwifiex_adapter *adapter) +{ + int i = 0; + + while (mwifiex_pcie_ok_to_access_hw(adapter)) { + i++; + usleep_range(10, 20); + /* 50ms max wait */ + if (i == 5000) + break; + } + + return; +} + +/* This function wakes up the card by reading fw_status register. */ +static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) +{ + u32 fw_status; + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + + dev_dbg(adapter->dev, "event: Wakeup device...\n"); + + if (reg->sleep_cookie) + mwifiex_pcie_dev_wakeup_delay(adapter); + + /* Reading fw_status register will wakeup device */ + if (mwifiex_read_reg(adapter, reg->fw_status, &fw_status)) { + dev_warn(adapter->dev, "Reading fw_status register failed\n"); + return -1; + } + + if (reg->sleep_cookie) { + mwifiex_pcie_dev_wakeup_delay(adapter); + dev_dbg(adapter->dev, "PCIE wakeup: Setting PS_STATE_AWAKE\n"); + adapter->ps_state = PS_STATE_AWAKE; + } + + return 0; +} + +/* + * This function is called after the card has woken up. + * + * The card configuration register is reset. + */ +static int mwifiex_pm_wakeup_card_complete(struct mwifiex_adapter *adapter) +{ + dev_dbg(adapter->dev, "cmd: Wakeup device completed\n"); + + return 0; +} + +/* + * This function disables the host interrupt. + * + * The host interrupt mask is read, the disable bit is reset and + * written back to the card host interrupt mask register. + */ +static int mwifiex_pcie_disable_host_int(struct mwifiex_adapter *adapter) +{ + if (mwifiex_pcie_ok_to_access_hw(adapter)) { + if (mwifiex_write_reg(adapter, PCIE_HOST_INT_MASK, + 0x00000000)) { + dev_warn(adapter->dev, "Disable host interrupt failed\n"); + return -1; + } + } + + return 0; +} + +/* + * This function enables the host interrupt. + * + * The host interrupt enable mask is written to the card + * host interrupt mask register. + */ +static int mwifiex_pcie_enable_host_int(struct mwifiex_adapter *adapter) +{ + if (mwifiex_pcie_ok_to_access_hw(adapter)) { + /* Simply write the mask to the register */ + if (mwifiex_write_reg(adapter, PCIE_HOST_INT_MASK, + HOST_INTR_MASK)) { + dev_warn(adapter->dev, "Enable host interrupt failed\n"); + return -1; + } + } + + return 0; +} + +/* + * This function initializes TX buffer ring descriptors + */ +static int mwifiex_init_txq_ring(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + struct mwifiex_pcie_buf_desc *desc; + struct mwifiex_pfu_buf_desc *desc2; + int i; + + for (i = 0; i < MWIFIEX_MAX_TXRX_BD; i++) { + card->tx_buf_list[i] = NULL; + if (reg->pfu_enabled) { + card->txbd_ring[i] = (void *)card->txbd_ring_vbase + + (sizeof(*desc2) * i); + desc2 = card->txbd_ring[i]; + memset(desc2, 0, sizeof(*desc2)); + } else { + card->txbd_ring[i] = (void *)card->txbd_ring_vbase + + (sizeof(*desc) * i); + desc = card->txbd_ring[i]; + memset(desc, 0, sizeof(*desc)); + } + } + + return 0; +} + +/* This function initializes RX buffer ring descriptors. Each SKB is allocated + * here and after mapping PCI memory, its physical address is assigned to + * PCIE Rx buffer descriptor's physical address. + */ +static int mwifiex_init_rxq_ring(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + struct sk_buff *skb; + struct mwifiex_pcie_buf_desc *desc; + struct mwifiex_pfu_buf_desc *desc2; + dma_addr_t buf_pa; + int i; + + for (i = 0; i < MWIFIEX_MAX_TXRX_BD; i++) { + /* Allocate skb here so that firmware can DMA data from it */ + skb = dev_alloc_skb(MWIFIEX_RX_DATA_BUF_SIZE); + if (!skb) { + dev_err(adapter->dev, + "Unable to allocate skb for RX ring.\n"); + kfree(card->rxbd_ring_vbase); + return -ENOMEM; + } + + if (mwifiex_map_pci_memory(adapter, skb, + MWIFIEX_RX_DATA_BUF_SIZE, + PCI_DMA_FROMDEVICE)) + return -1; + + MWIFIEX_SKB_PACB(skb, &buf_pa); + + dev_dbg(adapter->dev, + "info: RX ring: skb=%p len=%d data=%p buf_pa=%#x:%x\n", + skb, skb->len, skb->data, (u32)buf_pa, + (u32)((u64)buf_pa >> 32)); + + card->rx_buf_list[i] = skb; + if (reg->pfu_enabled) { + card->rxbd_ring[i] = (void *)card->rxbd_ring_vbase + + (sizeof(*desc2) * i); + desc2 = card->rxbd_ring[i]; + desc2->paddr = buf_pa; + desc2->len = (u16)skb->len; + desc2->frag_len = (u16)skb->len; + desc2->flags = reg->ring_flag_eop | reg->ring_flag_sop; + desc2->offset = 0; + } else { + card->rxbd_ring[i] = (void *)(card->rxbd_ring_vbase + + (sizeof(*desc) * i)); + desc = card->rxbd_ring[i]; + desc->paddr = buf_pa; + desc->len = (u16)skb->len; + desc->flags = 0; + } + } + + return 0; +} + +/* This function initializes event buffer ring descriptors. Each SKB is + * allocated here and after mapping PCI memory, its physical address is assigned + * to PCIE Rx buffer descriptor's physical address + */ +static int mwifiex_pcie_init_evt_ring(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + struct mwifiex_evt_buf_desc *desc; + struct sk_buff *skb; + dma_addr_t buf_pa; + int i; + + for (i = 0; i < MWIFIEX_MAX_EVT_BD; i++) { + /* Allocate skb here so that firmware can DMA data from it */ + skb = dev_alloc_skb(MAX_EVENT_SIZE); + if (!skb) { + dev_err(adapter->dev, + "Unable to allocate skb for EVENT buf.\n"); + kfree(card->evtbd_ring_vbase); + return -ENOMEM; + } + skb_put(skb, MAX_EVENT_SIZE); + + if (mwifiex_map_pci_memory(adapter, skb, MAX_EVENT_SIZE, + PCI_DMA_FROMDEVICE)) + return -1; + + MWIFIEX_SKB_PACB(skb, &buf_pa); + + dev_dbg(adapter->dev, + "info: EVT ring: skb=%p len=%d data=%p buf_pa=%#x:%x\n", + skb, skb->len, skb->data, (u32)buf_pa, + (u32)((u64)buf_pa >> 32)); + + card->evt_buf_list[i] = skb; + card->evtbd_ring[i] = (void *)(card->evtbd_ring_vbase + + (sizeof(*desc) * i)); + desc = card->evtbd_ring[i]; + desc->paddr = buf_pa; + desc->len = (u16)skb->len; + desc->flags = 0; + } + + return 0; +} + +/* This function cleans up TX buffer rings. If any of the buffer list has valid + * SKB address, associated SKB is freed. + */ +static void mwifiex_cleanup_txq_ring(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + struct sk_buff *skb; + struct mwifiex_pcie_buf_desc *desc; + struct mwifiex_pfu_buf_desc *desc2; + int i; + + for (i = 0; i < MWIFIEX_MAX_TXRX_BD; i++) { + if (reg->pfu_enabled) { + desc2 = card->txbd_ring[i]; + if (card->tx_buf_list[i]) { + skb = card->tx_buf_list[i]; + pci_unmap_single(card->dev, desc2->paddr, + skb->len, PCI_DMA_TODEVICE); + dev_kfree_skb_any(skb); + } + memset(desc2, 0, sizeof(*desc2)); + } else { + desc = card->txbd_ring[i]; + if (card->tx_buf_list[i]) { + skb = card->tx_buf_list[i]; + pci_unmap_single(card->dev, desc->paddr, + skb->len, PCI_DMA_TODEVICE); + dev_kfree_skb_any(skb); + } + memset(desc, 0, sizeof(*desc)); + } + card->tx_buf_list[i] = NULL; + } + + return; +} + +/* This function cleans up RX buffer rings. If any of the buffer list has valid + * SKB address, associated SKB is freed. + */ +static void mwifiex_cleanup_rxq_ring(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + struct mwifiex_pcie_buf_desc *desc; + struct mwifiex_pfu_buf_desc *desc2; + struct sk_buff *skb; + int i; + + for (i = 0; i < MWIFIEX_MAX_TXRX_BD; i++) { + if (reg->pfu_enabled) { + desc2 = card->rxbd_ring[i]; + if (card->rx_buf_list[i]) { + skb = card->rx_buf_list[i]; + pci_unmap_single(card->dev, desc2->paddr, + skb->len, PCI_DMA_FROMDEVICE); + dev_kfree_skb_any(skb); + } + memset(desc2, 0, sizeof(*desc2)); + } else { + desc = card->rxbd_ring[i]; + if (card->rx_buf_list[i]) { + skb = card->rx_buf_list[i]; + pci_unmap_single(card->dev, desc->paddr, + skb->len, PCI_DMA_FROMDEVICE); + dev_kfree_skb_any(skb); + } + memset(desc, 0, sizeof(*desc)); + } + card->rx_buf_list[i] = NULL; + } + + return; +} + +/* This function cleans up event buffer rings. If any of the buffer list has + * valid SKB address, associated SKB is freed. + */ +static void mwifiex_cleanup_evt_ring(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + struct mwifiex_evt_buf_desc *desc; + struct sk_buff *skb; + int i; + + for (i = 0; i < MWIFIEX_MAX_EVT_BD; i++) { + desc = card->evtbd_ring[i]; + if (card->evt_buf_list[i]) { + skb = card->evt_buf_list[i]; + pci_unmap_single(card->dev, desc->paddr, MAX_EVENT_SIZE, + PCI_DMA_FROMDEVICE); + dev_kfree_skb_any(skb); + } + card->evt_buf_list[i] = NULL; + memset(desc, 0, sizeof(*desc)); + } + + return; +} + +/* This function creates buffer descriptor ring for TX + */ +static int mwifiex_pcie_create_txbd_ring(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + + /* + * driver maintaines the write pointer and firmware maintaines the read + * pointer. The write pointer starts at 0 (zero) while the read pointer + * starts at zero with rollover bit set + */ + card->txbd_wrptr = 0; + + if (reg->pfu_enabled) + card->txbd_rdptr = 0; + else + card->txbd_rdptr |= reg->tx_rollover_ind; + + /* allocate shared memory for the BD ring and divide the same in to + several descriptors */ + if (reg->pfu_enabled) + card->txbd_ring_size = sizeof(struct mwifiex_pfu_buf_desc) * + MWIFIEX_MAX_TXRX_BD; + else + card->txbd_ring_size = sizeof(struct mwifiex_pcie_buf_desc) * + MWIFIEX_MAX_TXRX_BD; + + dev_dbg(adapter->dev, "info: txbd_ring: Allocating %d bytes\n", + card->txbd_ring_size); + card->txbd_ring_vbase = pci_alloc_consistent(card->dev, + card->txbd_ring_size, + &card->txbd_ring_pbase); + if (!card->txbd_ring_vbase) { + dev_err(adapter->dev, + "allocate consistent memory (%d bytes) failed!\n", + card->txbd_ring_size); + return -ENOMEM; + } + dev_dbg(adapter->dev, + "info: txbd_ring - base: %p, pbase: %#x:%x, len: %x\n", + card->txbd_ring_vbase, (unsigned int)card->txbd_ring_pbase, + (u32)((u64)card->txbd_ring_pbase >> 32), card->txbd_ring_size); + + return mwifiex_init_txq_ring(adapter); +} + +static int mwifiex_pcie_delete_txbd_ring(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + + mwifiex_cleanup_txq_ring(adapter); + + if (card->txbd_ring_vbase) + pci_free_consistent(card->dev, card->txbd_ring_size, + card->txbd_ring_vbase, + card->txbd_ring_pbase); + card->txbd_ring_size = 0; + card->txbd_wrptr = 0; + card->txbd_rdptr = 0 | reg->tx_rollover_ind; + card->txbd_ring_vbase = NULL; + card->txbd_ring_pbase = 0; + + return 0; +} + +/* + * This function creates buffer descriptor ring for RX + */ +static int mwifiex_pcie_create_rxbd_ring(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + + /* + * driver maintaines the read pointer and firmware maintaines the write + * pointer. The write pointer starts at 0 (zero) while the read pointer + * starts at zero with rollover bit set + */ + card->rxbd_wrptr = 0; + card->rxbd_rdptr = reg->rx_rollover_ind; + + if (reg->pfu_enabled) + card->rxbd_ring_size = sizeof(struct mwifiex_pfu_buf_desc) * + MWIFIEX_MAX_TXRX_BD; + else + card->rxbd_ring_size = sizeof(struct mwifiex_pcie_buf_desc) * + MWIFIEX_MAX_TXRX_BD; + + dev_dbg(adapter->dev, "info: rxbd_ring: Allocating %d bytes\n", + card->rxbd_ring_size); + card->rxbd_ring_vbase = pci_alloc_consistent(card->dev, + card->rxbd_ring_size, + &card->rxbd_ring_pbase); + if (!card->rxbd_ring_vbase) { + dev_err(adapter->dev, + "allocate consistent memory (%d bytes) failed!\n", + card->rxbd_ring_size); + return -ENOMEM; + } + + dev_dbg(adapter->dev, + "info: rxbd_ring - base: %p, pbase: %#x:%x, len: %#x\n", + card->rxbd_ring_vbase, (u32)card->rxbd_ring_pbase, + (u32)((u64)card->rxbd_ring_pbase >> 32), + card->rxbd_ring_size); + + return mwifiex_init_rxq_ring(adapter); +} + +/* + * This function deletes Buffer descriptor ring for RX + */ +static int mwifiex_pcie_delete_rxbd_ring(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + + mwifiex_cleanup_rxq_ring(adapter); + + if (card->rxbd_ring_vbase) + pci_free_consistent(card->dev, card->rxbd_ring_size, + card->rxbd_ring_vbase, + card->rxbd_ring_pbase); + card->rxbd_ring_size = 0; + card->rxbd_wrptr = 0; + card->rxbd_rdptr = 0 | reg->rx_rollover_ind; + card->rxbd_ring_vbase = NULL; + card->rxbd_ring_pbase = 0; + + return 0; +} + +/* + * This function creates buffer descriptor ring for Events + */ +static int mwifiex_pcie_create_evtbd_ring(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + + /* + * driver maintaines the read pointer and firmware maintaines the write + * pointer. The write pointer starts at 0 (zero) while the read pointer + * starts at zero with rollover bit set + */ + card->evtbd_wrptr = 0; + card->evtbd_rdptr = reg->evt_rollover_ind; + + card->evtbd_ring_size = sizeof(struct mwifiex_evt_buf_desc) * + MWIFIEX_MAX_EVT_BD; + + dev_dbg(adapter->dev, "info: evtbd_ring: Allocating %d bytes\n", + card->evtbd_ring_size); + card->evtbd_ring_vbase = pci_alloc_consistent(card->dev, + card->evtbd_ring_size, + &card->evtbd_ring_pbase); + if (!card->evtbd_ring_vbase) { + dev_err(adapter->dev, + "allocate consistent memory (%d bytes) failed!\n", + card->evtbd_ring_size); + return -ENOMEM; + } + + dev_dbg(adapter->dev, + "info: CMDRSP/EVT bd_ring - base: %p pbase: %#x:%x len: %#x\n", + card->evtbd_ring_vbase, (u32)card->evtbd_ring_pbase, + (u32)((u64)card->evtbd_ring_pbase >> 32), + card->evtbd_ring_size); + + return mwifiex_pcie_init_evt_ring(adapter); +} + +/* + * This function deletes Buffer descriptor ring for Events + */ +static int mwifiex_pcie_delete_evtbd_ring(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + + mwifiex_cleanup_evt_ring(adapter); + + if (card->evtbd_ring_vbase) + pci_free_consistent(card->dev, card->evtbd_ring_size, + card->evtbd_ring_vbase, + card->evtbd_ring_pbase); + card->evtbd_wrptr = 0; + card->evtbd_rdptr = 0 | reg->evt_rollover_ind; + card->evtbd_ring_size = 0; + card->evtbd_ring_vbase = NULL; + card->evtbd_ring_pbase = 0; + + return 0; +} + +/* + * This function allocates a buffer for CMDRSP + */ +static int mwifiex_pcie_alloc_cmdrsp_buf(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + struct sk_buff *skb; + + /* Allocate memory for receiving command response data */ + skb = dev_alloc_skb(MWIFIEX_UPLD_SIZE); + if (!skb) { + dev_err(adapter->dev, + "Unable to allocate skb for command response data.\n"); + return -ENOMEM; + } + skb_put(skb, MWIFIEX_UPLD_SIZE); + if (mwifiex_map_pci_memory(adapter, skb, MWIFIEX_UPLD_SIZE, + PCI_DMA_FROMDEVICE)) + return -1; + + card->cmdrsp_buf = skb; + + return 0; +} + +/* + * This function deletes a buffer for CMDRSP + */ +static int mwifiex_pcie_delete_cmdrsp_buf(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card; + dma_addr_t buf_pa; + + if (!adapter) + return 0; + + card = adapter->card; + + if (card && card->cmdrsp_buf) { + MWIFIEX_SKB_PACB(card->cmdrsp_buf, &buf_pa); + pci_unmap_single(card->dev, buf_pa, MWIFIEX_UPLD_SIZE, + PCI_DMA_FROMDEVICE); + dev_kfree_skb_any(card->cmdrsp_buf); + } + + if (card && card->cmd_buf) { + MWIFIEX_SKB_PACB(card->cmd_buf, &buf_pa); + pci_unmap_single(card->dev, buf_pa, card->cmd_buf->len, + PCI_DMA_TODEVICE); + } + return 0; +} + +/* + * This function allocates a buffer for sleep cookie + */ +static int mwifiex_pcie_alloc_sleep_cookie_buf(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + + card->sleep_cookie_vbase = pci_alloc_consistent(card->dev, sizeof(u32), + &card->sleep_cookie_pbase); + if (!card->sleep_cookie_vbase) { + dev_err(adapter->dev, "pci_alloc_consistent failed!\n"); + return -ENOMEM; + } + /* Init val of Sleep Cookie */ + *(u32 *)card->sleep_cookie_vbase = FW_AWAKE_COOKIE; + + dev_dbg(adapter->dev, "alloc_scook: sleep cookie=0x%x\n", + *((u32 *)card->sleep_cookie_vbase)); + + return 0; +} + +/* + * This function deletes buffer for sleep cookie + */ +static int mwifiex_pcie_delete_sleep_cookie_buf(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card; + + if (!adapter) + return 0; + + card = adapter->card; + + if (card && card->sleep_cookie_vbase) { + pci_free_consistent(card->dev, sizeof(u32), + card->sleep_cookie_vbase, + card->sleep_cookie_pbase); + card->sleep_cookie_vbase = NULL; + } + + return 0; +} + +/* This function flushes the TX buffer descriptor ring + * This function defined as handler is also called while cleaning TXRX + * during disconnect/ bss stop. + */ +static int mwifiex_clean_pcie_ring_buf(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + + if (!mwifiex_pcie_txbd_empty(card, card->txbd_rdptr)) { + card->txbd_flush = 1; + /* write pointer already set at last send + * send dnld-rdy intr again, wait for completion. + */ + if (mwifiex_write_reg(adapter, PCIE_CPU_INT_EVENT, + CPU_INTR_DNLD_RDY)) { + dev_err(adapter->dev, + "failed to assert dnld-rdy interrupt.\n"); + return -1; + } + } + return 0; +} + +/* + * This function unmaps and frees downloaded data buffer + */ +static int mwifiex_pcie_send_data_complete(struct mwifiex_adapter *adapter) +{ + struct sk_buff *skb; + dma_addr_t buf_pa; + u32 wrdoneidx, rdptr, num_tx_buffs, unmap_count = 0; + struct mwifiex_pcie_buf_desc *desc; + struct mwifiex_pfu_buf_desc *desc2; + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + + if (!mwifiex_pcie_ok_to_access_hw(adapter)) + mwifiex_pm_wakeup_card(adapter); + + /* Read the TX ring read pointer set by firmware */ + if (mwifiex_read_reg(adapter, reg->tx_rdptr, &rdptr)) { + dev_err(adapter->dev, + "SEND COMP: failed to read reg->tx_rdptr\n"); + return -1; + } + + dev_dbg(adapter->dev, "SEND COMP: rdptr_prev=0x%x, rdptr=0x%x\n", + card->txbd_rdptr, rdptr); + + num_tx_buffs = MWIFIEX_MAX_TXRX_BD << reg->tx_start_ptr; + /* free from previous txbd_rdptr to current txbd_rdptr */ + while (((card->txbd_rdptr & reg->tx_mask) != + (rdptr & reg->tx_mask)) || + ((card->txbd_rdptr & reg->tx_rollover_ind) != + (rdptr & reg->tx_rollover_ind))) { + wrdoneidx = (card->txbd_rdptr & reg->tx_mask) >> + reg->tx_start_ptr; + + skb = card->tx_buf_list[wrdoneidx]; + if (skb) { + dev_dbg(adapter->dev, + "SEND COMP: Detach skb %p at txbd_rdidx=%d\n", + skb, wrdoneidx); + MWIFIEX_SKB_PACB(skb, &buf_pa); + pci_unmap_single(card->dev, buf_pa, skb->len, + PCI_DMA_TODEVICE); + + unmap_count++; + + if (card->txbd_flush) + mwifiex_write_data_complete(adapter, skb, 0, + -1); + else + mwifiex_write_data_complete(adapter, skb, 0, 0); + } + + card->tx_buf_list[wrdoneidx] = NULL; + + if (reg->pfu_enabled) { + desc2 = (void *)card->txbd_ring[wrdoneidx]; + memset(desc2, 0, sizeof(*desc2)); + } else { + desc = card->txbd_ring[wrdoneidx]; + memset(desc, 0, sizeof(*desc)); + } + switch (card->dev->device) { + case PCIE_DEVICE_ID_MARVELL_88W8766P: + card->txbd_rdptr++; + break; + case PCIE_DEVICE_ID_MARVELL_88W8897: + card->txbd_rdptr += reg->ring_tx_start_ptr; + break; + } + + + if ((card->txbd_rdptr & reg->tx_mask) == num_tx_buffs) + card->txbd_rdptr = ((card->txbd_rdptr & + reg->tx_rollover_ind) ^ + reg->tx_rollover_ind); + } + + if (unmap_count) + adapter->data_sent = false; + + if (card->txbd_flush) { + if (mwifiex_pcie_txbd_empty(card, card->txbd_rdptr)) + card->txbd_flush = 0; + else + mwifiex_clean_pcie_ring_buf(adapter); + } + + return 0; +} + +/* This function sends data buffer to device. First 4 bytes of payload + * are filled with payload length and payload type. Then this payload + * is mapped to PCI device memory. Tx ring pointers are advanced accordingly. + * Download ready interrupt to FW is deffered if Tx ring is not full and + * additional payload can be accomodated. + */ +static int +mwifiex_pcie_send_data(struct mwifiex_adapter *adapter, struct sk_buff *skb, + struct mwifiex_tx_param *tx_param) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + u32 wrindx, num_tx_buffs, rx_val; + int ret; + dma_addr_t buf_pa; + struct mwifiex_pcie_buf_desc *desc = NULL; + struct mwifiex_pfu_buf_desc *desc2 = NULL; + __le16 *tmp; + + if (!(skb->data && skb->len)) { + dev_err(adapter->dev, "%s(): invalid parameter <%p, %#x>\n", + __func__, skb->data, skb->len); + return -1; + } + + if (!mwifiex_pcie_ok_to_access_hw(adapter)) + mwifiex_pm_wakeup_card(adapter); + + num_tx_buffs = MWIFIEX_MAX_TXRX_BD << reg->tx_start_ptr; + dev_dbg(adapter->dev, "info: SEND DATA: \n", + card->txbd_rdptr, card->txbd_wrptr); + if (mwifiex_pcie_txbd_not_full(card)) { + u8 *payload; + + adapter->data_sent = true; + payload = skb->data; + tmp = (__le16 *)&payload[0]; + *tmp = cpu_to_le16((u16)skb->len); + tmp = (__le16 *)&payload[2]; + *tmp = cpu_to_le16(MWIFIEX_TYPE_DATA); + + if (mwifiex_map_pci_memory(adapter, skb, skb->len , + PCI_DMA_TODEVICE)) + return -1; + + wrindx = (card->txbd_wrptr & reg->tx_mask) >> reg->tx_start_ptr; + MWIFIEX_SKB_PACB(skb, &buf_pa); + card->tx_buf_list[wrindx] = skb; + + if (reg->pfu_enabled) { + desc2 = (void *)card->txbd_ring[wrindx]; + desc2->paddr = buf_pa; + desc2->len = (u16)skb->len; + desc2->frag_len = (u16)skb->len; + desc2->offset = 0; + desc2->flags = MWIFIEX_BD_FLAG_FIRST_DESC | + MWIFIEX_BD_FLAG_LAST_DESC; + } else { + desc = card->txbd_ring[wrindx]; + desc->paddr = buf_pa; + desc->len = (u16)skb->len; + desc->flags = MWIFIEX_BD_FLAG_FIRST_DESC | + MWIFIEX_BD_FLAG_LAST_DESC; + } + + switch (card->dev->device) { + case PCIE_DEVICE_ID_MARVELL_88W8766P: + card->txbd_wrptr++; + break; + case PCIE_DEVICE_ID_MARVELL_88W8897: + card->txbd_wrptr += reg->ring_tx_start_ptr; + break; + } + + if ((card->txbd_wrptr & reg->tx_mask) == num_tx_buffs) + card->txbd_wrptr = ((card->txbd_wrptr & + reg->tx_rollover_ind) ^ + reg->tx_rollover_ind); + + rx_val = card->rxbd_rdptr & reg->rx_wrap_mask; + /* Write the TX ring write pointer in to reg->tx_wrptr */ + if (mwifiex_write_reg(adapter, reg->tx_wrptr, + card->txbd_wrptr | rx_val)) { + dev_err(adapter->dev, + "SEND DATA: failed to write reg->tx_wrptr\n"); + ret = -1; + goto done_unmap; + } + if ((mwifiex_pcie_txbd_not_full(card)) && + tx_param->next_pkt_len) { + /* have more packets and TxBD still can hold more */ + dev_dbg(adapter->dev, + "SEND DATA: delay dnld-rdy interrupt.\n"); + adapter->data_sent = false; + } else { + /* Send the TX ready interrupt */ + if (mwifiex_write_reg(adapter, PCIE_CPU_INT_EVENT, + CPU_INTR_DNLD_RDY)) { + dev_err(adapter->dev, + "SEND DATA: failed to assert dnld-rdy interrupt.\n"); + ret = -1; + goto done_unmap; + } + } + dev_dbg(adapter->dev, "info: SEND DATA: Updated and sent packet to firmware successfully\n", + card->txbd_rdptr, card->txbd_wrptr); + } else { + dev_dbg(adapter->dev, + "info: TX Ring full, can't send packets to fw\n"); + adapter->data_sent = true; + /* Send the TX ready interrupt */ + if (mwifiex_write_reg(adapter, PCIE_CPU_INT_EVENT, + CPU_INTR_DNLD_RDY)) + dev_err(adapter->dev, + "SEND DATA: failed to assert door-bell intr\n"); + return -EBUSY; + } + + return -EINPROGRESS; +done_unmap: + MWIFIEX_SKB_PACB(skb, &buf_pa); + pci_unmap_single(card->dev, buf_pa, skb->len, PCI_DMA_TODEVICE); + card->tx_buf_list[wrindx] = NULL; + if (reg->pfu_enabled) + memset(desc2, 0, sizeof(*desc2)); + else + memset(desc, 0, sizeof(*desc)); + + return ret; +} + +/* + * This function handles received buffer ring and + * dispatches packets to upper + */ +static int mwifiex_pcie_process_recv_data(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + u32 wrptr, rd_index, tx_val; + dma_addr_t buf_pa; + int ret = 0; + struct sk_buff *skb_tmp = NULL; + struct mwifiex_pcie_buf_desc *desc; + struct mwifiex_pfu_buf_desc *desc2; + + if (!mwifiex_pcie_ok_to_access_hw(adapter)) + mwifiex_pm_wakeup_card(adapter); + + /* Read the RX ring Write pointer set by firmware */ + if (mwifiex_read_reg(adapter, reg->rx_wrptr, &wrptr)) { + dev_err(adapter->dev, + "RECV DATA: failed to read reg->rx_wrptr\n"); + ret = -1; + goto done; + } + card->rxbd_wrptr = wrptr; + + while (((wrptr & reg->rx_mask) != + (card->rxbd_rdptr & reg->rx_mask)) || + ((wrptr & reg->rx_rollover_ind) == + (card->rxbd_rdptr & reg->rx_rollover_ind))) { + struct sk_buff *skb_data; + u16 rx_len; + __le16 pkt_len; + + rd_index = card->rxbd_rdptr & reg->rx_mask; + skb_data = card->rx_buf_list[rd_index]; + + MWIFIEX_SKB_PACB(skb_data, &buf_pa); + pci_unmap_single(card->dev, buf_pa, MWIFIEX_RX_DATA_BUF_SIZE, + PCI_DMA_FROMDEVICE); + card->rx_buf_list[rd_index] = NULL; + + /* Get data length from interface header - + * first 2 bytes for len, next 2 bytes is for type + */ + pkt_len = *((__le16 *)skb_data->data); + rx_len = le16_to_cpu(pkt_len); + skb_put(skb_data, rx_len); + dev_dbg(adapter->dev, + "info: RECV DATA: Rd=%#x, Wr=%#x, Len=%d\n", + card->rxbd_rdptr, wrptr, rx_len); + skb_pull(skb_data, INTF_HEADER_LEN); + mwifiex_handle_rx_packet(adapter, skb_data); + + skb_tmp = dev_alloc_skb(MWIFIEX_RX_DATA_BUF_SIZE); + if (!skb_tmp) { + dev_err(adapter->dev, + "Unable to allocate skb.\n"); + return -ENOMEM; + } + + if (mwifiex_map_pci_memory(adapter, skb_tmp, + MWIFIEX_RX_DATA_BUF_SIZE, + PCI_DMA_FROMDEVICE)) + return -1; + + MWIFIEX_SKB_PACB(skb_tmp, &buf_pa); + + dev_dbg(adapter->dev, + "RECV DATA: Attach new sk_buff %p at rxbd_rdidx=%d\n", + skb_tmp, rd_index); + card->rx_buf_list[rd_index] = skb_tmp; + + if (reg->pfu_enabled) { + desc2 = (void *)card->rxbd_ring[rd_index]; + desc2->paddr = buf_pa; + desc2->len = skb_tmp->len; + desc2->frag_len = skb_tmp->len; + desc2->offset = 0; + desc2->flags = reg->ring_flag_sop | reg->ring_flag_eop; + } else { + desc = card->rxbd_ring[rd_index]; + desc->paddr = buf_pa; + desc->len = skb_tmp->len; + desc->flags = 0; + } + + if ((++card->rxbd_rdptr & reg->rx_mask) == + MWIFIEX_MAX_TXRX_BD) { + card->rxbd_rdptr = ((card->rxbd_rdptr & + reg->rx_rollover_ind) ^ + reg->rx_rollover_ind); + } + dev_dbg(adapter->dev, "info: RECV DATA: \n", + card->rxbd_rdptr, wrptr); + + tx_val = card->txbd_wrptr & reg->tx_wrap_mask; + /* Write the RX ring read pointer in to reg->rx_rdptr */ + if (mwifiex_write_reg(adapter, reg->rx_rdptr, + card->rxbd_rdptr | tx_val)) { + dev_err(adapter->dev, + "RECV DATA: failed to write reg->rx_rdptr\n"); + ret = -1; + goto done; + } + + /* Read the RX ring Write pointer set by firmware */ + if (mwifiex_read_reg(adapter, reg->rx_wrptr, &wrptr)) { + dev_err(adapter->dev, + "RECV DATA: failed to read reg->rx_wrptr\n"); + ret = -1; + goto done; + } + dev_dbg(adapter->dev, + "info: RECV DATA: Rcvd packet from fw successfully\n"); + card->rxbd_wrptr = wrptr; + } + +done: + return ret; +} + +/* + * This function downloads the boot command to device + */ +static int +mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) +{ + dma_addr_t buf_pa; + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + + if (!(skb->data && skb->len)) { + dev_err(adapter->dev, + "Invalid parameter in %s <%p. len %d>\n", + __func__, skb->data, skb->len); + return -1; + } + + if (mwifiex_map_pci_memory(adapter, skb, skb->len , PCI_DMA_TODEVICE)) + return -1; + + MWIFIEX_SKB_PACB(skb, &buf_pa); + + /* Write the lower 32bits of the physical address to low command + * address scratch register + */ + if (mwifiex_write_reg(adapter, reg->cmd_addr_lo, (u32)buf_pa)) { + dev_err(adapter->dev, + "%s: failed to write download command to boot code.\n", + __func__); + pci_unmap_single(card->dev, buf_pa, MWIFIEX_UPLD_SIZE, + PCI_DMA_TODEVICE); + return -1; + } + + /* Write the upper 32bits of the physical address to high command + * address scratch register + */ + if (mwifiex_write_reg(adapter, reg->cmd_addr_hi, + (u32)((u64)buf_pa >> 32))) { + dev_err(adapter->dev, + "%s: failed to write download command to boot code.\n", + __func__); + pci_unmap_single(card->dev, buf_pa, MWIFIEX_UPLD_SIZE, + PCI_DMA_TODEVICE); + return -1; + } + + /* Write the command length to cmd_size scratch register */ + if (mwifiex_write_reg(adapter, reg->cmd_size, skb->len)) { + dev_err(adapter->dev, + "%s: failed to write command len to cmd_size scratch reg\n", + __func__); + pci_unmap_single(card->dev, buf_pa, MWIFIEX_UPLD_SIZE, + PCI_DMA_TODEVICE); + return -1; + } + + /* Ring the door bell */ + if (mwifiex_write_reg(adapter, PCIE_CPU_INT_EVENT, + CPU_INTR_DOOR_BELL)) { + dev_err(adapter->dev, + "%s: failed to assert door-bell intr\n", __func__); + pci_unmap_single(card->dev, buf_pa, + MWIFIEX_UPLD_SIZE, PCI_DMA_TODEVICE); + return -1; + } + + return 0; +} + +/* This function init rx port in firmware which in turn enables to receive data + * from device before transmitting any packet. + */ +static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; + + /* Write the RX ring read pointer in to reg->rx_rdptr */ + if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | + tx_wrap)) { + dev_err(adapter->dev, + "RECV DATA: failed to write reg->rx_rdptr\n"); + return -1; + } + return 0; +} + +/* This function downloads commands to the device + */ +static int +mwifiex_pcie_send_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + int ret = 0; + dma_addr_t cmd_buf_pa, cmdrsp_buf_pa; + u8 *payload = (u8 *)skb->data; + + if (!(skb->data && skb->len)) { + dev_err(adapter->dev, "Invalid parameter in %s <%p, %#x>\n", + __func__, skb->data, skb->len); + return -1; + } + + /* Make sure a command response buffer is available */ + if (!card->cmdrsp_buf) { + dev_err(adapter->dev, + "No response buffer available, send command failed\n"); + return -EBUSY; + } + + if (!mwifiex_pcie_ok_to_access_hw(adapter)) + mwifiex_pm_wakeup_card(adapter); + + adapter->cmd_sent = true; + + *(__le16 *)&payload[0] = cpu_to_le16((u16)skb->len); + *(__le16 *)&payload[2] = cpu_to_le16(MWIFIEX_TYPE_CMD); + + if (mwifiex_map_pci_memory(adapter, skb, skb->len, PCI_DMA_TODEVICE)) + return -1; + + card->cmd_buf = skb; + + /* To send a command, the driver will: + 1. Write the 64bit physical address of the data buffer to + cmd response address low + cmd response address high + 2. Ring the door bell (i.e. set the door bell interrupt) + + In response to door bell interrupt, the firmware will perform + the DMA of the command packet (first header to obtain the total + length and then rest of the command). + */ + + if (card->cmdrsp_buf) { + MWIFIEX_SKB_PACB(card->cmdrsp_buf, &cmdrsp_buf_pa); + /* Write the lower 32bits of the cmdrsp buffer physical + address */ + if (mwifiex_write_reg(adapter, reg->cmdrsp_addr_lo, + (u32)cmdrsp_buf_pa)) { + dev_err(adapter->dev, + "Failed to write download cmd to boot code.\n"); + ret = -1; + goto done; + } + /* Write the upper 32bits of the cmdrsp buffer physical + address */ + if (mwifiex_write_reg(adapter, reg->cmdrsp_addr_hi, + (u32)((u64)cmdrsp_buf_pa >> 32))) { + dev_err(adapter->dev, + "Failed to write download cmd to boot code.\n"); + ret = -1; + goto done; + } + } + + MWIFIEX_SKB_PACB(card->cmd_buf, &cmd_buf_pa); + /* Write the lower 32bits of the physical address to reg->cmd_addr_lo */ + if (mwifiex_write_reg(adapter, reg->cmd_addr_lo, + (u32)cmd_buf_pa)) { + dev_err(adapter->dev, + "Failed to write download cmd to boot code.\n"); + ret = -1; + goto done; + } + /* Write the upper 32bits of the physical address to reg->cmd_addr_hi */ + if (mwifiex_write_reg(adapter, reg->cmd_addr_hi, + (u32)((u64)cmd_buf_pa >> 32))) { + dev_err(adapter->dev, + "Failed to write download cmd to boot code.\n"); + ret = -1; + goto done; + } + + /* Write the command length to reg->cmd_size */ + if (mwifiex_write_reg(adapter, reg->cmd_size, + card->cmd_buf->len)) { + dev_err(adapter->dev, + "Failed to write cmd len to reg->cmd_size\n"); + ret = -1; + goto done; + } + + /* Ring the door bell */ + if (mwifiex_write_reg(adapter, PCIE_CPU_INT_EVENT, + CPU_INTR_DOOR_BELL)) { + dev_err(adapter->dev, + "Failed to assert door-bell intr\n"); + ret = -1; + goto done; + } + +done: + if (ret) + adapter->cmd_sent = false; + + return 0; +} + +/* + * This function handles command complete interrupt + */ +static int mwifiex_pcie_process_cmd_complete(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + struct sk_buff *skb = card->cmdrsp_buf; + int count = 0; + u16 rx_len; + __le16 pkt_len; + dma_addr_t buf_pa; + + dev_dbg(adapter->dev, "info: Rx CMD Response\n"); + + MWIFIEX_SKB_PACB(skb, &buf_pa); + pci_unmap_single(card->dev, buf_pa, MWIFIEX_UPLD_SIZE, + PCI_DMA_FROMDEVICE); + + pkt_len = *((__le16 *)skb->data); + rx_len = le16_to_cpu(pkt_len); + skb_trim(skb, rx_len); + skb_pull(skb, INTF_HEADER_LEN); + + if (!adapter->curr_cmd) { + if (adapter->ps_state == PS_STATE_SLEEP_CFM) { + mwifiex_process_sleep_confirm_resp(adapter, skb->data, + skb->len); + while (reg->sleep_cookie && (count++ < 10) && + mwifiex_pcie_ok_to_access_hw(adapter)) + usleep_range(50, 60); + } else { + dev_err(adapter->dev, + "There is no command but got cmdrsp\n"); + } + memcpy(adapter->upld_buf, skb->data, + min_t(u32, MWIFIEX_SIZE_OF_CMD_BUFFER, skb->len)); + skb_push(skb, INTF_HEADER_LEN); + if (mwifiex_map_pci_memory(adapter, skb, MWIFIEX_UPLD_SIZE, + PCI_DMA_FROMDEVICE)) + return -1; + + MWIFIEX_SKB_PACB(skb, &buf_pa); + } else if (mwifiex_pcie_ok_to_access_hw(adapter)) { + adapter->curr_cmd->resp_skb = skb; + adapter->cmd_resp_received = true; + /* Take the pointer and set it to CMD node and will + return in the response complete callback */ + card->cmdrsp_buf = NULL; + + /* Clear the cmd-rsp buffer address in scratch registers. This + will prevent firmware from writing to the same response + buffer again. */ + if (mwifiex_write_reg(adapter, reg->cmdrsp_addr_lo, 0)) { + dev_err(adapter->dev, + "cmd_done: failed to clear cmd_rsp_addr_lo\n"); + return -1; + } + /* Write the upper 32bits of the cmdrsp buffer physical + address */ + if (mwifiex_write_reg(adapter, reg->cmdrsp_addr_hi, 0)) { + dev_err(adapter->dev, + "cmd_done: failed to clear cmd_rsp_addr_hi\n"); + return -1; + } + } + + return 0; +} + +/* + * Command Response processing complete handler + */ +static int mwifiex_pcie_cmdrsp_complete(struct mwifiex_adapter *adapter, + struct sk_buff *skb) +{ + struct pcie_service_card *card = adapter->card; + dma_addr_t buf_pa; + struct sk_buff *skb_tmp; + + if (skb) { + card->cmdrsp_buf = skb; + skb_push(card->cmdrsp_buf, INTF_HEADER_LEN); + if (mwifiex_map_pci_memory(adapter, skb, MWIFIEX_UPLD_SIZE, + PCI_DMA_FROMDEVICE)) + return -1; + } + + skb_tmp = card->cmd_buf; + if (skb_tmp) { + MWIFIEX_SKB_PACB(skb_tmp, &buf_pa); + pci_unmap_single(card->dev, buf_pa, skb_tmp->len, + PCI_DMA_FROMDEVICE); + card->cmd_buf = NULL; + } + + return 0; +} + +/* + * This function handles firmware event ready interrupt + */ +static int mwifiex_pcie_process_event_ready(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + u32 rdptr = card->evtbd_rdptr & MWIFIEX_EVTBD_MASK; + u32 wrptr, event; + dma_addr_t buf_pa; + struct mwifiex_evt_buf_desc *desc; + + if (!mwifiex_pcie_ok_to_access_hw(adapter)) + mwifiex_pm_wakeup_card(adapter); + + if (adapter->event_received) { + dev_dbg(adapter->dev, "info: Event being processed, " + "do not process this interrupt just yet\n"); + return 0; + } + + if (rdptr >= MWIFIEX_MAX_EVT_BD) { + dev_dbg(adapter->dev, "info: Invalid read pointer...\n"); + return -1; + } + + /* Read the event ring write pointer set by firmware */ + if (mwifiex_read_reg(adapter, reg->evt_wrptr, &wrptr)) { + dev_err(adapter->dev, + "EventReady: failed to read reg->evt_wrptr\n"); + return -1; + } + + dev_dbg(adapter->dev, "info: EventReady: Initial ", + card->evtbd_rdptr, wrptr); + if (((wrptr & MWIFIEX_EVTBD_MASK) != (card->evtbd_rdptr + & MWIFIEX_EVTBD_MASK)) || + ((wrptr & reg->evt_rollover_ind) == + (card->evtbd_rdptr & reg->evt_rollover_ind))) { + struct sk_buff *skb_cmd; + __le16 data_len = 0; + u16 evt_len; + + dev_dbg(adapter->dev, "info: Read Index: %d\n", rdptr); + skb_cmd = card->evt_buf_list[rdptr]; + MWIFIEX_SKB_PACB(skb_cmd, &buf_pa); + pci_unmap_single(card->dev, buf_pa, MAX_EVENT_SIZE, + PCI_DMA_FROMDEVICE); + + /* Take the pointer and set it to event pointer in adapter + and will return back after event handling callback */ + card->evt_buf_list[rdptr] = NULL; + desc = card->evtbd_ring[rdptr]; + memset(desc, 0, sizeof(*desc)); + + event = *(u32 *) &skb_cmd->data[INTF_HEADER_LEN]; + adapter->event_cause = event; + /* The first 4bytes will be the event transfer header + len is 2 bytes followed by type which is 2 bytes */ + memcpy(&data_len, skb_cmd->data, sizeof(__le16)); + evt_len = le16_to_cpu(data_len); + + skb_pull(skb_cmd, INTF_HEADER_LEN); + dev_dbg(adapter->dev, "info: Event length: %d\n", evt_len); + + if ((evt_len > 0) && (evt_len < MAX_EVENT_SIZE)) + memcpy(adapter->event_body, skb_cmd->data + + MWIFIEX_EVENT_HEADER_LEN, evt_len - + MWIFIEX_EVENT_HEADER_LEN); + + adapter->event_received = true; + adapter->event_skb = skb_cmd; + + /* Do not update the event read pointer here, wait till the + buffer is released. This is just to make things simpler, + we need to find a better method of managing these buffers. + */ + } + + return 0; +} + +/* + * Event processing complete handler + */ +static int mwifiex_pcie_event_complete(struct mwifiex_adapter *adapter, + struct sk_buff *skb) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + int ret = 0; + u32 rdptr = card->evtbd_rdptr & MWIFIEX_EVTBD_MASK; + u32 wrptr; + dma_addr_t buf_pa; + struct mwifiex_evt_buf_desc *desc; + + if (!skb) + return 0; + + if (rdptr >= MWIFIEX_MAX_EVT_BD) { + dev_err(adapter->dev, "event_complete: Invalid rdptr 0x%x\n", + rdptr); + return -EINVAL; + } + + /* Read the event ring write pointer set by firmware */ + if (mwifiex_read_reg(adapter, reg->evt_wrptr, &wrptr)) { + dev_err(adapter->dev, + "event_complete: failed to read reg->evt_wrptr\n"); + return -1; + } + + if (!card->evt_buf_list[rdptr]) { + skb_push(skb, INTF_HEADER_LEN); + if (mwifiex_map_pci_memory(adapter, skb, + MAX_EVENT_SIZE, + PCI_DMA_FROMDEVICE)) + return -1; + MWIFIEX_SKB_PACB(skb, &buf_pa); + card->evt_buf_list[rdptr] = skb; + MWIFIEX_SKB_PACB(skb, &buf_pa); + desc = card->evtbd_ring[rdptr]; + desc->paddr = buf_pa; + desc->len = (u16)skb->len; + desc->flags = 0; + skb = NULL; + } else { + dev_dbg(adapter->dev, + "info: ERROR: buf still valid at index %d, <%p, %p>\n", + rdptr, card->evt_buf_list[rdptr], skb); + } + + if ((++card->evtbd_rdptr & MWIFIEX_EVTBD_MASK) == MWIFIEX_MAX_EVT_BD) { + card->evtbd_rdptr = ((card->evtbd_rdptr & + reg->evt_rollover_ind) ^ + reg->evt_rollover_ind); + } + + dev_dbg(adapter->dev, "info: Updated ", + card->evtbd_rdptr, wrptr); + + /* Write the event ring read pointer in to reg->evt_rdptr */ + if (mwifiex_write_reg(adapter, reg->evt_rdptr, + card->evtbd_rdptr)) { + dev_err(adapter->dev, + "event_complete: failed to read reg->evt_rdptr\n"); + return -1; + } + + dev_dbg(adapter->dev, "info: Check Events Again\n"); + ret = mwifiex_pcie_process_event_ready(adapter); + + return ret; +} + +/* + * This function downloads the firmware to the card. + * + * Firmware is downloaded to the card in blocks. Every block download + * is tested for CRC errors, and retried a number of times before + * returning failure. + */ +static int mwifiex_prog_fw_w_helper(struct mwifiex_adapter *adapter, + struct mwifiex_fw_image *fw) +{ + int ret; + u8 *firmware = fw->fw_buf; + u32 firmware_len = fw->fw_len; + u32 offset = 0; + struct sk_buff *skb; + u32 txlen, tx_blocks = 0, tries, len; + u32 block_retry_cnt = 0; + dma_addr_t buf_pa; + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + + if (!firmware || !firmware_len) { + dev_err(adapter->dev, + "No firmware image found! Terminating download\n"); + return -1; + } + + dev_dbg(adapter->dev, "info: Downloading FW image (%d bytes)\n", + firmware_len); + + if (mwifiex_pcie_disable_host_int(adapter)) { + dev_err(adapter->dev, + "%s: Disabling interrupts failed.\n", __func__); + return -1; + } + + skb = dev_alloc_skb(MWIFIEX_UPLD_SIZE); + if (!skb) { + ret = -ENOMEM; + goto done; + } + + /* Perform firmware data transfer */ + do { + u32 ireg_intr = 0; + + /* More data? */ + if (offset >= firmware_len) + break; + + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + ret = mwifiex_read_reg(adapter, reg->cmd_size, + &len); + if (ret) { + dev_warn(adapter->dev, + "Failed reading len from boot code\n"); + goto done; + } + if (len) + break; + usleep_range(10, 20); + } + + if (!len) { + break; + } else if (len > MWIFIEX_UPLD_SIZE) { + pr_err("FW download failure @ %d, invalid length %d\n", + offset, len); + ret = -1; + goto done; + } + + txlen = len; + + if (len & BIT(0)) { + block_retry_cnt++; + if (block_retry_cnt > MAX_WRITE_IOMEM_RETRY) { + pr_err("FW download failure @ %d, over max " + "retry count\n", offset); + ret = -1; + goto done; + } + dev_err(adapter->dev, "FW CRC error indicated by the " + "helper: len = 0x%04X, txlen = %d\n", + len, txlen); + len &= ~BIT(0); + /* Setting this to 0 to resend from same offset */ + txlen = 0; + } else { + block_retry_cnt = 0; + /* Set blocksize to transfer - checking for + last block */ + if (firmware_len - offset < txlen) + txlen = firmware_len - offset; + + dev_dbg(adapter->dev, "."); + + tx_blocks = (txlen + card->pcie.blksz_fw_dl - 1) / + card->pcie.blksz_fw_dl; + + /* Copy payload to buffer */ + memmove(skb->data, &firmware[offset], txlen); + } + + skb_put(skb, MWIFIEX_UPLD_SIZE - skb->len); + skb_trim(skb, tx_blocks * card->pcie.blksz_fw_dl); + + /* Send the boot command to device */ + if (mwifiex_pcie_send_boot_cmd(adapter, skb)) { + dev_err(adapter->dev, + "Failed to send firmware download command\n"); + ret = -1; + goto done; + } + + MWIFIEX_SKB_PACB(skb, &buf_pa); + + /* Wait for the command done interrupt */ + do { + if (mwifiex_read_reg(adapter, PCIE_CPU_INT_STATUS, + &ireg_intr)) { + dev_err(adapter->dev, "%s: Failed to read " + "interrupt status during fw dnld.\n", + __func__); + pci_unmap_single(card->dev, buf_pa, skb->len, + PCI_DMA_TODEVICE); + ret = -1; + goto done; + } + } while ((ireg_intr & CPU_INTR_DOOR_BELL) == + CPU_INTR_DOOR_BELL); + + pci_unmap_single(card->dev, buf_pa, skb->len, + PCI_DMA_TODEVICE); + + offset += txlen; + } while (true); + + dev_dbg(adapter->dev, "info:\nFW download over, size %d bytes\n", + offset); + + ret = 0; + +done: + dev_kfree_skb_any(skb); + return ret; +} + +/* + * This function checks the firmware status in card. + * + * The winner interface is also determined by this function. + */ +static int +mwifiex_check_fw_status(struct mwifiex_adapter *adapter, u32 poll_num) +{ + int ret = 0; + u32 firmware_stat, winner_status; + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + u32 tries; + + /* Mask spurios interrupts */ + if (mwifiex_write_reg(adapter, PCIE_HOST_INT_STATUS_MASK, + HOST_INTR_MASK)) { + dev_warn(adapter->dev, "Write register failed\n"); + return -1; + } + + dev_dbg(adapter->dev, "Setting driver ready signature\n"); + if (mwifiex_write_reg(adapter, reg->drv_rdy, + FIRMWARE_READY_PCIE)) { + dev_err(adapter->dev, + "Failed to write driver ready signature\n"); + return -1; + } + + /* Wait for firmware initialization event */ + for (tries = 0; tries < poll_num; tries++) { + if (mwifiex_read_reg(adapter, reg->fw_status, + &firmware_stat)) + ret = -1; + else + ret = 0; + if (ret) + continue; + if (firmware_stat == FIRMWARE_READY_PCIE) { + ret = 0; + break; + } else { + mdelay(100); + ret = -1; + } + } + + if (ret) { + if (mwifiex_read_reg(adapter, reg->fw_status, + &winner_status)) + ret = -1; + else if (!winner_status) { + dev_err(adapter->dev, "PCI-E is the winner\n"); + adapter->winner = 1; + ret = -1; + } else { + dev_err(adapter->dev, + "PCI-E is not the winner <%#x,%d>, exit dnld\n", + ret, adapter->winner); + ret = 0; + } + } + + return ret; +} + +/* + * This function reads the interrupt status from card. + */ +static void mwifiex_interrupt_status(struct mwifiex_adapter *adapter) +{ + u32 pcie_ireg; + unsigned long flags; + + if (!mwifiex_pcie_ok_to_access_hw(adapter)) + return; + + if (mwifiex_read_reg(adapter, PCIE_HOST_INT_STATUS, &pcie_ireg)) { + dev_warn(adapter->dev, "Read register failed\n"); + return; + } + + if ((pcie_ireg != 0xFFFFFFFF) && (pcie_ireg)) { + + mwifiex_pcie_disable_host_int(adapter); + + /* Clear the pending interrupts */ + if (mwifiex_write_reg(adapter, PCIE_HOST_INT_STATUS, + ~pcie_ireg)) { + dev_warn(adapter->dev, "Write register failed\n"); + return; + } + spin_lock_irqsave(&adapter->int_lock, flags); + adapter->int_status |= pcie_ireg; + spin_unlock_irqrestore(&adapter->int_lock, flags); + + if (pcie_ireg & HOST_INTR_CMD_DONE) { + if ((adapter->ps_state == PS_STATE_SLEEP_CFM) || + (adapter->ps_state == PS_STATE_SLEEP)) { + mwifiex_pcie_enable_host_int(adapter); + if (mwifiex_write_reg(adapter, + PCIE_CPU_INT_EVENT, + CPU_INTR_SLEEP_CFM_DONE) + ) { + dev_warn(adapter->dev, + "Write register failed\n"); + return; + + } + } + } else if (!adapter->pps_uapsd_mode && + adapter->ps_state == PS_STATE_SLEEP && + mwifiex_pcie_ok_to_access_hw(adapter)) { + /* Potentially for PCIe we could get other + * interrupts like shared. Don't change power + * state until cookie is set */ + adapter->ps_state = PS_STATE_AWAKE; + adapter->pm_wakeup_fw_try = false; + } + } +} + +/* + * Interrupt handler for PCIe root port + * + * This function reads the interrupt status from firmware and assigns + * the main process in workqueue which will handle the interrupt. + */ +static irqreturn_t mwifiex_pcie_interrupt(int irq, void *context) +{ + struct pci_dev *pdev = (struct pci_dev *)context; + struct pcie_service_card *card; + struct mwifiex_adapter *adapter; + + if (!pdev) { + pr_debug("info: %s: pdev is NULL\n", (u8 *)pdev); + goto exit; + } + + card = (struct pcie_service_card *) pci_get_drvdata(pdev); + if (!card || !card->adapter) { + pr_debug("info: %s: card=%p adapter=%p\n", __func__, card, + card ? card->adapter : NULL); + goto exit; + } + adapter = card->adapter; + + if (adapter->surprise_removed) + goto exit; + + mwifiex_interrupt_status(adapter); + queue_work(adapter->workqueue, &adapter->main_work); + +exit: + return IRQ_HANDLED; +} + +/* + * This function checks the current interrupt status. + * + * The following interrupts are checked and handled by this function - + * - Data sent + * - Command sent + * - Command received + * - Packets received + * - Events received + * + * In case of Rx packets received, the packets are uploaded from card to + * host and processed accordingly. + */ +static int mwifiex_process_int_status(struct mwifiex_adapter *adapter) +{ + int ret; + u32 pcie_ireg; + unsigned long flags; + + spin_lock_irqsave(&adapter->int_lock, flags); + /* Clear out unused interrupts */ + pcie_ireg = adapter->int_status; + adapter->int_status = 0; + spin_unlock_irqrestore(&adapter->int_lock, flags); + + while (pcie_ireg & HOST_INTR_MASK) { + if (pcie_ireg & HOST_INTR_DNLD_DONE) { + pcie_ireg &= ~HOST_INTR_DNLD_DONE; + dev_dbg(adapter->dev, "info: TX DNLD Done\n"); + ret = mwifiex_pcie_send_data_complete(adapter); + if (ret) + return ret; + } + if (pcie_ireg & HOST_INTR_UPLD_RDY) { + pcie_ireg &= ~HOST_INTR_UPLD_RDY; + dev_dbg(adapter->dev, "info: Rx DATA\n"); + ret = mwifiex_pcie_process_recv_data(adapter); + if (ret) + return ret; + } + if (pcie_ireg & HOST_INTR_EVENT_RDY) { + pcie_ireg &= ~HOST_INTR_EVENT_RDY; + dev_dbg(adapter->dev, "info: Rx EVENT\n"); + ret = mwifiex_pcie_process_event_ready(adapter); + if (ret) + return ret; + } + + if (pcie_ireg & HOST_INTR_CMD_DONE) { + pcie_ireg &= ~HOST_INTR_CMD_DONE; + if (adapter->cmd_sent) { + dev_dbg(adapter->dev, + "info: CMD sent Interrupt\n"); + adapter->cmd_sent = false; + } + /* Handle command response */ + ret = mwifiex_pcie_process_cmd_complete(adapter); + if (ret) + return ret; + } + + if (mwifiex_pcie_ok_to_access_hw(adapter)) { + if (mwifiex_read_reg(adapter, PCIE_HOST_INT_STATUS, + &pcie_ireg)) { + dev_warn(adapter->dev, + "Read register failed\n"); + return -1; + } + + if ((pcie_ireg != 0xFFFFFFFF) && (pcie_ireg)) { + if (mwifiex_write_reg(adapter, + PCIE_HOST_INT_STATUS, + ~pcie_ireg)) { + dev_warn(adapter->dev, + "Write register failed\n"); + return -1; + } + } + + } + } + dev_dbg(adapter->dev, "info: cmd_sent=%d data_sent=%d\n", + adapter->cmd_sent, adapter->data_sent); + if (adapter->ps_state != PS_STATE_SLEEP) + mwifiex_pcie_enable_host_int(adapter); + + return 0; +} + +/* + * This function downloads data from driver to card. + * + * Both commands and data packets are transferred to the card by this + * function. + * + * This function adds the PCIE specific header to the front of the buffer + * before transferring. The header contains the length of the packet and + * the type. The firmware handles the packets based upon this set type. + */ +static int mwifiex_pcie_host_to_card(struct mwifiex_adapter *adapter, u8 type, + struct sk_buff *skb, + struct mwifiex_tx_param *tx_param) +{ + if (!skb) { + dev_err(adapter->dev, "Passed NULL skb to %s\n", __func__); + return -1; + } + + if (type == MWIFIEX_TYPE_DATA) + return mwifiex_pcie_send_data(adapter, skb, tx_param); + else if (type == MWIFIEX_TYPE_CMD) + return mwifiex_pcie_send_cmd(adapter, skb); + + return 0; +} + +/* + * This function initializes the PCI-E host memory space, WCB rings, etc. + * + * The following initializations steps are followed - + * - Allocate TXBD ring buffers + * - Allocate RXBD ring buffers + * - Allocate event BD ring buffers + * - Allocate command response ring buffer + * - Allocate sleep cookie buffer + */ +static int mwifiex_pcie_init(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + int ret; + struct pci_dev *pdev = card->dev; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + + pci_set_drvdata(pdev, card); + + ret = pci_enable_device(pdev); + if (ret) + goto err_enable_dev; + + pci_set_master(pdev); + + dev_dbg(adapter->dev, "try set_consistent_dma_mask(32)\n"); + ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(adapter->dev, "set_dma_mask(32) failed\n"); + goto err_set_dma_mask; + } + + ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(adapter->dev, "set_consistent_dma_mask(64) failed\n"); + goto err_set_dma_mask; + } + + ret = pci_request_region(pdev, 0, DRV_NAME); + if (ret) { + dev_err(adapter->dev, "req_reg(0) error\n"); + goto err_req_region0; + } + card->pci_mmap = pci_iomap(pdev, 0, 0); + if (!card->pci_mmap) { + dev_err(adapter->dev, "iomap(0) error\n"); + ret = -EIO; + goto err_iomap0; + } + ret = pci_request_region(pdev, 2, DRV_NAME); + if (ret) { + dev_err(adapter->dev, "req_reg(2) error\n"); + goto err_req_region2; + } + card->pci_mmap1 = pci_iomap(pdev, 2, 0); + if (!card->pci_mmap1) { + dev_err(adapter->dev, "iomap(2) error\n"); + ret = -EIO; + goto err_iomap2; + } + + dev_dbg(adapter->dev, + "PCI memory map Virt0: %p PCI memory map Virt2: %p\n", + card->pci_mmap, card->pci_mmap1); + + card->cmdrsp_buf = NULL; + ret = mwifiex_pcie_create_txbd_ring(adapter); + if (ret) + goto err_cre_txbd; + ret = mwifiex_pcie_create_rxbd_ring(adapter); + if (ret) + goto err_cre_rxbd; + ret = mwifiex_pcie_create_evtbd_ring(adapter); + if (ret) + goto err_cre_evtbd; + ret = mwifiex_pcie_alloc_cmdrsp_buf(adapter); + if (ret) + goto err_alloc_cmdbuf; + if (reg->sleep_cookie) { + ret = mwifiex_pcie_alloc_sleep_cookie_buf(adapter); + if (ret) + goto err_alloc_cookie; + } else { + card->sleep_cookie_vbase = NULL; + } + return ret; + +err_alloc_cookie: + mwifiex_pcie_delete_cmdrsp_buf(adapter); +err_alloc_cmdbuf: + mwifiex_pcie_delete_evtbd_ring(adapter); +err_cre_evtbd: + mwifiex_pcie_delete_rxbd_ring(adapter); +err_cre_rxbd: + mwifiex_pcie_delete_txbd_ring(adapter); +err_cre_txbd: + pci_iounmap(pdev, card->pci_mmap1); +err_iomap2: + pci_release_region(pdev, 2); +err_req_region2: + pci_iounmap(pdev, card->pci_mmap); +err_iomap0: + pci_release_region(pdev, 0); +err_req_region0: +err_set_dma_mask: + pci_disable_device(pdev); +err_enable_dev: + pci_set_drvdata(pdev, NULL); + return ret; +} + +/* + * This function cleans up the allocated card buffers. + * + * The following are freed by this function - + * - TXBD ring buffers + * - RXBD ring buffers + * - Event BD ring buffers + * - Command response ring buffer + * - Sleep cookie buffer + */ +static void mwifiex_pcie_cleanup(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + struct pci_dev *pdev = card->dev; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + + if (user_rmmod) { + dev_dbg(adapter->dev, "Clearing driver ready signature\n"); + if (mwifiex_write_reg(adapter, reg->drv_rdy, 0x00000000)) + dev_err(adapter->dev, + "Failed to write driver not-ready signature\n"); + } + + if (pdev) { + pci_iounmap(pdev, card->pci_mmap); + pci_iounmap(pdev, card->pci_mmap1); + pci_disable_device(pdev); + pci_release_region(pdev, 2); + pci_release_region(pdev, 0); + pci_set_drvdata(pdev, NULL); + } +} + +/* + * This function registers the PCIE device. + * + * PCIE IRQ is claimed, block size is set and driver data is initialized. + */ +static int mwifiex_register_dev(struct mwifiex_adapter *adapter) +{ + int ret; + struct pcie_service_card *card = adapter->card; + struct pci_dev *pdev = card->dev; + + /* save adapter pointer in card */ + card->adapter = adapter; + + ret = request_irq(pdev->irq, mwifiex_pcie_interrupt, IRQF_SHARED, + "MRVL_PCIE", pdev); + if (ret) { + pr_err("request_irq failed: ret=%d\n", ret); + adapter->card = NULL; + return -1; + } + + adapter->dev = &pdev->dev; + strcpy(adapter->fw_name, card->pcie.firmware); + + return 0; +} + +/* + * This function unregisters the PCIE device. + * + * The PCIE IRQ is released, the function is disabled and driver + * data is set to null. + */ +static void mwifiex_unregister_dev(struct mwifiex_adapter *adapter) +{ + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg; + + if (card) { + dev_dbg(adapter->dev, "%s(): calling free_irq()\n", __func__); + free_irq(card->dev->irq, card->dev); + + reg = card->pcie.reg; + if (reg->sleep_cookie) + mwifiex_pcie_delete_sleep_cookie_buf(adapter); + + mwifiex_pcie_delete_cmdrsp_buf(adapter); + mwifiex_pcie_delete_evtbd_ring(adapter); + mwifiex_pcie_delete_rxbd_ring(adapter); + mwifiex_pcie_delete_txbd_ring(adapter); + card->cmdrsp_buf = NULL; + } +} + +static struct mwifiex_if_ops pcie_ops = { + .init_if = mwifiex_pcie_init, + .cleanup_if = mwifiex_pcie_cleanup, + .check_fw_status = mwifiex_check_fw_status, + .prog_fw = mwifiex_prog_fw_w_helper, + .register_dev = mwifiex_register_dev, + .unregister_dev = mwifiex_unregister_dev, + .enable_int = mwifiex_pcie_enable_host_int, + .process_int_status = mwifiex_process_int_status, + .host_to_card = mwifiex_pcie_host_to_card, + .wakeup = mwifiex_pm_wakeup_card, + .wakeup_complete = mwifiex_pm_wakeup_card_complete, + + /* PCIE specific */ + .cmdrsp_complete = mwifiex_pcie_cmdrsp_complete, + .event_complete = mwifiex_pcie_event_complete, + .update_mp_end_port = NULL, + .cleanup_mpa_buf = NULL, + .init_fw_port = mwifiex_pcie_init_fw_port, + .clean_pcie_ring = mwifiex_clean_pcie_ring_buf, +}; + +/* + * This function initializes the PCIE driver module. + * + * This initiates the semaphore and registers the device with + * PCIE bus. + */ +static int mwifiex_pcie_init_module(void) +{ + int ret; + + pr_debug("Marvell PCIe Driver\n"); + + sema_init(&add_remove_card_sem, 1); + + /* Clear the flag in case user removes the card. */ + user_rmmod = 0; + + ret = pci_register_driver(&mwifiex_pcie); + if (ret) + pr_err("Driver register failed!\n"); + else + pr_debug("info: Driver registered successfully!\n"); + + return ret; +} + +/* + * This function cleans up the PCIE driver. + * + * The following major steps are followed for cleanup - + * - Resume the device if its suspended + * - Disconnect the device if connected + * - Shutdown the firmware + * - Unregister the device from PCIE bus. + */ +static void mwifiex_pcie_cleanup_module(void) +{ + if (!down_interruptible(&add_remove_card_sem)) + up(&add_remove_card_sem); + + /* Set the flag as user is removing this module. */ + user_rmmod = 1; + + pci_unregister_driver(&mwifiex_pcie); +} + +module_init(mwifiex_pcie_init_module); +module_exit(mwifiex_pcie_cleanup_module); + +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_DESCRIPTION("Marvell WiFi-Ex PCI-Express Driver version " PCIE_VERSION); +MODULE_VERSION(PCIE_VERSION); +MODULE_LICENSE("GPL v2"); +MODULE_FIRMWARE(PCIE8766_DEFAULT_FW_NAME); +MODULE_FIRMWARE(PCIE8897_DEFAULT_FW_NAME); diff --git a/drivers/net/wireless/mwifiex/pcie.h b/drivers/net/wireless/mwifiex/pcie.h new file mode 100644 index 0000000..d322ab8 --- /dev/null +++ b/drivers/net/wireless/mwifiex/pcie.h @@ -0,0 +1,320 @@ +/* @file mwifiex_pcie.h + * + * @brief This file contains definitions for PCI-E interface. + * driver. + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _MWIFIEX_PCIE_H +#define _MWIFIEX_PCIE_H + +#include +#include +#include + +#include "main.h" + +#define PCIE8766_DEFAULT_FW_NAME "mrvl/pcie8766_uapsta.bin" +#define PCIE8897_DEFAULT_FW_NAME "mrvl/pcie8897_uapsta.bin" + +#define PCIE_VENDOR_ID_MARVELL (0x11ab) +#define PCIE_DEVICE_ID_MARVELL_88W8766P (0x2b30) +#define PCIE_DEVICE_ID_MARVELL_88W8897 (0x2b38) + +/* Constants for Buffer Descriptor (BD) rings */ +#define MWIFIEX_MAX_TXRX_BD 0x20 +#define MWIFIEX_TXBD_MASK 0x3F +#define MWIFIEX_RXBD_MASK 0x3F + +#define MWIFIEX_MAX_EVT_BD 0x04 +#define MWIFIEX_EVTBD_MASK 0x07 + +/* PCIE INTERNAL REGISTERS */ +#define PCIE_SCRATCH_0_REG 0xC10 +#define PCIE_SCRATCH_1_REG 0xC14 +#define PCIE_CPU_INT_EVENT 0xC18 +#define PCIE_CPU_INT_STATUS 0xC1C +#define PCIE_HOST_INT_STATUS 0xC30 +#define PCIE_HOST_INT_MASK 0xC34 +#define PCIE_HOST_INT_STATUS_MASK 0xC3C +#define PCIE_SCRATCH_2_REG 0xC40 +#define PCIE_SCRATCH_3_REG 0xC44 +#define PCIE_SCRATCH_4_REG 0xCD0 +#define PCIE_SCRATCH_5_REG 0xCD4 +#define PCIE_SCRATCH_6_REG 0xCD8 +#define PCIE_SCRATCH_7_REG 0xCDC +#define PCIE_SCRATCH_8_REG 0xCE0 +#define PCIE_SCRATCH_9_REG 0xCE4 +#define PCIE_SCRATCH_10_REG 0xCE8 +#define PCIE_SCRATCH_11_REG 0xCEC +#define PCIE_SCRATCH_12_REG 0xCF0 +#define PCIE_RD_DATA_PTR_Q0_Q1 0xC08C +#define PCIE_WR_DATA_PTR_Q0_Q1 0xC05C + +#define CPU_INTR_DNLD_RDY BIT(0) +#define CPU_INTR_DOOR_BELL BIT(1) +#define CPU_INTR_SLEEP_CFM_DONE BIT(2) +#define CPU_INTR_RESET BIT(3) + +#define HOST_INTR_DNLD_DONE BIT(0) +#define HOST_INTR_UPLD_RDY BIT(1) +#define HOST_INTR_CMD_DONE BIT(2) +#define HOST_INTR_EVENT_RDY BIT(3) +#define HOST_INTR_MASK (HOST_INTR_DNLD_DONE | \ + HOST_INTR_UPLD_RDY | \ + HOST_INTR_CMD_DONE | \ + HOST_INTR_EVENT_RDY) + +#define MWIFIEX_BD_FLAG_ROLLOVER_IND BIT(7) +#define MWIFIEX_BD_FLAG_FIRST_DESC BIT(0) +#define MWIFIEX_BD_FLAG_LAST_DESC BIT(1) +#define MWIFIEX_BD_FLAG_SOP BIT(0) +#define MWIFIEX_BD_FLAG_EOP BIT(1) +#define MWIFIEX_BD_FLAG_XS_SOP BIT(2) +#define MWIFIEX_BD_FLAG_XS_EOP BIT(3) +#define MWIFIEX_BD_FLAG_EVT_ROLLOVER_IND BIT(7) +#define MWIFIEX_BD_FLAG_RX_ROLLOVER_IND BIT(10) +#define MWIFIEX_BD_FLAG_TX_START_PTR BIT(16) +#define MWIFIEX_BD_FLAG_TX_ROLLOVER_IND BIT(26) + +/* Max retry number of command write */ +#define MAX_WRITE_IOMEM_RETRY 2 +/* Define PCIE block size for firmware download */ +#define MWIFIEX_PCIE_BLOCK_SIZE_FW_DNLD 256 +/* FW awake cookie after FW ready */ +#define FW_AWAKE_COOKIE (0xAA55AA55) + +struct mwifiex_pcie_card_reg { + u16 cmd_addr_lo; + u16 cmd_addr_hi; + u16 fw_status; + u16 cmd_size; + u16 cmdrsp_addr_lo; + u16 cmdrsp_addr_hi; + u16 tx_rdptr; + u16 tx_wrptr; + u16 rx_rdptr; + u16 rx_wrptr; + u16 evt_rdptr; + u16 evt_wrptr; + u16 drv_rdy; + u16 tx_start_ptr; + u32 tx_mask; + u32 tx_wrap_mask; + u32 rx_mask; + u32 rx_wrap_mask; + u32 tx_rollover_ind; + u32 rx_rollover_ind; + u32 evt_rollover_ind; + u8 ring_flag_sop; + u8 ring_flag_eop; + u8 ring_flag_xs_sop; + u8 ring_flag_xs_eop; + u32 ring_tx_start_ptr; + u8 pfu_enabled; + u8 sleep_cookie; +}; + +static const struct mwifiex_pcie_card_reg mwifiex_reg_8766 = { + .cmd_addr_lo = PCIE_SCRATCH_0_REG, + .cmd_addr_hi = PCIE_SCRATCH_1_REG, + .cmd_size = PCIE_SCRATCH_2_REG, + .fw_status = PCIE_SCRATCH_3_REG, + .cmdrsp_addr_lo = PCIE_SCRATCH_4_REG, + .cmdrsp_addr_hi = PCIE_SCRATCH_5_REG, + .tx_rdptr = PCIE_SCRATCH_6_REG, + .tx_wrptr = PCIE_SCRATCH_7_REG, + .rx_rdptr = PCIE_SCRATCH_8_REG, + .rx_wrptr = PCIE_SCRATCH_9_REG, + .evt_rdptr = PCIE_SCRATCH_10_REG, + .evt_wrptr = PCIE_SCRATCH_11_REG, + .drv_rdy = PCIE_SCRATCH_12_REG, + .tx_start_ptr = 0, + .tx_mask = MWIFIEX_TXBD_MASK, + .tx_wrap_mask = 0, + .rx_mask = MWIFIEX_RXBD_MASK, + .rx_wrap_mask = 0, + .tx_rollover_ind = MWIFIEX_BD_FLAG_ROLLOVER_IND, + .rx_rollover_ind = MWIFIEX_BD_FLAG_ROLLOVER_IND, + .evt_rollover_ind = MWIFIEX_BD_FLAG_ROLLOVER_IND, + .ring_flag_sop = 0, + .ring_flag_eop = 0, + .ring_flag_xs_sop = 0, + .ring_flag_xs_eop = 0, + .ring_tx_start_ptr = 0, + .pfu_enabled = 0, + .sleep_cookie = 1, +}; + +static const struct mwifiex_pcie_card_reg mwifiex_reg_8897 = { + .cmd_addr_lo = PCIE_SCRATCH_0_REG, + .cmd_addr_hi = PCIE_SCRATCH_1_REG, + .cmd_size = PCIE_SCRATCH_2_REG, + .fw_status = PCIE_SCRATCH_3_REG, + .cmdrsp_addr_lo = PCIE_SCRATCH_4_REG, + .cmdrsp_addr_hi = PCIE_SCRATCH_5_REG, + .tx_rdptr = PCIE_RD_DATA_PTR_Q0_Q1, + .tx_wrptr = PCIE_WR_DATA_PTR_Q0_Q1, + .rx_rdptr = PCIE_WR_DATA_PTR_Q0_Q1, + .rx_wrptr = PCIE_RD_DATA_PTR_Q0_Q1, + .evt_rdptr = PCIE_SCRATCH_10_REG, + .evt_wrptr = PCIE_SCRATCH_11_REG, + .drv_rdy = PCIE_SCRATCH_12_REG, + .tx_start_ptr = 16, + .tx_mask = 0x03FF0000, + .tx_wrap_mask = 0x07FF0000, + .rx_mask = 0x000003FF, + .rx_wrap_mask = 0x000007FF, + .tx_rollover_ind = MWIFIEX_BD_FLAG_TX_ROLLOVER_IND, + .rx_rollover_ind = MWIFIEX_BD_FLAG_RX_ROLLOVER_IND, + .evt_rollover_ind = MWIFIEX_BD_FLAG_EVT_ROLLOVER_IND, + .ring_flag_sop = MWIFIEX_BD_FLAG_SOP, + .ring_flag_eop = MWIFIEX_BD_FLAG_EOP, + .ring_flag_xs_sop = MWIFIEX_BD_FLAG_XS_SOP, + .ring_flag_xs_eop = MWIFIEX_BD_FLAG_XS_EOP, + .ring_tx_start_ptr = MWIFIEX_BD_FLAG_TX_START_PTR, + .pfu_enabled = 1, + .sleep_cookie = 0, +}; + +struct mwifiex_pcie_device { + const char *firmware; + const struct mwifiex_pcie_card_reg *reg; + u16 blksz_fw_dl; +}; + +static const struct mwifiex_pcie_device mwifiex_pcie8766 = { + .firmware = PCIE8766_DEFAULT_FW_NAME, + .reg = &mwifiex_reg_8766, + .blksz_fw_dl = MWIFIEX_PCIE_BLOCK_SIZE_FW_DNLD, +}; + +static const struct mwifiex_pcie_device mwifiex_pcie8897 = { + .firmware = PCIE8897_DEFAULT_FW_NAME, + .reg = &mwifiex_reg_8897, + .blksz_fw_dl = MWIFIEX_PCIE_BLOCK_SIZE_FW_DNLD, +}; + +struct mwifiex_evt_buf_desc { + u64 paddr; + u16 len; + u16 flags; +} __packed; + +struct mwifiex_pcie_buf_desc { + u64 paddr; + u16 len; + u16 flags; +} __packed; + +struct mwifiex_pfu_buf_desc { + u16 flags; + u16 offset; + u16 frag_len; + u16 len; + u64 paddr; + u32 reserved; +} __packed; + +struct pcie_service_card { + struct pci_dev *dev; + struct mwifiex_adapter *adapter; + struct mwifiex_pcie_device pcie; + + u8 txbd_flush; + u32 txbd_wrptr; + u32 txbd_rdptr; + u32 txbd_ring_size; + u8 *txbd_ring_vbase; + dma_addr_t txbd_ring_pbase; + void *txbd_ring[MWIFIEX_MAX_TXRX_BD]; + struct sk_buff *tx_buf_list[MWIFIEX_MAX_TXRX_BD]; + + u32 rxbd_wrptr; + u32 rxbd_rdptr; + u32 rxbd_ring_size; + u8 *rxbd_ring_vbase; + dma_addr_t rxbd_ring_pbase; + void *rxbd_ring[MWIFIEX_MAX_TXRX_BD]; + struct sk_buff *rx_buf_list[MWIFIEX_MAX_TXRX_BD]; + + u32 evtbd_wrptr; + u32 evtbd_rdptr; + u32 evtbd_ring_size; + u8 *evtbd_ring_vbase; + dma_addr_t evtbd_ring_pbase; + void *evtbd_ring[MWIFIEX_MAX_EVT_BD]; + struct sk_buff *evt_buf_list[MWIFIEX_MAX_EVT_BD]; + + struct sk_buff *cmd_buf; + struct sk_buff *cmdrsp_buf; + u8 *sleep_cookie_vbase; + dma_addr_t sleep_cookie_pbase; + void __iomem *pci_mmap; + void __iomem *pci_mmap1; +}; + +static inline int +mwifiex_pcie_txbd_empty(struct pcie_service_card *card, u32 rdptr) +{ + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + + switch (card->dev->device) { + case PCIE_DEVICE_ID_MARVELL_88W8766P: + if (((card->txbd_wrptr & reg->tx_mask) == + (rdptr & reg->tx_mask)) && + ((card->txbd_wrptr & reg->tx_rollover_ind) != + (rdptr & reg->tx_rollover_ind))) + return 1; + break; + case PCIE_DEVICE_ID_MARVELL_88W8897: + if (((card->txbd_wrptr & reg->tx_mask) == + (rdptr & reg->tx_mask)) && + ((card->txbd_wrptr & reg->tx_rollover_ind) == + (rdptr & reg->tx_rollover_ind))) + return 1; + break; + } + + return 0; +} + +static inline int +mwifiex_pcie_txbd_not_full(struct pcie_service_card *card) +{ + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + + switch (card->dev->device) { + case PCIE_DEVICE_ID_MARVELL_88W8766P: + if (((card->txbd_wrptr & reg->tx_mask) != + (card->txbd_rdptr & reg->tx_mask)) || + ((card->txbd_wrptr & reg->tx_rollover_ind) != + (card->txbd_rdptr & reg->tx_rollover_ind))) + return 1; + break; + case PCIE_DEVICE_ID_MARVELL_88W8897: + if (((card->txbd_wrptr & reg->tx_mask) != + (card->txbd_rdptr & reg->tx_mask)) || + ((card->txbd_wrptr & reg->tx_rollover_ind) == + (card->txbd_rdptr & reg->tx_rollover_ind))) + return 1; + break; + } + + return 0; +} +#endif /* _MWIFIEX_PCIE_H */ diff --git a/drivers/net/wireless/mwifiex/scan.c b/drivers/net/wireless/mwifiex/scan.c new file mode 100644 index 0000000..9cf5d8f --- /dev/null +++ b/drivers/net/wireless/mwifiex/scan.c @@ -0,0 +1,2071 @@ +/* + * Marvell Wireless LAN device driver: scan ioctl and command handling + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "11n.h" +#include "cfg80211.h" + +/* The maximum number of channels the firmware can scan per command */ +#define MWIFIEX_MAX_CHANNELS_PER_SPECIFIC_SCAN 14 + +#define MWIFIEX_DEF_CHANNELS_PER_SCAN_CMD 4 +#define MWIFIEX_LIMIT_1_CHANNEL_PER_SCAN_CMD 15 +#define MWIFIEX_LIMIT_2_CHANNELS_PER_SCAN_CMD 27 +#define MWIFIEX_LIMIT_3_CHANNELS_PER_SCAN_CMD 35 + +/* Memory needed to store a max sized Channel List TLV for a firmware scan */ +#define CHAN_TLV_MAX_SIZE (sizeof(struct mwifiex_ie_types_header) \ + + (MWIFIEX_MAX_CHANNELS_PER_SPECIFIC_SCAN \ + *sizeof(struct mwifiex_chan_scan_param_set))) + +/* Memory needed to store supported rate */ +#define RATE_TLV_MAX_SIZE (sizeof(struct mwifiex_ie_types_rates_param_set) \ + + HOSTCMD_SUPPORTED_RATES) + +/* Memory needed to store a max number/size WildCard SSID TLV for a firmware + scan */ +#define WILDCARD_SSID_TLV_MAX_SIZE \ + (MWIFIEX_MAX_SSID_LIST_LENGTH * \ + (sizeof(struct mwifiex_ie_types_wildcard_ssid_params) \ + + IEEE80211_MAX_SSID_LEN)) + +/* Maximum memory needed for a mwifiex_scan_cmd_config with all TLVs at max */ +#define MAX_SCAN_CFG_ALLOC (sizeof(struct mwifiex_scan_cmd_config) \ + + sizeof(struct mwifiex_ie_types_num_probes) \ + + sizeof(struct mwifiex_ie_types_htcap) \ + + CHAN_TLV_MAX_SIZE \ + + RATE_TLV_MAX_SIZE \ + + WILDCARD_SSID_TLV_MAX_SIZE) + + +union mwifiex_scan_cmd_config_tlv { + /* Scan configuration (variable length) */ + struct mwifiex_scan_cmd_config config; + /* Max allocated block */ + u8 config_alloc_buf[MAX_SCAN_CFG_ALLOC]; +}; + +enum cipher_suite { + CIPHER_SUITE_TKIP, + CIPHER_SUITE_CCMP, + CIPHER_SUITE_MAX +}; +static u8 mwifiex_wpa_oui[CIPHER_SUITE_MAX][4] = { + { 0x00, 0x50, 0xf2, 0x02 }, /* TKIP */ + { 0x00, 0x50, 0xf2, 0x04 }, /* AES */ +}; +static u8 mwifiex_rsn_oui[CIPHER_SUITE_MAX][4] = { + { 0x00, 0x0f, 0xac, 0x02 }, /* TKIP */ + { 0x00, 0x0f, 0xac, 0x04 }, /* AES */ +}; + +/* + * This function parses a given IE for a given OUI. + * + * This is used to parse a WPA/RSN IE to find if it has + * a given oui in PTK. + */ +static u8 +mwifiex_search_oui_in_ie(struct ie_body *iebody, u8 *oui) +{ + u8 count; + + count = iebody->ptk_cnt[0]; + + /* There could be multiple OUIs for PTK hence + 1) Take the length. + 2) Check all the OUIs for AES. + 3) If one of them is AES then pass success. */ + while (count) { + if (!memcmp(iebody->ptk_body, oui, sizeof(iebody->ptk_body))) + return MWIFIEX_OUI_PRESENT; + + --count; + if (count) + iebody = (struct ie_body *) ((u8 *) iebody + + sizeof(iebody->ptk_body)); + } + + pr_debug("info: %s: OUI is not found in PTK\n", __func__); + return MWIFIEX_OUI_NOT_PRESENT; +} + +/* + * This function checks if a given OUI is present in a RSN IE. + * + * The function first checks if a RSN IE is present or not in the + * BSS descriptor. It tries to locate the OUI only if such an IE is + * present. + */ +static u8 +mwifiex_is_rsn_oui_present(struct mwifiex_bssdescriptor *bss_desc, u32 cipher) +{ + u8 *oui; + struct ie_body *iebody; + u8 ret = MWIFIEX_OUI_NOT_PRESENT; + + if (((bss_desc->bcn_rsn_ie) && ((*(bss_desc->bcn_rsn_ie)). + ieee_hdr.element_id == WLAN_EID_RSN))) { + iebody = (struct ie_body *) + (((u8 *) bss_desc->bcn_rsn_ie->data) + + RSN_GTK_OUI_OFFSET); + oui = &mwifiex_rsn_oui[cipher][0]; + ret = mwifiex_search_oui_in_ie(iebody, oui); + if (ret) + return ret; + } + return ret; +} + +/* + * This function checks if a given OUI is present in a WPA IE. + * + * The function first checks if a WPA IE is present or not in the + * BSS descriptor. It tries to locate the OUI only if such an IE is + * present. + */ +static u8 +mwifiex_is_wpa_oui_present(struct mwifiex_bssdescriptor *bss_desc, u32 cipher) +{ + u8 *oui; + struct ie_body *iebody; + u8 ret = MWIFIEX_OUI_NOT_PRESENT; + + if (((bss_desc->bcn_wpa_ie) && + ((*(bss_desc->bcn_wpa_ie)).vend_hdr.element_id == + WLAN_EID_VENDOR_SPECIFIC))) { + iebody = (struct ie_body *) bss_desc->bcn_wpa_ie->data; + oui = &mwifiex_wpa_oui[cipher][0]; + ret = mwifiex_search_oui_in_ie(iebody, oui); + if (ret) + return ret; + } + return ret; +} + +/* + * This function compares two SSIDs and checks if they match. + */ +s32 +mwifiex_ssid_cmp(struct cfg80211_ssid *ssid1, struct cfg80211_ssid *ssid2) +{ + if (!ssid1 || !ssid2 || (ssid1->ssid_len != ssid2->ssid_len)) + return -1; + return memcmp(ssid1->ssid, ssid2->ssid, ssid1->ssid_len); +} + +/* + * This function checks if wapi is enabled in driver and scanned network is + * compatible with it. + */ +static bool +mwifiex_is_bss_wapi(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc) +{ + if (priv->sec_info.wapi_enabled && + (bss_desc->bcn_wapi_ie && + ((*(bss_desc->bcn_wapi_ie)).ieee_hdr.element_id == + WLAN_EID_BSS_AC_ACCESS_DELAY))) { + return true; + } + return false; +} + +/* + * This function checks if driver is configured with no security mode and + * scanned network is compatible with it. + */ +static bool +mwifiex_is_bss_no_sec(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && ((!bss_desc->bcn_wpa_ie) || + ((*(bss_desc->bcn_wpa_ie)).vend_hdr.element_id != + WLAN_EID_VENDOR_SPECIFIC)) && + ((!bss_desc->bcn_rsn_ie) || + ((*(bss_desc->bcn_rsn_ie)).ieee_hdr.element_id != + WLAN_EID_RSN)) && + !priv->sec_info.encryption_mode && !bss_desc->privacy) { + return true; + } + return false; +} + +/* + * This function checks if static WEP is enabled in driver and scanned network + * is compatible with it. + */ +static bool +mwifiex_is_bss_static_wep(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc) +{ + if (priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && bss_desc->privacy) { + return true; + } + return false; +} + +/* + * This function checks if wpa is enabled in driver and scanned network is + * compatible with it. + */ +static bool +mwifiex_is_bss_wpa(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && ((bss_desc->bcn_wpa_ie) && + ((*(bss_desc->bcn_wpa_ie)). + vend_hdr.element_id == WLAN_EID_VENDOR_SPECIFIC)) + /* + * Privacy bit may NOT be set in some APs like + * LinkSys WRT54G && bss_desc->privacy + */ + ) { + dev_dbg(priv->adapter->dev, "info: %s: WPA:" + " wpa_ie=%#x wpa2_ie=%#x WEP=%s WPA=%s WPA2=%s " + "EncMode=%#x privacy=%#x\n", __func__, + (bss_desc->bcn_wpa_ie) ? + (*(bss_desc->bcn_wpa_ie)). + vend_hdr.element_id : 0, + (bss_desc->bcn_rsn_ie) ? + (*(bss_desc->bcn_rsn_ie)). + ieee_hdr.element_id : 0, + (priv->sec_info.wep_enabled) ? "e" : "d", + (priv->sec_info.wpa_enabled) ? "e" : "d", + (priv->sec_info.wpa2_enabled) ? "e" : "d", + priv->sec_info.encryption_mode, + bss_desc->privacy); + return true; + } + return false; +} + +/* + * This function checks if wpa2 is enabled in driver and scanned network is + * compatible with it. + */ +static bool +mwifiex_is_bss_wpa2(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && + !priv->sec_info.wpa_enabled && + priv->sec_info.wpa2_enabled && + ((bss_desc->bcn_rsn_ie) && + ((*(bss_desc->bcn_rsn_ie)).ieee_hdr.element_id == WLAN_EID_RSN))) { + /* + * Privacy bit may NOT be set in some APs like + * LinkSys WRT54G && bss_desc->privacy + */ + dev_dbg(priv->adapter->dev, "info: %s: WPA2: " + " wpa_ie=%#x wpa2_ie=%#x WEP=%s WPA=%s WPA2=%s " + "EncMode=%#x privacy=%#x\n", __func__, + (bss_desc->bcn_wpa_ie) ? + (*(bss_desc->bcn_wpa_ie)). + vend_hdr.element_id : 0, + (bss_desc->bcn_rsn_ie) ? + (*(bss_desc->bcn_rsn_ie)). + ieee_hdr.element_id : 0, + (priv->sec_info.wep_enabled) ? "e" : "d", + (priv->sec_info.wpa_enabled) ? "e" : "d", + (priv->sec_info.wpa2_enabled) ? "e" : "d", + priv->sec_info.encryption_mode, + bss_desc->privacy); + return true; + } + return false; +} + +/* + * This function checks if adhoc AES is enabled in driver and scanned network is + * compatible with it. + */ +static bool +mwifiex_is_bss_adhoc_aes(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && + ((!bss_desc->bcn_wpa_ie) || + ((*(bss_desc->bcn_wpa_ie)). + vend_hdr.element_id != WLAN_EID_VENDOR_SPECIFIC)) && + ((!bss_desc->bcn_rsn_ie) || + ((*(bss_desc->bcn_rsn_ie)).ieee_hdr.element_id != WLAN_EID_RSN)) && + !priv->sec_info.encryption_mode && bss_desc->privacy) { + return true; + } + return false; +} + +/* + * This function checks if dynamic WEP is enabled in driver and scanned network + * is compatible with it. + */ +static bool +mwifiex_is_bss_dynamic_wep(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc) +{ + if (!priv->sec_info.wep_enabled && !priv->sec_info.wpa_enabled && + !priv->sec_info.wpa2_enabled && + ((!bss_desc->bcn_wpa_ie) || + ((*(bss_desc->bcn_wpa_ie)). + vend_hdr.element_id != WLAN_EID_VENDOR_SPECIFIC)) && + ((!bss_desc->bcn_rsn_ie) || + ((*(bss_desc->bcn_rsn_ie)).ieee_hdr.element_id != WLAN_EID_RSN)) && + priv->sec_info.encryption_mode && bss_desc->privacy) { + dev_dbg(priv->adapter->dev, "info: %s: dynamic " + "WEP: wpa_ie=%#x wpa2_ie=%#x " + "EncMode=%#x privacy=%#x\n", + __func__, + (bss_desc->bcn_wpa_ie) ? + (*(bss_desc->bcn_wpa_ie)). + vend_hdr.element_id : 0, + (bss_desc->bcn_rsn_ie) ? + (*(bss_desc->bcn_rsn_ie)). + ieee_hdr.element_id : 0, + priv->sec_info.encryption_mode, + bss_desc->privacy); + return true; + } + return false; +} + +/* + * This function checks if a scanned network is compatible with the driver + * settings. + * + * WEP WPA WPA2 ad-hoc encrypt Network + * enabled enabled enabled AES mode Privacy WPA WPA2 Compatible + * 0 0 0 0 NONE 0 0 0 yes No security + * 0 1 0 0 x 1x 1 x yes WPA (disable + * HT if no AES) + * 0 0 1 0 x 1x x 1 yes WPA2 (disable + * HT if no AES) + * 0 0 0 1 NONE 1 0 0 yes Ad-hoc AES + * 1 0 0 0 NONE 1 0 0 yes Static WEP + * (disable HT) + * 0 0 0 0 !=NONE 1 0 0 yes Dynamic WEP + * + * Compatibility is not matched while roaming, except for mode. + */ +static s32 +mwifiex_is_network_compatible(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc, u32 mode) +{ + struct mwifiex_adapter *adapter = priv->adapter; + + bss_desc->disable_11n = false; + + /* Don't check for compatibility if roaming */ + if (priv->media_connected && + (priv->bss_mode == NL80211_IFTYPE_STATION) && + (bss_desc->bss_mode == NL80211_IFTYPE_STATION)) + return 0; + + if (priv->wps.session_enable) { + dev_dbg(adapter->dev, + "info: return success directly in WPS period\n"); + return 0; + } + + if (mwifiex_is_bss_wapi(priv, bss_desc)) { + dev_dbg(adapter->dev, "info: return success for WAPI AP\n"); + return 0; + } + + if (bss_desc->bss_mode == mode) { + if (mwifiex_is_bss_no_sec(priv, bss_desc)) { + /* No security */ + return 0; + } else if (mwifiex_is_bss_static_wep(priv, bss_desc)) { + /* Static WEP enabled */ + dev_dbg(adapter->dev, "info: Disable 11n in WEP mode.\n"); + bss_desc->disable_11n = true; + return 0; + } else if (mwifiex_is_bss_wpa(priv, bss_desc)) { + /* WPA enabled */ + if (((priv->adapter->config_bands & BAND_GN || + priv->adapter->config_bands & BAND_AN) && + bss_desc->bcn_ht_cap) && + !mwifiex_is_wpa_oui_present(bss_desc, + CIPHER_SUITE_CCMP)) { + + if (mwifiex_is_wpa_oui_present + (bss_desc, CIPHER_SUITE_TKIP)) { + dev_dbg(adapter->dev, + "info: Disable 11n if AES " + "is not supported by AP\n"); + bss_desc->disable_11n = true; + } else { + return -1; + } + } + return 0; + } else if (mwifiex_is_bss_wpa2(priv, bss_desc)) { + /* WPA2 enabled */ + if (((priv->adapter->config_bands & BAND_GN || + priv->adapter->config_bands & BAND_AN) && + bss_desc->bcn_ht_cap) && + !mwifiex_is_rsn_oui_present(bss_desc, + CIPHER_SUITE_CCMP)) { + + if (mwifiex_is_rsn_oui_present + (bss_desc, CIPHER_SUITE_TKIP)) { + dev_dbg(adapter->dev, + "info: Disable 11n if AES " + "is not supported by AP\n"); + bss_desc->disable_11n = true; + } else { + return -1; + } + } + return 0; + } else if (mwifiex_is_bss_adhoc_aes(priv, bss_desc)) { + /* Ad-hoc AES enabled */ + return 0; + } else if (mwifiex_is_bss_dynamic_wep(priv, bss_desc)) { + /* Dynamic WEP enabled */ + return 0; + } + + /* Security doesn't match */ + dev_dbg(adapter->dev, + "info: %s: failed: wpa_ie=%#x wpa2_ie=%#x WEP=%s " + "WPA=%s WPA2=%s EncMode=%#x privacy=%#x\n", __func__, + (bss_desc->bcn_wpa_ie) ? + (*(bss_desc->bcn_wpa_ie)).vend_hdr.element_id : 0, + (bss_desc->bcn_rsn_ie) ? + (*(bss_desc->bcn_rsn_ie)).ieee_hdr.element_id : 0, + (priv->sec_info.wep_enabled) ? "e" : "d", + (priv->sec_info.wpa_enabled) ? "e" : "d", + (priv->sec_info.wpa2_enabled) ? "e" : "d", + priv->sec_info.encryption_mode, bss_desc->privacy); + return -1; + } + + /* Mode doesn't match */ + return -1; +} + +/* + * This function creates a channel list for the driver to scan, based + * on region/band information. + * + * This routine is used for any scan that is not provided with a + * specific channel list to scan. + */ +static int +mwifiex_scan_create_channel_list(struct mwifiex_private *priv, + const struct mwifiex_user_scan_cfg + *user_scan_in, + struct mwifiex_chan_scan_param_set + *scan_chan_list, + u8 filtered_scan) +{ + enum ieee80211_band band; + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + struct mwifiex_adapter *adapter = priv->adapter; + int chan_idx = 0, i; + + for (band = 0; (band < IEEE80211_NUM_BANDS) ; band++) { + + if (!priv->wdev->wiphy->bands[band]) + continue; + + sband = priv->wdev->wiphy->bands[band]; + + for (i = 0; (i < sband->n_channels) ; i++) { + ch = &sband->channels[i]; + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + scan_chan_list[chan_idx].radio_type = band; + + if (user_scan_in && + user_scan_in->chan_list[0].scan_time) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16((u16) user_scan_in-> + chan_list[0].scan_time); + else if (ch->flags & IEEE80211_CHAN_PASSIVE_SCAN) + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(adapter->passive_scan_time); + else + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(adapter->active_scan_time); + + if (ch->flags & IEEE80211_CHAN_PASSIVE_SCAN) + scan_chan_list[chan_idx].chan_scan_mode_bitmap + |= MWIFIEX_PASSIVE_SCAN; + else + scan_chan_list[chan_idx].chan_scan_mode_bitmap + &= ~MWIFIEX_PASSIVE_SCAN; + scan_chan_list[chan_idx].chan_number = + (u32) ch->hw_value; + if (filtered_scan) { + scan_chan_list[chan_idx].max_scan_time = + cpu_to_le16(adapter->specific_scan_time); + scan_chan_list[chan_idx].chan_scan_mode_bitmap + |= MWIFIEX_DISABLE_CHAN_FILT; + } + chan_idx++; + } + + } + return chan_idx; +} + +/* + * This function constructs and sends multiple scan config commands to + * the firmware. + * + * Previous routines in the code flow have created a scan command configuration + * with any requested TLVs. This function splits the channel TLV into maximum + * channels supported per scan lists and sends the portion of the channel TLV, + * along with the other TLVs, to the firmware. + */ +static int +mwifiex_scan_channel_list(struct mwifiex_private *priv, + u32 max_chan_per_scan, u8 filtered_scan, + struct mwifiex_scan_cmd_config *scan_cfg_out, + struct mwifiex_ie_types_chan_list_param_set + *chan_tlv_out, + struct mwifiex_chan_scan_param_set *scan_chan_list) +{ + int ret = 0; + struct mwifiex_chan_scan_param_set *tmp_chan_list; + struct mwifiex_chan_scan_param_set *start_chan; + + u32 tlv_idx; + u32 total_scan_time; + u32 done_early; + + if (!scan_cfg_out || !chan_tlv_out || !scan_chan_list) { + dev_dbg(priv->adapter->dev, + "info: Scan: Null detect: %p, %p, %p\n", + scan_cfg_out, chan_tlv_out, scan_chan_list); + return -1; + } + + chan_tlv_out->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + + /* Set the temp channel struct pointer to the start of the desired + list */ + tmp_chan_list = scan_chan_list; + + /* Loop through the desired channel list, sending a new firmware scan + commands for each max_chan_per_scan channels (or for 1,6,11 + individually if configured accordingly) */ + while (tmp_chan_list->chan_number) { + + tlv_idx = 0; + total_scan_time = 0; + chan_tlv_out->header.len = 0; + start_chan = tmp_chan_list; + done_early = false; + + /* + * Construct the Channel TLV for the scan command. Continue to + * insert channel TLVs until: + * - the tlv_idx hits the maximum configured per scan command + * - the next channel to insert is 0 (end of desired channel + * list) + * - done_early is set (controlling individual scanning of + * 1,6,11) + */ + while (tlv_idx < max_chan_per_scan && + tmp_chan_list->chan_number && !done_early) { + + dev_dbg(priv->adapter->dev, + "info: Scan: Chan(%3d), Radio(%d)," + " Mode(%d, %d), Dur(%d)\n", + tmp_chan_list->chan_number, + tmp_chan_list->radio_type, + tmp_chan_list->chan_scan_mode_bitmap + & MWIFIEX_PASSIVE_SCAN, + (tmp_chan_list->chan_scan_mode_bitmap + & MWIFIEX_DISABLE_CHAN_FILT) >> 1, + le16_to_cpu(tmp_chan_list->max_scan_time)); + + /* Copy the current channel TLV to the command being + prepared */ + memcpy(chan_tlv_out->chan_scan_param + tlv_idx, + tmp_chan_list, + sizeof(chan_tlv_out->chan_scan_param)); + + /* Increment the TLV header length by the size + appended */ + le16_add_cpu(&chan_tlv_out->header.len, + sizeof(chan_tlv_out->chan_scan_param)); + + /* + * The tlv buffer length is set to the number of bytes + * of the between the channel tlv pointer and the start + * of the tlv buffer. This compensates for any TLVs + * that were appended before the channel list. + */ + scan_cfg_out->tlv_buf_len = (u32) ((u8 *) chan_tlv_out - + scan_cfg_out->tlv_buf); + + /* Add the size of the channel tlv header and the data + length */ + scan_cfg_out->tlv_buf_len += + (sizeof(chan_tlv_out->header) + + le16_to_cpu(chan_tlv_out->header.len)); + + /* Increment the index to the channel tlv we are + constructing */ + tlv_idx++; + + /* Count the total scan time per command */ + total_scan_time += + le16_to_cpu(tmp_chan_list->max_scan_time); + + done_early = false; + + /* Stop the loop if the *current* channel is in the + 1,6,11 set and we are not filtering on a BSSID + or SSID. */ + if (!filtered_scan && + (tmp_chan_list->chan_number == 1 || + tmp_chan_list->chan_number == 6 || + tmp_chan_list->chan_number == 11)) + done_early = true; + + /* Increment the tmp pointer to the next channel to + be scanned */ + tmp_chan_list++; + + /* Stop the loop if the *next* channel is in the 1,6,11 + set. This will cause it to be the only channel + scanned on the next interation */ + if (!filtered_scan && + (tmp_chan_list->chan_number == 1 || + tmp_chan_list->chan_number == 6 || + tmp_chan_list->chan_number == 11)) + done_early = true; + } + + /* The total scan time should be less than scan command timeout + value */ + if (total_scan_time > MWIFIEX_MAX_TOTAL_SCAN_TIME) { + dev_err(priv->adapter->dev, "total scan time %dms" + " is over limit (%dms), scan skipped\n", + total_scan_time, MWIFIEX_MAX_TOTAL_SCAN_TIME); + ret = -1; + break; + } + + priv->adapter->scan_channels = start_chan; + + /* Send the scan command to the firmware with the specified + cfg */ + ret = mwifiex_send_cmd_async(priv, HostCmd_CMD_802_11_SCAN, + HostCmd_ACT_GEN_SET, 0, + scan_cfg_out); + if (ret) + break; + } + + if (ret) + return -1; + + return 0; +} + +/* + * This function constructs a scan command configuration structure to use + * in scan commands. + * + * Application layer or other functions can invoke network scanning + * with a scan configuration supplied in a user scan configuration structure. + * This structure is used as the basis of one or many scan command configuration + * commands that are sent to the command processing module and eventually to the + * firmware. + * + * This function creates a scan command configuration structure based on the + * following user supplied parameters (if present): + * - SSID filter + * - BSSID filter + * - Number of Probes to be sent + * - Channel list + * + * If the SSID or BSSID filter is not present, the filter is disabled/cleared. + * If the number of probes is not set, adapter default setting is used. + */ +static void +mwifiex_config_scan(struct mwifiex_private *priv, + const struct mwifiex_user_scan_cfg *user_scan_in, + struct mwifiex_scan_cmd_config *scan_cfg_out, + struct mwifiex_ie_types_chan_list_param_set **chan_list_out, + struct mwifiex_chan_scan_param_set *scan_chan_list, + u8 *max_chan_per_scan, u8 *filtered_scan, + u8 *scan_current_only) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct mwifiex_ie_types_num_probes *num_probes_tlv; + struct mwifiex_ie_types_wildcard_ssid_params *wildcard_ssid_tlv; + struct mwifiex_ie_types_rates_param_set *rates_tlv; + u8 *tlv_pos; + u32 num_probes; + u32 ssid_len; + u32 chan_idx; + u32 chan_num; + u32 scan_type; + u16 scan_dur; + u8 channel; + u8 radio_type; + int i; + u8 ssid_filter; + u8 rates[MWIFIEX_SUPPORTED_RATES]; + u32 rates_size; + struct mwifiex_ie_types_htcap *ht_cap; + + /* The tlv_buf_len is calculated for each scan command. The TLVs added + in this routine will be preserved since the routine that sends the + command will append channelTLVs at *chan_list_out. The difference + between the *chan_list_out and the tlv_buf start will be used to + calculate the size of anything we add in this routine. */ + scan_cfg_out->tlv_buf_len = 0; + + /* Running tlv pointer. Assigned to chan_list_out at end of function + so later routines know where channels can be added to the command + buf */ + tlv_pos = scan_cfg_out->tlv_buf; + + /* Initialize the scan as un-filtered; the flag is later set to TRUE + below if a SSID or BSSID filter is sent in the command */ + *filtered_scan = false; + + /* Initialize the scan as not being only on the current channel. If + the channel list is customized, only contains one channel, and is + the active channel, this is set true and data flow is not halted. */ + *scan_current_only = false; + + if (user_scan_in) { + + /* Default the ssid_filter flag to TRUE, set false under + certain wildcard conditions and qualified by the existence + of an SSID list before marking the scan as filtered */ + ssid_filter = true; + + /* Set the BSS type scan filter, use Adapter setting if + unset */ + scan_cfg_out->bss_mode = + (user_scan_in->bss_mode ? (u8) user_scan_in-> + bss_mode : (u8) adapter->scan_mode); + + /* Set the number of probes to send, use Adapter setting + if unset */ + num_probes = + (user_scan_in->num_probes ? user_scan_in-> + num_probes : adapter->scan_probes); + + /* + * Set the BSSID filter to the incoming configuration, + * if non-zero. If not set, it will remain disabled + * (all zeros). + */ + memcpy(scan_cfg_out->specific_bssid, + user_scan_in->specific_bssid, + sizeof(scan_cfg_out->specific_bssid)); + + for (i = 0; i < user_scan_in->num_ssids; i++) { + ssid_len = user_scan_in->ssid_list[i].ssid_len; + + wildcard_ssid_tlv = + (struct mwifiex_ie_types_wildcard_ssid_params *) + tlv_pos; + wildcard_ssid_tlv->header.type = + cpu_to_le16(TLV_TYPE_WILDCARDSSID); + wildcard_ssid_tlv->header.len = cpu_to_le16( + (u16) (ssid_len + sizeof(wildcard_ssid_tlv-> + max_ssid_length))); + + /* + * max_ssid_length = 0 tells firmware to perform + * specific scan for the SSID filled, whereas + * max_ssid_length = IEEE80211_MAX_SSID_LEN is for + * wildcard scan. + */ + if (ssid_len) + wildcard_ssid_tlv->max_ssid_length = 0; + else + wildcard_ssid_tlv->max_ssid_length = + IEEE80211_MAX_SSID_LEN; + + memcpy(wildcard_ssid_tlv->ssid, + user_scan_in->ssid_list[i].ssid, ssid_len); + + tlv_pos += (sizeof(wildcard_ssid_tlv->header) + + le16_to_cpu(wildcard_ssid_tlv->header.len)); + + dev_dbg(adapter->dev, "info: scan: ssid[%d]: %s, %d\n", + i, wildcard_ssid_tlv->ssid, + wildcard_ssid_tlv->max_ssid_length); + + /* Empty wildcard ssid with a maxlen will match many or + potentially all SSIDs (maxlen == 32), therefore do + not treat the scan as + filtered. */ + if (!ssid_len && wildcard_ssid_tlv->max_ssid_length) + ssid_filter = false; + } + + /* + * The default number of channels sent in the command is low to + * ensure the response buffer from the firmware does not + * truncate scan results. That is not an issue with an SSID + * or BSSID filter applied to the scan results in the firmware. + */ + if ((i && ssid_filter) || + !is_zero_ether_addr(scan_cfg_out->specific_bssid)) + *filtered_scan = true; + } else { + scan_cfg_out->bss_mode = (u8) adapter->scan_mode; + num_probes = adapter->scan_probes; + } + + /* + * If a specific BSSID or SSID is used, the number of channels in the + * scan command will be increased to the absolute maximum. + */ + if (*filtered_scan) + *max_chan_per_scan = MWIFIEX_MAX_CHANNELS_PER_SPECIFIC_SCAN; + else + *max_chan_per_scan = MWIFIEX_DEF_CHANNELS_PER_SCAN_CMD; + + /* If the input config or adapter has the number of Probes set, + add tlv */ + if (num_probes) { + + dev_dbg(adapter->dev, "info: scan: num_probes = %d\n", + num_probes); + + num_probes_tlv = (struct mwifiex_ie_types_num_probes *) tlv_pos; + num_probes_tlv->header.type = cpu_to_le16(TLV_TYPE_NUMPROBES); + num_probes_tlv->header.len = + cpu_to_le16(sizeof(num_probes_tlv->num_probes)); + num_probes_tlv->num_probes = cpu_to_le16((u16) num_probes); + + tlv_pos += sizeof(num_probes_tlv->header) + + le16_to_cpu(num_probes_tlv->header.len); + + } + + /* Append rates tlv */ + memset(rates, 0, sizeof(rates)); + + rates_size = mwifiex_get_supported_rates(priv, rates); + + rates_tlv = (struct mwifiex_ie_types_rates_param_set *) tlv_pos; + rates_tlv->header.type = cpu_to_le16(WLAN_EID_SUPP_RATES); + rates_tlv->header.len = cpu_to_le16((u16) rates_size); + memcpy(rates_tlv->rates, rates, rates_size); + tlv_pos += sizeof(rates_tlv->header) + rates_size; + + dev_dbg(adapter->dev, "info: SCAN_CMD: Rates size = %d\n", rates_size); + + if (ISSUPP_11NENABLED(priv->adapter->fw_cap_info) && + (priv->adapter->config_bands & BAND_GN || + priv->adapter->config_bands & BAND_AN)) { + ht_cap = (struct mwifiex_ie_types_htcap *) tlv_pos; + memset(ht_cap, 0, sizeof(struct mwifiex_ie_types_htcap)); + ht_cap->header.type = cpu_to_le16(WLAN_EID_HT_CAPABILITY); + ht_cap->header.len = + cpu_to_le16(sizeof(struct ieee80211_ht_cap)); + radio_type = + mwifiex_band_to_radio_type(priv->adapter->config_bands); + mwifiex_fill_cap_info(priv, radio_type, ht_cap); + tlv_pos += sizeof(struct mwifiex_ie_types_htcap); + } + + /* Append vendor specific IE TLV */ + mwifiex_cmd_append_vsie_tlv(priv, MWIFIEX_VSIE_MASK_SCAN, &tlv_pos); + + /* + * Set the output for the channel TLV to the address in the tlv buffer + * past any TLVs that were added in this function (SSID, num_probes). + * Channel TLVs will be added past this for each scan command, + * preserving the TLVs that were previously added. + */ + *chan_list_out = + (struct mwifiex_ie_types_chan_list_param_set *) tlv_pos; + + if (user_scan_in && user_scan_in->chan_list[0].chan_number) { + + dev_dbg(adapter->dev, "info: Scan: Using supplied channel list\n"); + + for (chan_idx = 0; + chan_idx < MWIFIEX_USER_SCAN_CHAN_MAX && + user_scan_in->chan_list[chan_idx].chan_number; + chan_idx++) { + + channel = user_scan_in->chan_list[chan_idx].chan_number; + (scan_chan_list + chan_idx)->chan_number = channel; + + radio_type = + user_scan_in->chan_list[chan_idx].radio_type; + (scan_chan_list + chan_idx)->radio_type = radio_type; + + scan_type = user_scan_in->chan_list[chan_idx].scan_type; + + if (scan_type == MWIFIEX_SCAN_TYPE_PASSIVE) + (scan_chan_list + + chan_idx)->chan_scan_mode_bitmap + |= MWIFIEX_PASSIVE_SCAN; + else + (scan_chan_list + + chan_idx)->chan_scan_mode_bitmap + &= ~MWIFIEX_PASSIVE_SCAN; + + if (*filtered_scan) + (scan_chan_list + + chan_idx)->chan_scan_mode_bitmap + |= MWIFIEX_DISABLE_CHAN_FILT; + + if (user_scan_in->chan_list[chan_idx].scan_time) { + scan_dur = (u16) user_scan_in-> + chan_list[chan_idx].scan_time; + } else { + if (scan_type == MWIFIEX_SCAN_TYPE_PASSIVE) + scan_dur = adapter->passive_scan_time; + else if (*filtered_scan) + scan_dur = adapter->specific_scan_time; + else + scan_dur = adapter->active_scan_time; + } + + (scan_chan_list + chan_idx)->min_scan_time = + cpu_to_le16(scan_dur); + (scan_chan_list + chan_idx)->max_scan_time = + cpu_to_le16(scan_dur); + } + + /* Check if we are only scanning the current channel */ + if ((chan_idx == 1) && + (user_scan_in->chan_list[0].chan_number == + priv->curr_bss_params.bss_descriptor.channel)) { + *scan_current_only = true; + dev_dbg(adapter->dev, + "info: Scan: Scanning current channel only\n"); + } + chan_num = chan_idx; + } else { + dev_dbg(adapter->dev, + "info: Scan: Creating full region channel list\n"); + chan_num = mwifiex_scan_create_channel_list(priv, user_scan_in, + scan_chan_list, + *filtered_scan); + } + + /* + * In associated state we will reduce the number of channels scanned per + * scan command to avoid any traffic delay/loss. This number is decided + * based on total number of channels to be scanned due to constraints + * of command buffers. + */ + if (priv->media_connected) { + if (chan_num < MWIFIEX_LIMIT_1_CHANNEL_PER_SCAN_CMD) + *max_chan_per_scan = 1; + else if (chan_num < MWIFIEX_LIMIT_2_CHANNELS_PER_SCAN_CMD) + *max_chan_per_scan = 2; + else if (chan_num < MWIFIEX_LIMIT_3_CHANNELS_PER_SCAN_CMD) + *max_chan_per_scan = 3; + else + *max_chan_per_scan = 4; + } +} + +/* + * This function inspects the scan response buffer for pointers to + * expected TLVs. + * + * TLVs can be included at the end of the scan response BSS information. + * + * Data in the buffer is parsed pointers to TLVs that can potentially + * be passed back in the response. + */ +static void +mwifiex_ret_802_11_scan_get_tlv_ptrs(struct mwifiex_adapter *adapter, + struct mwifiex_ie_types_data *tlv, + u32 tlv_buf_size, u32 req_tlv_type, + struct mwifiex_ie_types_data **tlv_data) +{ + struct mwifiex_ie_types_data *current_tlv; + u32 tlv_buf_left; + u32 tlv_type; + u32 tlv_len; + + current_tlv = tlv; + tlv_buf_left = tlv_buf_size; + *tlv_data = NULL; + + dev_dbg(adapter->dev, "info: SCAN_RESP: tlv_buf_size = %d\n", + tlv_buf_size); + + while (tlv_buf_left >= sizeof(struct mwifiex_ie_types_header)) { + + tlv_type = le16_to_cpu(current_tlv->header.type); + tlv_len = le16_to_cpu(current_tlv->header.len); + + if (sizeof(tlv->header) + tlv_len > tlv_buf_left) { + dev_err(adapter->dev, "SCAN_RESP: TLV buffer corrupt\n"); + break; + } + + if (req_tlv_type == tlv_type) { + switch (tlv_type) { + case TLV_TYPE_TSFTIMESTAMP: + dev_dbg(adapter->dev, "info: SCAN_RESP: TSF " + "timestamp TLV, len = %d\n", tlv_len); + *tlv_data = current_tlv; + break; + case TLV_TYPE_CHANNELBANDLIST: + dev_dbg(adapter->dev, "info: SCAN_RESP: channel" + " band list TLV, len = %d\n", tlv_len); + *tlv_data = current_tlv; + break; + default: + dev_err(adapter->dev, + "SCAN_RESP: unhandled TLV = %d\n", + tlv_type); + /* Give up, this seems corrupted */ + return; + } + } + + if (*tlv_data) + break; + + + tlv_buf_left -= (sizeof(tlv->header) + tlv_len); + current_tlv = + (struct mwifiex_ie_types_data *) (current_tlv->data + + tlv_len); + + } /* while */ +} + +/* + * This function parses provided beacon buffer and updates + * respective fields in bss descriptor structure. + */ +int mwifiex_update_bss_desc_with_ie(struct mwifiex_adapter *adapter, + struct mwifiex_bssdescriptor *bss_entry) +{ + int ret = 0; + u8 element_id; + struct ieee_types_fh_param_set *fh_param_set; + struct ieee_types_ds_param_set *ds_param_set; + struct ieee_types_cf_param_set *cf_param_set; + struct ieee_types_ibss_param_set *ibss_param_set; + u8 *current_ptr; + u8 *rate; + u8 element_len; + u16 total_ie_len; + u8 bytes_to_copy; + u8 rate_size; + u8 found_data_rate_ie; + u32 bytes_left; + struct ieee_types_vendor_specific *vendor_ie; + const u8 wpa_oui[4] = { 0x00, 0x50, 0xf2, 0x01 }; + const u8 wmm_oui[4] = { 0x00, 0x50, 0xf2, 0x02 }; + + found_data_rate_ie = false; + rate_size = 0; + current_ptr = bss_entry->beacon_buf; + bytes_left = bss_entry->beacon_buf_size; + + /* Process variable IE */ + while (bytes_left >= 2) { + element_id = *current_ptr; + element_len = *(current_ptr + 1); + total_ie_len = element_len + sizeof(struct ieee_types_header); + + if (bytes_left < total_ie_len) { + dev_err(adapter->dev, "err: InterpretIE: in processing" + " IE, bytes left < IE length\n"); + return -1; + } + switch (element_id) { + case WLAN_EID_SSID: + bss_entry->ssid.ssid_len = element_len; + memcpy(bss_entry->ssid.ssid, (current_ptr + 2), + element_len); + dev_dbg(adapter->dev, + "info: InterpretIE: ssid: %-32s\n", + bss_entry->ssid.ssid); + break; + + case WLAN_EID_SUPP_RATES: + memcpy(bss_entry->data_rates, current_ptr + 2, + element_len); + memcpy(bss_entry->supported_rates, current_ptr + 2, + element_len); + rate_size = element_len; + found_data_rate_ie = true; + break; + + case WLAN_EID_FH_PARAMS: + fh_param_set = + (struct ieee_types_fh_param_set *) current_ptr; + memcpy(&bss_entry->phy_param_set.fh_param_set, + fh_param_set, + sizeof(struct ieee_types_fh_param_set)); + break; + + case WLAN_EID_DS_PARAMS: + ds_param_set = + (struct ieee_types_ds_param_set *) current_ptr; + + bss_entry->channel = ds_param_set->current_chan; + + memcpy(&bss_entry->phy_param_set.ds_param_set, + ds_param_set, + sizeof(struct ieee_types_ds_param_set)); + break; + + case WLAN_EID_CF_PARAMS: + cf_param_set = + (struct ieee_types_cf_param_set *) current_ptr; + memcpy(&bss_entry->ss_param_set.cf_param_set, + cf_param_set, + sizeof(struct ieee_types_cf_param_set)); + break; + + case WLAN_EID_IBSS_PARAMS: + ibss_param_set = + (struct ieee_types_ibss_param_set *) + current_ptr; + memcpy(&bss_entry->ss_param_set.ibss_param_set, + ibss_param_set, + sizeof(struct ieee_types_ibss_param_set)); + break; + + case WLAN_EID_ERP_INFO: + bss_entry->erp_flags = *(current_ptr + 2); + break; + + case WLAN_EID_EXT_SUPP_RATES: + /* + * Only process extended supported rate + * if data rate is already found. + * Data rate IE should come before + * extended supported rate IE + */ + if (found_data_rate_ie) { + if ((element_len + rate_size) > + MWIFIEX_SUPPORTED_RATES) + bytes_to_copy = + (MWIFIEX_SUPPORTED_RATES - + rate_size); + else + bytes_to_copy = element_len; + + rate = (u8 *) bss_entry->data_rates; + rate += rate_size; + memcpy(rate, current_ptr + 2, bytes_to_copy); + + rate = (u8 *) bss_entry->supported_rates; + rate += rate_size; + memcpy(rate, current_ptr + 2, bytes_to_copy); + } + break; + + case WLAN_EID_VENDOR_SPECIFIC: + vendor_ie = (struct ieee_types_vendor_specific *) + current_ptr; + + if (!memcmp + (vendor_ie->vend_hdr.oui, wpa_oui, + sizeof(wpa_oui))) { + bss_entry->bcn_wpa_ie = + (struct ieee_types_vendor_specific *) + current_ptr; + bss_entry->wpa_offset = (u16) + (current_ptr - bss_entry->beacon_buf); + } else if (!memcmp(vendor_ie->vend_hdr.oui, wmm_oui, + sizeof(wmm_oui))) { + if (total_ie_len == + sizeof(struct ieee_types_wmm_parameter) || + total_ie_len == + sizeof(struct ieee_types_wmm_info)) + /* + * Only accept and copy the WMM IE if + * it matches the size expected for the + * WMM Info IE or the WMM Parameter IE. + */ + memcpy((u8 *) &bss_entry->wmm_ie, + current_ptr, total_ie_len); + } + break; + case WLAN_EID_RSN: + bss_entry->bcn_rsn_ie = + (struct ieee_types_generic *) current_ptr; + bss_entry->rsn_offset = (u16) (current_ptr - + bss_entry->beacon_buf); + break; + case WLAN_EID_BSS_AC_ACCESS_DELAY: + bss_entry->bcn_wapi_ie = + (struct ieee_types_generic *) current_ptr; + bss_entry->wapi_offset = (u16) (current_ptr - + bss_entry->beacon_buf); + break; + case WLAN_EID_HT_CAPABILITY: + bss_entry->bcn_ht_cap = (struct ieee80211_ht_cap *) + (current_ptr + + sizeof(struct ieee_types_header)); + bss_entry->ht_cap_offset = (u16) (current_ptr + + sizeof(struct ieee_types_header) - + bss_entry->beacon_buf); + break; + case WLAN_EID_HT_OPERATION: + bss_entry->bcn_ht_oper = + (struct ieee80211_ht_operation *)(current_ptr + + sizeof(struct ieee_types_header)); + bss_entry->ht_info_offset = (u16) (current_ptr + + sizeof(struct ieee_types_header) - + bss_entry->beacon_buf); + break; + case WLAN_EID_VHT_CAPABILITY: + bss_entry->disable_11ac = false; + bss_entry->bcn_vht_cap = + (void *)(current_ptr + + sizeof(struct ieee_types_header)); + bss_entry->vht_cap_offset = + (u16)((u8 *)bss_entry->bcn_vht_cap - + bss_entry->beacon_buf); + break; + case WLAN_EID_VHT_OPERATION: + bss_entry->bcn_vht_oper = + (void *)(current_ptr + + sizeof(struct ieee_types_header)); + bss_entry->vht_info_offset = + (u16)((u8 *)bss_entry->bcn_vht_oper - + bss_entry->beacon_buf); + break; + case WLAN_EID_BSS_COEX_2040: + bss_entry->bcn_bss_co_2040 = current_ptr + + sizeof(struct ieee_types_header); + bss_entry->bss_co_2040_offset = (u16) (current_ptr + + sizeof(struct ieee_types_header) - + bss_entry->beacon_buf); + break; + case WLAN_EID_EXT_CAPABILITY: + bss_entry->bcn_ext_cap = current_ptr + + sizeof(struct ieee_types_header); + bss_entry->ext_cap_offset = (u16) (current_ptr + + sizeof(struct ieee_types_header) - + bss_entry->beacon_buf); + break; + case WLAN_EID_OPMODE_NOTIF: + bss_entry->oper_mode = + (void *)(current_ptr + + sizeof(struct ieee_types_header)); + bss_entry->oper_mode_offset = + (u16)((u8 *)bss_entry->oper_mode - + bss_entry->beacon_buf); + break; + default: + break; + } + + current_ptr += element_len + 2; + + /* Need to account for IE ID and IE Len */ + bytes_left -= (element_len + 2); + + } /* while (bytes_left > 2) */ + return ret; +} + +/* + * This function converts radio type scan parameter to a band configuration + * to be used in join command. + */ +static u8 +mwifiex_radio_type_to_band(u8 radio_type) +{ + switch (radio_type) { + case HostCmd_SCAN_RADIO_TYPE_A: + return BAND_A; + case HostCmd_SCAN_RADIO_TYPE_BG: + default: + return BAND_G; + } +} + +/* + * This is an internal function used to start a scan based on an input + * configuration. + * + * This uses the input user scan configuration information when provided in + * order to send the appropriate scan commands to firmware to populate or + * update the internal driver scan table. + */ +int mwifiex_scan_networks(struct mwifiex_private *priv, + const struct mwifiex_user_scan_cfg *user_scan_in) +{ + int ret; + struct mwifiex_adapter *adapter = priv->adapter; + struct cmd_ctrl_node *cmd_node; + union mwifiex_scan_cmd_config_tlv *scan_cfg_out; + struct mwifiex_ie_types_chan_list_param_set *chan_list_out; + struct mwifiex_chan_scan_param_set *scan_chan_list; + u8 filtered_scan; + u8 scan_current_chan_only; + u8 max_chan_per_scan; + unsigned long flags; + + if (adapter->scan_processing) { + dev_err(adapter->dev, "cmd: Scan already in process...\n"); + return -EBUSY; + } + + if (priv->scan_block) { + dev_err(adapter->dev, + "cmd: Scan is blocked during association...\n"); + return -EBUSY; + } + + spin_lock_irqsave(&adapter->mwifiex_cmd_lock, flags); + adapter->scan_processing = true; + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, flags); + + scan_cfg_out = kzalloc(sizeof(union mwifiex_scan_cmd_config_tlv), + GFP_KERNEL); + if (!scan_cfg_out) { + ret = -ENOMEM; + goto done; + } + + scan_chan_list = kcalloc(MWIFIEX_USER_SCAN_CHAN_MAX, + sizeof(struct mwifiex_chan_scan_param_set), + GFP_KERNEL); + if (!scan_chan_list) { + kfree(scan_cfg_out); + ret = -ENOMEM; + goto done; + } + + mwifiex_config_scan(priv, user_scan_in, &scan_cfg_out->config, + &chan_list_out, scan_chan_list, &max_chan_per_scan, + &filtered_scan, &scan_current_chan_only); + + ret = mwifiex_scan_channel_list(priv, max_chan_per_scan, filtered_scan, + &scan_cfg_out->config, chan_list_out, + scan_chan_list); + + /* Get scan command from scan_pending_q and put to cmd_pending_q */ + if (!ret) { + spin_lock_irqsave(&adapter->scan_pending_q_lock, flags); + if (!list_empty(&adapter->scan_pending_q)) { + cmd_node = list_first_entry(&adapter->scan_pending_q, + struct cmd_ctrl_node, list); + list_del(&cmd_node->list); + spin_unlock_irqrestore(&adapter->scan_pending_q_lock, + flags); + mwifiex_insert_cmd_to_pending_q(adapter, cmd_node, + true); + queue_work(adapter->workqueue, &adapter->main_work); + + /* Perform internal scan synchronously */ + if (!priv->scan_request) { + dev_dbg(adapter->dev, "wait internal scan\n"); + mwifiex_wait_queue_complete(adapter, cmd_node); + } + } else { + spin_unlock_irqrestore(&adapter->scan_pending_q_lock, + flags); + } + } + + kfree(scan_cfg_out); + kfree(scan_chan_list); +done: + if (ret) { + spin_lock_irqsave(&adapter->mwifiex_cmd_lock, flags); + adapter->scan_processing = false; + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, flags); + } + return ret; +} + +/* + * This function prepares a scan command to be sent to the firmware. + * + * This uses the scan command configuration sent to the command processing + * module in command preparation stage to configure a scan command structure + * to send to firmware. + * + * The fixed fields specifying the BSS type and BSSID filters as well as a + * variable number/length of TLVs are sent in the command to firmware. + * + * Preparation also includes - + * - Setting command ID, and proper size + * - Ensuring correct endian-ness + */ +int mwifiex_cmd_802_11_scan(struct host_cmd_ds_command *cmd, + struct mwifiex_scan_cmd_config *scan_cfg) +{ + struct host_cmd_ds_802_11_scan *scan_cmd = &cmd->params.scan; + + /* Set fixed field variables in scan command */ + scan_cmd->bss_mode = scan_cfg->bss_mode; + memcpy(scan_cmd->bssid, scan_cfg->specific_bssid, + sizeof(scan_cmd->bssid)); + memcpy(scan_cmd->tlv_buffer, scan_cfg->tlv_buf, scan_cfg->tlv_buf_len); + + cmd->command = cpu_to_le16(HostCmd_CMD_802_11_SCAN); + + /* Size is equal to the sizeof(fixed portions) + the TLV len + header */ + cmd->size = cpu_to_le16((u16) (sizeof(scan_cmd->bss_mode) + + sizeof(scan_cmd->bssid) + + scan_cfg->tlv_buf_len + S_DS_GEN)); + + return 0; +} + +/* + * This function checks compatibility of requested network with current + * driver settings. + */ +int mwifiex_check_network_compatibility(struct mwifiex_private *priv, + struct mwifiex_bssdescriptor *bss_desc) +{ + int ret = -1; + + if (!bss_desc) + return -1; + + if ((mwifiex_get_cfp(priv, (u8) bss_desc->bss_band, + (u16) bss_desc->channel, 0))) { + switch (priv->bss_mode) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: + ret = mwifiex_is_network_compatible(priv, bss_desc, + priv->bss_mode); + if (ret) + dev_err(priv->adapter->dev, + "Incompatible network settings\n"); + break; + default: + ret = 0; + } + } + + return ret; +} + +static int mwifiex_update_curr_bss_params(struct mwifiex_private *priv, + struct cfg80211_bss *bss) +{ + struct mwifiex_bssdescriptor *bss_desc; + int ret; + unsigned long flags; + + /* Allocate and fill new bss descriptor */ + bss_desc = kzalloc(sizeof(struct mwifiex_bssdescriptor), GFP_KERNEL); + if (!bss_desc) + return -ENOMEM; + + ret = mwifiex_fill_new_bss_desc(priv, bss, bss_desc); + if (ret) + goto done; + + ret = mwifiex_check_network_compatibility(priv, bss_desc); + if (ret) + goto done; + + spin_lock_irqsave(&priv->curr_bcn_buf_lock, flags); + /* Make a copy of current BSSID descriptor */ + memcpy(&priv->curr_bss_params.bss_descriptor, bss_desc, + sizeof(priv->curr_bss_params.bss_descriptor)); + + /* The contents of beacon_ie will be copied to its own buffer + * in mwifiex_save_curr_bcn() + */ + mwifiex_save_curr_bcn(priv); + spin_unlock_irqrestore(&priv->curr_bcn_buf_lock, flags); + +done: + /* beacon_ie buffer was allocated in function + * mwifiex_fill_new_bss_desc(). Free it now. + */ + kfree(bss_desc->beacon_buf); + kfree(bss_desc); + return 0; +} + +/* + * This function handles the command response of scan. + * + * The response buffer for the scan command has the following + * memory layout: + * + * .-------------------------------------------------------------. + * | Header (4 * sizeof(t_u16)): Standard command response hdr | + * .-------------------------------------------------------------. + * | BufSize (t_u16) : sizeof the BSS Description data | + * .-------------------------------------------------------------. + * | NumOfSet (t_u8) : Number of BSS Descs returned | + * .-------------------------------------------------------------. + * | BSSDescription data (variable, size given in BufSize) | + * .-------------------------------------------------------------. + * | TLV data (variable, size calculated using Header->Size, | + * | BufSize and sizeof the fixed fields above) | + * .-------------------------------------------------------------. + */ +int mwifiex_ret_802_11_scan(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + int ret = 0; + struct mwifiex_adapter *adapter = priv->adapter; + struct cmd_ctrl_node *cmd_node; + struct host_cmd_ds_802_11_scan_rsp *scan_rsp; + struct mwifiex_ie_types_data *tlv_data; + struct mwifiex_ie_types_tsf_timestamp *tsf_tlv; + u8 *bss_info; + u32 scan_resp_size; + u32 bytes_left; + u32 idx; + u32 tlv_buf_size; + struct mwifiex_chan_freq_power *cfp; + struct mwifiex_ie_types_chan_band_list_param_set *chan_band_tlv; + struct chan_band_param_set *chan_band; + u8 is_bgscan_resp; + unsigned long flags; + struct cfg80211_bss *bss; + + is_bgscan_resp = (le16_to_cpu(resp->command) + == HostCmd_CMD_802_11_BG_SCAN_QUERY); + if (is_bgscan_resp) + scan_rsp = &resp->params.bg_scan_query_resp.scan_resp; + else + scan_rsp = &resp->params.scan_resp; + + + if (scan_rsp->number_of_sets > MWIFIEX_MAX_AP) { + dev_err(adapter->dev, "SCAN_RESP: too many AP returned (%d)\n", + scan_rsp->number_of_sets); + ret = -1; + goto check_next_scan; + } + + bytes_left = le16_to_cpu(scan_rsp->bss_descript_size); + dev_dbg(adapter->dev, "info: SCAN_RESP: bss_descript_size %d\n", + bytes_left); + + scan_resp_size = le16_to_cpu(resp->size); + + dev_dbg(adapter->dev, + "info: SCAN_RESP: returned %d APs before parsing\n", + scan_rsp->number_of_sets); + + bss_info = scan_rsp->bss_desc_and_tlv_buffer; + + /* + * The size of the TLV buffer is equal to the entire command response + * size (scan_resp_size) minus the fixed fields (sizeof()'s), the + * BSS Descriptions (bss_descript_size as bytesLef) and the command + * response header (S_DS_GEN) + */ + tlv_buf_size = scan_resp_size - (bytes_left + + sizeof(scan_rsp->bss_descript_size) + + sizeof(scan_rsp->number_of_sets) + + S_DS_GEN); + + tlv_data = (struct mwifiex_ie_types_data *) (scan_rsp-> + bss_desc_and_tlv_buffer + + bytes_left); + + /* Search the TLV buffer space in the scan response for any valid + TLVs */ + mwifiex_ret_802_11_scan_get_tlv_ptrs(adapter, tlv_data, tlv_buf_size, + TLV_TYPE_TSFTIMESTAMP, + (struct mwifiex_ie_types_data **) + &tsf_tlv); + + /* Search the TLV buffer space in the scan response for any valid + TLVs */ + mwifiex_ret_802_11_scan_get_tlv_ptrs(adapter, tlv_data, tlv_buf_size, + TLV_TYPE_CHANNELBANDLIST, + (struct mwifiex_ie_types_data **) + &chan_band_tlv); + + for (idx = 0; idx < scan_rsp->number_of_sets && bytes_left; idx++) { + u8 bssid[ETH_ALEN]; + s32 rssi; + const u8 *ie_buf; + size_t ie_len; + u16 channel = 0; + u64 fw_tsf = 0; + u16 beacon_size = 0; + u32 curr_bcn_bytes; + u32 freq; + u16 beacon_period; + u16 cap_info_bitmap; + u8 *current_ptr; + u64 timestamp; + struct mwifiex_bcn_param *bcn_param; + struct mwifiex_bss_priv *bss_priv; + + if (bytes_left >= sizeof(beacon_size)) { + /* Extract & convert beacon size from command buffer */ + memcpy(&beacon_size, bss_info, sizeof(beacon_size)); + bytes_left -= sizeof(beacon_size); + bss_info += sizeof(beacon_size); + } + + if (!beacon_size || beacon_size > bytes_left) { + bss_info += bytes_left; + bytes_left = 0; + ret = -1; + goto check_next_scan; + } + + /* Initialize the current working beacon pointer for this BSS + * iteration */ + current_ptr = bss_info; + + /* Advance the return beacon pointer past the current beacon */ + bss_info += beacon_size; + bytes_left -= beacon_size; + + curr_bcn_bytes = beacon_size; + + /* + * First 5 fields are bssid, RSSI, time stamp, beacon interval, + * and capability information + */ + if (curr_bcn_bytes < sizeof(struct mwifiex_bcn_param)) { + dev_err(adapter->dev, + "InterpretIE: not enough bytes left\n"); + continue; + } + bcn_param = (struct mwifiex_bcn_param *)current_ptr; + current_ptr += sizeof(*bcn_param); + curr_bcn_bytes -= sizeof(*bcn_param); + + memcpy(bssid, bcn_param->bssid, ETH_ALEN); + + rssi = (s32) bcn_param->rssi; + rssi = (-rssi) * 100; /* Convert dBm to mBm */ + dev_dbg(adapter->dev, "info: InterpretIE: RSSI=%d\n", rssi); + + timestamp = le64_to_cpu(bcn_param->timestamp); + beacon_period = le16_to_cpu(bcn_param->beacon_period); + + cap_info_bitmap = le16_to_cpu(bcn_param->cap_info_bitmap); + dev_dbg(adapter->dev, "info: InterpretIE: capabilities=0x%X\n", + cap_info_bitmap); + + /* Rest of the current buffer are IE's */ + ie_buf = current_ptr; + ie_len = curr_bcn_bytes; + dev_dbg(adapter->dev, + "info: InterpretIE: IELength for this AP = %d\n", + curr_bcn_bytes); + + while (curr_bcn_bytes >= sizeof(struct ieee_types_header)) { + u8 element_id, element_len; + + element_id = *current_ptr; + element_len = *(current_ptr + 1); + if (curr_bcn_bytes < element_len + + sizeof(struct ieee_types_header)) { + dev_err(priv->adapter->dev, + "%s: bytes left < IE length\n", + __func__); + goto check_next_scan; + } + if (element_id == WLAN_EID_DS_PARAMS) { + channel = *(current_ptr + sizeof(struct ieee_types_header)); + break; + } + + current_ptr += element_len + + sizeof(struct ieee_types_header); + curr_bcn_bytes -= element_len + + sizeof(struct ieee_types_header); + } + + /* + * If the TSF TLV was appended to the scan results, save this + * entry's TSF value in the fw_tsf field. It is the firmware's + * TSF value at the time the beacon or probe response was + * received. + */ + if (tsf_tlv) + memcpy(&fw_tsf, &tsf_tlv->tsf_data[idx * TSF_DATA_SIZE], + sizeof(fw_tsf)); + + if (channel) { + struct ieee80211_channel *chan; + u8 band; + + band = BAND_G; + if (chan_band_tlv) { + chan_band = + &chan_band_tlv->chan_band_param[idx]; + band = mwifiex_radio_type_to_band( + chan_band->radio_type + & (BIT(0) | BIT(1))); + } + + cfp = mwifiex_get_cfp(priv, band, channel, 0); + + freq = cfp ? cfp->freq : 0; + + chan = ieee80211_get_channel(priv->wdev->wiphy, freq); + + if (chan && !(chan->flags & IEEE80211_CHAN_DISABLED)) { + bss = cfg80211_inform_bss(priv->wdev->wiphy, + chan, bssid, timestamp, + cap_info_bitmap, beacon_period, + ie_buf, ie_len, rssi, GFP_KERNEL); + bss_priv = (struct mwifiex_bss_priv *)bss->priv; + bss_priv->band = band; + bss_priv->fw_tsf = fw_tsf; + if (priv->media_connected && + !memcmp(bssid, + priv->curr_bss_params.bss_descriptor + .mac_address, ETH_ALEN)) + mwifiex_update_curr_bss_params(priv, + bss); + cfg80211_put_bss(priv->wdev->wiphy, bss); + } + } else { + dev_dbg(adapter->dev, "missing BSS channel IE\n"); + } + } + +check_next_scan: + spin_lock_irqsave(&adapter->scan_pending_q_lock, flags); + if (list_empty(&adapter->scan_pending_q)) { + spin_unlock_irqrestore(&adapter->scan_pending_q_lock, flags); + spin_lock_irqsave(&adapter->mwifiex_cmd_lock, flags); + adapter->scan_processing = false; + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, flags); + + /* Need to indicate IOCTL complete */ + if (adapter->curr_cmd->wait_q_enabled) { + adapter->cmd_wait_q.status = 0; + if (!priv->scan_request) { + dev_dbg(adapter->dev, + "complete internal scan\n"); + mwifiex_complete_cmd(adapter, + adapter->curr_cmd); + } + } + if (priv->report_scan_result) + priv->report_scan_result = false; + + if (priv->user_scan_cfg) { + if (priv->scan_request) { + dev_dbg(priv->adapter->dev, + "info: notifying scan done\n"); + cfg80211_scan_done(priv->scan_request, 0); + priv->scan_request = NULL; + } else { + dev_dbg(priv->adapter->dev, + "info: scan already aborted\n"); + } + + kfree(priv->user_scan_cfg); + priv->user_scan_cfg = NULL; + } + } else { + if (priv->user_scan_cfg && !priv->scan_request) { + spin_unlock_irqrestore(&adapter->scan_pending_q_lock, + flags); + adapter->scan_delay_cnt = MWIFIEX_MAX_SCAN_DELAY_CNT; + mod_timer(&priv->scan_delay_timer, jiffies); + dev_dbg(priv->adapter->dev, + "info: %s: triggerring scan abort\n", __func__); + } else if (!mwifiex_wmm_lists_empty(adapter) && + (priv->scan_request && (priv->scan_request->flags & + NL80211_SCAN_FLAG_LOW_PRIORITY))) { + spin_unlock_irqrestore(&adapter->scan_pending_q_lock, + flags); + adapter->scan_delay_cnt = 1; + mod_timer(&priv->scan_delay_timer, jiffies + + msecs_to_jiffies(MWIFIEX_SCAN_DELAY_MSEC)); + dev_dbg(priv->adapter->dev, + "info: %s: deferring scan\n", __func__); + } else { + /* Get scan command from scan_pending_q and put to + cmd_pending_q */ + cmd_node = list_first_entry(&adapter->scan_pending_q, + struct cmd_ctrl_node, list); + list_del(&cmd_node->list); + spin_unlock_irqrestore(&adapter->scan_pending_q_lock, + flags); + mwifiex_insert_cmd_to_pending_q(adapter, cmd_node, + true); + } + } + + return ret; +} + +/* + * This function prepares command for background scan query. + * + * Preparation includes - + * - Setting command ID and proper size + * - Setting background scan flush parameter + * - Ensuring correct endian-ness + */ +int mwifiex_cmd_802_11_bg_scan_query(struct host_cmd_ds_command *cmd) +{ + struct host_cmd_ds_802_11_bg_scan_query *bg_query = + &cmd->params.bg_scan_query; + + cmd->command = cpu_to_le16(HostCmd_CMD_802_11_BG_SCAN_QUERY); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_bg_scan_query) + + S_DS_GEN); + + bg_query->flush = 1; + + return 0; +} + +/* + * This function inserts scan command node to the scan pending queue. + */ +void +mwifiex_queue_scan_cmd(struct mwifiex_private *priv, + struct cmd_ctrl_node *cmd_node) +{ + struct mwifiex_adapter *adapter = priv->adapter; + unsigned long flags; + + cmd_node->wait_q_enabled = true; + cmd_node->condition = &adapter->scan_wait_q_woken; + spin_lock_irqsave(&adapter->scan_pending_q_lock, flags); + list_add_tail(&cmd_node->list, &adapter->scan_pending_q); + spin_unlock_irqrestore(&adapter->scan_pending_q_lock, flags); +} + +/* + * This function sends a scan command for all available channels to the + * firmware, filtered on a specific SSID. + */ +static int mwifiex_scan_specific_ssid(struct mwifiex_private *priv, + struct cfg80211_ssid *req_ssid) +{ + struct mwifiex_adapter *adapter = priv->adapter; + int ret; + struct mwifiex_user_scan_cfg *scan_cfg; + + if (adapter->scan_processing) { + dev_err(adapter->dev, "cmd: Scan already in process...\n"); + return -EBUSY; + } + + if (priv->scan_block) { + dev_err(adapter->dev, + "cmd: Scan is blocked during association...\n"); + return -EBUSY; + } + + scan_cfg = kzalloc(sizeof(struct mwifiex_user_scan_cfg), GFP_KERNEL); + if (!scan_cfg) + return -ENOMEM; + + scan_cfg->ssid_list = req_ssid; + scan_cfg->num_ssids = 1; + + ret = mwifiex_scan_networks(priv, scan_cfg); + + kfree(scan_cfg); + return ret; +} + +/* + * Sends IOCTL request to start a scan. + * + * This function allocates the IOCTL request buffer, fills it + * with requisite parameters and calls the IOCTL handler. + * + * Scan command can be issued for both normal scan and specific SSID + * scan, depending upon whether an SSID is provided or not. + */ +int mwifiex_request_scan(struct mwifiex_private *priv, + struct cfg80211_ssid *req_ssid) +{ + int ret; + + if (down_interruptible(&priv->async_sem)) { + dev_err(priv->adapter->dev, "%s: acquire semaphore\n", + __func__); + return -1; + } + + priv->adapter->scan_wait_q_woken = false; + + if (req_ssid && req_ssid->ssid_len != 0) + /* Specific SSID scan */ + ret = mwifiex_scan_specific_ssid(priv, req_ssid); + else + /* Normal scan */ + ret = mwifiex_scan_networks(priv, NULL); + + up(&priv->async_sem); + + return ret; +} + +/* + * This function appends the vendor specific IE TLV to a buffer. + */ +int +mwifiex_cmd_append_vsie_tlv(struct mwifiex_private *priv, + u16 vsie_mask, u8 **buffer) +{ + int id, ret_len = 0; + struct mwifiex_ie_types_vendor_param_set *vs_param_set; + + if (!buffer) + return 0; + if (!(*buffer)) + return 0; + + /* + * Traverse through the saved vendor specific IE array and append + * the selected(scan/assoc/adhoc) IE as TLV to the command + */ + for (id = 0; id < MWIFIEX_MAX_VSIE_NUM; id++) { + if (priv->vs_ie[id].mask & vsie_mask) { + vs_param_set = + (struct mwifiex_ie_types_vendor_param_set *) + *buffer; + vs_param_set->header.type = + cpu_to_le16(TLV_TYPE_PASSTHROUGH); + vs_param_set->header.len = + cpu_to_le16((((u16) priv->vs_ie[id].ie[1]) + & 0x00FF) + 2); + memcpy(vs_param_set->ie, priv->vs_ie[id].ie, + le16_to_cpu(vs_param_set->header.len)); + *buffer += le16_to_cpu(vs_param_set->header.len) + + sizeof(struct mwifiex_ie_types_header); + ret_len += le16_to_cpu(vs_param_set->header.len) + + sizeof(struct mwifiex_ie_types_header); + } + } + return ret_len; +} + +/* + * This function saves a beacon buffer of the current BSS descriptor. + * + * The current beacon buffer is saved so that it can be restored in the + * following cases that makes the beacon buffer not to contain the current + * ssid's beacon buffer. + * - The current ssid was not found somehow in the last scan. + * - The current ssid was the last entry of the scan table and overloaded. + */ +void +mwifiex_save_curr_bcn(struct mwifiex_private *priv) +{ + struct mwifiex_bssdescriptor *curr_bss = + &priv->curr_bss_params.bss_descriptor; + + if (!curr_bss->beacon_buf_size) + return; + + /* allocate beacon buffer at 1st time; or if it's size has changed */ + if (!priv->curr_bcn_buf || + priv->curr_bcn_size != curr_bss->beacon_buf_size) { + priv->curr_bcn_size = curr_bss->beacon_buf_size; + + kfree(priv->curr_bcn_buf); + priv->curr_bcn_buf = kmalloc(curr_bss->beacon_buf_size, + GFP_ATOMIC); + if (!priv->curr_bcn_buf) + return; + } + + memcpy(priv->curr_bcn_buf, curr_bss->beacon_buf, + curr_bss->beacon_buf_size); + dev_dbg(priv->adapter->dev, "info: current beacon saved %d\n", + priv->curr_bcn_size); + + curr_bss->beacon_buf = priv->curr_bcn_buf; + + /* adjust the pointers in the current BSS descriptor */ + if (curr_bss->bcn_wpa_ie) + curr_bss->bcn_wpa_ie = + (struct ieee_types_vendor_specific *) + (curr_bss->beacon_buf + + curr_bss->wpa_offset); + + if (curr_bss->bcn_rsn_ie) + curr_bss->bcn_rsn_ie = (struct ieee_types_generic *) + (curr_bss->beacon_buf + + curr_bss->rsn_offset); + + if (curr_bss->bcn_ht_cap) + curr_bss->bcn_ht_cap = (struct ieee80211_ht_cap *) + (curr_bss->beacon_buf + + curr_bss->ht_cap_offset); + + if (curr_bss->bcn_ht_oper) + curr_bss->bcn_ht_oper = (struct ieee80211_ht_operation *) + (curr_bss->beacon_buf + + curr_bss->ht_info_offset); + + if (curr_bss->bcn_vht_cap) + curr_bss->bcn_ht_cap = (void *)(curr_bss->beacon_buf + + curr_bss->vht_cap_offset); + + if (curr_bss->bcn_vht_oper) + curr_bss->bcn_ht_oper = (void *)(curr_bss->beacon_buf + + curr_bss->vht_info_offset); + + if (curr_bss->bcn_bss_co_2040) + curr_bss->bcn_bss_co_2040 = + (curr_bss->beacon_buf + curr_bss->bss_co_2040_offset); + + if (curr_bss->bcn_ext_cap) + curr_bss->bcn_ext_cap = curr_bss->beacon_buf + + curr_bss->ext_cap_offset; + + if (curr_bss->oper_mode) + curr_bss->oper_mode = (void *)(curr_bss->beacon_buf + + curr_bss->oper_mode_offset); +} + +/* + * This function frees the current BSS descriptor beacon buffer. + */ +void +mwifiex_free_curr_bcn(struct mwifiex_private *priv) +{ + kfree(priv->curr_bcn_buf); + priv->curr_bcn_buf = NULL; +} diff --git a/drivers/net/wireless/mwifiex/sdio.c b/drivers/net/wireless/mwifiex/sdio.c new file mode 100644 index 0000000..57e5620 --- /dev/null +++ b/drivers/net/wireless/mwifiex/sdio.c @@ -0,0 +1,1843 @@ +/* + * Marvell Wireless LAN device driver: SDIO specific handling + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" +#include "sdio.h" + + +#define SDIO_VERSION "1.0" + +/* The mwifiex_sdio_remove() callback function is called when + * user removes this module from kernel space or ejects + * the card from the slot. The driver handles these 2 cases + * differently. + * If the user is removing the module, the few commands (FUNC_SHUTDOWN, + * HS_CANCEL etc.) are sent to the firmware. + * If the card is removed, there is no need to send these command. + * + * The variable 'user_rmmod' is used to distinguish these two + * scenarios. This flag is initialized as FALSE in case the card + * is removed, and will be set to TRUE for module removal when + * module_exit function is called. + */ +static u8 user_rmmod; + +static struct mwifiex_if_ops sdio_ops; + +static struct semaphore add_remove_card_sem; + +static int mwifiex_sdio_resume(struct device *dev); + +/* + * SDIO probe. + * + * This function probes an mwifiex device and registers it. It allocates + * the card structure, enables SDIO function number and initiates the + * device registration and initialization procedure by adding a logical + * interface. + */ +static int +mwifiex_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id) +{ + int ret; + struct sdio_mmc_card *card = NULL; + + pr_debug("info: vendor=0x%4.04X device=0x%4.04X class=%d function=%d\n", + func->vendor, func->device, func->class, func->num); + + card = kzalloc(sizeof(struct sdio_mmc_card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->func = func; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) + func->card->quirks |= MMC_QUIRK_BLKSZ_FOR_BYTE_MODE; +#endif + + sdio_claim_host(func); + ret = sdio_enable_func(func); + sdio_release_host(func); + + if (ret) { + pr_err("%s: failed to enable function\n", __func__); + kfree(card); + return -EIO; + } + + if (mwifiex_add_card(card, &add_remove_card_sem, &sdio_ops, + MWIFIEX_SDIO)) { + pr_err("%s: add card failed\n", __func__); + kfree(card); + sdio_claim_host(func); + ret = sdio_disable_func(func); + sdio_release_host(func); + ret = -1; + } + + return ret; +} + +/* + * SDIO remove. + * + * This function removes the interface and frees up the card structure. + */ +static void +mwifiex_sdio_remove(struct sdio_func *func) +{ + struct sdio_mmc_card *card; + struct mwifiex_adapter *adapter; + struct mwifiex_private *priv; + int i; + + pr_debug("info: SDIO func num=%d\n", func->num); + + card = sdio_get_drvdata(func); + if (!card) + return; + + adapter = card->adapter; + if (!adapter || !adapter->priv_num) + return; + + /* In case driver is removed when asynchronous FW load is in progress */ + wait_for_completion(&adapter->fw_load); + + if (user_rmmod) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) + if (adapter->is_suspended) + mwifiex_sdio_resume(adapter->dev); +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) */ + + for (i = 0; i < adapter->priv_num; i++) + if ((GET_BSS_ROLE(adapter->priv[i]) == + MWIFIEX_BSS_ROLE_STA) && + adapter->priv[i]->media_connected) + mwifiex_deauthenticate(adapter->priv[i], NULL); + + priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); + mwifiex_disable_auto_ds(priv); + mwifiex_init_shutdown_fw(priv, MWIFIEX_FUNC_SHUTDOWN); + } + + mwifiex_remove_card(card->adapter, &add_remove_card_sem); + kfree(card); +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) +/* + * SDIO suspend. + * + * Kernel needs to suspend all functions separately. Therefore all + * registered functions must have drivers with suspend and resume + * methods. Failing that the kernel simply removes the whole card. + * + * If already not suspended, this function allocates and sends a host + * sleep activate request to the firmware and turns off the traffic. + */ +static int mwifiex_sdio_suspend(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + struct sdio_mmc_card *card; + struct mwifiex_adapter *adapter; + mmc_pm_flag_t pm_flag = 0; + int ret = 0; + + if (func) { + pm_flag = sdio_get_host_pm_caps(func); + pr_debug("cmd: %s: suspend: PM flag = 0x%x\n", + sdio_func_id(func), pm_flag); + if (!(pm_flag & MMC_PM_KEEP_POWER)) { + pr_err("%s: cannot remain alive while host is" + " suspended\n", sdio_func_id(func)); + return -ENOSYS; + } + + card = sdio_get_drvdata(func); + if (!card || !card->adapter) { + pr_err("suspend: invalid card or adapter\n"); + return 0; + } + } else { + pr_err("suspend: sdio_func is not specified\n"); + return 0; + } + + adapter = card->adapter; + + /* Enable the Host Sleep */ + if (!mwifiex_enable_hs(adapter)) { + dev_err(adapter->dev, "cmd: failed to suspend\n"); + return -EFAULT; + } + + dev_dbg(adapter->dev, "cmd: suspend with MMC_PM_KEEP_POWER\n"); + ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); + + /* Indicate device suspended */ + adapter->is_suspended = true; + + return ret; +} + +/* + * SDIO resume. + * + * Kernel needs to suspend all functions separately. Therefore all + * registered functions must have drivers with suspend and resume + * methods. Failing that the kernel simply removes the whole card. + * + * If already not resumed, this function turns on the traffic and + * sends a host sleep cancel request to the firmware. + */ +static int mwifiex_sdio_resume(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + struct sdio_mmc_card *card; + struct mwifiex_adapter *adapter; + mmc_pm_flag_t pm_flag = 0; + + if (func) { + pm_flag = sdio_get_host_pm_caps(func); + card = sdio_get_drvdata(func); + if (!card || !card->adapter) { + pr_err("resume: invalid card or adapter\n"); + return 0; + } + } else { + pr_err("resume: sdio_func is not specified\n"); + return 0; + } + + adapter = card->adapter; + + if (!adapter->is_suspended) { + dev_warn(adapter->dev, "device already resumed\n"); + return 0; + } + + adapter->is_suspended = false; + + /* Disable Host Sleep */ + mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA), + MWIFIEX_ASYNC_CMD); + + return 0; +} +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) */ + +/* Device ID for SD8786 */ +#define SDIO_DEVICE_ID_MARVELL_8786 (0x9116) +/* Device ID for SD8787 */ +#define SDIO_DEVICE_ID_MARVELL_8787 (0x9119) +/* Device ID for SD8797 */ +#define SDIO_DEVICE_ID_MARVELL_8797 (0x9129) + +/* WLAN IDs */ +static const struct sdio_device_id mwifiex_ids[] = { + {SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8786)}, + {SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8787)}, + {SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8797)}, + {}, +}; + +MODULE_DEVICE_TABLE(sdio, mwifiex_ids); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) +static const struct dev_pm_ops mwifiex_sdio_pm_ops = { + .suspend = mwifiex_sdio_suspend, + .resume = mwifiex_sdio_resume, +}; +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) */ + +static struct sdio_driver mwifiex_sdio = { + .name = "mwifiex_sdio", + .id_table = mwifiex_ids, + .probe = mwifiex_sdio_probe, + .remove = mwifiex_sdio_remove, + .drv = { + .owner = THIS_MODULE, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) + .pm = &mwifiex_sdio_pm_ops, +#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,34)) */ + } +}; + +/* + * This function writes data into SDIO card register. + */ +static int +mwifiex_write_reg(struct mwifiex_adapter *adapter, u32 reg, u32 data) +{ + struct sdio_mmc_card *card = adapter->card; + int ret = -1; + + sdio_claim_host(card->func); + sdio_writeb(card->func, (u8) data, reg, &ret); + sdio_release_host(card->func); + + return ret; +} + +/* + * This function reads data from SDIO card register. + */ +static int +mwifiex_read_reg(struct mwifiex_adapter *adapter, u32 reg, u32 *data) +{ + struct sdio_mmc_card *card = adapter->card; + int ret = -1; + u8 val; + + sdio_claim_host(card->func); + val = sdio_readb(card->func, reg, &ret); + sdio_release_host(card->func); + + *data = val; + + return ret; +} + +/* + * This function writes multiple data into SDIO card memory. + * + * This does not work in suspended mode. + */ +static int +mwifiex_write_data_sync(struct mwifiex_adapter *adapter, + u8 *buffer, u32 pkt_len, u32 port) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + u8 blk_mode = + (port & MWIFIEX_SDIO_BYTE_MODE_MASK) ? BYTE_MODE : BLOCK_MODE; + u32 blk_size = (blk_mode == BLOCK_MODE) ? MWIFIEX_SDIO_BLOCK_SIZE : 1; + u32 blk_cnt = + (blk_mode == + BLOCK_MODE) ? (pkt_len / + MWIFIEX_SDIO_BLOCK_SIZE) : pkt_len; + u32 ioport = (port & MWIFIEX_SDIO_IO_PORT_MASK); + + if (adapter->is_suspended) { + dev_err(adapter->dev, + "%s: not allowed while suspended\n", __func__); + return -1; + } + + sdio_claim_host(card->func); + + ret = sdio_writesb(card->func, ioport, buffer, blk_cnt * blk_size); + + sdio_release_host(card->func); + + return ret; +} + +/* + * This function reads multiple data from SDIO card memory. + */ +static int mwifiex_read_data_sync(struct mwifiex_adapter *adapter, u8 *buffer, + u32 len, u32 port, u8 claim) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + u8 blk_mode = (port & MWIFIEX_SDIO_BYTE_MODE_MASK) ? BYTE_MODE + : BLOCK_MODE; + u32 blk_size = (blk_mode == BLOCK_MODE) ? MWIFIEX_SDIO_BLOCK_SIZE : 1; + u32 blk_cnt = (blk_mode == BLOCK_MODE) ? (len / MWIFIEX_SDIO_BLOCK_SIZE) + : len; + u32 ioport = (port & MWIFIEX_SDIO_IO_PORT_MASK); + + if (claim) + sdio_claim_host(card->func); + + ret = sdio_readsb(card->func, buffer, ioport, blk_cnt * blk_size); + + if (claim) + sdio_release_host(card->func); + + return ret; +} + +/* + * This function wakes up the card. + * + * A host power up command is written to the card configuration + * register to wake up the card. + */ +static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) +{ + dev_dbg(adapter->dev, "event: wakeup device...\n"); + + return mwifiex_write_reg(adapter, CONFIGURATION_REG, HOST_POWER_UP); +} + +/* + * This function is called after the card has woken up. + * + * The card configuration register is reset. + */ +static int mwifiex_pm_wakeup_card_complete(struct mwifiex_adapter *adapter) +{ + dev_dbg(adapter->dev, "cmd: wakeup device completed\n"); + + return mwifiex_write_reg(adapter, CONFIGURATION_REG, 0); +} + +/* + * This function initializes the IO ports. + * + * The following operations are performed - + * - Read the IO ports (0, 1 and 2) + * - Set host interrupt Reset-To-Read to clear + * - Set auto re-enable interrupt + */ +static int mwifiex_init_sdio_ioport(struct mwifiex_adapter *adapter) +{ + u32 reg; + + adapter->ioport = 0; + + /* Read the IO port */ + if (!mwifiex_read_reg(adapter, IO_PORT_0_REG, ®)) + adapter->ioport |= (reg & 0xff); + else + return -1; + + if (!mwifiex_read_reg(adapter, IO_PORT_1_REG, ®)) + adapter->ioport |= ((reg & 0xff) << 8); + else + return -1; + + if (!mwifiex_read_reg(adapter, IO_PORT_2_REG, ®)) + adapter->ioport |= ((reg & 0xff) << 16); + else + return -1; + + pr_debug("info: SDIO FUNC1 IO port: %#x\n", adapter->ioport); + + /* Set Host interrupt reset to read to clear */ + if (!mwifiex_read_reg(adapter, HOST_INT_RSR_REG, ®)) + mwifiex_write_reg(adapter, HOST_INT_RSR_REG, + reg | SDIO_INT_MASK); + else + return -1; + + /* Dnld/Upld ready set to auto reset */ + if (!mwifiex_read_reg(adapter, CARD_MISC_CFG_REG, ®)) + mwifiex_write_reg(adapter, CARD_MISC_CFG_REG, + reg | AUTO_RE_ENABLE_INT); + else + return -1; + + return 0; +} + +/* + * This function sends data to the card. + */ +static int mwifiex_write_data_to_card(struct mwifiex_adapter *adapter, + u8 *payload, u32 pkt_len, u32 port) +{ + u32 i = 0; + int ret; + + do { + ret = mwifiex_write_data_sync(adapter, payload, pkt_len, port); + if (ret) { + i++; + dev_err(adapter->dev, "host_to_card, write iomem" + " (%d) failed: %d\n", i, ret); + if (mwifiex_write_reg(adapter, CONFIGURATION_REG, 0x04)) + dev_err(adapter->dev, "write CFG reg failed\n"); + + ret = -1; + if (i > MAX_WRITE_IOMEM_RETRY) + return ret; + } + } while (ret == -1); + + return ret; +} + +/* + * This function gets the read port. + * + * If control port bit is set in MP read bitmap, the control port + * is returned, otherwise the current read port is returned and + * the value is increased (provided it does not reach the maximum + * limit, in which case it is reset to 1) + */ +static int mwifiex_get_rd_port(struct mwifiex_adapter *adapter, u8 *port) +{ + struct sdio_mmc_card *card = adapter->card; + u16 rd_bitmap = card->mp_rd_bitmap; + + dev_dbg(adapter->dev, "data: mp_rd_bitmap=0x%04x\n", rd_bitmap); + + if (!(rd_bitmap & (CTRL_PORT_MASK | DATA_PORT_MASK))) + return -1; + + if (card->mp_rd_bitmap & CTRL_PORT_MASK) { + card->mp_rd_bitmap &= (u16) (~CTRL_PORT_MASK); + *port = CTRL_PORT; + dev_dbg(adapter->dev, "data: port=%d mp_rd_bitmap=0x%04x\n", + *port, card->mp_rd_bitmap); + } else { + if (card->mp_rd_bitmap & (1 << card->curr_rd_port)) { + card->mp_rd_bitmap &= (u16) + (~(1 << card->curr_rd_port)); + *port = card->curr_rd_port; + + if (++card->curr_rd_port == MAX_PORT) + card->curr_rd_port = 1; + } else { + return -1; + } + + dev_dbg(adapter->dev, + "data: port=%d mp_rd_bitmap=0x%04x -> 0x%04x\n", + *port, rd_bitmap, card->mp_rd_bitmap); + } + return 0; +} + +/* + * This function gets the write port for data. + * + * The current write port is returned if available and the value is + * increased (provided it does not reach the maximum limit, in which + * case it is reset to 1) + */ +static int mwifiex_get_wr_port_data(struct mwifiex_adapter *adapter, u8 *port) +{ + struct sdio_mmc_card *card = adapter->card; + u16 wr_bitmap = card->mp_wr_bitmap; + + dev_dbg(adapter->dev, "data: mp_wr_bitmap=0x%04x\n", wr_bitmap); + + if (!(wr_bitmap & card->mp_data_port_mask)) + return -1; + + if (card->mp_wr_bitmap & (1 << card->curr_wr_port)) { + card->mp_wr_bitmap &= (u16) (~(1 << card->curr_wr_port)); + *port = card->curr_wr_port; + if (++card->curr_wr_port == card->mp_end_port) + card->curr_wr_port = 1; + } else { + adapter->data_sent = true; + return -EBUSY; + } + + if (*port == CTRL_PORT) { + dev_err(adapter->dev, "invalid data port=%d cur port=%d" + " mp_wr_bitmap=0x%04x -> 0x%04x\n", + *port, card->curr_wr_port, wr_bitmap, + card->mp_wr_bitmap); + return -1; + } + + dev_dbg(adapter->dev, "data: port=%d mp_wr_bitmap=0x%04x -> 0x%04x\n", + *port, wr_bitmap, card->mp_wr_bitmap); + + return 0; +} + +/* + * This function polls the card status. + */ +static int +mwifiex_sdio_poll_card_status(struct mwifiex_adapter *adapter, u8 bits) +{ + u32 tries; + u32 cs; + + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + if (mwifiex_read_reg(adapter, CARD_STATUS_REG, &cs)) + break; + else if ((cs & bits) == bits) + return 0; + + usleep_range(10, 20); + } + + dev_err(adapter->dev, "poll card status failed, tries = %d\n", tries); + + return -1; +} + +/* + * This function reads the firmware status. + */ +static int +mwifiex_sdio_read_fw_status(struct mwifiex_adapter *adapter, u16 *dat) +{ + u32 fws0, fws1; + + if (mwifiex_read_reg(adapter, CARD_FW_STATUS0_REG, &fws0)) + return -1; + + if (mwifiex_read_reg(adapter, CARD_FW_STATUS1_REG, &fws1)) + return -1; + + *dat = (u16) ((fws1 << 8) | fws0); + + return 0; +} + +/* + * This function disables the host interrupt. + * + * The host interrupt mask is read, the disable bit is reset and + * written back to the card host interrupt mask register. + */ +static int mwifiex_sdio_disable_host_int(struct mwifiex_adapter *adapter) +{ + u32 host_int_mask; + + /* Read back the host_int_mask register */ + if (mwifiex_read_reg(adapter, HOST_INT_MASK_REG, &host_int_mask)) + return -1; + + /* Update with the mask and write back to the register */ + host_int_mask &= ~HOST_INT_DISABLE; + + if (mwifiex_write_reg(adapter, HOST_INT_MASK_REG, host_int_mask)) { + dev_err(adapter->dev, "disable host interrupt failed\n"); + return -1; + } + + return 0; +} + +/* + * This function enables the host interrupt. + * + * The host interrupt enable mask is written to the card + * host interrupt mask register. + */ +static int mwifiex_sdio_enable_host_int(struct mwifiex_adapter *adapter) +{ + /* Simply write the mask to the register */ + if (mwifiex_write_reg(adapter, HOST_INT_MASK_REG, HOST_INT_ENABLE)) { + dev_err(adapter->dev, "enable host interrupt failed\n"); + return -1; + } + return 0; +} + +/* + * This function sends a data buffer to the card. + */ +static int mwifiex_sdio_card_to_host(struct mwifiex_adapter *adapter, + u32 *type, u8 *buffer, + u32 npayload, u32 ioport) +{ + int ret; + u32 nb; + + if (!buffer) { + dev_err(adapter->dev, "%s: buffer is NULL\n", __func__); + return -1; + } + + ret = mwifiex_read_data_sync(adapter, buffer, npayload, ioport, 1); + + if (ret) { + dev_err(adapter->dev, "%s: read iomem failed: %d\n", __func__, + ret); + return -1; + } + + nb = le16_to_cpu(*(__le16 *) (buffer)); + if (nb > npayload) { + dev_err(adapter->dev, "%s: invalid packet, nb=%d npayload=%d\n", + __func__, nb, npayload); + return -1; + } + + *type = le16_to_cpu(*(__le16 *) (buffer + 2)); + + return ret; +} + +/* + * This function downloads the firmware to the card. + * + * Firmware is downloaded to the card in blocks. Every block download + * is tested for CRC errors, and retried a number of times before + * returning failure. + */ +static int mwifiex_prog_fw_w_helper(struct mwifiex_adapter *adapter, + struct mwifiex_fw_image *fw) +{ + int ret; + u8 *firmware = fw->fw_buf; + u32 firmware_len = fw->fw_len; + u32 offset = 0; + u32 base0, base1; + u8 *fwbuf; + u16 len = 0; + u32 txlen, tx_blocks = 0, tries; + u32 i = 0; + + if (!firmware_len) { + dev_err(adapter->dev, + "firmware image not found! Terminating download\n"); + return -1; + } + + dev_dbg(adapter->dev, "info: downloading FW image (%d bytes)\n", + firmware_len); + + /* Assume that the allocated buffer is 8-byte aligned */ + fwbuf = kzalloc(MWIFIEX_UPLD_SIZE, GFP_KERNEL); + if (!fwbuf) + return -ENOMEM; + + /* Perform firmware data transfer */ + do { + /* The host polls for the DN_LD_CARD_RDY and CARD_IO_READY + bits */ + ret = mwifiex_sdio_poll_card_status(adapter, CARD_IO_READY | + DN_LD_CARD_RDY); + if (ret) { + dev_err(adapter->dev, "FW download with helper:" + " poll status timeout @ %d\n", offset); + goto done; + } + + /* More data? */ + if (offset >= firmware_len) + break; + + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + ret = mwifiex_read_reg(adapter, HOST_F1_RD_BASE_0, + &base0); + if (ret) { + dev_err(adapter->dev, + "dev BASE0 register read failed: " + "base0=%#04X(%d). Terminating dnld\n", + base0, base0); + goto done; + } + ret = mwifiex_read_reg(adapter, HOST_F1_RD_BASE_1, + &base1); + if (ret) { + dev_err(adapter->dev, + "dev BASE1 register read failed: " + "base1=%#04X(%d). Terminating dnld\n", + base1, base1); + goto done; + } + len = (u16) (((base1 & 0xff) << 8) | (base0 & 0xff)); + + if (len) + break; + + usleep_range(10, 20); + } + + if (!len) { + break; + } else if (len > MWIFIEX_UPLD_SIZE) { + dev_err(adapter->dev, + "FW dnld failed @ %d, invalid length %d\n", + offset, len); + ret = -1; + goto done; + } + + txlen = len; + + if (len & BIT(0)) { + i++; + if (i > MAX_WRITE_IOMEM_RETRY) { + dev_err(adapter->dev, + "FW dnld failed @ %d, over max retry\n", + offset); + ret = -1; + goto done; + } + dev_err(adapter->dev, "CRC indicated by the helper:" + " len = 0x%04X, txlen = %d\n", len, txlen); + len &= ~BIT(0); + /* Setting this to 0 to resend from same offset */ + txlen = 0; + } else { + i = 0; + + /* Set blocksize to transfer - checking for last + block */ + if (firmware_len - offset < txlen) + txlen = firmware_len - offset; + + tx_blocks = (txlen + MWIFIEX_SDIO_BLOCK_SIZE - 1) + / MWIFIEX_SDIO_BLOCK_SIZE; + + /* Copy payload to buffer */ + memmove(fwbuf, &firmware[offset], txlen); + } + + ret = mwifiex_write_data_sync(adapter, fwbuf, tx_blocks * + MWIFIEX_SDIO_BLOCK_SIZE, + adapter->ioport); + if (ret) { + dev_err(adapter->dev, + "FW download, write iomem (%d) failed @ %d\n", + i, offset); + if (mwifiex_write_reg(adapter, CONFIGURATION_REG, 0x04)) + dev_err(adapter->dev, "write CFG reg failed\n"); + + ret = -1; + goto done; + } + + offset += txlen; + } while (true); + + dev_dbg(adapter->dev, "info: FW download over, size %d bytes\n", + offset); + + ret = 0; +done: + kfree(fwbuf); + return ret; +} + +/* + * This function checks the firmware status in card. + * + * The winner interface is also determined by this function. + */ +static int mwifiex_check_fw_status(struct mwifiex_adapter *adapter, + u32 poll_num) +{ + int ret = 0; + u16 firmware_stat; + u32 tries; + u32 winner_status; + + /* Wait for firmware initialization event */ + for (tries = 0; tries < poll_num; tries++) { + ret = mwifiex_sdio_read_fw_status(adapter, &firmware_stat); + if (ret) + continue; + if (firmware_stat == FIRMWARE_READY_SDIO) { + ret = 0; + break; + } else { + mdelay(100); + ret = -1; + } + } + + if (ret) { + if (mwifiex_read_reg + (adapter, CARD_FW_STATUS0_REG, &winner_status)) + winner_status = 0; + + if (winner_status) + adapter->winner = 0; + else + adapter->winner = 1; + } + return ret; +} + +/* + * This function reads the interrupt status from card. + */ +static void mwifiex_interrupt_status(struct mwifiex_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + u32 sdio_ireg; + unsigned long flags; + + if (mwifiex_read_data_sync(adapter, card->mp_regs, MAX_MP_REGS, + REG_PORT | MWIFIEX_SDIO_BYTE_MODE_MASK, + 0)) { + dev_err(adapter->dev, "read mp_regs failed\n"); + return; + } + + sdio_ireg = card->mp_regs[HOST_INTSTATUS_REG]; + if (sdio_ireg) { + /* + * DN_LD_HOST_INT_STATUS and/or UP_LD_HOST_INT_STATUS + * Clear the interrupt status register + */ + dev_dbg(adapter->dev, "int: sdio_ireg = %#x\n", sdio_ireg); + spin_lock_irqsave(&adapter->int_lock, flags); + adapter->int_status |= sdio_ireg; + spin_unlock_irqrestore(&adapter->int_lock, flags); + } +} + +/* + * SDIO interrupt handler. + * + * This function reads the interrupt status from firmware and handles + * the interrupt in current thread (ksdioirqd) right away. + */ +static void +mwifiex_sdio_interrupt(struct sdio_func *func) +{ + struct mwifiex_adapter *adapter; + struct sdio_mmc_card *card; + + card = sdio_get_drvdata(func); + if (!card || !card->adapter) { + pr_debug("int: func=%p card=%p adapter=%p\n", + func, card, card ? card->adapter : NULL); + return; + } + adapter = card->adapter; + + if (adapter->surprise_removed) + return; + + if (!adapter->pps_uapsd_mode && adapter->ps_state == PS_STATE_SLEEP) + adapter->ps_state = PS_STATE_AWAKE; + + mwifiex_interrupt_status(adapter); + mwifiex_main_process(adapter); +} + +/* + * This function decodes a received packet. + * + * Based on the type, the packet is treated as either a data, or + * a command response, or an event, and the correct handler + * function is invoked. + */ +static int mwifiex_decode_rx_packet(struct mwifiex_adapter *adapter, + struct sk_buff *skb, u32 upld_typ) +{ + u8 *cmd_buf; + + skb_pull(skb, INTF_HEADER_LEN); + + switch (upld_typ) { + case MWIFIEX_TYPE_DATA: + dev_dbg(adapter->dev, "info: --- Rx: Data packet ---\n"); + mwifiex_handle_rx_packet(adapter, skb); + break; + + case MWIFIEX_TYPE_CMD: + dev_dbg(adapter->dev, "info: --- Rx: Cmd Response ---\n"); + /* take care of curr_cmd = NULL case */ + if (!adapter->curr_cmd) { + cmd_buf = adapter->upld_buf; + + if (adapter->ps_state == PS_STATE_SLEEP_CFM) + mwifiex_process_sleep_confirm_resp(adapter, + skb->data, + skb->len); + + memcpy(cmd_buf, skb->data, + min_t(u32, MWIFIEX_SIZE_OF_CMD_BUFFER, + skb->len)); + + dev_kfree_skb_any(skb); + } else { + adapter->cmd_resp_received = true; + adapter->curr_cmd->resp_skb = skb; + } + break; + + case MWIFIEX_TYPE_EVENT: + dev_dbg(adapter->dev, "info: --- Rx: Event ---\n"); + adapter->event_cause = *(u32 *) skb->data; + + if ((skb->len > 0) && (skb->len < MAX_EVENT_SIZE)) + memcpy(adapter->event_body, + skb->data + MWIFIEX_EVENT_HEADER_LEN, + skb->len); + + /* event cause has been saved to adapter->event_cause */ + adapter->event_received = true; + adapter->event_skb = skb; + + break; + + default: + dev_err(adapter->dev, "unknown upload type %#x\n", upld_typ); + dev_kfree_skb_any(skb); + break; + } + + return 0; +} + +/* + * This function transfers received packets from card to driver, performing + * aggregation if required. + * + * For data received on control port, or if aggregation is disabled, the + * received buffers are uploaded as separate packets. However, if aggregation + * is enabled and required, the buffers are copied onto an aggregation buffer, + * provided there is space left, processed and finally uploaded. + */ +static int mwifiex_sdio_card_to_host_mp_aggr(struct mwifiex_adapter *adapter, + struct sk_buff *skb, u8 port) +{ + struct sdio_mmc_card *card = adapter->card; + s32 f_do_rx_aggr = 0; + s32 f_do_rx_cur = 0; + s32 f_aggr_cur = 0; + struct sk_buff *skb_deaggr; + u32 pind; + u32 pkt_len, pkt_type = 0; + u8 *curr_ptr; + u32 rx_len = skb->len; + + if (port == CTRL_PORT) { + /* Read the command Resp without aggr */ + dev_dbg(adapter->dev, "info: %s: no aggregation for cmd " + "response\n", __func__); + + f_do_rx_cur = 1; + goto rx_curr_single; + } + + if (!card->mpa_rx.enabled) { + dev_dbg(adapter->dev, "info: %s: rx aggregation disabled\n", + __func__); + + f_do_rx_cur = 1; + goto rx_curr_single; + } + + if (card->mp_rd_bitmap & (~((u16) CTRL_PORT_MASK))) { + /* Some more data RX pending */ + dev_dbg(adapter->dev, "info: %s: not last packet\n", __func__); + + if (MP_RX_AGGR_IN_PROGRESS(card)) { + if (MP_RX_AGGR_BUF_HAS_ROOM(card, skb->len)) { + f_aggr_cur = 1; + } else { + /* No room in Aggr buf, do rx aggr now */ + f_do_rx_aggr = 1; + f_do_rx_cur = 1; + } + } else { + /* Rx aggr not in progress */ + f_aggr_cur = 1; + } + + } else { + /* No more data RX pending */ + dev_dbg(adapter->dev, "info: %s: last packet\n", __func__); + + if (MP_RX_AGGR_IN_PROGRESS(card)) { + f_do_rx_aggr = 1; + if (MP_RX_AGGR_BUF_HAS_ROOM(card, skb->len)) + f_aggr_cur = 1; + else + /* No room in Aggr buf, do rx aggr now */ + f_do_rx_cur = 1; + } else { + f_do_rx_cur = 1; + } + } + + if (f_aggr_cur) { + dev_dbg(adapter->dev, "info: current packet aggregation\n"); + /* Curr pkt can be aggregated */ + MP_RX_AGGR_SETUP(card, skb, port); + + if (MP_RX_AGGR_PKT_LIMIT_REACHED(card) || + MP_RX_AGGR_PORT_LIMIT_REACHED(card)) { + dev_dbg(adapter->dev, "info: %s: aggregated packet " + "limit reached\n", __func__); + /* No more pkts allowed in Aggr buf, rx it */ + f_do_rx_aggr = 1; + } + } + + if (f_do_rx_aggr) { + /* do aggr RX now */ + dev_dbg(adapter->dev, "info: do_rx_aggr: num of packets: %d\n", + card->mpa_rx.pkt_cnt); + + if (mwifiex_read_data_sync(adapter, card->mpa_rx.buf, + card->mpa_rx.buf_len, + (adapter->ioport | 0x1000 | + (card->mpa_rx.ports << 4)) + + card->mpa_rx.start_port, 1)) + goto error; + + curr_ptr = card->mpa_rx.buf; + + for (pind = 0; pind < card->mpa_rx.pkt_cnt; pind++) { + + /* get curr PKT len & type */ + pkt_len = *(u16 *) &curr_ptr[0]; + pkt_type = *(u16 *) &curr_ptr[2]; + + /* copy pkt to deaggr buf */ + skb_deaggr = card->mpa_rx.skb_arr[pind]; + + if ((pkt_type == MWIFIEX_TYPE_DATA) && (pkt_len <= + card->mpa_rx.len_arr[pind])) { + + memcpy(skb_deaggr->data, curr_ptr, pkt_len); + + skb_trim(skb_deaggr, pkt_len); + + /* Process de-aggr packet */ + mwifiex_decode_rx_packet(adapter, skb_deaggr, + pkt_type); + } else { + dev_err(adapter->dev, "wrong aggr pkt:" + " type=%d len=%d max_len=%d\n", + pkt_type, pkt_len, + card->mpa_rx.len_arr[pind]); + dev_kfree_skb_any(skb_deaggr); + } + curr_ptr += card->mpa_rx.len_arr[pind]; + } + MP_RX_AGGR_BUF_RESET(card); + } + +rx_curr_single: + if (f_do_rx_cur) { + dev_dbg(adapter->dev, "info: RX: port: %d, rx_len: %d\n", + port, rx_len); + + if (mwifiex_sdio_card_to_host(adapter, &pkt_type, + skb->data, skb->len, + adapter->ioport + port)) + goto error; + + mwifiex_decode_rx_packet(adapter, skb, pkt_type); + } + + return 0; + +error: + if (MP_RX_AGGR_IN_PROGRESS(card)) { + /* Multiport-aggregation transfer failed - cleanup */ + for (pind = 0; pind < card->mpa_rx.pkt_cnt; pind++) { + /* copy pkt to deaggr buf */ + skb_deaggr = card->mpa_rx.skb_arr[pind]; + dev_kfree_skb_any(skb_deaggr); + } + MP_RX_AGGR_BUF_RESET(card); + } + + if (f_do_rx_cur) + /* Single transfer pending. Free curr buff also */ + dev_kfree_skb_any(skb); + + return -1; +} + +/* + * This function checks the current interrupt status. + * + * The following interrupts are checked and handled by this function - + * - Data sent + * - Command sent + * - Packets received + * + * Since the firmware does not generate download ready interrupt if the + * port updated is command port only, command sent interrupt checking + * should be done manually, and for every SDIO interrupt. + * + * In case of Rx packets received, the packets are uploaded from card to + * host and processed accordingly. + */ +static int mwifiex_process_int_status(struct mwifiex_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + int ret = 0; + u8 sdio_ireg; + struct sk_buff *skb; + u8 port = CTRL_PORT; + u32 len_reg_l, len_reg_u; + u32 rx_blocks; + u16 rx_len; + unsigned long flags; + + spin_lock_irqsave(&adapter->int_lock, flags); + sdio_ireg = adapter->int_status; + adapter->int_status = 0; + spin_unlock_irqrestore(&adapter->int_lock, flags); + + if (!sdio_ireg) + return ret; + + if (sdio_ireg & DN_LD_HOST_INT_STATUS) { + card->mp_wr_bitmap = ((u16) card->mp_regs[WR_BITMAP_U]) << 8; + card->mp_wr_bitmap |= (u16) card->mp_regs[WR_BITMAP_L]; + dev_dbg(adapter->dev, "int: DNLD: wr_bitmap=0x%04x\n", + card->mp_wr_bitmap); + if (adapter->data_sent && + (card->mp_wr_bitmap & card->mp_data_port_mask)) { + dev_dbg(adapter->dev, + "info: <--- Tx DONE Interrupt --->\n"); + adapter->data_sent = false; + } + } + + /* As firmware will not generate download ready interrupt if the port + updated is command port only, cmd_sent should be done for any SDIO + interrupt. */ + if (adapter->cmd_sent) { + /* Check if firmware has attach buffer at command port and + update just that in wr_bit_map. */ + card->mp_wr_bitmap |= + (u16) card->mp_regs[WR_BITMAP_L] & CTRL_PORT_MASK; + if (card->mp_wr_bitmap & CTRL_PORT_MASK) + adapter->cmd_sent = false; + } + + dev_dbg(adapter->dev, "info: cmd_sent=%d data_sent=%d\n", + adapter->cmd_sent, adapter->data_sent); + if (sdio_ireg & UP_LD_HOST_INT_STATUS) { + card->mp_rd_bitmap = ((u16) card->mp_regs[RD_BITMAP_U]) << 8; + card->mp_rd_bitmap |= (u16) card->mp_regs[RD_BITMAP_L]; + dev_dbg(adapter->dev, "int: UPLD: rd_bitmap=0x%04x\n", + card->mp_rd_bitmap); + + while (true) { + ret = mwifiex_get_rd_port(adapter, &port); + if (ret) { + dev_dbg(adapter->dev, + "info: no more rd_port available\n"); + break; + } + len_reg_l = RD_LEN_P0_L + (port << 1); + len_reg_u = RD_LEN_P0_U + (port << 1); + rx_len = ((u16) card->mp_regs[len_reg_u]) << 8; + rx_len |= (u16) card->mp_regs[len_reg_l]; + dev_dbg(adapter->dev, "info: RX: port=%d rx_len=%u\n", + port, rx_len); + rx_blocks = + (rx_len + MWIFIEX_SDIO_BLOCK_SIZE - + 1) / MWIFIEX_SDIO_BLOCK_SIZE; + if (rx_len <= INTF_HEADER_LEN || + (rx_blocks * MWIFIEX_SDIO_BLOCK_SIZE) > + MWIFIEX_RX_DATA_BUF_SIZE) { + dev_err(adapter->dev, "invalid rx_len=%d\n", + rx_len); + return -1; + } + rx_len = (u16) (rx_blocks * MWIFIEX_SDIO_BLOCK_SIZE); + + skb = dev_alloc_skb(rx_len); + + if (!skb) { + dev_err(adapter->dev, "%s: failed to alloc skb", + __func__); + return -1; + } + + skb_put(skb, rx_len); + + dev_dbg(adapter->dev, "info: rx_len = %d skb->len = %d\n", + rx_len, skb->len); + + if (mwifiex_sdio_card_to_host_mp_aggr(adapter, skb, + port)) { + u32 cr = 0; + + dev_err(adapter->dev, "card_to_host_mpa failed:" + " int status=%#x\n", sdio_ireg); + if (mwifiex_read_reg(adapter, + CONFIGURATION_REG, &cr)) + dev_err(adapter->dev, + "read CFG reg failed\n"); + + dev_dbg(adapter->dev, + "info: CFG reg val = %d\n", cr); + if (mwifiex_write_reg(adapter, + CONFIGURATION_REG, + (cr | 0x04))) + dev_err(adapter->dev, + "write CFG reg failed\n"); + + dev_dbg(adapter->dev, "info: write success\n"); + if (mwifiex_read_reg(adapter, + CONFIGURATION_REG, &cr)) + dev_err(adapter->dev, + "read CFG reg failed\n"); + + dev_dbg(adapter->dev, + "info: CFG reg val =%x\n", cr); + return -1; + } + } + } + + return 0; +} + +/* + * This function aggregates transmission buffers in driver and downloads + * the aggregated packet to card. + * + * The individual packets are aggregated by copying into an aggregation + * buffer and then downloaded to the card. Previous unsent packets in the + * aggregation buffer are pre-copied first before new packets are added. + * Aggregation is done till there is space left in the aggregation buffer, + * or till new packets are available. + * + * The function will only download the packet to the card when aggregation + * stops, otherwise it will just aggregate the packet in aggregation buffer + * and return. + */ +static int mwifiex_host_to_card_mp_aggr(struct mwifiex_adapter *adapter, + u8 *payload, u32 pkt_len, u8 port, + u32 next_pkt_len) +{ + struct sdio_mmc_card *card = adapter->card; + int ret = 0; + s32 f_send_aggr_buf = 0; + s32 f_send_cur_buf = 0; + s32 f_precopy_cur_buf = 0; + s32 f_postcopy_cur_buf = 0; + + if ((!card->mpa_tx.enabled) || (port == CTRL_PORT)) { + dev_dbg(adapter->dev, "info: %s: tx aggregation disabled\n", + __func__); + + f_send_cur_buf = 1; + goto tx_curr_single; + } + + if (next_pkt_len) { + /* More pkt in TX queue */ + dev_dbg(adapter->dev, "info: %s: more packets in queue.\n", + __func__); + + if (MP_TX_AGGR_IN_PROGRESS(card)) { + if (!MP_TX_AGGR_PORT_LIMIT_REACHED(card) && + MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len)) { + f_precopy_cur_buf = 1; + + if (!(card->mp_wr_bitmap & + (1 << card->curr_wr_port)) || + !MP_TX_AGGR_BUF_HAS_ROOM( + card, pkt_len + next_pkt_len)) + f_send_aggr_buf = 1; + } else { + /* No room in Aggr buf, send it */ + f_send_aggr_buf = 1; + + if (MP_TX_AGGR_PORT_LIMIT_REACHED(card) || + !(card->mp_wr_bitmap & + (1 << card->curr_wr_port))) + f_send_cur_buf = 1; + else + f_postcopy_cur_buf = 1; + } + } else { + if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len) && + (card->mp_wr_bitmap & (1 << card->curr_wr_port))) + f_precopy_cur_buf = 1; + else + f_send_cur_buf = 1; + } + } else { + /* Last pkt in TX queue */ + dev_dbg(adapter->dev, "info: %s: Last packet in Tx Queue.\n", + __func__); + + if (MP_TX_AGGR_IN_PROGRESS(card)) { + /* some packs in Aggr buf already */ + f_send_aggr_buf = 1; + + if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len)) + f_precopy_cur_buf = 1; + else + /* No room in Aggr buf, send it */ + f_send_cur_buf = 1; + } else { + f_send_cur_buf = 1; + } + } + + if (f_precopy_cur_buf) { + dev_dbg(adapter->dev, "data: %s: precopy current buffer\n", + __func__); + MP_TX_AGGR_BUF_PUT(card, payload, pkt_len, port); + + if (MP_TX_AGGR_PKT_LIMIT_REACHED(card) || + MP_TX_AGGR_PORT_LIMIT_REACHED(card)) + /* No more pkts allowed in Aggr buf, send it */ + f_send_aggr_buf = 1; + } + + if (f_send_aggr_buf) { + dev_dbg(adapter->dev, "data: %s: send aggr buffer: %d %d\n", + __func__, + card->mpa_tx.start_port, card->mpa_tx.ports); + ret = mwifiex_write_data_to_card(adapter, card->mpa_tx.buf, + card->mpa_tx.buf_len, + (adapter->ioport | 0x1000 | + (card->mpa_tx.ports << 4)) + + card->mpa_tx.start_port); + + MP_TX_AGGR_BUF_RESET(card); + } + +tx_curr_single: + if (f_send_cur_buf) { + dev_dbg(adapter->dev, "data: %s: send current buffer %d\n", + __func__, port); + ret = mwifiex_write_data_to_card(adapter, payload, pkt_len, + adapter->ioport + port); + } + + if (f_postcopy_cur_buf) { + dev_dbg(adapter->dev, "data: %s: postcopy current buffer\n", + __func__); + MP_TX_AGGR_BUF_PUT(card, payload, pkt_len, port); + } + + return ret; +} + +/* + * This function downloads data from driver to card. + * + * Both commands and data packets are transferred to the card by this + * function. + * + * This function adds the SDIO specific header to the front of the buffer + * before transferring. The header contains the length of the packet and + * the type. The firmware handles the packets based upon this set type. + */ +static int mwifiex_sdio_host_to_card(struct mwifiex_adapter *adapter, + u8 type, struct sk_buff *skb, + struct mwifiex_tx_param *tx_param) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + u32 buf_block_len; + u32 blk_size; + u8 port = CTRL_PORT; + u8 *payload = (u8 *)skb->data; + u32 pkt_len = skb->len; + + /* Allocate buffer and copy payload */ + blk_size = MWIFIEX_SDIO_BLOCK_SIZE; + buf_block_len = (pkt_len + blk_size - 1) / blk_size; + *(__le16 *)&payload[0] = cpu_to_le16((u16)pkt_len); + *(__le16 *)&payload[2] = cpu_to_le16(type); + + /* + * This is SDIO specific header + * u16 length, + * u16 type (MWIFIEX_TYPE_DATA = 0, MWIFIEX_TYPE_CMD = 1, + * MWIFIEX_TYPE_EVENT = 3) + */ + if (type == MWIFIEX_TYPE_DATA) { + ret = mwifiex_get_wr_port_data(adapter, &port); + if (ret) { + dev_err(adapter->dev, "%s: no wr_port available\n", + __func__); + return ret; + } + } else { + adapter->cmd_sent = true; + /* Type must be MWIFIEX_TYPE_CMD */ + + if (pkt_len <= INTF_HEADER_LEN || + pkt_len > MWIFIEX_UPLD_SIZE) + dev_err(adapter->dev, "%s: payload=%p, nb=%d\n", + __func__, payload, pkt_len); + } + + /* Transfer data to card */ + pkt_len = buf_block_len * blk_size; + + if (tx_param) + ret = mwifiex_host_to_card_mp_aggr(adapter, payload, pkt_len, + port, tx_param->next_pkt_len + ); + else + ret = mwifiex_host_to_card_mp_aggr(adapter, payload, pkt_len, + port, 0); + + if (ret) { + if (type == MWIFIEX_TYPE_CMD) + adapter->cmd_sent = false; + if (type == MWIFIEX_TYPE_DATA) + adapter->data_sent = false; + } else { + if (type == MWIFIEX_TYPE_DATA) { + if (!(card->mp_wr_bitmap & (1 << card->curr_wr_port))) + adapter->data_sent = true; + else + adapter->data_sent = false; + } + } + + return ret; +} + +/* + * This function allocates the MPA Tx and Rx buffers. + */ +static int mwifiex_alloc_sdio_mpa_buffers(struct mwifiex_adapter *adapter, + u32 mpa_tx_buf_size, u32 mpa_rx_buf_size) +{ + struct sdio_mmc_card *card = adapter->card; + int ret = 0; + + card->mpa_tx.buf = kzalloc(mpa_tx_buf_size, GFP_KERNEL); + if (!card->mpa_tx.buf) { + ret = -1; + goto error; + } + + card->mpa_tx.buf_size = mpa_tx_buf_size; + + card->mpa_rx.buf = kzalloc(mpa_rx_buf_size, GFP_KERNEL); + if (!card->mpa_rx.buf) { + ret = -1; + goto error; + } + + card->mpa_rx.buf_size = mpa_rx_buf_size; + +error: + if (ret) { + kfree(card->mpa_tx.buf); + kfree(card->mpa_rx.buf); + } + + return ret; +} + +/* + * This function unregisters the SDIO device. + * + * The SDIO IRQ is released, the function is disabled and driver + * data is set to null. + */ +static void +mwifiex_unregister_dev(struct mwifiex_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + if (adapter->card) { + /* Release the SDIO IRQ */ + sdio_claim_host(card->func); + sdio_release_irq(card->func); + sdio_disable_func(card->func); + sdio_release_host(card->func); + sdio_set_drvdata(card->func, NULL); + } +} + +/* + * This function registers the SDIO device. + * + * SDIO IRQ is claimed, block size is set and driver data is initialized. + */ +static int mwifiex_register_dev(struct mwifiex_adapter *adapter) +{ + int ret = 0; + struct sdio_mmc_card *card = adapter->card; + struct sdio_func *func = card->func; + + /* save adapter pointer in card */ + card->adapter = adapter; + + sdio_claim_host(func); + + /* Request the SDIO IRQ */ + ret = sdio_claim_irq(func, mwifiex_sdio_interrupt); + if (ret) { + pr_err("claim irq failed: ret=%d\n", ret); + goto disable_func; + } + + /* Set block size */ + ret = sdio_set_block_size(card->func, MWIFIEX_SDIO_BLOCK_SIZE); + if (ret) { + pr_err("cannot set SDIO block size\n"); + ret = -1; + goto release_irq; + } + + sdio_release_host(func); + sdio_set_drvdata(func, card); + + adapter->dev = &func->dev; + + switch (func->device) { + case SDIO_DEVICE_ID_MARVELL_8786: + strcpy(adapter->fw_name, SD8786_DEFAULT_FW_NAME); + break; + case SDIO_DEVICE_ID_MARVELL_8797: + strcpy(adapter->fw_name, SD8797_DEFAULT_FW_NAME); + break; + case SDIO_DEVICE_ID_MARVELL_8787: + default: + strcpy(adapter->fw_name, SD8787_DEFAULT_FW_NAME); + break; + } + + return 0; + +release_irq: + sdio_release_irq(func); +disable_func: + sdio_disable_func(func); + sdio_release_host(func); + adapter->card = NULL; + + return -1; +} + +/* + * This function initializes the SDIO driver. + * + * The following initializations steps are followed - + * - Read the Host interrupt status register to acknowledge + * the first interrupt got from bootloader + * - Disable host interrupt mask register + * - Get SDIO port + * - Initialize SDIO variables in card + * - Allocate MP registers + * - Allocate MPA Tx and Rx buffers + */ +static int mwifiex_init_sdio(struct mwifiex_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + int ret; + u32 sdio_ireg; + + /* + * Read the HOST_INT_STATUS_REG for ACK the first interrupt got + * from the bootloader. If we don't do this we get a interrupt + * as soon as we register the irq. + */ + mwifiex_read_reg(adapter, HOST_INTSTATUS_REG, &sdio_ireg); + + /* Disable host interrupt mask register for SDIO */ + mwifiex_sdio_disable_host_int(adapter); + + /* Get SDIO ioport */ + mwifiex_init_sdio_ioport(adapter); + + /* Initialize SDIO variables in card */ + card->mp_rd_bitmap = 0; + card->mp_wr_bitmap = 0; + card->curr_rd_port = 1; + card->curr_wr_port = 1; + + card->mp_data_port_mask = DATA_PORT_MASK; + + card->mpa_tx.buf_len = 0; + card->mpa_tx.pkt_cnt = 0; + card->mpa_tx.start_port = 0; + + card->mpa_tx.enabled = 1; + card->mpa_tx.pkt_aggr_limit = SDIO_MP_AGGR_DEF_PKT_LIMIT; + + card->mpa_rx.buf_len = 0; + card->mpa_rx.pkt_cnt = 0; + card->mpa_rx.start_port = 0; + + card->mpa_rx.enabled = 1; + card->mpa_rx.pkt_aggr_limit = SDIO_MP_AGGR_DEF_PKT_LIMIT; + + /* Allocate buffers for SDIO MP-A */ + card->mp_regs = kzalloc(MAX_MP_REGS, GFP_KERNEL); + if (!card->mp_regs) + return -ENOMEM; + + ret = mwifiex_alloc_sdio_mpa_buffers(adapter, + SDIO_MP_TX_AGGR_DEF_BUF_SIZE, + SDIO_MP_RX_AGGR_DEF_BUF_SIZE); + if (ret) { + dev_err(adapter->dev, "failed to alloc sdio mp-a buffers\n"); + kfree(card->mp_regs); + return -1; + } + + return ret; +} + +/* + * This function resets the MPA Tx and Rx buffers. + */ +static void mwifiex_cleanup_mpa_buf(struct mwifiex_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + MP_TX_AGGR_BUF_RESET(card); + MP_RX_AGGR_BUF_RESET(card); +} + +/* + * This function cleans up the allocated card buffers. + * + * The following are freed by this function - + * - MP registers + * - MPA Tx buffer + * - MPA Rx buffer + */ +static void mwifiex_cleanup_sdio(struct mwifiex_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + kfree(card->mp_regs); + kfree(card->mpa_tx.buf); + kfree(card->mpa_rx.buf); +} + +/* + * This function updates the MP end port in card. + */ +static void +mwifiex_update_mp_end_port(struct mwifiex_adapter *adapter, u16 port) +{ + struct sdio_mmc_card *card = adapter->card; + int i; + + card->mp_end_port = port; + + card->mp_data_port_mask = DATA_PORT_MASK; + + for (i = 1; i <= MAX_PORT - card->mp_end_port; i++) + card->mp_data_port_mask &= ~(1 << (MAX_PORT - i)); + + card->curr_wr_port = 1; + + dev_dbg(adapter->dev, "cmd: mp_end_port %d, data port mask 0x%x\n", + port, card->mp_data_port_mask); +} + +static struct mmc_host *reset_host; +static void sdio_card_reset_worker(struct work_struct *work) +{ + struct mmc_host *target = reset_host; + + /* The actual reset operation must be run outside of driver thread. + * This is because mmc_remove_host() will cause the device to be + * instantly destroyed, and the driver then needs to end its thread, + * leading to a deadlock. + * + * We run it in a totally independent workqueue. + */ + + pr_err("Resetting card...\n"); + mmc_remove_host(target); + /* 20ms delay is based on experiment with sdhci controller */ + mdelay(20); + mmc_add_host(target); +} +static DECLARE_WORK(card_reset_work, sdio_card_reset_worker); + +/* This function resets the card */ +static void mwifiex_sdio_card_reset(struct mwifiex_adapter *adapter) +{ + struct sdio_mmc_card *card = adapter->card; + + reset_host = card->func->card->host; + schedule_work(&card_reset_work); +} + +static struct mwifiex_if_ops sdio_ops = { + .init_if = mwifiex_init_sdio, + .cleanup_if = mwifiex_cleanup_sdio, + .check_fw_status = mwifiex_check_fw_status, + .prog_fw = mwifiex_prog_fw_w_helper, + .register_dev = mwifiex_register_dev, + .unregister_dev = mwifiex_unregister_dev, + .enable_int = mwifiex_sdio_enable_host_int, + .process_int_status = mwifiex_process_int_status, + .host_to_card = mwifiex_sdio_host_to_card, + .wakeup = mwifiex_pm_wakeup_card, + .wakeup_complete = mwifiex_pm_wakeup_card_complete, + + /* SDIO specific */ + .update_mp_end_port = mwifiex_update_mp_end_port, + .cleanup_mpa_buf = mwifiex_cleanup_mpa_buf, + .cmdrsp_complete = mwifiex_sdio_cmdrsp_complete, + .event_complete = mwifiex_sdio_event_complete, + .card_reset = mwifiex_sdio_card_reset, +}; + +/* + * This function initializes the SDIO driver. + * + * This initiates the semaphore and registers the device with + * SDIO bus. + */ +static int +mwifiex_sdio_init_module(void) +{ + sema_init(&add_remove_card_sem, 1); + + /* Clear the flag in case user removes the card. */ + user_rmmod = 0; + + return sdio_register_driver(&mwifiex_sdio); +} + +/* + * This function cleans up the SDIO driver. + * + * The following major steps are followed for cleanup - + * - Resume the device if its suspended + * - Disconnect the device if connected + * - Shutdown the firmware + * - Unregister the device from SDIO bus. + */ +static void +mwifiex_sdio_cleanup_module(void) +{ + if (!down_interruptible(&add_remove_card_sem)) + up(&add_remove_card_sem); + + /* Set the flag as user is removing this module. */ + user_rmmod = 1; + + cancel_work_sync(&card_reset_work); + sdio_unregister_driver(&mwifiex_sdio); +} + +module_init(mwifiex_sdio_init_module); +module_exit(mwifiex_sdio_cleanup_module); + +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_DESCRIPTION("Marvell WiFi-Ex SDIO Driver version " SDIO_VERSION); +MODULE_VERSION(SDIO_VERSION); +MODULE_LICENSE("GPL v2"); +MODULE_FIRMWARE(SD8786_DEFAULT_FW_NAME); +MODULE_FIRMWARE(SD8787_DEFAULT_FW_NAME); +MODULE_FIRMWARE(SD8797_DEFAULT_FW_NAME); diff --git a/drivers/net/wireless/mwifiex/sdio.h b/drivers/net/wireless/mwifiex/sdio.h new file mode 100644 index 0000000..8cc5468 --- /dev/null +++ b/drivers/net/wireless/mwifiex/sdio.h @@ -0,0 +1,328 @@ +/* + * Marvell Wireless LAN device driver: SDIO specific definitions + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _MWIFIEX_SDIO_H +#define _MWIFIEX_SDIO_H + + +#include +#include +#include +#include +#include + +#include "main.h" + +#define SD8786_DEFAULT_FW_NAME "mrvl/sd8786_uapsta.bin" +#define SD8787_DEFAULT_FW_NAME "mrvl/sd8787_uapsta.bin" +#define SD8797_DEFAULT_FW_NAME "mrvl/sd8797_uapsta.bin" + +#define BLOCK_MODE 1 +#define BYTE_MODE 0 + +#define REG_PORT 0 +#define RD_BITMAP_L 0x04 +#define RD_BITMAP_U 0x05 +#define WR_BITMAP_L 0x06 +#define WR_BITMAP_U 0x07 +#define RD_LEN_P0_L 0x08 +#define RD_LEN_P0_U 0x09 + +#define MWIFIEX_SDIO_IO_PORT_MASK 0xfffff + +#define MWIFIEX_SDIO_BYTE_MODE_MASK 0x80000000 + +#define CTRL_PORT 0 +#define CTRL_PORT_MASK 0x0001 +#define DATA_PORT_MASK 0xfffe + +#define MAX_MP_REGS 64 +#define MAX_PORT 16 + +#define SDIO_MP_AGGR_DEF_PKT_LIMIT 8 + +#define SDIO_MP_TX_AGGR_DEF_BUF_SIZE (8192) /* 8K */ + +/* Multi port RX aggregation buffer size */ +#define SDIO_MP_RX_AGGR_DEF_BUF_SIZE (16384) /* 16K */ + +/* Misc. Config Register : Auto Re-enable interrupts */ +#define AUTO_RE_ENABLE_INT BIT(4) + +/* Host Control Registers */ +/* Host Control Registers : I/O port 0 */ +#define IO_PORT_0_REG 0x78 +/* Host Control Registers : I/O port 1 */ +#define IO_PORT_1_REG 0x79 +/* Host Control Registers : I/O port 2 */ +#define IO_PORT_2_REG 0x7A + +/* Host Control Registers : Configuration */ +#define CONFIGURATION_REG 0x00 +/* Host Control Registers : Host without Command 53 finish host*/ +#define HOST_TO_CARD_EVENT (0x1U << 3) +/* Host Control Registers : Host without Command 53 finish host */ +#define HOST_WO_CMD53_FINISH_HOST (0x1U << 2) +/* Host Control Registers : Host power up */ +#define HOST_POWER_UP (0x1U << 1) +/* Host Control Registers : Host power down */ +#define HOST_POWER_DOWN (0x1U << 0) + +/* Host Control Registers : Host interrupt mask */ +#define HOST_INT_MASK_REG 0x02 +/* Host Control Registers : Upload host interrupt mask */ +#define UP_LD_HOST_INT_MASK (0x1U) +/* Host Control Registers : Download host interrupt mask */ +#define DN_LD_HOST_INT_MASK (0x2U) +/* Enable Host interrupt mask */ +#define HOST_INT_ENABLE (UP_LD_HOST_INT_MASK | DN_LD_HOST_INT_MASK) +/* Disable Host interrupt mask */ +#define HOST_INT_DISABLE 0xff + +/* Host Control Registers : Host interrupt status */ +#define HOST_INTSTATUS_REG 0x03 +/* Host Control Registers : Upload host interrupt status */ +#define UP_LD_HOST_INT_STATUS (0x1U) +/* Host Control Registers : Download host interrupt status */ +#define DN_LD_HOST_INT_STATUS (0x2U) + +/* Host Control Registers : Host interrupt RSR */ +#define HOST_INT_RSR_REG 0x01 +/* Host Control Registers : Upload host interrupt RSR */ +#define UP_LD_HOST_INT_RSR (0x1U) +#define SDIO_INT_MASK 0x3F + +/* Host Control Registers : Host interrupt status */ +#define HOST_INT_STATUS_REG 0x28 +/* Host Control Registers : Upload CRC error */ +#define UP_LD_CRC_ERR (0x1U << 2) +/* Host Control Registers : Upload restart */ +#define UP_LD_RESTART (0x1U << 1) +/* Host Control Registers : Download restart */ +#define DN_LD_RESTART (0x1U << 0) + +/* Card Control Registers : Card status register */ +#define CARD_STATUS_REG 0x30 +/* Card Control Registers : Card I/O ready */ +#define CARD_IO_READY (0x1U << 3) +/* Card Control Registers : CIS card ready */ +#define CIS_CARD_RDY (0x1U << 2) +/* Card Control Registers : Upload card ready */ +#define UP_LD_CARD_RDY (0x1U << 1) +/* Card Control Registers : Download card ready */ +#define DN_LD_CARD_RDY (0x1U << 0) + +/* Card Control Registers : Host interrupt mask register */ +#define HOST_INTERRUPT_MASK_REG 0x34 +/* Card Control Registers : Host power interrupt mask */ +#define HOST_POWER_INT_MASK (0x1U << 3) +/* Card Control Registers : Abort card interrupt mask */ +#define ABORT_CARD_INT_MASK (0x1U << 2) +/* Card Control Registers : Upload card interrupt mask */ +#define UP_LD_CARD_INT_MASK (0x1U << 1) +/* Card Control Registers : Download card interrupt mask */ +#define DN_LD_CARD_INT_MASK (0x1U << 0) + +/* Card Control Registers : Card interrupt status register */ +#define CARD_INTERRUPT_STATUS_REG 0x38 +/* Card Control Registers : Power up interrupt */ +#define POWER_UP_INT (0x1U << 4) +/* Card Control Registers : Power down interrupt */ +#define POWER_DOWN_INT (0x1U << 3) + +/* Card Control Registers : Card interrupt RSR register */ +#define CARD_INTERRUPT_RSR_REG 0x3c +/* Card Control Registers : Power up RSR */ +#define POWER_UP_RSR (0x1U << 4) +/* Card Control Registers : Power down RSR */ +#define POWER_DOWN_RSR (0x1U << 3) + +/* Card Control Registers : Miscellaneous Configuration Register */ +#define CARD_MISC_CFG_REG 0x6C + +/* Host F1 read base 0 */ +#define HOST_F1_RD_BASE_0 0x0040 +/* Host F1 read base 1 */ +#define HOST_F1_RD_BASE_1 0x0041 +/* Host F1 card ready */ +#define HOST_F1_CARD_RDY 0x0020 + +/* Firmware status 0 register */ +#define CARD_FW_STATUS0_REG 0x60 +/* Firmware status 1 register */ +#define CARD_FW_STATUS1_REG 0x61 +/* Rx length register */ +#define CARD_RX_LEN_REG 0x62 +/* Rx unit register */ +#define CARD_RX_UNIT_REG 0x63 + +/* Max retry number of CMD53 write */ +#define MAX_WRITE_IOMEM_RETRY 2 + +/* SDIO Tx aggregation in progress ? */ +#define MP_TX_AGGR_IN_PROGRESS(a) (a->mpa_tx.pkt_cnt > 0) + +/* SDIO Tx aggregation buffer room for next packet ? */ +#define MP_TX_AGGR_BUF_HAS_ROOM(a, len) ((a->mpa_tx.buf_len+len) \ + <= a->mpa_tx.buf_size) + +/* Copy current packet (SDIO Tx aggregation buffer) to SDIO buffer */ +#define MP_TX_AGGR_BUF_PUT(a, payload, pkt_len, port) do { \ + memmove(&a->mpa_tx.buf[a->mpa_tx.buf_len], \ + payload, pkt_len); \ + a->mpa_tx.buf_len += pkt_len; \ + if (!a->mpa_tx.pkt_cnt) \ + a->mpa_tx.start_port = port; \ + if (a->mpa_tx.start_port <= port) \ + a->mpa_tx.ports |= (1<<(a->mpa_tx.pkt_cnt)); \ + else \ + a->mpa_tx.ports |= (1<<(a->mpa_tx.pkt_cnt+1+(MAX_PORT - \ + a->mp_end_port))); \ + a->mpa_tx.pkt_cnt++; \ +} while (0) + +/* SDIO Tx aggregation limit ? */ +#define MP_TX_AGGR_PKT_LIMIT_REACHED(a) \ + (a->mpa_tx.pkt_cnt == a->mpa_tx.pkt_aggr_limit) + +/* SDIO Tx aggregation port limit ? */ +#define MP_TX_AGGR_PORT_LIMIT_REACHED(a) ((a->curr_wr_port < \ + a->mpa_tx.start_port) && (((MAX_PORT - \ + a->mpa_tx.start_port) + a->curr_wr_port) >= \ + SDIO_MP_AGGR_DEF_PKT_LIMIT)) + +/* Reset SDIO Tx aggregation buffer parameters */ +#define MP_TX_AGGR_BUF_RESET(a) do { \ + a->mpa_tx.pkt_cnt = 0; \ + a->mpa_tx.buf_len = 0; \ + a->mpa_tx.ports = 0; \ + a->mpa_tx.start_port = 0; \ +} while (0) + +/* SDIO Rx aggregation limit ? */ +#define MP_RX_AGGR_PKT_LIMIT_REACHED(a) \ + (a->mpa_rx.pkt_cnt == a->mpa_rx.pkt_aggr_limit) + +/* SDIO Tx aggregation port limit ? */ +#define MP_RX_AGGR_PORT_LIMIT_REACHED(a) ((a->curr_rd_port < \ + a->mpa_rx.start_port) && (((MAX_PORT - \ + a->mpa_rx.start_port) + a->curr_rd_port) >= \ + SDIO_MP_AGGR_DEF_PKT_LIMIT)) + +/* SDIO Rx aggregation in progress ? */ +#define MP_RX_AGGR_IN_PROGRESS(a) (a->mpa_rx.pkt_cnt > 0) + +/* SDIO Rx aggregation buffer room for next packet ? */ +#define MP_RX_AGGR_BUF_HAS_ROOM(a, rx_len) \ + ((a->mpa_rx.buf_len+rx_len) <= a->mpa_rx.buf_size) + +/* Prepare to copy current packet from card to SDIO Rx aggregation buffer */ +#define MP_RX_AGGR_SETUP(a, skb, port) do { \ + a->mpa_rx.buf_len += skb->len; \ + if (!a->mpa_rx.pkt_cnt) \ + a->mpa_rx.start_port = port; \ + if (a->mpa_rx.start_port <= port) \ + a->mpa_rx.ports |= (1<<(a->mpa_rx.pkt_cnt)); \ + else \ + a->mpa_rx.ports |= (1<<(a->mpa_rx.pkt_cnt+1)); \ + a->mpa_rx.skb_arr[a->mpa_rx.pkt_cnt] = skb; \ + a->mpa_rx.len_arr[a->mpa_rx.pkt_cnt] = skb->len; \ + a->mpa_rx.pkt_cnt++; \ +} while (0) + +/* Reset SDIO Rx aggregation buffer parameters */ +#define MP_RX_AGGR_BUF_RESET(a) do { \ + a->mpa_rx.pkt_cnt = 0; \ + a->mpa_rx.buf_len = 0; \ + a->mpa_rx.ports = 0; \ + a->mpa_rx.start_port = 0; \ +} while (0) + + +/* data structure for SDIO MPA TX */ +struct mwifiex_sdio_mpa_tx { + /* multiport tx aggregation buffer pointer */ + u8 *buf; + u32 buf_len; + u32 pkt_cnt; + u16 ports; + u16 start_port; + u8 enabled; + u32 buf_size; + u32 pkt_aggr_limit; +}; + +struct mwifiex_sdio_mpa_rx { + u8 *buf; + u32 buf_len; + u32 pkt_cnt; + u16 ports; + u16 start_port; + + struct sk_buff *skb_arr[SDIO_MP_AGGR_DEF_PKT_LIMIT]; + u32 len_arr[SDIO_MP_AGGR_DEF_PKT_LIMIT]; + + u8 enabled; + u32 buf_size; + u32 pkt_aggr_limit; +}; + +int mwifiex_bus_register(void); +void mwifiex_bus_unregister(void); + +struct sdio_mmc_card { + struct sdio_func *func; + struct mwifiex_adapter *adapter; + + u16 mp_rd_bitmap; + u16 mp_wr_bitmap; + + u16 mp_end_port; + u16 mp_data_port_mask; + + u8 curr_rd_port; + u8 curr_wr_port; + + u8 *mp_regs; + + struct mwifiex_sdio_mpa_tx mpa_tx; + struct mwifiex_sdio_mpa_rx mpa_rx; +}; + +/* + * .cmdrsp_complete handler + */ +static inline int mwifiex_sdio_cmdrsp_complete(struct mwifiex_adapter *adapter, + struct sk_buff *skb) +{ + dev_kfree_skb_any(skb); + return 0; +} + +/* + * .event_complete handler + */ +static inline int mwifiex_sdio_event_complete(struct mwifiex_adapter *adapter, + struct sk_buff *skb) +{ + dev_kfree_skb_any(skb); + return 0; +} + +#endif /* _MWIFIEX_SDIO_H */ diff --git a/drivers/net/wireless/mwifiex/sta_cmd.c b/drivers/net/wireless/mwifiex/sta_cmd.c new file mode 100644 index 0000000..b193e25 --- /dev/null +++ b/drivers/net/wireless/mwifiex/sta_cmd.c @@ -0,0 +1,1507 @@ +/* + * Marvell Wireless LAN device driver: station command handling + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" +#include "11ac.h" + +/* + * This function prepares command to set/get RSSI information. + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Setting data/beacon average factors + * - Resetting SNR/NF/RSSI values in private structure + * - Ensuring correct endian-ness + */ +static int +mwifiex_cmd_802_11_rssi_info(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, u16 cmd_action) +{ + cmd->command = cpu_to_le16(HostCmd_CMD_RSSI_INFO); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_rssi_info) + + S_DS_GEN); + cmd->params.rssi_info.action = cpu_to_le16(cmd_action); + cmd->params.rssi_info.ndata = cpu_to_le16(priv->data_avg_factor); + cmd->params.rssi_info.nbcn = cpu_to_le16(priv->bcn_avg_factor); + + /* Reset SNR/NF/RSSI values in private structure */ + priv->data_rssi_last = 0; + priv->data_nf_last = 0; + priv->data_rssi_avg = 0; + priv->data_nf_avg = 0; + priv->bcn_rssi_last = 0; + priv->bcn_nf_last = 0; + priv->bcn_rssi_avg = 0; + priv->bcn_nf_avg = 0; + + return 0; +} + +/* + * This function prepares command to set MAC control. + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Ensuring correct endian-ness + */ +static int mwifiex_cmd_mac_control(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_action, u16 *action) +{ + struct host_cmd_ds_mac_control *mac_ctrl = &cmd->params.mac_ctrl; + + if (cmd_action != HostCmd_ACT_GEN_SET) { + dev_err(priv->adapter->dev, + "mac_control: only support set cmd\n"); + return -1; + } + + cmd->command = cpu_to_le16(HostCmd_CMD_MAC_CONTROL); + cmd->size = + cpu_to_le16(sizeof(struct host_cmd_ds_mac_control) + S_DS_GEN); + mac_ctrl->action = cpu_to_le16(*action); + + return 0; +} + +/* + * This function prepares command to set/get SNMP MIB. + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Setting SNMP MIB OID number and value + * (as required) + * - Ensuring correct endian-ness + * + * The following SNMP MIB OIDs are supported - + * - FRAG_THRESH_I : Fragmentation threshold + * - RTS_THRESH_I : RTS threshold + * - SHORT_RETRY_LIM_I : Short retry limit + * - DOT11D_I : 11d support + */ +static int mwifiex_cmd_802_11_snmp_mib(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_action, u32 cmd_oid, + u16 *ul_temp) +{ + struct host_cmd_ds_802_11_snmp_mib *snmp_mib = &cmd->params.smib; + + dev_dbg(priv->adapter->dev, "cmd: SNMP_CMD: cmd_oid = 0x%x\n", cmd_oid); + cmd->command = cpu_to_le16(HostCmd_CMD_802_11_SNMP_MIB); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_snmp_mib) + - 1 + S_DS_GEN); + + snmp_mib->oid = cpu_to_le16((u16)cmd_oid); + if (cmd_action == HostCmd_ACT_GEN_GET) { + snmp_mib->query_type = cpu_to_le16(HostCmd_ACT_GEN_GET); + snmp_mib->buf_size = cpu_to_le16(MAX_SNMP_BUF_SIZE); + le16_add_cpu(&cmd->size, MAX_SNMP_BUF_SIZE); + } else if (cmd_action == HostCmd_ACT_GEN_SET) { + snmp_mib->query_type = cpu_to_le16(HostCmd_ACT_GEN_SET); + snmp_mib->buf_size = cpu_to_le16(sizeof(u16)); + *((__le16 *) (snmp_mib->value)) = cpu_to_le16(*ul_temp); + le16_add_cpu(&cmd->size, sizeof(u16)); + } + + dev_dbg(priv->adapter->dev, + "cmd: SNMP_CMD: Action=0x%x, OID=0x%x, OIDSize=0x%x," + " Value=0x%x\n", + cmd_action, cmd_oid, le16_to_cpu(snmp_mib->buf_size), + le16_to_cpu(*(__le16 *) snmp_mib->value)); + return 0; +} + +/* + * This function prepares command to get log. + * + * Preparation includes - + * - Setting command ID and proper size + * - Ensuring correct endian-ness + */ +static int +mwifiex_cmd_802_11_get_log(struct host_cmd_ds_command *cmd) +{ + cmd->command = cpu_to_le16(HostCmd_CMD_802_11_GET_LOG); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_get_log) + + S_DS_GEN); + return 0; +} + +/* + * This function prepares command to set/get Tx data rate configuration. + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Setting configuration index, rate scope and rate drop pattern + * parameters (as required) + * - Ensuring correct endian-ness + */ +static int mwifiex_cmd_tx_rate_cfg(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_action, u16 *pbitmap_rates) +{ + struct host_cmd_ds_tx_rate_cfg *rate_cfg = &cmd->params.tx_rate_cfg; + struct mwifiex_rate_scope *rate_scope; + struct mwifiex_rate_drop_pattern *rate_drop; + u32 i; + + cmd->command = cpu_to_le16(HostCmd_CMD_TX_RATE_CFG); + + rate_cfg->action = cpu_to_le16(cmd_action); + rate_cfg->cfg_index = 0; + + rate_scope = (struct mwifiex_rate_scope *) ((u8 *) rate_cfg + + sizeof(struct host_cmd_ds_tx_rate_cfg)); + rate_scope->type = cpu_to_le16(TLV_TYPE_RATE_SCOPE); + rate_scope->length = cpu_to_le16 + (sizeof(*rate_scope) - sizeof(struct mwifiex_ie_types_header)); + if (pbitmap_rates != NULL) { + rate_scope->hr_dsss_rate_bitmap = cpu_to_le16(pbitmap_rates[0]); + rate_scope->ofdm_rate_bitmap = cpu_to_le16(pbitmap_rates[1]); + for (i = 0; + i < sizeof(rate_scope->ht_mcs_rate_bitmap) / sizeof(u16); + i++) + rate_scope->ht_mcs_rate_bitmap[i] = + cpu_to_le16(pbitmap_rates[2 + i]); + } else { + rate_scope->hr_dsss_rate_bitmap = + cpu_to_le16(priv->bitmap_rates[0]); + rate_scope->ofdm_rate_bitmap = + cpu_to_le16(priv->bitmap_rates[1]); + for (i = 0; + i < sizeof(rate_scope->ht_mcs_rate_bitmap) / sizeof(u16); + i++) + rate_scope->ht_mcs_rate_bitmap[i] = + cpu_to_le16(priv->bitmap_rates[2 + i]); + } + + rate_drop = (struct mwifiex_rate_drop_pattern *) ((u8 *) rate_scope + + sizeof(struct mwifiex_rate_scope)); + rate_drop->type = cpu_to_le16(TLV_TYPE_RATE_DROP_CONTROL); + rate_drop->length = cpu_to_le16(sizeof(rate_drop->rate_drop_mode)); + rate_drop->rate_drop_mode = 0; + + cmd->size = + cpu_to_le16(S_DS_GEN + sizeof(struct host_cmd_ds_tx_rate_cfg) + + sizeof(struct mwifiex_rate_scope) + + sizeof(struct mwifiex_rate_drop_pattern)); + + return 0; +} + +/* + * This function prepares command to set/get Tx power configuration. + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Setting Tx power mode, power group TLV + * (as required) + * - Ensuring correct endian-ness + */ +static int mwifiex_cmd_tx_power_cfg(struct host_cmd_ds_command *cmd, + u16 cmd_action, + struct host_cmd_ds_txpwr_cfg *txp) +{ + struct mwifiex_types_power_group *pg_tlv; + struct host_cmd_ds_txpwr_cfg *cmd_txp_cfg = &cmd->params.txp_cfg; + + cmd->command = cpu_to_le16(HostCmd_CMD_TXPWR_CFG); + cmd->size = + cpu_to_le16(S_DS_GEN + sizeof(struct host_cmd_ds_txpwr_cfg)); + switch (cmd_action) { + case HostCmd_ACT_GEN_SET: + if (txp->mode) { + pg_tlv = (struct mwifiex_types_power_group + *) ((unsigned long) txp + + sizeof(struct host_cmd_ds_txpwr_cfg)); + memmove(cmd_txp_cfg, txp, + sizeof(struct host_cmd_ds_txpwr_cfg) + + sizeof(struct mwifiex_types_power_group) + + pg_tlv->length); + + pg_tlv = (struct mwifiex_types_power_group *) ((u8 *) + cmd_txp_cfg + + sizeof(struct host_cmd_ds_txpwr_cfg)); + cmd->size = cpu_to_le16(le16_to_cpu(cmd->size) + + sizeof(struct mwifiex_types_power_group) + + pg_tlv->length); + } else { + memmove(cmd_txp_cfg, txp, sizeof(*txp)); + } + cmd_txp_cfg->action = cpu_to_le16(cmd_action); + break; + case HostCmd_ACT_GEN_GET: + cmd_txp_cfg->action = cpu_to_le16(cmd_action); + break; + } + + return 0; +} + +/* + * This function prepares command to get RF Tx power. + */ +static int mwifiex_cmd_rf_tx_power(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_action, void *data_buf) +{ + struct host_cmd_ds_rf_tx_pwr *txp = &cmd->params.txp; + + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_rf_tx_pwr) + + S_DS_GEN); + cmd->command = cpu_to_le16(HostCmd_CMD_RF_TX_PWR); + txp->action = cpu_to_le16(cmd_action); + + return 0; +} + +/* + * This function prepares command to set rf antenna. + */ +static int mwifiex_cmd_rf_antenna(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_action, + struct mwifiex_ds_ant_cfg *ant_cfg) +{ + struct host_cmd_ds_rf_ant_mimo *ant_mimo = &cmd->params.ant_mimo; + struct host_cmd_ds_rf_ant_siso *ant_siso = &cmd->params.ant_siso; + + cmd->command = cpu_to_le16(HostCmd_CMD_RF_ANTENNA); + + if (cmd_action != HostCmd_ACT_GEN_SET) + return 0; + + if (priv->adapter->hw_dev_mcs_support == HT_STREAM_2X2) { + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_rf_ant_mimo) + + S_DS_GEN); + ant_mimo->action_tx = cpu_to_le16(HostCmd_ACT_SET_TX); + ant_mimo->tx_ant_mode = cpu_to_le16((u16)ant_cfg->tx_ant); + ant_mimo->action_rx = cpu_to_le16(HostCmd_ACT_SET_RX); + ant_mimo->rx_ant_mode = cpu_to_le16((u16)ant_cfg->rx_ant); + } else { + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_rf_ant_siso) + + S_DS_GEN); + ant_siso->action = cpu_to_le16(HostCmd_ACT_SET_BOTH); + ant_siso->ant_mode = cpu_to_le16((u16)ant_cfg->tx_ant); + } + + return 0; +} + +/* + * This function prepares command to set Host Sleep configuration. + * + * Preparation includes - + * - Setting command ID and proper size + * - Setting Host Sleep action, conditions, ARP filters + * (as required) + * - Ensuring correct endian-ness + */ +static int +mwifiex_cmd_802_11_hs_cfg(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_action, + struct mwifiex_hs_config_param *hscfg_param) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg = &cmd->params.opt_hs_cfg; + u16 hs_activate = false; + + if (!hscfg_param) + /* New Activate command */ + hs_activate = true; + cmd->command = cpu_to_le16(HostCmd_CMD_802_11_HS_CFG_ENH); + + if (!hs_activate && + (hscfg_param->conditions != cpu_to_le32(HS_CFG_CANCEL)) && + ((adapter->arp_filter_size > 0) && + (adapter->arp_filter_size <= ARP_FILTER_MAX_BUF_SIZE))) { + dev_dbg(adapter->dev, + "cmd: Attach %d bytes ArpFilter to HSCfg cmd\n", + adapter->arp_filter_size); + memcpy(((u8 *) hs_cfg) + + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh), + adapter->arp_filter, adapter->arp_filter_size); + cmd->size = cpu_to_le16 + (adapter->arp_filter_size + + sizeof(struct host_cmd_ds_802_11_hs_cfg_enh) + + S_DS_GEN); + } else { + cmd->size = cpu_to_le16(S_DS_GEN + sizeof(struct + host_cmd_ds_802_11_hs_cfg_enh)); + } + if (hs_activate) { + hs_cfg->action = cpu_to_le16(HS_ACTIVATE); + hs_cfg->params.hs_activate.resp_ctrl = RESP_NEEDED; + } else { + hs_cfg->action = cpu_to_le16(HS_CONFIGURE); + hs_cfg->params.hs_config.conditions = hscfg_param->conditions; + hs_cfg->params.hs_config.gpio = hscfg_param->gpio; + hs_cfg->params.hs_config.gap = hscfg_param->gap; + dev_dbg(adapter->dev, + "cmd: HS_CFG_CMD: condition:0x%x gpio:0x%x gap:0x%x\n", + hs_cfg->params.hs_config.conditions, + hs_cfg->params.hs_config.gpio, + hs_cfg->params.hs_config.gap); + } + + return 0; +} + +/* + * This function prepares command to set/get MAC address. + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Setting MAC address (for SET only) + * - Ensuring correct endian-ness + */ +static int mwifiex_cmd_802_11_mac_address(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_action) +{ + cmd->command = cpu_to_le16(HostCmd_CMD_802_11_MAC_ADDRESS); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_mac_address) + + S_DS_GEN); + cmd->result = 0; + + cmd->params.mac_addr.action = cpu_to_le16(cmd_action); + + if (cmd_action == HostCmd_ACT_GEN_SET) + memcpy(cmd->params.mac_addr.mac_addr, priv->curr_addr, + ETH_ALEN); + return 0; +} + +/* + * This function prepares command to set MAC multicast address. + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Setting MAC multicast address + * - Ensuring correct endian-ness + */ +static int +mwifiex_cmd_mac_multicast_adr(struct host_cmd_ds_command *cmd, + u16 cmd_action, + struct mwifiex_multicast_list *mcast_list) +{ + struct host_cmd_ds_mac_multicast_adr *mcast_addr = &cmd->params.mc_addr; + + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_mac_multicast_adr) + + S_DS_GEN); + cmd->command = cpu_to_le16(HostCmd_CMD_MAC_MULTICAST_ADR); + + mcast_addr->action = cpu_to_le16(cmd_action); + mcast_addr->num_of_adrs = + cpu_to_le16((u16) mcast_list->num_multicast_addr); + memcpy(mcast_addr->mac_list, mcast_list->mac_list, + mcast_list->num_multicast_addr * ETH_ALEN); + + return 0; +} + +/* + * This function prepares command to deauthenticate. + * + * Preparation includes - + * - Setting command ID and proper size + * - Setting AP MAC address and reason code + * - Ensuring correct endian-ness + */ +static int mwifiex_cmd_802_11_deauthenticate(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + u8 *mac) +{ + struct host_cmd_ds_802_11_deauthenticate *deauth = &cmd->params.deauth; + + cmd->command = cpu_to_le16(HostCmd_CMD_802_11_DEAUTHENTICATE); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_deauthenticate) + + S_DS_GEN); + + /* Set AP MAC address */ + memcpy(deauth->mac_addr, mac, ETH_ALEN); + + dev_dbg(priv->adapter->dev, "cmd: Deauth: %pM\n", deauth->mac_addr); + + deauth->reason_code = cpu_to_le16(WLAN_REASON_DEAUTH_LEAVING); + + return 0; +} + +/* + * This function prepares command to stop Ad-Hoc network. + * + * Preparation includes - + * - Setting command ID and proper size + * - Ensuring correct endian-ness + */ +static int mwifiex_cmd_802_11_ad_hoc_stop(struct host_cmd_ds_command *cmd) +{ + cmd->command = cpu_to_le16(HostCmd_CMD_802_11_AD_HOC_STOP); + cmd->size = cpu_to_le16(S_DS_GEN); + return 0; +} + +/* + * This function sets WEP key(s) to key parameter TLV(s). + * + * Multi-key parameter TLVs are supported, so we can send multiple + * WEP keys in a single buffer. + */ +static int +mwifiex_set_keyparamset_wep(struct mwifiex_private *priv, + struct mwifiex_ie_type_key_param_set *key_param_set, + u16 *key_param_len) +{ + int cur_key_param_len; + u8 i; + + /* Multi-key_param_set TLV is supported */ + for (i = 0; i < NUM_WEP_KEYS; i++) { + if ((priv->wep_key[i].key_length == WLAN_KEY_LEN_WEP40) || + (priv->wep_key[i].key_length == WLAN_KEY_LEN_WEP104)) { + key_param_set->type = + cpu_to_le16(TLV_TYPE_KEY_MATERIAL); +/* Key_param_set WEP fixed length */ +#define KEYPARAMSET_WEP_FIXED_LEN 8 + key_param_set->length = cpu_to_le16((u16) + (priv->wep_key[i]. + key_length + + KEYPARAMSET_WEP_FIXED_LEN)); + key_param_set->key_type_id = + cpu_to_le16(KEY_TYPE_ID_WEP); + key_param_set->key_info = + cpu_to_le16(KEY_ENABLED | KEY_UNICAST | + KEY_MCAST); + key_param_set->key_len = + cpu_to_le16(priv->wep_key[i].key_length); + /* Set WEP key index */ + key_param_set->key[0] = i; + /* Set default Tx key flag */ + if (i == + (priv-> + wep_key_curr_index & HostCmd_WEP_KEY_INDEX_MASK)) + key_param_set->key[1] = 1; + else + key_param_set->key[1] = 0; + memmove(&key_param_set->key[2], + priv->wep_key[i].key_material, + priv->wep_key[i].key_length); + + cur_key_param_len = priv->wep_key[i].key_length + + KEYPARAMSET_WEP_FIXED_LEN + + sizeof(struct mwifiex_ie_types_header); + *key_param_len += (u16) cur_key_param_len; + key_param_set = + (struct mwifiex_ie_type_key_param_set *) + ((u8 *)key_param_set + + cur_key_param_len); + } else if (!priv->wep_key[i].key_length) { + continue; + } else { + dev_err(priv->adapter->dev, + "key%d Length = %d is incorrect\n", + (i + 1), priv->wep_key[i].key_length); + return -1; + } + } + + return 0; +} + +/* + * This function prepares command to set/get/reset network key(s). + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Setting WEP keys, WAPI keys or WPA keys along with required + * encryption (TKIP, AES) (as required) + * - Ensuring correct endian-ness + */ +static int +mwifiex_cmd_802_11_key_material(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_action, u32 cmd_oid, + struct mwifiex_ds_encrypt_key *enc_key) +{ + struct host_cmd_ds_802_11_key_material *key_material = + &cmd->params.key_material; + struct host_cmd_tlv_mac_addr *tlv_mac; + u16 key_param_len = 0, cmd_size; + int ret = 0; + + cmd->command = cpu_to_le16(HostCmd_CMD_802_11_KEY_MATERIAL); + key_material->action = cpu_to_le16(cmd_action); + + if (cmd_action == HostCmd_ACT_GEN_GET) { + cmd->size = + cpu_to_le16(sizeof(key_material->action) + S_DS_GEN); + return ret; + } + + if (!enc_key) { + memset(&key_material->key_param_set, 0, + (NUM_WEP_KEYS * + sizeof(struct mwifiex_ie_type_key_param_set))); + ret = mwifiex_set_keyparamset_wep(priv, + &key_material->key_param_set, + &key_param_len); + cmd->size = cpu_to_le16(key_param_len + + sizeof(key_material->action) + S_DS_GEN); + return ret; + } else + memset(&key_material->key_param_set, 0, + sizeof(struct mwifiex_ie_type_key_param_set)); + if (enc_key->is_wapi_key) { + dev_dbg(priv->adapter->dev, "info: Set WAPI Key\n"); + key_material->key_param_set.key_type_id = + cpu_to_le16(KEY_TYPE_ID_WAPI); + if (cmd_oid == KEY_INFO_ENABLED) + key_material->key_param_set.key_info = + cpu_to_le16(KEY_ENABLED); + else + key_material->key_param_set.key_info = + cpu_to_le16(!KEY_ENABLED); + + key_material->key_param_set.key[0] = enc_key->key_index; + if (!priv->sec_info.wapi_key_on) + key_material->key_param_set.key[1] = 1; + else + /* set 0 when re-key */ + key_material->key_param_set.key[1] = 0; + + if (!is_broadcast_ether_addr(enc_key->mac_addr)) { + /* WAPI pairwise key: unicast */ + key_material->key_param_set.key_info |= + cpu_to_le16(KEY_UNICAST); + } else { /* WAPI group key: multicast */ + key_material->key_param_set.key_info |= + cpu_to_le16(KEY_MCAST); + priv->sec_info.wapi_key_on = true; + } + + key_material->key_param_set.type = + cpu_to_le16(TLV_TYPE_KEY_MATERIAL); + key_material->key_param_set.key_len = + cpu_to_le16(WAPI_KEY_LEN); + memcpy(&key_material->key_param_set.key[2], + enc_key->key_material, enc_key->key_len); + memcpy(&key_material->key_param_set.key[2 + enc_key->key_len], + enc_key->pn, PN_LEN); + key_material->key_param_set.length = + cpu_to_le16(WAPI_KEY_LEN + KEYPARAMSET_FIXED_LEN); + + key_param_len = (WAPI_KEY_LEN + KEYPARAMSET_FIXED_LEN) + + sizeof(struct mwifiex_ie_types_header); + cmd->size = cpu_to_le16(sizeof(key_material->action) + + S_DS_GEN + key_param_len); + return ret; + } + if (enc_key->key_len == WLAN_KEY_LEN_CCMP) { + if (enc_key->is_igtk_key) { + dev_dbg(priv->adapter->dev, "cmd: CMAC_AES\n"); + key_material->key_param_set.key_type_id = + cpu_to_le16(KEY_TYPE_ID_AES_CMAC); + if (cmd_oid == KEY_INFO_ENABLED) + key_material->key_param_set.key_info = + cpu_to_le16(KEY_ENABLED); + else + key_material->key_param_set.key_info = + cpu_to_le16(!KEY_ENABLED); + + key_material->key_param_set.key_info |= + cpu_to_le16(KEY_IGTK); + } else { + dev_dbg(priv->adapter->dev, "cmd: WPA_AES\n"); + key_material->key_param_set.key_type_id = + cpu_to_le16(KEY_TYPE_ID_AES); + if (cmd_oid == KEY_INFO_ENABLED) + key_material->key_param_set.key_info = + cpu_to_le16(KEY_ENABLED); + else + key_material->key_param_set.key_info = + cpu_to_le16(!KEY_ENABLED); + + if (enc_key->key_index & MWIFIEX_KEY_INDEX_UNICAST) + /* AES pairwise key: unicast */ + key_material->key_param_set.key_info |= + cpu_to_le16(KEY_UNICAST); + else /* AES group key: multicast */ + key_material->key_param_set.key_info |= + cpu_to_le16(KEY_MCAST); + } + } else if (enc_key->key_len == WLAN_KEY_LEN_TKIP) { + dev_dbg(priv->adapter->dev, "cmd: WPA_TKIP\n"); + key_material->key_param_set.key_type_id = + cpu_to_le16(KEY_TYPE_ID_TKIP); + key_material->key_param_set.key_info = + cpu_to_le16(KEY_ENABLED); + + if (enc_key->key_index & MWIFIEX_KEY_INDEX_UNICAST) + /* TKIP pairwise key: unicast */ + key_material->key_param_set.key_info |= + cpu_to_le16(KEY_UNICAST); + else /* TKIP group key: multicast */ + key_material->key_param_set.key_info |= + cpu_to_le16(KEY_MCAST); + } + + if (key_material->key_param_set.key_type_id) { + key_material->key_param_set.type = + cpu_to_le16(TLV_TYPE_KEY_MATERIAL); + key_material->key_param_set.key_len = + cpu_to_le16((u16) enc_key->key_len); + memcpy(key_material->key_param_set.key, enc_key->key_material, + enc_key->key_len); + key_material->key_param_set.length = + cpu_to_le16((u16) enc_key->key_len + + KEYPARAMSET_FIXED_LEN); + + key_param_len = (u16)(enc_key->key_len + KEYPARAMSET_FIXED_LEN) + + sizeof(struct mwifiex_ie_types_header); + + if (le16_to_cpu(key_material->key_param_set.key_type_id) == + KEY_TYPE_ID_AES_CMAC) { + struct mwifiex_cmac_param *param = + (void *)key_material->key_param_set.key; + + memcpy(param->ipn, enc_key->pn, IGTK_PN_LEN); + memcpy(param->key, enc_key->key_material, + WLAN_KEY_LEN_AES_CMAC); + + key_param_len = sizeof(struct mwifiex_cmac_param); + key_material->key_param_set.key_len = + cpu_to_le16(key_param_len); + key_param_len += KEYPARAMSET_FIXED_LEN; + key_material->key_param_set.length = + cpu_to_le16(key_param_len); + key_param_len += sizeof(struct mwifiex_ie_types_header); + } + + cmd->size = cpu_to_le16(sizeof(key_material->action) + S_DS_GEN + + key_param_len); + + if (priv->bss_type == MWIFIEX_BSS_TYPE_UAP) { + tlv_mac = (void *)((u8 *)&key_material->key_param_set + + key_param_len); + tlv_mac->tlv.type = cpu_to_le16(TLV_TYPE_STA_MAC_ADDR); + tlv_mac->tlv.len = cpu_to_le16(ETH_ALEN); + memcpy(tlv_mac->mac_addr, enc_key->mac_addr, ETH_ALEN); + cmd_size = key_param_len + S_DS_GEN + + sizeof(key_material->action) + + sizeof(struct host_cmd_tlv_mac_addr); + } else { + cmd_size = key_param_len + S_DS_GEN + + sizeof(key_material->action); + } + cmd->size = cpu_to_le16(cmd_size); + } + + return ret; +} + +/* + * This function prepares command to set/get 11d domain information. + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Setting domain information fields (for SET only) + * - Ensuring correct endian-ness + */ +static int mwifiex_cmd_802_11d_domain_info(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + u16 cmd_action) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11d_domain_info *domain_info = + &cmd->params.domain_info; + struct mwifiex_ietypes_domain_param_set *domain = + &domain_info->domain; + u8 no_of_triplet = adapter->domain_reg.no_of_triplet; + + dev_dbg(adapter->dev, "info: 11D: no_of_triplet=0x%x\n", no_of_triplet); + + cmd->command = cpu_to_le16(HostCmd_CMD_802_11D_DOMAIN_INFO); + domain_info->action = cpu_to_le16(cmd_action); + if (cmd_action == HostCmd_ACT_GEN_GET) { + cmd->size = cpu_to_le16(sizeof(domain_info->action) + S_DS_GEN); + return 0; + } + + /* Set domain info fields */ + domain->header.type = cpu_to_le16(WLAN_EID_COUNTRY); + memcpy(domain->country_code, adapter->domain_reg.country_code, + sizeof(domain->country_code)); + + domain->header.len = + cpu_to_le16((no_of_triplet * + sizeof(struct ieee80211_country_ie_triplet)) + + sizeof(domain->country_code)); + + if (no_of_triplet) { + memcpy(domain->triplet, adapter->domain_reg.triplet, + no_of_triplet * sizeof(struct + ieee80211_country_ie_triplet)); + + cmd->size = cpu_to_le16(sizeof(domain_info->action) + + le16_to_cpu(domain->header.len) + + sizeof(struct mwifiex_ie_types_header) + + S_DS_GEN); + } else { + cmd->size = cpu_to_le16(sizeof(domain_info->action) + S_DS_GEN); + } + + return 0; +} + +/* + * This function prepares command to set/get IBSS coalescing status. + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Setting status to enable or disable (for SET only) + * - Ensuring correct endian-ness + */ +static int mwifiex_cmd_ibss_coalescing_status(struct host_cmd_ds_command *cmd, + u16 cmd_action, u16 *enable) +{ + struct host_cmd_ds_802_11_ibss_status *ibss_coal = + &(cmd->params.ibss_coalescing); + + cmd->command = cpu_to_le16(HostCmd_CMD_802_11_IBSS_COALESCING_STATUS); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_ibss_status) + + S_DS_GEN); + cmd->result = 0; + ibss_coal->action = cpu_to_le16(cmd_action); + + switch (cmd_action) { + case HostCmd_ACT_GEN_SET: + if (enable) + ibss_coal->enable = cpu_to_le16(*enable); + else + ibss_coal->enable = 0; + break; + + /* In other case.. Nothing to do */ + case HostCmd_ACT_GEN_GET: + default: + break; + } + + return 0; +} + +/* + * This function prepares command to set/get register value. + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Setting register offset (for both GET and SET) and + * register value (for SET only) + * - Ensuring correct endian-ness + * + * The following type of registers can be accessed with this function - + * - MAC register + * - BBP register + * - RF register + * - PMIC register + * - CAU register + * - EEPROM + */ +static int mwifiex_cmd_reg_access(struct host_cmd_ds_command *cmd, + u16 cmd_action, void *data_buf) +{ + struct mwifiex_ds_reg_rw *reg_rw = data_buf; + + switch (le16_to_cpu(cmd->command)) { + case HostCmd_CMD_MAC_REG_ACCESS: + { + struct host_cmd_ds_mac_reg_access *mac_reg; + + cmd->size = cpu_to_le16(sizeof(*mac_reg) + S_DS_GEN); + mac_reg = &cmd->params.mac_reg; + mac_reg->action = cpu_to_le16(cmd_action); + mac_reg->offset = + cpu_to_le16((u16) le32_to_cpu(reg_rw->offset)); + mac_reg->value = reg_rw->value; + break; + } + case HostCmd_CMD_BBP_REG_ACCESS: + { + struct host_cmd_ds_bbp_reg_access *bbp_reg; + + cmd->size = cpu_to_le16(sizeof(*bbp_reg) + S_DS_GEN); + bbp_reg = &cmd->params.bbp_reg; + bbp_reg->action = cpu_to_le16(cmd_action); + bbp_reg->offset = + cpu_to_le16((u16) le32_to_cpu(reg_rw->offset)); + bbp_reg->value = (u8) le32_to_cpu(reg_rw->value); + break; + } + case HostCmd_CMD_RF_REG_ACCESS: + { + struct host_cmd_ds_rf_reg_access *rf_reg; + + cmd->size = cpu_to_le16(sizeof(*rf_reg) + S_DS_GEN); + rf_reg = &cmd->params.rf_reg; + rf_reg->action = cpu_to_le16(cmd_action); + rf_reg->offset = cpu_to_le16((u16) le32_to_cpu(reg_rw->offset)); + rf_reg->value = (u8) le32_to_cpu(reg_rw->value); + break; + } + case HostCmd_CMD_PMIC_REG_ACCESS: + { + struct host_cmd_ds_pmic_reg_access *pmic_reg; + + cmd->size = cpu_to_le16(sizeof(*pmic_reg) + S_DS_GEN); + pmic_reg = &cmd->params.pmic_reg; + pmic_reg->action = cpu_to_le16(cmd_action); + pmic_reg->offset = + cpu_to_le16((u16) le32_to_cpu(reg_rw->offset)); + pmic_reg->value = (u8) le32_to_cpu(reg_rw->value); + break; + } + case HostCmd_CMD_CAU_REG_ACCESS: + { + struct host_cmd_ds_rf_reg_access *cau_reg; + + cmd->size = cpu_to_le16(sizeof(*cau_reg) + S_DS_GEN); + cau_reg = &cmd->params.rf_reg; + cau_reg->action = cpu_to_le16(cmd_action); + cau_reg->offset = + cpu_to_le16((u16) le32_to_cpu(reg_rw->offset)); + cau_reg->value = (u8) le32_to_cpu(reg_rw->value); + break; + } + case HostCmd_CMD_802_11_EEPROM_ACCESS: + { + struct mwifiex_ds_read_eeprom *rd_eeprom = data_buf; + struct host_cmd_ds_802_11_eeprom_access *cmd_eeprom = + &cmd->params.eeprom; + + cmd->size = cpu_to_le16(sizeof(*cmd_eeprom) + S_DS_GEN); + cmd_eeprom->action = cpu_to_le16(cmd_action); + cmd_eeprom->offset = rd_eeprom->offset; + cmd_eeprom->byte_count = rd_eeprom->byte_count; + cmd_eeprom->value = 0; + break; + } + default: + return -1; + } + + return 0; +} + +/* + * This function prepares command to set PCI-Express + * host buffer configuration + * + * Preparation includes - + * - Setting command ID, action and proper size + * - Setting host buffer configuration + * - Ensuring correct endian-ness + */ +static int +mwifiex_cmd_pcie_host_spec(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, u16 action) +{ + struct host_cmd_ds_pcie_details *host_spec = + &cmd->params.pcie_host_spec; + struct pcie_service_card *card = priv->adapter->card; + + cmd->command = cpu_to_le16(HostCmd_CMD_PCIE_DESC_DETAILS); + cmd->size = cpu_to_le16(sizeof(struct + host_cmd_ds_pcie_details) + S_DS_GEN); + cmd->result = 0; + + memset(host_spec, 0, sizeof(struct host_cmd_ds_pcie_details)); + + if (action != HostCmd_ACT_GEN_SET) + return 0; + + /* Send the ring base addresses and count to firmware */ + host_spec->txbd_addr_lo = (u32)(card->txbd_ring_pbase); + host_spec->txbd_addr_hi = (u32)(((u64)card->txbd_ring_pbase)>>32); + host_spec->txbd_count = MWIFIEX_MAX_TXRX_BD; + host_spec->rxbd_addr_lo = (u32)(card->rxbd_ring_pbase); + host_spec->rxbd_addr_hi = (u32)(((u64)card->rxbd_ring_pbase)>>32); + host_spec->rxbd_count = MWIFIEX_MAX_TXRX_BD; + host_spec->evtbd_addr_lo = (u32)(card->evtbd_ring_pbase); + host_spec->evtbd_addr_hi = (u32)(((u64)card->evtbd_ring_pbase)>>32); + host_spec->evtbd_count = MWIFIEX_MAX_EVT_BD; + if (card->sleep_cookie_vbase) { + host_spec->sleep_cookie_addr_lo = + (u32)(card->sleep_cookie_pbase); + host_spec->sleep_cookie_addr_hi = + (u32)(((u64)(card->sleep_cookie_pbase)) >> 32); + dev_dbg(priv->adapter->dev, "sleep_cook_lo phy addr: 0x%x\n", + host_spec->sleep_cookie_addr_lo); + } + + return 0; +} + +/* + * This function prepares command for event subscription, configuration + * and query. Events can be subscribed or unsubscribed. Current subscribed + * events can be queried. Also, current subscribed events are reported in + * every FW response. + */ +static int +mwifiex_cmd_802_11_subsc_evt(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + struct mwifiex_ds_misc_subsc_evt *subsc_evt_cfg) +{ + struct host_cmd_ds_802_11_subsc_evt *subsc_evt = &cmd->params.subsc_evt; + struct mwifiex_ie_types_rssi_threshold *rssi_tlv; + u16 event_bitmap; + u8 *pos; + + cmd->command = cpu_to_le16(HostCmd_CMD_802_11_SUBSCRIBE_EVENT); + cmd->size = cpu_to_le16(sizeof(struct host_cmd_ds_802_11_subsc_evt) + + S_DS_GEN); + + subsc_evt->action = cpu_to_le16(subsc_evt_cfg->action); + dev_dbg(priv->adapter->dev, "cmd: action: %d\n", subsc_evt_cfg->action); + + /*For query requests, no configuration TLV structures are to be added.*/ + if (subsc_evt_cfg->action == HostCmd_ACT_GEN_GET) + return 0; + + subsc_evt->events = cpu_to_le16(subsc_evt_cfg->events); + + event_bitmap = subsc_evt_cfg->events; + dev_dbg(priv->adapter->dev, "cmd: event bitmap : %16x\n", + event_bitmap); + + if (((subsc_evt_cfg->action == HostCmd_ACT_BITWISE_CLR) || + (subsc_evt_cfg->action == HostCmd_ACT_BITWISE_SET)) && + (event_bitmap == 0)) { + dev_dbg(priv->adapter->dev, "Error: No event specified " + "for bitwise action type\n"); + return -EINVAL; + } + + /* + * Append TLV structures for each of the specified events for + * subscribing or re-configuring. This is not required for + * bitwise unsubscribing request. + */ + if (subsc_evt_cfg->action == HostCmd_ACT_BITWISE_CLR) + return 0; + + pos = ((u8 *)subsc_evt) + + sizeof(struct host_cmd_ds_802_11_subsc_evt); + + if (event_bitmap & BITMASK_BCN_RSSI_LOW) { + rssi_tlv = (struct mwifiex_ie_types_rssi_threshold *) pos; + + rssi_tlv->header.type = cpu_to_le16(TLV_TYPE_RSSI_LOW); + rssi_tlv->header.len = + cpu_to_le16(sizeof(struct mwifiex_ie_types_rssi_threshold) - + sizeof(struct mwifiex_ie_types_header)); + rssi_tlv->abs_value = subsc_evt_cfg->bcn_l_rssi_cfg.abs_value; + rssi_tlv->evt_freq = subsc_evt_cfg->bcn_l_rssi_cfg.evt_freq; + + dev_dbg(priv->adapter->dev, "Cfg Beacon Low Rssi event, " + "RSSI:-%d dBm, Freq:%d\n", + subsc_evt_cfg->bcn_l_rssi_cfg.abs_value, + subsc_evt_cfg->bcn_l_rssi_cfg.evt_freq); + + pos += sizeof(struct mwifiex_ie_types_rssi_threshold); + le16_add_cpu(&cmd->size, + sizeof(struct mwifiex_ie_types_rssi_threshold)); + } + + if (event_bitmap & BITMASK_BCN_RSSI_HIGH) { + rssi_tlv = (struct mwifiex_ie_types_rssi_threshold *) pos; + + rssi_tlv->header.type = cpu_to_le16(TLV_TYPE_RSSI_HIGH); + rssi_tlv->header.len = + cpu_to_le16(sizeof(struct mwifiex_ie_types_rssi_threshold) - + sizeof(struct mwifiex_ie_types_header)); + rssi_tlv->abs_value = subsc_evt_cfg->bcn_h_rssi_cfg.abs_value; + rssi_tlv->evt_freq = subsc_evt_cfg->bcn_h_rssi_cfg.evt_freq; + + dev_dbg(priv->adapter->dev, "Cfg Beacon High Rssi event, " + "RSSI:-%d dBm, Freq:%d\n", + subsc_evt_cfg->bcn_h_rssi_cfg.abs_value, + subsc_evt_cfg->bcn_h_rssi_cfg.evt_freq); + + pos += sizeof(struct mwifiex_ie_types_rssi_threshold); + le16_add_cpu(&cmd->size, + sizeof(struct mwifiex_ie_types_rssi_threshold)); + } + + return 0; +} + +static int +mwifiex_cmd_append_rpn_expression(struct mwifiex_private *priv, + struct mwifiex_mef_entry *mef_entry, + u8 **buffer) +{ + struct mwifiex_mef_filter *filter = mef_entry->filter; + int i, byte_len; + u8 *stack_ptr = *buffer; + + for (i = 0; i < MWIFIEX_MAX_FILTERS; i++) { + filter = &mef_entry->filter[i]; + if (!filter->filt_type) + break; + *(__le32 *)stack_ptr = cpu_to_le32((u32)filter->repeat); + stack_ptr += 4; + *stack_ptr = TYPE_DNUM; + stack_ptr += 1; + + byte_len = filter->byte_seq[MAX_BYTESEQ]; + memcpy(stack_ptr, filter->byte_seq, byte_len); + stack_ptr += byte_len; + *stack_ptr = byte_len; + stack_ptr += 1; + *stack_ptr = TYPE_BYTESEQ; + stack_ptr += 1; + + *(__le32 *)stack_ptr = cpu_to_le32((u32)filter->offset); + stack_ptr += 4; + *stack_ptr = TYPE_DNUM; + stack_ptr += 1; + + *stack_ptr = filter->filt_type; + stack_ptr += 1; + + if (filter->filt_action) { + *stack_ptr = filter->filt_action; + stack_ptr += 1; + } + + if (stack_ptr - *buffer > STACK_NBYTES) + return -1; + } + + *buffer = stack_ptr; + return 0; +} + +static int +mwifiex_cmd_mef_cfg(struct mwifiex_private *priv, + struct host_cmd_ds_command *cmd, + struct mwifiex_ds_mef_cfg *mef) +{ + struct host_cmd_ds_mef_cfg *mef_cfg = &cmd->params.mef_cfg; + u8 *pos = (u8 *)mef_cfg; + + cmd->command = cpu_to_le16(HostCmd_CMD_MEF_CFG); + + mef_cfg->criteria = cpu_to_le32(mef->criteria); + mef_cfg->num_entries = cpu_to_le16(mef->num_entries); + pos += sizeof(*mef_cfg); + mef_cfg->mef_entry->mode = mef->mef_entry->mode; + mef_cfg->mef_entry->action = mef->mef_entry->action; + pos += sizeof(*(mef_cfg->mef_entry)); + + if (mwifiex_cmd_append_rpn_expression(priv, mef->mef_entry, &pos)) + return -1; + + mef_cfg->mef_entry->exprsize = + cpu_to_le16(pos - mef_cfg->mef_entry->expr); + cmd->size = cpu_to_le16((u16) (pos - (u8 *)mef_cfg) + S_DS_GEN); + + return 0; +} + +/* + * This function prepares the commands before sending them to the firmware. + * + * This is a generic function which calls specific command preparation + * routines based upon the command number. + */ +int mwifiex_sta_prepare_cmd(struct mwifiex_private *priv, uint16_t cmd_no, + u16 cmd_action, u32 cmd_oid, + void *data_buf, void *cmd_buf) +{ + struct host_cmd_ds_command *cmd_ptr = cmd_buf; + int ret = 0; + + /* Prepare command */ + switch (cmd_no) { + case HostCmd_CMD_GET_HW_SPEC: + ret = mwifiex_cmd_get_hw_spec(priv, cmd_ptr); + break; + case HostCmd_CMD_MAC_CONTROL: + ret = mwifiex_cmd_mac_control(priv, cmd_ptr, cmd_action, + data_buf); + break; + case HostCmd_CMD_802_11_MAC_ADDRESS: + ret = mwifiex_cmd_802_11_mac_address(priv, cmd_ptr, + cmd_action); + break; + case HostCmd_CMD_MAC_MULTICAST_ADR: + ret = mwifiex_cmd_mac_multicast_adr(cmd_ptr, cmd_action, + data_buf); + break; + case HostCmd_CMD_TX_RATE_CFG: + ret = mwifiex_cmd_tx_rate_cfg(priv, cmd_ptr, cmd_action, + data_buf); + break; + case HostCmd_CMD_TXPWR_CFG: + ret = mwifiex_cmd_tx_power_cfg(cmd_ptr, cmd_action, + data_buf); + break; + case HostCmd_CMD_RF_TX_PWR: + ret = mwifiex_cmd_rf_tx_power(priv, cmd_ptr, cmd_action, + data_buf); + break; + case HostCmd_CMD_RF_ANTENNA: + ret = mwifiex_cmd_rf_antenna(priv, cmd_ptr, cmd_action, + data_buf); + break; + case HostCmd_CMD_802_11_PS_MODE_ENH: + ret = mwifiex_cmd_enh_power_mode(priv, cmd_ptr, cmd_action, + (uint16_t)cmd_oid, data_buf); + break; + case HostCmd_CMD_802_11_HS_CFG_ENH: + ret = mwifiex_cmd_802_11_hs_cfg(priv, cmd_ptr, cmd_action, + (struct mwifiex_hs_config_param *) data_buf); + break; + case HostCmd_CMD_802_11_SCAN: + ret = mwifiex_cmd_802_11_scan(cmd_ptr, data_buf); + break; + case HostCmd_CMD_802_11_BG_SCAN_QUERY: + ret = mwifiex_cmd_802_11_bg_scan_query(cmd_ptr); + break; + case HostCmd_CMD_802_11_ASSOCIATE: + ret = mwifiex_cmd_802_11_associate(priv, cmd_ptr, data_buf); + break; + case HostCmd_CMD_802_11_DEAUTHENTICATE: + ret = mwifiex_cmd_802_11_deauthenticate(priv, cmd_ptr, + data_buf); + break; + case HostCmd_CMD_802_11_AD_HOC_START: + ret = mwifiex_cmd_802_11_ad_hoc_start(priv, cmd_ptr, + data_buf); + break; + case HostCmd_CMD_802_11_GET_LOG: + ret = mwifiex_cmd_802_11_get_log(cmd_ptr); + break; + case HostCmd_CMD_802_11_AD_HOC_JOIN: + ret = mwifiex_cmd_802_11_ad_hoc_join(priv, cmd_ptr, + data_buf); + break; + case HostCmd_CMD_802_11_AD_HOC_STOP: + ret = mwifiex_cmd_802_11_ad_hoc_stop(cmd_ptr); + break; + case HostCmd_CMD_RSSI_INFO: + ret = mwifiex_cmd_802_11_rssi_info(priv, cmd_ptr, cmd_action); + break; + case HostCmd_CMD_802_11_SNMP_MIB: + ret = mwifiex_cmd_802_11_snmp_mib(priv, cmd_ptr, cmd_action, + cmd_oid, data_buf); + break; + case HostCmd_CMD_802_11_TX_RATE_QUERY: + cmd_ptr->command = + cpu_to_le16(HostCmd_CMD_802_11_TX_RATE_QUERY); + cmd_ptr->size = + cpu_to_le16(sizeof(struct host_cmd_ds_tx_rate_query) + + S_DS_GEN); + priv->tx_rate = 0; + ret = 0; + break; + case HostCmd_CMD_VERSION_EXT: + cmd_ptr->command = cpu_to_le16(cmd_no); + cmd_ptr->params.verext.version_str_sel = + (u8) (*((u32 *) data_buf)); + memcpy(&cmd_ptr->params, data_buf, + sizeof(struct host_cmd_ds_version_ext)); + cmd_ptr->size = + cpu_to_le16(sizeof(struct host_cmd_ds_version_ext) + + S_DS_GEN); + ret = 0; + break; + case HostCmd_CMD_MGMT_FRAME_REG: + cmd_ptr->command = cpu_to_le16(cmd_no); + cmd_ptr->params.reg_mask.action = cpu_to_le16(cmd_action); + cmd_ptr->params.reg_mask.mask = cpu_to_le32(*(u32 *)data_buf); + cmd_ptr->size = + cpu_to_le16(sizeof(struct host_cmd_ds_mgmt_frame_reg) + + S_DS_GEN); + ret = 0; + break; + case HostCmd_CMD_REMAIN_ON_CHAN: + cmd_ptr->command = cpu_to_le16(cmd_no); + memcpy(&cmd_ptr->params, data_buf, + sizeof(struct host_cmd_ds_remain_on_chan)); + cmd_ptr->size = + cpu_to_le16(sizeof(struct host_cmd_ds_remain_on_chan) + + S_DS_GEN); + break; + case HostCmd_CMD_11AC_CFG: + ret = mwifiex_cmd_11ac_cfg(priv, cmd_ptr, cmd_action, data_buf); + break; + case HostCmd_CMD_P2P_MODE_CFG: + cmd_ptr->command = cpu_to_le16(cmd_no); + cmd_ptr->params.mode_cfg.action = cpu_to_le16(cmd_action); + cmd_ptr->params.mode_cfg.mode = cpu_to_le16(*(u16 *)data_buf); + cmd_ptr->size = + cpu_to_le16(sizeof(struct host_cmd_ds_p2p_mode_cfg) + + S_DS_GEN); + break; + case HostCmd_CMD_FUNC_INIT: + if (priv->adapter->hw_status == MWIFIEX_HW_STATUS_RESET) + priv->adapter->hw_status = MWIFIEX_HW_STATUS_READY; + cmd_ptr->command = cpu_to_le16(cmd_no); + cmd_ptr->size = cpu_to_le16(S_DS_GEN); + break; + case HostCmd_CMD_FUNC_SHUTDOWN: + priv->adapter->hw_status = MWIFIEX_HW_STATUS_RESET; + cmd_ptr->command = cpu_to_le16(cmd_no); + cmd_ptr->size = cpu_to_le16(S_DS_GEN); + break; + case HostCmd_CMD_11N_ADDBA_REQ: + ret = mwifiex_cmd_11n_addba_req(cmd_ptr, data_buf); + break; + case HostCmd_CMD_11N_DELBA: + ret = mwifiex_cmd_11n_delba(cmd_ptr, data_buf); + break; + case HostCmd_CMD_11N_ADDBA_RSP: + ret = mwifiex_cmd_11n_addba_rsp_gen(priv, cmd_ptr, data_buf); + break; + case HostCmd_CMD_802_11_KEY_MATERIAL: + ret = mwifiex_cmd_802_11_key_material(priv, cmd_ptr, + cmd_action, cmd_oid, + data_buf); + break; + case HostCmd_CMD_802_11D_DOMAIN_INFO: + ret = mwifiex_cmd_802_11d_domain_info(priv, cmd_ptr, + cmd_action); + break; + case HostCmd_CMD_RECONFIGURE_TX_BUFF: + ret = mwifiex_cmd_recfg_tx_buf(priv, cmd_ptr, cmd_action, + data_buf); + break; + case HostCmd_CMD_AMSDU_AGGR_CTRL: + ret = mwifiex_cmd_amsdu_aggr_ctrl(cmd_ptr, cmd_action, + data_buf); + break; + case HostCmd_CMD_11N_CFG: + ret = mwifiex_cmd_11n_cfg(priv, cmd_ptr, cmd_action, data_buf); + break; + case HostCmd_CMD_WMM_GET_STATUS: + dev_dbg(priv->adapter->dev, + "cmd: WMM: WMM_GET_STATUS cmd sent\n"); + cmd_ptr->command = cpu_to_le16(HostCmd_CMD_WMM_GET_STATUS); + cmd_ptr->size = + cpu_to_le16(sizeof(struct host_cmd_ds_wmm_get_status) + + S_DS_GEN); + ret = 0; + break; + case HostCmd_CMD_802_11_IBSS_COALESCING_STATUS: + ret = mwifiex_cmd_ibss_coalescing_status(cmd_ptr, cmd_action, + data_buf); + break; + case HostCmd_CMD_MAC_REG_ACCESS: + case HostCmd_CMD_BBP_REG_ACCESS: + case HostCmd_CMD_RF_REG_ACCESS: + case HostCmd_CMD_PMIC_REG_ACCESS: + case HostCmd_CMD_CAU_REG_ACCESS: + case HostCmd_CMD_802_11_EEPROM_ACCESS: + ret = mwifiex_cmd_reg_access(cmd_ptr, cmd_action, data_buf); + break; + case HostCmd_CMD_SET_BSS_MODE: + cmd_ptr->command = cpu_to_le16(cmd_no); + if (priv->bss_mode == NL80211_IFTYPE_ADHOC) + cmd_ptr->params.bss_mode.con_type = + CONNECTION_TYPE_ADHOC; + else if (priv->bss_mode == NL80211_IFTYPE_STATION) + cmd_ptr->params.bss_mode.con_type = + CONNECTION_TYPE_INFRA; + else if (priv->bss_mode == NL80211_IFTYPE_AP) + cmd_ptr->params.bss_mode.con_type = CONNECTION_TYPE_AP; + cmd_ptr->size = cpu_to_le16(sizeof(struct + host_cmd_ds_set_bss_mode) + S_DS_GEN); + ret = 0; + break; + case HostCmd_CMD_PCIE_DESC_DETAILS: + ret = mwifiex_cmd_pcie_host_spec(priv, cmd_ptr, cmd_action); + break; + case HostCmd_CMD_802_11_SUBSCRIBE_EVENT: + ret = mwifiex_cmd_802_11_subsc_evt(priv, cmd_ptr, data_buf); + break; + case HostCmd_CMD_MEF_CFG: + ret = mwifiex_cmd_mef_cfg(priv, cmd_ptr, data_buf); + break; + default: + dev_err(priv->adapter->dev, + "PREP_CMD: unknown cmd- %#x\n", cmd_no); + ret = -1; + break; + } + return ret; +} + +/* + * This function issues commands to initialize firmware. + * + * This is called after firmware download to bring the card to + * working state. + * + * The following commands are issued sequentially - + * - Set PCI-Express host buffer configuration (PCIE only) + * - Function init (for first interface only) + * - Read MAC address (for first interface only) + * - Reconfigure Tx buffer size (for first interface only) + * - Enable auto deep sleep (for first interface only) + * - Get Tx rate + * - Get Tx power + * - Set IBSS coalescing status + * - Set AMSDU aggregation control + * - Set 11d control + * - Set MAC control (this must be the last command to initialize firmware) + */ +int mwifiex_sta_init_cmd(struct mwifiex_private *priv, u8 first_sta) +{ + int ret; + u16 enable = true; + struct mwifiex_ds_11n_amsdu_aggr_ctrl amsdu_aggr_ctrl; + struct mwifiex_ds_auto_ds auto_ds; + enum state_11d_t state_11d; + struct mwifiex_ds_11n_tx_cfg tx_cfg; + + if (first_sta) { + if (priv->adapter->iface_type == MWIFIEX_PCIE) { + ret = mwifiex_send_cmd_sync(priv, + HostCmd_CMD_PCIE_DESC_DETAILS, + HostCmd_ACT_GEN_SET, 0, NULL); + if (ret) + return -1; + } + + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_FUNC_INIT, + HostCmd_ACT_GEN_SET, 0, NULL); + if (ret) + return -1; + /* Read MAC address from HW */ + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_GET_HW_SPEC, + HostCmd_ACT_GEN_GET, 0, NULL); + if (ret) + return -1; + + /* Reconfigure tx buf size */ + ret = mwifiex_send_cmd_sync(priv, + HostCmd_CMD_RECONFIGURE_TX_BUFF, + HostCmd_ACT_GEN_SET, 0, + &priv->adapter->tx_buf_size); + if (ret) + return -1; + + if (priv->bss_type != MWIFIEX_BSS_TYPE_UAP) { + /* Enable IEEE PS by default */ + priv->adapter->ps_mode = MWIFIEX_802_11_POWER_MODE_PSP; + ret = mwifiex_send_cmd_sync( + priv, HostCmd_CMD_802_11_PS_MODE_ENH, + EN_AUTO_PS, BITMAP_STA_PS, NULL); + if (ret) + return -1; + } + } + + /* get tx rate */ + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_TX_RATE_CFG, + HostCmd_ACT_GEN_GET, 0, NULL); + if (ret) + return -1; + priv->data_rate = 0; + + /* get tx power */ + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_RF_TX_PWR, + HostCmd_ACT_GEN_GET, 0, NULL); + if (ret) + return -1; + + if (priv->bss_type == MWIFIEX_BSS_TYPE_STA) { + /* set ibss coalescing_status */ + ret = mwifiex_send_cmd_sync( + priv, HostCmd_CMD_802_11_IBSS_COALESCING_STATUS, + HostCmd_ACT_GEN_SET, 0, &enable); + if (ret) + return -1; + } + + memset(&amsdu_aggr_ctrl, 0, sizeof(amsdu_aggr_ctrl)); + amsdu_aggr_ctrl.enable = true; + /* Send request to firmware */ + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_AMSDU_AGGR_CTRL, + HostCmd_ACT_GEN_SET, 0, + &amsdu_aggr_ctrl); + if (ret) + return -1; + /* MAC Control must be the last command in init_fw */ + /* set MAC Control */ + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_MAC_CONTROL, + HostCmd_ACT_GEN_SET, 0, + &priv->curr_pkt_filter); + if (ret) + return -1; + + if (first_sta && priv->adapter->iface_type != MWIFIEX_USB && + priv->bss_type != MWIFIEX_BSS_TYPE_UAP) { + /* Enable auto deep sleep */ + auto_ds.auto_ds = DEEP_SLEEP_ON; + auto_ds.idle_time = DEEP_SLEEP_IDLE_TIME; + ret = mwifiex_send_cmd_sync(priv, + HostCmd_CMD_802_11_PS_MODE_ENH, + EN_AUTO_PS, BITMAP_AUTO_DS, + &auto_ds); + if (ret) + return -1; + } + + if (priv->bss_type != MWIFIEX_BSS_TYPE_UAP) { + /* Send cmd to FW to enable/disable 11D function */ + state_11d = ENABLE_11D; + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_802_11_SNMP_MIB, + HostCmd_ACT_GEN_SET, DOT11D_I, + &state_11d); + if (ret) + dev_err(priv->adapter->dev, + "11D: failed to enable 11D\n"); + } + + /* set last_init_cmd before sending the command */ + priv->adapter->last_init_cmd = HostCmd_CMD_11N_CFG; + + /* Send cmd to FW to configure 11n specific configuration + * (Short GI, Channel BW, Green field support etc.) for transmit + */ + tx_cfg.tx_htcap = MWIFIEX_FW_DEF_HTTXCFG; + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_11N_CFG, + HostCmd_ACT_GEN_SET, 0, &tx_cfg); + + ret = -EINPROGRESS; + + return ret; +} diff --git a/drivers/net/wireless/mwifiex/sta_cmdresp.c b/drivers/net/wireless/mwifiex/sta_cmdresp.c new file mode 100644 index 0000000..9f990e1 --- /dev/null +++ b/drivers/net/wireless/mwifiex/sta_cmdresp.c @@ -0,0 +1,990 @@ +/* + * Marvell Wireless LAN device driver: station command response handling + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" +#include "11ac.h" + + +/* + * This function handles the command response error case. + * + * For scan response error, the function cancels all the pending + * scan commands and generates an event to inform the applications + * of the scan completion. + * + * For Power Save command failure, we do not retry enter PS + * command in case of Ad-hoc mode. + * + * For all other response errors, the current command buffer is freed + * and returned to the free command queue. + */ +static void +mwifiex_process_cmdresp_error(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + struct cmd_ctrl_node *cmd_node = NULL, *tmp_node; + struct mwifiex_adapter *adapter = priv->adapter; + struct host_cmd_ds_802_11_ps_mode_enh *pm; + unsigned long flags; + + dev_err(adapter->dev, "CMD_RESP: cmd %#x error, result=%#x\n", + resp->command, resp->result); + + if (adapter->curr_cmd->wait_q_enabled) + adapter->cmd_wait_q.status = -1; + + switch (le16_to_cpu(resp->command)) { + case HostCmd_CMD_802_11_PS_MODE_ENH: + pm = &resp->params.psmode_enh; + dev_err(adapter->dev, + "PS_MODE_ENH cmd failed: result=0x%x action=0x%X\n", + resp->result, le16_to_cpu(pm->action)); + /* We do not re-try enter-ps command in ad-hoc mode. */ + if (le16_to_cpu(pm->action) == EN_AUTO_PS && + (le16_to_cpu(pm->params.ps_bitmap) & BITMAP_STA_PS) && + priv->bss_mode == NL80211_IFTYPE_ADHOC) + adapter->ps_mode = MWIFIEX_802_11_POWER_MODE_CAM; + + break; + case HostCmd_CMD_802_11_SCAN: + /* Cancel all pending scan command */ + spin_lock_irqsave(&adapter->scan_pending_q_lock, flags); + list_for_each_entry_safe(cmd_node, tmp_node, + &adapter->scan_pending_q, list) { + list_del(&cmd_node->list); + spin_unlock_irqrestore(&adapter->scan_pending_q_lock, + flags); + mwifiex_insert_cmd_to_free_q(adapter, cmd_node); + spin_lock_irqsave(&adapter->scan_pending_q_lock, flags); + } + spin_unlock_irqrestore(&adapter->scan_pending_q_lock, flags); + + spin_lock_irqsave(&adapter->mwifiex_cmd_lock, flags); + adapter->scan_processing = false; + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, flags); + if (priv->report_scan_result) + priv->report_scan_result = false; + break; + + case HostCmd_CMD_MAC_CONTROL: + break; + + default: + break; + } + /* Handling errors here */ + mwifiex_recycle_cmd_node(adapter, adapter->curr_cmd); + + spin_lock_irqsave(&adapter->mwifiex_cmd_lock, flags); + adapter->curr_cmd = NULL; + spin_unlock_irqrestore(&adapter->mwifiex_cmd_lock, flags); +} + +/* + * This function handles the command response of get RSSI info. + * + * Handling includes changing the header fields into CPU format + * and saving the following parameters in driver - + * - Last data and beacon RSSI value + * - Average data and beacon RSSI value + * - Last data and beacon NF value + * - Average data and beacon NF value + * + * The parameters are send to the application as well, along with + * calculated SNR values. + */ +static int mwifiex_ret_802_11_rssi_info(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + struct host_cmd_ds_802_11_rssi_info_rsp *rssi_info_rsp = + &resp->params.rssi_info_rsp; + struct mwifiex_ds_misc_subsc_evt *subsc_evt = + &priv->async_subsc_evt_storage; + + priv->data_rssi_last = le16_to_cpu(rssi_info_rsp->data_rssi_last); + priv->data_nf_last = le16_to_cpu(rssi_info_rsp->data_nf_last); + + priv->data_rssi_avg = le16_to_cpu(rssi_info_rsp->data_rssi_avg); + priv->data_nf_avg = le16_to_cpu(rssi_info_rsp->data_nf_avg); + + priv->bcn_rssi_last = le16_to_cpu(rssi_info_rsp->bcn_rssi_last); + priv->bcn_nf_last = le16_to_cpu(rssi_info_rsp->bcn_nf_last); + + priv->bcn_rssi_avg = le16_to_cpu(rssi_info_rsp->bcn_rssi_avg); + priv->bcn_nf_avg = le16_to_cpu(rssi_info_rsp->bcn_nf_avg); + + if (priv->subsc_evt_rssi_state == EVENT_HANDLED) + return 0; + + memset(subsc_evt, 0x00, sizeof(struct mwifiex_ds_misc_subsc_evt)); + + /* Resubscribe low and high rssi events with new thresholds */ + subsc_evt->events = BITMASK_BCN_RSSI_LOW | BITMASK_BCN_RSSI_HIGH; + subsc_evt->action = HostCmd_ACT_BITWISE_SET; + if (priv->subsc_evt_rssi_state == RSSI_LOW_RECVD) { + subsc_evt->bcn_l_rssi_cfg.abs_value = abs(priv->bcn_rssi_avg - + priv->cqm_rssi_hyst); + subsc_evt->bcn_h_rssi_cfg.abs_value = abs(priv->cqm_rssi_thold); + } else if (priv->subsc_evt_rssi_state == RSSI_HIGH_RECVD) { + subsc_evt->bcn_l_rssi_cfg.abs_value = abs(priv->cqm_rssi_thold); + subsc_evt->bcn_h_rssi_cfg.abs_value = abs(priv->bcn_rssi_avg + + priv->cqm_rssi_hyst); + } + subsc_evt->bcn_l_rssi_cfg.evt_freq = 1; + subsc_evt->bcn_h_rssi_cfg.evt_freq = 1; + + priv->subsc_evt_rssi_state = EVENT_HANDLED; + + mwifiex_send_cmd_async(priv, HostCmd_CMD_802_11_SUBSCRIBE_EVENT, + 0, 0, subsc_evt); + + return 0; +} + +/* + * This function handles the command response of set/get SNMP + * MIB parameters. + * + * Handling includes changing the header fields into CPU format + * and saving the parameter in driver. + * + * The following parameters are supported - + * - Fragmentation threshold + * - RTS threshold + * - Short retry limit + */ +static int mwifiex_ret_802_11_snmp_mib(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp, + u32 *data_buf) +{ + struct host_cmd_ds_802_11_snmp_mib *smib = &resp->params.smib; + u16 oid = le16_to_cpu(smib->oid); + u16 query_type = le16_to_cpu(smib->query_type); + u32 ul_temp; + + dev_dbg(priv->adapter->dev, "info: SNMP_RESP: oid value = %#x," + " query_type = %#x, buf size = %#x\n", + oid, query_type, le16_to_cpu(smib->buf_size)); + if (query_type == HostCmd_ACT_GEN_GET) { + ul_temp = le16_to_cpu(*((__le16 *) (smib->value))); + if (data_buf) + *data_buf = ul_temp; + switch (oid) { + case FRAG_THRESH_I: + dev_dbg(priv->adapter->dev, + "info: SNMP_RESP: FragThsd =%u\n", ul_temp); + break; + case RTS_THRESH_I: + dev_dbg(priv->adapter->dev, + "info: SNMP_RESP: RTSThsd =%u\n", ul_temp); + break; + case SHORT_RETRY_LIM_I: + dev_dbg(priv->adapter->dev, + "info: SNMP_RESP: TxRetryCount=%u\n", ul_temp); + break; + case DTIM_PERIOD_I: + dev_dbg(priv->adapter->dev, + "info: SNMP_RESP: DTIM period=%u\n", ul_temp); + default: + break; + } + } + + return 0; +} + +/* + * This function handles the command response of get log request + * + * Handling includes changing the header fields into CPU format + * and sending the received parameters to application. + */ +static int mwifiex_ret_get_log(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp, + struct mwifiex_ds_get_stats *stats) +{ + struct host_cmd_ds_802_11_get_log *get_log = + &resp->params.get_log; + + if (stats) { + stats->mcast_tx_frame = le32_to_cpu(get_log->mcast_tx_frame); + stats->failed = le32_to_cpu(get_log->failed); + stats->retry = le32_to_cpu(get_log->retry); + stats->multi_retry = le32_to_cpu(get_log->multi_retry); + stats->frame_dup = le32_to_cpu(get_log->frame_dup); + stats->rts_success = le32_to_cpu(get_log->rts_success); + stats->rts_failure = le32_to_cpu(get_log->rts_failure); + stats->ack_failure = le32_to_cpu(get_log->ack_failure); + stats->rx_frag = le32_to_cpu(get_log->rx_frag); + stats->mcast_rx_frame = le32_to_cpu(get_log->mcast_rx_frame); + stats->fcs_error = le32_to_cpu(get_log->fcs_error); + stats->tx_frame = le32_to_cpu(get_log->tx_frame); + stats->wep_icv_error[0] = + le32_to_cpu(get_log->wep_icv_err_cnt[0]); + stats->wep_icv_error[1] = + le32_to_cpu(get_log->wep_icv_err_cnt[1]); + stats->wep_icv_error[2] = + le32_to_cpu(get_log->wep_icv_err_cnt[2]); + stats->wep_icv_error[3] = + le32_to_cpu(get_log->wep_icv_err_cnt[3]); + } + + return 0; +} + +/* + * This function handles the command response of set/get Tx rate + * configurations. + * + * Handling includes changing the header fields into CPU format + * and saving the following parameters in driver - + * - DSSS rate bitmap + * - OFDM rate bitmap + * - HT MCS rate bitmaps + * + * Based on the new rate bitmaps, the function re-evaluates if + * auto data rate has been activated. If not, it sends another + * query to the firmware to get the current Tx data rate. + */ +static int mwifiex_ret_tx_rate_cfg(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + struct host_cmd_ds_tx_rate_cfg *rate_cfg = &resp->params.tx_rate_cfg; + struct mwifiex_rate_scope *rate_scope; + struct mwifiex_ie_types_header *head; + u16 tlv, tlv_buf_len; + u8 *tlv_buf; + u32 i; + + tlv_buf = ((u8 *)rate_cfg) + + sizeof(struct host_cmd_ds_tx_rate_cfg); + tlv_buf_len = *(u16 *) (tlv_buf + sizeof(u16)); + + while (tlv_buf && tlv_buf_len > 0) { + tlv = (*tlv_buf); + tlv = tlv | (*(tlv_buf + 1) << 8); + + switch (tlv) { + case TLV_TYPE_RATE_SCOPE: + rate_scope = (struct mwifiex_rate_scope *) tlv_buf; + priv->bitmap_rates[0] = + le16_to_cpu(rate_scope->hr_dsss_rate_bitmap); + priv->bitmap_rates[1] = + le16_to_cpu(rate_scope->ofdm_rate_bitmap); + for (i = 0; + i < + sizeof(rate_scope->ht_mcs_rate_bitmap) / + sizeof(u16); i++) + priv->bitmap_rates[2 + i] = + le16_to_cpu(rate_scope-> + ht_mcs_rate_bitmap[i]); + break; + /* Add RATE_DROP tlv here */ + } + + head = (struct mwifiex_ie_types_header *) tlv_buf; + tlv_buf += le16_to_cpu(head->len) + sizeof(*head); + tlv_buf_len -= le16_to_cpu(head->len); + } + + priv->is_data_rate_auto = mwifiex_is_rate_auto(priv); + + if (priv->is_data_rate_auto) + priv->data_rate = 0; + else + return mwifiex_send_cmd_async(priv, + HostCmd_CMD_802_11_TX_RATE_QUERY, + HostCmd_ACT_GEN_GET, 0, NULL); + + return 0; +} + +/* + * This function handles the command response of get Tx power level. + * + * Handling includes saving the maximum and minimum Tx power levels + * in driver, as well as sending the values to user. + */ +static int mwifiex_get_power_level(struct mwifiex_private *priv, void *data_buf) +{ + int length, max_power = -1, min_power = -1; + struct mwifiex_types_power_group *pg_tlv_hdr; + struct mwifiex_power_group *pg; + + if (!data_buf) + return -1; + + pg_tlv_hdr = (struct mwifiex_types_power_group *) + ((u8 *) data_buf + sizeof(struct host_cmd_ds_txpwr_cfg)); + pg = (struct mwifiex_power_group *) + ((u8 *) pg_tlv_hdr + sizeof(struct mwifiex_types_power_group)); + length = pg_tlv_hdr->length; + if (length > 0) { + max_power = pg->power_max; + min_power = pg->power_min; + length -= sizeof(struct mwifiex_power_group); + } + while (length) { + pg++; + if (max_power < pg->power_max) + max_power = pg->power_max; + + if (min_power > pg->power_min) + min_power = pg->power_min; + + length -= sizeof(struct mwifiex_power_group); + } + if (pg_tlv_hdr->length > 0) { + priv->min_tx_power_level = (u8) min_power; + priv->max_tx_power_level = (u8) max_power; + } + + return 0; +} + +/* + * This function handles the command response of set/get Tx power + * configurations. + * + * Handling includes changing the header fields into CPU format + * and saving the current Tx power level in driver. + */ +static int mwifiex_ret_tx_power_cfg(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct host_cmd_ds_txpwr_cfg *txp_cfg = &resp->params.txp_cfg; + struct mwifiex_types_power_group *pg_tlv_hdr; + struct mwifiex_power_group *pg; + u16 action = le16_to_cpu(txp_cfg->action); + + switch (action) { + case HostCmd_ACT_GEN_GET: + pg_tlv_hdr = (struct mwifiex_types_power_group *) + ((u8 *) txp_cfg + + sizeof(struct host_cmd_ds_txpwr_cfg)); + + pg = (struct mwifiex_power_group *) + ((u8 *) pg_tlv_hdr + + sizeof(struct mwifiex_types_power_group)); + + if (adapter->hw_status == MWIFIEX_HW_STATUS_INITIALIZING) + mwifiex_get_power_level(priv, txp_cfg); + + priv->tx_power_level = (u16) pg->power_min; + break; + + case HostCmd_ACT_GEN_SET: + if (!le32_to_cpu(txp_cfg->mode)) + break; + + pg_tlv_hdr = (struct mwifiex_types_power_group *) + ((u8 *) txp_cfg + + sizeof(struct host_cmd_ds_txpwr_cfg)); + + pg = (struct mwifiex_power_group *) + ((u8 *) pg_tlv_hdr + + sizeof(struct mwifiex_types_power_group)); + + if (pg->power_max == pg->power_min) + priv->tx_power_level = (u16) pg->power_min; + break; + default: + dev_err(adapter->dev, "CMD_RESP: unknown cmd action %d\n", + action); + return 0; + } + dev_dbg(adapter->dev, + "info: Current TxPower Level = %d, Max Power=%d, Min Power=%d\n", + priv->tx_power_level, priv->max_tx_power_level, + priv->min_tx_power_level); + + return 0; +} + +/* + * This function handles the command response of get RF Tx power. + */ +static int mwifiex_ret_rf_tx_power(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + struct host_cmd_ds_rf_tx_pwr *txp = &resp->params.txp; + u16 action = le16_to_cpu(txp->action); + + priv->tx_power_level = le16_to_cpu(txp->cur_level); + + if (action == HostCmd_ACT_GEN_GET) { + priv->max_tx_power_level = txp->max_power; + priv->min_tx_power_level = txp->min_power; + } + + dev_dbg(priv->adapter->dev, + "Current TxPower Level=%d, Max Power=%d, Min Power=%d\n", + priv->tx_power_level, priv->max_tx_power_level, + priv->min_tx_power_level); + + return 0; +} + +/* + * This function handles the command response of set rf antenna + */ +static int mwifiex_ret_rf_antenna(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + struct host_cmd_ds_rf_ant_mimo *ant_mimo = &resp->params.ant_mimo; + struct host_cmd_ds_rf_ant_siso *ant_siso = &resp->params.ant_siso; + struct mwifiex_adapter *adapter = priv->adapter; + + if (adapter->hw_dev_mcs_support == HT_STREAM_2X2) + dev_dbg(adapter->dev, + "RF_ANT_RESP: Tx action = 0x%x, Tx Mode = 0x%04x" + " Rx action = 0x%x, Rx Mode = 0x%04x\n", + le16_to_cpu(ant_mimo->action_tx), + le16_to_cpu(ant_mimo->tx_ant_mode), + le16_to_cpu(ant_mimo->action_rx), + le16_to_cpu(ant_mimo->rx_ant_mode)); + else + dev_dbg(adapter->dev, + "RF_ANT_RESP: action = 0x%x, Mode = 0x%04x\n", + le16_to_cpu(ant_siso->action), + le16_to_cpu(ant_siso->ant_mode)); + + return 0; +} + +/* + * This function handles the command response of set/get MAC address. + * + * Handling includes saving the MAC address in driver. + */ +static int mwifiex_ret_802_11_mac_address(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + struct host_cmd_ds_802_11_mac_address *cmd_mac_addr = + &resp->params.mac_addr; + + memcpy(priv->curr_addr, cmd_mac_addr->mac_addr, ETH_ALEN); + + dev_dbg(priv->adapter->dev, + "info: set mac address: %pM\n", priv->curr_addr); + + return 0; +} + +/* + * This function handles the command response of set/get MAC multicast + * address. + */ +static int mwifiex_ret_mac_multicast_adr(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + return 0; +} + +/* + * This function handles the command response of get Tx rate query. + * + * Handling includes changing the header fields into CPU format + * and saving the Tx rate and HT information parameters in driver. + * + * Both rate configuration and current data rate can be retrieved + * with this request. + */ +static int mwifiex_ret_802_11_tx_rate_query(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + priv->tx_rate = resp->params.tx_rate.tx_rate; + priv->tx_htinfo = resp->params.tx_rate.ht_info; + if (!priv->is_data_rate_auto) + priv->data_rate = + mwifiex_index_to_data_rate(priv, priv->tx_rate, + priv->tx_htinfo); + + return 0; +} + +/* + * This function handles the command response of a deauthenticate + * command. + * + * If the deauthenticated MAC matches the current BSS MAC, the connection + * state is reset. + */ +static int mwifiex_ret_802_11_deauthenticate(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + struct mwifiex_adapter *adapter = priv->adapter; + + adapter->dbg.num_cmd_deauth++; + if (!memcmp(resp->params.deauth.mac_addr, + &priv->curr_bss_params.bss_descriptor.mac_address, + sizeof(resp->params.deauth.mac_addr))) + mwifiex_reset_connect_state(priv, WLAN_REASON_DEAUTH_LEAVING); + + return 0; +} + +/* + * This function handles the command response of ad-hoc stop. + * + * The function resets the connection state in driver. + */ +static int mwifiex_ret_802_11_ad_hoc_stop(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + mwifiex_reset_connect_state(priv, WLAN_REASON_DEAUTH_LEAVING); + return 0; +} + +/* + * This function handles the command response of set/get key material. + * + * Handling includes updating the driver parameters to reflect the + * changes. + */ +static int mwifiex_ret_802_11_key_material(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + struct host_cmd_ds_802_11_key_material *key = + &resp->params.key_material; + + if (le16_to_cpu(key->action) == HostCmd_ACT_GEN_SET) { + if ((le16_to_cpu(key->key_param_set.key_info) & KEY_MCAST)) { + dev_dbg(priv->adapter->dev, "info: key: GTK is set\n"); + priv->wpa_is_gtk_set = true; + priv->scan_block = false; + } + } + + memset(priv->aes_key.key_param_set.key, 0, + sizeof(key->key_param_set.key)); + priv->aes_key.key_param_set.key_len = key->key_param_set.key_len; + memcpy(priv->aes_key.key_param_set.key, key->key_param_set.key, + le16_to_cpu(priv->aes_key.key_param_set.key_len)); + + return 0; +} + +/* + * This function handles the command response of get 11d domain information. + */ +static int mwifiex_ret_802_11d_domain_info(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + struct host_cmd_ds_802_11d_domain_info_rsp *domain_info = + &resp->params.domain_info_resp; + struct mwifiex_ietypes_domain_param_set *domain = &domain_info->domain; + u16 action = le16_to_cpu(domain_info->action); + u8 no_of_triplet; + + no_of_triplet = (u8) ((le16_to_cpu(domain->header.len) + - IEEE80211_COUNTRY_STRING_LEN) + / sizeof(struct ieee80211_country_ie_triplet)); + + dev_dbg(priv->adapter->dev, + "info: 11D Domain Info Resp: no_of_triplet=%d\n", + no_of_triplet); + + if (no_of_triplet > MWIFIEX_MAX_TRIPLET_802_11D) { + dev_warn(priv->adapter->dev, + "11D: invalid number of triplets %d returned\n", + no_of_triplet); + return -1; + } + + switch (action) { + case HostCmd_ACT_GEN_SET: /* Proc Set Action */ + break; + case HostCmd_ACT_GEN_GET: + break; + default: + dev_err(priv->adapter->dev, + "11D: invalid action:%d\n", domain_info->action); + return -1; + } + + return 0; +} + +/* + * This function handles the command response of get extended version. + * + * Handling includes forming the extended version string and sending it + * to application. + */ +static int mwifiex_ret_ver_ext(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp, + struct host_cmd_ds_version_ext *version_ext) +{ + struct host_cmd_ds_version_ext *ver_ext = &resp->params.verext; + + if (version_ext) { + version_ext->version_str_sel = ver_ext->version_str_sel; + memcpy(version_ext->version_str, ver_ext->version_str, + sizeof(char) * 128); + memcpy(priv->version_str, ver_ext->version_str, 128); + } + return 0; +} + +/* + * This function handles the command response of remain on channel. + */ +static int +mwifiex_ret_remain_on_chan(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp, + struct host_cmd_ds_remain_on_chan *roc_cfg) +{ + struct host_cmd_ds_remain_on_chan *resp_cfg = &resp->params.roc_cfg; + + if (roc_cfg) + memcpy(roc_cfg, resp_cfg, sizeof(*roc_cfg)); + + return 0; +} + +/* + * This function handles the command response of P2P mode cfg. + */ +static int +mwifiex_ret_p2p_mode_cfg(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp, + void *data_buf) +{ + struct host_cmd_ds_p2p_mode_cfg *mode_cfg = &resp->params.mode_cfg; + + if (data_buf) + *((u16 *)data_buf) = le16_to_cpu(mode_cfg->mode); + + return 0; +} + +/* + * This function handles the command response of register access. + * + * The register value and offset are returned to the user. For EEPROM + * access, the byte count is also returned. + */ +static int mwifiex_ret_reg_access(u16 type, struct host_cmd_ds_command *resp, + void *data_buf) +{ + struct mwifiex_ds_reg_rw *reg_rw; + struct mwifiex_ds_read_eeprom *eeprom; + union reg { + struct host_cmd_ds_mac_reg_access *mac; + struct host_cmd_ds_bbp_reg_access *bbp; + struct host_cmd_ds_rf_reg_access *rf; + struct host_cmd_ds_pmic_reg_access *pmic; + struct host_cmd_ds_802_11_eeprom_access *eeprom; + } r; + + if (!data_buf) + return 0; + + reg_rw = data_buf; + eeprom = data_buf; + switch (type) { + case HostCmd_CMD_MAC_REG_ACCESS: + r.mac = &resp->params.mac_reg; + reg_rw->offset = cpu_to_le32((u32) le16_to_cpu(r.mac->offset)); + reg_rw->value = r.mac->value; + break; + case HostCmd_CMD_BBP_REG_ACCESS: + r.bbp = &resp->params.bbp_reg; + reg_rw->offset = cpu_to_le32((u32) le16_to_cpu(r.bbp->offset)); + reg_rw->value = cpu_to_le32((u32) r.bbp->value); + break; + + case HostCmd_CMD_RF_REG_ACCESS: + r.rf = &resp->params.rf_reg; + reg_rw->offset = cpu_to_le32((u32) le16_to_cpu(r.rf->offset)); + reg_rw->value = cpu_to_le32((u32) r.bbp->value); + break; + case HostCmd_CMD_PMIC_REG_ACCESS: + r.pmic = &resp->params.pmic_reg; + reg_rw->offset = cpu_to_le32((u32) le16_to_cpu(r.pmic->offset)); + reg_rw->value = cpu_to_le32((u32) r.pmic->value); + break; + case HostCmd_CMD_CAU_REG_ACCESS: + r.rf = &resp->params.rf_reg; + reg_rw->offset = cpu_to_le32((u32) le16_to_cpu(r.rf->offset)); + reg_rw->value = cpu_to_le32((u32) r.rf->value); + break; + case HostCmd_CMD_802_11_EEPROM_ACCESS: + r.eeprom = &resp->params.eeprom; + pr_debug("info: EEPROM read len=%x\n", r.eeprom->byte_count); + if (le16_to_cpu(eeprom->byte_count) < + le16_to_cpu(r.eeprom->byte_count)) { + eeprom->byte_count = cpu_to_le16(0); + pr_debug("info: EEPROM read length is too big\n"); + return -1; + } + eeprom->offset = r.eeprom->offset; + eeprom->byte_count = r.eeprom->byte_count; + if (le16_to_cpu(eeprom->byte_count) > 0) + memcpy(&eeprom->value, &r.eeprom->value, + le16_to_cpu(r.eeprom->byte_count)); + + break; + default: + return -1; + } + return 0; +} + +/* + * This function handles the command response of get IBSS coalescing status. + * + * If the received BSSID is different than the current one, the current BSSID, + * beacon interval, ATIM window and ERP information are updated, along with + * changing the ad-hoc state accordingly. + */ +static int mwifiex_ret_ibss_coalescing_status(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + struct host_cmd_ds_802_11_ibss_status *ibss_coal_resp = + &(resp->params.ibss_coalescing); + + if (le16_to_cpu(ibss_coal_resp->action) == HostCmd_ACT_GEN_SET) + return 0; + + dev_dbg(priv->adapter->dev, + "info: new BSSID %pM\n", ibss_coal_resp->bssid); + + /* If rsp has NULL BSSID, Just return..... No Action */ + if (is_zero_ether_addr(ibss_coal_resp->bssid)) { + dev_warn(priv->adapter->dev, "new BSSID is NULL\n"); + return 0; + } + + /* If BSSID is diff, modify current BSS parameters */ + if (memcmp(priv->curr_bss_params.bss_descriptor.mac_address, + ibss_coal_resp->bssid, ETH_ALEN)) { + /* BSSID */ + memcpy(priv->curr_bss_params.bss_descriptor.mac_address, + ibss_coal_resp->bssid, ETH_ALEN); + + /* Beacon Interval */ + priv->curr_bss_params.bss_descriptor.beacon_period + = le16_to_cpu(ibss_coal_resp->beacon_interval); + + /* ERP Information */ + priv->curr_bss_params.bss_descriptor.erp_flags = + (u8) le16_to_cpu(ibss_coal_resp->use_g_rate_protect); + + priv->adhoc_state = ADHOC_COALESCED; + } + + return 0; +} + +/* + * This function handles the command response for subscribe event command. + */ +static int mwifiex_ret_subsc_evt(struct mwifiex_private *priv, + struct host_cmd_ds_command *resp) +{ + struct host_cmd_ds_802_11_subsc_evt *cmd_sub_event = + &resp->params.subsc_evt; + + /* For every subscribe event command (Get/Set/Clear), FW reports the + * current set of subscribed events*/ + dev_dbg(priv->adapter->dev, "Bitmap of currently subscribed events: %16x\n", + le16_to_cpu(cmd_sub_event->events)); + + return 0; +} + +/* + * This function handles the command responses. + * + * This is a generic function, which calls command specific + * response handlers based on the command ID. + */ +int mwifiex_process_sta_cmdresp(struct mwifiex_private *priv, u16 cmdresp_no, + struct host_cmd_ds_command *resp) +{ + int ret = 0; + struct mwifiex_adapter *adapter = priv->adapter; + void *data_buf = adapter->curr_cmd->data_buf; + + /* If the command is not successful, cleanup and return failure */ + if (resp->result != HostCmd_RESULT_OK) { + mwifiex_process_cmdresp_error(priv, resp); + return -1; + } + /* Command successful, handle response */ + switch (cmdresp_no) { + case HostCmd_CMD_GET_HW_SPEC: + ret = mwifiex_ret_get_hw_spec(priv, resp); + break; + case HostCmd_CMD_MAC_CONTROL: + break; + case HostCmd_CMD_802_11_MAC_ADDRESS: + ret = mwifiex_ret_802_11_mac_address(priv, resp); + break; + case HostCmd_CMD_MAC_MULTICAST_ADR: + ret = mwifiex_ret_mac_multicast_adr(priv, resp); + break; + case HostCmd_CMD_TX_RATE_CFG: + ret = mwifiex_ret_tx_rate_cfg(priv, resp); + break; + case HostCmd_CMD_802_11_SCAN: + ret = mwifiex_ret_802_11_scan(priv, resp); + adapter->curr_cmd->wait_q_enabled = false; + break; + case HostCmd_CMD_802_11_BG_SCAN_QUERY: + ret = mwifiex_ret_802_11_scan(priv, resp); + dev_dbg(adapter->dev, + "info: CMD_RESP: BG_SCAN result is ready!\n"); + break; + case HostCmd_CMD_TXPWR_CFG: + ret = mwifiex_ret_tx_power_cfg(priv, resp); + break; + case HostCmd_CMD_RF_TX_PWR: + ret = mwifiex_ret_rf_tx_power(priv, resp); + break; + case HostCmd_CMD_RF_ANTENNA: + ret = mwifiex_ret_rf_antenna(priv, resp); + break; + case HostCmd_CMD_802_11_PS_MODE_ENH: + ret = mwifiex_ret_enh_power_mode(priv, resp, data_buf); + break; + case HostCmd_CMD_802_11_HS_CFG_ENH: + ret = mwifiex_ret_802_11_hs_cfg(priv, resp); + break; + case HostCmd_CMD_802_11_ASSOCIATE: + ret = mwifiex_ret_802_11_associate(priv, resp); + break; + case HostCmd_CMD_802_11_DEAUTHENTICATE: + ret = mwifiex_ret_802_11_deauthenticate(priv, resp); + break; + case HostCmd_CMD_802_11_AD_HOC_START: + case HostCmd_CMD_802_11_AD_HOC_JOIN: + ret = mwifiex_ret_802_11_ad_hoc(priv, resp); + break; + case HostCmd_CMD_802_11_AD_HOC_STOP: + ret = mwifiex_ret_802_11_ad_hoc_stop(priv, resp); + break; + case HostCmd_CMD_802_11_GET_LOG: + ret = mwifiex_ret_get_log(priv, resp, data_buf); + break; + case HostCmd_CMD_RSSI_INFO: + ret = mwifiex_ret_802_11_rssi_info(priv, resp); + break; + case HostCmd_CMD_802_11_SNMP_MIB: + ret = mwifiex_ret_802_11_snmp_mib(priv, resp, data_buf); + break; + case HostCmd_CMD_802_11_TX_RATE_QUERY: + ret = mwifiex_ret_802_11_tx_rate_query(priv, resp); + break; + case HostCmd_CMD_VERSION_EXT: + ret = mwifiex_ret_ver_ext(priv, resp, data_buf); + break; + case HostCmd_CMD_REMAIN_ON_CHAN: + ret = mwifiex_ret_remain_on_chan(priv, resp, data_buf); + break; + case HostCmd_CMD_11AC_CFG: + break; + case HostCmd_CMD_P2P_MODE_CFG: + ret = mwifiex_ret_p2p_mode_cfg(priv, resp, data_buf); + break; + case HostCmd_CMD_MGMT_FRAME_REG: + case HostCmd_CMD_FUNC_INIT: + case HostCmd_CMD_FUNC_SHUTDOWN: + break; + case HostCmd_CMD_802_11_KEY_MATERIAL: + ret = mwifiex_ret_802_11_key_material(priv, resp); + break; + case HostCmd_CMD_802_11D_DOMAIN_INFO: + ret = mwifiex_ret_802_11d_domain_info(priv, resp); + break; + case HostCmd_CMD_11N_ADDBA_REQ: + ret = mwifiex_ret_11n_addba_req(priv, resp); + break; + case HostCmd_CMD_11N_DELBA: + ret = mwifiex_ret_11n_delba(priv, resp); + break; + case HostCmd_CMD_11N_ADDBA_RSP: + ret = mwifiex_ret_11n_addba_resp(priv, resp); + break; + case HostCmd_CMD_RECONFIGURE_TX_BUFF: + adapter->tx_buf_size = (u16) le16_to_cpu(resp->params. + tx_buf.buff_size); + adapter->tx_buf_size = (adapter->tx_buf_size + / MWIFIEX_SDIO_BLOCK_SIZE) + * MWIFIEX_SDIO_BLOCK_SIZE; + adapter->curr_tx_buf_size = adapter->tx_buf_size; + dev_dbg(adapter->dev, "cmd: curr_tx_buf_size=%d\n", + adapter->curr_tx_buf_size); + + if (adapter->if_ops.update_mp_end_port) + adapter->if_ops.update_mp_end_port(adapter, + le16_to_cpu(resp->params.tx_buf.mp_end_port)); + break; + case HostCmd_CMD_AMSDU_AGGR_CTRL: + break; + case HostCmd_CMD_WMM_GET_STATUS: + ret = mwifiex_ret_wmm_get_status(priv, resp); + break; + case HostCmd_CMD_802_11_IBSS_COALESCING_STATUS: + ret = mwifiex_ret_ibss_coalescing_status(priv, resp); + break; + case HostCmd_CMD_MAC_REG_ACCESS: + case HostCmd_CMD_BBP_REG_ACCESS: + case HostCmd_CMD_RF_REG_ACCESS: + case HostCmd_CMD_PMIC_REG_ACCESS: + case HostCmd_CMD_CAU_REG_ACCESS: + case HostCmd_CMD_802_11_EEPROM_ACCESS: + ret = mwifiex_ret_reg_access(cmdresp_no, resp, data_buf); + break; + case HostCmd_CMD_SET_BSS_MODE: + break; + case HostCmd_CMD_11N_CFG: + break; + case HostCmd_CMD_PCIE_DESC_DETAILS: + break; + case HostCmd_CMD_802_11_SUBSCRIBE_EVENT: + ret = mwifiex_ret_subsc_evt(priv, resp); + break; + case HostCmd_CMD_UAP_SYS_CONFIG: + break; + case HostCmd_CMD_UAP_BSS_START: + priv->bss_started = 1; + break; + case HostCmd_CMD_UAP_BSS_STOP: + priv->bss_started = 0; + break; + case HostCmd_CMD_MEF_CFG: + break; + default: + dev_err(adapter->dev, "CMD_RESP: unknown cmd response %#x\n", + resp->command); + break; + } + + return ret; +} diff --git a/drivers/net/wireless/mwifiex/sta_event.c b/drivers/net/wireless/mwifiex/sta_event.c new file mode 100644 index 0000000..41aafc7 --- /dev/null +++ b/drivers/net/wireless/mwifiex/sta_event.c @@ -0,0 +1,437 @@ +/* + * Marvell Wireless LAN device driver: station event handling + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" + +/* + * This function resets the connection state. + * + * The function is invoked after receiving a disconnect event from firmware, + * and performs the following actions - + * - Set media status to disconnected + * - Clean up Tx and Rx packets + * - Resets SNR/NF/RSSI value in driver + * - Resets security configurations in driver + * - Enables auto data rate + * - Saves the previous SSID and BSSID so that they can + * be used for re-association, if required + * - Erases current SSID and BSSID information + * - Sends a disconnect event to upper layers/applications. + */ +void +mwifiex_reset_connect_state(struct mwifiex_private *priv, u16 reason_code) +{ + struct mwifiex_adapter *adapter = priv->adapter; + + if (!priv->media_connected) + return; + + dev_dbg(adapter->dev, "info: handles disconnect event\n"); + + priv->media_connected = false; + + priv->scan_block = false; + + /* Free Tx and Rx packets, report disconnect to upper layer */ + mwifiex_clean_txrx(priv); + + /* Reset SNR/NF/RSSI values */ + priv->data_rssi_last = 0; + priv->data_nf_last = 0; + priv->data_rssi_avg = 0; + priv->data_nf_avg = 0; + priv->bcn_rssi_last = 0; + priv->bcn_nf_last = 0; + priv->bcn_rssi_avg = 0; + priv->bcn_nf_avg = 0; + priv->rxpd_rate = 0; + priv->rxpd_htinfo = 0; + priv->sec_info.wpa_enabled = false; + priv->sec_info.wpa2_enabled = false; + priv->wpa_ie_len = 0; + + priv->sec_info.wapi_enabled = false; + priv->wapi_ie_len = 0; + priv->sec_info.wapi_key_on = false; + + priv->sec_info.encryption_mode = 0; + + /* Enable auto data rate */ + priv->is_data_rate_auto = true; + priv->data_rate = 0; + + if (priv->bss_mode == NL80211_IFTYPE_ADHOC) { + priv->adhoc_state = ADHOC_IDLE; + priv->adhoc_is_link_sensed = false; + } + + /* + * Memorize the previous SSID and BSSID so + * it could be used for re-assoc + */ + + dev_dbg(adapter->dev, "info: previous SSID=%s, SSID len=%u\n", + priv->prev_ssid.ssid, priv->prev_ssid.ssid_len); + + dev_dbg(adapter->dev, "info: current SSID=%s, SSID len=%u\n", + priv->curr_bss_params.bss_descriptor.ssid.ssid, + priv->curr_bss_params.bss_descriptor.ssid.ssid_len); + + memcpy(&priv->prev_ssid, + &priv->curr_bss_params.bss_descriptor.ssid, + sizeof(struct cfg80211_ssid)); + + memcpy(priv->prev_bssid, + priv->curr_bss_params.bss_descriptor.mac_address, ETH_ALEN); + + /* Need to erase the current SSID and BSSID info */ + memset(&priv->curr_bss_params, 0x00, sizeof(priv->curr_bss_params)); + + adapter->tx_lock_flag = false; + adapter->pps_uapsd_mode = false; + + if (adapter->num_cmd_timeout && adapter->curr_cmd) + return; + priv->media_connected = false; + dev_dbg(adapter->dev, + "info: successfully disconnected from %pM: reason code %d\n", + priv->cfg_bssid, reason_code); + if (priv->bss_mode == NL80211_IFTYPE_STATION) { + cfg80211_disconnected(priv->netdev, reason_code, NULL, 0, + GFP_KERNEL); + } + memset(priv->cfg_bssid, 0, ETH_ALEN); + + mwifiex_stop_net_dev_queue(priv->netdev, adapter); + if (netif_carrier_ok(priv->netdev)) + netif_carrier_off(priv->netdev); +} + +/* + * This function handles events generated by firmware. + * + * This is a generic function and handles all events. + * + * Event specific routines are called by this function based + * upon the generated event cause. + * + * For the following events, the function just forwards them to upper + * layers, optionally recording the change - + * - EVENT_LINK_SENSED + * - EVENT_MIC_ERR_UNICAST + * - EVENT_MIC_ERR_MULTICAST + * - EVENT_PORT_RELEASE + * - EVENT_RSSI_LOW + * - EVENT_SNR_LOW + * - EVENT_MAX_FAIL + * - EVENT_RSSI_HIGH + * - EVENT_SNR_HIGH + * - EVENT_DATA_RSSI_LOW + * - EVENT_DATA_SNR_LOW + * - EVENT_DATA_RSSI_HIGH + * - EVENT_DATA_SNR_HIGH + * - EVENT_LINK_QUALITY + * - EVENT_PRE_BEACON_LOST + * - EVENT_IBSS_COALESCED + * - EVENT_WEP_ICV_ERR + * - EVENT_BW_CHANGE + * - EVENT_HOSTWAKE_STAIE + * + * For the following events, no action is taken - + * - EVENT_MIB_CHANGED + * - EVENT_INIT_DONE + * - EVENT_DUMMY_HOST_WAKEUP_SIGNAL + * + * Rest of the supported events requires driver handling - + * - EVENT_DEAUTHENTICATED + * - EVENT_DISASSOCIATED + * - EVENT_LINK_LOST + * - EVENT_PS_SLEEP + * - EVENT_PS_AWAKE + * - EVENT_DEEP_SLEEP_AWAKE + * - EVENT_HS_ACT_REQ + * - EVENT_ADHOC_BCN_LOST + * - EVENT_BG_SCAN_REPORT + * - EVENT_WMM_STATUS_CHANGE + * - EVENT_ADDBA + * - EVENT_DELBA + * - EVENT_BA_STREAM_TIEMOUT + * - EVENT_AMSDU_AGGR_CTRL + */ +int mwifiex_process_sta_event(struct mwifiex_private *priv) +{ + struct mwifiex_adapter *adapter = priv->adapter; + int ret = 0; + u32 eventcause = adapter->event_cause; + u16 ctrl, reason_code; + + switch (eventcause) { + case EVENT_DUMMY_HOST_WAKEUP_SIGNAL: + dev_err(adapter->dev, + "invalid EVENT: DUMMY_HOST_WAKEUP_SIGNAL, ignore it\n"); + break; + case EVENT_LINK_SENSED: + dev_dbg(adapter->dev, "event: LINK_SENSED\n"); + if (!netif_carrier_ok(priv->netdev)) + netif_carrier_on(priv->netdev); + mwifiex_wake_up_net_dev_queue(priv->netdev, adapter); + break; + + case EVENT_DEAUTHENTICATED: + dev_dbg(adapter->dev, "event: Deauthenticated\n"); + adapter->dbg.num_event_deauth++; + if (priv->media_connected) { + reason_code = + le16_to_cpu(*(__le16 *)adapter->event_body); + mwifiex_reset_connect_state(priv, reason_code); + } + break; + + case EVENT_DISASSOCIATED: + dev_dbg(adapter->dev, "event: Disassociated\n"); + adapter->dbg.num_event_disassoc++; + if (priv->media_connected) { + reason_code = + le16_to_cpu(*(__le16 *)adapter->event_body); + mwifiex_reset_connect_state(priv, reason_code); + } + break; + + case EVENT_LINK_LOST: + dev_dbg(adapter->dev, "event: Link lost\n"); + adapter->dbg.num_event_link_lost++; + if (priv->media_connected) { + reason_code = + le16_to_cpu(*(__le16 *)adapter->event_body); + mwifiex_reset_connect_state(priv, reason_code); + } + break; + + case EVENT_PS_SLEEP: + dev_dbg(adapter->dev, "info: EVENT: SLEEP\n"); + + adapter->ps_state = PS_STATE_PRE_SLEEP; + + mwifiex_check_ps_cond(adapter); + break; + + case EVENT_PS_AWAKE: + dev_dbg(adapter->dev, "info: EVENT: AWAKE\n"); + if (!adapter->pps_uapsd_mode && + priv->media_connected && adapter->sleep_period.period) { + adapter->pps_uapsd_mode = true; + dev_dbg(adapter->dev, + "event: PPS/UAPSD mode activated\n"); + } + adapter->tx_lock_flag = false; + if (adapter->pps_uapsd_mode && adapter->gen_null_pkt) { + if (mwifiex_check_last_packet_indication(priv)) { + if (adapter->data_sent) { + adapter->ps_state = PS_STATE_AWAKE; + adapter->pm_wakeup_card_req = false; + adapter->pm_wakeup_fw_try = false; + break; + } + if (!mwifiex_send_null_packet + (priv, + MWIFIEX_TxPD_POWER_MGMT_NULL_PACKET | + MWIFIEX_TxPD_POWER_MGMT_LAST_PACKET)) + adapter->ps_state = + PS_STATE_SLEEP; + return 0; + } + } + adapter->ps_state = PS_STATE_AWAKE; + adapter->pm_wakeup_card_req = false; + adapter->pm_wakeup_fw_try = false; + + break; + + case EVENT_DEEP_SLEEP_AWAKE: + adapter->if_ops.wakeup_complete(adapter); + dev_dbg(adapter->dev, "event: DS_AWAKE\n"); + if (adapter->is_deep_sleep) + adapter->is_deep_sleep = false; + break; + + case EVENT_HS_ACT_REQ: + dev_dbg(adapter->dev, "event: HS_ACT_REQ\n"); + ret = mwifiex_send_cmd_async(priv, + HostCmd_CMD_802_11_HS_CFG_ENH, + 0, 0, NULL); + break; + + case EVENT_MIC_ERR_UNICAST: + dev_dbg(adapter->dev, "event: UNICAST MIC ERROR\n"); + cfg80211_michael_mic_failure(priv->netdev, priv->cfg_bssid, + NL80211_KEYTYPE_PAIRWISE, + -1, NULL, GFP_KERNEL); + break; + + case EVENT_MIC_ERR_MULTICAST: + dev_dbg(adapter->dev, "event: MULTICAST MIC ERROR\n"); + cfg80211_michael_mic_failure(priv->netdev, priv->cfg_bssid, + NL80211_KEYTYPE_GROUP, + -1, NULL, GFP_KERNEL); + break; + case EVENT_MIB_CHANGED: + case EVENT_INIT_DONE: + break; + + case EVENT_ADHOC_BCN_LOST: + dev_dbg(adapter->dev, "event: ADHOC_BCN_LOST\n"); + priv->adhoc_is_link_sensed = false; + mwifiex_clean_txrx(priv); + mwifiex_stop_net_dev_queue(priv->netdev, adapter); + if (netif_carrier_ok(priv->netdev)) + netif_carrier_off(priv->netdev); + break; + + case EVENT_BG_SCAN_REPORT: + dev_dbg(adapter->dev, "event: BGS_REPORT\n"); + ret = mwifiex_send_cmd_async(priv, + HostCmd_CMD_802_11_BG_SCAN_QUERY, + HostCmd_ACT_GEN_GET, 0, NULL); + break; + + case EVENT_PORT_RELEASE: + dev_dbg(adapter->dev, "event: PORT RELEASE\n"); + break; + + case EVENT_WMM_STATUS_CHANGE: + dev_dbg(adapter->dev, "event: WMM status changed\n"); + ret = mwifiex_send_cmd_async(priv, HostCmd_CMD_WMM_GET_STATUS, + 0, 0, NULL); + break; + + case EVENT_RSSI_LOW: + cfg80211_cqm_rssi_notify(priv->netdev, + NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW, + GFP_KERNEL); + mwifiex_send_cmd_async(priv, HostCmd_CMD_RSSI_INFO, + HostCmd_ACT_GEN_GET, 0, NULL); + priv->subsc_evt_rssi_state = RSSI_LOW_RECVD; + dev_dbg(adapter->dev, "event: Beacon RSSI_LOW\n"); + break; + case EVENT_SNR_LOW: + dev_dbg(adapter->dev, "event: Beacon SNR_LOW\n"); + break; + case EVENT_MAX_FAIL: + dev_dbg(adapter->dev, "event: MAX_FAIL\n"); + break; + case EVENT_RSSI_HIGH: + cfg80211_cqm_rssi_notify(priv->netdev, + NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH, + GFP_KERNEL); + mwifiex_send_cmd_async(priv, HostCmd_CMD_RSSI_INFO, + HostCmd_ACT_GEN_GET, 0, NULL); + priv->subsc_evt_rssi_state = RSSI_HIGH_RECVD; + dev_dbg(adapter->dev, "event: Beacon RSSI_HIGH\n"); + break; + case EVENT_SNR_HIGH: + dev_dbg(adapter->dev, "event: Beacon SNR_HIGH\n"); + break; + case EVENT_DATA_RSSI_LOW: + dev_dbg(adapter->dev, "event: Data RSSI_LOW\n"); + break; + case EVENT_DATA_SNR_LOW: + dev_dbg(adapter->dev, "event: Data SNR_LOW\n"); + break; + case EVENT_DATA_RSSI_HIGH: + dev_dbg(adapter->dev, "event: Data RSSI_HIGH\n"); + break; + case EVENT_DATA_SNR_HIGH: + dev_dbg(adapter->dev, "event: Data SNR_HIGH\n"); + break; + case EVENT_LINK_QUALITY: + dev_dbg(adapter->dev, "event: Link Quality\n"); + break; + case EVENT_PRE_BEACON_LOST: + dev_dbg(adapter->dev, "event: Pre-Beacon Lost\n"); + break; + case EVENT_IBSS_COALESCED: + dev_dbg(adapter->dev, "event: IBSS_COALESCED\n"); + ret = mwifiex_send_cmd_async(priv, + HostCmd_CMD_802_11_IBSS_COALESCING_STATUS, + HostCmd_ACT_GEN_GET, 0, NULL); + break; + case EVENT_ADDBA: + dev_dbg(adapter->dev, "event: ADDBA Request\n"); + mwifiex_send_cmd_async(priv, HostCmd_CMD_11N_ADDBA_RSP, + HostCmd_ACT_GEN_SET, 0, + adapter->event_body); + break; + case EVENT_DELBA: + dev_dbg(adapter->dev, "event: DELBA Request\n"); + mwifiex_11n_delete_ba_stream(priv, adapter->event_body); + break; + case EVENT_BA_STREAM_TIEMOUT: + dev_dbg(adapter->dev, "event: BA Stream timeout\n"); + mwifiex_11n_ba_stream_timeout(priv, + (struct host_cmd_ds_11n_batimeout + *) + adapter->event_body); + break; + case EVENT_AMSDU_AGGR_CTRL: + ctrl = le16_to_cpu(*(__le16 *)adapter->event_body); + dev_dbg(adapter->dev, "event: AMSDU_AGGR_CTRL %d\n", ctrl); + + adapter->tx_buf_size = + min_t(u16, adapter->curr_tx_buf_size, ctrl); + dev_dbg(adapter->dev, "event: tx_buf_size %d\n", + adapter->tx_buf_size); + break; + + case EVENT_WEP_ICV_ERR: + dev_dbg(adapter->dev, "event: WEP ICV error\n"); + break; + + case EVENT_BW_CHANGE: + dev_dbg(adapter->dev, "event: BW Change\n"); + break; + + case EVENT_HOSTWAKE_STAIE: + dev_dbg(adapter->dev, "event: HOSTWAKE_STAIE %d\n", eventcause); + break; + + case EVENT_REMAIN_ON_CHAN_EXPIRED: + dev_dbg(adapter->dev, "event: Remain on channel expired\n"); + cfg80211_remain_on_channel_expired(priv->wdev, + priv->roc_cfg.cookie, + &priv->roc_cfg.chan, + GFP_ATOMIC); + + memset(&priv->roc_cfg, 0x00, sizeof(struct mwifiex_roc_cfg)); + + break; + + default: + dev_dbg(adapter->dev, "event: unknown event id: %#x\n", + eventcause); + break; + } + + return ret; +} diff --git a/drivers/net/wireless/mwifiex/sta_ioctl.c b/drivers/net/wireless/mwifiex/sta_ioctl.c new file mode 100644 index 0000000..89946a3 --- /dev/null +++ b/drivers/net/wireless/mwifiex/sta_ioctl.c @@ -0,0 +1,1380 @@ +/* + * Marvell Wireless LAN device driver: functions for station ioctl + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" +#include "cfg80211.h" + +static int disconnect_on_suspend = 1; +module_param(disconnect_on_suspend, int, 0644); + +/* + * Copies the multicast address list from device to driver. + * + * This function does not validate the destination memory for + * size, and the calling function must ensure enough memory is + * available. + */ +int mwifiex_copy_mcast_addr(struct mwifiex_multicast_list *mlist, + struct net_device *dev) +{ + int i = 0; + struct netdev_hw_addr *ha; + + netdev_for_each_mc_addr(ha, dev) +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + memcpy(&mlist->mac_list[i++], ha->addr, ETH_ALEN); +#else + memcpy(&mlist->mac_list[i++], ha->dmi_addr, ETH_ALEN); +#endif + + return i; +} + +/* + * Wait queue completion handler. + * + * This function waits on a cmd wait queue. It also cancels the pending + * request after waking up, in case of errors. + */ +int mwifiex_wait_queue_complete(struct mwifiex_adapter *adapter, + struct cmd_ctrl_node *cmd_queued) +{ + int status; + + /* Wait for completion */ + status = wait_event_interruptible(adapter->cmd_wait_q.wait, + *(cmd_queued->condition)); + if (status) { + dev_err(adapter->dev, "cmd_wait_q terminated: %d\n", status); + return status; + } + + status = adapter->cmd_wait_q.status; + adapter->cmd_wait_q.status = 0; + + return status; +} + +/* + * This function prepares the correct firmware command and + * issues it to set the multicast list. + * + * This function can be used to enable promiscuous mode, or enable all + * multicast packets, or to enable selective multicast. + */ +int mwifiex_request_set_multicast_list(struct mwifiex_private *priv, + struct mwifiex_multicast_list *mcast_list) +{ + int ret = 0; + u16 old_pkt_filter; + + old_pkt_filter = priv->curr_pkt_filter; + + if (mcast_list->mode == MWIFIEX_PROMISC_MODE) { + dev_dbg(priv->adapter->dev, "info: Enable Promiscuous mode\n"); + priv->curr_pkt_filter |= HostCmd_ACT_MAC_PROMISCUOUS_ENABLE; + priv->curr_pkt_filter &= + ~HostCmd_ACT_MAC_ALL_MULTICAST_ENABLE; + } else { + /* Multicast */ + priv->curr_pkt_filter &= ~HostCmd_ACT_MAC_PROMISCUOUS_ENABLE; + if (mcast_list->mode == MWIFIEX_ALL_MULTI_MODE) { + dev_dbg(priv->adapter->dev, + "info: Enabling All Multicast!\n"); + priv->curr_pkt_filter |= + HostCmd_ACT_MAC_ALL_MULTICAST_ENABLE; + } else { + priv->curr_pkt_filter &= + ~HostCmd_ACT_MAC_ALL_MULTICAST_ENABLE; + dev_dbg(priv->adapter->dev, + "info: Set multicast list=%d\n", + mcast_list->num_multicast_addr); + /* Send multicast addresses to firmware */ + ret = mwifiex_send_cmd_async(priv, + HostCmd_CMD_MAC_MULTICAST_ADR, + HostCmd_ACT_GEN_SET, 0, + mcast_list); + } + } + dev_dbg(priv->adapter->dev, + "info: old_pkt_filter=%#x, curr_pkt_filter=%#x\n", + old_pkt_filter, priv->curr_pkt_filter); + if (old_pkt_filter != priv->curr_pkt_filter) { + ret = mwifiex_send_cmd_async(priv, HostCmd_CMD_MAC_CONTROL, + HostCmd_ACT_GEN_SET, + 0, &priv->curr_pkt_filter); + } + + return ret; +} + +/* + * This function fills bss descriptor structure using provided + * information. + * beacon_ie buffer is allocated in this function. It is caller's + * responsibility to free the memory. + */ +int mwifiex_fill_new_bss_desc(struct mwifiex_private *priv, + struct cfg80211_bss *bss, + struct mwifiex_bssdescriptor *bss_desc) +{ + u8 *beacon_ie; + size_t beacon_ie_len; + struct mwifiex_bss_priv *bss_priv = (void *)bss->priv; + const struct cfg80211_bss_ies *ies; + + rcu_read_lock(); + ies = rcu_dereference(bss->ies); + beacon_ie = kmemdup(ies->data, ies->len, GFP_ATOMIC); + beacon_ie_len = ies->len; + bss_desc->timestamp = ies->tsf; + rcu_read_unlock(); + + if (!beacon_ie) { + dev_err(priv->adapter->dev, " failed to alloc beacon_ie\n"); + return -ENOMEM; + } + + memcpy(bss_desc->mac_address, bss->bssid, ETH_ALEN); + bss_desc->rssi = bss->signal; + /* The caller of this function will free beacon_ie */ + bss_desc->beacon_buf = beacon_ie; + bss_desc->beacon_buf_size = beacon_ie_len; + bss_desc->beacon_period = bss->beacon_interval; + bss_desc->cap_info_bitmap = bss->capability; + bss_desc->bss_band = bss_priv->band; + bss_desc->fw_tsf = bss_priv->fw_tsf; + if (bss_desc->cap_info_bitmap & WLAN_CAPABILITY_PRIVACY) { + dev_dbg(priv->adapter->dev, "info: InterpretIE: AP WEP enabled\n"); + bss_desc->privacy = MWIFIEX_802_11_PRIV_FILTER_8021X_WEP; + } else { + bss_desc->privacy = MWIFIEX_802_11_PRIV_FILTER_ACCEPT_ALL; + } + if (bss_desc->cap_info_bitmap & WLAN_CAPABILITY_IBSS) + bss_desc->bss_mode = NL80211_IFTYPE_ADHOC; + else + bss_desc->bss_mode = NL80211_IFTYPE_STATION; + + /* Disable 11ac by default. Enable it only where there + * exist VHT_CAP IE in AP beacon + */ + bss_desc->disable_11ac = true; + + return mwifiex_update_bss_desc_with_ie(priv->adapter, bss_desc); +} + +static int mwifiex_process_country_ie(struct mwifiex_private *priv, + struct cfg80211_bss *bss) +{ + const u8 *country_ie; + u8 country_ie_len; + struct mwifiex_802_11d_domain_reg *domain_info = + &priv->adapter->domain_reg; + + rcu_read_lock(); + country_ie = ieee80211_bss_get_ie(bss, WLAN_EID_COUNTRY); + if (!country_ie) { + rcu_read_unlock(); + return 0; + } + + country_ie_len = country_ie[1]; + if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) { + rcu_read_unlock(); + return 0; + } + + domain_info->country_code[0] = country_ie[2]; + domain_info->country_code[1] = country_ie[3]; + domain_info->country_code[2] = ' '; + + country_ie_len -= IEEE80211_COUNTRY_STRING_LEN; + + domain_info->no_of_triplet = + country_ie_len / sizeof(struct ieee80211_country_ie_triplet); + + memcpy((u8 *)domain_info->triplet, + &country_ie[2] + IEEE80211_COUNTRY_STRING_LEN, country_ie_len); + + rcu_read_unlock(); + + if (mwifiex_send_cmd_async(priv, HostCmd_CMD_802_11D_DOMAIN_INFO, + HostCmd_ACT_GEN_SET, 0, NULL)) { + wiphy_err(priv->adapter->wiphy, + "11D: setting domain info in FW\n"); + return -1; + } + + return 0; +} + +/* + * In Ad-Hoc mode, the IBSS is created if not found in scan list. + * In both Ad-Hoc and infra mode, an deauthentication is performed + * first. + */ +int mwifiex_bss_start(struct mwifiex_private *priv, struct cfg80211_bss *bss, + struct cfg80211_ssid *req_ssid) +{ + int ret; + struct mwifiex_adapter *adapter = priv->adapter; + struct mwifiex_bssdescriptor *bss_desc = NULL; + + priv->scan_block = false; + + if (bss) { + mwifiex_process_country_ie(priv, bss); + + /* Allocate and fill new bss descriptor */ + bss_desc = kzalloc(sizeof(struct mwifiex_bssdescriptor), + GFP_KERNEL); + if (!bss_desc) + return -ENOMEM; + + ret = mwifiex_fill_new_bss_desc(priv, bss, bss_desc); + if (ret) + goto done; + } + + if (priv->bss_mode == NL80211_IFTYPE_STATION) { + /* Infra mode */ + ret = mwifiex_deauthenticate(priv, NULL); + if (ret) + goto done; + + if (bss_desc) { + u8 config_bands = 0; + + if (mwifiex_band_to_radio_type((u8) bss_desc->bss_band) + == HostCmd_SCAN_RADIO_TYPE_BG) + config_bands = BAND_B | BAND_G | BAND_GN | + BAND_GAC; + else + config_bands = BAND_A | BAND_AN | BAND_AAC; + + if (!((config_bands | adapter->fw_bands) & + ~adapter->fw_bands)) + adapter->config_bands = config_bands; + } + + ret = mwifiex_check_network_compatibility(priv, bss_desc); + if (ret) + goto done; + + dev_dbg(adapter->dev, "info: SSID found in scan list ... " + "associating...\n"); + + mwifiex_stop_net_dev_queue(priv->netdev, adapter); + if (netif_carrier_ok(priv->netdev)) + netif_carrier_off(priv->netdev); + + /* Clear any past association response stored for + * application retrieval */ + priv->assoc_rsp_size = 0; + ret = mwifiex_associate(priv, bss_desc); + + /* If auth type is auto and association fails using open mode, + * try to connect using shared mode */ + if (ret == WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG && + priv->sec_info.is_authtype_auto && + priv->sec_info.wep_enabled) { + priv->sec_info.authentication_mode = + NL80211_AUTHTYPE_SHARED_KEY; + ret = mwifiex_associate(priv, bss_desc); + } + + if (bss) + cfg80211_put_bss(priv->adapter->wiphy, bss); + } else { + /* Adhoc mode */ + /* If the requested SSID matches current SSID, return */ + if (bss_desc && bss_desc->ssid.ssid_len && + (!mwifiex_ssid_cmp(&priv->curr_bss_params.bss_descriptor. + ssid, &bss_desc->ssid))) { + kfree(bss_desc); + return 0; + } + + /* Exit Adhoc mode first */ + dev_dbg(adapter->dev, "info: Sending Adhoc Stop\n"); + ret = mwifiex_deauthenticate(priv, NULL); + if (ret) + goto done; + + priv->adhoc_is_link_sensed = false; + + ret = mwifiex_check_network_compatibility(priv, bss_desc); + + mwifiex_stop_net_dev_queue(priv->netdev, adapter); + if (netif_carrier_ok(priv->netdev)) + netif_carrier_off(priv->netdev); + + if (!ret) { + dev_dbg(adapter->dev, "info: network found in scan" + " list. Joining...\n"); + ret = mwifiex_adhoc_join(priv, bss_desc); + if (bss) + cfg80211_put_bss(priv->adapter->wiphy, bss); + } else { + dev_dbg(adapter->dev, "info: Network not found in " + "the list, creating adhoc with ssid = %s\n", + req_ssid->ssid); + ret = mwifiex_adhoc_start(priv, req_ssid); + } + } + +done: + /* beacon_ie buffer was allocated in function + * mwifiex_fill_new_bss_desc(). Free it now. + */ + if (bss_desc) + kfree(bss_desc->beacon_buf); + kfree(bss_desc); + return ret; +} + +/* + * IOCTL request handler to set host sleep configuration. + * + * This function prepares the correct firmware command and + * issues it. + */ +static int mwifiex_set_hs_params(struct mwifiex_private *priv, u16 action, + int cmd_type, struct mwifiex_ds_hs_cfg *hs_cfg) + +{ + struct mwifiex_adapter *adapter = priv->adapter; + int status = 0; + u32 prev_cond = 0; + + if (!hs_cfg) + return -ENOMEM; + + switch (action) { + case HostCmd_ACT_GEN_SET: + if (adapter->pps_uapsd_mode) { + dev_dbg(adapter->dev, "info: Host Sleep IOCTL" + " is blocked in UAPSD/PPS mode\n"); + status = -1; + break; + } + if (hs_cfg->is_invoke_hostcmd) { + if (hs_cfg->conditions == HS_CFG_CANCEL) { + if (!adapter->is_hs_configured) + /* Already cancelled */ + break; + /* Save previous condition */ + prev_cond = le32_to_cpu(adapter->hs_cfg + .conditions); + adapter->hs_cfg.conditions = + cpu_to_le32(hs_cfg->conditions); + } else if (hs_cfg->conditions) { + adapter->hs_cfg.conditions = + cpu_to_le32(hs_cfg->conditions); + adapter->hs_cfg.gpio = (u8)hs_cfg->gpio; + if (hs_cfg->gap) + adapter->hs_cfg.gap = (u8)hs_cfg->gap; + } else if (adapter->hs_cfg.conditions == + cpu_to_le32(HS_CFG_CANCEL)) { + /* Return failure if no parameters for HS + enable */ + status = -1; + break; + } + if (cmd_type == MWIFIEX_SYNC_CMD) + status = mwifiex_send_cmd_sync(priv, + HostCmd_CMD_802_11_HS_CFG_ENH, + HostCmd_ACT_GEN_SET, 0, + &adapter->hs_cfg); + else + status = mwifiex_send_cmd_async(priv, + HostCmd_CMD_802_11_HS_CFG_ENH, + HostCmd_ACT_GEN_SET, 0, + &adapter->hs_cfg); + if (hs_cfg->conditions == HS_CFG_CANCEL) + /* Restore previous condition */ + adapter->hs_cfg.conditions = + cpu_to_le32(prev_cond); + } else { + adapter->hs_cfg.conditions = + cpu_to_le32(hs_cfg->conditions); + adapter->hs_cfg.gpio = (u8)hs_cfg->gpio; + adapter->hs_cfg.gap = (u8)hs_cfg->gap; + } + break; + case HostCmd_ACT_GEN_GET: + hs_cfg->conditions = le32_to_cpu(adapter->hs_cfg.conditions); + hs_cfg->gpio = adapter->hs_cfg.gpio; + hs_cfg->gap = adapter->hs_cfg.gap; + break; + default: + status = -1; + break; + } + + return status; +} + +/* + * Sends IOCTL request to cancel the existing Host Sleep configuration. + * + * This function allocates the IOCTL request buffer, fills it + * with requisite parameters and calls the IOCTL handler. + */ +int mwifiex_cancel_hs(struct mwifiex_private *priv, int cmd_type) +{ + struct mwifiex_ds_hs_cfg hscfg; + + hscfg.conditions = HS_CFG_CANCEL; + hscfg.is_invoke_hostcmd = true; + + return mwifiex_set_hs_params(priv, HostCmd_ACT_GEN_SET, + cmd_type, &hscfg); +} +EXPORT_SYMBOL_GPL(mwifiex_cancel_hs); + +/* + * Sends IOCTL request to cancel the existing Host Sleep configuration. + * + * This function allocates the IOCTL request buffer, fills it + * with requisite parameters and calls the IOCTL handler. + */ +int mwifiex_enable_hs(struct mwifiex_adapter *adapter) +{ + struct mwifiex_ds_hs_cfg hscfg; + struct mwifiex_private *priv; + int i; + + if (disconnect_on_suspend) { + for (i = 0; i < adapter->priv_num; i++) { + priv = adapter->priv[i]; + if (priv) + mwifiex_deauthenticate(priv, NULL); + } + } + + if (adapter->hs_activated) { + dev_dbg(adapter->dev, "cmd: HS Already activated\n"); + return true; + } + + adapter->hs_activate_wait_q_woken = false; + + memset(&hscfg, 0, sizeof(struct mwifiex_ds_hs_cfg)); + hscfg.is_invoke_hostcmd = true; + + if (mwifiex_set_hs_params(mwifiex_get_priv(adapter, + MWIFIEX_BSS_ROLE_STA), + HostCmd_ACT_GEN_SET, MWIFIEX_SYNC_CMD, + &hscfg)) { + dev_err(adapter->dev, "IOCTL request HS enable failed\n"); + return false; + } + + if (wait_event_interruptible(adapter->hs_activate_wait_q, + adapter->hs_activate_wait_q_woken)) { + dev_err(adapter->dev, "hs_activate_wait_q terminated\n"); + return false; + } + + return true; +} +EXPORT_SYMBOL_GPL(mwifiex_enable_hs); + +/* + * IOCTL request handler to get BSS information. + * + * This function collates the information from different driver structures + * to send to the user. + */ +int mwifiex_get_bss_info(struct mwifiex_private *priv, + struct mwifiex_bss_info *info) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct mwifiex_bssdescriptor *bss_desc; + + if (!info) + return -1; + + bss_desc = &priv->curr_bss_params.bss_descriptor; + + info->bss_mode = priv->bss_mode; + + memcpy(&info->ssid, &bss_desc->ssid, sizeof(struct cfg80211_ssid)); + + memcpy(&info->bssid, &bss_desc->mac_address, ETH_ALEN); + + info->bss_chan = bss_desc->channel; + + memcpy(info->country_code, adapter->country_code, + IEEE80211_COUNTRY_STRING_LEN); + + info->media_connected = priv->media_connected; + + info->max_power_level = priv->max_tx_power_level; + info->min_power_level = priv->min_tx_power_level; + + info->adhoc_state = priv->adhoc_state; + + info->bcn_nf_last = priv->bcn_nf_last; + + if (priv->sec_info.wep_enabled) + info->wep_status = true; + else + info->wep_status = false; + + info->is_hs_configured = adapter->is_hs_configured; + info->is_deep_sleep = adapter->is_deep_sleep; + + return 0; +} + +/* + * The function disables auto deep sleep mode. + */ +int mwifiex_disable_auto_ds(struct mwifiex_private *priv) +{ + struct mwifiex_ds_auto_ds auto_ds; + + auto_ds.auto_ds = DEEP_SLEEP_OFF; + + return mwifiex_send_cmd_sync(priv, HostCmd_CMD_802_11_PS_MODE_ENH, + DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds); +} +EXPORT_SYMBOL_GPL(mwifiex_disable_auto_ds); + +/* + * Sends IOCTL request to get the data rate. + * + * This function allocates the IOCTL request buffer, fills it + * with requisite parameters and calls the IOCTL handler. + */ +int mwifiex_drv_get_data_rate(struct mwifiex_private *priv, u32 *rate) +{ + int ret; + + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_802_11_TX_RATE_QUERY, + HostCmd_ACT_GEN_GET, 0, NULL); + + if (!ret) { + if (priv->is_data_rate_auto) + *rate = mwifiex_index_to_data_rate(priv, priv->tx_rate, + priv->tx_htinfo); + else + *rate = priv->data_rate; + } + + return ret; +} + +/* + * IOCTL request handler to set tx power configuration. + * + * This function prepares the correct firmware command and + * issues it. + * + * For non-auto power mode, all the following power groups are set - + * - Modulation class HR/DSSS + * - Modulation class OFDM + * - Modulation class HTBW20 + * - Modulation class HTBW40 + */ +int mwifiex_set_tx_power(struct mwifiex_private *priv, + struct mwifiex_power_cfg *power_cfg) +{ + int ret; + struct host_cmd_ds_txpwr_cfg *txp_cfg; + struct mwifiex_types_power_group *pg_tlv; + struct mwifiex_power_group *pg; + u8 *buf; + u16 dbm = 0; + + if (!power_cfg->is_power_auto) { + dbm = (u16) power_cfg->power_level; + if ((dbm < priv->min_tx_power_level) || + (dbm > priv->max_tx_power_level)) { + dev_err(priv->adapter->dev, "txpower value %d dBm" + " is out of range (%d dBm-%d dBm)\n", + dbm, priv->min_tx_power_level, + priv->max_tx_power_level); + return -1; + } + } + buf = kzalloc(MWIFIEX_SIZE_OF_CMD_BUFFER, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + txp_cfg = (struct host_cmd_ds_txpwr_cfg *) buf; + txp_cfg->action = cpu_to_le16(HostCmd_ACT_GEN_SET); + if (!power_cfg->is_power_auto) { + txp_cfg->mode = cpu_to_le32(1); + pg_tlv = (struct mwifiex_types_power_group *) + (buf + sizeof(struct host_cmd_ds_txpwr_cfg)); + pg_tlv->type = TLV_TYPE_POWER_GROUP; + pg_tlv->length = 4 * sizeof(struct mwifiex_power_group); + pg = (struct mwifiex_power_group *) + (buf + sizeof(struct host_cmd_ds_txpwr_cfg) + + sizeof(struct mwifiex_types_power_group)); + /* Power group for modulation class HR/DSSS */ + pg->first_rate_code = 0x00; + pg->last_rate_code = 0x03; + pg->modulation_class = MOD_CLASS_HR_DSSS; + pg->power_step = 0; + pg->power_min = (s8) dbm; + pg->power_max = (s8) dbm; + pg++; + /* Power group for modulation class OFDM */ + pg->first_rate_code = 0x00; + pg->last_rate_code = 0x07; + pg->modulation_class = MOD_CLASS_OFDM; + pg->power_step = 0; + pg->power_min = (s8) dbm; + pg->power_max = (s8) dbm; + pg++; + /* Power group for modulation class HTBW20 */ + pg->first_rate_code = 0x00; + pg->last_rate_code = 0x20; + pg->modulation_class = MOD_CLASS_HT; + pg->power_step = 0; + pg->power_min = (s8) dbm; + pg->power_max = (s8) dbm; + pg->ht_bandwidth = HT_BW_20; + pg++; + /* Power group for modulation class HTBW40 */ + pg->first_rate_code = 0x00; + pg->last_rate_code = 0x20; + pg->modulation_class = MOD_CLASS_HT; + pg->power_step = 0; + pg->power_min = (s8) dbm; + pg->power_max = (s8) dbm; + pg->ht_bandwidth = HT_BW_40; + } + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_TXPWR_CFG, + HostCmd_ACT_GEN_SET, 0, buf); + + kfree(buf); + return ret; +} + +/* + * IOCTL request handler to get power save mode. + * + * This function prepares the correct firmware command and + * issues it. + */ +int mwifiex_drv_set_power(struct mwifiex_private *priv, u32 *ps_mode) +{ + int ret; + struct mwifiex_adapter *adapter = priv->adapter; + u16 sub_cmd; + + if (*ps_mode) + adapter->ps_mode = MWIFIEX_802_11_POWER_MODE_PSP; + else + adapter->ps_mode = MWIFIEX_802_11_POWER_MODE_CAM; + sub_cmd = (*ps_mode) ? EN_AUTO_PS : DIS_AUTO_PS; + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_802_11_PS_MODE_ENH, + sub_cmd, BITMAP_STA_PS, NULL); + if ((!ret) && (sub_cmd == DIS_AUTO_PS)) + ret = mwifiex_send_cmd_async(priv, + HostCmd_CMD_802_11_PS_MODE_ENH, + GET_PS, 0, NULL); + + return ret; +} + +/* + * IOCTL request handler to set/reset WPA IE. + * + * The supplied WPA IE is treated as a opaque buffer. Only the first field + * is checked to determine WPA version. If buffer length is zero, the existing + * WPA IE is reset. + */ +static int mwifiex_set_wpa_ie_helper(struct mwifiex_private *priv, + u8 *ie_data_ptr, u16 ie_len) +{ + if (ie_len) { + if (ie_len > sizeof(priv->wpa_ie)) { + dev_err(priv->adapter->dev, + "failed to copy WPA IE, too big\n"); + return -1; + } + memcpy(priv->wpa_ie, ie_data_ptr, ie_len); + priv->wpa_ie_len = (u8) ie_len; + dev_dbg(priv->adapter->dev, "cmd: Set Wpa_ie_len=%d IE=%#x\n", + priv->wpa_ie_len, priv->wpa_ie[0]); + + if (priv->wpa_ie[0] == WLAN_EID_VENDOR_SPECIFIC) { + priv->sec_info.wpa_enabled = true; + } else if (priv->wpa_ie[0] == WLAN_EID_RSN) { + priv->sec_info.wpa2_enabled = true; + } else { + priv->sec_info.wpa_enabled = false; + priv->sec_info.wpa2_enabled = false; + } + } else { + memset(priv->wpa_ie, 0, sizeof(priv->wpa_ie)); + priv->wpa_ie_len = 0; + dev_dbg(priv->adapter->dev, "info: reset wpa_ie_len=%d IE=%#x\n", + priv->wpa_ie_len, priv->wpa_ie[0]); + priv->sec_info.wpa_enabled = false; + priv->sec_info.wpa2_enabled = false; + } + + return 0; +} + +/* + * IOCTL request handler to set/reset WAPI IE. + * + * The supplied WAPI IE is treated as a opaque buffer. Only the first field + * is checked to internally enable WAPI. If buffer length is zero, the existing + * WAPI IE is reset. + */ +static int mwifiex_set_wapi_ie(struct mwifiex_private *priv, + u8 *ie_data_ptr, u16 ie_len) +{ + if (ie_len) { + if (ie_len > sizeof(priv->wapi_ie)) { + dev_dbg(priv->adapter->dev, + "info: failed to copy WAPI IE, too big\n"); + return -1; + } + memcpy(priv->wapi_ie, ie_data_ptr, ie_len); + priv->wapi_ie_len = ie_len; + dev_dbg(priv->adapter->dev, "cmd: Set wapi_ie_len=%d IE=%#x\n", + priv->wapi_ie_len, priv->wapi_ie[0]); + + if (priv->wapi_ie[0] == WLAN_EID_BSS_AC_ACCESS_DELAY) + priv->sec_info.wapi_enabled = true; + } else { + memset(priv->wapi_ie, 0, sizeof(priv->wapi_ie)); + priv->wapi_ie_len = ie_len; + dev_dbg(priv->adapter->dev, + "info: Reset wapi_ie_len=%d IE=%#x\n", + priv->wapi_ie_len, priv->wapi_ie[0]); + priv->sec_info.wapi_enabled = false; + } + return 0; +} + +/* + * IOCTL request handler to set/reset WPS IE. + * + * The supplied WPS IE is treated as a opaque buffer. Only the first field + * is checked to internally enable WPS. If buffer length is zero, the existing + * WPS IE is reset. + */ +static int mwifiex_set_wps_ie(struct mwifiex_private *priv, + u8 *ie_data_ptr, u16 ie_len) +{ + if (ie_len) { + priv->wps_ie = kzalloc(MWIFIEX_MAX_VSIE_LEN, GFP_KERNEL); + if (!priv->wps_ie) + return -ENOMEM; + if (ie_len > sizeof(priv->wps_ie)) { + dev_dbg(priv->adapter->dev, + "info: failed to copy WPS IE, too big\n"); + kfree(priv->wps_ie); + return -1; + } + memcpy(priv->wps_ie, ie_data_ptr, ie_len); + priv->wps_ie_len = ie_len; + dev_dbg(priv->adapter->dev, "cmd: Set wps_ie_len=%d IE=%#x\n", + priv->wps_ie_len, priv->wps_ie[0]); + } else { + kfree(priv->wps_ie); + priv->wps_ie_len = ie_len; + dev_dbg(priv->adapter->dev, + "info: Reset wps_ie_len=%d\n", priv->wps_ie_len); + } + return 0; +} + +/* + * IOCTL request handler to set WAPI key. + * + * This function prepares the correct firmware command and + * issues it. + */ +static int mwifiex_sec_ioctl_set_wapi_key(struct mwifiex_private *priv, + struct mwifiex_ds_encrypt_key *encrypt_key) +{ + + return mwifiex_send_cmd_sync(priv, HostCmd_CMD_802_11_KEY_MATERIAL, + HostCmd_ACT_GEN_SET, KEY_INFO_ENABLED, + encrypt_key); +} + +/* + * IOCTL request handler to set WEP network key. + * + * This function prepares the correct firmware command and + * issues it, after validation checks. + */ +static int mwifiex_sec_ioctl_set_wep_key(struct mwifiex_private *priv, + struct mwifiex_ds_encrypt_key *encrypt_key) +{ + int ret; + struct mwifiex_wep_key *wep_key; + int index; + + if (priv->wep_key_curr_index >= NUM_WEP_KEYS) + priv->wep_key_curr_index = 0; + wep_key = &priv->wep_key[priv->wep_key_curr_index]; + index = encrypt_key->key_index; + if (encrypt_key->key_disable) { + priv->sec_info.wep_enabled = 0; + } else if (!encrypt_key->key_len) { + /* Copy the required key as the current key */ + wep_key = &priv->wep_key[index]; + if (!wep_key->key_length) { + dev_err(priv->adapter->dev, + "key not set, so cannot enable it\n"); + return -1; + } + priv->wep_key_curr_index = (u16) index; + priv->sec_info.wep_enabled = 1; + } else { + wep_key = &priv->wep_key[index]; + memset(wep_key, 0, sizeof(struct mwifiex_wep_key)); + /* Copy the key in the driver */ + memcpy(wep_key->key_material, + encrypt_key->key_material, + encrypt_key->key_len); + wep_key->key_index = index; + wep_key->key_length = encrypt_key->key_len; + priv->sec_info.wep_enabled = 1; + } + if (wep_key->key_length) { + /* Send request to firmware */ + ret = mwifiex_send_cmd_async(priv, + HostCmd_CMD_802_11_KEY_MATERIAL, + HostCmd_ACT_GEN_SET, 0, NULL); + if (ret) + return ret; + } + if (priv->sec_info.wep_enabled) + priv->curr_pkt_filter |= HostCmd_ACT_MAC_WEP_ENABLE; + else + priv->curr_pkt_filter &= ~HostCmd_ACT_MAC_WEP_ENABLE; + + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_MAC_CONTROL, + HostCmd_ACT_GEN_SET, 0, + &priv->curr_pkt_filter); + + return ret; +} + +/* + * IOCTL request handler to set WPA key. + * + * This function prepares the correct firmware command and + * issues it, after validation checks. + * + * Current driver only supports key length of up to 32 bytes. + * + * This function can also be used to disable a currently set key. + */ +static int mwifiex_sec_ioctl_set_wpa_key(struct mwifiex_private *priv, + struct mwifiex_ds_encrypt_key *encrypt_key) +{ + int ret; + u8 remove_key = false; + struct host_cmd_ds_802_11_key_material *ibss_key; + + /* Current driver only supports key length of up to 32 bytes */ + if (encrypt_key->key_len > WLAN_MAX_KEY_LEN) { + dev_err(priv->adapter->dev, "key length too long\n"); + return -1; + } + + if (priv->bss_mode == NL80211_IFTYPE_ADHOC) { + /* + * IBSS/WPA-None uses only one key (Group) for both receiving + * and sending unicast and multicast packets. + */ + /* Send the key as PTK to firmware */ + encrypt_key->key_index = MWIFIEX_KEY_INDEX_UNICAST; + ret = mwifiex_send_cmd_async(priv, + HostCmd_CMD_802_11_KEY_MATERIAL, + HostCmd_ACT_GEN_SET, + KEY_INFO_ENABLED, encrypt_key); + if (ret) + return ret; + + ibss_key = &priv->aes_key; + memset(ibss_key, 0, + sizeof(struct host_cmd_ds_802_11_key_material)); + /* Copy the key in the driver */ + memcpy(ibss_key->key_param_set.key, encrypt_key->key_material, + encrypt_key->key_len); + memcpy(&ibss_key->key_param_set.key_len, &encrypt_key->key_len, + sizeof(ibss_key->key_param_set.key_len)); + ibss_key->key_param_set.key_type_id + = cpu_to_le16(KEY_TYPE_ID_TKIP); + ibss_key->key_param_set.key_info = cpu_to_le16(KEY_ENABLED); + + /* Send the key as GTK to firmware */ + encrypt_key->key_index = ~MWIFIEX_KEY_INDEX_UNICAST; + } + + if (!encrypt_key->key_index) + encrypt_key->key_index = MWIFIEX_KEY_INDEX_UNICAST; + + if (remove_key) + ret = mwifiex_send_cmd_sync(priv, + HostCmd_CMD_802_11_KEY_MATERIAL, + HostCmd_ACT_GEN_SET, + !KEY_INFO_ENABLED, encrypt_key); + else + ret = mwifiex_send_cmd_sync(priv, + HostCmd_CMD_802_11_KEY_MATERIAL, + HostCmd_ACT_GEN_SET, + KEY_INFO_ENABLED, encrypt_key); + + return ret; +} + +/* + * IOCTL request handler to set/get network keys. + * + * This is a generic key handling function which supports WEP, WPA + * and WAPI. + */ +static int +mwifiex_sec_ioctl_encrypt_key(struct mwifiex_private *priv, + struct mwifiex_ds_encrypt_key *encrypt_key) +{ + int status; + + if (encrypt_key->is_wapi_key) + status = mwifiex_sec_ioctl_set_wapi_key(priv, encrypt_key); + else if (encrypt_key->key_len > WLAN_KEY_LEN_WEP104) + status = mwifiex_sec_ioctl_set_wpa_key(priv, encrypt_key); + else + status = mwifiex_sec_ioctl_set_wep_key(priv, encrypt_key); + return status; +} + +/* + * This function returns the driver version. + */ +int +mwifiex_drv_get_driver_version(struct mwifiex_adapter *adapter, char *version, + int max_len) +{ + union { + u32 l; + u8 c[4]; + } ver; + char fw_ver[32]; + + ver.l = adapter->fw_release_number; + sprintf(fw_ver, "%u.%u.%u.p%u", ver.c[2], ver.c[1], ver.c[0], ver.c[3]); + + snprintf(version, max_len, driver_version, fw_ver); + + dev_dbg(adapter->dev, "info: MWIFIEX VERSION: %s\n", version); + + return 0; +} + +/* + * Sends IOCTL request to set encoding parameters. + * + * This function allocates the IOCTL request buffer, fills it + * with requisite parameters and calls the IOCTL handler. + */ +int mwifiex_set_encode(struct mwifiex_private *priv, struct key_params *kp, + const u8 *key, int key_len, u8 key_index, + const u8 *mac_addr, int disable) +{ + struct mwifiex_ds_encrypt_key encrypt_key; + + memset(&encrypt_key, 0, sizeof(struct mwifiex_ds_encrypt_key)); + encrypt_key.key_len = key_len; + + if (kp && kp->cipher == WLAN_CIPHER_SUITE_AES_CMAC) + encrypt_key.is_igtk_key = true; + + if (!disable) { + encrypt_key.key_index = key_index; + if (key_len) + memcpy(encrypt_key.key_material, key, key_len); + if (mac_addr) + memcpy(encrypt_key.mac_addr, mac_addr, ETH_ALEN); + if (kp && kp->seq && kp->seq_len) + memcpy(encrypt_key.pn, kp->seq, kp->seq_len); + } else { + encrypt_key.key_disable = true; + if (mac_addr) + memcpy(encrypt_key.mac_addr, mac_addr, ETH_ALEN); + } + + return mwifiex_sec_ioctl_encrypt_key(priv, &encrypt_key); +} + +/* + * Sends IOCTL request to get extended version. + * + * This function allocates the IOCTL request buffer, fills it + * with requisite parameters and calls the IOCTL handler. + */ +int +mwifiex_get_ver_ext(struct mwifiex_private *priv) +{ + struct mwifiex_ver_ext ver_ext; + + memset(&ver_ext, 0, sizeof(struct host_cmd_ds_version_ext)); + if (mwifiex_send_cmd_sync(priv, HostCmd_CMD_VERSION_EXT, + HostCmd_ACT_GEN_GET, 0, &ver_ext)) + return -1; + + return 0; +} + +int +mwifiex_remain_on_chan_cfg(struct mwifiex_private *priv, u16 action, + struct ieee80211_channel *chan, + unsigned int duration) +{ + struct host_cmd_ds_remain_on_chan roc_cfg; + u8 sc; + + memset(&roc_cfg, 0, sizeof(roc_cfg)); + roc_cfg.action = cpu_to_le16(action); + if (action == HostCmd_ACT_GEN_SET) { + roc_cfg.band_cfg = chan->band; + sc = mwifiex_chan_type_to_sec_chan_offset(NL80211_CHAN_NO_HT); + roc_cfg.band_cfg |= (sc << 2); + + roc_cfg.channel = + ieee80211_frequency_to_channel(chan->center_freq); + roc_cfg.duration = cpu_to_le32(duration); + } + if (mwifiex_send_cmd_sync(priv, HostCmd_CMD_REMAIN_ON_CHAN, + action, 0, &roc_cfg)) { + dev_err(priv->adapter->dev, "failed to remain on channel\n"); + return -1; + } + + return roc_cfg.status; +} + +int +mwifiex_set_bss_role(struct mwifiex_private *priv, u8 bss_role) +{ + if (GET_BSS_ROLE(priv) == bss_role) { + dev_dbg(priv->adapter->dev, + "info: already in the desired role.\n"); + return 0; + } + + mwifiex_free_priv(priv); + mwifiex_init_priv(priv); + + priv->bss_role = bss_role; + switch (bss_role) { + case MWIFIEX_BSS_ROLE_UAP: + priv->bss_mode = NL80211_IFTYPE_AP; + break; + case MWIFIEX_BSS_ROLE_STA: + case MWIFIEX_BSS_ROLE_ANY: + default: + priv->bss_mode = NL80211_IFTYPE_STATION; + break; + } + + mwifiex_send_cmd_sync(priv, HostCmd_CMD_SET_BSS_MODE, + HostCmd_ACT_GEN_SET, 0, NULL); + + return mwifiex_sta_init_cmd(priv, false); +} + +/* + * Sends IOCTL request to get statistics information. + * + * This function allocates the IOCTL request buffer, fills it + * with requisite parameters and calls the IOCTL handler. + */ +int +mwifiex_get_stats_info(struct mwifiex_private *priv, + struct mwifiex_ds_get_stats *log) +{ + return mwifiex_send_cmd_sync(priv, HostCmd_CMD_802_11_GET_LOG, + HostCmd_ACT_GEN_GET, 0, log); +} + +/* + * IOCTL request handler to read/write register. + * + * This function prepares the correct firmware command and + * issues it. + * + * Access to the following registers are supported - + * - MAC + * - BBP + * - RF + * - PMIC + * - CAU + */ +static int mwifiex_reg_mem_ioctl_reg_rw(struct mwifiex_private *priv, + struct mwifiex_ds_reg_rw *reg_rw, + u16 action) +{ + u16 cmd_no; + + switch (le32_to_cpu(reg_rw->type)) { + case MWIFIEX_REG_MAC: + cmd_no = HostCmd_CMD_MAC_REG_ACCESS; + break; + case MWIFIEX_REG_BBP: + cmd_no = HostCmd_CMD_BBP_REG_ACCESS; + break; + case MWIFIEX_REG_RF: + cmd_no = HostCmd_CMD_RF_REG_ACCESS; + break; + case MWIFIEX_REG_PMIC: + cmd_no = HostCmd_CMD_PMIC_REG_ACCESS; + break; + case MWIFIEX_REG_CAU: + cmd_no = HostCmd_CMD_CAU_REG_ACCESS; + break; + default: + return -1; + } + + return mwifiex_send_cmd_sync(priv, cmd_no, action, 0, reg_rw); + +} + +/* + * Sends IOCTL request to write to a register. + * + * This function allocates the IOCTL request buffer, fills it + * with requisite parameters and calls the IOCTL handler. + */ +int +mwifiex_reg_write(struct mwifiex_private *priv, u32 reg_type, + u32 reg_offset, u32 reg_value) +{ + struct mwifiex_ds_reg_rw reg_rw; + + reg_rw.type = cpu_to_le32(reg_type); + reg_rw.offset = cpu_to_le32(reg_offset); + reg_rw.value = cpu_to_le32(reg_value); + + return mwifiex_reg_mem_ioctl_reg_rw(priv, ®_rw, HostCmd_ACT_GEN_SET); +} + +/* + * Sends IOCTL request to read from a register. + * + * This function allocates the IOCTL request buffer, fills it + * with requisite parameters and calls the IOCTL handler. + */ +int +mwifiex_reg_read(struct mwifiex_private *priv, u32 reg_type, + u32 reg_offset, u32 *value) +{ + int ret; + struct mwifiex_ds_reg_rw reg_rw; + + reg_rw.type = cpu_to_le32(reg_type); + reg_rw.offset = cpu_to_le32(reg_offset); + ret = mwifiex_reg_mem_ioctl_reg_rw(priv, ®_rw, HostCmd_ACT_GEN_GET); + + if (ret) + goto done; + + *value = le32_to_cpu(reg_rw.value); + +done: + return ret; +} + +/* + * Sends IOCTL request to read from EEPROM. + * + * This function allocates the IOCTL request buffer, fills it + * with requisite parameters and calls the IOCTL handler. + */ +int +mwifiex_eeprom_read(struct mwifiex_private *priv, u16 offset, u16 bytes, + u8 *value) +{ + int ret; + struct mwifiex_ds_read_eeprom rd_eeprom; + + rd_eeprom.offset = cpu_to_le16((u16) offset); + rd_eeprom.byte_count = cpu_to_le16((u16) bytes); + + /* Send request to firmware */ + ret = mwifiex_send_cmd_sync(priv, HostCmd_CMD_802_11_EEPROM_ACCESS, + HostCmd_ACT_GEN_GET, 0, &rd_eeprom); + + if (!ret) + memcpy(value, rd_eeprom.value, MAX_EEPROM_DATA); + return ret; +} + +/* + * This function sets a generic IE. In addition to generic IE, it can + * also handle WPA, WPA2 and WAPI IEs. + */ +static int +mwifiex_set_gen_ie_helper(struct mwifiex_private *priv, u8 *ie_data_ptr, + u16 ie_len) +{ + int ret = 0; + struct ieee_types_vendor_header *pvendor_ie; + const u8 wpa_oui[] = { 0x00, 0x50, 0xf2, 0x01 }; + const u8 wps_oui[] = { 0x00, 0x50, 0xf2, 0x04 }; + + /* If the passed length is zero, reset the buffer */ + if (!ie_len) { + priv->gen_ie_buf_len = 0; + priv->wps.session_enable = false; + + return 0; + } else if (!ie_data_ptr) { + return -1; + } + pvendor_ie = (struct ieee_types_vendor_header *) ie_data_ptr; + /* Test to see if it is a WPA IE, if not, then it is a gen IE */ + if (((pvendor_ie->element_id == WLAN_EID_VENDOR_SPECIFIC) && + (!memcmp(pvendor_ie->oui, wpa_oui, sizeof(wpa_oui)))) || + (pvendor_ie->element_id == WLAN_EID_RSN)) { + + /* IE is a WPA/WPA2 IE so call set_wpa function */ + ret = mwifiex_set_wpa_ie_helper(priv, ie_data_ptr, ie_len); + priv->wps.session_enable = false; + + return ret; + } else if (pvendor_ie->element_id == WLAN_EID_BSS_AC_ACCESS_DELAY) { + /* IE is a WAPI IE so call set_wapi function */ + ret = mwifiex_set_wapi_ie(priv, ie_data_ptr, ie_len); + + return ret; + } + /* + * Verify that the passed length is not larger than the + * available space remaining in the buffer + */ + if (ie_len < (sizeof(priv->gen_ie_buf) - priv->gen_ie_buf_len)) { + + /* Test to see if it is a WPS IE, if so, enable + * wps session flag + */ + pvendor_ie = (struct ieee_types_vendor_header *) ie_data_ptr; + if ((pvendor_ie->element_id == WLAN_EID_VENDOR_SPECIFIC) && + (!memcmp(pvendor_ie->oui, wps_oui, sizeof(wps_oui)))) { + priv->wps.session_enable = true; + dev_dbg(priv->adapter->dev, + "info: WPS Session Enabled.\n"); + ret = mwifiex_set_wps_ie(priv, ie_data_ptr, ie_len); + } + + /* Append the passed data to the end of the + genIeBuffer */ + memcpy(priv->gen_ie_buf + priv->gen_ie_buf_len, ie_data_ptr, + ie_len); + /* Increment the stored buffer length by the + size passed */ + priv->gen_ie_buf_len += ie_len; + } else { + /* Passed data does not fit in the remaining + buffer space */ + ret = -1; + } + + /* Return 0, or -1 for error case */ + return ret; +} + +/* + * IOCTL request handler to set/get generic IE. + * + * In addition to various generic IEs, this function can also be + * used to set the ARP filter. + */ +static int mwifiex_misc_ioctl_gen_ie(struct mwifiex_private *priv, + struct mwifiex_ds_misc_gen_ie *gen_ie, + u16 action) +{ + struct mwifiex_adapter *adapter = priv->adapter; + + switch (gen_ie->type) { + case MWIFIEX_IE_TYPE_GEN_IE: + if (action == HostCmd_ACT_GEN_GET) { + gen_ie->len = priv->wpa_ie_len; + memcpy(gen_ie->ie_data, priv->wpa_ie, gen_ie->len); + } else { + mwifiex_set_gen_ie_helper(priv, gen_ie->ie_data, + (u16) gen_ie->len); + } + break; + case MWIFIEX_IE_TYPE_ARP_FILTER: + memset(adapter->arp_filter, 0, sizeof(adapter->arp_filter)); + if (gen_ie->len > ARP_FILTER_MAX_BUF_SIZE) { + adapter->arp_filter_size = 0; + dev_err(adapter->dev, "invalid ARP filter size\n"); + return -1; + } else { + memcpy(adapter->arp_filter, gen_ie->ie_data, + gen_ie->len); + adapter->arp_filter_size = gen_ie->len; + } + break; + default: + dev_err(adapter->dev, "invalid IE type\n"); + return -1; + } + return 0; +} + +/* + * Sends IOCTL request to set a generic IE. + * + * This function allocates the IOCTL request buffer, fills it + * with requisite parameters and calls the IOCTL handler. + */ +int +mwifiex_set_gen_ie(struct mwifiex_private *priv, u8 *ie, int ie_len) +{ + struct mwifiex_ds_misc_gen_ie gen_ie; + + if (ie_len > IEEE_MAX_IE_SIZE) + return -EFAULT; + + gen_ie.type = MWIFIEX_IE_TYPE_GEN_IE; + gen_ie.len = ie_len; + memcpy(gen_ie.ie_data, ie, ie_len); + if (mwifiex_misc_ioctl_gen_ie(priv, &gen_ie, HostCmd_ACT_GEN_SET)) + return -EFAULT; + + return 0; +} diff --git a/drivers/net/wireless/mwifiex/sta_rx.c b/drivers/net/wireless/mwifiex/sta_rx.c new file mode 100644 index 0000000..b5c1095 --- /dev/null +++ b/drivers/net/wireless/mwifiex/sta_rx.c @@ -0,0 +1,209 @@ +/* + * Marvell Wireless LAN device driver: station RX data handling + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "11n_aggr.h" +#include "11n_rxreorder.h" + +/* + * This function processes the received packet and forwards it + * to kernel/upper layer. + * + * This function parses through the received packet and determines + * if it is a debug packet or normal packet. + * + * For non-debug packets, the function chops off unnecessary leading + * header bytes, reconstructs the packet as an ethernet frame or + * 802.2/llc/snap frame as required, and sends it to kernel/upper layer. + * + * The completion callback is called after processing in complete. + */ +int mwifiex_process_rx_packet(struct mwifiex_private *priv, + struct sk_buff *skb) +{ + int ret; + struct rx_packet_hdr *rx_pkt_hdr; + struct rxpd *local_rx_pd; + int hdr_chop; + struct ethhdr *eth_hdr; + u8 rfc1042_eth_hdr[ETH_ALEN] = { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00 }; + + local_rx_pd = (struct rxpd *) (skb->data); + + rx_pkt_hdr = (void *)local_rx_pd + + le16_to_cpu(local_rx_pd->rx_pkt_offset); + + if (!memcmp(&rx_pkt_hdr->rfc1042_hdr, + rfc1042_eth_hdr, sizeof(rfc1042_eth_hdr))) { + /* + * Replace the 803 header and rfc1042 header (llc/snap) with an + * EthernetII header, keep the src/dst and snap_type + * (ethertype). + * The firmware only passes up SNAP frames converting + * all RX Data from 802.11 to 802.2/LLC/SNAP frames. + * To create the Ethernet II, just move the src, dst address + * right before the snap_type. + */ + eth_hdr = (struct ethhdr *) + ((u8 *) &rx_pkt_hdr->eth803_hdr + + sizeof(rx_pkt_hdr->eth803_hdr) + + sizeof(rx_pkt_hdr->rfc1042_hdr) + - sizeof(rx_pkt_hdr->eth803_hdr.h_dest) + - sizeof(rx_pkt_hdr->eth803_hdr.h_source) + - sizeof(rx_pkt_hdr->rfc1042_hdr.snap_type)); + + memcpy(eth_hdr->h_source, rx_pkt_hdr->eth803_hdr.h_source, + sizeof(eth_hdr->h_source)); + memcpy(eth_hdr->h_dest, rx_pkt_hdr->eth803_hdr.h_dest, + sizeof(eth_hdr->h_dest)); + + /* Chop off the rxpd + the excess memory from the 802.2/llc/snap + header that was removed. */ + hdr_chop = (u8 *) eth_hdr - (u8 *) local_rx_pd; + } else { + /* Chop off the rxpd */ + hdr_chop = (u8 *) &rx_pkt_hdr->eth803_hdr - + (u8 *) local_rx_pd; + } + + /* Chop off the leading header bytes so the it points to the start of + either the reconstructed EthII frame or the 802.2/llc/snap frame */ + skb_pull(skb, hdr_chop); + + priv->rxpd_rate = local_rx_pd->rx_rate; + + priv->rxpd_htinfo = local_rx_pd->ht_info; + + ret = mwifiex_recv_packet(priv, skb); + if (ret == -1) + dev_err(priv->adapter->dev, "recv packet failed\n"); + + return ret; +} + +/* + * This function processes the received buffer. + * + * The function looks into the RxPD and performs sanity tests on the + * received buffer to ensure its a valid packet, before processing it + * further. If the packet is determined to be aggregated, it is + * de-aggregated accordingly. Non-unicast packets are sent directly to + * the kernel/upper layers. Unicast packets are handed over to the + * Rx reordering routine if 11n is enabled. + * + * The completion callback is called after processing in complete. + */ +int mwifiex_process_sta_rx_packet(struct mwifiex_private *priv, + struct sk_buff *skb) +{ + struct mwifiex_adapter *adapter = priv->adapter; + int ret = 0; + struct rxpd *local_rx_pd; + struct rx_packet_hdr *rx_pkt_hdr; + u8 ta[ETH_ALEN]; + u16 rx_pkt_type, rx_pkt_offset, rx_pkt_length, seq_num; + + local_rx_pd = (struct rxpd *) (skb->data); + rx_pkt_type = le16_to_cpu(local_rx_pd->rx_pkt_type); + rx_pkt_offset = le16_to_cpu(local_rx_pd->rx_pkt_offset); + rx_pkt_length = le16_to_cpu(local_rx_pd->rx_pkt_length); + seq_num = le16_to_cpu(local_rx_pd->seq_num); + + rx_pkt_hdr = (void *)local_rx_pd + rx_pkt_offset; + + if ((rx_pkt_offset + rx_pkt_length) > (u16) skb->len) { + dev_err(adapter->dev, + "wrong rx packet: len=%d, rx_pkt_offset=%d, rx_pkt_length=%d\n", + skb->len, rx_pkt_offset, rx_pkt_length); + priv->stats.rx_dropped++; + + if (adapter->if_ops.data_complete) + adapter->if_ops.data_complete(adapter, skb); + else + dev_kfree_skb_any(skb); + + return ret; + } + + if (rx_pkt_type == PKT_TYPE_AMSDU) { + struct sk_buff_head list; + struct sk_buff *rx_skb; + + __skb_queue_head_init(&list); + + skb_pull(skb, rx_pkt_offset); + skb_trim(skb, rx_pkt_length); + + ieee80211_amsdu_to_8023s(skb, &list, priv->curr_addr, + priv->wdev->iftype, 0, false); + + while (!skb_queue_empty(&list)) { + rx_skb = __skb_dequeue(&list); + ret = mwifiex_recv_packet(priv, rx_skb); + if (ret == -1) + dev_err(adapter->dev, "Rx of A-MSDU failed"); + } + return 0; + } else if (rx_pkt_type == PKT_TYPE_MGMT) { + ret = mwifiex_process_mgmt_packet(priv, skb); + if (ret) + dev_err(adapter->dev, "Rx of mgmt packet failed"); + dev_kfree_skb_any(skb); + return ret; + } + + /* + * If the packet is not an unicast packet then send the packet + * directly to os. Don't pass thru rx reordering + */ + if (!IS_11N_ENABLED(priv) || + memcmp(priv->curr_addr, rx_pkt_hdr->eth803_hdr.h_dest, ETH_ALEN)) { + mwifiex_process_rx_packet(priv, skb); + return ret; + } + + if (mwifiex_queuing_ra_based(priv)) { + memcpy(ta, rx_pkt_hdr->eth803_hdr.h_source, ETH_ALEN); + } else { + if (rx_pkt_type != PKT_TYPE_BAR) + priv->rx_seq[local_rx_pd->priority] = seq_num; + memcpy(ta, priv->curr_bss_params.bss_descriptor.mac_address, + ETH_ALEN); + } + + /* Reorder and send to OS */ + ret = mwifiex_11n_rx_reorder_pkt(priv, seq_num, local_rx_pd->priority, + ta, (u8) rx_pkt_type, skb); + + if (ret || (rx_pkt_type == PKT_TYPE_BAR)) { + if (adapter->if_ops.data_complete) + adapter->if_ops.data_complete(adapter, skb); + else + dev_kfree_skb_any(skb); + } + + if (ret) + priv->stats.rx_dropped++; + + return ret; +} diff --git a/drivers/net/wireless/mwifiex/sta_tx.c b/drivers/net/wireless/mwifiex/sta_tx.c new file mode 100644 index 0000000..7b581af --- /dev/null +++ b/drivers/net/wireless/mwifiex/sta_tx.c @@ -0,0 +1,218 @@ +/* + * Marvell Wireless LAN device driver: station TX data handling + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" + +/* + * This function fills the TxPD for tx packets. + * + * The Tx buffer received by this function should already have the + * header space allocated for TxPD. + * + * This function inserts the TxPD in between interface header and actual + * data and adjusts the buffer pointers accordingly. + * + * The following TxPD fields are set by this function, as required - + * - BSS number + * - Tx packet length and offset + * - Priority + * - Packet delay + * - Priority specific Tx control + * - Flags + */ +void *mwifiex_process_sta_txpd(struct mwifiex_private *priv, + struct sk_buff *skb) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct txpd *local_tx_pd; + struct mwifiex_txinfo *tx_info = MWIFIEX_SKB_TXCB(skb); + u8 pad; + u16 pkt_type, pkt_offset; + + if (!skb->len) { + dev_err(adapter->dev, "Tx: bad packet length: %d\n", skb->len); + tx_info->status_code = -1; + return skb->data; + } + + pkt_type = mwifiex_is_skb_mgmt_frame(skb) ? PKT_TYPE_MGMT : 0; + + /* If skb->data is not aligned; add padding */ + pad = (4 - (((void *)skb->data - NULL) & 0x3)) % 4; + + BUG_ON(skb_headroom(skb) < (sizeof(*local_tx_pd) + INTF_HEADER_LEN + + pad)); + skb_push(skb, sizeof(*local_tx_pd) + pad); + + local_tx_pd = (struct txpd *) skb->data; + memset(local_tx_pd, 0, sizeof(struct txpd)); + local_tx_pd->bss_num = priv->bss_num; + local_tx_pd->bss_type = priv->bss_type; + local_tx_pd->tx_pkt_length = cpu_to_le16((u16)(skb->len - + (sizeof(struct txpd) + + pad))); + + local_tx_pd->priority = (u8) skb->priority; + local_tx_pd->pkt_delay_2ms = + mwifiex_wmm_compute_drv_pkt_delay(priv, skb); + + if (local_tx_pd->priority < + ARRAY_SIZE(priv->wmm.user_pri_pkt_tx_ctrl)) + /* + * Set the priority specific tx_control field, setting of 0 will + * cause the default value to be used later in this function + */ + local_tx_pd->tx_control = + cpu_to_le32(priv->wmm.user_pri_pkt_tx_ctrl[local_tx_pd-> + priority]); + + if (adapter->pps_uapsd_mode) { + if (mwifiex_check_last_packet_indication(priv)) { + adapter->tx_lock_flag = true; + local_tx_pd->flags = + MWIFIEX_TxPD_POWER_MGMT_LAST_PACKET; + } + } + + /* Offset of actual data */ + pkt_offset = sizeof(struct txpd) + pad; + if (pkt_type == PKT_TYPE_MGMT) { + /* Set the packet type and add header for management frame */ + local_tx_pd->tx_pkt_type = cpu_to_le16(pkt_type); + pkt_offset += MWIFIEX_MGMT_FRAME_HEADER_SIZE; + } + + local_tx_pd->tx_pkt_offset = cpu_to_le16(pkt_offset); + + /* make space for INTF_HEADER_LEN */ + skb_push(skb, INTF_HEADER_LEN); + + if (!local_tx_pd->tx_control) + /* TxCtrl set by user or default */ + local_tx_pd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl); + + return skb->data; +} + +/* + * This function tells firmware to send a NULL data packet. + * + * The function creates a NULL data packet with TxPD and sends to the + * firmware for transmission, with highest priority setting. + */ +int mwifiex_send_null_packet(struct mwifiex_private *priv, u8 flags) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct txpd *local_tx_pd; +/* sizeof(struct txpd) + Interface specific header */ +#define NULL_PACKET_HDR 64 + u32 data_len = NULL_PACKET_HDR; + struct sk_buff *skb; + int ret; + struct mwifiex_txinfo *tx_info = NULL; + + if (adapter->surprise_removed) + return -1; + + if (!priv->media_connected) + return -1; + + if (adapter->data_sent) + return -1; + + skb = dev_alloc_skb(data_len); + if (!skb) + return -1; + + tx_info = MWIFIEX_SKB_TXCB(skb); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + skb_reserve(skb, sizeof(struct txpd) + INTF_HEADER_LEN); + skb_push(skb, sizeof(struct txpd)); + + local_tx_pd = (struct txpd *) skb->data; + local_tx_pd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl); + local_tx_pd->flags = flags; + local_tx_pd->priority = WMM_HIGHEST_PRIORITY; + local_tx_pd->tx_pkt_offset = cpu_to_le16(sizeof(struct txpd)); + local_tx_pd->bss_num = priv->bss_num; + local_tx_pd->bss_type = priv->bss_type; + + if (adapter->iface_type == MWIFIEX_USB) { + ret = adapter->if_ops.host_to_card(adapter, MWIFIEX_USB_EP_DATA, + skb, NULL); + } else { + skb_push(skb, INTF_HEADER_LEN); + ret = adapter->if_ops.host_to_card(adapter, MWIFIEX_TYPE_DATA, + skb, NULL); + } + switch (ret) { + case -EBUSY: + adapter->data_sent = true; + /* Fall through FAILURE handling */ + case -1: + dev_kfree_skb_any(skb); + dev_err(adapter->dev, "%s: host_to_card failed: ret=%d\n", + __func__, ret); + adapter->dbg.num_tx_host_to_card_failure++; + break; + case 0: + dev_kfree_skb_any(skb); + dev_dbg(adapter->dev, "data: %s: host_to_card succeeded\n", + __func__); + adapter->tx_lock_flag = true; + break; + case -EINPROGRESS: + break; + default: + break; + } + + return ret; +} + +/* + * This function checks if we need to send last packet indication. + */ +u8 +mwifiex_check_last_packet_indication(struct mwifiex_private *priv) +{ + struct mwifiex_adapter *adapter = priv->adapter; + u8 ret = false; + + if (!adapter->sleep_period.period) + return ret; + if (mwifiex_wmm_lists_empty(adapter)) + ret = true; + + if (ret && !adapter->cmd_sent && !adapter->curr_cmd && + !is_command_pending(adapter)) { + adapter->delay_null_pkt = false; + ret = true; + } else { + ret = false; + adapter->delay_null_pkt = true; + } + return ret; +} diff --git a/drivers/net/wireless/mwifiex/txrx.c b/drivers/net/wireless/mwifiex/txrx.c new file mode 100644 index 0000000..8f923d0 --- /dev/null +++ b/drivers/net/wireless/mwifiex/txrx.c @@ -0,0 +1,201 @@ +/* + * Marvell Wireless LAN device driver: generic TX/RX data handling + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" + +/* + * This function processes the received buffer. + * + * Main responsibility of this function is to parse the RxPD to + * identify the correct interface this packet is headed for and + * forwarding it to the associated handling function, where the + * packet will be further processed and sent to kernel/upper layer + * if required. + */ +int mwifiex_handle_rx_packet(struct mwifiex_adapter *adapter, + struct sk_buff *skb) +{ + struct mwifiex_private *priv = + mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); + struct rxpd *local_rx_pd; + struct mwifiex_rxinfo *rx_info = MWIFIEX_SKB_RXCB(skb); + + local_rx_pd = (struct rxpd *) (skb->data); + /* Get the BSS number from rxpd, get corresponding priv */ + priv = mwifiex_get_priv_by_id(adapter, local_rx_pd->bss_num & + BSS_NUM_MASK, local_rx_pd->bss_type); + if (!priv) + priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); + + if (!priv) { + dev_err(adapter->dev, "data: priv not found. Drop RX packet\n"); + dev_kfree_skb_any(skb); + return -1; + } + + rx_info->bss_num = priv->bss_num; + rx_info->bss_type = priv->bss_type; + + if (priv->bss_role == MWIFIEX_BSS_ROLE_UAP) + return mwifiex_process_uap_rx_packet(priv, skb); + + return mwifiex_process_sta_rx_packet(priv, skb); +} +EXPORT_SYMBOL_GPL(mwifiex_handle_rx_packet); + +/* + * This function sends a packet to device. + * + * It processes the packet to add the TxPD, checks condition and + * sends the processed packet to firmware for transmission. + * + * On successful completion, the function calls the completion callback + * and logs the time. + */ +int mwifiex_process_tx(struct mwifiex_private *priv, struct sk_buff *skb, + struct mwifiex_tx_param *tx_param) +{ + int ret = -1; + struct mwifiex_adapter *adapter = priv->adapter; + u8 *head_ptr; + struct txpd *local_tx_pd = NULL; + + if (priv->bss_role == MWIFIEX_BSS_ROLE_UAP) + head_ptr = mwifiex_process_uap_txpd(priv, skb); + else + head_ptr = mwifiex_process_sta_txpd(priv, skb); + + if (head_ptr) { + if (GET_BSS_ROLE(priv) == MWIFIEX_BSS_ROLE_STA) + local_tx_pd = + (struct txpd *) (head_ptr + INTF_HEADER_LEN); + if (adapter->iface_type == MWIFIEX_USB) { + adapter->data_sent = true; + skb_pull(skb, INTF_HEADER_LEN); + ret = adapter->if_ops.host_to_card(adapter, + MWIFIEX_USB_EP_DATA, + skb, NULL); + } else { + ret = adapter->if_ops.host_to_card(adapter, + MWIFIEX_TYPE_DATA, + skb, tx_param); + } + } + + switch (ret) { + case -ENOSR: + dev_err(adapter->dev, "data: -ENOSR is returned\n"); + break; + case -EBUSY: + if ((GET_BSS_ROLE(priv) == MWIFIEX_BSS_ROLE_STA) && + (adapter->pps_uapsd_mode) && (adapter->tx_lock_flag)) { + priv->adapter->tx_lock_flag = false; + if (local_tx_pd) + local_tx_pd->flags = 0; + } + dev_dbg(adapter->dev, "data: -EBUSY is returned\n"); + break; + case -1: + if (adapter->iface_type != MWIFIEX_PCIE) + adapter->data_sent = false; + dev_err(adapter->dev, "mwifiex_write_data_async failed: 0x%X\n", + ret); + adapter->dbg.num_tx_host_to_card_failure++; + mwifiex_write_data_complete(adapter, skb, 0, ret); + break; + case -EINPROGRESS: + if (adapter->iface_type != MWIFIEX_PCIE) + adapter->data_sent = false; + break; + case 0: + mwifiex_write_data_complete(adapter, skb, 0, ret); + break; + default: + break; + } + + return ret; +} + +/* + * Packet send completion callback handler. + * + * It either frees the buffer directly or forwards it to another + * completion callback which checks conditions, updates statistics, + * wakes up stalled traffic queue if required, and then frees the buffer. + */ +int mwifiex_write_data_complete(struct mwifiex_adapter *adapter, + struct sk_buff *skb, int aggr, int status) +{ + struct mwifiex_private *priv; + struct mwifiex_txinfo *tx_info; + struct netdev_queue *txq; + int index; + + if (!skb) + return 0; + + tx_info = MWIFIEX_SKB_TXCB(skb); + priv = mwifiex_get_priv_by_id(adapter, tx_info->bss_num, + tx_info->bss_type); + if (!priv) + goto done; + + if (adapter->iface_type == MWIFIEX_USB) + adapter->data_sent = false; + + mwifiex_set_trans_start(priv->netdev); + if (!status) { + priv->stats.tx_packets++; + priv->stats.tx_bytes += skb->len; + if (priv->tx_timeout_cnt) + priv->tx_timeout_cnt = 0; + } else { + priv->stats.tx_errors++; + } + + if (tx_info->flags & MWIFIEX_BUF_FLAG_BRIDGED_PKT) + atomic_dec_return(&adapter->pending_bridged_pkts); + + if (aggr) + /* For skb_aggr, do not wake up tx queue */ + goto done; + + atomic_dec(&adapter->tx_pending); + + index = mwifiex_1d_to_wmm_queue[skb->priority]; + if (atomic_dec_return(&priv->wmm_tx_pending[index]) < LOW_TX_PENDING) { + txq = netdev_get_tx_queue(priv->netdev, index); + if (netif_tx_queue_stopped(txq)) { + netif_tx_wake_queue(txq); + dev_dbg(adapter->dev, "wake queue: %d\n", index); + } + } +done: + dev_kfree_skb_any(skb); + + return 0; +} +EXPORT_SYMBOL_GPL(mwifiex_write_data_complete); + diff --git a/drivers/net/wireless/mwifiex/uap_cmd.c b/drivers/net/wireless/mwifiex/uap_cmd.c new file mode 100644 index 0000000..b04b1db --- /dev/null +++ b/drivers/net/wireless/mwifiex/uap_cmd.c @@ -0,0 +1,720 @@ +/* + * Marvell Wireless LAN device driver: AP specific command handling + * + * Copyright (C) 2012, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "main.h" +#include "11ac.h" + +/* This function parses security related parameters from cfg80211_ap_settings + * and sets into FW understandable bss_config structure. + */ +int mwifiex_set_secure_params(struct mwifiex_private *priv, + struct mwifiex_uap_bss_param *bss_config, + struct cfg80211_ap_settings *params) { + int i; + struct mwifiex_wep_key wep_key; + + if (!params->privacy) { + bss_config->protocol = PROTOCOL_NO_SECURITY; + bss_config->key_mgmt = KEY_MGMT_NONE; + bss_config->wpa_cfg.length = 0; + priv->sec_info.wep_enabled = 0; + priv->sec_info.wpa_enabled = 0; + priv->sec_info.wpa2_enabled = 0; + + return 0; + } + + switch (params->auth_type) { + case NL80211_AUTHTYPE_OPEN_SYSTEM: + bss_config->auth_mode = WLAN_AUTH_OPEN; + break; + case NL80211_AUTHTYPE_SHARED_KEY: + bss_config->auth_mode = WLAN_AUTH_SHARED_KEY; + break; + case NL80211_AUTHTYPE_NETWORK_EAP: + bss_config->auth_mode = WLAN_AUTH_LEAP; + break; + default: + bss_config->auth_mode = MWIFIEX_AUTH_MODE_AUTO; + break; + } + + bss_config->key_mgmt_operation |= KEY_MGMT_ON_HOST; + + for (i = 0; i < params->crypto.n_akm_suites; i++) { + switch (params->crypto.akm_suites[i]) { + case WLAN_AKM_SUITE_8021X: + if (params->crypto.wpa_versions & + NL80211_WPA_VERSION_1) { + bss_config->protocol = PROTOCOL_WPA; + bss_config->key_mgmt = KEY_MGMT_EAP; + } + if (params->crypto.wpa_versions & + NL80211_WPA_VERSION_2) { + bss_config->protocol |= PROTOCOL_WPA2; + bss_config->key_mgmt = KEY_MGMT_EAP; + } + break; + case WLAN_AKM_SUITE_PSK: + if (params->crypto.wpa_versions & + NL80211_WPA_VERSION_1) { + bss_config->protocol = PROTOCOL_WPA; + bss_config->key_mgmt = KEY_MGMT_PSK; + } + if (params->crypto.wpa_versions & + NL80211_WPA_VERSION_2) { + bss_config->protocol |= PROTOCOL_WPA2; + bss_config->key_mgmt = KEY_MGMT_PSK; + } + break; + default: + break; + } + } + for (i = 0; i < params->crypto.n_ciphers_pairwise; i++) { + switch (params->crypto.ciphers_pairwise[i]) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + break; + case WLAN_CIPHER_SUITE_TKIP: + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_1) + bss_config->wpa_cfg.pairwise_cipher_wpa |= + CIPHER_TKIP; + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_2) + bss_config->wpa_cfg.pairwise_cipher_wpa2 |= + CIPHER_TKIP; + break; + case WLAN_CIPHER_SUITE_CCMP: + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_1) + bss_config->wpa_cfg.pairwise_cipher_wpa |= + CIPHER_AES_CCMP; + if (params->crypto.wpa_versions & NL80211_WPA_VERSION_2) + bss_config->wpa_cfg.pairwise_cipher_wpa2 |= + CIPHER_AES_CCMP; + default: + break; + } + } + + switch (params->crypto.cipher_group) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + if (priv->sec_info.wep_enabled) { + bss_config->protocol = PROTOCOL_STATIC_WEP; + bss_config->key_mgmt = KEY_MGMT_NONE; + bss_config->wpa_cfg.length = 0; + + for (i = 0; i < NUM_WEP_KEYS; i++) { + wep_key = priv->wep_key[i]; + bss_config->wep_cfg[i].key_index = i; + + if (priv->wep_key_curr_index == i) + bss_config->wep_cfg[i].is_default = 1; + else + bss_config->wep_cfg[i].is_default = 0; + + bss_config->wep_cfg[i].length = + wep_key.key_length; + memcpy(&bss_config->wep_cfg[i].key, + &wep_key.key_material, + wep_key.key_length); + } + } + break; + case WLAN_CIPHER_SUITE_TKIP: + bss_config->wpa_cfg.group_cipher = CIPHER_TKIP; + break; + case WLAN_CIPHER_SUITE_CCMP: + bss_config->wpa_cfg.group_cipher = CIPHER_AES_CCMP; + break; + default: + break; + } + + return 0; +} + +/* This function updates 11n related parameters from IE and sets them into + * bss_config structure. + */ +void +mwifiex_set_ht_params(struct mwifiex_private *priv, + struct mwifiex_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + const u8 *ht_ie; + + if (!ISSUPP_11NENABLED(priv->adapter->fw_cap_info)) + return; + + ht_ie = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, params->beacon.tail, + params->beacon.tail_len); + if (ht_ie) { + memcpy(&bss_cfg->ht_cap, ht_ie + 2, + sizeof(struct ieee80211_ht_cap)); + priv->ap_11n_enabled = 1; + } else { + memset(&bss_cfg->ht_cap , 0, sizeof(struct ieee80211_ht_cap)); + bss_cfg->ht_cap.cap_info = cpu_to_le16(MWIFIEX_DEF_HT_CAP); + bss_cfg->ht_cap.ampdu_params_info = MWIFIEX_DEF_AMPDU; + } + + return; +} + +/* This function updates 11ac related parameters from IE + * and sets them into bss_config structure. + */ +void mwifiex_set_vht_params(struct mwifiex_private *priv, + struct mwifiex_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + const u8 *vht_ie; + + vht_ie = cfg80211_find_ie(WLAN_EID_VHT_CAPABILITY, params->beacon.tail, + params->beacon.tail_len); + if (vht_ie) { + memcpy(&bss_cfg->vht_cap, vht_ie + 2, + sizeof(struct ieee80211_vht_cap)); + priv->ap_11ac_enabled = 1; + } else { + priv->ap_11ac_enabled = 0; + } + + return; +} + +/* Enable VHT only when cfg80211_ap_settings has VHT IE. + * Otherwise disable VHT. + */ +void mwifiex_set_vht_width(struct mwifiex_private *priv, + enum nl80211_chan_width width, + bool ap_11ac_enable) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct mwifiex_11ac_vht_cfg vht_cfg; + + vht_cfg.band_config = VHT_CFG_5GHZ; + vht_cfg.cap_info = adapter->hw_dot_11ac_dev_cap; + + if (!ap_11ac_enable) { + vht_cfg.mcs_tx_set = DISABLE_VHT_MCS_SET; + vht_cfg.mcs_rx_set = DISABLE_VHT_MCS_SET; + } else { + vht_cfg.mcs_tx_set = DEFAULT_VHT_MCS_SET; + vht_cfg.mcs_rx_set = DEFAULT_VHT_MCS_SET; + } + + vht_cfg.misc_config = VHT_CAP_UAP_ONLY; + + if (ap_11ac_enable && width >= NL80211_CHAN_WIDTH_80) + vht_cfg.misc_config |= VHT_BW_80_160_80P80; + + mwifiex_send_cmd_sync(priv, HostCmd_CMD_11AC_CFG, + HostCmd_ACT_GEN_SET, 0, &vht_cfg); + + return; +} + +/* This function finds supported rates IE from beacon parameter and sets + * these rates into bss_config structure. + */ +void +mwifiex_set_uap_rates(struct mwifiex_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + struct ieee_types_header *rate_ie; + int var_offset = offsetof(struct ieee80211_mgmt, u.beacon.variable); + const u8 *var_pos = params->beacon.head + var_offset; + int len = params->beacon.head_len - var_offset; + u8 rate_len = 0; + + rate_ie = (void *)cfg80211_find_ie(WLAN_EID_SUPP_RATES, var_pos, len); + if (rate_ie) { + memcpy(bss_cfg->rates, rate_ie + 1, rate_ie->len); + rate_len = rate_ie->len; + } + + rate_ie = (void *)cfg80211_find_ie(WLAN_EID_EXT_SUPP_RATES, + params->beacon.tail, + params->beacon.tail_len); + if (rate_ie) + memcpy(bss_cfg->rates + rate_len, rate_ie + 1, rate_ie->len); + + return; +} + +/* This function initializes some of mwifiex_uap_bss_param variables. + * This helps FW in ignoring invalid values. These values may or may not + * be get updated to valid ones at later stage. + */ +void mwifiex_set_sys_config_invalid_data(struct mwifiex_uap_bss_param *config) +{ + config->bcast_ssid_ctl = 0x7F; + config->radio_ctl = 0x7F; + config->dtim_period = 0x7F; + config->beacon_period = 0x7FFF; + config->auth_mode = 0x7F; + config->rts_threshold = 0x7FFF; + config->frag_threshold = 0x7FFF; + config->retry_limit = 0x7F; + config->qos_info = 0xFF; +} + +/* This function parses BSS related parameters from structure + * and prepares TLVs specific to WPA/WPA2 security. + * These TLVs are appended to command buffer. + */ +static void +mwifiex_uap_bss_wpa(u8 **tlv_buf, void *cmd_buf, u16 *param_size) +{ + struct host_cmd_tlv_pwk_cipher *pwk_cipher; + struct host_cmd_tlv_gwk_cipher *gwk_cipher; + struct host_cmd_tlv_passphrase *passphrase; + struct host_cmd_tlv_akmp *tlv_akmp; + struct mwifiex_uap_bss_param *bss_cfg = cmd_buf; + u16 cmd_size = *param_size; + u8 *tlv = *tlv_buf; + + tlv_akmp = (struct host_cmd_tlv_akmp *)tlv; + tlv_akmp->tlv.type = cpu_to_le16(TLV_TYPE_UAP_AKMP); + tlv_akmp->tlv.len = cpu_to_le16(sizeof(struct host_cmd_tlv_akmp) - + sizeof(struct host_cmd_tlv)); + tlv_akmp->key_mgmt_operation = cpu_to_le16(bss_cfg->key_mgmt_operation); + tlv_akmp->key_mgmt = cpu_to_le16(bss_cfg->key_mgmt); + cmd_size += sizeof(struct host_cmd_tlv_akmp); + tlv += sizeof(struct host_cmd_tlv_akmp); + + if (bss_cfg->wpa_cfg.pairwise_cipher_wpa & VALID_CIPHER_BITMAP) { + pwk_cipher = (struct host_cmd_tlv_pwk_cipher *)tlv; + pwk_cipher->tlv.type = cpu_to_le16(TLV_TYPE_PWK_CIPHER); + pwk_cipher->tlv.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_pwk_cipher) - + sizeof(struct host_cmd_tlv)); + pwk_cipher->proto = cpu_to_le16(PROTOCOL_WPA); + pwk_cipher->cipher = bss_cfg->wpa_cfg.pairwise_cipher_wpa; + cmd_size += sizeof(struct host_cmd_tlv_pwk_cipher); + tlv += sizeof(struct host_cmd_tlv_pwk_cipher); + } + + if (bss_cfg->wpa_cfg.pairwise_cipher_wpa2 & VALID_CIPHER_BITMAP) { + pwk_cipher = (struct host_cmd_tlv_pwk_cipher *)tlv; + pwk_cipher->tlv.type = cpu_to_le16(TLV_TYPE_PWK_CIPHER); + pwk_cipher->tlv.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_pwk_cipher) - + sizeof(struct host_cmd_tlv)); + pwk_cipher->proto = cpu_to_le16(PROTOCOL_WPA2); + pwk_cipher->cipher = bss_cfg->wpa_cfg.pairwise_cipher_wpa2; + cmd_size += sizeof(struct host_cmd_tlv_pwk_cipher); + tlv += sizeof(struct host_cmd_tlv_pwk_cipher); + } + + if (bss_cfg->wpa_cfg.group_cipher & VALID_CIPHER_BITMAP) { + gwk_cipher = (struct host_cmd_tlv_gwk_cipher *)tlv; + gwk_cipher->tlv.type = cpu_to_le16(TLV_TYPE_GWK_CIPHER); + gwk_cipher->tlv.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_gwk_cipher) - + sizeof(struct host_cmd_tlv)); + gwk_cipher->cipher = bss_cfg->wpa_cfg.group_cipher; + cmd_size += sizeof(struct host_cmd_tlv_gwk_cipher); + tlv += sizeof(struct host_cmd_tlv_gwk_cipher); + } + + if (bss_cfg->wpa_cfg.length) { + passphrase = (struct host_cmd_tlv_passphrase *)tlv; + passphrase->tlv.type = cpu_to_le16(TLV_TYPE_UAP_WPA_PASSPHRASE); + passphrase->tlv.len = cpu_to_le16(bss_cfg->wpa_cfg.length); + memcpy(passphrase->passphrase, bss_cfg->wpa_cfg.passphrase, + bss_cfg->wpa_cfg.length); + cmd_size += sizeof(struct host_cmd_tlv) + + bss_cfg->wpa_cfg.length; + tlv += sizeof(struct host_cmd_tlv) + bss_cfg->wpa_cfg.length; + } + + *param_size = cmd_size; + *tlv_buf = tlv; + + return; +} + +/* This function parses WMM related parameters from cfg80211_ap_settings + * structure and updates bss_config structure. + */ +void +mwifiex_set_wmm_params(struct mwifiex_private *priv, + struct mwifiex_uap_bss_param *bss_cfg, + struct cfg80211_ap_settings *params) +{ + const u8 *vendor_ie; + struct ieee_types_header *wmm_ie; + u8 wmm_oui[] = {0x00, 0x50, 0xf2, 0x02}; + + vendor_ie = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WMM, + params->beacon.tail, + params->beacon.tail_len); + if (vendor_ie) { + wmm_ie = (struct ieee_types_header *)vendor_ie; + memcpy(&bss_cfg->wmm_info, wmm_ie + 1, + sizeof(bss_cfg->wmm_info)); + priv->wmm_enabled = 1; + } else { + memset(&bss_cfg->wmm_info, 0, sizeof(bss_cfg->wmm_info)); + memcpy(&bss_cfg->wmm_info.oui, wmm_oui, sizeof(wmm_oui)); + bss_cfg->wmm_info.subtype = MWIFIEX_WMM_SUBTYPE; + bss_cfg->wmm_info.version = MWIFIEX_WMM_VERSION; + priv->wmm_enabled = 0; + } + + bss_cfg->qos_info = 0x00; + return; +} +/* This function parses BSS related parameters from structure + * and prepares TLVs specific to WEP encryption. + * These TLVs are appended to command buffer. + */ +static void +mwifiex_uap_bss_wep(u8 **tlv_buf, void *cmd_buf, u16 *param_size) +{ + struct host_cmd_tlv_wep_key *wep_key; + u16 cmd_size = *param_size; + int i; + u8 *tlv = *tlv_buf; + struct mwifiex_uap_bss_param *bss_cfg = cmd_buf; + + for (i = 0; i < NUM_WEP_KEYS; i++) { + if (bss_cfg->wep_cfg[i].length && + (bss_cfg->wep_cfg[i].length == WLAN_KEY_LEN_WEP40 || + bss_cfg->wep_cfg[i].length == WLAN_KEY_LEN_WEP104)) { + wep_key = (struct host_cmd_tlv_wep_key *)tlv; + wep_key->tlv.type = cpu_to_le16(TLV_TYPE_UAP_WEP_KEY); + wep_key->tlv.len = + cpu_to_le16(bss_cfg->wep_cfg[i].length + 2); + wep_key->key_index = bss_cfg->wep_cfg[i].key_index; + wep_key->is_default = bss_cfg->wep_cfg[i].is_default; + memcpy(wep_key->key, bss_cfg->wep_cfg[i].key, + bss_cfg->wep_cfg[i].length); + cmd_size += sizeof(struct host_cmd_tlv) + 2 + + bss_cfg->wep_cfg[i].length; + tlv += sizeof(struct host_cmd_tlv) + 2 + + bss_cfg->wep_cfg[i].length; + } + } + + *param_size = cmd_size; + *tlv_buf = tlv; + + return; +} + +/* This function parses BSS related parameters from structure + * and prepares TLVs. These TLVs are appended to command buffer. +*/ +static int +mwifiex_uap_bss_param_prepare(u8 *tlv, void *cmd_buf, u16 *param_size) +{ + struct host_cmd_tlv_dtim_period *dtim_period; + struct host_cmd_tlv_beacon_period *beacon_period; + struct host_cmd_tlv_ssid *ssid; + struct host_cmd_tlv_bcast_ssid *bcast_ssid; + struct host_cmd_tlv_channel_band *chan_band; + struct host_cmd_tlv_frag_threshold *frag_threshold; + struct host_cmd_tlv_rts_threshold *rts_threshold; + struct host_cmd_tlv_retry_limit *retry_limit; + struct host_cmd_tlv_encrypt_protocol *encrypt_protocol; + struct host_cmd_tlv_auth_type *auth_type; + struct host_cmd_tlv_rates *tlv_rates; + struct host_cmd_tlv_ageout_timer *ao_timer, *ps_ao_timer; + struct mwifiex_ie_types_htcap *htcap; + struct mwifiex_ie_types_wmmcap *wmm_cap; + struct mwifiex_uap_bss_param *bss_cfg = cmd_buf; + int i; + u16 cmd_size = *param_size; + + if (bss_cfg->ssid.ssid_len) { + ssid = (struct host_cmd_tlv_ssid *)tlv; + ssid->tlv.type = cpu_to_le16(TLV_TYPE_UAP_SSID); + ssid->tlv.len = cpu_to_le16((u16)bss_cfg->ssid.ssid_len); + memcpy(ssid->ssid, bss_cfg->ssid.ssid, bss_cfg->ssid.ssid_len); + cmd_size += sizeof(struct host_cmd_tlv) + + bss_cfg->ssid.ssid_len; + tlv += sizeof(struct host_cmd_tlv) + bss_cfg->ssid.ssid_len; + + bcast_ssid = (struct host_cmd_tlv_bcast_ssid *)tlv; + bcast_ssid->tlv.type = cpu_to_le16(TLV_TYPE_UAP_BCAST_SSID); + bcast_ssid->tlv.len = + cpu_to_le16(sizeof(bcast_ssid->bcast_ctl)); + bcast_ssid->bcast_ctl = bss_cfg->bcast_ssid_ctl; + cmd_size += sizeof(struct host_cmd_tlv_bcast_ssid); + tlv += sizeof(struct host_cmd_tlv_bcast_ssid); + } + if (bss_cfg->rates[0]) { + tlv_rates = (struct host_cmd_tlv_rates *)tlv; + tlv_rates->tlv.type = cpu_to_le16(TLV_TYPE_UAP_RATES); + + for (i = 0; i < MWIFIEX_SUPPORTED_RATES && bss_cfg->rates[i]; + i++) + tlv_rates->rates[i] = bss_cfg->rates[i]; + + tlv_rates->tlv.len = cpu_to_le16(i); + cmd_size += sizeof(struct host_cmd_tlv_rates) + i; + tlv += sizeof(struct host_cmd_tlv_rates) + i; + } + if (bss_cfg->channel && + ((bss_cfg->band_cfg == BAND_CONFIG_BG && + bss_cfg->channel <= MAX_CHANNEL_BAND_BG) || + (bss_cfg->band_cfg == BAND_CONFIG_A && + bss_cfg->channel <= MAX_CHANNEL_BAND_A))) { + chan_band = (struct host_cmd_tlv_channel_band *)tlv; + chan_band->tlv.type = cpu_to_le16(TLV_TYPE_CHANNELBANDLIST); + chan_band->tlv.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_channel_band) - + sizeof(struct host_cmd_tlv)); + chan_band->band_config = bss_cfg->band_cfg; + chan_band->channel = bss_cfg->channel; + cmd_size += sizeof(struct host_cmd_tlv_channel_band); + tlv += sizeof(struct host_cmd_tlv_channel_band); + } + if (bss_cfg->beacon_period >= MIN_BEACON_PERIOD && + bss_cfg->beacon_period <= MAX_BEACON_PERIOD) { + beacon_period = (struct host_cmd_tlv_beacon_period *)tlv; + beacon_period->tlv.type = + cpu_to_le16(TLV_TYPE_UAP_BEACON_PERIOD); + beacon_period->tlv.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_beacon_period) - + sizeof(struct host_cmd_tlv)); + beacon_period->period = cpu_to_le16(bss_cfg->beacon_period); + cmd_size += sizeof(struct host_cmd_tlv_beacon_period); + tlv += sizeof(struct host_cmd_tlv_beacon_period); + } + if (bss_cfg->dtim_period >= MIN_DTIM_PERIOD && + bss_cfg->dtim_period <= MAX_DTIM_PERIOD) { + dtim_period = (struct host_cmd_tlv_dtim_period *)tlv; + dtim_period->tlv.type = cpu_to_le16(TLV_TYPE_UAP_DTIM_PERIOD); + dtim_period->tlv.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_dtim_period) - + sizeof(struct host_cmd_tlv)); + dtim_period->period = bss_cfg->dtim_period; + cmd_size += sizeof(struct host_cmd_tlv_dtim_period); + tlv += sizeof(struct host_cmd_tlv_dtim_period); + } + if (bss_cfg->rts_threshold <= MWIFIEX_RTS_MAX_VALUE) { + rts_threshold = (struct host_cmd_tlv_rts_threshold *)tlv; + rts_threshold->tlv.type = + cpu_to_le16(TLV_TYPE_UAP_RTS_THRESHOLD); + rts_threshold->tlv.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_rts_threshold) - + sizeof(struct host_cmd_tlv)); + rts_threshold->rts_thr = cpu_to_le16(bss_cfg->rts_threshold); + cmd_size += sizeof(struct host_cmd_tlv_frag_threshold); + tlv += sizeof(struct host_cmd_tlv_frag_threshold); + } + if ((bss_cfg->frag_threshold >= MWIFIEX_FRAG_MIN_VALUE) && + (bss_cfg->frag_threshold <= MWIFIEX_FRAG_MAX_VALUE)) { + frag_threshold = (struct host_cmd_tlv_frag_threshold *)tlv; + frag_threshold->tlv.type = + cpu_to_le16(TLV_TYPE_UAP_FRAG_THRESHOLD); + frag_threshold->tlv.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_frag_threshold) - + sizeof(struct host_cmd_tlv)); + frag_threshold->frag_thr = cpu_to_le16(bss_cfg->frag_threshold); + cmd_size += sizeof(struct host_cmd_tlv_frag_threshold); + tlv += sizeof(struct host_cmd_tlv_frag_threshold); + } + if (bss_cfg->retry_limit <= MWIFIEX_RETRY_LIMIT) { + retry_limit = (struct host_cmd_tlv_retry_limit *)tlv; + retry_limit->tlv.type = cpu_to_le16(TLV_TYPE_UAP_RETRY_LIMIT); + retry_limit->tlv.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_retry_limit) - + sizeof(struct host_cmd_tlv)); + retry_limit->limit = (u8)bss_cfg->retry_limit; + cmd_size += sizeof(struct host_cmd_tlv_retry_limit); + tlv += sizeof(struct host_cmd_tlv_retry_limit); + } + if ((bss_cfg->protocol & PROTOCOL_WPA) || + (bss_cfg->protocol & PROTOCOL_WPA2) || + (bss_cfg->protocol & PROTOCOL_EAP)) + mwifiex_uap_bss_wpa(&tlv, cmd_buf, &cmd_size); + else + mwifiex_uap_bss_wep(&tlv, cmd_buf, &cmd_size); + + if ((bss_cfg->auth_mode <= WLAN_AUTH_SHARED_KEY) || + (bss_cfg->auth_mode == MWIFIEX_AUTH_MODE_AUTO)) { + auth_type = (struct host_cmd_tlv_auth_type *)tlv; + auth_type->tlv.type = cpu_to_le16(TLV_TYPE_AUTH_TYPE); + auth_type->tlv.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_auth_type) - + sizeof(struct host_cmd_tlv)); + auth_type->auth_type = (u8)bss_cfg->auth_mode; + cmd_size += sizeof(struct host_cmd_tlv_auth_type); + tlv += sizeof(struct host_cmd_tlv_auth_type); + } + if (bss_cfg->protocol) { + encrypt_protocol = (struct host_cmd_tlv_encrypt_protocol *)tlv; + encrypt_protocol->tlv.type = + cpu_to_le16(TLV_TYPE_UAP_ENCRY_PROTOCOL); + encrypt_protocol->tlv.len = + cpu_to_le16(sizeof(struct host_cmd_tlv_encrypt_protocol) + - sizeof(struct host_cmd_tlv)); + encrypt_protocol->proto = cpu_to_le16(bss_cfg->protocol); + cmd_size += sizeof(struct host_cmd_tlv_encrypt_protocol); + tlv += sizeof(struct host_cmd_tlv_encrypt_protocol); + } + + if (bss_cfg->ht_cap.cap_info) { + htcap = (struct mwifiex_ie_types_htcap *)tlv; + htcap->header.type = cpu_to_le16(WLAN_EID_HT_CAPABILITY); + htcap->header.len = + cpu_to_le16(sizeof(struct ieee80211_ht_cap)); + htcap->ht_cap.cap_info = bss_cfg->ht_cap.cap_info; + htcap->ht_cap.ampdu_params_info = + bss_cfg->ht_cap.ampdu_params_info; + memcpy(&htcap->ht_cap.mcs, &bss_cfg->ht_cap.mcs, + sizeof(struct ieee80211_mcs_info)); + htcap->ht_cap.extended_ht_cap_info = + bss_cfg->ht_cap.extended_ht_cap_info; + htcap->ht_cap.tx_BF_cap_info = bss_cfg->ht_cap.tx_BF_cap_info; + htcap->ht_cap.antenna_selection_info = + bss_cfg->ht_cap.antenna_selection_info; + cmd_size += sizeof(struct mwifiex_ie_types_htcap); + tlv += sizeof(struct mwifiex_ie_types_htcap); + } + + if (bss_cfg->wmm_info.qos_info != 0xFF) { + wmm_cap = (struct mwifiex_ie_types_wmmcap *)tlv; + wmm_cap->header.type = cpu_to_le16(WLAN_EID_VENDOR_SPECIFIC); + wmm_cap->header.len = cpu_to_le16(sizeof(wmm_cap->wmm_info)); + memcpy(&wmm_cap->wmm_info, &bss_cfg->wmm_info, + sizeof(wmm_cap->wmm_info)); + cmd_size += sizeof(struct mwifiex_ie_types_wmmcap); + tlv += sizeof(struct mwifiex_ie_types_wmmcap); + } + + if (bss_cfg->sta_ao_timer) { + ao_timer = (struct host_cmd_tlv_ageout_timer *)tlv; + ao_timer->tlv.type = cpu_to_le16(TLV_TYPE_UAP_AO_TIMER); + ao_timer->tlv.len = cpu_to_le16(sizeof(*ao_timer) - + sizeof(struct host_cmd_tlv)); + ao_timer->sta_ao_timer = cpu_to_le32(bss_cfg->sta_ao_timer); + cmd_size += sizeof(*ao_timer); + tlv += sizeof(*ao_timer); + } + + if (bss_cfg->ps_sta_ao_timer) { + ps_ao_timer = (struct host_cmd_tlv_ageout_timer *)tlv; + ps_ao_timer->tlv.type = cpu_to_le16(TLV_TYPE_UAP_PS_AO_TIMER); + ps_ao_timer->tlv.len = cpu_to_le16(sizeof(*ps_ao_timer) - + sizeof(struct host_cmd_tlv)); + ps_ao_timer->sta_ao_timer = + cpu_to_le32(bss_cfg->ps_sta_ao_timer); + cmd_size += sizeof(*ps_ao_timer); + tlv += sizeof(*ps_ao_timer); + } + + *param_size = cmd_size; + + return 0; +} + +/* This function parses custom IEs from IE list and prepares command buffer */ +static int mwifiex_uap_custom_ie_prepare(u8 *tlv, void *cmd_buf, u16 *ie_size) +{ + struct mwifiex_ie_list *ap_ie = cmd_buf; + struct host_cmd_tlv *tlv_ie = (struct host_cmd_tlv *)tlv; + + if (!ap_ie || !ap_ie->len || !ap_ie->ie_list) + return -1; + + *ie_size += le16_to_cpu(ap_ie->len) + sizeof(struct host_cmd_tlv); + + tlv_ie->type = cpu_to_le16(TLV_TYPE_MGMT_IE); + tlv_ie->len = ap_ie->len; + tlv += sizeof(struct host_cmd_tlv); + + memcpy(tlv, ap_ie->ie_list, le16_to_cpu(ap_ie->len)); + + return 0; +} + +/* Parse AP config structure and prepare TLV based command structure + * to be sent to FW for uAP configuration + */ +static int +mwifiex_cmd_uap_sys_config(struct host_cmd_ds_command *cmd, u16 cmd_action, + u32 type, void *cmd_buf) +{ + u8 *tlv; + u16 cmd_size, param_size, ie_size; + struct host_cmd_ds_sys_config *sys_cfg; + + cmd->command = cpu_to_le16(HostCmd_CMD_UAP_SYS_CONFIG); + cmd_size = (u16)(sizeof(struct host_cmd_ds_sys_config) + S_DS_GEN); + sys_cfg = (struct host_cmd_ds_sys_config *)&cmd->params.uap_sys_config; + sys_cfg->action = cpu_to_le16(cmd_action); + tlv = sys_cfg->tlv; + + switch (type) { + case UAP_BSS_PARAMS_I: + param_size = cmd_size; + if (mwifiex_uap_bss_param_prepare(tlv, cmd_buf, ¶m_size)) + return -1; + cmd->size = cpu_to_le16(param_size); + break; + case UAP_CUSTOM_IE_I: + ie_size = cmd_size; + if (mwifiex_uap_custom_ie_prepare(tlv, cmd_buf, &ie_size)) + return -1; + cmd->size = cpu_to_le16(ie_size); + break; + default: + return -1; + } + + return 0; +} + +/* This function prepares the AP specific commands before sending them + * to the firmware. + * This is a generic function which calls specific command preparation + * routines based upon the command number. + */ +int mwifiex_uap_prepare_cmd(struct mwifiex_private *priv, u16 cmd_no, + u16 cmd_action, u32 type, + void *data_buf, void *cmd_buf) +{ + struct host_cmd_ds_command *cmd = cmd_buf; + + switch (cmd_no) { + case HostCmd_CMD_UAP_SYS_CONFIG: + if (mwifiex_cmd_uap_sys_config(cmd, cmd_action, type, data_buf)) + return -1; + break; + case HostCmd_CMD_UAP_BSS_START: + case HostCmd_CMD_UAP_BSS_STOP: + cmd->command = cpu_to_le16(cmd_no); + cmd->size = cpu_to_le16(S_DS_GEN); + break; + default: + dev_err(priv->adapter->dev, + "PREP_CMD: unknown cmd %#x\n", cmd_no); + return -1; + } + + return 0; +} diff --git a/drivers/net/wireless/mwifiex/uap_event.c b/drivers/net/wireless/mwifiex/uap_event.c new file mode 100644 index 0000000..21c640d --- /dev/null +++ b/drivers/net/wireless/mwifiex/uap_event.c @@ -0,0 +1,297 @@ +/* + * Marvell Wireless LAN device driver: AP event handling + * + * Copyright (C) 2012, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "main.h" +#include "11n.h" + +/* + * This function will return the pointer to station entry in station list + * table which matches specified mac address. + * This function should be called after acquiring RA list spinlock. + * NULL is returned if station entry is not found in associated STA list. + */ +struct mwifiex_sta_node * +mwifiex_get_sta_entry(struct mwifiex_private *priv, u8 *mac) +{ + struct mwifiex_sta_node *node; + + if (!mac) + return NULL; + + list_for_each_entry(node, &priv->sta_list, list) { + if (!memcmp(node->mac_addr, mac, ETH_ALEN)) + return node; + } + + return NULL; +} + +/* + * This function will add a sta_node entry to associated station list + * table with the given mac address. + * If entry exist already, existing entry is returned. + * If received mac address is NULL, NULL is returned. + */ +static struct mwifiex_sta_node * +mwifiex_add_sta_entry(struct mwifiex_private *priv, u8 *mac) +{ + struct mwifiex_sta_node *node; + unsigned long flags; + + if (!mac) + return NULL; + + spin_lock_irqsave(&priv->sta_list_spinlock, flags); + node = mwifiex_get_sta_entry(priv, mac); + if (node) + goto done; + + node = kzalloc(sizeof(struct mwifiex_sta_node), GFP_ATOMIC); + if (!node) + goto done; + + memcpy(node->mac_addr, mac, ETH_ALEN); + list_add_tail(&node->list, &priv->sta_list); + +done: + spin_unlock_irqrestore(&priv->sta_list_spinlock, flags); + return node; +} + +/* + * This function will search for HT IE in association request IEs + * and set station HT parameters accordingly. + */ +static void +mwifiex_set_sta_ht_cap(struct mwifiex_private *priv, const u8 *ies, + int ies_len, struct mwifiex_sta_node *node) +{ + const struct ieee80211_ht_cap *ht_cap; + + if (!ies) + return; + + ht_cap = (void *)cfg80211_find_ie(WLAN_EID_HT_CAPABILITY, ies, ies_len); + if (ht_cap) { + node->is_11n_enabled = 1; + node->max_amsdu = le16_to_cpu(ht_cap->cap_info) & + IEEE80211_HT_CAP_MAX_AMSDU ? + MWIFIEX_TX_DATA_BUF_SIZE_8K : + MWIFIEX_TX_DATA_BUF_SIZE_4K; + } else { + node->is_11n_enabled = 0; + } + + return; +} + +/* + * This function will delete a station entry from station list + */ +static void mwifiex_del_sta_entry(struct mwifiex_private *priv, u8 *mac) +{ + struct mwifiex_sta_node *node, *tmp; + unsigned long flags; + + spin_lock_irqsave(&priv->sta_list_spinlock, flags); + + node = mwifiex_get_sta_entry(priv, mac); + if (node) { + list_for_each_entry_safe(node, tmp, &priv->sta_list, + list) { + list_del(&node->list); + kfree(node); + } + } + + spin_unlock_irqrestore(&priv->sta_list_spinlock, flags); + return; +} + +/* + * This function will delete all stations from associated station list. + */ +static void mwifiex_del_all_sta_list(struct mwifiex_private *priv) +{ + struct mwifiex_sta_node *node, *tmp; + unsigned long flags; + + spin_lock_irqsave(&priv->sta_list_spinlock, flags); + + list_for_each_entry_safe(node, tmp, &priv->sta_list, list) { + list_del(&node->list); + kfree(node); + } + + INIT_LIST_HEAD(&priv->sta_list); + spin_unlock_irqrestore(&priv->sta_list_spinlock, flags); + return; +} + +/* + * This function handles AP interface specific events generated by firmware. + * + * Event specific routines are called by this function based + * upon the generated event cause. + * + * + * Events supported for AP - + * - EVENT_UAP_STA_ASSOC + * - EVENT_UAP_STA_DEAUTH + * - EVENT_UAP_BSS_ACTIVE + * - EVENT_UAP_BSS_START + * - EVENT_UAP_BSS_IDLE + * - EVENT_UAP_MIC_COUNTERMEASURES: + */ +int mwifiex_process_uap_event(struct mwifiex_private *priv) +{ + struct mwifiex_adapter *adapter = priv->adapter; + int len, i; + u32 eventcause = adapter->event_cause; + struct station_info sinfo; + struct mwifiex_assoc_event *event; + struct mwifiex_sta_node *node; + u8 *deauth_mac; + struct host_cmd_ds_11n_batimeout *ba_timeout; + u16 ctrl; + + switch (eventcause) { + case EVENT_UAP_STA_ASSOC: + memset(&sinfo, 0, sizeof(sinfo)); + event = (struct mwifiex_assoc_event *) + (adapter->event_body + MWIFIEX_UAP_EVENT_EXTRA_HEADER); + if (le16_to_cpu(event->type) == TLV_TYPE_UAP_MGMT_FRAME) { + len = -1; + + if (ieee80211_is_assoc_req(event->frame_control)) + len = 0; + else if (ieee80211_is_reassoc_req(event->frame_control)) + /* There will be ETH_ALEN bytes of + * current_ap_addr before the re-assoc ies. + */ + len = ETH_ALEN; + + if (len != -1) { + sinfo.filled = STATION_INFO_ASSOC_REQ_IES; + sinfo.assoc_req_ies = &event->data[len]; + len = (u8 *)sinfo.assoc_req_ies - + (u8 *)&event->frame_control; + sinfo.assoc_req_ies_len = + le16_to_cpu(event->len) - (u16)len; + } + } + cfg80211_new_sta(priv->netdev, event->sta_addr, &sinfo, + GFP_KERNEL); + + node = mwifiex_add_sta_entry(priv, event->sta_addr); + if (!node) { + dev_warn(adapter->dev, + "could not create station entry!\n"); + return -1; + } + + if (!priv->ap_11n_enabled) + break; + + mwifiex_set_sta_ht_cap(priv, sinfo.assoc_req_ies, + sinfo.assoc_req_ies_len, node); + + for (i = 0; i < MAX_NUM_TID; i++) { + if (node->is_11n_enabled) + node->ampdu_sta[i] = + priv->aggr_prio_tbl[i].ampdu_user; + else + node->ampdu_sta[i] = BA_STREAM_NOT_ALLOWED; + } + memset(node->rx_seq, 0xff, sizeof(node->rx_seq)); + break; + case EVENT_UAP_STA_DEAUTH: + deauth_mac = adapter->event_body + + MWIFIEX_UAP_EVENT_EXTRA_HEADER; + cfg80211_del_sta(priv->netdev, deauth_mac, GFP_KERNEL); + + if (priv->ap_11n_enabled) { + mwifiex_11n_del_rx_reorder_tbl_by_ta(priv, deauth_mac); + mwifiex_del_tx_ba_stream_tbl_by_ra(priv, deauth_mac); + } + mwifiex_del_sta_entry(priv, deauth_mac); + break; + case EVENT_UAP_BSS_IDLE: + priv->media_connected = false; + if (netif_carrier_ok(priv->netdev)) + netif_carrier_off(priv->netdev); + mwifiex_stop_net_dev_queue(priv->netdev, adapter); + + mwifiex_clean_txrx(priv); + mwifiex_del_all_sta_list(priv); + break; + case EVENT_UAP_BSS_ACTIVE: + priv->media_connected = true; + if (!netif_carrier_ok(priv->netdev)) + netif_carrier_on(priv->netdev); + mwifiex_wake_up_net_dev_queue(priv->netdev, adapter); + break; + case EVENT_UAP_BSS_START: + dev_dbg(adapter->dev, "AP EVENT: event id: %#x\n", eventcause); + memcpy(priv->netdev->dev_addr, adapter->event_body + 2, + ETH_ALEN); + break; + case EVENT_UAP_MIC_COUNTERMEASURES: + /* For future development */ + dev_dbg(adapter->dev, "AP EVENT: event id: %#x\n", eventcause); + break; + case EVENT_AMSDU_AGGR_CTRL: + ctrl = le16_to_cpu(*(__le16 *)adapter->event_body); + dev_dbg(adapter->dev, "event: AMSDU_AGGR_CTRL %d\n", ctrl); + + if (priv->media_connected) { + adapter->tx_buf_size = + min_t(u16, adapter->curr_tx_buf_size, ctrl); + dev_dbg(adapter->dev, "event: tx_buf_size %d\n", + adapter->tx_buf_size); + } + break; + case EVENT_ADDBA: + dev_dbg(adapter->dev, "event: ADDBA Request\n"); + if (priv->media_connected) + mwifiex_send_cmd_async(priv, HostCmd_CMD_11N_ADDBA_RSP, + HostCmd_ACT_GEN_SET, 0, + adapter->event_body); + break; + case EVENT_DELBA: + dev_dbg(adapter->dev, "event: DELBA Request\n"); + if (priv->media_connected) + mwifiex_11n_delete_ba_stream(priv, adapter->event_body); + break; + case EVENT_BA_STREAM_TIEMOUT: + dev_dbg(adapter->dev, "event: BA Stream timeout\n"); + if (priv->media_connected) { + ba_timeout = (void *)adapter->event_body; + mwifiex_11n_ba_stream_timeout(priv, ba_timeout); + } + break; + default: + dev_dbg(adapter->dev, "event: unknown event id: %#x\n", + eventcause); + break; + } + + return 0; +} diff --git a/drivers/net/wireless/mwifiex/uap_txrx.c b/drivers/net/wireless/mwifiex/uap_txrx.c new file mode 100644 index 0000000..a018e42 --- /dev/null +++ b/drivers/net/wireless/mwifiex/uap_txrx.c @@ -0,0 +1,333 @@ +/* + * Marvell Wireless LAN device driver: AP TX and RX data handling + * + * Copyright (C) 2012, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "main.h" +#include "wmm.h" +#include "11n_aggr.h" +#include "11n_rxreorder.h" + +static void mwifiex_uap_queue_bridged_pkt(struct mwifiex_private *priv, + struct sk_buff *skb) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct uap_rxpd *uap_rx_pd; + struct rx_packet_hdr *rx_pkt_hdr; + struct sk_buff *new_skb; + struct mwifiex_txinfo *tx_info; + int hdr_chop; + struct timeval tv; + u8 rfc1042_eth_hdr[ETH_ALEN] = { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00 }; + + uap_rx_pd = (struct uap_rxpd *)(skb->data); + rx_pkt_hdr = (void *)uap_rx_pd + le16_to_cpu(uap_rx_pd->rx_pkt_offset); + + if ((atomic_read(&adapter->pending_bridged_pkts) >= + MWIFIEX_BRIDGED_PKTS_THRESHOLD)) { + dev_err(priv->adapter->dev, + "Tx: Bridge packet limit reached. Drop packet!\n"); + kfree_skb(skb); + return; + } + + if (!memcmp(&rx_pkt_hdr->rfc1042_hdr, + rfc1042_eth_hdr, sizeof(rfc1042_eth_hdr))) + /* Chop off the rxpd + the excess memory from + * 802.2/llc/snap header that was removed. + */ + hdr_chop = (u8 *)eth_hdr - (u8 *)uap_rx_pd; + else + /* Chop off the rxpd */ + hdr_chop = (u8 *)&rx_pkt_hdr->eth803_hdr - (u8 *)uap_rx_pd; + + /* Chop off the leading header bytes so the it points + * to the start of either the reconstructed EthII frame + * or the 802.2/llc/snap frame. + */ + skb_pull(skb, hdr_chop); + + if (skb_headroom(skb) < MWIFIEX_MIN_DATA_HEADER_LEN) { + dev_dbg(priv->adapter->dev, + "data: Tx: insufficient skb headroom %d\n", + skb_headroom(skb)); + /* Insufficient skb headroom - allocate a new skb */ + new_skb = + skb_realloc_headroom(skb, MWIFIEX_MIN_DATA_HEADER_LEN); + if (unlikely(!new_skb)) { + dev_err(priv->adapter->dev, + "Tx: cannot allocate new_skb\n"); + kfree_skb(skb); + priv->stats.tx_dropped++; + return; + } + + kfree_skb(skb); + skb = new_skb; + dev_dbg(priv->adapter->dev, "info: new skb headroom %d\n", + skb_headroom(skb)); + } + + tx_info = MWIFIEX_SKB_TXCB(skb); + tx_info->bss_num = priv->bss_num; + tx_info->bss_type = priv->bss_type; + tx_info->flags |= MWIFIEX_BUF_FLAG_BRIDGED_PKT; + + do_gettimeofday(&tv); + skb->tstamp = timeval_to_ktime(tv); + mwifiex_wmm_add_buf_txqueue(priv, skb); + atomic_inc(&adapter->tx_pending); + atomic_inc(&adapter->pending_bridged_pkts); + + if ((atomic_read(&adapter->tx_pending) >= MAX_TX_PENDING)) { + mwifiex_set_trans_start(priv->netdev); + mwifiex_stop_net_dev_queue(priv->netdev, priv->adapter); + } + return; +} + +/* + * This function contains logic for AP packet forwarding. + * + * If a packet is multicast/broadcast, it is sent to kernel/upper layer + * as well as queued back to AP TX queue so that it can be sent to other + * associated stations. + * If a packet is unicast and RA is present in associated station list, + * it is again requeued into AP TX queue. + * If a packet is unicast and RA is not in associated station list, + * packet is forwarded to kernel to handle routing logic. + */ +int mwifiex_handle_uap_rx_forward(struct mwifiex_private *priv, + struct sk_buff *skb) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct uap_rxpd *uap_rx_pd; + struct rx_packet_hdr *rx_pkt_hdr; + u8 ra[ETH_ALEN]; + struct sk_buff *skb_uap; + + uap_rx_pd = (struct uap_rxpd *)(skb->data); + rx_pkt_hdr = (void *)uap_rx_pd + le16_to_cpu(uap_rx_pd->rx_pkt_offset); + + /* don't do packet forwarding in disconnected state */ + if (!priv->media_connected) { + dev_err(adapter->dev, "drop packet in disconnected state.\n"); + dev_kfree_skb_any(skb); + return 0; + } + + memcpy(ra, rx_pkt_hdr->eth803_hdr.h_dest, ETH_ALEN); + + if (is_multicast_ether_addr(ra)) { + skb_uap = skb_copy(skb, GFP_ATOMIC); + mwifiex_uap_queue_bridged_pkt(priv, skb_uap); + } else { + if (mwifiex_get_sta_entry(priv, ra)) { + /* Requeue Intra-BSS packet */ + mwifiex_uap_queue_bridged_pkt(priv, skb); + return 0; + } + } + + /* Forward unicat/Inter-BSS packets to kernel. */ + return mwifiex_process_rx_packet(priv, skb); +} + +/* + * This function processes the packet received on AP interface. + * + * The function looks into the RxPD and performs sanity tests on the + * received buffer to ensure its a valid packet before processing it + * further. If the packet is determined to be aggregated, it is + * de-aggregated accordingly. Then skb is passed to AP packet forwarding logic. + * + * The completion callback is called after processing is complete. + */ +int mwifiex_process_uap_rx_packet(struct mwifiex_private *priv, + struct sk_buff *skb) +{ + struct mwifiex_adapter *adapter = priv->adapter; + int ret; + struct uap_rxpd *uap_rx_pd; + struct rx_packet_hdr *rx_pkt_hdr; + u16 rx_pkt_type; + u8 ta[ETH_ALEN], pkt_type; + struct mwifiex_sta_node *node; + + uap_rx_pd = (struct uap_rxpd *)(skb->data); + rx_pkt_type = le16_to_cpu(uap_rx_pd->rx_pkt_type); + rx_pkt_hdr = (void *)uap_rx_pd + le16_to_cpu(uap_rx_pd->rx_pkt_offset); + + if ((le16_to_cpu(uap_rx_pd->rx_pkt_offset) + + le16_to_cpu(uap_rx_pd->rx_pkt_length)) > (u16) skb->len) { + dev_err(adapter->dev, + "wrong rx packet: len=%d, offset=%d, length=%d\n", + skb->len, le16_to_cpu(uap_rx_pd->rx_pkt_offset), + le16_to_cpu(uap_rx_pd->rx_pkt_length)); + priv->stats.rx_dropped++; + + if (adapter->if_ops.data_complete) + adapter->if_ops.data_complete(adapter, skb); + else + dev_kfree_skb_any(skb); + + return 0; + } + + if (le16_to_cpu(uap_rx_pd->rx_pkt_type) == PKT_TYPE_AMSDU) { + struct sk_buff_head list; + struct sk_buff *rx_skb; + + __skb_queue_head_init(&list); + skb_pull(skb, le16_to_cpu(uap_rx_pd->rx_pkt_offset)); + skb_trim(skb, le16_to_cpu(uap_rx_pd->rx_pkt_length)); + + ieee80211_amsdu_to_8023s(skb, &list, priv->curr_addr, + priv->wdev->iftype, 0, false); + + while (!skb_queue_empty(&list)) { + rx_skb = __skb_dequeue(&list); + ret = mwifiex_recv_packet(priv, rx_skb); + if (ret) + dev_err(adapter->dev, + "AP:Rx A-MSDU failed"); + } + + return 0; + } else if (rx_pkt_type == PKT_TYPE_MGMT) { + ret = mwifiex_process_mgmt_packet(priv, skb); + if (ret) + dev_err(adapter->dev, "Rx of mgmt packet failed"); + dev_kfree_skb_any(skb); + return ret; + } + + memcpy(ta, rx_pkt_hdr->eth803_hdr.h_source, ETH_ALEN); + + if (rx_pkt_type != PKT_TYPE_BAR && uap_rx_pd->priority < MAX_NUM_TID) { + node = mwifiex_get_sta_entry(priv, ta); + if (node) + node->rx_seq[uap_rx_pd->priority] = + le16_to_cpu(uap_rx_pd->seq_num); + } + + if (!priv->ap_11n_enabled || + (!mwifiex_11n_get_rx_reorder_tbl(priv, uap_rx_pd->priority, ta) && + (le16_to_cpu(uap_rx_pd->rx_pkt_type) != PKT_TYPE_AMSDU))) { + ret = mwifiex_handle_uap_rx_forward(priv, skb); + return ret; + } + + /* Reorder and send to kernel */ + pkt_type = (u8)le16_to_cpu(uap_rx_pd->rx_pkt_type); + ret = mwifiex_11n_rx_reorder_pkt(priv, le16_to_cpu(uap_rx_pd->seq_num), + uap_rx_pd->priority, ta, pkt_type, + skb); + + if (ret || (rx_pkt_type == PKT_TYPE_BAR)) { + if (adapter->if_ops.data_complete) + adapter->if_ops.data_complete(adapter, skb); + else + dev_kfree_skb_any(skb); + } + + if (ret) + priv->stats.rx_dropped++; + + return ret; +} + +/* + * This function fills the TxPD for AP tx packets. + * + * The Tx buffer received by this function should already have the + * header space allocated for TxPD. + * + * This function inserts the TxPD in between interface header and actual + * data and adjusts the buffer pointers accordingly. + * + * The following TxPD fields are set by this function, as required - + * - BSS number + * - Tx packet length and offset + * - Priority + * - Packet delay + * - Priority specific Tx control + * - Flags + */ +void *mwifiex_process_uap_txpd(struct mwifiex_private *priv, + struct sk_buff *skb) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct uap_txpd *txpd; + struct mwifiex_txinfo *tx_info = MWIFIEX_SKB_TXCB(skb); + int pad, len; + u16 pkt_type; + + if (!skb->len) { + dev_err(adapter->dev, "Tx: bad packet length: %d\n", skb->len); + tx_info->status_code = -1; + return skb->data; + } + + pkt_type = mwifiex_is_skb_mgmt_frame(skb) ? PKT_TYPE_MGMT : 0; + + /* If skb->data is not aligned, add padding */ + pad = (4 - (((void *)skb->data - NULL) & 0x3)) % 4; + + len = sizeof(*txpd) + pad; + + BUG_ON(skb_headroom(skb) < len + INTF_HEADER_LEN); + + skb_push(skb, len); + + txpd = (struct uap_txpd *)skb->data; + memset(txpd, 0, sizeof(*txpd)); + txpd->bss_num = priv->bss_num; + txpd->bss_type = priv->bss_type; + txpd->tx_pkt_length = cpu_to_le16((u16)(skb->len - len)); + + txpd->priority = (u8)skb->priority; + txpd->pkt_delay_2ms = mwifiex_wmm_compute_drv_pkt_delay(priv, skb); + + if (txpd->priority < ARRAY_SIZE(priv->wmm.user_pri_pkt_tx_ctrl)) + /* + * Set the priority specific tx_control field, setting of 0 will + * cause the default value to be used later in this function. + */ + txpd->tx_control = + cpu_to_le32(priv->wmm.user_pri_pkt_tx_ctrl[txpd->priority]); + + /* Offset of actual data */ + if (pkt_type == PKT_TYPE_MGMT) { + /* Set the packet type and add header for management frame */ + txpd->tx_pkt_type = cpu_to_le16(pkt_type); + len += MWIFIEX_MGMT_FRAME_HEADER_SIZE; + } + + txpd->tx_pkt_offset = cpu_to_le16(len); + + /* make space for INTF_HEADER_LEN */ + skb_push(skb, INTF_HEADER_LEN); + + if (!txpd->tx_control) + /* TxCtrl set by user or default */ + txpd->tx_control = cpu_to_le32(priv->pkt_tx_ctrl); + + return skb->data; +} diff --git a/drivers/net/wireless/mwifiex/usb.c b/drivers/net/wireless/mwifiex/usb.c new file mode 100644 index 0000000..928f605 --- /dev/null +++ b/drivers/net/wireless/mwifiex/usb.c @@ -0,0 +1,1034 @@ +/* + * Marvell Wireless LAN device driver: USB specific handling + * + * Copyright (C) 2012, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "main.h" +#include "usb.h" +#include + +#define USB_VERSION "1.0" + +static const char usbdriver_name[] = "usb8797"; + +static u8 user_rmmod; +static struct mwifiex_if_ops usb_ops; +static struct semaphore add_remove_card_sem; + +static struct usb_device_id mwifiex_usb_table[] = { + {USB_DEVICE(USB8797_VID, USB8797_PID_1)}, + {USB_DEVICE_AND_INTERFACE_INFO(USB8797_VID, USB8797_PID_2, + USB_CLASS_VENDOR_SPEC, + USB_SUBCLASS_VENDOR_SPEC, 0xff)}, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, mwifiex_usb_table); + +static int mwifiex_usb_submit_rx_urb(struct urb_context *ctx, int size); + +/* This function handles received packet. Necessary action is taken based on + * cmd/event/data. + */ +static int mwifiex_usb_recv(struct mwifiex_adapter *adapter, + struct sk_buff *skb, u8 ep) +{ + struct device *dev = adapter->dev; + u32 recv_type; + __le32 tmp; + int ret; + + if (adapter->hs_activated) + mwifiex_process_hs_config(adapter); + + if (skb->len < INTF_HEADER_LEN) { + dev_err(dev, "%s: invalid skb->len\n", __func__); + return -1; + } + + switch (ep) { + case MWIFIEX_USB_EP_CMD_EVENT: + dev_dbg(dev, "%s: EP_CMD_EVENT\n", __func__); + skb_copy_from_linear_data(skb, &tmp, INTF_HEADER_LEN); + recv_type = le32_to_cpu(tmp); + skb_pull(skb, INTF_HEADER_LEN); + + switch (recv_type) { + case MWIFIEX_USB_TYPE_CMD: + if (skb->len > MWIFIEX_SIZE_OF_CMD_BUFFER) { + dev_err(dev, "CMD: skb->len too large\n"); + ret = -1; + goto exit_restore_skb; + } else if (!adapter->curr_cmd) { + dev_dbg(dev, "CMD: no curr_cmd\n"); + if (adapter->ps_state == PS_STATE_SLEEP_CFM) { + mwifiex_process_sleep_confirm_resp( + adapter, skb->data, + skb->len); + ret = 0; + goto exit_restore_skb; + } + ret = -1; + goto exit_restore_skb; + } + + adapter->curr_cmd->resp_skb = skb; + adapter->cmd_resp_received = true; + break; + case MWIFIEX_USB_TYPE_EVENT: + if (skb->len < sizeof(u32)) { + dev_err(dev, "EVENT: skb->len too small\n"); + ret = -1; + goto exit_restore_skb; + } + skb_copy_from_linear_data(skb, &tmp, sizeof(u32)); + adapter->event_cause = le32_to_cpu(tmp); + dev_dbg(dev, "event_cause %#x\n", adapter->event_cause); + + if (skb->len > MAX_EVENT_SIZE) { + dev_err(dev, "EVENT: event body too large\n"); + ret = -1; + goto exit_restore_skb; + } + + memcpy(adapter->event_body, skb->data + + MWIFIEX_EVENT_HEADER_LEN, skb->len); + + adapter->event_received = true; + adapter->event_skb = skb; + break; + default: + dev_err(dev, "unknown recv_type %#x\n", recv_type); + return -1; + } + break; + case MWIFIEX_USB_EP_DATA: + dev_dbg(dev, "%s: EP_DATA\n", __func__); + if (skb->len > MWIFIEX_RX_DATA_BUF_SIZE) { + dev_err(dev, "DATA: skb->len too large\n"); + return -1; + } + skb_queue_tail(&adapter->usb_rx_data_q, skb); + adapter->data_received = true; + break; + default: + dev_err(dev, "%s: unknown endport %#x\n", __func__, ep); + return -1; + } + + return -EINPROGRESS; + +exit_restore_skb: + /* The buffer will be reused for further cmds/events */ + skb_push(skb, INTF_HEADER_LEN); + + return ret; +} + +static void mwifiex_usb_rx_complete(struct urb *urb) +{ + struct urb_context *context = (struct urb_context *)urb->context; + struct mwifiex_adapter *adapter = context->adapter; + struct sk_buff *skb = context->skb; + struct usb_card_rec *card; + int recv_length = urb->actual_length; + int size, status; + + if (!adapter || !adapter->card) { + pr_err("mwifiex adapter or card structure is not valid\n"); + return; + } + + card = (struct usb_card_rec *)adapter->card; + if (card->rx_cmd_ep == context->ep) + atomic_dec(&card->rx_cmd_urb_pending); + else + atomic_dec(&card->rx_data_urb_pending); + + if (recv_length) { + if (urb->status || (adapter->surprise_removed)) { + dev_err(adapter->dev, + "URB status is failed: %d\n", urb->status); + /* Do not free skb in case of command ep */ + if (card->rx_cmd_ep != context->ep) + dev_kfree_skb_any(skb); + goto setup_for_next; + } + if (skb->len > recv_length) + skb_trim(skb, recv_length); + else + skb_put(skb, recv_length - skb->len); + + atomic_inc(&adapter->rx_pending); + status = mwifiex_usb_recv(adapter, skb, context->ep); + + dev_dbg(adapter->dev, "info: recv_length=%d, status=%d\n", + recv_length, status); + if (status == -EINPROGRESS) { + queue_work(adapter->workqueue, &adapter->main_work); + + /* urb for data_ep is re-submitted now; + * urb for cmd_ep will be re-submitted in callback + * mwifiex_usb_recv_complete + */ + if (card->rx_cmd_ep == context->ep) + return; + } else { + atomic_dec(&adapter->rx_pending); + if (status == -1) + dev_err(adapter->dev, + "received data processing failed!\n"); + + /* Do not free skb in case of command ep */ + if (card->rx_cmd_ep != context->ep) + dev_kfree_skb_any(skb); + } + } else if (urb->status) { + if (!adapter->is_suspended) { + dev_warn(adapter->dev, + "Card is removed: %d\n", urb->status); + adapter->surprise_removed = true; + } + dev_kfree_skb_any(skb); + return; + } else { + /* Do not free skb in case of command ep */ + if (card->rx_cmd_ep != context->ep) + dev_kfree_skb_any(skb); + + /* fall through setup_for_next */ + } + +setup_for_next: + if (card->rx_cmd_ep == context->ep) + size = MWIFIEX_RX_CMD_BUF_SIZE; + else + size = MWIFIEX_RX_DATA_BUF_SIZE; + + mwifiex_usb_submit_rx_urb(context, size); + + return; +} + +static void mwifiex_usb_tx_complete(struct urb *urb) +{ + struct urb_context *context = (struct urb_context *)(urb->context); + struct mwifiex_adapter *adapter = context->adapter; + struct usb_card_rec *card = adapter->card; + + dev_dbg(adapter->dev, "%s: status: %d\n", __func__, urb->status); + + if (context->ep == card->tx_cmd_ep) { + dev_dbg(adapter->dev, "%s: CMD\n", __func__); + atomic_dec(&card->tx_cmd_urb_pending); + adapter->cmd_sent = false; + } else { + dev_dbg(adapter->dev, "%s: DATA\n", __func__); + atomic_dec(&card->tx_data_urb_pending); + mwifiex_write_data_complete(adapter, context->skb, 0, + urb->status ? -1 : 0); + } + + queue_work(adapter->workqueue, &adapter->main_work); + + return; +} + +static int mwifiex_usb_submit_rx_urb(struct urb_context *ctx, int size) +{ + struct mwifiex_adapter *adapter = ctx->adapter; + struct usb_card_rec *card = (struct usb_card_rec *)adapter->card; + + if (card->rx_cmd_ep != ctx->ep) { + ctx->skb = dev_alloc_skb(size); + if (!ctx->skb) { + dev_err(adapter->dev, + "%s: dev_alloc_skb failed\n", __func__); + return -ENOMEM; + } + } + + usb_fill_bulk_urb(ctx->urb, card->udev, + usb_rcvbulkpipe(card->udev, ctx->ep), ctx->skb->data, + size, mwifiex_usb_rx_complete, (void *)ctx); + + if (card->rx_cmd_ep == ctx->ep) + atomic_inc(&card->rx_cmd_urb_pending); + else + atomic_inc(&card->rx_data_urb_pending); + + if (usb_submit_urb(ctx->urb, GFP_ATOMIC)) { + dev_err(adapter->dev, "usb_submit_urb failed\n"); + dev_kfree_skb_any(ctx->skb); + ctx->skb = NULL; + + if (card->rx_cmd_ep == ctx->ep) + atomic_dec(&card->rx_cmd_urb_pending); + else + atomic_dec(&card->rx_data_urb_pending); + + return -1; + } + + return 0; +} + +static void mwifiex_usb_free(struct usb_card_rec *card) +{ + int i; + + if (atomic_read(&card->rx_cmd_urb_pending) && card->rx_cmd.urb) + usb_kill_urb(card->rx_cmd.urb); + + usb_free_urb(card->rx_cmd.urb); + card->rx_cmd.urb = NULL; + + if (atomic_read(&card->rx_data_urb_pending)) + for (i = 0; i < MWIFIEX_RX_DATA_URB; i++) + if (card->rx_data_list[i].urb) + usb_kill_urb(card->rx_data_list[i].urb); + + for (i = 0; i < MWIFIEX_RX_DATA_URB; i++) { + usb_free_urb(card->rx_data_list[i].urb); + card->rx_data_list[i].urb = NULL; + } + + for (i = 0; i < MWIFIEX_TX_DATA_URB; i++) { + usb_free_urb(card->tx_data_list[i].urb); + card->tx_data_list[i].urb = NULL; + } + + usb_free_urb(card->tx_cmd.urb); + card->tx_cmd.urb = NULL; + + return; +} + +/* This function probes an mwifiex device and registers it. It allocates + * the card structure, initiates the device registration and initialization + * procedure by adding a logical interface. + */ +static int mwifiex_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_host_interface *iface_desc = intf->cur_altsetting; + struct usb_endpoint_descriptor *epd; + int ret, i; + struct usb_card_rec *card; + u16 id_vendor, id_product, bcd_device, bcd_usb; + + card = kzalloc(sizeof(struct usb_card_rec), GFP_KERNEL); + if (!card) + return -ENOMEM; + + id_vendor = le16_to_cpu(udev->descriptor.idVendor); + id_product = le16_to_cpu(udev->descriptor.idProduct); + bcd_device = le16_to_cpu(udev->descriptor.bcdDevice); + bcd_usb = le16_to_cpu(udev->descriptor.bcdUSB); + pr_debug("info: VID/PID = %X/%X, Boot2 version = %X\n", + id_vendor, id_product, bcd_device); + + /* PID_1 is used for firmware downloading only */ + if (id_product == USB8797_PID_1) + card->usb_boot_state = USB8797_FW_DNLD; + else + card->usb_boot_state = USB8797_FW_READY; + + card->udev = udev; + card->intf = intf; + + pr_debug("info: bcdUSB=%#x Device Class=%#x SubClass=%#x Protocol=%#x\n", + udev->descriptor.bcdUSB, udev->descriptor.bDeviceClass, + udev->descriptor.bDeviceSubClass, + udev->descriptor.bDeviceProtocol); + + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + epd = &iface_desc->endpoint[i].desc; + if (usb_endpoint_dir_in(epd) && + usb_endpoint_num(epd) == MWIFIEX_USB_EP_CMD_EVENT && + usb_endpoint_xfer_bulk(epd)) { + pr_debug("info: bulk IN: max pkt size: %d, addr: %d\n", + le16_to_cpu(epd->wMaxPacketSize), + epd->bEndpointAddress); + card->rx_cmd_ep = usb_endpoint_num(epd); + atomic_set(&card->rx_cmd_urb_pending, 0); + } + if (usb_endpoint_dir_in(epd) && + usb_endpoint_num(epd) == MWIFIEX_USB_EP_DATA && + usb_endpoint_xfer_bulk(epd)) { + pr_debug("info: bulk IN: max pkt size: %d, addr: %d\n", + le16_to_cpu(epd->wMaxPacketSize), + epd->bEndpointAddress); + card->rx_data_ep = usb_endpoint_num(epd); + atomic_set(&card->rx_data_urb_pending, 0); + } + if (usb_endpoint_dir_out(epd) && + usb_endpoint_num(epd) == MWIFIEX_USB_EP_DATA && + usb_endpoint_xfer_bulk(epd)) { + pr_debug("info: bulk OUT: max pkt size: %d, addr: %d\n", + le16_to_cpu(epd->wMaxPacketSize), + epd->bEndpointAddress); + card->tx_data_ep = usb_endpoint_num(epd); + atomic_set(&card->tx_data_urb_pending, 0); + } + if (usb_endpoint_dir_out(epd) && + usb_endpoint_num(epd) == MWIFIEX_USB_EP_CMD_EVENT && + usb_endpoint_xfer_bulk(epd)) { + pr_debug("info: bulk OUT: max pkt size: %d, addr: %d\n", + le16_to_cpu(epd->wMaxPacketSize), + epd->bEndpointAddress); + card->tx_cmd_ep = usb_endpoint_num(epd); + atomic_set(&card->tx_cmd_urb_pending, 0); + card->bulk_out_maxpktsize = + le16_to_cpu(epd->wMaxPacketSize); + } + } + + usb_set_intfdata(intf, card); + + ret = mwifiex_add_card(card, &add_remove_card_sem, &usb_ops, + MWIFIEX_USB); + if (ret) { + pr_err("%s: mwifiex_add_card failed: %d\n", __func__, ret); + usb_reset_device(udev); + kfree(card); + return ret; + } + + usb_get_dev(udev); + + return 0; +} + +/* Kernel needs to suspend all functions separately. Therefore all + * registered functions must have drivers with suspend and resume + * methods. Failing that the kernel simply removes the whole card. + * + * If already not suspended, this function allocates and sends a + * 'host sleep activate' request to the firmware and turns off the traffic. + */ +static int mwifiex_usb_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usb_card_rec *card = usb_get_intfdata(intf); + struct mwifiex_adapter *adapter; + int i; + + if (!card || !card->adapter) { + pr_err("%s: card or card->adapter is NULL\n", __func__); + return 0; + } + adapter = card->adapter; + + if (unlikely(adapter->is_suspended)) + dev_warn(adapter->dev, "Device already suspended\n"); + + mwifiex_enable_hs(adapter); + + /* 'is_suspended' flag indicates device is suspended. + * It must be set here before the usb_kill_urb() calls. Reason + * is in the complete handlers, urb->status(= -ENOENT) and + * this flag is used in combination to distinguish between a + * 'suspended' state and a 'disconnect' one. + */ + adapter->is_suspended = true; + + if (atomic_read(&card->rx_cmd_urb_pending) && card->rx_cmd.urb) + usb_kill_urb(card->rx_cmd.urb); + + if (atomic_read(&card->rx_data_urb_pending)) + for (i = 0; i < MWIFIEX_RX_DATA_URB; i++) + if (card->rx_data_list[i].urb) + usb_kill_urb(card->rx_data_list[i].urb); + + for (i = 0; i < MWIFIEX_TX_DATA_URB; i++) + if (card->tx_data_list[i].urb) + usb_kill_urb(card->tx_data_list[i].urb); + + if (card->tx_cmd.urb) + usb_kill_urb(card->tx_cmd.urb); + + return 0; +} + +/* Kernel needs to suspend all functions separately. Therefore all + * registered functions must have drivers with suspend and resume + * methods. Failing that the kernel simply removes the whole card. + * + * If already not resumed, this function turns on the traffic and + * sends a 'host sleep cancel' request to the firmware. + */ +static int mwifiex_usb_resume(struct usb_interface *intf) +{ + struct usb_card_rec *card = usb_get_intfdata(intf); + struct mwifiex_adapter *adapter; + int i; + + if (!card || !card->adapter) { + pr_err("%s: card or card->adapter is NULL\n", __func__); + return 0; + } + adapter = card->adapter; + + if (unlikely(!adapter->is_suspended)) { + dev_warn(adapter->dev, "Device already resumed\n"); + return 0; + } + + /* Indicate device resumed. The netdev queue will be resumed only + * after the urbs have been re-submitted + */ + adapter->is_suspended = false; + + if (!atomic_read(&card->rx_data_urb_pending)) + for (i = 0; i < MWIFIEX_RX_DATA_URB; i++) + mwifiex_usb_submit_rx_urb(&card->rx_data_list[i], + MWIFIEX_RX_DATA_BUF_SIZE); + + if (!atomic_read(&card->rx_cmd_urb_pending)) { + card->rx_cmd.skb = dev_alloc_skb(MWIFIEX_RX_CMD_BUF_SIZE); + if (card->rx_cmd.skb) + mwifiex_usb_submit_rx_urb(&card->rx_cmd, + MWIFIEX_RX_CMD_BUF_SIZE); + } + + /* Disable Host Sleep */ + if (adapter->hs_activated) + mwifiex_cancel_hs(mwifiex_get_priv(adapter, + MWIFIEX_BSS_ROLE_ANY), + MWIFIEX_ASYNC_CMD); + +#ifdef CONFIG_PM + /* Resume handler may be called due to remote wakeup, + * force to exit suspend anyway + */ + usb_disable_autosuspend(card->udev); +#endif /* CONFIG_PM */ + + return 0; +} + +static void mwifiex_usb_disconnect(struct usb_interface *intf) +{ + struct usb_card_rec *card = usb_get_intfdata(intf); + struct mwifiex_adapter *adapter; + int i; + + if (!card || !card->adapter) { + pr_err("%s: card or card->adapter is NULL\n", __func__); + return; + } + + adapter = card->adapter; + if (!adapter->priv_num) + return; + + /* In case driver is removed when asynchronous FW downloading is + * in progress + */ + wait_for_completion(&adapter->fw_load); + + if (user_rmmod) { +#ifdef CONFIG_PM + if (adapter->is_suspended) + mwifiex_usb_resume(intf); +#endif + for (i = 0; i < adapter->priv_num; i++) + if ((GET_BSS_ROLE(adapter->priv[i]) == + MWIFIEX_BSS_ROLE_STA) && + adapter->priv[i]->media_connected) + mwifiex_deauthenticate(adapter->priv[i], NULL); + + mwifiex_init_shutdown_fw(mwifiex_get_priv(adapter, + MWIFIEX_BSS_ROLE_ANY), + MWIFIEX_FUNC_SHUTDOWN); + } + + mwifiex_usb_free(card); + + dev_dbg(adapter->dev, "%s: removing card\n", __func__); + mwifiex_remove_card(adapter, &add_remove_card_sem); + + usb_set_intfdata(intf, NULL); + usb_put_dev(interface_to_usbdev(intf)); + kfree(card); + + return; +} + +static struct usb_driver mwifiex_usb_driver = { + .name = usbdriver_name, + .probe = mwifiex_usb_probe, + .disconnect = mwifiex_usb_disconnect, + .id_table = mwifiex_usb_table, + .suspend = mwifiex_usb_suspend, + .resume = mwifiex_usb_resume, + .supports_autosuspend = 1, +}; + +static int mwifiex_usb_tx_init(struct mwifiex_adapter *adapter) +{ + struct usb_card_rec *card = (struct usb_card_rec *)adapter->card; + int i; + + card->tx_cmd.adapter = adapter; + card->tx_cmd.ep = card->tx_cmd_ep; + + card->tx_cmd.urb = usb_alloc_urb(0, GFP_KERNEL); + if (!card->tx_cmd.urb) { + dev_err(adapter->dev, "tx_cmd.urb allocation failed\n"); + return -ENOMEM; + } + + card->tx_data_ix = 0; + + for (i = 0; i < MWIFIEX_TX_DATA_URB; i++) { + card->tx_data_list[i].adapter = adapter; + card->tx_data_list[i].ep = card->tx_data_ep; + + card->tx_data_list[i].urb = usb_alloc_urb(0, GFP_KERNEL); + if (!card->tx_data_list[i].urb) { + dev_err(adapter->dev, + "tx_data_list[] urb allocation failed\n"); + return -ENOMEM; + } + } + + return 0; +} + +static int mwifiex_usb_rx_init(struct mwifiex_adapter *adapter) +{ + struct usb_card_rec *card = (struct usb_card_rec *)adapter->card; + int i; + + card->rx_cmd.adapter = adapter; + card->rx_cmd.ep = card->rx_cmd_ep; + + card->rx_cmd.urb = usb_alloc_urb(0, GFP_KERNEL); + if (!card->rx_cmd.urb) { + dev_err(adapter->dev, "rx_cmd.urb allocation failed\n"); + return -ENOMEM; + } + + card->rx_cmd.skb = dev_alloc_skb(MWIFIEX_RX_CMD_BUF_SIZE); + if (!card->rx_cmd.skb) { + dev_err(adapter->dev, "rx_cmd.skb allocation failed\n"); + return -ENOMEM; + } + + if (mwifiex_usb_submit_rx_urb(&card->rx_cmd, MWIFIEX_RX_CMD_BUF_SIZE)) + return -1; + + for (i = 0; i < MWIFIEX_RX_DATA_URB; i++) { + card->rx_data_list[i].adapter = adapter; + card->rx_data_list[i].ep = card->rx_data_ep; + + card->rx_data_list[i].urb = usb_alloc_urb(0, GFP_KERNEL); + if (!card->rx_data_list[i].urb) { + dev_err(adapter->dev, + "rx_data_list[] urb allocation failed\n"); + return -1; + } + if (mwifiex_usb_submit_rx_urb(&card->rx_data_list[i], + MWIFIEX_RX_DATA_BUF_SIZE)) + return -1; + } + + return 0; +} + +static int mwifiex_write_data_sync(struct mwifiex_adapter *adapter, u8 *pbuf, + u32 *len, u8 ep, u32 timeout) +{ + struct usb_card_rec *card = adapter->card; + int actual_length, ret; + + if (!(*len % card->bulk_out_maxpktsize)) + (*len)++; + + /* Send the data block */ + ret = usb_bulk_msg(card->udev, usb_sndbulkpipe(card->udev, ep), pbuf, + *len, &actual_length, timeout); + if (ret) { + dev_err(adapter->dev, "usb_bulk_msg for tx failed: %d\n", ret); + return ret; + } + + *len = actual_length; + + return ret; +} + +static int mwifiex_read_data_sync(struct mwifiex_adapter *adapter, u8 *pbuf, + u32 *len, u8 ep, u32 timeout) +{ + struct usb_card_rec *card = adapter->card; + int actual_length, ret; + + /* Receive the data response */ + ret = usb_bulk_msg(card->udev, usb_rcvbulkpipe(card->udev, ep), pbuf, + *len, &actual_length, timeout); + if (ret) { + dev_err(adapter->dev, "usb_bulk_msg for rx failed: %d\n", ret); + return ret; + } + + *len = actual_length; + + return ret; +} + +/* This function write a command/data packet to card. */ +static int mwifiex_usb_host_to_card(struct mwifiex_adapter *adapter, u8 ep, + struct sk_buff *skb, + struct mwifiex_tx_param *tx_param) +{ + struct usb_card_rec *card = adapter->card; + struct urb_context *context; + u8 *data = (u8 *)skb->data; + struct urb *tx_urb; + + if (adapter->is_suspended) { + dev_err(adapter->dev, + "%s: not allowed while suspended\n", __func__); + return -1; + } + + if (adapter->surprise_removed) { + dev_err(adapter->dev, "%s: device removed\n", __func__); + return -1; + } + + if (ep == card->tx_data_ep && + atomic_read(&card->tx_data_urb_pending) >= MWIFIEX_TX_DATA_URB) { + return -EBUSY; + } + + dev_dbg(adapter->dev, "%s: ep=%d\n", __func__, ep); + + if (ep == card->tx_cmd_ep) { + context = &card->tx_cmd; + } else { + if (card->tx_data_ix >= MWIFIEX_TX_DATA_URB) + card->tx_data_ix = 0; + context = &card->tx_data_list[card->tx_data_ix++]; + } + + context->adapter = adapter; + context->ep = ep; + context->skb = skb; + tx_urb = context->urb; + + usb_fill_bulk_urb(tx_urb, card->udev, usb_sndbulkpipe(card->udev, ep), + data, skb->len, mwifiex_usb_tx_complete, + (void *)context); + + tx_urb->transfer_flags |= URB_ZERO_PACKET; + + if (ep == card->tx_cmd_ep) + atomic_inc(&card->tx_cmd_urb_pending); + else + atomic_inc(&card->tx_data_urb_pending); + + if (usb_submit_urb(tx_urb, GFP_ATOMIC)) { + dev_err(adapter->dev, "%s: usb_submit_urb failed\n", __func__); + if (ep == card->tx_cmd_ep) { + atomic_dec(&card->tx_cmd_urb_pending); + } else { + atomic_dec(&card->tx_data_urb_pending); + if (card->tx_data_ix) + card->tx_data_ix--; + else + card->tx_data_ix = MWIFIEX_TX_DATA_URB; + } + + return -1; + } else { + if (ep == card->tx_data_ep && + atomic_read(&card->tx_data_urb_pending) == + MWIFIEX_TX_DATA_URB) + return -ENOSR; + } + + return -EINPROGRESS; +} + +/* This function register usb device and initialize parameter. */ +static int mwifiex_register_dev(struct mwifiex_adapter *adapter) +{ + struct usb_card_rec *card = (struct usb_card_rec *)adapter->card; + + card->adapter = adapter; + adapter->dev = &card->udev->dev; + strcpy(adapter->fw_name, USB8797_DEFAULT_FW_NAME); + + return 0; +} + +static int mwifiex_prog_fw_w_helper(struct mwifiex_adapter *adapter, + struct mwifiex_fw_image *fw) +{ + int ret = 0; + u8 *firmware = fw->fw_buf, *recv_buff; + u32 retries = USB8797_FW_MAX_RETRY, dlen; + u32 fw_seqnum = 0, tlen = 0, dnld_cmd = 0; + struct fw_data *fwdata; + struct fw_sync_header sync_fw; + u8 check_winner = 1; + + if (!firmware) { + dev_err(adapter->dev, + "No firmware image found! Terminating download\n"); + ret = -1; + goto fw_exit; + } + + /* Allocate memory for transmit */ + fwdata = kzalloc(FW_DNLD_TX_BUF_SIZE, GFP_KERNEL); + if (!fwdata) + goto fw_exit; + + /* Allocate memory for receive */ + recv_buff = kzalloc(FW_DNLD_RX_BUF_SIZE, GFP_KERNEL); + if (!recv_buff) + goto cleanup; + + do { + /* Send pseudo data to check winner status first */ + if (check_winner) { + memset(&fwdata->fw_hdr, 0, sizeof(struct fw_header)); + dlen = 0; + } else { + /* copy the header of the fw_data to get the length */ + memcpy(&fwdata->fw_hdr, &firmware[tlen], + sizeof(struct fw_header)); + + dlen = le32_to_cpu(fwdata->fw_hdr.data_len); + dnld_cmd = le32_to_cpu(fwdata->fw_hdr.dnld_cmd); + tlen += sizeof(struct fw_header); + + memcpy(fwdata->data, &firmware[tlen], dlen); + + fwdata->seq_num = cpu_to_le32(fw_seqnum); + tlen += dlen; + } + + /* If the send/receive fails or CRC occurs then retry */ + while (retries--) { + u8 *buf = (u8 *)fwdata; + u32 len = FW_DATA_XMIT_SIZE; + + /* send the firmware block */ + ret = mwifiex_write_data_sync(adapter, buf, &len, + MWIFIEX_USB_EP_CMD_EVENT, + MWIFIEX_USB_TIMEOUT); + if (ret) { + dev_err(adapter->dev, + "write_data_sync: failed: %d\n", ret); + continue; + } + + buf = recv_buff; + len = FW_DNLD_RX_BUF_SIZE; + + /* Receive the firmware block response */ + ret = mwifiex_read_data_sync(adapter, buf, &len, + MWIFIEX_USB_EP_CMD_EVENT, + MWIFIEX_USB_TIMEOUT); + if (ret) { + dev_err(adapter->dev, + "read_data_sync: failed: %d\n", ret); + continue; + } + + memcpy(&sync_fw, recv_buff, + sizeof(struct fw_sync_header)); + + /* check 1st firmware block resp for highest bit set */ + if (check_winner) { + if (le32_to_cpu(sync_fw.cmd) & 0x80000000) { + dev_warn(adapter->dev, + "USB is not the winner %#x\n", + sync_fw.cmd); + + /* returning success */ + ret = 0; + goto cleanup; + } + + dev_dbg(adapter->dev, + "USB is the winner, start to download FW\n"); + + check_winner = 0; + break; + } + + /* check the firmware block response for CRC errors */ + if (sync_fw.cmd) { + dev_err(adapter->dev, + "FW received block with CRC %#x\n", + sync_fw.cmd); + ret = -1; + continue; + } + + retries = USB8797_FW_MAX_RETRY; + break; + } + fw_seqnum++; + } while ((dnld_cmd != FW_HAS_LAST_BLOCK) && retries); + +cleanup: + dev_dbg(adapter->dev, "%s: %d bytes downloaded\n", __func__, tlen); + + kfree(recv_buff); + kfree(fwdata); + + if (retries) + ret = 0; +fw_exit: + return ret; +} + +static int mwifiex_usb_dnld_fw(struct mwifiex_adapter *adapter, + struct mwifiex_fw_image *fw) +{ + int ret; + struct usb_card_rec *card = (struct usb_card_rec *)adapter->card; + + if (card->usb_boot_state == USB8797_FW_DNLD) { + ret = mwifiex_prog_fw_w_helper(adapter, fw); + if (ret) + return -1; + + /* Boot state changes after successful firmware download */ + if (card->usb_boot_state == USB8797_FW_DNLD) + return -1; + } + + ret = mwifiex_usb_rx_init(adapter); + if (!ret) + ret = mwifiex_usb_tx_init(adapter); + + return ret; +} + +static void mwifiex_submit_rx_urb(struct mwifiex_adapter *adapter, u8 ep) +{ + struct usb_card_rec *card = (struct usb_card_rec *)adapter->card; + + skb_push(card->rx_cmd.skb, INTF_HEADER_LEN); + if ((ep == card->rx_cmd_ep) && + (!atomic_read(&card->rx_cmd_urb_pending))) + mwifiex_usb_submit_rx_urb(&card->rx_cmd, + MWIFIEX_RX_CMD_BUF_SIZE); + + return; +} + +static int mwifiex_usb_cmd_event_complete(struct mwifiex_adapter *adapter, + struct sk_buff *skb) +{ + atomic_dec(&adapter->rx_pending); + mwifiex_submit_rx_urb(adapter, MWIFIEX_USB_EP_CMD_EVENT); + + return 0; +} + +static int mwifiex_usb_data_complete(struct mwifiex_adapter *adapter, + struct sk_buff *skb) +{ + atomic_dec(&adapter->rx_pending); + dev_kfree_skb_any(skb); + + return 0; +} + +/* This function wakes up the card. */ +static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) +{ + /* Simulation of HS_AWAKE event */ + adapter->pm_wakeup_fw_try = false; + adapter->pm_wakeup_card_req = false; + adapter->ps_state = PS_STATE_AWAKE; + + return 0; +} + +static struct mwifiex_if_ops usb_ops = { + .register_dev = mwifiex_register_dev, + .wakeup = mwifiex_pm_wakeup_card, + .wakeup_complete = mwifiex_pm_wakeup_card_complete, + + /* USB specific */ + .dnld_fw = mwifiex_usb_dnld_fw, + .cmdrsp_complete = mwifiex_usb_cmd_event_complete, + .event_complete = mwifiex_usb_cmd_event_complete, + .data_complete = mwifiex_usb_data_complete, + .host_to_card = mwifiex_usb_host_to_card, +}; + +/* This function initializes the USB driver module. + * + * This initiates the semaphore and registers the device with + * USB bus. + */ +static int mwifiex_usb_init_module(void) +{ + int ret; + + pr_debug("Marvell USB8797 Driver\n"); + + sema_init(&add_remove_card_sem, 1); + + ret = usb_register(&mwifiex_usb_driver); + if (ret) + pr_err("Driver register failed!\n"); + else + pr_debug("info: Driver registered successfully!\n"); + + return ret; +} + +/* This function cleans up the USB driver. + * + * The following major steps are followed in .disconnect for cleanup: + * - Resume the device if its suspended + * - Disconnect the device if connected + * - Shutdown the firmware + * - Unregister the device from USB bus. + */ +static void mwifiex_usb_cleanup_module(void) +{ + if (!down_interruptible(&add_remove_card_sem)) + up(&add_remove_card_sem); + + /* set the flag as user is removing this module */ + user_rmmod = 1; + + usb_deregister(&mwifiex_usb_driver); +} + +module_init(mwifiex_usb_init_module); +module_exit(mwifiex_usb_cleanup_module); + +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_DESCRIPTION("Marvell WiFi-Ex USB Driver version" USB_VERSION); +MODULE_VERSION(USB_VERSION); +MODULE_LICENSE("GPL v2"); +MODULE_FIRMWARE("mrvl/usb8797_uapsta.bin"); diff --git a/drivers/net/wireless/mwifiex/usb.h b/drivers/net/wireless/mwifiex/usb.h new file mode 100644 index 0000000..98c4316 --- /dev/null +++ b/drivers/net/wireless/mwifiex/usb.h @@ -0,0 +1,99 @@ +/* + * This file contains definitions for mwifiex USB interface driver. + * + * Copyright (C) 2012, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _MWIFIEX_USB_H +#define _MWIFIEX_USB_H + +#include + +#define USB8797_VID 0x1286 +#define USB8797_PID_1 0x2043 +#define USB8797_PID_2 0x2044 + +#define USB8797_FW_DNLD 1 +#define USB8797_FW_READY 2 +#define USB8797_FW_MAX_RETRY 3 + +#define MWIFIEX_TX_DATA_URB 6 +#define MWIFIEX_RX_DATA_URB 6 +#define MWIFIEX_USB_TIMEOUT 100 + +#define USB8797_DEFAULT_FW_NAME "mrvl/usb8797_uapsta.bin" + +#define FW_DNLD_TX_BUF_SIZE 620 +#define FW_DNLD_RX_BUF_SIZE 2048 +#define FW_HAS_LAST_BLOCK 0x00000004 + +#define FW_DATA_XMIT_SIZE \ + (sizeof(struct fw_header) + dlen + sizeof(u32)) + +struct urb_context { + struct mwifiex_adapter *adapter; + struct sk_buff *skb; + struct urb *urb; + u8 ep; +}; + +struct usb_card_rec { + struct mwifiex_adapter *adapter; + struct usb_device *udev; + struct usb_interface *intf; + u8 rx_cmd_ep; + struct urb_context rx_cmd; + atomic_t rx_cmd_urb_pending; + struct urb_context rx_data_list[MWIFIEX_RX_DATA_URB]; + u8 usb_boot_state; + u8 rx_data_ep; + atomic_t rx_data_urb_pending; + u8 tx_data_ep; + u8 tx_cmd_ep; + atomic_t tx_data_urb_pending; + atomic_t tx_cmd_urb_pending; + int bulk_out_maxpktsize; + struct urb_context tx_cmd; + int tx_data_ix; + struct urb_context tx_data_list[MWIFIEX_TX_DATA_URB]; +}; + +struct fw_header { + __le32 dnld_cmd; + __le32 base_addr; + __le32 data_len; + __le32 crc; +}; + +struct fw_sync_header { + __le32 cmd; + __le32 seq_num; +}; + +struct fw_data { + struct fw_header fw_hdr; + __le32 seq_num; + u8 data[1]; +}; + +/* This function is called after the card has woken up. */ +static inline int +mwifiex_pm_wakeup_card_complete(struct mwifiex_adapter *adapter) +{ + return 0; +} + +#endif /*_MWIFIEX_USB_H */ diff --git a/drivers/net/wireless/mwifiex/util.c b/drivers/net/wireless/mwifiex/util.c new file mode 100644 index 0000000..e57ac0d --- /dev/null +++ b/drivers/net/wireless/mwifiex/util.c @@ -0,0 +1,253 @@ +/* + * Marvell Wireless LAN device driver: utility functions + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" + +/* + * Firmware initialization complete callback handler. + * + * This function wakes up the function waiting on the init + * wait queue for the firmware initialization to complete. + */ +int mwifiex_init_fw_complete(struct mwifiex_adapter *adapter) +{ + + adapter->init_wait_q_woken = true; + wake_up_interruptible(&adapter->init_wait_q); + return 0; +} + +/* + * Firmware shutdown complete callback handler. + * + * This function sets the hardware status to not ready and wakes up + * the function waiting on the init wait queue for the firmware + * shutdown to complete. + */ +int mwifiex_shutdown_fw_complete(struct mwifiex_adapter *adapter) +{ + adapter->hw_status = MWIFIEX_HW_STATUS_NOT_READY; + adapter->init_wait_q_woken = true; + wake_up_interruptible(&adapter->init_wait_q); + return 0; +} + +/* + * This function sends init/shutdown command + * to firmware. + */ +int mwifiex_init_shutdown_fw(struct mwifiex_private *priv, + u32 func_init_shutdown) +{ + u16 cmd; + + if (func_init_shutdown == MWIFIEX_FUNC_INIT) { + cmd = HostCmd_CMD_FUNC_INIT; + } else if (func_init_shutdown == MWIFIEX_FUNC_SHUTDOWN) { + cmd = HostCmd_CMD_FUNC_SHUTDOWN; + } else { + dev_err(priv->adapter->dev, "unsupported parameter\n"); + return -1; + } + + return mwifiex_send_cmd_sync(priv, cmd, HostCmd_ACT_GEN_SET, 0, NULL); +} +EXPORT_SYMBOL_GPL(mwifiex_init_shutdown_fw); + +/* + * IOCTL request handler to set/get debug information. + * + * This function collates/sets the information from/to different driver + * structures. + */ +int mwifiex_get_debug_info(struct mwifiex_private *priv, + struct mwifiex_debug_info *info) +{ + struct mwifiex_adapter *adapter = priv->adapter; + + if (info) { + memcpy(info->packets_out, + priv->wmm.packets_out, + sizeof(priv->wmm.packets_out)); + info->curr_tx_buf_size = (u32) adapter->curr_tx_buf_size; + info->tx_buf_size = (u32) adapter->tx_buf_size; + info->rx_tbl_num = mwifiex_get_rx_reorder_tbl(priv, + info->rx_tbl); + info->tx_tbl_num = mwifiex_get_tx_ba_stream_tbl(priv, + info->tx_tbl); + info->ps_mode = adapter->ps_mode; + info->ps_state = adapter->ps_state; + info->is_deep_sleep = adapter->is_deep_sleep; + info->pm_wakeup_card_req = adapter->pm_wakeup_card_req; + info->pm_wakeup_fw_try = adapter->pm_wakeup_fw_try; + info->is_hs_configured = adapter->is_hs_configured; + info->hs_activated = adapter->hs_activated; + info->num_cmd_host_to_card_failure + = adapter->dbg.num_cmd_host_to_card_failure; + info->num_cmd_sleep_cfm_host_to_card_failure + = adapter->dbg.num_cmd_sleep_cfm_host_to_card_failure; + info->num_tx_host_to_card_failure + = adapter->dbg.num_tx_host_to_card_failure; + info->num_event_deauth = adapter->dbg.num_event_deauth; + info->num_event_disassoc = adapter->dbg.num_event_disassoc; + info->num_event_link_lost = adapter->dbg.num_event_link_lost; + info->num_cmd_deauth = adapter->dbg.num_cmd_deauth; + info->num_cmd_assoc_success = + adapter->dbg.num_cmd_assoc_success; + info->num_cmd_assoc_failure = + adapter->dbg.num_cmd_assoc_failure; + info->num_tx_timeout = adapter->dbg.num_tx_timeout; + info->num_cmd_timeout = adapter->dbg.num_cmd_timeout; + info->timeout_cmd_id = adapter->dbg.timeout_cmd_id; + info->timeout_cmd_act = adapter->dbg.timeout_cmd_act; + memcpy(info->last_cmd_id, adapter->dbg.last_cmd_id, + sizeof(adapter->dbg.last_cmd_id)); + memcpy(info->last_cmd_act, adapter->dbg.last_cmd_act, + sizeof(adapter->dbg.last_cmd_act)); + info->last_cmd_index = adapter->dbg.last_cmd_index; + memcpy(info->last_cmd_resp_id, adapter->dbg.last_cmd_resp_id, + sizeof(adapter->dbg.last_cmd_resp_id)); + info->last_cmd_resp_index = adapter->dbg.last_cmd_resp_index; + memcpy(info->last_event, adapter->dbg.last_event, + sizeof(adapter->dbg.last_event)); + info->last_event_index = adapter->dbg.last_event_index; + info->data_sent = adapter->data_sent; + info->cmd_sent = adapter->cmd_sent; + info->cmd_resp_received = adapter->cmd_resp_received; + } + + return 0; +} + +/* + * This function processes the received management packet and send it + * to the kernel. + */ +int +mwifiex_process_mgmt_packet(struct mwifiex_private *priv, + struct sk_buff *skb) +{ + struct rxpd *rx_pd; + u16 pkt_len; + + if (!skb) + return -1; + + rx_pd = (struct rxpd *)skb->data; + + skb_pull(skb, le16_to_cpu(rx_pd->rx_pkt_offset)); + skb_pull(skb, sizeof(pkt_len)); + + pkt_len = le16_to_cpu(rx_pd->rx_pkt_length); + + /* Remove address4 */ + memmove(skb->data + sizeof(struct ieee80211_hdr_3addr), + skb->data + sizeof(struct ieee80211_hdr), + pkt_len - sizeof(struct ieee80211_hdr)); + + pkt_len -= ETH_ALEN + sizeof(pkt_len); + rx_pd->rx_pkt_length = cpu_to_le16(pkt_len); + + cfg80211_rx_mgmt(priv->wdev, priv->roc_cfg.chan.center_freq, + CAL_RSSI(rx_pd->snr, rx_pd->nf), + skb->data, pkt_len, GFP_ATOMIC); + + return 0; +} + +/* + * This function processes the received packet before sending it to the + * kernel. + * + * It extracts the SKB from the received buffer and sends it to kernel. + * In case the received buffer does not contain the data in SKB format, + * the function creates a blank SKB, fills it with the data from the + * received buffer and then sends this new SKB to the kernel. + */ +int mwifiex_recv_packet(struct mwifiex_private *priv, struct sk_buff *skb) +{ + if (!skb) + return -1; + + skb->dev = priv->netdev; + skb->protocol = eth_type_trans(skb, priv->netdev); + skb->ip_summed = CHECKSUM_NONE; + + /* This is required only in case of 11n and USB/PCIE as we alloc + * a buffer of 4K only if its 11N (to be able to receive 4K + * AMSDU packets). In case of SD we allocate buffers based + * on the size of packet and hence this is not needed. + * + * Modifying the truesize here as our allocation for each + * skb is 4K but we only receive 2K packets and this cause + * the kernel to start dropping packets in case where + * application has allocated buffer based on 2K size i.e. + * if there a 64K packet received (in IP fragments and + * application allocates 64K to receive this packet but + * this packet would almost double up because we allocate + * each 1.5K fragment in 4K and pass it up. As soon as the + * 64K limit hits kernel will start to drop rest of the + * fragments. Currently we fail the Filesndl-ht.scr script + * for UDP, hence this fix + */ + if ((priv->adapter->iface_type == MWIFIEX_USB || + priv->adapter->iface_type == MWIFIEX_PCIE) && + (skb->truesize > MWIFIEX_RX_DATA_BUF_SIZE)) + skb->truesize += (skb->len - MWIFIEX_RX_DATA_BUF_SIZE); + + priv->stats.rx_bytes += skb->len; + priv->stats.rx_packets++; + if (in_interrupt()) + netif_rx(skb); + else + netif_rx_ni(skb); + + return 0; +} + +/* + * IOCTL completion callback handler. + * + * This function is called when a pending IOCTL is completed. + * + * If work queue support is enabled, the function wakes up the + * corresponding waiting function. Otherwise, it processes the + * IOCTL response and frees the response buffer. + */ +int mwifiex_complete_cmd(struct mwifiex_adapter *adapter, + struct cmd_ctrl_node *cmd_node) +{ + dev_dbg(adapter->dev, "cmd completed: status=%d\n", + adapter->cmd_wait_q.status); + + *(cmd_node->condition) = true; + + if (adapter->cmd_wait_q.status == -ETIMEDOUT) + dev_err(adapter->dev, "cmd timeout\n"); + else + wake_up_interruptible(&adapter->cmd_wait_q.wait); + + return 0; +} diff --git a/drivers/net/wireless/mwifiex/util.h b/drivers/net/wireless/mwifiex/util.h new file mode 100644 index 0000000..cb2d058 --- /dev/null +++ b/drivers/net/wireless/mwifiex/util.h @@ -0,0 +1,37 @@ +/* + * Marvell Wireless LAN device driver: utility functions + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _MWIFIEX_UTIL_H_ +#define _MWIFIEX_UTIL_H_ + +static inline struct mwifiex_rxinfo *MWIFIEX_SKB_RXCB(struct sk_buff *skb) +{ + return (struct mwifiex_rxinfo *)(skb->cb + sizeof(dma_addr_t)); +} + +static inline struct mwifiex_txinfo *MWIFIEX_SKB_TXCB(struct sk_buff *skb) +{ + return (struct mwifiex_txinfo *)(skb->cb + sizeof(dma_addr_t)); +} + +static inline void MWIFIEX_SKB_PACB(struct sk_buff *skb, dma_addr_t *buf_pa) +{ + memcpy(buf_pa, skb->cb, sizeof(dma_addr_t)); +} +#endif /* !_MWIFIEX_UTIL_H_ */ diff --git a/drivers/net/wireless/mwifiex/wmm.c b/drivers/net/wireless/mwifiex/wmm.c new file mode 100644 index 0000000..ae31e8d --- /dev/null +++ b/drivers/net/wireless/mwifiex/wmm.c @@ -0,0 +1,1265 @@ +/* + * Marvell Wireless LAN device driver: WMM + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#include "decl.h" +#include "ioctl.h" +#include "util.h" +#include "fw.h" +#include "main.h" +#include "wmm.h" +#include "11n.h" + + +/* Maximum value FW can accept for driver delay in packet transmission */ +#define DRV_PKT_DELAY_TO_FW_MAX 512 + + +#define WMM_QUEUED_PACKET_LOWER_LIMIT 180 + +#define WMM_QUEUED_PACKET_UPPER_LIMIT 200 + +/* Offset for TOS field in the IP header */ +#define IPTOS_OFFSET 5 + +/* WMM information IE */ +static const u8 wmm_info_ie[] = { WLAN_EID_VENDOR_SPECIFIC, 0x07, + 0x00, 0x50, 0xf2, 0x02, + 0x00, 0x01, 0x00 +}; + +static const u8 wmm_aci_to_qidx_map[] = { WMM_AC_BE, + WMM_AC_BK, + WMM_AC_VI, + WMM_AC_VO +}; + +static u8 tos_to_tid[] = { + /* TID DSCP_P2 DSCP_P1 DSCP_P0 WMM_AC */ + 0x01, /* 0 1 0 AC_BK */ + 0x02, /* 0 0 0 AC_BK */ + 0x00, /* 0 0 1 AC_BE */ + 0x03, /* 0 1 1 AC_BE */ + 0x04, /* 1 0 0 AC_VI */ + 0x05, /* 1 0 1 AC_VI */ + 0x06, /* 1 1 0 AC_VO */ + 0x07 /* 1 1 1 AC_VO */ +}; + +/* + * This table inverses the tos_to_tid operation to get a priority + * which is in sequential order, and can be compared. + * Use this to compare the priority of two different TIDs. + */ +static u8 tos_to_tid_inv[] = { + 0x02, /* from tos_to_tid[2] = 0 */ + 0x00, /* from tos_to_tid[0] = 1 */ + 0x01, /* from tos_to_tid[1] = 2 */ + 0x03, + 0x04, + 0x05, + 0x06, + 0x07}; + +static u8 ac_to_tid[4][2] = { {1, 2}, {0, 3}, {4, 5}, {6, 7} }; + +/* + * This function debug prints the priority parameters for a WMM AC. + */ +static void +mwifiex_wmm_ac_debug_print(const struct ieee_types_wmm_ac_parameters *ac_param) +{ + const char *ac_str[] = { "BK", "BE", "VI", "VO" }; + + pr_debug("info: WMM AC_%s: ACI=%d, ACM=%d, Aifsn=%d, " + "EcwMin=%d, EcwMax=%d, TxopLimit=%d\n", + ac_str[wmm_aci_to_qidx_map[(ac_param->aci_aifsn_bitmap + & MWIFIEX_ACI) >> 5]], + (ac_param->aci_aifsn_bitmap & MWIFIEX_ACI) >> 5, + (ac_param->aci_aifsn_bitmap & MWIFIEX_ACM) >> 4, + ac_param->aci_aifsn_bitmap & MWIFIEX_AIFSN, + ac_param->ecw_bitmap & MWIFIEX_ECW_MIN, + (ac_param->ecw_bitmap & MWIFIEX_ECW_MAX) >> 4, + le16_to_cpu(ac_param->tx_op_limit)); +} + +/* + * This function allocates a route address list. + * + * The function also initializes the list with the provided RA. + */ +static struct mwifiex_ra_list_tbl * +mwifiex_wmm_allocate_ralist_node(struct mwifiex_adapter *adapter, u8 *ra) +{ + struct mwifiex_ra_list_tbl *ra_list; + + ra_list = kzalloc(sizeof(struct mwifiex_ra_list_tbl), GFP_ATOMIC); + if (!ra_list) + return NULL; + + INIT_LIST_HEAD(&ra_list->list); + skb_queue_head_init(&ra_list->skb_head); + + memcpy(ra_list->ra, ra, ETH_ALEN); + + ra_list->total_pkts_size = 0; + + dev_dbg(adapter->dev, "info: allocated ra_list %p\n", ra_list); + + return ra_list; +} + +/* This function returns random no between 16 and 32 to be used as threshold + * for no of packets after which BA setup is initiated. + */ +static u8 mwifiex_get_random_ba_threshold(void) +{ + u32 sec, usec; + struct timeval ba_tstamp; + u8 ba_threshold; + + /* setup ba_packet_threshold here random number between + * [BA_SETUP_PACKET_OFFSET, + * BA_SETUP_PACKET_OFFSET+BA_SETUP_MAX_PACKET_THRESHOLD-1] + */ + + do_gettimeofday(&ba_tstamp); + sec = (ba_tstamp.tv_sec & 0xFFFF) + (ba_tstamp.tv_sec >> 16); + usec = (ba_tstamp.tv_usec & 0xFFFF) + (ba_tstamp.tv_usec >> 16); + ba_threshold = (((sec << 16) + usec) % BA_SETUP_MAX_PACKET_THRESHOLD) + + BA_SETUP_PACKET_OFFSET; + + return ba_threshold; +} + +/* + * This function allocates and adds a RA list for all TIDs + * with the given RA. + */ +void +mwifiex_ralist_add(struct mwifiex_private *priv, u8 *ra) +{ + int i; + struct mwifiex_ra_list_tbl *ra_list; + struct mwifiex_adapter *adapter = priv->adapter; + struct mwifiex_sta_node *node; + unsigned long flags; + + spin_lock_irqsave(&priv->sta_list_spinlock, flags); + node = mwifiex_get_sta_entry(priv, ra); + spin_unlock_irqrestore(&priv->sta_list_spinlock, flags); + + for (i = 0; i < MAX_NUM_TID; ++i) { + ra_list = mwifiex_wmm_allocate_ralist_node(adapter, ra); + dev_dbg(adapter->dev, "info: created ra_list %p\n", ra_list); + + if (!ra_list) + break; + + ra_list->is_11n_enabled = 0; + if (!mwifiex_queuing_ra_based(priv)) { + ra_list->is_11n_enabled = IS_11N_ENABLED(priv); + } else { + ra_list->is_11n_enabled = + mwifiex_is_sta_11n_enabled(priv, node); + if (ra_list->is_11n_enabled) + ra_list->max_amsdu = node->max_amsdu; + } + + dev_dbg(adapter->dev, "data: ralist %p: is_11n_enabled=%d\n", + ra_list, ra_list->is_11n_enabled); + + if (ra_list->is_11n_enabled) { + ra_list->pkt_count = 0; + ra_list->ba_packet_thr = + mwifiex_get_random_ba_threshold(); + } + list_add_tail(&ra_list->list, + &priv->wmm.tid_tbl_ptr[i].ra_list); + } +} + +/* + * This function sets the WMM queue priorities to their default values. + */ +static void mwifiex_wmm_default_queue_priorities(struct mwifiex_private *priv) +{ + /* Default queue priorities: VO->VI->BE->BK */ + priv->wmm.queue_priority[0] = WMM_AC_VO; + priv->wmm.queue_priority[1] = WMM_AC_VI; + priv->wmm.queue_priority[2] = WMM_AC_BE; + priv->wmm.queue_priority[3] = WMM_AC_BK; +} + +/* + * This function map ACs to TIDs. + */ +static void +mwifiex_wmm_queue_priorities_tid(struct mwifiex_wmm_desc *wmm) +{ + u8 *queue_priority = wmm->queue_priority; + int i; + + for (i = 0; i < 4; ++i) { + tos_to_tid[7 - (i * 2)] = ac_to_tid[queue_priority[i]][1]; + tos_to_tid[6 - (i * 2)] = ac_to_tid[queue_priority[i]][0]; + } + + for (i = 0; i < MAX_NUM_TID; ++i) + tos_to_tid_inv[tos_to_tid[i]] = (u8)i; + + atomic_set(&wmm->highest_queued_prio, HIGH_PRIO_TID); +} + +/* + * This function initializes WMM priority queues. + */ +void +mwifiex_wmm_setup_queue_priorities(struct mwifiex_private *priv, + struct ieee_types_wmm_parameter *wmm_ie) +{ + u16 cw_min, avg_back_off, tmp[4]; + u32 i, j, num_ac; + u8 ac_idx; + + if (!wmm_ie || !priv->wmm_enabled) { + /* WMM is not enabled, just set the defaults and return */ + mwifiex_wmm_default_queue_priorities(priv); + return; + } + + dev_dbg(priv->adapter->dev, "info: WMM Parameter IE: version=%d, " + "qos_info Parameter Set Count=%d, Reserved=%#x\n", + wmm_ie->vend_hdr.version, wmm_ie->qos_info_bitmap & + IEEE80211_WMM_IE_AP_QOSINFO_PARAM_SET_CNT_MASK, + wmm_ie->reserved); + + for (num_ac = 0; num_ac < ARRAY_SIZE(wmm_ie->ac_params); num_ac++) { + u8 ecw = wmm_ie->ac_params[num_ac].ecw_bitmap; + u8 aci_aifsn = wmm_ie->ac_params[num_ac].aci_aifsn_bitmap; + cw_min = (1 << (ecw & MWIFIEX_ECW_MIN)) - 1; + avg_back_off = (cw_min >> 1) + (aci_aifsn & MWIFIEX_AIFSN); + + ac_idx = wmm_aci_to_qidx_map[(aci_aifsn & MWIFIEX_ACI) >> 5]; + priv->wmm.queue_priority[ac_idx] = ac_idx; + tmp[ac_idx] = avg_back_off; + + dev_dbg(priv->adapter->dev, + "info: WMM: CWmax=%d CWmin=%d Avg Back-off=%d\n", + (1 << ((ecw & MWIFIEX_ECW_MAX) >> 4)) - 1, + cw_min, avg_back_off); + mwifiex_wmm_ac_debug_print(&wmm_ie->ac_params[num_ac]); + } + + /* Bubble sort */ + for (i = 0; i < num_ac; i++) { + for (j = 1; j < num_ac - i; j++) { + if (tmp[j - 1] > tmp[j]) { + swap(tmp[j - 1], tmp[j]); + swap(priv->wmm.queue_priority[j - 1], + priv->wmm.queue_priority[j]); + } else if (tmp[j - 1] == tmp[j]) { + if (priv->wmm.queue_priority[j - 1] + < priv->wmm.queue_priority[j]) + swap(priv->wmm.queue_priority[j - 1], + priv->wmm.queue_priority[j]); + } + } + } + + mwifiex_wmm_queue_priorities_tid(&priv->wmm); +} + +/* + * This function evaluates whether or not an AC is to be downgraded. + * + * In case the AC is not enabled, the highest AC is returned that is + * enabled and does not require admission control. + */ +static enum mwifiex_wmm_ac_e +mwifiex_wmm_eval_downgrade_ac(struct mwifiex_private *priv, + enum mwifiex_wmm_ac_e eval_ac) +{ + int down_ac; + enum mwifiex_wmm_ac_e ret_ac; + struct mwifiex_wmm_ac_status *ac_status; + + ac_status = &priv->wmm.ac_status[eval_ac]; + + if (!ac_status->disabled) + /* Okay to use this AC, its enabled */ + return eval_ac; + + /* Setup a default return value of the lowest priority */ + ret_ac = WMM_AC_BK; + + /* + * Find the highest AC that is enabled and does not require + * admission control. The spec disallows downgrading to an AC, + * which is enabled due to a completed admission control. + * Unadmitted traffic is not to be sent on an AC with admitted + * traffic. + */ + for (down_ac = WMM_AC_BK; down_ac < eval_ac; down_ac++) { + ac_status = &priv->wmm.ac_status[down_ac]; + + if (!ac_status->disabled && !ac_status->flow_required) + /* AC is enabled and does not require admission + control */ + ret_ac = (enum mwifiex_wmm_ac_e) down_ac; + } + + return ret_ac; +} + +/* + * This function downgrades WMM priority queue. + */ +void +mwifiex_wmm_setup_ac_downgrade(struct mwifiex_private *priv) +{ + int ac_val; + + dev_dbg(priv->adapter->dev, "info: WMM: AC Priorities:" + "BK(0), BE(1), VI(2), VO(3)\n"); + + if (!priv->wmm_enabled) { + /* WMM is not enabled, default priorities */ + for (ac_val = WMM_AC_BK; ac_val <= WMM_AC_VO; ac_val++) + priv->wmm.ac_down_graded_vals[ac_val] = + (enum mwifiex_wmm_ac_e) ac_val; + } else { + for (ac_val = WMM_AC_BK; ac_val <= WMM_AC_VO; ac_val++) { + priv->wmm.ac_down_graded_vals[ac_val] + = mwifiex_wmm_eval_downgrade_ac(priv, + (enum mwifiex_wmm_ac_e) ac_val); + dev_dbg(priv->adapter->dev, + "info: WMM: AC PRIO %d maps to %d\n", + ac_val, priv->wmm.ac_down_graded_vals[ac_val]); + } + } +} + +/* + * This function converts the IP TOS field to an WMM AC + * Queue assignment. + */ +static enum mwifiex_wmm_ac_e +mwifiex_wmm_convert_tos_to_ac(struct mwifiex_adapter *adapter, u32 tos) +{ + /* Map of TOS UP values to WMM AC */ + const enum mwifiex_wmm_ac_e tos_to_ac[] = { WMM_AC_BE, + WMM_AC_BK, + WMM_AC_BK, + WMM_AC_BE, + WMM_AC_VI, + WMM_AC_VI, + WMM_AC_VO, + WMM_AC_VO + }; + + if (tos >= ARRAY_SIZE(tos_to_ac)) + return WMM_AC_BE; + + return tos_to_ac[tos]; +} + +/* + * This function evaluates a given TID and downgrades it to a lower + * TID if the WMM Parameter IE received from the AP indicates that the + * AP is disabled (due to call admission control (ACM bit). Mapping + * of TID to AC is taken care of internally. + */ +static u8 +mwifiex_wmm_downgrade_tid(struct mwifiex_private *priv, u32 tid) +{ + enum mwifiex_wmm_ac_e ac, ac_down; + u8 new_tid; + + ac = mwifiex_wmm_convert_tos_to_ac(priv->adapter, tid); + ac_down = priv->wmm.ac_down_graded_vals[ac]; + + /* Send the index to tid array, picking from the array will be + * taken care by dequeuing function + */ + new_tid = ac_to_tid[ac_down][tid % 2]; + + return new_tid; +} + +/* + * This function initializes the WMM state information and the + * WMM data path queues. + */ +void +mwifiex_wmm_init(struct mwifiex_adapter *adapter) +{ + int i, j; + struct mwifiex_private *priv; + + for (j = 0; j < adapter->priv_num; ++j) { + priv = adapter->priv[j]; + if (!priv) + continue; + + for (i = 0; i < MAX_NUM_TID; ++i) { + priv->aggr_prio_tbl[i].amsdu = tos_to_tid_inv[i]; + priv->aggr_prio_tbl[i].ampdu_ap = tos_to_tid_inv[i]; + priv->aggr_prio_tbl[i].ampdu_user = tos_to_tid_inv[i]; + } + + priv->aggr_prio_tbl[6].amsdu + = priv->aggr_prio_tbl[6].ampdu_ap + = priv->aggr_prio_tbl[6].ampdu_user + = BA_STREAM_NOT_ALLOWED; + + priv->aggr_prio_tbl[7].amsdu = priv->aggr_prio_tbl[7].ampdu_ap + = priv->aggr_prio_tbl[7].ampdu_user + = BA_STREAM_NOT_ALLOWED; + + mwifiex_set_ba_params(priv); + mwifiex_reset_11n_rx_seq_num(priv); + + atomic_set(&priv->wmm.tx_pkts_queued, 0); + atomic_set(&priv->wmm.highest_queued_prio, HIGH_PRIO_TID); + } +} + +/* + * This function checks if WMM Tx queue is empty. + */ +int +mwifiex_wmm_lists_empty(struct mwifiex_adapter *adapter) +{ + int i; + struct mwifiex_private *priv; + + for (i = 0; i < adapter->priv_num; ++i) { + priv = adapter->priv[i]; + if (priv && atomic_read(&priv->wmm.tx_pkts_queued)) + return false; + } + + return true; +} + +/* + * This function deletes all packets in an RA list node. + * + * The packet sent completion callback handler are called with + * status failure, after they are dequeued to ensure proper + * cleanup. The RA list node itself is freed at the end. + */ +static void +mwifiex_wmm_del_pkts_in_ralist_node(struct mwifiex_private *priv, + struct mwifiex_ra_list_tbl *ra_list) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct sk_buff *skb, *tmp; + + skb_queue_walk_safe(&ra_list->skb_head, skb, tmp) + mwifiex_write_data_complete(adapter, skb, 0, -1); +} + +/* + * This function deletes all packets in an RA list. + * + * Each nodes in the RA list are freed individually first, and then + * the RA list itself is freed. + */ +static void +mwifiex_wmm_del_pkts_in_ralist(struct mwifiex_private *priv, + struct list_head *ra_list_head) +{ + struct mwifiex_ra_list_tbl *ra_list; + + list_for_each_entry(ra_list, ra_list_head, list) + mwifiex_wmm_del_pkts_in_ralist_node(priv, ra_list); +} + +/* + * This function deletes all packets in all RA lists. + */ +static void mwifiex_wmm_cleanup_queues(struct mwifiex_private *priv) +{ + int i; + + for (i = 0; i < MAX_NUM_TID; i++) + mwifiex_wmm_del_pkts_in_ralist(priv, &priv->wmm.tid_tbl_ptr[i]. + ra_list); + + atomic_set(&priv->wmm.tx_pkts_queued, 0); + atomic_set(&priv->wmm.highest_queued_prio, HIGH_PRIO_TID); +} + +/* + * This function deletes all route addresses from all RA lists. + */ +static void mwifiex_wmm_delete_all_ralist(struct mwifiex_private *priv) +{ + struct mwifiex_ra_list_tbl *ra_list, *tmp_node; + int i; + + for (i = 0; i < MAX_NUM_TID; ++i) { + dev_dbg(priv->adapter->dev, + "info: ra_list: freeing buf for tid %d\n", i); + list_for_each_entry_safe(ra_list, tmp_node, + &priv->wmm.tid_tbl_ptr[i].ra_list, + list) { + list_del(&ra_list->list); + kfree(ra_list); + } + + INIT_LIST_HEAD(&priv->wmm.tid_tbl_ptr[i].ra_list); + } +} + +/* + * This function cleans up the Tx and Rx queues. + * + * Cleanup includes - + * - All packets in RA lists + * - All entries in Rx reorder table + * - All entries in Tx BA stream table + * - MPA buffer (if required) + * - All RA lists + */ +void +mwifiex_clean_txrx(struct mwifiex_private *priv) +{ + unsigned long flags; + + mwifiex_11n_cleanup_reorder_tbl(priv); + spin_lock_irqsave(&priv->wmm.ra_list_spinlock, flags); + + mwifiex_wmm_cleanup_queues(priv); + mwifiex_11n_delete_all_tx_ba_stream_tbl(priv); + + if (priv->adapter->if_ops.cleanup_mpa_buf) + priv->adapter->if_ops.cleanup_mpa_buf(priv->adapter); + + mwifiex_wmm_delete_all_ralist(priv); + memcpy(tos_to_tid, ac_to_tid, sizeof(tos_to_tid)); + + if (priv->adapter->if_ops.clean_pcie_ring) + priv->adapter->if_ops.clean_pcie_ring(priv->adapter); + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, flags); +} + +/* + * This function retrieves a particular RA list node, matching with the + * given TID and RA address. + */ +static struct mwifiex_ra_list_tbl * +mwifiex_wmm_get_ralist_node(struct mwifiex_private *priv, u8 tid, + u8 *ra_addr) +{ + struct mwifiex_ra_list_tbl *ra_list; + + list_for_each_entry(ra_list, &priv->wmm.tid_tbl_ptr[tid].ra_list, + list) { + if (!memcmp(ra_list->ra, ra_addr, ETH_ALEN)) + return ra_list; + } + + return NULL; +} + +/* + * This function retrieves an RA list node for a given TID and + * RA address pair. + * + * If no such node is found, a new node is added first and then + * retrieved. + */ +static struct mwifiex_ra_list_tbl * +mwifiex_wmm_get_queue_raptr(struct mwifiex_private *priv, u8 tid, u8 *ra_addr) +{ + struct mwifiex_ra_list_tbl *ra_list; + + ra_list = mwifiex_wmm_get_ralist_node(priv, tid, ra_addr); + if (ra_list) + return ra_list; + mwifiex_ralist_add(priv, ra_addr); + + return mwifiex_wmm_get_ralist_node(priv, tid, ra_addr); +} + +/* + * This function checks if a particular RA list node exists in a given TID + * table index. + */ +int +mwifiex_is_ralist_valid(struct mwifiex_private *priv, + struct mwifiex_ra_list_tbl *ra_list, int ptr_index) +{ + struct mwifiex_ra_list_tbl *rlist; + + list_for_each_entry(rlist, &priv->wmm.tid_tbl_ptr[ptr_index].ra_list, + list) { + if (rlist == ra_list) + return true; + } + + return false; +} + +/* + * This function adds a packet to WMM queue. + * + * In disconnected state the packet is immediately dropped and the + * packet send completion callback is called with status failure. + * + * Otherwise, the correct RA list node is located and the packet + * is queued at the list tail. + */ +void +mwifiex_wmm_add_buf_txqueue(struct mwifiex_private *priv, + struct sk_buff *skb) +{ + struct mwifiex_adapter *adapter = priv->adapter; + u32 tid; + struct mwifiex_ra_list_tbl *ra_list; + u8 ra[ETH_ALEN], tid_down; + unsigned long flags; + + if (!priv->media_connected && !mwifiex_is_skb_mgmt_frame(skb)) { + dev_dbg(adapter->dev, "data: drop packet in disconnect\n"); + mwifiex_write_data_complete(adapter, skb, 0, -1); + return; + } + + tid = skb->priority; + + spin_lock_irqsave(&priv->wmm.ra_list_spinlock, flags); + + tid_down = mwifiex_wmm_downgrade_tid(priv, tid); + + /* In case of infra as we have already created the list during + association we just don't have to call get_queue_raptr, we will + have only 1 raptr for a tid in case of infra */ + if (!mwifiex_queuing_ra_based(priv) && + !mwifiex_is_skb_mgmt_frame(skb)) { + if (!list_empty(&priv->wmm.tid_tbl_ptr[tid_down].ra_list)) + ra_list = list_first_entry( + &priv->wmm.tid_tbl_ptr[tid_down].ra_list, + struct mwifiex_ra_list_tbl, list); + else + ra_list = NULL; + } else { + memcpy(ra, skb->data, ETH_ALEN); + if (ra[0] & 0x01 || mwifiex_is_skb_mgmt_frame(skb)) + memset(ra, 0xff, ETH_ALEN); + ra_list = mwifiex_wmm_get_queue_raptr(priv, tid_down, ra); + } + + if (!ra_list) { + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, flags); + mwifiex_write_data_complete(adapter, skb, 0, -1); + return; + } + + skb_queue_tail(&ra_list->skb_head, skb); + + ra_list->total_pkts_size += skb->len; + ra_list->pkt_count++; + + if (atomic_read(&priv->wmm.highest_queued_prio) < + tos_to_tid_inv[tid_down]) + atomic_set(&priv->wmm.highest_queued_prio, + tos_to_tid_inv[tid_down]); + + atomic_inc(&priv->wmm.tx_pkts_queued); + + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, flags); +} + +/* + * This function processes the get WMM status command response from firmware. + * + * The response may contain multiple TLVs - + * - AC Queue status TLVs + * - Current WMM Parameter IE TLV + * - Admission Control action frame TLVs + * + * This function parses the TLVs and then calls further specific functions + * to process any changes in the queue prioritize or state. + */ +int mwifiex_ret_wmm_get_status(struct mwifiex_private *priv, + const struct host_cmd_ds_command *resp) +{ + u8 *curr = (u8 *) &resp->params.get_wmm_status; + uint16_t resp_len = le16_to_cpu(resp->size), tlv_len; + int valid = true; + + struct mwifiex_ie_types_data *tlv_hdr; + struct mwifiex_ie_types_wmm_queue_status *tlv_wmm_qstatus; + struct ieee_types_wmm_parameter *wmm_param_ie = NULL; + struct mwifiex_wmm_ac_status *ac_status; + + dev_dbg(priv->adapter->dev, "info: WMM: WMM_GET_STATUS cmdresp received: %d\n", + resp_len); + + while ((resp_len >= sizeof(tlv_hdr->header)) && valid) { + tlv_hdr = (struct mwifiex_ie_types_data *) curr; + tlv_len = le16_to_cpu(tlv_hdr->header.len); + + switch (le16_to_cpu(tlv_hdr->header.type)) { + case TLV_TYPE_WMMQSTATUS: + tlv_wmm_qstatus = + (struct mwifiex_ie_types_wmm_queue_status *) + tlv_hdr; + dev_dbg(priv->adapter->dev, + "info: CMD_RESP: WMM_GET_STATUS:" + " QSTATUS TLV: %d, %d, %d\n", + tlv_wmm_qstatus->queue_index, + tlv_wmm_qstatus->flow_required, + tlv_wmm_qstatus->disabled); + + ac_status = &priv->wmm.ac_status[tlv_wmm_qstatus-> + queue_index]; + ac_status->disabled = tlv_wmm_qstatus->disabled; + ac_status->flow_required = + tlv_wmm_qstatus->flow_required; + ac_status->flow_created = tlv_wmm_qstatus->flow_created; + break; + + case WLAN_EID_VENDOR_SPECIFIC: + /* + * Point the regular IEEE IE 2 bytes into the Marvell IE + * and setup the IEEE IE type and length byte fields + */ + + wmm_param_ie = + (struct ieee_types_wmm_parameter *) (curr + + 2); + wmm_param_ie->vend_hdr.len = (u8) tlv_len; + wmm_param_ie->vend_hdr.element_id = + WLAN_EID_VENDOR_SPECIFIC; + + dev_dbg(priv->adapter->dev, + "info: CMD_RESP: WMM_GET_STATUS:" + " WMM Parameter Set Count: %d\n", + wmm_param_ie->qos_info_bitmap & + IEEE80211_WMM_IE_AP_QOSINFO_PARAM_SET_CNT_MASK); + + memcpy((u8 *) &priv->curr_bss_params.bss_descriptor. + wmm_ie, wmm_param_ie, + wmm_param_ie->vend_hdr.len + 2); + + break; + + default: + valid = false; + break; + } + + curr += (tlv_len + sizeof(tlv_hdr->header)); + resp_len -= (tlv_len + sizeof(tlv_hdr->header)); + } + + mwifiex_wmm_setup_queue_priorities(priv, wmm_param_ie); + mwifiex_wmm_setup_ac_downgrade(priv); + + return 0; +} + +/* + * Callback handler from the command module to allow insertion of a WMM TLV. + * + * If the BSS we are associating to supports WMM, this function adds the + * required WMM Information IE to the association request command buffer in + * the form of a Marvell extended IEEE IE. + */ +u32 +mwifiex_wmm_process_association_req(struct mwifiex_private *priv, + u8 **assoc_buf, + struct ieee_types_wmm_parameter *wmm_ie, + struct ieee80211_ht_cap *ht_cap) +{ + struct mwifiex_ie_types_wmm_param_set *wmm_tlv; + u32 ret_len = 0; + + /* Null checks */ + if (!assoc_buf) + return 0; + if (!(*assoc_buf)) + return 0; + + if (!wmm_ie) + return 0; + + dev_dbg(priv->adapter->dev, + "info: WMM: process assoc req: bss->wmm_ie=%#x\n", + wmm_ie->vend_hdr.element_id); + + if ((priv->wmm_required || + (ht_cap && (priv->adapter->config_bands & BAND_GN || + priv->adapter->config_bands & BAND_AN))) && + wmm_ie->vend_hdr.element_id == WLAN_EID_VENDOR_SPECIFIC) { + wmm_tlv = (struct mwifiex_ie_types_wmm_param_set *) *assoc_buf; + wmm_tlv->header.type = cpu_to_le16((u16) wmm_info_ie[0]); + wmm_tlv->header.len = cpu_to_le16((u16) wmm_info_ie[1]); + memcpy(wmm_tlv->wmm_ie, &wmm_info_ie[2], + le16_to_cpu(wmm_tlv->header.len)); + if (wmm_ie->qos_info_bitmap & IEEE80211_WMM_IE_AP_QOSINFO_UAPSD) + memcpy((u8 *) (wmm_tlv->wmm_ie + + le16_to_cpu(wmm_tlv->header.len) + - sizeof(priv->wmm_qosinfo)), + &priv->wmm_qosinfo, sizeof(priv->wmm_qosinfo)); + + ret_len = sizeof(wmm_tlv->header) + + le16_to_cpu(wmm_tlv->header.len); + + *assoc_buf += ret_len; + } + + return ret_len; +} + +/* + * This function computes the time delay in the driver queues for a + * given packet. + * + * When the packet is received at the OS/Driver interface, the current + * time is set in the packet structure. The difference between the present + * time and that received time is computed in this function and limited + * based on pre-compiled limits in the driver. + */ +u8 +mwifiex_wmm_compute_drv_pkt_delay(struct mwifiex_private *priv, + const struct sk_buff *skb) +{ + u8 ret_val; + struct timeval out_tstamp, in_tstamp; + u32 queue_delay; + + do_gettimeofday(&out_tstamp); + in_tstamp = ktime_to_timeval(skb->tstamp); + + queue_delay = (out_tstamp.tv_sec - in_tstamp.tv_sec) * 1000; + queue_delay += (out_tstamp.tv_usec - in_tstamp.tv_usec) / 1000; + + /* + * Queue delay is passed as a uint8 in units of 2ms (ms shifted + * by 1). Min value (other than 0) is therefore 2ms, max is 510ms. + * + * Pass max value if queue_delay is beyond the uint8 range + */ + ret_val = (u8) (min(queue_delay, priv->wmm.drv_pkt_delay_max) >> 1); + + dev_dbg(priv->adapter->dev, "data: WMM: Pkt Delay: %d ms," + " %d ms sent to FW\n", queue_delay, ret_val); + + return ret_val; +} + +/* + * This function retrieves the highest priority RA list table pointer. + */ +static struct mwifiex_ra_list_tbl * +mwifiex_wmm_get_highest_priolist_ptr(struct mwifiex_adapter *adapter, + struct mwifiex_private **priv, int *tid) +{ + struct mwifiex_private *priv_tmp; + struct mwifiex_ra_list_tbl *ptr; + struct mwifiex_tid_tbl *tid_ptr; + atomic_t *hqp; + unsigned long flags_bss, flags_ra; + int i, j; + + /* check the BSS with highest priority first */ + for (j = adapter->priv_num - 1; j >= 0; --j) { + spin_lock_irqsave(&adapter->bss_prio_tbl[j].bss_prio_lock, + flags_bss); + + /* iterate over BSS with the equal priority */ + list_for_each_entry(adapter->bss_prio_tbl[j].bss_prio_cur, + &adapter->bss_prio_tbl[j].bss_prio_head, + list) { + + priv_tmp = adapter->bss_prio_tbl[j].bss_prio_cur->priv; + + if (atomic_read(&priv_tmp->wmm.tx_pkts_queued) == 0) + continue; + + /* iterate over the WMM queues of the BSS */ + hqp = &priv_tmp->wmm.highest_queued_prio; + for (i = atomic_read(hqp); i >= LOW_PRIO_TID; --i) { + + spin_lock_irqsave(&priv_tmp->wmm. + ra_list_spinlock, flags_ra); + + tid_ptr = &(priv_tmp)->wmm. + tid_tbl_ptr[tos_to_tid[i]]; + + /* iterate over receiver addresses */ + list_for_each_entry(ptr, &tid_ptr->ra_list, + list) { + + if (!skb_queue_empty(&ptr->skb_head)) + /* holds both locks */ + goto found; + } + + spin_unlock_irqrestore(&priv_tmp->wmm. + ra_list_spinlock, + flags_ra); + } + } + + spin_unlock_irqrestore(&adapter->bss_prio_tbl[j].bss_prio_lock, + flags_bss); + } + + return NULL; + +found: + /* holds bss_prio_lock / ra_list_spinlock */ + if (atomic_read(hqp) > i) + atomic_set(hqp, i); + spin_unlock_irqrestore(&priv_tmp->wmm.ra_list_spinlock, flags_ra); + spin_unlock_irqrestore(&adapter->bss_prio_tbl[j].bss_prio_lock, + flags_bss); + + *priv = priv_tmp; + *tid = tos_to_tid[i]; + + return ptr; +} + +/* This functions rotates ra and bss lists so packets are picked round robin. + * + * After a packet is successfully transmitted, rotate the ra list, so the ra + * next to the one transmitted, will come first in the list. This way we pick + * the ra' in a round robin fashion. Same applies to bss nodes of equal + * priority. + * + * Function also increments wmm.packets_out counter. + */ +void mwifiex_rotate_priolists(struct mwifiex_private *priv, + struct mwifiex_ra_list_tbl *ra, + int tid) +{ + struct mwifiex_adapter *adapter = priv->adapter; + struct mwifiex_bss_prio_tbl *tbl = adapter->bss_prio_tbl; + struct mwifiex_tid_tbl *tid_ptr = &priv->wmm.tid_tbl_ptr[tid]; + unsigned long flags; + + spin_lock_irqsave(&tbl[priv->bss_priority].bss_prio_lock, flags); + /* + * dirty trick: we remove 'head' temporarily and reinsert it after + * curr bss node. imagine list to stay fixed while head is moved + */ + list_move(&tbl[priv->bss_priority].bss_prio_head, + &tbl[priv->bss_priority].bss_prio_cur->list); + spin_unlock_irqrestore(&tbl[priv->bss_priority].bss_prio_lock, flags); + + spin_lock_irqsave(&priv->wmm.ra_list_spinlock, flags); + if (mwifiex_is_ralist_valid(priv, ra, tid)) { + priv->wmm.packets_out[tid]++; + /* same as above */ + list_move(&tid_ptr->ra_list, &ra->list); + } + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, flags); +} + +/* + * This function checks if 11n aggregation is possible. + */ +static int +mwifiex_is_11n_aggragation_possible(struct mwifiex_private *priv, + struct mwifiex_ra_list_tbl *ptr, + int max_buf_size) +{ + int count = 0, total_size = 0; + struct sk_buff *skb, *tmp; + int max_amsdu_size; + + if (priv->bss_role == MWIFIEX_BSS_ROLE_UAP && priv->ap_11n_enabled && + ptr->is_11n_enabled) + max_amsdu_size = min_t(int, ptr->max_amsdu, max_buf_size); + else + max_amsdu_size = max_buf_size; + + skb_queue_walk_safe(&ptr->skb_head, skb, tmp) { + total_size += skb->len; + if (total_size >= max_amsdu_size) + break; + if (++count >= MIN_NUM_AMSDU) + return true; + } + + return false; +} + +/* + * This function sends a single packet to firmware for transmission. + */ +static void +mwifiex_send_single_packet(struct mwifiex_private *priv, + struct mwifiex_ra_list_tbl *ptr, int ptr_index, + unsigned long ra_list_flags) + __releases(&priv->wmm.ra_list_spinlock) +{ + struct sk_buff *skb, *skb_next; + struct mwifiex_tx_param tx_param; + struct mwifiex_adapter *adapter = priv->adapter; + struct mwifiex_txinfo *tx_info; + + if (skb_queue_empty(&ptr->skb_head)) { + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, + ra_list_flags); + dev_dbg(adapter->dev, "data: nothing to send\n"); + return; + } + + skb = skb_dequeue(&ptr->skb_head); + + tx_info = MWIFIEX_SKB_TXCB(skb); + dev_dbg(adapter->dev, "data: dequeuing the packet %p %p\n", ptr, skb); + + ptr->total_pkts_size -= skb->len; + + if (!skb_queue_empty(&ptr->skb_head)) + skb_next = skb_peek(&ptr->skb_head); + else + skb_next = NULL; + + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, ra_list_flags); + + tx_param.next_pkt_len = ((skb_next) ? skb_next->len + + sizeof(struct txpd) : 0); + + if (mwifiex_process_tx(priv, skb, &tx_param) == -EBUSY) { + /* Queue the packet back at the head */ + spin_lock_irqsave(&priv->wmm.ra_list_spinlock, ra_list_flags); + + if (!mwifiex_is_ralist_valid(priv, ptr, ptr_index)) { + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, + ra_list_flags); + mwifiex_write_data_complete(adapter, skb, 0, -1); + return; + } + + skb_queue_tail(&ptr->skb_head, skb); + + ptr->total_pkts_size += skb->len; + ptr->pkt_count++; + tx_info->flags |= MWIFIEX_BUF_FLAG_REQUEUED_PKT; + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, + ra_list_flags); + } else { + mwifiex_rotate_priolists(priv, ptr, ptr_index); + atomic_dec(&priv->wmm.tx_pkts_queued); + } +} + +/* + * This function checks if the first packet in the given RA list + * is already processed or not. + */ +static int +mwifiex_is_ptr_processed(struct mwifiex_private *priv, + struct mwifiex_ra_list_tbl *ptr) +{ + struct sk_buff *skb; + struct mwifiex_txinfo *tx_info; + + if (skb_queue_empty(&ptr->skb_head)) + return false; + + skb = skb_peek(&ptr->skb_head); + + tx_info = MWIFIEX_SKB_TXCB(skb); + if (tx_info->flags & MWIFIEX_BUF_FLAG_REQUEUED_PKT) + return true; + + return false; +} + +/* + * This function sends a single processed packet to firmware for + * transmission. + */ +static void +mwifiex_send_processed_packet(struct mwifiex_private *priv, + struct mwifiex_ra_list_tbl *ptr, int ptr_index, + unsigned long ra_list_flags) + __releases(&priv->wmm.ra_list_spinlock) +{ + struct mwifiex_tx_param tx_param; + struct mwifiex_adapter *adapter = priv->adapter; + int ret = -1; + struct sk_buff *skb, *skb_next; + struct mwifiex_txinfo *tx_info; + + if (skb_queue_empty(&ptr->skb_head)) { + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, + ra_list_flags); + return; + } + + skb = skb_dequeue(&ptr->skb_head); + + if (!skb_queue_empty(&ptr->skb_head)) + skb_next = skb_peek(&ptr->skb_head); + else + skb_next = NULL; + + tx_info = MWIFIEX_SKB_TXCB(skb); + + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, ra_list_flags); + + if (adapter->iface_type == MWIFIEX_USB) { + adapter->data_sent = true; + ret = adapter->if_ops.host_to_card(adapter, MWIFIEX_USB_EP_DATA, + skb, NULL); + } else { + tx_param.next_pkt_len = + ((skb_next) ? skb_next->len + + sizeof(struct txpd) : 0); + ret = adapter->if_ops.host_to_card(adapter, MWIFIEX_TYPE_DATA, + skb, &tx_param); + } + + switch (ret) { + case -EBUSY: + dev_dbg(adapter->dev, "data: -EBUSY is returned\n"); + spin_lock_irqsave(&priv->wmm.ra_list_spinlock, ra_list_flags); + + if (!mwifiex_is_ralist_valid(priv, ptr, ptr_index)) { + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, + ra_list_flags); + mwifiex_write_data_complete(adapter, skb, 0, -1); + return; + } + + skb_queue_tail(&ptr->skb_head, skb); + + tx_info->flags |= MWIFIEX_BUF_FLAG_REQUEUED_PKT; + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, + ra_list_flags); + break; + case -1: + if (adapter->iface_type != MWIFIEX_PCIE) + adapter->data_sent = false; + dev_err(adapter->dev, "host_to_card failed: %#x\n", ret); + adapter->dbg.num_tx_host_to_card_failure++; + mwifiex_write_data_complete(adapter, skb, 0, ret); + break; + case -EINPROGRESS: + if (adapter->iface_type != MWIFIEX_PCIE) + adapter->data_sent = false; + default: + break; + } + if (ret != -EBUSY) { + mwifiex_rotate_priolists(priv, ptr, ptr_index); + atomic_dec(&priv->wmm.tx_pkts_queued); + } +} + +/* + * This function dequeues a packet from the highest priority list + * and transmits it. + */ +static int +mwifiex_dequeue_tx_packet(struct mwifiex_adapter *adapter) +{ + struct mwifiex_ra_list_tbl *ptr; + struct mwifiex_private *priv = NULL; + int ptr_index = 0; + u8 ra[ETH_ALEN]; + int tid_del = 0, tid = 0; + unsigned long flags; + + ptr = mwifiex_wmm_get_highest_priolist_ptr(adapter, &priv, &ptr_index); + if (!ptr) + return -1; + + tid = mwifiex_get_tid(ptr); + + dev_dbg(adapter->dev, "data: tid=%d\n", tid); + + spin_lock_irqsave(&priv->wmm.ra_list_spinlock, flags); + if (!mwifiex_is_ralist_valid(priv, ptr, ptr_index)) { + spin_unlock_irqrestore(&priv->wmm.ra_list_spinlock, flags); + return -1; + } + + if (mwifiex_is_ptr_processed(priv, ptr)) { + mwifiex_send_processed_packet(priv, ptr, ptr_index, flags); + /* ra_list_spinlock has been freed in + mwifiex_send_processed_packet() */ + return 0; + } + + if (!ptr->is_11n_enabled || + mwifiex_is_ba_stream_setup(priv, ptr, tid) || + priv->wps.session_enable || + ((priv->sec_info.wpa_enabled || + priv->sec_info.wpa2_enabled) && + !priv->wpa_is_gtk_set)) { + mwifiex_send_single_packet(priv, ptr, ptr_index, flags); + /* ra_list_spinlock has been freed in + mwifiex_send_single_packet() */ + } else { + if (mwifiex_is_ampdu_allowed(priv, tid) && + ptr->pkt_count > ptr->ba_packet_thr) { + if (mwifiex_space_avail_for_new_ba_stream(adapter)) { + mwifiex_create_ba_tbl(priv, ptr->ra, tid, + BA_SETUP_INPROGRESS); + mwifiex_send_addba(priv, tid, ptr->ra); + } else if (mwifiex_find_stream_to_delete + (priv, tid, &tid_del, ra)) { + mwifiex_create_ba_tbl(priv, ptr->ra, tid, + BA_SETUP_INPROGRESS); + mwifiex_send_delba(priv, tid_del, ra, 1); + } + } + if (mwifiex_is_amsdu_allowed(priv, tid) && + mwifiex_is_11n_aggragation_possible(priv, ptr, + adapter->tx_buf_size)) + mwifiex_11n_aggregate_pkt(priv, ptr, ptr_index, flags); + /* ra_list_spinlock has been freed in + mwifiex_11n_aggregate_pkt() */ + else + mwifiex_send_single_packet(priv, ptr, ptr_index, flags); + /* ra_list_spinlock has been freed in + mwifiex_send_single_packet() */ + } + return 0; +} + +/* + * This function transmits the highest priority packet awaiting in the + * WMM Queues. + */ +void +mwifiex_wmm_process_tx(struct mwifiex_adapter *adapter) +{ + do { + /* Check if busy */ + if (adapter->data_sent || adapter->tx_lock_flag) + break; + + if (mwifiex_dequeue_tx_packet(adapter)) + break; + } while (!mwifiex_wmm_lists_empty(adapter)); +} diff --git a/drivers/net/wireless/mwifiex/wmm.h b/drivers/net/wireless/mwifiex/wmm.h new file mode 100644 index 0000000..644d6e0 --- /dev/null +++ b/drivers/net/wireless/mwifiex/wmm.h @@ -0,0 +1,115 @@ +/* + * Marvell Wireless LAN device driver: WMM + * + * Copyright (C) 2011, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + */ + +#ifndef _MWIFIEX_WMM_H_ +#define _MWIFIEX_WMM_H_ + +enum ieee_types_wmm_aciaifsn_bitmasks { + MWIFIEX_AIFSN = (BIT(0) | BIT(1) | BIT(2) | BIT(3)), + MWIFIEX_ACM = BIT(4), + MWIFIEX_ACI = (BIT(5) | BIT(6)), +}; + +enum ieee_types_wmm_ecw_bitmasks { + MWIFIEX_ECW_MIN = (BIT(0) | BIT(1) | BIT(2) | BIT(3)), + MWIFIEX_ECW_MAX = (BIT(4) | BIT(5) | BIT(6) | BIT(7)), +}; + +static const u16 mwifiex_1d_to_wmm_queue[8] = { 1, 0, 0, 1, 2, 2, 3, 3 }; + +/* + * This function retrieves the TID of the given RA list. + */ +static inline int +mwifiex_get_tid(struct mwifiex_ra_list_tbl *ptr) +{ + struct sk_buff *skb; + + if (skb_queue_empty(&ptr->skb_head)) + return 0; + + skb = skb_peek(&ptr->skb_head); + + return skb->priority; +} + +/* + * This function gets the length of a list. + */ +static inline int +mwifiex_wmm_list_len(struct list_head *head) +{ + struct list_head *pos; + int count = 0; + + list_for_each(pos, head) + ++count; + + return count; +} + +/* + * This function checks if a RA list is empty or not. + */ +static inline u8 +mwifiex_wmm_is_ra_list_empty(struct list_head *ra_list_hhead) +{ + struct mwifiex_ra_list_tbl *ra_list; + int is_list_empty; + + list_for_each_entry(ra_list, ra_list_hhead, list) { + is_list_empty = skb_queue_empty(&ra_list->skb_head); + if (!is_list_empty) + return false; + } + + return true; +} + +void mwifiex_wmm_add_buf_txqueue(struct mwifiex_private *priv, + struct sk_buff *skb); +void mwifiex_ralist_add(struct mwifiex_private *priv, u8 *ra); +void mwifiex_rotate_priolists(struct mwifiex_private *priv, + struct mwifiex_ra_list_tbl *ra, + int tid); + +int mwifiex_wmm_lists_empty(struct mwifiex_adapter *adapter); +void mwifiex_wmm_process_tx(struct mwifiex_adapter *adapter); +int mwifiex_is_ralist_valid(struct mwifiex_private *priv, + struct mwifiex_ra_list_tbl *ra_list, int tid); + +u8 mwifiex_wmm_compute_drv_pkt_delay(struct mwifiex_private *priv, + const struct sk_buff *skb); +void mwifiex_wmm_init(struct mwifiex_adapter *adapter); + +extern u32 mwifiex_wmm_process_association_req(struct mwifiex_private *priv, + u8 **assoc_buf, + struct ieee_types_wmm_parameter + *wmmie, + struct ieee80211_ht_cap + *htcap); + +void mwifiex_wmm_setup_queue_priorities(struct mwifiex_private *priv, + struct ieee_types_wmm_parameter + *wmm_ie); +void mwifiex_wmm_setup_ac_downgrade(struct mwifiex_private *priv); +extern int mwifiex_ret_wmm_get_status(struct mwifiex_private *priv, + const struct host_cmd_ds_command *resp); + +#endif /* !_MWIFIEX_WMM_H_ */ diff --git a/drivers/net/wireless/mwl8k.c b/drivers/net/wireless/mwl8k.c new file mode 100644 index 0000000..9088680 --- /dev/null +++ b/drivers/net/wireless/mwl8k.c @@ -0,0 +1,6193 @@ +/* + * drivers/net/wireless/mwl8k.c + * Driver for Marvell TOPDOG 802.11 Wireless cards + * + * Copyright (C) 2008, 2009, 2010 Marvell Semiconductor Inc. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MWL8K_DESC "Marvell TOPDOG(R) 802.11 Wireless Network Driver" +#define MWL8K_NAME KBUILD_MODNAME +#define MWL8K_VERSION "0.13" + +/* Module parameters */ +static bool ap_mode_default; +module_param(ap_mode_default, bool, 0); +MODULE_PARM_DESC(ap_mode_default, + "Set to 1 to make ap mode the default instead of sta mode"); + +/* Register definitions */ +#define MWL8K_HIU_GEN_PTR 0x00000c10 +#define MWL8K_MODE_STA 0x0000005a +#define MWL8K_MODE_AP 0x000000a5 +#define MWL8K_HIU_INT_CODE 0x00000c14 +#define MWL8K_FWSTA_READY 0xf0f1f2f4 +#define MWL8K_FWAP_READY 0xf1f2f4a5 +#define MWL8K_INT_CODE_CMD_FINISHED 0x00000005 +#define MWL8K_HIU_SCRATCH 0x00000c40 + +/* Host->device communications */ +#define MWL8K_HIU_H2A_INTERRUPT_EVENTS 0x00000c18 +#define MWL8K_HIU_H2A_INTERRUPT_STATUS 0x00000c1c +#define MWL8K_HIU_H2A_INTERRUPT_MASK 0x00000c20 +#define MWL8K_HIU_H2A_INTERRUPT_CLEAR_SEL 0x00000c24 +#define MWL8K_HIU_H2A_INTERRUPT_STATUS_MASK 0x00000c28 +#define MWL8K_H2A_INT_DUMMY (1 << 20) +#define MWL8K_H2A_INT_RESET (1 << 15) +#define MWL8K_H2A_INT_DOORBELL (1 << 1) +#define MWL8K_H2A_INT_PPA_READY (1 << 0) + +/* Device->host communications */ +#define MWL8K_HIU_A2H_INTERRUPT_EVENTS 0x00000c2c +#define MWL8K_HIU_A2H_INTERRUPT_STATUS 0x00000c30 +#define MWL8K_HIU_A2H_INTERRUPT_MASK 0x00000c34 +#define MWL8K_HIU_A2H_INTERRUPT_CLEAR_SEL 0x00000c38 +#define MWL8K_HIU_A2H_INTERRUPT_STATUS_MASK 0x00000c3c +#define MWL8K_A2H_INT_DUMMY (1 << 20) +#define MWL8K_A2H_INT_BA_WATCHDOG (1 << 14) +#define MWL8K_A2H_INT_CHNL_SWITCHED (1 << 11) +#define MWL8K_A2H_INT_QUEUE_EMPTY (1 << 10) +#define MWL8K_A2H_INT_RADAR_DETECT (1 << 7) +#define MWL8K_A2H_INT_RADIO_ON (1 << 6) +#define MWL8K_A2H_INT_RADIO_OFF (1 << 5) +#define MWL8K_A2H_INT_MAC_EVENT (1 << 3) +#define MWL8K_A2H_INT_OPC_DONE (1 << 2) +#define MWL8K_A2H_INT_RX_READY (1 << 1) +#define MWL8K_A2H_INT_TX_DONE (1 << 0) + +/* HW micro second timer register + * located at offset 0xA600. This + * will be used to timestamp tx + * packets. + */ + +#define MWL8K_HW_TIMER_REGISTER 0x0000a600 + +#define MWL8K_A2H_EVENTS (MWL8K_A2H_INT_DUMMY | \ + MWL8K_A2H_INT_CHNL_SWITCHED | \ + MWL8K_A2H_INT_QUEUE_EMPTY | \ + MWL8K_A2H_INT_RADAR_DETECT | \ + MWL8K_A2H_INT_RADIO_ON | \ + MWL8K_A2H_INT_RADIO_OFF | \ + MWL8K_A2H_INT_MAC_EVENT | \ + MWL8K_A2H_INT_OPC_DONE | \ + MWL8K_A2H_INT_RX_READY | \ + MWL8K_A2H_INT_TX_DONE | \ + MWL8K_A2H_INT_BA_WATCHDOG) + +#define MWL8K_RX_QUEUES 1 +#define MWL8K_TX_WMM_QUEUES 4 +#define MWL8K_MAX_AMPDU_QUEUES 8 +#define MWL8K_MAX_TX_QUEUES (MWL8K_TX_WMM_QUEUES + MWL8K_MAX_AMPDU_QUEUES) +#define mwl8k_tx_queues(priv) (MWL8K_TX_WMM_QUEUES + (priv)->num_ampdu_queues) + +/* txpriorities are mapped with hw queues. + * Each hw queue has a txpriority. + */ +#define TOTAL_HW_TX_QUEUES 8 + +/* Each HW queue can have one AMPDU stream. + * But, because one of the hw queue is reserved, + * maximum AMPDU queues that can be created are + * one short of total tx queues. + */ +#define MWL8K_NUM_AMPDU_STREAMS (TOTAL_HW_TX_QUEUES - 1) + +struct rxd_ops { + int rxd_size; + void (*rxd_init)(void *rxd, dma_addr_t next_dma_addr); + void (*rxd_refill)(void *rxd, dma_addr_t addr, int len); + int (*rxd_process)(void *rxd, struct ieee80211_rx_status *status, + __le16 *qos, s8 *noise); +}; + +struct mwl8k_device_info { + char *part_name; + char *helper_image; + char *fw_image_sta; + char *fw_image_ap; + struct rxd_ops *ap_rxd_ops; + u32 fw_api_ap; +}; + +struct mwl8k_rx_queue { + int rxd_count; + + /* hw receives here */ + int head; + + /* refill descs here */ + int tail; + + void *rxd; + dma_addr_t rxd_dma; + struct { + struct sk_buff *skb; + DEFINE_DMA_UNMAP_ADDR(dma); + } *buf; +}; + +struct mwl8k_tx_queue { + /* hw transmits here */ + int head; + + /* sw appends here */ + int tail; + + unsigned int len; + struct mwl8k_tx_desc *txd; + dma_addr_t txd_dma; + struct sk_buff **skb; +}; + +enum { + AMPDU_NO_STREAM, + AMPDU_STREAM_NEW, + AMPDU_STREAM_IN_PROGRESS, + AMPDU_STREAM_ACTIVE, +}; + +struct mwl8k_ampdu_stream { + struct ieee80211_sta *sta; + u8 tid; + u8 state; + u8 idx; +}; + +struct mwl8k_priv { + struct ieee80211_hw *hw; + struct pci_dev *pdev; + int irq; + + struct mwl8k_device_info *device_info; + + void __iomem *sram; + void __iomem *regs; + + /* firmware */ + const struct firmware *fw_helper; + const struct firmware *fw_ucode; + + /* hardware/firmware parameters */ + bool ap_fw; + struct rxd_ops *rxd_ops; + struct ieee80211_supported_band band_24; + struct ieee80211_channel channels_24[14]; + struct ieee80211_rate rates_24[13]; + struct ieee80211_supported_band band_50; + struct ieee80211_channel channels_50[4]; + struct ieee80211_rate rates_50[8]; + u32 ap_macids_supported; + u32 sta_macids_supported; + + /* Ampdu stream information */ + u8 num_ampdu_queues; + spinlock_t stream_lock; + struct mwl8k_ampdu_stream ampdu[MWL8K_MAX_AMPDU_QUEUES]; + struct work_struct watchdog_ba_handle; + + /* firmware access */ + struct mutex fw_mutex; + struct task_struct *fw_mutex_owner; + struct task_struct *hw_restart_owner; + int fw_mutex_depth; + struct completion *hostcmd_wait; + + atomic_t watchdog_event_pending; + + /* lock held over TX and TX reap */ + spinlock_t tx_lock; + + /* TX quiesce completion, protected by fw_mutex and tx_lock */ + struct completion *tx_wait; + + /* List of interfaces. */ + u32 macids_used; + struct list_head vif_list; + + /* power management status cookie from firmware */ + u32 *cookie; + dma_addr_t cookie_dma; + + u16 num_mcaddrs; + u8 hw_rev; + u32 fw_rev; + u32 caps; + + /* + * Running count of TX packets in flight, to avoid + * iterating over the transmit rings each time. + */ + int pending_tx_pkts; + + struct mwl8k_rx_queue rxq[MWL8K_RX_QUEUES]; + struct mwl8k_tx_queue txq[MWL8K_MAX_TX_QUEUES]; + u32 txq_offset[MWL8K_MAX_TX_QUEUES]; + + bool radio_on; + bool radio_short_preamble; + bool sniffer_enabled; + bool wmm_enabled; + + /* XXX need to convert this to handle multiple interfaces */ + bool capture_beacon; + u8 capture_bssid[ETH_ALEN]; + struct sk_buff *beacon_skb; + + /* + * This FJ worker has to be global as it is scheduled from the + * RX handler. At this point we don't know which interface it + * belongs to until the list of bssids waiting to complete join + * is checked. + */ + struct work_struct finalize_join_worker; + + /* Tasklet to perform TX reclaim. */ + struct tasklet_struct poll_tx_task; + + /* Tasklet to perform RX. */ + struct tasklet_struct poll_rx_task; + + /* Most recently reported noise in dBm */ + s8 noise; + + /* + * preserve the queue configurations so they can be restored if/when + * the firmware image is swapped. + */ + struct ieee80211_tx_queue_params wmm_params[MWL8K_TX_WMM_QUEUES]; + + /* To perform the task of reloading the firmware */ + struct work_struct fw_reload; + bool hw_restart_in_progress; + + /* async firmware loading state */ + unsigned fw_state; + char *fw_pref; + char *fw_alt; + bool is_8764; + struct completion firmware_loading_complete; + + /* bitmap of running BSSes */ + u32 running_bsses; +}; + +#define MAX_WEP_KEY_LEN 13 +#define NUM_WEP_KEYS 4 + +/* Per interface specific private data */ +struct mwl8k_vif { + struct list_head list; + struct ieee80211_vif *vif; + + /* Firmware macid for this vif. */ + int macid; + + /* Non AMPDU sequence number assigned by driver. */ + u16 seqno; + + /* Saved WEP keys */ + struct { + u8 enabled; + u8 key[sizeof(struct ieee80211_key_conf) + MAX_WEP_KEY_LEN]; + } wep_key_conf[NUM_WEP_KEYS]; + + /* BSSID */ + u8 bssid[ETH_ALEN]; + + /* A flag to indicate is HW crypto is enabled for this bssid */ + bool is_hw_crypto_enabled; +}; +#define MWL8K_VIF(_vif) ((struct mwl8k_vif *)&((_vif)->drv_priv)) +#define IEEE80211_KEY_CONF(_u8) ((struct ieee80211_key_conf *)(_u8)) + +struct tx_traffic_info { + u32 start_time; + u32 pkts; +}; + +#define MWL8K_MAX_TID 8 +struct mwl8k_sta { + /* Index into station database. Returned by UPDATE_STADB. */ + u8 peer_id; + u8 is_ampdu_allowed; + struct tx_traffic_info tx_stats[MWL8K_MAX_TID]; +}; +#define MWL8K_STA(_sta) ((struct mwl8k_sta *)&((_sta)->drv_priv)) + +static const struct ieee80211_channel mwl8k_channels_24[] = { + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2412, .hw_value = 1, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2417, .hw_value = 2, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2422, .hw_value = 3, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2427, .hw_value = 4, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2432, .hw_value = 5, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2437, .hw_value = 6, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2442, .hw_value = 7, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2447, .hw_value = 8, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2452, .hw_value = 9, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2457, .hw_value = 10, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2462, .hw_value = 11, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2467, .hw_value = 12, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2472, .hw_value = 13, }, + { .band = IEEE80211_BAND_2GHZ, .center_freq = 2484, .hw_value = 14, }, +}; + +static const struct ieee80211_rate mwl8k_rates_24[] = { + { .bitrate = 10, .hw_value = 2, }, + { .bitrate = 20, .hw_value = 4, }, + { .bitrate = 55, .hw_value = 11, }, + { .bitrate = 110, .hw_value = 22, }, + { .bitrate = 220, .hw_value = 44, }, + { .bitrate = 60, .hw_value = 12, }, + { .bitrate = 90, .hw_value = 18, }, + { .bitrate = 120, .hw_value = 24, }, + { .bitrate = 180, .hw_value = 36, }, + { .bitrate = 240, .hw_value = 48, }, + { .bitrate = 360, .hw_value = 72, }, + { .bitrate = 480, .hw_value = 96, }, + { .bitrate = 540, .hw_value = 108, }, +}; + +static const struct ieee80211_channel mwl8k_channels_50[] = { + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5180, .hw_value = 36, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5200, .hw_value = 40, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5220, .hw_value = 44, }, + { .band = IEEE80211_BAND_5GHZ, .center_freq = 5240, .hw_value = 48, }, +}; + +static const struct ieee80211_rate mwl8k_rates_50[] = { + { .bitrate = 60, .hw_value = 12, }, + { .bitrate = 90, .hw_value = 18, }, + { .bitrate = 120, .hw_value = 24, }, + { .bitrate = 180, .hw_value = 36, }, + { .bitrate = 240, .hw_value = 48, }, + { .bitrate = 360, .hw_value = 72, }, + { .bitrate = 480, .hw_value = 96, }, + { .bitrate = 540, .hw_value = 108, }, +}; + +/* Set or get info from Firmware */ +#define MWL8K_CMD_GET 0x0000 +#define MWL8K_CMD_SET 0x0001 +#define MWL8K_CMD_SET_LIST 0x0002 + +/* Firmware command codes */ +#define MWL8K_CMD_CODE_DNLD 0x0001 +#define MWL8K_CMD_GET_HW_SPEC 0x0003 +#define MWL8K_CMD_SET_HW_SPEC 0x0004 +#define MWL8K_CMD_MAC_MULTICAST_ADR 0x0010 +#define MWL8K_CMD_GET_STAT 0x0014 +#define MWL8K_CMD_RADIO_CONTROL 0x001c +#define MWL8K_CMD_RF_TX_POWER 0x001e +#define MWL8K_CMD_TX_POWER 0x001f +#define MWL8K_CMD_RF_ANTENNA 0x0020 +#define MWL8K_CMD_SET_BEACON 0x0100 /* per-vif */ +#define MWL8K_CMD_SET_PRE_SCAN 0x0107 +#define MWL8K_CMD_SET_POST_SCAN 0x0108 +#define MWL8K_CMD_SET_RF_CHANNEL 0x010a +#define MWL8K_CMD_SET_AID 0x010d +#define MWL8K_CMD_SET_RATE 0x0110 +#define MWL8K_CMD_SET_FINALIZE_JOIN 0x0111 +#define MWL8K_CMD_RTS_THRESHOLD 0x0113 +#define MWL8K_CMD_SET_SLOT 0x0114 +#define MWL8K_CMD_SET_EDCA_PARAMS 0x0115 +#define MWL8K_CMD_SET_WMM_MODE 0x0123 +#define MWL8K_CMD_MIMO_CONFIG 0x0125 +#define MWL8K_CMD_USE_FIXED_RATE 0x0126 +#define MWL8K_CMD_ENABLE_SNIFFER 0x0150 +#define MWL8K_CMD_SET_MAC_ADDR 0x0202 /* per-vif */ +#define MWL8K_CMD_SET_RATEADAPT_MODE 0x0203 +#define MWL8K_CMD_GET_WATCHDOG_BITMAP 0x0205 +#define MWL8K_CMD_DEL_MAC_ADDR 0x0206 /* per-vif */ +#define MWL8K_CMD_BSS_START 0x1100 /* per-vif */ +#define MWL8K_CMD_SET_NEW_STN 0x1111 /* per-vif */ +#define MWL8K_CMD_UPDATE_ENCRYPTION 0x1122 /* per-vif */ +#define MWL8K_CMD_UPDATE_STADB 0x1123 +#define MWL8K_CMD_BASTREAM 0x1125 + +static const char *mwl8k_cmd_name(__le16 cmd, char *buf, int bufsize) +{ + u16 command = le16_to_cpu(cmd); + +#define MWL8K_CMDNAME(x) case MWL8K_CMD_##x: do {\ + snprintf(buf, bufsize, "%s", #x);\ + return buf;\ + } while (0) + switch (command & ~0x8000) { + MWL8K_CMDNAME(CODE_DNLD); + MWL8K_CMDNAME(GET_HW_SPEC); + MWL8K_CMDNAME(SET_HW_SPEC); + MWL8K_CMDNAME(MAC_MULTICAST_ADR); + MWL8K_CMDNAME(GET_STAT); + MWL8K_CMDNAME(RADIO_CONTROL); + MWL8K_CMDNAME(RF_TX_POWER); + MWL8K_CMDNAME(TX_POWER); + MWL8K_CMDNAME(RF_ANTENNA); + MWL8K_CMDNAME(SET_BEACON); + MWL8K_CMDNAME(SET_PRE_SCAN); + MWL8K_CMDNAME(SET_POST_SCAN); + MWL8K_CMDNAME(SET_RF_CHANNEL); + MWL8K_CMDNAME(SET_AID); + MWL8K_CMDNAME(SET_RATE); + MWL8K_CMDNAME(SET_FINALIZE_JOIN); + MWL8K_CMDNAME(RTS_THRESHOLD); + MWL8K_CMDNAME(SET_SLOT); + MWL8K_CMDNAME(SET_EDCA_PARAMS); + MWL8K_CMDNAME(SET_WMM_MODE); + MWL8K_CMDNAME(MIMO_CONFIG); + MWL8K_CMDNAME(USE_FIXED_RATE); + MWL8K_CMDNAME(ENABLE_SNIFFER); + MWL8K_CMDNAME(SET_MAC_ADDR); + MWL8K_CMDNAME(SET_RATEADAPT_MODE); + MWL8K_CMDNAME(BSS_START); + MWL8K_CMDNAME(SET_NEW_STN); + MWL8K_CMDNAME(UPDATE_ENCRYPTION); + MWL8K_CMDNAME(UPDATE_STADB); + MWL8K_CMDNAME(BASTREAM); + MWL8K_CMDNAME(GET_WATCHDOG_BITMAP); + default: + snprintf(buf, bufsize, "0x%x", cmd); + } +#undef MWL8K_CMDNAME + + return buf; +} + +/* Hardware and firmware reset */ +static void mwl8k_hw_reset(struct mwl8k_priv *priv) +{ + iowrite32(MWL8K_H2A_INT_RESET, + priv->regs + MWL8K_HIU_H2A_INTERRUPT_EVENTS); + iowrite32(MWL8K_H2A_INT_RESET, + priv->regs + MWL8K_HIU_H2A_INTERRUPT_EVENTS); + msleep(20); +} + +/* Release fw image */ +static void mwl8k_release_fw(const struct firmware **fw) +{ + if (*fw == NULL) + return; + release_firmware(*fw); + *fw = NULL; +} + +static void mwl8k_release_firmware(struct mwl8k_priv *priv) +{ + mwl8k_release_fw(&priv->fw_ucode); + mwl8k_release_fw(&priv->fw_helper); +} + +/* states for asynchronous f/w loading */ +static void mwl8k_fw_state_machine(const struct firmware *fw, void *context); +enum { + FW_STATE_INIT = 0, + FW_STATE_LOADING_PREF, + FW_STATE_LOADING_ALT, + FW_STATE_ERROR, +}; + +/* Request fw image */ +static int mwl8k_request_fw(struct mwl8k_priv *priv, + const char *fname, const struct firmware **fw, + bool nowait) +{ + /* release current image */ + if (*fw != NULL) + mwl8k_release_fw(fw); + + if (nowait) + return request_firmware_nowait(THIS_MODULE, 1, fname, + &priv->pdev->dev, GFP_KERNEL, + priv, mwl8k_fw_state_machine); + else + return request_firmware(fw, fname, &priv->pdev->dev); +} + +static int mwl8k_request_firmware(struct mwl8k_priv *priv, char *fw_image, + bool nowait) +{ + struct mwl8k_device_info *di = priv->device_info; + int rc; + + if (di->helper_image != NULL) { + if (nowait) + rc = mwl8k_request_fw(priv, di->helper_image, + &priv->fw_helper, true); + else + rc = mwl8k_request_fw(priv, di->helper_image, + &priv->fw_helper, false); + if (rc) + printk(KERN_ERR "%s: Error requesting helper fw %s\n", + pci_name(priv->pdev), di->helper_image); + + if (rc || nowait) + return rc; + } + + if (nowait) { + /* + * if we get here, no helper image is needed. Skip the + * FW_STATE_INIT state. + */ + priv->fw_state = FW_STATE_LOADING_PREF; + rc = mwl8k_request_fw(priv, fw_image, + &priv->fw_ucode, + true); + } else + rc = mwl8k_request_fw(priv, fw_image, + &priv->fw_ucode, false); + if (rc) { + printk(KERN_ERR "%s: Error requesting firmware file %s\n", + pci_name(priv->pdev), fw_image); + mwl8k_release_fw(&priv->fw_helper); + return rc; + } + + return 0; +} + +struct mwl8k_cmd_pkt { + __le16 code; + __le16 length; + __u8 seq_num; + __u8 macid; + __le16 result; + char payload[0]; +} __packed; + +/* + * Firmware loading. + */ +static int +mwl8k_send_fw_load_cmd(struct mwl8k_priv *priv, void *data, int length) +{ + void __iomem *regs = priv->regs; + dma_addr_t dma_addr; + int loops; + + dma_addr = pci_map_single(priv->pdev, data, length, PCI_DMA_TODEVICE); + if (pci_dma_mapping_error(priv->pdev, dma_addr)) + return -ENOMEM; + + iowrite32(dma_addr, regs + MWL8K_HIU_GEN_PTR); + iowrite32(0, regs + MWL8K_HIU_INT_CODE); + iowrite32(MWL8K_H2A_INT_DOORBELL, + regs + MWL8K_HIU_H2A_INTERRUPT_EVENTS); + iowrite32(MWL8K_H2A_INT_DUMMY, + regs + MWL8K_HIU_H2A_INTERRUPT_EVENTS); + + loops = 1000; + do { + u32 int_code; + if (priv->is_8764) { + int_code = ioread32(regs + + MWL8K_HIU_H2A_INTERRUPT_STATUS); + if (int_code == 0) + break; + } else { + int_code = ioread32(regs + MWL8K_HIU_INT_CODE); + if (int_code == MWL8K_INT_CODE_CMD_FINISHED) { + iowrite32(0, regs + MWL8K_HIU_INT_CODE); + break; + } + } + cond_resched(); + udelay(1); + } while (--loops); + + pci_unmap_single(priv->pdev, dma_addr, length, PCI_DMA_TODEVICE); + + return loops ? 0 : -ETIMEDOUT; +} + +static int mwl8k_load_fw_image(struct mwl8k_priv *priv, + const u8 *data, size_t length) +{ + struct mwl8k_cmd_pkt *cmd; + int done; + int rc = 0; + + cmd = kmalloc(sizeof(*cmd) + 256, GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->code = cpu_to_le16(MWL8K_CMD_CODE_DNLD); + cmd->seq_num = 0; + cmd->macid = 0; + cmd->result = 0; + + done = 0; + while (length) { + int block_size = length > 256 ? 256 : length; + + memcpy(cmd->payload, data + done, block_size); + cmd->length = cpu_to_le16(block_size); + + rc = mwl8k_send_fw_load_cmd(priv, cmd, + sizeof(*cmd) + block_size); + if (rc) + break; + + done += block_size; + length -= block_size; + } + + if (!rc) { + cmd->length = 0; + rc = mwl8k_send_fw_load_cmd(priv, cmd, sizeof(*cmd)); + } + + kfree(cmd); + + return rc; +} + +static int mwl8k_feed_fw_image(struct mwl8k_priv *priv, + const u8 *data, size_t length) +{ + unsigned char *buffer; + int may_continue, rc = 0; + u32 done, prev_block_size; + + buffer = kmalloc(1024, GFP_KERNEL); + if (buffer == NULL) + return -ENOMEM; + + done = 0; + prev_block_size = 0; + may_continue = 1000; + while (may_continue > 0) { + u32 block_size; + + block_size = ioread32(priv->regs + MWL8K_HIU_SCRATCH); + if (block_size & 1) { + block_size &= ~1; + may_continue--; + } else { + done += prev_block_size; + length -= prev_block_size; + } + + if (block_size > 1024 || block_size > length) { + rc = -EOVERFLOW; + break; + } + + if (length == 0) { + rc = 0; + break; + } + + if (block_size == 0) { + rc = -EPROTO; + may_continue--; + udelay(1); + continue; + } + + prev_block_size = block_size; + memcpy(buffer, data + done, block_size); + + rc = mwl8k_send_fw_load_cmd(priv, buffer, block_size); + if (rc) + break; + } + + if (!rc && length != 0) + rc = -EREMOTEIO; + + kfree(buffer); + + return rc; +} + +static int mwl8k_load_firmware(struct ieee80211_hw *hw) +{ + struct mwl8k_priv *priv = hw->priv; + const struct firmware *fw = priv->fw_ucode; + int rc; + int loops; + + if (!memcmp(fw->data, "\x01\x00\x00\x00", 4) && !priv->is_8764) { + const struct firmware *helper = priv->fw_helper; + + if (helper == NULL) { + printk(KERN_ERR "%s: helper image needed but none " + "given\n", pci_name(priv->pdev)); + return -EINVAL; + } + + rc = mwl8k_load_fw_image(priv, helper->data, helper->size); + if (rc) { + printk(KERN_ERR "%s: unable to load firmware " + "helper image\n", pci_name(priv->pdev)); + return rc; + } + msleep(20); + + rc = mwl8k_feed_fw_image(priv, fw->data, fw->size); + } else { + if (priv->is_8764) + rc = mwl8k_feed_fw_image(priv, fw->data, fw->size); + else + rc = mwl8k_load_fw_image(priv, fw->data, fw->size); + } + + if (rc) { + printk(KERN_ERR "%s: unable to load firmware image\n", + pci_name(priv->pdev)); + return rc; + } + + iowrite32(MWL8K_MODE_STA, priv->regs + MWL8K_HIU_GEN_PTR); + + loops = 500000; + do { + u32 ready_code; + + ready_code = ioread32(priv->regs + MWL8K_HIU_INT_CODE); + if (ready_code == MWL8K_FWAP_READY) { + priv->ap_fw = true; + break; + } else if (ready_code == MWL8K_FWSTA_READY) { + priv->ap_fw = false; + break; + } + + cond_resched(); + udelay(1); + } while (--loops); + + return loops ? 0 : -ETIMEDOUT; +} + + +/* DMA header used by firmware and hardware. */ +struct mwl8k_dma_data { + __le16 fwlen; + struct ieee80211_hdr wh; + char data[0]; +} __packed; + +/* Routines to add/remove DMA header from skb. */ +static inline void mwl8k_remove_dma_header(struct sk_buff *skb, __le16 qos) +{ + struct mwl8k_dma_data *tr; + int hdrlen; + + tr = (struct mwl8k_dma_data *)skb->data; + hdrlen = ieee80211_hdrlen(tr->wh.frame_control); + + if (hdrlen != sizeof(tr->wh)) { + if (ieee80211_is_data_qos(tr->wh.frame_control)) { + memmove(tr->data - hdrlen, &tr->wh, hdrlen - 2); + *((__le16 *)(tr->data - 2)) = qos; + } else { + memmove(tr->data - hdrlen, &tr->wh, hdrlen); + } + } + + if (hdrlen != sizeof(*tr)) + skb_pull(skb, sizeof(*tr) - hdrlen); +} + +#define REDUCED_TX_HEADROOM 8 + +static void +mwl8k_add_dma_header(struct mwl8k_priv *priv, struct sk_buff *skb, + int head_pad, int tail_pad) +{ + struct ieee80211_hdr *wh; + int hdrlen; + int reqd_hdrlen; + struct mwl8k_dma_data *tr; + + /* + * Add a firmware DMA header; the firmware requires that we + * present a 2-byte payload length followed by a 4-address + * header (without QoS field), followed (optionally) by any + * WEP/ExtIV header (but only filled in for CCMP). + */ + wh = (struct ieee80211_hdr *)skb->data; + + hdrlen = ieee80211_hdrlen(wh->frame_control); + + /* + * Check if skb_resize is required because of + * tx_headroom adjustment. + */ + if (priv->ap_fw && (hdrlen < (sizeof(struct ieee80211_cts) + + REDUCED_TX_HEADROOM))) { + if (pskb_expand_head(skb, REDUCED_TX_HEADROOM, 0, GFP_ATOMIC)) { + + wiphy_err(priv->hw->wiphy, + "Failed to reallocate TX buffer\n"); + return; + } + skb->truesize += REDUCED_TX_HEADROOM; + } + + reqd_hdrlen = sizeof(*tr) + head_pad; + + if (hdrlen != reqd_hdrlen) + skb_push(skb, reqd_hdrlen - hdrlen); + + if (ieee80211_is_data_qos(wh->frame_control)) + hdrlen -= IEEE80211_QOS_CTL_LEN; + + tr = (struct mwl8k_dma_data *)skb->data; + if (wh != &tr->wh) + memmove(&tr->wh, wh, hdrlen); + if (hdrlen != sizeof(tr->wh)) + memset(((void *)&tr->wh) + hdrlen, 0, sizeof(tr->wh) - hdrlen); + + /* + * Firmware length is the length of the fully formed "802.11 + * payload". That is, everything except for the 802.11 header. + * This includes all crypto material including the MIC. + */ + tr->fwlen = cpu_to_le16(skb->len - sizeof(*tr) + tail_pad); +} + +static void mwl8k_encapsulate_tx_frame(struct mwl8k_priv *priv, + struct sk_buff *skb) +{ + struct ieee80211_hdr *wh; + struct ieee80211_tx_info *tx_info; + struct ieee80211_key_conf *key_conf; + int data_pad; + int head_pad = 0; + + wh = (struct ieee80211_hdr *)skb->data; + + tx_info = IEEE80211_SKB_CB(skb); + + key_conf = NULL; + if (ieee80211_is_data(wh->frame_control)) + key_conf = tx_info->control.hw_key; + + /* + * Make sure the packet header is in the DMA header format (4-address + * without QoS), and add head & tail padding when HW crypto is enabled. + * + * We have the following trailer padding requirements: + * - WEP: 4 trailer bytes (ICV) + * - TKIP: 12 trailer bytes (8 MIC + 4 ICV) + * - CCMP: 8 trailer bytes (MIC) + */ + data_pad = 0; + if (key_conf != NULL) { + head_pad = key_conf->iv_len; + switch (key_conf->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + data_pad = 4; + break; + case WLAN_CIPHER_SUITE_TKIP: + data_pad = 12; + break; + case WLAN_CIPHER_SUITE_CCMP: + data_pad = 8; + break; + } + } + mwl8k_add_dma_header(priv, skb, head_pad, data_pad); +} + +/* + * Packet reception for 88w8366/88w8764 AP firmware. + */ +struct mwl8k_rxd_ap { + __le16 pkt_len; + __u8 sq2; + __u8 rate; + __le32 pkt_phys_addr; + __le32 next_rxd_phys_addr; + __le16 qos_control; + __le16 htsig2; + __le32 hw_rssi_info; + __le32 hw_noise_floor_info; + __u8 noise_floor; + __u8 pad0[3]; + __u8 rssi; + __u8 rx_status; + __u8 channel; + __u8 rx_ctrl; +} __packed; + +#define MWL8K_AP_RATE_INFO_MCS_FORMAT 0x80 +#define MWL8K_AP_RATE_INFO_40MHZ 0x40 +#define MWL8K_AP_RATE_INFO_RATEID(x) ((x) & 0x3f) + +#define MWL8K_AP_RX_CTRL_OWNED_BY_HOST 0x80 + +/* 8366/8764 AP rx_status bits */ +#define MWL8K_AP_RXSTAT_DECRYPT_ERR_MASK 0x80 +#define MWL8K_AP_RXSTAT_GENERAL_DECRYPT_ERR 0xFF +#define MWL8K_AP_RXSTAT_TKIP_DECRYPT_MIC_ERR 0x02 +#define MWL8K_AP_RXSTAT_WEP_DECRYPT_ICV_ERR 0x04 +#define MWL8K_AP_RXSTAT_TKIP_DECRYPT_ICV_ERR 0x08 + +static void mwl8k_rxd_ap_init(void *_rxd, dma_addr_t next_dma_addr) +{ + struct mwl8k_rxd_ap *rxd = _rxd; + + rxd->next_rxd_phys_addr = cpu_to_le32(next_dma_addr); + rxd->rx_ctrl = MWL8K_AP_RX_CTRL_OWNED_BY_HOST; +} + +static void mwl8k_rxd_ap_refill(void *_rxd, dma_addr_t addr, int len) +{ + struct mwl8k_rxd_ap *rxd = _rxd; + + rxd->pkt_len = cpu_to_le16(len); + rxd->pkt_phys_addr = cpu_to_le32(addr); + wmb(); + rxd->rx_ctrl = 0; +} + +static int +mwl8k_rxd_ap_process(void *_rxd, struct ieee80211_rx_status *status, + __le16 *qos, s8 *noise) +{ + struct mwl8k_rxd_ap *rxd = _rxd; + + if (!(rxd->rx_ctrl & MWL8K_AP_RX_CTRL_OWNED_BY_HOST)) + return -1; + rmb(); + + memset(status, 0, sizeof(*status)); + + status->signal = -rxd->rssi; + *noise = -rxd->noise_floor; + + if (rxd->rate & MWL8K_AP_RATE_INFO_MCS_FORMAT) { + status->flag |= RX_FLAG_HT; + if (rxd->rate & MWL8K_AP_RATE_INFO_40MHZ) + status->flag |= RX_FLAG_40MHZ; + status->rate_idx = MWL8K_AP_RATE_INFO_RATEID(rxd->rate); + } else { + int i; + + for (i = 0; i < ARRAY_SIZE(mwl8k_rates_24); i++) { + if (mwl8k_rates_24[i].hw_value == rxd->rate) { + status->rate_idx = i; + break; + } + } + } + + if (rxd->channel > 14) { + status->band = IEEE80211_BAND_5GHZ; + if (!(status->flag & RX_FLAG_HT)) + status->rate_idx -= 5; + } else { + status->band = IEEE80211_BAND_2GHZ; + } + status->freq = ieee80211_channel_to_frequency(rxd->channel, + status->band); + + *qos = rxd->qos_control; + + if ((rxd->rx_status != MWL8K_AP_RXSTAT_GENERAL_DECRYPT_ERR) && + (rxd->rx_status & MWL8K_AP_RXSTAT_DECRYPT_ERR_MASK) && + (rxd->rx_status & MWL8K_AP_RXSTAT_TKIP_DECRYPT_MIC_ERR)) + status->flag |= RX_FLAG_MMIC_ERROR; + + return le16_to_cpu(rxd->pkt_len); +} + +static struct rxd_ops rxd_ap_ops = { + .rxd_size = sizeof(struct mwl8k_rxd_ap), + .rxd_init = mwl8k_rxd_ap_init, + .rxd_refill = mwl8k_rxd_ap_refill, + .rxd_process = mwl8k_rxd_ap_process, +}; + +/* + * Packet reception for STA firmware. + */ +struct mwl8k_rxd_sta { + __le16 pkt_len; + __u8 link_quality; + __u8 noise_level; + __le32 pkt_phys_addr; + __le32 next_rxd_phys_addr; + __le16 qos_control; + __le16 rate_info; + __le32 pad0[4]; + __u8 rssi; + __u8 channel; + __le16 pad1; + __u8 rx_ctrl; + __u8 rx_status; + __u8 pad2[2]; +} __packed; + +#define MWL8K_STA_RATE_INFO_SHORTPRE 0x8000 +#define MWL8K_STA_RATE_INFO_ANTSELECT(x) (((x) >> 11) & 0x3) +#define MWL8K_STA_RATE_INFO_RATEID(x) (((x) >> 3) & 0x3f) +#define MWL8K_STA_RATE_INFO_40MHZ 0x0004 +#define MWL8K_STA_RATE_INFO_SHORTGI 0x0002 +#define MWL8K_STA_RATE_INFO_MCS_FORMAT 0x0001 + +#define MWL8K_STA_RX_CTRL_OWNED_BY_HOST 0x02 +#define MWL8K_STA_RX_CTRL_DECRYPT_ERROR 0x04 +/* ICV=0 or MIC=1 */ +#define MWL8K_STA_RX_CTRL_DEC_ERR_TYPE 0x08 +/* Key is uploaded only in failure case */ +#define MWL8K_STA_RX_CTRL_KEY_INDEX 0x30 + +static void mwl8k_rxd_sta_init(void *_rxd, dma_addr_t next_dma_addr) +{ + struct mwl8k_rxd_sta *rxd = _rxd; + + rxd->next_rxd_phys_addr = cpu_to_le32(next_dma_addr); + rxd->rx_ctrl = MWL8K_STA_RX_CTRL_OWNED_BY_HOST; +} + +static void mwl8k_rxd_sta_refill(void *_rxd, dma_addr_t addr, int len) +{ + struct mwl8k_rxd_sta *rxd = _rxd; + + rxd->pkt_len = cpu_to_le16(len); + rxd->pkt_phys_addr = cpu_to_le32(addr); + wmb(); + rxd->rx_ctrl = 0; +} + +static int +mwl8k_rxd_sta_process(void *_rxd, struct ieee80211_rx_status *status, + __le16 *qos, s8 *noise) +{ + struct mwl8k_rxd_sta *rxd = _rxd; + u16 rate_info; + + if (!(rxd->rx_ctrl & MWL8K_STA_RX_CTRL_OWNED_BY_HOST)) + return -1; + rmb(); + + rate_info = le16_to_cpu(rxd->rate_info); + + memset(status, 0, sizeof(*status)); + + status->signal = -rxd->rssi; + *noise = -rxd->noise_level; + status->antenna = MWL8K_STA_RATE_INFO_ANTSELECT(rate_info); + status->rate_idx = MWL8K_STA_RATE_INFO_RATEID(rate_info); + + if (rate_info & MWL8K_STA_RATE_INFO_SHORTPRE) + status->flag |= RX_FLAG_SHORTPRE; + if (rate_info & MWL8K_STA_RATE_INFO_40MHZ) + status->flag |= RX_FLAG_40MHZ; + if (rate_info & MWL8K_STA_RATE_INFO_SHORTGI) + status->flag |= RX_FLAG_SHORT_GI; + if (rate_info & MWL8K_STA_RATE_INFO_MCS_FORMAT) + status->flag |= RX_FLAG_HT; + + if (rxd->channel > 14) { + status->band = IEEE80211_BAND_5GHZ; + if (!(status->flag & RX_FLAG_HT)) + status->rate_idx -= 5; + } else { + status->band = IEEE80211_BAND_2GHZ; + } + status->freq = ieee80211_channel_to_frequency(rxd->channel, + status->band); + + *qos = rxd->qos_control; + if ((rxd->rx_ctrl & MWL8K_STA_RX_CTRL_DECRYPT_ERROR) && + (rxd->rx_ctrl & MWL8K_STA_RX_CTRL_DEC_ERR_TYPE)) + status->flag |= RX_FLAG_MMIC_ERROR; + + return le16_to_cpu(rxd->pkt_len); +} + +static struct rxd_ops rxd_sta_ops = { + .rxd_size = sizeof(struct mwl8k_rxd_sta), + .rxd_init = mwl8k_rxd_sta_init, + .rxd_refill = mwl8k_rxd_sta_refill, + .rxd_process = mwl8k_rxd_sta_process, +}; + + +#define MWL8K_RX_DESCS 256 +#define MWL8K_RX_MAXSZ 3800 + +static int mwl8k_rxq_init(struct ieee80211_hw *hw, int index) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_rx_queue *rxq = priv->rxq + index; + int size; + int i; + + rxq->rxd_count = 0; + rxq->head = 0; + rxq->tail = 0; + + size = MWL8K_RX_DESCS * priv->rxd_ops->rxd_size; + + rxq->rxd = pci_alloc_consistent(priv->pdev, size, &rxq->rxd_dma); + if (rxq->rxd == NULL) { + wiphy_err(hw->wiphy, "failed to alloc RX descriptors\n"); + return -ENOMEM; + } + memset(rxq->rxd, 0, size); + + rxq->buf = kcalloc(MWL8K_RX_DESCS, sizeof(*rxq->buf), GFP_KERNEL); + if (rxq->buf == NULL) { + pci_free_consistent(priv->pdev, size, rxq->rxd, rxq->rxd_dma); + return -ENOMEM; + } + + for (i = 0; i < MWL8K_RX_DESCS; i++) { + int desc_size; + void *rxd; + int nexti; + dma_addr_t next_dma_addr; + + desc_size = priv->rxd_ops->rxd_size; + rxd = rxq->rxd + (i * priv->rxd_ops->rxd_size); + + nexti = i + 1; + if (nexti == MWL8K_RX_DESCS) + nexti = 0; + next_dma_addr = rxq->rxd_dma + (nexti * desc_size); + + priv->rxd_ops->rxd_init(rxd, next_dma_addr); + } + + return 0; +} + +static int rxq_refill(struct ieee80211_hw *hw, int index, int limit) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_rx_queue *rxq = priv->rxq + index; + int refilled; + + refilled = 0; + while (rxq->rxd_count < MWL8K_RX_DESCS && limit--) { + struct sk_buff *skb; + dma_addr_t addr; + int rx; + void *rxd; + + skb = dev_alloc_skb(MWL8K_RX_MAXSZ); + if (skb == NULL) + break; + + addr = pci_map_single(priv->pdev, skb->data, + MWL8K_RX_MAXSZ, DMA_FROM_DEVICE); + + rxq->rxd_count++; + rx = rxq->tail++; + if (rxq->tail == MWL8K_RX_DESCS) + rxq->tail = 0; + rxq->buf[rx].skb = skb; + dma_unmap_addr_set(&rxq->buf[rx], dma, addr); + + rxd = rxq->rxd + (rx * priv->rxd_ops->rxd_size); + priv->rxd_ops->rxd_refill(rxd, addr, MWL8K_RX_MAXSZ); + + refilled++; + } + + return refilled; +} + +/* Must be called only when the card's reception is completely halted */ +static void mwl8k_rxq_deinit(struct ieee80211_hw *hw, int index) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_rx_queue *rxq = priv->rxq + index; + int i; + + if (rxq->rxd == NULL) + return; + + for (i = 0; i < MWL8K_RX_DESCS; i++) { + if (rxq->buf[i].skb != NULL) { + pci_unmap_single(priv->pdev, + dma_unmap_addr(&rxq->buf[i], dma), + MWL8K_RX_MAXSZ, PCI_DMA_FROMDEVICE); + dma_unmap_addr_set(&rxq->buf[i], dma, 0); + + kfree_skb(rxq->buf[i].skb); + rxq->buf[i].skb = NULL; + } + } + + kfree(rxq->buf); + rxq->buf = NULL; + + pci_free_consistent(priv->pdev, + MWL8K_RX_DESCS * priv->rxd_ops->rxd_size, + rxq->rxd, rxq->rxd_dma); + rxq->rxd = NULL; +} + + +/* + * Scan a list of BSSIDs to process for finalize join. + * Allows for extension to process multiple BSSIDs. + */ +static inline int +mwl8k_capture_bssid(struct mwl8k_priv *priv, struct ieee80211_hdr *wh) +{ + return priv->capture_beacon && + ieee80211_is_beacon(wh->frame_control) && + ether_addr_equal(wh->addr3, priv->capture_bssid); +} + +static inline void mwl8k_save_beacon(struct ieee80211_hw *hw, + struct sk_buff *skb) +{ + struct mwl8k_priv *priv = hw->priv; + + priv->capture_beacon = false; + memset(priv->capture_bssid, 0, ETH_ALEN); + + /* + * Use GFP_ATOMIC as rxq_process is called from + * the primary interrupt handler, memory allocation call + * must not sleep. + */ + priv->beacon_skb = skb_copy(skb, GFP_ATOMIC); + if (priv->beacon_skb != NULL) + ieee80211_queue_work(hw, &priv->finalize_join_worker); +} + +static inline struct mwl8k_vif *mwl8k_find_vif_bss(struct list_head *vif_list, + u8 *bssid) +{ + struct mwl8k_vif *mwl8k_vif; + + list_for_each_entry(mwl8k_vif, + vif_list, list) { + if (memcmp(bssid, mwl8k_vif->bssid, + ETH_ALEN) == 0) + return mwl8k_vif; + } + + return NULL; +} + +static int rxq_process(struct ieee80211_hw *hw, int index, int limit) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_vif *mwl8k_vif = NULL; + struct mwl8k_rx_queue *rxq = priv->rxq + index; + int processed; + + processed = 0; + while (rxq->rxd_count && limit--) { + struct sk_buff *skb; + void *rxd; + int pkt_len; + struct ieee80211_rx_status status; + struct ieee80211_hdr *wh; + __le16 qos; + + skb = rxq->buf[rxq->head].skb; + if (skb == NULL) + break; + + rxd = rxq->rxd + (rxq->head * priv->rxd_ops->rxd_size); + + pkt_len = priv->rxd_ops->rxd_process(rxd, &status, &qos, + &priv->noise); + if (pkt_len < 0) + break; + + rxq->buf[rxq->head].skb = NULL; + + pci_unmap_single(priv->pdev, + dma_unmap_addr(&rxq->buf[rxq->head], dma), + MWL8K_RX_MAXSZ, PCI_DMA_FROMDEVICE); + dma_unmap_addr_set(&rxq->buf[rxq->head], dma, 0); + + rxq->head++; + if (rxq->head == MWL8K_RX_DESCS) + rxq->head = 0; + + rxq->rxd_count--; + + wh = &((struct mwl8k_dma_data *)skb->data)->wh; + + /* + * Check for a pending join operation. Save a + * copy of the beacon and schedule a tasklet to + * send a FINALIZE_JOIN command to the firmware. + */ + if (mwl8k_capture_bssid(priv, (void *)skb->data)) + mwl8k_save_beacon(hw, skb); + + if (ieee80211_has_protected(wh->frame_control)) { + + /* Check if hw crypto has been enabled for + * this bss. If yes, set the status flags + * accordingly + */ + mwl8k_vif = mwl8k_find_vif_bss(&priv->vif_list, + wh->addr1); + + if (mwl8k_vif != NULL && + mwl8k_vif->is_hw_crypto_enabled) { + /* + * When MMIC ERROR is encountered + * by the firmware, payload is + * dropped and only 32 bytes of + * mwl8k Firmware header is sent + * to the host. + * + * We need to add four bytes of + * key information. In it + * MAC80211 expects keyidx set to + * 0 for triggering Counter + * Measure of MMIC failure. + */ + if (status.flag & RX_FLAG_MMIC_ERROR) { + struct mwl8k_dma_data *tr; + tr = (struct mwl8k_dma_data *)skb->data; + memset((void *)&(tr->data), 0, 4); + pkt_len += 4; + } + + if (!ieee80211_is_auth(wh->frame_control)) + status.flag |= RX_FLAG_IV_STRIPPED | + RX_FLAG_DECRYPTED | + RX_FLAG_MMIC_STRIPPED; + } + } + + skb_put(skb, pkt_len); + mwl8k_remove_dma_header(skb, qos); + memcpy(IEEE80211_SKB_RXCB(skb), &status, sizeof(status)); + ieee80211_rx_irqsafe(hw, skb); + + processed++; + } + + return processed; +} + + +/* + * Packet transmission. + */ + +#define MWL8K_TXD_STATUS_OK 0x00000001 +#define MWL8K_TXD_STATUS_OK_RETRY 0x00000002 +#define MWL8K_TXD_STATUS_OK_MORE_RETRY 0x00000004 +#define MWL8K_TXD_STATUS_MULTICAST_TX 0x00000008 +#define MWL8K_TXD_STATUS_FW_OWNED 0x80000000 + +#define MWL8K_QOS_QLEN_UNSPEC 0xff00 +#define MWL8K_QOS_ACK_POLICY_MASK 0x0060 +#define MWL8K_QOS_ACK_POLICY_NORMAL 0x0000 +#define MWL8K_QOS_ACK_POLICY_BLOCKACK 0x0060 +#define MWL8K_QOS_EOSP 0x0010 + +struct mwl8k_tx_desc { + __le32 status; + __u8 data_rate; + __u8 tx_priority; + __le16 qos_control; + __le32 pkt_phys_addr; + __le16 pkt_len; + __u8 dest_MAC_addr[ETH_ALEN]; + __le32 next_txd_phys_addr; + __le32 timestamp; + __le16 rate_info; + __u8 peer_id; + __u8 tx_frag_cnt; +} __packed; + +#define MWL8K_TX_DESCS 128 + +static int mwl8k_txq_init(struct ieee80211_hw *hw, int index) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_tx_queue *txq = priv->txq + index; + int size; + int i; + + txq->len = 0; + txq->head = 0; + txq->tail = 0; + + size = MWL8K_TX_DESCS * sizeof(struct mwl8k_tx_desc); + + txq->txd = pci_alloc_consistent(priv->pdev, size, &txq->txd_dma); + if (txq->txd == NULL) { + wiphy_err(hw->wiphy, "failed to alloc TX descriptors\n"); + return -ENOMEM; + } + memset(txq->txd, 0, size); + + txq->skb = kcalloc(MWL8K_TX_DESCS, sizeof(*txq->skb), GFP_KERNEL); + if (txq->skb == NULL) { + pci_free_consistent(priv->pdev, size, txq->txd, txq->txd_dma); + return -ENOMEM; + } + + for (i = 0; i < MWL8K_TX_DESCS; i++) { + struct mwl8k_tx_desc *tx_desc; + int nexti; + + tx_desc = txq->txd + i; + nexti = (i + 1) % MWL8K_TX_DESCS; + + tx_desc->status = 0; + tx_desc->next_txd_phys_addr = + cpu_to_le32(txq->txd_dma + nexti * sizeof(*tx_desc)); + } + + return 0; +} + +static inline void mwl8k_tx_start(struct mwl8k_priv *priv) +{ + iowrite32(MWL8K_H2A_INT_PPA_READY, + priv->regs + MWL8K_HIU_H2A_INTERRUPT_EVENTS); + iowrite32(MWL8K_H2A_INT_DUMMY, + priv->regs + MWL8K_HIU_H2A_INTERRUPT_EVENTS); + ioread32(priv->regs + MWL8K_HIU_INT_CODE); +} + +static void mwl8k_dump_tx_rings(struct ieee80211_hw *hw) +{ + struct mwl8k_priv *priv = hw->priv; + int i; + + for (i = 0; i < mwl8k_tx_queues(priv); i++) { + struct mwl8k_tx_queue *txq = priv->txq + i; + int fw_owned = 0; + int drv_owned = 0; + int unused = 0; + int desc; + + for (desc = 0; desc < MWL8K_TX_DESCS; desc++) { + struct mwl8k_tx_desc *tx_desc = txq->txd + desc; + u32 status; + + status = le32_to_cpu(tx_desc->status); + if (status & MWL8K_TXD_STATUS_FW_OWNED) + fw_owned++; + else + drv_owned++; + + if (tx_desc->pkt_len == 0) + unused++; + } + + wiphy_err(hw->wiphy, + "txq[%d] len=%d head=%d tail=%d " + "fw_owned=%d drv_owned=%d unused=%d\n", + i, + txq->len, txq->head, txq->tail, + fw_owned, drv_owned, unused); + } +} + +/* + * Must be called with priv->fw_mutex held and tx queues stopped. + */ +#define MWL8K_TX_WAIT_TIMEOUT_MS 5000 + +static int mwl8k_tx_wait_empty(struct ieee80211_hw *hw) +{ + struct mwl8k_priv *priv = hw->priv; + DECLARE_COMPLETION_ONSTACK(tx_wait); + int retry; + int rc; + + might_sleep(); + + /* Since fw restart is in progress, allow only the firmware + * commands from the restart code and block the other + * commands since they are going to fail in any case since + * the firmware has crashed + */ + if (priv->hw_restart_in_progress) { + if (priv->hw_restart_owner == current) + return 0; + else + return -EBUSY; + } + + if (atomic_read(&priv->watchdog_event_pending)) + return 0; + + /* + * The TX queues are stopped at this point, so this test + * doesn't need to take ->tx_lock. + */ + if (!priv->pending_tx_pkts) + return 0; + + retry = 0; + rc = 0; + + spin_lock_bh(&priv->tx_lock); + priv->tx_wait = &tx_wait; + while (!rc) { + int oldcount; + unsigned long timeout; + + oldcount = priv->pending_tx_pkts; + + spin_unlock_bh(&priv->tx_lock); + timeout = wait_for_completion_timeout(&tx_wait, + msecs_to_jiffies(MWL8K_TX_WAIT_TIMEOUT_MS)); + + if (atomic_read(&priv->watchdog_event_pending)) { + spin_lock_bh(&priv->tx_lock); + priv->tx_wait = NULL; + spin_unlock_bh(&priv->tx_lock); + return 0; + } + + spin_lock_bh(&priv->tx_lock); + + if (timeout) { + WARN_ON(priv->pending_tx_pkts); + if (retry) + wiphy_notice(hw->wiphy, "tx rings drained\n"); + break; + } + + if (priv->pending_tx_pkts < oldcount) { + wiphy_notice(hw->wiphy, + "waiting for tx rings to drain (%d -> %d pkts)\n", + oldcount, priv->pending_tx_pkts); + retry = 1; + continue; + } + + priv->tx_wait = NULL; + + wiphy_err(hw->wiphy, "tx rings stuck for %d ms\n", + MWL8K_TX_WAIT_TIMEOUT_MS); + mwl8k_dump_tx_rings(hw); + priv->hw_restart_in_progress = true; + ieee80211_queue_work(hw, &priv->fw_reload); + + rc = -ETIMEDOUT; + } + priv->tx_wait = NULL; + spin_unlock_bh(&priv->tx_lock); + + return rc; +} + +#define MWL8K_TXD_SUCCESS(status) \ + ((status) & (MWL8K_TXD_STATUS_OK | \ + MWL8K_TXD_STATUS_OK_RETRY | \ + MWL8K_TXD_STATUS_OK_MORE_RETRY)) + +static int mwl8k_tid_queue_mapping(u8 tid) +{ + BUG_ON(tid > 7); + + switch (tid) { + case 0: + case 3: + return IEEE80211_AC_BE; + break; + case 1: + case 2: + return IEEE80211_AC_BK; + break; + case 4: + case 5: + return IEEE80211_AC_VI; + break; + case 6: + case 7: + return IEEE80211_AC_VO; + break; + default: + return -1; + break; + } +} + +/* The firmware will fill in the rate information + * for each packet that gets queued in the hardware + * and these macros will interpret that info. + */ + +#define RI_FORMAT(a) (a & 0x0001) +#define RI_RATE_ID_MCS(a) ((a & 0x01f8) >> 3) + +static int +mwl8k_txq_reclaim(struct ieee80211_hw *hw, int index, int limit, int force) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_tx_queue *txq = priv->txq + index; + int processed; + + processed = 0; + while (txq->len > 0 && limit--) { + int tx; + struct mwl8k_tx_desc *tx_desc; + unsigned long addr; + int size; + struct sk_buff *skb; + struct ieee80211_tx_info *info; + u32 status; + struct ieee80211_sta *sta; + struct mwl8k_sta *sta_info = NULL; + u16 rate_info; + struct ieee80211_hdr *wh; + + tx = txq->head; + tx_desc = txq->txd + tx; + + status = le32_to_cpu(tx_desc->status); + + if (status & MWL8K_TXD_STATUS_FW_OWNED) { + if (!force) + break; + tx_desc->status &= + ~cpu_to_le32(MWL8K_TXD_STATUS_FW_OWNED); + } + + txq->head = (tx + 1) % MWL8K_TX_DESCS; + BUG_ON(txq->len == 0); + txq->len--; + priv->pending_tx_pkts--; + + addr = le32_to_cpu(tx_desc->pkt_phys_addr); + size = le16_to_cpu(tx_desc->pkt_len); + skb = txq->skb[tx]; + txq->skb[tx] = NULL; + + BUG_ON(skb == NULL); + pci_unmap_single(priv->pdev, addr, size, PCI_DMA_TODEVICE); + + mwl8k_remove_dma_header(skb, tx_desc->qos_control); + + wh = (struct ieee80211_hdr *) skb->data; + + /* Mark descriptor as unused */ + tx_desc->pkt_phys_addr = 0; + tx_desc->pkt_len = 0; + + info = IEEE80211_SKB_CB(skb); + if (ieee80211_is_data(wh->frame_control)) { + rcu_read_lock(); + sta = ieee80211_find_sta_by_ifaddr(hw, wh->addr1, + wh->addr2); + if (sta) { + sta_info = MWL8K_STA(sta); + BUG_ON(sta_info == NULL); + rate_info = le16_to_cpu(tx_desc->rate_info); + /* If rate is < 6.5 Mpbs for an ht station + * do not form an ampdu. If the station is a + * legacy station (format = 0), do not form an + * ampdu + */ + if (RI_RATE_ID_MCS(rate_info) < 1 || + RI_FORMAT(rate_info) == 0) { + sta_info->is_ampdu_allowed = false; + } else { + sta_info->is_ampdu_allowed = true; + } + } + rcu_read_unlock(); + } + + ieee80211_tx_info_clear_status(info); + + /* Rate control is happening in the firmware. + * Ensure no tx rate is being reported. + */ + info->status.rates[0].idx = -1; + info->status.rates[0].count = 1; + + if (MWL8K_TXD_SUCCESS(status)) + info->flags |= IEEE80211_TX_STAT_ACK; + + ieee80211_tx_status_irqsafe(hw, skb); + + processed++; + } + + return processed; +} + +/* must be called only when the card's transmit is completely halted */ +static void mwl8k_txq_deinit(struct ieee80211_hw *hw, int index) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_tx_queue *txq = priv->txq + index; + + if (txq->txd == NULL) + return; + + mwl8k_txq_reclaim(hw, index, INT_MAX, 1); + + kfree(txq->skb); + txq->skb = NULL; + + pci_free_consistent(priv->pdev, + MWL8K_TX_DESCS * sizeof(struct mwl8k_tx_desc), + txq->txd, txq->txd_dma); + txq->txd = NULL; +} + +/* caller must hold priv->stream_lock when calling the stream functions */ +static struct mwl8k_ampdu_stream * +mwl8k_add_stream(struct ieee80211_hw *hw, struct ieee80211_sta *sta, u8 tid) +{ + struct mwl8k_ampdu_stream *stream; + struct mwl8k_priv *priv = hw->priv; + int i; + + for (i = 0; i < MWL8K_NUM_AMPDU_STREAMS; i++) { + stream = &priv->ampdu[i]; + if (stream->state == AMPDU_NO_STREAM) { + stream->sta = sta; + stream->state = AMPDU_STREAM_NEW; + stream->tid = tid; + stream->idx = i; + wiphy_debug(hw->wiphy, "Added a new stream for %pM %d", + sta->addr, tid); + return stream; + } + } + return NULL; +} + +static int +mwl8k_start_stream(struct ieee80211_hw *hw, struct mwl8k_ampdu_stream *stream) +{ + int ret; + + /* if the stream has already been started, don't start it again */ + if (stream->state != AMPDU_STREAM_NEW) + return 0; + ret = ieee80211_start_tx_ba_session(stream->sta, stream->tid, 0); + if (ret) + wiphy_debug(hw->wiphy, "Failed to start stream for %pM %d: " + "%d\n", stream->sta->addr, stream->tid, ret); + else + wiphy_debug(hw->wiphy, "Started stream for %pM %d\n", + stream->sta->addr, stream->tid); + return ret; +} + +static void +mwl8k_remove_stream(struct ieee80211_hw *hw, struct mwl8k_ampdu_stream *stream) +{ + wiphy_debug(hw->wiphy, "Remove stream for %pM %d\n", stream->sta->addr, + stream->tid); + memset(stream, 0, sizeof(*stream)); +} + +static struct mwl8k_ampdu_stream * +mwl8k_lookup_stream(struct ieee80211_hw *hw, u8 *addr, u8 tid) +{ + struct mwl8k_priv *priv = hw->priv; + int i; + + for (i = 0; i < MWL8K_NUM_AMPDU_STREAMS; i++) { + struct mwl8k_ampdu_stream *stream; + stream = &priv->ampdu[i]; + if (stream->state == AMPDU_NO_STREAM) + continue; + if (!memcmp(stream->sta->addr, addr, ETH_ALEN) && + stream->tid == tid) + return stream; + } + return NULL; +} + +#define MWL8K_AMPDU_PACKET_THRESHOLD 64 +static inline bool mwl8k_ampdu_allowed(struct ieee80211_sta *sta, u8 tid) +{ + struct mwl8k_sta *sta_info = MWL8K_STA(sta); + struct tx_traffic_info *tx_stats; + + BUG_ON(tid >= MWL8K_MAX_TID); + tx_stats = &sta_info->tx_stats[tid]; + + return sta_info->is_ampdu_allowed && + tx_stats->pkts > MWL8K_AMPDU_PACKET_THRESHOLD; +} + +static inline void mwl8k_tx_count_packet(struct ieee80211_sta *sta, u8 tid) +{ + struct mwl8k_sta *sta_info = MWL8K_STA(sta); + struct tx_traffic_info *tx_stats; + + BUG_ON(tid >= MWL8K_MAX_TID); + tx_stats = &sta_info->tx_stats[tid]; + + if (tx_stats->start_time == 0) + tx_stats->start_time = jiffies; + + /* reset the packet count after each second elapses. If the number of + * packets ever exceeds the ampdu_min_traffic threshold, we will allow + * an ampdu stream to be started. + */ + if (jiffies - tx_stats->start_time > HZ) { + tx_stats->pkts = 0; + tx_stats->start_time = 0; + } else + tx_stats->pkts++; +} + +/* The hardware ampdu queues start from 5. + * txpriorities for ampdu queues are + * 5 6 7 0 1 2 3 4 ie., queue 5 is highest + * and queue 3 is lowest (queue 4 is reserved) + */ +#define BA_QUEUE 5 + +static void +mwl8k_txq_xmit(struct ieee80211_hw *hw, + int index, + struct ieee80211_sta *sta, + struct sk_buff *skb) +{ + struct mwl8k_priv *priv = hw->priv; + struct ieee80211_tx_info *tx_info; + struct mwl8k_vif *mwl8k_vif; + struct ieee80211_hdr *wh; + struct mwl8k_tx_queue *txq; + struct mwl8k_tx_desc *tx; + dma_addr_t dma; + u32 txstatus; + u8 txdatarate; + u16 qos; + int txpriority; + u8 tid = 0; + struct mwl8k_ampdu_stream *stream = NULL; + bool start_ba_session = false; + bool mgmtframe = false; + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data; + bool eapol_frame = false; + + wh = (struct ieee80211_hdr *)skb->data; + if (ieee80211_is_data_qos(wh->frame_control)) + qos = le16_to_cpu(*((__le16 *)ieee80211_get_qos_ctl(wh))); + else + qos = 0; + + if (skb->protocol == cpu_to_be16(ETH_P_PAE)) + eapol_frame = true; + + if (ieee80211_is_mgmt(wh->frame_control)) + mgmtframe = true; + + if (priv->ap_fw) + mwl8k_encapsulate_tx_frame(priv, skb); + else + mwl8k_add_dma_header(priv, skb, 0, 0); + + wh = &((struct mwl8k_dma_data *)skb->data)->wh; + + tx_info = IEEE80211_SKB_CB(skb); + mwl8k_vif = MWL8K_VIF(tx_info->control.vif); + + if (tx_info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) { + wh->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); + wh->seq_ctrl |= cpu_to_le16(mwl8k_vif->seqno); + mwl8k_vif->seqno += 0x10; + } + + /* Setup firmware control bit fields for each frame type. */ + txstatus = 0; + txdatarate = 0; + if (ieee80211_is_mgmt(wh->frame_control) || + ieee80211_is_ctl(wh->frame_control)) { + txdatarate = 0; + qos |= MWL8K_QOS_QLEN_UNSPEC | MWL8K_QOS_EOSP; + } else if (ieee80211_is_data(wh->frame_control)) { + txdatarate = 1; + if (is_multicast_ether_addr(wh->addr1)) + txstatus |= MWL8K_TXD_STATUS_MULTICAST_TX; + + qos &= ~MWL8K_QOS_ACK_POLICY_MASK; + if (tx_info->flags & IEEE80211_TX_CTL_AMPDU) + qos |= MWL8K_QOS_ACK_POLICY_BLOCKACK; + else + qos |= MWL8K_QOS_ACK_POLICY_NORMAL; + } + + /* Queue ADDBA request in the respective data queue. While setting up + * the ampdu stream, mac80211 queues further packets for that + * particular ra/tid pair. However, packets piled up in the hardware + * for that ra/tid pair will still go out. ADDBA request and the + * related data packets going out from different queues asynchronously + * will cause a shift in the receiver window which might result in + * ampdu packets getting dropped at the receiver after the stream has + * been setup. + */ + if (unlikely(ieee80211_is_action(wh->frame_control) && + mgmt->u.action.category == WLAN_CATEGORY_BACK && + mgmt->u.action.u.addba_req.action_code == WLAN_ACTION_ADDBA_REQ && + priv->ap_fw)) { + u16 capab = le16_to_cpu(mgmt->u.action.u.addba_req.capab); + tid = (capab & IEEE80211_ADDBA_PARAM_TID_MASK) >> 2; + index = mwl8k_tid_queue_mapping(tid); + } + + txpriority = index; + + if (priv->ap_fw && sta && sta->ht_cap.ht_supported && !eapol_frame && + ieee80211_is_data_qos(wh->frame_control)) { + tid = qos & 0xf; + mwl8k_tx_count_packet(sta, tid); + spin_lock(&priv->stream_lock); + stream = mwl8k_lookup_stream(hw, sta->addr, tid); + if (stream != NULL) { + if (stream->state == AMPDU_STREAM_ACTIVE) { + WARN_ON(!(qos & MWL8K_QOS_ACK_POLICY_BLOCKACK)); + txpriority = (BA_QUEUE + stream->idx) % + TOTAL_HW_TX_QUEUES; + if (stream->idx <= 1) + index = stream->idx + + MWL8K_TX_WMM_QUEUES; + + } else if (stream->state == AMPDU_STREAM_NEW) { + /* We get here if the driver sends us packets + * after we've initiated a stream, but before + * our ampdu_action routine has been called + * with IEEE80211_AMPDU_TX_START to get the SSN + * for the ADDBA request. So this packet can + * go out with no risk of sequence number + * mismatch. No special handling is required. + */ + } else { + /* Drop packets that would go out after the + * ADDBA request was sent but before the ADDBA + * response is received. If we don't do this, + * the recipient would probably receive it + * after the ADDBA request with SSN 0. This + * will cause the recipient's BA receive window + * to shift, which would cause the subsequent + * packets in the BA stream to be discarded. + * mac80211 queues our packets for us in this + * case, so this is really just a safety check. + */ + wiphy_warn(hw->wiphy, + "Cannot send packet while ADDBA " + "dialog is underway.\n"); + spin_unlock(&priv->stream_lock); + dev_kfree_skb(skb); + return; + } + } else { + /* Defer calling mwl8k_start_stream so that the current + * skb can go out before the ADDBA request. This + * prevents sequence number mismatch at the recepient + * as described above. + */ + if (mwl8k_ampdu_allowed(sta, tid)) { + stream = mwl8k_add_stream(hw, sta, tid); + if (stream != NULL) + start_ba_session = true; + } + } + spin_unlock(&priv->stream_lock); + } else { + qos &= ~MWL8K_QOS_ACK_POLICY_MASK; + qos |= MWL8K_QOS_ACK_POLICY_NORMAL; + } + + dma = pci_map_single(priv->pdev, skb->data, + skb->len, PCI_DMA_TODEVICE); + + if (pci_dma_mapping_error(priv->pdev, dma)) { + wiphy_debug(hw->wiphy, + "failed to dma map skb, dropping TX frame.\n"); + if (start_ba_session) { + spin_lock(&priv->stream_lock); + mwl8k_remove_stream(hw, stream); + spin_unlock(&priv->stream_lock); + } + dev_kfree_skb(skb); + return; + } + + spin_lock_bh(&priv->tx_lock); + + txq = priv->txq + index; + + /* Mgmt frames that go out frequently are probe + * responses. Other mgmt frames got out relatively + * infrequently. Hence reserve 2 buffers so that + * other mgmt frames do not get dropped due to an + * already queued probe response in one of the + * reserved buffers. + */ + + if (txq->len >= MWL8K_TX_DESCS - 2) { + if (!mgmtframe || txq->len == MWL8K_TX_DESCS) { + if (start_ba_session) { + spin_lock(&priv->stream_lock); + mwl8k_remove_stream(hw, stream); + spin_unlock(&priv->stream_lock); + } + spin_unlock_bh(&priv->tx_lock); + pci_unmap_single(priv->pdev, dma, skb->len, + PCI_DMA_TODEVICE); + dev_kfree_skb(skb); + return; + } + } + + BUG_ON(txq->skb[txq->tail] != NULL); + txq->skb[txq->tail] = skb; + + tx = txq->txd + txq->tail; + tx->data_rate = txdatarate; + tx->tx_priority = txpriority; + tx->qos_control = cpu_to_le16(qos); + tx->pkt_phys_addr = cpu_to_le32(dma); + tx->pkt_len = cpu_to_le16(skb->len); + tx->rate_info = 0; + if (!priv->ap_fw && sta != NULL) + tx->peer_id = MWL8K_STA(sta)->peer_id; + else + tx->peer_id = 0; + + if (priv->ap_fw && ieee80211_is_data(wh->frame_control) && !eapol_frame) + tx->timestamp = cpu_to_le32(ioread32(priv->regs + + MWL8K_HW_TIMER_REGISTER)); + else + tx->timestamp = 0; + + wmb(); + tx->status = cpu_to_le32(MWL8K_TXD_STATUS_FW_OWNED | txstatus); + + txq->len++; + priv->pending_tx_pkts++; + + txq->tail++; + if (txq->tail == MWL8K_TX_DESCS) + txq->tail = 0; + + mwl8k_tx_start(priv); + + spin_unlock_bh(&priv->tx_lock); + + /* Initiate the ampdu session here */ + if (start_ba_session) { + spin_lock(&priv->stream_lock); + if (mwl8k_start_stream(hw, stream)) + mwl8k_remove_stream(hw, stream); + spin_unlock(&priv->stream_lock); + } +} + + +/* + * Firmware access. + * + * We have the following requirements for issuing firmware commands: + * - Some commands require that the packet transmit path is idle when + * the command is issued. (For simplicity, we'll just quiesce the + * transmit path for every command.) + * - There are certain sequences of commands that need to be issued to + * the hardware sequentially, with no other intervening commands. + * + * This leads to an implementation of a "firmware lock" as a mutex that + * can be taken recursively, and which is taken by both the low-level + * command submission function (mwl8k_post_cmd) as well as any users of + * that function that require issuing of an atomic sequence of commands, + * and quiesces the transmit path whenever it's taken. + */ +static int mwl8k_fw_lock(struct ieee80211_hw *hw) +{ + struct mwl8k_priv *priv = hw->priv; + + if (priv->fw_mutex_owner != current) { + int rc; + + mutex_lock(&priv->fw_mutex); + ieee80211_stop_queues(hw); + + rc = mwl8k_tx_wait_empty(hw); + if (rc) { + if (!priv->hw_restart_in_progress) + ieee80211_wake_queues(hw); + + mutex_unlock(&priv->fw_mutex); + + return rc; + } + + priv->fw_mutex_owner = current; + } + + priv->fw_mutex_depth++; + + return 0; +} + +static void mwl8k_fw_unlock(struct ieee80211_hw *hw) +{ + struct mwl8k_priv *priv = hw->priv; + + if (!--priv->fw_mutex_depth) { + if (!priv->hw_restart_in_progress) + ieee80211_wake_queues(hw); + + priv->fw_mutex_owner = NULL; + mutex_unlock(&priv->fw_mutex); + } +} + +static void mwl8k_enable_bsses(struct ieee80211_hw *hw, bool enable, + u32 bitmap); + +/* + * Command processing. + */ + +/* Timeout firmware commands after 10s */ +#define MWL8K_CMD_TIMEOUT_MS 10000 + +static int mwl8k_post_cmd(struct ieee80211_hw *hw, struct mwl8k_cmd_pkt *cmd) +{ + DECLARE_COMPLETION_ONSTACK(cmd_wait); + struct mwl8k_priv *priv = hw->priv; + void __iomem *regs = priv->regs; + dma_addr_t dma_addr; + unsigned int dma_size; + int rc; + unsigned long timeout = 0; + u8 buf[32]; + u32 bitmap = 0; + + wiphy_dbg(hw->wiphy, "Posting %s [%d]\n", + mwl8k_cmd_name(cmd->code, buf, sizeof(buf)), cmd->macid); + + /* Before posting firmware commands that could change the hardware + * characteristics, make sure that all BSSes are stopped temporary. + * Enable these stopped BSSes after completion of the commands + */ + + rc = mwl8k_fw_lock(hw); + if (rc) + return rc; + + if (priv->ap_fw && priv->running_bsses) { + switch (le16_to_cpu(cmd->code)) { + case MWL8K_CMD_SET_RF_CHANNEL: + case MWL8K_CMD_RADIO_CONTROL: + case MWL8K_CMD_RF_TX_POWER: + case MWL8K_CMD_TX_POWER: + case MWL8K_CMD_RF_ANTENNA: + case MWL8K_CMD_RTS_THRESHOLD: + case MWL8K_CMD_MIMO_CONFIG: + bitmap = priv->running_bsses; + mwl8k_enable_bsses(hw, false, bitmap); + break; + } + } + + cmd->result = (__force __le16) 0xffff; + dma_size = le16_to_cpu(cmd->length); + dma_addr = pci_map_single(priv->pdev, cmd, dma_size, + PCI_DMA_BIDIRECTIONAL); + if (pci_dma_mapping_error(priv->pdev, dma_addr)) + return -ENOMEM; + + priv->hostcmd_wait = &cmd_wait; + iowrite32(dma_addr, regs + MWL8K_HIU_GEN_PTR); + iowrite32(MWL8K_H2A_INT_DOORBELL, + regs + MWL8K_HIU_H2A_INTERRUPT_EVENTS); + iowrite32(MWL8K_H2A_INT_DUMMY, + regs + MWL8K_HIU_H2A_INTERRUPT_EVENTS); + + timeout = wait_for_completion_timeout(&cmd_wait, + msecs_to_jiffies(MWL8K_CMD_TIMEOUT_MS)); + + priv->hostcmd_wait = NULL; + + + pci_unmap_single(priv->pdev, dma_addr, dma_size, + PCI_DMA_BIDIRECTIONAL); + + if (!timeout) { + wiphy_err(hw->wiphy, "Command %s timeout after %u ms\n", + mwl8k_cmd_name(cmd->code, buf, sizeof(buf)), + MWL8K_CMD_TIMEOUT_MS); + rc = -ETIMEDOUT; + } else { + int ms; + + ms = MWL8K_CMD_TIMEOUT_MS - jiffies_to_msecs(timeout); + + rc = cmd->result ? -EINVAL : 0; + if (rc) + wiphy_err(hw->wiphy, "Command %s error 0x%x\n", + mwl8k_cmd_name(cmd->code, buf, sizeof(buf)), + le16_to_cpu(cmd->result)); + else if (ms > 2000) + wiphy_notice(hw->wiphy, "Command %s took %d ms\n", + mwl8k_cmd_name(cmd->code, + buf, sizeof(buf)), + ms); + } + + if (bitmap) + mwl8k_enable_bsses(hw, true, bitmap); + + mwl8k_fw_unlock(hw); + + return rc; +} + +static int mwl8k_post_pervif_cmd(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct mwl8k_cmd_pkt *cmd) +{ + if (vif != NULL) + cmd->macid = MWL8K_VIF(vif)->macid; + return mwl8k_post_cmd(hw, cmd); +} + +/* + * Setup code shared between STA and AP firmware images. + */ +static void mwl8k_setup_2ghz_band(struct ieee80211_hw *hw) +{ + struct mwl8k_priv *priv = hw->priv; + + BUILD_BUG_ON(sizeof(priv->channels_24) != sizeof(mwl8k_channels_24)); + memcpy(priv->channels_24, mwl8k_channels_24, sizeof(mwl8k_channels_24)); + + BUILD_BUG_ON(sizeof(priv->rates_24) != sizeof(mwl8k_rates_24)); + memcpy(priv->rates_24, mwl8k_rates_24, sizeof(mwl8k_rates_24)); + + priv->band_24.band = IEEE80211_BAND_2GHZ; + priv->band_24.channels = priv->channels_24; + priv->band_24.n_channels = ARRAY_SIZE(mwl8k_channels_24); + priv->band_24.bitrates = priv->rates_24; + priv->band_24.n_bitrates = ARRAY_SIZE(mwl8k_rates_24); + + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &priv->band_24; +} + +static void mwl8k_setup_5ghz_band(struct ieee80211_hw *hw) +{ + struct mwl8k_priv *priv = hw->priv; + + BUILD_BUG_ON(sizeof(priv->channels_50) != sizeof(mwl8k_channels_50)); + memcpy(priv->channels_50, mwl8k_channels_50, sizeof(mwl8k_channels_50)); + + BUILD_BUG_ON(sizeof(priv->rates_50) != sizeof(mwl8k_rates_50)); + memcpy(priv->rates_50, mwl8k_rates_50, sizeof(mwl8k_rates_50)); + + priv->band_50.band = IEEE80211_BAND_5GHZ; + priv->band_50.channels = priv->channels_50; + priv->band_50.n_channels = ARRAY_SIZE(mwl8k_channels_50); + priv->band_50.bitrates = priv->rates_50; + priv->band_50.n_bitrates = ARRAY_SIZE(mwl8k_rates_50); + + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = &priv->band_50; +} + +/* + * CMD_GET_HW_SPEC (STA version). + */ +struct mwl8k_cmd_get_hw_spec_sta { + struct mwl8k_cmd_pkt header; + __u8 hw_rev; + __u8 host_interface; + __le16 num_mcaddrs; + __u8 perm_addr[ETH_ALEN]; + __le16 region_code; + __le32 fw_rev; + __le32 ps_cookie; + __le32 caps; + __u8 mcs_bitmap[16]; + __le32 rx_queue_ptr; + __le32 num_tx_queues; + __le32 tx_queue_ptrs[MWL8K_TX_WMM_QUEUES]; + __le32 caps2; + __le32 num_tx_desc_per_queue; + __le32 total_rxd; +} __packed; + +#define MWL8K_CAP_MAX_AMSDU 0x20000000 +#define MWL8K_CAP_GREENFIELD 0x08000000 +#define MWL8K_CAP_AMPDU 0x04000000 +#define MWL8K_CAP_RX_STBC 0x01000000 +#define MWL8K_CAP_TX_STBC 0x00800000 +#define MWL8K_CAP_SHORTGI_40MHZ 0x00400000 +#define MWL8K_CAP_SHORTGI_20MHZ 0x00200000 +#define MWL8K_CAP_RX_ANTENNA_MASK 0x000e0000 +#define MWL8K_CAP_TX_ANTENNA_MASK 0x0001c000 +#define MWL8K_CAP_DELAY_BA 0x00003000 +#define MWL8K_CAP_MIMO 0x00000200 +#define MWL8K_CAP_40MHZ 0x00000100 +#define MWL8K_CAP_BAND_MASK 0x00000007 +#define MWL8K_CAP_5GHZ 0x00000004 +#define MWL8K_CAP_2GHZ4 0x00000001 + +static void +mwl8k_set_ht_caps(struct ieee80211_hw *hw, + struct ieee80211_supported_band *band, u32 cap) +{ + int rx_streams; + int tx_streams; + + band->ht_cap.ht_supported = 1; + + if (cap & MWL8K_CAP_MAX_AMSDU) + band->ht_cap.cap |= IEEE80211_HT_CAP_MAX_AMSDU; + if (cap & MWL8K_CAP_GREENFIELD) + band->ht_cap.cap |= IEEE80211_HT_CAP_GRN_FLD; + if (cap & MWL8K_CAP_AMPDU) { + hw->flags |= IEEE80211_HW_AMPDU_AGGREGATION; + band->ht_cap.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + band->ht_cap.ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE; + } + if (cap & MWL8K_CAP_RX_STBC) + band->ht_cap.cap |= IEEE80211_HT_CAP_RX_STBC; + if (cap & MWL8K_CAP_TX_STBC) + band->ht_cap.cap |= IEEE80211_HT_CAP_TX_STBC; + if (cap & MWL8K_CAP_SHORTGI_40MHZ) + band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40; + if (cap & MWL8K_CAP_SHORTGI_20MHZ) + band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_20; + if (cap & MWL8K_CAP_DELAY_BA) + band->ht_cap.cap |= IEEE80211_HT_CAP_DELAY_BA; + if (cap & MWL8K_CAP_40MHZ) + band->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; + + rx_streams = hweight32(cap & MWL8K_CAP_RX_ANTENNA_MASK); + tx_streams = hweight32(cap & MWL8K_CAP_TX_ANTENNA_MASK); + + band->ht_cap.mcs.rx_mask[0] = 0xff; + if (rx_streams >= 2) + band->ht_cap.mcs.rx_mask[1] = 0xff; + if (rx_streams >= 3) + band->ht_cap.mcs.rx_mask[2] = 0xff; + band->ht_cap.mcs.rx_mask[4] = 0x01; + band->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; + + if (rx_streams != tx_streams) { + band->ht_cap.mcs.tx_params |= IEEE80211_HT_MCS_TX_RX_DIFF; + band->ht_cap.mcs.tx_params |= (tx_streams - 1) << + IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT; + } +} + +static void +mwl8k_set_caps(struct ieee80211_hw *hw, u32 caps) +{ + struct mwl8k_priv *priv = hw->priv; + + if (priv->caps) + return; + + if ((caps & MWL8K_CAP_2GHZ4) || !(caps & MWL8K_CAP_BAND_MASK)) { + mwl8k_setup_2ghz_band(hw); + if (caps & MWL8K_CAP_MIMO) + mwl8k_set_ht_caps(hw, &priv->band_24, caps); + } + + if (caps & MWL8K_CAP_5GHZ) { + mwl8k_setup_5ghz_band(hw); + if (caps & MWL8K_CAP_MIMO) + mwl8k_set_ht_caps(hw, &priv->band_50, caps); + } + + priv->caps = caps; +} + +static int mwl8k_cmd_get_hw_spec_sta(struct ieee80211_hw *hw) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_cmd_get_hw_spec_sta *cmd; + int rc; + int i; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_GET_HW_SPEC); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + + memset(cmd->perm_addr, 0xff, sizeof(cmd->perm_addr)); + cmd->ps_cookie = cpu_to_le32(priv->cookie_dma); + cmd->rx_queue_ptr = cpu_to_le32(priv->rxq[0].rxd_dma); + cmd->num_tx_queues = cpu_to_le32(mwl8k_tx_queues(priv)); + for (i = 0; i < mwl8k_tx_queues(priv); i++) + cmd->tx_queue_ptrs[i] = cpu_to_le32(priv->txq[i].txd_dma); + cmd->num_tx_desc_per_queue = cpu_to_le32(MWL8K_TX_DESCS); + cmd->total_rxd = cpu_to_le32(MWL8K_RX_DESCS); + + rc = mwl8k_post_cmd(hw, &cmd->header); + + if (!rc) { + SET_IEEE80211_PERM_ADDR(hw, cmd->perm_addr); + priv->num_mcaddrs = le16_to_cpu(cmd->num_mcaddrs); + priv->fw_rev = le32_to_cpu(cmd->fw_rev); + priv->hw_rev = cmd->hw_rev; + mwl8k_set_caps(hw, le32_to_cpu(cmd->caps)); + priv->ap_macids_supported = 0x00000000; + priv->sta_macids_supported = 0x00000001; + } + + kfree(cmd); + return rc; +} + +/* + * CMD_GET_HW_SPEC (AP version). + */ +struct mwl8k_cmd_get_hw_spec_ap { + struct mwl8k_cmd_pkt header; + __u8 hw_rev; + __u8 host_interface; + __le16 num_wcb; + __le16 num_mcaddrs; + __u8 perm_addr[ETH_ALEN]; + __le16 region_code; + __le16 num_antenna; + __le32 fw_rev; + __le32 wcbbase0; + __le32 rxwrptr; + __le32 rxrdptr; + __le32 ps_cookie; + __le32 wcbbase1; + __le32 wcbbase2; + __le32 wcbbase3; + __le32 fw_api_version; + __le32 caps; + __le32 num_of_ampdu_queues; + __le32 wcbbase_ampdu[MWL8K_MAX_AMPDU_QUEUES]; +} __packed; + +static int mwl8k_cmd_get_hw_spec_ap(struct ieee80211_hw *hw) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_cmd_get_hw_spec_ap *cmd; + int rc, i; + u32 api_version; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_GET_HW_SPEC); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + + memset(cmd->perm_addr, 0xff, sizeof(cmd->perm_addr)); + cmd->ps_cookie = cpu_to_le32(priv->cookie_dma); + + rc = mwl8k_post_cmd(hw, &cmd->header); + + if (!rc) { + int off; + + api_version = le32_to_cpu(cmd->fw_api_version); + if (priv->device_info->fw_api_ap != api_version) { + printk(KERN_ERR "%s: Unsupported fw API version for %s." + " Expected %d got %d.\n", MWL8K_NAME, + priv->device_info->part_name, + priv->device_info->fw_api_ap, + api_version); + rc = -EINVAL; + goto done; + } + SET_IEEE80211_PERM_ADDR(hw, cmd->perm_addr); + priv->num_mcaddrs = le16_to_cpu(cmd->num_mcaddrs); + priv->fw_rev = le32_to_cpu(cmd->fw_rev); + priv->hw_rev = cmd->hw_rev; + mwl8k_set_caps(hw, le32_to_cpu(cmd->caps)); + priv->ap_macids_supported = 0x000000ff; + priv->sta_macids_supported = 0x00000100; + priv->num_ampdu_queues = le32_to_cpu(cmd->num_of_ampdu_queues); + if (priv->num_ampdu_queues > MWL8K_MAX_AMPDU_QUEUES) { + wiphy_warn(hw->wiphy, "fw reported %d ampdu queues" + " but we only support %d.\n", + priv->num_ampdu_queues, + MWL8K_MAX_AMPDU_QUEUES); + priv->num_ampdu_queues = MWL8K_MAX_AMPDU_QUEUES; + } + off = le32_to_cpu(cmd->rxwrptr) & 0xffff; + iowrite32(priv->rxq[0].rxd_dma, priv->sram + off); + + off = le32_to_cpu(cmd->rxrdptr) & 0xffff; + iowrite32(priv->rxq[0].rxd_dma, priv->sram + off); + + priv->txq_offset[0] = le32_to_cpu(cmd->wcbbase0) & 0xffff; + priv->txq_offset[1] = le32_to_cpu(cmd->wcbbase1) & 0xffff; + priv->txq_offset[2] = le32_to_cpu(cmd->wcbbase2) & 0xffff; + priv->txq_offset[3] = le32_to_cpu(cmd->wcbbase3) & 0xffff; + + for (i = 0; i < priv->num_ampdu_queues; i++) + priv->txq_offset[i + MWL8K_TX_WMM_QUEUES] = + le32_to_cpu(cmd->wcbbase_ampdu[i]) & 0xffff; + } + +done: + kfree(cmd); + return rc; +} + +/* + * CMD_SET_HW_SPEC. + */ +struct mwl8k_cmd_set_hw_spec { + struct mwl8k_cmd_pkt header; + __u8 hw_rev; + __u8 host_interface; + __le16 num_mcaddrs; + __u8 perm_addr[ETH_ALEN]; + __le16 region_code; + __le32 fw_rev; + __le32 ps_cookie; + __le32 caps; + __le32 rx_queue_ptr; + __le32 num_tx_queues; + __le32 tx_queue_ptrs[MWL8K_MAX_TX_QUEUES]; + __le32 flags; + __le32 num_tx_desc_per_queue; + __le32 total_rxd; +} __packed; + +/* If enabled, MWL8K_SET_HW_SPEC_FLAG_ENABLE_LIFE_TIME_EXPIRY will cause + * packets to expire 500 ms after the timestamp in the tx descriptor. That is, + * the packets that are queued for more than 500ms, will be dropped in the + * hardware. This helps minimizing the issues caused due to head-of-line + * blocking where a slow client can hog the bandwidth and affect traffic to a + * faster client. + */ +#define MWL8K_SET_HW_SPEC_FLAG_ENABLE_LIFE_TIME_EXPIRY 0x00000400 +#define MWL8K_SET_HW_SPEC_FLAG_GENERATE_CCMP_HDR 0x00000200 +#define MWL8K_SET_HW_SPEC_FLAG_HOST_DECR_MGMT 0x00000080 +#define MWL8K_SET_HW_SPEC_FLAG_HOSTFORM_PROBERESP 0x00000020 +#define MWL8K_SET_HW_SPEC_FLAG_HOSTFORM_BEACON 0x00000010 + +static int mwl8k_cmd_set_hw_spec(struct ieee80211_hw *hw) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_cmd_set_hw_spec *cmd; + int rc; + int i; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_HW_SPEC); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + + cmd->ps_cookie = cpu_to_le32(priv->cookie_dma); + cmd->rx_queue_ptr = cpu_to_le32(priv->rxq[0].rxd_dma); + cmd->num_tx_queues = cpu_to_le32(mwl8k_tx_queues(priv)); + + /* + * Mac80211 stack has Q0 as highest priority and Q3 as lowest in + * that order. Firmware has Q3 as highest priority and Q0 as lowest + * in that order. Map Q3 of mac80211 to Q0 of firmware so that the + * priority is interpreted the right way in firmware. + */ + for (i = 0; i < mwl8k_tx_queues(priv); i++) { + int j = mwl8k_tx_queues(priv) - 1 - i; + cmd->tx_queue_ptrs[i] = cpu_to_le32(priv->txq[j].txd_dma); + } + + cmd->flags = cpu_to_le32(MWL8K_SET_HW_SPEC_FLAG_HOST_DECR_MGMT | + MWL8K_SET_HW_SPEC_FLAG_HOSTFORM_PROBERESP | + MWL8K_SET_HW_SPEC_FLAG_HOSTFORM_BEACON | + MWL8K_SET_HW_SPEC_FLAG_ENABLE_LIFE_TIME_EXPIRY | + MWL8K_SET_HW_SPEC_FLAG_GENERATE_CCMP_HDR); + cmd->num_tx_desc_per_queue = cpu_to_le32(MWL8K_TX_DESCS); + cmd->total_rxd = cpu_to_le32(MWL8K_RX_DESCS); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_MAC_MULTICAST_ADR. + */ +struct mwl8k_cmd_mac_multicast_adr { + struct mwl8k_cmd_pkt header; + __le16 action; + __le16 numaddr; + __u8 addr[0][ETH_ALEN]; +}; + +#define MWL8K_ENABLE_RX_DIRECTED 0x0001 +#define MWL8K_ENABLE_RX_MULTICAST 0x0002 +#define MWL8K_ENABLE_RX_ALL_MULTICAST 0x0004 +#define MWL8K_ENABLE_RX_BROADCAST 0x0008 + +static struct mwl8k_cmd_pkt * +__mwl8k_cmd_mac_multicast_adr(struct ieee80211_hw *hw, int allmulti, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + struct netdev_hw_addr_list *mc_list) +#else + int mc_count, struct dev_addr_list *ha) +#endif +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_cmd_mac_multicast_adr *cmd; + int size; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + int mc_count = 0; + + if (mc_list) + mc_count = netdev_hw_addr_list_count(mc_list); +#endif + + if (allmulti || mc_count > priv->num_mcaddrs) { + allmulti = 1; + mc_count = 0; + } + + size = sizeof(*cmd) + mc_count * ETH_ALEN; + + cmd = kzalloc(size, GFP_ATOMIC); + if (cmd == NULL) + return NULL; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_MAC_MULTICAST_ADR); + cmd->header.length = cpu_to_le16(size); + cmd->action = cpu_to_le16(MWL8K_ENABLE_RX_DIRECTED | + MWL8K_ENABLE_RX_BROADCAST); + + if (allmulti) { + cmd->action |= cpu_to_le16(MWL8K_ENABLE_RX_ALL_MULTICAST); + } else if (mc_count) { +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + struct netdev_hw_addr *ha; + int i = 0; +#else + int i; +#endif + + cmd->action |= cpu_to_le16(MWL8K_ENABLE_RX_MULTICAST); + cmd->numaddr = cpu_to_le16(mc_count); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + netdev_hw_addr_list_for_each(ha, mc_list) { + memcpy(cmd->addr[i], ha->addr, ETH_ALEN); +#else + for (i = 0; i < mc_count && ha; i++) { + if (ha->da_addrlen != ETH_ALEN) { + kfree(cmd); + return NULL; + } + memcpy(cmd->addr[i], ha->da_addr, ETH_ALEN); + ha = ha->next; +#endif + } + } + + return &cmd->header; +} + +/* + * CMD_GET_STAT. + */ +struct mwl8k_cmd_get_stat { + struct mwl8k_cmd_pkt header; + __le32 stats[64]; +} __packed; + +#define MWL8K_STAT_ACK_FAILURE 9 +#define MWL8K_STAT_RTS_FAILURE 12 +#define MWL8K_STAT_FCS_ERROR 24 +#define MWL8K_STAT_RTS_SUCCESS 11 + +static int mwl8k_cmd_get_stat(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + struct mwl8k_cmd_get_stat *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_GET_STAT); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + + rc = mwl8k_post_cmd(hw, &cmd->header); + if (!rc) { + stats->dot11ACKFailureCount = + le32_to_cpu(cmd->stats[MWL8K_STAT_ACK_FAILURE]); + stats->dot11RTSFailureCount = + le32_to_cpu(cmd->stats[MWL8K_STAT_RTS_FAILURE]); + stats->dot11FCSErrorCount = + le32_to_cpu(cmd->stats[MWL8K_STAT_FCS_ERROR]); + stats->dot11RTSSuccessCount = + le32_to_cpu(cmd->stats[MWL8K_STAT_RTS_SUCCESS]); + } + kfree(cmd); + + return rc; +} + +/* + * CMD_RADIO_CONTROL. + */ +struct mwl8k_cmd_radio_control { + struct mwl8k_cmd_pkt header; + __le16 action; + __le16 control; + __le16 radio_on; +} __packed; + +static int +mwl8k_cmd_radio_control(struct ieee80211_hw *hw, bool enable, bool force) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_cmd_radio_control *cmd; + int rc; + + if (enable == priv->radio_on && !force) + return 0; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_RADIO_CONTROL); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le16(MWL8K_CMD_SET); + cmd->control = cpu_to_le16(priv->radio_short_preamble ? 3 : 1); + cmd->radio_on = cpu_to_le16(enable ? 0x0001 : 0x0000); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + if (!rc) + priv->radio_on = enable; + + return rc; +} + +static int mwl8k_cmd_radio_disable(struct ieee80211_hw *hw) +{ + return mwl8k_cmd_radio_control(hw, 0, 0); +} + +static int mwl8k_cmd_radio_enable(struct ieee80211_hw *hw) +{ + return mwl8k_cmd_radio_control(hw, 1, 0); +} + +static int +mwl8k_set_radio_preamble(struct ieee80211_hw *hw, bool short_preamble) +{ + struct mwl8k_priv *priv = hw->priv; + + priv->radio_short_preamble = short_preamble; + + return mwl8k_cmd_radio_control(hw, 1, 1); +} + +/* + * CMD_RF_TX_POWER. + */ +#define MWL8K_RF_TX_POWER_LEVEL_TOTAL 8 + +struct mwl8k_cmd_rf_tx_power { + struct mwl8k_cmd_pkt header; + __le16 action; + __le16 support_level; + __le16 current_level; + __le16 reserved; + __le16 power_level_list[MWL8K_RF_TX_POWER_LEVEL_TOTAL]; +} __packed; + +static int mwl8k_cmd_rf_tx_power(struct ieee80211_hw *hw, int dBm) +{ + struct mwl8k_cmd_rf_tx_power *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_RF_TX_POWER); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le16(MWL8K_CMD_SET); + cmd->support_level = cpu_to_le16(dBm); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_TX_POWER. + */ +#define MWL8K_TX_POWER_LEVEL_TOTAL 12 + +struct mwl8k_cmd_tx_power { + struct mwl8k_cmd_pkt header; + __le16 action; + __le16 band; + __le16 channel; + __le16 bw; + __le16 sub_ch; + __le16 power_level_list[MWL8K_TX_POWER_LEVEL_TOTAL]; +} __packed; + +static int mwl8k_cmd_tx_power(struct ieee80211_hw *hw, + struct ieee80211_conf *conf, + unsigned short pwr) +{ + struct ieee80211_channel *channel = conf->chandef.chan; + enum nl80211_channel_type channel_type = + cfg80211_get_chandef_type(&conf->chandef); + struct mwl8k_cmd_tx_power *cmd; + int rc; + int i; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_TX_POWER); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le16(MWL8K_CMD_SET_LIST); + + if (channel->band == IEEE80211_BAND_2GHZ) + cmd->band = cpu_to_le16(0x1); + else if (channel->band == IEEE80211_BAND_5GHZ) + cmd->band = cpu_to_le16(0x4); + + cmd->channel = cpu_to_le16(channel->hw_value); + + if (channel_type == NL80211_CHAN_NO_HT || + channel_type == NL80211_CHAN_HT20) { + cmd->bw = cpu_to_le16(0x2); + } else { + cmd->bw = cpu_to_le16(0x4); + if (channel_type == NL80211_CHAN_HT40MINUS) + cmd->sub_ch = cpu_to_le16(0x3); + else if (channel_type == NL80211_CHAN_HT40PLUS) + cmd->sub_ch = cpu_to_le16(0x1); + } + + for (i = 0; i < MWL8K_TX_POWER_LEVEL_TOTAL; i++) + cmd->power_level_list[i] = cpu_to_le16(pwr); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_RF_ANTENNA. + */ +struct mwl8k_cmd_rf_antenna { + struct mwl8k_cmd_pkt header; + __le16 antenna; + __le16 mode; +} __packed; + +#define MWL8K_RF_ANTENNA_RX 1 +#define MWL8K_RF_ANTENNA_TX 2 + +static int +mwl8k_cmd_rf_antenna(struct ieee80211_hw *hw, int antenna, int mask) +{ + struct mwl8k_cmd_rf_antenna *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_RF_ANTENNA); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->antenna = cpu_to_le16(antenna); + cmd->mode = cpu_to_le16(mask); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_SET_BEACON. + */ +struct mwl8k_cmd_set_beacon { + struct mwl8k_cmd_pkt header; + __le16 beacon_len; + __u8 beacon[0]; +}; + +static int mwl8k_cmd_set_beacon(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *beacon, int len) +{ + struct mwl8k_cmd_set_beacon *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd) + len, GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_BEACON); + cmd->header.length = cpu_to_le16(sizeof(*cmd) + len); + cmd->beacon_len = cpu_to_le16(len); + memcpy(cmd->beacon, beacon, len); + + rc = mwl8k_post_pervif_cmd(hw, vif, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_SET_PRE_SCAN. + */ +struct mwl8k_cmd_set_pre_scan { + struct mwl8k_cmd_pkt header; +} __packed; + +static int mwl8k_cmd_set_pre_scan(struct ieee80211_hw *hw) +{ + struct mwl8k_cmd_set_pre_scan *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_PRE_SCAN); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_SET_POST_SCAN. + */ +struct mwl8k_cmd_set_post_scan { + struct mwl8k_cmd_pkt header; + __le32 isibss; + __u8 bssid[ETH_ALEN]; +} __packed; + +static int +mwl8k_cmd_set_post_scan(struct ieee80211_hw *hw, const __u8 *mac) +{ + struct mwl8k_cmd_set_post_scan *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_POST_SCAN); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->isibss = 0; + memcpy(cmd->bssid, mac, ETH_ALEN); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_SET_RF_CHANNEL. + */ +struct mwl8k_cmd_set_rf_channel { + struct mwl8k_cmd_pkt header; + __le16 action; + __u8 current_channel; + __le32 channel_flags; +} __packed; + +static int mwl8k_cmd_set_rf_channel(struct ieee80211_hw *hw, + struct ieee80211_conf *conf) +{ + struct ieee80211_channel *channel = conf->chandef.chan; + enum nl80211_channel_type channel_type = + cfg80211_get_chandef_type(&conf->chandef); + struct mwl8k_cmd_set_rf_channel *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_RF_CHANNEL); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le16(MWL8K_CMD_SET); + cmd->current_channel = channel->hw_value; + + if (channel->band == IEEE80211_BAND_2GHZ) + cmd->channel_flags |= cpu_to_le32(0x00000001); + else if (channel->band == IEEE80211_BAND_5GHZ) + cmd->channel_flags |= cpu_to_le32(0x00000004); + + if (channel_type == NL80211_CHAN_NO_HT || + channel_type == NL80211_CHAN_HT20) + cmd->channel_flags |= cpu_to_le32(0x00000080); + else if (channel_type == NL80211_CHAN_HT40MINUS) + cmd->channel_flags |= cpu_to_le32(0x000001900); + else if (channel_type == NL80211_CHAN_HT40PLUS) + cmd->channel_flags |= cpu_to_le32(0x000000900); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_SET_AID. + */ +#define MWL8K_FRAME_PROT_DISABLED 0x00 +#define MWL8K_FRAME_PROT_11G 0x07 +#define MWL8K_FRAME_PROT_11N_HT_40MHZ_ONLY 0x02 +#define MWL8K_FRAME_PROT_11N_HT_ALL 0x06 + +struct mwl8k_cmd_update_set_aid { + struct mwl8k_cmd_pkt header; + __le16 aid; + + /* AP's MAC address (BSSID) */ + __u8 bssid[ETH_ALEN]; + __le16 protection_mode; + __u8 supp_rates[14]; +} __packed; + +static void legacy_rate_mask_to_array(u8 *rates, u32 mask) +{ + int i; + int j; + + /* + * Clear nonstandard rate 4. + */ + mask &= 0x1fef; + + for (i = 0, j = 0; i < 13; i++) { + if (mask & (1 << i)) + rates[j++] = mwl8k_rates_24[i].hw_value; + } +} + +static int +mwl8k_cmd_set_aid(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u32 legacy_rate_mask) +{ + struct mwl8k_cmd_update_set_aid *cmd; + u16 prot_mode; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_AID); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->aid = cpu_to_le16(vif->bss_conf.aid); + memcpy(cmd->bssid, vif->bss_conf.bssid, ETH_ALEN); + + if (vif->bss_conf.use_cts_prot) { + prot_mode = MWL8K_FRAME_PROT_11G; + } else { + switch (vif->bss_conf.ht_operation_mode & + IEEE80211_HT_OP_MODE_PROTECTION) { + case IEEE80211_HT_OP_MODE_PROTECTION_20MHZ: + prot_mode = MWL8K_FRAME_PROT_11N_HT_40MHZ_ONLY; + break; + case IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED: + prot_mode = MWL8K_FRAME_PROT_11N_HT_ALL; + break; + default: + prot_mode = MWL8K_FRAME_PROT_DISABLED; + break; + } + } + cmd->protection_mode = cpu_to_le16(prot_mode); + + legacy_rate_mask_to_array(cmd->supp_rates, legacy_rate_mask); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_SET_RATE. + */ +struct mwl8k_cmd_set_rate { + struct mwl8k_cmd_pkt header; + __u8 legacy_rates[14]; + + /* Bitmap for supported MCS codes. */ + __u8 mcs_set[16]; + __u8 reserved[16]; +} __packed; + +static int +mwl8k_cmd_set_rate(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + u32 legacy_rate_mask, u8 *mcs_rates) +{ + struct mwl8k_cmd_set_rate *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_RATE); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + legacy_rate_mask_to_array(cmd->legacy_rates, legacy_rate_mask); + memcpy(cmd->mcs_set, mcs_rates, 16); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_FINALIZE_JOIN. + */ +#define MWL8K_FJ_BEACON_MAXLEN 128 + +struct mwl8k_cmd_finalize_join { + struct mwl8k_cmd_pkt header; + __le32 sleep_interval; /* Number of beacon periods to sleep */ + __u8 beacon_data[MWL8K_FJ_BEACON_MAXLEN]; +} __packed; + +static int mwl8k_cmd_finalize_join(struct ieee80211_hw *hw, void *frame, + int framelen, int dtim) +{ + struct mwl8k_cmd_finalize_join *cmd; + struct ieee80211_mgmt *payload = frame; + int payload_len; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_FINALIZE_JOIN); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->sleep_interval = cpu_to_le32(dtim ? dtim : 1); + + payload_len = framelen - ieee80211_hdrlen(payload->frame_control); + if (payload_len < 0) + payload_len = 0; + else if (payload_len > MWL8K_FJ_BEACON_MAXLEN) + payload_len = MWL8K_FJ_BEACON_MAXLEN; + + memcpy(cmd->beacon_data, &payload->u.beacon, payload_len); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_SET_RTS_THRESHOLD. + */ +struct mwl8k_cmd_set_rts_threshold { + struct mwl8k_cmd_pkt header; + __le16 action; + __le16 threshold; +} __packed; + +static int +mwl8k_cmd_set_rts_threshold(struct ieee80211_hw *hw, int rts_thresh) +{ + struct mwl8k_cmd_set_rts_threshold *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_RTS_THRESHOLD); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le16(MWL8K_CMD_SET); + cmd->threshold = cpu_to_le16(rts_thresh); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_SET_SLOT. + */ +struct mwl8k_cmd_set_slot { + struct mwl8k_cmd_pkt header; + __le16 action; + __u8 short_slot; +} __packed; + +static int mwl8k_cmd_set_slot(struct ieee80211_hw *hw, bool short_slot_time) +{ + struct mwl8k_cmd_set_slot *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_SLOT); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le16(MWL8K_CMD_SET); + cmd->short_slot = short_slot_time; + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_SET_EDCA_PARAMS. + */ +struct mwl8k_cmd_set_edca_params { + struct mwl8k_cmd_pkt header; + + /* See MWL8K_SET_EDCA_XXX below */ + __le16 action; + + /* TX opportunity in units of 32 us */ + __le16 txop; + + union { + struct { + /* Log exponent of max contention period: 0...15 */ + __le32 log_cw_max; + + /* Log exponent of min contention period: 0...15 */ + __le32 log_cw_min; + + /* Adaptive interframe spacing in units of 32us */ + __u8 aifs; + + /* TX queue to configure */ + __u8 txq; + } ap; + struct { + /* Log exponent of max contention period: 0...15 */ + __u8 log_cw_max; + + /* Log exponent of min contention period: 0...15 */ + __u8 log_cw_min; + + /* Adaptive interframe spacing in units of 32us */ + __u8 aifs; + + /* TX queue to configure */ + __u8 txq; + } sta; + }; +} __packed; + +#define MWL8K_SET_EDCA_CW 0x01 +#define MWL8K_SET_EDCA_TXOP 0x02 +#define MWL8K_SET_EDCA_AIFS 0x04 + +#define MWL8K_SET_EDCA_ALL (MWL8K_SET_EDCA_CW | \ + MWL8K_SET_EDCA_TXOP | \ + MWL8K_SET_EDCA_AIFS) + +static int +mwl8k_cmd_set_edca_params(struct ieee80211_hw *hw, __u8 qnum, + __u16 cw_min, __u16 cw_max, + __u8 aifs, __u16 txop) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_cmd_set_edca_params *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_EDCA_PARAMS); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le16(MWL8K_SET_EDCA_ALL); + cmd->txop = cpu_to_le16(txop); + if (priv->ap_fw) { + cmd->ap.log_cw_max = cpu_to_le32(ilog2(cw_max + 1)); + cmd->ap.log_cw_min = cpu_to_le32(ilog2(cw_min + 1)); + cmd->ap.aifs = aifs; + cmd->ap.txq = qnum; + } else { + cmd->sta.log_cw_max = (u8)ilog2(cw_max + 1); + cmd->sta.log_cw_min = (u8)ilog2(cw_min + 1); + cmd->sta.aifs = aifs; + cmd->sta.txq = qnum; + } + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_SET_WMM_MODE. + */ +struct mwl8k_cmd_set_wmm_mode { + struct mwl8k_cmd_pkt header; + __le16 action; +} __packed; + +static int mwl8k_cmd_set_wmm_mode(struct ieee80211_hw *hw, bool enable) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_cmd_set_wmm_mode *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_WMM_MODE); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le16(!!enable); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + if (!rc) + priv->wmm_enabled = enable; + + return rc; +} + +/* + * CMD_MIMO_CONFIG. + */ +struct mwl8k_cmd_mimo_config { + struct mwl8k_cmd_pkt header; + __le32 action; + __u8 rx_antenna_map; + __u8 tx_antenna_map; +} __packed; + +static int mwl8k_cmd_mimo_config(struct ieee80211_hw *hw, __u8 rx, __u8 tx) +{ + struct mwl8k_cmd_mimo_config *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_MIMO_CONFIG); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le32((u32)MWL8K_CMD_SET); + cmd->rx_antenna_map = rx; + cmd->tx_antenna_map = tx; + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_USE_FIXED_RATE (STA version). + */ +struct mwl8k_cmd_use_fixed_rate_sta { + struct mwl8k_cmd_pkt header; + __le32 action; + __le32 allow_rate_drop; + __le32 num_rates; + struct { + __le32 is_ht_rate; + __le32 enable_retry; + __le32 rate; + __le32 retry_count; + } rate_entry[8]; + __le32 rate_type; + __le32 reserved1; + __le32 reserved2; +} __packed; + +#define MWL8K_USE_AUTO_RATE 0x0002 +#define MWL8K_UCAST_RATE 0 + +static int mwl8k_cmd_use_fixed_rate_sta(struct ieee80211_hw *hw) +{ + struct mwl8k_cmd_use_fixed_rate_sta *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_USE_FIXED_RATE); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le32(MWL8K_USE_AUTO_RATE); + cmd->rate_type = cpu_to_le32(MWL8K_UCAST_RATE); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_USE_FIXED_RATE (AP version). + */ +struct mwl8k_cmd_use_fixed_rate_ap { + struct mwl8k_cmd_pkt header; + __le32 action; + __le32 allow_rate_drop; + __le32 num_rates; + struct mwl8k_rate_entry_ap { + __le32 is_ht_rate; + __le32 enable_retry; + __le32 rate; + __le32 retry_count; + } rate_entry[4]; + u8 multicast_rate; + u8 multicast_rate_type; + u8 management_rate; +} __packed; + +static int +mwl8k_cmd_use_fixed_rate_ap(struct ieee80211_hw *hw, int mcast, int mgmt) +{ + struct mwl8k_cmd_use_fixed_rate_ap *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_USE_FIXED_RATE); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le32(MWL8K_USE_AUTO_RATE); + cmd->multicast_rate = mcast; + cmd->management_rate = mgmt; + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_ENABLE_SNIFFER. + */ +struct mwl8k_cmd_enable_sniffer { + struct mwl8k_cmd_pkt header; + __le32 action; +} __packed; + +static int mwl8k_cmd_enable_sniffer(struct ieee80211_hw *hw, bool enable) +{ + struct mwl8k_cmd_enable_sniffer *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_ENABLE_SNIFFER); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le32(!!enable); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +struct mwl8k_cmd_update_mac_addr { + struct mwl8k_cmd_pkt header; + union { + struct { + __le16 mac_type; + __u8 mac_addr[ETH_ALEN]; + } mbss; + __u8 mac_addr[ETH_ALEN]; + }; +} __packed; + +#define MWL8K_MAC_TYPE_PRIMARY_CLIENT 0 +#define MWL8K_MAC_TYPE_SECONDARY_CLIENT 1 +#define MWL8K_MAC_TYPE_PRIMARY_AP 2 +#define MWL8K_MAC_TYPE_SECONDARY_AP 3 + +static int mwl8k_cmd_update_mac_addr(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *mac, bool set) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_vif *mwl8k_vif = MWL8K_VIF(vif); + struct mwl8k_cmd_update_mac_addr *cmd; + int mac_type; + int rc; + + mac_type = MWL8K_MAC_TYPE_PRIMARY_AP; + if (vif != NULL && vif->type == NL80211_IFTYPE_STATION) { + if (mwl8k_vif->macid + 1 == ffs(priv->sta_macids_supported)) + if (priv->ap_fw) + mac_type = MWL8K_MAC_TYPE_SECONDARY_CLIENT; + else + mac_type = MWL8K_MAC_TYPE_PRIMARY_CLIENT; + else + mac_type = MWL8K_MAC_TYPE_SECONDARY_CLIENT; + } else if (vif != NULL && vif->type == NL80211_IFTYPE_AP) { + if (mwl8k_vif->macid + 1 == ffs(priv->ap_macids_supported)) + mac_type = MWL8K_MAC_TYPE_PRIMARY_AP; + else + mac_type = MWL8K_MAC_TYPE_SECONDARY_AP; + } + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + if (set) + cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_MAC_ADDR); + else + cmd->header.code = cpu_to_le16(MWL8K_CMD_DEL_MAC_ADDR); + + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + if (priv->ap_fw) { + cmd->mbss.mac_type = cpu_to_le16(mac_type); + memcpy(cmd->mbss.mac_addr, mac, ETH_ALEN); + } else { + memcpy(cmd->mac_addr, mac, ETH_ALEN); + } + + rc = mwl8k_post_pervif_cmd(hw, vif, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * MWL8K_CMD_SET_MAC_ADDR. + */ +static inline int mwl8k_cmd_set_mac_addr(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *mac) +{ + return mwl8k_cmd_update_mac_addr(hw, vif, mac, true); +} + +/* + * MWL8K_CMD_DEL_MAC_ADDR. + */ +static inline int mwl8k_cmd_del_mac_addr(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *mac) +{ + return mwl8k_cmd_update_mac_addr(hw, vif, mac, false); +} + +/* + * CMD_SET_RATEADAPT_MODE. + */ +struct mwl8k_cmd_set_rate_adapt_mode { + struct mwl8k_cmd_pkt header; + __le16 action; + __le16 mode; +} __packed; + +static int mwl8k_cmd_set_rateadapt_mode(struct ieee80211_hw *hw, __u16 mode) +{ + struct mwl8k_cmd_set_rate_adapt_mode *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_RATEADAPT_MODE); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le16(MWL8K_CMD_SET); + cmd->mode = cpu_to_le16(mode); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_GET_WATCHDOG_BITMAP. + */ +struct mwl8k_cmd_get_watchdog_bitmap { + struct mwl8k_cmd_pkt header; + u8 bitmap; +} __packed; + +static int mwl8k_cmd_get_watchdog_bitmap(struct ieee80211_hw *hw, u8 *bitmap) +{ + struct mwl8k_cmd_get_watchdog_bitmap *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_GET_WATCHDOG_BITMAP); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + + rc = mwl8k_post_cmd(hw, &cmd->header); + if (!rc) + *bitmap = cmd->bitmap; + + kfree(cmd); + + return rc; +} + +#define MWL8K_WMM_QUEUE_NUMBER 3 + +static void mwl8k_destroy_ba(struct ieee80211_hw *hw, + u8 idx); + +static void mwl8k_watchdog_ba_events(struct work_struct *work) +{ + int rc; + u8 bitmap = 0, stream_index; + struct mwl8k_ampdu_stream *streams; + struct mwl8k_priv *priv = + container_of(work, struct mwl8k_priv, watchdog_ba_handle); + struct ieee80211_hw *hw = priv->hw; + int i; + u32 status = 0; + + mwl8k_fw_lock(hw); + + rc = mwl8k_cmd_get_watchdog_bitmap(priv->hw, &bitmap); + if (rc) + goto done; + + spin_lock(&priv->stream_lock); + + /* the bitmap is the hw queue number. Map it to the ampdu queue. */ + for (i = 0; i < TOTAL_HW_TX_QUEUES; i++) { + if (bitmap & (1 << i)) { + stream_index = (i + MWL8K_WMM_QUEUE_NUMBER) % + TOTAL_HW_TX_QUEUES; + streams = &priv->ampdu[stream_index]; + if (streams->state == AMPDU_STREAM_ACTIVE) { + ieee80211_stop_tx_ba_session(streams->sta, + streams->tid); + spin_unlock(&priv->stream_lock); + mwl8k_destroy_ba(hw, stream_index); + spin_lock(&priv->stream_lock); + } + } + } + + spin_unlock(&priv->stream_lock); +done: + atomic_dec(&priv->watchdog_event_pending); + status = ioread32(priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS_MASK); + iowrite32((status | MWL8K_A2H_INT_BA_WATCHDOG), + priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS_MASK); + mwl8k_fw_unlock(hw); + return; +} + + +/* + * CMD_BSS_START. + */ +struct mwl8k_cmd_bss_start { + struct mwl8k_cmd_pkt header; + __le32 enable; +} __packed; + +static int mwl8k_cmd_bss_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, int enable) +{ + struct mwl8k_cmd_bss_start *cmd; + struct mwl8k_vif *mwl8k_vif = MWL8K_VIF(vif); + struct mwl8k_priv *priv = hw->priv; + int rc; + + if (enable && (priv->running_bsses & (1 << mwl8k_vif->macid))) + return 0; + + if (!enable && !(priv->running_bsses & (1 << mwl8k_vif->macid))) + return 0; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_BSS_START); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->enable = cpu_to_le32(enable); + + rc = mwl8k_post_pervif_cmd(hw, vif, &cmd->header); + kfree(cmd); + + if (!rc) { + if (enable) + priv->running_bsses |= (1 << mwl8k_vif->macid); + else + priv->running_bsses &= ~(1 << mwl8k_vif->macid); + } + return rc; +} + +static void mwl8k_enable_bsses(struct ieee80211_hw *hw, bool enable, u32 bitmap) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_vif *mwl8k_vif, *tmp_vif; + struct ieee80211_vif *vif; + + list_for_each_entry_safe(mwl8k_vif, tmp_vif, &priv->vif_list, list) { + vif = mwl8k_vif->vif; + + if (!(bitmap & (1 << mwl8k_vif->macid))) + continue; + + if (vif->type == NL80211_IFTYPE_AP) + mwl8k_cmd_bss_start(hw, vif, enable); + } +} +/* + * CMD_BASTREAM. + */ + +/* + * UPSTREAM is tx direction + */ +#define BASTREAM_FLAG_DIRECTION_UPSTREAM 0x00 +#define BASTREAM_FLAG_IMMEDIATE_TYPE 0x01 + +enum ba_stream_action_type { + MWL8K_BA_CREATE, + MWL8K_BA_UPDATE, + MWL8K_BA_DESTROY, + MWL8K_BA_FLUSH, + MWL8K_BA_CHECK, +}; + + +struct mwl8k_create_ba_stream { + __le32 flags; + __le32 idle_thrs; + __le32 bar_thrs; + __le32 window_size; + u8 peer_mac_addr[6]; + u8 dialog_token; + u8 tid; + u8 queue_id; + u8 param_info; + __le32 ba_context; + u8 reset_seq_no_flag; + __le16 curr_seq_no; + u8 sta_src_mac_addr[6]; +} __packed; + +struct mwl8k_destroy_ba_stream { + __le32 flags; + __le32 ba_context; +} __packed; + +struct mwl8k_cmd_bastream { + struct mwl8k_cmd_pkt header; + __le32 action; + union { + struct mwl8k_create_ba_stream create_params; + struct mwl8k_destroy_ba_stream destroy_params; + }; +} __packed; + +static int +mwl8k_check_ba(struct ieee80211_hw *hw, struct mwl8k_ampdu_stream *stream, + struct ieee80211_vif *vif) +{ + struct mwl8k_cmd_bastream *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_BASTREAM); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + + cmd->action = cpu_to_le32(MWL8K_BA_CHECK); + + cmd->create_params.queue_id = stream->idx; + memcpy(&cmd->create_params.peer_mac_addr[0], stream->sta->addr, + ETH_ALEN); + cmd->create_params.tid = stream->tid; + + cmd->create_params.flags = + cpu_to_le32(BASTREAM_FLAG_IMMEDIATE_TYPE) | + cpu_to_le32(BASTREAM_FLAG_DIRECTION_UPSTREAM); + + rc = mwl8k_post_pervif_cmd(hw, vif, &cmd->header); + + kfree(cmd); + + return rc; +} + +static int +mwl8k_create_ba(struct ieee80211_hw *hw, struct mwl8k_ampdu_stream *stream, + u8 buf_size, struct ieee80211_vif *vif) +{ + struct mwl8k_cmd_bastream *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + + cmd->header.code = cpu_to_le16(MWL8K_CMD_BASTREAM); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + + cmd->action = cpu_to_le32(MWL8K_BA_CREATE); + + cmd->create_params.bar_thrs = cpu_to_le32((u32)buf_size); + cmd->create_params.window_size = cpu_to_le32((u32)buf_size); + cmd->create_params.queue_id = stream->idx; + + memcpy(cmd->create_params.peer_mac_addr, stream->sta->addr, ETH_ALEN); + cmd->create_params.tid = stream->tid; + cmd->create_params.curr_seq_no = cpu_to_le16(0); + cmd->create_params.reset_seq_no_flag = 1; + + cmd->create_params.param_info = + (stream->sta->ht_cap.ampdu_factor & + IEEE80211_HT_AMPDU_PARM_FACTOR) | + ((stream->sta->ht_cap.ampdu_density << 2) & + IEEE80211_HT_AMPDU_PARM_DENSITY); + + cmd->create_params.flags = + cpu_to_le32(BASTREAM_FLAG_IMMEDIATE_TYPE | + BASTREAM_FLAG_DIRECTION_UPSTREAM); + + rc = mwl8k_post_pervif_cmd(hw, vif, &cmd->header); + + wiphy_debug(hw->wiphy, "Created a BA stream for %pM : tid %d\n", + stream->sta->addr, stream->tid); + kfree(cmd); + + return rc; +} + +static void mwl8k_destroy_ba(struct ieee80211_hw *hw, + u8 idx) +{ + struct mwl8k_cmd_bastream *cmd; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_BASTREAM); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le32(MWL8K_BA_DESTROY); + + cmd->destroy_params.ba_context = cpu_to_le32(idx); + mwl8k_post_cmd(hw, &cmd->header); + + wiphy_debug(hw->wiphy, "Deleted BA stream index %d\n", idx); + + kfree(cmd); +} + +/* + * CMD_SET_NEW_STN. + */ +struct mwl8k_cmd_set_new_stn { + struct mwl8k_cmd_pkt header; + __le16 aid; + __u8 mac_addr[6]; + __le16 stn_id; + __le16 action; + __le16 rsvd; + __le32 legacy_rates; + __u8 ht_rates[4]; + __le16 cap_info; + __le16 ht_capabilities_info; + __u8 mac_ht_param_info; + __u8 rev; + __u8 control_channel; + __u8 add_channel; + __le16 op_mode; + __le16 stbc; + __u8 add_qos_info; + __u8 is_qos_sta; + __le32 fw_sta_ptr; +} __packed; + +#define MWL8K_STA_ACTION_ADD 0 +#define MWL8K_STA_ACTION_REMOVE 2 + +static int mwl8k_cmd_set_new_stn_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mwl8k_cmd_set_new_stn *cmd; + u32 rates; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_NEW_STN); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->aid = cpu_to_le16(sta->aid); + memcpy(cmd->mac_addr, sta->addr, ETH_ALEN); + cmd->stn_id = cpu_to_le16(sta->aid); + cmd->action = cpu_to_le16(MWL8K_STA_ACTION_ADD); + if (hw->conf.chandef.chan->band == IEEE80211_BAND_2GHZ) + rates = sta->supp_rates[IEEE80211_BAND_2GHZ]; + else + rates = sta->supp_rates[IEEE80211_BAND_5GHZ] << 5; + cmd->legacy_rates = cpu_to_le32(rates); + if (sta->ht_cap.ht_supported) { + cmd->ht_rates[0] = sta->ht_cap.mcs.rx_mask[0]; + cmd->ht_rates[1] = sta->ht_cap.mcs.rx_mask[1]; + cmd->ht_rates[2] = sta->ht_cap.mcs.rx_mask[2]; + cmd->ht_rates[3] = sta->ht_cap.mcs.rx_mask[3]; + cmd->ht_capabilities_info = cpu_to_le16(sta->ht_cap.cap); + cmd->mac_ht_param_info = (sta->ht_cap.ampdu_factor & 3) | + ((sta->ht_cap.ampdu_density & 7) << 2); + cmd->is_qos_sta = 1; + } + + rc = mwl8k_post_pervif_cmd(hw, vif, &cmd->header); + kfree(cmd); + + return rc; +} + +static int mwl8k_cmd_set_new_stn_add_self(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mwl8k_cmd_set_new_stn *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_NEW_STN); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + memcpy(cmd->mac_addr, vif->addr, ETH_ALEN); + + rc = mwl8k_post_pervif_cmd(hw, vif, &cmd->header); + kfree(cmd); + + return rc; +} + +static int mwl8k_cmd_set_new_stn_del(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr) +{ + struct mwl8k_cmd_set_new_stn *cmd; + struct mwl8k_priv *priv = hw->priv; + int rc, i; + u8 idx; + + spin_lock(&priv->stream_lock); + /* Destroy any active ampdu streams for this sta */ + for (i = 0; i < MWL8K_NUM_AMPDU_STREAMS; i++) { + struct mwl8k_ampdu_stream *s; + s = &priv->ampdu[i]; + if (s->state != AMPDU_NO_STREAM) { + if (memcmp(s->sta->addr, addr, ETH_ALEN) == 0) { + if (s->state == AMPDU_STREAM_ACTIVE) { + idx = s->idx; + spin_unlock(&priv->stream_lock); + mwl8k_destroy_ba(hw, idx); + spin_lock(&priv->stream_lock); + } else if (s->state == AMPDU_STREAM_NEW) { + mwl8k_remove_stream(hw, s); + } + } + } + } + + spin_unlock(&priv->stream_lock); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_SET_NEW_STN); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + memcpy(cmd->mac_addr, addr, ETH_ALEN); + cmd->action = cpu_to_le16(MWL8K_STA_ACTION_REMOVE); + + rc = mwl8k_post_pervif_cmd(hw, vif, &cmd->header); + kfree(cmd); + + return rc; +} + +/* + * CMD_UPDATE_ENCRYPTION. + */ + +#define MAX_ENCR_KEY_LENGTH 16 +#define MIC_KEY_LENGTH 8 + +struct mwl8k_cmd_update_encryption { + struct mwl8k_cmd_pkt header; + + __le32 action; + __le32 reserved; + __u8 mac_addr[6]; + __u8 encr_type; + +} __packed; + +struct mwl8k_cmd_set_key { + struct mwl8k_cmd_pkt header; + + __le32 action; + __le32 reserved; + __le16 length; + __le16 key_type_id; + __le32 key_info; + __le32 key_id; + __le16 key_len; + __u8 key_material[MAX_ENCR_KEY_LENGTH]; + __u8 tkip_tx_mic_key[MIC_KEY_LENGTH]; + __u8 tkip_rx_mic_key[MIC_KEY_LENGTH]; + __le16 tkip_rsc_low; + __le32 tkip_rsc_high; + __le16 tkip_tsc_low; + __le32 tkip_tsc_high; + __u8 mac_addr[6]; +} __packed; + +enum { + MWL8K_ENCR_ENABLE, + MWL8K_ENCR_SET_KEY, + MWL8K_ENCR_REMOVE_KEY, + MWL8K_ENCR_SET_GROUP_KEY, +}; + +#define MWL8K_UPDATE_ENCRYPTION_TYPE_WEP 0 +#define MWL8K_UPDATE_ENCRYPTION_TYPE_DISABLE 1 +#define MWL8K_UPDATE_ENCRYPTION_TYPE_TKIP 4 +#define MWL8K_UPDATE_ENCRYPTION_TYPE_MIXED 7 +#define MWL8K_UPDATE_ENCRYPTION_TYPE_AES 8 + +enum { + MWL8K_ALG_WEP, + MWL8K_ALG_TKIP, + MWL8K_ALG_CCMP, +}; + +#define MWL8K_KEY_FLAG_TXGROUPKEY 0x00000004 +#define MWL8K_KEY_FLAG_PAIRWISE 0x00000008 +#define MWL8K_KEY_FLAG_TSC_VALID 0x00000040 +#define MWL8K_KEY_FLAG_WEP_TXKEY 0x01000000 +#define MWL8K_KEY_FLAG_MICKEY_VALID 0x02000000 + +static int mwl8k_cmd_update_encryption_enable(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u8 *addr, + u8 encr_type) +{ + struct mwl8k_cmd_update_encryption *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_UPDATE_ENCRYPTION); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le32(MWL8K_ENCR_ENABLE); + memcpy(cmd->mac_addr, addr, ETH_ALEN); + cmd->encr_type = encr_type; + + rc = mwl8k_post_pervif_cmd(hw, vif, &cmd->header); + kfree(cmd); + + return rc; +} + +static int mwl8k_encryption_set_cmd_info(struct mwl8k_cmd_set_key *cmd, + u8 *addr, + struct ieee80211_key_conf *key) +{ + cmd->header.code = cpu_to_le16(MWL8K_CMD_UPDATE_ENCRYPTION); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->length = cpu_to_le16(sizeof(*cmd) - + offsetof(struct mwl8k_cmd_set_key, length)); + cmd->key_id = cpu_to_le32(key->keyidx); + cmd->key_len = cpu_to_le16(key->keylen); + memcpy(cmd->mac_addr, addr, ETH_ALEN); + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + cmd->key_type_id = cpu_to_le16(MWL8K_ALG_WEP); + if (key->keyidx == 0) + cmd->key_info = cpu_to_le32(MWL8K_KEY_FLAG_WEP_TXKEY); + + break; + case WLAN_CIPHER_SUITE_TKIP: + cmd->key_type_id = cpu_to_le16(MWL8K_ALG_TKIP); + cmd->key_info = (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) + ? cpu_to_le32(MWL8K_KEY_FLAG_PAIRWISE) + : cpu_to_le32(MWL8K_KEY_FLAG_TXGROUPKEY); + cmd->key_info |= cpu_to_le32(MWL8K_KEY_FLAG_MICKEY_VALID + | MWL8K_KEY_FLAG_TSC_VALID); + break; + case WLAN_CIPHER_SUITE_CCMP: + cmd->key_type_id = cpu_to_le16(MWL8K_ALG_CCMP); + cmd->key_info = (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) + ? cpu_to_le32(MWL8K_KEY_FLAG_PAIRWISE) + : cpu_to_le32(MWL8K_KEY_FLAG_TXGROUPKEY); + break; + default: + return -ENOTSUPP; + } + + return 0; +} + +static int mwl8k_cmd_encryption_set_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u8 *addr, + struct ieee80211_key_conf *key) +{ + struct mwl8k_cmd_set_key *cmd; + int rc; + int keymlen; + u32 action; + u8 idx; + struct mwl8k_vif *mwl8k_vif = MWL8K_VIF(vif); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + rc = mwl8k_encryption_set_cmd_info(cmd, addr, key); + if (rc < 0) + goto done; + + idx = key->keyidx; + + if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) + action = MWL8K_ENCR_SET_KEY; + else + action = MWL8K_ENCR_SET_GROUP_KEY; + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + if (!mwl8k_vif->wep_key_conf[idx].enabled) { + memcpy(mwl8k_vif->wep_key_conf[idx].key, key, + sizeof(*key) + key->keylen); + mwl8k_vif->wep_key_conf[idx].enabled = 1; + } + + keymlen = key->keylen; + action = MWL8K_ENCR_SET_KEY; + break; + case WLAN_CIPHER_SUITE_TKIP: + keymlen = MAX_ENCR_KEY_LENGTH + 2 * MIC_KEY_LENGTH; + break; + case WLAN_CIPHER_SUITE_CCMP: + keymlen = key->keylen; + break; + default: + rc = -ENOTSUPP; + goto done; + } + + memcpy(cmd->key_material, key->key, keymlen); + cmd->action = cpu_to_le32(action); + + rc = mwl8k_post_pervif_cmd(hw, vif, &cmd->header); +done: + kfree(cmd); + + return rc; +} + +static int mwl8k_cmd_encryption_remove_key(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u8 *addr, + struct ieee80211_key_conf *key) +{ + struct mwl8k_cmd_set_key *cmd; + int rc; + struct mwl8k_vif *mwl8k_vif = MWL8K_VIF(vif); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + rc = mwl8k_encryption_set_cmd_info(cmd, addr, key); + if (rc < 0) + goto done; + + if (key->cipher == WLAN_CIPHER_SUITE_WEP40 || + key->cipher == WLAN_CIPHER_SUITE_WEP104) + mwl8k_vif->wep_key_conf[key->keyidx].enabled = 0; + + cmd->action = cpu_to_le32(MWL8K_ENCR_REMOVE_KEY); + + rc = mwl8k_post_pervif_cmd(hw, vif, &cmd->header); +done: + kfree(cmd); + + return rc; +} + +static int mwl8k_set_key(struct ieee80211_hw *hw, + enum set_key_cmd cmd_param, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + int rc = 0; + u8 encr_type; + u8 *addr; + struct mwl8k_vif *mwl8k_vif = MWL8K_VIF(vif); + struct mwl8k_priv *priv = hw->priv; + + if (vif->type == NL80211_IFTYPE_STATION && !priv->ap_fw) + return -EOPNOTSUPP; + + if (sta == NULL) + addr = vif->addr; + else + addr = sta->addr; + + if (cmd_param == SET_KEY) { + rc = mwl8k_cmd_encryption_set_key(hw, vif, addr, key); + if (rc) + goto out; + + if ((key->cipher == WLAN_CIPHER_SUITE_WEP40) + || (key->cipher == WLAN_CIPHER_SUITE_WEP104)) + encr_type = MWL8K_UPDATE_ENCRYPTION_TYPE_WEP; + else + encr_type = MWL8K_UPDATE_ENCRYPTION_TYPE_MIXED; + + rc = mwl8k_cmd_update_encryption_enable(hw, vif, addr, + encr_type); + if (rc) + goto out; + + mwl8k_vif->is_hw_crypto_enabled = true; + + } else { + rc = mwl8k_cmd_encryption_remove_key(hw, vif, addr, key); + + if (rc) + goto out; + } +out: + return rc; +} + +/* + * CMD_UPDATE_STADB. + */ +struct ewc_ht_info { + __le16 control1; + __le16 control2; + __le16 control3; +} __packed; + +struct peer_capability_info { + /* Peer type - AP vs. STA. */ + __u8 peer_type; + + /* Basic 802.11 capabilities from assoc resp. */ + __le16 basic_caps; + + /* Set if peer supports 802.11n high throughput (HT). */ + __u8 ht_support; + + /* Valid if HT is supported. */ + __le16 ht_caps; + __u8 extended_ht_caps; + struct ewc_ht_info ewc_info; + + /* Legacy rate table. Intersection of our rates and peer rates. */ + __u8 legacy_rates[12]; + + /* HT rate table. Intersection of our rates and peer rates. */ + __u8 ht_rates[16]; + __u8 pad[16]; + + /* If set, interoperability mode, no proprietary extensions. */ + __u8 interop; + __u8 pad2; + __u8 station_id; + __le16 amsdu_enabled; +} __packed; + +struct mwl8k_cmd_update_stadb { + struct mwl8k_cmd_pkt header; + + /* See STADB_ACTION_TYPE */ + __le32 action; + + /* Peer MAC address */ + __u8 peer_addr[ETH_ALEN]; + + __le32 reserved; + + /* Peer info - valid during add/update. */ + struct peer_capability_info peer_info; +} __packed; + +#define MWL8K_STA_DB_MODIFY_ENTRY 1 +#define MWL8K_STA_DB_DEL_ENTRY 2 + +/* Peer Entry flags - used to define the type of the peer node */ +#define MWL8K_PEER_TYPE_ACCESSPOINT 2 + +static int mwl8k_cmd_update_stadb_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mwl8k_cmd_update_stadb *cmd; + struct peer_capability_info *p; + u32 rates; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_UPDATE_STADB); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le32(MWL8K_STA_DB_MODIFY_ENTRY); + memcpy(cmd->peer_addr, sta->addr, ETH_ALEN); + + p = &cmd->peer_info; + p->peer_type = MWL8K_PEER_TYPE_ACCESSPOINT; + p->basic_caps = cpu_to_le16(vif->bss_conf.assoc_capability); + p->ht_support = sta->ht_cap.ht_supported; + p->ht_caps = cpu_to_le16(sta->ht_cap.cap); + p->extended_ht_caps = (sta->ht_cap.ampdu_factor & 3) | + ((sta->ht_cap.ampdu_density & 7) << 2); + if (hw->conf.chandef.chan->band == IEEE80211_BAND_2GHZ) + rates = sta->supp_rates[IEEE80211_BAND_2GHZ]; + else + rates = sta->supp_rates[IEEE80211_BAND_5GHZ] << 5; + legacy_rate_mask_to_array(p->legacy_rates, rates); + memcpy(p->ht_rates, sta->ht_cap.mcs.rx_mask, 16); + p->interop = 1; + p->amsdu_enabled = 0; + + rc = mwl8k_post_cmd(hw, &cmd->header); + if (!rc) + rc = p->station_id; + kfree(cmd); + + return rc; +} + +static int mwl8k_cmd_update_stadb_del(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u8 *addr) +{ + struct mwl8k_cmd_update_stadb *cmd; + int rc; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + + cmd->header.code = cpu_to_le16(MWL8K_CMD_UPDATE_STADB); + cmd->header.length = cpu_to_le16(sizeof(*cmd)); + cmd->action = cpu_to_le32(MWL8K_STA_DB_DEL_ENTRY); + memcpy(cmd->peer_addr, addr, ETH_ALEN); + + rc = mwl8k_post_cmd(hw, &cmd->header); + kfree(cmd); + + return rc; +} + + +/* + * Interrupt handling. + */ +static irqreturn_t mwl8k_interrupt(int irq, void *dev_id) +{ + struct ieee80211_hw *hw = dev_id; + struct mwl8k_priv *priv = hw->priv; + u32 status; + + status = ioread32(priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS); + if (!status) + return IRQ_NONE; + + if (status & MWL8K_A2H_INT_TX_DONE) { + status &= ~MWL8K_A2H_INT_TX_DONE; + tasklet_schedule(&priv->poll_tx_task); + } + + if (status & MWL8K_A2H_INT_RX_READY) { + status &= ~MWL8K_A2H_INT_RX_READY; + tasklet_schedule(&priv->poll_rx_task); + } + + if (status & MWL8K_A2H_INT_BA_WATCHDOG) { + iowrite32(~MWL8K_A2H_INT_BA_WATCHDOG, + priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS_MASK); + + atomic_inc(&priv->watchdog_event_pending); + status &= ~MWL8K_A2H_INT_BA_WATCHDOG; + ieee80211_queue_work(hw, &priv->watchdog_ba_handle); + } + + if (status) + iowrite32(~status, priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS); + + if (status & MWL8K_A2H_INT_OPC_DONE) { + if (priv->hostcmd_wait != NULL) + complete(priv->hostcmd_wait); + } + + if (status & MWL8K_A2H_INT_QUEUE_EMPTY) { + if (!mutex_is_locked(&priv->fw_mutex) && + priv->radio_on && priv->pending_tx_pkts) + mwl8k_tx_start(priv); + } + + return IRQ_HANDLED; +} + +static void mwl8k_tx_poll(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct mwl8k_priv *priv = hw->priv; + int limit; + int i; + + limit = 32; + + spin_lock_bh(&priv->tx_lock); + + for (i = 0; i < mwl8k_tx_queues(priv); i++) + limit -= mwl8k_txq_reclaim(hw, i, limit, 0); + + if (!priv->pending_tx_pkts && priv->tx_wait != NULL) { + complete(priv->tx_wait); + priv->tx_wait = NULL; + } + + spin_unlock_bh(&priv->tx_lock); + + if (limit) { + writel(~MWL8K_A2H_INT_TX_DONE, + priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS); + } else { + tasklet_schedule(&priv->poll_tx_task); + } +} + +static void mwl8k_rx_poll(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct mwl8k_priv *priv = hw->priv; + int limit; + + limit = 32; + limit -= rxq_process(hw, 0, limit); + limit -= rxq_refill(hw, 0, limit); + + if (limit) { + writel(~MWL8K_A2H_INT_RX_READY, + priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS); + } else { + tasklet_schedule(&priv->poll_rx_task); + } +} + + +/* + * Core driver operations. + */ +static void mwl8k_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct mwl8k_priv *priv = hw->priv; + int index = skb_get_queue_mapping(skb); + + if (!priv->radio_on) { + wiphy_debug(hw->wiphy, + "dropped TX frame since radio disabled\n"); + dev_kfree_skb(skb); + return; + } + + mwl8k_txq_xmit(hw, index, control->sta, skb); +} + +static int mwl8k_start(struct ieee80211_hw *hw) +{ + struct mwl8k_priv *priv = hw->priv; + int rc; + + rc = request_irq(priv->pdev->irq, mwl8k_interrupt, + IRQF_SHARED, MWL8K_NAME, hw); + if (rc) { + priv->irq = -1; + wiphy_err(hw->wiphy, "failed to register IRQ handler\n"); + return -EIO; + } + priv->irq = priv->pdev->irq; + + /* Enable TX reclaim and RX tasklets. */ + tasklet_enable(&priv->poll_tx_task); + tasklet_enable(&priv->poll_rx_task); + + /* Enable interrupts */ + iowrite32(MWL8K_A2H_EVENTS, priv->regs + MWL8K_HIU_A2H_INTERRUPT_MASK); + iowrite32(MWL8K_A2H_EVENTS, + priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS_MASK); + + rc = mwl8k_fw_lock(hw); + if (!rc) { + rc = mwl8k_cmd_radio_enable(hw); + + if (!priv->ap_fw) { + if (!rc) + rc = mwl8k_cmd_enable_sniffer(hw, 0); + + if (!rc) + rc = mwl8k_cmd_set_pre_scan(hw); + + if (!rc) + rc = mwl8k_cmd_set_post_scan(hw, + "\x00\x00\x00\x00\x00\x00"); + } + + if (!rc) + rc = mwl8k_cmd_set_rateadapt_mode(hw, 0); + + if (!rc) + rc = mwl8k_cmd_set_wmm_mode(hw, 0); + + mwl8k_fw_unlock(hw); + } + + if (rc) { + iowrite32(0, priv->regs + MWL8K_HIU_A2H_INTERRUPT_MASK); + free_irq(priv->pdev->irq, hw); + priv->irq = -1; + tasklet_disable(&priv->poll_tx_task); + tasklet_disable(&priv->poll_rx_task); + } else { + ieee80211_wake_queues(hw); + } + + return rc; +} + +static void mwl8k_stop(struct ieee80211_hw *hw) +{ + struct mwl8k_priv *priv = hw->priv; + int i; + + if (!priv->hw_restart_in_progress) + mwl8k_cmd_radio_disable(hw); + + ieee80211_stop_queues(hw); + + /* Disable interrupts */ + iowrite32(0, priv->regs + MWL8K_HIU_A2H_INTERRUPT_MASK); + if (priv->irq != -1) { + free_irq(priv->pdev->irq, hw); + priv->irq = -1; + } + + /* Stop finalize join worker */ + cancel_work_sync(&priv->finalize_join_worker); + cancel_work_sync(&priv->watchdog_ba_handle); + if (priv->beacon_skb != NULL) + dev_kfree_skb(priv->beacon_skb); + + /* Stop TX reclaim and RX tasklets. */ + tasklet_disable(&priv->poll_tx_task); + tasklet_disable(&priv->poll_rx_task); + + /* Return all skbs to mac80211 */ + for (i = 0; i < mwl8k_tx_queues(priv); i++) + mwl8k_txq_reclaim(hw, i, INT_MAX, 1); +} + +static int mwl8k_reload_firmware(struct ieee80211_hw *hw, char *fw_image); + +static int mwl8k_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_vif *mwl8k_vif; + u32 macids_supported; + int macid, rc; + struct mwl8k_device_info *di; + + /* + * Reject interface creation if sniffer mode is active, as + * STA operation is mutually exclusive with hardware sniffer + * mode. (Sniffer mode is only used on STA firmware.) + */ + if (priv->sniffer_enabled) { + wiphy_info(hw->wiphy, + "unable to create STA interface because sniffer mode is enabled\n"); + return -EINVAL; + } + + di = priv->device_info; + switch (vif->type) { + case NL80211_IFTYPE_AP: + if (!priv->ap_fw && di->fw_image_ap) { + /* we must load the ap fw to meet this request */ + if (!list_empty(&priv->vif_list)) + return -EBUSY; + rc = mwl8k_reload_firmware(hw, di->fw_image_ap); + if (rc) + return rc; + } + macids_supported = priv->ap_macids_supported; + break; + case NL80211_IFTYPE_STATION: + if (priv->ap_fw && di->fw_image_sta) { + if (!list_empty(&priv->vif_list)) { + wiphy_warn(hw->wiphy, "AP interface is running.\n" + "Adding STA interface for WDS"); + } else { + /* we must load the sta fw to + * meet this request. + */ + rc = mwl8k_reload_firmware(hw, + di->fw_image_sta); + if (rc) + return rc; + } + } + macids_supported = priv->sta_macids_supported; + break; + default: + return -EINVAL; + } + + macid = ffs(macids_supported & ~priv->macids_used); + if (!macid--) + return -EBUSY; + + /* Setup driver private area. */ + mwl8k_vif = MWL8K_VIF(vif); + memset(mwl8k_vif, 0, sizeof(*mwl8k_vif)); + mwl8k_vif->vif = vif; + mwl8k_vif->macid = macid; + mwl8k_vif->seqno = 0; + memcpy(mwl8k_vif->bssid, vif->addr, ETH_ALEN); + mwl8k_vif->is_hw_crypto_enabled = false; + + /* Set the mac address. */ + mwl8k_cmd_set_mac_addr(hw, vif, vif->addr); + + if (vif->type == NL80211_IFTYPE_AP) + mwl8k_cmd_set_new_stn_add_self(hw, vif); + + priv->macids_used |= 1 << mwl8k_vif->macid; + list_add_tail(&mwl8k_vif->list, &priv->vif_list); + + return 0; +} + +static void mwl8k_remove_vif(struct mwl8k_priv *priv, struct mwl8k_vif *vif) +{ + /* Has ieee80211_restart_hw re-added the removed interfaces? */ + if (!priv->macids_used) + return; + + priv->macids_used &= ~(1 << vif->macid); + list_del(&vif->list); +} + +static void mwl8k_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_vif *mwl8k_vif = MWL8K_VIF(vif); + + if (vif->type == NL80211_IFTYPE_AP) + mwl8k_cmd_set_new_stn_del(hw, vif, vif->addr); + + mwl8k_cmd_del_mac_addr(hw, vif, vif->addr); + + mwl8k_remove_vif(priv, mwl8k_vif); +} + +static void mwl8k_hw_restart_work(struct work_struct *work) +{ + struct mwl8k_priv *priv = + container_of(work, struct mwl8k_priv, fw_reload); + struct ieee80211_hw *hw = priv->hw; + struct mwl8k_device_info *di; + int rc; + + /* If some command is waiting for a response, clear it */ + if (priv->hostcmd_wait != NULL) { + complete(priv->hostcmd_wait); + priv->hostcmd_wait = NULL; + } + + priv->hw_restart_owner = current; + di = priv->device_info; + mwl8k_fw_lock(hw); + + if (priv->ap_fw) + rc = mwl8k_reload_firmware(hw, di->fw_image_ap); + else + rc = mwl8k_reload_firmware(hw, di->fw_image_sta); + + if (rc) + goto fail; + + priv->hw_restart_owner = NULL; + priv->hw_restart_in_progress = false; + + /* + * This unlock will wake up the queues and + * also opens the command path for other + * commands + */ + mwl8k_fw_unlock(hw); + + ieee80211_restart_hw(hw); + + wiphy_err(hw->wiphy, "Firmware restarted successfully\n"); + + return; +fail: + mwl8k_fw_unlock(hw); + + wiphy_err(hw->wiphy, "Firmware restart failed\n"); +} + +static int mwl8k_config(struct ieee80211_hw *hw, u32 changed) +{ + struct ieee80211_conf *conf = &hw->conf; + struct mwl8k_priv *priv = hw->priv; + int rc; + + rc = mwl8k_fw_lock(hw); + if (rc) + return rc; + + if (conf->flags & IEEE80211_CONF_IDLE) + rc = mwl8k_cmd_radio_disable(hw); + else + rc = mwl8k_cmd_radio_enable(hw); + if (rc) + goto out; + + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + rc = mwl8k_cmd_set_rf_channel(hw, conf); + if (rc) + goto out; + } + + if (conf->power_level > 18) + conf->power_level = 18; + + if (priv->ap_fw) { + + if (conf->flags & IEEE80211_CONF_CHANGE_POWER) { + rc = mwl8k_cmd_tx_power(hw, conf, conf->power_level); + if (rc) + goto out; + } + + + } else { + rc = mwl8k_cmd_rf_tx_power(hw, conf->power_level); + if (rc) + goto out; + rc = mwl8k_cmd_mimo_config(hw, 0x7, 0x7); + } + +out: + mwl8k_fw_unlock(hw); + + return rc; +} + +static void +mwl8k_bss_info_changed_sta(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, u32 changed) +{ + struct mwl8k_priv *priv = hw->priv; + u32 ap_legacy_rates = 0; + u8 ap_mcs_rates[16]; + int rc; + + if (mwl8k_fw_lock(hw)) + return; + + /* + * No need to capture a beacon if we're no longer associated. + */ + if ((changed & BSS_CHANGED_ASSOC) && !vif->bss_conf.assoc) + priv->capture_beacon = false; + + /* + * Get the AP's legacy and MCS rates. + */ + if (vif->bss_conf.assoc) { + struct ieee80211_sta *ap; + + rcu_read_lock(); + + ap = ieee80211_find_sta(vif, vif->bss_conf.bssid); + if (ap == NULL) { + rcu_read_unlock(); + goto out; + } + + if (hw->conf.chandef.chan->band == IEEE80211_BAND_2GHZ) { + ap_legacy_rates = ap->supp_rates[IEEE80211_BAND_2GHZ]; + } else { + ap_legacy_rates = + ap->supp_rates[IEEE80211_BAND_5GHZ] << 5; + } + memcpy(ap_mcs_rates, ap->ht_cap.mcs.rx_mask, 16); + + rcu_read_unlock(); + } + + if ((changed & BSS_CHANGED_ASSOC) && vif->bss_conf.assoc && + !priv->ap_fw) { + rc = mwl8k_cmd_set_rate(hw, vif, ap_legacy_rates, ap_mcs_rates); + if (rc) + goto out; + + rc = mwl8k_cmd_use_fixed_rate_sta(hw); + if (rc) + goto out; + } else { + if ((changed & BSS_CHANGED_ASSOC) && vif->bss_conf.assoc && + priv->ap_fw) { + int idx; + int rate; + + /* Use AP firmware specific rate command. + */ + idx = ffs(vif->bss_conf.basic_rates); + if (idx) + idx--; + + if (hw->conf.chandef.chan->band == IEEE80211_BAND_2GHZ) + rate = mwl8k_rates_24[idx].hw_value; + else + rate = mwl8k_rates_50[idx].hw_value; + + mwl8k_cmd_use_fixed_rate_ap(hw, rate, rate); + } + } + + if (changed & BSS_CHANGED_ERP_PREAMBLE) { + rc = mwl8k_set_radio_preamble(hw, + vif->bss_conf.use_short_preamble); + if (rc) + goto out; + } + + if ((changed & BSS_CHANGED_ERP_SLOT) && !priv->ap_fw) { + rc = mwl8k_cmd_set_slot(hw, vif->bss_conf.use_short_slot); + if (rc) + goto out; + } + + if (vif->bss_conf.assoc && !priv->ap_fw && + (changed & (BSS_CHANGED_ASSOC | BSS_CHANGED_ERP_CTS_PROT | + BSS_CHANGED_HT))) { + rc = mwl8k_cmd_set_aid(hw, vif, ap_legacy_rates); + if (rc) + goto out; + } + + if (vif->bss_conf.assoc && + (changed & (BSS_CHANGED_ASSOC | BSS_CHANGED_BEACON_INT))) { + /* + * Finalize the join. Tell rx handler to process + * next beacon from our BSSID. + */ + memcpy(priv->capture_bssid, vif->bss_conf.bssid, ETH_ALEN); + priv->capture_beacon = true; + } + +out: + mwl8k_fw_unlock(hw); +} + +static void +mwl8k_bss_info_changed_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, u32 changed) +{ + int rc; + + if (mwl8k_fw_lock(hw)) + return; + + if (changed & BSS_CHANGED_ERP_PREAMBLE) { + rc = mwl8k_set_radio_preamble(hw, + vif->bss_conf.use_short_preamble); + if (rc) + goto out; + } + + if (changed & BSS_CHANGED_BASIC_RATES) { + int idx; + int rate; + + /* + * Use lowest supported basic rate for multicasts + * and management frames (such as probe responses -- + * beacons will always go out at 1 Mb/s). + */ + idx = ffs(vif->bss_conf.basic_rates); + if (idx) + idx--; + + if (hw->conf.chandef.chan->band == IEEE80211_BAND_2GHZ) + rate = mwl8k_rates_24[idx].hw_value; + else + rate = mwl8k_rates_50[idx].hw_value; + + mwl8k_cmd_use_fixed_rate_ap(hw, rate, rate); + } + + if (changed & (BSS_CHANGED_BEACON_INT | BSS_CHANGED_BEACON)) { + struct sk_buff *skb; + + skb = ieee80211_beacon_get(hw, vif); + if (skb != NULL) { + mwl8k_cmd_set_beacon(hw, vif, skb->data, skb->len); + kfree_skb(skb); + } + } + + if (changed & BSS_CHANGED_BEACON_ENABLED) + mwl8k_cmd_bss_start(hw, vif, info->enable_beacon); + +out: + mwl8k_fw_unlock(hw); +} + +static void +mwl8k_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, u32 changed) +{ + if (vif->type == NL80211_IFTYPE_STATION) + mwl8k_bss_info_changed_sta(hw, vif, info, changed); + if (vif->type == NL80211_IFTYPE_AP) + mwl8k_bss_info_changed_ap(hw, vif, info, changed); +} + +static u64 mwl8k_prepare_multicast(struct ieee80211_hw *hw, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + struct netdev_hw_addr_list *mc_list) +#else + int mc_count, struct dev_addr_list *ha) +#endif +{ + struct mwl8k_cmd_pkt *cmd; + + /* + * Synthesize and return a command packet that programs the + * hardware multicast address filter. At this point we don't + * know whether FIF_ALLMULTI is being requested, but if it is, + * we'll end up throwing this packet away and creating a new + * one in mwl8k_configure_filter(). + */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + cmd = __mwl8k_cmd_mac_multicast_adr(hw, 0, mc_list); +#else + cmd = __mwl8k_cmd_mac_multicast_adr(hw, 0, mc_count, ha); +#endif + + return (unsigned long)cmd; +} + +static int +mwl8k_configure_filter_sniffer(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags) +{ + struct mwl8k_priv *priv = hw->priv; + + /* + * Hardware sniffer mode is mutually exclusive with STA + * operation, so refuse to enable sniffer mode if a STA + * interface is active. + */ + if (!list_empty(&priv->vif_list)) { + if (net_ratelimit()) + wiphy_info(hw->wiphy, + "not enabling sniffer mode because STA interface is active\n"); + return 0; + } + + if (!priv->sniffer_enabled) { + if (mwl8k_cmd_enable_sniffer(hw, 1)) + return 0; + priv->sniffer_enabled = true; + } + + *total_flags &= FIF_PROMISC_IN_BSS | FIF_ALLMULTI | + FIF_BCN_PRBRESP_PROMISC | FIF_CONTROL | + FIF_OTHER_BSS; + + return 1; +} + +static struct mwl8k_vif *mwl8k_first_vif(struct mwl8k_priv *priv) +{ + if (!list_empty(&priv->vif_list)) + return list_entry(priv->vif_list.next, struct mwl8k_vif, list); + + return NULL; +} + +static void mwl8k_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_cmd_pkt *cmd = (void *)(unsigned long)multicast; + + /* + * AP firmware doesn't allow fine-grained control over + * the receive filter. + */ + if (priv->ap_fw) { + *total_flags &= FIF_ALLMULTI | FIF_BCN_PRBRESP_PROMISC; + kfree(cmd); + return; + } + + /* + * Enable hardware sniffer mode if FIF_CONTROL or + * FIF_OTHER_BSS is requested. + */ + if (*total_flags & (FIF_CONTROL | FIF_OTHER_BSS) && + mwl8k_configure_filter_sniffer(hw, changed_flags, total_flags)) { + kfree(cmd); + return; + } + + /* Clear unsupported feature flags */ + *total_flags &= FIF_ALLMULTI | FIF_BCN_PRBRESP_PROMISC; + + if (mwl8k_fw_lock(hw)) { + kfree(cmd); + return; + } + + if (priv->sniffer_enabled) { + mwl8k_cmd_enable_sniffer(hw, 0); + priv->sniffer_enabled = false; + } + + if (changed_flags & FIF_BCN_PRBRESP_PROMISC) { + if (*total_flags & FIF_BCN_PRBRESP_PROMISC) { + /* + * Disable the BSS filter. + */ + mwl8k_cmd_set_pre_scan(hw); + } else { + struct mwl8k_vif *mwl8k_vif; + const u8 *bssid; + + /* + * Enable the BSS filter. + * + * If there is an active STA interface, use that + * interface's BSSID, otherwise use a dummy one + * (where the OUI part needs to be nonzero for + * the BSSID to be accepted by POST_SCAN). + */ + mwl8k_vif = mwl8k_first_vif(priv); + if (mwl8k_vif != NULL) + bssid = mwl8k_vif->vif->bss_conf.bssid; + else + bssid = "\x01\x00\x00\x00\x00\x00"; + + mwl8k_cmd_set_post_scan(hw, bssid); + } + } + + /* + * If FIF_ALLMULTI is being requested, throw away the command + * packet that ->prepare_multicast() built and replace it with + * a command packet that enables reception of all multicast + * packets. + */ + if (*total_flags & FIF_ALLMULTI) { + kfree(cmd); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + cmd = __mwl8k_cmd_mac_multicast_adr(hw, 1, NULL); +#else + cmd = __mwl8k_cmd_mac_multicast_adr(hw, 1, 0, NULL); +#endif + } + + if (cmd != NULL) { + mwl8k_post_cmd(hw, cmd); + kfree(cmd); + } + + mwl8k_fw_unlock(hw); +} + +static int mwl8k_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +{ + return mwl8k_cmd_set_rts_threshold(hw, value); +} + +static int mwl8k_sta_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mwl8k_priv *priv = hw->priv; + + if (priv->ap_fw) + return mwl8k_cmd_set_new_stn_del(hw, vif, sta->addr); + else + return mwl8k_cmd_update_stadb_del(hw, vif, sta->addr); +} + +static int mwl8k_sta_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mwl8k_priv *priv = hw->priv; + int ret; + int i; + struct mwl8k_vif *mwl8k_vif = MWL8K_VIF(vif); + struct ieee80211_key_conf *key; + + if (!priv->ap_fw) { + ret = mwl8k_cmd_update_stadb_add(hw, vif, sta); + if (ret >= 0) { + MWL8K_STA(sta)->peer_id = ret; + if (sta->ht_cap.ht_supported) + MWL8K_STA(sta)->is_ampdu_allowed = true; + ret = 0; + } + + } else { + ret = mwl8k_cmd_set_new_stn_add(hw, vif, sta); + } + + for (i = 0; i < NUM_WEP_KEYS; i++) { + key = IEEE80211_KEY_CONF(mwl8k_vif->wep_key_conf[i].key); + if (mwl8k_vif->wep_key_conf[i].enabled) + mwl8k_set_key(hw, SET_KEY, vif, sta, key); + } + return ret; +} + +static int mwl8k_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct mwl8k_priv *priv = hw->priv; + int rc; + + rc = mwl8k_fw_lock(hw); + if (!rc) { + BUG_ON(queue > MWL8K_TX_WMM_QUEUES - 1); + memcpy(&priv->wmm_params[queue], params, sizeof(*params)); + + if (!priv->wmm_enabled) + rc = mwl8k_cmd_set_wmm_mode(hw, 1); + + if (!rc) { + int q = MWL8K_TX_WMM_QUEUES - 1 - queue; + rc = mwl8k_cmd_set_edca_params(hw, q, + params->cw_min, + params->cw_max, + params->aifs, + params->txop); + } + + mwl8k_fw_unlock(hw); + } + + return rc; +} + +static int mwl8k_get_stats(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + return mwl8k_cmd_get_stat(hw, stats); +} + +static int mwl8k_get_survey(struct ieee80211_hw *hw, int idx, + struct survey_info *survey) +{ + struct mwl8k_priv *priv = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + + if (idx != 0) + return -ENOENT; + + survey->channel = conf->chandef.chan; + survey->filled = SURVEY_INFO_NOISE_DBM; + survey->noise = priv->noise; + + return 0; +} + +#define MAX_AMPDU_ATTEMPTS 5 + +static int +mwl8k_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, u16 *ssn, + u8 buf_size) +{ + + int i, rc = 0; + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_ampdu_stream *stream; + u8 *addr = sta->addr, idx; + struct mwl8k_sta *sta_info = MWL8K_STA(sta); + + if (!(hw->flags & IEEE80211_HW_AMPDU_AGGREGATION)) + return -ENOTSUPP; + + spin_lock(&priv->stream_lock); + stream = mwl8k_lookup_stream(hw, addr, tid); + + switch (action) { + case IEEE80211_AMPDU_RX_START: + case IEEE80211_AMPDU_RX_STOP: + break; + case IEEE80211_AMPDU_TX_START: + /* By the time we get here the hw queues may contain outgoing + * packets for this RA/TID that are not part of this BA + * session. The hw will assign sequence numbers to these + * packets as they go out. So if we query the hw for its next + * sequence number and use that for the SSN here, it may end up + * being wrong, which will lead to sequence number mismatch at + * the recipient. To avoid this, we reset the sequence number + * to O for the first MPDU in this BA stream. + */ + *ssn = 0; + if (stream == NULL) { + /* This means that somebody outside this driver called + * ieee80211_start_tx_ba_session. This is unexpected + * because we do our own rate control. Just warn and + * move on. + */ + wiphy_warn(hw->wiphy, "Unexpected call to %s. " + "Proceeding anyway.\n", __func__); + stream = mwl8k_add_stream(hw, sta, tid); + } + if (stream == NULL) { + wiphy_debug(hw->wiphy, "no free AMPDU streams\n"); + rc = -EBUSY; + break; + } + stream->state = AMPDU_STREAM_IN_PROGRESS; + + /* Release the lock before we do the time consuming stuff */ + spin_unlock(&priv->stream_lock); + for (i = 0; i < MAX_AMPDU_ATTEMPTS; i++) { + + /* Check if link is still valid */ + if (!sta_info->is_ampdu_allowed) { + spin_lock(&priv->stream_lock); + mwl8k_remove_stream(hw, stream); + spin_unlock(&priv->stream_lock); + return -EBUSY; + } + + rc = mwl8k_check_ba(hw, stream, vif); + + /* If HW restart is in progress mwl8k_post_cmd will + * return -EBUSY. Avoid retrying mwl8k_check_ba in + * such cases + */ + if (!rc || rc == -EBUSY) + break; + /* + * HW queues take time to be flushed, give them + * sufficient time + */ + + msleep(1000); + } + spin_lock(&priv->stream_lock); + if (rc) { + wiphy_err(hw->wiphy, "Stream for tid %d busy after %d" + " attempts\n", tid, MAX_AMPDU_ATTEMPTS); + mwl8k_remove_stream(hw, stream); + rc = -EBUSY; + break; + } + ieee80211_start_tx_ba_cb_irqsafe(vif, addr, tid); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + if (stream) { + if (stream->state == AMPDU_STREAM_ACTIVE) { + idx = stream->idx; + spin_unlock(&priv->stream_lock); + mwl8k_destroy_ba(hw, idx); + spin_lock(&priv->stream_lock); + } + mwl8k_remove_stream(hw, stream); + } + ieee80211_stop_tx_ba_cb_irqsafe(vif, addr, tid); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + BUG_ON(stream == NULL); + BUG_ON(stream->state != AMPDU_STREAM_IN_PROGRESS); + spin_unlock(&priv->stream_lock); + rc = mwl8k_create_ba(hw, stream, buf_size, vif); + spin_lock(&priv->stream_lock); + if (!rc) + stream->state = AMPDU_STREAM_ACTIVE; + else { + idx = stream->idx; + spin_unlock(&priv->stream_lock); + mwl8k_destroy_ba(hw, idx); + spin_lock(&priv->stream_lock); + wiphy_debug(hw->wiphy, + "Failed adding stream for sta %pM tid %d\n", + addr, tid); + mwl8k_remove_stream(hw, stream); + } + break; + + default: + rc = -ENOTSUPP; + } + + spin_unlock(&priv->stream_lock); + return rc; +} + +static const struct ieee80211_ops mwl8k_ops = { + .tx = mwl8k_tx, + .start = mwl8k_start, + .stop = mwl8k_stop, + .add_interface = mwl8k_add_interface, + .remove_interface = mwl8k_remove_interface, + .config = mwl8k_config, + .bss_info_changed = mwl8k_bss_info_changed, + .prepare_multicast = mwl8k_prepare_multicast, + .configure_filter = mwl8k_configure_filter, + .set_key = mwl8k_set_key, + .set_rts_threshold = mwl8k_set_rts_threshold, + .sta_add = mwl8k_sta_add, + .sta_remove = mwl8k_sta_remove, + .conf_tx = mwl8k_conf_tx, + .get_stats = mwl8k_get_stats, + .get_survey = mwl8k_get_survey, + .ampdu_action = mwl8k_ampdu_action, +}; + +static void mwl8k_finalize_join_worker(struct work_struct *work) +{ + struct mwl8k_priv *priv = + container_of(work, struct mwl8k_priv, finalize_join_worker); + struct sk_buff *skb = priv->beacon_skb; + struct ieee80211_mgmt *mgmt = (void *)skb->data; + int len = skb->len - offsetof(struct ieee80211_mgmt, u.beacon.variable); + const u8 *tim = cfg80211_find_ie(WLAN_EID_TIM, + mgmt->u.beacon.variable, len); + int dtim_period = 1; + + if (tim && tim[1] >= 2) + dtim_period = tim[3]; + + mwl8k_cmd_finalize_join(priv->hw, skb->data, skb->len, dtim_period); + + dev_kfree_skb(skb); + priv->beacon_skb = NULL; +} + +enum { + MWL8363 = 0, + MWL8687, + MWL8366, + MWL8764, +}; + +#define MWL8K_8366_AP_FW_API 3 +#define _MWL8K_8366_AP_FW(api) "mwl8k/fmimage_8366_ap-" #api ".fw" +#define MWL8K_8366_AP_FW(api) _MWL8K_8366_AP_FW(api) + +#define MWL8K_8764_AP_FW_API 1 +#define _MWL8K_8764_AP_FW(api) "mwl8k/fmimage_8764_ap-" #api ".fw" +#define MWL8K_8764_AP_FW(api) _MWL8K_8764_AP_FW(api) + +static struct mwl8k_device_info mwl8k_info_tbl[] = { + [MWL8363] = { + .part_name = "88w8363", + .helper_image = "mwl8k/helper_8363.fw", + .fw_image_sta = "mwl8k/fmimage_8363.fw", + }, + [MWL8687] = { + .part_name = "88w8687", + .helper_image = "mwl8k/helper_8687.fw", + .fw_image_sta = "mwl8k/fmimage_8687.fw", + }, + [MWL8366] = { + .part_name = "88w8366", + .helper_image = "mwl8k/helper_8366.fw", + .fw_image_sta = "mwl8k/fmimage_8366.fw", + .fw_image_ap = MWL8K_8366_AP_FW(MWL8K_8366_AP_FW_API), + .fw_api_ap = MWL8K_8366_AP_FW_API, + .ap_rxd_ops = &rxd_ap_ops, + }, + [MWL8764] = { + .part_name = "88w8764", + .fw_image_ap = MWL8K_8764_AP_FW(MWL8K_8764_AP_FW_API), + .fw_api_ap = MWL8K_8764_AP_FW_API, + .ap_rxd_ops = &rxd_ap_ops, + }, +}; + +MODULE_FIRMWARE("mwl8k/helper_8363.fw"); +MODULE_FIRMWARE("mwl8k/fmimage_8363.fw"); +MODULE_FIRMWARE("mwl8k/helper_8687.fw"); +MODULE_FIRMWARE("mwl8k/fmimage_8687.fw"); +MODULE_FIRMWARE("mwl8k/helper_8366.fw"); +MODULE_FIRMWARE("mwl8k/fmimage_8366.fw"); +MODULE_FIRMWARE(MWL8K_8366_AP_FW(MWL8K_8366_AP_FW_API)); + +static DEFINE_PCI_DEVICE_TABLE(mwl8k_pci_id_table) = { + { PCI_VDEVICE(MARVELL, 0x2a0a), .driver_data = MWL8363, }, + { PCI_VDEVICE(MARVELL, 0x2a0c), .driver_data = MWL8363, }, + { PCI_VDEVICE(MARVELL, 0x2a24), .driver_data = MWL8363, }, + { PCI_VDEVICE(MARVELL, 0x2a2b), .driver_data = MWL8687, }, + { PCI_VDEVICE(MARVELL, 0x2a30), .driver_data = MWL8687, }, + { PCI_VDEVICE(MARVELL, 0x2a40), .driver_data = MWL8366, }, + { PCI_VDEVICE(MARVELL, 0x2a41), .driver_data = MWL8366, }, + { PCI_VDEVICE(MARVELL, 0x2a42), .driver_data = MWL8366, }, + { PCI_VDEVICE(MARVELL, 0x2a43), .driver_data = MWL8366, }, + { PCI_VDEVICE(MARVELL, 0x2b36), .driver_data = MWL8764, }, + { }, +}; +MODULE_DEVICE_TABLE(pci, mwl8k_pci_id_table); + +static int mwl8k_request_alt_fw(struct mwl8k_priv *priv) +{ + int rc; + printk(KERN_ERR "%s: Error requesting preferred fw %s.\n" + "Trying alternative firmware %s\n", pci_name(priv->pdev), + priv->fw_pref, priv->fw_alt); + rc = mwl8k_request_fw(priv, priv->fw_alt, &priv->fw_ucode, true); + if (rc) { + printk(KERN_ERR "%s: Error requesting alt fw %s\n", + pci_name(priv->pdev), priv->fw_alt); + return rc; + } + return 0; +} + +static int mwl8k_firmware_load_success(struct mwl8k_priv *priv); +static void mwl8k_fw_state_machine(const struct firmware *fw, void *context) +{ + struct mwl8k_priv *priv = context; + struct mwl8k_device_info *di = priv->device_info; + int rc; + + switch (priv->fw_state) { + case FW_STATE_INIT: + if (!fw) { + printk(KERN_ERR "%s: Error requesting helper fw %s\n", + pci_name(priv->pdev), di->helper_image); + goto fail; + } + priv->fw_helper = fw; + rc = mwl8k_request_fw(priv, priv->fw_pref, &priv->fw_ucode, + true); + if (rc && priv->fw_alt) { + rc = mwl8k_request_alt_fw(priv); + if (rc) + goto fail; + priv->fw_state = FW_STATE_LOADING_ALT; + } else if (rc) + goto fail; + else + priv->fw_state = FW_STATE_LOADING_PREF; + break; + + case FW_STATE_LOADING_PREF: + if (!fw) { + if (priv->fw_alt) { + rc = mwl8k_request_alt_fw(priv); + if (rc) + goto fail; + priv->fw_state = FW_STATE_LOADING_ALT; + } else + goto fail; + } else { + priv->fw_ucode = fw; + rc = mwl8k_firmware_load_success(priv); + if (rc) + goto fail; + else + complete(&priv->firmware_loading_complete); + } + break; + + case FW_STATE_LOADING_ALT: + if (!fw) { + printk(KERN_ERR "%s: Error requesting alt fw %s\n", + pci_name(priv->pdev), di->helper_image); + goto fail; + } + priv->fw_ucode = fw; + rc = mwl8k_firmware_load_success(priv); + if (rc) + goto fail; + else + complete(&priv->firmware_loading_complete); + break; + + default: + printk(KERN_ERR "%s: Unexpected firmware loading state: %d\n", + MWL8K_NAME, priv->fw_state); + BUG_ON(1); + } + + return; + +fail: + priv->fw_state = FW_STATE_ERROR; + complete(&priv->firmware_loading_complete); + device_release_driver(&priv->pdev->dev); + mwl8k_release_firmware(priv); +} + +#define MAX_RESTART_ATTEMPTS 1 +static int mwl8k_init_firmware(struct ieee80211_hw *hw, char *fw_image, + bool nowait) +{ + struct mwl8k_priv *priv = hw->priv; + int rc; + int count = MAX_RESTART_ATTEMPTS; + +retry: + /* Reset firmware and hardware */ + mwl8k_hw_reset(priv); + + /* Ask userland hotplug daemon for the device firmware */ + rc = mwl8k_request_firmware(priv, fw_image, nowait); + if (rc) { + wiphy_err(hw->wiphy, "Firmware files not found\n"); + return rc; + } + + if (nowait) + return rc; + + /* Load firmware into hardware */ + rc = mwl8k_load_firmware(hw); + if (rc) + wiphy_err(hw->wiphy, "Cannot start firmware\n"); + + /* Reclaim memory once firmware is successfully loaded */ + mwl8k_release_firmware(priv); + + if (rc && count) { + /* FW did not start successfully; + * lets try one more time + */ + count--; + wiphy_err(hw->wiphy, "Trying to reload the firmware again\n"); + msleep(20); + goto retry; + } + + return rc; +} + +static int mwl8k_init_txqs(struct ieee80211_hw *hw) +{ + struct mwl8k_priv *priv = hw->priv; + int rc = 0; + int i; + + for (i = 0; i < mwl8k_tx_queues(priv); i++) { + rc = mwl8k_txq_init(hw, i); + if (rc) + break; + if (priv->ap_fw) + iowrite32(priv->txq[i].txd_dma, + priv->sram + priv->txq_offset[i]); + } + return rc; +} + +/* initialize hw after successfully loading a firmware image */ +static int mwl8k_probe_hw(struct ieee80211_hw *hw) +{ + struct mwl8k_priv *priv = hw->priv; + int rc = 0; + int i; + + if (priv->ap_fw) { + priv->rxd_ops = priv->device_info->ap_rxd_ops; + if (priv->rxd_ops == NULL) { + wiphy_err(hw->wiphy, + "Driver does not have AP firmware image support for this hardware\n"); + rc = -ENOENT; + goto err_stop_firmware; + } + } else { + priv->rxd_ops = &rxd_sta_ops; + } + + priv->sniffer_enabled = false; + priv->wmm_enabled = false; + priv->pending_tx_pkts = 0; + atomic_set(&priv->watchdog_event_pending, 0); + + rc = mwl8k_rxq_init(hw, 0); + if (rc) + goto err_stop_firmware; + rxq_refill(hw, 0, INT_MAX); + + /* For the sta firmware, we need to know the dma addresses of tx queues + * before sending MWL8K_CMD_GET_HW_SPEC. So we must initialize them + * prior to issuing this command. But for the AP case, we learn the + * total number of queues from the result CMD_GET_HW_SPEC, so for this + * case we must initialize the tx queues after. + */ + priv->num_ampdu_queues = 0; + if (!priv->ap_fw) { + rc = mwl8k_init_txqs(hw); + if (rc) + goto err_free_queues; + } + + iowrite32(0, priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS); + iowrite32(0, priv->regs + MWL8K_HIU_A2H_INTERRUPT_MASK); + iowrite32(MWL8K_A2H_INT_TX_DONE|MWL8K_A2H_INT_RX_READY| + MWL8K_A2H_INT_BA_WATCHDOG, + priv->regs + MWL8K_HIU_A2H_INTERRUPT_CLEAR_SEL); + iowrite32(MWL8K_A2H_INT_OPC_DONE, + priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS_MASK); + + rc = request_irq(priv->pdev->irq, mwl8k_interrupt, + IRQF_SHARED, MWL8K_NAME, hw); + if (rc) { + wiphy_err(hw->wiphy, "failed to register IRQ handler\n"); + goto err_free_queues; + } + + /* + * When hw restart is requested, + * mac80211 will take care of clearing + * the ampdu streams, so do not clear + * the ampdu state here + */ + if (!priv->hw_restart_in_progress) + memset(priv->ampdu, 0, sizeof(priv->ampdu)); + + /* + * Temporarily enable interrupts. Initial firmware host + * commands use interrupts and avoid polling. Disable + * interrupts when done. + */ + iowrite32(MWL8K_A2H_EVENTS, priv->regs + MWL8K_HIU_A2H_INTERRUPT_MASK); + + /* Get config data, mac addrs etc */ + if (priv->ap_fw) { + rc = mwl8k_cmd_get_hw_spec_ap(hw); + if (!rc) + rc = mwl8k_init_txqs(hw); + if (!rc) + rc = mwl8k_cmd_set_hw_spec(hw); + } else { + rc = mwl8k_cmd_get_hw_spec_sta(hw); + } + if (rc) { + wiphy_err(hw->wiphy, "Cannot initialise firmware\n"); + goto err_free_irq; + } + + /* Turn radio off */ + rc = mwl8k_cmd_radio_disable(hw); + if (rc) { + wiphy_err(hw->wiphy, "Cannot disable\n"); + goto err_free_irq; + } + + /* Clear MAC address */ + rc = mwl8k_cmd_set_mac_addr(hw, NULL, "\x00\x00\x00\x00\x00\x00"); + if (rc) { + wiphy_err(hw->wiphy, "Cannot clear MAC address\n"); + goto err_free_irq; + } + + /* Configure Antennas */ + rc = mwl8k_cmd_rf_antenna(hw, MWL8K_RF_ANTENNA_RX, 0x3); + if (rc) + wiphy_warn(hw->wiphy, "failed to set # of RX antennas"); + rc = mwl8k_cmd_rf_antenna(hw, MWL8K_RF_ANTENNA_TX, 0x7); + if (rc) + wiphy_warn(hw->wiphy, "failed to set # of TX antennas"); + + + /* Disable interrupts */ + iowrite32(0, priv->regs + MWL8K_HIU_A2H_INTERRUPT_MASK); + free_irq(priv->pdev->irq, hw); + + wiphy_info(hw->wiphy, "%s v%d, %pm, %s firmware %u.%u.%u.%u\n", + priv->device_info->part_name, + priv->hw_rev, hw->wiphy->perm_addr, + priv->ap_fw ? "AP" : "STA", + (priv->fw_rev >> 24) & 0xff, (priv->fw_rev >> 16) & 0xff, + (priv->fw_rev >> 8) & 0xff, priv->fw_rev & 0xff); + + return 0; + +err_free_irq: + iowrite32(0, priv->regs + MWL8K_HIU_A2H_INTERRUPT_MASK); + free_irq(priv->pdev->irq, hw); + +err_free_queues: + for (i = 0; i < mwl8k_tx_queues(priv); i++) + mwl8k_txq_deinit(hw, i); + mwl8k_rxq_deinit(hw, 0); + +err_stop_firmware: + mwl8k_hw_reset(priv); + + return rc; +} + +/* + * invoke mwl8k_reload_firmware to change the firmware image after the device + * has already been registered + */ +static int mwl8k_reload_firmware(struct ieee80211_hw *hw, char *fw_image) +{ + int i, rc = 0; + struct mwl8k_priv *priv = hw->priv; + struct mwl8k_vif *vif, *tmp_vif; + + mwl8k_stop(hw); + mwl8k_rxq_deinit(hw, 0); + + /* + * All the existing interfaces are re-added by the ieee80211_reconfig; + * which means driver should remove existing interfaces before calling + * ieee80211_restart_hw + */ + if (priv->hw_restart_in_progress) + list_for_each_entry_safe(vif, tmp_vif, &priv->vif_list, list) + mwl8k_remove_vif(priv, vif); + + for (i = 0; i < mwl8k_tx_queues(priv); i++) + mwl8k_txq_deinit(hw, i); + + rc = mwl8k_init_firmware(hw, fw_image, false); + if (rc) + goto fail; + + rc = mwl8k_probe_hw(hw); + if (rc) + goto fail; + + if (priv->hw_restart_in_progress) + return rc; + + rc = mwl8k_start(hw); + if (rc) + goto fail; + + rc = mwl8k_config(hw, ~0); + if (rc) + goto fail; + + for (i = 0; i < MWL8K_TX_WMM_QUEUES; i++) { + rc = mwl8k_conf_tx(hw, NULL, i, &priv->wmm_params[i]); + if (rc) + goto fail; + } + + return rc; + +fail: + printk(KERN_WARNING "mwl8k: Failed to reload firmware image.\n"); + return rc; +} + +static const struct ieee80211_iface_limit ap_if_limits[] = { + { .max = 8, .types = BIT(NL80211_IFTYPE_AP) }, + { .max = 1, .types = BIT(NL80211_IFTYPE_STATION) }, +}; + +static const struct ieee80211_iface_combination ap_if_comb = { + .limits = ap_if_limits, + .n_limits = ARRAY_SIZE(ap_if_limits), + .max_interfaces = 8, + .num_different_channels = 1, +}; + + +static int mwl8k_firmware_load_success(struct mwl8k_priv *priv) +{ + struct ieee80211_hw *hw = priv->hw; + int i, rc; + + rc = mwl8k_load_firmware(hw); + mwl8k_release_firmware(priv); + if (rc) { + wiphy_err(hw->wiphy, "Cannot start firmware\n"); + return rc; + } + + /* + * Extra headroom is the size of the required DMA header + * minus the size of the smallest 802.11 frame (CTS frame). + */ + hw->extra_tx_headroom = + sizeof(struct mwl8k_dma_data) - sizeof(struct ieee80211_cts); + + hw->extra_tx_headroom -= priv->ap_fw ? REDUCED_TX_HEADROOM : 0; + + hw->channel_change_time = 10; + + hw->queues = MWL8K_TX_WMM_QUEUES; + + /* Set rssi values to dBm */ + hw->flags |= IEEE80211_HW_SIGNAL_DBM | IEEE80211_HW_HAS_RATE_CONTROL; + + /* + * Ask mac80211 to not to trigger PS mode + * based on PM bit of incoming frames. + */ + if (priv->ap_fw) + hw->flags |= IEEE80211_HW_AP_LINK_PS; + + hw->vif_data_size = sizeof(struct mwl8k_vif); + hw->sta_data_size = sizeof(struct mwl8k_sta); + + priv->macids_used = 0; + INIT_LIST_HEAD(&priv->vif_list); + + /* Set default radio state and preamble */ + priv->radio_on = false; + priv->radio_short_preamble = false; + + /* Finalize join worker */ + INIT_WORK(&priv->finalize_join_worker, mwl8k_finalize_join_worker); + /* Handle watchdog ba events */ + INIT_WORK(&priv->watchdog_ba_handle, mwl8k_watchdog_ba_events); + /* To reload the firmware if it crashes */ + INIT_WORK(&priv->fw_reload, mwl8k_hw_restart_work); + + /* TX reclaim and RX tasklets. */ + tasklet_init(&priv->poll_tx_task, mwl8k_tx_poll, (unsigned long)hw); + tasklet_disable(&priv->poll_tx_task); + tasklet_init(&priv->poll_rx_task, mwl8k_rx_poll, (unsigned long)hw); + tasklet_disable(&priv->poll_rx_task); + + /* Power management cookie */ + priv->cookie = pci_alloc_consistent(priv->pdev, 4, &priv->cookie_dma); + if (priv->cookie == NULL) + return -ENOMEM; + + mutex_init(&priv->fw_mutex); + priv->fw_mutex_owner = NULL; + priv->fw_mutex_depth = 0; + priv->hostcmd_wait = NULL; + + spin_lock_init(&priv->tx_lock); + + spin_lock_init(&priv->stream_lock); + + priv->tx_wait = NULL; + + rc = mwl8k_probe_hw(hw); + if (rc) + goto err_free_cookie; + + hw->wiphy->interface_modes = 0; + + if (priv->ap_macids_supported || priv->device_info->fw_image_ap) { + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_AP); + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_STATION); + hw->wiphy->iface_combinations = &ap_if_comb; + hw->wiphy->n_iface_combinations = 1; + } + + if (priv->sta_macids_supported || priv->device_info->fw_image_sta) + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_STATION); + + rc = ieee80211_register_hw(hw); + if (rc) { + wiphy_err(hw->wiphy, "Cannot register device\n"); + goto err_unprobe_hw; + } + + return 0; + +err_unprobe_hw: + for (i = 0; i < mwl8k_tx_queues(priv); i++) + mwl8k_txq_deinit(hw, i); + mwl8k_rxq_deinit(hw, 0); + +err_free_cookie: + if (priv->cookie != NULL) + pci_free_consistent(priv->pdev, 4, + priv->cookie, priv->cookie_dma); + + return rc; +} +static int mwl8k_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + static int printed_version; + struct ieee80211_hw *hw; + struct mwl8k_priv *priv; + struct mwl8k_device_info *di; + int rc; + + if (!printed_version) { + printk(KERN_INFO "%s version %s\n", MWL8K_DESC, MWL8K_VERSION); + printed_version = 1; + } + + + rc = pci_enable_device(pdev); + if (rc) { + printk(KERN_ERR "%s: Cannot enable new PCI device\n", + MWL8K_NAME); + return rc; + } + + rc = pci_request_regions(pdev, MWL8K_NAME); + if (rc) { + printk(KERN_ERR "%s: Cannot obtain PCI resources\n", + MWL8K_NAME); + goto err_disable_device; + } + + pci_set_master(pdev); + + + hw = ieee80211_alloc_hw(sizeof(*priv), &mwl8k_ops); + if (hw == NULL) { + printk(KERN_ERR "%s: ieee80211 alloc failed\n", MWL8K_NAME); + rc = -ENOMEM; + goto err_free_reg; + } + + SET_IEEE80211_DEV(hw, &pdev->dev); + pci_set_drvdata(pdev, hw); + + priv = hw->priv; + priv->hw = hw; + priv->pdev = pdev; + priv->device_info = &mwl8k_info_tbl[id->driver_data]; + + if (id->driver_data == MWL8764) + priv->is_8764 = true; + + priv->sram = pci_iomap(pdev, 0, 0x10000); + if (priv->sram == NULL) { + wiphy_err(hw->wiphy, "Cannot map device SRAM\n"); + rc = -EIO; + goto err_iounmap; + } + + /* + * If BAR0 is a 32 bit BAR, the register BAR will be BAR1. + * If BAR0 is a 64 bit BAR, the register BAR will be BAR2. + */ + priv->regs = pci_iomap(pdev, 1, 0x10000); + if (priv->regs == NULL) { + priv->regs = pci_iomap(pdev, 2, 0x10000); + if (priv->regs == NULL) { + wiphy_err(hw->wiphy, "Cannot map device registers\n"); + rc = -EIO; + goto err_iounmap; + } + } + + /* + * Choose the initial fw image depending on user input. If a second + * image is available, make it the alternative image that will be + * loaded if the first one fails. + */ + init_completion(&priv->firmware_loading_complete); + di = priv->device_info; + if (ap_mode_default && di->fw_image_ap) { + priv->fw_pref = di->fw_image_ap; + priv->fw_alt = di->fw_image_sta; + } else if (!ap_mode_default && di->fw_image_sta) { + priv->fw_pref = di->fw_image_sta; + priv->fw_alt = di->fw_image_ap; + } else if (ap_mode_default && !di->fw_image_ap && di->fw_image_sta) { + printk(KERN_WARNING "AP fw is unavailable. Using STA fw."); + priv->fw_pref = di->fw_image_sta; + } else if (!ap_mode_default && !di->fw_image_sta && di->fw_image_ap) { + printk(KERN_WARNING "STA fw is unavailable. Using AP fw."); + priv->fw_pref = di->fw_image_ap; + } + rc = mwl8k_init_firmware(hw, priv->fw_pref, true); + if (rc) + goto err_stop_firmware; + + priv->hw_restart_in_progress = false; + + priv->running_bsses = 0; + + return rc; + +err_stop_firmware: + mwl8k_hw_reset(priv); + +err_iounmap: + if (priv->regs != NULL) + pci_iounmap(pdev, priv->regs); + + if (priv->sram != NULL) + pci_iounmap(pdev, priv->sram); + + pci_set_drvdata(pdev, NULL); + ieee80211_free_hw(hw); + +err_free_reg: + pci_release_regions(pdev); + +err_disable_device: + pci_disable_device(pdev); + + return rc; +} + +static void mwl8k_remove(struct pci_dev *pdev) +{ + struct ieee80211_hw *hw = pci_get_drvdata(pdev); + struct mwl8k_priv *priv; + int i; + + if (hw == NULL) + return; + priv = hw->priv; + + wait_for_completion(&priv->firmware_loading_complete); + + if (priv->fw_state == FW_STATE_ERROR) { + mwl8k_hw_reset(priv); + goto unmap; + } + + ieee80211_stop_queues(hw); + + ieee80211_unregister_hw(hw); + + /* Remove TX reclaim and RX tasklets. */ + tasklet_kill(&priv->poll_tx_task); + tasklet_kill(&priv->poll_rx_task); + + /* Stop hardware */ + mwl8k_hw_reset(priv); + + /* Return all skbs to mac80211 */ + for (i = 0; i < mwl8k_tx_queues(priv); i++) + mwl8k_txq_reclaim(hw, i, INT_MAX, 1); + + for (i = 0; i < mwl8k_tx_queues(priv); i++) + mwl8k_txq_deinit(hw, i); + + mwl8k_rxq_deinit(hw, 0); + + pci_free_consistent(priv->pdev, 4, priv->cookie, priv->cookie_dma); + +unmap: + pci_iounmap(pdev, priv->regs); + pci_iounmap(pdev, priv->sram); + pci_set_drvdata(pdev, NULL); + ieee80211_free_hw(hw); + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +static struct pci_driver mwl8k_driver = { + .name = MWL8K_NAME, + .id_table = mwl8k_pci_id_table, + .probe = mwl8k_probe, + .remove = mwl8k_remove, +}; + +module_pci_driver(mwl8k_driver); + +MODULE_DESCRIPTION(MWL8K_DESC); +MODULE_VERSION(MWL8K_VERSION); +MODULE_AUTHOR("Lennert Buytenhek "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/orinoco/Kconfig b/drivers/net/wireless/orinoco/Kconfig new file mode 100644 index 0000000..e4f2098 --- /dev/null +++ b/drivers/net/wireless/orinoco/Kconfig @@ -0,0 +1,150 @@ +config HERMES + tristate "Hermes chipset 802.11b support (Orinoco/Prism2/Symbol)" + depends on m + depends on (PPC_PMAC || PCI || PCMCIA) + depends on CFG80211 && CFG80211_WEXT + depends on WIRELESS_EXT + depends on WEXT_SPY + depends on WEXT_PRIV + select BACKPORT_FW_LOADER + depends on CRYPTO + depends on CRYPTO_MICHAEL_MIC + ---help--- + A driver for 802.11b wireless cards based on the "Hermes" or + Intersil HFA384x (Prism 2) MAC controller. This includes the vast + majority of the PCMCIA 802.11b cards (which are nearly all rebadges) + - except for the Cisco/Aironet cards. Cards supported include the + Apple Airport (not a PCMCIA card), WavelanIEEE/Orinoco, + Cabletron/EnteraSys Roamabout, ELSA AirLancer, MELCO Buffalo, Avaya, + IBM High Rate Wireless, Farralon Syyline, Samsung MagicLAN, Netgear + MA401, LinkSys WPC-11, D-Link DWL-650, 3Com AirConnect, Intel + IPW2011, and Symbol Spectrum24 High Rate amongst others. + + This option includes the guts of the driver, but in order to + actually use a card you will also need to enable support for PCMCIA + Hermes cards, PLX9052 based PCI adaptors or the Apple Airport below. + + You will also very likely also need the Wireless Tools in order to + configure your card and that /etc/pcmcia/wireless.opts works : + + +config HERMES_PRISM + bool "Support Prism 2/2.5 chipset" + depends on HERMES + ---help--- + + Say Y to enable support for Prism 2 and 2.5 chipsets. These + chipsets are better handled by the hostap driver. This driver + would not support WPA or firmware download for Prism chipset. + + If you are not sure, say N. + +config HERMES_CACHE_FW_ON_INIT + bool "Cache Hermes firmware on driver initialisation" + depends on HERMES + default y + ---help--- + Say Y to cache any firmware required by the Hermes drivers + on startup. The firmware will remain cached until the + driver is unloaded. The cache uses 64K of RAM. + + Otherwise load the firmware from userspace as required. In + this case the driver should be unloaded and restarted + whenever the firmware is changed. + + If you are not sure, say Y. + +config APPLE_AIRPORT + tristate "Apple Airport support (built-in)" + depends on m + depends on PPC_PMAC && HERMES + help + Say Y here to support the Airport 802.11b wireless Ethernet hardware + built into the Macintosh iBook and other recent PowerPC-based + Macintosh machines. This is essentially a Lucent Orinoco card with + a non-standard interface. + + This driver does not support the Airport Extreme (802.11b/g). Use + the BCM43xx driver for Airport Extreme cards. + +config PLX_HERMES + tristate "Hermes in PLX9052 based PCI adaptor support (Netgear MA301 etc.)" + depends on m + depends on PCI && HERMES + help + Enable support for PCMCIA cards supported by the "Hermes" (aka + orinoco) driver when used in PLX9052 based PCI adaptors. These + adaptors are not a full PCMCIA controller but act as a more limited + PCI <-> PCMCIA bridge. Several vendors sell such adaptors so that + 802.11b PCMCIA cards can be used in desktop machines. The Netgear + MA301 is such an adaptor. + +config TMD_HERMES + tristate "Hermes in TMD7160 based PCI adaptor support" + depends on m + depends on PCI && HERMES + help + Enable support for PCMCIA cards supported by the "Hermes" (aka + orinoco) driver when used in TMD7160 based PCI adaptors. These + adaptors are not a full PCMCIA controller but act as a more limited + PCI <-> PCMCIA bridge. Several vendors sell such adaptors so that + 802.11b PCMCIA cards can be used in desktop machines. + +config NORTEL_HERMES + tristate "Nortel emobility PCI adaptor support" + depends on m + depends on PCI && HERMES + help + Enable support for PCMCIA cards supported by the "Hermes" (aka + orinoco) driver when used in Nortel emobility PCI adaptors. These + adaptors are not full PCMCIA controllers, but act as a more limited + PCI <-> PCMCIA bridge. + +config PCI_HERMES + tristate "Prism 2.5 PCI 802.11b adaptor support" + depends on m + depends on PCI && HERMES && HERMES_PRISM + help + Enable support for PCI and mini-PCI 802.11b wireless NICs based on + the Prism 2.5 chipset. These are true PCI cards, not the 802.11b + PCMCIA cards bundled with PCI<->PCMCIA adaptors which are also + common. Some of the built-in wireless adaptors in laptops are of + this variety. + +config PCMCIA_HERMES + tristate "Hermes PCMCIA card support" + depends on m + depends on PCMCIA && HERMES + ---help--- + A driver for "Hermes" chipset based PCMCIA wireless adaptors, such + as the Lucent WavelanIEEE/Orinoco cards and their OEM (Cabletron/ + EnteraSys RoamAbout 802.11, ELSA Airlancer, Melco Buffalo and + others). It should also be usable on various Prism II based cards + such as the Linksys, D-Link and Farallon Skyline. It should also + work on Symbol cards such as the 3Com AirConnect and Ericsson WLAN. + + You will very likely need the Wireless Tools in order to + configure your card and that /etc/pcmcia/wireless.opts works: + . + +config PCMCIA_SPECTRUM + tristate "Symbol Spectrum24 Trilogy PCMCIA card support" + depends on m + depends on PCMCIA && HERMES + ---help--- + + This is a driver for 802.11b cards using RAM-loadable Symbol + firmware, such as Symbol Wireless Networker LA4100, CompactFlash + cards by Socket Communications and Intel PRO/Wireless 2011B. + + This driver requires firmware download on startup. Utilities + for downloading Symbol firmware are available at + + +config ORINOCO_USB + tristate "Agere Orinoco USB support" + depends on m + depends on USB && HERMES + select BACKPORT_FW_LOADER + ---help--- + This driver is for USB versions of the Agere Orinoco card. diff --git a/drivers/net/wireless/orinoco/Makefile b/drivers/net/wireless/orinoco/Makefile new file mode 100644 index 0000000..4dfc699 --- /dev/null +++ b/drivers/net/wireless/orinoco/Makefile @@ -0,0 +1,17 @@ +# +# Makefile for the orinoco wireless device drivers. +# +orinoco-objs := main.o fw.o hw.o mic.o scan.o wext.o hermes_dld.o hermes.o cfg.o + +obj-$(CPTCFG_HERMES) += orinoco.o +obj-$(CPTCFG_PCMCIA_HERMES) += orinoco_cs.o +obj-$(CPTCFG_APPLE_AIRPORT) += airport.o +obj-$(CPTCFG_PLX_HERMES) += orinoco_plx.o +obj-$(CPTCFG_PCI_HERMES) += orinoco_pci.o +obj-$(CPTCFG_TMD_HERMES) += orinoco_tmd.o +obj-$(CPTCFG_NORTEL_HERMES) += orinoco_nortel.o +obj-$(CPTCFG_PCMCIA_SPECTRUM) += spectrum_cs.o +obj-$(CPTCFG_ORINOCO_USB) += orinoco_usb.o + +# Orinoco should be endian clean. +ccflags-y += -D__CHECK_ENDIAN__ diff --git a/drivers/net/wireless/orinoco/airport.c b/drivers/net/wireless/orinoco/airport.c new file mode 100644 index 0000000..0ca8b14 --- /dev/null +++ b/drivers/net/wireless/orinoco/airport.c @@ -0,0 +1,267 @@ +/* airport.c + * + * A driver for "Hermes" chipset based Apple Airport wireless + * card. + * + * Copyright notice & release notes in file main.c + * + * Note specific to airport stub: + * + * 0.05 : first version of the new split driver + * 0.06 : fix possible hang on powerup, add sleep support + */ + +#define DRIVER_NAME "airport" +#define PFX DRIVER_NAME ": " + +#include +#include +#include +#include +#include + +#include "orinoco.h" + +#define AIRPORT_IO_LEN (0x1000) /* one page */ + +struct airport { + struct macio_dev *mdev; + void __iomem *vaddr; + unsigned int irq; + int irq_requested; + int ndev_registered; +}; + +static int +airport_suspend(struct macio_dev *mdev, pm_message_t state) +{ + struct orinoco_private *priv = dev_get_drvdata(&mdev->ofdev.dev); + struct net_device *dev = priv->ndev; + struct airport *card = priv->card; + unsigned long flags; + int err; + + printk(KERN_DEBUG "%s: Airport entering sleep mode\n", dev->name); + + err = orinoco_lock(priv, &flags); + if (err) { + printk(KERN_ERR "%s: hw_unavailable on PBOOK_SLEEP_NOW\n", + dev->name); + return 0; + } + + orinoco_down(priv); + orinoco_unlock(priv, &flags); + + disable_irq(card->irq); + pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE, + macio_get_of_node(mdev), 0, 0); + + return 0; +} + +static int +airport_resume(struct macio_dev *mdev) +{ + struct orinoco_private *priv = dev_get_drvdata(&mdev->ofdev.dev); + struct net_device *dev = priv->ndev; + struct airport *card = priv->card; + unsigned long flags; + int err; + + printk(KERN_DEBUG "%s: Airport waking up\n", dev->name); + + pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE, + macio_get_of_node(mdev), 0, 1); + msleep(200); + + enable_irq(card->irq); + + priv->hw.ops->lock_irqsave(&priv->lock, &flags); + err = orinoco_up(priv); + priv->hw.ops->unlock_irqrestore(&priv->lock, &flags); + + return err; +} + +static int +airport_detach(struct macio_dev *mdev) +{ + struct orinoco_private *priv = dev_get_drvdata(&mdev->ofdev.dev); + struct airport *card = priv->card; + + if (card->ndev_registered) + orinoco_if_del(priv); + card->ndev_registered = 0; + + if (card->irq_requested) + free_irq(card->irq, priv); + card->irq_requested = 0; + + if (card->vaddr) + iounmap(card->vaddr); + card->vaddr = NULL; + + macio_release_resource(mdev, 0); + + pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE, + macio_get_of_node(mdev), 0, 0); + ssleep(1); + + macio_set_drvdata(mdev, NULL); + free_orinocodev(priv); + + return 0; +} + +static int airport_hard_reset(struct orinoco_private *priv) +{ + /* It would be nice to power cycle the Airport for a real hard + * reset, but for some reason although it appears to + * re-initialize properly, it falls in a screaming heap + * shortly afterwards. */ +#if 0 + struct airport *card = priv->card; + + /* Vitally important. If we don't do this it seems we get an + * interrupt somewhere during the power cycle, since + * hw_unavailable is already set it doesn't get ACKed, we get + * into an interrupt loop and the PMU decides to turn us + * off. */ + disable_irq(card->irq); + + pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE, + macio_get_of_node(card->mdev), 0, 0); + ssleep(1); + pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE, + macio_get_of_node(card->mdev), 0, 1); + ssleep(1); + + enable_irq(card->irq); + ssleep(1); +#endif + + return 0; +} + +static int +airport_attach(struct macio_dev *mdev, const struct of_device_id *match) +{ + struct orinoco_private *priv; + struct airport *card; + unsigned long phys_addr; + struct hermes *hw; + + if (macio_resource_count(mdev) < 1 || macio_irq_count(mdev) < 1) { + printk(KERN_ERR PFX "Wrong interrupt/addresses in OF tree\n"); + return -ENODEV; + } + + /* Allocate space for private device-specific data */ + priv = alloc_orinocodev(sizeof(*card), &mdev->ofdev.dev, + airport_hard_reset, NULL); + if (!priv) { + printk(KERN_ERR PFX "Cannot allocate network device\n"); + return -ENODEV; + } + card = priv->card; + + hw = &priv->hw; + card->mdev = mdev; + + if (macio_request_resource(mdev, 0, DRIVER_NAME)) { + printk(KERN_ERR PFX "can't request IO resource !\n"); + free_orinocodev(priv); + return -EBUSY; + } + + macio_set_drvdata(mdev, priv); + + /* Setup interrupts & base address */ + card->irq = macio_irq(mdev, 0); + phys_addr = macio_resource_start(mdev, 0); /* Physical address */ + printk(KERN_DEBUG PFX "Physical address %lx\n", phys_addr); + card->vaddr = ioremap(phys_addr, AIRPORT_IO_LEN); + if (!card->vaddr) { + printk(KERN_ERR PFX "ioremap() failed\n"); + goto failed; + } + + hermes_struct_init(hw, card->vaddr, HERMES_16BIT_REGSPACING); + + /* Power up card */ + pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE, + macio_get_of_node(mdev), 0, 1); + ssleep(1); + + /* Reset it before we get the interrupt */ + hw->ops->init(hw); + + if (request_irq(card->irq, orinoco_interrupt, 0, DRIVER_NAME, priv)) { + printk(KERN_ERR PFX "Couldn't get IRQ %d\n", card->irq); + goto failed; + } + card->irq_requested = 1; + + /* Initialise the main driver */ + if (orinoco_init(priv) != 0) { + printk(KERN_ERR PFX "orinoco_init() failed\n"); + goto failed; + } + + /* Register an interface with the stack */ + if (orinoco_if_add(priv, phys_addr, card->irq, NULL) != 0) { + printk(KERN_ERR PFX "orinoco_if_add() failed\n"); + goto failed; + } + card->ndev_registered = 1; + return 0; + failed: + airport_detach(mdev); + return -ENODEV; +} /* airport_attach */ + + +static char version[] __initdata = DRIVER_NAME " " DRIVER_VERSION + " (Benjamin Herrenschmidt )"; +MODULE_AUTHOR("Benjamin Herrenschmidt "); +MODULE_DESCRIPTION("Driver for the Apple Airport wireless card."); +MODULE_LICENSE("Dual MPL/GPL"); + +static struct of_device_id airport_match[] = { + { + .name = "radio", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, airport_match); + +static struct macio_driver airport_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = airport_match, + }, + .probe = airport_attach, + .remove = airport_detach, + .suspend = airport_suspend, + .resume = airport_resume, +}; + +static int __init +init_airport(void) +{ + printk(KERN_DEBUG "%s\n", version); + + return macio_register_driver(&airport_driver); +} + +static void __exit +exit_airport(void) +{ + macio_unregister_driver(&airport_driver); +} + +module_init(init_airport); +module_exit(exit_airport); diff --git a/drivers/net/wireless/orinoco/cfg.c b/drivers/net/wireless/orinoco/cfg.c new file mode 100644 index 0000000..d01edd2 --- /dev/null +++ b/drivers/net/wireless/orinoco/cfg.c @@ -0,0 +1,290 @@ +/* cfg80211 support + * + * See copyright notice in main.c + */ +#include +#include +#include "hw.h" +#include "main.h" +#include "orinoco.h" + +#include "cfg.h" + +/* Supported bitrates. Must agree with hw.c */ +static struct ieee80211_rate orinoco_rates[] = { + { .bitrate = 10 }, + { .bitrate = 20 }, + { .bitrate = 55 }, + { .bitrate = 110 }, +}; + +static const void * const orinoco_wiphy_privid = &orinoco_wiphy_privid; + +/* Called after orinoco_private is allocated. */ +void orinoco_wiphy_init(struct wiphy *wiphy) +{ + struct orinoco_private *priv = wiphy_priv(wiphy); + + wiphy->privid = orinoco_wiphy_privid; + + set_wiphy_dev(wiphy, priv->dev); +} + +/* Called after firmware is initialised */ +int orinoco_wiphy_register(struct wiphy *wiphy) +{ + struct orinoco_private *priv = wiphy_priv(wiphy); + int i, channels = 0; + + if (priv->firmware_type == FIRMWARE_TYPE_AGERE) + wiphy->max_scan_ssids = 1; + else + wiphy->max_scan_ssids = 0; + + wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION); + + /* TODO: should we set if we only have demo ad-hoc? + * (priv->has_port3) + */ + if (priv->has_ibss) + wiphy->interface_modes |= BIT(NL80211_IFTYPE_ADHOC); + + if (!priv->broken_monitor || force_monitor) + wiphy->interface_modes |= BIT(NL80211_IFTYPE_MONITOR); + + priv->band.bitrates = orinoco_rates; + priv->band.n_bitrates = ARRAY_SIZE(orinoco_rates); + + /* Only support channels allowed by the card EEPROM */ + for (i = 0; i < NUM_CHANNELS; i++) { + if (priv->channel_mask & (1 << i)) { + priv->channels[i].center_freq = + ieee80211_dsss_chan_to_freq(i + 1); + channels++; + } + } + priv->band.channels = priv->channels; + priv->band.n_channels = channels; + + wiphy->bands[IEEE80211_BAND_2GHZ] = &priv->band; + wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; + + i = 0; + if (priv->has_wep) { + priv->cipher_suites[i] = WLAN_CIPHER_SUITE_WEP40; + i++; + + if (priv->has_big_wep) { + priv->cipher_suites[i] = WLAN_CIPHER_SUITE_WEP104; + i++; + } + } + if (priv->has_wpa) { + priv->cipher_suites[i] = WLAN_CIPHER_SUITE_TKIP; + i++; + } + wiphy->cipher_suites = priv->cipher_suites; + wiphy->n_cipher_suites = i; + + wiphy->rts_threshold = priv->rts_thresh; + if (!priv->has_mwo) + wiphy->frag_threshold = priv->frag_thresh + 1; + wiphy->retry_short = priv->short_retry_limit; + wiphy->retry_long = priv->long_retry_limit; + + return wiphy_register(wiphy); +} + +static int orinoco_change_vif(struct wiphy *wiphy, struct net_device *dev, + enum nl80211_iftype type, u32 *flags, + struct vif_params *params) +{ + struct orinoco_private *priv = wiphy_priv(wiphy); + int err = 0; + unsigned long lock; + + if (orinoco_lock(priv, &lock) != 0) + return -EBUSY; + + switch (type) { + case NL80211_IFTYPE_ADHOC: + if (!priv->has_ibss && !priv->has_port3) + err = -EINVAL; + break; + + case NL80211_IFTYPE_STATION: + break; + + case NL80211_IFTYPE_MONITOR: + if (priv->broken_monitor && !force_monitor) { + wiphy_warn(wiphy, + "Monitor mode support is buggy in this firmware, not enabling\n"); + err = -EINVAL; + } + break; + + default: + err = -EINVAL; + } + + if (!err) { + priv->iw_mode = type; + set_port_type(priv); + err = orinoco_commit(priv); + } + + orinoco_unlock(priv, &lock); + + return err; +} + +static int orinoco_scan(struct wiphy *wiphy, + struct cfg80211_scan_request *request) +{ + struct orinoco_private *priv = wiphy_priv(wiphy); + int err; + + if (!request) + return -EINVAL; + + if (priv->scan_request && priv->scan_request != request) + return -EBUSY; + + priv->scan_request = request; + + err = orinoco_hw_trigger_scan(priv, request->ssids); + /* On error the we aren't processing the request */ + if (err) + priv->scan_request = NULL; + + return err; +} + +static int orinoco_set_monitor_channel(struct wiphy *wiphy, + struct cfg80211_chan_def *chandef) +{ + struct orinoco_private *priv = wiphy_priv(wiphy); + int err = 0; + unsigned long flags; + int channel; + + if (!chandef->chan) + return -EINVAL; + + if (cfg80211_get_chandef_type(chandef) != NL80211_CHAN_NO_HT) + return -EINVAL; + + if (chandef->chan->band != IEEE80211_BAND_2GHZ) + return -EINVAL; + + channel = ieee80211_freq_to_dsss_chan(chandef->chan->center_freq); + + if ((channel < 1) || (channel > NUM_CHANNELS) || + !(priv->channel_mask & (1 << (channel - 1)))) + return -EINVAL; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + priv->channel = channel; + if (priv->iw_mode == NL80211_IFTYPE_MONITOR) { + /* Fast channel change - no commit if successful */ + struct hermes *hw = &priv->hw; + err = hw->ops->cmd_wait(hw, HERMES_CMD_TEST | + HERMES_TEST_SET_CHANNEL, + channel, NULL); + } + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_set_wiphy_params(struct wiphy *wiphy, u32 changed) +{ + struct orinoco_private *priv = wiphy_priv(wiphy); + int frag_value = -1; + int rts_value = -1; + int err = 0; + + if (changed & WIPHY_PARAM_RETRY_SHORT) { + /* Setting short retry not supported */ + err = -EINVAL; + } + + if (changed & WIPHY_PARAM_RETRY_LONG) { + /* Setting long retry not supported */ + err = -EINVAL; + } + + if (changed & WIPHY_PARAM_FRAG_THRESHOLD) { + /* Set fragmentation */ + if (priv->has_mwo) { + if (wiphy->frag_threshold < 0) + frag_value = 0; + else { + printk(KERN_WARNING "%s: Fixed fragmentation " + "is not supported on this firmware. " + "Using MWO robust instead.\n", + priv->ndev->name); + frag_value = 1; + } + } else { + if (wiphy->frag_threshold < 0) + frag_value = 2346; + else if ((wiphy->frag_threshold < 257) || + (wiphy->frag_threshold > 2347)) + err = -EINVAL; + else + /* cfg80211 value is 257-2347 (odd only) + * orinoco rid has range 256-2346 (even only) */ + frag_value = wiphy->frag_threshold & ~0x1; + } + } + + if (changed & WIPHY_PARAM_RTS_THRESHOLD) { + /* Set RTS. + * + * Prism documentation suggests default of 2432, + * and a range of 0-3000. + * + * Current implementation uses 2347 as the default and + * the upper limit. + */ + + if (wiphy->rts_threshold < 0) + rts_value = 2347; + else if (wiphy->rts_threshold > 2347) + err = -EINVAL; + else + rts_value = wiphy->rts_threshold; + } + + if (!err) { + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + if (frag_value >= 0) { + if (priv->has_mwo) + priv->mwo_robust = frag_value; + else + priv->frag_thresh = frag_value; + } + if (rts_value >= 0) + priv->rts_thresh = rts_value; + + err = orinoco_commit(priv); + + orinoco_unlock(priv, &flags); + } + + return err; +} + +const struct cfg80211_ops orinoco_cfg_ops = { + .change_virtual_intf = orinoco_change_vif, + .set_monitor_channel = orinoco_set_monitor_channel, + .scan = orinoco_scan, + .set_wiphy_params = orinoco_set_wiphy_params, +}; diff --git a/drivers/net/wireless/orinoco/cfg.h b/drivers/net/wireless/orinoco/cfg.h new file mode 100644 index 0000000..3ddc96a --- /dev/null +++ b/drivers/net/wireless/orinoco/cfg.h @@ -0,0 +1,15 @@ +/* cfg80211 support. + * + * See copyright notice in main.c + */ +#ifndef ORINOCO_CFG_H +#define ORINOCO_CFG_H + +#include + +extern const struct cfg80211_ops orinoco_cfg_ops; + +void orinoco_wiphy_init(struct wiphy *wiphy); +int orinoco_wiphy_register(struct wiphy *wiphy); + +#endif /* ORINOCO_CFG_H */ diff --git a/drivers/net/wireless/orinoco/fw.c b/drivers/net/wireless/orinoco/fw.c new file mode 100644 index 0000000..e71e5e0 --- /dev/null +++ b/drivers/net/wireless/orinoco/fw.c @@ -0,0 +1,387 @@ +/* Firmware file reading and download helpers + * + * See copyright notice in main.c + */ +#include +#include +#include +#include +#include + +#include "hermes.h" +#include "hermes_dld.h" +#include "orinoco.h" + +#include "fw.h" + +/* End markers (for Symbol firmware only) */ +#define TEXT_END 0x1A /* End of text header */ + +struct fw_info { + char *pri_fw; + char *sta_fw; + char *ap_fw; + u32 pda_addr; + u16 pda_size; +}; + +static const struct fw_info orinoco_fw[] = { + { NULL, "agere_sta_fw.bin", "agere_ap_fw.bin", 0x00390000, 1000 }, + { NULL, "prism_sta_fw.bin", "prism_ap_fw.bin", 0, 1024 }, + { "symbol_sp24t_prim_fw", "symbol_sp24t_sec_fw", NULL, 0x00003100, 512 } +}; +MODULE_FIRMWARE("agere_sta_fw.bin"); +MODULE_FIRMWARE("agere_ap_fw.bin"); +MODULE_FIRMWARE("prism_sta_fw.bin"); +MODULE_FIRMWARE("prism_ap_fw.bin"); +MODULE_FIRMWARE("symbol_sp24t_prim_fw"); +MODULE_FIRMWARE("symbol_sp24t_sec_fw"); + +/* Structure used to access fields in FW + * Make sure LE decoding macros are used + */ +struct orinoco_fw_header { + char hdr_vers[6]; /* ASCII string for header version */ + __le16 headersize; /* Total length of header */ + __le32 entry_point; /* NIC entry point */ + __le32 blocks; /* Number of blocks to program */ + __le32 block_offset; /* Offset of block data from eof header */ + __le32 pdr_offset; /* Offset to PDR data from eof header */ + __le32 pri_offset; /* Offset to primary plug data */ + __le32 compat_offset; /* Offset to compatibility data*/ + char signature[0]; /* FW signature length headersize-20 */ +} __packed; + +/* Check the range of various header entries. Return a pointer to a + * description of the problem, or NULL if everything checks out. */ +static const char *validate_fw(const struct orinoco_fw_header *hdr, size_t len) +{ + u16 hdrsize; + + if (len < sizeof(*hdr)) + return "image too small"; + if (memcmp(hdr->hdr_vers, "HFW", 3) != 0) + return "format not recognised"; + + hdrsize = le16_to_cpu(hdr->headersize); + if (hdrsize > len) + return "bad headersize"; + if ((hdrsize + le32_to_cpu(hdr->block_offset)) > len) + return "bad block offset"; + if ((hdrsize + le32_to_cpu(hdr->pdr_offset)) > len) + return "bad PDR offset"; + if ((hdrsize + le32_to_cpu(hdr->pri_offset)) > len) + return "bad PRI offset"; + if ((hdrsize + le32_to_cpu(hdr->compat_offset)) > len) + return "bad compat offset"; + + /* TODO: consider adding a checksum or CRC to the firmware format */ + return NULL; +} + +#if defined(CPTCFG_HERMES_CACHE_FW_ON_INIT) || defined(CONFIG_PM_SLEEP) +static inline const struct firmware * +orinoco_cached_fw_get(struct orinoco_private *priv, bool primary) +{ + if (primary) + return priv->cached_pri_fw; + else + return priv->cached_fw; +} +#else +#define orinoco_cached_fw_get(priv, primary) (NULL) +#endif + +/* Download either STA or AP firmware into the card. */ +static int +orinoco_dl_firmware(struct orinoco_private *priv, + const struct fw_info *fw, + int ap) +{ + /* Plug Data Area (PDA) */ + __le16 *pda; + + struct hermes *hw = &priv->hw; + const struct firmware *fw_entry; + const struct orinoco_fw_header *hdr; + const unsigned char *first_block; + const void *end; + const char *firmware; + const char *fw_err; + struct device *dev = priv->dev; + int err = 0; + + pda = kzalloc(fw->pda_size, GFP_KERNEL); + if (!pda) + return -ENOMEM; + + if (ap) + firmware = fw->ap_fw; + else + firmware = fw->sta_fw; + + dev_dbg(dev, "Attempting to download firmware %s\n", firmware); + + /* Read current plug data */ + err = hw->ops->read_pda_h(hw, pda, fw->pda_addr, fw->pda_size); + dev_dbg(dev, "Read PDA returned %d\n", err); + if (err) + goto free; + + if (!orinoco_cached_fw_get(priv, false)) { + err = request_firmware(&fw_entry, firmware, priv->dev); + + if (err) { + dev_err(dev, "Cannot find firmware %s\n", firmware); + err = -ENOENT; + goto free; + } + } else + fw_entry = orinoco_cached_fw_get(priv, false); + + hdr = (const struct orinoco_fw_header *) fw_entry->data; + + fw_err = validate_fw(hdr, fw_entry->size); + if (fw_err) { + dev_warn(dev, "Invalid firmware image detected (%s). " + "Aborting download\n", fw_err); + err = -EINVAL; + goto abort; + } + + /* Enable aux port to allow programming */ + err = hw->ops->program_init(hw, le32_to_cpu(hdr->entry_point)); + dev_dbg(dev, "Program init returned %d\n", err); + if (err != 0) + goto abort; + + /* Program data */ + first_block = (fw_entry->data + + le16_to_cpu(hdr->headersize) + + le32_to_cpu(hdr->block_offset)); + end = fw_entry->data + fw_entry->size; + + err = hermes_program(hw, first_block, end); + dev_dbg(dev, "Program returned %d\n", err); + if (err != 0) + goto abort; + + /* Update production data */ + first_block = (fw_entry->data + + le16_to_cpu(hdr->headersize) + + le32_to_cpu(hdr->pdr_offset)); + + err = hermes_apply_pda_with_defaults(hw, first_block, end, pda, + &pda[fw->pda_size / sizeof(*pda)]); + dev_dbg(dev, "Apply PDA returned %d\n", err); + if (err) + goto abort; + + /* Tell card we've finished */ + err = hw->ops->program_end(hw); + dev_dbg(dev, "Program end returned %d\n", err); + if (err != 0) + goto abort; + + /* Check if we're running */ + dev_dbg(dev, "hermes_present returned %d\n", hermes_present(hw)); + +abort: + /* If we requested the firmware, release it. */ + if (!orinoco_cached_fw_get(priv, false)) + release_firmware(fw_entry); + +free: + kfree(pda); + return err; +} + +/* + * Process a firmware image - stop the card, load the firmware, reset + * the card and make sure it responds. For the secondary firmware take + * care of the PDA - read it and then write it on top of the firmware. + */ +static int +symbol_dl_image(struct orinoco_private *priv, const struct fw_info *fw, + const unsigned char *image, const void *end, + int secondary) +{ + struct hermes *hw = &priv->hw; + int ret = 0; + const unsigned char *ptr; + const unsigned char *first_block; + + /* Plug Data Area (PDA) */ + __le16 *pda = NULL; + + /* Binary block begins after the 0x1A marker */ + ptr = image; + while (*ptr++ != TEXT_END); + first_block = ptr; + + /* Read the PDA from EEPROM */ + if (secondary) { + pda = kzalloc(fw->pda_size, GFP_KERNEL); + if (!pda) + return -ENOMEM; + + ret = hw->ops->read_pda_h(hw, pda, fw->pda_addr, fw->pda_size); + if (ret) + goto free; + } + + /* Stop the firmware, so that it can be safely rewritten */ + if (priv->stop_fw) { + ret = priv->stop_fw(priv, 1); + if (ret) + goto free; + } + + /* Program the adapter with new firmware */ + ret = hermes_program(hw, first_block, end); + if (ret) + goto free; + + /* Write the PDA to the adapter */ + if (secondary) { + size_t len = hermes_blocks_length(first_block, end); + ptr = first_block + len; + ret = hermes_apply_pda(hw, ptr, end, pda, + &pda[fw->pda_size / sizeof(*pda)]); + kfree(pda); + if (ret) + return ret; + } + + /* Run the firmware */ + if (priv->stop_fw) { + ret = priv->stop_fw(priv, 0); + if (ret) + return ret; + } + + /* Reset hermes chip and make sure it responds */ + ret = hw->ops->init(hw); + + /* hermes_reset() should return 0 with the secondary firmware */ + if (secondary && ret != 0) + return -ENODEV; + + /* And this should work with any firmware */ + if (!hermes_present(hw)) + return -ENODEV; + + return 0; + +free: + kfree(pda); + return ret; +} + + +/* + * Download the firmware into the card, this also does a PCMCIA soft + * reset on the card, to make sure it's in a sane state. + */ +static int +symbol_dl_firmware(struct orinoco_private *priv, + const struct fw_info *fw) +{ + struct device *dev = priv->dev; + int ret; + const struct firmware *fw_entry; + + if (!orinoco_cached_fw_get(priv, true)) { + if (request_firmware(&fw_entry, fw->pri_fw, priv->dev) != 0) { + dev_err(dev, "Cannot find firmware: %s\n", fw->pri_fw); + return -ENOENT; + } + } else + fw_entry = orinoco_cached_fw_get(priv, true); + + /* Load primary firmware */ + ret = symbol_dl_image(priv, fw, fw_entry->data, + fw_entry->data + fw_entry->size, 0); + + if (!orinoco_cached_fw_get(priv, true)) + release_firmware(fw_entry); + if (ret) { + dev_err(dev, "Primary firmware download failed\n"); + return ret; + } + + if (!orinoco_cached_fw_get(priv, false)) { + if (request_firmware(&fw_entry, fw->sta_fw, priv->dev) != 0) { + dev_err(dev, "Cannot find firmware: %s\n", fw->sta_fw); + return -ENOENT; + } + } else + fw_entry = orinoco_cached_fw_get(priv, false); + + /* Load secondary firmware */ + ret = symbol_dl_image(priv, fw, fw_entry->data, + fw_entry->data + fw_entry->size, 1); + if (!orinoco_cached_fw_get(priv, false)) + release_firmware(fw_entry); + if (ret) + dev_err(dev, "Secondary firmware download failed\n"); + + return ret; +} + +int orinoco_download(struct orinoco_private *priv) +{ + int err = 0; + /* Reload firmware */ + switch (priv->firmware_type) { + case FIRMWARE_TYPE_AGERE: + /* case FIRMWARE_TYPE_INTERSIL: */ + err = orinoco_dl_firmware(priv, + &orinoco_fw[priv->firmware_type], 0); + break; + + case FIRMWARE_TYPE_SYMBOL: + err = symbol_dl_firmware(priv, + &orinoco_fw[priv->firmware_type]); + break; + case FIRMWARE_TYPE_INTERSIL: + break; + } + /* TODO: if we fail we probably need to reinitialise + * the driver */ + + return err; +} + +#if defined(CPTCFG_HERMES_CACHE_FW_ON_INIT) || defined(CONFIG_PM_SLEEP) +void orinoco_cache_fw(struct orinoco_private *priv, int ap) +{ + const struct firmware *fw_entry = NULL; + const char *pri_fw; + const char *fw; + + pri_fw = orinoco_fw[priv->firmware_type].pri_fw; + if (ap) + fw = orinoco_fw[priv->firmware_type].ap_fw; + else + fw = orinoco_fw[priv->firmware_type].sta_fw; + + if (pri_fw) { + if (request_firmware(&fw_entry, pri_fw, priv->dev) == 0) + priv->cached_pri_fw = fw_entry; + } + + if (fw) { + if (request_firmware(&fw_entry, fw, priv->dev) == 0) + priv->cached_fw = fw_entry; + } +} + +void orinoco_uncache_fw(struct orinoco_private *priv) +{ + release_firmware(priv->cached_pri_fw); + release_firmware(priv->cached_fw); + priv->cached_pri_fw = NULL; + priv->cached_fw = NULL; +} +#endif diff --git a/drivers/net/wireless/orinoco/fw.h b/drivers/net/wireless/orinoco/fw.h new file mode 100644 index 0000000..2ed0247 --- /dev/null +++ b/drivers/net/wireless/orinoco/fw.h @@ -0,0 +1,21 @@ +/* Firmware file reading and download helpers + * + * See copyright notice in main.c + */ +#ifndef _ORINOCO_FW_H_ +#define _ORINOCO_FW_H_ + +/* Forward declations */ +struct orinoco_private; + +int orinoco_download(struct orinoco_private *priv); + +#if defined(CPTCFG_HERMES_CACHE_FW_ON_INIT) || defined(CONFIG_PM_SLEEP) +void orinoco_cache_fw(struct orinoco_private *priv, int ap); +void orinoco_uncache_fw(struct orinoco_private *priv); +#else +#define orinoco_cache_fw(priv, ap) do { } while (0) +#define orinoco_uncache_fw(priv) do { } while (0) +#endif + +#endif /* _ORINOCO_FW_H_ */ diff --git a/drivers/net/wireless/orinoco/hermes.c b/drivers/net/wireless/orinoco/hermes.c new file mode 100644 index 0000000..bb1243c --- /dev/null +++ b/drivers/net/wireless/orinoco/hermes.c @@ -0,0 +1,778 @@ +/* hermes.c + * + * Driver core for the "Hermes" wireless MAC controller, as used in + * the Lucent Orinoco and Cabletron RoamAbout cards. It should also + * work on the hfa3841 and hfa3842 MAC controller chips used in the + * Prism II chipsets. + * + * This is not a complete driver, just low-level access routines for + * the MAC controller itself. + * + * Based on the prism2 driver from Absolute Value Systems' linux-wlan + * project, the Linux wvlan_cs driver, Lucent's HCF-Light + * (wvlan_hcf.c) library, and the NetBSD wireless driver (in no + * particular order). + * + * Copyright (C) 2000, David Gibson, Linuxcare Australia. + * (C) Copyright David Gibson, IBM Corp. 2001-2003. + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#include +#include +#include +#include + +#include "hermes.h" + +/* These are maximum timeouts. Most often, card wil react much faster */ +#define CMD_BUSY_TIMEOUT (100) /* In iterations of ~1us */ +#define CMD_INIT_TIMEOUT (50000) /* in iterations of ~10us */ +#define CMD_COMPL_TIMEOUT (20000) /* in iterations of ~10us */ +#define ALLOC_COMPL_TIMEOUT (1000) /* in iterations of ~10us */ + +/* + * AUX port access. To unlock the AUX port write the access keys to the + * PARAM0-2 registers, then write HERMES_AUX_ENABLE to the HERMES_CONTROL + * register. Then read it and make sure it's HERMES_AUX_ENABLED. + */ +#define HERMES_AUX_ENABLE 0x8000 /* Enable auxiliary port access */ +#define HERMES_AUX_DISABLE 0x4000 /* Disable to auxiliary port access */ +#define HERMES_AUX_ENABLED 0xC000 /* Auxiliary port is open */ +#define HERMES_AUX_DISABLED 0x0000 /* Auxiliary port is closed */ + +#define HERMES_AUX_PW0 0xFE01 +#define HERMES_AUX_PW1 0xDC23 +#define HERMES_AUX_PW2 0xBA45 + +/* HERMES_CMD_DOWNLD */ +#define HERMES_PROGRAM_DISABLE (0x0000 | HERMES_CMD_DOWNLD) +#define HERMES_PROGRAM_ENABLE_VOLATILE (0x0100 | HERMES_CMD_DOWNLD) +#define HERMES_PROGRAM_ENABLE_NON_VOLATILE (0x0200 | HERMES_CMD_DOWNLD) +#define HERMES_PROGRAM_NON_VOLATILE (0x0300 | HERMES_CMD_DOWNLD) + +/* + * Debugging helpers + */ + +#define DMSG(stuff...) do {printk(KERN_DEBUG "hermes @ %p: " , hw->iobase); \ + printk(stuff); } while (0) + +#undef HERMES_DEBUG +#ifdef HERMES_DEBUG +#include + +#define DEBUG(lvl, stuff...) if ((lvl) <= HERMES_DEBUG) DMSG(stuff) + +#else /* ! HERMES_DEBUG */ + +#define DEBUG(lvl, stuff...) do { } while (0) + +#endif /* ! HERMES_DEBUG */ + +static const struct hermes_ops hermes_ops_local; + +/* + * Internal functions + */ + +/* Issue a command to the chip. Waiting for it to complete is the caller's + problem. + + Returns -EBUSY if the command register is busy, 0 on success. + + Callable from any context. +*/ +static int hermes_issue_cmd(struct hermes *hw, u16 cmd, u16 param0, + u16 param1, u16 param2) +{ + int k = CMD_BUSY_TIMEOUT; + u16 reg; + + /* First wait for the command register to unbusy */ + reg = hermes_read_regn(hw, CMD); + while ((reg & HERMES_CMD_BUSY) && k) { + k--; + udelay(1); + reg = hermes_read_regn(hw, CMD); + } + if (reg & HERMES_CMD_BUSY) + return -EBUSY; + + hermes_write_regn(hw, PARAM2, param2); + hermes_write_regn(hw, PARAM1, param1); + hermes_write_regn(hw, PARAM0, param0); + hermes_write_regn(hw, CMD, cmd); + + return 0; +} + +/* + * Function definitions + */ + +/* For doing cmds that wipe the magic constant in SWSUPPORT0 */ +static int hermes_doicmd_wait(struct hermes *hw, u16 cmd, + u16 parm0, u16 parm1, u16 parm2, + struct hermes_response *resp) +{ + int err = 0; + int k; + u16 status, reg; + + err = hermes_issue_cmd(hw, cmd, parm0, parm1, parm2); + if (err) + return err; + + reg = hermes_read_regn(hw, EVSTAT); + k = CMD_INIT_TIMEOUT; + while ((!(reg & HERMES_EV_CMD)) && k) { + k--; + udelay(10); + reg = hermes_read_regn(hw, EVSTAT); + } + + hermes_write_regn(hw, SWSUPPORT0, HERMES_MAGIC); + + if (!hermes_present(hw)) { + DEBUG(0, "hermes @ 0x%x: Card removed during reset.\n", + hw->iobase); + err = -ENODEV; + goto out; + } + + if (!(reg & HERMES_EV_CMD)) { + printk(KERN_ERR "hermes @ %p: " + "Timeout waiting for card to reset (reg=0x%04x)!\n", + hw->iobase, reg); + err = -ETIMEDOUT; + goto out; + } + + status = hermes_read_regn(hw, STATUS); + if (resp) { + resp->status = status; + resp->resp0 = hermes_read_regn(hw, RESP0); + resp->resp1 = hermes_read_regn(hw, RESP1); + resp->resp2 = hermes_read_regn(hw, RESP2); + } + + hermes_write_regn(hw, EVACK, HERMES_EV_CMD); + + if (status & HERMES_STATUS_RESULT) + err = -EIO; +out: + return err; +} + +void hermes_struct_init(struct hermes *hw, void __iomem *address, + int reg_spacing) +{ + hw->iobase = address; + hw->reg_spacing = reg_spacing; + hw->inten = 0x0; + hw->eeprom_pda = false; + hw->ops = &hermes_ops_local; +} +EXPORT_SYMBOL(hermes_struct_init); + +static int hermes_init(struct hermes *hw) +{ + u16 reg; + int err = 0; + int k; + + /* We don't want to be interrupted while resetting the chipset */ + hw->inten = 0x0; + hermes_write_regn(hw, INTEN, 0); + hermes_write_regn(hw, EVACK, 0xffff); + + /* Normally it's a "can't happen" for the command register to + be busy when we go to issue a command because we are + serializing all commands. However we want to have some + chance of resetting the card even if it gets into a stupid + state, so we actually wait to see if the command register + will unbusy itself here. */ + k = CMD_BUSY_TIMEOUT; + reg = hermes_read_regn(hw, CMD); + while (k && (reg & HERMES_CMD_BUSY)) { + if (reg == 0xffff) /* Special case - the card has probably been + removed, so don't wait for the timeout */ + return -ENODEV; + + k--; + udelay(1); + reg = hermes_read_regn(hw, CMD); + } + + /* No need to explicitly handle the timeout - if we've timed + out hermes_issue_cmd() will probably return -EBUSY below */ + + /* According to the documentation, EVSTAT may contain + obsolete event occurrence information. We have to acknowledge + it by writing EVACK. */ + reg = hermes_read_regn(hw, EVSTAT); + hermes_write_regn(hw, EVACK, reg); + + /* We don't use hermes_docmd_wait here, because the reset wipes + the magic constant in SWSUPPORT0 away, and it gets confused */ + err = hermes_doicmd_wait(hw, HERMES_CMD_INIT, 0, 0, 0, NULL); + + return err; +} + +/* Issue a command to the chip, and (busy!) wait for it to + * complete. + * + * Returns: + * < 0 on internal error + * 0 on success + * > 0 on error returned by the firmware + * + * Callable from any context, but locking is your problem. */ +static int hermes_docmd_wait(struct hermes *hw, u16 cmd, u16 parm0, + struct hermes_response *resp) +{ + int err; + int k; + u16 reg; + u16 status; + + err = hermes_issue_cmd(hw, cmd, parm0, 0, 0); + if (err) { + if (!hermes_present(hw)) { + if (net_ratelimit()) + printk(KERN_WARNING "hermes @ %p: " + "Card removed while issuing command " + "0x%04x.\n", hw->iobase, cmd); + err = -ENODEV; + } else + if (net_ratelimit()) + printk(KERN_ERR "hermes @ %p: " + "Error %d issuing command 0x%04x.\n", + hw->iobase, err, cmd); + goto out; + } + + reg = hermes_read_regn(hw, EVSTAT); + k = CMD_COMPL_TIMEOUT; + while ((!(reg & HERMES_EV_CMD)) && k) { + k--; + udelay(10); + reg = hermes_read_regn(hw, EVSTAT); + } + + if (!hermes_present(hw)) { + printk(KERN_WARNING "hermes @ %p: Card removed " + "while waiting for command 0x%04x completion.\n", + hw->iobase, cmd); + err = -ENODEV; + goto out; + } + + if (!(reg & HERMES_EV_CMD)) { + printk(KERN_ERR "hermes @ %p: Timeout waiting for " + "command 0x%04x completion.\n", hw->iobase, cmd); + err = -ETIMEDOUT; + goto out; + } + + status = hermes_read_regn(hw, STATUS); + if (resp) { + resp->status = status; + resp->resp0 = hermes_read_regn(hw, RESP0); + resp->resp1 = hermes_read_regn(hw, RESP1); + resp->resp2 = hermes_read_regn(hw, RESP2); + } + + hermes_write_regn(hw, EVACK, HERMES_EV_CMD); + + if (status & HERMES_STATUS_RESULT) + err = -EIO; + + out: + return err; +} + +static int hermes_allocate(struct hermes *hw, u16 size, u16 *fid) +{ + int err = 0; + int k; + u16 reg; + + if ((size < HERMES_ALLOC_LEN_MIN) || (size > HERMES_ALLOC_LEN_MAX)) + return -EINVAL; + + err = hermes_docmd_wait(hw, HERMES_CMD_ALLOC, size, NULL); + if (err) + return err; + + reg = hermes_read_regn(hw, EVSTAT); + k = ALLOC_COMPL_TIMEOUT; + while ((!(reg & HERMES_EV_ALLOC)) && k) { + k--; + udelay(10); + reg = hermes_read_regn(hw, EVSTAT); + } + + if (!hermes_present(hw)) { + printk(KERN_WARNING "hermes @ %p: " + "Card removed waiting for frame allocation.\n", + hw->iobase); + return -ENODEV; + } + + if (!(reg & HERMES_EV_ALLOC)) { + printk(KERN_ERR "hermes @ %p: " + "Timeout waiting for frame allocation\n", + hw->iobase); + return -ETIMEDOUT; + } + + *fid = hermes_read_regn(hw, ALLOCFID); + hermes_write_regn(hw, EVACK, HERMES_EV_ALLOC); + + return 0; +} + +/* Set up a BAP to read a particular chunk of data from card's internal buffer. + * + * Returns: + * < 0 on internal failure (errno) + * 0 on success + * > 0 on error + * from firmware + * + * Callable from any context */ +static int hermes_bap_seek(struct hermes *hw, int bap, u16 id, u16 offset) +{ + int sreg = bap ? HERMES_SELECT1 : HERMES_SELECT0; + int oreg = bap ? HERMES_OFFSET1 : HERMES_OFFSET0; + int k; + u16 reg; + + /* Paranoia.. */ + if ((offset > HERMES_BAP_OFFSET_MAX) || (offset % 2)) + return -EINVAL; + + k = HERMES_BAP_BUSY_TIMEOUT; + reg = hermes_read_reg(hw, oreg); + while ((reg & HERMES_OFFSET_BUSY) && k) { + k--; + udelay(1); + reg = hermes_read_reg(hw, oreg); + } + + if (reg & HERMES_OFFSET_BUSY) + return -ETIMEDOUT; + + /* Now we actually set up the transfer */ + hermes_write_reg(hw, sreg, id); + hermes_write_reg(hw, oreg, offset); + + /* Wait for the BAP to be ready */ + k = HERMES_BAP_BUSY_TIMEOUT; + reg = hermes_read_reg(hw, oreg); + while ((reg & (HERMES_OFFSET_BUSY | HERMES_OFFSET_ERR)) && k) { + k--; + udelay(1); + reg = hermes_read_reg(hw, oreg); + } + + if (reg != offset) { + printk(KERN_ERR "hermes @ %p: BAP%d offset %s: " + "reg=0x%x id=0x%x offset=0x%x\n", hw->iobase, bap, + (reg & HERMES_OFFSET_BUSY) ? "timeout" : "error", + reg, id, offset); + + if (reg & HERMES_OFFSET_BUSY) + return -ETIMEDOUT; + + return -EIO; /* error or wrong offset */ + } + + return 0; +} + +/* Read a block of data from the chip's buffer, via the + * BAP. Synchronization/serialization is the caller's problem. len + * must be even. + * + * Returns: + * < 0 on internal failure (errno) + * 0 on success + * > 0 on error from firmware + */ +static int hermes_bap_pread(struct hermes *hw, int bap, void *buf, int len, + u16 id, u16 offset) +{ + int dreg = bap ? HERMES_DATA1 : HERMES_DATA0; + int err = 0; + + if ((len < 0) || (len % 2)) + return -EINVAL; + + err = hermes_bap_seek(hw, bap, id, offset); + if (err) + goto out; + + /* Actually do the transfer */ + hermes_read_words(hw, dreg, buf, len / 2); + + out: + return err; +} + +/* Write a block of data to the chip's buffer, via the + * BAP. Synchronization/serialization is the caller's problem. + * + * Returns: + * < 0 on internal failure (errno) + * 0 on success + * > 0 on error from firmware + */ +static int hermes_bap_pwrite(struct hermes *hw, int bap, const void *buf, + int len, u16 id, u16 offset) +{ + int dreg = bap ? HERMES_DATA1 : HERMES_DATA0; + int err = 0; + + if (len < 0) + return -EINVAL; + + err = hermes_bap_seek(hw, bap, id, offset); + if (err) + goto out; + + /* Actually do the transfer */ + hermes_write_bytes(hw, dreg, buf, len); + + out: + return err; +} + +/* Read a Length-Type-Value record from the card. + * + * If length is NULL, we ignore the length read from the card, and + * read the entire buffer regardless. This is useful because some of + * the configuration records appear to have incorrect lengths in + * practice. + * + * Callable from user or bh context. */ +static int hermes_read_ltv(struct hermes *hw, int bap, u16 rid, + unsigned bufsize, u16 *length, void *buf) +{ + int err = 0; + int dreg = bap ? HERMES_DATA1 : HERMES_DATA0; + u16 rlength, rtype; + unsigned nwords; + + if (bufsize % 2) + return -EINVAL; + + err = hermes_docmd_wait(hw, HERMES_CMD_ACCESS, rid, NULL); + if (err) + return err; + + err = hermes_bap_seek(hw, bap, rid, 0); + if (err) + return err; + + rlength = hermes_read_reg(hw, dreg); + + if (!rlength) + return -ENODATA; + + rtype = hermes_read_reg(hw, dreg); + + if (length) + *length = rlength; + + if (rtype != rid) + printk(KERN_WARNING "hermes @ %p: %s(): " + "rid (0x%04x) does not match type (0x%04x)\n", + hw->iobase, __func__, rid, rtype); + if (HERMES_RECLEN_TO_BYTES(rlength) > bufsize) + printk(KERN_WARNING "hermes @ %p: " + "Truncating LTV record from %d to %d bytes. " + "(rid=0x%04x, len=0x%04x)\n", hw->iobase, + HERMES_RECLEN_TO_BYTES(rlength), bufsize, rid, rlength); + + nwords = min((unsigned)rlength - 1, bufsize / 2); + hermes_read_words(hw, dreg, buf, nwords); + + return 0; +} + +static int hermes_write_ltv(struct hermes *hw, int bap, u16 rid, + u16 length, const void *value) +{ + int dreg = bap ? HERMES_DATA1 : HERMES_DATA0; + int err = 0; + unsigned count; + + if (length == 0) + return -EINVAL; + + err = hermes_bap_seek(hw, bap, rid, 0); + if (err) + return err; + + hermes_write_reg(hw, dreg, length); + hermes_write_reg(hw, dreg, rid); + + count = length - 1; + + hermes_write_bytes(hw, dreg, value, count << 1); + + err = hermes_docmd_wait(hw, HERMES_CMD_ACCESS | HERMES_CMD_WRITE, + rid, NULL); + + return err; +} + +/*** Hermes AUX control ***/ + +static inline void +hermes_aux_setaddr(struct hermes *hw, u32 addr) +{ + hermes_write_reg(hw, HERMES_AUXPAGE, (u16) (addr >> 7)); + hermes_write_reg(hw, HERMES_AUXOFFSET, (u16) (addr & 0x7F)); +} + +static inline int +hermes_aux_control(struct hermes *hw, int enabled) +{ + int desired_state = enabled ? HERMES_AUX_ENABLED : HERMES_AUX_DISABLED; + int action = enabled ? HERMES_AUX_ENABLE : HERMES_AUX_DISABLE; + int i; + + /* Already open? */ + if (hermes_read_reg(hw, HERMES_CONTROL) == desired_state) + return 0; + + hermes_write_reg(hw, HERMES_PARAM0, HERMES_AUX_PW0); + hermes_write_reg(hw, HERMES_PARAM1, HERMES_AUX_PW1); + hermes_write_reg(hw, HERMES_PARAM2, HERMES_AUX_PW2); + hermes_write_reg(hw, HERMES_CONTROL, action); + + for (i = 0; i < 20; i++) { + udelay(10); + if (hermes_read_reg(hw, HERMES_CONTROL) == + desired_state) + return 0; + } + + return -EBUSY; +} + +/*** Hermes programming ***/ + +/* About to start programming data (Hermes I) + * offset is the entry point + * + * Spectrum_cs' Symbol fw does not require this + * wl_lkm Agere fw does + * Don't know about intersil + */ +static int hermesi_program_init(struct hermes *hw, u32 offset) +{ + int err; + + /* Disable interrupts?*/ + /*hw->inten = 0x0;*/ + /*hermes_write_regn(hw, INTEN, 0);*/ + /*hermes_set_irqmask(hw, 0);*/ + + /* Acknowledge any outstanding command */ + hermes_write_regn(hw, EVACK, 0xFFFF); + + /* Using init_cmd_wait rather than cmd_wait */ + err = hw->ops->init_cmd_wait(hw, + 0x0100 | HERMES_CMD_INIT, + 0, 0, 0, NULL); + if (err) + return err; + + err = hw->ops->init_cmd_wait(hw, + 0x0000 | HERMES_CMD_INIT, + 0, 0, 0, NULL); + if (err) + return err; + + err = hermes_aux_control(hw, 1); + pr_debug("AUX enable returned %d\n", err); + + if (err) + return err; + + pr_debug("Enabling volatile, EP 0x%08x\n", offset); + err = hw->ops->init_cmd_wait(hw, + HERMES_PROGRAM_ENABLE_VOLATILE, + offset & 0xFFFFu, + offset >> 16, + 0, + NULL); + pr_debug("PROGRAM_ENABLE returned %d\n", err); + + return err; +} + +/* Done programming data (Hermes I) + * + * Spectrum_cs' Symbol fw does not require this + * wl_lkm Agere fw does + * Don't know about intersil + */ +static int hermesi_program_end(struct hermes *hw) +{ + struct hermes_response resp; + int rc = 0; + int err; + + rc = hw->ops->cmd_wait(hw, HERMES_PROGRAM_DISABLE, 0, &resp); + + pr_debug("PROGRAM_DISABLE returned %d, " + "r0 0x%04x, r1 0x%04x, r2 0x%04x\n", + rc, resp.resp0, resp.resp1, resp.resp2); + + if ((rc == 0) && + ((resp.status & HERMES_STATUS_CMDCODE) != HERMES_CMD_DOWNLD)) + rc = -EIO; + + err = hermes_aux_control(hw, 0); + pr_debug("AUX disable returned %d\n", err); + + /* Acknowledge any outstanding command */ + hermes_write_regn(hw, EVACK, 0xFFFF); + + /* Reinitialise, ignoring return */ + (void) hw->ops->init_cmd_wait(hw, 0x0000 | HERMES_CMD_INIT, + 0, 0, 0, NULL); + + return rc ? rc : err; +} + +static int hermes_program_bytes(struct hermes *hw, const char *data, + u32 addr, u32 len) +{ + /* wl lkm splits the programming into chunks of 2000 bytes. + * This restriction appears to come from USB. The PCMCIA + * adapters can program the whole lot in one go */ + hermes_aux_setaddr(hw, addr); + hermes_write_bytes(hw, HERMES_AUXDATA, data, len); + return 0; +} + +/* Read PDA from the adapter */ +static int hermes_read_pda(struct hermes *hw, __le16 *pda, u32 pda_addr, + u16 pda_len) +{ + int ret; + u16 pda_size; + u16 data_len = pda_len; + __le16 *data = pda; + + if (hw->eeprom_pda) { + /* PDA of spectrum symbol is in eeprom */ + + /* Issue command to read EEPROM */ + ret = hw->ops->cmd_wait(hw, HERMES_CMD_READMIF, 0, NULL); + if (ret) + return ret; + } else { + /* wl_lkm does not include PDA size in the PDA area. + * We will pad the information into pda, so other routines + * don't have to be modified */ + pda[0] = cpu_to_le16(pda_len - 2); + /* Includes CFG_PROD_DATA but not itself */ + pda[1] = cpu_to_le16(0x0800); /* CFG_PROD_DATA */ + data_len = pda_len - 4; + data = pda + 2; + } + + /* Open auxiliary port */ + ret = hermes_aux_control(hw, 1); + pr_debug("AUX enable returned %d\n", ret); + if (ret) + return ret; + + /* Read PDA */ + hermes_aux_setaddr(hw, pda_addr); + hermes_read_words(hw, HERMES_AUXDATA, data, data_len / 2); + + /* Close aux port */ + ret = hermes_aux_control(hw, 0); + pr_debug("AUX disable returned %d\n", ret); + + /* Check PDA length */ + pda_size = le16_to_cpu(pda[0]); + pr_debug("Actual PDA length %d, Max allowed %d\n", + pda_size, pda_len); + if (pda_size > pda_len) + return -EINVAL; + + return 0; +} + +static void hermes_lock_irqsave(spinlock_t *lock, + unsigned long *flags) __acquires(lock) +{ + spin_lock_irqsave(lock, *flags); +} + +static void hermes_unlock_irqrestore(spinlock_t *lock, + unsigned long *flags) __releases(lock) +{ + spin_unlock_irqrestore(lock, *flags); +} + +static void hermes_lock_irq(spinlock_t *lock) __acquires(lock) +{ + spin_lock_irq(lock); +} + +static void hermes_unlock_irq(spinlock_t *lock) __releases(lock) +{ + spin_unlock_irq(lock); +} + +/* Hermes operations for local buses */ +static const struct hermes_ops hermes_ops_local = { + .init = hermes_init, + .cmd_wait = hermes_docmd_wait, + .init_cmd_wait = hermes_doicmd_wait, + .allocate = hermes_allocate, + .read_ltv = hermes_read_ltv, + .write_ltv = hermes_write_ltv, + .bap_pread = hermes_bap_pread, + .bap_pwrite = hermes_bap_pwrite, + .read_pda_h = hermes_read_pda, + .program_init = hermesi_program_init, + .program_end = hermesi_program_end, + .program = hermes_program_bytes, + .lock_irqsave = hermes_lock_irqsave, + .unlock_irqrestore = hermes_unlock_irqrestore, + .lock_irq = hermes_lock_irq, + .unlock_irq = hermes_unlock_irq, +}; diff --git a/drivers/net/wireless/orinoco/hermes.h b/drivers/net/wireless/orinoco/hermes.h new file mode 100644 index 0000000..0a6e7c6 --- /dev/null +++ b/drivers/net/wireless/orinoco/hermes.h @@ -0,0 +1,520 @@ +/* hermes.h + * + * Driver core for the "Hermes" wireless MAC controller, as used in + * the Lucent Orinoco and Cabletron RoamAbout cards. It should also + * work on the hfa3841 and hfa3842 MAC controller chips used in the + * Prism I & II chipsets. + * + * This is not a complete driver, just low-level access routines for + * the MAC controller itself. + * + * Based on the prism2 driver from Absolute Value Systems' linux-wlan + * project, the Linux wvlan_cs driver, Lucent's HCF-Light + * (wvlan_hcf.c) library, and the NetBSD wireless driver. + * + * Copyright (C) 2000, David Gibson, Linuxcare Australia. + * (C) Copyright David Gibson, IBM Corp. 2001-2003. + * + * Portions taken from hfa384x.h. + * Copyright (C) 1999 AbsoluteValue Systems, Inc. All Rights Reserved. + * + * This file distributed under the GPL, version 2. + */ + +#ifndef _HERMES_H +#define _HERMES_H + +/* Notes on locking: + * + * As a module of low level hardware access routines, there is no + * locking. Users of this module should ensure that they serialize + * access to the hermes structure, and to the hardware +*/ + +#include +#include + +/* + * Limits and constants + */ +#define HERMES_ALLOC_LEN_MIN (4) +#define HERMES_ALLOC_LEN_MAX (2400) +#define HERMES_LTV_LEN_MAX (34) +#define HERMES_BAP_DATALEN_MAX (4096) +#define HERMES_BAP_OFFSET_MAX (4096) +#define HERMES_PORTID_MAX (7) +#define HERMES_NUMPORTS_MAX (HERMES_PORTID_MAX + 1) +#define HERMES_PDR_LEN_MAX (260) /* in bytes, from EK */ +#define HERMES_PDA_RECS_MAX (200) /* a guess */ +#define HERMES_PDA_LEN_MAX (1024) /* in bytes, from EK */ +#define HERMES_SCANRESULT_MAX (35) +#define HERMES_CHINFORESULT_MAX (8) +#define HERMES_MAX_MULTICAST (16) +#define HERMES_MAGIC (0x7d1f) + +/* + * Hermes register offsets + */ +#define HERMES_CMD (0x00) +#define HERMES_PARAM0 (0x02) +#define HERMES_PARAM1 (0x04) +#define HERMES_PARAM2 (0x06) +#define HERMES_STATUS (0x08) +#define HERMES_RESP0 (0x0A) +#define HERMES_RESP1 (0x0C) +#define HERMES_RESP2 (0x0E) +#define HERMES_INFOFID (0x10) +#define HERMES_RXFID (0x20) +#define HERMES_ALLOCFID (0x22) +#define HERMES_TXCOMPLFID (0x24) +#define HERMES_SELECT0 (0x18) +#define HERMES_OFFSET0 (0x1C) +#define HERMES_DATA0 (0x36) +#define HERMES_SELECT1 (0x1A) +#define HERMES_OFFSET1 (0x1E) +#define HERMES_DATA1 (0x38) +#define HERMES_EVSTAT (0x30) +#define HERMES_INTEN (0x32) +#define HERMES_EVACK (0x34) +#define HERMES_CONTROL (0x14) +#define HERMES_SWSUPPORT0 (0x28) +#define HERMES_SWSUPPORT1 (0x2A) +#define HERMES_SWSUPPORT2 (0x2C) +#define HERMES_AUXPAGE (0x3A) +#define HERMES_AUXOFFSET (0x3C) +#define HERMES_AUXDATA (0x3E) + +/* + * CMD register bitmasks + */ +#define HERMES_CMD_BUSY (0x8000) +#define HERMES_CMD_AINFO (0x7f00) +#define HERMES_CMD_MACPORT (0x0700) +#define HERMES_CMD_RECL (0x0100) +#define HERMES_CMD_WRITE (0x0100) +#define HERMES_CMD_PROGMODE (0x0300) +#define HERMES_CMD_CMDCODE (0x003f) + +/* + * STATUS register bitmasks + */ +#define HERMES_STATUS_RESULT (0x7f00) +#define HERMES_STATUS_CMDCODE (0x003f) + +/* + * OFFSET register bitmasks + */ +#define HERMES_OFFSET_BUSY (0x8000) +#define HERMES_OFFSET_ERR (0x4000) +#define HERMES_OFFSET_DATAOFF (0x0ffe) + +/* + * Event register bitmasks (INTEN, EVSTAT, EVACK) + */ +#define HERMES_EV_TICK (0x8000) +#define HERMES_EV_WTERR (0x4000) +#define HERMES_EV_INFDROP (0x2000) +#define HERMES_EV_INFO (0x0080) +#define HERMES_EV_DTIM (0x0020) +#define HERMES_EV_CMD (0x0010) +#define HERMES_EV_ALLOC (0x0008) +#define HERMES_EV_TXEXC (0x0004) +#define HERMES_EV_TX (0x0002) +#define HERMES_EV_RX (0x0001) + +/* + * Command codes + */ +/*--- Controller Commands ----------------------------*/ +#define HERMES_CMD_INIT (0x0000) +#define HERMES_CMD_ENABLE (0x0001) +#define HERMES_CMD_DISABLE (0x0002) +#define HERMES_CMD_DIAG (0x0003) + +/*--- Buffer Mgmt Commands ---------------------------*/ +#define HERMES_CMD_ALLOC (0x000A) +#define HERMES_CMD_TX (0x000B) + +/*--- Regulate Commands ------------------------------*/ +#define HERMES_CMD_NOTIFY (0x0010) +#define HERMES_CMD_INQUIRE (0x0011) + +/*--- Configure Commands -----------------------------*/ +#define HERMES_CMD_ACCESS (0x0021) +#define HERMES_CMD_DOWNLD (0x0022) + +/*--- Serial I/O Commands ----------------------------*/ +#define HERMES_CMD_READMIF (0x0030) +#define HERMES_CMD_WRITEMIF (0x0031) + +/*--- Debugging Commands -----------------------------*/ +#define HERMES_CMD_TEST (0x0038) + + +/* Test command arguments */ +#define HERMES_TEST_SET_CHANNEL 0x0800 +#define HERMES_TEST_MONITOR 0x0b00 +#define HERMES_TEST_STOP 0x0f00 + +/* Authentication algorithms */ +#define HERMES_AUTH_OPEN 1 +#define HERMES_AUTH_SHARED_KEY 2 + +/* WEP settings */ +#define HERMES_WEP_PRIVACY_INVOKED 0x0001 +#define HERMES_WEP_EXCL_UNENCRYPTED 0x0002 +#define HERMES_WEP_HOST_ENCRYPT 0x0010 +#define HERMES_WEP_HOST_DECRYPT 0x0080 + +/* Symbol hostscan options */ +#define HERMES_HOSTSCAN_SYMBOL_5SEC 0x0001 +#define HERMES_HOSTSCAN_SYMBOL_ONCE 0x0002 +#define HERMES_HOSTSCAN_SYMBOL_PASSIVE 0x0040 +#define HERMES_HOSTSCAN_SYMBOL_BCAST 0x0080 + +/* + * Frame structures and constants + */ + +#define HERMES_DESCRIPTOR_OFFSET 0 +#define HERMES_802_11_OFFSET (14) +#define HERMES_802_3_OFFSET (14 + 32) +#define HERMES_802_2_OFFSET (14 + 32 + 14) +#define HERMES_TXCNTL2_OFFSET (HERMES_802_3_OFFSET - 2) + +#define HERMES_RXSTAT_ERR (0x0003) +#define HERMES_RXSTAT_BADCRC (0x0001) +#define HERMES_RXSTAT_UNDECRYPTABLE (0x0002) +#define HERMES_RXSTAT_MIC (0x0010) /* Frame contains MIC */ +#define HERMES_RXSTAT_MACPORT (0x0700) +#define HERMES_RXSTAT_PCF (0x1000) /* Frame was received in CF period */ +#define HERMES_RXSTAT_MIC_KEY_ID (0x1800) /* MIC key used */ +#define HERMES_RXSTAT_MSGTYPE (0xE000) +#define HERMES_RXSTAT_1042 (0x2000) /* RFC-1042 frame */ +#define HERMES_RXSTAT_TUNNEL (0x4000) /* bridge-tunnel encoded frame */ +#define HERMES_RXSTAT_WMP (0x6000) /* Wavelan-II Management Protocol frame */ + +/* Shift amount for key ID in RXSTAT and TXCTRL */ +#define HERMES_MIC_KEY_ID_SHIFT 11 + +struct hermes_tx_descriptor { + __le16 status; + __le16 reserved1; + __le16 reserved2; + __le32 sw_support; + u8 retry_count; + u8 tx_rate; + __le16 tx_control; +} __packed; + +#define HERMES_TXSTAT_RETRYERR (0x0001) +#define HERMES_TXSTAT_AGEDERR (0x0002) +#define HERMES_TXSTAT_DISCON (0x0004) +#define HERMES_TXSTAT_FORMERR (0x0008) + +#define HERMES_TXCTRL_TX_OK (0x0002) /* ?? interrupt on Tx complete */ +#define HERMES_TXCTRL_TX_EX (0x0004) /* ?? interrupt on Tx exception */ +#define HERMES_TXCTRL_802_11 (0x0008) /* We supply 802.11 header */ +#define HERMES_TXCTRL_MIC (0x0010) /* 802.3 + TKIP */ +#define HERMES_TXCTRL_MIC_KEY_ID (0x1800) /* MIC Key ID mask */ +#define HERMES_TXCTRL_ALT_RTRY (0x0020) + +/* Inquiry constants and data types */ + +#define HERMES_INQ_TALLIES (0xF100) +#define HERMES_INQ_SCAN (0xF101) +#define HERMES_INQ_CHANNELINFO (0xF102) +#define HERMES_INQ_HOSTSCAN (0xF103) +#define HERMES_INQ_HOSTSCAN_SYMBOL (0xF104) +#define HERMES_INQ_LINKSTATUS (0xF200) +#define HERMES_INQ_SEC_STAT_AGERE (0xF202) + +struct hermes_tallies_frame { + __le16 TxUnicastFrames; + __le16 TxMulticastFrames; + __le16 TxFragments; + __le16 TxUnicastOctets; + __le16 TxMulticastOctets; + __le16 TxDeferredTransmissions; + __le16 TxSingleRetryFrames; + __le16 TxMultipleRetryFrames; + __le16 TxRetryLimitExceeded; + __le16 TxDiscards; + __le16 RxUnicastFrames; + __le16 RxMulticastFrames; + __le16 RxFragments; + __le16 RxUnicastOctets; + __le16 RxMulticastOctets; + __le16 RxFCSErrors; + __le16 RxDiscards_NoBuffer; + __le16 TxDiscardsWrongSA; + __le16 RxWEPUndecryptable; + __le16 RxMsgInMsgFragments; + __le16 RxMsgInBadMsgFragments; + /* Those last are probably not available in very old firmwares */ + __le16 RxDiscards_WEPICVError; + __le16 RxDiscards_WEPExcluded; +} __packed; + +/* Grabbed from wlan-ng - Thanks Mark... - Jean II + * This is the result of a scan inquiry command */ +/* Structure describing info about an Access Point */ +struct prism2_scan_apinfo { + __le16 channel; /* Channel where the AP sits */ + __le16 noise; /* Noise level */ + __le16 level; /* Signal level */ + u8 bssid[ETH_ALEN]; /* MAC address of the Access Point */ + __le16 beacon_interv; /* Beacon interval */ + __le16 capabilities; /* Capabilities */ + __le16 essid_len; /* ESSID length */ + u8 essid[32]; /* ESSID of the network */ + u8 rates[10]; /* Bit rate supported */ + __le16 proberesp_rate; /* Data rate of the response frame */ + __le16 atim; /* ATIM window time, Kus (hostscan only) */ +} __packed; + +/* Same stuff for the Lucent/Agere card. + * Thanks to h1kari - Jean II */ +struct agere_scan_apinfo { + __le16 channel; /* Channel where the AP sits */ + __le16 noise; /* Noise level */ + __le16 level; /* Signal level */ + u8 bssid[ETH_ALEN]; /* MAC address of the Access Point */ + __le16 beacon_interv; /* Beacon interval */ + __le16 capabilities; /* Capabilities */ + /* bits: 0-ess, 1-ibss, 4-privacy [wep] */ + __le16 essid_len; /* ESSID length */ + u8 essid[32]; /* ESSID of the network */ +} __packed; + +/* Moustafa: Scan structure for Symbol cards */ +struct symbol_scan_apinfo { + u8 channel; /* Channel where the AP sits */ + u8 unknown1; /* 8 in 2.9x and 3.9x f/w, 0 otherwise */ + __le16 noise; /* Noise level */ + __le16 level; /* Signal level */ + u8 bssid[ETH_ALEN]; /* MAC address of the Access Point */ + __le16 beacon_interv; /* Beacon interval */ + __le16 capabilities; /* Capabilities */ + /* bits: 0-ess, 1-ibss, 4-privacy [wep] */ + __le16 essid_len; /* ESSID length */ + u8 essid[32]; /* ESSID of the network */ + __le16 rates[5]; /* Bit rate supported */ + __le16 basic_rates; /* Basic rates bitmask */ + u8 unknown2[6]; /* Always FF:FF:FF:FF:00:00 */ + u8 unknown3[8]; /* Always 0, appeared in f/w 3.91-68 */ +} __packed; + +union hermes_scan_info { + struct agere_scan_apinfo a; + struct prism2_scan_apinfo p; + struct symbol_scan_apinfo s; +}; + +/* Extended scan struct for HERMES_INQ_CHANNELINFO. + * wl_lkm calls this an ACS scan (Automatic Channel Select). + * Keep out of union hermes_scan_info because it is much bigger than + * the older scan structures. */ +struct agere_ext_scan_info { + __le16 reserved0; + + u8 noise; + u8 level; + u8 rx_flow; + u8 rate; + __le16 reserved1[2]; + + __le16 frame_control; + __le16 dur_id; + u8 addr1[ETH_ALEN]; + u8 addr2[ETH_ALEN]; + u8 bssid[ETH_ALEN]; + __le16 sequence; + u8 addr4[ETH_ALEN]; + + __le16 data_length; + + /* Next 3 fields do not get filled in. */ + u8 daddr[ETH_ALEN]; + u8 saddr[ETH_ALEN]; + __le16 len_type; + + __le64 timestamp; + __le16 beacon_interval; + __le16 capabilities; + u8 data[0]; +} __packed; + +#define HERMES_LINKSTATUS_NOT_CONNECTED (0x0000) +#define HERMES_LINKSTATUS_CONNECTED (0x0001) +#define HERMES_LINKSTATUS_DISCONNECTED (0x0002) +#define HERMES_LINKSTATUS_AP_CHANGE (0x0003) +#define HERMES_LINKSTATUS_AP_OUT_OF_RANGE (0x0004) +#define HERMES_LINKSTATUS_AP_IN_RANGE (0x0005) +#define HERMES_LINKSTATUS_ASSOC_FAILED (0x0006) + +struct hermes_linkstatus { + __le16 linkstatus; /* Link status */ +} __packed; + +struct hermes_response { + u16 status, resp0, resp1, resp2; +}; + +/* "ID" structure - used for ESSID and station nickname */ +struct hermes_idstring { + __le16 len; + __le16 val[16]; +} __packed; + +struct hermes_multicast { + u8 addr[HERMES_MAX_MULTICAST][ETH_ALEN]; +} __packed; + +/* Timeouts */ +#define HERMES_BAP_BUSY_TIMEOUT (10000) /* In iterations of ~1us */ + +struct hermes; + +/* Functions to access hardware */ +struct hermes_ops { + int (*init)(struct hermes *hw); + int (*cmd_wait)(struct hermes *hw, u16 cmd, u16 parm0, + struct hermes_response *resp); + int (*init_cmd_wait)(struct hermes *hw, u16 cmd, + u16 parm0, u16 parm1, u16 parm2, + struct hermes_response *resp); + int (*allocate)(struct hermes *hw, u16 size, u16 *fid); + int (*read_ltv)(struct hermes *hw, int bap, u16 rid, unsigned buflen, + u16 *length, void *buf); + int (*write_ltv)(struct hermes *hw, int bap, u16 rid, + u16 length, const void *value); + int (*bap_pread)(struct hermes *hw, int bap, void *buf, int len, + u16 id, u16 offset); + int (*bap_pwrite)(struct hermes *hw, int bap, const void *buf, + int len, u16 id, u16 offset); + int (*read_pda_h)(struct hermes *hw, __le16 *pda, + u32 pda_addr, u16 pda_len); + int (*program_init)(struct hermes *hw, u32 entry_point); + int (*program_end)(struct hermes *hw); + int (*program)(struct hermes *hw, const char *buf, + u32 addr, u32 len); + void (*lock_irqsave)(spinlock_t *lock, unsigned long *flags); + void (*unlock_irqrestore)(spinlock_t *lock, unsigned long *flags); + void (*lock_irq)(spinlock_t *lock); + void (*unlock_irq)(spinlock_t *lock); +}; + +/* Basic control structure */ +struct hermes { + void __iomem *iobase; + int reg_spacing; +#define HERMES_16BIT_REGSPACING 0 +#define HERMES_32BIT_REGSPACING 1 + u16 inten; /* Which interrupts should be enabled? */ + bool eeprom_pda; + const struct hermes_ops *ops; + void *priv; +}; + +/* Register access convenience macros */ +#define hermes_read_reg(hw, off) \ + (ioread16((hw)->iobase + ((off) << (hw)->reg_spacing))) +#define hermes_write_reg(hw, off, val) \ + (iowrite16((val), (hw)->iobase + ((off) << (hw)->reg_spacing))) +#define hermes_read_regn(hw, name) hermes_read_reg((hw), HERMES_##name) +#define hermes_write_regn(hw, name, val) \ + hermes_write_reg((hw), HERMES_##name, (val)) + +/* Function prototypes */ +void hermes_struct_init(struct hermes *hw, void __iomem *address, + int reg_spacing); + +/* Inline functions */ + +static inline int hermes_present(struct hermes *hw) +{ + return hermes_read_regn(hw, SWSUPPORT0) == HERMES_MAGIC; +} + +static inline void hermes_set_irqmask(struct hermes *hw, u16 events) +{ + hw->inten = events; + hermes_write_regn(hw, INTEN, events); +} + +static inline int hermes_enable_port(struct hermes *hw, int port) +{ + return hw->ops->cmd_wait(hw, HERMES_CMD_ENABLE | (port << 8), + 0, NULL); +} + +static inline int hermes_disable_port(struct hermes *hw, int port) +{ + return hw->ops->cmd_wait(hw, HERMES_CMD_DISABLE | (port << 8), + 0, NULL); +} + +/* Initiate an INQUIRE command (tallies or scan). The result will come as an + * information frame in __orinoco_ev_info() */ +static inline int hermes_inquire(struct hermes *hw, u16 rid) +{ + return hw->ops->cmd_wait(hw, HERMES_CMD_INQUIRE, rid, NULL); +} + +#define HERMES_BYTES_TO_RECLEN(n) ((((n) + 1) / 2) + 1) +#define HERMES_RECLEN_TO_BYTES(n) (((n) - 1) * 2) + +/* Note that for the next two, the count is in 16-bit words, not bytes */ +static inline void hermes_read_words(struct hermes *hw, int off, + void *buf, unsigned count) +{ + off = off << hw->reg_spacing; + ioread16_rep(hw->iobase + off, buf, count); +} + +static inline void hermes_write_bytes(struct hermes *hw, int off, + const char *buf, unsigned count) +{ + off = off << hw->reg_spacing; + iowrite16_rep(hw->iobase + off, buf, count >> 1); + if (unlikely(count & 1)) + iowrite8(buf[count - 1], hw->iobase + off); +} + +static inline void hermes_clear_words(struct hermes *hw, int off, + unsigned count) +{ + unsigned i; + + off = off << hw->reg_spacing; + + for (i = 0; i < count; i++) + iowrite16(0, hw->iobase + off); +} + +#define HERMES_READ_RECORD(hw, bap, rid, buf) \ + (hw->ops->read_ltv((hw), (bap), (rid), sizeof(*buf), NULL, (buf))) +#define HERMES_WRITE_RECORD(hw, bap, rid, buf) \ + (hw->ops->write_ltv((hw), (bap), (rid), \ + HERMES_BYTES_TO_RECLEN(sizeof(*buf)), (buf))) + +static inline int hermes_read_wordrec(struct hermes *hw, int bap, u16 rid, + u16 *word) +{ + __le16 rec; + int err; + + err = HERMES_READ_RECORD(hw, bap, rid, &rec); + *word = le16_to_cpu(rec); + return err; +} + +static inline int hermes_write_wordrec(struct hermes *hw, int bap, u16 rid, + u16 word) +{ + __le16 rec = cpu_to_le16(word); + return HERMES_WRITE_RECORD(hw, bap, rid, &rec); +} + +#endif /* _HERMES_H */ diff --git a/drivers/net/wireless/orinoco/hermes_dld.c b/drivers/net/wireless/orinoco/hermes_dld.c new file mode 100644 index 0000000..4a10b7a --- /dev/null +++ b/drivers/net/wireless/orinoco/hermes_dld.c @@ -0,0 +1,477 @@ +/* + * Hermes download helper. + * + * This helper: + * - is capable of writing to the volatile area of the hermes device + * - is currently not capable of writing to non-volatile areas + * - provide helpers to identify and update plugin data + * - is not capable of interpreting a fw image directly. That is up to + * the main card driver. + * - deals with Hermes I devices. It can probably be modified to deal + * with Hermes II devices + * + * Copyright (C) 2007, David Kilroy + * + * Plug data code slightly modified from spectrum_cs driver + * Copyright (C) 2002-2005 Pavel Roskin + * Portions based on information in wl_lkm_718 Agere driver + * COPYRIGHT (C) 2001-2004 by Agere Systems Inc. All Rights Reserved + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#include +#include +#include "hermes.h" +#include "hermes_dld.h" + +#define PFX "hermes_dld: " + +/* End markers used in dblocks */ +#define PDI_END 0x00000000 /* End of PDA */ +#define BLOCK_END 0xFFFFFFFF /* Last image block */ +#define TEXT_END 0x1A /* End of text header */ + +/* + * The following structures have little-endian fields denoted by + * the leading underscore. Don't access them directly - use inline + * functions defined below. + */ + +/* + * The binary image to be downloaded consists of series of data blocks. + * Each block has the following structure. + */ +struct dblock { + __le32 addr; /* adapter address where to write the block */ + __le16 len; /* length of the data only, in bytes */ + char data[0]; /* data to be written */ +} __packed; + +/* + * Plug Data References are located in the image after the last data + * block. They refer to areas in the adapter memory where the plug data + * items with matching ID should be written. + */ +struct pdr { + __le32 id; /* record ID */ + __le32 addr; /* adapter address where to write the data */ + __le32 len; /* expected length of the data, in bytes */ + char next[0]; /* next PDR starts here */ +} __packed; + +/* + * Plug Data Items are located in the EEPROM read from the adapter by + * primary firmware. They refer to the device-specific data that should + * be plugged into the secondary firmware. + */ +struct pdi { + __le16 len; /* length of ID and data, in words */ + __le16 id; /* record ID */ + char data[0]; /* plug data */ +} __packed; + +/*** FW data block access functions ***/ + +static inline u32 +dblock_addr(const struct dblock *blk) +{ + return le32_to_cpu(blk->addr); +} + +static inline u32 +dblock_len(const struct dblock *blk) +{ + return le16_to_cpu(blk->len); +} + +/*** PDR Access functions ***/ + +static inline u32 +pdr_id(const struct pdr *pdr) +{ + return le32_to_cpu(pdr->id); +} + +static inline u32 +pdr_addr(const struct pdr *pdr) +{ + return le32_to_cpu(pdr->addr); +} + +static inline u32 +pdr_len(const struct pdr *pdr) +{ + return le32_to_cpu(pdr->len); +} + +/*** PDI Access functions ***/ + +static inline u32 +pdi_id(const struct pdi *pdi) +{ + return le16_to_cpu(pdi->id); +} + +/* Return length of the data only, in bytes */ +static inline u32 +pdi_len(const struct pdi *pdi) +{ + return 2 * (le16_to_cpu(pdi->len) - 1); +} + +/*** Plug Data Functions ***/ + +/* + * Scan PDR for the record with the specified RECORD_ID. + * If it's not found, return NULL. + */ +static const struct pdr * +hermes_find_pdr(const struct pdr *first_pdr, u32 record_id, const void *end) +{ + const struct pdr *pdr = first_pdr; + + end -= sizeof(struct pdr); + + while (((void *) pdr <= end) && + (pdr_id(pdr) != PDI_END)) { + /* + * PDR area is currently not terminated by PDI_END. + * It's followed by CRC records, which have the type + * field where PDR has length. The type can be 0 or 1. + */ + if (pdr_len(pdr) < 2) + return NULL; + + /* If the record ID matches, we are done */ + if (pdr_id(pdr) == record_id) + return pdr; + + pdr = (struct pdr *) pdr->next; + } + return NULL; +} + +/* Scan production data items for a particular entry */ +static const struct pdi * +hermes_find_pdi(const struct pdi *first_pdi, u32 record_id, const void *end) +{ + const struct pdi *pdi = first_pdi; + + end -= sizeof(struct pdi); + + while (((void *) pdi <= end) && + (pdi_id(pdi) != PDI_END)) { + + /* If the record ID matches, we are done */ + if (pdi_id(pdi) == record_id) + return pdi; + + pdi = (struct pdi *) &pdi->data[pdi_len(pdi)]; + } + return NULL; +} + +/* Process one Plug Data Item - find corresponding PDR and plug it */ +static int +hermes_plug_pdi(struct hermes *hw, const struct pdr *first_pdr, + const struct pdi *pdi, const void *pdr_end) +{ + const struct pdr *pdr; + + /* Find the PDR corresponding to this PDI */ + pdr = hermes_find_pdr(first_pdr, pdi_id(pdi), pdr_end); + + /* No match is found, safe to ignore */ + if (!pdr) + return 0; + + /* Lengths of the data in PDI and PDR must match */ + if (pdi_len(pdi) != pdr_len(pdr)) + return -EINVAL; + + /* do the actual plugging */ + hw->ops->program(hw, pdi->data, pdr_addr(pdr), pdi_len(pdi)); + + return 0; +} + +/* Parse PDA and write the records into the adapter + * + * Attempt to write every records that is in the specified pda + * which also has a valid production data record for the firmware. + */ +int hermes_apply_pda(struct hermes *hw, + const char *first_pdr, + const void *pdr_end, + const __le16 *pda, + const void *pda_end) +{ + int ret; + const struct pdi *pdi; + const struct pdr *pdr; + + pdr = (const struct pdr *) first_pdr; + pda_end -= sizeof(struct pdi); + + /* Go through every PDI and plug them into the adapter */ + pdi = (const struct pdi *) (pda + 2); + while (((void *) pdi <= pda_end) && + (pdi_id(pdi) != PDI_END)) { + ret = hermes_plug_pdi(hw, pdr, pdi, pdr_end); + if (ret) + return ret; + + /* Increment to the next PDI */ + pdi = (const struct pdi *) &pdi->data[pdi_len(pdi)]; + } + return 0; +} + +/* Identify the total number of bytes in all blocks + * including the header data. + */ +size_t +hermes_blocks_length(const char *first_block, const void *end) +{ + const struct dblock *blk = (const struct dblock *) first_block; + int total_len = 0; + int len; + + end -= sizeof(*blk); + + /* Skip all blocks to locate Plug Data References + * (Spectrum CS) */ + while (((void *) blk <= end) && + (dblock_addr(blk) != BLOCK_END)) { + len = dblock_len(blk); + total_len += sizeof(*blk) + len; + blk = (struct dblock *) &blk->data[len]; + } + + return total_len; +} + +/*** Hermes programming ***/ + +/* Program the data blocks */ +int hermes_program(struct hermes *hw, const char *first_block, const void *end) +{ + const struct dblock *blk; + u32 blkaddr; + u32 blklen; + int err = 0; + + blk = (const struct dblock *) first_block; + + if ((void *) blk > (end - sizeof(*blk))) + return -EIO; + + blkaddr = dblock_addr(blk); + blklen = dblock_len(blk); + + while ((blkaddr != BLOCK_END) && + (((void *) blk + blklen) <= end)) { + pr_debug(PFX "Programming block of length %d " + "to address 0x%08x\n", blklen, blkaddr); + + err = hw->ops->program(hw, blk->data, blkaddr, blklen); + if (err) + break; + + blk = (const struct dblock *) &blk->data[blklen]; + + if ((void *) blk > (end - sizeof(*blk))) + return -EIO; + + blkaddr = dblock_addr(blk); + blklen = dblock_len(blk); + } + return err; +} + +/*** Default plugging data for Hermes I ***/ +/* Values from wl_lkm_718/hcf/dhf.c */ + +#define DEFINE_DEFAULT_PDR(pid, length, data) \ +static const struct { \ + __le16 len; \ + __le16 id; \ + u8 val[length]; \ +} __packed default_pdr_data_##pid = { \ + cpu_to_le16((sizeof(default_pdr_data_##pid)/ \ + sizeof(__le16)) - 1), \ + cpu_to_le16(pid), \ + data \ +} + +#define DEFAULT_PDR(pid) default_pdr_data_##pid + +/* HWIF Compatibility */ +DEFINE_DEFAULT_PDR(0x0005, 10, "\x00\x00\x06\x00\x01\x00\x01\x00\x01\x00"); + +/* PPPPSign */ +DEFINE_DEFAULT_PDR(0x0108, 4, "\x00\x00\x00\x00"); + +/* PPPPProf */ +DEFINE_DEFAULT_PDR(0x0109, 10, "\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00"); + +/* Antenna diversity */ +DEFINE_DEFAULT_PDR(0x0150, 2, "\x00\x3F"); + +/* Modem VCO band Set-up */ +DEFINE_DEFAULT_PDR(0x0160, 28, + "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00"); + +/* Modem Rx Gain Table Values */ +DEFINE_DEFAULT_PDR(0x0161, 256, + "\x3F\x01\x3F\01\x3F\x01\x3F\x01" + "\x3F\x01\x3F\01\x3F\x01\x3F\x01" + "\x3F\x01\x3F\01\x3F\x01\x3F\x01" + "\x3F\x01\x3F\01\x3F\x01\x3F\x01" + "\x3F\x01\x3E\01\x3E\x01\x3D\x01" + "\x3D\x01\x3C\01\x3C\x01\x3B\x01" + "\x3B\x01\x3A\01\x3A\x01\x39\x01" + "\x39\x01\x38\01\x38\x01\x37\x01" + "\x37\x01\x36\01\x36\x01\x35\x01" + "\x35\x01\x34\01\x34\x01\x33\x01" + "\x33\x01\x32\x01\x32\x01\x31\x01" + "\x31\x01\x30\x01\x30\x01\x7B\x01" + "\x7B\x01\x7A\x01\x7A\x01\x79\x01" + "\x79\x01\x78\x01\x78\x01\x77\x01" + "\x77\x01\x76\x01\x76\x01\x75\x01" + "\x75\x01\x74\x01\x74\x01\x73\x01" + "\x73\x01\x72\x01\x72\x01\x71\x01" + "\x71\x01\x70\x01\x70\x01\x68\x01" + "\x68\x01\x67\x01\x67\x01\x66\x01" + "\x66\x01\x65\x01\x65\x01\x57\x01" + "\x57\x01\x56\x01\x56\x01\x55\x01" + "\x55\x01\x54\x01\x54\x01\x53\x01" + "\x53\x01\x52\x01\x52\x01\x51\x01" + "\x51\x01\x50\x01\x50\x01\x48\x01" + "\x48\x01\x47\x01\x47\x01\x46\x01" + "\x46\x01\x45\x01\x45\x01\x44\x01" + "\x44\x01\x43\x01\x43\x01\x42\x01" + "\x42\x01\x41\x01\x41\x01\x40\x01" + "\x40\x01\x40\x01\x40\x01\x40\x01" + "\x40\x01\x40\x01\x40\x01\x40\x01" + "\x40\x01\x40\x01\x40\x01\x40\x01" + "\x40\x01\x40\x01\x40\x01\x40\x01"); + +/* Write PDA according to certain rules. + * + * For every production data record, look for a previous setting in + * the pda, and use that. + * + * For certain records, use defaults if they are not found in pda. + */ +int hermes_apply_pda_with_defaults(struct hermes *hw, + const char *first_pdr, + const void *pdr_end, + const __le16 *pda, + const void *pda_end) +{ + const struct pdr *pdr = (const struct pdr *) first_pdr; + const struct pdi *first_pdi = (const struct pdi *) &pda[2]; + const struct pdi *pdi; + const struct pdi *default_pdi = NULL; + const struct pdi *outdoor_pdi; + int record_id; + + pdr_end -= sizeof(struct pdr); + + while (((void *) pdr <= pdr_end) && + (pdr_id(pdr) != PDI_END)) { + /* + * For spectrum_cs firmwares, + * PDR area is currently not terminated by PDI_END. + * It's followed by CRC records, which have the type + * field where PDR has length. The type can be 0 or 1. + */ + if (pdr_len(pdr) < 2) + break; + record_id = pdr_id(pdr); + + pdi = hermes_find_pdi(first_pdi, record_id, pda_end); + if (pdi) + pr_debug(PFX "Found record 0x%04x at %p\n", + record_id, pdi); + + switch (record_id) { + case 0x110: /* Modem REFDAC values */ + case 0x120: /* Modem VGDAC values */ + outdoor_pdi = hermes_find_pdi(first_pdi, record_id + 1, + pda_end); + default_pdi = NULL; + if (outdoor_pdi) { + pdi = outdoor_pdi; + pr_debug(PFX + "Using outdoor record 0x%04x at %p\n", + record_id + 1, pdi); + } + break; + case 0x5: /* HWIF Compatibility */ + default_pdi = (struct pdi *) &DEFAULT_PDR(0x0005); + break; + case 0x108: /* PPPPSign */ + default_pdi = (struct pdi *) &DEFAULT_PDR(0x0108); + break; + case 0x109: /* PPPPProf */ + default_pdi = (struct pdi *) &DEFAULT_PDR(0x0109); + break; + case 0x150: /* Antenna diversity */ + default_pdi = (struct pdi *) &DEFAULT_PDR(0x0150); + break; + case 0x160: /* Modem VCO band Set-up */ + default_pdi = (struct pdi *) &DEFAULT_PDR(0x0160); + break; + case 0x161: /* Modem Rx Gain Table Values */ + default_pdi = (struct pdi *) &DEFAULT_PDR(0x0161); + break; + default: + default_pdi = NULL; + break; + } + if (!pdi && default_pdi) { + /* Use default */ + pdi = default_pdi; + pr_debug(PFX "Using default record 0x%04x at %p\n", + record_id, pdi); + } + + if (pdi) { + /* Lengths of the data in PDI and PDR must match */ + if ((pdi_len(pdi) == pdr_len(pdr)) && + ((void *) pdi->data + pdi_len(pdi) < pda_end)) { + /* do the actual plugging */ + hw->ops->program(hw, pdi->data, pdr_addr(pdr), + pdi_len(pdi)); + } + } + + pdr++; + } + return 0; +} diff --git a/drivers/net/wireless/orinoco/hermes_dld.h b/drivers/net/wireless/orinoco/hermes_dld.h new file mode 100644 index 0000000..b5377e2 --- /dev/null +++ b/drivers/net/wireless/orinoco/hermes_dld.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2007, David Kilroy + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ +#ifndef _HERMES_DLD_H +#define _HERMES_DLD_H + +#include "hermes.h" + +int hermesi_program_init(struct hermes *hw, u32 offset); +int hermesi_program_end(struct hermes *hw); +int hermes_program(struct hermes *hw, const char *first_block, const void *end); + +int hermes_read_pda(struct hermes *hw, + __le16 *pda, + u32 pda_addr, + u16 pda_len, + int use_eeprom); +int hermes_apply_pda(struct hermes *hw, + const char *first_pdr, + const void *pdr_end, + const __le16 *pda, + const void *pda_end); +int hermes_apply_pda_with_defaults(struct hermes *hw, + const char *first_pdr, + const void *pdr_end, + const __le16 *pda, + const void *pda_end); + +size_t hermes_blocks_length(const char *first_block, const void *end); + +#endif /* _HERMES_DLD_H */ diff --git a/drivers/net/wireless/orinoco/hermes_rid.h b/drivers/net/wireless/orinoco/hermes_rid.h new file mode 100644 index 0000000..42eb67d --- /dev/null +++ b/drivers/net/wireless/orinoco/hermes_rid.h @@ -0,0 +1,165 @@ +#ifndef _HERMES_RID_H +#define _HERMES_RID_H + +/* + * Configuration RIDs + */ +#define HERMES_RID_CNFPORTTYPE 0xFC00 +#define HERMES_RID_CNFOWNMACADDR 0xFC01 +#define HERMES_RID_CNFDESIREDSSID 0xFC02 +#define HERMES_RID_CNFOWNCHANNEL 0xFC03 +#define HERMES_RID_CNFOWNSSID 0xFC04 +#define HERMES_RID_CNFOWNATIMWINDOW 0xFC05 +#define HERMES_RID_CNFSYSTEMSCALE 0xFC06 +#define HERMES_RID_CNFMAXDATALEN 0xFC07 +#define HERMES_RID_CNFWDSADDRESS 0xFC08 +#define HERMES_RID_CNFPMENABLED 0xFC09 +#define HERMES_RID_CNFPMEPS 0xFC0A +#define HERMES_RID_CNFMULTICASTRECEIVE 0xFC0B +#define HERMES_RID_CNFMAXSLEEPDURATION 0xFC0C +#define HERMES_RID_CNFPMHOLDOVERDURATION 0xFC0D +#define HERMES_RID_CNFOWNNAME 0xFC0E +#define HERMES_RID_CNFOWNDTIMPERIOD 0xFC10 +#define HERMES_RID_CNFWDSADDRESS1 0xFC11 +#define HERMES_RID_CNFWDSADDRESS2 0xFC12 +#define HERMES_RID_CNFWDSADDRESS3 0xFC13 +#define HERMES_RID_CNFWDSADDRESS4 0xFC14 +#define HERMES_RID_CNFWDSADDRESS5 0xFC15 +#define HERMES_RID_CNFWDSADDRESS6 0xFC16 +#define HERMES_RID_CNFMULTICASTPMBUFFERING 0xFC17 +#define HERMES_RID_CNFWEPENABLED_AGERE 0xFC20 +#define HERMES_RID_CNFAUTHENTICATION_AGERE 0xFC21 +#define HERMES_RID_CNFMANDATORYBSSID_SYMBOL 0xFC21 +#define HERMES_RID_CNFDROPUNENCRYPTED 0xFC22 +#define HERMES_RID_CNFWEPDEFAULTKEYID 0xFC23 +#define HERMES_RID_CNFDEFAULTKEY0 0xFC24 +#define HERMES_RID_CNFDEFAULTKEY1 0xFC25 +#define HERMES_RID_CNFMWOROBUST_AGERE 0xFC25 +#define HERMES_RID_CNFDEFAULTKEY2 0xFC26 +#define HERMES_RID_CNFDEFAULTKEY3 0xFC27 +#define HERMES_RID_CNFWEPFLAGS_INTERSIL 0xFC28 +#define HERMES_RID_CNFWEPKEYMAPPINGTABLE 0xFC29 +#define HERMES_RID_CNFAUTHENTICATION 0xFC2A +#define HERMES_RID_CNFMAXASSOCSTA 0xFC2B +#define HERMES_RID_CNFKEYLENGTH_SYMBOL 0xFC2B +#define HERMES_RID_CNFTXCONTROL 0xFC2C +#define HERMES_RID_CNFROAMINGMODE 0xFC2D +#define HERMES_RID_CNFHOSTAUTHENTICATION 0xFC2E +#define HERMES_RID_CNFRCVCRCERROR 0xFC30 +#define HERMES_RID_CNFMMLIFE 0xFC31 +#define HERMES_RID_CNFALTRETRYCOUNT 0xFC32 +#define HERMES_RID_CNFBEACONINT 0xFC33 +#define HERMES_RID_CNFAPPCFINFO 0xFC34 +#define HERMES_RID_CNFSTAPCFINFO 0xFC35 +#define HERMES_RID_CNFPRIORITYQUSAGE 0xFC37 +#define HERMES_RID_CNFTIMCTRL 0xFC40 +#define HERMES_RID_CNFTHIRTY2TALLY 0xFC42 +#define HERMES_RID_CNFENHSECURITY 0xFC43 +#define HERMES_RID_CNFGROUPADDRESSES 0xFC80 +#define HERMES_RID_CNFCREATEIBSS 0xFC81 +#define HERMES_RID_CNFFRAGMENTATIONTHRESHOLD 0xFC82 +#define HERMES_RID_CNFRTSTHRESHOLD 0xFC83 +#define HERMES_RID_CNFTXRATECONTROL 0xFC84 +#define HERMES_RID_CNFPROMISCUOUSMODE 0xFC85 +#define HERMES_RID_CNFBASICRATES_SYMBOL 0xFC8A +#define HERMES_RID_CNFPREAMBLE_SYMBOL 0xFC8C +#define HERMES_RID_CNFFRAGMENTATIONTHRESHOLD0 0xFC90 +#define HERMES_RID_CNFFRAGMENTATIONTHRESHOLD1 0xFC91 +#define HERMES_RID_CNFFRAGMENTATIONTHRESHOLD2 0xFC92 +#define HERMES_RID_CNFFRAGMENTATIONTHRESHOLD3 0xFC93 +#define HERMES_RID_CNFFRAGMENTATIONTHRESHOLD4 0xFC94 +#define HERMES_RID_CNFFRAGMENTATIONTHRESHOLD5 0xFC95 +#define HERMES_RID_CNFFRAGMENTATIONTHRESHOLD6 0xFC96 +#define HERMES_RID_CNFRTSTHRESHOLD0 0xFC97 +#define HERMES_RID_CNFRTSTHRESHOLD1 0xFC98 +#define HERMES_RID_CNFRTSTHRESHOLD2 0xFC99 +#define HERMES_RID_CNFRTSTHRESHOLD3 0xFC9A +#define HERMES_RID_CNFRTSTHRESHOLD4 0xFC9B +#define HERMES_RID_CNFRTSTHRESHOLD5 0xFC9C +#define HERMES_RID_CNFRTSTHRESHOLD6 0xFC9D +#define HERMES_RID_CNFHOSTSCAN_SYMBOL 0xFCAB +#define HERMES_RID_CNFSHORTPREAMBLE 0xFCB0 +#define HERMES_RID_CNFWEPKEYS_AGERE 0xFCB0 +#define HERMES_RID_CNFEXCLUDELONGPREAMBLE 0xFCB1 +#define HERMES_RID_CNFTXKEY_AGERE 0xFCB1 +#define HERMES_RID_CNFAUTHENTICATIONRSPTO 0xFCB2 +#define HERMES_RID_CNFSCANSSID_AGERE 0xFCB2 +#define HERMES_RID_CNFBASICRATES 0xFCB3 +#define HERMES_RID_CNFSUPPORTEDRATES 0xFCB4 +#define HERMES_RID_CNFADDDEFAULTTKIPKEY_AGERE 0xFCB4 +#define HERMES_RID_CNFSETWPAAUTHMGMTSUITE_AGERE 0xFCB5 +#define HERMES_RID_CNFREMDEFAULTTKIPKEY_AGERE 0xFCB6 +#define HERMES_RID_CNFADDMAPPEDTKIPKEY_AGERE 0xFCB7 +#define HERMES_RID_CNFREMMAPPEDTKIPKEY_AGERE 0xFCB8 +#define HERMES_RID_CNFSETWPACAPABILITIES_AGERE 0xFCB9 +#define HERMES_RID_CNFCACHEDPMKADDRESS 0xFCBA +#define HERMES_RID_CNFREMOVEPMKADDRESS 0xFCBB +#define HERMES_RID_CNFSCANCHANNELS2GHZ 0xFCC2 +#define HERMES_RID_CNFDISASSOCIATE 0xFCC8 +#define HERMES_RID_CNFTICKTIME 0xFCE0 +#define HERMES_RID_CNFSCANREQUEST 0xFCE1 +#define HERMES_RID_CNFJOINREQUEST 0xFCE2 +#define HERMES_RID_CNFAUTHENTICATESTATION 0xFCE3 +#define HERMES_RID_CNFCHANNELINFOREQUEST 0xFCE4 +#define HERMES_RID_CNFHOSTSCAN 0xFCE5 + +/* + * Information RIDs + */ +#define HERMES_RID_MAXLOADTIME 0xFD00 +#define HERMES_RID_DOWNLOADBUFFER 0xFD01 +#define HERMES_RID_PRIID 0xFD02 +#define HERMES_RID_PRISUPRANGE 0xFD03 +#define HERMES_RID_CFIACTRANGES 0xFD04 +#define HERMES_RID_NICSERNUM 0xFD0A +#define HERMES_RID_NICID 0xFD0B +#define HERMES_RID_MFISUPRANGE 0xFD0C +#define HERMES_RID_CFISUPRANGE 0xFD0D +#define HERMES_RID_CHANNELLIST 0xFD10 +#define HERMES_RID_REGULATORYDOMAINS 0xFD11 +#define HERMES_RID_TEMPTYPE 0xFD12 +#define HERMES_RID_CIS 0xFD13 +#define HERMES_RID_STAID 0xFD20 +#define HERMES_RID_STASUPRANGE 0xFD21 +#define HERMES_RID_MFIACTRANGES 0xFD22 +#define HERMES_RID_CFIACTRANGES2 0xFD23 +#define HERMES_RID_SECONDARYVERSION_SYMBOL 0xFD24 +#define HERMES_RID_PORTSTATUS 0xFD40 +#define HERMES_RID_CURRENTSSID 0xFD41 +#define HERMES_RID_CURRENTBSSID 0xFD42 +#define HERMES_RID_COMMSQUALITY 0xFD43 +#define HERMES_RID_CURRENTTXRATE 0xFD44 +#define HERMES_RID_CURRENTBEACONINTERVAL 0xFD45 +#define HERMES_RID_CURRENTSCALETHRESHOLDS 0xFD46 +#define HERMES_RID_PROTOCOLRSPTIME 0xFD47 +#define HERMES_RID_SHORTRETRYLIMIT 0xFD48 +#define HERMES_RID_LONGRETRYLIMIT 0xFD49 +#define HERMES_RID_MAXTRANSMITLIFETIME 0xFD4A +#define HERMES_RID_MAXRECEIVELIFETIME 0xFD4B +#define HERMES_RID_CFPOLLABLE 0xFD4C +#define HERMES_RID_AUTHENTICATIONALGORITHMS 0xFD4D +#define HERMES_RID_PRIVACYOPTIONIMPLEMENTED 0xFD4F +#define HERMES_RID_DBMCOMMSQUALITY_INTERSIL 0xFD51 +#define HERMES_RID_CURRENTTXRATE1 0xFD80 +#define HERMES_RID_CURRENTTXRATE2 0xFD81 +#define HERMES_RID_CURRENTTXRATE3 0xFD82 +#define HERMES_RID_CURRENTTXRATE4 0xFD83 +#define HERMES_RID_CURRENTTXRATE5 0xFD84 +#define HERMES_RID_CURRENTTXRATE6 0xFD85 +#define HERMES_RID_OWNMACADDR 0xFD86 +#define HERMES_RID_SCANRESULTSTABLE 0xFD88 +#define HERMES_RID_CURRENT_COUNTRY_INFO 0xFD89 +#define HERMES_RID_CURRENT_WPA_IE 0xFD8A +#define HERMES_RID_CURRENT_TKIP_IV 0xFD8B +#define HERMES_RID_CURRENT_ASSOC_REQ_INFO 0xFD8C +#define HERMES_RID_CURRENT_ASSOC_RESP_INFO 0xFD8D +#define HERMES_RID_TXQUEUEEMPTY 0xFD91 +#define HERMES_RID_PHYTYPE 0xFDC0 +#define HERMES_RID_CURRENTCHANNEL 0xFDC1 +#define HERMES_RID_CURRENTPOWERSTATE 0xFDC2 +#define HERMES_RID_CCAMODE 0xFDC3 +#define HERMES_RID_SUPPORTEDDATARATES 0xFDC6 +#define HERMES_RID_BUILDSEQ 0xFFFE +#define HERMES_RID_FWID 0xFFFF + +#endif diff --git a/drivers/net/wireless/orinoco/hw.c b/drivers/net/wireless/orinoco/hw.c new file mode 100644 index 0000000..dfd2e45 --- /dev/null +++ b/drivers/net/wireless/orinoco/hw.c @@ -0,0 +1,1360 @@ +/* Encapsulate basic setting changes and retrieval on Hermes hardware + * + * See copyright notice in main.c + */ +#include +#include +#include +#include +#include +#include +#include "hermes.h" +#include "hermes_rid.h" +#include "orinoco.h" + +#include "hw.h" + +#define SYMBOL_MAX_VER_LEN (14) + +/* Symbol firmware has a bug allocating buffers larger than this */ +#define TX_NICBUF_SIZE_BUG 1585 + +/********************************************************************/ +/* Data tables */ +/********************************************************************/ + +/* This tables gives the actual meanings of the bitrate IDs returned + * by the firmware. */ +static const struct { + int bitrate; /* in 100s of kilobits */ + int automatic; + u16 agere_txratectrl; + u16 intersil_txratectrl; +} bitrate_table[] = { + {110, 1, 3, 15}, /* Entry 0 is the default */ + {10, 0, 1, 1}, + {10, 1, 1, 1}, + {20, 0, 2, 2}, + {20, 1, 6, 3}, + {55, 0, 4, 4}, + {55, 1, 7, 7}, + {110, 0, 5, 8}, +}; +#define BITRATE_TABLE_SIZE ARRAY_SIZE(bitrate_table) + +/* Firmware version encoding */ +struct comp_id { + u16 id, variant, major, minor; +} __packed; + +static inline enum fwtype determine_firmware_type(struct comp_id *nic_id) +{ + if (nic_id->id < 0x8000) + return FIRMWARE_TYPE_AGERE; + else if (nic_id->id == 0x8000 && nic_id->major == 0) + return FIRMWARE_TYPE_SYMBOL; + else + return FIRMWARE_TYPE_INTERSIL; +} + +/* Set priv->firmware type, determine firmware properties + * This function can be called before we have registerred with netdev, + * so all errors go out with dev_* rather than printk + * + * If non-NULL stores a firmware description in fw_name. + * If non-NULL stores a HW version in hw_ver + * + * These are output via generic cfg80211 ethtool support. + */ +int determine_fw_capabilities(struct orinoco_private *priv, + char *fw_name, size_t fw_name_len, + u32 *hw_ver) +{ + struct device *dev = priv->dev; + struct hermes *hw = &priv->hw; + int err; + struct comp_id nic_id, sta_id; + unsigned int firmver; + char tmp[SYMBOL_MAX_VER_LEN + 1] __attribute__((aligned(2))); + + /* Get the hardware version */ + err = HERMES_READ_RECORD(hw, USER_BAP, HERMES_RID_NICID, &nic_id); + if (err) { + dev_err(dev, "Cannot read hardware identity: error %d\n", + err); + return err; + } + + le16_to_cpus(&nic_id.id); + le16_to_cpus(&nic_id.variant); + le16_to_cpus(&nic_id.major); + le16_to_cpus(&nic_id.minor); + dev_info(dev, "Hardware identity %04x:%04x:%04x:%04x\n", + nic_id.id, nic_id.variant, nic_id.major, nic_id.minor); + + if (hw_ver) + *hw_ver = (((nic_id.id & 0xff) << 24) | + ((nic_id.variant & 0xff) << 16) | + ((nic_id.major & 0xff) << 8) | + (nic_id.minor & 0xff)); + + priv->firmware_type = determine_firmware_type(&nic_id); + + /* Get the firmware version */ + err = HERMES_READ_RECORD(hw, USER_BAP, HERMES_RID_STAID, &sta_id); + if (err) { + dev_err(dev, "Cannot read station identity: error %d\n", + err); + return err; + } + + le16_to_cpus(&sta_id.id); + le16_to_cpus(&sta_id.variant); + le16_to_cpus(&sta_id.major); + le16_to_cpus(&sta_id.minor); + dev_info(dev, "Station identity %04x:%04x:%04x:%04x\n", + sta_id.id, sta_id.variant, sta_id.major, sta_id.minor); + + switch (sta_id.id) { + case 0x15: + dev_err(dev, "Primary firmware is active\n"); + return -ENODEV; + case 0x14b: + dev_err(dev, "Tertiary firmware is active\n"); + return -ENODEV; + case 0x1f: /* Intersil, Agere, Symbol Spectrum24 */ + case 0x21: /* Symbol Spectrum24 Trilogy */ + break; + default: + dev_notice(dev, "Unknown station ID, please report\n"); + break; + } + + /* Default capabilities */ + priv->has_sensitivity = 1; + priv->has_mwo = 0; + priv->has_preamble = 0; + priv->has_port3 = 1; + priv->has_ibss = 1; + priv->has_wep = 0; + priv->has_big_wep = 0; + priv->has_alt_txcntl = 0; + priv->has_ext_scan = 0; + priv->has_wpa = 0; + priv->do_fw_download = 0; + + /* Determine capabilities from the firmware version */ + switch (priv->firmware_type) { + case FIRMWARE_TYPE_AGERE: + /* Lucent Wavelan IEEE, Lucent Orinoco, Cabletron RoamAbout, + ELSA, Melco, HP, IBM, Dell 1150, Compaq 110/210 */ + if (fw_name) + snprintf(fw_name, fw_name_len, "Lucent/Agere %d.%02d", + sta_id.major, sta_id.minor); + + firmver = ((unsigned long)sta_id.major << 16) | sta_id.minor; + + priv->has_ibss = (firmver >= 0x60006); + priv->has_wep = (firmver >= 0x40020); + priv->has_big_wep = 1; /* FIXME: this is wrong - how do we tell + Gold cards from the others? */ + priv->has_mwo = (firmver >= 0x60000); + priv->has_pm = (firmver >= 0x40020); /* Don't work in 7.52 ? */ + priv->ibss_port = 1; + priv->has_hostscan = (firmver >= 0x8000a); + priv->do_fw_download = 1; + priv->broken_monitor = (firmver >= 0x80000); + priv->has_alt_txcntl = (firmver >= 0x90000); /* All 9.x ? */ + priv->has_ext_scan = (firmver >= 0x90000); /* All 9.x ? */ + priv->has_wpa = (firmver >= 0x9002a); + /* Tested with Agere firmware : + * 1.16 ; 4.08 ; 4.52 ; 6.04 ; 6.16 ; 7.28 => Jean II + * Tested CableTron firmware : 4.32 => Anton */ + break; + case FIRMWARE_TYPE_SYMBOL: + /* Symbol , 3Com AirConnect, Intel, Ericsson WLAN */ + /* Intel MAC : 00:02:B3:* */ + /* 3Com MAC : 00:50:DA:* */ + memset(tmp, 0, sizeof(tmp)); + /* Get the Symbol firmware version */ + err = hw->ops->read_ltv(hw, USER_BAP, + HERMES_RID_SECONDARYVERSION_SYMBOL, + SYMBOL_MAX_VER_LEN, NULL, &tmp); + if (err) { + dev_warn(dev, "Error %d reading Symbol firmware info. " + "Wildly guessing capabilities...\n", err); + firmver = 0; + tmp[0] = '\0'; + } else { + /* The firmware revision is a string, the format is + * something like : "V2.20-01". + * Quick and dirty parsing... - Jean II + */ + firmver = ((tmp[1] - '0') << 16) + | ((tmp[3] - '0') << 12) + | ((tmp[4] - '0') << 8) + | ((tmp[6] - '0') << 4) + | (tmp[7] - '0'); + + tmp[SYMBOL_MAX_VER_LEN] = '\0'; + } + + if (fw_name) + snprintf(fw_name, fw_name_len, "Symbol %s", tmp); + + priv->has_ibss = (firmver >= 0x20000); + priv->has_wep = (firmver >= 0x15012); + priv->has_big_wep = (firmver >= 0x20000); + priv->has_pm = (firmver >= 0x20000 && firmver < 0x22000) || + (firmver >= 0x29000 && firmver < 0x30000) || + firmver >= 0x31000; + priv->has_preamble = (firmver >= 0x20000); + priv->ibss_port = 4; + + /* Symbol firmware is found on various cards, but + * there has been no attempt to check firmware + * download on non-spectrum_cs based cards. + * + * Given that the Agere firmware download works + * differently, we should avoid doing a firmware + * download with the Symbol algorithm on non-spectrum + * cards. + * + * For now we can identify a spectrum_cs based card + * because it has a firmware reset function. + */ + priv->do_fw_download = (priv->stop_fw != NULL); + + priv->broken_disableport = (firmver == 0x25013) || + (firmver >= 0x30000 && firmver <= 0x31000); + priv->has_hostscan = (firmver >= 0x31001) || + (firmver >= 0x29057 && firmver < 0x30000); + /* Tested with Intel firmware : 0x20015 => Jean II */ + /* Tested with 3Com firmware : 0x15012 & 0x22001 => Jean II */ + break; + case FIRMWARE_TYPE_INTERSIL: + /* D-Link, Linksys, Adtron, ZoomAir, and many others... + * Samsung, Compaq 100/200 and Proxim are slightly + * different and less well tested */ + /* D-Link MAC : 00:40:05:* */ + /* Addtron MAC : 00:90:D1:* */ + if (fw_name) + snprintf(fw_name, fw_name_len, "Intersil %d.%d.%d", + sta_id.major, sta_id.minor, sta_id.variant); + + firmver = ((unsigned long)sta_id.major << 16) | + ((unsigned long)sta_id.minor << 8) | sta_id.variant; + + priv->has_ibss = (firmver >= 0x000700); /* FIXME */ + priv->has_big_wep = priv->has_wep = (firmver >= 0x000800); + priv->has_pm = (firmver >= 0x000700); + priv->has_hostscan = (firmver >= 0x010301); + + if (firmver >= 0x000800) + priv->ibss_port = 0; + else { + dev_notice(dev, "Intersil firmware earlier than v0.8.x" + " - several features not supported\n"); + priv->ibss_port = 1; + } + break; + } + if (fw_name) + dev_info(dev, "Firmware determined as %s\n", fw_name); + +#ifndef CPTCFG_HERMES_PRISM + if (priv->firmware_type == FIRMWARE_TYPE_INTERSIL) { + dev_err(dev, "Support for Prism chipset is not enabled\n"); + return -ENODEV; + } +#endif + + return 0; +} + +/* Read settings from EEPROM into our private structure. + * MAC address gets dropped into callers buffer + * Can be called before netdev registration. + */ +int orinoco_hw_read_card_settings(struct orinoco_private *priv, u8 *dev_addr) +{ + struct device *dev = priv->dev; + struct hermes_idstring nickbuf; + struct hermes *hw = &priv->hw; + int len; + int err; + u16 reclen; + + /* Get the MAC address */ + err = hw->ops->read_ltv(hw, USER_BAP, HERMES_RID_CNFOWNMACADDR, + ETH_ALEN, NULL, dev_addr); + if (err) { + dev_warn(dev, "Failed to read MAC address!\n"); + goto out; + } + + dev_dbg(dev, "MAC address %pM\n", dev_addr); + + /* Get the station name */ + err = hw->ops->read_ltv(hw, USER_BAP, HERMES_RID_CNFOWNNAME, + sizeof(nickbuf), &reclen, &nickbuf); + if (err) { + dev_err(dev, "failed to read station name\n"); + goto out; + } + if (nickbuf.len) + len = min(IW_ESSID_MAX_SIZE, (int)le16_to_cpu(nickbuf.len)); + else + len = min(IW_ESSID_MAX_SIZE, 2 * reclen); + memcpy(priv->nick, &nickbuf.val, len); + priv->nick[len] = '\0'; + + dev_dbg(dev, "Station name \"%s\"\n", priv->nick); + + /* Get allowed channels */ + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_CHANNELLIST, + &priv->channel_mask); + if (err) { + dev_err(dev, "Failed to read channel list!\n"); + goto out; + } + + /* Get initial AP density */ + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_CNFSYSTEMSCALE, + &priv->ap_density); + if (err || priv->ap_density < 1 || priv->ap_density > 3) + priv->has_sensitivity = 0; + + /* Get initial RTS threshold */ + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_CNFRTSTHRESHOLD, + &priv->rts_thresh); + if (err) { + dev_err(dev, "Failed to read RTS threshold!\n"); + goto out; + } + + /* Get initial fragmentation settings */ + if (priv->has_mwo) + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CNFMWOROBUST_AGERE, + &priv->mwo_robust); + else + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CNFFRAGMENTATIONTHRESHOLD, + &priv->frag_thresh); + if (err) { + dev_err(dev, "Failed to read fragmentation settings!\n"); + goto out; + } + + /* Power management setup */ + if (priv->has_pm) { + priv->pm_on = 0; + priv->pm_mcast = 1; + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CNFMAXSLEEPDURATION, + &priv->pm_period); + if (err) { + dev_err(dev, "Failed to read power management " + "period!\n"); + goto out; + } + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CNFPMHOLDOVERDURATION, + &priv->pm_timeout); + if (err) { + dev_err(dev, "Failed to read power management " + "timeout!\n"); + goto out; + } + } + + /* Preamble setup */ + if (priv->has_preamble) { + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CNFPREAMBLE_SYMBOL, + &priv->preamble); + if (err) { + dev_err(dev, "Failed to read preamble setup\n"); + goto out; + } + } + + /* Retry settings */ + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_SHORTRETRYLIMIT, + &priv->short_retry_limit); + if (err) { + dev_err(dev, "Failed to read short retry limit\n"); + goto out; + } + + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_LONGRETRYLIMIT, + &priv->long_retry_limit); + if (err) { + dev_err(dev, "Failed to read long retry limit\n"); + goto out; + } + + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_MAXTRANSMITLIFETIME, + &priv->retry_lifetime); + if (err) { + dev_err(dev, "Failed to read max retry lifetime\n"); + goto out; + } + +out: + return err; +} + +/* Can be called before netdev registration */ +int orinoco_hw_allocate_fid(struct orinoco_private *priv) +{ + struct device *dev = priv->dev; + struct hermes *hw = &priv->hw; + int err; + + err = hw->ops->allocate(hw, priv->nicbuf_size, &priv->txfid); + if (err == -EIO && priv->nicbuf_size > TX_NICBUF_SIZE_BUG) { + /* Try workaround for old Symbol firmware bug */ + priv->nicbuf_size = TX_NICBUF_SIZE_BUG; + err = hw->ops->allocate(hw, priv->nicbuf_size, &priv->txfid); + + dev_warn(dev, "Firmware ALLOC bug detected " + "(old Symbol firmware?). Work around %s\n", + err ? "failed!" : "ok."); + } + + return err; +} + +int orinoco_get_bitratemode(int bitrate, int automatic) +{ + int ratemode = -1; + int i; + + if ((bitrate != 10) && (bitrate != 20) && + (bitrate != 55) && (bitrate != 110)) + return ratemode; + + for (i = 0; i < BITRATE_TABLE_SIZE; i++) { + if ((bitrate_table[i].bitrate == bitrate) && + (bitrate_table[i].automatic == automatic)) { + ratemode = i; + break; + } + } + return ratemode; +} + +void orinoco_get_ratemode_cfg(int ratemode, int *bitrate, int *automatic) +{ + BUG_ON((ratemode < 0) || (ratemode >= BITRATE_TABLE_SIZE)); + + *bitrate = bitrate_table[ratemode].bitrate * 100000; + *automatic = bitrate_table[ratemode].automatic; +} + +int orinoco_hw_program_rids(struct orinoco_private *priv) +{ + struct net_device *dev = priv->ndev; + struct wireless_dev *wdev = netdev_priv(dev); + struct hermes *hw = &priv->hw; + int err; + struct hermes_idstring idbuf; + + /* Set the MAC address */ + err = hw->ops->write_ltv(hw, USER_BAP, HERMES_RID_CNFOWNMACADDR, + HERMES_BYTES_TO_RECLEN(ETH_ALEN), + dev->dev_addr); + if (err) { + printk(KERN_ERR "%s: Error %d setting MAC address\n", + dev->name, err); + return err; + } + + /* Set up the link mode */ + err = hermes_write_wordrec(hw, USER_BAP, HERMES_RID_CNFPORTTYPE, + priv->port_type); + if (err) { + printk(KERN_ERR "%s: Error %d setting port type\n", + dev->name, err); + return err; + } + /* Set the channel/frequency */ + if (priv->channel != 0 && priv->iw_mode != NL80211_IFTYPE_STATION) { + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFOWNCHANNEL, + priv->channel); + if (err) { + printk(KERN_ERR "%s: Error %d setting channel %d\n", + dev->name, err, priv->channel); + return err; + } + } + + if (priv->has_ibss) { + u16 createibss; + + if ((strlen(priv->desired_essid) == 0) && (priv->createibss)) { + printk(KERN_WARNING "%s: This firmware requires an " + "ESSID in IBSS-Ad-Hoc mode.\n", dev->name); + /* With wvlan_cs, in this case, we would crash. + * hopefully, this driver will behave better... + * Jean II */ + createibss = 0; + } else { + createibss = priv->createibss; + } + + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFCREATEIBSS, + createibss); + if (err) { + printk(KERN_ERR "%s: Error %d setting CREATEIBSS\n", + dev->name, err); + return err; + } + } + + /* Set the desired BSSID */ + err = __orinoco_hw_set_wap(priv); + if (err) { + printk(KERN_ERR "%s: Error %d setting AP address\n", + dev->name, err); + return err; + } + + /* Set the desired ESSID */ + idbuf.len = cpu_to_le16(strlen(priv->desired_essid)); + memcpy(&idbuf.val, priv->desired_essid, sizeof(idbuf.val)); + /* WinXP wants partner to configure OWNSSID even in IBSS mode. (jimc) */ + err = hw->ops->write_ltv(hw, USER_BAP, HERMES_RID_CNFOWNSSID, + HERMES_BYTES_TO_RECLEN(strlen(priv->desired_essid) + 2), + &idbuf); + if (err) { + printk(KERN_ERR "%s: Error %d setting OWNSSID\n", + dev->name, err); + return err; + } + err = hw->ops->write_ltv(hw, USER_BAP, HERMES_RID_CNFDESIREDSSID, + HERMES_BYTES_TO_RECLEN(strlen(priv->desired_essid) + 2), + &idbuf); + if (err) { + printk(KERN_ERR "%s: Error %d setting DESIREDSSID\n", + dev->name, err); + return err; + } + + /* Set the station name */ + idbuf.len = cpu_to_le16(strlen(priv->nick)); + memcpy(&idbuf.val, priv->nick, sizeof(idbuf.val)); + err = hw->ops->write_ltv(hw, USER_BAP, HERMES_RID_CNFOWNNAME, + HERMES_BYTES_TO_RECLEN(strlen(priv->nick) + 2), + &idbuf); + if (err) { + printk(KERN_ERR "%s: Error %d setting nickname\n", + dev->name, err); + return err; + } + + /* Set AP density */ + if (priv->has_sensitivity) { + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFSYSTEMSCALE, + priv->ap_density); + if (err) { + printk(KERN_WARNING "%s: Error %d setting SYSTEMSCALE. " + "Disabling sensitivity control\n", + dev->name, err); + + priv->has_sensitivity = 0; + } + } + + /* Set RTS threshold */ + err = hermes_write_wordrec(hw, USER_BAP, HERMES_RID_CNFRTSTHRESHOLD, + priv->rts_thresh); + if (err) { + printk(KERN_ERR "%s: Error %d setting RTS threshold\n", + dev->name, err); + return err; + } + + /* Set fragmentation threshold or MWO robustness */ + if (priv->has_mwo) + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFMWOROBUST_AGERE, + priv->mwo_robust); + else + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFFRAGMENTATIONTHRESHOLD, + priv->frag_thresh); + if (err) { + printk(KERN_ERR "%s: Error %d setting fragmentation\n", + dev->name, err); + return err; + } + + /* Set bitrate */ + err = __orinoco_hw_set_bitrate(priv); + if (err) { + printk(KERN_ERR "%s: Error %d setting bitrate\n", + dev->name, err); + return err; + } + + /* Set power management */ + if (priv->has_pm) { + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFPMENABLED, + priv->pm_on); + if (err) { + printk(KERN_ERR "%s: Error %d setting up PM\n", + dev->name, err); + return err; + } + + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFMULTICASTRECEIVE, + priv->pm_mcast); + if (err) { + printk(KERN_ERR "%s: Error %d setting up PM\n", + dev->name, err); + return err; + } + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFMAXSLEEPDURATION, + priv->pm_period); + if (err) { + printk(KERN_ERR "%s: Error %d setting up PM\n", + dev->name, err); + return err; + } + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFPMHOLDOVERDURATION, + priv->pm_timeout); + if (err) { + printk(KERN_ERR "%s: Error %d setting up PM\n", + dev->name, err); + return err; + } + } + + /* Set preamble - only for Symbol so far... */ + if (priv->has_preamble) { + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFPREAMBLE_SYMBOL, + priv->preamble); + if (err) { + printk(KERN_ERR "%s: Error %d setting preamble\n", + dev->name, err); + return err; + } + } + + /* Set up encryption */ + if (priv->has_wep || priv->has_wpa) { + err = __orinoco_hw_setup_enc(priv); + if (err) { + printk(KERN_ERR "%s: Error %d activating encryption\n", + dev->name, err); + return err; + } + } + + if (priv->iw_mode == NL80211_IFTYPE_MONITOR) { + /* Enable monitor mode */ + dev->type = ARPHRD_IEEE80211; + err = hw->ops->cmd_wait(hw, HERMES_CMD_TEST | + HERMES_TEST_MONITOR, 0, NULL); + } else { + /* Disable monitor mode */ + dev->type = ARPHRD_ETHER; + err = hw->ops->cmd_wait(hw, HERMES_CMD_TEST | + HERMES_TEST_STOP, 0, NULL); + } + if (err) + return err; + + /* Reset promiscuity / multicast*/ + priv->promiscuous = 0; + priv->mc_count = 0; + + /* Record mode change */ + wdev->iftype = priv->iw_mode; + + return 0; +} + +/* Get tsc from the firmware */ +int orinoco_hw_get_tkip_iv(struct orinoco_private *priv, int key, u8 *tsc) +{ + struct hermes *hw = &priv->hw; + int err = 0; + u8 tsc_arr[4][ORINOCO_SEQ_LEN]; + + if ((key < 0) || (key >= 4)) + return -EINVAL; + + err = hw->ops->read_ltv(hw, USER_BAP, HERMES_RID_CURRENT_TKIP_IV, + sizeof(tsc_arr), NULL, &tsc_arr); + if (!err) + memcpy(tsc, &tsc_arr[key][0], sizeof(tsc_arr[0])); + + return err; +} + +int __orinoco_hw_set_bitrate(struct orinoco_private *priv) +{ + struct hermes *hw = &priv->hw; + int ratemode = priv->bitratemode; + int err = 0; + + if (ratemode >= BITRATE_TABLE_SIZE) { + printk(KERN_ERR "%s: BUG: Invalid bitrate mode %d\n", + priv->ndev->name, ratemode); + return -EINVAL; + } + + switch (priv->firmware_type) { + case FIRMWARE_TYPE_AGERE: + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFTXRATECONTROL, + bitrate_table[ratemode].agere_txratectrl); + break; + case FIRMWARE_TYPE_INTERSIL: + case FIRMWARE_TYPE_SYMBOL: + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFTXRATECONTROL, + bitrate_table[ratemode].intersil_txratectrl); + break; + default: + BUG(); + } + + return err; +} + +int orinoco_hw_get_act_bitrate(struct orinoco_private *priv, int *bitrate) +{ + struct hermes *hw = &priv->hw; + int i; + int err = 0; + u16 val; + + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CURRENTTXRATE, &val); + if (err) + return err; + + switch (priv->firmware_type) { + case FIRMWARE_TYPE_AGERE: /* Lucent style rate */ + /* Note : in Lucent firmware, the return value of + * HERMES_RID_CURRENTTXRATE is the bitrate in Mb/s, + * and therefore is totally different from the + * encoding of HERMES_RID_CNFTXRATECONTROL. + * Don't forget that 6Mb/s is really 5.5Mb/s */ + if (val == 6) + *bitrate = 5500000; + else + *bitrate = val * 1000000; + break; + case FIRMWARE_TYPE_INTERSIL: /* Intersil style rate */ + case FIRMWARE_TYPE_SYMBOL: /* Symbol style rate */ + for (i = 0; i < BITRATE_TABLE_SIZE; i++) + if (bitrate_table[i].intersil_txratectrl == val) { + *bitrate = bitrate_table[i].bitrate * 100000; + break; + } + + if (i >= BITRATE_TABLE_SIZE) { + printk(KERN_INFO "%s: Unable to determine current bitrate (0x%04hx)\n", + priv->ndev->name, val); + err = -EIO; + } + + break; + default: + BUG(); + } + + return err; +} + +/* Set fixed AP address */ +int __orinoco_hw_set_wap(struct orinoco_private *priv) +{ + int roaming_flag; + int err = 0; + struct hermes *hw = &priv->hw; + + switch (priv->firmware_type) { + case FIRMWARE_TYPE_AGERE: + /* not supported */ + break; + case FIRMWARE_TYPE_INTERSIL: + if (priv->bssid_fixed) + roaming_flag = 2; + else + roaming_flag = 1; + + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFROAMINGMODE, + roaming_flag); + break; + case FIRMWARE_TYPE_SYMBOL: + err = HERMES_WRITE_RECORD(hw, USER_BAP, + HERMES_RID_CNFMANDATORYBSSID_SYMBOL, + &priv->desired_bssid); + break; + } + return err; +} + +/* Change the WEP keys and/or the current keys. Can be called + * either from __orinoco_hw_setup_enc() or directly from + * orinoco_ioctl_setiwencode(). In the later case the association + * with the AP is not broken (if the firmware can handle it), + * which is needed for 802.1x implementations. */ +int __orinoco_hw_setup_wepkeys(struct orinoco_private *priv) +{ + struct hermes *hw = &priv->hw; + int err = 0; + int i; + + switch (priv->firmware_type) { + case FIRMWARE_TYPE_AGERE: + { + struct orinoco_key keys[ORINOCO_MAX_KEYS]; + + memset(&keys, 0, sizeof(keys)); + for (i = 0; i < ORINOCO_MAX_KEYS; i++) { + int len = min(priv->keys[i].key_len, + ORINOCO_MAX_KEY_SIZE); + memcpy(&keys[i].data, priv->keys[i].key, len); + if (len > SMALL_KEY_SIZE) + keys[i].len = cpu_to_le16(LARGE_KEY_SIZE); + else if (len > 0) + keys[i].len = cpu_to_le16(SMALL_KEY_SIZE); + else + keys[i].len = cpu_to_le16(0); + } + + err = HERMES_WRITE_RECORD(hw, USER_BAP, + HERMES_RID_CNFWEPKEYS_AGERE, + &keys); + if (err) + return err; + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFTXKEY_AGERE, + priv->tx_key); + if (err) + return err; + break; + } + case FIRMWARE_TYPE_INTERSIL: + case FIRMWARE_TYPE_SYMBOL: + { + int keylen; + + /* Force uniform key length to work around + * firmware bugs */ + keylen = priv->keys[priv->tx_key].key_len; + + if (keylen > LARGE_KEY_SIZE) { + printk(KERN_ERR "%s: BUG: Key %d has oversize length %d.\n", + priv->ndev->name, priv->tx_key, keylen); + return -E2BIG; + } else if (keylen > SMALL_KEY_SIZE) + keylen = LARGE_KEY_SIZE; + else if (keylen > 0) + keylen = SMALL_KEY_SIZE; + else + keylen = 0; + + /* Write all 4 keys */ + for (i = 0; i < ORINOCO_MAX_KEYS; i++) { + u8 key[LARGE_KEY_SIZE] = { 0 }; + + memcpy(key, priv->keys[i].key, + priv->keys[i].key_len); + + err = hw->ops->write_ltv(hw, USER_BAP, + HERMES_RID_CNFDEFAULTKEY0 + i, + HERMES_BYTES_TO_RECLEN(keylen), + key); + if (err) + return err; + } + + /* Write the index of the key used in transmission */ + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFWEPDEFAULTKEYID, + priv->tx_key); + if (err) + return err; + } + break; + } + + return 0; +} + +int __orinoco_hw_setup_enc(struct orinoco_private *priv) +{ + struct hermes *hw = &priv->hw; + int err = 0; + int master_wep_flag; + int auth_flag; + int enc_flag; + + /* Setup WEP keys */ + if (priv->encode_alg == ORINOCO_ALG_WEP) + __orinoco_hw_setup_wepkeys(priv); + + if (priv->wep_restrict) + auth_flag = HERMES_AUTH_SHARED_KEY; + else + auth_flag = HERMES_AUTH_OPEN; + + if (priv->wpa_enabled) + enc_flag = 2; + else if (priv->encode_alg == ORINOCO_ALG_WEP) + enc_flag = 1; + else + enc_flag = 0; + + switch (priv->firmware_type) { + case FIRMWARE_TYPE_AGERE: /* Agere style WEP */ + if (priv->encode_alg == ORINOCO_ALG_WEP) { + /* Enable the shared-key authentication. */ + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFAUTHENTICATION_AGERE, + auth_flag); + } + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFWEPENABLED_AGERE, + enc_flag); + if (err) + return err; + + if (priv->has_wpa) { + /* Set WPA key management */ + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFSETWPAAUTHMGMTSUITE_AGERE, + priv->key_mgmt); + if (err) + return err; + } + + break; + + case FIRMWARE_TYPE_INTERSIL: /* Intersil style WEP */ + case FIRMWARE_TYPE_SYMBOL: /* Symbol style WEP */ + if (priv->encode_alg == ORINOCO_ALG_WEP) { + if (priv->wep_restrict || + (priv->firmware_type == FIRMWARE_TYPE_SYMBOL)) + master_wep_flag = HERMES_WEP_PRIVACY_INVOKED | + HERMES_WEP_EXCL_UNENCRYPTED; + else + master_wep_flag = HERMES_WEP_PRIVACY_INVOKED; + + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFAUTHENTICATION, + auth_flag); + if (err) + return err; + } else + master_wep_flag = 0; + + if (priv->iw_mode == NL80211_IFTYPE_MONITOR) + master_wep_flag |= HERMES_WEP_HOST_DECRYPT; + + /* Master WEP setting : on/off */ + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFWEPFLAGS_INTERSIL, + master_wep_flag); + if (err) + return err; + + break; + } + + return 0; +} + +/* key must be 32 bytes, including the tx and rx MIC keys. + * rsc must be NULL or up to 8 bytes + * tsc must be NULL or up to 8 bytes + */ +int __orinoco_hw_set_tkip_key(struct orinoco_private *priv, int key_idx, + int set_tx, u8 *key, u8 *rsc, size_t rsc_len, + u8 *tsc, size_t tsc_len) +{ + struct { + __le16 idx; + u8 rsc[ORINOCO_SEQ_LEN]; + u8 key[TKIP_KEYLEN]; + u8 tx_mic[MIC_KEYLEN]; + u8 rx_mic[MIC_KEYLEN]; + u8 tsc[ORINOCO_SEQ_LEN]; + } __packed buf; + struct hermes *hw = &priv->hw; + int ret; + int err; + int k; + u16 xmitting; + + key_idx &= 0x3; + + if (set_tx) + key_idx |= 0x8000; + + buf.idx = cpu_to_le16(key_idx); + memcpy(buf.key, key, + sizeof(buf.key) + sizeof(buf.tx_mic) + sizeof(buf.rx_mic)); + + if (rsc_len > sizeof(buf.rsc)) + rsc_len = sizeof(buf.rsc); + + if (tsc_len > sizeof(buf.tsc)) + tsc_len = sizeof(buf.tsc); + + memset(buf.rsc, 0, sizeof(buf.rsc)); + memset(buf.tsc, 0, sizeof(buf.tsc)); + + if (rsc != NULL) + memcpy(buf.rsc, rsc, rsc_len); + + if (tsc != NULL) + memcpy(buf.tsc, tsc, tsc_len); + else + buf.tsc[4] = 0x10; + + /* Wait up to 100ms for tx queue to empty */ + for (k = 100; k > 0; k--) { + udelay(1000); + ret = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_TXQUEUEEMPTY, + &xmitting); + if (ret || !xmitting) + break; + } + + if (k == 0) + ret = -ETIMEDOUT; + + err = HERMES_WRITE_RECORD(hw, USER_BAP, + HERMES_RID_CNFADDDEFAULTTKIPKEY_AGERE, + &buf); + + return ret ? ret : err; +} + +int orinoco_clear_tkip_key(struct orinoco_private *priv, int key_idx) +{ + struct hermes *hw = &priv->hw; + int err; + + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFREMDEFAULTTKIPKEY_AGERE, + key_idx); + if (err) + printk(KERN_WARNING "%s: Error %d clearing TKIP key %d\n", + priv->ndev->name, err, key_idx); + return err; +} + +int __orinoco_hw_set_multicast_list(struct orinoco_private *priv, + struct net_device *dev, + int mc_count, int promisc) +{ + struct hermes *hw = &priv->hw; + int err = 0; + + if (promisc != priv->promiscuous) { + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFPROMISCUOUSMODE, + promisc); + if (err) { + printk(KERN_ERR "%s: Error %d setting PROMISCUOUSMODE to 1.\n", + priv->ndev->name, err); + } else + priv->promiscuous = promisc; + } + + /* If we're not in promiscuous mode, then we need to set the + * group address if either we want to multicast, or if we were + * multicasting and want to stop */ + if (!promisc && (mc_count || priv->mc_count)) { + struct netdev_hw_addr *ha; + struct hermes_multicast mclist; + int i = 0; + + netdev_for_each_mc_addr(ha, dev) { + if (i == mc_count) + break; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + memcpy(mclist.addr[i++], ha->addr, ETH_ALEN); +#else + memcpy(mclist.addr[i++], ha->dmi_addr, ETH_ALEN); +#endif + } + + err = hw->ops->write_ltv(hw, USER_BAP, + HERMES_RID_CNFGROUPADDRESSES, + HERMES_BYTES_TO_RECLEN(mc_count * ETH_ALEN), + &mclist); + if (err) + printk(KERN_ERR "%s: Error %d setting multicast list.\n", + priv->ndev->name, err); + else + priv->mc_count = mc_count; + } + return err; +} + +/* Return : < 0 -> error code ; >= 0 -> length */ +int orinoco_hw_get_essid(struct orinoco_private *priv, int *active, + char buf[IW_ESSID_MAX_SIZE + 1]) +{ + struct hermes *hw = &priv->hw; + int err = 0; + struct hermes_idstring essidbuf; + char *p = (char *)(&essidbuf.val); + int len; + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + if (strlen(priv->desired_essid) > 0) { + /* We read the desired SSID from the hardware rather + than from priv->desired_essid, just in case the + firmware is allowed to change it on us. I'm not + sure about this */ + /* My guess is that the OWNSSID should always be whatever + * we set to the card, whereas CURRENT_SSID is the one that + * may change... - Jean II */ + u16 rid; + + *active = 1; + + rid = (priv->port_type == 3) ? HERMES_RID_CNFOWNSSID : + HERMES_RID_CNFDESIREDSSID; + + err = hw->ops->read_ltv(hw, USER_BAP, rid, sizeof(essidbuf), + NULL, &essidbuf); + if (err) + goto fail_unlock; + } else { + *active = 0; + + err = hw->ops->read_ltv(hw, USER_BAP, HERMES_RID_CURRENTSSID, + sizeof(essidbuf), NULL, &essidbuf); + if (err) + goto fail_unlock; + } + + len = le16_to_cpu(essidbuf.len); + BUG_ON(len > IW_ESSID_MAX_SIZE); + + memset(buf, 0, IW_ESSID_MAX_SIZE); + memcpy(buf, p, len); + err = len; + + fail_unlock: + orinoco_unlock(priv, &flags); + + return err; +} + +int orinoco_hw_get_freq(struct orinoco_private *priv) +{ + struct hermes *hw = &priv->hw; + int err = 0; + u16 channel; + int freq = 0; + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_CURRENTCHANNEL, + &channel); + if (err) + goto out; + + /* Intersil firmware 1.3.5 returns 0 when the interface is down */ + if (channel == 0) { + err = -EBUSY; + goto out; + } + + if ((channel < 1) || (channel > NUM_CHANNELS)) { + printk(KERN_WARNING "%s: Channel out of range (%d)!\n", + priv->ndev->name, channel); + err = -EBUSY; + goto out; + + } + freq = ieee80211_dsss_chan_to_freq(channel); + + out: + orinoco_unlock(priv, &flags); + + if (err > 0) + err = -EBUSY; + return err ? err : freq; +} + +int orinoco_hw_get_bitratelist(struct orinoco_private *priv, + int *numrates, s32 *rates, int max) +{ + struct hermes *hw = &priv->hw; + struct hermes_idstring list; + unsigned char *p = (unsigned char *)&list.val; + int err = 0; + int num; + int i; + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + err = hw->ops->read_ltv(hw, USER_BAP, HERMES_RID_SUPPORTEDDATARATES, + sizeof(list), NULL, &list); + orinoco_unlock(priv, &flags); + + if (err) + return err; + + num = le16_to_cpu(list.len); + *numrates = num; + num = min(num, max); + + for (i = 0; i < num; i++) + rates[i] = (p[i] & 0x7f) * 500000; /* convert to bps */ + + return 0; +} + +int orinoco_hw_trigger_scan(struct orinoco_private *priv, + const struct cfg80211_ssid *ssid) +{ + struct net_device *dev = priv->ndev; + struct hermes *hw = &priv->hw; + unsigned long flags; + int err = 0; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + /* Scanning with port 0 disabled would fail */ + if (!netif_running(dev)) { + err = -ENETDOWN; + goto out; + } + + /* In monitor mode, the scan results are always empty. + * Probe responses are passed to the driver as received + * frames and could be processed in software. */ + if (priv->iw_mode == NL80211_IFTYPE_MONITOR) { + err = -EOPNOTSUPP; + goto out; + } + + if (priv->has_hostscan) { + switch (priv->firmware_type) { + case FIRMWARE_TYPE_SYMBOL: + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFHOSTSCAN_SYMBOL, + HERMES_HOSTSCAN_SYMBOL_ONCE | + HERMES_HOSTSCAN_SYMBOL_BCAST); + break; + case FIRMWARE_TYPE_INTERSIL: { + __le16 req[3]; + + req[0] = cpu_to_le16(0x3fff); /* All channels */ + req[1] = cpu_to_le16(0x0001); /* rate 1 Mbps */ + req[2] = 0; /* Any ESSID */ + err = HERMES_WRITE_RECORD(hw, USER_BAP, + HERMES_RID_CNFHOSTSCAN, &req); + break; + } + case FIRMWARE_TYPE_AGERE: + if (ssid->ssid_len > 0) { + struct hermes_idstring idbuf; + size_t len = ssid->ssid_len; + + idbuf.len = cpu_to_le16(len); + memcpy(idbuf.val, ssid->ssid, len); + + err = hw->ops->write_ltv(hw, USER_BAP, + HERMES_RID_CNFSCANSSID_AGERE, + HERMES_BYTES_TO_RECLEN(len + 2), + &idbuf); + } else + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFSCANSSID_AGERE, + 0); /* Any ESSID */ + if (err) + break; + + if (priv->has_ext_scan) { + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFSCANCHANNELS2GHZ, + 0x7FFF); + if (err) + goto out; + + err = hermes_inquire(hw, + HERMES_INQ_CHANNELINFO); + } else + err = hermes_inquire(hw, HERMES_INQ_SCAN); + + break; + } + } else + err = hermes_inquire(hw, HERMES_INQ_SCAN); + + out: + orinoco_unlock(priv, &flags); + + return err; +} + +/* Disassociate from node with BSSID addr */ +int orinoco_hw_disassociate(struct orinoco_private *priv, + u8 *addr, u16 reason_code) +{ + struct hermes *hw = &priv->hw; + int err; + + struct { + u8 addr[ETH_ALEN]; + __le16 reason_code; + } __packed buf; + + /* Currently only supported by WPA enabled Agere fw */ + if (!priv->has_wpa) + return -EOPNOTSUPP; + + memcpy(buf.addr, addr, ETH_ALEN); + buf.reason_code = cpu_to_le16(reason_code); + err = HERMES_WRITE_RECORD(hw, USER_BAP, + HERMES_RID_CNFDISASSOCIATE, + &buf); + return err; +} + +int orinoco_hw_get_current_bssid(struct orinoco_private *priv, + u8 *addr) +{ + struct hermes *hw = &priv->hw; + int err; + + err = hw->ops->read_ltv(hw, USER_BAP, HERMES_RID_CURRENTBSSID, + ETH_ALEN, NULL, addr); + + return err; +} diff --git a/drivers/net/wireless/orinoco/hw.h b/drivers/net/wireless/orinoco/hw.h new file mode 100644 index 0000000..e2abfc2 --- /dev/null +++ b/drivers/net/wireless/orinoco/hw.h @@ -0,0 +1,62 @@ +/* Encapsulate basic setting changes on Hermes hardware + * + * See copyright notice in main.c + */ +#ifndef _ORINOCO_HW_H_ +#define _ORINOCO_HW_H_ + +#include +#include +#include + +/* Hardware BAPs */ +#define USER_BAP 0 +#define IRQ_BAP 1 + +/* WEP key sizes */ +#define SMALL_KEY_SIZE 5 +#define LARGE_KEY_SIZE 13 + +/* Number of supported channels */ +#define NUM_CHANNELS 14 + +/* Forward declarations */ +struct orinoco_private; +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)) +struct dev_addr_list; +#endif + +int determine_fw_capabilities(struct orinoco_private *priv, char *fw_name, + size_t fw_name_len, u32 *hw_ver); +int orinoco_hw_read_card_settings(struct orinoco_private *priv, u8 *dev_addr); +int orinoco_hw_allocate_fid(struct orinoco_private *priv); +int orinoco_get_bitratemode(int bitrate, int automatic); +void orinoco_get_ratemode_cfg(int ratemode, int *bitrate, int *automatic); + +int orinoco_hw_program_rids(struct orinoco_private *priv); +int orinoco_hw_get_tkip_iv(struct orinoco_private *priv, int key, u8 *tsc); +int __orinoco_hw_set_bitrate(struct orinoco_private *priv); +int orinoco_hw_get_act_bitrate(struct orinoco_private *priv, int *bitrate); +int __orinoco_hw_set_wap(struct orinoco_private *priv); +int __orinoco_hw_setup_wepkeys(struct orinoco_private *priv); +int __orinoco_hw_setup_enc(struct orinoco_private *priv); +int __orinoco_hw_set_tkip_key(struct orinoco_private *priv, int key_idx, + int set_tx, u8 *key, u8 *rsc, size_t rsc_len, + u8 *tsc, size_t tsc_len); +int orinoco_clear_tkip_key(struct orinoco_private *priv, int key_idx); +int __orinoco_hw_set_multicast_list(struct orinoco_private *priv, + struct net_device *dev, + int mc_count, int promisc); +int orinoco_hw_get_essid(struct orinoco_private *priv, int *active, + char buf[IW_ESSID_MAX_SIZE + 1]); +int orinoco_hw_get_freq(struct orinoco_private *priv); +int orinoco_hw_get_bitratelist(struct orinoco_private *priv, + int *numrates, s32 *rates, int max); +int orinoco_hw_trigger_scan(struct orinoco_private *priv, + const struct cfg80211_ssid *ssid); +int orinoco_hw_disassociate(struct orinoco_private *priv, + u8 *addr, u16 reason_code); +int orinoco_hw_get_current_bssid(struct orinoco_private *priv, + u8 *addr); + +#endif /* _ORINOCO_HW_H_ */ diff --git a/drivers/net/wireless/orinoco/main.c b/drivers/net/wireless/orinoco/main.c new file mode 100644 index 0000000..f1b812e --- /dev/null +++ b/drivers/net/wireless/orinoco/main.c @@ -0,0 +1,2435 @@ +/* main.c - (formerly known as dldwd_cs.c, orinoco_cs.c and orinoco.c) + * + * A driver for Hermes or Prism 2 chipset based PCMCIA wireless + * adaptors, with Lucent/Agere, Intersil or Symbol firmware. + * + * Current maintainers (as of 29 September 2003) are: + * Pavel Roskin + * and David Gibson + * + * (C) Copyright David Gibson, IBM Corporation 2001-2003. + * Copyright (C) 2000 David Gibson, Linuxcare Australia. + * With some help from : + * Copyright (C) 2001 Jean Tourrilhes, HP Labs + * Copyright (C) 2001 Benjamin Herrenschmidt + * + * Based on dummy_cs.c 1.27 2000/06/12 21:27:25 + * + * Portions based on wvlan_cs.c 1.0.6, Copyright Andreas Neuhaus + * http://www.stud.fh-dortmund.de/~andy/wvlan/ + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * . Portions created by David + * A. Hinds are Copyright (C) 1999 David A. Hinds. All Rights + * Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. */ + +/* + * TODO + * o Handle de-encapsulation within network layer, provide 802.11 + * headers (patch from Thomas 'Dent' Mirlacher) + * o Fix possible races in SPY handling. + * o Disconnect wireless extensions from fundamental configuration. + * o (maybe) Software WEP support (patch from Stano Meduna). + * o (maybe) Use multiple Tx buffers - driver handling queue + * rather than firmware. + */ + +/* Locking and synchronization: + * + * The basic principle is that everything is serialized through a + * single spinlock, priv->lock. The lock is used in user, bh and irq + * context, so when taken outside hardirq context it should always be + * taken with interrupts disabled. The lock protects both the + * hardware and the struct orinoco_private. + * + * Another flag, priv->hw_unavailable indicates that the hardware is + * unavailable for an extended period of time (e.g. suspended, or in + * the middle of a hard reset). This flag is protected by the + * spinlock. All code which touches the hardware should check the + * flag after taking the lock, and if it is set, give up on whatever + * they are doing and drop the lock again. The orinoco_lock() + * function handles this (it unlocks and returns -EBUSY if + * hw_unavailable is non-zero). + */ + +#define DRIVER_NAME "orinoco" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hermes_rid.h" +#include "hermes_dld.h" +#include "hw.h" +#include "scan.h" +#include "mic.h" +#include "fw.h" +#include "wext.h" +#include "cfg.h" +#include "main.h" + +#include "orinoco.h" + +/********************************************************************/ +/* Module information */ +/********************************************************************/ + +MODULE_AUTHOR("Pavel Roskin & " + "David Gibson "); +MODULE_DESCRIPTION("Driver for Lucent Orinoco, Prism II based " + "and similar wireless cards"); +MODULE_LICENSE("Dual MPL/GPL"); + +/* Level of debugging. Used in the macros in orinoco.h */ +#ifdef ORINOCO_DEBUG +int orinoco_debug = ORINOCO_DEBUG; +EXPORT_SYMBOL(orinoco_debug); +module_param(orinoco_debug, int, 0644); +MODULE_PARM_DESC(orinoco_debug, "Debug level"); +#endif + +static bool suppress_linkstatus; /* = 0 */ +module_param(suppress_linkstatus, bool, 0644); +MODULE_PARM_DESC(suppress_linkstatus, "Don't log link status changes"); + +static int ignore_disconnect; /* = 0 */ +module_param(ignore_disconnect, int, 0644); +MODULE_PARM_DESC(ignore_disconnect, + "Don't report lost link to the network layer"); + +int force_monitor; /* = 0 */ +module_param(force_monitor, int, 0644); +MODULE_PARM_DESC(force_monitor, "Allow monitor mode for all firmware versions"); + +/********************************************************************/ +/* Internal constants */ +/********************************************************************/ + +/* 802.2 LLC/SNAP header used for Ethernet encapsulation over 802.11 */ +static const u8 encaps_hdr[] = {0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00}; +#define ENCAPS_OVERHEAD (sizeof(encaps_hdr) + 2) + +#define ORINOCO_MIN_MTU 256 +#define ORINOCO_MAX_MTU (IEEE80211_MAX_DATA_LEN - ENCAPS_OVERHEAD) + +#define MAX_IRQLOOPS_PER_IRQ 10 +#define MAX_IRQLOOPS_PER_JIFFY (20000 / HZ) /* Based on a guestimate of + * how many events the + * device could + * legitimately generate */ + +#define DUMMY_FID 0xFFFF + +/*#define MAX_MULTICAST(priv) (priv->firmware_type == FIRMWARE_TYPE_AGERE ? \ + HERMES_MAX_MULTICAST : 0)*/ +#define MAX_MULTICAST(priv) (HERMES_MAX_MULTICAST) + +#define ORINOCO_INTEN (HERMES_EV_RX | HERMES_EV_ALLOC \ + | HERMES_EV_TX | HERMES_EV_TXEXC \ + | HERMES_EV_WTERR | HERMES_EV_INFO \ + | HERMES_EV_INFDROP) + +/********************************************************************/ +/* Data types */ +/********************************************************************/ + +/* Beginning of the Tx descriptor, used in TxExc handling */ +struct hermes_txexc_data { + struct hermes_tx_descriptor desc; + __le16 frame_ctl; + __le16 duration_id; + u8 addr1[ETH_ALEN]; +} __packed; + +/* Rx frame header except compatibility 802.3 header */ +struct hermes_rx_descriptor { + /* Control */ + __le16 status; + __le32 time; + u8 silence; + u8 signal; + u8 rate; + u8 rxflow; + __le32 reserved; + + /* 802.11 header */ + __le16 frame_ctl; + __le16 duration_id; + u8 addr1[ETH_ALEN]; + u8 addr2[ETH_ALEN]; + u8 addr3[ETH_ALEN]; + __le16 seq_ctl; + u8 addr4[ETH_ALEN]; + + /* Data length */ + __le16 data_len; +} __packed; + +struct orinoco_rx_data { + struct hermes_rx_descriptor *desc; + struct sk_buff *skb; + struct list_head list; +}; + +struct orinoco_scan_data { + void *buf; + size_t len; + int type; + struct list_head list; +}; + +/********************************************************************/ +/* Function prototypes */ +/********************************************************************/ + +static int __orinoco_set_multicast_list(struct net_device *dev); +static int __orinoco_up(struct orinoco_private *priv); +static int __orinoco_down(struct orinoco_private *priv); +static int __orinoco_commit(struct orinoco_private *priv); + +/********************************************************************/ +/* Internal helper functions */ +/********************************************************************/ + +void set_port_type(struct orinoco_private *priv) +{ + switch (priv->iw_mode) { + case NL80211_IFTYPE_STATION: + priv->port_type = 1; + priv->createibss = 0; + break; + case NL80211_IFTYPE_ADHOC: + if (priv->prefer_port3) { + priv->port_type = 3; + priv->createibss = 0; + } else { + priv->port_type = priv->ibss_port; + priv->createibss = 1; + } + break; + case NL80211_IFTYPE_MONITOR: + priv->port_type = 3; + priv->createibss = 0; + break; + default: + printk(KERN_ERR "%s: Invalid priv->iw_mode in set_port_type()\n", + priv->ndev->name); + } +} + +/********************************************************************/ +/* Device methods */ +/********************************************************************/ + +int orinoco_open(struct net_device *dev) +{ + struct orinoco_private *priv = ndev_priv(dev); + unsigned long flags; + int err; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + err = __orinoco_up(priv); + + if (!err) + priv->open = 1; + + orinoco_unlock(priv, &flags); + + return err; +} +EXPORT_SYMBOL(orinoco_open); + +int orinoco_stop(struct net_device *dev) +{ + struct orinoco_private *priv = ndev_priv(dev); + int err = 0; + + /* We mustn't use orinoco_lock() here, because we need to be + able to close the interface even if hw_unavailable is set + (e.g. as we're released after a PC Card removal) */ + orinoco_lock_irq(priv); + + priv->open = 0; + + err = __orinoco_down(priv); + + orinoco_unlock_irq(priv); + + return err; +} +EXPORT_SYMBOL(orinoco_stop); + +struct net_device_stats *orinoco_get_stats(struct net_device *dev) +{ + struct orinoco_private *priv = ndev_priv(dev); + + return &priv->stats; +} +EXPORT_SYMBOL(orinoco_get_stats); + +void orinoco_set_multicast_list(struct net_device *dev) +{ + struct orinoco_private *priv = ndev_priv(dev); + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) { + printk(KERN_DEBUG "%s: orinoco_set_multicast_list() " + "called when hw_unavailable\n", dev->name); + return; + } + + __orinoco_set_multicast_list(dev); + orinoco_unlock(priv, &flags); +} +EXPORT_SYMBOL(orinoco_set_multicast_list); + +int orinoco_change_mtu(struct net_device *dev, int new_mtu) +{ + struct orinoco_private *priv = ndev_priv(dev); + + if ((new_mtu < ORINOCO_MIN_MTU) || (new_mtu > ORINOCO_MAX_MTU)) + return -EINVAL; + + /* MTU + encapsulation + header length */ + if ((new_mtu + ENCAPS_OVERHEAD + sizeof(struct ieee80211_hdr)) > + (priv->nicbuf_size - ETH_HLEN)) + return -EINVAL; + + dev->mtu = new_mtu; + + return 0; +} +EXPORT_SYMBOL(orinoco_change_mtu); + +/********************************************************************/ +/* Tx path */ +/********************************************************************/ + +/* Add encapsulation and MIC to the existing SKB. + * The main xmit routine will then send the whole lot to the card. + * Need 8 bytes headroom + * Need 8 bytes tailroom + * + * With encapsulated ethernet II frame + * -------- + * 803.3 header (14 bytes) + * dst[6] + * -------- src[6] + * 803.3 header (14 bytes) len[2] + * dst[6] 803.2 header (8 bytes) + * src[6] encaps[6] + * len[2] <- leave alone -> len[2] + * -------- -------- <-- 0 + * Payload Payload + * ... ... + * + * -------- -------- + * MIC (8 bytes) + * -------- + * + * returns 0 on success, -ENOMEM on error. + */ +int orinoco_process_xmit_skb(struct sk_buff *skb, + struct net_device *dev, + struct orinoco_private *priv, + int *tx_control, + u8 *mic_buf) +{ + struct orinoco_tkip_key *key; + struct ethhdr *eh; + int do_mic; + + key = (struct orinoco_tkip_key *) priv->keys[priv->tx_key].key; + + do_mic = ((priv->encode_alg == ORINOCO_ALG_TKIP) && + (key != NULL)); + + if (do_mic) + *tx_control |= (priv->tx_key << HERMES_MIC_KEY_ID_SHIFT) | + HERMES_TXCTRL_MIC; + + eh = (struct ethhdr *)skb->data; + + /* Encapsulate Ethernet-II frames */ + if (ntohs(eh->h_proto) > ETH_DATA_LEN) { /* Ethernet-II frame */ + struct header_struct { + struct ethhdr eth; /* 802.3 header */ + u8 encap[6]; /* 802.2 header */ + } __packed hdr; + int len = skb->len + sizeof(encaps_hdr) - (2 * ETH_ALEN); + + if (skb_headroom(skb) < ENCAPS_OVERHEAD) { + if (net_ratelimit()) + printk(KERN_ERR + "%s: Not enough headroom for 802.2 headers %d\n", + dev->name, skb_headroom(skb)); + return -ENOMEM; + } + + /* Fill in new header */ + memcpy(&hdr.eth, eh, 2 * ETH_ALEN); + hdr.eth.h_proto = htons(len); + memcpy(hdr.encap, encaps_hdr, sizeof(encaps_hdr)); + + /* Make room for the new header, and copy it in */ + eh = (struct ethhdr *) skb_push(skb, ENCAPS_OVERHEAD); + memcpy(eh, &hdr, sizeof(hdr)); + } + + /* Calculate Michael MIC */ + if (do_mic) { + size_t len = skb->len - ETH_HLEN; + u8 *mic = &mic_buf[0]; + + /* Have to write to an even address, so copy the spare + * byte across */ + if (skb->len % 2) { + *mic = skb->data[skb->len - 1]; + mic++; + } + + orinoco_mic(priv->tx_tfm_mic, key->tx_mic, + eh->h_dest, eh->h_source, 0 /* priority */, + skb->data + ETH_HLEN, + len, mic); + } + + return 0; +} +EXPORT_SYMBOL(orinoco_process_xmit_skb); + +static netdev_tx_t orinoco_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct orinoco_private *priv = ndev_priv(dev); + struct net_device_stats *stats = &priv->stats; + struct hermes *hw = &priv->hw; + int err = 0; + u16 txfid = priv->txfid; + int tx_control; + unsigned long flags; + u8 mic_buf[MICHAEL_MIC_LEN + 1]; + + if (!netif_running(dev)) { + printk(KERN_ERR "%s: Tx on stopped device!\n", + dev->name); + return NETDEV_TX_BUSY; + } + + if (netif_queue_stopped(dev)) { + printk(KERN_DEBUG "%s: Tx while transmitter busy!\n", + dev->name); + return NETDEV_TX_BUSY; + } + + if (orinoco_lock(priv, &flags) != 0) { + printk(KERN_ERR "%s: orinoco_xmit() called while hw_unavailable\n", + dev->name); + return NETDEV_TX_BUSY; + } + + if (!netif_carrier_ok(dev) || + (priv->iw_mode == NL80211_IFTYPE_MONITOR)) { + /* Oops, the firmware hasn't established a connection, + silently drop the packet (this seems to be the + safest approach). */ + goto drop; + } + + /* Check packet length */ + if (skb->len < ETH_HLEN) + goto drop; + + tx_control = HERMES_TXCTRL_TX_OK | HERMES_TXCTRL_TX_EX; + + err = orinoco_process_xmit_skb(skb, dev, priv, &tx_control, + &mic_buf[0]); + if (err) + goto drop; + + if (priv->has_alt_txcntl) { + /* WPA enabled firmwares have tx_cntl at the end of + * the 802.11 header. So write zeroed descriptor and + * 802.11 header at the same time + */ + char desc[HERMES_802_3_OFFSET]; + __le16 *txcntl = (__le16 *) &desc[HERMES_TXCNTL2_OFFSET]; + + memset(&desc, 0, sizeof(desc)); + + *txcntl = cpu_to_le16(tx_control); + err = hw->ops->bap_pwrite(hw, USER_BAP, &desc, sizeof(desc), + txfid, 0); + if (err) { + if (net_ratelimit()) + printk(KERN_ERR "%s: Error %d writing Tx " + "descriptor to BAP\n", dev->name, err); + goto busy; + } + } else { + struct hermes_tx_descriptor desc; + + memset(&desc, 0, sizeof(desc)); + + desc.tx_control = cpu_to_le16(tx_control); + err = hw->ops->bap_pwrite(hw, USER_BAP, &desc, sizeof(desc), + txfid, 0); + if (err) { + if (net_ratelimit()) + printk(KERN_ERR "%s: Error %d writing Tx " + "descriptor to BAP\n", dev->name, err); + goto busy; + } + + /* Clear the 802.11 header and data length fields - some + * firmwares (e.g. Lucent/Agere 8.xx) appear to get confused + * if this isn't done. */ + hermes_clear_words(hw, HERMES_DATA0, + HERMES_802_3_OFFSET - HERMES_802_11_OFFSET); + } + + err = hw->ops->bap_pwrite(hw, USER_BAP, skb->data, skb->len, + txfid, HERMES_802_3_OFFSET); + if (err) { + printk(KERN_ERR "%s: Error %d writing packet to BAP\n", + dev->name, err); + goto busy; + } + + if (tx_control & HERMES_TXCTRL_MIC) { + size_t offset = HERMES_802_3_OFFSET + skb->len; + size_t len = MICHAEL_MIC_LEN; + + if (offset % 2) { + offset--; + len++; + } + err = hw->ops->bap_pwrite(hw, USER_BAP, &mic_buf[0], len, + txfid, offset); + if (err) { + printk(KERN_ERR "%s: Error %d writing MIC to BAP\n", + dev->name, err); + goto busy; + } + } + + /* Finally, we actually initiate the send */ + netif_stop_queue(dev); + + err = hw->ops->cmd_wait(hw, HERMES_CMD_TX | HERMES_CMD_RECL, + txfid, NULL); + if (err) { + netif_start_queue(dev); + if (net_ratelimit()) + printk(KERN_ERR "%s: Error %d transmitting packet\n", + dev->name, err); + goto busy; + } + + stats->tx_bytes += HERMES_802_3_OFFSET + skb->len; + goto ok; + + drop: + stats->tx_errors++; + stats->tx_dropped++; + + ok: + orinoco_unlock(priv, &flags); + dev_kfree_skb(skb); + return NETDEV_TX_OK; + + busy: + if (err == -EIO) + schedule_work(&priv->reset_work); + orinoco_unlock(priv, &flags); + return NETDEV_TX_BUSY; +} + +static void __orinoco_ev_alloc(struct net_device *dev, struct hermes *hw) +{ + struct orinoco_private *priv = ndev_priv(dev); + u16 fid = hermes_read_regn(hw, ALLOCFID); + + if (fid != priv->txfid) { + if (fid != DUMMY_FID) + printk(KERN_WARNING "%s: Allocate event on unexpected fid (%04X)\n", + dev->name, fid); + return; + } + + hermes_write_regn(hw, ALLOCFID, DUMMY_FID); +} + +static void __orinoco_ev_tx(struct net_device *dev, struct hermes *hw) +{ + struct orinoco_private *priv = ndev_priv(dev); + struct net_device_stats *stats = &priv->stats; + + stats->tx_packets++; + + netif_wake_queue(dev); + + hermes_write_regn(hw, TXCOMPLFID, DUMMY_FID); +} + +static void __orinoco_ev_txexc(struct net_device *dev, struct hermes *hw) +{ + struct orinoco_private *priv = ndev_priv(dev); + struct net_device_stats *stats = &priv->stats; + u16 fid = hermes_read_regn(hw, TXCOMPLFID); + u16 status; + struct hermes_txexc_data hdr; + int err = 0; + + if (fid == DUMMY_FID) + return; /* Nothing's really happened */ + + /* Read part of the frame header - we need status and addr1 */ + err = hw->ops->bap_pread(hw, IRQ_BAP, &hdr, + sizeof(struct hermes_txexc_data), + fid, 0); + + hermes_write_regn(hw, TXCOMPLFID, DUMMY_FID); + stats->tx_errors++; + + if (err) { + printk(KERN_WARNING "%s: Unable to read descriptor on Tx error " + "(FID=%04X error %d)\n", + dev->name, fid, err); + return; + } + + DEBUG(1, "%s: Tx error, err %d (FID=%04X)\n", dev->name, + err, fid); + + /* We produce a TXDROP event only for retry or lifetime + * exceeded, because that's the only status that really mean + * that this particular node went away. + * Other errors means that *we* screwed up. - Jean II */ + status = le16_to_cpu(hdr.desc.status); + if (status & (HERMES_TXSTAT_RETRYERR | HERMES_TXSTAT_AGEDERR)) { + union iwreq_data wrqu; + + /* Copy 802.11 dest address. + * We use the 802.11 header because the frame may + * not be 802.3 or may be mangled... + * In Ad-Hoc mode, it will be the node address. + * In managed mode, it will be most likely the AP addr + * User space will figure out how to convert it to + * whatever it needs (IP address or else). + * - Jean II */ + memcpy(wrqu.addr.sa_data, hdr.addr1, ETH_ALEN); + wrqu.addr.sa_family = ARPHRD_ETHER; + + /* Send event to user space */ + wireless_send_event(dev, IWEVTXDROP, &wrqu, NULL); + } + + netif_wake_queue(dev); +} + +void orinoco_tx_timeout(struct net_device *dev) +{ + struct orinoco_private *priv = ndev_priv(dev); + struct net_device_stats *stats = &priv->stats; + struct hermes *hw = &priv->hw; + + printk(KERN_WARNING "%s: Tx timeout! " + "ALLOCFID=%04x, TXCOMPLFID=%04x, EVSTAT=%04x\n", + dev->name, hermes_read_regn(hw, ALLOCFID), + hermes_read_regn(hw, TXCOMPLFID), hermes_read_regn(hw, EVSTAT)); + + stats->tx_errors++; + + schedule_work(&priv->reset_work); +} +EXPORT_SYMBOL(orinoco_tx_timeout); + +/********************************************************************/ +/* Rx path (data frames) */ +/********************************************************************/ + +/* Does the frame have a SNAP header indicating it should be + * de-encapsulated to Ethernet-II? */ +static inline int is_ethersnap(void *_hdr) +{ + u8 *hdr = _hdr; + + /* We de-encapsulate all packets which, a) have SNAP headers + * (i.e. SSAP=DSAP=0xaa and CTRL=0x3 in the 802.2 LLC header + * and where b) the OUI of the SNAP header is 00:00:00 or + * 00:00:f8 - we need both because different APs appear to use + * different OUIs for some reason */ + return (memcmp(hdr, &encaps_hdr, 5) == 0) + && ((hdr[5] == 0x00) || (hdr[5] == 0xf8)); +} + +static inline void orinoco_spy_gather(struct net_device *dev, u_char *mac, + int level, int noise) +{ + struct iw_quality wstats; + wstats.level = level - 0x95; + wstats.noise = noise - 0x95; + wstats.qual = (level > noise) ? (level - noise) : 0; + wstats.updated = IW_QUAL_ALL_UPDATED | IW_QUAL_DBM; + /* Update spy records */ + wireless_spy_update(dev, mac, &wstats); +} + +static void orinoco_stat_gather(struct net_device *dev, + struct sk_buff *skb, + struct hermes_rx_descriptor *desc) +{ + struct orinoco_private *priv = ndev_priv(dev); + + /* Using spy support with lots of Rx packets, like in an + * infrastructure (AP), will really slow down everything, because + * the MAC address must be compared to each entry of the spy list. + * If the user really asks for it (set some address in the + * spy list), we do it, but he will pay the price. + * Note that to get here, you need both WIRELESS_SPY + * compiled in AND some addresses in the list !!! + */ + /* Note : gcc will optimise the whole section away if + * WIRELESS_SPY is not defined... - Jean II */ + if (SPY_NUMBER(priv)) { + orinoco_spy_gather(dev, skb_mac_header(skb) + ETH_ALEN, + desc->signal, desc->silence); + } +} + +/* + * orinoco_rx_monitor - handle received monitor frames. + * + * Arguments: + * dev network device + * rxfid received FID + * desc rx descriptor of the frame + * + * Call context: interrupt + */ +static void orinoco_rx_monitor(struct net_device *dev, u16 rxfid, + struct hermes_rx_descriptor *desc) +{ + u32 hdrlen = 30; /* return full header by default */ + u32 datalen = 0; + u16 fc; + int err; + int len; + struct sk_buff *skb; + struct orinoco_private *priv = ndev_priv(dev); + struct net_device_stats *stats = &priv->stats; + struct hermes *hw = &priv->hw; + + len = le16_to_cpu(desc->data_len); + + /* Determine the size of the header and the data */ + fc = le16_to_cpu(desc->frame_ctl); + switch (fc & IEEE80211_FCTL_FTYPE) { + case IEEE80211_FTYPE_DATA: + if ((fc & IEEE80211_FCTL_TODS) + && (fc & IEEE80211_FCTL_FROMDS)) + hdrlen = 30; + else + hdrlen = 24; + datalen = len; + break; + case IEEE80211_FTYPE_MGMT: + hdrlen = 24; + datalen = len; + break; + case IEEE80211_FTYPE_CTL: + switch (fc & IEEE80211_FCTL_STYPE) { + case IEEE80211_STYPE_PSPOLL: + case IEEE80211_STYPE_RTS: + case IEEE80211_STYPE_CFEND: + case IEEE80211_STYPE_CFENDACK: + hdrlen = 16; + break; + case IEEE80211_STYPE_CTS: + case IEEE80211_STYPE_ACK: + hdrlen = 10; + break; + } + break; + default: + /* Unknown frame type */ + break; + } + + /* sanity check the length */ + if (datalen > IEEE80211_MAX_DATA_LEN + 12) { + printk(KERN_DEBUG "%s: oversized monitor frame, " + "data length = %d\n", dev->name, datalen); + stats->rx_length_errors++; + goto update_stats; + } + + skb = dev_alloc_skb(hdrlen + datalen); + if (!skb) { + printk(KERN_WARNING "%s: Cannot allocate skb for monitor frame\n", + dev->name); + goto update_stats; + } + + /* Copy the 802.11 header to the skb */ + memcpy(skb_put(skb, hdrlen), &(desc->frame_ctl), hdrlen); + skb_reset_mac_header(skb); + + /* If any, copy the data from the card to the skb */ + if (datalen > 0) { + err = hw->ops->bap_pread(hw, IRQ_BAP, skb_put(skb, datalen), + ALIGN(datalen, 2), rxfid, + HERMES_802_2_OFFSET); + if (err) { + printk(KERN_ERR "%s: error %d reading monitor frame\n", + dev->name, err); + goto drop; + } + } + + skb->dev = dev; + skb->ip_summed = CHECKSUM_NONE; + skb->pkt_type = PACKET_OTHERHOST; + skb->protocol = cpu_to_be16(ETH_P_802_2); + + stats->rx_packets++; + stats->rx_bytes += skb->len; + + netif_rx(skb); + return; + + drop: + dev_kfree_skb_irq(skb); + update_stats: + stats->rx_errors++; + stats->rx_dropped++; +} + +void __orinoco_ev_rx(struct net_device *dev, struct hermes *hw) +{ + struct orinoco_private *priv = ndev_priv(dev); + struct net_device_stats *stats = &priv->stats; + struct iw_statistics *wstats = &priv->wstats; + struct sk_buff *skb = NULL; + u16 rxfid, status; + int length; + struct hermes_rx_descriptor *desc; + struct orinoco_rx_data *rx_data; + int err; + + desc = kmalloc(sizeof(*desc), GFP_ATOMIC); + if (!desc) + goto update_stats; + + rxfid = hermes_read_regn(hw, RXFID); + + err = hw->ops->bap_pread(hw, IRQ_BAP, desc, sizeof(*desc), + rxfid, 0); + if (err) { + printk(KERN_ERR "%s: error %d reading Rx descriptor. " + "Frame dropped.\n", dev->name, err); + goto update_stats; + } + + status = le16_to_cpu(desc->status); + + if (status & HERMES_RXSTAT_BADCRC) { + DEBUG(1, "%s: Bad CRC on Rx. Frame dropped.\n", + dev->name); + stats->rx_crc_errors++; + goto update_stats; + } + + /* Handle frames in monitor mode */ + if (priv->iw_mode == NL80211_IFTYPE_MONITOR) { + orinoco_rx_monitor(dev, rxfid, desc); + goto out; + } + + if (status & HERMES_RXSTAT_UNDECRYPTABLE) { + DEBUG(1, "%s: Undecryptable frame on Rx. Frame dropped.\n", + dev->name); + wstats->discard.code++; + goto update_stats; + } + + length = le16_to_cpu(desc->data_len); + + /* Sanity checks */ + if (length < 3) { /* No for even an 802.2 LLC header */ + /* At least on Symbol firmware with PCF we get quite a + lot of these legitimately - Poll frames with no + data. */ + goto out; + } + if (length > IEEE80211_MAX_DATA_LEN) { + printk(KERN_WARNING "%s: Oversized frame received (%d bytes)\n", + dev->name, length); + stats->rx_length_errors++; + goto update_stats; + } + + /* Payload size does not include Michael MIC. Increase payload + * size to read it together with the data. */ + if (status & HERMES_RXSTAT_MIC) + length += MICHAEL_MIC_LEN; + + /* We need space for the packet data itself, plus an ethernet + header, plus 2 bytes so we can align the IP header on a + 32bit boundary, plus 1 byte so we can read in odd length + packets from the card, which has an IO granularity of 16 + bits */ + skb = dev_alloc_skb(length + ETH_HLEN + 2 + 1); + if (!skb) { + printk(KERN_WARNING "%s: Can't allocate skb for Rx\n", + dev->name); + goto update_stats; + } + + /* We'll prepend the header, so reserve space for it. The worst + case is no decapsulation, when 802.3 header is prepended and + nothing is removed. 2 is for aligning the IP header. */ + skb_reserve(skb, ETH_HLEN + 2); + + err = hw->ops->bap_pread(hw, IRQ_BAP, skb_put(skb, length), + ALIGN(length, 2), rxfid, + HERMES_802_2_OFFSET); + if (err) { + printk(KERN_ERR "%s: error %d reading frame. " + "Frame dropped.\n", dev->name, err); + goto drop; + } + + /* Add desc and skb to rx queue */ + rx_data = kzalloc(sizeof(*rx_data), GFP_ATOMIC); + if (!rx_data) + goto drop; + + rx_data->desc = desc; + rx_data->skb = skb; + list_add_tail(&rx_data->list, &priv->rx_list); + tasklet_schedule(&priv->rx_tasklet); + + return; + +drop: + dev_kfree_skb_irq(skb); +update_stats: + stats->rx_errors++; + stats->rx_dropped++; +out: + kfree(desc); +} +EXPORT_SYMBOL(__orinoco_ev_rx); + +static void orinoco_rx(struct net_device *dev, + struct hermes_rx_descriptor *desc, + struct sk_buff *skb) +{ + struct orinoco_private *priv = ndev_priv(dev); + struct net_device_stats *stats = &priv->stats; + u16 status, fc; + int length; + struct ethhdr *hdr; + + status = le16_to_cpu(desc->status); + length = le16_to_cpu(desc->data_len); + fc = le16_to_cpu(desc->frame_ctl); + + /* Calculate and check MIC */ + if (status & HERMES_RXSTAT_MIC) { + struct orinoco_tkip_key *key; + int key_id = ((status & HERMES_RXSTAT_MIC_KEY_ID) >> + HERMES_MIC_KEY_ID_SHIFT); + u8 mic[MICHAEL_MIC_LEN]; + u8 *rxmic; + u8 *src = (fc & IEEE80211_FCTL_FROMDS) ? + desc->addr3 : desc->addr2; + + /* Extract Michael MIC from payload */ + rxmic = skb->data + skb->len - MICHAEL_MIC_LEN; + + skb_trim(skb, skb->len - MICHAEL_MIC_LEN); + length -= MICHAEL_MIC_LEN; + + key = (struct orinoco_tkip_key *) priv->keys[key_id].key; + + if (!key) { + printk(KERN_WARNING "%s: Received encrypted frame from " + "%pM using key %i, but key is not installed\n", + dev->name, src, key_id); + goto drop; + } + + orinoco_mic(priv->rx_tfm_mic, key->rx_mic, desc->addr1, src, + 0, /* priority or QoS? */ + skb->data, skb->len, &mic[0]); + + if (memcmp(mic, rxmic, + MICHAEL_MIC_LEN)) { + union iwreq_data wrqu; + struct iw_michaelmicfailure wxmic; + + printk(KERN_WARNING "%s: " + "Invalid Michael MIC in data frame from %pM, " + "using key %i\n", + dev->name, src, key_id); + + /* TODO: update stats */ + + /* Notify userspace */ + memset(&wxmic, 0, sizeof(wxmic)); + wxmic.flags = key_id & IW_MICFAILURE_KEY_ID; + wxmic.flags |= (desc->addr1[0] & 1) ? + IW_MICFAILURE_GROUP : IW_MICFAILURE_PAIRWISE; + wxmic.src_addr.sa_family = ARPHRD_ETHER; + memcpy(wxmic.src_addr.sa_data, src, ETH_ALEN); + + (void) orinoco_hw_get_tkip_iv(priv, key_id, + &wxmic.tsc[0]); + + memset(&wrqu, 0, sizeof(wrqu)); + wrqu.data.length = sizeof(wxmic); + wireless_send_event(dev, IWEVMICHAELMICFAILURE, &wrqu, + (char *) &wxmic); + + goto drop; + } + } + + /* Handle decapsulation + * In most cases, the firmware tell us about SNAP frames. + * For some reason, the SNAP frames sent by LinkSys APs + * are not properly recognised by most firmwares. + * So, check ourselves */ + if (length >= ENCAPS_OVERHEAD && + (((status & HERMES_RXSTAT_MSGTYPE) == HERMES_RXSTAT_1042) || + ((status & HERMES_RXSTAT_MSGTYPE) == HERMES_RXSTAT_TUNNEL) || + is_ethersnap(skb->data))) { + /* These indicate a SNAP within 802.2 LLC within + 802.11 frame which we'll need to de-encapsulate to + the original EthernetII frame. */ + hdr = (struct ethhdr *)skb_push(skb, + ETH_HLEN - ENCAPS_OVERHEAD); + } else { + /* 802.3 frame - prepend 802.3 header as is */ + hdr = (struct ethhdr *)skb_push(skb, ETH_HLEN); + hdr->h_proto = htons(length); + } + memcpy(hdr->h_dest, desc->addr1, ETH_ALEN); + if (fc & IEEE80211_FCTL_FROMDS) + memcpy(hdr->h_source, desc->addr3, ETH_ALEN); + else + memcpy(hdr->h_source, desc->addr2, ETH_ALEN); + + skb->protocol = eth_type_trans(skb, dev); + skb->ip_summed = CHECKSUM_NONE; + if (fc & IEEE80211_FCTL_TODS) + skb->pkt_type = PACKET_OTHERHOST; + + /* Process the wireless stats if needed */ + orinoco_stat_gather(dev, skb, desc); + + /* Pass the packet to the networking stack */ + netif_rx(skb); + stats->rx_packets++; + stats->rx_bytes += length; + + return; + + drop: + dev_kfree_skb(skb); + stats->rx_errors++; + stats->rx_dropped++; +} + +static void orinoco_rx_isr_tasklet(unsigned long data) +{ + struct orinoco_private *priv = (struct orinoco_private *) data; + struct net_device *dev = priv->ndev; + struct orinoco_rx_data *rx_data, *temp; + struct hermes_rx_descriptor *desc; + struct sk_buff *skb; + unsigned long flags; + + /* orinoco_rx requires the driver lock, and we also need to + * protect priv->rx_list, so just hold the lock over the + * lot. + * + * If orinoco_lock fails, we've unplugged the card. In this + * case just abort. */ + if (orinoco_lock(priv, &flags) != 0) + return; + + /* extract desc and skb from queue */ + list_for_each_entry_safe(rx_data, temp, &priv->rx_list, list) { + desc = rx_data->desc; + skb = rx_data->skb; + list_del(&rx_data->list); + kfree(rx_data); + + orinoco_rx(dev, desc, skb); + + kfree(desc); + } + + orinoco_unlock(priv, &flags); +} + +/********************************************************************/ +/* Rx path (info frames) */ +/********************************************************************/ + +static void print_linkstatus(struct net_device *dev, u16 status) +{ + char *s; + + if (suppress_linkstatus) + return; + + switch (status) { + case HERMES_LINKSTATUS_NOT_CONNECTED: + s = "Not Connected"; + break; + case HERMES_LINKSTATUS_CONNECTED: + s = "Connected"; + break; + case HERMES_LINKSTATUS_DISCONNECTED: + s = "Disconnected"; + break; + case HERMES_LINKSTATUS_AP_CHANGE: + s = "AP Changed"; + break; + case HERMES_LINKSTATUS_AP_OUT_OF_RANGE: + s = "AP Out of Range"; + break; + case HERMES_LINKSTATUS_AP_IN_RANGE: + s = "AP In Range"; + break; + case HERMES_LINKSTATUS_ASSOC_FAILED: + s = "Association Failed"; + break; + default: + s = "UNKNOWN"; + } + + printk(KERN_DEBUG "%s: New link status: %s (%04x)\n", + dev->name, s, status); +} + +/* Search scan results for requested BSSID, join it if found */ +static void orinoco_join_ap(struct work_struct *work) +{ + struct orinoco_private *priv = + container_of(work, struct orinoco_private, join_work); + struct net_device *dev = priv->ndev; + struct hermes *hw = &priv->hw; + int err; + unsigned long flags; + struct join_req { + u8 bssid[ETH_ALEN]; + __le16 channel; + } __packed req; + const int atom_len = offsetof(struct prism2_scan_apinfo, atim); + struct prism2_scan_apinfo *atom = NULL; + int offset = 4; + int found = 0; + u8 *buf; + u16 len; + + /* Allocate buffer for scan results */ + buf = kmalloc(MAX_SCAN_LEN, GFP_KERNEL); + if (!buf) + return; + + if (orinoco_lock(priv, &flags) != 0) + goto fail_lock; + + /* Sanity checks in case user changed something in the meantime */ + if (!priv->bssid_fixed) + goto out; + + if (strlen(priv->desired_essid) == 0) + goto out; + + /* Read scan results from the firmware */ + err = hw->ops->read_ltv(hw, USER_BAP, + HERMES_RID_SCANRESULTSTABLE, + MAX_SCAN_LEN, &len, buf); + if (err) { + printk(KERN_ERR "%s: Cannot read scan results\n", + dev->name); + goto out; + } + + len = HERMES_RECLEN_TO_BYTES(len); + + /* Go through the scan results looking for the channel of the AP + * we were requested to join */ + for (; offset + atom_len <= len; offset += atom_len) { + atom = (struct prism2_scan_apinfo *) (buf + offset); + if (memcmp(&atom->bssid, priv->desired_bssid, ETH_ALEN) == 0) { + found = 1; + break; + } + } + + if (!found) { + DEBUG(1, "%s: Requested AP not found in scan results\n", + dev->name); + goto out; + } + + memcpy(req.bssid, priv->desired_bssid, ETH_ALEN); + req.channel = atom->channel; /* both are little-endian */ + err = HERMES_WRITE_RECORD(hw, USER_BAP, HERMES_RID_CNFJOINREQUEST, + &req); + if (err) + printk(KERN_ERR "%s: Error issuing join request\n", dev->name); + + out: + orinoco_unlock(priv, &flags); + + fail_lock: + kfree(buf); +} + +/* Send new BSSID to userspace */ +static void orinoco_send_bssid_wevent(struct orinoco_private *priv) +{ + struct net_device *dev = priv->ndev; + struct hermes *hw = &priv->hw; + union iwreq_data wrqu; + int err; + + err = hw->ops->read_ltv(hw, USER_BAP, HERMES_RID_CURRENTBSSID, + ETH_ALEN, NULL, wrqu.ap_addr.sa_data); + if (err != 0) + return; + + wrqu.ap_addr.sa_family = ARPHRD_ETHER; + + /* Send event to user space */ + wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL); +} + +static void orinoco_send_assocreqie_wevent(struct orinoco_private *priv) +{ + struct net_device *dev = priv->ndev; + struct hermes *hw = &priv->hw; + union iwreq_data wrqu; + int err; + u8 buf[88]; + u8 *ie; + + if (!priv->has_wpa) + return; + + err = hw->ops->read_ltv(hw, USER_BAP, HERMES_RID_CURRENT_ASSOC_REQ_INFO, + sizeof(buf), NULL, &buf); + if (err != 0) + return; + + ie = orinoco_get_wpa_ie(buf, sizeof(buf)); + if (ie) { + int rem = sizeof(buf) - (ie - &buf[0]); + wrqu.data.length = ie[1] + 2; + if (wrqu.data.length > rem) + wrqu.data.length = rem; + + if (wrqu.data.length) + /* Send event to user space */ + wireless_send_event(dev, IWEVASSOCREQIE, &wrqu, ie); + } +} + +static void orinoco_send_assocrespie_wevent(struct orinoco_private *priv) +{ + struct net_device *dev = priv->ndev; + struct hermes *hw = &priv->hw; + union iwreq_data wrqu; + int err; + u8 buf[88]; /* TODO: verify max size or IW_GENERIC_IE_MAX */ + u8 *ie; + + if (!priv->has_wpa) + return; + + err = hw->ops->read_ltv(hw, USER_BAP, + HERMES_RID_CURRENT_ASSOC_RESP_INFO, + sizeof(buf), NULL, &buf); + if (err != 0) + return; + + ie = orinoco_get_wpa_ie(buf, sizeof(buf)); + if (ie) { + int rem = sizeof(buf) - (ie - &buf[0]); + wrqu.data.length = ie[1] + 2; + if (wrqu.data.length > rem) + wrqu.data.length = rem; + + if (wrqu.data.length) + /* Send event to user space */ + wireless_send_event(dev, IWEVASSOCRESPIE, &wrqu, ie); + } +} + +static void orinoco_send_wevents(struct work_struct *work) +{ + struct orinoco_private *priv = + container_of(work, struct orinoco_private, wevent_work); + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) + return; + + orinoco_send_assocreqie_wevent(priv); + orinoco_send_assocrespie_wevent(priv); + orinoco_send_bssid_wevent(priv); + + orinoco_unlock(priv, &flags); +} + +static void qbuf_scan(struct orinoco_private *priv, void *buf, + int len, int type) +{ + struct orinoco_scan_data *sd; + unsigned long flags; + + sd = kmalloc(sizeof(*sd), GFP_ATOMIC); + if (!sd) + return; + + sd->buf = buf; + sd->len = len; + sd->type = type; + + spin_lock_irqsave(&priv->scan_lock, flags); + list_add_tail(&sd->list, &priv->scan_list); + spin_unlock_irqrestore(&priv->scan_lock, flags); + + schedule_work(&priv->process_scan); +} + +static void qabort_scan(struct orinoco_private *priv) +{ + struct orinoco_scan_data *sd; + unsigned long flags; + + sd = kmalloc(sizeof(*sd), GFP_ATOMIC); + if (!sd) + return; + + sd->len = -1; /* Abort */ + + spin_lock_irqsave(&priv->scan_lock, flags); + list_add_tail(&sd->list, &priv->scan_list); + spin_unlock_irqrestore(&priv->scan_lock, flags); + + schedule_work(&priv->process_scan); +} + +static void orinoco_process_scan_results(struct work_struct *work) +{ + struct orinoco_private *priv = + container_of(work, struct orinoco_private, process_scan); + struct orinoco_scan_data *sd, *temp; + unsigned long flags; + void *buf; + int len; + int type; + + spin_lock_irqsave(&priv->scan_lock, flags); + list_for_each_entry_safe(sd, temp, &priv->scan_list, list) { + + buf = sd->buf; + len = sd->len; + type = sd->type; + + list_del(&sd->list); + spin_unlock_irqrestore(&priv->scan_lock, flags); + kfree(sd); + + if (len > 0) { + if (type == HERMES_INQ_CHANNELINFO) + orinoco_add_extscan_result(priv, buf, len); + else + orinoco_add_hostscan_results(priv, buf, len); + + kfree(buf); + } else { + /* Either abort or complete the scan */ + orinoco_scan_done(priv, (len < 0)); + } + + spin_lock_irqsave(&priv->scan_lock, flags); + } + spin_unlock_irqrestore(&priv->scan_lock, flags); +} + +void __orinoco_ev_info(struct net_device *dev, struct hermes *hw) +{ + struct orinoco_private *priv = ndev_priv(dev); + u16 infofid; + struct { + __le16 len; + __le16 type; + } __packed info; + int len, type; + int err; + + /* This is an answer to an INQUIRE command that we did earlier, + * or an information "event" generated by the card + * The controller return to us a pseudo frame containing + * the information in question - Jean II */ + infofid = hermes_read_regn(hw, INFOFID); + + /* Read the info frame header - don't try too hard */ + err = hw->ops->bap_pread(hw, IRQ_BAP, &info, sizeof(info), + infofid, 0); + if (err) { + printk(KERN_ERR "%s: error %d reading info frame. " + "Frame dropped.\n", dev->name, err); + return; + } + + len = HERMES_RECLEN_TO_BYTES(le16_to_cpu(info.len)); + type = le16_to_cpu(info.type); + + switch (type) { + case HERMES_INQ_TALLIES: { + struct hermes_tallies_frame tallies; + struct iw_statistics *wstats = &priv->wstats; + + if (len > sizeof(tallies)) { + printk(KERN_WARNING "%s: Tallies frame too long (%d bytes)\n", + dev->name, len); + len = sizeof(tallies); + } + + err = hw->ops->bap_pread(hw, IRQ_BAP, &tallies, len, + infofid, sizeof(info)); + if (err) + break; + + /* Increment our various counters */ + /* wstats->discard.nwid - no wrong BSSID stuff */ + wstats->discard.code += + le16_to_cpu(tallies.RxWEPUndecryptable); + if (len == sizeof(tallies)) + wstats->discard.code += + le16_to_cpu(tallies.RxDiscards_WEPICVError) + + le16_to_cpu(tallies.RxDiscards_WEPExcluded); + wstats->discard.misc += + le16_to_cpu(tallies.TxDiscardsWrongSA); + wstats->discard.fragment += + le16_to_cpu(tallies.RxMsgInBadMsgFragments); + wstats->discard.retries += + le16_to_cpu(tallies.TxRetryLimitExceeded); + /* wstats->miss.beacon - no match */ + } + break; + case HERMES_INQ_LINKSTATUS: { + struct hermes_linkstatus linkstatus; + u16 newstatus; + int connected; + + if (priv->iw_mode == NL80211_IFTYPE_MONITOR) + break; + + if (len != sizeof(linkstatus)) { + printk(KERN_WARNING "%s: Unexpected size for linkstatus frame (%d bytes)\n", + dev->name, len); + break; + } + + err = hw->ops->bap_pread(hw, IRQ_BAP, &linkstatus, len, + infofid, sizeof(info)); + if (err) + break; + newstatus = le16_to_cpu(linkstatus.linkstatus); + + /* Symbol firmware uses "out of range" to signal that + * the hostscan frame can be requested. */ + if (newstatus == HERMES_LINKSTATUS_AP_OUT_OF_RANGE && + priv->firmware_type == FIRMWARE_TYPE_SYMBOL && + priv->has_hostscan && priv->scan_request) { + hermes_inquire(hw, HERMES_INQ_HOSTSCAN_SYMBOL); + break; + } + + connected = (newstatus == HERMES_LINKSTATUS_CONNECTED) + || (newstatus == HERMES_LINKSTATUS_AP_CHANGE) + || (newstatus == HERMES_LINKSTATUS_AP_IN_RANGE); + + if (connected) + netif_carrier_on(dev); + else if (!ignore_disconnect) + netif_carrier_off(dev); + + if (newstatus != priv->last_linkstatus) { + priv->last_linkstatus = newstatus; + print_linkstatus(dev, newstatus); + /* The info frame contains only one word which is the + * status (see hermes.h). The status is pretty boring + * in itself, that's why we export the new BSSID... + * Jean II */ + schedule_work(&priv->wevent_work); + } + } + break; + case HERMES_INQ_SCAN: + if (!priv->scan_request && priv->bssid_fixed && + priv->firmware_type == FIRMWARE_TYPE_INTERSIL) { + schedule_work(&priv->join_work); + break; + } + /* fall through */ + case HERMES_INQ_HOSTSCAN: + case HERMES_INQ_HOSTSCAN_SYMBOL: { + /* Result of a scanning. Contains information about + * cells in the vicinity - Jean II */ + unsigned char *buf; + + /* Sanity check */ + if (len > 4096) { + printk(KERN_WARNING "%s: Scan results too large (%d bytes)\n", + dev->name, len); + qabort_scan(priv); + break; + } + + /* Allocate buffer for results */ + buf = kmalloc(len, GFP_ATOMIC); + if (buf == NULL) { + /* No memory, so can't printk()... */ + qabort_scan(priv); + break; + } + + /* Read scan data */ + err = hw->ops->bap_pread(hw, IRQ_BAP, (void *) buf, len, + infofid, sizeof(info)); + if (err) { + kfree(buf); + qabort_scan(priv); + break; + } + +#ifdef ORINOCO_DEBUG + { + int i; + printk(KERN_DEBUG "Scan result [%02X", buf[0]); + for (i = 1; i < (len * 2); i++) + printk(":%02X", buf[i]); + printk("]\n"); + } +#endif /* ORINOCO_DEBUG */ + + qbuf_scan(priv, buf, len, type); + } + break; + case HERMES_INQ_CHANNELINFO: + { + struct agere_ext_scan_info *bss; + + if (!priv->scan_request) { + printk(KERN_DEBUG "%s: Got chaninfo without scan, " + "len=%d\n", dev->name, len); + break; + } + + /* An empty result indicates that the scan is complete */ + if (len == 0) { + qbuf_scan(priv, NULL, len, type); + break; + } + + /* Sanity check */ + else if (len < (offsetof(struct agere_ext_scan_info, + data) + 2)) { + /* Drop this result now so we don't have to + * keep checking later */ + printk(KERN_WARNING + "%s: Ext scan results too short (%d bytes)\n", + dev->name, len); + break; + } + + bss = kmalloc(len, GFP_ATOMIC); + if (bss == NULL) + break; + + /* Read scan data */ + err = hw->ops->bap_pread(hw, IRQ_BAP, (void *) bss, len, + infofid, sizeof(info)); + if (err) + kfree(bss); + else + qbuf_scan(priv, bss, len, type); + + break; + } + case HERMES_INQ_SEC_STAT_AGERE: + /* Security status (Agere specific) */ + /* Ignore this frame for now */ + if (priv->firmware_type == FIRMWARE_TYPE_AGERE) + break; + /* fall through */ + default: + printk(KERN_DEBUG "%s: Unknown information frame received: " + "type 0x%04x, length %d\n", dev->name, type, len); + /* We don't actually do anything about it */ + break; + } +} +EXPORT_SYMBOL(__orinoco_ev_info); + +static void __orinoco_ev_infdrop(struct net_device *dev, struct hermes *hw) +{ + if (net_ratelimit()) + printk(KERN_DEBUG "%s: Information frame lost.\n", dev->name); +} + +/********************************************************************/ +/* Internal hardware control routines */ +/********************************************************************/ + +static int __orinoco_up(struct orinoco_private *priv) +{ + struct net_device *dev = priv->ndev; + struct hermes *hw = &priv->hw; + int err; + + netif_carrier_off(dev); /* just to make sure */ + + err = __orinoco_commit(priv); + if (err) { + printk(KERN_ERR "%s: Error %d configuring card\n", + dev->name, err); + return err; + } + + /* Fire things up again */ + hermes_set_irqmask(hw, ORINOCO_INTEN); + err = hermes_enable_port(hw, 0); + if (err) { + printk(KERN_ERR "%s: Error %d enabling MAC port\n", + dev->name, err); + return err; + } + + netif_start_queue(dev); + + return 0; +} + +static int __orinoco_down(struct orinoco_private *priv) +{ + struct net_device *dev = priv->ndev; + struct hermes *hw = &priv->hw; + int err; + + netif_stop_queue(dev); + + if (!priv->hw_unavailable) { + if (!priv->broken_disableport) { + err = hermes_disable_port(hw, 0); + if (err) { + /* Some firmwares (e.g. Intersil 1.3.x) seem + * to have problems disabling the port, oh + * well, too bad. */ + printk(KERN_WARNING "%s: Error %d disabling MAC port\n", + dev->name, err); + priv->broken_disableport = 1; + } + } + hermes_set_irqmask(hw, 0); + hermes_write_regn(hw, EVACK, 0xffff); + } + + orinoco_scan_done(priv, true); + + /* firmware will have to reassociate */ + netif_carrier_off(dev); + priv->last_linkstatus = 0xffff; + + return 0; +} + +static int orinoco_reinit_firmware(struct orinoco_private *priv) +{ + struct hermes *hw = &priv->hw; + int err; + + err = hw->ops->init(hw); + if (priv->do_fw_download && !err) { + err = orinoco_download(priv); + if (err) + priv->do_fw_download = 0; + } + if (!err) + err = orinoco_hw_allocate_fid(priv); + + return err; +} + +static int +__orinoco_set_multicast_list(struct net_device *dev) +{ + struct orinoco_private *priv = ndev_priv(dev); + int err = 0; + int promisc, mc_count; + + /* The Hermes doesn't seem to have an allmulti mode, so we go + * into promiscuous mode and let the upper levels deal. */ + if ((dev->flags & IFF_PROMISC) || (dev->flags & IFF_ALLMULTI) || + (netdev_mc_count(dev) > MAX_MULTICAST(priv))) { + promisc = 1; + mc_count = 0; + } else { + promisc = 0; + mc_count = netdev_mc_count(dev); + } + + err = __orinoco_hw_set_multicast_list(priv, dev, mc_count, promisc); + + return err; +} + +/* This must be called from user context, without locks held - use + * schedule_work() */ +void orinoco_reset(struct work_struct *work) +{ + struct orinoco_private *priv = + container_of(work, struct orinoco_private, reset_work); + struct net_device *dev = priv->ndev; + struct hermes *hw = &priv->hw; + int err; + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) + /* When the hardware becomes available again, whatever + * detects that is responsible for re-initializing + * it. So no need for anything further */ + return; + + netif_stop_queue(dev); + + /* Shut off interrupts. Depending on what state the hardware + * is in, this might not work, but we'll try anyway */ + hermes_set_irqmask(hw, 0); + hermes_write_regn(hw, EVACK, 0xffff); + + priv->hw_unavailable++; + priv->last_linkstatus = 0xffff; /* firmware will have to reassociate */ + netif_carrier_off(dev); + + orinoco_unlock(priv, &flags); + + /* Scanning support: Notify scan cancellation */ + orinoco_scan_done(priv, true); + + if (priv->hard_reset) { + err = (*priv->hard_reset)(priv); + if (err) { + printk(KERN_ERR "%s: orinoco_reset: Error %d " + "performing hard reset\n", dev->name, err); + goto disable; + } + } + + err = orinoco_reinit_firmware(priv); + if (err) { + printk(KERN_ERR "%s: orinoco_reset: Error %d re-initializing firmware\n", + dev->name, err); + goto disable; + } + + /* This has to be called from user context */ + orinoco_lock_irq(priv); + + priv->hw_unavailable--; + + /* priv->open or priv->hw_unavailable might have changed while + * we dropped the lock */ + if (priv->open && (!priv->hw_unavailable)) { + err = __orinoco_up(priv); + if (err) { + printk(KERN_ERR "%s: orinoco_reset: Error %d reenabling card\n", + dev->name, err); + } else + dev->trans_start = jiffies; + } + + orinoco_unlock_irq(priv); + + return; + disable: + hermes_set_irqmask(hw, 0); + netif_device_detach(dev); + printk(KERN_ERR "%s: Device has been disabled!\n", dev->name); +} + +static int __orinoco_commit(struct orinoco_private *priv) +{ + struct net_device *dev = priv->ndev; + int err = 0; + + /* If we've called commit, we are reconfiguring or bringing the + * interface up. Maintaining countermeasures across this would + * be confusing, so note that we've disabled them. The port will + * be enabled later in orinoco_commit or __orinoco_up. */ + priv->tkip_cm_active = 0; + + err = orinoco_hw_program_rids(priv); + + /* FIXME: what about netif_tx_lock */ + (void) __orinoco_set_multicast_list(dev); + + return err; +} + +/* Ensures configuration changes are applied. May result in a reset. + * The caller should hold priv->lock + */ +int orinoco_commit(struct orinoco_private *priv) +{ + struct net_device *dev = priv->ndev; + struct hermes *hw = &priv->hw; + int err; + + if (priv->broken_disableport) { + schedule_work(&priv->reset_work); + return 0; + } + + err = hermes_disable_port(hw, 0); + if (err) { + printk(KERN_WARNING "%s: Unable to disable port " + "while reconfiguring card\n", dev->name); + priv->broken_disableport = 1; + goto out; + } + + err = __orinoco_commit(priv); + if (err) { + printk(KERN_WARNING "%s: Unable to reconfigure card\n", + dev->name); + goto out; + } + + err = hermes_enable_port(hw, 0); + if (err) { + printk(KERN_WARNING "%s: Unable to enable port while reconfiguring card\n", + dev->name); + goto out; + } + + out: + if (err) { + printk(KERN_WARNING "%s: Resetting instead...\n", dev->name); + schedule_work(&priv->reset_work); + err = 0; + } + return err; +} + +/********************************************************************/ +/* Interrupt handler */ +/********************************************************************/ + +static void __orinoco_ev_tick(struct net_device *dev, struct hermes *hw) +{ + printk(KERN_DEBUG "%s: TICK\n", dev->name); +} + +static void __orinoco_ev_wterr(struct net_device *dev, struct hermes *hw) +{ + /* This seems to happen a fair bit under load, but ignoring it + seems to work fine...*/ + printk(KERN_DEBUG "%s: MAC controller error (WTERR). Ignoring.\n", + dev->name); +} + +irqreturn_t orinoco_interrupt(int irq, void *dev_id) +{ + struct orinoco_private *priv = dev_id; + struct net_device *dev = priv->ndev; + struct hermes *hw = &priv->hw; + int count = MAX_IRQLOOPS_PER_IRQ; + u16 evstat, events; + /* These are used to detect a runaway interrupt situation. + * + * If we get more than MAX_IRQLOOPS_PER_JIFFY iterations in a jiffy, + * we panic and shut down the hardware + */ + /* jiffies value the last time we were called */ + static int last_irq_jiffy; /* = 0 */ + static int loops_this_jiffy; /* = 0 */ + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) { + /* If hw is unavailable - we don't know if the irq was + * for us or not */ + return IRQ_HANDLED; + } + + evstat = hermes_read_regn(hw, EVSTAT); + events = evstat & hw->inten; + if (!events) { + orinoco_unlock(priv, &flags); + return IRQ_NONE; + } + + if (jiffies != last_irq_jiffy) + loops_this_jiffy = 0; + last_irq_jiffy = jiffies; + + while (events && count--) { + if (++loops_this_jiffy > MAX_IRQLOOPS_PER_JIFFY) { + printk(KERN_WARNING "%s: IRQ handler is looping too " + "much! Resetting.\n", dev->name); + /* Disable interrupts for now */ + hermes_set_irqmask(hw, 0); + schedule_work(&priv->reset_work); + break; + } + + /* Check the card hasn't been removed */ + if (!hermes_present(hw)) { + DEBUG(0, "orinoco_interrupt(): card removed\n"); + break; + } + + if (events & HERMES_EV_TICK) + __orinoco_ev_tick(dev, hw); + if (events & HERMES_EV_WTERR) + __orinoco_ev_wterr(dev, hw); + if (events & HERMES_EV_INFDROP) + __orinoco_ev_infdrop(dev, hw); + if (events & HERMES_EV_INFO) + __orinoco_ev_info(dev, hw); + if (events & HERMES_EV_RX) + __orinoco_ev_rx(dev, hw); + if (events & HERMES_EV_TXEXC) + __orinoco_ev_txexc(dev, hw); + if (events & HERMES_EV_TX) + __orinoco_ev_tx(dev, hw); + if (events & HERMES_EV_ALLOC) + __orinoco_ev_alloc(dev, hw); + + hermes_write_regn(hw, EVACK, evstat); + + evstat = hermes_read_regn(hw, EVSTAT); + events = evstat & hw->inten; + } + + orinoco_unlock(priv, &flags); + return IRQ_HANDLED; +} +EXPORT_SYMBOL(orinoco_interrupt); + +/********************************************************************/ +/* Power management */ +/********************************************************************/ +#if defined(CONFIG_PM_SLEEP) && !defined(CPTCFG_HERMES_CACHE_FW_ON_INIT) +static int orinoco_pm_notifier(struct notifier_block *notifier, + unsigned long pm_event, + void *unused) +{ + struct orinoco_private *priv = container_of(notifier, + struct orinoco_private, + pm_notifier); + + /* All we need to do is cache the firmware before suspend, and + * release it when we come out. + * + * Only need to do this if we're downloading firmware. */ + if (!priv->do_fw_download) + return NOTIFY_DONE; + + switch (pm_event) { + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + orinoco_cache_fw(priv, 0); + break; + + case PM_POST_RESTORE: + /* Restore from hibernation failed. We need to clean + * up in exactly the same way, so fall through. */ + case PM_POST_HIBERNATION: + case PM_POST_SUSPEND: + orinoco_uncache_fw(priv); + break; + + case PM_RESTORE_PREPARE: + default: + break; + } + + return NOTIFY_DONE; +} + +static void orinoco_register_pm_notifier(struct orinoco_private *priv) +{ + priv->pm_notifier.notifier_call = orinoco_pm_notifier; + register_pm_notifier(&priv->pm_notifier); +} + +static void orinoco_unregister_pm_notifier(struct orinoco_private *priv) +{ + unregister_pm_notifier(&priv->pm_notifier); +} +#else /* !PM_SLEEP || HERMES_CACHE_FW_ON_INIT */ +#define orinoco_register_pm_notifier(priv) do { } while (0) +#define orinoco_unregister_pm_notifier(priv) do { } while (0) +#endif + +/********************************************************************/ +/* Initialization */ +/********************************************************************/ + +int orinoco_init(struct orinoco_private *priv) +{ + struct device *dev = priv->dev; + struct wiphy *wiphy = priv_to_wiphy(priv); + struct hermes *hw = &priv->hw; + int err = 0; + + /* No need to lock, the hw_unavailable flag is already set in + * alloc_orinocodev() */ + priv->nicbuf_size = IEEE80211_MAX_FRAME_LEN + ETH_HLEN; + + /* Initialize the firmware */ + err = hw->ops->init(hw); + if (err != 0) { + dev_err(dev, "Failed to initialize firmware (err = %d)\n", + err); + goto out; + } + + err = determine_fw_capabilities(priv, wiphy->fw_version, + sizeof(wiphy->fw_version), + &wiphy->hw_version); + if (err != 0) { + dev_err(dev, "Incompatible firmware, aborting\n"); + goto out; + } + + if (priv->do_fw_download) { +#ifdef CPTCFG_HERMES_CACHE_FW_ON_INIT + orinoco_cache_fw(priv, 0); +#endif + + err = orinoco_download(priv); + if (err) + priv->do_fw_download = 0; + + /* Check firmware version again */ + err = determine_fw_capabilities(priv, wiphy->fw_version, + sizeof(wiphy->fw_version), + &wiphy->hw_version); + if (err != 0) { + dev_err(dev, "Incompatible firmware, aborting\n"); + goto out; + } + } + + if (priv->has_port3) + dev_info(dev, "Ad-hoc demo mode supported\n"); + if (priv->has_ibss) + dev_info(dev, "IEEE standard IBSS ad-hoc mode supported\n"); + if (priv->has_wep) + dev_info(dev, "WEP supported, %s-bit key\n", + priv->has_big_wep ? "104" : "40"); + if (priv->has_wpa) { + dev_info(dev, "WPA-PSK supported\n"); + if (orinoco_mic_init(priv)) { + dev_err(dev, "Failed to setup MIC crypto algorithm. " + "Disabling WPA support\n"); + priv->has_wpa = 0; + } + } + + err = orinoco_hw_read_card_settings(priv, wiphy->perm_addr); + if (err) + goto out; + + err = orinoco_hw_allocate_fid(priv); + if (err) { + dev_err(dev, "Failed to allocate NIC buffer!\n"); + goto out; + } + + /* Set up the default configuration */ + priv->iw_mode = NL80211_IFTYPE_STATION; + /* By default use IEEE/IBSS ad-hoc mode if we have it */ + priv->prefer_port3 = priv->has_port3 && (!priv->has_ibss); + set_port_type(priv); + priv->channel = 0; /* use firmware default */ + + priv->promiscuous = 0; + priv->encode_alg = ORINOCO_ALG_NONE; + priv->tx_key = 0; + priv->wpa_enabled = 0; + priv->tkip_cm_active = 0; + priv->key_mgmt = 0; + priv->wpa_ie_len = 0; + priv->wpa_ie = NULL; + + if (orinoco_wiphy_register(wiphy)) { + err = -ENODEV; + goto out; + } + + /* Make the hardware available, as long as it hasn't been + * removed elsewhere (e.g. by PCMCIA hot unplug) */ + orinoco_lock_irq(priv); + priv->hw_unavailable--; + orinoco_unlock_irq(priv); + + dev_dbg(dev, "Ready\n"); + + out: + return err; +} +EXPORT_SYMBOL(orinoco_init); + +static const struct net_device_ops orinoco_netdev_ops = { + .ndo_open = orinoco_open, + .ndo_stop = orinoco_stop, + .ndo_start_xmit = orinoco_xmit, + .ndo_set_rx_mode = orinoco_set_multicast_list, + .ndo_change_mtu = orinoco_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, + .ndo_tx_timeout = orinoco_tx_timeout, + .ndo_get_stats = orinoco_get_stats, +}; + +/* Allocate private data. + * + * This driver has a number of structures associated with it + * netdev - Net device structure for each network interface + * wiphy - structure associated with wireless phy + * wireless_dev (wdev) - structure for each wireless interface + * hw - structure for hermes chip info + * card - card specific structure for use by the card driver + * (airport, orinoco_cs) + * priv - orinoco private data + * device - generic linux device structure + * + * +---------+ +---------+ + * | wiphy | | netdev | + * | +-------+ | +-------+ + * | | priv | | | wdev | + * | | +-----+ +-+-------+ + * | | | hw | + * | +-+-----+ + * | | card | + * +-+-------+ + * + * priv has a link to netdev and device + * wdev has a link to wiphy + */ +struct orinoco_private +*alloc_orinocodev(int sizeof_card, + struct device *device, + int (*hard_reset)(struct orinoco_private *), + int (*stop_fw)(struct orinoco_private *, int)) +{ + struct orinoco_private *priv; + struct wiphy *wiphy; + + /* allocate wiphy + * NOTE: We only support a single virtual interface + * but this may change when monitor mode is added + */ + wiphy = wiphy_new(&orinoco_cfg_ops, + sizeof(struct orinoco_private) + sizeof_card); + if (!wiphy) + return NULL; + + priv = wiphy_priv(wiphy); + priv->dev = device; + + if (sizeof_card) + priv->card = (void *)((unsigned long)priv + + sizeof(struct orinoco_private)); + else + priv->card = NULL; + + orinoco_wiphy_init(wiphy); + +#ifdef WIRELESS_SPY + priv->wireless_data.spy_data = &priv->spy_data; +#endif + + /* Set up default callbacks */ + priv->hard_reset = hard_reset; + priv->stop_fw = stop_fw; + + spin_lock_init(&priv->lock); + priv->open = 0; + priv->hw_unavailable = 1; /* orinoco_init() must clear this + * before anything else touches the + * hardware */ + INIT_WORK(&priv->reset_work, orinoco_reset); + INIT_WORK(&priv->join_work, orinoco_join_ap); + INIT_WORK(&priv->wevent_work, orinoco_send_wevents); + + INIT_LIST_HEAD(&priv->rx_list); + tasklet_init(&priv->rx_tasklet, orinoco_rx_isr_tasklet, + (unsigned long) priv); + + spin_lock_init(&priv->scan_lock); + INIT_LIST_HEAD(&priv->scan_list); + INIT_WORK(&priv->process_scan, orinoco_process_scan_results); + + priv->last_linkstatus = 0xffff; + +#if defined(CPTCFG_HERMES_CACHE_FW_ON_INIT) || defined(CONFIG_PM_SLEEP) + priv->cached_pri_fw = NULL; + priv->cached_fw = NULL; +#endif + + /* Register PM notifiers */ + orinoco_register_pm_notifier(priv); + + return priv; +} +EXPORT_SYMBOL(alloc_orinocodev); + +/* We can only support a single interface. We provide a separate + * function to set it up to distinguish between hardware + * initialisation and interface setup. + * + * The base_addr and irq parameters are passed on to netdev for use + * with SIOCGIFMAP. + */ +int orinoco_if_add(struct orinoco_private *priv, + unsigned long base_addr, + unsigned int irq, + const struct net_device_ops *ops) +{ + struct wiphy *wiphy = priv_to_wiphy(priv); + struct wireless_dev *wdev; + struct net_device *dev; + int ret; + + dev = alloc_etherdev(sizeof(struct wireless_dev)); + + if (!dev) + return -ENOMEM; + + /* Initialise wireless_dev */ + wdev = netdev_priv(dev); + wdev->wiphy = wiphy; + wdev->iftype = NL80211_IFTYPE_STATION; + + /* Setup / override net_device fields */ + dev->ieee80211_ptr = wdev; + dev->watchdog_timeo = HZ; /* 1 second timeout */ + dev->wireless_handlers = &orinoco_handler_def; +#ifdef WIRELESS_SPY + dev->wireless_data = &priv->wireless_data; +#endif + /* Default to standard ops if not set */ + if (ops) + netdev_attach_ops(dev, ops); + else + netdev_attach_ops(dev, &orinoco_netdev_ops); + + /* we use the default eth_mac_addr for setting the MAC addr */ + + /* Reserve space in skb for the SNAP header */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)) + dev->needed_headroom = ENCAPS_OVERHEAD; +#else + dev->hard_header_len += ENCAPS_OVERHEAD; +#endif + + netif_carrier_off(dev); + + memcpy(dev->dev_addr, wiphy->perm_addr, ETH_ALEN); + + dev->base_addr = base_addr; + dev->irq = irq; + + SET_NETDEV_DEV(dev, priv->dev); + ret = register_netdev(dev); + if (ret) + goto fail; + + priv->ndev = dev; + + /* Report what we've done */ + dev_dbg(priv->dev, "Registerred interface %s.\n", dev->name); + + return 0; + + fail: + free_netdev(dev); + return ret; +} +EXPORT_SYMBOL(orinoco_if_add); + +void orinoco_if_del(struct orinoco_private *priv) +{ + struct net_device *dev = priv->ndev; + + unregister_netdev(dev); + free_netdev(dev); +} +EXPORT_SYMBOL(orinoco_if_del); + +void free_orinocodev(struct orinoco_private *priv) +{ + struct wiphy *wiphy = priv_to_wiphy(priv); + struct orinoco_rx_data *rx_data, *temp; + struct orinoco_scan_data *sd, *sdtemp; + + wiphy_unregister(wiphy); + + /* If the tasklet is scheduled when we call tasklet_kill it + * will run one final time. However the tasklet will only + * drain priv->rx_list if the hw is still available. */ + tasklet_kill(&priv->rx_tasklet); + + /* Explicitly drain priv->rx_list */ + list_for_each_entry_safe(rx_data, temp, &priv->rx_list, list) { + list_del(&rx_data->list); + + dev_kfree_skb(rx_data->skb); + kfree(rx_data->desc); + kfree(rx_data); + } + + cancel_work_sync(&priv->process_scan); + /* Explicitly drain priv->scan_list */ + list_for_each_entry_safe(sd, sdtemp, &priv->scan_list, list) { + list_del(&sd->list); + + if ((sd->len > 0) && sd->buf) + kfree(sd->buf); + kfree(sd); + } + + orinoco_unregister_pm_notifier(priv); + orinoco_uncache_fw(priv); + + priv->wpa_ie_len = 0; + kfree(priv->wpa_ie); + orinoco_mic_free(priv); + wiphy_free(wiphy); +} +EXPORT_SYMBOL(free_orinocodev); + +int orinoco_up(struct orinoco_private *priv) +{ + struct net_device *dev = priv->ndev; + unsigned long flags; + int err; + + priv->hw.ops->lock_irqsave(&priv->lock, &flags); + + err = orinoco_reinit_firmware(priv); + if (err) { + printk(KERN_ERR "%s: Error %d re-initializing firmware\n", + dev->name, err); + goto exit; + } + + netif_device_attach(dev); + priv->hw_unavailable--; + + if (priv->open && !priv->hw_unavailable) { + err = __orinoco_up(priv); + if (err) + printk(KERN_ERR "%s: Error %d restarting card\n", + dev->name, err); + } + +exit: + priv->hw.ops->unlock_irqrestore(&priv->lock, &flags); + + return 0; +} +EXPORT_SYMBOL(orinoco_up); + +void orinoco_down(struct orinoco_private *priv) +{ + struct net_device *dev = priv->ndev; + unsigned long flags; + int err; + + priv->hw.ops->lock_irqsave(&priv->lock, &flags); + err = __orinoco_down(priv); + if (err) + printk(KERN_WARNING "%s: Error %d downing interface\n", + dev->name, err); + + netif_device_detach(dev); + priv->hw_unavailable++; + priv->hw.ops->unlock_irqrestore(&priv->lock, &flags); +} +EXPORT_SYMBOL(orinoco_down); + +/********************************************************************/ +/* Module initialization */ +/********************************************************************/ + +/* Can't be declared "const" or the whole __initdata section will + * become const */ +static char version[] __initdata = DRIVER_NAME " " DRIVER_VERSION + " (David Gibson , " + "Pavel Roskin , et al)"; + +static int __init init_orinoco(void) +{ + printk(KERN_DEBUG "%s\n", version); + return 0; +} + +static void __exit exit_orinoco(void) +{ +} + +module_init(init_orinoco); +module_exit(exit_orinoco); diff --git a/drivers/net/wireless/orinoco/main.h b/drivers/net/wireless/orinoco/main.h new file mode 100644 index 0000000..5a8fec2 --- /dev/null +++ b/drivers/net/wireless/orinoco/main.h @@ -0,0 +1,50 @@ +/* Exports from main to helper modules + * + * See copyright notice in main.c + */ +#ifndef _ORINOCO_MAIN_H_ +#define _ORINOCO_MAIN_H_ + +#include +#include "orinoco.h" + +/********************************************************************/ +/* Compile time configuration and compatibility stuff */ +/********************************************************************/ + +/* We do this this way to avoid ifdefs in the actual code */ +#ifdef WIRELESS_SPY +#define SPY_NUMBER(priv) (priv->spy_data.spy_number) +#else +#define SPY_NUMBER(priv) 0 +#endif /* WIRELESS_SPY */ + +/********************************************************************/ + +/* Export module parameter */ +extern int force_monitor; + +/* Forward declarations */ +struct net_device; +struct work_struct; + +void set_port_type(struct orinoco_private *priv); +int orinoco_commit(struct orinoco_private *priv); +void orinoco_reset(struct work_struct *work); + +/* Information element helpers - find a home for these... */ +#define WPA_OUI_TYPE "\x00\x50\xF2\x01" +#define WPA_SELECTOR_LEN 4 +static inline u8 *orinoco_get_wpa_ie(u8 *data, size_t len) +{ + u8 *p = data; + while ((p + 2 + WPA_SELECTOR_LEN) < (data + len)) { + if ((p[0] == WLAN_EID_VENDOR_SPECIFIC) && + (memcmp(&p[2], WPA_OUI_TYPE, WPA_SELECTOR_LEN) == 0)) + return p; + p += p[1] + 2; + } + return NULL; +} + +#endif /* _ORINOCO_MAIN_H_ */ diff --git a/drivers/net/wireless/orinoco/mic.c b/drivers/net/wireless/orinoco/mic.c new file mode 100644 index 0000000..fce4a84 --- /dev/null +++ b/drivers/net/wireless/orinoco/mic.c @@ -0,0 +1,79 @@ +/* Orinoco MIC helpers + * + * See copyright notice in main.c + */ +#include +#include +#include +#include +#include + +#include "orinoco.h" +#include "mic.h" + +/********************************************************************/ +/* Michael MIC crypto setup */ +/********************************************************************/ +int orinoco_mic_init(struct orinoco_private *priv) +{ + priv->tx_tfm_mic = crypto_alloc_hash("michael_mic", 0, 0); + if (IS_ERR(priv->tx_tfm_mic)) { + printk(KERN_DEBUG "orinoco_mic_init: could not allocate " + "crypto API michael_mic\n"); + priv->tx_tfm_mic = NULL; + return -ENOMEM; + } + + priv->rx_tfm_mic = crypto_alloc_hash("michael_mic", 0, 0); + if (IS_ERR(priv->rx_tfm_mic)) { + printk(KERN_DEBUG "orinoco_mic_init: could not allocate " + "crypto API michael_mic\n"); + priv->rx_tfm_mic = NULL; + return -ENOMEM; + } + + return 0; +} + +void orinoco_mic_free(struct orinoco_private *priv) +{ + if (priv->tx_tfm_mic) + crypto_free_hash(priv->tx_tfm_mic); + if (priv->rx_tfm_mic) + crypto_free_hash(priv->rx_tfm_mic); +} + +int orinoco_mic(struct crypto_hash *tfm_michael, u8 *key, + u8 *da, u8 *sa, u8 priority, + u8 *data, size_t data_len, u8 *mic) +{ + struct hash_desc desc; + struct scatterlist sg[2]; + u8 hdr[ETH_HLEN + 2]; /* size of header + padding */ + + if (tfm_michael == NULL) { + printk(KERN_WARNING "orinoco_mic: tfm_michael == NULL\n"); + return -1; + } + + /* Copy header into buffer. We need the padding on the end zeroed */ + memcpy(&hdr[0], da, ETH_ALEN); + memcpy(&hdr[ETH_ALEN], sa, ETH_ALEN); + hdr[ETH_ALEN * 2] = priority; + hdr[ETH_ALEN * 2 + 1] = 0; + hdr[ETH_ALEN * 2 + 2] = 0; + hdr[ETH_ALEN * 2 + 3] = 0; + + /* Use scatter gather to MIC header and data in one go */ + sg_init_table(sg, 2); + sg_set_buf(&sg[0], hdr, sizeof(hdr)); + sg_set_buf(&sg[1], data, data_len); + + if (crypto_hash_setkey(tfm_michael, key, MIC_KEYLEN)) + return -1; + + desc.tfm = tfm_michael; + desc.flags = 0; + return crypto_hash_digest(&desc, sg, data_len + sizeof(hdr), + mic); +} diff --git a/drivers/net/wireless/orinoco/mic.h b/drivers/net/wireless/orinoco/mic.h new file mode 100644 index 0000000..04d05bc --- /dev/null +++ b/drivers/net/wireless/orinoco/mic.h @@ -0,0 +1,22 @@ +/* Orinoco MIC helpers + * + * See copyright notice in main.c + */ +#ifndef _ORINOCO_MIC_H_ +#define _ORINOCO_MIC_H_ + +#include + +#define MICHAEL_MIC_LEN 8 + +/* Forward declarations */ +struct orinoco_private; +struct crypto_hash; + +int orinoco_mic_init(struct orinoco_private *priv); +void orinoco_mic_free(struct orinoco_private *priv); +int orinoco_mic(struct crypto_hash *tfm_michael, u8 *key, + u8 *da, u8 *sa, u8 priority, + u8 *data, size_t data_len, u8 *mic); + +#endif /* ORINOCO_MIC_H */ diff --git a/drivers/net/wireless/orinoco/orinoco.h b/drivers/net/wireless/orinoco/orinoco.h new file mode 100644 index 0000000..41853d7 --- /dev/null +++ b/drivers/net/wireless/orinoco/orinoco.h @@ -0,0 +1,256 @@ +/* orinoco.h + * + * Common definitions to all pieces of the various orinoco + * drivers + */ + +#ifndef _ORINOCO_H +#define _ORINOCO_H + +#define DRIVER_VERSION "0.15" + +#include +#include +#include +#include +#include +#include + +#include "hermes.h" + +/* To enable debug messages */ +/*#define ORINOCO_DEBUG 3*/ + +#define WIRELESS_SPY /* enable iwspy support */ + +#define MAX_SCAN_LEN 4096 + +#define ORINOCO_SEQ_LEN 8 +#define ORINOCO_MAX_KEY_SIZE 14 +#define ORINOCO_MAX_KEYS 4 + +struct orinoco_key { + __le16 len; /* always stored as little-endian */ + char data[ORINOCO_MAX_KEY_SIZE]; +} __packed; + +#define TKIP_KEYLEN 16 +#define MIC_KEYLEN 8 + +struct orinoco_tkip_key { + u8 tkip[TKIP_KEYLEN]; + u8 tx_mic[MIC_KEYLEN]; + u8 rx_mic[MIC_KEYLEN]; +}; + +enum orinoco_alg { + ORINOCO_ALG_NONE, + ORINOCO_ALG_WEP, + ORINOCO_ALG_TKIP +}; + +enum fwtype { + FIRMWARE_TYPE_AGERE, + FIRMWARE_TYPE_INTERSIL, + FIRMWARE_TYPE_SYMBOL +}; + +struct firmware; + +struct orinoco_private { + void *card; /* Pointer to card dependent structure */ + struct device *dev; + int (*hard_reset)(struct orinoco_private *); + int (*stop_fw)(struct orinoco_private *, int); + + struct ieee80211_supported_band band; + struct ieee80211_channel channels[14]; + u32 cipher_suites[3]; + + /* Synchronisation stuff */ + spinlock_t lock; + int hw_unavailable; + struct work_struct reset_work; + + /* Interrupt tasklets */ + struct tasklet_struct rx_tasklet; + struct list_head rx_list; + + /* driver state */ + int open; + u16 last_linkstatus; + struct work_struct join_work; + struct work_struct wevent_work; + + /* Net device stuff */ + struct net_device *ndev; + struct net_device_stats stats; + struct iw_statistics wstats; + + /* Hardware control variables */ + struct hermes hw; + u16 txfid; + + /* Capabilities of the hardware/firmware */ + enum fwtype firmware_type; + int ibss_port; + int nicbuf_size; + u16 channel_mask; + + /* Boolean capabilities */ + unsigned int has_ibss:1; + unsigned int has_port3:1; + unsigned int has_wep:1; + unsigned int has_big_wep:1; + unsigned int has_mwo:1; + unsigned int has_pm:1; + unsigned int has_preamble:1; + unsigned int has_sensitivity:1; + unsigned int has_hostscan:1; + unsigned int has_alt_txcntl:1; + unsigned int has_ext_scan:1; + unsigned int has_wpa:1; + unsigned int do_fw_download:1; + unsigned int broken_disableport:1; + unsigned int broken_monitor:1; + unsigned int prefer_port3:1; + + /* Configuration paramaters */ + enum nl80211_iftype iw_mode; + enum orinoco_alg encode_alg; + u16 wep_restrict, tx_key; + struct key_params keys[ORINOCO_MAX_KEYS]; + + int bitratemode; + char nick[IW_ESSID_MAX_SIZE + 1]; + char desired_essid[IW_ESSID_MAX_SIZE + 1]; + char desired_bssid[ETH_ALEN]; + int bssid_fixed; + u16 frag_thresh, mwo_robust; + u16 channel; + u16 ap_density, rts_thresh; + u16 pm_on, pm_mcast, pm_period, pm_timeout; + u16 preamble; + u16 short_retry_limit, long_retry_limit; + u16 retry_lifetime; +#ifdef WIRELESS_SPY + struct iw_spy_data spy_data; /* iwspy support */ + struct iw_public_data wireless_data; +#endif + + /* Configuration dependent variables */ + int port_type, createibss; + int promiscuous, mc_count; + + /* Scanning support */ + struct cfg80211_scan_request *scan_request; + struct work_struct process_scan; + struct list_head scan_list; + spinlock_t scan_lock; /* protects the scan list */ + + /* WPA support */ + u8 *wpa_ie; + int wpa_ie_len; + + struct crypto_hash *rx_tfm_mic; + struct crypto_hash *tx_tfm_mic; + + unsigned int wpa_enabled:1; + unsigned int tkip_cm_active:1; + unsigned int key_mgmt:3; + +#if defined(CPTCFG_HERMES_CACHE_FW_ON_INIT) || defined(CONFIG_PM_SLEEP) + /* Cached in memory firmware to use during ->resume. */ + const struct firmware *cached_pri_fw; + const struct firmware *cached_fw; +#endif + + struct notifier_block pm_notifier; +}; + +#ifdef ORINOCO_DEBUG +extern int orinoco_debug; +#define DEBUG(n, args...) do { \ + if (orinoco_debug > (n)) \ + printk(KERN_DEBUG args); \ +} while (0) +#else +#define DEBUG(n, args...) do { } while (0) +#endif /* ORINOCO_DEBUG */ + +/********************************************************************/ +/* Exported prototypes */ +/********************************************************************/ + +extern struct orinoco_private *alloc_orinocodev( + int sizeof_card, struct device *device, + int (*hard_reset)(struct orinoco_private *), + int (*stop_fw)(struct orinoco_private *, int)); +extern void free_orinocodev(struct orinoco_private *priv); +extern int orinoco_init(struct orinoco_private *priv); +extern int orinoco_if_add(struct orinoco_private *priv, + unsigned long base_addr, + unsigned int irq, + const struct net_device_ops *ops); +extern void orinoco_if_del(struct orinoco_private *priv); +extern int orinoco_up(struct orinoco_private *priv); +extern void orinoco_down(struct orinoco_private *priv); +extern irqreturn_t orinoco_interrupt(int irq, void *dev_id); + +extern void __orinoco_ev_info(struct net_device *dev, struct hermes *hw); +extern void __orinoco_ev_rx(struct net_device *dev, struct hermes *hw); + +int orinoco_process_xmit_skb(struct sk_buff *skb, + struct net_device *dev, + struct orinoco_private *priv, + int *tx_control, + u8 *mic); + +/* Common ndo functions exported for reuse by orinoco_usb */ +int orinoco_open(struct net_device *dev); +int orinoco_stop(struct net_device *dev); +struct net_device_stats *orinoco_get_stats(struct net_device *dev); +void orinoco_set_multicast_list(struct net_device *dev); +int orinoco_change_mtu(struct net_device *dev, int new_mtu); +void orinoco_tx_timeout(struct net_device *dev); + +/********************************************************************/ +/* Locking and synchronization functions */ +/********************************************************************/ + +static inline int orinoco_lock(struct orinoco_private *priv, + unsigned long *flags) +{ + priv->hw.ops->lock_irqsave(&priv->lock, flags); + if (priv->hw_unavailable) { + DEBUG(1, "orinoco_lock() called with hw_unavailable (dev=%p)\n", + priv->ndev); + priv->hw.ops->unlock_irqrestore(&priv->lock, flags); + return -EBUSY; + } + return 0; +} + +static inline void orinoco_unlock(struct orinoco_private *priv, + unsigned long *flags) +{ + priv->hw.ops->unlock_irqrestore(&priv->lock, flags); +} + +static inline void orinoco_lock_irq(struct orinoco_private *priv) +{ + priv->hw.ops->lock_irq(&priv->lock); +} + +static inline void orinoco_unlock_irq(struct orinoco_private *priv) +{ + priv->hw.ops->unlock_irq(&priv->lock); +} + +/*** Navigate from net_device to orinoco_private ***/ +static inline struct orinoco_private *ndev_priv(struct net_device *dev) +{ + struct wireless_dev *wdev = netdev_priv(dev); + return wdev_priv(wdev); +} +#endif /* _ORINOCO_H */ diff --git a/drivers/net/wireless/orinoco/orinoco_cs.c b/drivers/net/wireless/orinoco/orinoco_cs.c new file mode 100644 index 0000000..8e352bf --- /dev/null +++ b/drivers/net/wireless/orinoco/orinoco_cs.c @@ -0,0 +1,472 @@ +/* orinoco_cs.c (formerly known as dldwd_cs.c) + * + * A driver for "Hermes" chipset based PCMCIA wireless adaptors, such + * as the Lucent WavelanIEEE/Orinoco cards and their OEM (Cabletron/ + * EnteraSys RoamAbout 802.11, ELSA Airlancer, Melco Buffalo and others). + * It should also be usable on various Prism II based cards such as the + * Linksys, D-Link and Farallon Skyline. It should also work on Symbol + * cards such as the 3Com AirConnect and Ericsson WLAN. + * + * Copyright notice & release notes in file main.c + */ + +#define DRIVER_NAME "orinoco_cs" +#define PFX DRIVER_NAME ": " + +#include +#include +#include +#include +#include +#include +#include + +#include "orinoco.h" + +/********************************************************************/ +/* Module stuff */ +/********************************************************************/ + +MODULE_AUTHOR("David Gibson "); +MODULE_DESCRIPTION("Driver for PCMCIA Lucent Orinoco," + " Prism II based and similar wireless cards"); +MODULE_LICENSE("Dual MPL/GPL"); + +/* Module parameters */ + +/* Some D-Link cards have buggy CIS. They do work at 5v properly, but + * don't have any CIS entry for it. This workaround it... */ +static int ignore_cis_vcc; /* = 0 */ +module_param(ignore_cis_vcc, int, 0); +MODULE_PARM_DESC(ignore_cis_vcc, "Allow voltage mismatch between card and socket"); + +/********************************************************************/ +/* Data structures */ +/********************************************************************/ + +/* PCMCIA specific device information (goes in the card field of + * struct orinoco_private */ +struct orinoco_pccard { + struct pcmcia_device *p_dev; + + /* Used to handle hard reset */ + /* yuck, we need this hack to work around the insanity of the + * PCMCIA layer */ + unsigned long hard_reset_in_progress; +}; + + +/********************************************************************/ +/* Function prototypes */ +/********************************************************************/ + +static int orinoco_cs_config(struct pcmcia_device *link); +static void orinoco_cs_release(struct pcmcia_device *link); +static void orinoco_cs_detach(struct pcmcia_device *p_dev); + +/********************************************************************/ +/* Device methods */ +/********************************************************************/ + +static int +orinoco_cs_hard_reset(struct orinoco_private *priv) +{ + struct orinoco_pccard *card = priv->card; + struct pcmcia_device *link = card->p_dev; + int err; + + /* We need atomic ops here, because we're not holding the lock */ + set_bit(0, &card->hard_reset_in_progress); + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,27) + err = pcmcia_reset_card(link, NULL); +#else + err = pcmcia_reset_card(link->socket); +#endif + if (err) + return err; + + msleep(100); + clear_bit(0, &card->hard_reset_in_progress); + + return 0; +} + +/********************************************************************/ +/* PCMCIA stuff */ +/********************************************************************/ + +static int +orinoco_cs_probe(struct pcmcia_device *link) +{ + struct orinoco_private *priv; + struct orinoco_pccard *card; + + priv = alloc_orinocodev(sizeof(*card), &link->dev, + orinoco_cs_hard_reset, NULL); + if (!priv) + return -ENOMEM; + card = priv->card; + + /* Link both structures together */ + card->p_dev = link; + link->priv = priv; + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)) + /* Interrupt setup */ + link->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING; + link->irq.Handler = orinoco_interrupt; +#endif +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37)) + link->conf.Attributes = 0; + link->conf.IntType = INT_MEMORY_AND_IO; +#endif + + return orinoco_cs_config(link); +} /* orinoco_cs_attach */ + +static void orinoco_cs_detach(struct pcmcia_device *link) +{ + struct orinoco_private *priv = link->priv; + + orinoco_if_del(priv); + + orinoco_cs_release(link); + + free_orinocodev(priv); +} /* orinoco_cs_detach */ + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)) +static int orinoco_cs_config_check(struct pcmcia_device *p_dev, void *priv_data) +{ + if (p_dev->config_index == 0) + return -EINVAL; + + return pcmcia_request_io(p_dev); +}; +#else +static int orinoco_cs_config_check(struct pcmcia_device *p_dev, + cistpl_cftable_entry_t *cfg, + cistpl_cftable_entry_t *dflt, + unsigned int vcc, + void *priv_data) +{ + if (cfg->index == 0) + goto next_entry; + + /* Use power settings for Vcc and Vpp if present */ + /* Note that the CIS values need to be rescaled */ + if (cfg->vcc.present & (1 << CISTPL_POWER_VNOM)) { + if (vcc != cfg->vcc.param[CISTPL_POWER_VNOM] / 10000) { + DEBUG(2, "%s: Vcc mismatch (vcc = %d, CIS = %d)\n", + __func__, vcc, + cfg->vcc.param[CISTPL_POWER_VNOM] / 10000); + if (!ignore_cis_vcc) + goto next_entry; + } + } else if (dflt->vcc.present & (1 << CISTPL_POWER_VNOM)) { + if (vcc != dflt->vcc.param[CISTPL_POWER_VNOM] / 10000) { + DEBUG(2, "%s: Vcc mismatch (vcc = %d, CIS = %d)\n", + __func__, vcc, + dflt->vcc.param[CISTPL_POWER_VNOM] / 10000); + if (!ignore_cis_vcc) + goto next_entry; + } + } + + if (cfg->vpp1.present & (1 << CISTPL_POWER_VNOM)) + p_dev->conf.Vpp = + cfg->vpp1.param[CISTPL_POWER_VNOM] / 10000; + else if (dflt->vpp1.present & (1 << CISTPL_POWER_VNOM)) + p_dev->conf.Vpp = + dflt->vpp1.param[CISTPL_POWER_VNOM] / 10000; + + /* Do we need to allocate an interrupt? */ + p_dev->conf.Attributes |= CONF_ENABLE_IRQ; + + /* IO window settings */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) + p_dev->resource[0]->end = p_dev->resource[1]->end = 0; +#else + p_dev->io.NumPorts1 = p_dev->io.NumPorts2 = 0; +#endif + if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) { + cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) + p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK; + p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; + p_dev->resource[0]->flags |= + pcmcia_io_cfg_data_width(io->flags); + p_dev->resource[0]->start = io->win[0].base; + p_dev->resource[0]->end = io->win[0].len; +#else + p_dev->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + if (!(io->flags & CISTPL_IO_8BIT)) + p_dev->io.Attributes1 = IO_DATA_PATH_WIDTH_16; + if (!(io->flags & CISTPL_IO_16BIT)) + p_dev->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + p_dev->io.IOAddrLines = io->flags & CISTPL_IO_LINES_MASK; + p_dev->io.BasePort1 = io->win[0].base; + p_dev->io.NumPorts1 = io->win[0].len; +#endif + if (io->nwin > 1) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) + p_dev->resource[1]->flags = p_dev->resource[0]->flags; + p_dev->resource[1]->start = io->win[1].base; + p_dev->resource[1]->end = io->win[1].len; +#else + p_dev->io.Attributes2 = p_dev->io.Attributes1; + p_dev->io.BasePort2 = io->win[1].base; + p_dev->io.NumPorts2 = io->win[1].len; +#endif + } + + /* This reserves IO space but doesn't actually enable it */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) + if (pcmcia_request_io(p_dev) != 0) +#else + if (pcmcia_request_io(p_dev, &p_dev->io) != 0) +#endif + goto next_entry; + } + return 0; + +next_entry: + pcmcia_disable_device(p_dev); + return -ENODEV; +}; +#endif + +static int +orinoco_cs_config(struct pcmcia_device *link) +{ + struct orinoco_private *priv = link->priv; + struct hermes *hw = &priv->hw; + int ret; + void __iomem *mem; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)) + link->config_flags |= CONF_AUTO_SET_VPP | CONF_AUTO_CHECK_VCC | + CONF_AUTO_SET_IO | CONF_ENABLE_IRQ; + if (ignore_cis_vcc) + link->config_flags &= ~CONF_AUTO_CHECK_VCC; +#endif + ret = pcmcia_loop_config(link, orinoco_cs_config_check, NULL); + if (ret) { + if (!ignore_cis_vcc) + printk(KERN_ERR PFX "GetNextTuple(): No matching " + "CIS configuration. Maybe you need the " + "ignore_cis_vcc=1 parameter.\n"); + goto failed; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) + mem = ioport_map(link->resource[0]->start, + resource_size(link->resource[0])); +#else + mem = ioport_map(link->io.BasePort1, link->io.NumPorts1); +#endif + if (!mem) + goto failed; + + /* We initialize the hermes structure before completing PCMCIA + * configuration just in case the interrupt handler gets + * called. */ + hermes_struct_init(hw, mem, HERMES_16BIT_REGSPACING); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + ret = pcmcia_request_irq(link, orinoco_interrupt); +#else + ret = pcmcia_request_irq(link, &link->irq); +#endif + if (ret) + goto failed; + + ret = pcmcia_enable_device(link); + if (ret) + goto failed; + + /* Initialise the main driver */ + if (orinoco_init(priv) != 0) { + printk(KERN_ERR PFX "orinoco_init() failed\n"); + goto failed; + } + + /* Register an interface with the stack */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)) + if (orinoco_if_add(priv, link->resource[0]->start, + link->irq, NULL) != 0) { +#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + if (orinoco_if_add(priv, link->io.BasePort1, + link->irq, NULL) != 0) { +#else + if (orinoco_if_add(priv, link->io.BasePort1, + link->irq.AssignedIRQ, NULL) != 0) { +#endif + printk(KERN_ERR PFX "orinoco_if_add() failed\n"); + goto failed; + } + + return 0; + + failed: + orinoco_cs_release(link); + return -ENODEV; +} /* orinoco_cs_config */ + +static void +orinoco_cs_release(struct pcmcia_device *link) +{ + struct orinoco_private *priv = link->priv; + unsigned long flags; + + /* We're committed to taking the device away now, so mark the + * hardware as unavailable */ + priv->hw.ops->lock_irqsave(&priv->lock, &flags); + priv->hw_unavailable++; + priv->hw.ops->unlock_irqrestore(&priv->lock, &flags); + + pcmcia_disable_device(link); + if (priv->hw.iobase) + ioport_unmap(priv->hw.iobase); +} /* orinoco_cs_release */ + +static int orinoco_cs_suspend(struct pcmcia_device *link) +{ + struct orinoco_private *priv = link->priv; + struct orinoco_pccard *card = priv->card; + + /* This is probably racy, but I can't think of + a better way, short of rewriting the PCMCIA + layer to not suck :-( */ + if (!test_bit(0, &card->hard_reset_in_progress)) + orinoco_down(priv); + + return 0; +} + +static int orinoco_cs_resume(struct pcmcia_device *link) +{ + struct orinoco_private *priv = link->priv; + struct orinoco_pccard *card = priv->card; + int err = 0; + + if (!test_bit(0, &card->hard_reset_in_progress)) + err = orinoco_up(priv); + + return err; +} + + +/********************************************************************/ +/* Module initialization */ +/********************************************************************/ + +static const struct pcmcia_device_id orinoco_cs_ids[] = { + PCMCIA_DEVICE_MANF_CARD(0x0101, 0x0777), /* 3Com AirConnect PCI 777A */ + PCMCIA_DEVICE_MANF_CARD(0x016b, 0x0001), /* Ericsson WLAN Card C11 */ + PCMCIA_DEVICE_MANF_CARD(0x01eb, 0x080a), /* Nortel Networks eMobility 802.11 Wireless Adapter */ + PCMCIA_DEVICE_MANF_CARD(0x0261, 0x0002), /* AirWay 802.11 Adapter (PCMCIA) */ + PCMCIA_DEVICE_MANF_CARD(0x0268, 0x0001), /* ARtem Onair */ + PCMCIA_DEVICE_MANF_CARD(0x0268, 0x0003), /* ARtem Onair Comcard 11 */ + PCMCIA_DEVICE_MANF_CARD(0x026f, 0x0305), /* Buffalo WLI-PCM-S11 */ + PCMCIA_DEVICE_MANF_CARD(0x02aa, 0x0002), /* ASUS SpaceLink WL-100 */ + PCMCIA_DEVICE_MANF_CARD(0x02ac, 0x0002), /* SpeedStream SS1021 Wireless Adapter */ + PCMCIA_DEVICE_MANF_CARD(0x02ac, 0x3021), /* SpeedStream Wireless Adapter */ + PCMCIA_DEVICE_MANF_CARD(0x14ea, 0xb001), /* PLANEX RoadLannerWave GW-NS11H */ + PCMCIA_DEVICE_PROD_ID12("3Com", "3CRWE737A AirConnect Wireless LAN PC Card", 0x41240e5b, 0x56010af3), + PCMCIA_DEVICE_PROD_ID12("Allied Telesyn", "AT-WCL452 Wireless PCMCIA Radio", 0x5cd01705, 0x4271660f), + PCMCIA_DEVICE_PROD_ID12("ASUS", "802_11B_CF_CARD_25", 0x78fc06ee, 0x45a50c1e), + PCMCIA_DEVICE_PROD_ID12("ASUS", "802_11b_PC_CARD_25", 0x78fc06ee, 0xdb9aa842), + PCMCIA_DEVICE_PROD_ID12("Avaya Communication", "Avaya Wireless PC Card", 0xd8a43b78, 0x0d341169), + PCMCIA_DEVICE_PROD_ID12("BENQ", "AWL100 PCMCIA ADAPTER", 0x35dadc74, 0x01f7fedb), + PCMCIA_DEVICE_PROD_ID12("Cabletron", "RoamAbout 802.11 DS", 0x32d445f5, 0xedeffd90), + PCMCIA_DEVICE_PROD_ID12("D-Link Corporation", "D-Link DWL-650H 11Mbps WLAN Adapter", 0xef544d24, 0xcd8ea916), + PCMCIA_DEVICE_PROD_ID12("ELSA", "AirLancer MC-11", 0x4507a33a, 0xef54f0e3), + PCMCIA_DEVICE_PROD_ID12("HyperLink", "Wireless PC Card 11Mbps", 0x56cc3f1a, 0x0bcf220c), + PCMCIA_DEVICE_PROD_ID12("Intel", "PRO/Wireless 2011 LAN PC Card", 0x816cc815, 0x07f58077), + PCMCIA_DEVICE_PROD_ID12("LeArtery", "SYNCBYAIR 11Mbps Wireless LAN PC Card", 0x7e3b326a, 0x49893e92), + PCMCIA_DEVICE_PROD_ID12("Lucent Technologies", "WaveLAN/IEEE", 0x23eb9949, 0xc562e72a), + PCMCIA_DEVICE_PROD_ID12("MELCO", "WLI-PCM-L11", 0x481e0094, 0x7360e410), + PCMCIA_DEVICE_PROD_ID12("MELCO", "WLI-PCM-L11G", 0x481e0094, 0xf57ca4b3), + PCMCIA_DEVICE_PROD_ID12("NCR", "WaveLAN/IEEE", 0x24358cd4, 0xc562e72a), + PCMCIA_DEVICE_PROD_ID12("Nortel Networks", "emobility 802.11 Wireless LAN PC Card", 0x2d617ea0, 0x88cd5767), + PCMCIA_DEVICE_PROD_ID12("OTC", "Wireless AirEZY 2411-PCC WLAN Card", 0x4ac44287, 0x235a6bed), + PCMCIA_DEVICE_PROD_ID12("PROXIM", "LAN PC CARD HARMONY 80211B", 0xc6536a5e, 0x090c3cd9), + PCMCIA_DEVICE_PROD_ID12("PROXIM", "LAN PCI CARD HARMONY 80211B", 0xc6536a5e, 0x9f494e26), + PCMCIA_DEVICE_PROD_ID12("SAMSUNG", "11Mbps WLAN Card", 0x43d74cb4, 0x579bd91b), + PCMCIA_DEVICE_PROD_ID12("Symbol Technologies", "LA4111 Spectrum24 Wireless LAN PC Card", 0x3f02b4d6, 0x3663cb0e), + PCMCIA_DEVICE_MANF_CARD_PROD_ID3(0x0156, 0x0002, "Version 01.01", 0xd27deb1a), /* Lucent Orinoco */ +#ifdef CPTCFG_HERMES_PRISM + /* Only entries that certainly identify Prism chipset */ + PCMCIA_DEVICE_MANF_CARD(0x000b, 0x7100), /* SonicWALL Long Range Wireless Card */ + PCMCIA_DEVICE_MANF_CARD(0x000b, 0x7300), /* Sohoware NCP110, Philips 802.11b */ + PCMCIA_DEVICE_MANF_CARD(0x0089, 0x0002), /* AnyPoint(TM) Wireless II PC Card */ + PCMCIA_DEVICE_MANF_CARD(0x0126, 0x8000), /* PROXIM RangeLAN-DS/LAN PC CARD */ + PCMCIA_DEVICE_MANF_CARD(0x0138, 0x0002), /* Compaq WL100 11 Mbps Wireless Adapter */ + PCMCIA_DEVICE_MANF_CARD(0x01ff, 0x0008), /* Intermec MobileLAN 11Mbps 802.11b WLAN Card */ + PCMCIA_DEVICE_MANF_CARD(0x0250, 0x0002), /* Samsung SWL2000-N 11Mb/s WLAN Card */ + PCMCIA_DEVICE_MANF_CARD(0x0274, 0x1612), /* Linksys WPC11 Version 2.5 */ + PCMCIA_DEVICE_MANF_CARD(0x0274, 0x1613), /* Linksys WPC11 Version 3 */ + PCMCIA_DEVICE_MANF_CARD(0x028a, 0x0002), /* Compaq HNW-100 11 Mbps Wireless Adapter */ + PCMCIA_DEVICE_MANF_CARD(0x028a, 0x0673), /* Linksys WCF12 Wireless CompactFlash Card */ + PCMCIA_DEVICE_MANF_CARD(0x50c2, 0x7300), /* Airvast WN-100 */ + PCMCIA_DEVICE_MANF_CARD(0x9005, 0x0021), /* Adaptec Ultra Wireless ANW-8030 */ + PCMCIA_DEVICE_MANF_CARD(0xc001, 0x0008), /* CONTEC FLEXSCAN/FX-DDS110-PCC */ + PCMCIA_DEVICE_MANF_CARD(0xc250, 0x0002), /* Conceptronic CON11Cpro, EMTAC A2424i */ + PCMCIA_DEVICE_MANF_CARD(0xd601, 0x0002), /* Safeway 802.11b, ZCOMAX AirRunner/XI-300 */ + PCMCIA_DEVICE_MANF_CARD(0xd601, 0x0005), /* D-Link DCF660, Sandisk Connect SDWCFB-000 */ + PCMCIA_DEVICE_PROD_ID123("Instant Wireless ", " Network PC CARD", "Version 01.02", 0x11d901af, 0x6e9bd926, 0x4b74baa0), + PCMCIA_DEVICE_PROD_ID12("ACTIONTEC", "PRISM Wireless LAN PC Card", 0x393089da, 0xa71e69d5), + PCMCIA_DEVICE_PROD_ID12("Addtron", "AWP-100 Wireless PCMCIA", 0xe6ec52ce, 0x08649af2), + PCMCIA_DEVICE_PROD_ID12("BUFFALO", "WLI-CF-S11G", 0x2decece3, 0x82067c18), + PCMCIA_DEVICE_PROD_ID12("BUFFALO", "WLI-PCM-L11G", 0x2decece3, 0xf57ca4b3), + PCMCIA_DEVICE_PROD_ID12("Compaq", "WL200_11Mbps_Wireless_PCI_Card", 0x54f7c49c, 0x15a75e5b), + PCMCIA_DEVICE_PROD_ID12("corega K.K.", "Wireless LAN PCC-11", 0x5261440f, 0xa6405584), + PCMCIA_DEVICE_PROD_ID12("corega K.K.", "Wireless LAN PCCA-11", 0x5261440f, 0xdf6115f9), + PCMCIA_DEVICE_PROD_ID12("corega_K.K.", "Wireless_LAN_PCCB-11", 0x29e33311, 0xee7a27ae), + PCMCIA_DEVICE_PROD_ID12("Digital Data Communications", "WPC-0100", 0xfdd73470, 0xe0b6f146), + PCMCIA_DEVICE_PROD_ID12("D", "Link DRC-650 11Mbps WLAN Card", 0x71b18589, 0xf144e3ac), + PCMCIA_DEVICE_PROD_ID12("D", "Link DWL-650 11Mbps WLAN Card", 0x71b18589, 0xb6f1b0ab), + PCMCIA_DEVICE_PROD_ID12(" ", "IEEE 802.11 Wireless LAN/PC Card", 0x3b6e20c8, 0xefccafe9), + PCMCIA_DEVICE_PROD_ID12("INTERSIL", "HFA384x/IEEE", 0x74c5e40d, 0xdb472a18), + PCMCIA_DEVICE_PROD_ID12("INTERSIL", "I-GATE 11M PC Card / PC Card plus", 0x74c5e40d, 0x8304ff77), + PCMCIA_DEVICE_PROD_ID12("Intersil", "PRISM 2_5 PCMCIA ADAPTER", 0x4b801a17, 0x6345a0bf), + PCMCIA_DEVICE_PROD_ID12("Linksys", "Wireless CompactFlash Card", 0x0733cc81, 0x0c52f395), + PCMCIA_DEVICE_PROD_ID12("Microsoft", "Wireless Notebook Adapter MN-520", 0x5961bf85, 0x6eec8c01), + PCMCIA_DEVICE_PROD_ID12("NETGEAR MA401RA Wireless PC", "Card", 0x0306467f, 0x9762e8f1), + PCMCIA_DEVICE_PROD_ID12("NETGEAR MA401 Wireless PC", "Card", 0xa37434e9, 0x9762e8f1), + PCMCIA_DEVICE_PROD_ID12("OEM", "PRISM2 IEEE 802.11 PC-Card", 0xfea54c90, 0x48f2bdd6), + PCMCIA_DEVICE_PROD_ID12("PLANEX", "GeoWave/GW-CF110", 0x209f40ab, 0xd9715264), + PCMCIA_DEVICE_PROD_ID12("PLANEX", "GeoWave/GW-NS110", 0x209f40ab, 0x46263178), + PCMCIA_DEVICE_PROD_ID12("SMC", "SMC2532W-B EliteConnect Wireless Adapter", 0xc4f8b18b, 0x196bd757), + PCMCIA_DEVICE_PROD_ID12("SMC", "SMC2632W", 0xc4f8b18b, 0x474a1f2a), + PCMCIA_DEVICE_PROD_ID12("ZoomAir 11Mbps High", "Rate wireless Networking", 0x273fe3db, 0x32a1eaee), + PCMCIA_DEVICE_PROD_ID3("HFA3863", 0x355cb092), + PCMCIA_DEVICE_PROD_ID3("ISL37100P", 0x630d52b2), + PCMCIA_DEVICE_PROD_ID3("ISL37101P-10", 0xdd97a26b), + PCMCIA_DEVICE_PROD_ID3("ISL37300P", 0xc9049a39), + + /* This may be Agere or Intersil Firmware */ + PCMCIA_DEVICE_MANF_CARD(0x0156, 0x0002), +#endif + PCMCIA_DEVICE_NULL, +}; +MODULE_DEVICE_TABLE(pcmcia, orinoco_cs_ids); + +static struct pcmcia_driver orinoco_driver = { + .owner = THIS_MODULE, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)) + .name = DRIVER_NAME, +#else + .drv = { + .name = DRIVER_NAME, + }, +#endif + .probe = orinoco_cs_probe, + .remove = orinoco_cs_detach, + .id_table = orinoco_cs_ids, + .suspend = orinoco_cs_suspend, + .resume = orinoco_cs_resume, +}; +module_pcmcia_driver(orinoco_driver); diff --git a/drivers/net/wireless/orinoco/orinoco_nortel.c b/drivers/net/wireless/orinoco/orinoco_nortel.c new file mode 100644 index 0000000..d73fdf6 --- /dev/null +++ b/drivers/net/wireless/orinoco/orinoco_nortel.c @@ -0,0 +1,322 @@ +/* orinoco_nortel.c + * + * Driver for Prism II devices which would usually be driven by orinoco_cs, + * but are connected to the PCI bus by a PCI-to-PCMCIA adapter used in + * Nortel emobility, Symbol LA-4113 and Symbol LA-4123. + * + * Copyright (C) 2002 Tobias Hoffmann + * (C) 2003 Christoph Jungegger + * + * Some of this code is borrowed from orinoco_plx.c + * Copyright (C) 2001 Daniel Barlow + * Some of this code is borrowed from orinoco_pci.c + * Copyright (C) 2001 Jean Tourrilhes + * Some of this code is "inspired" by linux-wlan-ng-0.1.10, but nothing + * has been copied from it. linux-wlan-ng-0.1.10 is originally : + * Copyright (C) 1999 AbsoluteValue Systems, Inc. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#define DRIVER_NAME "orinoco_nortel" +#define PFX DRIVER_NAME ": " + +#include +#include +#include +#include +#include +#include + +#include "orinoco.h" +#include "orinoco_pci.h" + +#define COR_OFFSET (0xe0) /* COR attribute offset of Prism2 PC card */ +#define COR_VALUE (COR_LEVEL_REQ | COR_FUNC_ENA) /* Enable PC card with interrupt in level trigger */ + + +/* + * Do a soft reset of the card using the Configuration Option Register + * We need this to get going... + * This is the part of the code that is strongly inspired from wlan-ng + * + * Note bis : Don't try to access HERMES_CMD during the reset phase. + * It just won't work ! + */ +static int orinoco_nortel_cor_reset(struct orinoco_private *priv) +{ + struct orinoco_pci_card *card = priv->card; + + /* Assert the reset until the card notices */ + iowrite16(8, card->bridge_io + 2); + ioread16(card->attr_io + COR_OFFSET); + iowrite16(0x80, card->attr_io + COR_OFFSET); + mdelay(1); + + /* Give time for the card to recover from this hard effort */ + iowrite16(0, card->attr_io + COR_OFFSET); + iowrite16(0, card->attr_io + COR_OFFSET); + mdelay(1); + + /* Set COR as usual */ + iowrite16(COR_VALUE, card->attr_io + COR_OFFSET); + iowrite16(COR_VALUE, card->attr_io + COR_OFFSET); + mdelay(1); + + iowrite16(0x228, card->bridge_io + 2); + + return 0; +} + +static int orinoco_nortel_hw_init(struct orinoco_pci_card *card) +{ + int i; + u32 reg; + + /* Setup bridge */ + if (ioread16(card->bridge_io) & 1) { + printk(KERN_ERR PFX "brg1 answer1 wrong\n"); + return -EBUSY; + } + iowrite16(0x118, card->bridge_io + 2); + iowrite16(0x108, card->bridge_io + 2); + mdelay(30); + iowrite16(0x8, card->bridge_io + 2); + for (i = 0; i < 30; i++) { + mdelay(30); + if (ioread16(card->bridge_io) & 0x10) + break; + } + if (i == 30) { + printk(KERN_ERR PFX "brg1 timed out\n"); + return -EBUSY; + } + if (ioread16(card->attr_io + COR_OFFSET) & 1) { + printk(KERN_ERR PFX "brg2 answer1 wrong\n"); + return -EBUSY; + } + if (ioread16(card->attr_io + COR_OFFSET + 2) & 1) { + printk(KERN_ERR PFX "brg2 answer2 wrong\n"); + return -EBUSY; + } + if (ioread16(card->attr_io + COR_OFFSET + 4) & 1) { + printk(KERN_ERR PFX "brg2 answer3 wrong\n"); + return -EBUSY; + } + + /* Set the PCMCIA COR register */ + iowrite16(COR_VALUE, card->attr_io + COR_OFFSET); + mdelay(1); + reg = ioread16(card->attr_io + COR_OFFSET); + if (reg != COR_VALUE) { + printk(KERN_ERR PFX "Error setting COR value (reg=%x)\n", + reg); + return -EBUSY; + } + + /* Set LEDs */ + iowrite16(1, card->bridge_io + 10); + return 0; +} + +static int orinoco_nortel_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int err; + struct orinoco_private *priv; + struct orinoco_pci_card *card; + void __iomem *hermes_io, *bridge_io, *attr_io; + + err = pci_enable_device(pdev); + if (err) { + printk(KERN_ERR PFX "Cannot enable PCI device\n"); + return err; + } + + err = pci_request_regions(pdev, DRIVER_NAME); + if (err) { + printk(KERN_ERR PFX "Cannot obtain PCI resources\n"); + goto fail_resources; + } + + bridge_io = pci_iomap(pdev, 0, 0); + if (!bridge_io) { + printk(KERN_ERR PFX "Cannot map bridge registers\n"); + err = -EIO; + goto fail_map_bridge; + } + + attr_io = pci_iomap(pdev, 1, 0); + if (!attr_io) { + printk(KERN_ERR PFX "Cannot map PCMCIA attributes\n"); + err = -EIO; + goto fail_map_attr; + } + + hermes_io = pci_iomap(pdev, 2, 0); + if (!hermes_io) { + printk(KERN_ERR PFX "Cannot map chipset registers\n"); + err = -EIO; + goto fail_map_hermes; + } + + /* Allocate network device */ + priv = alloc_orinocodev(sizeof(*card), &pdev->dev, + orinoco_nortel_cor_reset, NULL); + if (!priv) { + printk(KERN_ERR PFX "Cannot allocate network device\n"); + err = -ENOMEM; + goto fail_alloc; + } + + card = priv->card; + card->bridge_io = bridge_io; + card->attr_io = attr_io; + + hermes_struct_init(&priv->hw, hermes_io, HERMES_16BIT_REGSPACING); + + err = request_irq(pdev->irq, orinoco_interrupt, IRQF_SHARED, + DRIVER_NAME, priv); + if (err) { + printk(KERN_ERR PFX "Cannot allocate IRQ %d\n", pdev->irq); + err = -EBUSY; + goto fail_irq; + } + + err = orinoco_nortel_hw_init(card); + if (err) { + printk(KERN_ERR PFX "Hardware initialization failed\n"); + goto fail; + } + + err = orinoco_nortel_cor_reset(priv); + if (err) { + printk(KERN_ERR PFX "Initial reset failed\n"); + goto fail; + } + + err = orinoco_init(priv); + if (err) { + printk(KERN_ERR PFX "orinoco_init() failed\n"); + goto fail; + } + + err = orinoco_if_add(priv, 0, 0, NULL); + if (err) { + printk(KERN_ERR PFX "orinoco_if_add() failed\n"); + goto fail; + } + + pci_set_drvdata(pdev, priv); + + return 0; + + fail: + free_irq(pdev->irq, priv); + + fail_irq: + pci_set_drvdata(pdev, NULL); + free_orinocodev(priv); + + fail_alloc: + pci_iounmap(pdev, hermes_io); + + fail_map_hermes: + pci_iounmap(pdev, attr_io); + + fail_map_attr: + pci_iounmap(pdev, bridge_io); + + fail_map_bridge: + pci_release_regions(pdev); + + fail_resources: + pci_disable_device(pdev); + + return err; +} + +static void orinoco_nortel_remove_one(struct pci_dev *pdev) +{ + struct orinoco_private *priv = pci_get_drvdata(pdev); + struct orinoco_pci_card *card = priv->card; + + /* Clear LEDs */ + iowrite16(0, card->bridge_io + 10); + + orinoco_if_del(priv); + free_irq(pdev->irq, priv); + pci_set_drvdata(pdev, NULL); + free_orinocodev(priv); + pci_iounmap(pdev, priv->hw.iobase); + pci_iounmap(pdev, card->attr_io); + pci_iounmap(pdev, card->bridge_io); + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +static DEFINE_PCI_DEVICE_TABLE(orinoco_nortel_id_table) = { + /* Nortel emobility PCI */ + {0x126c, 0x8030, PCI_ANY_ID, PCI_ANY_ID,}, + /* Symbol LA-4123 PCI */ + {0x1562, 0x0001, PCI_ANY_ID, PCI_ANY_ID,}, + {0,}, +}; + +MODULE_DEVICE_TABLE(pci, orinoco_nortel_id_table); + +static struct pci_driver orinoco_nortel_driver = { + .name = DRIVER_NAME, + .id_table = orinoco_nortel_id_table, + .probe = orinoco_nortel_init_one, + .remove = orinoco_nortel_remove_one, + .suspend = orinoco_pci_suspend, + .resume = orinoco_pci_resume, +}; + +static char version[] __initdata = DRIVER_NAME " " DRIVER_VERSION + " (Tobias Hoffmann & Christoph Jungegger )"; +MODULE_AUTHOR("Christoph Jungegger "); +MODULE_DESCRIPTION("Driver for wireless LAN cards using the Nortel PCI bridge"); +MODULE_LICENSE("Dual MPL/GPL"); + +static int __init orinoco_nortel_init(void) +{ + printk(KERN_DEBUG "%s\n", version); + return pci_register_driver(&orinoco_nortel_driver); +} + +static void __exit orinoco_nortel_exit(void) +{ + pci_unregister_driver(&orinoco_nortel_driver); +} + +module_init(orinoco_nortel_init); +module_exit(orinoco_nortel_exit); + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * tab-width: 8 + * End: + */ diff --git a/drivers/net/wireless/orinoco/orinoco_pci.c b/drivers/net/wireless/orinoco/orinoco_pci.c new file mode 100644 index 0000000..677bf14 --- /dev/null +++ b/drivers/net/wireless/orinoco/orinoco_pci.c @@ -0,0 +1,265 @@ +/* orinoco_pci.c + * + * Driver for Prism 2.5/3 devices that have a direct PCI interface + * (i.e. these are not PCMCIA cards in a PCMCIA-to-PCI bridge). + * The card contains only one PCI region, which contains all the usual + * hermes registers, as well as the COR register. + * + * Current maintainers are: + * Pavel Roskin + * and David Gibson + * + * Some of this code is borrowed from orinoco_plx.c + * Copyright (C) 2001 Daniel Barlow + * Some of this code is "inspired" by linux-wlan-ng-0.1.10, but nothing + * has been copied from it. linux-wlan-ng-0.1.10 is originally : + * Copyright (C) 1999 AbsoluteValue Systems, Inc. All Rights Reserved. + * This file originally written by: + * Copyright (C) 2001 Jean Tourrilhes + * And is now maintained by: + * (C) Copyright David Gibson, IBM Corp. 2002-2003. + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#define DRIVER_NAME "orinoco_pci" +#define PFX DRIVER_NAME ": " + +#include +#include +#include +#include +#include + +#include "orinoco.h" +#include "orinoco_pci.h" + +/* Offset of the COR register of the PCI card */ +#define HERMES_PCI_COR (0x26) + +/* Bitmask to reset the card */ +#define HERMES_PCI_COR_MASK (0x0080) + +/* Magic timeouts for doing the reset. + * Those times are straight from wlan-ng, and it is claimed that they + * are necessary. Alan will kill me. Take your time and grab a coffee. */ +#define HERMES_PCI_COR_ONT (250) /* ms */ +#define HERMES_PCI_COR_OFFT (500) /* ms */ +#define HERMES_PCI_COR_BUSYT (500) /* ms */ + +/* + * Do a soft reset of the card using the Configuration Option Register + * We need this to get going... + * This is the part of the code that is strongly inspired from wlan-ng + * + * Note : This code is done with irq enabled. This mean that many + * interrupts will occur while we are there. This is why we use the + * jiffies to regulate time instead of a straight mdelay(). Usually we + * need only around 245 iteration of the loop to do 250 ms delay. + * + * Note bis : Don't try to access HERMES_CMD during the reset phase. + * It just won't work ! + */ +static int orinoco_pci_cor_reset(struct orinoco_private *priv) +{ + struct hermes *hw = &priv->hw; + unsigned long timeout; + u16 reg; + + /* Assert the reset until the card notices */ + hermes_write_regn(hw, PCI_COR, HERMES_PCI_COR_MASK); + mdelay(HERMES_PCI_COR_ONT); + + /* Give time for the card to recover from this hard effort */ + hermes_write_regn(hw, PCI_COR, 0x0000); + mdelay(HERMES_PCI_COR_OFFT); + + /* The card is ready when it's no longer busy */ + timeout = jiffies + (HERMES_PCI_COR_BUSYT * HZ / 1000); + reg = hermes_read_regn(hw, CMD); + while (time_before(jiffies, timeout) && (reg & HERMES_CMD_BUSY)) { + mdelay(1); + reg = hermes_read_regn(hw, CMD); + } + + /* Still busy? */ + if (reg & HERMES_CMD_BUSY) { + printk(KERN_ERR PFX "Busy timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int orinoco_pci_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int err; + struct orinoco_private *priv; + struct orinoco_pci_card *card; + void __iomem *hermes_io; + + err = pci_enable_device(pdev); + if (err) { + printk(KERN_ERR PFX "Cannot enable PCI device\n"); + return err; + } + + err = pci_request_regions(pdev, DRIVER_NAME); + if (err) { + printk(KERN_ERR PFX "Cannot obtain PCI resources\n"); + goto fail_resources; + } + + hermes_io = pci_iomap(pdev, 0, 0); + if (!hermes_io) { + printk(KERN_ERR PFX "Cannot remap chipset registers\n"); + err = -EIO; + goto fail_map_hermes; + } + + /* Allocate network device */ + priv = alloc_orinocodev(sizeof(*card), &pdev->dev, + orinoco_pci_cor_reset, NULL); + if (!priv) { + printk(KERN_ERR PFX "Cannot allocate network device\n"); + err = -ENOMEM; + goto fail_alloc; + } + + card = priv->card; + + hermes_struct_init(&priv->hw, hermes_io, HERMES_32BIT_REGSPACING); + + err = request_irq(pdev->irq, orinoco_interrupt, IRQF_SHARED, + DRIVER_NAME, priv); + if (err) { + printk(KERN_ERR PFX "Cannot allocate IRQ %d\n", pdev->irq); + err = -EBUSY; + goto fail_irq; + } + + err = orinoco_pci_cor_reset(priv); + if (err) { + printk(KERN_ERR PFX "Initial reset failed\n"); + goto fail; + } + + err = orinoco_init(priv); + if (err) { + printk(KERN_ERR PFX "orinoco_init() failed\n"); + goto fail; + } + + err = orinoco_if_add(priv, 0, 0, NULL); + if (err) { + printk(KERN_ERR PFX "orinoco_if_add() failed\n"); + goto fail; + } + + pci_set_drvdata(pdev, priv); + + return 0; + + fail: + free_irq(pdev->irq, priv); + + fail_irq: + pci_set_drvdata(pdev, NULL); + free_orinocodev(priv); + + fail_alloc: + pci_iounmap(pdev, hermes_io); + + fail_map_hermes: + pci_release_regions(pdev); + + fail_resources: + pci_disable_device(pdev); + + return err; +} + +static void orinoco_pci_remove_one(struct pci_dev *pdev) +{ + struct orinoco_private *priv = pci_get_drvdata(pdev); + + orinoco_if_del(priv); + free_irq(pdev->irq, priv); + pci_set_drvdata(pdev, NULL); + free_orinocodev(priv); + pci_iounmap(pdev, priv->hw.iobase); + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +static DEFINE_PCI_DEVICE_TABLE(orinoco_pci_id_table) = { + /* Intersil Prism 3 */ + {0x1260, 0x3872, PCI_ANY_ID, PCI_ANY_ID,}, + /* Intersil Prism 2.5 */ + {0x1260, 0x3873, PCI_ANY_ID, PCI_ANY_ID,}, + /* Samsung MagicLAN SWL-2210P */ + {0x167d, 0xa000, PCI_ANY_ID, PCI_ANY_ID,}, + {0,}, +}; + +MODULE_DEVICE_TABLE(pci, orinoco_pci_id_table); + +static struct pci_driver orinoco_pci_driver = { + .name = DRIVER_NAME, + .id_table = orinoco_pci_id_table, + .probe = orinoco_pci_init_one, + .remove = orinoco_pci_remove_one, + .suspend = orinoco_pci_suspend, + .resume = orinoco_pci_resume, +}; + +static char version[] __initdata = DRIVER_NAME " " DRIVER_VERSION + " (Pavel Roskin ," + " David Gibson &" + " Jean Tourrilhes )"; +MODULE_AUTHOR("Pavel Roskin &" + " David Gibson "); +MODULE_DESCRIPTION("Driver for wireless LAN cards using direct PCI interface"); +MODULE_LICENSE("Dual MPL/GPL"); + +static int __init orinoco_pci_init(void) +{ + printk(KERN_DEBUG "%s\n", version); + return pci_register_driver(&orinoco_pci_driver); +} + +static void __exit orinoco_pci_exit(void) +{ + pci_unregister_driver(&orinoco_pci_driver); +} + +module_init(orinoco_pci_init); +module_exit(orinoco_pci_exit); + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * tab-width: 8 + * End: + */ diff --git a/drivers/net/wireless/orinoco/orinoco_pci.h b/drivers/net/wireless/orinoco/orinoco_pci.h new file mode 100644 index 0000000..ea7231a --- /dev/null +++ b/drivers/net/wireless/orinoco/orinoco_pci.h @@ -0,0 +1,68 @@ +/* orinoco_pci.h + * + * Common code for all Orinoco drivers for PCI devices, including + * both native PCI and PCMCIA-to-PCI bridges. + * + * Copyright (C) 2005, Pavel Roskin. + * See main.c for license. + */ + +#ifndef _ORINOCO_PCI_H +#define _ORINOCO_PCI_H + +#include + +/* Driver specific data */ +struct orinoco_pci_card { + void __iomem *bridge_io; + void __iomem *attr_io; +}; + +#ifdef CONFIG_PM +static int orinoco_pci_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct orinoco_private *priv = pci_get_drvdata(pdev); + + orinoco_down(priv); + free_irq(pdev->irq, priv); + pci_save_state(pdev); + pci_disable_device(pdev); + pci_set_power_state(pdev, PCI_D3hot); + + return 0; +} + +static int orinoco_pci_resume(struct pci_dev *pdev) +{ + struct orinoco_private *priv = pci_get_drvdata(pdev); + struct net_device *dev = priv->ndev; + int err; + + pci_set_power_state(pdev, 0); + err = pci_enable_device(pdev); + if (err) { + printk(KERN_ERR "%s: pci_enable_device failed on resume\n", + dev->name); + return err; + } + pci_restore_state(pdev); + + err = request_irq(pdev->irq, orinoco_interrupt, IRQF_SHARED, + dev->name, priv); + if (err) { + printk(KERN_ERR "%s: cannot re-allocate IRQ on resume\n", + dev->name); + pci_disable_device(pdev); + return -EBUSY; + } + + err = orinoco_up(priv); + + return err; +} +#else +#define orinoco_pci_suspend NULL +#define orinoco_pci_resume NULL +#endif + +#endif /* _ORINOCO_PCI_H */ diff --git a/drivers/net/wireless/orinoco/orinoco_plx.c b/drivers/net/wireless/orinoco/orinoco_plx.c new file mode 100644 index 0000000..2559dbd --- /dev/null +++ b/drivers/net/wireless/orinoco/orinoco_plx.c @@ -0,0 +1,370 @@ +/* orinoco_plx.c + * + * Driver for Prism II devices which would usually be driven by orinoco_cs, + * but are connected to the PCI bus by a PLX9052. + * + * Current maintainers are: + * Pavel Roskin + * and David Gibson + * + * (C) Copyright David Gibson, IBM Corp. 2001-2003. + * Copyright (C) 2001 Daniel Barlow + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + * + * Here's the general details on how the PLX9052 adapter works: + * + * - Two PCI I/O address spaces, one 0x80 long which contains the + * PLX9052 registers, and one that's 0x40 long mapped to the PCMCIA + * slot I/O address space. + * + * - One PCI memory address space, mapped to the PCMCIA attribute space + * (containing the CIS). + * + * Using the later, you can read through the CIS data to make sure the + * card is compatible with the driver. Keep in mind that the PCMCIA + * spec specifies the CIS as the lower 8 bits of each word read from + * the CIS, so to read the bytes of the CIS, read every other byte + * (0,2,4,...). Passing that test, you need to enable the I/O address + * space on the PCMCIA card via the PCMCIA COR register. This is the + * first byte following the CIS. In my case (which may not have any + * relation to what's on the PRISM2 cards), COR was at offset 0x800 + * within the PCI memory space. Write 0x41 to the COR register to + * enable I/O mode and to select level triggered interrupts. To + * confirm you actually succeeded, read the COR register back and make + * sure it actually got set to 0x41, in case you have an unexpected + * card inserted. + * + * Following that, you can treat the second PCI I/O address space (the + * one that's not 0x80 in length) as the PCMCIA I/O space. + * + * Note that in the Eumitcom's source for their drivers, they register + * the interrupt as edge triggered when registering it with the + * Windows kernel. I don't recall how to register edge triggered on + * Linux (if it can be done at all). But in some experimentation, I + * don't see much operational difference between using either + * interrupt mode. Don't mess with the interrupt mode in the COR + * register though, as the PLX9052 wants level triggers with the way + * the serial EEPROM configures it on the WL11000. + * + * There's some other little quirks related to timing that I bumped + * into, but I don't recall right now. Also, there's two variants of + * the WL11000 I've seen, revision A1 and T2. These seem to differ + * slightly in the timings configured in the wait-state generator in + * the PLX9052. There have also been some comments from Eumitcom that + * cards shouldn't be hot swapped, apparently due to risk of cooking + * the PLX9052. I'm unsure why they believe this, as I can't see + * anything in the design that would really cause a problem, except + * for crashing drivers not written to expect it. And having developed + * drivers for the WL11000, I'd say it's quite tricky to write code + * that will successfully deal with a hot unplug. Very odd things + * happen on the I/O side of things. But anyway, be warned. Despite + * that, I've hot-swapped a number of times during debugging and + * driver development for various reasons (stuck WAIT# line after the + * radio card's firmware locks up). + */ + +#define DRIVER_NAME "orinoco_plx" +#define PFX DRIVER_NAME ": " + +#include +#include +#include +#include +#include +#include + +#include "orinoco.h" +#include "orinoco_pci.h" + +#define COR_OFFSET (0x3e0) /* COR attribute offset of Prism2 PC card */ +#define COR_VALUE (COR_LEVEL_REQ | COR_FUNC_ENA) /* Enable PC card with interrupt in level trigger */ +#define COR_RESET (0x80) /* reset bit in the COR register */ +#define PLX_RESET_TIME (500) /* milliseconds */ + +#define PLX_INTCSR 0x4c /* Interrupt Control & Status Register */ +#define PLX_INTCSR_INTEN (1 << 6) /* Interrupt Enable bit */ + +/* + * Do a soft reset of the card using the Configuration Option Register + */ +static int orinoco_plx_cor_reset(struct orinoco_private *priv) +{ + struct hermes *hw = &priv->hw; + struct orinoco_pci_card *card = priv->card; + unsigned long timeout; + u16 reg; + + iowrite8(COR_VALUE | COR_RESET, card->attr_io + COR_OFFSET); + mdelay(1); + + iowrite8(COR_VALUE, card->attr_io + COR_OFFSET); + mdelay(1); + + /* Just in case, wait more until the card is no longer busy */ + timeout = jiffies + (PLX_RESET_TIME * HZ / 1000); + reg = hermes_read_regn(hw, CMD); + while (time_before(jiffies, timeout) && (reg & HERMES_CMD_BUSY)) { + mdelay(1); + reg = hermes_read_regn(hw, CMD); + } + + /* Still busy? */ + if (reg & HERMES_CMD_BUSY) { + printk(KERN_ERR PFX "Busy timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int orinoco_plx_hw_init(struct orinoco_pci_card *card) +{ + int i; + u32 csr_reg; + static const u8 cis_magic[] = { + 0x01, 0x03, 0x00, 0x00, 0xff, 0x17, 0x04, 0x67 + }; + + printk(KERN_DEBUG PFX "CIS: "); + for (i = 0; i < 16; i++) + printk("%02X:", ioread8(card->attr_io + (i << 1))); + printk("\n"); + + /* Verify whether a supported PC card is present */ + /* FIXME: we probably need to be smarted about this */ + for (i = 0; i < sizeof(cis_magic); i++) { + if (cis_magic[i] != ioread8(card->attr_io + (i << 1))) { + printk(KERN_ERR PFX "The CIS value of Prism2 PC " + "card is unexpected\n"); + return -ENODEV; + } + } + + /* bjoern: We need to tell the card to enable interrupts, in + case the serial eprom didn't do this already. See the + PLX9052 data book, p8-1 and 8-24 for reference. */ + csr_reg = ioread32(card->bridge_io + PLX_INTCSR); + if (!(csr_reg & PLX_INTCSR_INTEN)) { + csr_reg |= PLX_INTCSR_INTEN; + iowrite32(csr_reg, card->bridge_io + PLX_INTCSR); + csr_reg = ioread32(card->bridge_io + PLX_INTCSR); + if (!(csr_reg & PLX_INTCSR_INTEN)) { + printk(KERN_ERR PFX "Cannot enable interrupts\n"); + return -EIO; + } + } + + return 0; +} + +static int orinoco_plx_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int err; + struct orinoco_private *priv; + struct orinoco_pci_card *card; + void __iomem *hermes_io, *attr_io, *bridge_io; + + err = pci_enable_device(pdev); + if (err) { + printk(KERN_ERR PFX "Cannot enable PCI device\n"); + return err; + } + + err = pci_request_regions(pdev, DRIVER_NAME); + if (err) { + printk(KERN_ERR PFX "Cannot obtain PCI resources\n"); + goto fail_resources; + } + + bridge_io = pci_iomap(pdev, 1, 0); + if (!bridge_io) { + printk(KERN_ERR PFX "Cannot map bridge registers\n"); + err = -EIO; + goto fail_map_bridge; + } + + attr_io = pci_iomap(pdev, 2, 0); + if (!attr_io) { + printk(KERN_ERR PFX "Cannot map PCMCIA attributes\n"); + err = -EIO; + goto fail_map_attr; + } + + hermes_io = pci_iomap(pdev, 3, 0); + if (!hermes_io) { + printk(KERN_ERR PFX "Cannot map chipset registers\n"); + err = -EIO; + goto fail_map_hermes; + } + + /* Allocate network device */ + priv = alloc_orinocodev(sizeof(*card), &pdev->dev, + orinoco_plx_cor_reset, NULL); + if (!priv) { + printk(KERN_ERR PFX "Cannot allocate network device\n"); + err = -ENOMEM; + goto fail_alloc; + } + + card = priv->card; + card->bridge_io = bridge_io; + card->attr_io = attr_io; + + hermes_struct_init(&priv->hw, hermes_io, HERMES_16BIT_REGSPACING); + + err = request_irq(pdev->irq, orinoco_interrupt, IRQF_SHARED, + DRIVER_NAME, priv); + if (err) { + printk(KERN_ERR PFX "Cannot allocate IRQ %d\n", pdev->irq); + err = -EBUSY; + goto fail_irq; + } + + err = orinoco_plx_hw_init(card); + if (err) { + printk(KERN_ERR PFX "Hardware initialization failed\n"); + goto fail; + } + + err = orinoco_plx_cor_reset(priv); + if (err) { + printk(KERN_ERR PFX "Initial reset failed\n"); + goto fail; + } + + err = orinoco_init(priv); + if (err) { + printk(KERN_ERR PFX "orinoco_init() failed\n"); + goto fail; + } + + err = orinoco_if_add(priv, 0, 0, NULL); + if (err) { + printk(KERN_ERR PFX "orinoco_if_add() failed\n"); + goto fail; + } + + pci_set_drvdata(pdev, priv); + + return 0; + + fail: + free_irq(pdev->irq, priv); + + fail_irq: + pci_set_drvdata(pdev, NULL); + free_orinocodev(priv); + + fail_alloc: + pci_iounmap(pdev, hermes_io); + + fail_map_hermes: + pci_iounmap(pdev, attr_io); + + fail_map_attr: + pci_iounmap(pdev, bridge_io); + + fail_map_bridge: + pci_release_regions(pdev); + + fail_resources: + pci_disable_device(pdev); + + return err; +} + +static void orinoco_plx_remove_one(struct pci_dev *pdev) +{ + struct orinoco_private *priv = pci_get_drvdata(pdev); + struct orinoco_pci_card *card = priv->card; + + orinoco_if_del(priv); + free_irq(pdev->irq, priv); + pci_set_drvdata(pdev, NULL); + free_orinocodev(priv); + pci_iounmap(pdev, priv->hw.iobase); + pci_iounmap(pdev, card->attr_io); + pci_iounmap(pdev, card->bridge_io); + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +static DEFINE_PCI_DEVICE_TABLE(orinoco_plx_id_table) = { + {0x111a, 0x1023, PCI_ANY_ID, PCI_ANY_ID,}, /* Siemens SpeedStream SS1023 */ + {0x1385, 0x4100, PCI_ANY_ID, PCI_ANY_ID,}, /* Netgear MA301 */ + {0x15e8, 0x0130, PCI_ANY_ID, PCI_ANY_ID,}, /* Correga - does this work? */ + {0x1638, 0x1100, PCI_ANY_ID, PCI_ANY_ID,}, /* SMC EZConnect SMC2602W, + Eumitcom PCI WL11000, + Addtron AWA-100 */ + {0x16ab, 0x1100, PCI_ANY_ID, PCI_ANY_ID,}, /* Global Sun Tech GL24110P */ + {0x16ab, 0x1101, PCI_ANY_ID, PCI_ANY_ID,}, /* Reported working, but unknown */ + {0x16ab, 0x1102, PCI_ANY_ID, PCI_ANY_ID,}, /* Linksys WDT11 */ + {0x16ec, 0x3685, PCI_ANY_ID, PCI_ANY_ID,}, /* USR 2415 */ + {0xec80, 0xec00, PCI_ANY_ID, PCI_ANY_ID,}, /* Belkin F5D6000 tested by + Brendan W. McAdams */ + {0x10b7, 0x7770, PCI_ANY_ID, PCI_ANY_ID,}, /* 3Com AirConnect PCI tested by + Damien Persohn */ + {0,}, +}; + +MODULE_DEVICE_TABLE(pci, orinoco_plx_id_table); + +static struct pci_driver orinoco_plx_driver = { + .name = DRIVER_NAME, + .id_table = orinoco_plx_id_table, + .probe = orinoco_plx_init_one, + .remove = orinoco_plx_remove_one, + .suspend = orinoco_pci_suspend, + .resume = orinoco_pci_resume, +}; + +static char version[] __initdata = DRIVER_NAME " " DRIVER_VERSION + " (Pavel Roskin ," + " David Gibson ," + " Daniel Barlow )"; +MODULE_AUTHOR("Daniel Barlow "); +MODULE_DESCRIPTION("Driver for wireless LAN cards using the PLX9052 PCI bridge"); +MODULE_LICENSE("Dual MPL/GPL"); + +static int __init orinoco_plx_init(void) +{ + printk(KERN_DEBUG "%s\n", version); + return pci_register_driver(&orinoco_plx_driver); +} + +static void __exit orinoco_plx_exit(void) +{ + pci_unregister_driver(&orinoco_plx_driver); +} + +module_init(orinoco_plx_init); +module_exit(orinoco_plx_exit); + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * tab-width: 8 + * End: + */ diff --git a/drivers/net/wireless/orinoco/orinoco_tmd.c b/drivers/net/wireless/orinoco/orinoco_tmd.c new file mode 100644 index 0000000..42afeee --- /dev/null +++ b/drivers/net/wireless/orinoco/orinoco_tmd.c @@ -0,0 +1,248 @@ +/* orinoco_tmd.c + * + * Driver for Prism II devices which would usually be driven by orinoco_cs, + * but are connected to the PCI bus by a TMD7160. + * + * Copyright (C) 2003 Joerg Dorchain + * based heavily upon orinoco_plx.c Copyright (C) 2001 Daniel Barlow + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + * + * The actual driving is done by main.c, this is just resource + * allocation stuff. + * + * This driver is modeled after the orinoco_plx driver. The main + * difference is that the TMD chip has only IO port ranges and doesn't + * provide access to the PCMCIA attribute space. + * + * Pheecom sells cards with the TMD chip as "ASIC version" + */ + +#define DRIVER_NAME "orinoco_tmd" +#define PFX DRIVER_NAME ": " + +#include +#include +#include +#include +#include +#include + +#include "orinoco.h" +#include "orinoco_pci.h" + +#define COR_VALUE (COR_LEVEL_REQ | COR_FUNC_ENA) /* Enable PC card with interrupt in level trigger */ +#define COR_RESET (0x80) /* reset bit in the COR register */ +#define TMD_RESET_TIME (500) /* milliseconds */ + +/* + * Do a soft reset of the card using the Configuration Option Register + */ +static int orinoco_tmd_cor_reset(struct orinoco_private *priv) +{ + struct hermes *hw = &priv->hw; + struct orinoco_pci_card *card = priv->card; + unsigned long timeout; + u16 reg; + + iowrite8(COR_VALUE | COR_RESET, card->bridge_io); + mdelay(1); + + iowrite8(COR_VALUE, card->bridge_io); + mdelay(1); + + /* Just in case, wait more until the card is no longer busy */ + timeout = jiffies + (TMD_RESET_TIME * HZ / 1000); + reg = hermes_read_regn(hw, CMD); + while (time_before(jiffies, timeout) && (reg & HERMES_CMD_BUSY)) { + mdelay(1); + reg = hermes_read_regn(hw, CMD); + } + + /* Still busy? */ + if (reg & HERMES_CMD_BUSY) { + printk(KERN_ERR PFX "Busy timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + + +static int orinoco_tmd_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int err; + struct orinoco_private *priv; + struct orinoco_pci_card *card; + void __iomem *hermes_io, *bridge_io; + + err = pci_enable_device(pdev); + if (err) { + printk(KERN_ERR PFX "Cannot enable PCI device\n"); + return err; + } + + err = pci_request_regions(pdev, DRIVER_NAME); + if (err) { + printk(KERN_ERR PFX "Cannot obtain PCI resources\n"); + goto fail_resources; + } + + bridge_io = pci_iomap(pdev, 1, 0); + if (!bridge_io) { + printk(KERN_ERR PFX "Cannot map bridge registers\n"); + err = -EIO; + goto fail_map_bridge; + } + + hermes_io = pci_iomap(pdev, 2, 0); + if (!hermes_io) { + printk(KERN_ERR PFX "Cannot map chipset registers\n"); + err = -EIO; + goto fail_map_hermes; + } + + /* Allocate network device */ + priv = alloc_orinocodev(sizeof(*card), &pdev->dev, + orinoco_tmd_cor_reset, NULL); + if (!priv) { + printk(KERN_ERR PFX "Cannot allocate network device\n"); + err = -ENOMEM; + goto fail_alloc; + } + + card = priv->card; + card->bridge_io = bridge_io; + + hermes_struct_init(&priv->hw, hermes_io, HERMES_16BIT_REGSPACING); + + err = request_irq(pdev->irq, orinoco_interrupt, IRQF_SHARED, + DRIVER_NAME, priv); + if (err) { + printk(KERN_ERR PFX "Cannot allocate IRQ %d\n", pdev->irq); + err = -EBUSY; + goto fail_irq; + } + + err = orinoco_tmd_cor_reset(priv); + if (err) { + printk(KERN_ERR PFX "Initial reset failed\n"); + goto fail; + } + + err = orinoco_init(priv); + if (err) { + printk(KERN_ERR PFX "orinoco_init() failed\n"); + goto fail; + } + + err = orinoco_if_add(priv, 0, 0, NULL); + if (err) { + printk(KERN_ERR PFX "orinoco_if_add() failed\n"); + goto fail; + } + + pci_set_drvdata(pdev, priv); + + return 0; + + fail: + free_irq(pdev->irq, priv); + + fail_irq: + pci_set_drvdata(pdev, NULL); + free_orinocodev(priv); + + fail_alloc: + pci_iounmap(pdev, hermes_io); + + fail_map_hermes: + pci_iounmap(pdev, bridge_io); + + fail_map_bridge: + pci_release_regions(pdev); + + fail_resources: + pci_disable_device(pdev); + + return err; +} + +static void orinoco_tmd_remove_one(struct pci_dev *pdev) +{ + struct orinoco_private *priv = pci_get_drvdata(pdev); + struct orinoco_pci_card *card = priv->card; + + orinoco_if_del(priv); + free_irq(pdev->irq, priv); + pci_set_drvdata(pdev, NULL); + free_orinocodev(priv); + pci_iounmap(pdev, priv->hw.iobase); + pci_iounmap(pdev, card->bridge_io); + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +static DEFINE_PCI_DEVICE_TABLE(orinoco_tmd_id_table) = { + {0x15e8, 0x0131, PCI_ANY_ID, PCI_ANY_ID,}, /* NDC and OEMs, e.g. pheecom */ + {0,}, +}; + +MODULE_DEVICE_TABLE(pci, orinoco_tmd_id_table); + +static struct pci_driver orinoco_tmd_driver = { + .name = DRIVER_NAME, + .id_table = orinoco_tmd_id_table, + .probe = orinoco_tmd_init_one, + .remove = orinoco_tmd_remove_one, + .suspend = orinoco_pci_suspend, + .resume = orinoco_pci_resume, +}; + +static char version[] __initdata = DRIVER_NAME " " DRIVER_VERSION + " (Joerg Dorchain )"; +MODULE_AUTHOR("Joerg Dorchain "); +MODULE_DESCRIPTION("Driver for wireless LAN cards using the TMD7160 PCI bridge"); +MODULE_LICENSE("Dual MPL/GPL"); + +static int __init orinoco_tmd_init(void) +{ + printk(KERN_DEBUG "%s\n", version); + return pci_register_driver(&orinoco_tmd_driver); +} + +static void __exit orinoco_tmd_exit(void) +{ + pci_unregister_driver(&orinoco_tmd_driver); +} + +module_init(orinoco_tmd_init); +module_exit(orinoco_tmd_exit); + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * tab-width: 8 + * End: + */ diff --git a/drivers/net/wireless/orinoco/orinoco_usb.c b/drivers/net/wireless/orinoco/orinoco_usb.c new file mode 100644 index 0000000..0f0e199 --- /dev/null +++ b/drivers/net/wireless/orinoco/orinoco_usb.c @@ -0,0 +1,1770 @@ +/* + * USB Orinoco driver + * + * Copyright (c) 2003 Manuel Estrada Sainz + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + * + * Queueing code based on linux-wlan-ng 0.2.1-pre5 + * + * Copyright (C) 1999 AbsoluteValue Systems, Inc. All Rights Reserved. + * + * The license is the same as above. + * + * Initialy based on USB Skeleton driver - 0.7 + * + * Copyright (c) 2001 Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * NOTE: The original USB Skeleton driver is GPL, but all that code is + * gone so MPL/GPL applies. + */ + +#define DRIVER_NAME "orinoco_usb" +#define PFX DRIVER_NAME ": " + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mic.h" +#include "orinoco.h" + +#ifndef URB_ASYNC_UNLINK +#define URB_ASYNC_UNLINK 0 +#endif + +/* 802.2 LLC/SNAP header used for Ethernet encapsulation over 802.11 */ +static const u8 encaps_hdr[] = {0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00}; +#define ENCAPS_OVERHEAD (sizeof(encaps_hdr) + 2) + +struct header_struct { + /* 802.3 */ + u8 dest[ETH_ALEN]; + u8 src[ETH_ALEN]; + __be16 len; + /* 802.2 */ + u8 dsap; + u8 ssap; + u8 ctrl; + /* SNAP */ + u8 oui[3]; + __be16 ethertype; +} __packed; + +struct ez_usb_fw { + u16 size; + const u8 *code; +}; + +static struct ez_usb_fw firmware = { + .size = 0, + .code = NULL, +}; + +#ifdef CONFIG_USB_DEBUG +static int debug = 1; +#else +static int debug; +#endif + +/* Debugging macros */ +#undef dbg +#define dbg(format, arg...) \ + do { if (debug) printk(KERN_DEBUG PFX "%s: " format "\n", \ + __func__ , ## arg); } while (0) +#undef err +#define err(format, arg...) \ + do { printk(KERN_ERR PFX format "\n", ## arg); } while (0) + +/* Module paramaters */ +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug enabled or not"); + +MODULE_FIRMWARE("orinoco_ezusb_fw"); + +/* + * Under some conditions, the card gets stuck and stops paying attention + * to the world (i.e. data communication stalls) until we do something to + * it. Sending an INQ_TALLIES command seems to be enough and should be + * harmless otherwise. This behaviour has been observed when using the + * driver on a systemimager client during installation. In the past a + * timer was used to send INQ_TALLIES commands when there was no other + * activity, but it was troublesome and was removed. + */ + +#define USB_COMPAQ_VENDOR_ID 0x049f /* Compaq Computer Corp. */ +#define USB_COMPAQ_WL215_ID 0x001f /* Compaq WL215 USB Adapter */ +#define USB_COMPAQ_W200_ID 0x0076 /* Compaq W200 USB Adapter */ +#define USB_HP_WL215_ID 0x0082 /* Compaq WL215 USB Adapter */ + +#define USB_MELCO_VENDOR_ID 0x0411 +#define USB_BUFFALO_L11_ID 0x0006 /* BUFFALO WLI-USB-L11 */ +#define USB_BUFFALO_L11G_WR_ID 0x000B /* BUFFALO WLI-USB-L11G-WR */ +#define USB_BUFFALO_L11G_ID 0x000D /* BUFFALO WLI-USB-L11G */ + +#define USB_LUCENT_VENDOR_ID 0x047E /* Lucent Technologies */ +#define USB_LUCENT_ORINOCO_ID 0x0300 /* Lucent/Agere Orinoco USB Client */ + +#define USB_AVAYA8_VENDOR_ID 0x0D98 +#define USB_AVAYAE_VENDOR_ID 0x0D9E +#define USB_AVAYA_WIRELESS_ID 0x0300 /* Avaya Wireless USB Card */ + +#define USB_AGERE_VENDOR_ID 0x0D4E /* Agere Systems */ +#define USB_AGERE_MODEL0801_ID 0x1000 /* Wireless USB Card Model 0801 */ +#define USB_AGERE_MODEL0802_ID 0x1001 /* Wireless USB Card Model 0802 */ +#define USB_AGERE_REBRANDED_ID 0x047A /* WLAN USB Card */ + +#define USB_ELSA_VENDOR_ID 0x05CC +#define USB_ELSA_AIRLANCER_ID 0x3100 /* ELSA AirLancer USB-11 */ + +#define USB_LEGEND_VENDOR_ID 0x0E7C +#define USB_LEGEND_JOYNET_ID 0x0300 /* Joynet WLAN USB Card */ + +#define USB_SAMSUNG_VENDOR_ID 0x04E8 +#define USB_SAMSUNG_SEW2001U1_ID 0x5002 /* Samsung SEW-2001u Card */ +#define USB_SAMSUNG_SEW2001U2_ID 0x5B11 /* Samsung SEW-2001u Card */ +#define USB_SAMSUNG_SEW2003U_ID 0x7011 /* Samsung SEW-2003U Card */ + +#define USB_IGATE_VENDOR_ID 0x0681 +#define USB_IGATE_IGATE_11M_ID 0x0012 /* I-GATE 11M USB Card */ + +#define USB_FUJITSU_VENDOR_ID 0x0BF8 +#define USB_FUJITSU_E1100_ID 0x1002 /* connect2AIR WLAN E-1100 USB */ + +#define USB_2WIRE_VENDOR_ID 0x1630 +#define USB_2WIRE_WIRELESS_ID 0xff81 /* 2Wire Wireless USB adapter */ + + +#define EZUSB_REQUEST_FW_TRANS 0xA0 +#define EZUSB_REQUEST_TRIGER 0xAA +#define EZUSB_REQUEST_TRIG_AC 0xAC +#define EZUSB_CPUCS_REG 0x7F92 + +#define EZUSB_RID_TX 0x0700 +#define EZUSB_RID_RX 0x0701 +#define EZUSB_RID_INIT1 0x0702 +#define EZUSB_RID_ACK 0x0710 +#define EZUSB_RID_READ_PDA 0x0800 +#define EZUSB_RID_PROG_INIT 0x0852 +#define EZUSB_RID_PROG_SET_ADDR 0x0853 +#define EZUSB_RID_PROG_BYTES 0x0854 +#define EZUSB_RID_PROG_END 0x0855 +#define EZUSB_RID_DOCMD 0x0860 + +/* Recognize info frames */ +#define EZUSB_IS_INFO(id) ((id >= 0xF000) && (id <= 0xF2FF)) + +#define EZUSB_MAGIC 0x0210 + +#define EZUSB_FRAME_DATA 1 +#define EZUSB_FRAME_CONTROL 2 + +#define DEF_TIMEOUT (3 * HZ) + +#define BULK_BUF_SIZE 2048 + +#define MAX_DL_SIZE (BULK_BUF_SIZE - sizeof(struct ezusb_packet)) + +#define FW_BUF_SIZE 64 +#define FW_VAR_OFFSET_PTR 0x359 +#define FW_VAR_VALUE 0 +#define FW_HOLE_START 0x100 +#define FW_HOLE_END 0x300 + +struct ezusb_packet { + __le16 magic; /* 0x0210 */ + u8 req_reply_count; + u8 ans_reply_count; + __le16 frame_type; /* 0x01 for data frames, 0x02 otherwise */ + __le16 size; /* transport size */ + __le16 crc; /* CRC up to here */ + __le16 hermes_len; + __le16 hermes_rid; + u8 data[0]; +} __packed; + +/* Table of devices that work or may work with this driver */ +static struct usb_device_id ezusb_table[] = { + {USB_DEVICE(USB_COMPAQ_VENDOR_ID, USB_COMPAQ_WL215_ID)}, + {USB_DEVICE(USB_COMPAQ_VENDOR_ID, USB_HP_WL215_ID)}, + {USB_DEVICE(USB_COMPAQ_VENDOR_ID, USB_COMPAQ_W200_ID)}, + {USB_DEVICE(USB_MELCO_VENDOR_ID, USB_BUFFALO_L11_ID)}, + {USB_DEVICE(USB_MELCO_VENDOR_ID, USB_BUFFALO_L11G_WR_ID)}, + {USB_DEVICE(USB_MELCO_VENDOR_ID, USB_BUFFALO_L11G_ID)}, + {USB_DEVICE(USB_LUCENT_VENDOR_ID, USB_LUCENT_ORINOCO_ID)}, + {USB_DEVICE(USB_AVAYA8_VENDOR_ID, USB_AVAYA_WIRELESS_ID)}, + {USB_DEVICE(USB_AVAYAE_VENDOR_ID, USB_AVAYA_WIRELESS_ID)}, + {USB_DEVICE(USB_AGERE_VENDOR_ID, USB_AGERE_MODEL0801_ID)}, + {USB_DEVICE(USB_AGERE_VENDOR_ID, USB_AGERE_MODEL0802_ID)}, + {USB_DEVICE(USB_ELSA_VENDOR_ID, USB_ELSA_AIRLANCER_ID)}, + {USB_DEVICE(USB_LEGEND_VENDOR_ID, USB_LEGEND_JOYNET_ID)}, + {USB_DEVICE_VER(USB_SAMSUNG_VENDOR_ID, USB_SAMSUNG_SEW2001U1_ID, + 0, 0)}, + {USB_DEVICE(USB_SAMSUNG_VENDOR_ID, USB_SAMSUNG_SEW2001U2_ID)}, + {USB_DEVICE(USB_SAMSUNG_VENDOR_ID, USB_SAMSUNG_SEW2003U_ID)}, + {USB_DEVICE(USB_IGATE_VENDOR_ID, USB_IGATE_IGATE_11M_ID)}, + {USB_DEVICE(USB_FUJITSU_VENDOR_ID, USB_FUJITSU_E1100_ID)}, + {USB_DEVICE(USB_2WIRE_VENDOR_ID, USB_2WIRE_WIRELESS_ID)}, + {USB_DEVICE(USB_AGERE_VENDOR_ID, USB_AGERE_REBRANDED_ID)}, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, ezusb_table); + +/* Structure to hold all of our device specific stuff */ +struct ezusb_priv { + struct usb_device *udev; + struct net_device *dev; + struct mutex mtx; + spinlock_t req_lock; + struct list_head req_pending; + struct list_head req_active; + spinlock_t reply_count_lock; + u16 hermes_reg_fake[0x40]; + u8 *bap_buf; + struct urb *read_urb; + int read_pipe; + int write_pipe; + u8 reply_count; +}; + +enum ezusb_state { + EZUSB_CTX_START, + EZUSB_CTX_QUEUED, + EZUSB_CTX_REQ_SUBMITTED, + EZUSB_CTX_REQ_COMPLETE, + EZUSB_CTX_RESP_RECEIVED, + EZUSB_CTX_REQ_TIMEOUT, + EZUSB_CTX_REQ_FAILED, + EZUSB_CTX_RESP_TIMEOUT, + EZUSB_CTX_REQSUBMIT_FAIL, + EZUSB_CTX_COMPLETE, +}; + +struct request_context { + struct list_head list; + atomic_t refcount; + struct completion done; /* Signals that CTX is dead */ + int killed; + struct urb *outurb; /* OUT for req pkt */ + struct ezusb_priv *upriv; + struct ezusb_packet *buf; + int buf_length; + struct timer_list timer; /* Timeout handling */ + enum ezusb_state state; /* Current state */ + /* the RID that we will wait for */ + u16 out_rid; + u16 in_rid; +}; + + +/* Forward declarations */ +static void ezusb_ctx_complete(struct request_context *ctx); +static void ezusb_req_queue_run(struct ezusb_priv *upriv); +static void ezusb_bulk_in_callback(struct urb *urb); + +static inline u8 ezusb_reply_inc(u8 count) +{ + if (count < 0x7F) + return count + 1; + else + return 1; +} + +static void ezusb_request_context_put(struct request_context *ctx) +{ + if (!atomic_dec_and_test(&ctx->refcount)) + return; + + WARN_ON(!ctx->done.done); + BUG_ON(ctx->outurb->status == -EINPROGRESS); + BUG_ON(timer_pending(&ctx->timer)); + usb_free_urb(ctx->outurb); + kfree(ctx->buf); + kfree(ctx); +} + +static inline void ezusb_mod_timer(struct ezusb_priv *upriv, + struct timer_list *timer, + unsigned long expire) +{ + if (!upriv->udev) + return; + mod_timer(timer, expire); +} + +static void ezusb_request_timerfn(u_long _ctx) +{ + struct request_context *ctx = (void *) _ctx; + + ctx->outurb->transfer_flags |= URB_ASYNC_UNLINK; + if (usb_unlink_urb(ctx->outurb) == -EINPROGRESS) { + ctx->state = EZUSB_CTX_REQ_TIMEOUT; + } else { + ctx->state = EZUSB_CTX_RESP_TIMEOUT; + dbg("couldn't unlink"); + atomic_inc(&ctx->refcount); + ctx->killed = 1; + ezusb_ctx_complete(ctx); + ezusb_request_context_put(ctx); + } +}; + +static struct request_context *ezusb_alloc_ctx(struct ezusb_priv *upriv, + u16 out_rid, u16 in_rid) +{ + struct request_context *ctx; + + ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC); + if (!ctx) + return NULL; + + ctx->buf = kmalloc(BULK_BUF_SIZE, GFP_ATOMIC); + if (!ctx->buf) { + kfree(ctx); + return NULL; + } + ctx->outurb = usb_alloc_urb(0, GFP_ATOMIC); + if (!ctx->outurb) { + kfree(ctx->buf); + kfree(ctx); + return NULL; + } + + ctx->upriv = upriv; + ctx->state = EZUSB_CTX_START; + ctx->out_rid = out_rid; + ctx->in_rid = in_rid; + + atomic_set(&ctx->refcount, 1); + init_completion(&ctx->done); + + init_timer(&ctx->timer); + ctx->timer.function = ezusb_request_timerfn; + ctx->timer.data = (u_long) ctx; + return ctx; +} + + +/* Hopefully the real complete_all will soon be exported, in the mean + * while this should work. */ +static inline void ezusb_complete_all(struct completion *comp) +{ + complete(comp); + complete(comp); + complete(comp); + complete(comp); +} + +static void ezusb_ctx_complete(struct request_context *ctx) +{ + struct ezusb_priv *upriv = ctx->upriv; + unsigned long flags; + + spin_lock_irqsave(&upriv->req_lock, flags); + + list_del_init(&ctx->list); + if (upriv->udev) { + spin_unlock_irqrestore(&upriv->req_lock, flags); + ezusb_req_queue_run(upriv); + spin_lock_irqsave(&upriv->req_lock, flags); + } + + switch (ctx->state) { + case EZUSB_CTX_COMPLETE: + case EZUSB_CTX_REQSUBMIT_FAIL: + case EZUSB_CTX_REQ_FAILED: + case EZUSB_CTX_REQ_TIMEOUT: + case EZUSB_CTX_RESP_TIMEOUT: + spin_unlock_irqrestore(&upriv->req_lock, flags); + + if ((ctx->out_rid == EZUSB_RID_TX) && upriv->dev) { + struct net_device *dev = upriv->dev; + struct orinoco_private *priv = ndev_priv(dev); + struct net_device_stats *stats = &priv->stats; + + if (ctx->state != EZUSB_CTX_COMPLETE) + stats->tx_errors++; + else + stats->tx_packets++; + + netif_wake_queue(dev); + } + ezusb_complete_all(&ctx->done); + ezusb_request_context_put(ctx); + break; + + default: + spin_unlock_irqrestore(&upriv->req_lock, flags); + if (!upriv->udev) { + /* This is normal, as all request contexts get flushed + * when the device is disconnected */ + err("Called, CTX not terminating, but device gone"); + ezusb_complete_all(&ctx->done); + ezusb_request_context_put(ctx); + break; + } + + err("Called, CTX not in terminating state."); + /* Things are really bad if this happens. Just leak + * the CTX because it may still be linked to the + * queue or the OUT urb may still be active. + * Just leaking at least prevents an Oops or Panic. + */ + break; + } +} + +/** + * ezusb_req_queue_run: + * Description: + * Note: Only one active CTX at any one time, because there's no + * other (reliable) way to match the response URB to the correct + * CTX. + **/ +static void ezusb_req_queue_run(struct ezusb_priv *upriv) +{ + unsigned long flags; + struct request_context *ctx; + int result; + + spin_lock_irqsave(&upriv->req_lock, flags); + + if (!list_empty(&upriv->req_active)) + goto unlock; + + if (list_empty(&upriv->req_pending)) + goto unlock; + + ctx = + list_entry(upriv->req_pending.next, struct request_context, + list); + + if (!ctx->upriv->udev) + goto unlock; + + /* We need to split this off to avoid a race condition */ + list_move_tail(&ctx->list, &upriv->req_active); + + if (ctx->state == EZUSB_CTX_QUEUED) { + atomic_inc(&ctx->refcount); + result = usb_submit_urb(ctx->outurb, GFP_ATOMIC); + if (result) { + ctx->state = EZUSB_CTX_REQSUBMIT_FAIL; + + spin_unlock_irqrestore(&upriv->req_lock, flags); + + err("Fatal, failed to submit command urb." + " error=%d\n", result); + + ezusb_ctx_complete(ctx); + ezusb_request_context_put(ctx); + goto done; + } + + ctx->state = EZUSB_CTX_REQ_SUBMITTED; + ezusb_mod_timer(ctx->upriv, &ctx->timer, + jiffies + DEF_TIMEOUT); + } + + unlock: + spin_unlock_irqrestore(&upriv->req_lock, flags); + + done: + return; +} + +static void ezusb_req_enqueue_run(struct ezusb_priv *upriv, + struct request_context *ctx) +{ + unsigned long flags; + + spin_lock_irqsave(&upriv->req_lock, flags); + + if (!ctx->upriv->udev) { + spin_unlock_irqrestore(&upriv->req_lock, flags); + goto done; + } + atomic_inc(&ctx->refcount); + list_add_tail(&ctx->list, &upriv->req_pending); + spin_unlock_irqrestore(&upriv->req_lock, flags); + + ctx->state = EZUSB_CTX_QUEUED; + ezusb_req_queue_run(upriv); + + done: + return; +} + +static void ezusb_request_out_callback(struct urb *urb) +{ + unsigned long flags; + enum ezusb_state state; + struct request_context *ctx = urb->context; + struct ezusb_priv *upriv = ctx->upriv; + + spin_lock_irqsave(&upriv->req_lock, flags); + + del_timer(&ctx->timer); + + if (ctx->killed) { + spin_unlock_irqrestore(&upriv->req_lock, flags); + pr_warning("interrupt called with dead ctx"); + goto out; + } + + state = ctx->state; + + if (urb->status == 0) { + switch (state) { + case EZUSB_CTX_REQ_SUBMITTED: + if (ctx->in_rid) { + ctx->state = EZUSB_CTX_REQ_COMPLETE; + /* reply URB still pending */ + ezusb_mod_timer(upriv, &ctx->timer, + jiffies + DEF_TIMEOUT); + spin_unlock_irqrestore(&upriv->req_lock, + flags); + break; + } + /* fall through */ + case EZUSB_CTX_RESP_RECEIVED: + /* IN already received before this OUT-ACK */ + ctx->state = EZUSB_CTX_COMPLETE; + spin_unlock_irqrestore(&upriv->req_lock, flags); + ezusb_ctx_complete(ctx); + break; + + default: + spin_unlock_irqrestore(&upriv->req_lock, flags); + err("Unexpected state(0x%x, %d) in OUT URB", + state, urb->status); + break; + } + } else { + /* If someone cancels the OUT URB then its status + * should be either -ECONNRESET or -ENOENT. + */ + switch (state) { + case EZUSB_CTX_REQ_SUBMITTED: + case EZUSB_CTX_RESP_RECEIVED: + ctx->state = EZUSB_CTX_REQ_FAILED; + /* fall through */ + + case EZUSB_CTX_REQ_FAILED: + case EZUSB_CTX_REQ_TIMEOUT: + spin_unlock_irqrestore(&upriv->req_lock, flags); + + ezusb_ctx_complete(ctx); + break; + + default: + spin_unlock_irqrestore(&upriv->req_lock, flags); + + err("Unexpected state(0x%x, %d) in OUT URB", + state, urb->status); + break; + } + } + out: + ezusb_request_context_put(ctx); +} + +static void ezusb_request_in_callback(struct ezusb_priv *upriv, + struct urb *urb) +{ + struct ezusb_packet *ans = urb->transfer_buffer; + struct request_context *ctx = NULL; + enum ezusb_state state; + unsigned long flags; + + /* Find the CTX on the active queue that requested this URB */ + spin_lock_irqsave(&upriv->req_lock, flags); + if (upriv->udev) { + struct list_head *item; + + list_for_each(item, &upriv->req_active) { + struct request_context *c; + int reply_count; + + c = list_entry(item, struct request_context, list); + reply_count = + ezusb_reply_inc(c->buf->req_reply_count); + if ((ans->ans_reply_count == reply_count) + && (le16_to_cpu(ans->hermes_rid) == c->in_rid)) { + ctx = c; + break; + } + dbg("Skipped (0x%x/0x%x) (%d/%d)", + le16_to_cpu(ans->hermes_rid), + c->in_rid, ans->ans_reply_count, reply_count); + } + } + + if (ctx == NULL) { + spin_unlock_irqrestore(&upriv->req_lock, flags); + err("%s: got unexpected RID: 0x%04X", __func__, + le16_to_cpu(ans->hermes_rid)); + ezusb_req_queue_run(upriv); + return; + } + + /* The data we want is in the in buffer, exchange */ + urb->transfer_buffer = ctx->buf; + ctx->buf = (void *) ans; + ctx->buf_length = urb->actual_length; + + state = ctx->state; + switch (state) { + case EZUSB_CTX_REQ_SUBMITTED: + /* We have received our response URB before + * our request has been acknowledged. Do NOT + * destroy our CTX yet, because our OUT URB + * is still alive ... + */ + ctx->state = EZUSB_CTX_RESP_RECEIVED; + spin_unlock_irqrestore(&upriv->req_lock, flags); + + /* Let the machine continue running. */ + break; + + case EZUSB_CTX_REQ_COMPLETE: + /* This is the usual path: our request + * has already been acknowledged, and + * we have now received the reply. + */ + ctx->state = EZUSB_CTX_COMPLETE; + + /* Stop the intimer */ + del_timer(&ctx->timer); + spin_unlock_irqrestore(&upriv->req_lock, flags); + + /* Call the completion handler */ + ezusb_ctx_complete(ctx); + break; + + default: + spin_unlock_irqrestore(&upriv->req_lock, flags); + + pr_warning("Matched IN URB, unexpected context state(0x%x)", + state); + /* Throw this CTX away and try submitting another */ + del_timer(&ctx->timer); + ctx->outurb->transfer_flags |= URB_ASYNC_UNLINK; + usb_unlink_urb(ctx->outurb); + ezusb_req_queue_run(upriv); + break; + } /* switch */ +} + + +static void ezusb_req_ctx_wait(struct ezusb_priv *upriv, + struct request_context *ctx) +{ + switch (ctx->state) { + case EZUSB_CTX_QUEUED: + case EZUSB_CTX_REQ_SUBMITTED: + case EZUSB_CTX_REQ_COMPLETE: + case EZUSB_CTX_RESP_RECEIVED: + if (in_softirq()) { + /* If we get called from a timer, timeout timers don't + * get the chance to run themselves. So we make sure + * that we don't sleep for ever */ + int msecs = DEF_TIMEOUT * (1000 / HZ); + while (!ctx->done.done && msecs--) + udelay(1000); + } else { + wait_event_interruptible(ctx->done.wait, + ctx->done.done); + } + break; + default: + /* Done or failed - nothing to wait for */ + break; + } +} + +static inline u16 build_crc(struct ezusb_packet *data) +{ + u16 crc = 0; + u8 *bytes = (u8 *)data; + int i; + + for (i = 0; i < 8; i++) + crc = (crc << 1) + bytes[i]; + + return crc; +} + +/** + * ezusb_fill_req: + * + * if data == NULL and length > 0 the data is assumed to be already in + * the target buffer and only the header is filled. + * + */ +static int ezusb_fill_req(struct ezusb_packet *req, u16 length, u16 rid, + const void *data, u16 frame_type, u8 reply_count) +{ + int total_size = sizeof(*req) + length; + + BUG_ON(total_size > BULK_BUF_SIZE); + + req->magic = cpu_to_le16(EZUSB_MAGIC); + req->req_reply_count = reply_count; + req->ans_reply_count = 0; + req->frame_type = cpu_to_le16(frame_type); + req->size = cpu_to_le16(length + 4); + req->crc = cpu_to_le16(build_crc(req)); + req->hermes_len = cpu_to_le16(HERMES_BYTES_TO_RECLEN(length)); + req->hermes_rid = cpu_to_le16(rid); + if (data) + memcpy(req->data, data, length); + return total_size; +} + +static int ezusb_submit_in_urb(struct ezusb_priv *upriv) +{ + int retval = 0; + void *cur_buf = upriv->read_urb->transfer_buffer; + + if (upriv->read_urb->status == -EINPROGRESS) { + dbg("urb busy, not resubmiting"); + retval = -EBUSY; + goto exit; + } + usb_fill_bulk_urb(upriv->read_urb, upriv->udev, upriv->read_pipe, + cur_buf, BULK_BUF_SIZE, + ezusb_bulk_in_callback, upriv); + upriv->read_urb->transfer_flags = 0; + retval = usb_submit_urb(upriv->read_urb, GFP_ATOMIC); + if (retval) + err("%s submit failed %d", __func__, retval); + + exit: + return retval; +} + +static inline int ezusb_8051_cpucs(struct ezusb_priv *upriv, int reset) +{ + u8 res_val = reset; /* avoid argument promotion */ + + if (!upriv->udev) { + err("%s: !upriv->udev", __func__); + return -EFAULT; + } + return usb_control_msg(upriv->udev, + usb_sndctrlpipe(upriv->udev, 0), + EZUSB_REQUEST_FW_TRANS, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | + USB_DIR_OUT, EZUSB_CPUCS_REG, 0, &res_val, + sizeof(res_val), DEF_TIMEOUT); +} + +static int ezusb_firmware_download(struct ezusb_priv *upriv, + struct ez_usb_fw *fw) +{ + u8 *fw_buffer; + int retval, addr; + int variant_offset; + + fw_buffer = kmalloc(FW_BUF_SIZE, GFP_KERNEL); + if (!fw_buffer) { + printk(KERN_ERR PFX "Out of memory for firmware buffer.\n"); + return -ENOMEM; + } + /* + * This byte is 1 and should be replaced with 0. The offset is + * 0x10AD in version 0.0.6. The byte in question should follow + * the end of the code pointed to by the jump in the beginning + * of the firmware. Also, it is read by code located at 0x358. + */ + variant_offset = be16_to_cpup((__be16 *) &fw->code[FW_VAR_OFFSET_PTR]); + if (variant_offset >= fw->size) { + printk(KERN_ERR PFX "Invalid firmware variant offset: " + "0x%04x\n", variant_offset); + retval = -EINVAL; + goto fail; + } + + retval = ezusb_8051_cpucs(upriv, 1); + if (retval < 0) + goto fail; + for (addr = 0; addr < fw->size; addr += FW_BUF_SIZE) { + /* 0x100-0x300 should be left alone, it contains card + * specific data, like USB enumeration information */ + if ((addr >= FW_HOLE_START) && (addr < FW_HOLE_END)) + continue; + + memcpy(fw_buffer, &fw->code[addr], FW_BUF_SIZE); + if (variant_offset >= addr && + variant_offset < addr + FW_BUF_SIZE) { + dbg("Patching card_variant byte at 0x%04X", + variant_offset); + fw_buffer[variant_offset - addr] = FW_VAR_VALUE; + } + retval = usb_control_msg(upriv->udev, + usb_sndctrlpipe(upriv->udev, 0), + EZUSB_REQUEST_FW_TRANS, + USB_TYPE_VENDOR | USB_RECIP_DEVICE + | USB_DIR_OUT, + addr, 0x0, + fw_buffer, FW_BUF_SIZE, + DEF_TIMEOUT); + + if (retval < 0) + goto fail; + } + retval = ezusb_8051_cpucs(upriv, 0); + if (retval < 0) + goto fail; + + goto exit; + fail: + printk(KERN_ERR PFX "Firmware download failed, error %d\n", + retval); + exit: + kfree(fw_buffer); + return retval; +} + +static int ezusb_access_ltv(struct ezusb_priv *upriv, + struct request_context *ctx, + u16 length, const void *data, u16 frame_type, + void *ans_buff, unsigned ans_size, u16 *ans_length) +{ + int req_size; + int retval = 0; + enum ezusb_state state; + + BUG_ON(in_irq()); + + if (!upriv->udev) { + dbg("Device disconnected"); + return -ENODEV; + } + + if (upriv->read_urb->status != -EINPROGRESS) + err("%s: in urb not pending", __func__); + + /* protect upriv->reply_count, guarantee sequential numbers */ + spin_lock_bh(&upriv->reply_count_lock); + req_size = ezusb_fill_req(ctx->buf, length, ctx->out_rid, data, + frame_type, upriv->reply_count); + usb_fill_bulk_urb(ctx->outurb, upriv->udev, upriv->write_pipe, + ctx->buf, req_size, + ezusb_request_out_callback, ctx); + + if (ctx->in_rid) + upriv->reply_count = ezusb_reply_inc(upriv->reply_count); + + ezusb_req_enqueue_run(upriv, ctx); + + spin_unlock_bh(&upriv->reply_count_lock); + + if (ctx->in_rid) + ezusb_req_ctx_wait(upriv, ctx); + + state = ctx->state; + switch (state) { + case EZUSB_CTX_COMPLETE: + retval = ctx->outurb->status; + break; + + case EZUSB_CTX_QUEUED: + case EZUSB_CTX_REQ_SUBMITTED: + if (!ctx->in_rid) + break; + default: + err("%s: Unexpected context state %d", __func__, + state); + /* fall though */ + case EZUSB_CTX_REQ_TIMEOUT: + case EZUSB_CTX_REQ_FAILED: + case EZUSB_CTX_RESP_TIMEOUT: + case EZUSB_CTX_REQSUBMIT_FAIL: + printk(KERN_ERR PFX "Access failed, resetting (state %d," + " reply_count %d)\n", state, upriv->reply_count); + upriv->reply_count = 0; + if (state == EZUSB_CTX_REQ_TIMEOUT + || state == EZUSB_CTX_RESP_TIMEOUT) { + printk(KERN_ERR PFX "ctx timed out\n"); + retval = -ETIMEDOUT; + } else { + printk(KERN_ERR PFX "ctx failed\n"); + retval = -EFAULT; + } + goto exit; + break; + } + if (ctx->in_rid) { + struct ezusb_packet *ans = ctx->buf; + unsigned exp_len; + + if (ans->hermes_len != 0) + exp_len = le16_to_cpu(ans->hermes_len) * 2 + 12; + else + exp_len = 14; + + if (exp_len != ctx->buf_length) { + err("%s: length mismatch for RID 0x%04x: " + "expected %d, got %d", __func__, + ctx->in_rid, exp_len, ctx->buf_length); + retval = -EIO; + goto exit; + } + + if (ans_buff) + memcpy(ans_buff, ans->data, min(exp_len, ans_size)); + if (ans_length) + *ans_length = le16_to_cpu(ans->hermes_len); + } + exit: + ezusb_request_context_put(ctx); + return retval; +} + +static int ezusb_write_ltv(struct hermes *hw, int bap, u16 rid, + u16 length, const void *data) +{ + struct ezusb_priv *upriv = hw->priv; + u16 frame_type; + struct request_context *ctx; + + if (length == 0) + return -EINVAL; + + length = HERMES_RECLEN_TO_BYTES(length); + + /* On memory mapped devices HERMES_RID_CNFGROUPADDRESSES can be + * set to be empty, but the USB bridge doesn't like it */ + if (length == 0) + return 0; + + ctx = ezusb_alloc_ctx(upriv, rid, EZUSB_RID_ACK); + if (!ctx) + return -ENOMEM; + + if (rid == EZUSB_RID_TX) + frame_type = EZUSB_FRAME_DATA; + else + frame_type = EZUSB_FRAME_CONTROL; + + return ezusb_access_ltv(upriv, ctx, length, data, frame_type, + NULL, 0, NULL); +} + +static int ezusb_read_ltv(struct hermes *hw, int bap, u16 rid, + unsigned bufsize, u16 *length, void *buf) +{ + struct ezusb_priv *upriv = hw->priv; + struct request_context *ctx; + + if (bufsize % 2) + return -EINVAL; + + ctx = ezusb_alloc_ctx(upriv, rid, rid); + if (!ctx) + return -ENOMEM; + + return ezusb_access_ltv(upriv, ctx, 0, NULL, EZUSB_FRAME_CONTROL, + buf, bufsize, length); +} + +static int ezusb_doicmd_wait(struct hermes *hw, u16 cmd, u16 parm0, u16 parm1, + u16 parm2, struct hermes_response *resp) +{ + struct ezusb_priv *upriv = hw->priv; + struct request_context *ctx; + + __le16 data[4] = { + cpu_to_le16(cmd), + cpu_to_le16(parm0), + cpu_to_le16(parm1), + cpu_to_le16(parm2), + }; + dbg("0x%04X, parm0 0x%04X, parm1 0x%04X, parm2 0x%04X", + cmd, parm0, parm1, parm2); + ctx = ezusb_alloc_ctx(upriv, EZUSB_RID_DOCMD, EZUSB_RID_ACK); + if (!ctx) + return -ENOMEM; + + return ezusb_access_ltv(upriv, ctx, sizeof(data), &data, + EZUSB_FRAME_CONTROL, NULL, 0, NULL); +} + +static int ezusb_docmd_wait(struct hermes *hw, u16 cmd, u16 parm0, + struct hermes_response *resp) +{ + struct ezusb_priv *upriv = hw->priv; + struct request_context *ctx; + + __le16 data[4] = { + cpu_to_le16(cmd), + cpu_to_le16(parm0), + 0, + 0, + }; + dbg("0x%04X, parm0 0x%04X", cmd, parm0); + ctx = ezusb_alloc_ctx(upriv, EZUSB_RID_DOCMD, EZUSB_RID_ACK); + if (!ctx) + return -ENOMEM; + + return ezusb_access_ltv(upriv, ctx, sizeof(data), &data, + EZUSB_FRAME_CONTROL, NULL, 0, NULL); +} + +static int ezusb_bap_pread(struct hermes *hw, int bap, + void *buf, int len, u16 id, u16 offset) +{ + struct ezusb_priv *upriv = hw->priv; + struct ezusb_packet *ans = (void *) upriv->read_urb->transfer_buffer; + int actual_length = upriv->read_urb->actual_length; + + if (id == EZUSB_RID_RX) { + if ((sizeof(*ans) + offset + len) > actual_length) { + printk(KERN_ERR PFX "BAP read beyond buffer end " + "in rx frame\n"); + return -EINVAL; + } + memcpy(buf, ans->data + offset, len); + return 0; + } + + if (EZUSB_IS_INFO(id)) { + /* Include 4 bytes for length/type */ + if ((sizeof(*ans) + offset + len - 4) > actual_length) { + printk(KERN_ERR PFX "BAP read beyond buffer end " + "in info frame\n"); + return -EFAULT; + } + memcpy(buf, ans->data + offset - 4, len); + } else { + printk(KERN_ERR PFX "Unexpected fid 0x%04x\n", id); + return -EINVAL; + } + + return 0; +} + +static int ezusb_read_pda(struct hermes *hw, __le16 *pda, + u32 pda_addr, u16 pda_len) +{ + struct ezusb_priv *upriv = hw->priv; + struct request_context *ctx; + __le16 data[] = { + cpu_to_le16(pda_addr & 0xffff), + cpu_to_le16(pda_len - 4) + }; + ctx = ezusb_alloc_ctx(upriv, EZUSB_RID_READ_PDA, EZUSB_RID_READ_PDA); + if (!ctx) + return -ENOMEM; + + /* wl_lkm does not include PDA size in the PDA area. + * We will pad the information into pda, so other routines + * don't have to be modified */ + pda[0] = cpu_to_le16(pda_len - 2); + /* Includes CFG_PROD_DATA but not itself */ + pda[1] = cpu_to_le16(0x0800); /* CFG_PROD_DATA */ + + return ezusb_access_ltv(upriv, ctx, sizeof(data), &data, + EZUSB_FRAME_CONTROL, &pda[2], pda_len - 4, + NULL); +} + +static int ezusb_program_init(struct hermes *hw, u32 entry_point) +{ + struct ezusb_priv *upriv = hw->priv; + struct request_context *ctx; + __le32 data = cpu_to_le32(entry_point); + + ctx = ezusb_alloc_ctx(upriv, EZUSB_RID_PROG_INIT, EZUSB_RID_ACK); + if (!ctx) + return -ENOMEM; + + return ezusb_access_ltv(upriv, ctx, sizeof(data), &data, + EZUSB_FRAME_CONTROL, NULL, 0, NULL); +} + +static int ezusb_program_end(struct hermes *hw) +{ + struct ezusb_priv *upriv = hw->priv; + struct request_context *ctx; + + ctx = ezusb_alloc_ctx(upriv, EZUSB_RID_PROG_END, EZUSB_RID_ACK); + if (!ctx) + return -ENOMEM; + + return ezusb_access_ltv(upriv, ctx, 0, NULL, + EZUSB_FRAME_CONTROL, NULL, 0, NULL); +} + +static int ezusb_program_bytes(struct hermes *hw, const char *buf, + u32 addr, u32 len) +{ + struct ezusb_priv *upriv = hw->priv; + struct request_context *ctx; + __le32 data = cpu_to_le32(addr); + int err; + + ctx = ezusb_alloc_ctx(upriv, EZUSB_RID_PROG_SET_ADDR, EZUSB_RID_ACK); + if (!ctx) + return -ENOMEM; + + err = ezusb_access_ltv(upriv, ctx, sizeof(data), &data, + EZUSB_FRAME_CONTROL, NULL, 0, NULL); + if (err) + return err; + + ctx = ezusb_alloc_ctx(upriv, EZUSB_RID_PROG_BYTES, EZUSB_RID_ACK); + if (!ctx) + return -ENOMEM; + + return ezusb_access_ltv(upriv, ctx, len, buf, + EZUSB_FRAME_CONTROL, NULL, 0, NULL); +} + +static int ezusb_program(struct hermes *hw, const char *buf, + u32 addr, u32 len) +{ + u32 ch_addr; + u32 ch_len; + int err = 0; + + /* We can only send 2048 bytes out of the bulk xmit at a time, + * so we have to split any programming into chunks of <2048 + * bytes. */ + + ch_len = (len < MAX_DL_SIZE) ? len : MAX_DL_SIZE; + ch_addr = addr; + + while (ch_addr < (addr + len)) { + pr_debug("Programming subblock of length %d " + "to address 0x%08x. Data @ %p\n", + ch_len, ch_addr, &buf[ch_addr - addr]); + + err = ezusb_program_bytes(hw, &buf[ch_addr - addr], + ch_addr, ch_len); + if (err) + break; + + ch_addr += ch_len; + ch_len = ((addr + len - ch_addr) < MAX_DL_SIZE) ? + (addr + len - ch_addr) : MAX_DL_SIZE; + } + + return err; +} + +static netdev_tx_t ezusb_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct orinoco_private *priv = ndev_priv(dev); + struct net_device_stats *stats = &priv->stats; + struct ezusb_priv *upriv = priv->card; + u8 mic[MICHAEL_MIC_LEN + 1]; + int err = 0; + int tx_control; + unsigned long flags; + struct request_context *ctx; + u8 *buf; + int tx_size; + + if (!netif_running(dev)) { + printk(KERN_ERR "%s: Tx on stopped device!\n", + dev->name); + return NETDEV_TX_BUSY; + } + + if (netif_queue_stopped(dev)) { + printk(KERN_DEBUG "%s: Tx while transmitter busy!\n", + dev->name); + return NETDEV_TX_BUSY; + } + + if (orinoco_lock(priv, &flags) != 0) { + printk(KERN_ERR + "%s: ezusb_xmit() called while hw_unavailable\n", + dev->name); + return NETDEV_TX_BUSY; + } + + if (!netif_carrier_ok(dev) || + (priv->iw_mode == NL80211_IFTYPE_MONITOR)) { + /* Oops, the firmware hasn't established a connection, + silently drop the packet (this seems to be the + safest approach). */ + goto drop; + } + + /* Check packet length */ + if (skb->len < ETH_HLEN) + goto drop; + + ctx = ezusb_alloc_ctx(upriv, EZUSB_RID_TX, 0); + if (!ctx) + goto busy; + + memset(ctx->buf, 0, BULK_BUF_SIZE); + buf = ctx->buf->data; + + tx_control = 0; + + err = orinoco_process_xmit_skb(skb, dev, priv, &tx_control, + &mic[0]); + if (err) + goto drop; + + { + __le16 *tx_cntl = (__le16 *)buf; + *tx_cntl = cpu_to_le16(tx_control); + buf += sizeof(*tx_cntl); + } + + memcpy(buf, skb->data, skb->len); + buf += skb->len; + + if (tx_control & HERMES_TXCTRL_MIC) { + u8 *m = mic; + /* Mic has been offset so it can be copied to an even + * address. We're copying eveything anyway, so we + * don't need to copy that first byte. */ + if (skb->len % 2) + m++; + memcpy(buf, m, MICHAEL_MIC_LEN); + buf += MICHAEL_MIC_LEN; + } + + /* Finally, we actually initiate the send */ + netif_stop_queue(dev); + + /* The card may behave better if we send evenly sized usb transfers */ + tx_size = ALIGN(buf - ctx->buf->data, 2); + + err = ezusb_access_ltv(upriv, ctx, tx_size, NULL, + EZUSB_FRAME_DATA, NULL, 0, NULL); + + if (err) { + netif_start_queue(dev); + if (net_ratelimit()) + printk(KERN_ERR "%s: Error %d transmitting packet\n", + dev->name, err); + goto busy; + } + + dev->trans_start = jiffies; + stats->tx_bytes += skb->len; + goto ok; + + drop: + stats->tx_errors++; + stats->tx_dropped++; + + ok: + orinoco_unlock(priv, &flags); + dev_kfree_skb(skb); + return NETDEV_TX_OK; + + busy: + orinoco_unlock(priv, &flags); + return NETDEV_TX_BUSY; +} + +static int ezusb_allocate(struct hermes *hw, u16 size, u16 *fid) +{ + *fid = EZUSB_RID_TX; + return 0; +} + + +static int ezusb_hard_reset(struct orinoco_private *priv) +{ + struct ezusb_priv *upriv = priv->card; + int retval = ezusb_8051_cpucs(upriv, 1); + + if (retval < 0) { + err("Failed to reset"); + return retval; + } + + retval = ezusb_8051_cpucs(upriv, 0); + if (retval < 0) { + err("Failed to unreset"); + return retval; + } + + dbg("sending control message"); + retval = usb_control_msg(upriv->udev, + usb_sndctrlpipe(upriv->udev, 0), + EZUSB_REQUEST_TRIGER, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | + USB_DIR_OUT, 0x0, 0x0, NULL, 0, + DEF_TIMEOUT); + if (retval < 0) { + err("EZUSB_REQUEST_TRIGER failed retval %d", retval); + return retval; + } +#if 0 + dbg("Sending EZUSB_REQUEST_TRIG_AC"); + retval = usb_control_msg(upriv->udev, + usb_sndctrlpipe(upriv->udev, 0), + EZUSB_REQUEST_TRIG_AC, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | + USB_DIR_OUT, 0x00FA, 0x0, NULL, 0, + DEF_TIMEOUT); + if (retval < 0) { + err("EZUSB_REQUEST_TRIG_AC failed retval %d", retval); + return retval; + } +#endif + + return 0; +} + + +static int ezusb_init(struct hermes *hw) +{ + struct ezusb_priv *upriv = hw->priv; + int retval; + + BUG_ON(in_interrupt()); + BUG_ON(!upriv); + + upriv->reply_count = 0; + /* Write the MAGIC number on the simulated registers to keep + * orinoco.c happy */ + hermes_write_regn(hw, SWSUPPORT0, HERMES_MAGIC); + hermes_write_regn(hw, RXFID, EZUSB_RID_RX); + + usb_kill_urb(upriv->read_urb); + ezusb_submit_in_urb(upriv); + + retval = ezusb_write_ltv(hw, 0, EZUSB_RID_INIT1, + HERMES_BYTES_TO_RECLEN(2), "\x10\x00"); + if (retval < 0) { + printk(KERN_ERR PFX "EZUSB_RID_INIT1 error %d\n", retval); + return retval; + } + + retval = ezusb_docmd_wait(hw, HERMES_CMD_INIT, 0, NULL); + if (retval < 0) { + printk(KERN_ERR PFX "HERMES_CMD_INIT error %d\n", retval); + return retval; + } + + return 0; +} + +static void ezusb_bulk_in_callback(struct urb *urb) +{ + struct ezusb_priv *upriv = (struct ezusb_priv *) urb->context; + struct ezusb_packet *ans = urb->transfer_buffer; + u16 crc; + u16 hermes_rid; + + if (upriv->udev == NULL) { + dbg("disconnected"); + return; + } + + if (urb->status == -ETIMEDOUT) { + /* When a device gets unplugged we get this every time + * we resubmit, flooding the logs. Since we don't use + * USB timeouts, it shouldn't happen any other time*/ + pr_warning("%s: urb timed out, not resubmiting", __func__); + return; + } + if (urb->status == -ECONNABORTED) { + pr_warning("%s: connection abort, resubmiting urb", + __func__); + goto resubmit; + } + if ((urb->status == -EILSEQ) + || (urb->status == -ENOENT) + || (urb->status == -ECONNRESET)) { + dbg("status %d, not resubmiting", urb->status); + return; + } + if (urb->status) + dbg("status: %d length: %d", + urb->status, urb->actual_length); + if (urb->actual_length < sizeof(*ans)) { + err("%s: short read, ignoring", __func__); + goto resubmit; + } + crc = build_crc(ans); + if (le16_to_cpu(ans->crc) != crc) { + err("CRC error, ignoring packet"); + goto resubmit; + } + + hermes_rid = le16_to_cpu(ans->hermes_rid); + if ((hermes_rid != EZUSB_RID_RX) && !EZUSB_IS_INFO(hermes_rid)) { + ezusb_request_in_callback(upriv, urb); + } else if (upriv->dev) { + struct net_device *dev = upriv->dev; + struct orinoco_private *priv = ndev_priv(dev); + struct hermes *hw = &priv->hw; + + if (hermes_rid == EZUSB_RID_RX) { + __orinoco_ev_rx(dev, hw); + } else { + hermes_write_regn(hw, INFOFID, + le16_to_cpu(ans->hermes_rid)); + __orinoco_ev_info(dev, hw); + } + } + + resubmit: + if (upriv->udev) + ezusb_submit_in_urb(upriv); +} + +static inline void ezusb_delete(struct ezusb_priv *upriv) +{ + struct net_device *dev; + struct list_head *item; + struct list_head *tmp_item; + unsigned long flags; + + BUG_ON(in_interrupt()); + BUG_ON(!upriv); + + dev = upriv->dev; + mutex_lock(&upriv->mtx); + + upriv->udev = NULL; /* No timer will be rearmed from here */ + + usb_kill_urb(upriv->read_urb); + + spin_lock_irqsave(&upriv->req_lock, flags); + list_for_each_safe(item, tmp_item, &upriv->req_active) { + struct request_context *ctx; + int err; + + ctx = list_entry(item, struct request_context, list); + atomic_inc(&ctx->refcount); + + ctx->outurb->transfer_flags |= URB_ASYNC_UNLINK; + err = usb_unlink_urb(ctx->outurb); + + spin_unlock_irqrestore(&upriv->req_lock, flags); + if (err == -EINPROGRESS) + wait_for_completion(&ctx->done); + + del_timer_sync(&ctx->timer); + /* FIXME: there is an slight chance for the irq handler to + * be running */ + if (!list_empty(&ctx->list)) + ezusb_ctx_complete(ctx); + + ezusb_request_context_put(ctx); + spin_lock_irqsave(&upriv->req_lock, flags); + } + spin_unlock_irqrestore(&upriv->req_lock, flags); + + list_for_each_safe(item, tmp_item, &upriv->req_pending) + ezusb_ctx_complete(list_entry(item, + struct request_context, list)); + + if (upriv->read_urb && upriv->read_urb->status == -EINPROGRESS) + printk(KERN_ERR PFX "Some URB in progress\n"); + + mutex_unlock(&upriv->mtx); + + if (upriv->read_urb) { + kfree(upriv->read_urb->transfer_buffer); + usb_free_urb(upriv->read_urb); + } + kfree(upriv->bap_buf); + if (upriv->dev) { + struct orinoco_private *priv = ndev_priv(upriv->dev); + orinoco_if_del(priv); + free_orinocodev(priv); + } +} + +static void ezusb_lock_irqsave(spinlock_t *lock, + unsigned long *flags) __acquires(lock) +{ + spin_lock_bh(lock); +} + +static void ezusb_unlock_irqrestore(spinlock_t *lock, + unsigned long *flags) __releases(lock) +{ + spin_unlock_bh(lock); +} + +static void ezusb_lock_irq(spinlock_t *lock) __acquires(lock) +{ + spin_lock_bh(lock); +} + +static void ezusb_unlock_irq(spinlock_t *lock) __releases(lock) +{ + spin_unlock_bh(lock); +} + +static const struct hermes_ops ezusb_ops = { + .init = ezusb_init, + .cmd_wait = ezusb_docmd_wait, + .init_cmd_wait = ezusb_doicmd_wait, + .allocate = ezusb_allocate, + .read_ltv = ezusb_read_ltv, + .write_ltv = ezusb_write_ltv, + .bap_pread = ezusb_bap_pread, + .read_pda_h = ezusb_read_pda, + .program_init = ezusb_program_init, + .program_end = ezusb_program_end, + .program = ezusb_program, + .lock_irqsave = ezusb_lock_irqsave, + .unlock_irqrestore = ezusb_unlock_irqrestore, + .lock_irq = ezusb_lock_irq, + .unlock_irq = ezusb_unlock_irq, +}; + +static const struct net_device_ops ezusb_netdev_ops = { + .ndo_open = orinoco_open, + .ndo_stop = orinoco_stop, + .ndo_start_xmit = ezusb_xmit, + .ndo_set_rx_mode = orinoco_set_multicast_list, + .ndo_change_mtu = orinoco_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, + .ndo_tx_timeout = orinoco_tx_timeout, + .ndo_get_stats = orinoco_get_stats, +}; + +static int ezusb_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct orinoco_private *priv; + struct hermes *hw; + struct ezusb_priv *upriv = NULL; + struct usb_interface_descriptor *iface_desc; + struct usb_endpoint_descriptor *ep; + const struct firmware *fw_entry = NULL; + int retval = 0; + int i; + + priv = alloc_orinocodev(sizeof(*upriv), &udev->dev, + ezusb_hard_reset, NULL); + if (!priv) { + err("Couldn't allocate orinocodev"); + goto exit; + } + + hw = &priv->hw; + + upriv = priv->card; + + mutex_init(&upriv->mtx); + spin_lock_init(&upriv->reply_count_lock); + + spin_lock_init(&upriv->req_lock); + INIT_LIST_HEAD(&upriv->req_pending); + INIT_LIST_HEAD(&upriv->req_active); + + upriv->udev = udev; + + hw->iobase = (void __force __iomem *) &upriv->hermes_reg_fake; + hw->reg_spacing = HERMES_16BIT_REGSPACING; + hw->priv = upriv; + hw->ops = &ezusb_ops; + + /* set up the endpoint information */ + /* check out the endpoints */ + + iface_desc = &interface->altsetting[0].desc; + for (i = 0; i < iface_desc->bNumEndpoints; ++i) { + ep = &interface->altsetting[0].endpoint[i].desc; + + if (((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) + == USB_DIR_IN) && + ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + == USB_ENDPOINT_XFER_BULK)) { + /* we found a bulk in endpoint */ + if (upriv->read_urb != NULL) { + pr_warning("Found a second bulk in ep, ignored"); + continue; + } + + upriv->read_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!upriv->read_urb) { + err("No free urbs available"); + goto error; + } + if (le16_to_cpu(ep->wMaxPacketSize) != 64) + pr_warning("bulk in: wMaxPacketSize!= 64"); + if (ep->bEndpointAddress != (2 | USB_DIR_IN)) + pr_warning("bulk in: bEndpointAddress: %d", + ep->bEndpointAddress); + upriv->read_pipe = usb_rcvbulkpipe(udev, + ep-> + bEndpointAddress); + upriv->read_urb->transfer_buffer = + kmalloc(BULK_BUF_SIZE, GFP_KERNEL); + if (!upriv->read_urb->transfer_buffer) { + err("Couldn't allocate IN buffer"); + goto error; + } + } + + if (((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) + == USB_DIR_OUT) && + ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + == USB_ENDPOINT_XFER_BULK)) { + /* we found a bulk out endpoint */ + if (upriv->bap_buf != NULL) { + pr_warning("Found a second bulk out ep, ignored"); + continue; + } + + if (le16_to_cpu(ep->wMaxPacketSize) != 64) + pr_warning("bulk out: wMaxPacketSize != 64"); + if (ep->bEndpointAddress != 2) + pr_warning("bulk out: bEndpointAddress: %d", + ep->bEndpointAddress); + upriv->write_pipe = usb_sndbulkpipe(udev, + ep-> + bEndpointAddress); + upriv->bap_buf = kmalloc(BULK_BUF_SIZE, GFP_KERNEL); + if (!upriv->bap_buf) { + err("Couldn't allocate bulk_out_buffer"); + goto error; + } + } + } + if (!upriv->bap_buf || !upriv->read_urb) { + err("Didn't find the required bulk endpoints"); + goto error; + } + + if (request_firmware(&fw_entry, "orinoco_ezusb_fw", + &interface->dev) == 0) { + firmware.size = fw_entry->size; + firmware.code = fw_entry->data; + } + if (firmware.size && firmware.code) { + if (ezusb_firmware_download(upriv, &firmware)) + goto error; + } else { + err("No firmware to download"); + goto error; + } + + if (ezusb_hard_reset(priv) < 0) { + err("Cannot reset the device"); + goto error; + } + + /* If the firmware is already downloaded orinoco.c will call + * ezusb_init but if the firmware is not already there, that will make + * the kernel very unstable, so we try initializing here and quit in + * case of error */ + if (ezusb_init(hw) < 0) { + err("Couldn't initialize the device"); + err("Firmware may not be downloaded or may be wrong."); + goto error; + } + + /* Initialise the main driver */ + if (orinoco_init(priv) != 0) { + err("orinoco_init() failed\n"); + goto error; + } + + if (orinoco_if_add(priv, 0, 0, &ezusb_netdev_ops) != 0) { + upriv->dev = NULL; + err("%s: orinoco_if_add() failed", __func__); + goto error; + } + upriv->dev = priv->ndev; + + goto exit; + + error: + ezusb_delete(upriv); + if (upriv->dev) { + /* upriv->dev was 0, so ezusb_delete() didn't free it */ + free_orinocodev(priv); + } + upriv = NULL; + retval = -EFAULT; + exit: + if (fw_entry) { + firmware.code = NULL; + firmware.size = 0; + release_firmware(fw_entry); + } + usb_set_intfdata(interface, upriv); + return retval; +} + + +static void ezusb_disconnect(struct usb_interface *intf) +{ + struct ezusb_priv *upriv = usb_get_intfdata(intf); + usb_set_intfdata(intf, NULL); + ezusb_delete(upriv); + printk(KERN_INFO PFX "Disconnected\n"); +} + + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver orinoco_driver = { + .name = DRIVER_NAME, + .probe = ezusb_probe, + .disconnect = ezusb_disconnect, + .id_table = ezusb_table, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +module_usb_driver(orinoco_driver); + +MODULE_AUTHOR("Manuel Estrada Sainz"); +MODULE_DESCRIPTION("Driver for Orinoco wireless LAN cards using EZUSB bridge"); +MODULE_LICENSE("Dual MPL/GPL"); diff --git a/drivers/net/wireless/orinoco/scan.c b/drivers/net/wireless/orinoco/scan.c new file mode 100644 index 0000000..e8c5714 --- /dev/null +++ b/drivers/net/wireless/orinoco/scan.c @@ -0,0 +1,248 @@ +/* Helpers for managing scan queues + * + * See copyright notice in main.c + */ + +#include +#include +#include +#include +#include + +#include "hermes.h" +#include "orinoco.h" +#include "main.h" + +#include "scan.h" + +#define ZERO_DBM_OFFSET 0x95 +#define MAX_SIGNAL_LEVEL 0x8A +#define MIN_SIGNAL_LEVEL 0x2F + +#define SIGNAL_TO_DBM(x) \ + (clamp_t(s32, (x), MIN_SIGNAL_LEVEL, MAX_SIGNAL_LEVEL) \ + - ZERO_DBM_OFFSET) +#define SIGNAL_TO_MBM(x) (SIGNAL_TO_DBM(x) * 100) + +static int symbol_build_supp_rates(u8 *buf, const __le16 *rates) +{ + int i; + u8 rate; + + buf[0] = WLAN_EID_SUPP_RATES; + for (i = 0; i < 5; i++) { + rate = le16_to_cpu(rates[i]); + /* NULL terminated */ + if (rate == 0x0) + break; + buf[i + 2] = rate; + } + buf[1] = i; + + return i + 2; +} + +static int prism_build_supp_rates(u8 *buf, const u8 *rates) +{ + int i; + + buf[0] = WLAN_EID_SUPP_RATES; + for (i = 0; i < 8; i++) { + /* NULL terminated */ + if (rates[i] == 0x0) + break; + buf[i + 2] = rates[i]; + } + buf[1] = i; + + /* We might still have another 2 rates, which need to go in + * extended supported rates */ + if (i == 8 && rates[i] > 0) { + buf[10] = WLAN_EID_EXT_SUPP_RATES; + for (; i < 10; i++) { + /* NULL terminated */ + if (rates[i] == 0x0) + break; + buf[i + 2] = rates[i]; + } + buf[11] = i - 8; + } + + return (i < 8) ? i + 2 : i + 4; +} + +static void orinoco_add_hostscan_result(struct orinoco_private *priv, + const union hermes_scan_info *bss) +{ + struct wiphy *wiphy = priv_to_wiphy(priv); + struct ieee80211_channel *channel; + struct cfg80211_bss *cbss; + u8 *ie; + u8 ie_buf[46]; + u64 timestamp; + s32 signal; + u16 capability; + u16 beacon_interval; + int ie_len; + int freq; + int len; + + len = le16_to_cpu(bss->a.essid_len); + + /* Reconstruct SSID and bitrate IEs to pass up */ + ie_buf[0] = WLAN_EID_SSID; + ie_buf[1] = len; + memcpy(&ie_buf[2], bss->a.essid, len); + + ie = ie_buf + len + 2; + ie_len = ie_buf[1] + 2; + switch (priv->firmware_type) { + case FIRMWARE_TYPE_SYMBOL: + ie_len += symbol_build_supp_rates(ie, bss->s.rates); + break; + + case FIRMWARE_TYPE_INTERSIL: + ie_len += prism_build_supp_rates(ie, bss->p.rates); + break; + + case FIRMWARE_TYPE_AGERE: + default: + break; + } + + freq = ieee80211_dsss_chan_to_freq(le16_to_cpu(bss->a.channel)); + channel = ieee80211_get_channel(wiphy, freq); + if (!channel) { + printk(KERN_DEBUG "Invalid channel designation %04X(%04X)", + bss->a.channel, freq); + return; /* Then ignore it for now */ + } + timestamp = 0; + capability = le16_to_cpu(bss->a.capabilities); + beacon_interval = le16_to_cpu(bss->a.beacon_interv); + signal = SIGNAL_TO_MBM(le16_to_cpu(bss->a.level)); + + cbss = cfg80211_inform_bss(wiphy, channel, bss->a.bssid, timestamp, + capability, beacon_interval, ie_buf, ie_len, + signal, GFP_KERNEL); + cfg80211_put_bss(wiphy, cbss); +} + +void orinoco_add_extscan_result(struct orinoco_private *priv, + struct agere_ext_scan_info *bss, + size_t len) +{ + struct wiphy *wiphy = priv_to_wiphy(priv); + struct ieee80211_channel *channel; + struct cfg80211_bss *cbss; + const u8 *ie; + u64 timestamp; + s32 signal; + u16 capability; + u16 beacon_interval; + size_t ie_len; + int chan, freq; + + ie_len = len - sizeof(*bss); + ie = cfg80211_find_ie(WLAN_EID_DS_PARAMS, bss->data, ie_len); + chan = ie ? ie[2] : 0; + freq = ieee80211_dsss_chan_to_freq(chan); + channel = ieee80211_get_channel(wiphy, freq); + + timestamp = le64_to_cpu(bss->timestamp); + capability = le16_to_cpu(bss->capabilities); + beacon_interval = le16_to_cpu(bss->beacon_interval); + ie = bss->data; + signal = SIGNAL_TO_MBM(bss->level); + + cbss = cfg80211_inform_bss(wiphy, channel, bss->bssid, timestamp, + capability, beacon_interval, ie, ie_len, + signal, GFP_KERNEL); + cfg80211_put_bss(wiphy, cbss); +} + +void orinoco_add_hostscan_results(struct orinoco_private *priv, + unsigned char *buf, + size_t len) +{ + int offset; /* In the scan data */ + size_t atom_len; + bool abort = false; + + switch (priv->firmware_type) { + case FIRMWARE_TYPE_AGERE: + atom_len = sizeof(struct agere_scan_apinfo); + offset = 0; + break; + + case FIRMWARE_TYPE_SYMBOL: + /* Lack of documentation necessitates this hack. + * Different firmwares have 68 or 76 byte long atoms. + * We try modulo first. If the length divides by both, + * we check what would be the channel in the second + * frame for a 68-byte atom. 76-byte atoms have 0 there. + * Valid channel cannot be 0. */ + if (len % 76) + atom_len = 68; + else if (len % 68) + atom_len = 76; + else if (len >= 1292 && buf[68] == 0) + atom_len = 76; + else + atom_len = 68; + offset = 0; + break; + + case FIRMWARE_TYPE_INTERSIL: + offset = 4; + if (priv->has_hostscan) { + atom_len = le16_to_cpup((__le16 *)buf); + /* Sanity check for atom_len */ + if (atom_len < sizeof(struct prism2_scan_apinfo)) { + printk(KERN_ERR "%s: Invalid atom_len in scan " + "data: %zu\n", priv->ndev->name, + atom_len); + abort = true; + goto scan_abort; + } + } else + atom_len = offsetof(struct prism2_scan_apinfo, atim); + break; + + default: + abort = true; + goto scan_abort; + } + + /* Check that we got an whole number of atoms */ + if ((len - offset) % atom_len) { + printk(KERN_ERR "%s: Unexpected scan data length %zu, " + "atom_len %zu, offset %d\n", priv->ndev->name, len, + atom_len, offset); + abort = true; + goto scan_abort; + } + + /* Process the entries one by one */ + for (; offset + atom_len <= len; offset += atom_len) { + union hermes_scan_info *atom; + + atom = (union hermes_scan_info *) (buf + offset); + + orinoco_add_hostscan_result(priv, atom); + } + + scan_abort: + if (priv->scan_request) { + cfg80211_scan_done(priv->scan_request, abort); + priv->scan_request = NULL; + } +} + +void orinoco_scan_done(struct orinoco_private *priv, bool abort) +{ + if (priv->scan_request) { + cfg80211_scan_done(priv->scan_request, abort); + priv->scan_request = NULL; + } +} diff --git a/drivers/net/wireless/orinoco/scan.h b/drivers/net/wireless/orinoco/scan.h new file mode 100644 index 0000000..27281fb --- /dev/null +++ b/drivers/net/wireless/orinoco/scan.h @@ -0,0 +1,21 @@ +/* Helpers for managing scan queues + * + * See copyright notice in main.c + */ +#ifndef _ORINOCO_SCAN_H_ +#define _ORINOCO_SCAN_H_ + +/* Forward declarations */ +struct orinoco_private; +struct agere_ext_scan_info; + +/* Add scan results */ +void orinoco_add_extscan_result(struct orinoco_private *priv, + struct agere_ext_scan_info *atom, + size_t len); +void orinoco_add_hostscan_results(struct orinoco_private *dev, + unsigned char *buf, + size_t len); +void orinoco_scan_done(struct orinoco_private *priv, bool abort); + +#endif /* _ORINOCO_SCAN_H_ */ diff --git a/drivers/net/wireless/orinoco/spectrum_cs.c b/drivers/net/wireless/orinoco/spectrum_cs.c new file mode 100644 index 0000000..7eb42f4 --- /dev/null +++ b/drivers/net/wireless/orinoco/spectrum_cs.c @@ -0,0 +1,448 @@ +/* + * Driver for 802.11b cards using RAM-loadable Symbol firmware, such as + * Symbol Wireless Networker LA4137, CompactFlash cards by Socket + * Communications and Intel PRO/Wireless 2011B. + * + * The driver implements Symbol firmware download. The rest is handled + * in hermes.c and main.c. + * + * Utilities for downloading the Symbol firmware are available at + * http://sourceforge.net/projects/orinoco/ + * + * Copyright (C) 2002-2005 Pavel Roskin + * Portions based on orinoco_cs.c: + * Copyright (C) David Gibson, Linuxcare Australia + * Portions based on Spectrum24tDnld.c from original spectrum24 driver: + * Copyright (C) Symbol Technologies. + * + * See copyright notice in file main.c. + */ + +#define DRIVER_NAME "spectrum_cs" +#define PFX DRIVER_NAME ": " + +#include +#include +#include +#include +#include +#include +#include + +#include "orinoco.h" + +/********************************************************************/ +/* Module stuff */ +/********************************************************************/ + +MODULE_AUTHOR("Pavel Roskin "); +MODULE_DESCRIPTION("Driver for Symbol Spectrum24 Trilogy cards with firmware downloader"); +MODULE_LICENSE("Dual MPL/GPL"); + +/* Module parameters */ + +/* Some D-Link cards have buggy CIS. They do work at 5v properly, but + * don't have any CIS entry for it. This workaround it... */ +static int ignore_cis_vcc; /* = 0 */ +module_param(ignore_cis_vcc, int, 0); +MODULE_PARM_DESC(ignore_cis_vcc, "Allow voltage mismatch between card and socket"); + +/********************************************************************/ +/* Data structures */ +/********************************************************************/ + +/* PCMCIA specific device information (goes in the card field of + * struct orinoco_private */ +struct orinoco_pccard { + struct pcmcia_device *p_dev; +}; + +/********************************************************************/ +/* Function prototypes */ +/********************************************************************/ + +static int spectrum_cs_config(struct pcmcia_device *link); +static void spectrum_cs_release(struct pcmcia_device *link); + +/* Constants for the CISREG_CCSR register */ +#define HCR_RUN 0x07 /* run firmware after reset */ +#define HCR_IDLE 0x0E /* don't run firmware after reset */ +#define HCR_MEM16 0x10 /* memory width bit, should be preserved */ + + +/* + * Reset the card using configuration registers COR and CCSR. + * If IDLE is 1, stop the firmware, so that it can be safely rewritten. + */ +static int +spectrum_reset(struct pcmcia_device *link, int idle) +{ + int ret; + u8 save_cor; + u8 ccsr; + + /* Doing it if hardware is gone is guaranteed crash */ + if (!pcmcia_dev_present(link)) + return -ENODEV; + + /* Save original COR value */ + ret = pcmcia_read_config_byte(link, CISREG_COR, &save_cor); + if (ret) + goto failed; + + /* Soft-Reset card */ + ret = pcmcia_write_config_byte(link, CISREG_COR, + (save_cor | COR_SOFT_RESET)); + if (ret) + goto failed; + udelay(1000); + + /* Read CCSR */ + ret = pcmcia_read_config_byte(link, CISREG_CCSR, &ccsr); + if (ret) + goto failed; + + /* + * Start or stop the firmware. Memory width bit should be + * preserved from the value we've just read. + */ + ccsr = (idle ? HCR_IDLE : HCR_RUN) | (ccsr & HCR_MEM16); + ret = pcmcia_write_config_byte(link, CISREG_CCSR, ccsr); + if (ret) + goto failed; + udelay(1000); + + /* Restore original COR configuration index */ + ret = pcmcia_write_config_byte(link, CISREG_COR, + (save_cor & ~COR_SOFT_RESET)); + if (ret) + goto failed; + udelay(1000); + return 0; + +failed: + return -ENODEV; +} + +/********************************************************************/ +/* Device methods */ +/********************************************************************/ + +static int +spectrum_cs_hard_reset(struct orinoco_private *priv) +{ + struct orinoco_pccard *card = priv->card; + struct pcmcia_device *link = card->p_dev; + + /* Soft reset using COR and HCR */ + spectrum_reset(link, 0); + + return 0; +} + +static int +spectrum_cs_stop_firmware(struct orinoco_private *priv, int idle) +{ + struct orinoco_pccard *card = priv->card; + struct pcmcia_device *link = card->p_dev; + + return spectrum_reset(link, idle); +} + +/********************************************************************/ +/* PCMCIA stuff */ +/********************************************************************/ + +static int +spectrum_cs_probe(struct pcmcia_device *link) +{ + struct orinoco_private *priv; + struct orinoco_pccard *card; + + priv = alloc_orinocodev(sizeof(*card), &link->dev, + spectrum_cs_hard_reset, + spectrum_cs_stop_firmware); + if (!priv) + return -ENOMEM; + card = priv->card; + + /* Link both structures together */ + card->p_dev = link; + link->priv = priv; + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)) + /* Interrupt setup */ + link->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING; + link->irq.Handler = orinoco_interrupt; +#endif +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37)) + link->conf.Attributes = 0; + link->conf.IntType = INT_MEMORY_AND_IO; +#endif + + return spectrum_cs_config(link); +} /* spectrum_cs_attach */ + +static void spectrum_cs_detach(struct pcmcia_device *link) +{ + struct orinoco_private *priv = link->priv; + + orinoco_if_del(priv); + + spectrum_cs_release(link); + + free_orinocodev(priv); +} /* spectrum_cs_detach */ + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)) +static int spectrum_cs_config_check(struct pcmcia_device *p_dev, + void *priv_data) +{ + if (p_dev->config_index == 0) + return -EINVAL; + + return pcmcia_request_io(p_dev); +}; +#else +static int spectrum_cs_config_check(struct pcmcia_device *p_dev, + cistpl_cftable_entry_t *cfg, + cistpl_cftable_entry_t *dflt, + unsigned int vcc, + void *priv_data) +{ + if (cfg->index == 0) + goto next_entry; + + /* Use power settings for Vcc and Vpp if present */ + /* Note that the CIS values need to be rescaled */ + if (cfg->vcc.present & (1 << CISTPL_POWER_VNOM)) { + if (vcc != cfg->vcc.param[CISTPL_POWER_VNOM] / 10000) { + DEBUG(2, "%s: Vcc mismatch (vcc = %d, CIS = %d)\n", + __func__, vcc, + cfg->vcc.param[CISTPL_POWER_VNOM] / 10000); + if (!ignore_cis_vcc) + goto next_entry; + } + } else if (dflt->vcc.present & (1 << CISTPL_POWER_VNOM)) { + if (vcc != dflt->vcc.param[CISTPL_POWER_VNOM] / 10000) { + DEBUG(2, "%s: Vcc mismatch (vcc = %d, CIS = %d)\n", + __func__, vcc, + dflt->vcc.param[CISTPL_POWER_VNOM] / 10000); + if (!ignore_cis_vcc) + goto next_entry; + } + } + + if (cfg->vpp1.present & (1 << CISTPL_POWER_VNOM)) + p_dev->conf.Vpp = + cfg->vpp1.param[CISTPL_POWER_VNOM] / 10000; + else if (dflt->vpp1.present & (1 << CISTPL_POWER_VNOM)) + p_dev->conf.Vpp = + dflt->vpp1.param[CISTPL_POWER_VNOM] / 10000; + + /* Do we need to allocate an interrupt? */ + p_dev->conf.Attributes |= CONF_ENABLE_IRQ; + + /* IO window settings */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) + p_dev->resource[0]->end = p_dev->resource[1]->end = 0; +#else + p_dev->io.NumPorts1 = p_dev->io.NumPorts2 = 0; +#endif + if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) { + cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) + p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK; + p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; + p_dev->resource[0]->flags |= + pcmcia_io_cfg_data_width(io->flags); + p_dev->resource[0]->start = io->win[0].base; + p_dev->resource[0]->end = io->win[0].len; +#else + p_dev->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + if (!(io->flags & CISTPL_IO_8BIT)) + p_dev->io.Attributes1 = IO_DATA_PATH_WIDTH_16; + if (!(io->flags & CISTPL_IO_16BIT)) + p_dev->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + p_dev->io.IOAddrLines = io->flags & CISTPL_IO_LINES_MASK; + p_dev->io.BasePort1 = io->win[0].base; + p_dev->io.NumPorts1 = io->win[0].len; +#endif + if (io->nwin > 1) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) + p_dev->resource[1]->flags = p_dev->resource[0]->flags; + p_dev->resource[1]->start = io->win[1].base; + p_dev->resource[1]->end = io->win[1].len; +#else + p_dev->io.Attributes2 = p_dev->io.Attributes1; + p_dev->io.BasePort2 = io->win[1].base; + p_dev->io.NumPorts2 = io->win[1].len; +#endif + } + + /* This reserves IO space but doesn't actually enable it */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) + if (pcmcia_request_io(p_dev) != 0) +#else + if (pcmcia_request_io(p_dev, &p_dev->io) != 0) +#endif + goto next_entry; + } + return 0; + +next_entry: + pcmcia_disable_device(p_dev); + return -ENODEV; +}; +#endif + +static int +spectrum_cs_config(struct pcmcia_device *link) +{ + struct orinoco_private *priv = link->priv; + struct hermes *hw = &priv->hw; + int ret; + void __iomem *mem; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)) + link->config_flags |= CONF_AUTO_SET_VPP | CONF_AUTO_CHECK_VCC | + CONF_AUTO_SET_IO | CONF_ENABLE_IRQ; + if (ignore_cis_vcc) + link->config_flags &= ~CONF_AUTO_CHECK_VCC; +#endif + ret = pcmcia_loop_config(link, spectrum_cs_config_check, NULL); + if (ret) { + if (!ignore_cis_vcc) + printk(KERN_ERR PFX "GetNextTuple(): No matching " + "CIS configuration. Maybe you need the " + "ignore_cis_vcc=1 parameter.\n"); + goto failed; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) + mem = ioport_map(link->resource[0]->start, + resource_size(link->resource[0])); +#else + mem = ioport_map(link->io.BasePort1, link->io.NumPorts1); +#endif + if (!mem) + goto failed; + + /* We initialize the hermes structure before completing PCMCIA + * configuration just in case the interrupt handler gets + * called. */ + hermes_struct_init(hw, mem, HERMES_16BIT_REGSPACING); + hw->eeprom_pda = true; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + ret = pcmcia_request_irq(link, orinoco_interrupt); +#else + ret = pcmcia_request_irq(link, &link->irq); +#endif + if (ret) + goto failed; + + ret = pcmcia_enable_device(link); + if (ret) + goto failed; + + /* Reset card */ + if (spectrum_cs_hard_reset(priv) != 0) + goto failed; + + /* Initialise the main driver */ + if (orinoco_init(priv) != 0) { + printk(KERN_ERR PFX "orinoco_init() failed\n"); + goto failed; + } + + /* Register an interface with the stack */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)) + if (orinoco_if_add(priv, link->resource[0]->start, + link->irq, NULL) != 0) { +#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + if (orinoco_if_add(priv, link->io.BasePort1, + link->irq, NULL) != 0) { +#else + if (orinoco_if_add(priv, link->io.BasePort1, + link->irq.AssignedIRQ, NULL) != 0) { +#endif + printk(KERN_ERR PFX "orinoco_if_add() failed\n"); + goto failed; + } + + return 0; + + failed: + spectrum_cs_release(link); + return -ENODEV; +} /* spectrum_cs_config */ + +static void +spectrum_cs_release(struct pcmcia_device *link) +{ + struct orinoco_private *priv = link->priv; + unsigned long flags; + + /* We're committed to taking the device away now, so mark the + * hardware as unavailable */ + priv->hw.ops->lock_irqsave(&priv->lock, &flags); + priv->hw_unavailable++; + priv->hw.ops->unlock_irqrestore(&priv->lock, &flags); + + pcmcia_disable_device(link); + if (priv->hw.iobase) + ioport_unmap(priv->hw.iobase); +} /* spectrum_cs_release */ + + +static int +spectrum_cs_suspend(struct pcmcia_device *link) +{ + struct orinoco_private *priv = link->priv; + int err = 0; + + /* Mark the device as stopped, to block IO until later */ + orinoco_down(priv); + + return err; +} + +static int +spectrum_cs_resume(struct pcmcia_device *link) +{ + struct orinoco_private *priv = link->priv; + int err = orinoco_up(priv); + + return err; +} + + +/********************************************************************/ +/* Module initialization */ +/********************************************************************/ + +static const struct pcmcia_device_id spectrum_cs_ids[] = { + PCMCIA_DEVICE_MANF_CARD(0x026c, 0x0001), /* Symbol Spectrum24 LA4137 */ + PCMCIA_DEVICE_MANF_CARD(0x0104, 0x0001), /* Socket Communications CF */ + PCMCIA_DEVICE_PROD_ID12("Intel", "PRO/Wireless LAN PC Card", 0x816cc815, 0x6fbf459a), /* 2011B, not 2011 */ + PCMCIA_DEVICE_NULL, +}; +MODULE_DEVICE_TABLE(pcmcia, spectrum_cs_ids); + +static struct pcmcia_driver orinoco_driver = { + .owner = THIS_MODULE, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)) + .name = DRIVER_NAME, +#else + .drv = { + .name = DRIVER_NAME, + }, +#endif + .probe = spectrum_cs_probe, + .remove = spectrum_cs_detach, + .suspend = spectrum_cs_suspend, + .resume = spectrum_cs_resume, + .id_table = spectrum_cs_ids, +}; +module_pcmcia_driver(orinoco_driver); diff --git a/drivers/net/wireless/orinoco/wext.c b/drivers/net/wireless/orinoco/wext.c new file mode 100644 index 0000000..a30f93c --- /dev/null +++ b/drivers/net/wireless/orinoco/wext.c @@ -0,0 +1,1427 @@ +/* Wireless extensions support. + * + * See copyright notice in main.c + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hermes.h" +#include "hermes_rid.h" +#include "orinoco.h" + +#include "hw.h" +#include "mic.h" +#include "scan.h" +#include "main.h" + +#include "wext.h" + +#define MAX_RID_LEN 1024 + +/* Helper routine to record keys + * It is called under orinoco_lock so it may not sleep */ +static int orinoco_set_key(struct orinoco_private *priv, int index, + enum orinoco_alg alg, const u8 *key, int key_len, + const u8 *seq, int seq_len) +{ +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,28) + int len; + if (!unlikely(ZERO_OR_NULL_PTR(priv->keys[index].key))) { + len = priv->keys[index].key_len; + memset(priv->keys[index].key, 0, len); + kfree(priv->keys[index].key); + } + if (!unlikely(ZERO_OR_NULL_PTR(priv->keys[index].seq))) { + len = priv->keys[index].seq_len; + memset(priv->keys[index].seq, 0, len); + kfree(priv->keys[index].seq); + } +#else + kzfree(priv->keys[index].key); + kzfree(priv->keys[index].seq); +#endif + + if (key_len) { + priv->keys[index].key = kzalloc(key_len, GFP_ATOMIC); + if (!priv->keys[index].key) + goto nomem; + } else + priv->keys[index].key = NULL; + + if (seq_len) { + priv->keys[index].seq = kzalloc(seq_len, GFP_ATOMIC); + if (!priv->keys[index].seq) + goto free_key; + } else + priv->keys[index].seq = NULL; + + priv->keys[index].key_len = key_len; + priv->keys[index].seq_len = seq_len; + + if (key_len) + memcpy(priv->keys[index].key, key, key_len); + if (seq_len) + memcpy(priv->keys[index].seq, seq, seq_len); + + switch (alg) { + case ORINOCO_ALG_TKIP: + priv->keys[index].cipher = WLAN_CIPHER_SUITE_TKIP; + break; + + case ORINOCO_ALG_WEP: + priv->keys[index].cipher = (key_len > SMALL_KEY_SIZE) ? + WLAN_CIPHER_SUITE_WEP104 : WLAN_CIPHER_SUITE_WEP40; + break; + + case ORINOCO_ALG_NONE: + default: + priv->keys[index].cipher = 0; + break; + } + + return 0; + +free_key: + kfree(priv->keys[index].key); + priv->keys[index].key = NULL; + +nomem: + priv->keys[index].key_len = 0; + priv->keys[index].seq_len = 0; + priv->keys[index].cipher = 0; + + return -ENOMEM; +} + +static struct iw_statistics *orinoco_get_wireless_stats(struct net_device *dev) +{ + struct orinoco_private *priv = ndev_priv(dev); + struct hermes *hw = &priv->hw; + struct iw_statistics *wstats = &priv->wstats; + int err; + unsigned long flags; + + if (!netif_device_present(dev)) { + printk(KERN_WARNING "%s: get_wireless_stats() called while device not present\n", + dev->name); + return NULL; /* FIXME: Can we do better than this? */ + } + + /* If busy, return the old stats. Returning NULL may cause + * the interface to disappear from /proc/net/wireless */ + if (orinoco_lock(priv, &flags) != 0) + return wstats; + + /* We can't really wait for the tallies inquiry command to + * complete, so we just use the previous results and trigger + * a new tallies inquiry command for next time - Jean II */ + /* FIXME: Really we should wait for the inquiry to come back - + * as it is the stats we give don't make a whole lot of sense. + * Unfortunately, it's not clear how to do that within the + * wireless extensions framework: I think we're in user + * context, but a lock seems to be held by the time we get in + * here so we're not safe to sleep here. */ + hermes_inquire(hw, HERMES_INQ_TALLIES); + + if (priv->iw_mode == NL80211_IFTYPE_ADHOC) { + memset(&wstats->qual, 0, sizeof(wstats->qual)); + /* If a spy address is defined, we report stats of the + * first spy address - Jean II */ + if (SPY_NUMBER(priv)) { + wstats->qual.qual = priv->spy_data.spy_stat[0].qual; + wstats->qual.level = priv->spy_data.spy_stat[0].level; + wstats->qual.noise = priv->spy_data.spy_stat[0].noise; + wstats->qual.updated = + priv->spy_data.spy_stat[0].updated; + } + } else { + struct { + __le16 qual, signal, noise, unused; + } __packed cq; + + err = HERMES_READ_RECORD(hw, USER_BAP, + HERMES_RID_COMMSQUALITY, &cq); + + if (!err) { + wstats->qual.qual = (int)le16_to_cpu(cq.qual); + wstats->qual.level = (int)le16_to_cpu(cq.signal) - 0x95; + wstats->qual.noise = (int)le16_to_cpu(cq.noise) - 0x95; + wstats->qual.updated = + IW_QUAL_ALL_UPDATED | IW_QUAL_DBM; + } + } + + orinoco_unlock(priv, &flags); + return wstats; +} + +/********************************************************************/ +/* Wireless extensions */ +/********************************************************************/ + +static int orinoco_ioctl_setwap(struct net_device *dev, + struct iw_request_info *info, + struct sockaddr *ap_addr, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + int err = -EINPROGRESS; /* Call commit handler */ + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + /* Enable automatic roaming - no sanity checks are needed */ + if (is_zero_ether_addr(ap_addr->sa_data) || + is_broadcast_ether_addr(ap_addr->sa_data)) { + priv->bssid_fixed = 0; + memset(priv->desired_bssid, 0, ETH_ALEN); + + /* "off" means keep existing connection */ + if (ap_addr->sa_data[0] == 0) { + __orinoco_hw_set_wap(priv); + err = 0; + } + goto out; + } + + if (priv->firmware_type == FIRMWARE_TYPE_AGERE) { + printk(KERN_WARNING "%s: Lucent/Agere firmware doesn't " + "support manual roaming\n", + dev->name); + err = -EOPNOTSUPP; + goto out; + } + + if (priv->iw_mode != NL80211_IFTYPE_STATION) { + printk(KERN_WARNING "%s: Manual roaming supported only in " + "managed mode\n", dev->name); + err = -EOPNOTSUPP; + goto out; + } + + /* Intersil firmware hangs without Desired ESSID */ + if (priv->firmware_type == FIRMWARE_TYPE_INTERSIL && + strlen(priv->desired_essid) == 0) { + printk(KERN_WARNING "%s: Desired ESSID must be set for " + "manual roaming\n", dev->name); + err = -EOPNOTSUPP; + goto out; + } + + /* Finally, enable manual roaming */ + priv->bssid_fixed = 1; + memcpy(priv->desired_bssid, &ap_addr->sa_data, ETH_ALEN); + + out: + orinoco_unlock(priv, &flags); + return err; +} + +static int orinoco_ioctl_getwap(struct net_device *dev, + struct iw_request_info *info, + struct sockaddr *ap_addr, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + + int err = 0; + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + ap_addr->sa_family = ARPHRD_ETHER; + err = orinoco_hw_get_current_bssid(priv, ap_addr->sa_data); + + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_ioctl_setiwencode(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *erq, + char *keybuf) +{ + struct orinoco_private *priv = ndev_priv(dev); + int index = (erq->flags & IW_ENCODE_INDEX) - 1; + int setindex = priv->tx_key; + enum orinoco_alg encode_alg = priv->encode_alg; + int restricted = priv->wep_restrict; + int err = -EINPROGRESS; /* Call commit handler */ + unsigned long flags; + + if (!priv->has_wep) + return -EOPNOTSUPP; + + if (erq->pointer) { + /* We actually have a key to set - check its length */ + if (erq->length > LARGE_KEY_SIZE) + return -E2BIG; + + if ((erq->length > SMALL_KEY_SIZE) && !priv->has_big_wep) + return -E2BIG; + } + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + /* Clear any TKIP key we have */ + if ((priv->has_wpa) && (priv->encode_alg == ORINOCO_ALG_TKIP)) + (void) orinoco_clear_tkip_key(priv, setindex); + + if (erq->length > 0) { + if ((index < 0) || (index >= ORINOCO_MAX_KEYS)) + index = priv->tx_key; + + /* Switch on WEP if off */ + if (encode_alg != ORINOCO_ALG_WEP) { + setindex = index; + encode_alg = ORINOCO_ALG_WEP; + } + } else { + /* Important note : if the user do "iwconfig eth0 enc off", + * we will arrive there with an index of -1. This is valid + * but need to be taken care off... Jean II */ + if ((index < 0) || (index >= ORINOCO_MAX_KEYS)) { + if ((index != -1) || (erq->flags == 0)) { + err = -EINVAL; + goto out; + } + } else { + /* Set the index : Check that the key is valid */ + if (priv->keys[index].key_len == 0) { + err = -EINVAL; + goto out; + } + setindex = index; + } + } + + if (erq->flags & IW_ENCODE_DISABLED) + encode_alg = ORINOCO_ALG_NONE; + if (erq->flags & IW_ENCODE_OPEN) + restricted = 0; + if (erq->flags & IW_ENCODE_RESTRICTED) + restricted = 1; + + if (erq->pointer && erq->length > 0) { + err = orinoco_set_key(priv, index, ORINOCO_ALG_WEP, keybuf, + erq->length, NULL, 0); + } + priv->tx_key = setindex; + + /* Try fast key change if connected and only keys are changed */ + if ((priv->encode_alg == encode_alg) && + (priv->wep_restrict == restricted) && + netif_carrier_ok(dev)) { + err = __orinoco_hw_setup_wepkeys(priv); + /* No need to commit if successful */ + goto out; + } + + priv->encode_alg = encode_alg; + priv->wep_restrict = restricted; + + out: + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_ioctl_getiwencode(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *erq, + char *keybuf) +{ + struct orinoco_private *priv = ndev_priv(dev); + int index = (erq->flags & IW_ENCODE_INDEX) - 1; + unsigned long flags; + + if (!priv->has_wep) + return -EOPNOTSUPP; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + if ((index < 0) || (index >= ORINOCO_MAX_KEYS)) + index = priv->tx_key; + + erq->flags = 0; + if (!priv->encode_alg) + erq->flags |= IW_ENCODE_DISABLED; + erq->flags |= index + 1; + + if (priv->wep_restrict) + erq->flags |= IW_ENCODE_RESTRICTED; + else + erq->flags |= IW_ENCODE_OPEN; + + erq->length = priv->keys[index].key_len; + + memcpy(keybuf, priv->keys[index].key, erq->length); + + orinoco_unlock(priv, &flags); + return 0; +} + +static int orinoco_ioctl_setessid(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *erq, + char *essidbuf) +{ + struct orinoco_private *priv = ndev_priv(dev); + unsigned long flags; + + /* Note : ESSID is ignored in Ad-Hoc demo mode, but we can set it + * anyway... - Jean II */ + + /* Hum... Should not use Wireless Extension constant (may change), + * should use our own... - Jean II */ + if (erq->length > IW_ESSID_MAX_SIZE) + return -E2BIG; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + /* NULL the string (for NULL termination & ESSID = ANY) - Jean II */ + memset(priv->desired_essid, 0, sizeof(priv->desired_essid)); + + /* If not ANY, get the new ESSID */ + if (erq->flags) + memcpy(priv->desired_essid, essidbuf, erq->length); + + orinoco_unlock(priv, &flags); + + return -EINPROGRESS; /* Call commit handler */ +} + +static int orinoco_ioctl_getessid(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *erq, + char *essidbuf) +{ + struct orinoco_private *priv = ndev_priv(dev); + int active; + int err = 0; + unsigned long flags; + + if (netif_running(dev)) { + err = orinoco_hw_get_essid(priv, &active, essidbuf); + if (err < 0) + return err; + erq->length = err; + } else { + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + memcpy(essidbuf, priv->desired_essid, IW_ESSID_MAX_SIZE); + erq->length = strlen(priv->desired_essid); + orinoco_unlock(priv, &flags); + } + + erq->flags = 1; + + return 0; +} + +static int orinoco_ioctl_setfreq(struct net_device *dev, + struct iw_request_info *info, + struct iw_freq *frq, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + int chan = -1; + unsigned long flags; + int err = -EINPROGRESS; /* Call commit handler */ + + /* In infrastructure mode the AP sets the channel */ + if (priv->iw_mode == NL80211_IFTYPE_STATION) + return -EBUSY; + + if ((frq->e == 0) && (frq->m <= 1000)) { + /* Setting by channel number */ + chan = frq->m; + } else { + /* Setting by frequency */ + int denom = 1; + int i; + + /* Calculate denominator to rescale to MHz */ + for (i = 0; i < (6 - frq->e); i++) + denom *= 10; + + chan = ieee80211_freq_to_dsss_chan(frq->m / denom); + } + + if ((chan < 1) || (chan > NUM_CHANNELS) || + !(priv->channel_mask & (1 << (chan - 1)))) + return -EINVAL; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + priv->channel = chan; + if (priv->iw_mode == NL80211_IFTYPE_MONITOR) { + /* Fast channel change - no commit if successful */ + struct hermes *hw = &priv->hw; + err = hw->ops->cmd_wait(hw, HERMES_CMD_TEST | + HERMES_TEST_SET_CHANNEL, + chan, NULL); + } + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_ioctl_getfreq(struct net_device *dev, + struct iw_request_info *info, + struct iw_freq *frq, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + int tmp; + + /* Locking done in there */ + tmp = orinoco_hw_get_freq(priv); + if (tmp < 0) + return tmp; + + frq->m = tmp * 100000; + frq->e = 1; + + return 0; +} + +static int orinoco_ioctl_getsens(struct net_device *dev, + struct iw_request_info *info, + struct iw_param *srq, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + struct hermes *hw = &priv->hw; + u16 val; + int err; + unsigned long flags; + + if (!priv->has_sensitivity) + return -EOPNOTSUPP; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CNFSYSTEMSCALE, &val); + orinoco_unlock(priv, &flags); + + if (err) + return err; + + srq->value = val; + srq->fixed = 0; /* auto */ + + return 0; +} + +static int orinoco_ioctl_setsens(struct net_device *dev, + struct iw_request_info *info, + struct iw_param *srq, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + int val = srq->value; + unsigned long flags; + + if (!priv->has_sensitivity) + return -EOPNOTSUPP; + + if ((val < 1) || (val > 3)) + return -EINVAL; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + priv->ap_density = val; + orinoco_unlock(priv, &flags); + + return -EINPROGRESS; /* Call commit handler */ +} + +static int orinoco_ioctl_setrate(struct net_device *dev, + struct iw_request_info *info, + struct iw_param *rrq, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + int ratemode; + int bitrate; /* 100s of kilobits */ + unsigned long flags; + + /* As the user space doesn't know our highest rate, it uses -1 + * to ask us to set the highest rate. Test it using "iwconfig + * ethX rate auto" - Jean II */ + if (rrq->value == -1) + bitrate = 110; + else { + if (rrq->value % 100000) + return -EINVAL; + bitrate = rrq->value / 100000; + } + + ratemode = orinoco_get_bitratemode(bitrate, !rrq->fixed); + + if (ratemode == -1) + return -EINVAL; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + priv->bitratemode = ratemode; + orinoco_unlock(priv, &flags); + + return -EINPROGRESS; +} + +static int orinoco_ioctl_getrate(struct net_device *dev, + struct iw_request_info *info, + struct iw_param *rrq, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + int err = 0; + int bitrate, automatic; + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + orinoco_get_ratemode_cfg(priv->bitratemode, &bitrate, &automatic); + + /* If the interface is running we try to find more about the + current mode */ + if (netif_running(dev)) { + int act_bitrate; + int lerr; + + /* Ignore errors if we can't get the actual bitrate */ + lerr = orinoco_hw_get_act_bitrate(priv, &act_bitrate); + if (!lerr) + bitrate = act_bitrate; + } + + orinoco_unlock(priv, &flags); + + rrq->value = bitrate; + rrq->fixed = !automatic; + rrq->disabled = 0; + + return err; +} + +static int orinoco_ioctl_setpower(struct net_device *dev, + struct iw_request_info *info, + struct iw_param *prq, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + int err = -EINPROGRESS; /* Call commit handler */ + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + if (prq->disabled) { + priv->pm_on = 0; + } else { + switch (prq->flags & IW_POWER_MODE) { + case IW_POWER_UNICAST_R: + priv->pm_mcast = 0; + priv->pm_on = 1; + break; + case IW_POWER_ALL_R: + priv->pm_mcast = 1; + priv->pm_on = 1; + break; + case IW_POWER_ON: + /* No flags : but we may have a value - Jean II */ + break; + default: + err = -EINVAL; + goto out; + } + + if (prq->flags & IW_POWER_TIMEOUT) { + priv->pm_on = 1; + priv->pm_timeout = prq->value / 1000; + } + if (prq->flags & IW_POWER_PERIOD) { + priv->pm_on = 1; + priv->pm_period = prq->value / 1000; + } + /* It's valid to not have a value if we are just toggling + * the flags... Jean II */ + if (!priv->pm_on) { + err = -EINVAL; + goto out; + } + } + + out: + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_ioctl_getpower(struct net_device *dev, + struct iw_request_info *info, + struct iw_param *prq, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + struct hermes *hw = &priv->hw; + int err = 0; + u16 enable, period, timeout, mcast; + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CNFPMENABLED, &enable); + if (err) + goto out; + + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CNFMAXSLEEPDURATION, &period); + if (err) + goto out; + + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CNFPMHOLDOVERDURATION, &timeout); + if (err) + goto out; + + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CNFMULTICASTRECEIVE, &mcast); + if (err) + goto out; + + prq->disabled = !enable; + /* Note : by default, display the period */ + if ((prq->flags & IW_POWER_TYPE) == IW_POWER_TIMEOUT) { + prq->flags = IW_POWER_TIMEOUT; + prq->value = timeout * 1000; + } else { + prq->flags = IW_POWER_PERIOD; + prq->value = period * 1000; + } + if (mcast) + prq->flags |= IW_POWER_ALL_R; + else + prq->flags |= IW_POWER_UNICAST_R; + + out: + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_ioctl_set_encodeext(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + struct iw_point *encoding = &wrqu->encoding; + struct iw_encode_ext *ext = (struct iw_encode_ext *)extra; + int idx, alg = ext->alg, set_key = 1; + unsigned long flags; + int err = -EINVAL; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + /* Determine and validate the key index */ + idx = encoding->flags & IW_ENCODE_INDEX; + if (idx) { + if ((idx < 1) || (idx > 4)) + goto out; + idx--; + } else + idx = priv->tx_key; + + if (encoding->flags & IW_ENCODE_DISABLED) + alg = IW_ENCODE_ALG_NONE; + + if (priv->has_wpa && (alg != IW_ENCODE_ALG_TKIP)) { + /* Clear any TKIP TX key we had */ + (void) orinoco_clear_tkip_key(priv, priv->tx_key); + } + + if (ext->ext_flags & IW_ENCODE_EXT_SET_TX_KEY) { + priv->tx_key = idx; + set_key = ((alg == IW_ENCODE_ALG_TKIP) || + (ext->key_len > 0)) ? 1 : 0; + } + + if (set_key) { + /* Set the requested key first */ + switch (alg) { + case IW_ENCODE_ALG_NONE: + priv->encode_alg = ORINOCO_ALG_NONE; + err = orinoco_set_key(priv, idx, ORINOCO_ALG_NONE, + NULL, 0, NULL, 0); + break; + + case IW_ENCODE_ALG_WEP: + if (ext->key_len <= 0) + goto out; + + priv->encode_alg = ORINOCO_ALG_WEP; + err = orinoco_set_key(priv, idx, ORINOCO_ALG_WEP, + ext->key, ext->key_len, NULL, 0); + break; + + case IW_ENCODE_ALG_TKIP: + { + u8 *tkip_iv = NULL; + + if (!priv->has_wpa || + (ext->key_len > sizeof(struct orinoco_tkip_key))) + goto out; + + priv->encode_alg = ORINOCO_ALG_TKIP; + + if (ext->ext_flags & IW_ENCODE_EXT_RX_SEQ_VALID) + tkip_iv = &ext->rx_seq[0]; + + err = orinoco_set_key(priv, idx, ORINOCO_ALG_TKIP, + ext->key, ext->key_len, tkip_iv, + ORINOCO_SEQ_LEN); + + err = __orinoco_hw_set_tkip_key(priv, idx, + ext->ext_flags & IW_ENCODE_EXT_SET_TX_KEY, + priv->keys[idx].key, + tkip_iv, ORINOCO_SEQ_LEN, NULL, 0); + if (err) + printk(KERN_ERR "%s: Error %d setting TKIP key" + "\n", dev->name, err); + + goto out; + } + default: + goto out; + } + } + err = -EINPROGRESS; + out: + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_ioctl_get_encodeext(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + struct iw_point *encoding = &wrqu->encoding; + struct iw_encode_ext *ext = (struct iw_encode_ext *)extra; + int idx, max_key_len; + unsigned long flags; + int err; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + err = -EINVAL; + max_key_len = encoding->length - sizeof(*ext); + if (max_key_len < 0) + goto out; + + idx = encoding->flags & IW_ENCODE_INDEX; + if (idx) { + if ((idx < 1) || (idx > 4)) + goto out; + idx--; + } else + idx = priv->tx_key; + + encoding->flags = idx + 1; + memset(ext, 0, sizeof(*ext)); + + switch (priv->encode_alg) { + case ORINOCO_ALG_NONE: + ext->alg = IW_ENCODE_ALG_NONE; + ext->key_len = 0; + encoding->flags |= IW_ENCODE_DISABLED; + break; + case ORINOCO_ALG_WEP: + ext->alg = IW_ENCODE_ALG_WEP; + ext->key_len = min(priv->keys[idx].key_len, max_key_len); + memcpy(ext->key, priv->keys[idx].key, ext->key_len); + encoding->flags |= IW_ENCODE_ENABLED; + break; + case ORINOCO_ALG_TKIP: + ext->alg = IW_ENCODE_ALG_TKIP; + ext->key_len = min(priv->keys[idx].key_len, max_key_len); + memcpy(ext->key, priv->keys[idx].key, ext->key_len); + encoding->flags |= IW_ENCODE_ENABLED; + break; + } + + err = 0; + out: + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_ioctl_set_auth(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + struct hermes *hw = &priv->hw; + struct iw_param *param = &wrqu->param; + unsigned long flags; + int ret = -EINPROGRESS; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + switch (param->flags & IW_AUTH_INDEX) { + case IW_AUTH_WPA_VERSION: + case IW_AUTH_CIPHER_PAIRWISE: + case IW_AUTH_CIPHER_GROUP: + case IW_AUTH_RX_UNENCRYPTED_EAPOL: + case IW_AUTH_PRIVACY_INVOKED: + case IW_AUTH_DROP_UNENCRYPTED: + /* + * orinoco does not use these parameters + */ + break; + + case IW_AUTH_MFP: + /* Management Frame Protection not supported. + * Only fail if set to required. + */ + if (param->value == IW_AUTH_MFP_REQUIRED) + ret = -EINVAL; + break; + + case IW_AUTH_KEY_MGMT: + /* wl_lkm implies value 2 == PSK for Hermes I + * which ties in with WEXT + * no other hints tho :( + */ + priv->key_mgmt = param->value; + break; + + case IW_AUTH_TKIP_COUNTERMEASURES: + /* When countermeasures are enabled, shut down the + * card; when disabled, re-enable the card. This must + * take effect immediately. + * + * TODO: Make sure that the EAPOL message is getting + * out before card disabled + */ + if (param->value) { + priv->tkip_cm_active = 1; + ret = hermes_disable_port(hw, 0); + } else { + priv->tkip_cm_active = 0; + ret = hermes_enable_port(hw, 0); + } + break; + + case IW_AUTH_80211_AUTH_ALG: + if (param->value & IW_AUTH_ALG_SHARED_KEY) + priv->wep_restrict = 1; + else if (param->value & IW_AUTH_ALG_OPEN_SYSTEM) + priv->wep_restrict = 0; + else + ret = -EINVAL; + break; + + case IW_AUTH_WPA_ENABLED: + if (priv->has_wpa) { + priv->wpa_enabled = param->value ? 1 : 0; + } else { + if (param->value) + ret = -EOPNOTSUPP; + /* else silently accept disable of WPA */ + priv->wpa_enabled = 0; + } + break; + + default: + ret = -EOPNOTSUPP; + } + + orinoco_unlock(priv, &flags); + return ret; +} + +static int orinoco_ioctl_get_auth(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + struct iw_param *param = &wrqu->param; + unsigned long flags; + int ret = 0; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + switch (param->flags & IW_AUTH_INDEX) { + case IW_AUTH_KEY_MGMT: + param->value = priv->key_mgmt; + break; + + case IW_AUTH_TKIP_COUNTERMEASURES: + param->value = priv->tkip_cm_active; + break; + + case IW_AUTH_80211_AUTH_ALG: + if (priv->wep_restrict) + param->value = IW_AUTH_ALG_SHARED_KEY; + else + param->value = IW_AUTH_ALG_OPEN_SYSTEM; + break; + + case IW_AUTH_WPA_ENABLED: + param->value = priv->wpa_enabled; + break; + + default: + ret = -EOPNOTSUPP; + } + + orinoco_unlock(priv, &flags); + return ret; +} + +static int orinoco_ioctl_set_genie(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + u8 *buf; + unsigned long flags; + + /* cut off at IEEE80211_MAX_DATA_LEN */ + if ((wrqu->data.length > IEEE80211_MAX_DATA_LEN) || + (wrqu->data.length && (extra == NULL))) + return -EINVAL; + + if (wrqu->data.length) { + buf = kmemdup(extra, wrqu->data.length, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + } else + buf = NULL; + + if (orinoco_lock(priv, &flags) != 0) { + kfree(buf); + return -EBUSY; + } + + kfree(priv->wpa_ie); + priv->wpa_ie = buf; + priv->wpa_ie_len = wrqu->data.length; + + if (priv->wpa_ie) { + /* Looks like wl_lkm wants to check the auth alg, and + * somehow pass it to the firmware. + * Instead it just calls the key mgmt rid + * - we do this in set auth. + */ + } + + orinoco_unlock(priv, &flags); + return 0; +} + +static int orinoco_ioctl_get_genie(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + unsigned long flags; + int err = 0; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + if ((priv->wpa_ie_len == 0) || (priv->wpa_ie == NULL)) { + wrqu->data.length = 0; + goto out; + } + + if (wrqu->data.length < priv->wpa_ie_len) { + err = -E2BIG; + goto out; + } + + wrqu->data.length = priv->wpa_ie_len; + memcpy(extra, priv->wpa_ie, priv->wpa_ie_len); + +out: + orinoco_unlock(priv, &flags); + return err; +} + +static int orinoco_ioctl_set_mlme(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + struct iw_mlme *mlme = (struct iw_mlme *)extra; + unsigned long flags; + int ret = 0; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + switch (mlme->cmd) { + case IW_MLME_DEAUTH: + /* silently ignore */ + break; + + case IW_MLME_DISASSOC: + + ret = orinoco_hw_disassociate(priv, mlme->addr.sa_data, + mlme->reason_code); + break; + + default: + ret = -EOPNOTSUPP; + } + + orinoco_unlock(priv, &flags); + return ret; +} + +static int orinoco_ioctl_reset(struct net_device *dev, + struct iw_request_info *info, + void *wrqu, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + if (info->cmd == (SIOCIWFIRSTPRIV + 0x1)) { + printk(KERN_DEBUG "%s: Forcing reset!\n", dev->name); + + /* Firmware reset */ + orinoco_reset(&priv->reset_work); + } else { + printk(KERN_DEBUG "%s: Force scheduling reset!\n", dev->name); + + schedule_work(&priv->reset_work); + } + + return 0; +} + +static int orinoco_ioctl_setibssport(struct net_device *dev, + struct iw_request_info *info, + void *wrqu, + char *extra) + +{ + struct orinoco_private *priv = ndev_priv(dev); + int val = *((int *) extra); + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + priv->ibss_port = val; + + /* Actually update the mode we are using */ + set_port_type(priv); + + orinoco_unlock(priv, &flags); + return -EINPROGRESS; /* Call commit handler */ +} + +static int orinoco_ioctl_getibssport(struct net_device *dev, + struct iw_request_info *info, + void *wrqu, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + int *val = (int *) extra; + + *val = priv->ibss_port; + return 0; +} + +static int orinoco_ioctl_setport3(struct net_device *dev, + struct iw_request_info *info, + void *wrqu, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + int val = *((int *) extra); + int err = 0; + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + switch (val) { + case 0: /* Try to do IEEE ad-hoc mode */ + if (!priv->has_ibss) { + err = -EINVAL; + break; + } + priv->prefer_port3 = 0; + + break; + + case 1: /* Try to do Lucent proprietary ad-hoc mode */ + if (!priv->has_port3) { + err = -EINVAL; + break; + } + priv->prefer_port3 = 1; + break; + + default: + err = -EINVAL; + } + + if (!err) { + /* Actually update the mode we are using */ + set_port_type(priv); + err = -EINPROGRESS; + } + + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_ioctl_getport3(struct net_device *dev, + struct iw_request_info *info, + void *wrqu, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + int *val = (int *) extra; + + *val = priv->prefer_port3; + return 0; +} + +static int orinoco_ioctl_setpreamble(struct net_device *dev, + struct iw_request_info *info, + void *wrqu, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + unsigned long flags; + int val; + + if (!priv->has_preamble) + return -EOPNOTSUPP; + + /* 802.11b has recently defined some short preamble. + * Basically, the Phy header has been reduced in size. + * This increase performance, especially at high rates + * (the preamble is transmitted at 1Mb/s), unfortunately + * this give compatibility troubles... - Jean II */ + val = *((int *) extra); + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + if (val) + priv->preamble = 1; + else + priv->preamble = 0; + + orinoco_unlock(priv, &flags); + + return -EINPROGRESS; /* Call commit handler */ +} + +static int orinoco_ioctl_getpreamble(struct net_device *dev, + struct iw_request_info *info, + void *wrqu, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + int *val = (int *) extra; + + if (!priv->has_preamble) + return -EOPNOTSUPP; + + *val = priv->preamble; + return 0; +} + +/* ioctl interface to hermes_read_ltv() + * To use with iwpriv, pass the RID as the token argument, e.g. + * iwpriv get_rid [0xfc00] + * At least Wireless Tools 25 is required to use iwpriv. + * For Wireless Tools 25 and 26 append "dummy" are the end. */ +static int orinoco_ioctl_getrid(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *data, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + struct hermes *hw = &priv->hw; + int rid = data->flags; + u16 length; + int err; + unsigned long flags; + + /* It's a "get" function, but we don't want users to access the + * WEP key and other raw firmware data */ + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + if (rid < 0xfc00 || rid > 0xffff) + return -EINVAL; + + if (orinoco_lock(priv, &flags) != 0) + return -EBUSY; + + err = hw->ops->read_ltv(hw, USER_BAP, rid, MAX_RID_LEN, &length, + extra); + if (err) + goto out; + + data->length = min_t(u16, HERMES_RECLEN_TO_BYTES(length), + MAX_RID_LEN); + + out: + orinoco_unlock(priv, &flags); + return err; +} + + +/* Commit handler, called after set operations */ +static int orinoco_ioctl_commit(struct net_device *dev, + struct iw_request_info *info, + void *wrqu, + char *extra) +{ + struct orinoco_private *priv = ndev_priv(dev); + unsigned long flags; + int err = 0; + + if (!priv->open) + return 0; + + if (orinoco_lock(priv, &flags) != 0) + return err; + + err = orinoco_commit(priv); + + orinoco_unlock(priv, &flags); + return err; +} + +static const struct iw_priv_args orinoco_privtab[] = { + { SIOCIWFIRSTPRIV + 0x0, 0, 0, "force_reset" }, + { SIOCIWFIRSTPRIV + 0x1, 0, 0, "card_reset" }, + { SIOCIWFIRSTPRIV + 0x2, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + 0, "set_port3" }, + { SIOCIWFIRSTPRIV + 0x3, 0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "get_port3" }, + { SIOCIWFIRSTPRIV + 0x4, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + 0, "set_preamble" }, + { SIOCIWFIRSTPRIV + 0x5, 0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "get_preamble" }, + { SIOCIWFIRSTPRIV + 0x6, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + 0, "set_ibssport" }, + { SIOCIWFIRSTPRIV + 0x7, 0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "get_ibssport" }, + { SIOCIWFIRSTPRIV + 0x9, 0, IW_PRIV_TYPE_BYTE | MAX_RID_LEN, + "get_rid" }, +}; + + +/* + * Structures to export the Wireless Handlers + */ + +static const iw_handler orinoco_handler[] = { + IW_HANDLER(SIOCSIWCOMMIT, (iw_handler)orinoco_ioctl_commit), + IW_HANDLER(SIOCGIWNAME, (iw_handler)cfg80211_wext_giwname), + IW_HANDLER(SIOCSIWFREQ, (iw_handler)orinoco_ioctl_setfreq), + IW_HANDLER(SIOCGIWFREQ, (iw_handler)orinoco_ioctl_getfreq), + IW_HANDLER(SIOCSIWMODE, (iw_handler)cfg80211_wext_siwmode), + IW_HANDLER(SIOCGIWMODE, (iw_handler)cfg80211_wext_giwmode), + IW_HANDLER(SIOCSIWSENS, (iw_handler)orinoco_ioctl_setsens), + IW_HANDLER(SIOCGIWSENS, (iw_handler)orinoco_ioctl_getsens), + IW_HANDLER(SIOCGIWRANGE, (iw_handler)cfg80211_wext_giwrange), + IW_HANDLER(SIOCSIWSPY, iw_handler_set_spy), + IW_HANDLER(SIOCGIWSPY, iw_handler_get_spy), + IW_HANDLER(SIOCSIWTHRSPY, iw_handler_set_thrspy), + IW_HANDLER(SIOCGIWTHRSPY, iw_handler_get_thrspy), + IW_HANDLER(SIOCSIWAP, (iw_handler)orinoco_ioctl_setwap), + IW_HANDLER(SIOCGIWAP, (iw_handler)orinoco_ioctl_getwap), + IW_HANDLER(SIOCSIWSCAN, (iw_handler)cfg80211_wext_siwscan), + IW_HANDLER(SIOCGIWSCAN, (iw_handler)cfg80211_wext_giwscan), + IW_HANDLER(SIOCSIWESSID, (iw_handler)orinoco_ioctl_setessid), + IW_HANDLER(SIOCGIWESSID, (iw_handler)orinoco_ioctl_getessid), + IW_HANDLER(SIOCSIWRATE, (iw_handler)orinoco_ioctl_setrate), + IW_HANDLER(SIOCGIWRATE, (iw_handler)orinoco_ioctl_getrate), + IW_HANDLER(SIOCSIWRTS, (iw_handler)cfg80211_wext_siwrts), + IW_HANDLER(SIOCGIWRTS, (iw_handler)cfg80211_wext_giwrts), + IW_HANDLER(SIOCSIWFRAG, (iw_handler)cfg80211_wext_siwfrag), + IW_HANDLER(SIOCGIWFRAG, (iw_handler)cfg80211_wext_giwfrag), + IW_HANDLER(SIOCGIWRETRY, (iw_handler)cfg80211_wext_giwretry), + IW_HANDLER(SIOCSIWENCODE, (iw_handler)orinoco_ioctl_setiwencode), + IW_HANDLER(SIOCGIWENCODE, (iw_handler)orinoco_ioctl_getiwencode), + IW_HANDLER(SIOCSIWPOWER, (iw_handler)orinoco_ioctl_setpower), + IW_HANDLER(SIOCGIWPOWER, (iw_handler)orinoco_ioctl_getpower), + IW_HANDLER(SIOCSIWGENIE, orinoco_ioctl_set_genie), + IW_HANDLER(SIOCGIWGENIE, orinoco_ioctl_get_genie), + IW_HANDLER(SIOCSIWMLME, orinoco_ioctl_set_mlme), + IW_HANDLER(SIOCSIWAUTH, orinoco_ioctl_set_auth), + IW_HANDLER(SIOCGIWAUTH, orinoco_ioctl_get_auth), + IW_HANDLER(SIOCSIWENCODEEXT, orinoco_ioctl_set_encodeext), + IW_HANDLER(SIOCGIWENCODEEXT, orinoco_ioctl_get_encodeext), +}; + + +/* + Added typecasting since we no longer use iwreq_data -- Moustafa + */ +static const iw_handler orinoco_private_handler[] = { + [0] = (iw_handler)orinoco_ioctl_reset, + [1] = (iw_handler)orinoco_ioctl_reset, + [2] = (iw_handler)orinoco_ioctl_setport3, + [3] = (iw_handler)orinoco_ioctl_getport3, + [4] = (iw_handler)orinoco_ioctl_setpreamble, + [5] = (iw_handler)orinoco_ioctl_getpreamble, + [6] = (iw_handler)orinoco_ioctl_setibssport, + [7] = (iw_handler)orinoco_ioctl_getibssport, + [9] = (iw_handler)orinoco_ioctl_getrid, +}; + +const struct iw_handler_def orinoco_handler_def = { + .num_standard = ARRAY_SIZE(orinoco_handler), + .num_private = ARRAY_SIZE(orinoco_private_handler), + .num_private_args = ARRAY_SIZE(orinoco_privtab), + .standard = orinoco_handler, + .private = orinoco_private_handler, + .private_args = orinoco_privtab, + .get_wireless_stats = orinoco_get_wireless_stats, +}; diff --git a/drivers/net/wireless/orinoco/wext.h b/drivers/net/wireless/orinoco/wext.h new file mode 100644 index 0000000..1479f4e --- /dev/null +++ b/drivers/net/wireless/orinoco/wext.h @@ -0,0 +1,13 @@ +/* Wireless extensions support. + * + * See copyright notice in main.c + */ +#ifndef _ORINOCO_WEXT_H_ +#define _ORINOCO_WEXT_H_ + +#include + +/* Structure defining all our WEXT handlers */ +extern const struct iw_handler_def orinoco_handler_def; + +#endif /* _ORINOCO_WEXT_H_ */ diff --git a/drivers/net/wireless/p54/Kconfig b/drivers/net/wireless/p54/Kconfig new file mode 100644 index 0000000..caea05e --- /dev/null +++ b/drivers/net/wireless/p54/Kconfig @@ -0,0 +1,76 @@ +config P54_COMMON + tristate "Softmac Prism54 support" + depends on m + depends on MAC80211 + select BACKPORT_FW_LOADER + depends on CRC_CCITT + ---help--- + This is common code for isl38xx/stlc45xx based modules. + This module does nothing by itself - the USB/PCI/SPI front-ends + also need to be enabled in order to support any devices. + + These devices require softmac firmware which can be found at + + + If you choose to build a module, it'll be called p54common. + +config P54_USB + tristate "Prism54 USB support" + depends on m + depends on P54_COMMON && USB + depends on CRC32 + ---help--- + This driver is for USB isl38xx based wireless cards. + + These devices require softmac firmware which can be found at + + + If you choose to build a module, it'll be called p54usb. + +config P54_PCI + tristate "Prism54 PCI support" + depends on m + depends on P54_COMMON && PCI + ---help--- + This driver is for PCI isl38xx based wireless cards. + This driver supports most devices that are supported by the + fullmac prism54 driver plus many devices which are not + supported by the fullmac driver/firmware. + + This driver requires softmac firmware which can be found at + + + If you choose to build a module, it'll be called p54pci. + +config P54_SPI + depends on !BACKPORT_KERNEL_2_6_25 + tristate "Prism54 SPI (stlc45xx) support" + depends on m + depends on P54_COMMON && SPI_MASTER && GENERIC_HARDIRQS + ---help--- + This driver is for stlc4550 or stlc4560 based wireless chips + such as Nokia's N800/N810 Portable Internet Tablet. + + If you choose to build a module, it'll be called p54spi. + +config P54_SPI_DEFAULT_EEPROM + bool "Include fallback EEPROM blob" + depends on P54_SPI + default n + ---help--- + Unlike the PCI or USB devices, the SPI variants don't have + a dedicated EEPROM chip to store all device specific values + for calibration, country and interface settings. + + The driver will try to load the image "3826.eeprom", if the + file is put at the right place. (usually /lib/firmware.) + + Only if this request fails, this option will provide a + backup set of generic values to get the device working. + + Enabling this option adds about 4k to p54spi. + +config P54_LEDS + bool + depends on P54_COMMON && MAC80211_LEDS && (LEDS_CLASS = y || LEDS_CLASS = P54_COMMON) + default y diff --git a/drivers/net/wireless/p54/Makefile b/drivers/net/wireless/p54/Makefile new file mode 100644 index 0000000..31a79fe --- /dev/null +++ b/drivers/net/wireless/p54/Makefile @@ -0,0 +1,7 @@ +p54common-objs := eeprom.o fwio.o txrx.o main.o +p54common-$(CPTCFG_P54_LEDS) += led.o + +obj-$(CPTCFG_P54_COMMON) += p54common.o +obj-$(CPTCFG_P54_USB) += p54usb.o +obj-$(CPTCFG_P54_PCI) += p54pci.o +obj-$(CPTCFG_P54_SPI) += p54spi.o diff --git a/drivers/net/wireless/p54/eeprom.c b/drivers/net/wireless/p54/eeprom.c new file mode 100644 index 0000000..d43e374 --- /dev/null +++ b/drivers/net/wireless/p54/eeprom.c @@ -0,0 +1,983 @@ +/* + * EEPROM parser code for mac80211 Prism54 drivers + * + * Copyright (c) 2006, Michael Wu + * Copyright (c) 2007-2009, Christian Lamparter + * Copyright 2008, Johannes Berg + * + * Based on: + * - the islsm (softmac prism54) driver, which is: + * Copyright 2004-2006 Jean-Baptiste Note , et al. + * - stlc45xx driver + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "p54.h" +#include "eeprom.h" +#include "lmac.h" + +static struct ieee80211_rate p54_bgrates[] = { + { .bitrate = 10, .hw_value = 0, }, + { .bitrate = 20, .hw_value = 1, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 55, .hw_value = 2, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 110, .hw_value = 3, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 60, .hw_value = 4, }, + { .bitrate = 90, .hw_value = 5, }, + { .bitrate = 120, .hw_value = 6, }, + { .bitrate = 180, .hw_value = 7, }, + { .bitrate = 240, .hw_value = 8, }, + { .bitrate = 360, .hw_value = 9, }, + { .bitrate = 480, .hw_value = 10, }, + { .bitrate = 540, .hw_value = 11, }, +}; + +static struct ieee80211_rate p54_arates[] = { + { .bitrate = 60, .hw_value = 4, }, + { .bitrate = 90, .hw_value = 5, }, + { .bitrate = 120, .hw_value = 6, }, + { .bitrate = 180, .hw_value = 7, }, + { .bitrate = 240, .hw_value = 8, }, + { .bitrate = 360, .hw_value = 9, }, + { .bitrate = 480, .hw_value = 10, }, + { .bitrate = 540, .hw_value = 11, }, +}; + +static struct p54_rssi_db_entry p54_rssi_default = { + /* + * The defaults are taken from usb-logs of the + * vendor driver. So, they should be safe to + * use in case we can't get a match from the + * rssi <-> dBm conversion database. + */ + .mul = 130, + .add = -398, +}; + +#define CHAN_HAS_CAL BIT(0) +#define CHAN_HAS_LIMIT BIT(1) +#define CHAN_HAS_CURVE BIT(2) +#define CHAN_HAS_ALL (CHAN_HAS_CAL | CHAN_HAS_LIMIT | CHAN_HAS_CURVE) + +struct p54_channel_entry { + u16 freq; + u16 data; + int index; + int max_power; + enum ieee80211_band band; +}; + +struct p54_channel_list { + struct p54_channel_entry *channels; + size_t entries; + size_t max_entries; + size_t band_channel_num[IEEE80211_NUM_BANDS]; +}; + +static int p54_get_band_from_freq(u16 freq) +{ + /* FIXME: sync these values with the 802.11 spec */ + + if ((freq >= 2412) && (freq <= 2484)) + return IEEE80211_BAND_2GHZ; + + if ((freq >= 4920) && (freq <= 5825)) + return IEEE80211_BAND_5GHZ; + + return -1; +} + +static int same_band(u16 freq, u16 freq2) +{ + return p54_get_band_from_freq(freq) == p54_get_band_from_freq(freq2); +} + +static int p54_compare_channels(const void *_a, + const void *_b) +{ + const struct p54_channel_entry *a = _a; + const struct p54_channel_entry *b = _b; + + return a->freq - b->freq; +} + +static int p54_compare_rssichan(const void *_a, + const void *_b) +{ + const struct p54_rssi_db_entry *a = _a; + const struct p54_rssi_db_entry *b = _b; + + return a->freq - b->freq; +} + +static int p54_fill_band_bitrates(struct ieee80211_hw *dev, + struct ieee80211_supported_band *band_entry, + enum ieee80211_band band) +{ + /* TODO: generate rate array dynamically */ + + switch (band) { + case IEEE80211_BAND_2GHZ: + band_entry->bitrates = p54_bgrates; + band_entry->n_bitrates = ARRAY_SIZE(p54_bgrates); + break; + case IEEE80211_BAND_5GHZ: + band_entry->bitrates = p54_arates; + band_entry->n_bitrates = ARRAY_SIZE(p54_arates); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int p54_generate_band(struct ieee80211_hw *dev, + struct p54_channel_list *list, + unsigned int *chan_num, + enum ieee80211_band band) +{ + struct p54_common *priv = dev->priv; + struct ieee80211_supported_band *tmp, *old; + unsigned int i, j; + int ret = -ENOMEM; + + if ((!list->entries) || (!list->band_channel_num[band])) + return -EINVAL; + + tmp = kzalloc(sizeof(*tmp), GFP_KERNEL); + if (!tmp) + goto err_out; + + tmp->channels = kzalloc(sizeof(struct ieee80211_channel) * + list->band_channel_num[band], GFP_KERNEL); + if (!tmp->channels) + goto err_out; + + ret = p54_fill_band_bitrates(dev, tmp, band); + if (ret) + goto err_out; + + for (i = 0, j = 0; (j < list->band_channel_num[band]) && + (i < list->entries); i++) { + struct p54_channel_entry *chan = &list->channels[i]; + struct ieee80211_channel *dest = &tmp->channels[j]; + + if (chan->band != band) + continue; + + if (chan->data != CHAN_HAS_ALL) { + wiphy_err(dev->wiphy, "%s%s%s is/are missing for " + "channel:%d [%d MHz].\n", + (chan->data & CHAN_HAS_CAL ? "" : + " [iqauto calibration data]"), + (chan->data & CHAN_HAS_LIMIT ? "" : + " [output power limits]"), + (chan->data & CHAN_HAS_CURVE ? "" : + " [curve data]"), + chan->index, chan->freq); + continue; + } + + dest->band = chan->band; + dest->center_freq = chan->freq; + dest->max_power = chan->max_power; + priv->survey[*chan_num].channel = &tmp->channels[j]; + priv->survey[*chan_num].filled = SURVEY_INFO_NOISE_DBM | + SURVEY_INFO_CHANNEL_TIME | + SURVEY_INFO_CHANNEL_TIME_BUSY | + SURVEY_INFO_CHANNEL_TIME_TX; + dest->hw_value = (*chan_num); + j++; + (*chan_num)++; + } + + if (j == 0) { + wiphy_err(dev->wiphy, "Disabling totally damaged %d GHz band\n", + (band == IEEE80211_BAND_2GHZ) ? 2 : 5); + + ret = -ENODATA; + goto err_out; + } + + tmp->n_channels = j; + old = priv->band_table[band]; + priv->band_table[band] = tmp; + if (old) { + kfree(old->channels); + kfree(old); + } + + return 0; + +err_out: + if (tmp) { + kfree(tmp->channels); + kfree(tmp); + } + + return ret; +} + +static struct p54_channel_entry *p54_update_channel_param(struct p54_channel_list *list, + u16 freq, u16 data) +{ + int i; + struct p54_channel_entry *entry = NULL; + + /* + * usually all lists in the eeprom are mostly sorted. + * so it's very likely that the entry we are looking for + * is right at the end of the list + */ + for (i = list->entries; i >= 0; i--) { + if (freq == list->channels[i].freq) { + entry = &list->channels[i]; + break; + } + } + + if ((i < 0) && (list->entries < list->max_entries)) { + /* entry does not exist yet. Initialize a new one. */ + int band = p54_get_band_from_freq(freq); + + /* + * filter out frequencies which don't belong into + * any supported band. + */ + if (band >= 0) { + i = list->entries++; + list->band_channel_num[band]++; + + entry = &list->channels[i]; + entry->freq = freq; + entry->band = band; + entry->index = ieee80211_frequency_to_channel(freq); + entry->max_power = 0; + entry->data = 0; + } + } + + if (entry) + entry->data |= data; + + return entry; +} + +static int p54_get_maxpower(struct p54_common *priv, void *data) +{ + switch (priv->rxhw & PDR_SYNTH_FRONTEND_MASK) { + case PDR_SYNTH_FRONTEND_LONGBOW: { + struct pda_channel_output_limit_longbow *pda = data; + int j; + u16 rawpower = 0; + pda = data; + for (j = 0; j < ARRAY_SIZE(pda->point); j++) { + struct pda_channel_output_limit_point_longbow *point = + &pda->point[j]; + rawpower = max_t(u16, + rawpower, le16_to_cpu(point->val_qpsk)); + rawpower = max_t(u16, + rawpower, le16_to_cpu(point->val_bpsk)); + rawpower = max_t(u16, + rawpower, le16_to_cpu(point->val_16qam)); + rawpower = max_t(u16, + rawpower, le16_to_cpu(point->val_64qam)); + } + /* longbow seems to use 1/16 dBm units */ + return rawpower / 16; + } + + case PDR_SYNTH_FRONTEND_DUETTE3: + case PDR_SYNTH_FRONTEND_DUETTE2: + case PDR_SYNTH_FRONTEND_FRISBEE: + case PDR_SYNTH_FRONTEND_XBOW: { + struct pda_channel_output_limit *pda = data; + u8 rawpower = 0; + rawpower = max(rawpower, pda->val_qpsk); + rawpower = max(rawpower, pda->val_bpsk); + rawpower = max(rawpower, pda->val_16qam); + rawpower = max(rawpower, pda->val_64qam); + /* raw values are in 1/4 dBm units */ + return rawpower / 4; + } + + default: + return 20; + } +} + +static int p54_generate_channel_lists(struct ieee80211_hw *dev) +{ + struct p54_common *priv = dev->priv; + struct p54_channel_list *list; + unsigned int i, j, k, max_channel_num; + int ret = 0; + u16 freq; + + if ((priv->iq_autocal_len != priv->curve_data->entries) || + (priv->iq_autocal_len != priv->output_limit->entries)) + wiphy_err(dev->wiphy, + "Unsupported or damaged EEPROM detected. " + "You may not be able to use all channels.\n"); + + max_channel_num = max_t(unsigned int, priv->output_limit->entries, + priv->iq_autocal_len); + max_channel_num = max_t(unsigned int, max_channel_num, + priv->curve_data->entries); + + list = kzalloc(sizeof(*list), GFP_KERNEL); + if (!list) { + ret = -ENOMEM; + goto free; + } + priv->chan_num = max_channel_num; + priv->survey = kzalloc(sizeof(struct survey_info) * max_channel_num, + GFP_KERNEL); + if (!priv->survey) { + ret = -ENOMEM; + goto free; + } + + list->max_entries = max_channel_num; + list->channels = kzalloc(sizeof(struct p54_channel_entry) * + max_channel_num, GFP_KERNEL); + if (!list->channels) { + ret = -ENOMEM; + goto free; + } + + for (i = 0; i < max_channel_num; i++) { + if (i < priv->iq_autocal_len) { + freq = le16_to_cpu(priv->iq_autocal[i].freq); + p54_update_channel_param(list, freq, CHAN_HAS_CAL); + } + + if (i < priv->output_limit->entries) { + struct p54_channel_entry *tmp; + + void *data = (void *) ((unsigned long) i * + priv->output_limit->entry_size + + priv->output_limit->offset + + priv->output_limit->data); + + freq = le16_to_cpup((__le16 *) data); + tmp = p54_update_channel_param(list, freq, + CHAN_HAS_LIMIT); + if (tmp) { + tmp->max_power = p54_get_maxpower(priv, data); + } + } + + if (i < priv->curve_data->entries) { + freq = le16_to_cpup((__le16 *) (i * + priv->curve_data->entry_size + + priv->curve_data->offset + + priv->curve_data->data)); + + p54_update_channel_param(list, freq, CHAN_HAS_CURVE); + } + } + + /* sort the channel list by frequency */ + sort(list->channels, list->entries, sizeof(struct p54_channel_entry), + p54_compare_channels, NULL); + + k = 0; + for (i = 0, j = 0; i < IEEE80211_NUM_BANDS; i++) { + if (p54_generate_band(dev, list, &k, i) == 0) + j++; + } + if (j == 0) { + /* no useable band available. */ + ret = -EINVAL; + } + +free: + if (list) { + kfree(list->channels); + kfree(list); + } + if (ret) { + kfree(priv->survey); + priv->survey = NULL; + } + + return ret; +} + +static int p54_convert_rev0(struct ieee80211_hw *dev, + struct pda_pa_curve_data *curve_data) +{ + struct p54_common *priv = dev->priv; + struct p54_pa_curve_data_sample *dst; + struct pda_pa_curve_data_sample_rev0 *src; + size_t cd_len = sizeof(*curve_data) + + (curve_data->points_per_channel*sizeof(*dst) + 2) * + curve_data->channels; + unsigned int i, j; + void *source, *target; + + priv->curve_data = kmalloc(sizeof(*priv->curve_data) + cd_len, + GFP_KERNEL); + if (!priv->curve_data) + return -ENOMEM; + + priv->curve_data->entries = curve_data->channels; + priv->curve_data->entry_size = sizeof(__le16) + + sizeof(*dst) * curve_data->points_per_channel; + priv->curve_data->offset = offsetof(struct pda_pa_curve_data, data); + priv->curve_data->len = cd_len; + memcpy(priv->curve_data->data, curve_data, sizeof(*curve_data)); + source = curve_data->data; + target = ((struct pda_pa_curve_data *) priv->curve_data->data)->data; + for (i = 0; i < curve_data->channels; i++) { + __le16 *freq = source; + source += sizeof(__le16); + *((__le16 *)target) = *freq; + target += sizeof(__le16); + for (j = 0; j < curve_data->points_per_channel; j++) { + dst = target; + src = source; + + dst->rf_power = src->rf_power; + dst->pa_detector = src->pa_detector; + dst->data_64qam = src->pcv; + /* "invent" the points for the other modulations */ +#define SUB(x, y) (u8)(((x) - (y)) > (x) ? 0 : (x) - (y)) + dst->data_16qam = SUB(src->pcv, 12); + dst->data_qpsk = SUB(dst->data_16qam, 12); + dst->data_bpsk = SUB(dst->data_qpsk, 12); + dst->data_barker = SUB(dst->data_bpsk, 14); +#undef SUB + target += sizeof(*dst); + source += sizeof(*src); + } + } + + return 0; +} + +static int p54_convert_rev1(struct ieee80211_hw *dev, + struct pda_pa_curve_data *curve_data) +{ + struct p54_common *priv = dev->priv; + struct p54_pa_curve_data_sample *dst; + struct pda_pa_curve_data_sample_rev1 *src; + size_t cd_len = sizeof(*curve_data) + + (curve_data->points_per_channel*sizeof(*dst) + 2) * + curve_data->channels; + unsigned int i, j; + void *source, *target; + + priv->curve_data = kzalloc(cd_len + sizeof(*priv->curve_data), + GFP_KERNEL); + if (!priv->curve_data) + return -ENOMEM; + + priv->curve_data->entries = curve_data->channels; + priv->curve_data->entry_size = sizeof(__le16) + + sizeof(*dst) * curve_data->points_per_channel; + priv->curve_data->offset = offsetof(struct pda_pa_curve_data, data); + priv->curve_data->len = cd_len; + memcpy(priv->curve_data->data, curve_data, sizeof(*curve_data)); + source = curve_data->data; + target = ((struct pda_pa_curve_data *) priv->curve_data->data)->data; + for (i = 0; i < curve_data->channels; i++) { + __le16 *freq = source; + source += sizeof(__le16); + *((__le16 *)target) = *freq; + target += sizeof(__le16); + for (j = 0; j < curve_data->points_per_channel; j++) { + memcpy(target, source, sizeof(*src)); + + target += sizeof(*dst); + source += sizeof(*src); + } + source++; + } + + return 0; +} + +static const char *p54_rf_chips[] = { "INVALID-0", "Duette3", "Duette2", + "Frisbee", "Xbow", "Longbow", "INVALID-6", "INVALID-7" }; + +static int p54_parse_rssical(struct ieee80211_hw *dev, + u8 *data, int len, u16 type) +{ + struct p54_common *priv = dev->priv; + struct p54_rssi_db_entry *entry; + size_t db_len, entries; + int offset = 0, i; + + if (type != PDR_RSSI_LINEAR_APPROXIMATION_EXTENDED) { + entries = (type == PDR_RSSI_LINEAR_APPROXIMATION) ? 1 : 2; + if (len != sizeof(struct pda_rssi_cal_entry) * entries) { + wiphy_err(dev->wiphy, "rssical size mismatch.\n"); + goto err_data; + } + } else { + /* + * Some devices (Dell 1450 USB, Xbow 5GHz card, etc...) + * have an empty two byte header. + */ + if (*((__le16 *)&data[offset]) == cpu_to_le16(0)) + offset += 2; + + entries = (len - offset) / + sizeof(struct pda_rssi_cal_ext_entry); + + if (len < offset || + (len - offset) % sizeof(struct pda_rssi_cal_ext_entry) || + entries == 0) { + wiphy_err(dev->wiphy, "invalid rssi database.\n"); + goto err_data; + } + } + + db_len = sizeof(*entry) * entries; + priv->rssi_db = kzalloc(db_len + sizeof(*priv->rssi_db), GFP_KERNEL); + if (!priv->rssi_db) + return -ENOMEM; + + priv->rssi_db->offset = 0; + priv->rssi_db->entries = entries; + priv->rssi_db->entry_size = sizeof(*entry); + priv->rssi_db->len = db_len; + + entry = (void *)((unsigned long)priv->rssi_db->data + priv->rssi_db->offset); + if (type == PDR_RSSI_LINEAR_APPROXIMATION_EXTENDED) { + struct pda_rssi_cal_ext_entry *cal = (void *) &data[offset]; + + for (i = 0; i < entries; i++) { + entry[i].freq = le16_to_cpu(cal[i].freq); + entry[i].mul = (s16) le16_to_cpu(cal[i].mul); + entry[i].add = (s16) le16_to_cpu(cal[i].add); + } + } else { + struct pda_rssi_cal_entry *cal = (void *) &data[offset]; + + for (i = 0; i < entries; i++) { + u16 freq = 0; + switch (i) { + case IEEE80211_BAND_2GHZ: + freq = 2437; + break; + case IEEE80211_BAND_5GHZ: + freq = 5240; + break; + } + + entry[i].freq = freq; + entry[i].mul = (s16) le16_to_cpu(cal[i].mul); + entry[i].add = (s16) le16_to_cpu(cal[i].add); + } + } + + /* sort the list by channel frequency */ + sort(entry, entries, sizeof(*entry), p54_compare_rssichan, NULL); + return 0; + +err_data: + wiphy_err(dev->wiphy, + "rssi calibration data packing type:(%x) len:%d.\n", + type, len); + + print_hex_dump_bytes("rssical:", DUMP_PREFIX_NONE, data, len); + + wiphy_err(dev->wiphy, "please report this issue.\n"); + return -EINVAL; +} + +struct p54_rssi_db_entry *p54_rssi_find(struct p54_common *priv, const u16 freq) +{ + struct p54_rssi_db_entry *entry; + int i, found = -1; + + if (!priv->rssi_db) + return &p54_rssi_default; + + entry = (void *)(priv->rssi_db->data + priv->rssi_db->offset); + for (i = 0; i < priv->rssi_db->entries; i++) { + if (!same_band(freq, entry[i].freq)) + continue; + + if (found == -1) { + found = i; + continue; + } + + /* nearest match */ + if (abs(freq - entry[i].freq) < + abs(freq - entry[found].freq)) { + found = i; + continue; + } else { + break; + } + } + + return found < 0 ? &p54_rssi_default : &entry[found]; +} + +static void p54_parse_default_country(struct ieee80211_hw *dev, + void *data, int len) +{ + struct pda_country *country; + + if (len != sizeof(*country)) { + wiphy_err(dev->wiphy, + "found possible invalid default country eeprom entry. (entry size: %d)\n", + len); + + print_hex_dump_bytes("country:", DUMP_PREFIX_NONE, + data, len); + + wiphy_err(dev->wiphy, "please report this issue.\n"); + return; + } + + country = (struct pda_country *) data; + if (country->flags == PDR_COUNTRY_CERT_CODE_PSEUDO) + regulatory_hint(dev->wiphy, country->alpha2); + else { + /* TODO: + * write a shared/common function that converts + * "Regulatory domain codes" (802.11-2007 14.8.2.2) + * into ISO/IEC 3166-1 alpha2 for regulatory_hint. + */ + } +} + +static int p54_convert_output_limits(struct ieee80211_hw *dev, + u8 *data, size_t len) +{ + struct p54_common *priv = dev->priv; + + if (len < 2) + return -EINVAL; + + if (data[0] != 0) { + wiphy_err(dev->wiphy, "unknown output power db revision:%x\n", + data[0]); + return -EINVAL; + } + + if (2 + data[1] * sizeof(struct pda_channel_output_limit) > len) + return -EINVAL; + + priv->output_limit = kmalloc(data[1] * + sizeof(struct pda_channel_output_limit) + + sizeof(*priv->output_limit), GFP_KERNEL); + + if (!priv->output_limit) + return -ENOMEM; + + priv->output_limit->offset = 0; + priv->output_limit->entries = data[1]; + priv->output_limit->entry_size = + sizeof(struct pda_channel_output_limit); + priv->output_limit->len = priv->output_limit->entry_size * + priv->output_limit->entries + + priv->output_limit->offset; + + memcpy(priv->output_limit->data, &data[2], + data[1] * sizeof(struct pda_channel_output_limit)); + + return 0; +} + +static struct p54_cal_database *p54_convert_db(struct pda_custom_wrapper *src, + size_t total_len) +{ + struct p54_cal_database *dst; + size_t payload_len, entries, entry_size, offset; + + payload_len = le16_to_cpu(src->len); + entries = le16_to_cpu(src->entries); + entry_size = le16_to_cpu(src->entry_size); + offset = le16_to_cpu(src->offset); + if (((entries * entry_size + offset) != payload_len) || + (payload_len + sizeof(*src) != total_len)) + return NULL; + + dst = kmalloc(sizeof(*dst) + payload_len, GFP_KERNEL); + if (!dst) + return NULL; + + dst->entries = entries; + dst->entry_size = entry_size; + dst->offset = offset; + dst->len = payload_len; + + memcpy(dst->data, src->data, payload_len); + return dst; +} + +int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len) +{ + struct p54_common *priv = dev->priv; + struct eeprom_pda_wrap *wrap; + struct pda_entry *entry; + unsigned int data_len, entry_len; + void *tmp; + int err; + u8 *end = (u8 *)eeprom + len; + u16 synth = 0; + u16 crc16 = ~0; + + wrap = (struct eeprom_pda_wrap *) eeprom; + entry = (void *)wrap->data + le16_to_cpu(wrap->len); + + /* verify that at least the entry length/code fits */ + while ((u8 *)entry <= end - sizeof(*entry)) { + entry_len = le16_to_cpu(entry->len); + data_len = ((entry_len - 1) << 1); + + /* abort if entry exceeds whole structure */ + if ((u8 *)entry + sizeof(*entry) + data_len > end) + break; + + switch (le16_to_cpu(entry->code)) { + case PDR_MAC_ADDRESS: + if (data_len != ETH_ALEN) + break; + SET_IEEE80211_PERM_ADDR(dev, entry->data); + break; + case PDR_PRISM_PA_CAL_OUTPUT_POWER_LIMITS: + if (priv->output_limit) + break; + err = p54_convert_output_limits(dev, entry->data, + data_len); + if (err) + goto err; + break; + case PDR_PRISM_PA_CAL_CURVE_DATA: { + struct pda_pa_curve_data *curve_data = + (struct pda_pa_curve_data *)entry->data; + if (data_len < sizeof(*curve_data)) { + err = -EINVAL; + goto err; + } + + switch (curve_data->cal_method_rev) { + case 0: + err = p54_convert_rev0(dev, curve_data); + break; + case 1: + err = p54_convert_rev1(dev, curve_data); + break; + default: + wiphy_err(dev->wiphy, + "unknown curve data revision %d\n", + curve_data->cal_method_rev); + err = -ENODEV; + break; + } + if (err) + goto err; + } + break; + case PDR_PRISM_ZIF_TX_IQ_CALIBRATION: + priv->iq_autocal = kmemdup(entry->data, data_len, + GFP_KERNEL); + if (!priv->iq_autocal) { + err = -ENOMEM; + goto err; + } + + priv->iq_autocal_len = data_len / sizeof(struct pda_iq_autocal_entry); + break; + case PDR_DEFAULT_COUNTRY: + p54_parse_default_country(dev, entry->data, data_len); + break; + case PDR_INTERFACE_LIST: + tmp = entry->data; + while ((u8 *)tmp < entry->data + data_len) { + struct exp_if *exp_if = tmp; + if (exp_if->if_id == cpu_to_le16(IF_ID_ISL39000)) + synth = le16_to_cpu(exp_if->variant); + tmp += sizeof(*exp_if); + } + break; + case PDR_HARDWARE_PLATFORM_COMPONENT_ID: + if (data_len < 2) + break; + priv->version = *(u8 *)(entry->data + 1); + break; + case PDR_RSSI_LINEAR_APPROXIMATION: + case PDR_RSSI_LINEAR_APPROXIMATION_DUAL_BAND: + case PDR_RSSI_LINEAR_APPROXIMATION_EXTENDED: + err = p54_parse_rssical(dev, entry->data, data_len, + le16_to_cpu(entry->code)); + if (err) + goto err; + break; + case PDR_RSSI_LINEAR_APPROXIMATION_CUSTOMV2: { + struct pda_custom_wrapper *pda = (void *) entry->data; + __le16 *src; + u16 *dst; + int i; + + if (priv->rssi_db || data_len < sizeof(*pda)) + break; + + priv->rssi_db = p54_convert_db(pda, data_len); + if (!priv->rssi_db) + break; + + src = (void *) priv->rssi_db->data; + dst = (void *) priv->rssi_db->data; + + for (i = 0; i < priv->rssi_db->entries; i++) + *(dst++) = (s16) le16_to_cpu(*(src++)); + + } + break; + case PDR_PRISM_PA_CAL_OUTPUT_POWER_LIMITS_CUSTOM: { + struct pda_custom_wrapper *pda = (void *) entry->data; + if (priv->output_limit || data_len < sizeof(*pda)) + break; + priv->output_limit = p54_convert_db(pda, data_len); + } + break; + case PDR_PRISM_PA_CAL_CURVE_DATA_CUSTOM: { + struct pda_custom_wrapper *pda = (void *) entry->data; + if (priv->curve_data || data_len < sizeof(*pda)) + break; + priv->curve_data = p54_convert_db(pda, data_len); + } + break; + case PDR_END: + crc16 = ~crc_ccitt(crc16, (u8 *) entry, sizeof(*entry)); + if (crc16 != le16_to_cpup((__le16 *)entry->data)) { + wiphy_err(dev->wiphy, "eeprom failed checksum " + "test!\n"); + err = -ENOMSG; + goto err; + } else { + goto good_eeprom; + } + break; + default: + break; + } + + crc16 = crc_ccitt(crc16, (u8 *)entry, (entry_len + 1) * 2); + entry = (void *)entry + (entry_len + 1) * 2; + } + + wiphy_err(dev->wiphy, "unexpected end of eeprom data.\n"); + err = -ENODATA; + goto err; + +good_eeprom: + if (!synth || !priv->iq_autocal || !priv->output_limit || + !priv->curve_data) { + wiphy_err(dev->wiphy, + "not all required entries found in eeprom!\n"); + err = -EINVAL; + goto err; + } + + priv->rxhw = synth & PDR_SYNTH_FRONTEND_MASK; + + err = p54_generate_channel_lists(dev); + if (err) + goto err; + + if (priv->rxhw == PDR_SYNTH_FRONTEND_XBOW) + p54_init_xbow_synth(priv); + if (!(synth & PDR_SYNTH_24_GHZ_DISABLED)) + dev->wiphy->bands[IEEE80211_BAND_2GHZ] = + priv->band_table[IEEE80211_BAND_2GHZ]; + if (!(synth & PDR_SYNTH_5_GHZ_DISABLED)) + dev->wiphy->bands[IEEE80211_BAND_5GHZ] = + priv->band_table[IEEE80211_BAND_5GHZ]; + if ((synth & PDR_SYNTH_RX_DIV_MASK) == PDR_SYNTH_RX_DIV_SUPPORTED) + priv->rx_diversity_mask = 3; + if ((synth & PDR_SYNTH_TX_DIV_MASK) == PDR_SYNTH_TX_DIV_SUPPORTED) + priv->tx_diversity_mask = 3; + + if (!is_valid_ether_addr(dev->wiphy->perm_addr)) { + u8 perm_addr[ETH_ALEN]; + + wiphy_warn(dev->wiphy, + "Invalid hwaddr! Using randomly generated MAC addr\n"); + eth_random_addr(perm_addr); + SET_IEEE80211_PERM_ADDR(dev, perm_addr); + } + + priv->cur_rssi = &p54_rssi_default; + + wiphy_info(dev->wiphy, "hwaddr %pM, MAC:isl38%02x RF:%s\n", + dev->wiphy->perm_addr, priv->version, + p54_rf_chips[priv->rxhw]); + + return 0; + +err: + kfree(priv->iq_autocal); + kfree(priv->output_limit); + kfree(priv->curve_data); + kfree(priv->rssi_db); + kfree(priv->survey); + priv->iq_autocal = NULL; + priv->output_limit = NULL; + priv->curve_data = NULL; + priv->rssi_db = NULL; + priv->survey = NULL; + + wiphy_err(dev->wiphy, "eeprom parse failed!\n"); + return err; +} +EXPORT_SYMBOL_GPL(p54_parse_eeprom); + +int p54_read_eeprom(struct ieee80211_hw *dev) +{ + struct p54_common *priv = dev->priv; + size_t eeprom_size = 0x2020, offset = 0, blocksize, maxblocksize; + int ret = -ENOMEM; + void *eeprom; + + maxblocksize = EEPROM_READBACK_LEN; + if (priv->fw_var >= 0x509) + maxblocksize -= 0xc; + else + maxblocksize -= 0x4; + + eeprom = kzalloc(eeprom_size, GFP_KERNEL); + if (unlikely(!eeprom)) + goto free; + + while (eeprom_size) { + blocksize = min(eeprom_size, maxblocksize); + ret = p54_download_eeprom(priv, eeprom + offset, + offset, blocksize); + if (unlikely(ret)) + goto free; + + offset += blocksize; + eeprom_size -= blocksize; + } + + ret = p54_parse_eeprom(dev, eeprom, offset); +free: + kfree(eeprom); + return ret; +} +EXPORT_SYMBOL_GPL(p54_read_eeprom); diff --git a/drivers/net/wireless/p54/eeprom.h b/drivers/net/wireless/p54/eeprom.h new file mode 100644 index 0000000..20ebe39 --- /dev/null +++ b/drivers/net/wireless/p54/eeprom.h @@ -0,0 +1,245 @@ +/* + * eeprom specific definitions for mac80211 Prism54 drivers + * + * Copyright (c) 2006, Michael Wu + * Copyright (c) 2007-2009, Christian Lamparter + * + * Based on: + * - the islsm (softmac prism54) driver, which is: + * Copyright 2004-2006 Jean-Baptiste Note , et al. + * + * - LMAC API interface header file for STLC4560 (lmac_longbow.h) + * Copyright (C) 2007 Conexant Systems, Inc. + * + * - islmvc driver + * Copyright (C) 2001 Intersil Americas Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef EEPROM_H +#define EEPROM_H + +/* PDA defines are Copyright (C) 2005 Nokia Corporation (taken from islsm_pda.h) */ + +struct pda_entry { + __le16 len; /* includes both code and data */ + __le16 code; + u8 data[0]; +} __packed; + +struct eeprom_pda_wrap { + __le32 magic; + __le16 pad; + __le16 len; + __le32 arm_opcode; + u8 data[0]; +} __packed; + +struct p54_iq_autocal_entry { + __le16 iq_param[4]; +} __packed; + +struct pda_iq_autocal_entry { + __le16 freq; + struct p54_iq_autocal_entry params; +} __packed; + +struct pda_channel_output_limit { + __le16 freq; + u8 val_bpsk; + u8 val_qpsk; + u8 val_16qam; + u8 val_64qam; + u8 rate_set_mask; + u8 rate_set_size; +} __packed; + +struct pda_channel_output_limit_point_longbow { + __le16 val_bpsk; + __le16 val_qpsk; + __le16 val_16qam; + __le16 val_64qam; +} __packed; + +struct pda_channel_output_limit_longbow { + __le16 freq; + struct pda_channel_output_limit_point_longbow point[3]; +} __packed; + +struct pda_pa_curve_data_sample_rev0 { + u8 rf_power; + u8 pa_detector; + u8 pcv; +} __packed; + +struct pda_pa_curve_data_sample_rev1 { + u8 rf_power; + u8 pa_detector; + u8 data_barker; + u8 data_bpsk; + u8 data_qpsk; + u8 data_16qam; + u8 data_64qam; +} __packed; + +struct pda_pa_curve_data { + u8 cal_method_rev; + u8 channels; + u8 points_per_channel; + u8 padding; + u8 data[0]; +} __packed; + +struct pda_rssi_cal_ext_entry { + __le16 freq; + __le16 mul; + __le16 add; +} __packed; + +struct pda_rssi_cal_entry { + __le16 mul; + __le16 add; +} __packed; + +struct pda_country { + u8 regdomain; + u8 alpha2[2]; + u8 flags; +} __packed; + +struct pda_antenna_gain { + struct { + u8 gain_5GHz; /* 0.25 dBi units */ + u8 gain_2GHz; /* 0.25 dBi units */ + } __packed antenna[0]; +} __packed; + +struct pda_custom_wrapper { + __le16 entries; + __le16 entry_size; + __le16 offset; + __le16 len; + u8 data[0]; +} __packed; + +/* + * this defines the PDR codes used to build PDAs as defined in document + * number 553155. The current implementation mirrors version 1.1 of the + * document and lists only PDRs supported by the ARM platform. + */ + +/* common and choice range (0x0000 - 0x0fff) */ +#define PDR_END 0x0000 +#define PDR_MANUFACTURING_PART_NUMBER 0x0001 +#define PDR_PDA_VERSION 0x0002 +#define PDR_NIC_SERIAL_NUMBER 0x0003 +#define PDR_NIC_RAM_SIZE 0x0005 +#define PDR_RFMODEM_SUP_RANGE 0x0006 +#define PDR_PRISM_MAC_SUP_RANGE 0x0007 +#define PDR_NIC_ID 0x0008 + +#define PDR_MAC_ADDRESS 0x0101 +#define PDR_REGULATORY_DOMAIN_LIST 0x0103 /* obsolete */ +#define PDR_ALLOWED_CHAN_SET 0x0104 +#define PDR_DEFAULT_CHAN 0x0105 +#define PDR_TEMPERATURE_TYPE 0x0107 + +#define PDR_IFR_SETTING 0x0200 +#define PDR_RFR_SETTING 0x0201 +#define PDR_3861_BASELINE_REG_SETTINGS 0x0202 +#define PDR_3861_SHADOW_REG_SETTINGS 0x0203 +#define PDR_3861_IFRF_REG_SETTINGS 0x0204 + +#define PDR_3861_CHAN_CALIB_SET_POINTS 0x0300 +#define PDR_3861_CHAN_CALIB_INTEGRATOR 0x0301 + +#define PDR_3842_PRISM_II_NIC_CONFIG 0x0400 +#define PDR_PRISM_USB_ID 0x0401 +#define PDR_PRISM_PCI_ID 0x0402 +#define PDR_PRISM_PCI_IF_CONFIG 0x0403 +#define PDR_PRISM_PCI_PM_CONFIG 0x0404 + +#define PDR_3861_MF_TEST_CHAN_SET_POINTS 0x0900 +#define PDR_3861_MF_TEST_CHAN_INTEGRATORS 0x0901 + +/* ARM range (0x1000 - 0x1fff) */ +#define PDR_COUNTRY_INFORMATION 0x1000 /* obsolete */ +#define PDR_INTERFACE_LIST 0x1001 +#define PDR_HARDWARE_PLATFORM_COMPONENT_ID 0x1002 +#define PDR_OEM_NAME 0x1003 +#define PDR_PRODUCT_NAME 0x1004 +#define PDR_UTF8_OEM_NAME 0x1005 +#define PDR_UTF8_PRODUCT_NAME 0x1006 +#define PDR_COUNTRY_LIST 0x1007 +#define PDR_DEFAULT_COUNTRY 0x1008 + +#define PDR_ANTENNA_GAIN 0x1100 + +#define PDR_PRISM_INDIGO_PA_CALIBRATION_DATA 0x1901 +#define PDR_RSSI_LINEAR_APPROXIMATION 0x1902 +#define PDR_PRISM_PA_CAL_OUTPUT_POWER_LIMITS 0x1903 +#define PDR_PRISM_PA_CAL_CURVE_DATA 0x1904 +#define PDR_RSSI_LINEAR_APPROXIMATION_DUAL_BAND 0x1905 +#define PDR_PRISM_ZIF_TX_IQ_CALIBRATION 0x1906 +#define PDR_REGULATORY_POWER_LIMITS 0x1907 +#define PDR_RSSI_LINEAR_APPROXIMATION_EXTENDED 0x1908 +#define PDR_RADIATED_TRANSMISSION_CORRECTION 0x1909 +#define PDR_PRISM_TX_IQ_CALIBRATION 0x190a + +/* reserved range (0x2000 - 0x7fff) */ + +/* customer range (0x8000 - 0xffff) */ +#define PDR_BASEBAND_REGISTERS 0x8000 +#define PDR_PER_CHANNEL_BASEBAND_REGISTERS 0x8001 + +/* used by our modificated eeprom image */ +#define PDR_RSSI_LINEAR_APPROXIMATION_CUSTOM 0xDEAD +#define PDR_RSSI_LINEAR_APPROXIMATION_CUSTOMV2 0xCAFF +#define PDR_PRISM_PA_CAL_OUTPUT_POWER_LIMITS_CUSTOM 0xBEEF +#define PDR_PRISM_PA_CAL_CURVE_DATA_CUSTOM 0xB05D + +/* Interface Definitions */ +#define PDR_INTERFACE_ROLE_SERVER 0x0000 +#define PDR_INTERFACE_ROLE_CLIENT 0x0001 + +/* PDR definitions for default country & country list */ +#define PDR_COUNTRY_CERT_CODE 0x80 +#define PDR_COUNTRY_CERT_CODE_REAL 0x00 +#define PDR_COUNTRY_CERT_CODE_PSEUDO 0x80 +#define PDR_COUNTRY_CERT_BAND 0x40 +#define PDR_COUNTRY_CERT_BAND_2GHZ 0x00 +#define PDR_COUNTRY_CERT_BAND_5GHZ 0x40 +#define PDR_COUNTRY_CERT_IODOOR 0x30 +#define PDR_COUNTRY_CERT_IODOOR_BOTH 0x00 +#define PDR_COUNTRY_CERT_IODOOR_INDOOR 0x20 +#define PDR_COUNTRY_CERT_IODOOR_OUTDOOR 0x30 +#define PDR_COUNTRY_CERT_INDEX 0x0f + +/* Specific LMAC FW/HW variant definitions */ +#define PDR_SYNTH_FRONTEND_MASK 0x0007 +#define PDR_SYNTH_FRONTEND_DUETTE3 0x0001 +#define PDR_SYNTH_FRONTEND_DUETTE2 0x0002 +#define PDR_SYNTH_FRONTEND_FRISBEE 0x0003 +#define PDR_SYNTH_FRONTEND_XBOW 0x0004 +#define PDR_SYNTH_FRONTEND_LONGBOW 0x0005 +#define PDR_SYNTH_IQ_CAL_MASK 0x0018 +#define PDR_SYNTH_IQ_CAL_PA_DETECTOR 0x0000 +#define PDR_SYNTH_IQ_CAL_DISABLED 0x0008 +#define PDR_SYNTH_IQ_CAL_ZIF 0x0010 +#define PDR_SYNTH_FAA_SWITCH_MASK 0x0020 +#define PDR_SYNTH_FAA_SWITCH_ENABLED 0x0020 +#define PDR_SYNTH_24_GHZ_MASK 0x0040 +#define PDR_SYNTH_24_GHZ_DISABLED 0x0040 +#define PDR_SYNTH_5_GHZ_MASK 0x0080 +#define PDR_SYNTH_5_GHZ_DISABLED 0x0080 +#define PDR_SYNTH_RX_DIV_MASK 0x0100 +#define PDR_SYNTH_RX_DIV_SUPPORTED 0x0100 +#define PDR_SYNTH_TX_DIV_MASK 0x0200 +#define PDR_SYNTH_TX_DIV_SUPPORTED 0x0200 +#define PDR_SYNTH_ASM_MASK 0x0400 +#define PDR_SYNTH_ASM_XSWON 0x0400 + +#endif /* EEPROM_H */ diff --git a/drivers/net/wireless/p54/fwio.c b/drivers/net/wireless/p54/fwio.c new file mode 100644 index 0000000..b3879fb --- /dev/null +++ b/drivers/net/wireless/p54/fwio.c @@ -0,0 +1,763 @@ +/* + * Firmware I/O code for mac80211 Prism54 drivers + * + * Copyright (c) 2006, Michael Wu + * Copyright (c) 2007-2009, Christian Lamparter + * Copyright 2008, Johannes Berg + * + * Based on: + * - the islsm (softmac prism54) driver, which is: + * Copyright 2004-2006 Jean-Baptiste Note , et al. + * - stlc45xx driver + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include + +#include "p54.h" +#include "eeprom.h" +#include "lmac.h" + +int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw) +{ + struct p54_common *priv = dev->priv; + struct exp_if *exp_if; + struct bootrec *bootrec; + u32 *data = (u32 *)fw->data; + u32 *end_data = (u32 *)fw->data + (fw->size >> 2); + u8 *fw_version = NULL; + size_t len; + int i; + int maxlen; + + if (priv->rx_start) + return 0; + + while (data < end_data && *data) + data++; + + while (data < end_data && !*data) + data++; + + bootrec = (struct bootrec *) data; + + while (bootrec->data <= end_data && (bootrec->data + + (len = le32_to_cpu(bootrec->len))) <= end_data) { + u32 code = le32_to_cpu(bootrec->code); + switch (code) { + case BR_CODE_COMPONENT_ID: + priv->fw_interface = be32_to_cpup((__be32 *) + bootrec->data); + switch (priv->fw_interface) { + case FW_LM86: + case FW_LM20: + case FW_LM87: { + char *iftype = (char *)bootrec->data; + wiphy_info(priv->hw->wiphy, + "p54 detected a LM%c%c firmware\n", + iftype[2], iftype[3]); + break; + } + case FW_FMAC: + default: + wiphy_err(priv->hw->wiphy, + "unsupported firmware\n"); + return -ENODEV; + } + break; + case BR_CODE_COMPONENT_VERSION: + /* 24 bytes should be enough for all firmwares */ + if (strnlen((unsigned char *) bootrec->data, 24) < 24) + fw_version = (unsigned char *) bootrec->data; + break; + case BR_CODE_DESCR: { + struct bootrec_desc *desc = + (struct bootrec_desc *)bootrec->data; + priv->rx_start = le32_to_cpu(desc->rx_start); + /* FIXME add sanity checking */ + priv->rx_end = le32_to_cpu(desc->rx_end) - 0x3500; + priv->headroom = desc->headroom; + priv->tailroom = desc->tailroom; + priv->privacy_caps = desc->privacy_caps; + priv->rx_keycache_size = desc->rx_keycache_size; + if (le32_to_cpu(bootrec->len) == 11) + priv->rx_mtu = le16_to_cpu(desc->rx_mtu); + else + priv->rx_mtu = (size_t) + 0x620 - priv->tx_hdr_len; + maxlen = priv->tx_hdr_len + /* USB devices */ + sizeof(struct p54_rx_data) + + 4 + /* rx alignment */ + IEEE80211_MAX_FRAG_THRESHOLD; + if (priv->rx_mtu > maxlen && PAGE_SIZE == 4096) { + printk(KERN_INFO "p54: rx_mtu reduced from %d " + "to %d\n", priv->rx_mtu, maxlen); + priv->rx_mtu = maxlen; + } + break; + } + case BR_CODE_EXPOSED_IF: + exp_if = (struct exp_if *) bootrec->data; + for (i = 0; i < (len * sizeof(*exp_if) / 4); i++) + if (exp_if[i].if_id == cpu_to_le16(IF_ID_LMAC)) + priv->fw_var = le16_to_cpu(exp_if[i].variant); + break; + case BR_CODE_DEPENDENT_IF: + break; + case BR_CODE_END_OF_BRA: + case LEGACY_BR_CODE_END_OF_BRA: + end_data = NULL; + break; + default: + break; + } + bootrec = (struct bootrec *)&bootrec->data[len]; + } + + if (fw_version) { + wiphy_info(priv->hw->wiphy, + "FW rev %s - Softmac protocol %x.%x\n", + fw_version, priv->fw_var >> 8, priv->fw_var & 0xff); + snprintf(dev->wiphy->fw_version, sizeof(dev->wiphy->fw_version), + "%s - %x.%x", fw_version, + priv->fw_var >> 8, priv->fw_var & 0xff); + } + + if (priv->fw_var < 0x500) + wiphy_info(priv->hw->wiphy, + "you are using an obsolete firmware. " + "visit http://wireless.kernel.org/en/users/Drivers/p54 " + "and grab one for \"kernel >= 2.6.28\"!\n"); + + if (priv->fw_var >= 0x300) { + /* Firmware supports QoS, use it! */ + + if (priv->fw_var >= 0x500) { + priv->tx_stats[P54_QUEUE_AC_VO].limit = 16; + priv->tx_stats[P54_QUEUE_AC_VI].limit = 16; + priv->tx_stats[P54_QUEUE_AC_BE].limit = 16; + priv->tx_stats[P54_QUEUE_AC_BK].limit = 16; + } else { + priv->tx_stats[P54_QUEUE_AC_VO].limit = 3; + priv->tx_stats[P54_QUEUE_AC_VI].limit = 4; + priv->tx_stats[P54_QUEUE_AC_BE].limit = 3; + priv->tx_stats[P54_QUEUE_AC_BK].limit = 2; + } + priv->hw->queues = P54_QUEUE_AC_NUM; + } + + wiphy_info(priv->hw->wiphy, + "cryptographic accelerator WEP:%s, TKIP:%s, CCMP:%s\n", + (priv->privacy_caps & BR_DESC_PRIV_CAP_WEP) ? "YES" : "no", + (priv->privacy_caps & + (BR_DESC_PRIV_CAP_TKIP | BR_DESC_PRIV_CAP_MICHAEL)) + ? "YES" : "no", + (priv->privacy_caps & BR_DESC_PRIV_CAP_AESCCMP) + ? "YES" : "no"); + + if (priv->rx_keycache_size) { + /* + * NOTE: + * + * The firmware provides at most 255 (0 - 254) slots + * for keys which are then used to offload decryption. + * As a result the 255 entry (aka 0xff) can be used + * safely by the driver to mark keys that didn't fit + * into the full cache. This trick saves us from + * keeping a extra list for uploaded keys. + */ + + priv->used_rxkeys = kzalloc(BITS_TO_LONGS( + priv->rx_keycache_size), GFP_KERNEL); + + if (!priv->used_rxkeys) + return -ENOMEM; + } + + return 0; +} +EXPORT_SYMBOL_GPL(p54_parse_firmware); + +static struct sk_buff *p54_alloc_skb(struct p54_common *priv, u16 hdr_flags, + u16 payload_len, u16 type, gfp_t memflags) +{ + struct p54_hdr *hdr; + struct sk_buff *skb; + size_t frame_len = sizeof(*hdr) + payload_len; + + if (frame_len > P54_MAX_CTRL_FRAME_LEN) + return NULL; + + if (unlikely(skb_queue_len(&priv->tx_pending) > 64)) + return NULL; + + skb = __dev_alloc_skb(priv->tx_hdr_len + frame_len, memflags); + if (!skb) + return NULL; + skb_reserve(skb, priv->tx_hdr_len); + + hdr = (struct p54_hdr *) skb_put(skb, sizeof(*hdr)); + hdr->flags = cpu_to_le16(hdr_flags); + hdr->len = cpu_to_le16(payload_len); + hdr->type = cpu_to_le16(type); + hdr->tries = hdr->rts_tries = 0; + return skb; +} + +int p54_download_eeprom(struct p54_common *priv, void *buf, + u16 offset, u16 len) +{ + struct p54_eeprom_lm86 *eeprom_hdr; + struct sk_buff *skb; + size_t eeprom_hdr_size; + int ret = 0; + + if (priv->fw_var >= 0x509) + eeprom_hdr_size = sizeof(*eeprom_hdr); + else + eeprom_hdr_size = 0x4; + + skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL, eeprom_hdr_size + + len, P54_CONTROL_TYPE_EEPROM_READBACK, + GFP_KERNEL); + if (unlikely(!skb)) + return -ENOMEM; + + mutex_lock(&priv->eeprom_mutex); + priv->eeprom = buf; + eeprom_hdr = (struct p54_eeprom_lm86 *) skb_put(skb, + eeprom_hdr_size + len); + + if (priv->fw_var < 0x509) { + eeprom_hdr->v1.offset = cpu_to_le16(offset); + eeprom_hdr->v1.len = cpu_to_le16(len); + } else { + eeprom_hdr->v2.offset = cpu_to_le32(offset); + eeprom_hdr->v2.len = cpu_to_le16(len); + eeprom_hdr->v2.magic2 = 0xf; + memcpy(eeprom_hdr->v2.magic, (const char *)"LOCK", 4); + } + + p54_tx(priv, skb); + + if (!wait_for_completion_interruptible_timeout( + &priv->eeprom_comp, HZ)) { + wiphy_err(priv->hw->wiphy, "device does not respond!\n"); + ret = -EBUSY; + } + priv->eeprom = NULL; + mutex_unlock(&priv->eeprom_mutex); + return ret; +} + +int p54_update_beacon_tim(struct p54_common *priv, u16 aid, bool set) +{ + struct sk_buff *skb; + struct p54_tim *tim; + + skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*tim), + P54_CONTROL_TYPE_TIM, GFP_ATOMIC); + if (unlikely(!skb)) + return -ENOMEM; + + tim = (struct p54_tim *) skb_put(skb, sizeof(*tim)); + tim->count = 1; + tim->entry[0] = cpu_to_le16(set ? (aid | 0x8000) : aid); + p54_tx(priv, skb); + return 0; +} + +int p54_sta_unlock(struct p54_common *priv, u8 *addr) +{ + struct sk_buff *skb; + struct p54_sta_unlock *sta; + + skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*sta), + P54_CONTROL_TYPE_PSM_STA_UNLOCK, GFP_ATOMIC); + if (unlikely(!skb)) + return -ENOMEM; + + sta = (struct p54_sta_unlock *)skb_put(skb, sizeof(*sta)); + memcpy(sta->addr, addr, ETH_ALEN); + p54_tx(priv, skb); + return 0; +} + +int p54_tx_cancel(struct p54_common *priv, __le32 req_id) +{ + struct sk_buff *skb; + struct p54_txcancel *cancel; + u32 _req_id = le32_to_cpu(req_id); + + if (unlikely(_req_id < priv->rx_start || _req_id > priv->rx_end)) + return -EINVAL; + + skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*cancel), + P54_CONTROL_TYPE_TXCANCEL, GFP_ATOMIC); + if (unlikely(!skb)) + return -ENOMEM; + + cancel = (struct p54_txcancel *)skb_put(skb, sizeof(*cancel)); + cancel->req_id = req_id; + p54_tx(priv, skb); + return 0; +} + +int p54_setup_mac(struct p54_common *priv) +{ + struct sk_buff *skb; + struct p54_setup_mac *setup; + u16 mode; + + skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*setup), + P54_CONTROL_TYPE_SETUP, GFP_ATOMIC); + if (!skb) + return -ENOMEM; + + setup = (struct p54_setup_mac *) skb_put(skb, sizeof(*setup)); + if (!(priv->hw->conf.flags & IEEE80211_CONF_IDLE)) { + switch (priv->mode) { + case NL80211_IFTYPE_STATION: + mode = P54_FILTER_TYPE_STATION; + break; + case NL80211_IFTYPE_AP: + mode = P54_FILTER_TYPE_AP; + break; + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_MESH_POINT: + mode = P54_FILTER_TYPE_IBSS; + break; + case NL80211_IFTYPE_MONITOR: + mode = P54_FILTER_TYPE_PROMISCUOUS; + break; + default: + mode = P54_FILTER_TYPE_HIBERNATE; + break; + } + + /* + * "TRANSPARENT and PROMISCUOUS are mutually exclusive" + * STSW45X0C LMAC API - page 12 + */ + if (((priv->filter_flags & FIF_PROMISC_IN_BSS) || + (priv->filter_flags & FIF_OTHER_BSS)) && + (mode != P54_FILTER_TYPE_PROMISCUOUS)) + mode |= P54_FILTER_TYPE_TRANSPARENT; + } else { + mode = P54_FILTER_TYPE_HIBERNATE; + } + + setup->mac_mode = cpu_to_le16(mode); + memcpy(setup->mac_addr, priv->mac_addr, ETH_ALEN); + memcpy(setup->bssid, priv->bssid, ETH_ALEN); + setup->rx_antenna = 2 & priv->rx_diversity_mask; /* automatic */ + setup->rx_align = 0; + if (priv->fw_var < 0x500) { + setup->v1.basic_rate_mask = cpu_to_le32(priv->basic_rate_mask); + memset(setup->v1.rts_rates, 0, 8); + setup->v1.rx_addr = cpu_to_le32(priv->rx_end); + setup->v1.max_rx = cpu_to_le16(priv->rx_mtu); + setup->v1.rxhw = cpu_to_le16(priv->rxhw); + setup->v1.wakeup_timer = cpu_to_le16(priv->wakeup_timer); + setup->v1.unalloc0 = cpu_to_le16(0); + } else { + setup->v2.rx_addr = cpu_to_le32(priv->rx_end); + setup->v2.max_rx = cpu_to_le16(priv->rx_mtu); + setup->v2.rxhw = cpu_to_le16(priv->rxhw); + setup->v2.timer = cpu_to_le16(priv->wakeup_timer); + setup->v2.truncate = cpu_to_le16(48896); + setup->v2.basic_rate_mask = cpu_to_le32(priv->basic_rate_mask); + setup->v2.sbss_offset = 0; + setup->v2.mcast_window = 0; + setup->v2.rx_rssi_threshold = 0; + setup->v2.rx_ed_threshold = 0; + setup->v2.ref_clock = cpu_to_le32(644245094); + setup->v2.lpf_bandwidth = cpu_to_le16(65535); + setup->v2.osc_start_delay = cpu_to_le16(65535); + } + p54_tx(priv, skb); + priv->phy_idle = mode == P54_FILTER_TYPE_HIBERNATE; + return 0; +} + +int p54_scan(struct p54_common *priv, u16 mode, u16 dwell) +{ + struct sk_buff *skb; + struct p54_hdr *hdr; + struct p54_scan_head *head; + struct p54_iq_autocal_entry *iq_autocal; + union p54_scan_body_union *body; + struct p54_scan_tail_rate *rate; + struct pda_rssi_cal_entry *rssi; + struct p54_rssi_db_entry *rssi_data; + unsigned int i; + void *entry; + __le16 freq = cpu_to_le16(priv->hw->conf.chandef.chan->center_freq); + + skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*head) + + 2 + sizeof(*iq_autocal) + sizeof(*body) + + sizeof(*rate) + 2 * sizeof(*rssi), + P54_CONTROL_TYPE_SCAN, GFP_ATOMIC); + if (!skb) + return -ENOMEM; + + head = (struct p54_scan_head *) skb_put(skb, sizeof(*head)); + memset(head->scan_params, 0, sizeof(head->scan_params)); + head->mode = cpu_to_le16(mode); + head->dwell = cpu_to_le16(dwell); + head->freq = freq; + + if (priv->rxhw == PDR_SYNTH_FRONTEND_LONGBOW) { + __le16 *pa_power_points = (__le16 *) skb_put(skb, 2); + *pa_power_points = cpu_to_le16(0x0c); + } + + iq_autocal = (void *) skb_put(skb, sizeof(*iq_autocal)); + for (i = 0; i < priv->iq_autocal_len; i++) { + if (priv->iq_autocal[i].freq != freq) + continue; + + memcpy(iq_autocal, &priv->iq_autocal[i].params, + sizeof(struct p54_iq_autocal_entry)); + break; + } + if (i == priv->iq_autocal_len) + goto err; + + if (priv->rxhw == PDR_SYNTH_FRONTEND_LONGBOW) + body = (void *) skb_put(skb, sizeof(body->longbow)); + else + body = (void *) skb_put(skb, sizeof(body->normal)); + + for (i = 0; i < priv->output_limit->entries; i++) { + __le16 *entry_freq = (void *) (priv->output_limit->data + + priv->output_limit->entry_size * i); + + if (*entry_freq != freq) + continue; + + if (priv->rxhw == PDR_SYNTH_FRONTEND_LONGBOW) { + memcpy(&body->longbow.power_limits, + (void *) entry_freq + sizeof(__le16), + priv->output_limit->entry_size); + } else { + struct pda_channel_output_limit *limits = + (void *) entry_freq; + + body->normal.val_barker = 0x38; + body->normal.val_bpsk = body->normal.dup_bpsk = + limits->val_bpsk; + body->normal.val_qpsk = body->normal.dup_qpsk = + limits->val_qpsk; + body->normal.val_16qam = body->normal.dup_16qam = + limits->val_16qam; + body->normal.val_64qam = body->normal.dup_64qam = + limits->val_64qam; + } + break; + } + if (i == priv->output_limit->entries) + goto err; + + entry = (void *)(priv->curve_data->data + priv->curve_data->offset); + for (i = 0; i < priv->curve_data->entries; i++) { + if (*((__le16 *)entry) != freq) { + entry += priv->curve_data->entry_size; + continue; + } + + if (priv->rxhw == PDR_SYNTH_FRONTEND_LONGBOW) { + memcpy(&body->longbow.curve_data, + entry + sizeof(__le16), + priv->curve_data->entry_size); + } else { + struct p54_scan_body *chan = &body->normal; + struct pda_pa_curve_data *curve_data = + (void *) priv->curve_data->data; + + entry += sizeof(__le16); + chan->pa_points_per_curve = 8; + memset(chan->curve_data, 0, sizeof(*chan->curve_data)); + memcpy(chan->curve_data, entry, + sizeof(struct p54_pa_curve_data_sample) * + min((u8)8, curve_data->points_per_channel)); + } + break; + } + if (i == priv->curve_data->entries) + goto err; + + if ((priv->fw_var >= 0x500) && (priv->fw_var < 0x509)) { + rate = (void *) skb_put(skb, sizeof(*rate)); + rate->basic_rate_mask = cpu_to_le32(priv->basic_rate_mask); + for (i = 0; i < sizeof(rate->rts_rates); i++) + rate->rts_rates[i] = i; + } + + rssi = (struct pda_rssi_cal_entry *) skb_put(skb, sizeof(*rssi)); + rssi_data = p54_rssi_find(priv, le16_to_cpu(freq)); + rssi->mul = cpu_to_le16(rssi_data->mul); + rssi->add = cpu_to_le16(rssi_data->add); + if (priv->rxhw == PDR_SYNTH_FRONTEND_LONGBOW) { + /* Longbow frontend needs ever more */ + rssi = (void *) skb_put(skb, sizeof(*rssi)); + rssi->mul = cpu_to_le16(rssi_data->longbow_unkn); + rssi->add = cpu_to_le16(rssi_data->longbow_unk2); + } + + if (priv->fw_var >= 0x509) { + rate = (void *) skb_put(skb, sizeof(*rate)); + rate->basic_rate_mask = cpu_to_le32(priv->basic_rate_mask); + for (i = 0; i < sizeof(rate->rts_rates); i++) + rate->rts_rates[i] = i; + } + + hdr = (struct p54_hdr *) skb->data; + hdr->len = cpu_to_le16(skb->len - sizeof(*hdr)); + + p54_tx(priv, skb); + priv->cur_rssi = rssi_data; + return 0; + +err: + wiphy_err(priv->hw->wiphy, "frequency change to channel %d failed.\n", + ieee80211_frequency_to_channel( + priv->hw->conf.chandef.chan->center_freq)); + + dev_kfree_skb_any(skb); + return -EINVAL; +} + +int p54_set_leds(struct p54_common *priv) +{ + struct sk_buff *skb; + struct p54_led *led; + + skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*led), + P54_CONTROL_TYPE_LED, GFP_ATOMIC); + if (unlikely(!skb)) + return -ENOMEM; + + led = (struct p54_led *) skb_put(skb, sizeof(*led)); + led->flags = cpu_to_le16(0x0003); + led->mask[0] = led->mask[1] = cpu_to_le16(priv->softled_state); + led->delay[0] = cpu_to_le16(1); + led->delay[1] = cpu_to_le16(0); + p54_tx(priv, skb); + return 0; +} + +int p54_set_edcf(struct p54_common *priv) +{ + struct sk_buff *skb; + struct p54_edcf *edcf; + u8 rtd; + + skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*edcf), + P54_CONTROL_TYPE_DCFINIT, GFP_ATOMIC); + if (unlikely(!skb)) + return -ENOMEM; + + edcf = (struct p54_edcf *)skb_put(skb, sizeof(*edcf)); + if (priv->use_short_slot) { + edcf->slottime = 9; + edcf->sifs = 0x10; + edcf->eofpad = 0x00; + } else { + edcf->slottime = 20; + edcf->sifs = 0x0a; + edcf->eofpad = 0x06; + } + /* + * calculate the extra round trip delay according to the + * formula from 802.11-2007 17.3.8.6. + */ + rtd = 3 * priv->coverage_class; + edcf->slottime += rtd; + edcf->round_trip_delay = cpu_to_le16(rtd); + /* (see prism54/isl_oid.h for further details) */ + edcf->frameburst = cpu_to_le16(0); + edcf->flags = 0; + memset(edcf->mapping, 0, sizeof(edcf->mapping)); + memcpy(edcf->queue, priv->qos_params, sizeof(edcf->queue)); + p54_tx(priv, skb); + return 0; +} + +int p54_set_ps(struct p54_common *priv) +{ + struct sk_buff *skb; + struct p54_psm *psm; + unsigned int i; + u16 mode; + + if (priv->hw->conf.flags & IEEE80211_CONF_PS && + !priv->powersave_override) + mode = P54_PSM | P54_PSM_BEACON_TIMEOUT | P54_PSM_DTIM | + P54_PSM_CHECKSUM | P54_PSM_MCBC; + else + mode = P54_PSM_CAM; + + skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*psm), + P54_CONTROL_TYPE_PSM, GFP_ATOMIC); + if (!skb) + return -ENOMEM; + + psm = (struct p54_psm *)skb_put(skb, sizeof(*psm)); + psm->mode = cpu_to_le16(mode); + psm->aid = cpu_to_le16(priv->aid); + for (i = 0; i < ARRAY_SIZE(psm->intervals); i++) { + psm->intervals[i].interval = + cpu_to_le16(priv->hw->conf.listen_interval); + psm->intervals[i].periods = cpu_to_le16(1); + } + + psm->beacon_rssi_skip_max = 200; + psm->rssi_delta_threshold = 0; + psm->nr = 1; + psm->exclude[0] = WLAN_EID_TIM; + + p54_tx(priv, skb); + priv->phy_ps = mode != P54_PSM_CAM; + return 0; +} + +int p54_init_xbow_synth(struct p54_common *priv) +{ + struct sk_buff *skb; + struct p54_xbow_synth *xbow; + + skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*xbow), + P54_CONTROL_TYPE_XBOW_SYNTH_CFG, GFP_KERNEL); + if (unlikely(!skb)) + return -ENOMEM; + + xbow = (struct p54_xbow_synth *)skb_put(skb, sizeof(*xbow)); + xbow->magic1 = cpu_to_le16(0x1); + xbow->magic2 = cpu_to_le16(0x2); + xbow->freq = cpu_to_le16(5390); + memset(xbow->padding, 0, sizeof(xbow->padding)); + p54_tx(priv, skb); + return 0; +} + +int p54_upload_key(struct p54_common *priv, u8 algo, int slot, u8 idx, u8 len, + u8 *addr, u8* key) +{ + struct sk_buff *skb; + struct p54_keycache *rxkey; + + skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*rxkey), + P54_CONTROL_TYPE_RX_KEYCACHE, GFP_KERNEL); + if (unlikely(!skb)) + return -ENOMEM; + + rxkey = (struct p54_keycache *)skb_put(skb, sizeof(*rxkey)); + rxkey->entry = slot; + rxkey->key_id = idx; + rxkey->key_type = algo; + if (addr) + memcpy(rxkey->mac, addr, ETH_ALEN); + else + memset(rxkey->mac, ~0, ETH_ALEN); + + switch (algo) { + case P54_CRYPTO_WEP: + case P54_CRYPTO_AESCCMP: + rxkey->key_len = min_t(u8, 16, len); + memcpy(rxkey->key, key, rxkey->key_len); + break; + + case P54_CRYPTO_TKIPMICHAEL: + rxkey->key_len = 24; + memcpy(rxkey->key, key, 16); + memcpy(&(rxkey->key[16]), &(key + [NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY]), 8); + break; + + case P54_CRYPTO_NONE: + rxkey->key_len = 0; + memset(rxkey->key, 0, sizeof(rxkey->key)); + break; + + default: + wiphy_err(priv->hw->wiphy, + "invalid cryptographic algorithm: %d\n", algo); + dev_kfree_skb(skb); + return -EINVAL; + } + + p54_tx(priv, skb); + return 0; +} + +int p54_fetch_statistics(struct p54_common *priv) +{ + struct ieee80211_tx_info *txinfo; + struct p54_tx_info *p54info; + struct sk_buff *skb; + + skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL, + sizeof(struct p54_statistics), + P54_CONTROL_TYPE_STAT_READBACK, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + /* + * The statistic feedback causes some extra headaches here, if it + * is not to crash/corrupt the firmware data structures. + * + * Unlike all other Control Get OIDs we can not use helpers like + * skb_put to reserve the space for the data we're requesting. + * Instead the extra frame length -which will hold the results later- + * will only be told to the p54_assign_address, so that following + * frames won't be placed into the allegedly empty area. + */ + txinfo = IEEE80211_SKB_CB(skb); + p54info = (void *) txinfo->rate_driver_data; + p54info->extra_len = sizeof(struct p54_statistics); + + p54_tx(priv, skb); + return 0; +} + +int p54_set_groupfilter(struct p54_common *priv) +{ + struct p54_group_address_table *grp; + struct sk_buff *skb; + bool on = false; + + skb = p54_alloc_skb(priv, P54_HDR_FLAG_CONTROL_OPSET, sizeof(*grp), + P54_CONTROL_TYPE_GROUP_ADDRESS_TABLE, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + grp = (struct p54_group_address_table *)skb_put(skb, sizeof(*grp)); + + on = !(priv->filter_flags & FIF_ALLMULTI) && + (priv->mc_maclist_num > 0 && + priv->mc_maclist_num <= MC_FILTER_ADDRESS_NUM); + + if (on) { + grp->filter_enable = cpu_to_le16(1); + grp->num_address = cpu_to_le16(priv->mc_maclist_num); + memcpy(grp->mac_list, priv->mc_maclist, sizeof(grp->mac_list)); + } else { + grp->filter_enable = cpu_to_le16(0); + grp->num_address = cpu_to_le16(0); + memset(grp->mac_list, 0, sizeof(grp->mac_list)); + } + + p54_tx(priv, skb); + return 0; +} diff --git a/drivers/net/wireless/p54/led.c b/drivers/net/wireless/p54/led.c new file mode 100644 index 0000000..6efc9c4 --- /dev/null +++ b/drivers/net/wireless/p54/led.c @@ -0,0 +1,162 @@ +/* + * Common code for mac80211 Prism54 drivers + * + * Copyright (c) 2006, Michael Wu + * Copyright (c) 2007-2009, Christian Lamparter + * Copyright 2008, Johannes Berg + * + * Based on: + * - the islsm (softmac prism54) driver, which is: + * Copyright 2004-2006 Jean-Baptiste Note , et al. + * - stlc45xx driver + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#include +#ifdef CPTCFG_P54_LEDS +#include +#endif /* CPTCFG_P54_LEDS */ + +#include "p54.h" +#include "lmac.h" + +static void p54_update_leds(struct work_struct *work) +{ + struct p54_common *priv = container_of(work, struct p54_common, + led_work.work); + int err, i, tmp, blink_delay = 400; + bool rerun = false; + + /* Don't toggle the LED, when the device is down. */ + if (priv->mode == NL80211_IFTYPE_UNSPECIFIED) + return ; + + for (i = 0; i < ARRAY_SIZE(priv->leds); i++) + if (priv->leds[i].toggled) { + priv->softled_state |= BIT(i); + + tmp = 70 + 200 / (priv->leds[i].toggled); + if (tmp < blink_delay) + blink_delay = tmp; + + if (priv->leds[i].led_dev.brightness == LED_OFF) + rerun = true; + + priv->leds[i].toggled = + !!priv->leds[i].led_dev.brightness; + } else + priv->softled_state &= ~BIT(i); + + err = p54_set_leds(priv); + if (err && net_ratelimit()) + wiphy_err(priv->hw->wiphy, + "failed to update LEDs (%d).\n", err); + + if (rerun) + ieee80211_queue_delayed_work(priv->hw, &priv->led_work, + msecs_to_jiffies(blink_delay)); +} + +static void p54_led_brightness_set(struct led_classdev *led_dev, + enum led_brightness brightness) +{ + struct p54_led_dev *led = container_of(led_dev, struct p54_led_dev, + led_dev); + struct ieee80211_hw *dev = led->hw_dev; + struct p54_common *priv = dev->priv; + + if (priv->mode == NL80211_IFTYPE_UNSPECIFIED) + return ; + + if ((brightness) && (led->registered)) { + led->toggled++; + ieee80211_queue_delayed_work(priv->hw, &priv->led_work, HZ/10); + } +} + +static int p54_register_led(struct p54_common *priv, + unsigned int led_index, + char *name, char *trigger) +{ + struct p54_led_dev *led = &priv->leds[led_index]; + int err; + + if (led->registered) + return -EEXIST; + + snprintf(led->name, sizeof(led->name), "p54-%s::%s", + wiphy_name(priv->hw->wiphy), name); + led->hw_dev = priv->hw; + led->index = led_index; + led->led_dev.name = led->name; + led->led_dev.default_trigger = trigger; + led->led_dev.brightness_set = p54_led_brightness_set; + + err = led_classdev_register(wiphy_dev(priv->hw->wiphy), &led->led_dev); + if (err) + wiphy_err(priv->hw->wiphy, + "Failed to register %s LED.\n", name); + else + led->registered = 1; + + return err; +} + +int p54_init_leds(struct p54_common *priv) +{ + int err; + + /* + * TODO: + * Figure out if the EEPROM contains some hints about the number + * of available/programmable LEDs of the device. + */ + + INIT_DELAYED_WORK(&priv->led_work, p54_update_leds); + + err = p54_register_led(priv, 0, "assoc", + ieee80211_get_assoc_led_name(priv->hw)); + if (err) + return err; + + err = p54_register_led(priv, 1, "tx", + ieee80211_get_tx_led_name(priv->hw)); + if (err) + return err; + + err = p54_register_led(priv, 2, "rx", + ieee80211_get_rx_led_name(priv->hw)); + if (err) + return err; + + err = p54_register_led(priv, 3, "radio", + ieee80211_get_radio_led_name(priv->hw)); + if (err) + return err; + + err = p54_set_leds(priv); + return err; +} + +void p54_unregister_leds(struct p54_common *priv) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(priv->leds); i++) { + if (priv->leds[i].registered) { + priv->leds[i].registered = false; + priv->leds[i].toggled = 0; + led_classdev_unregister(&priv->leds[i].led_dev); + } + } + + cancel_delayed_work_sync(&priv->led_work); +} diff --git a/drivers/net/wireless/p54/lmac.h b/drivers/net/wireless/p54/lmac.h new file mode 100644 index 0000000..de1d46b --- /dev/null +++ b/drivers/net/wireless/p54/lmac.h @@ -0,0 +1,562 @@ +/* + * LMAC Interface specific definitions for mac80211 Prism54 drivers + * + * Copyright (c) 2006, Michael Wu + * Copyright (c) 2007 - 2009, Christian Lamparter + * + * Based on: + * - the islsm (softmac prism54) driver, which is: + * Copyright 2004-2006 Jean-Baptiste Note , et al. + * + * - LMAC API interface header file for STLC4560 (lmac_longbow.h) + * Copyright (C) 2007 Conexant Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef LMAC_H +#define LMAC_H + +enum p54_control_frame_types { + P54_CONTROL_TYPE_SETUP = 0, + P54_CONTROL_TYPE_SCAN, + P54_CONTROL_TYPE_TRAP, + P54_CONTROL_TYPE_DCFINIT, + P54_CONTROL_TYPE_RX_KEYCACHE, + P54_CONTROL_TYPE_TIM, + P54_CONTROL_TYPE_PSM, + P54_CONTROL_TYPE_TXCANCEL, + P54_CONTROL_TYPE_TXDONE, + P54_CONTROL_TYPE_BURST, + P54_CONTROL_TYPE_STAT_READBACK, + P54_CONTROL_TYPE_BBP, + P54_CONTROL_TYPE_EEPROM_READBACK, + P54_CONTROL_TYPE_LED, + P54_CONTROL_TYPE_GPIO, + P54_CONTROL_TYPE_TIMER, + P54_CONTROL_TYPE_MODULATION, + P54_CONTROL_TYPE_SYNTH_CONFIG, + P54_CONTROL_TYPE_DETECTOR_VALUE, + P54_CONTROL_TYPE_XBOW_SYNTH_CFG, + P54_CONTROL_TYPE_CCE_QUIET, + P54_CONTROL_TYPE_PSM_STA_UNLOCK, + P54_CONTROL_TYPE_PCS, + P54_CONTROL_TYPE_BT_BALANCER = 28, + P54_CONTROL_TYPE_GROUP_ADDRESS_TABLE = 30, + P54_CONTROL_TYPE_ARPTABLE = 31, + P54_CONTROL_TYPE_BT_OPTIONS = 35, +}; + +#define P54_HDR_FLAG_CONTROL BIT(15) +#define P54_HDR_FLAG_CONTROL_OPSET (BIT(15) + BIT(0)) +#define P54_HDR_FLAG_DATA_ALIGN BIT(14) + +#define P54_HDR_FLAG_DATA_OUT_PROMISC BIT(0) +#define P54_HDR_FLAG_DATA_OUT_TIMESTAMP BIT(1) +#define P54_HDR_FLAG_DATA_OUT_SEQNR BIT(2) +#define P54_HDR_FLAG_DATA_OUT_BIT3 BIT(3) +#define P54_HDR_FLAG_DATA_OUT_BURST BIT(4) +#define P54_HDR_FLAG_DATA_OUT_NOCANCEL BIT(5) +#define P54_HDR_FLAG_DATA_OUT_CLEARTIM BIT(6) +#define P54_HDR_FLAG_DATA_OUT_HITCHHIKE BIT(7) +#define P54_HDR_FLAG_DATA_OUT_COMPRESS BIT(8) +#define P54_HDR_FLAG_DATA_OUT_CONCAT BIT(9) +#define P54_HDR_FLAG_DATA_OUT_PCS_ACCEPT BIT(10) +#define P54_HDR_FLAG_DATA_OUT_WAITEOSP BIT(11) + +#define P54_HDR_FLAG_DATA_IN_FCS_GOOD BIT(0) +#define P54_HDR_FLAG_DATA_IN_MATCH_MAC BIT(1) +#define P54_HDR_FLAG_DATA_IN_MCBC BIT(2) +#define P54_HDR_FLAG_DATA_IN_BEACON BIT(3) +#define P54_HDR_FLAG_DATA_IN_MATCH_BSS BIT(4) +#define P54_HDR_FLAG_DATA_IN_BCAST_BSS BIT(5) +#define P54_HDR_FLAG_DATA_IN_DATA BIT(6) +#define P54_HDR_FLAG_DATA_IN_TRUNCATED BIT(7) +#define P54_HDR_FLAG_DATA_IN_BIT8 BIT(8) +#define P54_HDR_FLAG_DATA_IN_TRANSPARENT BIT(9) + +struct p54_hdr { + __le16 flags; + __le16 len; + __le32 req_id; + __le16 type; /* enum p54_control_frame_types */ + u8 rts_tries; + u8 tries; + u8 data[0]; +} __packed; + +#define GET_REQ_ID(skb) \ + (((struct p54_hdr *) ((struct sk_buff *) skb)->data)->req_id) \ + +#define FREE_AFTER_TX(skb) \ + ((((struct p54_hdr *) ((struct sk_buff *) skb)->data)-> \ + flags) == cpu_to_le16(P54_HDR_FLAG_CONTROL_OPSET)) + +#define IS_DATA_FRAME(skb) \ + (!((((struct p54_hdr *) ((struct sk_buff *) skb)->data)-> \ + flags) & cpu_to_le16(P54_HDR_FLAG_CONTROL))) + +#define GET_HW_QUEUE(skb) \ + (((struct p54_tx_data *)((struct p54_hdr *) \ + skb->data)->data)->hw_queue) + +/* + * shared interface ID definitions + * The interface ID is a unique identification of a specific interface. + * The following values are reserved: 0x0000, 0x0002, 0x0012, 0x0014, 0x0015 + */ +#define IF_ID_ISL36356A 0x0001 /* ISL36356A <-> Firmware */ +#define IF_ID_MVC 0x0003 /* MAC Virtual Coprocessor */ +#define IF_ID_DEBUG 0x0008 /* PolDebug Interface */ +#define IF_ID_PRODUCT 0x0009 +#define IF_ID_OEM 0x000a +#define IF_ID_PCI3877 0x000b /* 3877 <-> Host PCI */ +#define IF_ID_ISL37704C 0x000c /* ISL37704C <-> Fw */ +#define IF_ID_ISL39000 0x000f /* ISL39000 <-> Fw */ +#define IF_ID_ISL39300A 0x0010 /* ISL39300A <-> Fw */ +#define IF_ID_ISL37700_UAP 0x0016 /* ISL37700 uAP Fw <-> Fw */ +#define IF_ID_ISL39000_UAP 0x0017 /* ISL39000 uAP Fw <-> Fw */ +#define IF_ID_LMAC 0x001a /* Interface exposed by LMAC */ + +struct exp_if { + __le16 role; + __le16 if_id; + __le16 variant; + __le16 btm_compat; + __le16 top_compat; +} __packed; + +struct dep_if { + __le16 role; + __le16 if_id; + __le16 variant; +} __packed; + +/* driver <-> lmac definitions */ +struct p54_eeprom_lm86 { + union { + struct { + __le16 offset; + __le16 len; + u8 data[0]; + } __packed v1; + struct { + __le32 offset; + __le16 len; + u8 magic2; + u8 pad; + u8 magic[4]; + u8 data[0]; + } __packed v2; + } __packed; +} __packed; + +enum p54_rx_decrypt_status { + P54_DECRYPT_NONE = 0, + P54_DECRYPT_OK, + P54_DECRYPT_NOKEY, + P54_DECRYPT_NOMICHAEL, + P54_DECRYPT_NOCKIPMIC, + P54_DECRYPT_FAIL_WEP, + P54_DECRYPT_FAIL_TKIP, + P54_DECRYPT_FAIL_MICHAEL, + P54_DECRYPT_FAIL_CKIPKP, + P54_DECRYPT_FAIL_CKIPMIC, + P54_DECRYPT_FAIL_AESCCMP +}; + +struct p54_rx_data { + __le16 flags; + __le16 len; + __le16 freq; + u8 antenna; + u8 rate; + u8 rssi; + u8 quality; + u8 decrypt_status; + u8 rssi_raw; + __le32 tsf32; + __le32 unalloc0; + u8 align[0]; +} __packed; + +enum p54_trap_type { + P54_TRAP_SCAN = 0, + P54_TRAP_TIMER, + P54_TRAP_BEACON_TX, + P54_TRAP_FAA_RADIO_ON, + P54_TRAP_FAA_RADIO_OFF, + P54_TRAP_RADAR, + P54_TRAP_NO_BEACON, + P54_TRAP_TBTT, + P54_TRAP_SCO_ENTER, + P54_TRAP_SCO_EXIT +}; + +struct p54_trap { + __le16 event; + __le16 frequency; +} __packed; + +enum p54_frame_sent_status { + P54_TX_OK = 0, + P54_TX_FAILED, + P54_TX_PSM, + P54_TX_PSM_CANCELLED = 4 +}; + +struct p54_frame_sent { + u8 status; + u8 tries; + u8 ack_rssi; + u8 quality; + __le16 seq; + u8 antenna; + u8 padding; +} __packed; + +enum p54_tx_data_crypt { + P54_CRYPTO_NONE = 0, + P54_CRYPTO_WEP, + P54_CRYPTO_TKIP, + P54_CRYPTO_TKIPMICHAEL, + P54_CRYPTO_CCX_WEPMIC, + P54_CRYPTO_CCX_KPMIC, + P54_CRYPTO_CCX_KP, + P54_CRYPTO_AESCCMP +}; + +enum p54_tx_data_queue { + P54_QUEUE_BEACON = 0, + P54_QUEUE_FWSCAN = 1, + P54_QUEUE_MGMT = 2, + P54_QUEUE_CAB = 3, + P54_QUEUE_DATA = 4, + + P54_QUEUE_AC_NUM = 4, + P54_QUEUE_AC_VO = 4, + P54_QUEUE_AC_VI = 5, + P54_QUEUE_AC_BE = 6, + P54_QUEUE_AC_BK = 7, + + /* keep last */ + P54_QUEUE_NUM = 8, +}; + +#define IS_QOS_QUEUE(n) (n >= P54_QUEUE_DATA) + +struct p54_tx_data { + u8 rateset[8]; + u8 rts_rate_idx; + u8 crypt_offset; + u8 key_type; + u8 key_len; + u8 key[16]; + u8 hw_queue; + u8 backlog; + __le16 durations[4]; + u8 tx_antenna; + union { + struct { + u8 cts_rate; + __le16 output_power; + } __packed longbow; + struct { + u8 output_power; + u8 cts_rate; + u8 unalloc; + } __packed normal; + } __packed; + u8 unalloc2[2]; + u8 align[0]; +} __packed; + +/* unit is ms */ +#define P54_TX_FRAME_LIFETIME 2000 +#define P54_TX_TIMEOUT 4000 +#define P54_STATISTICS_UPDATE 5000 + +#define P54_FILTER_TYPE_NONE 0 +#define P54_FILTER_TYPE_STATION BIT(0) +#define P54_FILTER_TYPE_IBSS BIT(1) +#define P54_FILTER_TYPE_AP BIT(2) +#define P54_FILTER_TYPE_TRANSPARENT BIT(3) +#define P54_FILTER_TYPE_PROMISCUOUS BIT(4) +#define P54_FILTER_TYPE_HIBERNATE BIT(5) +#define P54_FILTER_TYPE_NOACK BIT(6) +#define P54_FILTER_TYPE_RX_DISABLED BIT(7) + +struct p54_setup_mac { + __le16 mac_mode; + u8 mac_addr[ETH_ALEN]; + u8 bssid[ETH_ALEN]; + u8 rx_antenna; + u8 rx_align; + union { + struct { + __le32 basic_rate_mask; + u8 rts_rates[8]; + __le32 rx_addr; + __le16 max_rx; + __le16 rxhw; + __le16 wakeup_timer; + __le16 unalloc0; + } __packed v1; + struct { + __le32 rx_addr; + __le16 max_rx; + __le16 rxhw; + __le16 timer; + __le16 truncate; + __le32 basic_rate_mask; + u8 sbss_offset; + u8 mcast_window; + u8 rx_rssi_threshold; + u8 rx_ed_threshold; + __le32 ref_clock; + __le16 lpf_bandwidth; + __le16 osc_start_delay; + } __packed v2; + } __packed; +} __packed; + +#define P54_SETUP_V1_LEN 40 +#define P54_SETUP_V2_LEN (sizeof(struct p54_setup_mac)) + +#define P54_SCAN_EXIT BIT(0) +#define P54_SCAN_TRAP BIT(1) +#define P54_SCAN_ACTIVE BIT(2) +#define P54_SCAN_FILTER BIT(3) + +struct p54_scan_head { + __le16 mode; + __le16 dwell; + u8 scan_params[20]; + __le16 freq; +} __packed; + +struct p54_pa_curve_data_sample { + u8 rf_power; + u8 pa_detector; + u8 data_barker; + u8 data_bpsk; + u8 data_qpsk; + u8 data_16qam; + u8 data_64qam; + u8 padding; +} __packed; + +struct p54_scan_body { + u8 pa_points_per_curve; + u8 val_barker; + u8 val_bpsk; + u8 val_qpsk; + u8 val_16qam; + u8 val_64qam; + struct p54_pa_curve_data_sample curve_data[8]; + u8 dup_bpsk; + u8 dup_qpsk; + u8 dup_16qam; + u8 dup_64qam; +} __packed; + +/* + * Warning: Longbow's structures are bogus. + */ +struct p54_channel_output_limit_longbow { + __le16 rf_power_points[12]; +} __packed; + +struct p54_pa_curve_data_sample_longbow { + __le16 rf_power; + __le16 pa_detector; + struct { + __le16 data[4]; + } points[3] __packed; +} __packed; + +struct p54_scan_body_longbow { + struct p54_channel_output_limit_longbow power_limits; + struct p54_pa_curve_data_sample_longbow curve_data[8]; + __le16 unkn[6]; /* maybe more power_limits or rate_mask */ +} __packed; + +union p54_scan_body_union { + struct p54_scan_body normal; + struct p54_scan_body_longbow longbow; +} __packed; + +struct p54_scan_tail_rate { + __le32 basic_rate_mask; + u8 rts_rates[8]; +} __packed; + +struct p54_led { + __le16 flags; + __le16 mask[2]; + __le16 delay[2]; +} __packed; + +struct p54_edcf { + u8 flags; + u8 slottime; + u8 sifs; + u8 eofpad; + struct p54_edcf_queue_param queue[8]; + u8 mapping[4]; + __le16 frameburst; + __le16 round_trip_delay; +} __packed; + +struct p54_statistics { + __le32 rx_success; + __le32 rx_bad_fcs; + __le32 rx_abort; + __le32 rx_abort_phy; + __le32 rts_success; + __le32 rts_fail; + __le32 tsf32; + __le32 airtime; + __le32 noise; + __le32 sample_noise[8]; + __le32 sample_cca; + __le32 sample_tx; +} __packed; + +struct p54_xbow_synth { + __le16 magic1; + __le16 magic2; + __le16 freq; + u32 padding[5]; +} __packed; + +struct p54_timer { + __le32 interval; +} __packed; + +struct p54_keycache { + u8 entry; + u8 key_id; + u8 mac[ETH_ALEN]; + u8 padding[2]; + u8 key_type; + u8 key_len; + u8 key[24]; +} __packed; + +struct p54_burst { + u8 flags; + u8 queue; + u8 backlog; + u8 pad; + __le16 durations[32]; +} __packed; + +struct p54_psm_interval { + __le16 interval; + __le16 periods; +} __packed; + +#define P54_PSM_CAM 0 +#define P54_PSM BIT(0) +#define P54_PSM_DTIM BIT(1) +#define P54_PSM_MCBC BIT(2) +#define P54_PSM_CHECKSUM BIT(3) +#define P54_PSM_SKIP_MORE_DATA BIT(4) +#define P54_PSM_BEACON_TIMEOUT BIT(5) +#define P54_PSM_HFOSLEEP BIT(6) +#define P54_PSM_AUTOSWITCH_SLEEP BIT(7) +#define P54_PSM_LPIT BIT(8) +#define P54_PSM_BF_UCAST_SKIP BIT(9) +#define P54_PSM_BF_MCAST_SKIP BIT(10) + +struct p54_psm { + __le16 mode; + __le16 aid; + struct p54_psm_interval intervals[4]; + u8 beacon_rssi_skip_max; + u8 rssi_delta_threshold; + u8 nr; + u8 exclude[1]; +} __packed; + +#define MC_FILTER_ADDRESS_NUM 4 + +struct p54_group_address_table { + __le16 filter_enable; + __le16 num_address; + u8 mac_list[MC_FILTER_ADDRESS_NUM][ETH_ALEN]; +} __packed; + +struct p54_txcancel { + __le32 req_id; +} __packed; + +struct p54_sta_unlock { + u8 addr[ETH_ALEN]; + u16 padding; +} __packed; + +#define P54_TIM_CLEAR BIT(15) +struct p54_tim { + u8 count; + u8 padding[3]; + __le16 entry[8]; +} __packed; + +struct p54_cce_quiet { + __le32 period; +} __packed; + +struct p54_bt_balancer { + __le16 prio_thresh; + __le16 acl_thresh; +} __packed; + +struct p54_arp_table { + __le16 filter_enable; + u8 ipv4_addr[4]; +} __packed; + +/* LED control */ +int p54_set_leds(struct p54_common *priv); +int p54_init_leds(struct p54_common *priv); +void p54_unregister_leds(struct p54_common *priv); + +/* xmit functions */ +void p54_tx_80211(struct ieee80211_hw *dev, + struct ieee80211_tx_control *control, + struct sk_buff *skb); +int p54_tx_cancel(struct p54_common *priv, __le32 req_id); +void p54_tx(struct p54_common *priv, struct sk_buff *skb); + +/* synth/phy configuration */ +int p54_init_xbow_synth(struct p54_common *priv); +int p54_scan(struct p54_common *priv, u16 mode, u16 dwell); + +/* MAC */ +int p54_sta_unlock(struct p54_common *priv, u8 *addr); +int p54_update_beacon_tim(struct p54_common *priv, u16 aid, bool set); +int p54_setup_mac(struct p54_common *priv); +int p54_set_ps(struct p54_common *priv); +int p54_fetch_statistics(struct p54_common *priv); +int p54_set_groupfilter(struct p54_common *priv); + +/* e/v DCF setup */ +int p54_set_edcf(struct p54_common *priv); + +/* cryptographic engine */ +int p54_upload_key(struct p54_common *priv, u8 algo, int slot, + u8 idx, u8 len, u8 *addr, u8* key); + +/* eeprom */ +int p54_download_eeprom(struct p54_common *priv, void *buf, + u16 offset, u16 len); +struct p54_rssi_db_entry *p54_rssi_find(struct p54_common *p, const u16 freq); + +/* utility */ +u8 *p54_find_ie(struct sk_buff *skb, u8 ie); + +#endif /* LMAC_H */ diff --git a/drivers/net/wireless/p54/main.c b/drivers/net/wireless/p54/main.c new file mode 100644 index 0000000..7ab3632 --- /dev/null +++ b/drivers/net/wireless/p54/main.c @@ -0,0 +1,885 @@ +/* + * mac80211 glue code for mac80211 Prism54 drivers + * + * Copyright (c) 2006, Michael Wu + * Copyright (c) 2007-2009, Christian Lamparter + * Copyright 2008, Johannes Berg + * + * Based on: + * - the islsm (softmac prism54) driver, which is: + * Copyright 2004-2006 Jean-Baptiste Note , et al. + * - stlc45xx driver + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include + +#include "p54.h" +#include "lmac.h" + +static bool modparam_nohwcrypt; +module_param_named(nohwcrypt, modparam_nohwcrypt, bool, S_IRUGO); +MODULE_PARM_DESC(nohwcrypt, "Disable hardware encryption."); +MODULE_AUTHOR("Michael Wu "); +MODULE_DESCRIPTION("Softmac Prism54 common code"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("prism54common"); + +static int p54_sta_add_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct p54_common *priv = hw->priv; + + /* + * Notify the firmware that we don't want or we don't + * need to buffer frames for this station anymore. + */ + + p54_sta_unlock(priv, sta->addr); + + return 0; +} + +static void p54_sta_notify(struct ieee80211_hw *dev, struct ieee80211_vif *vif, + enum sta_notify_cmd notify_cmd, + struct ieee80211_sta *sta) +{ + struct p54_common *priv = dev->priv; + + switch (notify_cmd) { + case STA_NOTIFY_AWAKE: + /* update the firmware's filter table */ + p54_sta_unlock(priv, sta->addr); + break; + default: + break; + } +} + +static int p54_set_tim(struct ieee80211_hw *dev, struct ieee80211_sta *sta, + bool set) +{ + struct p54_common *priv = dev->priv; + + return p54_update_beacon_tim(priv, sta->aid, set); +} + +u8 *p54_find_ie(struct sk_buff *skb, u8 ie) +{ + struct ieee80211_mgmt *mgmt = (void *)skb->data; + u8 *pos, *end; + + if (skb->len <= sizeof(mgmt)) + return NULL; + + pos = (u8 *)mgmt->u.beacon.variable; + end = skb->data + skb->len; + while (pos < end) { + if (pos + 2 + pos[1] > end) + return NULL; + + if (pos[0] == ie) + return pos; + + pos += 2 + pos[1]; + } + return NULL; +} + +static int p54_beacon_format_ie_tim(struct sk_buff *skb) +{ + /* + * the good excuse for this mess is ... the firmware. + * The dummy TIM MUST be at the end of the beacon frame, + * because it'll be overwritten! + */ + u8 *tim; + u8 dtim_len; + u8 dtim_period; + u8 *next; + + tim = p54_find_ie(skb, WLAN_EID_TIM); + if (!tim) + return 0; + + dtim_len = tim[1]; + dtim_period = tim[3]; + next = tim + 2 + dtim_len; + + if (dtim_len < 3) + return -EINVAL; + + memmove(tim, next, skb_tail_pointer(skb) - next); + tim = skb_tail_pointer(skb) - (dtim_len + 2); + + /* add the dummy at the end */ + tim[0] = WLAN_EID_TIM; + tim[1] = 3; + tim[2] = 0; + tim[3] = dtim_period; + tim[4] = 0; + + if (dtim_len > 3) + skb_trim(skb, skb->len - (dtim_len - 3)); + + return 0; +} + +static int p54_beacon_update(struct p54_common *priv, + struct ieee80211_vif *vif) +{ + struct ieee80211_tx_control control = { }; + struct sk_buff *beacon; + int ret; + + beacon = ieee80211_beacon_get(priv->hw, vif); + if (!beacon) + return -ENOMEM; + ret = p54_beacon_format_ie_tim(beacon); + if (ret) + return ret; + + /* + * During operation, the firmware takes care of beaconing. + * The driver only needs to upload a new beacon template, once + * the template was changed by the stack or userspace. + * + * LMAC API 3.2.2 also specifies that the driver does not need + * to cancel the old beacon template by hand, instead the firmware + * will release the previous one through the feedback mechanism. + */ + p54_tx_80211(priv->hw, &control, beacon); + priv->tsf_high32 = 0; + priv->tsf_low32 = 0; + + return 0; +} + +static int p54_start(struct ieee80211_hw *dev) +{ + struct p54_common *priv = dev->priv; + int err; + + mutex_lock(&priv->conf_mutex); + err = priv->open(dev); + if (err) + goto out; + P54_SET_QUEUE(priv->qos_params[0], 0x0002, 0x0003, 0x0007, 47); + P54_SET_QUEUE(priv->qos_params[1], 0x0002, 0x0007, 0x000f, 94); + P54_SET_QUEUE(priv->qos_params[2], 0x0003, 0x000f, 0x03ff, 0); + P54_SET_QUEUE(priv->qos_params[3], 0x0007, 0x000f, 0x03ff, 0); + err = p54_set_edcf(priv); + if (err) + goto out; + + memset(priv->bssid, ~0, ETH_ALEN); + priv->mode = NL80211_IFTYPE_MONITOR; + err = p54_setup_mac(priv); + if (err) { + priv->mode = NL80211_IFTYPE_UNSPECIFIED; + goto out; + } + + ieee80211_queue_delayed_work(dev, &priv->work, 0); + + priv->softled_state = 0; + err = p54_set_leds(priv); + +out: + mutex_unlock(&priv->conf_mutex); + return err; +} + +static void p54_stop(struct ieee80211_hw *dev) +{ + struct p54_common *priv = dev->priv; + int i; + + priv->mode = NL80211_IFTYPE_UNSPECIFIED; + priv->softled_state = 0; + cancel_delayed_work_sync(&priv->work); + mutex_lock(&priv->conf_mutex); + p54_set_leds(priv); + priv->stop(dev); + skb_queue_purge(&priv->tx_pending); + skb_queue_purge(&priv->tx_queue); + for (i = 0; i < P54_QUEUE_NUM; i++) { + priv->tx_stats[i].count = 0; + priv->tx_stats[i].len = 0; + } + + priv->beacon_req_id = cpu_to_le32(0); + priv->tsf_high32 = priv->tsf_low32 = 0; + mutex_unlock(&priv->conf_mutex); +} + +static int p54_add_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif) +{ + struct p54_common *priv = dev->priv; + int err; + + vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER; + + mutex_lock(&priv->conf_mutex); + if (priv->mode != NL80211_IFTYPE_MONITOR) { + mutex_unlock(&priv->conf_mutex); + return -EOPNOTSUPP; + } + + priv->vif = vif; + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_MESH_POINT: + priv->mode = vif->type; + break; + default: + mutex_unlock(&priv->conf_mutex); + return -EOPNOTSUPP; + } + + memcpy(priv->mac_addr, vif->addr, ETH_ALEN); + err = p54_setup_mac(priv); + mutex_unlock(&priv->conf_mutex); + return err; +} + +static void p54_remove_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif) +{ + struct p54_common *priv = dev->priv; + + mutex_lock(&priv->conf_mutex); + priv->vif = NULL; + + /* + * LMAC API 3.2.2 states that any active beacon template must be + * canceled by the driver before attempting a mode transition. + */ + if (le32_to_cpu(priv->beacon_req_id) != 0) { + p54_tx_cancel(priv, priv->beacon_req_id); + wait_for_completion_interruptible_timeout(&priv->beacon_comp, HZ); + } + priv->mode = NL80211_IFTYPE_MONITOR; + memset(priv->mac_addr, 0, ETH_ALEN); + memset(priv->bssid, 0, ETH_ALEN); + p54_setup_mac(priv); + mutex_unlock(&priv->conf_mutex); +} + +static int p54_wait_for_stats(struct ieee80211_hw *dev) +{ + struct p54_common *priv = dev->priv; + int ret; + + priv->update_stats = true; + ret = p54_fetch_statistics(priv); + if (ret) + return ret; + + ret = wait_for_completion_interruptible_timeout(&priv->stat_comp, HZ); + if (ret == 0) + return -ETIMEDOUT; + + return 0; +} + +static void p54_reset_stats(struct p54_common *priv) +{ + struct ieee80211_channel *chan = priv->curchan; + + if (chan) { + struct survey_info *info = &priv->survey[chan->hw_value]; + + /* only reset channel statistics, don't touch .filled, etc. */ + info->channel_time = 0; + info->channel_time_busy = 0; + info->channel_time_tx = 0; + } + + priv->update_stats = true; + priv->survey_raw.active = 0; + priv->survey_raw.cca = 0; + priv->survey_raw.tx = 0; +} + +static int p54_config(struct ieee80211_hw *dev, u32 changed) +{ + int ret = 0; + struct p54_common *priv = dev->priv; + struct ieee80211_conf *conf = &dev->conf; + + mutex_lock(&priv->conf_mutex); + if (changed & IEEE80211_CONF_CHANGE_POWER) + priv->output_power = conf->power_level << 2; + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + struct ieee80211_channel *oldchan; + WARN_ON(p54_wait_for_stats(dev)); + oldchan = priv->curchan; + priv->curchan = NULL; + ret = p54_scan(priv, P54_SCAN_EXIT, 0); + if (ret) { + priv->curchan = oldchan; + goto out; + } + /* + * TODO: Use the LM_SCAN_TRAP to determine the current + * operating channel. + */ + priv->curchan = priv->hw->conf.chandef.chan; + p54_reset_stats(priv); + WARN_ON(p54_fetch_statistics(priv)); + } + if (changed & IEEE80211_CONF_CHANGE_PS) { + WARN_ON(p54_wait_for_stats(dev)); + ret = p54_set_ps(priv); + if (ret) + goto out; + WARN_ON(p54_wait_for_stats(dev)); + } + if (changed & IEEE80211_CONF_CHANGE_IDLE) { + WARN_ON(p54_wait_for_stats(dev)); + ret = p54_setup_mac(priv); + if (ret) + goto out; + WARN_ON(p54_wait_for_stats(dev)); + } + +out: + mutex_unlock(&priv->conf_mutex); + return ret; +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) +static u64 p54_prepare_multicast(struct ieee80211_hw *dev, + struct netdev_hw_addr_list *mc_list) +#else +static u64 p54_prepare_multicast(struct ieee80211_hw *dev, int mc_count, + struct dev_addr_list *ha) +#endif +{ + struct p54_common *priv = dev->priv; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + struct netdev_hw_addr *ha; +#endif + int i; + + BUILD_BUG_ON(ARRAY_SIZE(priv->mc_maclist) != + ARRAY_SIZE(((struct p54_group_address_table *)NULL)->mac_list)); + /* + * The first entry is reserved for the global broadcast MAC. + * Otherwise the firmware will drop it and ARP will no longer work. + */ + i = 1; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + priv->mc_maclist_num = netdev_hw_addr_list_count(mc_list) + i; + netdev_hw_addr_list_for_each(ha, mc_list) { + memcpy(&priv->mc_maclist[i], ha->addr, ETH_ALEN); +#else + priv->mc_maclist_num = mc_count + i; + while (i <= mc_count) { + if (!ha) + break; + memcpy(&priv->mc_maclist[i], ha->dmi_addr, ETH_ALEN); +#endif + i++; + if (i >= ARRAY_SIZE(priv->mc_maclist)) + break; +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)) + ha = ha->next; +#endif + } + + return 1; /* update */ +} + +static void p54_configure_filter(struct ieee80211_hw *dev, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + struct p54_common *priv = dev->priv; + + *total_flags &= FIF_PROMISC_IN_BSS | + FIF_ALLMULTI | + FIF_OTHER_BSS; + + priv->filter_flags = *total_flags; + + if (changed_flags & (FIF_PROMISC_IN_BSS | FIF_OTHER_BSS)) + p54_setup_mac(priv); + + if (changed_flags & FIF_ALLMULTI || multicast) + p54_set_groupfilter(priv); +} + +static int p54_conf_tx(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct p54_common *priv = dev->priv; + int ret; + + mutex_lock(&priv->conf_mutex); + if (queue < dev->queues) { + P54_SET_QUEUE(priv->qos_params[queue], params->aifs, + params->cw_min, params->cw_max, params->txop); + ret = p54_set_edcf(priv); + } else + ret = -EINVAL; + mutex_unlock(&priv->conf_mutex); + return ret; +} + +static void p54_work(struct work_struct *work) +{ + struct p54_common *priv = container_of(work, struct p54_common, + work.work); + + if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED)) + return ; + + /* + * TODO: walk through tx_queue and do the following tasks + * 1. initiate bursts. + * 2. cancel stuck frames / reset the device if necessary. + */ + + mutex_lock(&priv->conf_mutex); + WARN_ON_ONCE(p54_fetch_statistics(priv)); + mutex_unlock(&priv->conf_mutex); +} + +static int p54_get_stats(struct ieee80211_hw *dev, + struct ieee80211_low_level_stats *stats) +{ + struct p54_common *priv = dev->priv; + + memcpy(stats, &priv->stats, sizeof(*stats)); + return 0; +} + +static void p54_bss_info_changed(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u32 changed) +{ + struct p54_common *priv = dev->priv; + + mutex_lock(&priv->conf_mutex); + if (changed & BSS_CHANGED_BSSID) { + memcpy(priv->bssid, info->bssid, ETH_ALEN); + p54_setup_mac(priv); + } + + if (changed & BSS_CHANGED_BEACON) { + p54_scan(priv, P54_SCAN_EXIT, 0); + p54_setup_mac(priv); + p54_beacon_update(priv, vif); + p54_set_edcf(priv); + } + + if (changed & (BSS_CHANGED_ERP_SLOT | BSS_CHANGED_BEACON)) { + priv->use_short_slot = info->use_short_slot; + p54_set_edcf(priv); + } + if (changed & BSS_CHANGED_BASIC_RATES) { + if (dev->conf.chandef.chan->band == IEEE80211_BAND_5GHZ) + priv->basic_rate_mask = (info->basic_rates << 4); + else + priv->basic_rate_mask = info->basic_rates; + p54_setup_mac(priv); + if (priv->fw_var >= 0x500) + p54_scan(priv, P54_SCAN_EXIT, 0); + } + if (changed & BSS_CHANGED_ASSOC) { + if (info->assoc) { + priv->aid = info->aid; + priv->wakeup_timer = info->beacon_int * + info->dtim_period * 5; + p54_setup_mac(priv); + } else { + priv->wakeup_timer = 500; + priv->aid = 0; + } + } + + mutex_unlock(&priv->conf_mutex); +} + +static int p54_set_key(struct ieee80211_hw *dev, enum set_key_cmd cmd, + struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct p54_common *priv = dev->priv; + int slot, ret = 0; + u8 algo = 0; + u8 *addr = NULL; + + if (modparam_nohwcrypt) + return -EOPNOTSUPP; + + if (key->flags & IEEE80211_KEY_FLAG_RX_MGMT) { + /* + * Unfortunately most/all firmwares are trying to decrypt + * incoming management frames if a suitable key can be found. + * However, in doing so the data in these frames gets + * corrupted. So, we can't have firmware supported crypto + * offload in this case. + */ + return -EOPNOTSUPP; + } + + mutex_lock(&priv->conf_mutex); + if (cmd == SET_KEY) { + switch (key->cipher) { + case WLAN_CIPHER_SUITE_TKIP: + if (!(priv->privacy_caps & (BR_DESC_PRIV_CAP_MICHAEL | + BR_DESC_PRIV_CAP_TKIP))) { + ret = -EOPNOTSUPP; + goto out_unlock; + } + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + algo = P54_CRYPTO_TKIPMICHAEL; + break; + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + if (!(priv->privacy_caps & BR_DESC_PRIV_CAP_WEP)) { + ret = -EOPNOTSUPP; + goto out_unlock; + } + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + algo = P54_CRYPTO_WEP; + break; + case WLAN_CIPHER_SUITE_CCMP: + if (!(priv->privacy_caps & BR_DESC_PRIV_CAP_AESCCMP)) { + ret = -EOPNOTSUPP; + goto out_unlock; + } + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + algo = P54_CRYPTO_AESCCMP; + break; + default: + ret = -EOPNOTSUPP; + goto out_unlock; + } + slot = bitmap_find_free_region(priv->used_rxkeys, + priv->rx_keycache_size, 0); + + if (slot < 0) { + /* + * The device supports the chosen algorithm, but the + * firmware does not provide enough key slots to store + * all of them. + * But encryption offload for outgoing frames is always + * possible, so we just pretend that the upload was + * successful and do the decryption in software. + */ + + /* mark the key as invalid. */ + key->hw_key_idx = 0xff; + goto out_unlock; + } + } else { + slot = key->hw_key_idx; + + if (slot == 0xff) { + /* This key was not uploaded into the rx key cache. */ + + goto out_unlock; + } + + bitmap_release_region(priv->used_rxkeys, slot, 0); + algo = 0; + } + + if (sta) + addr = sta->addr; + + ret = p54_upload_key(priv, algo, slot, key->keyidx, + key->keylen, addr, key->key); + if (ret) { + bitmap_release_region(priv->used_rxkeys, slot, 0); + ret = -EOPNOTSUPP; + goto out_unlock; + } + + key->hw_key_idx = slot; + +out_unlock: + mutex_unlock(&priv->conf_mutex); + return ret; +} + +static int p54_get_survey(struct ieee80211_hw *dev, int idx, + struct survey_info *survey) +{ + struct p54_common *priv = dev->priv; + struct ieee80211_channel *chan; + int err, tries; + bool in_use = false; + + if (idx >= priv->chan_num) + return -ENOENT; + +#define MAX_TRIES 1 + for (tries = 0; tries < MAX_TRIES; tries++) { + chan = priv->curchan; + if (chan && chan->hw_value == idx) { + mutex_lock(&priv->conf_mutex); + err = p54_wait_for_stats(dev); + mutex_unlock(&priv->conf_mutex); + if (err) + return err; + + in_use = true; + } + + memcpy(survey, &priv->survey[idx], sizeof(*survey)); + + if (in_use) { + /* test if the reported statistics are valid. */ + if (survey->channel_time != 0) { + survey->filled |= SURVEY_INFO_IN_USE; + } else { + /* + * hw/fw has not accumulated enough sample sets. + * Wait for 100ms, this ought to be enough to + * to get at least one non-null set of channel + * usage statistics. + */ + msleep(100); + continue; + } + } + return 0; + } + return -ETIMEDOUT; +#undef MAX_TRIES +} + +static unsigned int p54_flush_count(struct p54_common *priv) +{ + unsigned int total = 0, i; + + BUILD_BUG_ON(P54_QUEUE_NUM > ARRAY_SIZE(priv->tx_stats)); + + /* + * Because the firmware has the sole control over any frames + * in the P54_QUEUE_BEACON or P54_QUEUE_SCAN queues, they + * don't really count as pending or active. + */ + for (i = P54_QUEUE_MGMT; i < P54_QUEUE_NUM; i++) + total += priv->tx_stats[i].len; + return total; +} + +static void p54_flush(struct ieee80211_hw *dev, u32 queues, bool drop) +{ + struct p54_common *priv = dev->priv; + unsigned int total, i; + + /* + * Currently, it wouldn't really matter if we wait for one second + * or 15 minutes. But once someone gets around and completes the + * TODOs [ancel stuck frames / reset device] in p54_work, it will + * suddenly make sense to wait that long. + */ + i = P54_STATISTICS_UPDATE * 2 / 20; + + /* + * In this case no locking is required because as we speak the + * queues have already been stopped and no new frames can sneak + * up from behind. + */ + while ((total = p54_flush_count(priv) && i--)) { + /* waste time */ + msleep(20); + } + + WARN(total, "tx flush timeout, unresponsive firmware"); +} + +static void p54_set_coverage_class(struct ieee80211_hw *dev, u8 coverage_class) +{ + struct p54_common *priv = dev->priv; + + mutex_lock(&priv->conf_mutex); + /* support all coverage class values as in 802.11-2007 Table 7-27 */ + priv->coverage_class = clamp_t(u8, coverage_class, 0, 31); + p54_set_edcf(priv); + mutex_unlock(&priv->conf_mutex); +} + +static const struct ieee80211_ops p54_ops = { + .tx = p54_tx_80211, + .start = p54_start, + .stop = p54_stop, + .add_interface = p54_add_interface, + .remove_interface = p54_remove_interface, + .set_tim = p54_set_tim, + .sta_notify = p54_sta_notify, + .sta_add = p54_sta_add_remove, + .sta_remove = p54_sta_add_remove, + .set_key = p54_set_key, + .config = p54_config, + .flush = p54_flush, + .bss_info_changed = p54_bss_info_changed, + .prepare_multicast = p54_prepare_multicast, + .configure_filter = p54_configure_filter, + .conf_tx = p54_conf_tx, + .get_stats = p54_get_stats, + .get_survey = p54_get_survey, + .set_coverage_class = p54_set_coverage_class, +}; + +struct ieee80211_hw *p54_init_common(size_t priv_data_len) +{ + struct ieee80211_hw *dev; + struct p54_common *priv; + + dev = ieee80211_alloc_hw(priv_data_len, &p54_ops); + if (!dev) + return NULL; + + priv = dev->priv; + priv->hw = dev; + priv->mode = NL80211_IFTYPE_UNSPECIFIED; + priv->basic_rate_mask = 0x15f; + spin_lock_init(&priv->tx_stats_lock); + skb_queue_head_init(&priv->tx_queue); + skb_queue_head_init(&priv->tx_pending); + dev->flags = IEEE80211_HW_RX_INCLUDES_FCS | + IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_PS_NULLFUNC_STACK | + IEEE80211_HW_MFP_CAPABLE | + IEEE80211_HW_REPORTS_TX_ACK_STATUS; + + dev->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_MESH_POINT); + + dev->channel_change_time = 1000; /* TODO: find actual value */ + priv->beacon_req_id = cpu_to_le32(0); + priv->tx_stats[P54_QUEUE_BEACON].limit = 1; + priv->tx_stats[P54_QUEUE_FWSCAN].limit = 1; + priv->tx_stats[P54_QUEUE_MGMT].limit = 3; + priv->tx_stats[P54_QUEUE_CAB].limit = 3; + priv->tx_stats[P54_QUEUE_DATA].limit = 5; + dev->queues = 1; + priv->noise = -94; + /* + * We support at most 8 tries no matter which rate they're at, + * we cannot support max_rates * max_rate_tries as we set it + * here, but setting it correctly to 4/2 or so would limit us + * artificially if the RC algorithm wants just two rates, so + * let's say 4/7, we'll redistribute it at TX time, see the + * comments there. + */ + dev->max_rates = 4; + dev->max_rate_tries = 7; + dev->extra_tx_headroom = sizeof(struct p54_hdr) + 4 + + sizeof(struct p54_tx_data); + + /* + * For now, disable PS by default because it affects + * link stability significantly. + */ + dev->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + + mutex_init(&priv->conf_mutex); + mutex_init(&priv->eeprom_mutex); + init_completion(&priv->stat_comp); + init_completion(&priv->eeprom_comp); + init_completion(&priv->beacon_comp); + INIT_DELAYED_WORK(&priv->work, p54_work); + + memset(&priv->mc_maclist[0], ~0, ETH_ALEN); + priv->curchan = NULL; + p54_reset_stats(priv); + return dev; +} +EXPORT_SYMBOL_GPL(p54_init_common); + +int p54_register_common(struct ieee80211_hw *dev, struct device *pdev) +{ + struct p54_common __maybe_unused *priv = dev->priv; + int err; + + err = ieee80211_register_hw(dev); + if (err) { + dev_err(pdev, "Cannot register device (%d).\n", err); + return err; + } + priv->registered = true; + +#ifdef CPTCFG_P54_LEDS + err = p54_init_leds(priv); + if (err) { + p54_unregister_common(dev); + return err; + } +#endif /* CPTCFG_P54_LEDS */ + + dev_info(pdev, "is registered as '%s'\n", wiphy_name(dev->wiphy)); + return 0; +} +EXPORT_SYMBOL_GPL(p54_register_common); + +void p54_free_common(struct ieee80211_hw *dev) +{ + struct p54_common *priv = dev->priv; + unsigned int i; + + for (i = 0; i < IEEE80211_NUM_BANDS; i++) + kfree(priv->band_table[i]); + + kfree(priv->iq_autocal); + kfree(priv->output_limit); + kfree(priv->curve_data); + kfree(priv->rssi_db); + kfree(priv->used_rxkeys); + kfree(priv->survey); + priv->iq_autocal = NULL; + priv->output_limit = NULL; + priv->curve_data = NULL; + priv->rssi_db = NULL; + priv->used_rxkeys = NULL; + priv->survey = NULL; + ieee80211_free_hw(dev); +} +EXPORT_SYMBOL_GPL(p54_free_common); + +void p54_unregister_common(struct ieee80211_hw *dev) +{ + struct p54_common *priv = dev->priv; + +#ifdef CPTCFG_P54_LEDS + p54_unregister_leds(priv); +#endif /* CPTCFG_P54_LEDS */ + + if (priv->registered) { + priv->registered = false; + ieee80211_unregister_hw(dev); + } + + mutex_destroy(&priv->conf_mutex); + mutex_destroy(&priv->eeprom_mutex); +} +EXPORT_SYMBOL_GPL(p54_unregister_common); diff --git a/drivers/net/wireless/p54/net2280.h b/drivers/net/wireless/p54/net2280.h new file mode 100644 index 0000000..e3ed893 --- /dev/null +++ b/drivers/net/wireless/p54/net2280.h @@ -0,0 +1,452 @@ +#ifndef NET2280_H +#define NET2280_H +/* + * NetChip 2280 high/full speed USB device controller. + * Unlike many such controllers, this one talks PCI. + */ + +/* + * Copyright (C) 2002 NetChip Technology, Inc. (http://www.netchip.com) + * Copyright (C) 2003 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/*-------------------------------------------------------------------------*/ + +/* NET2280 MEMORY MAPPED REGISTERS + * + * The register layout came from the chip documentation, and the bit + * number definitions were extracted from chip specification. + * + * Use the shift operator ('<<') to build bit masks, with readl/writel + * to access the registers through PCI. + */ + +/* main registers, BAR0 + 0x0000 */ +struct net2280_regs { + /* offset 0x0000 */ + __le32 devinit; +#define LOCAL_CLOCK_FREQUENCY 8 +#define FORCE_PCI_RESET 7 +#define PCI_ID 6 +#define PCI_ENABLE 5 +#define FIFO_SOFT_RESET 4 +#define CFG_SOFT_RESET 3 +#define PCI_SOFT_RESET 2 +#define USB_SOFT_RESET 1 +#define M8051_RESET 0 + __le32 eectl; +#define EEPROM_ADDRESS_WIDTH 23 +#define EEPROM_CHIP_SELECT_ACTIVE 22 +#define EEPROM_PRESENT 21 +#define EEPROM_VALID 20 +#define EEPROM_BUSY 19 +#define EEPROM_CHIP_SELECT_ENABLE 18 +#define EEPROM_BYTE_READ_START 17 +#define EEPROM_BYTE_WRITE_START 16 +#define EEPROM_READ_DATA 8 +#define EEPROM_WRITE_DATA 0 + __le32 eeclkfreq; + u32 _unused0; + /* offset 0x0010 */ + + __le32 pciirqenb0; /* interrupt PCI master ... */ +#define SETUP_PACKET_INTERRUPT_ENABLE 7 +#define ENDPOINT_F_INTERRUPT_ENABLE 6 +#define ENDPOINT_E_INTERRUPT_ENABLE 5 +#define ENDPOINT_D_INTERRUPT_ENABLE 4 +#define ENDPOINT_C_INTERRUPT_ENABLE 3 +#define ENDPOINT_B_INTERRUPT_ENABLE 2 +#define ENDPOINT_A_INTERRUPT_ENABLE 1 +#define ENDPOINT_0_INTERRUPT_ENABLE 0 + __le32 pciirqenb1; +#define PCI_INTERRUPT_ENABLE 31 +#define POWER_STATE_CHANGE_INTERRUPT_ENABLE 27 +#define PCI_ARBITER_TIMEOUT_INTERRUPT_ENABLE 26 +#define PCI_PARITY_ERROR_INTERRUPT_ENABLE 25 +#define PCI_MASTER_ABORT_RECEIVED_INTERRUPT_ENABLE 20 +#define PCI_TARGET_ABORT_RECEIVED_INTERRUPT_ENABLE 19 +#define PCI_TARGET_ABORT_ASSERTED_INTERRUPT_ENABLE 18 +#define PCI_RETRY_ABORT_INTERRUPT_ENABLE 17 +#define PCI_MASTER_CYCLE_DONE_INTERRUPT_ENABLE 16 +#define GPIO_INTERRUPT_ENABLE 13 +#define DMA_D_INTERRUPT_ENABLE 12 +#define DMA_C_INTERRUPT_ENABLE 11 +#define DMA_B_INTERRUPT_ENABLE 10 +#define DMA_A_INTERRUPT_ENABLE 9 +#define EEPROM_DONE_INTERRUPT_ENABLE 8 +#define VBUS_INTERRUPT_ENABLE 7 +#define CONTROL_STATUS_INTERRUPT_ENABLE 6 +#define ROOT_PORT_RESET_INTERRUPT_ENABLE 4 +#define SUSPEND_REQUEST_INTERRUPT_ENABLE 3 +#define SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE 2 +#define RESUME_INTERRUPT_ENABLE 1 +#define SOF_INTERRUPT_ENABLE 0 + __le32 cpu_irqenb0; /* ... or onboard 8051 */ +#define SETUP_PACKET_INTERRUPT_ENABLE 7 +#define ENDPOINT_F_INTERRUPT_ENABLE 6 +#define ENDPOINT_E_INTERRUPT_ENABLE 5 +#define ENDPOINT_D_INTERRUPT_ENABLE 4 +#define ENDPOINT_C_INTERRUPT_ENABLE 3 +#define ENDPOINT_B_INTERRUPT_ENABLE 2 +#define ENDPOINT_A_INTERRUPT_ENABLE 1 +#define ENDPOINT_0_INTERRUPT_ENABLE 0 + __le32 cpu_irqenb1; +#define CPU_INTERRUPT_ENABLE 31 +#define POWER_STATE_CHANGE_INTERRUPT_ENABLE 27 +#define PCI_ARBITER_TIMEOUT_INTERRUPT_ENABLE 26 +#define PCI_PARITY_ERROR_INTERRUPT_ENABLE 25 +#define PCI_INTA_INTERRUPT_ENABLE 24 +#define PCI_PME_INTERRUPT_ENABLE 23 +#define PCI_SERR_INTERRUPT_ENABLE 22 +#define PCI_PERR_INTERRUPT_ENABLE 21 +#define PCI_MASTER_ABORT_RECEIVED_INTERRUPT_ENABLE 20 +#define PCI_TARGET_ABORT_RECEIVED_INTERRUPT_ENABLE 19 +#define PCI_RETRY_ABORT_INTERRUPT_ENABLE 17 +#define PCI_MASTER_CYCLE_DONE_INTERRUPT_ENABLE 16 +#define GPIO_INTERRUPT_ENABLE 13 +#define DMA_D_INTERRUPT_ENABLE 12 +#define DMA_C_INTERRUPT_ENABLE 11 +#define DMA_B_INTERRUPT_ENABLE 10 +#define DMA_A_INTERRUPT_ENABLE 9 +#define EEPROM_DONE_INTERRUPT_ENABLE 8 +#define VBUS_INTERRUPT_ENABLE 7 +#define CONTROL_STATUS_INTERRUPT_ENABLE 6 +#define ROOT_PORT_RESET_INTERRUPT_ENABLE 4 +#define SUSPEND_REQUEST_INTERRUPT_ENABLE 3 +#define SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE 2 +#define RESUME_INTERRUPT_ENABLE 1 +#define SOF_INTERRUPT_ENABLE 0 + + /* offset 0x0020 */ + u32 _unused1; + __le32 usbirqenb1; +#define USB_INTERRUPT_ENABLE 31 +#define POWER_STATE_CHANGE_INTERRUPT_ENABLE 27 +#define PCI_ARBITER_TIMEOUT_INTERRUPT_ENABLE 26 +#define PCI_PARITY_ERROR_INTERRUPT_ENABLE 25 +#define PCI_INTA_INTERRUPT_ENABLE 24 +#define PCI_PME_INTERRUPT_ENABLE 23 +#define PCI_SERR_INTERRUPT_ENABLE 22 +#define PCI_PERR_INTERRUPT_ENABLE 21 +#define PCI_MASTER_ABORT_RECEIVED_INTERRUPT_ENABLE 20 +#define PCI_TARGET_ABORT_RECEIVED_INTERRUPT_ENABLE 19 +#define PCI_RETRY_ABORT_INTERRUPT_ENABLE 17 +#define PCI_MASTER_CYCLE_DONE_INTERRUPT_ENABLE 16 +#define GPIO_INTERRUPT_ENABLE 13 +#define DMA_D_INTERRUPT_ENABLE 12 +#define DMA_C_INTERRUPT_ENABLE 11 +#define DMA_B_INTERRUPT_ENABLE 10 +#define DMA_A_INTERRUPT_ENABLE 9 +#define EEPROM_DONE_INTERRUPT_ENABLE 8 +#define VBUS_INTERRUPT_ENABLE 7 +#define CONTROL_STATUS_INTERRUPT_ENABLE 6 +#define ROOT_PORT_RESET_INTERRUPT_ENABLE 4 +#define SUSPEND_REQUEST_INTERRUPT_ENABLE 3 +#define SUSPEND_REQUEST_CHANGE_INTERRUPT_ENABLE 2 +#define RESUME_INTERRUPT_ENABLE 1 +#define SOF_INTERRUPT_ENABLE 0 + __le32 irqstat0; +#define INTA_ASSERTED 12 +#define SETUP_PACKET_INTERRUPT 7 +#define ENDPOINT_F_INTERRUPT 6 +#define ENDPOINT_E_INTERRUPT 5 +#define ENDPOINT_D_INTERRUPT 4 +#define ENDPOINT_C_INTERRUPT 3 +#define ENDPOINT_B_INTERRUPT 2 +#define ENDPOINT_A_INTERRUPT 1 +#define ENDPOINT_0_INTERRUPT 0 + __le32 irqstat1; +#define POWER_STATE_CHANGE_INTERRUPT 27 +#define PCI_ARBITER_TIMEOUT_INTERRUPT 26 +#define PCI_PARITY_ERROR_INTERRUPT 25 +#define PCI_INTA_INTERRUPT 24 +#define PCI_PME_INTERRUPT 23 +#define PCI_SERR_INTERRUPT 22 +#define PCI_PERR_INTERRUPT 21 +#define PCI_MASTER_ABORT_RECEIVED_INTERRUPT 20 +#define PCI_TARGET_ABORT_RECEIVED_INTERRUPT 19 +#define PCI_RETRY_ABORT_INTERRUPT 17 +#define PCI_MASTER_CYCLE_DONE_INTERRUPT 16 +#define GPIO_INTERRUPT 13 +#define DMA_D_INTERRUPT 12 +#define DMA_C_INTERRUPT 11 +#define DMA_B_INTERRUPT 10 +#define DMA_A_INTERRUPT 9 +#define EEPROM_DONE_INTERRUPT 8 +#define VBUS_INTERRUPT 7 +#define CONTROL_STATUS_INTERRUPT 6 +#define ROOT_PORT_RESET_INTERRUPT 4 +#define SUSPEND_REQUEST_INTERRUPT 3 +#define SUSPEND_REQUEST_CHANGE_INTERRUPT 2 +#define RESUME_INTERRUPT 1 +#define SOF_INTERRUPT 0 + /* offset 0x0030 */ + __le32 idxaddr; + __le32 idxdata; + __le32 fifoctl; +#define PCI_BASE2_RANGE 16 +#define IGNORE_FIFO_AVAILABILITY 3 +#define PCI_BASE2_SELECT 2 +#define FIFO_CONFIGURATION_SELECT 0 + u32 _unused2; + /* offset 0x0040 */ + __le32 memaddr; +#define START 28 +#define DIRECTION 27 +#define FIFO_DIAGNOSTIC_SELECT 24 +#define MEMORY_ADDRESS 0 + __le32 memdata0; + __le32 memdata1; + u32 _unused3; + /* offset 0x0050 */ + __le32 gpioctl; +#define GPIO3_LED_SELECT 12 +#define GPIO3_INTERRUPT_ENABLE 11 +#define GPIO2_INTERRUPT_ENABLE 10 +#define GPIO1_INTERRUPT_ENABLE 9 +#define GPIO0_INTERRUPT_ENABLE 8 +#define GPIO3_OUTPUT_ENABLE 7 +#define GPIO2_OUTPUT_ENABLE 6 +#define GPIO1_OUTPUT_ENABLE 5 +#define GPIO0_OUTPUT_ENABLE 4 +#define GPIO3_DATA 3 +#define GPIO2_DATA 2 +#define GPIO1_DATA 1 +#define GPIO0_DATA 0 + __le32 gpiostat; +#define GPIO3_INTERRUPT 3 +#define GPIO2_INTERRUPT 2 +#define GPIO1_INTERRUPT 1 +#define GPIO0_INTERRUPT 0 +} __packed; + +/* usb control, BAR0 + 0x0080 */ +struct net2280_usb_regs { + /* offset 0x0080 */ + __le32 stdrsp; +#define STALL_UNSUPPORTED_REQUESTS 31 +#define SET_TEST_MODE 16 +#define GET_OTHER_SPEED_CONFIGURATION 15 +#define GET_DEVICE_QUALIFIER 14 +#define SET_ADDRESS 13 +#define ENDPOINT_SET_CLEAR_HALT 12 +#define DEVICE_SET_CLEAR_DEVICE_REMOTE_WAKEUP 11 +#define GET_STRING_DESCRIPTOR_2 10 +#define GET_STRING_DESCRIPTOR_1 9 +#define GET_STRING_DESCRIPTOR_0 8 +#define GET_SET_INTERFACE 6 +#define GET_SET_CONFIGURATION 5 +#define GET_CONFIGURATION_DESCRIPTOR 4 +#define GET_DEVICE_DESCRIPTOR 3 +#define GET_ENDPOINT_STATUS 2 +#define GET_INTERFACE_STATUS 1 +#define GET_DEVICE_STATUS 0 + __le32 prodvendid; +#define PRODUCT_ID 16 +#define VENDOR_ID 0 + __le32 relnum; + __le32 usbctl; +#define SERIAL_NUMBER_INDEX 16 +#define PRODUCT_ID_STRING_ENABLE 13 +#define VENDOR_ID_STRING_ENABLE 12 +#define USB_ROOT_PORT_WAKEUP_ENABLE 11 +#define VBUS_PIN 10 +#define TIMED_DISCONNECT 9 +#define SUSPEND_IMMEDIATELY 7 +#define SELF_POWERED_USB_DEVICE 6 +#define REMOTE_WAKEUP_SUPPORT 5 +#define PME_POLARITY 4 +#define USB_DETECT_ENABLE 3 +#define PME_WAKEUP_ENABLE 2 +#define DEVICE_REMOTE_WAKEUP_ENABLE 1 +#define SELF_POWERED_STATUS 0 + /* offset 0x0090 */ + __le32 usbstat; +#define HIGH_SPEED 7 +#define FULL_SPEED 6 +#define GENERATE_RESUME 5 +#define GENERATE_DEVICE_REMOTE_WAKEUP 4 + __le32 xcvrdiag; +#define FORCE_HIGH_SPEED_MODE 31 +#define FORCE_FULL_SPEED_MODE 30 +#define USB_TEST_MODE 24 +#define LINE_STATE 16 +#define TRANSCEIVER_OPERATION_MODE 2 +#define TRANSCEIVER_SELECT 1 +#define TERMINATION_SELECT 0 + __le32 setup0123; + __le32 setup4567; + /* offset 0x0090 */ + u32 _unused0; + __le32 ouraddr; +#define FORCE_IMMEDIATE 7 +#define OUR_USB_ADDRESS 0 + __le32 ourconfig; +} __packed; + +/* pci control, BAR0 + 0x0100 */ +struct net2280_pci_regs { + /* offset 0x0100 */ + __le32 pcimstctl; +#define PCI_ARBITER_PARK_SELECT 13 +#define PCI_MULTI LEVEL_ARBITER 12 +#define PCI_RETRY_ABORT_ENABLE 11 +#define DMA_MEMORY_WRITE_AND_INVALIDATE_ENABLE 10 +#define DMA_READ_MULTIPLE_ENABLE 9 +#define DMA_READ_LINE_ENABLE 8 +#define PCI_MASTER_COMMAND_SELECT 6 +#define MEM_READ_OR_WRITE 0 +#define IO_READ_OR_WRITE 1 +#define CFG_READ_OR_WRITE 2 +#define PCI_MASTER_START 5 +#define PCI_MASTER_READ_WRITE 4 +#define PCI_MASTER_WRITE 0 +#define PCI_MASTER_READ 1 +#define PCI_MASTER_BYTE_WRITE_ENABLES 0 + __le32 pcimstaddr; + __le32 pcimstdata; + __le32 pcimststat; +#define PCI_ARBITER_CLEAR 2 +#define PCI_EXTERNAL_ARBITER 1 +#define PCI_HOST_MODE 0 +} __packed; + +/* dma control, BAR0 + 0x0180 ... array of four structs like this, + * for channels 0..3. see also struct net2280_dma: descriptor + * that can be loaded into some of these registers. + */ +struct net2280_dma_regs { /* [11.7] */ + /* offset 0x0180, 0x01a0, 0x01c0, 0x01e0, */ + __le32 dmactl; +#define DMA_SCATTER_GATHER_DONE_INTERRUPT_ENABLE 25 +#define DMA_CLEAR_COUNT_ENABLE 21 +#define DESCRIPTOR_POLLING_RATE 19 +#define POLL_CONTINUOUS 0 +#define POLL_1_USEC 1 +#define POLL_100_USEC 2 +#define POLL_1_MSEC 3 +#define DMA_VALID_BIT_POLLING_ENABLE 18 +#define DMA_VALID_BIT_ENABLE 17 +#define DMA_SCATTER_GATHER_ENABLE 16 +#define DMA_OUT_AUTO_START_ENABLE 4 +#define DMA_PREEMPT_ENABLE 3 +#define DMA_FIFO_VALIDATE 2 +#define DMA_ENABLE 1 +#define DMA_ADDRESS_HOLD 0 + __le32 dmastat; +#define DMA_SCATTER_GATHER_DONE_INTERRUPT 25 +#define DMA_TRANSACTION_DONE_INTERRUPT 24 +#define DMA_ABORT 1 +#define DMA_START 0 + u32 _unused0[2]; + /* offset 0x0190, 0x01b0, 0x01d0, 0x01f0, */ + __le32 dmacount; +#define VALID_BIT 31 +#define DMA_DIRECTION 30 +#define DMA_DONE_INTERRUPT_ENABLE 29 +#define END_OF_CHAIN 28 +#define DMA_BYTE_COUNT_MASK ((1<<24)-1) +#define DMA_BYTE_COUNT 0 + __le32 dmaaddr; + __le32 dmadesc; + u32 _unused1; +} __packed; + +/* dedicated endpoint registers, BAR0 + 0x0200 */ + +struct net2280_dep_regs { /* [11.8] */ + /* offset 0x0200, 0x0210, 0x220, 0x230, 0x240 */ + __le32 dep_cfg; + /* offset 0x0204, 0x0214, 0x224, 0x234, 0x244 */ + __le32 dep_rsp; + u32 _unused[2]; +} __packed; + +/* configurable endpoint registers, BAR0 + 0x0300 ... array of seven structs + * like this, for ep0 then the configurable endpoints A..F + * ep0 reserved for control; E and F have only 64 bytes of fifo + */ +struct net2280_ep_regs { /* [11.9] */ + /* offset 0x0300, 0x0320, 0x0340, 0x0360, 0x0380, 0x03a0, 0x03c0 */ + __le32 ep_cfg; +#define ENDPOINT_BYTE_COUNT 16 +#define ENDPOINT_ENABLE 10 +#define ENDPOINT_TYPE 8 +#define ENDPOINT_DIRECTION 7 +#define ENDPOINT_NUMBER 0 + __le32 ep_rsp; +#define SET_NAK_OUT_PACKETS 15 +#define SET_EP_HIDE_STATUS_PHASE 14 +#define SET_EP_FORCE_CRC_ERROR 13 +#define SET_INTERRUPT_MODE 12 +#define SET_CONTROL_STATUS_PHASE_HANDSHAKE 11 +#define SET_NAK_OUT_PACKETS_MODE 10 +#define SET_ENDPOINT_TOGGLE 9 +#define SET_ENDPOINT_HALT 8 +#define CLEAR_NAK_OUT_PACKETS 7 +#define CLEAR_EP_HIDE_STATUS_PHASE 6 +#define CLEAR_EP_FORCE_CRC_ERROR 5 +#define CLEAR_INTERRUPT_MODE 4 +#define CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE 3 +#define CLEAR_NAK_OUT_PACKETS_MODE 2 +#define CLEAR_ENDPOINT_TOGGLE 1 +#define CLEAR_ENDPOINT_HALT 0 + __le32 ep_irqenb; +#define SHORT_PACKET_OUT_DONE_INTERRUPT_ENABLE 6 +#define SHORT_PACKET_TRANSFERRED_INTERRUPT_ENABLE 5 +#define DATA_PACKET_RECEIVED_INTERRUPT_ENABLE 3 +#define DATA_PACKET_TRANSMITTED_INTERRUPT_ENABLE 2 +#define DATA_OUT_PING_TOKEN_INTERRUPT_ENABLE 1 +#define DATA_IN_TOKEN_INTERRUPT_ENABLE 0 + __le32 ep_stat; +#define FIFO_VALID_COUNT 24 +#define HIGH_BANDWIDTH_OUT_TRANSACTION_PID 22 +#define TIMEOUT 21 +#define USB_STALL_SENT 20 +#define USB_IN_NAK_SENT 19 +#define USB_IN_ACK_RCVD 18 +#define USB_OUT_PING_NAK_SENT 17 +#define USB_OUT_ACK_SENT 16 +#define FIFO_OVERFLOW 13 +#define FIFO_UNDERFLOW 12 +#define FIFO_FULL 11 +#define FIFO_EMPTY 10 +#define FIFO_FLUSH 9 +#define SHORT_PACKET_OUT_DONE_INTERRUPT 6 +#define SHORT_PACKET_TRANSFERRED_INTERRUPT 5 +#define NAK_OUT_PACKETS 4 +#define DATA_PACKET_RECEIVED_INTERRUPT 3 +#define DATA_PACKET_TRANSMITTED_INTERRUPT 2 +#define DATA_OUT_PING_TOKEN_INTERRUPT 1 +#define DATA_IN_TOKEN_INTERRUPT 0 + /* offset 0x0310, 0x0330, 0x0350, 0x0370, 0x0390, 0x03b0, 0x03d0 */ + __le32 ep_avail; + __le32 ep_data; + u32 _unused0[2]; +} __packed; + +struct net2280_reg_write { + __le16 port; + __le32 addr; + __le32 val; +} __packed; + +struct net2280_reg_read { + __le16 port; + __le32 addr; +} __packed; +#endif /* NET2280_H */ diff --git a/drivers/net/wireless/p54/p54.h b/drivers/net/wireless/p54/p54.h new file mode 100644 index 0000000..978496b --- /dev/null +++ b/drivers/net/wireless/p54/p54.h @@ -0,0 +1,281 @@ +/* + * Shared defines for all mac80211 Prism54 code + * + * Copyright (c) 2006, Michael Wu + * + * Based on the islsm (softmac prism54) driver, which is: + * Copyright 2004-2006 Jean-Baptiste Note , et al. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef P54_H +#define P54_H + +#ifdef CPTCFG_P54_LEDS +#include +#endif /* CPTCFG_P54_LEDS */ + +#define ISL38XX_DEV_FIRMWARE_ADDR 0x20000 + +#define BR_CODE_MIN 0x80000000 +#define BR_CODE_COMPONENT_ID 0x80000001 +#define BR_CODE_COMPONENT_VERSION 0x80000002 +#define BR_CODE_DEPENDENT_IF 0x80000003 +#define BR_CODE_EXPOSED_IF 0x80000004 +#define BR_CODE_DESCR 0x80000101 +#define BR_CODE_MAX 0x8FFFFFFF +#define BR_CODE_END_OF_BRA 0xFF0000FF +#define LEGACY_BR_CODE_END_OF_BRA 0xFFFFFFFF + +struct bootrec { + __le32 code; + __le32 len; + u32 data[10]; +} __packed; + +/* Interface role definitions */ +#define BR_INTERFACE_ROLE_SERVER 0x0000 +#define BR_INTERFACE_ROLE_CLIENT 0x8000 + +#define BR_DESC_PRIV_CAP_WEP BIT(0) +#define BR_DESC_PRIV_CAP_TKIP BIT(1) +#define BR_DESC_PRIV_CAP_MICHAEL BIT(2) +#define BR_DESC_PRIV_CAP_CCX_CP BIT(3) +#define BR_DESC_PRIV_CAP_CCX_MIC BIT(4) +#define BR_DESC_PRIV_CAP_AESCCMP BIT(5) + +struct bootrec_desc { + __le16 modes; + __le16 flags; + __le32 rx_start; + __le32 rx_end; + u8 headroom; + u8 tailroom; + u8 tx_queues; + u8 tx_depth; + u8 privacy_caps; + u8 rx_keycache_size; + u8 time_size; + u8 padding; + u8 rates[16]; + u8 padding2[4]; + __le16 rx_mtu; +} __packed; + +#define FW_FMAC 0x464d4143 +#define FW_LM86 0x4c4d3836 +#define FW_LM87 0x4c4d3837 +#define FW_LM20 0x4c4d3230 + +struct bootrec_comp_id { + __le32 fw_variant; +} __packed; + +struct bootrec_comp_ver { + char fw_version[24]; +} __packed; + +struct bootrec_end { + __le16 crc; + u8 padding[2]; + u8 md5[16]; +} __packed; + +/* provide 16 bytes for the transport back-end */ +#define P54_TX_INFO_DATA_SIZE 16 + +/* stored in ieee80211_tx_info's rate_driver_data */ +struct p54_tx_info { + u32 start_addr; + u32 end_addr; + union { + void *data[P54_TX_INFO_DATA_SIZE / sizeof(void *)]; + struct { + u32 extra_len; + }; + }; +}; + +#define P54_MAX_CTRL_FRAME_LEN 0x1000 + +#define P54_SET_QUEUE(queue, ai_fs, cw_min, cw_max, _txop) \ +do { \ + queue.aifs = cpu_to_le16(ai_fs); \ + queue.cwmin = cpu_to_le16(cw_min); \ + queue.cwmax = cpu_to_le16(cw_max); \ + queue.txop = cpu_to_le16(_txop); \ +} while (0) + +struct p54_edcf_queue_param { + __le16 aifs; + __le16 cwmin; + __le16 cwmax; + __le16 txop; +} __packed; + +struct p54_rssi_db_entry { + u16 freq; + s16 mul; + s16 add; + s16 longbow_unkn; + s16 longbow_unk2; +}; + +struct p54_cal_database { + size_t entries; + size_t entry_size; + size_t offset; + size_t len; + u8 data[0]; +}; + +#define EEPROM_READBACK_LEN 0x3fc + +enum fw_state { + FW_STATE_OFF, + FW_STATE_BOOTING, + FW_STATE_READY, + FW_STATE_RESET, + FW_STATE_RESETTING, +}; + +#ifdef CPTCFG_P54_LEDS + +#define P54_LED_MAX_NAME_LEN 31 + +struct p54_led_dev { + struct ieee80211_hw *hw_dev; + struct led_classdev led_dev; + char name[P54_LED_MAX_NAME_LEN + 1]; + + unsigned int toggled; + unsigned int index; + unsigned int registered; +}; + +#endif /* CPTCFG_P54_LEDS */ + +struct p54_tx_queue_stats { + unsigned int len; + unsigned int limit; + unsigned int count; +}; + +struct p54_common { + struct ieee80211_hw *hw; + struct ieee80211_vif *vif; + void (*tx)(struct ieee80211_hw *dev, struct sk_buff *skb); + int (*open)(struct ieee80211_hw *dev); + void (*stop)(struct ieee80211_hw *dev); + struct sk_buff_head tx_pending; + struct sk_buff_head tx_queue; + struct mutex conf_mutex; + bool registered; + + /* memory management (as seen by the firmware) */ + u32 rx_start; + u32 rx_end; + u16 rx_mtu; + u8 headroom; + u8 tailroom; + + /* firmware/hardware info */ + unsigned int tx_hdr_len; + unsigned int fw_var; + unsigned int fw_interface; + u8 version; + + /* (e)DCF / QOS state */ + bool use_short_slot; + spinlock_t tx_stats_lock; + struct p54_tx_queue_stats tx_stats[8]; + struct p54_edcf_queue_param qos_params[8]; + + /* Radio data */ + u16 rxhw; + u8 rx_diversity_mask; + u8 tx_diversity_mask; + unsigned int output_power; + struct p54_rssi_db_entry *cur_rssi; + struct ieee80211_channel *curchan; + struct survey_info *survey; + unsigned int chan_num; + struct completion stat_comp; + bool update_stats; + struct { + unsigned int timestamp; + unsigned int cached_cca; + unsigned int cached_tx; + unsigned int cached_rssi; + u64 active; + u64 cca; + u64 tx; + u64 rssi; + } survey_raw; + + int noise; + /* calibration, output power limit and rssi<->dBm conversation data */ + struct pda_iq_autocal_entry *iq_autocal; + unsigned int iq_autocal_len; + struct p54_cal_database *curve_data; + struct p54_cal_database *output_limit; + struct p54_cal_database *rssi_db; + struct ieee80211_supported_band *band_table[IEEE80211_NUM_BANDS]; + + /* BBP/MAC state */ + u8 mac_addr[ETH_ALEN]; + u8 bssid[ETH_ALEN]; + u8 mc_maclist[4][ETH_ALEN]; + u16 wakeup_timer; + unsigned int filter_flags; + int mc_maclist_num; + int mode; + u32 tsf_low32, tsf_high32; + u32 basic_rate_mask; + u16 aid; + u8 coverage_class; + bool phy_idle; + bool phy_ps; + bool powersave_override; + __le32 beacon_req_id; + struct completion beacon_comp; + + /* cryptographic engine information */ + u8 privacy_caps; + u8 rx_keycache_size; + unsigned long *used_rxkeys; + + /* LED management */ +#ifdef CPTCFG_P54_LEDS + struct p54_led_dev leds[4]; + struct delayed_work led_work; +#endif /* CPTCFG_P54_LEDS */ + u16 softled_state; /* bit field of glowing LEDs */ + + /* statistics */ + struct ieee80211_low_level_stats stats; + struct delayed_work work; + + /* eeprom handling */ + void *eeprom; + struct completion eeprom_comp; + struct mutex eeprom_mutex; +}; + +/* interfaces for the drivers */ +int p54_rx(struct ieee80211_hw *dev, struct sk_buff *skb); +void p54_free_skb(struct ieee80211_hw *dev, struct sk_buff *skb); +int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw); +int p54_parse_eeprom(struct ieee80211_hw *dev, void *eeprom, int len); +int p54_read_eeprom(struct ieee80211_hw *dev); + +struct ieee80211_hw *p54_init_common(size_t priv_data_len); +int p54_register_common(struct ieee80211_hw *dev, struct device *pdev); +void p54_free_common(struct ieee80211_hw *dev); + +void p54_unregister_common(struct ieee80211_hw *dev); + +#endif /* P54_H */ diff --git a/drivers/net/wireless/p54/p54pci.c b/drivers/net/wireless/p54/p54pci.c new file mode 100644 index 0000000..b7ebeaf --- /dev/null +++ b/drivers/net/wireless/p54/p54pci.c @@ -0,0 +1,710 @@ + +/* + * Linux device driver for PCI based Prism54 + * + * Copyright (c) 2006, Michael Wu + * Copyright (c) 2008, Christian Lamparter + * + * Based on the islsm (softmac prism54) driver, which is: + * Copyright 2004-2006 Jean-Baptiste Note , et al. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "p54.h" +#include "lmac.h" +#include "p54pci.h" + +MODULE_AUTHOR("Michael Wu "); +MODULE_DESCRIPTION("Prism54 PCI wireless driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("prism54pci"); +MODULE_FIRMWARE("isl3886pci"); + +static DEFINE_PCI_DEVICE_TABLE(p54p_table) = { + /* Intersil PRISM Duette/Prism GT Wireless LAN adapter */ + { PCI_DEVICE(0x1260, 0x3890) }, + /* 3COM 3CRWE154G72 Wireless LAN adapter */ + { PCI_DEVICE(0x10b7, 0x6001) }, + /* Intersil PRISM Indigo Wireless LAN adapter */ + { PCI_DEVICE(0x1260, 0x3877) }, + /* Intersil PRISM Javelin/Xbow Wireless LAN adapter */ + { PCI_DEVICE(0x1260, 0x3886) }, + /* Intersil PRISM Xbow Wireless LAN adapter (Symbol AP-300) */ + { PCI_DEVICE(0x1260, 0xffff) }, + { }, +}; + +MODULE_DEVICE_TABLE(pci, p54p_table); + +static int p54p_upload_firmware(struct ieee80211_hw *dev) +{ + struct p54p_priv *priv = dev->priv; + __le32 reg; + int err; + __le32 *data; + u32 remains, left, device_addr; + + P54P_WRITE(int_enable, cpu_to_le32(0)); + P54P_READ(int_enable); + udelay(10); + + reg = P54P_READ(ctrl_stat); + reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RESET); + reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RAMBOOT); + P54P_WRITE(ctrl_stat, reg); + P54P_READ(ctrl_stat); + udelay(10); + + reg |= cpu_to_le32(ISL38XX_CTRL_STAT_RESET); + P54P_WRITE(ctrl_stat, reg); + wmb(); + udelay(10); + + reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RESET); + P54P_WRITE(ctrl_stat, reg); + wmb(); + + /* wait for the firmware to reset properly */ + mdelay(10); + + err = p54_parse_firmware(dev, priv->firmware); + if (err) + return err; + + if (priv->common.fw_interface != FW_LM86) { + dev_err(&priv->pdev->dev, "wrong firmware, " + "please get a LM86(PCI) firmware a try again.\n"); + return -EINVAL; + } + + data = (__le32 *) priv->firmware->data; + remains = priv->firmware->size; + device_addr = ISL38XX_DEV_FIRMWARE_ADDR; + while (remains) { + u32 i = 0; + left = min((u32)0x1000, remains); + P54P_WRITE(direct_mem_base, cpu_to_le32(device_addr)); + P54P_READ(int_enable); + + device_addr += 0x1000; + while (i < left) { + P54P_WRITE(direct_mem_win[i], *data++); + i += sizeof(u32); + } + + remains -= left; + P54P_READ(int_enable); + } + + reg = P54P_READ(ctrl_stat); + reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_CLKRUN); + reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RESET); + reg |= cpu_to_le32(ISL38XX_CTRL_STAT_RAMBOOT); + P54P_WRITE(ctrl_stat, reg); + P54P_READ(ctrl_stat); + udelay(10); + + reg |= cpu_to_le32(ISL38XX_CTRL_STAT_RESET); + P54P_WRITE(ctrl_stat, reg); + wmb(); + udelay(10); + + reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RESET); + P54P_WRITE(ctrl_stat, reg); + wmb(); + udelay(10); + + /* wait for the firmware to boot properly */ + mdelay(100); + + return 0; +} + +static void p54p_refill_rx_ring(struct ieee80211_hw *dev, + int ring_index, struct p54p_desc *ring, u32 ring_limit, + struct sk_buff **rx_buf, u32 index) +{ + struct p54p_priv *priv = dev->priv; + struct p54p_ring_control *ring_control = priv->ring_control; + u32 limit, idx, i; + + idx = le32_to_cpu(ring_control->host_idx[ring_index]); + limit = idx; + limit -= index; + limit = ring_limit - limit; + + i = idx % ring_limit; + while (limit-- > 1) { + struct p54p_desc *desc = &ring[i]; + + if (!desc->host_addr) { + struct sk_buff *skb; + dma_addr_t mapping; + skb = dev_alloc_skb(priv->common.rx_mtu + 32); + if (!skb) + break; + + mapping = pci_map_single(priv->pdev, + skb_tail_pointer(skb), + priv->common.rx_mtu + 32, + PCI_DMA_FROMDEVICE); + + if (pci_dma_mapping_error(priv->pdev, mapping)) { + dev_kfree_skb_any(skb); + dev_err(&priv->pdev->dev, + "RX DMA Mapping error\n"); + break; + } + + desc->host_addr = cpu_to_le32(mapping); + desc->device_addr = 0; // FIXME: necessary? + desc->len = cpu_to_le16(priv->common.rx_mtu + 32); + desc->flags = 0; + rx_buf[i] = skb; + } + + i++; + idx++; + i %= ring_limit; + } + + wmb(); + ring_control->host_idx[ring_index] = cpu_to_le32(idx); +} + +static void p54p_check_rx_ring(struct ieee80211_hw *dev, u32 *index, + int ring_index, struct p54p_desc *ring, u32 ring_limit, + struct sk_buff **rx_buf) +{ + struct p54p_priv *priv = dev->priv; + struct p54p_ring_control *ring_control = priv->ring_control; + struct p54p_desc *desc; + u32 idx, i; + + i = (*index) % ring_limit; + (*index) = idx = le32_to_cpu(ring_control->device_idx[ring_index]); + idx %= ring_limit; + while (i != idx) { + u16 len; + struct sk_buff *skb; + dma_addr_t dma_addr; + desc = &ring[i]; + len = le16_to_cpu(desc->len); + skb = rx_buf[i]; + + if (!skb) { + i++; + i %= ring_limit; + continue; + } + + if (unlikely(len > priv->common.rx_mtu)) { + if (net_ratelimit()) + dev_err(&priv->pdev->dev, "rx'd frame size " + "exceeds length threshold.\n"); + + len = priv->common.rx_mtu; + } + dma_addr = le32_to_cpu(desc->host_addr); + pci_dma_sync_single_for_cpu(priv->pdev, dma_addr, + priv->common.rx_mtu + 32, PCI_DMA_FROMDEVICE); + skb_put(skb, len); + + if (p54_rx(dev, skb)) { + pci_unmap_single(priv->pdev, dma_addr, + priv->common.rx_mtu + 32, PCI_DMA_FROMDEVICE); + rx_buf[i] = NULL; + desc->host_addr = cpu_to_le32(0); + } else { + skb_trim(skb, 0); + pci_dma_sync_single_for_device(priv->pdev, dma_addr, + priv->common.rx_mtu + 32, PCI_DMA_FROMDEVICE); + desc->len = cpu_to_le16(priv->common.rx_mtu + 32); + } + + i++; + i %= ring_limit; + } + + p54p_refill_rx_ring(dev, ring_index, ring, ring_limit, rx_buf, *index); +} + +static void p54p_check_tx_ring(struct ieee80211_hw *dev, u32 *index, + int ring_index, struct p54p_desc *ring, u32 ring_limit, + struct sk_buff **tx_buf) +{ + struct p54p_priv *priv = dev->priv; + struct p54p_ring_control *ring_control = priv->ring_control; + struct p54p_desc *desc; + struct sk_buff *skb; + u32 idx, i; + + i = (*index) % ring_limit; + (*index) = idx = le32_to_cpu(ring_control->device_idx[ring_index]); + idx %= ring_limit; + + while (i != idx) { + desc = &ring[i]; + + skb = tx_buf[i]; + tx_buf[i] = NULL; + + pci_unmap_single(priv->pdev, le32_to_cpu(desc->host_addr), + le16_to_cpu(desc->len), PCI_DMA_TODEVICE); + + desc->host_addr = 0; + desc->device_addr = 0; + desc->len = 0; + desc->flags = 0; + + if (skb && FREE_AFTER_TX(skb)) + p54_free_skb(dev, skb); + + i++; + i %= ring_limit; + } +} + +static void p54p_tasklet(unsigned long dev_id) +{ + struct ieee80211_hw *dev = (struct ieee80211_hw *)dev_id; + struct p54p_priv *priv = dev->priv; + struct p54p_ring_control *ring_control = priv->ring_control; + + p54p_check_tx_ring(dev, &priv->tx_idx_mgmt, 3, ring_control->tx_mgmt, + ARRAY_SIZE(ring_control->tx_mgmt), + priv->tx_buf_mgmt); + + p54p_check_tx_ring(dev, &priv->tx_idx_data, 1, ring_control->tx_data, + ARRAY_SIZE(ring_control->tx_data), + priv->tx_buf_data); + + p54p_check_rx_ring(dev, &priv->rx_idx_mgmt, 2, ring_control->rx_mgmt, + ARRAY_SIZE(ring_control->rx_mgmt), priv->rx_buf_mgmt); + + p54p_check_rx_ring(dev, &priv->rx_idx_data, 0, ring_control->rx_data, + ARRAY_SIZE(ring_control->rx_data), priv->rx_buf_data); + + wmb(); + P54P_WRITE(dev_int, cpu_to_le32(ISL38XX_DEV_INT_UPDATE)); +} + +static irqreturn_t p54p_interrupt(int irq, void *dev_id) +{ + struct ieee80211_hw *dev = dev_id; + struct p54p_priv *priv = dev->priv; + __le32 reg; + + reg = P54P_READ(int_ident); + if (unlikely(reg == cpu_to_le32(0xFFFFFFFF))) { + goto out; + } + P54P_WRITE(int_ack, reg); + + reg &= P54P_READ(int_enable); + + if (reg & cpu_to_le32(ISL38XX_INT_IDENT_UPDATE)) + tasklet_schedule(&priv->tasklet); + else if (reg & cpu_to_le32(ISL38XX_INT_IDENT_INIT)) + complete(&priv->boot_comp); + +out: + return reg ? IRQ_HANDLED : IRQ_NONE; +} + +static void p54p_tx(struct ieee80211_hw *dev, struct sk_buff *skb) +{ + unsigned long flags; + struct p54p_priv *priv = dev->priv; + struct p54p_ring_control *ring_control = priv->ring_control; + struct p54p_desc *desc; + dma_addr_t mapping; + u32 idx, i; + + spin_lock_irqsave(&priv->lock, flags); + idx = le32_to_cpu(ring_control->host_idx[1]); + i = idx % ARRAY_SIZE(ring_control->tx_data); + + mapping = pci_map_single(priv->pdev, skb->data, skb->len, + PCI_DMA_TODEVICE); + if (pci_dma_mapping_error(priv->pdev, mapping)) { + spin_unlock_irqrestore(&priv->lock, flags); + p54_free_skb(dev, skb); + dev_err(&priv->pdev->dev, "TX DMA mapping error\n"); + return ; + } + priv->tx_buf_data[i] = skb; + + desc = &ring_control->tx_data[i]; + desc->host_addr = cpu_to_le32(mapping); + desc->device_addr = ((struct p54_hdr *)skb->data)->req_id; + desc->len = cpu_to_le16(skb->len); + desc->flags = 0; + + wmb(); + ring_control->host_idx[1] = cpu_to_le32(idx + 1); + spin_unlock_irqrestore(&priv->lock, flags); + + P54P_WRITE(dev_int, cpu_to_le32(ISL38XX_DEV_INT_UPDATE)); + P54P_READ(dev_int); +} + +static void p54p_stop(struct ieee80211_hw *dev) +{ + struct p54p_priv *priv = dev->priv; + struct p54p_ring_control *ring_control = priv->ring_control; + unsigned int i; + struct p54p_desc *desc; + + P54P_WRITE(int_enable, cpu_to_le32(0)); + P54P_READ(int_enable); + udelay(10); + + free_irq(priv->pdev->irq, dev); + + tasklet_kill(&priv->tasklet); + + P54P_WRITE(dev_int, cpu_to_le32(ISL38XX_DEV_INT_RESET)); + + for (i = 0; i < ARRAY_SIZE(priv->rx_buf_data); i++) { + desc = &ring_control->rx_data[i]; + if (desc->host_addr) + pci_unmap_single(priv->pdev, + le32_to_cpu(desc->host_addr), + priv->common.rx_mtu + 32, + PCI_DMA_FROMDEVICE); + kfree_skb(priv->rx_buf_data[i]); + priv->rx_buf_data[i] = NULL; + } + + for (i = 0; i < ARRAY_SIZE(priv->rx_buf_mgmt); i++) { + desc = &ring_control->rx_mgmt[i]; + if (desc->host_addr) + pci_unmap_single(priv->pdev, + le32_to_cpu(desc->host_addr), + priv->common.rx_mtu + 32, + PCI_DMA_FROMDEVICE); + kfree_skb(priv->rx_buf_mgmt[i]); + priv->rx_buf_mgmt[i] = NULL; + } + + for (i = 0; i < ARRAY_SIZE(priv->tx_buf_data); i++) { + desc = &ring_control->tx_data[i]; + if (desc->host_addr) + pci_unmap_single(priv->pdev, + le32_to_cpu(desc->host_addr), + le16_to_cpu(desc->len), + PCI_DMA_TODEVICE); + + p54_free_skb(dev, priv->tx_buf_data[i]); + priv->tx_buf_data[i] = NULL; + } + + for (i = 0; i < ARRAY_SIZE(priv->tx_buf_mgmt); i++) { + desc = &ring_control->tx_mgmt[i]; + if (desc->host_addr) + pci_unmap_single(priv->pdev, + le32_to_cpu(desc->host_addr), + le16_to_cpu(desc->len), + PCI_DMA_TODEVICE); + + p54_free_skb(dev, priv->tx_buf_mgmt[i]); + priv->tx_buf_mgmt[i] = NULL; + } + + memset(ring_control, 0, sizeof(*ring_control)); +} + +static int p54p_open(struct ieee80211_hw *dev) +{ + struct p54p_priv *priv = dev->priv; + int err; + + init_completion(&priv->boot_comp); + err = request_irq(priv->pdev->irq, p54p_interrupt, + IRQF_SHARED, "p54pci", dev); + if (err) { + dev_err(&priv->pdev->dev, "failed to register IRQ handler\n"); + return err; + } + + memset(priv->ring_control, 0, sizeof(*priv->ring_control)); + err = p54p_upload_firmware(dev); + if (err) { + free_irq(priv->pdev->irq, dev); + return err; + } + priv->rx_idx_data = priv->tx_idx_data = 0; + priv->rx_idx_mgmt = priv->tx_idx_mgmt = 0; + + p54p_refill_rx_ring(dev, 0, priv->ring_control->rx_data, + ARRAY_SIZE(priv->ring_control->rx_data), priv->rx_buf_data, 0); + + p54p_refill_rx_ring(dev, 2, priv->ring_control->rx_mgmt, + ARRAY_SIZE(priv->ring_control->rx_mgmt), priv->rx_buf_mgmt, 0); + + P54P_WRITE(ring_control_base, cpu_to_le32(priv->ring_control_dma)); + P54P_READ(ring_control_base); + wmb(); + udelay(10); + + P54P_WRITE(int_enable, cpu_to_le32(ISL38XX_INT_IDENT_INIT)); + P54P_READ(int_enable); + wmb(); + udelay(10); + + P54P_WRITE(dev_int, cpu_to_le32(ISL38XX_DEV_INT_RESET)); + P54P_READ(dev_int); + + if (!wait_for_completion_interruptible_timeout(&priv->boot_comp, HZ)) { + wiphy_err(dev->wiphy, "Cannot boot firmware!\n"); + p54p_stop(dev); + return -ETIMEDOUT; + } + + P54P_WRITE(int_enable, cpu_to_le32(ISL38XX_INT_IDENT_UPDATE)); + P54P_READ(int_enable); + wmb(); + udelay(10); + + P54P_WRITE(dev_int, cpu_to_le32(ISL38XX_DEV_INT_UPDATE)); + P54P_READ(dev_int); + wmb(); + udelay(10); + + return 0; +} + +static void p54p_firmware_step2(const struct firmware *fw, + void *context) +{ + struct p54p_priv *priv = context; + struct ieee80211_hw *dev = priv->common.hw; + struct pci_dev *pdev = priv->pdev; + int err; + + if (!fw) { + dev_err(&pdev->dev, "Cannot find firmware (isl3886pci)\n"); + err = -ENOENT; + goto out; + } + + priv->firmware = fw; + + err = p54p_open(dev); + if (err) + goto out; + err = p54_read_eeprom(dev); + p54p_stop(dev); + if (err) + goto out; + + err = p54_register_common(dev, &pdev->dev); + if (err) + goto out; + +out: + + complete(&priv->fw_loaded); + + if (err) { + struct device *parent = pdev->dev.parent; + + if (parent) + device_lock(parent); + + /* + * This will indirectly result in a call to p54p_remove. + * Hence, we don't need to bother with freeing any + * allocated ressources at all. + */ + device_release_driver(&pdev->dev); + + if (parent) + device_unlock(parent); + } + + pci_dev_put(pdev); +} + +static int p54p_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct p54p_priv *priv; + struct ieee80211_hw *dev; + unsigned long mem_addr, mem_len; + int err; + + pci_dev_get(pdev); + err = pci_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "Cannot enable new PCI device\n"); + return err; + } + + mem_addr = pci_resource_start(pdev, 0); + mem_len = pci_resource_len(pdev, 0); + if (mem_len < sizeof(struct p54p_csr)) { + dev_err(&pdev->dev, "Too short PCI resources\n"); + err = -ENODEV; + goto err_disable_dev; + } + + err = pci_request_regions(pdev, "p54pci"); + if (err) { + dev_err(&pdev->dev, "Cannot obtain PCI resources\n"); + goto err_disable_dev; + } + + err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + if (!err) + err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)); + if (err) { + dev_err(&pdev->dev, "No suitable DMA available\n"); + goto err_free_reg; + } + + pci_set_master(pdev); + pci_try_set_mwi(pdev); + + pci_write_config_byte(pdev, 0x40, 0); + pci_write_config_byte(pdev, 0x41, 0); + + dev = p54_init_common(sizeof(*priv)); + if (!dev) { + dev_err(&pdev->dev, "ieee80211 alloc failed\n"); + err = -ENOMEM; + goto err_free_reg; + } + + priv = dev->priv; + priv->pdev = pdev; + + init_completion(&priv->fw_loaded); + SET_IEEE80211_DEV(dev, &pdev->dev); + pci_set_drvdata(pdev, dev); + + priv->map = ioremap(mem_addr, mem_len); + if (!priv->map) { + dev_err(&pdev->dev, "Cannot map device memory\n"); + err = -ENOMEM; + goto err_free_dev; + } + + priv->ring_control = pci_alloc_consistent(pdev, sizeof(*priv->ring_control), + &priv->ring_control_dma); + if (!priv->ring_control) { + dev_err(&pdev->dev, "Cannot allocate rings\n"); + err = -ENOMEM; + goto err_iounmap; + } + priv->common.open = p54p_open; + priv->common.stop = p54p_stop; + priv->common.tx = p54p_tx; + + spin_lock_init(&priv->lock); + tasklet_init(&priv->tasklet, p54p_tasklet, (unsigned long)dev); + + err = request_firmware_nowait(THIS_MODULE, 1, "isl3886pci", + &priv->pdev->dev, GFP_KERNEL, + priv, p54p_firmware_step2); + if (!err) + return 0; + + pci_free_consistent(pdev, sizeof(*priv->ring_control), + priv->ring_control, priv->ring_control_dma); + + err_iounmap: + iounmap(priv->map); + + err_free_dev: + pci_set_drvdata(pdev, NULL); + p54_free_common(dev); + + err_free_reg: + pci_release_regions(pdev); + err_disable_dev: + pci_disable_device(pdev); + pci_dev_put(pdev); + return err; +} + +static void p54p_remove(struct pci_dev *pdev) +{ + struct ieee80211_hw *dev = pci_get_drvdata(pdev); + struct p54p_priv *priv; + + if (!dev) + return; + + priv = dev->priv; + wait_for_completion(&priv->fw_loaded); + p54_unregister_common(dev); + release_firmware(priv->firmware); + pci_free_consistent(pdev, sizeof(*priv->ring_control), + priv->ring_control, priv->ring_control_dma); + iounmap(priv->map); + pci_release_regions(pdev); + pci_disable_device(pdev); + p54_free_common(dev); +} + +#ifdef CONFIG_PM_SLEEP +static int p54p_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + + pci_save_state(pdev); + pci_set_power_state(pdev, PCI_D3hot); + pci_disable_device(pdev); + return 0; +} + +static int p54p_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + int err; + + err = pci_reenable_device(pdev); + if (err) + return err; + return pci_set_power_state(pdev, PCI_D0); +} + +compat_pci_suspend(p54p_suspend); +compat_pci_resume(p54p_resume); + +static SIMPLE_DEV_PM_OPS(p54pci_pm_ops, p54p_suspend, p54p_resume); + +#define P54P_PM_OPS (&p54pci_pm_ops) +#else +#define P54P_PM_OPS (NULL) +#endif /* CONFIG_PM_SLEEP */ + +static struct pci_driver p54p_driver = { + .name = "p54pci", + .id_table = p54p_table, + .probe = p54p_probe, + .remove = p54p_remove, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) + .driver.pm = P54P_PM_OPS, +#elif defined(CONFIG_PM_SLEEP) + .suspend = p54p_suspend_compat, + .resume = p54p_resume_compat, +#endif +}; + +module_pci_driver(p54p_driver); diff --git a/drivers/net/wireless/p54/p54pci.h b/drivers/net/wireless/p54/p54pci.h new file mode 100644 index 0000000..68405c1 --- /dev/null +++ b/drivers/net/wireless/p54/p54pci.h @@ -0,0 +1,112 @@ +#ifndef P54PCI_H +#define P54PCI_H +#include + +/* + * Defines for PCI based mac80211 Prism54 driver + * + * Copyright (c) 2006, Michael Wu + * + * Based on the islsm (softmac prism54) driver, which is: + * Copyright 2004-2006 Jean-Baptiste Note , et al. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* Device Interrupt register bits */ +#define ISL38XX_DEV_INT_RESET 0x0001 +#define ISL38XX_DEV_INT_UPDATE 0x0002 +#define ISL38XX_DEV_INT_WAKEUP 0x0008 +#define ISL38XX_DEV_INT_SLEEP 0x0010 +#define ISL38XX_DEV_INT_ABORT 0x0020 +/* these two only used in USB */ +#define ISL38XX_DEV_INT_DATA 0x0040 +#define ISL38XX_DEV_INT_MGMT 0x0080 + +#define ISL38XX_DEV_INT_PCIUART_CTS 0x4000 +#define ISL38XX_DEV_INT_PCIUART_DR 0x8000 + +/* Interrupt Identification/Acknowledge/Enable register bits */ +#define ISL38XX_INT_IDENT_UPDATE 0x0002 +#define ISL38XX_INT_IDENT_INIT 0x0004 +#define ISL38XX_INT_IDENT_WAKEUP 0x0008 +#define ISL38XX_INT_IDENT_SLEEP 0x0010 +#define ISL38XX_INT_IDENT_PCIUART_CTS 0x4000 +#define ISL38XX_INT_IDENT_PCIUART_DR 0x8000 + +/* Control/Status register bits */ +#define ISL38XX_CTRL_STAT_SLEEPMODE 0x00000200 +#define ISL38XX_CTRL_STAT_CLKRUN 0x00800000 +#define ISL38XX_CTRL_STAT_RESET 0x10000000 +#define ISL38XX_CTRL_STAT_RAMBOOT 0x20000000 +#define ISL38XX_CTRL_STAT_STARTHALTED 0x40000000 +#define ISL38XX_CTRL_STAT_HOST_OVERRIDE 0x80000000 + +struct p54p_csr { + __le32 dev_int; + u8 unused_1[12]; + __le32 int_ident; + __le32 int_ack; + __le32 int_enable; + u8 unused_2[4]; + union { + __le32 ring_control_base; + __le32 gen_purp_com[2]; + }; + u8 unused_3[8]; + __le32 direct_mem_base; + u8 unused_4[44]; + __le32 dma_addr; + __le32 dma_len; + __le32 dma_ctrl; + u8 unused_5[12]; + __le32 ctrl_stat; + u8 unused_6[1924]; + u8 cardbus_cis[0x800]; + u8 direct_mem_win[0x1000]; +} __packed; + +/* usb backend only needs the register defines above */ +#ifndef P54USB_H +struct p54p_desc { + __le32 host_addr; + __le32 device_addr; + __le16 len; + __le16 flags; +} __packed; + +struct p54p_ring_control { + __le32 host_idx[4]; + __le32 device_idx[4]; + struct p54p_desc rx_data[8]; + struct p54p_desc tx_data[32]; + struct p54p_desc rx_mgmt[4]; + struct p54p_desc tx_mgmt[4]; +} __packed; + +#define P54P_READ(r) (__force __le32)__raw_readl(&priv->map->r) +#define P54P_WRITE(r, val) __raw_writel((__force u32)(__le32)(val), &priv->map->r) + +struct p54p_priv { + struct p54_common common; + struct pci_dev *pdev; + struct p54p_csr __iomem *map; + struct tasklet_struct tasklet; + const struct firmware *firmware; + spinlock_t lock; + struct p54p_ring_control *ring_control; + dma_addr_t ring_control_dma; + u32 rx_idx_data, tx_idx_data; + u32 rx_idx_mgmt, tx_idx_mgmt; + struct sk_buff *rx_buf_data[8]; + struct sk_buff *rx_buf_mgmt[4]; + struct sk_buff *tx_buf_data[32]; + struct sk_buff *tx_buf_mgmt[4]; + struct completion boot_comp; + struct completion fw_loaded; +}; + +#endif /* P54USB_H */ +#endif /* P54PCI_H */ diff --git a/drivers/net/wireless/p54/p54spi.c b/drivers/net/wireless/p54/p54spi.c new file mode 100644 index 0000000..1323d3c --- /dev/null +++ b/drivers/net/wireless/p54/p54spi.c @@ -0,0 +1,742 @@ +/* + * Copyright (C) 2008 Christian Lamparter + * Copyright 2008 Johannes Berg + * + * This driver is a port from stlc45xx: + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "p54spi.h" +#include "p54.h" + +#include "lmac.h" + +#ifdef CPTCFG_P54_SPI_DEFAULT_EEPROM +#include "p54spi_eeprom.h" +#endif /* CPTCFG_P54_SPI_DEFAULT_EEPROM */ + +MODULE_FIRMWARE("3826.arm"); + +/* + * gpios should be handled in board files and provided via platform data, + * but because it's currently impossible for p54spi to have a header file + * in include/linux, let's use module paramaters for now + */ + +static int p54spi_gpio_power = 97; +module_param(p54spi_gpio_power, int, 0444); +MODULE_PARM_DESC(p54spi_gpio_power, "gpio number for power line"); + +static int p54spi_gpio_irq = 87; +module_param(p54spi_gpio_irq, int, 0444); +MODULE_PARM_DESC(p54spi_gpio_irq, "gpio number for irq line"); + +static void p54spi_spi_read(struct p54s_priv *priv, u8 address, + void *buf, size_t len) +{ + struct spi_transfer t[2]; + struct spi_message m; + __le16 addr; + + /* We first push the address */ + addr = cpu_to_le16(address << 8 | SPI_ADRS_READ_BIT_15); + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + t[0].tx_buf = &addr; + t[0].len = sizeof(addr); + spi_message_add_tail(&t[0], &m); + + t[1].rx_buf = buf; + t[1].len = len; + spi_message_add_tail(&t[1], &m); + + spi_sync(priv->spi, &m); +} + + +static void p54spi_spi_write(struct p54s_priv *priv, u8 address, + const void *buf, size_t len) +{ + struct spi_transfer t[3]; + struct spi_message m; + __le16 addr; + + /* We first push the address */ + addr = cpu_to_le16(address << 8); + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + t[0].tx_buf = &addr; + t[0].len = sizeof(addr); + spi_message_add_tail(&t[0], &m); + + t[1].tx_buf = buf; + t[1].len = len & ~1; + spi_message_add_tail(&t[1], &m); + + if (len % 2) { + __le16 last_word; + last_word = cpu_to_le16(((u8 *)buf)[len - 1]); + + t[2].tx_buf = &last_word; + t[2].len = sizeof(last_word); + spi_message_add_tail(&t[2], &m); + } + + spi_sync(priv->spi, &m); +} + +static u32 p54spi_read32(struct p54s_priv *priv, u8 addr) +{ + __le32 val; + + p54spi_spi_read(priv, addr, &val, sizeof(val)); + + return le32_to_cpu(val); +} + +static inline void p54spi_write16(struct p54s_priv *priv, u8 addr, __le16 val) +{ + p54spi_spi_write(priv, addr, &val, sizeof(val)); +} + +static inline void p54spi_write32(struct p54s_priv *priv, u8 addr, __le32 val) +{ + p54spi_spi_write(priv, addr, &val, sizeof(val)); +} + +static int p54spi_wait_bit(struct p54s_priv *priv, u16 reg, u32 bits) +{ + int i; + + for (i = 0; i < 2000; i++) { + u32 buffer = p54spi_read32(priv, reg); + if ((buffer & bits) == bits) + return 1; + } + return 0; +} + +static int p54spi_spi_write_dma(struct p54s_priv *priv, __le32 base, + const void *buf, size_t len) +{ + if (!p54spi_wait_bit(priv, SPI_ADRS_DMA_WRITE_CTRL, HOST_ALLOWED)) { + dev_err(&priv->spi->dev, "spi_write_dma not allowed " + "to DMA write.\n"); + return -EAGAIN; + } + + p54spi_write16(priv, SPI_ADRS_DMA_WRITE_CTRL, + cpu_to_le16(SPI_DMA_WRITE_CTRL_ENABLE)); + + p54spi_write16(priv, SPI_ADRS_DMA_WRITE_LEN, cpu_to_le16(len)); + p54spi_write32(priv, SPI_ADRS_DMA_WRITE_BASE, base); + p54spi_spi_write(priv, SPI_ADRS_DMA_DATA, buf, len); + return 0; +} + +static int p54spi_request_firmware(struct ieee80211_hw *dev) +{ + struct p54s_priv *priv = dev->priv; + int ret; + + /* FIXME: should driver use it's own struct device? */ + ret = request_firmware(&priv->firmware, "3826.arm", &priv->spi->dev); + + if (ret < 0) { + dev_err(&priv->spi->dev, "request_firmware() failed: %d", ret); + return ret; + } + + ret = p54_parse_firmware(dev, priv->firmware); + if (ret) { + release_firmware(priv->firmware); + return ret; + } + + return 0; +} + +static int p54spi_request_eeprom(struct ieee80211_hw *dev) +{ + struct p54s_priv *priv = dev->priv; + const struct firmware *eeprom; + int ret; + + /* + * allow users to customize their eeprom. + */ + + ret = request_firmware(&eeprom, "3826.eeprom", &priv->spi->dev); + if (ret < 0) { +#ifdef CPTCFG_P54_SPI_DEFAULT_EEPROM + dev_info(&priv->spi->dev, "loading default eeprom...\n"); + ret = p54_parse_eeprom(dev, (void *) p54spi_eeprom, + sizeof(p54spi_eeprom)); +#else + dev_err(&priv->spi->dev, "Failed to request user eeprom\n"); +#endif /* CPTCFG_P54_SPI_DEFAULT_EEPROM */ + } else { + dev_info(&priv->spi->dev, "loading user eeprom...\n"); + ret = p54_parse_eeprom(dev, (void *) eeprom->data, + (int)eeprom->size); + release_firmware(eeprom); + } + return ret; +} + +static int p54spi_upload_firmware(struct ieee80211_hw *dev) +{ + struct p54s_priv *priv = dev->priv; + unsigned long fw_len, _fw_len; + unsigned int offset = 0; + int err = 0; + u8 *fw; + + fw_len = priv->firmware->size; + fw = kmemdup(priv->firmware->data, fw_len, GFP_KERNEL); + if (!fw) + return -ENOMEM; + + /* stop the device */ + p54spi_write16(priv, SPI_ADRS_DEV_CTRL_STAT, cpu_to_le16( + SPI_CTRL_STAT_HOST_OVERRIDE | SPI_CTRL_STAT_HOST_RESET | + SPI_CTRL_STAT_START_HALTED)); + + msleep(TARGET_BOOT_SLEEP); + + p54spi_write16(priv, SPI_ADRS_DEV_CTRL_STAT, cpu_to_le16( + SPI_CTRL_STAT_HOST_OVERRIDE | + SPI_CTRL_STAT_START_HALTED)); + + msleep(TARGET_BOOT_SLEEP); + + while (fw_len > 0) { + _fw_len = min_t(long, fw_len, SPI_MAX_PACKET_SIZE); + + err = p54spi_spi_write_dma(priv, cpu_to_le32( + ISL38XX_DEV_FIRMWARE_ADDR + offset), + (fw + offset), _fw_len); + if (err < 0) + goto out; + + fw_len -= _fw_len; + offset += _fw_len; + } + + BUG_ON(fw_len != 0); + + /* enable host interrupts */ + p54spi_write32(priv, SPI_ADRS_HOST_INT_EN, + cpu_to_le32(SPI_HOST_INTS_DEFAULT)); + + /* boot the device */ + p54spi_write16(priv, SPI_ADRS_DEV_CTRL_STAT, cpu_to_le16( + SPI_CTRL_STAT_HOST_OVERRIDE | SPI_CTRL_STAT_HOST_RESET | + SPI_CTRL_STAT_RAM_BOOT)); + + msleep(TARGET_BOOT_SLEEP); + + p54spi_write16(priv, SPI_ADRS_DEV_CTRL_STAT, cpu_to_le16( + SPI_CTRL_STAT_HOST_OVERRIDE | SPI_CTRL_STAT_RAM_BOOT)); + msleep(TARGET_BOOT_SLEEP); + +out: + kfree(fw); + return err; +} + +static void p54spi_power_off(struct p54s_priv *priv) +{ + disable_irq(gpio_to_irq(p54spi_gpio_irq)); + gpio_set_value(p54spi_gpio_power, 0); +} + +static void p54spi_power_on(struct p54s_priv *priv) +{ + gpio_set_value(p54spi_gpio_power, 1); + enable_irq(gpio_to_irq(p54spi_gpio_irq)); + + /* + * need to wait a while before device can be accessed, the length + * is just a guess + */ + msleep(10); +} + +static inline void p54spi_int_ack(struct p54s_priv *priv, u32 val) +{ + p54spi_write32(priv, SPI_ADRS_HOST_INT_ACK, cpu_to_le32(val)); +} + +static int p54spi_wakeup(struct p54s_priv *priv) +{ + /* wake the chip */ + p54spi_write32(priv, SPI_ADRS_ARM_INTERRUPTS, + cpu_to_le32(SPI_TARGET_INT_WAKEUP)); + + /* And wait for the READY interrupt */ + if (!p54spi_wait_bit(priv, SPI_ADRS_HOST_INTERRUPTS, + SPI_HOST_INT_READY)) { + dev_err(&priv->spi->dev, "INT_READY timeout\n"); + return -EBUSY; + } + + p54spi_int_ack(priv, SPI_HOST_INT_READY); + return 0; +} + +static inline void p54spi_sleep(struct p54s_priv *priv) +{ + p54spi_write32(priv, SPI_ADRS_ARM_INTERRUPTS, + cpu_to_le32(SPI_TARGET_INT_SLEEP)); +} + +static void p54spi_int_ready(struct p54s_priv *priv) +{ + p54spi_write32(priv, SPI_ADRS_HOST_INT_EN, cpu_to_le32( + SPI_HOST_INT_UPDATE | SPI_HOST_INT_SW_UPDATE)); + + switch (priv->fw_state) { + case FW_STATE_BOOTING: + priv->fw_state = FW_STATE_READY; + complete(&priv->fw_comp); + break; + case FW_STATE_RESETTING: + priv->fw_state = FW_STATE_READY; + /* TODO: reinitialize state */ + break; + default: + break; + } +} + +static int p54spi_rx(struct p54s_priv *priv) +{ + struct sk_buff *skb; + u16 len; + u16 rx_head[2]; +#define READAHEAD_SZ (sizeof(rx_head)-sizeof(u16)) + + if (p54spi_wakeup(priv) < 0) + return -EBUSY; + + /* Read data size and first data word in one SPI transaction + * This is workaround for firmware/DMA bug, + * when first data word gets lost under high load. + */ + p54spi_spi_read(priv, SPI_ADRS_DMA_DATA, rx_head, sizeof(rx_head)); + len = rx_head[0]; + + if (len == 0) { + p54spi_sleep(priv); + dev_err(&priv->spi->dev, "rx request of zero bytes\n"); + return 0; + } + + /* Firmware may insert up to 4 padding bytes after the lmac header, + * but it does not amend the size of SPI data transfer. + * Such packets has correct data size in header, thus referencing + * past the end of allocated skb. Reserve extra 4 bytes for this case */ + skb = dev_alloc_skb(len + 4); + if (!skb) { + p54spi_sleep(priv); + dev_err(&priv->spi->dev, "could not alloc skb"); + return -ENOMEM; + } + + if (len <= READAHEAD_SZ) { + memcpy(skb_put(skb, len), rx_head + 1, len); + } else { + memcpy(skb_put(skb, READAHEAD_SZ), rx_head + 1, READAHEAD_SZ); + p54spi_spi_read(priv, SPI_ADRS_DMA_DATA, + skb_put(skb, len - READAHEAD_SZ), + len - READAHEAD_SZ); + } + p54spi_sleep(priv); + /* Put additional bytes to compensate for the possible + * alignment-caused truncation */ + skb_put(skb, 4); + + if (p54_rx(priv->hw, skb) == 0) + dev_kfree_skb(skb); + + return 0; +} + + +static irqreturn_t p54spi_interrupt(int irq, void *config) +{ + struct spi_device *spi = config; + struct p54s_priv *priv = spi_get_drvdata(spi); + + ieee80211_queue_work(priv->hw, &priv->work); + + return IRQ_HANDLED; +} + +static int p54spi_tx_frame(struct p54s_priv *priv, struct sk_buff *skb) +{ + struct p54_hdr *hdr = (struct p54_hdr *) skb->data; + int ret = 0; + + if (p54spi_wakeup(priv) < 0) + return -EBUSY; + + ret = p54spi_spi_write_dma(priv, hdr->req_id, skb->data, skb->len); + if (ret < 0) + goto out; + + if (!p54spi_wait_bit(priv, SPI_ADRS_HOST_INTERRUPTS, + SPI_HOST_INT_WR_READY)) { + dev_err(&priv->spi->dev, "WR_READY timeout\n"); + ret = -EAGAIN; + goto out; + } + + p54spi_int_ack(priv, SPI_HOST_INT_WR_READY); + + if (FREE_AFTER_TX(skb)) + p54_free_skb(priv->hw, skb); +out: + p54spi_sleep(priv); + return ret; +} + +static int p54spi_wq_tx(struct p54s_priv *priv) +{ + struct p54s_tx_info *entry; + struct sk_buff *skb; + struct ieee80211_tx_info *info; + struct p54_tx_info *minfo; + struct p54s_tx_info *dinfo; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&priv->tx_lock, flags); + + while (!list_empty(&priv->tx_pending)) { + entry = list_entry(priv->tx_pending.next, + struct p54s_tx_info, tx_list); + + list_del_init(&entry->tx_list); + + spin_unlock_irqrestore(&priv->tx_lock, flags); + + dinfo = container_of((void *) entry, struct p54s_tx_info, + tx_list); + minfo = container_of((void *) dinfo, struct p54_tx_info, + data); + info = container_of((void *) minfo, struct ieee80211_tx_info, + rate_driver_data); + skb = container_of((void *) info, struct sk_buff, cb); + + ret = p54spi_tx_frame(priv, skb); + + if (ret < 0) { + p54_free_skb(priv->hw, skb); + return ret; + } + + spin_lock_irqsave(&priv->tx_lock, flags); + } + spin_unlock_irqrestore(&priv->tx_lock, flags); + return ret; +} + +static void p54spi_op_tx(struct ieee80211_hw *dev, struct sk_buff *skb) +{ + struct p54s_priv *priv = dev->priv; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct p54_tx_info *mi = (struct p54_tx_info *) info->rate_driver_data; + struct p54s_tx_info *di = (struct p54s_tx_info *) mi->data; + unsigned long flags; + + BUILD_BUG_ON(sizeof(*di) > sizeof((mi->data))); + + spin_lock_irqsave(&priv->tx_lock, flags); + list_add_tail(&di->tx_list, &priv->tx_pending); + spin_unlock_irqrestore(&priv->tx_lock, flags); + + ieee80211_queue_work(priv->hw, &priv->work); +} + +static void p54spi_work(struct work_struct *work) +{ + struct p54s_priv *priv = container_of(work, struct p54s_priv, work); + u32 ints; + int ret; + + mutex_lock(&priv->mutex); + + if (priv->fw_state == FW_STATE_OFF) + goto out; + + ints = p54spi_read32(priv, SPI_ADRS_HOST_INTERRUPTS); + + if (ints & SPI_HOST_INT_READY) { + p54spi_int_ready(priv); + p54spi_int_ack(priv, SPI_HOST_INT_READY); + } + + if (priv->fw_state != FW_STATE_READY) + goto out; + + if (ints & SPI_HOST_INT_UPDATE) { + p54spi_int_ack(priv, SPI_HOST_INT_UPDATE); + ret = p54spi_rx(priv); + if (ret < 0) + goto out; + } + if (ints & SPI_HOST_INT_SW_UPDATE) { + p54spi_int_ack(priv, SPI_HOST_INT_SW_UPDATE); + ret = p54spi_rx(priv); + if (ret < 0) + goto out; + } + + ret = p54spi_wq_tx(priv); +out: + mutex_unlock(&priv->mutex); +} + +static int p54spi_op_start(struct ieee80211_hw *dev) +{ + struct p54s_priv *priv = dev->priv; + unsigned long timeout; + int ret = 0; + + if (mutex_lock_interruptible(&priv->mutex)) { + ret = -EINTR; + goto out; + } + + priv->fw_state = FW_STATE_BOOTING; + + p54spi_power_on(priv); + + ret = p54spi_upload_firmware(dev); + if (ret < 0) { + p54spi_power_off(priv); + goto out_unlock; + } + + mutex_unlock(&priv->mutex); + + timeout = msecs_to_jiffies(2000); + timeout = wait_for_completion_interruptible_timeout(&priv->fw_comp, + timeout); + if (!timeout) { + dev_err(&priv->spi->dev, "firmware boot failed"); + p54spi_power_off(priv); + ret = -1; + goto out; + } + + if (mutex_lock_interruptible(&priv->mutex)) { + ret = -EINTR; + p54spi_power_off(priv); + goto out; + } + + WARN_ON(priv->fw_state != FW_STATE_READY); + +out_unlock: + mutex_unlock(&priv->mutex); + +out: + return ret; +} + +static void p54spi_op_stop(struct ieee80211_hw *dev) +{ + struct p54s_priv *priv = dev->priv; + unsigned long flags; + + mutex_lock(&priv->mutex); + WARN_ON(priv->fw_state != FW_STATE_READY); + + p54spi_power_off(priv); + spin_lock_irqsave(&priv->tx_lock, flags); + INIT_LIST_HEAD(&priv->tx_pending); + spin_unlock_irqrestore(&priv->tx_lock, flags); + + priv->fw_state = FW_STATE_OFF; + mutex_unlock(&priv->mutex); + + cancel_work_sync(&priv->work); +} + +static int p54spi_probe(struct spi_device *spi) +{ + struct p54s_priv *priv = NULL; + struct ieee80211_hw *hw; + int ret = -EINVAL; + + hw = p54_init_common(sizeof(*priv)); + if (!hw) { + dev_err(&spi->dev, "could not alloc ieee80211_hw"); + return -ENOMEM; + } + + priv = hw->priv; + priv->hw = hw; + spi_set_drvdata(spi, priv); + priv->spi = spi; + + spi->bits_per_word = 16; + spi->max_speed_hz = 24000000; + + ret = spi_setup(spi); + if (ret < 0) { + dev_err(&priv->spi->dev, "spi_setup failed"); + goto err_free; + } + + ret = gpio_request(p54spi_gpio_power, "p54spi power"); + if (ret < 0) { + dev_err(&priv->spi->dev, "power GPIO request failed: %d", ret); + goto err_free; + } + + ret = gpio_request(p54spi_gpio_irq, "p54spi irq"); + if (ret < 0) { + dev_err(&priv->spi->dev, "irq GPIO request failed: %d", ret); + goto err_free_gpio_power; + } + + gpio_direction_output(p54spi_gpio_power, 0); + gpio_direction_input(p54spi_gpio_irq); + + ret = request_irq(gpio_to_irq(p54spi_gpio_irq), + p54spi_interrupt, IRQF_DISABLED, "p54spi", + priv->spi); + if (ret < 0) { + dev_err(&priv->spi->dev, "request_irq() failed"); + goto err_free_gpio_irq; + } + + irq_set_irq_type(gpio_to_irq(p54spi_gpio_irq), IRQ_TYPE_EDGE_RISING); + + disable_irq(gpio_to_irq(p54spi_gpio_irq)); + + INIT_WORK(&priv->work, p54spi_work); + init_completion(&priv->fw_comp); + INIT_LIST_HEAD(&priv->tx_pending); + mutex_init(&priv->mutex); + spin_lock_init(&priv->tx_lock); + SET_IEEE80211_DEV(hw, &spi->dev); + priv->common.open = p54spi_op_start; + priv->common.stop = p54spi_op_stop; + priv->common.tx = p54spi_op_tx; + + ret = p54spi_request_firmware(hw); + if (ret < 0) + goto err_free_common; + + ret = p54spi_request_eeprom(hw); + if (ret) + goto err_free_common; + + ret = p54_register_common(hw, &priv->spi->dev); + if (ret) + goto err_free_common; + + return 0; + +err_free_common: + free_irq(gpio_to_irq(p54spi_gpio_irq), spi); +err_free_gpio_irq: + gpio_free(p54spi_gpio_irq); +err_free_gpio_power: + gpio_free(p54spi_gpio_power); +err_free: + p54_free_common(priv->hw); + return ret; +} + +static int p54spi_remove(struct spi_device *spi) +{ + struct p54s_priv *priv = spi_get_drvdata(spi); + + p54_unregister_common(priv->hw); + + free_irq(gpio_to_irq(p54spi_gpio_irq), spi); + + gpio_free(p54spi_gpio_power); + gpio_free(p54spi_gpio_irq); + release_firmware(priv->firmware); + + mutex_destroy(&priv->mutex); + + p54_free_common(priv->hw); + + return 0; +} + + +static struct spi_driver p54spi_driver = { + .driver = { + .name = "p54spi", + .owner = THIS_MODULE, + }, + + .probe = p54spi_probe, + .remove = p54spi_remove, +}; + +static int __init p54spi_init(void) +{ + int ret; + + ret = spi_register_driver(&p54spi_driver); + if (ret < 0) { + printk(KERN_ERR "failed to register SPI driver: %d", ret); + goto out; + } + +out: + return ret; +} + +static void __exit p54spi_exit(void) +{ + spi_unregister_driver(&p54spi_driver); +} + +module_init(p54spi_init); +module_exit(p54spi_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Christian Lamparter "); +MODULE_ALIAS("spi:cx3110x"); +MODULE_ALIAS("spi:p54spi"); +MODULE_ALIAS("spi:stlc45xx"); diff --git a/drivers/net/wireless/p54/p54spi.h b/drivers/net/wireless/p54/p54spi.h new file mode 100644 index 0000000..dfaa62a --- /dev/null +++ b/drivers/net/wireless/p54/p54spi.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2008 Christian Lamparter + * + * This driver is a port from stlc45xx: + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef P54SPI_H +#define P54SPI_H + +#include +#include +#include + +#include "p54.h" + +/* Bit 15 is read/write bit; ON = READ, OFF = WRITE */ +#define SPI_ADRS_READ_BIT_15 0x8000 + +#define SPI_ADRS_ARM_INTERRUPTS 0x00 +#define SPI_ADRS_ARM_INT_EN 0x04 + +#define SPI_ADRS_HOST_INTERRUPTS 0x08 +#define SPI_ADRS_HOST_INT_EN 0x0c +#define SPI_ADRS_HOST_INT_ACK 0x10 + +#define SPI_ADRS_GEN_PURP_1 0x14 +#define SPI_ADRS_GEN_PURP_2 0x18 + +#define SPI_ADRS_DEV_CTRL_STAT 0x26 /* high word */ + +#define SPI_ADRS_DMA_DATA 0x28 + +#define SPI_ADRS_DMA_WRITE_CTRL 0x2c +#define SPI_ADRS_DMA_WRITE_LEN 0x2e +#define SPI_ADRS_DMA_WRITE_BASE 0x30 + +#define SPI_ADRS_DMA_READ_CTRL 0x34 +#define SPI_ADRS_DMA_READ_LEN 0x36 +#define SPI_ADRS_DMA_READ_BASE 0x38 + +#define SPI_CTRL_STAT_HOST_OVERRIDE 0x8000 +#define SPI_CTRL_STAT_START_HALTED 0x4000 +#define SPI_CTRL_STAT_RAM_BOOT 0x2000 +#define SPI_CTRL_STAT_HOST_RESET 0x1000 +#define SPI_CTRL_STAT_HOST_CPU_EN 0x0800 + +#define SPI_DMA_WRITE_CTRL_ENABLE 0x0001 +#define SPI_DMA_READ_CTRL_ENABLE 0x0001 +#define HOST_ALLOWED (1 << 7) + +#define SPI_TIMEOUT 100 /* msec */ + +#define SPI_MAX_TX_PACKETS 32 + +#define SPI_MAX_PACKET_SIZE 32767 + +#define SPI_TARGET_INT_WAKEUP 0x00000001 +#define SPI_TARGET_INT_SLEEP 0x00000002 +#define SPI_TARGET_INT_RDDONE 0x00000004 + +#define SPI_TARGET_INT_CTS 0x00004000 +#define SPI_TARGET_INT_DR 0x00008000 + +#define SPI_HOST_INT_READY 0x00000001 +#define SPI_HOST_INT_WR_READY 0x00000002 +#define SPI_HOST_INT_SW_UPDATE 0x00000004 +#define SPI_HOST_INT_UPDATE 0x10000000 + +/* clear to send */ +#define SPI_HOST_INT_CR 0x00004000 + +/* data ready */ +#define SPI_HOST_INT_DR 0x00008000 + +#define SPI_HOST_INTS_DEFAULT \ + (SPI_HOST_INT_READY | SPI_HOST_INT_UPDATE | SPI_HOST_INT_SW_UPDATE) + +#define TARGET_BOOT_SLEEP 50 + +struct p54s_dma_regs { + __le16 cmd; + __le16 len; + __le32 addr; +} __packed; + +struct p54s_tx_info { + struct list_head tx_list; +}; + +struct p54s_priv { + /* p54_common has to be the first entry */ + struct p54_common common; + struct ieee80211_hw *hw; + struct spi_device *spi; + + struct work_struct work; + + struct mutex mutex; + struct completion fw_comp; + + spinlock_t tx_lock; + + /* protected by tx_lock */ + struct list_head tx_pending; + + enum fw_state fw_state; + const struct firmware *firmware; +}; + +#endif /* P54SPI_H */ diff --git a/drivers/net/wireless/p54/p54spi_eeprom.h b/drivers/net/wireless/p54/p54spi_eeprom.h new file mode 100644 index 0000000..0b7bfb0 --- /dev/null +++ b/drivers/net/wireless/p54/p54spi_eeprom.h @@ -0,0 +1,679 @@ +/* + * Copyright (C) 2003 Conexant Americas Inc. All Rights Reserved. + * Copyright (C) 2004, 2005, 2006 Nokia Corporation + * Copyright 2008 Johannes Berg + * Copyright 2008 Christian Lamparter + * + * based on: + * - cx3110x's pda.h from Nokia + * - cx3110-transfer.log by Johannes Berg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef P54SPI_EEPROM_H +#define P54SPI_EEPROM_H + +static unsigned char p54spi_eeprom[] = { + +/* struct eeprom_pda_wrap */ +0x47, 0x4d, 0x55, 0xaa, /* magic */ +0x00, 0x00, /* pad */ +0x00, 0x00, /* eeprom_pda_data_wrap length */ +0x00, 0x00, 0x00, 0x00, /* arm opcode */ + +/* bogus MAC address */ +0x04, 0x00, 0x01, 0x01, /* PDR_MAC_ADDRESS */ + 0x00, 0x02, 0xee, 0xc0, 0xff, 0xee, + +/* struct bootrec_exp_if */ +0x06, 0x00, 0x01, 0x10, /* PDR_INTERFACE_LIST */ + 0x00, 0x00, /* role */ + 0x0f, 0x00, /* if_id */ + 0x85, 0x00, /* variant = Longbow RF, 2GHz */ + 0x01, 0x00, /* btm_compat */ + 0x1f, 0x00, /* top_compat */ + +0x03, 0x00, 0x02, 0x10, /* PDR_HARDWARE_PLATFORM_COMPONENT_ID */ + 0x03, 0x20, 0x00, 0x43, + +/* struct pda_country[6] */ +0x0d, 0x00, 0x07, 0x10, /* PDR_COUNTRY_LIST */ + 0x10, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, + 0x31, 0x00, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, + +/* struct pda_country */ +0x03, 0x00, 0x08, 0x10, /* PDR_DEFAULT_COUNTRY */ + 0x30, 0x00, 0x00, 0x00, /* ETSI */ + +0x03, 0x00, 0x00, 0x11, /* PDR_ANTENNA_GAIN */ + 0x08, 0x08, 0x08, 0x08, + +0x0a, 0x00, 0xff, 0xca, /* PDR_RSSI_LINEAR_APPROXIMATION_CUSTOMV2 */ + 0x01, 0x00, 0x0a, 0x00, + 0x00, 0x00, 0x0a, 0x00, + 0x85, 0x09, 0x0a, 0x01, 0x72, 0xfe, 0x1a, 0x00, 0x00, 0x00, + +/* struct pda_custom_wrapper */ +0x10, 0x06, 0x5d, 0xb0, /* PDR_PRISM_PA_CAL_CURVE_DATA_CUSTOM */ + 0x0d, 0x00, 0xee, 0x00, /* 13 entries, 238 bytes per entry */ + 0x00, 0x00, 0x16, 0x0c, /* no offset, 3094 total len */ + /* 2412 MHz */ + 0x6c, 0x09, + 0x10, 0x01, 0x9a, 0x84, + 0xaa, 0x8a, 0xaa, 0x8a, 0xaa, 0x8a, 0xaa, 0x8a, + 0x3c, 0xb6, 0x3c, 0xb6, 0x3c, 0xb6, 0x3c, 0xb6, + 0x3c, 0xb6, 0x3c, 0xb6, 0x3c, 0xb6, 0x3c, 0xb6, + 0xf0, 0x00, 0x94, 0x6c, + 0x99, 0x82, 0x99, 0x82, 0x99, 0x82, 0x99, 0x82, + 0x2b, 0xae, 0x2b, 0xae, 0x2b, 0xae, 0x2b, 0xae, + 0x2b, 0xae, 0x2b, 0xae, 0x2b, 0xae, 0x2b, 0xae, + 0xd0, 0x00, 0xaa, 0x5a, + 0x88, 0x7a, 0x88, 0x7a, 0x88, 0x7a, 0x88, 0x7a, + 0x1a, 0xa6, 0x1a, 0xa6, 0x1a, 0xa6, 0x1a, 0xa6, + 0x1a, 0xa6, 0x1a, 0xa6, 0x1a, 0xa6, 0x1a, 0xa6, + 0xa0, 0x00, 0xf3, 0x47, + 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, + 0x00, 0x9a, 0x00, 0x9a, 0x00, 0x9a, 0x00, 0x9a, + 0x00, 0x9a, 0x00, 0x9a, 0x00, 0x9a, 0x00, 0x9a, + 0x50, 0x00, 0x59, 0x36, + 0x43, 0x5a, 0x43, 0x5a, 0x43, 0x5a, 0x43, 0x5a, + 0xd5, 0x85, 0xd5, 0x85, 0xd5, 0x85, 0xd5, 0x85, + 0xd5, 0x85, 0xd5, 0x85, 0xd5, 0x85, 0xd5, 0x85, + 0x00, 0x00, 0xe4, 0x2d, + 0x18, 0x46, 0x18, 0x46, 0x18, 0x46, 0x18, 0x46, + 0xaa, 0x71, 0xaa, 0x71, 0xaa, 0x71, 0xaa, 0x71, + 0xaa, 0x71, 0xaa, 0x71, 0xaa, 0x71, 0xaa, 0x71, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, + + /* 2417 MHz */ + 0x71, 0x09, + 0x10, 0x01, 0xb9, 0x83, + 0x7d, 0x8a, 0x7d, 0x8a, 0x7d, 0x8a, 0x7d, 0x8a, + 0x0f, 0xb6, 0x0f, 0xb6, 0x0f, 0xb6, 0x0f, 0xb6, + 0x0f, 0xb6, 0x0f, 0xb6, 0x0f, 0xb6, 0x0f, 0xb6, + 0xf0, 0x00, 0x2e, 0x6c, + 0x68, 0x82, 0x68, 0x82, 0x68, 0x82, 0x68, 0x82, + 0xfa, 0xad, 0xfa, 0xad, 0xfa, 0xad, 0xfa, 0xad, + 0xfa, 0xad, 0xfa, 0xad, 0xfa, 0xad, 0xfa, 0xad, + 0xd0, 0x00, 0x8d, 0x5a, + 0x52, 0x7a, 0x52, 0x7a, 0x52, 0x7a, 0x52, 0x7a, + 0xe4, 0xa5, 0xe4, 0xa5, 0xe4, 0xa5, 0xe4, 0xa5, + 0xe4, 0xa5, 0xe4, 0xa5, 0xe4, 0xa5, 0xe4, 0xa5, + 0xa0, 0x00, 0x0a, 0x48, + 0x32, 0x6e, 0x32, 0x6e, 0x32, 0x6e, 0x32, 0x6e, + 0xc4, 0x99, 0xc4, 0x99, 0xc4, 0x99, 0xc4, 0x99, + 0xc4, 0x99, 0xc4, 0x99, 0xc4, 0x99, 0xc4, 0x99, + 0x50, 0x00, 0x7c, 0x36, + 0xfc, 0x59, 0xfc, 0x59, 0xfc, 0x59, 0xfc, 0x59, + 0x8e, 0x85, 0x8e, 0x85, 0x8e, 0x85, 0x8e, 0x85, + 0x8e, 0x85, 0x8e, 0x85, 0x8e, 0x85, 0x8e, 0x85, + 0x00, 0x00, 0xf5, 0x2d, + 0xc6, 0x45, 0xc6, 0x45, 0xc6, 0x45, 0xc6, 0x45, + 0x58, 0x71, 0x58, 0x71, 0x58, 0x71, 0x58, 0x71, + 0x58, 0x71, 0x58, 0x71, 0x58, 0x71, 0x58, 0x71, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, + + /* 2422 MHz */ + 0x76, 0x09, + 0x10, 0x01, 0xb9, 0x83, + 0x7d, 0x8a, 0x7d, 0x8a, 0x7d, 0x8a, 0x7d, 0x8a, + 0x0f, 0xb6, 0x0f, 0xb6, 0x0f, 0xb6, 0x0f, 0xb6, + 0x0f, 0xb6, 0x0f, 0xb6, 0x0f, 0xb6, 0x0f, 0xb6, + 0xf0, 0x00, 0x2e, 0x6c, + 0x68, 0x82, 0x68, 0x82, 0x68, 0x82, 0x68, 0x82, + 0xfa, 0xad, 0xfa, 0xad, 0xfa, 0xad, 0xfa, 0xad, + 0xfa, 0xad, 0xfa, 0xad, 0xfa, 0xad, 0xfa, 0xad, + 0xd0, 0x00, 0x8d, 0x5a, + 0x52, 0x7a, 0x52, 0x7a, 0x52, 0x7a, 0x52, 0x7a, + 0xe4, 0xa5, 0xe4, 0xa5, 0xe4, 0xa5, 0xe4, 0xa5, + 0xe4, 0xa5, 0xe4, 0xa5, 0xe4, 0xa5, 0xe4, 0xa5, + 0xa0, 0x00, 0x0a, 0x48, + 0x32, 0x6e, 0x32, 0x6e, 0x32, 0x6e, 0x32, 0x6e, + 0xc4, 0x99, 0xc4, 0x99, 0xc4, 0x99, 0xc4, 0x99, + 0xc4, 0x99, 0xc4, 0x99, 0xc4, 0x99, 0xc4, 0x99, + 0x50, 0x00, 0x7c, 0x36, + 0xfc, 0x59, 0xfc, 0x59, 0xfc, 0x59, 0xfc, 0x59, + 0x8e, 0x85, 0x8e, 0x85, 0x8e, 0x85, 0x8e, 0x85, + 0x8e, 0x85, 0x8e, 0x85, 0x8e, 0x85, 0x8e, 0x85, + 0x00, 0x00, 0xf5, 0x2d, + 0xc6, 0x45, 0xc6, 0x45, 0xc6, 0x45, 0xc6, 0x45, + 0x58, 0x71, 0x58, 0x71, 0x58, 0x71, 0x58, 0x71, + 0x58, 0x71, 0x58, 0x71, 0x58, 0x71, 0x58, 0x71, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, + + /* 2427 MHz */ + 0x7b, 0x09, + 0x10, 0x01, 0x48, 0x83, + 0x67, 0x8a, 0x67, 0x8a, 0x67, 0x8a, 0x67, 0x8a, + 0xf9, 0xb5, 0xf9, 0xb5, 0xf9, 0xb5, 0xf9, 0xb5, + 0xf9, 0xb5, 0xf9, 0xb5, 0xf9, 0xb5, 0xf9, 0xb5, + 0xf0, 0x00, 0xfb, 0x6b, + 0x50, 0x82, 0x50, 0x82, 0x50, 0x82, 0x50, 0x82, + 0xe2, 0xad, 0xe2, 0xad, 0xe2, 0xad, 0xe2, 0xad, + 0xe2, 0xad, 0xe2, 0xad, 0xe2, 0xad, 0xe2, 0xad, + 0xd0, 0x00, 0x7e, 0x5a, + 0x38, 0x7a, 0x38, 0x7a, 0x38, 0x7a, 0x38, 0x7a, + 0xca, 0xa5, 0xca, 0xa5, 0xca, 0xa5, 0xca, 0xa5, + 0xca, 0xa5, 0xca, 0xa5, 0xca, 0xa5, 0xca, 0xa5, + 0xa0, 0x00, 0x15, 0x48, + 0x14, 0x6e, 0x14, 0x6e, 0x14, 0x6e, 0x14, 0x6e, + 0xa6, 0x99, 0xa6, 0x99, 0xa6, 0x99, 0xa6, 0x99, + 0xa6, 0x99, 0xa6, 0x99, 0xa6, 0x99, 0xa6, 0x99, + 0x50, 0x00, 0x8e, 0x36, + 0xd9, 0x59, 0xd9, 0x59, 0xd9, 0x59, 0xd9, 0x59, + 0x6b, 0x85, 0x6b, 0x85, 0x6b, 0x85, 0x6b, 0x85, + 0x6b, 0x85, 0x6b, 0x85, 0x6b, 0x85, 0x6b, 0x85, + 0x00, 0x00, 0xfe, 0x2d, + 0x9d, 0x45, 0x9d, 0x45, 0x9d, 0x45, 0x9d, 0x45, + 0x2f, 0x71, 0x2f, 0x71, 0x2f, 0x71, 0x2f, 0x71, + 0x2f, 0x71, 0x2f, 0x71, 0x2f, 0x71, 0x2f, 0x71, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, + + /* 2432 MHz */ + 0x80, 0x09, + 0x10, 0x01, 0xd7, 0x82, + 0x51, 0x8a, 0x51, 0x8a, 0x51, 0x8a, 0x51, 0x8a, + 0xe3, 0xb5, 0xe3, 0xb5, 0xe3, 0xb5, 0xe3, 0xb5, + 0xe3, 0xb5, 0xe3, 0xb5, 0xe3, 0xb5, 0xe3, 0xb5, + 0xf0, 0x00, 0xc8, 0x6b, + 0x37, 0x82, 0x37, 0x82, 0x37, 0x82, 0x37, 0x82, + 0xc9, 0xad, 0xc9, 0xad, 0xc9, 0xad, 0xc9, 0xad, + 0xc9, 0xad, 0xc9, 0xad, 0xc9, 0xad, 0xc9, 0xad, + 0xd0, 0x00, 0x6f, 0x5a, + 0x1d, 0x7a, 0x1d, 0x7a, 0x1d, 0x7a, 0x1d, 0x7a, + 0xaf, 0xa5, 0xaf, 0xa5, 0xaf, 0xa5, 0xaf, 0xa5, + 0xaf, 0xa5, 0xaf, 0xa5, 0xaf, 0xa5, 0xaf, 0xa5, + 0xa0, 0x00, 0x20, 0x48, + 0xf6, 0x6d, 0xf6, 0x6d, 0xf6, 0x6d, 0xf6, 0x6d, + 0x88, 0x99, 0x88, 0x99, 0x88, 0x99, 0x88, 0x99, + 0x88, 0x99, 0x88, 0x99, 0x88, 0x99, 0x88, 0x99, + 0x50, 0x00, 0x9f, 0x36, + 0xb5, 0x59, 0xb5, 0x59, 0xb5, 0x59, 0xb5, 0x59, + 0x47, 0x85, 0x47, 0x85, 0x47, 0x85, 0x47, 0x85, + 0x47, 0x85, 0x47, 0x85, 0x47, 0x85, 0x47, 0x85, + 0x00, 0x00, 0x06, 0x2e, + 0x74, 0x45, 0x74, 0x45, 0x74, 0x45, 0x74, 0x45, + 0x06, 0x71, 0x06, 0x71, 0x06, 0x71, 0x06, 0x71, + 0x06, 0x71, 0x06, 0x71, 0x06, 0x71, 0x06, 0x71, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, + + /* 2437 MHz */ + 0x85, 0x09, + 0x10, 0x01, 0x67, 0x82, + 0x3a, 0x8a, 0x3a, 0x8a, 0x3a, 0x8a, 0x3a, 0x8a, + 0xcc, 0xb5, 0xcc, 0xb5, 0xcc, 0xb5, 0xcc, 0xb5, + 0xcc, 0xb5, 0xcc, 0xb5, 0xcc, 0xb5, 0xcc, 0xb5, + 0xf0, 0x00, 0x95, 0x6b, + 0x1f, 0x82, 0x1f, 0x82, 0x1f, 0x82, 0x1f, 0x82, + 0xb1, 0xad, 0xb1, 0xad, 0xb1, 0xad, 0xb1, 0xad, + 0xb1, 0xad, 0xb1, 0xad, 0xb1, 0xad, 0xb1, 0xad, + 0xd0, 0x00, 0x61, 0x5a, + 0x02, 0x7a, 0x02, 0x7a, 0x02, 0x7a, 0x02, 0x7a, + 0x94, 0xa5, 0x94, 0xa5, 0x94, 0xa5, 0x94, 0xa5, + 0x94, 0xa5, 0x94, 0xa5, 0x94, 0xa5, 0x94, 0xa5, + 0xa0, 0x00, 0x2c, 0x48, + 0xd8, 0x6d, 0xd8, 0x6d, 0xd8, 0x6d, 0xd8, 0x6d, + 0x6a, 0x99, 0x6a, 0x99, 0x6a, 0x99, 0x6a, 0x99, + 0x6a, 0x99, 0x6a, 0x99, 0x6a, 0x99, 0x6a, 0x99, + 0x50, 0x00, 0xb1, 0x36, + 0x92, 0x59, 0x92, 0x59, 0x92, 0x59, 0x92, 0x59, + 0x24, 0x85, 0x24, 0x85, 0x24, 0x85, 0x24, 0x85, + 0x24, 0x85, 0x24, 0x85, 0x24, 0x85, 0x24, 0x85, + 0x00, 0x00, 0x0f, 0x2e, + 0x4b, 0x45, 0x4b, 0x45, 0x4b, 0x45, 0x4b, 0x45, + 0xdd, 0x70, 0xdd, 0x70, 0xdd, 0x70, 0xdd, 0x70, + 0xdd, 0x70, 0xdd, 0x70, 0xdd, 0x70, 0xdd, 0x70, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, + + /* 2442 MHz */ + 0x8a, 0x09, + 0x10, 0x01, 0xf6, 0x81, + 0x24, 0x8a, 0x24, 0x8a, 0x24, 0x8a, 0x24, 0x8a, + 0xb6, 0xb5, 0xb6, 0xb5, 0xb6, 0xb5, 0xb6, 0xb5, + 0xb6, 0xb5, 0xb6, 0xb5, 0xb6, 0xb5, 0xb6, 0xb5, + 0xf0, 0x00, 0x62, 0x6b, + 0x06, 0x82, 0x06, 0x82, 0x06, 0x82, 0x06, 0x82, + 0x98, 0xad, 0x98, 0xad, 0x98, 0xad, 0x98, 0xad, + 0x98, 0xad, 0x98, 0xad, 0x98, 0xad, 0x98, 0xad, + 0xd0, 0x00, 0x52, 0x5a, + 0xe7, 0x79, 0xe7, 0x79, 0xe7, 0x79, 0xe7, 0x79, + 0x79, 0xa5, 0x79, 0xa5, 0x79, 0xa5, 0x79, 0xa5, + 0x79, 0xa5, 0x79, 0xa5, 0x79, 0xa5, 0x79, 0xa5, + 0xa0, 0x00, 0x37, 0x48, + 0xba, 0x6d, 0xba, 0x6d, 0xba, 0x6d, 0xba, 0x6d, + 0x4c, 0x99, 0x4c, 0x99, 0x4c, 0x99, 0x4c, 0x99, + 0x4c, 0x99, 0x4c, 0x99, 0x4c, 0x99, 0x4c, 0x99, + 0x50, 0x00, 0xc2, 0x36, + 0x6e, 0x59, 0x6e, 0x59, 0x6e, 0x59, 0x6e, 0x59, + 0x00, 0x85, 0x00, 0x85, 0x00, 0x85, 0x00, 0x85, + 0x00, 0x85, 0x00, 0x85, 0x00, 0x85, 0x00, 0x85, + 0x00, 0x00, 0x17, 0x2e, + 0x22, 0x45, 0x22, 0x45, 0x22, 0x45, 0x22, 0x45, + 0xb4, 0x70, 0xb4, 0x70, 0xb4, 0x70, 0xb4, 0x70, + 0xb4, 0x70, 0xb4, 0x70, 0xb4, 0x70, 0xb4, 0x70, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, + + /* 2447 MHz */ + 0x8f, 0x09, + 0x10, 0x01, 0x75, 0x83, + 0x61, 0x8a, 0x61, 0x8a, 0x61, 0x8a, 0x61, 0x8a, + 0xf3, 0xb5, 0xf3, 0xb5, 0xf3, 0xb5, 0xf3, 0xb5, + 0xf3, 0xb5, 0xf3, 0xb5, 0xf3, 0xb5, 0xf3, 0xb5, + 0xf0, 0x00, 0x4b, 0x6c, + 0x3f, 0x82, 0x3f, 0x82, 0x3f, 0x82, 0x3f, 0x82, + 0xd1, 0xad, 0xd1, 0xad, 0xd1, 0xad, 0xd1, 0xad, + 0xd1, 0xad, 0xd1, 0xad, 0xd1, 0xad, 0xd1, 0xad, + 0xd0, 0x00, 0xda, 0x5a, + 0x1c, 0x7a, 0x1c, 0x7a, 0x1c, 0x7a, 0x1c, 0x7a, + 0xae, 0xa5, 0xae, 0xa5, 0xae, 0xa5, 0xae, 0xa5, + 0xae, 0xa5, 0xae, 0xa5, 0xae, 0xa5, 0xae, 0xa5, + 0xa0, 0x00, 0x6d, 0x48, + 0xe9, 0x6d, 0xe9, 0x6d, 0xe9, 0x6d, 0xe9, 0x6d, + 0x7b, 0x99, 0x7b, 0x99, 0x7b, 0x99, 0x7b, 0x99, + 0x7b, 0x99, 0x7b, 0x99, 0x7b, 0x99, 0x7b, 0x99, + 0x50, 0x00, 0xc6, 0x36, + 0x92, 0x59, 0x92, 0x59, 0x92, 0x59, 0x92, 0x59, + 0x24, 0x85, 0x24, 0x85, 0x24, 0x85, 0x24, 0x85, + 0x24, 0x85, 0x24, 0x85, 0x24, 0x85, 0x24, 0x85, + 0x00, 0x00, 0x15, 0x2e, + 0x3c, 0x45, 0x3c, 0x45, 0x3c, 0x45, 0x3c, 0x45, + 0xce, 0x70, 0xce, 0x70, 0xce, 0x70, 0xce, 0x70, + 0xce, 0x70, 0xce, 0x70, 0xce, 0x70, 0xce, 0x70, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, + + /* 2452 MHz */ + 0x94, 0x09, + 0x10, 0x01, 0xf4, 0x84, + 0x9e, 0x8a, 0x9e, 0x8a, 0x9e, 0x8a, 0x9e, 0x8a, + 0x30, 0xb6, 0x30, 0xb6, 0x30, 0xb6, 0x30, 0xb6, + 0x30, 0xb6, 0x30, 0xb6, 0x30, 0xb6, 0x30, 0xb6, + 0xf0, 0x00, 0x34, 0x6d, + 0x77, 0x82, 0x77, 0x82, 0x77, 0x82, 0x77, 0x82, + 0x09, 0xae, 0x09, 0xae, 0x09, 0xae, 0x09, 0xae, + 0x09, 0xae, 0x09, 0xae, 0x09, 0xae, 0x09, 0xae, + 0xd0, 0x00, 0x62, 0x5b, + 0x50, 0x7a, 0x50, 0x7a, 0x50, 0x7a, 0x50, 0x7a, + 0xe2, 0xa5, 0xe2, 0xa5, 0xe2, 0xa5, 0xe2, 0xa5, + 0xe2, 0xa5, 0xe2, 0xa5, 0xe2, 0xa5, 0xe2, 0xa5, + 0xa0, 0x00, 0xa2, 0x48, + 0x17, 0x6e, 0x17, 0x6e, 0x17, 0x6e, 0x17, 0x6e, + 0xa9, 0x99, 0xa9, 0x99, 0xa9, 0x99, 0xa9, 0x99, + 0xa9, 0x99, 0xa9, 0x99, 0xa9, 0x99, 0xa9, 0x99, + 0x50, 0x00, 0xc9, 0x36, + 0xb7, 0x59, 0xb7, 0x59, 0xb7, 0x59, 0xb7, 0x59, + 0x49, 0x85, 0x49, 0x85, 0x49, 0x85, 0x49, 0x85, + 0x49, 0x85, 0x49, 0x85, 0x49, 0x85, 0x49, 0x85, + 0x00, 0x00, 0x12, 0x2e, + 0x57, 0x45, 0x57, 0x45, 0x57, 0x45, 0x57, 0x45, + 0xe9, 0x70, 0xe9, 0x70, 0xe9, 0x70, 0xe9, 0x70, + 0xe9, 0x70, 0xe9, 0x70, 0xe9, 0x70, 0xe9, 0x70, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, + + /* 2452 MHz */ + 0x99, 0x09, + 0x10, 0x01, 0x74, 0x86, + 0xdb, 0x8a, 0xdb, 0x8a, 0xdb, 0x8a, 0xdb, 0x8a, + 0x6d, 0xb6, 0x6d, 0xb6, 0x6d, 0xb6, 0x6d, 0xb6, + 0x6d, 0xb6, 0x6d, 0xb6, 0x6d, 0xb6, 0x6d, 0xb6, + 0xf0, 0x00, 0x1e, 0x6e, + 0xb0, 0x82, 0xb0, 0x82, 0xb0, 0x82, 0xb0, 0x82, + 0x42, 0xae, 0x42, 0xae, 0x42, 0xae, 0x42, 0xae, + 0x42, 0xae, 0x42, 0xae, 0x42, 0xae, 0x42, 0xae, + 0xd0, 0x00, 0xeb, 0x5b, + 0x85, 0x7a, 0x85, 0x7a, 0x85, 0x7a, 0x85, 0x7a, + 0x17, 0xa6, 0x17, 0xa6, 0x17, 0xa6, 0x17, 0xa6, + 0x17, 0xa6, 0x17, 0xa6, 0x17, 0xa6, 0x17, 0xa6, + 0xa0, 0x00, 0xd8, 0x48, + 0x46, 0x6e, 0x46, 0x6e, 0x46, 0x6e, 0x46, 0x6e, + 0xd8, 0x99, 0xd8, 0x99, 0xd8, 0x99, 0xd8, 0x99, + 0xd8, 0x99, 0xd8, 0x99, 0xd8, 0x99, 0xd8, 0x99, + 0x50, 0x00, 0xcd, 0x36, + 0xdb, 0x59, 0xdb, 0x59, 0xdb, 0x59, 0xdb, 0x59, + 0x6d, 0x85, 0x6d, 0x85, 0x6d, 0x85, 0x6d, 0x85, + 0x6d, 0x85, 0x6d, 0x85, 0x6d, 0x85, 0x6d, 0x85, + 0x00, 0x00, 0x10, 0x2e, + 0x71, 0x45, 0x71, 0x45, 0x71, 0x45, 0x71, 0x45, + 0x03, 0x71, 0x03, 0x71, 0x03, 0x71, 0x03, 0x71, + 0x03, 0x71, 0x03, 0x71, 0x03, 0x71, 0x03, 0x71, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, + + /* 2557 MHz */ + 0x9e, 0x09, + 0x10, 0x01, 0xf3, 0x87, + 0x17, 0x8b, 0x17, 0x8b, 0x17, 0x8b, 0x17, 0x8b, + 0xa9, 0xb6, 0xa9, 0xb6, 0xa9, 0xb6, 0xa9, 0xb6, + 0xa9, 0xb6, 0xa9, 0xb6, 0xa9, 0xb6, 0xa9, 0xb6, + 0xf0, 0x00, 0x07, 0x6f, + 0xe9, 0x82, 0xe9, 0x82, 0xe9, 0x82, 0xe9, 0x82, + 0x7b, 0xae, 0x7b, 0xae, 0x7b, 0xae, 0x7b, 0xae, + 0x7b, 0xae, 0x7b, 0xae, 0x7b, 0xae, 0x7b, 0xae, + 0xd0, 0x00, 0x73, 0x5c, + 0xba, 0x7a, 0xba, 0x7a, 0xba, 0x7a, 0xba, 0x7a, + 0x4c, 0xa6, 0x4c, 0xa6, 0x4c, 0xa6, 0x4c, 0xa6, + 0x4c, 0xa6, 0x4c, 0xa6, 0x4c, 0xa6, 0x4c, 0xa6, + 0xa0, 0x00, 0x0d, 0x49, + 0x74, 0x6e, 0x74, 0x6e, 0x74, 0x6e, 0x74, 0x6e, + 0x06, 0x9a, 0x06, 0x9a, 0x06, 0x9a, 0x06, 0x9a, + 0x06, 0x9a, 0x06, 0x9a, 0x06, 0x9a, 0x06, 0x9a, + 0x50, 0x00, 0xd1, 0x36, + 0xff, 0x59, 0xff, 0x59, 0xff, 0x59, 0xff, 0x59, + 0x91, 0x85, 0x91, 0x85, 0x91, 0x85, 0x91, 0x85, + 0x91, 0x85, 0x91, 0x85, 0x91, 0x85, 0x91, 0x85, + 0x00, 0x00, 0x0e, 0x2e, + 0x8b, 0x45, 0x8b, 0x45, 0x8b, 0x45, 0x8b, 0x45, + 0x1d, 0x71, 0x1d, 0x71, 0x1d, 0x71, 0x1d, 0x71, + 0x1d, 0x71, 0x1d, 0x71, 0x1d, 0x71, 0x1d, 0x71, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, + + /* 2562 MHz */ + 0xa3, 0x09, + 0x10, 0x01, 0x72, 0x89, + 0x54, 0x8b, 0x54, 0x8b, 0x54, 0x8b, 0x54, 0x8b, + 0xe6, 0xb6, 0xe6, 0xb6, 0xe6, 0xb6, 0xe6, 0xb6, + 0xe6, 0xb6, 0xe6, 0xb6, 0xe6, 0xb6, 0xe6, 0xb6, + 0xf0, 0x00, 0xf0, 0x6f, + 0x21, 0x83, 0x21, 0x83, 0x21, 0x83, 0x21, 0x83, + 0xb3, 0xae, 0xb3, 0xae, 0xb3, 0xae, 0xb3, 0xae, + 0xb3, 0xae, 0xb3, 0xae, 0xb3, 0xae, 0xb3, 0xae, + 0xd0, 0x00, 0xfb, 0x5c, + 0xee, 0x7a, 0xee, 0x7a, 0xee, 0x7a, 0xee, 0x7a, + 0x80, 0xa6, 0x80, 0xa6, 0x80, 0xa6, 0x80, 0xa6, + 0x80, 0xa6, 0x80, 0xa6, 0x80, 0xa6, 0x80, 0xa6, + 0xa0, 0x00, 0x43, 0x49, + 0xa3, 0x6e, 0xa3, 0x6e, 0xa3, 0x6e, 0xa3, 0x6e, + 0x35, 0x9a, 0x35, 0x9a, 0x35, 0x9a, 0x35, 0x9a, + 0x35, 0x9a, 0x35, 0x9a, 0x35, 0x9a, 0x35, 0x9a, + 0x50, 0x00, 0xd4, 0x36, + 0x24, 0x5a, 0x24, 0x5a, 0x24, 0x5a, 0x24, 0x5a, + 0xb6, 0x85, 0xb6, 0x85, 0xb6, 0x85, 0xb6, 0x85, + 0xb6, 0x85, 0xb6, 0x85, 0xb6, 0x85, 0xb6, 0x85, + 0x00, 0x00, 0x0b, 0x2e, + 0xa6, 0x45, 0xa6, 0x45, 0xa6, 0x45, 0xa6, 0x45, + 0x38, 0x71, 0x38, 0x71, 0x38, 0x71, 0x38, 0x71, + 0x38, 0x71, 0x38, 0x71, 0x38, 0x71, 0x38, 0x71, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, + + /* 2572 MHz */ + 0xa8, 0x09, + 0x10, 0x01, 0xf1, 0x8a, + 0x91, 0x8b, 0x91, 0x8b, 0x91, 0x8b, 0x91, 0x8b, + 0x23, 0xb7, 0x23, 0xb7, 0x23, 0xb7, 0x23, 0xb7, + 0x23, 0xb7, 0x23, 0xb7, 0x23, 0xb7, 0x23, 0xb7, + 0xf0, 0x00, 0xd9, 0x70, + 0x5a, 0x83, 0x5a, 0x83, 0x5a, 0x83, 0x5a, 0x83, + 0xec, 0xae, 0xec, 0xae, 0xec, 0xae, 0xec, 0xae, + 0xec, 0xae, 0xec, 0xae, 0xec, 0xae, 0xec, 0xae, + 0xd0, 0x00, 0x83, 0x5d, + 0x23, 0x7b, 0x23, 0x7b, 0x23, 0x7b, 0x23, 0x7b, + 0xb5, 0xa6, 0xb5, 0xa6, 0xb5, 0xa6, 0xb5, 0xa6, + 0xb5, 0xa6, 0xb5, 0xa6, 0xb5, 0xa6, 0xb5, 0xa6, + 0xa0, 0x00, 0x78, 0x49, + 0xd1, 0x6e, 0xd1, 0x6e, 0xd1, 0x6e, 0xd1, 0x6e, + 0x63, 0x9a, 0x63, 0x9a, 0x63, 0x9a, 0x63, 0x9a, + 0x63, 0x9a, 0x63, 0x9a, 0x63, 0x9a, 0x63, 0x9a, + 0x50, 0x00, 0xd8, 0x36, + 0x48, 0x5a, 0x48, 0x5a, 0x48, 0x5a, 0x48, 0x5a, + 0xda, 0x85, 0xda, 0x85, 0xda, 0x85, 0xda, 0x85, + 0xda, 0x85, 0xda, 0x85, 0xda, 0x85, 0xda, 0x85, + 0x00, 0x00, 0x09, 0x2e, + 0xc0, 0x45, 0xc0, 0x45, 0xc0, 0x45, 0xc0, 0x45, + 0x52, 0x71, 0x52, 0x71, 0x52, 0x71, 0x52, 0x71, + 0x52, 0x71, 0x52, 0x71, 0x52, 0x71, 0x52, 0x71, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x80, 0x80, 0x00, + +/* + * Not really sure if this is actually the power_limit database, + * it looks a bit "related" to PDR_PRISM_ZIF_TX_IQ_CALIBRATION + */ +/* struct pda_custom_wrapper */ +0xae, 0x00, 0xef, 0xbe, /* PDR_PRISM_PA_CAL_OUTPUT_POWER_LIMITS_CUSTOM */ + 0x0d, 0x00, 0x1a, 0x00, /* 13 entries, 26 bytes per entry */ + 0x00, 0x00, 0x52, 0x01, /* no offset, 338 bytes total */ + + /* 2412 MHz */ + 0x6c, 0x09, + 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, + 0xe0, 0x00, 0xe0, 0x00, 0xe0, 0x00, 0xe0, 0x00, + 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, + + /* 2417 MHz */ + 0x71, 0x09, + 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, + 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, + 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, + + /* 2422 MHz */ + 0x76, 0x09, + 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, + 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, + 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, + + /* 2427 MHz */ + 0x7b, 0x09, + 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, + 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, + 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, + + /* 2432 MHz */ + 0x80, 0x09, + 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, + 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, + 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, + + /* 2437 MHz */ + 0x85, 0x09, + 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, + 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, + 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, + + /* 2442 MHz */ + 0x8a, 0x09, + 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, + 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, + 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, + + /* 2447 MHz */ + 0x8f, 0x09, + 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, + 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, + 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, + + /* 2452 MHz */ + 0x94, 0x09, + 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, + 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, + 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, + + /* 2457 MHz */ + 0x99, 0x09, + 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, + 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, + 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, + + /* 2462 MHz */ + 0x9e, 0x09, + 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, + 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, + 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, + + /* 2467 MHz */ + 0xa3, 0x09, + 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, + 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, + 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, + + /* 2472 MHz */ + 0xa8, 0x09, + 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, + 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, + 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, 0xd0, 0x00, + +/* struct pda_iq_autocal_entry[13] */ +0x42, 0x00, 0x06, 0x19, /* PDR_PRISM_ZIF_TX_IQ_CALIBRATION */ + /* 2412 MHz */ + 0x6c, 0x09, 0x26, 0x00, 0xf8, 0xff, 0xf7, 0xff, 0xff, 0x00, + /* 2417 MHz */ + 0x71, 0x09, 0x26, 0x00, 0xf8, 0xff, 0xf7, 0xff, 0xff, 0x00, + /* 2422 MHz */ + 0x76, 0x09, 0x26, 0x00, 0xf8, 0xff, 0xf7, 0xff, 0xff, 0x00, + /* 2427 MHz */ + 0x7b, 0x09, 0x26, 0x00, 0xf8, 0xff, 0xf7, 0xff, 0xff, 0x00, + /* 2432 MHz */ + 0x80, 0x09, 0x25, 0x00, 0xf7, 0xff, 0xf7, 0xff, 0xff, 0x00, + /* 2437 MHz */ + 0x85, 0x09, 0x25, 0x00, 0xf7, 0xff, 0xf7, 0xff, 0xff, 0x00, + /* 2442 MHz */ + 0x8a, 0x09, 0x25, 0x00, 0xf7, 0xff, 0xf7, 0xff, 0xff, 0x00, + /* 2447 MHz */ + 0x8f, 0x09, 0x25, 0x00, 0xf7, 0xff, 0xf7, 0xff, 0xff, 0x00, + /* 2452 MHz */ + 0x94, 0x09, 0x25, 0x00, 0xf7, 0xff, 0xf7, 0xff, 0xff, 0x00, + /* 2457 MHz */ + 0x99, 0x09, 0x25, 0x00, 0xf5, 0xff, 0xf9, 0xff, 0x00, 0x01, + /* 2462 MHz */ + 0x9e, 0x09, 0x25, 0x00, 0xf5, 0xff, 0xf9, 0xff, 0x00, 0x01, + /* 2467 MHz */ + 0xa3, 0x09, 0x25, 0x00, 0xf5, 0xff, 0xf9, 0xff, 0x00, 0x01, + /* 2472 MHz */ + 0xa8, 0x09, 0x25, 0x00, 0xf5, 0xff, 0xf9, 0xff, 0x00, 0x01, + +0x02, 0x00, 0x00, 0x00, /* PDR_END */ + 0xb6, 0x04, +}; + +#endif /* P54SPI_EEPROM_H */ + diff --git a/drivers/net/wireless/p54/p54usb.c b/drivers/net/wireless/p54/p54usb.c new file mode 100644 index 0000000..09cec28 --- /dev/null +++ b/drivers/net/wireless/p54/p54usb.c @@ -0,0 +1,1149 @@ + +/* + * Linux device driver for USB based Prism54 + * + * Copyright (c) 2006, Michael Wu + * + * Based on the islsm (softmac prism54) driver, which is: + * Copyright 2004-2006 Jean-Baptiste Note , et al. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "p54.h" +#include "lmac.h" +#include "p54usb.h" + +MODULE_AUTHOR("Michael Wu "); +MODULE_DESCRIPTION("Prism54 USB wireless driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("prism54usb"); +MODULE_FIRMWARE("isl3886usb"); +MODULE_FIRMWARE("isl3887usb"); + +/* + * Note: + * + * Always update our wiki's device list (located at: + * http://wireless.kernel.org/en/users/Drivers/p54/devices ), + * whenever you add a new device. + */ + +static struct usb_device_id p54u_table[] = { + /* Version 1 devices (pci chip + net2280) */ + {USB_DEVICE(0x0411, 0x0050)}, /* Buffalo WLI2-USB2-G54 */ + {USB_DEVICE(0x045e, 0x00c2)}, /* Microsoft MN-710 */ + {USB_DEVICE(0x0506, 0x0a11)}, /* 3COM 3CRWE254G72 */ + {USB_DEVICE(0x0675, 0x0530)}, /* DrayTek Vigor 530 */ + {USB_DEVICE(0x06b9, 0x0120)}, /* Thomson SpeedTouch 120g */ + {USB_DEVICE(0x0707, 0xee06)}, /* SMC 2862W-G */ + {USB_DEVICE(0x07aa, 0x001c)}, /* Corega CG-WLUSB2GT */ + {USB_DEVICE(0x083a, 0x4501)}, /* Accton 802.11g WN4501 USB */ + {USB_DEVICE(0x083a, 0x4502)}, /* Siemens Gigaset USB Adapter */ + {USB_DEVICE(0x083a, 0x5501)}, /* Phillips CPWUA054 */ + {USB_DEVICE(0x0846, 0x4200)}, /* Netgear WG121 */ + {USB_DEVICE(0x0846, 0x4210)}, /* Netgear WG121 the second ? */ + {USB_DEVICE(0x0846, 0x4220)}, /* Netgear WG111 */ + {USB_DEVICE(0x09aa, 0x1000)}, /* Spinnaker Proto board */ + {USB_DEVICE(0x0bf8, 0x1007)}, /* Fujitsu E-5400 USB */ + {USB_DEVICE(0x0cde, 0x0006)}, /* Medion 40900, Roper Europe */ + {USB_DEVICE(0x0db0, 0x6826)}, /* MSI UB54G (MS-6826) */ + {USB_DEVICE(0x107b, 0x55f2)}, /* Gateway WGU-210 (Gemtek) */ + {USB_DEVICE(0x124a, 0x4023)}, /* Shuttle PN15, Airvast WM168g, IOGear GWU513 */ + {USB_DEVICE(0x1435, 0x0210)}, /* Inventel UR054G */ + {USB_DEVICE(0x15a9, 0x0002)}, /* Gemtek WUBI-100GW 802.11g */ + {USB_DEVICE(0x1630, 0x0005)}, /* 2Wire 802.11g USB (v1) / Z-Com */ + {USB_DEVICE(0x182d, 0x096b)}, /* Sitecom WL-107 */ + {USB_DEVICE(0x1915, 0x2234)}, /* Linksys WUSB54G OEM */ + {USB_DEVICE(0x1915, 0x2235)}, /* Linksys WUSB54G Portable OEM */ + {USB_DEVICE(0x2001, 0x3701)}, /* DLink DWL-G120 Spinnaker */ + {USB_DEVICE(0x2001, 0x3703)}, /* DLink DWL-G122 */ + {USB_DEVICE(0x2001, 0x3762)}, /* Conceptronic C54U */ + {USB_DEVICE(0x5041, 0x2234)}, /* Linksys WUSB54G */ + {USB_DEVICE(0x5041, 0x2235)}, /* Linksys WUSB54G Portable */ + + /* Version 2 devices (3887) */ + {USB_DEVICE(0x0471, 0x1230)}, /* Philips CPWUA054/00 */ + {USB_DEVICE(0x050d, 0x7050)}, /* Belkin F5D7050 ver 1000 */ + {USB_DEVICE(0x0572, 0x2000)}, /* Cohiba Proto board */ + {USB_DEVICE(0x0572, 0x2002)}, /* Cohiba Proto board */ + {USB_DEVICE(0x06a9, 0x000e)}, /* Westell 802.11g USB (A90-211WG-01) */ + {USB_DEVICE(0x06b9, 0x0121)}, /* Thomson SpeedTouch 121g */ + {USB_DEVICE(0x0707, 0xee13)}, /* SMC 2862W-G version 2 */ + {USB_DEVICE(0x07aa, 0x0020)}, /* Corega WLUSB2GTST USB */ + {USB_DEVICE(0x0803, 0x4310)}, /* Zoom 4410a */ + {USB_DEVICE(0x083a, 0x4521)}, /* Siemens Gigaset USB Adapter 54 version 2 */ + {USB_DEVICE(0x083a, 0x4531)}, /* T-Com Sinus 154 data II */ + {USB_DEVICE(0x083a, 0xc501)}, /* Zoom Wireless-G 4410 */ + {USB_DEVICE(0x083a, 0xf503)}, /* Accton FD7050E ver 1010ec */ + {USB_DEVICE(0x0846, 0x4240)}, /* Netgear WG111 (v2) */ + {USB_DEVICE(0x0915, 0x2000)}, /* Cohiba Proto board */ + {USB_DEVICE(0x0915, 0x2002)}, /* Cohiba Proto board */ + {USB_DEVICE(0x0baf, 0x0118)}, /* U.S. Robotics U5 802.11g Adapter*/ + {USB_DEVICE(0x0bf8, 0x1009)}, /* FUJITSU E-5400 USB D1700*/ + /* {USB_DEVICE(0x0cde, 0x0006)}, * Medion MD40900 already listed above, + * just noting it here for clarity */ + {USB_DEVICE(0x0cde, 0x0008)}, /* Sagem XG703A */ + {USB_DEVICE(0x0cde, 0x0015)}, /* Zcomax XG-705A */ + {USB_DEVICE(0x0d8e, 0x3762)}, /* DLink DWL-G120 Cohiba */ + {USB_DEVICE(0x124a, 0x4025)}, /* IOGear GWU513 (GW3887IK chip) */ + {USB_DEVICE(0x1260, 0xee22)}, /* SMC 2862W-G version 2 */ + {USB_DEVICE(0x13b1, 0x000a)}, /* Linksys WUSB54G ver 2 */ + {USB_DEVICE(0x13B1, 0x000C)}, /* Linksys WUSB54AG */ + {USB_DEVICE(0x1413, 0x5400)}, /* Telsey 802.11g USB2.0 Adapter */ + {USB_DEVICE(0x1435, 0x0427)}, /* Inventel UR054G */ + /* {USB_DEVICE(0x15a9, 0x0002)}, * Also SparkLAN WL-682 with 3887 */ + {USB_DEVICE(0x1668, 0x1050)}, /* Actiontec 802UIG-1 */ + {USB_DEVICE(0x1740, 0x1000)}, /* Senao NUB-350 */ + {USB_DEVICE(0x2001, 0x3704)}, /* DLink DWL-G122 rev A2 */ + {USB_DEVICE(0x2001, 0x3705)}, /* D-Link DWL-G120 rev C1 */ + {USB_DEVICE(0x413c, 0x5513)}, /* Dell WLA3310 USB Wireless Adapter */ + {USB_DEVICE(0x413c, 0x8102)}, /* Spinnaker DUT */ + {USB_DEVICE(0x413c, 0x8104)}, /* Cohiba Proto board */ + {} +}; + +MODULE_DEVICE_TABLE(usb, p54u_table); + +static const struct { + u32 intf; + enum p54u_hw_type type; + const char *fw; + char hw[20]; +} p54u_fwlist[__NUM_P54U_HWTYPES] = { + { + .type = P54U_NET2280, + .intf = FW_LM86, + .fw = "isl3886usb", + .hw = "ISL3886 + net2280", + }, + { + .type = P54U_3887, + .intf = FW_LM87, + .fw = "isl3887usb", + .hw = "ISL3887", + }, +}; + +static void p54u_rx_cb(struct urb *urb) +{ + struct sk_buff *skb = (struct sk_buff *) urb->context; + struct p54u_rx_info *info = (struct p54u_rx_info *)skb->cb; + struct ieee80211_hw *dev = info->dev; + struct p54u_priv *priv = dev->priv; + + skb_unlink(skb, &priv->rx_queue); + + if (unlikely(urb->status)) { + dev_kfree_skb_irq(skb); + return; + } + + skb_put(skb, urb->actual_length); + + if (priv->hw_type == P54U_NET2280) + skb_pull(skb, priv->common.tx_hdr_len); + if (priv->common.fw_interface == FW_LM87) { + skb_pull(skb, 4); + skb_put(skb, 4); + } + + if (p54_rx(dev, skb)) { + skb = dev_alloc_skb(priv->common.rx_mtu + 32); + if (unlikely(!skb)) { + /* TODO check rx queue length and refill *somewhere* */ + return; + } + + info = (struct p54u_rx_info *) skb->cb; + info->urb = urb; + info->dev = dev; + urb->transfer_buffer = skb_tail_pointer(skb); + urb->context = skb; + } else { + if (priv->hw_type == P54U_NET2280) + skb_push(skb, priv->common.tx_hdr_len); + if (priv->common.fw_interface == FW_LM87) { + skb_push(skb, 4); + skb_put(skb, 4); + } + skb_reset_tail_pointer(skb); + skb_trim(skb, 0); + urb->transfer_buffer = skb_tail_pointer(skb); + } + skb_queue_tail(&priv->rx_queue, skb); + usb_anchor_urb(urb, &priv->submitted); + if (usb_submit_urb(urb, GFP_ATOMIC)) { + skb_unlink(skb, &priv->rx_queue); + usb_unanchor_urb(urb); + dev_kfree_skb_irq(skb); + } +} + +static void p54u_tx_cb(struct urb *urb) +{ + struct sk_buff *skb = urb->context; + struct ieee80211_hw *dev = + usb_get_intfdata(usb_ifnum_to_if(urb->dev, 0)); + + p54_free_skb(dev, skb); +} + +static void p54u_tx_dummy_cb(struct urb *urb) { } + +static void p54u_free_urbs(struct ieee80211_hw *dev) +{ + struct p54u_priv *priv = dev->priv; + usb_kill_anchored_urbs(&priv->submitted); +} + +static void p54u_stop(struct ieee80211_hw *dev) +{ + /* + * TODO: figure out how to reliably stop the 3887 and net2280 so + * the hardware is still usable next time we want to start it. + * until then, we just stop listening to the hardware.. + */ + p54u_free_urbs(dev); +} + +static int p54u_init_urbs(struct ieee80211_hw *dev) +{ + struct p54u_priv *priv = dev->priv; + struct urb *entry = NULL; + struct sk_buff *skb; + struct p54u_rx_info *info; + int ret = 0; + + while (skb_queue_len(&priv->rx_queue) < 32) { + skb = __dev_alloc_skb(priv->common.rx_mtu + 32, GFP_KERNEL); + if (!skb) { + ret = -ENOMEM; + goto err; + } + entry = usb_alloc_urb(0, GFP_KERNEL); + if (!entry) { + ret = -ENOMEM; + goto err; + } + + usb_fill_bulk_urb(entry, priv->udev, + usb_rcvbulkpipe(priv->udev, P54U_PIPE_DATA), + skb_tail_pointer(skb), + priv->common.rx_mtu + 32, p54u_rx_cb, skb); + info = (struct p54u_rx_info *) skb->cb; + info->urb = entry; + info->dev = dev; + skb_queue_tail(&priv->rx_queue, skb); + + usb_anchor_urb(entry, &priv->submitted); + ret = usb_submit_urb(entry, GFP_KERNEL); + if (ret) { + skb_unlink(skb, &priv->rx_queue); + usb_unanchor_urb(entry); + goto err; + } + usb_free_urb(entry); + entry = NULL; + } + + return 0; + + err: + usb_free_urb(entry); + kfree_skb(skb); + p54u_free_urbs(dev); + return ret; +} + +static int p54u_open(struct ieee80211_hw *dev) +{ + /* + * TODO: Because we don't know how to reliably stop the 3887 and + * the isl3886+net2280, other than brutally cut off all + * communications. We have to reinitialize the urbs on every start. + */ + return p54u_init_urbs(dev); +} + +static __le32 p54u_lm87_chksum(const __le32 *data, size_t length) +{ + u32 chk = 0; + + length >>= 2; + while (length--) { + chk ^= le32_to_cpu(*data++); + chk = (chk >> 5) ^ (chk << 3); + } + + return cpu_to_le32(chk); +} + +static void p54u_tx_lm87(struct ieee80211_hw *dev, struct sk_buff *skb) +{ + struct p54u_priv *priv = dev->priv; + struct urb *data_urb; + struct lm87_tx_hdr *hdr = (void *)skb->data - sizeof(*hdr); + + data_urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!data_urb) { + p54_free_skb(dev, skb); + return; + } + + hdr->chksum = p54u_lm87_chksum((__le32 *)skb->data, skb->len); + hdr->device_addr = ((struct p54_hdr *)skb->data)->req_id; + + usb_fill_bulk_urb(data_urb, priv->udev, + usb_sndbulkpipe(priv->udev, P54U_PIPE_DATA), + hdr, skb->len + sizeof(*hdr), FREE_AFTER_TX(skb) ? + p54u_tx_cb : p54u_tx_dummy_cb, skb); + data_urb->transfer_flags |= URB_ZERO_PACKET; + + usb_anchor_urb(data_urb, &priv->submitted); + if (usb_submit_urb(data_urb, GFP_ATOMIC)) { + usb_unanchor_urb(data_urb); + p54_free_skb(dev, skb); + } + usb_free_urb(data_urb); +} + +static void p54u_tx_net2280(struct ieee80211_hw *dev, struct sk_buff *skb) +{ + struct p54u_priv *priv = dev->priv; + struct urb *int_urb = NULL, *data_urb = NULL; + struct net2280_tx_hdr *hdr = (void *)skb->data - sizeof(*hdr); + struct net2280_reg_write *reg = NULL; + int err = -ENOMEM; + + reg = kmalloc(sizeof(*reg), GFP_ATOMIC); + if (!reg) + goto out; + + int_urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!int_urb) + goto out; + + data_urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!data_urb) + goto out; + + reg->port = cpu_to_le16(NET2280_DEV_U32); + reg->addr = cpu_to_le32(P54U_DEV_BASE); + reg->val = cpu_to_le32(ISL38XX_DEV_INT_DATA); + + memset(hdr, 0, sizeof(*hdr)); + hdr->len = cpu_to_le16(skb->len); + hdr->device_addr = ((struct p54_hdr *) skb->data)->req_id; + + usb_fill_bulk_urb(int_urb, priv->udev, + usb_sndbulkpipe(priv->udev, P54U_PIPE_DEV), reg, sizeof(*reg), + p54u_tx_dummy_cb, dev); + + /* + * URB_FREE_BUFFER triggers a code path in the USB subsystem that will + * free what is inside the transfer_buffer after the last reference to + * the int_urb is dropped. + */ + int_urb->transfer_flags |= URB_FREE_BUFFER | URB_ZERO_PACKET; + reg = NULL; + + usb_fill_bulk_urb(data_urb, priv->udev, + usb_sndbulkpipe(priv->udev, P54U_PIPE_DATA), + hdr, skb->len + sizeof(*hdr), FREE_AFTER_TX(skb) ? + p54u_tx_cb : p54u_tx_dummy_cb, skb); + data_urb->transfer_flags |= URB_ZERO_PACKET; + + usb_anchor_urb(int_urb, &priv->submitted); + err = usb_submit_urb(int_urb, GFP_ATOMIC); + if (err) { + usb_unanchor_urb(int_urb); + goto out; + } + + usb_anchor_urb(data_urb, &priv->submitted); + err = usb_submit_urb(data_urb, GFP_ATOMIC); + if (err) { + usb_unanchor_urb(data_urb); + goto out; + } +out: + usb_free_urb(int_urb); + usb_free_urb(data_urb); + + if (err) { + kfree(reg); + p54_free_skb(dev, skb); + } +} + +static int p54u_write(struct p54u_priv *priv, + struct net2280_reg_write *buf, + enum net2280_op_type type, + __le32 addr, __le32 val) +{ + unsigned int ep; + int alen; + + if (type & 0x0800) + ep = usb_sndbulkpipe(priv->udev, P54U_PIPE_DEV); + else + ep = usb_sndbulkpipe(priv->udev, P54U_PIPE_BRG); + + buf->port = cpu_to_le16(type); + buf->addr = addr; + buf->val = val; + + return usb_bulk_msg(priv->udev, ep, buf, sizeof(*buf), &alen, 1000); +} + +static int p54u_read(struct p54u_priv *priv, void *buf, + enum net2280_op_type type, + __le32 addr, __le32 *val) +{ + struct net2280_reg_read *read = buf; + __le32 *reg = buf; + unsigned int ep; + int alen, err; + + if (type & 0x0800) + ep = P54U_PIPE_DEV; + else + ep = P54U_PIPE_BRG; + + read->port = cpu_to_le16(type); + read->addr = addr; + + err = usb_bulk_msg(priv->udev, usb_sndbulkpipe(priv->udev, ep), + read, sizeof(*read), &alen, 1000); + if (err) + return err; + + err = usb_bulk_msg(priv->udev, usb_rcvbulkpipe(priv->udev, ep), + reg, sizeof(*reg), &alen, 1000); + if (err) + return err; + + *val = *reg; + return 0; +} + +static int p54u_bulk_msg(struct p54u_priv *priv, unsigned int ep, + void *data, size_t len) +{ + int alen; + return usb_bulk_msg(priv->udev, usb_sndbulkpipe(priv->udev, ep), + data, len, &alen, 2000); +} + +static int p54u_device_reset(struct ieee80211_hw *dev) +{ + struct p54u_priv *priv = dev->priv; + int ret, lock = (priv->intf->condition != USB_INTERFACE_BINDING); + + if (lock) { + ret = usb_lock_device_for_reset(priv->udev, priv->intf); + if (ret < 0) { + dev_err(&priv->udev->dev, "(p54usb) unable to lock " + "device for reset (%d)!\n", ret); + return ret; + } + } + + ret = usb_reset_device(priv->udev); + if (lock) + usb_unlock_device(priv->udev); + + if (ret) + dev_err(&priv->udev->dev, "(p54usb) unable to reset " + "device (%d)!\n", ret); + + return ret; +} + +static const char p54u_romboot_3887[] = "~~~~"; +static int p54u_firmware_reset_3887(struct ieee80211_hw *dev) +{ + struct p54u_priv *priv = dev->priv; + u8 *buf; + int ret; + + buf = kmemdup(p54u_romboot_3887, 4, GFP_KERNEL); + if (!buf) + return -ENOMEM; + ret = p54u_bulk_msg(priv, P54U_PIPE_DATA, + buf, 4); + kfree(buf); + if (ret) + dev_err(&priv->udev->dev, "(p54usb) unable to jump to " + "boot ROM (%d)!\n", ret); + + return ret; +} + +static const char p54u_firmware_upload_3887[] = "<\r"; +static int p54u_upload_firmware_3887(struct ieee80211_hw *dev) +{ + struct p54u_priv *priv = dev->priv; + int err, alen; + u8 carry = 0; + u8 *buf, *tmp; + const u8 *data; + unsigned int left, remains, block_size; + struct x2_header *hdr; + unsigned long timeout; + + err = p54u_firmware_reset_3887(dev); + if (err) + return err; + + tmp = buf = kmalloc(P54U_FW_BLOCK, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + left = block_size = min((size_t)P54U_FW_BLOCK, priv->fw->size); + strcpy(buf, p54u_firmware_upload_3887); + left -= strlen(p54u_firmware_upload_3887); + tmp += strlen(p54u_firmware_upload_3887); + + data = priv->fw->data; + remains = priv->fw->size; + + hdr = (struct x2_header *)(buf + strlen(p54u_firmware_upload_3887)); + memcpy(hdr->signature, X2_SIGNATURE, X2_SIGNATURE_SIZE); + hdr->fw_load_addr = cpu_to_le32(ISL38XX_DEV_FIRMWARE_ADDR); + hdr->fw_length = cpu_to_le32(priv->fw->size); + hdr->crc = cpu_to_le32(~crc32_le(~0, (void *)&hdr->fw_load_addr, + sizeof(u32)*2)); + left -= sizeof(*hdr); + tmp += sizeof(*hdr); + + while (remains) { + while (left--) { + if (carry) { + *tmp++ = carry; + carry = 0; + remains--; + continue; + } + switch (*data) { + case '~': + *tmp++ = '}'; + carry = '^'; + break; + case '}': + *tmp++ = '}'; + carry = ']'; + break; + default: + *tmp++ = *data; + remains--; + break; + } + data++; + } + + err = p54u_bulk_msg(priv, P54U_PIPE_DATA, buf, block_size); + if (err) { + dev_err(&priv->udev->dev, "(p54usb) firmware " + "upload failed!\n"); + goto err_upload_failed; + } + + tmp = buf; + left = block_size = min((unsigned int)P54U_FW_BLOCK, remains); + } + + *((__le32 *)buf) = cpu_to_le32(~crc32_le(~0, priv->fw->data, + priv->fw->size)); + err = p54u_bulk_msg(priv, P54U_PIPE_DATA, buf, sizeof(u32)); + if (err) { + dev_err(&priv->udev->dev, "(p54usb) firmware upload failed!\n"); + goto err_upload_failed; + } + timeout = jiffies + msecs_to_jiffies(1000); + while (!(err = usb_bulk_msg(priv->udev, + usb_rcvbulkpipe(priv->udev, P54U_PIPE_DATA), buf, 128, &alen, 1000))) { + if (alen > 2 && !memcmp(buf, "OK", 2)) + break; + + if (alen > 5 && !memcmp(buf, "ERROR", 5)) { + err = -EINVAL; + break; + } + + if (time_after(jiffies, timeout)) { + dev_err(&priv->udev->dev, "(p54usb) firmware boot " + "timed out!\n"); + err = -ETIMEDOUT; + break; + } + } + if (err) { + dev_err(&priv->udev->dev, "(p54usb) firmware upload failed!\n"); + goto err_upload_failed; + } + + buf[0] = 'g'; + buf[1] = '\r'; + err = p54u_bulk_msg(priv, P54U_PIPE_DATA, buf, 2); + if (err) { + dev_err(&priv->udev->dev, "(p54usb) firmware boot failed!\n"); + goto err_upload_failed; + } + + timeout = jiffies + msecs_to_jiffies(1000); + while (!(err = usb_bulk_msg(priv->udev, + usb_rcvbulkpipe(priv->udev, P54U_PIPE_DATA), buf, 128, &alen, 1000))) { + if (alen > 0 && buf[0] == 'g') + break; + + if (time_after(jiffies, timeout)) { + err = -ETIMEDOUT; + break; + } + } + if (err) + goto err_upload_failed; + +err_upload_failed: + kfree(buf); + return err; +} + +static int p54u_upload_firmware_net2280(struct ieee80211_hw *dev) +{ + struct p54u_priv *priv = dev->priv; + const struct p54p_csr *devreg = (const struct p54p_csr *) P54U_DEV_BASE; + int err, alen; + void *buf; + __le32 reg; + unsigned int remains, offset; + const u8 *data; + + buf = kmalloc(512, GFP_KERNEL); + if (!buf) + return -ENOMEM; + +#define P54U_WRITE(type, addr, data) \ + do {\ + err = p54u_write(priv, buf, type,\ + cpu_to_le32((u32)(unsigned long)addr), data);\ + if (err) \ + goto fail;\ + } while (0) + +#define P54U_READ(type, addr) \ + do {\ + err = p54u_read(priv, buf, type,\ + cpu_to_le32((u32)(unsigned long)addr), ®);\ + if (err)\ + goto fail;\ + } while (0) + + /* power down net2280 bridge */ + P54U_READ(NET2280_BRG_U32, NET2280_GPIOCTL); + reg |= cpu_to_le32(P54U_BRG_POWER_DOWN); + reg &= cpu_to_le32(~P54U_BRG_POWER_UP); + P54U_WRITE(NET2280_BRG_U32, NET2280_GPIOCTL, reg); + + mdelay(100); + + /* power up bridge */ + reg |= cpu_to_le32(P54U_BRG_POWER_UP); + reg &= cpu_to_le32(~P54U_BRG_POWER_DOWN); + P54U_WRITE(NET2280_BRG_U32, NET2280_GPIOCTL, reg); + + mdelay(100); + + P54U_WRITE(NET2280_BRG_U32, NET2280_DEVINIT, + cpu_to_le32(NET2280_CLK_30Mhz | + NET2280_PCI_ENABLE | + NET2280_PCI_SOFT_RESET)); + + mdelay(20); + + P54U_WRITE(NET2280_BRG_CFG_U16, PCI_COMMAND, + cpu_to_le32(PCI_COMMAND_MEMORY | + PCI_COMMAND_MASTER)); + + P54U_WRITE(NET2280_BRG_CFG_U32, PCI_BASE_ADDRESS_0, + cpu_to_le32(NET2280_BASE)); + + P54U_READ(NET2280_BRG_CFG_U16, PCI_STATUS); + reg |= cpu_to_le32(PCI_STATUS_REC_MASTER_ABORT); + P54U_WRITE(NET2280_BRG_CFG_U16, PCI_STATUS, reg); + + // TODO: we really need this? + P54U_READ(NET2280_BRG_U32, NET2280_RELNUM); + + P54U_WRITE(NET2280_BRG_U32, NET2280_EPA_RSP, + cpu_to_le32(NET2280_CLEAR_NAK_OUT_PACKETS_MODE)); + P54U_WRITE(NET2280_BRG_U32, NET2280_EPC_RSP, + cpu_to_le32(NET2280_CLEAR_NAK_OUT_PACKETS_MODE)); + + P54U_WRITE(NET2280_BRG_CFG_U32, PCI_BASE_ADDRESS_2, + cpu_to_le32(NET2280_BASE2)); + + /* finally done setting up the bridge */ + + P54U_WRITE(NET2280_DEV_CFG_U16, 0x10000 | PCI_COMMAND, + cpu_to_le32(PCI_COMMAND_MEMORY | + PCI_COMMAND_MASTER)); + + P54U_WRITE(NET2280_DEV_CFG_U16, 0x10000 | 0x40 /* TRDY timeout */, 0); + P54U_WRITE(NET2280_DEV_CFG_U32, 0x10000 | PCI_BASE_ADDRESS_0, + cpu_to_le32(P54U_DEV_BASE)); + + P54U_WRITE(NET2280_BRG_U32, NET2280_USBIRQENB1, 0); + P54U_WRITE(NET2280_BRG_U32, NET2280_IRQSTAT1, + cpu_to_le32(NET2280_PCI_INTA_INTERRUPT)); + + /* do romboot */ + P54U_WRITE(NET2280_DEV_U32, &devreg->int_enable, 0); + + P54U_READ(NET2280_DEV_U32, &devreg->ctrl_stat); + reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RESET); + reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RAMBOOT); + reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_CLKRUN); + P54U_WRITE(NET2280_DEV_U32, &devreg->ctrl_stat, reg); + + mdelay(20); + + reg |= cpu_to_le32(ISL38XX_CTRL_STAT_RESET); + P54U_WRITE(NET2280_DEV_U32, &devreg->ctrl_stat, reg); + + mdelay(20); + + reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RESET); + P54U_WRITE(NET2280_DEV_U32, &devreg->ctrl_stat, reg); + + mdelay(100); + + P54U_READ(NET2280_DEV_U32, &devreg->int_ident); + P54U_WRITE(NET2280_DEV_U32, &devreg->int_ack, reg); + + /* finally, we can upload firmware now! */ + remains = priv->fw->size; + data = priv->fw->data; + offset = ISL38XX_DEV_FIRMWARE_ADDR; + + while (remains) { + unsigned int block_len = min(remains, (unsigned int)512); + memcpy(buf, data, block_len); + + err = p54u_bulk_msg(priv, P54U_PIPE_DATA, buf, block_len); + if (err) { + dev_err(&priv->udev->dev, "(p54usb) firmware block " + "upload failed\n"); + goto fail; + } + + P54U_WRITE(NET2280_DEV_U32, &devreg->direct_mem_base, + cpu_to_le32(0xc0000f00)); + + P54U_WRITE(NET2280_DEV_U32, + 0x0020 | (unsigned long)&devreg->direct_mem_win, 0); + P54U_WRITE(NET2280_DEV_U32, + 0x0020 | (unsigned long)&devreg->direct_mem_win, + cpu_to_le32(1)); + + P54U_WRITE(NET2280_DEV_U32, + 0x0024 | (unsigned long)&devreg->direct_mem_win, + cpu_to_le32(block_len)); + P54U_WRITE(NET2280_DEV_U32, + 0x0028 | (unsigned long)&devreg->direct_mem_win, + cpu_to_le32(offset)); + + P54U_WRITE(NET2280_DEV_U32, &devreg->dma_addr, + cpu_to_le32(NET2280_EPA_FIFO_PCI_ADDR)); + P54U_WRITE(NET2280_DEV_U32, &devreg->dma_len, + cpu_to_le32(block_len >> 2)); + P54U_WRITE(NET2280_DEV_U32, &devreg->dma_ctrl, + cpu_to_le32(ISL38XX_DMA_MASTER_CONTROL_TRIGGER)); + + mdelay(10); + + P54U_READ(NET2280_DEV_U32, + 0x002C | (unsigned long)&devreg->direct_mem_win); + if (!(reg & cpu_to_le32(ISL38XX_DMA_STATUS_DONE)) || + !(reg & cpu_to_le32(ISL38XX_DMA_STATUS_READY))) { + dev_err(&priv->udev->dev, "(p54usb) firmware DMA " + "transfer failed\n"); + goto fail; + } + + P54U_WRITE(NET2280_BRG_U32, NET2280_EPA_STAT, + cpu_to_le32(NET2280_FIFO_FLUSH)); + + remains -= block_len; + data += block_len; + offset += block_len; + } + + /* do ramboot */ + P54U_READ(NET2280_DEV_U32, &devreg->ctrl_stat); + reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RESET); + reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_CLKRUN); + reg |= cpu_to_le32(ISL38XX_CTRL_STAT_RAMBOOT); + P54U_WRITE(NET2280_DEV_U32, &devreg->ctrl_stat, reg); + + mdelay(20); + + reg |= cpu_to_le32(ISL38XX_CTRL_STAT_RESET); + P54U_WRITE(NET2280_DEV_U32, &devreg->ctrl_stat, reg); + + reg &= cpu_to_le32(~ISL38XX_CTRL_STAT_RESET); + P54U_WRITE(NET2280_DEV_U32, &devreg->ctrl_stat, reg); + + mdelay(100); + + P54U_READ(NET2280_DEV_U32, &devreg->int_ident); + P54U_WRITE(NET2280_DEV_U32, &devreg->int_ack, reg); + + /* start up the firmware */ + P54U_WRITE(NET2280_DEV_U32, &devreg->int_enable, + cpu_to_le32(ISL38XX_INT_IDENT_INIT)); + + P54U_WRITE(NET2280_BRG_U32, NET2280_IRQSTAT1, + cpu_to_le32(NET2280_PCI_INTA_INTERRUPT)); + + P54U_WRITE(NET2280_BRG_U32, NET2280_USBIRQENB1, + cpu_to_le32(NET2280_PCI_INTA_INTERRUPT_ENABLE | + NET2280_USB_INTERRUPT_ENABLE)); + + P54U_WRITE(NET2280_DEV_U32, &devreg->dev_int, + cpu_to_le32(ISL38XX_DEV_INT_RESET)); + + err = usb_interrupt_msg(priv->udev, + usb_rcvbulkpipe(priv->udev, P54U_PIPE_INT), + buf, sizeof(__le32), &alen, 1000); + if (err || alen != sizeof(__le32)) + goto fail; + + P54U_READ(NET2280_DEV_U32, &devreg->int_ident); + P54U_WRITE(NET2280_DEV_U32, &devreg->int_ack, reg); + + if (!(reg & cpu_to_le32(ISL38XX_INT_IDENT_INIT))) + err = -EINVAL; + + P54U_WRITE(NET2280_BRG_U32, NET2280_USBIRQENB1, 0); + P54U_WRITE(NET2280_BRG_U32, NET2280_IRQSTAT1, + cpu_to_le32(NET2280_PCI_INTA_INTERRUPT)); + +#undef P54U_WRITE +#undef P54U_READ + +fail: + kfree(buf); + return err; +} + +static int p54_find_type(struct p54u_priv *priv) +{ + int i; + + for (i = 0; i < __NUM_P54U_HWTYPES; i++) + if (p54u_fwlist[i].type == priv->hw_type) + break; + if (i == __NUM_P54U_HWTYPES) + return -EOPNOTSUPP; + + return i; +} + +static int p54u_start_ops(struct p54u_priv *priv) +{ + struct ieee80211_hw *dev = priv->common.hw; + int ret; + + ret = p54_parse_firmware(dev, priv->fw); + if (ret) + goto err_out; + + ret = p54_find_type(priv); + if (ret < 0) + goto err_out; + + if (priv->common.fw_interface != p54u_fwlist[ret].intf) { + dev_err(&priv->udev->dev, "wrong firmware, please get " + "a firmware for \"%s\" and try again.\n", + p54u_fwlist[ret].hw); + ret = -ENODEV; + goto err_out; + } + + ret = priv->upload_fw(dev); + if (ret) + goto err_out; + + ret = p54u_open(dev); + if (ret) + goto err_out; + + ret = p54_read_eeprom(dev); + if (ret) + goto err_stop; + + p54u_stop(dev); + + ret = p54_register_common(dev, &priv->udev->dev); + if (ret) + goto err_stop; + + return 0; + +err_stop: + p54u_stop(dev); + +err_out: + /* + * p54u_disconnect will do the rest of the + * cleanup + */ + return ret; +} + +static void p54u_load_firmware_cb(const struct firmware *firmware, + void *context) +{ + struct p54u_priv *priv = context; + struct usb_device *udev = priv->udev; + int err; + + complete(&priv->fw_wait_load); + if (firmware) { + priv->fw = firmware; + err = p54u_start_ops(priv); + } else { + err = -ENOENT; + dev_err(&udev->dev, "Firmware not found.\n"); + } + + if (err) { + struct device *parent = priv->udev->dev.parent; + + dev_err(&udev->dev, "failed to initialize device (%d)\n", err); + + if (parent) + device_lock(parent); + + device_release_driver(&udev->dev); + /* + * At this point p54u_disconnect has already freed + * the "priv" context. Do not use it anymore! + */ + priv = NULL; + + if (parent) + device_unlock(parent); + } + + usb_put_dev(udev); +} + +static int p54u_load_firmware(struct ieee80211_hw *dev, + struct usb_interface *intf) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct p54u_priv *priv = dev->priv; + struct device *device = &udev->dev; + int err, i; + + BUILD_BUG_ON(ARRAY_SIZE(p54u_fwlist) != __NUM_P54U_HWTYPES); + + init_completion(&priv->fw_wait_load); + i = p54_find_type(priv); + if (i < 0) + return i; + + dev_info(&priv->udev->dev, "Loading firmware file %s\n", + p54u_fwlist[i].fw); + + usb_get_dev(udev); + err = request_firmware_nowait(THIS_MODULE, 1, p54u_fwlist[i].fw, + device, GFP_KERNEL, priv, + p54u_load_firmware_cb); + if (err) { + dev_err(&priv->udev->dev, "(p54usb) cannot load firmware %s " + "(%d)!\n", p54u_fwlist[i].fw, err); + } + + return err; +} + +static int p54u_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct ieee80211_hw *dev; + struct p54u_priv *priv; + int err; + unsigned int i, recognized_pipes; + + dev = p54_init_common(sizeof(*priv)); + + if (!dev) { + dev_err(&udev->dev, "(p54usb) ieee80211 alloc failed\n"); + return -ENOMEM; + } + + priv = dev->priv; + priv->hw_type = P54U_INVALID_HW; + + SET_IEEE80211_DEV(dev, &intf->dev); + usb_set_intfdata(intf, dev); + priv->udev = udev; + priv->intf = intf; + skb_queue_head_init(&priv->rx_queue); + init_usb_anchor(&priv->submitted); + + usb_get_dev(udev); + + /* really lazy and simple way of figuring out if we're a 3887 */ + /* TODO: should just stick the identification in the device table */ + i = intf->altsetting->desc.bNumEndpoints; + recognized_pipes = 0; + while (i--) { + switch (intf->altsetting->endpoint[i].desc.bEndpointAddress) { + case P54U_PIPE_DATA: + case P54U_PIPE_MGMT: + case P54U_PIPE_BRG: + case P54U_PIPE_DEV: + case P54U_PIPE_DATA | USB_DIR_IN: + case P54U_PIPE_MGMT | USB_DIR_IN: + case P54U_PIPE_BRG | USB_DIR_IN: + case P54U_PIPE_DEV | USB_DIR_IN: + case P54U_PIPE_INT | USB_DIR_IN: + recognized_pipes++; + } + } + priv->common.open = p54u_open; + priv->common.stop = p54u_stop; + if (recognized_pipes < P54U_PIPE_NUMBER) { +#ifdef CONFIG_PM + /* ISL3887 needs a full reset on resume */ + udev->reset_resume = 1; +#endif /* CONFIG_PM */ + err = p54u_device_reset(dev); + + priv->hw_type = P54U_3887; + dev->extra_tx_headroom += sizeof(struct lm87_tx_hdr); + priv->common.tx_hdr_len = sizeof(struct lm87_tx_hdr); + priv->common.tx = p54u_tx_lm87; + priv->upload_fw = p54u_upload_firmware_3887; + } else { + priv->hw_type = P54U_NET2280; + dev->extra_tx_headroom += sizeof(struct net2280_tx_hdr); + priv->common.tx_hdr_len = sizeof(struct net2280_tx_hdr); + priv->common.tx = p54u_tx_net2280; + priv->upload_fw = p54u_upload_firmware_net2280; + } + err = p54u_load_firmware(dev, intf); + return err; +} + +static void p54u_disconnect(struct usb_interface *intf) +{ + struct ieee80211_hw *dev = usb_get_intfdata(intf); + struct p54u_priv *priv; + + if (!dev) + return; + + priv = dev->priv; + wait_for_completion(&priv->fw_wait_load); + p54_unregister_common(dev); + + usb_put_dev(interface_to_usbdev(intf)); + release_firmware(priv->fw); + p54_free_common(dev); +} + +static int p54u_pre_reset(struct usb_interface *intf) +{ + struct ieee80211_hw *dev = usb_get_intfdata(intf); + + if (!dev) + return -ENODEV; + + p54u_stop(dev); + return 0; +} + +static int p54u_resume(struct usb_interface *intf) +{ + struct ieee80211_hw *dev = usb_get_intfdata(intf); + struct p54u_priv *priv; + + if (!dev) + return -ENODEV; + + priv = dev->priv; + if (unlikely(!(priv->upload_fw && priv->fw))) + return 0; + + return priv->upload_fw(dev); +} + +static int p54u_post_reset(struct usb_interface *intf) +{ + struct ieee80211_hw *dev = usb_get_intfdata(intf); + struct p54u_priv *priv; + int err; + + err = p54u_resume(intf); + if (err) + return err; + + /* reinitialize old device state */ + priv = dev->priv; + if (priv->common.mode != NL80211_IFTYPE_UNSPECIFIED) + ieee80211_restart_hw(dev); + + return 0; +} + +#ifdef CONFIG_PM + +static int p54u_suspend(struct usb_interface *intf, pm_message_t message) +{ + return p54u_pre_reset(intf); +} + +#endif /* CONFIG_PM */ + +static struct usb_driver p54u_driver = { + .name = "p54usb", + .id_table = p54u_table, + .probe = p54u_probe, + .disconnect = p54u_disconnect, + .pre_reset = p54u_pre_reset, + .post_reset = p54u_post_reset, +#ifdef CONFIG_PM + .suspend = p54u_suspend, + .resume = p54u_resume, + .reset_resume = p54u_resume, +#endif /* CONFIG_PM */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) + .soft_unbind = 1, +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +module_usb_driver(p54u_driver); diff --git a/drivers/net/wireless/p54/p54usb.h b/drivers/net/wireless/p54/p54usb.h new file mode 100644 index 0000000..d273be7 --- /dev/null +++ b/drivers/net/wireless/p54/p54usb.h @@ -0,0 +1,151 @@ +#ifndef P54USB_H +#define P54USB_H + +/* + * Defines for USB based mac80211 Prism54 driver + * + * Copyright (c) 2006, Michael Wu + * + * Based on the islsm (softmac prism54) driver, which is: + * Copyright 2004-2006 Jean-Baptiste Note , et al. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* for isl3886 register definitions used on ver 1 devices */ +#include "p54pci.h" +#include "net2280.h" + +/* pci */ +#define NET2280_BASE 0x10000000 +#define NET2280_BASE2 0x20000000 + +/* gpio */ +#define P54U_BRG_POWER_UP (1 << GPIO0_DATA) +#define P54U_BRG_POWER_DOWN (1 << GPIO1_DATA) + +/* devinit */ +#define NET2280_CLK_4Mhz (15 << LOCAL_CLOCK_FREQUENCY) +#define NET2280_CLK_30Mhz (2 << LOCAL_CLOCK_FREQUENCY) +#define NET2280_CLK_60Mhz (1 << LOCAL_CLOCK_FREQUENCY) +#define NET2280_CLK_STOP (0 << LOCAL_CLOCK_FREQUENCY) +#define NET2280_PCI_ENABLE (1 << PCI_ENABLE) +#define NET2280_PCI_SOFT_RESET (1 << PCI_SOFT_RESET) + +/* endpoints */ +#define NET2280_CLEAR_NAK_OUT_PACKETS_MODE (1 << CLEAR_NAK_OUT_PACKETS_MODE) +#define NET2280_FIFO_FLUSH (1 << FIFO_FLUSH) + +/* irq */ +#define NET2280_USB_INTERRUPT_ENABLE (1 << USB_INTERRUPT_ENABLE) +#define NET2280_PCI_INTA_INTERRUPT (1 << PCI_INTA_INTERRUPT) +#define NET2280_PCI_INTA_INTERRUPT_ENABLE (1 << PCI_INTA_INTERRUPT_ENABLE) + +/* registers */ +#define NET2280_DEVINIT 0x00 +#define NET2280_USBIRQENB1 0x24 +#define NET2280_IRQSTAT1 0x2c +#define NET2280_FIFOCTL 0x38 +#define NET2280_GPIOCTL 0x50 +#define NET2280_RELNUM 0x88 +#define NET2280_EPA_RSP 0x324 +#define NET2280_EPA_STAT 0x32c +#define NET2280_EPB_STAT 0x34c +#define NET2280_EPC_RSP 0x364 +#define NET2280_EPC_STAT 0x36c +#define NET2280_EPD_STAT 0x38c + +#define NET2280_EPA_CFG 0x320 +#define NET2280_EPB_CFG 0x340 +#define NET2280_EPC_CFG 0x360 +#define NET2280_EPD_CFG 0x380 +#define NET2280_EPE_CFG 0x3A0 +#define NET2280_EPF_CFG 0x3C0 +#define P54U_DEV_BASE 0x40000000 + +struct net2280_tx_hdr { + __le32 device_addr; + __le16 len; + __le16 follower; /* ? */ + u8 padding[8]; +} __packed; + +struct lm87_tx_hdr { + __le32 device_addr; + __le32 chksum; +} __packed; + +/* Some flags for the isl hardware registers controlling DMA inside the + * chip */ +#define ISL38XX_DMA_STATUS_DONE 0x00000001 +#define ISL38XX_DMA_STATUS_READY 0x00000002 +#define NET2280_EPA_FIFO_PCI_ADDR 0x20000000 +#define ISL38XX_DMA_MASTER_CONTROL_TRIGGER 0x00000004 + +enum net2280_op_type { + NET2280_BRG_U32 = 0x001F, + NET2280_BRG_CFG_U32 = 0x000F, + NET2280_BRG_CFG_U16 = 0x0003, + NET2280_DEV_U32 = 0x080F, + NET2280_DEV_CFG_U32 = 0x088F, + NET2280_DEV_CFG_U16 = 0x0883 +}; + +#define P54U_FW_BLOCK 2048 + +#define X2_SIGNATURE "x2 " +#define X2_SIGNATURE_SIZE 4 + +struct x2_header { + u8 signature[X2_SIGNATURE_SIZE]; + __le32 fw_load_addr; + __le32 fw_length; + __le32 crc; +} __packed; + +/* pipes 3 and 4 are not used by the driver */ +#define P54U_PIPE_NUMBER 9 + +enum p54u_pipe_addr { + P54U_PIPE_DATA = 0x01, + P54U_PIPE_MGMT = 0x02, + P54U_PIPE_3 = 0x03, + P54U_PIPE_4 = 0x04, + P54U_PIPE_BRG = 0x0d, + P54U_PIPE_DEV = 0x0e, + P54U_PIPE_INT = 0x0f +}; + +struct p54u_rx_info { + struct urb *urb; + struct ieee80211_hw *dev; +}; + +enum p54u_hw_type { + P54U_INVALID_HW, + P54U_NET2280, + P54U_3887, + + /* keep last */ + __NUM_P54U_HWTYPES, +}; + +struct p54u_priv { + struct p54_common common; + struct usb_device *udev; + struct usb_interface *intf; + int (*upload_fw)(struct ieee80211_hw *dev); + + enum p54u_hw_type hw_type; + spinlock_t lock; + struct sk_buff_head rx_queue; + struct usb_anchor submitted; + const struct firmware *fw; + + /* asynchronous firmware callback */ + struct completion fw_wait_load; +}; + +#endif /* P54USB_H */ diff --git a/drivers/net/wireless/p54/txrx.c b/drivers/net/wireless/p54/txrx.c new file mode 100644 index 0000000..f95de0d --- /dev/null +++ b/drivers/net/wireless/p54/txrx.c @@ -0,0 +1,941 @@ +/* + * Common code for mac80211 Prism54 drivers + * + * Copyright (c) 2006, Michael Wu + * Copyright (c) 2007-2009, Christian Lamparter + * Copyright 2008, Johannes Berg + * + * Based on: + * - the islsm (softmac prism54) driver, which is: + * Copyright 2004-2006 Jean-Baptiste Note , et al. + * - stlc45xx driver + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include + +#include "p54.h" +#include "lmac.h" + +#ifdef P54_MM_DEBUG +static void p54_dump_tx_queue(struct p54_common *priv) +{ + unsigned long flags; + struct ieee80211_tx_info *info; + struct p54_tx_info *range; + struct sk_buff *skb; + struct p54_hdr *hdr; + unsigned int i = 0; + u32 prev_addr; + u32 largest_hole = 0, free; + + spin_lock_irqsave(&priv->tx_queue.lock, flags); + wiphy_debug(priv->hw->wiphy, "/ --- tx queue dump (%d entries) ---\n", + skb_queue_len(&priv->tx_queue)); + + prev_addr = priv->rx_start; + skb_queue_walk(&priv->tx_queue, skb) { + info = IEEE80211_SKB_CB(skb); + range = (void *) info->rate_driver_data; + hdr = (void *) skb->data; + + free = range->start_addr - prev_addr; + wiphy_debug(priv->hw->wiphy, + "| [%02d] => [skb:%p skb_len:0x%04x " + "hdr:{flags:%02x len:%04x req_id:%04x type:%02x} " + "mem:{start:%04x end:%04x, free:%d}]\n", + i++, skb, skb->len, + le16_to_cpu(hdr->flags), le16_to_cpu(hdr->len), + le32_to_cpu(hdr->req_id), le16_to_cpu(hdr->type), + range->start_addr, range->end_addr, free); + + prev_addr = range->end_addr; + largest_hole = max(largest_hole, free); + } + free = priv->rx_end - prev_addr; + largest_hole = max(largest_hole, free); + wiphy_debug(priv->hw->wiphy, + "\\ --- [free: %d], largest free block: %d ---\n", + free, largest_hole); + spin_unlock_irqrestore(&priv->tx_queue.lock, flags); +} +#endif /* P54_MM_DEBUG */ + +/* + * So, the firmware is somewhat stupid and doesn't know what places in its + * memory incoming data should go to. By poking around in the firmware, we + * can find some unused memory to upload our packets to. However, data that we + * want the card to TX needs to stay intact until the card has told us that + * it is done with it. This function finds empty places we can upload to and + * marks allocated areas as reserved if necessary. p54_find_and_unlink_skb or + * p54_free_skb frees allocated areas. + */ +static int p54_assign_address(struct p54_common *priv, struct sk_buff *skb) +{ + struct sk_buff *entry, *target_skb = NULL; + struct ieee80211_tx_info *info; + struct p54_tx_info *range; + struct p54_hdr *data = (void *) skb->data; + unsigned long flags; + u32 last_addr = priv->rx_start; + u32 target_addr = priv->rx_start; + u16 len = priv->headroom + skb->len + priv->tailroom + 3; + + info = IEEE80211_SKB_CB(skb); + range = (void *) info->rate_driver_data; + len = (range->extra_len + len) & ~0x3; + + spin_lock_irqsave(&priv->tx_queue.lock, flags); + if (unlikely(skb_queue_len(&priv->tx_queue) == 32)) { + /* + * The tx_queue is now really full. + * + * TODO: check if the device has crashed and reset it. + */ + spin_unlock_irqrestore(&priv->tx_queue.lock, flags); + return -EBUSY; + } + + skb_queue_walk(&priv->tx_queue, entry) { + u32 hole_size; + info = IEEE80211_SKB_CB(entry); + range = (void *) info->rate_driver_data; + hole_size = range->start_addr - last_addr; + + if (!target_skb && hole_size >= len) { + target_skb = entry->prev; + hole_size -= len; + target_addr = last_addr; + break; + } + last_addr = range->end_addr; + } + if (unlikely(!target_skb)) { + if (priv->rx_end - last_addr >= len) { + target_skb = priv->tx_queue.prev; + if (!skb_queue_empty(&priv->tx_queue)) { + info = IEEE80211_SKB_CB(target_skb); + range = (void *)info->rate_driver_data; + target_addr = range->end_addr; + } + } else { + spin_unlock_irqrestore(&priv->tx_queue.lock, flags); + return -ENOSPC; + } + } + + info = IEEE80211_SKB_CB(skb); + range = (void *) info->rate_driver_data; + range->start_addr = target_addr; + range->end_addr = target_addr + len; + data->req_id = cpu_to_le32(target_addr + priv->headroom); + if (IS_DATA_FRAME(skb) && + unlikely(GET_HW_QUEUE(skb) == P54_QUEUE_BEACON)) + priv->beacon_req_id = data->req_id; + + __skb_queue_after(&priv->tx_queue, target_skb, skb); + spin_unlock_irqrestore(&priv->tx_queue.lock, flags); + return 0; +} + +static void p54_tx_pending(struct p54_common *priv) +{ + struct sk_buff *skb; + int ret; + + skb = skb_dequeue(&priv->tx_pending); + if (unlikely(!skb)) + return ; + + ret = p54_assign_address(priv, skb); + if (unlikely(ret)) + skb_queue_head(&priv->tx_pending, skb); + else + priv->tx(priv->hw, skb); +} + +static void p54_wake_queues(struct p54_common *priv) +{ + unsigned long flags; + unsigned int i; + + if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED)) + return ; + + p54_tx_pending(priv); + + spin_lock_irqsave(&priv->tx_stats_lock, flags); + for (i = 0; i < priv->hw->queues; i++) { + if (priv->tx_stats[i + P54_QUEUE_DATA].len < + priv->tx_stats[i + P54_QUEUE_DATA].limit) + ieee80211_wake_queue(priv->hw, i); + } + spin_unlock_irqrestore(&priv->tx_stats_lock, flags); +} + +static int p54_tx_qos_accounting_alloc(struct p54_common *priv, + struct sk_buff *skb, + const u16 p54_queue) +{ + struct p54_tx_queue_stats *queue; + unsigned long flags; + + if (WARN_ON(p54_queue >= P54_QUEUE_NUM)) + return -EINVAL; + + queue = &priv->tx_stats[p54_queue]; + + spin_lock_irqsave(&priv->tx_stats_lock, flags); + if (unlikely(queue->len >= queue->limit && IS_QOS_QUEUE(p54_queue))) { + spin_unlock_irqrestore(&priv->tx_stats_lock, flags); + return -ENOSPC; + } + + queue->len++; + queue->count++; + + if (unlikely(queue->len == queue->limit && IS_QOS_QUEUE(p54_queue))) { + u16 ac_queue = p54_queue - P54_QUEUE_DATA; + ieee80211_stop_queue(priv->hw, ac_queue); + } + + spin_unlock_irqrestore(&priv->tx_stats_lock, flags); + return 0; +} + +static void p54_tx_qos_accounting_free(struct p54_common *priv, + struct sk_buff *skb) +{ + if (IS_DATA_FRAME(skb)) { + unsigned long flags; + + spin_lock_irqsave(&priv->tx_stats_lock, flags); + priv->tx_stats[GET_HW_QUEUE(skb)].len--; + spin_unlock_irqrestore(&priv->tx_stats_lock, flags); + + if (unlikely(GET_HW_QUEUE(skb) == P54_QUEUE_BEACON)) { + if (priv->beacon_req_id == GET_REQ_ID(skb)) { + /* this is the active beacon set anymore */ + priv->beacon_req_id = 0; + } + complete(&priv->beacon_comp); + } + } + p54_wake_queues(priv); +} + +void p54_free_skb(struct ieee80211_hw *dev, struct sk_buff *skb) +{ + struct p54_common *priv = dev->priv; + if (unlikely(!skb)) + return ; + + skb_unlink(skb, &priv->tx_queue); + p54_tx_qos_accounting_free(priv, skb); + ieee80211_free_txskb(dev, skb); +} +EXPORT_SYMBOL_GPL(p54_free_skb); + +static struct sk_buff *p54_find_and_unlink_skb(struct p54_common *priv, + const __le32 req_id) +{ + struct sk_buff *entry; + unsigned long flags; + + spin_lock_irqsave(&priv->tx_queue.lock, flags); + skb_queue_walk(&priv->tx_queue, entry) { + struct p54_hdr *hdr = (struct p54_hdr *) entry->data; + + if (hdr->req_id == req_id) { + __skb_unlink(entry, &priv->tx_queue); + spin_unlock_irqrestore(&priv->tx_queue.lock, flags); + p54_tx_qos_accounting_free(priv, entry); + return entry; + } + } + spin_unlock_irqrestore(&priv->tx_queue.lock, flags); + return NULL; +} + +void p54_tx(struct p54_common *priv, struct sk_buff *skb) +{ + skb_queue_tail(&priv->tx_pending, skb); + p54_tx_pending(priv); +} + +static int p54_rssi_to_dbm(struct p54_common *priv, int rssi) +{ + if (priv->rxhw != 5) { + return ((rssi * priv->cur_rssi->mul) / 64 + + priv->cur_rssi->add) / 4; + } else { + /* + * TODO: find the correct formula + */ + return rssi / 2 - 110; + } +} + +/* + * Even if the firmware is capable of dealing with incoming traffic, + * while dozing, we have to prepared in case mac80211 uses PS-POLL + * to retrieve outstanding frames from our AP. + * (see comment in net/mac80211/mlme.c @ line 1993) + */ +static void p54_pspoll_workaround(struct p54_common *priv, struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (void *) skb->data; + struct ieee80211_tim_ie *tim_ie; + u8 *tim; + u8 tim_len; + bool new_psm; + + /* only beacons have a TIM IE */ + if (!ieee80211_is_beacon(hdr->frame_control)) + return; + + if (!priv->aid) + return; + + /* only consider beacons from the associated BSSID */ + if (!ether_addr_equal(hdr->addr3, priv->bssid)) + return; + + tim = p54_find_ie(skb, WLAN_EID_TIM); + if (!tim) + return; + + tim_len = tim[1]; + tim_ie = (struct ieee80211_tim_ie *) &tim[2]; + + new_psm = ieee80211_check_tim(tim_ie, tim_len, priv->aid); + if (new_psm != priv->powersave_override) { + priv->powersave_override = new_psm; + p54_set_ps(priv); + } +} + +static int p54_rx_data(struct p54_common *priv, struct sk_buff *skb) +{ + struct p54_rx_data *hdr = (struct p54_rx_data *) skb->data; + struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); + u16 freq = le16_to_cpu(hdr->freq); + size_t header_len = sizeof(*hdr); + u32 tsf32; + u8 rate = hdr->rate & 0xf; + + /* + * If the device is in a unspecified state we have to + * ignore all data frames. Else we could end up with a + * nasty crash. + */ + if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED)) + return 0; + + if (!(hdr->flags & cpu_to_le16(P54_HDR_FLAG_DATA_IN_FCS_GOOD))) + return 0; + + if (hdr->decrypt_status == P54_DECRYPT_OK) + rx_status->flag |= RX_FLAG_DECRYPTED; + if ((hdr->decrypt_status == P54_DECRYPT_FAIL_MICHAEL) || + (hdr->decrypt_status == P54_DECRYPT_FAIL_TKIP)) + rx_status->flag |= RX_FLAG_MMIC_ERROR; + + rx_status->signal = p54_rssi_to_dbm(priv, hdr->rssi); + if (hdr->rate & 0x10) + rx_status->flag |= RX_FLAG_SHORTPRE; + if (priv->hw->conf.chandef.chan->band == IEEE80211_BAND_5GHZ) + rx_status->rate_idx = (rate < 4) ? 0 : rate - 4; + else + rx_status->rate_idx = rate; + + rx_status->freq = freq; + rx_status->band = priv->hw->conf.chandef.chan->band; + rx_status->antenna = hdr->antenna; + + tsf32 = le32_to_cpu(hdr->tsf32); + if (tsf32 < priv->tsf_low32) + priv->tsf_high32++; + rx_status->mactime = ((u64)priv->tsf_high32) << 32 | tsf32; + priv->tsf_low32 = tsf32; + + /* LMAC API Page 10/29 - s_lm_data_in - clock + * "usec accurate timestamp of hardware clock + * at end of frame (before OFDM SIFS EOF padding" + */ + rx_status->flag |= RX_FLAG_MACTIME_END; + + if (hdr->flags & cpu_to_le16(P54_HDR_FLAG_DATA_ALIGN)) + header_len += hdr->align[0]; + + skb_pull(skb, header_len); + skb_trim(skb, le16_to_cpu(hdr->len)); + if (unlikely(priv->hw->conf.flags & IEEE80211_CONF_PS)) + p54_pspoll_workaround(priv, skb); + + ieee80211_rx_irqsafe(priv->hw, skb); + + ieee80211_queue_delayed_work(priv->hw, &priv->work, + msecs_to_jiffies(P54_STATISTICS_UPDATE)); + + return -1; +} + +static void p54_rx_frame_sent(struct p54_common *priv, struct sk_buff *skb) +{ + struct p54_hdr *hdr = (struct p54_hdr *) skb->data; + struct p54_frame_sent *payload = (struct p54_frame_sent *) hdr->data; + struct ieee80211_tx_info *info; + struct p54_hdr *entry_hdr; + struct p54_tx_data *entry_data; + struct sk_buff *entry; + unsigned int pad = 0, frame_len; + int count, idx; + + entry = p54_find_and_unlink_skb(priv, hdr->req_id); + if (unlikely(!entry)) + return ; + + frame_len = entry->len; + info = IEEE80211_SKB_CB(entry); + entry_hdr = (struct p54_hdr *) entry->data; + entry_data = (struct p54_tx_data *) entry_hdr->data; + priv->stats.dot11ACKFailureCount += payload->tries - 1; + + /* + * Frames in P54_QUEUE_FWSCAN and P54_QUEUE_BEACON are + * generated by the driver. Therefore tx_status is bogus + * and we don't want to confuse the mac80211 stack. + */ + if (unlikely(entry_data->hw_queue < P54_QUEUE_FWSCAN)) { + dev_kfree_skb_any(entry); + return ; + } + + /* + * Clear manually, ieee80211_tx_info_clear_status would + * clear the counts too and we need them. + */ + memset(&info->status.ack_signal, 0, + sizeof(struct ieee80211_tx_info) - + offsetof(struct ieee80211_tx_info, status.ack_signal)); + BUILD_BUG_ON(offsetof(struct ieee80211_tx_info, + status.ack_signal) != 20); + + if (entry_hdr->flags & cpu_to_le16(P54_HDR_FLAG_DATA_ALIGN)) + pad = entry_data->align[0]; + + /* walk through the rates array and adjust the counts */ + count = payload->tries; + for (idx = 0; idx < 4; idx++) { + if (count >= info->status.rates[idx].count) { + count -= info->status.rates[idx].count; + } else if (count > 0) { + info->status.rates[idx].count = count; + count = 0; + } else { + info->status.rates[idx].idx = -1; + info->status.rates[idx].count = 0; + } + } + + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK) && + !(payload->status & P54_TX_FAILED)) + info->flags |= IEEE80211_TX_STAT_ACK; + if (payload->status & P54_TX_PSM_CANCELLED) + info->flags |= IEEE80211_TX_STAT_TX_FILTERED; + info->status.ack_signal = p54_rssi_to_dbm(priv, + (int)payload->ack_rssi); + + /* Undo all changes to the frame. */ + switch (entry_data->key_type) { + case P54_CRYPTO_TKIPMICHAEL: { + u8 *iv = (u8 *)(entry_data->align + pad + + entry_data->crypt_offset); + + /* Restore the original TKIP IV. */ + iv[2] = iv[0]; + iv[0] = iv[1]; + iv[1] = (iv[0] | 0x20) & 0x7f; /* WEPSeed - 8.3.2.2 */ + + frame_len -= 12; /* remove TKIP_MMIC + TKIP_ICV */ + break; + } + case P54_CRYPTO_AESCCMP: + frame_len -= 8; /* remove CCMP_MIC */ + break; + case P54_CRYPTO_WEP: + frame_len -= 4; /* remove WEP_ICV */ + break; + } + + skb_trim(entry, frame_len); + skb_pull(entry, sizeof(*hdr) + pad + sizeof(*entry_data)); + ieee80211_tx_status_irqsafe(priv->hw, entry); +} + +static void p54_rx_eeprom_readback(struct p54_common *priv, + struct sk_buff *skb) +{ + struct p54_hdr *hdr = (struct p54_hdr *) skb->data; + struct p54_eeprom_lm86 *eeprom = (struct p54_eeprom_lm86 *) hdr->data; + struct sk_buff *tmp; + + if (!priv->eeprom) + return ; + + if (priv->fw_var >= 0x509) { + memcpy(priv->eeprom, eeprom->v2.data, + le16_to_cpu(eeprom->v2.len)); + } else { + memcpy(priv->eeprom, eeprom->v1.data, + le16_to_cpu(eeprom->v1.len)); + } + + priv->eeprom = NULL; + tmp = p54_find_and_unlink_skb(priv, hdr->req_id); + dev_kfree_skb_any(tmp); + complete(&priv->eeprom_comp); +} + +static void p54_rx_stats(struct p54_common *priv, struct sk_buff *skb) +{ + struct p54_hdr *hdr = (struct p54_hdr *) skb->data; + struct p54_statistics *stats = (struct p54_statistics *) hdr->data; + struct sk_buff *tmp; + struct ieee80211_channel *chan; + unsigned int i, rssi, tx, cca, dtime, dtotal, dcca, dtx, drssi, unit; + u32 tsf32; + + if (unlikely(priv->mode == NL80211_IFTYPE_UNSPECIFIED)) + return ; + + tsf32 = le32_to_cpu(stats->tsf32); + if (tsf32 < priv->tsf_low32) + priv->tsf_high32++; + priv->tsf_low32 = tsf32; + + priv->stats.dot11RTSFailureCount = le32_to_cpu(stats->rts_fail); + priv->stats.dot11RTSSuccessCount = le32_to_cpu(stats->rts_success); + priv->stats.dot11FCSErrorCount = le32_to_cpu(stats->rx_bad_fcs); + + priv->noise = p54_rssi_to_dbm(priv, le32_to_cpu(stats->noise)); + + /* + * STSW450X LMAC API page 26 - 3.8 Statistics + * "The exact measurement period can be derived from the + * timestamp member". + */ + dtime = tsf32 - priv->survey_raw.timestamp; + + /* + * STSW450X LMAC API page 26 - 3.8.1 Noise histogram + * The LMAC samples RSSI, CCA and transmit state at regular + * periods (typically 8 times per 1k [as in 1024] usec). + */ + cca = le32_to_cpu(stats->sample_cca); + tx = le32_to_cpu(stats->sample_tx); + rssi = 0; + for (i = 0; i < ARRAY_SIZE(stats->sample_noise); i++) + rssi += le32_to_cpu(stats->sample_noise[i]); + + dcca = cca - priv->survey_raw.cached_cca; + drssi = rssi - priv->survey_raw.cached_rssi; + dtx = tx - priv->survey_raw.cached_tx; + dtotal = dcca + drssi + dtx; + + /* + * update statistics when more than a second is over since the + * last call, or when a update is badly needed. + */ + if (dtotal && (priv->update_stats || dtime >= USEC_PER_SEC) && + dtime >= dtotal) { + priv->survey_raw.timestamp = tsf32; + priv->update_stats = false; + unit = dtime / dtotal; + + if (dcca) { + priv->survey_raw.cca += dcca * unit; + priv->survey_raw.cached_cca = cca; + } + if (dtx) { + priv->survey_raw.tx += dtx * unit; + priv->survey_raw.cached_tx = tx; + } + if (drssi) { + priv->survey_raw.rssi += drssi * unit; + priv->survey_raw.cached_rssi = rssi; + } + + /* 1024 usec / 8 times = 128 usec / time */ + if (!(priv->phy_ps || priv->phy_idle)) + priv->survey_raw.active += dtotal * unit; + else + priv->survey_raw.active += (dcca + dtx) * unit; + } + + chan = priv->curchan; + if (chan) { + struct survey_info *survey = &priv->survey[chan->hw_value]; + survey->noise = clamp_t(s8, priv->noise, -128, 127); + survey->channel_time = priv->survey_raw.active; + survey->channel_time_tx = priv->survey_raw.tx; + survey->channel_time_busy = priv->survey_raw.tx + + priv->survey_raw.cca; + do_div(survey->channel_time, 1024); + do_div(survey->channel_time_tx, 1024); + do_div(survey->channel_time_busy, 1024); + } + + tmp = p54_find_and_unlink_skb(priv, hdr->req_id); + dev_kfree_skb_any(tmp); + complete(&priv->stat_comp); +} + +static void p54_rx_trap(struct p54_common *priv, struct sk_buff *skb) +{ + struct p54_hdr *hdr = (struct p54_hdr *) skb->data; + struct p54_trap *trap = (struct p54_trap *) hdr->data; + u16 event = le16_to_cpu(trap->event); + u16 freq = le16_to_cpu(trap->frequency); + + switch (event) { + case P54_TRAP_BEACON_TX: + break; + case P54_TRAP_RADAR: + wiphy_info(priv->hw->wiphy, "radar (freq:%d MHz)\n", freq); + break; + case P54_TRAP_NO_BEACON: + if (priv->vif) + ieee80211_beacon_loss(priv->vif); + break; + case P54_TRAP_SCAN: + break; + case P54_TRAP_TBTT: + break; + case P54_TRAP_TIMER: + break; + case P54_TRAP_FAA_RADIO_OFF: + wiphy_rfkill_set_hw_state(priv->hw->wiphy, true); + break; + case P54_TRAP_FAA_RADIO_ON: + wiphy_rfkill_set_hw_state(priv->hw->wiphy, false); + break; + default: + wiphy_info(priv->hw->wiphy, "received event:%x freq:%d\n", + event, freq); + break; + } +} + +static int p54_rx_control(struct p54_common *priv, struct sk_buff *skb) +{ + struct p54_hdr *hdr = (struct p54_hdr *) skb->data; + + switch (le16_to_cpu(hdr->type)) { + case P54_CONTROL_TYPE_TXDONE: + p54_rx_frame_sent(priv, skb); + break; + case P54_CONTROL_TYPE_TRAP: + p54_rx_trap(priv, skb); + break; + case P54_CONTROL_TYPE_BBP: + break; + case P54_CONTROL_TYPE_STAT_READBACK: + p54_rx_stats(priv, skb); + break; + case P54_CONTROL_TYPE_EEPROM_READBACK: + p54_rx_eeprom_readback(priv, skb); + break; + default: + wiphy_debug(priv->hw->wiphy, + "not handling 0x%02x type control frame\n", + le16_to_cpu(hdr->type)); + break; + } + return 0; +} + +/* returns zero if skb can be reused */ +int p54_rx(struct ieee80211_hw *dev, struct sk_buff *skb) +{ + struct p54_common *priv = dev->priv; + u16 type = le16_to_cpu(*((__le16 *)skb->data)); + + if (type & P54_HDR_FLAG_CONTROL) + return p54_rx_control(priv, skb); + else + return p54_rx_data(priv, skb); +} +EXPORT_SYMBOL_GPL(p54_rx); + +static void p54_tx_80211_header(struct p54_common *priv, struct sk_buff *skb, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + u8 *queue, u32 *extra_len, u16 *flags, u16 *aid, + bool *burst_possible) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + + if (ieee80211_is_data_qos(hdr->frame_control)) + *burst_possible = true; + else + *burst_possible = false; + + if (!(info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ)) + *flags |= P54_HDR_FLAG_DATA_OUT_SEQNR; + + if (info->flags & IEEE80211_TX_CTL_NO_PS_BUFFER) + *flags |= P54_HDR_FLAG_DATA_OUT_NOCANCEL; + + if (info->flags & IEEE80211_TX_CTL_CLEAR_PS_FILT) + *flags |= P54_HDR_FLAG_DATA_OUT_NOCANCEL; + + *queue = skb_get_queue_mapping(skb) + P54_QUEUE_DATA; + + switch (priv->mode) { + case NL80211_IFTYPE_MONITOR: + /* + * We have to set P54_HDR_FLAG_DATA_OUT_PROMISC for + * every frame in promiscuous/monitor mode. + * see STSW45x0C LMAC API - page 12. + */ + *aid = 0; + *flags |= P54_HDR_FLAG_DATA_OUT_PROMISC; + break; + case NL80211_IFTYPE_STATION: + *aid = 1; + break; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_MESH_POINT: + if (info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM) { + *aid = 0; + *queue = P54_QUEUE_CAB; + return; + } + + if (unlikely(ieee80211_is_mgmt(hdr->frame_control))) { + if (ieee80211_is_probe_resp(hdr->frame_control)) { + *aid = 0; + *flags |= P54_HDR_FLAG_DATA_OUT_TIMESTAMP | + P54_HDR_FLAG_DATA_OUT_NOCANCEL; + return; + } else if (ieee80211_is_beacon(hdr->frame_control)) { + *aid = 0; + + if (info->flags & IEEE80211_TX_CTL_INJECTED) { + /* + * Injecting beacons on top of a AP is + * not a good idea... nevertheless, + * it should be doable. + */ + + return; + } + + *flags |= P54_HDR_FLAG_DATA_OUT_TIMESTAMP; + *queue = P54_QUEUE_BEACON; + *extra_len = IEEE80211_MAX_TIM_LEN; + return; + } + } + + if (sta) + *aid = sta->aid; + break; + } +} + +static u8 p54_convert_algo(u32 cipher) +{ + switch (cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + return P54_CRYPTO_WEP; + case WLAN_CIPHER_SUITE_TKIP: + return P54_CRYPTO_TKIPMICHAEL; + case WLAN_CIPHER_SUITE_CCMP: + return P54_CRYPTO_AESCCMP; + default: + return 0; + } +} + +void p54_tx_80211(struct ieee80211_hw *dev, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct p54_common *priv = dev->priv; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct p54_tx_info *p54info; + struct p54_hdr *hdr; + struct p54_tx_data *txhdr; + unsigned int padding, len, extra_len = 0; + int i, j, ridx; + u16 hdr_flags = 0, aid = 0; + u8 rate, queue = 0, crypt_offset = 0; + u8 cts_rate = 0x20; + u8 rc_flags; + u8 calculated_tries[4]; + u8 nrates = 0, nremaining = 8; + bool burst_allowed = false; + + p54_tx_80211_header(priv, skb, info, control->sta, &queue, &extra_len, + &hdr_flags, &aid, &burst_allowed); + + if (p54_tx_qos_accounting_alloc(priv, skb, queue)) { + ieee80211_free_txskb(dev, skb); + return; + } + + padding = (unsigned long)(skb->data - (sizeof(*hdr) + sizeof(*txhdr))) & 3; + len = skb->len; + + if (info->control.hw_key) { + crypt_offset = ieee80211_get_hdrlen_from_skb(skb); + if (info->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) { + u8 *iv = (u8 *)(skb->data + crypt_offset); + /* + * The firmware excepts that the IV has to have + * this special format + */ + iv[1] = iv[0]; + iv[0] = iv[2]; + iv[2] = 0; + } + } + + txhdr = (struct p54_tx_data *) skb_push(skb, sizeof(*txhdr) + padding); + hdr = (struct p54_hdr *) skb_push(skb, sizeof(*hdr)); + + if (padding) + hdr_flags |= P54_HDR_FLAG_DATA_ALIGN; + hdr->type = cpu_to_le16(aid); + hdr->rts_tries = info->control.rates[0].count; + + /* + * we register the rates in perfect order, and + * RTS/CTS won't happen on 5 GHz + */ + cts_rate = info->control.rts_cts_rate_idx; + + memset(&txhdr->rateset, 0, sizeof(txhdr->rateset)); + + /* see how many rates got used */ + for (i = 0; i < dev->max_rates; i++) { + if (info->control.rates[i].idx < 0) + break; + nrates++; + } + + /* limit tries to 8/nrates per rate */ + for (i = 0; i < nrates; i++) { + /* + * The magic expression here is equivalent to 8/nrates for + * all values that matter, but avoids division and jumps. + * Note that nrates can only take the values 1 through 4. + */ + calculated_tries[i] = min_t(int, ((15 >> nrates) | 1) + 1, + info->control.rates[i].count); + nremaining -= calculated_tries[i]; + } + + /* if there are tries left, distribute from back to front */ + for (i = nrates - 1; nremaining > 0 && i >= 0; i--) { + int tmp = info->control.rates[i].count - calculated_tries[i]; + + if (tmp <= 0) + continue; + /* RC requested more tries at this rate */ + + tmp = min_t(int, tmp, nremaining); + calculated_tries[i] += tmp; + nremaining -= tmp; + } + + ridx = 0; + for (i = 0; i < nrates && ridx < 8; i++) { + /* we register the rates in perfect order */ + rate = info->control.rates[i].idx; + if (info->band == IEEE80211_BAND_5GHZ) + rate += 4; + + /* store the count we actually calculated for TX status */ + info->control.rates[i].count = calculated_tries[i]; + + rc_flags = info->control.rates[i].flags; + if (rc_flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE) { + rate |= 0x10; + cts_rate |= 0x10; + } + if (rc_flags & IEEE80211_TX_RC_USE_RTS_CTS) { + burst_allowed = false; + rate |= 0x40; + } else if (rc_flags & IEEE80211_TX_RC_USE_CTS_PROTECT) { + rate |= 0x20; + burst_allowed = false; + } + for (j = 0; j < calculated_tries[i] && ridx < 8; j++) { + txhdr->rateset[ridx] = rate; + ridx++; + } + } + + if (burst_allowed) + hdr_flags |= P54_HDR_FLAG_DATA_OUT_BURST; + + /* TODO: enable bursting */ + hdr->flags = cpu_to_le16(hdr_flags); + hdr->tries = ridx; + txhdr->rts_rate_idx = 0; + if (info->control.hw_key) { + txhdr->key_type = p54_convert_algo(info->control.hw_key->cipher); + txhdr->key_len = min((u8)16, info->control.hw_key->keylen); + memcpy(txhdr->key, info->control.hw_key->key, txhdr->key_len); + if (info->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) { + /* reserve space for the MIC key */ + len += 8; + memcpy(skb_put(skb, 8), &(info->control.hw_key->key + [NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY]), 8); + } + /* reserve some space for ICV */ + len += info->control.hw_key->icv_len; + memset(skb_put(skb, info->control.hw_key->icv_len), 0, + info->control.hw_key->icv_len); + } else { + txhdr->key_type = 0; + txhdr->key_len = 0; + } + txhdr->crypt_offset = crypt_offset; + txhdr->hw_queue = queue; + txhdr->backlog = priv->tx_stats[queue].len - 1; + memset(txhdr->durations, 0, sizeof(txhdr->durations)); + txhdr->tx_antenna = 2 & priv->tx_diversity_mask; + if (priv->rxhw == 5) { + txhdr->longbow.cts_rate = cts_rate; + txhdr->longbow.output_power = cpu_to_le16(priv->output_power); + } else { + txhdr->normal.output_power = priv->output_power; + txhdr->normal.cts_rate = cts_rate; + } + if (padding) + txhdr->align[0] = padding; + + hdr->len = cpu_to_le16(len); + /* modifies skb->cb and with it info, so must be last! */ + p54info = (void *) info->rate_driver_data; + p54info->extra_len = extra_len; + + p54_tx(priv, skb); +} diff --git a/drivers/net/wireless/rndis_wlan.c b/drivers/net/wireless/rndis_wlan.c new file mode 100644 index 0000000..ca84018 --- /dev/null +++ b/drivers/net/wireless/rndis_wlan.c @@ -0,0 +1,3771 @@ +/* + * Driver for RNDIS based wireless USB devices. + * + * Copyright (C) 2007 by Bjorge Dijkstra + * Copyright (C) 2008-2009 by Jussi Kivilinna + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Portions of this file are based on NDISwrapper project, + * Copyright (C) 2003-2005 Pontus Fuchs, Giridhar Pemmasani + * http://ndiswrapper.sourceforge.net/ + */ + +// #define DEBUG // error path messages, extra info +// #define VERBOSE // more; success messages + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* NOTE: All these are settings for Broadcom chipset */ +static char modparam_country[4] = "EU"; +module_param_string(country, modparam_country, 4, 0444); +MODULE_PARM_DESC(country, "Country code (ISO 3166-1 alpha-2), default: EU"); + +static int modparam_frameburst = 1; +module_param_named(frameburst, modparam_frameburst, int, 0444); +MODULE_PARM_DESC(frameburst, "enable frame bursting (default: on)"); + +static int modparam_afterburner = 0; +module_param_named(afterburner, modparam_afterburner, int, 0444); +MODULE_PARM_DESC(afterburner, + "enable afterburner aka '125 High Speed Mode' (default: off)"); + +static int modparam_power_save = 0; +module_param_named(power_save, modparam_power_save, int, 0444); +MODULE_PARM_DESC(power_save, + "set power save mode: 0=off, 1=on, 2=fast (default: off)"); + +static int modparam_power_output = 3; +module_param_named(power_output, modparam_power_output, int, 0444); +MODULE_PARM_DESC(power_output, + "set power output: 0=25%, 1=50%, 2=75%, 3=100% (default: 100%)"); + +static int modparam_roamtrigger = -70; +module_param_named(roamtrigger, modparam_roamtrigger, int, 0444); +MODULE_PARM_DESC(roamtrigger, + "set roaming dBm trigger: -80=optimize for distance, " + "-60=bandwidth (default: -70)"); + +static int modparam_roamdelta = 1; +module_param_named(roamdelta, modparam_roamdelta, int, 0444); +MODULE_PARM_DESC(roamdelta, + "set roaming tendency: 0=aggressive, 1=moderate, " + "2=conservative (default: moderate)"); + +static int modparam_workaround_interval; +module_param_named(workaround_interval, modparam_workaround_interval, + int, 0444); +MODULE_PARM_DESC(workaround_interval, + "set stall workaround interval in msecs (0=disabled) (default: 0)"); + +/* Typical noise/maximum signal level values taken from ndiswrapper iw_ndis.h */ +#define WL_NOISE -96 /* typical noise level in dBm */ +#define WL_SIGMAX -32 /* typical maximum signal level in dBm */ + + +/* Assume that Broadcom 4320 (only chipset at time of writing known to be + * based on wireless rndis) has default txpower of 13dBm. + * This value is from Linksys WUSB54GSC User Guide, Appendix F: Specifications. + * 100% : 20 mW ~ 13dBm + * 75% : 15 mW ~ 12dBm + * 50% : 10 mW ~ 10dBm + * 25% : 5 mW ~ 7dBm + */ +#define BCM4320_DEFAULT_TXPOWER_DBM_100 13 +#define BCM4320_DEFAULT_TXPOWER_DBM_75 12 +#define BCM4320_DEFAULT_TXPOWER_DBM_50 10 +#define BCM4320_DEFAULT_TXPOWER_DBM_25 7 + +/* Known device types */ +#define RNDIS_UNKNOWN 0 +#define RNDIS_BCM4320A 1 +#define RNDIS_BCM4320B 2 + + +/* NDIS data structures. Taken from wpa_supplicant driver_ndis.c + * slightly modified for datatype endianess, etc + */ +#define NDIS_802_11_LENGTH_SSID 32 +#define NDIS_802_11_LENGTH_RATES 8 +#define NDIS_802_11_LENGTH_RATES_EX 16 + +enum ndis_80211_net_type { + NDIS_80211_TYPE_FREQ_HOP, + NDIS_80211_TYPE_DIRECT_SEQ, + NDIS_80211_TYPE_OFDM_A, + NDIS_80211_TYPE_OFDM_G +}; + +enum ndis_80211_net_infra { + NDIS_80211_INFRA_ADHOC, + NDIS_80211_INFRA_INFRA, + NDIS_80211_INFRA_AUTO_UNKNOWN +}; + +enum ndis_80211_auth_mode { + NDIS_80211_AUTH_OPEN, + NDIS_80211_AUTH_SHARED, + NDIS_80211_AUTH_AUTO_SWITCH, + NDIS_80211_AUTH_WPA, + NDIS_80211_AUTH_WPA_PSK, + NDIS_80211_AUTH_WPA_NONE, + NDIS_80211_AUTH_WPA2, + NDIS_80211_AUTH_WPA2_PSK +}; + +enum ndis_80211_encr_status { + NDIS_80211_ENCR_WEP_ENABLED, + NDIS_80211_ENCR_DISABLED, + NDIS_80211_ENCR_WEP_KEY_ABSENT, + NDIS_80211_ENCR_NOT_SUPPORTED, + NDIS_80211_ENCR_TKIP_ENABLED, + NDIS_80211_ENCR_TKIP_KEY_ABSENT, + NDIS_80211_ENCR_CCMP_ENABLED, + NDIS_80211_ENCR_CCMP_KEY_ABSENT +}; + +enum ndis_80211_priv_filter { + NDIS_80211_PRIV_ACCEPT_ALL, + NDIS_80211_PRIV_8021X_WEP +}; + +enum ndis_80211_status_type { + NDIS_80211_STATUSTYPE_AUTHENTICATION, + NDIS_80211_STATUSTYPE_MEDIASTREAMMODE, + NDIS_80211_STATUSTYPE_PMKID_CANDIDATELIST, + NDIS_80211_STATUSTYPE_RADIOSTATE, +}; + +enum ndis_80211_media_stream_mode { + NDIS_80211_MEDIA_STREAM_OFF, + NDIS_80211_MEDIA_STREAM_ON +}; + +enum ndis_80211_radio_status { + NDIS_80211_RADIO_STATUS_ON, + NDIS_80211_RADIO_STATUS_HARDWARE_OFF, + NDIS_80211_RADIO_STATUS_SOFTWARE_OFF, +}; + +enum ndis_80211_addkey_bits { + NDIS_80211_ADDKEY_8021X_AUTH = cpu_to_le32(1 << 28), + NDIS_80211_ADDKEY_SET_INIT_RECV_SEQ = cpu_to_le32(1 << 29), + NDIS_80211_ADDKEY_PAIRWISE_KEY = cpu_to_le32(1 << 30), + NDIS_80211_ADDKEY_TRANSMIT_KEY = cpu_to_le32(1 << 31) +}; + +enum ndis_80211_addwep_bits { + NDIS_80211_ADDWEP_PERCLIENT_KEY = cpu_to_le32(1 << 30), + NDIS_80211_ADDWEP_TRANSMIT_KEY = cpu_to_le32(1 << 31) +}; + +enum ndis_80211_power_mode { + NDIS_80211_POWER_MODE_CAM, + NDIS_80211_POWER_MODE_MAX_PSP, + NDIS_80211_POWER_MODE_FAST_PSP, +}; + +enum ndis_80211_pmkid_cand_list_flag_bits { + NDIS_80211_PMKID_CAND_PREAUTH = cpu_to_le32(1 << 0) +}; + +struct ndis_80211_auth_request { + __le32 length; + u8 bssid[6]; + u8 padding[2]; + __le32 flags; +} __packed; + +struct ndis_80211_pmkid_candidate { + u8 bssid[6]; + u8 padding[2]; + __le32 flags; +} __packed; + +struct ndis_80211_pmkid_cand_list { + __le32 version; + __le32 num_candidates; + struct ndis_80211_pmkid_candidate candidate_list[0]; +} __packed; + +struct ndis_80211_status_indication { + __le32 status_type; + union { + __le32 media_stream_mode; + __le32 radio_status; + struct ndis_80211_auth_request auth_request[0]; + struct ndis_80211_pmkid_cand_list cand_list; + } u; +} __packed; + +struct ndis_80211_ssid { + __le32 length; + u8 essid[NDIS_802_11_LENGTH_SSID]; +} __packed; + +struct ndis_80211_conf_freq_hop { + __le32 length; + __le32 hop_pattern; + __le32 hop_set; + __le32 dwell_time; +} __packed; + +struct ndis_80211_conf { + __le32 length; + __le32 beacon_period; + __le32 atim_window; + __le32 ds_config; + struct ndis_80211_conf_freq_hop fh_config; +} __packed; + +struct ndis_80211_bssid_ex { + __le32 length; + u8 mac[6]; + u8 padding[2]; + struct ndis_80211_ssid ssid; + __le32 privacy; + __le32 rssi; + __le32 net_type; + struct ndis_80211_conf config; + __le32 net_infra; + u8 rates[NDIS_802_11_LENGTH_RATES_EX]; + __le32 ie_length; + u8 ies[0]; +} __packed; + +struct ndis_80211_bssid_list_ex { + __le32 num_items; + struct ndis_80211_bssid_ex bssid[0]; +} __packed; + +struct ndis_80211_fixed_ies { + u8 timestamp[8]; + __le16 beacon_interval; + __le16 capabilities; +} __packed; + +struct ndis_80211_wep_key { + __le32 size; + __le32 index; + __le32 length; + u8 material[32]; +} __packed; + +struct ndis_80211_key { + __le32 size; + __le32 index; + __le32 length; + u8 bssid[6]; + u8 padding[6]; + u8 rsc[8]; + u8 material[32]; +} __packed; + +struct ndis_80211_remove_key { + __le32 size; + __le32 index; + u8 bssid[6]; + u8 padding[2]; +} __packed; + +struct ndis_config_param { + __le32 name_offs; + __le32 name_length; + __le32 type; + __le32 value_offs; + __le32 value_length; +} __packed; + +struct ndis_80211_assoc_info { + __le32 length; + __le16 req_ies; + struct req_ie { + __le16 capa; + __le16 listen_interval; + u8 cur_ap_address[6]; + } req_ie; + __le32 req_ie_length; + __le32 offset_req_ies; + __le16 resp_ies; + struct resp_ie { + __le16 capa; + __le16 status_code; + __le16 assoc_id; + } resp_ie; + __le32 resp_ie_length; + __le32 offset_resp_ies; +} __packed; + +struct ndis_80211_auth_encr_pair { + __le32 auth_mode; + __le32 encr_mode; +} __packed; + +struct ndis_80211_capability { + __le32 length; + __le32 version; + __le32 num_pmkids; + __le32 num_auth_encr_pair; + struct ndis_80211_auth_encr_pair auth_encr_pair[0]; +} __packed; + +struct ndis_80211_bssid_info { + u8 bssid[6]; + u8 pmkid[16]; +} __packed; + +struct ndis_80211_pmkid { + __le32 length; + __le32 bssid_info_count; + struct ndis_80211_bssid_info bssid_info[0]; +} __packed; + +/* + * private data + */ +#define CAP_MODE_80211A 1 +#define CAP_MODE_80211B 2 +#define CAP_MODE_80211G 4 +#define CAP_MODE_MASK 7 + +#define WORK_LINK_UP (1<<0) +#define WORK_LINK_DOWN (1<<1) +#define WORK_SET_MULTICAST_LIST (1<<2) + +#define RNDIS_WLAN_ALG_NONE 0 +#define RNDIS_WLAN_ALG_WEP (1<<0) +#define RNDIS_WLAN_ALG_TKIP (1<<1) +#define RNDIS_WLAN_ALG_CCMP (1<<2) + +#define RNDIS_WLAN_NUM_KEYS 4 +#define RNDIS_WLAN_KEY_MGMT_NONE 0 +#define RNDIS_WLAN_KEY_MGMT_802_1X (1<<0) +#define RNDIS_WLAN_KEY_MGMT_PSK (1<<1) + +#define COMMAND_BUFFER_SIZE (CONTROL_BUFFER_SIZE + sizeof(struct rndis_set)) + +static const struct ieee80211_channel rndis_channels[] = { + { .center_freq = 2412 }, + { .center_freq = 2417 }, + { .center_freq = 2422 }, + { .center_freq = 2427 }, + { .center_freq = 2432 }, + { .center_freq = 2437 }, + { .center_freq = 2442 }, + { .center_freq = 2447 }, + { .center_freq = 2452 }, + { .center_freq = 2457 }, + { .center_freq = 2462 }, + { .center_freq = 2467 }, + { .center_freq = 2472 }, + { .center_freq = 2484 }, +}; + +static const struct ieee80211_rate rndis_rates[] = { + { .bitrate = 10 }, + { .bitrate = 20, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 55, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 110, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 60 }, + { .bitrate = 90 }, + { .bitrate = 120 }, + { .bitrate = 180 }, + { .bitrate = 240 }, + { .bitrate = 360 }, + { .bitrate = 480 }, + { .bitrate = 540 } +}; + +static const u32 rndis_cipher_suites[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, +}; + +struct rndis_wlan_encr_key { + int len; + u32 cipher; + u8 material[32]; + u8 bssid[ETH_ALEN]; + bool pairwise; + bool tx_key; +}; + +/* RNDIS device private data */ +struct rndis_wlan_private { + struct usbnet *usbdev; + + struct wireless_dev wdev; + + struct cfg80211_scan_request *scan_request; + + struct workqueue_struct *workqueue; + struct delayed_work dev_poller_work; + struct delayed_work scan_work; + struct work_struct work; + struct mutex command_lock; + unsigned long work_pending; + int last_qual; + s32 cqm_rssi_thold; + u32 cqm_rssi_hyst; + int last_cqm_event_rssi; + + struct ieee80211_supported_band band; + struct ieee80211_channel channels[ARRAY_SIZE(rndis_channels)]; + struct ieee80211_rate rates[ARRAY_SIZE(rndis_rates)]; + u32 cipher_suites[ARRAY_SIZE(rndis_cipher_suites)]; + + int device_type; + int caps; + int multicast_size; + + /* module parameters */ + char param_country[4]; + int param_frameburst; + int param_afterburner; + int param_power_save; + int param_power_output; + int param_roamtrigger; + int param_roamdelta; + u32 param_workaround_interval; + + /* hardware state */ + bool radio_on; + int power_mode; + int infra_mode; + bool connected; + u8 bssid[ETH_ALEN]; + u32 current_command_oid; + + /* encryption stuff */ + u8 encr_tx_key_index; + struct rndis_wlan_encr_key encr_keys[RNDIS_WLAN_NUM_KEYS]; + int wpa_version; + + u8 command_buffer[COMMAND_BUFFER_SIZE]; +}; + +/* + * cfg80211 ops + */ +static int rndis_change_virtual_intf(struct wiphy *wiphy, + struct net_device *dev, + enum nl80211_iftype type, u32 *flags, + struct vif_params *params); + +static int rndis_scan(struct wiphy *wiphy, + struct cfg80211_scan_request *request); + +static int rndis_set_wiphy_params(struct wiphy *wiphy, u32 changed); + +static int rndis_set_tx_power(struct wiphy *wiphy, + struct wireless_dev *wdev, + enum nl80211_tx_power_setting type, + int mbm); +static int rndis_get_tx_power(struct wiphy *wiphy, + struct wireless_dev *wdev, + int *dbm); + +static int rndis_connect(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_connect_params *sme); + +static int rndis_disconnect(struct wiphy *wiphy, struct net_device *dev, + u16 reason_code); + +static int rndis_join_ibss(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_ibss_params *params); + +static int rndis_leave_ibss(struct wiphy *wiphy, struct net_device *dev); + +static int rndis_add_key(struct wiphy *wiphy, struct net_device *netdev, + u8 key_index, bool pairwise, const u8 *mac_addr, + struct key_params *params); + +static int rndis_del_key(struct wiphy *wiphy, struct net_device *netdev, + u8 key_index, bool pairwise, const u8 *mac_addr); + +static int rndis_set_default_key(struct wiphy *wiphy, struct net_device *netdev, + u8 key_index, bool unicast, bool multicast); + +static int rndis_get_station(struct wiphy *wiphy, struct net_device *dev, + u8 *mac, struct station_info *sinfo); + +static int rndis_dump_station(struct wiphy *wiphy, struct net_device *dev, + int idx, u8 *mac, struct station_info *sinfo); + +static int rndis_set_pmksa(struct wiphy *wiphy, struct net_device *netdev, + struct cfg80211_pmksa *pmksa); + +static int rndis_del_pmksa(struct wiphy *wiphy, struct net_device *netdev, + struct cfg80211_pmksa *pmksa); + +static int rndis_flush_pmksa(struct wiphy *wiphy, struct net_device *netdev); + +static int rndis_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev, + bool enabled, int timeout); + +static int rndis_set_cqm_rssi_config(struct wiphy *wiphy, + struct net_device *dev, + s32 rssi_thold, u32 rssi_hyst); + +static const struct cfg80211_ops rndis_config_ops = { + .change_virtual_intf = rndis_change_virtual_intf, + .scan = rndis_scan, + .set_wiphy_params = rndis_set_wiphy_params, + .set_tx_power = rndis_set_tx_power, + .get_tx_power = rndis_get_tx_power, + .connect = rndis_connect, + .disconnect = rndis_disconnect, + .join_ibss = rndis_join_ibss, + .leave_ibss = rndis_leave_ibss, + .add_key = rndis_add_key, + .del_key = rndis_del_key, + .set_default_key = rndis_set_default_key, + .get_station = rndis_get_station, + .dump_station = rndis_dump_station, + .set_pmksa = rndis_set_pmksa, + .del_pmksa = rndis_del_pmksa, + .flush_pmksa = rndis_flush_pmksa, + .set_power_mgmt = rndis_set_power_mgmt, + .set_cqm_rssi_config = rndis_set_cqm_rssi_config, +}; + +static void *rndis_wiphy_privid = &rndis_wiphy_privid; + + +static struct rndis_wlan_private *get_rndis_wlan_priv(struct usbnet *dev) +{ + return (struct rndis_wlan_private *)dev->driver_priv; +} + +static u32 get_bcm4320_power_dbm(struct rndis_wlan_private *priv) +{ + switch (priv->param_power_output) { + default: + case 3: + return BCM4320_DEFAULT_TXPOWER_DBM_100; + case 2: + return BCM4320_DEFAULT_TXPOWER_DBM_75; + case 1: + return BCM4320_DEFAULT_TXPOWER_DBM_50; + case 0: + return BCM4320_DEFAULT_TXPOWER_DBM_25; + } +} + +static bool is_wpa_key(struct rndis_wlan_private *priv, u8 idx) +{ + int cipher = priv->encr_keys[idx].cipher; + + return (cipher == WLAN_CIPHER_SUITE_CCMP || + cipher == WLAN_CIPHER_SUITE_TKIP); +} + +static int rndis_cipher_to_alg(u32 cipher) +{ + switch (cipher) { + default: + return RNDIS_WLAN_ALG_NONE; + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + return RNDIS_WLAN_ALG_WEP; + case WLAN_CIPHER_SUITE_TKIP: + return RNDIS_WLAN_ALG_TKIP; + case WLAN_CIPHER_SUITE_CCMP: + return RNDIS_WLAN_ALG_CCMP; + } +} + +static int rndis_akm_suite_to_key_mgmt(u32 akm_suite) +{ + switch (akm_suite) { + default: + return RNDIS_WLAN_KEY_MGMT_NONE; + case WLAN_AKM_SUITE_8021X: + return RNDIS_WLAN_KEY_MGMT_802_1X; + case WLAN_AKM_SUITE_PSK: + return RNDIS_WLAN_KEY_MGMT_PSK; + } +} + +#ifdef DEBUG +static const char *oid_to_string(u32 oid) +{ + switch (oid) { +#define OID_STR(oid) case oid: return(#oid) + /* from rndis_host.h */ + OID_STR(RNDIS_OID_802_3_PERMANENT_ADDRESS); + OID_STR(RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE); + OID_STR(RNDIS_OID_GEN_CURRENT_PACKET_FILTER); + OID_STR(RNDIS_OID_GEN_PHYSICAL_MEDIUM); + + /* from rndis_wlan.c */ + OID_STR(RNDIS_OID_GEN_LINK_SPEED); + OID_STR(RNDIS_OID_GEN_RNDIS_CONFIG_PARAMETER); + + OID_STR(RNDIS_OID_GEN_XMIT_OK); + OID_STR(RNDIS_OID_GEN_RCV_OK); + OID_STR(RNDIS_OID_GEN_XMIT_ERROR); + OID_STR(RNDIS_OID_GEN_RCV_ERROR); + OID_STR(RNDIS_OID_GEN_RCV_NO_BUFFER); + + OID_STR(RNDIS_OID_802_3_CURRENT_ADDRESS); + OID_STR(RNDIS_OID_802_3_MULTICAST_LIST); + OID_STR(RNDIS_OID_802_3_MAXIMUM_LIST_SIZE); + + OID_STR(RNDIS_OID_802_11_BSSID); + OID_STR(RNDIS_OID_802_11_SSID); + OID_STR(RNDIS_OID_802_11_INFRASTRUCTURE_MODE); + OID_STR(RNDIS_OID_802_11_ADD_WEP); + OID_STR(RNDIS_OID_802_11_REMOVE_WEP); + OID_STR(RNDIS_OID_802_11_DISASSOCIATE); + OID_STR(RNDIS_OID_802_11_AUTHENTICATION_MODE); + OID_STR(RNDIS_OID_802_11_PRIVACY_FILTER); + OID_STR(RNDIS_OID_802_11_BSSID_LIST_SCAN); + OID_STR(RNDIS_OID_802_11_ENCRYPTION_STATUS); + OID_STR(RNDIS_OID_802_11_ADD_KEY); + OID_STR(RNDIS_OID_802_11_REMOVE_KEY); + OID_STR(RNDIS_OID_802_11_ASSOCIATION_INFORMATION); + OID_STR(RNDIS_OID_802_11_CAPABILITY); + OID_STR(RNDIS_OID_802_11_PMKID); + OID_STR(RNDIS_OID_802_11_NETWORK_TYPES_SUPPORTED); + OID_STR(RNDIS_OID_802_11_NETWORK_TYPE_IN_USE); + OID_STR(RNDIS_OID_802_11_TX_POWER_LEVEL); + OID_STR(RNDIS_OID_802_11_RSSI); + OID_STR(RNDIS_OID_802_11_RSSI_TRIGGER); + OID_STR(RNDIS_OID_802_11_FRAGMENTATION_THRESHOLD); + OID_STR(RNDIS_OID_802_11_RTS_THRESHOLD); + OID_STR(RNDIS_OID_802_11_SUPPORTED_RATES); + OID_STR(RNDIS_OID_802_11_CONFIGURATION); + OID_STR(RNDIS_OID_802_11_POWER_MODE); + OID_STR(RNDIS_OID_802_11_BSSID_LIST); +#undef OID_STR + } + + return "?"; +} +#else +static const char *oid_to_string(u32 oid) +{ + return "?"; +} +#endif + +/* translate error code */ +static int rndis_error_status(__le32 rndis_status) +{ + int ret = -EINVAL; + switch (le32_to_cpu(rndis_status)) { + case RNDIS_STATUS_SUCCESS: + ret = 0; + break; + case RNDIS_STATUS_FAILURE: + case RNDIS_STATUS_INVALID_DATA: + ret = -EINVAL; + break; + case RNDIS_STATUS_NOT_SUPPORTED: + ret = -EOPNOTSUPP; + break; + case RNDIS_STATUS_ADAPTER_NOT_READY: + case RNDIS_STATUS_ADAPTER_NOT_OPEN: + ret = -EBUSY; + break; + } + return ret; +} + +static int rndis_query_oid(struct usbnet *dev, u32 oid, void *data, int *len) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(dev); + union { + void *buf; + struct rndis_msg_hdr *header; + struct rndis_query *get; + struct rndis_query_c *get_c; + } u; + int ret, buflen; + int resplen, respoffs, copylen; + + buflen = *len + sizeof(*u.get); + if (buflen < CONTROL_BUFFER_SIZE) + buflen = CONTROL_BUFFER_SIZE; + + if (buflen > COMMAND_BUFFER_SIZE) { + u.buf = kmalloc(buflen, GFP_KERNEL); + if (!u.buf) + return -ENOMEM; + } else { + u.buf = priv->command_buffer; + } + + mutex_lock(&priv->command_lock); + + memset(u.get, 0, sizeof *u.get); + u.get->msg_type = cpu_to_le32(RNDIS_MSG_QUERY); + u.get->msg_len = cpu_to_le32(sizeof *u.get); + u.get->oid = cpu_to_le32(oid); + + priv->current_command_oid = oid; + ret = rndis_command(dev, u.header, buflen); + priv->current_command_oid = 0; + if (ret < 0) + netdev_dbg(dev->net, "%s(%s): rndis_command() failed, %d (%08x)\n", + __func__, oid_to_string(oid), ret, + le32_to_cpu(u.get_c->status)); + + if (ret == 0) { + resplen = le32_to_cpu(u.get_c->len); + respoffs = le32_to_cpu(u.get_c->offset) + 8; + + if (respoffs > buflen) { + /* Device returned data offset outside buffer, error. */ + netdev_dbg(dev->net, "%s(%s): received invalid " + "data offset: %d > %d\n", __func__, + oid_to_string(oid), respoffs, buflen); + + ret = -EINVAL; + goto exit_unlock; + } + + if ((resplen + respoffs) > buflen) { + /* Device would have returned more data if buffer would + * have been big enough. Copy just the bits that we got. + */ + copylen = buflen - respoffs; + } else { + copylen = resplen; + } + + if (copylen > *len) + copylen = *len; + + memcpy(data, u.buf + respoffs, copylen); + + *len = resplen; + + ret = rndis_error_status(u.get_c->status); + if (ret < 0) + netdev_dbg(dev->net, "%s(%s): device returned error, 0x%08x (%d)\n", + __func__, oid_to_string(oid), + le32_to_cpu(u.get_c->status), ret); + } + +exit_unlock: + mutex_unlock(&priv->command_lock); + + if (u.buf != priv->command_buffer) + kfree(u.buf); + return ret; +} + +static int rndis_set_oid(struct usbnet *dev, u32 oid, const void *data, + int len) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(dev); + union { + void *buf; + struct rndis_msg_hdr *header; + struct rndis_set *set; + struct rndis_set_c *set_c; + } u; + int ret, buflen; + + buflen = len + sizeof(*u.set); + if (buflen < CONTROL_BUFFER_SIZE) + buflen = CONTROL_BUFFER_SIZE; + + if (buflen > COMMAND_BUFFER_SIZE) { + u.buf = kmalloc(buflen, GFP_KERNEL); + if (!u.buf) + return -ENOMEM; + } else { + u.buf = priv->command_buffer; + } + + mutex_lock(&priv->command_lock); + + memset(u.set, 0, sizeof *u.set); + u.set->msg_type = cpu_to_le32(RNDIS_MSG_SET); + u.set->msg_len = cpu_to_le32(sizeof(*u.set) + len); + u.set->oid = cpu_to_le32(oid); + u.set->len = cpu_to_le32(len); + u.set->offset = cpu_to_le32(sizeof(*u.set) - 8); + u.set->handle = cpu_to_le32(0); + memcpy(u.buf + sizeof(*u.set), data, len); + + priv->current_command_oid = oid; + ret = rndis_command(dev, u.header, buflen); + priv->current_command_oid = 0; + if (ret < 0) + netdev_dbg(dev->net, "%s(%s): rndis_command() failed, %d (%08x)\n", + __func__, oid_to_string(oid), ret, + le32_to_cpu(u.set_c->status)); + + if (ret == 0) { + ret = rndis_error_status(u.set_c->status); + + if (ret < 0) + netdev_dbg(dev->net, "%s(%s): device returned error, 0x%08x (%d)\n", + __func__, oid_to_string(oid), + le32_to_cpu(u.set_c->status), ret); + } + + mutex_unlock(&priv->command_lock); + + if (u.buf != priv->command_buffer) + kfree(u.buf); + return ret; +} + +static int rndis_reset(struct usbnet *usbdev) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + struct rndis_reset *reset; + int ret; + + mutex_lock(&priv->command_lock); + + reset = (void *)priv->command_buffer; + memset(reset, 0, sizeof(*reset)); + reset->msg_type = cpu_to_le32(RNDIS_MSG_RESET); + reset->msg_len = cpu_to_le32(sizeof(*reset)); + priv->current_command_oid = 0; + ret = rndis_command(usbdev, (void *)reset, CONTROL_BUFFER_SIZE); + + mutex_unlock(&priv->command_lock); + + if (ret < 0) + return ret; + return 0; +} + +/* + * Specs say that we can only set config parameters only soon after device + * initialization. + * value_type: 0 = u32, 2 = unicode string + */ +static int rndis_set_config_parameter(struct usbnet *dev, char *param, + int value_type, void *value) +{ + struct ndis_config_param *infobuf; + int value_len, info_len, param_len, ret, i; + __le16 *unibuf; + __le32 *dst_value; + + if (value_type == 0) + value_len = sizeof(__le32); + else if (value_type == 2) + value_len = strlen(value) * sizeof(__le16); + else + return -EINVAL; + + param_len = strlen(param) * sizeof(__le16); + info_len = sizeof(*infobuf) + param_len + value_len; + +#ifdef DEBUG + info_len += 12; +#endif + infobuf = kmalloc(info_len, GFP_KERNEL); + if (!infobuf) + return -ENOMEM; + +#ifdef DEBUG + info_len -= 12; + /* extra 12 bytes are for padding (debug output) */ + memset(infobuf, 0xCC, info_len + 12); +#endif + + if (value_type == 2) + netdev_dbg(dev->net, "setting config parameter: %s, value: %s\n", + param, (u8 *)value); + else + netdev_dbg(dev->net, "setting config parameter: %s, value: %d\n", + param, *(u32 *)value); + + infobuf->name_offs = cpu_to_le32(sizeof(*infobuf)); + infobuf->name_length = cpu_to_le32(param_len); + infobuf->type = cpu_to_le32(value_type); + infobuf->value_offs = cpu_to_le32(sizeof(*infobuf) + param_len); + infobuf->value_length = cpu_to_le32(value_len); + + /* simple string to unicode string conversion */ + unibuf = (void *)infobuf + sizeof(*infobuf); + for (i = 0; i < param_len / sizeof(__le16); i++) + unibuf[i] = cpu_to_le16(param[i]); + + if (value_type == 2) { + unibuf = (void *)infobuf + sizeof(*infobuf) + param_len; + for (i = 0; i < value_len / sizeof(__le16); i++) + unibuf[i] = cpu_to_le16(((u8 *)value)[i]); + } else { + dst_value = (void *)infobuf + sizeof(*infobuf) + param_len; + *dst_value = cpu_to_le32(*(u32 *)value); + } + +#ifdef DEBUG + netdev_dbg(dev->net, "info buffer (len: %d)\n", info_len); + for (i = 0; i < info_len; i += 12) { + u32 *tmp = (u32 *)((u8 *)infobuf + i); + netdev_dbg(dev->net, "%08X:%08X:%08X\n", + cpu_to_be32(tmp[0]), + cpu_to_be32(tmp[1]), + cpu_to_be32(tmp[2])); + } +#endif + + ret = rndis_set_oid(dev, RNDIS_OID_GEN_RNDIS_CONFIG_PARAMETER, + infobuf, info_len); + if (ret != 0) + netdev_dbg(dev->net, "setting rndis config parameter failed, %d\n", + ret); + + kfree(infobuf); + return ret; +} + +static int rndis_set_config_parameter_str(struct usbnet *dev, + char *param, char *value) +{ + return rndis_set_config_parameter(dev, param, 2, value); +} + +/* + * data conversion functions + */ +static int level_to_qual(int level) +{ + int qual = 100 * (level - WL_NOISE) / (WL_SIGMAX - WL_NOISE); + return qual >= 0 ? (qual <= 100 ? qual : 100) : 0; +} + +/* + * common functions + */ +static int set_infra_mode(struct usbnet *usbdev, int mode); +static void restore_keys(struct usbnet *usbdev); +static int rndis_check_bssid_list(struct usbnet *usbdev, u8 *match_bssid, + bool *matched); + +static int rndis_start_bssid_list_scan(struct usbnet *usbdev) +{ + __le32 tmp; + + /* Note: RNDIS_OID_802_11_BSSID_LIST_SCAN clears internal BSS list. */ + tmp = cpu_to_le32(1); + return rndis_set_oid(usbdev, RNDIS_OID_802_11_BSSID_LIST_SCAN, &tmp, + sizeof(tmp)); +} + +static int set_essid(struct usbnet *usbdev, struct ndis_80211_ssid *ssid) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + int ret; + + ret = rndis_set_oid(usbdev, RNDIS_OID_802_11_SSID, + ssid, sizeof(*ssid)); + if (ret < 0) { + netdev_warn(usbdev->net, "setting SSID failed (%08X)\n", ret); + return ret; + } + if (ret == 0) { + priv->radio_on = true; + netdev_dbg(usbdev->net, "%s(): radio_on = true\n", __func__); + } + + return ret; +} + +static int set_bssid(struct usbnet *usbdev, const u8 *bssid) +{ + int ret; + + ret = rndis_set_oid(usbdev, RNDIS_OID_802_11_BSSID, + bssid, ETH_ALEN); + if (ret < 0) { + netdev_warn(usbdev->net, "setting BSSID[%pM] failed (%08X)\n", + bssid, ret); + return ret; + } + + return ret; +} + +static int clear_bssid(struct usbnet *usbdev) +{ + static const u8 broadcast_mac[ETH_ALEN] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + return set_bssid(usbdev, broadcast_mac); +} + +static int get_bssid(struct usbnet *usbdev, u8 bssid[ETH_ALEN]) +{ + int ret, len; + + len = ETH_ALEN; + ret = rndis_query_oid(usbdev, RNDIS_OID_802_11_BSSID, + bssid, &len); + + if (ret != 0) + memset(bssid, 0, ETH_ALEN); + + return ret; +} + +static int get_association_info(struct usbnet *usbdev, + struct ndis_80211_assoc_info *info, int len) +{ + return rndis_query_oid(usbdev, + RNDIS_OID_802_11_ASSOCIATION_INFORMATION, + info, &len); +} + +static bool is_associated(struct usbnet *usbdev) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + u8 bssid[ETH_ALEN]; + int ret; + + if (!priv->radio_on) + return false; + + ret = get_bssid(usbdev, bssid); + + return (ret == 0 && !is_zero_ether_addr(bssid)); +} + +static int disassociate(struct usbnet *usbdev, bool reset_ssid) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + struct ndis_80211_ssid ssid; + int i, ret = 0; + + if (priv->radio_on) { + ret = rndis_set_oid(usbdev, + RNDIS_OID_802_11_DISASSOCIATE, + NULL, 0); + if (ret == 0) { + priv->radio_on = false; + netdev_dbg(usbdev->net, "%s(): radio_on = false\n", + __func__); + + if (reset_ssid) + msleep(100); + } + } + + /* disassociate causes radio to be turned off; if reset_ssid + * is given, set random ssid to enable radio */ + if (reset_ssid) { + /* Set device to infrastructure mode so we don't get ad-hoc + * 'media connect' indications with the random ssid. + */ + set_infra_mode(usbdev, NDIS_80211_INFRA_INFRA); + + ssid.length = cpu_to_le32(sizeof(ssid.essid)); + get_random_bytes(&ssid.essid[2], sizeof(ssid.essid)-2); + ssid.essid[0] = 0x1; + ssid.essid[1] = 0xff; + for (i = 2; i < sizeof(ssid.essid); i++) + ssid.essid[i] = 0x1 + (ssid.essid[i] * 0xfe / 0xff); + ret = set_essid(usbdev, &ssid); + } + return ret; +} + +static int set_auth_mode(struct usbnet *usbdev, u32 wpa_version, + enum nl80211_auth_type auth_type, int keymgmt) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + __le32 tmp; + int auth_mode, ret; + + netdev_dbg(usbdev->net, "%s(): wpa_version=0x%x authalg=0x%x keymgmt=0x%x\n", + __func__, wpa_version, auth_type, keymgmt); + + if (wpa_version & NL80211_WPA_VERSION_2) { + if (keymgmt & RNDIS_WLAN_KEY_MGMT_802_1X) + auth_mode = NDIS_80211_AUTH_WPA2; + else + auth_mode = NDIS_80211_AUTH_WPA2_PSK; + } else if (wpa_version & NL80211_WPA_VERSION_1) { + if (keymgmt & RNDIS_WLAN_KEY_MGMT_802_1X) + auth_mode = NDIS_80211_AUTH_WPA; + else if (keymgmt & RNDIS_WLAN_KEY_MGMT_PSK) + auth_mode = NDIS_80211_AUTH_WPA_PSK; + else + auth_mode = NDIS_80211_AUTH_WPA_NONE; + } else if (auth_type == NL80211_AUTHTYPE_SHARED_KEY) + auth_mode = NDIS_80211_AUTH_SHARED; + else if (auth_type == NL80211_AUTHTYPE_OPEN_SYSTEM) + auth_mode = NDIS_80211_AUTH_OPEN; + else if (auth_type == NL80211_AUTHTYPE_AUTOMATIC) + auth_mode = NDIS_80211_AUTH_AUTO_SWITCH; + else + return -ENOTSUPP; + + tmp = cpu_to_le32(auth_mode); + ret = rndis_set_oid(usbdev, + RNDIS_OID_802_11_AUTHENTICATION_MODE, + &tmp, sizeof(tmp)); + if (ret != 0) { + netdev_warn(usbdev->net, "setting auth mode failed (%08X)\n", + ret); + return ret; + } + + priv->wpa_version = wpa_version; + + return 0; +} + +static int set_priv_filter(struct usbnet *usbdev) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + __le32 tmp; + + netdev_dbg(usbdev->net, "%s(): wpa_version=0x%x\n", + __func__, priv->wpa_version); + + if (priv->wpa_version & NL80211_WPA_VERSION_2 || + priv->wpa_version & NL80211_WPA_VERSION_1) + tmp = cpu_to_le32(NDIS_80211_PRIV_8021X_WEP); + else + tmp = cpu_to_le32(NDIS_80211_PRIV_ACCEPT_ALL); + + return rndis_set_oid(usbdev, + RNDIS_OID_802_11_PRIVACY_FILTER, &tmp, + sizeof(tmp)); +} + +static int set_encr_mode(struct usbnet *usbdev, int pairwise, int groupwise) +{ + __le32 tmp; + int encr_mode, ret; + + netdev_dbg(usbdev->net, "%s(): cipher_pair=0x%x cipher_group=0x%x\n", + __func__, pairwise, groupwise); + + if (pairwise & RNDIS_WLAN_ALG_CCMP) + encr_mode = NDIS_80211_ENCR_CCMP_ENABLED; + else if (pairwise & RNDIS_WLAN_ALG_TKIP) + encr_mode = NDIS_80211_ENCR_TKIP_ENABLED; + else if (pairwise & RNDIS_WLAN_ALG_WEP) + encr_mode = NDIS_80211_ENCR_WEP_ENABLED; + else if (groupwise & RNDIS_WLAN_ALG_CCMP) + encr_mode = NDIS_80211_ENCR_CCMP_ENABLED; + else if (groupwise & RNDIS_WLAN_ALG_TKIP) + encr_mode = NDIS_80211_ENCR_TKIP_ENABLED; + else + encr_mode = NDIS_80211_ENCR_DISABLED; + + tmp = cpu_to_le32(encr_mode); + ret = rndis_set_oid(usbdev, + RNDIS_OID_802_11_ENCRYPTION_STATUS, &tmp, + sizeof(tmp)); + if (ret != 0) { + netdev_warn(usbdev->net, "setting encr mode failed (%08X)\n", + ret); + return ret; + } + + return 0; +} + +static int set_infra_mode(struct usbnet *usbdev, int mode) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + __le32 tmp; + int ret; + + netdev_dbg(usbdev->net, "%s(): infra_mode=0x%x\n", + __func__, priv->infra_mode); + + tmp = cpu_to_le32(mode); + ret = rndis_set_oid(usbdev, + RNDIS_OID_802_11_INFRASTRUCTURE_MODE, + &tmp, sizeof(tmp)); + if (ret != 0) { + netdev_warn(usbdev->net, "setting infra mode failed (%08X)\n", + ret); + return ret; + } + + /* NDIS drivers clear keys when infrastructure mode is + * changed. But Linux tools assume otherwise. So set the + * keys */ + restore_keys(usbdev); + + priv->infra_mode = mode; + return 0; +} + +static int set_rts_threshold(struct usbnet *usbdev, u32 rts_threshold) +{ + __le32 tmp; + + netdev_dbg(usbdev->net, "%s(): %i\n", __func__, rts_threshold); + + if (rts_threshold < 0 || rts_threshold > 2347) + rts_threshold = 2347; + + tmp = cpu_to_le32(rts_threshold); + return rndis_set_oid(usbdev, + RNDIS_OID_802_11_RTS_THRESHOLD, + &tmp, sizeof(tmp)); +} + +static int set_frag_threshold(struct usbnet *usbdev, u32 frag_threshold) +{ + __le32 tmp; + + netdev_dbg(usbdev->net, "%s(): %i\n", __func__, frag_threshold); + + if (frag_threshold < 256 || frag_threshold > 2346) + frag_threshold = 2346; + + tmp = cpu_to_le32(frag_threshold); + return rndis_set_oid(usbdev, + RNDIS_OID_802_11_FRAGMENTATION_THRESHOLD, + &tmp, sizeof(tmp)); +} + +static void set_default_iw_params(struct usbnet *usbdev) +{ + set_infra_mode(usbdev, NDIS_80211_INFRA_INFRA); + set_auth_mode(usbdev, 0, NL80211_AUTHTYPE_OPEN_SYSTEM, + RNDIS_WLAN_KEY_MGMT_NONE); + set_priv_filter(usbdev); + set_encr_mode(usbdev, RNDIS_WLAN_ALG_NONE, RNDIS_WLAN_ALG_NONE); +} + +static int deauthenticate(struct usbnet *usbdev) +{ + int ret; + + ret = disassociate(usbdev, true); + set_default_iw_params(usbdev); + return ret; +} + +static int set_channel(struct usbnet *usbdev, int channel) +{ + struct ndis_80211_conf config; + unsigned int dsconfig; + int len, ret; + + netdev_dbg(usbdev->net, "%s(%d)\n", __func__, channel); + + /* this OID is valid only when not associated */ + if (is_associated(usbdev)) + return 0; + + dsconfig = ieee80211_dsss_chan_to_freq(channel) * 1000; + + len = sizeof(config); + ret = rndis_query_oid(usbdev, + RNDIS_OID_802_11_CONFIGURATION, + &config, &len); + if (ret < 0) { + netdev_dbg(usbdev->net, "%s(): querying configuration failed\n", + __func__); + return ret; + } + + config.ds_config = cpu_to_le32(dsconfig); + ret = rndis_set_oid(usbdev, + RNDIS_OID_802_11_CONFIGURATION, + &config, sizeof(config)); + + netdev_dbg(usbdev->net, "%s(): %d -> %d\n", __func__, channel, ret); + + return ret; +} + +static struct ieee80211_channel *get_current_channel(struct usbnet *usbdev, + u32 *beacon_period) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + struct ieee80211_channel *channel; + struct ndis_80211_conf config; + int len, ret; + + /* Get channel and beacon interval */ + len = sizeof(config); + ret = rndis_query_oid(usbdev, + RNDIS_OID_802_11_CONFIGURATION, + &config, &len); + netdev_dbg(usbdev->net, "%s(): RNDIS_OID_802_11_CONFIGURATION -> %d\n", + __func__, ret); + if (ret < 0) + return NULL; + + channel = ieee80211_get_channel(priv->wdev.wiphy, + KHZ_TO_MHZ(le32_to_cpu(config.ds_config))); + if (!channel) + return NULL; + + if (beacon_period) + *beacon_period = le32_to_cpu(config.beacon_period); + return channel; +} + +/* index must be 0 - N, as per NDIS */ +static int add_wep_key(struct usbnet *usbdev, const u8 *key, int key_len, + u8 index) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + struct ndis_80211_wep_key ndis_key; + u32 cipher; + int ret; + + netdev_dbg(usbdev->net, "%s(idx: %d, len: %d)\n", + __func__, index, key_len); + + if (index >= RNDIS_WLAN_NUM_KEYS) + return -EINVAL; + + if (key_len == 5) + cipher = WLAN_CIPHER_SUITE_WEP40; + else if (key_len == 13) + cipher = WLAN_CIPHER_SUITE_WEP104; + else + return -EINVAL; + + memset(&ndis_key, 0, sizeof(ndis_key)); + + ndis_key.size = cpu_to_le32(sizeof(ndis_key)); + ndis_key.length = cpu_to_le32(key_len); + ndis_key.index = cpu_to_le32(index); + memcpy(&ndis_key.material, key, key_len); + + if (index == priv->encr_tx_key_index) { + ndis_key.index |= NDIS_80211_ADDWEP_TRANSMIT_KEY; + ret = set_encr_mode(usbdev, RNDIS_WLAN_ALG_WEP, + RNDIS_WLAN_ALG_NONE); + if (ret) + netdev_warn(usbdev->net, "encryption couldn't be enabled (%08X)\n", + ret); + } + + ret = rndis_set_oid(usbdev, + RNDIS_OID_802_11_ADD_WEP, &ndis_key, + sizeof(ndis_key)); + if (ret != 0) { + netdev_warn(usbdev->net, "adding encryption key %d failed (%08X)\n", + index + 1, ret); + return ret; + } + + priv->encr_keys[index].len = key_len; + priv->encr_keys[index].cipher = cipher; + memcpy(&priv->encr_keys[index].material, key, key_len); + memset(&priv->encr_keys[index].bssid, 0xff, ETH_ALEN); + + return 0; +} + +static int add_wpa_key(struct usbnet *usbdev, const u8 *key, int key_len, + u8 index, const u8 *addr, const u8 *rx_seq, + int seq_len, u32 cipher, __le32 flags) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + struct ndis_80211_key ndis_key; + bool is_addr_ok; + int ret; + + if (index >= RNDIS_WLAN_NUM_KEYS) { + netdev_dbg(usbdev->net, "%s(): index out of range (%i)\n", + __func__, index); + return -EINVAL; + } + if (key_len > sizeof(ndis_key.material) || key_len < 0) { + netdev_dbg(usbdev->net, "%s(): key length out of range (%i)\n", + __func__, key_len); + return -EINVAL; + } + if (flags & NDIS_80211_ADDKEY_SET_INIT_RECV_SEQ) { + if (!rx_seq || seq_len <= 0) { + netdev_dbg(usbdev->net, "%s(): recv seq flag without buffer\n", + __func__); + return -EINVAL; + } + if (rx_seq && seq_len > sizeof(ndis_key.rsc)) { + netdev_dbg(usbdev->net, "%s(): too big recv seq buffer\n", __func__); + return -EINVAL; + } + } + + is_addr_ok = addr && !is_zero_ether_addr(addr) && + !is_broadcast_ether_addr(addr); + if ((flags & NDIS_80211_ADDKEY_PAIRWISE_KEY) && !is_addr_ok) { + netdev_dbg(usbdev->net, "%s(): pairwise but bssid invalid (%pM)\n", + __func__, addr); + return -EINVAL; + } + + netdev_dbg(usbdev->net, "%s(%i): flags:%i%i%i\n", + __func__, index, + !!(flags & NDIS_80211_ADDKEY_TRANSMIT_KEY), + !!(flags & NDIS_80211_ADDKEY_PAIRWISE_KEY), + !!(flags & NDIS_80211_ADDKEY_SET_INIT_RECV_SEQ)); + + memset(&ndis_key, 0, sizeof(ndis_key)); + + ndis_key.size = cpu_to_le32(sizeof(ndis_key) - + sizeof(ndis_key.material) + key_len); + ndis_key.length = cpu_to_le32(key_len); + ndis_key.index = cpu_to_le32(index) | flags; + + if (cipher == WLAN_CIPHER_SUITE_TKIP && key_len == 32) { + /* wpa_supplicant gives us the Michael MIC RX/TX keys in + * different order than NDIS spec, so swap the order here. */ + memcpy(ndis_key.material, key, 16); + memcpy(ndis_key.material + 16, key + 24, 8); + memcpy(ndis_key.material + 24, key + 16, 8); + } else + memcpy(ndis_key.material, key, key_len); + + if (flags & NDIS_80211_ADDKEY_SET_INIT_RECV_SEQ) + memcpy(ndis_key.rsc, rx_seq, seq_len); + + if (flags & NDIS_80211_ADDKEY_PAIRWISE_KEY) { + /* pairwise key */ + memcpy(ndis_key.bssid, addr, ETH_ALEN); + } else { + /* group key */ + if (priv->infra_mode == NDIS_80211_INFRA_ADHOC) + memset(ndis_key.bssid, 0xff, ETH_ALEN); + else + get_bssid(usbdev, ndis_key.bssid); + } + + ret = rndis_set_oid(usbdev, + RNDIS_OID_802_11_ADD_KEY, &ndis_key, + le32_to_cpu(ndis_key.size)); + netdev_dbg(usbdev->net, "%s(): RNDIS_OID_802_11_ADD_KEY -> %08X\n", + __func__, ret); + if (ret != 0) + return ret; + + memset(&priv->encr_keys[index], 0, sizeof(priv->encr_keys[index])); + priv->encr_keys[index].len = key_len; + priv->encr_keys[index].cipher = cipher; + memcpy(&priv->encr_keys[index].material, key, key_len); + if (flags & NDIS_80211_ADDKEY_PAIRWISE_KEY) + memcpy(&priv->encr_keys[index].bssid, ndis_key.bssid, ETH_ALEN); + else + memset(&priv->encr_keys[index].bssid, 0xff, ETH_ALEN); + + if (flags & NDIS_80211_ADDKEY_TRANSMIT_KEY) + priv->encr_tx_key_index = index; + + return 0; +} + +static int restore_key(struct usbnet *usbdev, u8 key_idx) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + struct rndis_wlan_encr_key key; + + if (is_wpa_key(priv, key_idx)) + return 0; + + key = priv->encr_keys[key_idx]; + + netdev_dbg(usbdev->net, "%s(): %i:%i\n", __func__, key_idx, key.len); + + if (key.len == 0) + return 0; + + return add_wep_key(usbdev, key.material, key.len, key_idx); +} + +static void restore_keys(struct usbnet *usbdev) +{ + int i; + + for (i = 0; i < 4; i++) + restore_key(usbdev, i); +} + +static void clear_key(struct rndis_wlan_private *priv, u8 idx) +{ + memset(&priv->encr_keys[idx], 0, sizeof(priv->encr_keys[idx])); +} + +/* remove_key is for both wep and wpa */ +static int remove_key(struct usbnet *usbdev, u8 index, const u8 *bssid) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + struct ndis_80211_remove_key remove_key; + __le32 keyindex; + bool is_wpa; + int ret; + + if (index >= RNDIS_WLAN_NUM_KEYS) + return -ENOENT; + + if (priv->encr_keys[index].len == 0) + return 0; + + is_wpa = is_wpa_key(priv, index); + + netdev_dbg(usbdev->net, "%s(): %i:%s:%i\n", + __func__, index, is_wpa ? "wpa" : "wep", + priv->encr_keys[index].len); + + clear_key(priv, index); + + if (is_wpa) { + remove_key.size = cpu_to_le32(sizeof(remove_key)); + remove_key.index = cpu_to_le32(index); + if (bssid) { + /* pairwise key */ + if (!is_broadcast_ether_addr(bssid)) + remove_key.index |= + NDIS_80211_ADDKEY_PAIRWISE_KEY; + memcpy(remove_key.bssid, bssid, + sizeof(remove_key.bssid)); + } else + memset(remove_key.bssid, 0xff, + sizeof(remove_key.bssid)); + + ret = rndis_set_oid(usbdev, + RNDIS_OID_802_11_REMOVE_KEY, + &remove_key, sizeof(remove_key)); + if (ret != 0) + return ret; + } else { + keyindex = cpu_to_le32(index); + ret = rndis_set_oid(usbdev, + RNDIS_OID_802_11_REMOVE_WEP, + &keyindex, sizeof(keyindex)); + if (ret != 0) { + netdev_warn(usbdev->net, + "removing encryption key %d failed (%08X)\n", + index, ret); + return ret; + } + } + + /* if it is transmit key, disable encryption */ + if (index == priv->encr_tx_key_index) + set_encr_mode(usbdev, RNDIS_WLAN_ALG_NONE, RNDIS_WLAN_ALG_NONE); + + return 0; +} + +static void set_multicast_list(struct usbnet *usbdev) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + struct netdev_hw_addr *ha; + __le32 filter, basefilter; + int ret; + char *mc_addrs = NULL; + int mc_count; + + basefilter = filter = cpu_to_le32(RNDIS_PACKET_TYPE_DIRECTED | + RNDIS_PACKET_TYPE_BROADCAST); + + if (usbdev->net->flags & IFF_PROMISC) { + filter |= cpu_to_le32(RNDIS_PACKET_TYPE_PROMISCUOUS | + RNDIS_PACKET_TYPE_ALL_LOCAL); + } else if (usbdev->net->flags & IFF_ALLMULTI) { + filter |= cpu_to_le32(RNDIS_PACKET_TYPE_ALL_MULTICAST); + } + + if (filter != basefilter) + goto set_filter; + + /* + * mc_list should be accessed holding the lock, so copy addresses to + * local buffer first. + */ + netif_addr_lock_bh(usbdev->net); + mc_count = netdev_mc_count(usbdev->net); + if (mc_count > priv->multicast_size) { + filter |= cpu_to_le32(RNDIS_PACKET_TYPE_ALL_MULTICAST); + } else if (mc_count) { + int i = 0; + + mc_addrs = kmalloc_array(mc_count, ETH_ALEN, GFP_ATOMIC); + if (!mc_addrs) { + netif_addr_unlock_bh(usbdev->net); + return; + } + + netdev_for_each_mc_addr(ha, usbdev->net) + memcpy(mc_addrs + i++ * ETH_ALEN, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + ha->addr, ETH_ALEN); +#else + ha->dmi_addr, ETH_ALEN); +#endif + } + netif_addr_unlock_bh(usbdev->net); + + if (filter != basefilter) + goto set_filter; + + if (mc_count) { + ret = rndis_set_oid(usbdev, + RNDIS_OID_802_3_MULTICAST_LIST, + mc_addrs, mc_count * ETH_ALEN); + kfree(mc_addrs); + if (ret == 0) + filter |= cpu_to_le32(RNDIS_PACKET_TYPE_MULTICAST); + else + filter |= cpu_to_le32(RNDIS_PACKET_TYPE_ALL_MULTICAST); + + netdev_dbg(usbdev->net, "RNDIS_OID_802_3_MULTICAST_LIST(%d, max: %d) -> %d\n", + mc_count, priv->multicast_size, ret); + } + +set_filter: + ret = rndis_set_oid(usbdev, RNDIS_OID_GEN_CURRENT_PACKET_FILTER, &filter, + sizeof(filter)); + if (ret < 0) { + netdev_warn(usbdev->net, "couldn't set packet filter: %08x\n", + le32_to_cpu(filter)); + } + + netdev_dbg(usbdev->net, "RNDIS_OID_GEN_CURRENT_PACKET_FILTER(%08x) -> %d\n", + le32_to_cpu(filter), ret); +} + +#ifdef DEBUG +static void debug_print_pmkids(struct usbnet *usbdev, + struct ndis_80211_pmkid *pmkids, + const char *func_str) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + int i, len, count, max_pmkids, entry_len; + + max_pmkids = priv->wdev.wiphy->max_num_pmkids; + len = le32_to_cpu(pmkids->length); + count = le32_to_cpu(pmkids->bssid_info_count); + + entry_len = (count > 0) ? (len - sizeof(*pmkids)) / count : -1; + + netdev_dbg(usbdev->net, "%s(): %d PMKIDs (data len: %d, entry len: " + "%d)\n", func_str, count, len, entry_len); + + if (count > max_pmkids) + count = max_pmkids; + + for (i = 0; i < count; i++) { + u32 *tmp = (u32 *)pmkids->bssid_info[i].pmkid; + + netdev_dbg(usbdev->net, "%s(): bssid: %pM, " + "pmkid: %08X:%08X:%08X:%08X\n", + func_str, pmkids->bssid_info[i].bssid, + cpu_to_be32(tmp[0]), cpu_to_be32(tmp[1]), + cpu_to_be32(tmp[2]), cpu_to_be32(tmp[3])); + } +} +#else +static void debug_print_pmkids(struct usbnet *usbdev, + struct ndis_80211_pmkid *pmkids, + const char *func_str) +{ + return; +} +#endif + +static struct ndis_80211_pmkid *get_device_pmkids(struct usbnet *usbdev) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + struct ndis_80211_pmkid *pmkids; + int len, ret, max_pmkids; + + max_pmkids = priv->wdev.wiphy->max_num_pmkids; + len = sizeof(*pmkids) + max_pmkids * sizeof(pmkids->bssid_info[0]); + + pmkids = kzalloc(len, GFP_KERNEL); + if (!pmkids) + return ERR_PTR(-ENOMEM); + + pmkids->length = cpu_to_le32(len); + pmkids->bssid_info_count = cpu_to_le32(max_pmkids); + + ret = rndis_query_oid(usbdev, RNDIS_OID_802_11_PMKID, + pmkids, &len); + if (ret < 0) { + netdev_dbg(usbdev->net, "%s(): RNDIS_OID_802_11_PMKID(%d, %d)" + " -> %d\n", __func__, len, max_pmkids, ret); + + kfree(pmkids); + return ERR_PTR(ret); + } + + if (le32_to_cpu(pmkids->bssid_info_count) > max_pmkids) + pmkids->bssid_info_count = cpu_to_le32(max_pmkids); + + debug_print_pmkids(usbdev, pmkids, __func__); + + return pmkids; +} + +static int set_device_pmkids(struct usbnet *usbdev, + struct ndis_80211_pmkid *pmkids) +{ + int ret, len, num_pmkids; + + num_pmkids = le32_to_cpu(pmkids->bssid_info_count); + len = sizeof(*pmkids) + num_pmkids * sizeof(pmkids->bssid_info[0]); + pmkids->length = cpu_to_le32(len); + + debug_print_pmkids(usbdev, pmkids, __func__); + + ret = rndis_set_oid(usbdev, RNDIS_OID_802_11_PMKID, pmkids, + le32_to_cpu(pmkids->length)); + if (ret < 0) { + netdev_dbg(usbdev->net, "%s(): RNDIS_OID_802_11_PMKID(%d, %d) -> %d" + "\n", __func__, len, num_pmkids, ret); + } + + kfree(pmkids); + return ret; +} + +static struct ndis_80211_pmkid *remove_pmkid(struct usbnet *usbdev, + struct ndis_80211_pmkid *pmkids, + struct cfg80211_pmksa *pmksa, + int max_pmkids) +{ + int i, newlen, err; + unsigned int count; + + count = le32_to_cpu(pmkids->bssid_info_count); + + if (count > max_pmkids) + count = max_pmkids; + + for (i = 0; i < count; i++) + if (ether_addr_equal(pmkids->bssid_info[i].bssid, + pmksa->bssid)) + break; + + /* pmkid not found */ + if (i == count) { + netdev_dbg(usbdev->net, "%s(): bssid not found (%pM)\n", + __func__, pmksa->bssid); + err = -ENOENT; + goto error; + } + + for (; i + 1 < count; i++) + pmkids->bssid_info[i] = pmkids->bssid_info[i + 1]; + + count--; + newlen = sizeof(*pmkids) + count * sizeof(pmkids->bssid_info[0]); + + pmkids->length = cpu_to_le32(newlen); + pmkids->bssid_info_count = cpu_to_le32(count); + + return pmkids; +error: + kfree(pmkids); + return ERR_PTR(err); +} + +static struct ndis_80211_pmkid *update_pmkid(struct usbnet *usbdev, + struct ndis_80211_pmkid *pmkids, + struct cfg80211_pmksa *pmksa, + int max_pmkids) +{ + struct ndis_80211_pmkid *new_pmkids; + int i, err, newlen; + unsigned int count; + + count = le32_to_cpu(pmkids->bssid_info_count); + + if (count > max_pmkids) + count = max_pmkids; + + /* update with new pmkid */ + for (i = 0; i < count; i++) { + if (!ether_addr_equal(pmkids->bssid_info[i].bssid, + pmksa->bssid)) + continue; + + memcpy(pmkids->bssid_info[i].pmkid, pmksa->pmkid, + WLAN_PMKID_LEN); + + return pmkids; + } + + /* out of space, return error */ + if (i == max_pmkids) { + netdev_dbg(usbdev->net, "%s(): out of space\n", __func__); + err = -ENOSPC; + goto error; + } + + /* add new pmkid */ + newlen = sizeof(*pmkids) + (count + 1) * sizeof(pmkids->bssid_info[0]); + + new_pmkids = krealloc(pmkids, newlen, GFP_KERNEL); + if (!new_pmkids) { + err = -ENOMEM; + goto error; + } + pmkids = new_pmkids; + + pmkids->length = cpu_to_le32(newlen); + pmkids->bssid_info_count = cpu_to_le32(count + 1); + + memcpy(pmkids->bssid_info[count].bssid, pmksa->bssid, ETH_ALEN); + memcpy(pmkids->bssid_info[count].pmkid, pmksa->pmkid, WLAN_PMKID_LEN); + + return pmkids; +error: + kfree(pmkids); + return ERR_PTR(err); +} + +/* + * cfg80211 ops + */ +static int rndis_change_virtual_intf(struct wiphy *wiphy, + struct net_device *dev, + enum nl80211_iftype type, u32 *flags, + struct vif_params *params) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + int mode; + + switch (type) { + case NL80211_IFTYPE_ADHOC: + mode = NDIS_80211_INFRA_ADHOC; + break; + case NL80211_IFTYPE_STATION: + mode = NDIS_80211_INFRA_INFRA; + break; + default: + return -EINVAL; + } + + priv->wdev.iftype = type; + + return set_infra_mode(usbdev, mode); +} + +static int rndis_set_wiphy_params(struct wiphy *wiphy, u32 changed) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + int err; + + if (changed & WIPHY_PARAM_FRAG_THRESHOLD) { + err = set_frag_threshold(usbdev, wiphy->frag_threshold); + if (err < 0) + return err; + } + + if (changed & WIPHY_PARAM_RTS_THRESHOLD) { + err = set_rts_threshold(usbdev, wiphy->rts_threshold); + if (err < 0) + return err; + } + + return 0; +} + +static int rndis_set_tx_power(struct wiphy *wiphy, + struct wireless_dev *wdev, + enum nl80211_tx_power_setting type, + int mbm) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + + netdev_dbg(usbdev->net, "%s(): type:0x%x mbm:%i\n", + __func__, type, mbm); + + if (mbm < 0 || (mbm % 100)) + return -ENOTSUPP; + + /* Device doesn't support changing txpower after initialization, only + * turn off/on radio. Support 'auto' mode and setting same dBm that is + * currently used. + */ + if (type == NL80211_TX_POWER_AUTOMATIC || + MBM_TO_DBM(mbm) == get_bcm4320_power_dbm(priv)) { + if (!priv->radio_on) + disassociate(usbdev, true); /* turn on radio */ + + return 0; + } + + return -ENOTSUPP; +} + +static int rndis_get_tx_power(struct wiphy *wiphy, + struct wireless_dev *wdev, + int *dbm) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + + *dbm = get_bcm4320_power_dbm(priv); + + netdev_dbg(usbdev->net, "%s(): dbm:%i\n", __func__, *dbm); + + return 0; +} + +#define SCAN_DELAY_JIFFIES (6 * HZ) +static int rndis_scan(struct wiphy *wiphy, + struct cfg80211_scan_request *request) +{ + struct net_device *dev = request->wdev->netdev; + struct usbnet *usbdev = netdev_priv(dev); + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + int ret; + int delay = SCAN_DELAY_JIFFIES; + + netdev_dbg(usbdev->net, "cfg80211.scan\n"); + + /* Get current bssid list from device before new scan, as new scan + * clears internal bssid list. + */ + rndis_check_bssid_list(usbdev, NULL, NULL); + + if (priv->scan_request && priv->scan_request != request) + return -EBUSY; + + priv->scan_request = request; + + ret = rndis_start_bssid_list_scan(usbdev); + if (ret == 0) { + if (priv->device_type == RNDIS_BCM4320A) + delay = HZ; + + /* Wait before retrieving scan results from device */ + queue_delayed_work(priv->workqueue, &priv->scan_work, delay); + } + + return ret; +} + +static bool rndis_bss_info_update(struct usbnet *usbdev, + struct ndis_80211_bssid_ex *bssid) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + struct ieee80211_channel *channel; + struct cfg80211_bss *bss; + s32 signal; + u64 timestamp; + u16 capability; + u16 beacon_interval; + struct ndis_80211_fixed_ies *fixed; + int ie_len, bssid_len; + u8 *ie; + + netdev_dbg(usbdev->net, " found bssid: '%.32s' [%pM], len: %d\n", + bssid->ssid.essid, bssid->mac, le32_to_cpu(bssid->length)); + + /* parse bssid structure */ + bssid_len = le32_to_cpu(bssid->length); + + if (bssid_len < sizeof(struct ndis_80211_bssid_ex) + + sizeof(struct ndis_80211_fixed_ies)) + return NULL; + + fixed = (struct ndis_80211_fixed_ies *)bssid->ies; + + ie = (void *)(bssid->ies + sizeof(struct ndis_80211_fixed_ies)); + ie_len = min(bssid_len - (int)sizeof(*bssid), + (int)le32_to_cpu(bssid->ie_length)); + ie_len -= sizeof(struct ndis_80211_fixed_ies); + if (ie_len < 0) + return NULL; + + /* extract data for cfg80211_inform_bss */ + channel = ieee80211_get_channel(priv->wdev.wiphy, + KHZ_TO_MHZ(le32_to_cpu(bssid->config.ds_config))); + if (!channel) + return NULL; + + signal = level_to_qual(le32_to_cpu(bssid->rssi)); + timestamp = le64_to_cpu(*(__le64 *)fixed->timestamp); + capability = le16_to_cpu(fixed->capabilities); + beacon_interval = le16_to_cpu(fixed->beacon_interval); + + bss = cfg80211_inform_bss(priv->wdev.wiphy, channel, bssid->mac, + timestamp, capability, beacon_interval, ie, ie_len, signal, + GFP_KERNEL); + cfg80211_put_bss(priv->wdev.wiphy, bss); + + return (bss != NULL); +} + +static struct ndis_80211_bssid_ex *next_bssid_list_item( + struct ndis_80211_bssid_ex *bssid, + int *bssid_len, void *buf, int len) +{ + void *buf_end, *bssid_end; + + buf_end = (char *)buf + len; + bssid_end = (char *)bssid + *bssid_len; + + if ((int)(buf_end - bssid_end) < sizeof(bssid->length)) { + *bssid_len = 0; + return NULL; + } else { + bssid = (void *)((char *)bssid + *bssid_len); + *bssid_len = le32_to_cpu(bssid->length); + return bssid; + } +} + +static bool check_bssid_list_item(struct ndis_80211_bssid_ex *bssid, + int bssid_len, void *buf, int len) +{ + void *buf_end, *bssid_end; + + if (!bssid || bssid_len <= 0 || bssid_len > len) + return false; + + buf_end = (char *)buf + len; + bssid_end = (char *)bssid + bssid_len; + + return (int)(buf_end - bssid_end) >= 0 && (int)(bssid_end - buf) >= 0; +} + +static int rndis_check_bssid_list(struct usbnet *usbdev, u8 *match_bssid, + bool *matched) +{ + void *buf = NULL; + struct ndis_80211_bssid_list_ex *bssid_list; + struct ndis_80211_bssid_ex *bssid; + int ret = -EINVAL, len, count, bssid_len, real_count, new_len; + + netdev_dbg(usbdev->net, "%s()\n", __func__); + + len = CONTROL_BUFFER_SIZE; +resize_buf: + buf = kzalloc(len, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto out; + } + + /* BSSID-list might have got bigger last time we checked, keep + * resizing until it won't get any bigger. + */ + new_len = len; + ret = rndis_query_oid(usbdev, RNDIS_OID_802_11_BSSID_LIST, + buf, &new_len); + if (ret != 0 || new_len < sizeof(struct ndis_80211_bssid_list_ex)) + goto out; + + if (new_len > len) { + len = new_len; + kfree(buf); + goto resize_buf; + } + + len = new_len; + + bssid_list = buf; + count = le32_to_cpu(bssid_list->num_items); + real_count = 0; + netdev_dbg(usbdev->net, "%s(): buflen: %d\n", __func__, len); + + bssid_len = 0; + bssid = next_bssid_list_item(bssid_list->bssid, &bssid_len, buf, len); + + /* Device returns incorrect 'num_items'. Workaround by ignoring the + * received 'num_items' and walking through full bssid buffer instead. + */ + while (check_bssid_list_item(bssid, bssid_len, buf, len)) { + if (rndis_bss_info_update(usbdev, bssid) && match_bssid && + matched) { + if (ether_addr_equal(bssid->mac, match_bssid)) + *matched = true; + } + + real_count++; + bssid = next_bssid_list_item(bssid, &bssid_len, buf, len); + } + + netdev_dbg(usbdev->net, "%s(): num_items from device: %d, really found:" + " %d\n", __func__, count, real_count); + +out: + kfree(buf); + return ret; +} + +static void rndis_get_scan_results(struct work_struct *work) +{ + struct rndis_wlan_private *priv = + container_of(work, struct rndis_wlan_private, scan_work.work); + struct usbnet *usbdev = priv->usbdev; + int ret; + + netdev_dbg(usbdev->net, "get_scan_results\n"); + + if (!priv->scan_request) + return; + + ret = rndis_check_bssid_list(usbdev, NULL, NULL); + + cfg80211_scan_done(priv->scan_request, ret < 0); + + priv->scan_request = NULL; +} + +static int rndis_connect(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_connect_params *sme) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + struct ieee80211_channel *channel = sme->channel; + struct ndis_80211_ssid ssid; + int pairwise = RNDIS_WLAN_ALG_NONE; + int groupwise = RNDIS_WLAN_ALG_NONE; + int keymgmt = RNDIS_WLAN_KEY_MGMT_NONE; + int length, i, ret, chan = -1; + + if (channel) + chan = ieee80211_frequency_to_channel(channel->center_freq); + + groupwise = rndis_cipher_to_alg(sme->crypto.cipher_group); + for (i = 0; i < sme->crypto.n_ciphers_pairwise; i++) + pairwise |= + rndis_cipher_to_alg(sme->crypto.ciphers_pairwise[i]); + + if (sme->crypto.n_ciphers_pairwise > 0 && + pairwise == RNDIS_WLAN_ALG_NONE) { + netdev_err(usbdev->net, "Unsupported pairwise cipher\n"); + return -ENOTSUPP; + } + + for (i = 0; i < sme->crypto.n_akm_suites; i++) + keymgmt |= + rndis_akm_suite_to_key_mgmt(sme->crypto.akm_suites[i]); + + if (sme->crypto.n_akm_suites > 0 && + keymgmt == RNDIS_WLAN_KEY_MGMT_NONE) { + netdev_err(usbdev->net, "Invalid keymgmt\n"); + return -ENOTSUPP; + } + + netdev_dbg(usbdev->net, "cfg80211.connect('%.32s':[%pM]:%d:[%d,0x%x:0x%x]:[0x%x:0x%x]:0x%x)\n", + sme->ssid, sme->bssid, chan, + sme->privacy, sme->crypto.wpa_versions, sme->auth_type, + groupwise, pairwise, keymgmt); + + if (is_associated(usbdev)) + disassociate(usbdev, false); + + ret = set_infra_mode(usbdev, NDIS_80211_INFRA_INFRA); + if (ret < 0) { + netdev_dbg(usbdev->net, "connect: set_infra_mode failed, %d\n", + ret); + goto err_turn_radio_on; + } + + ret = set_auth_mode(usbdev, sme->crypto.wpa_versions, sme->auth_type, + keymgmt); + if (ret < 0) { + netdev_dbg(usbdev->net, "connect: set_auth_mode failed, %d\n", + ret); + goto err_turn_radio_on; + } + + set_priv_filter(usbdev); + + ret = set_encr_mode(usbdev, pairwise, groupwise); + if (ret < 0) { + netdev_dbg(usbdev->net, "connect: set_encr_mode failed, %d\n", + ret); + goto err_turn_radio_on; + } + + if (channel) { + ret = set_channel(usbdev, chan); + if (ret < 0) { + netdev_dbg(usbdev->net, "connect: set_channel failed, %d\n", + ret); + goto err_turn_radio_on; + } + } + + if (sme->key && ((groupwise | pairwise) & RNDIS_WLAN_ALG_WEP)) { + priv->encr_tx_key_index = sme->key_idx; + ret = add_wep_key(usbdev, sme->key, sme->key_len, sme->key_idx); + if (ret < 0) { + netdev_dbg(usbdev->net, "connect: add_wep_key failed, %d (%d, %d)\n", + ret, sme->key_len, sme->key_idx); + goto err_turn_radio_on; + } + } + + if (sme->bssid && !is_zero_ether_addr(sme->bssid) && + !is_broadcast_ether_addr(sme->bssid)) { + ret = set_bssid(usbdev, sme->bssid); + if (ret < 0) { + netdev_dbg(usbdev->net, "connect: set_bssid failed, %d\n", + ret); + goto err_turn_radio_on; + } + } else + clear_bssid(usbdev); + + length = sme->ssid_len; + if (length > NDIS_802_11_LENGTH_SSID) + length = NDIS_802_11_LENGTH_SSID; + + memset(&ssid, 0, sizeof(ssid)); + ssid.length = cpu_to_le32(length); + memcpy(ssid.essid, sme->ssid, length); + + /* Pause and purge rx queue, so we don't pass packets before + * 'media connect'-indication. + */ + usbnet_pause_rx(usbdev); + usbnet_purge_paused_rxq(usbdev); + + ret = set_essid(usbdev, &ssid); + if (ret < 0) + netdev_dbg(usbdev->net, "connect: set_essid failed, %d\n", ret); + return ret; + +err_turn_radio_on: + disassociate(usbdev, true); + + return ret; +} + +static int rndis_disconnect(struct wiphy *wiphy, struct net_device *dev, + u16 reason_code) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + + netdev_dbg(usbdev->net, "cfg80211.disconnect(%d)\n", reason_code); + + priv->connected = false; + memset(priv->bssid, 0, ETH_ALEN); + + return deauthenticate(usbdev); +} + +static int rndis_join_ibss(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_ibss_params *params) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + struct ieee80211_channel *channel = params->chandef.chan; + struct ndis_80211_ssid ssid; + enum nl80211_auth_type auth_type; + int ret, alg, length, chan = -1; + + if (channel) + chan = ieee80211_frequency_to_channel(channel->center_freq); + + /* TODO: How to handle ad-hoc encryption? + * connect() has *key, join_ibss() doesn't. RNDIS requires key to be + * pre-shared for encryption (open/shared/wpa), is key set before + * join_ibss? Which auth_type to use (not in params)? What about WPA? + */ + if (params->privacy) { + auth_type = NL80211_AUTHTYPE_SHARED_KEY; + alg = RNDIS_WLAN_ALG_WEP; + } else { + auth_type = NL80211_AUTHTYPE_OPEN_SYSTEM; + alg = RNDIS_WLAN_ALG_NONE; + } + + netdev_dbg(usbdev->net, "cfg80211.join_ibss('%.32s':[%pM]:%d:%d)\n", + params->ssid, params->bssid, chan, params->privacy); + + if (is_associated(usbdev)) + disassociate(usbdev, false); + + ret = set_infra_mode(usbdev, NDIS_80211_INFRA_ADHOC); + if (ret < 0) { + netdev_dbg(usbdev->net, "join_ibss: set_infra_mode failed, %d\n", + ret); + goto err_turn_radio_on; + } + + ret = set_auth_mode(usbdev, 0, auth_type, RNDIS_WLAN_KEY_MGMT_NONE); + if (ret < 0) { + netdev_dbg(usbdev->net, "join_ibss: set_auth_mode failed, %d\n", + ret); + goto err_turn_radio_on; + } + + set_priv_filter(usbdev); + + ret = set_encr_mode(usbdev, alg, RNDIS_WLAN_ALG_NONE); + if (ret < 0) { + netdev_dbg(usbdev->net, "join_ibss: set_encr_mode failed, %d\n", + ret); + goto err_turn_radio_on; + } + + if (channel) { + ret = set_channel(usbdev, chan); + if (ret < 0) { + netdev_dbg(usbdev->net, "join_ibss: set_channel failed, %d\n", + ret); + goto err_turn_radio_on; + } + } + + if (params->bssid && !is_zero_ether_addr(params->bssid) && + !is_broadcast_ether_addr(params->bssid)) { + ret = set_bssid(usbdev, params->bssid); + if (ret < 0) { + netdev_dbg(usbdev->net, "join_ibss: set_bssid failed, %d\n", + ret); + goto err_turn_radio_on; + } + } else + clear_bssid(usbdev); + + length = params->ssid_len; + if (length > NDIS_802_11_LENGTH_SSID) + length = NDIS_802_11_LENGTH_SSID; + + memset(&ssid, 0, sizeof(ssid)); + ssid.length = cpu_to_le32(length); + memcpy(ssid.essid, params->ssid, length); + + /* Don't need to pause rx queue for ad-hoc. */ + usbnet_purge_paused_rxq(usbdev); + usbnet_resume_rx(usbdev); + + ret = set_essid(usbdev, &ssid); + if (ret < 0) + netdev_dbg(usbdev->net, "join_ibss: set_essid failed, %d\n", + ret); + return ret; + +err_turn_radio_on: + disassociate(usbdev, true); + + return ret; +} + +static int rndis_leave_ibss(struct wiphy *wiphy, struct net_device *dev) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + + netdev_dbg(usbdev->net, "cfg80211.leave_ibss()\n"); + + priv->connected = false; + memset(priv->bssid, 0, ETH_ALEN); + + return deauthenticate(usbdev); +} + +static int rndis_add_key(struct wiphy *wiphy, struct net_device *netdev, + u8 key_index, bool pairwise, const u8 *mac_addr, + struct key_params *params) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + __le32 flags; + + netdev_dbg(usbdev->net, "%s(%i, %pM, %08x)\n", + __func__, key_index, mac_addr, params->cipher); + + switch (params->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + return add_wep_key(usbdev, params->key, params->key_len, + key_index); + case WLAN_CIPHER_SUITE_TKIP: + case WLAN_CIPHER_SUITE_CCMP: + flags = 0; + + if (params->seq && params->seq_len > 0) + flags |= NDIS_80211_ADDKEY_SET_INIT_RECV_SEQ; + if (mac_addr) + flags |= NDIS_80211_ADDKEY_PAIRWISE_KEY | + NDIS_80211_ADDKEY_TRANSMIT_KEY; + + return add_wpa_key(usbdev, params->key, params->key_len, + key_index, mac_addr, params->seq, + params->seq_len, params->cipher, flags); + default: + netdev_dbg(usbdev->net, "%s(): unsupported cipher %08x\n", + __func__, params->cipher); + return -ENOTSUPP; + } +} + +static int rndis_del_key(struct wiphy *wiphy, struct net_device *netdev, + u8 key_index, bool pairwise, const u8 *mac_addr) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + + netdev_dbg(usbdev->net, "%s(%i, %pM)\n", __func__, key_index, mac_addr); + + return remove_key(usbdev, key_index, mac_addr); +} + +static int rndis_set_default_key(struct wiphy *wiphy, struct net_device *netdev, + u8 key_index, bool unicast, bool multicast) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + struct rndis_wlan_encr_key key; + + netdev_dbg(usbdev->net, "%s(%i)\n", __func__, key_index); + + if (key_index >= RNDIS_WLAN_NUM_KEYS) + return -ENOENT; + + priv->encr_tx_key_index = key_index; + + if (is_wpa_key(priv, key_index)) + return 0; + + key = priv->encr_keys[key_index]; + + return add_wep_key(usbdev, key.material, key.len, key_index); +} + +static void rndis_fill_station_info(struct usbnet *usbdev, + struct station_info *sinfo) +{ + __le32 linkspeed, rssi; + int ret, len; + + memset(sinfo, 0, sizeof(*sinfo)); + + len = sizeof(linkspeed); + ret = rndis_query_oid(usbdev, RNDIS_OID_GEN_LINK_SPEED, &linkspeed, &len); + if (ret == 0) { + sinfo->txrate.legacy = le32_to_cpu(linkspeed) / 1000; + sinfo->filled |= STATION_INFO_TX_BITRATE; + } + + len = sizeof(rssi); + ret = rndis_query_oid(usbdev, RNDIS_OID_802_11_RSSI, + &rssi, &len); + if (ret == 0) { + sinfo->signal = level_to_qual(le32_to_cpu(rssi)); + sinfo->filled |= STATION_INFO_SIGNAL; + } +} + +static int rndis_get_station(struct wiphy *wiphy, struct net_device *dev, + u8 *mac, struct station_info *sinfo) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + + if (!ether_addr_equal(priv->bssid, mac)) + return -ENOENT; + + rndis_fill_station_info(usbdev, sinfo); + + return 0; +} + +static int rndis_dump_station(struct wiphy *wiphy, struct net_device *dev, + int idx, u8 *mac, struct station_info *sinfo) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + + if (idx != 0) + return -ENOENT; + + memcpy(mac, priv->bssid, ETH_ALEN); + + rndis_fill_station_info(usbdev, sinfo); + + return 0; +} + +static int rndis_set_pmksa(struct wiphy *wiphy, struct net_device *netdev, + struct cfg80211_pmksa *pmksa) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + struct ndis_80211_pmkid *pmkids; + u32 *tmp = (u32 *)pmksa->pmkid; + + netdev_dbg(usbdev->net, "%s(%pM, %08X:%08X:%08X:%08X)\n", __func__, + pmksa->bssid, + cpu_to_be32(tmp[0]), cpu_to_be32(tmp[1]), + cpu_to_be32(tmp[2]), cpu_to_be32(tmp[3])); + + pmkids = get_device_pmkids(usbdev); + if (IS_ERR(pmkids)) { + /* couldn't read PMKID cache from device */ + return PTR_ERR(pmkids); + } + + pmkids = update_pmkid(usbdev, pmkids, pmksa, wiphy->max_num_pmkids); + if (IS_ERR(pmkids)) { + /* not found, list full, etc */ + return PTR_ERR(pmkids); + } + + return set_device_pmkids(usbdev, pmkids); +} + +static int rndis_del_pmksa(struct wiphy *wiphy, struct net_device *netdev, + struct cfg80211_pmksa *pmksa) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + struct ndis_80211_pmkid *pmkids; + u32 *tmp = (u32 *)pmksa->pmkid; + + netdev_dbg(usbdev->net, "%s(%pM, %08X:%08X:%08X:%08X)\n", __func__, + pmksa->bssid, + cpu_to_be32(tmp[0]), cpu_to_be32(tmp[1]), + cpu_to_be32(tmp[2]), cpu_to_be32(tmp[3])); + + pmkids = get_device_pmkids(usbdev); + if (IS_ERR(pmkids)) { + /* Couldn't read PMKID cache from device */ + return PTR_ERR(pmkids); + } + + pmkids = remove_pmkid(usbdev, pmkids, pmksa, wiphy->max_num_pmkids); + if (IS_ERR(pmkids)) { + /* not found, etc */ + return PTR_ERR(pmkids); + } + + return set_device_pmkids(usbdev, pmkids); +} + +static int rndis_flush_pmksa(struct wiphy *wiphy, struct net_device *netdev) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + struct ndis_80211_pmkid pmkid; + + netdev_dbg(usbdev->net, "%s()\n", __func__); + + memset(&pmkid, 0, sizeof(pmkid)); + + pmkid.length = cpu_to_le32(sizeof(pmkid)); + pmkid.bssid_info_count = cpu_to_le32(0); + + return rndis_set_oid(usbdev, RNDIS_OID_802_11_PMKID, + &pmkid, sizeof(pmkid)); +} + +static int rndis_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev, + bool enabled, int timeout) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + struct usbnet *usbdev = priv->usbdev; + int power_mode; + __le32 mode; + int ret; + + if (priv->device_type != RNDIS_BCM4320B) + return -ENOTSUPP; + + netdev_dbg(usbdev->net, "%s(): %s, %d\n", __func__, + enabled ? "enabled" : "disabled", + timeout); + + if (enabled) + power_mode = NDIS_80211_POWER_MODE_FAST_PSP; + else + power_mode = NDIS_80211_POWER_MODE_CAM; + + if (power_mode == priv->power_mode) + return 0; + + priv->power_mode = power_mode; + + mode = cpu_to_le32(power_mode); + ret = rndis_set_oid(usbdev, RNDIS_OID_802_11_POWER_MODE, + &mode, sizeof(mode)); + + netdev_dbg(usbdev->net, "%s(): RNDIS_OID_802_11_POWER_MODE -> %d\n", + __func__, ret); + + return ret; +} + +static int rndis_set_cqm_rssi_config(struct wiphy *wiphy, + struct net_device *dev, + s32 rssi_thold, u32 rssi_hyst) +{ + struct rndis_wlan_private *priv = wiphy_priv(wiphy); + + priv->cqm_rssi_thold = rssi_thold; + priv->cqm_rssi_hyst = rssi_hyst; + priv->last_cqm_event_rssi = 0; + + return 0; +} + +static void rndis_wlan_craft_connected_bss(struct usbnet *usbdev, u8 *bssid, + struct ndis_80211_assoc_info *info) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + struct ieee80211_channel *channel; + struct ndis_80211_ssid ssid; + struct cfg80211_bss *bss; + s32 signal; + u64 timestamp; + u16 capability; + u32 beacon_period = 0; + __le32 rssi; + u8 ie_buf[34]; + int len, ret, ie_len; + + /* Get signal quality, in case of error use rssi=0 and ignore error. */ + len = sizeof(rssi); + rssi = 0; + ret = rndis_query_oid(usbdev, RNDIS_OID_802_11_RSSI, + &rssi, &len); + signal = level_to_qual(le32_to_cpu(rssi)); + + netdev_dbg(usbdev->net, "%s(): RNDIS_OID_802_11_RSSI -> %d, " + "rssi:%d, qual: %d\n", __func__, ret, le32_to_cpu(rssi), + level_to_qual(le32_to_cpu(rssi))); + + /* Get AP capabilities */ + if (info) { + capability = le16_to_cpu(info->resp_ie.capa); + } else { + /* Set atleast ESS/IBSS capability */ + capability = (priv->infra_mode == NDIS_80211_INFRA_INFRA) ? + WLAN_CAPABILITY_ESS : WLAN_CAPABILITY_IBSS; + } + + /* Get channel and beacon interval */ + channel = get_current_channel(usbdev, &beacon_period); + if (!channel) { + netdev_warn(usbdev->net, "%s(): could not get channel.\n", + __func__); + return; + } + + /* Get SSID, in case of error, use zero length SSID and ignore error. */ + len = sizeof(ssid); + memset(&ssid, 0, sizeof(ssid)); + ret = rndis_query_oid(usbdev, RNDIS_OID_802_11_SSID, + &ssid, &len); + netdev_dbg(usbdev->net, "%s(): RNDIS_OID_802_11_SSID -> %d, len: %d, ssid: " + "'%.32s'\n", __func__, ret, + le32_to_cpu(ssid.length), ssid.essid); + + if (le32_to_cpu(ssid.length) > 32) + ssid.length = cpu_to_le32(32); + + ie_buf[0] = WLAN_EID_SSID; + ie_buf[1] = le32_to_cpu(ssid.length); + memcpy(&ie_buf[2], ssid.essid, le32_to_cpu(ssid.length)); + + ie_len = le32_to_cpu(ssid.length) + 2; + + /* no tsf */ + timestamp = 0; + + netdev_dbg(usbdev->net, "%s(): channel:%d(freq), bssid:[%pM], tsf:%d, " + "capa:%x, beacon int:%d, resp_ie(len:%d, essid:'%.32s'), " + "signal:%d\n", __func__, (channel ? channel->center_freq : -1), + bssid, (u32)timestamp, capability, beacon_period, ie_len, + ssid.essid, signal); + + bss = cfg80211_inform_bss(priv->wdev.wiphy, channel, bssid, + timestamp, capability, beacon_period, ie_buf, ie_len, + signal, GFP_KERNEL); + cfg80211_put_bss(priv->wdev.wiphy, bss); +} + +/* + * workers, indication handlers, device poller + */ +static void rndis_wlan_do_link_up_work(struct usbnet *usbdev) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + struct ndis_80211_assoc_info *info = NULL; + u8 bssid[ETH_ALEN]; + unsigned int resp_ie_len, req_ie_len; + unsigned int offset; + u8 *req_ie, *resp_ie; + int ret; + bool roamed = false; + bool match_bss; + + if (priv->infra_mode == NDIS_80211_INFRA_INFRA && priv->connected) { + /* received media connect indication while connected, either + * device reassociated with same AP or roamed to new. */ + roamed = true; + } + + req_ie_len = 0; + resp_ie_len = 0; + req_ie = NULL; + resp_ie = NULL; + + if (priv->infra_mode == NDIS_80211_INFRA_INFRA) { + info = kzalloc(CONTROL_BUFFER_SIZE, GFP_KERNEL); + if (!info) { + /* No memory? Try resume work later */ + set_bit(WORK_LINK_UP, &priv->work_pending); + queue_work(priv->workqueue, &priv->work); + return; + } + + /* Get association info IEs from device. */ + ret = get_association_info(usbdev, info, CONTROL_BUFFER_SIZE); + if (!ret) { + req_ie_len = le32_to_cpu(info->req_ie_length); + if (req_ie_len > CONTROL_BUFFER_SIZE) + req_ie_len = CONTROL_BUFFER_SIZE; + if (req_ie_len != 0) { + offset = le32_to_cpu(info->offset_req_ies); + + if (offset > CONTROL_BUFFER_SIZE) + offset = CONTROL_BUFFER_SIZE; + + req_ie = (u8 *)info + offset; + + if (offset + req_ie_len > CONTROL_BUFFER_SIZE) + req_ie_len = + CONTROL_BUFFER_SIZE - offset; + } + + resp_ie_len = le32_to_cpu(info->resp_ie_length); + if (resp_ie_len > CONTROL_BUFFER_SIZE) + resp_ie_len = CONTROL_BUFFER_SIZE; + if (resp_ie_len != 0) { + offset = le32_to_cpu(info->offset_resp_ies); + + if (offset > CONTROL_BUFFER_SIZE) + offset = CONTROL_BUFFER_SIZE; + + resp_ie = (u8 *)info + offset; + + if (offset + resp_ie_len > CONTROL_BUFFER_SIZE) + resp_ie_len = + CONTROL_BUFFER_SIZE - offset; + } + } else { + /* Since rndis_wlan_craft_connected_bss() might use info + * later and expects info to contain valid data if + * non-null, free info and set NULL here. + */ + kfree(info); + info = NULL; + } + } else if (WARN_ON(priv->infra_mode != NDIS_80211_INFRA_ADHOC)) + return; + + ret = get_bssid(usbdev, bssid); + if (ret < 0) + memset(bssid, 0, sizeof(bssid)); + + netdev_dbg(usbdev->net, "link up work: [%pM]%s\n", + bssid, roamed ? " roamed" : ""); + + /* Internal bss list in device should contain at least the currently + * connected bss and we can get it to cfg80211 with + * rndis_check_bssid_list(). + * + * NDIS spec says: "If the device is associated, but the associated + * BSSID is not in its BSSID scan list, then the driver must add an + * entry for the BSSID at the end of the data that it returns in + * response to query of RNDIS_OID_802_11_BSSID_LIST." + * + * NOTE: Seems to be true for BCM4320b variant, but not BCM4320a. + */ + match_bss = false; + rndis_check_bssid_list(usbdev, bssid, &match_bss); + + if (!is_zero_ether_addr(bssid) && !match_bss) { + /* Couldn't get bss from device, we need to manually craft bss + * for cfg80211. + */ + rndis_wlan_craft_connected_bss(usbdev, bssid, info); + } + + if (priv->infra_mode == NDIS_80211_INFRA_INFRA) { + if (!roamed) + cfg80211_connect_result(usbdev->net, bssid, req_ie, + req_ie_len, resp_ie, + resp_ie_len, 0, GFP_KERNEL); + else + cfg80211_roamed(usbdev->net, + get_current_channel(usbdev, NULL), + bssid, req_ie, req_ie_len, + resp_ie, resp_ie_len, GFP_KERNEL); + } else if (priv->infra_mode == NDIS_80211_INFRA_ADHOC) + cfg80211_ibss_joined(usbdev->net, bssid, GFP_KERNEL); + + kfree(info); + + priv->connected = true; + memcpy(priv->bssid, bssid, ETH_ALEN); + + usbnet_resume_rx(usbdev); + netif_carrier_on(usbdev->net); +} + +static void rndis_wlan_do_link_down_work(struct usbnet *usbdev) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + + if (priv->connected) { + priv->connected = false; + memset(priv->bssid, 0, ETH_ALEN); + + deauthenticate(usbdev); + + cfg80211_disconnected(usbdev->net, 0, NULL, 0, GFP_KERNEL); + } + + netif_carrier_off(usbdev->net); +} + +static void rndis_wlan_worker(struct work_struct *work) +{ + struct rndis_wlan_private *priv = + container_of(work, struct rndis_wlan_private, work); + struct usbnet *usbdev = priv->usbdev; + + if (test_and_clear_bit(WORK_LINK_UP, &priv->work_pending)) + rndis_wlan_do_link_up_work(usbdev); + + if (test_and_clear_bit(WORK_LINK_DOWN, &priv->work_pending)) + rndis_wlan_do_link_down_work(usbdev); + + if (test_and_clear_bit(WORK_SET_MULTICAST_LIST, &priv->work_pending)) + set_multicast_list(usbdev); +} + +static void rndis_wlan_set_multicast_list(struct net_device *dev) +{ + struct usbnet *usbdev = netdev_priv(dev); + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + + if (test_bit(WORK_SET_MULTICAST_LIST, &priv->work_pending)) + return; + + set_bit(WORK_SET_MULTICAST_LIST, &priv->work_pending); + queue_work(priv->workqueue, &priv->work); +} + +static void rndis_wlan_auth_indication(struct usbnet *usbdev, + struct ndis_80211_status_indication *indication, + int len) +{ + u8 *buf; + const char *type; + int flags, buflen, key_id; + bool pairwise_error, group_error; + struct ndis_80211_auth_request *auth_req; + enum nl80211_key_type key_type; + + /* must have at least one array entry */ + if (len < offsetof(struct ndis_80211_status_indication, u) + + sizeof(struct ndis_80211_auth_request)) { + netdev_info(usbdev->net, "authentication indication: too short message (%i)\n", + len); + return; + } + + buf = (void *)&indication->u.auth_request[0]; + buflen = len - offsetof(struct ndis_80211_status_indication, u); + + while (buflen >= sizeof(*auth_req)) { + auth_req = (void *)buf; + type = "unknown"; + flags = le32_to_cpu(auth_req->flags); + pairwise_error = false; + group_error = false; + + if (flags & 0x1) + type = "reauth request"; + if (flags & 0x2) + type = "key update request"; + if (flags & 0x6) { + pairwise_error = true; + type = "pairwise_error"; + } + if (flags & 0xe) { + group_error = true; + type = "group_error"; + } + + netdev_info(usbdev->net, "authentication indication: %s (0x%08x)\n", + type, le32_to_cpu(auth_req->flags)); + + if (pairwise_error) { + key_type = NL80211_KEYTYPE_PAIRWISE; + key_id = -1; + + cfg80211_michael_mic_failure(usbdev->net, + auth_req->bssid, + key_type, key_id, NULL, + GFP_KERNEL); + } + + if (group_error) { + key_type = NL80211_KEYTYPE_GROUP; + key_id = -1; + + cfg80211_michael_mic_failure(usbdev->net, + auth_req->bssid, + key_type, key_id, NULL, + GFP_KERNEL); + } + + buflen -= le32_to_cpu(auth_req->length); + buf += le32_to_cpu(auth_req->length); + } +} + +static void rndis_wlan_pmkid_cand_list_indication(struct usbnet *usbdev, + struct ndis_80211_status_indication *indication, + int len) +{ + struct ndis_80211_pmkid_cand_list *cand_list; + int list_len, expected_len, i; + + if (len < offsetof(struct ndis_80211_status_indication, u) + + sizeof(struct ndis_80211_pmkid_cand_list)) { + netdev_info(usbdev->net, "pmkid candidate list indication: too short message (%i)\n", + len); + return; + } + + list_len = le32_to_cpu(indication->u.cand_list.num_candidates) * + sizeof(struct ndis_80211_pmkid_candidate); + expected_len = sizeof(struct ndis_80211_pmkid_cand_list) + list_len + + offsetof(struct ndis_80211_status_indication, u); + + if (len < expected_len) { + netdev_info(usbdev->net, "pmkid candidate list indication: list larger than buffer (%i < %i)\n", + len, expected_len); + return; + } + + cand_list = &indication->u.cand_list; + + netdev_info(usbdev->net, "pmkid candidate list indication: version %i, candidates %i\n", + le32_to_cpu(cand_list->version), + le32_to_cpu(cand_list->num_candidates)); + + if (le32_to_cpu(cand_list->version) != 1) + return; + + for (i = 0; i < le32_to_cpu(cand_list->num_candidates); i++) { + struct ndis_80211_pmkid_candidate *cand = + &cand_list->candidate_list[i]; + bool preauth = !!(cand->flags & NDIS_80211_PMKID_CAND_PREAUTH); + + netdev_dbg(usbdev->net, "cand[%i]: flags: 0x%08x, preauth: %d, bssid: %pM\n", + i, le32_to_cpu(cand->flags), preauth, cand->bssid); + + cfg80211_pmksa_candidate_notify(usbdev->net, i, cand->bssid, + preauth, GFP_ATOMIC); + } +} + +static void rndis_wlan_media_specific_indication(struct usbnet *usbdev, + struct rndis_indicate *msg, int buflen) +{ + struct ndis_80211_status_indication *indication; + unsigned int len, offset; + + offset = offsetof(struct rndis_indicate, status) + + le32_to_cpu(msg->offset); + len = le32_to_cpu(msg->length); + + if (len < 8) { + netdev_info(usbdev->net, "media specific indication, ignore too short message (%i < 8)\n", + len); + return; + } + + if (len > buflen || offset > buflen || offset + len > buflen) { + netdev_info(usbdev->net, "media specific indication, too large to fit to buffer (%i > %i)\n", + offset + len, buflen); + return; + } + + indication = (void *)((u8 *)msg + offset); + + switch (le32_to_cpu(indication->status_type)) { + case NDIS_80211_STATUSTYPE_RADIOSTATE: + netdev_info(usbdev->net, "radio state indication: %i\n", + le32_to_cpu(indication->u.radio_status)); + return; + + case NDIS_80211_STATUSTYPE_MEDIASTREAMMODE: + netdev_info(usbdev->net, "media stream mode indication: %i\n", + le32_to_cpu(indication->u.media_stream_mode)); + return; + + case NDIS_80211_STATUSTYPE_AUTHENTICATION: + rndis_wlan_auth_indication(usbdev, indication, len); + return; + + case NDIS_80211_STATUSTYPE_PMKID_CANDIDATELIST: + rndis_wlan_pmkid_cand_list_indication(usbdev, indication, len); + return; + + default: + netdev_info(usbdev->net, "media specific indication: unknown status type 0x%08x\n", + le32_to_cpu(indication->status_type)); + } +} + +static void rndis_wlan_indication(struct usbnet *usbdev, void *ind, int buflen) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + struct rndis_indicate *msg = ind; + + switch (le32_to_cpu(msg->status)) { + case RNDIS_STATUS_MEDIA_CONNECT: + if (priv->current_command_oid == RNDIS_OID_802_11_ADD_KEY) { + /* RNDIS_OID_802_11_ADD_KEY causes sometimes extra + * "media connect" indications which confuses driver + * and userspace to think that device is + * roaming/reassociating when it isn't. + */ + netdev_dbg(usbdev->net, "ignored RNDIS_OID_802_11_ADD_KEY triggered 'media connect'\n"); + return; + } + + usbnet_pause_rx(usbdev); + + netdev_info(usbdev->net, "media connect\n"); + + /* queue work to avoid recursive calls into rndis_command */ + set_bit(WORK_LINK_UP, &priv->work_pending); + queue_work(priv->workqueue, &priv->work); + break; + + case RNDIS_STATUS_MEDIA_DISCONNECT: + netdev_info(usbdev->net, "media disconnect\n"); + + /* queue work to avoid recursive calls into rndis_command */ + set_bit(WORK_LINK_DOWN, &priv->work_pending); + queue_work(priv->workqueue, &priv->work); + break; + + case RNDIS_STATUS_MEDIA_SPECIFIC_INDICATION: + rndis_wlan_media_specific_indication(usbdev, msg, buflen); + break; + + default: + netdev_info(usbdev->net, "indication: 0x%08x\n", + le32_to_cpu(msg->status)); + break; + } +} + +static int rndis_wlan_get_caps(struct usbnet *usbdev, struct wiphy *wiphy) +{ + struct { + __le32 num_items; + __le32 items[8]; + } networks_supported; + struct ndis_80211_capability *caps; + u8 caps_buf[sizeof(*caps) + sizeof(caps->auth_encr_pair) * 16]; + int len, retval, i, n; + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + + /* determine supported modes */ + len = sizeof(networks_supported); + retval = rndis_query_oid(usbdev, + RNDIS_OID_802_11_NETWORK_TYPES_SUPPORTED, + &networks_supported, &len); + if (retval >= 0) { + n = le32_to_cpu(networks_supported.num_items); + if (n > 8) + n = 8; + for (i = 0; i < n; i++) { + switch (le32_to_cpu(networks_supported.items[i])) { + case NDIS_80211_TYPE_FREQ_HOP: + case NDIS_80211_TYPE_DIRECT_SEQ: + priv->caps |= CAP_MODE_80211B; + break; + case NDIS_80211_TYPE_OFDM_A: + priv->caps |= CAP_MODE_80211A; + break; + case NDIS_80211_TYPE_OFDM_G: + priv->caps |= CAP_MODE_80211G; + break; + } + } + } + + /* get device 802.11 capabilities, number of PMKIDs */ + caps = (struct ndis_80211_capability *)caps_buf; + len = sizeof(caps_buf); + retval = rndis_query_oid(usbdev, + RNDIS_OID_802_11_CAPABILITY, + caps, &len); + if (retval >= 0) { + netdev_dbg(usbdev->net, "RNDIS_OID_802_11_CAPABILITY -> len %d, " + "ver %d, pmkids %d, auth-encr-pairs %d\n", + le32_to_cpu(caps->length), + le32_to_cpu(caps->version), + le32_to_cpu(caps->num_pmkids), + le32_to_cpu(caps->num_auth_encr_pair)); + wiphy->max_num_pmkids = le32_to_cpu(caps->num_pmkids); + } else + wiphy->max_num_pmkids = 0; + + return retval; +} + +static void rndis_do_cqm(struct usbnet *usbdev, s32 rssi) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + enum nl80211_cqm_rssi_threshold_event event; + int thold, hyst, last_event; + + if (priv->cqm_rssi_thold >= 0 || rssi >= 0) + return; + if (priv->infra_mode != NDIS_80211_INFRA_INFRA) + return; + + last_event = priv->last_cqm_event_rssi; + thold = priv->cqm_rssi_thold; + hyst = priv->cqm_rssi_hyst; + + if (rssi < thold && (last_event == 0 || rssi < last_event - hyst)) + event = NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW; + else if (rssi > thold && (last_event == 0 || rssi > last_event + hyst)) + event = NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH; + else + return; + + priv->last_cqm_event_rssi = rssi; + cfg80211_cqm_rssi_notify(usbdev->net, event, GFP_KERNEL); +} + +#define DEVICE_POLLER_JIFFIES (HZ) +static void rndis_device_poller(struct work_struct *work) +{ + struct rndis_wlan_private *priv = + container_of(work, struct rndis_wlan_private, + dev_poller_work.work); + struct usbnet *usbdev = priv->usbdev; + __le32 rssi, tmp; + int len, ret, j; + int update_jiffies = DEVICE_POLLER_JIFFIES; + void *buf; + + /* Only check/do workaround when connected. Calling is_associated() + * also polls device with rndis_command() and catches for media link + * indications. + */ + if (!is_associated(usbdev)) { + /* Workaround bad scanning in BCM4320a devices with active + * background scanning when not associated. + */ + if (priv->device_type == RNDIS_BCM4320A && priv->radio_on && + !priv->scan_request) { + /* Get previous scan results */ + rndis_check_bssid_list(usbdev, NULL, NULL); + + /* Initiate new scan */ + rndis_start_bssid_list_scan(usbdev); + } + + goto end; + } + + len = sizeof(rssi); + ret = rndis_query_oid(usbdev, RNDIS_OID_802_11_RSSI, + &rssi, &len); + if (ret == 0) { + priv->last_qual = level_to_qual(le32_to_cpu(rssi)); + rndis_do_cqm(usbdev, le32_to_cpu(rssi)); + } + + netdev_dbg(usbdev->net, "dev-poller: RNDIS_OID_802_11_RSSI -> %d, rssi:%d, qual: %d\n", + ret, le32_to_cpu(rssi), level_to_qual(le32_to_cpu(rssi))); + + /* Workaround transfer stalls on poor quality links. + * TODO: find right way to fix these stalls (as stalls do not happen + * with ndiswrapper/windows driver). */ + if (priv->param_workaround_interval > 0 && priv->last_qual <= 25) { + /* Decrease stats worker interval to catch stalls. + * faster. Faster than 400-500ms causes packet loss, + * Slower doesn't catch stalls fast enough. + */ + j = msecs_to_jiffies(priv->param_workaround_interval); + if (j > DEVICE_POLLER_JIFFIES) + j = DEVICE_POLLER_JIFFIES; + else if (j <= 0) + j = 1; + update_jiffies = j; + + /* Send scan OID. Use of both OIDs is required to get device + * working. + */ + tmp = cpu_to_le32(1); + rndis_set_oid(usbdev, + RNDIS_OID_802_11_BSSID_LIST_SCAN, + &tmp, sizeof(tmp)); + + len = CONTROL_BUFFER_SIZE; + buf = kmalloc(len, GFP_KERNEL); + if (!buf) + goto end; + + rndis_query_oid(usbdev, + RNDIS_OID_802_11_BSSID_LIST, + buf, &len); + kfree(buf); + } + +end: + if (update_jiffies >= HZ) + update_jiffies = round_jiffies_relative(update_jiffies); + else { + j = round_jiffies_relative(update_jiffies); + if (abs(j - update_jiffies) <= 10) + update_jiffies = j; + } + + queue_delayed_work(priv->workqueue, &priv->dev_poller_work, + update_jiffies); +} + +/* + * driver/device initialization + */ +static void rndis_copy_module_params(struct usbnet *usbdev, int device_type) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + + priv->device_type = device_type; + + priv->param_country[0] = modparam_country[0]; + priv->param_country[1] = modparam_country[1]; + priv->param_country[2] = 0; + priv->param_frameburst = modparam_frameburst; + priv->param_afterburner = modparam_afterburner; + priv->param_power_save = modparam_power_save; + priv->param_power_output = modparam_power_output; + priv->param_roamtrigger = modparam_roamtrigger; + priv->param_roamdelta = modparam_roamdelta; + + priv->param_country[0] = toupper(priv->param_country[0]); + priv->param_country[1] = toupper(priv->param_country[1]); + /* doesn't support EU as country code, use FI instead */ + if (!strcmp(priv->param_country, "EU")) + strcpy(priv->param_country, "FI"); + + if (priv->param_power_save < 0) + priv->param_power_save = 0; + else if (priv->param_power_save > 2) + priv->param_power_save = 2; + + if (priv->param_power_output < 0) + priv->param_power_output = 0; + else if (priv->param_power_output > 3) + priv->param_power_output = 3; + + if (priv->param_roamtrigger < -80) + priv->param_roamtrigger = -80; + else if (priv->param_roamtrigger > -60) + priv->param_roamtrigger = -60; + + if (priv->param_roamdelta < 0) + priv->param_roamdelta = 0; + else if (priv->param_roamdelta > 2) + priv->param_roamdelta = 2; + + if (modparam_workaround_interval < 0) + priv->param_workaround_interval = 500; + else + priv->param_workaround_interval = modparam_workaround_interval; +} + +static int unknown_early_init(struct usbnet *usbdev) +{ + /* copy module parameters for unknown so that iwconfig reports txpower + * and workaround parameter is copied to private structure correctly. + */ + rndis_copy_module_params(usbdev, RNDIS_UNKNOWN); + + /* This is unknown device, so do not try set configuration parameters. + */ + + return 0; +} + +static int bcm4320a_early_init(struct usbnet *usbdev) +{ + /* copy module parameters for bcm4320a so that iwconfig reports txpower + * and workaround parameter is copied to private structure correctly. + */ + rndis_copy_module_params(usbdev, RNDIS_BCM4320A); + + /* bcm4320a doesn't handle configuration parameters well. Try + * set any and you get partially zeroed mac and broken device. + */ + + return 0; +} + +static int bcm4320b_early_init(struct usbnet *usbdev) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + char buf[8]; + + rndis_copy_module_params(usbdev, RNDIS_BCM4320B); + + /* Early initialization settings, setting these won't have effect + * if called after generic_rndis_bind(). + */ + + rndis_set_config_parameter_str(usbdev, "Country", priv->param_country); + rndis_set_config_parameter_str(usbdev, "FrameBursting", + priv->param_frameburst ? "1" : "0"); + rndis_set_config_parameter_str(usbdev, "Afterburner", + priv->param_afterburner ? "1" : "0"); + sprintf(buf, "%d", priv->param_power_save); + rndis_set_config_parameter_str(usbdev, "PowerSaveMode", buf); + sprintf(buf, "%d", priv->param_power_output); + rndis_set_config_parameter_str(usbdev, "PwrOut", buf); + sprintf(buf, "%d", priv->param_roamtrigger); + rndis_set_config_parameter_str(usbdev, "RoamTrigger", buf); + sprintf(buf, "%d", priv->param_roamdelta); + rndis_set_config_parameter_str(usbdev, "RoamDelta", buf); + + return 0; +} + +/* same as rndis_netdev_ops but with local multicast handler */ +static const struct net_device_ops rndis_wlan_netdev_ops = { + .ndo_open = usbnet_open, + .ndo_stop = usbnet_stop, + .ndo_start_xmit = usbnet_start_xmit, + .ndo_tx_timeout = usbnet_tx_timeout, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_rx_mode = rndis_wlan_set_multicast_list, +}; + +static int rndis_wlan_bind(struct usbnet *usbdev, struct usb_interface *intf) +{ + struct wiphy *wiphy; + struct rndis_wlan_private *priv; + int retval, len; + __le32 tmp; + + /* allocate wiphy and rndis private data + * NOTE: We only support a single virtual interface, so wiphy + * and wireless_dev are somewhat synonymous for this device. + */ + wiphy = wiphy_new(&rndis_config_ops, sizeof(struct rndis_wlan_private)); + if (!wiphy) + return -ENOMEM; + + priv = wiphy_priv(wiphy); + usbdev->net->ieee80211_ptr = &priv->wdev; + priv->wdev.wiphy = wiphy; + priv->wdev.iftype = NL80211_IFTYPE_STATION; + + /* These have to be initialized before calling generic_rndis_bind(). + * Otherwise we'll be in big trouble in rndis_wlan_early_init(). + */ + usbdev->driver_priv = priv; + priv->usbdev = usbdev; + + mutex_init(&priv->command_lock); + + /* because rndis_command() sleeps we need to use workqueue */ + priv->workqueue = create_singlethread_workqueue("rndis_wlan"); + INIT_WORK(&priv->work, rndis_wlan_worker); + INIT_DELAYED_WORK(&priv->dev_poller_work, rndis_device_poller); + INIT_DELAYED_WORK(&priv->scan_work, rndis_get_scan_results); + + /* try bind rndis_host */ + retval = generic_rndis_bind(usbdev, intf, FLAG_RNDIS_PHYM_WIRELESS); + if (retval < 0) + goto fail; + + /* generic_rndis_bind set packet filter to multicast_all+ + * promisc mode which doesn't work well for our devices (device + * picks up rssi to closest station instead of to access point). + * + * rndis_host wants to avoid all OID as much as possible + * so do promisc/multicast handling in rndis_wlan. + */ + netdev_attach_ops(usbdev->net, &rndis_wlan_netdev_ops); + + tmp = cpu_to_le32(RNDIS_PACKET_TYPE_DIRECTED | RNDIS_PACKET_TYPE_BROADCAST); + retval = rndis_set_oid(usbdev, + RNDIS_OID_GEN_CURRENT_PACKET_FILTER, + &tmp, sizeof(tmp)); + + len = sizeof(tmp); + retval = rndis_query_oid(usbdev, + RNDIS_OID_802_3_MAXIMUM_LIST_SIZE, + &tmp, &len); + priv->multicast_size = le32_to_cpu(tmp); + if (retval < 0 || priv->multicast_size < 0) + priv->multicast_size = 0; + if (priv->multicast_size > 0) + usbdev->net->flags |= IFF_MULTICAST; + else + usbdev->net->flags &= ~IFF_MULTICAST; + + /* fill-out wiphy structure and register w/ cfg80211 */ + memcpy(wiphy->perm_addr, usbdev->net->dev_addr, ETH_ALEN); + wiphy->privid = rndis_wiphy_privid; + wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) + | BIT(NL80211_IFTYPE_ADHOC); + wiphy->max_scan_ssids = 1; + + /* TODO: fill-out band/encr information based on priv->caps */ + rndis_wlan_get_caps(usbdev, wiphy); + + memcpy(priv->channels, rndis_channels, sizeof(rndis_channels)); + memcpy(priv->rates, rndis_rates, sizeof(rndis_rates)); + priv->band.channels = priv->channels; + priv->band.n_channels = ARRAY_SIZE(rndis_channels); + priv->band.bitrates = priv->rates; + priv->band.n_bitrates = ARRAY_SIZE(rndis_rates); + wiphy->bands[IEEE80211_BAND_2GHZ] = &priv->band; + wiphy->signal_type = CFG80211_SIGNAL_TYPE_UNSPEC; + + memcpy(priv->cipher_suites, rndis_cipher_suites, + sizeof(rndis_cipher_suites)); + wiphy->cipher_suites = priv->cipher_suites; + wiphy->n_cipher_suites = ARRAY_SIZE(rndis_cipher_suites); + + set_wiphy_dev(wiphy, &usbdev->udev->dev); + + if (wiphy_register(wiphy)) { + retval = -ENODEV; + goto fail; + } + + set_default_iw_params(usbdev); + + priv->power_mode = -1; + + /* set default rts/frag */ + rndis_set_wiphy_params(wiphy, + WIPHY_PARAM_FRAG_THRESHOLD | WIPHY_PARAM_RTS_THRESHOLD); + + /* turn radio off on init */ + priv->radio_on = false; + disassociate(usbdev, false); + netif_carrier_off(usbdev->net); + + return 0; + +fail: + cancel_delayed_work_sync(&priv->dev_poller_work); + cancel_delayed_work_sync(&priv->scan_work); + cancel_work_sync(&priv->work); + flush_workqueue(priv->workqueue); + destroy_workqueue(priv->workqueue); + + wiphy_free(wiphy); + return retval; +} + +static void rndis_wlan_unbind(struct usbnet *usbdev, struct usb_interface *intf) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + + /* turn radio off */ + disassociate(usbdev, false); + + cancel_delayed_work_sync(&priv->dev_poller_work); + cancel_delayed_work_sync(&priv->scan_work); + cancel_work_sync(&priv->work); + flush_workqueue(priv->workqueue); + destroy_workqueue(priv->workqueue); + + rndis_unbind(usbdev, intf); + + wiphy_unregister(priv->wdev.wiphy); + wiphy_free(priv->wdev.wiphy); +} + +static int rndis_wlan_reset(struct usbnet *usbdev) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + int retval; + + netdev_dbg(usbdev->net, "%s()\n", __func__); + + retval = rndis_reset(usbdev); + if (retval) + netdev_warn(usbdev->net, "rndis_reset failed: %d\n", retval); + + /* rndis_reset cleared multicast list, so restore here. + (set_multicast_list() also turns on current packet filter) */ + set_multicast_list(usbdev); + + queue_delayed_work(priv->workqueue, &priv->dev_poller_work, + round_jiffies_relative(DEVICE_POLLER_JIFFIES)); + + return deauthenticate(usbdev); +} + +static int rndis_wlan_stop(struct usbnet *usbdev) +{ + struct rndis_wlan_private *priv = get_rndis_wlan_priv(usbdev); + int retval; + __le32 filter; + + netdev_dbg(usbdev->net, "%s()\n", __func__); + + retval = disassociate(usbdev, false); + + priv->work_pending = 0; + cancel_delayed_work_sync(&priv->dev_poller_work); + cancel_delayed_work_sync(&priv->scan_work); + cancel_work_sync(&priv->work); + flush_workqueue(priv->workqueue); + + if (priv->scan_request) { + cfg80211_scan_done(priv->scan_request, true); + priv->scan_request = NULL; + } + + /* Set current packet filter zero to block receiving data packets from + device. */ + filter = 0; + rndis_set_oid(usbdev, RNDIS_OID_GEN_CURRENT_PACKET_FILTER, &filter, + sizeof(filter)); + + return retval; +} + +static const struct driver_info bcm4320b_info = { + .description = "Wireless RNDIS device, BCM4320b based", + .flags = FLAG_WLAN | FLAG_FRAMING_RN | FLAG_NO_SETINT | + FLAG_AVOID_UNLINK_URBS, + .bind = rndis_wlan_bind, + .unbind = rndis_wlan_unbind, + .status = rndis_status, + .rx_fixup = rndis_rx_fixup, + .tx_fixup = rndis_tx_fixup, + .reset = rndis_wlan_reset, + .stop = rndis_wlan_stop, + .early_init = bcm4320b_early_init, + .indication = rndis_wlan_indication, +}; + +static const struct driver_info bcm4320a_info = { + .description = "Wireless RNDIS device, BCM4320a based", + .flags = FLAG_WLAN | FLAG_FRAMING_RN | FLAG_NO_SETINT | + FLAG_AVOID_UNLINK_URBS, + .bind = rndis_wlan_bind, + .unbind = rndis_wlan_unbind, + .status = rndis_status, + .rx_fixup = rndis_rx_fixup, + .tx_fixup = rndis_tx_fixup, + .reset = rndis_wlan_reset, + .stop = rndis_wlan_stop, + .early_init = bcm4320a_early_init, + .indication = rndis_wlan_indication, +}; + +static const struct driver_info rndis_wlan_info = { + .description = "Wireless RNDIS device", + .flags = FLAG_WLAN | FLAG_FRAMING_RN | FLAG_NO_SETINT | + FLAG_AVOID_UNLINK_URBS, + .bind = rndis_wlan_bind, + .unbind = rndis_wlan_unbind, + .status = rndis_status, + .rx_fixup = rndis_rx_fixup, + .tx_fixup = rndis_tx_fixup, + .reset = rndis_wlan_reset, + .stop = rndis_wlan_stop, + .early_init = unknown_early_init, + .indication = rndis_wlan_indication, +}; + +/*-------------------------------------------------------------------------*/ + +static const struct usb_device_id products [] = { +#define RNDIS_MASTER_INTERFACE \ + .bInterfaceClass = USB_CLASS_COMM, \ + .bInterfaceSubClass = 2 /* ACM */, \ + .bInterfaceProtocol = 0x0ff + +/* INF driver for these devices have DriverVer >= 4.xx.xx.xx and many custom + * parameters available. Chipset marked as 'BCM4320SKFBG' in NDISwrapper-wiki. + */ +{ + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0411, + .idProduct = 0x00bc, /* Buffalo WLI-U2-KG125S */ + RNDIS_MASTER_INTERFACE, + .driver_info = (unsigned long) &bcm4320b_info, +}, { + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0baf, + .idProduct = 0x011b, /* U.S. Robotics USR5421 */ + RNDIS_MASTER_INTERFACE, + .driver_info = (unsigned long) &bcm4320b_info, +}, { + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x050d, + .idProduct = 0x011b, /* Belkin F5D7051 */ + RNDIS_MASTER_INTERFACE, + .driver_info = (unsigned long) &bcm4320b_info, +}, { + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x1799, /* Belkin has two vendor ids */ + .idProduct = 0x011b, /* Belkin F5D7051 */ + RNDIS_MASTER_INTERFACE, + .driver_info = (unsigned long) &bcm4320b_info, +}, { + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x13b1, + .idProduct = 0x0014, /* Linksys WUSB54GSv2 */ + RNDIS_MASTER_INTERFACE, + .driver_info = (unsigned long) &bcm4320b_info, +}, { + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x13b1, + .idProduct = 0x0026, /* Linksys WUSB54GSC */ + RNDIS_MASTER_INTERFACE, + .driver_info = (unsigned long) &bcm4320b_info, +}, { + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0b05, + .idProduct = 0x1717, /* Asus WL169gE */ + RNDIS_MASTER_INTERFACE, + .driver_info = (unsigned long) &bcm4320b_info, +}, { + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0a5c, + .idProduct = 0xd11b, /* Eminent EM4045 */ + RNDIS_MASTER_INTERFACE, + .driver_info = (unsigned long) &bcm4320b_info, +}, { + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x1690, + .idProduct = 0x0715, /* BT Voyager 1055 */ + RNDIS_MASTER_INTERFACE, + .driver_info = (unsigned long) &bcm4320b_info, +}, +/* These devices have DriverVer < 4.xx.xx.xx and do not have any custom + * parameters available, hardware probably contain older firmware version with + * no way of updating. Chipset marked as 'BCM4320????' in NDISwrapper-wiki. + */ +{ + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x13b1, + .idProduct = 0x000e, /* Linksys WUSB54GSv1 */ + RNDIS_MASTER_INTERFACE, + .driver_info = (unsigned long) &bcm4320a_info, +}, { + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0baf, + .idProduct = 0x0111, /* U.S. Robotics USR5420 */ + RNDIS_MASTER_INTERFACE, + .driver_info = (unsigned long) &bcm4320a_info, +}, { + .match_flags = USB_DEVICE_ID_MATCH_INT_INFO + | USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0411, + .idProduct = 0x004b, /* BUFFALO WLI-USB-G54 */ + RNDIS_MASTER_INTERFACE, + .driver_info = (unsigned long) &bcm4320a_info, +}, +/* Generic Wireless RNDIS devices that we don't have exact + * idVendor/idProduct/chip yet. + */ +{ + /* RNDIS is MSFT's un-official variant of CDC ACM */ + USB_INTERFACE_INFO(USB_CLASS_COMM, 2 /* ACM */, 0x0ff), + .driver_info = (unsigned long) &rndis_wlan_info, +}, { + /* "ActiveSync" is an undocumented variant of RNDIS, used in WM5 */ + USB_INTERFACE_INFO(USB_CLASS_MISC, 1, 1), + .driver_info = (unsigned long) &rndis_wlan_info, +}, + { }, // END +}; +MODULE_DEVICE_TABLE(usb, products); + +static struct usb_driver rndis_wlan_driver = { + .name = "rndis_wlan", + .id_table = products, + .probe = usbnet_probe, + .disconnect = usbnet_disconnect, + .suspend = usbnet_suspend, + .resume = usbnet_resume, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +module_usb_driver(rndis_wlan_driver); + +MODULE_AUTHOR("Bjorge Dijkstra"); +MODULE_AUTHOR("Jussi Kivilinna"); +MODULE_DESCRIPTION("Driver for RNDIS based USB Wireless adapters"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/net/wireless/rt2x00/Kconfig b/drivers/net/wireless/rt2x00/Kconfig new file mode 100644 index 0000000..f4419a7 --- /dev/null +++ b/drivers/net/wireless/rt2x00/Kconfig @@ -0,0 +1,257 @@ +menuconfig RT2X00 + tristate "Ralink driver support" + depends on m + depends on MAC80211 + ---help--- + This will enable the support for the Ralink drivers, + developed in the rt2x00 project . + + These drivers make use of the mac80211 stack. + + When building one of the individual drivers, the rt2x00 library + will also be created. That library (when the driver is built as + a module) will be called rt2x00lib. + + Additionally PCI and USB libraries will also be build depending + on the types of drivers being selected, these libraries will be + called rt2x00pci and rt2x00usb. + +if RT2X00 + +config RT2400PCI + tristate "Ralink rt2400 (PCI/PCMCIA) support" + depends on m + depends on PCI + select RT2X00_LIB_MMIO + select RT2X00_LIB_PCI + depends on EEPROM_93CX6 + ---help--- + This adds support for rt2400 wireless chipset family. + Supported chips: RT2460. + + When compiled as a module, this driver will be called rt2400pci. + +config RT2500PCI + tristate "Ralink rt2500 (PCI/PCMCIA) support" + depends on m + depends on PCI + select RT2X00_LIB_MMIO + select RT2X00_LIB_PCI + depends on EEPROM_93CX6 + ---help--- + This adds support for rt2500 wireless chipset family. + Supported chips: RT2560. + + When compiled as a module, this driver will be called rt2500pci. + +config RT61PCI + tristate "Ralink rt2501/rt61 (PCI/PCMCIA) support" + depends on m + depends on PCI + select RT2X00_LIB_PCI + select RT2X00_LIB_MMIO + select RT2X00_LIB_FIRMWARE + select RT2X00_LIB_CRYPTO + depends on CRC_ITU_T + depends on EEPROM_93CX6 + ---help--- + This adds support for rt2501 wireless chipset family. + Supported chips: RT2561, RT2561S & RT2661. + + When compiled as a module, this driver will be called rt61pci. + +config RT2800PCI + tristate "Ralink rt27xx/rt28xx/rt30xx (PCI/PCIe/PCMCIA) support" + depends on m + depends on PCI || SOC_RT288X || SOC_RT305X + select RT2800_LIB + select RT2X00_LIB_MMIO + select RT2X00_LIB_PCI if PCI + select RT2X00_LIB_SOC if SOC_RT288X || SOC_RT305X + select RT2X00_LIB_FIRMWARE + select RT2X00_LIB_CRYPTO + depends on CRC_CCITT + depends on EEPROM_93CX6 + ---help--- + This adds support for rt27xx/rt28xx/rt30xx wireless chipset family. + Supported chips: RT2760, RT2790, RT2860, RT2880, RT2890, RT3052, + RT3090, RT3091 & RT3092 + + When compiled as a module, this driver will be called "rt2800pci.ko". + +if RT2800PCI + +config RT2800PCI_RT33XX + bool "rt2800pci - Include support for rt33xx devices" + default y + ---help--- + This adds support for rt33xx wireless chipset family to the + rt2800pci driver. + Supported chips: RT3390 + +config RT2800PCI_RT35XX + bool "rt2800pci - Include support for rt35xx devices (EXPERIMENTAL)" + default y + ---help--- + This adds support for rt35xx wireless chipset family to the + rt2800pci driver. + Supported chips: RT3060, RT3062, RT3562, RT3592 + + +config RT2800PCI_RT53XX + bool "rt2800pci - Include support for rt53xx devices (EXPERIMENTAL)" + default y + ---help--- + This adds support for rt53xx wireless chipset family to the + rt2800pci driver. + Supported chips: RT5390 + +config RT2800PCI_RT3290 + bool "rt2800pci - Include support for rt3290 devices (EXPERIMENTAL)" + default y + ---help--- + This adds support for rt3290 wireless chipset family to the + rt2800pci driver. + Supported chips: RT3290 +endif + +config RT2500USB + tristate "Ralink rt2500 (USB) support" + depends on m + depends on USB + select RT2X00_LIB_USB + select RT2X00_LIB_CRYPTO + ---help--- + This adds support for rt2500 wireless chipset family. + Supported chips: RT2571 & RT2572. + + When compiled as a module, this driver will be called rt2500usb. + +config RT73USB + tristate "Ralink rt2501/rt73 (USB) support" + depends on m + depends on USB + select RT2X00_LIB_USB + select RT2X00_LIB_FIRMWARE + select RT2X00_LIB_CRYPTO + depends on CRC_ITU_T + ---help--- + This adds support for rt2501 wireless chipset family. + Supported chips: RT2571W, RT2573 & RT2671. + + When compiled as a module, this driver will be called rt73usb. + +config RT2800USB + tristate "Ralink rt27xx/rt28xx/rt30xx (USB) support" + depends on m + depends on USB + select RT2800_LIB + select RT2X00_LIB_USB + select RT2X00_LIB_FIRMWARE + select RT2X00_LIB_CRYPTO + depends on CRC_CCITT + ---help--- + This adds support for rt27xx/rt28xx/rt30xx wireless chipset family. + Supported chips: RT2770, RT2870 & RT3070, RT3071 & RT3072 + + When compiled as a module, this driver will be called "rt2800usb.ko". + +if RT2800USB + +config RT2800USB_RT33XX + bool "rt2800usb - Include support for rt33xx devices" + default y + ---help--- + This adds support for rt33xx wireless chipset family to the + rt2800usb driver. + Supported chips: RT3370 + +config RT2800USB_RT35XX + bool "rt2800usb - Include support for rt35xx devices (EXPERIMENTAL)" + default y + ---help--- + This adds support for rt35xx wireless chipset family to the + rt2800usb driver. + Supported chips: RT3572 + +config RT2800USB_RT53XX + bool "rt2800usb - Include support for rt53xx devices (EXPERIMENTAL)" + ---help--- + This adds support for rt53xx wireless chipset family to the + rt2800usb driver. + Supported chips: RT5370 + +config RT2800USB_RT55XX + bool "rt2800usb - Include support for rt55xx devices (EXPERIMENTAL)" + ---help--- + This adds support for rt55xx wireless chipset family to the + rt2800usb driver. + Supported chips: RT5572 + +config RT2800USB_UNKNOWN + bool "rt2800usb - Include support for unknown (USB) devices" + default n + ---help--- + This adds support for rt2800usb devices that are known to + have a rt28xx family compatible chipset, but for which the exact + chipset is unknown. + + Support status for these devices is unknown, and enabling these + devices may or may not work. + +endif + +config RT2800_LIB + tristate + depends on m + +config RT2X00_LIB_MMIO + tristate + depends on m + +config RT2X00_LIB_PCI + tristate + depends on m + select RT2X00_LIB + +config RT2X00_LIB_SOC + tristate + depends on m + select RT2X00_LIB + +config RT2X00_LIB_USB + tristate + depends on m + select RT2X00_LIB + +config RT2X00_LIB + tristate + depends on m + +config RT2X00_LIB_FIRMWARE + boolean + select BACKPORT_FW_LOADER + +config RT2X00_LIB_CRYPTO + boolean + +config RT2X00_LIB_LEDS + depends on !BACKPORT_KERNEL_2_6_25 + boolean + default y if (RT2X00_LIB=y && LEDS_CLASS=y) || (RT2X00_LIB=m && LEDS_CLASS!=n) + +config RT2X00_LIB_DEBUGFS + bool "Ralink debugfs support" + depends on RT2X00_LIB && MAC80211_DEBUGFS + ---help--- + Enable creation of debugfs files for the rt2x00 drivers. + These debugfs files support both reading and writing of the + most important register types of the rt2x00 hardware. + +config RT2X00_DEBUG + bool "Ralink debug output" + depends on RT2X00_LIB + ---help--- + Enable debugging output for all rt2x00 modules + +endif diff --git a/drivers/net/wireless/rt2x00/Makefile b/drivers/net/wireless/rt2x00/Makefile new file mode 100644 index 0000000..14fb11d --- /dev/null +++ b/drivers/net/wireless/rt2x00/Makefile @@ -0,0 +1,23 @@ +rt2x00lib-y += rt2x00dev.o +rt2x00lib-y += rt2x00mac.o +rt2x00lib-y += rt2x00config.o +rt2x00lib-y += rt2x00queue.o +rt2x00lib-y += rt2x00link.o +rt2x00lib-$(CPTCFG_RT2X00_LIB_DEBUGFS) += rt2x00debug.o +rt2x00lib-$(CPTCFG_RT2X00_LIB_CRYPTO) += rt2x00crypto.o +rt2x00lib-$(CPTCFG_RT2X00_LIB_FIRMWARE) += rt2x00firmware.o +rt2x00lib-$(CPTCFG_RT2X00_LIB_LEDS) += rt2x00leds.o + +obj-$(CPTCFG_RT2X00_LIB) += rt2x00lib.o +obj-$(CPTCFG_RT2X00_LIB_MMIO) += rt2x00mmio.o +obj-$(CPTCFG_RT2X00_LIB_PCI) += rt2x00pci.o +obj-$(CPTCFG_RT2X00_LIB_SOC) += rt2x00soc.o +obj-$(CPTCFG_RT2X00_LIB_USB) += rt2x00usb.o +obj-$(CPTCFG_RT2800_LIB) += rt2800lib.o +obj-$(CPTCFG_RT2400PCI) += rt2400pci.o +obj-$(CPTCFG_RT2500PCI) += rt2500pci.o +obj-$(CPTCFG_RT61PCI) += rt61pci.o +obj-$(CPTCFG_RT2800PCI) += rt2800pci.o +obj-$(CPTCFG_RT2500USB) += rt2500usb.o +obj-$(CPTCFG_RT73USB) += rt73usb.o +obj-$(CPTCFG_RT2800USB) += rt2800usb.o diff --git a/drivers/net/wireless/rt2x00/rt2400pci.c b/drivers/net/wireless/rt2x00/rt2400pci.c new file mode 100644 index 0000000..fe0350f --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2400pci.c @@ -0,0 +1,1847 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2400pci + Abstract: rt2400pci device specific routines. + Supported chipsets: RT2460. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rt2x00.h" +#include "rt2x00mmio.h" +#include "rt2x00pci.h" +#include "rt2400pci.h" + +/* + * Register access. + * All access to the CSR registers will go through the methods + * rt2x00mmio_register_read and rt2x00mmio_register_write. + * BBP and RF register require indirect register access, + * and use the CSR registers BBPCSR and RFCSR to achieve this. + * These indirect registers work with busy bits, + * and we will try maximal REGISTER_BUSY_COUNT times to access + * the register while taking a REGISTER_BUSY_DELAY us delay + * between each attempt. When the busy bit is still set at that time, + * the access attempt is considered to have failed, + * and we will print an error. + */ +#define WAIT_FOR_BBP(__dev, __reg) \ + rt2x00mmio_regbusy_read((__dev), BBPCSR, BBPCSR_BUSY, (__reg)) +#define WAIT_FOR_RF(__dev, __reg) \ + rt2x00mmio_regbusy_read((__dev), RFCSR, RFCSR_BUSY, (__reg)) + +static void rt2400pci_bbp_write(struct rt2x00_dev *rt2x00dev, + const unsigned int word, const u8 value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the BBP becomes available, afterwards we + * can safely write the new data into the register. + */ + if (WAIT_FOR_BBP(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, BBPCSR_VALUE, value); + rt2x00_set_field32(®, BBPCSR_REGNUM, word); + rt2x00_set_field32(®, BBPCSR_BUSY, 1); + rt2x00_set_field32(®, BBPCSR_WRITE_CONTROL, 1); + + rt2x00mmio_register_write(rt2x00dev, BBPCSR, reg); + } + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt2400pci_bbp_read(struct rt2x00_dev *rt2x00dev, + const unsigned int word, u8 *value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the BBP becomes available, afterwards we + * can safely write the read request into the register. + * After the data has been written, we wait until hardware + * returns the correct value, if at any time the register + * doesn't become available in time, reg will be 0xffffffff + * which means we return 0xff to the caller. + */ + if (WAIT_FOR_BBP(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, BBPCSR_REGNUM, word); + rt2x00_set_field32(®, BBPCSR_BUSY, 1); + rt2x00_set_field32(®, BBPCSR_WRITE_CONTROL, 0); + + rt2x00mmio_register_write(rt2x00dev, BBPCSR, reg); + + WAIT_FOR_BBP(rt2x00dev, ®); + } + + *value = rt2x00_get_field32(reg, BBPCSR_VALUE); + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt2400pci_rf_write(struct rt2x00_dev *rt2x00dev, + const unsigned int word, const u32 value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the RF becomes available, afterwards we + * can safely write the new data into the register. + */ + if (WAIT_FOR_RF(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, RFCSR_VALUE, value); + rt2x00_set_field32(®, RFCSR_NUMBER_OF_BITS, 20); + rt2x00_set_field32(®, RFCSR_IF_SELECT, 0); + rt2x00_set_field32(®, RFCSR_BUSY, 1); + + rt2x00mmio_register_write(rt2x00dev, RFCSR, reg); + rt2x00_rf_write(rt2x00dev, word, value); + } + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt2400pci_eepromregister_read(struct eeprom_93cx6 *eeprom) +{ + struct rt2x00_dev *rt2x00dev = eeprom->data; + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, CSR21, ®); + + eeprom->reg_data_in = !!rt2x00_get_field32(reg, CSR21_EEPROM_DATA_IN); + eeprom->reg_data_out = !!rt2x00_get_field32(reg, CSR21_EEPROM_DATA_OUT); + eeprom->reg_data_clock = + !!rt2x00_get_field32(reg, CSR21_EEPROM_DATA_CLOCK); + eeprom->reg_chip_select = + !!rt2x00_get_field32(reg, CSR21_EEPROM_CHIP_SELECT); +} + +static void rt2400pci_eepromregister_write(struct eeprom_93cx6 *eeprom) +{ + struct rt2x00_dev *rt2x00dev = eeprom->data; + u32 reg = 0; + + rt2x00_set_field32(®, CSR21_EEPROM_DATA_IN, !!eeprom->reg_data_in); + rt2x00_set_field32(®, CSR21_EEPROM_DATA_OUT, !!eeprom->reg_data_out); + rt2x00_set_field32(®, CSR21_EEPROM_DATA_CLOCK, + !!eeprom->reg_data_clock); + rt2x00_set_field32(®, CSR21_EEPROM_CHIP_SELECT, + !!eeprom->reg_chip_select); + + rt2x00mmio_register_write(rt2x00dev, CSR21, reg); +} + +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS +static const struct rt2x00debug rt2400pci_rt2x00debug = { + .owner = THIS_MODULE, + .csr = { + .read = rt2x00mmio_register_read, + .write = rt2x00mmio_register_write, + .flags = RT2X00DEBUGFS_OFFSET, + .word_base = CSR_REG_BASE, + .word_size = sizeof(u32), + .word_count = CSR_REG_SIZE / sizeof(u32), + }, + .eeprom = { + .read = rt2x00_eeprom_read, + .write = rt2x00_eeprom_write, + .word_base = EEPROM_BASE, + .word_size = sizeof(u16), + .word_count = EEPROM_SIZE / sizeof(u16), + }, + .bbp = { + .read = rt2400pci_bbp_read, + .write = rt2400pci_bbp_write, + .word_base = BBP_BASE, + .word_size = sizeof(u8), + .word_count = BBP_SIZE / sizeof(u8), + }, + .rf = { + .read = rt2x00_rf_read, + .write = rt2400pci_rf_write, + .word_base = RF_BASE, + .word_size = sizeof(u32), + .word_count = RF_SIZE / sizeof(u32), + }, +}; +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ + +static int rt2400pci_rfkill_poll(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, GPIOCSR, ®); + return rt2x00_get_field32(reg, GPIOCSR_VAL0); +} + +#ifdef CPTCFG_RT2X00_LIB_LEDS +static void rt2400pci_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct rt2x00_led *led = + container_of(led_cdev, struct rt2x00_led, led_dev); + unsigned int enabled = brightness != LED_OFF; + u32 reg; + + rt2x00mmio_register_read(led->rt2x00dev, LEDCSR, ®); + + if (led->type == LED_TYPE_RADIO || led->type == LED_TYPE_ASSOC) + rt2x00_set_field32(®, LEDCSR_LINK, enabled); + else if (led->type == LED_TYPE_ACTIVITY) + rt2x00_set_field32(®, LEDCSR_ACTIVITY, enabled); + + rt2x00mmio_register_write(led->rt2x00dev, LEDCSR, reg); +} + +static int rt2400pci_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct rt2x00_led *led = + container_of(led_cdev, struct rt2x00_led, led_dev); + u32 reg; + + rt2x00mmio_register_read(led->rt2x00dev, LEDCSR, ®); + rt2x00_set_field32(®, LEDCSR_ON_PERIOD, *delay_on); + rt2x00_set_field32(®, LEDCSR_OFF_PERIOD, *delay_off); + rt2x00mmio_register_write(led->rt2x00dev, LEDCSR, reg); + + return 0; +} + +static void rt2400pci_init_led(struct rt2x00_dev *rt2x00dev, + struct rt2x00_led *led, + enum led_type type) +{ + led->rt2x00dev = rt2x00dev; + led->type = type; + led->led_dev.brightness_set = rt2400pci_brightness_set; + led->led_dev.blink_set = rt2400pci_blink_set; + led->flags = LED_INITIALIZED; +} +#endif /* CPTCFG_RT2X00_LIB_LEDS */ + +/* + * Configuration handlers. + */ +static void rt2400pci_config_filter(struct rt2x00_dev *rt2x00dev, + const unsigned int filter_flags) +{ + u32 reg; + + /* + * Start configuration steps. + * Note that the version error will always be dropped + * since there is no filter for it at this time. + */ + rt2x00mmio_register_read(rt2x00dev, RXCSR0, ®); + rt2x00_set_field32(®, RXCSR0_DROP_CRC, + !(filter_flags & FIF_FCSFAIL)); + rt2x00_set_field32(®, RXCSR0_DROP_PHYSICAL, + !(filter_flags & FIF_PLCPFAIL)); + rt2x00_set_field32(®, RXCSR0_DROP_CONTROL, + !(filter_flags & FIF_CONTROL)); + rt2x00_set_field32(®, RXCSR0_DROP_NOT_TO_ME, + !(filter_flags & FIF_PROMISC_IN_BSS)); + rt2x00_set_field32(®, RXCSR0_DROP_TODS, + !(filter_flags & FIF_PROMISC_IN_BSS) && + !rt2x00dev->intf_ap_count); + rt2x00_set_field32(®, RXCSR0_DROP_VERSION_ERROR, 1); + rt2x00mmio_register_write(rt2x00dev, RXCSR0, reg); +} + +static void rt2400pci_config_intf(struct rt2x00_dev *rt2x00dev, + struct rt2x00_intf *intf, + struct rt2x00intf_conf *conf, + const unsigned int flags) +{ + unsigned int bcn_preload; + u32 reg; + + if (flags & CONFIG_UPDATE_TYPE) { + /* + * Enable beacon config + */ + bcn_preload = PREAMBLE + GET_DURATION(IEEE80211_HEADER, 20); + rt2x00mmio_register_read(rt2x00dev, BCNCSR1, ®); + rt2x00_set_field32(®, BCNCSR1_PRELOAD, bcn_preload); + rt2x00mmio_register_write(rt2x00dev, BCNCSR1, reg); + + /* + * Enable synchronisation. + */ + rt2x00mmio_register_read(rt2x00dev, CSR14, ®); + rt2x00_set_field32(®, CSR14_TSF_SYNC, conf->sync); + rt2x00mmio_register_write(rt2x00dev, CSR14, reg); + } + + if (flags & CONFIG_UPDATE_MAC) + rt2x00mmio_register_multiwrite(rt2x00dev, CSR3, + conf->mac, sizeof(conf->mac)); + + if (flags & CONFIG_UPDATE_BSSID) + rt2x00mmio_register_multiwrite(rt2x00dev, CSR5, + conf->bssid, + sizeof(conf->bssid)); +} + +static void rt2400pci_config_erp(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_erp *erp, + u32 changed) +{ + int preamble_mask; + u32 reg; + + /* + * When short preamble is enabled, we should set bit 0x08 + */ + if (changed & BSS_CHANGED_ERP_PREAMBLE) { + preamble_mask = erp->short_preamble << 3; + + rt2x00mmio_register_read(rt2x00dev, TXCSR1, ®); + rt2x00_set_field32(®, TXCSR1_ACK_TIMEOUT, 0x1ff); + rt2x00_set_field32(®, TXCSR1_ACK_CONSUME_TIME, 0x13a); + rt2x00_set_field32(®, TXCSR1_TSF_OFFSET, IEEE80211_HEADER); + rt2x00_set_field32(®, TXCSR1_AUTORESPONDER, 1); + rt2x00mmio_register_write(rt2x00dev, TXCSR1, reg); + + rt2x00mmio_register_read(rt2x00dev, ARCSR2, ®); + rt2x00_set_field32(®, ARCSR2_SIGNAL, 0x00); + rt2x00_set_field32(®, ARCSR2_SERVICE, 0x04); + rt2x00_set_field32(®, ARCSR2_LENGTH, + GET_DURATION(ACK_SIZE, 10)); + rt2x00mmio_register_write(rt2x00dev, ARCSR2, reg); + + rt2x00mmio_register_read(rt2x00dev, ARCSR3, ®); + rt2x00_set_field32(®, ARCSR3_SIGNAL, 0x01 | preamble_mask); + rt2x00_set_field32(®, ARCSR3_SERVICE, 0x04); + rt2x00_set_field32(®, ARCSR2_LENGTH, + GET_DURATION(ACK_SIZE, 20)); + rt2x00mmio_register_write(rt2x00dev, ARCSR3, reg); + + rt2x00mmio_register_read(rt2x00dev, ARCSR4, ®); + rt2x00_set_field32(®, ARCSR4_SIGNAL, 0x02 | preamble_mask); + rt2x00_set_field32(®, ARCSR4_SERVICE, 0x04); + rt2x00_set_field32(®, ARCSR2_LENGTH, + GET_DURATION(ACK_SIZE, 55)); + rt2x00mmio_register_write(rt2x00dev, ARCSR4, reg); + + rt2x00mmio_register_read(rt2x00dev, ARCSR5, ®); + rt2x00_set_field32(®, ARCSR5_SIGNAL, 0x03 | preamble_mask); + rt2x00_set_field32(®, ARCSR5_SERVICE, 0x84); + rt2x00_set_field32(®, ARCSR2_LENGTH, + GET_DURATION(ACK_SIZE, 110)); + rt2x00mmio_register_write(rt2x00dev, ARCSR5, reg); + } + + if (changed & BSS_CHANGED_BASIC_RATES) + rt2x00mmio_register_write(rt2x00dev, ARCSR1, erp->basic_rates); + + if (changed & BSS_CHANGED_ERP_SLOT) { + rt2x00mmio_register_read(rt2x00dev, CSR11, ®); + rt2x00_set_field32(®, CSR11_SLOT_TIME, erp->slot_time); + rt2x00mmio_register_write(rt2x00dev, CSR11, reg); + + rt2x00mmio_register_read(rt2x00dev, CSR18, ®); + rt2x00_set_field32(®, CSR18_SIFS, erp->sifs); + rt2x00_set_field32(®, CSR18_PIFS, erp->pifs); + rt2x00mmio_register_write(rt2x00dev, CSR18, reg); + + rt2x00mmio_register_read(rt2x00dev, CSR19, ®); + rt2x00_set_field32(®, CSR19_DIFS, erp->difs); + rt2x00_set_field32(®, CSR19_EIFS, erp->eifs); + rt2x00mmio_register_write(rt2x00dev, CSR19, reg); + } + + if (changed & BSS_CHANGED_BEACON_INT) { + rt2x00mmio_register_read(rt2x00dev, CSR12, ®); + rt2x00_set_field32(®, CSR12_BEACON_INTERVAL, + erp->beacon_int * 16); + rt2x00_set_field32(®, CSR12_CFP_MAX_DURATION, + erp->beacon_int * 16); + rt2x00mmio_register_write(rt2x00dev, CSR12, reg); + } +} + +static void rt2400pci_config_ant(struct rt2x00_dev *rt2x00dev, + struct antenna_setup *ant) +{ + u8 r1; + u8 r4; + + /* + * We should never come here because rt2x00lib is supposed + * to catch this and send us the correct antenna explicitely. + */ + BUG_ON(ant->rx == ANTENNA_SW_DIVERSITY || + ant->tx == ANTENNA_SW_DIVERSITY); + + rt2400pci_bbp_read(rt2x00dev, 4, &r4); + rt2400pci_bbp_read(rt2x00dev, 1, &r1); + + /* + * Configure the TX antenna. + */ + switch (ant->tx) { + case ANTENNA_HW_DIVERSITY: + rt2x00_set_field8(&r1, BBP_R1_TX_ANTENNA, 1); + break; + case ANTENNA_A: + rt2x00_set_field8(&r1, BBP_R1_TX_ANTENNA, 0); + break; + case ANTENNA_B: + default: + rt2x00_set_field8(&r1, BBP_R1_TX_ANTENNA, 2); + break; + } + + /* + * Configure the RX antenna. + */ + switch (ant->rx) { + case ANTENNA_HW_DIVERSITY: + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA, 1); + break; + case ANTENNA_A: + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA, 0); + break; + case ANTENNA_B: + default: + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA, 2); + break; + } + + rt2400pci_bbp_write(rt2x00dev, 4, r4); + rt2400pci_bbp_write(rt2x00dev, 1, r1); +} + +static void rt2400pci_config_channel(struct rt2x00_dev *rt2x00dev, + struct rf_channel *rf) +{ + /* + * Switch on tuning bits. + */ + rt2x00_set_field32(&rf->rf1, RF1_TUNER, 1); + rt2x00_set_field32(&rf->rf3, RF3_TUNER, 1); + + rt2400pci_rf_write(rt2x00dev, 1, rf->rf1); + rt2400pci_rf_write(rt2x00dev, 2, rf->rf2); + rt2400pci_rf_write(rt2x00dev, 3, rf->rf3); + + /* + * RF2420 chipset don't need any additional actions. + */ + if (rt2x00_rf(rt2x00dev, RF2420)) + return; + + /* + * For the RT2421 chipsets we need to write an invalid + * reference clock rate to activate auto_tune. + * After that we set the value back to the correct channel. + */ + rt2400pci_rf_write(rt2x00dev, 1, rf->rf1); + rt2400pci_rf_write(rt2x00dev, 2, 0x000c2a32); + rt2400pci_rf_write(rt2x00dev, 3, rf->rf3); + + msleep(1); + + rt2400pci_rf_write(rt2x00dev, 1, rf->rf1); + rt2400pci_rf_write(rt2x00dev, 2, rf->rf2); + rt2400pci_rf_write(rt2x00dev, 3, rf->rf3); + + msleep(1); + + /* + * Switch off tuning bits. + */ + rt2x00_set_field32(&rf->rf1, RF1_TUNER, 0); + rt2x00_set_field32(&rf->rf3, RF3_TUNER, 0); + + rt2400pci_rf_write(rt2x00dev, 1, rf->rf1); + rt2400pci_rf_write(rt2x00dev, 3, rf->rf3); + + /* + * Clear false CRC during channel switch. + */ + rt2x00mmio_register_read(rt2x00dev, CNT0, &rf->rf1); +} + +static void rt2400pci_config_txpower(struct rt2x00_dev *rt2x00dev, int txpower) +{ + rt2400pci_bbp_write(rt2x00dev, 3, TXPOWER_TO_DEV(txpower)); +} + +static void rt2400pci_config_retry_limit(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf) +{ + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, CSR11, ®); + rt2x00_set_field32(®, CSR11_LONG_RETRY, + libconf->conf->long_frame_max_tx_count); + rt2x00_set_field32(®, CSR11_SHORT_RETRY, + libconf->conf->short_frame_max_tx_count); + rt2x00mmio_register_write(rt2x00dev, CSR11, reg); +} + +static void rt2400pci_config_ps(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf) +{ + enum dev_state state = + (libconf->conf->flags & IEEE80211_CONF_PS) ? + STATE_SLEEP : STATE_AWAKE; + u32 reg; + + if (state == STATE_SLEEP) { + rt2x00mmio_register_read(rt2x00dev, CSR20, ®); + rt2x00_set_field32(®, CSR20_DELAY_AFTER_TBCN, + (rt2x00dev->beacon_int - 20) * 16); + rt2x00_set_field32(®, CSR20_TBCN_BEFORE_WAKEUP, + libconf->conf->listen_interval - 1); + + /* We must first disable autowake before it can be enabled */ + rt2x00_set_field32(®, CSR20_AUTOWAKE, 0); + rt2x00mmio_register_write(rt2x00dev, CSR20, reg); + + rt2x00_set_field32(®, CSR20_AUTOWAKE, 1); + rt2x00mmio_register_write(rt2x00dev, CSR20, reg); + } else { + rt2x00mmio_register_read(rt2x00dev, CSR20, ®); + rt2x00_set_field32(®, CSR20_AUTOWAKE, 0); + rt2x00mmio_register_write(rt2x00dev, CSR20, reg); + } + + rt2x00dev->ops->lib->set_device_state(rt2x00dev, state); +} + +static void rt2400pci_config(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf, + const unsigned int flags) +{ + if (flags & IEEE80211_CONF_CHANGE_CHANNEL) + rt2400pci_config_channel(rt2x00dev, &libconf->rf); + if (flags & IEEE80211_CONF_CHANGE_POWER) + rt2400pci_config_txpower(rt2x00dev, + libconf->conf->power_level); + if (flags & IEEE80211_CONF_CHANGE_RETRY_LIMITS) + rt2400pci_config_retry_limit(rt2x00dev, libconf); + if (flags & IEEE80211_CONF_CHANGE_PS) + rt2400pci_config_ps(rt2x00dev, libconf); +} + +static void rt2400pci_config_cw(struct rt2x00_dev *rt2x00dev, + const int cw_min, const int cw_max) +{ + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, CSR11, ®); + rt2x00_set_field32(®, CSR11_CWMIN, cw_min); + rt2x00_set_field32(®, CSR11_CWMAX, cw_max); + rt2x00mmio_register_write(rt2x00dev, CSR11, reg); +} + +/* + * Link tuning + */ +static void rt2400pci_link_stats(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual) +{ + u32 reg; + u8 bbp; + + /* + * Update FCS error count from register. + */ + rt2x00mmio_register_read(rt2x00dev, CNT0, ®); + qual->rx_failed = rt2x00_get_field32(reg, CNT0_FCS_ERROR); + + /* + * Update False CCA count from register. + */ + rt2400pci_bbp_read(rt2x00dev, 39, &bbp); + qual->false_cca = bbp; +} + +static inline void rt2400pci_set_vgc(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual, u8 vgc_level) +{ + if (qual->vgc_level_reg != vgc_level) { + rt2400pci_bbp_write(rt2x00dev, 13, vgc_level); + qual->vgc_level = vgc_level; + qual->vgc_level_reg = vgc_level; + } +} + +static void rt2400pci_reset_tuner(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual) +{ + rt2400pci_set_vgc(rt2x00dev, qual, 0x08); +} + +static void rt2400pci_link_tuner(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual, const u32 count) +{ + /* + * The link tuner should not run longer then 60 seconds, + * and should run once every 2 seconds. + */ + if (count > 60 || !(count & 1)) + return; + + /* + * Base r13 link tuning on the false cca count. + */ + if ((qual->false_cca > 512) && (qual->vgc_level < 0x20)) + rt2400pci_set_vgc(rt2x00dev, qual, ++qual->vgc_level); + else if ((qual->false_cca < 100) && (qual->vgc_level > 0x08)) + rt2400pci_set_vgc(rt2x00dev, qual, --qual->vgc_level); +} + +/* + * Queue handlers. + */ +static void rt2400pci_start_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u32 reg; + + switch (queue->qid) { + case QID_RX: + rt2x00mmio_register_read(rt2x00dev, RXCSR0, ®); + rt2x00_set_field32(®, RXCSR0_DISABLE_RX, 0); + rt2x00mmio_register_write(rt2x00dev, RXCSR0, reg); + break; + case QID_BEACON: + rt2x00mmio_register_read(rt2x00dev, CSR14, ®); + rt2x00_set_field32(®, CSR14_TSF_COUNT, 1); + rt2x00_set_field32(®, CSR14_TBCN, 1); + rt2x00_set_field32(®, CSR14_BEACON_GEN, 1); + rt2x00mmio_register_write(rt2x00dev, CSR14, reg); + break; + default: + break; + } +} + +static void rt2400pci_kick_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u32 reg; + + switch (queue->qid) { + case QID_AC_VO: + rt2x00mmio_register_read(rt2x00dev, TXCSR0, ®); + rt2x00_set_field32(®, TXCSR0_KICK_PRIO, 1); + rt2x00mmio_register_write(rt2x00dev, TXCSR0, reg); + break; + case QID_AC_VI: + rt2x00mmio_register_read(rt2x00dev, TXCSR0, ®); + rt2x00_set_field32(®, TXCSR0_KICK_TX, 1); + rt2x00mmio_register_write(rt2x00dev, TXCSR0, reg); + break; + case QID_ATIM: + rt2x00mmio_register_read(rt2x00dev, TXCSR0, ®); + rt2x00_set_field32(®, TXCSR0_KICK_ATIM, 1); + rt2x00mmio_register_write(rt2x00dev, TXCSR0, reg); + break; + default: + break; + } +} + +static void rt2400pci_stop_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u32 reg; + + switch (queue->qid) { + case QID_AC_VO: + case QID_AC_VI: + case QID_ATIM: + rt2x00mmio_register_read(rt2x00dev, TXCSR0, ®); + rt2x00_set_field32(®, TXCSR0_ABORT, 1); + rt2x00mmio_register_write(rt2x00dev, TXCSR0, reg); + break; + case QID_RX: + rt2x00mmio_register_read(rt2x00dev, RXCSR0, ®); + rt2x00_set_field32(®, RXCSR0_DISABLE_RX, 1); + rt2x00mmio_register_write(rt2x00dev, RXCSR0, reg); + break; + case QID_BEACON: + rt2x00mmio_register_read(rt2x00dev, CSR14, ®); + rt2x00_set_field32(®, CSR14_TSF_COUNT, 0); + rt2x00_set_field32(®, CSR14_TBCN, 0); + rt2x00_set_field32(®, CSR14_BEACON_GEN, 0); + rt2x00mmio_register_write(rt2x00dev, CSR14, reg); + + /* + * Wait for possibly running tbtt tasklets. + */ + tasklet_kill(&rt2x00dev->tbtt_tasklet); + break; + default: + break; + } +} + +/* + * Initialization functions. + */ +static bool rt2400pci_get_entry_state(struct queue_entry *entry) +{ + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + u32 word; + + if (entry->queue->qid == QID_RX) { + rt2x00_desc_read(entry_priv->desc, 0, &word); + + return rt2x00_get_field32(word, RXD_W0_OWNER_NIC); + } else { + rt2x00_desc_read(entry_priv->desc, 0, &word); + + return (rt2x00_get_field32(word, TXD_W0_OWNER_NIC) || + rt2x00_get_field32(word, TXD_W0_VALID)); + } +} + +static void rt2400pci_clear_entry(struct queue_entry *entry) +{ + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + u32 word; + + if (entry->queue->qid == QID_RX) { + rt2x00_desc_read(entry_priv->desc, 2, &word); + rt2x00_set_field32(&word, RXD_W2_BUFFER_LENGTH, entry->skb->len); + rt2x00_desc_write(entry_priv->desc, 2, word); + + rt2x00_desc_read(entry_priv->desc, 1, &word); + rt2x00_set_field32(&word, RXD_W1_BUFFER_ADDRESS, skbdesc->skb_dma); + rt2x00_desc_write(entry_priv->desc, 1, word); + + rt2x00_desc_read(entry_priv->desc, 0, &word); + rt2x00_set_field32(&word, RXD_W0_OWNER_NIC, 1); + rt2x00_desc_write(entry_priv->desc, 0, word); + } else { + rt2x00_desc_read(entry_priv->desc, 0, &word); + rt2x00_set_field32(&word, TXD_W0_VALID, 0); + rt2x00_set_field32(&word, TXD_W0_OWNER_NIC, 0); + rt2x00_desc_write(entry_priv->desc, 0, word); + } +} + +static int rt2400pci_init_queues(struct rt2x00_dev *rt2x00dev) +{ + struct queue_entry_priv_mmio *entry_priv; + u32 reg; + + /* + * Initialize registers. + */ + rt2x00mmio_register_read(rt2x00dev, TXCSR2, ®); + rt2x00_set_field32(®, TXCSR2_TXD_SIZE, rt2x00dev->tx[0].desc_size); + rt2x00_set_field32(®, TXCSR2_NUM_TXD, rt2x00dev->tx[1].limit); + rt2x00_set_field32(®, TXCSR2_NUM_ATIM, rt2x00dev->atim->limit); + rt2x00_set_field32(®, TXCSR2_NUM_PRIO, rt2x00dev->tx[0].limit); + rt2x00mmio_register_write(rt2x00dev, TXCSR2, reg); + + entry_priv = rt2x00dev->tx[1].entries[0].priv_data; + rt2x00mmio_register_read(rt2x00dev, TXCSR3, ®); + rt2x00_set_field32(®, TXCSR3_TX_RING_REGISTER, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, TXCSR3, reg); + + entry_priv = rt2x00dev->tx[0].entries[0].priv_data; + rt2x00mmio_register_read(rt2x00dev, TXCSR5, ®); + rt2x00_set_field32(®, TXCSR5_PRIO_RING_REGISTER, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, TXCSR5, reg); + + entry_priv = rt2x00dev->atim->entries[0].priv_data; + rt2x00mmio_register_read(rt2x00dev, TXCSR4, ®); + rt2x00_set_field32(®, TXCSR4_ATIM_RING_REGISTER, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, TXCSR4, reg); + + entry_priv = rt2x00dev->bcn->entries[0].priv_data; + rt2x00mmio_register_read(rt2x00dev, TXCSR6, ®); + rt2x00_set_field32(®, TXCSR6_BEACON_RING_REGISTER, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, TXCSR6, reg); + + rt2x00mmio_register_read(rt2x00dev, RXCSR1, ®); + rt2x00_set_field32(®, RXCSR1_RXD_SIZE, rt2x00dev->rx->desc_size); + rt2x00_set_field32(®, RXCSR1_NUM_RXD, rt2x00dev->rx->limit); + rt2x00mmio_register_write(rt2x00dev, RXCSR1, reg); + + entry_priv = rt2x00dev->rx->entries[0].priv_data; + rt2x00mmio_register_read(rt2x00dev, RXCSR2, ®); + rt2x00_set_field32(®, RXCSR2_RX_RING_REGISTER, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, RXCSR2, reg); + + return 0; +} + +static int rt2400pci_init_registers(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + rt2x00mmio_register_write(rt2x00dev, PSCSR0, 0x00020002); + rt2x00mmio_register_write(rt2x00dev, PSCSR1, 0x00000002); + rt2x00mmio_register_write(rt2x00dev, PSCSR2, 0x00023f20); + rt2x00mmio_register_write(rt2x00dev, PSCSR3, 0x00000002); + + rt2x00mmio_register_read(rt2x00dev, TIMECSR, ®); + rt2x00_set_field32(®, TIMECSR_US_COUNT, 33); + rt2x00_set_field32(®, TIMECSR_US_64_COUNT, 63); + rt2x00_set_field32(®, TIMECSR_BEACON_EXPECT, 0); + rt2x00mmio_register_write(rt2x00dev, TIMECSR, reg); + + rt2x00mmio_register_read(rt2x00dev, CSR9, ®); + rt2x00_set_field32(®, CSR9_MAX_FRAME_UNIT, + (rt2x00dev->rx->data_size / 128)); + rt2x00mmio_register_write(rt2x00dev, CSR9, reg); + + rt2x00mmio_register_read(rt2x00dev, CSR14, ®); + rt2x00_set_field32(®, CSR14_TSF_COUNT, 0); + rt2x00_set_field32(®, CSR14_TSF_SYNC, 0); + rt2x00_set_field32(®, CSR14_TBCN, 0); + rt2x00_set_field32(®, CSR14_TCFP, 0); + rt2x00_set_field32(®, CSR14_TATIMW, 0); + rt2x00_set_field32(®, CSR14_BEACON_GEN, 0); + rt2x00_set_field32(®, CSR14_CFP_COUNT_PRELOAD, 0); + rt2x00_set_field32(®, CSR14_TBCM_PRELOAD, 0); + rt2x00mmio_register_write(rt2x00dev, CSR14, reg); + + rt2x00mmio_register_write(rt2x00dev, CNT3, 0x3f080000); + + rt2x00mmio_register_read(rt2x00dev, ARCSR0, ®); + rt2x00_set_field32(®, ARCSR0_AR_BBP_DATA0, 133); + rt2x00_set_field32(®, ARCSR0_AR_BBP_ID0, 134); + rt2x00_set_field32(®, ARCSR0_AR_BBP_DATA1, 136); + rt2x00_set_field32(®, ARCSR0_AR_BBP_ID1, 135); + rt2x00mmio_register_write(rt2x00dev, ARCSR0, reg); + + rt2x00mmio_register_read(rt2x00dev, RXCSR3, ®); + rt2x00_set_field32(®, RXCSR3_BBP_ID0, 3); /* Tx power.*/ + rt2x00_set_field32(®, RXCSR3_BBP_ID0_VALID, 1); + rt2x00_set_field32(®, RXCSR3_BBP_ID1, 32); /* Signal */ + rt2x00_set_field32(®, RXCSR3_BBP_ID1_VALID, 1); + rt2x00_set_field32(®, RXCSR3_BBP_ID2, 36); /* Rssi */ + rt2x00_set_field32(®, RXCSR3_BBP_ID2_VALID, 1); + rt2x00mmio_register_write(rt2x00dev, RXCSR3, reg); + + rt2x00mmio_register_write(rt2x00dev, PWRCSR0, 0x3f3b3100); + + if (rt2x00dev->ops->lib->set_device_state(rt2x00dev, STATE_AWAKE)) + return -EBUSY; + + rt2x00mmio_register_write(rt2x00dev, MACCSR0, 0x00217223); + rt2x00mmio_register_write(rt2x00dev, MACCSR1, 0x00235518); + + rt2x00mmio_register_read(rt2x00dev, MACCSR2, ®); + rt2x00_set_field32(®, MACCSR2_DELAY, 64); + rt2x00mmio_register_write(rt2x00dev, MACCSR2, reg); + + rt2x00mmio_register_read(rt2x00dev, RALINKCSR, ®); + rt2x00_set_field32(®, RALINKCSR_AR_BBP_DATA0, 17); + rt2x00_set_field32(®, RALINKCSR_AR_BBP_ID0, 154); + rt2x00_set_field32(®, RALINKCSR_AR_BBP_DATA1, 0); + rt2x00_set_field32(®, RALINKCSR_AR_BBP_ID1, 154); + rt2x00mmio_register_write(rt2x00dev, RALINKCSR, reg); + + rt2x00mmio_register_read(rt2x00dev, CSR1, ®); + rt2x00_set_field32(®, CSR1_SOFT_RESET, 1); + rt2x00_set_field32(®, CSR1_BBP_RESET, 0); + rt2x00_set_field32(®, CSR1_HOST_READY, 0); + rt2x00mmio_register_write(rt2x00dev, CSR1, reg); + + rt2x00mmio_register_read(rt2x00dev, CSR1, ®); + rt2x00_set_field32(®, CSR1_SOFT_RESET, 0); + rt2x00_set_field32(®, CSR1_HOST_READY, 1); + rt2x00mmio_register_write(rt2x00dev, CSR1, reg); + + /* + * We must clear the FCS and FIFO error count. + * These registers are cleared on read, + * so we may pass a useless variable to store the value. + */ + rt2x00mmio_register_read(rt2x00dev, CNT0, ®); + rt2x00mmio_register_read(rt2x00dev, CNT4, ®); + + return 0; +} + +static int rt2400pci_wait_bbp_ready(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i; + u8 value; + + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2400pci_bbp_read(rt2x00dev, 0, &value); + if ((value != 0xff) && (value != 0x00)) + return 0; + udelay(REGISTER_BUSY_DELAY); + } + + rt2x00_err(rt2x00dev, "BBP register access failed, aborting\n"); + return -EACCES; +} + +static int rt2400pci_init_bbp(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i; + u16 eeprom; + u8 reg_id; + u8 value; + + if (unlikely(rt2400pci_wait_bbp_ready(rt2x00dev))) + return -EACCES; + + rt2400pci_bbp_write(rt2x00dev, 1, 0x00); + rt2400pci_bbp_write(rt2x00dev, 3, 0x27); + rt2400pci_bbp_write(rt2x00dev, 4, 0x08); + rt2400pci_bbp_write(rt2x00dev, 10, 0x0f); + rt2400pci_bbp_write(rt2x00dev, 15, 0x72); + rt2400pci_bbp_write(rt2x00dev, 16, 0x74); + rt2400pci_bbp_write(rt2x00dev, 17, 0x20); + rt2400pci_bbp_write(rt2x00dev, 18, 0x72); + rt2400pci_bbp_write(rt2x00dev, 19, 0x0b); + rt2400pci_bbp_write(rt2x00dev, 20, 0x00); + rt2400pci_bbp_write(rt2x00dev, 28, 0x11); + rt2400pci_bbp_write(rt2x00dev, 29, 0x04); + rt2400pci_bbp_write(rt2x00dev, 30, 0x21); + rt2400pci_bbp_write(rt2x00dev, 31, 0x00); + + for (i = 0; i < EEPROM_BBP_SIZE; i++) { + rt2x00_eeprom_read(rt2x00dev, EEPROM_BBP_START + i, &eeprom); + + if (eeprom != 0xffff && eeprom != 0x0000) { + reg_id = rt2x00_get_field16(eeprom, EEPROM_BBP_REG_ID); + value = rt2x00_get_field16(eeprom, EEPROM_BBP_VALUE); + rt2400pci_bbp_write(rt2x00dev, reg_id, value); + } + } + + return 0; +} + +/* + * Device state switch handlers. + */ +static void rt2400pci_toggle_irq(struct rt2x00_dev *rt2x00dev, + enum dev_state state) +{ + int mask = (state == STATE_RADIO_IRQ_OFF); + u32 reg; + unsigned long flags; + + /* + * When interrupts are being enabled, the interrupt registers + * should clear the register to assure a clean state. + */ + if (state == STATE_RADIO_IRQ_ON) { + rt2x00mmio_register_read(rt2x00dev, CSR7, ®); + rt2x00mmio_register_write(rt2x00dev, CSR7, reg); + } + + /* + * Only toggle the interrupts bits we are going to use. + * Non-checked interrupt bits are disabled by default. + */ + spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags); + + rt2x00mmio_register_read(rt2x00dev, CSR8, ®); + rt2x00_set_field32(®, CSR8_TBCN_EXPIRE, mask); + rt2x00_set_field32(®, CSR8_TXDONE_TXRING, mask); + rt2x00_set_field32(®, CSR8_TXDONE_ATIMRING, mask); + rt2x00_set_field32(®, CSR8_TXDONE_PRIORING, mask); + rt2x00_set_field32(®, CSR8_RXDONE, mask); + rt2x00mmio_register_write(rt2x00dev, CSR8, reg); + + spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags); + + if (state == STATE_RADIO_IRQ_OFF) { + /* + * Ensure that all tasklets are finished before + * disabling the interrupts. + */ + tasklet_kill(&rt2x00dev->txstatus_tasklet); + tasklet_kill(&rt2x00dev->rxdone_tasklet); + tasklet_kill(&rt2x00dev->tbtt_tasklet); + } +} + +static int rt2400pci_enable_radio(struct rt2x00_dev *rt2x00dev) +{ + /* + * Initialize all registers. + */ + if (unlikely(rt2400pci_init_queues(rt2x00dev) || + rt2400pci_init_registers(rt2x00dev) || + rt2400pci_init_bbp(rt2x00dev))) + return -EIO; + + return 0; +} + +static void rt2400pci_disable_radio(struct rt2x00_dev *rt2x00dev) +{ + /* + * Disable power + */ + rt2x00mmio_register_write(rt2x00dev, PWRCSR0, 0); +} + +static int rt2400pci_set_state(struct rt2x00_dev *rt2x00dev, + enum dev_state state) +{ + u32 reg, reg2; + unsigned int i; + char put_to_sleep; + char bbp_state; + char rf_state; + + put_to_sleep = (state != STATE_AWAKE); + + rt2x00mmio_register_read(rt2x00dev, PWRCSR1, ®); + rt2x00_set_field32(®, PWRCSR1_SET_STATE, 1); + rt2x00_set_field32(®, PWRCSR1_BBP_DESIRE_STATE, state); + rt2x00_set_field32(®, PWRCSR1_RF_DESIRE_STATE, state); + rt2x00_set_field32(®, PWRCSR1_PUT_TO_SLEEP, put_to_sleep); + rt2x00mmio_register_write(rt2x00dev, PWRCSR1, reg); + + /* + * Device is not guaranteed to be in the requested state yet. + * We must wait until the register indicates that the + * device has entered the correct state. + */ + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2x00mmio_register_read(rt2x00dev, PWRCSR1, ®2); + bbp_state = rt2x00_get_field32(reg2, PWRCSR1_BBP_CURR_STATE); + rf_state = rt2x00_get_field32(reg2, PWRCSR1_RF_CURR_STATE); + if (bbp_state == state && rf_state == state) + return 0; + rt2x00mmio_register_write(rt2x00dev, PWRCSR1, reg); + msleep(10); + } + + return -EBUSY; +} + +static int rt2400pci_set_device_state(struct rt2x00_dev *rt2x00dev, + enum dev_state state) +{ + int retval = 0; + + switch (state) { + case STATE_RADIO_ON: + retval = rt2400pci_enable_radio(rt2x00dev); + break; + case STATE_RADIO_OFF: + rt2400pci_disable_radio(rt2x00dev); + break; + case STATE_RADIO_IRQ_ON: + case STATE_RADIO_IRQ_OFF: + rt2400pci_toggle_irq(rt2x00dev, state); + break; + case STATE_DEEP_SLEEP: + case STATE_SLEEP: + case STATE_STANDBY: + case STATE_AWAKE: + retval = rt2400pci_set_state(rt2x00dev, state); + break; + default: + retval = -ENOTSUPP; + break; + } + + if (unlikely(retval)) + rt2x00_err(rt2x00dev, "Device failed to enter state %d (%d)\n", + state, retval); + + return retval; +} + +/* + * TX descriptor initialization + */ +static void rt2400pci_write_tx_desc(struct queue_entry *entry, + struct txentry_desc *txdesc) +{ + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + __le32 *txd = entry_priv->desc; + u32 word; + + /* + * Start writing the descriptor words. + */ + rt2x00_desc_read(txd, 1, &word); + rt2x00_set_field32(&word, TXD_W1_BUFFER_ADDRESS, skbdesc->skb_dma); + rt2x00_desc_write(txd, 1, word); + + rt2x00_desc_read(txd, 2, &word); + rt2x00_set_field32(&word, TXD_W2_BUFFER_LENGTH, txdesc->length); + rt2x00_set_field32(&word, TXD_W2_DATABYTE_COUNT, txdesc->length); + rt2x00_desc_write(txd, 2, word); + + rt2x00_desc_read(txd, 3, &word); + rt2x00_set_field32(&word, TXD_W3_PLCP_SIGNAL, txdesc->u.plcp.signal); + rt2x00_set_field32(&word, TXD_W3_PLCP_SIGNAL_REGNUM, 5); + rt2x00_set_field32(&word, TXD_W3_PLCP_SIGNAL_BUSY, 1); + rt2x00_set_field32(&word, TXD_W3_PLCP_SERVICE, txdesc->u.plcp.service); + rt2x00_set_field32(&word, TXD_W3_PLCP_SERVICE_REGNUM, 6); + rt2x00_set_field32(&word, TXD_W3_PLCP_SERVICE_BUSY, 1); + rt2x00_desc_write(txd, 3, word); + + rt2x00_desc_read(txd, 4, &word); + rt2x00_set_field32(&word, TXD_W4_PLCP_LENGTH_LOW, + txdesc->u.plcp.length_low); + rt2x00_set_field32(&word, TXD_W3_PLCP_LENGTH_LOW_REGNUM, 8); + rt2x00_set_field32(&word, TXD_W3_PLCP_LENGTH_LOW_BUSY, 1); + rt2x00_set_field32(&word, TXD_W4_PLCP_LENGTH_HIGH, + txdesc->u.plcp.length_high); + rt2x00_set_field32(&word, TXD_W3_PLCP_LENGTH_HIGH_REGNUM, 7); + rt2x00_set_field32(&word, TXD_W3_PLCP_LENGTH_HIGH_BUSY, 1); + rt2x00_desc_write(txd, 4, word); + + /* + * Writing TXD word 0 must the last to prevent a race condition with + * the device, whereby the device may take hold of the TXD before we + * finished updating it. + */ + rt2x00_desc_read(txd, 0, &word); + rt2x00_set_field32(&word, TXD_W0_OWNER_NIC, 1); + rt2x00_set_field32(&word, TXD_W0_VALID, 1); + rt2x00_set_field32(&word, TXD_W0_MORE_FRAG, + test_bit(ENTRY_TXD_MORE_FRAG, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_ACK, + test_bit(ENTRY_TXD_ACK, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_TIMESTAMP, + test_bit(ENTRY_TXD_REQ_TIMESTAMP, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_RTS, + test_bit(ENTRY_TXD_RTS_FRAME, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_IFS, txdesc->u.plcp.ifs); + rt2x00_set_field32(&word, TXD_W0_RETRY_MODE, + test_bit(ENTRY_TXD_RETRY_MODE, &txdesc->flags)); + rt2x00_desc_write(txd, 0, word); + + /* + * Register descriptor details in skb frame descriptor. + */ + skbdesc->desc = txd; + skbdesc->desc_len = TXD_DESC_SIZE; +} + +/* + * TX data initialization + */ +static void rt2400pci_write_beacon(struct queue_entry *entry, + struct txentry_desc *txdesc) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + u32 reg; + + /* + * Disable beaconing while we are reloading the beacon data, + * otherwise we might be sending out invalid data. + */ + rt2x00mmio_register_read(rt2x00dev, CSR14, ®); + rt2x00_set_field32(®, CSR14_BEACON_GEN, 0); + rt2x00mmio_register_write(rt2x00dev, CSR14, reg); + + if (rt2x00queue_map_txskb(entry)) { + rt2x00_err(rt2x00dev, "Fail to map beacon, aborting\n"); + goto out; + } + /* + * Enable beaconing again. + */ + rt2x00_set_field32(®, CSR14_BEACON_GEN, 1); + /* + * Write the TX descriptor for the beacon. + */ + rt2400pci_write_tx_desc(entry, txdesc); + + /* + * Dump beacon to userspace through debugfs. + */ + rt2x00debug_dump_frame(rt2x00dev, DUMP_FRAME_BEACON, entry->skb); +out: + /* + * Enable beaconing again. + */ + rt2x00_set_field32(®, CSR14_BEACON_GEN, 1); + rt2x00mmio_register_write(rt2x00dev, CSR14, reg); +} + +/* + * RX control handlers + */ +static void rt2400pci_fill_rxdone(struct queue_entry *entry, + struct rxdone_entry_desc *rxdesc) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + u32 word0; + u32 word2; + u32 word3; + u32 word4; + u64 tsf; + u32 rx_low; + u32 rx_high; + + rt2x00_desc_read(entry_priv->desc, 0, &word0); + rt2x00_desc_read(entry_priv->desc, 2, &word2); + rt2x00_desc_read(entry_priv->desc, 3, &word3); + rt2x00_desc_read(entry_priv->desc, 4, &word4); + + if (rt2x00_get_field32(word0, RXD_W0_CRC_ERROR)) + rxdesc->flags |= RX_FLAG_FAILED_FCS_CRC; + if (rt2x00_get_field32(word0, RXD_W0_PHYSICAL_ERROR)) + rxdesc->flags |= RX_FLAG_FAILED_PLCP_CRC; + + /* + * We only get the lower 32bits from the timestamp, + * to get the full 64bits we must complement it with + * the timestamp from get_tsf(). + * Note that when a wraparound of the lower 32bits + * has occurred between the frame arrival and the get_tsf() + * call, we must decrease the higher 32bits with 1 to get + * to correct value. + */ + tsf = rt2x00dev->ops->hw->get_tsf(rt2x00dev->hw, NULL); + rx_low = rt2x00_get_field32(word4, RXD_W4_RX_END_TIME); + rx_high = upper_32_bits(tsf); + + if ((u32)tsf <= rx_low) + rx_high--; + + /* + * Obtain the status about this packet. + * The signal is the PLCP value, and needs to be stripped + * of the preamble bit (0x08). + */ + rxdesc->timestamp = ((u64)rx_high << 32) | rx_low; + rxdesc->signal = rt2x00_get_field32(word2, RXD_W2_SIGNAL) & ~0x08; + rxdesc->rssi = rt2x00_get_field32(word2, RXD_W3_RSSI) - + entry->queue->rt2x00dev->rssi_offset; + rxdesc->size = rt2x00_get_field32(word0, RXD_W0_DATABYTE_COUNT); + + rxdesc->dev_flags |= RXDONE_SIGNAL_PLCP; + if (rt2x00_get_field32(word0, RXD_W0_MY_BSS)) + rxdesc->dev_flags |= RXDONE_MY_BSS; +} + +/* + * Interrupt functions. + */ +static void rt2400pci_txdone(struct rt2x00_dev *rt2x00dev, + const enum data_queue_qid queue_idx) +{ + struct data_queue *queue = rt2x00queue_get_tx_queue(rt2x00dev, queue_idx); + struct queue_entry_priv_mmio *entry_priv; + struct queue_entry *entry; + struct txdone_entry_desc txdesc; + u32 word; + + while (!rt2x00queue_empty(queue)) { + entry = rt2x00queue_get_entry(queue, Q_INDEX_DONE); + entry_priv = entry->priv_data; + rt2x00_desc_read(entry_priv->desc, 0, &word); + + if (rt2x00_get_field32(word, TXD_W0_OWNER_NIC) || + !rt2x00_get_field32(word, TXD_W0_VALID)) + break; + + /* + * Obtain the status about this packet. + */ + txdesc.flags = 0; + switch (rt2x00_get_field32(word, TXD_W0_RESULT)) { + case 0: /* Success */ + case 1: /* Success with retry */ + __set_bit(TXDONE_SUCCESS, &txdesc.flags); + break; + case 2: /* Failure, excessive retries */ + __set_bit(TXDONE_EXCESSIVE_RETRY, &txdesc.flags); + /* Don't break, this is a failed frame! */ + default: /* Failure */ + __set_bit(TXDONE_FAILURE, &txdesc.flags); + } + txdesc.retry = rt2x00_get_field32(word, TXD_W0_RETRY_COUNT); + + rt2x00lib_txdone(entry, &txdesc); + } +} + +static inline void rt2400pci_enable_interrupt(struct rt2x00_dev *rt2x00dev, + struct rt2x00_field32 irq_field) +{ + u32 reg; + + /* + * Enable a single interrupt. The interrupt mask register + * access needs locking. + */ + spin_lock_irq(&rt2x00dev->irqmask_lock); + + rt2x00mmio_register_read(rt2x00dev, CSR8, ®); + rt2x00_set_field32(®, irq_field, 0); + rt2x00mmio_register_write(rt2x00dev, CSR8, reg); + + spin_unlock_irq(&rt2x00dev->irqmask_lock); +} + +static void rt2400pci_txstatus_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + u32 reg; + + /* + * Handle all tx queues. + */ + rt2400pci_txdone(rt2x00dev, QID_ATIM); + rt2400pci_txdone(rt2x00dev, QID_AC_VO); + rt2400pci_txdone(rt2x00dev, QID_AC_VI); + + /* + * Enable all TXDONE interrupts again. + */ + if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) { + spin_lock_irq(&rt2x00dev->irqmask_lock); + + rt2x00mmio_register_read(rt2x00dev, CSR8, ®); + rt2x00_set_field32(®, CSR8_TXDONE_TXRING, 0); + rt2x00_set_field32(®, CSR8_TXDONE_ATIMRING, 0); + rt2x00_set_field32(®, CSR8_TXDONE_PRIORING, 0); + rt2x00mmio_register_write(rt2x00dev, CSR8, reg); + + spin_unlock_irq(&rt2x00dev->irqmask_lock); + } +} + +static void rt2400pci_tbtt_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + rt2x00lib_beacondone(rt2x00dev); + if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + rt2400pci_enable_interrupt(rt2x00dev, CSR8_TBCN_EXPIRE); +} + +static void rt2400pci_rxdone_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + if (rt2x00mmio_rxdone(rt2x00dev)) + tasklet_schedule(&rt2x00dev->rxdone_tasklet); + else if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + rt2400pci_enable_interrupt(rt2x00dev, CSR8_RXDONE); +} + +static irqreturn_t rt2400pci_interrupt(int irq, void *dev_instance) +{ + struct rt2x00_dev *rt2x00dev = dev_instance; + u32 reg, mask; + + /* + * Get the interrupt sources & saved to local variable. + * Write register value back to clear pending interrupts. + */ + rt2x00mmio_register_read(rt2x00dev, CSR7, ®); + rt2x00mmio_register_write(rt2x00dev, CSR7, reg); + + if (!reg) + return IRQ_NONE; + + if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + return IRQ_HANDLED; + + mask = reg; + + /* + * Schedule tasklets for interrupt handling. + */ + if (rt2x00_get_field32(reg, CSR7_TBCN_EXPIRE)) + tasklet_hi_schedule(&rt2x00dev->tbtt_tasklet); + + if (rt2x00_get_field32(reg, CSR7_RXDONE)) + tasklet_schedule(&rt2x00dev->rxdone_tasklet); + + if (rt2x00_get_field32(reg, CSR7_TXDONE_ATIMRING) || + rt2x00_get_field32(reg, CSR7_TXDONE_PRIORING) || + rt2x00_get_field32(reg, CSR7_TXDONE_TXRING)) { + tasklet_schedule(&rt2x00dev->txstatus_tasklet); + /* + * Mask out all txdone interrupts. + */ + rt2x00_set_field32(&mask, CSR8_TXDONE_TXRING, 1); + rt2x00_set_field32(&mask, CSR8_TXDONE_ATIMRING, 1); + rt2x00_set_field32(&mask, CSR8_TXDONE_PRIORING, 1); + } + + /* + * Disable all interrupts for which a tasklet was scheduled right now, + * the tasklet will reenable the appropriate interrupts. + */ + spin_lock(&rt2x00dev->irqmask_lock); + + rt2x00mmio_register_read(rt2x00dev, CSR8, ®); + reg |= mask; + rt2x00mmio_register_write(rt2x00dev, CSR8, reg); + + spin_unlock(&rt2x00dev->irqmask_lock); + + + + return IRQ_HANDLED; +} + +/* + * Device probe functions. + */ +static int rt2400pci_validate_eeprom(struct rt2x00_dev *rt2x00dev) +{ + struct eeprom_93cx6 eeprom; + u32 reg; + u16 word; + u8 *mac; + + rt2x00mmio_register_read(rt2x00dev, CSR21, ®); + + eeprom.data = rt2x00dev; + eeprom.register_read = rt2400pci_eepromregister_read; + eeprom.register_write = rt2400pci_eepromregister_write; + eeprom.width = rt2x00_get_field32(reg, CSR21_TYPE_93C46) ? + PCI_EEPROM_WIDTH_93C46 : PCI_EEPROM_WIDTH_93C66; + eeprom.reg_data_in = 0; + eeprom.reg_data_out = 0; + eeprom.reg_data_clock = 0; + eeprom.reg_chip_select = 0; + + eeprom_93cx6_multiread(&eeprom, EEPROM_BASE, rt2x00dev->eeprom, + EEPROM_SIZE / sizeof(u16)); + + /* + * Start validation of the data that has been read. + */ + mac = rt2x00_eeprom_addr(rt2x00dev, EEPROM_MAC_ADDR_0); + if (!is_valid_ether_addr(mac)) { + eth_random_addr(mac); + rt2x00_eeprom_dbg(rt2x00dev, "MAC: %pM\n", mac); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_ANTENNA, &word); + if (word == 0xffff) { + rt2x00_err(rt2x00dev, "Invalid EEPROM data detected\n"); + return -EINVAL; + } + + return 0; +} + +static int rt2400pci_init_eeprom(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + u16 value; + u16 eeprom; + + /* + * Read EEPROM word for configuration. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_ANTENNA, &eeprom); + + /* + * Identify RF chipset. + */ + value = rt2x00_get_field16(eeprom, EEPROM_ANTENNA_RF_TYPE); + rt2x00mmio_register_read(rt2x00dev, CSR0, ®); + rt2x00_set_chip(rt2x00dev, RT2460, value, + rt2x00_get_field32(reg, CSR0_REVISION)); + + if (!rt2x00_rf(rt2x00dev, RF2420) && !rt2x00_rf(rt2x00dev, RF2421)) { + rt2x00_err(rt2x00dev, "Invalid RF chipset detected\n"); + return -ENODEV; + } + + /* + * Identify default antenna configuration. + */ + rt2x00dev->default_ant.tx = + rt2x00_get_field16(eeprom, EEPROM_ANTENNA_TX_DEFAULT); + rt2x00dev->default_ant.rx = + rt2x00_get_field16(eeprom, EEPROM_ANTENNA_RX_DEFAULT); + + /* + * When the eeprom indicates SW_DIVERSITY use HW_DIVERSITY instead. + * I am not 100% sure about this, but the legacy drivers do not + * indicate antenna swapping in software is required when + * diversity is enabled. + */ + if (rt2x00dev->default_ant.tx == ANTENNA_SW_DIVERSITY) + rt2x00dev->default_ant.tx = ANTENNA_HW_DIVERSITY; + if (rt2x00dev->default_ant.rx == ANTENNA_SW_DIVERSITY) + rt2x00dev->default_ant.rx = ANTENNA_HW_DIVERSITY; + + /* + * Store led mode, for correct led behaviour. + */ +#ifdef CPTCFG_RT2X00_LIB_LEDS + value = rt2x00_get_field16(eeprom, EEPROM_ANTENNA_LED_MODE); + + rt2400pci_init_led(rt2x00dev, &rt2x00dev->led_radio, LED_TYPE_RADIO); + if (value == LED_MODE_TXRX_ACTIVITY || + value == LED_MODE_DEFAULT || + value == LED_MODE_ASUS) + rt2400pci_init_led(rt2x00dev, &rt2x00dev->led_qual, + LED_TYPE_ACTIVITY); +#endif /* CPTCFG_RT2X00_LIB_LEDS */ + + /* + * Detect if this device has an hardware controlled radio. + */ + if (rt2x00_get_field16(eeprom, EEPROM_ANTENNA_HARDWARE_RADIO)) + __set_bit(CAPABILITY_HW_BUTTON, &rt2x00dev->cap_flags); + + /* + * Check if the BBP tuning should be enabled. + */ + if (rt2x00_get_field16(eeprom, EEPROM_ANTENNA_RX_AGCVGC_TUNING)) + __set_bit(CAPABILITY_LINK_TUNING, &rt2x00dev->cap_flags); + + return 0; +} + +/* + * RF value list for RF2420 & RF2421 + * Supports: 2.4 GHz + */ +static const struct rf_channel rf_vals_b[] = { + { 1, 0x00022058, 0x000c1fda, 0x00000101, 0 }, + { 2, 0x00022058, 0x000c1fee, 0x00000101, 0 }, + { 3, 0x00022058, 0x000c2002, 0x00000101, 0 }, + { 4, 0x00022058, 0x000c2016, 0x00000101, 0 }, + { 5, 0x00022058, 0x000c202a, 0x00000101, 0 }, + { 6, 0x00022058, 0x000c203e, 0x00000101, 0 }, + { 7, 0x00022058, 0x000c2052, 0x00000101, 0 }, + { 8, 0x00022058, 0x000c2066, 0x00000101, 0 }, + { 9, 0x00022058, 0x000c207a, 0x00000101, 0 }, + { 10, 0x00022058, 0x000c208e, 0x00000101, 0 }, + { 11, 0x00022058, 0x000c20a2, 0x00000101, 0 }, + { 12, 0x00022058, 0x000c20b6, 0x00000101, 0 }, + { 13, 0x00022058, 0x000c20ca, 0x00000101, 0 }, + { 14, 0x00022058, 0x000c20fa, 0x00000101, 0 }, +}; + +static int rt2400pci_probe_hw_mode(struct rt2x00_dev *rt2x00dev) +{ + struct hw_mode_spec *spec = &rt2x00dev->spec; + struct channel_info *info; + char *tx_power; + unsigned int i; + + /* + * Initialize all hw fields. + */ + rt2x00dev->hw->flags = IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING | + IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_PS_NULLFUNC_STACK; + + SET_IEEE80211_DEV(rt2x00dev->hw, rt2x00dev->dev); + SET_IEEE80211_PERM_ADDR(rt2x00dev->hw, + rt2x00_eeprom_addr(rt2x00dev, + EEPROM_MAC_ADDR_0)); + + /* + * Initialize hw_mode information. + */ + spec->supported_bands = SUPPORT_BAND_2GHZ; + spec->supported_rates = SUPPORT_RATE_CCK; + + spec->num_channels = ARRAY_SIZE(rf_vals_b); + spec->channels = rf_vals_b; + + /* + * Create channel information array + */ + info = kcalloc(spec->num_channels, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + spec->channels_info = info; + + tx_power = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_START); + for (i = 0; i < 14; i++) { + info[i].max_power = TXPOWER_FROM_DEV(MAX_TXPOWER); + info[i].default_power1 = TXPOWER_FROM_DEV(tx_power[i]); + } + + return 0; +} + +static int rt2400pci_probe_hw(struct rt2x00_dev *rt2x00dev) +{ + int retval; + u32 reg; + + /* + * Allocate eeprom data. + */ + retval = rt2400pci_validate_eeprom(rt2x00dev); + if (retval) + return retval; + + retval = rt2400pci_init_eeprom(rt2x00dev); + if (retval) + return retval; + + /* + * Enable rfkill polling by setting GPIO direction of the + * rfkill switch GPIO pin correctly. + */ + rt2x00mmio_register_read(rt2x00dev, GPIOCSR, ®); + rt2x00_set_field32(®, GPIOCSR_DIR0, 1); + rt2x00mmio_register_write(rt2x00dev, GPIOCSR, reg); + + /* + * Initialize hw specifications. + */ + retval = rt2400pci_probe_hw_mode(rt2x00dev); + if (retval) + return retval; + + /* + * This device requires the atim queue and DMA-mapped skbs. + */ + __set_bit(REQUIRE_ATIM_QUEUE, &rt2x00dev->cap_flags); + __set_bit(REQUIRE_DMA, &rt2x00dev->cap_flags); + __set_bit(REQUIRE_SW_SEQNO, &rt2x00dev->cap_flags); + + /* + * Set the rssi offset. + */ + rt2x00dev->rssi_offset = DEFAULT_RSSI_OFFSET; + + return 0; +} + +/* + * IEEE80211 stack callback functions. + */ +static int rt2400pci_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + + /* + * We don't support variating cw_min and cw_max variables + * per queue. So by default we only configure the TX queue, + * and ignore all other configurations. + */ + if (queue != 0) + return -EINVAL; + + if (rt2x00mac_conf_tx(hw, vif, queue, params)) + return -EINVAL; + + /* + * Write configuration to register. + */ + rt2400pci_config_cw(rt2x00dev, + rt2x00dev->tx->cw_min, rt2x00dev->tx->cw_max); + + return 0; +} + +static u64 rt2400pci_get_tsf(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + u64 tsf; + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, CSR17, ®); + tsf = (u64) rt2x00_get_field32(reg, CSR17_HIGH_TSFTIMER) << 32; + rt2x00mmio_register_read(rt2x00dev, CSR16, ®); + tsf |= rt2x00_get_field32(reg, CSR16_LOW_TSFTIMER); + + return tsf; +} + +static int rt2400pci_tx_last_beacon(struct ieee80211_hw *hw) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, CSR15, ®); + return rt2x00_get_field32(reg, CSR15_BEACON_SENT); +} + +static const struct ieee80211_ops rt2400pci_mac80211_ops = { + .tx = rt2x00mac_tx, + .start = rt2x00mac_start, + .stop = rt2x00mac_stop, + .add_interface = rt2x00mac_add_interface, + .remove_interface = rt2x00mac_remove_interface, + .config = rt2x00mac_config, + .configure_filter = rt2x00mac_configure_filter, + .sw_scan_start = rt2x00mac_sw_scan_start, + .sw_scan_complete = rt2x00mac_sw_scan_complete, + .get_stats = rt2x00mac_get_stats, + .bss_info_changed = rt2x00mac_bss_info_changed, + .conf_tx = rt2400pci_conf_tx, + .get_tsf = rt2400pci_get_tsf, + .tx_last_beacon = rt2400pci_tx_last_beacon, + .rfkill_poll = rt2x00mac_rfkill_poll, + .flush = rt2x00mac_flush, + .set_antenna = rt2x00mac_set_antenna, + .get_antenna = rt2x00mac_get_antenna, + .get_ringparam = rt2x00mac_get_ringparam, + .tx_frames_pending = rt2x00mac_tx_frames_pending, +}; + +static const struct rt2x00lib_ops rt2400pci_rt2x00_ops = { + .irq_handler = rt2400pci_interrupt, + .txstatus_tasklet = rt2400pci_txstatus_tasklet, + .tbtt_tasklet = rt2400pci_tbtt_tasklet, + .rxdone_tasklet = rt2400pci_rxdone_tasklet, + .probe_hw = rt2400pci_probe_hw, + .initialize = rt2x00mmio_initialize, + .uninitialize = rt2x00mmio_uninitialize, + .get_entry_state = rt2400pci_get_entry_state, + .clear_entry = rt2400pci_clear_entry, + .set_device_state = rt2400pci_set_device_state, + .rfkill_poll = rt2400pci_rfkill_poll, + .link_stats = rt2400pci_link_stats, + .reset_tuner = rt2400pci_reset_tuner, + .link_tuner = rt2400pci_link_tuner, + .start_queue = rt2400pci_start_queue, + .kick_queue = rt2400pci_kick_queue, + .stop_queue = rt2400pci_stop_queue, + .flush_queue = rt2x00mmio_flush_queue, + .write_tx_desc = rt2400pci_write_tx_desc, + .write_beacon = rt2400pci_write_beacon, + .fill_rxdone = rt2400pci_fill_rxdone, + .config_filter = rt2400pci_config_filter, + .config_intf = rt2400pci_config_intf, + .config_erp = rt2400pci_config_erp, + .config_ant = rt2400pci_config_ant, + .config = rt2400pci_config, +}; + +static const struct data_queue_desc rt2400pci_queue_rx = { + .entry_num = 24, + .data_size = DATA_FRAME_SIZE, + .desc_size = RXD_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_mmio), +}; + +static const struct data_queue_desc rt2400pci_queue_tx = { + .entry_num = 24, + .data_size = DATA_FRAME_SIZE, + .desc_size = TXD_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_mmio), +}; + +static const struct data_queue_desc rt2400pci_queue_bcn = { + .entry_num = 1, + .data_size = MGMT_FRAME_SIZE, + .desc_size = TXD_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_mmio), +}; + +static const struct data_queue_desc rt2400pci_queue_atim = { + .entry_num = 8, + .data_size = DATA_FRAME_SIZE, + .desc_size = TXD_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_mmio), +}; + +static const struct rt2x00_ops rt2400pci_ops = { + .name = KBUILD_MODNAME, + .max_ap_intf = 1, + .eeprom_size = EEPROM_SIZE, + .rf_size = RF_SIZE, + .tx_queues = NUM_TX_QUEUES, + .extra_tx_headroom = 0, + .rx = &rt2400pci_queue_rx, + .tx = &rt2400pci_queue_tx, + .bcn = &rt2400pci_queue_bcn, + .atim = &rt2400pci_queue_atim, + .lib = &rt2400pci_rt2x00_ops, + .hw = &rt2400pci_mac80211_ops, +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS + .debugfs = &rt2400pci_rt2x00debug, +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ +}; + +/* + * RT2400pci module information. + */ +static DEFINE_PCI_DEVICE_TABLE(rt2400pci_device_table) = { + { PCI_DEVICE(0x1814, 0x0101) }, + { 0, } +}; + + +MODULE_AUTHOR(DRV_PROJECT); +MODULE_VERSION(DRV_VERSION); +MODULE_DESCRIPTION("Ralink RT2400 PCI & PCMCIA Wireless LAN driver."); +MODULE_SUPPORTED_DEVICE("Ralink RT2460 PCI & PCMCIA chipset based cards"); +MODULE_DEVICE_TABLE(pci, rt2400pci_device_table); +MODULE_LICENSE("GPL"); + +static int rt2400pci_probe(struct pci_dev *pci_dev, + const struct pci_device_id *id) +{ + return rt2x00pci_probe(pci_dev, &rt2400pci_ops); +} + +static struct pci_driver rt2400pci_driver = { + .name = KBUILD_MODNAME, + .id_table = rt2400pci_device_table, + .probe = rt2400pci_probe, + .remove = rt2x00pci_remove, + .suspend = rt2x00pci_suspend, + .resume = rt2x00pci_resume, +}; + +module_pci_driver(rt2400pci_driver); diff --git a/drivers/net/wireless/rt2x00/rt2400pci.h b/drivers/net/wireless/rt2x00/rt2400pci.h new file mode 100644 index 0000000..e4b07f0 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2400pci.h @@ -0,0 +1,963 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2400pci + Abstract: Data structures and registers for the rt2400pci module. + Supported chipsets: RT2460. + */ + +#ifndef RT2400PCI_H +#define RT2400PCI_H + +/* + * RF chip defines. + */ +#define RF2420 0x0000 +#define RF2421 0x0001 + +/* + * Signal information. + * Default offset is required for RSSI <-> dBm conversion. + */ +#define DEFAULT_RSSI_OFFSET 100 + +/* + * Register layout information. + */ +#define CSR_REG_BASE 0x0000 +#define CSR_REG_SIZE 0x014c +#define EEPROM_BASE 0x0000 +#define EEPROM_SIZE 0x0100 +#define BBP_BASE 0x0000 +#define BBP_SIZE 0x0020 +#define RF_BASE 0x0004 +#define RF_SIZE 0x000c + +/* + * Number of TX queues. + */ +#define NUM_TX_QUEUES 2 + +/* + * Control/Status Registers(CSR). + * Some values are set in TU, whereas 1 TU == 1024 us. + */ + +/* + * CSR0: ASIC revision number. + */ +#define CSR0 0x0000 +#define CSR0_REVISION FIELD32(0x0000ffff) + +/* + * CSR1: System control register. + * SOFT_RESET: Software reset, 1: reset, 0: normal. + * BBP_RESET: Hardware reset, 1: reset, 0, release. + * HOST_READY: Host ready after initialization. + */ +#define CSR1 0x0004 +#define CSR1_SOFT_RESET FIELD32(0x00000001) +#define CSR1_BBP_RESET FIELD32(0x00000002) +#define CSR1_HOST_READY FIELD32(0x00000004) + +/* + * CSR2: System admin status register (invalid). + */ +#define CSR2 0x0008 + +/* + * CSR3: STA MAC address register 0. + */ +#define CSR3 0x000c +#define CSR3_BYTE0 FIELD32(0x000000ff) +#define CSR3_BYTE1 FIELD32(0x0000ff00) +#define CSR3_BYTE2 FIELD32(0x00ff0000) +#define CSR3_BYTE3 FIELD32(0xff000000) + +/* + * CSR4: STA MAC address register 1. + */ +#define CSR4 0x0010 +#define CSR4_BYTE4 FIELD32(0x000000ff) +#define CSR4_BYTE5 FIELD32(0x0000ff00) + +/* + * CSR5: BSSID register 0. + */ +#define CSR5 0x0014 +#define CSR5_BYTE0 FIELD32(0x000000ff) +#define CSR5_BYTE1 FIELD32(0x0000ff00) +#define CSR5_BYTE2 FIELD32(0x00ff0000) +#define CSR5_BYTE3 FIELD32(0xff000000) + +/* + * CSR6: BSSID register 1. + */ +#define CSR6 0x0018 +#define CSR6_BYTE4 FIELD32(0x000000ff) +#define CSR6_BYTE5 FIELD32(0x0000ff00) + +/* + * CSR7: Interrupt source register. + * Write 1 to clear interrupt. + * TBCN_EXPIRE: Beacon timer expired interrupt. + * TWAKE_EXPIRE: Wakeup timer expired interrupt. + * TATIMW_EXPIRE: Timer of atim window expired interrupt. + * TXDONE_TXRING: Tx ring transmit done interrupt. + * TXDONE_ATIMRING: Atim ring transmit done interrupt. + * TXDONE_PRIORING: Priority ring transmit done interrupt. + * RXDONE: Receive done interrupt. + */ +#define CSR7 0x001c +#define CSR7_TBCN_EXPIRE FIELD32(0x00000001) +#define CSR7_TWAKE_EXPIRE FIELD32(0x00000002) +#define CSR7_TATIMW_EXPIRE FIELD32(0x00000004) +#define CSR7_TXDONE_TXRING FIELD32(0x00000008) +#define CSR7_TXDONE_ATIMRING FIELD32(0x00000010) +#define CSR7_TXDONE_PRIORING FIELD32(0x00000020) +#define CSR7_RXDONE FIELD32(0x00000040) + +/* + * CSR8: Interrupt mask register. + * Write 1 to mask interrupt. + * TBCN_EXPIRE: Beacon timer expired interrupt. + * TWAKE_EXPIRE: Wakeup timer expired interrupt. + * TATIMW_EXPIRE: Timer of atim window expired interrupt. + * TXDONE_TXRING: Tx ring transmit done interrupt. + * TXDONE_ATIMRING: Atim ring transmit done interrupt. + * TXDONE_PRIORING: Priority ring transmit done interrupt. + * RXDONE: Receive done interrupt. + */ +#define CSR8 0x0020 +#define CSR8_TBCN_EXPIRE FIELD32(0x00000001) +#define CSR8_TWAKE_EXPIRE FIELD32(0x00000002) +#define CSR8_TATIMW_EXPIRE FIELD32(0x00000004) +#define CSR8_TXDONE_TXRING FIELD32(0x00000008) +#define CSR8_TXDONE_ATIMRING FIELD32(0x00000010) +#define CSR8_TXDONE_PRIORING FIELD32(0x00000020) +#define CSR8_RXDONE FIELD32(0x00000040) + +/* + * CSR9: Maximum frame length register. + * MAX_FRAME_UNIT: Maximum frame length in 128b unit, default: 12. + */ +#define CSR9 0x0024 +#define CSR9_MAX_FRAME_UNIT FIELD32(0x00000f80) + +/* + * CSR11: Back-off control register. + * CWMIN: CWmin. Default cwmin is 31 (2^5 - 1). + * CWMAX: CWmax. Default cwmax is 1023 (2^10 - 1). + * SLOT_TIME: Slot time, default is 20us for 802.11b. + * LONG_RETRY: Long retry count. + * SHORT_RETRY: Short retry count. + */ +#define CSR11 0x002c +#define CSR11_CWMIN FIELD32(0x0000000f) +#define CSR11_CWMAX FIELD32(0x000000f0) +#define CSR11_SLOT_TIME FIELD32(0x00001f00) +#define CSR11_LONG_RETRY FIELD32(0x00ff0000) +#define CSR11_SHORT_RETRY FIELD32(0xff000000) + +/* + * CSR12: Synchronization configuration register 0. + * All units in 1/16 TU. + * BEACON_INTERVAL: Beacon interval, default is 100 TU. + * CFPMAX_DURATION: Cfp maximum duration, default is 100 TU. + */ +#define CSR12 0x0030 +#define CSR12_BEACON_INTERVAL FIELD32(0x0000ffff) +#define CSR12_CFP_MAX_DURATION FIELD32(0xffff0000) + +/* + * CSR13: Synchronization configuration register 1. + * All units in 1/16 TU. + * ATIMW_DURATION: Atim window duration. + * CFP_PERIOD: Cfp period, default is 0 TU. + */ +#define CSR13 0x0034 +#define CSR13_ATIMW_DURATION FIELD32(0x0000ffff) +#define CSR13_CFP_PERIOD FIELD32(0x00ff0000) + +/* + * CSR14: Synchronization control register. + * TSF_COUNT: Enable tsf auto counting. + * TSF_SYNC: Tsf sync, 0: disable, 1: infra, 2: ad-hoc/master mode. + * TBCN: Enable tbcn with reload value. + * TCFP: Enable tcfp & cfp / cp switching. + * TATIMW: Enable tatimw & atim window switching. + * BEACON_GEN: Enable beacon generator. + * CFP_COUNT_PRELOAD: Cfp count preload value. + * TBCM_PRELOAD: Tbcn preload value in units of 64us. + */ +#define CSR14 0x0038 +#define CSR14_TSF_COUNT FIELD32(0x00000001) +#define CSR14_TSF_SYNC FIELD32(0x00000006) +#define CSR14_TBCN FIELD32(0x00000008) +#define CSR14_TCFP FIELD32(0x00000010) +#define CSR14_TATIMW FIELD32(0x00000020) +#define CSR14_BEACON_GEN FIELD32(0x00000040) +#define CSR14_CFP_COUNT_PRELOAD FIELD32(0x0000ff00) +#define CSR14_TBCM_PRELOAD FIELD32(0xffff0000) + +/* + * CSR15: Synchronization status register. + * CFP: ASIC is in contention-free period. + * ATIMW: ASIC is in ATIM window. + * BEACON_SENT: Beacon is send. + */ +#define CSR15 0x003c +#define CSR15_CFP FIELD32(0x00000001) +#define CSR15_ATIMW FIELD32(0x00000002) +#define CSR15_BEACON_SENT FIELD32(0x00000004) + +/* + * CSR16: TSF timer register 0. + */ +#define CSR16 0x0040 +#define CSR16_LOW_TSFTIMER FIELD32(0xffffffff) + +/* + * CSR17: TSF timer register 1. + */ +#define CSR17 0x0044 +#define CSR17_HIGH_TSFTIMER FIELD32(0xffffffff) + +/* + * CSR18: IFS timer register 0. + * SIFS: Sifs, default is 10 us. + * PIFS: Pifs, default is 30 us. + */ +#define CSR18 0x0048 +#define CSR18_SIFS FIELD32(0x0000ffff) +#define CSR18_PIFS FIELD32(0xffff0000) + +/* + * CSR19: IFS timer register 1. + * DIFS: Difs, default is 50 us. + * EIFS: Eifs, default is 364 us. + */ +#define CSR19 0x004c +#define CSR19_DIFS FIELD32(0x0000ffff) +#define CSR19_EIFS FIELD32(0xffff0000) + +/* + * CSR20: Wakeup timer register. + * DELAY_AFTER_TBCN: Delay after tbcn expired in units of 1/16 TU. + * TBCN_BEFORE_WAKEUP: Number of beacon before wakeup. + * AUTOWAKE: Enable auto wakeup / sleep mechanism. + */ +#define CSR20 0x0050 +#define CSR20_DELAY_AFTER_TBCN FIELD32(0x0000ffff) +#define CSR20_TBCN_BEFORE_WAKEUP FIELD32(0x00ff0000) +#define CSR20_AUTOWAKE FIELD32(0x01000000) + +/* + * CSR21: EEPROM control register. + * RELOAD: Write 1 to reload eeprom content. + * TYPE_93C46: 1: 93c46, 0:93c66. + */ +#define CSR21 0x0054 +#define CSR21_RELOAD FIELD32(0x00000001) +#define CSR21_EEPROM_DATA_CLOCK FIELD32(0x00000002) +#define CSR21_EEPROM_CHIP_SELECT FIELD32(0x00000004) +#define CSR21_EEPROM_DATA_IN FIELD32(0x00000008) +#define CSR21_EEPROM_DATA_OUT FIELD32(0x00000010) +#define CSR21_TYPE_93C46 FIELD32(0x00000020) + +/* + * CSR22: CFP control register. + * CFP_DURATION_REMAIN: Cfp duration remain, in units of TU. + * RELOAD_CFP_DURATION: Write 1 to reload cfp duration remain. + */ +#define CSR22 0x0058 +#define CSR22_CFP_DURATION_REMAIN FIELD32(0x0000ffff) +#define CSR22_RELOAD_CFP_DURATION FIELD32(0x00010000) + +/* + * Transmit related CSRs. + * Some values are set in TU, whereas 1 TU == 1024 us. + */ + +/* + * TXCSR0: TX Control Register. + * KICK_TX: Kick tx ring. + * KICK_ATIM: Kick atim ring. + * KICK_PRIO: Kick priority ring. + * ABORT: Abort all transmit related ring operation. + */ +#define TXCSR0 0x0060 +#define TXCSR0_KICK_TX FIELD32(0x00000001) +#define TXCSR0_KICK_ATIM FIELD32(0x00000002) +#define TXCSR0_KICK_PRIO FIELD32(0x00000004) +#define TXCSR0_ABORT FIELD32(0x00000008) + +/* + * TXCSR1: TX Configuration Register. + * ACK_TIMEOUT: Ack timeout, default = sifs + 2*slottime + acktime @ 1mbps. + * ACK_CONSUME_TIME: Ack consume time, default = sifs + acktime @ 1mbps. + * TSF_OFFSET: Insert tsf offset. + * AUTORESPONDER: Enable auto responder which include ack & cts. + */ +#define TXCSR1 0x0064 +#define TXCSR1_ACK_TIMEOUT FIELD32(0x000001ff) +#define TXCSR1_ACK_CONSUME_TIME FIELD32(0x0003fe00) +#define TXCSR1_TSF_OFFSET FIELD32(0x00fc0000) +#define TXCSR1_AUTORESPONDER FIELD32(0x01000000) + +/* + * TXCSR2: Tx descriptor configuration register. + * TXD_SIZE: Tx descriptor size, default is 48. + * NUM_TXD: Number of tx entries in ring. + * NUM_ATIM: Number of atim entries in ring. + * NUM_PRIO: Number of priority entries in ring. + */ +#define TXCSR2 0x0068 +#define TXCSR2_TXD_SIZE FIELD32(0x000000ff) +#define TXCSR2_NUM_TXD FIELD32(0x0000ff00) +#define TXCSR2_NUM_ATIM FIELD32(0x00ff0000) +#define TXCSR2_NUM_PRIO FIELD32(0xff000000) + +/* + * TXCSR3: TX Ring Base address register. + */ +#define TXCSR3 0x006c +#define TXCSR3_TX_RING_REGISTER FIELD32(0xffffffff) + +/* + * TXCSR4: TX Atim Ring Base address register. + */ +#define TXCSR4 0x0070 +#define TXCSR4_ATIM_RING_REGISTER FIELD32(0xffffffff) + +/* + * TXCSR5: TX Prio Ring Base address register. + */ +#define TXCSR5 0x0074 +#define TXCSR5_PRIO_RING_REGISTER FIELD32(0xffffffff) + +/* + * TXCSR6: Beacon Base address register. + */ +#define TXCSR6 0x0078 +#define TXCSR6_BEACON_RING_REGISTER FIELD32(0xffffffff) + +/* + * TXCSR7: Auto responder control register. + * AR_POWERMANAGEMENT: Auto responder power management bit. + */ +#define TXCSR7 0x007c +#define TXCSR7_AR_POWERMANAGEMENT FIELD32(0x00000001) + +/* + * Receive related CSRs. + * Some values are set in TU, whereas 1 TU == 1024 us. + */ + +/* + * RXCSR0: RX Control Register. + * DISABLE_RX: Disable rx engine. + * DROP_CRC: Drop crc error. + * DROP_PHYSICAL: Drop physical error. + * DROP_CONTROL: Drop control frame. + * DROP_NOT_TO_ME: Drop not to me unicast frame. + * DROP_TODS: Drop frame tods bit is true. + * DROP_VERSION_ERROR: Drop version error frame. + * PASS_CRC: Pass all packets with crc attached. + */ +#define RXCSR0 0x0080 +#define RXCSR0_DISABLE_RX FIELD32(0x00000001) +#define RXCSR0_DROP_CRC FIELD32(0x00000002) +#define RXCSR0_DROP_PHYSICAL FIELD32(0x00000004) +#define RXCSR0_DROP_CONTROL FIELD32(0x00000008) +#define RXCSR0_DROP_NOT_TO_ME FIELD32(0x00000010) +#define RXCSR0_DROP_TODS FIELD32(0x00000020) +#define RXCSR0_DROP_VERSION_ERROR FIELD32(0x00000040) +#define RXCSR0_PASS_CRC FIELD32(0x00000080) + +/* + * RXCSR1: RX descriptor configuration register. + * RXD_SIZE: Rx descriptor size, default is 32b. + * NUM_RXD: Number of rx entries in ring. + */ +#define RXCSR1 0x0084 +#define RXCSR1_RXD_SIZE FIELD32(0x000000ff) +#define RXCSR1_NUM_RXD FIELD32(0x0000ff00) + +/* + * RXCSR2: RX Ring base address register. + */ +#define RXCSR2 0x0088 +#define RXCSR2_RX_RING_REGISTER FIELD32(0xffffffff) + +/* + * RXCSR3: BBP ID register for Rx operation. + * BBP_ID#: BBP register # id. + * BBP_ID#_VALID: BBP register # id is valid or not. + */ +#define RXCSR3 0x0090 +#define RXCSR3_BBP_ID0 FIELD32(0x0000007f) +#define RXCSR3_BBP_ID0_VALID FIELD32(0x00000080) +#define RXCSR3_BBP_ID1 FIELD32(0x00007f00) +#define RXCSR3_BBP_ID1_VALID FIELD32(0x00008000) +#define RXCSR3_BBP_ID2 FIELD32(0x007f0000) +#define RXCSR3_BBP_ID2_VALID FIELD32(0x00800000) +#define RXCSR3_BBP_ID3 FIELD32(0x7f000000) +#define RXCSR3_BBP_ID3_VALID FIELD32(0x80000000) + +/* + * RXCSR4: BBP ID register for Rx operation. + * BBP_ID#: BBP register # id. + * BBP_ID#_VALID: BBP register # id is valid or not. + */ +#define RXCSR4 0x0094 +#define RXCSR4_BBP_ID4 FIELD32(0x0000007f) +#define RXCSR4_BBP_ID4_VALID FIELD32(0x00000080) +#define RXCSR4_BBP_ID5 FIELD32(0x00007f00) +#define RXCSR4_BBP_ID5_VALID FIELD32(0x00008000) + +/* + * ARCSR0: Auto Responder PLCP config register 0. + * ARCSR0_AR_BBP_DATA#: Auto responder BBP register # data. + * ARCSR0_AR_BBP_ID#: Auto responder BBP register # Id. + */ +#define ARCSR0 0x0098 +#define ARCSR0_AR_BBP_DATA0 FIELD32(0x000000ff) +#define ARCSR0_AR_BBP_ID0 FIELD32(0x0000ff00) +#define ARCSR0_AR_BBP_DATA1 FIELD32(0x00ff0000) +#define ARCSR0_AR_BBP_ID1 FIELD32(0xff000000) + +/* + * ARCSR1: Auto Responder PLCP config register 1. + * ARCSR0_AR_BBP_DATA#: Auto responder BBP register # data. + * ARCSR0_AR_BBP_ID#: Auto responder BBP register # Id. + */ +#define ARCSR1 0x009c +#define ARCSR1_AR_BBP_DATA2 FIELD32(0x000000ff) +#define ARCSR1_AR_BBP_ID2 FIELD32(0x0000ff00) +#define ARCSR1_AR_BBP_DATA3 FIELD32(0x00ff0000) +#define ARCSR1_AR_BBP_ID3 FIELD32(0xff000000) + +/* + * Miscellaneous Registers. + * Some values are set in TU, whereas 1 TU == 1024 us. + */ + +/* + * PCICSR: PCI control register. + * BIG_ENDIAN: 1: big endian, 0: little endian. + * RX_TRESHOLD: Rx threshold in dw to start pci access + * 0: 16dw (default), 1: 8dw, 2: 4dw, 3: 32dw. + * TX_TRESHOLD: Tx threshold in dw to start pci access + * 0: 0dw (default), 1: 1dw, 2: 4dw, 3: forward. + * BURST_LENTH: Pci burst length 0: 4dw (default, 1: 8dw, 2: 16dw, 3:32dw. + * ENABLE_CLK: Enable clk_run, pci clock can't going down to non-operational. + */ +#define PCICSR 0x008c +#define PCICSR_BIG_ENDIAN FIELD32(0x00000001) +#define PCICSR_RX_TRESHOLD FIELD32(0x00000006) +#define PCICSR_TX_TRESHOLD FIELD32(0x00000018) +#define PCICSR_BURST_LENTH FIELD32(0x00000060) +#define PCICSR_ENABLE_CLK FIELD32(0x00000080) + +/* + * CNT0: FCS error count. + * FCS_ERROR: FCS error count, cleared when read. + */ +#define CNT0 0x00a0 +#define CNT0_FCS_ERROR FIELD32(0x0000ffff) + +/* + * Statistic Register. + * CNT1: PLCP error count. + * CNT2: Long error count. + * CNT3: CCA false alarm count. + * CNT4: Rx FIFO overflow count. + * CNT5: Tx FIFO underrun count. + */ +#define TIMECSR2 0x00a8 +#define CNT1 0x00ac +#define CNT2 0x00b0 +#define TIMECSR3 0x00b4 +#define CNT3 0x00b8 +#define CNT4 0x00bc +#define CNT5 0x00c0 + +/* + * Baseband Control Register. + */ + +/* + * PWRCSR0: Power mode configuration register. + */ +#define PWRCSR0 0x00c4 + +/* + * Power state transition time registers. + */ +#define PSCSR0 0x00c8 +#define PSCSR1 0x00cc +#define PSCSR2 0x00d0 +#define PSCSR3 0x00d4 + +/* + * PWRCSR1: Manual power control / status register. + * Allowed state: 0 deep_sleep, 1: sleep, 2: standby, 3: awake. + * SET_STATE: Set state. Write 1 to trigger, self cleared. + * BBP_DESIRE_STATE: BBP desired state. + * RF_DESIRE_STATE: RF desired state. + * BBP_CURR_STATE: BBP current state. + * RF_CURR_STATE: RF current state. + * PUT_TO_SLEEP: Put to sleep. Write 1 to trigger, self cleared. + */ +#define PWRCSR1 0x00d8 +#define PWRCSR1_SET_STATE FIELD32(0x00000001) +#define PWRCSR1_BBP_DESIRE_STATE FIELD32(0x00000006) +#define PWRCSR1_RF_DESIRE_STATE FIELD32(0x00000018) +#define PWRCSR1_BBP_CURR_STATE FIELD32(0x00000060) +#define PWRCSR1_RF_CURR_STATE FIELD32(0x00000180) +#define PWRCSR1_PUT_TO_SLEEP FIELD32(0x00000200) + +/* + * TIMECSR: Timer control register. + * US_COUNT: 1 us timer count in units of clock cycles. + * US_64_COUNT: 64 us timer count in units of 1 us timer. + * BEACON_EXPECT: Beacon expect window. + */ +#define TIMECSR 0x00dc +#define TIMECSR_US_COUNT FIELD32(0x000000ff) +#define TIMECSR_US_64_COUNT FIELD32(0x0000ff00) +#define TIMECSR_BEACON_EXPECT FIELD32(0x00070000) + +/* + * MACCSR0: MAC configuration register 0. + */ +#define MACCSR0 0x00e0 + +/* + * MACCSR1: MAC configuration register 1. + * KICK_RX: Kick one-shot rx in one-shot rx mode. + * ONESHOT_RXMODE: Enable one-shot rx mode for debugging. + * BBPRX_RESET_MODE: Ralink bbp rx reset mode. + * AUTO_TXBBP: Auto tx logic access bbp control register. + * AUTO_RXBBP: Auto rx logic access bbp control register. + * LOOPBACK: Loopback mode. 0: normal, 1: internal, 2: external, 3:rsvd. + * INTERSIL_IF: Intersil if calibration pin. + */ +#define MACCSR1 0x00e4 +#define MACCSR1_KICK_RX FIELD32(0x00000001) +#define MACCSR1_ONESHOT_RXMODE FIELD32(0x00000002) +#define MACCSR1_BBPRX_RESET_MODE FIELD32(0x00000004) +#define MACCSR1_AUTO_TXBBP FIELD32(0x00000008) +#define MACCSR1_AUTO_RXBBP FIELD32(0x00000010) +#define MACCSR1_LOOPBACK FIELD32(0x00000060) +#define MACCSR1_INTERSIL_IF FIELD32(0x00000080) + +/* + * RALINKCSR: Ralink Rx auto-reset BBCR. + * AR_BBP_DATA#: Auto reset BBP register # data. + * AR_BBP_ID#: Auto reset BBP register # id. + */ +#define RALINKCSR 0x00e8 +#define RALINKCSR_AR_BBP_DATA0 FIELD32(0x000000ff) +#define RALINKCSR_AR_BBP_ID0 FIELD32(0x0000ff00) +#define RALINKCSR_AR_BBP_DATA1 FIELD32(0x00ff0000) +#define RALINKCSR_AR_BBP_ID1 FIELD32(0xff000000) + +/* + * BCNCSR: Beacon interval control register. + * CHANGE: Write one to change beacon interval. + * DELTATIME: The delta time value. + * NUM_BEACON: Number of beacon according to mode. + * MODE: Please refer to asic specs. + * PLUS: Plus or minus delta time value. + */ +#define BCNCSR 0x00ec +#define BCNCSR_CHANGE FIELD32(0x00000001) +#define BCNCSR_DELTATIME FIELD32(0x0000001e) +#define BCNCSR_NUM_BEACON FIELD32(0x00001fe0) +#define BCNCSR_MODE FIELD32(0x00006000) +#define BCNCSR_PLUS FIELD32(0x00008000) + +/* + * BBP / RF / IF Control Register. + */ + +/* + * BBPCSR: BBP serial control register. + * VALUE: Register value to program into BBP. + * REGNUM: Selected BBP register. + * BUSY: 1: asic is busy execute BBP programming. + * WRITE_CONTROL: 1: write BBP, 0: read BBP. + */ +#define BBPCSR 0x00f0 +#define BBPCSR_VALUE FIELD32(0x000000ff) +#define BBPCSR_REGNUM FIELD32(0x00007f00) +#define BBPCSR_BUSY FIELD32(0x00008000) +#define BBPCSR_WRITE_CONTROL FIELD32(0x00010000) + +/* + * RFCSR: RF serial control register. + * VALUE: Register value + id to program into rf/if. + * NUMBER_OF_BITS: Number of bits used in value (i:20, rfmd:22). + * IF_SELECT: Chip to program: 0: rf, 1: if. + * PLL_LD: Rf pll_ld status. + * BUSY: 1: asic is busy execute rf programming. + */ +#define RFCSR 0x00f4 +#define RFCSR_VALUE FIELD32(0x00ffffff) +#define RFCSR_NUMBER_OF_BITS FIELD32(0x1f000000) +#define RFCSR_IF_SELECT FIELD32(0x20000000) +#define RFCSR_PLL_LD FIELD32(0x40000000) +#define RFCSR_BUSY FIELD32(0x80000000) + +/* + * LEDCSR: LED control register. + * ON_PERIOD: On period, default 70ms. + * OFF_PERIOD: Off period, default 30ms. + * LINK: 0: linkoff, 1: linkup. + * ACTIVITY: 0: idle, 1: active. + */ +#define LEDCSR 0x00f8 +#define LEDCSR_ON_PERIOD FIELD32(0x000000ff) +#define LEDCSR_OFF_PERIOD FIELD32(0x0000ff00) +#define LEDCSR_LINK FIELD32(0x00010000) +#define LEDCSR_ACTIVITY FIELD32(0x00020000) + +/* + * ASIC pointer information. + * RXPTR: Current RX ring address. + * TXPTR: Current Tx ring address. + * PRIPTR: Current Priority ring address. + * ATIMPTR: Current ATIM ring address. + */ +#define RXPTR 0x0100 +#define TXPTR 0x0104 +#define PRIPTR 0x0108 +#define ATIMPTR 0x010c + +/* + * GPIO and others. + */ + +/* + * GPIOCSR: GPIO control register. + * GPIOCSR_VALx: Actual GPIO pin x value + * GPIOCSR_DIRx: GPIO direction: 0 = output; 1 = input + */ +#define GPIOCSR 0x0120 +#define GPIOCSR_VAL0 FIELD32(0x00000001) +#define GPIOCSR_VAL1 FIELD32(0x00000002) +#define GPIOCSR_VAL2 FIELD32(0x00000004) +#define GPIOCSR_VAL3 FIELD32(0x00000008) +#define GPIOCSR_VAL4 FIELD32(0x00000010) +#define GPIOCSR_VAL5 FIELD32(0x00000020) +#define GPIOCSR_VAL6 FIELD32(0x00000040) +#define GPIOCSR_VAL7 FIELD32(0x00000080) +#define GPIOCSR_DIR0 FIELD32(0x00000100) +#define GPIOCSR_DIR1 FIELD32(0x00000200) +#define GPIOCSR_DIR2 FIELD32(0x00000400) +#define GPIOCSR_DIR3 FIELD32(0x00000800) +#define GPIOCSR_DIR4 FIELD32(0x00001000) +#define GPIOCSR_DIR5 FIELD32(0x00002000) +#define GPIOCSR_DIR6 FIELD32(0x00004000) +#define GPIOCSR_DIR7 FIELD32(0x00008000) + +/* + * BBPPCSR: BBP Pin control register. + */ +#define BBPPCSR 0x0124 + +/* + * BCNCSR1: Tx BEACON offset time control register. + * PRELOAD: Beacon timer offset in units of usec. + */ +#define BCNCSR1 0x0130 +#define BCNCSR1_PRELOAD FIELD32(0x0000ffff) + +/* + * MACCSR2: TX_PE to RX_PE turn-around time control register + * DELAY: RX_PE low width, in units of pci clock cycle. + */ +#define MACCSR2 0x0134 +#define MACCSR2_DELAY FIELD32(0x000000ff) + +/* + * ARCSR2: 1 Mbps ACK/CTS PLCP. + */ +#define ARCSR2 0x013c +#define ARCSR2_SIGNAL FIELD32(0x000000ff) +#define ARCSR2_SERVICE FIELD32(0x0000ff00) +#define ARCSR2_LENGTH_LOW FIELD32(0x00ff0000) +#define ARCSR2_LENGTH FIELD32(0xffff0000) + +/* + * ARCSR3: 2 Mbps ACK/CTS PLCP. + */ +#define ARCSR3 0x0140 +#define ARCSR3_SIGNAL FIELD32(0x000000ff) +#define ARCSR3_SERVICE FIELD32(0x0000ff00) +#define ARCSR3_LENGTH FIELD32(0xffff0000) + +/* + * ARCSR4: 5.5 Mbps ACK/CTS PLCP. + */ +#define ARCSR4 0x0144 +#define ARCSR4_SIGNAL FIELD32(0x000000ff) +#define ARCSR4_SERVICE FIELD32(0x0000ff00) +#define ARCSR4_LENGTH FIELD32(0xffff0000) + +/* + * ARCSR5: 11 Mbps ACK/CTS PLCP. + */ +#define ARCSR5 0x0148 +#define ARCSR5_SIGNAL FIELD32(0x000000ff) +#define ARCSR5_SERVICE FIELD32(0x0000ff00) +#define ARCSR5_LENGTH FIELD32(0xffff0000) + +/* + * BBP registers. + * The wordsize of the BBP is 8 bits. + */ + +/* + * R1: TX antenna control + */ +#define BBP_R1_TX_ANTENNA FIELD8(0x03) + +/* + * R4: RX antenna control + */ +#define BBP_R4_RX_ANTENNA FIELD8(0x06) + +/* + * RF registers + */ + +/* + * RF 1 + */ +#define RF1_TUNER FIELD32(0x00020000) + +/* + * RF 3 + */ +#define RF3_TUNER FIELD32(0x00000100) +#define RF3_TXPOWER FIELD32(0x00003e00) + +/* + * EEPROM content. + * The wordsize of the EEPROM is 16 bits. + */ + +/* + * HW MAC address. + */ +#define EEPROM_MAC_ADDR_0 0x0002 +#define EEPROM_MAC_ADDR_BYTE0 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE1 FIELD16(0xff00) +#define EEPROM_MAC_ADDR1 0x0003 +#define EEPROM_MAC_ADDR_BYTE2 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE3 FIELD16(0xff00) +#define EEPROM_MAC_ADDR_2 0x0004 +#define EEPROM_MAC_ADDR_BYTE4 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE5 FIELD16(0xff00) + +/* + * EEPROM antenna. + * ANTENNA_NUM: Number of antenna's. + * TX_DEFAULT: Default antenna 0: diversity, 1: A, 2: B. + * RX_DEFAULT: Default antenna 0: diversity, 1: A, 2: B. + * RF_TYPE: Rf_type of this adapter. + * LED_MODE: 0: default, 1: TX/RX activity,2: Single (ignore link), 3: rsvd. + * RX_AGCVGC: 0: disable, 1:enable BBP R13 tuning. + * HARDWARE_RADIO: 1: Hardware controlled radio. Read GPIO0. + */ +#define EEPROM_ANTENNA 0x0b +#define EEPROM_ANTENNA_NUM FIELD16(0x0003) +#define EEPROM_ANTENNA_TX_DEFAULT FIELD16(0x000c) +#define EEPROM_ANTENNA_RX_DEFAULT FIELD16(0x0030) +#define EEPROM_ANTENNA_RF_TYPE FIELD16(0x0040) +#define EEPROM_ANTENNA_LED_MODE FIELD16(0x0180) +#define EEPROM_ANTENNA_RX_AGCVGC_TUNING FIELD16(0x0200) +#define EEPROM_ANTENNA_HARDWARE_RADIO FIELD16(0x0400) + +/* + * EEPROM BBP. + */ +#define EEPROM_BBP_START 0x0c +#define EEPROM_BBP_SIZE 7 +#define EEPROM_BBP_VALUE FIELD16(0x00ff) +#define EEPROM_BBP_REG_ID FIELD16(0xff00) + +/* + * EEPROM TXPOWER + */ +#define EEPROM_TXPOWER_START 0x13 +#define EEPROM_TXPOWER_SIZE 7 +#define EEPROM_TXPOWER_1 FIELD16(0x00ff) +#define EEPROM_TXPOWER_2 FIELD16(0xff00) + +/* + * DMA descriptor defines. + */ +#define TXD_DESC_SIZE (8 * sizeof(__le32)) +#define RXD_DESC_SIZE (8 * sizeof(__le32)) + +/* + * TX descriptor format for TX, PRIO, ATIM and Beacon Ring. + */ + +/* + * Word0 + */ +#define TXD_W0_OWNER_NIC FIELD32(0x00000001) +#define TXD_W0_VALID FIELD32(0x00000002) +#define TXD_W0_RESULT FIELD32(0x0000001c) +#define TXD_W0_RETRY_COUNT FIELD32(0x000000e0) +#define TXD_W0_MORE_FRAG FIELD32(0x00000100) +#define TXD_W0_ACK FIELD32(0x00000200) +#define TXD_W0_TIMESTAMP FIELD32(0x00000400) +#define TXD_W0_RTS FIELD32(0x00000800) +#define TXD_W0_IFS FIELD32(0x00006000) +#define TXD_W0_RETRY_MODE FIELD32(0x00008000) +#define TXD_W0_AGC FIELD32(0x00ff0000) +#define TXD_W0_R2 FIELD32(0xff000000) + +/* + * Word1 + */ +#define TXD_W1_BUFFER_ADDRESS FIELD32(0xffffffff) + +/* + * Word2 + */ +#define TXD_W2_BUFFER_LENGTH FIELD32(0x0000ffff) +#define TXD_W2_DATABYTE_COUNT FIELD32(0xffff0000) + +/* + * Word3 & 4: PLCP information + * The PLCP values should be treated as if they were BBP values. + */ +#define TXD_W3_PLCP_SIGNAL FIELD32(0x000000ff) +#define TXD_W3_PLCP_SIGNAL_REGNUM FIELD32(0x00007f00) +#define TXD_W3_PLCP_SIGNAL_BUSY FIELD32(0x00008000) +#define TXD_W3_PLCP_SERVICE FIELD32(0x00ff0000) +#define TXD_W3_PLCP_SERVICE_REGNUM FIELD32(0x7f000000) +#define TXD_W3_PLCP_SERVICE_BUSY FIELD32(0x80000000) + +#define TXD_W4_PLCP_LENGTH_LOW FIELD32(0x000000ff) +#define TXD_W3_PLCP_LENGTH_LOW_REGNUM FIELD32(0x00007f00) +#define TXD_W3_PLCP_LENGTH_LOW_BUSY FIELD32(0x00008000) +#define TXD_W4_PLCP_LENGTH_HIGH FIELD32(0x00ff0000) +#define TXD_W3_PLCP_LENGTH_HIGH_REGNUM FIELD32(0x7f000000) +#define TXD_W3_PLCP_LENGTH_HIGH_BUSY FIELD32(0x80000000) + +/* + * Word5 + */ +#define TXD_W5_BBCR4 FIELD32(0x0000ffff) +#define TXD_W5_AGC_REG FIELD32(0x007f0000) +#define TXD_W5_AGC_REG_VALID FIELD32(0x00800000) +#define TXD_W5_XXX_REG FIELD32(0x7f000000) +#define TXD_W5_XXX_REG_VALID FIELD32(0x80000000) + +/* + * Word6 + */ +#define TXD_W6_SK_BUFF FIELD32(0xffffffff) + +/* + * Word7 + */ +#define TXD_W7_RESERVED FIELD32(0xffffffff) + +/* + * RX descriptor format for RX Ring. + */ + +/* + * Word0 + */ +#define RXD_W0_OWNER_NIC FIELD32(0x00000001) +#define RXD_W0_UNICAST_TO_ME FIELD32(0x00000002) +#define RXD_W0_MULTICAST FIELD32(0x00000004) +#define RXD_W0_BROADCAST FIELD32(0x00000008) +#define RXD_W0_MY_BSS FIELD32(0x00000010) +#define RXD_W0_CRC_ERROR FIELD32(0x00000020) +#define RXD_W0_PHYSICAL_ERROR FIELD32(0x00000080) +#define RXD_W0_DATABYTE_COUNT FIELD32(0xffff0000) + +/* + * Word1 + */ +#define RXD_W1_BUFFER_ADDRESS FIELD32(0xffffffff) + +/* + * Word2 + */ +#define RXD_W2_BUFFER_LENGTH FIELD32(0x0000ffff) +#define RXD_W2_BBR0 FIELD32(0x00ff0000) +#define RXD_W2_SIGNAL FIELD32(0xff000000) + +/* + * Word3 + */ +#define RXD_W3_RSSI FIELD32(0x000000ff) +#define RXD_W3_BBR3 FIELD32(0x0000ff00) +#define RXD_W3_BBR4 FIELD32(0x00ff0000) +#define RXD_W3_BBR5 FIELD32(0xff000000) + +/* + * Word4 + */ +#define RXD_W4_RX_END_TIME FIELD32(0xffffffff) + +/* + * Word5 & 6 & 7: Reserved + */ +#define RXD_W5_RESERVED FIELD32(0xffffffff) +#define RXD_W6_RESERVED FIELD32(0xffffffff) +#define RXD_W7_RESERVED FIELD32(0xffffffff) + +/* + * Macros for converting txpower from EEPROM to mac80211 value + * and from mac80211 value to register value. + * NOTE: Logics in rt2400pci for txpower are reversed + * compared to the other rt2x00 drivers. A higher txpower + * value means that the txpower must be lowered. This is + * important when converting the value coming from the + * mac80211 stack to the rt2400 acceptable value. + */ +#define MIN_TXPOWER 31 +#define MAX_TXPOWER 62 +#define DEFAULT_TXPOWER 39 + +#define __CLAMP_TX(__txpower) \ + clamp_t(char, (__txpower), MIN_TXPOWER, MAX_TXPOWER) + +#define TXPOWER_FROM_DEV(__txpower) \ + ((__CLAMP_TX(__txpower) - MAX_TXPOWER) + MIN_TXPOWER) + +#define TXPOWER_TO_DEV(__txpower) \ + (MAX_TXPOWER - (__CLAMP_TX(__txpower) - MIN_TXPOWER)) + +#endif /* RT2400PCI_H */ diff --git a/drivers/net/wireless/rt2x00/rt2500pci.c b/drivers/net/wireless/rt2x00/rt2500pci.c new file mode 100644 index 0000000..db888be --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2500pci.c @@ -0,0 +1,2135 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2500pci + Abstract: rt2500pci device specific routines. + Supported chipsets: RT2560. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rt2x00.h" +#include "rt2x00mmio.h" +#include "rt2x00pci.h" +#include "rt2500pci.h" + +/* + * Register access. + * All access to the CSR registers will go through the methods + * rt2x00mmio_register_read and rt2x00mmio_register_write. + * BBP and RF register require indirect register access, + * and use the CSR registers BBPCSR and RFCSR to achieve this. + * These indirect registers work with busy bits, + * and we will try maximal REGISTER_BUSY_COUNT times to access + * the register while taking a REGISTER_BUSY_DELAY us delay + * between each attampt. When the busy bit is still set at that time, + * the access attempt is considered to have failed, + * and we will print an error. + */ +#define WAIT_FOR_BBP(__dev, __reg) \ + rt2x00mmio_regbusy_read((__dev), BBPCSR, BBPCSR_BUSY, (__reg)) +#define WAIT_FOR_RF(__dev, __reg) \ + rt2x00mmio_regbusy_read((__dev), RFCSR, RFCSR_BUSY, (__reg)) + +static void rt2500pci_bbp_write(struct rt2x00_dev *rt2x00dev, + const unsigned int word, const u8 value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the BBP becomes available, afterwards we + * can safely write the new data into the register. + */ + if (WAIT_FOR_BBP(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, BBPCSR_VALUE, value); + rt2x00_set_field32(®, BBPCSR_REGNUM, word); + rt2x00_set_field32(®, BBPCSR_BUSY, 1); + rt2x00_set_field32(®, BBPCSR_WRITE_CONTROL, 1); + + rt2x00mmio_register_write(rt2x00dev, BBPCSR, reg); + } + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt2500pci_bbp_read(struct rt2x00_dev *rt2x00dev, + const unsigned int word, u8 *value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the BBP becomes available, afterwards we + * can safely write the read request into the register. + * After the data has been written, we wait until hardware + * returns the correct value, if at any time the register + * doesn't become available in time, reg will be 0xffffffff + * which means we return 0xff to the caller. + */ + if (WAIT_FOR_BBP(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, BBPCSR_REGNUM, word); + rt2x00_set_field32(®, BBPCSR_BUSY, 1); + rt2x00_set_field32(®, BBPCSR_WRITE_CONTROL, 0); + + rt2x00mmio_register_write(rt2x00dev, BBPCSR, reg); + + WAIT_FOR_BBP(rt2x00dev, ®); + } + + *value = rt2x00_get_field32(reg, BBPCSR_VALUE); + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt2500pci_rf_write(struct rt2x00_dev *rt2x00dev, + const unsigned int word, const u32 value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the RF becomes available, afterwards we + * can safely write the new data into the register. + */ + if (WAIT_FOR_RF(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, RFCSR_VALUE, value); + rt2x00_set_field32(®, RFCSR_NUMBER_OF_BITS, 20); + rt2x00_set_field32(®, RFCSR_IF_SELECT, 0); + rt2x00_set_field32(®, RFCSR_BUSY, 1); + + rt2x00mmio_register_write(rt2x00dev, RFCSR, reg); + rt2x00_rf_write(rt2x00dev, word, value); + } + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt2500pci_eepromregister_read(struct eeprom_93cx6 *eeprom) +{ + struct rt2x00_dev *rt2x00dev = eeprom->data; + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, CSR21, ®); + + eeprom->reg_data_in = !!rt2x00_get_field32(reg, CSR21_EEPROM_DATA_IN); + eeprom->reg_data_out = !!rt2x00_get_field32(reg, CSR21_EEPROM_DATA_OUT); + eeprom->reg_data_clock = + !!rt2x00_get_field32(reg, CSR21_EEPROM_DATA_CLOCK); + eeprom->reg_chip_select = + !!rt2x00_get_field32(reg, CSR21_EEPROM_CHIP_SELECT); +} + +static void rt2500pci_eepromregister_write(struct eeprom_93cx6 *eeprom) +{ + struct rt2x00_dev *rt2x00dev = eeprom->data; + u32 reg = 0; + + rt2x00_set_field32(®, CSR21_EEPROM_DATA_IN, !!eeprom->reg_data_in); + rt2x00_set_field32(®, CSR21_EEPROM_DATA_OUT, !!eeprom->reg_data_out); + rt2x00_set_field32(®, CSR21_EEPROM_DATA_CLOCK, + !!eeprom->reg_data_clock); + rt2x00_set_field32(®, CSR21_EEPROM_CHIP_SELECT, + !!eeprom->reg_chip_select); + + rt2x00mmio_register_write(rt2x00dev, CSR21, reg); +} + +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS +static const struct rt2x00debug rt2500pci_rt2x00debug = { + .owner = THIS_MODULE, + .csr = { + .read = rt2x00mmio_register_read, + .write = rt2x00mmio_register_write, + .flags = RT2X00DEBUGFS_OFFSET, + .word_base = CSR_REG_BASE, + .word_size = sizeof(u32), + .word_count = CSR_REG_SIZE / sizeof(u32), + }, + .eeprom = { + .read = rt2x00_eeprom_read, + .write = rt2x00_eeprom_write, + .word_base = EEPROM_BASE, + .word_size = sizeof(u16), + .word_count = EEPROM_SIZE / sizeof(u16), + }, + .bbp = { + .read = rt2500pci_bbp_read, + .write = rt2500pci_bbp_write, + .word_base = BBP_BASE, + .word_size = sizeof(u8), + .word_count = BBP_SIZE / sizeof(u8), + }, + .rf = { + .read = rt2x00_rf_read, + .write = rt2500pci_rf_write, + .word_base = RF_BASE, + .word_size = sizeof(u32), + .word_count = RF_SIZE / sizeof(u32), + }, +}; +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ + +static int rt2500pci_rfkill_poll(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, GPIOCSR, ®); + return rt2x00_get_field32(reg, GPIOCSR_VAL0); +} + +#ifdef CPTCFG_RT2X00_LIB_LEDS +static void rt2500pci_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct rt2x00_led *led = + container_of(led_cdev, struct rt2x00_led, led_dev); + unsigned int enabled = brightness != LED_OFF; + u32 reg; + + rt2x00mmio_register_read(led->rt2x00dev, LEDCSR, ®); + + if (led->type == LED_TYPE_RADIO || led->type == LED_TYPE_ASSOC) + rt2x00_set_field32(®, LEDCSR_LINK, enabled); + else if (led->type == LED_TYPE_ACTIVITY) + rt2x00_set_field32(®, LEDCSR_ACTIVITY, enabled); + + rt2x00mmio_register_write(led->rt2x00dev, LEDCSR, reg); +} + +static int rt2500pci_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct rt2x00_led *led = + container_of(led_cdev, struct rt2x00_led, led_dev); + u32 reg; + + rt2x00mmio_register_read(led->rt2x00dev, LEDCSR, ®); + rt2x00_set_field32(®, LEDCSR_ON_PERIOD, *delay_on); + rt2x00_set_field32(®, LEDCSR_OFF_PERIOD, *delay_off); + rt2x00mmio_register_write(led->rt2x00dev, LEDCSR, reg); + + return 0; +} + +static void rt2500pci_init_led(struct rt2x00_dev *rt2x00dev, + struct rt2x00_led *led, + enum led_type type) +{ + led->rt2x00dev = rt2x00dev; + led->type = type; + led->led_dev.brightness_set = rt2500pci_brightness_set; + led->led_dev.blink_set = rt2500pci_blink_set; + led->flags = LED_INITIALIZED; +} +#endif /* CPTCFG_RT2X00_LIB_LEDS */ + +/* + * Configuration handlers. + */ +static void rt2500pci_config_filter(struct rt2x00_dev *rt2x00dev, + const unsigned int filter_flags) +{ + u32 reg; + + /* + * Start configuration steps. + * Note that the version error will always be dropped + * and broadcast frames will always be accepted since + * there is no filter for it at this time. + */ + rt2x00mmio_register_read(rt2x00dev, RXCSR0, ®); + rt2x00_set_field32(®, RXCSR0_DROP_CRC, + !(filter_flags & FIF_FCSFAIL)); + rt2x00_set_field32(®, RXCSR0_DROP_PHYSICAL, + !(filter_flags & FIF_PLCPFAIL)); + rt2x00_set_field32(®, RXCSR0_DROP_CONTROL, + !(filter_flags & FIF_CONTROL)); + rt2x00_set_field32(®, RXCSR0_DROP_NOT_TO_ME, + !(filter_flags & FIF_PROMISC_IN_BSS)); + rt2x00_set_field32(®, RXCSR0_DROP_TODS, + !(filter_flags & FIF_PROMISC_IN_BSS) && + !rt2x00dev->intf_ap_count); + rt2x00_set_field32(®, RXCSR0_DROP_VERSION_ERROR, 1); + rt2x00_set_field32(®, RXCSR0_DROP_MCAST, + !(filter_flags & FIF_ALLMULTI)); + rt2x00_set_field32(®, RXCSR0_DROP_BCAST, 0); + rt2x00mmio_register_write(rt2x00dev, RXCSR0, reg); +} + +static void rt2500pci_config_intf(struct rt2x00_dev *rt2x00dev, + struct rt2x00_intf *intf, + struct rt2x00intf_conf *conf, + const unsigned int flags) +{ + struct data_queue *queue = rt2x00dev->bcn; + unsigned int bcn_preload; + u32 reg; + + if (flags & CONFIG_UPDATE_TYPE) { + /* + * Enable beacon config + */ + bcn_preload = PREAMBLE + GET_DURATION(IEEE80211_HEADER, 20); + rt2x00mmio_register_read(rt2x00dev, BCNCSR1, ®); + rt2x00_set_field32(®, BCNCSR1_PRELOAD, bcn_preload); + rt2x00_set_field32(®, BCNCSR1_BEACON_CWMIN, queue->cw_min); + rt2x00mmio_register_write(rt2x00dev, BCNCSR1, reg); + + /* + * Enable synchronisation. + */ + rt2x00mmio_register_read(rt2x00dev, CSR14, ®); + rt2x00_set_field32(®, CSR14_TSF_SYNC, conf->sync); + rt2x00mmio_register_write(rt2x00dev, CSR14, reg); + } + + if (flags & CONFIG_UPDATE_MAC) + rt2x00mmio_register_multiwrite(rt2x00dev, CSR3, + conf->mac, sizeof(conf->mac)); + + if (flags & CONFIG_UPDATE_BSSID) + rt2x00mmio_register_multiwrite(rt2x00dev, CSR5, + conf->bssid, sizeof(conf->bssid)); +} + +static void rt2500pci_config_erp(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_erp *erp, + u32 changed) +{ + int preamble_mask; + u32 reg; + + /* + * When short preamble is enabled, we should set bit 0x08 + */ + if (changed & BSS_CHANGED_ERP_PREAMBLE) { + preamble_mask = erp->short_preamble << 3; + + rt2x00mmio_register_read(rt2x00dev, TXCSR1, ®); + rt2x00_set_field32(®, TXCSR1_ACK_TIMEOUT, 0x162); + rt2x00_set_field32(®, TXCSR1_ACK_CONSUME_TIME, 0xa2); + rt2x00_set_field32(®, TXCSR1_TSF_OFFSET, IEEE80211_HEADER); + rt2x00_set_field32(®, TXCSR1_AUTORESPONDER, 1); + rt2x00mmio_register_write(rt2x00dev, TXCSR1, reg); + + rt2x00mmio_register_read(rt2x00dev, ARCSR2, ®); + rt2x00_set_field32(®, ARCSR2_SIGNAL, 0x00); + rt2x00_set_field32(®, ARCSR2_SERVICE, 0x04); + rt2x00_set_field32(®, ARCSR2_LENGTH, + GET_DURATION(ACK_SIZE, 10)); + rt2x00mmio_register_write(rt2x00dev, ARCSR2, reg); + + rt2x00mmio_register_read(rt2x00dev, ARCSR3, ®); + rt2x00_set_field32(®, ARCSR3_SIGNAL, 0x01 | preamble_mask); + rt2x00_set_field32(®, ARCSR3_SERVICE, 0x04); + rt2x00_set_field32(®, ARCSR2_LENGTH, + GET_DURATION(ACK_SIZE, 20)); + rt2x00mmio_register_write(rt2x00dev, ARCSR3, reg); + + rt2x00mmio_register_read(rt2x00dev, ARCSR4, ®); + rt2x00_set_field32(®, ARCSR4_SIGNAL, 0x02 | preamble_mask); + rt2x00_set_field32(®, ARCSR4_SERVICE, 0x04); + rt2x00_set_field32(®, ARCSR2_LENGTH, + GET_DURATION(ACK_SIZE, 55)); + rt2x00mmio_register_write(rt2x00dev, ARCSR4, reg); + + rt2x00mmio_register_read(rt2x00dev, ARCSR5, ®); + rt2x00_set_field32(®, ARCSR5_SIGNAL, 0x03 | preamble_mask); + rt2x00_set_field32(®, ARCSR5_SERVICE, 0x84); + rt2x00_set_field32(®, ARCSR2_LENGTH, + GET_DURATION(ACK_SIZE, 110)); + rt2x00mmio_register_write(rt2x00dev, ARCSR5, reg); + } + + if (changed & BSS_CHANGED_BASIC_RATES) + rt2x00mmio_register_write(rt2x00dev, ARCSR1, erp->basic_rates); + + if (changed & BSS_CHANGED_ERP_SLOT) { + rt2x00mmio_register_read(rt2x00dev, CSR11, ®); + rt2x00_set_field32(®, CSR11_SLOT_TIME, erp->slot_time); + rt2x00mmio_register_write(rt2x00dev, CSR11, reg); + + rt2x00mmio_register_read(rt2x00dev, CSR18, ®); + rt2x00_set_field32(®, CSR18_SIFS, erp->sifs); + rt2x00_set_field32(®, CSR18_PIFS, erp->pifs); + rt2x00mmio_register_write(rt2x00dev, CSR18, reg); + + rt2x00mmio_register_read(rt2x00dev, CSR19, ®); + rt2x00_set_field32(®, CSR19_DIFS, erp->difs); + rt2x00_set_field32(®, CSR19_EIFS, erp->eifs); + rt2x00mmio_register_write(rt2x00dev, CSR19, reg); + } + + if (changed & BSS_CHANGED_BEACON_INT) { + rt2x00mmio_register_read(rt2x00dev, CSR12, ®); + rt2x00_set_field32(®, CSR12_BEACON_INTERVAL, + erp->beacon_int * 16); + rt2x00_set_field32(®, CSR12_CFP_MAX_DURATION, + erp->beacon_int * 16); + rt2x00mmio_register_write(rt2x00dev, CSR12, reg); + } + +} + +static void rt2500pci_config_ant(struct rt2x00_dev *rt2x00dev, + struct antenna_setup *ant) +{ + u32 reg; + u8 r14; + u8 r2; + + /* + * We should never come here because rt2x00lib is supposed + * to catch this and send us the correct antenna explicitely. + */ + BUG_ON(ant->rx == ANTENNA_SW_DIVERSITY || + ant->tx == ANTENNA_SW_DIVERSITY); + + rt2x00mmio_register_read(rt2x00dev, BBPCSR1, ®); + rt2500pci_bbp_read(rt2x00dev, 14, &r14); + rt2500pci_bbp_read(rt2x00dev, 2, &r2); + + /* + * Configure the TX antenna. + */ + switch (ant->tx) { + case ANTENNA_A: + rt2x00_set_field8(&r2, BBP_R2_TX_ANTENNA, 0); + rt2x00_set_field32(®, BBPCSR1_CCK, 0); + rt2x00_set_field32(®, BBPCSR1_OFDM, 0); + break; + case ANTENNA_B: + default: + rt2x00_set_field8(&r2, BBP_R2_TX_ANTENNA, 2); + rt2x00_set_field32(®, BBPCSR1_CCK, 2); + rt2x00_set_field32(®, BBPCSR1_OFDM, 2); + break; + } + + /* + * Configure the RX antenna. + */ + switch (ant->rx) { + case ANTENNA_A: + rt2x00_set_field8(&r14, BBP_R14_RX_ANTENNA, 0); + break; + case ANTENNA_B: + default: + rt2x00_set_field8(&r14, BBP_R14_RX_ANTENNA, 2); + break; + } + + /* + * RT2525E and RT5222 need to flip TX I/Q + */ + if (rt2x00_rf(rt2x00dev, RF2525E) || rt2x00_rf(rt2x00dev, RF5222)) { + rt2x00_set_field8(&r2, BBP_R2_TX_IQ_FLIP, 1); + rt2x00_set_field32(®, BBPCSR1_CCK_FLIP, 1); + rt2x00_set_field32(®, BBPCSR1_OFDM_FLIP, 1); + + /* + * RT2525E does not need RX I/Q Flip. + */ + if (rt2x00_rf(rt2x00dev, RF2525E)) + rt2x00_set_field8(&r14, BBP_R14_RX_IQ_FLIP, 0); + } else { + rt2x00_set_field32(®, BBPCSR1_CCK_FLIP, 0); + rt2x00_set_field32(®, BBPCSR1_OFDM_FLIP, 0); + } + + rt2x00mmio_register_write(rt2x00dev, BBPCSR1, reg); + rt2500pci_bbp_write(rt2x00dev, 14, r14); + rt2500pci_bbp_write(rt2x00dev, 2, r2); +} + +static void rt2500pci_config_channel(struct rt2x00_dev *rt2x00dev, + struct rf_channel *rf, const int txpower) +{ + u8 r70; + + /* + * Set TXpower. + */ + rt2x00_set_field32(&rf->rf3, RF3_TXPOWER, TXPOWER_TO_DEV(txpower)); + + /* + * Switch on tuning bits. + * For RT2523 devices we do not need to update the R1 register. + */ + if (!rt2x00_rf(rt2x00dev, RF2523)) + rt2x00_set_field32(&rf->rf1, RF1_TUNER, 1); + rt2x00_set_field32(&rf->rf3, RF3_TUNER, 1); + + /* + * For RT2525 we should first set the channel to half band higher. + */ + if (rt2x00_rf(rt2x00dev, RF2525)) { + static const u32 vals[] = { + 0x00080cbe, 0x00080d02, 0x00080d06, 0x00080d0a, + 0x00080d0e, 0x00080d12, 0x00080d16, 0x00080d1a, + 0x00080d1e, 0x00080d22, 0x00080d26, 0x00080d2a, + 0x00080d2e, 0x00080d3a + }; + + rt2500pci_rf_write(rt2x00dev, 1, rf->rf1); + rt2500pci_rf_write(rt2x00dev, 2, vals[rf->channel - 1]); + rt2500pci_rf_write(rt2x00dev, 3, rf->rf3); + if (rf->rf4) + rt2500pci_rf_write(rt2x00dev, 4, rf->rf4); + } + + rt2500pci_rf_write(rt2x00dev, 1, rf->rf1); + rt2500pci_rf_write(rt2x00dev, 2, rf->rf2); + rt2500pci_rf_write(rt2x00dev, 3, rf->rf3); + if (rf->rf4) + rt2500pci_rf_write(rt2x00dev, 4, rf->rf4); + + /* + * Channel 14 requires the Japan filter bit to be set. + */ + r70 = 0x46; + rt2x00_set_field8(&r70, BBP_R70_JAPAN_FILTER, rf->channel == 14); + rt2500pci_bbp_write(rt2x00dev, 70, r70); + + msleep(1); + + /* + * Switch off tuning bits. + * For RT2523 devices we do not need to update the R1 register. + */ + if (!rt2x00_rf(rt2x00dev, RF2523)) { + rt2x00_set_field32(&rf->rf1, RF1_TUNER, 0); + rt2500pci_rf_write(rt2x00dev, 1, rf->rf1); + } + + rt2x00_set_field32(&rf->rf3, RF3_TUNER, 0); + rt2500pci_rf_write(rt2x00dev, 3, rf->rf3); + + /* + * Clear false CRC during channel switch. + */ + rt2x00mmio_register_read(rt2x00dev, CNT0, &rf->rf1); +} + +static void rt2500pci_config_txpower(struct rt2x00_dev *rt2x00dev, + const int txpower) +{ + u32 rf3; + + rt2x00_rf_read(rt2x00dev, 3, &rf3); + rt2x00_set_field32(&rf3, RF3_TXPOWER, TXPOWER_TO_DEV(txpower)); + rt2500pci_rf_write(rt2x00dev, 3, rf3); +} + +static void rt2500pci_config_retry_limit(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf) +{ + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, CSR11, ®); + rt2x00_set_field32(®, CSR11_LONG_RETRY, + libconf->conf->long_frame_max_tx_count); + rt2x00_set_field32(®, CSR11_SHORT_RETRY, + libconf->conf->short_frame_max_tx_count); + rt2x00mmio_register_write(rt2x00dev, CSR11, reg); +} + +static void rt2500pci_config_ps(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf) +{ + enum dev_state state = + (libconf->conf->flags & IEEE80211_CONF_PS) ? + STATE_SLEEP : STATE_AWAKE; + u32 reg; + + if (state == STATE_SLEEP) { + rt2x00mmio_register_read(rt2x00dev, CSR20, ®); + rt2x00_set_field32(®, CSR20_DELAY_AFTER_TBCN, + (rt2x00dev->beacon_int - 20) * 16); + rt2x00_set_field32(®, CSR20_TBCN_BEFORE_WAKEUP, + libconf->conf->listen_interval - 1); + + /* We must first disable autowake before it can be enabled */ + rt2x00_set_field32(®, CSR20_AUTOWAKE, 0); + rt2x00mmio_register_write(rt2x00dev, CSR20, reg); + + rt2x00_set_field32(®, CSR20_AUTOWAKE, 1); + rt2x00mmio_register_write(rt2x00dev, CSR20, reg); + } else { + rt2x00mmio_register_read(rt2x00dev, CSR20, ®); + rt2x00_set_field32(®, CSR20_AUTOWAKE, 0); + rt2x00mmio_register_write(rt2x00dev, CSR20, reg); + } + + rt2x00dev->ops->lib->set_device_state(rt2x00dev, state); +} + +static void rt2500pci_config(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf, + const unsigned int flags) +{ + if (flags & IEEE80211_CONF_CHANGE_CHANNEL) + rt2500pci_config_channel(rt2x00dev, &libconf->rf, + libconf->conf->power_level); + if ((flags & IEEE80211_CONF_CHANGE_POWER) && + !(flags & IEEE80211_CONF_CHANGE_CHANNEL)) + rt2500pci_config_txpower(rt2x00dev, + libconf->conf->power_level); + if (flags & IEEE80211_CONF_CHANGE_RETRY_LIMITS) + rt2500pci_config_retry_limit(rt2x00dev, libconf); + if (flags & IEEE80211_CONF_CHANGE_PS) + rt2500pci_config_ps(rt2x00dev, libconf); +} + +/* + * Link tuning + */ +static void rt2500pci_link_stats(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual) +{ + u32 reg; + + /* + * Update FCS error count from register. + */ + rt2x00mmio_register_read(rt2x00dev, CNT0, ®); + qual->rx_failed = rt2x00_get_field32(reg, CNT0_FCS_ERROR); + + /* + * Update False CCA count from register. + */ + rt2x00mmio_register_read(rt2x00dev, CNT3, ®); + qual->false_cca = rt2x00_get_field32(reg, CNT3_FALSE_CCA); +} + +static inline void rt2500pci_set_vgc(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual, u8 vgc_level) +{ + if (qual->vgc_level_reg != vgc_level) { + rt2500pci_bbp_write(rt2x00dev, 17, vgc_level); + qual->vgc_level = vgc_level; + qual->vgc_level_reg = vgc_level; + } +} + +static void rt2500pci_reset_tuner(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual) +{ + rt2500pci_set_vgc(rt2x00dev, qual, 0x48); +} + +static void rt2500pci_link_tuner(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual, const u32 count) +{ + /* + * To prevent collisions with MAC ASIC on chipsets + * up to version C the link tuning should halt after 20 + * seconds while being associated. + */ + if (rt2x00_rev(rt2x00dev) < RT2560_VERSION_D && + rt2x00dev->intf_associated && count > 20) + return; + + /* + * Chipset versions C and lower should directly continue + * to the dynamic CCA tuning. Chipset version D and higher + * should go straight to dynamic CCA tuning when they + * are not associated. + */ + if (rt2x00_rev(rt2x00dev) < RT2560_VERSION_D || + !rt2x00dev->intf_associated) + goto dynamic_cca_tune; + + /* + * A too low RSSI will cause too much false CCA which will + * then corrupt the R17 tuning. To remidy this the tuning should + * be stopped (While making sure the R17 value will not exceed limits) + */ + if (qual->rssi < -80 && count > 20) { + if (qual->vgc_level_reg >= 0x41) + rt2500pci_set_vgc(rt2x00dev, qual, qual->vgc_level); + return; + } + + /* + * Special big-R17 for short distance + */ + if (qual->rssi >= -58) { + rt2500pci_set_vgc(rt2x00dev, qual, 0x50); + return; + } + + /* + * Special mid-R17 for middle distance + */ + if (qual->rssi >= -74) { + rt2500pci_set_vgc(rt2x00dev, qual, 0x41); + return; + } + + /* + * Leave short or middle distance condition, restore r17 + * to the dynamic tuning range. + */ + if (qual->vgc_level_reg >= 0x41) { + rt2500pci_set_vgc(rt2x00dev, qual, qual->vgc_level); + return; + } + +dynamic_cca_tune: + + /* + * R17 is inside the dynamic tuning range, + * start tuning the link based on the false cca counter. + */ + if (qual->false_cca > 512 && qual->vgc_level_reg < 0x40) + rt2500pci_set_vgc(rt2x00dev, qual, ++qual->vgc_level_reg); + else if (qual->false_cca < 100 && qual->vgc_level_reg > 0x32) + rt2500pci_set_vgc(rt2x00dev, qual, --qual->vgc_level_reg); +} + +/* + * Queue handlers. + */ +static void rt2500pci_start_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u32 reg; + + switch (queue->qid) { + case QID_RX: + rt2x00mmio_register_read(rt2x00dev, RXCSR0, ®); + rt2x00_set_field32(®, RXCSR0_DISABLE_RX, 0); + rt2x00mmio_register_write(rt2x00dev, RXCSR0, reg); + break; + case QID_BEACON: + rt2x00mmio_register_read(rt2x00dev, CSR14, ®); + rt2x00_set_field32(®, CSR14_TSF_COUNT, 1); + rt2x00_set_field32(®, CSR14_TBCN, 1); + rt2x00_set_field32(®, CSR14_BEACON_GEN, 1); + rt2x00mmio_register_write(rt2x00dev, CSR14, reg); + break; + default: + break; + } +} + +static void rt2500pci_kick_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u32 reg; + + switch (queue->qid) { + case QID_AC_VO: + rt2x00mmio_register_read(rt2x00dev, TXCSR0, ®); + rt2x00_set_field32(®, TXCSR0_KICK_PRIO, 1); + rt2x00mmio_register_write(rt2x00dev, TXCSR0, reg); + break; + case QID_AC_VI: + rt2x00mmio_register_read(rt2x00dev, TXCSR0, ®); + rt2x00_set_field32(®, TXCSR0_KICK_TX, 1); + rt2x00mmio_register_write(rt2x00dev, TXCSR0, reg); + break; + case QID_ATIM: + rt2x00mmio_register_read(rt2x00dev, TXCSR0, ®); + rt2x00_set_field32(®, TXCSR0_KICK_ATIM, 1); + rt2x00mmio_register_write(rt2x00dev, TXCSR0, reg); + break; + default: + break; + } +} + +static void rt2500pci_stop_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u32 reg; + + switch (queue->qid) { + case QID_AC_VO: + case QID_AC_VI: + case QID_ATIM: + rt2x00mmio_register_read(rt2x00dev, TXCSR0, ®); + rt2x00_set_field32(®, TXCSR0_ABORT, 1); + rt2x00mmio_register_write(rt2x00dev, TXCSR0, reg); + break; + case QID_RX: + rt2x00mmio_register_read(rt2x00dev, RXCSR0, ®); + rt2x00_set_field32(®, RXCSR0_DISABLE_RX, 1); + rt2x00mmio_register_write(rt2x00dev, RXCSR0, reg); + break; + case QID_BEACON: + rt2x00mmio_register_read(rt2x00dev, CSR14, ®); + rt2x00_set_field32(®, CSR14_TSF_COUNT, 0); + rt2x00_set_field32(®, CSR14_TBCN, 0); + rt2x00_set_field32(®, CSR14_BEACON_GEN, 0); + rt2x00mmio_register_write(rt2x00dev, CSR14, reg); + + /* + * Wait for possibly running tbtt tasklets. + */ + tasklet_kill(&rt2x00dev->tbtt_tasklet); + break; + default: + break; + } +} + +/* + * Initialization functions. + */ +static bool rt2500pci_get_entry_state(struct queue_entry *entry) +{ + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + u32 word; + + if (entry->queue->qid == QID_RX) { + rt2x00_desc_read(entry_priv->desc, 0, &word); + + return rt2x00_get_field32(word, RXD_W0_OWNER_NIC); + } else { + rt2x00_desc_read(entry_priv->desc, 0, &word); + + return (rt2x00_get_field32(word, TXD_W0_OWNER_NIC) || + rt2x00_get_field32(word, TXD_W0_VALID)); + } +} + +static void rt2500pci_clear_entry(struct queue_entry *entry) +{ + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + u32 word; + + if (entry->queue->qid == QID_RX) { + rt2x00_desc_read(entry_priv->desc, 1, &word); + rt2x00_set_field32(&word, RXD_W1_BUFFER_ADDRESS, skbdesc->skb_dma); + rt2x00_desc_write(entry_priv->desc, 1, word); + + rt2x00_desc_read(entry_priv->desc, 0, &word); + rt2x00_set_field32(&word, RXD_W0_OWNER_NIC, 1); + rt2x00_desc_write(entry_priv->desc, 0, word); + } else { + rt2x00_desc_read(entry_priv->desc, 0, &word); + rt2x00_set_field32(&word, TXD_W0_VALID, 0); + rt2x00_set_field32(&word, TXD_W0_OWNER_NIC, 0); + rt2x00_desc_write(entry_priv->desc, 0, word); + } +} + +static int rt2500pci_init_queues(struct rt2x00_dev *rt2x00dev) +{ + struct queue_entry_priv_mmio *entry_priv; + u32 reg; + + /* + * Initialize registers. + */ + rt2x00mmio_register_read(rt2x00dev, TXCSR2, ®); + rt2x00_set_field32(®, TXCSR2_TXD_SIZE, rt2x00dev->tx[0].desc_size); + rt2x00_set_field32(®, TXCSR2_NUM_TXD, rt2x00dev->tx[1].limit); + rt2x00_set_field32(®, TXCSR2_NUM_ATIM, rt2x00dev->atim->limit); + rt2x00_set_field32(®, TXCSR2_NUM_PRIO, rt2x00dev->tx[0].limit); + rt2x00mmio_register_write(rt2x00dev, TXCSR2, reg); + + entry_priv = rt2x00dev->tx[1].entries[0].priv_data; + rt2x00mmio_register_read(rt2x00dev, TXCSR3, ®); + rt2x00_set_field32(®, TXCSR3_TX_RING_REGISTER, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, TXCSR3, reg); + + entry_priv = rt2x00dev->tx[0].entries[0].priv_data; + rt2x00mmio_register_read(rt2x00dev, TXCSR5, ®); + rt2x00_set_field32(®, TXCSR5_PRIO_RING_REGISTER, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, TXCSR5, reg); + + entry_priv = rt2x00dev->atim->entries[0].priv_data; + rt2x00mmio_register_read(rt2x00dev, TXCSR4, ®); + rt2x00_set_field32(®, TXCSR4_ATIM_RING_REGISTER, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, TXCSR4, reg); + + entry_priv = rt2x00dev->bcn->entries[0].priv_data; + rt2x00mmio_register_read(rt2x00dev, TXCSR6, ®); + rt2x00_set_field32(®, TXCSR6_BEACON_RING_REGISTER, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, TXCSR6, reg); + + rt2x00mmio_register_read(rt2x00dev, RXCSR1, ®); + rt2x00_set_field32(®, RXCSR1_RXD_SIZE, rt2x00dev->rx->desc_size); + rt2x00_set_field32(®, RXCSR1_NUM_RXD, rt2x00dev->rx->limit); + rt2x00mmio_register_write(rt2x00dev, RXCSR1, reg); + + entry_priv = rt2x00dev->rx->entries[0].priv_data; + rt2x00mmio_register_read(rt2x00dev, RXCSR2, ®); + rt2x00_set_field32(®, RXCSR2_RX_RING_REGISTER, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, RXCSR2, reg); + + return 0; +} + +static int rt2500pci_init_registers(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + rt2x00mmio_register_write(rt2x00dev, PSCSR0, 0x00020002); + rt2x00mmio_register_write(rt2x00dev, PSCSR1, 0x00000002); + rt2x00mmio_register_write(rt2x00dev, PSCSR2, 0x00020002); + rt2x00mmio_register_write(rt2x00dev, PSCSR3, 0x00000002); + + rt2x00mmio_register_read(rt2x00dev, TIMECSR, ®); + rt2x00_set_field32(®, TIMECSR_US_COUNT, 33); + rt2x00_set_field32(®, TIMECSR_US_64_COUNT, 63); + rt2x00_set_field32(®, TIMECSR_BEACON_EXPECT, 0); + rt2x00mmio_register_write(rt2x00dev, TIMECSR, reg); + + rt2x00mmio_register_read(rt2x00dev, CSR9, ®); + rt2x00_set_field32(®, CSR9_MAX_FRAME_UNIT, + rt2x00dev->rx->data_size / 128); + rt2x00mmio_register_write(rt2x00dev, CSR9, reg); + + /* + * Always use CWmin and CWmax set in descriptor. + */ + rt2x00mmio_register_read(rt2x00dev, CSR11, ®); + rt2x00_set_field32(®, CSR11_CW_SELECT, 0); + rt2x00mmio_register_write(rt2x00dev, CSR11, reg); + + rt2x00mmio_register_read(rt2x00dev, CSR14, ®); + rt2x00_set_field32(®, CSR14_TSF_COUNT, 0); + rt2x00_set_field32(®, CSR14_TSF_SYNC, 0); + rt2x00_set_field32(®, CSR14_TBCN, 0); + rt2x00_set_field32(®, CSR14_TCFP, 0); + rt2x00_set_field32(®, CSR14_TATIMW, 0); + rt2x00_set_field32(®, CSR14_BEACON_GEN, 0); + rt2x00_set_field32(®, CSR14_CFP_COUNT_PRELOAD, 0); + rt2x00_set_field32(®, CSR14_TBCM_PRELOAD, 0); + rt2x00mmio_register_write(rt2x00dev, CSR14, reg); + + rt2x00mmio_register_write(rt2x00dev, CNT3, 0); + + rt2x00mmio_register_read(rt2x00dev, TXCSR8, ®); + rt2x00_set_field32(®, TXCSR8_BBP_ID0, 10); + rt2x00_set_field32(®, TXCSR8_BBP_ID0_VALID, 1); + rt2x00_set_field32(®, TXCSR8_BBP_ID1, 11); + rt2x00_set_field32(®, TXCSR8_BBP_ID1_VALID, 1); + rt2x00_set_field32(®, TXCSR8_BBP_ID2, 13); + rt2x00_set_field32(®, TXCSR8_BBP_ID2_VALID, 1); + rt2x00_set_field32(®, TXCSR8_BBP_ID3, 12); + rt2x00_set_field32(®, TXCSR8_BBP_ID3_VALID, 1); + rt2x00mmio_register_write(rt2x00dev, TXCSR8, reg); + + rt2x00mmio_register_read(rt2x00dev, ARTCSR0, ®); + rt2x00_set_field32(®, ARTCSR0_ACK_CTS_1MBS, 112); + rt2x00_set_field32(®, ARTCSR0_ACK_CTS_2MBS, 56); + rt2x00_set_field32(®, ARTCSR0_ACK_CTS_5_5MBS, 20); + rt2x00_set_field32(®, ARTCSR0_ACK_CTS_11MBS, 10); + rt2x00mmio_register_write(rt2x00dev, ARTCSR0, reg); + + rt2x00mmio_register_read(rt2x00dev, ARTCSR1, ®); + rt2x00_set_field32(®, ARTCSR1_ACK_CTS_6MBS, 45); + rt2x00_set_field32(®, ARTCSR1_ACK_CTS_9MBS, 37); + rt2x00_set_field32(®, ARTCSR1_ACK_CTS_12MBS, 33); + rt2x00_set_field32(®, ARTCSR1_ACK_CTS_18MBS, 29); + rt2x00mmio_register_write(rt2x00dev, ARTCSR1, reg); + + rt2x00mmio_register_read(rt2x00dev, ARTCSR2, ®); + rt2x00_set_field32(®, ARTCSR2_ACK_CTS_24MBS, 29); + rt2x00_set_field32(®, ARTCSR2_ACK_CTS_36MBS, 25); + rt2x00_set_field32(®, ARTCSR2_ACK_CTS_48MBS, 25); + rt2x00_set_field32(®, ARTCSR2_ACK_CTS_54MBS, 25); + rt2x00mmio_register_write(rt2x00dev, ARTCSR2, reg); + + rt2x00mmio_register_read(rt2x00dev, RXCSR3, ®); + rt2x00_set_field32(®, RXCSR3_BBP_ID0, 47); /* CCK Signal */ + rt2x00_set_field32(®, RXCSR3_BBP_ID0_VALID, 1); + rt2x00_set_field32(®, RXCSR3_BBP_ID1, 51); /* Rssi */ + rt2x00_set_field32(®, RXCSR3_BBP_ID1_VALID, 1); + rt2x00_set_field32(®, RXCSR3_BBP_ID2, 42); /* OFDM Rate */ + rt2x00_set_field32(®, RXCSR3_BBP_ID2_VALID, 1); + rt2x00_set_field32(®, RXCSR3_BBP_ID3, 51); /* RSSI */ + rt2x00_set_field32(®, RXCSR3_BBP_ID3_VALID, 1); + rt2x00mmio_register_write(rt2x00dev, RXCSR3, reg); + + rt2x00mmio_register_read(rt2x00dev, PCICSR, ®); + rt2x00_set_field32(®, PCICSR_BIG_ENDIAN, 0); + rt2x00_set_field32(®, PCICSR_RX_TRESHOLD, 0); + rt2x00_set_field32(®, PCICSR_TX_TRESHOLD, 3); + rt2x00_set_field32(®, PCICSR_BURST_LENTH, 1); + rt2x00_set_field32(®, PCICSR_ENABLE_CLK, 1); + rt2x00_set_field32(®, PCICSR_READ_MULTIPLE, 1); + rt2x00_set_field32(®, PCICSR_WRITE_INVALID, 1); + rt2x00mmio_register_write(rt2x00dev, PCICSR, reg); + + rt2x00mmio_register_write(rt2x00dev, PWRCSR0, 0x3f3b3100); + + rt2x00mmio_register_write(rt2x00dev, GPIOCSR, 0x0000ff00); + rt2x00mmio_register_write(rt2x00dev, TESTCSR, 0x000000f0); + + if (rt2x00dev->ops->lib->set_device_state(rt2x00dev, STATE_AWAKE)) + return -EBUSY; + + rt2x00mmio_register_write(rt2x00dev, MACCSR0, 0x00213223); + rt2x00mmio_register_write(rt2x00dev, MACCSR1, 0x00235518); + + rt2x00mmio_register_read(rt2x00dev, MACCSR2, ®); + rt2x00_set_field32(®, MACCSR2_DELAY, 64); + rt2x00mmio_register_write(rt2x00dev, MACCSR2, reg); + + rt2x00mmio_register_read(rt2x00dev, RALINKCSR, ®); + rt2x00_set_field32(®, RALINKCSR_AR_BBP_DATA0, 17); + rt2x00_set_field32(®, RALINKCSR_AR_BBP_ID0, 26); + rt2x00_set_field32(®, RALINKCSR_AR_BBP_VALID0, 1); + rt2x00_set_field32(®, RALINKCSR_AR_BBP_DATA1, 0); + rt2x00_set_field32(®, RALINKCSR_AR_BBP_ID1, 26); + rt2x00_set_field32(®, RALINKCSR_AR_BBP_VALID1, 1); + rt2x00mmio_register_write(rt2x00dev, RALINKCSR, reg); + + rt2x00mmio_register_write(rt2x00dev, BBPCSR1, 0x82188200); + + rt2x00mmio_register_write(rt2x00dev, TXACKCSR0, 0x00000020); + + rt2x00mmio_register_read(rt2x00dev, CSR1, ®); + rt2x00_set_field32(®, CSR1_SOFT_RESET, 1); + rt2x00_set_field32(®, CSR1_BBP_RESET, 0); + rt2x00_set_field32(®, CSR1_HOST_READY, 0); + rt2x00mmio_register_write(rt2x00dev, CSR1, reg); + + rt2x00mmio_register_read(rt2x00dev, CSR1, ®); + rt2x00_set_field32(®, CSR1_SOFT_RESET, 0); + rt2x00_set_field32(®, CSR1_HOST_READY, 1); + rt2x00mmio_register_write(rt2x00dev, CSR1, reg); + + /* + * We must clear the FCS and FIFO error count. + * These registers are cleared on read, + * so we may pass a useless variable to store the value. + */ + rt2x00mmio_register_read(rt2x00dev, CNT0, ®); + rt2x00mmio_register_read(rt2x00dev, CNT4, ®); + + return 0; +} + +static int rt2500pci_wait_bbp_ready(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i; + u8 value; + + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2500pci_bbp_read(rt2x00dev, 0, &value); + if ((value != 0xff) && (value != 0x00)) + return 0; + udelay(REGISTER_BUSY_DELAY); + } + + rt2x00_err(rt2x00dev, "BBP register access failed, aborting\n"); + return -EACCES; +} + +static int rt2500pci_init_bbp(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i; + u16 eeprom; + u8 reg_id; + u8 value; + + if (unlikely(rt2500pci_wait_bbp_ready(rt2x00dev))) + return -EACCES; + + rt2500pci_bbp_write(rt2x00dev, 3, 0x02); + rt2500pci_bbp_write(rt2x00dev, 4, 0x19); + rt2500pci_bbp_write(rt2x00dev, 14, 0x1c); + rt2500pci_bbp_write(rt2x00dev, 15, 0x30); + rt2500pci_bbp_write(rt2x00dev, 16, 0xac); + rt2500pci_bbp_write(rt2x00dev, 18, 0x18); + rt2500pci_bbp_write(rt2x00dev, 19, 0xff); + rt2500pci_bbp_write(rt2x00dev, 20, 0x1e); + rt2500pci_bbp_write(rt2x00dev, 21, 0x08); + rt2500pci_bbp_write(rt2x00dev, 22, 0x08); + rt2500pci_bbp_write(rt2x00dev, 23, 0x08); + rt2500pci_bbp_write(rt2x00dev, 24, 0x70); + rt2500pci_bbp_write(rt2x00dev, 25, 0x40); + rt2500pci_bbp_write(rt2x00dev, 26, 0x08); + rt2500pci_bbp_write(rt2x00dev, 27, 0x23); + rt2500pci_bbp_write(rt2x00dev, 30, 0x10); + rt2500pci_bbp_write(rt2x00dev, 31, 0x2b); + rt2500pci_bbp_write(rt2x00dev, 32, 0xb9); + rt2500pci_bbp_write(rt2x00dev, 34, 0x12); + rt2500pci_bbp_write(rt2x00dev, 35, 0x50); + rt2500pci_bbp_write(rt2x00dev, 39, 0xc4); + rt2500pci_bbp_write(rt2x00dev, 40, 0x02); + rt2500pci_bbp_write(rt2x00dev, 41, 0x60); + rt2500pci_bbp_write(rt2x00dev, 53, 0x10); + rt2500pci_bbp_write(rt2x00dev, 54, 0x18); + rt2500pci_bbp_write(rt2x00dev, 56, 0x08); + rt2500pci_bbp_write(rt2x00dev, 57, 0x10); + rt2500pci_bbp_write(rt2x00dev, 58, 0x08); + rt2500pci_bbp_write(rt2x00dev, 61, 0x6d); + rt2500pci_bbp_write(rt2x00dev, 62, 0x10); + + for (i = 0; i < EEPROM_BBP_SIZE; i++) { + rt2x00_eeprom_read(rt2x00dev, EEPROM_BBP_START + i, &eeprom); + + if (eeprom != 0xffff && eeprom != 0x0000) { + reg_id = rt2x00_get_field16(eeprom, EEPROM_BBP_REG_ID); + value = rt2x00_get_field16(eeprom, EEPROM_BBP_VALUE); + rt2500pci_bbp_write(rt2x00dev, reg_id, value); + } + } + + return 0; +} + +/* + * Device state switch handlers. + */ +static void rt2500pci_toggle_irq(struct rt2x00_dev *rt2x00dev, + enum dev_state state) +{ + int mask = (state == STATE_RADIO_IRQ_OFF); + u32 reg; + unsigned long flags; + + /* + * When interrupts are being enabled, the interrupt registers + * should clear the register to assure a clean state. + */ + if (state == STATE_RADIO_IRQ_ON) { + rt2x00mmio_register_read(rt2x00dev, CSR7, ®); + rt2x00mmio_register_write(rt2x00dev, CSR7, reg); + } + + /* + * Only toggle the interrupts bits we are going to use. + * Non-checked interrupt bits are disabled by default. + */ + spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags); + + rt2x00mmio_register_read(rt2x00dev, CSR8, ®); + rt2x00_set_field32(®, CSR8_TBCN_EXPIRE, mask); + rt2x00_set_field32(®, CSR8_TXDONE_TXRING, mask); + rt2x00_set_field32(®, CSR8_TXDONE_ATIMRING, mask); + rt2x00_set_field32(®, CSR8_TXDONE_PRIORING, mask); + rt2x00_set_field32(®, CSR8_RXDONE, mask); + rt2x00mmio_register_write(rt2x00dev, CSR8, reg); + + spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags); + + if (state == STATE_RADIO_IRQ_OFF) { + /* + * Ensure that all tasklets are finished. + */ + tasklet_kill(&rt2x00dev->txstatus_tasklet); + tasklet_kill(&rt2x00dev->rxdone_tasklet); + tasklet_kill(&rt2x00dev->tbtt_tasklet); + } +} + +static int rt2500pci_enable_radio(struct rt2x00_dev *rt2x00dev) +{ + /* + * Initialize all registers. + */ + if (unlikely(rt2500pci_init_queues(rt2x00dev) || + rt2500pci_init_registers(rt2x00dev) || + rt2500pci_init_bbp(rt2x00dev))) + return -EIO; + + return 0; +} + +static void rt2500pci_disable_radio(struct rt2x00_dev *rt2x00dev) +{ + /* + * Disable power + */ + rt2x00mmio_register_write(rt2x00dev, PWRCSR0, 0); +} + +static int rt2500pci_set_state(struct rt2x00_dev *rt2x00dev, + enum dev_state state) +{ + u32 reg, reg2; + unsigned int i; + char put_to_sleep; + char bbp_state; + char rf_state; + + put_to_sleep = (state != STATE_AWAKE); + + rt2x00mmio_register_read(rt2x00dev, PWRCSR1, ®); + rt2x00_set_field32(®, PWRCSR1_SET_STATE, 1); + rt2x00_set_field32(®, PWRCSR1_BBP_DESIRE_STATE, state); + rt2x00_set_field32(®, PWRCSR1_RF_DESIRE_STATE, state); + rt2x00_set_field32(®, PWRCSR1_PUT_TO_SLEEP, put_to_sleep); + rt2x00mmio_register_write(rt2x00dev, PWRCSR1, reg); + + /* + * Device is not guaranteed to be in the requested state yet. + * We must wait until the register indicates that the + * device has entered the correct state. + */ + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2x00mmio_register_read(rt2x00dev, PWRCSR1, ®2); + bbp_state = rt2x00_get_field32(reg2, PWRCSR1_BBP_CURR_STATE); + rf_state = rt2x00_get_field32(reg2, PWRCSR1_RF_CURR_STATE); + if (bbp_state == state && rf_state == state) + return 0; + rt2x00mmio_register_write(rt2x00dev, PWRCSR1, reg); + msleep(10); + } + + return -EBUSY; +} + +static int rt2500pci_set_device_state(struct rt2x00_dev *rt2x00dev, + enum dev_state state) +{ + int retval = 0; + + switch (state) { + case STATE_RADIO_ON: + retval = rt2500pci_enable_radio(rt2x00dev); + break; + case STATE_RADIO_OFF: + rt2500pci_disable_radio(rt2x00dev); + break; + case STATE_RADIO_IRQ_ON: + case STATE_RADIO_IRQ_OFF: + rt2500pci_toggle_irq(rt2x00dev, state); + break; + case STATE_DEEP_SLEEP: + case STATE_SLEEP: + case STATE_STANDBY: + case STATE_AWAKE: + retval = rt2500pci_set_state(rt2x00dev, state); + break; + default: + retval = -ENOTSUPP; + break; + } + + if (unlikely(retval)) + rt2x00_err(rt2x00dev, "Device failed to enter state %d (%d)\n", + state, retval); + + return retval; +} + +/* + * TX descriptor initialization + */ +static void rt2500pci_write_tx_desc(struct queue_entry *entry, + struct txentry_desc *txdesc) +{ + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + __le32 *txd = entry_priv->desc; + u32 word; + + /* + * Start writing the descriptor words. + */ + rt2x00_desc_read(txd, 1, &word); + rt2x00_set_field32(&word, TXD_W1_BUFFER_ADDRESS, skbdesc->skb_dma); + rt2x00_desc_write(txd, 1, word); + + rt2x00_desc_read(txd, 2, &word); + rt2x00_set_field32(&word, TXD_W2_IV_OFFSET, IEEE80211_HEADER); + rt2x00_set_field32(&word, TXD_W2_AIFS, entry->queue->aifs); + rt2x00_set_field32(&word, TXD_W2_CWMIN, entry->queue->cw_min); + rt2x00_set_field32(&word, TXD_W2_CWMAX, entry->queue->cw_max); + rt2x00_desc_write(txd, 2, word); + + rt2x00_desc_read(txd, 3, &word); + rt2x00_set_field32(&word, TXD_W3_PLCP_SIGNAL, txdesc->u.plcp.signal); + rt2x00_set_field32(&word, TXD_W3_PLCP_SERVICE, txdesc->u.plcp.service); + rt2x00_set_field32(&word, TXD_W3_PLCP_LENGTH_LOW, + txdesc->u.plcp.length_low); + rt2x00_set_field32(&word, TXD_W3_PLCP_LENGTH_HIGH, + txdesc->u.plcp.length_high); + rt2x00_desc_write(txd, 3, word); + + rt2x00_desc_read(txd, 10, &word); + rt2x00_set_field32(&word, TXD_W10_RTS, + test_bit(ENTRY_TXD_RTS_FRAME, &txdesc->flags)); + rt2x00_desc_write(txd, 10, word); + + /* + * Writing TXD word 0 must the last to prevent a race condition with + * the device, whereby the device may take hold of the TXD before we + * finished updating it. + */ + rt2x00_desc_read(txd, 0, &word); + rt2x00_set_field32(&word, TXD_W0_OWNER_NIC, 1); + rt2x00_set_field32(&word, TXD_W0_VALID, 1); + rt2x00_set_field32(&word, TXD_W0_MORE_FRAG, + test_bit(ENTRY_TXD_MORE_FRAG, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_ACK, + test_bit(ENTRY_TXD_ACK, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_TIMESTAMP, + test_bit(ENTRY_TXD_REQ_TIMESTAMP, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_OFDM, + (txdesc->rate_mode == RATE_MODE_OFDM)); + rt2x00_set_field32(&word, TXD_W0_CIPHER_OWNER, 1); + rt2x00_set_field32(&word, TXD_W0_IFS, txdesc->u.plcp.ifs); + rt2x00_set_field32(&word, TXD_W0_RETRY_MODE, + test_bit(ENTRY_TXD_RETRY_MODE, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_DATABYTE_COUNT, txdesc->length); + rt2x00_set_field32(&word, TXD_W0_CIPHER_ALG, CIPHER_NONE); + rt2x00_desc_write(txd, 0, word); + + /* + * Register descriptor details in skb frame descriptor. + */ + skbdesc->desc = txd; + skbdesc->desc_len = TXD_DESC_SIZE; +} + +/* + * TX data initialization + */ +static void rt2500pci_write_beacon(struct queue_entry *entry, + struct txentry_desc *txdesc) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + u32 reg; + + /* + * Disable beaconing while we are reloading the beacon data, + * otherwise we might be sending out invalid data. + */ + rt2x00mmio_register_read(rt2x00dev, CSR14, ®); + rt2x00_set_field32(®, CSR14_BEACON_GEN, 0); + rt2x00mmio_register_write(rt2x00dev, CSR14, reg); + + if (rt2x00queue_map_txskb(entry)) { + rt2x00_err(rt2x00dev, "Fail to map beacon, aborting\n"); + goto out; + } + + /* + * Write the TX descriptor for the beacon. + */ + rt2500pci_write_tx_desc(entry, txdesc); + + /* + * Dump beacon to userspace through debugfs. + */ + rt2x00debug_dump_frame(rt2x00dev, DUMP_FRAME_BEACON, entry->skb); +out: + /* + * Enable beaconing again. + */ + rt2x00_set_field32(®, CSR14_BEACON_GEN, 1); + rt2x00mmio_register_write(rt2x00dev, CSR14, reg); +} + +/* + * RX control handlers + */ +static void rt2500pci_fill_rxdone(struct queue_entry *entry, + struct rxdone_entry_desc *rxdesc) +{ + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + u32 word0; + u32 word2; + + rt2x00_desc_read(entry_priv->desc, 0, &word0); + rt2x00_desc_read(entry_priv->desc, 2, &word2); + + if (rt2x00_get_field32(word0, RXD_W0_CRC_ERROR)) + rxdesc->flags |= RX_FLAG_FAILED_FCS_CRC; + if (rt2x00_get_field32(word0, RXD_W0_PHYSICAL_ERROR)) + rxdesc->flags |= RX_FLAG_FAILED_PLCP_CRC; + + /* + * Obtain the status about this packet. + * When frame was received with an OFDM bitrate, + * the signal is the PLCP value. If it was received with + * a CCK bitrate the signal is the rate in 100kbit/s. + */ + rxdesc->signal = rt2x00_get_field32(word2, RXD_W2_SIGNAL); + rxdesc->rssi = rt2x00_get_field32(word2, RXD_W2_RSSI) - + entry->queue->rt2x00dev->rssi_offset; + rxdesc->size = rt2x00_get_field32(word0, RXD_W0_DATABYTE_COUNT); + + if (rt2x00_get_field32(word0, RXD_W0_OFDM)) + rxdesc->dev_flags |= RXDONE_SIGNAL_PLCP; + else + rxdesc->dev_flags |= RXDONE_SIGNAL_BITRATE; + if (rt2x00_get_field32(word0, RXD_W0_MY_BSS)) + rxdesc->dev_flags |= RXDONE_MY_BSS; +} + +/* + * Interrupt functions. + */ +static void rt2500pci_txdone(struct rt2x00_dev *rt2x00dev, + const enum data_queue_qid queue_idx) +{ + struct data_queue *queue = rt2x00queue_get_tx_queue(rt2x00dev, queue_idx); + struct queue_entry_priv_mmio *entry_priv; + struct queue_entry *entry; + struct txdone_entry_desc txdesc; + u32 word; + + while (!rt2x00queue_empty(queue)) { + entry = rt2x00queue_get_entry(queue, Q_INDEX_DONE); + entry_priv = entry->priv_data; + rt2x00_desc_read(entry_priv->desc, 0, &word); + + if (rt2x00_get_field32(word, TXD_W0_OWNER_NIC) || + !rt2x00_get_field32(word, TXD_W0_VALID)) + break; + + /* + * Obtain the status about this packet. + */ + txdesc.flags = 0; + switch (rt2x00_get_field32(word, TXD_W0_RESULT)) { + case 0: /* Success */ + case 1: /* Success with retry */ + __set_bit(TXDONE_SUCCESS, &txdesc.flags); + break; + case 2: /* Failure, excessive retries */ + __set_bit(TXDONE_EXCESSIVE_RETRY, &txdesc.flags); + /* Don't break, this is a failed frame! */ + default: /* Failure */ + __set_bit(TXDONE_FAILURE, &txdesc.flags); + } + txdesc.retry = rt2x00_get_field32(word, TXD_W0_RETRY_COUNT); + + rt2x00lib_txdone(entry, &txdesc); + } +} + +static inline void rt2500pci_enable_interrupt(struct rt2x00_dev *rt2x00dev, + struct rt2x00_field32 irq_field) +{ + u32 reg; + + /* + * Enable a single interrupt. The interrupt mask register + * access needs locking. + */ + spin_lock_irq(&rt2x00dev->irqmask_lock); + + rt2x00mmio_register_read(rt2x00dev, CSR8, ®); + rt2x00_set_field32(®, irq_field, 0); + rt2x00mmio_register_write(rt2x00dev, CSR8, reg); + + spin_unlock_irq(&rt2x00dev->irqmask_lock); +} + +static void rt2500pci_txstatus_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + u32 reg; + + /* + * Handle all tx queues. + */ + rt2500pci_txdone(rt2x00dev, QID_ATIM); + rt2500pci_txdone(rt2x00dev, QID_AC_VO); + rt2500pci_txdone(rt2x00dev, QID_AC_VI); + + /* + * Enable all TXDONE interrupts again. + */ + if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) { + spin_lock_irq(&rt2x00dev->irqmask_lock); + + rt2x00mmio_register_read(rt2x00dev, CSR8, ®); + rt2x00_set_field32(®, CSR8_TXDONE_TXRING, 0); + rt2x00_set_field32(®, CSR8_TXDONE_ATIMRING, 0); + rt2x00_set_field32(®, CSR8_TXDONE_PRIORING, 0); + rt2x00mmio_register_write(rt2x00dev, CSR8, reg); + + spin_unlock_irq(&rt2x00dev->irqmask_lock); + } +} + +static void rt2500pci_tbtt_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + rt2x00lib_beacondone(rt2x00dev); + if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + rt2500pci_enable_interrupt(rt2x00dev, CSR8_TBCN_EXPIRE); +} + +static void rt2500pci_rxdone_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + if (rt2x00mmio_rxdone(rt2x00dev)) + tasklet_schedule(&rt2x00dev->rxdone_tasklet); + else if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + rt2500pci_enable_interrupt(rt2x00dev, CSR8_RXDONE); +} + +static irqreturn_t rt2500pci_interrupt(int irq, void *dev_instance) +{ + struct rt2x00_dev *rt2x00dev = dev_instance; + u32 reg, mask; + + /* + * Get the interrupt sources & saved to local variable. + * Write register value back to clear pending interrupts. + */ + rt2x00mmio_register_read(rt2x00dev, CSR7, ®); + rt2x00mmio_register_write(rt2x00dev, CSR7, reg); + + if (!reg) + return IRQ_NONE; + + if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + return IRQ_HANDLED; + + mask = reg; + + /* + * Schedule tasklets for interrupt handling. + */ + if (rt2x00_get_field32(reg, CSR7_TBCN_EXPIRE)) + tasklet_hi_schedule(&rt2x00dev->tbtt_tasklet); + + if (rt2x00_get_field32(reg, CSR7_RXDONE)) + tasklet_schedule(&rt2x00dev->rxdone_tasklet); + + if (rt2x00_get_field32(reg, CSR7_TXDONE_ATIMRING) || + rt2x00_get_field32(reg, CSR7_TXDONE_PRIORING) || + rt2x00_get_field32(reg, CSR7_TXDONE_TXRING)) { + tasklet_schedule(&rt2x00dev->txstatus_tasklet); + /* + * Mask out all txdone interrupts. + */ + rt2x00_set_field32(&mask, CSR8_TXDONE_TXRING, 1); + rt2x00_set_field32(&mask, CSR8_TXDONE_ATIMRING, 1); + rt2x00_set_field32(&mask, CSR8_TXDONE_PRIORING, 1); + } + + /* + * Disable all interrupts for which a tasklet was scheduled right now, + * the tasklet will reenable the appropriate interrupts. + */ + spin_lock(&rt2x00dev->irqmask_lock); + + rt2x00mmio_register_read(rt2x00dev, CSR8, ®); + reg |= mask; + rt2x00mmio_register_write(rt2x00dev, CSR8, reg); + + spin_unlock(&rt2x00dev->irqmask_lock); + + return IRQ_HANDLED; +} + +/* + * Device probe functions. + */ +static int rt2500pci_validate_eeprom(struct rt2x00_dev *rt2x00dev) +{ + struct eeprom_93cx6 eeprom; + u32 reg; + u16 word; + u8 *mac; + + rt2x00mmio_register_read(rt2x00dev, CSR21, ®); + + eeprom.data = rt2x00dev; + eeprom.register_read = rt2500pci_eepromregister_read; + eeprom.register_write = rt2500pci_eepromregister_write; + eeprom.width = rt2x00_get_field32(reg, CSR21_TYPE_93C46) ? + PCI_EEPROM_WIDTH_93C46 : PCI_EEPROM_WIDTH_93C66; + eeprom.reg_data_in = 0; + eeprom.reg_data_out = 0; + eeprom.reg_data_clock = 0; + eeprom.reg_chip_select = 0; + + eeprom_93cx6_multiread(&eeprom, EEPROM_BASE, rt2x00dev->eeprom, + EEPROM_SIZE / sizeof(u16)); + + /* + * Start validation of the data that has been read. + */ + mac = rt2x00_eeprom_addr(rt2x00dev, EEPROM_MAC_ADDR_0); + if (!is_valid_ether_addr(mac)) { + eth_random_addr(mac); + rt2x00_eeprom_dbg(rt2x00dev, "MAC: %pM\n", mac); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_ANTENNA, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_ANTENNA_NUM, 2); + rt2x00_set_field16(&word, EEPROM_ANTENNA_TX_DEFAULT, + ANTENNA_SW_DIVERSITY); + rt2x00_set_field16(&word, EEPROM_ANTENNA_RX_DEFAULT, + ANTENNA_SW_DIVERSITY); + rt2x00_set_field16(&word, EEPROM_ANTENNA_LED_MODE, + LED_MODE_DEFAULT); + rt2x00_set_field16(&word, EEPROM_ANTENNA_DYN_TXAGC, 0); + rt2x00_set_field16(&word, EEPROM_ANTENNA_HARDWARE_RADIO, 0); + rt2x00_set_field16(&word, EEPROM_ANTENNA_RF_TYPE, RF2522); + rt2x00_eeprom_write(rt2x00dev, EEPROM_ANTENNA, word); + rt2x00_eeprom_dbg(rt2x00dev, "Antenna: 0x%04x\n", word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_NIC_CARDBUS_ACCEL, 0); + rt2x00_set_field16(&word, EEPROM_NIC_DYN_BBP_TUNE, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CCK_TX_POWER, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_NIC, word); + rt2x00_eeprom_dbg(rt2x00dev, "NIC: 0x%04x\n", word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_CALIBRATE_OFFSET, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_CALIBRATE_OFFSET_RSSI, + DEFAULT_RSSI_OFFSET); + rt2x00_eeprom_write(rt2x00dev, EEPROM_CALIBRATE_OFFSET, word); + rt2x00_eeprom_dbg(rt2x00dev, "Calibrate offset: 0x%04x\n", + word); + } + + return 0; +} + +static int rt2500pci_init_eeprom(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + u16 value; + u16 eeprom; + + /* + * Read EEPROM word for configuration. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_ANTENNA, &eeprom); + + /* + * Identify RF chipset. + */ + value = rt2x00_get_field16(eeprom, EEPROM_ANTENNA_RF_TYPE); + rt2x00mmio_register_read(rt2x00dev, CSR0, ®); + rt2x00_set_chip(rt2x00dev, RT2560, value, + rt2x00_get_field32(reg, CSR0_REVISION)); + + if (!rt2x00_rf(rt2x00dev, RF2522) && + !rt2x00_rf(rt2x00dev, RF2523) && + !rt2x00_rf(rt2x00dev, RF2524) && + !rt2x00_rf(rt2x00dev, RF2525) && + !rt2x00_rf(rt2x00dev, RF2525E) && + !rt2x00_rf(rt2x00dev, RF5222)) { + rt2x00_err(rt2x00dev, "Invalid RF chipset detected\n"); + return -ENODEV; + } + + /* + * Identify default antenna configuration. + */ + rt2x00dev->default_ant.tx = + rt2x00_get_field16(eeprom, EEPROM_ANTENNA_TX_DEFAULT); + rt2x00dev->default_ant.rx = + rt2x00_get_field16(eeprom, EEPROM_ANTENNA_RX_DEFAULT); + + /* + * Store led mode, for correct led behaviour. + */ +#ifdef CPTCFG_RT2X00_LIB_LEDS + value = rt2x00_get_field16(eeprom, EEPROM_ANTENNA_LED_MODE); + + rt2500pci_init_led(rt2x00dev, &rt2x00dev->led_radio, LED_TYPE_RADIO); + if (value == LED_MODE_TXRX_ACTIVITY || + value == LED_MODE_DEFAULT || + value == LED_MODE_ASUS) + rt2500pci_init_led(rt2x00dev, &rt2x00dev->led_qual, + LED_TYPE_ACTIVITY); +#endif /* CPTCFG_RT2X00_LIB_LEDS */ + + /* + * Detect if this device has an hardware controlled radio. + */ + if (rt2x00_get_field16(eeprom, EEPROM_ANTENNA_HARDWARE_RADIO)) + __set_bit(CAPABILITY_HW_BUTTON, &rt2x00dev->cap_flags); + + /* + * Check if the BBP tuning should be enabled. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC, &eeprom); + if (!rt2x00_get_field16(eeprom, EEPROM_NIC_DYN_BBP_TUNE)) + __set_bit(CAPABILITY_LINK_TUNING, &rt2x00dev->cap_flags); + + /* + * Read the RSSI <-> dBm offset information. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_CALIBRATE_OFFSET, &eeprom); + rt2x00dev->rssi_offset = + rt2x00_get_field16(eeprom, EEPROM_CALIBRATE_OFFSET_RSSI); + + return 0; +} + +/* + * RF value list for RF2522 + * Supports: 2.4 GHz + */ +static const struct rf_channel rf_vals_bg_2522[] = { + { 1, 0x00002050, 0x000c1fda, 0x00000101, 0 }, + { 2, 0x00002050, 0x000c1fee, 0x00000101, 0 }, + { 3, 0x00002050, 0x000c2002, 0x00000101, 0 }, + { 4, 0x00002050, 0x000c2016, 0x00000101, 0 }, + { 5, 0x00002050, 0x000c202a, 0x00000101, 0 }, + { 6, 0x00002050, 0x000c203e, 0x00000101, 0 }, + { 7, 0x00002050, 0x000c2052, 0x00000101, 0 }, + { 8, 0x00002050, 0x000c2066, 0x00000101, 0 }, + { 9, 0x00002050, 0x000c207a, 0x00000101, 0 }, + { 10, 0x00002050, 0x000c208e, 0x00000101, 0 }, + { 11, 0x00002050, 0x000c20a2, 0x00000101, 0 }, + { 12, 0x00002050, 0x000c20b6, 0x00000101, 0 }, + { 13, 0x00002050, 0x000c20ca, 0x00000101, 0 }, + { 14, 0x00002050, 0x000c20fa, 0x00000101, 0 }, +}; + +/* + * RF value list for RF2523 + * Supports: 2.4 GHz + */ +static const struct rf_channel rf_vals_bg_2523[] = { + { 1, 0x00022010, 0x00000c9e, 0x000e0111, 0x00000a1b }, + { 2, 0x00022010, 0x00000ca2, 0x000e0111, 0x00000a1b }, + { 3, 0x00022010, 0x00000ca6, 0x000e0111, 0x00000a1b }, + { 4, 0x00022010, 0x00000caa, 0x000e0111, 0x00000a1b }, + { 5, 0x00022010, 0x00000cae, 0x000e0111, 0x00000a1b }, + { 6, 0x00022010, 0x00000cb2, 0x000e0111, 0x00000a1b }, + { 7, 0x00022010, 0x00000cb6, 0x000e0111, 0x00000a1b }, + { 8, 0x00022010, 0x00000cba, 0x000e0111, 0x00000a1b }, + { 9, 0x00022010, 0x00000cbe, 0x000e0111, 0x00000a1b }, + { 10, 0x00022010, 0x00000d02, 0x000e0111, 0x00000a1b }, + { 11, 0x00022010, 0x00000d06, 0x000e0111, 0x00000a1b }, + { 12, 0x00022010, 0x00000d0a, 0x000e0111, 0x00000a1b }, + { 13, 0x00022010, 0x00000d0e, 0x000e0111, 0x00000a1b }, + { 14, 0x00022010, 0x00000d1a, 0x000e0111, 0x00000a03 }, +}; + +/* + * RF value list for RF2524 + * Supports: 2.4 GHz + */ +static const struct rf_channel rf_vals_bg_2524[] = { + { 1, 0x00032020, 0x00000c9e, 0x00000101, 0x00000a1b }, + { 2, 0x00032020, 0x00000ca2, 0x00000101, 0x00000a1b }, + { 3, 0x00032020, 0x00000ca6, 0x00000101, 0x00000a1b }, + { 4, 0x00032020, 0x00000caa, 0x00000101, 0x00000a1b }, + { 5, 0x00032020, 0x00000cae, 0x00000101, 0x00000a1b }, + { 6, 0x00032020, 0x00000cb2, 0x00000101, 0x00000a1b }, + { 7, 0x00032020, 0x00000cb6, 0x00000101, 0x00000a1b }, + { 8, 0x00032020, 0x00000cba, 0x00000101, 0x00000a1b }, + { 9, 0x00032020, 0x00000cbe, 0x00000101, 0x00000a1b }, + { 10, 0x00032020, 0x00000d02, 0x00000101, 0x00000a1b }, + { 11, 0x00032020, 0x00000d06, 0x00000101, 0x00000a1b }, + { 12, 0x00032020, 0x00000d0a, 0x00000101, 0x00000a1b }, + { 13, 0x00032020, 0x00000d0e, 0x00000101, 0x00000a1b }, + { 14, 0x00032020, 0x00000d1a, 0x00000101, 0x00000a03 }, +}; + +/* + * RF value list for RF2525 + * Supports: 2.4 GHz + */ +static const struct rf_channel rf_vals_bg_2525[] = { + { 1, 0x00022020, 0x00080c9e, 0x00060111, 0x00000a1b }, + { 2, 0x00022020, 0x00080ca2, 0x00060111, 0x00000a1b }, + { 3, 0x00022020, 0x00080ca6, 0x00060111, 0x00000a1b }, + { 4, 0x00022020, 0x00080caa, 0x00060111, 0x00000a1b }, + { 5, 0x00022020, 0x00080cae, 0x00060111, 0x00000a1b }, + { 6, 0x00022020, 0x00080cb2, 0x00060111, 0x00000a1b }, + { 7, 0x00022020, 0x00080cb6, 0x00060111, 0x00000a1b }, + { 8, 0x00022020, 0x00080cba, 0x00060111, 0x00000a1b }, + { 9, 0x00022020, 0x00080cbe, 0x00060111, 0x00000a1b }, + { 10, 0x00022020, 0x00080d02, 0x00060111, 0x00000a1b }, + { 11, 0x00022020, 0x00080d06, 0x00060111, 0x00000a1b }, + { 12, 0x00022020, 0x00080d0a, 0x00060111, 0x00000a1b }, + { 13, 0x00022020, 0x00080d0e, 0x00060111, 0x00000a1b }, + { 14, 0x00022020, 0x00080d1a, 0x00060111, 0x00000a03 }, +}; + +/* + * RF value list for RF2525e + * Supports: 2.4 GHz + */ +static const struct rf_channel rf_vals_bg_2525e[] = { + { 1, 0x00022020, 0x00081136, 0x00060111, 0x00000a0b }, + { 2, 0x00022020, 0x0008113a, 0x00060111, 0x00000a0b }, + { 3, 0x00022020, 0x0008113e, 0x00060111, 0x00000a0b }, + { 4, 0x00022020, 0x00081182, 0x00060111, 0x00000a0b }, + { 5, 0x00022020, 0x00081186, 0x00060111, 0x00000a0b }, + { 6, 0x00022020, 0x0008118a, 0x00060111, 0x00000a0b }, + { 7, 0x00022020, 0x0008118e, 0x00060111, 0x00000a0b }, + { 8, 0x00022020, 0x00081192, 0x00060111, 0x00000a0b }, + { 9, 0x00022020, 0x00081196, 0x00060111, 0x00000a0b }, + { 10, 0x00022020, 0x0008119a, 0x00060111, 0x00000a0b }, + { 11, 0x00022020, 0x0008119e, 0x00060111, 0x00000a0b }, + { 12, 0x00022020, 0x000811a2, 0x00060111, 0x00000a0b }, + { 13, 0x00022020, 0x000811a6, 0x00060111, 0x00000a0b }, + { 14, 0x00022020, 0x000811ae, 0x00060111, 0x00000a1b }, +}; + +/* + * RF value list for RF5222 + * Supports: 2.4 GHz & 5.2 GHz + */ +static const struct rf_channel rf_vals_5222[] = { + { 1, 0x00022020, 0x00001136, 0x00000101, 0x00000a0b }, + { 2, 0x00022020, 0x0000113a, 0x00000101, 0x00000a0b }, + { 3, 0x00022020, 0x0000113e, 0x00000101, 0x00000a0b }, + { 4, 0x00022020, 0x00001182, 0x00000101, 0x00000a0b }, + { 5, 0x00022020, 0x00001186, 0x00000101, 0x00000a0b }, + { 6, 0x00022020, 0x0000118a, 0x00000101, 0x00000a0b }, + { 7, 0x00022020, 0x0000118e, 0x00000101, 0x00000a0b }, + { 8, 0x00022020, 0x00001192, 0x00000101, 0x00000a0b }, + { 9, 0x00022020, 0x00001196, 0x00000101, 0x00000a0b }, + { 10, 0x00022020, 0x0000119a, 0x00000101, 0x00000a0b }, + { 11, 0x00022020, 0x0000119e, 0x00000101, 0x00000a0b }, + { 12, 0x00022020, 0x000011a2, 0x00000101, 0x00000a0b }, + { 13, 0x00022020, 0x000011a6, 0x00000101, 0x00000a0b }, + { 14, 0x00022020, 0x000011ae, 0x00000101, 0x00000a1b }, + + /* 802.11 UNI / HyperLan 2 */ + { 36, 0x00022010, 0x00018896, 0x00000101, 0x00000a1f }, + { 40, 0x00022010, 0x0001889a, 0x00000101, 0x00000a1f }, + { 44, 0x00022010, 0x0001889e, 0x00000101, 0x00000a1f }, + { 48, 0x00022010, 0x000188a2, 0x00000101, 0x00000a1f }, + { 52, 0x00022010, 0x000188a6, 0x00000101, 0x00000a1f }, + { 66, 0x00022010, 0x000188aa, 0x00000101, 0x00000a1f }, + { 60, 0x00022010, 0x000188ae, 0x00000101, 0x00000a1f }, + { 64, 0x00022010, 0x000188b2, 0x00000101, 0x00000a1f }, + + /* 802.11 HyperLan 2 */ + { 100, 0x00022010, 0x00008802, 0x00000101, 0x00000a0f }, + { 104, 0x00022010, 0x00008806, 0x00000101, 0x00000a0f }, + { 108, 0x00022010, 0x0000880a, 0x00000101, 0x00000a0f }, + { 112, 0x00022010, 0x0000880e, 0x00000101, 0x00000a0f }, + { 116, 0x00022010, 0x00008812, 0x00000101, 0x00000a0f }, + { 120, 0x00022010, 0x00008816, 0x00000101, 0x00000a0f }, + { 124, 0x00022010, 0x0000881a, 0x00000101, 0x00000a0f }, + { 128, 0x00022010, 0x0000881e, 0x00000101, 0x00000a0f }, + { 132, 0x00022010, 0x00008822, 0x00000101, 0x00000a0f }, + { 136, 0x00022010, 0x00008826, 0x00000101, 0x00000a0f }, + + /* 802.11 UNII */ + { 140, 0x00022010, 0x0000882a, 0x00000101, 0x00000a0f }, + { 149, 0x00022020, 0x000090a6, 0x00000101, 0x00000a07 }, + { 153, 0x00022020, 0x000090ae, 0x00000101, 0x00000a07 }, + { 157, 0x00022020, 0x000090b6, 0x00000101, 0x00000a07 }, + { 161, 0x00022020, 0x000090be, 0x00000101, 0x00000a07 }, +}; + +static int rt2500pci_probe_hw_mode(struct rt2x00_dev *rt2x00dev) +{ + struct hw_mode_spec *spec = &rt2x00dev->spec; + struct channel_info *info; + char *tx_power; + unsigned int i; + + /* + * Initialize all hw fields. + */ + rt2x00dev->hw->flags = IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING | + IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_PS_NULLFUNC_STACK; + + SET_IEEE80211_DEV(rt2x00dev->hw, rt2x00dev->dev); + SET_IEEE80211_PERM_ADDR(rt2x00dev->hw, + rt2x00_eeprom_addr(rt2x00dev, + EEPROM_MAC_ADDR_0)); + + /* + * Initialize hw_mode information. + */ + spec->supported_bands = SUPPORT_BAND_2GHZ; + spec->supported_rates = SUPPORT_RATE_CCK | SUPPORT_RATE_OFDM; + + if (rt2x00_rf(rt2x00dev, RF2522)) { + spec->num_channels = ARRAY_SIZE(rf_vals_bg_2522); + spec->channels = rf_vals_bg_2522; + } else if (rt2x00_rf(rt2x00dev, RF2523)) { + spec->num_channels = ARRAY_SIZE(rf_vals_bg_2523); + spec->channels = rf_vals_bg_2523; + } else if (rt2x00_rf(rt2x00dev, RF2524)) { + spec->num_channels = ARRAY_SIZE(rf_vals_bg_2524); + spec->channels = rf_vals_bg_2524; + } else if (rt2x00_rf(rt2x00dev, RF2525)) { + spec->num_channels = ARRAY_SIZE(rf_vals_bg_2525); + spec->channels = rf_vals_bg_2525; + } else if (rt2x00_rf(rt2x00dev, RF2525E)) { + spec->num_channels = ARRAY_SIZE(rf_vals_bg_2525e); + spec->channels = rf_vals_bg_2525e; + } else if (rt2x00_rf(rt2x00dev, RF5222)) { + spec->supported_bands |= SUPPORT_BAND_5GHZ; + spec->num_channels = ARRAY_SIZE(rf_vals_5222); + spec->channels = rf_vals_5222; + } + + /* + * Create channel information array + */ + info = kcalloc(spec->num_channels, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + spec->channels_info = info; + + tx_power = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_START); + for (i = 0; i < 14; i++) { + info[i].max_power = MAX_TXPOWER; + info[i].default_power1 = TXPOWER_FROM_DEV(tx_power[i]); + } + + if (spec->num_channels > 14) { + for (i = 14; i < spec->num_channels; i++) { + info[i].max_power = MAX_TXPOWER; + info[i].default_power1 = DEFAULT_TXPOWER; + } + } + + return 0; +} + +static int rt2500pci_probe_hw(struct rt2x00_dev *rt2x00dev) +{ + int retval; + u32 reg; + + /* + * Allocate eeprom data. + */ + retval = rt2500pci_validate_eeprom(rt2x00dev); + if (retval) + return retval; + + retval = rt2500pci_init_eeprom(rt2x00dev); + if (retval) + return retval; + + /* + * Enable rfkill polling by setting GPIO direction of the + * rfkill switch GPIO pin correctly. + */ + rt2x00mmio_register_read(rt2x00dev, GPIOCSR, ®); + rt2x00_set_field32(®, GPIOCSR_DIR0, 1); + rt2x00mmio_register_write(rt2x00dev, GPIOCSR, reg); + + /* + * Initialize hw specifications. + */ + retval = rt2500pci_probe_hw_mode(rt2x00dev); + if (retval) + return retval; + + /* + * This device requires the atim queue and DMA-mapped skbs. + */ + __set_bit(REQUIRE_ATIM_QUEUE, &rt2x00dev->cap_flags); + __set_bit(REQUIRE_DMA, &rt2x00dev->cap_flags); + __set_bit(REQUIRE_SW_SEQNO, &rt2x00dev->cap_flags); + + /* + * Set the rssi offset. + */ + rt2x00dev->rssi_offset = DEFAULT_RSSI_OFFSET; + + return 0; +} + +/* + * IEEE80211 stack callback functions. + */ +static u64 rt2500pci_get_tsf(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + u64 tsf; + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, CSR17, ®); + tsf = (u64) rt2x00_get_field32(reg, CSR17_HIGH_TSFTIMER) << 32; + rt2x00mmio_register_read(rt2x00dev, CSR16, ®); + tsf |= rt2x00_get_field32(reg, CSR16_LOW_TSFTIMER); + + return tsf; +} + +static int rt2500pci_tx_last_beacon(struct ieee80211_hw *hw) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, CSR15, ®); + return rt2x00_get_field32(reg, CSR15_BEACON_SENT); +} + +static const struct ieee80211_ops rt2500pci_mac80211_ops = { + .tx = rt2x00mac_tx, + .start = rt2x00mac_start, + .stop = rt2x00mac_stop, + .add_interface = rt2x00mac_add_interface, + .remove_interface = rt2x00mac_remove_interface, + .config = rt2x00mac_config, + .configure_filter = rt2x00mac_configure_filter, + .sw_scan_start = rt2x00mac_sw_scan_start, + .sw_scan_complete = rt2x00mac_sw_scan_complete, + .get_stats = rt2x00mac_get_stats, + .bss_info_changed = rt2x00mac_bss_info_changed, + .conf_tx = rt2x00mac_conf_tx, + .get_tsf = rt2500pci_get_tsf, + .tx_last_beacon = rt2500pci_tx_last_beacon, + .rfkill_poll = rt2x00mac_rfkill_poll, + .flush = rt2x00mac_flush, + .set_antenna = rt2x00mac_set_antenna, + .get_antenna = rt2x00mac_get_antenna, + .get_ringparam = rt2x00mac_get_ringparam, + .tx_frames_pending = rt2x00mac_tx_frames_pending, +}; + +static const struct rt2x00lib_ops rt2500pci_rt2x00_ops = { + .irq_handler = rt2500pci_interrupt, + .txstatus_tasklet = rt2500pci_txstatus_tasklet, + .tbtt_tasklet = rt2500pci_tbtt_tasklet, + .rxdone_tasklet = rt2500pci_rxdone_tasklet, + .probe_hw = rt2500pci_probe_hw, + .initialize = rt2x00mmio_initialize, + .uninitialize = rt2x00mmio_uninitialize, + .get_entry_state = rt2500pci_get_entry_state, + .clear_entry = rt2500pci_clear_entry, + .set_device_state = rt2500pci_set_device_state, + .rfkill_poll = rt2500pci_rfkill_poll, + .link_stats = rt2500pci_link_stats, + .reset_tuner = rt2500pci_reset_tuner, + .link_tuner = rt2500pci_link_tuner, + .start_queue = rt2500pci_start_queue, + .kick_queue = rt2500pci_kick_queue, + .stop_queue = rt2500pci_stop_queue, + .flush_queue = rt2x00mmio_flush_queue, + .write_tx_desc = rt2500pci_write_tx_desc, + .write_beacon = rt2500pci_write_beacon, + .fill_rxdone = rt2500pci_fill_rxdone, + .config_filter = rt2500pci_config_filter, + .config_intf = rt2500pci_config_intf, + .config_erp = rt2500pci_config_erp, + .config_ant = rt2500pci_config_ant, + .config = rt2500pci_config, +}; + +static const struct data_queue_desc rt2500pci_queue_rx = { + .entry_num = 32, + .data_size = DATA_FRAME_SIZE, + .desc_size = RXD_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_mmio), +}; + +static const struct data_queue_desc rt2500pci_queue_tx = { + .entry_num = 32, + .data_size = DATA_FRAME_SIZE, + .desc_size = TXD_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_mmio), +}; + +static const struct data_queue_desc rt2500pci_queue_bcn = { + .entry_num = 1, + .data_size = MGMT_FRAME_SIZE, + .desc_size = TXD_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_mmio), +}; + +static const struct data_queue_desc rt2500pci_queue_atim = { + .entry_num = 8, + .data_size = DATA_FRAME_SIZE, + .desc_size = TXD_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_mmio), +}; + +static const struct rt2x00_ops rt2500pci_ops = { + .name = KBUILD_MODNAME, + .max_ap_intf = 1, + .eeprom_size = EEPROM_SIZE, + .rf_size = RF_SIZE, + .tx_queues = NUM_TX_QUEUES, + .extra_tx_headroom = 0, + .rx = &rt2500pci_queue_rx, + .tx = &rt2500pci_queue_tx, + .bcn = &rt2500pci_queue_bcn, + .atim = &rt2500pci_queue_atim, + .lib = &rt2500pci_rt2x00_ops, + .hw = &rt2500pci_mac80211_ops, +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS + .debugfs = &rt2500pci_rt2x00debug, +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ +}; + +/* + * RT2500pci module information. + */ +static DEFINE_PCI_DEVICE_TABLE(rt2500pci_device_table) = { + { PCI_DEVICE(0x1814, 0x0201) }, + { 0, } +}; + +MODULE_AUTHOR(DRV_PROJECT); +MODULE_VERSION(DRV_VERSION); +MODULE_DESCRIPTION("Ralink RT2500 PCI & PCMCIA Wireless LAN driver."); +MODULE_SUPPORTED_DEVICE("Ralink RT2560 PCI & PCMCIA chipset based cards"); +MODULE_DEVICE_TABLE(pci, rt2500pci_device_table); +MODULE_LICENSE("GPL"); + +static int rt2500pci_probe(struct pci_dev *pci_dev, + const struct pci_device_id *id) +{ + return rt2x00pci_probe(pci_dev, &rt2500pci_ops); +} + +static struct pci_driver rt2500pci_driver = { + .name = KBUILD_MODNAME, + .id_table = rt2500pci_device_table, + .probe = rt2500pci_probe, + .remove = rt2x00pci_remove, + .suspend = rt2x00pci_suspend, + .resume = rt2x00pci_resume, +}; + +module_pci_driver(rt2500pci_driver); diff --git a/drivers/net/wireless/rt2x00/rt2500pci.h b/drivers/net/wireless/rt2x00/rt2500pci.h new file mode 100644 index 0000000..9c10068 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2500pci.h @@ -0,0 +1,1237 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2500pci + Abstract: Data structures and registers for the rt2500pci module. + Supported chipsets: RT2560. + */ + +#ifndef RT2500PCI_H +#define RT2500PCI_H + +/* + * RF chip defines. + */ +#define RF2522 0x0000 +#define RF2523 0x0001 +#define RF2524 0x0002 +#define RF2525 0x0003 +#define RF2525E 0x0004 +#define RF5222 0x0010 + +/* + * RT2560 version + */ +#define RT2560_VERSION_B 2 +#define RT2560_VERSION_C 3 +#define RT2560_VERSION_D 4 + +/* + * Signal information. + * Default offset is required for RSSI <-> dBm conversion. + */ +#define DEFAULT_RSSI_OFFSET 121 + +/* + * Register layout information. + */ +#define CSR_REG_BASE 0x0000 +#define CSR_REG_SIZE 0x0174 +#define EEPROM_BASE 0x0000 +#define EEPROM_SIZE 0x0200 +#define BBP_BASE 0x0000 +#define BBP_SIZE 0x0040 +#define RF_BASE 0x0004 +#define RF_SIZE 0x0010 + +/* + * Number of TX queues. + */ +#define NUM_TX_QUEUES 2 + +/* + * Control/Status Registers(CSR). + * Some values are set in TU, whereas 1 TU == 1024 us. + */ + +/* + * CSR0: ASIC revision number. + */ +#define CSR0 0x0000 +#define CSR0_REVISION FIELD32(0x0000ffff) + +/* + * CSR1: System control register. + * SOFT_RESET: Software reset, 1: reset, 0: normal. + * BBP_RESET: Hardware reset, 1: reset, 0, release. + * HOST_READY: Host ready after initialization. + */ +#define CSR1 0x0004 +#define CSR1_SOFT_RESET FIELD32(0x00000001) +#define CSR1_BBP_RESET FIELD32(0x00000002) +#define CSR1_HOST_READY FIELD32(0x00000004) + +/* + * CSR2: System admin status register (invalid). + */ +#define CSR2 0x0008 + +/* + * CSR3: STA MAC address register 0. + */ +#define CSR3 0x000c +#define CSR3_BYTE0 FIELD32(0x000000ff) +#define CSR3_BYTE1 FIELD32(0x0000ff00) +#define CSR3_BYTE2 FIELD32(0x00ff0000) +#define CSR3_BYTE3 FIELD32(0xff000000) + +/* + * CSR4: STA MAC address register 1. + */ +#define CSR4 0x0010 +#define CSR4_BYTE4 FIELD32(0x000000ff) +#define CSR4_BYTE5 FIELD32(0x0000ff00) + +/* + * CSR5: BSSID register 0. + */ +#define CSR5 0x0014 +#define CSR5_BYTE0 FIELD32(0x000000ff) +#define CSR5_BYTE1 FIELD32(0x0000ff00) +#define CSR5_BYTE2 FIELD32(0x00ff0000) +#define CSR5_BYTE3 FIELD32(0xff000000) + +/* + * CSR6: BSSID register 1. + */ +#define CSR6 0x0018 +#define CSR6_BYTE4 FIELD32(0x000000ff) +#define CSR6_BYTE5 FIELD32(0x0000ff00) + +/* + * CSR7: Interrupt source register. + * Write 1 to clear. + * TBCN_EXPIRE: Beacon timer expired interrupt. + * TWAKE_EXPIRE: Wakeup timer expired interrupt. + * TATIMW_EXPIRE: Timer of atim window expired interrupt. + * TXDONE_TXRING: Tx ring transmit done interrupt. + * TXDONE_ATIMRING: Atim ring transmit done interrupt. + * TXDONE_PRIORING: Priority ring transmit done interrupt. + * RXDONE: Receive done interrupt. + * DECRYPTION_DONE: Decryption done interrupt. + * ENCRYPTION_DONE: Encryption done interrupt. + * UART1_TX_TRESHOLD: UART1 TX reaches threshold. + * UART1_RX_TRESHOLD: UART1 RX reaches threshold. + * UART1_IDLE_TRESHOLD: UART1 IDLE over threshold. + * UART1_TX_BUFF_ERROR: UART1 TX buffer error. + * UART1_RX_BUFF_ERROR: UART1 RX buffer error. + * UART2_TX_TRESHOLD: UART2 TX reaches threshold. + * UART2_RX_TRESHOLD: UART2 RX reaches threshold. + * UART2_IDLE_TRESHOLD: UART2 IDLE over threshold. + * UART2_TX_BUFF_ERROR: UART2 TX buffer error. + * UART2_RX_BUFF_ERROR: UART2 RX buffer error. + * TIMER_CSR3_EXPIRE: TIMECSR3 timer expired (802.1H quiet period). + + */ +#define CSR7 0x001c +#define CSR7_TBCN_EXPIRE FIELD32(0x00000001) +#define CSR7_TWAKE_EXPIRE FIELD32(0x00000002) +#define CSR7_TATIMW_EXPIRE FIELD32(0x00000004) +#define CSR7_TXDONE_TXRING FIELD32(0x00000008) +#define CSR7_TXDONE_ATIMRING FIELD32(0x00000010) +#define CSR7_TXDONE_PRIORING FIELD32(0x00000020) +#define CSR7_RXDONE FIELD32(0x00000040) +#define CSR7_DECRYPTION_DONE FIELD32(0x00000080) +#define CSR7_ENCRYPTION_DONE FIELD32(0x00000100) +#define CSR7_UART1_TX_TRESHOLD FIELD32(0x00000200) +#define CSR7_UART1_RX_TRESHOLD FIELD32(0x00000400) +#define CSR7_UART1_IDLE_TRESHOLD FIELD32(0x00000800) +#define CSR7_UART1_TX_BUFF_ERROR FIELD32(0x00001000) +#define CSR7_UART1_RX_BUFF_ERROR FIELD32(0x00002000) +#define CSR7_UART2_TX_TRESHOLD FIELD32(0x00004000) +#define CSR7_UART2_RX_TRESHOLD FIELD32(0x00008000) +#define CSR7_UART2_IDLE_TRESHOLD FIELD32(0x00010000) +#define CSR7_UART2_TX_BUFF_ERROR FIELD32(0x00020000) +#define CSR7_UART2_RX_BUFF_ERROR FIELD32(0x00040000) +#define CSR7_TIMER_CSR3_EXPIRE FIELD32(0x00080000) + +/* + * CSR8: Interrupt mask register. + * Write 1 to mask interrupt. + * TBCN_EXPIRE: Beacon timer expired interrupt. + * TWAKE_EXPIRE: Wakeup timer expired interrupt. + * TATIMW_EXPIRE: Timer of atim window expired interrupt. + * TXDONE_TXRING: Tx ring transmit done interrupt. + * TXDONE_ATIMRING: Atim ring transmit done interrupt. + * TXDONE_PRIORING: Priority ring transmit done interrupt. + * RXDONE: Receive done interrupt. + * DECRYPTION_DONE: Decryption done interrupt. + * ENCRYPTION_DONE: Encryption done interrupt. + * UART1_TX_TRESHOLD: UART1 TX reaches threshold. + * UART1_RX_TRESHOLD: UART1 RX reaches threshold. + * UART1_IDLE_TRESHOLD: UART1 IDLE over threshold. + * UART1_TX_BUFF_ERROR: UART1 TX buffer error. + * UART1_RX_BUFF_ERROR: UART1 RX buffer error. + * UART2_TX_TRESHOLD: UART2 TX reaches threshold. + * UART2_RX_TRESHOLD: UART2 RX reaches threshold. + * UART2_IDLE_TRESHOLD: UART2 IDLE over threshold. + * UART2_TX_BUFF_ERROR: UART2 TX buffer error. + * UART2_RX_BUFF_ERROR: UART2 RX buffer error. + * TIMER_CSR3_EXPIRE: TIMECSR3 timer expired (802.1H quiet period). + */ +#define CSR8 0x0020 +#define CSR8_TBCN_EXPIRE FIELD32(0x00000001) +#define CSR8_TWAKE_EXPIRE FIELD32(0x00000002) +#define CSR8_TATIMW_EXPIRE FIELD32(0x00000004) +#define CSR8_TXDONE_TXRING FIELD32(0x00000008) +#define CSR8_TXDONE_ATIMRING FIELD32(0x00000010) +#define CSR8_TXDONE_PRIORING FIELD32(0x00000020) +#define CSR8_RXDONE FIELD32(0x00000040) +#define CSR8_DECRYPTION_DONE FIELD32(0x00000080) +#define CSR8_ENCRYPTION_DONE FIELD32(0x00000100) +#define CSR8_UART1_TX_TRESHOLD FIELD32(0x00000200) +#define CSR8_UART1_RX_TRESHOLD FIELD32(0x00000400) +#define CSR8_UART1_IDLE_TRESHOLD FIELD32(0x00000800) +#define CSR8_UART1_TX_BUFF_ERROR FIELD32(0x00001000) +#define CSR8_UART1_RX_BUFF_ERROR FIELD32(0x00002000) +#define CSR8_UART2_TX_TRESHOLD FIELD32(0x00004000) +#define CSR8_UART2_RX_TRESHOLD FIELD32(0x00008000) +#define CSR8_UART2_IDLE_TRESHOLD FIELD32(0x00010000) +#define CSR8_UART2_TX_BUFF_ERROR FIELD32(0x00020000) +#define CSR8_UART2_RX_BUFF_ERROR FIELD32(0x00040000) +#define CSR8_TIMER_CSR3_EXPIRE FIELD32(0x00080000) + +/* + * CSR9: Maximum frame length register. + * MAX_FRAME_UNIT: Maximum frame length in 128b unit, default: 12. + */ +#define CSR9 0x0024 +#define CSR9_MAX_FRAME_UNIT FIELD32(0x00000f80) + +/* + * SECCSR0: WEP control register. + * KICK_DECRYPT: Kick decryption engine, self-clear. + * ONE_SHOT: 0: ring mode, 1: One shot only mode. + * DESC_ADDRESS: Descriptor physical address of frame. + */ +#define SECCSR0 0x0028 +#define SECCSR0_KICK_DECRYPT FIELD32(0x00000001) +#define SECCSR0_ONE_SHOT FIELD32(0x00000002) +#define SECCSR0_DESC_ADDRESS FIELD32(0xfffffffc) + +/* + * CSR11: Back-off control register. + * CWMIN: CWmin. Default cwmin is 31 (2^5 - 1). + * CWMAX: CWmax. Default cwmax is 1023 (2^10 - 1). + * SLOT_TIME: Slot time, default is 20us for 802.11b + * CW_SELECT: CWmin/CWmax selection, 1: Register, 0: TXD. + * LONG_RETRY: Long retry count. + * SHORT_RETRY: Short retry count. + */ +#define CSR11 0x002c +#define CSR11_CWMIN FIELD32(0x0000000f) +#define CSR11_CWMAX FIELD32(0x000000f0) +#define CSR11_SLOT_TIME FIELD32(0x00001f00) +#define CSR11_CW_SELECT FIELD32(0x00002000) +#define CSR11_LONG_RETRY FIELD32(0x00ff0000) +#define CSR11_SHORT_RETRY FIELD32(0xff000000) + +/* + * CSR12: Synchronization configuration register 0. + * All units in 1/16 TU. + * BEACON_INTERVAL: Beacon interval, default is 100 TU. + * CFP_MAX_DURATION: Cfp maximum duration, default is 100 TU. + */ +#define CSR12 0x0030 +#define CSR12_BEACON_INTERVAL FIELD32(0x0000ffff) +#define CSR12_CFP_MAX_DURATION FIELD32(0xffff0000) + +/* + * CSR13: Synchronization configuration register 1. + * All units in 1/16 TU. + * ATIMW_DURATION: Atim window duration. + * CFP_PERIOD: Cfp period, default is 0 TU. + */ +#define CSR13 0x0034 +#define CSR13_ATIMW_DURATION FIELD32(0x0000ffff) +#define CSR13_CFP_PERIOD FIELD32(0x00ff0000) + +/* + * CSR14: Synchronization control register. + * TSF_COUNT: Enable tsf auto counting. + * TSF_SYNC: Tsf sync, 0: disable, 1: infra, 2: ad-hoc/master mode. + * TBCN: Enable tbcn with reload value. + * TCFP: Enable tcfp & cfp / cp switching. + * TATIMW: Enable tatimw & atim window switching. + * BEACON_GEN: Enable beacon generator. + * CFP_COUNT_PRELOAD: Cfp count preload value. + * TBCM_PRELOAD: Tbcn preload value in units of 64us. + */ +#define CSR14 0x0038 +#define CSR14_TSF_COUNT FIELD32(0x00000001) +#define CSR14_TSF_SYNC FIELD32(0x00000006) +#define CSR14_TBCN FIELD32(0x00000008) +#define CSR14_TCFP FIELD32(0x00000010) +#define CSR14_TATIMW FIELD32(0x00000020) +#define CSR14_BEACON_GEN FIELD32(0x00000040) +#define CSR14_CFP_COUNT_PRELOAD FIELD32(0x0000ff00) +#define CSR14_TBCM_PRELOAD FIELD32(0xffff0000) + +/* + * CSR15: Synchronization status register. + * CFP: ASIC is in contention-free period. + * ATIMW: ASIC is in ATIM window. + * BEACON_SENT: Beacon is send. + */ +#define CSR15 0x003c +#define CSR15_CFP FIELD32(0x00000001) +#define CSR15_ATIMW FIELD32(0x00000002) +#define CSR15_BEACON_SENT FIELD32(0x00000004) + +/* + * CSR16: TSF timer register 0. + */ +#define CSR16 0x0040 +#define CSR16_LOW_TSFTIMER FIELD32(0xffffffff) + +/* + * CSR17: TSF timer register 1. + */ +#define CSR17 0x0044 +#define CSR17_HIGH_TSFTIMER FIELD32(0xffffffff) + +/* + * CSR18: IFS timer register 0. + * SIFS: Sifs, default is 10 us. + * PIFS: Pifs, default is 30 us. + */ +#define CSR18 0x0048 +#define CSR18_SIFS FIELD32(0x000001ff) +#define CSR18_PIFS FIELD32(0x001f0000) + +/* + * CSR19: IFS timer register 1. + * DIFS: Difs, default is 50 us. + * EIFS: Eifs, default is 364 us. + */ +#define CSR19 0x004c +#define CSR19_DIFS FIELD32(0x0000ffff) +#define CSR19_EIFS FIELD32(0xffff0000) + +/* + * CSR20: Wakeup timer register. + * DELAY_AFTER_TBCN: Delay after tbcn expired in units of 1/16 TU. + * TBCN_BEFORE_WAKEUP: Number of beacon before wakeup. + * AUTOWAKE: Enable auto wakeup / sleep mechanism. + */ +#define CSR20 0x0050 +#define CSR20_DELAY_AFTER_TBCN FIELD32(0x0000ffff) +#define CSR20_TBCN_BEFORE_WAKEUP FIELD32(0x00ff0000) +#define CSR20_AUTOWAKE FIELD32(0x01000000) + +/* + * CSR21: EEPROM control register. + * RELOAD: Write 1 to reload eeprom content. + * TYPE_93C46: 1: 93c46, 0:93c66. + */ +#define CSR21 0x0054 +#define CSR21_RELOAD FIELD32(0x00000001) +#define CSR21_EEPROM_DATA_CLOCK FIELD32(0x00000002) +#define CSR21_EEPROM_CHIP_SELECT FIELD32(0x00000004) +#define CSR21_EEPROM_DATA_IN FIELD32(0x00000008) +#define CSR21_EEPROM_DATA_OUT FIELD32(0x00000010) +#define CSR21_TYPE_93C46 FIELD32(0x00000020) + +/* + * CSR22: CFP control register. + * CFP_DURATION_REMAIN: Cfp duration remain, in units of TU. + * RELOAD_CFP_DURATION: Write 1 to reload cfp duration remain. + */ +#define CSR22 0x0058 +#define CSR22_CFP_DURATION_REMAIN FIELD32(0x0000ffff) +#define CSR22_RELOAD_CFP_DURATION FIELD32(0x00010000) + +/* + * Transmit related CSRs. + * Some values are set in TU, whereas 1 TU == 1024 us. + */ + +/* + * TXCSR0: TX Control Register. + * KICK_TX: Kick tx ring. + * KICK_ATIM: Kick atim ring. + * KICK_PRIO: Kick priority ring. + * ABORT: Abort all transmit related ring operation. + */ +#define TXCSR0 0x0060 +#define TXCSR0_KICK_TX FIELD32(0x00000001) +#define TXCSR0_KICK_ATIM FIELD32(0x00000002) +#define TXCSR0_KICK_PRIO FIELD32(0x00000004) +#define TXCSR0_ABORT FIELD32(0x00000008) + +/* + * TXCSR1: TX Configuration Register. + * ACK_TIMEOUT: Ack timeout, default = sifs + 2*slottime + acktime @ 1mbps. + * ACK_CONSUME_TIME: Ack consume time, default = sifs + acktime @ 1mbps. + * TSF_OFFSET: Insert tsf offset. + * AUTORESPONDER: Enable auto responder which include ack & cts. + */ +#define TXCSR1 0x0064 +#define TXCSR1_ACK_TIMEOUT FIELD32(0x000001ff) +#define TXCSR1_ACK_CONSUME_TIME FIELD32(0x0003fe00) +#define TXCSR1_TSF_OFFSET FIELD32(0x00fc0000) +#define TXCSR1_AUTORESPONDER FIELD32(0x01000000) + +/* + * TXCSR2: Tx descriptor configuration register. + * TXD_SIZE: Tx descriptor size, default is 48. + * NUM_TXD: Number of tx entries in ring. + * NUM_ATIM: Number of atim entries in ring. + * NUM_PRIO: Number of priority entries in ring. + */ +#define TXCSR2 0x0068 +#define TXCSR2_TXD_SIZE FIELD32(0x000000ff) +#define TXCSR2_NUM_TXD FIELD32(0x0000ff00) +#define TXCSR2_NUM_ATIM FIELD32(0x00ff0000) +#define TXCSR2_NUM_PRIO FIELD32(0xff000000) + +/* + * TXCSR3: TX Ring Base address register. + */ +#define TXCSR3 0x006c +#define TXCSR3_TX_RING_REGISTER FIELD32(0xffffffff) + +/* + * TXCSR4: TX Atim Ring Base address register. + */ +#define TXCSR4 0x0070 +#define TXCSR4_ATIM_RING_REGISTER FIELD32(0xffffffff) + +/* + * TXCSR5: TX Prio Ring Base address register. + */ +#define TXCSR5 0x0074 +#define TXCSR5_PRIO_RING_REGISTER FIELD32(0xffffffff) + +/* + * TXCSR6: Beacon Base address register. + */ +#define TXCSR6 0x0078 +#define TXCSR6_BEACON_RING_REGISTER FIELD32(0xffffffff) + +/* + * TXCSR7: Auto responder control register. + * AR_POWERMANAGEMENT: Auto responder power management bit. + */ +#define TXCSR7 0x007c +#define TXCSR7_AR_POWERMANAGEMENT FIELD32(0x00000001) + +/* + * TXCSR8: CCK Tx BBP register. + */ +#define TXCSR8 0x0098 +#define TXCSR8_BBP_ID0 FIELD32(0x0000007f) +#define TXCSR8_BBP_ID0_VALID FIELD32(0x00000080) +#define TXCSR8_BBP_ID1 FIELD32(0x00007f00) +#define TXCSR8_BBP_ID1_VALID FIELD32(0x00008000) +#define TXCSR8_BBP_ID2 FIELD32(0x007f0000) +#define TXCSR8_BBP_ID2_VALID FIELD32(0x00800000) +#define TXCSR8_BBP_ID3 FIELD32(0x7f000000) +#define TXCSR8_BBP_ID3_VALID FIELD32(0x80000000) + +/* + * TXCSR9: OFDM TX BBP registers + * OFDM_SIGNAL: BBP rate field address for OFDM. + * OFDM_SERVICE: BBP service field address for OFDM. + * OFDM_LENGTH_LOW: BBP length low byte address for OFDM. + * OFDM_LENGTH_HIGH: BBP length high byte address for OFDM. + */ +#define TXCSR9 0x0094 +#define TXCSR9_OFDM_RATE FIELD32(0x000000ff) +#define TXCSR9_OFDM_SERVICE FIELD32(0x0000ff00) +#define TXCSR9_OFDM_LENGTH_LOW FIELD32(0x00ff0000) +#define TXCSR9_OFDM_LENGTH_HIGH FIELD32(0xff000000) + +/* + * Receive related CSRs. + * Some values are set in TU, whereas 1 TU == 1024 us. + */ + +/* + * RXCSR0: RX Control Register. + * DISABLE_RX: Disable rx engine. + * DROP_CRC: Drop crc error. + * DROP_PHYSICAL: Drop physical error. + * DROP_CONTROL: Drop control frame. + * DROP_NOT_TO_ME: Drop not to me unicast frame. + * DROP_TODS: Drop frame tods bit is true. + * DROP_VERSION_ERROR: Drop version error frame. + * PASS_CRC: Pass all packets with crc attached. + * PASS_CRC: Pass all packets with crc attached. + * PASS_PLCP: Pass all packets with 4 bytes PLCP attached. + * DROP_MCAST: Drop multicast frames. + * DROP_BCAST: Drop broadcast frames. + * ENABLE_QOS: Accept QOS data frame and parse QOS field. + */ +#define RXCSR0 0x0080 +#define RXCSR0_DISABLE_RX FIELD32(0x00000001) +#define RXCSR0_DROP_CRC FIELD32(0x00000002) +#define RXCSR0_DROP_PHYSICAL FIELD32(0x00000004) +#define RXCSR0_DROP_CONTROL FIELD32(0x00000008) +#define RXCSR0_DROP_NOT_TO_ME FIELD32(0x00000010) +#define RXCSR0_DROP_TODS FIELD32(0x00000020) +#define RXCSR0_DROP_VERSION_ERROR FIELD32(0x00000040) +#define RXCSR0_PASS_CRC FIELD32(0x00000080) +#define RXCSR0_PASS_PLCP FIELD32(0x00000100) +#define RXCSR0_DROP_MCAST FIELD32(0x00000200) +#define RXCSR0_DROP_BCAST FIELD32(0x00000400) +#define RXCSR0_ENABLE_QOS FIELD32(0x00000800) + +/* + * RXCSR1: RX descriptor configuration register. + * RXD_SIZE: Rx descriptor size, default is 32b. + * NUM_RXD: Number of rx entries in ring. + */ +#define RXCSR1 0x0084 +#define RXCSR1_RXD_SIZE FIELD32(0x000000ff) +#define RXCSR1_NUM_RXD FIELD32(0x0000ff00) + +/* + * RXCSR2: RX Ring base address register. + */ +#define RXCSR2 0x0088 +#define RXCSR2_RX_RING_REGISTER FIELD32(0xffffffff) + +/* + * RXCSR3: BBP ID register for Rx operation. + * BBP_ID#: BBP register # id. + * BBP_ID#_VALID: BBP register # id is valid or not. + */ +#define RXCSR3 0x0090 +#define RXCSR3_BBP_ID0 FIELD32(0x0000007f) +#define RXCSR3_BBP_ID0_VALID FIELD32(0x00000080) +#define RXCSR3_BBP_ID1 FIELD32(0x00007f00) +#define RXCSR3_BBP_ID1_VALID FIELD32(0x00008000) +#define RXCSR3_BBP_ID2 FIELD32(0x007f0000) +#define RXCSR3_BBP_ID2_VALID FIELD32(0x00800000) +#define RXCSR3_BBP_ID3 FIELD32(0x7f000000) +#define RXCSR3_BBP_ID3_VALID FIELD32(0x80000000) + +/* + * ARCSR1: Auto Responder PLCP config register 1. + * AR_BBP_DATA#: Auto responder BBP register # data. + * AR_BBP_ID#: Auto responder BBP register # Id. + */ +#define ARCSR1 0x009c +#define ARCSR1_AR_BBP_DATA2 FIELD32(0x000000ff) +#define ARCSR1_AR_BBP_ID2 FIELD32(0x0000ff00) +#define ARCSR1_AR_BBP_DATA3 FIELD32(0x00ff0000) +#define ARCSR1_AR_BBP_ID3 FIELD32(0xff000000) + +/* + * Miscellaneous Registers. + * Some values are set in TU, whereas 1 TU == 1024 us. + + */ + +/* + * PCICSR: PCI control register. + * BIG_ENDIAN: 1: big endian, 0: little endian. + * RX_TRESHOLD: Rx threshold in dw to start pci access + * 0: 16dw (default), 1: 8dw, 2: 4dw, 3: 32dw. + * TX_TRESHOLD: Tx threshold in dw to start pci access + * 0: 0dw (default), 1: 1dw, 2: 4dw, 3: forward. + * BURST_LENTH: Pci burst length 0: 4dw (default, 1: 8dw, 2: 16dw, 3:32dw. + * ENABLE_CLK: Enable clk_run, pci clock can't going down to non-operational. + * READ_MULTIPLE: Enable memory read multiple. + * WRITE_INVALID: Enable memory write & invalid. + */ +#define PCICSR 0x008c +#define PCICSR_BIG_ENDIAN FIELD32(0x00000001) +#define PCICSR_RX_TRESHOLD FIELD32(0x00000006) +#define PCICSR_TX_TRESHOLD FIELD32(0x00000018) +#define PCICSR_BURST_LENTH FIELD32(0x00000060) +#define PCICSR_ENABLE_CLK FIELD32(0x00000080) +#define PCICSR_READ_MULTIPLE FIELD32(0x00000100) +#define PCICSR_WRITE_INVALID FIELD32(0x00000200) + +/* + * CNT0: FCS error count. + * FCS_ERROR: FCS error count, cleared when read. + */ +#define CNT0 0x00a0 +#define CNT0_FCS_ERROR FIELD32(0x0000ffff) + +/* + * Statistic Register. + * CNT1: PLCP error count. + * CNT2: Long error count. + */ +#define TIMECSR2 0x00a8 +#define CNT1 0x00ac +#define CNT2 0x00b0 +#define TIMECSR3 0x00b4 + +/* + * CNT3: CCA false alarm count. + */ +#define CNT3 0x00b8 +#define CNT3_FALSE_CCA FIELD32(0x0000ffff) + +/* + * Statistic Register. + * CNT4: Rx FIFO overflow count. + * CNT5: Tx FIFO underrun count. + */ +#define CNT4 0x00bc +#define CNT5 0x00c0 + +/* + * Baseband Control Register. + */ + +/* + * PWRCSR0: Power mode configuration register. + */ +#define PWRCSR0 0x00c4 + +/* + * Power state transition time registers. + */ +#define PSCSR0 0x00c8 +#define PSCSR1 0x00cc +#define PSCSR2 0x00d0 +#define PSCSR3 0x00d4 + +/* + * PWRCSR1: Manual power control / status register. + * Allowed state: 0 deep_sleep, 1: sleep, 2: standby, 3: awake. + * SET_STATE: Set state. Write 1 to trigger, self cleared. + * BBP_DESIRE_STATE: BBP desired state. + * RF_DESIRE_STATE: RF desired state. + * BBP_CURR_STATE: BBP current state. + * RF_CURR_STATE: RF current state. + * PUT_TO_SLEEP: Put to sleep. Write 1 to trigger, self cleared. + */ +#define PWRCSR1 0x00d8 +#define PWRCSR1_SET_STATE FIELD32(0x00000001) +#define PWRCSR1_BBP_DESIRE_STATE FIELD32(0x00000006) +#define PWRCSR1_RF_DESIRE_STATE FIELD32(0x00000018) +#define PWRCSR1_BBP_CURR_STATE FIELD32(0x00000060) +#define PWRCSR1_RF_CURR_STATE FIELD32(0x00000180) +#define PWRCSR1_PUT_TO_SLEEP FIELD32(0x00000200) + +/* + * TIMECSR: Timer control register. + * US_COUNT: 1 us timer count in units of clock cycles. + * US_64_COUNT: 64 us timer count in units of 1 us timer. + * BEACON_EXPECT: Beacon expect window. + */ +#define TIMECSR 0x00dc +#define TIMECSR_US_COUNT FIELD32(0x000000ff) +#define TIMECSR_US_64_COUNT FIELD32(0x0000ff00) +#define TIMECSR_BEACON_EXPECT FIELD32(0x00070000) + +/* + * MACCSR0: MAC configuration register 0. + */ +#define MACCSR0 0x00e0 + +/* + * MACCSR1: MAC configuration register 1. + * KICK_RX: Kick one-shot rx in one-shot rx mode. + * ONESHOT_RXMODE: Enable one-shot rx mode for debugging. + * BBPRX_RESET_MODE: Ralink bbp rx reset mode. + * AUTO_TXBBP: Auto tx logic access bbp control register. + * AUTO_RXBBP: Auto rx logic access bbp control register. + * LOOPBACK: Loopback mode. 0: normal, 1: internal, 2: external, 3:rsvd. + * INTERSIL_IF: Intersil if calibration pin. + */ +#define MACCSR1 0x00e4 +#define MACCSR1_KICK_RX FIELD32(0x00000001) +#define MACCSR1_ONESHOT_RXMODE FIELD32(0x00000002) +#define MACCSR1_BBPRX_RESET_MODE FIELD32(0x00000004) +#define MACCSR1_AUTO_TXBBP FIELD32(0x00000008) +#define MACCSR1_AUTO_RXBBP FIELD32(0x00000010) +#define MACCSR1_LOOPBACK FIELD32(0x00000060) +#define MACCSR1_INTERSIL_IF FIELD32(0x00000080) + +/* + * RALINKCSR: Ralink Rx auto-reset BBCR. + * AR_BBP_DATA#: Auto reset BBP register # data. + * AR_BBP_ID#: Auto reset BBP register # id. + */ +#define RALINKCSR 0x00e8 +#define RALINKCSR_AR_BBP_DATA0 FIELD32(0x000000ff) +#define RALINKCSR_AR_BBP_ID0 FIELD32(0x00007f00) +#define RALINKCSR_AR_BBP_VALID0 FIELD32(0x00008000) +#define RALINKCSR_AR_BBP_DATA1 FIELD32(0x00ff0000) +#define RALINKCSR_AR_BBP_ID1 FIELD32(0x7f000000) +#define RALINKCSR_AR_BBP_VALID1 FIELD32(0x80000000) + +/* + * BCNCSR: Beacon interval control register. + * CHANGE: Write one to change beacon interval. + * DELTATIME: The delta time value. + * NUM_BEACON: Number of beacon according to mode. + * MODE: Please refer to asic specs. + * PLUS: Plus or minus delta time value. + */ +#define BCNCSR 0x00ec +#define BCNCSR_CHANGE FIELD32(0x00000001) +#define BCNCSR_DELTATIME FIELD32(0x0000001e) +#define BCNCSR_NUM_BEACON FIELD32(0x00001fe0) +#define BCNCSR_MODE FIELD32(0x00006000) +#define BCNCSR_PLUS FIELD32(0x00008000) + +/* + * BBP / RF / IF Control Register. + */ + +/* + * BBPCSR: BBP serial control register. + * VALUE: Register value to program into BBP. + * REGNUM: Selected BBP register. + * BUSY: 1: asic is busy execute BBP programming. + * WRITE_CONTROL: 1: write BBP, 0: read BBP. + */ +#define BBPCSR 0x00f0 +#define BBPCSR_VALUE FIELD32(0x000000ff) +#define BBPCSR_REGNUM FIELD32(0x00007f00) +#define BBPCSR_BUSY FIELD32(0x00008000) +#define BBPCSR_WRITE_CONTROL FIELD32(0x00010000) + +/* + * RFCSR: RF serial control register. + * VALUE: Register value + id to program into rf/if. + * NUMBER_OF_BITS: Number of bits used in value (i:20, rfmd:22). + * IF_SELECT: Chip to program: 0: rf, 1: if. + * PLL_LD: Rf pll_ld status. + * BUSY: 1: asic is busy execute rf programming. + */ +#define RFCSR 0x00f4 +#define RFCSR_VALUE FIELD32(0x00ffffff) +#define RFCSR_NUMBER_OF_BITS FIELD32(0x1f000000) +#define RFCSR_IF_SELECT FIELD32(0x20000000) +#define RFCSR_PLL_LD FIELD32(0x40000000) +#define RFCSR_BUSY FIELD32(0x80000000) + +/* + * LEDCSR: LED control register. + * ON_PERIOD: On period, default 70ms. + * OFF_PERIOD: Off period, default 30ms. + * LINK: 0: linkoff, 1: linkup. + * ACTIVITY: 0: idle, 1: active. + * LINK_POLARITY: 0: active low, 1: active high. + * ACTIVITY_POLARITY: 0: active low, 1: active high. + * LED_DEFAULT: LED state for "enable" 0: ON, 1: OFF. + */ +#define LEDCSR 0x00f8 +#define LEDCSR_ON_PERIOD FIELD32(0x000000ff) +#define LEDCSR_OFF_PERIOD FIELD32(0x0000ff00) +#define LEDCSR_LINK FIELD32(0x00010000) +#define LEDCSR_ACTIVITY FIELD32(0x00020000) +#define LEDCSR_LINK_POLARITY FIELD32(0x00040000) +#define LEDCSR_ACTIVITY_POLARITY FIELD32(0x00080000) +#define LEDCSR_LED_DEFAULT FIELD32(0x00100000) + +/* + * SECCSR3: AES control register. + */ +#define SECCSR3 0x00fc + +/* + * ASIC pointer information. + * RXPTR: Current RX ring address. + * TXPTR: Current Tx ring address. + * PRIPTR: Current Priority ring address. + * ATIMPTR: Current ATIM ring address. + */ +#define RXPTR 0x0100 +#define TXPTR 0x0104 +#define PRIPTR 0x0108 +#define ATIMPTR 0x010c + +/* + * TXACKCSR0: TX ACK timeout. + */ +#define TXACKCSR0 0x0110 + +/* + * ACK timeout count registers. + * ACKCNT0: TX ACK timeout count. + * ACKCNT1: RX ACK timeout count. + */ +#define ACKCNT0 0x0114 +#define ACKCNT1 0x0118 + +/* + * GPIO and others. + */ + +/* + * GPIOCSR: GPIO control register. + * GPIOCSR_VALx: GPIO value + * GPIOCSR_DIRx: GPIO direction: 0 = output; 1 = input + */ +#define GPIOCSR 0x0120 +#define GPIOCSR_VAL0 FIELD32(0x00000001) +#define GPIOCSR_VAL1 FIELD32(0x00000002) +#define GPIOCSR_VAL2 FIELD32(0x00000004) +#define GPIOCSR_VAL3 FIELD32(0x00000008) +#define GPIOCSR_VAL4 FIELD32(0x00000010) +#define GPIOCSR_VAL5 FIELD32(0x00000020) +#define GPIOCSR_VAL6 FIELD32(0x00000040) +#define GPIOCSR_VAL7 FIELD32(0x00000080) +#define GPIOCSR_DIR0 FIELD32(0x00000100) +#define GPIOCSR_DIR1 FIELD32(0x00000200) +#define GPIOCSR_DIR2 FIELD32(0x00000400) +#define GPIOCSR_DIR3 FIELD32(0x00000800) +#define GPIOCSR_DIR4 FIELD32(0x00001000) +#define GPIOCSR_DIR5 FIELD32(0x00002000) +#define GPIOCSR_DIR6 FIELD32(0x00004000) +#define GPIOCSR_DIR7 FIELD32(0x00008000) + +/* + * FIFO pointer registers. + * FIFOCSR0: TX FIFO pointer. + * FIFOCSR1: RX FIFO pointer. + */ +#define FIFOCSR0 0x0128 +#define FIFOCSR1 0x012c + +/* + * BCNCSR1: Tx BEACON offset time control register. + * PRELOAD: Beacon timer offset in units of usec. + * BEACON_CWMIN: 2^CwMin. + */ +#define BCNCSR1 0x0130 +#define BCNCSR1_PRELOAD FIELD32(0x0000ffff) +#define BCNCSR1_BEACON_CWMIN FIELD32(0x000f0000) + +/* + * MACCSR2: TX_PE to RX_PE turn-around time control register + * DELAY: RX_PE low width, in units of pci clock cycle. + */ +#define MACCSR2 0x0134 +#define MACCSR2_DELAY FIELD32(0x000000ff) + +/* + * TESTCSR: TEST mode selection register. + */ +#define TESTCSR 0x0138 + +/* + * ARCSR2: 1 Mbps ACK/CTS PLCP. + */ +#define ARCSR2 0x013c +#define ARCSR2_SIGNAL FIELD32(0x000000ff) +#define ARCSR2_SERVICE FIELD32(0x0000ff00) +#define ARCSR2_LENGTH FIELD32(0xffff0000) + +/* + * ARCSR3: 2 Mbps ACK/CTS PLCP. + */ +#define ARCSR3 0x0140 +#define ARCSR3_SIGNAL FIELD32(0x000000ff) +#define ARCSR3_SERVICE FIELD32(0x0000ff00) +#define ARCSR3_LENGTH FIELD32(0xffff0000) + +/* + * ARCSR4: 5.5 Mbps ACK/CTS PLCP. + */ +#define ARCSR4 0x0144 +#define ARCSR4_SIGNAL FIELD32(0x000000ff) +#define ARCSR4_SERVICE FIELD32(0x0000ff00) +#define ARCSR4_LENGTH FIELD32(0xffff0000) + +/* + * ARCSR5: 11 Mbps ACK/CTS PLCP. + */ +#define ARCSR5 0x0148 +#define ARCSR5_SIGNAL FIELD32(0x000000ff) +#define ARCSR5_SERVICE FIELD32(0x0000ff00) +#define ARCSR5_LENGTH FIELD32(0xffff0000) + +/* + * ARTCSR0: CCK ACK/CTS payload consumed time for 1/2/5.5/11 mbps. + */ +#define ARTCSR0 0x014c +#define ARTCSR0_ACK_CTS_11MBS FIELD32(0x000000ff) +#define ARTCSR0_ACK_CTS_5_5MBS FIELD32(0x0000ff00) +#define ARTCSR0_ACK_CTS_2MBS FIELD32(0x00ff0000) +#define ARTCSR0_ACK_CTS_1MBS FIELD32(0xff000000) + + +/* + * ARTCSR1: OFDM ACK/CTS payload consumed time for 6/9/12/18 mbps. + */ +#define ARTCSR1 0x0150 +#define ARTCSR1_ACK_CTS_6MBS FIELD32(0x000000ff) +#define ARTCSR1_ACK_CTS_9MBS FIELD32(0x0000ff00) +#define ARTCSR1_ACK_CTS_12MBS FIELD32(0x00ff0000) +#define ARTCSR1_ACK_CTS_18MBS FIELD32(0xff000000) + +/* + * ARTCSR2: OFDM ACK/CTS payload consumed time for 24/36/48/54 mbps. + */ +#define ARTCSR2 0x0154 +#define ARTCSR2_ACK_CTS_24MBS FIELD32(0x000000ff) +#define ARTCSR2_ACK_CTS_36MBS FIELD32(0x0000ff00) +#define ARTCSR2_ACK_CTS_48MBS FIELD32(0x00ff0000) +#define ARTCSR2_ACK_CTS_54MBS FIELD32(0xff000000) + +/* + * SECCSR1: WEP control register. + * KICK_ENCRYPT: Kick encryption engine, self-clear. + * ONE_SHOT: 0: ring mode, 1: One shot only mode. + * DESC_ADDRESS: Descriptor physical address of frame. + */ +#define SECCSR1 0x0158 +#define SECCSR1_KICK_ENCRYPT FIELD32(0x00000001) +#define SECCSR1_ONE_SHOT FIELD32(0x00000002) +#define SECCSR1_DESC_ADDRESS FIELD32(0xfffffffc) + +/* + * BBPCSR1: BBP TX configuration. + */ +#define BBPCSR1 0x015c +#define BBPCSR1_CCK FIELD32(0x00000003) +#define BBPCSR1_CCK_FLIP FIELD32(0x00000004) +#define BBPCSR1_OFDM FIELD32(0x00030000) +#define BBPCSR1_OFDM_FLIP FIELD32(0x00040000) + +/* + * Dual band configuration registers. + * DBANDCSR0: Dual band configuration register 0. + * DBANDCSR1: Dual band configuration register 1. + */ +#define DBANDCSR0 0x0160 +#define DBANDCSR1 0x0164 + +/* + * BBPPCSR: BBP Pin control register. + */ +#define BBPPCSR 0x0168 + +/* + * MAC special debug mode selection registers. + * DBGSEL0: MAC special debug mode selection register 0. + * DBGSEL1: MAC special debug mode selection register 1. + */ +#define DBGSEL0 0x016c +#define DBGSEL1 0x0170 + +/* + * BISTCSR: BBP BIST register. + */ +#define BISTCSR 0x0174 + +/* + * Multicast filter registers. + * MCAST0: Multicast filter register 0. + * MCAST1: Multicast filter register 1. + */ +#define MCAST0 0x0178 +#define MCAST1 0x017c + +/* + * UART registers. + * UARTCSR0: UART1 TX register. + * UARTCSR1: UART1 RX register. + * UARTCSR3: UART1 frame control register. + * UARTCSR4: UART1 buffer control register. + * UART2CSR0: UART2 TX register. + * UART2CSR1: UART2 RX register. + * UART2CSR3: UART2 frame control register. + * UART2CSR4: UART2 buffer control register. + */ +#define UARTCSR0 0x0180 +#define UARTCSR1 0x0184 +#define UARTCSR3 0x0188 +#define UARTCSR4 0x018c +#define UART2CSR0 0x0190 +#define UART2CSR1 0x0194 +#define UART2CSR3 0x0198 +#define UART2CSR4 0x019c + +/* + * BBP registers. + * The wordsize of the BBP is 8 bits. + */ + +/* + * R2: TX antenna control + */ +#define BBP_R2_TX_ANTENNA FIELD8(0x03) +#define BBP_R2_TX_IQ_FLIP FIELD8(0x04) + +/* + * R14: RX antenna control + */ +#define BBP_R14_RX_ANTENNA FIELD8(0x03) +#define BBP_R14_RX_IQ_FLIP FIELD8(0x04) + +/* + * BBP_R70 + */ +#define BBP_R70_JAPAN_FILTER FIELD8(0x08) + +/* + * RF registers + */ + +/* + * RF 1 + */ +#define RF1_TUNER FIELD32(0x00020000) + +/* + * RF 3 + */ +#define RF3_TUNER FIELD32(0x00000100) +#define RF3_TXPOWER FIELD32(0x00003e00) + +/* + * EEPROM content. + * The wordsize of the EEPROM is 16 bits. + */ + +/* + * HW MAC address. + */ +#define EEPROM_MAC_ADDR_0 0x0002 +#define EEPROM_MAC_ADDR_BYTE0 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE1 FIELD16(0xff00) +#define EEPROM_MAC_ADDR1 0x0003 +#define EEPROM_MAC_ADDR_BYTE2 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE3 FIELD16(0xff00) +#define EEPROM_MAC_ADDR_2 0x0004 +#define EEPROM_MAC_ADDR_BYTE4 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE5 FIELD16(0xff00) + +/* + * EEPROM antenna. + * ANTENNA_NUM: Number of antenna's. + * TX_DEFAULT: Default antenna 0: diversity, 1: A, 2: B. + * RX_DEFAULT: Default antenna 0: diversity, 1: A, 2: B. + * LED_MODE: 0: default, 1: TX/RX activity,2: Single (ignore link), 3: rsvd. + * DYN_TXAGC: Dynamic TX AGC control. + * HARDWARE_RADIO: 1: Hardware controlled radio. Read GPIO0. + * RF_TYPE: Rf_type of this adapter. + */ +#define EEPROM_ANTENNA 0x10 +#define EEPROM_ANTENNA_NUM FIELD16(0x0003) +#define EEPROM_ANTENNA_TX_DEFAULT FIELD16(0x000c) +#define EEPROM_ANTENNA_RX_DEFAULT FIELD16(0x0030) +#define EEPROM_ANTENNA_LED_MODE FIELD16(0x01c0) +#define EEPROM_ANTENNA_DYN_TXAGC FIELD16(0x0200) +#define EEPROM_ANTENNA_HARDWARE_RADIO FIELD16(0x0400) +#define EEPROM_ANTENNA_RF_TYPE FIELD16(0xf800) + +/* + * EEPROM NIC config. + * CARDBUS_ACCEL: 0: enable, 1: disable. + * DYN_BBP_TUNE: 0: enable, 1: disable. + * CCK_TX_POWER: CCK TX power compensation. + */ +#define EEPROM_NIC 0x11 +#define EEPROM_NIC_CARDBUS_ACCEL FIELD16(0x0001) +#define EEPROM_NIC_DYN_BBP_TUNE FIELD16(0x0002) +#define EEPROM_NIC_CCK_TX_POWER FIELD16(0x000c) + +/* + * EEPROM geography. + * GEO: Default geography setting for device. + */ +#define EEPROM_GEOGRAPHY 0x12 +#define EEPROM_GEOGRAPHY_GEO FIELD16(0x0f00) + +/* + * EEPROM BBP. + */ +#define EEPROM_BBP_START 0x13 +#define EEPROM_BBP_SIZE 16 +#define EEPROM_BBP_VALUE FIELD16(0x00ff) +#define EEPROM_BBP_REG_ID FIELD16(0xff00) + +/* + * EEPROM TXPOWER + */ +#define EEPROM_TXPOWER_START 0x23 +#define EEPROM_TXPOWER_SIZE 7 +#define EEPROM_TXPOWER_1 FIELD16(0x00ff) +#define EEPROM_TXPOWER_2 FIELD16(0xff00) + +/* + * RSSI <-> dBm offset calibration + */ +#define EEPROM_CALIBRATE_OFFSET 0x3e +#define EEPROM_CALIBRATE_OFFSET_RSSI FIELD16(0x00ff) + +/* + * DMA descriptor defines. + */ +#define TXD_DESC_SIZE (11 * sizeof(__le32)) +#define RXD_DESC_SIZE (11 * sizeof(__le32)) + +/* + * TX descriptor format for TX, PRIO, ATIM and Beacon Ring. + */ + +/* + * Word0 + */ +#define TXD_W0_OWNER_NIC FIELD32(0x00000001) +#define TXD_W0_VALID FIELD32(0x00000002) +#define TXD_W0_RESULT FIELD32(0x0000001c) +#define TXD_W0_RETRY_COUNT FIELD32(0x000000e0) +#define TXD_W0_MORE_FRAG FIELD32(0x00000100) +#define TXD_W0_ACK FIELD32(0x00000200) +#define TXD_W0_TIMESTAMP FIELD32(0x00000400) +#define TXD_W0_OFDM FIELD32(0x00000800) +#define TXD_W0_CIPHER_OWNER FIELD32(0x00001000) +#define TXD_W0_IFS FIELD32(0x00006000) +#define TXD_W0_RETRY_MODE FIELD32(0x00008000) +#define TXD_W0_DATABYTE_COUNT FIELD32(0x0fff0000) +#define TXD_W0_CIPHER_ALG FIELD32(0xe0000000) + +/* + * Word1 + */ +#define TXD_W1_BUFFER_ADDRESS FIELD32(0xffffffff) + +/* + * Word2 + */ +#define TXD_W2_IV_OFFSET FIELD32(0x0000003f) +#define TXD_W2_AIFS FIELD32(0x000000c0) +#define TXD_W2_CWMIN FIELD32(0x00000f00) +#define TXD_W2_CWMAX FIELD32(0x0000f000) + +/* + * Word3: PLCP information + */ +#define TXD_W3_PLCP_SIGNAL FIELD32(0x000000ff) +#define TXD_W3_PLCP_SERVICE FIELD32(0x0000ff00) +#define TXD_W3_PLCP_LENGTH_LOW FIELD32(0x00ff0000) +#define TXD_W3_PLCP_LENGTH_HIGH FIELD32(0xff000000) + +/* + * Word4 + */ +#define TXD_W4_IV FIELD32(0xffffffff) + +/* + * Word5 + */ +#define TXD_W5_EIV FIELD32(0xffffffff) + +/* + * Word6-9: Key + */ +#define TXD_W6_KEY FIELD32(0xffffffff) +#define TXD_W7_KEY FIELD32(0xffffffff) +#define TXD_W8_KEY FIELD32(0xffffffff) +#define TXD_W9_KEY FIELD32(0xffffffff) + +/* + * Word10 + */ +#define TXD_W10_RTS FIELD32(0x00000001) +#define TXD_W10_TX_RATE FIELD32(0x000000fe) + +/* + * RX descriptor format for RX Ring. + */ + +/* + * Word0 + */ +#define RXD_W0_OWNER_NIC FIELD32(0x00000001) +#define RXD_W0_UNICAST_TO_ME FIELD32(0x00000002) +#define RXD_W0_MULTICAST FIELD32(0x00000004) +#define RXD_W0_BROADCAST FIELD32(0x00000008) +#define RXD_W0_MY_BSS FIELD32(0x00000010) +#define RXD_W0_CRC_ERROR FIELD32(0x00000020) +#define RXD_W0_OFDM FIELD32(0x00000040) +#define RXD_W0_PHYSICAL_ERROR FIELD32(0x00000080) +#define RXD_W0_CIPHER_OWNER FIELD32(0x00000100) +#define RXD_W0_ICV_ERROR FIELD32(0x00000200) +#define RXD_W0_IV_OFFSET FIELD32(0x0000fc00) +#define RXD_W0_DATABYTE_COUNT FIELD32(0x0fff0000) +#define RXD_W0_CIPHER_ALG FIELD32(0xe0000000) + +/* + * Word1 + */ +#define RXD_W1_BUFFER_ADDRESS FIELD32(0xffffffff) + +/* + * Word2 + */ +#define RXD_W2_SIGNAL FIELD32(0x000000ff) +#define RXD_W2_RSSI FIELD32(0x0000ff00) +#define RXD_W2_TA FIELD32(0xffff0000) + +/* + * Word3 + */ +#define RXD_W3_TA FIELD32(0xffffffff) + +/* + * Word4 + */ +#define RXD_W4_IV FIELD32(0xffffffff) + +/* + * Word5 + */ +#define RXD_W5_EIV FIELD32(0xffffffff) + +/* + * Word6-9: Key + */ +#define RXD_W6_KEY FIELD32(0xffffffff) +#define RXD_W7_KEY FIELD32(0xffffffff) +#define RXD_W8_KEY FIELD32(0xffffffff) +#define RXD_W9_KEY FIELD32(0xffffffff) + +/* + * Word10 + */ +#define RXD_W10_DROP FIELD32(0x00000001) + +/* + * Macros for converting txpower from EEPROM to mac80211 value + * and from mac80211 value to register value. + */ +#define MIN_TXPOWER 0 +#define MAX_TXPOWER 31 +#define DEFAULT_TXPOWER 24 + +#define TXPOWER_FROM_DEV(__txpower) \ + (((u8)(__txpower)) > MAX_TXPOWER) ? DEFAULT_TXPOWER : (__txpower) + +#define TXPOWER_TO_DEV(__txpower) \ + clamp_t(char, __txpower, MIN_TXPOWER, MAX_TXPOWER) + +#endif /* RT2500PCI_H */ diff --git a/drivers/net/wireless/rt2x00/rt2500usb.c b/drivers/net/wireless/rt2x00/rt2500usb.c new file mode 100644 index 0000000..6e09409 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2500usb.c @@ -0,0 +1,1998 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2500usb + Abstract: rt2500usb device specific routines. + Supported chipsets: RT2570. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "rt2x00.h" +#include "rt2x00usb.h" +#include "rt2500usb.h" + +/* + * Allow hardware encryption to be disabled. + */ +static bool modparam_nohwcrypt; +module_param_named(nohwcrypt, modparam_nohwcrypt, bool, S_IRUGO); +MODULE_PARM_DESC(nohwcrypt, "Disable hardware encryption."); + +/* + * Register access. + * All access to the CSR registers will go through the methods + * rt2500usb_register_read and rt2500usb_register_write. + * BBP and RF register require indirect register access, + * and use the CSR registers BBPCSR and RFCSR to achieve this. + * These indirect registers work with busy bits, + * and we will try maximal REGISTER_BUSY_COUNT times to access + * the register while taking a REGISTER_BUSY_DELAY us delay + * between each attampt. When the busy bit is still set at that time, + * the access attempt is considered to have failed, + * and we will print an error. + * If the csr_mutex is already held then the _lock variants must + * be used instead. + */ +static inline void rt2500usb_register_read(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + u16 *value) +{ + __le16 reg; + rt2x00usb_vendor_request_buff(rt2x00dev, USB_MULTI_READ, + USB_VENDOR_REQUEST_IN, offset, + ®, sizeof(reg), REGISTER_TIMEOUT); + *value = le16_to_cpu(reg); +} + +static inline void rt2500usb_register_read_lock(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + u16 *value) +{ + __le16 reg; + rt2x00usb_vendor_req_buff_lock(rt2x00dev, USB_MULTI_READ, + USB_VENDOR_REQUEST_IN, offset, + ®, sizeof(reg), REGISTER_TIMEOUT); + *value = le16_to_cpu(reg); +} + +static inline void rt2500usb_register_multiread(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + void *value, const u16 length) +{ + rt2x00usb_vendor_request_buff(rt2x00dev, USB_MULTI_READ, + USB_VENDOR_REQUEST_IN, offset, + value, length, + REGISTER_TIMEOUT16(length)); +} + +static inline void rt2500usb_register_write(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + u16 value) +{ + __le16 reg = cpu_to_le16(value); + rt2x00usb_vendor_request_buff(rt2x00dev, USB_MULTI_WRITE, + USB_VENDOR_REQUEST_OUT, offset, + ®, sizeof(reg), REGISTER_TIMEOUT); +} + +static inline void rt2500usb_register_write_lock(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + u16 value) +{ + __le16 reg = cpu_to_le16(value); + rt2x00usb_vendor_req_buff_lock(rt2x00dev, USB_MULTI_WRITE, + USB_VENDOR_REQUEST_OUT, offset, + ®, sizeof(reg), REGISTER_TIMEOUT); +} + +static inline void rt2500usb_register_multiwrite(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + void *value, const u16 length) +{ + rt2x00usb_vendor_request_buff(rt2x00dev, USB_MULTI_WRITE, + USB_VENDOR_REQUEST_OUT, offset, + value, length, + REGISTER_TIMEOUT16(length)); +} + +static int rt2500usb_regbusy_read(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + struct rt2x00_field16 field, + u16 *reg) +{ + unsigned int i; + + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2500usb_register_read_lock(rt2x00dev, offset, reg); + if (!rt2x00_get_field16(*reg, field)) + return 1; + udelay(REGISTER_BUSY_DELAY); + } + + rt2x00_err(rt2x00dev, "Indirect register access failed: offset=0x%.08x, value=0x%.08x\n", + offset, *reg); + *reg = ~0; + + return 0; +} + +#define WAIT_FOR_BBP(__dev, __reg) \ + rt2500usb_regbusy_read((__dev), PHY_CSR8, PHY_CSR8_BUSY, (__reg)) +#define WAIT_FOR_RF(__dev, __reg) \ + rt2500usb_regbusy_read((__dev), PHY_CSR10, PHY_CSR10_RF_BUSY, (__reg)) + +static void rt2500usb_bbp_write(struct rt2x00_dev *rt2x00dev, + const unsigned int word, const u8 value) +{ + u16 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the BBP becomes available, afterwards we + * can safely write the new data into the register. + */ + if (WAIT_FOR_BBP(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field16(®, PHY_CSR7_DATA, value); + rt2x00_set_field16(®, PHY_CSR7_REG_ID, word); + rt2x00_set_field16(®, PHY_CSR7_READ_CONTROL, 0); + + rt2500usb_register_write_lock(rt2x00dev, PHY_CSR7, reg); + } + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt2500usb_bbp_read(struct rt2x00_dev *rt2x00dev, + const unsigned int word, u8 *value) +{ + u16 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the BBP becomes available, afterwards we + * can safely write the read request into the register. + * After the data has been written, we wait until hardware + * returns the correct value, if at any time the register + * doesn't become available in time, reg will be 0xffffffff + * which means we return 0xff to the caller. + */ + if (WAIT_FOR_BBP(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field16(®, PHY_CSR7_REG_ID, word); + rt2x00_set_field16(®, PHY_CSR7_READ_CONTROL, 1); + + rt2500usb_register_write_lock(rt2x00dev, PHY_CSR7, reg); + + if (WAIT_FOR_BBP(rt2x00dev, ®)) + rt2500usb_register_read_lock(rt2x00dev, PHY_CSR7, ®); + } + + *value = rt2x00_get_field16(reg, PHY_CSR7_DATA); + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt2500usb_rf_write(struct rt2x00_dev *rt2x00dev, + const unsigned int word, const u32 value) +{ + u16 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the RF becomes available, afterwards we + * can safely write the new data into the register. + */ + if (WAIT_FOR_RF(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field16(®, PHY_CSR9_RF_VALUE, value); + rt2500usb_register_write_lock(rt2x00dev, PHY_CSR9, reg); + + reg = 0; + rt2x00_set_field16(®, PHY_CSR10_RF_VALUE, value >> 16); + rt2x00_set_field16(®, PHY_CSR10_RF_NUMBER_OF_BITS, 20); + rt2x00_set_field16(®, PHY_CSR10_RF_IF_SELECT, 0); + rt2x00_set_field16(®, PHY_CSR10_RF_BUSY, 1); + + rt2500usb_register_write_lock(rt2x00dev, PHY_CSR10, reg); + rt2x00_rf_write(rt2x00dev, word, value); + } + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS +static void _rt2500usb_register_read(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + u32 *value) +{ + rt2500usb_register_read(rt2x00dev, offset, (u16 *)value); +} + +static void _rt2500usb_register_write(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + u32 value) +{ + rt2500usb_register_write(rt2x00dev, offset, value); +} + +static const struct rt2x00debug rt2500usb_rt2x00debug = { + .owner = THIS_MODULE, + .csr = { + .read = _rt2500usb_register_read, + .write = _rt2500usb_register_write, + .flags = RT2X00DEBUGFS_OFFSET, + .word_base = CSR_REG_BASE, + .word_size = sizeof(u16), + .word_count = CSR_REG_SIZE / sizeof(u16), + }, + .eeprom = { + .read = rt2x00_eeprom_read, + .write = rt2x00_eeprom_write, + .word_base = EEPROM_BASE, + .word_size = sizeof(u16), + .word_count = EEPROM_SIZE / sizeof(u16), + }, + .bbp = { + .read = rt2500usb_bbp_read, + .write = rt2500usb_bbp_write, + .word_base = BBP_BASE, + .word_size = sizeof(u8), + .word_count = BBP_SIZE / sizeof(u8), + }, + .rf = { + .read = rt2x00_rf_read, + .write = rt2500usb_rf_write, + .word_base = RF_BASE, + .word_size = sizeof(u32), + .word_count = RF_SIZE / sizeof(u32), + }, +}; +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ + +static int rt2500usb_rfkill_poll(struct rt2x00_dev *rt2x00dev) +{ + u16 reg; + + rt2500usb_register_read(rt2x00dev, MAC_CSR19, ®); + return rt2x00_get_field16(reg, MAC_CSR19_VAL7); +} + +#ifdef CPTCFG_RT2X00_LIB_LEDS +static void rt2500usb_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct rt2x00_led *led = + container_of(led_cdev, struct rt2x00_led, led_dev); + unsigned int enabled = brightness != LED_OFF; + u16 reg; + + rt2500usb_register_read(led->rt2x00dev, MAC_CSR20, ®); + + if (led->type == LED_TYPE_RADIO || led->type == LED_TYPE_ASSOC) + rt2x00_set_field16(®, MAC_CSR20_LINK, enabled); + else if (led->type == LED_TYPE_ACTIVITY) + rt2x00_set_field16(®, MAC_CSR20_ACTIVITY, enabled); + + rt2500usb_register_write(led->rt2x00dev, MAC_CSR20, reg); +} + +static int rt2500usb_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct rt2x00_led *led = + container_of(led_cdev, struct rt2x00_led, led_dev); + u16 reg; + + rt2500usb_register_read(led->rt2x00dev, MAC_CSR21, ®); + rt2x00_set_field16(®, MAC_CSR21_ON_PERIOD, *delay_on); + rt2x00_set_field16(®, MAC_CSR21_OFF_PERIOD, *delay_off); + rt2500usb_register_write(led->rt2x00dev, MAC_CSR21, reg); + + return 0; +} + +static void rt2500usb_init_led(struct rt2x00_dev *rt2x00dev, + struct rt2x00_led *led, + enum led_type type) +{ + led->rt2x00dev = rt2x00dev; + led->type = type; + led->led_dev.brightness_set = rt2500usb_brightness_set; + led->led_dev.blink_set = rt2500usb_blink_set; + led->flags = LED_INITIALIZED; +} +#endif /* CPTCFG_RT2X00_LIB_LEDS */ + +/* + * Configuration handlers. + */ + +/* + * rt2500usb does not differentiate between shared and pairwise + * keys, so we should use the same function for both key types. + */ +static int rt2500usb_config_key(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_crypto *crypto, + struct ieee80211_key_conf *key) +{ + u32 mask; + u16 reg; + enum cipher curr_cipher; + + if (crypto->cmd == SET_KEY) { + /* + * Disallow to set WEP key other than with index 0, + * it is known that not work at least on some hardware. + * SW crypto will be used in that case. + */ + if ((key->cipher == WLAN_CIPHER_SUITE_WEP40 || + key->cipher == WLAN_CIPHER_SUITE_WEP104) && + key->keyidx != 0) + return -EOPNOTSUPP; + + /* + * Pairwise key will always be entry 0, but this + * could collide with a shared key on the same + * position... + */ + mask = TXRX_CSR0_KEY_ID.bit_mask; + + rt2500usb_register_read(rt2x00dev, TXRX_CSR0, ®); + curr_cipher = rt2x00_get_field16(reg, TXRX_CSR0_ALGORITHM); + reg &= mask; + + if (reg && reg == mask) + return -ENOSPC; + + reg = rt2x00_get_field16(reg, TXRX_CSR0_KEY_ID); + + key->hw_key_idx += reg ? ffz(reg) : 0; + /* + * Hardware requires that all keys use the same cipher + * (e.g. TKIP-only, AES-only, but not TKIP+AES). + * If this is not the first key, compare the cipher with the + * first one and fall back to SW crypto if not the same. + */ + if (key->hw_key_idx > 0 && crypto->cipher != curr_cipher) + return -EOPNOTSUPP; + + rt2500usb_register_multiwrite(rt2x00dev, KEY_ENTRY(key->hw_key_idx), + crypto->key, sizeof(crypto->key)); + + /* + * The driver does not support the IV/EIV generation + * in hardware. However it demands the data to be provided + * both separately as well as inside the frame. + * We already provided the CONFIG_CRYPTO_COPY_IV to rt2x00lib + * to ensure rt2x00lib will not strip the data from the + * frame after the copy, now we must tell mac80211 + * to generate the IV/EIV data. + */ + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC; + } + + /* + * TXRX_CSR0_KEY_ID contains only single-bit fields to indicate + * a particular key is valid. + */ + rt2500usb_register_read(rt2x00dev, TXRX_CSR0, ®); + rt2x00_set_field16(®, TXRX_CSR0_ALGORITHM, crypto->cipher); + rt2x00_set_field16(®, TXRX_CSR0_IV_OFFSET, IEEE80211_HEADER); + + mask = rt2x00_get_field16(reg, TXRX_CSR0_KEY_ID); + if (crypto->cmd == SET_KEY) + mask |= 1 << key->hw_key_idx; + else if (crypto->cmd == DISABLE_KEY) + mask &= ~(1 << key->hw_key_idx); + rt2x00_set_field16(®, TXRX_CSR0_KEY_ID, mask); + rt2500usb_register_write(rt2x00dev, TXRX_CSR0, reg); + + return 0; +} + +static void rt2500usb_config_filter(struct rt2x00_dev *rt2x00dev, + const unsigned int filter_flags) +{ + u16 reg; + + /* + * Start configuration steps. + * Note that the version error will always be dropped + * and broadcast frames will always be accepted since + * there is no filter for it at this time. + */ + rt2500usb_register_read(rt2x00dev, TXRX_CSR2, ®); + rt2x00_set_field16(®, TXRX_CSR2_DROP_CRC, + !(filter_flags & FIF_FCSFAIL)); + rt2x00_set_field16(®, TXRX_CSR2_DROP_PHYSICAL, + !(filter_flags & FIF_PLCPFAIL)); + rt2x00_set_field16(®, TXRX_CSR2_DROP_CONTROL, + !(filter_flags & FIF_CONTROL)); + rt2x00_set_field16(®, TXRX_CSR2_DROP_NOT_TO_ME, + !(filter_flags & FIF_PROMISC_IN_BSS)); + rt2x00_set_field16(®, TXRX_CSR2_DROP_TODS, + !(filter_flags & FIF_PROMISC_IN_BSS) && + !rt2x00dev->intf_ap_count); + rt2x00_set_field16(®, TXRX_CSR2_DROP_VERSION_ERROR, 1); + rt2x00_set_field16(®, TXRX_CSR2_DROP_MULTICAST, + !(filter_flags & FIF_ALLMULTI)); + rt2x00_set_field16(®, TXRX_CSR2_DROP_BROADCAST, 0); + rt2500usb_register_write(rt2x00dev, TXRX_CSR2, reg); +} + +static void rt2500usb_config_intf(struct rt2x00_dev *rt2x00dev, + struct rt2x00_intf *intf, + struct rt2x00intf_conf *conf, + const unsigned int flags) +{ + unsigned int bcn_preload; + u16 reg; + + if (flags & CONFIG_UPDATE_TYPE) { + /* + * Enable beacon config + */ + bcn_preload = PREAMBLE + GET_DURATION(IEEE80211_HEADER, 20); + rt2500usb_register_read(rt2x00dev, TXRX_CSR20, ®); + rt2x00_set_field16(®, TXRX_CSR20_OFFSET, bcn_preload >> 6); + rt2x00_set_field16(®, TXRX_CSR20_BCN_EXPECT_WINDOW, + 2 * (conf->type != NL80211_IFTYPE_STATION)); + rt2500usb_register_write(rt2x00dev, TXRX_CSR20, reg); + + /* + * Enable synchronisation. + */ + rt2500usb_register_read(rt2x00dev, TXRX_CSR18, ®); + rt2x00_set_field16(®, TXRX_CSR18_OFFSET, 0); + rt2500usb_register_write(rt2x00dev, TXRX_CSR18, reg); + + rt2500usb_register_read(rt2x00dev, TXRX_CSR19, ®); + rt2x00_set_field16(®, TXRX_CSR19_TSF_SYNC, conf->sync); + rt2500usb_register_write(rt2x00dev, TXRX_CSR19, reg); + } + + if (flags & CONFIG_UPDATE_MAC) + rt2500usb_register_multiwrite(rt2x00dev, MAC_CSR2, conf->mac, + (3 * sizeof(__le16))); + + if (flags & CONFIG_UPDATE_BSSID) + rt2500usb_register_multiwrite(rt2x00dev, MAC_CSR5, conf->bssid, + (3 * sizeof(__le16))); +} + +static void rt2500usb_config_erp(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_erp *erp, + u32 changed) +{ + u16 reg; + + if (changed & BSS_CHANGED_ERP_PREAMBLE) { + rt2500usb_register_read(rt2x00dev, TXRX_CSR10, ®); + rt2x00_set_field16(®, TXRX_CSR10_AUTORESPOND_PREAMBLE, + !!erp->short_preamble); + rt2500usb_register_write(rt2x00dev, TXRX_CSR10, reg); + } + + if (changed & BSS_CHANGED_BASIC_RATES) + rt2500usb_register_write(rt2x00dev, TXRX_CSR11, + erp->basic_rates); + + if (changed & BSS_CHANGED_BEACON_INT) { + rt2500usb_register_read(rt2x00dev, TXRX_CSR18, ®); + rt2x00_set_field16(®, TXRX_CSR18_INTERVAL, + erp->beacon_int * 4); + rt2500usb_register_write(rt2x00dev, TXRX_CSR18, reg); + } + + if (changed & BSS_CHANGED_ERP_SLOT) { + rt2500usb_register_write(rt2x00dev, MAC_CSR10, erp->slot_time); + rt2500usb_register_write(rt2x00dev, MAC_CSR11, erp->sifs); + rt2500usb_register_write(rt2x00dev, MAC_CSR12, erp->eifs); + } +} + +static void rt2500usb_config_ant(struct rt2x00_dev *rt2x00dev, + struct antenna_setup *ant) +{ + u8 r2; + u8 r14; + u16 csr5; + u16 csr6; + + /* + * We should never come here because rt2x00lib is supposed + * to catch this and send us the correct antenna explicitely. + */ + BUG_ON(ant->rx == ANTENNA_SW_DIVERSITY || + ant->tx == ANTENNA_SW_DIVERSITY); + + rt2500usb_bbp_read(rt2x00dev, 2, &r2); + rt2500usb_bbp_read(rt2x00dev, 14, &r14); + rt2500usb_register_read(rt2x00dev, PHY_CSR5, &csr5); + rt2500usb_register_read(rt2x00dev, PHY_CSR6, &csr6); + + /* + * Configure the TX antenna. + */ + switch (ant->tx) { + case ANTENNA_HW_DIVERSITY: + rt2x00_set_field8(&r2, BBP_R2_TX_ANTENNA, 1); + rt2x00_set_field16(&csr5, PHY_CSR5_CCK, 1); + rt2x00_set_field16(&csr6, PHY_CSR6_OFDM, 1); + break; + case ANTENNA_A: + rt2x00_set_field8(&r2, BBP_R2_TX_ANTENNA, 0); + rt2x00_set_field16(&csr5, PHY_CSR5_CCK, 0); + rt2x00_set_field16(&csr6, PHY_CSR6_OFDM, 0); + break; + case ANTENNA_B: + default: + rt2x00_set_field8(&r2, BBP_R2_TX_ANTENNA, 2); + rt2x00_set_field16(&csr5, PHY_CSR5_CCK, 2); + rt2x00_set_field16(&csr6, PHY_CSR6_OFDM, 2); + break; + } + + /* + * Configure the RX antenna. + */ + switch (ant->rx) { + case ANTENNA_HW_DIVERSITY: + rt2x00_set_field8(&r14, BBP_R14_RX_ANTENNA, 1); + break; + case ANTENNA_A: + rt2x00_set_field8(&r14, BBP_R14_RX_ANTENNA, 0); + break; + case ANTENNA_B: + default: + rt2x00_set_field8(&r14, BBP_R14_RX_ANTENNA, 2); + break; + } + + /* + * RT2525E and RT5222 need to flip TX I/Q + */ + if (rt2x00_rf(rt2x00dev, RF2525E) || rt2x00_rf(rt2x00dev, RF5222)) { + rt2x00_set_field8(&r2, BBP_R2_TX_IQ_FLIP, 1); + rt2x00_set_field16(&csr5, PHY_CSR5_CCK_FLIP, 1); + rt2x00_set_field16(&csr6, PHY_CSR6_OFDM_FLIP, 1); + + /* + * RT2525E does not need RX I/Q Flip. + */ + if (rt2x00_rf(rt2x00dev, RF2525E)) + rt2x00_set_field8(&r14, BBP_R14_RX_IQ_FLIP, 0); + } else { + rt2x00_set_field16(&csr5, PHY_CSR5_CCK_FLIP, 0); + rt2x00_set_field16(&csr6, PHY_CSR6_OFDM_FLIP, 0); + } + + rt2500usb_bbp_write(rt2x00dev, 2, r2); + rt2500usb_bbp_write(rt2x00dev, 14, r14); + rt2500usb_register_write(rt2x00dev, PHY_CSR5, csr5); + rt2500usb_register_write(rt2x00dev, PHY_CSR6, csr6); +} + +static void rt2500usb_config_channel(struct rt2x00_dev *rt2x00dev, + struct rf_channel *rf, const int txpower) +{ + /* + * Set TXpower. + */ + rt2x00_set_field32(&rf->rf3, RF3_TXPOWER, TXPOWER_TO_DEV(txpower)); + + /* + * For RT2525E we should first set the channel to half band higher. + */ + if (rt2x00_rf(rt2x00dev, RF2525E)) { + static const u32 vals[] = { + 0x000008aa, 0x000008ae, 0x000008ae, 0x000008b2, + 0x000008b2, 0x000008b6, 0x000008b6, 0x000008ba, + 0x000008ba, 0x000008be, 0x000008b7, 0x00000902, + 0x00000902, 0x00000906 + }; + + rt2500usb_rf_write(rt2x00dev, 2, vals[rf->channel - 1]); + if (rf->rf4) + rt2500usb_rf_write(rt2x00dev, 4, rf->rf4); + } + + rt2500usb_rf_write(rt2x00dev, 1, rf->rf1); + rt2500usb_rf_write(rt2x00dev, 2, rf->rf2); + rt2500usb_rf_write(rt2x00dev, 3, rf->rf3); + if (rf->rf4) + rt2500usb_rf_write(rt2x00dev, 4, rf->rf4); +} + +static void rt2500usb_config_txpower(struct rt2x00_dev *rt2x00dev, + const int txpower) +{ + u32 rf3; + + rt2x00_rf_read(rt2x00dev, 3, &rf3); + rt2x00_set_field32(&rf3, RF3_TXPOWER, TXPOWER_TO_DEV(txpower)); + rt2500usb_rf_write(rt2x00dev, 3, rf3); +} + +static void rt2500usb_config_ps(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf) +{ + enum dev_state state = + (libconf->conf->flags & IEEE80211_CONF_PS) ? + STATE_SLEEP : STATE_AWAKE; + u16 reg; + + if (state == STATE_SLEEP) { + rt2500usb_register_read(rt2x00dev, MAC_CSR18, ®); + rt2x00_set_field16(®, MAC_CSR18_DELAY_AFTER_BEACON, + rt2x00dev->beacon_int - 20); + rt2x00_set_field16(®, MAC_CSR18_BEACONS_BEFORE_WAKEUP, + libconf->conf->listen_interval - 1); + + /* We must first disable autowake before it can be enabled */ + rt2x00_set_field16(®, MAC_CSR18_AUTO_WAKE, 0); + rt2500usb_register_write(rt2x00dev, MAC_CSR18, reg); + + rt2x00_set_field16(®, MAC_CSR18_AUTO_WAKE, 1); + rt2500usb_register_write(rt2x00dev, MAC_CSR18, reg); + } else { + rt2500usb_register_read(rt2x00dev, MAC_CSR18, ®); + rt2x00_set_field16(®, MAC_CSR18_AUTO_WAKE, 0); + rt2500usb_register_write(rt2x00dev, MAC_CSR18, reg); + } + + rt2x00dev->ops->lib->set_device_state(rt2x00dev, state); +} + +static void rt2500usb_config(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf, + const unsigned int flags) +{ + if (flags & IEEE80211_CONF_CHANGE_CHANNEL) + rt2500usb_config_channel(rt2x00dev, &libconf->rf, + libconf->conf->power_level); + if ((flags & IEEE80211_CONF_CHANGE_POWER) && + !(flags & IEEE80211_CONF_CHANGE_CHANNEL)) + rt2500usb_config_txpower(rt2x00dev, + libconf->conf->power_level); + if (flags & IEEE80211_CONF_CHANGE_PS) + rt2500usb_config_ps(rt2x00dev, libconf); +} + +/* + * Link tuning + */ +static void rt2500usb_link_stats(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual) +{ + u16 reg; + + /* + * Update FCS error count from register. + */ + rt2500usb_register_read(rt2x00dev, STA_CSR0, ®); + qual->rx_failed = rt2x00_get_field16(reg, STA_CSR0_FCS_ERROR); + + /* + * Update False CCA count from register. + */ + rt2500usb_register_read(rt2x00dev, STA_CSR3, ®); + qual->false_cca = rt2x00_get_field16(reg, STA_CSR3_FALSE_CCA_ERROR); +} + +static void rt2500usb_reset_tuner(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual) +{ + u16 eeprom; + u16 value; + + rt2x00_eeprom_read(rt2x00dev, EEPROM_BBPTUNE_R24, &eeprom); + value = rt2x00_get_field16(eeprom, EEPROM_BBPTUNE_R24_LOW); + rt2500usb_bbp_write(rt2x00dev, 24, value); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_BBPTUNE_R25, &eeprom); + value = rt2x00_get_field16(eeprom, EEPROM_BBPTUNE_R25_LOW); + rt2500usb_bbp_write(rt2x00dev, 25, value); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_BBPTUNE_R61, &eeprom); + value = rt2x00_get_field16(eeprom, EEPROM_BBPTUNE_R61_LOW); + rt2500usb_bbp_write(rt2x00dev, 61, value); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_BBPTUNE_VGC, &eeprom); + value = rt2x00_get_field16(eeprom, EEPROM_BBPTUNE_VGCUPPER); + rt2500usb_bbp_write(rt2x00dev, 17, value); + + qual->vgc_level = value; +} + +/* + * Queue handlers. + */ +static void rt2500usb_start_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u16 reg; + + switch (queue->qid) { + case QID_RX: + rt2500usb_register_read(rt2x00dev, TXRX_CSR2, ®); + rt2x00_set_field16(®, TXRX_CSR2_DISABLE_RX, 0); + rt2500usb_register_write(rt2x00dev, TXRX_CSR2, reg); + break; + case QID_BEACON: + rt2500usb_register_read(rt2x00dev, TXRX_CSR19, ®); + rt2x00_set_field16(®, TXRX_CSR19_TSF_COUNT, 1); + rt2x00_set_field16(®, TXRX_CSR19_TBCN, 1); + rt2x00_set_field16(®, TXRX_CSR19_BEACON_GEN, 1); + rt2500usb_register_write(rt2x00dev, TXRX_CSR19, reg); + break; + default: + break; + } +} + +static void rt2500usb_stop_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u16 reg; + + switch (queue->qid) { + case QID_RX: + rt2500usb_register_read(rt2x00dev, TXRX_CSR2, ®); + rt2x00_set_field16(®, TXRX_CSR2_DISABLE_RX, 1); + rt2500usb_register_write(rt2x00dev, TXRX_CSR2, reg); + break; + case QID_BEACON: + rt2500usb_register_read(rt2x00dev, TXRX_CSR19, ®); + rt2x00_set_field16(®, TXRX_CSR19_TSF_COUNT, 0); + rt2x00_set_field16(®, TXRX_CSR19_TBCN, 0); + rt2x00_set_field16(®, TXRX_CSR19_BEACON_GEN, 0); + rt2500usb_register_write(rt2x00dev, TXRX_CSR19, reg); + break; + default: + break; + } +} + +/* + * Initialization functions. + */ +static int rt2500usb_init_registers(struct rt2x00_dev *rt2x00dev) +{ + u16 reg; + + rt2x00usb_vendor_request_sw(rt2x00dev, USB_DEVICE_MODE, 0x0001, + USB_MODE_TEST, REGISTER_TIMEOUT); + rt2x00usb_vendor_request_sw(rt2x00dev, USB_SINGLE_WRITE, 0x0308, + 0x00f0, REGISTER_TIMEOUT); + + rt2500usb_register_read(rt2x00dev, TXRX_CSR2, ®); + rt2x00_set_field16(®, TXRX_CSR2_DISABLE_RX, 1); + rt2500usb_register_write(rt2x00dev, TXRX_CSR2, reg); + + rt2500usb_register_write(rt2x00dev, MAC_CSR13, 0x1111); + rt2500usb_register_write(rt2x00dev, MAC_CSR14, 0x1e11); + + rt2500usb_register_read(rt2x00dev, MAC_CSR1, ®); + rt2x00_set_field16(®, MAC_CSR1_SOFT_RESET, 1); + rt2x00_set_field16(®, MAC_CSR1_BBP_RESET, 1); + rt2x00_set_field16(®, MAC_CSR1_HOST_READY, 0); + rt2500usb_register_write(rt2x00dev, MAC_CSR1, reg); + + rt2500usb_register_read(rt2x00dev, MAC_CSR1, ®); + rt2x00_set_field16(®, MAC_CSR1_SOFT_RESET, 0); + rt2x00_set_field16(®, MAC_CSR1_BBP_RESET, 0); + rt2x00_set_field16(®, MAC_CSR1_HOST_READY, 0); + rt2500usb_register_write(rt2x00dev, MAC_CSR1, reg); + + rt2500usb_register_read(rt2x00dev, TXRX_CSR5, ®); + rt2x00_set_field16(®, TXRX_CSR5_BBP_ID0, 13); + rt2x00_set_field16(®, TXRX_CSR5_BBP_ID0_VALID, 1); + rt2x00_set_field16(®, TXRX_CSR5_BBP_ID1, 12); + rt2x00_set_field16(®, TXRX_CSR5_BBP_ID1_VALID, 1); + rt2500usb_register_write(rt2x00dev, TXRX_CSR5, reg); + + rt2500usb_register_read(rt2x00dev, TXRX_CSR6, ®); + rt2x00_set_field16(®, TXRX_CSR6_BBP_ID0, 10); + rt2x00_set_field16(®, TXRX_CSR6_BBP_ID0_VALID, 1); + rt2x00_set_field16(®, TXRX_CSR6_BBP_ID1, 11); + rt2x00_set_field16(®, TXRX_CSR6_BBP_ID1_VALID, 1); + rt2500usb_register_write(rt2x00dev, TXRX_CSR6, reg); + + rt2500usb_register_read(rt2x00dev, TXRX_CSR7, ®); + rt2x00_set_field16(®, TXRX_CSR7_BBP_ID0, 7); + rt2x00_set_field16(®, TXRX_CSR7_BBP_ID0_VALID, 1); + rt2x00_set_field16(®, TXRX_CSR7_BBP_ID1, 6); + rt2x00_set_field16(®, TXRX_CSR7_BBP_ID1_VALID, 1); + rt2500usb_register_write(rt2x00dev, TXRX_CSR7, reg); + + rt2500usb_register_read(rt2x00dev, TXRX_CSR8, ®); + rt2x00_set_field16(®, TXRX_CSR8_BBP_ID0, 5); + rt2x00_set_field16(®, TXRX_CSR8_BBP_ID0_VALID, 1); + rt2x00_set_field16(®, TXRX_CSR8_BBP_ID1, 0); + rt2x00_set_field16(®, TXRX_CSR8_BBP_ID1_VALID, 0); + rt2500usb_register_write(rt2x00dev, TXRX_CSR8, reg); + + rt2500usb_register_read(rt2x00dev, TXRX_CSR19, ®); + rt2x00_set_field16(®, TXRX_CSR19_TSF_COUNT, 0); + rt2x00_set_field16(®, TXRX_CSR19_TSF_SYNC, 0); + rt2x00_set_field16(®, TXRX_CSR19_TBCN, 0); + rt2x00_set_field16(®, TXRX_CSR19_BEACON_GEN, 0); + rt2500usb_register_write(rt2x00dev, TXRX_CSR19, reg); + + rt2500usb_register_write(rt2x00dev, TXRX_CSR21, 0xe78f); + rt2500usb_register_write(rt2x00dev, MAC_CSR9, 0xff1d); + + if (rt2x00dev->ops->lib->set_device_state(rt2x00dev, STATE_AWAKE)) + return -EBUSY; + + rt2500usb_register_read(rt2x00dev, MAC_CSR1, ®); + rt2x00_set_field16(®, MAC_CSR1_SOFT_RESET, 0); + rt2x00_set_field16(®, MAC_CSR1_BBP_RESET, 0); + rt2x00_set_field16(®, MAC_CSR1_HOST_READY, 1); + rt2500usb_register_write(rt2x00dev, MAC_CSR1, reg); + + if (rt2x00_rev(rt2x00dev) >= RT2570_VERSION_C) { + rt2500usb_register_read(rt2x00dev, PHY_CSR2, ®); + rt2x00_set_field16(®, PHY_CSR2_LNA, 0); + } else { + reg = 0; + rt2x00_set_field16(®, PHY_CSR2_LNA, 1); + rt2x00_set_field16(®, PHY_CSR2_LNA_MODE, 3); + } + rt2500usb_register_write(rt2x00dev, PHY_CSR2, reg); + + rt2500usb_register_write(rt2x00dev, MAC_CSR11, 0x0002); + rt2500usb_register_write(rt2x00dev, MAC_CSR22, 0x0053); + rt2500usb_register_write(rt2x00dev, MAC_CSR15, 0x01ee); + rt2500usb_register_write(rt2x00dev, MAC_CSR16, 0x0000); + + rt2500usb_register_read(rt2x00dev, MAC_CSR8, ®); + rt2x00_set_field16(®, MAC_CSR8_MAX_FRAME_UNIT, + rt2x00dev->rx->data_size); + rt2500usb_register_write(rt2x00dev, MAC_CSR8, reg); + + rt2500usb_register_read(rt2x00dev, TXRX_CSR0, ®); + rt2x00_set_field16(®, TXRX_CSR0_ALGORITHM, CIPHER_NONE); + rt2x00_set_field16(®, TXRX_CSR0_IV_OFFSET, IEEE80211_HEADER); + rt2x00_set_field16(®, TXRX_CSR0_KEY_ID, 0); + rt2500usb_register_write(rt2x00dev, TXRX_CSR0, reg); + + rt2500usb_register_read(rt2x00dev, MAC_CSR18, ®); + rt2x00_set_field16(®, MAC_CSR18_DELAY_AFTER_BEACON, 90); + rt2500usb_register_write(rt2x00dev, MAC_CSR18, reg); + + rt2500usb_register_read(rt2x00dev, PHY_CSR4, ®); + rt2x00_set_field16(®, PHY_CSR4_LOW_RF_LE, 1); + rt2500usb_register_write(rt2x00dev, PHY_CSR4, reg); + + rt2500usb_register_read(rt2x00dev, TXRX_CSR1, ®); + rt2x00_set_field16(®, TXRX_CSR1_AUTO_SEQUENCE, 1); + rt2500usb_register_write(rt2x00dev, TXRX_CSR1, reg); + + return 0; +} + +static int rt2500usb_wait_bbp_ready(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i; + u8 value; + + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2500usb_bbp_read(rt2x00dev, 0, &value); + if ((value != 0xff) && (value != 0x00)) + return 0; + udelay(REGISTER_BUSY_DELAY); + } + + rt2x00_err(rt2x00dev, "BBP register access failed, aborting\n"); + return -EACCES; +} + +static int rt2500usb_init_bbp(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i; + u16 eeprom; + u8 value; + u8 reg_id; + + if (unlikely(rt2500usb_wait_bbp_ready(rt2x00dev))) + return -EACCES; + + rt2500usb_bbp_write(rt2x00dev, 3, 0x02); + rt2500usb_bbp_write(rt2x00dev, 4, 0x19); + rt2500usb_bbp_write(rt2x00dev, 14, 0x1c); + rt2500usb_bbp_write(rt2x00dev, 15, 0x30); + rt2500usb_bbp_write(rt2x00dev, 16, 0xac); + rt2500usb_bbp_write(rt2x00dev, 18, 0x18); + rt2500usb_bbp_write(rt2x00dev, 19, 0xff); + rt2500usb_bbp_write(rt2x00dev, 20, 0x1e); + rt2500usb_bbp_write(rt2x00dev, 21, 0x08); + rt2500usb_bbp_write(rt2x00dev, 22, 0x08); + rt2500usb_bbp_write(rt2x00dev, 23, 0x08); + rt2500usb_bbp_write(rt2x00dev, 24, 0x80); + rt2500usb_bbp_write(rt2x00dev, 25, 0x50); + rt2500usb_bbp_write(rt2x00dev, 26, 0x08); + rt2500usb_bbp_write(rt2x00dev, 27, 0x23); + rt2500usb_bbp_write(rt2x00dev, 30, 0x10); + rt2500usb_bbp_write(rt2x00dev, 31, 0x2b); + rt2500usb_bbp_write(rt2x00dev, 32, 0xb9); + rt2500usb_bbp_write(rt2x00dev, 34, 0x12); + rt2500usb_bbp_write(rt2x00dev, 35, 0x50); + rt2500usb_bbp_write(rt2x00dev, 39, 0xc4); + rt2500usb_bbp_write(rt2x00dev, 40, 0x02); + rt2500usb_bbp_write(rt2x00dev, 41, 0x60); + rt2500usb_bbp_write(rt2x00dev, 53, 0x10); + rt2500usb_bbp_write(rt2x00dev, 54, 0x18); + rt2500usb_bbp_write(rt2x00dev, 56, 0x08); + rt2500usb_bbp_write(rt2x00dev, 57, 0x10); + rt2500usb_bbp_write(rt2x00dev, 58, 0x08); + rt2500usb_bbp_write(rt2x00dev, 61, 0x60); + rt2500usb_bbp_write(rt2x00dev, 62, 0x10); + rt2500usb_bbp_write(rt2x00dev, 75, 0xff); + + for (i = 0; i < EEPROM_BBP_SIZE; i++) { + rt2x00_eeprom_read(rt2x00dev, EEPROM_BBP_START + i, &eeprom); + + if (eeprom != 0xffff && eeprom != 0x0000) { + reg_id = rt2x00_get_field16(eeprom, EEPROM_BBP_REG_ID); + value = rt2x00_get_field16(eeprom, EEPROM_BBP_VALUE); + rt2500usb_bbp_write(rt2x00dev, reg_id, value); + } + } + + return 0; +} + +/* + * Device state switch handlers. + */ +static int rt2500usb_enable_radio(struct rt2x00_dev *rt2x00dev) +{ + /* + * Initialize all registers. + */ + if (unlikely(rt2500usb_init_registers(rt2x00dev) || + rt2500usb_init_bbp(rt2x00dev))) + return -EIO; + + return 0; +} + +static void rt2500usb_disable_radio(struct rt2x00_dev *rt2x00dev) +{ + rt2500usb_register_write(rt2x00dev, MAC_CSR13, 0x2121); + rt2500usb_register_write(rt2x00dev, MAC_CSR14, 0x2121); + + /* + * Disable synchronisation. + */ + rt2500usb_register_write(rt2x00dev, TXRX_CSR19, 0); + + rt2x00usb_disable_radio(rt2x00dev); +} + +static int rt2500usb_set_state(struct rt2x00_dev *rt2x00dev, + enum dev_state state) +{ + u16 reg; + u16 reg2; + unsigned int i; + char put_to_sleep; + char bbp_state; + char rf_state; + + put_to_sleep = (state != STATE_AWAKE); + + reg = 0; + rt2x00_set_field16(®, MAC_CSR17_BBP_DESIRE_STATE, state); + rt2x00_set_field16(®, MAC_CSR17_RF_DESIRE_STATE, state); + rt2x00_set_field16(®, MAC_CSR17_PUT_TO_SLEEP, put_to_sleep); + rt2500usb_register_write(rt2x00dev, MAC_CSR17, reg); + rt2x00_set_field16(®, MAC_CSR17_SET_STATE, 1); + rt2500usb_register_write(rt2x00dev, MAC_CSR17, reg); + + /* + * Device is not guaranteed to be in the requested state yet. + * We must wait until the register indicates that the + * device has entered the correct state. + */ + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2500usb_register_read(rt2x00dev, MAC_CSR17, ®2); + bbp_state = rt2x00_get_field16(reg2, MAC_CSR17_BBP_CURR_STATE); + rf_state = rt2x00_get_field16(reg2, MAC_CSR17_RF_CURR_STATE); + if (bbp_state == state && rf_state == state) + return 0; + rt2500usb_register_write(rt2x00dev, MAC_CSR17, reg); + msleep(30); + } + + return -EBUSY; +} + +static int rt2500usb_set_device_state(struct rt2x00_dev *rt2x00dev, + enum dev_state state) +{ + int retval = 0; + + switch (state) { + case STATE_RADIO_ON: + retval = rt2500usb_enable_radio(rt2x00dev); + break; + case STATE_RADIO_OFF: + rt2500usb_disable_radio(rt2x00dev); + break; + case STATE_RADIO_IRQ_ON: + case STATE_RADIO_IRQ_OFF: + /* No support, but no error either */ + break; + case STATE_DEEP_SLEEP: + case STATE_SLEEP: + case STATE_STANDBY: + case STATE_AWAKE: + retval = rt2500usb_set_state(rt2x00dev, state); + break; + default: + retval = -ENOTSUPP; + break; + } + + if (unlikely(retval)) + rt2x00_err(rt2x00dev, "Device failed to enter state %d (%d)\n", + state, retval); + + return retval; +} + +/* + * TX descriptor initialization + */ +static void rt2500usb_write_tx_desc(struct queue_entry *entry, + struct txentry_desc *txdesc) +{ + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + __le32 *txd = (__le32 *) entry->skb->data; + u32 word; + + /* + * Start writing the descriptor words. + */ + rt2x00_desc_read(txd, 0, &word); + rt2x00_set_field32(&word, TXD_W0_RETRY_LIMIT, txdesc->retry_limit); + rt2x00_set_field32(&word, TXD_W0_MORE_FRAG, + test_bit(ENTRY_TXD_MORE_FRAG, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_ACK, + test_bit(ENTRY_TXD_ACK, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_TIMESTAMP, + test_bit(ENTRY_TXD_REQ_TIMESTAMP, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_OFDM, + (txdesc->rate_mode == RATE_MODE_OFDM)); + rt2x00_set_field32(&word, TXD_W0_NEW_SEQ, + test_bit(ENTRY_TXD_FIRST_FRAGMENT, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_IFS, txdesc->u.plcp.ifs); + rt2x00_set_field32(&word, TXD_W0_DATABYTE_COUNT, txdesc->length); + rt2x00_set_field32(&word, TXD_W0_CIPHER, !!txdesc->cipher); + rt2x00_set_field32(&word, TXD_W0_KEY_ID, txdesc->key_idx); + rt2x00_desc_write(txd, 0, word); + + rt2x00_desc_read(txd, 1, &word); + rt2x00_set_field32(&word, TXD_W1_IV_OFFSET, txdesc->iv_offset); + rt2x00_set_field32(&word, TXD_W1_AIFS, entry->queue->aifs); + rt2x00_set_field32(&word, TXD_W1_CWMIN, entry->queue->cw_min); + rt2x00_set_field32(&word, TXD_W1_CWMAX, entry->queue->cw_max); + rt2x00_desc_write(txd, 1, word); + + rt2x00_desc_read(txd, 2, &word); + rt2x00_set_field32(&word, TXD_W2_PLCP_SIGNAL, txdesc->u.plcp.signal); + rt2x00_set_field32(&word, TXD_W2_PLCP_SERVICE, txdesc->u.plcp.service); + rt2x00_set_field32(&word, TXD_W2_PLCP_LENGTH_LOW, + txdesc->u.plcp.length_low); + rt2x00_set_field32(&word, TXD_W2_PLCP_LENGTH_HIGH, + txdesc->u.plcp.length_high); + rt2x00_desc_write(txd, 2, word); + + if (test_bit(ENTRY_TXD_ENCRYPT, &txdesc->flags)) { + _rt2x00_desc_write(txd, 3, skbdesc->iv[0]); + _rt2x00_desc_write(txd, 4, skbdesc->iv[1]); + } + + /* + * Register descriptor details in skb frame descriptor. + */ + skbdesc->flags |= SKBDESC_DESC_IN_SKB; + skbdesc->desc = txd; + skbdesc->desc_len = TXD_DESC_SIZE; +} + +/* + * TX data initialization + */ +static void rt2500usb_beacondone(struct urb *urb); + +static void rt2500usb_write_beacon(struct queue_entry *entry, + struct txentry_desc *txdesc) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + struct usb_device *usb_dev = to_usb_device_intf(rt2x00dev->dev); + struct queue_entry_priv_usb_bcn *bcn_priv = entry->priv_data; + int pipe = usb_sndbulkpipe(usb_dev, entry->queue->usb_endpoint); + int length; + u16 reg, reg0; + + /* + * Disable beaconing while we are reloading the beacon data, + * otherwise we might be sending out invalid data. + */ + rt2500usb_register_read(rt2x00dev, TXRX_CSR19, ®); + rt2x00_set_field16(®, TXRX_CSR19_BEACON_GEN, 0); + rt2500usb_register_write(rt2x00dev, TXRX_CSR19, reg); + + /* + * Add space for the descriptor in front of the skb. + */ + skb_push(entry->skb, TXD_DESC_SIZE); + memset(entry->skb->data, 0, TXD_DESC_SIZE); + + /* + * Write the TX descriptor for the beacon. + */ + rt2500usb_write_tx_desc(entry, txdesc); + + /* + * Dump beacon to userspace through debugfs. + */ + rt2x00debug_dump_frame(rt2x00dev, DUMP_FRAME_BEACON, entry->skb); + + /* + * USB devices cannot blindly pass the skb->len as the + * length of the data to usb_fill_bulk_urb. Pass the skb + * to the driver to determine what the length should be. + */ + length = rt2x00dev->ops->lib->get_tx_data_len(entry); + + usb_fill_bulk_urb(bcn_priv->urb, usb_dev, pipe, + entry->skb->data, length, rt2500usb_beacondone, + entry); + + /* + * Second we need to create the guardian byte. + * We only need a single byte, so lets recycle + * the 'flags' field we are not using for beacons. + */ + bcn_priv->guardian_data = 0; + usb_fill_bulk_urb(bcn_priv->guardian_urb, usb_dev, pipe, + &bcn_priv->guardian_data, 1, rt2500usb_beacondone, + entry); + + /* + * Send out the guardian byte. + */ + usb_submit_urb(bcn_priv->guardian_urb, GFP_ATOMIC); + + /* + * Enable beaconing again. + */ + rt2x00_set_field16(®, TXRX_CSR19_TSF_COUNT, 1); + rt2x00_set_field16(®, TXRX_CSR19_TBCN, 1); + reg0 = reg; + rt2x00_set_field16(®, TXRX_CSR19_BEACON_GEN, 1); + /* + * Beacon generation will fail initially. + * To prevent this we need to change the TXRX_CSR19 + * register several times (reg0 is the same as reg + * except for TXRX_CSR19_BEACON_GEN, which is 0 in reg0 + * and 1 in reg). + */ + rt2500usb_register_write(rt2x00dev, TXRX_CSR19, reg); + rt2500usb_register_write(rt2x00dev, TXRX_CSR19, reg0); + rt2500usb_register_write(rt2x00dev, TXRX_CSR19, reg); + rt2500usb_register_write(rt2x00dev, TXRX_CSR19, reg0); + rt2500usb_register_write(rt2x00dev, TXRX_CSR19, reg); +} + +static int rt2500usb_get_tx_data_len(struct queue_entry *entry) +{ + int length; + + /* + * The length _must_ be a multiple of 2, + * but it must _not_ be a multiple of the USB packet size. + */ + length = roundup(entry->skb->len, 2); + length += (2 * !(length % entry->queue->usb_maxpacket)); + + return length; +} + +/* + * RX control handlers + */ +static void rt2500usb_fill_rxdone(struct queue_entry *entry, + struct rxdone_entry_desc *rxdesc) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + struct queue_entry_priv_usb *entry_priv = entry->priv_data; + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + __le32 *rxd = + (__le32 *)(entry->skb->data + + (entry_priv->urb->actual_length - + entry->queue->desc_size)); + u32 word0; + u32 word1; + + /* + * Copy descriptor to the skbdesc->desc buffer, making it safe from moving of + * frame data in rt2x00usb. + */ + memcpy(skbdesc->desc, rxd, skbdesc->desc_len); + rxd = (__le32 *)skbdesc->desc; + + /* + * It is now safe to read the descriptor on all architectures. + */ + rt2x00_desc_read(rxd, 0, &word0); + rt2x00_desc_read(rxd, 1, &word1); + + if (rt2x00_get_field32(word0, RXD_W0_CRC_ERROR)) + rxdesc->flags |= RX_FLAG_FAILED_FCS_CRC; + if (rt2x00_get_field32(word0, RXD_W0_PHYSICAL_ERROR)) + rxdesc->flags |= RX_FLAG_FAILED_PLCP_CRC; + + rxdesc->cipher = rt2x00_get_field32(word0, RXD_W0_CIPHER); + if (rt2x00_get_field32(word0, RXD_W0_CIPHER_ERROR)) + rxdesc->cipher_status = RX_CRYPTO_FAIL_KEY; + + if (rxdesc->cipher != CIPHER_NONE) { + _rt2x00_desc_read(rxd, 2, &rxdesc->iv[0]); + _rt2x00_desc_read(rxd, 3, &rxdesc->iv[1]); + rxdesc->dev_flags |= RXDONE_CRYPTO_IV; + + /* ICV is located at the end of frame */ + + rxdesc->flags |= RX_FLAG_MMIC_STRIPPED; + if (rxdesc->cipher_status == RX_CRYPTO_SUCCESS) + rxdesc->flags |= RX_FLAG_DECRYPTED; + else if (rxdesc->cipher_status == RX_CRYPTO_FAIL_MIC) + rxdesc->flags |= RX_FLAG_MMIC_ERROR; + } + + /* + * Obtain the status about this packet. + * When frame was received with an OFDM bitrate, + * the signal is the PLCP value. If it was received with + * a CCK bitrate the signal is the rate in 100kbit/s. + */ + rxdesc->signal = rt2x00_get_field32(word1, RXD_W1_SIGNAL); + rxdesc->rssi = + rt2x00_get_field32(word1, RXD_W1_RSSI) - rt2x00dev->rssi_offset; + rxdesc->size = rt2x00_get_field32(word0, RXD_W0_DATABYTE_COUNT); + + if (rt2x00_get_field32(word0, RXD_W0_OFDM)) + rxdesc->dev_flags |= RXDONE_SIGNAL_PLCP; + else + rxdesc->dev_flags |= RXDONE_SIGNAL_BITRATE; + if (rt2x00_get_field32(word0, RXD_W0_MY_BSS)) + rxdesc->dev_flags |= RXDONE_MY_BSS; + + /* + * Adjust the skb memory window to the frame boundaries. + */ + skb_trim(entry->skb, rxdesc->size); +} + +/* + * Interrupt functions. + */ +static void rt2500usb_beacondone(struct urb *urb) +{ + struct queue_entry *entry = (struct queue_entry *)urb->context; + struct queue_entry_priv_usb_bcn *bcn_priv = entry->priv_data; + + if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &entry->queue->rt2x00dev->flags)) + return; + + /* + * Check if this was the guardian beacon, + * if that was the case we need to send the real beacon now. + * Otherwise we should free the sk_buffer, the device + * should be doing the rest of the work now. + */ + if (bcn_priv->guardian_urb == urb) { + usb_submit_urb(bcn_priv->urb, GFP_ATOMIC); + } else if (bcn_priv->urb == urb) { + dev_kfree_skb(entry->skb); + entry->skb = NULL; + } +} + +/* + * Device probe functions. + */ +static int rt2500usb_validate_eeprom(struct rt2x00_dev *rt2x00dev) +{ + u16 word; + u8 *mac; + u8 bbp; + + rt2x00usb_eeprom_read(rt2x00dev, rt2x00dev->eeprom, EEPROM_SIZE); + + /* + * Start validation of the data that has been read. + */ + mac = rt2x00_eeprom_addr(rt2x00dev, EEPROM_MAC_ADDR_0); + if (!is_valid_ether_addr(mac)) { + eth_random_addr(mac); + rt2x00_eeprom_dbg(rt2x00dev, "MAC: %pM\n", mac); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_ANTENNA, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_ANTENNA_NUM, 2); + rt2x00_set_field16(&word, EEPROM_ANTENNA_TX_DEFAULT, + ANTENNA_SW_DIVERSITY); + rt2x00_set_field16(&word, EEPROM_ANTENNA_RX_DEFAULT, + ANTENNA_SW_DIVERSITY); + rt2x00_set_field16(&word, EEPROM_ANTENNA_LED_MODE, + LED_MODE_DEFAULT); + rt2x00_set_field16(&word, EEPROM_ANTENNA_DYN_TXAGC, 0); + rt2x00_set_field16(&word, EEPROM_ANTENNA_HARDWARE_RADIO, 0); + rt2x00_set_field16(&word, EEPROM_ANTENNA_RF_TYPE, RF2522); + rt2x00_eeprom_write(rt2x00dev, EEPROM_ANTENNA, word); + rt2x00_eeprom_dbg(rt2x00dev, "Antenna: 0x%04x\n", word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_NIC_CARDBUS_ACCEL, 0); + rt2x00_set_field16(&word, EEPROM_NIC_DYN_BBP_TUNE, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CCK_TX_POWER, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_NIC, word); + rt2x00_eeprom_dbg(rt2x00dev, "NIC: 0x%04x\n", word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_CALIBRATE_OFFSET, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_CALIBRATE_OFFSET_RSSI, + DEFAULT_RSSI_OFFSET); + rt2x00_eeprom_write(rt2x00dev, EEPROM_CALIBRATE_OFFSET, word); + rt2x00_eeprom_dbg(rt2x00dev, "Calibrate offset: 0x%04x\n", + word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_BBPTUNE, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_BBPTUNE_THRESHOLD, 45); + rt2x00_eeprom_write(rt2x00dev, EEPROM_BBPTUNE, word); + rt2x00_eeprom_dbg(rt2x00dev, "BBPtune: 0x%04x\n", word); + } + + /* + * Switch lower vgc bound to current BBP R17 value, + * lower the value a bit for better quality. + */ + rt2500usb_bbp_read(rt2x00dev, 17, &bbp); + bbp -= 6; + + rt2x00_eeprom_read(rt2x00dev, EEPROM_BBPTUNE_VGC, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_BBPTUNE_VGCUPPER, 0x40); + rt2x00_set_field16(&word, EEPROM_BBPTUNE_VGCLOWER, bbp); + rt2x00_eeprom_write(rt2x00dev, EEPROM_BBPTUNE_VGC, word); + rt2x00_eeprom_dbg(rt2x00dev, "BBPtune vgc: 0x%04x\n", word); + } else { + rt2x00_set_field16(&word, EEPROM_BBPTUNE_VGCLOWER, bbp); + rt2x00_eeprom_write(rt2x00dev, EEPROM_BBPTUNE_VGC, word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_BBPTUNE_R17, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_BBPTUNE_R17_LOW, 0x48); + rt2x00_set_field16(&word, EEPROM_BBPTUNE_R17_HIGH, 0x41); + rt2x00_eeprom_write(rt2x00dev, EEPROM_BBPTUNE_R17, word); + rt2x00_eeprom_dbg(rt2x00dev, "BBPtune r17: 0x%04x\n", word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_BBPTUNE_R24, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_BBPTUNE_R24_LOW, 0x40); + rt2x00_set_field16(&word, EEPROM_BBPTUNE_R24_HIGH, 0x80); + rt2x00_eeprom_write(rt2x00dev, EEPROM_BBPTUNE_R24, word); + rt2x00_eeprom_dbg(rt2x00dev, "BBPtune r24: 0x%04x\n", word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_BBPTUNE_R25, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_BBPTUNE_R25_LOW, 0x40); + rt2x00_set_field16(&word, EEPROM_BBPTUNE_R25_HIGH, 0x50); + rt2x00_eeprom_write(rt2x00dev, EEPROM_BBPTUNE_R25, word); + rt2x00_eeprom_dbg(rt2x00dev, "BBPtune r25: 0x%04x\n", word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_BBPTUNE_R61, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_BBPTUNE_R61_LOW, 0x60); + rt2x00_set_field16(&word, EEPROM_BBPTUNE_R61_HIGH, 0x6d); + rt2x00_eeprom_write(rt2x00dev, EEPROM_BBPTUNE_R61, word); + rt2x00_eeprom_dbg(rt2x00dev, "BBPtune r61: 0x%04x\n", word); + } + + return 0; +} + +static int rt2500usb_init_eeprom(struct rt2x00_dev *rt2x00dev) +{ + u16 reg; + u16 value; + u16 eeprom; + + /* + * Read EEPROM word for configuration. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_ANTENNA, &eeprom); + + /* + * Identify RF chipset. + */ + value = rt2x00_get_field16(eeprom, EEPROM_ANTENNA_RF_TYPE); + rt2500usb_register_read(rt2x00dev, MAC_CSR0, ®); + rt2x00_set_chip(rt2x00dev, RT2570, value, reg); + + if (((reg & 0xfff0) != 0) || ((reg & 0x0000000f) == 0)) { + rt2x00_err(rt2x00dev, "Invalid RT chipset detected\n"); + return -ENODEV; + } + + if (!rt2x00_rf(rt2x00dev, RF2522) && + !rt2x00_rf(rt2x00dev, RF2523) && + !rt2x00_rf(rt2x00dev, RF2524) && + !rt2x00_rf(rt2x00dev, RF2525) && + !rt2x00_rf(rt2x00dev, RF2525E) && + !rt2x00_rf(rt2x00dev, RF5222)) { + rt2x00_err(rt2x00dev, "Invalid RF chipset detected\n"); + return -ENODEV; + } + + /* + * Identify default antenna configuration. + */ + rt2x00dev->default_ant.tx = + rt2x00_get_field16(eeprom, EEPROM_ANTENNA_TX_DEFAULT); + rt2x00dev->default_ant.rx = + rt2x00_get_field16(eeprom, EEPROM_ANTENNA_RX_DEFAULT); + + /* + * When the eeprom indicates SW_DIVERSITY use HW_DIVERSITY instead. + * I am not 100% sure about this, but the legacy drivers do not + * indicate antenna swapping in software is required when + * diversity is enabled. + */ + if (rt2x00dev->default_ant.tx == ANTENNA_SW_DIVERSITY) + rt2x00dev->default_ant.tx = ANTENNA_HW_DIVERSITY; + if (rt2x00dev->default_ant.rx == ANTENNA_SW_DIVERSITY) + rt2x00dev->default_ant.rx = ANTENNA_HW_DIVERSITY; + + /* + * Store led mode, for correct led behaviour. + */ +#ifdef CPTCFG_RT2X00_LIB_LEDS + value = rt2x00_get_field16(eeprom, EEPROM_ANTENNA_LED_MODE); + + rt2500usb_init_led(rt2x00dev, &rt2x00dev->led_radio, LED_TYPE_RADIO); + if (value == LED_MODE_TXRX_ACTIVITY || + value == LED_MODE_DEFAULT || + value == LED_MODE_ASUS) + rt2500usb_init_led(rt2x00dev, &rt2x00dev->led_qual, + LED_TYPE_ACTIVITY); +#endif /* CPTCFG_RT2X00_LIB_LEDS */ + + /* + * Detect if this device has an hardware controlled radio. + */ + if (rt2x00_get_field16(eeprom, EEPROM_ANTENNA_HARDWARE_RADIO)) + __set_bit(CAPABILITY_HW_BUTTON, &rt2x00dev->cap_flags); + + /* + * Read the RSSI <-> dBm offset information. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_CALIBRATE_OFFSET, &eeprom); + rt2x00dev->rssi_offset = + rt2x00_get_field16(eeprom, EEPROM_CALIBRATE_OFFSET_RSSI); + + return 0; +} + +/* + * RF value list for RF2522 + * Supports: 2.4 GHz + */ +static const struct rf_channel rf_vals_bg_2522[] = { + { 1, 0x00002050, 0x000c1fda, 0x00000101, 0 }, + { 2, 0x00002050, 0x000c1fee, 0x00000101, 0 }, + { 3, 0x00002050, 0x000c2002, 0x00000101, 0 }, + { 4, 0x00002050, 0x000c2016, 0x00000101, 0 }, + { 5, 0x00002050, 0x000c202a, 0x00000101, 0 }, + { 6, 0x00002050, 0x000c203e, 0x00000101, 0 }, + { 7, 0x00002050, 0x000c2052, 0x00000101, 0 }, + { 8, 0x00002050, 0x000c2066, 0x00000101, 0 }, + { 9, 0x00002050, 0x000c207a, 0x00000101, 0 }, + { 10, 0x00002050, 0x000c208e, 0x00000101, 0 }, + { 11, 0x00002050, 0x000c20a2, 0x00000101, 0 }, + { 12, 0x00002050, 0x000c20b6, 0x00000101, 0 }, + { 13, 0x00002050, 0x000c20ca, 0x00000101, 0 }, + { 14, 0x00002050, 0x000c20fa, 0x00000101, 0 }, +}; + +/* + * RF value list for RF2523 + * Supports: 2.4 GHz + */ +static const struct rf_channel rf_vals_bg_2523[] = { + { 1, 0x00022010, 0x00000c9e, 0x000e0111, 0x00000a1b }, + { 2, 0x00022010, 0x00000ca2, 0x000e0111, 0x00000a1b }, + { 3, 0x00022010, 0x00000ca6, 0x000e0111, 0x00000a1b }, + { 4, 0x00022010, 0x00000caa, 0x000e0111, 0x00000a1b }, + { 5, 0x00022010, 0x00000cae, 0x000e0111, 0x00000a1b }, + { 6, 0x00022010, 0x00000cb2, 0x000e0111, 0x00000a1b }, + { 7, 0x00022010, 0x00000cb6, 0x000e0111, 0x00000a1b }, + { 8, 0x00022010, 0x00000cba, 0x000e0111, 0x00000a1b }, + { 9, 0x00022010, 0x00000cbe, 0x000e0111, 0x00000a1b }, + { 10, 0x00022010, 0x00000d02, 0x000e0111, 0x00000a1b }, + { 11, 0x00022010, 0x00000d06, 0x000e0111, 0x00000a1b }, + { 12, 0x00022010, 0x00000d0a, 0x000e0111, 0x00000a1b }, + { 13, 0x00022010, 0x00000d0e, 0x000e0111, 0x00000a1b }, + { 14, 0x00022010, 0x00000d1a, 0x000e0111, 0x00000a03 }, +}; + +/* + * RF value list for RF2524 + * Supports: 2.4 GHz + */ +static const struct rf_channel rf_vals_bg_2524[] = { + { 1, 0x00032020, 0x00000c9e, 0x00000101, 0x00000a1b }, + { 2, 0x00032020, 0x00000ca2, 0x00000101, 0x00000a1b }, + { 3, 0x00032020, 0x00000ca6, 0x00000101, 0x00000a1b }, + { 4, 0x00032020, 0x00000caa, 0x00000101, 0x00000a1b }, + { 5, 0x00032020, 0x00000cae, 0x00000101, 0x00000a1b }, + { 6, 0x00032020, 0x00000cb2, 0x00000101, 0x00000a1b }, + { 7, 0x00032020, 0x00000cb6, 0x00000101, 0x00000a1b }, + { 8, 0x00032020, 0x00000cba, 0x00000101, 0x00000a1b }, + { 9, 0x00032020, 0x00000cbe, 0x00000101, 0x00000a1b }, + { 10, 0x00032020, 0x00000d02, 0x00000101, 0x00000a1b }, + { 11, 0x00032020, 0x00000d06, 0x00000101, 0x00000a1b }, + { 12, 0x00032020, 0x00000d0a, 0x00000101, 0x00000a1b }, + { 13, 0x00032020, 0x00000d0e, 0x00000101, 0x00000a1b }, + { 14, 0x00032020, 0x00000d1a, 0x00000101, 0x00000a03 }, +}; + +/* + * RF value list for RF2525 + * Supports: 2.4 GHz + */ +static const struct rf_channel rf_vals_bg_2525[] = { + { 1, 0x00022020, 0x00080c9e, 0x00060111, 0x00000a1b }, + { 2, 0x00022020, 0x00080ca2, 0x00060111, 0x00000a1b }, + { 3, 0x00022020, 0x00080ca6, 0x00060111, 0x00000a1b }, + { 4, 0x00022020, 0x00080caa, 0x00060111, 0x00000a1b }, + { 5, 0x00022020, 0x00080cae, 0x00060111, 0x00000a1b }, + { 6, 0x00022020, 0x00080cb2, 0x00060111, 0x00000a1b }, + { 7, 0x00022020, 0x00080cb6, 0x00060111, 0x00000a1b }, + { 8, 0x00022020, 0x00080cba, 0x00060111, 0x00000a1b }, + { 9, 0x00022020, 0x00080cbe, 0x00060111, 0x00000a1b }, + { 10, 0x00022020, 0x00080d02, 0x00060111, 0x00000a1b }, + { 11, 0x00022020, 0x00080d06, 0x00060111, 0x00000a1b }, + { 12, 0x00022020, 0x00080d0a, 0x00060111, 0x00000a1b }, + { 13, 0x00022020, 0x00080d0e, 0x00060111, 0x00000a1b }, + { 14, 0x00022020, 0x00080d1a, 0x00060111, 0x00000a03 }, +}; + +/* + * RF value list for RF2525e + * Supports: 2.4 GHz + */ +static const struct rf_channel rf_vals_bg_2525e[] = { + { 1, 0x00022010, 0x0000089a, 0x00060111, 0x00000e1b }, + { 2, 0x00022010, 0x0000089e, 0x00060111, 0x00000e07 }, + { 3, 0x00022010, 0x0000089e, 0x00060111, 0x00000e1b }, + { 4, 0x00022010, 0x000008a2, 0x00060111, 0x00000e07 }, + { 5, 0x00022010, 0x000008a2, 0x00060111, 0x00000e1b }, + { 6, 0x00022010, 0x000008a6, 0x00060111, 0x00000e07 }, + { 7, 0x00022010, 0x000008a6, 0x00060111, 0x00000e1b }, + { 8, 0x00022010, 0x000008aa, 0x00060111, 0x00000e07 }, + { 9, 0x00022010, 0x000008aa, 0x00060111, 0x00000e1b }, + { 10, 0x00022010, 0x000008ae, 0x00060111, 0x00000e07 }, + { 11, 0x00022010, 0x000008ae, 0x00060111, 0x00000e1b }, + { 12, 0x00022010, 0x000008b2, 0x00060111, 0x00000e07 }, + { 13, 0x00022010, 0x000008b2, 0x00060111, 0x00000e1b }, + { 14, 0x00022010, 0x000008b6, 0x00060111, 0x00000e23 }, +}; + +/* + * RF value list for RF5222 + * Supports: 2.4 GHz & 5.2 GHz + */ +static const struct rf_channel rf_vals_5222[] = { + { 1, 0x00022020, 0x00001136, 0x00000101, 0x00000a0b }, + { 2, 0x00022020, 0x0000113a, 0x00000101, 0x00000a0b }, + { 3, 0x00022020, 0x0000113e, 0x00000101, 0x00000a0b }, + { 4, 0x00022020, 0x00001182, 0x00000101, 0x00000a0b }, + { 5, 0x00022020, 0x00001186, 0x00000101, 0x00000a0b }, + { 6, 0x00022020, 0x0000118a, 0x00000101, 0x00000a0b }, + { 7, 0x00022020, 0x0000118e, 0x00000101, 0x00000a0b }, + { 8, 0x00022020, 0x00001192, 0x00000101, 0x00000a0b }, + { 9, 0x00022020, 0x00001196, 0x00000101, 0x00000a0b }, + { 10, 0x00022020, 0x0000119a, 0x00000101, 0x00000a0b }, + { 11, 0x00022020, 0x0000119e, 0x00000101, 0x00000a0b }, + { 12, 0x00022020, 0x000011a2, 0x00000101, 0x00000a0b }, + { 13, 0x00022020, 0x000011a6, 0x00000101, 0x00000a0b }, + { 14, 0x00022020, 0x000011ae, 0x00000101, 0x00000a1b }, + + /* 802.11 UNI / HyperLan 2 */ + { 36, 0x00022010, 0x00018896, 0x00000101, 0x00000a1f }, + { 40, 0x00022010, 0x0001889a, 0x00000101, 0x00000a1f }, + { 44, 0x00022010, 0x0001889e, 0x00000101, 0x00000a1f }, + { 48, 0x00022010, 0x000188a2, 0x00000101, 0x00000a1f }, + { 52, 0x00022010, 0x000188a6, 0x00000101, 0x00000a1f }, + { 66, 0x00022010, 0x000188aa, 0x00000101, 0x00000a1f }, + { 60, 0x00022010, 0x000188ae, 0x00000101, 0x00000a1f }, + { 64, 0x00022010, 0x000188b2, 0x00000101, 0x00000a1f }, + + /* 802.11 HyperLan 2 */ + { 100, 0x00022010, 0x00008802, 0x00000101, 0x00000a0f }, + { 104, 0x00022010, 0x00008806, 0x00000101, 0x00000a0f }, + { 108, 0x00022010, 0x0000880a, 0x00000101, 0x00000a0f }, + { 112, 0x00022010, 0x0000880e, 0x00000101, 0x00000a0f }, + { 116, 0x00022010, 0x00008812, 0x00000101, 0x00000a0f }, + { 120, 0x00022010, 0x00008816, 0x00000101, 0x00000a0f }, + { 124, 0x00022010, 0x0000881a, 0x00000101, 0x00000a0f }, + { 128, 0x00022010, 0x0000881e, 0x00000101, 0x00000a0f }, + { 132, 0x00022010, 0x00008822, 0x00000101, 0x00000a0f }, + { 136, 0x00022010, 0x00008826, 0x00000101, 0x00000a0f }, + + /* 802.11 UNII */ + { 140, 0x00022010, 0x0000882a, 0x00000101, 0x00000a0f }, + { 149, 0x00022020, 0x000090a6, 0x00000101, 0x00000a07 }, + { 153, 0x00022020, 0x000090ae, 0x00000101, 0x00000a07 }, + { 157, 0x00022020, 0x000090b6, 0x00000101, 0x00000a07 }, + { 161, 0x00022020, 0x000090be, 0x00000101, 0x00000a07 }, +}; + +static int rt2500usb_probe_hw_mode(struct rt2x00_dev *rt2x00dev) +{ + struct hw_mode_spec *spec = &rt2x00dev->spec; + struct channel_info *info; + char *tx_power; + unsigned int i; + + /* + * Initialize all hw fields. + * + * Don't set IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING unless we are + * capable of sending the buffered frames out after the DTIM + * transmission using rt2x00lib_beacondone. This will send out + * multicast and broadcast traffic immediately instead of buffering it + * infinitly and thus dropping it after some time. + */ + rt2x00dev->hw->flags = + IEEE80211_HW_RX_INCLUDES_FCS | + IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_PS_NULLFUNC_STACK; + + SET_IEEE80211_DEV(rt2x00dev->hw, rt2x00dev->dev); + SET_IEEE80211_PERM_ADDR(rt2x00dev->hw, + rt2x00_eeprom_addr(rt2x00dev, + EEPROM_MAC_ADDR_0)); + + /* + * Initialize hw_mode information. + */ + spec->supported_bands = SUPPORT_BAND_2GHZ; + spec->supported_rates = SUPPORT_RATE_CCK | SUPPORT_RATE_OFDM; + + if (rt2x00_rf(rt2x00dev, RF2522)) { + spec->num_channels = ARRAY_SIZE(rf_vals_bg_2522); + spec->channels = rf_vals_bg_2522; + } else if (rt2x00_rf(rt2x00dev, RF2523)) { + spec->num_channels = ARRAY_SIZE(rf_vals_bg_2523); + spec->channels = rf_vals_bg_2523; + } else if (rt2x00_rf(rt2x00dev, RF2524)) { + spec->num_channels = ARRAY_SIZE(rf_vals_bg_2524); + spec->channels = rf_vals_bg_2524; + } else if (rt2x00_rf(rt2x00dev, RF2525)) { + spec->num_channels = ARRAY_SIZE(rf_vals_bg_2525); + spec->channels = rf_vals_bg_2525; + } else if (rt2x00_rf(rt2x00dev, RF2525E)) { + spec->num_channels = ARRAY_SIZE(rf_vals_bg_2525e); + spec->channels = rf_vals_bg_2525e; + } else if (rt2x00_rf(rt2x00dev, RF5222)) { + spec->supported_bands |= SUPPORT_BAND_5GHZ; + spec->num_channels = ARRAY_SIZE(rf_vals_5222); + spec->channels = rf_vals_5222; + } + + /* + * Create channel information array + */ + info = kcalloc(spec->num_channels, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + spec->channels_info = info; + + tx_power = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_START); + for (i = 0; i < 14; i++) { + info[i].max_power = MAX_TXPOWER; + info[i].default_power1 = TXPOWER_FROM_DEV(tx_power[i]); + } + + if (spec->num_channels > 14) { + for (i = 14; i < spec->num_channels; i++) { + info[i].max_power = MAX_TXPOWER; + info[i].default_power1 = DEFAULT_TXPOWER; + } + } + + return 0; +} + +static int rt2500usb_probe_hw(struct rt2x00_dev *rt2x00dev) +{ + int retval; + u16 reg; + + /* + * Allocate eeprom data. + */ + retval = rt2500usb_validate_eeprom(rt2x00dev); + if (retval) + return retval; + + retval = rt2500usb_init_eeprom(rt2x00dev); + if (retval) + return retval; + + /* + * Enable rfkill polling by setting GPIO direction of the + * rfkill switch GPIO pin correctly. + */ + rt2500usb_register_read(rt2x00dev, MAC_CSR19, ®); + rt2x00_set_field16(®, MAC_CSR19_DIR0, 0); + rt2500usb_register_write(rt2x00dev, MAC_CSR19, reg); + + /* + * Initialize hw specifications. + */ + retval = rt2500usb_probe_hw_mode(rt2x00dev); + if (retval) + return retval; + + /* + * This device requires the atim queue + */ + __set_bit(REQUIRE_ATIM_QUEUE, &rt2x00dev->cap_flags); + __set_bit(REQUIRE_BEACON_GUARD, &rt2x00dev->cap_flags); + if (!modparam_nohwcrypt) { + __set_bit(CAPABILITY_HW_CRYPTO, &rt2x00dev->cap_flags); + __set_bit(REQUIRE_COPY_IV, &rt2x00dev->cap_flags); + } + __set_bit(REQUIRE_SW_SEQNO, &rt2x00dev->cap_flags); + __set_bit(REQUIRE_PS_AUTOWAKE, &rt2x00dev->cap_flags); + + /* + * Set the rssi offset. + */ + rt2x00dev->rssi_offset = DEFAULT_RSSI_OFFSET; + + return 0; +} + +static const struct ieee80211_ops rt2500usb_mac80211_ops = { + .tx = rt2x00mac_tx, + .start = rt2x00mac_start, + .stop = rt2x00mac_stop, + .add_interface = rt2x00mac_add_interface, + .remove_interface = rt2x00mac_remove_interface, + .config = rt2x00mac_config, + .configure_filter = rt2x00mac_configure_filter, + .set_tim = rt2x00mac_set_tim, + .set_key = rt2x00mac_set_key, + .sw_scan_start = rt2x00mac_sw_scan_start, + .sw_scan_complete = rt2x00mac_sw_scan_complete, + .get_stats = rt2x00mac_get_stats, + .bss_info_changed = rt2x00mac_bss_info_changed, + .conf_tx = rt2x00mac_conf_tx, + .rfkill_poll = rt2x00mac_rfkill_poll, + .flush = rt2x00mac_flush, + .set_antenna = rt2x00mac_set_antenna, + .get_antenna = rt2x00mac_get_antenna, + .get_ringparam = rt2x00mac_get_ringparam, + .tx_frames_pending = rt2x00mac_tx_frames_pending, +}; + +static const struct rt2x00lib_ops rt2500usb_rt2x00_ops = { + .probe_hw = rt2500usb_probe_hw, + .initialize = rt2x00usb_initialize, + .uninitialize = rt2x00usb_uninitialize, + .clear_entry = rt2x00usb_clear_entry, + .set_device_state = rt2500usb_set_device_state, + .rfkill_poll = rt2500usb_rfkill_poll, + .link_stats = rt2500usb_link_stats, + .reset_tuner = rt2500usb_reset_tuner, + .watchdog = rt2x00usb_watchdog, + .start_queue = rt2500usb_start_queue, + .kick_queue = rt2x00usb_kick_queue, + .stop_queue = rt2500usb_stop_queue, + .flush_queue = rt2x00usb_flush_queue, + .write_tx_desc = rt2500usb_write_tx_desc, + .write_beacon = rt2500usb_write_beacon, + .get_tx_data_len = rt2500usb_get_tx_data_len, + .fill_rxdone = rt2500usb_fill_rxdone, + .config_shared_key = rt2500usb_config_key, + .config_pairwise_key = rt2500usb_config_key, + .config_filter = rt2500usb_config_filter, + .config_intf = rt2500usb_config_intf, + .config_erp = rt2500usb_config_erp, + .config_ant = rt2500usb_config_ant, + .config = rt2500usb_config, +}; + +static const struct data_queue_desc rt2500usb_queue_rx = { + .entry_num = 32, + .data_size = DATA_FRAME_SIZE, + .desc_size = RXD_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_usb), +}; + +static const struct data_queue_desc rt2500usb_queue_tx = { + .entry_num = 32, + .data_size = DATA_FRAME_SIZE, + .desc_size = TXD_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_usb), +}; + +static const struct data_queue_desc rt2500usb_queue_bcn = { + .entry_num = 1, + .data_size = MGMT_FRAME_SIZE, + .desc_size = TXD_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_usb_bcn), +}; + +static const struct data_queue_desc rt2500usb_queue_atim = { + .entry_num = 8, + .data_size = DATA_FRAME_SIZE, + .desc_size = TXD_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_usb), +}; + +static const struct rt2x00_ops rt2500usb_ops = { + .name = KBUILD_MODNAME, + .max_ap_intf = 1, + .eeprom_size = EEPROM_SIZE, + .rf_size = RF_SIZE, + .tx_queues = NUM_TX_QUEUES, + .extra_tx_headroom = TXD_DESC_SIZE, + .rx = &rt2500usb_queue_rx, + .tx = &rt2500usb_queue_tx, + .bcn = &rt2500usb_queue_bcn, + .atim = &rt2500usb_queue_atim, + .lib = &rt2500usb_rt2x00_ops, + .hw = &rt2500usb_mac80211_ops, +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS + .debugfs = &rt2500usb_rt2x00debug, +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ +}; + +/* + * rt2500usb module information. + */ +static struct usb_device_id rt2500usb_device_table[] = { + /* ASUS */ + { USB_DEVICE(0x0b05, 0x1706) }, + { USB_DEVICE(0x0b05, 0x1707) }, + /* Belkin */ + { USB_DEVICE(0x050d, 0x7050) }, /* FCC ID: K7SF5D7050A ver. 2.x */ + { USB_DEVICE(0x050d, 0x7051) }, + /* Cisco Systems */ + { USB_DEVICE(0x13b1, 0x000d) }, + { USB_DEVICE(0x13b1, 0x0011) }, + { USB_DEVICE(0x13b1, 0x001a) }, + /* Conceptronic */ + { USB_DEVICE(0x14b2, 0x3c02) }, + /* D-LINK */ + { USB_DEVICE(0x2001, 0x3c00) }, + /* Gigabyte */ + { USB_DEVICE(0x1044, 0x8001) }, + { USB_DEVICE(0x1044, 0x8007) }, + /* Hercules */ + { USB_DEVICE(0x06f8, 0xe000) }, + /* Melco */ + { USB_DEVICE(0x0411, 0x005e) }, + { USB_DEVICE(0x0411, 0x0066) }, + { USB_DEVICE(0x0411, 0x0067) }, + { USB_DEVICE(0x0411, 0x008b) }, + { USB_DEVICE(0x0411, 0x0097) }, + /* MSI */ + { USB_DEVICE(0x0db0, 0x6861) }, + { USB_DEVICE(0x0db0, 0x6865) }, + { USB_DEVICE(0x0db0, 0x6869) }, + /* Ralink */ + { USB_DEVICE(0x148f, 0x1706) }, + { USB_DEVICE(0x148f, 0x2570) }, + { USB_DEVICE(0x148f, 0x9020) }, + /* Sagem */ + { USB_DEVICE(0x079b, 0x004b) }, + /* Siemens */ + { USB_DEVICE(0x0681, 0x3c06) }, + /* SMC */ + { USB_DEVICE(0x0707, 0xee13) }, + /* Spairon */ + { USB_DEVICE(0x114b, 0x0110) }, + /* SURECOM */ + { USB_DEVICE(0x0769, 0x11f3) }, + /* Trust */ + { USB_DEVICE(0x0eb0, 0x9020) }, + /* VTech */ + { USB_DEVICE(0x0f88, 0x3012) }, + /* Zinwell */ + { USB_DEVICE(0x5a57, 0x0260) }, + { 0, } +}; + +MODULE_AUTHOR(DRV_PROJECT); +MODULE_VERSION(DRV_VERSION); +MODULE_DESCRIPTION("Ralink RT2500 USB Wireless LAN driver."); +MODULE_SUPPORTED_DEVICE("Ralink RT2570 USB chipset based cards"); +MODULE_DEVICE_TABLE(usb, rt2500usb_device_table); +MODULE_LICENSE("GPL"); + +static int rt2500usb_probe(struct usb_interface *usb_intf, + const struct usb_device_id *id) +{ + return rt2x00usb_probe(usb_intf, &rt2500usb_ops); +} + +static struct usb_driver rt2500usb_driver = { + .name = KBUILD_MODNAME, + .id_table = rt2500usb_device_table, + .probe = rt2500usb_probe, + .disconnect = rt2x00usb_disconnect, + .suspend = rt2x00usb_suspend, + .resume = rt2x00usb_resume, + .reset_resume = rt2x00usb_resume, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +module_usb_driver(rt2500usb_driver); diff --git a/drivers/net/wireless/rt2x00/rt2500usb.h b/drivers/net/wireless/rt2x00/rt2500usb.h new file mode 100644 index 0000000..1b91a4c --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2500usb.h @@ -0,0 +1,857 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2500usb + Abstract: Data structures and registers for the rt2500usb module. + Supported chipsets: RT2570. + */ + +#ifndef RT2500USB_H +#define RT2500USB_H + +/* + * RF chip defines. + */ +#define RF2522 0x0000 +#define RF2523 0x0001 +#define RF2524 0x0002 +#define RF2525 0x0003 +#define RF2525E 0x0005 +#define RF5222 0x0010 + +/* + * RT2570 version + */ +#define RT2570_VERSION_B 2 +#define RT2570_VERSION_C 3 +#define RT2570_VERSION_D 4 + +/* + * Signal information. + * Default offset is required for RSSI <-> dBm conversion. + */ +#define DEFAULT_RSSI_OFFSET 120 + +/* + * Register layout information. + */ +#define CSR_REG_BASE 0x0400 +#define CSR_REG_SIZE 0x0100 +#define EEPROM_BASE 0x0000 +#define EEPROM_SIZE 0x006a +#define BBP_BASE 0x0000 +#define BBP_SIZE 0x0060 +#define RF_BASE 0x0004 +#define RF_SIZE 0x0010 + +/* + * Number of TX queues. + */ +#define NUM_TX_QUEUES 2 + +/* + * Control/Status Registers(CSR). + * Some values are set in TU, whereas 1 TU == 1024 us. + */ + +/* + * MAC_CSR0: ASIC revision number. + */ +#define MAC_CSR0 0x0400 + +/* + * MAC_CSR1: System control. + * SOFT_RESET: Software reset, 1: reset, 0: normal. + * BBP_RESET: Hardware reset, 1: reset, 0, release. + * HOST_READY: Host ready after initialization. + */ +#define MAC_CSR1 0x0402 +#define MAC_CSR1_SOFT_RESET FIELD16(0x00000001) +#define MAC_CSR1_BBP_RESET FIELD16(0x00000002) +#define MAC_CSR1_HOST_READY FIELD16(0x00000004) + +/* + * MAC_CSR2: STA MAC register 0. + */ +#define MAC_CSR2 0x0404 +#define MAC_CSR2_BYTE0 FIELD16(0x00ff) +#define MAC_CSR2_BYTE1 FIELD16(0xff00) + +/* + * MAC_CSR3: STA MAC register 1. + */ +#define MAC_CSR3 0x0406 +#define MAC_CSR3_BYTE2 FIELD16(0x00ff) +#define MAC_CSR3_BYTE3 FIELD16(0xff00) + +/* + * MAC_CSR4: STA MAC register 2. + */ +#define MAC_CSR4 0X0408 +#define MAC_CSR4_BYTE4 FIELD16(0x00ff) +#define MAC_CSR4_BYTE5 FIELD16(0xff00) + +/* + * MAC_CSR5: BSSID register 0. + */ +#define MAC_CSR5 0x040a +#define MAC_CSR5_BYTE0 FIELD16(0x00ff) +#define MAC_CSR5_BYTE1 FIELD16(0xff00) + +/* + * MAC_CSR6: BSSID register 1. + */ +#define MAC_CSR6 0x040c +#define MAC_CSR6_BYTE2 FIELD16(0x00ff) +#define MAC_CSR6_BYTE3 FIELD16(0xff00) + +/* + * MAC_CSR7: BSSID register 2. + */ +#define MAC_CSR7 0x040e +#define MAC_CSR7_BYTE4 FIELD16(0x00ff) +#define MAC_CSR7_BYTE5 FIELD16(0xff00) + +/* + * MAC_CSR8: Max frame length. + */ +#define MAC_CSR8 0x0410 +#define MAC_CSR8_MAX_FRAME_UNIT FIELD16(0x0fff) + +/* + * Misc MAC_CSR registers. + * MAC_CSR9: Timer control. + * MAC_CSR10: Slot time. + * MAC_CSR11: SIFS. + * MAC_CSR12: EIFS. + * MAC_CSR13: Power mode0. + * MAC_CSR14: Power mode1. + * MAC_CSR15: Power saving transition0 + * MAC_CSR16: Power saving transition1 + */ +#define MAC_CSR9 0x0412 +#define MAC_CSR10 0x0414 +#define MAC_CSR11 0x0416 +#define MAC_CSR12 0x0418 +#define MAC_CSR13 0x041a +#define MAC_CSR14 0x041c +#define MAC_CSR15 0x041e +#define MAC_CSR16 0x0420 + +/* + * MAC_CSR17: Manual power control / status register. + * Allowed state: 0 deep_sleep, 1: sleep, 2: standby, 3: awake. + * SET_STATE: Set state. Write 1 to trigger, self cleared. + * BBP_DESIRE_STATE: BBP desired state. + * RF_DESIRE_STATE: RF desired state. + * BBP_CURRENT_STATE: BBP current state. + * RF_CURRENT_STATE: RF current state. + * PUT_TO_SLEEP: Put to sleep. Write 1 to trigger, self cleared. + */ +#define MAC_CSR17 0x0422 +#define MAC_CSR17_SET_STATE FIELD16(0x0001) +#define MAC_CSR17_BBP_DESIRE_STATE FIELD16(0x0006) +#define MAC_CSR17_RF_DESIRE_STATE FIELD16(0x0018) +#define MAC_CSR17_BBP_CURR_STATE FIELD16(0x0060) +#define MAC_CSR17_RF_CURR_STATE FIELD16(0x0180) +#define MAC_CSR17_PUT_TO_SLEEP FIELD16(0x0200) + +/* + * MAC_CSR18: Wakeup timer register. + * DELAY_AFTER_BEACON: Delay after Tbcn expired in units of 1/16 TU. + * BEACONS_BEFORE_WAKEUP: Number of beacon before wakeup. + * AUTO_WAKE: Enable auto wakeup / sleep mechanism. + */ +#define MAC_CSR18 0x0424 +#define MAC_CSR18_DELAY_AFTER_BEACON FIELD16(0x00ff) +#define MAC_CSR18_BEACONS_BEFORE_WAKEUP FIELD16(0x7f00) +#define MAC_CSR18_AUTO_WAKE FIELD16(0x8000) + +/* + * MAC_CSR19: GPIO control register. + * MAC_CSR19_VALx: GPIO value + * MAC_CSR19_DIRx: GPIO direction: 0 = input; 1 = output + */ +#define MAC_CSR19 0x0426 +#define MAC_CSR19_VAL0 FIELD16(0x0001) +#define MAC_CSR19_VAL1 FIELD16(0x0002) +#define MAC_CSR19_VAL2 FIELD16(0x0004) +#define MAC_CSR19_VAL3 FIELD16(0x0008) +#define MAC_CSR19_VAL4 FIELD16(0x0010) +#define MAC_CSR19_VAL5 FIELD16(0x0020) +#define MAC_CSR19_VAL6 FIELD16(0x0040) +#define MAC_CSR19_VAL7 FIELD16(0x0080) +#define MAC_CSR19_DIR0 FIELD16(0x0100) +#define MAC_CSR19_DIR1 FIELD16(0x0200) +#define MAC_CSR19_DIR2 FIELD16(0x0400) +#define MAC_CSR19_DIR3 FIELD16(0x0800) +#define MAC_CSR19_DIR4 FIELD16(0x1000) +#define MAC_CSR19_DIR5 FIELD16(0x2000) +#define MAC_CSR19_DIR6 FIELD16(0x4000) +#define MAC_CSR19_DIR7 FIELD16(0x8000) + +/* + * MAC_CSR20: LED control register. + * ACTIVITY: 0: idle, 1: active. + * LINK: 0: linkoff, 1: linkup. + * ACTIVITY_POLARITY: 0: active low, 1: active high. + */ +#define MAC_CSR20 0x0428 +#define MAC_CSR20_ACTIVITY FIELD16(0x0001) +#define MAC_CSR20_LINK FIELD16(0x0002) +#define MAC_CSR20_ACTIVITY_POLARITY FIELD16(0x0004) + +/* + * MAC_CSR21: LED control register. + * ON_PERIOD: On period, default 70ms. + * OFF_PERIOD: Off period, default 30ms. + */ +#define MAC_CSR21 0x042a +#define MAC_CSR21_ON_PERIOD FIELD16(0x00ff) +#define MAC_CSR21_OFF_PERIOD FIELD16(0xff00) + +/* + * MAC_CSR22: Collision window control register. + */ +#define MAC_CSR22 0x042c + +/* + * Transmit related CSRs. + * Some values are set in TU, whereas 1 TU == 1024 us. + */ + +/* + * TXRX_CSR0: Security control register. + */ +#define TXRX_CSR0 0x0440 +#define TXRX_CSR0_ALGORITHM FIELD16(0x0007) +#define TXRX_CSR0_IV_OFFSET FIELD16(0x01f8) +#define TXRX_CSR0_KEY_ID FIELD16(0x1e00) + +/* + * TXRX_CSR1: TX configuration. + * ACK_TIMEOUT: ACK Timeout in unit of 1-us. + * TSF_OFFSET: TSF offset in MAC header. + * AUTO_SEQUENCE: Let ASIC control frame sequence number. + */ +#define TXRX_CSR1 0x0442 +#define TXRX_CSR1_ACK_TIMEOUT FIELD16(0x00ff) +#define TXRX_CSR1_TSF_OFFSET FIELD16(0x7f00) +#define TXRX_CSR1_AUTO_SEQUENCE FIELD16(0x8000) + +/* + * TXRX_CSR2: RX control. + * DISABLE_RX: Disable rx engine. + * DROP_CRC: Drop crc error. + * DROP_PHYSICAL: Drop physical error. + * DROP_CONTROL: Drop control frame. + * DROP_NOT_TO_ME: Drop not to me unicast frame. + * DROP_TODS: Drop frame tods bit is true. + * DROP_VERSION_ERROR: Drop version error frame. + * DROP_MCAST: Drop multicast frames. + * DROP_BCAST: Drop broadcast frames. + */ +#define TXRX_CSR2 0x0444 +#define TXRX_CSR2_DISABLE_RX FIELD16(0x0001) +#define TXRX_CSR2_DROP_CRC FIELD16(0x0002) +#define TXRX_CSR2_DROP_PHYSICAL FIELD16(0x0004) +#define TXRX_CSR2_DROP_CONTROL FIELD16(0x0008) +#define TXRX_CSR2_DROP_NOT_TO_ME FIELD16(0x0010) +#define TXRX_CSR2_DROP_TODS FIELD16(0x0020) +#define TXRX_CSR2_DROP_VERSION_ERROR FIELD16(0x0040) +#define TXRX_CSR2_DROP_MULTICAST FIELD16(0x0200) +#define TXRX_CSR2_DROP_BROADCAST FIELD16(0x0400) + +/* + * RX BBP ID registers + * TXRX_CSR3: CCK RX BBP ID. + * TXRX_CSR4: OFDM RX BBP ID. + */ +#define TXRX_CSR3 0x0446 +#define TXRX_CSR4 0x0448 + +/* + * TXRX_CSR5: CCK TX BBP ID0. + */ +#define TXRX_CSR5 0x044a +#define TXRX_CSR5_BBP_ID0 FIELD16(0x007f) +#define TXRX_CSR5_BBP_ID0_VALID FIELD16(0x0080) +#define TXRX_CSR5_BBP_ID1 FIELD16(0x7f00) +#define TXRX_CSR5_BBP_ID1_VALID FIELD16(0x8000) + +/* + * TXRX_CSR6: CCK TX BBP ID1. + */ +#define TXRX_CSR6 0x044c +#define TXRX_CSR6_BBP_ID0 FIELD16(0x007f) +#define TXRX_CSR6_BBP_ID0_VALID FIELD16(0x0080) +#define TXRX_CSR6_BBP_ID1 FIELD16(0x7f00) +#define TXRX_CSR6_BBP_ID1_VALID FIELD16(0x8000) + +/* + * TXRX_CSR7: OFDM TX BBP ID0. + */ +#define TXRX_CSR7 0x044e +#define TXRX_CSR7_BBP_ID0 FIELD16(0x007f) +#define TXRX_CSR7_BBP_ID0_VALID FIELD16(0x0080) +#define TXRX_CSR7_BBP_ID1 FIELD16(0x7f00) +#define TXRX_CSR7_BBP_ID1_VALID FIELD16(0x8000) + +/* + * TXRX_CSR8: OFDM TX BBP ID1. + */ +#define TXRX_CSR8 0x0450 +#define TXRX_CSR8_BBP_ID0 FIELD16(0x007f) +#define TXRX_CSR8_BBP_ID0_VALID FIELD16(0x0080) +#define TXRX_CSR8_BBP_ID1 FIELD16(0x7f00) +#define TXRX_CSR8_BBP_ID1_VALID FIELD16(0x8000) + +/* + * TXRX_CSR9: TX ACK time-out. + */ +#define TXRX_CSR9 0x0452 + +/* + * TXRX_CSR10: Auto responder control. + */ +#define TXRX_CSR10 0x0454 +#define TXRX_CSR10_AUTORESPOND_PREAMBLE FIELD16(0x0004) + +/* + * TXRX_CSR11: Auto responder basic rate. + */ +#define TXRX_CSR11 0x0456 + +/* + * ACK/CTS time registers. + */ +#define TXRX_CSR12 0x0458 +#define TXRX_CSR13 0x045a +#define TXRX_CSR14 0x045c +#define TXRX_CSR15 0x045e +#define TXRX_CSR16 0x0460 +#define TXRX_CSR17 0x0462 + +/* + * TXRX_CSR18: Synchronization control register. + */ +#define TXRX_CSR18 0x0464 +#define TXRX_CSR18_OFFSET FIELD16(0x000f) +#define TXRX_CSR18_INTERVAL FIELD16(0xfff0) + +/* + * TXRX_CSR19: Synchronization control register. + * TSF_COUNT: Enable TSF auto counting. + * TSF_SYNC: Tsf sync, 0: disable, 1: infra, 2: ad-hoc/master mode. + * TBCN: Enable Tbcn with reload value. + * BEACON_GEN: Enable beacon generator. + */ +#define TXRX_CSR19 0x0466 +#define TXRX_CSR19_TSF_COUNT FIELD16(0x0001) +#define TXRX_CSR19_TSF_SYNC FIELD16(0x0006) +#define TXRX_CSR19_TBCN FIELD16(0x0008) +#define TXRX_CSR19_BEACON_GEN FIELD16(0x0010) + +/* + * TXRX_CSR20: Tx BEACON offset time control register. + * OFFSET: In units of usec. + * BCN_EXPECT_WINDOW: Default: 2^CWmin + */ +#define TXRX_CSR20 0x0468 +#define TXRX_CSR20_OFFSET FIELD16(0x1fff) +#define TXRX_CSR20_BCN_EXPECT_WINDOW FIELD16(0xe000) + +/* + * TXRX_CSR21 + */ +#define TXRX_CSR21 0x046a + +/* + * Encryption related CSRs. + * + */ + +/* + * SEC_CSR0: Shared key 0, word 0 + * SEC_CSR1: Shared key 0, word 1 + * SEC_CSR2: Shared key 0, word 2 + * SEC_CSR3: Shared key 0, word 3 + * SEC_CSR4: Shared key 0, word 4 + * SEC_CSR5: Shared key 0, word 5 + * SEC_CSR6: Shared key 0, word 6 + * SEC_CSR7: Shared key 0, word 7 + */ +#define SEC_CSR0 0x0480 +#define SEC_CSR1 0x0482 +#define SEC_CSR2 0x0484 +#define SEC_CSR3 0x0486 +#define SEC_CSR4 0x0488 +#define SEC_CSR5 0x048a +#define SEC_CSR6 0x048c +#define SEC_CSR7 0x048e + +/* + * SEC_CSR8: Shared key 1, word 0 + * SEC_CSR9: Shared key 1, word 1 + * SEC_CSR10: Shared key 1, word 2 + * SEC_CSR11: Shared key 1, word 3 + * SEC_CSR12: Shared key 1, word 4 + * SEC_CSR13: Shared key 1, word 5 + * SEC_CSR14: Shared key 1, word 6 + * SEC_CSR15: Shared key 1, word 7 + */ +#define SEC_CSR8 0x0490 +#define SEC_CSR9 0x0492 +#define SEC_CSR10 0x0494 +#define SEC_CSR11 0x0496 +#define SEC_CSR12 0x0498 +#define SEC_CSR13 0x049a +#define SEC_CSR14 0x049c +#define SEC_CSR15 0x049e + +/* + * SEC_CSR16: Shared key 2, word 0 + * SEC_CSR17: Shared key 2, word 1 + * SEC_CSR18: Shared key 2, word 2 + * SEC_CSR19: Shared key 2, word 3 + * SEC_CSR20: Shared key 2, word 4 + * SEC_CSR21: Shared key 2, word 5 + * SEC_CSR22: Shared key 2, word 6 + * SEC_CSR23: Shared key 2, word 7 + */ +#define SEC_CSR16 0x04a0 +#define SEC_CSR17 0x04a2 +#define SEC_CSR18 0X04A4 +#define SEC_CSR19 0x04a6 +#define SEC_CSR20 0x04a8 +#define SEC_CSR21 0x04aa +#define SEC_CSR22 0x04ac +#define SEC_CSR23 0x04ae + +/* + * SEC_CSR24: Shared key 3, word 0 + * SEC_CSR25: Shared key 3, word 1 + * SEC_CSR26: Shared key 3, word 2 + * SEC_CSR27: Shared key 3, word 3 + * SEC_CSR28: Shared key 3, word 4 + * SEC_CSR29: Shared key 3, word 5 + * SEC_CSR30: Shared key 3, word 6 + * SEC_CSR31: Shared key 3, word 7 + */ +#define SEC_CSR24 0x04b0 +#define SEC_CSR25 0x04b2 +#define SEC_CSR26 0x04b4 +#define SEC_CSR27 0x04b6 +#define SEC_CSR28 0x04b8 +#define SEC_CSR29 0x04ba +#define SEC_CSR30 0x04bc +#define SEC_CSR31 0x04be + +#define KEY_ENTRY(__idx) \ + ( SEC_CSR0 + ((__idx) * 16) ) + +/* + * PHY control registers. + */ + +/* + * PHY_CSR0: RF switching timing control. + */ +#define PHY_CSR0 0x04c0 + +/* + * PHY_CSR1: TX PA configuration. + */ +#define PHY_CSR1 0x04c2 + +/* + * MAC configuration registers. + */ + +/* + * PHY_CSR2: TX MAC configuration. + * NOTE: Both register fields are complete dummy, + * documentation and legacy drivers are unclear un + * what this register means or what fields exists. + */ +#define PHY_CSR2 0x04c4 +#define PHY_CSR2_LNA FIELD16(0x0002) +#define PHY_CSR2_LNA_MODE FIELD16(0x3000) + +/* + * PHY_CSR3: RX MAC configuration. + */ +#define PHY_CSR3 0x04c6 + +/* + * PHY_CSR4: Interface configuration. + */ +#define PHY_CSR4 0x04c8 +#define PHY_CSR4_LOW_RF_LE FIELD16(0x0001) + +/* + * BBP pre-TX registers. + * PHY_CSR5: BBP pre-TX CCK. + */ +#define PHY_CSR5 0x04ca +#define PHY_CSR5_CCK FIELD16(0x0003) +#define PHY_CSR5_CCK_FLIP FIELD16(0x0004) + +/* + * BBP pre-TX registers. + * PHY_CSR6: BBP pre-TX OFDM. + */ +#define PHY_CSR6 0x04cc +#define PHY_CSR6_OFDM FIELD16(0x0003) +#define PHY_CSR6_OFDM_FLIP FIELD16(0x0004) + +/* + * PHY_CSR7: BBP access register 0. + * BBP_DATA: BBP data. + * BBP_REG_ID: BBP register ID. + * BBP_READ_CONTROL: 0: write, 1: read. + */ +#define PHY_CSR7 0x04ce +#define PHY_CSR7_DATA FIELD16(0x00ff) +#define PHY_CSR7_REG_ID FIELD16(0x7f00) +#define PHY_CSR7_READ_CONTROL FIELD16(0x8000) + +/* + * PHY_CSR8: BBP access register 1. + * BBP_BUSY: ASIC is busy execute BBP programming. + */ +#define PHY_CSR8 0x04d0 +#define PHY_CSR8_BUSY FIELD16(0x0001) + +/* + * PHY_CSR9: RF access register. + * RF_VALUE: Register value + id to program into rf/if. + */ +#define PHY_CSR9 0x04d2 +#define PHY_CSR9_RF_VALUE FIELD16(0xffff) + +/* + * PHY_CSR10: RF access register. + * RF_VALUE: Register value + id to program into rf/if. + * RF_NUMBER_OF_BITS: Number of bits used in value (i:20, rfmd:22). + * RF_IF_SELECT: Chip to program: 0: rf, 1: if. + * RF_PLL_LD: Rf pll_ld status. + * RF_BUSY: 1: asic is busy execute rf programming. + */ +#define PHY_CSR10 0x04d4 +#define PHY_CSR10_RF_VALUE FIELD16(0x00ff) +#define PHY_CSR10_RF_NUMBER_OF_BITS FIELD16(0x1f00) +#define PHY_CSR10_RF_IF_SELECT FIELD16(0x2000) +#define PHY_CSR10_RF_PLL_LD FIELD16(0x4000) +#define PHY_CSR10_RF_BUSY FIELD16(0x8000) + +/* + * STA_CSR0: FCS error count. + * FCS_ERROR: FCS error count, cleared when read. + */ +#define STA_CSR0 0x04e0 +#define STA_CSR0_FCS_ERROR FIELD16(0xffff) + +/* + * STA_CSR1: PLCP error count. + */ +#define STA_CSR1 0x04e2 + +/* + * STA_CSR2: LONG error count. + */ +#define STA_CSR2 0x04e4 + +/* + * STA_CSR3: CCA false alarm. + * FALSE_CCA_ERROR: False CCA error count, cleared when read. + */ +#define STA_CSR3 0x04e6 +#define STA_CSR3_FALSE_CCA_ERROR FIELD16(0xffff) + +/* + * STA_CSR4: RX FIFO overflow. + */ +#define STA_CSR4 0x04e8 + +/* + * STA_CSR5: Beacon sent counter. + */ +#define STA_CSR5 0x04ea + +/* + * Statistics registers + */ +#define STA_CSR6 0x04ec +#define STA_CSR7 0x04ee +#define STA_CSR8 0x04f0 +#define STA_CSR9 0x04f2 +#define STA_CSR10 0x04f4 + +/* + * BBP registers. + * The wordsize of the BBP is 8 bits. + */ + +/* + * R2: TX antenna control + */ +#define BBP_R2_TX_ANTENNA FIELD8(0x03) +#define BBP_R2_TX_IQ_FLIP FIELD8(0x04) + +/* + * R14: RX antenna control + */ +#define BBP_R14_RX_ANTENNA FIELD8(0x03) +#define BBP_R14_RX_IQ_FLIP FIELD8(0x04) + +/* + * RF registers. + */ + +/* + * RF 1 + */ +#define RF1_TUNER FIELD32(0x00020000) + +/* + * RF 3 + */ +#define RF3_TUNER FIELD32(0x00000100) +#define RF3_TXPOWER FIELD32(0x00003e00) + +/* + * EEPROM contents. + */ + +/* + * HW MAC address. + */ +#define EEPROM_MAC_ADDR_0 0x0002 +#define EEPROM_MAC_ADDR_BYTE0 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE1 FIELD16(0xff00) +#define EEPROM_MAC_ADDR1 0x0003 +#define EEPROM_MAC_ADDR_BYTE2 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE3 FIELD16(0xff00) +#define EEPROM_MAC_ADDR_2 0x0004 +#define EEPROM_MAC_ADDR_BYTE4 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE5 FIELD16(0xff00) + +/* + * EEPROM antenna. + * ANTENNA_NUM: Number of antenna's. + * TX_DEFAULT: Default antenna 0: diversity, 1: A, 2: B. + * RX_DEFAULT: Default antenna 0: diversity, 1: A, 2: B. + * LED_MODE: 0: default, 1: TX/RX activity, 2: Single (ignore link), 3: rsvd. + * DYN_TXAGC: Dynamic TX AGC control. + * HARDWARE_RADIO: 1: Hardware controlled radio. Read GPIO0. + * RF_TYPE: Rf_type of this adapter. + */ +#define EEPROM_ANTENNA 0x000b +#define EEPROM_ANTENNA_NUM FIELD16(0x0003) +#define EEPROM_ANTENNA_TX_DEFAULT FIELD16(0x000c) +#define EEPROM_ANTENNA_RX_DEFAULT FIELD16(0x0030) +#define EEPROM_ANTENNA_LED_MODE FIELD16(0x01c0) +#define EEPROM_ANTENNA_DYN_TXAGC FIELD16(0x0200) +#define EEPROM_ANTENNA_HARDWARE_RADIO FIELD16(0x0400) +#define EEPROM_ANTENNA_RF_TYPE FIELD16(0xf800) + +/* + * EEPROM NIC config. + * CARDBUS_ACCEL: 0: enable, 1: disable. + * DYN_BBP_TUNE: 0: enable, 1: disable. + * CCK_TX_POWER: CCK TX power compensation. + */ +#define EEPROM_NIC 0x000c +#define EEPROM_NIC_CARDBUS_ACCEL FIELD16(0x0001) +#define EEPROM_NIC_DYN_BBP_TUNE FIELD16(0x0002) +#define EEPROM_NIC_CCK_TX_POWER FIELD16(0x000c) + +/* + * EEPROM geography. + * GEO: Default geography setting for device. + */ +#define EEPROM_GEOGRAPHY 0x000d +#define EEPROM_GEOGRAPHY_GEO FIELD16(0x0f00) + +/* + * EEPROM BBP. + */ +#define EEPROM_BBP_START 0x000e +#define EEPROM_BBP_SIZE 16 +#define EEPROM_BBP_VALUE FIELD16(0x00ff) +#define EEPROM_BBP_REG_ID FIELD16(0xff00) + +/* + * EEPROM TXPOWER + */ +#define EEPROM_TXPOWER_START 0x001e +#define EEPROM_TXPOWER_SIZE 7 +#define EEPROM_TXPOWER_1 FIELD16(0x00ff) +#define EEPROM_TXPOWER_2 FIELD16(0xff00) + +/* + * EEPROM Tuning threshold + */ +#define EEPROM_BBPTUNE 0x0030 +#define EEPROM_BBPTUNE_THRESHOLD FIELD16(0x00ff) + +/* + * EEPROM BBP R24 Tuning. + */ +#define EEPROM_BBPTUNE_R24 0x0031 +#define EEPROM_BBPTUNE_R24_LOW FIELD16(0x00ff) +#define EEPROM_BBPTUNE_R24_HIGH FIELD16(0xff00) + +/* + * EEPROM BBP R25 Tuning. + */ +#define EEPROM_BBPTUNE_R25 0x0032 +#define EEPROM_BBPTUNE_R25_LOW FIELD16(0x00ff) +#define EEPROM_BBPTUNE_R25_HIGH FIELD16(0xff00) + +/* + * EEPROM BBP R24 Tuning. + */ +#define EEPROM_BBPTUNE_R61 0x0033 +#define EEPROM_BBPTUNE_R61_LOW FIELD16(0x00ff) +#define EEPROM_BBPTUNE_R61_HIGH FIELD16(0xff00) + +/* + * EEPROM BBP VGC Tuning. + */ +#define EEPROM_BBPTUNE_VGC 0x0034 +#define EEPROM_BBPTUNE_VGCUPPER FIELD16(0x00ff) +#define EEPROM_BBPTUNE_VGCLOWER FIELD16(0xff00) + +/* + * EEPROM BBP R17 Tuning. + */ +#define EEPROM_BBPTUNE_R17 0x0035 +#define EEPROM_BBPTUNE_R17_LOW FIELD16(0x00ff) +#define EEPROM_BBPTUNE_R17_HIGH FIELD16(0xff00) + +/* + * RSSI <-> dBm offset calibration + */ +#define EEPROM_CALIBRATE_OFFSET 0x0036 +#define EEPROM_CALIBRATE_OFFSET_RSSI FIELD16(0x00ff) + +/* + * DMA descriptor defines. + */ +#define TXD_DESC_SIZE ( 5 * sizeof(__le32) ) +#define RXD_DESC_SIZE ( 4 * sizeof(__le32) ) + +/* + * TX descriptor format for TX, PRIO, ATIM and Beacon Ring. + */ + +/* + * Word0 + */ +#define TXD_W0_PACKET_ID FIELD32(0x0000000f) +#define TXD_W0_RETRY_LIMIT FIELD32(0x000000f0) +#define TXD_W0_MORE_FRAG FIELD32(0x00000100) +#define TXD_W0_ACK FIELD32(0x00000200) +#define TXD_W0_TIMESTAMP FIELD32(0x00000400) +#define TXD_W0_OFDM FIELD32(0x00000800) +#define TXD_W0_NEW_SEQ FIELD32(0x00001000) +#define TXD_W0_IFS FIELD32(0x00006000) +#define TXD_W0_DATABYTE_COUNT FIELD32(0x0fff0000) +#define TXD_W0_CIPHER FIELD32(0x20000000) +#define TXD_W0_KEY_ID FIELD32(0xc0000000) + +/* + * Word1 + */ +#define TXD_W1_IV_OFFSET FIELD32(0x0000003f) +#define TXD_W1_AIFS FIELD32(0x000000c0) +#define TXD_W1_CWMIN FIELD32(0x00000f00) +#define TXD_W1_CWMAX FIELD32(0x0000f000) + +/* + * Word2: PLCP information + */ +#define TXD_W2_PLCP_SIGNAL FIELD32(0x000000ff) +#define TXD_W2_PLCP_SERVICE FIELD32(0x0000ff00) +#define TXD_W2_PLCP_LENGTH_LOW FIELD32(0x00ff0000) +#define TXD_W2_PLCP_LENGTH_HIGH FIELD32(0xff000000) + +/* + * Word3 + */ +#define TXD_W3_IV FIELD32(0xffffffff) + +/* + * Word4 + */ +#define TXD_W4_EIV FIELD32(0xffffffff) + +/* + * RX descriptor format for RX Ring. + */ + +/* + * Word0 + */ +#define RXD_W0_UNICAST_TO_ME FIELD32(0x00000002) +#define RXD_W0_MULTICAST FIELD32(0x00000004) +#define RXD_W0_BROADCAST FIELD32(0x00000008) +#define RXD_W0_MY_BSS FIELD32(0x00000010) +#define RXD_W0_CRC_ERROR FIELD32(0x00000020) +#define RXD_W0_OFDM FIELD32(0x00000040) +#define RXD_W0_PHYSICAL_ERROR FIELD32(0x00000080) +#define RXD_W0_CIPHER FIELD32(0x00000100) +#define RXD_W0_CIPHER_ERROR FIELD32(0x00000200) +#define RXD_W0_DATABYTE_COUNT FIELD32(0x0fff0000) + +/* + * Word1 + */ +#define RXD_W1_RSSI FIELD32(0x000000ff) +#define RXD_W1_SIGNAL FIELD32(0x0000ff00) + +/* + * Word2 + */ +#define RXD_W2_IV FIELD32(0xffffffff) + +/* + * Word3 + */ +#define RXD_W3_EIV FIELD32(0xffffffff) + +/* + * Macros for converting txpower from EEPROM to mac80211 value + * and from mac80211 value to register value. + */ +#define MIN_TXPOWER 0 +#define MAX_TXPOWER 31 +#define DEFAULT_TXPOWER 24 + +#define TXPOWER_FROM_DEV(__txpower) \ + (((u8)(__txpower)) > MAX_TXPOWER) ? DEFAULT_TXPOWER : (__txpower) + +#define TXPOWER_TO_DEV(__txpower) \ + clamp_t(char, __txpower, MIN_TXPOWER, MAX_TXPOWER) + +#endif /* RT2500USB_H */ diff --git a/drivers/net/wireless/rt2x00/rt2800.h b/drivers/net/wireless/rt2x00/rt2800.h new file mode 100644 index 0000000..a7630d5 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2800.h @@ -0,0 +1,2787 @@ +/* + Copyright (C) 2004 - 2010 Ivo van Doorn + Copyright (C) 2010 Willow Garage + Copyright (C) 2009 Alban Browaeys + Copyright (C) 2009 Felix Fietkau + Copyright (C) 2009 Luis Correia + Copyright (C) 2009 Mattias Nissler + Copyright (C) 2009 Mark Asselstine + Copyright (C) 2009 Xose Vazquez Perez + Copyright (C) 2009 Bart Zolnierkiewicz + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2800 + Abstract: Data structures and registers for the rt2800 modules. + Supported chipsets: RT2800E, RT2800ED & RT2800U. + */ + +#ifndef RT2800_H +#define RT2800_H + +/* + * RF chip defines. + * + * RF2820 2.4G 2T3R + * RF2850 2.4G/5G 2T3R + * RF2720 2.4G 1T2R + * RF2750 2.4G/5G 1T2R + * RF3020 2.4G 1T1R + * RF2020 2.4G B/G + * RF3021 2.4G 1T2R + * RF3022 2.4G 2T2R + * RF3052 2.4G/5G 2T2R + * RF2853 2.4G/5G 3T3R + * RF3320 2.4G 1T1R(RT3350/RT3370/RT3390) + * RF3322 2.4G 2T2R(RT3352/RT3371/RT3372/RT3391/RT3392) + * RF3053 2.4G/5G 3T3R(RT3883/RT3563/RT3573/RT3593/RT3662) + * RF5592 2.4G/5G 2T2R + * RF5360 2.4G 1T1R + * RF5370 2.4G 1T1R + * RF5390 2.4G 1T1R + */ +#define RF2820 0x0001 +#define RF2850 0x0002 +#define RF2720 0x0003 +#define RF2750 0x0004 +#define RF3020 0x0005 +#define RF2020 0x0006 +#define RF3021 0x0007 +#define RF3022 0x0008 +#define RF3052 0x0009 +#define RF2853 0x000a +#define RF3320 0x000b +#define RF3322 0x000c +#define RF3053 0x000d +#define RF5592 0x000f +#define RF3290 0x3290 +#define RF5360 0x5360 +#define RF5370 0x5370 +#define RF5372 0x5372 +#define RF5390 0x5390 +#define RF5392 0x5392 + +/* + * Chipset revisions. + */ +#define REV_RT2860C 0x0100 +#define REV_RT2860D 0x0101 +#define REV_RT2872E 0x0200 +#define REV_RT3070E 0x0200 +#define REV_RT3070F 0x0201 +#define REV_RT3071E 0x0211 +#define REV_RT3090E 0x0211 +#define REV_RT3390E 0x0211 +#define REV_RT5390F 0x0502 +#define REV_RT5390R 0x1502 +#define REV_RT5592C 0x0221 + +#define DEFAULT_RSSI_OFFSET 120 + +/* + * Register layout information. + */ +#define CSR_REG_BASE 0x1000 +#define CSR_REG_SIZE 0x0800 +#define EEPROM_BASE 0x0000 +#define EEPROM_SIZE 0x0110 +#define BBP_BASE 0x0000 +#define BBP_SIZE 0x00ff +#define RF_BASE 0x0004 +#define RF_SIZE 0x0010 +#define RFCSR_BASE 0x0000 +#define RFCSR_SIZE 0x0040 + +/* + * Number of TX queues. + */ +#define NUM_TX_QUEUES 4 + +/* + * Registers. + */ + + +/* + * MAC_CSR0_3290: MAC_CSR0 for RT3290 to identity MAC version number. + */ +#define MAC_CSR0_3290 0x0000 + +/* + * E2PROM_CSR: PCI EEPROM control register. + * RELOAD: Write 1 to reload eeprom content. + * TYPE: 0: 93c46, 1:93c66. + * LOAD_STATUS: 1:loading, 0:done. + */ +#define E2PROM_CSR 0x0004 +#define E2PROM_CSR_DATA_CLOCK FIELD32(0x00000001) +#define E2PROM_CSR_CHIP_SELECT FIELD32(0x00000002) +#define E2PROM_CSR_DATA_IN FIELD32(0x00000004) +#define E2PROM_CSR_DATA_OUT FIELD32(0x00000008) +#define E2PROM_CSR_TYPE FIELD32(0x00000030) +#define E2PROM_CSR_LOAD_STATUS FIELD32(0x00000040) +#define E2PROM_CSR_RELOAD FIELD32(0x00000080) + +/* + * CMB_CTRL_CFG + */ +#define CMB_CTRL 0x0020 +#define AUX_OPT_BIT0 FIELD32(0x00000001) +#define AUX_OPT_BIT1 FIELD32(0x00000002) +#define AUX_OPT_BIT2 FIELD32(0x00000004) +#define AUX_OPT_BIT3 FIELD32(0x00000008) +#define AUX_OPT_BIT4 FIELD32(0x00000010) +#define AUX_OPT_BIT5 FIELD32(0x00000020) +#define AUX_OPT_BIT6 FIELD32(0x00000040) +#define AUX_OPT_BIT7 FIELD32(0x00000080) +#define AUX_OPT_BIT8 FIELD32(0x00000100) +#define AUX_OPT_BIT9 FIELD32(0x00000200) +#define AUX_OPT_BIT10 FIELD32(0x00000400) +#define AUX_OPT_BIT11 FIELD32(0x00000800) +#define AUX_OPT_BIT12 FIELD32(0x00001000) +#define AUX_OPT_BIT13 FIELD32(0x00002000) +#define AUX_OPT_BIT14 FIELD32(0x00004000) +#define AUX_OPT_BIT15 FIELD32(0x00008000) +#define LDO25_LEVEL FIELD32(0x00030000) +#define LDO25_LARGEA FIELD32(0x00040000) +#define LDO25_FRC_ON FIELD32(0x00080000) +#define CMB_RSV FIELD32(0x00300000) +#define XTAL_RDY FIELD32(0x00400000) +#define PLL_LD FIELD32(0x00800000) +#define LDO_CORE_LEVEL FIELD32(0x0F000000) +#define LDO_BGSEL FIELD32(0x30000000) +#define LDO3_EN FIELD32(0x40000000) +#define LDO0_EN FIELD32(0x80000000) + +/* + * EFUSE_CSR_3290: RT3290 EEPROM + */ +#define EFUSE_CTRL_3290 0x0024 + +/* + * EFUSE_DATA3 of 3290 + */ +#define EFUSE_DATA3_3290 0x0028 + +/* + * EFUSE_DATA2 of 3290 + */ +#define EFUSE_DATA2_3290 0x002c + +/* + * EFUSE_DATA1 of 3290 + */ +#define EFUSE_DATA1_3290 0x0030 + +/* + * EFUSE_DATA0 of 3290 + */ +#define EFUSE_DATA0_3290 0x0034 + +/* + * OSC_CTRL_CFG + * Ring oscillator configuration + */ +#define OSC_CTRL 0x0038 +#define OSC_REF_CYCLE FIELD32(0x00001fff) +#define OSC_RSV FIELD32(0x0000e000) +#define OSC_CAL_CNT FIELD32(0x0fff0000) +#define OSC_CAL_ACK FIELD32(0x10000000) +#define OSC_CLK_32K_VLD FIELD32(0x20000000) +#define OSC_CAL_REQ FIELD32(0x40000000) +#define OSC_ROSC_EN FIELD32(0x80000000) + +/* + * COEX_CFG_0 + */ +#define COEX_CFG0 0x0040 +#define COEX_CFG_ANT FIELD32(0xff000000) +/* + * COEX_CFG_1 + */ +#define COEX_CFG1 0x0044 + +/* + * COEX_CFG_2 + */ +#define COEX_CFG2 0x0048 +#define BT_COEX_CFG1 FIELD32(0xff000000) +#define BT_COEX_CFG0 FIELD32(0x00ff0000) +#define WL_COEX_CFG1 FIELD32(0x0000ff00) +#define WL_COEX_CFG0 FIELD32(0x000000ff) +/* + * PLL_CTRL_CFG + * PLL configuration register + */ +#define PLL_CTRL 0x0050 +#define PLL_RESERVED_INPUT1 FIELD32(0x000000ff) +#define PLL_RESERVED_INPUT2 FIELD32(0x0000ff00) +#define PLL_CONTROL FIELD32(0x00070000) +#define PLL_LPF_R1 FIELD32(0x00080000) +#define PLL_LPF_C1_CTRL FIELD32(0x00300000) +#define PLL_LPF_C2_CTRL FIELD32(0x00c00000) +#define PLL_CP_CURRENT_CTRL FIELD32(0x03000000) +#define PLL_PFD_DELAY_CTRL FIELD32(0x0c000000) +#define PLL_LOCK_CTRL FIELD32(0x70000000) +#define PLL_VBGBK_EN FIELD32(0x80000000) + + +/* + * WLAN_CTRL_CFG + * RT3290 wlan configuration + */ +#define WLAN_FUN_CTRL 0x0080 +#define WLAN_EN FIELD32(0x00000001) +#define WLAN_CLK_EN FIELD32(0x00000002) +#define WLAN_RSV1 FIELD32(0x00000004) +#define WLAN_RESET FIELD32(0x00000008) +#define PCIE_APP0_CLK_REQ FIELD32(0x00000010) +#define FRC_WL_ANT_SET FIELD32(0x00000020) +#define INV_TR_SW0 FIELD32(0x00000040) +#define WLAN_GPIO_IN_BIT0 FIELD32(0x00000100) +#define WLAN_GPIO_IN_BIT1 FIELD32(0x00000200) +#define WLAN_GPIO_IN_BIT2 FIELD32(0x00000400) +#define WLAN_GPIO_IN_BIT3 FIELD32(0x00000800) +#define WLAN_GPIO_IN_BIT4 FIELD32(0x00001000) +#define WLAN_GPIO_IN_BIT5 FIELD32(0x00002000) +#define WLAN_GPIO_IN_BIT6 FIELD32(0x00004000) +#define WLAN_GPIO_IN_BIT7 FIELD32(0x00008000) +#define WLAN_GPIO_IN_BIT_ALL FIELD32(0x0000ff00) +#define WLAN_GPIO_OUT_BIT0 FIELD32(0x00010000) +#define WLAN_GPIO_OUT_BIT1 FIELD32(0x00020000) +#define WLAN_GPIO_OUT_BIT2 FIELD32(0x00040000) +#define WLAN_GPIO_OUT_BIT3 FIELD32(0x00050000) +#define WLAN_GPIO_OUT_BIT4 FIELD32(0x00100000) +#define WLAN_GPIO_OUT_BIT5 FIELD32(0x00200000) +#define WLAN_GPIO_OUT_BIT6 FIELD32(0x00400000) +#define WLAN_GPIO_OUT_BIT7 FIELD32(0x00800000) +#define WLAN_GPIO_OUT_BIT_ALL FIELD32(0x00ff0000) +#define WLAN_GPIO_OUT_OE_BIT0 FIELD32(0x01000000) +#define WLAN_GPIO_OUT_OE_BIT1 FIELD32(0x02000000) +#define WLAN_GPIO_OUT_OE_BIT2 FIELD32(0x04000000) +#define WLAN_GPIO_OUT_OE_BIT3 FIELD32(0x08000000) +#define WLAN_GPIO_OUT_OE_BIT4 FIELD32(0x10000000) +#define WLAN_GPIO_OUT_OE_BIT5 FIELD32(0x20000000) +#define WLAN_GPIO_OUT_OE_BIT6 FIELD32(0x40000000) +#define WLAN_GPIO_OUT_OE_BIT7 FIELD32(0x80000000) +#define WLAN_GPIO_OUT_OE_BIT_ALL FIELD32(0xff000000) + +/* + * AUX_CTRL: Aux/PCI-E related configuration + */ +#define AUX_CTRL 0x10c +#define AUX_CTRL_WAKE_PCIE_EN FIELD32(0x00000002) +#define AUX_CTRL_FORCE_PCIE_CLK FIELD32(0x00000400) + +/* + * OPT_14: Unknown register used by rt3xxx devices. + */ +#define OPT_14_CSR 0x0114 +#define OPT_14_CSR_BIT0 FIELD32(0x00000001) + +/* + * INT_SOURCE_CSR: Interrupt source register. + * Write one to clear corresponding bit. + * TX_FIFO_STATUS: FIFO Statistics is full, sw should read TX_STA_FIFO + */ +#define INT_SOURCE_CSR 0x0200 +#define INT_SOURCE_CSR_RXDELAYINT FIELD32(0x00000001) +#define INT_SOURCE_CSR_TXDELAYINT FIELD32(0x00000002) +#define INT_SOURCE_CSR_RX_DONE FIELD32(0x00000004) +#define INT_SOURCE_CSR_AC0_DMA_DONE FIELD32(0x00000008) +#define INT_SOURCE_CSR_AC1_DMA_DONE FIELD32(0x00000010) +#define INT_SOURCE_CSR_AC2_DMA_DONE FIELD32(0x00000020) +#define INT_SOURCE_CSR_AC3_DMA_DONE FIELD32(0x00000040) +#define INT_SOURCE_CSR_HCCA_DMA_DONE FIELD32(0x00000080) +#define INT_SOURCE_CSR_MGMT_DMA_DONE FIELD32(0x00000100) +#define INT_SOURCE_CSR_MCU_COMMAND FIELD32(0x00000200) +#define INT_SOURCE_CSR_RXTX_COHERENT FIELD32(0x00000400) +#define INT_SOURCE_CSR_TBTT FIELD32(0x00000800) +#define INT_SOURCE_CSR_PRE_TBTT FIELD32(0x00001000) +#define INT_SOURCE_CSR_TX_FIFO_STATUS FIELD32(0x00002000) +#define INT_SOURCE_CSR_AUTO_WAKEUP FIELD32(0x00004000) +#define INT_SOURCE_CSR_GPTIMER FIELD32(0x00008000) +#define INT_SOURCE_CSR_RX_COHERENT FIELD32(0x00010000) +#define INT_SOURCE_CSR_TX_COHERENT FIELD32(0x00020000) + +/* + * INT_MASK_CSR: Interrupt MASK register. 1: the interrupt is mask OFF. + */ +#define INT_MASK_CSR 0x0204 +#define INT_MASK_CSR_RXDELAYINT FIELD32(0x00000001) +#define INT_MASK_CSR_TXDELAYINT FIELD32(0x00000002) +#define INT_MASK_CSR_RX_DONE FIELD32(0x00000004) +#define INT_MASK_CSR_AC0_DMA_DONE FIELD32(0x00000008) +#define INT_MASK_CSR_AC1_DMA_DONE FIELD32(0x00000010) +#define INT_MASK_CSR_AC2_DMA_DONE FIELD32(0x00000020) +#define INT_MASK_CSR_AC3_DMA_DONE FIELD32(0x00000040) +#define INT_MASK_CSR_HCCA_DMA_DONE FIELD32(0x00000080) +#define INT_MASK_CSR_MGMT_DMA_DONE FIELD32(0x00000100) +#define INT_MASK_CSR_MCU_COMMAND FIELD32(0x00000200) +#define INT_MASK_CSR_RXTX_COHERENT FIELD32(0x00000400) +#define INT_MASK_CSR_TBTT FIELD32(0x00000800) +#define INT_MASK_CSR_PRE_TBTT FIELD32(0x00001000) +#define INT_MASK_CSR_TX_FIFO_STATUS FIELD32(0x00002000) +#define INT_MASK_CSR_AUTO_WAKEUP FIELD32(0x00004000) +#define INT_MASK_CSR_GPTIMER FIELD32(0x00008000) +#define INT_MASK_CSR_RX_COHERENT FIELD32(0x00010000) +#define INT_MASK_CSR_TX_COHERENT FIELD32(0x00020000) + +/* + * WPDMA_GLO_CFG + */ +#define WPDMA_GLO_CFG 0x0208 +#define WPDMA_GLO_CFG_ENABLE_TX_DMA FIELD32(0x00000001) +#define WPDMA_GLO_CFG_TX_DMA_BUSY FIELD32(0x00000002) +#define WPDMA_GLO_CFG_ENABLE_RX_DMA FIELD32(0x00000004) +#define WPDMA_GLO_CFG_RX_DMA_BUSY FIELD32(0x00000008) +#define WPDMA_GLO_CFG_WP_DMA_BURST_SIZE FIELD32(0x00000030) +#define WPDMA_GLO_CFG_TX_WRITEBACK_DONE FIELD32(0x00000040) +#define WPDMA_GLO_CFG_BIG_ENDIAN FIELD32(0x00000080) +#define WPDMA_GLO_CFG_RX_HDR_SCATTER FIELD32(0x0000ff00) +#define WPDMA_GLO_CFG_HDR_SEG_LEN FIELD32(0xffff0000) + +/* + * WPDMA_RST_IDX + */ +#define WPDMA_RST_IDX 0x020c +#define WPDMA_RST_IDX_DTX_IDX0 FIELD32(0x00000001) +#define WPDMA_RST_IDX_DTX_IDX1 FIELD32(0x00000002) +#define WPDMA_RST_IDX_DTX_IDX2 FIELD32(0x00000004) +#define WPDMA_RST_IDX_DTX_IDX3 FIELD32(0x00000008) +#define WPDMA_RST_IDX_DTX_IDX4 FIELD32(0x00000010) +#define WPDMA_RST_IDX_DTX_IDX5 FIELD32(0x00000020) +#define WPDMA_RST_IDX_DRX_IDX0 FIELD32(0x00010000) + +/* + * DELAY_INT_CFG + */ +#define DELAY_INT_CFG 0x0210 +#define DELAY_INT_CFG_RXMAX_PTIME FIELD32(0x000000ff) +#define DELAY_INT_CFG_RXMAX_PINT FIELD32(0x00007f00) +#define DELAY_INT_CFG_RXDLY_INT_EN FIELD32(0x00008000) +#define DELAY_INT_CFG_TXMAX_PTIME FIELD32(0x00ff0000) +#define DELAY_INT_CFG_TXMAX_PINT FIELD32(0x7f000000) +#define DELAY_INT_CFG_TXDLY_INT_EN FIELD32(0x80000000) + +/* + * WMM_AIFSN_CFG: Aifsn for each EDCA AC + * AIFSN0: AC_VO + * AIFSN1: AC_VI + * AIFSN2: AC_BE + * AIFSN3: AC_BK + */ +#define WMM_AIFSN_CFG 0x0214 +#define WMM_AIFSN_CFG_AIFSN0 FIELD32(0x0000000f) +#define WMM_AIFSN_CFG_AIFSN1 FIELD32(0x000000f0) +#define WMM_AIFSN_CFG_AIFSN2 FIELD32(0x00000f00) +#define WMM_AIFSN_CFG_AIFSN3 FIELD32(0x0000f000) + +/* + * WMM_CWMIN_CSR: CWmin for each EDCA AC + * CWMIN0: AC_VO + * CWMIN1: AC_VI + * CWMIN2: AC_BE + * CWMIN3: AC_BK + */ +#define WMM_CWMIN_CFG 0x0218 +#define WMM_CWMIN_CFG_CWMIN0 FIELD32(0x0000000f) +#define WMM_CWMIN_CFG_CWMIN1 FIELD32(0x000000f0) +#define WMM_CWMIN_CFG_CWMIN2 FIELD32(0x00000f00) +#define WMM_CWMIN_CFG_CWMIN3 FIELD32(0x0000f000) + +/* + * WMM_CWMAX_CSR: CWmax for each EDCA AC + * CWMAX0: AC_VO + * CWMAX1: AC_VI + * CWMAX2: AC_BE + * CWMAX3: AC_BK + */ +#define WMM_CWMAX_CFG 0x021c +#define WMM_CWMAX_CFG_CWMAX0 FIELD32(0x0000000f) +#define WMM_CWMAX_CFG_CWMAX1 FIELD32(0x000000f0) +#define WMM_CWMAX_CFG_CWMAX2 FIELD32(0x00000f00) +#define WMM_CWMAX_CFG_CWMAX3 FIELD32(0x0000f000) + +/* + * AC_TXOP0: AC_VO/AC_VI TXOP register + * AC0TXOP: AC_VO in unit of 32us + * AC1TXOP: AC_VI in unit of 32us + */ +#define WMM_TXOP0_CFG 0x0220 +#define WMM_TXOP0_CFG_AC0TXOP FIELD32(0x0000ffff) +#define WMM_TXOP0_CFG_AC1TXOP FIELD32(0xffff0000) + +/* + * AC_TXOP1: AC_BE/AC_BK TXOP register + * AC2TXOP: AC_BE in unit of 32us + * AC3TXOP: AC_BK in unit of 32us + */ +#define WMM_TXOP1_CFG 0x0224 +#define WMM_TXOP1_CFG_AC2TXOP FIELD32(0x0000ffff) +#define WMM_TXOP1_CFG_AC3TXOP FIELD32(0xffff0000) + +/* + * GPIO_CTRL: + * GPIO_CTRL_VALx: GPIO value + * GPIO_CTRL_DIRx: GPIO direction: 0 = output; 1 = input + */ +#define GPIO_CTRL 0x0228 +#define GPIO_CTRL_VAL0 FIELD32(0x00000001) +#define GPIO_CTRL_VAL1 FIELD32(0x00000002) +#define GPIO_CTRL_VAL2 FIELD32(0x00000004) +#define GPIO_CTRL_VAL3 FIELD32(0x00000008) +#define GPIO_CTRL_VAL4 FIELD32(0x00000010) +#define GPIO_CTRL_VAL5 FIELD32(0x00000020) +#define GPIO_CTRL_VAL6 FIELD32(0x00000040) +#define GPIO_CTRL_VAL7 FIELD32(0x00000080) +#define GPIO_CTRL_DIR0 FIELD32(0x00000100) +#define GPIO_CTRL_DIR1 FIELD32(0x00000200) +#define GPIO_CTRL_DIR2 FIELD32(0x00000400) +#define GPIO_CTRL_DIR3 FIELD32(0x00000800) +#define GPIO_CTRL_DIR4 FIELD32(0x00001000) +#define GPIO_CTRL_DIR5 FIELD32(0x00002000) +#define GPIO_CTRL_DIR6 FIELD32(0x00004000) +#define GPIO_CTRL_DIR7 FIELD32(0x00008000) +#define GPIO_CTRL_VAL8 FIELD32(0x00010000) +#define GPIO_CTRL_VAL9 FIELD32(0x00020000) +#define GPIO_CTRL_VAL10 FIELD32(0x00040000) +#define GPIO_CTRL_DIR8 FIELD32(0x01000000) +#define GPIO_CTRL_DIR9 FIELD32(0x02000000) +#define GPIO_CTRL_DIR10 FIELD32(0x04000000) + +/* + * MCU_CMD_CFG + */ +#define MCU_CMD_CFG 0x022c + +/* + * AC_VO register offsets + */ +#define TX_BASE_PTR0 0x0230 +#define TX_MAX_CNT0 0x0234 +#define TX_CTX_IDX0 0x0238 +#define TX_DTX_IDX0 0x023c + +/* + * AC_VI register offsets + */ +#define TX_BASE_PTR1 0x0240 +#define TX_MAX_CNT1 0x0244 +#define TX_CTX_IDX1 0x0248 +#define TX_DTX_IDX1 0x024c + +/* + * AC_BE register offsets + */ +#define TX_BASE_PTR2 0x0250 +#define TX_MAX_CNT2 0x0254 +#define TX_CTX_IDX2 0x0258 +#define TX_DTX_IDX2 0x025c + +/* + * AC_BK register offsets + */ +#define TX_BASE_PTR3 0x0260 +#define TX_MAX_CNT3 0x0264 +#define TX_CTX_IDX3 0x0268 +#define TX_DTX_IDX3 0x026c + +/* + * HCCA register offsets + */ +#define TX_BASE_PTR4 0x0270 +#define TX_MAX_CNT4 0x0274 +#define TX_CTX_IDX4 0x0278 +#define TX_DTX_IDX4 0x027c + +/* + * MGMT register offsets + */ +#define TX_BASE_PTR5 0x0280 +#define TX_MAX_CNT5 0x0284 +#define TX_CTX_IDX5 0x0288 +#define TX_DTX_IDX5 0x028c + +/* + * RX register offsets + */ +#define RX_BASE_PTR 0x0290 +#define RX_MAX_CNT 0x0294 +#define RX_CRX_IDX 0x0298 +#define RX_DRX_IDX 0x029c + +/* + * USB_DMA_CFG + * RX_BULK_AGG_TIMEOUT: Rx Bulk Aggregation TimeOut in unit of 33ns. + * RX_BULK_AGG_LIMIT: Rx Bulk Aggregation Limit in unit of 256 bytes. + * PHY_CLEAR: phy watch dog enable. + * TX_CLEAR: Clear USB DMA TX path. + * TXOP_HALT: Halt TXOP count down when TX buffer is full. + * RX_BULK_AGG_EN: Enable Rx Bulk Aggregation. + * RX_BULK_EN: Enable USB DMA Rx. + * TX_BULK_EN: Enable USB DMA Tx. + * EP_OUT_VALID: OUT endpoint data valid. + * RX_BUSY: USB DMA RX FSM busy. + * TX_BUSY: USB DMA TX FSM busy. + */ +#define USB_DMA_CFG 0x02a0 +#define USB_DMA_CFG_RX_BULK_AGG_TIMEOUT FIELD32(0x000000ff) +#define USB_DMA_CFG_RX_BULK_AGG_LIMIT FIELD32(0x0000ff00) +#define USB_DMA_CFG_PHY_CLEAR FIELD32(0x00010000) +#define USB_DMA_CFG_TX_CLEAR FIELD32(0x00080000) +#define USB_DMA_CFG_TXOP_HALT FIELD32(0x00100000) +#define USB_DMA_CFG_RX_BULK_AGG_EN FIELD32(0x00200000) +#define USB_DMA_CFG_RX_BULK_EN FIELD32(0x00400000) +#define USB_DMA_CFG_TX_BULK_EN FIELD32(0x00800000) +#define USB_DMA_CFG_EP_OUT_VALID FIELD32(0x3f000000) +#define USB_DMA_CFG_RX_BUSY FIELD32(0x40000000) +#define USB_DMA_CFG_TX_BUSY FIELD32(0x80000000) + +/* + * US_CYC_CNT + * BT_MODE_EN: Bluetooth mode enable + * CLOCK CYCLE: Clock cycle count in 1us. + * PCI:0x21, PCIE:0x7d, USB:0x1e + */ +#define US_CYC_CNT 0x02a4 +#define US_CYC_CNT_BT_MODE_EN FIELD32(0x00000100) +#define US_CYC_CNT_CLOCK_CYCLE FIELD32(0x000000ff) + +/* + * PBF_SYS_CTRL + * HOST_RAM_WRITE: enable Host program ram write selection + */ +#define PBF_SYS_CTRL 0x0400 +#define PBF_SYS_CTRL_READY FIELD32(0x00000080) +#define PBF_SYS_CTRL_HOST_RAM_WRITE FIELD32(0x00010000) + +/* + * HOST-MCU shared memory + */ +#define HOST_CMD_CSR 0x0404 +#define HOST_CMD_CSR_HOST_COMMAND FIELD32(0x000000ff) + +/* + * PBF registers + * Most are for debug. Driver doesn't touch PBF register. + */ +#define PBF_CFG 0x0408 +#define PBF_MAX_PCNT 0x040c +#define PBF_CTRL 0x0410 +#define PBF_INT_STA 0x0414 +#define PBF_INT_ENA 0x0418 + +/* + * BCN_OFFSET0: + */ +#define BCN_OFFSET0 0x042c +#define BCN_OFFSET0_BCN0 FIELD32(0x000000ff) +#define BCN_OFFSET0_BCN1 FIELD32(0x0000ff00) +#define BCN_OFFSET0_BCN2 FIELD32(0x00ff0000) +#define BCN_OFFSET0_BCN3 FIELD32(0xff000000) + +/* + * BCN_OFFSET1: + */ +#define BCN_OFFSET1 0x0430 +#define BCN_OFFSET1_BCN4 FIELD32(0x000000ff) +#define BCN_OFFSET1_BCN5 FIELD32(0x0000ff00) +#define BCN_OFFSET1_BCN6 FIELD32(0x00ff0000) +#define BCN_OFFSET1_BCN7 FIELD32(0xff000000) + +/* + * TXRXQ_PCNT: PBF register + * PCNT_TX0Q: Page count for TX hardware queue 0 + * PCNT_TX1Q: Page count for TX hardware queue 1 + * PCNT_TX2Q: Page count for TX hardware queue 2 + * PCNT_RX0Q: Page count for RX hardware queue + */ +#define TXRXQ_PCNT 0x0438 +#define TXRXQ_PCNT_TX0Q FIELD32(0x000000ff) +#define TXRXQ_PCNT_TX1Q FIELD32(0x0000ff00) +#define TXRXQ_PCNT_TX2Q FIELD32(0x00ff0000) +#define TXRXQ_PCNT_RX0Q FIELD32(0xff000000) + +/* + * PBF register + * Debug. Driver doesn't touch PBF register. + */ +#define PBF_DBG 0x043c + +/* + * RF registers + */ +#define RF_CSR_CFG 0x0500 +#define RF_CSR_CFG_DATA FIELD32(0x000000ff) +#define RF_CSR_CFG_REGNUM FIELD32(0x00003f00) +#define RF_CSR_CFG_WRITE FIELD32(0x00010000) +#define RF_CSR_CFG_BUSY FIELD32(0x00020000) + +/* + * EFUSE_CSR: RT30x0 EEPROM + */ +#define EFUSE_CTRL 0x0580 +#define EFUSE_CTRL_ADDRESS_IN FIELD32(0x03fe0000) +#define EFUSE_CTRL_MODE FIELD32(0x000000c0) +#define EFUSE_CTRL_KICK FIELD32(0x40000000) +#define EFUSE_CTRL_PRESENT FIELD32(0x80000000) + +/* + * EFUSE_DATA0 + */ +#define EFUSE_DATA0 0x0590 + +/* + * EFUSE_DATA1 + */ +#define EFUSE_DATA1 0x0594 + +/* + * EFUSE_DATA2 + */ +#define EFUSE_DATA2 0x0598 + +/* + * EFUSE_DATA3 + */ +#define EFUSE_DATA3 0x059c + +/* + * LDO_CFG0 + */ +#define LDO_CFG0 0x05d4 +#define LDO_CFG0_DELAY3 FIELD32(0x000000ff) +#define LDO_CFG0_DELAY2 FIELD32(0x0000ff00) +#define LDO_CFG0_DELAY1 FIELD32(0x00ff0000) +#define LDO_CFG0_BGSEL FIELD32(0x03000000) +#define LDO_CFG0_LDO_CORE_VLEVEL FIELD32(0x1c000000) +#define LD0_CFG0_LDO25_LEVEL FIELD32(0x60000000) +#define LDO_CFG0_LDO25_LARGEA FIELD32(0x80000000) + +/* + * GPIO_SWITCH + */ +#define GPIO_SWITCH 0x05dc +#define GPIO_SWITCH_0 FIELD32(0x00000001) +#define GPIO_SWITCH_1 FIELD32(0x00000002) +#define GPIO_SWITCH_2 FIELD32(0x00000004) +#define GPIO_SWITCH_3 FIELD32(0x00000008) +#define GPIO_SWITCH_4 FIELD32(0x00000010) +#define GPIO_SWITCH_5 FIELD32(0x00000020) +#define GPIO_SWITCH_6 FIELD32(0x00000040) +#define GPIO_SWITCH_7 FIELD32(0x00000080) + +/* + * FIXME: where the DEBUG_INDEX name come from? + */ +#define MAC_DEBUG_INDEX 0x05e8 +#define MAC_DEBUG_INDEX_XTAL FIELD32(0x80000000) + +/* + * MAC Control/Status Registers(CSR). + * Some values are set in TU, whereas 1 TU == 1024 us. + */ + +/* + * MAC_CSR0: ASIC revision number. + * ASIC_REV: 0 + * ASIC_VER: 2860 or 2870 + */ +#define MAC_CSR0 0x1000 +#define MAC_CSR0_REVISION FIELD32(0x0000ffff) +#define MAC_CSR0_CHIPSET FIELD32(0xffff0000) + +/* + * MAC_SYS_CTRL: + */ +#define MAC_SYS_CTRL 0x1004 +#define MAC_SYS_CTRL_RESET_CSR FIELD32(0x00000001) +#define MAC_SYS_CTRL_RESET_BBP FIELD32(0x00000002) +#define MAC_SYS_CTRL_ENABLE_TX FIELD32(0x00000004) +#define MAC_SYS_CTRL_ENABLE_RX FIELD32(0x00000008) +#define MAC_SYS_CTRL_CONTINUOUS_TX FIELD32(0x00000010) +#define MAC_SYS_CTRL_LOOPBACK FIELD32(0x00000020) +#define MAC_SYS_CTRL_WLAN_HALT FIELD32(0x00000040) +#define MAC_SYS_CTRL_RX_TIMESTAMP FIELD32(0x00000080) + +/* + * MAC_ADDR_DW0: STA MAC register 0 + */ +#define MAC_ADDR_DW0 0x1008 +#define MAC_ADDR_DW0_BYTE0 FIELD32(0x000000ff) +#define MAC_ADDR_DW0_BYTE1 FIELD32(0x0000ff00) +#define MAC_ADDR_DW0_BYTE2 FIELD32(0x00ff0000) +#define MAC_ADDR_DW0_BYTE3 FIELD32(0xff000000) + +/* + * MAC_ADDR_DW1: STA MAC register 1 + * UNICAST_TO_ME_MASK: + * Used to mask off bits from byte 5 of the MAC address + * to determine the UNICAST_TO_ME bit for RX frames. + * The full mask is complemented by BSS_ID_MASK: + * MASK = BSS_ID_MASK & UNICAST_TO_ME_MASK + */ +#define MAC_ADDR_DW1 0x100c +#define MAC_ADDR_DW1_BYTE4 FIELD32(0x000000ff) +#define MAC_ADDR_DW1_BYTE5 FIELD32(0x0000ff00) +#define MAC_ADDR_DW1_UNICAST_TO_ME_MASK FIELD32(0x00ff0000) + +/* + * MAC_BSSID_DW0: BSSID register 0 + */ +#define MAC_BSSID_DW0 0x1010 +#define MAC_BSSID_DW0_BYTE0 FIELD32(0x000000ff) +#define MAC_BSSID_DW0_BYTE1 FIELD32(0x0000ff00) +#define MAC_BSSID_DW0_BYTE2 FIELD32(0x00ff0000) +#define MAC_BSSID_DW0_BYTE3 FIELD32(0xff000000) + +/* + * MAC_BSSID_DW1: BSSID register 1 + * BSS_ID_MASK: + * 0: 1-BSSID mode (BSS index = 0) + * 1: 2-BSSID mode (BSS index: Byte5, bit 0) + * 2: 4-BSSID mode (BSS index: byte5, bit 0 - 1) + * 3: 8-BSSID mode (BSS index: byte5, bit 0 - 2) + * This mask is used to mask off bits 0, 1 and 2 of byte 5 of the + * BSSID. This will make sure that those bits will be ignored + * when determining the MY_BSS of RX frames. + */ +#define MAC_BSSID_DW1 0x1014 +#define MAC_BSSID_DW1_BYTE4 FIELD32(0x000000ff) +#define MAC_BSSID_DW1_BYTE5 FIELD32(0x0000ff00) +#define MAC_BSSID_DW1_BSS_ID_MASK FIELD32(0x00030000) +#define MAC_BSSID_DW1_BSS_BCN_NUM FIELD32(0x001c0000) + +/* + * MAX_LEN_CFG: Maximum frame length register. + * MAX_MPDU: rt2860b max 16k bytes + * MAX_PSDU: Maximum PSDU length + * (power factor) 0:2^13, 1:2^14, 2:2^15, 3:2^16 + */ +#define MAX_LEN_CFG 0x1018 +#define MAX_LEN_CFG_MAX_MPDU FIELD32(0x00000fff) +#define MAX_LEN_CFG_MAX_PSDU FIELD32(0x00003000) +#define MAX_LEN_CFG_MIN_PSDU FIELD32(0x0000c000) +#define MAX_LEN_CFG_MIN_MPDU FIELD32(0x000f0000) + +/* + * BBP_CSR_CFG: BBP serial control register + * VALUE: Register value to program into BBP + * REG_NUM: Selected BBP register + * READ_CONTROL: 0 write BBP, 1 read BBP + * BUSY: ASIC is busy executing BBP commands + * BBP_PAR_DUR: 0 4 MAC clocks, 1 8 MAC clocks + * BBP_RW_MODE: 0 serial, 1 parallel + */ +#define BBP_CSR_CFG 0x101c +#define BBP_CSR_CFG_VALUE FIELD32(0x000000ff) +#define BBP_CSR_CFG_REGNUM FIELD32(0x0000ff00) +#define BBP_CSR_CFG_READ_CONTROL FIELD32(0x00010000) +#define BBP_CSR_CFG_BUSY FIELD32(0x00020000) +#define BBP_CSR_CFG_BBP_PAR_DUR FIELD32(0x00040000) +#define BBP_CSR_CFG_BBP_RW_MODE FIELD32(0x00080000) + +/* + * RF_CSR_CFG0: RF control register + * REGID_AND_VALUE: Register value to program into RF + * BITWIDTH: Selected RF register + * STANDBYMODE: 0 high when standby, 1 low when standby + * SEL: 0 RF_LE0 activate, 1 RF_LE1 activate + * BUSY: ASIC is busy executing RF commands + */ +#define RF_CSR_CFG0 0x1020 +#define RF_CSR_CFG0_REGID_AND_VALUE FIELD32(0x00ffffff) +#define RF_CSR_CFG0_BITWIDTH FIELD32(0x1f000000) +#define RF_CSR_CFG0_REG_VALUE_BW FIELD32(0x1fffffff) +#define RF_CSR_CFG0_STANDBYMODE FIELD32(0x20000000) +#define RF_CSR_CFG0_SEL FIELD32(0x40000000) +#define RF_CSR_CFG0_BUSY FIELD32(0x80000000) + +/* + * RF_CSR_CFG1: RF control register + * REGID_AND_VALUE: Register value to program into RF + * RFGAP: Gap between BB_CONTROL_RF and RF_LE + * 0: 3 system clock cycle (37.5usec) + * 1: 5 system clock cycle (62.5usec) + */ +#define RF_CSR_CFG1 0x1024 +#define RF_CSR_CFG1_REGID_AND_VALUE FIELD32(0x00ffffff) +#define RF_CSR_CFG1_RFGAP FIELD32(0x1f000000) + +/* + * RF_CSR_CFG2: RF control register + * VALUE: Register value to program into RF + */ +#define RF_CSR_CFG2 0x1028 +#define RF_CSR_CFG2_VALUE FIELD32(0x00ffffff) + +/* + * LED_CFG: LED control + * ON_PERIOD: LED active time (ms) during TX (only used for LED mode 1) + * OFF_PERIOD: LED inactive time (ms) during TX (only used for LED mode 1) + * SLOW_BLINK_PERIOD: LED blink interval in seconds (only used for LED mode 2) + * color LED's: + * 0: off + * 1: blinking upon TX2 + * 2: periodic slow blinking + * 3: always on + * LED polarity: + * 0: active low + * 1: active high + */ +#define LED_CFG 0x102c +#define LED_CFG_ON_PERIOD FIELD32(0x000000ff) +#define LED_CFG_OFF_PERIOD FIELD32(0x0000ff00) +#define LED_CFG_SLOW_BLINK_PERIOD FIELD32(0x003f0000) +#define LED_CFG_R_LED_MODE FIELD32(0x03000000) +#define LED_CFG_G_LED_MODE FIELD32(0x0c000000) +#define LED_CFG_Y_LED_MODE FIELD32(0x30000000) +#define LED_CFG_LED_POLAR FIELD32(0x40000000) + +/* + * AMPDU_BA_WINSIZE: Force BlockAck window size + * FORCE_WINSIZE_ENABLE: + * 0: Disable forcing of BlockAck window size + * 1: Enable forcing of BlockAck window size, overwrites values BlockAck + * window size values in the TXWI + * FORCE_WINSIZE: BlockAck window size + */ +#define AMPDU_BA_WINSIZE 0x1040 +#define AMPDU_BA_WINSIZE_FORCE_WINSIZE_ENABLE FIELD32(0x00000020) +#define AMPDU_BA_WINSIZE_FORCE_WINSIZE FIELD32(0x0000001f) + +/* + * XIFS_TIME_CFG: MAC timing + * CCKM_SIFS_TIME: unit 1us. Applied after CCK RX/TX + * OFDM_SIFS_TIME: unit 1us. Applied after OFDM RX/TX + * OFDM_XIFS_TIME: unit 1us. Applied after OFDM RX + * when MAC doesn't reference BBP signal BBRXEND + * EIFS: unit 1us + * BB_RXEND_ENABLE: reference RXEND signal to begin XIFS defer + * + */ +#define XIFS_TIME_CFG 0x1100 +#define XIFS_TIME_CFG_CCKM_SIFS_TIME FIELD32(0x000000ff) +#define XIFS_TIME_CFG_OFDM_SIFS_TIME FIELD32(0x0000ff00) +#define XIFS_TIME_CFG_OFDM_XIFS_TIME FIELD32(0x000f0000) +#define XIFS_TIME_CFG_EIFS FIELD32(0x1ff00000) +#define XIFS_TIME_CFG_BB_RXEND_ENABLE FIELD32(0x20000000) + +/* + * BKOFF_SLOT_CFG: + */ +#define BKOFF_SLOT_CFG 0x1104 +#define BKOFF_SLOT_CFG_SLOT_TIME FIELD32(0x000000ff) +#define BKOFF_SLOT_CFG_CC_DELAY_TIME FIELD32(0x0000ff00) + +/* + * NAV_TIME_CFG: + */ +#define NAV_TIME_CFG 0x1108 +#define NAV_TIME_CFG_SIFS FIELD32(0x000000ff) +#define NAV_TIME_CFG_SLOT_TIME FIELD32(0x0000ff00) +#define NAV_TIME_CFG_EIFS FIELD32(0x01ff0000) +#define NAV_TIME_ZERO_SIFS FIELD32(0x02000000) + +/* + * CH_TIME_CFG: count as channel busy + * EIFS_BUSY: Count EIFS as channel busy + * NAV_BUSY: Count NAS as channel busy + * RX_BUSY: Count RX as channel busy + * TX_BUSY: Count TX as channel busy + * TMR_EN: Enable channel statistics timer + */ +#define CH_TIME_CFG 0x110c +#define CH_TIME_CFG_EIFS_BUSY FIELD32(0x00000010) +#define CH_TIME_CFG_NAV_BUSY FIELD32(0x00000008) +#define CH_TIME_CFG_RX_BUSY FIELD32(0x00000004) +#define CH_TIME_CFG_TX_BUSY FIELD32(0x00000002) +#define CH_TIME_CFG_TMR_EN FIELD32(0x00000001) + +/* + * PBF_LIFE_TIMER: TX/RX MPDU timestamp timer (free run) Unit: 1us + */ +#define PBF_LIFE_TIMER 0x1110 + +/* + * BCN_TIME_CFG: + * BEACON_INTERVAL: in unit of 1/16 TU + * TSF_TICKING: Enable TSF auto counting + * TSF_SYNC: Enable TSF sync, 00: disable, 01: infra mode, 10: ad-hoc mode + * BEACON_GEN: Enable beacon generator + */ +#define BCN_TIME_CFG 0x1114 +#define BCN_TIME_CFG_BEACON_INTERVAL FIELD32(0x0000ffff) +#define BCN_TIME_CFG_TSF_TICKING FIELD32(0x00010000) +#define BCN_TIME_CFG_TSF_SYNC FIELD32(0x00060000) +#define BCN_TIME_CFG_TBTT_ENABLE FIELD32(0x00080000) +#define BCN_TIME_CFG_BEACON_GEN FIELD32(0x00100000) +#define BCN_TIME_CFG_TX_TIME_COMPENSATE FIELD32(0xf0000000) + +/* + * TBTT_SYNC_CFG: + * BCN_AIFSN: Beacon AIFSN after TBTT interrupt in slots + * BCN_CWMIN: Beacon CWMin after TBTT interrupt in slots + */ +#define TBTT_SYNC_CFG 0x1118 +#define TBTT_SYNC_CFG_TBTT_ADJUST FIELD32(0x000000ff) +#define TBTT_SYNC_CFG_BCN_EXP_WIN FIELD32(0x0000ff00) +#define TBTT_SYNC_CFG_BCN_AIFSN FIELD32(0x000f0000) +#define TBTT_SYNC_CFG_BCN_CWMIN FIELD32(0x00f00000) + +/* + * TSF_TIMER_DW0: Local lsb TSF timer, read-only + */ +#define TSF_TIMER_DW0 0x111c +#define TSF_TIMER_DW0_LOW_WORD FIELD32(0xffffffff) + +/* + * TSF_TIMER_DW1: Local msb TSF timer, read-only + */ +#define TSF_TIMER_DW1 0x1120 +#define TSF_TIMER_DW1_HIGH_WORD FIELD32(0xffffffff) + +/* + * TBTT_TIMER: TImer remains till next TBTT, read-only + */ +#define TBTT_TIMER 0x1124 + +/* + * INT_TIMER_CFG: timer configuration + * PRE_TBTT_TIMER: leadtime to tbtt for pretbtt interrupt in units of 1/16 TU + * GP_TIMER: period of general purpose timer in units of 1/16 TU + */ +#define INT_TIMER_CFG 0x1128 +#define INT_TIMER_CFG_PRE_TBTT_TIMER FIELD32(0x0000ffff) +#define INT_TIMER_CFG_GP_TIMER FIELD32(0xffff0000) + +/* + * INT_TIMER_EN: GP-timer and pre-tbtt Int enable + */ +#define INT_TIMER_EN 0x112c +#define INT_TIMER_EN_PRE_TBTT_TIMER FIELD32(0x00000001) +#define INT_TIMER_EN_GP_TIMER FIELD32(0x00000002) + +/* + * CH_IDLE_STA: channel idle time (in us) + */ +#define CH_IDLE_STA 0x1130 + +/* + * CH_BUSY_STA: channel busy time on primary channel (in us) + */ +#define CH_BUSY_STA 0x1134 + +/* + * CH_BUSY_STA_SEC: channel busy time on secondary channel in HT40 mode (in us) + */ +#define CH_BUSY_STA_SEC 0x1138 + +/* + * MAC_STATUS_CFG: + * BBP_RF_BUSY: When set to 0, BBP and RF are stable. + * if 1 or higher one of the 2 registers is busy. + */ +#define MAC_STATUS_CFG 0x1200 +#define MAC_STATUS_CFG_BBP_RF_BUSY FIELD32(0x00000003) + +/* + * PWR_PIN_CFG: + */ +#define PWR_PIN_CFG 0x1204 + +/* + * AUTOWAKEUP_CFG: Manual power control / status register + * TBCN_BEFORE_WAKE: ForceWake has high privilege than PutToSleep when both set + * AUTOWAKE: 0:sleep, 1:awake + */ +#define AUTOWAKEUP_CFG 0x1208 +#define AUTOWAKEUP_CFG_AUTO_LEAD_TIME FIELD32(0x000000ff) +#define AUTOWAKEUP_CFG_TBCN_BEFORE_WAKE FIELD32(0x00007f00) +#define AUTOWAKEUP_CFG_AUTOWAKE FIELD32(0x00008000) + +/* + * EDCA_AC0_CFG: + */ +#define EDCA_AC0_CFG 0x1300 +#define EDCA_AC0_CFG_TX_OP FIELD32(0x000000ff) +#define EDCA_AC0_CFG_AIFSN FIELD32(0x00000f00) +#define EDCA_AC0_CFG_CWMIN FIELD32(0x0000f000) +#define EDCA_AC0_CFG_CWMAX FIELD32(0x000f0000) + +/* + * EDCA_AC1_CFG: + */ +#define EDCA_AC1_CFG 0x1304 +#define EDCA_AC1_CFG_TX_OP FIELD32(0x000000ff) +#define EDCA_AC1_CFG_AIFSN FIELD32(0x00000f00) +#define EDCA_AC1_CFG_CWMIN FIELD32(0x0000f000) +#define EDCA_AC1_CFG_CWMAX FIELD32(0x000f0000) + +/* + * EDCA_AC2_CFG: + */ +#define EDCA_AC2_CFG 0x1308 +#define EDCA_AC2_CFG_TX_OP FIELD32(0x000000ff) +#define EDCA_AC2_CFG_AIFSN FIELD32(0x00000f00) +#define EDCA_AC2_CFG_CWMIN FIELD32(0x0000f000) +#define EDCA_AC2_CFG_CWMAX FIELD32(0x000f0000) + +/* + * EDCA_AC3_CFG: + */ +#define EDCA_AC3_CFG 0x130c +#define EDCA_AC3_CFG_TX_OP FIELD32(0x000000ff) +#define EDCA_AC3_CFG_AIFSN FIELD32(0x00000f00) +#define EDCA_AC3_CFG_CWMIN FIELD32(0x0000f000) +#define EDCA_AC3_CFG_CWMAX FIELD32(0x000f0000) + +/* + * EDCA_TID_AC_MAP: + */ +#define EDCA_TID_AC_MAP 0x1310 + +/* + * TX_PWR_CFG: + */ +#define TX_PWR_CFG_RATE0 FIELD32(0x0000000f) +#define TX_PWR_CFG_RATE1 FIELD32(0x000000f0) +#define TX_PWR_CFG_RATE2 FIELD32(0x00000f00) +#define TX_PWR_CFG_RATE3 FIELD32(0x0000f000) +#define TX_PWR_CFG_RATE4 FIELD32(0x000f0000) +#define TX_PWR_CFG_RATE5 FIELD32(0x00f00000) +#define TX_PWR_CFG_RATE6 FIELD32(0x0f000000) +#define TX_PWR_CFG_RATE7 FIELD32(0xf0000000) + +/* + * TX_PWR_CFG_0: + */ +#define TX_PWR_CFG_0 0x1314 +#define TX_PWR_CFG_0_1MBS FIELD32(0x0000000f) +#define TX_PWR_CFG_0_2MBS FIELD32(0x000000f0) +#define TX_PWR_CFG_0_55MBS FIELD32(0x00000f00) +#define TX_PWR_CFG_0_11MBS FIELD32(0x0000f000) +#define TX_PWR_CFG_0_6MBS FIELD32(0x000f0000) +#define TX_PWR_CFG_0_9MBS FIELD32(0x00f00000) +#define TX_PWR_CFG_0_12MBS FIELD32(0x0f000000) +#define TX_PWR_CFG_0_18MBS FIELD32(0xf0000000) + +/* + * TX_PWR_CFG_1: + */ +#define TX_PWR_CFG_1 0x1318 +#define TX_PWR_CFG_1_24MBS FIELD32(0x0000000f) +#define TX_PWR_CFG_1_36MBS FIELD32(0x000000f0) +#define TX_PWR_CFG_1_48MBS FIELD32(0x00000f00) +#define TX_PWR_CFG_1_54MBS FIELD32(0x0000f000) +#define TX_PWR_CFG_1_MCS0 FIELD32(0x000f0000) +#define TX_PWR_CFG_1_MCS1 FIELD32(0x00f00000) +#define TX_PWR_CFG_1_MCS2 FIELD32(0x0f000000) +#define TX_PWR_CFG_1_MCS3 FIELD32(0xf0000000) + +/* + * TX_PWR_CFG_2: + */ +#define TX_PWR_CFG_2 0x131c +#define TX_PWR_CFG_2_MCS4 FIELD32(0x0000000f) +#define TX_PWR_CFG_2_MCS5 FIELD32(0x000000f0) +#define TX_PWR_CFG_2_MCS6 FIELD32(0x00000f00) +#define TX_PWR_CFG_2_MCS7 FIELD32(0x0000f000) +#define TX_PWR_CFG_2_MCS8 FIELD32(0x000f0000) +#define TX_PWR_CFG_2_MCS9 FIELD32(0x00f00000) +#define TX_PWR_CFG_2_MCS10 FIELD32(0x0f000000) +#define TX_PWR_CFG_2_MCS11 FIELD32(0xf0000000) + +/* + * TX_PWR_CFG_3: + */ +#define TX_PWR_CFG_3 0x1320 +#define TX_PWR_CFG_3_MCS12 FIELD32(0x0000000f) +#define TX_PWR_CFG_3_MCS13 FIELD32(0x000000f0) +#define TX_PWR_CFG_3_MCS14 FIELD32(0x00000f00) +#define TX_PWR_CFG_3_MCS15 FIELD32(0x0000f000) +#define TX_PWR_CFG_3_UKNOWN1 FIELD32(0x000f0000) +#define TX_PWR_CFG_3_UKNOWN2 FIELD32(0x00f00000) +#define TX_PWR_CFG_3_UKNOWN3 FIELD32(0x0f000000) +#define TX_PWR_CFG_3_UKNOWN4 FIELD32(0xf0000000) + +/* + * TX_PWR_CFG_4: + */ +#define TX_PWR_CFG_4 0x1324 +#define TX_PWR_CFG_4_UKNOWN5 FIELD32(0x0000000f) +#define TX_PWR_CFG_4_UKNOWN6 FIELD32(0x000000f0) +#define TX_PWR_CFG_4_UKNOWN7 FIELD32(0x00000f00) +#define TX_PWR_CFG_4_UKNOWN8 FIELD32(0x0000f000) + +/* + * TX_PIN_CFG: + */ +#define TX_PIN_CFG 0x1328 +#define TX_PIN_CFG_PA_PE_DISABLE 0xfcfffff0 +#define TX_PIN_CFG_PA_PE_A0_EN FIELD32(0x00000001) +#define TX_PIN_CFG_PA_PE_G0_EN FIELD32(0x00000002) +#define TX_PIN_CFG_PA_PE_A1_EN FIELD32(0x00000004) +#define TX_PIN_CFG_PA_PE_G1_EN FIELD32(0x00000008) +#define TX_PIN_CFG_PA_PE_A0_POL FIELD32(0x00000010) +#define TX_PIN_CFG_PA_PE_G0_POL FIELD32(0x00000020) +#define TX_PIN_CFG_PA_PE_A1_POL FIELD32(0x00000040) +#define TX_PIN_CFG_PA_PE_G1_POL FIELD32(0x00000080) +#define TX_PIN_CFG_LNA_PE_A0_EN FIELD32(0x00000100) +#define TX_PIN_CFG_LNA_PE_G0_EN FIELD32(0x00000200) +#define TX_PIN_CFG_LNA_PE_A1_EN FIELD32(0x00000400) +#define TX_PIN_CFG_LNA_PE_G1_EN FIELD32(0x00000800) +#define TX_PIN_CFG_LNA_PE_A0_POL FIELD32(0x00001000) +#define TX_PIN_CFG_LNA_PE_G0_POL FIELD32(0x00002000) +#define TX_PIN_CFG_LNA_PE_A1_POL FIELD32(0x00004000) +#define TX_PIN_CFG_LNA_PE_G1_POL FIELD32(0x00008000) +#define TX_PIN_CFG_RFTR_EN FIELD32(0x00010000) +#define TX_PIN_CFG_RFTR_POL FIELD32(0x00020000) +#define TX_PIN_CFG_TRSW_EN FIELD32(0x00040000) +#define TX_PIN_CFG_TRSW_POL FIELD32(0x00080000) +#define TX_PIN_CFG_PA_PE_A2_EN FIELD32(0x01000000) +#define TX_PIN_CFG_PA_PE_G2_EN FIELD32(0x02000000) +#define TX_PIN_CFG_PA_PE_A2_POL FIELD32(0x04000000) +#define TX_PIN_CFG_PA_PE_G2_POL FIELD32(0x08000000) +#define TX_PIN_CFG_LNA_PE_A2_EN FIELD32(0x10000000) +#define TX_PIN_CFG_LNA_PE_G2_EN FIELD32(0x20000000) +#define TX_PIN_CFG_LNA_PE_A2_POL FIELD32(0x40000000) +#define TX_PIN_CFG_LNA_PE_G2_POL FIELD32(0x80000000) + +/* + * TX_BAND_CFG: 0x1 use upper 20MHz, 0x0 use lower 20MHz + */ +#define TX_BAND_CFG 0x132c +#define TX_BAND_CFG_HT40_MINUS FIELD32(0x00000001) +#define TX_BAND_CFG_A FIELD32(0x00000002) +#define TX_BAND_CFG_BG FIELD32(0x00000004) + +/* + * TX_SW_CFG0: + */ +#define TX_SW_CFG0 0x1330 + +/* + * TX_SW_CFG1: + */ +#define TX_SW_CFG1 0x1334 + +/* + * TX_SW_CFG2: + */ +#define TX_SW_CFG2 0x1338 + +/* + * TXOP_THRES_CFG: + */ +#define TXOP_THRES_CFG 0x133c + +/* + * TXOP_CTRL_CFG: + * TIMEOUT_TRUN_EN: Enable/Disable TXOP timeout truncation + * AC_TRUN_EN: Enable/Disable truncation for AC change + * TXRATEGRP_TRUN_EN: Enable/Disable truncation for TX rate group change + * USER_MODE_TRUN_EN: Enable/Disable truncation for user TXOP mode + * MIMO_PS_TRUN_EN: Enable/Disable truncation for MIMO PS RTS/CTS + * RESERVED_TRUN_EN: Reserved + * LSIG_TXOP_EN: Enable/Disable L-SIG TXOP protection + * EXT_CCA_EN: Enable/Disable extension channel CCA reference (Defer 40Mhz + * transmissions if extension CCA is clear). + * EXT_CCA_DLY: Extension CCA signal delay time (unit: us) + * EXT_CWMIN: CwMin for extension channel backoff + * 0: Disabled + * + */ +#define TXOP_CTRL_CFG 0x1340 +#define TXOP_CTRL_CFG_TIMEOUT_TRUN_EN FIELD32(0x00000001) +#define TXOP_CTRL_CFG_AC_TRUN_EN FIELD32(0x00000002) +#define TXOP_CTRL_CFG_TXRATEGRP_TRUN_EN FIELD32(0x00000004) +#define TXOP_CTRL_CFG_USER_MODE_TRUN_EN FIELD32(0x00000008) +#define TXOP_CTRL_CFG_MIMO_PS_TRUN_EN FIELD32(0x00000010) +#define TXOP_CTRL_CFG_RESERVED_TRUN_EN FIELD32(0x00000020) +#define TXOP_CTRL_CFG_LSIG_TXOP_EN FIELD32(0x00000040) +#define TXOP_CTRL_CFG_EXT_CCA_EN FIELD32(0x00000080) +#define TXOP_CTRL_CFG_EXT_CCA_DLY FIELD32(0x0000ff00) +#define TXOP_CTRL_CFG_EXT_CWMIN FIELD32(0x000f0000) + +/* + * TX_RTS_CFG: + * RTS_THRES: unit:byte + * RTS_FBK_EN: enable rts rate fallback + */ +#define TX_RTS_CFG 0x1344 +#define TX_RTS_CFG_AUTO_RTS_RETRY_LIMIT FIELD32(0x000000ff) +#define TX_RTS_CFG_RTS_THRES FIELD32(0x00ffff00) +#define TX_RTS_CFG_RTS_FBK_EN FIELD32(0x01000000) + +/* + * TX_TIMEOUT_CFG: + * MPDU_LIFETIME: expiration time = 2^(9+MPDU LIFE TIME) us + * RX_ACK_TIMEOUT: unit:slot. Used for TX procedure + * TX_OP_TIMEOUT: TXOP timeout value for TXOP truncation. + * it is recommended that: + * (SLOT_TIME) > (TX_OP_TIMEOUT) > (RX_ACK_TIMEOUT) + */ +#define TX_TIMEOUT_CFG 0x1348 +#define TX_TIMEOUT_CFG_MPDU_LIFETIME FIELD32(0x000000f0) +#define TX_TIMEOUT_CFG_RX_ACK_TIMEOUT FIELD32(0x0000ff00) +#define TX_TIMEOUT_CFG_TX_OP_TIMEOUT FIELD32(0x00ff0000) + +/* + * TX_RTY_CFG: + * SHORT_RTY_LIMIT: short retry limit + * LONG_RTY_LIMIT: long retry limit + * LONG_RTY_THRE: Long retry threshoold + * NON_AGG_RTY_MODE: Non-Aggregate MPDU retry mode + * 0:expired by retry limit, 1: expired by mpdu life timer + * AGG_RTY_MODE: Aggregate MPDU retry mode + * 0:expired by retry limit, 1: expired by mpdu life timer + * TX_AUTO_FB_ENABLE: Tx retry PHY rate auto fallback enable + */ +#define TX_RTY_CFG 0x134c +#define TX_RTY_CFG_SHORT_RTY_LIMIT FIELD32(0x000000ff) +#define TX_RTY_CFG_LONG_RTY_LIMIT FIELD32(0x0000ff00) +#define TX_RTY_CFG_LONG_RTY_THRE FIELD32(0x0fff0000) +#define TX_RTY_CFG_NON_AGG_RTY_MODE FIELD32(0x10000000) +#define TX_RTY_CFG_AGG_RTY_MODE FIELD32(0x20000000) +#define TX_RTY_CFG_TX_AUTO_FB_ENABLE FIELD32(0x40000000) + +/* + * TX_LINK_CFG: + * REMOTE_MFB_LIFETIME: remote MFB life time. unit: 32us + * MFB_ENABLE: TX apply remote MFB 1:enable + * REMOTE_UMFS_ENABLE: remote unsolicit MFB enable + * 0: not apply remote remote unsolicit (MFS=7) + * TX_MRQ_EN: MCS request TX enable + * TX_RDG_EN: RDG TX enable + * TX_CF_ACK_EN: Piggyback CF-ACK enable + * REMOTE_MFB: remote MCS feedback + * REMOTE_MFS: remote MCS feedback sequence number + */ +#define TX_LINK_CFG 0x1350 +#define TX_LINK_CFG_REMOTE_MFB_LIFETIME FIELD32(0x000000ff) +#define TX_LINK_CFG_MFB_ENABLE FIELD32(0x00000100) +#define TX_LINK_CFG_REMOTE_UMFS_ENABLE FIELD32(0x00000200) +#define TX_LINK_CFG_TX_MRQ_EN FIELD32(0x00000400) +#define TX_LINK_CFG_TX_RDG_EN FIELD32(0x00000800) +#define TX_LINK_CFG_TX_CF_ACK_EN FIELD32(0x00001000) +#define TX_LINK_CFG_REMOTE_MFB FIELD32(0x00ff0000) +#define TX_LINK_CFG_REMOTE_MFS FIELD32(0xff000000) + +/* + * HT_FBK_CFG0: + */ +#define HT_FBK_CFG0 0x1354 +#define HT_FBK_CFG0_HTMCS0FBK FIELD32(0x0000000f) +#define HT_FBK_CFG0_HTMCS1FBK FIELD32(0x000000f0) +#define HT_FBK_CFG0_HTMCS2FBK FIELD32(0x00000f00) +#define HT_FBK_CFG0_HTMCS3FBK FIELD32(0x0000f000) +#define HT_FBK_CFG0_HTMCS4FBK FIELD32(0x000f0000) +#define HT_FBK_CFG0_HTMCS5FBK FIELD32(0x00f00000) +#define HT_FBK_CFG0_HTMCS6FBK FIELD32(0x0f000000) +#define HT_FBK_CFG0_HTMCS7FBK FIELD32(0xf0000000) + +/* + * HT_FBK_CFG1: + */ +#define HT_FBK_CFG1 0x1358 +#define HT_FBK_CFG1_HTMCS8FBK FIELD32(0x0000000f) +#define HT_FBK_CFG1_HTMCS9FBK FIELD32(0x000000f0) +#define HT_FBK_CFG1_HTMCS10FBK FIELD32(0x00000f00) +#define HT_FBK_CFG1_HTMCS11FBK FIELD32(0x0000f000) +#define HT_FBK_CFG1_HTMCS12FBK FIELD32(0x000f0000) +#define HT_FBK_CFG1_HTMCS13FBK FIELD32(0x00f00000) +#define HT_FBK_CFG1_HTMCS14FBK FIELD32(0x0f000000) +#define HT_FBK_CFG1_HTMCS15FBK FIELD32(0xf0000000) + +/* + * LG_FBK_CFG0: + */ +#define LG_FBK_CFG0 0x135c +#define LG_FBK_CFG0_OFDMMCS0FBK FIELD32(0x0000000f) +#define LG_FBK_CFG0_OFDMMCS1FBK FIELD32(0x000000f0) +#define LG_FBK_CFG0_OFDMMCS2FBK FIELD32(0x00000f00) +#define LG_FBK_CFG0_OFDMMCS3FBK FIELD32(0x0000f000) +#define LG_FBK_CFG0_OFDMMCS4FBK FIELD32(0x000f0000) +#define LG_FBK_CFG0_OFDMMCS5FBK FIELD32(0x00f00000) +#define LG_FBK_CFG0_OFDMMCS6FBK FIELD32(0x0f000000) +#define LG_FBK_CFG0_OFDMMCS7FBK FIELD32(0xf0000000) + +/* + * LG_FBK_CFG1: + */ +#define LG_FBK_CFG1 0x1360 +#define LG_FBK_CFG0_CCKMCS0FBK FIELD32(0x0000000f) +#define LG_FBK_CFG0_CCKMCS1FBK FIELD32(0x000000f0) +#define LG_FBK_CFG0_CCKMCS2FBK FIELD32(0x00000f00) +#define LG_FBK_CFG0_CCKMCS3FBK FIELD32(0x0000f000) + +/* + * CCK_PROT_CFG: CCK Protection + * PROTECT_RATE: Protection control frame rate for CCK TX(RTS/CTS/CFEnd) + * PROTECT_CTRL: Protection control frame type for CCK TX + * 0:none, 1:RTS/CTS, 2:CTS-to-self + * PROTECT_NAV_SHORT: TXOP protection type for CCK TX with short NAV + * PROTECT_NAV_LONG: TXOP protection type for CCK TX with long NAV + * TX_OP_ALLOW_CCK: CCK TXOP allowance, 0:disallow + * TX_OP_ALLOW_OFDM: CCK TXOP allowance, 0:disallow + * TX_OP_ALLOW_MM20: CCK TXOP allowance, 0:disallow + * TX_OP_ALLOW_MM40: CCK TXOP allowance, 0:disallow + * TX_OP_ALLOW_GF20: CCK TXOP allowance, 0:disallow + * TX_OP_ALLOW_GF40: CCK TXOP allowance, 0:disallow + * RTS_TH_EN: RTS threshold enable on CCK TX + */ +#define CCK_PROT_CFG 0x1364 +#define CCK_PROT_CFG_PROTECT_RATE FIELD32(0x0000ffff) +#define CCK_PROT_CFG_PROTECT_CTRL FIELD32(0x00030000) +#define CCK_PROT_CFG_PROTECT_NAV_SHORT FIELD32(0x00040000) +#define CCK_PROT_CFG_PROTECT_NAV_LONG FIELD32(0x00080000) +#define CCK_PROT_CFG_TX_OP_ALLOW_CCK FIELD32(0x00100000) +#define CCK_PROT_CFG_TX_OP_ALLOW_OFDM FIELD32(0x00200000) +#define CCK_PROT_CFG_TX_OP_ALLOW_MM20 FIELD32(0x00400000) +#define CCK_PROT_CFG_TX_OP_ALLOW_MM40 FIELD32(0x00800000) +#define CCK_PROT_CFG_TX_OP_ALLOW_GF20 FIELD32(0x01000000) +#define CCK_PROT_CFG_TX_OP_ALLOW_GF40 FIELD32(0x02000000) +#define CCK_PROT_CFG_RTS_TH_EN FIELD32(0x04000000) + +/* + * OFDM_PROT_CFG: OFDM Protection + */ +#define OFDM_PROT_CFG 0x1368 +#define OFDM_PROT_CFG_PROTECT_RATE FIELD32(0x0000ffff) +#define OFDM_PROT_CFG_PROTECT_CTRL FIELD32(0x00030000) +#define OFDM_PROT_CFG_PROTECT_NAV_SHORT FIELD32(0x00040000) +#define OFDM_PROT_CFG_PROTECT_NAV_LONG FIELD32(0x00080000) +#define OFDM_PROT_CFG_TX_OP_ALLOW_CCK FIELD32(0x00100000) +#define OFDM_PROT_CFG_TX_OP_ALLOW_OFDM FIELD32(0x00200000) +#define OFDM_PROT_CFG_TX_OP_ALLOW_MM20 FIELD32(0x00400000) +#define OFDM_PROT_CFG_TX_OP_ALLOW_MM40 FIELD32(0x00800000) +#define OFDM_PROT_CFG_TX_OP_ALLOW_GF20 FIELD32(0x01000000) +#define OFDM_PROT_CFG_TX_OP_ALLOW_GF40 FIELD32(0x02000000) +#define OFDM_PROT_CFG_RTS_TH_EN FIELD32(0x04000000) + +/* + * MM20_PROT_CFG: MM20 Protection + */ +#define MM20_PROT_CFG 0x136c +#define MM20_PROT_CFG_PROTECT_RATE FIELD32(0x0000ffff) +#define MM20_PROT_CFG_PROTECT_CTRL FIELD32(0x00030000) +#define MM20_PROT_CFG_PROTECT_NAV_SHORT FIELD32(0x00040000) +#define MM20_PROT_CFG_PROTECT_NAV_LONG FIELD32(0x00080000) +#define MM20_PROT_CFG_TX_OP_ALLOW_CCK FIELD32(0x00100000) +#define MM20_PROT_CFG_TX_OP_ALLOW_OFDM FIELD32(0x00200000) +#define MM20_PROT_CFG_TX_OP_ALLOW_MM20 FIELD32(0x00400000) +#define MM20_PROT_CFG_TX_OP_ALLOW_MM40 FIELD32(0x00800000) +#define MM20_PROT_CFG_TX_OP_ALLOW_GF20 FIELD32(0x01000000) +#define MM20_PROT_CFG_TX_OP_ALLOW_GF40 FIELD32(0x02000000) +#define MM20_PROT_CFG_RTS_TH_EN FIELD32(0x04000000) + +/* + * MM40_PROT_CFG: MM40 Protection + */ +#define MM40_PROT_CFG 0x1370 +#define MM40_PROT_CFG_PROTECT_RATE FIELD32(0x0000ffff) +#define MM40_PROT_CFG_PROTECT_CTRL FIELD32(0x00030000) +#define MM40_PROT_CFG_PROTECT_NAV_SHORT FIELD32(0x00040000) +#define MM40_PROT_CFG_PROTECT_NAV_LONG FIELD32(0x00080000) +#define MM40_PROT_CFG_TX_OP_ALLOW_CCK FIELD32(0x00100000) +#define MM40_PROT_CFG_TX_OP_ALLOW_OFDM FIELD32(0x00200000) +#define MM40_PROT_CFG_TX_OP_ALLOW_MM20 FIELD32(0x00400000) +#define MM40_PROT_CFG_TX_OP_ALLOW_MM40 FIELD32(0x00800000) +#define MM40_PROT_CFG_TX_OP_ALLOW_GF20 FIELD32(0x01000000) +#define MM40_PROT_CFG_TX_OP_ALLOW_GF40 FIELD32(0x02000000) +#define MM40_PROT_CFG_RTS_TH_EN FIELD32(0x04000000) + +/* + * GF20_PROT_CFG: GF20 Protection + */ +#define GF20_PROT_CFG 0x1374 +#define GF20_PROT_CFG_PROTECT_RATE FIELD32(0x0000ffff) +#define GF20_PROT_CFG_PROTECT_CTRL FIELD32(0x00030000) +#define GF20_PROT_CFG_PROTECT_NAV_SHORT FIELD32(0x00040000) +#define GF20_PROT_CFG_PROTECT_NAV_LONG FIELD32(0x00080000) +#define GF20_PROT_CFG_TX_OP_ALLOW_CCK FIELD32(0x00100000) +#define GF20_PROT_CFG_TX_OP_ALLOW_OFDM FIELD32(0x00200000) +#define GF20_PROT_CFG_TX_OP_ALLOW_MM20 FIELD32(0x00400000) +#define GF20_PROT_CFG_TX_OP_ALLOW_MM40 FIELD32(0x00800000) +#define GF20_PROT_CFG_TX_OP_ALLOW_GF20 FIELD32(0x01000000) +#define GF20_PROT_CFG_TX_OP_ALLOW_GF40 FIELD32(0x02000000) +#define GF20_PROT_CFG_RTS_TH_EN FIELD32(0x04000000) + +/* + * GF40_PROT_CFG: GF40 Protection + */ +#define GF40_PROT_CFG 0x1378 +#define GF40_PROT_CFG_PROTECT_RATE FIELD32(0x0000ffff) +#define GF40_PROT_CFG_PROTECT_CTRL FIELD32(0x00030000) +#define GF40_PROT_CFG_PROTECT_NAV_SHORT FIELD32(0x00040000) +#define GF40_PROT_CFG_PROTECT_NAV_LONG FIELD32(0x00080000) +#define GF40_PROT_CFG_TX_OP_ALLOW_CCK FIELD32(0x00100000) +#define GF40_PROT_CFG_TX_OP_ALLOW_OFDM FIELD32(0x00200000) +#define GF40_PROT_CFG_TX_OP_ALLOW_MM20 FIELD32(0x00400000) +#define GF40_PROT_CFG_TX_OP_ALLOW_MM40 FIELD32(0x00800000) +#define GF40_PROT_CFG_TX_OP_ALLOW_GF20 FIELD32(0x01000000) +#define GF40_PROT_CFG_TX_OP_ALLOW_GF40 FIELD32(0x02000000) +#define GF40_PROT_CFG_RTS_TH_EN FIELD32(0x04000000) + +/* + * EXP_CTS_TIME: + */ +#define EXP_CTS_TIME 0x137c + +/* + * EXP_ACK_TIME: + */ +#define EXP_ACK_TIME 0x1380 + +/* + * RX_FILTER_CFG: RX configuration register. + */ +#define RX_FILTER_CFG 0x1400 +#define RX_FILTER_CFG_DROP_CRC_ERROR FIELD32(0x00000001) +#define RX_FILTER_CFG_DROP_PHY_ERROR FIELD32(0x00000002) +#define RX_FILTER_CFG_DROP_NOT_TO_ME FIELD32(0x00000004) +#define RX_FILTER_CFG_DROP_NOT_MY_BSSD FIELD32(0x00000008) +#define RX_FILTER_CFG_DROP_VER_ERROR FIELD32(0x00000010) +#define RX_FILTER_CFG_DROP_MULTICAST FIELD32(0x00000020) +#define RX_FILTER_CFG_DROP_BROADCAST FIELD32(0x00000040) +#define RX_FILTER_CFG_DROP_DUPLICATE FIELD32(0x00000080) +#define RX_FILTER_CFG_DROP_CF_END_ACK FIELD32(0x00000100) +#define RX_FILTER_CFG_DROP_CF_END FIELD32(0x00000200) +#define RX_FILTER_CFG_DROP_ACK FIELD32(0x00000400) +#define RX_FILTER_CFG_DROP_CTS FIELD32(0x00000800) +#define RX_FILTER_CFG_DROP_RTS FIELD32(0x00001000) +#define RX_FILTER_CFG_DROP_PSPOLL FIELD32(0x00002000) +#define RX_FILTER_CFG_DROP_BA FIELD32(0x00004000) +#define RX_FILTER_CFG_DROP_BAR FIELD32(0x00008000) +#define RX_FILTER_CFG_DROP_CNTL FIELD32(0x00010000) + +/* + * AUTO_RSP_CFG: + * AUTORESPONDER: 0: disable, 1: enable + * BAC_ACK_POLICY: 0:long, 1:short preamble + * CTS_40_MMODE: Response CTS 40MHz duplicate mode + * CTS_40_MREF: Response CTS 40MHz duplicate mode + * AR_PREAMBLE: Auto responder preamble 0:long, 1:short preamble + * DUAL_CTS_EN: Power bit value in control frame + * ACK_CTS_PSM_BIT:Power bit value in control frame + */ +#define AUTO_RSP_CFG 0x1404 +#define AUTO_RSP_CFG_AUTORESPONDER FIELD32(0x00000001) +#define AUTO_RSP_CFG_BAC_ACK_POLICY FIELD32(0x00000002) +#define AUTO_RSP_CFG_CTS_40_MMODE FIELD32(0x00000004) +#define AUTO_RSP_CFG_CTS_40_MREF FIELD32(0x00000008) +#define AUTO_RSP_CFG_AR_PREAMBLE FIELD32(0x00000010) +#define AUTO_RSP_CFG_DUAL_CTS_EN FIELD32(0x00000040) +#define AUTO_RSP_CFG_ACK_CTS_PSM_BIT FIELD32(0x00000080) + +/* + * LEGACY_BASIC_RATE: + */ +#define LEGACY_BASIC_RATE 0x1408 + +/* + * HT_BASIC_RATE: + */ +#define HT_BASIC_RATE 0x140c + +/* + * HT_CTRL_CFG: + */ +#define HT_CTRL_CFG 0x1410 + +/* + * SIFS_COST_CFG: + */ +#define SIFS_COST_CFG 0x1414 + +/* + * RX_PARSER_CFG: + * Set NAV for all received frames + */ +#define RX_PARSER_CFG 0x1418 + +/* + * TX_SEC_CNT0: + */ +#define TX_SEC_CNT0 0x1500 + +/* + * RX_SEC_CNT0: + */ +#define RX_SEC_CNT0 0x1504 + +/* + * CCMP_FC_MUTE: + */ +#define CCMP_FC_MUTE 0x1508 + +/* + * TXOP_HLDR_ADDR0: + */ +#define TXOP_HLDR_ADDR0 0x1600 + +/* + * TXOP_HLDR_ADDR1: + */ +#define TXOP_HLDR_ADDR1 0x1604 + +/* + * TXOP_HLDR_ET: + */ +#define TXOP_HLDR_ET 0x1608 + +/* + * QOS_CFPOLL_RA_DW0: + */ +#define QOS_CFPOLL_RA_DW0 0x160c + +/* + * QOS_CFPOLL_RA_DW1: + */ +#define QOS_CFPOLL_RA_DW1 0x1610 + +/* + * QOS_CFPOLL_QC: + */ +#define QOS_CFPOLL_QC 0x1614 + +/* + * RX_STA_CNT0: RX PLCP error count & RX CRC error count + */ +#define RX_STA_CNT0 0x1700 +#define RX_STA_CNT0_CRC_ERR FIELD32(0x0000ffff) +#define RX_STA_CNT0_PHY_ERR FIELD32(0xffff0000) + +/* + * RX_STA_CNT1: RX False CCA count & RX LONG frame count + */ +#define RX_STA_CNT1 0x1704 +#define RX_STA_CNT1_FALSE_CCA FIELD32(0x0000ffff) +#define RX_STA_CNT1_PLCP_ERR FIELD32(0xffff0000) + +/* + * RX_STA_CNT2: + */ +#define RX_STA_CNT2 0x1708 +#define RX_STA_CNT2_RX_DUPLI_COUNT FIELD32(0x0000ffff) +#define RX_STA_CNT2_RX_FIFO_OVERFLOW FIELD32(0xffff0000) + +/* + * TX_STA_CNT0: TX Beacon count + */ +#define TX_STA_CNT0 0x170c +#define TX_STA_CNT0_TX_FAIL_COUNT FIELD32(0x0000ffff) +#define TX_STA_CNT0_TX_BEACON_COUNT FIELD32(0xffff0000) + +/* + * TX_STA_CNT1: TX tx count + */ +#define TX_STA_CNT1 0x1710 +#define TX_STA_CNT1_TX_SUCCESS FIELD32(0x0000ffff) +#define TX_STA_CNT1_TX_RETRANSMIT FIELD32(0xffff0000) + +/* + * TX_STA_CNT2: TX tx count + */ +#define TX_STA_CNT2 0x1714 +#define TX_STA_CNT2_TX_ZERO_LEN_COUNT FIELD32(0x0000ffff) +#define TX_STA_CNT2_TX_UNDER_FLOW_COUNT FIELD32(0xffff0000) + +/* + * TX_STA_FIFO: TX Result for specific PID status fifo register. + * + * This register is implemented as FIFO with 16 entries in the HW. Each + * register read fetches the next tx result. If the FIFO is full because + * it wasn't read fast enough after the according interrupt (TX_FIFO_STATUS) + * triggered, the hw seems to simply drop further tx results. + * + * VALID: 1: this tx result is valid + * 0: no valid tx result -> driver should stop reading + * PID_TYPE: The PID latched from the PID field in the TXWI, can be used + * to match a frame with its tx result (even though the PID is + * only 4 bits wide). + * PID_QUEUE: Part of PID_TYPE, this is the queue index number (0-3) + * PID_ENTRY: Part of PID_TYPE, this is the queue entry index number (1-3) + * This identification number is calculated by ((idx % 3) + 1). + * TX_SUCCESS: Indicates tx success (1) or failure (0) + * TX_AGGRE: Indicates if the frame was part of an aggregate (1) or not (0) + * TX_ACK_REQUIRED: Indicates if the frame needed to get ack'ed (1) or not (0) + * WCID: The wireless client ID. + * MCS: The tx rate used during the last transmission of this frame, be it + * successful or not. + * PHYMODE: The phymode used for the transmission. + */ +#define TX_STA_FIFO 0x1718 +#define TX_STA_FIFO_VALID FIELD32(0x00000001) +#define TX_STA_FIFO_PID_TYPE FIELD32(0x0000001e) +#define TX_STA_FIFO_PID_QUEUE FIELD32(0x00000006) +#define TX_STA_FIFO_PID_ENTRY FIELD32(0x00000018) +#define TX_STA_FIFO_TX_SUCCESS FIELD32(0x00000020) +#define TX_STA_FIFO_TX_AGGRE FIELD32(0x00000040) +#define TX_STA_FIFO_TX_ACK_REQUIRED FIELD32(0x00000080) +#define TX_STA_FIFO_WCID FIELD32(0x0000ff00) +#define TX_STA_FIFO_SUCCESS_RATE FIELD32(0xffff0000) +#define TX_STA_FIFO_MCS FIELD32(0x007f0000) +#define TX_STA_FIFO_PHYMODE FIELD32(0xc0000000) + +/* + * TX_AGG_CNT: Debug counter + */ +#define TX_AGG_CNT 0x171c +#define TX_AGG_CNT_NON_AGG_TX_COUNT FIELD32(0x0000ffff) +#define TX_AGG_CNT_AGG_TX_COUNT FIELD32(0xffff0000) + +/* + * TX_AGG_CNT0: + */ +#define TX_AGG_CNT0 0x1720 +#define TX_AGG_CNT0_AGG_SIZE_1_COUNT FIELD32(0x0000ffff) +#define TX_AGG_CNT0_AGG_SIZE_2_COUNT FIELD32(0xffff0000) + +/* + * TX_AGG_CNT1: + */ +#define TX_AGG_CNT1 0x1724 +#define TX_AGG_CNT1_AGG_SIZE_3_COUNT FIELD32(0x0000ffff) +#define TX_AGG_CNT1_AGG_SIZE_4_COUNT FIELD32(0xffff0000) + +/* + * TX_AGG_CNT2: + */ +#define TX_AGG_CNT2 0x1728 +#define TX_AGG_CNT2_AGG_SIZE_5_COUNT FIELD32(0x0000ffff) +#define TX_AGG_CNT2_AGG_SIZE_6_COUNT FIELD32(0xffff0000) + +/* + * TX_AGG_CNT3: + */ +#define TX_AGG_CNT3 0x172c +#define TX_AGG_CNT3_AGG_SIZE_7_COUNT FIELD32(0x0000ffff) +#define TX_AGG_CNT3_AGG_SIZE_8_COUNT FIELD32(0xffff0000) + +/* + * TX_AGG_CNT4: + */ +#define TX_AGG_CNT4 0x1730 +#define TX_AGG_CNT4_AGG_SIZE_9_COUNT FIELD32(0x0000ffff) +#define TX_AGG_CNT4_AGG_SIZE_10_COUNT FIELD32(0xffff0000) + +/* + * TX_AGG_CNT5: + */ +#define TX_AGG_CNT5 0x1734 +#define TX_AGG_CNT5_AGG_SIZE_11_COUNT FIELD32(0x0000ffff) +#define TX_AGG_CNT5_AGG_SIZE_12_COUNT FIELD32(0xffff0000) + +/* + * TX_AGG_CNT6: + */ +#define TX_AGG_CNT6 0x1738 +#define TX_AGG_CNT6_AGG_SIZE_13_COUNT FIELD32(0x0000ffff) +#define TX_AGG_CNT6_AGG_SIZE_14_COUNT FIELD32(0xffff0000) + +/* + * TX_AGG_CNT7: + */ +#define TX_AGG_CNT7 0x173c +#define TX_AGG_CNT7_AGG_SIZE_15_COUNT FIELD32(0x0000ffff) +#define TX_AGG_CNT7_AGG_SIZE_16_COUNT FIELD32(0xffff0000) + +/* + * MPDU_DENSITY_CNT: + * TX_ZERO_DEL: TX zero length delimiter count + * RX_ZERO_DEL: RX zero length delimiter count + */ +#define MPDU_DENSITY_CNT 0x1740 +#define MPDU_DENSITY_CNT_TX_ZERO_DEL FIELD32(0x0000ffff) +#define MPDU_DENSITY_CNT_RX_ZERO_DEL FIELD32(0xffff0000) + +/* + * Security key table memory. + * + * The pairwise key table shares some memory with the beacon frame + * buffers 6 and 7. That basically means that when beacon 6 & 7 + * are used we should only use the reduced pairwise key table which + * has a maximum of 222 entries. + * + * --------------------------------------------- + * |0x4000 | Pairwise Key | Reduced Pairwise | + * | | Table | Key Table | + * | | Size: 256 * 32 | Size: 222 * 32 | + * |0x5BC0 | |------------------- + * | | | Beacon 6 | + * |0x5DC0 | |------------------- + * | | | Beacon 7 | + * |0x5FC0 | |------------------- + * |0x5FFF | | + * -------------------------- + * + * MAC_WCID_BASE: 8-bytes (use only 6 bytes) * 256 entry + * PAIRWISE_KEY_TABLE_BASE: 32-byte * 256 entry + * MAC_IVEIV_TABLE_BASE: 8-byte * 256-entry + * MAC_WCID_ATTRIBUTE_BASE: 4-byte * 256-entry + * SHARED_KEY_TABLE_BASE: 32-byte * 16-entry + * SHARED_KEY_MODE_BASE: 4-byte * 16-entry + */ +#define MAC_WCID_BASE 0x1800 +#define PAIRWISE_KEY_TABLE_BASE 0x4000 +#define MAC_IVEIV_TABLE_BASE 0x6000 +#define MAC_WCID_ATTRIBUTE_BASE 0x6800 +#define SHARED_KEY_TABLE_BASE 0x6c00 +#define SHARED_KEY_MODE_BASE 0x7000 + +#define MAC_WCID_ENTRY(__idx) \ + (MAC_WCID_BASE + ((__idx) * sizeof(struct mac_wcid_entry))) +#define PAIRWISE_KEY_ENTRY(__idx) \ + (PAIRWISE_KEY_TABLE_BASE + ((__idx) * sizeof(struct hw_key_entry))) +#define MAC_IVEIV_ENTRY(__idx) \ + (MAC_IVEIV_TABLE_BASE + ((__idx) * sizeof(struct mac_iveiv_entry))) +#define MAC_WCID_ATTR_ENTRY(__idx) \ + (MAC_WCID_ATTRIBUTE_BASE + ((__idx) * sizeof(u32))) +#define SHARED_KEY_ENTRY(__idx) \ + (SHARED_KEY_TABLE_BASE + ((__idx) * sizeof(struct hw_key_entry))) +#define SHARED_KEY_MODE_ENTRY(__idx) \ + (SHARED_KEY_MODE_BASE + ((__idx) * sizeof(u32))) + +struct mac_wcid_entry { + u8 mac[6]; + u8 reserved[2]; +} __packed; + +struct hw_key_entry { + u8 key[16]; + u8 tx_mic[8]; + u8 rx_mic[8]; +} __packed; + +struct mac_iveiv_entry { + u8 iv[8]; +} __packed; + +/* + * MAC_WCID_ATTRIBUTE: + */ +#define MAC_WCID_ATTRIBUTE_KEYTAB FIELD32(0x00000001) +#define MAC_WCID_ATTRIBUTE_CIPHER FIELD32(0x0000000e) +#define MAC_WCID_ATTRIBUTE_BSS_IDX FIELD32(0x00000070) +#define MAC_WCID_ATTRIBUTE_RX_WIUDF FIELD32(0x00000380) +#define MAC_WCID_ATTRIBUTE_CIPHER_EXT FIELD32(0x00000400) +#define MAC_WCID_ATTRIBUTE_BSS_IDX_EXT FIELD32(0x00000800) +#define MAC_WCID_ATTRIBUTE_WAPI_MCBC FIELD32(0x00008000) +#define MAC_WCID_ATTRIBUTE_WAPI_KEY_IDX FIELD32(0xff000000) + +/* + * SHARED_KEY_MODE: + */ +#define SHARED_KEY_MODE_BSS0_KEY0 FIELD32(0x00000007) +#define SHARED_KEY_MODE_BSS0_KEY1 FIELD32(0x00000070) +#define SHARED_KEY_MODE_BSS0_KEY2 FIELD32(0x00000700) +#define SHARED_KEY_MODE_BSS0_KEY3 FIELD32(0x00007000) +#define SHARED_KEY_MODE_BSS1_KEY0 FIELD32(0x00070000) +#define SHARED_KEY_MODE_BSS1_KEY1 FIELD32(0x00700000) +#define SHARED_KEY_MODE_BSS1_KEY2 FIELD32(0x07000000) +#define SHARED_KEY_MODE_BSS1_KEY3 FIELD32(0x70000000) + +/* + * HOST-MCU communication + */ + +/* + * H2M_MAILBOX_CSR: Host-to-MCU Mailbox. + * CMD_TOKEN: Command id, 0xff disable status reporting. + */ +#define H2M_MAILBOX_CSR 0x7010 +#define H2M_MAILBOX_CSR_ARG0 FIELD32(0x000000ff) +#define H2M_MAILBOX_CSR_ARG1 FIELD32(0x0000ff00) +#define H2M_MAILBOX_CSR_CMD_TOKEN FIELD32(0x00ff0000) +#define H2M_MAILBOX_CSR_OWNER FIELD32(0xff000000) + +/* + * H2M_MAILBOX_CID: + * Free slots contain 0xff. MCU will store command's token to lowest free slot. + * If all slots are occupied status will be dropped. + */ +#define H2M_MAILBOX_CID 0x7014 +#define H2M_MAILBOX_CID_CMD0 FIELD32(0x000000ff) +#define H2M_MAILBOX_CID_CMD1 FIELD32(0x0000ff00) +#define H2M_MAILBOX_CID_CMD2 FIELD32(0x00ff0000) +#define H2M_MAILBOX_CID_CMD3 FIELD32(0xff000000) + +/* + * H2M_MAILBOX_STATUS: + * Command status will be saved to same slot as command id. + */ +#define H2M_MAILBOX_STATUS 0x701c + +/* + * H2M_INT_SRC: + */ +#define H2M_INT_SRC 0x7024 + +/* + * H2M_BBP_AGENT: + */ +#define H2M_BBP_AGENT 0x7028 + +/* + * MCU_LEDCS: LED control for MCU Mailbox. + */ +#define MCU_LEDCS_LED_MODE FIELD8(0x1f) +#define MCU_LEDCS_POLARITY FIELD8(0x01) + +/* + * HW_CS_CTS_BASE: + * Carrier-sense CTS frame base address. + * It's where mac stores carrier-sense frame for carrier-sense function. + */ +#define HW_CS_CTS_BASE 0x7700 + +/* + * HW_DFS_CTS_BASE: + * DFS CTS frame base address. It's where mac stores CTS frame for DFS. + */ +#define HW_DFS_CTS_BASE 0x7780 + +/* + * TXRX control registers - base address 0x3000 + */ + +/* + * TXRX_CSR1: + * rt2860b UNKNOWN reg use R/O Reg Addr 0x77d0 first.. + */ +#define TXRX_CSR1 0x77d0 + +/* + * HW_DEBUG_SETTING_BASE: + * since NULL frame won't be that long (256 byte) + * We steal 16 tail bytes to save debugging settings + */ +#define HW_DEBUG_SETTING_BASE 0x77f0 +#define HW_DEBUG_SETTING_BASE2 0x7770 + +/* + * HW_BEACON_BASE + * In order to support maximum 8 MBSS and its maximum length + * is 512 bytes for each beacon + * Three section discontinue memory segments will be used. + * 1. The original region for BCN 0~3 + * 2. Extract memory from FCE table for BCN 4~5 + * 3. Extract memory from Pair-wise key table for BCN 6~7 + * It occupied those memory of wcid 238~253 for BCN 6 + * and wcid 222~237 for BCN 7 (see Security key table memory + * for more info). + * + * IMPORTANT NOTE: Not sure why legacy driver does this, + * but HW_BEACON_BASE7 is 0x0200 bytes below HW_BEACON_BASE6. + */ +#define HW_BEACON_BASE0 0x7800 +#define HW_BEACON_BASE1 0x7a00 +#define HW_BEACON_BASE2 0x7c00 +#define HW_BEACON_BASE3 0x7e00 +#define HW_BEACON_BASE4 0x7200 +#define HW_BEACON_BASE5 0x7400 +#define HW_BEACON_BASE6 0x5dc0 +#define HW_BEACON_BASE7 0x5bc0 + +#define HW_BEACON_OFFSET(__index) \ + (((__index) < 4) ? (HW_BEACON_BASE0 + (__index * 0x0200)) : \ + (((__index) < 6) ? (HW_BEACON_BASE4 + ((__index - 4) * 0x0200)) : \ + (HW_BEACON_BASE6 - ((__index - 6) * 0x0200)))) + +/* + * BBP registers. + * The wordsize of the BBP is 8 bits. + */ + +/* + * BBP 1: TX Antenna & Power Control + * POWER_CTRL: + * 0 - normal, + * 1 - drop tx power by 6dBm, + * 2 - drop tx power by 12dBm, + * 3 - increase tx power by 6dBm + */ +#define BBP1_TX_POWER_CTRL FIELD8(0x07) +#define BBP1_TX_ANTENNA FIELD8(0x18) + +/* + * BBP 3: RX Antenna + */ +#define BBP3_RX_ADC FIELD8(0x03) +#define BBP3_RX_ANTENNA FIELD8(0x18) +#define BBP3_HT40_MINUS FIELD8(0x20) +#define BBP3_ADC_MODE_SWITCH FIELD8(0x40) +#define BBP3_ADC_INIT_MODE FIELD8(0x80) + +/* + * BBP 4: Bandwidth + */ +#define BBP4_TX_BF FIELD8(0x01) +#define BBP4_BANDWIDTH FIELD8(0x18) +#define BBP4_MAC_IF_CTRL FIELD8(0x40) + +/* BBP27 */ +#define BBP27_RX_CHAIN_SEL FIELD8(0x60) + +/* + * BBP 47: Bandwidth + */ +#define BBP47_TSSI_REPORT_SEL FIELD8(0x03) +#define BBP47_TSSI_UPDATE_REQ FIELD8(0x04) +#define BBP47_TSSI_TSSI_MODE FIELD8(0x18) +#define BBP47_TSSI_ADC6 FIELD8(0x80) + +/* + * BBP 49 + */ +#define BBP49_UPDATE_FLAG FIELD8(0x01) + +/* + * BBP 105: + * - bit0: detect SIG on primary channel only (on 40MHz bandwidth) + * - bit1: FEQ (Feed Forward Compensation) for independend streams + * - bit2: MLD (Maximum Likehood Detection) for 2 streams (reserved on single + * stream) + * - bit4: channel estimation updates based on remodulation of + * L-SIG and HT-SIG symbols + */ +#define BBP105_DETECT_SIG_ON_PRIMARY FIELD8(0x01) +#define BBP105_FEQ FIELD8(0x02) +#define BBP105_MLD FIELD8(0x04) +#define BBP105_SIG_REMODULATION FIELD8(0x08) + +/* + * BBP 109 + */ +#define BBP109_TX0_POWER FIELD8(0x0f) +#define BBP109_TX1_POWER FIELD8(0xf0) + +/* + * BBP 138: Unknown + */ +#define BBP138_RX_ADC1 FIELD8(0x02) +#define BBP138_RX_ADC2 FIELD8(0x04) +#define BBP138_TX_DAC1 FIELD8(0x20) +#define BBP138_TX_DAC2 FIELD8(0x40) + +/* + * BBP 152: Rx Ant + */ +#define BBP152_RX_DEFAULT_ANT FIELD8(0x80) + +/* + * BBP 254: unknown + */ +#define BBP254_BIT7 FIELD8(0x80) + +/* + * RFCSR registers + * The wordsize of the RFCSR is 8 bits. + */ + +/* + * RFCSR 1: + */ +#define RFCSR1_RF_BLOCK_EN FIELD8(0x01) +#define RFCSR1_PLL_PD FIELD8(0x02) +#define RFCSR1_RX0_PD FIELD8(0x04) +#define RFCSR1_TX0_PD FIELD8(0x08) +#define RFCSR1_RX1_PD FIELD8(0x10) +#define RFCSR1_TX1_PD FIELD8(0x20) +#define RFCSR1_RX2_PD FIELD8(0x40) +#define RFCSR1_TX2_PD FIELD8(0x80) + +/* + * RFCSR 2: + */ +#define RFCSR2_RESCAL_EN FIELD8(0x80) + +/* + * RFCSR 3: + */ +#define RFCSR3_K FIELD8(0x0f) +/* Bits [7-4] for RF3320 (RT3370/RT3390), on other chipsets reserved */ +#define RFCSR3_PA1_BIAS_CCK FIELD8(0x70) +#define RFCSR3_PA2_CASCODE_BIAS_CCKK FIELD8(0x80) +/* Bits for RF3290/RF5360/RF5370/RF5372/RF5390/RF5392 */ +#define RFCSR3_VCOCAL_EN FIELD8(0x80) + +/* + * FRCSR 5: + */ +#define RFCSR5_R1 FIELD8(0x0c) + +/* + * RFCSR 6: + */ +#define RFCSR6_R1 FIELD8(0x03) +#define RFCSR6_R2 FIELD8(0x40) +#define RFCSR6_TXDIV FIELD8(0x0c) + +/* + * RFCSR 7: + */ +#define RFCSR7_RF_TUNING FIELD8(0x01) +#define RFCSR7_BIT1 FIELD8(0x02) +#define RFCSR7_BIT2 FIELD8(0x04) +#define RFCSR7_BIT3 FIELD8(0x08) +#define RFCSR7_BIT4 FIELD8(0x10) +#define RFCSR7_BIT5 FIELD8(0x20) +#define RFCSR7_BITS67 FIELD8(0xc0) + +/* + * RFCSR 9: + */ +#define RFCSR9_K FIELD8(0x0f) +#define RFCSR9_N FIELD8(0x10) +#define RFCSR9_UNKNOWN FIELD8(0x60) +#define RFCSR9_MOD FIELD8(0x80) + +/* + * RFCSR 11: + */ +#define RFCSR11_R FIELD8(0x03) +#define RFCSR11_MOD FIELD8(0xc0) + +/* + * RFCSR 12: + */ +#define RFCSR12_TX_POWER FIELD8(0x1f) +#define RFCSR12_DR0 FIELD8(0xe0) + +/* + * RFCSR 13: + */ +#define RFCSR13_TX_POWER FIELD8(0x1f) +#define RFCSR13_DR0 FIELD8(0xe0) + +/* + * RFCSR 15: + */ +#define RFCSR15_TX_LO2_EN FIELD8(0x08) + +/* + * RFCSR 16: + */ +#define RFCSR16_TXMIXER_GAIN FIELD8(0x07) + +/* + * RFCSR 17: + */ +#define RFCSR17_TXMIXER_GAIN FIELD8(0x07) +#define RFCSR17_TX_LO1_EN FIELD8(0x08) +#define RFCSR17_R FIELD8(0x20) +#define RFCSR17_CODE FIELD8(0x7f) + +/* + * RFCSR 20: + */ +#define RFCSR20_RX_LO1_EN FIELD8(0x08) + +/* + * RFCSR 21: + */ +#define RFCSR21_RX_LO2_EN FIELD8(0x08) + +/* + * RFCSR 22: + */ +#define RFCSR22_BASEBAND_LOOPBACK FIELD8(0x01) + +/* + * RFCSR 23: + */ +#define RFCSR23_FREQ_OFFSET FIELD8(0x7f) + +/* + * RFCSR 24: + */ +#define RFCSR24_TX_AGC_FC FIELD8(0x1f) +#define RFCSR24_TX_H20M FIELD8(0x20) +#define RFCSR24_TX_CALIB FIELD8(0x7f) + +/* + * RFCSR 27: + */ +#define RFCSR27_R1 FIELD8(0x03) +#define RFCSR27_R2 FIELD8(0x04) +#define RFCSR27_R3 FIELD8(0x30) +#define RFCSR27_R4 FIELD8(0x40) + +/* + * RFCSR 29: + */ +#define RFCSR29_ADC6_TEST FIELD8(0x01) +#define RFCSR29_ADC6_INT_TEST FIELD8(0x02) +#define RFCSR29_RSSI_RESET FIELD8(0x04) +#define RFCSR29_RSSI_ON FIELD8(0x08) +#define RFCSR29_RSSI_RIP_CTRL FIELD8(0x30) +#define RFCSR29_RSSI_GAIN FIELD8(0xc0) + +/* + * RFCSR 30: + */ +#define RFCSR30_TX_H20M FIELD8(0x02) +#define RFCSR30_RX_H20M FIELD8(0x04) +#define RFCSR30_RX_VCM FIELD8(0x18) +#define RFCSR30_RF_CALIBRATION FIELD8(0x80) + +/* + * RFCSR 31: + */ +#define RFCSR31_RX_AGC_FC FIELD8(0x1f) +#define RFCSR31_RX_H20M FIELD8(0x20) +#define RFCSR31_RX_CALIB FIELD8(0x7f) + +/* + * RFCSR 38: + */ +#define RFCSR38_RX_LO1_EN FIELD8(0x20) + +/* + * RFCSR 39: + */ +#define RFCSR39_RX_LO2_EN FIELD8(0x80) + +/* + * RFCSR 49: + */ +#define RFCSR49_TX FIELD8(0x3f) +#define RFCSR49_EP FIELD8(0xc0) + +/* + * RFCSR 50: + */ +#define RFCSR50_TX FIELD8(0x3f) +#define RFCSR50_EP FIELD8(0xc0) + +/* + * RF registers + */ + +/* + * RF 2 + */ +#define RF2_ANTENNA_RX2 FIELD32(0x00000040) +#define RF2_ANTENNA_TX1 FIELD32(0x00004000) +#define RF2_ANTENNA_RX1 FIELD32(0x00020000) + +/* + * RF 3 + */ +#define RF3_TXPOWER_G FIELD32(0x00003e00) +#define RF3_TXPOWER_A_7DBM_BOOST FIELD32(0x00000200) +#define RF3_TXPOWER_A FIELD32(0x00003c00) + +/* + * RF 4 + */ +#define RF4_TXPOWER_G FIELD32(0x000007c0) +#define RF4_TXPOWER_A_7DBM_BOOST FIELD32(0x00000040) +#define RF4_TXPOWER_A FIELD32(0x00000780) +#define RF4_FREQ_OFFSET FIELD32(0x001f8000) +#define RF4_HT40 FIELD32(0x00200000) + +/* + * EEPROM content. + * The wordsize of the EEPROM is 16 bits. + */ + +/* + * Chip ID + */ +#define EEPROM_CHIP_ID 0x0000 + +/* + * EEPROM Version + */ +#define EEPROM_VERSION 0x0001 +#define EEPROM_VERSION_FAE FIELD16(0x00ff) +#define EEPROM_VERSION_VERSION FIELD16(0xff00) + +/* + * HW MAC address. + */ +#define EEPROM_MAC_ADDR_0 0x0002 +#define EEPROM_MAC_ADDR_BYTE0 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE1 FIELD16(0xff00) +#define EEPROM_MAC_ADDR_1 0x0003 +#define EEPROM_MAC_ADDR_BYTE2 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE3 FIELD16(0xff00) +#define EEPROM_MAC_ADDR_2 0x0004 +#define EEPROM_MAC_ADDR_BYTE4 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE5 FIELD16(0xff00) + +/* + * EEPROM NIC Configuration 0 + * RXPATH: 1: 1R, 2: 2R, 3: 3R + * TXPATH: 1: 1T, 2: 2T, 3: 3T + * RF_TYPE: RFIC type + */ +#define EEPROM_NIC_CONF0 0x001a +#define EEPROM_NIC_CONF0_RXPATH FIELD16(0x000f) +#define EEPROM_NIC_CONF0_TXPATH FIELD16(0x00f0) +#define EEPROM_NIC_CONF0_RF_TYPE FIELD16(0x0f00) + +/* + * EEPROM NIC Configuration 1 + * HW_RADIO: 0: disable, 1: enable + * EXTERNAL_TX_ALC: 0: disable, 1: enable + * EXTERNAL_LNA_2G: 0: disable, 1: enable + * EXTERNAL_LNA_5G: 0: disable, 1: enable + * CARDBUS_ACCEL: 0: enable, 1: disable + * BW40M_SB_2G: 0: disable, 1: enable + * BW40M_SB_5G: 0: disable, 1: enable + * WPS_PBC: 0: disable, 1: enable + * BW40M_2G: 0: enable, 1: disable + * BW40M_5G: 0: enable, 1: disable + * BROADBAND_EXT_LNA: 0: disable, 1: enable + * ANT_DIVERSITY: 00: Disable, 01: Diversity, + * 10: Main antenna, 11: Aux antenna + * INTERNAL_TX_ALC: 0: disable, 1: enable + * BT_COEXIST: 0: disable, 1: enable + * DAC_TEST: 0: disable, 1: enable + */ +#define EEPROM_NIC_CONF1 0x001b +#define EEPROM_NIC_CONF1_HW_RADIO FIELD16(0x0001) +#define EEPROM_NIC_CONF1_EXTERNAL_TX_ALC FIELD16(0x0002) +#define EEPROM_NIC_CONF1_EXTERNAL_LNA_2G FIELD16(0x0004) +#define EEPROM_NIC_CONF1_EXTERNAL_LNA_5G FIELD16(0x0008) +#define EEPROM_NIC_CONF1_CARDBUS_ACCEL FIELD16(0x0010) +#define EEPROM_NIC_CONF1_BW40M_SB_2G FIELD16(0x0020) +#define EEPROM_NIC_CONF1_BW40M_SB_5G FIELD16(0x0040) +#define EEPROM_NIC_CONF1_WPS_PBC FIELD16(0x0080) +#define EEPROM_NIC_CONF1_BW40M_2G FIELD16(0x0100) +#define EEPROM_NIC_CONF1_BW40M_5G FIELD16(0x0200) +#define EEPROM_NIC_CONF1_BROADBAND_EXT_LNA FIELD16(0x400) +#define EEPROM_NIC_CONF1_ANT_DIVERSITY FIELD16(0x1800) +#define EEPROM_NIC_CONF1_INTERNAL_TX_ALC FIELD16(0x2000) +#define EEPROM_NIC_CONF1_BT_COEXIST FIELD16(0x4000) +#define EEPROM_NIC_CONF1_DAC_TEST FIELD16(0x8000) + +/* + * EEPROM frequency + */ +#define EEPROM_FREQ 0x001d +#define EEPROM_FREQ_OFFSET FIELD16(0x00ff) +#define EEPROM_FREQ_LED_MODE FIELD16(0x7f00) +#define EEPROM_FREQ_LED_POLARITY FIELD16(0x1000) + +/* + * EEPROM LED + * POLARITY_RDY_G: Polarity RDY_G setting. + * POLARITY_RDY_A: Polarity RDY_A setting. + * POLARITY_ACT: Polarity ACT setting. + * POLARITY_GPIO_0: Polarity GPIO0 setting. + * POLARITY_GPIO_1: Polarity GPIO1 setting. + * POLARITY_GPIO_2: Polarity GPIO2 setting. + * POLARITY_GPIO_3: Polarity GPIO3 setting. + * POLARITY_GPIO_4: Polarity GPIO4 setting. + * LED_MODE: Led mode. + */ +#define EEPROM_LED_AG_CONF 0x001e +#define EEPROM_LED_ACT_CONF 0x001f +#define EEPROM_LED_POLARITY 0x0020 +#define EEPROM_LED_POLARITY_RDY_BG FIELD16(0x0001) +#define EEPROM_LED_POLARITY_RDY_A FIELD16(0x0002) +#define EEPROM_LED_POLARITY_ACT FIELD16(0x0004) +#define EEPROM_LED_POLARITY_GPIO_0 FIELD16(0x0008) +#define EEPROM_LED_POLARITY_GPIO_1 FIELD16(0x0010) +#define EEPROM_LED_POLARITY_GPIO_2 FIELD16(0x0020) +#define EEPROM_LED_POLARITY_GPIO_3 FIELD16(0x0040) +#define EEPROM_LED_POLARITY_GPIO_4 FIELD16(0x0080) +#define EEPROM_LED_LED_MODE FIELD16(0x1f00) + +/* + * EEPROM NIC Configuration 2 + * RX_STREAM: 0: Reserved, 1: 1 Stream, 2: 2 Stream + * TX_STREAM: 0: Reserved, 1: 1 Stream, 2: 2 Stream + * CRYSTAL: 00: Reserved, 01: One crystal, 10: Two crystal, 11: Reserved + */ +#define EEPROM_NIC_CONF2 0x0021 +#define EEPROM_NIC_CONF2_RX_STREAM FIELD16(0x000f) +#define EEPROM_NIC_CONF2_TX_STREAM FIELD16(0x00f0) +#define EEPROM_NIC_CONF2_CRYSTAL FIELD16(0x0600) + +/* + * EEPROM LNA + */ +#define EEPROM_LNA 0x0022 +#define EEPROM_LNA_BG FIELD16(0x00ff) +#define EEPROM_LNA_A0 FIELD16(0xff00) + +/* + * EEPROM RSSI BG offset + */ +#define EEPROM_RSSI_BG 0x0023 +#define EEPROM_RSSI_BG_OFFSET0 FIELD16(0x00ff) +#define EEPROM_RSSI_BG_OFFSET1 FIELD16(0xff00) + +/* + * EEPROM RSSI BG2 offset + */ +#define EEPROM_RSSI_BG2 0x0024 +#define EEPROM_RSSI_BG2_OFFSET2 FIELD16(0x00ff) +#define EEPROM_RSSI_BG2_LNA_A1 FIELD16(0xff00) + +/* + * EEPROM TXMIXER GAIN BG offset (note overlaps with EEPROM RSSI BG2). + */ +#define EEPROM_TXMIXER_GAIN_BG 0x0024 +#define EEPROM_TXMIXER_GAIN_BG_VAL FIELD16(0x0007) + +/* + * EEPROM RSSI A offset + */ +#define EEPROM_RSSI_A 0x0025 +#define EEPROM_RSSI_A_OFFSET0 FIELD16(0x00ff) +#define EEPROM_RSSI_A_OFFSET1 FIELD16(0xff00) + +/* + * EEPROM RSSI A2 offset + */ +#define EEPROM_RSSI_A2 0x0026 +#define EEPROM_RSSI_A2_OFFSET2 FIELD16(0x00ff) +#define EEPROM_RSSI_A2_LNA_A2 FIELD16(0xff00) + +/* + * EEPROM TXMIXER GAIN A offset (note overlaps with EEPROM RSSI A2). + */ +#define EEPROM_TXMIXER_GAIN_A 0x0026 +#define EEPROM_TXMIXER_GAIN_A_VAL FIELD16(0x0007) + +/* + * EEPROM EIRP Maximum TX power values(unit: dbm) + */ +#define EEPROM_EIRP_MAX_TX_POWER 0x0027 +#define EEPROM_EIRP_MAX_TX_POWER_2GHZ FIELD16(0x00ff) +#define EEPROM_EIRP_MAX_TX_POWER_5GHZ FIELD16(0xff00) + +/* + * EEPROM TXpower delta: 20MHZ AND 40 MHZ use different power. + * This is delta in 40MHZ. + * VALUE: Tx Power dalta value, MAX=4(unit: dbm) + * TYPE: 1: Plus the delta value, 0: minus the delta value + * ENABLE: enable tx power compensation for 40BW + */ +#define EEPROM_TXPOWER_DELTA 0x0028 +#define EEPROM_TXPOWER_DELTA_VALUE_2G FIELD16(0x003f) +#define EEPROM_TXPOWER_DELTA_TYPE_2G FIELD16(0x0040) +#define EEPROM_TXPOWER_DELTA_ENABLE_2G FIELD16(0x0080) +#define EEPROM_TXPOWER_DELTA_VALUE_5G FIELD16(0x3f00) +#define EEPROM_TXPOWER_DELTA_TYPE_5G FIELD16(0x4000) +#define EEPROM_TXPOWER_DELTA_ENABLE_5G FIELD16(0x8000) + +/* + * EEPROM TXPOWER 802.11BG + */ +#define EEPROM_TXPOWER_BG1 0x0029 +#define EEPROM_TXPOWER_BG2 0x0030 +#define EEPROM_TXPOWER_BG_SIZE 7 +#define EEPROM_TXPOWER_BG_1 FIELD16(0x00ff) +#define EEPROM_TXPOWER_BG_2 FIELD16(0xff00) + +/* + * EEPROM temperature compensation boundaries 802.11BG + * MINUS4: If the actual TSSI is below this boundary, tx power needs to be + * reduced by (agc_step * -4) + * MINUS3: If the actual TSSI is below this boundary, tx power needs to be + * reduced by (agc_step * -3) + */ +#define EEPROM_TSSI_BOUND_BG1 0x0037 +#define EEPROM_TSSI_BOUND_BG1_MINUS4 FIELD16(0x00ff) +#define EEPROM_TSSI_BOUND_BG1_MINUS3 FIELD16(0xff00) + +/* + * EEPROM temperature compensation boundaries 802.11BG + * MINUS2: If the actual TSSI is below this boundary, tx power needs to be + * reduced by (agc_step * -2) + * MINUS1: If the actual TSSI is below this boundary, tx power needs to be + * reduced by (agc_step * -1) + */ +#define EEPROM_TSSI_BOUND_BG2 0x0038 +#define EEPROM_TSSI_BOUND_BG2_MINUS2 FIELD16(0x00ff) +#define EEPROM_TSSI_BOUND_BG2_MINUS1 FIELD16(0xff00) + +/* + * EEPROM temperature compensation boundaries 802.11BG + * REF: Reference TSSI value, no tx power changes needed + * PLUS1: If the actual TSSI is above this boundary, tx power needs to be + * increased by (agc_step * 1) + */ +#define EEPROM_TSSI_BOUND_BG3 0x0039 +#define EEPROM_TSSI_BOUND_BG3_REF FIELD16(0x00ff) +#define EEPROM_TSSI_BOUND_BG3_PLUS1 FIELD16(0xff00) + +/* + * EEPROM temperature compensation boundaries 802.11BG + * PLUS2: If the actual TSSI is above this boundary, tx power needs to be + * increased by (agc_step * 2) + * PLUS3: If the actual TSSI is above this boundary, tx power needs to be + * increased by (agc_step * 3) + */ +#define EEPROM_TSSI_BOUND_BG4 0x003a +#define EEPROM_TSSI_BOUND_BG4_PLUS2 FIELD16(0x00ff) +#define EEPROM_TSSI_BOUND_BG4_PLUS3 FIELD16(0xff00) + +/* + * EEPROM temperature compensation boundaries 802.11BG + * PLUS4: If the actual TSSI is above this boundary, tx power needs to be + * increased by (agc_step * 4) + * AGC_STEP: Temperature compensation step. + */ +#define EEPROM_TSSI_BOUND_BG5 0x003b +#define EEPROM_TSSI_BOUND_BG5_PLUS4 FIELD16(0x00ff) +#define EEPROM_TSSI_BOUND_BG5_AGC_STEP FIELD16(0xff00) + +/* + * EEPROM TXPOWER 802.11A + */ +#define EEPROM_TXPOWER_A1 0x003c +#define EEPROM_TXPOWER_A2 0x0053 +#define EEPROM_TXPOWER_A_SIZE 6 +#define EEPROM_TXPOWER_A_1 FIELD16(0x00ff) +#define EEPROM_TXPOWER_A_2 FIELD16(0xff00) + +/* + * EEPROM temperature compensation boundaries 802.11A + * MINUS4: If the actual TSSI is below this boundary, tx power needs to be + * reduced by (agc_step * -4) + * MINUS3: If the actual TSSI is below this boundary, tx power needs to be + * reduced by (agc_step * -3) + */ +#define EEPROM_TSSI_BOUND_A1 0x006a +#define EEPROM_TSSI_BOUND_A1_MINUS4 FIELD16(0x00ff) +#define EEPROM_TSSI_BOUND_A1_MINUS3 FIELD16(0xff00) + +/* + * EEPROM temperature compensation boundaries 802.11A + * MINUS2: If the actual TSSI is below this boundary, tx power needs to be + * reduced by (agc_step * -2) + * MINUS1: If the actual TSSI is below this boundary, tx power needs to be + * reduced by (agc_step * -1) + */ +#define EEPROM_TSSI_BOUND_A2 0x006b +#define EEPROM_TSSI_BOUND_A2_MINUS2 FIELD16(0x00ff) +#define EEPROM_TSSI_BOUND_A2_MINUS1 FIELD16(0xff00) + +/* + * EEPROM temperature compensation boundaries 802.11A + * REF: Reference TSSI value, no tx power changes needed + * PLUS1: If the actual TSSI is above this boundary, tx power needs to be + * increased by (agc_step * 1) + */ +#define EEPROM_TSSI_BOUND_A3 0x006c +#define EEPROM_TSSI_BOUND_A3_REF FIELD16(0x00ff) +#define EEPROM_TSSI_BOUND_A3_PLUS1 FIELD16(0xff00) + +/* + * EEPROM temperature compensation boundaries 802.11A + * PLUS2: If the actual TSSI is above this boundary, tx power needs to be + * increased by (agc_step * 2) + * PLUS3: If the actual TSSI is above this boundary, tx power needs to be + * increased by (agc_step * 3) + */ +#define EEPROM_TSSI_BOUND_A4 0x006d +#define EEPROM_TSSI_BOUND_A4_PLUS2 FIELD16(0x00ff) +#define EEPROM_TSSI_BOUND_A4_PLUS3 FIELD16(0xff00) + +/* + * EEPROM temperature compensation boundaries 802.11A + * PLUS4: If the actual TSSI is above this boundary, tx power needs to be + * increased by (agc_step * 4) + * AGC_STEP: Temperature compensation step. + */ +#define EEPROM_TSSI_BOUND_A5 0x006e +#define EEPROM_TSSI_BOUND_A5_PLUS4 FIELD16(0x00ff) +#define EEPROM_TSSI_BOUND_A5_AGC_STEP FIELD16(0xff00) + +/* + * EEPROM TXPOWER by rate: tx power per tx rate for HT20 mode + */ +#define EEPROM_TXPOWER_BYRATE 0x006f +#define EEPROM_TXPOWER_BYRATE_SIZE 9 + +#define EEPROM_TXPOWER_BYRATE_RATE0 FIELD16(0x000f) +#define EEPROM_TXPOWER_BYRATE_RATE1 FIELD16(0x00f0) +#define EEPROM_TXPOWER_BYRATE_RATE2 FIELD16(0x0f00) +#define EEPROM_TXPOWER_BYRATE_RATE3 FIELD16(0xf000) + +/* + * EEPROM BBP. + */ +#define EEPROM_BBP_START 0x0078 +#define EEPROM_BBP_SIZE 16 +#define EEPROM_BBP_VALUE FIELD16(0x00ff) +#define EEPROM_BBP_REG_ID FIELD16(0xff00) + +/* + * EEPROM IQ Calibration, unlike other entries those are byte addresses. + */ + +#define EEPROM_IQ_GAIN_CAL_TX0_2G 0x130 +#define EEPROM_IQ_PHASE_CAL_TX0_2G 0x131 +#define EEPROM_IQ_GROUPDELAY_CAL_TX0_2G 0x132 +#define EEPROM_IQ_GAIN_CAL_TX1_2G 0x133 +#define EEPROM_IQ_PHASE_CAL_TX1_2G 0x134 +#define EEPROM_IQ_GROUPDELAY_CAL_TX1_2G 0x135 +#define EEPROM_IQ_GAIN_CAL_RX0_2G 0x136 +#define EEPROM_IQ_PHASE_CAL_RX0_2G 0x137 +#define EEPROM_IQ_GROUPDELAY_CAL_RX0_2G 0x138 +#define EEPROM_IQ_GAIN_CAL_RX1_2G 0x139 +#define EEPROM_IQ_PHASE_CAL_RX1_2G 0x13A +#define EEPROM_IQ_GROUPDELAY_CAL_RX1_2G 0x13B +#define EEPROM_RF_IQ_COMPENSATION_CONTROL 0x13C +#define EEPROM_RF_IQ_IMBALANCE_COMPENSATION_CONTROL 0x13D +#define EEPROM_IQ_GAIN_CAL_TX0_CH36_TO_CH64_5G 0x144 +#define EEPROM_IQ_PHASE_CAL_TX0_CH36_TO_CH64_5G 0x145 +#define EEPROM_IQ_GAIN_CAL_TX0_CH100_TO_CH138_5G 0X146 +#define EEPROM_IQ_PHASE_CAL_TX0_CH100_TO_CH138_5G 0x147 +#define EEPROM_IQ_GAIN_CAL_TX0_CH140_TO_CH165_5G 0x148 +#define EEPROM_IQ_PHASE_CAL_TX0_CH140_TO_CH165_5G 0x149 +#define EEPROM_IQ_GAIN_CAL_TX1_CH36_TO_CH64_5G 0x14A +#define EEPROM_IQ_PHASE_CAL_TX1_CH36_TO_CH64_5G 0x14B +#define EEPROM_IQ_GAIN_CAL_TX1_CH100_TO_CH138_5G 0X14C +#define EEPROM_IQ_PHASE_CAL_TX1_CH100_TO_CH138_5G 0x14D +#define EEPROM_IQ_GAIN_CAL_TX1_CH140_TO_CH165_5G 0x14E +#define EEPROM_IQ_PHASE_CAL_TX1_CH140_TO_CH165_5G 0x14F +#define EEPROM_IQ_GROUPDELAY_CAL_TX0_CH36_TO_CH64_5G 0x150 +#define EEPROM_IQ_GROUPDELAY_CAL_TX1_CH36_TO_CH64_5G 0x151 +#define EEPROM_IQ_GROUPDELAY_CAL_TX0_CH100_TO_CH138_5G 0x152 +#define EEPROM_IQ_GROUPDELAY_CAL_TX1_CH100_TO_CH138_5G 0x153 +#define EEPROM_IQ_GROUPDELAY_CAL_TX0_CH140_TO_CH165_5G 0x154 +#define EEPROM_IQ_GROUPDELAY_CAL_TX1_CH140_TO_CH165_5G 0x155 +#define EEPROM_IQ_GAIN_CAL_RX0_CH36_TO_CH64_5G 0x156 +#define EEPROM_IQ_PHASE_CAL_RX0_CH36_TO_CH64_5G 0x157 +#define EEPROM_IQ_GAIN_CAL_RX0_CH100_TO_CH138_5G 0X158 +#define EEPROM_IQ_PHASE_CAL_RX0_CH100_TO_CH138_5G 0x159 +#define EEPROM_IQ_GAIN_CAL_RX0_CH140_TO_CH165_5G 0x15A +#define EEPROM_IQ_PHASE_CAL_RX0_CH140_TO_CH165_5G 0x15B +#define EEPROM_IQ_GAIN_CAL_RX1_CH36_TO_CH64_5G 0x15C +#define EEPROM_IQ_PHASE_CAL_RX1_CH36_TO_CH64_5G 0x15D +#define EEPROM_IQ_GAIN_CAL_RX1_CH100_TO_CH138_5G 0X15E +#define EEPROM_IQ_PHASE_CAL_RX1_CH100_TO_CH138_5G 0x15F +#define EEPROM_IQ_GAIN_CAL_RX1_CH140_TO_CH165_5G 0x160 +#define EEPROM_IQ_PHASE_CAL_RX1_CH140_TO_CH165_5G 0x161 +#define EEPROM_IQ_GROUPDELAY_CAL_RX0_CH36_TO_CH64_5G 0x162 +#define EEPROM_IQ_GROUPDELAY_CAL_RX1_CH36_TO_CH64_5G 0x163 +#define EEPROM_IQ_GROUPDELAY_CAL_RX0_CH100_TO_CH138_5G 0x164 +#define EEPROM_IQ_GROUPDELAY_CAL_RX1_CH100_TO_CH138_5G 0x165 +#define EEPROM_IQ_GROUPDELAY_CAL_RX0_CH140_TO_CH165_5G 0x166 +#define EEPROM_IQ_GROUPDELAY_CAL_RX1_CH140_TO_CH165_5G 0x167 + +/* + * MCU mailbox commands. + * MCU_SLEEP - go to power-save mode. + * arg1: 1: save as much power as possible, 0: save less power. + * status: 1: success, 2: already asleep, + * 3: maybe MAC is busy so can't finish this task. + * MCU_RADIO_OFF + * arg0: 0: do power-saving, NOT turn off radio. + */ +#define MCU_SLEEP 0x30 +#define MCU_WAKEUP 0x31 +#define MCU_RADIO_OFF 0x35 +#define MCU_CURRENT 0x36 +#define MCU_LED 0x50 +#define MCU_LED_STRENGTH 0x51 +#define MCU_LED_AG_CONF 0x52 +#define MCU_LED_ACT_CONF 0x53 +#define MCU_LED_LED_POLARITY 0x54 +#define MCU_RADAR 0x60 +#define MCU_BOOT_SIGNAL 0x72 +#define MCU_ANT_SELECT 0X73 +#define MCU_BBP_SIGNAL 0x80 +#define MCU_POWER_SAVE 0x83 +#define MCU_BAND_SELECT 0x91 + +/* + * MCU mailbox tokens + */ +#define TOKEN_SLEEP 1 +#define TOKEN_RADIO_OFF 2 +#define TOKEN_WAKEUP 3 + + +/* + * DMA descriptor defines. + */ +#define TXWI_DESC_SIZE (4 * sizeof(__le32)) +#define RXWI_DESC_SIZE (4 * sizeof(__le32)) + +#define TXWI_DESC_SIZE_5592 (5 * sizeof(__le32)) +#define RXWI_DESC_SIZE_5592 (6 * sizeof(__le32)) +/* + * TX WI structure + */ + +/* + * Word0 + * FRAG: 1 To inform TKIP engine this is a fragment. + * MIMO_PS: The remote peer is in dynamic MIMO-PS mode + * TX_OP: 0:HT TXOP rule , 1:PIFS TX ,2:Backoff, 3:sifs + * BW: Channel bandwidth 0:20MHz, 1:40 MHz (for legacy rates this will + * duplicate the frame to both channels). + * STBC: 1: STBC support MCS =0-7, 2,3 : RESERVED + * AMPDU: 1: this frame is eligible for AMPDU aggregation, the hw will + * aggregate consecutive frames with the same RA and QoS TID. If + * a frame A with the same RA and QoS TID but AMPDU=0 is queued + * directly after a frame B with AMPDU=1, frame A might still + * get aggregated into the AMPDU started by frame B. So, setting + * AMPDU to 0 does _not_ necessarily mean the frame is sent as + * MPDU, it can still end up in an AMPDU if the previous frame + * was tagged as AMPDU. + */ +#define TXWI_W0_FRAG FIELD32(0x00000001) +#define TXWI_W0_MIMO_PS FIELD32(0x00000002) +#define TXWI_W0_CF_ACK FIELD32(0x00000004) +#define TXWI_W0_TS FIELD32(0x00000008) +#define TXWI_W0_AMPDU FIELD32(0x00000010) +#define TXWI_W0_MPDU_DENSITY FIELD32(0x000000e0) +#define TXWI_W0_TX_OP FIELD32(0x00000300) +#define TXWI_W0_MCS FIELD32(0x007f0000) +#define TXWI_W0_BW FIELD32(0x00800000) +#define TXWI_W0_SHORT_GI FIELD32(0x01000000) +#define TXWI_W0_STBC FIELD32(0x06000000) +#define TXWI_W0_IFS FIELD32(0x08000000) +#define TXWI_W0_PHYMODE FIELD32(0xc0000000) + +/* + * Word1 + * ACK: 0: No Ack needed, 1: Ack needed + * NSEQ: 0: Don't assign hw sequence number, 1: Assign hw sequence number + * BW_WIN_SIZE: BA windows size of the recipient + * WIRELESS_CLI_ID: Client ID for WCID table access + * MPDU_TOTAL_BYTE_COUNT: Length of 802.11 frame + * PACKETID: Will be latched into the TX_STA_FIFO register once the according + * frame was processed. If multiple frames are aggregated together + * (AMPDU==1) the reported tx status will always contain the packet + * id of the first frame. 0: Don't report tx status for this frame. + * PACKETID_QUEUE: Part of PACKETID, This is the queue index (0-3) + * PACKETID_ENTRY: Part of PACKETID, THis is the queue entry index (1-3) + * This identification number is calculated by ((idx % 3) + 1). + * The (+1) is required to prevent PACKETID to become 0. + */ +#define TXWI_W1_ACK FIELD32(0x00000001) +#define TXWI_W1_NSEQ FIELD32(0x00000002) +#define TXWI_W1_BW_WIN_SIZE FIELD32(0x000000fc) +#define TXWI_W1_WIRELESS_CLI_ID FIELD32(0x0000ff00) +#define TXWI_W1_MPDU_TOTAL_BYTE_COUNT FIELD32(0x0fff0000) +#define TXWI_W1_PACKETID FIELD32(0xf0000000) +#define TXWI_W1_PACKETID_QUEUE FIELD32(0x30000000) +#define TXWI_W1_PACKETID_ENTRY FIELD32(0xc0000000) + +/* + * Word2 + */ +#define TXWI_W2_IV FIELD32(0xffffffff) + +/* + * Word3 + */ +#define TXWI_W3_EIV FIELD32(0xffffffff) + +/* + * RX WI structure + */ + +/* + * Word0 + */ +#define RXWI_W0_WIRELESS_CLI_ID FIELD32(0x000000ff) +#define RXWI_W0_KEY_INDEX FIELD32(0x00000300) +#define RXWI_W0_BSSID FIELD32(0x00001c00) +#define RXWI_W0_UDF FIELD32(0x0000e000) +#define RXWI_W0_MPDU_TOTAL_BYTE_COUNT FIELD32(0x0fff0000) +#define RXWI_W0_TID FIELD32(0xf0000000) + +/* + * Word1 + */ +#define RXWI_W1_FRAG FIELD32(0x0000000f) +#define RXWI_W1_SEQUENCE FIELD32(0x0000fff0) +#define RXWI_W1_MCS FIELD32(0x007f0000) +#define RXWI_W1_BW FIELD32(0x00800000) +#define RXWI_W1_SHORT_GI FIELD32(0x01000000) +#define RXWI_W1_STBC FIELD32(0x06000000) +#define RXWI_W1_PHYMODE FIELD32(0xc0000000) + +/* + * Word2 + */ +#define RXWI_W2_RSSI0 FIELD32(0x000000ff) +#define RXWI_W2_RSSI1 FIELD32(0x0000ff00) +#define RXWI_W2_RSSI2 FIELD32(0x00ff0000) + +/* + * Word3 + */ +#define RXWI_W3_SNR0 FIELD32(0x000000ff) +#define RXWI_W3_SNR1 FIELD32(0x0000ff00) + +/* + * Macros for converting txpower from EEPROM to mac80211 value + * and from mac80211 value to register value. + */ +#define MIN_G_TXPOWER 0 +#define MIN_A_TXPOWER -7 +#define MAX_G_TXPOWER 31 +#define MAX_A_TXPOWER 15 +#define DEFAULT_TXPOWER 5 + +#define TXPOWER_G_FROM_DEV(__txpower) \ + ((__txpower) > MAX_G_TXPOWER) ? DEFAULT_TXPOWER : (__txpower) + +#define TXPOWER_G_TO_DEV(__txpower) \ + clamp_t(char, __txpower, MIN_G_TXPOWER, MAX_G_TXPOWER) + +#define TXPOWER_A_FROM_DEV(__txpower) \ + ((__txpower) > MAX_A_TXPOWER) ? DEFAULT_TXPOWER : (__txpower) + +#define TXPOWER_A_TO_DEV(__txpower) \ + clamp_t(char, __txpower, MIN_A_TXPOWER, MAX_A_TXPOWER) + +/* + * Board's maximun TX power limitation + */ +#define EIRP_MAX_TX_POWER_LIMIT 0x50 + +/* + * Number of TBTT intervals after which we have to adjust + * the hw beacon timer. + */ +#define BCN_TBTT_OFFSET 64 + +/* + * RT2800 driver data structure + */ +struct rt2800_drv_data { + u8 calibration_bw20; + u8 calibration_bw40; + u8 bbp25; + u8 bbp26; + u8 txmixer_gain_24g; + u8 txmixer_gain_5g; + unsigned int tbtt_tick; +}; + +#endif /* RT2800_H */ diff --git a/drivers/net/wireless/rt2x00/rt2800lib.c b/drivers/net/wireless/rt2x00/rt2800lib.c new file mode 100644 index 0000000..e593603 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2800lib.c @@ -0,0 +1,6426 @@ +/* + Copyright (C) 2010 Willow Garage + Copyright (C) 2010 Ivo van Doorn + Copyright (C) 2009 Bartlomiej Zolnierkiewicz + Copyright (C) 2009 Gertjan van Wingerde + + Based on the original rt2800pci.c and rt2800usb.c. + Copyright (C) 2009 Alban Browaeys + Copyright (C) 2009 Felix Fietkau + Copyright (C) 2009 Luis Correia + Copyright (C) 2009 Mattias Nissler + Copyright (C) 2009 Mark Asselstine + Copyright (C) 2009 Xose Vazquez Perez + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2800lib + Abstract: rt2800 generic device routines. + */ + +#include +#include +#include +#include + +#include "rt2x00.h" +#include "rt2800lib.h" +#include "rt2800.h" + +/* + * Register access. + * All access to the CSR registers will go through the methods + * rt2800_register_read and rt2800_register_write. + * BBP and RF register require indirect register access, + * and use the CSR registers BBPCSR and RFCSR to achieve this. + * These indirect registers work with busy bits, + * and we will try maximal REGISTER_BUSY_COUNT times to access + * the register while taking a REGISTER_BUSY_DELAY us delay + * between each attampt. When the busy bit is still set at that time, + * the access attempt is considered to have failed, + * and we will print an error. + * The _lock versions must be used if you already hold the csr_mutex + */ +#define WAIT_FOR_BBP(__dev, __reg) \ + rt2800_regbusy_read((__dev), BBP_CSR_CFG, BBP_CSR_CFG_BUSY, (__reg)) +#define WAIT_FOR_RFCSR(__dev, __reg) \ + rt2800_regbusy_read((__dev), RF_CSR_CFG, RF_CSR_CFG_BUSY, (__reg)) +#define WAIT_FOR_RF(__dev, __reg) \ + rt2800_regbusy_read((__dev), RF_CSR_CFG0, RF_CSR_CFG0_BUSY, (__reg)) +#define WAIT_FOR_MCU(__dev, __reg) \ + rt2800_regbusy_read((__dev), H2M_MAILBOX_CSR, \ + H2M_MAILBOX_CSR_OWNER, (__reg)) + +static inline bool rt2800_is_305x_soc(struct rt2x00_dev *rt2x00dev) +{ + /* check for rt2872 on SoC */ + if (!rt2x00_is_soc(rt2x00dev) || + !rt2x00_rt(rt2x00dev, RT2872)) + return false; + + /* we know for sure that these rf chipsets are used on rt305x boards */ + if (rt2x00_rf(rt2x00dev, RF3020) || + rt2x00_rf(rt2x00dev, RF3021) || + rt2x00_rf(rt2x00dev, RF3022)) + return true; + + rt2x00_warn(rt2x00dev, "Unknown RF chipset on rt305x\n"); + return false; +} + +static void rt2800_bbp_write(struct rt2x00_dev *rt2x00dev, + const unsigned int word, const u8 value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the BBP becomes available, afterwards we + * can safely write the new data into the register. + */ + if (WAIT_FOR_BBP(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, BBP_CSR_CFG_VALUE, value); + rt2x00_set_field32(®, BBP_CSR_CFG_REGNUM, word); + rt2x00_set_field32(®, BBP_CSR_CFG_BUSY, 1); + rt2x00_set_field32(®, BBP_CSR_CFG_READ_CONTROL, 0); + rt2x00_set_field32(®, BBP_CSR_CFG_BBP_RW_MODE, 1); + + rt2800_register_write_lock(rt2x00dev, BBP_CSR_CFG, reg); + } + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt2800_bbp_read(struct rt2x00_dev *rt2x00dev, + const unsigned int word, u8 *value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the BBP becomes available, afterwards we + * can safely write the read request into the register. + * After the data has been written, we wait until hardware + * returns the correct value, if at any time the register + * doesn't become available in time, reg will be 0xffffffff + * which means we return 0xff to the caller. + */ + if (WAIT_FOR_BBP(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, BBP_CSR_CFG_REGNUM, word); + rt2x00_set_field32(®, BBP_CSR_CFG_BUSY, 1); + rt2x00_set_field32(®, BBP_CSR_CFG_READ_CONTROL, 1); + rt2x00_set_field32(®, BBP_CSR_CFG_BBP_RW_MODE, 1); + + rt2800_register_write_lock(rt2x00dev, BBP_CSR_CFG, reg); + + WAIT_FOR_BBP(rt2x00dev, ®); + } + + *value = rt2x00_get_field32(reg, BBP_CSR_CFG_VALUE); + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt2800_rfcsr_write(struct rt2x00_dev *rt2x00dev, + const unsigned int word, const u8 value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the RFCSR becomes available, afterwards we + * can safely write the new data into the register. + */ + if (WAIT_FOR_RFCSR(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, RF_CSR_CFG_DATA, value); + rt2x00_set_field32(®, RF_CSR_CFG_REGNUM, word); + rt2x00_set_field32(®, RF_CSR_CFG_WRITE, 1); + rt2x00_set_field32(®, RF_CSR_CFG_BUSY, 1); + + rt2800_register_write_lock(rt2x00dev, RF_CSR_CFG, reg); + } + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt2800_rfcsr_read(struct rt2x00_dev *rt2x00dev, + const unsigned int word, u8 *value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the RFCSR becomes available, afterwards we + * can safely write the read request into the register. + * After the data has been written, we wait until hardware + * returns the correct value, if at any time the register + * doesn't become available in time, reg will be 0xffffffff + * which means we return 0xff to the caller. + */ + if (WAIT_FOR_RFCSR(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, RF_CSR_CFG_REGNUM, word); + rt2x00_set_field32(®, RF_CSR_CFG_WRITE, 0); + rt2x00_set_field32(®, RF_CSR_CFG_BUSY, 1); + + rt2800_register_write_lock(rt2x00dev, RF_CSR_CFG, reg); + + WAIT_FOR_RFCSR(rt2x00dev, ®); + } + + *value = rt2x00_get_field32(reg, RF_CSR_CFG_DATA); + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt2800_rf_write(struct rt2x00_dev *rt2x00dev, + const unsigned int word, const u32 value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the RF becomes available, afterwards we + * can safely write the new data into the register. + */ + if (WAIT_FOR_RF(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, RF_CSR_CFG0_REG_VALUE_BW, value); + rt2x00_set_field32(®, RF_CSR_CFG0_STANDBYMODE, 0); + rt2x00_set_field32(®, RF_CSR_CFG0_SEL, 0); + rt2x00_set_field32(®, RF_CSR_CFG0_BUSY, 1); + + rt2800_register_write_lock(rt2x00dev, RF_CSR_CFG0, reg); + rt2x00_rf_write(rt2x00dev, word, value); + } + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static int rt2800_enable_wlan_rt3290(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + int i, count; + + rt2800_register_read(rt2x00dev, WLAN_FUN_CTRL, ®); + if (rt2x00_get_field32(reg, WLAN_EN)) + return 0; + + rt2x00_set_field32(®, WLAN_GPIO_OUT_OE_BIT_ALL, 0xff); + rt2x00_set_field32(®, FRC_WL_ANT_SET, 1); + rt2x00_set_field32(®, WLAN_CLK_EN, 0); + rt2x00_set_field32(®, WLAN_EN, 1); + rt2800_register_write(rt2x00dev, WLAN_FUN_CTRL, reg); + + udelay(REGISTER_BUSY_DELAY); + + count = 0; + do { + /* + * Check PLL_LD & XTAL_RDY. + */ + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2800_register_read(rt2x00dev, CMB_CTRL, ®); + if (rt2x00_get_field32(reg, PLL_LD) && + rt2x00_get_field32(reg, XTAL_RDY)) + break; + udelay(REGISTER_BUSY_DELAY); + } + + if (i >= REGISTER_BUSY_COUNT) { + + if (count >= 10) + return -EIO; + + rt2800_register_write(rt2x00dev, 0x58, 0x018); + udelay(REGISTER_BUSY_DELAY); + rt2800_register_write(rt2x00dev, 0x58, 0x418); + udelay(REGISTER_BUSY_DELAY); + rt2800_register_write(rt2x00dev, 0x58, 0x618); + udelay(REGISTER_BUSY_DELAY); + count++; + } else { + count = 0; + } + + rt2800_register_read(rt2x00dev, WLAN_FUN_CTRL, ®); + rt2x00_set_field32(®, PCIE_APP0_CLK_REQ, 0); + rt2x00_set_field32(®, WLAN_CLK_EN, 1); + rt2x00_set_field32(®, WLAN_RESET, 1); + rt2800_register_write(rt2x00dev, WLAN_FUN_CTRL, reg); + udelay(10); + rt2x00_set_field32(®, WLAN_RESET, 0); + rt2800_register_write(rt2x00dev, WLAN_FUN_CTRL, reg); + udelay(10); + rt2800_register_write(rt2x00dev, INT_SOURCE_CSR, 0x7fffffff); + } while (count != 0); + + return 0; +} + +void rt2800_mcu_request(struct rt2x00_dev *rt2x00dev, + const u8 command, const u8 token, + const u8 arg0, const u8 arg1) +{ + u32 reg; + + /* + * SOC devices don't support MCU requests. + */ + if (rt2x00_is_soc(rt2x00dev)) + return; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the MCU becomes available, afterwards we + * can safely write the new data into the register. + */ + if (WAIT_FOR_MCU(rt2x00dev, ®)) { + rt2x00_set_field32(®, H2M_MAILBOX_CSR_OWNER, 1); + rt2x00_set_field32(®, H2M_MAILBOX_CSR_CMD_TOKEN, token); + rt2x00_set_field32(®, H2M_MAILBOX_CSR_ARG0, arg0); + rt2x00_set_field32(®, H2M_MAILBOX_CSR_ARG1, arg1); + rt2800_register_write_lock(rt2x00dev, H2M_MAILBOX_CSR, reg); + + reg = 0; + rt2x00_set_field32(®, HOST_CMD_CSR_HOST_COMMAND, command); + rt2800_register_write_lock(rt2x00dev, HOST_CMD_CSR, reg); + } + + mutex_unlock(&rt2x00dev->csr_mutex); +} +EXPORT_SYMBOL_GPL(rt2800_mcu_request); + +int rt2800_wait_csr_ready(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i = 0; + u32 reg; + + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2800_register_read(rt2x00dev, MAC_CSR0, ®); + if (reg && reg != ~0) + return 0; + msleep(1); + } + + rt2x00_err(rt2x00dev, "Unstable hardware\n"); + return -EBUSY; +} +EXPORT_SYMBOL_GPL(rt2800_wait_csr_ready); + +int rt2800_wait_wpdma_ready(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i; + u32 reg; + + /* + * Some devices are really slow to respond here. Wait a whole second + * before timing out. + */ + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2800_register_read(rt2x00dev, WPDMA_GLO_CFG, ®); + if (!rt2x00_get_field32(reg, WPDMA_GLO_CFG_TX_DMA_BUSY) && + !rt2x00_get_field32(reg, WPDMA_GLO_CFG_RX_DMA_BUSY)) + return 0; + + msleep(10); + } + + rt2x00_err(rt2x00dev, "WPDMA TX/RX busy [0x%08x]\n", reg); + return -EACCES; +} +EXPORT_SYMBOL_GPL(rt2800_wait_wpdma_ready); + +void rt2800_disable_wpdma(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + rt2800_register_read(rt2x00dev, WPDMA_GLO_CFG, ®); + rt2x00_set_field32(®, WPDMA_GLO_CFG_ENABLE_TX_DMA, 0); + rt2x00_set_field32(®, WPDMA_GLO_CFG_TX_DMA_BUSY, 0); + rt2x00_set_field32(®, WPDMA_GLO_CFG_ENABLE_RX_DMA, 0); + rt2x00_set_field32(®, WPDMA_GLO_CFG_RX_DMA_BUSY, 0); + rt2x00_set_field32(®, WPDMA_GLO_CFG_TX_WRITEBACK_DONE, 1); + rt2800_register_write(rt2x00dev, WPDMA_GLO_CFG, reg); +} +EXPORT_SYMBOL_GPL(rt2800_disable_wpdma); + +static bool rt2800_check_firmware_crc(const u8 *data, const size_t len) +{ + u16 fw_crc; + u16 crc; + + /* + * The last 2 bytes in the firmware array are the crc checksum itself, + * this means that we should never pass those 2 bytes to the crc + * algorithm. + */ + fw_crc = (data[len - 2] << 8 | data[len - 1]); + + /* + * Use the crc ccitt algorithm. + * This will return the same value as the legacy driver which + * used bit ordering reversion on the both the firmware bytes + * before input input as well as on the final output. + * Obviously using crc ccitt directly is much more efficient. + */ + crc = crc_ccitt(~0, data, len - 2); + + /* + * There is a small difference between the crc-itu-t + bitrev and + * the crc-ccitt crc calculation. In the latter method the 2 bytes + * will be swapped, use swab16 to convert the crc to the correct + * value. + */ + crc = swab16(crc); + + return fw_crc == crc; +} + +int rt2800_check_firmware(struct rt2x00_dev *rt2x00dev, + const u8 *data, const size_t len) +{ + size_t offset = 0; + size_t fw_len; + bool multiple; + + /* + * PCI(e) & SOC devices require firmware with a length + * of 8kb. USB devices require firmware files with a length + * of 4kb. Certain USB chipsets however require different firmware, + * which Ralink only provides attached to the original firmware + * file. Thus for USB devices, firmware files have a length + * which is a multiple of 4kb. The firmware for rt3290 chip also + * have a length which is a multiple of 4kb. + */ + if (rt2x00_is_usb(rt2x00dev) || rt2x00_rt(rt2x00dev, RT3290)) + fw_len = 4096; + else + fw_len = 8192; + + multiple = true; + /* + * Validate the firmware length + */ + if (len != fw_len && (!multiple || (len % fw_len) != 0)) + return FW_BAD_LENGTH; + + /* + * Check if the chipset requires one of the upper parts + * of the firmware. + */ + if (rt2x00_is_usb(rt2x00dev) && + !rt2x00_rt(rt2x00dev, RT2860) && + !rt2x00_rt(rt2x00dev, RT2872) && + !rt2x00_rt(rt2x00dev, RT3070) && + ((len / fw_len) == 1)) + return FW_BAD_VERSION; + + /* + * 8kb firmware files must be checked as if it were + * 2 separate firmware files. + */ + while (offset < len) { + if (!rt2800_check_firmware_crc(data + offset, fw_len)) + return FW_BAD_CRC; + + offset += fw_len; + } + + return FW_OK; +} +EXPORT_SYMBOL_GPL(rt2800_check_firmware); + +int rt2800_load_firmware(struct rt2x00_dev *rt2x00dev, + const u8 *data, const size_t len) +{ + unsigned int i; + u32 reg; + int retval; + + if (rt2x00_rt(rt2x00dev, RT3290)) { + retval = rt2800_enable_wlan_rt3290(rt2x00dev); + if (retval) + return -EBUSY; + } + + /* + * If driver doesn't wake up firmware here, + * rt2800_load_firmware will hang forever when interface is up again. + */ + rt2800_register_write(rt2x00dev, AUTOWAKEUP_CFG, 0x00000000); + + /* + * Wait for stable hardware. + */ + if (rt2800_wait_csr_ready(rt2x00dev)) + return -EBUSY; + + if (rt2x00_is_pci(rt2x00dev)) { + if (rt2x00_rt(rt2x00dev, RT3290) || + rt2x00_rt(rt2x00dev, RT3572) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) { + rt2800_register_read(rt2x00dev, AUX_CTRL, ®); + rt2x00_set_field32(®, AUX_CTRL_FORCE_PCIE_CLK, 1); + rt2x00_set_field32(®, AUX_CTRL_WAKE_PCIE_EN, 1); + rt2800_register_write(rt2x00dev, AUX_CTRL, reg); + } + rt2800_register_write(rt2x00dev, PWR_PIN_CFG, 0x00000002); + } + + rt2800_disable_wpdma(rt2x00dev); + + /* + * Write firmware to the device. + */ + rt2800_drv_write_firmware(rt2x00dev, data, len); + + /* + * Wait for device to stabilize. + */ + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2800_register_read(rt2x00dev, PBF_SYS_CTRL, ®); + if (rt2x00_get_field32(reg, PBF_SYS_CTRL_READY)) + break; + msleep(1); + } + + if (i == REGISTER_BUSY_COUNT) { + rt2x00_err(rt2x00dev, "PBF system register not ready\n"); + return -EBUSY; + } + + /* + * Disable DMA, will be reenabled later when enabling + * the radio. + */ + rt2800_disable_wpdma(rt2x00dev); + + /* + * Initialize firmware. + */ + rt2800_register_write(rt2x00dev, H2M_BBP_AGENT, 0); + rt2800_register_write(rt2x00dev, H2M_MAILBOX_CSR, 0); + if (rt2x00_is_usb(rt2x00dev)) { + rt2800_register_write(rt2x00dev, H2M_INT_SRC, 0); + rt2800_mcu_request(rt2x00dev, MCU_BOOT_SIGNAL, 0, 0, 0); + } + msleep(1); + + return 0; +} +EXPORT_SYMBOL_GPL(rt2800_load_firmware); + +void rt2800_write_tx_data(struct queue_entry *entry, + struct txentry_desc *txdesc) +{ + __le32 *txwi = rt2800_drv_get_txwi(entry); + u32 word; + int i; + + /* + * Initialize TX Info descriptor + */ + rt2x00_desc_read(txwi, 0, &word); + rt2x00_set_field32(&word, TXWI_W0_FRAG, + test_bit(ENTRY_TXD_MORE_FRAG, &txdesc->flags)); + rt2x00_set_field32(&word, TXWI_W0_MIMO_PS, + test_bit(ENTRY_TXD_HT_MIMO_PS, &txdesc->flags)); + rt2x00_set_field32(&word, TXWI_W0_CF_ACK, 0); + rt2x00_set_field32(&word, TXWI_W0_TS, + test_bit(ENTRY_TXD_REQ_TIMESTAMP, &txdesc->flags)); + rt2x00_set_field32(&word, TXWI_W0_AMPDU, + test_bit(ENTRY_TXD_HT_AMPDU, &txdesc->flags)); + rt2x00_set_field32(&word, TXWI_W0_MPDU_DENSITY, + txdesc->u.ht.mpdu_density); + rt2x00_set_field32(&word, TXWI_W0_TX_OP, txdesc->u.ht.txop); + rt2x00_set_field32(&word, TXWI_W0_MCS, txdesc->u.ht.mcs); + rt2x00_set_field32(&word, TXWI_W0_BW, + test_bit(ENTRY_TXD_HT_BW_40, &txdesc->flags)); + rt2x00_set_field32(&word, TXWI_W0_SHORT_GI, + test_bit(ENTRY_TXD_HT_SHORT_GI, &txdesc->flags)); + rt2x00_set_field32(&word, TXWI_W0_STBC, txdesc->u.ht.stbc); + rt2x00_set_field32(&word, TXWI_W0_PHYMODE, txdesc->rate_mode); + rt2x00_desc_write(txwi, 0, word); + + rt2x00_desc_read(txwi, 1, &word); + rt2x00_set_field32(&word, TXWI_W1_ACK, + test_bit(ENTRY_TXD_ACK, &txdesc->flags)); + rt2x00_set_field32(&word, TXWI_W1_NSEQ, + test_bit(ENTRY_TXD_GENERATE_SEQ, &txdesc->flags)); + rt2x00_set_field32(&word, TXWI_W1_BW_WIN_SIZE, txdesc->u.ht.ba_size); + rt2x00_set_field32(&word, TXWI_W1_WIRELESS_CLI_ID, + test_bit(ENTRY_TXD_ENCRYPT, &txdesc->flags) ? + txdesc->key_idx : txdesc->u.ht.wcid); + rt2x00_set_field32(&word, TXWI_W1_MPDU_TOTAL_BYTE_COUNT, + txdesc->length); + rt2x00_set_field32(&word, TXWI_W1_PACKETID_QUEUE, entry->queue->qid); + rt2x00_set_field32(&word, TXWI_W1_PACKETID_ENTRY, (entry->entry_idx % 3) + 1); + rt2x00_desc_write(txwi, 1, word); + + /* + * Always write 0 to IV/EIV fields (word 2 and 3), hardware will insert + * the IV from the IVEIV register when TXD_W3_WIV is set to 0. + * When TXD_W3_WIV is set to 1 it will use the IV data + * from the descriptor. The TXWI_W1_WIRELESS_CLI_ID indicates which + * crypto entry in the registers should be used to encrypt the frame. + * + * Nulify all remaining words as well, we don't know how to program them. + */ + for (i = 2; i < entry->queue->winfo_size / sizeof(__le32); i++) + _rt2x00_desc_write(txwi, i, 0); +} +EXPORT_SYMBOL_GPL(rt2800_write_tx_data); + +static int rt2800_agc_to_rssi(struct rt2x00_dev *rt2x00dev, u32 rxwi_w2) +{ + s8 rssi0 = rt2x00_get_field32(rxwi_w2, RXWI_W2_RSSI0); + s8 rssi1 = rt2x00_get_field32(rxwi_w2, RXWI_W2_RSSI1); + s8 rssi2 = rt2x00_get_field32(rxwi_w2, RXWI_W2_RSSI2); + u16 eeprom; + u8 offset0; + u8 offset1; + u8 offset2; + + if (rt2x00dev->curr_band == IEEE80211_BAND_2GHZ) { + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_BG, &eeprom); + offset0 = rt2x00_get_field16(eeprom, EEPROM_RSSI_BG_OFFSET0); + offset1 = rt2x00_get_field16(eeprom, EEPROM_RSSI_BG_OFFSET1); + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_BG2, &eeprom); + offset2 = rt2x00_get_field16(eeprom, EEPROM_RSSI_BG2_OFFSET2); + } else { + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_A, &eeprom); + offset0 = rt2x00_get_field16(eeprom, EEPROM_RSSI_A_OFFSET0); + offset1 = rt2x00_get_field16(eeprom, EEPROM_RSSI_A_OFFSET1); + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_A2, &eeprom); + offset2 = rt2x00_get_field16(eeprom, EEPROM_RSSI_A2_OFFSET2); + } + + /* + * Convert the value from the descriptor into the RSSI value + * If the value in the descriptor is 0, it is considered invalid + * and the default (extremely low) rssi value is assumed + */ + rssi0 = (rssi0) ? (-12 - offset0 - rt2x00dev->lna_gain - rssi0) : -128; + rssi1 = (rssi1) ? (-12 - offset1 - rt2x00dev->lna_gain - rssi1) : -128; + rssi2 = (rssi2) ? (-12 - offset2 - rt2x00dev->lna_gain - rssi2) : -128; + + /* + * mac80211 only accepts a single RSSI value. Calculating the + * average doesn't deliver a fair answer either since -60:-60 would + * be considered equally good as -50:-70 while the second is the one + * which gives less energy... + */ + rssi0 = max(rssi0, rssi1); + return (int)max(rssi0, rssi2); +} + +void rt2800_process_rxwi(struct queue_entry *entry, + struct rxdone_entry_desc *rxdesc) +{ + __le32 *rxwi = (__le32 *) entry->skb->data; + u32 word; + + rt2x00_desc_read(rxwi, 0, &word); + + rxdesc->cipher = rt2x00_get_field32(word, RXWI_W0_UDF); + rxdesc->size = rt2x00_get_field32(word, RXWI_W0_MPDU_TOTAL_BYTE_COUNT); + + rt2x00_desc_read(rxwi, 1, &word); + + if (rt2x00_get_field32(word, RXWI_W1_SHORT_GI)) + rxdesc->flags |= RX_FLAG_SHORT_GI; + + if (rt2x00_get_field32(word, RXWI_W1_BW)) + rxdesc->flags |= RX_FLAG_40MHZ; + + /* + * Detect RX rate, always use MCS as signal type. + */ + rxdesc->dev_flags |= RXDONE_SIGNAL_MCS; + rxdesc->signal = rt2x00_get_field32(word, RXWI_W1_MCS); + rxdesc->rate_mode = rt2x00_get_field32(word, RXWI_W1_PHYMODE); + + /* + * Mask of 0x8 bit to remove the short preamble flag. + */ + if (rxdesc->rate_mode == RATE_MODE_CCK) + rxdesc->signal &= ~0x8; + + rt2x00_desc_read(rxwi, 2, &word); + + /* + * Convert descriptor AGC value to RSSI value. + */ + rxdesc->rssi = rt2800_agc_to_rssi(entry->queue->rt2x00dev, word); + /* + * Remove RXWI descriptor from start of the buffer. + */ + skb_pull(entry->skb, entry->queue->winfo_size); +} +EXPORT_SYMBOL_GPL(rt2800_process_rxwi); + +void rt2800_txdone_entry(struct queue_entry *entry, u32 status, __le32 *txwi) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + struct txdone_entry_desc txdesc; + u32 word; + u16 mcs, real_mcs; + int aggr, ampdu; + + /* + * Obtain the status about this packet. + */ + txdesc.flags = 0; + rt2x00_desc_read(txwi, 0, &word); + + mcs = rt2x00_get_field32(word, TXWI_W0_MCS); + ampdu = rt2x00_get_field32(word, TXWI_W0_AMPDU); + + real_mcs = rt2x00_get_field32(status, TX_STA_FIFO_MCS); + aggr = rt2x00_get_field32(status, TX_STA_FIFO_TX_AGGRE); + + /* + * If a frame was meant to be sent as a single non-aggregated MPDU + * but ended up in an aggregate the used tx rate doesn't correlate + * with the one specified in the TXWI as the whole aggregate is sent + * with the same rate. + * + * For example: two frames are sent to rt2x00, the first one sets + * AMPDU=1 and requests MCS7 whereas the second frame sets AMDPU=0 + * and requests MCS15. If the hw aggregates both frames into one + * AMDPU the tx status for both frames will contain MCS7 although + * the frame was sent successfully. + * + * Hence, replace the requested rate with the real tx rate to not + * confuse the rate control algortihm by providing clearly wrong + * data. + */ + if (unlikely(aggr == 1 && ampdu == 0 && real_mcs != mcs)) { + skbdesc->tx_rate_idx = real_mcs; + mcs = real_mcs; + } + + if (aggr == 1 || ampdu == 1) + __set_bit(TXDONE_AMPDU, &txdesc.flags); + + /* + * Ralink has a retry mechanism using a global fallback + * table. We setup this fallback table to try the immediate + * lower rate for all rates. In the TX_STA_FIFO, the MCS field + * always contains the MCS used for the last transmission, be + * it successful or not. + */ + if (rt2x00_get_field32(status, TX_STA_FIFO_TX_SUCCESS)) { + /* + * Transmission succeeded. The number of retries is + * mcs - real_mcs + */ + __set_bit(TXDONE_SUCCESS, &txdesc.flags); + txdesc.retry = ((mcs > real_mcs) ? mcs - real_mcs : 0); + } else { + /* + * Transmission failed. The number of retries is + * always 7 in this case (for a total number of 8 + * frames sent). + */ + __set_bit(TXDONE_FAILURE, &txdesc.flags); + txdesc.retry = rt2x00dev->long_retry; + } + + /* + * the frame was retried at least once + * -> hw used fallback rates + */ + if (txdesc.retry) + __set_bit(TXDONE_FALLBACK, &txdesc.flags); + + rt2x00lib_txdone(entry, &txdesc); +} +EXPORT_SYMBOL_GPL(rt2800_txdone_entry); + +void rt2800_write_beacon(struct queue_entry *entry, struct txentry_desc *txdesc) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + unsigned int beacon_base; + unsigned int padding_len; + u32 orig_reg, reg; + const int txwi_desc_size = entry->queue->winfo_size; + + /* + * Disable beaconing while we are reloading the beacon data, + * otherwise we might be sending out invalid data. + */ + rt2800_register_read(rt2x00dev, BCN_TIME_CFG, ®); + orig_reg = reg; + rt2x00_set_field32(®, BCN_TIME_CFG_BEACON_GEN, 0); + rt2800_register_write(rt2x00dev, BCN_TIME_CFG, reg); + + /* + * Add space for the TXWI in front of the skb. + */ + memset(skb_push(entry->skb, txwi_desc_size), 0, txwi_desc_size); + + /* + * Register descriptor details in skb frame descriptor. + */ + skbdesc->flags |= SKBDESC_DESC_IN_SKB; + skbdesc->desc = entry->skb->data; + skbdesc->desc_len = txwi_desc_size; + + /* + * Add the TXWI for the beacon to the skb. + */ + rt2800_write_tx_data(entry, txdesc); + + /* + * Dump beacon to userspace through debugfs. + */ + rt2x00debug_dump_frame(rt2x00dev, DUMP_FRAME_BEACON, entry->skb); + + /* + * Write entire beacon with TXWI and padding to register. + */ + padding_len = roundup(entry->skb->len, 4) - entry->skb->len; + if (padding_len && skb_pad(entry->skb, padding_len)) { + rt2x00_err(rt2x00dev, "Failure padding beacon, aborting\n"); + /* skb freed by skb_pad() on failure */ + entry->skb = NULL; + rt2800_register_write(rt2x00dev, BCN_TIME_CFG, orig_reg); + return; + } + + beacon_base = HW_BEACON_OFFSET(entry->entry_idx); + rt2800_register_multiwrite(rt2x00dev, beacon_base, entry->skb->data, + entry->skb->len + padding_len); + + /* + * Enable beaconing again. + */ + rt2x00_set_field32(®, BCN_TIME_CFG_BEACON_GEN, 1); + rt2800_register_write(rt2x00dev, BCN_TIME_CFG, reg); + + /* + * Clean up beacon skb. + */ + dev_kfree_skb_any(entry->skb); + entry->skb = NULL; +} +EXPORT_SYMBOL_GPL(rt2800_write_beacon); + +static inline void rt2800_clear_beacon_register(struct rt2x00_dev *rt2x00dev, + unsigned int beacon_base) +{ + int i; + const int txwi_desc_size = rt2x00dev->ops->bcn->winfo_size; + + /* + * For the Beacon base registers we only need to clear + * the whole TXWI which (when set to 0) will invalidate + * the entire beacon. + */ + for (i = 0; i < txwi_desc_size; i += sizeof(__le32)) + rt2800_register_write(rt2x00dev, beacon_base + i, 0); +} + +void rt2800_clear_beacon(struct queue_entry *entry) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + u32 reg; + + /* + * Disable beaconing while we are reloading the beacon data, + * otherwise we might be sending out invalid data. + */ + rt2800_register_read(rt2x00dev, BCN_TIME_CFG, ®); + rt2x00_set_field32(®, BCN_TIME_CFG_BEACON_GEN, 0); + rt2800_register_write(rt2x00dev, BCN_TIME_CFG, reg); + + /* + * Clear beacon. + */ + rt2800_clear_beacon_register(rt2x00dev, + HW_BEACON_OFFSET(entry->entry_idx)); + + /* + * Enabled beaconing again. + */ + rt2x00_set_field32(®, BCN_TIME_CFG_BEACON_GEN, 1); + rt2800_register_write(rt2x00dev, BCN_TIME_CFG, reg); +} +EXPORT_SYMBOL_GPL(rt2800_clear_beacon); + +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS +const struct rt2x00debug rt2800_rt2x00debug = { + .owner = THIS_MODULE, + .csr = { + .read = rt2800_register_read, + .write = rt2800_register_write, + .flags = RT2X00DEBUGFS_OFFSET, + .word_base = CSR_REG_BASE, + .word_size = sizeof(u32), + .word_count = CSR_REG_SIZE / sizeof(u32), + }, + .eeprom = { + .read = rt2x00_eeprom_read, + .write = rt2x00_eeprom_write, + .word_base = EEPROM_BASE, + .word_size = sizeof(u16), + .word_count = EEPROM_SIZE / sizeof(u16), + }, + .bbp = { + .read = rt2800_bbp_read, + .write = rt2800_bbp_write, + .word_base = BBP_BASE, + .word_size = sizeof(u8), + .word_count = BBP_SIZE / sizeof(u8), + }, + .rf = { + .read = rt2x00_rf_read, + .write = rt2800_rf_write, + .word_base = RF_BASE, + .word_size = sizeof(u32), + .word_count = RF_SIZE / sizeof(u32), + }, + .rfcsr = { + .read = rt2800_rfcsr_read, + .write = rt2800_rfcsr_write, + .word_base = RFCSR_BASE, + .word_size = sizeof(u8), + .word_count = RFCSR_SIZE / sizeof(u8), + }, +}; +EXPORT_SYMBOL_GPL(rt2800_rt2x00debug); +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ + +int rt2800_rfkill_poll(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + if (rt2x00_rt(rt2x00dev, RT3290)) { + rt2800_register_read(rt2x00dev, WLAN_FUN_CTRL, ®); + return rt2x00_get_field32(reg, WLAN_GPIO_IN_BIT0); + } else { + rt2800_register_read(rt2x00dev, GPIO_CTRL, ®); + return rt2x00_get_field32(reg, GPIO_CTRL_VAL2); + } +} +EXPORT_SYMBOL_GPL(rt2800_rfkill_poll); + +#ifdef CPTCFG_RT2X00_LIB_LEDS +static void rt2800_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct rt2x00_led *led = + container_of(led_cdev, struct rt2x00_led, led_dev); + unsigned int enabled = brightness != LED_OFF; + unsigned int bg_mode = + (enabled && led->rt2x00dev->curr_band == IEEE80211_BAND_2GHZ); + unsigned int polarity = + rt2x00_get_field16(led->rt2x00dev->led_mcu_reg, + EEPROM_FREQ_LED_POLARITY); + unsigned int ledmode = + rt2x00_get_field16(led->rt2x00dev->led_mcu_reg, + EEPROM_FREQ_LED_MODE); + u32 reg; + + /* Check for SoC (SOC devices don't support MCU requests) */ + if (rt2x00_is_soc(led->rt2x00dev)) { + rt2800_register_read(led->rt2x00dev, LED_CFG, ®); + + /* Set LED Polarity */ + rt2x00_set_field32(®, LED_CFG_LED_POLAR, polarity); + + /* Set LED Mode */ + if (led->type == LED_TYPE_RADIO) { + rt2x00_set_field32(®, LED_CFG_G_LED_MODE, + enabled ? 3 : 0); + } else if (led->type == LED_TYPE_ASSOC) { + rt2x00_set_field32(®, LED_CFG_Y_LED_MODE, + enabled ? 3 : 0); + } else if (led->type == LED_TYPE_QUALITY) { + rt2x00_set_field32(®, LED_CFG_R_LED_MODE, + enabled ? 3 : 0); + } + + rt2800_register_write(led->rt2x00dev, LED_CFG, reg); + + } else { + if (led->type == LED_TYPE_RADIO) { + rt2800_mcu_request(led->rt2x00dev, MCU_LED, 0xff, ledmode, + enabled ? 0x20 : 0); + } else if (led->type == LED_TYPE_ASSOC) { + rt2800_mcu_request(led->rt2x00dev, MCU_LED, 0xff, ledmode, + enabled ? (bg_mode ? 0x60 : 0xa0) : 0x20); + } else if (led->type == LED_TYPE_QUALITY) { + /* + * The brightness is divided into 6 levels (0 - 5), + * The specs tell us the following levels: + * 0, 1 ,3, 7, 15, 31 + * to determine the level in a simple way we can simply + * work with bitshifting: + * (1 << level) - 1 + */ + rt2800_mcu_request(led->rt2x00dev, MCU_LED_STRENGTH, 0xff, + (1 << brightness / (LED_FULL / 6)) - 1, + polarity); + } + } +} + +static void rt2800_init_led(struct rt2x00_dev *rt2x00dev, + struct rt2x00_led *led, enum led_type type) +{ + led->rt2x00dev = rt2x00dev; + led->type = type; + led->led_dev.brightness_set = rt2800_brightness_set; + led->flags = LED_INITIALIZED; +} +#endif /* CPTCFG_RT2X00_LIB_LEDS */ + +/* + * Configuration handlers. + */ +static void rt2800_config_wcid(struct rt2x00_dev *rt2x00dev, + const u8 *address, + int wcid) +{ + struct mac_wcid_entry wcid_entry; + u32 offset; + + offset = MAC_WCID_ENTRY(wcid); + + memset(&wcid_entry, 0xff, sizeof(wcid_entry)); + if (address) + memcpy(wcid_entry.mac, address, ETH_ALEN); + + rt2800_register_multiwrite(rt2x00dev, offset, + &wcid_entry, sizeof(wcid_entry)); +} + +static void rt2800_delete_wcid_attr(struct rt2x00_dev *rt2x00dev, int wcid) +{ + u32 offset; + offset = MAC_WCID_ATTR_ENTRY(wcid); + rt2800_register_write(rt2x00dev, offset, 0); +} + +static void rt2800_config_wcid_attr_bssidx(struct rt2x00_dev *rt2x00dev, + int wcid, u32 bssidx) +{ + u32 offset = MAC_WCID_ATTR_ENTRY(wcid); + u32 reg; + + /* + * The BSS Idx numbers is split in a main value of 3 bits, + * and a extended field for adding one additional bit to the value. + */ + rt2800_register_read(rt2x00dev, offset, ®); + rt2x00_set_field32(®, MAC_WCID_ATTRIBUTE_BSS_IDX, (bssidx & 0x7)); + rt2x00_set_field32(®, MAC_WCID_ATTRIBUTE_BSS_IDX_EXT, + (bssidx & 0x8) >> 3); + rt2800_register_write(rt2x00dev, offset, reg); +} + +static void rt2800_config_wcid_attr_cipher(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_crypto *crypto, + struct ieee80211_key_conf *key) +{ + struct mac_iveiv_entry iveiv_entry; + u32 offset; + u32 reg; + + offset = MAC_WCID_ATTR_ENTRY(key->hw_key_idx); + + if (crypto->cmd == SET_KEY) { + rt2800_register_read(rt2x00dev, offset, ®); + rt2x00_set_field32(®, MAC_WCID_ATTRIBUTE_KEYTAB, + !!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)); + /* + * Both the cipher as the BSS Idx numbers are split in a main + * value of 3 bits, and a extended field for adding one additional + * bit to the value. + */ + rt2x00_set_field32(®, MAC_WCID_ATTRIBUTE_CIPHER, + (crypto->cipher & 0x7)); + rt2x00_set_field32(®, MAC_WCID_ATTRIBUTE_CIPHER_EXT, + (crypto->cipher & 0x8) >> 3); + rt2x00_set_field32(®, MAC_WCID_ATTRIBUTE_RX_WIUDF, crypto->cipher); + rt2800_register_write(rt2x00dev, offset, reg); + } else { + /* Delete the cipher without touching the bssidx */ + rt2800_register_read(rt2x00dev, offset, ®); + rt2x00_set_field32(®, MAC_WCID_ATTRIBUTE_KEYTAB, 0); + rt2x00_set_field32(®, MAC_WCID_ATTRIBUTE_CIPHER, 0); + rt2x00_set_field32(®, MAC_WCID_ATTRIBUTE_CIPHER_EXT, 0); + rt2x00_set_field32(®, MAC_WCID_ATTRIBUTE_RX_WIUDF, 0); + rt2800_register_write(rt2x00dev, offset, reg); + } + + offset = MAC_IVEIV_ENTRY(key->hw_key_idx); + + memset(&iveiv_entry, 0, sizeof(iveiv_entry)); + if ((crypto->cipher == CIPHER_TKIP) || + (crypto->cipher == CIPHER_TKIP_NO_MIC) || + (crypto->cipher == CIPHER_AES)) + iveiv_entry.iv[3] |= 0x20; + iveiv_entry.iv[3] |= key->keyidx << 6; + rt2800_register_multiwrite(rt2x00dev, offset, + &iveiv_entry, sizeof(iveiv_entry)); +} + +int rt2800_config_shared_key(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_crypto *crypto, + struct ieee80211_key_conf *key) +{ + struct hw_key_entry key_entry; + struct rt2x00_field32 field; + u32 offset; + u32 reg; + + if (crypto->cmd == SET_KEY) { + key->hw_key_idx = (4 * crypto->bssidx) + key->keyidx; + + memcpy(key_entry.key, crypto->key, + sizeof(key_entry.key)); + memcpy(key_entry.tx_mic, crypto->tx_mic, + sizeof(key_entry.tx_mic)); + memcpy(key_entry.rx_mic, crypto->rx_mic, + sizeof(key_entry.rx_mic)); + + offset = SHARED_KEY_ENTRY(key->hw_key_idx); + rt2800_register_multiwrite(rt2x00dev, offset, + &key_entry, sizeof(key_entry)); + } + + /* + * The cipher types are stored over multiple registers + * starting with SHARED_KEY_MODE_BASE each word will have + * 32 bits and contains the cipher types for 2 bssidx each. + * Using the correct defines correctly will cause overhead, + * so just calculate the correct offset. + */ + field.bit_offset = 4 * (key->hw_key_idx % 8); + field.bit_mask = 0x7 << field.bit_offset; + + offset = SHARED_KEY_MODE_ENTRY(key->hw_key_idx / 8); + + rt2800_register_read(rt2x00dev, offset, ®); + rt2x00_set_field32(®, field, + (crypto->cmd == SET_KEY) * crypto->cipher); + rt2800_register_write(rt2x00dev, offset, reg); + + /* + * Update WCID information + */ + rt2800_config_wcid(rt2x00dev, crypto->address, key->hw_key_idx); + rt2800_config_wcid_attr_bssidx(rt2x00dev, key->hw_key_idx, + crypto->bssidx); + rt2800_config_wcid_attr_cipher(rt2x00dev, crypto, key); + + return 0; +} +EXPORT_SYMBOL_GPL(rt2800_config_shared_key); + +static inline int rt2800_find_wcid(struct rt2x00_dev *rt2x00dev) +{ + struct mac_wcid_entry wcid_entry; + int idx; + u32 offset; + + /* + * Search for the first free WCID entry and return the corresponding + * index. + * + * Make sure the WCID starts _after_ the last possible shared key + * entry (>32). + * + * Since parts of the pairwise key table might be shared with + * the beacon frame buffers 6 & 7 we should only write into the + * first 222 entries. + */ + for (idx = 33; idx <= 222; idx++) { + offset = MAC_WCID_ENTRY(idx); + rt2800_register_multiread(rt2x00dev, offset, &wcid_entry, + sizeof(wcid_entry)); + if (is_broadcast_ether_addr(wcid_entry.mac)) + return idx; + } + + /* + * Use -1 to indicate that we don't have any more space in the WCID + * table. + */ + return -1; +} + +int rt2800_config_pairwise_key(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_crypto *crypto, + struct ieee80211_key_conf *key) +{ + struct hw_key_entry key_entry; + u32 offset; + + if (crypto->cmd == SET_KEY) { + /* + * Allow key configuration only for STAs that are + * known by the hw. + */ + if (crypto->wcid < 0) + return -ENOSPC; + key->hw_key_idx = crypto->wcid; + + memcpy(key_entry.key, crypto->key, + sizeof(key_entry.key)); + memcpy(key_entry.tx_mic, crypto->tx_mic, + sizeof(key_entry.tx_mic)); + memcpy(key_entry.rx_mic, crypto->rx_mic, + sizeof(key_entry.rx_mic)); + + offset = PAIRWISE_KEY_ENTRY(key->hw_key_idx); + rt2800_register_multiwrite(rt2x00dev, offset, + &key_entry, sizeof(key_entry)); + } + + /* + * Update WCID information + */ + rt2800_config_wcid_attr_cipher(rt2x00dev, crypto, key); + + return 0; +} +EXPORT_SYMBOL_GPL(rt2800_config_pairwise_key); + +int rt2800_sta_add(struct rt2x00_dev *rt2x00dev, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + int wcid; + struct rt2x00_sta *sta_priv = sta_to_rt2x00_sta(sta); + + /* + * Find next free WCID. + */ + wcid = rt2800_find_wcid(rt2x00dev); + + /* + * Store selected wcid even if it is invalid so that we can + * later decide if the STA is uploaded into the hw. + */ + sta_priv->wcid = wcid; + + /* + * No space left in the device, however, we can still communicate + * with the STA -> No error. + */ + if (wcid < 0) + return 0; + + /* + * Clean up WCID attributes and write STA address to the device. + */ + rt2800_delete_wcid_attr(rt2x00dev, wcid); + rt2800_config_wcid(rt2x00dev, sta->addr, wcid); + rt2800_config_wcid_attr_bssidx(rt2x00dev, wcid, + rt2x00lib_get_bssidx(rt2x00dev, vif)); + return 0; +} +EXPORT_SYMBOL_GPL(rt2800_sta_add); + +int rt2800_sta_remove(struct rt2x00_dev *rt2x00dev, int wcid) +{ + /* + * Remove WCID entry, no need to clean the attributes as they will + * get renewed when the WCID is reused. + */ + rt2800_config_wcid(rt2x00dev, NULL, wcid); + + return 0; +} +EXPORT_SYMBOL_GPL(rt2800_sta_remove); + +void rt2800_config_filter(struct rt2x00_dev *rt2x00dev, + const unsigned int filter_flags) +{ + u32 reg; + + /* + * Start configuration steps. + * Note that the version error will always be dropped + * and broadcast frames will always be accepted since + * there is no filter for it at this time. + */ + rt2800_register_read(rt2x00dev, RX_FILTER_CFG, ®); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_CRC_ERROR, + !(filter_flags & FIF_FCSFAIL)); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_PHY_ERROR, + !(filter_flags & FIF_PLCPFAIL)); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_NOT_TO_ME, + !(filter_flags & FIF_PROMISC_IN_BSS)); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_NOT_MY_BSSD, 0); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_VER_ERROR, 1); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_MULTICAST, + !(filter_flags & FIF_ALLMULTI)); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_BROADCAST, 0); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_DUPLICATE, 1); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_CF_END_ACK, + !(filter_flags & FIF_CONTROL)); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_CF_END, + !(filter_flags & FIF_CONTROL)); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_ACK, + !(filter_flags & FIF_CONTROL)); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_CTS, + !(filter_flags & FIF_CONTROL)); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_RTS, + !(filter_flags & FIF_CONTROL)); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_PSPOLL, + !(filter_flags & FIF_PSPOLL)); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_BA, 0); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_BAR, + !(filter_flags & FIF_CONTROL)); + rt2x00_set_field32(®, RX_FILTER_CFG_DROP_CNTL, + !(filter_flags & FIF_CONTROL)); + rt2800_register_write(rt2x00dev, RX_FILTER_CFG, reg); +} +EXPORT_SYMBOL_GPL(rt2800_config_filter); + +void rt2800_config_intf(struct rt2x00_dev *rt2x00dev, struct rt2x00_intf *intf, + struct rt2x00intf_conf *conf, const unsigned int flags) +{ + u32 reg; + bool update_bssid = false; + + if (flags & CONFIG_UPDATE_TYPE) { + /* + * Enable synchronisation. + */ + rt2800_register_read(rt2x00dev, BCN_TIME_CFG, ®); + rt2x00_set_field32(®, BCN_TIME_CFG_TSF_SYNC, conf->sync); + rt2800_register_write(rt2x00dev, BCN_TIME_CFG, reg); + + if (conf->sync == TSF_SYNC_AP_NONE) { + /* + * Tune beacon queue transmit parameters for AP mode + */ + rt2800_register_read(rt2x00dev, TBTT_SYNC_CFG, ®); + rt2x00_set_field32(®, TBTT_SYNC_CFG_BCN_CWMIN, 0); + rt2x00_set_field32(®, TBTT_SYNC_CFG_BCN_AIFSN, 1); + rt2x00_set_field32(®, TBTT_SYNC_CFG_BCN_EXP_WIN, 32); + rt2x00_set_field32(®, TBTT_SYNC_CFG_TBTT_ADJUST, 0); + rt2800_register_write(rt2x00dev, TBTT_SYNC_CFG, reg); + } else { + rt2800_register_read(rt2x00dev, TBTT_SYNC_CFG, ®); + rt2x00_set_field32(®, TBTT_SYNC_CFG_BCN_CWMIN, 4); + rt2x00_set_field32(®, TBTT_SYNC_CFG_BCN_AIFSN, 2); + rt2x00_set_field32(®, TBTT_SYNC_CFG_BCN_EXP_WIN, 32); + rt2x00_set_field32(®, TBTT_SYNC_CFG_TBTT_ADJUST, 16); + rt2800_register_write(rt2x00dev, TBTT_SYNC_CFG, reg); + } + } + + if (flags & CONFIG_UPDATE_MAC) { + if (flags & CONFIG_UPDATE_TYPE && + conf->sync == TSF_SYNC_AP_NONE) { + /* + * The BSSID register has to be set to our own mac + * address in AP mode. + */ + memcpy(conf->bssid, conf->mac, sizeof(conf->mac)); + update_bssid = true; + } + + if (!is_zero_ether_addr((const u8 *)conf->mac)) { + reg = le32_to_cpu(conf->mac[1]); + rt2x00_set_field32(®, MAC_ADDR_DW1_UNICAST_TO_ME_MASK, 0xff); + conf->mac[1] = cpu_to_le32(reg); + } + + rt2800_register_multiwrite(rt2x00dev, MAC_ADDR_DW0, + conf->mac, sizeof(conf->mac)); + } + + if ((flags & CONFIG_UPDATE_BSSID) || update_bssid) { + if (!is_zero_ether_addr((const u8 *)conf->bssid)) { + reg = le32_to_cpu(conf->bssid[1]); + rt2x00_set_field32(®, MAC_BSSID_DW1_BSS_ID_MASK, 3); + rt2x00_set_field32(®, MAC_BSSID_DW1_BSS_BCN_NUM, 7); + conf->bssid[1] = cpu_to_le32(reg); + } + + rt2800_register_multiwrite(rt2x00dev, MAC_BSSID_DW0, + conf->bssid, sizeof(conf->bssid)); + } +} +EXPORT_SYMBOL_GPL(rt2800_config_intf); + +static void rt2800_config_ht_opmode(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_erp *erp) +{ + bool any_sta_nongf = !!(erp->ht_opmode & + IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT); + u8 protection = erp->ht_opmode & IEEE80211_HT_OP_MODE_PROTECTION; + u8 mm20_mode, mm40_mode, gf20_mode, gf40_mode; + u16 mm20_rate, mm40_rate, gf20_rate, gf40_rate; + u32 reg; + + /* default protection rate for HT20: OFDM 24M */ + mm20_rate = gf20_rate = 0x4004; + + /* default protection rate for HT40: duplicate OFDM 24M */ + mm40_rate = gf40_rate = 0x4084; + + switch (protection) { + case IEEE80211_HT_OP_MODE_PROTECTION_NONE: + /* + * All STAs in this BSS are HT20/40 but there might be + * STAs not supporting greenfield mode. + * => Disable protection for HT transmissions. + */ + mm20_mode = mm40_mode = gf20_mode = gf40_mode = 0; + + break; + case IEEE80211_HT_OP_MODE_PROTECTION_20MHZ: + /* + * All STAs in this BSS are HT20 or HT20/40 but there + * might be STAs not supporting greenfield mode. + * => Protect all HT40 transmissions. + */ + mm20_mode = gf20_mode = 0; + mm40_mode = gf40_mode = 2; + + break; + case IEEE80211_HT_OP_MODE_PROTECTION_NONMEMBER: + /* + * Nonmember protection: + * According to 802.11n we _should_ protect all + * HT transmissions (but we don't have to). + * + * But if cts_protection is enabled we _shall_ protect + * all HT transmissions using a CCK rate. + * + * And if any station is non GF we _shall_ protect + * GF transmissions. + * + * We decide to protect everything + * -> fall through to mixed mode. + */ + case IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED: + /* + * Legacy STAs are present + * => Protect all HT transmissions. + */ + mm20_mode = mm40_mode = gf20_mode = gf40_mode = 2; + + /* + * If erp protection is needed we have to protect HT + * transmissions with CCK 11M long preamble. + */ + if (erp->cts_protection) { + /* don't duplicate RTS/CTS in CCK mode */ + mm20_rate = mm40_rate = 0x0003; + gf20_rate = gf40_rate = 0x0003; + } + break; + } + + /* check for STAs not supporting greenfield mode */ + if (any_sta_nongf) + gf20_mode = gf40_mode = 2; + + /* Update HT protection config */ + rt2800_register_read(rt2x00dev, MM20_PROT_CFG, ®); + rt2x00_set_field32(®, MM20_PROT_CFG_PROTECT_RATE, mm20_rate); + rt2x00_set_field32(®, MM20_PROT_CFG_PROTECT_CTRL, mm20_mode); + rt2800_register_write(rt2x00dev, MM20_PROT_CFG, reg); + + rt2800_register_read(rt2x00dev, MM40_PROT_CFG, ®); + rt2x00_set_field32(®, MM40_PROT_CFG_PROTECT_RATE, mm40_rate); + rt2x00_set_field32(®, MM40_PROT_CFG_PROTECT_CTRL, mm40_mode); + rt2800_register_write(rt2x00dev, MM40_PROT_CFG, reg); + + rt2800_register_read(rt2x00dev, GF20_PROT_CFG, ®); + rt2x00_set_field32(®, GF20_PROT_CFG_PROTECT_RATE, gf20_rate); + rt2x00_set_field32(®, GF20_PROT_CFG_PROTECT_CTRL, gf20_mode); + rt2800_register_write(rt2x00dev, GF20_PROT_CFG, reg); + + rt2800_register_read(rt2x00dev, GF40_PROT_CFG, ®); + rt2x00_set_field32(®, GF40_PROT_CFG_PROTECT_RATE, gf40_rate); + rt2x00_set_field32(®, GF40_PROT_CFG_PROTECT_CTRL, gf40_mode); + rt2800_register_write(rt2x00dev, GF40_PROT_CFG, reg); +} + +void rt2800_config_erp(struct rt2x00_dev *rt2x00dev, struct rt2x00lib_erp *erp, + u32 changed) +{ + u32 reg; + + if (changed & BSS_CHANGED_ERP_PREAMBLE) { + rt2800_register_read(rt2x00dev, AUTO_RSP_CFG, ®); + rt2x00_set_field32(®, AUTO_RSP_CFG_BAC_ACK_POLICY, + !!erp->short_preamble); + rt2x00_set_field32(®, AUTO_RSP_CFG_AR_PREAMBLE, + !!erp->short_preamble); + rt2800_register_write(rt2x00dev, AUTO_RSP_CFG, reg); + } + + if (changed & BSS_CHANGED_ERP_CTS_PROT) { + rt2800_register_read(rt2x00dev, OFDM_PROT_CFG, ®); + rt2x00_set_field32(®, OFDM_PROT_CFG_PROTECT_CTRL, + erp->cts_protection ? 2 : 0); + rt2800_register_write(rt2x00dev, OFDM_PROT_CFG, reg); + } + + if (changed & BSS_CHANGED_BASIC_RATES) { + rt2800_register_write(rt2x00dev, LEGACY_BASIC_RATE, + erp->basic_rates); + rt2800_register_write(rt2x00dev, HT_BASIC_RATE, 0x00008003); + } + + if (changed & BSS_CHANGED_ERP_SLOT) { + rt2800_register_read(rt2x00dev, BKOFF_SLOT_CFG, ®); + rt2x00_set_field32(®, BKOFF_SLOT_CFG_SLOT_TIME, + erp->slot_time); + rt2800_register_write(rt2x00dev, BKOFF_SLOT_CFG, reg); + + rt2800_register_read(rt2x00dev, XIFS_TIME_CFG, ®); + rt2x00_set_field32(®, XIFS_TIME_CFG_EIFS, erp->eifs); + rt2800_register_write(rt2x00dev, XIFS_TIME_CFG, reg); + } + + if (changed & BSS_CHANGED_BEACON_INT) { + rt2800_register_read(rt2x00dev, BCN_TIME_CFG, ®); + rt2x00_set_field32(®, BCN_TIME_CFG_BEACON_INTERVAL, + erp->beacon_int * 16); + rt2800_register_write(rt2x00dev, BCN_TIME_CFG, reg); + } + + if (changed & BSS_CHANGED_HT) + rt2800_config_ht_opmode(rt2x00dev, erp); +} +EXPORT_SYMBOL_GPL(rt2800_config_erp); + +static void rt2800_config_3572bt_ant(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + u16 eeprom; + u8 led_ctrl, led_g_mode, led_r_mode; + + rt2800_register_read(rt2x00dev, GPIO_SWITCH, ®); + if (rt2x00dev->curr_band == IEEE80211_BAND_5GHZ) { + rt2x00_set_field32(®, GPIO_SWITCH_0, 1); + rt2x00_set_field32(®, GPIO_SWITCH_1, 1); + } else { + rt2x00_set_field32(®, GPIO_SWITCH_0, 0); + rt2x00_set_field32(®, GPIO_SWITCH_1, 0); + } + rt2800_register_write(rt2x00dev, GPIO_SWITCH, reg); + + rt2800_register_read(rt2x00dev, LED_CFG, ®); + led_g_mode = rt2x00_get_field32(reg, LED_CFG_LED_POLAR) ? 3 : 0; + led_r_mode = rt2x00_get_field32(reg, LED_CFG_LED_POLAR) ? 0 : 3; + if (led_g_mode != rt2x00_get_field32(reg, LED_CFG_G_LED_MODE) || + led_r_mode != rt2x00_get_field32(reg, LED_CFG_R_LED_MODE)) { + rt2x00_eeprom_read(rt2x00dev, EEPROM_FREQ, &eeprom); + led_ctrl = rt2x00_get_field16(eeprom, EEPROM_FREQ_LED_MODE); + if (led_ctrl == 0 || led_ctrl > 0x40) { + rt2x00_set_field32(®, LED_CFG_G_LED_MODE, led_g_mode); + rt2x00_set_field32(®, LED_CFG_R_LED_MODE, led_r_mode); + rt2800_register_write(rt2x00dev, LED_CFG, reg); + } else { + rt2800_mcu_request(rt2x00dev, MCU_BAND_SELECT, 0xff, + (led_g_mode << 2) | led_r_mode, 1); + } + } +} + +static void rt2800_set_ant_diversity(struct rt2x00_dev *rt2x00dev, + enum antenna ant) +{ + u32 reg; + u8 eesk_pin = (ant == ANTENNA_A) ? 1 : 0; + u8 gpio_bit3 = (ant == ANTENNA_A) ? 0 : 1; + + if (rt2x00_is_pci(rt2x00dev)) { + rt2800_register_read(rt2x00dev, E2PROM_CSR, ®); + rt2x00_set_field32(®, E2PROM_CSR_DATA_CLOCK, eesk_pin); + rt2800_register_write(rt2x00dev, E2PROM_CSR, reg); + } else if (rt2x00_is_usb(rt2x00dev)) + rt2800_mcu_request(rt2x00dev, MCU_ANT_SELECT, 0xff, + eesk_pin, 0); + + rt2800_register_read(rt2x00dev, GPIO_CTRL, ®); + rt2x00_set_field32(®, GPIO_CTRL_DIR3, 0); + rt2x00_set_field32(®, GPIO_CTRL_VAL3, gpio_bit3); + rt2800_register_write(rt2x00dev, GPIO_CTRL, reg); +} + +void rt2800_config_ant(struct rt2x00_dev *rt2x00dev, struct antenna_setup *ant) +{ + u8 r1; + u8 r3; + u16 eeprom; + + rt2800_bbp_read(rt2x00dev, 1, &r1); + rt2800_bbp_read(rt2x00dev, 3, &r3); + + if (rt2x00_rt(rt2x00dev, RT3572) && + test_bit(CAPABILITY_BT_COEXIST, &rt2x00dev->cap_flags)) + rt2800_config_3572bt_ant(rt2x00dev); + + /* + * Configure the TX antenna. + */ + switch (ant->tx_chain_num) { + case 1: + rt2x00_set_field8(&r1, BBP1_TX_ANTENNA, 0); + break; + case 2: + if (rt2x00_rt(rt2x00dev, RT3572) && + test_bit(CAPABILITY_BT_COEXIST, &rt2x00dev->cap_flags)) + rt2x00_set_field8(&r1, BBP1_TX_ANTENNA, 1); + else + rt2x00_set_field8(&r1, BBP1_TX_ANTENNA, 2); + break; + case 3: + rt2x00_set_field8(&r1, BBP1_TX_ANTENNA, 0); + break; + } + + /* + * Configure the RX antenna. + */ + switch (ant->rx_chain_num) { + case 1: + if (rt2x00_rt(rt2x00dev, RT3070) || + rt2x00_rt(rt2x00dev, RT3090) || + rt2x00_rt(rt2x00dev, RT3352) || + rt2x00_rt(rt2x00dev, RT3390)) { + rt2x00_eeprom_read(rt2x00dev, + EEPROM_NIC_CONF1, &eeprom); + if (rt2x00_get_field16(eeprom, + EEPROM_NIC_CONF1_ANT_DIVERSITY)) + rt2800_set_ant_diversity(rt2x00dev, + rt2x00dev->default_ant.rx); + } + rt2x00_set_field8(&r3, BBP3_RX_ANTENNA, 0); + break; + case 2: + if (rt2x00_rt(rt2x00dev, RT3572) && + test_bit(CAPABILITY_BT_COEXIST, &rt2x00dev->cap_flags)) { + rt2x00_set_field8(&r3, BBP3_RX_ADC, 1); + rt2x00_set_field8(&r3, BBP3_RX_ANTENNA, + rt2x00dev->curr_band == IEEE80211_BAND_5GHZ); + rt2800_set_ant_diversity(rt2x00dev, ANTENNA_B); + } else { + rt2x00_set_field8(&r3, BBP3_RX_ANTENNA, 1); + } + break; + case 3: + rt2x00_set_field8(&r3, BBP3_RX_ANTENNA, 2); + break; + } + + rt2800_bbp_write(rt2x00dev, 3, r3); + rt2800_bbp_write(rt2x00dev, 1, r1); +} +EXPORT_SYMBOL_GPL(rt2800_config_ant); + +static void rt2800_config_lna_gain(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf) +{ + u16 eeprom; + short lna_gain; + + if (libconf->rf.channel <= 14) { + rt2x00_eeprom_read(rt2x00dev, EEPROM_LNA, &eeprom); + lna_gain = rt2x00_get_field16(eeprom, EEPROM_LNA_BG); + } else if (libconf->rf.channel <= 64) { + rt2x00_eeprom_read(rt2x00dev, EEPROM_LNA, &eeprom); + lna_gain = rt2x00_get_field16(eeprom, EEPROM_LNA_A0); + } else if (libconf->rf.channel <= 128) { + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_BG2, &eeprom); + lna_gain = rt2x00_get_field16(eeprom, EEPROM_RSSI_BG2_LNA_A1); + } else { + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_A2, &eeprom); + lna_gain = rt2x00_get_field16(eeprom, EEPROM_RSSI_A2_LNA_A2); + } + + rt2x00dev->lna_gain = lna_gain; +} + +static void rt2800_config_channel_rf2xxx(struct rt2x00_dev *rt2x00dev, + struct ieee80211_conf *conf, + struct rf_channel *rf, + struct channel_info *info) +{ + rt2x00_set_field32(&rf->rf4, RF4_FREQ_OFFSET, rt2x00dev->freq_offset); + + if (rt2x00dev->default_ant.tx_chain_num == 1) + rt2x00_set_field32(&rf->rf2, RF2_ANTENNA_TX1, 1); + + if (rt2x00dev->default_ant.rx_chain_num == 1) { + rt2x00_set_field32(&rf->rf2, RF2_ANTENNA_RX1, 1); + rt2x00_set_field32(&rf->rf2, RF2_ANTENNA_RX2, 1); + } else if (rt2x00dev->default_ant.rx_chain_num == 2) + rt2x00_set_field32(&rf->rf2, RF2_ANTENNA_RX2, 1); + + if (rf->channel > 14) { + /* + * When TX power is below 0, we should increase it by 7 to + * make it a positive value (Minimum value is -7). + * However this means that values between 0 and 7 have + * double meaning, and we should set a 7DBm boost flag. + */ + rt2x00_set_field32(&rf->rf3, RF3_TXPOWER_A_7DBM_BOOST, + (info->default_power1 >= 0)); + + if (info->default_power1 < 0) + info->default_power1 += 7; + + rt2x00_set_field32(&rf->rf3, RF3_TXPOWER_A, info->default_power1); + + rt2x00_set_field32(&rf->rf4, RF4_TXPOWER_A_7DBM_BOOST, + (info->default_power2 >= 0)); + + if (info->default_power2 < 0) + info->default_power2 += 7; + + rt2x00_set_field32(&rf->rf4, RF4_TXPOWER_A, info->default_power2); + } else { + rt2x00_set_field32(&rf->rf3, RF3_TXPOWER_G, info->default_power1); + rt2x00_set_field32(&rf->rf4, RF4_TXPOWER_G, info->default_power2); + } + + rt2x00_set_field32(&rf->rf4, RF4_HT40, conf_is_ht40(conf)); + + rt2800_rf_write(rt2x00dev, 1, rf->rf1); + rt2800_rf_write(rt2x00dev, 2, rf->rf2); + rt2800_rf_write(rt2x00dev, 3, rf->rf3 & ~0x00000004); + rt2800_rf_write(rt2x00dev, 4, rf->rf4); + + udelay(200); + + rt2800_rf_write(rt2x00dev, 1, rf->rf1); + rt2800_rf_write(rt2x00dev, 2, rf->rf2); + rt2800_rf_write(rt2x00dev, 3, rf->rf3 | 0x00000004); + rt2800_rf_write(rt2x00dev, 4, rf->rf4); + + udelay(200); + + rt2800_rf_write(rt2x00dev, 1, rf->rf1); + rt2800_rf_write(rt2x00dev, 2, rf->rf2); + rt2800_rf_write(rt2x00dev, 3, rf->rf3 & ~0x00000004); + rt2800_rf_write(rt2x00dev, 4, rf->rf4); +} + +static void rt2800_config_channel_rf3xxx(struct rt2x00_dev *rt2x00dev, + struct ieee80211_conf *conf, + struct rf_channel *rf, + struct channel_info *info) +{ + struct rt2800_drv_data *drv_data = rt2x00dev->drv_data; + u8 rfcsr, calib_tx, calib_rx; + + rt2800_rfcsr_write(rt2x00dev, 2, rf->rf1); + + rt2800_rfcsr_read(rt2x00dev, 3, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR3_K, rf->rf3); + rt2800_rfcsr_write(rt2x00dev, 3, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 6, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR6_R1, rf->rf2); + rt2800_rfcsr_write(rt2x00dev, 6, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 12, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR12_TX_POWER, info->default_power1); + rt2800_rfcsr_write(rt2x00dev, 12, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 13, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR13_TX_POWER, info->default_power2); + rt2800_rfcsr_write(rt2x00dev, 13, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 1, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR1_RX0_PD, 0); + rt2x00_set_field8(&rfcsr, RFCSR1_RX1_PD, + rt2x00dev->default_ant.rx_chain_num <= 1); + rt2x00_set_field8(&rfcsr, RFCSR1_RX2_PD, + rt2x00dev->default_ant.rx_chain_num <= 2); + rt2x00_set_field8(&rfcsr, RFCSR1_TX0_PD, 0); + rt2x00_set_field8(&rfcsr, RFCSR1_TX1_PD, + rt2x00dev->default_ant.tx_chain_num <= 1); + rt2x00_set_field8(&rfcsr, RFCSR1_TX2_PD, + rt2x00dev->default_ant.tx_chain_num <= 2); + rt2800_rfcsr_write(rt2x00dev, 1, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 30, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR30_RF_CALIBRATION, 1); + rt2800_rfcsr_write(rt2x00dev, 30, rfcsr); + msleep(1); + rt2x00_set_field8(&rfcsr, RFCSR30_RF_CALIBRATION, 0); + rt2800_rfcsr_write(rt2x00dev, 30, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 23, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR23_FREQ_OFFSET, rt2x00dev->freq_offset); + rt2800_rfcsr_write(rt2x00dev, 23, rfcsr); + + if (rt2x00_rt(rt2x00dev, RT3390)) { + calib_tx = conf_is_ht40(conf) ? 0x68 : 0x4f; + calib_rx = conf_is_ht40(conf) ? 0x6f : 0x4f; + } else { + if (conf_is_ht40(conf)) { + calib_tx = drv_data->calibration_bw40; + calib_rx = drv_data->calibration_bw40; + } else { + calib_tx = drv_data->calibration_bw20; + calib_rx = drv_data->calibration_bw20; + } + } + + rt2800_rfcsr_read(rt2x00dev, 24, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR24_TX_CALIB, calib_tx); + rt2800_rfcsr_write(rt2x00dev, 24, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 31, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR31_RX_CALIB, calib_rx); + rt2800_rfcsr_write(rt2x00dev, 31, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 7, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR7_RF_TUNING, 1); + rt2800_rfcsr_write(rt2x00dev, 7, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 30, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR30_RF_CALIBRATION, 1); + rt2800_rfcsr_write(rt2x00dev, 30, rfcsr); + msleep(1); + rt2x00_set_field8(&rfcsr, RFCSR30_RF_CALIBRATION, 0); + rt2800_rfcsr_write(rt2x00dev, 30, rfcsr); +} + +static void rt2800_config_channel_rf3052(struct rt2x00_dev *rt2x00dev, + struct ieee80211_conf *conf, + struct rf_channel *rf, + struct channel_info *info) +{ + struct rt2800_drv_data *drv_data = rt2x00dev->drv_data; + u8 rfcsr; + u32 reg; + + if (rf->channel <= 14) { + rt2800_bbp_write(rt2x00dev, 25, drv_data->bbp25); + rt2800_bbp_write(rt2x00dev, 26, drv_data->bbp26); + } else { + rt2800_bbp_write(rt2x00dev, 25, 0x09); + rt2800_bbp_write(rt2x00dev, 26, 0xff); + } + + rt2800_rfcsr_write(rt2x00dev, 2, rf->rf1); + rt2800_rfcsr_write(rt2x00dev, 3, rf->rf3); + + rt2800_rfcsr_read(rt2x00dev, 6, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR6_R1, rf->rf2); + if (rf->channel <= 14) + rt2x00_set_field8(&rfcsr, RFCSR6_TXDIV, 2); + else + rt2x00_set_field8(&rfcsr, RFCSR6_TXDIV, 1); + rt2800_rfcsr_write(rt2x00dev, 6, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 5, &rfcsr); + if (rf->channel <= 14) + rt2x00_set_field8(&rfcsr, RFCSR5_R1, 1); + else + rt2x00_set_field8(&rfcsr, RFCSR5_R1, 2); + rt2800_rfcsr_write(rt2x00dev, 5, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 12, &rfcsr); + if (rf->channel <= 14) { + rt2x00_set_field8(&rfcsr, RFCSR12_DR0, 3); + rt2x00_set_field8(&rfcsr, RFCSR12_TX_POWER, + info->default_power1); + } else { + rt2x00_set_field8(&rfcsr, RFCSR12_DR0, 7); + rt2x00_set_field8(&rfcsr, RFCSR12_TX_POWER, + (info->default_power1 & 0x3) | + ((info->default_power1 & 0xC) << 1)); + } + rt2800_rfcsr_write(rt2x00dev, 12, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 13, &rfcsr); + if (rf->channel <= 14) { + rt2x00_set_field8(&rfcsr, RFCSR13_DR0, 3); + rt2x00_set_field8(&rfcsr, RFCSR13_TX_POWER, + info->default_power2); + } else { + rt2x00_set_field8(&rfcsr, RFCSR13_DR0, 7); + rt2x00_set_field8(&rfcsr, RFCSR13_TX_POWER, + (info->default_power2 & 0x3) | + ((info->default_power2 & 0xC) << 1)); + } + rt2800_rfcsr_write(rt2x00dev, 13, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 1, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR1_RX0_PD, 0); + rt2x00_set_field8(&rfcsr, RFCSR1_TX0_PD, 0); + rt2x00_set_field8(&rfcsr, RFCSR1_RX1_PD, 0); + rt2x00_set_field8(&rfcsr, RFCSR1_TX1_PD, 0); + rt2x00_set_field8(&rfcsr, RFCSR1_RX2_PD, 0); + rt2x00_set_field8(&rfcsr, RFCSR1_TX2_PD, 0); + if (test_bit(CAPABILITY_BT_COEXIST, &rt2x00dev->cap_flags)) { + if (rf->channel <= 14) { + rt2x00_set_field8(&rfcsr, RFCSR1_RX0_PD, 1); + rt2x00_set_field8(&rfcsr, RFCSR1_TX0_PD, 1); + } + rt2x00_set_field8(&rfcsr, RFCSR1_RX2_PD, 1); + rt2x00_set_field8(&rfcsr, RFCSR1_TX2_PD, 1); + } else { + switch (rt2x00dev->default_ant.tx_chain_num) { + case 1: + rt2x00_set_field8(&rfcsr, RFCSR1_TX1_PD, 1); + case 2: + rt2x00_set_field8(&rfcsr, RFCSR1_TX2_PD, 1); + break; + } + + switch (rt2x00dev->default_ant.rx_chain_num) { + case 1: + rt2x00_set_field8(&rfcsr, RFCSR1_RX1_PD, 1); + case 2: + rt2x00_set_field8(&rfcsr, RFCSR1_RX2_PD, 1); + break; + } + } + rt2800_rfcsr_write(rt2x00dev, 1, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 23, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR23_FREQ_OFFSET, rt2x00dev->freq_offset); + rt2800_rfcsr_write(rt2x00dev, 23, rfcsr); + + if (conf_is_ht40(conf)) { + rt2800_rfcsr_write(rt2x00dev, 24, drv_data->calibration_bw40); + rt2800_rfcsr_write(rt2x00dev, 31, drv_data->calibration_bw40); + } else { + rt2800_rfcsr_write(rt2x00dev, 24, drv_data->calibration_bw20); + rt2800_rfcsr_write(rt2x00dev, 31, drv_data->calibration_bw20); + } + + if (rf->channel <= 14) { + rt2800_rfcsr_write(rt2x00dev, 7, 0xd8); + rt2800_rfcsr_write(rt2x00dev, 9, 0xc3); + rt2800_rfcsr_write(rt2x00dev, 10, 0xf1); + rt2800_rfcsr_write(rt2x00dev, 11, 0xb9); + rt2800_rfcsr_write(rt2x00dev, 15, 0x53); + rfcsr = 0x4c; + rt2x00_set_field8(&rfcsr, RFCSR16_TXMIXER_GAIN, + drv_data->txmixer_gain_24g); + rt2800_rfcsr_write(rt2x00dev, 16, rfcsr); + rt2800_rfcsr_write(rt2x00dev, 17, 0x23); + rt2800_rfcsr_write(rt2x00dev, 19, 0x93); + rt2800_rfcsr_write(rt2x00dev, 20, 0xb3); + rt2800_rfcsr_write(rt2x00dev, 25, 0x15); + rt2800_rfcsr_write(rt2x00dev, 26, 0x85); + rt2800_rfcsr_write(rt2x00dev, 27, 0x00); + rt2800_rfcsr_write(rt2x00dev, 29, 0x9b); + } else { + rt2800_rfcsr_read(rt2x00dev, 7, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR7_BIT2, 1); + rt2x00_set_field8(&rfcsr, RFCSR7_BIT3, 0); + rt2x00_set_field8(&rfcsr, RFCSR7_BIT4, 1); + rt2x00_set_field8(&rfcsr, RFCSR7_BITS67, 0); + rt2800_rfcsr_write(rt2x00dev, 7, rfcsr); + rt2800_rfcsr_write(rt2x00dev, 9, 0xc0); + rt2800_rfcsr_write(rt2x00dev, 10, 0xf1); + rt2800_rfcsr_write(rt2x00dev, 11, 0x00); + rt2800_rfcsr_write(rt2x00dev, 15, 0x43); + rfcsr = 0x7a; + rt2x00_set_field8(&rfcsr, RFCSR16_TXMIXER_GAIN, + drv_data->txmixer_gain_5g); + rt2800_rfcsr_write(rt2x00dev, 16, rfcsr); + rt2800_rfcsr_write(rt2x00dev, 17, 0x23); + if (rf->channel <= 64) { + rt2800_rfcsr_write(rt2x00dev, 19, 0xb7); + rt2800_rfcsr_write(rt2x00dev, 20, 0xf6); + rt2800_rfcsr_write(rt2x00dev, 25, 0x3d); + } else if (rf->channel <= 128) { + rt2800_rfcsr_write(rt2x00dev, 19, 0x74); + rt2800_rfcsr_write(rt2x00dev, 20, 0xf4); + rt2800_rfcsr_write(rt2x00dev, 25, 0x01); + } else { + rt2800_rfcsr_write(rt2x00dev, 19, 0x72); + rt2800_rfcsr_write(rt2x00dev, 20, 0xf3); + rt2800_rfcsr_write(rt2x00dev, 25, 0x01); + } + rt2800_rfcsr_write(rt2x00dev, 26, 0x87); + rt2800_rfcsr_write(rt2x00dev, 27, 0x01); + rt2800_rfcsr_write(rt2x00dev, 29, 0x9f); + } + + rt2800_register_read(rt2x00dev, GPIO_CTRL, ®); + rt2x00_set_field32(®, GPIO_CTRL_DIR7, 0); + if (rf->channel <= 14) + rt2x00_set_field32(®, GPIO_CTRL_VAL7, 1); + else + rt2x00_set_field32(®, GPIO_CTRL_VAL7, 0); + rt2800_register_write(rt2x00dev, GPIO_CTRL, reg); + + rt2800_rfcsr_read(rt2x00dev, 7, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR7_RF_TUNING, 1); + rt2800_rfcsr_write(rt2x00dev, 7, rfcsr); +} + +#define POWER_BOUND 0x27 +#define POWER_BOUND_5G 0x2b +#define FREQ_OFFSET_BOUND 0x5f + +static void rt2800_adjust_freq_offset(struct rt2x00_dev *rt2x00dev) +{ + u8 rfcsr; + + rt2800_rfcsr_read(rt2x00dev, 17, &rfcsr); + if (rt2x00dev->freq_offset > FREQ_OFFSET_BOUND) + rt2x00_set_field8(&rfcsr, RFCSR17_CODE, FREQ_OFFSET_BOUND); + else + rt2x00_set_field8(&rfcsr, RFCSR17_CODE, rt2x00dev->freq_offset); + rt2800_rfcsr_write(rt2x00dev, 17, rfcsr); +} + +static void rt2800_config_channel_rf3290(struct rt2x00_dev *rt2x00dev, + struct ieee80211_conf *conf, + struct rf_channel *rf, + struct channel_info *info) +{ + u8 rfcsr; + + rt2800_rfcsr_write(rt2x00dev, 8, rf->rf1); + rt2800_rfcsr_write(rt2x00dev, 9, rf->rf3); + rt2800_rfcsr_read(rt2x00dev, 11, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR11_R, rf->rf2); + rt2800_rfcsr_write(rt2x00dev, 11, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 49, &rfcsr); + if (info->default_power1 > POWER_BOUND) + rt2x00_set_field8(&rfcsr, RFCSR49_TX, POWER_BOUND); + else + rt2x00_set_field8(&rfcsr, RFCSR49_TX, info->default_power1); + rt2800_rfcsr_write(rt2x00dev, 49, rfcsr); + + rt2800_adjust_freq_offset(rt2x00dev); + + if (rf->channel <= 14) { + if (rf->channel == 6) + rt2800_bbp_write(rt2x00dev, 68, 0x0c); + else + rt2800_bbp_write(rt2x00dev, 68, 0x0b); + + if (rf->channel >= 1 && rf->channel <= 6) + rt2800_bbp_write(rt2x00dev, 59, 0x0f); + else if (rf->channel >= 7 && rf->channel <= 11) + rt2800_bbp_write(rt2x00dev, 59, 0x0e); + else if (rf->channel >= 12 && rf->channel <= 14) + rt2800_bbp_write(rt2x00dev, 59, 0x0d); + } +} + +static void rt2800_config_channel_rf3322(struct rt2x00_dev *rt2x00dev, + struct ieee80211_conf *conf, + struct rf_channel *rf, + struct channel_info *info) +{ + u8 rfcsr; + + rt2800_rfcsr_write(rt2x00dev, 8, rf->rf1); + rt2800_rfcsr_write(rt2x00dev, 9, rf->rf3); + + rt2800_rfcsr_write(rt2x00dev, 11, 0x42); + rt2800_rfcsr_write(rt2x00dev, 12, 0x1c); + rt2800_rfcsr_write(rt2x00dev, 13, 0x00); + + if (info->default_power1 > POWER_BOUND) + rt2800_rfcsr_write(rt2x00dev, 47, POWER_BOUND); + else + rt2800_rfcsr_write(rt2x00dev, 47, info->default_power1); + + if (info->default_power2 > POWER_BOUND) + rt2800_rfcsr_write(rt2x00dev, 48, POWER_BOUND); + else + rt2800_rfcsr_write(rt2x00dev, 48, info->default_power2); + + rt2800_adjust_freq_offset(rt2x00dev); + + rt2800_rfcsr_read(rt2x00dev, 1, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR1_RX0_PD, 1); + rt2x00_set_field8(&rfcsr, RFCSR1_TX0_PD, 1); + + if ( rt2x00dev->default_ant.tx_chain_num == 2 ) + rt2x00_set_field8(&rfcsr, RFCSR1_TX1_PD, 1); + else + rt2x00_set_field8(&rfcsr, RFCSR1_TX1_PD, 0); + + if ( rt2x00dev->default_ant.rx_chain_num == 2 ) + rt2x00_set_field8(&rfcsr, RFCSR1_RX1_PD, 1); + else + rt2x00_set_field8(&rfcsr, RFCSR1_RX1_PD, 0); + + rt2x00_set_field8(&rfcsr, RFCSR1_RX2_PD, 0); + rt2x00_set_field8(&rfcsr, RFCSR1_TX2_PD, 0); + + rt2800_rfcsr_write(rt2x00dev, 1, rfcsr); + + rt2800_rfcsr_write(rt2x00dev, 31, 80); +} + +static void rt2800_config_channel_rf53xx(struct rt2x00_dev *rt2x00dev, + struct ieee80211_conf *conf, + struct rf_channel *rf, + struct channel_info *info) +{ + u8 rfcsr; + + rt2800_rfcsr_write(rt2x00dev, 8, rf->rf1); + rt2800_rfcsr_write(rt2x00dev, 9, rf->rf3); + rt2800_rfcsr_read(rt2x00dev, 11, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR11_R, rf->rf2); + rt2800_rfcsr_write(rt2x00dev, 11, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 49, &rfcsr); + if (info->default_power1 > POWER_BOUND) + rt2x00_set_field8(&rfcsr, RFCSR49_TX, POWER_BOUND); + else + rt2x00_set_field8(&rfcsr, RFCSR49_TX, info->default_power1); + rt2800_rfcsr_write(rt2x00dev, 49, rfcsr); + + if (rt2x00_rt(rt2x00dev, RT5392)) { + rt2800_rfcsr_read(rt2x00dev, 50, &rfcsr); + if (info->default_power1 > POWER_BOUND) + rt2x00_set_field8(&rfcsr, RFCSR50_TX, POWER_BOUND); + else + rt2x00_set_field8(&rfcsr, RFCSR50_TX, + info->default_power2); + rt2800_rfcsr_write(rt2x00dev, 50, rfcsr); + } + + rt2800_rfcsr_read(rt2x00dev, 1, &rfcsr); + if (rt2x00_rt(rt2x00dev, RT5392)) { + rt2x00_set_field8(&rfcsr, RFCSR1_RX1_PD, 1); + rt2x00_set_field8(&rfcsr, RFCSR1_TX1_PD, 1); + } + rt2x00_set_field8(&rfcsr, RFCSR1_RF_BLOCK_EN, 1); + rt2x00_set_field8(&rfcsr, RFCSR1_PLL_PD, 1); + rt2x00_set_field8(&rfcsr, RFCSR1_RX0_PD, 1); + rt2x00_set_field8(&rfcsr, RFCSR1_TX0_PD, 1); + rt2800_rfcsr_write(rt2x00dev, 1, rfcsr); + + rt2800_adjust_freq_offset(rt2x00dev); + + if (rf->channel <= 14) { + int idx = rf->channel-1; + + if (test_bit(CAPABILITY_BT_COEXIST, &rt2x00dev->cap_flags)) { + if (rt2x00_rt_rev_gte(rt2x00dev, RT5390, REV_RT5390F)) { + /* r55/r59 value array of channel 1~14 */ + static const char r55_bt_rev[] = {0x83, 0x83, + 0x83, 0x73, 0x73, 0x63, 0x53, 0x53, + 0x53, 0x43, 0x43, 0x43, 0x43, 0x43}; + static const char r59_bt_rev[] = {0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0b, 0x0a, 0x09, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07}; + + rt2800_rfcsr_write(rt2x00dev, 55, + r55_bt_rev[idx]); + rt2800_rfcsr_write(rt2x00dev, 59, + r59_bt_rev[idx]); + } else { + static const char r59_bt[] = {0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8a, 0x89, + 0x88, 0x88, 0x86, 0x85, 0x84}; + + rt2800_rfcsr_write(rt2x00dev, 59, r59_bt[idx]); + } + } else { + if (rt2x00_rt_rev_gte(rt2x00dev, RT5390, REV_RT5390F)) { + static const char r55_nonbt_rev[] = {0x23, 0x23, + 0x23, 0x23, 0x13, 0x13, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03}; + static const char r59_nonbt_rev[] = {0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x06, 0x05, 0x04, 0x04}; + + rt2800_rfcsr_write(rt2x00dev, 55, + r55_nonbt_rev[idx]); + rt2800_rfcsr_write(rt2x00dev, 59, + r59_nonbt_rev[idx]); + } else if (rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) { + static const char r59_non_bt[] = {0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8d, + 0x8a, 0x88, 0x88, 0x87, 0x87, 0x86}; + + rt2800_rfcsr_write(rt2x00dev, 59, + r59_non_bt[idx]); + } + } + } +} + +static void rt2800_config_channel_rf55xx(struct rt2x00_dev *rt2x00dev, + struct ieee80211_conf *conf, + struct rf_channel *rf, + struct channel_info *info) +{ + u8 rfcsr, ep_reg; + u32 reg; + int power_bound; + + /* TODO */ + const bool is_11b = false; + const bool is_type_ep = false; + + rt2800_register_read(rt2x00dev, LDO_CFG0, ®); + rt2x00_set_field32(®, LDO_CFG0_LDO_CORE_VLEVEL, + (rf->channel > 14 || conf_is_ht40(conf)) ? 5 : 0); + rt2800_register_write(rt2x00dev, LDO_CFG0, reg); + + /* Order of values on rf_channel entry: N, K, mod, R */ + rt2800_rfcsr_write(rt2x00dev, 8, rf->rf1 & 0xff); + + rt2800_rfcsr_read(rt2x00dev, 9, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR9_K, rf->rf2 & 0xf); + rt2x00_set_field8(&rfcsr, RFCSR9_N, (rf->rf1 & 0x100) >> 8); + rt2x00_set_field8(&rfcsr, RFCSR9_MOD, ((rf->rf3 - 8) & 0x4) >> 2); + rt2800_rfcsr_write(rt2x00dev, 9, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 11, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR11_R, rf->rf4 - 1); + rt2x00_set_field8(&rfcsr, RFCSR11_MOD, (rf->rf3 - 8) & 0x3); + rt2800_rfcsr_write(rt2x00dev, 11, rfcsr); + + if (rf->channel <= 14) { + rt2800_rfcsr_write(rt2x00dev, 10, 0x90); + /* FIXME: RF11 owerwrite ? */ + rt2800_rfcsr_write(rt2x00dev, 11, 0x4A); + rt2800_rfcsr_write(rt2x00dev, 12, 0x52); + rt2800_rfcsr_write(rt2x00dev, 13, 0x42); + rt2800_rfcsr_write(rt2x00dev, 22, 0x40); + rt2800_rfcsr_write(rt2x00dev, 24, 0x4A); + rt2800_rfcsr_write(rt2x00dev, 25, 0x80); + rt2800_rfcsr_write(rt2x00dev, 27, 0x42); + rt2800_rfcsr_write(rt2x00dev, 36, 0x80); + rt2800_rfcsr_write(rt2x00dev, 37, 0x08); + rt2800_rfcsr_write(rt2x00dev, 38, 0x89); + rt2800_rfcsr_write(rt2x00dev, 39, 0x1B); + rt2800_rfcsr_write(rt2x00dev, 40, 0x0D); + rt2800_rfcsr_write(rt2x00dev, 41, 0x9B); + rt2800_rfcsr_write(rt2x00dev, 42, 0xD5); + rt2800_rfcsr_write(rt2x00dev, 43, 0x72); + rt2800_rfcsr_write(rt2x00dev, 44, 0x0E); + rt2800_rfcsr_write(rt2x00dev, 45, 0xA2); + rt2800_rfcsr_write(rt2x00dev, 46, 0x6B); + rt2800_rfcsr_write(rt2x00dev, 48, 0x10); + rt2800_rfcsr_write(rt2x00dev, 51, 0x3E); + rt2800_rfcsr_write(rt2x00dev, 52, 0x48); + rt2800_rfcsr_write(rt2x00dev, 54, 0x38); + rt2800_rfcsr_write(rt2x00dev, 56, 0xA1); + rt2800_rfcsr_write(rt2x00dev, 57, 0x00); + rt2800_rfcsr_write(rt2x00dev, 58, 0x39); + rt2800_rfcsr_write(rt2x00dev, 60, 0x45); + rt2800_rfcsr_write(rt2x00dev, 61, 0x91); + rt2800_rfcsr_write(rt2x00dev, 62, 0x39); + + /* TODO RF27 <- tssi */ + + rfcsr = rf->channel <= 10 ? 0x07 : 0x06; + rt2800_rfcsr_write(rt2x00dev, 23, rfcsr); + rt2800_rfcsr_write(rt2x00dev, 59, rfcsr); + + if (is_11b) { + /* CCK */ + rt2800_rfcsr_write(rt2x00dev, 31, 0xF8); + rt2800_rfcsr_write(rt2x00dev, 32, 0xC0); + if (is_type_ep) + rt2800_rfcsr_write(rt2x00dev, 55, 0x06); + else + rt2800_rfcsr_write(rt2x00dev, 55, 0x47); + } else { + /* OFDM */ + if (is_type_ep) + rt2800_rfcsr_write(rt2x00dev, 55, 0x03); + else + rt2800_rfcsr_write(rt2x00dev, 55, 0x43); + } + + power_bound = POWER_BOUND; + ep_reg = 0x2; + } else { + rt2800_rfcsr_write(rt2x00dev, 10, 0x97); + /* FIMXE: RF11 overwrite */ + rt2800_rfcsr_write(rt2x00dev, 11, 0x40); + rt2800_rfcsr_write(rt2x00dev, 25, 0xBF); + rt2800_rfcsr_write(rt2x00dev, 27, 0x42); + rt2800_rfcsr_write(rt2x00dev, 36, 0x00); + rt2800_rfcsr_write(rt2x00dev, 37, 0x04); + rt2800_rfcsr_write(rt2x00dev, 38, 0x85); + rt2800_rfcsr_write(rt2x00dev, 40, 0x42); + rt2800_rfcsr_write(rt2x00dev, 41, 0xBB); + rt2800_rfcsr_write(rt2x00dev, 42, 0xD7); + rt2800_rfcsr_write(rt2x00dev, 45, 0x41); + rt2800_rfcsr_write(rt2x00dev, 48, 0x00); + rt2800_rfcsr_write(rt2x00dev, 57, 0x77); + rt2800_rfcsr_write(rt2x00dev, 60, 0x05); + rt2800_rfcsr_write(rt2x00dev, 61, 0x01); + + /* TODO RF27 <- tssi */ + + if (rf->channel >= 36 && rf->channel <= 64) { + + rt2800_rfcsr_write(rt2x00dev, 12, 0x2E); + rt2800_rfcsr_write(rt2x00dev, 13, 0x22); + rt2800_rfcsr_write(rt2x00dev, 22, 0x60); + rt2800_rfcsr_write(rt2x00dev, 23, 0x7F); + if (rf->channel <= 50) + rt2800_rfcsr_write(rt2x00dev, 24, 0x09); + else if (rf->channel >= 52) + rt2800_rfcsr_write(rt2x00dev, 24, 0x07); + rt2800_rfcsr_write(rt2x00dev, 39, 0x1C); + rt2800_rfcsr_write(rt2x00dev, 43, 0x5B); + rt2800_rfcsr_write(rt2x00dev, 44, 0X40); + rt2800_rfcsr_write(rt2x00dev, 46, 0X00); + rt2800_rfcsr_write(rt2x00dev, 51, 0xFE); + rt2800_rfcsr_write(rt2x00dev, 52, 0x0C); + rt2800_rfcsr_write(rt2x00dev, 54, 0xF8); + if (rf->channel <= 50) { + rt2800_rfcsr_write(rt2x00dev, 55, 0x06), + rt2800_rfcsr_write(rt2x00dev, 56, 0xD3); + } else if (rf->channel >= 52) { + rt2800_rfcsr_write(rt2x00dev, 55, 0x04); + rt2800_rfcsr_write(rt2x00dev, 56, 0xBB); + } + + rt2800_rfcsr_write(rt2x00dev, 58, 0x15); + rt2800_rfcsr_write(rt2x00dev, 59, 0x7F); + rt2800_rfcsr_write(rt2x00dev, 62, 0x15); + + } else if (rf->channel >= 100 && rf->channel <= 165) { + + rt2800_rfcsr_write(rt2x00dev, 12, 0x0E); + rt2800_rfcsr_write(rt2x00dev, 13, 0x42); + rt2800_rfcsr_write(rt2x00dev, 22, 0x40); + if (rf->channel <= 153) { + rt2800_rfcsr_write(rt2x00dev, 23, 0x3C); + rt2800_rfcsr_write(rt2x00dev, 24, 0x06); + } else if (rf->channel >= 155) { + rt2800_rfcsr_write(rt2x00dev, 23, 0x38); + rt2800_rfcsr_write(rt2x00dev, 24, 0x05); + } + if (rf->channel <= 138) { + rt2800_rfcsr_write(rt2x00dev, 39, 0x1A); + rt2800_rfcsr_write(rt2x00dev, 43, 0x3B); + rt2800_rfcsr_write(rt2x00dev, 44, 0x20); + rt2800_rfcsr_write(rt2x00dev, 46, 0x18); + } else if (rf->channel >= 140) { + rt2800_rfcsr_write(rt2x00dev, 39, 0x18); + rt2800_rfcsr_write(rt2x00dev, 43, 0x1B); + rt2800_rfcsr_write(rt2x00dev, 44, 0x10); + rt2800_rfcsr_write(rt2x00dev, 46, 0X08); + } + if (rf->channel <= 124) + rt2800_rfcsr_write(rt2x00dev, 51, 0xFC); + else if (rf->channel >= 126) + rt2800_rfcsr_write(rt2x00dev, 51, 0xEC); + if (rf->channel <= 138) + rt2800_rfcsr_write(rt2x00dev, 52, 0x06); + else if (rf->channel >= 140) + rt2800_rfcsr_write(rt2x00dev, 52, 0x06); + rt2800_rfcsr_write(rt2x00dev, 54, 0xEB); + if (rf->channel <= 138) + rt2800_rfcsr_write(rt2x00dev, 55, 0x01); + else if (rf->channel >= 140) + rt2800_rfcsr_write(rt2x00dev, 55, 0x00); + if (rf->channel <= 128) + rt2800_rfcsr_write(rt2x00dev, 56, 0xBB); + else if (rf->channel >= 130) + rt2800_rfcsr_write(rt2x00dev, 56, 0xAB); + if (rf->channel <= 116) + rt2800_rfcsr_write(rt2x00dev, 58, 0x1D); + else if (rf->channel >= 118) + rt2800_rfcsr_write(rt2x00dev, 58, 0x15); + if (rf->channel <= 138) + rt2800_rfcsr_write(rt2x00dev, 59, 0x3F); + else if (rf->channel >= 140) + rt2800_rfcsr_write(rt2x00dev, 59, 0x7C); + if (rf->channel <= 116) + rt2800_rfcsr_write(rt2x00dev, 62, 0x1D); + else if (rf->channel >= 118) + rt2800_rfcsr_write(rt2x00dev, 62, 0x15); + } + + power_bound = POWER_BOUND_5G; + ep_reg = 0x3; + } + + rt2800_rfcsr_read(rt2x00dev, 49, &rfcsr); + if (info->default_power1 > power_bound) + rt2x00_set_field8(&rfcsr, RFCSR49_TX, power_bound); + else + rt2x00_set_field8(&rfcsr, RFCSR49_TX, info->default_power1); + if (is_type_ep) + rt2x00_set_field8(&rfcsr, RFCSR49_EP, ep_reg); + rt2800_rfcsr_write(rt2x00dev, 49, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 50, &rfcsr); + if (info->default_power2 > power_bound) + rt2x00_set_field8(&rfcsr, RFCSR50_TX, power_bound); + else + rt2x00_set_field8(&rfcsr, RFCSR50_TX, info->default_power2); + if (is_type_ep) + rt2x00_set_field8(&rfcsr, RFCSR50_EP, ep_reg); + rt2800_rfcsr_write(rt2x00dev, 50, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 1, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR1_RF_BLOCK_EN, 1); + rt2x00_set_field8(&rfcsr, RFCSR1_PLL_PD, 1); + + rt2x00_set_field8(&rfcsr, RFCSR1_TX0_PD, + rt2x00dev->default_ant.tx_chain_num >= 1); + rt2x00_set_field8(&rfcsr, RFCSR1_TX1_PD, + rt2x00dev->default_ant.tx_chain_num == 2); + rt2x00_set_field8(&rfcsr, RFCSR1_TX2_PD, 0); + + rt2x00_set_field8(&rfcsr, RFCSR1_RX0_PD, + rt2x00dev->default_ant.rx_chain_num >= 1); + rt2x00_set_field8(&rfcsr, RFCSR1_RX1_PD, + rt2x00dev->default_ant.rx_chain_num == 2); + rt2x00_set_field8(&rfcsr, RFCSR1_RX2_PD, 0); + + rt2800_rfcsr_write(rt2x00dev, 1, rfcsr); + rt2800_rfcsr_write(rt2x00dev, 6, 0xe4); + + if (conf_is_ht40(conf)) + rt2800_rfcsr_write(rt2x00dev, 30, 0x16); + else + rt2800_rfcsr_write(rt2x00dev, 30, 0x10); + + if (!is_11b) { + rt2800_rfcsr_write(rt2x00dev, 31, 0x80); + rt2800_rfcsr_write(rt2x00dev, 32, 0x80); + } + + /* TODO proper frequency adjustment */ + rt2800_adjust_freq_offset(rt2x00dev); + + /* TODO merge with others */ + rt2800_rfcsr_read(rt2x00dev, 3, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR3_VCOCAL_EN, 1); + rt2800_rfcsr_write(rt2x00dev, 3, rfcsr); + + /* BBP settings */ + rt2800_bbp_write(rt2x00dev, 62, 0x37 - rt2x00dev->lna_gain); + rt2800_bbp_write(rt2x00dev, 63, 0x37 - rt2x00dev->lna_gain); + rt2800_bbp_write(rt2x00dev, 64, 0x37 - rt2x00dev->lna_gain); + + rt2800_bbp_write(rt2x00dev, 79, (rf->channel <= 14) ? 0x1C : 0x18); + rt2800_bbp_write(rt2x00dev, 80, (rf->channel <= 14) ? 0x0E : 0x08); + rt2800_bbp_write(rt2x00dev, 81, (rf->channel <= 14) ? 0x3A : 0x38); + rt2800_bbp_write(rt2x00dev, 82, (rf->channel <= 14) ? 0x62 : 0x92); + + /* GLRT band configuration */ + rt2800_bbp_write(rt2x00dev, 195, 128); + rt2800_bbp_write(rt2x00dev, 196, (rf->channel <= 14) ? 0xE0 : 0xF0); + rt2800_bbp_write(rt2x00dev, 195, 129); + rt2800_bbp_write(rt2x00dev, 196, (rf->channel <= 14) ? 0x1F : 0x1E); + rt2800_bbp_write(rt2x00dev, 195, 130); + rt2800_bbp_write(rt2x00dev, 196, (rf->channel <= 14) ? 0x38 : 0x28); + rt2800_bbp_write(rt2x00dev, 195, 131); + rt2800_bbp_write(rt2x00dev, 196, (rf->channel <= 14) ? 0x32 : 0x20); + rt2800_bbp_write(rt2x00dev, 195, 133); + rt2800_bbp_write(rt2x00dev, 196, (rf->channel <= 14) ? 0x28 : 0x7F); + rt2800_bbp_write(rt2x00dev, 195, 124); + rt2800_bbp_write(rt2x00dev, 196, (rf->channel <= 14) ? 0x19 : 0x7F); +} + +static void rt2800_bbp_write_with_rx_chain(struct rt2x00_dev *rt2x00dev, + const unsigned int word, + const u8 value) +{ + u8 chain, reg; + + for (chain = 0; chain < rt2x00dev->default_ant.rx_chain_num; chain++) { + rt2800_bbp_read(rt2x00dev, 27, ®); + rt2x00_set_field8(®, BBP27_RX_CHAIN_SEL, chain); + rt2800_bbp_write(rt2x00dev, 27, reg); + + rt2800_bbp_write(rt2x00dev, word, value); + } +} + +static void rt2800_iq_calibrate(struct rt2x00_dev *rt2x00dev, int channel) +{ + u8 cal; + + /* TX0 IQ Gain */ + rt2800_bbp_write(rt2x00dev, 158, 0x2c); + if (channel <= 14) + cal = rt2x00_eeprom_byte(rt2x00dev, EEPROM_IQ_GAIN_CAL_TX0_2G); + else if (channel >= 36 && channel <= 64) + cal = rt2x00_eeprom_byte(rt2x00dev, + EEPROM_IQ_GAIN_CAL_TX0_CH36_TO_CH64_5G); + else if (channel >= 100 && channel <= 138) + cal = rt2x00_eeprom_byte(rt2x00dev, + EEPROM_IQ_GAIN_CAL_TX0_CH100_TO_CH138_5G); + else if (channel >= 140 && channel <= 165) + cal = rt2x00_eeprom_byte(rt2x00dev, + EEPROM_IQ_GAIN_CAL_TX0_CH140_TO_CH165_5G); + else + cal = 0; + rt2800_bbp_write(rt2x00dev, 159, cal); + + /* TX0 IQ Phase */ + rt2800_bbp_write(rt2x00dev, 158, 0x2d); + if (channel <= 14) + cal = rt2x00_eeprom_byte(rt2x00dev, EEPROM_IQ_PHASE_CAL_TX0_2G); + else if (channel >= 36 && channel <= 64) + cal = rt2x00_eeprom_byte(rt2x00dev, + EEPROM_IQ_PHASE_CAL_TX0_CH36_TO_CH64_5G); + else if (channel >= 100 && channel <= 138) + cal = rt2x00_eeprom_byte(rt2x00dev, + EEPROM_IQ_PHASE_CAL_TX0_CH100_TO_CH138_5G); + else if (channel >= 140 && channel <= 165) + cal = rt2x00_eeprom_byte(rt2x00dev, + EEPROM_IQ_PHASE_CAL_TX0_CH140_TO_CH165_5G); + else + cal = 0; + rt2800_bbp_write(rt2x00dev, 159, cal); + + /* TX1 IQ Gain */ + rt2800_bbp_write(rt2x00dev, 158, 0x4a); + if (channel <= 14) + cal = rt2x00_eeprom_byte(rt2x00dev, EEPROM_IQ_GAIN_CAL_TX1_2G); + else if (channel >= 36 && channel <= 64) + cal = rt2x00_eeprom_byte(rt2x00dev, + EEPROM_IQ_GAIN_CAL_TX1_CH36_TO_CH64_5G); + else if (channel >= 100 && channel <= 138) + cal = rt2x00_eeprom_byte(rt2x00dev, + EEPROM_IQ_GAIN_CAL_TX1_CH100_TO_CH138_5G); + else if (channel >= 140 && channel <= 165) + cal = rt2x00_eeprom_byte(rt2x00dev, + EEPROM_IQ_GAIN_CAL_TX1_CH140_TO_CH165_5G); + else + cal = 0; + rt2800_bbp_write(rt2x00dev, 159, cal); + + /* TX1 IQ Phase */ + rt2800_bbp_write(rt2x00dev, 158, 0x4b); + if (channel <= 14) + cal = rt2x00_eeprom_byte(rt2x00dev, EEPROM_IQ_PHASE_CAL_TX1_2G); + else if (channel >= 36 && channel <= 64) + cal = rt2x00_eeprom_byte(rt2x00dev, + EEPROM_IQ_PHASE_CAL_TX1_CH36_TO_CH64_5G); + else if (channel >= 100 && channel <= 138) + cal = rt2x00_eeprom_byte(rt2x00dev, + EEPROM_IQ_PHASE_CAL_TX1_CH100_TO_CH138_5G); + else if (channel >= 140 && channel <= 165) + cal = rt2x00_eeprom_byte(rt2x00dev, + EEPROM_IQ_PHASE_CAL_TX1_CH140_TO_CH165_5G); + else + cal = 0; + rt2800_bbp_write(rt2x00dev, 159, cal); + + /* FIXME: possible RX0, RX1 callibration ? */ + + /* RF IQ compensation control */ + rt2800_bbp_write(rt2x00dev, 158, 0x04); + cal = rt2x00_eeprom_byte(rt2x00dev, EEPROM_RF_IQ_COMPENSATION_CONTROL); + rt2800_bbp_write(rt2x00dev, 159, cal != 0xff ? cal : 0); + + /* RF IQ imbalance compensation control */ + rt2800_bbp_write(rt2x00dev, 158, 0x03); + cal = rt2x00_eeprom_byte(rt2x00dev, + EEPROM_RF_IQ_IMBALANCE_COMPENSATION_CONTROL); + rt2800_bbp_write(rt2x00dev, 159, cal != 0xff ? cal : 0); +} + +static void rt2800_config_channel(struct rt2x00_dev *rt2x00dev, + struct ieee80211_conf *conf, + struct rf_channel *rf, + struct channel_info *info) +{ + u32 reg; + unsigned int tx_pin; + u8 bbp, rfcsr; + + if (rf->channel <= 14) { + info->default_power1 = TXPOWER_G_TO_DEV(info->default_power1); + info->default_power2 = TXPOWER_G_TO_DEV(info->default_power2); + } else { + info->default_power1 = TXPOWER_A_TO_DEV(info->default_power1); + info->default_power2 = TXPOWER_A_TO_DEV(info->default_power2); + } + + switch (rt2x00dev->chip.rf) { + case RF2020: + case RF3020: + case RF3021: + case RF3022: + case RF3320: + rt2800_config_channel_rf3xxx(rt2x00dev, conf, rf, info); + break; + case RF3052: + rt2800_config_channel_rf3052(rt2x00dev, conf, rf, info); + break; + case RF3290: + rt2800_config_channel_rf3290(rt2x00dev, conf, rf, info); + break; + case RF3322: + rt2800_config_channel_rf3322(rt2x00dev, conf, rf, info); + break; + case RF5360: + case RF5370: + case RF5372: + case RF5390: + case RF5392: + rt2800_config_channel_rf53xx(rt2x00dev, conf, rf, info); + break; + case RF5592: + rt2800_config_channel_rf55xx(rt2x00dev, conf, rf, info); + break; + default: + rt2800_config_channel_rf2xxx(rt2x00dev, conf, rf, info); + } + + if (rt2x00_rf(rt2x00dev, RF3290) || + rt2x00_rf(rt2x00dev, RF3322) || + rt2x00_rf(rt2x00dev, RF5360) || + rt2x00_rf(rt2x00dev, RF5370) || + rt2x00_rf(rt2x00dev, RF5372) || + rt2x00_rf(rt2x00dev, RF5390) || + rt2x00_rf(rt2x00dev, RF5392)) { + rt2800_rfcsr_read(rt2x00dev, 30, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR30_TX_H20M, 0); + rt2x00_set_field8(&rfcsr, RFCSR30_RX_H20M, 0); + rt2800_rfcsr_write(rt2x00dev, 30, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 3, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR3_VCOCAL_EN, 1); + rt2800_rfcsr_write(rt2x00dev, 3, rfcsr); + } + + /* + * Change BBP settings + */ + if (rt2x00_rt(rt2x00dev, RT3352)) { + rt2800_bbp_write(rt2x00dev, 27, 0x0); + rt2800_bbp_write(rt2x00dev, 66, 0x26 + rt2x00dev->lna_gain); + rt2800_bbp_write(rt2x00dev, 27, 0x20); + rt2800_bbp_write(rt2x00dev, 66, 0x26 + rt2x00dev->lna_gain); + } else { + rt2800_bbp_write(rt2x00dev, 62, 0x37 - rt2x00dev->lna_gain); + rt2800_bbp_write(rt2x00dev, 63, 0x37 - rt2x00dev->lna_gain); + rt2800_bbp_write(rt2x00dev, 64, 0x37 - rt2x00dev->lna_gain); + rt2800_bbp_write(rt2x00dev, 86, 0); + } + + if (rf->channel <= 14) { + if (!rt2x00_rt(rt2x00dev, RT5390) && + !rt2x00_rt(rt2x00dev, RT5392)) { + if (test_bit(CAPABILITY_EXTERNAL_LNA_BG, + &rt2x00dev->cap_flags)) { + rt2800_bbp_write(rt2x00dev, 82, 0x62); + rt2800_bbp_write(rt2x00dev, 75, 0x46); + } else { + rt2800_bbp_write(rt2x00dev, 82, 0x84); + rt2800_bbp_write(rt2x00dev, 75, 0x50); + } + } + } else { + if (rt2x00_rt(rt2x00dev, RT3572)) + rt2800_bbp_write(rt2x00dev, 82, 0x94); + else + rt2800_bbp_write(rt2x00dev, 82, 0xf2); + + if (test_bit(CAPABILITY_EXTERNAL_LNA_A, &rt2x00dev->cap_flags)) + rt2800_bbp_write(rt2x00dev, 75, 0x46); + else + rt2800_bbp_write(rt2x00dev, 75, 0x50); + } + + rt2800_register_read(rt2x00dev, TX_BAND_CFG, ®); + rt2x00_set_field32(®, TX_BAND_CFG_HT40_MINUS, conf_is_ht40_minus(conf)); + rt2x00_set_field32(®, TX_BAND_CFG_A, rf->channel > 14); + rt2x00_set_field32(®, TX_BAND_CFG_BG, rf->channel <= 14); + rt2800_register_write(rt2x00dev, TX_BAND_CFG, reg); + + if (rt2x00_rt(rt2x00dev, RT3572)) + rt2800_rfcsr_write(rt2x00dev, 8, 0); + + tx_pin = 0; + + /* Turn on unused PA or LNA when not using 1T or 1R */ + if (rt2x00dev->default_ant.tx_chain_num == 2) { + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_A1_EN, + rf->channel > 14); + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_G1_EN, + rf->channel <= 14); + } + + /* Turn on unused PA or LNA when not using 1T or 1R */ + if (rt2x00dev->default_ant.rx_chain_num == 2) { + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_LNA_PE_A1_EN, 1); + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_LNA_PE_G1_EN, 1); + } + + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_LNA_PE_A0_EN, 1); + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_LNA_PE_G0_EN, 1); + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_RFTR_EN, 1); + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_TRSW_EN, 1); + if (test_bit(CAPABILITY_BT_COEXIST, &rt2x00dev->cap_flags)) + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_G0_EN, 1); + else + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_G0_EN, + rf->channel <= 14); + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_A0_EN, rf->channel > 14); + + rt2800_register_write(rt2x00dev, TX_PIN_CFG, tx_pin); + + if (rt2x00_rt(rt2x00dev, RT3572)) + rt2800_rfcsr_write(rt2x00dev, 8, 0x80); + + if (rt2x00_rt(rt2x00dev, RT5592)) { + rt2800_bbp_write(rt2x00dev, 195, 141); + rt2800_bbp_write(rt2x00dev, 196, conf_is_ht40(conf) ? 0x10 : 0x1a); + + /* AGC init */ + reg = (rf->channel <= 14 ? 0x1c : 0x24) + 2 * rt2x00dev->lna_gain; + rt2800_bbp_write_with_rx_chain(rt2x00dev, 66, reg); + + rt2800_iq_calibrate(rt2x00dev, rf->channel); + } + + rt2800_bbp_read(rt2x00dev, 4, &bbp); + rt2x00_set_field8(&bbp, BBP4_BANDWIDTH, 2 * conf_is_ht40(conf)); + rt2800_bbp_write(rt2x00dev, 4, bbp); + + rt2800_bbp_read(rt2x00dev, 3, &bbp); + rt2x00_set_field8(&bbp, BBP3_HT40_MINUS, conf_is_ht40_minus(conf)); + rt2800_bbp_write(rt2x00dev, 3, bbp); + + if (rt2x00_rt_rev(rt2x00dev, RT2860, REV_RT2860C)) { + if (conf_is_ht40(conf)) { + rt2800_bbp_write(rt2x00dev, 69, 0x1a); + rt2800_bbp_write(rt2x00dev, 70, 0x0a); + rt2800_bbp_write(rt2x00dev, 73, 0x16); + } else { + rt2800_bbp_write(rt2x00dev, 69, 0x16); + rt2800_bbp_write(rt2x00dev, 70, 0x08); + rt2800_bbp_write(rt2x00dev, 73, 0x11); + } + } + + msleep(1); + + /* + * Clear channel statistic counters + */ + rt2800_register_read(rt2x00dev, CH_IDLE_STA, ®); + rt2800_register_read(rt2x00dev, CH_BUSY_STA, ®); + rt2800_register_read(rt2x00dev, CH_BUSY_STA_SEC, ®); + + /* + * Clear update flag + */ + if (rt2x00_rt(rt2x00dev, RT3352)) { + rt2800_bbp_read(rt2x00dev, 49, &bbp); + rt2x00_set_field8(&bbp, BBP49_UPDATE_FLAG, 0); + rt2800_bbp_write(rt2x00dev, 49, bbp); + } +} + +static int rt2800_get_gain_calibration_delta(struct rt2x00_dev *rt2x00dev) +{ + u8 tssi_bounds[9]; + u8 current_tssi; + u16 eeprom; + u8 step; + int i; + + /* + * First check if temperature compensation is supported. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC_CONF1, &eeprom); + if (!rt2x00_get_field16(eeprom, EEPROM_NIC_CONF1_EXTERNAL_TX_ALC)) + return 0; + + /* + * Read TSSI boundaries for temperature compensation from + * the EEPROM. + * + * Array idx 0 1 2 3 4 5 6 7 8 + * Matching Delta value -4 -3 -2 -1 0 +1 +2 +3 +4 + * Example TSSI bounds 0xF0 0xD0 0xB5 0xA0 0x88 0x45 0x25 0x15 0x00 + */ + if (rt2x00dev->curr_band == IEEE80211_BAND_2GHZ) { + rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_BG1, &eeprom); + tssi_bounds[0] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_BG1_MINUS4); + tssi_bounds[1] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_BG1_MINUS3); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_BG2, &eeprom); + tssi_bounds[2] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_BG2_MINUS2); + tssi_bounds[3] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_BG2_MINUS1); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_BG3, &eeprom); + tssi_bounds[4] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_BG3_REF); + tssi_bounds[5] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_BG3_PLUS1); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_BG4, &eeprom); + tssi_bounds[6] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_BG4_PLUS2); + tssi_bounds[7] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_BG4_PLUS3); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_BG5, &eeprom); + tssi_bounds[8] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_BG5_PLUS4); + + step = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_BG5_AGC_STEP); + } else { + rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_A1, &eeprom); + tssi_bounds[0] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_A1_MINUS4); + tssi_bounds[1] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_A1_MINUS3); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_A2, &eeprom); + tssi_bounds[2] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_A2_MINUS2); + tssi_bounds[3] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_A2_MINUS1); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_A3, &eeprom); + tssi_bounds[4] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_A3_REF); + tssi_bounds[5] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_A3_PLUS1); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_A4, &eeprom); + tssi_bounds[6] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_A4_PLUS2); + tssi_bounds[7] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_A4_PLUS3); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_TSSI_BOUND_A5, &eeprom); + tssi_bounds[8] = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_A5_PLUS4); + + step = rt2x00_get_field16(eeprom, + EEPROM_TSSI_BOUND_A5_AGC_STEP); + } + + /* + * Check if temperature compensation is supported. + */ + if (tssi_bounds[4] == 0xff || step == 0xff) + return 0; + + /* + * Read current TSSI (BBP 49). + */ + rt2800_bbp_read(rt2x00dev, 49, ¤t_tssi); + + /* + * Compare TSSI value (BBP49) with the compensation boundaries + * from the EEPROM and increase or decrease tx power. + */ + for (i = 0; i <= 3; i++) { + if (current_tssi > tssi_bounds[i]) + break; + } + + if (i == 4) { + for (i = 8; i >= 5; i--) { + if (current_tssi < tssi_bounds[i]) + break; + } + } + + return (i - 4) * step; +} + +static int rt2800_get_txpower_bw_comp(struct rt2x00_dev *rt2x00dev, + enum ieee80211_band band) +{ + u16 eeprom; + u8 comp_en; + u8 comp_type; + int comp_value = 0; + + rt2x00_eeprom_read(rt2x00dev, EEPROM_TXPOWER_DELTA, &eeprom); + + /* + * HT40 compensation not required. + */ + if (eeprom == 0xffff || + !test_bit(CONFIG_CHANNEL_HT40, &rt2x00dev->flags)) + return 0; + + if (band == IEEE80211_BAND_2GHZ) { + comp_en = rt2x00_get_field16(eeprom, + EEPROM_TXPOWER_DELTA_ENABLE_2G); + if (comp_en) { + comp_type = rt2x00_get_field16(eeprom, + EEPROM_TXPOWER_DELTA_TYPE_2G); + comp_value = rt2x00_get_field16(eeprom, + EEPROM_TXPOWER_DELTA_VALUE_2G); + if (!comp_type) + comp_value = -comp_value; + } + } else { + comp_en = rt2x00_get_field16(eeprom, + EEPROM_TXPOWER_DELTA_ENABLE_5G); + if (comp_en) { + comp_type = rt2x00_get_field16(eeprom, + EEPROM_TXPOWER_DELTA_TYPE_5G); + comp_value = rt2x00_get_field16(eeprom, + EEPROM_TXPOWER_DELTA_VALUE_5G); + if (!comp_type) + comp_value = -comp_value; + } + } + + return comp_value; +} + +static int rt2800_get_txpower_reg_delta(struct rt2x00_dev *rt2x00dev, + int power_level, int max_power) +{ + int delta; + + if (test_bit(CAPABILITY_POWER_LIMIT, &rt2x00dev->cap_flags)) + return 0; + + /* + * XXX: We don't know the maximum transmit power of our hardware since + * the EEPROM doesn't expose it. We only know that we are calibrated + * to 100% tx power. + * + * Hence, we assume the regulatory limit that cfg80211 calulated for + * the current channel is our maximum and if we are requested to lower + * the value we just reduce our tx power accordingly. + */ + delta = power_level - max_power; + return min(delta, 0); +} + +static u8 rt2800_compensate_txpower(struct rt2x00_dev *rt2x00dev, int is_rate_b, + enum ieee80211_band band, int power_level, + u8 txpower, int delta) +{ + u16 eeprom; + u8 criterion; + u8 eirp_txpower; + u8 eirp_txpower_criterion; + u8 reg_limit; + + if (test_bit(CAPABILITY_POWER_LIMIT, &rt2x00dev->cap_flags)) { + /* + * Check if eirp txpower exceed txpower_limit. + * We use OFDM 6M as criterion and its eirp txpower + * is stored at EEPROM_EIRP_MAX_TX_POWER. + * .11b data rate need add additional 4dbm + * when calculating eirp txpower. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_TXPOWER_BYRATE + 1, + &eeprom); + criterion = rt2x00_get_field16(eeprom, + EEPROM_TXPOWER_BYRATE_RATE0); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_EIRP_MAX_TX_POWER, + &eeprom); + + if (band == IEEE80211_BAND_2GHZ) + eirp_txpower_criterion = rt2x00_get_field16(eeprom, + EEPROM_EIRP_MAX_TX_POWER_2GHZ); + else + eirp_txpower_criterion = rt2x00_get_field16(eeprom, + EEPROM_EIRP_MAX_TX_POWER_5GHZ); + + eirp_txpower = eirp_txpower_criterion + (txpower - criterion) + + (is_rate_b ? 4 : 0) + delta; + + reg_limit = (eirp_txpower > power_level) ? + (eirp_txpower - power_level) : 0; + } else + reg_limit = 0; + + txpower = max(0, txpower + delta - reg_limit); + return min_t(u8, txpower, 0xc); +} + +/* + * We configure transmit power using MAC TX_PWR_CFG_{0,...,N} registers and + * BBP R1 register. TX_PWR_CFG_X allow to configure per rate TX power values, + * 4 bits for each rate (tune from 0 to 15 dBm). BBP_R1 controls transmit power + * for all rates, but allow to set only 4 discrete values: -12, -6, 0 and 6 dBm. + * Reference per rate transmit power values are located in the EEPROM at + * EEPROM_TXPOWER_BYRATE offset. We adjust them and BBP R1 settings according to + * current conditions (i.e. band, bandwidth, temperature, user settings). + */ +static void rt2800_config_txpower(struct rt2x00_dev *rt2x00dev, + struct ieee80211_channel *chan, + int power_level) +{ + u8 txpower, r1; + u16 eeprom; + u32 reg, offset; + int i, is_rate_b, delta, power_ctrl; + enum ieee80211_band band = chan->band; + + /* + * Calculate HT40 compensation. For 40MHz we need to add or subtract + * value read from EEPROM (different for 2GHz and for 5GHz). + */ + delta = rt2800_get_txpower_bw_comp(rt2x00dev, band); + + /* + * Calculate temperature compensation. Depends on measurement of current + * TSSI (Transmitter Signal Strength Indication) we know TX power (due + * to temperature or maybe other factors) is smaller or bigger than + * expected. We adjust it, based on TSSI reference and boundaries values + * provided in EEPROM. + */ + delta += rt2800_get_gain_calibration_delta(rt2x00dev); + + /* + * Decrease power according to user settings, on devices with unknown + * maximum tx power. For other devices we take user power_level into + * consideration on rt2800_compensate_txpower(). + */ + delta += rt2800_get_txpower_reg_delta(rt2x00dev, power_level, + chan->max_power); + + /* + * BBP_R1 controls TX power for all rates, it allow to set the following + * gains -12, -6, 0, +6 dBm by setting values 2, 1, 0, 3 respectively. + * + * TODO: we do not use +6 dBm option to do not increase power beyond + * regulatory limit, however this could be utilized for devices with + * CAPABILITY_POWER_LIMIT. + * + * TODO: add different temperature compensation code for RT3290 & RT5390 + * to allow to use BBP_R1 for those chips. + */ + if (!rt2x00_rt(rt2x00dev, RT3290) && + !rt2x00_rt(rt2x00dev, RT5390)) { + rt2800_bbp_read(rt2x00dev, 1, &r1); + if (delta <= -12) { + power_ctrl = 2; + delta += 12; + } else if (delta <= -6) { + power_ctrl = 1; + delta += 6; + } else { + power_ctrl = 0; + } + rt2x00_set_field8(&r1, BBP1_TX_POWER_CTRL, power_ctrl); + rt2800_bbp_write(rt2x00dev, 1, r1); + } + + offset = TX_PWR_CFG_0; + + for (i = 0; i < EEPROM_TXPOWER_BYRATE_SIZE; i += 2) { + /* just to be safe */ + if (offset > TX_PWR_CFG_4) + break; + + rt2800_register_read(rt2x00dev, offset, ®); + + /* read the next four txpower values */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_TXPOWER_BYRATE + i, + &eeprom); + + is_rate_b = i ? 0 : 1; + /* + * TX_PWR_CFG_0: 1MBS, TX_PWR_CFG_1: 24MBS, + * TX_PWR_CFG_2: MCS4, TX_PWR_CFG_3: MCS12, + * TX_PWR_CFG_4: unknown + */ + txpower = rt2x00_get_field16(eeprom, + EEPROM_TXPOWER_BYRATE_RATE0); + txpower = rt2800_compensate_txpower(rt2x00dev, is_rate_b, band, + power_level, txpower, delta); + rt2x00_set_field32(®, TX_PWR_CFG_RATE0, txpower); + + /* + * TX_PWR_CFG_0: 2MBS, TX_PWR_CFG_1: 36MBS, + * TX_PWR_CFG_2: MCS5, TX_PWR_CFG_3: MCS13, + * TX_PWR_CFG_4: unknown + */ + txpower = rt2x00_get_field16(eeprom, + EEPROM_TXPOWER_BYRATE_RATE1); + txpower = rt2800_compensate_txpower(rt2x00dev, is_rate_b, band, + power_level, txpower, delta); + rt2x00_set_field32(®, TX_PWR_CFG_RATE1, txpower); + + /* + * TX_PWR_CFG_0: 5.5MBS, TX_PWR_CFG_1: 48MBS, + * TX_PWR_CFG_2: MCS6, TX_PWR_CFG_3: MCS14, + * TX_PWR_CFG_4: unknown + */ + txpower = rt2x00_get_field16(eeprom, + EEPROM_TXPOWER_BYRATE_RATE2); + txpower = rt2800_compensate_txpower(rt2x00dev, is_rate_b, band, + power_level, txpower, delta); + rt2x00_set_field32(®, TX_PWR_CFG_RATE2, txpower); + + /* + * TX_PWR_CFG_0: 11MBS, TX_PWR_CFG_1: 54MBS, + * TX_PWR_CFG_2: MCS7, TX_PWR_CFG_3: MCS15, + * TX_PWR_CFG_4: unknown + */ + txpower = rt2x00_get_field16(eeprom, + EEPROM_TXPOWER_BYRATE_RATE3); + txpower = rt2800_compensate_txpower(rt2x00dev, is_rate_b, band, + power_level, txpower, delta); + rt2x00_set_field32(®, TX_PWR_CFG_RATE3, txpower); + + /* read the next four txpower values */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_TXPOWER_BYRATE + i + 1, + &eeprom); + + is_rate_b = 0; + /* + * TX_PWR_CFG_0: 6MBS, TX_PWR_CFG_1: MCS0, + * TX_PWR_CFG_2: MCS8, TX_PWR_CFG_3: unknown, + * TX_PWR_CFG_4: unknown + */ + txpower = rt2x00_get_field16(eeprom, + EEPROM_TXPOWER_BYRATE_RATE0); + txpower = rt2800_compensate_txpower(rt2x00dev, is_rate_b, band, + power_level, txpower, delta); + rt2x00_set_field32(®, TX_PWR_CFG_RATE4, txpower); + + /* + * TX_PWR_CFG_0: 9MBS, TX_PWR_CFG_1: MCS1, + * TX_PWR_CFG_2: MCS9, TX_PWR_CFG_3: unknown, + * TX_PWR_CFG_4: unknown + */ + txpower = rt2x00_get_field16(eeprom, + EEPROM_TXPOWER_BYRATE_RATE1); + txpower = rt2800_compensate_txpower(rt2x00dev, is_rate_b, band, + power_level, txpower, delta); + rt2x00_set_field32(®, TX_PWR_CFG_RATE5, txpower); + + /* + * TX_PWR_CFG_0: 12MBS, TX_PWR_CFG_1: MCS2, + * TX_PWR_CFG_2: MCS10, TX_PWR_CFG_3: unknown, + * TX_PWR_CFG_4: unknown + */ + txpower = rt2x00_get_field16(eeprom, + EEPROM_TXPOWER_BYRATE_RATE2); + txpower = rt2800_compensate_txpower(rt2x00dev, is_rate_b, band, + power_level, txpower, delta); + rt2x00_set_field32(®, TX_PWR_CFG_RATE6, txpower); + + /* + * TX_PWR_CFG_0: 18MBS, TX_PWR_CFG_1: MCS3, + * TX_PWR_CFG_2: MCS11, TX_PWR_CFG_3: unknown, + * TX_PWR_CFG_4: unknown + */ + txpower = rt2x00_get_field16(eeprom, + EEPROM_TXPOWER_BYRATE_RATE3); + txpower = rt2800_compensate_txpower(rt2x00dev, is_rate_b, band, + power_level, txpower, delta); + rt2x00_set_field32(®, TX_PWR_CFG_RATE7, txpower); + + rt2800_register_write(rt2x00dev, offset, reg); + + /* next TX_PWR_CFG register */ + offset += 4; + } +} + +void rt2800_gain_calibration(struct rt2x00_dev *rt2x00dev) +{ + rt2800_config_txpower(rt2x00dev, rt2x00dev->hw->conf.chandef.chan, + rt2x00dev->tx_power); +} +EXPORT_SYMBOL_GPL(rt2800_gain_calibration); + +void rt2800_vco_calibration(struct rt2x00_dev *rt2x00dev) +{ + u32 tx_pin; + u8 rfcsr; + + /* + * A voltage-controlled oscillator(VCO) is an electronic oscillator + * designed to be controlled in oscillation frequency by a voltage + * input. Maybe the temperature will affect the frequency of + * oscillation to be shifted. The VCO calibration will be called + * periodically to adjust the frequency to be precision. + */ + + rt2800_register_read(rt2x00dev, TX_PIN_CFG, &tx_pin); + tx_pin &= TX_PIN_CFG_PA_PE_DISABLE; + rt2800_register_write(rt2x00dev, TX_PIN_CFG, tx_pin); + + switch (rt2x00dev->chip.rf) { + case RF2020: + case RF3020: + case RF3021: + case RF3022: + case RF3320: + case RF3052: + rt2800_rfcsr_read(rt2x00dev, 7, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR7_RF_TUNING, 1); + rt2800_rfcsr_write(rt2x00dev, 7, rfcsr); + break; + case RF3290: + case RF5360: + case RF5370: + case RF5372: + case RF5390: + case RF5392: + rt2800_rfcsr_read(rt2x00dev, 3, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR3_VCOCAL_EN, 1); + rt2800_rfcsr_write(rt2x00dev, 3, rfcsr); + break; + default: + return; + } + + mdelay(1); + + rt2800_register_read(rt2x00dev, TX_PIN_CFG, &tx_pin); + if (rt2x00dev->rf_channel <= 14) { + switch (rt2x00dev->default_ant.tx_chain_num) { + case 3: + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_G2_EN, 1); + /* fall through */ + case 2: + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_G1_EN, 1); + /* fall through */ + case 1: + default: + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_G0_EN, 1); + break; + } + } else { + switch (rt2x00dev->default_ant.tx_chain_num) { + case 3: + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_A2_EN, 1); + /* fall through */ + case 2: + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_A1_EN, 1); + /* fall through */ + case 1: + default: + rt2x00_set_field32(&tx_pin, TX_PIN_CFG_PA_PE_A0_EN, 1); + break; + } + } + rt2800_register_write(rt2x00dev, TX_PIN_CFG, tx_pin); + +} +EXPORT_SYMBOL_GPL(rt2800_vco_calibration); + +static void rt2800_config_retry_limit(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf) +{ + u32 reg; + + rt2800_register_read(rt2x00dev, TX_RTY_CFG, ®); + rt2x00_set_field32(®, TX_RTY_CFG_SHORT_RTY_LIMIT, + libconf->conf->short_frame_max_tx_count); + rt2x00_set_field32(®, TX_RTY_CFG_LONG_RTY_LIMIT, + libconf->conf->long_frame_max_tx_count); + rt2800_register_write(rt2x00dev, TX_RTY_CFG, reg); +} + +static void rt2800_config_ps(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf) +{ + enum dev_state state = + (libconf->conf->flags & IEEE80211_CONF_PS) ? + STATE_SLEEP : STATE_AWAKE; + u32 reg; + + if (state == STATE_SLEEP) { + rt2800_register_write(rt2x00dev, AUTOWAKEUP_CFG, 0); + + rt2800_register_read(rt2x00dev, AUTOWAKEUP_CFG, ®); + rt2x00_set_field32(®, AUTOWAKEUP_CFG_AUTO_LEAD_TIME, 5); + rt2x00_set_field32(®, AUTOWAKEUP_CFG_TBCN_BEFORE_WAKE, + libconf->conf->listen_interval - 1); + rt2x00_set_field32(®, AUTOWAKEUP_CFG_AUTOWAKE, 1); + rt2800_register_write(rt2x00dev, AUTOWAKEUP_CFG, reg); + + rt2x00dev->ops->lib->set_device_state(rt2x00dev, state); + } else { + rt2800_register_read(rt2x00dev, AUTOWAKEUP_CFG, ®); + rt2x00_set_field32(®, AUTOWAKEUP_CFG_AUTO_LEAD_TIME, 0); + rt2x00_set_field32(®, AUTOWAKEUP_CFG_TBCN_BEFORE_WAKE, 0); + rt2x00_set_field32(®, AUTOWAKEUP_CFG_AUTOWAKE, 0); + rt2800_register_write(rt2x00dev, AUTOWAKEUP_CFG, reg); + + rt2x00dev->ops->lib->set_device_state(rt2x00dev, state); + } +} + +void rt2800_config(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf, + const unsigned int flags) +{ + /* Always recalculate LNA gain before changing configuration */ + rt2800_config_lna_gain(rt2x00dev, libconf); + + if (flags & IEEE80211_CONF_CHANGE_CHANNEL) { + rt2800_config_channel(rt2x00dev, libconf->conf, + &libconf->rf, &libconf->channel); + rt2800_config_txpower(rt2x00dev, libconf->conf->chandef.chan, + libconf->conf->power_level); + } + if (flags & IEEE80211_CONF_CHANGE_POWER) + rt2800_config_txpower(rt2x00dev, libconf->conf->chandef.chan, + libconf->conf->power_level); + if (flags & IEEE80211_CONF_CHANGE_RETRY_LIMITS) + rt2800_config_retry_limit(rt2x00dev, libconf); + if (flags & IEEE80211_CONF_CHANGE_PS) + rt2800_config_ps(rt2x00dev, libconf); +} +EXPORT_SYMBOL_GPL(rt2800_config); + +/* + * Link tuning + */ +void rt2800_link_stats(struct rt2x00_dev *rt2x00dev, struct link_qual *qual) +{ + u32 reg; + + /* + * Update FCS error count from register. + */ + rt2800_register_read(rt2x00dev, RX_STA_CNT0, ®); + qual->rx_failed = rt2x00_get_field32(reg, RX_STA_CNT0_CRC_ERR); +} +EXPORT_SYMBOL_GPL(rt2800_link_stats); + +static u8 rt2800_get_default_vgc(struct rt2x00_dev *rt2x00dev) +{ + u8 vgc; + + if (rt2x00dev->curr_band == IEEE80211_BAND_2GHZ) { + if (rt2x00_rt(rt2x00dev, RT3070) || + rt2x00_rt(rt2x00dev, RT3071) || + rt2x00_rt(rt2x00dev, RT3090) || + rt2x00_rt(rt2x00dev, RT3290) || + rt2x00_rt(rt2x00dev, RT3390) || + rt2x00_rt(rt2x00dev, RT3572) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392) || + rt2x00_rt(rt2x00dev, RT5592)) + vgc = 0x1c + (2 * rt2x00dev->lna_gain); + else + vgc = 0x2e + rt2x00dev->lna_gain; + } else { /* 5GHZ band */ + if (rt2x00_rt(rt2x00dev, RT3572)) + vgc = 0x22 + (rt2x00dev->lna_gain * 5) / 3; + else if (rt2x00_rt(rt2x00dev, RT5592)) + vgc = 0x24 + (2 * rt2x00dev->lna_gain); + else { + if (!test_bit(CONFIG_CHANNEL_HT40, &rt2x00dev->flags)) + vgc = 0x32 + (rt2x00dev->lna_gain * 5) / 3; + else + vgc = 0x3a + (rt2x00dev->lna_gain * 5) / 3; + } + } + + return vgc; +} + +static inline void rt2800_set_vgc(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual, u8 vgc_level) +{ + if (qual->vgc_level != vgc_level) { + if (rt2x00_rt(rt2x00dev, RT5592)) { + rt2800_bbp_write(rt2x00dev, 83, qual->rssi > -65 ? 0x4a : 0x7a); + rt2800_bbp_write_with_rx_chain(rt2x00dev, 66, vgc_level); + } else + rt2800_bbp_write(rt2x00dev, 66, vgc_level); + qual->vgc_level = vgc_level; + qual->vgc_level_reg = vgc_level; + } +} + +void rt2800_reset_tuner(struct rt2x00_dev *rt2x00dev, struct link_qual *qual) +{ + rt2800_set_vgc(rt2x00dev, qual, rt2800_get_default_vgc(rt2x00dev)); +} +EXPORT_SYMBOL_GPL(rt2800_reset_tuner); + +void rt2800_link_tuner(struct rt2x00_dev *rt2x00dev, struct link_qual *qual, + const u32 count) +{ + u8 vgc; + + if (rt2x00_rt_rev(rt2x00dev, RT2860, REV_RT2860C)) + return; + /* + * When RSSI is better then -80 increase VGC level with 0x10, except + * for rt5592 chip. + */ + + vgc = rt2800_get_default_vgc(rt2x00dev); + + if (rt2x00_rt(rt2x00dev, RT5592) && qual->rssi > -65) + vgc += 0x20; + else if (qual->rssi > -80) + vgc += 0x10; + + rt2800_set_vgc(rt2x00dev, qual, vgc); +} +EXPORT_SYMBOL_GPL(rt2800_link_tuner); + +/* + * Initialization functions. + */ +static int rt2800_init_registers(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + u16 eeprom; + unsigned int i; + int ret; + + rt2800_disable_wpdma(rt2x00dev); + + ret = rt2800_drv_init_registers(rt2x00dev); + if (ret) + return ret; + + rt2800_register_read(rt2x00dev, BCN_OFFSET0, ®); + rt2x00_set_field32(®, BCN_OFFSET0_BCN0, 0xe0); /* 0x3800 */ + rt2x00_set_field32(®, BCN_OFFSET0_BCN1, 0xe8); /* 0x3a00 */ + rt2x00_set_field32(®, BCN_OFFSET0_BCN2, 0xf0); /* 0x3c00 */ + rt2x00_set_field32(®, BCN_OFFSET0_BCN3, 0xf8); /* 0x3e00 */ + rt2800_register_write(rt2x00dev, BCN_OFFSET0, reg); + + rt2800_register_read(rt2x00dev, BCN_OFFSET1, ®); + rt2x00_set_field32(®, BCN_OFFSET1_BCN4, 0xc8); /* 0x3200 */ + rt2x00_set_field32(®, BCN_OFFSET1_BCN5, 0xd0); /* 0x3400 */ + rt2x00_set_field32(®, BCN_OFFSET1_BCN6, 0x77); /* 0x1dc0 */ + rt2x00_set_field32(®, BCN_OFFSET1_BCN7, 0x6f); /* 0x1bc0 */ + rt2800_register_write(rt2x00dev, BCN_OFFSET1, reg); + + rt2800_register_write(rt2x00dev, LEGACY_BASIC_RATE, 0x0000013f); + rt2800_register_write(rt2x00dev, HT_BASIC_RATE, 0x00008003); + + rt2800_register_write(rt2x00dev, MAC_SYS_CTRL, 0x00000000); + + rt2800_register_read(rt2x00dev, BCN_TIME_CFG, ®); + rt2x00_set_field32(®, BCN_TIME_CFG_BEACON_INTERVAL, 1600); + rt2x00_set_field32(®, BCN_TIME_CFG_TSF_TICKING, 0); + rt2x00_set_field32(®, BCN_TIME_CFG_TSF_SYNC, 0); + rt2x00_set_field32(®, BCN_TIME_CFG_TBTT_ENABLE, 0); + rt2x00_set_field32(®, BCN_TIME_CFG_BEACON_GEN, 0); + rt2x00_set_field32(®, BCN_TIME_CFG_TX_TIME_COMPENSATE, 0); + rt2800_register_write(rt2x00dev, BCN_TIME_CFG, reg); + + rt2800_config_filter(rt2x00dev, FIF_ALLMULTI); + + rt2800_register_read(rt2x00dev, BKOFF_SLOT_CFG, ®); + rt2x00_set_field32(®, BKOFF_SLOT_CFG_SLOT_TIME, 9); + rt2x00_set_field32(®, BKOFF_SLOT_CFG_CC_DELAY_TIME, 2); + rt2800_register_write(rt2x00dev, BKOFF_SLOT_CFG, reg); + + if (rt2x00_rt(rt2x00dev, RT3290)) { + rt2800_register_read(rt2x00dev, WLAN_FUN_CTRL, ®); + if (rt2x00_get_field32(reg, WLAN_EN) == 1) { + rt2x00_set_field32(®, PCIE_APP0_CLK_REQ, 1); + rt2800_register_write(rt2x00dev, WLAN_FUN_CTRL, reg); + } + + rt2800_register_read(rt2x00dev, CMB_CTRL, ®); + if (!(rt2x00_get_field32(reg, LDO0_EN) == 1)) { + rt2x00_set_field32(®, LDO0_EN, 1); + rt2x00_set_field32(®, LDO_BGSEL, 3); + rt2800_register_write(rt2x00dev, CMB_CTRL, reg); + } + + rt2800_register_read(rt2x00dev, OSC_CTRL, ®); + rt2x00_set_field32(®, OSC_ROSC_EN, 1); + rt2x00_set_field32(®, OSC_CAL_REQ, 1); + rt2x00_set_field32(®, OSC_REF_CYCLE, 0x27); + rt2800_register_write(rt2x00dev, OSC_CTRL, reg); + + rt2800_register_read(rt2x00dev, COEX_CFG0, ®); + rt2x00_set_field32(®, COEX_CFG_ANT, 0x5e); + rt2800_register_write(rt2x00dev, COEX_CFG0, reg); + + rt2800_register_read(rt2x00dev, COEX_CFG2, ®); + rt2x00_set_field32(®, BT_COEX_CFG1, 0x00); + rt2x00_set_field32(®, BT_COEX_CFG0, 0x17); + rt2x00_set_field32(®, WL_COEX_CFG1, 0x93); + rt2x00_set_field32(®, WL_COEX_CFG0, 0x7f); + rt2800_register_write(rt2x00dev, COEX_CFG2, reg); + + rt2800_register_read(rt2x00dev, PLL_CTRL, ®); + rt2x00_set_field32(®, PLL_CONTROL, 1); + rt2800_register_write(rt2x00dev, PLL_CTRL, reg); + } + + if (rt2x00_rt(rt2x00dev, RT3071) || + rt2x00_rt(rt2x00dev, RT3090) || + rt2x00_rt(rt2x00dev, RT3290) || + rt2x00_rt(rt2x00dev, RT3390)) { + + if (rt2x00_rt(rt2x00dev, RT3290)) + rt2800_register_write(rt2x00dev, TX_SW_CFG0, + 0x00000404); + else + rt2800_register_write(rt2x00dev, TX_SW_CFG0, + 0x00000400); + + rt2800_register_write(rt2x00dev, TX_SW_CFG1, 0x00000000); + if (rt2x00_rt_rev_lt(rt2x00dev, RT3071, REV_RT3071E) || + rt2x00_rt_rev_lt(rt2x00dev, RT3090, REV_RT3090E) || + rt2x00_rt_rev_lt(rt2x00dev, RT3390, REV_RT3390E)) { + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC_CONF1, &eeprom); + if (rt2x00_get_field16(eeprom, EEPROM_NIC_CONF1_DAC_TEST)) + rt2800_register_write(rt2x00dev, TX_SW_CFG2, + 0x0000002c); + else + rt2800_register_write(rt2x00dev, TX_SW_CFG2, + 0x0000000f); + } else { + rt2800_register_write(rt2x00dev, TX_SW_CFG2, 0x00000000); + } + } else if (rt2x00_rt(rt2x00dev, RT3070)) { + rt2800_register_write(rt2x00dev, TX_SW_CFG0, 0x00000400); + + if (rt2x00_rt_rev_lt(rt2x00dev, RT3070, REV_RT3070F)) { + rt2800_register_write(rt2x00dev, TX_SW_CFG1, 0x00000000); + rt2800_register_write(rt2x00dev, TX_SW_CFG2, 0x0000002c); + } else { + rt2800_register_write(rt2x00dev, TX_SW_CFG1, 0x00080606); + rt2800_register_write(rt2x00dev, TX_SW_CFG2, 0x00000000); + } + } else if (rt2800_is_305x_soc(rt2x00dev)) { + rt2800_register_write(rt2x00dev, TX_SW_CFG0, 0x00000400); + rt2800_register_write(rt2x00dev, TX_SW_CFG1, 0x00000000); + rt2800_register_write(rt2x00dev, TX_SW_CFG2, 0x00000030); + } else if (rt2x00_rt(rt2x00dev, RT3352)) { + rt2800_register_write(rt2x00dev, TX_SW_CFG0, 0x00000402); + rt2800_register_write(rt2x00dev, TX_SW_CFG1, 0x00080606); + rt2800_register_write(rt2x00dev, TX_SW_CFG2, 0x00000000); + } else if (rt2x00_rt(rt2x00dev, RT3572)) { + rt2800_register_write(rt2x00dev, TX_SW_CFG0, 0x00000400); + rt2800_register_write(rt2x00dev, TX_SW_CFG1, 0x00080606); + } else if (rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392) || + rt2x00_rt(rt2x00dev, RT5592)) { + rt2800_register_write(rt2x00dev, TX_SW_CFG0, 0x00000404); + rt2800_register_write(rt2x00dev, TX_SW_CFG1, 0x00080606); + rt2800_register_write(rt2x00dev, TX_SW_CFG2, 0x00000000); + } else { + rt2800_register_write(rt2x00dev, TX_SW_CFG0, 0x00000000); + rt2800_register_write(rt2x00dev, TX_SW_CFG1, 0x00080606); + } + + rt2800_register_read(rt2x00dev, TX_LINK_CFG, ®); + rt2x00_set_field32(®, TX_LINK_CFG_REMOTE_MFB_LIFETIME, 32); + rt2x00_set_field32(®, TX_LINK_CFG_MFB_ENABLE, 0); + rt2x00_set_field32(®, TX_LINK_CFG_REMOTE_UMFS_ENABLE, 0); + rt2x00_set_field32(®, TX_LINK_CFG_TX_MRQ_EN, 0); + rt2x00_set_field32(®, TX_LINK_CFG_TX_RDG_EN, 0); + rt2x00_set_field32(®, TX_LINK_CFG_TX_CF_ACK_EN, 1); + rt2x00_set_field32(®, TX_LINK_CFG_REMOTE_MFB, 0); + rt2x00_set_field32(®, TX_LINK_CFG_REMOTE_MFS, 0); + rt2800_register_write(rt2x00dev, TX_LINK_CFG, reg); + + rt2800_register_read(rt2x00dev, TX_TIMEOUT_CFG, ®); + rt2x00_set_field32(®, TX_TIMEOUT_CFG_MPDU_LIFETIME, 9); + rt2x00_set_field32(®, TX_TIMEOUT_CFG_RX_ACK_TIMEOUT, 32); + rt2x00_set_field32(®, TX_TIMEOUT_CFG_TX_OP_TIMEOUT, 10); + rt2800_register_write(rt2x00dev, TX_TIMEOUT_CFG, reg); + + rt2800_register_read(rt2x00dev, MAX_LEN_CFG, ®); + rt2x00_set_field32(®, MAX_LEN_CFG_MAX_MPDU, AGGREGATION_SIZE); + if (rt2x00_rt_rev_gte(rt2x00dev, RT2872, REV_RT2872E) || + rt2x00_rt(rt2x00dev, RT2883) || + rt2x00_rt_rev_lt(rt2x00dev, RT3070, REV_RT3070E)) + rt2x00_set_field32(®, MAX_LEN_CFG_MAX_PSDU, 2); + else + rt2x00_set_field32(®, MAX_LEN_CFG_MAX_PSDU, 1); + rt2x00_set_field32(®, MAX_LEN_CFG_MIN_PSDU, 0); + rt2x00_set_field32(®, MAX_LEN_CFG_MIN_MPDU, 0); + rt2800_register_write(rt2x00dev, MAX_LEN_CFG, reg); + + rt2800_register_read(rt2x00dev, LED_CFG, ®); + rt2x00_set_field32(®, LED_CFG_ON_PERIOD, 70); + rt2x00_set_field32(®, LED_CFG_OFF_PERIOD, 30); + rt2x00_set_field32(®, LED_CFG_SLOW_BLINK_PERIOD, 3); + rt2x00_set_field32(®, LED_CFG_R_LED_MODE, 3); + rt2x00_set_field32(®, LED_CFG_G_LED_MODE, 3); + rt2x00_set_field32(®, LED_CFG_Y_LED_MODE, 3); + rt2x00_set_field32(®, LED_CFG_LED_POLAR, 1); + rt2800_register_write(rt2x00dev, LED_CFG, reg); + + rt2800_register_write(rt2x00dev, PBF_MAX_PCNT, 0x1f3fbf9f); + + rt2800_register_read(rt2x00dev, TX_RTY_CFG, ®); + rt2x00_set_field32(®, TX_RTY_CFG_SHORT_RTY_LIMIT, 15); + rt2x00_set_field32(®, TX_RTY_CFG_LONG_RTY_LIMIT, 31); + rt2x00_set_field32(®, TX_RTY_CFG_LONG_RTY_THRE, 2000); + rt2x00_set_field32(®, TX_RTY_CFG_NON_AGG_RTY_MODE, 0); + rt2x00_set_field32(®, TX_RTY_CFG_AGG_RTY_MODE, 0); + rt2x00_set_field32(®, TX_RTY_CFG_TX_AUTO_FB_ENABLE, 1); + rt2800_register_write(rt2x00dev, TX_RTY_CFG, reg); + + rt2800_register_read(rt2x00dev, AUTO_RSP_CFG, ®); + rt2x00_set_field32(®, AUTO_RSP_CFG_AUTORESPONDER, 1); + rt2x00_set_field32(®, AUTO_RSP_CFG_BAC_ACK_POLICY, 1); + rt2x00_set_field32(®, AUTO_RSP_CFG_CTS_40_MMODE, 0); + rt2x00_set_field32(®, AUTO_RSP_CFG_CTS_40_MREF, 0); + rt2x00_set_field32(®, AUTO_RSP_CFG_AR_PREAMBLE, 1); + rt2x00_set_field32(®, AUTO_RSP_CFG_DUAL_CTS_EN, 0); + rt2x00_set_field32(®, AUTO_RSP_CFG_ACK_CTS_PSM_BIT, 0); + rt2800_register_write(rt2x00dev, AUTO_RSP_CFG, reg); + + rt2800_register_read(rt2x00dev, CCK_PROT_CFG, ®); + rt2x00_set_field32(®, CCK_PROT_CFG_PROTECT_RATE, 3); + rt2x00_set_field32(®, CCK_PROT_CFG_PROTECT_CTRL, 0); + rt2x00_set_field32(®, CCK_PROT_CFG_PROTECT_NAV_SHORT, 1); + rt2x00_set_field32(®, CCK_PROT_CFG_TX_OP_ALLOW_CCK, 1); + rt2x00_set_field32(®, CCK_PROT_CFG_TX_OP_ALLOW_OFDM, 1); + rt2x00_set_field32(®, CCK_PROT_CFG_TX_OP_ALLOW_MM20, 1); + rt2x00_set_field32(®, CCK_PROT_CFG_TX_OP_ALLOW_MM40, 0); + rt2x00_set_field32(®, CCK_PROT_CFG_TX_OP_ALLOW_GF20, 1); + rt2x00_set_field32(®, CCK_PROT_CFG_TX_OP_ALLOW_GF40, 0); + rt2x00_set_field32(®, CCK_PROT_CFG_RTS_TH_EN, 1); + rt2800_register_write(rt2x00dev, CCK_PROT_CFG, reg); + + rt2800_register_read(rt2x00dev, OFDM_PROT_CFG, ®); + rt2x00_set_field32(®, OFDM_PROT_CFG_PROTECT_RATE, 3); + rt2x00_set_field32(®, OFDM_PROT_CFG_PROTECT_CTRL, 0); + rt2x00_set_field32(®, OFDM_PROT_CFG_PROTECT_NAV_SHORT, 1); + rt2x00_set_field32(®, OFDM_PROT_CFG_TX_OP_ALLOW_CCK, 1); + rt2x00_set_field32(®, OFDM_PROT_CFG_TX_OP_ALLOW_OFDM, 1); + rt2x00_set_field32(®, OFDM_PROT_CFG_TX_OP_ALLOW_MM20, 1); + rt2x00_set_field32(®, OFDM_PROT_CFG_TX_OP_ALLOW_MM40, 0); + rt2x00_set_field32(®, OFDM_PROT_CFG_TX_OP_ALLOW_GF20, 1); + rt2x00_set_field32(®, OFDM_PROT_CFG_TX_OP_ALLOW_GF40, 0); + rt2x00_set_field32(®, OFDM_PROT_CFG_RTS_TH_EN, 1); + rt2800_register_write(rt2x00dev, OFDM_PROT_CFG, reg); + + rt2800_register_read(rt2x00dev, MM20_PROT_CFG, ®); + rt2x00_set_field32(®, MM20_PROT_CFG_PROTECT_RATE, 0x4004); + rt2x00_set_field32(®, MM20_PROT_CFG_PROTECT_CTRL, 0); + rt2x00_set_field32(®, MM20_PROT_CFG_PROTECT_NAV_SHORT, 1); + rt2x00_set_field32(®, MM20_PROT_CFG_TX_OP_ALLOW_CCK, 1); + rt2x00_set_field32(®, MM20_PROT_CFG_TX_OP_ALLOW_OFDM, 1); + rt2x00_set_field32(®, MM20_PROT_CFG_TX_OP_ALLOW_MM20, 1); + rt2x00_set_field32(®, MM20_PROT_CFG_TX_OP_ALLOW_MM40, 0); + rt2x00_set_field32(®, MM20_PROT_CFG_TX_OP_ALLOW_GF20, 1); + rt2x00_set_field32(®, MM20_PROT_CFG_TX_OP_ALLOW_GF40, 0); + rt2x00_set_field32(®, MM20_PROT_CFG_RTS_TH_EN, 0); + rt2800_register_write(rt2x00dev, MM20_PROT_CFG, reg); + + rt2800_register_read(rt2x00dev, MM40_PROT_CFG, ®); + rt2x00_set_field32(®, MM40_PROT_CFG_PROTECT_RATE, 0x4084); + rt2x00_set_field32(®, MM40_PROT_CFG_PROTECT_CTRL, 0); + rt2x00_set_field32(®, MM40_PROT_CFG_PROTECT_NAV_SHORT, 1); + rt2x00_set_field32(®, MM40_PROT_CFG_TX_OP_ALLOW_CCK, 1); + rt2x00_set_field32(®, MM40_PROT_CFG_TX_OP_ALLOW_OFDM, 1); + rt2x00_set_field32(®, MM40_PROT_CFG_TX_OP_ALLOW_MM20, 1); + rt2x00_set_field32(®, MM40_PROT_CFG_TX_OP_ALLOW_MM40, 1); + rt2x00_set_field32(®, MM40_PROT_CFG_TX_OP_ALLOW_GF20, 1); + rt2x00_set_field32(®, MM40_PROT_CFG_TX_OP_ALLOW_GF40, 1); + rt2x00_set_field32(®, MM40_PROT_CFG_RTS_TH_EN, 0); + rt2800_register_write(rt2x00dev, MM40_PROT_CFG, reg); + + rt2800_register_read(rt2x00dev, GF20_PROT_CFG, ®); + rt2x00_set_field32(®, GF20_PROT_CFG_PROTECT_RATE, 0x4004); + rt2x00_set_field32(®, GF20_PROT_CFG_PROTECT_CTRL, 0); + rt2x00_set_field32(®, GF20_PROT_CFG_PROTECT_NAV_SHORT, 1); + rt2x00_set_field32(®, GF20_PROT_CFG_TX_OP_ALLOW_CCK, 1); + rt2x00_set_field32(®, GF20_PROT_CFG_TX_OP_ALLOW_OFDM, 1); + rt2x00_set_field32(®, GF20_PROT_CFG_TX_OP_ALLOW_MM20, 1); + rt2x00_set_field32(®, GF20_PROT_CFG_TX_OP_ALLOW_MM40, 0); + rt2x00_set_field32(®, GF20_PROT_CFG_TX_OP_ALLOW_GF20, 1); + rt2x00_set_field32(®, GF20_PROT_CFG_TX_OP_ALLOW_GF40, 0); + rt2x00_set_field32(®, GF20_PROT_CFG_RTS_TH_EN, 0); + rt2800_register_write(rt2x00dev, GF20_PROT_CFG, reg); + + rt2800_register_read(rt2x00dev, GF40_PROT_CFG, ®); + rt2x00_set_field32(®, GF40_PROT_CFG_PROTECT_RATE, 0x4084); + rt2x00_set_field32(®, GF40_PROT_CFG_PROTECT_CTRL, 0); + rt2x00_set_field32(®, GF40_PROT_CFG_PROTECT_NAV_SHORT, 1); + rt2x00_set_field32(®, GF40_PROT_CFG_TX_OP_ALLOW_CCK, 1); + rt2x00_set_field32(®, GF40_PROT_CFG_TX_OP_ALLOW_OFDM, 1); + rt2x00_set_field32(®, GF40_PROT_CFG_TX_OP_ALLOW_MM20, 1); + rt2x00_set_field32(®, GF40_PROT_CFG_TX_OP_ALLOW_MM40, 1); + rt2x00_set_field32(®, GF40_PROT_CFG_TX_OP_ALLOW_GF20, 1); + rt2x00_set_field32(®, GF40_PROT_CFG_TX_OP_ALLOW_GF40, 1); + rt2x00_set_field32(®, GF40_PROT_CFG_RTS_TH_EN, 0); + rt2800_register_write(rt2x00dev, GF40_PROT_CFG, reg); + + if (rt2x00_is_usb(rt2x00dev)) { + rt2800_register_write(rt2x00dev, PBF_CFG, 0xf40006); + + rt2800_register_read(rt2x00dev, WPDMA_GLO_CFG, ®); + rt2x00_set_field32(®, WPDMA_GLO_CFG_ENABLE_TX_DMA, 0); + rt2x00_set_field32(®, WPDMA_GLO_CFG_TX_DMA_BUSY, 0); + rt2x00_set_field32(®, WPDMA_GLO_CFG_ENABLE_RX_DMA, 0); + rt2x00_set_field32(®, WPDMA_GLO_CFG_RX_DMA_BUSY, 0); + rt2x00_set_field32(®, WPDMA_GLO_CFG_WP_DMA_BURST_SIZE, 3); + rt2x00_set_field32(®, WPDMA_GLO_CFG_TX_WRITEBACK_DONE, 0); + rt2x00_set_field32(®, WPDMA_GLO_CFG_BIG_ENDIAN, 0); + rt2x00_set_field32(®, WPDMA_GLO_CFG_RX_HDR_SCATTER, 0); + rt2x00_set_field32(®, WPDMA_GLO_CFG_HDR_SEG_LEN, 0); + rt2800_register_write(rt2x00dev, WPDMA_GLO_CFG, reg); + } + + /* + * The legacy driver also sets TXOP_CTRL_CFG_RESERVED_TRUN_EN to 1 + * although it is reserved. + */ + rt2800_register_read(rt2x00dev, TXOP_CTRL_CFG, ®); + rt2x00_set_field32(®, TXOP_CTRL_CFG_TIMEOUT_TRUN_EN, 1); + rt2x00_set_field32(®, TXOP_CTRL_CFG_AC_TRUN_EN, 1); + rt2x00_set_field32(®, TXOP_CTRL_CFG_TXRATEGRP_TRUN_EN, 1); + rt2x00_set_field32(®, TXOP_CTRL_CFG_USER_MODE_TRUN_EN, 1); + rt2x00_set_field32(®, TXOP_CTRL_CFG_MIMO_PS_TRUN_EN, 1); + rt2x00_set_field32(®, TXOP_CTRL_CFG_RESERVED_TRUN_EN, 1); + rt2x00_set_field32(®, TXOP_CTRL_CFG_LSIG_TXOP_EN, 0); + rt2x00_set_field32(®, TXOP_CTRL_CFG_EXT_CCA_EN, 0); + rt2x00_set_field32(®, TXOP_CTRL_CFG_EXT_CCA_DLY, 88); + rt2x00_set_field32(®, TXOP_CTRL_CFG_EXT_CWMIN, 0); + rt2800_register_write(rt2x00dev, TXOP_CTRL_CFG, reg); + + reg = rt2x00_rt(rt2x00dev, RT5592) ? 0x00000082 : 0x00000002; + rt2800_register_write(rt2x00dev, TXOP_HLDR_ET, reg); + + rt2800_register_read(rt2x00dev, TX_RTS_CFG, ®); + rt2x00_set_field32(®, TX_RTS_CFG_AUTO_RTS_RETRY_LIMIT, 32); + rt2x00_set_field32(®, TX_RTS_CFG_RTS_THRES, + IEEE80211_MAX_RTS_THRESHOLD); + rt2x00_set_field32(®, TX_RTS_CFG_RTS_FBK_EN, 0); + rt2800_register_write(rt2x00dev, TX_RTS_CFG, reg); + + rt2800_register_write(rt2x00dev, EXP_ACK_TIME, 0x002400ca); + + /* + * Usually the CCK SIFS time should be set to 10 and the OFDM SIFS + * time should be set to 16. However, the original Ralink driver uses + * 16 for both and indeed using a value of 10 for CCK SIFS results in + * connection problems with 11g + CTS protection. Hence, use the same + * defaults as the Ralink driver: 16 for both, CCK and OFDM SIFS. + */ + rt2800_register_read(rt2x00dev, XIFS_TIME_CFG, ®); + rt2x00_set_field32(®, XIFS_TIME_CFG_CCKM_SIFS_TIME, 16); + rt2x00_set_field32(®, XIFS_TIME_CFG_OFDM_SIFS_TIME, 16); + rt2x00_set_field32(®, XIFS_TIME_CFG_OFDM_XIFS_TIME, 4); + rt2x00_set_field32(®, XIFS_TIME_CFG_EIFS, 314); + rt2x00_set_field32(®, XIFS_TIME_CFG_BB_RXEND_ENABLE, 1); + rt2800_register_write(rt2x00dev, XIFS_TIME_CFG, reg); + + rt2800_register_write(rt2x00dev, PWR_PIN_CFG, 0x00000003); + + /* + * ASIC will keep garbage value after boot, clear encryption keys. + */ + for (i = 0; i < 4; i++) + rt2800_register_write(rt2x00dev, + SHARED_KEY_MODE_ENTRY(i), 0); + + for (i = 0; i < 256; i++) { + rt2800_config_wcid(rt2x00dev, NULL, i); + rt2800_delete_wcid_attr(rt2x00dev, i); + rt2800_register_write(rt2x00dev, MAC_IVEIV_ENTRY(i), 0); + } + + /* + * Clear all beacons + */ + rt2800_clear_beacon_register(rt2x00dev, HW_BEACON_BASE0); + rt2800_clear_beacon_register(rt2x00dev, HW_BEACON_BASE1); + rt2800_clear_beacon_register(rt2x00dev, HW_BEACON_BASE2); + rt2800_clear_beacon_register(rt2x00dev, HW_BEACON_BASE3); + rt2800_clear_beacon_register(rt2x00dev, HW_BEACON_BASE4); + rt2800_clear_beacon_register(rt2x00dev, HW_BEACON_BASE5); + rt2800_clear_beacon_register(rt2x00dev, HW_BEACON_BASE6); + rt2800_clear_beacon_register(rt2x00dev, HW_BEACON_BASE7); + + if (rt2x00_is_usb(rt2x00dev)) { + rt2800_register_read(rt2x00dev, US_CYC_CNT, ®); + rt2x00_set_field32(®, US_CYC_CNT_CLOCK_CYCLE, 30); + rt2800_register_write(rt2x00dev, US_CYC_CNT, reg); + } else if (rt2x00_is_pcie(rt2x00dev)) { + rt2800_register_read(rt2x00dev, US_CYC_CNT, ®); + rt2x00_set_field32(®, US_CYC_CNT_CLOCK_CYCLE, 125); + rt2800_register_write(rt2x00dev, US_CYC_CNT, reg); + } + + rt2800_register_read(rt2x00dev, HT_FBK_CFG0, ®); + rt2x00_set_field32(®, HT_FBK_CFG0_HTMCS0FBK, 0); + rt2x00_set_field32(®, HT_FBK_CFG0_HTMCS1FBK, 0); + rt2x00_set_field32(®, HT_FBK_CFG0_HTMCS2FBK, 1); + rt2x00_set_field32(®, HT_FBK_CFG0_HTMCS3FBK, 2); + rt2x00_set_field32(®, HT_FBK_CFG0_HTMCS4FBK, 3); + rt2x00_set_field32(®, HT_FBK_CFG0_HTMCS5FBK, 4); + rt2x00_set_field32(®, HT_FBK_CFG0_HTMCS6FBK, 5); + rt2x00_set_field32(®, HT_FBK_CFG0_HTMCS7FBK, 6); + rt2800_register_write(rt2x00dev, HT_FBK_CFG0, reg); + + rt2800_register_read(rt2x00dev, HT_FBK_CFG1, ®); + rt2x00_set_field32(®, HT_FBK_CFG1_HTMCS8FBK, 8); + rt2x00_set_field32(®, HT_FBK_CFG1_HTMCS9FBK, 8); + rt2x00_set_field32(®, HT_FBK_CFG1_HTMCS10FBK, 9); + rt2x00_set_field32(®, HT_FBK_CFG1_HTMCS11FBK, 10); + rt2x00_set_field32(®, HT_FBK_CFG1_HTMCS12FBK, 11); + rt2x00_set_field32(®, HT_FBK_CFG1_HTMCS13FBK, 12); + rt2x00_set_field32(®, HT_FBK_CFG1_HTMCS14FBK, 13); + rt2x00_set_field32(®, HT_FBK_CFG1_HTMCS15FBK, 14); + rt2800_register_write(rt2x00dev, HT_FBK_CFG1, reg); + + rt2800_register_read(rt2x00dev, LG_FBK_CFG0, ®); + rt2x00_set_field32(®, LG_FBK_CFG0_OFDMMCS0FBK, 8); + rt2x00_set_field32(®, LG_FBK_CFG0_OFDMMCS1FBK, 8); + rt2x00_set_field32(®, LG_FBK_CFG0_OFDMMCS2FBK, 9); + rt2x00_set_field32(®, LG_FBK_CFG0_OFDMMCS3FBK, 10); + rt2x00_set_field32(®, LG_FBK_CFG0_OFDMMCS4FBK, 11); + rt2x00_set_field32(®, LG_FBK_CFG0_OFDMMCS5FBK, 12); + rt2x00_set_field32(®, LG_FBK_CFG0_OFDMMCS6FBK, 13); + rt2x00_set_field32(®, LG_FBK_CFG0_OFDMMCS7FBK, 14); + rt2800_register_write(rt2x00dev, LG_FBK_CFG0, reg); + + rt2800_register_read(rt2x00dev, LG_FBK_CFG1, ®); + rt2x00_set_field32(®, LG_FBK_CFG0_CCKMCS0FBK, 0); + rt2x00_set_field32(®, LG_FBK_CFG0_CCKMCS1FBK, 0); + rt2x00_set_field32(®, LG_FBK_CFG0_CCKMCS2FBK, 1); + rt2x00_set_field32(®, LG_FBK_CFG0_CCKMCS3FBK, 2); + rt2800_register_write(rt2x00dev, LG_FBK_CFG1, reg); + + /* + * Do not force the BA window size, we use the TXWI to set it + */ + rt2800_register_read(rt2x00dev, AMPDU_BA_WINSIZE, ®); + rt2x00_set_field32(®, AMPDU_BA_WINSIZE_FORCE_WINSIZE_ENABLE, 0); + rt2x00_set_field32(®, AMPDU_BA_WINSIZE_FORCE_WINSIZE, 0); + rt2800_register_write(rt2x00dev, AMPDU_BA_WINSIZE, reg); + + /* + * We must clear the error counters. + * These registers are cleared on read, + * so we may pass a useless variable to store the value. + */ + rt2800_register_read(rt2x00dev, RX_STA_CNT0, ®); + rt2800_register_read(rt2x00dev, RX_STA_CNT1, ®); + rt2800_register_read(rt2x00dev, RX_STA_CNT2, ®); + rt2800_register_read(rt2x00dev, TX_STA_CNT0, ®); + rt2800_register_read(rt2x00dev, TX_STA_CNT1, ®); + rt2800_register_read(rt2x00dev, TX_STA_CNT2, ®); + + /* + * Setup leadtime for pre tbtt interrupt to 6ms + */ + rt2800_register_read(rt2x00dev, INT_TIMER_CFG, ®); + rt2x00_set_field32(®, INT_TIMER_CFG_PRE_TBTT_TIMER, 6 << 4); + rt2800_register_write(rt2x00dev, INT_TIMER_CFG, reg); + + /* + * Set up channel statistics timer + */ + rt2800_register_read(rt2x00dev, CH_TIME_CFG, ®); + rt2x00_set_field32(®, CH_TIME_CFG_EIFS_BUSY, 1); + rt2x00_set_field32(®, CH_TIME_CFG_NAV_BUSY, 1); + rt2x00_set_field32(®, CH_TIME_CFG_RX_BUSY, 1); + rt2x00_set_field32(®, CH_TIME_CFG_TX_BUSY, 1); + rt2x00_set_field32(®, CH_TIME_CFG_TMR_EN, 1); + rt2800_register_write(rt2x00dev, CH_TIME_CFG, reg); + + return 0; +} + +static int rt2800_wait_bbp_rf_ready(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i; + u32 reg; + + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2800_register_read(rt2x00dev, MAC_STATUS_CFG, ®); + if (!rt2x00_get_field32(reg, MAC_STATUS_CFG_BBP_RF_BUSY)) + return 0; + + udelay(REGISTER_BUSY_DELAY); + } + + rt2x00_err(rt2x00dev, "BBP/RF register access failed, aborting\n"); + return -EACCES; +} + +static int rt2800_wait_bbp_ready(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i; + u8 value; + + /* + * BBP was enabled after firmware was loaded, + * but we need to reactivate it now. + */ + rt2800_register_write(rt2x00dev, H2M_BBP_AGENT, 0); + rt2800_register_write(rt2x00dev, H2M_MAILBOX_CSR, 0); + msleep(1); + + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2800_bbp_read(rt2x00dev, 0, &value); + if ((value != 0xff) && (value != 0x00)) + return 0; + udelay(REGISTER_BUSY_DELAY); + } + + rt2x00_err(rt2x00dev, "BBP register access failed, aborting\n"); + return -EACCES; +} + +static void rt2800_bbp4_mac_if_ctrl(struct rt2x00_dev *rt2x00dev) +{ + u8 value; + + rt2800_bbp_read(rt2x00dev, 4, &value); + rt2x00_set_field8(&value, BBP4_MAC_IF_CTRL, 1); + rt2800_bbp_write(rt2x00dev, 4, value); +} + +static void rt2800_init_freq_calibration(struct rt2x00_dev *rt2x00dev) +{ + rt2800_bbp_write(rt2x00dev, 142, 1); + rt2800_bbp_write(rt2x00dev, 143, 57); +} + +static void rt2800_init_bbp_5592_glrt(struct rt2x00_dev *rt2x00dev) +{ + const u8 glrt_table[] = { + 0xE0, 0x1F, 0X38, 0x32, 0x08, 0x28, 0x19, 0x0A, 0xFF, 0x00, /* 128 ~ 137 */ + 0x16, 0x10, 0x10, 0x0B, 0x36, 0x2C, 0x26, 0x24, 0x42, 0x36, /* 138 ~ 147 */ + 0x30, 0x2D, 0x4C, 0x46, 0x3D, 0x40, 0x3E, 0x42, 0x3D, 0x40, /* 148 ~ 157 */ + 0X3C, 0x34, 0x2C, 0x2F, 0x3C, 0x35, 0x2E, 0x2A, 0x49, 0x41, /* 158 ~ 167 */ + 0x36, 0x31, 0x30, 0x30, 0x0E, 0x0D, 0x28, 0x21, 0x1C, 0x16, /* 168 ~ 177 */ + 0x50, 0x4A, 0x43, 0x40, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, /* 178 ~ 187 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 188 ~ 197 */ + 0x00, 0x00, 0x7D, 0x14, 0x32, 0x2C, 0x36, 0x4C, 0x43, 0x2C, /* 198 ~ 207 */ + 0x2E, 0x36, 0x30, 0x6E, /* 208 ~ 211 */ + }; + int i; + + for (i = 0; i < ARRAY_SIZE(glrt_table); i++) { + rt2800_bbp_write(rt2x00dev, 195, 128 + i); + rt2800_bbp_write(rt2x00dev, 196, glrt_table[i]); + } +}; + +static void rt2800_init_bbp_early(struct rt2x00_dev *rt2x00dev) +{ + rt2800_bbp_write(rt2x00dev, 65, 0x2C); + rt2800_bbp_write(rt2x00dev, 66, 0x38); + rt2800_bbp_write(rt2x00dev, 68, 0x0B); + rt2800_bbp_write(rt2x00dev, 69, 0x12); + rt2800_bbp_write(rt2x00dev, 70, 0x0a); + rt2800_bbp_write(rt2x00dev, 73, 0x10); + rt2800_bbp_write(rt2x00dev, 81, 0x37); + rt2800_bbp_write(rt2x00dev, 82, 0x62); + rt2800_bbp_write(rt2x00dev, 83, 0x6A); + rt2800_bbp_write(rt2x00dev, 84, 0x99); + rt2800_bbp_write(rt2x00dev, 86, 0x00); + rt2800_bbp_write(rt2x00dev, 91, 0x04); + rt2800_bbp_write(rt2x00dev, 92, 0x00); + rt2800_bbp_write(rt2x00dev, 103, 0x00); + rt2800_bbp_write(rt2x00dev, 105, 0x05); + rt2800_bbp_write(rt2x00dev, 106, 0x35); +} + +static void rt2800_init_bbp_5592(struct rt2x00_dev *rt2x00dev) +{ + int ant, div_mode; + u16 eeprom; + u8 value; + + rt2800_init_bbp_early(rt2x00dev); + + rt2800_bbp_read(rt2x00dev, 105, &value); + rt2x00_set_field8(&value, BBP105_MLD, + rt2x00dev->default_ant.rx_chain_num == 2); + rt2800_bbp_write(rt2x00dev, 105, value); + + rt2800_bbp4_mac_if_ctrl(rt2x00dev); + + rt2800_bbp_write(rt2x00dev, 20, 0x06); + rt2800_bbp_write(rt2x00dev, 31, 0x08); + rt2800_bbp_write(rt2x00dev, 65, 0x2C); + rt2800_bbp_write(rt2x00dev, 68, 0xDD); + rt2800_bbp_write(rt2x00dev, 69, 0x1A); + rt2800_bbp_write(rt2x00dev, 70, 0x05); + rt2800_bbp_write(rt2x00dev, 73, 0x13); + rt2800_bbp_write(rt2x00dev, 74, 0x0F); + rt2800_bbp_write(rt2x00dev, 75, 0x4F); + rt2800_bbp_write(rt2x00dev, 76, 0x28); + rt2800_bbp_write(rt2x00dev, 77, 0x59); + rt2800_bbp_write(rt2x00dev, 84, 0x9A); + rt2800_bbp_write(rt2x00dev, 86, 0x38); + rt2800_bbp_write(rt2x00dev, 88, 0x90); + rt2800_bbp_write(rt2x00dev, 91, 0x04); + rt2800_bbp_write(rt2x00dev, 92, 0x02); + rt2800_bbp_write(rt2x00dev, 95, 0x9a); + rt2800_bbp_write(rt2x00dev, 98, 0x12); + rt2800_bbp_write(rt2x00dev, 103, 0xC0); + rt2800_bbp_write(rt2x00dev, 104, 0x92); + /* FIXME BBP105 owerwrite */ + rt2800_bbp_write(rt2x00dev, 105, 0x3C); + rt2800_bbp_write(rt2x00dev, 106, 0x35); + rt2800_bbp_write(rt2x00dev, 128, 0x12); + rt2800_bbp_write(rt2x00dev, 134, 0xD0); + rt2800_bbp_write(rt2x00dev, 135, 0xF6); + rt2800_bbp_write(rt2x00dev, 137, 0x0F); + + /* Initialize GLRT (Generalized Likehood Radio Test) */ + rt2800_init_bbp_5592_glrt(rt2x00dev); + + rt2800_bbp4_mac_if_ctrl(rt2x00dev); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC_CONF1, &eeprom); + div_mode = rt2x00_get_field16(eeprom, EEPROM_NIC_CONF1_ANT_DIVERSITY); + ant = (div_mode == 3) ? 1 : 0; + rt2800_bbp_read(rt2x00dev, 152, &value); + if (ant == 0) { + /* Main antenna */ + rt2x00_set_field8(&value, BBP152_RX_DEFAULT_ANT, 1); + } else { + /* Auxiliary antenna */ + rt2x00_set_field8(&value, BBP152_RX_DEFAULT_ANT, 0); + } + rt2800_bbp_write(rt2x00dev, 152, value); + + if (rt2x00_rt_rev_gte(rt2x00dev, RT5592, REV_RT5592C)) { + rt2800_bbp_read(rt2x00dev, 254, &value); + rt2x00_set_field8(&value, BBP254_BIT7, 1); + rt2800_bbp_write(rt2x00dev, 254, value); + } + + rt2800_init_freq_calibration(rt2x00dev); + + rt2800_bbp_write(rt2x00dev, 84, 0x19); + if (rt2x00_rt_rev_gte(rt2x00dev, RT5592, REV_RT5592C)) + rt2800_bbp_write(rt2x00dev, 103, 0xc0); +} + +static int rt2800_init_bbp(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i; + u16 eeprom; + u8 reg_id; + u8 value; + + if (rt2x00_rt(rt2x00dev, RT5592)) { + rt2800_init_bbp_5592(rt2x00dev); + return 0; + } + + if (rt2x00_rt(rt2x00dev, RT3352)) { + rt2800_bbp_write(rt2x00dev, 3, 0x00); + rt2800_bbp_write(rt2x00dev, 4, 0x50); + } + + if (rt2x00_rt(rt2x00dev, RT3290) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) + rt2800_bbp4_mac_if_ctrl(rt2x00dev); + + if (rt2800_is_305x_soc(rt2x00dev) || + rt2x00_rt(rt2x00dev, RT3290) || + rt2x00_rt(rt2x00dev, RT3352) || + rt2x00_rt(rt2x00dev, RT3572) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) + rt2800_bbp_write(rt2x00dev, 31, 0x08); + + if (rt2x00_rt(rt2x00dev, RT3352)) + rt2800_bbp_write(rt2x00dev, 47, 0x48); + + rt2800_bbp_write(rt2x00dev, 65, 0x2c); + rt2800_bbp_write(rt2x00dev, 66, 0x38); + + if (rt2x00_rt(rt2x00dev, RT3290) || + rt2x00_rt(rt2x00dev, RT3352) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) + rt2800_bbp_write(rt2x00dev, 68, 0x0b); + + if (rt2x00_rt_rev(rt2x00dev, RT2860, REV_RT2860C)) { + rt2800_bbp_write(rt2x00dev, 69, 0x16); + rt2800_bbp_write(rt2x00dev, 73, 0x12); + } else if (rt2x00_rt(rt2x00dev, RT3290) || + rt2x00_rt(rt2x00dev, RT3352) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) { + rt2800_bbp_write(rt2x00dev, 69, 0x12); + rt2800_bbp_write(rt2x00dev, 73, 0x13); + rt2800_bbp_write(rt2x00dev, 75, 0x46); + rt2800_bbp_write(rt2x00dev, 76, 0x28); + + if (rt2x00_rt(rt2x00dev, RT3290)) + rt2800_bbp_write(rt2x00dev, 77, 0x58); + else + rt2800_bbp_write(rt2x00dev, 77, 0x59); + } else { + rt2800_bbp_write(rt2x00dev, 69, 0x12); + rt2800_bbp_write(rt2x00dev, 73, 0x10); + } + + rt2800_bbp_write(rt2x00dev, 70, 0x0a); + + if (rt2x00_rt(rt2x00dev, RT3070) || + rt2x00_rt(rt2x00dev, RT3071) || + rt2x00_rt(rt2x00dev, RT3090) || + rt2x00_rt(rt2x00dev, RT3390) || + rt2x00_rt(rt2x00dev, RT3572) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) { + rt2800_bbp_write(rt2x00dev, 79, 0x13); + rt2800_bbp_write(rt2x00dev, 80, 0x05); + rt2800_bbp_write(rt2x00dev, 81, 0x33); + } else if (rt2800_is_305x_soc(rt2x00dev)) { + rt2800_bbp_write(rt2x00dev, 78, 0x0e); + rt2800_bbp_write(rt2x00dev, 80, 0x08); + } else if (rt2x00_rt(rt2x00dev, RT3290)) { + rt2800_bbp_write(rt2x00dev, 74, 0x0b); + rt2800_bbp_write(rt2x00dev, 79, 0x18); + rt2800_bbp_write(rt2x00dev, 80, 0x09); + rt2800_bbp_write(rt2x00dev, 81, 0x33); + } else if (rt2x00_rt(rt2x00dev, RT3352)) { + rt2800_bbp_write(rt2x00dev, 78, 0x0e); + rt2800_bbp_write(rt2x00dev, 80, 0x08); + rt2800_bbp_write(rt2x00dev, 81, 0x37); + } else { + rt2800_bbp_write(rt2x00dev, 81, 0x37); + } + + rt2800_bbp_write(rt2x00dev, 82, 0x62); + if (rt2x00_rt(rt2x00dev, RT3290) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) + rt2800_bbp_write(rt2x00dev, 83, 0x7a); + else + rt2800_bbp_write(rt2x00dev, 83, 0x6a); + + if (rt2x00_rt_rev(rt2x00dev, RT2860, REV_RT2860D)) + rt2800_bbp_write(rt2x00dev, 84, 0x19); + else if (rt2x00_rt(rt2x00dev, RT3290) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) + rt2800_bbp_write(rt2x00dev, 84, 0x9a); + else + rt2800_bbp_write(rt2x00dev, 84, 0x99); + + if (rt2x00_rt(rt2x00dev, RT3290) || + rt2x00_rt(rt2x00dev, RT3352) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) + rt2800_bbp_write(rt2x00dev, 86, 0x38); + else + rt2800_bbp_write(rt2x00dev, 86, 0x00); + + if (rt2x00_rt(rt2x00dev, RT3352) || + rt2x00_rt(rt2x00dev, RT5392)) + rt2800_bbp_write(rt2x00dev, 88, 0x90); + + rt2800_bbp_write(rt2x00dev, 91, 0x04); + + if (rt2x00_rt(rt2x00dev, RT3290) || + rt2x00_rt(rt2x00dev, RT3352) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) + rt2800_bbp_write(rt2x00dev, 92, 0x02); + else + rt2800_bbp_write(rt2x00dev, 92, 0x00); + + if (rt2x00_rt(rt2x00dev, RT5392)) { + rt2800_bbp_write(rt2x00dev, 95, 0x9a); + rt2800_bbp_write(rt2x00dev, 98, 0x12); + } + + if (rt2x00_rt_rev_gte(rt2x00dev, RT3070, REV_RT3070F) || + rt2x00_rt_rev_gte(rt2x00dev, RT3071, REV_RT3071E) || + rt2x00_rt_rev_gte(rt2x00dev, RT3090, REV_RT3090E) || + rt2x00_rt_rev_gte(rt2x00dev, RT3390, REV_RT3390E) || + rt2x00_rt(rt2x00dev, RT3290) || + rt2x00_rt(rt2x00dev, RT3352) || + rt2x00_rt(rt2x00dev, RT3572) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392) || + rt2800_is_305x_soc(rt2x00dev)) + rt2800_bbp_write(rt2x00dev, 103, 0xc0); + else + rt2800_bbp_write(rt2x00dev, 103, 0x00); + + if (rt2x00_rt(rt2x00dev, RT3290) || + rt2x00_rt(rt2x00dev, RT3352) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) + rt2800_bbp_write(rt2x00dev, 104, 0x92); + + if (rt2800_is_305x_soc(rt2x00dev)) + rt2800_bbp_write(rt2x00dev, 105, 0x01); + else if (rt2x00_rt(rt2x00dev, RT3290)) + rt2800_bbp_write(rt2x00dev, 105, 0x1c); + else if (rt2x00_rt(rt2x00dev, RT3352)) + rt2800_bbp_write(rt2x00dev, 105, 0x34); + else if (rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) + rt2800_bbp_write(rt2x00dev, 105, 0x3c); + else + rt2800_bbp_write(rt2x00dev, 105, 0x05); + + if (rt2x00_rt(rt2x00dev, RT3290) || + rt2x00_rt(rt2x00dev, RT5390)) + rt2800_bbp_write(rt2x00dev, 106, 0x03); + else if (rt2x00_rt(rt2x00dev, RT3352)) + rt2800_bbp_write(rt2x00dev, 106, 0x05); + else if (rt2x00_rt(rt2x00dev, RT5392)) + rt2800_bbp_write(rt2x00dev, 106, 0x12); + else + rt2800_bbp_write(rt2x00dev, 106, 0x35); + + if (rt2x00_rt(rt2x00dev, RT3352)) + rt2800_bbp_write(rt2x00dev, 120, 0x50); + + if (rt2x00_rt(rt2x00dev, RT3290) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) + rt2800_bbp_write(rt2x00dev, 128, 0x12); + + if (rt2x00_rt(rt2x00dev, RT5392)) { + rt2800_bbp_write(rt2x00dev, 134, 0xd0); + rt2800_bbp_write(rt2x00dev, 135, 0xf6); + } + + if (rt2x00_rt(rt2x00dev, RT3352)) + rt2800_bbp_write(rt2x00dev, 137, 0x0f); + + if (rt2x00_rt(rt2x00dev, RT3071) || + rt2x00_rt(rt2x00dev, RT3090) || + rt2x00_rt(rt2x00dev, RT3390) || + rt2x00_rt(rt2x00dev, RT3572) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) { + rt2800_bbp_read(rt2x00dev, 138, &value); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC_CONF0, &eeprom); + if (rt2x00_get_field16(eeprom, EEPROM_NIC_CONF0_TXPATH) == 1) + value |= 0x20; + if (rt2x00_get_field16(eeprom, EEPROM_NIC_CONF0_RXPATH) == 1) + value &= ~0x02; + + rt2800_bbp_write(rt2x00dev, 138, value); + } + + if (rt2x00_rt(rt2x00dev, RT3290)) { + rt2800_bbp_write(rt2x00dev, 67, 0x24); + rt2800_bbp_write(rt2x00dev, 143, 0x04); + rt2800_bbp_write(rt2x00dev, 142, 0x99); + rt2800_bbp_write(rt2x00dev, 150, 0x30); + rt2800_bbp_write(rt2x00dev, 151, 0x2e); + rt2800_bbp_write(rt2x00dev, 152, 0x20); + rt2800_bbp_write(rt2x00dev, 153, 0x34); + rt2800_bbp_write(rt2x00dev, 154, 0x40); + rt2800_bbp_write(rt2x00dev, 155, 0x3b); + rt2800_bbp_write(rt2x00dev, 253, 0x04); + + rt2800_bbp_read(rt2x00dev, 47, &value); + rt2x00_set_field8(&value, BBP47_TSSI_ADC6, 1); + rt2800_bbp_write(rt2x00dev, 47, value); + + /* Use 5-bit ADC for Acquisition and 8-bit ADC for data */ + rt2800_bbp_read(rt2x00dev, 3, &value); + rt2x00_set_field8(&value, BBP3_ADC_MODE_SWITCH, 1); + rt2x00_set_field8(&value, BBP3_ADC_INIT_MODE, 1); + rt2800_bbp_write(rt2x00dev, 3, value); + } + + if (rt2x00_rt(rt2x00dev, RT3352)) { + rt2800_bbp_write(rt2x00dev, 163, 0xbd); + /* Set ITxBF timeout to 0x9c40=1000msec */ + rt2800_bbp_write(rt2x00dev, 179, 0x02); + rt2800_bbp_write(rt2x00dev, 180, 0x00); + rt2800_bbp_write(rt2x00dev, 182, 0x40); + rt2800_bbp_write(rt2x00dev, 180, 0x01); + rt2800_bbp_write(rt2x00dev, 182, 0x9c); + rt2800_bbp_write(rt2x00dev, 179, 0x00); + /* Reprogram the inband interface to put right values in RXWI */ + rt2800_bbp_write(rt2x00dev, 142, 0x04); + rt2800_bbp_write(rt2x00dev, 143, 0x3b); + rt2800_bbp_write(rt2x00dev, 142, 0x06); + rt2800_bbp_write(rt2x00dev, 143, 0xa0); + rt2800_bbp_write(rt2x00dev, 142, 0x07); + rt2800_bbp_write(rt2x00dev, 143, 0xa1); + rt2800_bbp_write(rt2x00dev, 142, 0x08); + rt2800_bbp_write(rt2x00dev, 143, 0xa2); + + rt2800_bbp_write(rt2x00dev, 148, 0xc8); + } + + if (rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) { + int ant, div_mode; + + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC_CONF1, &eeprom); + div_mode = rt2x00_get_field16(eeprom, + EEPROM_NIC_CONF1_ANT_DIVERSITY); + ant = (div_mode == 3) ? 1 : 0; + + /* check if this is a Bluetooth combo card */ + if (test_bit(CAPABILITY_BT_COEXIST, &rt2x00dev->cap_flags)) { + u32 reg; + + rt2800_register_read(rt2x00dev, GPIO_CTRL, ®); + rt2x00_set_field32(®, GPIO_CTRL_DIR3, 0); + rt2x00_set_field32(®, GPIO_CTRL_DIR6, 0); + rt2x00_set_field32(®, GPIO_CTRL_VAL3, 0); + rt2x00_set_field32(®, GPIO_CTRL_VAL6, 0); + if (ant == 0) + rt2x00_set_field32(®, GPIO_CTRL_VAL3, 1); + else if (ant == 1) + rt2x00_set_field32(®, GPIO_CTRL_VAL6, 1); + rt2800_register_write(rt2x00dev, GPIO_CTRL, reg); + } + + /* This chip has hardware antenna diversity*/ + if (rt2x00_rt_rev_gte(rt2x00dev, RT5390, REV_RT5390R)) { + rt2800_bbp_write(rt2x00dev, 150, 0); /* Disable Antenna Software OFDM */ + rt2800_bbp_write(rt2x00dev, 151, 0); /* Disable Antenna Software CCK */ + rt2800_bbp_write(rt2x00dev, 154, 0); /* Clear previously selected antenna */ + } + + rt2800_bbp_read(rt2x00dev, 152, &value); + if (ant == 0) + rt2x00_set_field8(&value, BBP152_RX_DEFAULT_ANT, 1); + else + rt2x00_set_field8(&value, BBP152_RX_DEFAULT_ANT, 0); + rt2800_bbp_write(rt2x00dev, 152, value); + + rt2800_init_freq_calibration(rt2x00dev); + } + + for (i = 0; i < EEPROM_BBP_SIZE; i++) { + rt2x00_eeprom_read(rt2x00dev, EEPROM_BBP_START + i, &eeprom); + + if (eeprom != 0xffff && eeprom != 0x0000) { + reg_id = rt2x00_get_field16(eeprom, EEPROM_BBP_REG_ID); + value = rt2x00_get_field16(eeprom, EEPROM_BBP_VALUE); + rt2800_bbp_write(rt2x00dev, reg_id, value); + } + } + + return 0; +} + +static void rt2800_led_open_drain_enable(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + rt2800_register_read(rt2x00dev, OPT_14_CSR, ®); + rt2x00_set_field32(®, OPT_14_CSR_BIT0, 1); + rt2800_register_write(rt2x00dev, OPT_14_CSR, reg); +} + +static u8 rt2800_init_rx_filter(struct rt2x00_dev *rt2x00dev, bool bw40, + u8 filter_target) +{ + unsigned int i; + u8 bbp; + u8 rfcsr; + u8 passband; + u8 stopband; + u8 overtuned = 0; + u8 rfcsr24 = (bw40) ? 0x27 : 0x07; + + rt2800_rfcsr_write(rt2x00dev, 24, rfcsr24); + + rt2800_bbp_read(rt2x00dev, 4, &bbp); + rt2x00_set_field8(&bbp, BBP4_BANDWIDTH, 2 * bw40); + rt2800_bbp_write(rt2x00dev, 4, bbp); + + rt2800_rfcsr_read(rt2x00dev, 31, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR31_RX_H20M, bw40); + rt2800_rfcsr_write(rt2x00dev, 31, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 22, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR22_BASEBAND_LOOPBACK, 1); + rt2800_rfcsr_write(rt2x00dev, 22, rfcsr); + + /* + * Set power & frequency of passband test tone + */ + rt2800_bbp_write(rt2x00dev, 24, 0); + + for (i = 0; i < 100; i++) { + rt2800_bbp_write(rt2x00dev, 25, 0x90); + msleep(1); + + rt2800_bbp_read(rt2x00dev, 55, &passband); + if (passband) + break; + } + + /* + * Set power & frequency of stopband test tone + */ + rt2800_bbp_write(rt2x00dev, 24, 0x06); + + for (i = 0; i < 100; i++) { + rt2800_bbp_write(rt2x00dev, 25, 0x90); + msleep(1); + + rt2800_bbp_read(rt2x00dev, 55, &stopband); + + if ((passband - stopband) <= filter_target) { + rfcsr24++; + overtuned += ((passband - stopband) == filter_target); + } else + break; + + rt2800_rfcsr_write(rt2x00dev, 24, rfcsr24); + } + + rfcsr24 -= !!overtuned; + + rt2800_rfcsr_write(rt2x00dev, 24, rfcsr24); + return rfcsr24; +} + +static void rt2800_rf_init_calibration(struct rt2x00_dev *rt2x00dev, + const unsigned int rf_reg) +{ + u8 rfcsr; + + rt2800_rfcsr_read(rt2x00dev, rf_reg, &rfcsr); + rt2x00_set_field8(&rfcsr, FIELD8(0x80), 1); + rt2800_rfcsr_write(rt2x00dev, rf_reg, rfcsr); + msleep(1); + rt2x00_set_field8(&rfcsr, FIELD8(0x80), 0); + rt2800_rfcsr_write(rt2x00dev, rf_reg, rfcsr); +} + +static void rt2800_rx_filter_calibration(struct rt2x00_dev *rt2x00dev) +{ + struct rt2800_drv_data *drv_data = rt2x00dev->drv_data; + u8 filter_tgt_bw20; + u8 filter_tgt_bw40; + u8 rfcsr, bbp; + + /* + * TODO: sync filter_tgt values with vendor driver + */ + if (rt2x00_rt(rt2x00dev, RT3070)) { + filter_tgt_bw20 = 0x16; + filter_tgt_bw40 = 0x19; + } else { + filter_tgt_bw20 = 0x13; + filter_tgt_bw40 = 0x15; + } + + drv_data->calibration_bw20 = + rt2800_init_rx_filter(rt2x00dev, false, filter_tgt_bw20); + drv_data->calibration_bw40 = + rt2800_init_rx_filter(rt2x00dev, true, filter_tgt_bw40); + + /* + * Save BBP 25 & 26 values for later use in channel switching (for 3052) + */ + rt2800_bbp_read(rt2x00dev, 25, &drv_data->bbp25); + rt2800_bbp_read(rt2x00dev, 26, &drv_data->bbp26); + + /* + * Set back to initial state + */ + rt2800_bbp_write(rt2x00dev, 24, 0); + + rt2800_rfcsr_read(rt2x00dev, 22, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR22_BASEBAND_LOOPBACK, 0); + rt2800_rfcsr_write(rt2x00dev, 22, rfcsr); + + /* + * Set BBP back to BW20 + */ + rt2800_bbp_read(rt2x00dev, 4, &bbp); + rt2x00_set_field8(&bbp, BBP4_BANDWIDTH, 0); + rt2800_bbp_write(rt2x00dev, 4, bbp); +} + +static void rt2800_normal_mode_setup_3xxx(struct rt2x00_dev *rt2x00dev) +{ + struct rt2800_drv_data *drv_data = rt2x00dev->drv_data; + u8 min_gain, rfcsr, bbp; + u16 eeprom; + + rt2800_rfcsr_read(rt2x00dev, 17, &rfcsr); + + rt2x00_set_field8(&rfcsr, RFCSR17_TX_LO1_EN, 0); + if (rt2x00_rt(rt2x00dev, RT3070) || + rt2x00_rt_rev_lt(rt2x00dev, RT3071, REV_RT3071E) || + rt2x00_rt_rev_lt(rt2x00dev, RT3090, REV_RT3090E) || + rt2x00_rt_rev_lt(rt2x00dev, RT3390, REV_RT3390E)) { + if (!test_bit(CAPABILITY_EXTERNAL_LNA_BG, &rt2x00dev->cap_flags)) + rt2x00_set_field8(&rfcsr, RFCSR17_R, 1); + } + + min_gain = rt2x00_rt(rt2x00dev, RT3070) ? 1 : 2; + if (drv_data->txmixer_gain_24g >= min_gain) { + rt2x00_set_field8(&rfcsr, RFCSR17_TXMIXER_GAIN, + drv_data->txmixer_gain_24g); + } + + rt2800_rfcsr_write(rt2x00dev, 17, rfcsr); + + if (rt2x00_rt(rt2x00dev, RT3090)) { + /* Turn off unused DAC1 and ADC1 to reduce power consumption */ + rt2800_bbp_read(rt2x00dev, 138, &bbp); + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC_CONF0, &eeprom); + if (rt2x00_get_field16(eeprom, EEPROM_NIC_CONF0_RXPATH) == 1) + rt2x00_set_field8(&bbp, BBP138_RX_ADC1, 0); + if (rt2x00_get_field16(eeprom, EEPROM_NIC_CONF0_TXPATH) == 1) + rt2x00_set_field8(&bbp, BBP138_TX_DAC1, 1); + rt2800_bbp_write(rt2x00dev, 138, bbp); + } + + if (rt2x00_rt(rt2x00dev, RT3070)) { + rt2800_rfcsr_read(rt2x00dev, 27, &rfcsr); + if (rt2x00_rt_rev_lt(rt2x00dev, RT3070, REV_RT3070F)) + rt2x00_set_field8(&rfcsr, RFCSR27_R1, 3); + else + rt2x00_set_field8(&rfcsr, RFCSR27_R1, 0); + rt2x00_set_field8(&rfcsr, RFCSR27_R2, 0); + rt2x00_set_field8(&rfcsr, RFCSR27_R3, 0); + rt2x00_set_field8(&rfcsr, RFCSR27_R4, 0); + rt2800_rfcsr_write(rt2x00dev, 27, rfcsr); + } else if (rt2x00_rt(rt2x00dev, RT3071) || + rt2x00_rt(rt2x00dev, RT3090) || + rt2x00_rt(rt2x00dev, RT3390)) { + rt2800_rfcsr_read(rt2x00dev, 1, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR1_RF_BLOCK_EN, 1); + rt2x00_set_field8(&rfcsr, RFCSR1_RX0_PD, 0); + rt2x00_set_field8(&rfcsr, RFCSR1_TX0_PD, 0); + rt2x00_set_field8(&rfcsr, RFCSR1_RX1_PD, 1); + rt2x00_set_field8(&rfcsr, RFCSR1_TX1_PD, 1); + rt2800_rfcsr_write(rt2x00dev, 1, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 15, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR15_TX_LO2_EN, 0); + rt2800_rfcsr_write(rt2x00dev, 15, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 20, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR20_RX_LO1_EN, 0); + rt2800_rfcsr_write(rt2x00dev, 20, rfcsr); + + rt2800_rfcsr_read(rt2x00dev, 21, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR21_RX_LO2_EN, 0); + rt2800_rfcsr_write(rt2x00dev, 21, rfcsr); + } +} + +static void rt2800_normal_mode_setup_5xxx(struct rt2x00_dev *rt2x00dev) +{ + u8 reg; + u16 eeprom; + + /* Turn off unused DAC1 and ADC1 to reduce power consumption */ + rt2800_bbp_read(rt2x00dev, 138, ®); + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC_CONF0, &eeprom); + if (rt2x00_get_field16(eeprom, EEPROM_NIC_CONF0_RXPATH) == 1) + rt2x00_set_field8(®, BBP138_RX_ADC1, 0); + if (rt2x00_get_field16(eeprom, EEPROM_NIC_CONF0_TXPATH) == 1) + rt2x00_set_field8(®, BBP138_TX_DAC1, 1); + rt2800_bbp_write(rt2x00dev, 138, reg); + + rt2800_rfcsr_read(rt2x00dev, 38, ®); + rt2x00_set_field8(®, RFCSR38_RX_LO1_EN, 0); + rt2800_rfcsr_write(rt2x00dev, 38, reg); + + rt2800_rfcsr_read(rt2x00dev, 39, ®); + rt2x00_set_field8(®, RFCSR39_RX_LO2_EN, 0); + rt2800_rfcsr_write(rt2x00dev, 39, reg); + + rt2800_bbp4_mac_if_ctrl(rt2x00dev); + + rt2800_rfcsr_read(rt2x00dev, 30, ®); + rt2x00_set_field8(®, RFCSR30_RX_VCM, 2); + rt2800_rfcsr_write(rt2x00dev, 30, reg); +} + +static void rt2800_init_rfcsr_305x_soc(struct rt2x00_dev *rt2x00dev) +{ + rt2800_rf_init_calibration(rt2x00dev, 30); + + rt2800_rfcsr_write(rt2x00dev, 0, 0x50); + rt2800_rfcsr_write(rt2x00dev, 1, 0x01); + rt2800_rfcsr_write(rt2x00dev, 2, 0xf7); + rt2800_rfcsr_write(rt2x00dev, 3, 0x75); + rt2800_rfcsr_write(rt2x00dev, 4, 0x40); + rt2800_rfcsr_write(rt2x00dev, 5, 0x03); + rt2800_rfcsr_write(rt2x00dev, 6, 0x02); + rt2800_rfcsr_write(rt2x00dev, 7, 0x50); + rt2800_rfcsr_write(rt2x00dev, 8, 0x39); + rt2800_rfcsr_write(rt2x00dev, 9, 0x0f); + rt2800_rfcsr_write(rt2x00dev, 10, 0x60); + rt2800_rfcsr_write(rt2x00dev, 11, 0x21); + rt2800_rfcsr_write(rt2x00dev, 12, 0x75); + rt2800_rfcsr_write(rt2x00dev, 13, 0x75); + rt2800_rfcsr_write(rt2x00dev, 14, 0x90); + rt2800_rfcsr_write(rt2x00dev, 15, 0x58); + rt2800_rfcsr_write(rt2x00dev, 16, 0xb3); + rt2800_rfcsr_write(rt2x00dev, 17, 0x92); + rt2800_rfcsr_write(rt2x00dev, 18, 0x2c); + rt2800_rfcsr_write(rt2x00dev, 19, 0x02); + rt2800_rfcsr_write(rt2x00dev, 20, 0xba); + rt2800_rfcsr_write(rt2x00dev, 21, 0xdb); + rt2800_rfcsr_write(rt2x00dev, 22, 0x00); + rt2800_rfcsr_write(rt2x00dev, 23, 0x31); + rt2800_rfcsr_write(rt2x00dev, 24, 0x08); + rt2800_rfcsr_write(rt2x00dev, 25, 0x01); + rt2800_rfcsr_write(rt2x00dev, 26, 0x25); + rt2800_rfcsr_write(rt2x00dev, 27, 0x23); + rt2800_rfcsr_write(rt2x00dev, 28, 0x13); + rt2800_rfcsr_write(rt2x00dev, 29, 0x83); + rt2800_rfcsr_write(rt2x00dev, 30, 0x00); + rt2800_rfcsr_write(rt2x00dev, 31, 0x00); +} + +static void rt2800_init_rfcsr_30xx(struct rt2x00_dev *rt2x00dev) +{ + u8 rfcsr; + u16 eeprom; + u32 reg; + + /* XXX vendor driver do this only for 3070 */ + rt2800_rf_init_calibration(rt2x00dev, 30); + + rt2800_rfcsr_write(rt2x00dev, 4, 0x40); + rt2800_rfcsr_write(rt2x00dev, 5, 0x03); + rt2800_rfcsr_write(rt2x00dev, 6, 0x02); + rt2800_rfcsr_write(rt2x00dev, 7, 0x60); + rt2800_rfcsr_write(rt2x00dev, 9, 0x0f); + rt2800_rfcsr_write(rt2x00dev, 10, 0x41); + rt2800_rfcsr_write(rt2x00dev, 11, 0x21); + rt2800_rfcsr_write(rt2x00dev, 12, 0x7b); + rt2800_rfcsr_write(rt2x00dev, 14, 0x90); + rt2800_rfcsr_write(rt2x00dev, 15, 0x58); + rt2800_rfcsr_write(rt2x00dev, 16, 0xb3); + rt2800_rfcsr_write(rt2x00dev, 17, 0x92); + rt2800_rfcsr_write(rt2x00dev, 18, 0x2c); + rt2800_rfcsr_write(rt2x00dev, 19, 0x02); + rt2800_rfcsr_write(rt2x00dev, 20, 0xba); + rt2800_rfcsr_write(rt2x00dev, 21, 0xdb); + rt2800_rfcsr_write(rt2x00dev, 24, 0x16); + rt2800_rfcsr_write(rt2x00dev, 25, 0x01); + rt2800_rfcsr_write(rt2x00dev, 29, 0x1f); + + if (rt2x00_rt_rev_lt(rt2x00dev, RT3070, REV_RT3070F)) { + rt2800_register_read(rt2x00dev, LDO_CFG0, ®); + rt2x00_set_field32(®, LDO_CFG0_BGSEL, 1); + rt2x00_set_field32(®, LDO_CFG0_LDO_CORE_VLEVEL, 3); + rt2800_register_write(rt2x00dev, LDO_CFG0, reg); + } else if (rt2x00_rt(rt2x00dev, RT3071) || + rt2x00_rt(rt2x00dev, RT3090)) { + rt2800_rfcsr_write(rt2x00dev, 31, 0x14); + + rt2800_rfcsr_read(rt2x00dev, 6, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR6_R2, 1); + rt2800_rfcsr_write(rt2x00dev, 6, rfcsr); + + rt2800_register_read(rt2x00dev, LDO_CFG0, ®); + rt2x00_set_field32(®, LDO_CFG0_BGSEL, 1); + if (rt2x00_rt_rev_lt(rt2x00dev, RT3071, REV_RT3071E) || + rt2x00_rt_rev_lt(rt2x00dev, RT3090, REV_RT3090E)) { + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC_CONF1, &eeprom); + if (rt2x00_get_field16(eeprom, EEPROM_NIC_CONF1_DAC_TEST)) + rt2x00_set_field32(®, LDO_CFG0_LDO_CORE_VLEVEL, 3); + else + rt2x00_set_field32(®, LDO_CFG0_LDO_CORE_VLEVEL, 0); + } + rt2800_register_write(rt2x00dev, LDO_CFG0, reg); + + rt2800_register_read(rt2x00dev, GPIO_SWITCH, ®); + rt2x00_set_field32(®, GPIO_SWITCH_5, 0); + rt2800_register_write(rt2x00dev, GPIO_SWITCH, reg); + } + + rt2800_rx_filter_calibration(rt2x00dev); + + if (rt2x00_rt_rev_lt(rt2x00dev, RT3070, REV_RT3070F) || + rt2x00_rt_rev_lt(rt2x00dev, RT3071, REV_RT3071E) || + rt2x00_rt_rev_lt(rt2x00dev, RT3090, REV_RT3090E)) + rt2800_rfcsr_write(rt2x00dev, 27, 0x03); + + rt2800_led_open_drain_enable(rt2x00dev); + rt2800_normal_mode_setup_3xxx(rt2x00dev); +} + +static void rt2800_init_rfcsr_3290(struct rt2x00_dev *rt2x00dev) +{ + u8 rfcsr; + + rt2800_rf_init_calibration(rt2x00dev, 2); + + rt2800_rfcsr_write(rt2x00dev, 1, 0x0f); + rt2800_rfcsr_write(rt2x00dev, 2, 0x80); + rt2800_rfcsr_write(rt2x00dev, 3, 0x08); + rt2800_rfcsr_write(rt2x00dev, 4, 0x00); + rt2800_rfcsr_write(rt2x00dev, 6, 0xa0); + rt2800_rfcsr_write(rt2x00dev, 8, 0xf3); + rt2800_rfcsr_write(rt2x00dev, 9, 0x02); + rt2800_rfcsr_write(rt2x00dev, 10, 0x53); + rt2800_rfcsr_write(rt2x00dev, 11, 0x4a); + rt2800_rfcsr_write(rt2x00dev, 12, 0x46); + rt2800_rfcsr_write(rt2x00dev, 13, 0x9f); + rt2800_rfcsr_write(rt2x00dev, 18, 0x02); + rt2800_rfcsr_write(rt2x00dev, 22, 0x20); + rt2800_rfcsr_write(rt2x00dev, 25, 0x83); + rt2800_rfcsr_write(rt2x00dev, 26, 0x82); + rt2800_rfcsr_write(rt2x00dev, 27, 0x09); + rt2800_rfcsr_write(rt2x00dev, 29, 0x10); + rt2800_rfcsr_write(rt2x00dev, 30, 0x10); + rt2800_rfcsr_write(rt2x00dev, 31, 0x80); + rt2800_rfcsr_write(rt2x00dev, 32, 0x80); + rt2800_rfcsr_write(rt2x00dev, 33, 0x00); + rt2800_rfcsr_write(rt2x00dev, 34, 0x05); + rt2800_rfcsr_write(rt2x00dev, 35, 0x12); + rt2800_rfcsr_write(rt2x00dev, 36, 0x00); + rt2800_rfcsr_write(rt2x00dev, 38, 0x85); + rt2800_rfcsr_write(rt2x00dev, 39, 0x1b); + rt2800_rfcsr_write(rt2x00dev, 40, 0x0b); + rt2800_rfcsr_write(rt2x00dev, 41, 0xbb); + rt2800_rfcsr_write(rt2x00dev, 42, 0xd5); + rt2800_rfcsr_write(rt2x00dev, 43, 0x7b); + rt2800_rfcsr_write(rt2x00dev, 44, 0x0e); + rt2800_rfcsr_write(rt2x00dev, 45, 0xa2); + rt2800_rfcsr_write(rt2x00dev, 46, 0x73); + rt2800_rfcsr_write(rt2x00dev, 47, 0x00); + rt2800_rfcsr_write(rt2x00dev, 48, 0x10); + rt2800_rfcsr_write(rt2x00dev, 49, 0x98); + rt2800_rfcsr_write(rt2x00dev, 52, 0x38); + rt2800_rfcsr_write(rt2x00dev, 53, 0x00); + rt2800_rfcsr_write(rt2x00dev, 54, 0x78); + rt2800_rfcsr_write(rt2x00dev, 55, 0x43); + rt2800_rfcsr_write(rt2x00dev, 56, 0x02); + rt2800_rfcsr_write(rt2x00dev, 57, 0x80); + rt2800_rfcsr_write(rt2x00dev, 58, 0x7f); + rt2800_rfcsr_write(rt2x00dev, 59, 0x09); + rt2800_rfcsr_write(rt2x00dev, 60, 0x45); + rt2800_rfcsr_write(rt2x00dev, 61, 0xc1); + + rt2800_rfcsr_read(rt2x00dev, 29, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR29_RSSI_GAIN, 3); + rt2800_rfcsr_write(rt2x00dev, 29, rfcsr); + + rt2800_led_open_drain_enable(rt2x00dev); + rt2800_normal_mode_setup_3xxx(rt2x00dev); +} + +static void rt2800_init_rfcsr_3352(struct rt2x00_dev *rt2x00dev) +{ + rt2800_rf_init_calibration(rt2x00dev, 30); + + rt2800_rfcsr_write(rt2x00dev, 0, 0xf0); + rt2800_rfcsr_write(rt2x00dev, 1, 0x23); + rt2800_rfcsr_write(rt2x00dev, 2, 0x50); + rt2800_rfcsr_write(rt2x00dev, 3, 0x18); + rt2800_rfcsr_write(rt2x00dev, 4, 0x00); + rt2800_rfcsr_write(rt2x00dev, 5, 0x00); + rt2800_rfcsr_write(rt2x00dev, 6, 0x33); + rt2800_rfcsr_write(rt2x00dev, 7, 0x00); + rt2800_rfcsr_write(rt2x00dev, 8, 0xf1); + rt2800_rfcsr_write(rt2x00dev, 9, 0x02); + rt2800_rfcsr_write(rt2x00dev, 10, 0xd2); + rt2800_rfcsr_write(rt2x00dev, 11, 0x42); + rt2800_rfcsr_write(rt2x00dev, 12, 0x1c); + rt2800_rfcsr_write(rt2x00dev, 13, 0x00); + rt2800_rfcsr_write(rt2x00dev, 14, 0x5a); + rt2800_rfcsr_write(rt2x00dev, 15, 0x00); + rt2800_rfcsr_write(rt2x00dev, 16, 0x01); + rt2800_rfcsr_write(rt2x00dev, 18, 0x45); + rt2800_rfcsr_write(rt2x00dev, 19, 0x02); + rt2800_rfcsr_write(rt2x00dev, 20, 0x00); + rt2800_rfcsr_write(rt2x00dev, 21, 0x00); + rt2800_rfcsr_write(rt2x00dev, 22, 0x00); + rt2800_rfcsr_write(rt2x00dev, 23, 0x00); + rt2800_rfcsr_write(rt2x00dev, 24, 0x00); + rt2800_rfcsr_write(rt2x00dev, 25, 0x80); + rt2800_rfcsr_write(rt2x00dev, 26, 0x00); + rt2800_rfcsr_write(rt2x00dev, 27, 0x03); + rt2800_rfcsr_write(rt2x00dev, 28, 0x03); + rt2800_rfcsr_write(rt2x00dev, 29, 0x00); + rt2800_rfcsr_write(rt2x00dev, 30, 0x10); + rt2800_rfcsr_write(rt2x00dev, 31, 0x80); + rt2800_rfcsr_write(rt2x00dev, 32, 0x80); + rt2800_rfcsr_write(rt2x00dev, 33, 0x00); + rt2800_rfcsr_write(rt2x00dev, 34, 0x01); + rt2800_rfcsr_write(rt2x00dev, 35, 0x03); + rt2800_rfcsr_write(rt2x00dev, 36, 0xbd); + rt2800_rfcsr_write(rt2x00dev, 37, 0x3c); + rt2800_rfcsr_write(rt2x00dev, 38, 0x5f); + rt2800_rfcsr_write(rt2x00dev, 39, 0xc5); + rt2800_rfcsr_write(rt2x00dev, 40, 0x33); + rt2800_rfcsr_write(rt2x00dev, 41, 0x5b); + rt2800_rfcsr_write(rt2x00dev, 42, 0x5b); + rt2800_rfcsr_write(rt2x00dev, 43, 0xdb); + rt2800_rfcsr_write(rt2x00dev, 44, 0xdb); + rt2800_rfcsr_write(rt2x00dev, 45, 0xdb); + rt2800_rfcsr_write(rt2x00dev, 46, 0xdd); + rt2800_rfcsr_write(rt2x00dev, 47, 0x0d); + rt2800_rfcsr_write(rt2x00dev, 48, 0x14); + rt2800_rfcsr_write(rt2x00dev, 49, 0x00); + rt2800_rfcsr_write(rt2x00dev, 50, 0x2d); + rt2800_rfcsr_write(rt2x00dev, 51, 0x7f); + rt2800_rfcsr_write(rt2x00dev, 52, 0x00); + rt2800_rfcsr_write(rt2x00dev, 53, 0x52); + rt2800_rfcsr_write(rt2x00dev, 54, 0x1b); + rt2800_rfcsr_write(rt2x00dev, 55, 0x7f); + rt2800_rfcsr_write(rt2x00dev, 56, 0x00); + rt2800_rfcsr_write(rt2x00dev, 57, 0x52); + rt2800_rfcsr_write(rt2x00dev, 58, 0x1b); + rt2800_rfcsr_write(rt2x00dev, 59, 0x00); + rt2800_rfcsr_write(rt2x00dev, 60, 0x00); + rt2800_rfcsr_write(rt2x00dev, 61, 0x00); + rt2800_rfcsr_write(rt2x00dev, 62, 0x00); + rt2800_rfcsr_write(rt2x00dev, 63, 0x00); + + rt2800_rx_filter_calibration(rt2x00dev); + rt2800_led_open_drain_enable(rt2x00dev); + rt2800_normal_mode_setup_3xxx(rt2x00dev); +} + +static void rt2800_init_rfcsr_3390(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + rt2800_rf_init_calibration(rt2x00dev, 30); + + rt2800_rfcsr_write(rt2x00dev, 0, 0xa0); + rt2800_rfcsr_write(rt2x00dev, 1, 0xe1); + rt2800_rfcsr_write(rt2x00dev, 2, 0xf1); + rt2800_rfcsr_write(rt2x00dev, 3, 0x62); + rt2800_rfcsr_write(rt2x00dev, 4, 0x40); + rt2800_rfcsr_write(rt2x00dev, 5, 0x8b); + rt2800_rfcsr_write(rt2x00dev, 6, 0x42); + rt2800_rfcsr_write(rt2x00dev, 7, 0x34); + rt2800_rfcsr_write(rt2x00dev, 8, 0x00); + rt2800_rfcsr_write(rt2x00dev, 9, 0xc0); + rt2800_rfcsr_write(rt2x00dev, 10, 0x61); + rt2800_rfcsr_write(rt2x00dev, 11, 0x21); + rt2800_rfcsr_write(rt2x00dev, 12, 0x3b); + rt2800_rfcsr_write(rt2x00dev, 13, 0xe0); + rt2800_rfcsr_write(rt2x00dev, 14, 0x90); + rt2800_rfcsr_write(rt2x00dev, 15, 0x53); + rt2800_rfcsr_write(rt2x00dev, 16, 0xe0); + rt2800_rfcsr_write(rt2x00dev, 17, 0x94); + rt2800_rfcsr_write(rt2x00dev, 18, 0x5c); + rt2800_rfcsr_write(rt2x00dev, 19, 0x4a); + rt2800_rfcsr_write(rt2x00dev, 20, 0xb2); + rt2800_rfcsr_write(rt2x00dev, 21, 0xf6); + rt2800_rfcsr_write(rt2x00dev, 22, 0x00); + rt2800_rfcsr_write(rt2x00dev, 23, 0x14); + rt2800_rfcsr_write(rt2x00dev, 24, 0x08); + rt2800_rfcsr_write(rt2x00dev, 25, 0x3d); + rt2800_rfcsr_write(rt2x00dev, 26, 0x85); + rt2800_rfcsr_write(rt2x00dev, 27, 0x00); + rt2800_rfcsr_write(rt2x00dev, 28, 0x41); + rt2800_rfcsr_write(rt2x00dev, 29, 0x8f); + rt2800_rfcsr_write(rt2x00dev, 30, 0x20); + rt2800_rfcsr_write(rt2x00dev, 31, 0x0f); + + rt2800_register_read(rt2x00dev, GPIO_SWITCH, ®); + rt2x00_set_field32(®, GPIO_SWITCH_5, 0); + rt2800_register_write(rt2x00dev, GPIO_SWITCH, reg); + + rt2800_rx_filter_calibration(rt2x00dev); + + if (rt2x00_rt_rev_lt(rt2x00dev, RT3390, REV_RT3390E)) + rt2800_rfcsr_write(rt2x00dev, 27, 0x03); + + rt2800_led_open_drain_enable(rt2x00dev); + rt2800_normal_mode_setup_3xxx(rt2x00dev); +} + +static void rt2800_init_rfcsr_3572(struct rt2x00_dev *rt2x00dev) +{ + u8 rfcsr; + u32 reg; + + rt2800_rf_init_calibration(rt2x00dev, 30); + + rt2800_rfcsr_write(rt2x00dev, 0, 0x70); + rt2800_rfcsr_write(rt2x00dev, 1, 0x81); + rt2800_rfcsr_write(rt2x00dev, 2, 0xf1); + rt2800_rfcsr_write(rt2x00dev, 3, 0x02); + rt2800_rfcsr_write(rt2x00dev, 4, 0x4c); + rt2800_rfcsr_write(rt2x00dev, 5, 0x05); + rt2800_rfcsr_write(rt2x00dev, 6, 0x4a); + rt2800_rfcsr_write(rt2x00dev, 7, 0xd8); + rt2800_rfcsr_write(rt2x00dev, 9, 0xc3); + rt2800_rfcsr_write(rt2x00dev, 10, 0xf1); + rt2800_rfcsr_write(rt2x00dev, 11, 0xb9); + rt2800_rfcsr_write(rt2x00dev, 12, 0x70); + rt2800_rfcsr_write(rt2x00dev, 13, 0x65); + rt2800_rfcsr_write(rt2x00dev, 14, 0xa0); + rt2800_rfcsr_write(rt2x00dev, 15, 0x53); + rt2800_rfcsr_write(rt2x00dev, 16, 0x4c); + rt2800_rfcsr_write(rt2x00dev, 17, 0x23); + rt2800_rfcsr_write(rt2x00dev, 18, 0xac); + rt2800_rfcsr_write(rt2x00dev, 19, 0x93); + rt2800_rfcsr_write(rt2x00dev, 20, 0xb3); + rt2800_rfcsr_write(rt2x00dev, 21, 0xd0); + rt2800_rfcsr_write(rt2x00dev, 22, 0x00); + rt2800_rfcsr_write(rt2x00dev, 23, 0x3c); + rt2800_rfcsr_write(rt2x00dev, 24, 0x16); + rt2800_rfcsr_write(rt2x00dev, 25, 0x15); + rt2800_rfcsr_write(rt2x00dev, 26, 0x85); + rt2800_rfcsr_write(rt2x00dev, 27, 0x00); + rt2800_rfcsr_write(rt2x00dev, 28, 0x00); + rt2800_rfcsr_write(rt2x00dev, 29, 0x9b); + rt2800_rfcsr_write(rt2x00dev, 30, 0x09); + rt2800_rfcsr_write(rt2x00dev, 31, 0x10); + + rt2800_rfcsr_read(rt2x00dev, 6, &rfcsr); + rt2x00_set_field8(&rfcsr, RFCSR6_R2, 1); + rt2800_rfcsr_write(rt2x00dev, 6, rfcsr); + + rt2800_register_read(rt2x00dev, LDO_CFG0, ®); + rt2x00_set_field32(®, LDO_CFG0_LDO_CORE_VLEVEL, 3); + rt2x00_set_field32(®, LDO_CFG0_BGSEL, 1); + rt2800_register_write(rt2x00dev, LDO_CFG0, reg); + msleep(1); + rt2800_register_read(rt2x00dev, LDO_CFG0, ®); + rt2x00_set_field32(®, LDO_CFG0_LDO_CORE_VLEVEL, 0); + rt2x00_set_field32(®, LDO_CFG0_BGSEL, 1); + rt2800_register_write(rt2x00dev, LDO_CFG0, reg); + + rt2800_rx_filter_calibration(rt2x00dev); + rt2800_led_open_drain_enable(rt2x00dev); + rt2800_normal_mode_setup_3xxx(rt2x00dev); +} + +static void rt2800_init_rfcsr_5390(struct rt2x00_dev *rt2x00dev) +{ + rt2800_rf_init_calibration(rt2x00dev, 2); + + rt2800_rfcsr_write(rt2x00dev, 1, 0x0f); + rt2800_rfcsr_write(rt2x00dev, 2, 0x80); + rt2800_rfcsr_write(rt2x00dev, 3, 0x88); + rt2800_rfcsr_write(rt2x00dev, 5, 0x10); + if (rt2x00_rt_rev_gte(rt2x00dev, RT5390, REV_RT5390F)) + rt2800_rfcsr_write(rt2x00dev, 6, 0xe0); + else + rt2800_rfcsr_write(rt2x00dev, 6, 0xa0); + rt2800_rfcsr_write(rt2x00dev, 7, 0x00); + rt2800_rfcsr_write(rt2x00dev, 10, 0x53); + rt2800_rfcsr_write(rt2x00dev, 11, 0x4a); + rt2800_rfcsr_write(rt2x00dev, 12, 0xc6); + rt2800_rfcsr_write(rt2x00dev, 13, 0x9f); + rt2800_rfcsr_write(rt2x00dev, 14, 0x00); + rt2800_rfcsr_write(rt2x00dev, 15, 0x00); + rt2800_rfcsr_write(rt2x00dev, 16, 0x00); + rt2800_rfcsr_write(rt2x00dev, 18, 0x03); + rt2800_rfcsr_write(rt2x00dev, 19, 0x00); + + rt2800_rfcsr_write(rt2x00dev, 20, 0x00); + rt2800_rfcsr_write(rt2x00dev, 21, 0x00); + rt2800_rfcsr_write(rt2x00dev, 22, 0x20); + rt2800_rfcsr_write(rt2x00dev, 23, 0x00); + rt2800_rfcsr_write(rt2x00dev, 24, 0x00); + if (rt2x00_rt_rev_gte(rt2x00dev, RT5390, REV_RT5390F)) + rt2800_rfcsr_write(rt2x00dev, 25, 0x80); + else + rt2800_rfcsr_write(rt2x00dev, 25, 0xc0); + rt2800_rfcsr_write(rt2x00dev, 26, 0x00); + rt2800_rfcsr_write(rt2x00dev, 27, 0x09); + rt2800_rfcsr_write(rt2x00dev, 28, 0x00); + rt2800_rfcsr_write(rt2x00dev, 29, 0x10); + + rt2800_rfcsr_write(rt2x00dev, 30, 0x00); + rt2800_rfcsr_write(rt2x00dev, 31, 0x80); + rt2800_rfcsr_write(rt2x00dev, 32, 0x80); + rt2800_rfcsr_write(rt2x00dev, 33, 0x00); + rt2800_rfcsr_write(rt2x00dev, 34, 0x07); + rt2800_rfcsr_write(rt2x00dev, 35, 0x12); + rt2800_rfcsr_write(rt2x00dev, 36, 0x00); + rt2800_rfcsr_write(rt2x00dev, 37, 0x08); + rt2800_rfcsr_write(rt2x00dev, 38, 0x85); + rt2800_rfcsr_write(rt2x00dev, 39, 0x1b); + + if (rt2x00_rt_rev_gte(rt2x00dev, RT5390, REV_RT5390F)) + rt2800_rfcsr_write(rt2x00dev, 40, 0x0b); + else + rt2800_rfcsr_write(rt2x00dev, 40, 0x4b); + rt2800_rfcsr_write(rt2x00dev, 41, 0xbb); + rt2800_rfcsr_write(rt2x00dev, 42, 0xd2); + rt2800_rfcsr_write(rt2x00dev, 43, 0x9a); + rt2800_rfcsr_write(rt2x00dev, 44, 0x0e); + rt2800_rfcsr_write(rt2x00dev, 45, 0xa2); + if (rt2x00_rt_rev_gte(rt2x00dev, RT5390, REV_RT5390F)) + rt2800_rfcsr_write(rt2x00dev, 46, 0x73); + else + rt2800_rfcsr_write(rt2x00dev, 46, 0x7b); + rt2800_rfcsr_write(rt2x00dev, 47, 0x00); + rt2800_rfcsr_write(rt2x00dev, 48, 0x10); + rt2800_rfcsr_write(rt2x00dev, 49, 0x94); + + rt2800_rfcsr_write(rt2x00dev, 52, 0x38); + if (rt2x00_rt_rev_gte(rt2x00dev, RT5390, REV_RT5390F)) + rt2800_rfcsr_write(rt2x00dev, 53, 0x00); + else + rt2800_rfcsr_write(rt2x00dev, 53, 0x84); + rt2800_rfcsr_write(rt2x00dev, 54, 0x78); + rt2800_rfcsr_write(rt2x00dev, 55, 0x44); + rt2800_rfcsr_write(rt2x00dev, 56, 0x22); + rt2800_rfcsr_write(rt2x00dev, 57, 0x80); + rt2800_rfcsr_write(rt2x00dev, 58, 0x7f); + rt2800_rfcsr_write(rt2x00dev, 59, 0x63); + + rt2800_rfcsr_write(rt2x00dev, 60, 0x45); + if (rt2x00_rt_rev_gte(rt2x00dev, RT5390, REV_RT5390F)) + rt2800_rfcsr_write(rt2x00dev, 61, 0xd1); + else + rt2800_rfcsr_write(rt2x00dev, 61, 0xdd); + rt2800_rfcsr_write(rt2x00dev, 62, 0x00); + rt2800_rfcsr_write(rt2x00dev, 63, 0x00); + + rt2800_normal_mode_setup_5xxx(rt2x00dev); + + rt2800_led_open_drain_enable(rt2x00dev); +} + +static void rt2800_init_rfcsr_5392(struct rt2x00_dev *rt2x00dev) +{ + rt2800_rf_init_calibration(rt2x00dev, 2); + + rt2800_rfcsr_write(rt2x00dev, 1, 0x17); + rt2800_rfcsr_write(rt2x00dev, 2, 0x80); + rt2800_rfcsr_write(rt2x00dev, 3, 0x88); + rt2800_rfcsr_write(rt2x00dev, 5, 0x10); + rt2800_rfcsr_write(rt2x00dev, 6, 0xe0); + rt2800_rfcsr_write(rt2x00dev, 7, 0x00); + rt2800_rfcsr_write(rt2x00dev, 10, 0x53); + rt2800_rfcsr_write(rt2x00dev, 11, 0x4a); + rt2800_rfcsr_write(rt2x00dev, 12, 0x46); + rt2800_rfcsr_write(rt2x00dev, 13, 0x9f); + rt2800_rfcsr_write(rt2x00dev, 14, 0x00); + rt2800_rfcsr_write(rt2x00dev, 15, 0x00); + rt2800_rfcsr_write(rt2x00dev, 16, 0x00); + rt2800_rfcsr_write(rt2x00dev, 18, 0x03); + rt2800_rfcsr_write(rt2x00dev, 19, 0x4d); + rt2800_rfcsr_write(rt2x00dev, 20, 0x00); + rt2800_rfcsr_write(rt2x00dev, 21, 0x8d); + rt2800_rfcsr_write(rt2x00dev, 22, 0x20); + rt2800_rfcsr_write(rt2x00dev, 23, 0x0b); + rt2800_rfcsr_write(rt2x00dev, 24, 0x44); + rt2800_rfcsr_write(rt2x00dev, 25, 0x80); + rt2800_rfcsr_write(rt2x00dev, 26, 0x82); + rt2800_rfcsr_write(rt2x00dev, 27, 0x09); + rt2800_rfcsr_write(rt2x00dev, 28, 0x00); + rt2800_rfcsr_write(rt2x00dev, 29, 0x10); + rt2800_rfcsr_write(rt2x00dev, 30, 0x10); + rt2800_rfcsr_write(rt2x00dev, 31, 0x80); + rt2800_rfcsr_write(rt2x00dev, 32, 0x20); + rt2800_rfcsr_write(rt2x00dev, 33, 0xC0); + rt2800_rfcsr_write(rt2x00dev, 34, 0x07); + rt2800_rfcsr_write(rt2x00dev, 35, 0x12); + rt2800_rfcsr_write(rt2x00dev, 36, 0x00); + rt2800_rfcsr_write(rt2x00dev, 37, 0x08); + rt2800_rfcsr_write(rt2x00dev, 38, 0x89); + rt2800_rfcsr_write(rt2x00dev, 39, 0x1b); + rt2800_rfcsr_write(rt2x00dev, 40, 0x0f); + rt2800_rfcsr_write(rt2x00dev, 41, 0xbb); + rt2800_rfcsr_write(rt2x00dev, 42, 0xd5); + rt2800_rfcsr_write(rt2x00dev, 43, 0x9b); + rt2800_rfcsr_write(rt2x00dev, 44, 0x0e); + rt2800_rfcsr_write(rt2x00dev, 45, 0xa2); + rt2800_rfcsr_write(rt2x00dev, 46, 0x73); + rt2800_rfcsr_write(rt2x00dev, 47, 0x0c); + rt2800_rfcsr_write(rt2x00dev, 48, 0x10); + rt2800_rfcsr_write(rt2x00dev, 49, 0x94); + rt2800_rfcsr_write(rt2x00dev, 50, 0x94); + rt2800_rfcsr_write(rt2x00dev, 51, 0x3a); + rt2800_rfcsr_write(rt2x00dev, 52, 0x48); + rt2800_rfcsr_write(rt2x00dev, 53, 0x44); + rt2800_rfcsr_write(rt2x00dev, 54, 0x38); + rt2800_rfcsr_write(rt2x00dev, 55, 0x43); + rt2800_rfcsr_write(rt2x00dev, 56, 0xa1); + rt2800_rfcsr_write(rt2x00dev, 57, 0x00); + rt2800_rfcsr_write(rt2x00dev, 58, 0x39); + rt2800_rfcsr_write(rt2x00dev, 59, 0x07); + rt2800_rfcsr_write(rt2x00dev, 60, 0x45); + rt2800_rfcsr_write(rt2x00dev, 61, 0x91); + rt2800_rfcsr_write(rt2x00dev, 62, 0x39); + rt2800_rfcsr_write(rt2x00dev, 63, 0x07); + + rt2800_normal_mode_setup_5xxx(rt2x00dev); + + rt2800_led_open_drain_enable(rt2x00dev); +} + +static void rt2800_init_rfcsr_5592(struct rt2x00_dev *rt2x00dev) +{ + rt2800_rf_init_calibration(rt2x00dev, 30); + + rt2800_rfcsr_write(rt2x00dev, 1, 0x3F); + rt2800_rfcsr_write(rt2x00dev, 3, 0x08); + rt2800_rfcsr_write(rt2x00dev, 3, 0x08); + rt2800_rfcsr_write(rt2x00dev, 5, 0x10); + rt2800_rfcsr_write(rt2x00dev, 6, 0xE4); + rt2800_rfcsr_write(rt2x00dev, 7, 0x00); + rt2800_rfcsr_write(rt2x00dev, 14, 0x00); + rt2800_rfcsr_write(rt2x00dev, 15, 0x00); + rt2800_rfcsr_write(rt2x00dev, 16, 0x00); + rt2800_rfcsr_write(rt2x00dev, 18, 0x03); + rt2800_rfcsr_write(rt2x00dev, 19, 0x4D); + rt2800_rfcsr_write(rt2x00dev, 20, 0x10); + rt2800_rfcsr_write(rt2x00dev, 21, 0x8D); + rt2800_rfcsr_write(rt2x00dev, 26, 0x82); + rt2800_rfcsr_write(rt2x00dev, 28, 0x00); + rt2800_rfcsr_write(rt2x00dev, 29, 0x10); + rt2800_rfcsr_write(rt2x00dev, 33, 0xC0); + rt2800_rfcsr_write(rt2x00dev, 34, 0x07); + rt2800_rfcsr_write(rt2x00dev, 35, 0x12); + rt2800_rfcsr_write(rt2x00dev, 47, 0x0C); + rt2800_rfcsr_write(rt2x00dev, 53, 0x22); + rt2800_rfcsr_write(rt2x00dev, 63, 0x07); + + rt2800_rfcsr_write(rt2x00dev, 2, 0x80); + msleep(1); + + rt2800_adjust_freq_offset(rt2x00dev); + + /* Enable DC filter */ + if (rt2x00_rt_rev_gte(rt2x00dev, RT5592, REV_RT5592C)) + rt2800_bbp_write(rt2x00dev, 103, 0xc0); + + rt2800_normal_mode_setup_5xxx(rt2x00dev); + + if (rt2x00_rt_rev_lt(rt2x00dev, RT5592, REV_RT5592C)) + rt2800_rfcsr_write(rt2x00dev, 27, 0x03); + + rt2800_led_open_drain_enable(rt2x00dev); +} + +static void rt2800_init_rfcsr(struct rt2x00_dev *rt2x00dev) +{ + if (rt2800_is_305x_soc(rt2x00dev)) { + rt2800_init_rfcsr_305x_soc(rt2x00dev); + return; + } + + switch (rt2x00dev->chip.rt) { + case RT3070: + case RT3071: + case RT3090: + rt2800_init_rfcsr_30xx(rt2x00dev); + break; + case RT3290: + rt2800_init_rfcsr_3290(rt2x00dev); + break; + case RT3352: + rt2800_init_rfcsr_3352(rt2x00dev); + break; + case RT3390: + rt2800_init_rfcsr_3390(rt2x00dev); + break; + case RT3572: + rt2800_init_rfcsr_3572(rt2x00dev); + break; + case RT5390: + rt2800_init_rfcsr_5390(rt2x00dev); + break; + case RT5392: + rt2800_init_rfcsr_5392(rt2x00dev); + break; + case RT5592: + rt2800_init_rfcsr_5592(rt2x00dev); + break; + } +} + +int rt2800_enable_radio(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + u16 word; + + /* + * Initialize all registers. + */ + if (unlikely(rt2800_wait_wpdma_ready(rt2x00dev) || + rt2800_init_registers(rt2x00dev))) + return -EIO; + + if (unlikely(rt2800_wait_bbp_rf_ready(rt2x00dev))) + return -EIO; + + /* + * Send signal to firmware during boot time. + */ + rt2800_register_write(rt2x00dev, H2M_BBP_AGENT, 0); + rt2800_register_write(rt2x00dev, H2M_MAILBOX_CSR, 0); + if (rt2x00_is_usb(rt2x00dev)) + rt2800_register_write(rt2x00dev, H2M_INT_SRC, 0); + rt2800_mcu_request(rt2x00dev, MCU_BOOT_SIGNAL, 0, 0, 0); + msleep(1); + + if (unlikely(rt2800_wait_bbp_ready(rt2x00dev))) + return -EIO; + + rt2800_init_bbp(rt2x00dev); + rt2800_init_rfcsr(rt2x00dev); + + if (rt2x00_is_usb(rt2x00dev) && + (rt2x00_rt(rt2x00dev, RT3070) || + rt2x00_rt(rt2x00dev, RT3071) || + rt2x00_rt(rt2x00dev, RT3572))) { + udelay(200); + rt2800_mcu_request(rt2x00dev, MCU_CURRENT, 0, 0, 0); + udelay(10); + } + + /* + * Enable RX. + */ + rt2800_register_read(rt2x00dev, MAC_SYS_CTRL, ®); + rt2x00_set_field32(®, MAC_SYS_CTRL_ENABLE_TX, 1); + rt2x00_set_field32(®, MAC_SYS_CTRL_ENABLE_RX, 0); + rt2800_register_write(rt2x00dev, MAC_SYS_CTRL, reg); + + udelay(50); + + rt2800_register_read(rt2x00dev, WPDMA_GLO_CFG, ®); + rt2x00_set_field32(®, WPDMA_GLO_CFG_ENABLE_TX_DMA, 1); + rt2x00_set_field32(®, WPDMA_GLO_CFG_ENABLE_RX_DMA, 1); + rt2x00_set_field32(®, WPDMA_GLO_CFG_WP_DMA_BURST_SIZE, 2); + rt2x00_set_field32(®, WPDMA_GLO_CFG_TX_WRITEBACK_DONE, 1); + rt2800_register_write(rt2x00dev, WPDMA_GLO_CFG, reg); + + rt2800_register_read(rt2x00dev, MAC_SYS_CTRL, ®); + rt2x00_set_field32(®, MAC_SYS_CTRL_ENABLE_TX, 1); + rt2x00_set_field32(®, MAC_SYS_CTRL_ENABLE_RX, 1); + rt2800_register_write(rt2x00dev, MAC_SYS_CTRL, reg); + + /* + * Initialize LED control + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_LED_AG_CONF, &word); + rt2800_mcu_request(rt2x00dev, MCU_LED_AG_CONF, 0xff, + word & 0xff, (word >> 8) & 0xff); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_LED_ACT_CONF, &word); + rt2800_mcu_request(rt2x00dev, MCU_LED_ACT_CONF, 0xff, + word & 0xff, (word >> 8) & 0xff); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_LED_POLARITY, &word); + rt2800_mcu_request(rt2x00dev, MCU_LED_LED_POLARITY, 0xff, + word & 0xff, (word >> 8) & 0xff); + + return 0; +} +EXPORT_SYMBOL_GPL(rt2800_enable_radio); + +void rt2800_disable_radio(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + rt2800_disable_wpdma(rt2x00dev); + + /* Wait for DMA, ignore error */ + rt2800_wait_wpdma_ready(rt2x00dev); + + rt2800_register_read(rt2x00dev, MAC_SYS_CTRL, ®); + rt2x00_set_field32(®, MAC_SYS_CTRL_ENABLE_TX, 0); + rt2x00_set_field32(®, MAC_SYS_CTRL_ENABLE_RX, 0); + rt2800_register_write(rt2x00dev, MAC_SYS_CTRL, reg); +} +EXPORT_SYMBOL_GPL(rt2800_disable_radio); + +int rt2800_efuse_detect(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + u16 efuse_ctrl_reg; + + if (rt2x00_rt(rt2x00dev, RT3290)) + efuse_ctrl_reg = EFUSE_CTRL_3290; + else + efuse_ctrl_reg = EFUSE_CTRL; + + rt2800_register_read(rt2x00dev, efuse_ctrl_reg, ®); + return rt2x00_get_field32(reg, EFUSE_CTRL_PRESENT); +} +EXPORT_SYMBOL_GPL(rt2800_efuse_detect); + +static void rt2800_efuse_read(struct rt2x00_dev *rt2x00dev, unsigned int i) +{ + u32 reg; + u16 efuse_ctrl_reg; + u16 efuse_data0_reg; + u16 efuse_data1_reg; + u16 efuse_data2_reg; + u16 efuse_data3_reg; + + if (rt2x00_rt(rt2x00dev, RT3290)) { + efuse_ctrl_reg = EFUSE_CTRL_3290; + efuse_data0_reg = EFUSE_DATA0_3290; + efuse_data1_reg = EFUSE_DATA1_3290; + efuse_data2_reg = EFUSE_DATA2_3290; + efuse_data3_reg = EFUSE_DATA3_3290; + } else { + efuse_ctrl_reg = EFUSE_CTRL; + efuse_data0_reg = EFUSE_DATA0; + efuse_data1_reg = EFUSE_DATA1; + efuse_data2_reg = EFUSE_DATA2; + efuse_data3_reg = EFUSE_DATA3; + } + mutex_lock(&rt2x00dev->csr_mutex); + + rt2800_register_read_lock(rt2x00dev, efuse_ctrl_reg, ®); + rt2x00_set_field32(®, EFUSE_CTRL_ADDRESS_IN, i); + rt2x00_set_field32(®, EFUSE_CTRL_MODE, 0); + rt2x00_set_field32(®, EFUSE_CTRL_KICK, 1); + rt2800_register_write_lock(rt2x00dev, efuse_ctrl_reg, reg); + + /* Wait until the EEPROM has been loaded */ + rt2800_regbusy_read(rt2x00dev, efuse_ctrl_reg, EFUSE_CTRL_KICK, ®); + /* Apparently the data is read from end to start */ + rt2800_register_read_lock(rt2x00dev, efuse_data3_reg, ®); + /* The returned value is in CPU order, but eeprom is le */ + *(u32 *)&rt2x00dev->eeprom[i] = cpu_to_le32(reg); + rt2800_register_read_lock(rt2x00dev, efuse_data2_reg, ®); + *(u32 *)&rt2x00dev->eeprom[i + 2] = cpu_to_le32(reg); + rt2800_register_read_lock(rt2x00dev, efuse_data1_reg, ®); + *(u32 *)&rt2x00dev->eeprom[i + 4] = cpu_to_le32(reg); + rt2800_register_read_lock(rt2x00dev, efuse_data0_reg, ®); + *(u32 *)&rt2x00dev->eeprom[i + 6] = cpu_to_le32(reg); + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +int rt2800_read_eeprom_efuse(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i; + + for (i = 0; i < EEPROM_SIZE / sizeof(u16); i += 8) + rt2800_efuse_read(rt2x00dev, i); + + return 0; +} +EXPORT_SYMBOL_GPL(rt2800_read_eeprom_efuse); + +static int rt2800_validate_eeprom(struct rt2x00_dev *rt2x00dev) +{ + struct rt2800_drv_data *drv_data = rt2x00dev->drv_data; + u16 word; + u8 *mac; + u8 default_lna_gain; + int retval; + + /* + * Read the EEPROM. + */ + retval = rt2800_read_eeprom(rt2x00dev); + if (retval) + return retval; + + /* + * Start validation of the data that has been read. + */ + mac = rt2x00_eeprom_addr(rt2x00dev, EEPROM_MAC_ADDR_0); + if (!is_valid_ether_addr(mac)) { + eth_random_addr(mac); + rt2x00_eeprom_dbg(rt2x00dev, "MAC: %pM\n", mac); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC_CONF0, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_NIC_CONF0_RXPATH, 2); + rt2x00_set_field16(&word, EEPROM_NIC_CONF0_TXPATH, 1); + rt2x00_set_field16(&word, EEPROM_NIC_CONF0_RF_TYPE, RF2820); + rt2x00_eeprom_write(rt2x00dev, EEPROM_NIC_CONF0, word); + rt2x00_eeprom_dbg(rt2x00dev, "Antenna: 0x%04x\n", word); + } else if (rt2x00_rt(rt2x00dev, RT2860) || + rt2x00_rt(rt2x00dev, RT2872)) { + /* + * There is a max of 2 RX streams for RT28x0 series + */ + if (rt2x00_get_field16(word, EEPROM_NIC_CONF0_RXPATH) > 2) + rt2x00_set_field16(&word, EEPROM_NIC_CONF0_RXPATH, 2); + rt2x00_eeprom_write(rt2x00dev, EEPROM_NIC_CONF0, word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC_CONF1, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_NIC_CONF1_HW_RADIO, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CONF1_EXTERNAL_TX_ALC, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CONF1_EXTERNAL_LNA_2G, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CONF1_EXTERNAL_LNA_5G, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CONF1_CARDBUS_ACCEL, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CONF1_BW40M_SB_2G, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CONF1_BW40M_SB_5G, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CONF1_WPS_PBC, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CONF1_BW40M_2G, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CONF1_BW40M_5G, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CONF1_BROADBAND_EXT_LNA, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CONF1_ANT_DIVERSITY, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CONF1_INTERNAL_TX_ALC, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CONF1_BT_COEXIST, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CONF1_DAC_TEST, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_NIC_CONF1, word); + rt2x00_eeprom_dbg(rt2x00dev, "NIC: 0x%04x\n", word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_FREQ, &word); + if ((word & 0x00ff) == 0x00ff) { + rt2x00_set_field16(&word, EEPROM_FREQ_OFFSET, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_FREQ, word); + rt2x00_eeprom_dbg(rt2x00dev, "Freq: 0x%04x\n", word); + } + if ((word & 0xff00) == 0xff00) { + rt2x00_set_field16(&word, EEPROM_FREQ_LED_MODE, + LED_MODE_TXRX_ACTIVITY); + rt2x00_set_field16(&word, EEPROM_FREQ_LED_POLARITY, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_FREQ, word); + rt2x00_eeprom_write(rt2x00dev, EEPROM_LED_AG_CONF, 0x5555); + rt2x00_eeprom_write(rt2x00dev, EEPROM_LED_ACT_CONF, 0x2221); + rt2x00_eeprom_write(rt2x00dev, EEPROM_LED_POLARITY, 0xa9f8); + rt2x00_eeprom_dbg(rt2x00dev, "Led Mode: 0x%04x\n", word); + } + + /* + * During the LNA validation we are going to use + * lna0 as correct value. Note that EEPROM_LNA + * is never validated. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_LNA, &word); + default_lna_gain = rt2x00_get_field16(word, EEPROM_LNA_A0); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_BG, &word); + if (abs(rt2x00_get_field16(word, EEPROM_RSSI_BG_OFFSET0)) > 10) + rt2x00_set_field16(&word, EEPROM_RSSI_BG_OFFSET0, 0); + if (abs(rt2x00_get_field16(word, EEPROM_RSSI_BG_OFFSET1)) > 10) + rt2x00_set_field16(&word, EEPROM_RSSI_BG_OFFSET1, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_RSSI_BG, word); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_TXMIXER_GAIN_BG, &word); + if ((word & 0x00ff) != 0x00ff) { + drv_data->txmixer_gain_24g = + rt2x00_get_field16(word, EEPROM_TXMIXER_GAIN_BG_VAL); + } else { + drv_data->txmixer_gain_24g = 0; + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_BG2, &word); + if (abs(rt2x00_get_field16(word, EEPROM_RSSI_BG2_OFFSET2)) > 10) + rt2x00_set_field16(&word, EEPROM_RSSI_BG2_OFFSET2, 0); + if (rt2x00_get_field16(word, EEPROM_RSSI_BG2_LNA_A1) == 0x00 || + rt2x00_get_field16(word, EEPROM_RSSI_BG2_LNA_A1) == 0xff) + rt2x00_set_field16(&word, EEPROM_RSSI_BG2_LNA_A1, + default_lna_gain); + rt2x00_eeprom_write(rt2x00dev, EEPROM_RSSI_BG2, word); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_TXMIXER_GAIN_A, &word); + if ((word & 0x00ff) != 0x00ff) { + drv_data->txmixer_gain_5g = + rt2x00_get_field16(word, EEPROM_TXMIXER_GAIN_A_VAL); + } else { + drv_data->txmixer_gain_5g = 0; + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_A, &word); + if (abs(rt2x00_get_field16(word, EEPROM_RSSI_A_OFFSET0)) > 10) + rt2x00_set_field16(&word, EEPROM_RSSI_A_OFFSET0, 0); + if (abs(rt2x00_get_field16(word, EEPROM_RSSI_A_OFFSET1)) > 10) + rt2x00_set_field16(&word, EEPROM_RSSI_A_OFFSET1, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_RSSI_A, word); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_A2, &word); + if (abs(rt2x00_get_field16(word, EEPROM_RSSI_A2_OFFSET2)) > 10) + rt2x00_set_field16(&word, EEPROM_RSSI_A2_OFFSET2, 0); + if (rt2x00_get_field16(word, EEPROM_RSSI_A2_LNA_A2) == 0x00 || + rt2x00_get_field16(word, EEPROM_RSSI_A2_LNA_A2) == 0xff) + rt2x00_set_field16(&word, EEPROM_RSSI_A2_LNA_A2, + default_lna_gain); + rt2x00_eeprom_write(rt2x00dev, EEPROM_RSSI_A2, word); + + return 0; +} + +static int rt2800_init_eeprom(struct rt2x00_dev *rt2x00dev) +{ + u16 value; + u16 eeprom; + u16 rf; + + /* + * Read EEPROM word for configuration. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC_CONF0, &eeprom); + + /* + * Identify RF chipset by EEPROM value + * RT28xx/RT30xx: defined in "EEPROM_NIC_CONF0_RF_TYPE" field + * RT53xx: defined in "EEPROM_CHIP_ID" field + */ + if (rt2x00_rt(rt2x00dev, RT3290) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392)) + rt2x00_eeprom_read(rt2x00dev, EEPROM_CHIP_ID, &rf); + else + rf = rt2x00_get_field16(eeprom, EEPROM_NIC_CONF0_RF_TYPE); + + switch (rf) { + case RF2820: + case RF2850: + case RF2720: + case RF2750: + case RF3020: + case RF2020: + case RF3021: + case RF3022: + case RF3052: + case RF3290: + case RF3320: + case RF3322: + case RF5360: + case RF5370: + case RF5372: + case RF5390: + case RF5392: + case RF5592: + break; + default: + rt2x00_err(rt2x00dev, "Invalid RF chipset 0x%04x detected\n", + rf); + return -ENODEV; + } + + rt2x00_set_rf(rt2x00dev, rf); + + /* + * Identify default antenna configuration. + */ + rt2x00dev->default_ant.tx_chain_num = + rt2x00_get_field16(eeprom, EEPROM_NIC_CONF0_TXPATH); + rt2x00dev->default_ant.rx_chain_num = + rt2x00_get_field16(eeprom, EEPROM_NIC_CONF0_RXPATH); + + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC_CONF1, &eeprom); + + if (rt2x00_rt(rt2x00dev, RT3070) || + rt2x00_rt(rt2x00dev, RT3090) || + rt2x00_rt(rt2x00dev, RT3352) || + rt2x00_rt(rt2x00dev, RT3390)) { + value = rt2x00_get_field16(eeprom, + EEPROM_NIC_CONF1_ANT_DIVERSITY); + switch (value) { + case 0: + case 1: + case 2: + rt2x00dev->default_ant.tx = ANTENNA_A; + rt2x00dev->default_ant.rx = ANTENNA_A; + break; + case 3: + rt2x00dev->default_ant.tx = ANTENNA_A; + rt2x00dev->default_ant.rx = ANTENNA_B; + break; + } + } else { + rt2x00dev->default_ant.tx = ANTENNA_A; + rt2x00dev->default_ant.rx = ANTENNA_A; + } + + if (rt2x00_rt_rev_gte(rt2x00dev, RT5390, REV_RT5390R)) { + rt2x00dev->default_ant.tx = ANTENNA_HW_DIVERSITY; /* Unused */ + rt2x00dev->default_ant.rx = ANTENNA_HW_DIVERSITY; /* Unused */ + } + + /* + * Determine external LNA informations. + */ + if (rt2x00_get_field16(eeprom, EEPROM_NIC_CONF1_EXTERNAL_LNA_5G)) + __set_bit(CAPABILITY_EXTERNAL_LNA_A, &rt2x00dev->cap_flags); + if (rt2x00_get_field16(eeprom, EEPROM_NIC_CONF1_EXTERNAL_LNA_2G)) + __set_bit(CAPABILITY_EXTERNAL_LNA_BG, &rt2x00dev->cap_flags); + + /* + * Detect if this device has an hardware controlled radio. + */ + if (rt2x00_get_field16(eeprom, EEPROM_NIC_CONF1_HW_RADIO)) + __set_bit(CAPABILITY_HW_BUTTON, &rt2x00dev->cap_flags); + + /* + * Detect if this device has Bluetooth co-existence. + */ + if (rt2x00_get_field16(eeprom, EEPROM_NIC_CONF1_BT_COEXIST)) + __set_bit(CAPABILITY_BT_COEXIST, &rt2x00dev->cap_flags); + + /* + * Read frequency offset and RF programming sequence. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_FREQ, &eeprom); + rt2x00dev->freq_offset = rt2x00_get_field16(eeprom, EEPROM_FREQ_OFFSET); + + /* + * Store led settings, for correct led behaviour. + */ +#ifdef CPTCFG_RT2X00_LIB_LEDS + rt2800_init_led(rt2x00dev, &rt2x00dev->led_radio, LED_TYPE_RADIO); + rt2800_init_led(rt2x00dev, &rt2x00dev->led_assoc, LED_TYPE_ASSOC); + rt2800_init_led(rt2x00dev, &rt2x00dev->led_qual, LED_TYPE_QUALITY); + + rt2x00dev->led_mcu_reg = eeprom; +#endif /* CPTCFG_RT2X00_LIB_LEDS */ + + /* + * Check if support EIRP tx power limit feature. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_EIRP_MAX_TX_POWER, &eeprom); + + if (rt2x00_get_field16(eeprom, EEPROM_EIRP_MAX_TX_POWER_2GHZ) < + EIRP_MAX_TX_POWER_LIMIT) + __set_bit(CAPABILITY_POWER_LIMIT, &rt2x00dev->cap_flags); + + return 0; +} + +/* + * RF value list for rt28xx + * Supports: 2.4 GHz (all) & 5.2 GHz (RF2850 & RF2750) + */ +static const struct rf_channel rf_vals[] = { + { 1, 0x18402ecc, 0x184c0786, 0x1816b455, 0x1800510b }, + { 2, 0x18402ecc, 0x184c0786, 0x18168a55, 0x1800519f }, + { 3, 0x18402ecc, 0x184c078a, 0x18168a55, 0x1800518b }, + { 4, 0x18402ecc, 0x184c078a, 0x18168a55, 0x1800519f }, + { 5, 0x18402ecc, 0x184c078e, 0x18168a55, 0x1800518b }, + { 6, 0x18402ecc, 0x184c078e, 0x18168a55, 0x1800519f }, + { 7, 0x18402ecc, 0x184c0792, 0x18168a55, 0x1800518b }, + { 8, 0x18402ecc, 0x184c0792, 0x18168a55, 0x1800519f }, + { 9, 0x18402ecc, 0x184c0796, 0x18168a55, 0x1800518b }, + { 10, 0x18402ecc, 0x184c0796, 0x18168a55, 0x1800519f }, + { 11, 0x18402ecc, 0x184c079a, 0x18168a55, 0x1800518b }, + { 12, 0x18402ecc, 0x184c079a, 0x18168a55, 0x1800519f }, + { 13, 0x18402ecc, 0x184c079e, 0x18168a55, 0x1800518b }, + { 14, 0x18402ecc, 0x184c07a2, 0x18168a55, 0x18005193 }, + + /* 802.11 UNI / HyperLan 2 */ + { 36, 0x18402ecc, 0x184c099a, 0x18158a55, 0x180ed1a3 }, + { 38, 0x18402ecc, 0x184c099e, 0x18158a55, 0x180ed193 }, + { 40, 0x18402ec8, 0x184c0682, 0x18158a55, 0x180ed183 }, + { 44, 0x18402ec8, 0x184c0682, 0x18158a55, 0x180ed1a3 }, + { 46, 0x18402ec8, 0x184c0686, 0x18158a55, 0x180ed18b }, + { 48, 0x18402ec8, 0x184c0686, 0x18158a55, 0x180ed19b }, + { 52, 0x18402ec8, 0x184c068a, 0x18158a55, 0x180ed193 }, + { 54, 0x18402ec8, 0x184c068a, 0x18158a55, 0x180ed1a3 }, + { 56, 0x18402ec8, 0x184c068e, 0x18158a55, 0x180ed18b }, + { 60, 0x18402ec8, 0x184c0692, 0x18158a55, 0x180ed183 }, + { 62, 0x18402ec8, 0x184c0692, 0x18158a55, 0x180ed193 }, + { 64, 0x18402ec8, 0x184c0692, 0x18158a55, 0x180ed1a3 }, + + /* 802.11 HyperLan 2 */ + { 100, 0x18402ec8, 0x184c06b2, 0x18178a55, 0x180ed783 }, + { 102, 0x18402ec8, 0x184c06b2, 0x18578a55, 0x180ed793 }, + { 104, 0x18402ec8, 0x185c06b2, 0x18578a55, 0x180ed1a3 }, + { 108, 0x18402ecc, 0x185c0a32, 0x18578a55, 0x180ed193 }, + { 110, 0x18402ecc, 0x184c0a36, 0x18178a55, 0x180ed183 }, + { 112, 0x18402ecc, 0x184c0a36, 0x18178a55, 0x180ed19b }, + { 116, 0x18402ecc, 0x184c0a3a, 0x18178a55, 0x180ed1a3 }, + { 118, 0x18402ecc, 0x184c0a3e, 0x18178a55, 0x180ed193 }, + { 120, 0x18402ec4, 0x184c0382, 0x18178a55, 0x180ed183 }, + { 124, 0x18402ec4, 0x184c0382, 0x18178a55, 0x180ed193 }, + { 126, 0x18402ec4, 0x184c0382, 0x18178a55, 0x180ed15b }, + { 128, 0x18402ec4, 0x184c0382, 0x18178a55, 0x180ed1a3 }, + { 132, 0x18402ec4, 0x184c0386, 0x18178a55, 0x180ed18b }, + { 134, 0x18402ec4, 0x184c0386, 0x18178a55, 0x180ed193 }, + { 136, 0x18402ec4, 0x184c0386, 0x18178a55, 0x180ed19b }, + { 140, 0x18402ec4, 0x184c038a, 0x18178a55, 0x180ed183 }, + + /* 802.11 UNII */ + { 149, 0x18402ec4, 0x184c038a, 0x18178a55, 0x180ed1a7 }, + { 151, 0x18402ec4, 0x184c038e, 0x18178a55, 0x180ed187 }, + { 153, 0x18402ec4, 0x184c038e, 0x18178a55, 0x180ed18f }, + { 157, 0x18402ec4, 0x184c038e, 0x18178a55, 0x180ed19f }, + { 159, 0x18402ec4, 0x184c038e, 0x18178a55, 0x180ed1a7 }, + { 161, 0x18402ec4, 0x184c0392, 0x18178a55, 0x180ed187 }, + { 165, 0x18402ec4, 0x184c0392, 0x18178a55, 0x180ed197 }, + { 167, 0x18402ec4, 0x184c03d2, 0x18179855, 0x1815531f }, + { 169, 0x18402ec4, 0x184c03d2, 0x18179855, 0x18155327 }, + { 171, 0x18402ec4, 0x184c03d6, 0x18179855, 0x18155307 }, + { 173, 0x18402ec4, 0x184c03d6, 0x18179855, 0x1815530f }, + + /* 802.11 Japan */ + { 184, 0x15002ccc, 0x1500491e, 0x1509be55, 0x150c0a0b }, + { 188, 0x15002ccc, 0x15004922, 0x1509be55, 0x150c0a13 }, + { 192, 0x15002ccc, 0x15004926, 0x1509be55, 0x150c0a1b }, + { 196, 0x15002ccc, 0x1500492a, 0x1509be55, 0x150c0a23 }, + { 208, 0x15002ccc, 0x1500493a, 0x1509be55, 0x150c0a13 }, + { 212, 0x15002ccc, 0x1500493e, 0x1509be55, 0x150c0a1b }, + { 216, 0x15002ccc, 0x15004982, 0x1509be55, 0x150c0a23 }, +}; + +/* + * RF value list for rt3xxx + * Supports: 2.4 GHz (all) & 5.2 GHz (RF3052) + */ +static const struct rf_channel rf_vals_3x[] = { + {1, 241, 2, 2 }, + {2, 241, 2, 7 }, + {3, 242, 2, 2 }, + {4, 242, 2, 7 }, + {5, 243, 2, 2 }, + {6, 243, 2, 7 }, + {7, 244, 2, 2 }, + {8, 244, 2, 7 }, + {9, 245, 2, 2 }, + {10, 245, 2, 7 }, + {11, 246, 2, 2 }, + {12, 246, 2, 7 }, + {13, 247, 2, 2 }, + {14, 248, 2, 4 }, + + /* 802.11 UNI / HyperLan 2 */ + {36, 0x56, 0, 4}, + {38, 0x56, 0, 6}, + {40, 0x56, 0, 8}, + {44, 0x57, 0, 0}, + {46, 0x57, 0, 2}, + {48, 0x57, 0, 4}, + {52, 0x57, 0, 8}, + {54, 0x57, 0, 10}, + {56, 0x58, 0, 0}, + {60, 0x58, 0, 4}, + {62, 0x58, 0, 6}, + {64, 0x58, 0, 8}, + + /* 802.11 HyperLan 2 */ + {100, 0x5b, 0, 8}, + {102, 0x5b, 0, 10}, + {104, 0x5c, 0, 0}, + {108, 0x5c, 0, 4}, + {110, 0x5c, 0, 6}, + {112, 0x5c, 0, 8}, + {116, 0x5d, 0, 0}, + {118, 0x5d, 0, 2}, + {120, 0x5d, 0, 4}, + {124, 0x5d, 0, 8}, + {126, 0x5d, 0, 10}, + {128, 0x5e, 0, 0}, + {132, 0x5e, 0, 4}, + {134, 0x5e, 0, 6}, + {136, 0x5e, 0, 8}, + {140, 0x5f, 0, 0}, + + /* 802.11 UNII */ + {149, 0x5f, 0, 9}, + {151, 0x5f, 0, 11}, + {153, 0x60, 0, 1}, + {157, 0x60, 0, 5}, + {159, 0x60, 0, 7}, + {161, 0x60, 0, 9}, + {165, 0x61, 0, 1}, + {167, 0x61, 0, 3}, + {169, 0x61, 0, 5}, + {171, 0x61, 0, 7}, + {173, 0x61, 0, 9}, +}; + +static const struct rf_channel rf_vals_5592_xtal20[] = { + /* Channel, N, K, mod, R */ + {1, 482, 4, 10, 3}, + {2, 483, 4, 10, 3}, + {3, 484, 4, 10, 3}, + {4, 485, 4, 10, 3}, + {5, 486, 4, 10, 3}, + {6, 487, 4, 10, 3}, + {7, 488, 4, 10, 3}, + {8, 489, 4, 10, 3}, + {9, 490, 4, 10, 3}, + {10, 491, 4, 10, 3}, + {11, 492, 4, 10, 3}, + {12, 493, 4, 10, 3}, + {13, 494, 4, 10, 3}, + {14, 496, 8, 10, 3}, + {36, 172, 8, 12, 1}, + {38, 173, 0, 12, 1}, + {40, 173, 4, 12, 1}, + {42, 173, 8, 12, 1}, + {44, 174, 0, 12, 1}, + {46, 174, 4, 12, 1}, + {48, 174, 8, 12, 1}, + {50, 175, 0, 12, 1}, + {52, 175, 4, 12, 1}, + {54, 175, 8, 12, 1}, + {56, 176, 0, 12, 1}, + {58, 176, 4, 12, 1}, + {60, 176, 8, 12, 1}, + {62, 177, 0, 12, 1}, + {64, 177, 4, 12, 1}, + {100, 183, 4, 12, 1}, + {102, 183, 8, 12, 1}, + {104, 184, 0, 12, 1}, + {106, 184, 4, 12, 1}, + {108, 184, 8, 12, 1}, + {110, 185, 0, 12, 1}, + {112, 185, 4, 12, 1}, + {114, 185, 8, 12, 1}, + {116, 186, 0, 12, 1}, + {118, 186, 4, 12, 1}, + {120, 186, 8, 12, 1}, + {122, 187, 0, 12, 1}, + {124, 187, 4, 12, 1}, + {126, 187, 8, 12, 1}, + {128, 188, 0, 12, 1}, + {130, 188, 4, 12, 1}, + {132, 188, 8, 12, 1}, + {134, 189, 0, 12, 1}, + {136, 189, 4, 12, 1}, + {138, 189, 8, 12, 1}, + {140, 190, 0, 12, 1}, + {149, 191, 6, 12, 1}, + {151, 191, 10, 12, 1}, + {153, 192, 2, 12, 1}, + {155, 192, 6, 12, 1}, + {157, 192, 10, 12, 1}, + {159, 193, 2, 12, 1}, + {161, 193, 6, 12, 1}, + {165, 194, 2, 12, 1}, + {184, 164, 0, 12, 1}, + {188, 164, 4, 12, 1}, + {192, 165, 8, 12, 1}, + {196, 166, 0, 12, 1}, +}; + +static const struct rf_channel rf_vals_5592_xtal40[] = { + /* Channel, N, K, mod, R */ + {1, 241, 2, 10, 3}, + {2, 241, 7, 10, 3}, + {3, 242, 2, 10, 3}, + {4, 242, 7, 10, 3}, + {5, 243, 2, 10, 3}, + {6, 243, 7, 10, 3}, + {7, 244, 2, 10, 3}, + {8, 244, 7, 10, 3}, + {9, 245, 2, 10, 3}, + {10, 245, 7, 10, 3}, + {11, 246, 2, 10, 3}, + {12, 246, 7, 10, 3}, + {13, 247, 2, 10, 3}, + {14, 248, 4, 10, 3}, + {36, 86, 4, 12, 1}, + {38, 86, 6, 12, 1}, + {40, 86, 8, 12, 1}, + {42, 86, 10, 12, 1}, + {44, 87, 0, 12, 1}, + {46, 87, 2, 12, 1}, + {48, 87, 4, 12, 1}, + {50, 87, 6, 12, 1}, + {52, 87, 8, 12, 1}, + {54, 87, 10, 12, 1}, + {56, 88, 0, 12, 1}, + {58, 88, 2, 12, 1}, + {60, 88, 4, 12, 1}, + {62, 88, 6, 12, 1}, + {64, 88, 8, 12, 1}, + {100, 91, 8, 12, 1}, + {102, 91, 10, 12, 1}, + {104, 92, 0, 12, 1}, + {106, 92, 2, 12, 1}, + {108, 92, 4, 12, 1}, + {110, 92, 6, 12, 1}, + {112, 92, 8, 12, 1}, + {114, 92, 10, 12, 1}, + {116, 93, 0, 12, 1}, + {118, 93, 2, 12, 1}, + {120, 93, 4, 12, 1}, + {122, 93, 6, 12, 1}, + {124, 93, 8, 12, 1}, + {126, 93, 10, 12, 1}, + {128, 94, 0, 12, 1}, + {130, 94, 2, 12, 1}, + {132, 94, 4, 12, 1}, + {134, 94, 6, 12, 1}, + {136, 94, 8, 12, 1}, + {138, 94, 10, 12, 1}, + {140, 95, 0, 12, 1}, + {149, 95, 9, 12, 1}, + {151, 95, 11, 12, 1}, + {153, 96, 1, 12, 1}, + {155, 96, 3, 12, 1}, + {157, 96, 5, 12, 1}, + {159, 96, 7, 12, 1}, + {161, 96, 9, 12, 1}, + {165, 97, 1, 12, 1}, + {184, 82, 0, 12, 1}, + {188, 82, 4, 12, 1}, + {192, 82, 8, 12, 1}, + {196, 83, 0, 12, 1}, +}; + +static int rt2800_probe_hw_mode(struct rt2x00_dev *rt2x00dev) +{ + struct hw_mode_spec *spec = &rt2x00dev->spec; + struct channel_info *info; + char *default_power1; + char *default_power2; + unsigned int i; + u16 eeprom; + u32 reg; + + /* + * Disable powersaving as default on PCI devices. + */ + if (rt2x00_is_pci(rt2x00dev) || rt2x00_is_soc(rt2x00dev)) + rt2x00dev->hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + + /* + * Initialize all hw fields. + */ + rt2x00dev->hw->flags = + IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_PS_NULLFUNC_STACK | + IEEE80211_HW_AMPDU_AGGREGATION | + IEEE80211_HW_REPORTS_TX_ACK_STATUS | + IEEE80211_HW_SUPPORTS_HT_CCK_RATES; + + /* + * Don't set IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING for USB devices + * unless we are capable of sending the buffered frames out after the + * DTIM transmission using rt2x00lib_beacondone. This will send out + * multicast and broadcast traffic immediately instead of buffering it + * infinitly and thus dropping it after some time. + */ + if (!rt2x00_is_usb(rt2x00dev)) + rt2x00dev->hw->flags |= + IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING; + + SET_IEEE80211_DEV(rt2x00dev->hw, rt2x00dev->dev); + SET_IEEE80211_PERM_ADDR(rt2x00dev->hw, + rt2x00_eeprom_addr(rt2x00dev, + EEPROM_MAC_ADDR_0)); + + /* + * As rt2800 has a global fallback table we cannot specify + * more then one tx rate per frame but since the hw will + * try several rates (based on the fallback table) we should + * initialize max_report_rates to the maximum number of rates + * we are going to try. Otherwise mac80211 will truncate our + * reported tx rates and the rc algortihm will end up with + * incorrect data. + */ + rt2x00dev->hw->max_rates = 1; + rt2x00dev->hw->max_report_rates = 7; + rt2x00dev->hw->max_rate_tries = 1; + + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC_CONF0, &eeprom); + + /* + * Initialize hw_mode information. + */ + spec->supported_bands = SUPPORT_BAND_2GHZ; + spec->supported_rates = SUPPORT_RATE_CCK | SUPPORT_RATE_OFDM; + + if (rt2x00_rf(rt2x00dev, RF2820) || + rt2x00_rf(rt2x00dev, RF2720)) { + spec->num_channels = 14; + spec->channels = rf_vals; + } else if (rt2x00_rf(rt2x00dev, RF2850) || + rt2x00_rf(rt2x00dev, RF2750)) { + spec->supported_bands |= SUPPORT_BAND_5GHZ; + spec->num_channels = ARRAY_SIZE(rf_vals); + spec->channels = rf_vals; + } else if (rt2x00_rf(rt2x00dev, RF3020) || + rt2x00_rf(rt2x00dev, RF2020) || + rt2x00_rf(rt2x00dev, RF3021) || + rt2x00_rf(rt2x00dev, RF3022) || + rt2x00_rf(rt2x00dev, RF3290) || + rt2x00_rf(rt2x00dev, RF3320) || + rt2x00_rf(rt2x00dev, RF3322) || + rt2x00_rf(rt2x00dev, RF5360) || + rt2x00_rf(rt2x00dev, RF5370) || + rt2x00_rf(rt2x00dev, RF5372) || + rt2x00_rf(rt2x00dev, RF5390) || + rt2x00_rf(rt2x00dev, RF5392)) { + spec->num_channels = 14; + spec->channels = rf_vals_3x; + } else if (rt2x00_rf(rt2x00dev, RF3052)) { + spec->supported_bands |= SUPPORT_BAND_5GHZ; + spec->num_channels = ARRAY_SIZE(rf_vals_3x); + spec->channels = rf_vals_3x; + } else if (rt2x00_rf(rt2x00dev, RF5592)) { + spec->supported_bands |= SUPPORT_BAND_5GHZ; + + rt2800_register_read(rt2x00dev, MAC_DEBUG_INDEX, ®); + if (rt2x00_get_field32(reg, MAC_DEBUG_INDEX_XTAL)) { + spec->num_channels = ARRAY_SIZE(rf_vals_5592_xtal40); + spec->channels = rf_vals_5592_xtal40; + } else { + spec->num_channels = ARRAY_SIZE(rf_vals_5592_xtal20); + spec->channels = rf_vals_5592_xtal20; + } + } + + if (WARN_ON_ONCE(!spec->channels)) + return -ENODEV; + + /* + * Initialize HT information. + */ + if (!rt2x00_rf(rt2x00dev, RF2020)) + spec->ht.ht_supported = true; + else + spec->ht.ht_supported = false; + + spec->ht.cap = + IEEE80211_HT_CAP_SUP_WIDTH_20_40 | + IEEE80211_HT_CAP_GRN_FLD | + IEEE80211_HT_CAP_SGI_20 | + IEEE80211_HT_CAP_SGI_40; + + if (rt2x00_get_field16(eeprom, EEPROM_NIC_CONF0_TXPATH) >= 2) + spec->ht.cap |= IEEE80211_HT_CAP_TX_STBC; + + spec->ht.cap |= + rt2x00_get_field16(eeprom, EEPROM_NIC_CONF0_RXPATH) << + IEEE80211_HT_CAP_RX_STBC_SHIFT; + + spec->ht.ampdu_factor = 3; + spec->ht.ampdu_density = 4; + spec->ht.mcs.tx_params = + IEEE80211_HT_MCS_TX_DEFINED | + IEEE80211_HT_MCS_TX_RX_DIFF | + ((rt2x00_get_field16(eeprom, EEPROM_NIC_CONF0_TXPATH) - 1) << + IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT); + + switch (rt2x00_get_field16(eeprom, EEPROM_NIC_CONF0_RXPATH)) { + case 3: + spec->ht.mcs.rx_mask[2] = 0xff; + case 2: + spec->ht.mcs.rx_mask[1] = 0xff; + case 1: + spec->ht.mcs.rx_mask[0] = 0xff; + spec->ht.mcs.rx_mask[4] = 0x1; /* MCS32 */ + break; + } + + /* + * Create channel information array + */ + info = kcalloc(spec->num_channels, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + spec->channels_info = info; + + default_power1 = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_BG1); + default_power2 = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_BG2); + + for (i = 0; i < 14; i++) { + info[i].default_power1 = default_power1[i]; + info[i].default_power2 = default_power2[i]; + } + + if (spec->num_channels > 14) { + default_power1 = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_A1); + default_power2 = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_A2); + + for (i = 14; i < spec->num_channels; i++) { + info[i].default_power1 = default_power1[i - 14]; + info[i].default_power2 = default_power2[i - 14]; + } + } + + switch (rt2x00dev->chip.rf) { + case RF2020: + case RF3020: + case RF3021: + case RF3022: + case RF3320: + case RF3052: + case RF3290: + case RF5360: + case RF5370: + case RF5372: + case RF5390: + case RF5392: + __set_bit(CAPABILITY_VCO_RECALIBRATION, &rt2x00dev->cap_flags); + break; + } + + return 0; +} + +static int rt2800_probe_rt(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + u32 rt; + u32 rev; + + if (rt2x00_rt(rt2x00dev, RT3290)) + rt2800_register_read(rt2x00dev, MAC_CSR0_3290, ®); + else + rt2800_register_read(rt2x00dev, MAC_CSR0, ®); + + rt = rt2x00_get_field32(reg, MAC_CSR0_CHIPSET); + rev = rt2x00_get_field32(reg, MAC_CSR0_REVISION); + + switch (rt) { + case RT2860: + case RT2872: + case RT2883: + case RT3070: + case RT3071: + case RT3090: + case RT3290: + case RT3352: + case RT3390: + case RT3572: + case RT5390: + case RT5392: + case RT5592: + break; + default: + rt2x00_err(rt2x00dev, "Invalid RT chipset 0x%04x, rev %04x detected\n", + rt, rev); + return -ENODEV; + } + + rt2x00_set_rt(rt2x00dev, rt, rev); + + return 0; +} + +int rt2800_probe_hw(struct rt2x00_dev *rt2x00dev) +{ + int retval; + u32 reg; + + retval = rt2800_probe_rt(rt2x00dev); + if (retval) + return retval; + + /* + * Allocate eeprom data. + */ + retval = rt2800_validate_eeprom(rt2x00dev); + if (retval) + return retval; + + retval = rt2800_init_eeprom(rt2x00dev); + if (retval) + return retval; + + /* + * Enable rfkill polling by setting GPIO direction of the + * rfkill switch GPIO pin correctly. + */ + rt2800_register_read(rt2x00dev, GPIO_CTRL, ®); + rt2x00_set_field32(®, GPIO_CTRL_DIR2, 1); + rt2800_register_write(rt2x00dev, GPIO_CTRL, reg); + + /* + * Initialize hw specifications. + */ + retval = rt2800_probe_hw_mode(rt2x00dev); + if (retval) + return retval; + + /* + * Set device capabilities. + */ + __set_bit(CAPABILITY_CONTROL_FILTERS, &rt2x00dev->cap_flags); + __set_bit(CAPABILITY_CONTROL_FILTER_PSPOLL, &rt2x00dev->cap_flags); + if (!rt2x00_is_usb(rt2x00dev)) + __set_bit(CAPABILITY_PRE_TBTT_INTERRUPT, &rt2x00dev->cap_flags); + + /* + * Set device requirements. + */ + if (!rt2x00_is_soc(rt2x00dev)) + __set_bit(REQUIRE_FIRMWARE, &rt2x00dev->cap_flags); + __set_bit(REQUIRE_L2PAD, &rt2x00dev->cap_flags); + __set_bit(REQUIRE_TXSTATUS_FIFO, &rt2x00dev->cap_flags); + if (!rt2800_hwcrypt_disabled(rt2x00dev)) + __set_bit(CAPABILITY_HW_CRYPTO, &rt2x00dev->cap_flags); + __set_bit(CAPABILITY_LINK_TUNING, &rt2x00dev->cap_flags); + __set_bit(REQUIRE_HT_TX_DESC, &rt2x00dev->cap_flags); + if (rt2x00_is_usb(rt2x00dev)) + __set_bit(REQUIRE_PS_AUTOWAKE, &rt2x00dev->cap_flags); + else { + __set_bit(REQUIRE_DMA, &rt2x00dev->cap_flags); + __set_bit(REQUIRE_TASKLET_CONTEXT, &rt2x00dev->cap_flags); + } + + /* + * Set the rssi offset. + */ + rt2x00dev->rssi_offset = DEFAULT_RSSI_OFFSET; + + return 0; +} +EXPORT_SYMBOL_GPL(rt2800_probe_hw); + +/* + * IEEE80211 stack callback functions. + */ +void rt2800_get_tkip_seq(struct ieee80211_hw *hw, u8 hw_key_idx, u32 *iv32, + u16 *iv16) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct mac_iveiv_entry iveiv_entry; + u32 offset; + + offset = MAC_IVEIV_ENTRY(hw_key_idx); + rt2800_register_multiread(rt2x00dev, offset, + &iveiv_entry, sizeof(iveiv_entry)); + + memcpy(iv16, &iveiv_entry.iv[0], sizeof(*iv16)); + memcpy(iv32, &iveiv_entry.iv[4], sizeof(*iv32)); +} +EXPORT_SYMBOL_GPL(rt2800_get_tkip_seq); + +int rt2800_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + u32 reg; + bool enabled = (value < IEEE80211_MAX_RTS_THRESHOLD); + + rt2800_register_read(rt2x00dev, TX_RTS_CFG, ®); + rt2x00_set_field32(®, TX_RTS_CFG_RTS_THRES, value); + rt2800_register_write(rt2x00dev, TX_RTS_CFG, reg); + + rt2800_register_read(rt2x00dev, CCK_PROT_CFG, ®); + rt2x00_set_field32(®, CCK_PROT_CFG_RTS_TH_EN, enabled); + rt2800_register_write(rt2x00dev, CCK_PROT_CFG, reg); + + rt2800_register_read(rt2x00dev, OFDM_PROT_CFG, ®); + rt2x00_set_field32(®, OFDM_PROT_CFG_RTS_TH_EN, enabled); + rt2800_register_write(rt2x00dev, OFDM_PROT_CFG, reg); + + rt2800_register_read(rt2x00dev, MM20_PROT_CFG, ®); + rt2x00_set_field32(®, MM20_PROT_CFG_RTS_TH_EN, enabled); + rt2800_register_write(rt2x00dev, MM20_PROT_CFG, reg); + + rt2800_register_read(rt2x00dev, MM40_PROT_CFG, ®); + rt2x00_set_field32(®, MM40_PROT_CFG_RTS_TH_EN, enabled); + rt2800_register_write(rt2x00dev, MM40_PROT_CFG, reg); + + rt2800_register_read(rt2x00dev, GF20_PROT_CFG, ®); + rt2x00_set_field32(®, GF20_PROT_CFG_RTS_TH_EN, enabled); + rt2800_register_write(rt2x00dev, GF20_PROT_CFG, reg); + + rt2800_register_read(rt2x00dev, GF40_PROT_CFG, ®); + rt2x00_set_field32(®, GF40_PROT_CFG_RTS_TH_EN, enabled); + rt2800_register_write(rt2x00dev, GF40_PROT_CFG, reg); + + return 0; +} +EXPORT_SYMBOL_GPL(rt2800_set_rts_threshold); + +int rt2800_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue_idx, + const struct ieee80211_tx_queue_params *params) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct data_queue *queue; + struct rt2x00_field32 field; + int retval; + u32 reg; + u32 offset; + + /* + * First pass the configuration through rt2x00lib, that will + * update the queue settings and validate the input. After that + * we are free to update the registers based on the value + * in the queue parameter. + */ + retval = rt2x00mac_conf_tx(hw, vif, queue_idx, params); + if (retval) + return retval; + + /* + * We only need to perform additional register initialization + * for WMM queues/ + */ + if (queue_idx >= 4) + return 0; + + queue = rt2x00queue_get_tx_queue(rt2x00dev, queue_idx); + + /* Update WMM TXOP register */ + offset = WMM_TXOP0_CFG + (sizeof(u32) * (!!(queue_idx & 2))); + field.bit_offset = (queue_idx & 1) * 16; + field.bit_mask = 0xffff << field.bit_offset; + + rt2800_register_read(rt2x00dev, offset, ®); + rt2x00_set_field32(®, field, queue->txop); + rt2800_register_write(rt2x00dev, offset, reg); + + /* Update WMM registers */ + field.bit_offset = queue_idx * 4; + field.bit_mask = 0xf << field.bit_offset; + + rt2800_register_read(rt2x00dev, WMM_AIFSN_CFG, ®); + rt2x00_set_field32(®, field, queue->aifs); + rt2800_register_write(rt2x00dev, WMM_AIFSN_CFG, reg); + + rt2800_register_read(rt2x00dev, WMM_CWMIN_CFG, ®); + rt2x00_set_field32(®, field, queue->cw_min); + rt2800_register_write(rt2x00dev, WMM_CWMIN_CFG, reg); + + rt2800_register_read(rt2x00dev, WMM_CWMAX_CFG, ®); + rt2x00_set_field32(®, field, queue->cw_max); + rt2800_register_write(rt2x00dev, WMM_CWMAX_CFG, reg); + + /* Update EDCA registers */ + offset = EDCA_AC0_CFG + (sizeof(u32) * queue_idx); + + rt2800_register_read(rt2x00dev, offset, ®); + rt2x00_set_field32(®, EDCA_AC0_CFG_TX_OP, queue->txop); + rt2x00_set_field32(®, EDCA_AC0_CFG_AIFSN, queue->aifs); + rt2x00_set_field32(®, EDCA_AC0_CFG_CWMIN, queue->cw_min); + rt2x00_set_field32(®, EDCA_AC0_CFG_CWMAX, queue->cw_max); + rt2800_register_write(rt2x00dev, offset, reg); + + return 0; +} +EXPORT_SYMBOL_GPL(rt2800_conf_tx); + +u64 rt2800_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + u64 tsf; + u32 reg; + + rt2800_register_read(rt2x00dev, TSF_TIMER_DW1, ®); + tsf = (u64) rt2x00_get_field32(reg, TSF_TIMER_DW1_HIGH_WORD) << 32; + rt2800_register_read(rt2x00dev, TSF_TIMER_DW0, ®); + tsf |= rt2x00_get_field32(reg, TSF_TIMER_DW0_LOW_WORD); + + return tsf; +} +EXPORT_SYMBOL_GPL(rt2800_get_tsf); + +int rt2800_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, u16 *ssn, + u8 buf_size) +{ + struct rt2x00_sta *sta_priv = (struct rt2x00_sta *)sta->drv_priv; + int ret = 0; + + /* + * Don't allow aggregation for stations the hardware isn't aware + * of because tx status reports for frames to an unknown station + * always contain wcid=255 and thus we can't distinguish between + * multiple stations which leads to unwanted situations when the + * hw reorders frames due to aggregation. + */ + if (sta_priv->wcid < 0) + return 1; + + switch (action) { + case IEEE80211_AMPDU_RX_START: + case IEEE80211_AMPDU_RX_STOP: + /* + * The hw itself takes care of setting up BlockAck mechanisms. + * So, we only have to allow mac80211 to nagotiate a BlockAck + * agreement. Once that is done, the hw will BlockAck incoming + * AMPDUs without further setup. + */ + break; + case IEEE80211_AMPDU_TX_START: + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + break; + default: + rt2x00_warn((struct rt2x00_dev *)hw->priv, + "Unknown AMPDU action\n"); + } + + return ret; +} +EXPORT_SYMBOL_GPL(rt2800_ampdu_action); + +int rt2800_get_survey(struct ieee80211_hw *hw, int idx, + struct survey_info *survey) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + u32 idle, busy, busy_ext; + + if (idx != 0) + return -ENOENT; + + survey->channel = conf->chandef.chan; + + rt2800_register_read(rt2x00dev, CH_IDLE_STA, &idle); + rt2800_register_read(rt2x00dev, CH_BUSY_STA, &busy); + rt2800_register_read(rt2x00dev, CH_BUSY_STA_SEC, &busy_ext); + + if (idle || busy) { + survey->filled = SURVEY_INFO_CHANNEL_TIME | + SURVEY_INFO_CHANNEL_TIME_BUSY | + SURVEY_INFO_CHANNEL_TIME_EXT_BUSY; + + survey->channel_time = (idle + busy) / 1000; + survey->channel_time_busy = busy / 1000; + survey->channel_time_ext_busy = busy_ext / 1000; + } + + if (!(hw->conf.flags & IEEE80211_CONF_OFFCHANNEL)) + survey->filled |= SURVEY_INFO_IN_USE; + + return 0; + +} +EXPORT_SYMBOL_GPL(rt2800_get_survey); + +MODULE_AUTHOR(DRV_PROJECT ", Bartlomiej Zolnierkiewicz"); +MODULE_VERSION(DRV_VERSION); +MODULE_DESCRIPTION("Ralink RT2800 library"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/rt2x00/rt2800lib.h b/drivers/net/wireless/rt2x00/rt2800lib.h new file mode 100644 index 0000000..6ec7394 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2800lib.h @@ -0,0 +1,229 @@ +/* + Copyright (C) 2010 Willow Garage + Copyright (C) 2010 Ivo van Doorn + Copyright (C) 2009 Bartlomiej Zolnierkiewicz + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef RT2800LIB_H +#define RT2800LIB_H + +struct rt2800_ops { + void (*register_read)(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, u32 *value); + void (*register_read_lock)(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, u32 *value); + void (*register_write)(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, u32 value); + void (*register_write_lock)(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, u32 value); + + void (*register_multiread)(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + void *value, const u32 length); + void (*register_multiwrite)(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + const void *value, const u32 length); + + int (*regbusy_read)(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + const struct rt2x00_field32 field, u32 *reg); + + int (*read_eeprom)(struct rt2x00_dev *rt2x00dev); + bool (*hwcrypt_disabled)(struct rt2x00_dev *rt2x00dev); + + int (*drv_write_firmware)(struct rt2x00_dev *rt2x00dev, + const u8 *data, const size_t len); + int (*drv_init_registers)(struct rt2x00_dev *rt2x00dev); + __le32 *(*drv_get_txwi)(struct queue_entry *entry); +}; + +static inline void rt2800_register_read(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + u32 *value) +{ + const struct rt2800_ops *rt2800ops = rt2x00dev->ops->drv; + + rt2800ops->register_read(rt2x00dev, offset, value); +} + +static inline void rt2800_register_read_lock(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + u32 *value) +{ + const struct rt2800_ops *rt2800ops = rt2x00dev->ops->drv; + + rt2800ops->register_read_lock(rt2x00dev, offset, value); +} + +static inline void rt2800_register_write(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + u32 value) +{ + const struct rt2800_ops *rt2800ops = rt2x00dev->ops->drv; + + rt2800ops->register_write(rt2x00dev, offset, value); +} + +static inline void rt2800_register_write_lock(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + u32 value) +{ + const struct rt2800_ops *rt2800ops = rt2x00dev->ops->drv; + + rt2800ops->register_write_lock(rt2x00dev, offset, value); +} + +static inline void rt2800_register_multiread(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + void *value, const u32 length) +{ + const struct rt2800_ops *rt2800ops = rt2x00dev->ops->drv; + + rt2800ops->register_multiread(rt2x00dev, offset, value, length); +} + +static inline void rt2800_register_multiwrite(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + const void *value, + const u32 length) +{ + const struct rt2800_ops *rt2800ops = rt2x00dev->ops->drv; + + rt2800ops->register_multiwrite(rt2x00dev, offset, value, length); +} + +static inline int rt2800_regbusy_read(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + const struct rt2x00_field32 field, + u32 *reg) +{ + const struct rt2800_ops *rt2800ops = rt2x00dev->ops->drv; + + return rt2800ops->regbusy_read(rt2x00dev, offset, field, reg); +} + +static inline int rt2800_read_eeprom(struct rt2x00_dev *rt2x00dev) +{ + const struct rt2800_ops *rt2800ops = rt2x00dev->ops->drv; + + return rt2800ops->read_eeprom(rt2x00dev); +} + +static inline bool rt2800_hwcrypt_disabled(struct rt2x00_dev *rt2x00dev) +{ + const struct rt2800_ops *rt2800ops = rt2x00dev->ops->drv; + + return rt2800ops->hwcrypt_disabled(rt2x00dev); +} + +static inline int rt2800_drv_write_firmware(struct rt2x00_dev *rt2x00dev, + const u8 *data, const size_t len) +{ + const struct rt2800_ops *rt2800ops = rt2x00dev->ops->drv; + + return rt2800ops->drv_write_firmware(rt2x00dev, data, len); +} + +static inline int rt2800_drv_init_registers(struct rt2x00_dev *rt2x00dev) +{ + const struct rt2800_ops *rt2800ops = rt2x00dev->ops->drv; + + return rt2800ops->drv_init_registers(rt2x00dev); +} + +static inline __le32 *rt2800_drv_get_txwi(struct queue_entry *entry) +{ + const struct rt2800_ops *rt2800ops = entry->queue->rt2x00dev->ops->drv; + + return rt2800ops->drv_get_txwi(entry); +} + +void rt2800_mcu_request(struct rt2x00_dev *rt2x00dev, + const u8 command, const u8 token, + const u8 arg0, const u8 arg1); + +int rt2800_wait_csr_ready(struct rt2x00_dev *rt2x00dev); +int rt2800_wait_wpdma_ready(struct rt2x00_dev *rt2x00dev); + +int rt2800_check_firmware(struct rt2x00_dev *rt2x00dev, + const u8 *data, const size_t len); +int rt2800_load_firmware(struct rt2x00_dev *rt2x00dev, + const u8 *data, const size_t len); + +void rt2800_write_tx_data(struct queue_entry *entry, + struct txentry_desc *txdesc); +void rt2800_process_rxwi(struct queue_entry *entry, struct rxdone_entry_desc *txdesc); + +void rt2800_txdone_entry(struct queue_entry *entry, u32 status, __le32* txwi); + +void rt2800_write_beacon(struct queue_entry *entry, struct txentry_desc *txdesc); +void rt2800_clear_beacon(struct queue_entry *entry); + +extern const struct rt2x00debug rt2800_rt2x00debug; + +int rt2800_rfkill_poll(struct rt2x00_dev *rt2x00dev); +int rt2800_config_shared_key(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_crypto *crypto, + struct ieee80211_key_conf *key); +int rt2800_config_pairwise_key(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_crypto *crypto, + struct ieee80211_key_conf *key); +int rt2800_sta_add(struct rt2x00_dev *rt2x00dev, struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +int rt2800_sta_remove(struct rt2x00_dev *rt2x00dev, int wcid); +void rt2800_config_filter(struct rt2x00_dev *rt2x00dev, + const unsigned int filter_flags); +void rt2800_config_intf(struct rt2x00_dev *rt2x00dev, struct rt2x00_intf *intf, + struct rt2x00intf_conf *conf, const unsigned int flags); +void rt2800_config_erp(struct rt2x00_dev *rt2x00dev, struct rt2x00lib_erp *erp, + u32 changed); +void rt2800_config_ant(struct rt2x00_dev *rt2x00dev, struct antenna_setup *ant); +void rt2800_config(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf, + const unsigned int flags); +void rt2800_link_stats(struct rt2x00_dev *rt2x00dev, struct link_qual *qual); +void rt2800_reset_tuner(struct rt2x00_dev *rt2x00dev, struct link_qual *qual); +void rt2800_link_tuner(struct rt2x00_dev *rt2x00dev, struct link_qual *qual, + const u32 count); +void rt2800_gain_calibration(struct rt2x00_dev *rt2x00dev); +void rt2800_vco_calibration(struct rt2x00_dev *rt2x00dev); + +int rt2800_enable_radio(struct rt2x00_dev *rt2x00dev); +void rt2800_disable_radio(struct rt2x00_dev *rt2x00dev); + +int rt2800_efuse_detect(struct rt2x00_dev *rt2x00dev); +int rt2800_read_eeprom_efuse(struct rt2x00_dev *rt2x00dev); + +int rt2800_probe_hw(struct rt2x00_dev *rt2x00dev); + +void rt2800_get_tkip_seq(struct ieee80211_hw *hw, u8 hw_key_idx, u32 *iv32, + u16 *iv16); +int rt2800_set_rts_threshold(struct ieee80211_hw *hw, u32 value); +int rt2800_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue_idx, + const struct ieee80211_tx_queue_params *params); +u64 rt2800_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif); +int rt2800_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, u16 *ssn, + u8 buf_size); +int rt2800_get_survey(struct ieee80211_hw *hw, int idx, + struct survey_info *survey); +void rt2800_disable_wpdma(struct rt2x00_dev *rt2x00dev); + +#endif /* RT2800LIB_H */ diff --git a/drivers/net/wireless/rt2x00/rt2800pci.c b/drivers/net/wireless/rt2x00/rt2800pci.c new file mode 100644 index 0000000..ffa0255 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2800pci.c @@ -0,0 +1,1361 @@ +/* + Copyright (C) 2009 - 2010 Ivo van Doorn + Copyright (C) 2009 Alban Browaeys + Copyright (C) 2009 Felix Fietkau + Copyright (C) 2009 Luis Correia + Copyright (C) 2009 Mattias Nissler + Copyright (C) 2009 Mark Asselstine + Copyright (C) 2009 Xose Vazquez Perez + Copyright (C) 2009 Bart Zolnierkiewicz + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2800pci + Abstract: rt2800pci device specific routines. + Supported chipsets: RT2800E & RT2800ED. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rt2x00.h" +#include "rt2x00mmio.h" +#include "rt2x00pci.h" +#include "rt2x00soc.h" +#include "rt2800lib.h" +#include "rt2800.h" +#include "rt2800pci.h" + +/* + * Allow hardware encryption to be disabled. + */ +static bool modparam_nohwcrypt = false; +module_param_named(nohwcrypt, modparam_nohwcrypt, bool, S_IRUGO); +MODULE_PARM_DESC(nohwcrypt, "Disable hardware encryption."); + +static bool rt2800pci_hwcrypt_disabled(struct rt2x00_dev *rt2x00dev) +{ + return modparam_nohwcrypt; +} + +static void rt2800pci_mcu_status(struct rt2x00_dev *rt2x00dev, const u8 token) +{ + unsigned int i; + u32 reg; + + /* + * SOC devices don't support MCU requests. + */ + if (rt2x00_is_soc(rt2x00dev)) + return; + + for (i = 0; i < 200; i++) { + rt2x00mmio_register_read(rt2x00dev, H2M_MAILBOX_CID, ®); + + if ((rt2x00_get_field32(reg, H2M_MAILBOX_CID_CMD0) == token) || + (rt2x00_get_field32(reg, H2M_MAILBOX_CID_CMD1) == token) || + (rt2x00_get_field32(reg, H2M_MAILBOX_CID_CMD2) == token) || + (rt2x00_get_field32(reg, H2M_MAILBOX_CID_CMD3) == token)) + break; + + udelay(REGISTER_BUSY_DELAY); + } + + if (i == 200) + rt2x00_err(rt2x00dev, "MCU request failed, no response from hardware\n"); + + rt2x00mmio_register_write(rt2x00dev, H2M_MAILBOX_STATUS, ~0); + rt2x00mmio_register_write(rt2x00dev, H2M_MAILBOX_CID, ~0); +} + +#if defined(CONFIG_SOC_RT288X) || defined(CONFIG_SOC_RT305X) +static int rt2800pci_read_eeprom_soc(struct rt2x00_dev *rt2x00dev) +{ + void __iomem *base_addr = ioremap(0x1F040000, EEPROM_SIZE); + + if (!base_addr) + return -ENOMEM; + + memcpy_fromio(rt2x00dev->eeprom, base_addr, EEPROM_SIZE); + + iounmap(base_addr); + return 0; +} +#else +static inline int rt2800pci_read_eeprom_soc(struct rt2x00_dev *rt2x00dev) +{ + return -ENOMEM; +} +#endif /* CONFIG_SOC_RT288X || CONFIG_SOC_RT305X */ + +#ifdef CONFIG_PCI +static void rt2800pci_eepromregister_read(struct eeprom_93cx6 *eeprom) +{ + struct rt2x00_dev *rt2x00dev = eeprom->data; + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, E2PROM_CSR, ®); + + eeprom->reg_data_in = !!rt2x00_get_field32(reg, E2PROM_CSR_DATA_IN); + eeprom->reg_data_out = !!rt2x00_get_field32(reg, E2PROM_CSR_DATA_OUT); + eeprom->reg_data_clock = + !!rt2x00_get_field32(reg, E2PROM_CSR_DATA_CLOCK); + eeprom->reg_chip_select = + !!rt2x00_get_field32(reg, E2PROM_CSR_CHIP_SELECT); +} + +static void rt2800pci_eepromregister_write(struct eeprom_93cx6 *eeprom) +{ + struct rt2x00_dev *rt2x00dev = eeprom->data; + u32 reg = 0; + + rt2x00_set_field32(®, E2PROM_CSR_DATA_IN, !!eeprom->reg_data_in); + rt2x00_set_field32(®, E2PROM_CSR_DATA_OUT, !!eeprom->reg_data_out); + rt2x00_set_field32(®, E2PROM_CSR_DATA_CLOCK, + !!eeprom->reg_data_clock); + rt2x00_set_field32(®, E2PROM_CSR_CHIP_SELECT, + !!eeprom->reg_chip_select); + + rt2x00mmio_register_write(rt2x00dev, E2PROM_CSR, reg); +} + +static int rt2800pci_read_eeprom_pci(struct rt2x00_dev *rt2x00dev) +{ + struct eeprom_93cx6 eeprom; + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, E2PROM_CSR, ®); + + eeprom.data = rt2x00dev; + eeprom.register_read = rt2800pci_eepromregister_read; + eeprom.register_write = rt2800pci_eepromregister_write; + switch (rt2x00_get_field32(reg, E2PROM_CSR_TYPE)) + { + case 0: + eeprom.width = PCI_EEPROM_WIDTH_93C46; + break; + case 1: + eeprom.width = PCI_EEPROM_WIDTH_93C66; + break; + default: + eeprom.width = PCI_EEPROM_WIDTH_93C86; + break; + } + eeprom.reg_data_in = 0; + eeprom.reg_data_out = 0; + eeprom.reg_data_clock = 0; + eeprom.reg_chip_select = 0; + + eeprom_93cx6_multiread(&eeprom, EEPROM_BASE, rt2x00dev->eeprom, + EEPROM_SIZE / sizeof(u16)); + + return 0; +} + +static int rt2800pci_efuse_detect(struct rt2x00_dev *rt2x00dev) +{ + return rt2800_efuse_detect(rt2x00dev); +} + +static inline int rt2800pci_read_eeprom_efuse(struct rt2x00_dev *rt2x00dev) +{ + return rt2800_read_eeprom_efuse(rt2x00dev); +} +#else +static inline int rt2800pci_read_eeprom_pci(struct rt2x00_dev *rt2x00dev) +{ + return -EOPNOTSUPP; +} + +static inline int rt2800pci_efuse_detect(struct rt2x00_dev *rt2x00dev) +{ + return 0; +} + +static inline int rt2800pci_read_eeprom_efuse(struct rt2x00_dev *rt2x00dev) +{ + return -EOPNOTSUPP; +} +#endif /* CONFIG_PCI */ + +/* + * Queue handlers. + */ +static void rt2800pci_start_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u32 reg; + + switch (queue->qid) { + case QID_RX: + rt2x00mmio_register_read(rt2x00dev, MAC_SYS_CTRL, ®); + rt2x00_set_field32(®, MAC_SYS_CTRL_ENABLE_RX, 1); + rt2x00mmio_register_write(rt2x00dev, MAC_SYS_CTRL, reg); + break; + case QID_BEACON: + rt2x00mmio_register_read(rt2x00dev, BCN_TIME_CFG, ®); + rt2x00_set_field32(®, BCN_TIME_CFG_TSF_TICKING, 1); + rt2x00_set_field32(®, BCN_TIME_CFG_TBTT_ENABLE, 1); + rt2x00_set_field32(®, BCN_TIME_CFG_BEACON_GEN, 1); + rt2x00mmio_register_write(rt2x00dev, BCN_TIME_CFG, reg); + + rt2x00mmio_register_read(rt2x00dev, INT_TIMER_EN, ®); + rt2x00_set_field32(®, INT_TIMER_EN_PRE_TBTT_TIMER, 1); + rt2x00mmio_register_write(rt2x00dev, INT_TIMER_EN, reg); + break; + default: + break; + } +} + +static void rt2800pci_kick_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + struct queue_entry *entry; + + switch (queue->qid) { + case QID_AC_VO: + case QID_AC_VI: + case QID_AC_BE: + case QID_AC_BK: + entry = rt2x00queue_get_entry(queue, Q_INDEX); + rt2x00mmio_register_write(rt2x00dev, TX_CTX_IDX(queue->qid), + entry->entry_idx); + break; + case QID_MGMT: + entry = rt2x00queue_get_entry(queue, Q_INDEX); + rt2x00mmio_register_write(rt2x00dev, TX_CTX_IDX(5), + entry->entry_idx); + break; + default: + break; + } +} + +static void rt2800pci_stop_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u32 reg; + + switch (queue->qid) { + case QID_RX: + rt2x00mmio_register_read(rt2x00dev, MAC_SYS_CTRL, ®); + rt2x00_set_field32(®, MAC_SYS_CTRL_ENABLE_RX, 0); + rt2x00mmio_register_write(rt2x00dev, MAC_SYS_CTRL, reg); + break; + case QID_BEACON: + rt2x00mmio_register_read(rt2x00dev, BCN_TIME_CFG, ®); + rt2x00_set_field32(®, BCN_TIME_CFG_TSF_TICKING, 0); + rt2x00_set_field32(®, BCN_TIME_CFG_TBTT_ENABLE, 0); + rt2x00_set_field32(®, BCN_TIME_CFG_BEACON_GEN, 0); + rt2x00mmio_register_write(rt2x00dev, BCN_TIME_CFG, reg); + + rt2x00mmio_register_read(rt2x00dev, INT_TIMER_EN, ®); + rt2x00_set_field32(®, INT_TIMER_EN_PRE_TBTT_TIMER, 0); + rt2x00mmio_register_write(rt2x00dev, INT_TIMER_EN, reg); + + /* + * Wait for current invocation to finish. The tasklet + * won't be scheduled anymore afterwards since we disabled + * the TBTT and PRE TBTT timer. + */ + tasklet_kill(&rt2x00dev->tbtt_tasklet); + tasklet_kill(&rt2x00dev->pretbtt_tasklet); + + break; + default: + break; + } +} + +/* + * Firmware functions + */ +static char *rt2800pci_get_firmware_name(struct rt2x00_dev *rt2x00dev) +{ + /* + * Chip rt3290 use specific 4KB firmware named rt3290.bin. + */ + if (rt2x00_rt(rt2x00dev, RT3290)) + return FIRMWARE_RT3290; + else + return FIRMWARE_RT2860; +} + +static int rt2800pci_write_firmware(struct rt2x00_dev *rt2x00dev, + const u8 *data, const size_t len) +{ + u32 reg; + + /* + * enable Host program ram write selection + */ + reg = 0; + rt2x00_set_field32(®, PBF_SYS_CTRL_HOST_RAM_WRITE, 1); + rt2x00mmio_register_write(rt2x00dev, PBF_SYS_CTRL, reg); + + /* + * Write firmware to device. + */ + rt2x00mmio_register_multiwrite(rt2x00dev, FIRMWARE_IMAGE_BASE, + data, len); + + rt2x00mmio_register_write(rt2x00dev, PBF_SYS_CTRL, 0x00000); + rt2x00mmio_register_write(rt2x00dev, PBF_SYS_CTRL, 0x00001); + + rt2x00mmio_register_write(rt2x00dev, H2M_BBP_AGENT, 0); + rt2x00mmio_register_write(rt2x00dev, H2M_MAILBOX_CSR, 0); + + return 0; +} + +/* + * Initialization functions. + */ +static bool rt2800pci_get_entry_state(struct queue_entry *entry) +{ + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + u32 word; + + if (entry->queue->qid == QID_RX) { + rt2x00_desc_read(entry_priv->desc, 1, &word); + + return (!rt2x00_get_field32(word, RXD_W1_DMA_DONE)); + } else { + rt2x00_desc_read(entry_priv->desc, 1, &word); + + return (!rt2x00_get_field32(word, TXD_W1_DMA_DONE)); + } +} + +static void rt2800pci_clear_entry(struct queue_entry *entry) +{ + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + u32 word; + + if (entry->queue->qid == QID_RX) { + rt2x00_desc_read(entry_priv->desc, 0, &word); + rt2x00_set_field32(&word, RXD_W0_SDP0, skbdesc->skb_dma); + rt2x00_desc_write(entry_priv->desc, 0, word); + + rt2x00_desc_read(entry_priv->desc, 1, &word); + rt2x00_set_field32(&word, RXD_W1_DMA_DONE, 0); + rt2x00_desc_write(entry_priv->desc, 1, word); + + /* + * Set RX IDX in register to inform hardware that we have + * handled this entry and it is available for reuse again. + */ + rt2x00mmio_register_write(rt2x00dev, RX_CRX_IDX, + entry->entry_idx); + } else { + rt2x00_desc_read(entry_priv->desc, 1, &word); + rt2x00_set_field32(&word, TXD_W1_DMA_DONE, 1); + rt2x00_desc_write(entry_priv->desc, 1, word); + } +} + +static int rt2800pci_init_queues(struct rt2x00_dev *rt2x00dev) +{ + struct queue_entry_priv_mmio *entry_priv; + + /* + * Initialize registers. + */ + entry_priv = rt2x00dev->tx[0].entries[0].priv_data; + rt2x00mmio_register_write(rt2x00dev, TX_BASE_PTR0, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, TX_MAX_CNT0, + rt2x00dev->tx[0].limit); + rt2x00mmio_register_write(rt2x00dev, TX_CTX_IDX0, 0); + rt2x00mmio_register_write(rt2x00dev, TX_DTX_IDX0, 0); + + entry_priv = rt2x00dev->tx[1].entries[0].priv_data; + rt2x00mmio_register_write(rt2x00dev, TX_BASE_PTR1, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, TX_MAX_CNT1, + rt2x00dev->tx[1].limit); + rt2x00mmio_register_write(rt2x00dev, TX_CTX_IDX1, 0); + rt2x00mmio_register_write(rt2x00dev, TX_DTX_IDX1, 0); + + entry_priv = rt2x00dev->tx[2].entries[0].priv_data; + rt2x00mmio_register_write(rt2x00dev, TX_BASE_PTR2, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, TX_MAX_CNT2, + rt2x00dev->tx[2].limit); + rt2x00mmio_register_write(rt2x00dev, TX_CTX_IDX2, 0); + rt2x00mmio_register_write(rt2x00dev, TX_DTX_IDX2, 0); + + entry_priv = rt2x00dev->tx[3].entries[0].priv_data; + rt2x00mmio_register_write(rt2x00dev, TX_BASE_PTR3, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, TX_MAX_CNT3, + rt2x00dev->tx[3].limit); + rt2x00mmio_register_write(rt2x00dev, TX_CTX_IDX3, 0); + rt2x00mmio_register_write(rt2x00dev, TX_DTX_IDX3, 0); + + rt2x00mmio_register_write(rt2x00dev, TX_BASE_PTR4, 0); + rt2x00mmio_register_write(rt2x00dev, TX_MAX_CNT4, 0); + rt2x00mmio_register_write(rt2x00dev, TX_CTX_IDX4, 0); + rt2x00mmio_register_write(rt2x00dev, TX_DTX_IDX4, 0); + + rt2x00mmio_register_write(rt2x00dev, TX_BASE_PTR5, 0); + rt2x00mmio_register_write(rt2x00dev, TX_MAX_CNT5, 0); + rt2x00mmio_register_write(rt2x00dev, TX_CTX_IDX5, 0); + rt2x00mmio_register_write(rt2x00dev, TX_DTX_IDX5, 0); + + entry_priv = rt2x00dev->rx->entries[0].priv_data; + rt2x00mmio_register_write(rt2x00dev, RX_BASE_PTR, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, RX_MAX_CNT, + rt2x00dev->rx[0].limit); + rt2x00mmio_register_write(rt2x00dev, RX_CRX_IDX, + rt2x00dev->rx[0].limit - 1); + rt2x00mmio_register_write(rt2x00dev, RX_DRX_IDX, 0); + + rt2800_disable_wpdma(rt2x00dev); + + rt2x00mmio_register_write(rt2x00dev, DELAY_INT_CFG, 0); + + return 0; +} + +/* + * Device state switch handlers. + */ +static void rt2800pci_toggle_irq(struct rt2x00_dev *rt2x00dev, + enum dev_state state) +{ + u32 reg; + unsigned long flags; + + /* + * When interrupts are being enabled, the interrupt registers + * should clear the register to assure a clean state. + */ + if (state == STATE_RADIO_IRQ_ON) { + rt2x00mmio_register_read(rt2x00dev, INT_SOURCE_CSR, ®); + rt2x00mmio_register_write(rt2x00dev, INT_SOURCE_CSR, reg); + } + + spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags); + reg = 0; + if (state == STATE_RADIO_IRQ_ON) { + rt2x00_set_field32(®, INT_MASK_CSR_RX_DONE, 1); + rt2x00_set_field32(®, INT_MASK_CSR_TBTT, 1); + rt2x00_set_field32(®, INT_MASK_CSR_PRE_TBTT, 1); + rt2x00_set_field32(®, INT_MASK_CSR_TX_FIFO_STATUS, 1); + rt2x00_set_field32(®, INT_MASK_CSR_AUTO_WAKEUP, 1); + } + rt2x00mmio_register_write(rt2x00dev, INT_MASK_CSR, reg); + spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags); + + if (state == STATE_RADIO_IRQ_OFF) { + /* + * Wait for possibly running tasklets to finish. + */ + tasklet_kill(&rt2x00dev->txstatus_tasklet); + tasklet_kill(&rt2x00dev->rxdone_tasklet); + tasklet_kill(&rt2x00dev->autowake_tasklet); + tasklet_kill(&rt2x00dev->tbtt_tasklet); + tasklet_kill(&rt2x00dev->pretbtt_tasklet); + } +} + +static int rt2800pci_init_registers(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + /* + * Reset DMA indexes + */ + rt2x00mmio_register_read(rt2x00dev, WPDMA_RST_IDX, ®); + rt2x00_set_field32(®, WPDMA_RST_IDX_DTX_IDX0, 1); + rt2x00_set_field32(®, WPDMA_RST_IDX_DTX_IDX1, 1); + rt2x00_set_field32(®, WPDMA_RST_IDX_DTX_IDX2, 1); + rt2x00_set_field32(®, WPDMA_RST_IDX_DTX_IDX3, 1); + rt2x00_set_field32(®, WPDMA_RST_IDX_DTX_IDX4, 1); + rt2x00_set_field32(®, WPDMA_RST_IDX_DTX_IDX5, 1); + rt2x00_set_field32(®, WPDMA_RST_IDX_DRX_IDX0, 1); + rt2x00mmio_register_write(rt2x00dev, WPDMA_RST_IDX, reg); + + rt2x00mmio_register_write(rt2x00dev, PBF_SYS_CTRL, 0x00000e1f); + rt2x00mmio_register_write(rt2x00dev, PBF_SYS_CTRL, 0x00000e00); + + if (rt2x00_is_pcie(rt2x00dev) && + (rt2x00_rt(rt2x00dev, RT3572) || + rt2x00_rt(rt2x00dev, RT5390) || + rt2x00_rt(rt2x00dev, RT5392))) { + rt2x00mmio_register_read(rt2x00dev, AUX_CTRL, ®); + rt2x00_set_field32(®, AUX_CTRL_FORCE_PCIE_CLK, 1); + rt2x00_set_field32(®, AUX_CTRL_WAKE_PCIE_EN, 1); + rt2x00mmio_register_write(rt2x00dev, AUX_CTRL, reg); + } + + rt2x00mmio_register_write(rt2x00dev, PWR_PIN_CFG, 0x00000003); + + reg = 0; + rt2x00_set_field32(®, MAC_SYS_CTRL_RESET_CSR, 1); + rt2x00_set_field32(®, MAC_SYS_CTRL_RESET_BBP, 1); + rt2x00mmio_register_write(rt2x00dev, MAC_SYS_CTRL, reg); + + rt2x00mmio_register_write(rt2x00dev, MAC_SYS_CTRL, 0x00000000); + + return 0; +} + +static int rt2800pci_enable_radio(struct rt2x00_dev *rt2x00dev) +{ + int retval; + + /* Wait for DMA, ignore error until we initialize queues. */ + rt2800_wait_wpdma_ready(rt2x00dev); + + if (unlikely(rt2800pci_init_queues(rt2x00dev))) + return -EIO; + + retval = rt2800_enable_radio(rt2x00dev); + if (retval) + return retval; + + /* After resume MCU_BOOT_SIGNAL will trash these. */ + rt2x00mmio_register_write(rt2x00dev, H2M_MAILBOX_STATUS, ~0); + rt2x00mmio_register_write(rt2x00dev, H2M_MAILBOX_CID, ~0); + + rt2800_mcu_request(rt2x00dev, MCU_SLEEP, TOKEN_RADIO_OFF, 0xff, 0x02); + rt2800pci_mcu_status(rt2x00dev, TOKEN_RADIO_OFF); + + rt2800_mcu_request(rt2x00dev, MCU_WAKEUP, TOKEN_WAKEUP, 0, 0); + rt2800pci_mcu_status(rt2x00dev, TOKEN_WAKEUP); + + return retval; +} + +static void rt2800pci_disable_radio(struct rt2x00_dev *rt2x00dev) +{ + if (rt2x00_is_soc(rt2x00dev)) { + rt2800_disable_radio(rt2x00dev); + rt2x00mmio_register_write(rt2x00dev, PWR_PIN_CFG, 0); + rt2x00mmio_register_write(rt2x00dev, TX_PIN_CFG, 0); + } +} + +static int rt2800pci_set_state(struct rt2x00_dev *rt2x00dev, + enum dev_state state) +{ + if (state == STATE_AWAKE) { + rt2800_mcu_request(rt2x00dev, MCU_WAKEUP, TOKEN_WAKEUP, + 0, 0x02); + rt2800pci_mcu_status(rt2x00dev, TOKEN_WAKEUP); + } else if (state == STATE_SLEEP) { + rt2x00mmio_register_write(rt2x00dev, H2M_MAILBOX_STATUS, + 0xffffffff); + rt2x00mmio_register_write(rt2x00dev, H2M_MAILBOX_CID, + 0xffffffff); + rt2800_mcu_request(rt2x00dev, MCU_SLEEP, TOKEN_SLEEP, + 0xff, 0x01); + } + + return 0; +} + +static int rt2800pci_set_device_state(struct rt2x00_dev *rt2x00dev, + enum dev_state state) +{ + int retval = 0; + + switch (state) { + case STATE_RADIO_ON: + retval = rt2800pci_enable_radio(rt2x00dev); + break; + case STATE_RADIO_OFF: + /* + * After the radio has been disabled, the device should + * be put to sleep for powersaving. + */ + rt2800pci_disable_radio(rt2x00dev); + rt2800pci_set_state(rt2x00dev, STATE_SLEEP); + break; + case STATE_RADIO_IRQ_ON: + case STATE_RADIO_IRQ_OFF: + rt2800pci_toggle_irq(rt2x00dev, state); + break; + case STATE_DEEP_SLEEP: + case STATE_SLEEP: + case STATE_STANDBY: + case STATE_AWAKE: + retval = rt2800pci_set_state(rt2x00dev, state); + break; + default: + retval = -ENOTSUPP; + break; + } + + if (unlikely(retval)) + rt2x00_err(rt2x00dev, "Device failed to enter state %d (%d)\n", + state, retval); + + return retval; +} + +/* + * TX descriptor initialization + */ +static __le32 *rt2800pci_get_txwi(struct queue_entry *entry) +{ + return (__le32 *) entry->skb->data; +} + +static void rt2800pci_write_tx_desc(struct queue_entry *entry, + struct txentry_desc *txdesc) +{ + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + __le32 *txd = entry_priv->desc; + u32 word; + + /* + * The buffers pointed by SD_PTR0/SD_LEN0 and SD_PTR1/SD_LEN1 + * must contains a TXWI structure + 802.11 header + padding + 802.11 + * data. We choose to have SD_PTR0/SD_LEN0 only contains TXWI and + * SD_PTR1/SD_LEN1 contains 802.11 header + padding + 802.11 + * data. It means that LAST_SEC0 is always 0. + */ + + /* + * Initialize TX descriptor + */ + word = 0; + rt2x00_set_field32(&word, TXD_W0_SD_PTR0, skbdesc->skb_dma); + rt2x00_desc_write(txd, 0, word); + + word = 0; + rt2x00_set_field32(&word, TXD_W1_SD_LEN1, entry->skb->len); + rt2x00_set_field32(&word, TXD_W1_LAST_SEC1, + !test_bit(ENTRY_TXD_MORE_FRAG, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W1_BURST, + test_bit(ENTRY_TXD_BURST, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W1_SD_LEN0, TXWI_DESC_SIZE); + rt2x00_set_field32(&word, TXD_W1_LAST_SEC0, 0); + rt2x00_set_field32(&word, TXD_W1_DMA_DONE, 0); + rt2x00_desc_write(txd, 1, word); + + word = 0; + rt2x00_set_field32(&word, TXD_W2_SD_PTR1, + skbdesc->skb_dma + TXWI_DESC_SIZE); + rt2x00_desc_write(txd, 2, word); + + word = 0; + rt2x00_set_field32(&word, TXD_W3_WIV, + !test_bit(ENTRY_TXD_ENCRYPT_IV, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W3_QSEL, 2); + rt2x00_desc_write(txd, 3, word); + + /* + * Register descriptor details in skb frame descriptor. + */ + skbdesc->desc = txd; + skbdesc->desc_len = TXD_DESC_SIZE; +} + +/* + * RX control handlers + */ +static void rt2800pci_fill_rxdone(struct queue_entry *entry, + struct rxdone_entry_desc *rxdesc) +{ + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + __le32 *rxd = entry_priv->desc; + u32 word; + + rt2x00_desc_read(rxd, 3, &word); + + if (rt2x00_get_field32(word, RXD_W3_CRC_ERROR)) + rxdesc->flags |= RX_FLAG_FAILED_FCS_CRC; + + /* + * Unfortunately we don't know the cipher type used during + * decryption. This prevents us from correct providing + * correct statistics through debugfs. + */ + rxdesc->cipher_status = rt2x00_get_field32(word, RXD_W3_CIPHER_ERROR); + + if (rt2x00_get_field32(word, RXD_W3_DECRYPTED)) { + /* + * Hardware has stripped IV/EIV data from 802.11 frame during + * decryption. Unfortunately the descriptor doesn't contain + * any fields with the EIV/IV data either, so they can't + * be restored by rt2x00lib. + */ + rxdesc->flags |= RX_FLAG_IV_STRIPPED; + + /* + * The hardware has already checked the Michael Mic and has + * stripped it from the frame. Signal this to mac80211. + */ + rxdesc->flags |= RX_FLAG_MMIC_STRIPPED; + + if (rxdesc->cipher_status == RX_CRYPTO_SUCCESS) + rxdesc->flags |= RX_FLAG_DECRYPTED; + else if (rxdesc->cipher_status == RX_CRYPTO_FAIL_MIC) + rxdesc->flags |= RX_FLAG_MMIC_ERROR; + } + + if (rt2x00_get_field32(word, RXD_W3_MY_BSS)) + rxdesc->dev_flags |= RXDONE_MY_BSS; + + if (rt2x00_get_field32(word, RXD_W3_L2PAD)) + rxdesc->dev_flags |= RXDONE_L2PAD; + + /* + * Process the RXWI structure that is at the start of the buffer. + */ + rt2800_process_rxwi(entry, rxdesc); +} + +/* + * Interrupt functions. + */ +static void rt2800pci_wakeup(struct rt2x00_dev *rt2x00dev) +{ + struct ieee80211_conf conf = { .flags = 0 }; + struct rt2x00lib_conf libconf = { .conf = &conf }; + + rt2800_config(rt2x00dev, &libconf, IEEE80211_CONF_CHANGE_PS); +} + +static bool rt2800pci_txdone_entry_check(struct queue_entry *entry, u32 status) +{ + __le32 *txwi; + u32 word; + int wcid, tx_wcid; + + wcid = rt2x00_get_field32(status, TX_STA_FIFO_WCID); + + txwi = rt2800_drv_get_txwi(entry); + rt2x00_desc_read(txwi, 1, &word); + tx_wcid = rt2x00_get_field32(word, TXWI_W1_WIRELESS_CLI_ID); + + return (tx_wcid == wcid); +} + +static bool rt2800pci_txdone_find_entry(struct queue_entry *entry, void *data) +{ + u32 status = *(u32 *)data; + + /* + * rt2800pci hardware might reorder frames when exchanging traffic + * with multiple BA enabled STAs. + * + * For example, a tx queue + * [ STA1 | STA2 | STA1 | STA2 ] + * can result in tx status reports + * [ STA1 | STA1 | STA2 | STA2 ] + * when the hw decides to aggregate the frames for STA1 into one AMPDU. + * + * To mitigate this effect, associate the tx status to the first frame + * in the tx queue with a matching wcid. + */ + if (rt2800pci_txdone_entry_check(entry, status) && + !test_bit(ENTRY_DATA_STATUS_SET, &entry->flags)) { + /* + * Got a matching frame, associate the tx status with + * the frame + */ + entry->status = status; + set_bit(ENTRY_DATA_STATUS_SET, &entry->flags); + return true; + } + + /* Check the next frame */ + return false; +} + +static bool rt2800pci_txdone_match_first(struct queue_entry *entry, void *data) +{ + u32 status = *(u32 *)data; + + /* + * Find the first frame without tx status and assign this status to it + * regardless if it matches or not. + */ + if (!test_bit(ENTRY_DATA_STATUS_SET, &entry->flags)) { + /* + * Got a matching frame, associate the tx status with + * the frame + */ + entry->status = status; + set_bit(ENTRY_DATA_STATUS_SET, &entry->flags); + return true; + } + + /* Check the next frame */ + return false; +} +static bool rt2800pci_txdone_release_entries(struct queue_entry *entry, + void *data) +{ + if (test_bit(ENTRY_DATA_STATUS_SET, &entry->flags)) { + rt2800_txdone_entry(entry, entry->status, + rt2800pci_get_txwi(entry)); + return false; + } + + /* No more frames to release */ + return true; +} + +static bool rt2800pci_txdone(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + u32 status; + u8 qid; + int max_tx_done = 16; + + while (kfifo_get(&rt2x00dev->txstatus_fifo, &status)) { + qid = rt2x00_get_field32(status, TX_STA_FIFO_PID_QUEUE); + if (unlikely(qid >= QID_RX)) { + /* + * Unknown queue, this shouldn't happen. Just drop + * this tx status. + */ + rt2x00_warn(rt2x00dev, "Got TX status report with unexpected pid %u, dropping\n", + qid); + break; + } + + queue = rt2x00queue_get_tx_queue(rt2x00dev, qid); + if (unlikely(queue == NULL)) { + /* + * The queue is NULL, this shouldn't happen. Stop + * processing here and drop the tx status + */ + rt2x00_warn(rt2x00dev, "Got TX status for an unavailable queue %u, dropping\n", + qid); + break; + } + + if (unlikely(rt2x00queue_empty(queue))) { + /* + * The queue is empty. Stop processing here + * and drop the tx status. + */ + rt2x00_warn(rt2x00dev, "Got TX status for an empty queue %u, dropping\n", + qid); + break; + } + + /* + * Let's associate this tx status with the first + * matching frame. + */ + if (!rt2x00queue_for_each_entry(queue, Q_INDEX_DONE, + Q_INDEX, &status, + rt2800pci_txdone_find_entry)) { + /* + * We cannot match the tx status to any frame, so just + * use the first one. + */ + if (!rt2x00queue_for_each_entry(queue, Q_INDEX_DONE, + Q_INDEX, &status, + rt2800pci_txdone_match_first)) { + rt2x00_warn(rt2x00dev, "No frame found for TX status on queue %u, dropping\n", + qid); + break; + } + } + + /* + * Release all frames with a valid tx status. + */ + rt2x00queue_for_each_entry(queue, Q_INDEX_DONE, + Q_INDEX, NULL, + rt2800pci_txdone_release_entries); + + if (--max_tx_done == 0) + break; + } + + return !max_tx_done; +} + +static inline void rt2800pci_enable_interrupt(struct rt2x00_dev *rt2x00dev, + struct rt2x00_field32 irq_field) +{ + u32 reg; + + /* + * Enable a single interrupt. The interrupt mask register + * access needs locking. + */ + spin_lock_irq(&rt2x00dev->irqmask_lock); + rt2x00mmio_register_read(rt2x00dev, INT_MASK_CSR, ®); + rt2x00_set_field32(®, irq_field, 1); + rt2x00mmio_register_write(rt2x00dev, INT_MASK_CSR, reg); + spin_unlock_irq(&rt2x00dev->irqmask_lock); +} + +static void rt2800pci_txstatus_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + if (rt2800pci_txdone(rt2x00dev)) + tasklet_schedule(&rt2x00dev->txstatus_tasklet); + + /* + * No need to enable the tx status interrupt here as we always + * leave it enabled to minimize the possibility of a tx status + * register overflow. See comment in interrupt handler. + */ +} + +static void rt2800pci_pretbtt_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + rt2x00lib_pretbtt(rt2x00dev); + if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + rt2800pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_PRE_TBTT); +} + +static void rt2800pci_tbtt_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + struct rt2800_drv_data *drv_data = rt2x00dev->drv_data; + u32 reg; + + rt2x00lib_beacondone(rt2x00dev); + + if (rt2x00dev->intf_ap_count) { + /* + * The rt2800pci hardware tbtt timer is off by 1us per tbtt + * causing beacon skew and as a result causing problems with + * some powersaving clients over time. Shorten the beacon + * interval every 64 beacons by 64us to mitigate this effect. + */ + if (drv_data->tbtt_tick == (BCN_TBTT_OFFSET - 2)) { + rt2x00mmio_register_read(rt2x00dev, BCN_TIME_CFG, ®); + rt2x00_set_field32(®, BCN_TIME_CFG_BEACON_INTERVAL, + (rt2x00dev->beacon_int * 16) - 1); + rt2x00mmio_register_write(rt2x00dev, BCN_TIME_CFG, reg); + } else if (drv_data->tbtt_tick == (BCN_TBTT_OFFSET - 1)) { + rt2x00mmio_register_read(rt2x00dev, BCN_TIME_CFG, ®); + rt2x00_set_field32(®, BCN_TIME_CFG_BEACON_INTERVAL, + (rt2x00dev->beacon_int * 16)); + rt2x00mmio_register_write(rt2x00dev, BCN_TIME_CFG, reg); + } + drv_data->tbtt_tick++; + drv_data->tbtt_tick %= BCN_TBTT_OFFSET; + } + + if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + rt2800pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_TBTT); +} + +static void rt2800pci_rxdone_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + if (rt2x00mmio_rxdone(rt2x00dev)) + tasklet_schedule(&rt2x00dev->rxdone_tasklet); + else if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + rt2800pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_RX_DONE); +} + +static void rt2800pci_autowake_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + rt2800pci_wakeup(rt2x00dev); + if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + rt2800pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_AUTO_WAKEUP); +} + +static void rt2800pci_txstatus_interrupt(struct rt2x00_dev *rt2x00dev) +{ + u32 status; + int i; + + /* + * The TX_FIFO_STATUS interrupt needs special care. We should + * read TX_STA_FIFO but we should do it immediately as otherwise + * the register can overflow and we would lose status reports. + * + * Hence, read the TX_STA_FIFO register and copy all tx status + * reports into a kernel FIFO which is handled in the txstatus + * tasklet. We use a tasklet to process the tx status reports + * because we can schedule the tasklet multiple times (when the + * interrupt fires again during tx status processing). + * + * Furthermore we don't disable the TX_FIFO_STATUS + * interrupt here but leave it enabled so that the TX_STA_FIFO + * can also be read while the tx status tasklet gets executed. + * + * Since we have only one producer and one consumer we don't + * need to lock the kfifo. + */ + for (i = 0; i < rt2x00dev->ops->tx->entry_num; i++) { + rt2x00mmio_register_read(rt2x00dev, TX_STA_FIFO, &status); + + if (!rt2x00_get_field32(status, TX_STA_FIFO_VALID)) + break; + + if (!kfifo_put(&rt2x00dev->txstatus_fifo, &status)) { + rt2x00_warn(rt2x00dev, "TX status FIFO overrun, drop tx status report\n"); + break; + } + } + + /* Schedule the tasklet for processing the tx status. */ + tasklet_schedule(&rt2x00dev->txstatus_tasklet); +} + +static irqreturn_t rt2800pci_interrupt(int irq, void *dev_instance) +{ + struct rt2x00_dev *rt2x00dev = dev_instance; + u32 reg, mask; + + /* Read status and ACK all interrupts */ + rt2x00mmio_register_read(rt2x00dev, INT_SOURCE_CSR, ®); + rt2x00mmio_register_write(rt2x00dev, INT_SOURCE_CSR, reg); + + if (!reg) + return IRQ_NONE; + + if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + return IRQ_HANDLED; + + /* + * Since INT_MASK_CSR and INT_SOURCE_CSR use the same bits + * for interrupts and interrupt masks we can just use the value of + * INT_SOURCE_CSR to create the interrupt mask. + */ + mask = ~reg; + + if (rt2x00_get_field32(reg, INT_SOURCE_CSR_TX_FIFO_STATUS)) { + rt2800pci_txstatus_interrupt(rt2x00dev); + /* + * Never disable the TX_FIFO_STATUS interrupt. + */ + rt2x00_set_field32(&mask, INT_MASK_CSR_TX_FIFO_STATUS, 1); + } + + if (rt2x00_get_field32(reg, INT_SOURCE_CSR_PRE_TBTT)) + tasklet_hi_schedule(&rt2x00dev->pretbtt_tasklet); + + if (rt2x00_get_field32(reg, INT_SOURCE_CSR_TBTT)) + tasklet_hi_schedule(&rt2x00dev->tbtt_tasklet); + + if (rt2x00_get_field32(reg, INT_SOURCE_CSR_RX_DONE)) + tasklet_schedule(&rt2x00dev->rxdone_tasklet); + + if (rt2x00_get_field32(reg, INT_SOURCE_CSR_AUTO_WAKEUP)) + tasklet_schedule(&rt2x00dev->autowake_tasklet); + + /* + * Disable all interrupts for which a tasklet was scheduled right now, + * the tasklet will reenable the appropriate interrupts. + */ + spin_lock(&rt2x00dev->irqmask_lock); + rt2x00mmio_register_read(rt2x00dev, INT_MASK_CSR, ®); + reg &= mask; + rt2x00mmio_register_write(rt2x00dev, INT_MASK_CSR, reg); + spin_unlock(&rt2x00dev->irqmask_lock); + + return IRQ_HANDLED; +} + +/* + * Device probe functions. + */ +static int rt2800pci_read_eeprom(struct rt2x00_dev *rt2x00dev) +{ + int retval; + + if (rt2x00_is_soc(rt2x00dev)) + retval = rt2800pci_read_eeprom_soc(rt2x00dev); + else if (rt2800pci_efuse_detect(rt2x00dev)) + retval = rt2800pci_read_eeprom_efuse(rt2x00dev); + else + retval = rt2800pci_read_eeprom_pci(rt2x00dev); + + return retval; +} + +static const struct ieee80211_ops rt2800pci_mac80211_ops = { + .tx = rt2x00mac_tx, + .start = rt2x00mac_start, + .stop = rt2x00mac_stop, + .add_interface = rt2x00mac_add_interface, + .remove_interface = rt2x00mac_remove_interface, + .config = rt2x00mac_config, + .configure_filter = rt2x00mac_configure_filter, + .set_key = rt2x00mac_set_key, + .sw_scan_start = rt2x00mac_sw_scan_start, + .sw_scan_complete = rt2x00mac_sw_scan_complete, + .get_stats = rt2x00mac_get_stats, + .get_tkip_seq = rt2800_get_tkip_seq, + .set_rts_threshold = rt2800_set_rts_threshold, + .sta_add = rt2x00mac_sta_add, + .sta_remove = rt2x00mac_sta_remove, + .bss_info_changed = rt2x00mac_bss_info_changed, + .conf_tx = rt2800_conf_tx, + .get_tsf = rt2800_get_tsf, + .rfkill_poll = rt2x00mac_rfkill_poll, + .ampdu_action = rt2800_ampdu_action, + .flush = rt2x00mac_flush, + .get_survey = rt2800_get_survey, + .get_ringparam = rt2x00mac_get_ringparam, + .tx_frames_pending = rt2x00mac_tx_frames_pending, +}; + +static const struct rt2800_ops rt2800pci_rt2800_ops = { + .register_read = rt2x00mmio_register_read, + .register_read_lock = rt2x00mmio_register_read, /* same for PCI */ + .register_write = rt2x00mmio_register_write, + .register_write_lock = rt2x00mmio_register_write, /* same for PCI */ + .register_multiread = rt2x00mmio_register_multiread, + .register_multiwrite = rt2x00mmio_register_multiwrite, + .regbusy_read = rt2x00mmio_regbusy_read, + .read_eeprom = rt2800pci_read_eeprom, + .hwcrypt_disabled = rt2800pci_hwcrypt_disabled, + .drv_write_firmware = rt2800pci_write_firmware, + .drv_init_registers = rt2800pci_init_registers, + .drv_get_txwi = rt2800pci_get_txwi, +}; + +static const struct rt2x00lib_ops rt2800pci_rt2x00_ops = { + .irq_handler = rt2800pci_interrupt, + .txstatus_tasklet = rt2800pci_txstatus_tasklet, + .pretbtt_tasklet = rt2800pci_pretbtt_tasklet, + .tbtt_tasklet = rt2800pci_tbtt_tasklet, + .rxdone_tasklet = rt2800pci_rxdone_tasklet, + .autowake_tasklet = rt2800pci_autowake_tasklet, + .probe_hw = rt2800_probe_hw, + .get_firmware_name = rt2800pci_get_firmware_name, + .check_firmware = rt2800_check_firmware, + .load_firmware = rt2800_load_firmware, + .initialize = rt2x00mmio_initialize, + .uninitialize = rt2x00mmio_uninitialize, + .get_entry_state = rt2800pci_get_entry_state, + .clear_entry = rt2800pci_clear_entry, + .set_device_state = rt2800pci_set_device_state, + .rfkill_poll = rt2800_rfkill_poll, + .link_stats = rt2800_link_stats, + .reset_tuner = rt2800_reset_tuner, + .link_tuner = rt2800_link_tuner, + .gain_calibration = rt2800_gain_calibration, + .vco_calibration = rt2800_vco_calibration, + .start_queue = rt2800pci_start_queue, + .kick_queue = rt2800pci_kick_queue, + .stop_queue = rt2800pci_stop_queue, + .flush_queue = rt2x00mmio_flush_queue, + .write_tx_desc = rt2800pci_write_tx_desc, + .write_tx_data = rt2800_write_tx_data, + .write_beacon = rt2800_write_beacon, + .clear_beacon = rt2800_clear_beacon, + .fill_rxdone = rt2800pci_fill_rxdone, + .config_shared_key = rt2800_config_shared_key, + .config_pairwise_key = rt2800_config_pairwise_key, + .config_filter = rt2800_config_filter, + .config_intf = rt2800_config_intf, + .config_erp = rt2800_config_erp, + .config_ant = rt2800_config_ant, + .config = rt2800_config, + .sta_add = rt2800_sta_add, + .sta_remove = rt2800_sta_remove, +}; + +static const struct data_queue_desc rt2800pci_queue_rx = { + .entry_num = 128, + .data_size = AGGREGATION_SIZE, + .desc_size = RXD_DESC_SIZE, + .winfo_size = RXWI_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_mmio), +}; + +static const struct data_queue_desc rt2800pci_queue_tx = { + .entry_num = 64, + .data_size = AGGREGATION_SIZE, + .desc_size = TXD_DESC_SIZE, + .winfo_size = TXWI_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_mmio), +}; + +static const struct data_queue_desc rt2800pci_queue_bcn = { + .entry_num = 8, + .data_size = 0, /* No DMA required for beacons */ + .desc_size = TXD_DESC_SIZE, + .winfo_size = TXWI_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_mmio), +}; + +static const struct rt2x00_ops rt2800pci_ops = { + .name = KBUILD_MODNAME, + .drv_data_size = sizeof(struct rt2800_drv_data), + .max_ap_intf = 8, + .eeprom_size = EEPROM_SIZE, + .rf_size = RF_SIZE, + .tx_queues = NUM_TX_QUEUES, + .extra_tx_headroom = TXWI_DESC_SIZE, + .rx = &rt2800pci_queue_rx, + .tx = &rt2800pci_queue_tx, + .bcn = &rt2800pci_queue_bcn, + .lib = &rt2800pci_rt2x00_ops, + .drv = &rt2800pci_rt2800_ops, + .hw = &rt2800pci_mac80211_ops, +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS + .debugfs = &rt2800_rt2x00debug, +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ +}; + +/* + * RT2800pci module information. + */ +#ifdef CONFIG_PCI +static DEFINE_PCI_DEVICE_TABLE(rt2800pci_device_table) = { + { PCI_DEVICE(0x1814, 0x0601) }, + { PCI_DEVICE(0x1814, 0x0681) }, + { PCI_DEVICE(0x1814, 0x0701) }, + { PCI_DEVICE(0x1814, 0x0781) }, + { PCI_DEVICE(0x1814, 0x3090) }, + { PCI_DEVICE(0x1814, 0x3091) }, + { PCI_DEVICE(0x1814, 0x3092) }, + { PCI_DEVICE(0x1432, 0x7708) }, + { PCI_DEVICE(0x1432, 0x7727) }, + { PCI_DEVICE(0x1432, 0x7728) }, + { PCI_DEVICE(0x1432, 0x7738) }, + { PCI_DEVICE(0x1432, 0x7748) }, + { PCI_DEVICE(0x1432, 0x7758) }, + { PCI_DEVICE(0x1432, 0x7768) }, + { PCI_DEVICE(0x1462, 0x891a) }, + { PCI_DEVICE(0x1a3b, 0x1059) }, +#ifdef CPTCFG_RT2800PCI_RT3290 + { PCI_DEVICE(0x1814, 0x3290) }, +#endif +#ifdef CPTCFG_RT2800PCI_RT33XX + { PCI_DEVICE(0x1814, 0x3390) }, +#endif +#ifdef CPTCFG_RT2800PCI_RT35XX + { PCI_DEVICE(0x1432, 0x7711) }, + { PCI_DEVICE(0x1432, 0x7722) }, + { PCI_DEVICE(0x1814, 0x3060) }, + { PCI_DEVICE(0x1814, 0x3062) }, + { PCI_DEVICE(0x1814, 0x3562) }, + { PCI_DEVICE(0x1814, 0x3592) }, + { PCI_DEVICE(0x1814, 0x3593) }, + { PCI_DEVICE(0x1814, 0x359f) }, +#endif +#ifdef CPTCFG_RT2800PCI_RT53XX + { PCI_DEVICE(0x1814, 0x5360) }, + { PCI_DEVICE(0x1814, 0x5362) }, + { PCI_DEVICE(0x1814, 0x5390) }, + { PCI_DEVICE(0x1814, 0x5392) }, + { PCI_DEVICE(0x1814, 0x539a) }, + { PCI_DEVICE(0x1814, 0x539b) }, + { PCI_DEVICE(0x1814, 0x539f) }, +#endif + { 0, } +}; +#endif /* CONFIG_PCI */ + +MODULE_AUTHOR(DRV_PROJECT); +MODULE_VERSION(DRV_VERSION); +MODULE_DESCRIPTION("Ralink RT2800 PCI & PCMCIA Wireless LAN driver."); +MODULE_SUPPORTED_DEVICE("Ralink RT2860 PCI & PCMCIA chipset based cards"); +#ifdef CONFIG_PCI +MODULE_FIRMWARE(FIRMWARE_RT2860); +MODULE_DEVICE_TABLE(pci, rt2800pci_device_table); +#endif /* CONFIG_PCI */ +MODULE_LICENSE("GPL"); + +#if defined(CONFIG_SOC_RT288X) || defined(CONFIG_SOC_RT305X) +static int rt2800soc_probe(struct platform_device *pdev) +{ + return rt2x00soc_probe(pdev, &rt2800pci_ops); +} + +static struct platform_driver rt2800soc_driver = { + .driver = { + .name = "rt2800_wmac", + .owner = THIS_MODULE, + .mod_name = KBUILD_MODNAME, + }, + .probe = rt2800soc_probe, + .remove = rt2x00soc_remove, + .suspend = rt2x00soc_suspend, + .resume = rt2x00soc_resume, +}; +#endif /* CONFIG_SOC_RT288X || CONFIG_SOC_RT305X */ + +#ifdef CONFIG_PCI +static int rt2800pci_probe(struct pci_dev *pci_dev, + const struct pci_device_id *id) +{ + return rt2x00pci_probe(pci_dev, &rt2800pci_ops); +} + +static struct pci_driver rt2800pci_driver = { + .name = KBUILD_MODNAME, + .id_table = rt2800pci_device_table, + .probe = rt2800pci_probe, + .remove = rt2x00pci_remove, + .suspend = rt2x00pci_suspend, + .resume = rt2x00pci_resume, +}; +#endif /* CONFIG_PCI */ + +static int __init rt2800pci_init(void) +{ + int ret = 0; + +#if defined(CONFIG_SOC_RT288X) || defined(CONFIG_SOC_RT305X) + ret = platform_driver_register(&rt2800soc_driver); + if (ret) + return ret; +#endif +#ifdef CONFIG_PCI + ret = pci_register_driver(&rt2800pci_driver); + if (ret) { +#if defined(CONFIG_SOC_RT288X) || defined(CONFIG_SOC_RT305X) + platform_driver_unregister(&rt2800soc_driver); +#endif + return ret; + } +#endif + + return ret; +} + +static void __exit rt2800pci_exit(void) +{ +#ifdef CONFIG_PCI + pci_unregister_driver(&rt2800pci_driver); +#endif +#if defined(CONFIG_SOC_RT288X) || defined(CONFIG_SOC_RT305X) + platform_driver_unregister(&rt2800soc_driver); +#endif +} + +module_init(rt2800pci_init); +module_exit(rt2800pci_exit); diff --git a/drivers/net/wireless/rt2x00/rt2800pci.h b/drivers/net/wireless/rt2x00/rt2800pci.h new file mode 100644 index 0000000..ab22a08 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2800pci.h @@ -0,0 +1,141 @@ +/* + Copyright (C) 2009 Ivo van Doorn + Copyright (C) 2009 Alban Browaeys + Copyright (C) 2009 Felix Fietkau + Copyright (C) 2009 Luis Correia + Copyright (C) 2009 Mattias Nissler + Copyright (C) 2009 Mark Asselstine + Copyright (C) 2009 Xose Vazquez Perez + Copyright (C) 2009 Bart Zolnierkiewicz + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2800pci + Abstract: Data structures and registers for the rt2800pci module. + Supported chipsets: RT2800E & RT2800ED. + */ + +#ifndef RT2800PCI_H +#define RT2800PCI_H + +/* + * Queue register offset macros + */ +#define TX_QUEUE_REG_OFFSET 0x10 +#define TX_BASE_PTR(__x) (TX_BASE_PTR0 + ((__x) * TX_QUEUE_REG_OFFSET)) +#define TX_MAX_CNT(__x) (TX_MAX_CNT0 + ((__x) * TX_QUEUE_REG_OFFSET)) +#define TX_CTX_IDX(__x) (TX_CTX_IDX0 + ((__x) * TX_QUEUE_REG_OFFSET)) +#define TX_DTX_IDX(__x) (TX_DTX_IDX0 + ((__x) * TX_QUEUE_REG_OFFSET)) + +/* + * 8051 firmware image. + */ +#define FIRMWARE_RT2860 "rt2860.bin" +#define FIRMWARE_RT3290 "rt3290.bin" +#define FIRMWARE_IMAGE_BASE 0x2000 + +/* + * DMA descriptor defines. + */ +#define TXD_DESC_SIZE (4 * sizeof(__le32)) +#define RXD_DESC_SIZE (4 * sizeof(__le32)) + +/* + * TX descriptor format for TX, PRIO and Beacon Ring. + */ + +/* + * Word0 + */ +#define TXD_W0_SD_PTR0 FIELD32(0xffffffff) + +/* + * Word1 + */ +#define TXD_W1_SD_LEN1 FIELD32(0x00003fff) +#define TXD_W1_LAST_SEC1 FIELD32(0x00004000) +#define TXD_W1_BURST FIELD32(0x00008000) +#define TXD_W1_SD_LEN0 FIELD32(0x3fff0000) +#define TXD_W1_LAST_SEC0 FIELD32(0x40000000) +#define TXD_W1_DMA_DONE FIELD32(0x80000000) + +/* + * Word2 + */ +#define TXD_W2_SD_PTR1 FIELD32(0xffffffff) + +/* + * Word3 + * WIV: Wireless Info Valid. 1: Driver filled WI, 0: DMA needs to copy WI + * QSEL: Select on-chip FIFO ID for 2nd-stage output scheduler. + * 0:MGMT, 1:HCCA 2:EDCA + */ +#define TXD_W3_WIV FIELD32(0x01000000) +#define TXD_W3_QSEL FIELD32(0x06000000) +#define TXD_W3_TCO FIELD32(0x20000000) +#define TXD_W3_UCO FIELD32(0x40000000) +#define TXD_W3_ICO FIELD32(0x80000000) + +/* + * RX descriptor format for RX Ring. + */ + +/* + * Word0 + */ +#define RXD_W0_SDP0 FIELD32(0xffffffff) + +/* + * Word1 + */ +#define RXD_W1_SDL1 FIELD32(0x00003fff) +#define RXD_W1_SDL0 FIELD32(0x3fff0000) +#define RXD_W1_LS0 FIELD32(0x40000000) +#define RXD_W1_DMA_DONE FIELD32(0x80000000) + +/* + * Word2 + */ +#define RXD_W2_SDP1 FIELD32(0xffffffff) + +/* + * Word3 + * AMSDU: RX with 802.3 header, not 802.11 header. + * DECRYPTED: This frame is being decrypted. + */ +#define RXD_W3_BA FIELD32(0x00000001) +#define RXD_W3_DATA FIELD32(0x00000002) +#define RXD_W3_NULLDATA FIELD32(0x00000004) +#define RXD_W3_FRAG FIELD32(0x00000008) +#define RXD_W3_UNICAST_TO_ME FIELD32(0x00000010) +#define RXD_W3_MULTICAST FIELD32(0x00000020) +#define RXD_W3_BROADCAST FIELD32(0x00000040) +#define RXD_W3_MY_BSS FIELD32(0x00000080) +#define RXD_W3_CRC_ERROR FIELD32(0x00000100) +#define RXD_W3_CIPHER_ERROR FIELD32(0x00000600) +#define RXD_W3_AMSDU FIELD32(0x00000800) +#define RXD_W3_HTC FIELD32(0x00001000) +#define RXD_W3_RSSI FIELD32(0x00002000) +#define RXD_W3_L2PAD FIELD32(0x00004000) +#define RXD_W3_AMPDU FIELD32(0x00008000) +#define RXD_W3_DECRYPTED FIELD32(0x00010000) +#define RXD_W3_PLCP_SIGNAL FIELD32(0x00020000) +#define RXD_W3_PLCP_RSSI FIELD32(0x00040000) + +#endif /* RT2800PCI_H */ diff --git a/drivers/net/wireless/rt2x00/rt2800usb.c b/drivers/net/wireless/rt2x00/rt2800usb.c new file mode 100644 index 0000000..20a7c1b --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2800usb.c @@ -0,0 +1,1383 @@ +/* + Copyright (C) 2010 Willow Garage + Copyright (C) 2009 - 2010 Ivo van Doorn + Copyright (C) 2009 Mattias Nissler + Copyright (C) 2009 Felix Fietkau + Copyright (C) 2009 Xose Vazquez Perez + Copyright (C) 2009 Axel Kollhofer + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2800usb + Abstract: rt2800usb device specific routines. + Supported chipsets: RT2800U. + */ + +#include +#include +#include +#include +#include +#include + +#include "rt2x00.h" +#include "rt2x00usb.h" +#include "rt2800lib.h" +#include "rt2800.h" +#include "rt2800usb.h" + +/* + * Allow hardware encryption to be disabled. + */ +static bool modparam_nohwcrypt; +module_param_named(nohwcrypt, modparam_nohwcrypt, bool, S_IRUGO); +MODULE_PARM_DESC(nohwcrypt, "Disable hardware encryption."); + +static bool rt2800usb_hwcrypt_disabled(struct rt2x00_dev *rt2x00dev) +{ + return modparam_nohwcrypt; +} + +/* + * Queue handlers. + */ +static void rt2800usb_start_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u32 reg; + + switch (queue->qid) { + case QID_RX: + rt2x00usb_register_read(rt2x00dev, MAC_SYS_CTRL, ®); + rt2x00_set_field32(®, MAC_SYS_CTRL_ENABLE_RX, 1); + rt2x00usb_register_write(rt2x00dev, MAC_SYS_CTRL, reg); + break; + case QID_BEACON: + rt2x00usb_register_read(rt2x00dev, BCN_TIME_CFG, ®); + rt2x00_set_field32(®, BCN_TIME_CFG_TSF_TICKING, 1); + rt2x00_set_field32(®, BCN_TIME_CFG_TBTT_ENABLE, 1); + rt2x00_set_field32(®, BCN_TIME_CFG_BEACON_GEN, 1); + rt2x00usb_register_write(rt2x00dev, BCN_TIME_CFG, reg); + break; + default: + break; + } +} + +static void rt2800usb_stop_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u32 reg; + + switch (queue->qid) { + case QID_RX: + rt2x00usb_register_read(rt2x00dev, MAC_SYS_CTRL, ®); + rt2x00_set_field32(®, MAC_SYS_CTRL_ENABLE_RX, 0); + rt2x00usb_register_write(rt2x00dev, MAC_SYS_CTRL, reg); + break; + case QID_BEACON: + rt2x00usb_register_read(rt2x00dev, BCN_TIME_CFG, ®); + rt2x00_set_field32(®, BCN_TIME_CFG_TSF_TICKING, 0); + rt2x00_set_field32(®, BCN_TIME_CFG_TBTT_ENABLE, 0); + rt2x00_set_field32(®, BCN_TIME_CFG_BEACON_GEN, 0); + rt2x00usb_register_write(rt2x00dev, BCN_TIME_CFG, reg); + break; + default: + break; + } +} + +/* + * test if there is an entry in any TX queue for which DMA is done + * but the TX status has not been returned yet + */ +static bool rt2800usb_txstatus_pending(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + + tx_queue_for_each(rt2x00dev, queue) { + if (rt2x00queue_get_entry(queue, Q_INDEX_DMA_DONE) != + rt2x00queue_get_entry(queue, Q_INDEX_DONE)) + return true; + } + return false; +} + +static inline bool rt2800usb_entry_txstatus_timeout(struct queue_entry *entry) +{ + bool tout; + + if (!test_bit(ENTRY_DATA_STATUS_PENDING, &entry->flags)) + return false; + + tout = time_after(jiffies, entry->last_action + msecs_to_jiffies(100)); + if (unlikely(tout)) + rt2x00_warn(entry->queue->rt2x00dev, + "TX status timeout for entry %d in queue %d\n", + entry->entry_idx, entry->queue->qid); + return tout; + +} + +static bool rt2800usb_txstatus_timeout(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + struct queue_entry *entry; + + tx_queue_for_each(rt2x00dev, queue) { + entry = rt2x00queue_get_entry(queue, Q_INDEX_DONE); + if (rt2800usb_entry_txstatus_timeout(entry)) + return true; + } + return false; +} + +static bool rt2800usb_tx_sta_fifo_read_completed(struct rt2x00_dev *rt2x00dev, + int urb_status, u32 tx_status) +{ + bool valid; + + if (urb_status) { + rt2x00_warn(rt2x00dev, "TX status read failed %d\n", + urb_status); + + goto stop_reading; + } + + valid = rt2x00_get_field32(tx_status, TX_STA_FIFO_VALID); + if (valid) { + if (!kfifo_put(&rt2x00dev->txstatus_fifo, &tx_status)) + rt2x00_warn(rt2x00dev, "TX status FIFO overrun\n"); + + queue_work(rt2x00dev->workqueue, &rt2x00dev->txdone_work); + + /* Reschedule urb to read TX status again instantly */ + return true; + } + + /* Check if there is any entry that timedout waiting on TX status */ + if (rt2800usb_txstatus_timeout(rt2x00dev)) + queue_work(rt2x00dev->workqueue, &rt2x00dev->txdone_work); + + if (rt2800usb_txstatus_pending(rt2x00dev)) { + /* Read register after 250 us */ + hrtimer_start(&rt2x00dev->txstatus_timer, ktime_set(0, 250000), + HRTIMER_MODE_REL); + return false; + } + +stop_reading: + clear_bit(TX_STATUS_READING, &rt2x00dev->flags); + /* + * There is small race window above, between txstatus pending check and + * clear_bit someone could do rt2x00usb_interrupt_txdone, so recheck + * here again if status reading is needed. + */ + if (rt2800usb_txstatus_pending(rt2x00dev) && + !test_and_set_bit(TX_STATUS_READING, &rt2x00dev->flags)) + return true; + else + return false; +} + +static void rt2800usb_async_read_tx_status(struct rt2x00_dev *rt2x00dev) +{ + + if (test_and_set_bit(TX_STATUS_READING, &rt2x00dev->flags)) + return; + + /* Read TX_STA_FIFO register after 500 us */ + hrtimer_start(&rt2x00dev->txstatus_timer, ktime_set(0, 500000), + HRTIMER_MODE_REL); +} + +static void rt2800usb_tx_dma_done(struct queue_entry *entry) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + + rt2800usb_async_read_tx_status(rt2x00dev); +} + +static enum hrtimer_restart rt2800usb_tx_sta_fifo_timeout(struct hrtimer *timer) +{ + struct rt2x00_dev *rt2x00dev = + container_of(timer, struct rt2x00_dev, txstatus_timer); + + rt2x00usb_register_read_async(rt2x00dev, TX_STA_FIFO, + rt2800usb_tx_sta_fifo_read_completed); + + return HRTIMER_NORESTART; +} + +/* + * Firmware functions + */ +static char *rt2800usb_get_firmware_name(struct rt2x00_dev *rt2x00dev) +{ + return FIRMWARE_RT2870; +} + +static int rt2800usb_write_firmware(struct rt2x00_dev *rt2x00dev, + const u8 *data, const size_t len) +{ + int status; + u32 offset; + u32 length; + + /* + * Check which section of the firmware we need. + */ + if (rt2x00_rt(rt2x00dev, RT2860) || + rt2x00_rt(rt2x00dev, RT2872) || + rt2x00_rt(rt2x00dev, RT3070)) { + offset = 0; + length = 4096; + } else { + offset = 4096; + length = 4096; + } + + /* + * Write firmware to device. + */ + rt2x00usb_register_multiwrite(rt2x00dev, FIRMWARE_IMAGE_BASE, + data + offset, length); + + rt2x00usb_register_write(rt2x00dev, H2M_MAILBOX_CID, ~0); + rt2x00usb_register_write(rt2x00dev, H2M_MAILBOX_STATUS, ~0); + + /* + * Send firmware request to device to load firmware, + * we need to specify a long timeout time. + */ + status = rt2x00usb_vendor_request_sw(rt2x00dev, USB_DEVICE_MODE, + 0, USB_MODE_FIRMWARE, + REGISTER_TIMEOUT_FIRMWARE); + if (status < 0) { + rt2x00_err(rt2x00dev, "Failed to write Firmware to device\n"); + return status; + } + + msleep(10); + rt2x00usb_register_write(rt2x00dev, H2M_MAILBOX_CSR, 0); + + return 0; +} + +/* + * Device state switch handlers. + */ +static int rt2800usb_init_registers(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + /* + * Wait until BBP and RF are ready. + */ + if (rt2800_wait_csr_ready(rt2x00dev)) + return -EBUSY; + + rt2x00usb_register_read(rt2x00dev, PBF_SYS_CTRL, ®); + rt2x00usb_register_write(rt2x00dev, PBF_SYS_CTRL, reg & ~0x00002000); + + reg = 0; + rt2x00_set_field32(®, MAC_SYS_CTRL_RESET_CSR, 1); + rt2x00_set_field32(®, MAC_SYS_CTRL_RESET_BBP, 1); + rt2x00usb_register_write(rt2x00dev, MAC_SYS_CTRL, reg); + + rt2x00usb_register_write(rt2x00dev, USB_DMA_CFG, 0x00000000); + + rt2x00usb_vendor_request_sw(rt2x00dev, USB_DEVICE_MODE, 0, + USB_MODE_RESET, REGISTER_TIMEOUT); + + rt2x00usb_register_write(rt2x00dev, MAC_SYS_CTRL, 0x00000000); + + return 0; +} + +static int rt2800usb_enable_radio(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + if (unlikely(rt2800_wait_wpdma_ready(rt2x00dev))) + return -EIO; + + rt2x00usb_register_read(rt2x00dev, USB_DMA_CFG, ®); + rt2x00_set_field32(®, USB_DMA_CFG_PHY_CLEAR, 0); + rt2x00_set_field32(®, USB_DMA_CFG_RX_BULK_AGG_EN, 0); + rt2x00_set_field32(®, USB_DMA_CFG_RX_BULK_AGG_TIMEOUT, 128); + /* + * Total room for RX frames in kilobytes, PBF might still exceed + * this limit so reduce the number to prevent errors. + */ + rt2x00_set_field32(®, USB_DMA_CFG_RX_BULK_AGG_LIMIT, + ((rt2x00dev->ops->rx->entry_num * DATA_FRAME_SIZE) + / 1024) - 3); + rt2x00_set_field32(®, USB_DMA_CFG_RX_BULK_EN, 1); + rt2x00_set_field32(®, USB_DMA_CFG_TX_BULK_EN, 1); + rt2x00usb_register_write(rt2x00dev, USB_DMA_CFG, reg); + + return rt2800_enable_radio(rt2x00dev); +} + +static void rt2800usb_disable_radio(struct rt2x00_dev *rt2x00dev) +{ + rt2800_disable_radio(rt2x00dev); + rt2x00usb_disable_radio(rt2x00dev); +} + +static int rt2800usb_set_state(struct rt2x00_dev *rt2x00dev, + enum dev_state state) +{ + if (state == STATE_AWAKE) + rt2800_mcu_request(rt2x00dev, MCU_WAKEUP, 0xff, 0, 2); + else + rt2800_mcu_request(rt2x00dev, MCU_SLEEP, 0xff, 0xff, 2); + + return 0; +} + +static int rt2800usb_set_device_state(struct rt2x00_dev *rt2x00dev, + enum dev_state state) +{ + int retval = 0; + + switch (state) { + case STATE_RADIO_ON: + /* + * Before the radio can be enabled, the device first has + * to be woken up. After that it needs a bit of time + * to be fully awake and then the radio can be enabled. + */ + rt2800usb_set_state(rt2x00dev, STATE_AWAKE); + msleep(1); + retval = rt2800usb_enable_radio(rt2x00dev); + break; + case STATE_RADIO_OFF: + /* + * After the radio has been disabled, the device should + * be put to sleep for powersaving. + */ + rt2800usb_disable_radio(rt2x00dev); + rt2800usb_set_state(rt2x00dev, STATE_SLEEP); + break; + case STATE_RADIO_IRQ_ON: + case STATE_RADIO_IRQ_OFF: + /* No support, but no error either */ + break; + case STATE_DEEP_SLEEP: + case STATE_SLEEP: + case STATE_STANDBY: + case STATE_AWAKE: + retval = rt2800usb_set_state(rt2x00dev, state); + break; + default: + retval = -ENOTSUPP; + break; + } + + if (unlikely(retval)) + rt2x00_err(rt2x00dev, "Device failed to enter state %d (%d)\n", + state, retval); + + return retval; +} + +/* + * Watchdog handlers + */ +static void rt2800usb_watchdog(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i; + u32 reg; + + rt2x00usb_register_read(rt2x00dev, TXRXQ_PCNT, ®); + if (rt2x00_get_field32(reg, TXRXQ_PCNT_TX0Q)) { + rt2x00_warn(rt2x00dev, "TX HW queue 0 timed out, invoke forced kick\n"); + + rt2x00usb_register_write(rt2x00dev, PBF_CFG, 0xf40012); + + for (i = 0; i < 10; i++) { + udelay(10); + if (!rt2x00_get_field32(reg, TXRXQ_PCNT_TX0Q)) + break; + } + + rt2x00usb_register_write(rt2x00dev, PBF_CFG, 0xf40006); + } + + rt2x00usb_register_read(rt2x00dev, TXRXQ_PCNT, ®); + if (rt2x00_get_field32(reg, TXRXQ_PCNT_TX1Q)) { + rt2x00_warn(rt2x00dev, "TX HW queue 1 timed out, invoke forced kick\n"); + + rt2x00usb_register_write(rt2x00dev, PBF_CFG, 0xf4000a); + + for (i = 0; i < 10; i++) { + udelay(10); + if (!rt2x00_get_field32(reg, TXRXQ_PCNT_TX1Q)) + break; + } + + rt2x00usb_register_write(rt2x00dev, PBF_CFG, 0xf40006); + } + + rt2x00usb_watchdog(rt2x00dev); +} + +/* + * TX descriptor initialization + */ +static __le32 *rt2800usb_get_txwi(struct queue_entry *entry) +{ + if (entry->queue->qid == QID_BEACON) + return (__le32 *) (entry->skb->data); + else + return (__le32 *) (entry->skb->data + TXINFO_DESC_SIZE); +} + +static void rt2800usb_write_tx_desc(struct queue_entry *entry, + struct txentry_desc *txdesc) +{ + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + __le32 *txi = (__le32 *) entry->skb->data; + u32 word; + + /* + * Initialize TXINFO descriptor + */ + rt2x00_desc_read(txi, 0, &word); + + /* + * The size of TXINFO_W0_USB_DMA_TX_PKT_LEN is + * TXWI + 802.11 header + L2 pad + payload + pad, + * so need to decrease size of TXINFO. + */ + rt2x00_set_field32(&word, TXINFO_W0_USB_DMA_TX_PKT_LEN, + roundup(entry->skb->len, 4) - TXINFO_DESC_SIZE); + rt2x00_set_field32(&word, TXINFO_W0_WIV, + !test_bit(ENTRY_TXD_ENCRYPT_IV, &txdesc->flags)); + rt2x00_set_field32(&word, TXINFO_W0_QSEL, 2); + rt2x00_set_field32(&word, TXINFO_W0_SW_USE_LAST_ROUND, 0); + rt2x00_set_field32(&word, TXINFO_W0_USB_DMA_NEXT_VALID, 0); + rt2x00_set_field32(&word, TXINFO_W0_USB_DMA_TX_BURST, + test_bit(ENTRY_TXD_BURST, &txdesc->flags)); + rt2x00_desc_write(txi, 0, word); + + /* + * Register descriptor details in skb frame descriptor. + */ + skbdesc->flags |= SKBDESC_DESC_IN_SKB; + skbdesc->desc = txi; + skbdesc->desc_len = TXINFO_DESC_SIZE + entry->queue->winfo_size; +} + +/* + * TX data initialization + */ +static int rt2800usb_get_tx_data_len(struct queue_entry *entry) +{ + /* + * pad(1~3 bytes) is needed after each 802.11 payload. + * USB end pad(4 bytes) is needed at each USB bulk out packet end. + * TX frame format is : + * | TXINFO | TXWI | 802.11 header | L2 pad | payload | pad | USB end pad | + * |<------------- tx_pkt_len ------------->| + */ + + return roundup(entry->skb->len, 4) + 4; +} + +/* + * TX control handlers + */ +static enum txdone_entry_desc_flags +rt2800usb_txdone_entry_check(struct queue_entry *entry, u32 reg) +{ + __le32 *txwi; + u32 word; + int wcid, ack, pid; + int tx_wcid, tx_ack, tx_pid, is_agg; + + /* + * This frames has returned with an IO error, + * so the status report is not intended for this + * frame. + */ + if (test_bit(ENTRY_DATA_IO_FAILED, &entry->flags)) + return TXDONE_FAILURE; + + wcid = rt2x00_get_field32(reg, TX_STA_FIFO_WCID); + ack = rt2x00_get_field32(reg, TX_STA_FIFO_TX_ACK_REQUIRED); + pid = rt2x00_get_field32(reg, TX_STA_FIFO_PID_TYPE); + is_agg = rt2x00_get_field32(reg, TX_STA_FIFO_TX_AGGRE); + + /* + * Validate if this TX status report is intended for + * this entry by comparing the WCID/ACK/PID fields. + */ + txwi = rt2800usb_get_txwi(entry); + + rt2x00_desc_read(txwi, 1, &word); + tx_wcid = rt2x00_get_field32(word, TXWI_W1_WIRELESS_CLI_ID); + tx_ack = rt2x00_get_field32(word, TXWI_W1_ACK); + tx_pid = rt2x00_get_field32(word, TXWI_W1_PACKETID); + + if (wcid != tx_wcid || ack != tx_ack || (!is_agg && pid != tx_pid)) { + rt2x00_dbg(entry->queue->rt2x00dev, + "TX status report missed for queue %d entry %d\n", + entry->queue->qid, entry->entry_idx); + return TXDONE_UNKNOWN; + } + + return TXDONE_SUCCESS; +} + +static void rt2800usb_txdone(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + struct queue_entry *entry; + u32 reg; + u8 qid; + enum txdone_entry_desc_flags done_status; + + while (kfifo_get(&rt2x00dev->txstatus_fifo, ®)) { + /* + * TX_STA_FIFO_PID_QUEUE is a 2-bit field, thus qid is + * guaranteed to be one of the TX QIDs . + */ + qid = rt2x00_get_field32(reg, TX_STA_FIFO_PID_QUEUE); + queue = rt2x00queue_get_tx_queue(rt2x00dev, qid); + + if (unlikely(rt2x00queue_empty(queue))) { + rt2x00_warn(rt2x00dev, "Got TX status for an empty queue %u, dropping\n", + qid); + break; + } + + entry = rt2x00queue_get_entry(queue, Q_INDEX_DONE); + + if (unlikely(test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags) || + !test_bit(ENTRY_DATA_STATUS_PENDING, &entry->flags))) { + rt2x00_warn(rt2x00dev, "Data pending for entry %u in queue %u\n", + entry->entry_idx, qid); + break; + } + + done_status = rt2800usb_txdone_entry_check(entry, reg); + if (likely(done_status == TXDONE_SUCCESS)) + rt2800_txdone_entry(entry, reg, rt2800usb_get_txwi(entry)); + else + rt2x00lib_txdone_noinfo(entry, done_status); + } +} + +static void rt2800usb_txdone_nostatus(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + struct queue_entry *entry; + + /* + * Process any trailing TX status reports for IO failures, + * we loop until we find the first non-IO error entry. This + * can either be a frame which is free, is being uploaded, + * or has completed the upload but didn't have an entry + * in the TX_STAT_FIFO register yet. + */ + tx_queue_for_each(rt2x00dev, queue) { + while (!rt2x00queue_empty(queue)) { + entry = rt2x00queue_get_entry(queue, Q_INDEX_DONE); + + if (test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags) || + !test_bit(ENTRY_DATA_STATUS_PENDING, &entry->flags)) + break; + + if (test_bit(ENTRY_DATA_IO_FAILED, &entry->flags)) + rt2x00lib_txdone_noinfo(entry, TXDONE_FAILURE); + else if (rt2800usb_entry_txstatus_timeout(entry)) + rt2x00lib_txdone_noinfo(entry, TXDONE_UNKNOWN); + else + break; + } + } +} + +static void rt2800usb_work_txdone(struct work_struct *work) +{ + struct rt2x00_dev *rt2x00dev = + container_of(work, struct rt2x00_dev, txdone_work); + + while (!kfifo_is_empty(&rt2x00dev->txstatus_fifo) || + rt2800usb_txstatus_timeout(rt2x00dev)) { + + rt2800usb_txdone(rt2x00dev); + + rt2800usb_txdone_nostatus(rt2x00dev); + + /* + * The hw may delay sending the packet after DMA complete + * if the medium is busy, thus the TX_STA_FIFO entry is + * also delayed -> use a timer to retrieve it. + */ + if (rt2800usb_txstatus_pending(rt2x00dev)) + rt2800usb_async_read_tx_status(rt2x00dev); + } +} + +/* + * RX control handlers + */ +static void rt2800usb_fill_rxdone(struct queue_entry *entry, + struct rxdone_entry_desc *rxdesc) +{ + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + __le32 *rxi = (__le32 *)entry->skb->data; + __le32 *rxd; + u32 word; + int rx_pkt_len; + + /* + * Copy descriptor to the skbdesc->desc buffer, making it safe from + * moving of frame data in rt2x00usb. + */ + memcpy(skbdesc->desc, rxi, skbdesc->desc_len); + + /* + * RX frame format is : + * | RXINFO | RXWI | header | L2 pad | payload | pad | RXD | USB pad | + * |<------------ rx_pkt_len -------------->| + */ + rt2x00_desc_read(rxi, 0, &word); + rx_pkt_len = rt2x00_get_field32(word, RXINFO_W0_USB_DMA_RX_PKT_LEN); + + /* + * Remove the RXINFO structure from the sbk. + */ + skb_pull(entry->skb, RXINFO_DESC_SIZE); + + /* + * Check for rx_pkt_len validity. Return if invalid, leaving + * rxdesc->size zeroed out by the upper level. + */ + if (unlikely(rx_pkt_len == 0 || + rx_pkt_len > entry->queue->data_size)) { + rt2x00_err(entry->queue->rt2x00dev, + "Bad frame size %d, forcing to 0\n", rx_pkt_len); + return; + } + + rxd = (__le32 *)(entry->skb->data + rx_pkt_len); + + /* + * It is now safe to read the descriptor on all architectures. + */ + rt2x00_desc_read(rxd, 0, &word); + + if (rt2x00_get_field32(word, RXD_W0_CRC_ERROR)) + rxdesc->flags |= RX_FLAG_FAILED_FCS_CRC; + + rxdesc->cipher_status = rt2x00_get_field32(word, RXD_W0_CIPHER_ERROR); + + if (rt2x00_get_field32(word, RXD_W0_DECRYPTED)) { + /* + * Hardware has stripped IV/EIV data from 802.11 frame during + * decryption. Unfortunately the descriptor doesn't contain + * any fields with the EIV/IV data either, so they can't + * be restored by rt2x00lib. + */ + rxdesc->flags |= RX_FLAG_IV_STRIPPED; + + /* + * The hardware has already checked the Michael Mic and has + * stripped it from the frame. Signal this to mac80211. + */ + rxdesc->flags |= RX_FLAG_MMIC_STRIPPED; + + if (rxdesc->cipher_status == RX_CRYPTO_SUCCESS) + rxdesc->flags |= RX_FLAG_DECRYPTED; + else if (rxdesc->cipher_status == RX_CRYPTO_FAIL_MIC) + rxdesc->flags |= RX_FLAG_MMIC_ERROR; + } + + if (rt2x00_get_field32(word, RXD_W0_MY_BSS)) + rxdesc->dev_flags |= RXDONE_MY_BSS; + + if (rt2x00_get_field32(word, RXD_W0_L2PAD)) + rxdesc->dev_flags |= RXDONE_L2PAD; + + /* + * Remove RXD descriptor from end of buffer. + */ + skb_trim(entry->skb, rx_pkt_len); + + /* + * Process the RXWI structure. + */ + rt2800_process_rxwi(entry, rxdesc); +} + +/* + * Device probe functions. + */ +static int rt2800usb_read_eeprom(struct rt2x00_dev *rt2x00dev) +{ + int retval; + + if (rt2800_efuse_detect(rt2x00dev)) + retval = rt2800_read_eeprom_efuse(rt2x00dev); + else + retval = rt2x00usb_eeprom_read(rt2x00dev, rt2x00dev->eeprom, + EEPROM_SIZE); + + return retval; +} + +static int rt2800usb_probe_hw(struct rt2x00_dev *rt2x00dev) +{ + int retval; + + retval = rt2800_probe_hw(rt2x00dev); + if (retval) + return retval; + + /* + * Set txstatus timer function. + */ + rt2x00dev->txstatus_timer.function = rt2800usb_tx_sta_fifo_timeout; + + /* + * Overwrite TX done handler + */ + PREPARE_WORK(&rt2x00dev->txdone_work, rt2800usb_work_txdone); + + return 0; +} + +static const struct ieee80211_ops rt2800usb_mac80211_ops = { + .tx = rt2x00mac_tx, + .start = rt2x00mac_start, + .stop = rt2x00mac_stop, + .add_interface = rt2x00mac_add_interface, + .remove_interface = rt2x00mac_remove_interface, + .config = rt2x00mac_config, + .configure_filter = rt2x00mac_configure_filter, + .set_tim = rt2x00mac_set_tim, + .set_key = rt2x00mac_set_key, + .sw_scan_start = rt2x00mac_sw_scan_start, + .sw_scan_complete = rt2x00mac_sw_scan_complete, + .get_stats = rt2x00mac_get_stats, + .get_tkip_seq = rt2800_get_tkip_seq, + .set_rts_threshold = rt2800_set_rts_threshold, + .sta_add = rt2x00mac_sta_add, + .sta_remove = rt2x00mac_sta_remove, + .bss_info_changed = rt2x00mac_bss_info_changed, + .conf_tx = rt2800_conf_tx, + .get_tsf = rt2800_get_tsf, + .rfkill_poll = rt2x00mac_rfkill_poll, + .ampdu_action = rt2800_ampdu_action, + .flush = rt2x00mac_flush, + .get_survey = rt2800_get_survey, + .get_ringparam = rt2x00mac_get_ringparam, + .tx_frames_pending = rt2x00mac_tx_frames_pending, +}; + +static const struct rt2800_ops rt2800usb_rt2800_ops = { + .register_read = rt2x00usb_register_read, + .register_read_lock = rt2x00usb_register_read_lock, + .register_write = rt2x00usb_register_write, + .register_write_lock = rt2x00usb_register_write_lock, + .register_multiread = rt2x00usb_register_multiread, + .register_multiwrite = rt2x00usb_register_multiwrite, + .regbusy_read = rt2x00usb_regbusy_read, + .read_eeprom = rt2800usb_read_eeprom, + .hwcrypt_disabled = rt2800usb_hwcrypt_disabled, + .drv_write_firmware = rt2800usb_write_firmware, + .drv_init_registers = rt2800usb_init_registers, + .drv_get_txwi = rt2800usb_get_txwi, +}; + +static const struct rt2x00lib_ops rt2800usb_rt2x00_ops = { + .probe_hw = rt2800usb_probe_hw, + .get_firmware_name = rt2800usb_get_firmware_name, + .check_firmware = rt2800_check_firmware, + .load_firmware = rt2800_load_firmware, + .initialize = rt2x00usb_initialize, + .uninitialize = rt2x00usb_uninitialize, + .clear_entry = rt2x00usb_clear_entry, + .set_device_state = rt2800usb_set_device_state, + .rfkill_poll = rt2800_rfkill_poll, + .link_stats = rt2800_link_stats, + .reset_tuner = rt2800_reset_tuner, + .link_tuner = rt2800_link_tuner, + .gain_calibration = rt2800_gain_calibration, + .vco_calibration = rt2800_vco_calibration, + .watchdog = rt2800usb_watchdog, + .start_queue = rt2800usb_start_queue, + .kick_queue = rt2x00usb_kick_queue, + .stop_queue = rt2800usb_stop_queue, + .flush_queue = rt2x00usb_flush_queue, + .tx_dma_done = rt2800usb_tx_dma_done, + .write_tx_desc = rt2800usb_write_tx_desc, + .write_tx_data = rt2800_write_tx_data, + .write_beacon = rt2800_write_beacon, + .clear_beacon = rt2800_clear_beacon, + .get_tx_data_len = rt2800usb_get_tx_data_len, + .fill_rxdone = rt2800usb_fill_rxdone, + .config_shared_key = rt2800_config_shared_key, + .config_pairwise_key = rt2800_config_pairwise_key, + .config_filter = rt2800_config_filter, + .config_intf = rt2800_config_intf, + .config_erp = rt2800_config_erp, + .config_ant = rt2800_config_ant, + .config = rt2800_config, + .sta_add = rt2800_sta_add, + .sta_remove = rt2800_sta_remove, +}; + +static const struct data_queue_desc rt2800usb_queue_rx = { + .entry_num = 128, + .data_size = AGGREGATION_SIZE, + .desc_size = RXINFO_DESC_SIZE, + .winfo_size = RXWI_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_usb), +}; + +static const struct data_queue_desc rt2800usb_queue_tx = { + .entry_num = 16, + .data_size = AGGREGATION_SIZE, + .desc_size = TXINFO_DESC_SIZE, + .winfo_size = TXWI_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_usb), +}; + +static const struct data_queue_desc rt2800usb_queue_bcn = { + .entry_num = 8, + .data_size = MGMT_FRAME_SIZE, + .desc_size = TXINFO_DESC_SIZE, + .winfo_size = TXWI_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_usb), +}; + +static const struct rt2x00_ops rt2800usb_ops = { + .name = KBUILD_MODNAME, + .drv_data_size = sizeof(struct rt2800_drv_data), + .max_ap_intf = 8, + .eeprom_size = EEPROM_SIZE, + .rf_size = RF_SIZE, + .tx_queues = NUM_TX_QUEUES, + .extra_tx_headroom = TXINFO_DESC_SIZE + TXWI_DESC_SIZE, + .rx = &rt2800usb_queue_rx, + .tx = &rt2800usb_queue_tx, + .bcn = &rt2800usb_queue_bcn, + .lib = &rt2800usb_rt2x00_ops, + .drv = &rt2800usb_rt2800_ops, + .hw = &rt2800usb_mac80211_ops, +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS + .debugfs = &rt2800_rt2x00debug, +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ +}; + +static const struct data_queue_desc rt2800usb_queue_rx_5592 = { + .entry_num = 128, + .data_size = AGGREGATION_SIZE, + .desc_size = RXINFO_DESC_SIZE, + .winfo_size = RXWI_DESC_SIZE_5592, + .priv_size = sizeof(struct queue_entry_priv_usb), +}; + +static const struct data_queue_desc rt2800usb_queue_tx_5592 = { + .entry_num = 16, + .data_size = AGGREGATION_SIZE, + .desc_size = TXINFO_DESC_SIZE, + .winfo_size = TXWI_DESC_SIZE_5592, + .priv_size = sizeof(struct queue_entry_priv_usb), +}; + +static const struct data_queue_desc rt2800usb_queue_bcn_5592 = { + .entry_num = 8, + .data_size = MGMT_FRAME_SIZE, + .desc_size = TXINFO_DESC_SIZE, + .winfo_size = TXWI_DESC_SIZE_5592, + .priv_size = sizeof(struct queue_entry_priv_usb), +}; + + +static const struct rt2x00_ops rt2800usb_ops_5592 = { + .name = KBUILD_MODNAME, + .drv_data_size = sizeof(struct rt2800_drv_data), + .max_ap_intf = 8, + .eeprom_size = EEPROM_SIZE, + .rf_size = RF_SIZE, + .tx_queues = NUM_TX_QUEUES, + .extra_tx_headroom = TXINFO_DESC_SIZE + TXWI_DESC_SIZE_5592, + .rx = &rt2800usb_queue_rx_5592, + .tx = &rt2800usb_queue_tx_5592, + .bcn = &rt2800usb_queue_bcn_5592, + .lib = &rt2800usb_rt2x00_ops, + .drv = &rt2800usb_rt2800_ops, + .hw = &rt2800usb_mac80211_ops, +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS + .debugfs = &rt2800_rt2x00debug, +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ +}; + +/* + * rt2800usb module information. + */ +static struct usb_device_id rt2800usb_device_table[] = { + /* Abocom */ + { USB_DEVICE(0x07b8, 0x2870) }, + { USB_DEVICE(0x07b8, 0x2770) }, + { USB_DEVICE(0x07b8, 0x3070) }, + { USB_DEVICE(0x07b8, 0x3071) }, + { USB_DEVICE(0x07b8, 0x3072) }, + { USB_DEVICE(0x1482, 0x3c09) }, + /* AirTies */ + { USB_DEVICE(0x1eda, 0x2012) }, + { USB_DEVICE(0x1eda, 0x2210) }, + { USB_DEVICE(0x1eda, 0x2310) }, + /* Allwin */ + { USB_DEVICE(0x8516, 0x2070) }, + { USB_DEVICE(0x8516, 0x2770) }, + { USB_DEVICE(0x8516, 0x2870) }, + { USB_DEVICE(0x8516, 0x3070) }, + { USB_DEVICE(0x8516, 0x3071) }, + { USB_DEVICE(0x8516, 0x3072) }, + /* Alpha Networks */ + { USB_DEVICE(0x14b2, 0x3c06) }, + { USB_DEVICE(0x14b2, 0x3c07) }, + { USB_DEVICE(0x14b2, 0x3c09) }, + { USB_DEVICE(0x14b2, 0x3c12) }, + { USB_DEVICE(0x14b2, 0x3c23) }, + { USB_DEVICE(0x14b2, 0x3c25) }, + { USB_DEVICE(0x14b2, 0x3c27) }, + { USB_DEVICE(0x14b2, 0x3c28) }, + { USB_DEVICE(0x14b2, 0x3c2c) }, + /* Amit */ + { USB_DEVICE(0x15c5, 0x0008) }, + /* Askey */ + { USB_DEVICE(0x1690, 0x0740) }, + /* ASUS */ + { USB_DEVICE(0x0b05, 0x1731) }, + { USB_DEVICE(0x0b05, 0x1732) }, + { USB_DEVICE(0x0b05, 0x1742) }, + { USB_DEVICE(0x0b05, 0x1784) }, + { USB_DEVICE(0x1761, 0x0b05) }, + /* AzureWave */ + { USB_DEVICE(0x13d3, 0x3247) }, + { USB_DEVICE(0x13d3, 0x3273) }, + { USB_DEVICE(0x13d3, 0x3305) }, + { USB_DEVICE(0x13d3, 0x3307) }, + { USB_DEVICE(0x13d3, 0x3321) }, + /* Belkin */ + { USB_DEVICE(0x050d, 0x8053) }, + { USB_DEVICE(0x050d, 0x805c) }, + { USB_DEVICE(0x050d, 0x815c) }, + { USB_DEVICE(0x050d, 0x825a) }, + { USB_DEVICE(0x050d, 0x825b) }, + { USB_DEVICE(0x050d, 0x935a) }, + { USB_DEVICE(0x050d, 0x935b) }, + /* Buffalo */ + { USB_DEVICE(0x0411, 0x00e8) }, + { USB_DEVICE(0x0411, 0x0158) }, + { USB_DEVICE(0x0411, 0x015d) }, + { USB_DEVICE(0x0411, 0x016f) }, + { USB_DEVICE(0x0411, 0x01a2) }, + { USB_DEVICE(0x0411, 0x01ee) }, + /* Corega */ + { USB_DEVICE(0x07aa, 0x002f) }, + { USB_DEVICE(0x07aa, 0x003c) }, + { USB_DEVICE(0x07aa, 0x003f) }, + { USB_DEVICE(0x18c5, 0x0012) }, + /* D-Link */ + { USB_DEVICE(0x07d1, 0x3c09) }, + { USB_DEVICE(0x07d1, 0x3c0a) }, + { USB_DEVICE(0x07d1, 0x3c0d) }, + { USB_DEVICE(0x07d1, 0x3c0e) }, + { USB_DEVICE(0x07d1, 0x3c0f) }, + { USB_DEVICE(0x07d1, 0x3c11) }, + { USB_DEVICE(0x07d1, 0x3c13) }, + { USB_DEVICE(0x07d1, 0x3c15) }, + { USB_DEVICE(0x07d1, 0x3c16) }, + { USB_DEVICE(0x07d1, 0x3c17) }, + { USB_DEVICE(0x2001, 0x3c1b) }, + /* Draytek */ + { USB_DEVICE(0x07fa, 0x7712) }, + /* DVICO */ + { USB_DEVICE(0x0fe9, 0xb307) }, + /* Edimax */ + { USB_DEVICE(0x7392, 0x4085) }, + { USB_DEVICE(0x7392, 0x7711) }, + { USB_DEVICE(0x7392, 0x7717) }, + { USB_DEVICE(0x7392, 0x7718) }, + { USB_DEVICE(0x7392, 0x7722) }, + /* Encore */ + { USB_DEVICE(0x203d, 0x1480) }, + { USB_DEVICE(0x203d, 0x14a9) }, + /* EnGenius */ + { USB_DEVICE(0x1740, 0x9701) }, + { USB_DEVICE(0x1740, 0x9702) }, + { USB_DEVICE(0x1740, 0x9703) }, + { USB_DEVICE(0x1740, 0x9705) }, + { USB_DEVICE(0x1740, 0x9706) }, + { USB_DEVICE(0x1740, 0x9707) }, + { USB_DEVICE(0x1740, 0x9708) }, + { USB_DEVICE(0x1740, 0x9709) }, + /* Gemtek */ + { USB_DEVICE(0x15a9, 0x0012) }, + /* Gigabyte */ + { USB_DEVICE(0x1044, 0x800b) }, + { USB_DEVICE(0x1044, 0x800d) }, + /* Hawking */ + { USB_DEVICE(0x0e66, 0x0001) }, + { USB_DEVICE(0x0e66, 0x0003) }, + { USB_DEVICE(0x0e66, 0x0009) }, + { USB_DEVICE(0x0e66, 0x000b) }, + { USB_DEVICE(0x0e66, 0x0013) }, + { USB_DEVICE(0x0e66, 0x0017) }, + { USB_DEVICE(0x0e66, 0x0018) }, + /* I-O DATA */ + { USB_DEVICE(0x04bb, 0x0945) }, + { USB_DEVICE(0x04bb, 0x0947) }, + { USB_DEVICE(0x04bb, 0x0948) }, + /* Linksys */ + { USB_DEVICE(0x13b1, 0x0031) }, + { USB_DEVICE(0x1737, 0x0070) }, + { USB_DEVICE(0x1737, 0x0071) }, + { USB_DEVICE(0x1737, 0x0077) }, + { USB_DEVICE(0x1737, 0x0078) }, + /* Logitec */ + { USB_DEVICE(0x0789, 0x0162) }, + { USB_DEVICE(0x0789, 0x0163) }, + { USB_DEVICE(0x0789, 0x0164) }, + { USB_DEVICE(0x0789, 0x0166) }, + /* Motorola */ + { USB_DEVICE(0x100d, 0x9031) }, + /* MSI */ + { USB_DEVICE(0x0db0, 0x3820) }, + { USB_DEVICE(0x0db0, 0x3821) }, + { USB_DEVICE(0x0db0, 0x3822) }, + { USB_DEVICE(0x0db0, 0x3870) }, + { USB_DEVICE(0x0db0, 0x3871) }, + { USB_DEVICE(0x0db0, 0x6899) }, + { USB_DEVICE(0x0db0, 0x821a) }, + { USB_DEVICE(0x0db0, 0x822a) }, + { USB_DEVICE(0x0db0, 0x822b) }, + { USB_DEVICE(0x0db0, 0x822c) }, + { USB_DEVICE(0x0db0, 0x870a) }, + { USB_DEVICE(0x0db0, 0x871a) }, + { USB_DEVICE(0x0db0, 0x871b) }, + { USB_DEVICE(0x0db0, 0x871c) }, + { USB_DEVICE(0x0db0, 0x899a) }, + /* Ovislink */ + { USB_DEVICE(0x1b75, 0x3071) }, + { USB_DEVICE(0x1b75, 0x3072) }, + /* Para */ + { USB_DEVICE(0x20b8, 0x8888) }, + /* Pegatron */ + { USB_DEVICE(0x1d4d, 0x0002) }, + { USB_DEVICE(0x1d4d, 0x000c) }, + { USB_DEVICE(0x1d4d, 0x000e) }, + { USB_DEVICE(0x1d4d, 0x0011) }, + /* Philips */ + { USB_DEVICE(0x0471, 0x200f) }, + /* Planex */ + { USB_DEVICE(0x2019, 0x5201) }, + { USB_DEVICE(0x2019, 0xab25) }, + { USB_DEVICE(0x2019, 0xed06) }, + /* Quanta */ + { USB_DEVICE(0x1a32, 0x0304) }, + /* Ralink */ + { USB_DEVICE(0x148f, 0x2070) }, + { USB_DEVICE(0x148f, 0x2770) }, + { USB_DEVICE(0x148f, 0x2870) }, + { USB_DEVICE(0x148f, 0x3070) }, + { USB_DEVICE(0x148f, 0x3071) }, + { USB_DEVICE(0x148f, 0x3072) }, + /* Samsung */ + { USB_DEVICE(0x04e8, 0x2018) }, + /* Siemens */ + { USB_DEVICE(0x129b, 0x1828) }, + /* Sitecom */ + { USB_DEVICE(0x0df6, 0x0017) }, + { USB_DEVICE(0x0df6, 0x002b) }, + { USB_DEVICE(0x0df6, 0x002c) }, + { USB_DEVICE(0x0df6, 0x002d) }, + { USB_DEVICE(0x0df6, 0x0039) }, + { USB_DEVICE(0x0df6, 0x003b) }, + { USB_DEVICE(0x0df6, 0x003d) }, + { USB_DEVICE(0x0df6, 0x003e) }, + { USB_DEVICE(0x0df6, 0x003f) }, + { USB_DEVICE(0x0df6, 0x0040) }, + { USB_DEVICE(0x0df6, 0x0042) }, + { USB_DEVICE(0x0df6, 0x0047) }, + { USB_DEVICE(0x0df6, 0x0048) }, + { USB_DEVICE(0x0df6, 0x0051) }, + { USB_DEVICE(0x0df6, 0x005f) }, + { USB_DEVICE(0x0df6, 0x0060) }, + /* SMC */ + { USB_DEVICE(0x083a, 0x6618) }, + { USB_DEVICE(0x083a, 0x7511) }, + { USB_DEVICE(0x083a, 0x7512) }, + { USB_DEVICE(0x083a, 0x7522) }, + { USB_DEVICE(0x083a, 0x8522) }, + { USB_DEVICE(0x083a, 0xa618) }, + { USB_DEVICE(0x083a, 0xa701) }, + { USB_DEVICE(0x083a, 0xa702) }, + { USB_DEVICE(0x083a, 0xa703) }, + { USB_DEVICE(0x083a, 0xb522) }, + /* Sparklan */ + { USB_DEVICE(0x15a9, 0x0006) }, + /* Sweex */ + { USB_DEVICE(0x177f, 0x0153) }, + { USB_DEVICE(0x177f, 0x0164) }, + { USB_DEVICE(0x177f, 0x0302) }, + { USB_DEVICE(0x177f, 0x0313) }, + { USB_DEVICE(0x177f, 0x0323) }, + { USB_DEVICE(0x177f, 0x0324) }, + /* U-Media */ + { USB_DEVICE(0x157e, 0x300e) }, + { USB_DEVICE(0x157e, 0x3013) }, + /* ZCOM */ + { USB_DEVICE(0x0cde, 0x0022) }, + { USB_DEVICE(0x0cde, 0x0025) }, + /* Zinwell */ + { USB_DEVICE(0x5a57, 0x0280) }, + { USB_DEVICE(0x5a57, 0x0282) }, + { USB_DEVICE(0x5a57, 0x0283) }, + { USB_DEVICE(0x5a57, 0x5257) }, + /* Zyxel */ + { USB_DEVICE(0x0586, 0x3416) }, + { USB_DEVICE(0x0586, 0x3418) }, + { USB_DEVICE(0x0586, 0x341a) }, + { USB_DEVICE(0x0586, 0x341e) }, + { USB_DEVICE(0x0586, 0x343e) }, +#ifdef CPTCFG_RT2800USB_RT33XX + /* Belkin */ + { USB_DEVICE(0x050d, 0x945b) }, + /* D-Link */ + { USB_DEVICE(0x2001, 0x3c17) }, + /* Panasonic */ + { USB_DEVICE(0x083a, 0xb511) }, + /* Philips */ + { USB_DEVICE(0x0471, 0x20dd) }, + /* Ralink */ + { USB_DEVICE(0x148f, 0x3370) }, + { USB_DEVICE(0x148f, 0x8070) }, + /* Sitecom */ + { USB_DEVICE(0x0df6, 0x0050) }, + /* Sweex */ + { USB_DEVICE(0x177f, 0x0163) }, + { USB_DEVICE(0x177f, 0x0165) }, +#endif +#ifdef CPTCFG_RT2800USB_RT35XX + /* Allwin */ + { USB_DEVICE(0x8516, 0x3572) }, + /* Askey */ + { USB_DEVICE(0x1690, 0x0744) }, + { USB_DEVICE(0x1690, 0x0761) }, + { USB_DEVICE(0x1690, 0x0764) }, + /* ASUS */ + { USB_DEVICE(0x0b05, 0x179d) }, + /* Cisco */ + { USB_DEVICE(0x167b, 0x4001) }, + /* EnGenius */ + { USB_DEVICE(0x1740, 0x9801) }, + /* I-O DATA */ + { USB_DEVICE(0x04bb, 0x0944) }, + /* Linksys */ + { USB_DEVICE(0x13b1, 0x002f) }, + { USB_DEVICE(0x1737, 0x0079) }, + /* Ralink */ + { USB_DEVICE(0x148f, 0x3572) }, + /* Sitecom */ + { USB_DEVICE(0x0df6, 0x0041) }, + { USB_DEVICE(0x0df6, 0x0062) }, + { USB_DEVICE(0x0df6, 0x0065) }, + { USB_DEVICE(0x0df6, 0x0066) }, + { USB_DEVICE(0x0df6, 0x0068) }, + /* Toshiba */ + { USB_DEVICE(0x0930, 0x0a07) }, + /* Zinwell */ + { USB_DEVICE(0x5a57, 0x0284) }, +#endif +#ifdef CPTCFG_RT2800USB_RT53XX + /* Arcadyan */ + { USB_DEVICE(0x043e, 0x7a12) }, + { USB_DEVICE(0x043e, 0x7a32) }, + /* Azurewave */ + { USB_DEVICE(0x13d3, 0x3329) }, + { USB_DEVICE(0x13d3, 0x3365) }, + /* D-Link */ + { USB_DEVICE(0x2001, 0x3c15) }, + { USB_DEVICE(0x2001, 0x3c19) }, + { USB_DEVICE(0x2001, 0x3c1c) }, + { USB_DEVICE(0x2001, 0x3c1d) }, + { USB_DEVICE(0x2001, 0x3c1e) }, + /* LG innotek */ + { USB_DEVICE(0x043e, 0x7a22) }, + { USB_DEVICE(0x043e, 0x7a42) }, + /* Panasonic */ + { USB_DEVICE(0x04da, 0x1801) }, + { USB_DEVICE(0x04da, 0x1800) }, + { USB_DEVICE(0x04da, 0x23f6) }, + /* Philips */ + { USB_DEVICE(0x0471, 0x2104) }, + { USB_DEVICE(0x0471, 0x2126) }, + { USB_DEVICE(0x0471, 0x2180) }, + { USB_DEVICE(0x0471, 0x2181) }, + { USB_DEVICE(0x0471, 0x2182) }, + /* Ralink */ + { USB_DEVICE(0x148f, 0x5370) }, + { USB_DEVICE(0x148f, 0x5372) }, +#endif +#ifdef CPTCFG_RT2800USB_RT55XX + /* Arcadyan */ + { USB_DEVICE(0x043e, 0x7a32), .driver_info = 5592 }, + /* AVM GmbH */ + { USB_DEVICE(0x057c, 0x8501), .driver_info = 5592 }, + /* D-Link DWA-160-B2 */ + { USB_DEVICE(0x2001, 0x3c1a), .driver_info = 5592 }, + /* Proware */ + { USB_DEVICE(0x043e, 0x7a13), .driver_info = 5592 }, + /* Ralink */ + { USB_DEVICE(0x148f, 0x5572), .driver_info = 5592 }, +#endif +#ifdef CPTCFG_RT2800USB_UNKNOWN + /* + * Unclear what kind of devices these are (they aren't supported by the + * vendor linux driver). + */ + /* Abocom */ + { USB_DEVICE(0x07b8, 0x3073) }, + { USB_DEVICE(0x07b8, 0x3074) }, + /* Alpha Networks */ + { USB_DEVICE(0x14b2, 0x3c08) }, + { USB_DEVICE(0x14b2, 0x3c11) }, + /* Amigo */ + { USB_DEVICE(0x0e0b, 0x9031) }, + { USB_DEVICE(0x0e0b, 0x9041) }, + /* ASUS */ + { USB_DEVICE(0x0b05, 0x166a) }, + { USB_DEVICE(0x0b05, 0x1760) }, + { USB_DEVICE(0x0b05, 0x1761) }, + { USB_DEVICE(0x0b05, 0x1790) }, + { USB_DEVICE(0x0b05, 0x17a7) }, + /* AzureWave */ + { USB_DEVICE(0x13d3, 0x3262) }, + { USB_DEVICE(0x13d3, 0x3284) }, + { USB_DEVICE(0x13d3, 0x3322) }, + { USB_DEVICE(0x13d3, 0x3340) }, + { USB_DEVICE(0x13d3, 0x3399) }, + { USB_DEVICE(0x13d3, 0x3400) }, + { USB_DEVICE(0x13d3, 0x3401) }, + /* Belkin */ + { USB_DEVICE(0x050d, 0x1003) }, + /* Buffalo */ + { USB_DEVICE(0x0411, 0x012e) }, + { USB_DEVICE(0x0411, 0x0148) }, + { USB_DEVICE(0x0411, 0x0150) }, + /* Corega */ + { USB_DEVICE(0x07aa, 0x0041) }, + { USB_DEVICE(0x07aa, 0x0042) }, + { USB_DEVICE(0x18c5, 0x0008) }, + /* D-Link */ + { USB_DEVICE(0x07d1, 0x3c0b) }, + /* Encore */ + { USB_DEVICE(0x203d, 0x14a1) }, + /* EnGenius */ + { USB_DEVICE(0x1740, 0x0600) }, + { USB_DEVICE(0x1740, 0x0602) }, + /* Gemtek */ + { USB_DEVICE(0x15a9, 0x0010) }, + /* Gigabyte */ + { USB_DEVICE(0x1044, 0x800c) }, + /* Hercules */ + { USB_DEVICE(0x06f8, 0xe036) }, + /* Huawei */ + { USB_DEVICE(0x148f, 0xf101) }, + /* I-O DATA */ + { USB_DEVICE(0x04bb, 0x094b) }, + /* LevelOne */ + { USB_DEVICE(0x1740, 0x0605) }, + { USB_DEVICE(0x1740, 0x0615) }, + /* Logitec */ + { USB_DEVICE(0x0789, 0x0168) }, + { USB_DEVICE(0x0789, 0x0169) }, + /* Motorola */ + { USB_DEVICE(0x100d, 0x9032) }, + /* Pegatron */ + { USB_DEVICE(0x05a6, 0x0101) }, + { USB_DEVICE(0x1d4d, 0x0010) }, + /* Planex */ + { USB_DEVICE(0x2019, 0xab24) }, + /* Qcom */ + { USB_DEVICE(0x18e8, 0x6259) }, + /* RadioShack */ + { USB_DEVICE(0x08b9, 0x1197) }, + /* Sitecom */ + { USB_DEVICE(0x0df6, 0x003c) }, + { USB_DEVICE(0x0df6, 0x004a) }, + { USB_DEVICE(0x0df6, 0x004d) }, + { USB_DEVICE(0x0df6, 0x0053) }, + { USB_DEVICE(0x0df6, 0x0069) }, + { USB_DEVICE(0x0df6, 0x006f) }, + /* SMC */ + { USB_DEVICE(0x083a, 0xa512) }, + { USB_DEVICE(0x083a, 0xc522) }, + { USB_DEVICE(0x083a, 0xd522) }, + { USB_DEVICE(0x083a, 0xf511) }, + /* Sweex */ + { USB_DEVICE(0x177f, 0x0254) }, + /* TP-LINK */ + { USB_DEVICE(0xf201, 0x5370) }, +#endif + { 0, } +}; + +MODULE_AUTHOR(DRV_PROJECT); +MODULE_VERSION(DRV_VERSION); +MODULE_DESCRIPTION("Ralink RT2800 USB Wireless LAN driver."); +MODULE_SUPPORTED_DEVICE("Ralink RT2870 USB chipset based cards"); +MODULE_DEVICE_TABLE(usb, rt2800usb_device_table); +MODULE_FIRMWARE(FIRMWARE_RT2870); +MODULE_LICENSE("GPL"); + +static int rt2800usb_probe(struct usb_interface *usb_intf, + const struct usb_device_id *id) +{ + if (id->driver_info == 5592) + return rt2x00usb_probe(usb_intf, &rt2800usb_ops_5592); + + return rt2x00usb_probe(usb_intf, &rt2800usb_ops); +} + +static struct usb_driver rt2800usb_driver = { + .name = KBUILD_MODNAME, + .id_table = rt2800usb_device_table, + .probe = rt2800usb_probe, + .disconnect = rt2x00usb_disconnect, + .suspend = rt2x00usb_suspend, + .resume = rt2x00usb_resume, + .reset_resume = rt2x00usb_resume, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +module_usb_driver(rt2800usb_driver); diff --git a/drivers/net/wireless/rt2x00/rt2800usb.h b/drivers/net/wireless/rt2x00/rt2800usb.h new file mode 100644 index 0000000..671ea35 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2800usb.h @@ -0,0 +1,112 @@ +/* + Copyright (C) 2009 Ivo van Doorn + Copyright (C) 2009 Mattias Nissler + Copyright (C) 2009 Felix Fietkau + Copyright (C) 2009 Xose Vazquez Perez + Copyright (C) 2009 Axel Kollhofer + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2800usb + Abstract: Data structures and registers for the rt2800usb module. + Supported chipsets: RT2800U. + */ + +#ifndef RT2800USB_H +#define RT2800USB_H + +/* + * 8051 firmware image. + */ +#define FIRMWARE_RT2870 "rt2870.bin" +#define FIRMWARE_IMAGE_BASE 0x3000 + +/* + * DMA descriptor defines. + */ +#define TXINFO_DESC_SIZE (1 * sizeof(__le32)) +#define RXINFO_DESC_SIZE (1 * sizeof(__le32)) + +/* + * TX Info structure + */ + +/* + * Word0 + * WIV: Wireless Info Valid. 1: Driver filled WI, 0: DMA needs to copy WI + * QSEL: Select on-chip FIFO ID for 2nd-stage output scheduler. + * 0:MGMT, 1:HCCA 2:EDCA + * USB_DMA_NEXT_VALID: Used ONLY in USB bulk Aggregation, NextValid + * DMA_TX_BURST: used ONLY in USB bulk Aggregation. + * Force USB DMA transmit frame from current selected endpoint + */ +#define TXINFO_W0_USB_DMA_TX_PKT_LEN FIELD32(0x0000ffff) +#define TXINFO_W0_WIV FIELD32(0x01000000) +#define TXINFO_W0_QSEL FIELD32(0x06000000) +#define TXINFO_W0_SW_USE_LAST_ROUND FIELD32(0x08000000) +#define TXINFO_W0_USB_DMA_NEXT_VALID FIELD32(0x40000000) +#define TXINFO_W0_USB_DMA_TX_BURST FIELD32(0x80000000) + +/* + * RX Info structure + */ + +/* + * Word 0 + */ + +#define RXINFO_W0_USB_DMA_RX_PKT_LEN FIELD32(0x0000ffff) + +/* + * RX descriptor format for RX Ring. + */ + +/* + * Word0 + * UNICAST_TO_ME: This RX frame is unicast to me. + * MULTICAST: This is a multicast frame. + * BROADCAST: This is a broadcast frame. + * MY_BSS: this frame belongs to the same BSSID. + * CRC_ERROR: CRC error. + * CIPHER_ERROR: 0: decryption okay, 1:ICV error, 2:MIC error, 3:KEY not valid. + * AMSDU: rx with 802.3 header, not 802.11 header. + */ + +#define RXD_W0_BA FIELD32(0x00000001) +#define RXD_W0_DATA FIELD32(0x00000002) +#define RXD_W0_NULLDATA FIELD32(0x00000004) +#define RXD_W0_FRAG FIELD32(0x00000008) +#define RXD_W0_UNICAST_TO_ME FIELD32(0x00000010) +#define RXD_W0_MULTICAST FIELD32(0x00000020) +#define RXD_W0_BROADCAST FIELD32(0x00000040) +#define RXD_W0_MY_BSS FIELD32(0x00000080) +#define RXD_W0_CRC_ERROR FIELD32(0x00000100) +#define RXD_W0_CIPHER_ERROR FIELD32(0x00000600) +#define RXD_W0_AMSDU FIELD32(0x00000800) +#define RXD_W0_HTC FIELD32(0x00001000) +#define RXD_W0_RSSI FIELD32(0x00002000) +#define RXD_W0_L2PAD FIELD32(0x00004000) +#define RXD_W0_AMPDU FIELD32(0x00008000) +#define RXD_W0_DECRYPTED FIELD32(0x00010000) +#define RXD_W0_PLCP_RSSI FIELD32(0x00020000) +#define RXD_W0_CIPHER_ALG FIELD32(0x00040000) +#define RXD_W0_LAST_AMSDU FIELD32(0x00080000) +#define RXD_W0_PLCP_SIGNAL FIELD32(0xfff00000) + +#endif /* RT2800USB_H */ diff --git a/drivers/net/wireless/rt2x00/rt2x00.h b/drivers/net/wireless/rt2x00/rt2x00.h new file mode 100644 index 0000000..7834bab --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00.h @@ -0,0 +1,1393 @@ +/* + Copyright (C) 2010 Willow Garage + Copyright (C) 2004 - 2010 Ivo van Doorn + Copyright (C) 2004 - 2009 Gertjan van Wingerde + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00 + Abstract: rt2x00 global information. + */ + +#ifndef RT2X00_H +#define RT2X00_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "rt2x00debug.h" +#include "rt2x00dump.h" +#include "rt2x00leds.h" +#include "rt2x00reg.h" +#include "rt2x00queue.h" + +/* + * Module information. + */ +#define DRV_VERSION "2.3.0" +#define DRV_PROJECT "http://rt2x00.serialmonkey.com" + +/* Debug definitions. + * Debug output has to be enabled during compile time. + */ +#ifdef CPTCFG_RT2X00_DEBUG +#define DEBUG +#endif /* CPTCFG_RT2X00_DEBUG */ + +/* Utility printing macros + * rt2x00_probe_err is for messages when rt2x00_dev is uninitialized + */ +#define rt2x00_probe_err(fmt, ...) \ + printk(KERN_ERR KBUILD_MODNAME ": %s: Error - " fmt, \ + __func__, ##__VA_ARGS__) +#define rt2x00_err(dev, fmt, ...) \ + wiphy_err((dev)->hw->wiphy, "%s: Error - " fmt, \ + __func__, ##__VA_ARGS__) +#define rt2x00_warn(dev, fmt, ...) \ + wiphy_warn((dev)->hw->wiphy, "%s: Warning - " fmt, \ + __func__, ##__VA_ARGS__) +#define rt2x00_info(dev, fmt, ...) \ + wiphy_info((dev)->hw->wiphy, "%s: Info - " fmt, \ + __func__, ##__VA_ARGS__) + +/* Various debug levels */ +#define rt2x00_dbg(dev, fmt, ...) \ + wiphy_dbg((dev)->hw->wiphy, "%s: Debug - " fmt, \ + __func__, ##__VA_ARGS__) +#define rt2x00_eeprom_dbg(dev, fmt, ...) \ + wiphy_dbg((dev)->hw->wiphy, "%s: EEPROM recovery - " fmt, \ + __func__, ##__VA_ARGS__) + +/* + * Duration calculations + * The rate variable passed is: 100kbs. + * To convert from bytes to bits we multiply size with 8, + * then the size is multiplied with 10 to make the + * real rate -> rate argument correction. + */ +#define GET_DURATION(__size, __rate) (((__size) * 8 * 10) / (__rate)) +#define GET_DURATION_RES(__size, __rate)(((__size) * 8 * 10) % (__rate)) + +/* + * Determine the number of L2 padding bytes required between the header and + * the payload. + */ +#define L2PAD_SIZE(__hdrlen) (-(__hdrlen) & 3) + +/* + * Determine the alignment requirement, + * to make sure the 802.11 payload is padded to a 4-byte boundrary + * we must determine the address of the payload and calculate the + * amount of bytes needed to move the data. + */ +#define ALIGN_SIZE(__skb, __header) \ + ( ((unsigned long)((__skb)->data + (__header))) & 3 ) + +/* + * Constants for extra TX headroom for alignment purposes. + */ +#define RT2X00_ALIGN_SIZE 4 /* Only whole frame needs alignment */ +#define RT2X00_L2PAD_SIZE 8 /* Both header & payload need alignment */ + +/* + * Standard timing and size defines. + * These values should follow the ieee80211 specifications. + */ +#define ACK_SIZE 14 +#define IEEE80211_HEADER 24 +#define PLCP 48 +#define BEACON 100 +#define PREAMBLE 144 +#define SHORT_PREAMBLE 72 +#define SLOT_TIME 20 +#define SHORT_SLOT_TIME 9 +#define SIFS 10 +#define PIFS ( SIFS + SLOT_TIME ) +#define SHORT_PIFS ( SIFS + SHORT_SLOT_TIME ) +#define DIFS ( PIFS + SLOT_TIME ) +#define SHORT_DIFS ( SHORT_PIFS + SHORT_SLOT_TIME ) +#define EIFS ( SIFS + DIFS + \ + GET_DURATION(IEEE80211_HEADER + ACK_SIZE, 10) ) +#define SHORT_EIFS ( SIFS + SHORT_DIFS + \ + GET_DURATION(IEEE80211_HEADER + ACK_SIZE, 10) ) + +/* + * Structure for average calculation + * The avg field contains the actual average value, + * but avg_weight is internally used during calculations + * to prevent rounding errors. + */ +struct avg_val { + int avg; + int avg_weight; +}; + +enum rt2x00_chip_intf { + RT2X00_CHIP_INTF_PCI, + RT2X00_CHIP_INTF_PCIE, + RT2X00_CHIP_INTF_USB, + RT2X00_CHIP_INTF_SOC, +}; + +/* + * Chipset identification + * The chipset on the device is composed of a RT and RF chip. + * The chipset combination is important for determining device capabilities. + */ +struct rt2x00_chip { + u16 rt; +#define RT2460 0x2460 +#define RT2560 0x2560 +#define RT2570 0x2570 +#define RT2661 0x2661 +#define RT2573 0x2573 +#define RT2860 0x2860 /* 2.4GHz */ +#define RT2872 0x2872 /* WSOC */ +#define RT2883 0x2883 /* WSOC */ +#define RT3070 0x3070 +#define RT3071 0x3071 +#define RT3090 0x3090 /* 2.4GHz PCIe */ +#define RT3290 0x3290 +#define RT3352 0x3352 /* WSOC */ +#define RT3390 0x3390 +#define RT3572 0x3572 +#define RT3593 0x3593 +#define RT3883 0x3883 /* WSOC */ +#define RT5390 0x5390 /* 2.4GHz */ +#define RT5392 0x5392 /* 2.4GHz */ +#define RT5592 0x5592 + + u16 rf; + u16 rev; + + enum rt2x00_chip_intf intf; +}; + +/* + * RF register values that belong to a particular channel. + */ +struct rf_channel { + int channel; + u32 rf1; + u32 rf2; + u32 rf3; + u32 rf4; +}; + +/* + * Channel information structure + */ +struct channel_info { + unsigned int flags; +#define GEOGRAPHY_ALLOWED 0x00000001 + + short max_power; + short default_power1; + short default_power2; +}; + +/* + * Antenna setup values. + */ +struct antenna_setup { + enum antenna rx; + enum antenna tx; + u8 rx_chain_num; + u8 tx_chain_num; +}; + +/* + * Quality statistics about the currently active link. + */ +struct link_qual { + /* + * Statistics required for Link tuning by driver + * The rssi value is provided by rt2x00lib during the + * link_tuner() callback function. + * The false_cca field is filled during the link_stats() + * callback function and could be used during the + * link_tuner() callback function. + */ + int rssi; + int false_cca; + + /* + * VGC levels + * Hardware driver will tune the VGC level during each call + * to the link_tuner() callback function. This vgc_level is + * is determined based on the link quality statistics like + * average RSSI and the false CCA count. + * + * In some cases the drivers need to differentiate between + * the currently "desired" VGC level and the level configured + * in the hardware. The latter is important to reduce the + * number of BBP register reads to reduce register access + * overhead. For this reason we store both values here. + */ + u8 vgc_level; + u8 vgc_level_reg; + + /* + * Statistics required for Signal quality calculation. + * These fields might be changed during the link_stats() + * callback function. + */ + int rx_success; + int rx_failed; + int tx_success; + int tx_failed; +}; + +/* + * Antenna settings about the currently active link. + */ +struct link_ant { + /* + * Antenna flags + */ + unsigned int flags; +#define ANTENNA_RX_DIVERSITY 0x00000001 +#define ANTENNA_TX_DIVERSITY 0x00000002 +#define ANTENNA_MODE_SAMPLE 0x00000004 + + /* + * Currently active TX/RX antenna setup. + * When software diversity is used, this will indicate + * which antenna is actually used at this time. + */ + struct antenna_setup active; + + /* + * RSSI history information for the antenna. + * Used to determine when to switch antenna + * when using software diversity. + */ + int rssi_history; + + /* + * Current RSSI average of the currently active antenna. + * Similar to the avg_rssi in the link_qual structure + * this value is updated by using the walking average. + */ + struct avg_val rssi_ant; +}; + +/* + * To optimize the quality of the link we need to store + * the quality of received frames and periodically + * optimize the link. + */ +struct link { + /* + * Link tuner counter + * The number of times the link has been tuned + * since the radio has been switched on. + */ + u32 count; + + /* + * Quality measurement values. + */ + struct link_qual qual; + + /* + * TX/RX antenna setup. + */ + struct link_ant ant; + + /* + * Currently active average RSSI value + */ + struct avg_val avg_rssi; + + /* + * Work structure for scheduling periodic link tuning. + */ + struct delayed_work work; + + /* + * Work structure for scheduling periodic watchdog monitoring. + * This work must be scheduled on the kernel workqueue, while + * all other work structures must be queued on the mac80211 + * workqueue. This guarantees that the watchdog can schedule + * other work structures and wait for their completion in order + * to bring the device/driver back into the desired state. + */ + struct delayed_work watchdog_work; + + /* + * Work structure for scheduling periodic AGC adjustments. + */ + struct delayed_work agc_work; + + /* + * Work structure for scheduling periodic VCO calibration. + */ + struct delayed_work vco_work; +}; + +enum rt2x00_delayed_flags { + DELAYED_UPDATE_BEACON, +}; + +/* + * Interface structure + * Per interface configuration details, this structure + * is allocated as the private data for ieee80211_vif. + */ +struct rt2x00_intf { + /* + * beacon->skb must be protected with the mutex. + */ + struct mutex beacon_skb_mutex; + + /* + * Entry in the beacon queue which belongs to + * this interface. Each interface has its own + * dedicated beacon entry. + */ + struct queue_entry *beacon; + bool enable_beacon; + + /* + * Actions that needed rescheduling. + */ + unsigned long delayed_flags; + + /* + * Software sequence counter, this is only required + * for hardware which doesn't support hardware + * sequence counting. + */ + atomic_t seqno; +}; + +static inline struct rt2x00_intf* vif_to_intf(struct ieee80211_vif *vif) +{ + return (struct rt2x00_intf *)vif->drv_priv; +} + +/** + * struct hw_mode_spec: Hardware specifications structure + * + * Details about the supported modes, rates and channels + * of a particular chipset. This is used by rt2x00lib + * to build the ieee80211_hw_mode array for mac80211. + * + * @supported_bands: Bitmask contained the supported bands (2.4GHz, 5.2GHz). + * @supported_rates: Rate types which are supported (CCK, OFDM). + * @num_channels: Number of supported channels. This is used as array size + * for @tx_power_a, @tx_power_bg and @channels. + * @channels: Device/chipset specific channel values (See &struct rf_channel). + * @channels_info: Additional information for channels (See &struct channel_info). + * @ht: Driver HT Capabilities (See &ieee80211_sta_ht_cap). + */ +struct hw_mode_spec { + unsigned int supported_bands; +#define SUPPORT_BAND_2GHZ 0x00000001 +#define SUPPORT_BAND_5GHZ 0x00000002 + + unsigned int supported_rates; +#define SUPPORT_RATE_CCK 0x00000001 +#define SUPPORT_RATE_OFDM 0x00000002 + + unsigned int num_channels; + const struct rf_channel *channels; + const struct channel_info *channels_info; + + struct ieee80211_sta_ht_cap ht; +}; + +/* + * Configuration structure wrapper around the + * mac80211 configuration structure. + * When mac80211 configures the driver, rt2x00lib + * can precalculate values which are equal for all + * rt2x00 drivers. Those values can be stored in here. + */ +struct rt2x00lib_conf { + struct ieee80211_conf *conf; + + struct rf_channel rf; + struct channel_info channel; +}; + +/* + * Configuration structure for erp settings. + */ +struct rt2x00lib_erp { + int short_preamble; + int cts_protection; + + u32 basic_rates; + + int slot_time; + + short sifs; + short pifs; + short difs; + short eifs; + + u16 beacon_int; + u16 ht_opmode; +}; + +/* + * Configuration structure for hardware encryption. + */ +struct rt2x00lib_crypto { + enum cipher cipher; + + enum set_key_cmd cmd; + const u8 *address; + + u32 bssidx; + + u8 key[16]; + u8 tx_mic[8]; + u8 rx_mic[8]; + + int wcid; +}; + +/* + * Configuration structure wrapper around the + * rt2x00 interface configuration handler. + */ +struct rt2x00intf_conf { + /* + * Interface type + */ + enum nl80211_iftype type; + + /* + * TSF sync value, this is dependent on the operation type. + */ + enum tsf_sync sync; + + /* + * The MAC and BSSID addresses are simple array of bytes, + * these arrays are little endian, so when sending the addresses + * to the drivers, copy the it into a endian-signed variable. + * + * Note that all devices (except rt2500usb) have 32 bits + * register word sizes. This means that whatever variable we + * pass _must_ be a multiple of 32 bits. Otherwise the device + * might not accept what we are sending to it. + * This will also make it easier for the driver to write + * the data to the device. + */ + __le32 mac[2]; + __le32 bssid[2]; +}; + +/* + * Private structure for storing STA details + * wcid: Wireless Client ID + */ +struct rt2x00_sta { + int wcid; +}; + +static inline struct rt2x00_sta* sta_to_rt2x00_sta(struct ieee80211_sta *sta) +{ + return (struct rt2x00_sta *)sta->drv_priv; +} + +/* + * rt2x00lib callback functions. + */ +struct rt2x00lib_ops { + /* + * Interrupt handlers. + */ + irq_handler_t irq_handler; + + /* + * TX status tasklet handler. + */ + void (*txstatus_tasklet) (unsigned long data); + void (*pretbtt_tasklet) (unsigned long data); + void (*tbtt_tasklet) (unsigned long data); + void (*rxdone_tasklet) (unsigned long data); + void (*autowake_tasklet) (unsigned long data); + + /* + * Device init handlers. + */ + int (*probe_hw) (struct rt2x00_dev *rt2x00dev); + char *(*get_firmware_name) (struct rt2x00_dev *rt2x00dev); + int (*check_firmware) (struct rt2x00_dev *rt2x00dev, + const u8 *data, const size_t len); + int (*load_firmware) (struct rt2x00_dev *rt2x00dev, + const u8 *data, const size_t len); + + /* + * Device initialization/deinitialization handlers. + */ + int (*initialize) (struct rt2x00_dev *rt2x00dev); + void (*uninitialize) (struct rt2x00_dev *rt2x00dev); + + /* + * queue initialization handlers + */ + bool (*get_entry_state) (struct queue_entry *entry); + void (*clear_entry) (struct queue_entry *entry); + + /* + * Radio control handlers. + */ + int (*set_device_state) (struct rt2x00_dev *rt2x00dev, + enum dev_state state); + int (*rfkill_poll) (struct rt2x00_dev *rt2x00dev); + void (*link_stats) (struct rt2x00_dev *rt2x00dev, + struct link_qual *qual); + void (*reset_tuner) (struct rt2x00_dev *rt2x00dev, + struct link_qual *qual); + void (*link_tuner) (struct rt2x00_dev *rt2x00dev, + struct link_qual *qual, const u32 count); + void (*gain_calibration) (struct rt2x00_dev *rt2x00dev); + void (*vco_calibration) (struct rt2x00_dev *rt2x00dev); + + /* + * Data queue handlers. + */ + void (*watchdog) (struct rt2x00_dev *rt2x00dev); + void (*start_queue) (struct data_queue *queue); + void (*kick_queue) (struct data_queue *queue); + void (*stop_queue) (struct data_queue *queue); + void (*flush_queue) (struct data_queue *queue, bool drop); + void (*tx_dma_done) (struct queue_entry *entry); + + /* + * TX control handlers + */ + void (*write_tx_desc) (struct queue_entry *entry, + struct txentry_desc *txdesc); + void (*write_tx_data) (struct queue_entry *entry, + struct txentry_desc *txdesc); + void (*write_beacon) (struct queue_entry *entry, + struct txentry_desc *txdesc); + void (*clear_beacon) (struct queue_entry *entry); + int (*get_tx_data_len) (struct queue_entry *entry); + + /* + * RX control handlers + */ + void (*fill_rxdone) (struct queue_entry *entry, + struct rxdone_entry_desc *rxdesc); + + /* + * Configuration handlers. + */ + int (*config_shared_key) (struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_crypto *crypto, + struct ieee80211_key_conf *key); + int (*config_pairwise_key) (struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_crypto *crypto, + struct ieee80211_key_conf *key); + void (*config_filter) (struct rt2x00_dev *rt2x00dev, + const unsigned int filter_flags); + void (*config_intf) (struct rt2x00_dev *rt2x00dev, + struct rt2x00_intf *intf, + struct rt2x00intf_conf *conf, + const unsigned int flags); +#define CONFIG_UPDATE_TYPE ( 1 << 1 ) +#define CONFIG_UPDATE_MAC ( 1 << 2 ) +#define CONFIG_UPDATE_BSSID ( 1 << 3 ) + + void (*config_erp) (struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_erp *erp, + u32 changed); + void (*config_ant) (struct rt2x00_dev *rt2x00dev, + struct antenna_setup *ant); + void (*config) (struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf, + const unsigned int changed_flags); + int (*sta_add) (struct rt2x00_dev *rt2x00dev, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta); + int (*sta_remove) (struct rt2x00_dev *rt2x00dev, + int wcid); +}; + +/* + * rt2x00 driver callback operation structure. + */ +struct rt2x00_ops { + const char *name; + const unsigned int drv_data_size; + const unsigned int max_ap_intf; + const unsigned int eeprom_size; + const unsigned int rf_size; + const unsigned int tx_queues; + const unsigned int extra_tx_headroom; + const struct data_queue_desc *rx; + const struct data_queue_desc *tx; + const struct data_queue_desc *bcn; + const struct data_queue_desc *atim; + const struct rt2x00lib_ops *lib; + const void *drv; + const struct ieee80211_ops *hw; +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS + const struct rt2x00debug *debugfs; +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ +}; + +/* + * rt2x00 state flags + */ +enum rt2x00_state_flags { + /* + * Device flags + */ + DEVICE_STATE_PRESENT, + DEVICE_STATE_REGISTERED_HW, + DEVICE_STATE_INITIALIZED, + DEVICE_STATE_STARTED, + DEVICE_STATE_ENABLED_RADIO, + DEVICE_STATE_SCANNING, + + /* + * Driver configuration + */ + CONFIG_CHANNEL_HT40, + CONFIG_POWERSAVING, + CONFIG_HT_DISABLED, + CONFIG_QOS_DISABLED, + + /* + * Mark we currently are sequentially reading TX_STA_FIFO register + * FIXME: this is for only rt2800usb, should go to private data + */ + TX_STATUS_READING, +}; + +/* + * rt2x00 capability flags + */ +enum rt2x00_capability_flags { + /* + * Requirements + */ + REQUIRE_FIRMWARE, + REQUIRE_BEACON_GUARD, + REQUIRE_ATIM_QUEUE, + REQUIRE_DMA, + REQUIRE_COPY_IV, + REQUIRE_L2PAD, + REQUIRE_TXSTATUS_FIFO, + REQUIRE_TASKLET_CONTEXT, + REQUIRE_SW_SEQNO, + REQUIRE_HT_TX_DESC, + REQUIRE_PS_AUTOWAKE, + + /* + * Capabilities + */ + CAPABILITY_HW_BUTTON, + CAPABILITY_HW_CRYPTO, + CAPABILITY_POWER_LIMIT, + CAPABILITY_CONTROL_FILTERS, + CAPABILITY_CONTROL_FILTER_PSPOLL, + CAPABILITY_PRE_TBTT_INTERRUPT, + CAPABILITY_LINK_TUNING, + CAPABILITY_FRAME_TYPE, + CAPABILITY_RF_SEQUENCE, + CAPABILITY_EXTERNAL_LNA_A, + CAPABILITY_EXTERNAL_LNA_BG, + CAPABILITY_DOUBLE_ANTENNA, + CAPABILITY_BT_COEXIST, + CAPABILITY_VCO_RECALIBRATION, +}; + +/* + * Interface combinations + */ +enum { + IF_COMB_AP = 0, + NUM_IF_COMB, +}; + +/* + * rt2x00 device structure. + */ +struct rt2x00_dev { + /* + * Device structure. + * The structure stored in here depends on the + * system bus (PCI or USB). + * When accessing this variable, the rt2x00dev_{pci,usb} + * macros should be used for correct typecasting. + */ + struct device *dev; + + /* + * Callback functions. + */ + const struct rt2x00_ops *ops; + + /* + * Driver data. + */ + void *drv_data; + + /* + * IEEE80211 control structure. + */ + struct ieee80211_hw *hw; + struct ieee80211_supported_band bands[IEEE80211_NUM_BANDS]; + enum ieee80211_band curr_band; + int curr_freq; + + /* + * If enabled, the debugfs interface structures + * required for deregistration of debugfs. + */ +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS + struct rt2x00debug_intf *debugfs_intf; +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ + + /* + * LED structure for changing the LED status + * by mac8011 or the kernel. + */ +#ifdef CPTCFG_RT2X00_LIB_LEDS + struct rt2x00_led led_radio; + struct rt2x00_led led_assoc; + struct rt2x00_led led_qual; + u16 led_mcu_reg; +#endif /* CPTCFG_RT2X00_LIB_LEDS */ + + /* + * Device state flags. + * In these flags the current status is stored. + * Access to these flags should occur atomically. + */ + unsigned long flags; + + /* + * Device capabiltiy flags. + * In these flags the device/driver capabilities are stored. + * Access to these flags should occur non-atomically. + */ + unsigned long cap_flags; + + /* + * Device information, Bus IRQ and name (PCI, SoC) + */ + int irq; + const char *name; + + /* + * Chipset identification. + */ + struct rt2x00_chip chip; + + /* + * hw capability specifications. + */ + struct hw_mode_spec spec; + + /* + * This is the default TX/RX antenna setup as indicated + * by the device's EEPROM. + */ + struct antenna_setup default_ant; + + /* + * Register pointers + * csr.base: CSR base register address. (PCI) + * csr.cache: CSR cache for usb_control_msg. (USB) + */ + union csr { + void __iomem *base; + void *cache; + } csr; + + /* + * Mutex to protect register accesses. + * For PCI and USB devices it protects against concurrent indirect + * register access (BBP, RF, MCU) since accessing those + * registers require multiple calls to the CSR registers. + * For USB devices it also protects the csr_cache since that + * field is used for normal CSR access and it cannot support + * multiple callers simultaneously. + */ + struct mutex csr_mutex; + + /* + * Current packet filter configuration for the device. + * This contains all currently active FIF_* flags send + * to us by mac80211 during configure_filter(). + */ + unsigned int packet_filter; + + /* + * Interface details: + * - Open ap interface count. + * - Open sta interface count. + * - Association count. + * - Beaconing enabled count. + */ + unsigned int intf_ap_count; + unsigned int intf_sta_count; + unsigned int intf_associated; + unsigned int intf_beaconing; + + /* + * Interface combinations + */ + struct ieee80211_iface_limit if_limits_ap; + struct ieee80211_iface_combination if_combinations[NUM_IF_COMB]; + + /* + * Link quality + */ + struct link link; + + /* + * EEPROM data. + */ + __le16 *eeprom; + + /* + * Active RF register values. + * These are stored here so we don't need + * to read the rf registers and can directly + * use this value instead. + * This field should be accessed by using + * rt2x00_rf_read() and rt2x00_rf_write(). + */ + u32 *rf; + + /* + * LNA gain + */ + short lna_gain; + + /* + * Current TX power value. + */ + u16 tx_power; + + /* + * Current retry values. + */ + u8 short_retry; + u8 long_retry; + + /* + * Rssi <-> Dbm offset + */ + u8 rssi_offset; + + /* + * Frequency offset. + */ + u8 freq_offset; + + /* + * Association id. + */ + u16 aid; + + /* + * Beacon interval. + */ + u16 beacon_int; + + /** + * Timestamp of last received beacon + */ + unsigned long last_beacon; + + /* + * Low level statistics which will have + * to be kept up to date while device is running. + */ + struct ieee80211_low_level_stats low_level_stats; + + /** + * Work queue for all work which should not be placed + * on the mac80211 workqueue (because of dependencies + * between various work structures). + */ + struct workqueue_struct *workqueue; + + /* + * Scheduled work. + * NOTE: intf_work will use ieee80211_iterate_active_interfaces() + * which means it cannot be placed on the hw->workqueue + * due to RTNL locking requirements. + */ + struct work_struct intf_work; + + /** + * Scheduled work for TX/RX done handling (USB devices) + */ + struct work_struct rxdone_work; + struct work_struct txdone_work; + + /* + * Powersaving work + */ + struct delayed_work autowakeup_work; + struct work_struct sleep_work; + + /* + * Data queue arrays for RX, TX, Beacon and ATIM. + */ + unsigned int data_queues; + struct data_queue *rx; + struct data_queue *tx; + struct data_queue *bcn; + struct data_queue *atim; + + /* + * Firmware image. + */ + const struct firmware *fw; + + /* + * FIFO for storing tx status reports between isr and tasklet. + */ + DECLARE_KFIFO_PTR(txstatus_fifo, u32); + + /* + * Timer to ensure tx status reports are read (rt2800usb). + */ + struct hrtimer txstatus_timer; + + /* + * Tasklet for processing tx status reports (rt2800pci). + */ + struct tasklet_struct txstatus_tasklet; + struct tasklet_struct pretbtt_tasklet; + struct tasklet_struct tbtt_tasklet; + struct tasklet_struct rxdone_tasklet; + struct tasklet_struct autowake_tasklet; + + /* + * Used for VCO periodic calibration. + */ + int rf_channel; + + /* + * Protect the interrupt mask register. + */ + spinlock_t irqmask_lock; + + /* + * List of BlockAckReq TX entries that need driver BlockAck processing. + */ + struct list_head bar_list; + spinlock_t bar_list_lock; +}; + +struct rt2x00_bar_list_entry { + struct list_head list; + struct rcu_head head; + + struct queue_entry *entry; + int block_acked; + + /* Relevant parts of the IEEE80211 BAR header */ + __u8 ra[6]; + __u8 ta[6]; + __le16 control; + __le16 start_seq_num; +}; + +/* + * Register defines. + * Some registers require multiple attempts before success, + * in those cases REGISTER_BUSY_COUNT attempts should be + * taken with a REGISTER_BUSY_DELAY interval. + */ +#define REGISTER_BUSY_COUNT 100 +#define REGISTER_BUSY_DELAY 100 + +/* + * Generic RF access. + * The RF is being accessed by word index. + */ +static inline void rt2x00_rf_read(struct rt2x00_dev *rt2x00dev, + const unsigned int word, u32 *data) +{ + BUG_ON(word < 1 || word > rt2x00dev->ops->rf_size / sizeof(u32)); + *data = rt2x00dev->rf[word - 1]; +} + +static inline void rt2x00_rf_write(struct rt2x00_dev *rt2x00dev, + const unsigned int word, u32 data) +{ + BUG_ON(word < 1 || word > rt2x00dev->ops->rf_size / sizeof(u32)); + rt2x00dev->rf[word - 1] = data; +} + +/* + * Generic EEPROM access. The EEPROM is being accessed by word or byte index. + */ +static inline void *rt2x00_eeprom_addr(struct rt2x00_dev *rt2x00dev, + const unsigned int word) +{ + return (void *)&rt2x00dev->eeprom[word]; +} + +static inline void rt2x00_eeprom_read(struct rt2x00_dev *rt2x00dev, + const unsigned int word, u16 *data) +{ + *data = le16_to_cpu(rt2x00dev->eeprom[word]); +} + +static inline void rt2x00_eeprom_write(struct rt2x00_dev *rt2x00dev, + const unsigned int word, u16 data) +{ + rt2x00dev->eeprom[word] = cpu_to_le16(data); +} + +static inline u8 rt2x00_eeprom_byte(struct rt2x00_dev *rt2x00dev, + const unsigned int byte) +{ + return *(((u8 *)rt2x00dev->eeprom) + byte); +} + +/* + * Chipset handlers + */ +static inline void rt2x00_set_chip(struct rt2x00_dev *rt2x00dev, + const u16 rt, const u16 rf, const u16 rev) +{ + rt2x00dev->chip.rt = rt; + rt2x00dev->chip.rf = rf; + rt2x00dev->chip.rev = rev; + + rt2x00_info(rt2x00dev, "Chipset detected - rt: %04x, rf: %04x, rev: %04x\n", + rt2x00dev->chip.rt, rt2x00dev->chip.rf, + rt2x00dev->chip.rev); +} + +static inline void rt2x00_set_rt(struct rt2x00_dev *rt2x00dev, + const u16 rt, const u16 rev) +{ + rt2x00dev->chip.rt = rt; + rt2x00dev->chip.rev = rev; + + rt2x00_info(rt2x00dev, "RT chipset %04x, rev %04x detected\n", + rt2x00dev->chip.rt, rt2x00dev->chip.rev); +} + +static inline void rt2x00_set_rf(struct rt2x00_dev *rt2x00dev, const u16 rf) +{ + rt2x00dev->chip.rf = rf; + + rt2x00_info(rt2x00dev, "RF chipset %04x detected\n", + rt2x00dev->chip.rf); +} + +static inline bool rt2x00_rt(struct rt2x00_dev *rt2x00dev, const u16 rt) +{ + return (rt2x00dev->chip.rt == rt); +} + +static inline bool rt2x00_rf(struct rt2x00_dev *rt2x00dev, const u16 rf) +{ + return (rt2x00dev->chip.rf == rf); +} + +static inline u16 rt2x00_rev(struct rt2x00_dev *rt2x00dev) +{ + return rt2x00dev->chip.rev; +} + +static inline bool rt2x00_rt_rev(struct rt2x00_dev *rt2x00dev, + const u16 rt, const u16 rev) +{ + return (rt2x00_rt(rt2x00dev, rt) && rt2x00_rev(rt2x00dev) == rev); +} + +static inline bool rt2x00_rt_rev_lt(struct rt2x00_dev *rt2x00dev, + const u16 rt, const u16 rev) +{ + return (rt2x00_rt(rt2x00dev, rt) && rt2x00_rev(rt2x00dev) < rev); +} + +static inline bool rt2x00_rt_rev_gte(struct rt2x00_dev *rt2x00dev, + const u16 rt, const u16 rev) +{ + return (rt2x00_rt(rt2x00dev, rt) && rt2x00_rev(rt2x00dev) >= rev); +} + +static inline void rt2x00_set_chip_intf(struct rt2x00_dev *rt2x00dev, + enum rt2x00_chip_intf intf) +{ + rt2x00dev->chip.intf = intf; +} + +static inline bool rt2x00_intf(struct rt2x00_dev *rt2x00dev, + enum rt2x00_chip_intf intf) +{ + return (rt2x00dev->chip.intf == intf); +} + +static inline bool rt2x00_is_pci(struct rt2x00_dev *rt2x00dev) +{ + return rt2x00_intf(rt2x00dev, RT2X00_CHIP_INTF_PCI) || + rt2x00_intf(rt2x00dev, RT2X00_CHIP_INTF_PCIE); +} + +static inline bool rt2x00_is_pcie(struct rt2x00_dev *rt2x00dev) +{ + return rt2x00_intf(rt2x00dev, RT2X00_CHIP_INTF_PCIE); +} + +static inline bool rt2x00_is_usb(struct rt2x00_dev *rt2x00dev) +{ + return rt2x00_intf(rt2x00dev, RT2X00_CHIP_INTF_USB); +} + +static inline bool rt2x00_is_soc(struct rt2x00_dev *rt2x00dev) +{ + return rt2x00_intf(rt2x00dev, RT2X00_CHIP_INTF_SOC); +} + +/** + * rt2x00queue_map_txskb - Map a skb into DMA for TX purposes. + * @entry: Pointer to &struct queue_entry + * + * Returns -ENOMEM if mapping fail, 0 otherwise. + */ +int rt2x00queue_map_txskb(struct queue_entry *entry); + +/** + * rt2x00queue_unmap_skb - Unmap a skb from DMA. + * @entry: Pointer to &struct queue_entry + */ +void rt2x00queue_unmap_skb(struct queue_entry *entry); + +/** + * rt2x00queue_get_tx_queue - Convert tx queue index to queue pointer + * @rt2x00dev: Pointer to &struct rt2x00_dev. + * @queue: rt2x00 queue index (see &enum data_queue_qid). + * + * Returns NULL for non tx queues. + */ +static inline struct data_queue * +rt2x00queue_get_tx_queue(struct rt2x00_dev *rt2x00dev, + const enum data_queue_qid queue) +{ + if (queue < rt2x00dev->ops->tx_queues && rt2x00dev->tx) + return &rt2x00dev->tx[queue]; + + if (queue == QID_ATIM) + return rt2x00dev->atim; + + return NULL; +} + +/** + * rt2x00queue_get_entry - Get queue entry where the given index points to. + * @queue: Pointer to &struct data_queue from where we obtain the entry. + * @index: Index identifier for obtaining the correct index. + */ +struct queue_entry *rt2x00queue_get_entry(struct data_queue *queue, + enum queue_index index); + +/** + * rt2x00queue_pause_queue - Pause a data queue + * @queue: Pointer to &struct data_queue. + * + * This function will pause the data queue locally, preventing + * new frames to be added to the queue (while the hardware is + * still allowed to run). + */ +void rt2x00queue_pause_queue(struct data_queue *queue); + +/** + * rt2x00queue_unpause_queue - unpause a data queue + * @queue: Pointer to &struct data_queue. + * + * This function will unpause the data queue locally, allowing + * new frames to be added to the queue again. + */ +void rt2x00queue_unpause_queue(struct data_queue *queue); + +/** + * rt2x00queue_start_queue - Start a data queue + * @queue: Pointer to &struct data_queue. + * + * This function will start handling all pending frames in the queue. + */ +void rt2x00queue_start_queue(struct data_queue *queue); + +/** + * rt2x00queue_stop_queue - Halt a data queue + * @queue: Pointer to &struct data_queue. + * + * This function will stop all pending frames in the queue. + */ +void rt2x00queue_stop_queue(struct data_queue *queue); + +/** + * rt2x00queue_flush_queue - Flush a data queue + * @queue: Pointer to &struct data_queue. + * @drop: True to drop all pending frames. + * + * This function will flush the queue. After this call + * the queue is guaranteed to be empty. + */ +void rt2x00queue_flush_queue(struct data_queue *queue, bool drop); + +/** + * rt2x00queue_start_queues - Start all data queues + * @rt2x00dev: Pointer to &struct rt2x00_dev. + * + * This function will loop through all available queues to start them + */ +void rt2x00queue_start_queues(struct rt2x00_dev *rt2x00dev); + +/** + * rt2x00queue_stop_queues - Halt all data queues + * @rt2x00dev: Pointer to &struct rt2x00_dev. + * + * This function will loop through all available queues to stop + * any pending frames. + */ +void rt2x00queue_stop_queues(struct rt2x00_dev *rt2x00dev); + +/** + * rt2x00queue_flush_queues - Flush all data queues + * @rt2x00dev: Pointer to &struct rt2x00_dev. + * @drop: True to drop all pending frames. + * + * This function will loop through all available queues to flush + * any pending frames. + */ +void rt2x00queue_flush_queues(struct rt2x00_dev *rt2x00dev, bool drop); + +/* + * Debugfs handlers. + */ +/** + * rt2x00debug_dump_frame - Dump a frame to userspace through debugfs. + * @rt2x00dev: Pointer to &struct rt2x00_dev. + * @type: The type of frame that is being dumped. + * @skb: The skb containing the frame to be dumped. + */ +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS +void rt2x00debug_dump_frame(struct rt2x00_dev *rt2x00dev, + enum rt2x00_dump_type type, struct sk_buff *skb); +#else +static inline void rt2x00debug_dump_frame(struct rt2x00_dev *rt2x00dev, + enum rt2x00_dump_type type, + struct sk_buff *skb) +{ +} +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ + +/* + * Utility functions. + */ +u32 rt2x00lib_get_bssidx(struct rt2x00_dev *rt2x00dev, + struct ieee80211_vif *vif); + +/* + * Interrupt context handlers. + */ +void rt2x00lib_beacondone(struct rt2x00_dev *rt2x00dev); +void rt2x00lib_pretbtt(struct rt2x00_dev *rt2x00dev); +void rt2x00lib_dmastart(struct queue_entry *entry); +void rt2x00lib_dmadone(struct queue_entry *entry); +void rt2x00lib_txdone(struct queue_entry *entry, + struct txdone_entry_desc *txdesc); +void rt2x00lib_txdone_noinfo(struct queue_entry *entry, u32 status); +void rt2x00lib_rxdone(struct queue_entry *entry, gfp_t gfp); + +/* + * mac80211 handlers. + */ +void rt2x00mac_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb); +int rt2x00mac_start(struct ieee80211_hw *hw); +void rt2x00mac_stop(struct ieee80211_hw *hw); +int rt2x00mac_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif); +void rt2x00mac_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif); +int rt2x00mac_config(struct ieee80211_hw *hw, u32 changed); +void rt2x00mac_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast); +int rt2x00mac_set_tim(struct ieee80211_hw *hw, struct ieee80211_sta *sta, + bool set); +#ifdef CPTCFG_RT2X00_LIB_CRYPTO +int rt2x00mac_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ieee80211_key_conf *key); +#else +#define rt2x00mac_set_key NULL +#endif /* CPTCFG_RT2X00_LIB_CRYPTO */ +int rt2x00mac_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +int rt2x00mac_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +void rt2x00mac_sw_scan_start(struct ieee80211_hw *hw); +void rt2x00mac_sw_scan_complete(struct ieee80211_hw *hw); +int rt2x00mac_get_stats(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats); +void rt2x00mac_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changes); +int rt2x00mac_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params); +void rt2x00mac_rfkill_poll(struct ieee80211_hw *hw); +void rt2x00mac_flush(struct ieee80211_hw *hw, u32 queues, bool drop); +int rt2x00mac_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant); +int rt2x00mac_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant); +void rt2x00mac_get_ringparam(struct ieee80211_hw *hw, + u32 *tx, u32 *tx_max, u32 *rx, u32 *rx_max); +bool rt2x00mac_tx_frames_pending(struct ieee80211_hw *hw); + +/* + * Driver allocation handlers. + */ +int rt2x00lib_probe_dev(struct rt2x00_dev *rt2x00dev); +void rt2x00lib_remove_dev(struct rt2x00_dev *rt2x00dev); +#ifdef CONFIG_PM +int rt2x00lib_suspend(struct rt2x00_dev *rt2x00dev, pm_message_t state); +int rt2x00lib_resume(struct rt2x00_dev *rt2x00dev); +#endif /* CONFIG_PM */ + +#endif /* RT2X00_H */ diff --git a/drivers/net/wireless/rt2x00/rt2x00config.c b/drivers/net/wireless/rt2x00/rt2x00config.c new file mode 100644 index 0000000..8cb43f8 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00config.c @@ -0,0 +1,287 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00lib + Abstract: rt2x00 generic configuration routines. + */ + +#include +#include + +#include "rt2x00.h" +#include "rt2x00lib.h" + +void rt2x00lib_config_intf(struct rt2x00_dev *rt2x00dev, + struct rt2x00_intf *intf, + enum nl80211_iftype type, + const u8 *mac, const u8 *bssid) +{ + struct rt2x00intf_conf conf; + unsigned int flags = 0; + + conf.type = type; + + switch (type) { + case NL80211_IFTYPE_ADHOC: + conf.sync = TSF_SYNC_ADHOC; + break; + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_MESH_POINT: + case NL80211_IFTYPE_WDS: + conf.sync = TSF_SYNC_AP_NONE; + break; + case NL80211_IFTYPE_STATION: + conf.sync = TSF_SYNC_INFRA; + break; + default: + conf.sync = TSF_SYNC_NONE; + break; + } + + /* + * Note that when NULL is passed as address we will send + * 00:00:00:00:00 to the device to clear the address. + * This will prevent the device being confused when it wants + * to ACK frames or considers itself associated. + */ + memset(conf.mac, 0, sizeof(conf.mac)); + if (mac) + memcpy(conf.mac, mac, ETH_ALEN); + + memset(conf.bssid, 0, sizeof(conf.bssid)); + if (bssid) + memcpy(conf.bssid, bssid, ETH_ALEN); + + flags |= CONFIG_UPDATE_TYPE; + if (mac || (!rt2x00dev->intf_ap_count && !rt2x00dev->intf_sta_count)) + flags |= CONFIG_UPDATE_MAC; + if (bssid || (!rt2x00dev->intf_ap_count && !rt2x00dev->intf_sta_count)) + flags |= CONFIG_UPDATE_BSSID; + + rt2x00dev->ops->lib->config_intf(rt2x00dev, intf, &conf, flags); +} + +void rt2x00lib_config_erp(struct rt2x00_dev *rt2x00dev, + struct rt2x00_intf *intf, + struct ieee80211_bss_conf *bss_conf, + u32 changed) +{ + struct rt2x00lib_erp erp; + + memset(&erp, 0, sizeof(erp)); + + erp.short_preamble = bss_conf->use_short_preamble; + erp.cts_protection = bss_conf->use_cts_prot; + + erp.slot_time = bss_conf->use_short_slot ? SHORT_SLOT_TIME : SLOT_TIME; + erp.sifs = SIFS; + erp.pifs = bss_conf->use_short_slot ? SHORT_PIFS : PIFS; + erp.difs = bss_conf->use_short_slot ? SHORT_DIFS : DIFS; + erp.eifs = bss_conf->use_short_slot ? SHORT_EIFS : EIFS; + + erp.basic_rates = bss_conf->basic_rates; + erp.beacon_int = bss_conf->beacon_int; + + /* Update the AID, this is needed for dynamic PS support */ + rt2x00dev->aid = bss_conf->assoc ? bss_conf->aid : 0; + rt2x00dev->last_beacon = bss_conf->sync_tsf; + + /* Update global beacon interval time, this is needed for PS support */ + rt2x00dev->beacon_int = bss_conf->beacon_int; + + if (changed & BSS_CHANGED_HT) + erp.ht_opmode = bss_conf->ht_operation_mode; + + rt2x00dev->ops->lib->config_erp(rt2x00dev, &erp, changed); +} + +void rt2x00lib_config_antenna(struct rt2x00_dev *rt2x00dev, + struct antenna_setup config) +{ + struct link_ant *ant = &rt2x00dev->link.ant; + struct antenna_setup *def = &rt2x00dev->default_ant; + struct antenna_setup *active = &rt2x00dev->link.ant.active; + + /* + * When the caller tries to send the SW diversity, + * we must update the ANTENNA_RX_DIVERSITY flag to + * enable the antenna diversity in the link tuner. + * + * Secondly, we must guarentee we never send the + * software antenna diversity command to the driver. + */ + if (!(ant->flags & ANTENNA_RX_DIVERSITY)) { + if (config.rx == ANTENNA_SW_DIVERSITY) { + ant->flags |= ANTENNA_RX_DIVERSITY; + + if (def->rx == ANTENNA_SW_DIVERSITY) + config.rx = ANTENNA_B; + else + config.rx = def->rx; + } + } else if (config.rx == ANTENNA_SW_DIVERSITY) + config.rx = active->rx; + + if (!(ant->flags & ANTENNA_TX_DIVERSITY)) { + if (config.tx == ANTENNA_SW_DIVERSITY) { + ant->flags |= ANTENNA_TX_DIVERSITY; + + if (def->tx == ANTENNA_SW_DIVERSITY) + config.tx = ANTENNA_B; + else + config.tx = def->tx; + } + } else if (config.tx == ANTENNA_SW_DIVERSITY) + config.tx = active->tx; + + /* + * Antenna setup changes require the RX to be disabled, + * else the changes will be ignored by the device. + */ + if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + rt2x00queue_stop_queue(rt2x00dev->rx); + + /* + * Write new antenna setup to device and reset the link tuner. + * The latter is required since we need to recalibrate the + * noise-sensitivity ratio for the new setup. + */ + rt2x00dev->ops->lib->config_ant(rt2x00dev, &config); + + rt2x00link_reset_tuner(rt2x00dev, true); + + memcpy(active, &config, sizeof(config)); + + if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + rt2x00queue_start_queue(rt2x00dev->rx); +} + +static u16 rt2x00ht_center_channel(struct rt2x00_dev *rt2x00dev, + struct ieee80211_conf *conf) +{ + struct hw_mode_spec *spec = &rt2x00dev->spec; + int center_channel; + u16 i; + + /* + * Initialize center channel to current channel. + */ + center_channel = spec->channels[conf->chandef.chan->hw_value].channel; + + /* + * Adjust center channel to HT40+ and HT40- operation. + */ + if (conf_is_ht40_plus(conf)) + center_channel += 2; + else if (conf_is_ht40_minus(conf)) + center_channel -= (center_channel == 14) ? 1 : 2; + + for (i = 0; i < spec->num_channels; i++) + if (spec->channels[i].channel == center_channel) + return i; + + WARN_ON(1); + return conf->chandef.chan->hw_value; +} + +void rt2x00lib_config(struct rt2x00_dev *rt2x00dev, + struct ieee80211_conf *conf, + unsigned int ieee80211_flags) +{ + struct rt2x00lib_conf libconf; + u16 hw_value; + u16 autowake_timeout; + u16 beacon_int; + u16 beacon_diff; + + memset(&libconf, 0, sizeof(libconf)); + + libconf.conf = conf; + + if (ieee80211_flags & IEEE80211_CONF_CHANGE_CHANNEL) { + if (!conf_is_ht(conf)) + set_bit(CONFIG_HT_DISABLED, &rt2x00dev->flags); + else + clear_bit(CONFIG_HT_DISABLED, &rt2x00dev->flags); + + if (conf_is_ht40(conf)) { + set_bit(CONFIG_CHANNEL_HT40, &rt2x00dev->flags); + hw_value = rt2x00ht_center_channel(rt2x00dev, conf); + } else { + clear_bit(CONFIG_CHANNEL_HT40, &rt2x00dev->flags); + hw_value = conf->chandef.chan->hw_value; + } + + memcpy(&libconf.rf, + &rt2x00dev->spec.channels[hw_value], + sizeof(libconf.rf)); + + memcpy(&libconf.channel, + &rt2x00dev->spec.channels_info[hw_value], + sizeof(libconf.channel)); + + /* Used for VCO periodic calibration */ + rt2x00dev->rf_channel = libconf.rf.channel; + } + + if (test_bit(REQUIRE_PS_AUTOWAKE, &rt2x00dev->cap_flags) && + (ieee80211_flags & IEEE80211_CONF_CHANGE_PS)) + cancel_delayed_work_sync(&rt2x00dev->autowakeup_work); + + /* + * Start configuration. + */ + rt2x00dev->ops->lib->config(rt2x00dev, &libconf, ieee80211_flags); + + /* + * Some configuration changes affect the link quality + * which means we need to reset the link tuner. + */ + if (ieee80211_flags & IEEE80211_CONF_CHANGE_CHANNEL) + rt2x00link_reset_tuner(rt2x00dev, false); + + if (test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags) && + test_bit(REQUIRE_PS_AUTOWAKE, &rt2x00dev->cap_flags) && + (ieee80211_flags & IEEE80211_CONF_CHANGE_PS) && + (conf->flags & IEEE80211_CONF_PS)) { + beacon_diff = (long)jiffies - (long)rt2x00dev->last_beacon; + beacon_int = msecs_to_jiffies(rt2x00dev->beacon_int); + + if (beacon_diff > beacon_int) + beacon_diff = 0; + + autowake_timeout = (conf->max_sleep_period * beacon_int) - beacon_diff; + queue_delayed_work(rt2x00dev->workqueue, + &rt2x00dev->autowakeup_work, + autowake_timeout - 15); + } + + if (conf->flags & IEEE80211_CONF_PS) + set_bit(CONFIG_POWERSAVING, &rt2x00dev->flags); + else + clear_bit(CONFIG_POWERSAVING, &rt2x00dev->flags); + + rt2x00dev->curr_band = conf->chandef.chan->band; + rt2x00dev->curr_freq = conf->chandef.chan->center_freq; + rt2x00dev->tx_power = conf->power_level; + rt2x00dev->short_retry = conf->short_frame_max_tx_count; + rt2x00dev->long_retry = conf->long_frame_max_tx_count; +} diff --git a/drivers/net/wireless/rt2x00/rt2x00crypto.c b/drivers/net/wireless/rt2x00/rt2x00crypto.c new file mode 100644 index 0000000..1ca4c7f --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00crypto.c @@ -0,0 +1,258 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00lib + Abstract: rt2x00 crypto specific routines. + */ + +#include +#include + +#include "rt2x00.h" +#include "rt2x00lib.h" + +enum cipher rt2x00crypto_key_to_cipher(struct ieee80211_key_conf *key) +{ + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + return CIPHER_WEP64; + case WLAN_CIPHER_SUITE_WEP104: + return CIPHER_WEP128; + case WLAN_CIPHER_SUITE_TKIP: + return CIPHER_TKIP; + case WLAN_CIPHER_SUITE_CCMP: + return CIPHER_AES; + default: + return CIPHER_NONE; + } +} + +void rt2x00crypto_create_tx_descriptor(struct rt2x00_dev *rt2x00dev, + struct sk_buff *skb, + struct txentry_desc *txdesc) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_key_conf *hw_key = tx_info->control.hw_key; + + if (!test_bit(CAPABILITY_HW_CRYPTO, &rt2x00dev->cap_flags) || !hw_key) + return; + + __set_bit(ENTRY_TXD_ENCRYPT, &txdesc->flags); + + txdesc->cipher = rt2x00crypto_key_to_cipher(hw_key); + + if (hw_key->flags & IEEE80211_KEY_FLAG_PAIRWISE) + __set_bit(ENTRY_TXD_ENCRYPT_PAIRWISE, &txdesc->flags); + + txdesc->key_idx = hw_key->hw_key_idx; + txdesc->iv_offset = txdesc->header_length; + txdesc->iv_len = hw_key->iv_len; + + if (!(hw_key->flags & IEEE80211_KEY_FLAG_GENERATE_IV)) + __set_bit(ENTRY_TXD_ENCRYPT_IV, &txdesc->flags); + + if (!(hw_key->flags & IEEE80211_KEY_FLAG_GENERATE_MMIC)) + __set_bit(ENTRY_TXD_ENCRYPT_MMIC, &txdesc->flags); +} + +unsigned int rt2x00crypto_tx_overhead(struct rt2x00_dev *rt2x00dev, + struct sk_buff *skb) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_key_conf *key = tx_info->control.hw_key; + unsigned int overhead = 0; + + if (!test_bit(CAPABILITY_HW_CRYPTO, &rt2x00dev->cap_flags) || !key) + return overhead; + + /* + * Extend frame length to include IV/EIV/ICV/MMIC, + * note that these lengths should only be added when + * mac80211 does not generate it. + */ + overhead += key->icv_len; + + if (!(key->flags & IEEE80211_KEY_FLAG_GENERATE_IV)) + overhead += key->iv_len; + + if (!(key->flags & IEEE80211_KEY_FLAG_GENERATE_MMIC)) { + if (key->cipher == WLAN_CIPHER_SUITE_TKIP) + overhead += 8; + } + + return overhead; +} + +void rt2x00crypto_tx_copy_iv(struct sk_buff *skb, struct txentry_desc *txdesc) +{ + struct skb_frame_desc *skbdesc = get_skb_frame_desc(skb); + + if (unlikely(!txdesc->iv_len)) + return; + + /* Copy IV/EIV data */ + memcpy(skbdesc->iv, skb->data + txdesc->iv_offset, txdesc->iv_len); +} + +void rt2x00crypto_tx_remove_iv(struct sk_buff *skb, struct txentry_desc *txdesc) +{ + struct skb_frame_desc *skbdesc = get_skb_frame_desc(skb); + + if (unlikely(!txdesc->iv_len)) + return; + + /* Copy IV/EIV data */ + memcpy(skbdesc->iv, skb->data + txdesc->iv_offset, txdesc->iv_len); + + /* Move ieee80211 header */ + memmove(skb->data + txdesc->iv_len, skb->data, txdesc->iv_offset); + + /* Pull buffer to correct size */ + skb_pull(skb, txdesc->iv_len); + txdesc->length -= txdesc->iv_len; + + /* IV/EIV data has officially been stripped */ + skbdesc->flags |= SKBDESC_IV_STRIPPED; +} + +void rt2x00crypto_tx_insert_iv(struct sk_buff *skb, unsigned int header_length) +{ + struct skb_frame_desc *skbdesc = get_skb_frame_desc(skb); + const unsigned int iv_len = + ((!!(skbdesc->iv[0])) * 4) + ((!!(skbdesc->iv[1])) * 4); + + if (!(skbdesc->flags & SKBDESC_IV_STRIPPED)) + return; + + skb_push(skb, iv_len); + + /* Move ieee80211 header */ + memmove(skb->data, skb->data + iv_len, header_length); + + /* Copy IV/EIV data */ + memcpy(skb->data + header_length, skbdesc->iv, iv_len); + + /* IV/EIV data has returned into the frame */ + skbdesc->flags &= ~SKBDESC_IV_STRIPPED; +} + +void rt2x00crypto_rx_insert_iv(struct sk_buff *skb, + unsigned int header_length, + struct rxdone_entry_desc *rxdesc) +{ + unsigned int payload_len = rxdesc->size - header_length; + unsigned int align = ALIGN_SIZE(skb, header_length); + unsigned int iv_len; + unsigned int icv_len; + unsigned int transfer = 0; + + /* + * WEP64/WEP128: Provides IV & ICV + * TKIP: Provides IV/EIV & ICV + * AES: Provies IV/EIV & ICV + */ + switch (rxdesc->cipher) { + case CIPHER_WEP64: + case CIPHER_WEP128: + iv_len = 4; + icv_len = 4; + break; + case CIPHER_TKIP: + iv_len = 8; + icv_len = 4; + break; + case CIPHER_AES: + iv_len = 8; + icv_len = 8; + break; + default: + /* Unsupport type */ + return; + } + + /* + * Make room for new data. There are 2 possibilities + * either the alignment is already present between + * the 802.11 header and payload. In that case we + * we have to move the header less then the iv_len + * since we can use the already available l2pad bytes + * for the iv data. + * When the alignment must be added manually we must + * move the header more then iv_len since we must + * make room for the payload move as well. + */ + if (rxdesc->dev_flags & RXDONE_L2PAD) { + skb_push(skb, iv_len - align); + skb_put(skb, icv_len); + + /* Move ieee80211 header */ + memmove(skb->data + transfer, + skb->data + transfer + (iv_len - align), + header_length); + transfer += header_length; + } else { + skb_push(skb, iv_len + align); + if (align < icv_len) + skb_put(skb, icv_len - align); + else if (align > icv_len) + skb_trim(skb, rxdesc->size + iv_len + icv_len); + + /* Move ieee80211 header */ + memmove(skb->data + transfer, + skb->data + transfer + iv_len + align, + header_length); + transfer += header_length; + } + + /* Copy IV/EIV data */ + memcpy(skb->data + transfer, rxdesc->iv, iv_len); + transfer += iv_len; + + /* + * Move payload for alignment purposes. Note that + * this is only needed when no l2 padding is present. + */ + if (!(rxdesc->dev_flags & RXDONE_L2PAD)) { + memmove(skb->data + transfer, + skb->data + transfer + align, + payload_len); + } + + /* + * NOTE: Always count the payload as transferred, + * even when alignment was set to zero. This is required + * for determining the correct offset for the ICV data. + */ + transfer += payload_len; + + /* + * Copy ICV data + * AES appends 8 bytes, we can't fill the upper + * 4 bytes, but mac80211 doesn't care about what + * we provide here anyway and strips it immediately. + */ + memcpy(skb->data + transfer, &rxdesc->icv, 4); + transfer += icv_len; + + /* IV/EIV/ICV has been inserted into frame */ + rxdesc->size = transfer; + rxdesc->flags &= ~RX_FLAG_IV_STRIPPED; +} diff --git a/drivers/net/wireless/rt2x00/rt2x00debug.c b/drivers/net/wireless/rt2x00/rt2x00debug.c new file mode 100644 index 0000000..6e4ec11 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00debug.c @@ -0,0 +1,802 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00lib + Abstract: rt2x00 debugfs specific routines. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "rt2x00.h" +#include "rt2x00lib.h" +#include "rt2x00dump.h" + +#define MAX_LINE_LENGTH 64 + +struct rt2x00debug_crypto { + unsigned long success; + unsigned long icv_error; + unsigned long mic_error; + unsigned long key_error; +}; + +struct rt2x00debug_intf { + /* + * Pointer to driver structure where + * this debugfs entry belongs to. + */ + struct rt2x00_dev *rt2x00dev; + + /* + * Reference to the rt2x00debug structure + * which can be used to communicate with + * the registers. + */ + const struct rt2x00debug *debug; + + /* + * Debugfs entries for: + * - driver folder + * - driver file + * - chipset file + * - device state flags file + * - device capability flags file + * - register folder + * - csr offset/value files + * - eeprom offset/value files + * - bbp offset/value files + * - rf offset/value files + * - rfcsr offset/value files + * - queue folder + * - frame dump file + * - queue stats file + * - crypto stats file + */ + struct dentry *driver_folder; + struct dentry *driver_entry; + struct dentry *chipset_entry; + struct dentry *dev_flags; + struct dentry *cap_flags; + struct dentry *register_folder; + struct dentry *csr_off_entry; + struct dentry *csr_val_entry; + struct dentry *eeprom_off_entry; + struct dentry *eeprom_val_entry; + struct dentry *bbp_off_entry; + struct dentry *bbp_val_entry; + struct dentry *rf_off_entry; + struct dentry *rf_val_entry; + struct dentry *rfcsr_off_entry; + struct dentry *rfcsr_val_entry; + struct dentry *queue_folder; + struct dentry *queue_frame_dump_entry; + struct dentry *queue_stats_entry; + struct dentry *crypto_stats_entry; + + /* + * The frame dump file only allows a single reader, + * so we need to store the current state here. + */ + unsigned long frame_dump_flags; +#define FRAME_DUMP_FILE_OPEN 1 + + /* + * We queue each frame before dumping it to the user, + * per read command we will pass a single skb structure + * so we should be prepared to queue multiple sk buffers + * before sending it to userspace. + */ + struct sk_buff_head frame_dump_skbqueue; + wait_queue_head_t frame_dump_waitqueue; + + /* + * HW crypto statistics. + * All statistics are stored separately per cipher type. + */ + struct rt2x00debug_crypto crypto_stats[CIPHER_MAX]; + + /* + * Driver and chipset files will use a data buffer + * that has been created in advance. This will simplify + * the code since we can use the debugfs functions. + */ + struct debugfs_blob_wrapper driver_blob; + struct debugfs_blob_wrapper chipset_blob; + + /* + * Requested offset for each register type. + */ + unsigned int offset_csr; + unsigned int offset_eeprom; + unsigned int offset_bbp; + unsigned int offset_rf; + unsigned int offset_rfcsr; +}; + +void rt2x00debug_update_crypto(struct rt2x00_dev *rt2x00dev, + struct rxdone_entry_desc *rxdesc) +{ + struct rt2x00debug_intf *intf = rt2x00dev->debugfs_intf; + enum cipher cipher = rxdesc->cipher; + enum rx_crypto status = rxdesc->cipher_status; + + if (cipher == CIPHER_TKIP_NO_MIC) + cipher = CIPHER_TKIP; + if (cipher == CIPHER_NONE || cipher >= CIPHER_MAX) + return; + + /* Remove CIPHER_NONE index */ + cipher--; + + intf->crypto_stats[cipher].success += (status == RX_CRYPTO_SUCCESS); + intf->crypto_stats[cipher].icv_error += (status == RX_CRYPTO_FAIL_ICV); + intf->crypto_stats[cipher].mic_error += (status == RX_CRYPTO_FAIL_MIC); + intf->crypto_stats[cipher].key_error += (status == RX_CRYPTO_FAIL_KEY); +} + +void rt2x00debug_dump_frame(struct rt2x00_dev *rt2x00dev, + enum rt2x00_dump_type type, struct sk_buff *skb) +{ + struct rt2x00debug_intf *intf = rt2x00dev->debugfs_intf; + struct skb_frame_desc *skbdesc = get_skb_frame_desc(skb); + struct sk_buff *skbcopy; + struct rt2x00dump_hdr *dump_hdr; + struct timeval timestamp; + u32 data_len; + + if (likely(!test_bit(FRAME_DUMP_FILE_OPEN, &intf->frame_dump_flags))) + return; + + do_gettimeofday(×tamp); + + if (skb_queue_len(&intf->frame_dump_skbqueue) > 20) { + rt2x00_dbg(rt2x00dev, "txrx dump queue length exceeded\n"); + return; + } + + data_len = skb->len; + if (skbdesc->flags & SKBDESC_DESC_IN_SKB) + data_len -= skbdesc->desc_len; + + skbcopy = alloc_skb(sizeof(*dump_hdr) + skbdesc->desc_len + data_len, + GFP_ATOMIC); + if (!skbcopy) { + rt2x00_dbg(rt2x00dev, "Failed to copy skb for dump\n"); + return; + } + + dump_hdr = (struct rt2x00dump_hdr *)skb_put(skbcopy, sizeof(*dump_hdr)); + dump_hdr->version = cpu_to_le32(DUMP_HEADER_VERSION); + dump_hdr->header_length = cpu_to_le32(sizeof(*dump_hdr)); + dump_hdr->desc_length = cpu_to_le32(skbdesc->desc_len); + dump_hdr->data_length = cpu_to_le32(data_len); + dump_hdr->chip_rt = cpu_to_le16(rt2x00dev->chip.rt); + dump_hdr->chip_rf = cpu_to_le16(rt2x00dev->chip.rf); + dump_hdr->chip_rev = cpu_to_le16(rt2x00dev->chip.rev); + dump_hdr->type = cpu_to_le16(type); + dump_hdr->queue_index = skbdesc->entry->queue->qid; + dump_hdr->entry_index = skbdesc->entry->entry_idx; + dump_hdr->timestamp_sec = cpu_to_le32(timestamp.tv_sec); + dump_hdr->timestamp_usec = cpu_to_le32(timestamp.tv_usec); + + if (!(skbdesc->flags & SKBDESC_DESC_IN_SKB)) + memcpy(skb_put(skbcopy, skbdesc->desc_len), skbdesc->desc, + skbdesc->desc_len); + memcpy(skb_put(skbcopy, skb->len), skb->data, skb->len); + + skb_queue_tail(&intf->frame_dump_skbqueue, skbcopy); + wake_up_interruptible(&intf->frame_dump_waitqueue); + + /* + * Verify that the file has not been closed while we were working. + */ + if (!test_bit(FRAME_DUMP_FILE_OPEN, &intf->frame_dump_flags)) + skb_queue_purge(&intf->frame_dump_skbqueue); +} +EXPORT_SYMBOL_GPL(rt2x00debug_dump_frame); + +static int rt2x00debug_file_open(struct inode *inode, struct file *file) +{ + struct rt2x00debug_intf *intf = inode->i_private; + + file->private_data = inode->i_private; + + if (!try_module_get(intf->debug->owner)) + return -EBUSY; + + return 0; +} + +static int rt2x00debug_file_release(struct inode *inode, struct file *file) +{ + struct rt2x00debug_intf *intf = file->private_data; + + module_put(intf->debug->owner); + + return 0; +} + +static int rt2x00debug_open_queue_dump(struct inode *inode, struct file *file) +{ + struct rt2x00debug_intf *intf = inode->i_private; + int retval; + + retval = rt2x00debug_file_open(inode, file); + if (retval) + return retval; + + if (test_and_set_bit(FRAME_DUMP_FILE_OPEN, &intf->frame_dump_flags)) { + rt2x00debug_file_release(inode, file); + return -EBUSY; + } + + return 0; +} + +static int rt2x00debug_release_queue_dump(struct inode *inode, struct file *file) +{ + struct rt2x00debug_intf *intf = inode->i_private; + + skb_queue_purge(&intf->frame_dump_skbqueue); + + clear_bit(FRAME_DUMP_FILE_OPEN, &intf->frame_dump_flags); + + return rt2x00debug_file_release(inode, file); +} + +static ssize_t rt2x00debug_read_queue_dump(struct file *file, + char __user *buf, + size_t length, + loff_t *offset) +{ + struct rt2x00debug_intf *intf = file->private_data; + struct sk_buff *skb; + size_t status; + int retval; + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + retval = + wait_event_interruptible(intf->frame_dump_waitqueue, + (skb = + skb_dequeue(&intf->frame_dump_skbqueue))); + if (retval) + return retval; + + status = min((size_t)skb->len, length); + if (copy_to_user(buf, skb->data, status)) { + status = -EFAULT; + goto exit; + } + + *offset += status; + +exit: + kfree_skb(skb); + + return status; +} + +static unsigned int rt2x00debug_poll_queue_dump(struct file *file, + poll_table *wait) +{ + struct rt2x00debug_intf *intf = file->private_data; + + poll_wait(file, &intf->frame_dump_waitqueue, wait); + + if (!skb_queue_empty(&intf->frame_dump_skbqueue)) + return POLLOUT | POLLWRNORM; + + return 0; +} + +static const struct file_operations rt2x00debug_fop_queue_dump = { + .owner = THIS_MODULE, + .read = rt2x00debug_read_queue_dump, + .poll = rt2x00debug_poll_queue_dump, + .open = rt2x00debug_open_queue_dump, + .release = rt2x00debug_release_queue_dump, + .llseek = default_llseek, +}; + +static ssize_t rt2x00debug_read_queue_stats(struct file *file, + char __user *buf, + size_t length, + loff_t *offset) +{ + struct rt2x00debug_intf *intf = file->private_data; + struct data_queue *queue; + unsigned long irqflags; + unsigned int lines = 1 + intf->rt2x00dev->data_queues; + size_t size; + char *data; + char *temp; + + if (*offset) + return 0; + + data = kcalloc(lines, MAX_LINE_LENGTH, GFP_KERNEL); + if (!data) + return -ENOMEM; + + temp = data + + sprintf(data, "qid\tflags\t\tcount\tlimit\tlength\tindex\tdma done\tdone\n"); + + queue_for_each(intf->rt2x00dev, queue) { + spin_lock_irqsave(&queue->index_lock, irqflags); + + temp += sprintf(temp, "%d\t0x%.8x\t%d\t%d\t%d\t%d\t%d\t\t%d\n", + queue->qid, (unsigned int)queue->flags, + queue->count, queue->limit, queue->length, + queue->index[Q_INDEX], + queue->index[Q_INDEX_DMA_DONE], + queue->index[Q_INDEX_DONE]); + + spin_unlock_irqrestore(&queue->index_lock, irqflags); + } + + size = strlen(data); + size = min(size, length); + + if (copy_to_user(buf, data, size)) { + kfree(data); + return -EFAULT; + } + + kfree(data); + + *offset += size; + return size; +} + +static const struct file_operations rt2x00debug_fop_queue_stats = { + .owner = THIS_MODULE, + .read = rt2x00debug_read_queue_stats, + .open = rt2x00debug_file_open, + .release = rt2x00debug_file_release, + .llseek = default_llseek, +}; + +#ifdef CPTCFG_RT2X00_LIB_CRYPTO +static ssize_t rt2x00debug_read_crypto_stats(struct file *file, + char __user *buf, + size_t length, + loff_t *offset) +{ + struct rt2x00debug_intf *intf = file->private_data; + static const char * const name[] = { "WEP64", "WEP128", "TKIP", "AES" }; + char *data; + char *temp; + size_t size; + unsigned int i; + + if (*offset) + return 0; + + data = kzalloc((1 + CIPHER_MAX) * MAX_LINE_LENGTH, GFP_KERNEL); + if (!data) + return -ENOMEM; + + temp = data; + temp += sprintf(data, "cipher\tsuccess\ticv err\tmic err\tkey err\n"); + + for (i = 0; i < CIPHER_MAX; i++) { + temp += sprintf(temp, "%s\t%lu\t%lu\t%lu\t%lu\n", name[i], + intf->crypto_stats[i].success, + intf->crypto_stats[i].icv_error, + intf->crypto_stats[i].mic_error, + intf->crypto_stats[i].key_error); + } + + size = strlen(data); + size = min(size, length); + + if (copy_to_user(buf, data, size)) { + kfree(data); + return -EFAULT; + } + + kfree(data); + + *offset += size; + return size; +} + +static const struct file_operations rt2x00debug_fop_crypto_stats = { + .owner = THIS_MODULE, + .read = rt2x00debug_read_crypto_stats, + .open = rt2x00debug_file_open, + .release = rt2x00debug_file_release, + .llseek = default_llseek, +}; +#endif + +#define RT2X00DEBUGFS_OPS_READ(__name, __format, __type) \ +static ssize_t rt2x00debug_read_##__name(struct file *file, \ + char __user *buf, \ + size_t length, \ + loff_t *offset) \ +{ \ + struct rt2x00debug_intf *intf = file->private_data; \ + const struct rt2x00debug *debug = intf->debug; \ + char line[16]; \ + size_t size; \ + unsigned int index = intf->offset_##__name; \ + __type value; \ + \ + if (*offset) \ + return 0; \ + \ + if (index >= debug->__name.word_count) \ + return -EINVAL; \ + \ + index += (debug->__name.word_base / \ + debug->__name.word_size); \ + \ + if (debug->__name.flags & RT2X00DEBUGFS_OFFSET) \ + index *= debug->__name.word_size; \ + \ + debug->__name.read(intf->rt2x00dev, index, &value); \ + \ + size = sprintf(line, __format, value); \ + \ + if (copy_to_user(buf, line, size)) \ + return -EFAULT; \ + \ + *offset += size; \ + return size; \ +} + +#define RT2X00DEBUGFS_OPS_WRITE(__name, __type) \ +static ssize_t rt2x00debug_write_##__name(struct file *file, \ + const char __user *buf,\ + size_t length, \ + loff_t *offset) \ +{ \ + struct rt2x00debug_intf *intf = file->private_data; \ + const struct rt2x00debug *debug = intf->debug; \ + char line[16]; \ + size_t size; \ + unsigned int index = intf->offset_##__name; \ + __type value; \ + \ + if (*offset) \ + return 0; \ + \ + if (index >= debug->__name.word_count) \ + return -EINVAL; \ + \ + if (length > sizeof(line)) \ + return -EINVAL; \ + \ + if (copy_from_user(line, buf, length)) \ + return -EFAULT; \ + \ + size = strlen(line); \ + value = simple_strtoul(line, NULL, 0); \ + \ + index += (debug->__name.word_base / \ + debug->__name.word_size); \ + \ + if (debug->__name.flags & RT2X00DEBUGFS_OFFSET) \ + index *= debug->__name.word_size; \ + \ + debug->__name.write(intf->rt2x00dev, index, value); \ + \ + *offset += size; \ + return size; \ +} + +#define RT2X00DEBUGFS_OPS(__name, __format, __type) \ +RT2X00DEBUGFS_OPS_READ(__name, __format, __type); \ +RT2X00DEBUGFS_OPS_WRITE(__name, __type); \ + \ +static const struct file_operations rt2x00debug_fop_##__name = {\ + .owner = THIS_MODULE, \ + .read = rt2x00debug_read_##__name, \ + .write = rt2x00debug_write_##__name, \ + .open = rt2x00debug_file_open, \ + .release = rt2x00debug_file_release, \ + .llseek = generic_file_llseek, \ +}; + +RT2X00DEBUGFS_OPS(csr, "0x%.8x\n", u32); +RT2X00DEBUGFS_OPS(eeprom, "0x%.4x\n", u16); +RT2X00DEBUGFS_OPS(bbp, "0x%.2x\n", u8); +RT2X00DEBUGFS_OPS(rf, "0x%.8x\n", u32); +RT2X00DEBUGFS_OPS(rfcsr, "0x%.2x\n", u8); + +static ssize_t rt2x00debug_read_dev_flags(struct file *file, + char __user *buf, + size_t length, + loff_t *offset) +{ + struct rt2x00debug_intf *intf = file->private_data; + char line[16]; + size_t size; + + if (*offset) + return 0; + + size = sprintf(line, "0x%.8x\n", (unsigned int)intf->rt2x00dev->flags); + + if (copy_to_user(buf, line, size)) + return -EFAULT; + + *offset += size; + return size; +} + +static const struct file_operations rt2x00debug_fop_dev_flags = { + .owner = THIS_MODULE, + .read = rt2x00debug_read_dev_flags, + .open = rt2x00debug_file_open, + .release = rt2x00debug_file_release, + .llseek = default_llseek, +}; + +static ssize_t rt2x00debug_read_cap_flags(struct file *file, + char __user *buf, + size_t length, + loff_t *offset) +{ + struct rt2x00debug_intf *intf = file->private_data; + char line[16]; + size_t size; + + if (*offset) + return 0; + + size = sprintf(line, "0x%.8x\n", (unsigned int)intf->rt2x00dev->cap_flags); + + if (copy_to_user(buf, line, size)) + return -EFAULT; + + *offset += size; + return size; +} + +static const struct file_operations rt2x00debug_fop_cap_flags = { + .owner = THIS_MODULE, + .read = rt2x00debug_read_cap_flags, + .open = rt2x00debug_file_open, + .release = rt2x00debug_file_release, + .llseek = default_llseek, +}; + +static struct dentry *rt2x00debug_create_file_driver(const char *name, + struct rt2x00debug_intf + *intf, + struct debugfs_blob_wrapper + *blob) +{ + char *data; + + data = kzalloc(3 * MAX_LINE_LENGTH, GFP_KERNEL); + if (!data) + return NULL; + + blob->data = data; + data += sprintf(data, "driver:\t%s\n", intf->rt2x00dev->ops->name); + data += sprintf(data, "version:\t%s\n", DRV_VERSION); + blob->size = strlen(blob->data); + + return debugfs_create_blob(name, S_IRUSR, intf->driver_folder, blob); +} + +static struct dentry *rt2x00debug_create_file_chipset(const char *name, + struct rt2x00debug_intf + *intf, + struct + debugfs_blob_wrapper + *blob) +{ + const struct rt2x00debug *debug = intf->debug; + char *data; + + data = kzalloc(9 * MAX_LINE_LENGTH, GFP_KERNEL); + if (!data) + return NULL; + + blob->data = data; + data += sprintf(data, "rt chip:\t%04x\n", intf->rt2x00dev->chip.rt); + data += sprintf(data, "rf chip:\t%04x\n", intf->rt2x00dev->chip.rf); + data += sprintf(data, "revision:\t%04x\n", intf->rt2x00dev->chip.rev); + data += sprintf(data, "\n"); + data += sprintf(data, "register\tbase\twords\twordsize\n"); +#define RT2X00DEBUGFS_SPRINTF_REGISTER(__name) \ +{ \ + if(debug->__name.read) \ + data += sprintf(data, __stringify(__name) \ + "\t%d\t%d\t%d\n", \ + debug->__name.word_base, \ + debug->__name.word_count, \ + debug->__name.word_size); \ +} + RT2X00DEBUGFS_SPRINTF_REGISTER(csr); + RT2X00DEBUGFS_SPRINTF_REGISTER(eeprom); + RT2X00DEBUGFS_SPRINTF_REGISTER(bbp); + RT2X00DEBUGFS_SPRINTF_REGISTER(rf); + RT2X00DEBUGFS_SPRINTF_REGISTER(rfcsr); +#undef RT2X00DEBUGFS_SPRINTF_REGISTER + + blob->size = strlen(blob->data); + + return debugfs_create_blob(name, S_IRUSR, intf->driver_folder, blob); +} + +void rt2x00debug_register(struct rt2x00_dev *rt2x00dev) +{ + const struct rt2x00debug *debug = rt2x00dev->ops->debugfs; + struct rt2x00debug_intf *intf; + + intf = kzalloc(sizeof(struct rt2x00debug_intf), GFP_KERNEL); + if (!intf) { + rt2x00_err(rt2x00dev, "Failed to allocate debug handler\n"); + return; + } + + intf->debug = debug; + intf->rt2x00dev = rt2x00dev; + rt2x00dev->debugfs_intf = intf; + + intf->driver_folder = + debugfs_create_dir(intf->rt2x00dev->ops->name, + rt2x00dev->hw->wiphy->debugfsdir); + if (IS_ERR(intf->driver_folder) || !intf->driver_folder) + goto exit; + + intf->driver_entry = + rt2x00debug_create_file_driver("driver", intf, &intf->driver_blob); + if (IS_ERR(intf->driver_entry) || !intf->driver_entry) + goto exit; + + intf->chipset_entry = + rt2x00debug_create_file_chipset("chipset", + intf, &intf->chipset_blob); + if (IS_ERR(intf->chipset_entry) || !intf->chipset_entry) + goto exit; + + intf->dev_flags = debugfs_create_file("dev_flags", S_IRUSR, + intf->driver_folder, intf, + &rt2x00debug_fop_dev_flags); + if (IS_ERR(intf->dev_flags) || !intf->dev_flags) + goto exit; + + intf->cap_flags = debugfs_create_file("cap_flags", S_IRUSR, + intf->driver_folder, intf, + &rt2x00debug_fop_cap_flags); + if (IS_ERR(intf->cap_flags) || !intf->cap_flags) + goto exit; + + intf->register_folder = + debugfs_create_dir("register", intf->driver_folder); + if (IS_ERR(intf->register_folder) || !intf->register_folder) + goto exit; + +#define RT2X00DEBUGFS_CREATE_REGISTER_ENTRY(__intf, __name) \ +({ \ + if(debug->__name.read) { \ + (__intf)->__name##_off_entry = \ + debugfs_create_u32(__stringify(__name) "_offset", \ + S_IRUSR | S_IWUSR, \ + (__intf)->register_folder, \ + &(__intf)->offset_##__name); \ + if (IS_ERR((__intf)->__name##_off_entry) \ + || !(__intf)->__name##_off_entry) \ + goto exit; \ + \ + (__intf)->__name##_val_entry = \ + debugfs_create_file(__stringify(__name) "_value", \ + S_IRUSR | S_IWUSR, \ + (__intf)->register_folder, \ + (__intf), &rt2x00debug_fop_##__name); \ + if (IS_ERR((__intf)->__name##_val_entry) \ + || !(__intf)->__name##_val_entry) \ + goto exit; \ + } \ +}) + + RT2X00DEBUGFS_CREATE_REGISTER_ENTRY(intf, csr); + RT2X00DEBUGFS_CREATE_REGISTER_ENTRY(intf, eeprom); + RT2X00DEBUGFS_CREATE_REGISTER_ENTRY(intf, bbp); + RT2X00DEBUGFS_CREATE_REGISTER_ENTRY(intf, rf); + RT2X00DEBUGFS_CREATE_REGISTER_ENTRY(intf, rfcsr); + +#undef RT2X00DEBUGFS_CREATE_REGISTER_ENTRY + + intf->queue_folder = + debugfs_create_dir("queue", intf->driver_folder); + if (IS_ERR(intf->queue_folder) || !intf->queue_folder) + goto exit; + + intf->queue_frame_dump_entry = + debugfs_create_file("dump", S_IRUSR, intf->queue_folder, + intf, &rt2x00debug_fop_queue_dump); + if (IS_ERR(intf->queue_frame_dump_entry) + || !intf->queue_frame_dump_entry) + goto exit; + + skb_queue_head_init(&intf->frame_dump_skbqueue); + init_waitqueue_head(&intf->frame_dump_waitqueue); + + intf->queue_stats_entry = + debugfs_create_file("queue", S_IRUSR, intf->queue_folder, + intf, &rt2x00debug_fop_queue_stats); + +#ifdef CPTCFG_RT2X00_LIB_CRYPTO + if (test_bit(CAPABILITY_HW_CRYPTO, &rt2x00dev->cap_flags)) + intf->crypto_stats_entry = + debugfs_create_file("crypto", S_IRUGO, intf->queue_folder, + intf, &rt2x00debug_fop_crypto_stats); +#endif + + return; + +exit: + rt2x00debug_deregister(rt2x00dev); + rt2x00_err(rt2x00dev, "Failed to register debug handler\n"); +} + +void rt2x00debug_deregister(struct rt2x00_dev *rt2x00dev) +{ + struct rt2x00debug_intf *intf = rt2x00dev->debugfs_intf; + + if (unlikely(!intf)) + return; + + skb_queue_purge(&intf->frame_dump_skbqueue); + +#ifdef CPTCFG_RT2X00_LIB_CRYPTO + debugfs_remove(intf->crypto_stats_entry); +#endif + debugfs_remove(intf->queue_stats_entry); + debugfs_remove(intf->queue_frame_dump_entry); + debugfs_remove(intf->queue_folder); + debugfs_remove(intf->rfcsr_val_entry); + debugfs_remove(intf->rfcsr_off_entry); + debugfs_remove(intf->rf_val_entry); + debugfs_remove(intf->rf_off_entry); + debugfs_remove(intf->bbp_val_entry); + debugfs_remove(intf->bbp_off_entry); + debugfs_remove(intf->eeprom_val_entry); + debugfs_remove(intf->eeprom_off_entry); + debugfs_remove(intf->csr_val_entry); + debugfs_remove(intf->csr_off_entry); + debugfs_remove(intf->register_folder); + debugfs_remove(intf->dev_flags); + debugfs_remove(intf->cap_flags); + debugfs_remove(intf->chipset_entry); + debugfs_remove(intf->driver_entry); + debugfs_remove(intf->driver_folder); + kfree(intf->chipset_blob.data); + kfree(intf->driver_blob.data); + kfree(intf); + + rt2x00dev->debugfs_intf = NULL; +} diff --git a/drivers/net/wireless/rt2x00/rt2x00debug.h b/drivers/net/wireless/rt2x00/rt2x00debug.h new file mode 100644 index 0000000..e11d39b --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00debug.h @@ -0,0 +1,71 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00debug + Abstract: Data structures for the rt2x00debug. + */ + +#ifndef RT2X00DEBUG_H +#define RT2X00DEBUG_H + +struct rt2x00_dev; + +/** + * enum rt2x00debugfs_entry_flags: Flags for debugfs registry entry + * + * @RT2X00DEBUGFS_OFFSET: rt2x00lib should pass the register offset + * as argument when using the callback function read()/write() + */ +enum rt2x00debugfs_entry_flags { + RT2X00DEBUGFS_OFFSET = (1 << 0), +}; + +#define RT2X00DEBUGFS_REGISTER_ENTRY(__name, __type) \ +struct reg##__name { \ + void (*read)(struct rt2x00_dev *rt2x00dev, \ + const unsigned int word, __type *data); \ + void (*write)(struct rt2x00_dev *rt2x00dev, \ + const unsigned int word, __type data); \ + \ + unsigned int flags; \ + \ + unsigned int word_base; \ + unsigned int word_size; \ + unsigned int word_count; \ +} __name + +struct rt2x00debug { + /* + * Reference to the modules structure. + */ + struct module *owner; + + /* + * Register access entries. + */ + RT2X00DEBUGFS_REGISTER_ENTRY(csr, u32); + RT2X00DEBUGFS_REGISTER_ENTRY(eeprom, u16); + RT2X00DEBUGFS_REGISTER_ENTRY(bbp, u8); + RT2X00DEBUGFS_REGISTER_ENTRY(rf, u32); + RT2X00DEBUGFS_REGISTER_ENTRY(rfcsr, u8); +}; + +#endif /* RT2X00DEBUG_H */ diff --git a/drivers/net/wireless/rt2x00/rt2x00dev.c b/drivers/net/wireless/rt2x00/rt2x00dev.c new file mode 100644 index 0000000..45f5455 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00dev.c @@ -0,0 +1,1516 @@ +/* + Copyright (C) 2010 Willow Garage + Copyright (C) 2004 - 2010 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00lib + Abstract: rt2x00 generic device routines. + */ + +#include +#include +#include +#include + +#include "rt2x00.h" +#include "rt2x00lib.h" + +/* + * Utility functions. + */ +u32 rt2x00lib_get_bssidx(struct rt2x00_dev *rt2x00dev, + struct ieee80211_vif *vif) +{ + /* + * When in STA mode, bssidx is always 0 otherwise local_address[5] + * contains the bss number, see BSS_ID_MASK comments for details. + */ + if (rt2x00dev->intf_sta_count) + return 0; + return vif->addr[5] & (rt2x00dev->ops->max_ap_intf - 1); +} +EXPORT_SYMBOL_GPL(rt2x00lib_get_bssidx); + +/* + * Radio control handlers. + */ +int rt2x00lib_enable_radio(struct rt2x00_dev *rt2x00dev) +{ + int status; + + /* + * Don't enable the radio twice. + * And check if the hardware button has been disabled. + */ + if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + return 0; + + /* + * Initialize all data queues. + */ + rt2x00queue_init_queues(rt2x00dev); + + /* + * Enable radio. + */ + status = + rt2x00dev->ops->lib->set_device_state(rt2x00dev, STATE_RADIO_ON); + if (status) + return status; + + rt2x00dev->ops->lib->set_device_state(rt2x00dev, STATE_RADIO_IRQ_ON); + + rt2x00leds_led_radio(rt2x00dev, true); + rt2x00led_led_activity(rt2x00dev, true); + + set_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags); + + /* + * Enable queues. + */ + rt2x00queue_start_queues(rt2x00dev); + rt2x00link_start_tuner(rt2x00dev); + rt2x00link_start_agc(rt2x00dev); + if (test_bit(CAPABILITY_VCO_RECALIBRATION, &rt2x00dev->cap_flags)) + rt2x00link_start_vcocal(rt2x00dev); + + /* + * Start watchdog monitoring. + */ + rt2x00link_start_watchdog(rt2x00dev); + + return 0; +} + +void rt2x00lib_disable_radio(struct rt2x00_dev *rt2x00dev) +{ + if (!test_and_clear_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + return; + + /* + * Stop watchdog monitoring. + */ + rt2x00link_stop_watchdog(rt2x00dev); + + /* + * Stop all queues + */ + rt2x00link_stop_agc(rt2x00dev); + if (test_bit(CAPABILITY_VCO_RECALIBRATION, &rt2x00dev->cap_flags)) + rt2x00link_stop_vcocal(rt2x00dev); + rt2x00link_stop_tuner(rt2x00dev); + rt2x00queue_stop_queues(rt2x00dev); + rt2x00queue_flush_queues(rt2x00dev, true); + + /* + * Disable radio. + */ + rt2x00dev->ops->lib->set_device_state(rt2x00dev, STATE_RADIO_OFF); + rt2x00dev->ops->lib->set_device_state(rt2x00dev, STATE_RADIO_IRQ_OFF); + rt2x00led_led_activity(rt2x00dev, false); + rt2x00leds_led_radio(rt2x00dev, false); +} + +static void rt2x00lib_intf_scheduled_iter(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct rt2x00_dev *rt2x00dev = data; + struct rt2x00_intf *intf = vif_to_intf(vif); + + /* + * It is possible the radio was disabled while the work had been + * scheduled. If that happens we should return here immediately, + * note that in the spinlock protected area above the delayed_flags + * have been cleared correctly. + */ + if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + return; + + if (test_and_clear_bit(DELAYED_UPDATE_BEACON, &intf->delayed_flags)) + rt2x00queue_update_beacon(rt2x00dev, vif); +} + +static void rt2x00lib_intf_scheduled(struct work_struct *work) +{ + struct rt2x00_dev *rt2x00dev = + container_of(work, struct rt2x00_dev, intf_work); + + /* + * Iterate over each interface and perform the + * requested configurations. + */ + ieee80211_iterate_active_interfaces(rt2x00dev->hw, + IEEE80211_IFACE_ITER_RESUME_ALL, + rt2x00lib_intf_scheduled_iter, + rt2x00dev); +} + +static void rt2x00lib_autowakeup(struct work_struct *work) +{ + struct rt2x00_dev *rt2x00dev = + container_of(work, struct rt2x00_dev, autowakeup_work.work); + + if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + return; + + if (rt2x00dev->ops->lib->set_device_state(rt2x00dev, STATE_AWAKE)) + rt2x00_err(rt2x00dev, "Device failed to wakeup\n"); + clear_bit(CONFIG_POWERSAVING, &rt2x00dev->flags); +} + +/* + * Interrupt context handlers. + */ +static void rt2x00lib_bc_buffer_iter(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct rt2x00_dev *rt2x00dev = data; + struct sk_buff *skb; + + /* + * Only AP mode interfaces do broad- and multicast buffering + */ + if (vif->type != NL80211_IFTYPE_AP) + return; + + /* + * Send out buffered broad- and multicast frames + */ + skb = ieee80211_get_buffered_bc(rt2x00dev->hw, vif); + while (skb) { + rt2x00mac_tx(rt2x00dev->hw, NULL, skb); + skb = ieee80211_get_buffered_bc(rt2x00dev->hw, vif); + } +} + +static void rt2x00lib_beaconupdate_iter(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct rt2x00_dev *rt2x00dev = data; + + if (vif->type != NL80211_IFTYPE_AP && + vif->type != NL80211_IFTYPE_ADHOC && + vif->type != NL80211_IFTYPE_MESH_POINT && + vif->type != NL80211_IFTYPE_WDS) + return; + + /* + * Update the beacon without locking. This is safe on PCI devices + * as they only update the beacon periodically here. This should + * never be called for USB devices. + */ + WARN_ON(rt2x00_is_usb(rt2x00dev)); + rt2x00queue_update_beacon_locked(rt2x00dev, vif); +} + +void rt2x00lib_beacondone(struct rt2x00_dev *rt2x00dev) +{ + if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + return; + + /* send buffered bc/mc frames out for every bssid */ + ieee80211_iterate_active_interfaces_atomic( + rt2x00dev->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + rt2x00lib_bc_buffer_iter, rt2x00dev); + /* + * Devices with pre tbtt interrupt don't need to update the beacon + * here as they will fetch the next beacon directly prior to + * transmission. + */ + if (test_bit(CAPABILITY_PRE_TBTT_INTERRUPT, &rt2x00dev->cap_flags)) + return; + + /* fetch next beacon */ + ieee80211_iterate_active_interfaces_atomic( + rt2x00dev->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + rt2x00lib_beaconupdate_iter, rt2x00dev); +} +EXPORT_SYMBOL_GPL(rt2x00lib_beacondone); + +void rt2x00lib_pretbtt(struct rt2x00_dev *rt2x00dev) +{ + if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + return; + + /* fetch next beacon */ + ieee80211_iterate_active_interfaces_atomic( + rt2x00dev->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + rt2x00lib_beaconupdate_iter, rt2x00dev); +} +EXPORT_SYMBOL_GPL(rt2x00lib_pretbtt); + +void rt2x00lib_dmastart(struct queue_entry *entry) +{ + set_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags); + rt2x00queue_index_inc(entry, Q_INDEX); +} +EXPORT_SYMBOL_GPL(rt2x00lib_dmastart); + +void rt2x00lib_dmadone(struct queue_entry *entry) +{ + set_bit(ENTRY_DATA_STATUS_PENDING, &entry->flags); + clear_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags); + rt2x00queue_index_inc(entry, Q_INDEX_DMA_DONE); +} +EXPORT_SYMBOL_GPL(rt2x00lib_dmadone); + +static inline int rt2x00lib_txdone_bar_status(struct queue_entry *entry) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + struct ieee80211_bar *bar = (void *) entry->skb->data; + struct rt2x00_bar_list_entry *bar_entry; + int ret; + + if (likely(!ieee80211_is_back_req(bar->frame_control))) + return 0; + + /* + * Unlike all other frames, the status report for BARs does + * not directly come from the hardware as it is incapable of + * matching a BA to a previously send BAR. The hardware will + * report all BARs as if they weren't acked at all. + * + * Instead the RX-path will scan for incoming BAs and set the + * block_acked flag if it sees one that was likely caused by + * a BAR from us. + * + * Remove remaining BARs here and return their status for + * TX done processing. + */ + ret = 0; + rcu_read_lock(); + list_for_each_entry_rcu(bar_entry, &rt2x00dev->bar_list, list) { + if (bar_entry->entry != entry) + continue; + + spin_lock_bh(&rt2x00dev->bar_list_lock); + /* Return whether this BAR was blockacked or not */ + ret = bar_entry->block_acked; + /* Remove the BAR from our checklist */ + list_del_rcu(&bar_entry->list); + spin_unlock_bh(&rt2x00dev->bar_list_lock); + kfree_rcu(bar_entry, head); + + break; + } + rcu_read_unlock(); + + return ret; +} + +void rt2x00lib_txdone(struct queue_entry *entry, + struct txdone_entry_desc *txdesc) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(entry->skb); + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + unsigned int header_length, i; + u8 rate_idx, rate_flags, retry_rates; + u8 skbdesc_flags = skbdesc->flags; + bool success; + + /* + * Unmap the skb. + */ + rt2x00queue_unmap_skb(entry); + + /* + * Remove the extra tx headroom from the skb. + */ + skb_pull(entry->skb, rt2x00dev->ops->extra_tx_headroom); + + /* + * Signal that the TX descriptor is no longer in the skb. + */ + skbdesc->flags &= ~SKBDESC_DESC_IN_SKB; + + /* + * Determine the length of 802.11 header. + */ + header_length = ieee80211_get_hdrlen_from_skb(entry->skb); + + /* + * Remove L2 padding which was added during + */ + if (test_bit(REQUIRE_L2PAD, &rt2x00dev->cap_flags)) + rt2x00queue_remove_l2pad(entry->skb, header_length); + + /* + * If the IV/EIV data was stripped from the frame before it was + * passed to the hardware, we should now reinsert it again because + * mac80211 will expect the same data to be present it the + * frame as it was passed to us. + */ + if (test_bit(CAPABILITY_HW_CRYPTO, &rt2x00dev->cap_flags)) + rt2x00crypto_tx_insert_iv(entry->skb, header_length); + + /* + * Send frame to debugfs immediately, after this call is completed + * we are going to overwrite the skb->cb array. + */ + rt2x00debug_dump_frame(rt2x00dev, DUMP_FRAME_TXDONE, entry->skb); + + /* + * Determine if the frame has been successfully transmitted and + * remove BARs from our check list while checking for their + * TX status. + */ + success = + rt2x00lib_txdone_bar_status(entry) || + test_bit(TXDONE_SUCCESS, &txdesc->flags) || + test_bit(TXDONE_UNKNOWN, &txdesc->flags); + + /* + * Update TX statistics. + */ + rt2x00dev->link.qual.tx_success += success; + rt2x00dev->link.qual.tx_failed += !success; + + rate_idx = skbdesc->tx_rate_idx; + rate_flags = skbdesc->tx_rate_flags; + retry_rates = test_bit(TXDONE_FALLBACK, &txdesc->flags) ? + (txdesc->retry + 1) : 1; + + /* + * Initialize TX status + */ + memset(&tx_info->status, 0, sizeof(tx_info->status)); + tx_info->status.ack_signal = 0; + + /* + * Frame was send with retries, hardware tried + * different rates to send out the frame, at each + * retry it lowered the rate 1 step except when the + * lowest rate was used. + */ + for (i = 0; i < retry_rates && i < IEEE80211_TX_MAX_RATES; i++) { + tx_info->status.rates[i].idx = rate_idx - i; + tx_info->status.rates[i].flags = rate_flags; + + if (rate_idx - i == 0) { + /* + * The lowest rate (index 0) was used until the + * number of max retries was reached. + */ + tx_info->status.rates[i].count = retry_rates - i; + i++; + break; + } + tx_info->status.rates[i].count = 1; + } + if (i < (IEEE80211_TX_MAX_RATES - 1)) + tx_info->status.rates[i].idx = -1; /* terminate */ + + if (!(tx_info->flags & IEEE80211_TX_CTL_NO_ACK)) { + if (success) + tx_info->flags |= IEEE80211_TX_STAT_ACK; + else + rt2x00dev->low_level_stats.dot11ACKFailureCount++; + } + + /* + * Every single frame has it's own tx status, hence report + * every frame as ampdu of size 1. + * + * TODO: if we can find out how many frames were aggregated + * by the hw we could provide the real ampdu_len to mac80211 + * which would allow the rc algorithm to better decide on + * which rates are suitable. + */ + if (test_bit(TXDONE_AMPDU, &txdesc->flags) || + tx_info->flags & IEEE80211_TX_CTL_AMPDU) { + tx_info->flags |= IEEE80211_TX_STAT_AMPDU; + tx_info->status.ampdu_len = 1; + tx_info->status.ampdu_ack_len = success ? 1 : 0; + + if (!success) + tx_info->flags |= IEEE80211_TX_STAT_AMPDU_NO_BACK; + } + + if (rate_flags & IEEE80211_TX_RC_USE_RTS_CTS) { + if (success) + rt2x00dev->low_level_stats.dot11RTSSuccessCount++; + else + rt2x00dev->low_level_stats.dot11RTSFailureCount++; + } + + /* + * Only send the status report to mac80211 when it's a frame + * that originated in mac80211. If this was a extra frame coming + * through a mac80211 library call (RTS/CTS) then we should not + * send the status report back. + */ + if (!(skbdesc_flags & SKBDESC_NOT_MAC80211)) { + if (test_bit(REQUIRE_TASKLET_CONTEXT, &rt2x00dev->cap_flags)) + ieee80211_tx_status(rt2x00dev->hw, entry->skb); + else + ieee80211_tx_status_ni(rt2x00dev->hw, entry->skb); + } else + dev_kfree_skb_any(entry->skb); + + /* + * Make this entry available for reuse. + */ + entry->skb = NULL; + entry->flags = 0; + + rt2x00dev->ops->lib->clear_entry(entry); + + rt2x00queue_index_inc(entry, Q_INDEX_DONE); + + /* + * If the data queue was below the threshold before the txdone + * handler we must make sure the packet queue in the mac80211 stack + * is reenabled when the txdone handler has finished. This has to be + * serialized with rt2x00mac_tx(), otherwise we can wake up queue + * before it was stopped. + */ + spin_lock_bh(&entry->queue->tx_lock); + if (!rt2x00queue_threshold(entry->queue)) + rt2x00queue_unpause_queue(entry->queue); + spin_unlock_bh(&entry->queue->tx_lock); +} +EXPORT_SYMBOL_GPL(rt2x00lib_txdone); + +void rt2x00lib_txdone_noinfo(struct queue_entry *entry, u32 status) +{ + struct txdone_entry_desc txdesc; + + txdesc.flags = 0; + __set_bit(status, &txdesc.flags); + txdesc.retry = 0; + + rt2x00lib_txdone(entry, &txdesc); +} +EXPORT_SYMBOL_GPL(rt2x00lib_txdone_noinfo); + +static u8 *rt2x00lib_find_ie(u8 *data, unsigned int len, u8 ie) +{ + struct ieee80211_mgmt *mgmt = (void *)data; + u8 *pos, *end; + + pos = (u8 *)mgmt->u.beacon.variable; + end = data + len; + while (pos < end) { + if (pos + 2 + pos[1] > end) + return NULL; + + if (pos[0] == ie) + return pos; + + pos += 2 + pos[1]; + } + + return NULL; +} + +static void rt2x00lib_sleep(struct work_struct *work) +{ + struct rt2x00_dev *rt2x00dev = + container_of(work, struct rt2x00_dev, sleep_work); + + if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + return; + + /* + * Check again is powersaving is enabled, to prevent races from delayed + * work execution. + */ + if (!test_bit(CONFIG_POWERSAVING, &rt2x00dev->flags)) + rt2x00lib_config(rt2x00dev, &rt2x00dev->hw->conf, + IEEE80211_CONF_CHANGE_PS); +} + +static void rt2x00lib_rxdone_check_ba(struct rt2x00_dev *rt2x00dev, + struct sk_buff *skb, + struct rxdone_entry_desc *rxdesc) +{ + struct rt2x00_bar_list_entry *entry; + struct ieee80211_bar *ba = (void *)skb->data; + + if (likely(!ieee80211_is_back(ba->frame_control))) + return; + + if (rxdesc->size < sizeof(*ba) + FCS_LEN) + return; + + rcu_read_lock(); + list_for_each_entry_rcu(entry, &rt2x00dev->bar_list, list) { + + if (ba->start_seq_num != entry->start_seq_num) + continue; + +#define TID_CHECK(a, b) ( \ + ((a) & cpu_to_le16(IEEE80211_BAR_CTRL_TID_INFO_MASK)) == \ + ((b) & cpu_to_le16(IEEE80211_BAR_CTRL_TID_INFO_MASK))) \ + + if (!TID_CHECK(ba->control, entry->control)) + continue; + +#undef TID_CHECK + + if (compare_ether_addr(ba->ra, entry->ta)) + continue; + + if (compare_ether_addr(ba->ta, entry->ra)) + continue; + + /* Mark BAR since we received the according BA */ + spin_lock_bh(&rt2x00dev->bar_list_lock); + entry->block_acked = 1; + spin_unlock_bh(&rt2x00dev->bar_list_lock); + break; + } + rcu_read_unlock(); + +} + +static void rt2x00lib_rxdone_check_ps(struct rt2x00_dev *rt2x00dev, + struct sk_buff *skb, + struct rxdone_entry_desc *rxdesc) +{ + struct ieee80211_hdr *hdr = (void *) skb->data; + struct ieee80211_tim_ie *tim_ie; + u8 *tim; + u8 tim_len; + bool cam; + + /* If this is not a beacon, or if mac80211 has no powersaving + * configured, or if the device is already in powersaving mode + * we can exit now. */ + if (likely(!ieee80211_is_beacon(hdr->frame_control) || + !(rt2x00dev->hw->conf.flags & IEEE80211_CONF_PS))) + return; + + /* min. beacon length + FCS_LEN */ + if (skb->len <= 40 + FCS_LEN) + return; + + /* and only beacons from the associated BSSID, please */ + if (!(rxdesc->dev_flags & RXDONE_MY_BSS) || + !rt2x00dev->aid) + return; + + rt2x00dev->last_beacon = jiffies; + + tim = rt2x00lib_find_ie(skb->data, skb->len - FCS_LEN, WLAN_EID_TIM); + if (!tim) + return; + + if (tim[1] < sizeof(*tim_ie)) + return; + + tim_len = tim[1]; + tim_ie = (struct ieee80211_tim_ie *) &tim[2]; + + /* Check whenever the PHY can be turned off again. */ + + /* 1. What about buffered unicast traffic for our AID? */ + cam = ieee80211_check_tim(tim_ie, tim_len, rt2x00dev->aid); + + /* 2. Maybe the AP wants to send multicast/broadcast data? */ + cam |= (tim_ie->bitmap_ctrl & 0x01); + + if (!cam && !test_bit(CONFIG_POWERSAVING, &rt2x00dev->flags)) + queue_work(rt2x00dev->workqueue, &rt2x00dev->sleep_work); +} + +static int rt2x00lib_rxdone_read_signal(struct rt2x00_dev *rt2x00dev, + struct rxdone_entry_desc *rxdesc) +{ + struct ieee80211_supported_band *sband; + const struct rt2x00_rate *rate; + unsigned int i; + int signal = rxdesc->signal; + int type = (rxdesc->dev_flags & RXDONE_SIGNAL_MASK); + + switch (rxdesc->rate_mode) { + case RATE_MODE_CCK: + case RATE_MODE_OFDM: + /* + * For non-HT rates the MCS value needs to contain the + * actually used rate modulation (CCK or OFDM). + */ + if (rxdesc->dev_flags & RXDONE_SIGNAL_MCS) + signal = RATE_MCS(rxdesc->rate_mode, signal); + + sband = &rt2x00dev->bands[rt2x00dev->curr_band]; + for (i = 0; i < sband->n_bitrates; i++) { + rate = rt2x00_get_rate(sband->bitrates[i].hw_value); + if (((type == RXDONE_SIGNAL_PLCP) && + (rate->plcp == signal)) || + ((type == RXDONE_SIGNAL_BITRATE) && + (rate->bitrate == signal)) || + ((type == RXDONE_SIGNAL_MCS) && + (rate->mcs == signal))) { + return i; + } + } + break; + case RATE_MODE_HT_MIX: + case RATE_MODE_HT_GREENFIELD: + if (signal >= 0 && signal <= 76) + return signal; + break; + default: + break; + } + + rt2x00_warn(rt2x00dev, "Frame received with unrecognized signal, mode=0x%.4x, signal=0x%.4x, type=%d\n", + rxdesc->rate_mode, signal, type); + return 0; +} + +void rt2x00lib_rxdone(struct queue_entry *entry, gfp_t gfp) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + struct rxdone_entry_desc rxdesc; + struct sk_buff *skb; + struct ieee80211_rx_status *rx_status; + unsigned int header_length; + int rate_idx; + + if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags) || + !test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + goto submit_entry; + + if (test_bit(ENTRY_DATA_IO_FAILED, &entry->flags)) + goto submit_entry; + + /* + * Allocate a new sk_buffer. If no new buffer available, drop the + * received frame and reuse the existing buffer. + */ + skb = rt2x00queue_alloc_rxskb(entry, gfp); + if (!skb) + goto submit_entry; + + /* + * Unmap the skb. + */ + rt2x00queue_unmap_skb(entry); + + /* + * Extract the RXD details. + */ + memset(&rxdesc, 0, sizeof(rxdesc)); + rt2x00dev->ops->lib->fill_rxdone(entry, &rxdesc); + + /* + * Check for valid size in case we get corrupted descriptor from + * hardware. + */ + if (unlikely(rxdesc.size == 0 || + rxdesc.size > entry->queue->data_size)) { + rt2x00_err(rt2x00dev, "Wrong frame size %d max %d\n", + rxdesc.size, entry->queue->data_size); + dev_kfree_skb(entry->skb); + goto renew_skb; + } + + /* + * The data behind the ieee80211 header must be + * aligned on a 4 byte boundary. + */ + header_length = ieee80211_get_hdrlen_from_skb(entry->skb); + + /* + * Hardware might have stripped the IV/EIV/ICV data, + * in that case it is possible that the data was + * provided separately (through hardware descriptor) + * in which case we should reinsert the data into the frame. + */ + if ((rxdesc.dev_flags & RXDONE_CRYPTO_IV) && + (rxdesc.flags & RX_FLAG_IV_STRIPPED)) + rt2x00crypto_rx_insert_iv(entry->skb, header_length, + &rxdesc); + else if (header_length && + (rxdesc.size > header_length) && + (rxdesc.dev_flags & RXDONE_L2PAD)) + rt2x00queue_remove_l2pad(entry->skb, header_length); + + /* Trim buffer to correct size */ + skb_trim(entry->skb, rxdesc.size); + + /* + * Translate the signal to the correct bitrate index. + */ + rate_idx = rt2x00lib_rxdone_read_signal(rt2x00dev, &rxdesc); + if (rxdesc.rate_mode == RATE_MODE_HT_MIX || + rxdesc.rate_mode == RATE_MODE_HT_GREENFIELD) + rxdesc.flags |= RX_FLAG_HT; + + /* + * Check if this is a beacon, and more frames have been + * buffered while we were in powersaving mode. + */ + rt2x00lib_rxdone_check_ps(rt2x00dev, entry->skb, &rxdesc); + + /* + * Check for incoming BlockAcks to match to the BlockAckReqs + * we've send out. + */ + rt2x00lib_rxdone_check_ba(rt2x00dev, entry->skb, &rxdesc); + + /* + * Update extra components + */ + rt2x00link_update_stats(rt2x00dev, entry->skb, &rxdesc); + rt2x00debug_update_crypto(rt2x00dev, &rxdesc); + rt2x00debug_dump_frame(rt2x00dev, DUMP_FRAME_RXDONE, entry->skb); + + /* + * Initialize RX status information, and send frame + * to mac80211. + */ + rx_status = IEEE80211_SKB_RXCB(entry->skb); + + /* Ensure that all fields of rx_status are initialized + * properly. The skb->cb array was used for driver + * specific informations, so rx_status might contain + * garbage. + */ + memset(rx_status, 0, sizeof(*rx_status)); + + rx_status->mactime = rxdesc.timestamp; + rx_status->band = rt2x00dev->curr_band; + rx_status->freq = rt2x00dev->curr_freq; + rx_status->rate_idx = rate_idx; + rx_status->signal = rxdesc.rssi; + rx_status->flag = rxdesc.flags; + rx_status->antenna = rt2x00dev->link.ant.active.rx; + + ieee80211_rx_ni(rt2x00dev->hw, entry->skb); + +renew_skb: + /* + * Replace the skb with the freshly allocated one. + */ + entry->skb = skb; + +submit_entry: + entry->flags = 0; + rt2x00queue_index_inc(entry, Q_INDEX_DONE); + if (test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags) && + test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + rt2x00dev->ops->lib->clear_entry(entry); +} +EXPORT_SYMBOL_GPL(rt2x00lib_rxdone); + +/* + * Driver initialization handlers. + */ +const struct rt2x00_rate rt2x00_supported_rates[12] = { + { + .flags = DEV_RATE_CCK, + .bitrate = 10, + .ratemask = BIT(0), + .plcp = 0x00, + .mcs = RATE_MCS(RATE_MODE_CCK, 0), + }, + { + .flags = DEV_RATE_CCK | DEV_RATE_SHORT_PREAMBLE, + .bitrate = 20, + .ratemask = BIT(1), + .plcp = 0x01, + .mcs = RATE_MCS(RATE_MODE_CCK, 1), + }, + { + .flags = DEV_RATE_CCK | DEV_RATE_SHORT_PREAMBLE, + .bitrate = 55, + .ratemask = BIT(2), + .plcp = 0x02, + .mcs = RATE_MCS(RATE_MODE_CCK, 2), + }, + { + .flags = DEV_RATE_CCK | DEV_RATE_SHORT_PREAMBLE, + .bitrate = 110, + .ratemask = BIT(3), + .plcp = 0x03, + .mcs = RATE_MCS(RATE_MODE_CCK, 3), + }, + { + .flags = DEV_RATE_OFDM, + .bitrate = 60, + .ratemask = BIT(4), + .plcp = 0x0b, + .mcs = RATE_MCS(RATE_MODE_OFDM, 0), + }, + { + .flags = DEV_RATE_OFDM, + .bitrate = 90, + .ratemask = BIT(5), + .plcp = 0x0f, + .mcs = RATE_MCS(RATE_MODE_OFDM, 1), + }, + { + .flags = DEV_RATE_OFDM, + .bitrate = 120, + .ratemask = BIT(6), + .plcp = 0x0a, + .mcs = RATE_MCS(RATE_MODE_OFDM, 2), + }, + { + .flags = DEV_RATE_OFDM, + .bitrate = 180, + .ratemask = BIT(7), + .plcp = 0x0e, + .mcs = RATE_MCS(RATE_MODE_OFDM, 3), + }, + { + .flags = DEV_RATE_OFDM, + .bitrate = 240, + .ratemask = BIT(8), + .plcp = 0x09, + .mcs = RATE_MCS(RATE_MODE_OFDM, 4), + }, + { + .flags = DEV_RATE_OFDM, + .bitrate = 360, + .ratemask = BIT(9), + .plcp = 0x0d, + .mcs = RATE_MCS(RATE_MODE_OFDM, 5), + }, + { + .flags = DEV_RATE_OFDM, + .bitrate = 480, + .ratemask = BIT(10), + .plcp = 0x08, + .mcs = RATE_MCS(RATE_MODE_OFDM, 6), + }, + { + .flags = DEV_RATE_OFDM, + .bitrate = 540, + .ratemask = BIT(11), + .plcp = 0x0c, + .mcs = RATE_MCS(RATE_MODE_OFDM, 7), + }, +}; + +static void rt2x00lib_channel(struct ieee80211_channel *entry, + const int channel, const int tx_power, + const int value) +{ + /* XXX: this assumption about the band is wrong for 802.11j */ + entry->band = channel <= 14 ? IEEE80211_BAND_2GHZ : IEEE80211_BAND_5GHZ; + entry->center_freq = ieee80211_channel_to_frequency(channel, + entry->band); + entry->hw_value = value; + entry->max_power = tx_power; + entry->max_antenna_gain = 0xff; +} + +static void rt2x00lib_rate(struct ieee80211_rate *entry, + const u16 index, const struct rt2x00_rate *rate) +{ + entry->flags = 0; + entry->bitrate = rate->bitrate; + entry->hw_value = index; + entry->hw_value_short = index; + + if (rate->flags & DEV_RATE_SHORT_PREAMBLE) + entry->flags |= IEEE80211_RATE_SHORT_PREAMBLE; +} + +static int rt2x00lib_probe_hw_modes(struct rt2x00_dev *rt2x00dev, + struct hw_mode_spec *spec) +{ + struct ieee80211_hw *hw = rt2x00dev->hw; + struct ieee80211_channel *channels; + struct ieee80211_rate *rates; + unsigned int num_rates; + unsigned int i; + + num_rates = 0; + if (spec->supported_rates & SUPPORT_RATE_CCK) + num_rates += 4; + if (spec->supported_rates & SUPPORT_RATE_OFDM) + num_rates += 8; + + channels = kcalloc(spec->num_channels, sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + rates = kcalloc(num_rates, sizeof(*rates), GFP_KERNEL); + if (!rates) + goto exit_free_channels; + + /* + * Initialize Rate list. + */ + for (i = 0; i < num_rates; i++) + rt2x00lib_rate(&rates[i], i, rt2x00_get_rate(i)); + + /* + * Initialize Channel list. + */ + for (i = 0; i < spec->num_channels; i++) { + rt2x00lib_channel(&channels[i], + spec->channels[i].channel, + spec->channels_info[i].max_power, i); + } + + /* + * Intitialize 802.11b, 802.11g + * Rates: CCK, OFDM. + * Channels: 2.4 GHz + */ + if (spec->supported_bands & SUPPORT_BAND_2GHZ) { + rt2x00dev->bands[IEEE80211_BAND_2GHZ].n_channels = 14; + rt2x00dev->bands[IEEE80211_BAND_2GHZ].n_bitrates = num_rates; + rt2x00dev->bands[IEEE80211_BAND_2GHZ].channels = channels; + rt2x00dev->bands[IEEE80211_BAND_2GHZ].bitrates = rates; + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = + &rt2x00dev->bands[IEEE80211_BAND_2GHZ]; + memcpy(&rt2x00dev->bands[IEEE80211_BAND_2GHZ].ht_cap, + &spec->ht, sizeof(spec->ht)); + } + + /* + * Intitialize 802.11a + * Rates: OFDM. + * Channels: OFDM, UNII, HiperLAN2. + */ + if (spec->supported_bands & SUPPORT_BAND_5GHZ) { + rt2x00dev->bands[IEEE80211_BAND_5GHZ].n_channels = + spec->num_channels - 14; + rt2x00dev->bands[IEEE80211_BAND_5GHZ].n_bitrates = + num_rates - 4; + rt2x00dev->bands[IEEE80211_BAND_5GHZ].channels = &channels[14]; + rt2x00dev->bands[IEEE80211_BAND_5GHZ].bitrates = &rates[4]; + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = + &rt2x00dev->bands[IEEE80211_BAND_5GHZ]; + memcpy(&rt2x00dev->bands[IEEE80211_BAND_5GHZ].ht_cap, + &spec->ht, sizeof(spec->ht)); + } + + return 0; + + exit_free_channels: + kfree(channels); + rt2x00_err(rt2x00dev, "Allocation ieee80211 modes failed\n"); + return -ENOMEM; +} + +static void rt2x00lib_remove_hw(struct rt2x00_dev *rt2x00dev) +{ + if (test_bit(DEVICE_STATE_REGISTERED_HW, &rt2x00dev->flags)) + ieee80211_unregister_hw(rt2x00dev->hw); + + if (likely(rt2x00dev->hw->wiphy->bands[IEEE80211_BAND_2GHZ])) { + kfree(rt2x00dev->hw->wiphy->bands[IEEE80211_BAND_2GHZ]->channels); + kfree(rt2x00dev->hw->wiphy->bands[IEEE80211_BAND_2GHZ]->bitrates); + rt2x00dev->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = NULL; + rt2x00dev->hw->wiphy->bands[IEEE80211_BAND_5GHZ] = NULL; + } + + kfree(rt2x00dev->spec.channels_info); +} + +static int rt2x00lib_probe_hw(struct rt2x00_dev *rt2x00dev) +{ + struct hw_mode_spec *spec = &rt2x00dev->spec; + int status; + + if (test_bit(DEVICE_STATE_REGISTERED_HW, &rt2x00dev->flags)) + return 0; + + /* + * Initialize HW modes. + */ + status = rt2x00lib_probe_hw_modes(rt2x00dev, spec); + if (status) + return status; + + /* + * Initialize HW fields. + */ + rt2x00dev->hw->queues = rt2x00dev->ops->tx_queues; + + /* + * Initialize extra TX headroom required. + */ + rt2x00dev->hw->extra_tx_headroom = + max_t(unsigned int, IEEE80211_TX_STATUS_HEADROOM, + rt2x00dev->ops->extra_tx_headroom); + + /* + * Take TX headroom required for alignment into account. + */ + if (test_bit(REQUIRE_L2PAD, &rt2x00dev->cap_flags)) + rt2x00dev->hw->extra_tx_headroom += RT2X00_L2PAD_SIZE; + else if (test_bit(REQUIRE_DMA, &rt2x00dev->cap_flags)) + rt2x00dev->hw->extra_tx_headroom += RT2X00_ALIGN_SIZE; + + /* + * Tell mac80211 about the size of our private STA structure. + */ + rt2x00dev->hw->sta_data_size = sizeof(struct rt2x00_sta); + + /* + * Allocate tx status FIFO for driver use. + */ + if (test_bit(REQUIRE_TXSTATUS_FIFO, &rt2x00dev->cap_flags)) { + /* + * Allocate the txstatus fifo. In the worst case the tx + * status fifo has to hold the tx status of all entries + * in all tx queues. Hence, calculate the kfifo size as + * tx_queues * entry_num and round up to the nearest + * power of 2. + */ + int kfifo_size = + roundup_pow_of_two(rt2x00dev->ops->tx_queues * + rt2x00dev->ops->tx->entry_num * + sizeof(u32)); + + status = kfifo_alloc(&rt2x00dev->txstatus_fifo, kfifo_size, + GFP_KERNEL); + if (status) + return status; + } + + /* + * Initialize tasklets if used by the driver. Tasklets are + * disabled until the interrupts are turned on. The driver + * has to handle that. + */ +#define RT2X00_TASKLET_INIT(taskletname) \ + if (rt2x00dev->ops->lib->taskletname) { \ + tasklet_init(&rt2x00dev->taskletname, \ + rt2x00dev->ops->lib->taskletname, \ + (unsigned long)rt2x00dev); \ + } + + RT2X00_TASKLET_INIT(txstatus_tasklet); + RT2X00_TASKLET_INIT(pretbtt_tasklet); + RT2X00_TASKLET_INIT(tbtt_tasklet); + RT2X00_TASKLET_INIT(rxdone_tasklet); + RT2X00_TASKLET_INIT(autowake_tasklet); + +#undef RT2X00_TASKLET_INIT + + /* + * Register HW. + */ + status = ieee80211_register_hw(rt2x00dev->hw); + if (status) + return status; + + set_bit(DEVICE_STATE_REGISTERED_HW, &rt2x00dev->flags); + + return 0; +} + +/* + * Initialization/uninitialization handlers. + */ +static void rt2x00lib_uninitialize(struct rt2x00_dev *rt2x00dev) +{ + if (!test_and_clear_bit(DEVICE_STATE_INITIALIZED, &rt2x00dev->flags)) + return; + + /* + * Unregister extra components. + */ + rt2x00rfkill_unregister(rt2x00dev); + + /* + * Allow the HW to uninitialize. + */ + rt2x00dev->ops->lib->uninitialize(rt2x00dev); + + /* + * Free allocated queue entries. + */ + rt2x00queue_uninitialize(rt2x00dev); +} + +static int rt2x00lib_initialize(struct rt2x00_dev *rt2x00dev) +{ + int status; + + if (test_bit(DEVICE_STATE_INITIALIZED, &rt2x00dev->flags)) + return 0; + + /* + * Allocate all queue entries. + */ + status = rt2x00queue_initialize(rt2x00dev); + if (status) + return status; + + /* + * Initialize the device. + */ + status = rt2x00dev->ops->lib->initialize(rt2x00dev); + if (status) { + rt2x00queue_uninitialize(rt2x00dev); + return status; + } + + set_bit(DEVICE_STATE_INITIALIZED, &rt2x00dev->flags); + + return 0; +} + +int rt2x00lib_start(struct rt2x00_dev *rt2x00dev) +{ + int retval; + + if (test_bit(DEVICE_STATE_STARTED, &rt2x00dev->flags)) + return 0; + + /* + * If this is the first interface which is added, + * we should load the firmware now. + */ + retval = rt2x00lib_load_firmware(rt2x00dev); + if (retval) + return retval; + + /* + * Initialize the device. + */ + retval = rt2x00lib_initialize(rt2x00dev); + if (retval) + return retval; + + rt2x00dev->intf_ap_count = 0; + rt2x00dev->intf_sta_count = 0; + rt2x00dev->intf_associated = 0; + + /* Enable the radio */ + retval = rt2x00lib_enable_radio(rt2x00dev); + if (retval) + return retval; + + set_bit(DEVICE_STATE_STARTED, &rt2x00dev->flags); + + return 0; +} + +void rt2x00lib_stop(struct rt2x00_dev *rt2x00dev) +{ + if (!test_and_clear_bit(DEVICE_STATE_STARTED, &rt2x00dev->flags)) + return; + + /* + * Perhaps we can add something smarter here, + * but for now just disabling the radio should do. + */ + rt2x00lib_disable_radio(rt2x00dev); + + rt2x00dev->intf_ap_count = 0; + rt2x00dev->intf_sta_count = 0; + rt2x00dev->intf_associated = 0; +} + +static inline void rt2x00lib_set_if_combinations(struct rt2x00_dev *rt2x00dev) +{ + struct ieee80211_iface_limit *if_limit; + struct ieee80211_iface_combination *if_combination; + + if (rt2x00dev->ops->max_ap_intf < 2) + return; + + /* + * Build up AP interface limits structure. + */ + if_limit = &rt2x00dev->if_limits_ap; + if_limit->max = rt2x00dev->ops->max_ap_intf; + if_limit->types = BIT(NL80211_IFTYPE_AP); +#ifdef CPTCFG_MAC80211_MESH + if_limit->types |= BIT(NL80211_IFTYPE_MESH_POINT); +#endif + + /* + * Build up AP interface combinations structure. + */ + if_combination = &rt2x00dev->if_combinations[IF_COMB_AP]; + if_combination->limits = if_limit; + if_combination->n_limits = 1; + if_combination->max_interfaces = if_limit->max; + if_combination->num_different_channels = 1; + + /* + * Finally, specify the possible combinations to mac80211. + */ + rt2x00dev->hw->wiphy->iface_combinations = rt2x00dev->if_combinations; + rt2x00dev->hw->wiphy->n_iface_combinations = 1; +} + +/* + * driver allocation handlers. + */ +int rt2x00lib_probe_dev(struct rt2x00_dev *rt2x00dev) +{ + int retval = -ENOMEM; + + /* + * Set possible interface combinations. + */ + rt2x00lib_set_if_combinations(rt2x00dev); + + /* + * Allocate the driver data memory, if necessary. + */ + if (rt2x00dev->ops->drv_data_size > 0) { + rt2x00dev->drv_data = kzalloc(rt2x00dev->ops->drv_data_size, + GFP_KERNEL); + if (!rt2x00dev->drv_data) { + retval = -ENOMEM; + goto exit; + } + } + + spin_lock_init(&rt2x00dev->irqmask_lock); + mutex_init(&rt2x00dev->csr_mutex); + INIT_LIST_HEAD(&rt2x00dev->bar_list); + spin_lock_init(&rt2x00dev->bar_list_lock); + + set_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags); + + /* + * Make room for rt2x00_intf inside the per-interface + * structure ieee80211_vif. + */ + rt2x00dev->hw->vif_data_size = sizeof(struct rt2x00_intf); + + /* + * rt2x00 devices can only use the last n bits of the MAC address + * for virtual interfaces. + */ + rt2x00dev->hw->wiphy->addr_mask[ETH_ALEN - 1] = + (rt2x00dev->ops->max_ap_intf - 1); + + /* + * Determine which operating modes are supported, all modes + * which require beaconing, depend on the availability of + * beacon entries. + */ + rt2x00dev->hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION); + if (rt2x00dev->ops->bcn->entry_num > 0) + rt2x00dev->hw->wiphy->interface_modes |= + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_AP) | +#ifdef CPTCFG_MAC80211_MESH + BIT(NL80211_IFTYPE_MESH_POINT) | +#endif + BIT(NL80211_IFTYPE_WDS); + + rt2x00dev->hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN; + + /* + * Initialize work. + */ + rt2x00dev->workqueue = + alloc_ordered_workqueue(wiphy_name(rt2x00dev->hw->wiphy), 0); + if (!rt2x00dev->workqueue) { + retval = -ENOMEM; + goto exit; + } + + INIT_WORK(&rt2x00dev->intf_work, rt2x00lib_intf_scheduled); + INIT_DELAYED_WORK(&rt2x00dev->autowakeup_work, rt2x00lib_autowakeup); + INIT_WORK(&rt2x00dev->sleep_work, rt2x00lib_sleep); + + /* + * Let the driver probe the device to detect the capabilities. + */ + retval = rt2x00dev->ops->lib->probe_hw(rt2x00dev); + if (retval) { + rt2x00_err(rt2x00dev, "Failed to allocate device\n"); + goto exit; + } + + /* + * Allocate queue array. + */ + retval = rt2x00queue_allocate(rt2x00dev); + if (retval) + goto exit; + + /* + * Initialize ieee80211 structure. + */ + retval = rt2x00lib_probe_hw(rt2x00dev); + if (retval) { + rt2x00_err(rt2x00dev, "Failed to initialize hw\n"); + goto exit; + } + + /* + * Register extra components. + */ + rt2x00link_register(rt2x00dev); + rt2x00leds_register(rt2x00dev); + rt2x00debug_register(rt2x00dev); + rt2x00rfkill_register(rt2x00dev); + + return 0; + +exit: + rt2x00lib_remove_dev(rt2x00dev); + + return retval; +} +EXPORT_SYMBOL_GPL(rt2x00lib_probe_dev); + +void rt2x00lib_remove_dev(struct rt2x00_dev *rt2x00dev) +{ + clear_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags); + + /* + * Disable radio. + */ + rt2x00lib_disable_radio(rt2x00dev); + + /* + * Stop all work. + */ + cancel_work_sync(&rt2x00dev->intf_work); + cancel_delayed_work_sync(&rt2x00dev->autowakeup_work); + cancel_work_sync(&rt2x00dev->sleep_work); + if (rt2x00_is_usb(rt2x00dev)) { + hrtimer_cancel(&rt2x00dev->txstatus_timer); + cancel_work_sync(&rt2x00dev->rxdone_work); + cancel_work_sync(&rt2x00dev->txdone_work); + } + if (rt2x00dev->workqueue) + destroy_workqueue(rt2x00dev->workqueue); + + /* + * Free the tx status fifo. + */ + kfifo_free(&rt2x00dev->txstatus_fifo); + + /* + * Kill the tx status tasklet. + */ + tasklet_kill(&rt2x00dev->txstatus_tasklet); + tasklet_kill(&rt2x00dev->pretbtt_tasklet); + tasklet_kill(&rt2x00dev->tbtt_tasklet); + tasklet_kill(&rt2x00dev->rxdone_tasklet); + tasklet_kill(&rt2x00dev->autowake_tasklet); + + /* + * Uninitialize device. + */ + rt2x00lib_uninitialize(rt2x00dev); + + /* + * Free extra components + */ + rt2x00debug_deregister(rt2x00dev); + rt2x00leds_unregister(rt2x00dev); + + /* + * Free ieee80211_hw memory. + */ + rt2x00lib_remove_hw(rt2x00dev); + + /* + * Free firmware image. + */ + rt2x00lib_free_firmware(rt2x00dev); + + /* + * Free queue structures. + */ + rt2x00queue_free(rt2x00dev); + + /* + * Free the driver data. + */ + if (rt2x00dev->drv_data) + kfree(rt2x00dev->drv_data); +} +EXPORT_SYMBOL_GPL(rt2x00lib_remove_dev); + +/* + * Device state handlers + */ +#ifdef CONFIG_PM +int rt2x00lib_suspend(struct rt2x00_dev *rt2x00dev, pm_message_t state) +{ + rt2x00_dbg(rt2x00dev, "Going to sleep\n"); + + /* + * Prevent mac80211 from accessing driver while suspended. + */ + if (!test_and_clear_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + return 0; + + /* + * Cleanup as much as possible. + */ + rt2x00lib_uninitialize(rt2x00dev); + + /* + * Suspend/disable extra components. + */ + rt2x00leds_suspend(rt2x00dev); + rt2x00debug_deregister(rt2x00dev); + + /* + * Set device mode to sleep for power management, + * on some hardware this call seems to consistently fail. + * From the specifications it is hard to tell why it fails, + * and if this is a "bad thing". + * Overall it is safe to just ignore the failure and + * continue suspending. The only downside is that the + * device will not be in optimal power save mode, but with + * the radio and the other components already disabled the + * device is as good as disabled. + */ + if (rt2x00dev->ops->lib->set_device_state(rt2x00dev, STATE_SLEEP)) + rt2x00_warn(rt2x00dev, "Device failed to enter sleep state, continue suspending\n"); + + return 0; +} +EXPORT_SYMBOL_GPL(rt2x00lib_suspend); + +int rt2x00lib_resume(struct rt2x00_dev *rt2x00dev) +{ + rt2x00_dbg(rt2x00dev, "Waking up\n"); + + /* + * Restore/enable extra components. + */ + rt2x00debug_register(rt2x00dev); + rt2x00leds_resume(rt2x00dev); + + /* + * We are ready again to receive requests from mac80211. + */ + set_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags); + + return 0; +} +EXPORT_SYMBOL_GPL(rt2x00lib_resume); +#endif /* CONFIG_PM */ + +/* + * rt2x00lib module information. + */ +MODULE_AUTHOR(DRV_PROJECT); +MODULE_VERSION(DRV_VERSION); +MODULE_DESCRIPTION("rt2x00 library"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/rt2x00/rt2x00dump.h b/drivers/net/wireless/rt2x00/rt2x00dump.h new file mode 100644 index 0000000..063ebcc --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00dump.h @@ -0,0 +1,129 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00dump + Abstract: + Data structures for the rt2x00debug & userspace. + + The declarations in this file can be used by both rt2x00 + and userspace and therefore should be kept together in + this file. + */ + +#ifndef RT2X00DUMP_H +#define RT2X00DUMP_H + +/** + * DOC: Introduction + * + * This header is intended to be exported to userspace, + * to make the structures and enumerations available to userspace + * applications. This means that all data types should be exportable. + * + * When rt2x00 is compiled with debugfs support enabled, + * it is possible to capture all data coming in and out of the device + * by reading the frame dump file. This file can have only a single reader. + * The following frames will be reported: + * - All incoming frames (rx) + * - All outgoing frames (tx, including beacon and atim) + * - All completed frames (txdone including atim) + * + * The data is send to the file using the following format: + * + * [rt2x00dump header][hardware descriptor][ieee802.11 frame] + * + * rt2x00dump header: The description of the dumped frame, as well as + * additional information useful for debugging. See &rt2x00dump_hdr. + * hardware descriptor: Descriptor that was used to receive or transmit + * the frame. + * ieee802.11 frame: The actual frame that was received or transmitted. + */ + +/** + * enum rt2x00_dump_type - Frame type + * + * These values are used for the @type member of &rt2x00dump_hdr. + * @DUMP_FRAME_RXDONE: This frame has been received by the hardware. + * @DUMP_FRAME_TX: This frame is queued for transmission to the hardware. + * @DUMP_FRAME_TXDONE: This frame indicates the device has handled + * the tx event which has either succeeded or failed. A frame + * with this type should also have been reported with as a + * %DUMP_FRAME_TX frame. + * @DUMP_FRAME_BEACON: This beacon frame is queued for transmission to the + * hardware. + */ +enum rt2x00_dump_type { + DUMP_FRAME_RXDONE = 1, + DUMP_FRAME_TX = 2, + DUMP_FRAME_TXDONE = 3, + DUMP_FRAME_BEACON = 4, +}; + +/** + * struct rt2x00dump_hdr - Dump frame header + * + * Each frame dumped to the debugfs file starts with this header + * attached. This header contains the description of the actual + * frame which was dumped. + * + * New fields inside the structure must be appended to the end of + * the structure. This way userspace tools compiled for earlier + * header versions can still correctly handle the frame dump + * (although they will not handle all data passed to them in the dump). + * + * @version: Header version should always be set to %DUMP_HEADER_VERSION. + * This field must be checked by userspace to determine if it can + * handle this frame. + * @header_length: The length of the &rt2x00dump_hdr structure. This is + * used for compatibility reasons so userspace can easily determine + * the location of the next field in the dump. + * @desc_length: The length of the device descriptor. + * @data_length: The length of the frame data (including the ieee802.11 header. + * @chip_rt: RT chipset + * @chip_rf: RF chipset + * @chip_rev: Chipset revision + * @type: The frame type (&rt2x00_dump_type) + * @queue_index: The index number of the data queue. + * @entry_index: The index number of the entry inside the data queue. + * @timestamp_sec: Timestamp - seconds + * @timestamp_usec: Timestamp - microseconds + */ +struct rt2x00dump_hdr { + __le32 version; +#define DUMP_HEADER_VERSION 2 + + __le32 header_length; + __le32 desc_length; + __le32 data_length; + + __le16 chip_rt; + __le16 chip_rf; + __le16 chip_rev; + + __le16 type; + __u8 queue_index; + __u8 entry_index; + + __le32 timestamp_sec; + __le32 timestamp_usec; +}; + +#endif /* RT2X00DUMP_H */ diff --git a/drivers/net/wireless/rt2x00/rt2x00firmware.c b/drivers/net/wireless/rt2x00/rt2x00firmware.c new file mode 100644 index 0000000..1b4254b --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00firmware.c @@ -0,0 +1,131 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + Copyright (C) 2004 - 2009 Gertjan van Wingerde + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00lib + Abstract: rt2x00 firmware loading routines. + */ + +#include +#include + +#include "rt2x00.h" +#include "rt2x00lib.h" + +static int rt2x00lib_request_firmware(struct rt2x00_dev *rt2x00dev) +{ + struct device *device = wiphy_dev(rt2x00dev->hw->wiphy); + const struct firmware *fw; + char *fw_name; + int retval; + + /* + * Read correct firmware from harddisk. + */ + fw_name = rt2x00dev->ops->lib->get_firmware_name(rt2x00dev); + if (!fw_name) { + rt2x00_err(rt2x00dev, + "Invalid firmware filename\n" + "Please file bug report to %s\n", DRV_PROJECT); + return -EINVAL; + } + + rt2x00_info(rt2x00dev, "Loading firmware file '%s'\n", fw_name); + + retval = request_firmware(&fw, fw_name, device); + if (retval) { + rt2x00_err(rt2x00dev, "Failed to request Firmware\n"); + return retval; + } + + if (!fw || !fw->size || !fw->data) { + rt2x00_err(rt2x00dev, "Failed to read Firmware\n"); + release_firmware(fw); + return -ENOENT; + } + + rt2x00_info(rt2x00dev, "Firmware detected - version: %d.%d\n", + fw->data[fw->size - 4], fw->data[fw->size - 3]); + snprintf(rt2x00dev->hw->wiphy->fw_version, + sizeof(rt2x00dev->hw->wiphy->fw_version), "%d.%d", + fw->data[fw->size - 4], fw->data[fw->size - 3]); + + retval = rt2x00dev->ops->lib->check_firmware(rt2x00dev, fw->data, fw->size); + switch (retval) { + case FW_OK: + break; + case FW_BAD_CRC: + rt2x00_err(rt2x00dev, "Firmware checksum error\n"); + goto exit; + case FW_BAD_LENGTH: + rt2x00_err(rt2x00dev, "Invalid firmware file length (len=%zu)\n", + fw->size); + goto exit; + case FW_BAD_VERSION: + rt2x00_err(rt2x00dev, "Current firmware does not support detected chipset\n"); + goto exit; + } + + rt2x00dev->fw = fw; + + return 0; + +exit: + release_firmware(fw); + + return -ENOENT; +} + +int rt2x00lib_load_firmware(struct rt2x00_dev *rt2x00dev) +{ + int retval; + + if (!test_bit(REQUIRE_FIRMWARE, &rt2x00dev->cap_flags)) + return 0; + + if (!rt2x00dev->fw) { + retval = rt2x00lib_request_firmware(rt2x00dev); + if (retval) + return retval; + } + + /* + * Send firmware to the device. + */ + retval = rt2x00dev->ops->lib->load_firmware(rt2x00dev, + rt2x00dev->fw->data, + rt2x00dev->fw->size); + + /* + * When the firmware is uploaded to the hardware the LED + * association status might have been triggered, for correct + * LED handling it should now be reset. + */ + rt2x00leds_led_assoc(rt2x00dev, false); + + return retval; +} + +void rt2x00lib_free_firmware(struct rt2x00_dev *rt2x00dev) +{ + release_firmware(rt2x00dev->fw); + rt2x00dev->fw = NULL; +} diff --git a/drivers/net/wireless/rt2x00/rt2x00leds.c b/drivers/net/wireless/rt2x00/rt2x00leds.c new file mode 100644 index 0000000..997a6c8 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00leds.c @@ -0,0 +1,246 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00lib + Abstract: rt2x00 led specific routines. + */ + +#include +#include + +#include "rt2x00.h" +#include "rt2x00lib.h" + +void rt2x00leds_led_quality(struct rt2x00_dev *rt2x00dev, int rssi) +{ + struct rt2x00_led *led = &rt2x00dev->led_qual; + unsigned int brightness; + + if ((led->type != LED_TYPE_QUALITY) || !(led->flags & LED_REGISTERED)) + return; + + /* + * Led handling requires a positive value for the rssi, + * to do that correctly we need to add the correction. + */ + rssi += rt2x00dev->rssi_offset; + + /* + * Get the rssi level, this is used to convert the rssi + * to a LED value inside the range LED_OFF - LED_FULL. + */ + if (rssi <= 30) + rssi = 0; + else if (rssi <= 39) + rssi = 1; + else if (rssi <= 49) + rssi = 2; + else if (rssi <= 53) + rssi = 3; + else if (rssi <= 63) + rssi = 4; + else + rssi = 5; + + /* + * Note that we must _not_ send LED_OFF since the driver + * is going to calculate the value and might use it in a + * division. + */ + brightness = ((LED_FULL / 6) * rssi) + 1; + if (brightness != led->led_dev.brightness) { + led->led_dev.brightness_set(&led->led_dev, brightness); + led->led_dev.brightness = brightness; + } +} + +static void rt2x00led_led_simple(struct rt2x00_led *led, bool enabled) +{ + unsigned int brightness = enabled ? LED_FULL : LED_OFF; + + if (!(led->flags & LED_REGISTERED)) + return; + + led->led_dev.brightness_set(&led->led_dev, brightness); + led->led_dev.brightness = brightness; +} + +void rt2x00led_led_activity(struct rt2x00_dev *rt2x00dev, bool enabled) +{ + if (rt2x00dev->led_qual.type == LED_TYPE_ACTIVITY) + rt2x00led_led_simple(&rt2x00dev->led_qual, enabled); +} + +void rt2x00leds_led_assoc(struct rt2x00_dev *rt2x00dev, bool enabled) +{ + if (rt2x00dev->led_assoc.type == LED_TYPE_ASSOC) + rt2x00led_led_simple(&rt2x00dev->led_assoc, enabled); +} + +void rt2x00leds_led_radio(struct rt2x00_dev *rt2x00dev, bool enabled) +{ + if (rt2x00dev->led_radio.type == LED_TYPE_RADIO) + rt2x00led_led_simple(&rt2x00dev->led_radio, enabled); +} + +static int rt2x00leds_register_led(struct rt2x00_dev *rt2x00dev, + struct rt2x00_led *led, + const char *name) +{ + struct device *device = wiphy_dev(rt2x00dev->hw->wiphy); + int retval; + + led->led_dev.name = name; + led->led_dev.brightness = LED_OFF; + + retval = led_classdev_register(device, &led->led_dev); + if (retval) { + rt2x00_err(rt2x00dev, "Failed to register led handler\n"); + return retval; + } + + led->flags |= LED_REGISTERED; + + return 0; +} + +void rt2x00leds_register(struct rt2x00_dev *rt2x00dev) +{ + char name[36]; + int retval; + unsigned long on_period; + unsigned long off_period; + const char *phy_name = wiphy_name(rt2x00dev->hw->wiphy); + + if (rt2x00dev->led_radio.flags & LED_INITIALIZED) { + snprintf(name, sizeof(name), "%s-%s::radio", + rt2x00dev->ops->name, phy_name); + + retval = rt2x00leds_register_led(rt2x00dev, + &rt2x00dev->led_radio, + name); + if (retval) + goto exit_fail; + } + + if (rt2x00dev->led_assoc.flags & LED_INITIALIZED) { + snprintf(name, sizeof(name), "%s-%s::assoc", + rt2x00dev->ops->name, phy_name); + + retval = rt2x00leds_register_led(rt2x00dev, + &rt2x00dev->led_assoc, + name); + if (retval) + goto exit_fail; + } + + if (rt2x00dev->led_qual.flags & LED_INITIALIZED) { + snprintf(name, sizeof(name), "%s-%s::quality", + rt2x00dev->ops->name, phy_name); + + retval = rt2x00leds_register_led(rt2x00dev, + &rt2x00dev->led_qual, + name); + if (retval) + goto exit_fail; + } + + /* + * Initialize blink time to default value: + * On period: 70ms + * Off period: 30ms + */ + if (rt2x00dev->led_radio.led_dev.blink_set) { + on_period = 70; + off_period = 30; + rt2x00dev->led_radio.led_dev.blink_set( + &rt2x00dev->led_radio.led_dev, &on_period, &off_period); + } + + return; + +exit_fail: + rt2x00leds_unregister(rt2x00dev); +} + +static void rt2x00leds_unregister_led(struct rt2x00_led *led) +{ + led_classdev_unregister(&led->led_dev); + + /* + * This might look weird, but when we are unregistering while + * suspended the led is already off, and since we haven't + * fully resumed yet, access to the device might not be + * possible yet. + */ + if (!(led->led_dev.flags & LED_SUSPENDED)) + led->led_dev.brightness_set(&led->led_dev, LED_OFF); + + led->flags &= ~LED_REGISTERED; +} + +void rt2x00leds_unregister(struct rt2x00_dev *rt2x00dev) +{ + if (rt2x00dev->led_qual.flags & LED_REGISTERED) + rt2x00leds_unregister_led(&rt2x00dev->led_qual); + if (rt2x00dev->led_assoc.flags & LED_REGISTERED) + rt2x00leds_unregister_led(&rt2x00dev->led_assoc); + if (rt2x00dev->led_radio.flags & LED_REGISTERED) + rt2x00leds_unregister_led(&rt2x00dev->led_radio); +} + +static inline void rt2x00leds_suspend_led(struct rt2x00_led *led) +{ + led_classdev_suspend(&led->led_dev); + + /* This shouldn't be needed, but just to be safe */ + led->led_dev.brightness_set(&led->led_dev, LED_OFF); + led->led_dev.brightness = LED_OFF; +} + +void rt2x00leds_suspend(struct rt2x00_dev *rt2x00dev) +{ + if (rt2x00dev->led_qual.flags & LED_REGISTERED) + rt2x00leds_suspend_led(&rt2x00dev->led_qual); + if (rt2x00dev->led_assoc.flags & LED_REGISTERED) + rt2x00leds_suspend_led(&rt2x00dev->led_assoc); + if (rt2x00dev->led_radio.flags & LED_REGISTERED) + rt2x00leds_suspend_led(&rt2x00dev->led_radio); +} + +static inline void rt2x00leds_resume_led(struct rt2x00_led *led) +{ + led_classdev_resume(&led->led_dev); + + /* Device might have enabled the LEDS during resume */ + led->led_dev.brightness_set(&led->led_dev, LED_OFF); + led->led_dev.brightness = LED_OFF; +} + +void rt2x00leds_resume(struct rt2x00_dev *rt2x00dev) +{ + if (rt2x00dev->led_radio.flags & LED_REGISTERED) + rt2x00leds_resume_led(&rt2x00dev->led_radio); + if (rt2x00dev->led_assoc.flags & LED_REGISTERED) + rt2x00leds_resume_led(&rt2x00dev->led_assoc); + if (rt2x00dev->led_qual.flags & LED_REGISTERED) + rt2x00leds_resume_led(&rt2x00dev->led_qual); +} diff --git a/drivers/net/wireless/rt2x00/rt2x00leds.h b/drivers/net/wireless/rt2x00/rt2x00leds.h new file mode 100644 index 0000000..3b46f0c --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00leds.h @@ -0,0 +1,46 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00lib + Abstract: rt2x00 led datastructures and routines + */ + +#ifndef RT2X00LEDS_H +#define RT2X00LEDS_H + +enum led_type { + LED_TYPE_RADIO, + LED_TYPE_ASSOC, + LED_TYPE_ACTIVITY, + LED_TYPE_QUALITY, +}; + +struct rt2x00_led { + struct rt2x00_dev *rt2x00dev; + struct led_classdev led_dev; + + enum led_type type; + unsigned int flags; +#define LED_INITIALIZED ( 1 << 0 ) +#define LED_REGISTERED ( 1 << 1 ) +}; + +#endif /* RT2X00LEDS_H */ diff --git a/drivers/net/wireless/rt2x00/rt2x00lib.h b/drivers/net/wireless/rt2x00/rt2x00lib.h new file mode 100644 index 0000000..5300ecb --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00lib.h @@ -0,0 +1,470 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + Copyright (C) 2004 - 2009 Gertjan van Wingerde + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00lib + Abstract: Data structures and definitions for the rt2x00lib module. + */ + +#ifndef RT2X00LIB_H +#define RT2X00LIB_H + +/* + * Interval defines + */ +#define WATCHDOG_INTERVAL round_jiffies_relative(HZ) +#define LINK_TUNE_INTERVAL round_jiffies_relative(HZ) +#define AGC_INTERVAL round_jiffies_relative(4 * HZ) +#define VCO_INTERVAL round_jiffies_relative(10 * HZ) /* 10 sec */ + +/* + * rt2x00_rate: Per rate device information + */ +struct rt2x00_rate { + unsigned short flags; +#define DEV_RATE_CCK 0x0001 +#define DEV_RATE_OFDM 0x0002 +#define DEV_RATE_SHORT_PREAMBLE 0x0004 + + unsigned short bitrate; /* In 100kbit/s */ + unsigned short ratemask; + + unsigned short plcp; + unsigned short mcs; +}; + +extern const struct rt2x00_rate rt2x00_supported_rates[12]; + +static inline const struct rt2x00_rate *rt2x00_get_rate(const u16 hw_value) +{ + return &rt2x00_supported_rates[hw_value & 0xff]; +} + +#define RATE_MCS(__mode, __mcs) \ + ((((__mode) & 0x00ff) << 8) | ((__mcs) & 0x00ff)) + +static inline int rt2x00_get_rate_mcs(const u16 mcs_value) +{ + return (mcs_value & 0x00ff); +} + +/* + * Radio control handlers. + */ +int rt2x00lib_enable_radio(struct rt2x00_dev *rt2x00dev); +void rt2x00lib_disable_radio(struct rt2x00_dev *rt2x00dev); + +/* + * Initialization handlers. + */ +int rt2x00lib_start(struct rt2x00_dev *rt2x00dev); +void rt2x00lib_stop(struct rt2x00_dev *rt2x00dev); + +/* + * Configuration handlers. + */ +void rt2x00lib_config_intf(struct rt2x00_dev *rt2x00dev, + struct rt2x00_intf *intf, + enum nl80211_iftype type, + const u8 *mac, const u8 *bssid); +void rt2x00lib_config_erp(struct rt2x00_dev *rt2x00dev, + struct rt2x00_intf *intf, + struct ieee80211_bss_conf *conf, + u32 changed); +void rt2x00lib_config_antenna(struct rt2x00_dev *rt2x00dev, + struct antenna_setup ant); +void rt2x00lib_config(struct rt2x00_dev *rt2x00dev, + struct ieee80211_conf *conf, + const unsigned int changed_flags); + +/** + * DOC: Queue handlers + */ + +/** + * rt2x00queue_alloc_rxskb - allocate a skb for RX purposes. + * @entry: The entry for which the skb will be applicable. + */ +struct sk_buff *rt2x00queue_alloc_rxskb(struct queue_entry *entry, gfp_t gfp); + +/** + * rt2x00queue_free_skb - free a skb + * @entry: The entry for which the skb will be applicable. + */ +void rt2x00queue_free_skb(struct queue_entry *entry); + +/** + * rt2x00queue_align_frame - Align 802.11 frame to 4-byte boundary + * @skb: The skb to align + * + * Align the start of the 802.11 frame to a 4-byte boundary, this could + * mean the payload is not aligned properly though. + */ +void rt2x00queue_align_frame(struct sk_buff *skb); + +/** + * rt2x00queue_insert_l2pad - Align 802.11 header & payload to 4-byte boundary + * @skb: The skb to align + * @header_length: Length of 802.11 header + * + * Apply L2 padding to align both header and payload to 4-byte boundary + */ +void rt2x00queue_insert_l2pad(struct sk_buff *skb, unsigned int header_length); + +/** + * rt2x00queue_insert_l2pad - Remove L2 padding from 802.11 frame + * @skb: The skb to align + * @header_length: Length of 802.11 header + * + * Remove L2 padding used to align both header and payload to 4-byte boundary, + * by removing the L2 padding the header will no longer be 4-byte aligned. + */ +void rt2x00queue_remove_l2pad(struct sk_buff *skb, unsigned int header_length); + +/** + * rt2x00queue_write_tx_frame - Write TX frame to hardware + * @queue: Queue over which the frame should be send + * @skb: The skb to send + * @local: frame is not from mac80211 + */ +int rt2x00queue_write_tx_frame(struct data_queue *queue, struct sk_buff *skb, + bool local); + +/** + * rt2x00queue_update_beacon - Send new beacon from mac80211 + * to hardware. Handles locking by itself (mutex). + * @rt2x00dev: Pointer to &struct rt2x00_dev. + * @vif: Interface for which the beacon should be updated. + */ +int rt2x00queue_update_beacon(struct rt2x00_dev *rt2x00dev, + struct ieee80211_vif *vif); + +/** + * rt2x00queue_update_beacon_locked - Send new beacon from mac80211 + * to hardware. Caller needs to ensure locking. + * @rt2x00dev: Pointer to &struct rt2x00_dev. + * @vif: Interface for which the beacon should be updated. + */ +int rt2x00queue_update_beacon_locked(struct rt2x00_dev *rt2x00dev, + struct ieee80211_vif *vif); + +/** + * rt2x00queue_clear_beacon - Clear beacon in hardware + * @rt2x00dev: Pointer to &struct rt2x00_dev. + * @vif: Interface for which the beacon should be updated. + */ +int rt2x00queue_clear_beacon(struct rt2x00_dev *rt2x00dev, + struct ieee80211_vif *vif); + +/** + * rt2x00queue_index_inc - Index incrementation function + * @entry: Queue entry (&struct queue_entry) to perform the action on. + * @index: Index type (&enum queue_index) to perform the action on. + * + * This function will increase the requested index on the entry's queue, + * it will grab the appropriate locks and handle queue overflow events by + * resetting the index to the start of the queue. + */ +void rt2x00queue_index_inc(struct queue_entry *entry, enum queue_index index); + +/** + * rt2x00queue_init_queues - Initialize all data queues + * @rt2x00dev: Pointer to &struct rt2x00_dev. + * + * This function will loop through all available queues to clear all + * index numbers and set the queue entry to the correct initialization + * state. + */ +void rt2x00queue_init_queues(struct rt2x00_dev *rt2x00dev); + +int rt2x00queue_initialize(struct rt2x00_dev *rt2x00dev); +void rt2x00queue_uninitialize(struct rt2x00_dev *rt2x00dev); +int rt2x00queue_allocate(struct rt2x00_dev *rt2x00dev); +void rt2x00queue_free(struct rt2x00_dev *rt2x00dev); + +/** + * rt2x00link_update_stats - Update link statistics from RX frame + * @rt2x00dev: Pointer to &struct rt2x00_dev. + * @skb: Received frame + * @rxdesc: Received frame descriptor + * + * Update link statistics based on the information from the + * received frame descriptor. + */ +void rt2x00link_update_stats(struct rt2x00_dev *rt2x00dev, + struct sk_buff *skb, + struct rxdone_entry_desc *rxdesc); + +/** + * rt2x00link_start_tuner - Start periodic link tuner work + * @rt2x00dev: Pointer to &struct rt2x00_dev. + * + * This start the link tuner periodic work, this work will + * be executed periodically until &rt2x00link_stop_tuner has + * been called. + */ +void rt2x00link_start_tuner(struct rt2x00_dev *rt2x00dev); + +/** + * rt2x00link_stop_tuner - Stop periodic link tuner work + * @rt2x00dev: Pointer to &struct rt2x00_dev. + * + * After this function completed the link tuner will not + * be running until &rt2x00link_start_tuner is called. + */ +void rt2x00link_stop_tuner(struct rt2x00_dev *rt2x00dev); + +/** + * rt2x00link_reset_tuner - Reset periodic link tuner work + * @rt2x00dev: Pointer to &struct rt2x00_dev. + * @antenna: Should the antenna tuning also be reset + * + * The VGC limit configured in the hardware will be reset to 0 + * which forces the driver to rediscover the correct value for + * the current association. This is needed when configuration + * options have changed which could drastically change the + * SNR level or link quality (i.e. changing the antenna setting). + * + * Resetting the link tuner will also cause the periodic work counter + * to be reset. Any driver which has a fixed limit on the number + * of rounds the link tuner is supposed to work will accept the + * tuner actions again if this limit was previously reached. + * + * If @antenna is set to true a the software antenna diversity + * tuning will also be reset. + */ +void rt2x00link_reset_tuner(struct rt2x00_dev *rt2x00dev, bool antenna); + +/** + * rt2x00link_start_watchdog - Start periodic watchdog monitoring + * @rt2x00dev: Pointer to &struct rt2x00_dev. + * + * This start the watchdog periodic work, this work will + *be executed periodically until &rt2x00link_stop_watchdog has + * been called. + */ +void rt2x00link_start_watchdog(struct rt2x00_dev *rt2x00dev); + +/** + * rt2x00link_stop_watchdog - Stop periodic watchdog monitoring + * @rt2x00dev: Pointer to &struct rt2x00_dev. + * + * After this function completed the watchdog monitoring will not + * be running until &rt2x00link_start_watchdog is called. + */ +void rt2x00link_stop_watchdog(struct rt2x00_dev *rt2x00dev); + +/** + * rt2x00link_start_agc - Start periodic gain calibration + * @rt2x00dev: Pointer to &struct rt2x00_dev. + */ +void rt2x00link_start_agc(struct rt2x00_dev *rt2x00dev); + +/** + * rt2x00link_start_vcocal - Start periodic VCO calibration + * @rt2x00dev: Pointer to &struct rt2x00_dev. + */ +void rt2x00link_start_vcocal(struct rt2x00_dev *rt2x00dev); + +/** + * rt2x00link_stop_agc - Stop periodic gain calibration + * @rt2x00dev: Pointer to &struct rt2x00_dev. + */ +void rt2x00link_stop_agc(struct rt2x00_dev *rt2x00dev); + +/** + * rt2x00link_stop_vcocal - Stop periodic VCO calibration + * @rt2x00dev: Pointer to &struct rt2x00_dev. + */ +void rt2x00link_stop_vcocal(struct rt2x00_dev *rt2x00dev); + +/** + * rt2x00link_register - Initialize link tuning & watchdog functionality + * @rt2x00dev: Pointer to &struct rt2x00_dev. + * + * Initialize work structure and all link tuning and watchdog related + * parameters. This will not start the periodic work itself. + */ +void rt2x00link_register(struct rt2x00_dev *rt2x00dev); + +/* + * Firmware handlers. + */ +#ifdef CPTCFG_RT2X00_LIB_FIRMWARE +int rt2x00lib_load_firmware(struct rt2x00_dev *rt2x00dev); +void rt2x00lib_free_firmware(struct rt2x00_dev *rt2x00dev); +#else +static inline int rt2x00lib_load_firmware(struct rt2x00_dev *rt2x00dev) +{ + return 0; +} +static inline void rt2x00lib_free_firmware(struct rt2x00_dev *rt2x00dev) +{ +} +#endif /* CPTCFG_RT2X00_LIB_FIRMWARE */ + +/* + * Debugfs handlers. + */ +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS +void rt2x00debug_register(struct rt2x00_dev *rt2x00dev); +void rt2x00debug_deregister(struct rt2x00_dev *rt2x00dev); +void rt2x00debug_update_crypto(struct rt2x00_dev *rt2x00dev, + struct rxdone_entry_desc *rxdesc); +#else +static inline void rt2x00debug_register(struct rt2x00_dev *rt2x00dev) +{ +} + +static inline void rt2x00debug_deregister(struct rt2x00_dev *rt2x00dev) +{ +} + +static inline void rt2x00debug_update_crypto(struct rt2x00_dev *rt2x00dev, + struct rxdone_entry_desc *rxdesc) +{ +} +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ + +/* + * Crypto handlers. + */ +#ifdef CPTCFG_RT2X00_LIB_CRYPTO +enum cipher rt2x00crypto_key_to_cipher(struct ieee80211_key_conf *key); +void rt2x00crypto_create_tx_descriptor(struct rt2x00_dev *rt2x00dev, + struct sk_buff *skb, + struct txentry_desc *txdesc); +unsigned int rt2x00crypto_tx_overhead(struct rt2x00_dev *rt2x00dev, + struct sk_buff *skb); +void rt2x00crypto_tx_copy_iv(struct sk_buff *skb, + struct txentry_desc *txdesc); +void rt2x00crypto_tx_remove_iv(struct sk_buff *skb, + struct txentry_desc *txdesc); +void rt2x00crypto_tx_insert_iv(struct sk_buff *skb, unsigned int header_length); +void rt2x00crypto_rx_insert_iv(struct sk_buff *skb, + unsigned int header_length, + struct rxdone_entry_desc *rxdesc); +#else +static inline enum cipher rt2x00crypto_key_to_cipher(struct ieee80211_key_conf *key) +{ + return CIPHER_NONE; +} + +static inline void rt2x00crypto_create_tx_descriptor(struct rt2x00_dev *rt2x00dev, + struct sk_buff *skb, + struct txentry_desc *txdesc) +{ +} + +static inline unsigned int rt2x00crypto_tx_overhead(struct rt2x00_dev *rt2x00dev, + struct sk_buff *skb) +{ + return 0; +} + +static inline void rt2x00crypto_tx_copy_iv(struct sk_buff *skb, + struct txentry_desc *txdesc) +{ +} + +static inline void rt2x00crypto_tx_remove_iv(struct sk_buff *skb, + struct txentry_desc *txdesc) +{ +} + +static inline void rt2x00crypto_tx_insert_iv(struct sk_buff *skb, + unsigned int header_length) +{ +} + +static inline void rt2x00crypto_rx_insert_iv(struct sk_buff *skb, + unsigned int header_length, + struct rxdone_entry_desc *rxdesc) +{ +} +#endif /* CPTCFG_RT2X00_LIB_CRYPTO */ + +/* + * RFkill handlers. + */ +static inline void rt2x00rfkill_register(struct rt2x00_dev *rt2x00dev) +{ + if (test_bit(CAPABILITY_HW_BUTTON, &rt2x00dev->cap_flags)) + wiphy_rfkill_start_polling(rt2x00dev->hw->wiphy); +} + +static inline void rt2x00rfkill_unregister(struct rt2x00_dev *rt2x00dev) +{ + if (test_bit(CAPABILITY_HW_BUTTON, &rt2x00dev->cap_flags)) + wiphy_rfkill_stop_polling(rt2x00dev->hw->wiphy); +} + +/* + * LED handlers + */ +#ifdef CPTCFG_RT2X00_LIB_LEDS +void rt2x00leds_led_quality(struct rt2x00_dev *rt2x00dev, int rssi); +void rt2x00led_led_activity(struct rt2x00_dev *rt2x00dev, bool enabled); +void rt2x00leds_led_assoc(struct rt2x00_dev *rt2x00dev, bool enabled); +void rt2x00leds_led_radio(struct rt2x00_dev *rt2x00dev, bool enabled); +void rt2x00leds_register(struct rt2x00_dev *rt2x00dev); +void rt2x00leds_unregister(struct rt2x00_dev *rt2x00dev); +void rt2x00leds_suspend(struct rt2x00_dev *rt2x00dev); +void rt2x00leds_resume(struct rt2x00_dev *rt2x00dev); +#else +static inline void rt2x00leds_led_quality(struct rt2x00_dev *rt2x00dev, + int rssi) +{ +} + +static inline void rt2x00led_led_activity(struct rt2x00_dev *rt2x00dev, + bool enabled) +{ +} + +static inline void rt2x00leds_led_assoc(struct rt2x00_dev *rt2x00dev, + bool enabled) +{ +} + +static inline void rt2x00leds_led_radio(struct rt2x00_dev *rt2x00dev, + bool enabled) +{ +} + +static inline void rt2x00leds_register(struct rt2x00_dev *rt2x00dev) +{ +} + +static inline void rt2x00leds_unregister(struct rt2x00_dev *rt2x00dev) +{ +} + +static inline void rt2x00leds_suspend(struct rt2x00_dev *rt2x00dev) +{ +} + +static inline void rt2x00leds_resume(struct rt2x00_dev *rt2x00dev) +{ +} +#endif /* CPTCFG_RT2X00_LIB_LEDS */ + +#endif /* RT2X00LIB_H */ diff --git a/drivers/net/wireless/rt2x00/rt2x00link.c b/drivers/net/wireless/rt2x00/rt2x00link.c new file mode 100644 index 0000000..8368aab --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00link.c @@ -0,0 +1,520 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00lib + Abstract: rt2x00 generic link tuning routines. + */ + +#include +#include + +#include "rt2x00.h" +#include "rt2x00lib.h" + +/* + * When we lack RSSI information return something less then -80 to + * tell the driver to tune the device to maximum sensitivity. + */ +#define DEFAULT_RSSI -128 + +/* + * Helper struct and macro to work with moving/walking averages. + * When adding a value to the average value the following calculation + * is needed: + * + * avg_rssi = ((avg_rssi * 7) + rssi) / 8; + * + * The advantage of this approach is that we only need 1 variable + * to store the average in (No need for a count and a total). + * But more importantly, normal average values will over time + * move less and less towards newly added values this results + * that with link tuning, the device can have a very good RSSI + * for a few minutes but when the device is moved away from the AP + * the average will not decrease fast enough to compensate. + * The walking average compensates this and will move towards + * the new values correctly allowing a effective link tuning, + * the speed of the average moving towards other values depends + * on the value for the number of samples. The higher the number + * of samples, the slower the average will move. + * We use two variables to keep track of the average value to + * compensate for the rounding errors. This can be a significant + * error (>5dBm) if the factor is too low. + */ +#define AVG_SAMPLES 8 +#define AVG_FACTOR 1000 +#define MOVING_AVERAGE(__avg, __val) \ +({ \ + struct avg_val __new; \ + __new.avg_weight = \ + (__avg).avg_weight ? \ + ((((__avg).avg_weight * ((AVG_SAMPLES) - 1)) + \ + ((__val) * (AVG_FACTOR))) / \ + (AVG_SAMPLES)) : \ + ((__val) * (AVG_FACTOR)); \ + __new.avg = __new.avg_weight / (AVG_FACTOR); \ + __new; \ +}) + +static int rt2x00link_antenna_get_link_rssi(struct rt2x00_dev *rt2x00dev) +{ + struct link_ant *ant = &rt2x00dev->link.ant; + + if (ant->rssi_ant.avg && rt2x00dev->link.qual.rx_success) + return ant->rssi_ant.avg; + return DEFAULT_RSSI; +} + +static int rt2x00link_antenna_get_rssi_history(struct rt2x00_dev *rt2x00dev) +{ + struct link_ant *ant = &rt2x00dev->link.ant; + + if (ant->rssi_history) + return ant->rssi_history; + return DEFAULT_RSSI; +} + +static void rt2x00link_antenna_update_rssi_history(struct rt2x00_dev *rt2x00dev, + int rssi) +{ + struct link_ant *ant = &rt2x00dev->link.ant; + ant->rssi_history = rssi; +} + +static void rt2x00link_antenna_reset(struct rt2x00_dev *rt2x00dev) +{ + rt2x00dev->link.ant.rssi_ant.avg = 0; + rt2x00dev->link.ant.rssi_ant.avg_weight = 0; +} + +static void rt2x00lib_antenna_diversity_sample(struct rt2x00_dev *rt2x00dev) +{ + struct link_ant *ant = &rt2x00dev->link.ant; + struct antenna_setup new_ant; + int other_antenna; + + int sample_current = rt2x00link_antenna_get_link_rssi(rt2x00dev); + int sample_other = rt2x00link_antenna_get_rssi_history(rt2x00dev); + + memcpy(&new_ant, &ant->active, sizeof(new_ant)); + + /* + * We are done sampling. Now we should evaluate the results. + */ + ant->flags &= ~ANTENNA_MODE_SAMPLE; + + /* + * During the last period we have sampled the RSSI + * from both antennas. It now is time to determine + * which antenna demonstrated the best performance. + * When we are already on the antenna with the best + * performance, just create a good starting point + * for the history and we are done. + */ + if (sample_current >= sample_other) { + rt2x00link_antenna_update_rssi_history(rt2x00dev, + sample_current); + return; + } + + other_antenna = (ant->active.rx == ANTENNA_A) ? ANTENNA_B : ANTENNA_A; + + if (ant->flags & ANTENNA_RX_DIVERSITY) + new_ant.rx = other_antenna; + + if (ant->flags & ANTENNA_TX_DIVERSITY) + new_ant.tx = other_antenna; + + rt2x00lib_config_antenna(rt2x00dev, new_ant); +} + +static void rt2x00lib_antenna_diversity_eval(struct rt2x00_dev *rt2x00dev) +{ + struct link_ant *ant = &rt2x00dev->link.ant; + struct antenna_setup new_ant; + int rssi_curr; + int rssi_old; + + memcpy(&new_ant, &ant->active, sizeof(new_ant)); + + /* + * Get current RSSI value along with the historical value, + * after that update the history with the current value. + */ + rssi_curr = rt2x00link_antenna_get_link_rssi(rt2x00dev); + rssi_old = rt2x00link_antenna_get_rssi_history(rt2x00dev); + rt2x00link_antenna_update_rssi_history(rt2x00dev, rssi_curr); + + /* + * Legacy driver indicates that we should swap antenna's + * when the difference in RSSI is greater that 5. This + * also should be done when the RSSI was actually better + * then the previous sample. + * When the difference exceeds the threshold we should + * sample the rssi from the other antenna to make a valid + * comparison between the 2 antennas. + */ + if (abs(rssi_curr - rssi_old) < 5) + return; + + ant->flags |= ANTENNA_MODE_SAMPLE; + + if (ant->flags & ANTENNA_RX_DIVERSITY) + new_ant.rx = (new_ant.rx == ANTENNA_A) ? ANTENNA_B : ANTENNA_A; + + if (ant->flags & ANTENNA_TX_DIVERSITY) + new_ant.tx = (new_ant.tx == ANTENNA_A) ? ANTENNA_B : ANTENNA_A; + + rt2x00lib_config_antenna(rt2x00dev, new_ant); +} + +static bool rt2x00lib_antenna_diversity(struct rt2x00_dev *rt2x00dev) +{ + struct link_ant *ant = &rt2x00dev->link.ant; + + /* + * Determine if software diversity is enabled for + * either the TX or RX antenna (or both). + */ + if (!(ant->flags & ANTENNA_RX_DIVERSITY) && + !(ant->flags & ANTENNA_TX_DIVERSITY)) { + ant->flags = 0; + return true; + } + + /* + * If we have only sampled the data over the last period + * we should now harvest the data. Otherwise just evaluate + * the data. The latter should only be performed once + * every 2 seconds. + */ + if (ant->flags & ANTENNA_MODE_SAMPLE) { + rt2x00lib_antenna_diversity_sample(rt2x00dev); + return true; + } else if (rt2x00dev->link.count & 1) { + rt2x00lib_antenna_diversity_eval(rt2x00dev); + return true; + } + + return false; +} + +void rt2x00link_update_stats(struct rt2x00_dev *rt2x00dev, + struct sk_buff *skb, + struct rxdone_entry_desc *rxdesc) +{ + struct link *link = &rt2x00dev->link; + struct link_qual *qual = &rt2x00dev->link.qual; + struct link_ant *ant = &rt2x00dev->link.ant; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + + /* + * No need to update the stats for !=STA interfaces + */ + if (!rt2x00dev->intf_sta_count) + return; + + /* + * Frame was received successfully since non-succesfull + * frames would have been dropped by the hardware. + */ + qual->rx_success++; + + /* + * We are only interested in quality statistics from + * beacons which came from the BSS which we are + * associated with. + */ + if (!ieee80211_is_beacon(hdr->frame_control) || + !(rxdesc->dev_flags & RXDONE_MY_BSS)) + return; + + /* + * Update global RSSI + */ + link->avg_rssi = MOVING_AVERAGE(link->avg_rssi, rxdesc->rssi); + + /* + * Update antenna RSSI + */ + ant->rssi_ant = MOVING_AVERAGE(ant->rssi_ant, rxdesc->rssi); +} + +void rt2x00link_start_tuner(struct rt2x00_dev *rt2x00dev) +{ + struct link *link = &rt2x00dev->link; + + /* + * Link tuning should only be performed when + * an active sta interface exists. AP interfaces + * don't need link tuning and monitor mode interfaces + * should never have to work with link tuners. + */ + if (!rt2x00dev->intf_sta_count) + return; + + /** + * While scanning, link tuning is disabled. By default + * the most sensitive settings will be used to make sure + * that all beacons and probe responses will be received + * during the scan. + */ + if (test_bit(DEVICE_STATE_SCANNING, &rt2x00dev->flags)) + return; + + rt2x00link_reset_tuner(rt2x00dev, false); + + if (test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + ieee80211_queue_delayed_work(rt2x00dev->hw, + &link->work, LINK_TUNE_INTERVAL); +} + +void rt2x00link_stop_tuner(struct rt2x00_dev *rt2x00dev) +{ + cancel_delayed_work_sync(&rt2x00dev->link.work); +} + +void rt2x00link_reset_tuner(struct rt2x00_dev *rt2x00dev, bool antenna) +{ + struct link_qual *qual = &rt2x00dev->link.qual; + u8 vgc_level = qual->vgc_level_reg; + + if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + return; + + /* + * Reset link information. + * Both the currently active vgc level as well as + * the link tuner counter should be reset. Resetting + * the counter is important for devices where the + * device should only perform link tuning during the + * first minute after being enabled. + */ + rt2x00dev->link.count = 0; + memset(qual, 0, sizeof(*qual)); + + /* + * Restore the VGC level as stored in the registers, + * the driver can use this to determine if the register + * must be updated during reset or not. + */ + qual->vgc_level_reg = vgc_level; + + /* + * Reset the link tuner. + */ + rt2x00dev->ops->lib->reset_tuner(rt2x00dev, qual); + + if (antenna) + rt2x00link_antenna_reset(rt2x00dev); +} + +static void rt2x00link_reset_qual(struct rt2x00_dev *rt2x00dev) +{ + struct link_qual *qual = &rt2x00dev->link.qual; + + qual->rx_success = 0; + qual->rx_failed = 0; + qual->tx_success = 0; + qual->tx_failed = 0; +} + +static void rt2x00link_tuner(struct work_struct *work) +{ + struct rt2x00_dev *rt2x00dev = + container_of(work, struct rt2x00_dev, link.work.work); + struct link *link = &rt2x00dev->link; + struct link_qual *qual = &rt2x00dev->link.qual; + + /* + * When the radio is shutting down we should + * immediately cease all link tuning. + */ + if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags) || + test_bit(DEVICE_STATE_SCANNING, &rt2x00dev->flags)) + return; + + /* + * Update statistics. + */ + rt2x00dev->ops->lib->link_stats(rt2x00dev, qual); + rt2x00dev->low_level_stats.dot11FCSErrorCount += qual->rx_failed; + + /* + * Update quality RSSI for link tuning, + * when we have received some frames and we managed to + * collect the RSSI data we could use this. Otherwise we + * must fallback to the default RSSI value. + */ + if (!link->avg_rssi.avg || !qual->rx_success) + qual->rssi = DEFAULT_RSSI; + else + qual->rssi = link->avg_rssi.avg; + + /* + * Check if link tuning is supported by the hardware, some hardware + * do not support link tuning at all, while other devices can disable + * the feature from the EEPROM. + */ + if (test_bit(CAPABILITY_LINK_TUNING, &rt2x00dev->cap_flags)) + rt2x00dev->ops->lib->link_tuner(rt2x00dev, qual, link->count); + + /* + * Send a signal to the led to update the led signal strength. + */ + rt2x00leds_led_quality(rt2x00dev, qual->rssi); + + /* + * Evaluate antenna setup, make this the last step when + * rt2x00lib_antenna_diversity made changes the quality + * statistics will be reset. + */ + if (rt2x00lib_antenna_diversity(rt2x00dev)) + rt2x00link_reset_qual(rt2x00dev); + + /* + * Increase tuner counter, and reschedule the next link tuner run. + */ + link->count++; + + if (test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + ieee80211_queue_delayed_work(rt2x00dev->hw, + &link->work, LINK_TUNE_INTERVAL); +} + +void rt2x00link_start_watchdog(struct rt2x00_dev *rt2x00dev) +{ + struct link *link = &rt2x00dev->link; + + if (test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags) && + rt2x00dev->ops->lib->watchdog) + ieee80211_queue_delayed_work(rt2x00dev->hw, + &link->watchdog_work, + WATCHDOG_INTERVAL); +} + +void rt2x00link_stop_watchdog(struct rt2x00_dev *rt2x00dev) +{ + cancel_delayed_work_sync(&rt2x00dev->link.watchdog_work); +} + +static void rt2x00link_watchdog(struct work_struct *work) +{ + struct rt2x00_dev *rt2x00dev = + container_of(work, struct rt2x00_dev, link.watchdog_work.work); + struct link *link = &rt2x00dev->link; + + /* + * When the radio is shutting down we should + * immediately cease the watchdog monitoring. + */ + if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + return; + + rt2x00dev->ops->lib->watchdog(rt2x00dev); + + if (test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + ieee80211_queue_delayed_work(rt2x00dev->hw, + &link->watchdog_work, + WATCHDOG_INTERVAL); +} + +void rt2x00link_start_agc(struct rt2x00_dev *rt2x00dev) +{ + struct link *link = &rt2x00dev->link; + + if (test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags) && + rt2x00dev->ops->lib->gain_calibration) + ieee80211_queue_delayed_work(rt2x00dev->hw, + &link->agc_work, + AGC_INTERVAL); +} + +void rt2x00link_start_vcocal(struct rt2x00_dev *rt2x00dev) +{ + struct link *link = &rt2x00dev->link; + + if (test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags) && + rt2x00dev->ops->lib->vco_calibration) + ieee80211_queue_delayed_work(rt2x00dev->hw, + &link->vco_work, + VCO_INTERVAL); +} + +void rt2x00link_stop_agc(struct rt2x00_dev *rt2x00dev) +{ + cancel_delayed_work_sync(&rt2x00dev->link.agc_work); +} + +void rt2x00link_stop_vcocal(struct rt2x00_dev *rt2x00dev) +{ + cancel_delayed_work_sync(&rt2x00dev->link.vco_work); +} + +static void rt2x00link_agc(struct work_struct *work) +{ + struct rt2x00_dev *rt2x00dev = + container_of(work, struct rt2x00_dev, link.agc_work.work); + struct link *link = &rt2x00dev->link; + + /* + * When the radio is shutting down we should + * immediately cease the watchdog monitoring. + */ + if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + return; + + rt2x00dev->ops->lib->gain_calibration(rt2x00dev); + + if (test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + ieee80211_queue_delayed_work(rt2x00dev->hw, + &link->agc_work, + AGC_INTERVAL); +} + +static void rt2x00link_vcocal(struct work_struct *work) +{ + struct rt2x00_dev *rt2x00dev = + container_of(work, struct rt2x00_dev, link.vco_work.work); + struct link *link = &rt2x00dev->link; + + /* + * When the radio is shutting down we should + * immediately cease the VCO calibration. + */ + if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + return; + + rt2x00dev->ops->lib->vco_calibration(rt2x00dev); + + if (test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + ieee80211_queue_delayed_work(rt2x00dev->hw, + &link->vco_work, + VCO_INTERVAL); +} + +void rt2x00link_register(struct rt2x00_dev *rt2x00dev) +{ + INIT_DELAYED_WORK(&rt2x00dev->link.agc_work, rt2x00link_agc); + if (test_bit(CAPABILITY_VCO_RECALIBRATION, &rt2x00dev->cap_flags)) + INIT_DELAYED_WORK(&rt2x00dev->link.vco_work, rt2x00link_vcocal); + INIT_DELAYED_WORK(&rt2x00dev->link.watchdog_work, rt2x00link_watchdog); + INIT_DELAYED_WORK(&rt2x00dev->link.work, rt2x00link_tuner); +} diff --git a/drivers/net/wireless/rt2x00/rt2x00mac.c b/drivers/net/wireless/rt2x00/rt2x00mac.c new file mode 100644 index 0000000..7768d3f --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00mac.c @@ -0,0 +1,855 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00mac + Abstract: rt2x00 generic mac80211 routines. + */ + +#include +#include + +#include "rt2x00.h" +#include "rt2x00lib.h" + +static int rt2x00mac_tx_rts_cts(struct rt2x00_dev *rt2x00dev, + struct data_queue *queue, + struct sk_buff *frag_skb) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(frag_skb); + struct ieee80211_tx_info *rts_info; + struct sk_buff *skb; + unsigned int data_length; + int retval = 0; + + if (tx_info->control.rates[0].flags & IEEE80211_TX_RC_USE_CTS_PROTECT) + data_length = sizeof(struct ieee80211_cts); + else + data_length = sizeof(struct ieee80211_rts); + + skb = dev_alloc_skb(data_length + rt2x00dev->hw->extra_tx_headroom); + if (unlikely(!skb)) { + rt2x00_warn(rt2x00dev, "Failed to create RTS/CTS frame\n"); + return -ENOMEM; + } + + skb_reserve(skb, rt2x00dev->hw->extra_tx_headroom); + skb_put(skb, data_length); + + /* + * Copy TX information over from original frame to + * RTS/CTS frame. Note that we set the no encryption flag + * since we don't want this frame to be encrypted. + * RTS frames should be acked, while CTS-to-self frames + * should not. The ready for TX flag is cleared to prevent + * it being automatically send when the descriptor is + * written to the hardware. + */ + memcpy(skb->cb, frag_skb->cb, sizeof(skb->cb)); + rts_info = IEEE80211_SKB_CB(skb); + rts_info->control.rates[0].flags &= ~IEEE80211_TX_RC_USE_RTS_CTS; + rts_info->control.rates[0].flags &= ~IEEE80211_TX_RC_USE_CTS_PROTECT; + + if (tx_info->control.rates[0].flags & IEEE80211_TX_RC_USE_CTS_PROTECT) + rts_info->flags |= IEEE80211_TX_CTL_NO_ACK; + else + rts_info->flags &= ~IEEE80211_TX_CTL_NO_ACK; + + /* Disable hardware encryption */ + rts_info->control.hw_key = NULL; + + /* + * RTS/CTS frame should use the length of the frame plus any + * encryption overhead that will be added by the hardware. + */ + data_length += rt2x00crypto_tx_overhead(rt2x00dev, skb); + + if (tx_info->control.rates[0].flags & IEEE80211_TX_RC_USE_CTS_PROTECT) + ieee80211_ctstoself_get(rt2x00dev->hw, tx_info->control.vif, + frag_skb->data, data_length, tx_info, + (struct ieee80211_cts *)(skb->data)); + else + ieee80211_rts_get(rt2x00dev->hw, tx_info->control.vif, + frag_skb->data, data_length, tx_info, + (struct ieee80211_rts *)(skb->data)); + + retval = rt2x00queue_write_tx_frame(queue, skb, true); + if (retval) { + dev_kfree_skb_any(skb); + rt2x00_warn(rt2x00dev, "Failed to send RTS/CTS frame\n"); + } + + return retval; +} + +void rt2x00mac_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + enum data_queue_qid qid = skb_get_queue_mapping(skb); + struct data_queue *queue = NULL; + + /* + * Mac80211 might be calling this function while we are trying + * to remove the device or perhaps suspending it. + * Note that we can only stop the TX queues inside the TX path + * due to possible race conditions in mac80211. + */ + if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + goto exit_free_skb; + + /* + * Use the ATIM queue if appropriate and present. + */ + if (tx_info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM && + test_bit(REQUIRE_ATIM_QUEUE, &rt2x00dev->cap_flags)) + qid = QID_ATIM; + + queue = rt2x00queue_get_tx_queue(rt2x00dev, qid); + if (unlikely(!queue)) { + rt2x00_err(rt2x00dev, + "Attempt to send packet over invalid queue %d\n" + "Please file bug report to %s\n", qid, DRV_PROJECT); + goto exit_free_skb; + } + + /* + * If CTS/RTS is required. create and queue that frame first. + * Make sure we have at least enough entries available to send + * this CTS/RTS frame as well as the data frame. + * Note that when the driver has set the set_rts_threshold() + * callback function it doesn't need software generation of + * either RTS or CTS-to-self frame and handles everything + * inside the hardware. + */ + if (!rt2x00dev->ops->hw->set_rts_threshold && + (tx_info->control.rates[0].flags & (IEEE80211_TX_RC_USE_RTS_CTS | + IEEE80211_TX_RC_USE_CTS_PROTECT))) { + if (rt2x00queue_available(queue) <= 1) + goto exit_fail; + + if (rt2x00mac_tx_rts_cts(rt2x00dev, queue, skb)) + goto exit_fail; + } + + if (unlikely(rt2x00queue_write_tx_frame(queue, skb, false))) + goto exit_fail; + + /* + * Pausing queue has to be serialized with rt2x00lib_txdone(). Note + * we should not use spin_lock_bh variant as bottom halve was already + * disabled before ieee80211_xmit() call. + */ + spin_lock(&queue->tx_lock); + if (rt2x00queue_threshold(queue)) + rt2x00queue_pause_queue(queue); + spin_unlock(&queue->tx_lock); + + return; + + exit_fail: + spin_lock(&queue->tx_lock); + rt2x00queue_pause_queue(queue); + spin_unlock(&queue->tx_lock); + exit_free_skb: + ieee80211_free_txskb(hw, skb); +} +EXPORT_SYMBOL_GPL(rt2x00mac_tx); + +int rt2x00mac_start(struct ieee80211_hw *hw) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + + if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + return 0; + + return rt2x00lib_start(rt2x00dev); +} +EXPORT_SYMBOL_GPL(rt2x00mac_start); + +void rt2x00mac_stop(struct ieee80211_hw *hw) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + + if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + return; + + rt2x00lib_stop(rt2x00dev); +} +EXPORT_SYMBOL_GPL(rt2x00mac_stop); + +int rt2x00mac_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct rt2x00_intf *intf = vif_to_intf(vif); + struct data_queue *queue = rt2x00dev->bcn; + struct queue_entry *entry = NULL; + unsigned int i; + + /* + * Don't allow interfaces to be added + * the device has disappeared. + */ + if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags) || + !test_bit(DEVICE_STATE_STARTED, &rt2x00dev->flags)) + return -ENODEV; + + /* + * Loop through all beacon queues to find a free + * entry. Since there are as much beacon entries + * as the maximum interfaces, this search shouldn't + * fail. + */ + for (i = 0; i < queue->limit; i++) { + entry = &queue->entries[i]; + if (!test_and_set_bit(ENTRY_BCN_ASSIGNED, &entry->flags)) + break; + } + + if (unlikely(i == queue->limit)) + return -ENOBUFS; + + /* + * We are now absolutely sure the interface can be created, + * increase interface count and start initialization. + */ + + if (vif->type == NL80211_IFTYPE_AP) + rt2x00dev->intf_ap_count++; + else + rt2x00dev->intf_sta_count++; + + mutex_init(&intf->beacon_skb_mutex); + intf->beacon = entry; + + /* + * The MAC address must be configured after the device + * has been initialized. Otherwise the device can reset + * the MAC registers. + * The BSSID address must only be configured in AP mode, + * however we should not send an empty BSSID address for + * STA interfaces at this time, since this can cause + * invalid behavior in the device. + */ + rt2x00lib_config_intf(rt2x00dev, intf, vif->type, + vif->addr, NULL); + + /* + * Some filters depend on the current working mode. We can force + * an update during the next configure_filter() run by mac80211 by + * resetting the current packet_filter state. + */ + rt2x00dev->packet_filter = 0; + + return 0; +} +EXPORT_SYMBOL_GPL(rt2x00mac_add_interface); + +void rt2x00mac_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct rt2x00_intf *intf = vif_to_intf(vif); + + /* + * Don't allow interfaces to be remove while + * either the device has disappeared or when + * no interface is present. + */ + if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags) || + (vif->type == NL80211_IFTYPE_AP && !rt2x00dev->intf_ap_count) || + (vif->type != NL80211_IFTYPE_AP && !rt2x00dev->intf_sta_count)) + return; + + if (vif->type == NL80211_IFTYPE_AP) + rt2x00dev->intf_ap_count--; + else + rt2x00dev->intf_sta_count--; + + /* + * Release beacon entry so it is available for + * new interfaces again. + */ + clear_bit(ENTRY_BCN_ASSIGNED, &intf->beacon->flags); + + /* + * Make sure the bssid and mac address registers + * are cleared to prevent false ACKing of frames. + */ + rt2x00lib_config_intf(rt2x00dev, intf, + NL80211_IFTYPE_UNSPECIFIED, NULL, NULL); +} +EXPORT_SYMBOL_GPL(rt2x00mac_remove_interface); + +int rt2x00mac_config(struct ieee80211_hw *hw, u32 changed) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + + /* + * mac80211 might be calling this function while we are trying + * to remove the device or perhaps suspending it. + */ + if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + return 0; + + /* + * Some configuration parameters (e.g. channel and antenna values) can + * only be set when the radio is enabled, but do require the RX to + * be off. During this period we should keep link tuning enabled, + * if for any reason the link tuner must be reset, this will be + * handled by rt2x00lib_config(). + */ + rt2x00queue_stop_queue(rt2x00dev->rx); + + /* + * When we've just turned on the radio, we want to reprogram + * everything to ensure a consistent state + */ + rt2x00lib_config(rt2x00dev, conf, changed); + + /* + * After the radio has been enabled we need to configure + * the antenna to the default settings. rt2x00lib_config_antenna() + * should determine if any action should be taken based on + * checking if diversity has been enabled or no antenna changes + * have been made since the last configuration change. + */ + rt2x00lib_config_antenna(rt2x00dev, rt2x00dev->default_ant); + + /* Turn RX back on */ + rt2x00queue_start_queue(rt2x00dev->rx); + + return 0; +} +EXPORT_SYMBOL_GPL(rt2x00mac_config); + +void rt2x00mac_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + + /* + * Mask off any flags we are going to ignore + * from the total_flags field. + */ + *total_flags &= + FIF_ALLMULTI | + FIF_FCSFAIL | + FIF_PLCPFAIL | + FIF_CONTROL | + FIF_PSPOLL | + FIF_OTHER_BSS | + FIF_PROMISC_IN_BSS; + + /* + * Apply some rules to the filters: + * - Some filters imply different filters to be set. + * - Some things we can't filter out at all. + * - Multicast filter seems to kill broadcast traffic so never use it. + */ + *total_flags |= FIF_ALLMULTI; + if (*total_flags & FIF_OTHER_BSS || + *total_flags & FIF_PROMISC_IN_BSS) + *total_flags |= FIF_PROMISC_IN_BSS | FIF_OTHER_BSS; + + /* + * If the device has a single filter for all control frames, + * FIF_CONTROL and FIF_PSPOLL flags imply each other. + * And if the device has more than one filter for control frames + * of different types, but has no a separate filter for PS Poll frames, + * FIF_CONTROL flag implies FIF_PSPOLL. + */ + if (!test_bit(CAPABILITY_CONTROL_FILTERS, &rt2x00dev->cap_flags)) { + if (*total_flags & FIF_CONTROL || *total_flags & FIF_PSPOLL) + *total_flags |= FIF_CONTROL | FIF_PSPOLL; + } + if (!test_bit(CAPABILITY_CONTROL_FILTER_PSPOLL, &rt2x00dev->cap_flags)) { + if (*total_flags & FIF_CONTROL) + *total_flags |= FIF_PSPOLL; + } + + /* + * Check if there is any work left for us. + */ + if (rt2x00dev->packet_filter == *total_flags) + return; + rt2x00dev->packet_filter = *total_flags; + + rt2x00dev->ops->lib->config_filter(rt2x00dev, *total_flags); +} +EXPORT_SYMBOL_GPL(rt2x00mac_configure_filter); + +static void rt2x00mac_set_tim_iter(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct rt2x00_intf *intf = vif_to_intf(vif); + + if (vif->type != NL80211_IFTYPE_AP && + vif->type != NL80211_IFTYPE_ADHOC && + vif->type != NL80211_IFTYPE_MESH_POINT && + vif->type != NL80211_IFTYPE_WDS) + return; + + set_bit(DELAYED_UPDATE_BEACON, &intf->delayed_flags); +} + +int rt2x00mac_set_tim(struct ieee80211_hw *hw, struct ieee80211_sta *sta, + bool set) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + + if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + return 0; + + ieee80211_iterate_active_interfaces_atomic( + rt2x00dev->hw, IEEE80211_IFACE_ITER_RESUME_ALL, + rt2x00mac_set_tim_iter, rt2x00dev); + + /* queue work to upodate the beacon template */ + ieee80211_queue_work(rt2x00dev->hw, &rt2x00dev->intf_work); + return 0; +} +EXPORT_SYMBOL_GPL(rt2x00mac_set_tim); + +#ifdef CPTCFG_RT2X00_LIB_CRYPTO +static void memcpy_tkip(struct rt2x00lib_crypto *crypto, u8 *key, u8 key_len) +{ + if (key_len > NL80211_TKIP_DATA_OFFSET_ENCR_KEY) + memcpy(crypto->key, + &key[NL80211_TKIP_DATA_OFFSET_ENCR_KEY], + sizeof(crypto->key)); + + if (key_len > NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY) + memcpy(crypto->tx_mic, + &key[NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY], + sizeof(crypto->tx_mic)); + + if (key_len > NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY) + memcpy(crypto->rx_mic, + &key[NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY], + sizeof(crypto->rx_mic)); +} + +int rt2x00mac_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + int (*set_key) (struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_crypto *crypto, + struct ieee80211_key_conf *key); + struct rt2x00lib_crypto crypto; + static const u8 bcast_addr[ETH_ALEN] = + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }; + struct rt2x00_sta *sta_priv = NULL; + + if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + return 0; + + if (!test_bit(CAPABILITY_HW_CRYPTO, &rt2x00dev->cap_flags)) + return -EOPNOTSUPP; + + /* + * To support IBSS RSN, don't program group keys in IBSS, the + * hardware will then not attempt to decrypt the frames. + */ + if (vif->type == NL80211_IFTYPE_ADHOC && + !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) + return -EOPNOTSUPP; + + if (key->keylen > 32) + return -ENOSPC; + + memset(&crypto, 0, sizeof(crypto)); + + crypto.bssidx = rt2x00lib_get_bssidx(rt2x00dev, vif); + crypto.cipher = rt2x00crypto_key_to_cipher(key); + if (crypto.cipher == CIPHER_NONE) + return -EOPNOTSUPP; + + crypto.cmd = cmd; + + if (sta) { + crypto.address = sta->addr; + sta_priv = sta_to_rt2x00_sta(sta); + crypto.wcid = sta_priv->wcid; + } else + crypto.address = bcast_addr; + + if (crypto.cipher == CIPHER_TKIP) + memcpy_tkip(&crypto, &key->key[0], key->keylen); + else + memcpy(crypto.key, &key->key[0], key->keylen); + /* + * Each BSS has a maximum of 4 shared keys. + * Shared key index values: + * 0) BSS0 key0 + * 1) BSS0 key1 + * ... + * 4) BSS1 key0 + * ... + * 8) BSS2 key0 + * ... + * Both pairwise as shared key indeces are determined by + * driver. This is required because the hardware requires + * keys to be assigned in correct order (When key 1 is + * provided but key 0 is not, then the key is not found + * by the hardware during RX). + */ + if (cmd == SET_KEY) + key->hw_key_idx = 0; + + if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) + set_key = rt2x00dev->ops->lib->config_pairwise_key; + else + set_key = rt2x00dev->ops->lib->config_shared_key; + + if (!set_key) + return -EOPNOTSUPP; + + return set_key(rt2x00dev, &crypto, key); +} +EXPORT_SYMBOL_GPL(rt2x00mac_set_key); +#endif /* CPTCFG_RT2X00_LIB_CRYPTO */ + +int rt2x00mac_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct rt2x00_sta *sta_priv = sta_to_rt2x00_sta(sta); + + /* + * If there's no space left in the device table store + * -1 as wcid but tell mac80211 everything went ok. + */ + if (rt2x00dev->ops->lib->sta_add(rt2x00dev, vif, sta)) + sta_priv->wcid = -1; + + return 0; +} +EXPORT_SYMBOL_GPL(rt2x00mac_sta_add); + +int rt2x00mac_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct rt2x00_sta *sta_priv = sta_to_rt2x00_sta(sta); + + /* + * If we never sent the STA to the device no need to clean it up. + */ + if (sta_priv->wcid < 0) + return 0; + + return rt2x00dev->ops->lib->sta_remove(rt2x00dev, sta_priv->wcid); +} +EXPORT_SYMBOL_GPL(rt2x00mac_sta_remove); + +void rt2x00mac_sw_scan_start(struct ieee80211_hw *hw) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + set_bit(DEVICE_STATE_SCANNING, &rt2x00dev->flags); + rt2x00link_stop_tuner(rt2x00dev); +} +EXPORT_SYMBOL_GPL(rt2x00mac_sw_scan_start); + +void rt2x00mac_sw_scan_complete(struct ieee80211_hw *hw) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + clear_bit(DEVICE_STATE_SCANNING, &rt2x00dev->flags); + rt2x00link_start_tuner(rt2x00dev); +} +EXPORT_SYMBOL_GPL(rt2x00mac_sw_scan_complete); + +int rt2x00mac_get_stats(struct ieee80211_hw *hw, + struct ieee80211_low_level_stats *stats) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + + /* + * The dot11ACKFailureCount, dot11RTSFailureCount and + * dot11RTSSuccessCount are updated in interrupt time. + * dot11FCSErrorCount is updated in the link tuner. + */ + memcpy(stats, &rt2x00dev->low_level_stats, sizeof(*stats)); + + return 0; +} +EXPORT_SYMBOL_GPL(rt2x00mac_get_stats); + +void rt2x00mac_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changes) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct rt2x00_intf *intf = vif_to_intf(vif); + + /* + * mac80211 might be calling this function while we are trying + * to remove the device or perhaps suspending it. + */ + if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + return; + + /* + * Update the BSSID. + */ + if (changes & BSS_CHANGED_BSSID) + rt2x00lib_config_intf(rt2x00dev, intf, vif->type, NULL, + bss_conf->bssid); + + /* + * Update the beacon. This is only required on USB devices. PCI + * devices fetch beacons periodically. + */ + if (changes & BSS_CHANGED_BEACON && rt2x00_is_usb(rt2x00dev)) + rt2x00queue_update_beacon(rt2x00dev, vif); + + /* + * Start/stop beaconing. + */ + if (changes & BSS_CHANGED_BEACON_ENABLED) { + if (!bss_conf->enable_beacon && intf->enable_beacon) { + rt2x00queue_clear_beacon(rt2x00dev, vif); + rt2x00dev->intf_beaconing--; + intf->enable_beacon = false; + + if (rt2x00dev->intf_beaconing == 0) { + /* + * Last beaconing interface disabled + * -> stop beacon queue. + */ + mutex_lock(&intf->beacon_skb_mutex); + rt2x00queue_stop_queue(rt2x00dev->bcn); + mutex_unlock(&intf->beacon_skb_mutex); + } + + + } else if (bss_conf->enable_beacon && !intf->enable_beacon) { + rt2x00dev->intf_beaconing++; + intf->enable_beacon = true; + + if (rt2x00dev->intf_beaconing == 1) { + /* + * First beaconing interface enabled + * -> start beacon queue. + */ + mutex_lock(&intf->beacon_skb_mutex); + rt2x00queue_start_queue(rt2x00dev->bcn); + mutex_unlock(&intf->beacon_skb_mutex); + } + } + } + + /* + * When the association status has changed we must reset the link + * tuner counter. This is because some drivers determine if they + * should perform link tuning based on the number of seconds + * while associated or not associated. + */ + if (changes & BSS_CHANGED_ASSOC) { + rt2x00dev->link.count = 0; + + if (bss_conf->assoc) + rt2x00dev->intf_associated++; + else + rt2x00dev->intf_associated--; + + rt2x00leds_led_assoc(rt2x00dev, !!rt2x00dev->intf_associated); + + clear_bit(CONFIG_QOS_DISABLED, &rt2x00dev->flags); + } + + /* + * Check for access point which do not support 802.11e . We have to + * generate data frames sequence number in S/W for such AP, because + * of H/W bug. + */ + if (changes & BSS_CHANGED_QOS && !bss_conf->qos) + set_bit(CONFIG_QOS_DISABLED, &rt2x00dev->flags); + + /* + * When the erp information has changed, we should perform + * additional configuration steps. For all other changes we are done. + */ + if (changes & (BSS_CHANGED_ERP_CTS_PROT | BSS_CHANGED_ERP_PREAMBLE | + BSS_CHANGED_ERP_SLOT | BSS_CHANGED_BASIC_RATES | + BSS_CHANGED_BEACON_INT | BSS_CHANGED_HT)) + rt2x00lib_config_erp(rt2x00dev, intf, bss_conf, changes); +} +EXPORT_SYMBOL_GPL(rt2x00mac_bss_info_changed); + +int rt2x00mac_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue_idx, + const struct ieee80211_tx_queue_params *params) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct data_queue *queue; + + queue = rt2x00queue_get_tx_queue(rt2x00dev, queue_idx); + if (unlikely(!queue)) + return -EINVAL; + + /* + * The passed variables are stored as real value ((2^n)-1). + * Ralink registers require to know the bit number 'n'. + */ + if (params->cw_min > 0) + queue->cw_min = fls(params->cw_min); + else + queue->cw_min = 5; /* cw_min: 2^5 = 32. */ + + if (params->cw_max > 0) + queue->cw_max = fls(params->cw_max); + else + queue->cw_max = 10; /* cw_min: 2^10 = 1024. */ + + queue->aifs = params->aifs; + queue->txop = params->txop; + + rt2x00_dbg(rt2x00dev, + "Configured TX queue %d - CWmin: %d, CWmax: %d, Aifs: %d, TXop: %d\n", + queue_idx, queue->cw_min, queue->cw_max, queue->aifs, + queue->txop); + + return 0; +} +EXPORT_SYMBOL_GPL(rt2x00mac_conf_tx); + +void rt2x00mac_rfkill_poll(struct ieee80211_hw *hw) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + bool active = !!rt2x00dev->ops->lib->rfkill_poll(rt2x00dev); + + wiphy_rfkill_set_hw_state(hw->wiphy, !active); +} +EXPORT_SYMBOL_GPL(rt2x00mac_rfkill_poll); + +void rt2x00mac_flush(struct ieee80211_hw *hw, u32 queues, bool drop) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct data_queue *queue; + + tx_queue_for_each(rt2x00dev, queue) + rt2x00queue_flush_queue(queue, drop); +} +EXPORT_SYMBOL_GPL(rt2x00mac_flush); + +int rt2x00mac_set_antenna(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct link_ant *ant = &rt2x00dev->link.ant; + struct antenna_setup *def = &rt2x00dev->default_ant; + struct antenna_setup setup; + + // The antenna value is not supposed to be 0, + // or exceed the maximum number of antenna's. + if (!tx_ant || (tx_ant & ~3) || !rx_ant || (rx_ant & ~3)) + return -EINVAL; + + // When the client tried to configure the antenna to or from + // diversity mode, we must reset the default antenna as well + // as that controls the diversity switch. + if (ant->flags & ANTENNA_TX_DIVERSITY && tx_ant != 3) + ant->flags &= ~ANTENNA_TX_DIVERSITY; + if (ant->flags & ANTENNA_RX_DIVERSITY && rx_ant != 3) + ant->flags &= ~ANTENNA_RX_DIVERSITY; + + // If diversity is being enabled, check if we need hardware + // or software diversity. In the latter case, reset the value, + // and make sure we update the antenna flags to have the + // link tuner pick up the diversity tuning. + if (tx_ant == 3 && def->tx == ANTENNA_SW_DIVERSITY) { + tx_ant = ANTENNA_SW_DIVERSITY; + ant->flags |= ANTENNA_TX_DIVERSITY; + } + + if (rx_ant == 3 && def->rx == ANTENNA_SW_DIVERSITY) { + rx_ant = ANTENNA_SW_DIVERSITY; + ant->flags |= ANTENNA_RX_DIVERSITY; + } + + setup.tx = tx_ant; + setup.rx = rx_ant; + + rt2x00lib_config_antenna(rt2x00dev, setup); + + return 0; +} +EXPORT_SYMBOL_GPL(rt2x00mac_set_antenna); + +int rt2x00mac_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct link_ant *ant = &rt2x00dev->link.ant; + struct antenna_setup *active = &rt2x00dev->link.ant.active; + + // When software diversity is active, we must report this to the + // client and not the current active antenna state. + if (ant->flags & ANTENNA_TX_DIVERSITY) + *tx_ant = ANTENNA_HW_DIVERSITY; + else + *tx_ant = active->tx; + + if (ant->flags & ANTENNA_RX_DIVERSITY) + *rx_ant = ANTENNA_HW_DIVERSITY; + else + *rx_ant = active->rx; + + return 0; +} +EXPORT_SYMBOL_GPL(rt2x00mac_get_antenna); + +void rt2x00mac_get_ringparam(struct ieee80211_hw *hw, + u32 *tx, u32 *tx_max, u32 *rx, u32 *rx_max) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct data_queue *queue; + + tx_queue_for_each(rt2x00dev, queue) { + *tx += queue->length; + *tx_max += queue->limit; + } + + *rx = rt2x00dev->rx->length; + *rx_max = rt2x00dev->rx->limit; +} +EXPORT_SYMBOL_GPL(rt2x00mac_get_ringparam); + +bool rt2x00mac_tx_frames_pending(struct ieee80211_hw *hw) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct data_queue *queue; + + tx_queue_for_each(rt2x00dev, queue) { + if (!rt2x00queue_empty(queue)) + return true; + } + + return false; +} +EXPORT_SYMBOL_GPL(rt2x00mac_tx_frames_pending); diff --git a/drivers/net/wireless/rt2x00/rt2x00mmio.c b/drivers/net/wireless/rt2x00/rt2x00mmio.c new file mode 100644 index 0000000..64b06c6 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00mmio.c @@ -0,0 +1,216 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00mmio + Abstract: rt2x00 generic mmio device routines. + */ + +#include +#include +#include +#include + +#include "rt2x00.h" +#include "rt2x00mmio.h" + +/* + * Register access. + */ +int rt2x00mmio_regbusy_read(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + const struct rt2x00_field32 field, + u32 *reg) +{ + unsigned int i; + + if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + return 0; + + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2x00mmio_register_read(rt2x00dev, offset, reg); + if (!rt2x00_get_field32(*reg, field)) + return 1; + udelay(REGISTER_BUSY_DELAY); + } + + printk_once(KERN_ERR "%s() Indirect register access failed: " + "offset=0x%.08x, value=0x%.08x\n", __func__, offset, *reg); + *reg = ~0; + + return 0; +} +EXPORT_SYMBOL_GPL(rt2x00mmio_regbusy_read); + +bool rt2x00mmio_rxdone(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue = rt2x00dev->rx; + struct queue_entry *entry; + struct queue_entry_priv_mmio *entry_priv; + struct skb_frame_desc *skbdesc; + int max_rx = 16; + + while (--max_rx) { + entry = rt2x00queue_get_entry(queue, Q_INDEX); + entry_priv = entry->priv_data; + + if (rt2x00dev->ops->lib->get_entry_state(entry)) + break; + + /* + * Fill in desc fields of the skb descriptor + */ + skbdesc = get_skb_frame_desc(entry->skb); + skbdesc->desc = entry_priv->desc; + skbdesc->desc_len = entry->queue->desc_size; + + /* + * DMA is already done, notify rt2x00lib that + * it finished successfully. + */ + rt2x00lib_dmastart(entry); + rt2x00lib_dmadone(entry); + + /* + * Send the frame to rt2x00lib for further processing. + */ + rt2x00lib_rxdone(entry, GFP_ATOMIC); + } + + return !max_rx; +} +EXPORT_SYMBOL_GPL(rt2x00mmio_rxdone); + +void rt2x00mmio_flush_queue(struct data_queue *queue, bool drop) +{ + unsigned int i; + + for (i = 0; !rt2x00queue_empty(queue) && i < 10; i++) + msleep(10); +} +EXPORT_SYMBOL_GPL(rt2x00mmio_flush_queue); + +/* + * Device initialization handlers. + */ +static int rt2x00mmio_alloc_queue_dma(struct rt2x00_dev *rt2x00dev, + struct data_queue *queue) +{ + struct queue_entry_priv_mmio *entry_priv; + void *addr; + dma_addr_t dma; + unsigned int i; + + /* + * Allocate DMA memory for descriptor and buffer. + */ + addr = dma_alloc_coherent(rt2x00dev->dev, + queue->limit * queue->desc_size, + &dma, GFP_KERNEL); + if (!addr) + return -ENOMEM; + + memset(addr, 0, queue->limit * queue->desc_size); + + /* + * Initialize all queue entries to contain valid addresses. + */ + for (i = 0; i < queue->limit; i++) { + entry_priv = queue->entries[i].priv_data; + entry_priv->desc = addr + i * queue->desc_size; + entry_priv->desc_dma = dma + i * queue->desc_size; + } + + return 0; +} + +static void rt2x00mmio_free_queue_dma(struct rt2x00_dev *rt2x00dev, + struct data_queue *queue) +{ + struct queue_entry_priv_mmio *entry_priv = + queue->entries[0].priv_data; + + if (entry_priv->desc) + dma_free_coherent(rt2x00dev->dev, + queue->limit * queue->desc_size, + entry_priv->desc, entry_priv->desc_dma); + entry_priv->desc = NULL; +} + +int rt2x00mmio_initialize(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + int status; + + /* + * Allocate DMA + */ + queue_for_each(rt2x00dev, queue) { + status = rt2x00mmio_alloc_queue_dma(rt2x00dev, queue); + if (status) + goto exit; + } + + /* + * Register interrupt handler. + */ + status = request_irq(rt2x00dev->irq, + rt2x00dev->ops->lib->irq_handler, + IRQF_SHARED, rt2x00dev->name, rt2x00dev); + if (status) { + rt2x00_err(rt2x00dev, "IRQ %d allocation failed (error %d)\n", + rt2x00dev->irq, status); + goto exit; + } + + return 0; + +exit: + queue_for_each(rt2x00dev, queue) + rt2x00mmio_free_queue_dma(rt2x00dev, queue); + + return status; +} +EXPORT_SYMBOL_GPL(rt2x00mmio_initialize); + +void rt2x00mmio_uninitialize(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + + /* + * Free irq line. + */ + free_irq(rt2x00dev->irq, rt2x00dev); + + /* + * Free DMA + */ + queue_for_each(rt2x00dev, queue) + rt2x00mmio_free_queue_dma(rt2x00dev, queue); +} +EXPORT_SYMBOL_GPL(rt2x00mmio_uninitialize); + +/* + * rt2x00mmio module information. + */ +MODULE_AUTHOR(DRV_PROJECT); +MODULE_VERSION(DRV_VERSION); +MODULE_DESCRIPTION("rt2x00 mmio library"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/rt2x00/rt2x00mmio.h b/drivers/net/wireless/rt2x00/rt2x00mmio.h new file mode 100644 index 0000000..cda3dbc --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00mmio.h @@ -0,0 +1,119 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00mmio + Abstract: Data structures for the rt2x00mmio module. + */ + +#ifndef RT2X00MMIO_H +#define RT2X00MMIO_H + +#include + +/* + * Register access. + */ +static inline void rt2x00mmio_register_read(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + u32 *value) +{ + *value = readl(rt2x00dev->csr.base + offset); +} + +static inline void rt2x00mmio_register_multiread(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + void *value, const u32 length) +{ + memcpy_fromio(value, rt2x00dev->csr.base + offset, length); +} + +static inline void rt2x00mmio_register_write(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + u32 value) +{ + writel(value, rt2x00dev->csr.base + offset); +} + +static inline void rt2x00mmio_register_multiwrite(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + const void *value, + const u32 length) +{ + __iowrite32_copy(rt2x00dev->csr.base + offset, value, length >> 2); +} + +/** + * rt2x00mmio_regbusy_read - Read from register with busy check + * @rt2x00dev: Device pointer, see &struct rt2x00_dev. + * @offset: Register offset + * @field: Field to check if register is busy + * @reg: Pointer to where register contents should be stored + * + * This function will read the given register, and checks if the + * register is busy. If it is, it will sleep for a couple of + * microseconds before reading the register again. If the register + * is not read after a certain timeout, this function will return + * FALSE. + */ +int rt2x00mmio_regbusy_read(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + const struct rt2x00_field32 field, + u32 *reg); + +/** + * struct queue_entry_priv_mmio: Per entry PCI specific information + * + * @desc: Pointer to device descriptor + * @desc_dma: DMA pointer to &desc. + * @data: Pointer to device's entry memory. + * @data_dma: DMA pointer to &data. + */ +struct queue_entry_priv_mmio { + __le32 *desc; + dma_addr_t desc_dma; +}; + +/** + * rt2x00mmio_rxdone - Handle RX done events + * @rt2x00dev: Device pointer, see &struct rt2x00_dev. + * + * Returns true if there are still rx frames pending and false if all + * pending rx frames were processed. + */ +bool rt2x00mmio_rxdone(struct rt2x00_dev *rt2x00dev); + +/** + * rt2x00mmio_flush_queue - Flush data queue + * @queue: Data queue to stop + * @drop: True to drop all pending frames. + * + * This will wait for a maximum of 100ms, waiting for the queues + * to become empty. + */ +void rt2x00mmio_flush_queue(struct data_queue *queue, bool drop); + +/* + * Device initialization handlers. + */ +int rt2x00mmio_initialize(struct rt2x00_dev *rt2x00dev); +void rt2x00mmio_uninitialize(struct rt2x00_dev *rt2x00dev); + +#endif /* RT2X00MMIO_H */ diff --git a/drivers/net/wireless/rt2x00/rt2x00pci.c b/drivers/net/wireless/rt2x00/rt2x00pci.c new file mode 100644 index 0000000..dc49e52 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00pci.c @@ -0,0 +1,226 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00pci + Abstract: rt2x00 generic pci device routines. + */ + +#include +#include +#include +#include +#include + +#include "rt2x00.h" +#include "rt2x00pci.h" + +/* + * PCI driver handlers. + */ +static void rt2x00pci_free_reg(struct rt2x00_dev *rt2x00dev) +{ + kfree(rt2x00dev->rf); + rt2x00dev->rf = NULL; + + kfree(rt2x00dev->eeprom); + rt2x00dev->eeprom = NULL; + + if (rt2x00dev->csr.base) { + iounmap(rt2x00dev->csr.base); + rt2x00dev->csr.base = NULL; + } +} + +static int rt2x00pci_alloc_reg(struct rt2x00_dev *rt2x00dev) +{ + struct pci_dev *pci_dev = to_pci_dev(rt2x00dev->dev); + + rt2x00dev->csr.base = pci_ioremap_bar(pci_dev, 0); + if (!rt2x00dev->csr.base) + goto exit; + + rt2x00dev->eeprom = kzalloc(rt2x00dev->ops->eeprom_size, GFP_KERNEL); + if (!rt2x00dev->eeprom) + goto exit; + + rt2x00dev->rf = kzalloc(rt2x00dev->ops->rf_size, GFP_KERNEL); + if (!rt2x00dev->rf) + goto exit; + + return 0; + +exit: + rt2x00_probe_err("Failed to allocate registers\n"); + + rt2x00pci_free_reg(rt2x00dev); + + return -ENOMEM; +} + +int rt2x00pci_probe(struct pci_dev *pci_dev, const struct rt2x00_ops *ops) +{ + struct ieee80211_hw *hw; + struct rt2x00_dev *rt2x00dev; + int retval; + u16 chip; + + retval = pci_enable_device(pci_dev); + if (retval) { + rt2x00_probe_err("Enable device failed\n"); + return retval; + } + + retval = pci_request_regions(pci_dev, pci_name(pci_dev)); + if (retval) { + rt2x00_probe_err("PCI request regions failed\n"); + goto exit_disable_device; + } + + pci_set_master(pci_dev); + + if (pci_set_mwi(pci_dev)) + rt2x00_probe_err("MWI not available\n"); + + if (dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(32))) { + rt2x00_probe_err("PCI DMA not supported\n"); + retval = -EIO; + goto exit_release_regions; + } + + hw = ieee80211_alloc_hw(sizeof(struct rt2x00_dev), ops->hw); + if (!hw) { + rt2x00_probe_err("Failed to allocate hardware\n"); + retval = -ENOMEM; + goto exit_release_regions; + } + + pci_set_drvdata(pci_dev, hw); + + rt2x00dev = hw->priv; + rt2x00dev->dev = &pci_dev->dev; + rt2x00dev->ops = ops; + rt2x00dev->hw = hw; + rt2x00dev->irq = pci_dev->irq; + rt2x00dev->name = pci_name(pci_dev); + + if (pci_is_pcie(pci_dev)) + rt2x00_set_chip_intf(rt2x00dev, RT2X00_CHIP_INTF_PCIE); + else + rt2x00_set_chip_intf(rt2x00dev, RT2X00_CHIP_INTF_PCI); + + retval = rt2x00pci_alloc_reg(rt2x00dev); + if (retval) + goto exit_free_device; + + /* + * Because rt3290 chip use different efuse offset to read efuse data. + * So before read efuse it need to indicate it is the + * rt3290 or not. + */ + pci_read_config_word(pci_dev, PCI_DEVICE_ID, &chip); + rt2x00dev->chip.rt = chip; + + retval = rt2x00lib_probe_dev(rt2x00dev); + if (retval) + goto exit_free_reg; + + return 0; + +exit_free_reg: + rt2x00pci_free_reg(rt2x00dev); + +exit_free_device: + ieee80211_free_hw(hw); + +exit_release_regions: + pci_release_regions(pci_dev); + +exit_disable_device: + pci_disable_device(pci_dev); + + pci_set_drvdata(pci_dev, NULL); + + return retval; +} +EXPORT_SYMBOL_GPL(rt2x00pci_probe); + +void rt2x00pci_remove(struct pci_dev *pci_dev) +{ + struct ieee80211_hw *hw = pci_get_drvdata(pci_dev); + struct rt2x00_dev *rt2x00dev = hw->priv; + + /* + * Free all allocated data. + */ + rt2x00lib_remove_dev(rt2x00dev); + rt2x00pci_free_reg(rt2x00dev); + ieee80211_free_hw(hw); + + /* + * Free the PCI device data. + */ + pci_set_drvdata(pci_dev, NULL); + pci_disable_device(pci_dev); + pci_release_regions(pci_dev); +} +EXPORT_SYMBOL_GPL(rt2x00pci_remove); + +#ifdef CONFIG_PM +int rt2x00pci_suspend(struct pci_dev *pci_dev, pm_message_t state) +{ + struct ieee80211_hw *hw = pci_get_drvdata(pci_dev); + struct rt2x00_dev *rt2x00dev = hw->priv; + int retval; + + retval = rt2x00lib_suspend(rt2x00dev, state); + if (retval) + return retval; + + pci_save_state(pci_dev); + pci_disable_device(pci_dev); + return pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state)); +} +EXPORT_SYMBOL_GPL(rt2x00pci_suspend); + +int rt2x00pci_resume(struct pci_dev *pci_dev) +{ + struct ieee80211_hw *hw = pci_get_drvdata(pci_dev); + struct rt2x00_dev *rt2x00dev = hw->priv; + + if (pci_set_power_state(pci_dev, PCI_D0) || + pci_enable_device(pci_dev)) { + rt2x00_err(rt2x00dev, "Failed to resume device\n"); + return -EIO; + } + + pci_restore_state(pci_dev); + return rt2x00lib_resume(rt2x00dev); +} +EXPORT_SYMBOL_GPL(rt2x00pci_resume); +#endif /* CONFIG_PM */ + +/* + * rt2x00pci module information. + */ +MODULE_AUTHOR(DRV_PROJECT); +MODULE_VERSION(DRV_VERSION); +MODULE_DESCRIPTION("rt2x00 pci library"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/rt2x00/rt2x00pci.h b/drivers/net/wireless/rt2x00/rt2x00pci.h new file mode 100644 index 0000000..60d90b2 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00pci.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00pci + Abstract: Data structures for the rt2x00pci module. + */ + +#ifndef RT2X00PCI_H +#define RT2X00PCI_H + +#include +#include + +/* + * This variable should be used with the + * pci_driver structure initialization. + */ +#define PCI_DEVICE_DATA(__ops) .driver_data = (kernel_ulong_t)(__ops) + +/* + * PCI driver handlers. + */ +int rt2x00pci_probe(struct pci_dev *pci_dev, const struct rt2x00_ops *ops); +void rt2x00pci_remove(struct pci_dev *pci_dev); +#ifdef CONFIG_PM +int rt2x00pci_suspend(struct pci_dev *pci_dev, pm_message_t state); +int rt2x00pci_resume(struct pci_dev *pci_dev); +#else +#define rt2x00pci_suspend NULL +#define rt2x00pci_resume NULL +#endif /* CONFIG_PM */ + +#endif /* RT2X00PCI_H */ diff --git a/drivers/net/wireless/rt2x00/rt2x00queue.c b/drivers/net/wireless/rt2x00/rt2x00queue.c new file mode 100644 index 0000000..d955741 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00queue.c @@ -0,0 +1,1364 @@ +/* + Copyright (C) 2010 Willow Garage + Copyright (C) 2004 - 2010 Ivo van Doorn + Copyright (C) 2004 - 2009 Gertjan van Wingerde + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00lib + Abstract: rt2x00 queue specific routines. + */ + +#include +#include +#include +#include + +#include "rt2x00.h" +#include "rt2x00lib.h" + +struct sk_buff *rt2x00queue_alloc_rxskb(struct queue_entry *entry, gfp_t gfp) +{ + struct data_queue *queue = entry->queue; + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + struct sk_buff *skb; + struct skb_frame_desc *skbdesc; + unsigned int frame_size; + unsigned int head_size = 0; + unsigned int tail_size = 0; + + /* + * The frame size includes descriptor size, because the + * hardware directly receive the frame into the skbuffer. + */ + frame_size = queue->data_size + queue->desc_size + queue->winfo_size; + + /* + * The payload should be aligned to a 4-byte boundary, + * this means we need at least 3 bytes for moving the frame + * into the correct offset. + */ + head_size = 4; + + /* + * For IV/EIV/ICV assembly we must make sure there is + * at least 8 bytes bytes available in headroom for IV/EIV + * and 8 bytes for ICV data as tailroon. + */ + if (test_bit(CAPABILITY_HW_CRYPTO, &rt2x00dev->cap_flags)) { + head_size += 8; + tail_size += 8; + } + + /* + * Allocate skbuffer. + */ + skb = __dev_alloc_skb(frame_size + head_size + tail_size, gfp); + if (!skb) + return NULL; + + /* + * Make sure we not have a frame with the requested bytes + * available in the head and tail. + */ + skb_reserve(skb, head_size); + skb_put(skb, frame_size); + + /* + * Populate skbdesc. + */ + skbdesc = get_skb_frame_desc(skb); + memset(skbdesc, 0, sizeof(*skbdesc)); + skbdesc->entry = entry; + + if (test_bit(REQUIRE_DMA, &rt2x00dev->cap_flags)) { + dma_addr_t skb_dma; + + skb_dma = dma_map_single(rt2x00dev->dev, skb->data, skb->len, + DMA_FROM_DEVICE); + if (unlikely(dma_mapping_error(rt2x00dev->dev, skb_dma))) { + dev_kfree_skb_any(skb); + return NULL; + } + + skbdesc->skb_dma = skb_dma; + skbdesc->flags |= SKBDESC_DMA_MAPPED_RX; + } + + return skb; +} + +int rt2x00queue_map_txskb(struct queue_entry *entry) +{ + struct device *dev = entry->queue->rt2x00dev->dev; + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + + skbdesc->skb_dma = + dma_map_single(dev, entry->skb->data, entry->skb->len, DMA_TO_DEVICE); + + if (unlikely(dma_mapping_error(dev, skbdesc->skb_dma))) + return -ENOMEM; + + skbdesc->flags |= SKBDESC_DMA_MAPPED_TX; + return 0; +} +EXPORT_SYMBOL_GPL(rt2x00queue_map_txskb); + +void rt2x00queue_unmap_skb(struct queue_entry *entry) +{ + struct device *dev = entry->queue->rt2x00dev->dev; + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + + if (skbdesc->flags & SKBDESC_DMA_MAPPED_RX) { + dma_unmap_single(dev, skbdesc->skb_dma, entry->skb->len, + DMA_FROM_DEVICE); + skbdesc->flags &= ~SKBDESC_DMA_MAPPED_RX; + } else if (skbdesc->flags & SKBDESC_DMA_MAPPED_TX) { + dma_unmap_single(dev, skbdesc->skb_dma, entry->skb->len, + DMA_TO_DEVICE); + skbdesc->flags &= ~SKBDESC_DMA_MAPPED_TX; + } +} +EXPORT_SYMBOL_GPL(rt2x00queue_unmap_skb); + +void rt2x00queue_free_skb(struct queue_entry *entry) +{ + if (!entry->skb) + return; + + rt2x00queue_unmap_skb(entry); + dev_kfree_skb_any(entry->skb); + entry->skb = NULL; +} + +void rt2x00queue_align_frame(struct sk_buff *skb) +{ + unsigned int frame_length = skb->len; + unsigned int align = ALIGN_SIZE(skb, 0); + + if (!align) + return; + + skb_push(skb, align); + memmove(skb->data, skb->data + align, frame_length); + skb_trim(skb, frame_length); +} + +void rt2x00queue_insert_l2pad(struct sk_buff *skb, unsigned int header_length) +{ + unsigned int payload_length = skb->len - header_length; + unsigned int header_align = ALIGN_SIZE(skb, 0); + unsigned int payload_align = ALIGN_SIZE(skb, header_length); + unsigned int l2pad = payload_length ? L2PAD_SIZE(header_length) : 0; + + /* + * Adjust the header alignment if the payload needs to be moved more + * than the header. + */ + if (payload_align > header_align) + header_align += 4; + + /* There is nothing to do if no alignment is needed */ + if (!header_align) + return; + + /* Reserve the amount of space needed in front of the frame */ + skb_push(skb, header_align); + + /* + * Move the header. + */ + memmove(skb->data, skb->data + header_align, header_length); + + /* Move the payload, if present and if required */ + if (payload_length && payload_align) + memmove(skb->data + header_length + l2pad, + skb->data + header_length + l2pad + payload_align, + payload_length); + + /* Trim the skb to the correct size */ + skb_trim(skb, header_length + l2pad + payload_length); +} + +void rt2x00queue_remove_l2pad(struct sk_buff *skb, unsigned int header_length) +{ + /* + * L2 padding is only present if the skb contains more than just the + * IEEE 802.11 header. + */ + unsigned int l2pad = (skb->len > header_length) ? + L2PAD_SIZE(header_length) : 0; + + if (!l2pad) + return; + + memmove(skb->data + l2pad, skb->data, header_length); + skb_pull(skb, l2pad); +} + +static void rt2x00queue_create_tx_descriptor_seq(struct rt2x00_dev *rt2x00dev, + struct sk_buff *skb, + struct txentry_desc *txdesc) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct rt2x00_intf *intf = vif_to_intf(tx_info->control.vif); + u16 seqno; + + if (!(tx_info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ)) + return; + + __set_bit(ENTRY_TXD_GENERATE_SEQ, &txdesc->flags); + + if (!test_bit(REQUIRE_SW_SEQNO, &rt2x00dev->cap_flags)) { + /* + * rt2800 has a H/W (or F/W) bug, device incorrectly increase + * seqno on retransmited data (non-QOS) frames. To workaround + * the problem let's generate seqno in software if QOS is + * disabled. + */ + if (test_bit(CONFIG_QOS_DISABLED, &rt2x00dev->flags)) + __clear_bit(ENTRY_TXD_GENERATE_SEQ, &txdesc->flags); + else + /* H/W will generate sequence number */ + return; + } + + /* + * The hardware is not able to insert a sequence number. Assign a + * software generated one here. + * + * This is wrong because beacons are not getting sequence + * numbers assigned properly. + * + * A secondary problem exists for drivers that cannot toggle + * sequence counting per-frame, since those will override the + * sequence counter given by mac80211. + */ + if (test_bit(ENTRY_TXD_FIRST_FRAGMENT, &txdesc->flags)) + seqno = atomic_add_return(0x10, &intf->seqno); + else + seqno = atomic_read(&intf->seqno); + + hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); + hdr->seq_ctrl |= cpu_to_le16(seqno); +} + +static void rt2x00queue_create_tx_descriptor_plcp(struct rt2x00_dev *rt2x00dev, + struct sk_buff *skb, + struct txentry_desc *txdesc, + const struct rt2x00_rate *hwrate) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_tx_rate *txrate = &tx_info->control.rates[0]; + unsigned int data_length; + unsigned int duration; + unsigned int residual; + + /* + * Determine with what IFS priority this frame should be send. + * Set ifs to IFS_SIFS when the this is not the first fragment, + * or this fragment came after RTS/CTS. + */ + if (test_bit(ENTRY_TXD_FIRST_FRAGMENT, &txdesc->flags)) + txdesc->u.plcp.ifs = IFS_BACKOFF; + else + txdesc->u.plcp.ifs = IFS_SIFS; + + /* Data length + CRC + Crypto overhead (IV/EIV/ICV/MIC) */ + data_length = skb->len + 4; + data_length += rt2x00crypto_tx_overhead(rt2x00dev, skb); + + /* + * PLCP setup + * Length calculation depends on OFDM/CCK rate. + */ + txdesc->u.plcp.signal = hwrate->plcp; + txdesc->u.plcp.service = 0x04; + + if (hwrate->flags & DEV_RATE_OFDM) { + txdesc->u.plcp.length_high = (data_length >> 6) & 0x3f; + txdesc->u.plcp.length_low = data_length & 0x3f; + } else { + /* + * Convert length to microseconds. + */ + residual = GET_DURATION_RES(data_length, hwrate->bitrate); + duration = GET_DURATION(data_length, hwrate->bitrate); + + if (residual != 0) { + duration++; + + /* + * Check if we need to set the Length Extension + */ + if (hwrate->bitrate == 110 && residual <= 30) + txdesc->u.plcp.service |= 0x80; + } + + txdesc->u.plcp.length_high = (duration >> 8) & 0xff; + txdesc->u.plcp.length_low = duration & 0xff; + + /* + * When preamble is enabled we should set the + * preamble bit for the signal. + */ + if (txrate->flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE) + txdesc->u.plcp.signal |= 0x08; + } +} + +static void rt2x00queue_create_tx_descriptor_ht(struct rt2x00_dev *rt2x00dev, + struct sk_buff *skb, + struct txentry_desc *txdesc, + struct ieee80211_sta *sta, + const struct rt2x00_rate *hwrate) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_tx_rate *txrate = &tx_info->control.rates[0]; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct rt2x00_sta *sta_priv = NULL; + + if (sta) { + txdesc->u.ht.mpdu_density = + sta->ht_cap.ampdu_density; + + sta_priv = sta_to_rt2x00_sta(sta); + txdesc->u.ht.wcid = sta_priv->wcid; + } + + /* + * If IEEE80211_TX_RC_MCS is set txrate->idx just contains the + * mcs rate to be used + */ + if (txrate->flags & IEEE80211_TX_RC_MCS) { + txdesc->u.ht.mcs = txrate->idx; + + /* + * MIMO PS should be set to 1 for STA's using dynamic SM PS + * when using more then one tx stream (>MCS7). + */ + if (sta && txdesc->u.ht.mcs > 7 && + sta->smps_mode == IEEE80211_SMPS_DYNAMIC) + __set_bit(ENTRY_TXD_HT_MIMO_PS, &txdesc->flags); + } else { + txdesc->u.ht.mcs = rt2x00_get_rate_mcs(hwrate->mcs); + if (txrate->flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE) + txdesc->u.ht.mcs |= 0x08; + } + + if (test_bit(CONFIG_HT_DISABLED, &rt2x00dev->flags)) { + if (!(tx_info->flags & IEEE80211_TX_CTL_FIRST_FRAGMENT)) + txdesc->u.ht.txop = TXOP_SIFS; + else + txdesc->u.ht.txop = TXOP_BACKOFF; + + /* Left zero on all other settings. */ + return; + } + + txdesc->u.ht.ba_size = 7; /* FIXME: What value is needed? */ + + /* + * Only one STBC stream is supported for now. + */ + if (tx_info->flags & IEEE80211_TX_CTL_STBC) + txdesc->u.ht.stbc = 1; + + /* + * This frame is eligible for an AMPDU, however, don't aggregate + * frames that are intended to probe a specific tx rate. + */ + if (tx_info->flags & IEEE80211_TX_CTL_AMPDU && + !(tx_info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE)) + __set_bit(ENTRY_TXD_HT_AMPDU, &txdesc->flags); + + /* + * Set 40Mhz mode if necessary (for legacy rates this will + * duplicate the frame to both channels). + */ + if (txrate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH || + txrate->flags & IEEE80211_TX_RC_DUP_DATA) + __set_bit(ENTRY_TXD_HT_BW_40, &txdesc->flags); + if (txrate->flags & IEEE80211_TX_RC_SHORT_GI) + __set_bit(ENTRY_TXD_HT_SHORT_GI, &txdesc->flags); + + /* + * Determine IFS values + * - Use TXOP_BACKOFF for management frames except beacons + * - Use TXOP_SIFS for fragment bursts + * - Use TXOP_HTTXOP for everything else + * + * Note: rt2800 devices won't use CTS protection (if used) + * for frames not transmitted with TXOP_HTTXOP + */ + if (ieee80211_is_mgmt(hdr->frame_control) && + !ieee80211_is_beacon(hdr->frame_control)) + txdesc->u.ht.txop = TXOP_BACKOFF; + else if (!(tx_info->flags & IEEE80211_TX_CTL_FIRST_FRAGMENT)) + txdesc->u.ht.txop = TXOP_SIFS; + else + txdesc->u.ht.txop = TXOP_HTTXOP; +} + +static void rt2x00queue_create_tx_descriptor(struct rt2x00_dev *rt2x00dev, + struct sk_buff *skb, + struct txentry_desc *txdesc, + struct ieee80211_sta *sta) +{ + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_tx_rate *txrate = &tx_info->control.rates[0]; + struct ieee80211_rate *rate; + const struct rt2x00_rate *hwrate = NULL; + + memset(txdesc, 0, sizeof(*txdesc)); + + /* + * Header and frame information. + */ + txdesc->length = skb->len; + txdesc->header_length = ieee80211_get_hdrlen_from_skb(skb); + + /* + * Check whether this frame is to be acked. + */ + if (!(tx_info->flags & IEEE80211_TX_CTL_NO_ACK)) + __set_bit(ENTRY_TXD_ACK, &txdesc->flags); + + /* + * Check if this is a RTS/CTS frame + */ + if (ieee80211_is_rts(hdr->frame_control) || + ieee80211_is_cts(hdr->frame_control)) { + __set_bit(ENTRY_TXD_BURST, &txdesc->flags); + if (ieee80211_is_rts(hdr->frame_control)) + __set_bit(ENTRY_TXD_RTS_FRAME, &txdesc->flags); + else + __set_bit(ENTRY_TXD_CTS_FRAME, &txdesc->flags); + if (tx_info->control.rts_cts_rate_idx >= 0) + rate = + ieee80211_get_rts_cts_rate(rt2x00dev->hw, tx_info); + } + + /* + * Determine retry information. + */ + txdesc->retry_limit = tx_info->control.rates[0].count - 1; + if (txdesc->retry_limit >= rt2x00dev->long_retry) + __set_bit(ENTRY_TXD_RETRY_MODE, &txdesc->flags); + + /* + * Check if more fragments are pending + */ + if (ieee80211_has_morefrags(hdr->frame_control)) { + __set_bit(ENTRY_TXD_BURST, &txdesc->flags); + __set_bit(ENTRY_TXD_MORE_FRAG, &txdesc->flags); + } + + /* + * Check if more frames (!= fragments) are pending + */ + if (tx_info->flags & IEEE80211_TX_CTL_MORE_FRAMES) + __set_bit(ENTRY_TXD_BURST, &txdesc->flags); + + /* + * Beacons and probe responses require the tsf timestamp + * to be inserted into the frame. + */ + if (ieee80211_is_beacon(hdr->frame_control) || + ieee80211_is_probe_resp(hdr->frame_control)) + __set_bit(ENTRY_TXD_REQ_TIMESTAMP, &txdesc->flags); + + if ((tx_info->flags & IEEE80211_TX_CTL_FIRST_FRAGMENT) && + !test_bit(ENTRY_TXD_RTS_FRAME, &txdesc->flags)) + __set_bit(ENTRY_TXD_FIRST_FRAGMENT, &txdesc->flags); + + /* + * Determine rate modulation. + */ + if (txrate->flags & IEEE80211_TX_RC_GREEN_FIELD) + txdesc->rate_mode = RATE_MODE_HT_GREENFIELD; + else if (txrate->flags & IEEE80211_TX_RC_MCS) + txdesc->rate_mode = RATE_MODE_HT_MIX; + else { + rate = ieee80211_get_tx_rate(rt2x00dev->hw, tx_info); + hwrate = rt2x00_get_rate(rate->hw_value); + if (hwrate->flags & DEV_RATE_OFDM) + txdesc->rate_mode = RATE_MODE_OFDM; + else + txdesc->rate_mode = RATE_MODE_CCK; + } + + /* + * Apply TX descriptor handling by components + */ + rt2x00crypto_create_tx_descriptor(rt2x00dev, skb, txdesc); + rt2x00queue_create_tx_descriptor_seq(rt2x00dev, skb, txdesc); + + if (test_bit(REQUIRE_HT_TX_DESC, &rt2x00dev->cap_flags)) + rt2x00queue_create_tx_descriptor_ht(rt2x00dev, skb, txdesc, + sta, hwrate); + else + rt2x00queue_create_tx_descriptor_plcp(rt2x00dev, skb, txdesc, + hwrate); +} + +static int rt2x00queue_write_tx_data(struct queue_entry *entry, + struct txentry_desc *txdesc) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + + /* + * This should not happen, we already checked the entry + * was ours. When the hardware disagrees there has been + * a queue corruption! + */ + if (unlikely(rt2x00dev->ops->lib->get_entry_state && + rt2x00dev->ops->lib->get_entry_state(entry))) { + rt2x00_err(rt2x00dev, + "Corrupt queue %d, accessing entry which is not ours\n" + "Please file bug report to %s\n", + entry->queue->qid, DRV_PROJECT); + return -EINVAL; + } + + /* + * Add the requested extra tx headroom in front of the skb. + */ + skb_push(entry->skb, rt2x00dev->ops->extra_tx_headroom); + memset(entry->skb->data, 0, rt2x00dev->ops->extra_tx_headroom); + + /* + * Call the driver's write_tx_data function, if it exists. + */ + if (rt2x00dev->ops->lib->write_tx_data) + rt2x00dev->ops->lib->write_tx_data(entry, txdesc); + + /* + * Map the skb to DMA. + */ + if (test_bit(REQUIRE_DMA, &rt2x00dev->cap_flags) && + rt2x00queue_map_txskb(entry)) + return -ENOMEM; + + return 0; +} + +static void rt2x00queue_write_tx_descriptor(struct queue_entry *entry, + struct txentry_desc *txdesc) +{ + struct data_queue *queue = entry->queue; + + queue->rt2x00dev->ops->lib->write_tx_desc(entry, txdesc); + + /* + * All processing on the frame has been completed, this means + * it is now ready to be dumped to userspace through debugfs. + */ + rt2x00debug_dump_frame(queue->rt2x00dev, DUMP_FRAME_TX, entry->skb); +} + +static void rt2x00queue_kick_tx_queue(struct data_queue *queue, + struct txentry_desc *txdesc) +{ + /* + * Check if we need to kick the queue, there are however a few rules + * 1) Don't kick unless this is the last in frame in a burst. + * When the burst flag is set, this frame is always followed + * by another frame which in some way are related to eachother. + * This is true for fragments, RTS or CTS-to-self frames. + * 2) Rule 1 can be broken when the available entries + * in the queue are less then a certain threshold. + */ + if (rt2x00queue_threshold(queue) || + !test_bit(ENTRY_TXD_BURST, &txdesc->flags)) + queue->rt2x00dev->ops->lib->kick_queue(queue); +} + +static void rt2x00queue_bar_check(struct queue_entry *entry) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + struct ieee80211_bar *bar = (void *) (entry->skb->data + + rt2x00dev->ops->extra_tx_headroom); + struct rt2x00_bar_list_entry *bar_entry; + + if (likely(!ieee80211_is_back_req(bar->frame_control))) + return; + + bar_entry = kmalloc(sizeof(*bar_entry), GFP_ATOMIC); + + /* + * If the alloc fails we still send the BAR out but just don't track + * it in our bar list. And as a result we will report it to mac80211 + * back as failed. + */ + if (!bar_entry) + return; + + bar_entry->entry = entry; + bar_entry->block_acked = 0; + + /* + * Copy the relevant parts of the 802.11 BAR into out check list + * such that we can use RCU for less-overhead in the RX path since + * sending BARs and processing the according BlockAck should be + * the exception. + */ + memcpy(bar_entry->ra, bar->ra, sizeof(bar->ra)); + memcpy(bar_entry->ta, bar->ta, sizeof(bar->ta)); + bar_entry->control = bar->control; + bar_entry->start_seq_num = bar->start_seq_num; + + /* + * Insert BAR into our BAR check list. + */ + spin_lock_bh(&rt2x00dev->bar_list_lock); + list_add_tail_rcu(&bar_entry->list, &rt2x00dev->bar_list); + spin_unlock_bh(&rt2x00dev->bar_list_lock); +} + +int rt2x00queue_write_tx_frame(struct data_queue *queue, struct sk_buff *skb, + bool local) +{ + struct ieee80211_tx_info *tx_info; + struct queue_entry *entry; + struct txentry_desc txdesc; + struct skb_frame_desc *skbdesc; + u8 rate_idx, rate_flags; + int ret = 0; + + /* + * Copy all TX descriptor information into txdesc, + * after that we are free to use the skb->cb array + * for our information. + */ + rt2x00queue_create_tx_descriptor(queue->rt2x00dev, skb, &txdesc, NULL); + + /* + * All information is retrieved from the skb->cb array, + * now we should claim ownership of the driver part of that + * array, preserving the bitrate index and flags. + */ + tx_info = IEEE80211_SKB_CB(skb); + rate_idx = tx_info->control.rates[0].idx; + rate_flags = tx_info->control.rates[0].flags; + skbdesc = get_skb_frame_desc(skb); + memset(skbdesc, 0, sizeof(*skbdesc)); + skbdesc->tx_rate_idx = rate_idx; + skbdesc->tx_rate_flags = rate_flags; + + if (local) + skbdesc->flags |= SKBDESC_NOT_MAC80211; + + /* + * When hardware encryption is supported, and this frame + * is to be encrypted, we should strip the IV/EIV data from + * the frame so we can provide it to the driver separately. + */ + if (test_bit(ENTRY_TXD_ENCRYPT, &txdesc.flags) && + !test_bit(ENTRY_TXD_ENCRYPT_IV, &txdesc.flags)) { + if (test_bit(REQUIRE_COPY_IV, &queue->rt2x00dev->cap_flags)) + rt2x00crypto_tx_copy_iv(skb, &txdesc); + else + rt2x00crypto_tx_remove_iv(skb, &txdesc); + } + + /* + * When DMA allocation is required we should guarantee to the + * driver that the DMA is aligned to a 4-byte boundary. + * However some drivers require L2 padding to pad the payload + * rather then the header. This could be a requirement for + * PCI and USB devices, while header alignment only is valid + * for PCI devices. + */ + if (test_bit(REQUIRE_L2PAD, &queue->rt2x00dev->cap_flags)) + rt2x00queue_insert_l2pad(skb, txdesc.header_length); + else if (test_bit(REQUIRE_DMA, &queue->rt2x00dev->cap_flags)) + rt2x00queue_align_frame(skb); + + /* + * That function must be called with bh disabled. + */ + spin_lock(&queue->tx_lock); + + if (unlikely(rt2x00queue_full(queue))) { + rt2x00_err(queue->rt2x00dev, "Dropping frame due to full tx queue %d\n", + queue->qid); + ret = -ENOBUFS; + goto out; + } + + entry = rt2x00queue_get_entry(queue, Q_INDEX); + + if (unlikely(test_and_set_bit(ENTRY_OWNER_DEVICE_DATA, + &entry->flags))) { + rt2x00_err(queue->rt2x00dev, + "Arrived at non-free entry in the non-full queue %d\n" + "Please file bug report to %s\n", + queue->qid, DRV_PROJECT); + ret = -EINVAL; + goto out; + } + + skbdesc->entry = entry; + entry->skb = skb; + + /* + * It could be possible that the queue was corrupted and this + * call failed. Since we always return NETDEV_TX_OK to mac80211, + * this frame will simply be dropped. + */ + if (unlikely(rt2x00queue_write_tx_data(entry, &txdesc))) { + clear_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags); + entry->skb = NULL; + ret = -EIO; + goto out; + } + + /* + * Put BlockAckReqs into our check list for driver BA processing. + */ + rt2x00queue_bar_check(entry); + + set_bit(ENTRY_DATA_PENDING, &entry->flags); + + rt2x00queue_index_inc(entry, Q_INDEX); + rt2x00queue_write_tx_descriptor(entry, &txdesc); + rt2x00queue_kick_tx_queue(queue, &txdesc); + +out: + spin_unlock(&queue->tx_lock); + return ret; +} + +int rt2x00queue_clear_beacon(struct rt2x00_dev *rt2x00dev, + struct ieee80211_vif *vif) +{ + struct rt2x00_intf *intf = vif_to_intf(vif); + + if (unlikely(!intf->beacon)) + return -ENOBUFS; + + mutex_lock(&intf->beacon_skb_mutex); + + /* + * Clean up the beacon skb. + */ + rt2x00queue_free_skb(intf->beacon); + + /* + * Clear beacon (single bssid devices don't need to clear the beacon + * since the beacon queue will get stopped anyway). + */ + if (rt2x00dev->ops->lib->clear_beacon) + rt2x00dev->ops->lib->clear_beacon(intf->beacon); + + mutex_unlock(&intf->beacon_skb_mutex); + + return 0; +} + +int rt2x00queue_update_beacon_locked(struct rt2x00_dev *rt2x00dev, + struct ieee80211_vif *vif) +{ + struct rt2x00_intf *intf = vif_to_intf(vif); + struct skb_frame_desc *skbdesc; + struct txentry_desc txdesc; + + if (unlikely(!intf->beacon)) + return -ENOBUFS; + + /* + * Clean up the beacon skb. + */ + rt2x00queue_free_skb(intf->beacon); + + intf->beacon->skb = ieee80211_beacon_get(rt2x00dev->hw, vif); + if (!intf->beacon->skb) + return -ENOMEM; + + /* + * Copy all TX descriptor information into txdesc, + * after that we are free to use the skb->cb array + * for our information. + */ + rt2x00queue_create_tx_descriptor(rt2x00dev, intf->beacon->skb, &txdesc, NULL); + + /* + * Fill in skb descriptor + */ + skbdesc = get_skb_frame_desc(intf->beacon->skb); + memset(skbdesc, 0, sizeof(*skbdesc)); + skbdesc->entry = intf->beacon; + + /* + * Send beacon to hardware. + */ + rt2x00dev->ops->lib->write_beacon(intf->beacon, &txdesc); + + return 0; + +} + +int rt2x00queue_update_beacon(struct rt2x00_dev *rt2x00dev, + struct ieee80211_vif *vif) +{ + struct rt2x00_intf *intf = vif_to_intf(vif); + int ret; + + mutex_lock(&intf->beacon_skb_mutex); + ret = rt2x00queue_update_beacon_locked(rt2x00dev, vif); + mutex_unlock(&intf->beacon_skb_mutex); + + return ret; +} + +bool rt2x00queue_for_each_entry(struct data_queue *queue, + enum queue_index start, + enum queue_index end, + void *data, + bool (*fn)(struct queue_entry *entry, + void *data)) +{ + unsigned long irqflags; + unsigned int index_start; + unsigned int index_end; + unsigned int i; + + if (unlikely(start >= Q_INDEX_MAX || end >= Q_INDEX_MAX)) { + rt2x00_err(queue->rt2x00dev, + "Entry requested from invalid index range (%d - %d)\n", + start, end); + return true; + } + + /* + * Only protect the range we are going to loop over, + * if during our loop a extra entry is set to pending + * it should not be kicked during this run, since it + * is part of another TX operation. + */ + spin_lock_irqsave(&queue->index_lock, irqflags); + index_start = queue->index[start]; + index_end = queue->index[end]; + spin_unlock_irqrestore(&queue->index_lock, irqflags); + + /* + * Start from the TX done pointer, this guarantees that we will + * send out all frames in the correct order. + */ + if (index_start < index_end) { + for (i = index_start; i < index_end; i++) { + if (fn(&queue->entries[i], data)) + return true; + } + } else { + for (i = index_start; i < queue->limit; i++) { + if (fn(&queue->entries[i], data)) + return true; + } + + for (i = 0; i < index_end; i++) { + if (fn(&queue->entries[i], data)) + return true; + } + } + + return false; +} +EXPORT_SYMBOL_GPL(rt2x00queue_for_each_entry); + +struct queue_entry *rt2x00queue_get_entry(struct data_queue *queue, + enum queue_index index) +{ + struct queue_entry *entry; + unsigned long irqflags; + + if (unlikely(index >= Q_INDEX_MAX)) { + rt2x00_err(queue->rt2x00dev, "Entry requested from invalid index type (%d)\n", + index); + return NULL; + } + + spin_lock_irqsave(&queue->index_lock, irqflags); + + entry = &queue->entries[queue->index[index]]; + + spin_unlock_irqrestore(&queue->index_lock, irqflags); + + return entry; +} +EXPORT_SYMBOL_GPL(rt2x00queue_get_entry); + +void rt2x00queue_index_inc(struct queue_entry *entry, enum queue_index index) +{ + struct data_queue *queue = entry->queue; + unsigned long irqflags; + + if (unlikely(index >= Q_INDEX_MAX)) { + rt2x00_err(queue->rt2x00dev, + "Index change on invalid index type (%d)\n", index); + return; + } + + spin_lock_irqsave(&queue->index_lock, irqflags); + + queue->index[index]++; + if (queue->index[index] >= queue->limit) + queue->index[index] = 0; + + entry->last_action = jiffies; + + if (index == Q_INDEX) { + queue->length++; + } else if (index == Q_INDEX_DONE) { + queue->length--; + queue->count++; + } + + spin_unlock_irqrestore(&queue->index_lock, irqflags); +} + +void rt2x00queue_pause_queue_nocheck(struct data_queue *queue) +{ + switch (queue->qid) { + case QID_AC_VO: + case QID_AC_VI: + case QID_AC_BE: + case QID_AC_BK: + /* + * For TX queues, we have to disable the queue + * inside mac80211. + */ + ieee80211_stop_queue(queue->rt2x00dev->hw, queue->qid); + break; + default: + break; + } +} +void rt2x00queue_pause_queue(struct data_queue *queue) +{ + if (!test_bit(DEVICE_STATE_PRESENT, &queue->rt2x00dev->flags) || + !test_bit(QUEUE_STARTED, &queue->flags) || + test_and_set_bit(QUEUE_PAUSED, &queue->flags)) + return; + + rt2x00queue_pause_queue_nocheck(queue); +} +EXPORT_SYMBOL_GPL(rt2x00queue_pause_queue); + +void rt2x00queue_unpause_queue(struct data_queue *queue) +{ + if (!test_bit(DEVICE_STATE_PRESENT, &queue->rt2x00dev->flags) || + !test_bit(QUEUE_STARTED, &queue->flags) || + !test_and_clear_bit(QUEUE_PAUSED, &queue->flags)) + return; + + switch (queue->qid) { + case QID_AC_VO: + case QID_AC_VI: + case QID_AC_BE: + case QID_AC_BK: + /* + * For TX queues, we have to enable the queue + * inside mac80211. + */ + ieee80211_wake_queue(queue->rt2x00dev->hw, queue->qid); + break; + case QID_RX: + /* + * For RX we need to kick the queue now in order to + * receive frames. + */ + queue->rt2x00dev->ops->lib->kick_queue(queue); + default: + break; + } +} +EXPORT_SYMBOL_GPL(rt2x00queue_unpause_queue); + +void rt2x00queue_start_queue(struct data_queue *queue) +{ + mutex_lock(&queue->status_lock); + + if (!test_bit(DEVICE_STATE_PRESENT, &queue->rt2x00dev->flags) || + test_and_set_bit(QUEUE_STARTED, &queue->flags)) { + mutex_unlock(&queue->status_lock); + return; + } + + set_bit(QUEUE_PAUSED, &queue->flags); + + queue->rt2x00dev->ops->lib->start_queue(queue); + + rt2x00queue_unpause_queue(queue); + + mutex_unlock(&queue->status_lock); +} +EXPORT_SYMBOL_GPL(rt2x00queue_start_queue); + +void rt2x00queue_stop_queue(struct data_queue *queue) +{ + mutex_lock(&queue->status_lock); + + if (!test_and_clear_bit(QUEUE_STARTED, &queue->flags)) { + mutex_unlock(&queue->status_lock); + return; + } + + rt2x00queue_pause_queue_nocheck(queue); + + queue->rt2x00dev->ops->lib->stop_queue(queue); + + mutex_unlock(&queue->status_lock); +} +EXPORT_SYMBOL_GPL(rt2x00queue_stop_queue); + +void rt2x00queue_flush_queue(struct data_queue *queue, bool drop) +{ + bool started; + bool tx_queue = + (queue->qid == QID_AC_VO) || + (queue->qid == QID_AC_VI) || + (queue->qid == QID_AC_BE) || + (queue->qid == QID_AC_BK); + + mutex_lock(&queue->status_lock); + + /* + * If the queue has been started, we must stop it temporarily + * to prevent any new frames to be queued on the device. If + * we are not dropping the pending frames, the queue must + * only be stopped in the software and not the hardware, + * otherwise the queue will never become empty on its own. + */ + started = test_bit(QUEUE_STARTED, &queue->flags); + if (started) { + /* + * Pause the queue + */ + rt2x00queue_pause_queue(queue); + + /* + * If we are not supposed to drop any pending + * frames, this means we must force a start (=kick) + * to the queue to make sure the hardware will + * start transmitting. + */ + if (!drop && tx_queue) + queue->rt2x00dev->ops->lib->kick_queue(queue); + } + + /* + * Check if driver supports flushing, if that is the case we can + * defer the flushing to the driver. Otherwise we must use the + * alternative which just waits for the queue to become empty. + */ + if (likely(queue->rt2x00dev->ops->lib->flush_queue)) + queue->rt2x00dev->ops->lib->flush_queue(queue, drop); + + /* + * The queue flush has failed... + */ + if (unlikely(!rt2x00queue_empty(queue))) + rt2x00_warn(queue->rt2x00dev, "Queue %d failed to flush\n", + queue->qid); + + /* + * Restore the queue to the previous status + */ + if (started) + rt2x00queue_unpause_queue(queue); + + mutex_unlock(&queue->status_lock); +} +EXPORT_SYMBOL_GPL(rt2x00queue_flush_queue); + +void rt2x00queue_start_queues(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + + /* + * rt2x00queue_start_queue will call ieee80211_wake_queue + * for each queue after is has been properly initialized. + */ + tx_queue_for_each(rt2x00dev, queue) + rt2x00queue_start_queue(queue); + + rt2x00queue_start_queue(rt2x00dev->rx); +} +EXPORT_SYMBOL_GPL(rt2x00queue_start_queues); + +void rt2x00queue_stop_queues(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + + /* + * rt2x00queue_stop_queue will call ieee80211_stop_queue + * as well, but we are completely shutting doing everything + * now, so it is much safer to stop all TX queues at once, + * and use rt2x00queue_stop_queue for cleaning up. + */ + ieee80211_stop_queues(rt2x00dev->hw); + + tx_queue_for_each(rt2x00dev, queue) + rt2x00queue_stop_queue(queue); + + rt2x00queue_stop_queue(rt2x00dev->rx); +} +EXPORT_SYMBOL_GPL(rt2x00queue_stop_queues); + +void rt2x00queue_flush_queues(struct rt2x00_dev *rt2x00dev, bool drop) +{ + struct data_queue *queue; + + tx_queue_for_each(rt2x00dev, queue) + rt2x00queue_flush_queue(queue, drop); + + rt2x00queue_flush_queue(rt2x00dev->rx, drop); +} +EXPORT_SYMBOL_GPL(rt2x00queue_flush_queues); + +static void rt2x00queue_reset(struct data_queue *queue) +{ + unsigned long irqflags; + unsigned int i; + + spin_lock_irqsave(&queue->index_lock, irqflags); + + queue->count = 0; + queue->length = 0; + + for (i = 0; i < Q_INDEX_MAX; i++) + queue->index[i] = 0; + + spin_unlock_irqrestore(&queue->index_lock, irqflags); +} + +void rt2x00queue_init_queues(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + unsigned int i; + + queue_for_each(rt2x00dev, queue) { + rt2x00queue_reset(queue); + + for (i = 0; i < queue->limit; i++) + rt2x00dev->ops->lib->clear_entry(&queue->entries[i]); + } +} + +static int rt2x00queue_alloc_entries(struct data_queue *queue, + const struct data_queue_desc *qdesc) +{ + struct queue_entry *entries; + unsigned int entry_size; + unsigned int i; + + rt2x00queue_reset(queue); + + queue->limit = qdesc->entry_num; + queue->threshold = DIV_ROUND_UP(qdesc->entry_num, 10); + queue->data_size = qdesc->data_size; + queue->desc_size = qdesc->desc_size; + queue->winfo_size = qdesc->winfo_size; + + /* + * Allocate all queue entries. + */ + entry_size = sizeof(*entries) + qdesc->priv_size; + entries = kcalloc(queue->limit, entry_size, GFP_KERNEL); + if (!entries) + return -ENOMEM; + +#define QUEUE_ENTRY_PRIV_OFFSET(__base, __index, __limit, __esize, __psize) \ + (((char *)(__base)) + ((__limit) * (__esize)) + \ + ((__index) * (__psize))) + + for (i = 0; i < queue->limit; i++) { + entries[i].flags = 0; + entries[i].queue = queue; + entries[i].skb = NULL; + entries[i].entry_idx = i; + entries[i].priv_data = + QUEUE_ENTRY_PRIV_OFFSET(entries, i, queue->limit, + sizeof(*entries), qdesc->priv_size); + } + +#undef QUEUE_ENTRY_PRIV_OFFSET + + queue->entries = entries; + + return 0; +} + +static void rt2x00queue_free_skbs(struct data_queue *queue) +{ + unsigned int i; + + if (!queue->entries) + return; + + for (i = 0; i < queue->limit; i++) { + rt2x00queue_free_skb(&queue->entries[i]); + } +} + +static int rt2x00queue_alloc_rxskbs(struct data_queue *queue) +{ + unsigned int i; + struct sk_buff *skb; + + for (i = 0; i < queue->limit; i++) { + skb = rt2x00queue_alloc_rxskb(&queue->entries[i], GFP_KERNEL); + if (!skb) + return -ENOMEM; + queue->entries[i].skb = skb; + } + + return 0; +} + +int rt2x00queue_initialize(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + int status; + + status = rt2x00queue_alloc_entries(rt2x00dev->rx, rt2x00dev->ops->rx); + if (status) + goto exit; + + tx_queue_for_each(rt2x00dev, queue) { + status = rt2x00queue_alloc_entries(queue, rt2x00dev->ops->tx); + if (status) + goto exit; + } + + status = rt2x00queue_alloc_entries(rt2x00dev->bcn, rt2x00dev->ops->bcn); + if (status) + goto exit; + + if (test_bit(REQUIRE_ATIM_QUEUE, &rt2x00dev->cap_flags)) { + status = rt2x00queue_alloc_entries(rt2x00dev->atim, + rt2x00dev->ops->atim); + if (status) + goto exit; + } + + status = rt2x00queue_alloc_rxskbs(rt2x00dev->rx); + if (status) + goto exit; + + return 0; + +exit: + rt2x00_err(rt2x00dev, "Queue entries allocation failed\n"); + + rt2x00queue_uninitialize(rt2x00dev); + + return status; +} + +void rt2x00queue_uninitialize(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + + rt2x00queue_free_skbs(rt2x00dev->rx); + + queue_for_each(rt2x00dev, queue) { + kfree(queue->entries); + queue->entries = NULL; + } +} + +static void rt2x00queue_init(struct rt2x00_dev *rt2x00dev, + struct data_queue *queue, enum data_queue_qid qid) +{ + mutex_init(&queue->status_lock); + spin_lock_init(&queue->tx_lock); + spin_lock_init(&queue->index_lock); + + queue->rt2x00dev = rt2x00dev; + queue->qid = qid; + queue->txop = 0; + queue->aifs = 2; + queue->cw_min = 5; + queue->cw_max = 10; +} + +int rt2x00queue_allocate(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + enum data_queue_qid qid; + unsigned int req_atim = + !!test_bit(REQUIRE_ATIM_QUEUE, &rt2x00dev->cap_flags); + + /* + * We need the following queues: + * RX: 1 + * TX: ops->tx_queues + * Beacon: 1 + * Atim: 1 (if required) + */ + rt2x00dev->data_queues = 2 + rt2x00dev->ops->tx_queues + req_atim; + + queue = kcalloc(rt2x00dev->data_queues, sizeof(*queue), GFP_KERNEL); + if (!queue) { + rt2x00_err(rt2x00dev, "Queue allocation failed\n"); + return -ENOMEM; + } + + /* + * Initialize pointers + */ + rt2x00dev->rx = queue; + rt2x00dev->tx = &queue[1]; + rt2x00dev->bcn = &queue[1 + rt2x00dev->ops->tx_queues]; + rt2x00dev->atim = req_atim ? &queue[2 + rt2x00dev->ops->tx_queues] : NULL; + + /* + * Initialize queue parameters. + * RX: qid = QID_RX + * TX: qid = QID_AC_VO + index + * TX: cw_min: 2^5 = 32. + * TX: cw_max: 2^10 = 1024. + * BCN: qid = QID_BEACON + * ATIM: qid = QID_ATIM + */ + rt2x00queue_init(rt2x00dev, rt2x00dev->rx, QID_RX); + + qid = QID_AC_VO; + tx_queue_for_each(rt2x00dev, queue) + rt2x00queue_init(rt2x00dev, queue, qid++); + + rt2x00queue_init(rt2x00dev, rt2x00dev->bcn, QID_BEACON); + if (req_atim) + rt2x00queue_init(rt2x00dev, rt2x00dev->atim, QID_ATIM); + + return 0; +} + +void rt2x00queue_free(struct rt2x00_dev *rt2x00dev) +{ + kfree(rt2x00dev->rx); + rt2x00dev->rx = NULL; + rt2x00dev->tx = NULL; + rt2x00dev->bcn = NULL; +} diff --git a/drivers/net/wireless/rt2x00/rt2x00queue.h b/drivers/net/wireless/rt2x00/rt2x00queue.h new file mode 100644 index 0000000..4a7b34e --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00queue.h @@ -0,0 +1,704 @@ +/* + Copyright (C) 2004 - 2010 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00 + Abstract: rt2x00 queue datastructures and routines + */ + +#ifndef RT2X00QUEUE_H +#define RT2X00QUEUE_H + +#include + +/** + * DOC: Entry frame size + * + * Ralink PCI devices demand the Frame size to be a multiple of 128 bytes, + * for USB devices this restriction does not apply, but the value of + * 2432 makes sense since it is big enough to contain the maximum fragment + * size according to the ieee802.11 specs. + * The aggregation size depends on support from the driver, but should + * be something around 3840 bytes. + */ +#define DATA_FRAME_SIZE 2432 +#define MGMT_FRAME_SIZE 256 +#define AGGREGATION_SIZE 3840 + +/** + * enum data_queue_qid: Queue identification + * + * @QID_AC_VO: AC VO queue + * @QID_AC_VI: AC VI queue + * @QID_AC_BE: AC BE queue + * @QID_AC_BK: AC BK queue + * @QID_HCCA: HCCA queue + * @QID_MGMT: MGMT queue (prio queue) + * @QID_RX: RX queue + * @QID_OTHER: None of the above (don't use, only present for completeness) + * @QID_BEACON: Beacon queue (value unspecified, don't send it to device) + * @QID_ATIM: Atim queue (value unspecified, don't send it to device) + */ +enum data_queue_qid { + QID_AC_VO = 0, + QID_AC_VI = 1, + QID_AC_BE = 2, + QID_AC_BK = 3, + QID_HCCA = 4, + QID_MGMT = 13, + QID_RX = 14, + QID_OTHER = 15, + QID_BEACON, + QID_ATIM, +}; + +/** + * enum skb_frame_desc_flags: Flags for &struct skb_frame_desc + * + * @SKBDESC_DMA_MAPPED_RX: &skb_dma field has been mapped for RX + * @SKBDESC_DMA_MAPPED_TX: &skb_dma field has been mapped for TX + * @SKBDESC_IV_STRIPPED: Frame contained a IV/EIV provided by + * mac80211 but was stripped for processing by the driver. + * @SKBDESC_NOT_MAC80211: Frame didn't originate from mac80211, + * don't try to pass it back. + * @SKBDESC_DESC_IN_SKB: The descriptor is at the start of the + * skb, instead of in the desc field. + */ +enum skb_frame_desc_flags { + SKBDESC_DMA_MAPPED_RX = 1 << 0, + SKBDESC_DMA_MAPPED_TX = 1 << 1, + SKBDESC_IV_STRIPPED = 1 << 2, + SKBDESC_NOT_MAC80211 = 1 << 3, + SKBDESC_DESC_IN_SKB = 1 << 4, +}; + +/** + * struct skb_frame_desc: Descriptor information for the skb buffer + * + * This structure is placed over the driver_data array, this means that + * this structure should not exceed the size of that array (40 bytes). + * + * @flags: Frame flags, see &enum skb_frame_desc_flags. + * @desc_len: Length of the frame descriptor. + * @tx_rate_idx: the index of the TX rate, used for TX status reporting + * @tx_rate_flags: the TX rate flags, used for TX status reporting + * @desc: Pointer to descriptor part of the frame. + * Note that this pointer could point to something outside + * of the scope of the skb->data pointer. + * @iv: IV/EIV data used during encryption/decryption. + * @skb_dma: (PCI-only) the DMA address associated with the sk buffer. + * @entry: The entry to which this sk buffer belongs. + */ +struct skb_frame_desc { + u8 flags; + + u8 desc_len; + u8 tx_rate_idx; + u8 tx_rate_flags; + + void *desc; + + __le32 iv[2]; + + dma_addr_t skb_dma; + + struct queue_entry *entry; +}; + +/** + * get_skb_frame_desc - Obtain the rt2x00 frame descriptor from a sk_buff. + * @skb: &struct sk_buff from where we obtain the &struct skb_frame_desc + */ +static inline struct skb_frame_desc* get_skb_frame_desc(struct sk_buff *skb) +{ + BUILD_BUG_ON(sizeof(struct skb_frame_desc) > + IEEE80211_TX_INFO_DRIVER_DATA_SIZE); + return (struct skb_frame_desc *)&IEEE80211_SKB_CB(skb)->driver_data; +} + +/** + * enum rxdone_entry_desc_flags: Flags for &struct rxdone_entry_desc + * + * @RXDONE_SIGNAL_PLCP: Signal field contains the plcp value. + * @RXDONE_SIGNAL_BITRATE: Signal field contains the bitrate value. + * @RXDONE_SIGNAL_MCS: Signal field contains the mcs value. + * @RXDONE_MY_BSS: Does this frame originate from device's BSS. + * @RXDONE_CRYPTO_IV: Driver provided IV/EIV data. + * @RXDONE_CRYPTO_ICV: Driver provided ICV data. + * @RXDONE_L2PAD: 802.11 payload has been padded to 4-byte boundary. + */ +enum rxdone_entry_desc_flags { + RXDONE_SIGNAL_PLCP = BIT(0), + RXDONE_SIGNAL_BITRATE = BIT(1), + RXDONE_SIGNAL_MCS = BIT(2), + RXDONE_MY_BSS = BIT(3), + RXDONE_CRYPTO_IV = BIT(4), + RXDONE_CRYPTO_ICV = BIT(5), + RXDONE_L2PAD = BIT(6), +}; + +/** + * RXDONE_SIGNAL_MASK - Define to mask off all &rxdone_entry_desc_flags flags + * except for the RXDONE_SIGNAL_* flags. This is useful to convert the dev_flags + * from &rxdone_entry_desc to a signal value type. + */ +#define RXDONE_SIGNAL_MASK \ + ( RXDONE_SIGNAL_PLCP | RXDONE_SIGNAL_BITRATE | RXDONE_SIGNAL_MCS ) + +/** + * struct rxdone_entry_desc: RX Entry descriptor + * + * Summary of information that has been read from the RX frame descriptor. + * + * @timestamp: RX Timestamp + * @signal: Signal of the received frame. + * @rssi: RSSI of the received frame. + * @size: Data size of the received frame. + * @flags: MAC80211 receive flags (See &enum mac80211_rx_flags). + * @dev_flags: Ralink receive flags (See &enum rxdone_entry_desc_flags). + * @rate_mode: Rate mode (See @enum rate_modulation). + * @cipher: Cipher type used during decryption. + * @cipher_status: Decryption status. + * @iv: IV/EIV data used during decryption. + * @icv: ICV data used during decryption. + */ +struct rxdone_entry_desc { + u64 timestamp; + int signal; + int rssi; + int size; + int flags; + int dev_flags; + u16 rate_mode; + u8 cipher; + u8 cipher_status; + + __le32 iv[2]; + __le32 icv; +}; + +/** + * enum txdone_entry_desc_flags: Flags for &struct txdone_entry_desc + * + * Every txdone report has to contain the basic result of the + * transmission, either &TXDONE_UNKNOWN, &TXDONE_SUCCESS or + * &TXDONE_FAILURE. The flag &TXDONE_FALLBACK can be used in + * conjunction with all of these flags but should only be set + * if retires > 0. The flag &TXDONE_EXCESSIVE_RETRY can only be used + * in conjunction with &TXDONE_FAILURE. + * + * @TXDONE_UNKNOWN: Hardware could not determine success of transmission. + * @TXDONE_SUCCESS: Frame was successfully send + * @TXDONE_FALLBACK: Hardware used fallback rates for retries + * @TXDONE_FAILURE: Frame was not successfully send + * @TXDONE_EXCESSIVE_RETRY: In addition to &TXDONE_FAILURE, the + * frame transmission failed due to excessive retries. + */ +enum txdone_entry_desc_flags { + TXDONE_UNKNOWN, + TXDONE_SUCCESS, + TXDONE_FALLBACK, + TXDONE_FAILURE, + TXDONE_EXCESSIVE_RETRY, + TXDONE_AMPDU, +}; + +/** + * struct txdone_entry_desc: TX done entry descriptor + * + * Summary of information that has been read from the TX frame descriptor + * after the device is done with transmission. + * + * @flags: TX done flags (See &enum txdone_entry_desc_flags). + * @retry: Retry count. + */ +struct txdone_entry_desc { + unsigned long flags; + int retry; +}; + +/** + * enum txentry_desc_flags: Status flags for TX entry descriptor + * + * @ENTRY_TXD_RTS_FRAME: This frame is a RTS frame. + * @ENTRY_TXD_CTS_FRAME: This frame is a CTS-to-self frame. + * @ENTRY_TXD_GENERATE_SEQ: This frame requires sequence counter. + * @ENTRY_TXD_FIRST_FRAGMENT: This is the first frame. + * @ENTRY_TXD_MORE_FRAG: This frame is followed by another fragment. + * @ENTRY_TXD_REQ_TIMESTAMP: Require timestamp to be inserted. + * @ENTRY_TXD_BURST: This frame belongs to the same burst event. + * @ENTRY_TXD_ACK: An ACK is required for this frame. + * @ENTRY_TXD_RETRY_MODE: When set, the long retry count is used. + * @ENTRY_TXD_ENCRYPT: This frame should be encrypted. + * @ENTRY_TXD_ENCRYPT_PAIRWISE: Use pairwise key table (instead of shared). + * @ENTRY_TXD_ENCRYPT_IV: Generate IV/EIV in hardware. + * @ENTRY_TXD_ENCRYPT_MMIC: Generate MIC in hardware. + * @ENTRY_TXD_HT_AMPDU: This frame is part of an AMPDU. + * @ENTRY_TXD_HT_BW_40: Use 40MHz Bandwidth. + * @ENTRY_TXD_HT_SHORT_GI: Use short GI. + * @ENTRY_TXD_HT_MIMO_PS: The receiving STA is in dynamic SM PS mode. + */ +enum txentry_desc_flags { + ENTRY_TXD_RTS_FRAME, + ENTRY_TXD_CTS_FRAME, + ENTRY_TXD_GENERATE_SEQ, + ENTRY_TXD_FIRST_FRAGMENT, + ENTRY_TXD_MORE_FRAG, + ENTRY_TXD_REQ_TIMESTAMP, + ENTRY_TXD_BURST, + ENTRY_TXD_ACK, + ENTRY_TXD_RETRY_MODE, + ENTRY_TXD_ENCRYPT, + ENTRY_TXD_ENCRYPT_PAIRWISE, + ENTRY_TXD_ENCRYPT_IV, + ENTRY_TXD_ENCRYPT_MMIC, + ENTRY_TXD_HT_AMPDU, + ENTRY_TXD_HT_BW_40, + ENTRY_TXD_HT_SHORT_GI, + ENTRY_TXD_HT_MIMO_PS, +}; + +/** + * struct txentry_desc: TX Entry descriptor + * + * Summary of information for the frame descriptor before sending a TX frame. + * + * @flags: Descriptor flags (See &enum queue_entry_flags). + * @length: Length of the entire frame. + * @header_length: Length of 802.11 header. + * @length_high: PLCP length high word. + * @length_low: PLCP length low word. + * @signal: PLCP signal. + * @service: PLCP service. + * @msc: MCS. + * @stbc: Use Space Time Block Coding (only available for MCS rates < 8). + * @ba_size: Size of the recepients RX reorder buffer - 1. + * @rate_mode: Rate mode (See @enum rate_modulation). + * @mpdu_density: MDPU density. + * @retry_limit: Max number of retries. + * @ifs: IFS value. + * @txop: IFS value for 11n capable chips. + * @cipher: Cipher type used for encryption. + * @key_idx: Key index used for encryption. + * @iv_offset: Position where IV should be inserted by hardware. + * @iv_len: Length of IV data. + */ +struct txentry_desc { + unsigned long flags; + + u16 length; + u16 header_length; + + union { + struct { + u16 length_high; + u16 length_low; + u16 signal; + u16 service; + enum ifs ifs; + } plcp; + + struct { + u16 mcs; + u8 stbc; + u8 ba_size; + u8 mpdu_density; + enum txop txop; + int wcid; + } ht; + } u; + + enum rate_modulation rate_mode; + + short retry_limit; + + enum cipher cipher; + u16 key_idx; + u16 iv_offset; + u16 iv_len; +}; + +/** + * enum queue_entry_flags: Status flags for queue entry + * + * @ENTRY_BCN_ASSIGNED: This entry has been assigned to an interface. + * As long as this bit is set, this entry may only be touched + * through the interface structure. + * @ENTRY_OWNER_DEVICE_DATA: This entry is owned by the device for data + * transfer (either TX or RX depending on the queue). The entry should + * only be touched after the device has signaled it is done with it. + * @ENTRY_DATA_PENDING: This entry contains a valid frame and is waiting + * for the signal to start sending. + * @ENTRY_DATA_IO_FAILED: Hardware indicated that an IO error occurred + * while transferring the data to the hardware. No TX status report will + * be expected from the hardware. + * @ENTRY_DATA_STATUS_PENDING: The entry has been send to the device and + * returned. It is now waiting for the status reporting before the + * entry can be reused again. + */ +enum queue_entry_flags { + ENTRY_BCN_ASSIGNED, + ENTRY_OWNER_DEVICE_DATA, + ENTRY_DATA_PENDING, + ENTRY_DATA_IO_FAILED, + ENTRY_DATA_STATUS_PENDING, + ENTRY_DATA_STATUS_SET, +}; + +/** + * struct queue_entry: Entry inside the &struct data_queue + * + * @flags: Entry flags, see &enum queue_entry_flags. + * @last_action: Timestamp of last change. + * @queue: The data queue (&struct data_queue) to which this entry belongs. + * @skb: The buffer which is currently being transmitted (for TX queue), + * or used to directly receive data in (for RX queue). + * @entry_idx: The entry index number. + * @priv_data: Private data belonging to this queue entry. The pointer + * points to data specific to a particular driver and queue type. + * @status: Device specific status + */ +struct queue_entry { + unsigned long flags; + unsigned long last_action; + + struct data_queue *queue; + + struct sk_buff *skb; + + unsigned int entry_idx; + + u32 status; + + void *priv_data; +}; + +/** + * enum queue_index: Queue index type + * + * @Q_INDEX: Index pointer to the current entry in the queue, if this entry is + * owned by the hardware then the queue is considered to be full. + * @Q_INDEX_DMA_DONE: Index pointer for the next entry which will have been + * transferred to the hardware. + * @Q_INDEX_DONE: Index pointer to the next entry which will be completed by + * the hardware and for which we need to run the txdone handler. If this + * entry is not owned by the hardware the queue is considered to be empty. + * @Q_INDEX_MAX: Keep last, used in &struct data_queue to determine the size + * of the index array. + */ +enum queue_index { + Q_INDEX, + Q_INDEX_DMA_DONE, + Q_INDEX_DONE, + Q_INDEX_MAX, +}; + +/** + * enum data_queue_flags: Status flags for data queues + * + * @QUEUE_STARTED: The queue has been started. Fox RX queues this means the + * device might be DMA'ing skbuffers. TX queues will accept skbuffers to + * be transmitted and beacon queues will start beaconing the configured + * beacons. + * @QUEUE_PAUSED: The queue has been started but is currently paused. + * When this bit is set, the queue has been stopped in mac80211, + * preventing new frames to be enqueued. However, a few frames + * might still appear shortly after the pausing... + */ +enum data_queue_flags { + QUEUE_STARTED, + QUEUE_PAUSED, +}; + +/** + * struct data_queue: Data queue + * + * @rt2x00dev: Pointer to main &struct rt2x00dev where this queue belongs to. + * @entries: Base address of the &struct queue_entry which are + * part of this queue. + * @qid: The queue identification, see &enum data_queue_qid. + * @flags: Entry flags, see &enum queue_entry_flags. + * @status_lock: The mutex for protecting the start/stop/flush + * handling on this queue. + * @tx_lock: Spinlock to serialize tx operations on this queue. + * @index_lock: Spinlock to protect index handling. Whenever @index, @index_done or + * @index_crypt needs to be changed this lock should be grabbed to prevent + * index corruption due to concurrency. + * @count: Number of frames handled in the queue. + * @limit: Maximum number of entries in the queue. + * @threshold: Minimum number of free entries before queue is kicked by force. + * @length: Number of frames in queue. + * @index: Index pointers to entry positions in the queue, + * use &enum queue_index to get a specific index field. + * @txop: maximum burst time. + * @aifs: The aifs value for outgoing frames (field ignored in RX queue). + * @cw_min: The cw min value for outgoing frames (field ignored in RX queue). + * @cw_max: The cw max value for outgoing frames (field ignored in RX queue). + * @data_size: Maximum data size for the frames in this queue. + * @desc_size: Hardware descriptor size for the data in this queue. + * @usb_endpoint: Device endpoint used for communication (USB only) + * @usb_maxpacket: Max packet size for given endpoint (USB only) + */ +struct data_queue { + struct rt2x00_dev *rt2x00dev; + struct queue_entry *entries; + + enum data_queue_qid qid; + unsigned long flags; + + struct mutex status_lock; + spinlock_t tx_lock; + spinlock_t index_lock; + + unsigned int count; + unsigned short limit; + unsigned short threshold; + unsigned short length; + unsigned short index[Q_INDEX_MAX]; + + unsigned short txop; + unsigned short aifs; + unsigned short cw_min; + unsigned short cw_max; + + unsigned short data_size; + unsigned char desc_size; + unsigned char winfo_size; + + unsigned short usb_endpoint; + unsigned short usb_maxpacket; +}; + +/** + * struct data_queue_desc: Data queue description + * + * The information in this structure is used by drivers + * to inform rt2x00lib about the creation of the data queue. + * + * @entry_num: Maximum number of entries for a queue. + * @data_size: Maximum data size for the frames in this queue. + * @desc_size: Hardware descriptor size for the data in this queue. + * @priv_size: Size of per-queue_entry private data. + */ +struct data_queue_desc { + unsigned short entry_num; + unsigned short data_size; + unsigned char desc_size; + unsigned char winfo_size; + unsigned short priv_size; +}; + +/** + * queue_end - Return pointer to the last queue (HELPER MACRO). + * @__dev: Pointer to &struct rt2x00_dev + * + * Using the base rx pointer and the maximum number of available queues, + * this macro will return the address of 1 position beyond the end of the + * queues array. + */ +#define queue_end(__dev) \ + &(__dev)->rx[(__dev)->data_queues] + +/** + * tx_queue_end - Return pointer to the last TX queue (HELPER MACRO). + * @__dev: Pointer to &struct rt2x00_dev + * + * Using the base tx pointer and the maximum number of available TX + * queues, this macro will return the address of 1 position beyond + * the end of the TX queue array. + */ +#define tx_queue_end(__dev) \ + &(__dev)->tx[(__dev)->ops->tx_queues] + +/** + * queue_next - Return pointer to next queue in list (HELPER MACRO). + * @__queue: Current queue for which we need the next queue + * + * Using the current queue address we take the address directly + * after the queue to take the next queue. Note that this macro + * should be used carefully since it does not protect against + * moving past the end of the list. (See macros &queue_end and + * &tx_queue_end for determining the end of the queue). + */ +#define queue_next(__queue) \ + &(__queue)[1] + +/** + * queue_loop - Loop through the queues within a specific range (HELPER MACRO). + * @__entry: Pointer where the current queue entry will be stored in. + * @__start: Start queue pointer. + * @__end: End queue pointer. + * + * This macro will loop through all queues between &__start and &__end. + */ +#define queue_loop(__entry, __start, __end) \ + for ((__entry) = (__start); \ + prefetch(queue_next(__entry)), (__entry) != (__end);\ + (__entry) = queue_next(__entry)) + +/** + * queue_for_each - Loop through all queues + * @__dev: Pointer to &struct rt2x00_dev + * @__entry: Pointer where the current queue entry will be stored in. + * + * This macro will loop through all available queues. + */ +#define queue_for_each(__dev, __entry) \ + queue_loop(__entry, (__dev)->rx, queue_end(__dev)) + +/** + * tx_queue_for_each - Loop through the TX queues + * @__dev: Pointer to &struct rt2x00_dev + * @__entry: Pointer where the current queue entry will be stored in. + * + * This macro will loop through all TX related queues excluding + * the Beacon and Atim queues. + */ +#define tx_queue_for_each(__dev, __entry) \ + queue_loop(__entry, (__dev)->tx, tx_queue_end(__dev)) + +/** + * txall_queue_for_each - Loop through all TX related queues + * @__dev: Pointer to &struct rt2x00_dev + * @__entry: Pointer where the current queue entry will be stored in. + * + * This macro will loop through all TX related queues including + * the Beacon and Atim queues. + */ +#define txall_queue_for_each(__dev, __entry) \ + queue_loop(__entry, (__dev)->tx, queue_end(__dev)) + +/** + * rt2x00queue_for_each_entry - Loop through all entries in the queue + * @queue: Pointer to @data_queue + * @start: &enum queue_index Pointer to start index + * @end: &enum queue_index Pointer to end index + * @data: Data to pass to the callback function + * @fn: The function to call for each &struct queue_entry + * + * This will walk through all entries in the queue, in chronological + * order. This means it will start at the current @start pointer + * and will walk through the queue until it reaches the @end pointer. + * + * If fn returns true for an entry rt2x00queue_for_each_entry will stop + * processing and return true as well. + */ +bool rt2x00queue_for_each_entry(struct data_queue *queue, + enum queue_index start, + enum queue_index end, + void *data, + bool (*fn)(struct queue_entry *entry, + void *data)); + +/** + * rt2x00queue_empty - Check if the queue is empty. + * @queue: Queue to check if empty. + */ +static inline int rt2x00queue_empty(struct data_queue *queue) +{ + return queue->length == 0; +} + +/** + * rt2x00queue_full - Check if the queue is full. + * @queue: Queue to check if full. + */ +static inline int rt2x00queue_full(struct data_queue *queue) +{ + return queue->length == queue->limit; +} + +/** + * rt2x00queue_free - Check the number of available entries in queue. + * @queue: Queue to check. + */ +static inline int rt2x00queue_available(struct data_queue *queue) +{ + return queue->limit - queue->length; +} + +/** + * rt2x00queue_threshold - Check if the queue is below threshold + * @queue: Queue to check. + */ +static inline int rt2x00queue_threshold(struct data_queue *queue) +{ + return rt2x00queue_available(queue) < queue->threshold; +} +/** + * rt2x00queue_dma_timeout - Check if a timeout occurred for DMA transfers + * @entry: Queue entry to check. + */ +static inline int rt2x00queue_dma_timeout(struct queue_entry *entry) +{ + if (!test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags)) + return false; + return time_after(jiffies, entry->last_action + msecs_to_jiffies(100)); +} + +/** + * _rt2x00_desc_read - Read a word from the hardware descriptor. + * @desc: Base descriptor address + * @word: Word index from where the descriptor should be read. + * @value: Address where the descriptor value should be written into. + */ +static inline void _rt2x00_desc_read(__le32 *desc, const u8 word, __le32 *value) +{ + *value = desc[word]; +} + +/** + * rt2x00_desc_read - Read a word from the hardware descriptor, this + * function will take care of the byte ordering. + * @desc: Base descriptor address + * @word: Word index from where the descriptor should be read. + * @value: Address where the descriptor value should be written into. + */ +static inline void rt2x00_desc_read(__le32 *desc, const u8 word, u32 *value) +{ + __le32 tmp; + _rt2x00_desc_read(desc, word, &tmp); + *value = le32_to_cpu(tmp); +} + +/** + * rt2x00_desc_write - write a word to the hardware descriptor, this + * function will take care of the byte ordering. + * @desc: Base descriptor address + * @word: Word index from where the descriptor should be written. + * @value: Value that should be written into the descriptor. + */ +static inline void _rt2x00_desc_write(__le32 *desc, const u8 word, __le32 value) +{ + desc[word] = value; +} + +/** + * rt2x00_desc_write - write a word to the hardware descriptor. + * @desc: Base descriptor address + * @word: Word index from where the descriptor should be written. + * @value: Value that should be written into the descriptor. + */ +static inline void rt2x00_desc_write(__le32 *desc, const u8 word, u32 value) +{ + _rt2x00_desc_write(desc, word, cpu_to_le32(value)); +} + +#endif /* RT2X00QUEUE_H */ diff --git a/drivers/net/wireless/rt2x00/rt2x00reg.h b/drivers/net/wireless/rt2x00/rt2x00reg.h new file mode 100644 index 0000000..6f867ee --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00reg.h @@ -0,0 +1,279 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00 + Abstract: rt2x00 generic register information. + */ + +#ifndef RT2X00REG_H +#define RT2X00REG_H + +/* + * RX crypto status + */ +enum rx_crypto { + RX_CRYPTO_SUCCESS = 0, + RX_CRYPTO_FAIL_ICV = 1, + RX_CRYPTO_FAIL_MIC = 2, + RX_CRYPTO_FAIL_KEY = 3, +}; + +/* + * Antenna values + */ +enum antenna { + ANTENNA_SW_DIVERSITY = 0, + ANTENNA_A = 1, + ANTENNA_B = 2, + ANTENNA_HW_DIVERSITY = 3, +}; + +/* + * Led mode values. + */ +enum led_mode { + LED_MODE_DEFAULT = 0, + LED_MODE_TXRX_ACTIVITY = 1, + LED_MODE_SIGNAL_STRENGTH = 2, + LED_MODE_ASUS = 3, + LED_MODE_ALPHA = 4, +}; + +/* + * TSF sync values + */ +enum tsf_sync { + TSF_SYNC_NONE = 0, + TSF_SYNC_INFRA = 1, + TSF_SYNC_ADHOC = 2, + TSF_SYNC_AP_NONE = 3, +}; + +/* + * Device states + */ +enum dev_state { + STATE_DEEP_SLEEP = 0, + STATE_SLEEP = 1, + STATE_STANDBY = 2, + STATE_AWAKE = 3, + +/* + * Additional device states, these values are + * not strict since they are not directly passed + * into the device. + */ + STATE_RADIO_ON, + STATE_RADIO_OFF, + STATE_RADIO_IRQ_ON, + STATE_RADIO_IRQ_OFF, +}; + +/* + * IFS backoff values + */ +enum ifs { + IFS_BACKOFF = 0, + IFS_SIFS = 1, + IFS_NEW_BACKOFF = 2, + IFS_NONE = 3, +}; + +/* + * IFS backoff values for HT devices + */ +enum txop { + TXOP_HTTXOP = 0, + TXOP_PIFS = 1, + TXOP_SIFS = 2, + TXOP_BACKOFF = 3, +}; + +/* + * Cipher types for hardware encryption + */ +enum cipher { + CIPHER_NONE = 0, + CIPHER_WEP64 = 1, + CIPHER_WEP128 = 2, + CIPHER_TKIP = 3, + CIPHER_AES = 4, +/* + * The following fields were added by rt61pci and rt73usb. + */ + CIPHER_CKIP64 = 5, + CIPHER_CKIP128 = 6, + CIPHER_TKIP_NO_MIC = 7, /* Don't send to device */ + +/* + * Max cipher type. + * Note that CIPHER_NONE isn't counted, and CKIP64 and CKIP128 + * are excluded due to limitations in mac80211. + */ + CIPHER_MAX = 4, +}; + +/* + * Rate modulations + */ +enum rate_modulation { + RATE_MODE_CCK = 0, + RATE_MODE_OFDM = 1, + RATE_MODE_HT_MIX = 2, + RATE_MODE_HT_GREENFIELD = 3, +}; + +/* + * Firmware validation error codes + */ +enum firmware_errors { + FW_OK, + FW_BAD_CRC, + FW_BAD_LENGTH, + FW_BAD_VERSION, +}; + +/* + * Register handlers. + * We store the position of a register field inside a field structure, + * This will simplify the process of setting and reading a certain field + * inside the register while making sure the process remains byte order safe. + */ +struct rt2x00_field8 { + u8 bit_offset; + u8 bit_mask; +}; + +struct rt2x00_field16 { + u16 bit_offset; + u16 bit_mask; +}; + +struct rt2x00_field32 { + u32 bit_offset; + u32 bit_mask; +}; + +/* + * Power of two check, this will check + * if the mask that has been given contains and contiguous set of bits. + * Note that we cannot use the is_power_of_2() function since this + * check must be done at compile-time. + */ +#define is_power_of_two(x) ( !((x) & ((x)-1)) ) +#define low_bit_mask(x) ( ((x)-1) & ~(x) ) +#define is_valid_mask(x) is_power_of_two(1LU + (x) + low_bit_mask(x)) + +/* + * Macros to find first set bit in a variable. + * These macros behave the same as the __ffs() functions but + * the most important difference that this is done during + * compile-time rather then run-time. + */ +#define compile_ffs2(__x) \ + __builtin_choose_expr(((__x) & 0x1), 0, 1) + +#define compile_ffs4(__x) \ + __builtin_choose_expr(((__x) & 0x3), \ + (compile_ffs2((__x))), \ + (compile_ffs2((__x) >> 2) + 2)) + +#define compile_ffs8(__x) \ + __builtin_choose_expr(((__x) & 0xf), \ + (compile_ffs4((__x))), \ + (compile_ffs4((__x) >> 4) + 4)) + +#define compile_ffs16(__x) \ + __builtin_choose_expr(((__x) & 0xff), \ + (compile_ffs8((__x))), \ + (compile_ffs8((__x) >> 8) + 8)) + +#define compile_ffs32(__x) \ + __builtin_choose_expr(((__x) & 0xffff), \ + (compile_ffs16((__x))), \ + (compile_ffs16((__x) >> 16) + 16)) + +/* + * This macro will check the requirements for the FIELD{8,16,32} macros + * The mask should be a constant non-zero contiguous set of bits which + * does not exceed the given typelimit. + */ +#define FIELD_CHECK(__mask, __type) \ + BUILD_BUG_ON(!(__mask) || \ + !is_valid_mask(__mask) || \ + (__mask) != (__type)(__mask)) \ + +#define FIELD8(__mask) \ +({ \ + FIELD_CHECK(__mask, u8); \ + (struct rt2x00_field8) { \ + compile_ffs8(__mask), (__mask) \ + }; \ +}) + +#define FIELD16(__mask) \ +({ \ + FIELD_CHECK(__mask, u16); \ + (struct rt2x00_field16) { \ + compile_ffs16(__mask), (__mask) \ + }; \ +}) + +#define FIELD32(__mask) \ +({ \ + FIELD_CHECK(__mask, u32); \ + (struct rt2x00_field32) { \ + compile_ffs32(__mask), (__mask) \ + }; \ +}) + +#define SET_FIELD(__reg, __type, __field, __value)\ +({ \ + typecheck(__type, __field); \ + *(__reg) &= ~((__field).bit_mask); \ + *(__reg) |= ((__value) << \ + ((__field).bit_offset)) & \ + ((__field).bit_mask); \ +}) + +#define GET_FIELD(__reg, __type, __field) \ +({ \ + typecheck(__type, __field); \ + ((__reg) & ((__field).bit_mask)) >> \ + ((__field).bit_offset); \ +}) + +#define rt2x00_set_field32(__reg, __field, __value) \ + SET_FIELD(__reg, struct rt2x00_field32, __field, __value) +#define rt2x00_get_field32(__reg, __field) \ + GET_FIELD(__reg, struct rt2x00_field32, __field) + +#define rt2x00_set_field16(__reg, __field, __value) \ + SET_FIELD(__reg, struct rt2x00_field16, __field, __value) +#define rt2x00_get_field16(__reg, __field) \ + GET_FIELD(__reg, struct rt2x00_field16, __field) + +#define rt2x00_set_field8(__reg, __field, __value) \ + SET_FIELD(__reg, struct rt2x00_field8, __field, __value) +#define rt2x00_get_field8(__reg, __field) \ + GET_FIELD(__reg, struct rt2x00_field8, __field) + +#endif /* RT2X00REG_H */ diff --git a/drivers/net/wireless/rt2x00/rt2x00soc.c b/drivers/net/wireless/rt2x00/rt2x00soc.c new file mode 100644 index 0000000..9271a5f --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00soc.c @@ -0,0 +1,162 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + Copyright (C) 2004 - 2009 Felix Fietkau + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00soc + Abstract: rt2x00 generic soc device routines. + */ + +#include +#include +#include +#include +#include + +#include "rt2x00.h" +#include "rt2x00soc.h" + +static void rt2x00soc_free_reg(struct rt2x00_dev *rt2x00dev) +{ + kfree(rt2x00dev->rf); + rt2x00dev->rf = NULL; + + kfree(rt2x00dev->eeprom); + rt2x00dev->eeprom = NULL; + + iounmap(rt2x00dev->csr.base); +} + +static int rt2x00soc_alloc_reg(struct rt2x00_dev *rt2x00dev) +{ + struct platform_device *pdev = to_platform_device(rt2x00dev->dev); + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + rt2x00dev->csr.base = ioremap(res->start, resource_size(res)); + if (!rt2x00dev->csr.base) + return -ENOMEM; + + rt2x00dev->eeprom = kzalloc(rt2x00dev->ops->eeprom_size, GFP_KERNEL); + if (!rt2x00dev->eeprom) + goto exit; + + rt2x00dev->rf = kzalloc(rt2x00dev->ops->rf_size, GFP_KERNEL); + if (!rt2x00dev->rf) + goto exit; + + return 0; + +exit: + rt2x00_probe_err("Failed to allocate registers\n"); + rt2x00soc_free_reg(rt2x00dev); + + return -ENOMEM; +} + +int rt2x00soc_probe(struct platform_device *pdev, const struct rt2x00_ops *ops) +{ + struct ieee80211_hw *hw; + struct rt2x00_dev *rt2x00dev; + int retval; + + hw = ieee80211_alloc_hw(sizeof(struct rt2x00_dev), ops->hw); + if (!hw) { + rt2x00_probe_err("Failed to allocate hardware\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, hw); + + rt2x00dev = hw->priv; + rt2x00dev->dev = &pdev->dev; + rt2x00dev->ops = ops; + rt2x00dev->hw = hw; + rt2x00dev->irq = platform_get_irq(pdev, 0); + rt2x00dev->name = pdev->dev.driver->name; + + rt2x00_set_chip_intf(rt2x00dev, RT2X00_CHIP_INTF_SOC); + + retval = rt2x00soc_alloc_reg(rt2x00dev); + if (retval) + goto exit_free_device; + + retval = rt2x00lib_probe_dev(rt2x00dev); + if (retval) + goto exit_free_reg; + + return 0; + +exit_free_reg: + rt2x00soc_free_reg(rt2x00dev); + +exit_free_device: + ieee80211_free_hw(hw); + + return retval; +} +EXPORT_SYMBOL_GPL(rt2x00soc_probe); + +int rt2x00soc_remove(struct platform_device *pdev) +{ + struct ieee80211_hw *hw = platform_get_drvdata(pdev); + struct rt2x00_dev *rt2x00dev = hw->priv; + + /* + * Free all allocated data. + */ + rt2x00lib_remove_dev(rt2x00dev); + rt2x00soc_free_reg(rt2x00dev); + ieee80211_free_hw(hw); + + return 0; +} +EXPORT_SYMBOL_GPL(rt2x00soc_remove); + +#ifdef CONFIG_PM +int rt2x00soc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct ieee80211_hw *hw = platform_get_drvdata(pdev); + struct rt2x00_dev *rt2x00dev = hw->priv; + + return rt2x00lib_suspend(rt2x00dev, state); +} +EXPORT_SYMBOL_GPL(rt2x00soc_suspend); + +int rt2x00soc_resume(struct platform_device *pdev) +{ + struct ieee80211_hw *hw = platform_get_drvdata(pdev); + struct rt2x00_dev *rt2x00dev = hw->priv; + + return rt2x00lib_resume(rt2x00dev); +} +EXPORT_SYMBOL_GPL(rt2x00soc_resume); +#endif /* CONFIG_PM */ + +/* + * rt2x00soc module information. + */ +MODULE_AUTHOR(DRV_PROJECT); +MODULE_VERSION(DRV_VERSION); +MODULE_DESCRIPTION("rt2x00 soc library"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/rt2x00/rt2x00soc.h b/drivers/net/wireless/rt2x00/rt2x00soc.h new file mode 100644 index 0000000..474cbfc --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00soc.h @@ -0,0 +1,42 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00soc + Abstract: Data structures for the rt2x00soc module. + */ + +#ifndef RT2X00SOC_H +#define RT2X00SOC_H + +/* + * SoC driver handlers. + */ +int rt2x00soc_probe(struct platform_device *pdev, const struct rt2x00_ops *ops); +int rt2x00soc_remove(struct platform_device *pdev); +#ifdef CONFIG_PM +int rt2x00soc_suspend(struct platform_device *pdev, pm_message_t state); +int rt2x00soc_resume(struct platform_device *pdev); +#else +#define rt2x00soc_suspend NULL +#define rt2x00soc_resume NULL +#endif /* CONFIG_PM */ + +#endif /* RT2X00SOC_H */ diff --git a/drivers/net/wireless/rt2x00/rt2x00usb.c b/drivers/net/wireless/rt2x00/rt2x00usb.c new file mode 100644 index 0000000..8828987 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00usb.c @@ -0,0 +1,888 @@ +/* + Copyright (C) 2010 Willow Garage + Copyright (C) 2004 - 2010 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00usb + Abstract: rt2x00 generic usb device routines. + */ + +#include +#include +#include +#include +#include + +#include "rt2x00.h" +#include "rt2x00usb.h" + +/* + * Interfacing with the HW. + */ +int rt2x00usb_vendor_request(struct rt2x00_dev *rt2x00dev, + const u8 request, const u8 requesttype, + const u16 offset, const u16 value, + void *buffer, const u16 buffer_length, + const int timeout) +{ + struct usb_device *usb_dev = to_usb_device_intf(rt2x00dev->dev); + int status; + unsigned int i; + unsigned int pipe = + (requesttype == USB_VENDOR_REQUEST_IN) ? + usb_rcvctrlpipe(usb_dev, 0) : usb_sndctrlpipe(usb_dev, 0); + + if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + return -ENODEV; + + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + status = usb_control_msg(usb_dev, pipe, request, requesttype, + value, offset, buffer, buffer_length, + timeout); + if (status >= 0) + return 0; + + /* + * Check for errors + * -ENODEV: Device has disappeared, no point continuing. + * All other errors: Try again. + */ + else if (status == -ENODEV) { + clear_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags); + break; + } + } + + rt2x00_err(rt2x00dev, + "Vendor Request 0x%02x failed for offset 0x%04x with error %d\n", + request, offset, status); + + return status; +} +EXPORT_SYMBOL_GPL(rt2x00usb_vendor_request); + +int rt2x00usb_vendor_req_buff_lock(struct rt2x00_dev *rt2x00dev, + const u8 request, const u8 requesttype, + const u16 offset, void *buffer, + const u16 buffer_length, const int timeout) +{ + int status; + + BUG_ON(!mutex_is_locked(&rt2x00dev->csr_mutex)); + + /* + * Check for Cache availability. + */ + if (unlikely(!rt2x00dev->csr.cache || buffer_length > CSR_CACHE_SIZE)) { + rt2x00_err(rt2x00dev, "CSR cache not available\n"); + return -ENOMEM; + } + + if (requesttype == USB_VENDOR_REQUEST_OUT) + memcpy(rt2x00dev->csr.cache, buffer, buffer_length); + + status = rt2x00usb_vendor_request(rt2x00dev, request, requesttype, + offset, 0, rt2x00dev->csr.cache, + buffer_length, timeout); + + if (!status && requesttype == USB_VENDOR_REQUEST_IN) + memcpy(buffer, rt2x00dev->csr.cache, buffer_length); + + return status; +} +EXPORT_SYMBOL_GPL(rt2x00usb_vendor_req_buff_lock); + +int rt2x00usb_vendor_request_buff(struct rt2x00_dev *rt2x00dev, + const u8 request, const u8 requesttype, + const u16 offset, void *buffer, + const u16 buffer_length, const int timeout) +{ + int status = 0; + unsigned char *tb; + u16 off, len, bsize; + + mutex_lock(&rt2x00dev->csr_mutex); + + tb = (char *)buffer; + off = offset; + len = buffer_length; + while (len && !status) { + bsize = min_t(u16, CSR_CACHE_SIZE, len); + status = rt2x00usb_vendor_req_buff_lock(rt2x00dev, request, + requesttype, off, tb, + bsize, timeout); + + tb += bsize; + len -= bsize; + off += bsize; + } + + mutex_unlock(&rt2x00dev->csr_mutex); + + return status; +} +EXPORT_SYMBOL_GPL(rt2x00usb_vendor_request_buff); + +int rt2x00usb_regbusy_read(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + const struct rt2x00_field32 field, + u32 *reg) +{ + unsigned int i; + + if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags)) + return -ENODEV; + + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2x00usb_register_read_lock(rt2x00dev, offset, reg); + if (!rt2x00_get_field32(*reg, field)) + return 1; + udelay(REGISTER_BUSY_DELAY); + } + + rt2x00_err(rt2x00dev, "Indirect register access failed: offset=0x%.08x, value=0x%.08x\n", + offset, *reg); + *reg = ~0; + + return 0; +} +EXPORT_SYMBOL_GPL(rt2x00usb_regbusy_read); + + +struct rt2x00_async_read_data { + __le32 reg; + struct usb_ctrlrequest cr; + struct rt2x00_dev *rt2x00dev; + bool (*callback)(struct rt2x00_dev *, int, u32); +}; + +static void rt2x00usb_register_read_async_cb(struct urb *urb) +{ + struct rt2x00_async_read_data *rd = urb->context; + if (rd->callback(rd->rt2x00dev, urb->status, le32_to_cpu(rd->reg))) { + if (usb_submit_urb(urb, GFP_ATOMIC) < 0) + kfree(rd); + } else + kfree(rd); +} + +void rt2x00usb_register_read_async(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + bool (*callback)(struct rt2x00_dev*, int, u32)) +{ + struct usb_device *usb_dev = to_usb_device_intf(rt2x00dev->dev); + struct urb *urb; + struct rt2x00_async_read_data *rd; + + rd = kmalloc(sizeof(*rd), GFP_ATOMIC); + if (!rd) + return; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + kfree(rd); + return; + } + + rd->rt2x00dev = rt2x00dev; + rd->callback = callback; + rd->cr.bRequestType = USB_VENDOR_REQUEST_IN; + rd->cr.bRequest = USB_MULTI_READ; + rd->cr.wValue = 0; + rd->cr.wIndex = cpu_to_le16(offset); + rd->cr.wLength = cpu_to_le16(sizeof(u32)); + + usb_fill_control_urb(urb, usb_dev, usb_rcvctrlpipe(usb_dev, 0), + (unsigned char *)(&rd->cr), &rd->reg, sizeof(rd->reg), + rt2x00usb_register_read_async_cb, rd); + if (usb_submit_urb(urb, GFP_ATOMIC) < 0) + kfree(rd); + usb_free_urb(urb); +} +EXPORT_SYMBOL_GPL(rt2x00usb_register_read_async); + +/* + * TX data handlers. + */ +static void rt2x00usb_work_txdone_entry(struct queue_entry *entry) +{ + /* + * If the transfer to hardware succeeded, it does not mean the + * frame was send out correctly. It only means the frame + * was successfully pushed to the hardware, we have no + * way to determine the transmission status right now. + * (Only indirectly by looking at the failed TX counters + * in the register). + */ + if (test_bit(ENTRY_DATA_IO_FAILED, &entry->flags)) + rt2x00lib_txdone_noinfo(entry, TXDONE_FAILURE); + else + rt2x00lib_txdone_noinfo(entry, TXDONE_UNKNOWN); +} + +static void rt2x00usb_work_txdone(struct work_struct *work) +{ + struct rt2x00_dev *rt2x00dev = + container_of(work, struct rt2x00_dev, txdone_work); + struct data_queue *queue; + struct queue_entry *entry; + + tx_queue_for_each(rt2x00dev, queue) { + while (!rt2x00queue_empty(queue)) { + entry = rt2x00queue_get_entry(queue, Q_INDEX_DONE); + + if (test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags) || + !test_bit(ENTRY_DATA_STATUS_PENDING, &entry->flags)) + break; + + rt2x00usb_work_txdone_entry(entry); + } + } +} + +static void rt2x00usb_interrupt_txdone(struct urb *urb) +{ + struct queue_entry *entry = (struct queue_entry *)urb->context; + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + + if (!test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags)) + return; + /* + * Check if the frame was correctly uploaded + */ + if (urb->status) + set_bit(ENTRY_DATA_IO_FAILED, &entry->flags); + /* + * Report the frame as DMA done + */ + rt2x00lib_dmadone(entry); + + if (rt2x00dev->ops->lib->tx_dma_done) + rt2x00dev->ops->lib->tx_dma_done(entry); + /* + * Schedule the delayed work for reading the TX status + * from the device. + */ + if (!test_bit(REQUIRE_TXSTATUS_FIFO, &rt2x00dev->cap_flags) || + !kfifo_is_empty(&rt2x00dev->txstatus_fifo)) + queue_work(rt2x00dev->workqueue, &rt2x00dev->txdone_work); +} + +static bool rt2x00usb_kick_tx_entry(struct queue_entry *entry, void *data) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + struct usb_device *usb_dev = to_usb_device_intf(rt2x00dev->dev); + struct queue_entry_priv_usb *entry_priv = entry->priv_data; + u32 length; + int status; + + if (!test_and_clear_bit(ENTRY_DATA_PENDING, &entry->flags) || + test_bit(ENTRY_DATA_STATUS_PENDING, &entry->flags)) + return false; + + /* + * USB devices require certain padding at the end of each frame + * and urb. Those paddings are not included in skbs. Pass entry + * to the driver to determine what the overall length should be. + */ + length = rt2x00dev->ops->lib->get_tx_data_len(entry); + + status = skb_padto(entry->skb, length); + if (unlikely(status)) { + /* TODO: report something more appropriate than IO_FAILED. */ + rt2x00_warn(rt2x00dev, "TX SKB padding error, out of memory\n"); + set_bit(ENTRY_DATA_IO_FAILED, &entry->flags); + rt2x00lib_dmadone(entry); + + return false; + } + + usb_fill_bulk_urb(entry_priv->urb, usb_dev, + usb_sndbulkpipe(usb_dev, entry->queue->usb_endpoint), + entry->skb->data, length, + rt2x00usb_interrupt_txdone, entry); + + status = usb_submit_urb(entry_priv->urb, GFP_ATOMIC); + if (status) { + if (status == -ENODEV) + clear_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags); + set_bit(ENTRY_DATA_IO_FAILED, &entry->flags); + rt2x00lib_dmadone(entry); + } + + return false; +} + +/* + * RX data handlers. + */ +static void rt2x00usb_work_rxdone(struct work_struct *work) +{ + struct rt2x00_dev *rt2x00dev = + container_of(work, struct rt2x00_dev, rxdone_work); + struct queue_entry *entry; + struct skb_frame_desc *skbdesc; + u8 rxd[32]; + + while (!rt2x00queue_empty(rt2x00dev->rx)) { + entry = rt2x00queue_get_entry(rt2x00dev->rx, Q_INDEX_DONE); + + if (test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags) || + !test_bit(ENTRY_DATA_STATUS_PENDING, &entry->flags)) + break; + + /* + * Fill in desc fields of the skb descriptor + */ + skbdesc = get_skb_frame_desc(entry->skb); + skbdesc->desc = rxd; + skbdesc->desc_len = entry->queue->desc_size; + + /* + * Send the frame to rt2x00lib for further processing. + */ + rt2x00lib_rxdone(entry, GFP_KERNEL); + } +} + +static void rt2x00usb_interrupt_rxdone(struct urb *urb) +{ + struct queue_entry *entry = (struct queue_entry *)urb->context; + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + + if (!test_and_clear_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags)) + return; + + /* + * Report the frame as DMA done + */ + rt2x00lib_dmadone(entry); + + /* + * Check if the received data is simply too small + * to be actually valid, or if the urb is signaling + * a problem. + */ + if (urb->actual_length < entry->queue->desc_size || urb->status) + set_bit(ENTRY_DATA_IO_FAILED, &entry->flags); + + /* + * Schedule the delayed work for reading the RX status + * from the device. + */ + queue_work(rt2x00dev->workqueue, &rt2x00dev->rxdone_work); +} + +static bool rt2x00usb_kick_rx_entry(struct queue_entry *entry, void *data) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + struct usb_device *usb_dev = to_usb_device_intf(rt2x00dev->dev); + struct queue_entry_priv_usb *entry_priv = entry->priv_data; + int status; + + if (test_and_set_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags) || + test_bit(ENTRY_DATA_STATUS_PENDING, &entry->flags)) + return false; + + rt2x00lib_dmastart(entry); + + usb_fill_bulk_urb(entry_priv->urb, usb_dev, + usb_rcvbulkpipe(usb_dev, entry->queue->usb_endpoint), + entry->skb->data, entry->skb->len, + rt2x00usb_interrupt_rxdone, entry); + + status = usb_submit_urb(entry_priv->urb, GFP_ATOMIC); + if (status) { + if (status == -ENODEV) + clear_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags); + set_bit(ENTRY_DATA_IO_FAILED, &entry->flags); + rt2x00lib_dmadone(entry); + } + + return false; +} + +void rt2x00usb_kick_queue(struct data_queue *queue) +{ + switch (queue->qid) { + case QID_AC_VO: + case QID_AC_VI: + case QID_AC_BE: + case QID_AC_BK: + if (!rt2x00queue_empty(queue)) + rt2x00queue_for_each_entry(queue, + Q_INDEX_DONE, + Q_INDEX, + NULL, + rt2x00usb_kick_tx_entry); + break; + case QID_RX: + if (!rt2x00queue_full(queue)) + rt2x00queue_for_each_entry(queue, + Q_INDEX, + Q_INDEX_DONE, + NULL, + rt2x00usb_kick_rx_entry); + break; + default: + break; + } +} +EXPORT_SYMBOL_GPL(rt2x00usb_kick_queue); + +static bool rt2x00usb_flush_entry(struct queue_entry *entry, void *data) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + struct queue_entry_priv_usb *entry_priv = entry->priv_data; + struct queue_entry_priv_usb_bcn *bcn_priv = entry->priv_data; + + if (!test_bit(ENTRY_OWNER_DEVICE_DATA, &entry->flags)) + return false; + + usb_kill_urb(entry_priv->urb); + + /* + * Kill guardian urb (if required by driver). + */ + if ((entry->queue->qid == QID_BEACON) && + (test_bit(REQUIRE_BEACON_GUARD, &rt2x00dev->cap_flags))) + usb_kill_urb(bcn_priv->guardian_urb); + + return false; +} + +void rt2x00usb_flush_queue(struct data_queue *queue, bool drop) +{ + struct work_struct *completion; + unsigned int i; + + if (drop) + rt2x00queue_for_each_entry(queue, Q_INDEX_DONE, Q_INDEX, NULL, + rt2x00usb_flush_entry); + + /* + * Obtain the queue completion handler + */ + switch (queue->qid) { + case QID_AC_VO: + case QID_AC_VI: + case QID_AC_BE: + case QID_AC_BK: + completion = &queue->rt2x00dev->txdone_work; + break; + case QID_RX: + completion = &queue->rt2x00dev->rxdone_work; + break; + default: + return; + } + + for (i = 0; i < 10; i++) { + /* + * Check if the driver is already done, otherwise we + * have to sleep a little while to give the driver/hw + * the oppurtunity to complete interrupt process itself. + */ + if (rt2x00queue_empty(queue)) + break; + + /* + * Schedule the completion handler manually, when this + * worker function runs, it should cleanup the queue. + */ + queue_work(queue->rt2x00dev->workqueue, completion); + + /* + * Wait for a little while to give the driver + * the oppurtunity to recover itself. + */ + msleep(10); + } +} +EXPORT_SYMBOL_GPL(rt2x00usb_flush_queue); + +static void rt2x00usb_watchdog_tx_dma(struct data_queue *queue) +{ + rt2x00_warn(queue->rt2x00dev, "TX queue %d DMA timed out, invoke forced forced reset\n", + queue->qid); + + rt2x00queue_flush_queue(queue, true); +} + +static int rt2x00usb_dma_timeout(struct data_queue *queue) +{ + struct queue_entry *entry; + + entry = rt2x00queue_get_entry(queue, Q_INDEX_DMA_DONE); + return rt2x00queue_dma_timeout(entry); +} + +void rt2x00usb_watchdog(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + + tx_queue_for_each(rt2x00dev, queue) { + if (!rt2x00queue_empty(queue)) { + if (rt2x00usb_dma_timeout(queue)) + rt2x00usb_watchdog_tx_dma(queue); + } + } +} +EXPORT_SYMBOL_GPL(rt2x00usb_watchdog); + +/* + * Radio handlers + */ +void rt2x00usb_disable_radio(struct rt2x00_dev *rt2x00dev) +{ + rt2x00usb_vendor_request_sw(rt2x00dev, USB_RX_CONTROL, 0, 0, + REGISTER_TIMEOUT); +} +EXPORT_SYMBOL_GPL(rt2x00usb_disable_radio); + +/* + * Device initialization handlers. + */ +void rt2x00usb_clear_entry(struct queue_entry *entry) +{ + entry->flags = 0; + + if (entry->queue->qid == QID_RX) + rt2x00usb_kick_rx_entry(entry, NULL); +} +EXPORT_SYMBOL_GPL(rt2x00usb_clear_entry); + +static void rt2x00usb_assign_endpoint(struct data_queue *queue, + struct usb_endpoint_descriptor *ep_desc) +{ + struct usb_device *usb_dev = to_usb_device_intf(queue->rt2x00dev->dev); + int pipe; + + queue->usb_endpoint = usb_endpoint_num(ep_desc); + + if (queue->qid == QID_RX) { + pipe = usb_rcvbulkpipe(usb_dev, queue->usb_endpoint); + queue->usb_maxpacket = usb_maxpacket(usb_dev, pipe, 0); + } else { + pipe = usb_sndbulkpipe(usb_dev, queue->usb_endpoint); + queue->usb_maxpacket = usb_maxpacket(usb_dev, pipe, 1); + } + + if (!queue->usb_maxpacket) + queue->usb_maxpacket = 1; +} + +static int rt2x00usb_find_endpoints(struct rt2x00_dev *rt2x00dev) +{ + struct usb_interface *intf = to_usb_interface(rt2x00dev->dev); + struct usb_host_interface *intf_desc = intf->cur_altsetting; + struct usb_endpoint_descriptor *ep_desc; + struct data_queue *queue = rt2x00dev->tx; + struct usb_endpoint_descriptor *tx_ep_desc = NULL; + unsigned int i; + + /* + * Walk through all available endpoints to search for "bulk in" + * and "bulk out" endpoints. When we find such endpoints collect + * the information we need from the descriptor and assign it + * to the queue. + */ + for (i = 0; i < intf_desc->desc.bNumEndpoints; i++) { + ep_desc = &intf_desc->endpoint[i].desc; + + if (usb_endpoint_is_bulk_in(ep_desc)) { + rt2x00usb_assign_endpoint(rt2x00dev->rx, ep_desc); + } else if (usb_endpoint_is_bulk_out(ep_desc) && + (queue != queue_end(rt2x00dev))) { + rt2x00usb_assign_endpoint(queue, ep_desc); + queue = queue_next(queue); + + tx_ep_desc = ep_desc; + } + } + + /* + * At least 1 endpoint for RX and 1 endpoint for TX must be available. + */ + if (!rt2x00dev->rx->usb_endpoint || !rt2x00dev->tx->usb_endpoint) { + rt2x00_err(rt2x00dev, "Bulk-in/Bulk-out endpoints not found\n"); + return -EPIPE; + } + + /* + * It might be possible not all queues have a dedicated endpoint. + * Loop through all TX queues and copy the endpoint information + * which we have gathered from already assigned endpoints. + */ + txall_queue_for_each(rt2x00dev, queue) { + if (!queue->usb_endpoint) + rt2x00usb_assign_endpoint(queue, tx_ep_desc); + } + + return 0; +} + +static int rt2x00usb_alloc_entries(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + struct queue_entry_priv_usb *entry_priv; + struct queue_entry_priv_usb_bcn *bcn_priv; + unsigned int i; + + for (i = 0; i < queue->limit; i++) { + entry_priv = queue->entries[i].priv_data; + entry_priv->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!entry_priv->urb) + return -ENOMEM; + } + + /* + * If this is not the beacon queue or + * no guardian byte was required for the beacon, + * then we are done. + */ + if (queue->qid != QID_BEACON || + !test_bit(REQUIRE_BEACON_GUARD, &rt2x00dev->cap_flags)) + return 0; + + for (i = 0; i < queue->limit; i++) { + bcn_priv = queue->entries[i].priv_data; + bcn_priv->guardian_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!bcn_priv->guardian_urb) + return -ENOMEM; + } + + return 0; +} + +static void rt2x00usb_free_entries(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + struct queue_entry_priv_usb *entry_priv; + struct queue_entry_priv_usb_bcn *bcn_priv; + unsigned int i; + + if (!queue->entries) + return; + + for (i = 0; i < queue->limit; i++) { + entry_priv = queue->entries[i].priv_data; + usb_kill_urb(entry_priv->urb); + usb_free_urb(entry_priv->urb); + } + + /* + * If this is not the beacon queue or + * no guardian byte was required for the beacon, + * then we are done. + */ + if (queue->qid != QID_BEACON || + !test_bit(REQUIRE_BEACON_GUARD, &rt2x00dev->cap_flags)) + return; + + for (i = 0; i < queue->limit; i++) { + bcn_priv = queue->entries[i].priv_data; + usb_kill_urb(bcn_priv->guardian_urb); + usb_free_urb(bcn_priv->guardian_urb); + } +} + +int rt2x00usb_initialize(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + int status; + + /* + * Find endpoints for each queue + */ + status = rt2x00usb_find_endpoints(rt2x00dev); + if (status) + goto exit; + + /* + * Allocate DMA + */ + queue_for_each(rt2x00dev, queue) { + status = rt2x00usb_alloc_entries(queue); + if (status) + goto exit; + } + + return 0; + +exit: + rt2x00usb_uninitialize(rt2x00dev); + + return status; +} +EXPORT_SYMBOL_GPL(rt2x00usb_initialize); + +void rt2x00usb_uninitialize(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + + queue_for_each(rt2x00dev, queue) + rt2x00usb_free_entries(queue); +} +EXPORT_SYMBOL_GPL(rt2x00usb_uninitialize); + +/* + * USB driver handlers. + */ +static void rt2x00usb_free_reg(struct rt2x00_dev *rt2x00dev) +{ + kfree(rt2x00dev->rf); + rt2x00dev->rf = NULL; + + kfree(rt2x00dev->eeprom); + rt2x00dev->eeprom = NULL; + + kfree(rt2x00dev->csr.cache); + rt2x00dev->csr.cache = NULL; +} + +static int rt2x00usb_alloc_reg(struct rt2x00_dev *rt2x00dev) +{ + rt2x00dev->csr.cache = kzalloc(CSR_CACHE_SIZE, GFP_KERNEL); + if (!rt2x00dev->csr.cache) + goto exit; + + rt2x00dev->eeprom = kzalloc(rt2x00dev->ops->eeprom_size, GFP_KERNEL); + if (!rt2x00dev->eeprom) + goto exit; + + rt2x00dev->rf = kzalloc(rt2x00dev->ops->rf_size, GFP_KERNEL); + if (!rt2x00dev->rf) + goto exit; + + return 0; + +exit: + rt2x00_probe_err("Failed to allocate registers\n"); + + rt2x00usb_free_reg(rt2x00dev); + + return -ENOMEM; +} + +int rt2x00usb_probe(struct usb_interface *usb_intf, + const struct rt2x00_ops *ops) +{ + struct usb_device *usb_dev = interface_to_usbdev(usb_intf); + struct ieee80211_hw *hw; + struct rt2x00_dev *rt2x00dev; + int retval; + + usb_dev = usb_get_dev(usb_dev); + usb_reset_device(usb_dev); + + hw = ieee80211_alloc_hw(sizeof(struct rt2x00_dev), ops->hw); + if (!hw) { + rt2x00_probe_err("Failed to allocate hardware\n"); + retval = -ENOMEM; + goto exit_put_device; + } + + usb_set_intfdata(usb_intf, hw); + + rt2x00dev = hw->priv; + rt2x00dev->dev = &usb_intf->dev; + rt2x00dev->ops = ops; + rt2x00dev->hw = hw; + + rt2x00_set_chip_intf(rt2x00dev, RT2X00_CHIP_INTF_USB); + + INIT_WORK(&rt2x00dev->rxdone_work, rt2x00usb_work_rxdone); + INIT_WORK(&rt2x00dev->txdone_work, rt2x00usb_work_txdone); + hrtimer_init(&rt2x00dev->txstatus_timer, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + + retval = rt2x00usb_alloc_reg(rt2x00dev); + if (retval) + goto exit_free_device; + + retval = rt2x00lib_probe_dev(rt2x00dev); + if (retval) + goto exit_free_reg; + + return 0; + +exit_free_reg: + rt2x00usb_free_reg(rt2x00dev); + +exit_free_device: + ieee80211_free_hw(hw); + +exit_put_device: + usb_put_dev(usb_dev); + + usb_set_intfdata(usb_intf, NULL); + + return retval; +} +EXPORT_SYMBOL_GPL(rt2x00usb_probe); + +void rt2x00usb_disconnect(struct usb_interface *usb_intf) +{ + struct ieee80211_hw *hw = usb_get_intfdata(usb_intf); + struct rt2x00_dev *rt2x00dev = hw->priv; + + /* + * Free all allocated data. + */ + rt2x00lib_remove_dev(rt2x00dev); + rt2x00usb_free_reg(rt2x00dev); + ieee80211_free_hw(hw); + + /* + * Free the USB device data. + */ + usb_set_intfdata(usb_intf, NULL); + usb_put_dev(interface_to_usbdev(usb_intf)); +} +EXPORT_SYMBOL_GPL(rt2x00usb_disconnect); + +#ifdef CONFIG_PM +int rt2x00usb_suspend(struct usb_interface *usb_intf, pm_message_t state) +{ + struct ieee80211_hw *hw = usb_get_intfdata(usb_intf); + struct rt2x00_dev *rt2x00dev = hw->priv; + + return rt2x00lib_suspend(rt2x00dev, state); +} +EXPORT_SYMBOL_GPL(rt2x00usb_suspend); + +int rt2x00usb_resume(struct usb_interface *usb_intf) +{ + struct ieee80211_hw *hw = usb_get_intfdata(usb_intf); + struct rt2x00_dev *rt2x00dev = hw->priv; + + return rt2x00lib_resume(rt2x00dev); +} +EXPORT_SYMBOL_GPL(rt2x00usb_resume); +#endif /* CONFIG_PM */ + +/* + * rt2x00usb module information. + */ +MODULE_AUTHOR(DRV_PROJECT); +MODULE_VERSION(DRV_VERSION); +MODULE_DESCRIPTION("rt2x00 usb library"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/rt2x00/rt2x00usb.h b/drivers/net/wireless/rt2x00/rt2x00usb.h new file mode 100644 index 0000000..323ca7b --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt2x00usb.h @@ -0,0 +1,442 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt2x00usb + Abstract: Data structures for the rt2x00usb module. + */ + +#ifndef RT2X00USB_H +#define RT2X00USB_H + +#include + +#define to_usb_device_intf(d) \ +({ \ + struct usb_interface *intf = to_usb_interface(d); \ + interface_to_usbdev(intf); \ +}) + +/* + * For USB vendor requests we need to pass a timeout + * time in ms, for this we use the REGISTER_TIMEOUT, + * however when loading firmware a higher value is + * required. In that case we use the REGISTER_TIMEOUT_FIRMWARE. + */ +#define REGISTER_TIMEOUT 500 +#define REGISTER_TIMEOUT_FIRMWARE 1000 + +/** + * REGISTER_TIMEOUT16 - Determine the timeout for 16bit register access + * @__datalen: Data length + */ +#define REGISTER_TIMEOUT16(__datalen) \ + ( REGISTER_TIMEOUT * ((__datalen) / sizeof(u16)) ) + +/** + * REGISTER_TIMEOUT32 - Determine the timeout for 32bit register access + * @__datalen: Data length + */ +#define REGISTER_TIMEOUT32(__datalen) \ + ( REGISTER_TIMEOUT * ((__datalen) / sizeof(u32)) ) + +/* + * Cache size + */ +#define CSR_CACHE_SIZE 64 + +/* + * USB request types. + */ +#define USB_VENDOR_REQUEST ( USB_TYPE_VENDOR | USB_RECIP_DEVICE ) +#define USB_VENDOR_REQUEST_IN ( USB_DIR_IN | USB_VENDOR_REQUEST ) +#define USB_VENDOR_REQUEST_OUT ( USB_DIR_OUT | USB_VENDOR_REQUEST ) + +/** + * enum rt2x00usb_vendor_request: USB vendor commands. + */ +enum rt2x00usb_vendor_request { + USB_DEVICE_MODE = 1, + USB_SINGLE_WRITE = 2, + USB_SINGLE_READ = 3, + USB_MULTI_WRITE = 6, + USB_MULTI_READ = 7, + USB_EEPROM_WRITE = 8, + USB_EEPROM_READ = 9, + USB_LED_CONTROL = 10, /* RT73USB */ + USB_RX_CONTROL = 12, +}; + +/** + * enum rt2x00usb_mode_offset: Device modes offset. + */ +enum rt2x00usb_mode_offset { + USB_MODE_RESET = 1, + USB_MODE_UNPLUG = 2, + USB_MODE_FUNCTION = 3, + USB_MODE_TEST = 4, + USB_MODE_SLEEP = 7, /* RT73USB */ + USB_MODE_FIRMWARE = 8, /* RT73USB */ + USB_MODE_WAKEUP = 9, /* RT73USB */ +}; + +/** + * rt2x00usb_vendor_request - Send register command to device + * @rt2x00dev: Pointer to &struct rt2x00_dev + * @request: USB vendor command (See &enum rt2x00usb_vendor_request) + * @requesttype: Request type &USB_VENDOR_REQUEST_* + * @offset: Register offset to perform action on + * @value: Value to write to device + * @buffer: Buffer where information will be read/written to by device + * @buffer_length: Size of &buffer + * @timeout: Operation timeout + * + * This is the main function to communicate with the device, + * the &buffer argument _must_ either be NULL or point to + * a buffer allocated by kmalloc. Failure to do so can lead + * to unexpected behavior depending on the architecture. + */ +int rt2x00usb_vendor_request(struct rt2x00_dev *rt2x00dev, + const u8 request, const u8 requesttype, + const u16 offset, const u16 value, + void *buffer, const u16 buffer_length, + const int timeout); + +/** + * rt2x00usb_vendor_request_buff - Send register command to device (buffered) + * @rt2x00dev: Pointer to &struct rt2x00_dev + * @request: USB vendor command (See &enum rt2x00usb_vendor_request) + * @requesttype: Request type &USB_VENDOR_REQUEST_* + * @offset: Register offset to perform action on + * @buffer: Buffer where information will be read/written to by device + * @buffer_length: Size of &buffer + * @timeout: Operation timeout + * + * This function will use a previously with kmalloc allocated cache + * to communicate with the device. The contents of the buffer pointer + * will be copied to this cache when writing, or read from the cache + * when reading. + * Buffers send to &rt2x00usb_vendor_request _must_ be allocated with + * kmalloc. Hence the reason for using a previously allocated cache + * which has been allocated properly. + */ +int rt2x00usb_vendor_request_buff(struct rt2x00_dev *rt2x00dev, + const u8 request, const u8 requesttype, + const u16 offset, void *buffer, + const u16 buffer_length, const int timeout); + +/** + * rt2x00usb_vendor_request_buff - Send register command to device (buffered) + * @rt2x00dev: Pointer to &struct rt2x00_dev + * @request: USB vendor command (See &enum rt2x00usb_vendor_request) + * @requesttype: Request type &USB_VENDOR_REQUEST_* + * @offset: Register offset to perform action on + * @buffer: Buffer where information will be read/written to by device + * @buffer_length: Size of &buffer + * @timeout: Operation timeout + * + * A version of &rt2x00usb_vendor_request_buff which must be called + * if the usb_cache_mutex is already held. + */ +int rt2x00usb_vendor_req_buff_lock(struct rt2x00_dev *rt2x00dev, + const u8 request, const u8 requesttype, + const u16 offset, void *buffer, + const u16 buffer_length, const int timeout); + +/** + * rt2x00usb_vendor_request_sw - Send single register command to device + * @rt2x00dev: Pointer to &struct rt2x00_dev + * @request: USB vendor command (See &enum rt2x00usb_vendor_request) + * @offset: Register offset to perform action on + * @value: Value to write to device + * @timeout: Operation timeout + * + * Simple wrapper around rt2x00usb_vendor_request to write a single + * command to the device. Since we don't use the buffer argument we + * don't have to worry about kmalloc here. + */ +static inline int rt2x00usb_vendor_request_sw(struct rt2x00_dev *rt2x00dev, + const u8 request, + const u16 offset, + const u16 value, + const int timeout) +{ + return rt2x00usb_vendor_request(rt2x00dev, request, + USB_VENDOR_REQUEST_OUT, offset, + value, NULL, 0, timeout); +} + +/** + * rt2x00usb_eeprom_read - Read eeprom from device + * @rt2x00dev: Pointer to &struct rt2x00_dev + * @eeprom: Pointer to eeprom array to store the information in + * @length: Number of bytes to read from the eeprom + * + * Simple wrapper around rt2x00usb_vendor_request to read the eeprom + * from the device. Note that the eeprom argument _must_ be allocated using + * kmalloc for correct handling inside the kernel USB layer. + */ +static inline int rt2x00usb_eeprom_read(struct rt2x00_dev *rt2x00dev, + __le16 *eeprom, const u16 length) +{ + return rt2x00usb_vendor_request(rt2x00dev, USB_EEPROM_READ, + USB_VENDOR_REQUEST_IN, 0, 0, + eeprom, length, + REGISTER_TIMEOUT16(length)); +} + +/** + * rt2x00usb_register_read - Read 32bit register word + * @rt2x00dev: Device pointer, see &struct rt2x00_dev. + * @offset: Register offset + * @value: Pointer to where register contents should be stored + * + * This function is a simple wrapper for 32bit register access + * through rt2x00usb_vendor_request_buff(). + */ +static inline void rt2x00usb_register_read(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + u32 *value) +{ + __le32 reg; + rt2x00usb_vendor_request_buff(rt2x00dev, USB_MULTI_READ, + USB_VENDOR_REQUEST_IN, offset, + ®, sizeof(reg), REGISTER_TIMEOUT); + *value = le32_to_cpu(reg); +} + +/** + * rt2x00usb_register_read_lock - Read 32bit register word + * @rt2x00dev: Device pointer, see &struct rt2x00_dev. + * @offset: Register offset + * @value: Pointer to where register contents should be stored + * + * This function is a simple wrapper for 32bit register access + * through rt2x00usb_vendor_req_buff_lock(). + */ +static inline void rt2x00usb_register_read_lock(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + u32 *value) +{ + __le32 reg; + rt2x00usb_vendor_req_buff_lock(rt2x00dev, USB_MULTI_READ, + USB_VENDOR_REQUEST_IN, offset, + ®, sizeof(reg), REGISTER_TIMEOUT); + *value = le32_to_cpu(reg); +} + +/** + * rt2x00usb_register_multiread - Read 32bit register words + * @rt2x00dev: Device pointer, see &struct rt2x00_dev. + * @offset: Register offset + * @value: Pointer to where register contents should be stored + * @length: Length of the data + * + * This function is a simple wrapper for 32bit register access + * through rt2x00usb_vendor_request_buff(). + */ +static inline void rt2x00usb_register_multiread(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + void *value, const u32 length) +{ + rt2x00usb_vendor_request_buff(rt2x00dev, USB_MULTI_READ, + USB_VENDOR_REQUEST_IN, offset, + value, length, + REGISTER_TIMEOUT32(length)); +} + +/** + * rt2x00usb_register_write - Write 32bit register word + * @rt2x00dev: Device pointer, see &struct rt2x00_dev. + * @offset: Register offset + * @value: Data which should be written + * + * This function is a simple wrapper for 32bit register access + * through rt2x00usb_vendor_request_buff(). + */ +static inline void rt2x00usb_register_write(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + u32 value) +{ + __le32 reg = cpu_to_le32(value); + rt2x00usb_vendor_request_buff(rt2x00dev, USB_MULTI_WRITE, + USB_VENDOR_REQUEST_OUT, offset, + ®, sizeof(reg), REGISTER_TIMEOUT); +} + +/** + * rt2x00usb_register_write_lock - Write 32bit register word + * @rt2x00dev: Device pointer, see &struct rt2x00_dev. + * @offset: Register offset + * @value: Data which should be written + * + * This function is a simple wrapper for 32bit register access + * through rt2x00usb_vendor_req_buff_lock(). + */ +static inline void rt2x00usb_register_write_lock(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + u32 value) +{ + __le32 reg = cpu_to_le32(value); + rt2x00usb_vendor_req_buff_lock(rt2x00dev, USB_MULTI_WRITE, + USB_VENDOR_REQUEST_OUT, offset, + ®, sizeof(reg), REGISTER_TIMEOUT); +} + +/** + * rt2x00usb_register_multiwrite - Write 32bit register words + * @rt2x00dev: Device pointer, see &struct rt2x00_dev. + * @offset: Register offset + * @value: Data which should be written + * @length: Length of the data + * + * This function is a simple wrapper for 32bit register access + * through rt2x00usb_vendor_request_buff(). + */ +static inline void rt2x00usb_register_multiwrite(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + const void *value, + const u32 length) +{ + rt2x00usb_vendor_request_buff(rt2x00dev, USB_MULTI_WRITE, + USB_VENDOR_REQUEST_OUT, offset, + (void *)value, length, + REGISTER_TIMEOUT32(length)); +} + +/** + * rt2x00usb_regbusy_read - Read from register with busy check + * @rt2x00dev: Device pointer, see &struct rt2x00_dev. + * @offset: Register offset + * @field: Field to check if register is busy + * @reg: Pointer to where register contents should be stored + * + * This function will read the given register, and checks if the + * register is busy. If it is, it will sleep for a couple of + * microseconds before reading the register again. If the register + * is not read after a certain timeout, this function will return + * FALSE. + */ +int rt2x00usb_regbusy_read(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + const struct rt2x00_field32 field, + u32 *reg); + +/** + * rt2x00usb_register_read_async - Asynchronously read 32bit register word + * @rt2x00dev: Device pointer, see &struct rt2x00_dev. + * @offset: Register offset + * @callback: Functon to call when read completes. + * + * Submit a control URB to read a 32bit register. This safe to + * be called from atomic context. The callback will be called + * when the URB completes. Otherwise the function is similar + * to rt2x00usb_register_read(). + * When the callback function returns false, the memory will be cleaned up, + * when it returns true, the urb will be fired again. + */ +void rt2x00usb_register_read_async(struct rt2x00_dev *rt2x00dev, + const unsigned int offset, + bool (*callback)(struct rt2x00_dev*, int, u32)); + +/* + * Radio handlers + */ +void rt2x00usb_disable_radio(struct rt2x00_dev *rt2x00dev); + +/** + * struct queue_entry_priv_usb: Per entry USB specific information + * + * @urb: Urb structure used for device communication. + */ +struct queue_entry_priv_usb { + struct urb *urb; +}; + +/** + * struct queue_entry_priv_usb_bcn: Per TX entry USB specific information + * + * The first section should match &struct queue_entry_priv_usb exactly. + * rt2500usb can use this structure to send a guardian byte when working + * with beacons. + * + * @urb: Urb structure used for device communication. + * @guardian_data: Set to 0, used for sending the guardian data. + * @guardian_urb: Urb structure used to send the guardian data. + */ +struct queue_entry_priv_usb_bcn { + struct urb *urb; + + unsigned int guardian_data; + struct urb *guardian_urb; +}; + +/** + * rt2x00usb_kick_queue - Kick data queue + * @queue: Data queue to kick + * + * This will walk through all entries of the queue and push all pending + * frames to the hardware as a single burst. + */ +void rt2x00usb_kick_queue(struct data_queue *queue); + +/** + * rt2x00usb_flush_queue - Flush data queue + * @queue: Data queue to stop + * @drop: True to drop all pending frames. + * + * This will walk through all entries of the queue and will optionally + * kill all URB's which were send to the device, or at least wait until + * they have been returned from the device.. + */ +void rt2x00usb_flush_queue(struct data_queue *queue, bool drop); + +/** + * rt2x00usb_watchdog - Watchdog for USB communication + * @rt2x00dev: Pointer to &struct rt2x00_dev + * + * Check the health of the USB communication and determine + * if timeouts have occurred. If this is the case, this function + * will reset all communication to restore functionality again. + */ +void rt2x00usb_watchdog(struct rt2x00_dev *rt2x00dev); + +/* + * Device initialization handlers. + */ +void rt2x00usb_clear_entry(struct queue_entry *entry); +int rt2x00usb_initialize(struct rt2x00_dev *rt2x00dev); +void rt2x00usb_uninitialize(struct rt2x00_dev *rt2x00dev); + +/* + * USB driver handlers. + */ +int rt2x00usb_probe(struct usb_interface *usb_intf, + const struct rt2x00_ops *ops); +void rt2x00usb_disconnect(struct usb_interface *usb_intf); +#ifdef CONFIG_PM +int rt2x00usb_suspend(struct usb_interface *usb_intf, pm_message_t state); +int rt2x00usb_resume(struct usb_interface *usb_intf); +#else +#define rt2x00usb_suspend NULL +#define rt2x00usb_resume NULL +#endif /* CONFIG_PM */ + +#endif /* RT2X00USB_H */ diff --git a/drivers/net/wireless/rt2x00/rt61pci.c b/drivers/net/wireless/rt2x00/rt61pci.c new file mode 100644 index 0000000..4633c71 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt61pci.c @@ -0,0 +1,3106 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt61pci + Abstract: rt61pci device specific routines. + Supported chipsets: RT2561, RT2561s, RT2661. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rt2x00.h" +#include "rt2x00mmio.h" +#include "rt2x00pci.h" +#include "rt61pci.h" + +/* + * Allow hardware encryption to be disabled. + */ +static bool modparam_nohwcrypt = false; +module_param_named(nohwcrypt, modparam_nohwcrypt, bool, S_IRUGO); +MODULE_PARM_DESC(nohwcrypt, "Disable hardware encryption."); + +/* + * Register access. + * BBP and RF register require indirect register access, + * and use the CSR registers PHY_CSR3 and PHY_CSR4 to achieve this. + * These indirect registers work with busy bits, + * and we will try maximal REGISTER_BUSY_COUNT times to access + * the register while taking a REGISTER_BUSY_DELAY us delay + * between each attempt. When the busy bit is still set at that time, + * the access attempt is considered to have failed, + * and we will print an error. + */ +#define WAIT_FOR_BBP(__dev, __reg) \ + rt2x00mmio_regbusy_read((__dev), PHY_CSR3, PHY_CSR3_BUSY, (__reg)) +#define WAIT_FOR_RF(__dev, __reg) \ + rt2x00mmio_regbusy_read((__dev), PHY_CSR4, PHY_CSR4_BUSY, (__reg)) +#define WAIT_FOR_MCU(__dev, __reg) \ + rt2x00mmio_regbusy_read((__dev), H2M_MAILBOX_CSR, \ + H2M_MAILBOX_CSR_OWNER, (__reg)) + +static void rt61pci_bbp_write(struct rt2x00_dev *rt2x00dev, + const unsigned int word, const u8 value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the BBP becomes available, afterwards we + * can safely write the new data into the register. + */ + if (WAIT_FOR_BBP(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, PHY_CSR3_VALUE, value); + rt2x00_set_field32(®, PHY_CSR3_REGNUM, word); + rt2x00_set_field32(®, PHY_CSR3_BUSY, 1); + rt2x00_set_field32(®, PHY_CSR3_READ_CONTROL, 0); + + rt2x00mmio_register_write(rt2x00dev, PHY_CSR3, reg); + } + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt61pci_bbp_read(struct rt2x00_dev *rt2x00dev, + const unsigned int word, u8 *value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the BBP becomes available, afterwards we + * can safely write the read request into the register. + * After the data has been written, we wait until hardware + * returns the correct value, if at any time the register + * doesn't become available in time, reg will be 0xffffffff + * which means we return 0xff to the caller. + */ + if (WAIT_FOR_BBP(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, PHY_CSR3_REGNUM, word); + rt2x00_set_field32(®, PHY_CSR3_BUSY, 1); + rt2x00_set_field32(®, PHY_CSR3_READ_CONTROL, 1); + + rt2x00mmio_register_write(rt2x00dev, PHY_CSR3, reg); + + WAIT_FOR_BBP(rt2x00dev, ®); + } + + *value = rt2x00_get_field32(reg, PHY_CSR3_VALUE); + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt61pci_rf_write(struct rt2x00_dev *rt2x00dev, + const unsigned int word, const u32 value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the RF becomes available, afterwards we + * can safely write the new data into the register. + */ + if (WAIT_FOR_RF(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, PHY_CSR4_VALUE, value); + rt2x00_set_field32(®, PHY_CSR4_NUMBER_OF_BITS, 21); + rt2x00_set_field32(®, PHY_CSR4_IF_SELECT, 0); + rt2x00_set_field32(®, PHY_CSR4_BUSY, 1); + + rt2x00mmio_register_write(rt2x00dev, PHY_CSR4, reg); + rt2x00_rf_write(rt2x00dev, word, value); + } + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt61pci_mcu_request(struct rt2x00_dev *rt2x00dev, + const u8 command, const u8 token, + const u8 arg0, const u8 arg1) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the MCU becomes available, afterwards we + * can safely write the new data into the register. + */ + if (WAIT_FOR_MCU(rt2x00dev, ®)) { + rt2x00_set_field32(®, H2M_MAILBOX_CSR_OWNER, 1); + rt2x00_set_field32(®, H2M_MAILBOX_CSR_CMD_TOKEN, token); + rt2x00_set_field32(®, H2M_MAILBOX_CSR_ARG0, arg0); + rt2x00_set_field32(®, H2M_MAILBOX_CSR_ARG1, arg1); + rt2x00mmio_register_write(rt2x00dev, H2M_MAILBOX_CSR, reg); + + rt2x00mmio_register_read(rt2x00dev, HOST_CMD_CSR, ®); + rt2x00_set_field32(®, HOST_CMD_CSR_HOST_COMMAND, command); + rt2x00_set_field32(®, HOST_CMD_CSR_INTERRUPT_MCU, 1); + rt2x00mmio_register_write(rt2x00dev, HOST_CMD_CSR, reg); + } + + mutex_unlock(&rt2x00dev->csr_mutex); + +} + +static void rt61pci_eepromregister_read(struct eeprom_93cx6 *eeprom) +{ + struct rt2x00_dev *rt2x00dev = eeprom->data; + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, E2PROM_CSR, ®); + + eeprom->reg_data_in = !!rt2x00_get_field32(reg, E2PROM_CSR_DATA_IN); + eeprom->reg_data_out = !!rt2x00_get_field32(reg, E2PROM_CSR_DATA_OUT); + eeprom->reg_data_clock = + !!rt2x00_get_field32(reg, E2PROM_CSR_DATA_CLOCK); + eeprom->reg_chip_select = + !!rt2x00_get_field32(reg, E2PROM_CSR_CHIP_SELECT); +} + +static void rt61pci_eepromregister_write(struct eeprom_93cx6 *eeprom) +{ + struct rt2x00_dev *rt2x00dev = eeprom->data; + u32 reg = 0; + + rt2x00_set_field32(®, E2PROM_CSR_DATA_IN, !!eeprom->reg_data_in); + rt2x00_set_field32(®, E2PROM_CSR_DATA_OUT, !!eeprom->reg_data_out); + rt2x00_set_field32(®, E2PROM_CSR_DATA_CLOCK, + !!eeprom->reg_data_clock); + rt2x00_set_field32(®, E2PROM_CSR_CHIP_SELECT, + !!eeprom->reg_chip_select); + + rt2x00mmio_register_write(rt2x00dev, E2PROM_CSR, reg); +} + +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS +static const struct rt2x00debug rt61pci_rt2x00debug = { + .owner = THIS_MODULE, + .csr = { + .read = rt2x00mmio_register_read, + .write = rt2x00mmio_register_write, + .flags = RT2X00DEBUGFS_OFFSET, + .word_base = CSR_REG_BASE, + .word_size = sizeof(u32), + .word_count = CSR_REG_SIZE / sizeof(u32), + }, + .eeprom = { + .read = rt2x00_eeprom_read, + .write = rt2x00_eeprom_write, + .word_base = EEPROM_BASE, + .word_size = sizeof(u16), + .word_count = EEPROM_SIZE / sizeof(u16), + }, + .bbp = { + .read = rt61pci_bbp_read, + .write = rt61pci_bbp_write, + .word_base = BBP_BASE, + .word_size = sizeof(u8), + .word_count = BBP_SIZE / sizeof(u8), + }, + .rf = { + .read = rt2x00_rf_read, + .write = rt61pci_rf_write, + .word_base = RF_BASE, + .word_size = sizeof(u32), + .word_count = RF_SIZE / sizeof(u32), + }, +}; +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ + +static int rt61pci_rfkill_poll(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, MAC_CSR13, ®); + return rt2x00_get_field32(reg, MAC_CSR13_VAL5); +} + +#ifdef CPTCFG_RT2X00_LIB_LEDS +static void rt61pci_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct rt2x00_led *led = + container_of(led_cdev, struct rt2x00_led, led_dev); + unsigned int enabled = brightness != LED_OFF; + unsigned int a_mode = + (enabled && led->rt2x00dev->curr_band == IEEE80211_BAND_5GHZ); + unsigned int bg_mode = + (enabled && led->rt2x00dev->curr_band == IEEE80211_BAND_2GHZ); + + if (led->type == LED_TYPE_RADIO) { + rt2x00_set_field16(&led->rt2x00dev->led_mcu_reg, + MCU_LEDCS_RADIO_STATUS, enabled); + + rt61pci_mcu_request(led->rt2x00dev, MCU_LED, 0xff, + (led->rt2x00dev->led_mcu_reg & 0xff), + ((led->rt2x00dev->led_mcu_reg >> 8))); + } else if (led->type == LED_TYPE_ASSOC) { + rt2x00_set_field16(&led->rt2x00dev->led_mcu_reg, + MCU_LEDCS_LINK_BG_STATUS, bg_mode); + rt2x00_set_field16(&led->rt2x00dev->led_mcu_reg, + MCU_LEDCS_LINK_A_STATUS, a_mode); + + rt61pci_mcu_request(led->rt2x00dev, MCU_LED, 0xff, + (led->rt2x00dev->led_mcu_reg & 0xff), + ((led->rt2x00dev->led_mcu_reg >> 8))); + } else if (led->type == LED_TYPE_QUALITY) { + /* + * The brightness is divided into 6 levels (0 - 5), + * this means we need to convert the brightness + * argument into the matching level within that range. + */ + rt61pci_mcu_request(led->rt2x00dev, MCU_LED_STRENGTH, 0xff, + brightness / (LED_FULL / 6), 0); + } +} + +static int rt61pci_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct rt2x00_led *led = + container_of(led_cdev, struct rt2x00_led, led_dev); + u32 reg; + + rt2x00mmio_register_read(led->rt2x00dev, MAC_CSR14, ®); + rt2x00_set_field32(®, MAC_CSR14_ON_PERIOD, *delay_on); + rt2x00_set_field32(®, MAC_CSR14_OFF_PERIOD, *delay_off); + rt2x00mmio_register_write(led->rt2x00dev, MAC_CSR14, reg); + + return 0; +} + +static void rt61pci_init_led(struct rt2x00_dev *rt2x00dev, + struct rt2x00_led *led, + enum led_type type) +{ + led->rt2x00dev = rt2x00dev; + led->type = type; + led->led_dev.brightness_set = rt61pci_brightness_set; + led->led_dev.blink_set = rt61pci_blink_set; + led->flags = LED_INITIALIZED; +} +#endif /* CPTCFG_RT2X00_LIB_LEDS */ + +/* + * Configuration handlers. + */ +static int rt61pci_config_shared_key(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_crypto *crypto, + struct ieee80211_key_conf *key) +{ + struct hw_key_entry key_entry; + struct rt2x00_field32 field; + u32 mask; + u32 reg; + + if (crypto->cmd == SET_KEY) { + /* + * rt2x00lib can't determine the correct free + * key_idx for shared keys. We have 1 register + * with key valid bits. The goal is simple, read + * the register, if that is full we have no slots + * left. + * Note that each BSS is allowed to have up to 4 + * shared keys, so put a mask over the allowed + * entries. + */ + mask = (0xf << crypto->bssidx); + + rt2x00mmio_register_read(rt2x00dev, SEC_CSR0, ®); + reg &= mask; + + if (reg && reg == mask) + return -ENOSPC; + + key->hw_key_idx += reg ? ffz(reg) : 0; + + /* + * Upload key to hardware + */ + memcpy(key_entry.key, crypto->key, + sizeof(key_entry.key)); + memcpy(key_entry.tx_mic, crypto->tx_mic, + sizeof(key_entry.tx_mic)); + memcpy(key_entry.rx_mic, crypto->rx_mic, + sizeof(key_entry.rx_mic)); + + reg = SHARED_KEY_ENTRY(key->hw_key_idx); + rt2x00mmio_register_multiwrite(rt2x00dev, reg, + &key_entry, sizeof(key_entry)); + + /* + * The cipher types are stored over 2 registers. + * bssidx 0 and 1 keys are stored in SEC_CSR1 and + * bssidx 1 and 2 keys are stored in SEC_CSR5. + * Using the correct defines correctly will cause overhead, + * so just calculate the correct offset. + */ + if (key->hw_key_idx < 8) { + field.bit_offset = (3 * key->hw_key_idx); + field.bit_mask = 0x7 << field.bit_offset; + + rt2x00mmio_register_read(rt2x00dev, SEC_CSR1, ®); + rt2x00_set_field32(®, field, crypto->cipher); + rt2x00mmio_register_write(rt2x00dev, SEC_CSR1, reg); + } else { + field.bit_offset = (3 * (key->hw_key_idx - 8)); + field.bit_mask = 0x7 << field.bit_offset; + + rt2x00mmio_register_read(rt2x00dev, SEC_CSR5, ®); + rt2x00_set_field32(®, field, crypto->cipher); + rt2x00mmio_register_write(rt2x00dev, SEC_CSR5, reg); + } + + /* + * The driver does not support the IV/EIV generation + * in hardware. However it doesn't support the IV/EIV + * inside the ieee80211 frame either, but requires it + * to be provided separately for the descriptor. + * rt2x00lib will cut the IV/EIV data out of all frames + * given to us by mac80211, but we must tell mac80211 + * to generate the IV/EIV data. + */ + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + } + + /* + * SEC_CSR0 contains only single-bit fields to indicate + * a particular key is valid. Because using the FIELD32() + * defines directly will cause a lot of overhead, we use + * a calculation to determine the correct bit directly. + */ + mask = 1 << key->hw_key_idx; + + rt2x00mmio_register_read(rt2x00dev, SEC_CSR0, ®); + if (crypto->cmd == SET_KEY) + reg |= mask; + else if (crypto->cmd == DISABLE_KEY) + reg &= ~mask; + rt2x00mmio_register_write(rt2x00dev, SEC_CSR0, reg); + + return 0; +} + +static int rt61pci_config_pairwise_key(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_crypto *crypto, + struct ieee80211_key_conf *key) +{ + struct hw_pairwise_ta_entry addr_entry; + struct hw_key_entry key_entry; + u32 mask; + u32 reg; + + if (crypto->cmd == SET_KEY) { + /* + * rt2x00lib can't determine the correct free + * key_idx for pairwise keys. We have 2 registers + * with key valid bits. The goal is simple: read + * the first register. If that is full, move to + * the next register. + * When both registers are full, we drop the key. + * Otherwise, we use the first invalid entry. + */ + rt2x00mmio_register_read(rt2x00dev, SEC_CSR2, ®); + if (reg && reg == ~0) { + key->hw_key_idx = 32; + rt2x00mmio_register_read(rt2x00dev, SEC_CSR3, ®); + if (reg && reg == ~0) + return -ENOSPC; + } + + key->hw_key_idx += reg ? ffz(reg) : 0; + + /* + * Upload key to hardware + */ + memcpy(key_entry.key, crypto->key, + sizeof(key_entry.key)); + memcpy(key_entry.tx_mic, crypto->tx_mic, + sizeof(key_entry.tx_mic)); + memcpy(key_entry.rx_mic, crypto->rx_mic, + sizeof(key_entry.rx_mic)); + + memset(&addr_entry, 0, sizeof(addr_entry)); + memcpy(&addr_entry, crypto->address, ETH_ALEN); + addr_entry.cipher = crypto->cipher; + + reg = PAIRWISE_KEY_ENTRY(key->hw_key_idx); + rt2x00mmio_register_multiwrite(rt2x00dev, reg, + &key_entry, sizeof(key_entry)); + + reg = PAIRWISE_TA_ENTRY(key->hw_key_idx); + rt2x00mmio_register_multiwrite(rt2x00dev, reg, + &addr_entry, sizeof(addr_entry)); + + /* + * Enable pairwise lookup table for given BSS idx. + * Without this, received frames will not be decrypted + * by the hardware. + */ + rt2x00mmio_register_read(rt2x00dev, SEC_CSR4, ®); + reg |= (1 << crypto->bssidx); + rt2x00mmio_register_write(rt2x00dev, SEC_CSR4, reg); + + /* + * The driver does not support the IV/EIV generation + * in hardware. However it doesn't support the IV/EIV + * inside the ieee80211 frame either, but requires it + * to be provided separately for the descriptor. + * rt2x00lib will cut the IV/EIV data out of all frames + * given to us by mac80211, but we must tell mac80211 + * to generate the IV/EIV data. + */ + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + } + + /* + * SEC_CSR2 and SEC_CSR3 contain only single-bit fields to indicate + * a particular key is valid. Because using the FIELD32() + * defines directly will cause a lot of overhead, we use + * a calculation to determine the correct bit directly. + */ + if (key->hw_key_idx < 32) { + mask = 1 << key->hw_key_idx; + + rt2x00mmio_register_read(rt2x00dev, SEC_CSR2, ®); + if (crypto->cmd == SET_KEY) + reg |= mask; + else if (crypto->cmd == DISABLE_KEY) + reg &= ~mask; + rt2x00mmio_register_write(rt2x00dev, SEC_CSR2, reg); + } else { + mask = 1 << (key->hw_key_idx - 32); + + rt2x00mmio_register_read(rt2x00dev, SEC_CSR3, ®); + if (crypto->cmd == SET_KEY) + reg |= mask; + else if (crypto->cmd == DISABLE_KEY) + reg &= ~mask; + rt2x00mmio_register_write(rt2x00dev, SEC_CSR3, reg); + } + + return 0; +} + +static void rt61pci_config_filter(struct rt2x00_dev *rt2x00dev, + const unsigned int filter_flags) +{ + u32 reg; + + /* + * Start configuration steps. + * Note that the version error will always be dropped + * and broadcast frames will always be accepted since + * there is no filter for it at this time. + */ + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR0, ®); + rt2x00_set_field32(®, TXRX_CSR0_DROP_CRC, + !(filter_flags & FIF_FCSFAIL)); + rt2x00_set_field32(®, TXRX_CSR0_DROP_PHYSICAL, + !(filter_flags & FIF_PLCPFAIL)); + rt2x00_set_field32(®, TXRX_CSR0_DROP_CONTROL, + !(filter_flags & (FIF_CONTROL | FIF_PSPOLL))); + rt2x00_set_field32(®, TXRX_CSR0_DROP_NOT_TO_ME, + !(filter_flags & FIF_PROMISC_IN_BSS)); + rt2x00_set_field32(®, TXRX_CSR0_DROP_TO_DS, + !(filter_flags & FIF_PROMISC_IN_BSS) && + !rt2x00dev->intf_ap_count); + rt2x00_set_field32(®, TXRX_CSR0_DROP_VERSION_ERROR, 1); + rt2x00_set_field32(®, TXRX_CSR0_DROP_MULTICAST, + !(filter_flags & FIF_ALLMULTI)); + rt2x00_set_field32(®, TXRX_CSR0_DROP_BROADCAST, 0); + rt2x00_set_field32(®, TXRX_CSR0_DROP_ACK_CTS, + !(filter_flags & FIF_CONTROL)); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR0, reg); +} + +static void rt61pci_config_intf(struct rt2x00_dev *rt2x00dev, + struct rt2x00_intf *intf, + struct rt2x00intf_conf *conf, + const unsigned int flags) +{ + u32 reg; + + if (flags & CONFIG_UPDATE_TYPE) { + /* + * Enable synchronisation. + */ + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR9, ®); + rt2x00_set_field32(®, TXRX_CSR9_TSF_SYNC, conf->sync); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR9, reg); + } + + if (flags & CONFIG_UPDATE_MAC) { + reg = le32_to_cpu(conf->mac[1]); + rt2x00_set_field32(®, MAC_CSR3_UNICAST_TO_ME_MASK, 0xff); + conf->mac[1] = cpu_to_le32(reg); + + rt2x00mmio_register_multiwrite(rt2x00dev, MAC_CSR2, + conf->mac, sizeof(conf->mac)); + } + + if (flags & CONFIG_UPDATE_BSSID) { + reg = le32_to_cpu(conf->bssid[1]); + rt2x00_set_field32(®, MAC_CSR5_BSS_ID_MASK, 3); + conf->bssid[1] = cpu_to_le32(reg); + + rt2x00mmio_register_multiwrite(rt2x00dev, MAC_CSR4, + conf->bssid, + sizeof(conf->bssid)); + } +} + +static void rt61pci_config_erp(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_erp *erp, + u32 changed) +{ + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR0, ®); + rt2x00_set_field32(®, TXRX_CSR0_RX_ACK_TIMEOUT, 0x32); + rt2x00_set_field32(®, TXRX_CSR0_TSF_OFFSET, IEEE80211_HEADER); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR0, reg); + + if (changed & BSS_CHANGED_ERP_PREAMBLE) { + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR4, ®); + rt2x00_set_field32(®, TXRX_CSR4_AUTORESPOND_ENABLE, 1); + rt2x00_set_field32(®, TXRX_CSR4_AUTORESPOND_PREAMBLE, + !!erp->short_preamble); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR4, reg); + } + + if (changed & BSS_CHANGED_BASIC_RATES) + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR5, + erp->basic_rates); + + if (changed & BSS_CHANGED_BEACON_INT) { + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR9, ®); + rt2x00_set_field32(®, TXRX_CSR9_BEACON_INTERVAL, + erp->beacon_int * 16); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR9, reg); + } + + if (changed & BSS_CHANGED_ERP_SLOT) { + rt2x00mmio_register_read(rt2x00dev, MAC_CSR9, ®); + rt2x00_set_field32(®, MAC_CSR9_SLOT_TIME, erp->slot_time); + rt2x00mmio_register_write(rt2x00dev, MAC_CSR9, reg); + + rt2x00mmio_register_read(rt2x00dev, MAC_CSR8, ®); + rt2x00_set_field32(®, MAC_CSR8_SIFS, erp->sifs); + rt2x00_set_field32(®, MAC_CSR8_SIFS_AFTER_RX_OFDM, 3); + rt2x00_set_field32(®, MAC_CSR8_EIFS, erp->eifs); + rt2x00mmio_register_write(rt2x00dev, MAC_CSR8, reg); + } +} + +static void rt61pci_config_antenna_5x(struct rt2x00_dev *rt2x00dev, + struct antenna_setup *ant) +{ + u8 r3; + u8 r4; + u8 r77; + + rt61pci_bbp_read(rt2x00dev, 3, &r3); + rt61pci_bbp_read(rt2x00dev, 4, &r4); + rt61pci_bbp_read(rt2x00dev, 77, &r77); + + rt2x00_set_field8(&r3, BBP_R3_SMART_MODE, rt2x00_rf(rt2x00dev, RF5325)); + + /* + * Configure the RX antenna. + */ + switch (ant->rx) { + case ANTENNA_HW_DIVERSITY: + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA_CONTROL, 2); + rt2x00_set_field8(&r4, BBP_R4_RX_FRAME_END, + (rt2x00dev->curr_band != IEEE80211_BAND_5GHZ)); + break; + case ANTENNA_A: + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA_CONTROL, 1); + rt2x00_set_field8(&r4, BBP_R4_RX_FRAME_END, 0); + if (rt2x00dev->curr_band == IEEE80211_BAND_5GHZ) + rt2x00_set_field8(&r77, BBP_R77_RX_ANTENNA, 0); + else + rt2x00_set_field8(&r77, BBP_R77_RX_ANTENNA, 3); + break; + case ANTENNA_B: + default: + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA_CONTROL, 1); + rt2x00_set_field8(&r4, BBP_R4_RX_FRAME_END, 0); + if (rt2x00dev->curr_band == IEEE80211_BAND_5GHZ) + rt2x00_set_field8(&r77, BBP_R77_RX_ANTENNA, 3); + else + rt2x00_set_field8(&r77, BBP_R77_RX_ANTENNA, 0); + break; + } + + rt61pci_bbp_write(rt2x00dev, 77, r77); + rt61pci_bbp_write(rt2x00dev, 3, r3); + rt61pci_bbp_write(rt2x00dev, 4, r4); +} + +static void rt61pci_config_antenna_2x(struct rt2x00_dev *rt2x00dev, + struct antenna_setup *ant) +{ + u8 r3; + u8 r4; + u8 r77; + + rt61pci_bbp_read(rt2x00dev, 3, &r3); + rt61pci_bbp_read(rt2x00dev, 4, &r4); + rt61pci_bbp_read(rt2x00dev, 77, &r77); + + rt2x00_set_field8(&r3, BBP_R3_SMART_MODE, rt2x00_rf(rt2x00dev, RF2529)); + rt2x00_set_field8(&r4, BBP_R4_RX_FRAME_END, + !test_bit(CAPABILITY_FRAME_TYPE, &rt2x00dev->cap_flags)); + + /* + * Configure the RX antenna. + */ + switch (ant->rx) { + case ANTENNA_HW_DIVERSITY: + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA_CONTROL, 2); + break; + case ANTENNA_A: + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA_CONTROL, 1); + rt2x00_set_field8(&r77, BBP_R77_RX_ANTENNA, 3); + break; + case ANTENNA_B: + default: + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA_CONTROL, 1); + rt2x00_set_field8(&r77, BBP_R77_RX_ANTENNA, 0); + break; + } + + rt61pci_bbp_write(rt2x00dev, 77, r77); + rt61pci_bbp_write(rt2x00dev, 3, r3); + rt61pci_bbp_write(rt2x00dev, 4, r4); +} + +static void rt61pci_config_antenna_2529_rx(struct rt2x00_dev *rt2x00dev, + const int p1, const int p2) +{ + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, MAC_CSR13, ®); + + rt2x00_set_field32(®, MAC_CSR13_DIR4, 0); + rt2x00_set_field32(®, MAC_CSR13_VAL4, p1); + + rt2x00_set_field32(®, MAC_CSR13_DIR3, 0); + rt2x00_set_field32(®, MAC_CSR13_VAL3, !p2); + + rt2x00mmio_register_write(rt2x00dev, MAC_CSR13, reg); +} + +static void rt61pci_config_antenna_2529(struct rt2x00_dev *rt2x00dev, + struct antenna_setup *ant) +{ + u8 r3; + u8 r4; + u8 r77; + + rt61pci_bbp_read(rt2x00dev, 3, &r3); + rt61pci_bbp_read(rt2x00dev, 4, &r4); + rt61pci_bbp_read(rt2x00dev, 77, &r77); + + /* + * Configure the RX antenna. + */ + switch (ant->rx) { + case ANTENNA_A: + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA_CONTROL, 1); + rt2x00_set_field8(&r77, BBP_R77_RX_ANTENNA, 0); + rt61pci_config_antenna_2529_rx(rt2x00dev, 0, 0); + break; + case ANTENNA_HW_DIVERSITY: + /* + * FIXME: Antenna selection for the rf 2529 is very confusing + * in the legacy driver. Just default to antenna B until the + * legacy code can be properly translated into rt2x00 code. + */ + case ANTENNA_B: + default: + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA_CONTROL, 1); + rt2x00_set_field8(&r77, BBP_R77_RX_ANTENNA, 3); + rt61pci_config_antenna_2529_rx(rt2x00dev, 1, 1); + break; + } + + rt61pci_bbp_write(rt2x00dev, 77, r77); + rt61pci_bbp_write(rt2x00dev, 3, r3); + rt61pci_bbp_write(rt2x00dev, 4, r4); +} + +struct antenna_sel { + u8 word; + /* + * value[0] -> non-LNA + * value[1] -> LNA + */ + u8 value[2]; +}; + +static const struct antenna_sel antenna_sel_a[] = { + { 96, { 0x58, 0x78 } }, + { 104, { 0x38, 0x48 } }, + { 75, { 0xfe, 0x80 } }, + { 86, { 0xfe, 0x80 } }, + { 88, { 0xfe, 0x80 } }, + { 35, { 0x60, 0x60 } }, + { 97, { 0x58, 0x58 } }, + { 98, { 0x58, 0x58 } }, +}; + +static const struct antenna_sel antenna_sel_bg[] = { + { 96, { 0x48, 0x68 } }, + { 104, { 0x2c, 0x3c } }, + { 75, { 0xfe, 0x80 } }, + { 86, { 0xfe, 0x80 } }, + { 88, { 0xfe, 0x80 } }, + { 35, { 0x50, 0x50 } }, + { 97, { 0x48, 0x48 } }, + { 98, { 0x48, 0x48 } }, +}; + +static void rt61pci_config_ant(struct rt2x00_dev *rt2x00dev, + struct antenna_setup *ant) +{ + const struct antenna_sel *sel; + unsigned int lna; + unsigned int i; + u32 reg; + + /* + * We should never come here because rt2x00lib is supposed + * to catch this and send us the correct antenna explicitely. + */ + BUG_ON(ant->rx == ANTENNA_SW_DIVERSITY || + ant->tx == ANTENNA_SW_DIVERSITY); + + if (rt2x00dev->curr_band == IEEE80211_BAND_5GHZ) { + sel = antenna_sel_a; + lna = test_bit(CAPABILITY_EXTERNAL_LNA_A, &rt2x00dev->cap_flags); + } else { + sel = antenna_sel_bg; + lna = test_bit(CAPABILITY_EXTERNAL_LNA_BG, &rt2x00dev->cap_flags); + } + + for (i = 0; i < ARRAY_SIZE(antenna_sel_a); i++) + rt61pci_bbp_write(rt2x00dev, sel[i].word, sel[i].value[lna]); + + rt2x00mmio_register_read(rt2x00dev, PHY_CSR0, ®); + + rt2x00_set_field32(®, PHY_CSR0_PA_PE_BG, + rt2x00dev->curr_band == IEEE80211_BAND_2GHZ); + rt2x00_set_field32(®, PHY_CSR0_PA_PE_A, + rt2x00dev->curr_band == IEEE80211_BAND_5GHZ); + + rt2x00mmio_register_write(rt2x00dev, PHY_CSR0, reg); + + if (rt2x00_rf(rt2x00dev, RF5225) || rt2x00_rf(rt2x00dev, RF5325)) + rt61pci_config_antenna_5x(rt2x00dev, ant); + else if (rt2x00_rf(rt2x00dev, RF2527)) + rt61pci_config_antenna_2x(rt2x00dev, ant); + else if (rt2x00_rf(rt2x00dev, RF2529)) { + if (test_bit(CAPABILITY_DOUBLE_ANTENNA, &rt2x00dev->cap_flags)) + rt61pci_config_antenna_2x(rt2x00dev, ant); + else + rt61pci_config_antenna_2529(rt2x00dev, ant); + } +} + +static void rt61pci_config_lna_gain(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf) +{ + u16 eeprom; + short lna_gain = 0; + + if (libconf->conf->chandef.chan->band == IEEE80211_BAND_2GHZ) { + if (test_bit(CAPABILITY_EXTERNAL_LNA_BG, &rt2x00dev->cap_flags)) + lna_gain += 14; + + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_OFFSET_BG, &eeprom); + lna_gain -= rt2x00_get_field16(eeprom, EEPROM_RSSI_OFFSET_BG_1); + } else { + if (test_bit(CAPABILITY_EXTERNAL_LNA_A, &rt2x00dev->cap_flags)) + lna_gain += 14; + + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_OFFSET_A, &eeprom); + lna_gain -= rt2x00_get_field16(eeprom, EEPROM_RSSI_OFFSET_A_1); + } + + rt2x00dev->lna_gain = lna_gain; +} + +static void rt61pci_config_channel(struct rt2x00_dev *rt2x00dev, + struct rf_channel *rf, const int txpower) +{ + u8 r3; + u8 r94; + u8 smart; + + rt2x00_set_field32(&rf->rf3, RF3_TXPOWER, TXPOWER_TO_DEV(txpower)); + rt2x00_set_field32(&rf->rf4, RF4_FREQ_OFFSET, rt2x00dev->freq_offset); + + smart = !(rt2x00_rf(rt2x00dev, RF5225) || rt2x00_rf(rt2x00dev, RF2527)); + + rt61pci_bbp_read(rt2x00dev, 3, &r3); + rt2x00_set_field8(&r3, BBP_R3_SMART_MODE, smart); + rt61pci_bbp_write(rt2x00dev, 3, r3); + + r94 = 6; + if (txpower > MAX_TXPOWER && txpower <= (MAX_TXPOWER + r94)) + r94 += txpower - MAX_TXPOWER; + else if (txpower < MIN_TXPOWER && txpower >= (MIN_TXPOWER - r94)) + r94 += txpower; + rt61pci_bbp_write(rt2x00dev, 94, r94); + + rt61pci_rf_write(rt2x00dev, 1, rf->rf1); + rt61pci_rf_write(rt2x00dev, 2, rf->rf2); + rt61pci_rf_write(rt2x00dev, 3, rf->rf3 & ~0x00000004); + rt61pci_rf_write(rt2x00dev, 4, rf->rf4); + + udelay(200); + + rt61pci_rf_write(rt2x00dev, 1, rf->rf1); + rt61pci_rf_write(rt2x00dev, 2, rf->rf2); + rt61pci_rf_write(rt2x00dev, 3, rf->rf3 | 0x00000004); + rt61pci_rf_write(rt2x00dev, 4, rf->rf4); + + udelay(200); + + rt61pci_rf_write(rt2x00dev, 1, rf->rf1); + rt61pci_rf_write(rt2x00dev, 2, rf->rf2); + rt61pci_rf_write(rt2x00dev, 3, rf->rf3 & ~0x00000004); + rt61pci_rf_write(rt2x00dev, 4, rf->rf4); + + msleep(1); +} + +static void rt61pci_config_txpower(struct rt2x00_dev *rt2x00dev, + const int txpower) +{ + struct rf_channel rf; + + rt2x00_rf_read(rt2x00dev, 1, &rf.rf1); + rt2x00_rf_read(rt2x00dev, 2, &rf.rf2); + rt2x00_rf_read(rt2x00dev, 3, &rf.rf3); + rt2x00_rf_read(rt2x00dev, 4, &rf.rf4); + + rt61pci_config_channel(rt2x00dev, &rf, txpower); +} + +static void rt61pci_config_retry_limit(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf) +{ + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR4, ®); + rt2x00_set_field32(®, TXRX_CSR4_OFDM_TX_RATE_DOWN, 1); + rt2x00_set_field32(®, TXRX_CSR4_OFDM_TX_RATE_STEP, 0); + rt2x00_set_field32(®, TXRX_CSR4_OFDM_TX_FALLBACK_CCK, 0); + rt2x00_set_field32(®, TXRX_CSR4_LONG_RETRY_LIMIT, + libconf->conf->long_frame_max_tx_count); + rt2x00_set_field32(®, TXRX_CSR4_SHORT_RETRY_LIMIT, + libconf->conf->short_frame_max_tx_count); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR4, reg); +} + +static void rt61pci_config_ps(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf) +{ + enum dev_state state = + (libconf->conf->flags & IEEE80211_CONF_PS) ? + STATE_SLEEP : STATE_AWAKE; + u32 reg; + + if (state == STATE_SLEEP) { + rt2x00mmio_register_read(rt2x00dev, MAC_CSR11, ®); + rt2x00_set_field32(®, MAC_CSR11_DELAY_AFTER_TBCN, + rt2x00dev->beacon_int - 10); + rt2x00_set_field32(®, MAC_CSR11_TBCN_BEFORE_WAKEUP, + libconf->conf->listen_interval - 1); + rt2x00_set_field32(®, MAC_CSR11_WAKEUP_LATENCY, 5); + + /* We must first disable autowake before it can be enabled */ + rt2x00_set_field32(®, MAC_CSR11_AUTOWAKE, 0); + rt2x00mmio_register_write(rt2x00dev, MAC_CSR11, reg); + + rt2x00_set_field32(®, MAC_CSR11_AUTOWAKE, 1); + rt2x00mmio_register_write(rt2x00dev, MAC_CSR11, reg); + + rt2x00mmio_register_write(rt2x00dev, SOFT_RESET_CSR, + 0x00000005); + rt2x00mmio_register_write(rt2x00dev, IO_CNTL_CSR, 0x0000001c); + rt2x00mmio_register_write(rt2x00dev, PCI_USEC_CSR, 0x00000060); + + rt61pci_mcu_request(rt2x00dev, MCU_SLEEP, 0xff, 0, 0); + } else { + rt2x00mmio_register_read(rt2x00dev, MAC_CSR11, ®); + rt2x00_set_field32(®, MAC_CSR11_DELAY_AFTER_TBCN, 0); + rt2x00_set_field32(®, MAC_CSR11_TBCN_BEFORE_WAKEUP, 0); + rt2x00_set_field32(®, MAC_CSR11_AUTOWAKE, 0); + rt2x00_set_field32(®, MAC_CSR11_WAKEUP_LATENCY, 0); + rt2x00mmio_register_write(rt2x00dev, MAC_CSR11, reg); + + rt2x00mmio_register_write(rt2x00dev, SOFT_RESET_CSR, + 0x00000007); + rt2x00mmio_register_write(rt2x00dev, IO_CNTL_CSR, 0x00000018); + rt2x00mmio_register_write(rt2x00dev, PCI_USEC_CSR, 0x00000020); + + rt61pci_mcu_request(rt2x00dev, MCU_WAKEUP, 0xff, 0, 0); + } +} + +static void rt61pci_config(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf, + const unsigned int flags) +{ + /* Always recalculate LNA gain before changing configuration */ + rt61pci_config_lna_gain(rt2x00dev, libconf); + + if (flags & IEEE80211_CONF_CHANGE_CHANNEL) + rt61pci_config_channel(rt2x00dev, &libconf->rf, + libconf->conf->power_level); + if ((flags & IEEE80211_CONF_CHANGE_POWER) && + !(flags & IEEE80211_CONF_CHANGE_CHANNEL)) + rt61pci_config_txpower(rt2x00dev, libconf->conf->power_level); + if (flags & IEEE80211_CONF_CHANGE_RETRY_LIMITS) + rt61pci_config_retry_limit(rt2x00dev, libconf); + if (flags & IEEE80211_CONF_CHANGE_PS) + rt61pci_config_ps(rt2x00dev, libconf); +} + +/* + * Link tuning + */ +static void rt61pci_link_stats(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual) +{ + u32 reg; + + /* + * Update FCS error count from register. + */ + rt2x00mmio_register_read(rt2x00dev, STA_CSR0, ®); + qual->rx_failed = rt2x00_get_field32(reg, STA_CSR0_FCS_ERROR); + + /* + * Update False CCA count from register. + */ + rt2x00mmio_register_read(rt2x00dev, STA_CSR1, ®); + qual->false_cca = rt2x00_get_field32(reg, STA_CSR1_FALSE_CCA_ERROR); +} + +static inline void rt61pci_set_vgc(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual, u8 vgc_level) +{ + if (qual->vgc_level != vgc_level) { + rt61pci_bbp_write(rt2x00dev, 17, vgc_level); + qual->vgc_level = vgc_level; + qual->vgc_level_reg = vgc_level; + } +} + +static void rt61pci_reset_tuner(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual) +{ + rt61pci_set_vgc(rt2x00dev, qual, 0x20); +} + +static void rt61pci_link_tuner(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual, const u32 count) +{ + u8 up_bound; + u8 low_bound; + + /* + * Determine r17 bounds. + */ + if (rt2x00dev->curr_band == IEEE80211_BAND_5GHZ) { + low_bound = 0x28; + up_bound = 0x48; + if (test_bit(CAPABILITY_EXTERNAL_LNA_A, &rt2x00dev->cap_flags)) { + low_bound += 0x10; + up_bound += 0x10; + } + } else { + low_bound = 0x20; + up_bound = 0x40; + if (test_bit(CAPABILITY_EXTERNAL_LNA_BG, &rt2x00dev->cap_flags)) { + low_bound += 0x10; + up_bound += 0x10; + } + } + + /* + * If we are not associated, we should go straight to the + * dynamic CCA tuning. + */ + if (!rt2x00dev->intf_associated) + goto dynamic_cca_tune; + + /* + * Special big-R17 for very short distance + */ + if (qual->rssi >= -35) { + rt61pci_set_vgc(rt2x00dev, qual, 0x60); + return; + } + + /* + * Special big-R17 for short distance + */ + if (qual->rssi >= -58) { + rt61pci_set_vgc(rt2x00dev, qual, up_bound); + return; + } + + /* + * Special big-R17 for middle-short distance + */ + if (qual->rssi >= -66) { + rt61pci_set_vgc(rt2x00dev, qual, low_bound + 0x10); + return; + } + + /* + * Special mid-R17 for middle distance + */ + if (qual->rssi >= -74) { + rt61pci_set_vgc(rt2x00dev, qual, low_bound + 0x08); + return; + } + + /* + * Special case: Change up_bound based on the rssi. + * Lower up_bound when rssi is weaker then -74 dBm. + */ + up_bound -= 2 * (-74 - qual->rssi); + if (low_bound > up_bound) + up_bound = low_bound; + + if (qual->vgc_level > up_bound) { + rt61pci_set_vgc(rt2x00dev, qual, up_bound); + return; + } + +dynamic_cca_tune: + + /* + * r17 does not yet exceed upper limit, continue and base + * the r17 tuning on the false CCA count. + */ + if ((qual->false_cca > 512) && (qual->vgc_level < up_bound)) + rt61pci_set_vgc(rt2x00dev, qual, ++qual->vgc_level); + else if ((qual->false_cca < 100) && (qual->vgc_level > low_bound)) + rt61pci_set_vgc(rt2x00dev, qual, --qual->vgc_level); +} + +/* + * Queue handlers. + */ +static void rt61pci_start_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u32 reg; + + switch (queue->qid) { + case QID_RX: + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR0, ®); + rt2x00_set_field32(®, TXRX_CSR0_DISABLE_RX, 0); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR0, reg); + break; + case QID_BEACON: + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR9, ®); + rt2x00_set_field32(®, TXRX_CSR9_TSF_TICKING, 1); + rt2x00_set_field32(®, TXRX_CSR9_TBTT_ENABLE, 1); + rt2x00_set_field32(®, TXRX_CSR9_BEACON_GEN, 1); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR9, reg); + break; + default: + break; + } +} + +static void rt61pci_kick_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u32 reg; + + switch (queue->qid) { + case QID_AC_VO: + rt2x00mmio_register_read(rt2x00dev, TX_CNTL_CSR, ®); + rt2x00_set_field32(®, TX_CNTL_CSR_KICK_TX_AC0, 1); + rt2x00mmio_register_write(rt2x00dev, TX_CNTL_CSR, reg); + break; + case QID_AC_VI: + rt2x00mmio_register_read(rt2x00dev, TX_CNTL_CSR, ®); + rt2x00_set_field32(®, TX_CNTL_CSR_KICK_TX_AC1, 1); + rt2x00mmio_register_write(rt2x00dev, TX_CNTL_CSR, reg); + break; + case QID_AC_BE: + rt2x00mmio_register_read(rt2x00dev, TX_CNTL_CSR, ®); + rt2x00_set_field32(®, TX_CNTL_CSR_KICK_TX_AC2, 1); + rt2x00mmio_register_write(rt2x00dev, TX_CNTL_CSR, reg); + break; + case QID_AC_BK: + rt2x00mmio_register_read(rt2x00dev, TX_CNTL_CSR, ®); + rt2x00_set_field32(®, TX_CNTL_CSR_KICK_TX_AC3, 1); + rt2x00mmio_register_write(rt2x00dev, TX_CNTL_CSR, reg); + break; + default: + break; + } +} + +static void rt61pci_stop_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u32 reg; + + switch (queue->qid) { + case QID_AC_VO: + rt2x00mmio_register_read(rt2x00dev, TX_CNTL_CSR, ®); + rt2x00_set_field32(®, TX_CNTL_CSR_ABORT_TX_AC0, 1); + rt2x00mmio_register_write(rt2x00dev, TX_CNTL_CSR, reg); + break; + case QID_AC_VI: + rt2x00mmio_register_read(rt2x00dev, TX_CNTL_CSR, ®); + rt2x00_set_field32(®, TX_CNTL_CSR_ABORT_TX_AC1, 1); + rt2x00mmio_register_write(rt2x00dev, TX_CNTL_CSR, reg); + break; + case QID_AC_BE: + rt2x00mmio_register_read(rt2x00dev, TX_CNTL_CSR, ®); + rt2x00_set_field32(®, TX_CNTL_CSR_ABORT_TX_AC2, 1); + rt2x00mmio_register_write(rt2x00dev, TX_CNTL_CSR, reg); + break; + case QID_AC_BK: + rt2x00mmio_register_read(rt2x00dev, TX_CNTL_CSR, ®); + rt2x00_set_field32(®, TX_CNTL_CSR_ABORT_TX_AC3, 1); + rt2x00mmio_register_write(rt2x00dev, TX_CNTL_CSR, reg); + break; + case QID_RX: + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR0, ®); + rt2x00_set_field32(®, TXRX_CSR0_DISABLE_RX, 1); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR0, reg); + break; + case QID_BEACON: + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR9, ®); + rt2x00_set_field32(®, TXRX_CSR9_TSF_TICKING, 0); + rt2x00_set_field32(®, TXRX_CSR9_TBTT_ENABLE, 0); + rt2x00_set_field32(®, TXRX_CSR9_BEACON_GEN, 0); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR9, reg); + + /* + * Wait for possibly running tbtt tasklets. + */ + tasklet_kill(&rt2x00dev->tbtt_tasklet); + break; + default: + break; + } +} + +/* + * Firmware functions + */ +static char *rt61pci_get_firmware_name(struct rt2x00_dev *rt2x00dev) +{ + u16 chip; + char *fw_name; + + pci_read_config_word(to_pci_dev(rt2x00dev->dev), PCI_DEVICE_ID, &chip); + switch (chip) { + case RT2561_PCI_ID: + fw_name = FIRMWARE_RT2561; + break; + case RT2561s_PCI_ID: + fw_name = FIRMWARE_RT2561s; + break; + case RT2661_PCI_ID: + fw_name = FIRMWARE_RT2661; + break; + default: + fw_name = NULL; + break; + } + + return fw_name; +} + +static int rt61pci_check_firmware(struct rt2x00_dev *rt2x00dev, + const u8 *data, const size_t len) +{ + u16 fw_crc; + u16 crc; + + /* + * Only support 8kb firmware files. + */ + if (len != 8192) + return FW_BAD_LENGTH; + + /* + * The last 2 bytes in the firmware array are the crc checksum itself. + * This means that we should never pass those 2 bytes to the crc + * algorithm. + */ + fw_crc = (data[len - 2] << 8 | data[len - 1]); + + /* + * Use the crc itu-t algorithm. + */ + crc = crc_itu_t(0, data, len - 2); + crc = crc_itu_t_byte(crc, 0); + crc = crc_itu_t_byte(crc, 0); + + return (fw_crc == crc) ? FW_OK : FW_BAD_CRC; +} + +static int rt61pci_load_firmware(struct rt2x00_dev *rt2x00dev, + const u8 *data, const size_t len) +{ + int i; + u32 reg; + + /* + * Wait for stable hardware. + */ + for (i = 0; i < 100; i++) { + rt2x00mmio_register_read(rt2x00dev, MAC_CSR0, ®); + if (reg) + break; + msleep(1); + } + + if (!reg) { + rt2x00_err(rt2x00dev, "Unstable hardware\n"); + return -EBUSY; + } + + /* + * Prepare MCU and mailbox for firmware loading. + */ + reg = 0; + rt2x00_set_field32(®, MCU_CNTL_CSR_RESET, 1); + rt2x00mmio_register_write(rt2x00dev, MCU_CNTL_CSR, reg); + rt2x00mmio_register_write(rt2x00dev, M2H_CMD_DONE_CSR, 0xffffffff); + rt2x00mmio_register_write(rt2x00dev, H2M_MAILBOX_CSR, 0); + rt2x00mmio_register_write(rt2x00dev, HOST_CMD_CSR, 0); + + /* + * Write firmware to device. + */ + reg = 0; + rt2x00_set_field32(®, MCU_CNTL_CSR_RESET, 1); + rt2x00_set_field32(®, MCU_CNTL_CSR_SELECT_BANK, 1); + rt2x00mmio_register_write(rt2x00dev, MCU_CNTL_CSR, reg); + + rt2x00mmio_register_multiwrite(rt2x00dev, FIRMWARE_IMAGE_BASE, + data, len); + + rt2x00_set_field32(®, MCU_CNTL_CSR_SELECT_BANK, 0); + rt2x00mmio_register_write(rt2x00dev, MCU_CNTL_CSR, reg); + + rt2x00_set_field32(®, MCU_CNTL_CSR_RESET, 0); + rt2x00mmio_register_write(rt2x00dev, MCU_CNTL_CSR, reg); + + for (i = 0; i < 100; i++) { + rt2x00mmio_register_read(rt2x00dev, MCU_CNTL_CSR, ®); + if (rt2x00_get_field32(reg, MCU_CNTL_CSR_READY)) + break; + msleep(1); + } + + if (i == 100) { + rt2x00_err(rt2x00dev, "MCU Control register not ready\n"); + return -EBUSY; + } + + /* + * Hardware needs another millisecond before it is ready. + */ + msleep(1); + + /* + * Reset MAC and BBP registers. + */ + reg = 0; + rt2x00_set_field32(®, MAC_CSR1_SOFT_RESET, 1); + rt2x00_set_field32(®, MAC_CSR1_BBP_RESET, 1); + rt2x00mmio_register_write(rt2x00dev, MAC_CSR1, reg); + + rt2x00mmio_register_read(rt2x00dev, MAC_CSR1, ®); + rt2x00_set_field32(®, MAC_CSR1_SOFT_RESET, 0); + rt2x00_set_field32(®, MAC_CSR1_BBP_RESET, 0); + rt2x00mmio_register_write(rt2x00dev, MAC_CSR1, reg); + + rt2x00mmio_register_read(rt2x00dev, MAC_CSR1, ®); + rt2x00_set_field32(®, MAC_CSR1_HOST_READY, 1); + rt2x00mmio_register_write(rt2x00dev, MAC_CSR1, reg); + + return 0; +} + +/* + * Initialization functions. + */ +static bool rt61pci_get_entry_state(struct queue_entry *entry) +{ + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + u32 word; + + if (entry->queue->qid == QID_RX) { + rt2x00_desc_read(entry_priv->desc, 0, &word); + + return rt2x00_get_field32(word, RXD_W0_OWNER_NIC); + } else { + rt2x00_desc_read(entry_priv->desc, 0, &word); + + return (rt2x00_get_field32(word, TXD_W0_OWNER_NIC) || + rt2x00_get_field32(word, TXD_W0_VALID)); + } +} + +static void rt61pci_clear_entry(struct queue_entry *entry) +{ + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + u32 word; + + if (entry->queue->qid == QID_RX) { + rt2x00_desc_read(entry_priv->desc, 5, &word); + rt2x00_set_field32(&word, RXD_W5_BUFFER_PHYSICAL_ADDRESS, + skbdesc->skb_dma); + rt2x00_desc_write(entry_priv->desc, 5, word); + + rt2x00_desc_read(entry_priv->desc, 0, &word); + rt2x00_set_field32(&word, RXD_W0_OWNER_NIC, 1); + rt2x00_desc_write(entry_priv->desc, 0, word); + } else { + rt2x00_desc_read(entry_priv->desc, 0, &word); + rt2x00_set_field32(&word, TXD_W0_VALID, 0); + rt2x00_set_field32(&word, TXD_W0_OWNER_NIC, 0); + rt2x00_desc_write(entry_priv->desc, 0, word); + } +} + +static int rt61pci_init_queues(struct rt2x00_dev *rt2x00dev) +{ + struct queue_entry_priv_mmio *entry_priv; + u32 reg; + + /* + * Initialize registers. + */ + rt2x00mmio_register_read(rt2x00dev, TX_RING_CSR0, ®); + rt2x00_set_field32(®, TX_RING_CSR0_AC0_RING_SIZE, + rt2x00dev->tx[0].limit); + rt2x00_set_field32(®, TX_RING_CSR0_AC1_RING_SIZE, + rt2x00dev->tx[1].limit); + rt2x00_set_field32(®, TX_RING_CSR0_AC2_RING_SIZE, + rt2x00dev->tx[2].limit); + rt2x00_set_field32(®, TX_RING_CSR0_AC3_RING_SIZE, + rt2x00dev->tx[3].limit); + rt2x00mmio_register_write(rt2x00dev, TX_RING_CSR0, reg); + + rt2x00mmio_register_read(rt2x00dev, TX_RING_CSR1, ®); + rt2x00_set_field32(®, TX_RING_CSR1_TXD_SIZE, + rt2x00dev->tx[0].desc_size / 4); + rt2x00mmio_register_write(rt2x00dev, TX_RING_CSR1, reg); + + entry_priv = rt2x00dev->tx[0].entries[0].priv_data; + rt2x00mmio_register_read(rt2x00dev, AC0_BASE_CSR, ®); + rt2x00_set_field32(®, AC0_BASE_CSR_RING_REGISTER, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, AC0_BASE_CSR, reg); + + entry_priv = rt2x00dev->tx[1].entries[0].priv_data; + rt2x00mmio_register_read(rt2x00dev, AC1_BASE_CSR, ®); + rt2x00_set_field32(®, AC1_BASE_CSR_RING_REGISTER, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, AC1_BASE_CSR, reg); + + entry_priv = rt2x00dev->tx[2].entries[0].priv_data; + rt2x00mmio_register_read(rt2x00dev, AC2_BASE_CSR, ®); + rt2x00_set_field32(®, AC2_BASE_CSR_RING_REGISTER, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, AC2_BASE_CSR, reg); + + entry_priv = rt2x00dev->tx[3].entries[0].priv_data; + rt2x00mmio_register_read(rt2x00dev, AC3_BASE_CSR, ®); + rt2x00_set_field32(®, AC3_BASE_CSR_RING_REGISTER, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, AC3_BASE_CSR, reg); + + rt2x00mmio_register_read(rt2x00dev, RX_RING_CSR, ®); + rt2x00_set_field32(®, RX_RING_CSR_RING_SIZE, rt2x00dev->rx->limit); + rt2x00_set_field32(®, RX_RING_CSR_RXD_SIZE, + rt2x00dev->rx->desc_size / 4); + rt2x00_set_field32(®, RX_RING_CSR_RXD_WRITEBACK_SIZE, 4); + rt2x00mmio_register_write(rt2x00dev, RX_RING_CSR, reg); + + entry_priv = rt2x00dev->rx->entries[0].priv_data; + rt2x00mmio_register_read(rt2x00dev, RX_BASE_CSR, ®); + rt2x00_set_field32(®, RX_BASE_CSR_RING_REGISTER, + entry_priv->desc_dma); + rt2x00mmio_register_write(rt2x00dev, RX_BASE_CSR, reg); + + rt2x00mmio_register_read(rt2x00dev, TX_DMA_DST_CSR, ®); + rt2x00_set_field32(®, TX_DMA_DST_CSR_DEST_AC0, 2); + rt2x00_set_field32(®, TX_DMA_DST_CSR_DEST_AC1, 2); + rt2x00_set_field32(®, TX_DMA_DST_CSR_DEST_AC2, 2); + rt2x00_set_field32(®, TX_DMA_DST_CSR_DEST_AC3, 2); + rt2x00mmio_register_write(rt2x00dev, TX_DMA_DST_CSR, reg); + + rt2x00mmio_register_read(rt2x00dev, LOAD_TX_RING_CSR, ®); + rt2x00_set_field32(®, LOAD_TX_RING_CSR_LOAD_TXD_AC0, 1); + rt2x00_set_field32(®, LOAD_TX_RING_CSR_LOAD_TXD_AC1, 1); + rt2x00_set_field32(®, LOAD_TX_RING_CSR_LOAD_TXD_AC2, 1); + rt2x00_set_field32(®, LOAD_TX_RING_CSR_LOAD_TXD_AC3, 1); + rt2x00mmio_register_write(rt2x00dev, LOAD_TX_RING_CSR, reg); + + rt2x00mmio_register_read(rt2x00dev, RX_CNTL_CSR, ®); + rt2x00_set_field32(®, RX_CNTL_CSR_LOAD_RXD, 1); + rt2x00mmio_register_write(rt2x00dev, RX_CNTL_CSR, reg); + + return 0; +} + +static int rt61pci_init_registers(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR0, ®); + rt2x00_set_field32(®, TXRX_CSR0_AUTO_TX_SEQ, 1); + rt2x00_set_field32(®, TXRX_CSR0_DISABLE_RX, 0); + rt2x00_set_field32(®, TXRX_CSR0_TX_WITHOUT_WAITING, 0); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR0, reg); + + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR1, ®); + rt2x00_set_field32(®, TXRX_CSR1_BBP_ID0, 47); /* CCK Signal */ + rt2x00_set_field32(®, TXRX_CSR1_BBP_ID0_VALID, 1); + rt2x00_set_field32(®, TXRX_CSR1_BBP_ID1, 30); /* Rssi */ + rt2x00_set_field32(®, TXRX_CSR1_BBP_ID1_VALID, 1); + rt2x00_set_field32(®, TXRX_CSR1_BBP_ID2, 42); /* OFDM Rate */ + rt2x00_set_field32(®, TXRX_CSR1_BBP_ID2_VALID, 1); + rt2x00_set_field32(®, TXRX_CSR1_BBP_ID3, 30); /* Rssi */ + rt2x00_set_field32(®, TXRX_CSR1_BBP_ID3_VALID, 1); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR1, reg); + + /* + * CCK TXD BBP registers + */ + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR2, ®); + rt2x00_set_field32(®, TXRX_CSR2_BBP_ID0, 13); + rt2x00_set_field32(®, TXRX_CSR2_BBP_ID0_VALID, 1); + rt2x00_set_field32(®, TXRX_CSR2_BBP_ID1, 12); + rt2x00_set_field32(®, TXRX_CSR2_BBP_ID1_VALID, 1); + rt2x00_set_field32(®, TXRX_CSR2_BBP_ID2, 11); + rt2x00_set_field32(®, TXRX_CSR2_BBP_ID2_VALID, 1); + rt2x00_set_field32(®, TXRX_CSR2_BBP_ID3, 10); + rt2x00_set_field32(®, TXRX_CSR2_BBP_ID3_VALID, 1); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR2, reg); + + /* + * OFDM TXD BBP registers + */ + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR3, ®); + rt2x00_set_field32(®, TXRX_CSR3_BBP_ID0, 7); + rt2x00_set_field32(®, TXRX_CSR3_BBP_ID0_VALID, 1); + rt2x00_set_field32(®, TXRX_CSR3_BBP_ID1, 6); + rt2x00_set_field32(®, TXRX_CSR3_BBP_ID1_VALID, 1); + rt2x00_set_field32(®, TXRX_CSR3_BBP_ID2, 5); + rt2x00_set_field32(®, TXRX_CSR3_BBP_ID2_VALID, 1); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR3, reg); + + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR7, ®); + rt2x00_set_field32(®, TXRX_CSR7_ACK_CTS_6MBS, 59); + rt2x00_set_field32(®, TXRX_CSR7_ACK_CTS_9MBS, 53); + rt2x00_set_field32(®, TXRX_CSR7_ACK_CTS_12MBS, 49); + rt2x00_set_field32(®, TXRX_CSR7_ACK_CTS_18MBS, 46); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR7, reg); + + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR8, ®); + rt2x00_set_field32(®, TXRX_CSR8_ACK_CTS_24MBS, 44); + rt2x00_set_field32(®, TXRX_CSR8_ACK_CTS_36MBS, 42); + rt2x00_set_field32(®, TXRX_CSR8_ACK_CTS_48MBS, 42); + rt2x00_set_field32(®, TXRX_CSR8_ACK_CTS_54MBS, 42); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR8, reg); + + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR9, ®); + rt2x00_set_field32(®, TXRX_CSR9_BEACON_INTERVAL, 0); + rt2x00_set_field32(®, TXRX_CSR9_TSF_TICKING, 0); + rt2x00_set_field32(®, TXRX_CSR9_TSF_SYNC, 0); + rt2x00_set_field32(®, TXRX_CSR9_TBTT_ENABLE, 0); + rt2x00_set_field32(®, TXRX_CSR9_BEACON_GEN, 0); + rt2x00_set_field32(®, TXRX_CSR9_TIMESTAMP_COMPENSATE, 0); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR9, reg); + + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR15, 0x0000000f); + + rt2x00mmio_register_write(rt2x00dev, MAC_CSR6, 0x00000fff); + + rt2x00mmio_register_read(rt2x00dev, MAC_CSR9, ®); + rt2x00_set_field32(®, MAC_CSR9_CW_SELECT, 0); + rt2x00mmio_register_write(rt2x00dev, MAC_CSR9, reg); + + rt2x00mmio_register_write(rt2x00dev, MAC_CSR10, 0x0000071c); + + if (rt2x00dev->ops->lib->set_device_state(rt2x00dev, STATE_AWAKE)) + return -EBUSY; + + rt2x00mmio_register_write(rt2x00dev, MAC_CSR13, 0x0000e000); + + /* + * Invalidate all Shared Keys (SEC_CSR0), + * and clear the Shared key Cipher algorithms (SEC_CSR1 & SEC_CSR5) + */ + rt2x00mmio_register_write(rt2x00dev, SEC_CSR0, 0x00000000); + rt2x00mmio_register_write(rt2x00dev, SEC_CSR1, 0x00000000); + rt2x00mmio_register_write(rt2x00dev, SEC_CSR5, 0x00000000); + + rt2x00mmio_register_write(rt2x00dev, PHY_CSR1, 0x000023b0); + rt2x00mmio_register_write(rt2x00dev, PHY_CSR5, 0x060a100c); + rt2x00mmio_register_write(rt2x00dev, PHY_CSR6, 0x00080606); + rt2x00mmio_register_write(rt2x00dev, PHY_CSR7, 0x00000a08); + + rt2x00mmio_register_write(rt2x00dev, PCI_CFG_CSR, 0x28ca4404); + + rt2x00mmio_register_write(rt2x00dev, TEST_MODE_CSR, 0x00000200); + + rt2x00mmio_register_write(rt2x00dev, M2H_CMD_DONE_CSR, 0xffffffff); + + /* + * Clear all beacons + * For the Beacon base registers we only need to clear + * the first byte since that byte contains the VALID and OWNER + * bits which (when set to 0) will invalidate the entire beacon. + */ + rt2x00mmio_register_write(rt2x00dev, HW_BEACON_BASE0, 0); + rt2x00mmio_register_write(rt2x00dev, HW_BEACON_BASE1, 0); + rt2x00mmio_register_write(rt2x00dev, HW_BEACON_BASE2, 0); + rt2x00mmio_register_write(rt2x00dev, HW_BEACON_BASE3, 0); + + /* + * We must clear the error counters. + * These registers are cleared on read, + * so we may pass a useless variable to store the value. + */ + rt2x00mmio_register_read(rt2x00dev, STA_CSR0, ®); + rt2x00mmio_register_read(rt2x00dev, STA_CSR1, ®); + rt2x00mmio_register_read(rt2x00dev, STA_CSR2, ®); + + /* + * Reset MAC and BBP registers. + */ + rt2x00mmio_register_read(rt2x00dev, MAC_CSR1, ®); + rt2x00_set_field32(®, MAC_CSR1_SOFT_RESET, 1); + rt2x00_set_field32(®, MAC_CSR1_BBP_RESET, 1); + rt2x00mmio_register_write(rt2x00dev, MAC_CSR1, reg); + + rt2x00mmio_register_read(rt2x00dev, MAC_CSR1, ®); + rt2x00_set_field32(®, MAC_CSR1_SOFT_RESET, 0); + rt2x00_set_field32(®, MAC_CSR1_BBP_RESET, 0); + rt2x00mmio_register_write(rt2x00dev, MAC_CSR1, reg); + + rt2x00mmio_register_read(rt2x00dev, MAC_CSR1, ®); + rt2x00_set_field32(®, MAC_CSR1_HOST_READY, 1); + rt2x00mmio_register_write(rt2x00dev, MAC_CSR1, reg); + + return 0; +} + +static int rt61pci_wait_bbp_ready(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i; + u8 value; + + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt61pci_bbp_read(rt2x00dev, 0, &value); + if ((value != 0xff) && (value != 0x00)) + return 0; + udelay(REGISTER_BUSY_DELAY); + } + + rt2x00_err(rt2x00dev, "BBP register access failed, aborting\n"); + return -EACCES; +} + +static int rt61pci_init_bbp(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i; + u16 eeprom; + u8 reg_id; + u8 value; + + if (unlikely(rt61pci_wait_bbp_ready(rt2x00dev))) + return -EACCES; + + rt61pci_bbp_write(rt2x00dev, 3, 0x00); + rt61pci_bbp_write(rt2x00dev, 15, 0x30); + rt61pci_bbp_write(rt2x00dev, 21, 0xc8); + rt61pci_bbp_write(rt2x00dev, 22, 0x38); + rt61pci_bbp_write(rt2x00dev, 23, 0x06); + rt61pci_bbp_write(rt2x00dev, 24, 0xfe); + rt61pci_bbp_write(rt2x00dev, 25, 0x0a); + rt61pci_bbp_write(rt2x00dev, 26, 0x0d); + rt61pci_bbp_write(rt2x00dev, 34, 0x12); + rt61pci_bbp_write(rt2x00dev, 37, 0x07); + rt61pci_bbp_write(rt2x00dev, 39, 0xf8); + rt61pci_bbp_write(rt2x00dev, 41, 0x60); + rt61pci_bbp_write(rt2x00dev, 53, 0x10); + rt61pci_bbp_write(rt2x00dev, 54, 0x18); + rt61pci_bbp_write(rt2x00dev, 60, 0x10); + rt61pci_bbp_write(rt2x00dev, 61, 0x04); + rt61pci_bbp_write(rt2x00dev, 62, 0x04); + rt61pci_bbp_write(rt2x00dev, 75, 0xfe); + rt61pci_bbp_write(rt2x00dev, 86, 0xfe); + rt61pci_bbp_write(rt2x00dev, 88, 0xfe); + rt61pci_bbp_write(rt2x00dev, 90, 0x0f); + rt61pci_bbp_write(rt2x00dev, 99, 0x00); + rt61pci_bbp_write(rt2x00dev, 102, 0x16); + rt61pci_bbp_write(rt2x00dev, 107, 0x04); + + for (i = 0; i < EEPROM_BBP_SIZE; i++) { + rt2x00_eeprom_read(rt2x00dev, EEPROM_BBP_START + i, &eeprom); + + if (eeprom != 0xffff && eeprom != 0x0000) { + reg_id = rt2x00_get_field16(eeprom, EEPROM_BBP_REG_ID); + value = rt2x00_get_field16(eeprom, EEPROM_BBP_VALUE); + rt61pci_bbp_write(rt2x00dev, reg_id, value); + } + } + + return 0; +} + +/* + * Device state switch handlers. + */ +static void rt61pci_toggle_irq(struct rt2x00_dev *rt2x00dev, + enum dev_state state) +{ + int mask = (state == STATE_RADIO_IRQ_OFF); + u32 reg; + unsigned long flags; + + /* + * When interrupts are being enabled, the interrupt registers + * should clear the register to assure a clean state. + */ + if (state == STATE_RADIO_IRQ_ON) { + rt2x00mmio_register_read(rt2x00dev, INT_SOURCE_CSR, ®); + rt2x00mmio_register_write(rt2x00dev, INT_SOURCE_CSR, reg); + + rt2x00mmio_register_read(rt2x00dev, MCU_INT_SOURCE_CSR, ®); + rt2x00mmio_register_write(rt2x00dev, MCU_INT_SOURCE_CSR, reg); + } + + /* + * Only toggle the interrupts bits we are going to use. + * Non-checked interrupt bits are disabled by default. + */ + spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags); + + rt2x00mmio_register_read(rt2x00dev, INT_MASK_CSR, ®); + rt2x00_set_field32(®, INT_MASK_CSR_TXDONE, mask); + rt2x00_set_field32(®, INT_MASK_CSR_RXDONE, mask); + rt2x00_set_field32(®, INT_MASK_CSR_BEACON_DONE, mask); + rt2x00_set_field32(®, INT_MASK_CSR_ENABLE_MITIGATION, mask); + rt2x00_set_field32(®, INT_MASK_CSR_MITIGATION_PERIOD, 0xff); + rt2x00mmio_register_write(rt2x00dev, INT_MASK_CSR, reg); + + rt2x00mmio_register_read(rt2x00dev, MCU_INT_MASK_CSR, ®); + rt2x00_set_field32(®, MCU_INT_MASK_CSR_0, mask); + rt2x00_set_field32(®, MCU_INT_MASK_CSR_1, mask); + rt2x00_set_field32(®, MCU_INT_MASK_CSR_2, mask); + rt2x00_set_field32(®, MCU_INT_MASK_CSR_3, mask); + rt2x00_set_field32(®, MCU_INT_MASK_CSR_4, mask); + rt2x00_set_field32(®, MCU_INT_MASK_CSR_5, mask); + rt2x00_set_field32(®, MCU_INT_MASK_CSR_6, mask); + rt2x00_set_field32(®, MCU_INT_MASK_CSR_7, mask); + rt2x00_set_field32(®, MCU_INT_MASK_CSR_TWAKEUP, mask); + rt2x00mmio_register_write(rt2x00dev, MCU_INT_MASK_CSR, reg); + + spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags); + + if (state == STATE_RADIO_IRQ_OFF) { + /* + * Ensure that all tasklets are finished. + */ + tasklet_kill(&rt2x00dev->txstatus_tasklet); + tasklet_kill(&rt2x00dev->rxdone_tasklet); + tasklet_kill(&rt2x00dev->autowake_tasklet); + tasklet_kill(&rt2x00dev->tbtt_tasklet); + } +} + +static int rt61pci_enable_radio(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + /* + * Initialize all registers. + */ + if (unlikely(rt61pci_init_queues(rt2x00dev) || + rt61pci_init_registers(rt2x00dev) || + rt61pci_init_bbp(rt2x00dev))) + return -EIO; + + /* + * Enable RX. + */ + rt2x00mmio_register_read(rt2x00dev, RX_CNTL_CSR, ®); + rt2x00_set_field32(®, RX_CNTL_CSR_ENABLE_RX_DMA, 1); + rt2x00mmio_register_write(rt2x00dev, RX_CNTL_CSR, reg); + + return 0; +} + +static void rt61pci_disable_radio(struct rt2x00_dev *rt2x00dev) +{ + /* + * Disable power + */ + rt2x00mmio_register_write(rt2x00dev, MAC_CSR10, 0x00001818); +} + +static int rt61pci_set_state(struct rt2x00_dev *rt2x00dev, enum dev_state state) +{ + u32 reg, reg2; + unsigned int i; + char put_to_sleep; + + put_to_sleep = (state != STATE_AWAKE); + + rt2x00mmio_register_read(rt2x00dev, MAC_CSR12, ®); + rt2x00_set_field32(®, MAC_CSR12_FORCE_WAKEUP, !put_to_sleep); + rt2x00_set_field32(®, MAC_CSR12_PUT_TO_SLEEP, put_to_sleep); + rt2x00mmio_register_write(rt2x00dev, MAC_CSR12, reg); + + /* + * Device is not guaranteed to be in the requested state yet. + * We must wait until the register indicates that the + * device has entered the correct state. + */ + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2x00mmio_register_read(rt2x00dev, MAC_CSR12, ®2); + state = rt2x00_get_field32(reg2, MAC_CSR12_BBP_CURRENT_STATE); + if (state == !put_to_sleep) + return 0; + rt2x00mmio_register_write(rt2x00dev, MAC_CSR12, reg); + msleep(10); + } + + return -EBUSY; +} + +static int rt61pci_set_device_state(struct rt2x00_dev *rt2x00dev, + enum dev_state state) +{ + int retval = 0; + + switch (state) { + case STATE_RADIO_ON: + retval = rt61pci_enable_radio(rt2x00dev); + break; + case STATE_RADIO_OFF: + rt61pci_disable_radio(rt2x00dev); + break; + case STATE_RADIO_IRQ_ON: + case STATE_RADIO_IRQ_OFF: + rt61pci_toggle_irq(rt2x00dev, state); + break; + case STATE_DEEP_SLEEP: + case STATE_SLEEP: + case STATE_STANDBY: + case STATE_AWAKE: + retval = rt61pci_set_state(rt2x00dev, state); + break; + default: + retval = -ENOTSUPP; + break; + } + + if (unlikely(retval)) + rt2x00_err(rt2x00dev, "Device failed to enter state %d (%d)\n", + state, retval); + + return retval; +} + +/* + * TX descriptor initialization + */ +static void rt61pci_write_tx_desc(struct queue_entry *entry, + struct txentry_desc *txdesc) +{ + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + __le32 *txd = entry_priv->desc; + u32 word; + + /* + * Start writing the descriptor words. + */ + rt2x00_desc_read(txd, 1, &word); + rt2x00_set_field32(&word, TXD_W1_HOST_Q_ID, entry->queue->qid); + rt2x00_set_field32(&word, TXD_W1_AIFSN, entry->queue->aifs); + rt2x00_set_field32(&word, TXD_W1_CWMIN, entry->queue->cw_min); + rt2x00_set_field32(&word, TXD_W1_CWMAX, entry->queue->cw_max); + rt2x00_set_field32(&word, TXD_W1_IV_OFFSET, txdesc->iv_offset); + rt2x00_set_field32(&word, TXD_W1_HW_SEQUENCE, + test_bit(ENTRY_TXD_GENERATE_SEQ, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W1_BUFFER_COUNT, 1); + rt2x00_desc_write(txd, 1, word); + + rt2x00_desc_read(txd, 2, &word); + rt2x00_set_field32(&word, TXD_W2_PLCP_SIGNAL, txdesc->u.plcp.signal); + rt2x00_set_field32(&word, TXD_W2_PLCP_SERVICE, txdesc->u.plcp.service); + rt2x00_set_field32(&word, TXD_W2_PLCP_LENGTH_LOW, + txdesc->u.plcp.length_low); + rt2x00_set_field32(&word, TXD_W2_PLCP_LENGTH_HIGH, + txdesc->u.plcp.length_high); + rt2x00_desc_write(txd, 2, word); + + if (test_bit(ENTRY_TXD_ENCRYPT, &txdesc->flags)) { + _rt2x00_desc_write(txd, 3, skbdesc->iv[0]); + _rt2x00_desc_write(txd, 4, skbdesc->iv[1]); + } + + rt2x00_desc_read(txd, 5, &word); + rt2x00_set_field32(&word, TXD_W5_PID_TYPE, entry->queue->qid); + rt2x00_set_field32(&word, TXD_W5_PID_SUBTYPE, + skbdesc->entry->entry_idx); + rt2x00_set_field32(&word, TXD_W5_TX_POWER, + TXPOWER_TO_DEV(entry->queue->rt2x00dev->tx_power)); + rt2x00_set_field32(&word, TXD_W5_WAITING_DMA_DONE_INT, 1); + rt2x00_desc_write(txd, 5, word); + + if (entry->queue->qid != QID_BEACON) { + rt2x00_desc_read(txd, 6, &word); + rt2x00_set_field32(&word, TXD_W6_BUFFER_PHYSICAL_ADDRESS, + skbdesc->skb_dma); + rt2x00_desc_write(txd, 6, word); + + rt2x00_desc_read(txd, 11, &word); + rt2x00_set_field32(&word, TXD_W11_BUFFER_LENGTH0, + txdesc->length); + rt2x00_desc_write(txd, 11, word); + } + + /* + * Writing TXD word 0 must the last to prevent a race condition with + * the device, whereby the device may take hold of the TXD before we + * finished updating it. + */ + rt2x00_desc_read(txd, 0, &word); + rt2x00_set_field32(&word, TXD_W0_OWNER_NIC, 1); + rt2x00_set_field32(&word, TXD_W0_VALID, 1); + rt2x00_set_field32(&word, TXD_W0_MORE_FRAG, + test_bit(ENTRY_TXD_MORE_FRAG, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_ACK, + test_bit(ENTRY_TXD_ACK, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_TIMESTAMP, + test_bit(ENTRY_TXD_REQ_TIMESTAMP, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_OFDM, + (txdesc->rate_mode == RATE_MODE_OFDM)); + rt2x00_set_field32(&word, TXD_W0_IFS, txdesc->u.plcp.ifs); + rt2x00_set_field32(&word, TXD_W0_RETRY_MODE, + test_bit(ENTRY_TXD_RETRY_MODE, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_TKIP_MIC, + test_bit(ENTRY_TXD_ENCRYPT_MMIC, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_KEY_TABLE, + test_bit(ENTRY_TXD_ENCRYPT_PAIRWISE, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_KEY_INDEX, txdesc->key_idx); + rt2x00_set_field32(&word, TXD_W0_DATABYTE_COUNT, txdesc->length); + rt2x00_set_field32(&word, TXD_W0_BURST, + test_bit(ENTRY_TXD_BURST, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_CIPHER_ALG, txdesc->cipher); + rt2x00_desc_write(txd, 0, word); + + /* + * Register descriptor details in skb frame descriptor. + */ + skbdesc->desc = txd; + skbdesc->desc_len = (entry->queue->qid == QID_BEACON) ? TXINFO_SIZE : + TXD_DESC_SIZE; +} + +/* + * TX data initialization + */ +static void rt61pci_write_beacon(struct queue_entry *entry, + struct txentry_desc *txdesc) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + unsigned int beacon_base; + unsigned int padding_len; + u32 orig_reg, reg; + + /* + * Disable beaconing while we are reloading the beacon data, + * otherwise we might be sending out invalid data. + */ + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR9, ®); + orig_reg = reg; + rt2x00_set_field32(®, TXRX_CSR9_BEACON_GEN, 0); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR9, reg); + + /* + * Write the TX descriptor for the beacon. + */ + rt61pci_write_tx_desc(entry, txdesc); + + /* + * Dump beacon to userspace through debugfs. + */ + rt2x00debug_dump_frame(rt2x00dev, DUMP_FRAME_BEACON, entry->skb); + + /* + * Write entire beacon with descriptor and padding to register. + */ + padding_len = roundup(entry->skb->len, 4) - entry->skb->len; + if (padding_len && skb_pad(entry->skb, padding_len)) { + rt2x00_err(rt2x00dev, "Failure padding beacon, aborting\n"); + /* skb freed by skb_pad() on failure */ + entry->skb = NULL; + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR9, orig_reg); + return; + } + + beacon_base = HW_BEACON_OFFSET(entry->entry_idx); + rt2x00mmio_register_multiwrite(rt2x00dev, beacon_base, + entry_priv->desc, TXINFO_SIZE); + rt2x00mmio_register_multiwrite(rt2x00dev, beacon_base + TXINFO_SIZE, + entry->skb->data, + entry->skb->len + padding_len); + + /* + * Enable beaconing again. + * + * For Wi-Fi faily generated beacons between participating + * stations. Set TBTT phase adaptive adjustment step to 8us. + */ + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR10, 0x00001008); + + rt2x00_set_field32(®, TXRX_CSR9_BEACON_GEN, 1); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR9, reg); + + /* + * Clean up beacon skb. + */ + dev_kfree_skb_any(entry->skb); + entry->skb = NULL; +} + +static void rt61pci_clear_beacon(struct queue_entry *entry) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + u32 reg; + + /* + * Disable beaconing while we are reloading the beacon data, + * otherwise we might be sending out invalid data. + */ + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR9, ®); + rt2x00_set_field32(®, TXRX_CSR9_BEACON_GEN, 0); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR9, reg); + + /* + * Clear beacon. + */ + rt2x00mmio_register_write(rt2x00dev, + HW_BEACON_OFFSET(entry->entry_idx), 0); + + /* + * Enable beaconing again. + */ + rt2x00_set_field32(®, TXRX_CSR9_BEACON_GEN, 1); + rt2x00mmio_register_write(rt2x00dev, TXRX_CSR9, reg); +} + +/* + * RX control handlers + */ +static int rt61pci_agc_to_rssi(struct rt2x00_dev *rt2x00dev, int rxd_w1) +{ + u8 offset = rt2x00dev->lna_gain; + u8 lna; + + lna = rt2x00_get_field32(rxd_w1, RXD_W1_RSSI_LNA); + switch (lna) { + case 3: + offset += 90; + break; + case 2: + offset += 74; + break; + case 1: + offset += 64; + break; + default: + return 0; + } + + if (rt2x00dev->curr_band == IEEE80211_BAND_5GHZ) { + if (lna == 3 || lna == 2) + offset += 10; + } + + return rt2x00_get_field32(rxd_w1, RXD_W1_RSSI_AGC) * 2 - offset; +} + +static void rt61pci_fill_rxdone(struct queue_entry *entry, + struct rxdone_entry_desc *rxdesc) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + struct queue_entry_priv_mmio *entry_priv = entry->priv_data; + u32 word0; + u32 word1; + + rt2x00_desc_read(entry_priv->desc, 0, &word0); + rt2x00_desc_read(entry_priv->desc, 1, &word1); + + if (rt2x00_get_field32(word0, RXD_W0_CRC_ERROR)) + rxdesc->flags |= RX_FLAG_FAILED_FCS_CRC; + + rxdesc->cipher = rt2x00_get_field32(word0, RXD_W0_CIPHER_ALG); + rxdesc->cipher_status = rt2x00_get_field32(word0, RXD_W0_CIPHER_ERROR); + + if (rxdesc->cipher != CIPHER_NONE) { + _rt2x00_desc_read(entry_priv->desc, 2, &rxdesc->iv[0]); + _rt2x00_desc_read(entry_priv->desc, 3, &rxdesc->iv[1]); + rxdesc->dev_flags |= RXDONE_CRYPTO_IV; + + _rt2x00_desc_read(entry_priv->desc, 4, &rxdesc->icv); + rxdesc->dev_flags |= RXDONE_CRYPTO_ICV; + + /* + * Hardware has stripped IV/EIV data from 802.11 frame during + * decryption. It has provided the data separately but rt2x00lib + * should decide if it should be reinserted. + */ + rxdesc->flags |= RX_FLAG_IV_STRIPPED; + + /* + * The hardware has already checked the Michael Mic and has + * stripped it from the frame. Signal this to mac80211. + */ + rxdesc->flags |= RX_FLAG_MMIC_STRIPPED; + + if (rxdesc->cipher_status == RX_CRYPTO_SUCCESS) + rxdesc->flags |= RX_FLAG_DECRYPTED; + else if (rxdesc->cipher_status == RX_CRYPTO_FAIL_MIC) + rxdesc->flags |= RX_FLAG_MMIC_ERROR; + } + + /* + * Obtain the status about this packet. + * When frame was received with an OFDM bitrate, + * the signal is the PLCP value. If it was received with + * a CCK bitrate the signal is the rate in 100kbit/s. + */ + rxdesc->signal = rt2x00_get_field32(word1, RXD_W1_SIGNAL); + rxdesc->rssi = rt61pci_agc_to_rssi(rt2x00dev, word1); + rxdesc->size = rt2x00_get_field32(word0, RXD_W0_DATABYTE_COUNT); + + if (rt2x00_get_field32(word0, RXD_W0_OFDM)) + rxdesc->dev_flags |= RXDONE_SIGNAL_PLCP; + else + rxdesc->dev_flags |= RXDONE_SIGNAL_BITRATE; + if (rt2x00_get_field32(word0, RXD_W0_MY_BSS)) + rxdesc->dev_flags |= RXDONE_MY_BSS; +} + +/* + * Interrupt functions. + */ +static void rt61pci_txdone(struct rt2x00_dev *rt2x00dev) +{ + struct data_queue *queue; + struct queue_entry *entry; + struct queue_entry *entry_done; + struct queue_entry_priv_mmio *entry_priv; + struct txdone_entry_desc txdesc; + u32 word; + u32 reg; + int type; + int index; + int i; + + /* + * TX_STA_FIFO is a stack of X entries, hence read TX_STA_FIFO + * at most X times and also stop processing once the TX_STA_FIFO_VALID + * flag is not set anymore. + * + * The legacy drivers use X=TX_RING_SIZE but state in a comment + * that the TX_STA_FIFO stack has a size of 16. We stick to our + * tx ring size for now. + */ + for (i = 0; i < rt2x00dev->ops->tx->entry_num; i++) { + rt2x00mmio_register_read(rt2x00dev, STA_CSR4, ®); + if (!rt2x00_get_field32(reg, STA_CSR4_VALID)) + break; + + /* + * Skip this entry when it contains an invalid + * queue identication number. + */ + type = rt2x00_get_field32(reg, STA_CSR4_PID_TYPE); + queue = rt2x00queue_get_tx_queue(rt2x00dev, type); + if (unlikely(!queue)) + continue; + + /* + * Skip this entry when it contains an invalid + * index number. + */ + index = rt2x00_get_field32(reg, STA_CSR4_PID_SUBTYPE); + if (unlikely(index >= queue->limit)) + continue; + + entry = &queue->entries[index]; + entry_priv = entry->priv_data; + rt2x00_desc_read(entry_priv->desc, 0, &word); + + if (rt2x00_get_field32(word, TXD_W0_OWNER_NIC) || + !rt2x00_get_field32(word, TXD_W0_VALID)) + return; + + entry_done = rt2x00queue_get_entry(queue, Q_INDEX_DONE); + while (entry != entry_done) { + /* Catch up. + * Just report any entries we missed as failed. + */ + rt2x00_warn(rt2x00dev, "TX status report missed for entry %d\n", + entry_done->entry_idx); + + rt2x00lib_txdone_noinfo(entry_done, TXDONE_UNKNOWN); + entry_done = rt2x00queue_get_entry(queue, Q_INDEX_DONE); + } + + /* + * Obtain the status about this packet. + */ + txdesc.flags = 0; + switch (rt2x00_get_field32(reg, STA_CSR4_TX_RESULT)) { + case 0: /* Success, maybe with retry */ + __set_bit(TXDONE_SUCCESS, &txdesc.flags); + break; + case 6: /* Failure, excessive retries */ + __set_bit(TXDONE_EXCESSIVE_RETRY, &txdesc.flags); + /* Don't break, this is a failed frame! */ + default: /* Failure */ + __set_bit(TXDONE_FAILURE, &txdesc.flags); + } + txdesc.retry = rt2x00_get_field32(reg, STA_CSR4_RETRY_COUNT); + + /* + * the frame was retried at least once + * -> hw used fallback rates + */ + if (txdesc.retry) + __set_bit(TXDONE_FALLBACK, &txdesc.flags); + + rt2x00lib_txdone(entry, &txdesc); + } +} + +static void rt61pci_wakeup(struct rt2x00_dev *rt2x00dev) +{ + struct rt2x00lib_conf libconf = { .conf = &rt2x00dev->hw->conf }; + + rt61pci_config(rt2x00dev, &libconf, IEEE80211_CONF_CHANGE_PS); +} + +static inline void rt61pci_enable_interrupt(struct rt2x00_dev *rt2x00dev, + struct rt2x00_field32 irq_field) +{ + u32 reg; + + /* + * Enable a single interrupt. The interrupt mask register + * access needs locking. + */ + spin_lock_irq(&rt2x00dev->irqmask_lock); + + rt2x00mmio_register_read(rt2x00dev, INT_MASK_CSR, ®); + rt2x00_set_field32(®, irq_field, 0); + rt2x00mmio_register_write(rt2x00dev, INT_MASK_CSR, reg); + + spin_unlock_irq(&rt2x00dev->irqmask_lock); +} + +static void rt61pci_enable_mcu_interrupt(struct rt2x00_dev *rt2x00dev, + struct rt2x00_field32 irq_field) +{ + u32 reg; + + /* + * Enable a single MCU interrupt. The interrupt mask register + * access needs locking. + */ + spin_lock_irq(&rt2x00dev->irqmask_lock); + + rt2x00mmio_register_read(rt2x00dev, MCU_INT_MASK_CSR, ®); + rt2x00_set_field32(®, irq_field, 0); + rt2x00mmio_register_write(rt2x00dev, MCU_INT_MASK_CSR, reg); + + spin_unlock_irq(&rt2x00dev->irqmask_lock); +} + +static void rt61pci_txstatus_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + rt61pci_txdone(rt2x00dev); + if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + rt61pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_TXDONE); +} + +static void rt61pci_tbtt_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + rt2x00lib_beacondone(rt2x00dev); + if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + rt61pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_BEACON_DONE); +} + +static void rt61pci_rxdone_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + if (rt2x00mmio_rxdone(rt2x00dev)) + tasklet_schedule(&rt2x00dev->rxdone_tasklet); + else if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + rt61pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_RXDONE); +} + +static void rt61pci_autowake_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + rt61pci_wakeup(rt2x00dev); + rt2x00mmio_register_write(rt2x00dev, + M2H_CMD_DONE_CSR, 0xffffffff); + if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + rt61pci_enable_mcu_interrupt(rt2x00dev, MCU_INT_MASK_CSR_TWAKEUP); +} + +static irqreturn_t rt61pci_interrupt(int irq, void *dev_instance) +{ + struct rt2x00_dev *rt2x00dev = dev_instance; + u32 reg_mcu, mask_mcu; + u32 reg, mask; + + /* + * Get the interrupt sources & saved to local variable. + * Write register value back to clear pending interrupts. + */ + rt2x00mmio_register_read(rt2x00dev, MCU_INT_SOURCE_CSR, ®_mcu); + rt2x00mmio_register_write(rt2x00dev, MCU_INT_SOURCE_CSR, reg_mcu); + + rt2x00mmio_register_read(rt2x00dev, INT_SOURCE_CSR, ®); + rt2x00mmio_register_write(rt2x00dev, INT_SOURCE_CSR, reg); + + if (!reg && !reg_mcu) + return IRQ_NONE; + + if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) + return IRQ_HANDLED; + + /* + * Schedule tasklets for interrupt handling. + */ + if (rt2x00_get_field32(reg, INT_SOURCE_CSR_RXDONE)) + tasklet_schedule(&rt2x00dev->rxdone_tasklet); + + if (rt2x00_get_field32(reg, INT_SOURCE_CSR_TXDONE)) + tasklet_schedule(&rt2x00dev->txstatus_tasklet); + + if (rt2x00_get_field32(reg, INT_SOURCE_CSR_BEACON_DONE)) + tasklet_hi_schedule(&rt2x00dev->tbtt_tasklet); + + if (rt2x00_get_field32(reg_mcu, MCU_INT_SOURCE_CSR_TWAKEUP)) + tasklet_schedule(&rt2x00dev->autowake_tasklet); + + /* + * Since INT_MASK_CSR and INT_SOURCE_CSR use the same bits + * for interrupts and interrupt masks we can just use the value of + * INT_SOURCE_CSR to create the interrupt mask. + */ + mask = reg; + mask_mcu = reg_mcu; + + /* + * Disable all interrupts for which a tasklet was scheduled right now, + * the tasklet will reenable the appropriate interrupts. + */ + spin_lock(&rt2x00dev->irqmask_lock); + + rt2x00mmio_register_read(rt2x00dev, INT_MASK_CSR, ®); + reg |= mask; + rt2x00mmio_register_write(rt2x00dev, INT_MASK_CSR, reg); + + rt2x00mmio_register_read(rt2x00dev, MCU_INT_MASK_CSR, ®); + reg |= mask_mcu; + rt2x00mmio_register_write(rt2x00dev, MCU_INT_MASK_CSR, reg); + + spin_unlock(&rt2x00dev->irqmask_lock); + + return IRQ_HANDLED; +} + +/* + * Device probe functions. + */ +static int rt61pci_validate_eeprom(struct rt2x00_dev *rt2x00dev) +{ + struct eeprom_93cx6 eeprom; + u32 reg; + u16 word; + u8 *mac; + s8 value; + + rt2x00mmio_register_read(rt2x00dev, E2PROM_CSR, ®); + + eeprom.data = rt2x00dev; + eeprom.register_read = rt61pci_eepromregister_read; + eeprom.register_write = rt61pci_eepromregister_write; + eeprom.width = rt2x00_get_field32(reg, E2PROM_CSR_TYPE_93C46) ? + PCI_EEPROM_WIDTH_93C46 : PCI_EEPROM_WIDTH_93C66; + eeprom.reg_data_in = 0; + eeprom.reg_data_out = 0; + eeprom.reg_data_clock = 0; + eeprom.reg_chip_select = 0; + + eeprom_93cx6_multiread(&eeprom, EEPROM_BASE, rt2x00dev->eeprom, + EEPROM_SIZE / sizeof(u16)); + + /* + * Start validation of the data that has been read. + */ + mac = rt2x00_eeprom_addr(rt2x00dev, EEPROM_MAC_ADDR_0); + if (!is_valid_ether_addr(mac)) { + eth_random_addr(mac); + rt2x00_eeprom_dbg(rt2x00dev, "MAC: %pM\n", mac); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_ANTENNA, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_ANTENNA_NUM, 2); + rt2x00_set_field16(&word, EEPROM_ANTENNA_TX_DEFAULT, + ANTENNA_B); + rt2x00_set_field16(&word, EEPROM_ANTENNA_RX_DEFAULT, + ANTENNA_B); + rt2x00_set_field16(&word, EEPROM_ANTENNA_FRAME_TYPE, 0); + rt2x00_set_field16(&word, EEPROM_ANTENNA_DYN_TXAGC, 0); + rt2x00_set_field16(&word, EEPROM_ANTENNA_HARDWARE_RADIO, 0); + rt2x00_set_field16(&word, EEPROM_ANTENNA_RF_TYPE, RF5225); + rt2x00_eeprom_write(rt2x00dev, EEPROM_ANTENNA, word); + rt2x00_eeprom_dbg(rt2x00dev, "Antenna: 0x%04x\n", word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_NIC_ENABLE_DIVERSITY, 0); + rt2x00_set_field16(&word, EEPROM_NIC_TX_DIVERSITY, 0); + rt2x00_set_field16(&word, EEPROM_NIC_RX_FIXED, 0); + rt2x00_set_field16(&word, EEPROM_NIC_TX_FIXED, 0); + rt2x00_set_field16(&word, EEPROM_NIC_EXTERNAL_LNA_BG, 0); + rt2x00_set_field16(&word, EEPROM_NIC_CARDBUS_ACCEL, 0); + rt2x00_set_field16(&word, EEPROM_NIC_EXTERNAL_LNA_A, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_NIC, word); + rt2x00_eeprom_dbg(rt2x00dev, "NIC: 0x%04x\n", word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_LED, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_LED_LED_MODE, + LED_MODE_DEFAULT); + rt2x00_eeprom_write(rt2x00dev, EEPROM_LED, word); + rt2x00_eeprom_dbg(rt2x00dev, "Led: 0x%04x\n", word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_FREQ, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_FREQ_OFFSET, 0); + rt2x00_set_field16(&word, EEPROM_FREQ_SEQ, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_FREQ, word); + rt2x00_eeprom_dbg(rt2x00dev, "Freq: 0x%04x\n", word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_OFFSET_BG, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_RSSI_OFFSET_BG_1, 0); + rt2x00_set_field16(&word, EEPROM_RSSI_OFFSET_BG_2, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_RSSI_OFFSET_BG, word); + rt2x00_eeprom_dbg(rt2x00dev, "RSSI OFFSET BG: 0x%04x\n", word); + } else { + value = rt2x00_get_field16(word, EEPROM_RSSI_OFFSET_BG_1); + if (value < -10 || value > 10) + rt2x00_set_field16(&word, EEPROM_RSSI_OFFSET_BG_1, 0); + value = rt2x00_get_field16(word, EEPROM_RSSI_OFFSET_BG_2); + if (value < -10 || value > 10) + rt2x00_set_field16(&word, EEPROM_RSSI_OFFSET_BG_2, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_RSSI_OFFSET_BG, word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_OFFSET_A, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_RSSI_OFFSET_A_1, 0); + rt2x00_set_field16(&word, EEPROM_RSSI_OFFSET_A_2, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_RSSI_OFFSET_A, word); + rt2x00_eeprom_dbg(rt2x00dev, "RSSI OFFSET A: 0x%04x\n", word); + } else { + value = rt2x00_get_field16(word, EEPROM_RSSI_OFFSET_A_1); + if (value < -10 || value > 10) + rt2x00_set_field16(&word, EEPROM_RSSI_OFFSET_A_1, 0); + value = rt2x00_get_field16(word, EEPROM_RSSI_OFFSET_A_2); + if (value < -10 || value > 10) + rt2x00_set_field16(&word, EEPROM_RSSI_OFFSET_A_2, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_RSSI_OFFSET_A, word); + } + + return 0; +} + +static int rt61pci_init_eeprom(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + u16 value; + u16 eeprom; + + /* + * Read EEPROM word for configuration. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_ANTENNA, &eeprom); + + /* + * Identify RF chipset. + */ + value = rt2x00_get_field16(eeprom, EEPROM_ANTENNA_RF_TYPE); + rt2x00mmio_register_read(rt2x00dev, MAC_CSR0, ®); + rt2x00_set_chip(rt2x00dev, rt2x00_get_field32(reg, MAC_CSR0_CHIPSET), + value, rt2x00_get_field32(reg, MAC_CSR0_REVISION)); + + if (!rt2x00_rf(rt2x00dev, RF5225) && + !rt2x00_rf(rt2x00dev, RF5325) && + !rt2x00_rf(rt2x00dev, RF2527) && + !rt2x00_rf(rt2x00dev, RF2529)) { + rt2x00_err(rt2x00dev, "Invalid RF chipset detected\n"); + return -ENODEV; + } + + /* + * Determine number of antennas. + */ + if (rt2x00_get_field16(eeprom, EEPROM_ANTENNA_NUM) == 2) + __set_bit(CAPABILITY_DOUBLE_ANTENNA, &rt2x00dev->cap_flags); + + /* + * Identify default antenna configuration. + */ + rt2x00dev->default_ant.tx = + rt2x00_get_field16(eeprom, EEPROM_ANTENNA_TX_DEFAULT); + rt2x00dev->default_ant.rx = + rt2x00_get_field16(eeprom, EEPROM_ANTENNA_RX_DEFAULT); + + /* + * Read the Frame type. + */ + if (rt2x00_get_field16(eeprom, EEPROM_ANTENNA_FRAME_TYPE)) + __set_bit(CAPABILITY_FRAME_TYPE, &rt2x00dev->cap_flags); + + /* + * Detect if this device has a hardware controlled radio. + */ + if (rt2x00_get_field16(eeprom, EEPROM_ANTENNA_HARDWARE_RADIO)) + __set_bit(CAPABILITY_HW_BUTTON, &rt2x00dev->cap_flags); + + /* + * Read frequency offset and RF programming sequence. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_FREQ, &eeprom); + if (rt2x00_get_field16(eeprom, EEPROM_FREQ_SEQ)) + __set_bit(CAPABILITY_RF_SEQUENCE, &rt2x00dev->cap_flags); + + rt2x00dev->freq_offset = rt2x00_get_field16(eeprom, EEPROM_FREQ_OFFSET); + + /* + * Read external LNA informations. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC, &eeprom); + + if (rt2x00_get_field16(eeprom, EEPROM_NIC_EXTERNAL_LNA_A)) + __set_bit(CAPABILITY_EXTERNAL_LNA_A, &rt2x00dev->cap_flags); + if (rt2x00_get_field16(eeprom, EEPROM_NIC_EXTERNAL_LNA_BG)) + __set_bit(CAPABILITY_EXTERNAL_LNA_BG, &rt2x00dev->cap_flags); + + /* + * When working with a RF2529 chip without double antenna, + * the antenna settings should be gathered from the NIC + * eeprom word. + */ + if (rt2x00_rf(rt2x00dev, RF2529) && + !test_bit(CAPABILITY_DOUBLE_ANTENNA, &rt2x00dev->cap_flags)) { + rt2x00dev->default_ant.rx = + ANTENNA_A + rt2x00_get_field16(eeprom, EEPROM_NIC_RX_FIXED); + rt2x00dev->default_ant.tx = + ANTENNA_B - rt2x00_get_field16(eeprom, EEPROM_NIC_TX_FIXED); + + if (rt2x00_get_field16(eeprom, EEPROM_NIC_TX_DIVERSITY)) + rt2x00dev->default_ant.tx = ANTENNA_SW_DIVERSITY; + if (rt2x00_get_field16(eeprom, EEPROM_NIC_ENABLE_DIVERSITY)) + rt2x00dev->default_ant.rx = ANTENNA_SW_DIVERSITY; + } + + /* + * Store led settings, for correct led behaviour. + * If the eeprom value is invalid, + * switch to default led mode. + */ +#ifdef CPTCFG_RT2X00_LIB_LEDS + rt2x00_eeprom_read(rt2x00dev, EEPROM_LED, &eeprom); + value = rt2x00_get_field16(eeprom, EEPROM_LED_LED_MODE); + + rt61pci_init_led(rt2x00dev, &rt2x00dev->led_radio, LED_TYPE_RADIO); + rt61pci_init_led(rt2x00dev, &rt2x00dev->led_assoc, LED_TYPE_ASSOC); + if (value == LED_MODE_SIGNAL_STRENGTH) + rt61pci_init_led(rt2x00dev, &rt2x00dev->led_qual, + LED_TYPE_QUALITY); + + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_LED_MODE, value); + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_POLARITY_GPIO_0, + rt2x00_get_field16(eeprom, + EEPROM_LED_POLARITY_GPIO_0)); + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_POLARITY_GPIO_1, + rt2x00_get_field16(eeprom, + EEPROM_LED_POLARITY_GPIO_1)); + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_POLARITY_GPIO_2, + rt2x00_get_field16(eeprom, + EEPROM_LED_POLARITY_GPIO_2)); + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_POLARITY_GPIO_3, + rt2x00_get_field16(eeprom, + EEPROM_LED_POLARITY_GPIO_3)); + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_POLARITY_GPIO_4, + rt2x00_get_field16(eeprom, + EEPROM_LED_POLARITY_GPIO_4)); + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_POLARITY_ACT, + rt2x00_get_field16(eeprom, EEPROM_LED_POLARITY_ACT)); + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_POLARITY_READY_BG, + rt2x00_get_field16(eeprom, + EEPROM_LED_POLARITY_RDY_G)); + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_POLARITY_READY_A, + rt2x00_get_field16(eeprom, + EEPROM_LED_POLARITY_RDY_A)); +#endif /* CPTCFG_RT2X00_LIB_LEDS */ + + return 0; +} + +/* + * RF value list for RF5225 & RF5325 + * Supports: 2.4 GHz & 5.2 GHz, rf_sequence disabled + */ +static const struct rf_channel rf_vals_noseq[] = { + { 1, 0x00002ccc, 0x00004786, 0x00068455, 0x000ffa0b }, + { 2, 0x00002ccc, 0x00004786, 0x00068455, 0x000ffa1f }, + { 3, 0x00002ccc, 0x0000478a, 0x00068455, 0x000ffa0b }, + { 4, 0x00002ccc, 0x0000478a, 0x00068455, 0x000ffa1f }, + { 5, 0x00002ccc, 0x0000478e, 0x00068455, 0x000ffa0b }, + { 6, 0x00002ccc, 0x0000478e, 0x00068455, 0x000ffa1f }, + { 7, 0x00002ccc, 0x00004792, 0x00068455, 0x000ffa0b }, + { 8, 0x00002ccc, 0x00004792, 0x00068455, 0x000ffa1f }, + { 9, 0x00002ccc, 0x00004796, 0x00068455, 0x000ffa0b }, + { 10, 0x00002ccc, 0x00004796, 0x00068455, 0x000ffa1f }, + { 11, 0x00002ccc, 0x0000479a, 0x00068455, 0x000ffa0b }, + { 12, 0x00002ccc, 0x0000479a, 0x00068455, 0x000ffa1f }, + { 13, 0x00002ccc, 0x0000479e, 0x00068455, 0x000ffa0b }, + { 14, 0x00002ccc, 0x000047a2, 0x00068455, 0x000ffa13 }, + + /* 802.11 UNI / HyperLan 2 */ + { 36, 0x00002ccc, 0x0000499a, 0x0009be55, 0x000ffa23 }, + { 40, 0x00002ccc, 0x000049a2, 0x0009be55, 0x000ffa03 }, + { 44, 0x00002ccc, 0x000049a6, 0x0009be55, 0x000ffa0b }, + { 48, 0x00002ccc, 0x000049aa, 0x0009be55, 0x000ffa13 }, + { 52, 0x00002ccc, 0x000049ae, 0x0009ae55, 0x000ffa1b }, + { 56, 0x00002ccc, 0x000049b2, 0x0009ae55, 0x000ffa23 }, + { 60, 0x00002ccc, 0x000049ba, 0x0009ae55, 0x000ffa03 }, + { 64, 0x00002ccc, 0x000049be, 0x0009ae55, 0x000ffa0b }, + + /* 802.11 HyperLan 2 */ + { 100, 0x00002ccc, 0x00004a2a, 0x000bae55, 0x000ffa03 }, + { 104, 0x00002ccc, 0x00004a2e, 0x000bae55, 0x000ffa0b }, + { 108, 0x00002ccc, 0x00004a32, 0x000bae55, 0x000ffa13 }, + { 112, 0x00002ccc, 0x00004a36, 0x000bae55, 0x000ffa1b }, + { 116, 0x00002ccc, 0x00004a3a, 0x000bbe55, 0x000ffa23 }, + { 120, 0x00002ccc, 0x00004a82, 0x000bbe55, 0x000ffa03 }, + { 124, 0x00002ccc, 0x00004a86, 0x000bbe55, 0x000ffa0b }, + { 128, 0x00002ccc, 0x00004a8a, 0x000bbe55, 0x000ffa13 }, + { 132, 0x00002ccc, 0x00004a8e, 0x000bbe55, 0x000ffa1b }, + { 136, 0x00002ccc, 0x00004a92, 0x000bbe55, 0x000ffa23 }, + + /* 802.11 UNII */ + { 140, 0x00002ccc, 0x00004a9a, 0x000bbe55, 0x000ffa03 }, + { 149, 0x00002ccc, 0x00004aa2, 0x000bbe55, 0x000ffa1f }, + { 153, 0x00002ccc, 0x00004aa6, 0x000bbe55, 0x000ffa27 }, + { 157, 0x00002ccc, 0x00004aae, 0x000bbe55, 0x000ffa07 }, + { 161, 0x00002ccc, 0x00004ab2, 0x000bbe55, 0x000ffa0f }, + { 165, 0x00002ccc, 0x00004ab6, 0x000bbe55, 0x000ffa17 }, + + /* MMAC(Japan)J52 ch 34,38,42,46 */ + { 34, 0x00002ccc, 0x0000499a, 0x0009be55, 0x000ffa0b }, + { 38, 0x00002ccc, 0x0000499e, 0x0009be55, 0x000ffa13 }, + { 42, 0x00002ccc, 0x000049a2, 0x0009be55, 0x000ffa1b }, + { 46, 0x00002ccc, 0x000049a6, 0x0009be55, 0x000ffa23 }, +}; + +/* + * RF value list for RF5225 & RF5325 + * Supports: 2.4 GHz & 5.2 GHz, rf_sequence enabled + */ +static const struct rf_channel rf_vals_seq[] = { + { 1, 0x00002ccc, 0x00004786, 0x00068455, 0x000ffa0b }, + { 2, 0x00002ccc, 0x00004786, 0x00068455, 0x000ffa1f }, + { 3, 0x00002ccc, 0x0000478a, 0x00068455, 0x000ffa0b }, + { 4, 0x00002ccc, 0x0000478a, 0x00068455, 0x000ffa1f }, + { 5, 0x00002ccc, 0x0000478e, 0x00068455, 0x000ffa0b }, + { 6, 0x00002ccc, 0x0000478e, 0x00068455, 0x000ffa1f }, + { 7, 0x00002ccc, 0x00004792, 0x00068455, 0x000ffa0b }, + { 8, 0x00002ccc, 0x00004792, 0x00068455, 0x000ffa1f }, + { 9, 0x00002ccc, 0x00004796, 0x00068455, 0x000ffa0b }, + { 10, 0x00002ccc, 0x00004796, 0x00068455, 0x000ffa1f }, + { 11, 0x00002ccc, 0x0000479a, 0x00068455, 0x000ffa0b }, + { 12, 0x00002ccc, 0x0000479a, 0x00068455, 0x000ffa1f }, + { 13, 0x00002ccc, 0x0000479e, 0x00068455, 0x000ffa0b }, + { 14, 0x00002ccc, 0x000047a2, 0x00068455, 0x000ffa13 }, + + /* 802.11 UNI / HyperLan 2 */ + { 36, 0x00002cd4, 0x0004481a, 0x00098455, 0x000c0a03 }, + { 40, 0x00002cd0, 0x00044682, 0x00098455, 0x000c0a03 }, + { 44, 0x00002cd0, 0x00044686, 0x00098455, 0x000c0a1b }, + { 48, 0x00002cd0, 0x0004468e, 0x00098655, 0x000c0a0b }, + { 52, 0x00002cd0, 0x00044692, 0x00098855, 0x000c0a23 }, + { 56, 0x00002cd0, 0x0004469a, 0x00098c55, 0x000c0a13 }, + { 60, 0x00002cd0, 0x000446a2, 0x00098e55, 0x000c0a03 }, + { 64, 0x00002cd0, 0x000446a6, 0x00099255, 0x000c0a1b }, + + /* 802.11 HyperLan 2 */ + { 100, 0x00002cd4, 0x0004489a, 0x000b9855, 0x000c0a03 }, + { 104, 0x00002cd4, 0x000448a2, 0x000b9855, 0x000c0a03 }, + { 108, 0x00002cd4, 0x000448aa, 0x000b9855, 0x000c0a03 }, + { 112, 0x00002cd4, 0x000448b2, 0x000b9a55, 0x000c0a03 }, + { 116, 0x00002cd4, 0x000448ba, 0x000b9a55, 0x000c0a03 }, + { 120, 0x00002cd0, 0x00044702, 0x000b9a55, 0x000c0a03 }, + { 124, 0x00002cd0, 0x00044706, 0x000b9a55, 0x000c0a1b }, + { 128, 0x00002cd0, 0x0004470e, 0x000b9c55, 0x000c0a0b }, + { 132, 0x00002cd0, 0x00044712, 0x000b9c55, 0x000c0a23 }, + { 136, 0x00002cd0, 0x0004471a, 0x000b9e55, 0x000c0a13 }, + + /* 802.11 UNII */ + { 140, 0x00002cd0, 0x00044722, 0x000b9e55, 0x000c0a03 }, + { 149, 0x00002cd0, 0x0004472e, 0x000ba255, 0x000c0a1b }, + { 153, 0x00002cd0, 0x00044736, 0x000ba255, 0x000c0a0b }, + { 157, 0x00002cd4, 0x0004490a, 0x000ba255, 0x000c0a17 }, + { 161, 0x00002cd4, 0x00044912, 0x000ba255, 0x000c0a17 }, + { 165, 0x00002cd4, 0x0004491a, 0x000ba255, 0x000c0a17 }, + + /* MMAC(Japan)J52 ch 34,38,42,46 */ + { 34, 0x00002ccc, 0x0000499a, 0x0009be55, 0x000c0a0b }, + { 38, 0x00002ccc, 0x0000499e, 0x0009be55, 0x000c0a13 }, + { 42, 0x00002ccc, 0x000049a2, 0x0009be55, 0x000c0a1b }, + { 46, 0x00002ccc, 0x000049a6, 0x0009be55, 0x000c0a23 }, +}; + +static int rt61pci_probe_hw_mode(struct rt2x00_dev *rt2x00dev) +{ + struct hw_mode_spec *spec = &rt2x00dev->spec; + struct channel_info *info; + char *tx_power; + unsigned int i; + + /* + * Disable powersaving as default. + */ + rt2x00dev->hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + + /* + * Initialize all hw fields. + */ + rt2x00dev->hw->flags = + IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING | + IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_PS_NULLFUNC_STACK; + + SET_IEEE80211_DEV(rt2x00dev->hw, rt2x00dev->dev); + SET_IEEE80211_PERM_ADDR(rt2x00dev->hw, + rt2x00_eeprom_addr(rt2x00dev, + EEPROM_MAC_ADDR_0)); + + /* + * As rt61 has a global fallback table we cannot specify + * more then one tx rate per frame but since the hw will + * try several rates (based on the fallback table) we should + * initialize max_report_rates to the maximum number of rates + * we are going to try. Otherwise mac80211 will truncate our + * reported tx rates and the rc algortihm will end up with + * incorrect data. + */ + rt2x00dev->hw->max_rates = 1; + rt2x00dev->hw->max_report_rates = 7; + rt2x00dev->hw->max_rate_tries = 1; + + /* + * Initialize hw_mode information. + */ + spec->supported_bands = SUPPORT_BAND_2GHZ; + spec->supported_rates = SUPPORT_RATE_CCK | SUPPORT_RATE_OFDM; + + if (!test_bit(CAPABILITY_RF_SEQUENCE, &rt2x00dev->cap_flags)) { + spec->num_channels = 14; + spec->channels = rf_vals_noseq; + } else { + spec->num_channels = 14; + spec->channels = rf_vals_seq; + } + + if (rt2x00_rf(rt2x00dev, RF5225) || rt2x00_rf(rt2x00dev, RF5325)) { + spec->supported_bands |= SUPPORT_BAND_5GHZ; + spec->num_channels = ARRAY_SIZE(rf_vals_seq); + } + + /* + * Create channel information array + */ + info = kcalloc(spec->num_channels, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + spec->channels_info = info; + + tx_power = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_G_START); + for (i = 0; i < 14; i++) { + info[i].max_power = MAX_TXPOWER; + info[i].default_power1 = TXPOWER_FROM_DEV(tx_power[i]); + } + + if (spec->num_channels > 14) { + tx_power = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_A_START); + for (i = 14; i < spec->num_channels; i++) { + info[i].max_power = MAX_TXPOWER; + info[i].default_power1 = + TXPOWER_FROM_DEV(tx_power[i - 14]); + } + } + + return 0; +} + +static int rt61pci_probe_hw(struct rt2x00_dev *rt2x00dev) +{ + int retval; + u32 reg; + + /* + * Disable power saving. + */ + rt2x00mmio_register_write(rt2x00dev, SOFT_RESET_CSR, 0x00000007); + + /* + * Allocate eeprom data. + */ + retval = rt61pci_validate_eeprom(rt2x00dev); + if (retval) + return retval; + + retval = rt61pci_init_eeprom(rt2x00dev); + if (retval) + return retval; + + /* + * Enable rfkill polling by setting GPIO direction of the + * rfkill switch GPIO pin correctly. + */ + rt2x00mmio_register_read(rt2x00dev, MAC_CSR13, ®); + rt2x00_set_field32(®, MAC_CSR13_DIR5, 1); + rt2x00mmio_register_write(rt2x00dev, MAC_CSR13, reg); + + /* + * Initialize hw specifications. + */ + retval = rt61pci_probe_hw_mode(rt2x00dev); + if (retval) + return retval; + + /* + * This device has multiple filters for control frames, + * but has no a separate filter for PS Poll frames. + */ + __set_bit(CAPABILITY_CONTROL_FILTERS, &rt2x00dev->cap_flags); + + /* + * This device requires firmware and DMA mapped skbs. + */ + __set_bit(REQUIRE_FIRMWARE, &rt2x00dev->cap_flags); + __set_bit(REQUIRE_DMA, &rt2x00dev->cap_flags); + if (!modparam_nohwcrypt) + __set_bit(CAPABILITY_HW_CRYPTO, &rt2x00dev->cap_flags); + __set_bit(CAPABILITY_LINK_TUNING, &rt2x00dev->cap_flags); + + /* + * Set the rssi offset. + */ + rt2x00dev->rssi_offset = DEFAULT_RSSI_OFFSET; + + return 0; +} + +/* + * IEEE80211 stack callback functions. + */ +static int rt61pci_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue_idx, + const struct ieee80211_tx_queue_params *params) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct data_queue *queue; + struct rt2x00_field32 field; + int retval; + u32 reg; + u32 offset; + + /* + * First pass the configuration through rt2x00lib, that will + * update the queue settings and validate the input. After that + * we are free to update the registers based on the value + * in the queue parameter. + */ + retval = rt2x00mac_conf_tx(hw, vif, queue_idx, params); + if (retval) + return retval; + + /* + * We only need to perform additional register initialization + * for WMM queues. + */ + if (queue_idx >= 4) + return 0; + + queue = rt2x00queue_get_tx_queue(rt2x00dev, queue_idx); + + /* Update WMM TXOP register */ + offset = AC_TXOP_CSR0 + (sizeof(u32) * (!!(queue_idx & 2))); + field.bit_offset = (queue_idx & 1) * 16; + field.bit_mask = 0xffff << field.bit_offset; + + rt2x00mmio_register_read(rt2x00dev, offset, ®); + rt2x00_set_field32(®, field, queue->txop); + rt2x00mmio_register_write(rt2x00dev, offset, reg); + + /* Update WMM registers */ + field.bit_offset = queue_idx * 4; + field.bit_mask = 0xf << field.bit_offset; + + rt2x00mmio_register_read(rt2x00dev, AIFSN_CSR, ®); + rt2x00_set_field32(®, field, queue->aifs); + rt2x00mmio_register_write(rt2x00dev, AIFSN_CSR, reg); + + rt2x00mmio_register_read(rt2x00dev, CWMIN_CSR, ®); + rt2x00_set_field32(®, field, queue->cw_min); + rt2x00mmio_register_write(rt2x00dev, CWMIN_CSR, reg); + + rt2x00mmio_register_read(rt2x00dev, CWMAX_CSR, ®); + rt2x00_set_field32(®, field, queue->cw_max); + rt2x00mmio_register_write(rt2x00dev, CWMAX_CSR, reg); + + return 0; +} + +static u64 rt61pci_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + u64 tsf; + u32 reg; + + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR13, ®); + tsf = (u64) rt2x00_get_field32(reg, TXRX_CSR13_HIGH_TSFTIMER) << 32; + rt2x00mmio_register_read(rt2x00dev, TXRX_CSR12, ®); + tsf |= rt2x00_get_field32(reg, TXRX_CSR12_LOW_TSFTIMER); + + return tsf; +} + +static const struct ieee80211_ops rt61pci_mac80211_ops = { + .tx = rt2x00mac_tx, + .start = rt2x00mac_start, + .stop = rt2x00mac_stop, + .add_interface = rt2x00mac_add_interface, + .remove_interface = rt2x00mac_remove_interface, + .config = rt2x00mac_config, + .configure_filter = rt2x00mac_configure_filter, + .set_key = rt2x00mac_set_key, + .sw_scan_start = rt2x00mac_sw_scan_start, + .sw_scan_complete = rt2x00mac_sw_scan_complete, + .get_stats = rt2x00mac_get_stats, + .bss_info_changed = rt2x00mac_bss_info_changed, + .conf_tx = rt61pci_conf_tx, + .get_tsf = rt61pci_get_tsf, + .rfkill_poll = rt2x00mac_rfkill_poll, + .flush = rt2x00mac_flush, + .set_antenna = rt2x00mac_set_antenna, + .get_antenna = rt2x00mac_get_antenna, + .get_ringparam = rt2x00mac_get_ringparam, + .tx_frames_pending = rt2x00mac_tx_frames_pending, +}; + +static const struct rt2x00lib_ops rt61pci_rt2x00_ops = { + .irq_handler = rt61pci_interrupt, + .txstatus_tasklet = rt61pci_txstatus_tasklet, + .tbtt_tasklet = rt61pci_tbtt_tasklet, + .rxdone_tasklet = rt61pci_rxdone_tasklet, + .autowake_tasklet = rt61pci_autowake_tasklet, + .probe_hw = rt61pci_probe_hw, + .get_firmware_name = rt61pci_get_firmware_name, + .check_firmware = rt61pci_check_firmware, + .load_firmware = rt61pci_load_firmware, + .initialize = rt2x00mmio_initialize, + .uninitialize = rt2x00mmio_uninitialize, + .get_entry_state = rt61pci_get_entry_state, + .clear_entry = rt61pci_clear_entry, + .set_device_state = rt61pci_set_device_state, + .rfkill_poll = rt61pci_rfkill_poll, + .link_stats = rt61pci_link_stats, + .reset_tuner = rt61pci_reset_tuner, + .link_tuner = rt61pci_link_tuner, + .start_queue = rt61pci_start_queue, + .kick_queue = rt61pci_kick_queue, + .stop_queue = rt61pci_stop_queue, + .flush_queue = rt2x00mmio_flush_queue, + .write_tx_desc = rt61pci_write_tx_desc, + .write_beacon = rt61pci_write_beacon, + .clear_beacon = rt61pci_clear_beacon, + .fill_rxdone = rt61pci_fill_rxdone, + .config_shared_key = rt61pci_config_shared_key, + .config_pairwise_key = rt61pci_config_pairwise_key, + .config_filter = rt61pci_config_filter, + .config_intf = rt61pci_config_intf, + .config_erp = rt61pci_config_erp, + .config_ant = rt61pci_config_ant, + .config = rt61pci_config, +}; + +static const struct data_queue_desc rt61pci_queue_rx = { + .entry_num = 32, + .data_size = DATA_FRAME_SIZE, + .desc_size = RXD_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_mmio), +}; + +static const struct data_queue_desc rt61pci_queue_tx = { + .entry_num = 32, + .data_size = DATA_FRAME_SIZE, + .desc_size = TXD_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_mmio), +}; + +static const struct data_queue_desc rt61pci_queue_bcn = { + .entry_num = 4, + .data_size = 0, /* No DMA required for beacons */ + .desc_size = TXINFO_SIZE, + .priv_size = sizeof(struct queue_entry_priv_mmio), +}; + +static const struct rt2x00_ops rt61pci_ops = { + .name = KBUILD_MODNAME, + .max_ap_intf = 4, + .eeprom_size = EEPROM_SIZE, + .rf_size = RF_SIZE, + .tx_queues = NUM_TX_QUEUES, + .extra_tx_headroom = 0, + .rx = &rt61pci_queue_rx, + .tx = &rt61pci_queue_tx, + .bcn = &rt61pci_queue_bcn, + .lib = &rt61pci_rt2x00_ops, + .hw = &rt61pci_mac80211_ops, +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS + .debugfs = &rt61pci_rt2x00debug, +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ +}; + +/* + * RT61pci module information. + */ +static DEFINE_PCI_DEVICE_TABLE(rt61pci_device_table) = { + /* RT2561s */ + { PCI_DEVICE(0x1814, 0x0301) }, + /* RT2561 v2 */ + { PCI_DEVICE(0x1814, 0x0302) }, + /* RT2661 */ + { PCI_DEVICE(0x1814, 0x0401) }, + { 0, } +}; + +MODULE_AUTHOR(DRV_PROJECT); +MODULE_VERSION(DRV_VERSION); +MODULE_DESCRIPTION("Ralink RT61 PCI & PCMCIA Wireless LAN driver."); +MODULE_SUPPORTED_DEVICE("Ralink RT2561, RT2561s & RT2661 " + "PCI & PCMCIA chipset based cards"); +MODULE_DEVICE_TABLE(pci, rt61pci_device_table); +MODULE_FIRMWARE(FIRMWARE_RT2561); +MODULE_FIRMWARE(FIRMWARE_RT2561s); +MODULE_FIRMWARE(FIRMWARE_RT2661); +MODULE_LICENSE("GPL"); + +static int rt61pci_probe(struct pci_dev *pci_dev, + const struct pci_device_id *id) +{ + return rt2x00pci_probe(pci_dev, &rt61pci_ops); +} + +static struct pci_driver rt61pci_driver = { + .name = KBUILD_MODNAME, + .id_table = rt61pci_device_table, + .probe = rt61pci_probe, + .remove = rt2x00pci_remove, + .suspend = rt2x00pci_suspend, + .resume = rt2x00pci_resume, +}; + +module_pci_driver(rt61pci_driver); diff --git a/drivers/net/wireless/rt2x00/rt61pci.h b/drivers/net/wireless/rt2x00/rt61pci.h new file mode 100644 index 0000000..9bc6b60 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt61pci.h @@ -0,0 +1,1502 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt61pci + Abstract: Data structures and registers for the rt61pci module. + Supported chipsets: RT2561, RT2561s, RT2661. + */ + +#ifndef RT61PCI_H +#define RT61PCI_H + +/* + * RT chip PCI IDs. + */ +#define RT2561s_PCI_ID 0x0301 +#define RT2561_PCI_ID 0x0302 +#define RT2661_PCI_ID 0x0401 + +/* + * RF chip defines. + */ +#define RF5225 0x0001 +#define RF5325 0x0002 +#define RF2527 0x0003 +#define RF2529 0x0004 + +/* + * Signal information. + * Default offset is required for RSSI <-> dBm conversion. + */ +#define DEFAULT_RSSI_OFFSET 120 + +/* + * Register layout information. + */ +#define CSR_REG_BASE 0x3000 +#define CSR_REG_SIZE 0x04b0 +#define EEPROM_BASE 0x0000 +#define EEPROM_SIZE 0x0100 +#define BBP_BASE 0x0000 +#define BBP_SIZE 0x0080 +#define RF_BASE 0x0004 +#define RF_SIZE 0x0010 + +/* + * Number of TX queues. + */ +#define NUM_TX_QUEUES 4 + +/* + * PCI registers. + */ + +/* + * HOST_CMD_CSR: For HOST to interrupt embedded processor + */ +#define HOST_CMD_CSR 0x0008 +#define HOST_CMD_CSR_HOST_COMMAND FIELD32(0x0000007f) +#define HOST_CMD_CSR_INTERRUPT_MCU FIELD32(0x00000080) + +/* + * MCU_CNTL_CSR + * SELECT_BANK: Select 8051 program bank. + * RESET: Enable 8051 reset state. + * READY: Ready state for 8051. + */ +#define MCU_CNTL_CSR 0x000c +#define MCU_CNTL_CSR_SELECT_BANK FIELD32(0x00000001) +#define MCU_CNTL_CSR_RESET FIELD32(0x00000002) +#define MCU_CNTL_CSR_READY FIELD32(0x00000004) + +/* + * SOFT_RESET_CSR + * FORCE_CLOCK_ON: Host force MAC clock ON + */ +#define SOFT_RESET_CSR 0x0010 +#define SOFT_RESET_CSR_FORCE_CLOCK_ON FIELD32(0x00000002) + +/* + * MCU_INT_SOURCE_CSR: MCU interrupt source/mask register. + */ +#define MCU_INT_SOURCE_CSR 0x0014 +#define MCU_INT_SOURCE_CSR_0 FIELD32(0x00000001) +#define MCU_INT_SOURCE_CSR_1 FIELD32(0x00000002) +#define MCU_INT_SOURCE_CSR_2 FIELD32(0x00000004) +#define MCU_INT_SOURCE_CSR_3 FIELD32(0x00000008) +#define MCU_INT_SOURCE_CSR_4 FIELD32(0x00000010) +#define MCU_INT_SOURCE_CSR_5 FIELD32(0x00000020) +#define MCU_INT_SOURCE_CSR_6 FIELD32(0x00000040) +#define MCU_INT_SOURCE_CSR_7 FIELD32(0x00000080) +#define MCU_INT_SOURCE_CSR_TWAKEUP FIELD32(0x00000100) +#define MCU_INT_SOURCE_CSR_TBTT_EXPIRE FIELD32(0x00000200) + +/* + * MCU_INT_MASK_CSR: MCU interrupt source/mask register. + */ +#define MCU_INT_MASK_CSR 0x0018 +#define MCU_INT_MASK_CSR_0 FIELD32(0x00000001) +#define MCU_INT_MASK_CSR_1 FIELD32(0x00000002) +#define MCU_INT_MASK_CSR_2 FIELD32(0x00000004) +#define MCU_INT_MASK_CSR_3 FIELD32(0x00000008) +#define MCU_INT_MASK_CSR_4 FIELD32(0x00000010) +#define MCU_INT_MASK_CSR_5 FIELD32(0x00000020) +#define MCU_INT_MASK_CSR_6 FIELD32(0x00000040) +#define MCU_INT_MASK_CSR_7 FIELD32(0x00000080) +#define MCU_INT_MASK_CSR_TWAKEUP FIELD32(0x00000100) +#define MCU_INT_MASK_CSR_TBTT_EXPIRE FIELD32(0x00000200) + +/* + * PCI_USEC_CSR + */ +#define PCI_USEC_CSR 0x001c + +/* + * Security key table memory. + * 16 entries 32-byte for shared key table + * 64 entries 32-byte for pairwise key table + * 64 entries 8-byte for pairwise ta key table + */ +#define SHARED_KEY_TABLE_BASE 0x1000 +#define PAIRWISE_KEY_TABLE_BASE 0x1200 +#define PAIRWISE_TA_TABLE_BASE 0x1a00 + +#define SHARED_KEY_ENTRY(__idx) \ + ( SHARED_KEY_TABLE_BASE + \ + ((__idx) * sizeof(struct hw_key_entry)) ) +#define PAIRWISE_KEY_ENTRY(__idx) \ + ( PAIRWISE_KEY_TABLE_BASE + \ + ((__idx) * sizeof(struct hw_key_entry)) ) +#define PAIRWISE_TA_ENTRY(__idx) \ + ( PAIRWISE_TA_TABLE_BASE + \ + ((__idx) * sizeof(struct hw_pairwise_ta_entry)) ) + +struct hw_key_entry { + u8 key[16]; + u8 tx_mic[8]; + u8 rx_mic[8]; +} __packed; + +struct hw_pairwise_ta_entry { + u8 address[6]; + u8 cipher; + u8 reserved; +} __packed; + +/* + * Other on-chip shared memory space. + */ +#define HW_CIS_BASE 0x2000 +#define HW_NULL_BASE 0x2b00 + +/* + * Since NULL frame won't be that long (256 byte), + * We steal 16 tail bytes to save debugging settings. + */ +#define HW_DEBUG_SETTING_BASE 0x2bf0 + +/* + * On-chip BEACON frame space. + */ +#define HW_BEACON_BASE0 0x2c00 +#define HW_BEACON_BASE1 0x2d00 +#define HW_BEACON_BASE2 0x2e00 +#define HW_BEACON_BASE3 0x2f00 + +#define HW_BEACON_OFFSET(__index) \ + ( HW_BEACON_BASE0 + (__index * 0x0100) ) + +/* + * HOST-MCU shared memory. + */ + +/* + * H2M_MAILBOX_CSR: Host-to-MCU Mailbox. + */ +#define H2M_MAILBOX_CSR 0x2100 +#define H2M_MAILBOX_CSR_ARG0 FIELD32(0x000000ff) +#define H2M_MAILBOX_CSR_ARG1 FIELD32(0x0000ff00) +#define H2M_MAILBOX_CSR_CMD_TOKEN FIELD32(0x00ff0000) +#define H2M_MAILBOX_CSR_OWNER FIELD32(0xff000000) + +/* + * MCU_LEDCS: LED control for MCU Mailbox. + */ +#define MCU_LEDCS_LED_MODE FIELD16(0x001f) +#define MCU_LEDCS_RADIO_STATUS FIELD16(0x0020) +#define MCU_LEDCS_LINK_BG_STATUS FIELD16(0x0040) +#define MCU_LEDCS_LINK_A_STATUS FIELD16(0x0080) +#define MCU_LEDCS_POLARITY_GPIO_0 FIELD16(0x0100) +#define MCU_LEDCS_POLARITY_GPIO_1 FIELD16(0x0200) +#define MCU_LEDCS_POLARITY_GPIO_2 FIELD16(0x0400) +#define MCU_LEDCS_POLARITY_GPIO_3 FIELD16(0x0800) +#define MCU_LEDCS_POLARITY_GPIO_4 FIELD16(0x1000) +#define MCU_LEDCS_POLARITY_ACT FIELD16(0x2000) +#define MCU_LEDCS_POLARITY_READY_BG FIELD16(0x4000) +#define MCU_LEDCS_POLARITY_READY_A FIELD16(0x8000) + +/* + * M2H_CMD_DONE_CSR. + */ +#define M2H_CMD_DONE_CSR 0x2104 + +/* + * MCU_TXOP_ARRAY_BASE. + */ +#define MCU_TXOP_ARRAY_BASE 0x2110 + +/* + * MAC Control/Status Registers(CSR). + * Some values are set in TU, whereas 1 TU == 1024 us. + */ + +/* + * MAC_CSR0: ASIC revision number. + */ +#define MAC_CSR0 0x3000 +#define MAC_CSR0_REVISION FIELD32(0x0000000f) +#define MAC_CSR0_CHIPSET FIELD32(0x000ffff0) + +/* + * MAC_CSR1: System control register. + * SOFT_RESET: Software reset bit, 1: reset, 0: normal. + * BBP_RESET: Hardware reset BBP. + * HOST_READY: Host is ready after initialization, 1: ready. + */ +#define MAC_CSR1 0x3004 +#define MAC_CSR1_SOFT_RESET FIELD32(0x00000001) +#define MAC_CSR1_BBP_RESET FIELD32(0x00000002) +#define MAC_CSR1_HOST_READY FIELD32(0x00000004) + +/* + * MAC_CSR2: STA MAC register 0. + */ +#define MAC_CSR2 0x3008 +#define MAC_CSR2_BYTE0 FIELD32(0x000000ff) +#define MAC_CSR2_BYTE1 FIELD32(0x0000ff00) +#define MAC_CSR2_BYTE2 FIELD32(0x00ff0000) +#define MAC_CSR2_BYTE3 FIELD32(0xff000000) + +/* + * MAC_CSR3: STA MAC register 1. + * UNICAST_TO_ME_MASK: + * Used to mask off bits from byte 5 of the MAC address + * to determine the UNICAST_TO_ME bit for RX frames. + * The full mask is complemented by BSS_ID_MASK: + * MASK = BSS_ID_MASK & UNICAST_TO_ME_MASK + */ +#define MAC_CSR3 0x300c +#define MAC_CSR3_BYTE4 FIELD32(0x000000ff) +#define MAC_CSR3_BYTE5 FIELD32(0x0000ff00) +#define MAC_CSR3_UNICAST_TO_ME_MASK FIELD32(0x00ff0000) + +/* + * MAC_CSR4: BSSID register 0. + */ +#define MAC_CSR4 0x3010 +#define MAC_CSR4_BYTE0 FIELD32(0x000000ff) +#define MAC_CSR4_BYTE1 FIELD32(0x0000ff00) +#define MAC_CSR4_BYTE2 FIELD32(0x00ff0000) +#define MAC_CSR4_BYTE3 FIELD32(0xff000000) + +/* + * MAC_CSR5: BSSID register 1. + * BSS_ID_MASK: + * This mask is used to mask off bits 0 and 1 of byte 5 of the + * BSSID. This will make sure that those bits will be ignored + * when determining the MY_BSS of RX frames. + * 0: 1-BSSID mode (BSS index = 0) + * 1: 2-BSSID mode (BSS index: Byte5, bit 0) + * 2: 2-BSSID mode (BSS index: byte5, bit 1) + * 3: 4-BSSID mode (BSS index: byte5, bit 0 - 1) + */ +#define MAC_CSR5 0x3014 +#define MAC_CSR5_BYTE4 FIELD32(0x000000ff) +#define MAC_CSR5_BYTE5 FIELD32(0x0000ff00) +#define MAC_CSR5_BSS_ID_MASK FIELD32(0x00ff0000) + +/* + * MAC_CSR6: Maximum frame length register. + */ +#define MAC_CSR6 0x3018 +#define MAC_CSR6_MAX_FRAME_UNIT FIELD32(0x00000fff) + +/* + * MAC_CSR7: Reserved + */ +#define MAC_CSR7 0x301c + +/* + * MAC_CSR8: SIFS/EIFS register. + * All units are in US. + */ +#define MAC_CSR8 0x3020 +#define MAC_CSR8_SIFS FIELD32(0x000000ff) +#define MAC_CSR8_SIFS_AFTER_RX_OFDM FIELD32(0x0000ff00) +#define MAC_CSR8_EIFS FIELD32(0xffff0000) + +/* + * MAC_CSR9: Back-Off control register. + * SLOT_TIME: Slot time, default is 20us for 802.11BG. + * CWMIN: Bit for Cwmin. default Cwmin is 31 (2^5 - 1). + * CWMAX: Bit for Cwmax, default Cwmax is 1023 (2^10 - 1). + * CW_SELECT: 1: CWmin/Cwmax select from register, 0:select from TxD. + */ +#define MAC_CSR9 0x3024 +#define MAC_CSR9_SLOT_TIME FIELD32(0x000000ff) +#define MAC_CSR9_CWMIN FIELD32(0x00000f00) +#define MAC_CSR9_CWMAX FIELD32(0x0000f000) +#define MAC_CSR9_CW_SELECT FIELD32(0x00010000) + +/* + * MAC_CSR10: Power state configuration. + */ +#define MAC_CSR10 0x3028 + +/* + * MAC_CSR11: Power saving transition time register. + * DELAY_AFTER_TBCN: Delay after Tbcn expired in units of TU. + * TBCN_BEFORE_WAKEUP: Number of beacon before wakeup. + * WAKEUP_LATENCY: In unit of TU. + */ +#define MAC_CSR11 0x302c +#define MAC_CSR11_DELAY_AFTER_TBCN FIELD32(0x000000ff) +#define MAC_CSR11_TBCN_BEFORE_WAKEUP FIELD32(0x00007f00) +#define MAC_CSR11_AUTOWAKE FIELD32(0x00008000) +#define MAC_CSR11_WAKEUP_LATENCY FIELD32(0x000f0000) + +/* + * MAC_CSR12: Manual power control / status register (merge CSR20 & PWRCSR1). + * CURRENT_STATE: 0:sleep, 1:awake. + * FORCE_WAKEUP: This has higher priority than PUT_TO_SLEEP. + * BBP_CURRENT_STATE: 0: BBP sleep, 1: BBP awake. + */ +#define MAC_CSR12 0x3030 +#define MAC_CSR12_CURRENT_STATE FIELD32(0x00000001) +#define MAC_CSR12_PUT_TO_SLEEP FIELD32(0x00000002) +#define MAC_CSR12_FORCE_WAKEUP FIELD32(0x00000004) +#define MAC_CSR12_BBP_CURRENT_STATE FIELD32(0x00000008) + +/* + * MAC_CSR13: GPIO. + * MAC_CSR13_VALx: GPIO value + * MAC_CSR13_DIRx: GPIO direction: 0 = output; 1 = input + */ +#define MAC_CSR13 0x3034 +#define MAC_CSR13_VAL0 FIELD32(0x00000001) +#define MAC_CSR13_VAL1 FIELD32(0x00000002) +#define MAC_CSR13_VAL2 FIELD32(0x00000004) +#define MAC_CSR13_VAL3 FIELD32(0x00000008) +#define MAC_CSR13_VAL4 FIELD32(0x00000010) +#define MAC_CSR13_VAL5 FIELD32(0x00000020) +#define MAC_CSR13_DIR0 FIELD32(0x00000100) +#define MAC_CSR13_DIR1 FIELD32(0x00000200) +#define MAC_CSR13_DIR2 FIELD32(0x00000400) +#define MAC_CSR13_DIR3 FIELD32(0x00000800) +#define MAC_CSR13_DIR4 FIELD32(0x00001000) +#define MAC_CSR13_DIR5 FIELD32(0x00002000) + +/* + * MAC_CSR14: LED control register. + * ON_PERIOD: On period, default 70ms. + * OFF_PERIOD: Off period, default 30ms. + * HW_LED: HW TX activity, 1: normal OFF, 0: normal ON. + * SW_LED: s/w LED, 1: ON, 0: OFF. + * HW_LED_POLARITY: 0: active low, 1: active high. + */ +#define MAC_CSR14 0x3038 +#define MAC_CSR14_ON_PERIOD FIELD32(0x000000ff) +#define MAC_CSR14_OFF_PERIOD FIELD32(0x0000ff00) +#define MAC_CSR14_HW_LED FIELD32(0x00010000) +#define MAC_CSR14_SW_LED FIELD32(0x00020000) +#define MAC_CSR14_HW_LED_POLARITY FIELD32(0x00040000) +#define MAC_CSR14_SW_LED2 FIELD32(0x00080000) + +/* + * MAC_CSR15: NAV control. + */ +#define MAC_CSR15 0x303c + +/* + * TXRX control registers. + * Some values are set in TU, whereas 1 TU == 1024 us. + */ + +/* + * TXRX_CSR0: TX/RX configuration register. + * TSF_OFFSET: Default is 24. + * AUTO_TX_SEQ: 1: ASIC auto replace sequence nr in outgoing frame. + * DISABLE_RX: Disable Rx engine. + * DROP_CRC: Drop CRC error. + * DROP_PHYSICAL: Drop physical error. + * DROP_CONTROL: Drop control frame. + * DROP_NOT_TO_ME: Drop not to me unicast frame. + * DROP_TO_DS: Drop fram ToDs bit is true. + * DROP_VERSION_ERROR: Drop version error frame. + * DROP_MULTICAST: Drop multicast frames. + * DROP_BORADCAST: Drop broadcast frames. + * DROP_ACK_CTS: Drop received ACK and CTS. + */ +#define TXRX_CSR0 0x3040 +#define TXRX_CSR0_RX_ACK_TIMEOUT FIELD32(0x000001ff) +#define TXRX_CSR0_TSF_OFFSET FIELD32(0x00007e00) +#define TXRX_CSR0_AUTO_TX_SEQ FIELD32(0x00008000) +#define TXRX_CSR0_DISABLE_RX FIELD32(0x00010000) +#define TXRX_CSR0_DROP_CRC FIELD32(0x00020000) +#define TXRX_CSR0_DROP_PHYSICAL FIELD32(0x00040000) +#define TXRX_CSR0_DROP_CONTROL FIELD32(0x00080000) +#define TXRX_CSR0_DROP_NOT_TO_ME FIELD32(0x00100000) +#define TXRX_CSR0_DROP_TO_DS FIELD32(0x00200000) +#define TXRX_CSR0_DROP_VERSION_ERROR FIELD32(0x00400000) +#define TXRX_CSR0_DROP_MULTICAST FIELD32(0x00800000) +#define TXRX_CSR0_DROP_BROADCAST FIELD32(0x01000000) +#define TXRX_CSR0_DROP_ACK_CTS FIELD32(0x02000000) +#define TXRX_CSR0_TX_WITHOUT_WAITING FIELD32(0x04000000) + +/* + * TXRX_CSR1 + */ +#define TXRX_CSR1 0x3044 +#define TXRX_CSR1_BBP_ID0 FIELD32(0x0000007f) +#define TXRX_CSR1_BBP_ID0_VALID FIELD32(0x00000080) +#define TXRX_CSR1_BBP_ID1 FIELD32(0x00007f00) +#define TXRX_CSR1_BBP_ID1_VALID FIELD32(0x00008000) +#define TXRX_CSR1_BBP_ID2 FIELD32(0x007f0000) +#define TXRX_CSR1_BBP_ID2_VALID FIELD32(0x00800000) +#define TXRX_CSR1_BBP_ID3 FIELD32(0x7f000000) +#define TXRX_CSR1_BBP_ID3_VALID FIELD32(0x80000000) + +/* + * TXRX_CSR2 + */ +#define TXRX_CSR2 0x3048 +#define TXRX_CSR2_BBP_ID0 FIELD32(0x0000007f) +#define TXRX_CSR2_BBP_ID0_VALID FIELD32(0x00000080) +#define TXRX_CSR2_BBP_ID1 FIELD32(0x00007f00) +#define TXRX_CSR2_BBP_ID1_VALID FIELD32(0x00008000) +#define TXRX_CSR2_BBP_ID2 FIELD32(0x007f0000) +#define TXRX_CSR2_BBP_ID2_VALID FIELD32(0x00800000) +#define TXRX_CSR2_BBP_ID3 FIELD32(0x7f000000) +#define TXRX_CSR2_BBP_ID3_VALID FIELD32(0x80000000) + +/* + * TXRX_CSR3 + */ +#define TXRX_CSR3 0x304c +#define TXRX_CSR3_BBP_ID0 FIELD32(0x0000007f) +#define TXRX_CSR3_BBP_ID0_VALID FIELD32(0x00000080) +#define TXRX_CSR3_BBP_ID1 FIELD32(0x00007f00) +#define TXRX_CSR3_BBP_ID1_VALID FIELD32(0x00008000) +#define TXRX_CSR3_BBP_ID2 FIELD32(0x007f0000) +#define TXRX_CSR3_BBP_ID2_VALID FIELD32(0x00800000) +#define TXRX_CSR3_BBP_ID3 FIELD32(0x7f000000) +#define TXRX_CSR3_BBP_ID3_VALID FIELD32(0x80000000) + +/* + * TXRX_CSR4: Auto-Responder/Tx-retry register. + * AUTORESPOND_PREAMBLE: 0:long, 1:short preamble. + * OFDM_TX_RATE_DOWN: 1:enable. + * OFDM_TX_RATE_STEP: 0:1-step, 1: 2-step, 2:3-step, 3:4-step. + * OFDM_TX_FALLBACK_CCK: 0: Fallback to OFDM 6M only, 1: Fallback to CCK 1M,2M. + */ +#define TXRX_CSR4 0x3050 +#define TXRX_CSR4_TX_ACK_TIMEOUT FIELD32(0x000000ff) +#define TXRX_CSR4_CNTL_ACK_POLICY FIELD32(0x00000700) +#define TXRX_CSR4_ACK_CTS_PSM FIELD32(0x00010000) +#define TXRX_CSR4_AUTORESPOND_ENABLE FIELD32(0x00020000) +#define TXRX_CSR4_AUTORESPOND_PREAMBLE FIELD32(0x00040000) +#define TXRX_CSR4_OFDM_TX_RATE_DOWN FIELD32(0x00080000) +#define TXRX_CSR4_OFDM_TX_RATE_STEP FIELD32(0x00300000) +#define TXRX_CSR4_OFDM_TX_FALLBACK_CCK FIELD32(0x00400000) +#define TXRX_CSR4_LONG_RETRY_LIMIT FIELD32(0x0f000000) +#define TXRX_CSR4_SHORT_RETRY_LIMIT FIELD32(0xf0000000) + +/* + * TXRX_CSR5 + */ +#define TXRX_CSR5 0x3054 + +/* + * TXRX_CSR6: ACK/CTS payload consumed time + */ +#define TXRX_CSR6 0x3058 + +/* + * TXRX_CSR7: OFDM ACK/CTS payload consumed time for 6/9/12/18 mbps. + */ +#define TXRX_CSR7 0x305c +#define TXRX_CSR7_ACK_CTS_6MBS FIELD32(0x000000ff) +#define TXRX_CSR7_ACK_CTS_9MBS FIELD32(0x0000ff00) +#define TXRX_CSR7_ACK_CTS_12MBS FIELD32(0x00ff0000) +#define TXRX_CSR7_ACK_CTS_18MBS FIELD32(0xff000000) + +/* + * TXRX_CSR8: OFDM ACK/CTS payload consumed time for 24/36/48/54 mbps. + */ +#define TXRX_CSR8 0x3060 +#define TXRX_CSR8_ACK_CTS_24MBS FIELD32(0x000000ff) +#define TXRX_CSR8_ACK_CTS_36MBS FIELD32(0x0000ff00) +#define TXRX_CSR8_ACK_CTS_48MBS FIELD32(0x00ff0000) +#define TXRX_CSR8_ACK_CTS_54MBS FIELD32(0xff000000) + +/* + * TXRX_CSR9: Synchronization control register. + * BEACON_INTERVAL: In unit of 1/16 TU. + * TSF_TICKING: Enable TSF auto counting. + * TSF_SYNC: Tsf sync, 0: disable, 1: infra, 2: ad-hoc/master mode. + * BEACON_GEN: Enable beacon generator. + */ +#define TXRX_CSR9 0x3064 +#define TXRX_CSR9_BEACON_INTERVAL FIELD32(0x0000ffff) +#define TXRX_CSR9_TSF_TICKING FIELD32(0x00010000) +#define TXRX_CSR9_TSF_SYNC FIELD32(0x00060000) +#define TXRX_CSR9_TBTT_ENABLE FIELD32(0x00080000) +#define TXRX_CSR9_BEACON_GEN FIELD32(0x00100000) +#define TXRX_CSR9_TIMESTAMP_COMPENSATE FIELD32(0xff000000) + +/* + * TXRX_CSR10: BEACON alignment. + */ +#define TXRX_CSR10 0x3068 + +/* + * TXRX_CSR11: AES mask. + */ +#define TXRX_CSR11 0x306c + +/* + * TXRX_CSR12: TSF low 32. + */ +#define TXRX_CSR12 0x3070 +#define TXRX_CSR12_LOW_TSFTIMER FIELD32(0xffffffff) + +/* + * TXRX_CSR13: TSF high 32. + */ +#define TXRX_CSR13 0x3074 +#define TXRX_CSR13_HIGH_TSFTIMER FIELD32(0xffffffff) + +/* + * TXRX_CSR14: TBTT timer. + */ +#define TXRX_CSR14 0x3078 + +/* + * TXRX_CSR15: TKIP MIC priority byte "AND" mask. + */ +#define TXRX_CSR15 0x307c + +/* + * PHY control registers. + * Some values are set in TU, whereas 1 TU == 1024 us. + */ + +/* + * PHY_CSR0: RF/PS control. + */ +#define PHY_CSR0 0x3080 +#define PHY_CSR0_PA_PE_BG FIELD32(0x00010000) +#define PHY_CSR0_PA_PE_A FIELD32(0x00020000) + +/* + * PHY_CSR1 + */ +#define PHY_CSR1 0x3084 + +/* + * PHY_CSR2: Pre-TX BBP control. + */ +#define PHY_CSR2 0x3088 + +/* + * PHY_CSR3: BBP serial control register. + * VALUE: Register value to program into BBP. + * REG_NUM: Selected BBP register. + * READ_CONTROL: 0: Write BBP, 1: Read BBP. + * BUSY: 1: ASIC is busy execute BBP programming. + */ +#define PHY_CSR3 0x308c +#define PHY_CSR3_VALUE FIELD32(0x000000ff) +#define PHY_CSR3_REGNUM FIELD32(0x00007f00) +#define PHY_CSR3_READ_CONTROL FIELD32(0x00008000) +#define PHY_CSR3_BUSY FIELD32(0x00010000) + +/* + * PHY_CSR4: RF serial control register + * VALUE: Register value (include register id) serial out to RF/IF chip. + * NUMBER_OF_BITS: Number of bits used in RFRegValue (I:20, RFMD:22). + * IF_SELECT: 1: select IF to program, 0: select RF to program. + * PLL_LD: RF PLL_LD status. + * BUSY: 1: ASIC is busy execute RF programming. + */ +#define PHY_CSR4 0x3090 +#define PHY_CSR4_VALUE FIELD32(0x00ffffff) +#define PHY_CSR4_NUMBER_OF_BITS FIELD32(0x1f000000) +#define PHY_CSR4_IF_SELECT FIELD32(0x20000000) +#define PHY_CSR4_PLL_LD FIELD32(0x40000000) +#define PHY_CSR4_BUSY FIELD32(0x80000000) + +/* + * PHY_CSR5: RX to TX signal switch timing control. + */ +#define PHY_CSR5 0x3094 +#define PHY_CSR5_IQ_FLIP FIELD32(0x00000004) + +/* + * PHY_CSR6: TX to RX signal timing control. + */ +#define PHY_CSR6 0x3098 +#define PHY_CSR6_IQ_FLIP FIELD32(0x00000004) + +/* + * PHY_CSR7: TX DAC switching timing control. + */ +#define PHY_CSR7 0x309c + +/* + * Security control register. + */ + +/* + * SEC_CSR0: Shared key table control. + */ +#define SEC_CSR0 0x30a0 +#define SEC_CSR0_BSS0_KEY0_VALID FIELD32(0x00000001) +#define SEC_CSR0_BSS0_KEY1_VALID FIELD32(0x00000002) +#define SEC_CSR0_BSS0_KEY2_VALID FIELD32(0x00000004) +#define SEC_CSR0_BSS0_KEY3_VALID FIELD32(0x00000008) +#define SEC_CSR0_BSS1_KEY0_VALID FIELD32(0x00000010) +#define SEC_CSR0_BSS1_KEY1_VALID FIELD32(0x00000020) +#define SEC_CSR0_BSS1_KEY2_VALID FIELD32(0x00000040) +#define SEC_CSR0_BSS1_KEY3_VALID FIELD32(0x00000080) +#define SEC_CSR0_BSS2_KEY0_VALID FIELD32(0x00000100) +#define SEC_CSR0_BSS2_KEY1_VALID FIELD32(0x00000200) +#define SEC_CSR0_BSS2_KEY2_VALID FIELD32(0x00000400) +#define SEC_CSR0_BSS2_KEY3_VALID FIELD32(0x00000800) +#define SEC_CSR0_BSS3_KEY0_VALID FIELD32(0x00001000) +#define SEC_CSR0_BSS3_KEY1_VALID FIELD32(0x00002000) +#define SEC_CSR0_BSS3_KEY2_VALID FIELD32(0x00004000) +#define SEC_CSR0_BSS3_KEY3_VALID FIELD32(0x00008000) + +/* + * SEC_CSR1: Shared key table security mode register. + */ +#define SEC_CSR1 0x30a4 +#define SEC_CSR1_BSS0_KEY0_CIPHER_ALG FIELD32(0x00000007) +#define SEC_CSR1_BSS0_KEY1_CIPHER_ALG FIELD32(0x00000070) +#define SEC_CSR1_BSS0_KEY2_CIPHER_ALG FIELD32(0x00000700) +#define SEC_CSR1_BSS0_KEY3_CIPHER_ALG FIELD32(0x00007000) +#define SEC_CSR1_BSS1_KEY0_CIPHER_ALG FIELD32(0x00070000) +#define SEC_CSR1_BSS1_KEY1_CIPHER_ALG FIELD32(0x00700000) +#define SEC_CSR1_BSS1_KEY2_CIPHER_ALG FIELD32(0x07000000) +#define SEC_CSR1_BSS1_KEY3_CIPHER_ALG FIELD32(0x70000000) + +/* + * Pairwise key table valid bitmap registers. + * SEC_CSR2: pairwise key table valid bitmap 0. + * SEC_CSR3: pairwise key table valid bitmap 1. + */ +#define SEC_CSR2 0x30a8 +#define SEC_CSR3 0x30ac + +/* + * SEC_CSR4: Pairwise key table lookup control. + */ +#define SEC_CSR4 0x30b0 +#define SEC_CSR4_ENABLE_BSS0 FIELD32(0x00000001) +#define SEC_CSR4_ENABLE_BSS1 FIELD32(0x00000002) +#define SEC_CSR4_ENABLE_BSS2 FIELD32(0x00000004) +#define SEC_CSR4_ENABLE_BSS3 FIELD32(0x00000008) + +/* + * SEC_CSR5: shared key table security mode register. + */ +#define SEC_CSR5 0x30b4 +#define SEC_CSR5_BSS2_KEY0_CIPHER_ALG FIELD32(0x00000007) +#define SEC_CSR5_BSS2_KEY1_CIPHER_ALG FIELD32(0x00000070) +#define SEC_CSR5_BSS2_KEY2_CIPHER_ALG FIELD32(0x00000700) +#define SEC_CSR5_BSS2_KEY3_CIPHER_ALG FIELD32(0x00007000) +#define SEC_CSR5_BSS3_KEY0_CIPHER_ALG FIELD32(0x00070000) +#define SEC_CSR5_BSS3_KEY1_CIPHER_ALG FIELD32(0x00700000) +#define SEC_CSR5_BSS3_KEY2_CIPHER_ALG FIELD32(0x07000000) +#define SEC_CSR5_BSS3_KEY3_CIPHER_ALG FIELD32(0x70000000) + +/* + * STA control registers. + */ + +/* + * STA_CSR0: RX PLCP error count & RX FCS error count. + */ +#define STA_CSR0 0x30c0 +#define STA_CSR0_FCS_ERROR FIELD32(0x0000ffff) +#define STA_CSR0_PLCP_ERROR FIELD32(0xffff0000) + +/* + * STA_CSR1: RX False CCA count & RX LONG frame count. + */ +#define STA_CSR1 0x30c4 +#define STA_CSR1_PHYSICAL_ERROR FIELD32(0x0000ffff) +#define STA_CSR1_FALSE_CCA_ERROR FIELD32(0xffff0000) + +/* + * STA_CSR2: TX Beacon count and RX FIFO overflow count. + */ +#define STA_CSR2 0x30c8 +#define STA_CSR2_RX_FIFO_OVERFLOW_COUNT FIELD32(0x0000ffff) +#define STA_CSR2_RX_OVERFLOW_COUNT FIELD32(0xffff0000) + +/* + * STA_CSR3: TX Beacon count. + */ +#define STA_CSR3 0x30cc +#define STA_CSR3_TX_BEACON_COUNT FIELD32(0x0000ffff) + +/* + * STA_CSR4: TX Result status register. + * VALID: 1:This register contains a valid TX result. + */ +#define STA_CSR4 0x30d0 +#define STA_CSR4_VALID FIELD32(0x00000001) +#define STA_CSR4_TX_RESULT FIELD32(0x0000000e) +#define STA_CSR4_RETRY_COUNT FIELD32(0x000000f0) +#define STA_CSR4_PID_SUBTYPE FIELD32(0x00001f00) +#define STA_CSR4_PID_TYPE FIELD32(0x0000e000) +#define STA_CSR4_TXRATE FIELD32(0x000f0000) + +/* + * QOS control registers. + */ + +/* + * QOS_CSR0: TXOP holder MAC address register. + */ +#define QOS_CSR0 0x30e0 +#define QOS_CSR0_BYTE0 FIELD32(0x000000ff) +#define QOS_CSR0_BYTE1 FIELD32(0x0000ff00) +#define QOS_CSR0_BYTE2 FIELD32(0x00ff0000) +#define QOS_CSR0_BYTE3 FIELD32(0xff000000) + +/* + * QOS_CSR1: TXOP holder MAC address register. + */ +#define QOS_CSR1 0x30e4 +#define QOS_CSR1_BYTE4 FIELD32(0x000000ff) +#define QOS_CSR1_BYTE5 FIELD32(0x0000ff00) + +/* + * QOS_CSR2: TXOP holder timeout register. + */ +#define QOS_CSR2 0x30e8 + +/* + * RX QOS-CFPOLL MAC address register. + * QOS_CSR3: RX QOS-CFPOLL MAC address 0. + * QOS_CSR4: RX QOS-CFPOLL MAC address 1. + */ +#define QOS_CSR3 0x30ec +#define QOS_CSR4 0x30f0 + +/* + * QOS_CSR5: "QosControl" field of the RX QOS-CFPOLL. + */ +#define QOS_CSR5 0x30f4 + +/* + * Host DMA registers. + */ + +/* + * AC0_BASE_CSR: AC_VO base address. + */ +#define AC0_BASE_CSR 0x3400 +#define AC0_BASE_CSR_RING_REGISTER FIELD32(0xffffffff) + +/* + * AC1_BASE_CSR: AC_VI base address. + */ +#define AC1_BASE_CSR 0x3404 +#define AC1_BASE_CSR_RING_REGISTER FIELD32(0xffffffff) + +/* + * AC2_BASE_CSR: AC_BE base address. + */ +#define AC2_BASE_CSR 0x3408 +#define AC2_BASE_CSR_RING_REGISTER FIELD32(0xffffffff) + +/* + * AC3_BASE_CSR: AC_BK base address. + */ +#define AC3_BASE_CSR 0x340c +#define AC3_BASE_CSR_RING_REGISTER FIELD32(0xffffffff) + +/* + * MGMT_BASE_CSR: MGMT ring base address. + */ +#define MGMT_BASE_CSR 0x3410 +#define MGMT_BASE_CSR_RING_REGISTER FIELD32(0xffffffff) + +/* + * TX_RING_CSR0: TX Ring size for AC_VO, AC_VI, AC_BE, AC_BK. + */ +#define TX_RING_CSR0 0x3418 +#define TX_RING_CSR0_AC0_RING_SIZE FIELD32(0x000000ff) +#define TX_RING_CSR0_AC1_RING_SIZE FIELD32(0x0000ff00) +#define TX_RING_CSR0_AC2_RING_SIZE FIELD32(0x00ff0000) +#define TX_RING_CSR0_AC3_RING_SIZE FIELD32(0xff000000) + +/* + * TX_RING_CSR1: TX Ring size for MGMT Ring, HCCA Ring + * TXD_SIZE: In unit of 32-bit. + */ +#define TX_RING_CSR1 0x341c +#define TX_RING_CSR1_MGMT_RING_SIZE FIELD32(0x000000ff) +#define TX_RING_CSR1_HCCA_RING_SIZE FIELD32(0x0000ff00) +#define TX_RING_CSR1_TXD_SIZE FIELD32(0x003f0000) + +/* + * AIFSN_CSR: AIFSN for each EDCA AC. + * AIFSN0: For AC_VO. + * AIFSN1: For AC_VI. + * AIFSN2: For AC_BE. + * AIFSN3: For AC_BK. + */ +#define AIFSN_CSR 0x3420 +#define AIFSN_CSR_AIFSN0 FIELD32(0x0000000f) +#define AIFSN_CSR_AIFSN1 FIELD32(0x000000f0) +#define AIFSN_CSR_AIFSN2 FIELD32(0x00000f00) +#define AIFSN_CSR_AIFSN3 FIELD32(0x0000f000) + +/* + * CWMIN_CSR: CWmin for each EDCA AC. + * CWMIN0: For AC_VO. + * CWMIN1: For AC_VI. + * CWMIN2: For AC_BE. + * CWMIN3: For AC_BK. + */ +#define CWMIN_CSR 0x3424 +#define CWMIN_CSR_CWMIN0 FIELD32(0x0000000f) +#define CWMIN_CSR_CWMIN1 FIELD32(0x000000f0) +#define CWMIN_CSR_CWMIN2 FIELD32(0x00000f00) +#define CWMIN_CSR_CWMIN3 FIELD32(0x0000f000) + +/* + * CWMAX_CSR: CWmax for each EDCA AC. + * CWMAX0: For AC_VO. + * CWMAX1: For AC_VI. + * CWMAX2: For AC_BE. + * CWMAX3: For AC_BK. + */ +#define CWMAX_CSR 0x3428 +#define CWMAX_CSR_CWMAX0 FIELD32(0x0000000f) +#define CWMAX_CSR_CWMAX1 FIELD32(0x000000f0) +#define CWMAX_CSR_CWMAX2 FIELD32(0x00000f00) +#define CWMAX_CSR_CWMAX3 FIELD32(0x0000f000) + +/* + * TX_DMA_DST_CSR: TX DMA destination + * 0: TX ring0, 1: TX ring1, 2: TX ring2 3: invalid + */ +#define TX_DMA_DST_CSR 0x342c +#define TX_DMA_DST_CSR_DEST_AC0 FIELD32(0x00000003) +#define TX_DMA_DST_CSR_DEST_AC1 FIELD32(0x0000000c) +#define TX_DMA_DST_CSR_DEST_AC2 FIELD32(0x00000030) +#define TX_DMA_DST_CSR_DEST_AC3 FIELD32(0x000000c0) +#define TX_DMA_DST_CSR_DEST_MGMT FIELD32(0x00000300) + +/* + * TX_CNTL_CSR: KICK/Abort TX. + * KICK_TX_AC0: For AC_VO. + * KICK_TX_AC1: For AC_VI. + * KICK_TX_AC2: For AC_BE. + * KICK_TX_AC3: For AC_BK. + * ABORT_TX_AC0: For AC_VO. + * ABORT_TX_AC1: For AC_VI. + * ABORT_TX_AC2: For AC_BE. + * ABORT_TX_AC3: For AC_BK. + */ +#define TX_CNTL_CSR 0x3430 +#define TX_CNTL_CSR_KICK_TX_AC0 FIELD32(0x00000001) +#define TX_CNTL_CSR_KICK_TX_AC1 FIELD32(0x00000002) +#define TX_CNTL_CSR_KICK_TX_AC2 FIELD32(0x00000004) +#define TX_CNTL_CSR_KICK_TX_AC3 FIELD32(0x00000008) +#define TX_CNTL_CSR_KICK_TX_MGMT FIELD32(0x00000010) +#define TX_CNTL_CSR_ABORT_TX_AC0 FIELD32(0x00010000) +#define TX_CNTL_CSR_ABORT_TX_AC1 FIELD32(0x00020000) +#define TX_CNTL_CSR_ABORT_TX_AC2 FIELD32(0x00040000) +#define TX_CNTL_CSR_ABORT_TX_AC3 FIELD32(0x00080000) +#define TX_CNTL_CSR_ABORT_TX_MGMT FIELD32(0x00100000) + +/* + * LOAD_TX_RING_CSR: Load RX desriptor + */ +#define LOAD_TX_RING_CSR 0x3434 +#define LOAD_TX_RING_CSR_LOAD_TXD_AC0 FIELD32(0x00000001) +#define LOAD_TX_RING_CSR_LOAD_TXD_AC1 FIELD32(0x00000002) +#define LOAD_TX_RING_CSR_LOAD_TXD_AC2 FIELD32(0x00000004) +#define LOAD_TX_RING_CSR_LOAD_TXD_AC3 FIELD32(0x00000008) +#define LOAD_TX_RING_CSR_LOAD_TXD_MGMT FIELD32(0x00000010) + +/* + * Several read-only registers, for debugging. + */ +#define AC0_TXPTR_CSR 0x3438 +#define AC1_TXPTR_CSR 0x343c +#define AC2_TXPTR_CSR 0x3440 +#define AC3_TXPTR_CSR 0x3444 +#define MGMT_TXPTR_CSR 0x3448 + +/* + * RX_BASE_CSR + */ +#define RX_BASE_CSR 0x3450 +#define RX_BASE_CSR_RING_REGISTER FIELD32(0xffffffff) + +/* + * RX_RING_CSR. + * RXD_SIZE: In unit of 32-bit. + */ +#define RX_RING_CSR 0x3454 +#define RX_RING_CSR_RING_SIZE FIELD32(0x000000ff) +#define RX_RING_CSR_RXD_SIZE FIELD32(0x00003f00) +#define RX_RING_CSR_RXD_WRITEBACK_SIZE FIELD32(0x00070000) + +/* + * RX_CNTL_CSR + */ +#define RX_CNTL_CSR 0x3458 +#define RX_CNTL_CSR_ENABLE_RX_DMA FIELD32(0x00000001) +#define RX_CNTL_CSR_LOAD_RXD FIELD32(0x00000002) + +/* + * RXPTR_CSR: Read-only, for debugging. + */ +#define RXPTR_CSR 0x345c + +/* + * PCI_CFG_CSR + */ +#define PCI_CFG_CSR 0x3460 + +/* + * BUF_FORMAT_CSR + */ +#define BUF_FORMAT_CSR 0x3464 + +/* + * INT_SOURCE_CSR: Interrupt source register. + * Write one to clear corresponding bit. + */ +#define INT_SOURCE_CSR 0x3468 +#define INT_SOURCE_CSR_TXDONE FIELD32(0x00000001) +#define INT_SOURCE_CSR_RXDONE FIELD32(0x00000002) +#define INT_SOURCE_CSR_BEACON_DONE FIELD32(0x00000004) +#define INT_SOURCE_CSR_TX_ABORT_DONE FIELD32(0x00000010) +#define INT_SOURCE_CSR_AC0_DMA_DONE FIELD32(0x00010000) +#define INT_SOURCE_CSR_AC1_DMA_DONE FIELD32(0x00020000) +#define INT_SOURCE_CSR_AC2_DMA_DONE FIELD32(0x00040000) +#define INT_SOURCE_CSR_AC3_DMA_DONE FIELD32(0x00080000) +#define INT_SOURCE_CSR_MGMT_DMA_DONE FIELD32(0x00100000) +#define INT_SOURCE_CSR_HCCA_DMA_DONE FIELD32(0x00200000) + +/* + * INT_MASK_CSR: Interrupt MASK register. 1: the interrupt is mask OFF. + * MITIGATION_PERIOD: Interrupt mitigation in unit of 32 PCI clock. + */ +#define INT_MASK_CSR 0x346c +#define INT_MASK_CSR_TXDONE FIELD32(0x00000001) +#define INT_MASK_CSR_RXDONE FIELD32(0x00000002) +#define INT_MASK_CSR_BEACON_DONE FIELD32(0x00000004) +#define INT_MASK_CSR_TX_ABORT_DONE FIELD32(0x00000010) +#define INT_MASK_CSR_ENABLE_MITIGATION FIELD32(0x00000080) +#define INT_MASK_CSR_MITIGATION_PERIOD FIELD32(0x0000ff00) +#define INT_MASK_CSR_AC0_DMA_DONE FIELD32(0x00010000) +#define INT_MASK_CSR_AC1_DMA_DONE FIELD32(0x00020000) +#define INT_MASK_CSR_AC2_DMA_DONE FIELD32(0x00040000) +#define INT_MASK_CSR_AC3_DMA_DONE FIELD32(0x00080000) +#define INT_MASK_CSR_MGMT_DMA_DONE FIELD32(0x00100000) +#define INT_MASK_CSR_HCCA_DMA_DONE FIELD32(0x00200000) + +/* + * E2PROM_CSR: EEPROM control register. + * RELOAD: Write 1 to reload eeprom content. + * TYPE_93C46: 1: 93c46, 0:93c66. + * LOAD_STATUS: 1:loading, 0:done. + */ +#define E2PROM_CSR 0x3470 +#define E2PROM_CSR_RELOAD FIELD32(0x00000001) +#define E2PROM_CSR_DATA_CLOCK FIELD32(0x00000002) +#define E2PROM_CSR_CHIP_SELECT FIELD32(0x00000004) +#define E2PROM_CSR_DATA_IN FIELD32(0x00000008) +#define E2PROM_CSR_DATA_OUT FIELD32(0x00000010) +#define E2PROM_CSR_TYPE_93C46 FIELD32(0x00000020) +#define E2PROM_CSR_LOAD_STATUS FIELD32(0x00000040) + +/* + * AC_TXOP_CSR0: AC_VO/AC_VI TXOP register. + * AC0_TX_OP: For AC_VO, in unit of 32us. + * AC1_TX_OP: For AC_VI, in unit of 32us. + */ +#define AC_TXOP_CSR0 0x3474 +#define AC_TXOP_CSR0_AC0_TX_OP FIELD32(0x0000ffff) +#define AC_TXOP_CSR0_AC1_TX_OP FIELD32(0xffff0000) + +/* + * AC_TXOP_CSR1: AC_BE/AC_BK TXOP register. + * AC2_TX_OP: For AC_BE, in unit of 32us. + * AC3_TX_OP: For AC_BK, in unit of 32us. + */ +#define AC_TXOP_CSR1 0x3478 +#define AC_TXOP_CSR1_AC2_TX_OP FIELD32(0x0000ffff) +#define AC_TXOP_CSR1_AC3_TX_OP FIELD32(0xffff0000) + +/* + * DMA_STATUS_CSR + */ +#define DMA_STATUS_CSR 0x3480 + +/* + * TEST_MODE_CSR + */ +#define TEST_MODE_CSR 0x3484 + +/* + * UART0_TX_CSR + */ +#define UART0_TX_CSR 0x3488 + +/* + * UART0_RX_CSR + */ +#define UART0_RX_CSR 0x348c + +/* + * UART0_FRAME_CSR + */ +#define UART0_FRAME_CSR 0x3490 + +/* + * UART0_BUFFER_CSR + */ +#define UART0_BUFFER_CSR 0x3494 + +/* + * IO_CNTL_CSR + * RF_PS: Set RF interface value to power save + */ +#define IO_CNTL_CSR 0x3498 +#define IO_CNTL_CSR_RF_PS FIELD32(0x00000004) + +/* + * UART_INT_SOURCE_CSR + */ +#define UART_INT_SOURCE_CSR 0x34a8 + +/* + * UART_INT_MASK_CSR + */ +#define UART_INT_MASK_CSR 0x34ac + +/* + * PBF_QUEUE_CSR + */ +#define PBF_QUEUE_CSR 0x34b0 + +/* + * Firmware DMA registers. + * Firmware DMA registers are dedicated for MCU usage + * and should not be touched by host driver. + * Therefore we skip the definition of these registers. + */ +#define FW_TX_BASE_CSR 0x34c0 +#define FW_TX_START_CSR 0x34c4 +#define FW_TX_LAST_CSR 0x34c8 +#define FW_MODE_CNTL_CSR 0x34cc +#define FW_TXPTR_CSR 0x34d0 + +/* + * 8051 firmware image. + */ +#define FIRMWARE_RT2561 "rt2561.bin" +#define FIRMWARE_RT2561s "rt2561s.bin" +#define FIRMWARE_RT2661 "rt2661.bin" +#define FIRMWARE_IMAGE_BASE 0x4000 + +/* + * BBP registers. + * The wordsize of the BBP is 8 bits. + */ + +/* + * R2 + */ +#define BBP_R2_BG_MODE FIELD8(0x20) + +/* + * R3 + */ +#define BBP_R3_SMART_MODE FIELD8(0x01) + +/* + * R4: RX antenna control + * FRAME_END: 1 - DPDT, 0 - SPDT (Only valid for 802.11G, RF2527 & RF2529) + */ + +/* + * ANTENNA_CONTROL semantics (guessed): + * 0x1: Software controlled antenna switching (fixed or SW diversity) + * 0x2: Hardware diversity. + */ +#define BBP_R4_RX_ANTENNA_CONTROL FIELD8(0x03) +#define BBP_R4_RX_FRAME_END FIELD8(0x20) + +/* + * R77 + */ +#define BBP_R77_RX_ANTENNA FIELD8(0x03) + +/* + * RF registers + */ + +/* + * RF 3 + */ +#define RF3_TXPOWER FIELD32(0x00003e00) + +/* + * RF 4 + */ +#define RF4_FREQ_OFFSET FIELD32(0x0003f000) + +/* + * EEPROM content. + * The wordsize of the EEPROM is 16 bits. + */ + +/* + * HW MAC address. + */ +#define EEPROM_MAC_ADDR_0 0x0002 +#define EEPROM_MAC_ADDR_BYTE0 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE1 FIELD16(0xff00) +#define EEPROM_MAC_ADDR1 0x0003 +#define EEPROM_MAC_ADDR_BYTE2 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE3 FIELD16(0xff00) +#define EEPROM_MAC_ADDR_2 0x0004 +#define EEPROM_MAC_ADDR_BYTE4 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE5 FIELD16(0xff00) + +/* + * EEPROM antenna. + * ANTENNA_NUM: Number of antenna's. + * TX_DEFAULT: Default antenna 0: diversity, 1: A, 2: B. + * RX_DEFAULT: Default antenna 0: diversity, 1: A, 2: B. + * FRAME_TYPE: 0: DPDT , 1: SPDT , noted this bit is valid for g only. + * DYN_TXAGC: Dynamic TX AGC control. + * HARDWARE_RADIO: 1: Hardware controlled radio. Read GPIO0. + * RF_TYPE: Rf_type of this adapter. + */ +#define EEPROM_ANTENNA 0x0010 +#define EEPROM_ANTENNA_NUM FIELD16(0x0003) +#define EEPROM_ANTENNA_TX_DEFAULT FIELD16(0x000c) +#define EEPROM_ANTENNA_RX_DEFAULT FIELD16(0x0030) +#define EEPROM_ANTENNA_FRAME_TYPE FIELD16(0x0040) +#define EEPROM_ANTENNA_DYN_TXAGC FIELD16(0x0200) +#define EEPROM_ANTENNA_HARDWARE_RADIO FIELD16(0x0400) +#define EEPROM_ANTENNA_RF_TYPE FIELD16(0xf800) + +/* + * EEPROM NIC config. + * ENABLE_DIVERSITY: 1:enable, 0:disable. + * EXTERNAL_LNA_BG: External LNA enable for 2.4G. + * CARDBUS_ACCEL: 0:enable, 1:disable. + * EXTERNAL_LNA_A: External LNA enable for 5G. + */ +#define EEPROM_NIC 0x0011 +#define EEPROM_NIC_ENABLE_DIVERSITY FIELD16(0x0001) +#define EEPROM_NIC_TX_DIVERSITY FIELD16(0x0002) +#define EEPROM_NIC_RX_FIXED FIELD16(0x0004) +#define EEPROM_NIC_TX_FIXED FIELD16(0x0008) +#define EEPROM_NIC_EXTERNAL_LNA_BG FIELD16(0x0010) +#define EEPROM_NIC_CARDBUS_ACCEL FIELD16(0x0020) +#define EEPROM_NIC_EXTERNAL_LNA_A FIELD16(0x0040) + +/* + * EEPROM geography. + * GEO_A: Default geographical setting for 5GHz band + * GEO: Default geographical setting. + */ +#define EEPROM_GEOGRAPHY 0x0012 +#define EEPROM_GEOGRAPHY_GEO_A FIELD16(0x00ff) +#define EEPROM_GEOGRAPHY_GEO FIELD16(0xff00) + +/* + * EEPROM BBP. + */ +#define EEPROM_BBP_START 0x0013 +#define EEPROM_BBP_SIZE 16 +#define EEPROM_BBP_VALUE FIELD16(0x00ff) +#define EEPROM_BBP_REG_ID FIELD16(0xff00) + +/* + * EEPROM TXPOWER 802.11G + */ +#define EEPROM_TXPOWER_G_START 0x0023 +#define EEPROM_TXPOWER_G_SIZE 7 +#define EEPROM_TXPOWER_G_1 FIELD16(0x00ff) +#define EEPROM_TXPOWER_G_2 FIELD16(0xff00) + +/* + * EEPROM Frequency + */ +#define EEPROM_FREQ 0x002f +#define EEPROM_FREQ_OFFSET FIELD16(0x00ff) +#define EEPROM_FREQ_SEQ_MASK FIELD16(0xff00) +#define EEPROM_FREQ_SEQ FIELD16(0x0300) + +/* + * EEPROM LED. + * POLARITY_RDY_G: Polarity RDY_G setting. + * POLARITY_RDY_A: Polarity RDY_A setting. + * POLARITY_ACT: Polarity ACT setting. + * POLARITY_GPIO_0: Polarity GPIO0 setting. + * POLARITY_GPIO_1: Polarity GPIO1 setting. + * POLARITY_GPIO_2: Polarity GPIO2 setting. + * POLARITY_GPIO_3: Polarity GPIO3 setting. + * POLARITY_GPIO_4: Polarity GPIO4 setting. + * LED_MODE: Led mode. + */ +#define EEPROM_LED 0x0030 +#define EEPROM_LED_POLARITY_RDY_G FIELD16(0x0001) +#define EEPROM_LED_POLARITY_RDY_A FIELD16(0x0002) +#define EEPROM_LED_POLARITY_ACT FIELD16(0x0004) +#define EEPROM_LED_POLARITY_GPIO_0 FIELD16(0x0008) +#define EEPROM_LED_POLARITY_GPIO_1 FIELD16(0x0010) +#define EEPROM_LED_POLARITY_GPIO_2 FIELD16(0x0020) +#define EEPROM_LED_POLARITY_GPIO_3 FIELD16(0x0040) +#define EEPROM_LED_POLARITY_GPIO_4 FIELD16(0x0080) +#define EEPROM_LED_LED_MODE FIELD16(0x1f00) + +/* + * EEPROM TXPOWER 802.11A + */ +#define EEPROM_TXPOWER_A_START 0x0031 +#define EEPROM_TXPOWER_A_SIZE 12 +#define EEPROM_TXPOWER_A_1 FIELD16(0x00ff) +#define EEPROM_TXPOWER_A_2 FIELD16(0xff00) + +/* + * EEPROM RSSI offset 802.11BG + */ +#define EEPROM_RSSI_OFFSET_BG 0x004d +#define EEPROM_RSSI_OFFSET_BG_1 FIELD16(0x00ff) +#define EEPROM_RSSI_OFFSET_BG_2 FIELD16(0xff00) + +/* + * EEPROM RSSI offset 802.11A + */ +#define EEPROM_RSSI_OFFSET_A 0x004e +#define EEPROM_RSSI_OFFSET_A_1 FIELD16(0x00ff) +#define EEPROM_RSSI_OFFSET_A_2 FIELD16(0xff00) + +/* + * MCU mailbox commands. + */ +#define MCU_SLEEP 0x30 +#define MCU_WAKEUP 0x31 +#define MCU_LED 0x50 +#define MCU_LED_STRENGTH 0x52 + +/* + * DMA descriptor defines. + */ +#define TXD_DESC_SIZE ( 16 * sizeof(__le32) ) +#define TXINFO_SIZE ( 6 * sizeof(__le32) ) +#define RXD_DESC_SIZE ( 16 * sizeof(__le32) ) + +/* + * TX descriptor format for TX, PRIO and Beacon Ring. + */ + +/* + * Word0 + * TKIP_MIC: ASIC appends TKIP MIC if TKIP is used. + * KEY_TABLE: Use per-client pairwise KEY table. + * KEY_INDEX: + * Key index (0~31) to the pairwise KEY table. + * 0~3 to shared KEY table 0 (BSS0). + * 4~7 to shared KEY table 1 (BSS1). + * 8~11 to shared KEY table 2 (BSS2). + * 12~15 to shared KEY table 3 (BSS3). + * BURST: Next frame belongs to same "burst" event. + */ +#define TXD_W0_OWNER_NIC FIELD32(0x00000001) +#define TXD_W0_VALID FIELD32(0x00000002) +#define TXD_W0_MORE_FRAG FIELD32(0x00000004) +#define TXD_W0_ACK FIELD32(0x00000008) +#define TXD_W0_TIMESTAMP FIELD32(0x00000010) +#define TXD_W0_OFDM FIELD32(0x00000020) +#define TXD_W0_IFS FIELD32(0x00000040) +#define TXD_W0_RETRY_MODE FIELD32(0x00000080) +#define TXD_W0_TKIP_MIC FIELD32(0x00000100) +#define TXD_W0_KEY_TABLE FIELD32(0x00000200) +#define TXD_W0_KEY_INDEX FIELD32(0x0000fc00) +#define TXD_W0_DATABYTE_COUNT FIELD32(0x0fff0000) +#define TXD_W0_BURST FIELD32(0x10000000) +#define TXD_W0_CIPHER_ALG FIELD32(0xe0000000) + +/* + * Word1 + * HOST_Q_ID: EDCA/HCCA queue ID. + * HW_SEQUENCE: MAC overwrites the frame sequence number. + * BUFFER_COUNT: Number of buffers in this TXD. + */ +#define TXD_W1_HOST_Q_ID FIELD32(0x0000000f) +#define TXD_W1_AIFSN FIELD32(0x000000f0) +#define TXD_W1_CWMIN FIELD32(0x00000f00) +#define TXD_W1_CWMAX FIELD32(0x0000f000) +#define TXD_W1_IV_OFFSET FIELD32(0x003f0000) +#define TXD_W1_PIGGY_BACK FIELD32(0x01000000) +#define TXD_W1_HW_SEQUENCE FIELD32(0x10000000) +#define TXD_W1_BUFFER_COUNT FIELD32(0xe0000000) + +/* + * Word2: PLCP information + */ +#define TXD_W2_PLCP_SIGNAL FIELD32(0x000000ff) +#define TXD_W2_PLCP_SERVICE FIELD32(0x0000ff00) +#define TXD_W2_PLCP_LENGTH_LOW FIELD32(0x00ff0000) +#define TXD_W2_PLCP_LENGTH_HIGH FIELD32(0xff000000) + +/* + * Word3 + */ +#define TXD_W3_IV FIELD32(0xffffffff) + +/* + * Word4 + */ +#define TXD_W4_EIV FIELD32(0xffffffff) + +/* + * Word5 + * FRAME_OFFSET: Frame start offset inside ASIC TXFIFO (after TXINFO field). + * TXD_W5_PID_SUBTYPE: Driver assigned packet ID index for txdone handler. + * TXD_W5_PID_TYPE: Driver assigned packet ID type for txdone handler. + * WAITING_DMA_DONE_INT: TXD been filled with data + * and waiting for TxDoneISR housekeeping. + */ +#define TXD_W5_FRAME_OFFSET FIELD32(0x000000ff) +#define TXD_W5_PID_SUBTYPE FIELD32(0x00001f00) +#define TXD_W5_PID_TYPE FIELD32(0x0000e000) +#define TXD_W5_TX_POWER FIELD32(0x00ff0000) +#define TXD_W5_WAITING_DMA_DONE_INT FIELD32(0x01000000) + +/* + * the above 24-byte is called TXINFO and will be DMAed to MAC block + * through TXFIFO. MAC block use this TXINFO to control the transmission + * behavior of this frame. + * The following fields are not used by MAC block. + * They are used by DMA block and HOST driver only. + * Once a frame has been DMA to ASIC, all the following fields are useless + * to ASIC. + */ + +/* + * Word6-10: Buffer physical address + */ +#define TXD_W6_BUFFER_PHYSICAL_ADDRESS FIELD32(0xffffffff) +#define TXD_W7_BUFFER_PHYSICAL_ADDRESS FIELD32(0xffffffff) +#define TXD_W8_BUFFER_PHYSICAL_ADDRESS FIELD32(0xffffffff) +#define TXD_W9_BUFFER_PHYSICAL_ADDRESS FIELD32(0xffffffff) +#define TXD_W10_BUFFER_PHYSICAL_ADDRESS FIELD32(0xffffffff) + +/* + * Word11-13: Buffer length + */ +#define TXD_W11_BUFFER_LENGTH0 FIELD32(0x00000fff) +#define TXD_W11_BUFFER_LENGTH1 FIELD32(0x0fff0000) +#define TXD_W12_BUFFER_LENGTH2 FIELD32(0x00000fff) +#define TXD_W12_BUFFER_LENGTH3 FIELD32(0x0fff0000) +#define TXD_W13_BUFFER_LENGTH4 FIELD32(0x00000fff) + +/* + * Word14 + */ +#define TXD_W14_SK_BUFFER FIELD32(0xffffffff) + +/* + * Word15 + */ +#define TXD_W15_NEXT_SK_BUFFER FIELD32(0xffffffff) + +/* + * RX descriptor format for RX Ring. + */ + +/* + * Word0 + * CIPHER_ERROR: 1:ICV error, 2:MIC error, 3:invalid key. + * KEY_INDEX: Decryption key actually used. + */ +#define RXD_W0_OWNER_NIC FIELD32(0x00000001) +#define RXD_W0_DROP FIELD32(0x00000002) +#define RXD_W0_UNICAST_TO_ME FIELD32(0x00000004) +#define RXD_W0_MULTICAST FIELD32(0x00000008) +#define RXD_W0_BROADCAST FIELD32(0x00000010) +#define RXD_W0_MY_BSS FIELD32(0x00000020) +#define RXD_W0_CRC_ERROR FIELD32(0x00000040) +#define RXD_W0_OFDM FIELD32(0x00000080) +#define RXD_W0_CIPHER_ERROR FIELD32(0x00000300) +#define RXD_W0_KEY_INDEX FIELD32(0x0000fc00) +#define RXD_W0_DATABYTE_COUNT FIELD32(0x0fff0000) +#define RXD_W0_CIPHER_ALG FIELD32(0xe0000000) + +/* + * Word1 + * SIGNAL: RX raw data rate reported by BBP. + */ +#define RXD_W1_SIGNAL FIELD32(0x000000ff) +#define RXD_W1_RSSI_AGC FIELD32(0x00001f00) +#define RXD_W1_RSSI_LNA FIELD32(0x00006000) +#define RXD_W1_FRAME_OFFSET FIELD32(0x7f000000) + +/* + * Word2 + * IV: Received IV of originally encrypted. + */ +#define RXD_W2_IV FIELD32(0xffffffff) + +/* + * Word3 + * EIV: Received EIV of originally encrypted. + */ +#define RXD_W3_EIV FIELD32(0xffffffff) + +/* + * Word4 + * ICV: Received ICV of originally encrypted. + * NOTE: This is a guess, the official definition is "reserved" + */ +#define RXD_W4_ICV FIELD32(0xffffffff) + +/* + * the above 20-byte is called RXINFO and will be DMAed to MAC RX block + * and passed to the HOST driver. + * The following fields are for DMA block and HOST usage only. + * Can't be touched by ASIC MAC block. + */ + +/* + * Word5 + */ +#define RXD_W5_BUFFER_PHYSICAL_ADDRESS FIELD32(0xffffffff) + +/* + * Word6-15: Reserved + */ +#define RXD_W6_RESERVED FIELD32(0xffffffff) +#define RXD_W7_RESERVED FIELD32(0xffffffff) +#define RXD_W8_RESERVED FIELD32(0xffffffff) +#define RXD_W9_RESERVED FIELD32(0xffffffff) +#define RXD_W10_RESERVED FIELD32(0xffffffff) +#define RXD_W11_RESERVED FIELD32(0xffffffff) +#define RXD_W12_RESERVED FIELD32(0xffffffff) +#define RXD_W13_RESERVED FIELD32(0xffffffff) +#define RXD_W14_RESERVED FIELD32(0xffffffff) +#define RXD_W15_RESERVED FIELD32(0xffffffff) + +/* + * Macros for converting txpower from EEPROM to mac80211 value + * and from mac80211 value to register value. + */ +#define MIN_TXPOWER 0 +#define MAX_TXPOWER 31 +#define DEFAULT_TXPOWER 24 + +#define TXPOWER_FROM_DEV(__txpower) \ + (((u8)(__txpower)) > MAX_TXPOWER) ? DEFAULT_TXPOWER : (__txpower) + +#define TXPOWER_TO_DEV(__txpower) \ + clamp_t(char, __txpower, MIN_TXPOWER, MAX_TXPOWER) + +#endif /* RT61PCI_H */ diff --git a/drivers/net/wireless/rt2x00/rt73usb.c b/drivers/net/wireless/rt2x00/rt73usb.c new file mode 100644 index 0000000..c44b225 --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt73usb.c @@ -0,0 +1,2545 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt73usb + Abstract: rt73usb device specific routines. + Supported chipsets: rt2571W & rt2671. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rt2x00.h" +#include "rt2x00usb.h" +#include "rt73usb.h" + +/* + * Allow hardware encryption to be disabled. + */ +static bool modparam_nohwcrypt; +module_param_named(nohwcrypt, modparam_nohwcrypt, bool, S_IRUGO); +MODULE_PARM_DESC(nohwcrypt, "Disable hardware encryption."); + +/* + * Register access. + * All access to the CSR registers will go through the methods + * rt2x00usb_register_read and rt2x00usb_register_write. + * BBP and RF register require indirect register access, + * and use the CSR registers BBPCSR and RFCSR to achieve this. + * These indirect registers work with busy bits, + * and we will try maximal REGISTER_BUSY_COUNT times to access + * the register while taking a REGISTER_BUSY_DELAY us delay + * between each attampt. When the busy bit is still set at that time, + * the access attempt is considered to have failed, + * and we will print an error. + * The _lock versions must be used if you already hold the csr_mutex + */ +#define WAIT_FOR_BBP(__dev, __reg) \ + rt2x00usb_regbusy_read((__dev), PHY_CSR3, PHY_CSR3_BUSY, (__reg)) +#define WAIT_FOR_RF(__dev, __reg) \ + rt2x00usb_regbusy_read((__dev), PHY_CSR4, PHY_CSR4_BUSY, (__reg)) + +static void rt73usb_bbp_write(struct rt2x00_dev *rt2x00dev, + const unsigned int word, const u8 value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the BBP becomes available, afterwards we + * can safely write the new data into the register. + */ + if (WAIT_FOR_BBP(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, PHY_CSR3_VALUE, value); + rt2x00_set_field32(®, PHY_CSR3_REGNUM, word); + rt2x00_set_field32(®, PHY_CSR3_BUSY, 1); + rt2x00_set_field32(®, PHY_CSR3_READ_CONTROL, 0); + + rt2x00usb_register_write_lock(rt2x00dev, PHY_CSR3, reg); + } + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt73usb_bbp_read(struct rt2x00_dev *rt2x00dev, + const unsigned int word, u8 *value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the BBP becomes available, afterwards we + * can safely write the read request into the register. + * After the data has been written, we wait until hardware + * returns the correct value, if at any time the register + * doesn't become available in time, reg will be 0xffffffff + * which means we return 0xff to the caller. + */ + if (WAIT_FOR_BBP(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, PHY_CSR3_REGNUM, word); + rt2x00_set_field32(®, PHY_CSR3_BUSY, 1); + rt2x00_set_field32(®, PHY_CSR3_READ_CONTROL, 1); + + rt2x00usb_register_write_lock(rt2x00dev, PHY_CSR3, reg); + + WAIT_FOR_BBP(rt2x00dev, ®); + } + + *value = rt2x00_get_field32(reg, PHY_CSR3_VALUE); + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +static void rt73usb_rf_write(struct rt2x00_dev *rt2x00dev, + const unsigned int word, const u32 value) +{ + u32 reg; + + mutex_lock(&rt2x00dev->csr_mutex); + + /* + * Wait until the RF becomes available, afterwards we + * can safely write the new data into the register. + */ + if (WAIT_FOR_RF(rt2x00dev, ®)) { + reg = 0; + rt2x00_set_field32(®, PHY_CSR4_VALUE, value); + /* + * RF5225 and RF2527 contain 21 bits per RF register value, + * all others contain 20 bits. + */ + rt2x00_set_field32(®, PHY_CSR4_NUMBER_OF_BITS, + 20 + (rt2x00_rf(rt2x00dev, RF5225) || + rt2x00_rf(rt2x00dev, RF2527))); + rt2x00_set_field32(®, PHY_CSR4_IF_SELECT, 0); + rt2x00_set_field32(®, PHY_CSR4_BUSY, 1); + + rt2x00usb_register_write_lock(rt2x00dev, PHY_CSR4, reg); + rt2x00_rf_write(rt2x00dev, word, value); + } + + mutex_unlock(&rt2x00dev->csr_mutex); +} + +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS +static const struct rt2x00debug rt73usb_rt2x00debug = { + .owner = THIS_MODULE, + .csr = { + .read = rt2x00usb_register_read, + .write = rt2x00usb_register_write, + .flags = RT2X00DEBUGFS_OFFSET, + .word_base = CSR_REG_BASE, + .word_size = sizeof(u32), + .word_count = CSR_REG_SIZE / sizeof(u32), + }, + .eeprom = { + .read = rt2x00_eeprom_read, + .write = rt2x00_eeprom_write, + .word_base = EEPROM_BASE, + .word_size = sizeof(u16), + .word_count = EEPROM_SIZE / sizeof(u16), + }, + .bbp = { + .read = rt73usb_bbp_read, + .write = rt73usb_bbp_write, + .word_base = BBP_BASE, + .word_size = sizeof(u8), + .word_count = BBP_SIZE / sizeof(u8), + }, + .rf = { + .read = rt2x00_rf_read, + .write = rt73usb_rf_write, + .word_base = RF_BASE, + .word_size = sizeof(u32), + .word_count = RF_SIZE / sizeof(u32), + }, +}; +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ + +static int rt73usb_rfkill_poll(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + rt2x00usb_register_read(rt2x00dev, MAC_CSR13, ®); + return rt2x00_get_field32(reg, MAC_CSR13_VAL7); +} + +#ifdef CPTCFG_RT2X00_LIB_LEDS +static void rt73usb_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct rt2x00_led *led = + container_of(led_cdev, struct rt2x00_led, led_dev); + unsigned int enabled = brightness != LED_OFF; + unsigned int a_mode = + (enabled && led->rt2x00dev->curr_band == IEEE80211_BAND_5GHZ); + unsigned int bg_mode = + (enabled && led->rt2x00dev->curr_band == IEEE80211_BAND_2GHZ); + + if (led->type == LED_TYPE_RADIO) { + rt2x00_set_field16(&led->rt2x00dev->led_mcu_reg, + MCU_LEDCS_RADIO_STATUS, enabled); + + rt2x00usb_vendor_request_sw(led->rt2x00dev, USB_LED_CONTROL, + 0, led->rt2x00dev->led_mcu_reg, + REGISTER_TIMEOUT); + } else if (led->type == LED_TYPE_ASSOC) { + rt2x00_set_field16(&led->rt2x00dev->led_mcu_reg, + MCU_LEDCS_LINK_BG_STATUS, bg_mode); + rt2x00_set_field16(&led->rt2x00dev->led_mcu_reg, + MCU_LEDCS_LINK_A_STATUS, a_mode); + + rt2x00usb_vendor_request_sw(led->rt2x00dev, USB_LED_CONTROL, + 0, led->rt2x00dev->led_mcu_reg, + REGISTER_TIMEOUT); + } else if (led->type == LED_TYPE_QUALITY) { + /* + * The brightness is divided into 6 levels (0 - 5), + * this means we need to convert the brightness + * argument into the matching level within that range. + */ + rt2x00usb_vendor_request_sw(led->rt2x00dev, USB_LED_CONTROL, + brightness / (LED_FULL / 6), + led->rt2x00dev->led_mcu_reg, + REGISTER_TIMEOUT); + } +} + +static int rt73usb_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct rt2x00_led *led = + container_of(led_cdev, struct rt2x00_led, led_dev); + u32 reg; + + rt2x00usb_register_read(led->rt2x00dev, MAC_CSR14, ®); + rt2x00_set_field32(®, MAC_CSR14_ON_PERIOD, *delay_on); + rt2x00_set_field32(®, MAC_CSR14_OFF_PERIOD, *delay_off); + rt2x00usb_register_write(led->rt2x00dev, MAC_CSR14, reg); + + return 0; +} + +static void rt73usb_init_led(struct rt2x00_dev *rt2x00dev, + struct rt2x00_led *led, + enum led_type type) +{ + led->rt2x00dev = rt2x00dev; + led->type = type; + led->led_dev.brightness_set = rt73usb_brightness_set; + led->led_dev.blink_set = rt73usb_blink_set; + led->flags = LED_INITIALIZED; +} +#endif /* CPTCFG_RT2X00_LIB_LEDS */ + +/* + * Configuration handlers. + */ +static int rt73usb_config_shared_key(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_crypto *crypto, + struct ieee80211_key_conf *key) +{ + struct hw_key_entry key_entry; + struct rt2x00_field32 field; + u32 mask; + u32 reg; + + if (crypto->cmd == SET_KEY) { + /* + * rt2x00lib can't determine the correct free + * key_idx for shared keys. We have 1 register + * with key valid bits. The goal is simple, read + * the register, if that is full we have no slots + * left. + * Note that each BSS is allowed to have up to 4 + * shared keys, so put a mask over the allowed + * entries. + */ + mask = (0xf << crypto->bssidx); + + rt2x00usb_register_read(rt2x00dev, SEC_CSR0, ®); + reg &= mask; + + if (reg && reg == mask) + return -ENOSPC; + + key->hw_key_idx += reg ? ffz(reg) : 0; + + /* + * Upload key to hardware + */ + memcpy(key_entry.key, crypto->key, + sizeof(key_entry.key)); + memcpy(key_entry.tx_mic, crypto->tx_mic, + sizeof(key_entry.tx_mic)); + memcpy(key_entry.rx_mic, crypto->rx_mic, + sizeof(key_entry.rx_mic)); + + reg = SHARED_KEY_ENTRY(key->hw_key_idx); + rt2x00usb_register_multiwrite(rt2x00dev, reg, + &key_entry, sizeof(key_entry)); + + /* + * The cipher types are stored over 2 registers. + * bssidx 0 and 1 keys are stored in SEC_CSR1 and + * bssidx 1 and 2 keys are stored in SEC_CSR5. + * Using the correct defines correctly will cause overhead, + * so just calculate the correct offset. + */ + if (key->hw_key_idx < 8) { + field.bit_offset = (3 * key->hw_key_idx); + field.bit_mask = 0x7 << field.bit_offset; + + rt2x00usb_register_read(rt2x00dev, SEC_CSR1, ®); + rt2x00_set_field32(®, field, crypto->cipher); + rt2x00usb_register_write(rt2x00dev, SEC_CSR1, reg); + } else { + field.bit_offset = (3 * (key->hw_key_idx - 8)); + field.bit_mask = 0x7 << field.bit_offset; + + rt2x00usb_register_read(rt2x00dev, SEC_CSR5, ®); + rt2x00_set_field32(®, field, crypto->cipher); + rt2x00usb_register_write(rt2x00dev, SEC_CSR5, reg); + } + + /* + * The driver does not support the IV/EIV generation + * in hardware. However it doesn't support the IV/EIV + * inside the ieee80211 frame either, but requires it + * to be provided separately for the descriptor. + * rt2x00lib will cut the IV/EIV data out of all frames + * given to us by mac80211, but we must tell mac80211 + * to generate the IV/EIV data. + */ + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + } + + /* + * SEC_CSR0 contains only single-bit fields to indicate + * a particular key is valid. Because using the FIELD32() + * defines directly will cause a lot of overhead we use + * a calculation to determine the correct bit directly. + */ + mask = 1 << key->hw_key_idx; + + rt2x00usb_register_read(rt2x00dev, SEC_CSR0, ®); + if (crypto->cmd == SET_KEY) + reg |= mask; + else if (crypto->cmd == DISABLE_KEY) + reg &= ~mask; + rt2x00usb_register_write(rt2x00dev, SEC_CSR0, reg); + + return 0; +} + +static int rt73usb_config_pairwise_key(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_crypto *crypto, + struct ieee80211_key_conf *key) +{ + struct hw_pairwise_ta_entry addr_entry; + struct hw_key_entry key_entry; + u32 mask; + u32 reg; + + if (crypto->cmd == SET_KEY) { + /* + * rt2x00lib can't determine the correct free + * key_idx for pairwise keys. We have 2 registers + * with key valid bits. The goal is simple, read + * the first register, if that is full move to + * the next register. + * When both registers are full, we drop the key, + * otherwise we use the first invalid entry. + */ + rt2x00usb_register_read(rt2x00dev, SEC_CSR2, ®); + if (reg && reg == ~0) { + key->hw_key_idx = 32; + rt2x00usb_register_read(rt2x00dev, SEC_CSR3, ®); + if (reg && reg == ~0) + return -ENOSPC; + } + + key->hw_key_idx += reg ? ffz(reg) : 0; + + /* + * Upload key to hardware + */ + memcpy(key_entry.key, crypto->key, + sizeof(key_entry.key)); + memcpy(key_entry.tx_mic, crypto->tx_mic, + sizeof(key_entry.tx_mic)); + memcpy(key_entry.rx_mic, crypto->rx_mic, + sizeof(key_entry.rx_mic)); + + reg = PAIRWISE_KEY_ENTRY(key->hw_key_idx); + rt2x00usb_register_multiwrite(rt2x00dev, reg, + &key_entry, sizeof(key_entry)); + + /* + * Send the address and cipher type to the hardware register. + */ + memset(&addr_entry, 0, sizeof(addr_entry)); + memcpy(&addr_entry, crypto->address, ETH_ALEN); + addr_entry.cipher = crypto->cipher; + + reg = PAIRWISE_TA_ENTRY(key->hw_key_idx); + rt2x00usb_register_multiwrite(rt2x00dev, reg, + &addr_entry, sizeof(addr_entry)); + + /* + * Enable pairwise lookup table for given BSS idx, + * without this received frames will not be decrypted + * by the hardware. + */ + rt2x00usb_register_read(rt2x00dev, SEC_CSR4, ®); + reg |= (1 << crypto->bssidx); + rt2x00usb_register_write(rt2x00dev, SEC_CSR4, reg); + + /* + * The driver does not support the IV/EIV generation + * in hardware. However it doesn't support the IV/EIV + * inside the ieee80211 frame either, but requires it + * to be provided separately for the descriptor. + * rt2x00lib will cut the IV/EIV data out of all frames + * given to us by mac80211, but we must tell mac80211 + * to generate the IV/EIV data. + */ + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + } + + /* + * SEC_CSR2 and SEC_CSR3 contain only single-bit fields to indicate + * a particular key is valid. Because using the FIELD32() + * defines directly will cause a lot of overhead we use + * a calculation to determine the correct bit directly. + */ + if (key->hw_key_idx < 32) { + mask = 1 << key->hw_key_idx; + + rt2x00usb_register_read(rt2x00dev, SEC_CSR2, ®); + if (crypto->cmd == SET_KEY) + reg |= mask; + else if (crypto->cmd == DISABLE_KEY) + reg &= ~mask; + rt2x00usb_register_write(rt2x00dev, SEC_CSR2, reg); + } else { + mask = 1 << (key->hw_key_idx - 32); + + rt2x00usb_register_read(rt2x00dev, SEC_CSR3, ®); + if (crypto->cmd == SET_KEY) + reg |= mask; + else if (crypto->cmd == DISABLE_KEY) + reg &= ~mask; + rt2x00usb_register_write(rt2x00dev, SEC_CSR3, reg); + } + + return 0; +} + +static void rt73usb_config_filter(struct rt2x00_dev *rt2x00dev, + const unsigned int filter_flags) +{ + u32 reg; + + /* + * Start configuration steps. + * Note that the version error will always be dropped + * and broadcast frames will always be accepted since + * there is no filter for it at this time. + */ + rt2x00usb_register_read(rt2x00dev, TXRX_CSR0, ®); + rt2x00_set_field32(®, TXRX_CSR0_DROP_CRC, + !(filter_flags & FIF_FCSFAIL)); + rt2x00_set_field32(®, TXRX_CSR0_DROP_PHYSICAL, + !(filter_flags & FIF_PLCPFAIL)); + rt2x00_set_field32(®, TXRX_CSR0_DROP_CONTROL, + !(filter_flags & (FIF_CONTROL | FIF_PSPOLL))); + rt2x00_set_field32(®, TXRX_CSR0_DROP_NOT_TO_ME, + !(filter_flags & FIF_PROMISC_IN_BSS)); + rt2x00_set_field32(®, TXRX_CSR0_DROP_TO_DS, + !(filter_flags & FIF_PROMISC_IN_BSS) && + !rt2x00dev->intf_ap_count); + rt2x00_set_field32(®, TXRX_CSR0_DROP_VERSION_ERROR, 1); + rt2x00_set_field32(®, TXRX_CSR0_DROP_MULTICAST, + !(filter_flags & FIF_ALLMULTI)); + rt2x00_set_field32(®, TXRX_CSR0_DROP_BROADCAST, 0); + rt2x00_set_field32(®, TXRX_CSR0_DROP_ACK_CTS, + !(filter_flags & FIF_CONTROL)); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR0, reg); +} + +static void rt73usb_config_intf(struct rt2x00_dev *rt2x00dev, + struct rt2x00_intf *intf, + struct rt2x00intf_conf *conf, + const unsigned int flags) +{ + u32 reg; + + if (flags & CONFIG_UPDATE_TYPE) { + /* + * Enable synchronisation. + */ + rt2x00usb_register_read(rt2x00dev, TXRX_CSR9, ®); + rt2x00_set_field32(®, TXRX_CSR9_TSF_SYNC, conf->sync); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR9, reg); + } + + if (flags & CONFIG_UPDATE_MAC) { + reg = le32_to_cpu(conf->mac[1]); + rt2x00_set_field32(®, MAC_CSR3_UNICAST_TO_ME_MASK, 0xff); + conf->mac[1] = cpu_to_le32(reg); + + rt2x00usb_register_multiwrite(rt2x00dev, MAC_CSR2, + conf->mac, sizeof(conf->mac)); + } + + if (flags & CONFIG_UPDATE_BSSID) { + reg = le32_to_cpu(conf->bssid[1]); + rt2x00_set_field32(®, MAC_CSR5_BSS_ID_MASK, 3); + conf->bssid[1] = cpu_to_le32(reg); + + rt2x00usb_register_multiwrite(rt2x00dev, MAC_CSR4, + conf->bssid, sizeof(conf->bssid)); + } +} + +static void rt73usb_config_erp(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_erp *erp, + u32 changed) +{ + u32 reg; + + rt2x00usb_register_read(rt2x00dev, TXRX_CSR0, ®); + rt2x00_set_field32(®, TXRX_CSR0_RX_ACK_TIMEOUT, 0x32); + rt2x00_set_field32(®, TXRX_CSR0_TSF_OFFSET, IEEE80211_HEADER); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR0, reg); + + if (changed & BSS_CHANGED_ERP_PREAMBLE) { + rt2x00usb_register_read(rt2x00dev, TXRX_CSR4, ®); + rt2x00_set_field32(®, TXRX_CSR4_AUTORESPOND_ENABLE, 1); + rt2x00_set_field32(®, TXRX_CSR4_AUTORESPOND_PREAMBLE, + !!erp->short_preamble); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR4, reg); + } + + if (changed & BSS_CHANGED_BASIC_RATES) + rt2x00usb_register_write(rt2x00dev, TXRX_CSR5, + erp->basic_rates); + + if (changed & BSS_CHANGED_BEACON_INT) { + rt2x00usb_register_read(rt2x00dev, TXRX_CSR9, ®); + rt2x00_set_field32(®, TXRX_CSR9_BEACON_INTERVAL, + erp->beacon_int * 16); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR9, reg); + } + + if (changed & BSS_CHANGED_ERP_SLOT) { + rt2x00usb_register_read(rt2x00dev, MAC_CSR9, ®); + rt2x00_set_field32(®, MAC_CSR9_SLOT_TIME, erp->slot_time); + rt2x00usb_register_write(rt2x00dev, MAC_CSR9, reg); + + rt2x00usb_register_read(rt2x00dev, MAC_CSR8, ®); + rt2x00_set_field32(®, MAC_CSR8_SIFS, erp->sifs); + rt2x00_set_field32(®, MAC_CSR8_SIFS_AFTER_RX_OFDM, 3); + rt2x00_set_field32(®, MAC_CSR8_EIFS, erp->eifs); + rt2x00usb_register_write(rt2x00dev, MAC_CSR8, reg); + } +} + +static void rt73usb_config_antenna_5x(struct rt2x00_dev *rt2x00dev, + struct antenna_setup *ant) +{ + u8 r3; + u8 r4; + u8 r77; + u8 temp; + + rt73usb_bbp_read(rt2x00dev, 3, &r3); + rt73usb_bbp_read(rt2x00dev, 4, &r4); + rt73usb_bbp_read(rt2x00dev, 77, &r77); + + rt2x00_set_field8(&r3, BBP_R3_SMART_MODE, 0); + + /* + * Configure the RX antenna. + */ + switch (ant->rx) { + case ANTENNA_HW_DIVERSITY: + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA_CONTROL, 2); + temp = !test_bit(CAPABILITY_FRAME_TYPE, &rt2x00dev->cap_flags) + && (rt2x00dev->curr_band != IEEE80211_BAND_5GHZ); + rt2x00_set_field8(&r4, BBP_R4_RX_FRAME_END, temp); + break; + case ANTENNA_A: + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA_CONTROL, 1); + rt2x00_set_field8(&r4, BBP_R4_RX_FRAME_END, 0); + if (rt2x00dev->curr_band == IEEE80211_BAND_5GHZ) + rt2x00_set_field8(&r77, BBP_R77_RX_ANTENNA, 0); + else + rt2x00_set_field8(&r77, BBP_R77_RX_ANTENNA, 3); + break; + case ANTENNA_B: + default: + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA_CONTROL, 1); + rt2x00_set_field8(&r4, BBP_R4_RX_FRAME_END, 0); + if (rt2x00dev->curr_band == IEEE80211_BAND_5GHZ) + rt2x00_set_field8(&r77, BBP_R77_RX_ANTENNA, 3); + else + rt2x00_set_field8(&r77, BBP_R77_RX_ANTENNA, 0); + break; + } + + rt73usb_bbp_write(rt2x00dev, 77, r77); + rt73usb_bbp_write(rt2x00dev, 3, r3); + rt73usb_bbp_write(rt2x00dev, 4, r4); +} + +static void rt73usb_config_antenna_2x(struct rt2x00_dev *rt2x00dev, + struct antenna_setup *ant) +{ + u8 r3; + u8 r4; + u8 r77; + + rt73usb_bbp_read(rt2x00dev, 3, &r3); + rt73usb_bbp_read(rt2x00dev, 4, &r4); + rt73usb_bbp_read(rt2x00dev, 77, &r77); + + rt2x00_set_field8(&r3, BBP_R3_SMART_MODE, 0); + rt2x00_set_field8(&r4, BBP_R4_RX_FRAME_END, + !test_bit(CAPABILITY_FRAME_TYPE, &rt2x00dev->cap_flags)); + + /* + * Configure the RX antenna. + */ + switch (ant->rx) { + case ANTENNA_HW_DIVERSITY: + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA_CONTROL, 2); + break; + case ANTENNA_A: + rt2x00_set_field8(&r77, BBP_R77_RX_ANTENNA, 3); + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA_CONTROL, 1); + break; + case ANTENNA_B: + default: + rt2x00_set_field8(&r77, BBP_R77_RX_ANTENNA, 0); + rt2x00_set_field8(&r4, BBP_R4_RX_ANTENNA_CONTROL, 1); + break; + } + + rt73usb_bbp_write(rt2x00dev, 77, r77); + rt73usb_bbp_write(rt2x00dev, 3, r3); + rt73usb_bbp_write(rt2x00dev, 4, r4); +} + +struct antenna_sel { + u8 word; + /* + * value[0] -> non-LNA + * value[1] -> LNA + */ + u8 value[2]; +}; + +static const struct antenna_sel antenna_sel_a[] = { + { 96, { 0x58, 0x78 } }, + { 104, { 0x38, 0x48 } }, + { 75, { 0xfe, 0x80 } }, + { 86, { 0xfe, 0x80 } }, + { 88, { 0xfe, 0x80 } }, + { 35, { 0x60, 0x60 } }, + { 97, { 0x58, 0x58 } }, + { 98, { 0x58, 0x58 } }, +}; + +static const struct antenna_sel antenna_sel_bg[] = { + { 96, { 0x48, 0x68 } }, + { 104, { 0x2c, 0x3c } }, + { 75, { 0xfe, 0x80 } }, + { 86, { 0xfe, 0x80 } }, + { 88, { 0xfe, 0x80 } }, + { 35, { 0x50, 0x50 } }, + { 97, { 0x48, 0x48 } }, + { 98, { 0x48, 0x48 } }, +}; + +static void rt73usb_config_ant(struct rt2x00_dev *rt2x00dev, + struct antenna_setup *ant) +{ + const struct antenna_sel *sel; + unsigned int lna; + unsigned int i; + u32 reg; + + /* + * We should never come here because rt2x00lib is supposed + * to catch this and send us the correct antenna explicitely. + */ + BUG_ON(ant->rx == ANTENNA_SW_DIVERSITY || + ant->tx == ANTENNA_SW_DIVERSITY); + + if (rt2x00dev->curr_band == IEEE80211_BAND_5GHZ) { + sel = antenna_sel_a; + lna = test_bit(CAPABILITY_EXTERNAL_LNA_A, &rt2x00dev->cap_flags); + } else { + sel = antenna_sel_bg; + lna = test_bit(CAPABILITY_EXTERNAL_LNA_BG, &rt2x00dev->cap_flags); + } + + for (i = 0; i < ARRAY_SIZE(antenna_sel_a); i++) + rt73usb_bbp_write(rt2x00dev, sel[i].word, sel[i].value[lna]); + + rt2x00usb_register_read(rt2x00dev, PHY_CSR0, ®); + + rt2x00_set_field32(®, PHY_CSR0_PA_PE_BG, + (rt2x00dev->curr_band == IEEE80211_BAND_2GHZ)); + rt2x00_set_field32(®, PHY_CSR0_PA_PE_A, + (rt2x00dev->curr_band == IEEE80211_BAND_5GHZ)); + + rt2x00usb_register_write(rt2x00dev, PHY_CSR0, reg); + + if (rt2x00_rf(rt2x00dev, RF5226) || rt2x00_rf(rt2x00dev, RF5225)) + rt73usb_config_antenna_5x(rt2x00dev, ant); + else if (rt2x00_rf(rt2x00dev, RF2528) || rt2x00_rf(rt2x00dev, RF2527)) + rt73usb_config_antenna_2x(rt2x00dev, ant); +} + +static void rt73usb_config_lna_gain(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf) +{ + u16 eeprom; + short lna_gain = 0; + + if (libconf->conf->chandef.chan->band == IEEE80211_BAND_2GHZ) { + if (test_bit(CAPABILITY_EXTERNAL_LNA_BG, &rt2x00dev->cap_flags)) + lna_gain += 14; + + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_OFFSET_BG, &eeprom); + lna_gain -= rt2x00_get_field16(eeprom, EEPROM_RSSI_OFFSET_BG_1); + } else { + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_OFFSET_A, &eeprom); + lna_gain -= rt2x00_get_field16(eeprom, EEPROM_RSSI_OFFSET_A_1); + } + + rt2x00dev->lna_gain = lna_gain; +} + +static void rt73usb_config_channel(struct rt2x00_dev *rt2x00dev, + struct rf_channel *rf, const int txpower) +{ + u8 r3; + u8 r94; + u8 smart; + + rt2x00_set_field32(&rf->rf3, RF3_TXPOWER, TXPOWER_TO_DEV(txpower)); + rt2x00_set_field32(&rf->rf4, RF4_FREQ_OFFSET, rt2x00dev->freq_offset); + + smart = !(rt2x00_rf(rt2x00dev, RF5225) || rt2x00_rf(rt2x00dev, RF2527)); + + rt73usb_bbp_read(rt2x00dev, 3, &r3); + rt2x00_set_field8(&r3, BBP_R3_SMART_MODE, smart); + rt73usb_bbp_write(rt2x00dev, 3, r3); + + r94 = 6; + if (txpower > MAX_TXPOWER && txpower <= (MAX_TXPOWER + r94)) + r94 += txpower - MAX_TXPOWER; + else if (txpower < MIN_TXPOWER && txpower >= (MIN_TXPOWER - r94)) + r94 += txpower; + rt73usb_bbp_write(rt2x00dev, 94, r94); + + rt73usb_rf_write(rt2x00dev, 1, rf->rf1); + rt73usb_rf_write(rt2x00dev, 2, rf->rf2); + rt73usb_rf_write(rt2x00dev, 3, rf->rf3 & ~0x00000004); + rt73usb_rf_write(rt2x00dev, 4, rf->rf4); + + rt73usb_rf_write(rt2x00dev, 1, rf->rf1); + rt73usb_rf_write(rt2x00dev, 2, rf->rf2); + rt73usb_rf_write(rt2x00dev, 3, rf->rf3 | 0x00000004); + rt73usb_rf_write(rt2x00dev, 4, rf->rf4); + + rt73usb_rf_write(rt2x00dev, 1, rf->rf1); + rt73usb_rf_write(rt2x00dev, 2, rf->rf2); + rt73usb_rf_write(rt2x00dev, 3, rf->rf3 & ~0x00000004); + rt73usb_rf_write(rt2x00dev, 4, rf->rf4); + + udelay(10); +} + +static void rt73usb_config_txpower(struct rt2x00_dev *rt2x00dev, + const int txpower) +{ + struct rf_channel rf; + + rt2x00_rf_read(rt2x00dev, 1, &rf.rf1); + rt2x00_rf_read(rt2x00dev, 2, &rf.rf2); + rt2x00_rf_read(rt2x00dev, 3, &rf.rf3); + rt2x00_rf_read(rt2x00dev, 4, &rf.rf4); + + rt73usb_config_channel(rt2x00dev, &rf, txpower); +} + +static void rt73usb_config_retry_limit(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf) +{ + u32 reg; + + rt2x00usb_register_read(rt2x00dev, TXRX_CSR4, ®); + rt2x00_set_field32(®, TXRX_CSR4_OFDM_TX_RATE_DOWN, 1); + rt2x00_set_field32(®, TXRX_CSR4_OFDM_TX_RATE_STEP, 0); + rt2x00_set_field32(®, TXRX_CSR4_OFDM_TX_FALLBACK_CCK, 0); + rt2x00_set_field32(®, TXRX_CSR4_LONG_RETRY_LIMIT, + libconf->conf->long_frame_max_tx_count); + rt2x00_set_field32(®, TXRX_CSR4_SHORT_RETRY_LIMIT, + libconf->conf->short_frame_max_tx_count); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR4, reg); +} + +static void rt73usb_config_ps(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf) +{ + enum dev_state state = + (libconf->conf->flags & IEEE80211_CONF_PS) ? + STATE_SLEEP : STATE_AWAKE; + u32 reg; + + if (state == STATE_SLEEP) { + rt2x00usb_register_read(rt2x00dev, MAC_CSR11, ®); + rt2x00_set_field32(®, MAC_CSR11_DELAY_AFTER_TBCN, + rt2x00dev->beacon_int - 10); + rt2x00_set_field32(®, MAC_CSR11_TBCN_BEFORE_WAKEUP, + libconf->conf->listen_interval - 1); + rt2x00_set_field32(®, MAC_CSR11_WAKEUP_LATENCY, 5); + + /* We must first disable autowake before it can be enabled */ + rt2x00_set_field32(®, MAC_CSR11_AUTOWAKE, 0); + rt2x00usb_register_write(rt2x00dev, MAC_CSR11, reg); + + rt2x00_set_field32(®, MAC_CSR11_AUTOWAKE, 1); + rt2x00usb_register_write(rt2x00dev, MAC_CSR11, reg); + + rt2x00usb_vendor_request_sw(rt2x00dev, USB_DEVICE_MODE, 0, + USB_MODE_SLEEP, REGISTER_TIMEOUT); + } else { + rt2x00usb_register_read(rt2x00dev, MAC_CSR11, ®); + rt2x00_set_field32(®, MAC_CSR11_DELAY_AFTER_TBCN, 0); + rt2x00_set_field32(®, MAC_CSR11_TBCN_BEFORE_WAKEUP, 0); + rt2x00_set_field32(®, MAC_CSR11_AUTOWAKE, 0); + rt2x00_set_field32(®, MAC_CSR11_WAKEUP_LATENCY, 0); + rt2x00usb_register_write(rt2x00dev, MAC_CSR11, reg); + + rt2x00usb_vendor_request_sw(rt2x00dev, USB_DEVICE_MODE, 0, + USB_MODE_WAKEUP, REGISTER_TIMEOUT); + } +} + +static void rt73usb_config(struct rt2x00_dev *rt2x00dev, + struct rt2x00lib_conf *libconf, + const unsigned int flags) +{ + /* Always recalculate LNA gain before changing configuration */ + rt73usb_config_lna_gain(rt2x00dev, libconf); + + if (flags & IEEE80211_CONF_CHANGE_CHANNEL) + rt73usb_config_channel(rt2x00dev, &libconf->rf, + libconf->conf->power_level); + if ((flags & IEEE80211_CONF_CHANGE_POWER) && + !(flags & IEEE80211_CONF_CHANGE_CHANNEL)) + rt73usb_config_txpower(rt2x00dev, libconf->conf->power_level); + if (flags & IEEE80211_CONF_CHANGE_RETRY_LIMITS) + rt73usb_config_retry_limit(rt2x00dev, libconf); + if (flags & IEEE80211_CONF_CHANGE_PS) + rt73usb_config_ps(rt2x00dev, libconf); +} + +/* + * Link tuning + */ +static void rt73usb_link_stats(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual) +{ + u32 reg; + + /* + * Update FCS error count from register. + */ + rt2x00usb_register_read(rt2x00dev, STA_CSR0, ®); + qual->rx_failed = rt2x00_get_field32(reg, STA_CSR0_FCS_ERROR); + + /* + * Update False CCA count from register. + */ + rt2x00usb_register_read(rt2x00dev, STA_CSR1, ®); + qual->false_cca = rt2x00_get_field32(reg, STA_CSR1_FALSE_CCA_ERROR); +} + +static inline void rt73usb_set_vgc(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual, u8 vgc_level) +{ + if (qual->vgc_level != vgc_level) { + rt73usb_bbp_write(rt2x00dev, 17, vgc_level); + qual->vgc_level = vgc_level; + qual->vgc_level_reg = vgc_level; + } +} + +static void rt73usb_reset_tuner(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual) +{ + rt73usb_set_vgc(rt2x00dev, qual, 0x20); +} + +static void rt73usb_link_tuner(struct rt2x00_dev *rt2x00dev, + struct link_qual *qual, const u32 count) +{ + u8 up_bound; + u8 low_bound; + + /* + * Determine r17 bounds. + */ + if (rt2x00dev->curr_band == IEEE80211_BAND_5GHZ) { + low_bound = 0x28; + up_bound = 0x48; + + if (test_bit(CAPABILITY_EXTERNAL_LNA_A, &rt2x00dev->cap_flags)) { + low_bound += 0x10; + up_bound += 0x10; + } + } else { + if (qual->rssi > -82) { + low_bound = 0x1c; + up_bound = 0x40; + } else if (qual->rssi > -84) { + low_bound = 0x1c; + up_bound = 0x20; + } else { + low_bound = 0x1c; + up_bound = 0x1c; + } + + if (test_bit(CAPABILITY_EXTERNAL_LNA_BG, &rt2x00dev->cap_flags)) { + low_bound += 0x14; + up_bound += 0x10; + } + } + + /* + * If we are not associated, we should go straight to the + * dynamic CCA tuning. + */ + if (!rt2x00dev->intf_associated) + goto dynamic_cca_tune; + + /* + * Special big-R17 for very short distance + */ + if (qual->rssi > -35) { + rt73usb_set_vgc(rt2x00dev, qual, 0x60); + return; + } + + /* + * Special big-R17 for short distance + */ + if (qual->rssi >= -58) { + rt73usb_set_vgc(rt2x00dev, qual, up_bound); + return; + } + + /* + * Special big-R17 for middle-short distance + */ + if (qual->rssi >= -66) { + rt73usb_set_vgc(rt2x00dev, qual, low_bound + 0x10); + return; + } + + /* + * Special mid-R17 for middle distance + */ + if (qual->rssi >= -74) { + rt73usb_set_vgc(rt2x00dev, qual, low_bound + 0x08); + return; + } + + /* + * Special case: Change up_bound based on the rssi. + * Lower up_bound when rssi is weaker then -74 dBm. + */ + up_bound -= 2 * (-74 - qual->rssi); + if (low_bound > up_bound) + up_bound = low_bound; + + if (qual->vgc_level > up_bound) { + rt73usb_set_vgc(rt2x00dev, qual, up_bound); + return; + } + +dynamic_cca_tune: + + /* + * r17 does not yet exceed upper limit, continue and base + * the r17 tuning on the false CCA count. + */ + if ((qual->false_cca > 512) && (qual->vgc_level < up_bound)) + rt73usb_set_vgc(rt2x00dev, qual, + min_t(u8, qual->vgc_level + 4, up_bound)); + else if ((qual->false_cca < 100) && (qual->vgc_level > low_bound)) + rt73usb_set_vgc(rt2x00dev, qual, + max_t(u8, qual->vgc_level - 4, low_bound)); +} + +/* + * Queue handlers. + */ +static void rt73usb_start_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u32 reg; + + switch (queue->qid) { + case QID_RX: + rt2x00usb_register_read(rt2x00dev, TXRX_CSR0, ®); + rt2x00_set_field32(®, TXRX_CSR0_DISABLE_RX, 0); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR0, reg); + break; + case QID_BEACON: + rt2x00usb_register_read(rt2x00dev, TXRX_CSR9, ®); + rt2x00_set_field32(®, TXRX_CSR9_TSF_TICKING, 1); + rt2x00_set_field32(®, TXRX_CSR9_TBTT_ENABLE, 1); + rt2x00_set_field32(®, TXRX_CSR9_BEACON_GEN, 1); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR9, reg); + break; + default: + break; + } +} + +static void rt73usb_stop_queue(struct data_queue *queue) +{ + struct rt2x00_dev *rt2x00dev = queue->rt2x00dev; + u32 reg; + + switch (queue->qid) { + case QID_RX: + rt2x00usb_register_read(rt2x00dev, TXRX_CSR0, ®); + rt2x00_set_field32(®, TXRX_CSR0_DISABLE_RX, 1); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR0, reg); + break; + case QID_BEACON: + rt2x00usb_register_read(rt2x00dev, TXRX_CSR9, ®); + rt2x00_set_field32(®, TXRX_CSR9_TSF_TICKING, 0); + rt2x00_set_field32(®, TXRX_CSR9_TBTT_ENABLE, 0); + rt2x00_set_field32(®, TXRX_CSR9_BEACON_GEN, 0); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR9, reg); + break; + default: + break; + } +} + +/* + * Firmware functions + */ +static char *rt73usb_get_firmware_name(struct rt2x00_dev *rt2x00dev) +{ + return FIRMWARE_RT2571; +} + +static int rt73usb_check_firmware(struct rt2x00_dev *rt2x00dev, + const u8 *data, const size_t len) +{ + u16 fw_crc; + u16 crc; + + /* + * Only support 2kb firmware files. + */ + if (len != 2048) + return FW_BAD_LENGTH; + + /* + * The last 2 bytes in the firmware array are the crc checksum itself, + * this means that we should never pass those 2 bytes to the crc + * algorithm. + */ + fw_crc = (data[len - 2] << 8 | data[len - 1]); + + /* + * Use the crc itu-t algorithm. + */ + crc = crc_itu_t(0, data, len - 2); + crc = crc_itu_t_byte(crc, 0); + crc = crc_itu_t_byte(crc, 0); + + return (fw_crc == crc) ? FW_OK : FW_BAD_CRC; +} + +static int rt73usb_load_firmware(struct rt2x00_dev *rt2x00dev, + const u8 *data, const size_t len) +{ + unsigned int i; + int status; + u32 reg; + + /* + * Wait for stable hardware. + */ + for (i = 0; i < 100; i++) { + rt2x00usb_register_read(rt2x00dev, MAC_CSR0, ®); + if (reg) + break; + msleep(1); + } + + if (!reg) { + rt2x00_err(rt2x00dev, "Unstable hardware\n"); + return -EBUSY; + } + + /* + * Write firmware to device. + */ + rt2x00usb_register_multiwrite(rt2x00dev, FIRMWARE_IMAGE_BASE, data, len); + + /* + * Send firmware request to device to load firmware, + * we need to specify a long timeout time. + */ + status = rt2x00usb_vendor_request_sw(rt2x00dev, USB_DEVICE_MODE, + 0, USB_MODE_FIRMWARE, + REGISTER_TIMEOUT_FIRMWARE); + if (status < 0) { + rt2x00_err(rt2x00dev, "Failed to write Firmware to device\n"); + return status; + } + + return 0; +} + +/* + * Initialization functions. + */ +static int rt73usb_init_registers(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + + rt2x00usb_register_read(rt2x00dev, TXRX_CSR0, ®); + rt2x00_set_field32(®, TXRX_CSR0_AUTO_TX_SEQ, 1); + rt2x00_set_field32(®, TXRX_CSR0_DISABLE_RX, 0); + rt2x00_set_field32(®, TXRX_CSR0_TX_WITHOUT_WAITING, 0); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR0, reg); + + rt2x00usb_register_read(rt2x00dev, TXRX_CSR1, ®); + rt2x00_set_field32(®, TXRX_CSR1_BBP_ID0, 47); /* CCK Signal */ + rt2x00_set_field32(®, TXRX_CSR1_BBP_ID0_VALID, 1); + rt2x00_set_field32(®, TXRX_CSR1_BBP_ID1, 30); /* Rssi */ + rt2x00_set_field32(®, TXRX_CSR1_BBP_ID1_VALID, 1); + rt2x00_set_field32(®, TXRX_CSR1_BBP_ID2, 42); /* OFDM Rate */ + rt2x00_set_field32(®, TXRX_CSR1_BBP_ID2_VALID, 1); + rt2x00_set_field32(®, TXRX_CSR1_BBP_ID3, 30); /* Rssi */ + rt2x00_set_field32(®, TXRX_CSR1_BBP_ID3_VALID, 1); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR1, reg); + + /* + * CCK TXD BBP registers + */ + rt2x00usb_register_read(rt2x00dev, TXRX_CSR2, ®); + rt2x00_set_field32(®, TXRX_CSR2_BBP_ID0, 13); + rt2x00_set_field32(®, TXRX_CSR2_BBP_ID0_VALID, 1); + rt2x00_set_field32(®, TXRX_CSR2_BBP_ID1, 12); + rt2x00_set_field32(®, TXRX_CSR2_BBP_ID1_VALID, 1); + rt2x00_set_field32(®, TXRX_CSR2_BBP_ID2, 11); + rt2x00_set_field32(®, TXRX_CSR2_BBP_ID2_VALID, 1); + rt2x00_set_field32(®, TXRX_CSR2_BBP_ID3, 10); + rt2x00_set_field32(®, TXRX_CSR2_BBP_ID3_VALID, 1); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR2, reg); + + /* + * OFDM TXD BBP registers + */ + rt2x00usb_register_read(rt2x00dev, TXRX_CSR3, ®); + rt2x00_set_field32(®, TXRX_CSR3_BBP_ID0, 7); + rt2x00_set_field32(®, TXRX_CSR3_BBP_ID0_VALID, 1); + rt2x00_set_field32(®, TXRX_CSR3_BBP_ID1, 6); + rt2x00_set_field32(®, TXRX_CSR3_BBP_ID1_VALID, 1); + rt2x00_set_field32(®, TXRX_CSR3_BBP_ID2, 5); + rt2x00_set_field32(®, TXRX_CSR3_BBP_ID2_VALID, 1); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR3, reg); + + rt2x00usb_register_read(rt2x00dev, TXRX_CSR7, ®); + rt2x00_set_field32(®, TXRX_CSR7_ACK_CTS_6MBS, 59); + rt2x00_set_field32(®, TXRX_CSR7_ACK_CTS_9MBS, 53); + rt2x00_set_field32(®, TXRX_CSR7_ACK_CTS_12MBS, 49); + rt2x00_set_field32(®, TXRX_CSR7_ACK_CTS_18MBS, 46); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR7, reg); + + rt2x00usb_register_read(rt2x00dev, TXRX_CSR8, ®); + rt2x00_set_field32(®, TXRX_CSR8_ACK_CTS_24MBS, 44); + rt2x00_set_field32(®, TXRX_CSR8_ACK_CTS_36MBS, 42); + rt2x00_set_field32(®, TXRX_CSR8_ACK_CTS_48MBS, 42); + rt2x00_set_field32(®, TXRX_CSR8_ACK_CTS_54MBS, 42); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR8, reg); + + rt2x00usb_register_read(rt2x00dev, TXRX_CSR9, ®); + rt2x00_set_field32(®, TXRX_CSR9_BEACON_INTERVAL, 0); + rt2x00_set_field32(®, TXRX_CSR9_TSF_TICKING, 0); + rt2x00_set_field32(®, TXRX_CSR9_TSF_SYNC, 0); + rt2x00_set_field32(®, TXRX_CSR9_TBTT_ENABLE, 0); + rt2x00_set_field32(®, TXRX_CSR9_BEACON_GEN, 0); + rt2x00_set_field32(®, TXRX_CSR9_TIMESTAMP_COMPENSATE, 0); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR9, reg); + + rt2x00usb_register_write(rt2x00dev, TXRX_CSR15, 0x0000000f); + + rt2x00usb_register_read(rt2x00dev, MAC_CSR6, ®); + rt2x00_set_field32(®, MAC_CSR6_MAX_FRAME_UNIT, 0xfff); + rt2x00usb_register_write(rt2x00dev, MAC_CSR6, reg); + + rt2x00usb_register_write(rt2x00dev, MAC_CSR10, 0x00000718); + + if (rt2x00dev->ops->lib->set_device_state(rt2x00dev, STATE_AWAKE)) + return -EBUSY; + + rt2x00usb_register_write(rt2x00dev, MAC_CSR13, 0x00007f00); + + /* + * Invalidate all Shared Keys (SEC_CSR0), + * and clear the Shared key Cipher algorithms (SEC_CSR1 & SEC_CSR5) + */ + rt2x00usb_register_write(rt2x00dev, SEC_CSR0, 0x00000000); + rt2x00usb_register_write(rt2x00dev, SEC_CSR1, 0x00000000); + rt2x00usb_register_write(rt2x00dev, SEC_CSR5, 0x00000000); + + reg = 0x000023b0; + if (rt2x00_rf(rt2x00dev, RF5225) || rt2x00_rf(rt2x00dev, RF2527)) + rt2x00_set_field32(®, PHY_CSR1_RF_RPI, 1); + rt2x00usb_register_write(rt2x00dev, PHY_CSR1, reg); + + rt2x00usb_register_write(rt2x00dev, PHY_CSR5, 0x00040a06); + rt2x00usb_register_write(rt2x00dev, PHY_CSR6, 0x00080606); + rt2x00usb_register_write(rt2x00dev, PHY_CSR7, 0x00000408); + + rt2x00usb_register_read(rt2x00dev, MAC_CSR9, ®); + rt2x00_set_field32(®, MAC_CSR9_CW_SELECT, 0); + rt2x00usb_register_write(rt2x00dev, MAC_CSR9, reg); + + /* + * Clear all beacons + * For the Beacon base registers we only need to clear + * the first byte since that byte contains the VALID and OWNER + * bits which (when set to 0) will invalidate the entire beacon. + */ + rt2x00usb_register_write(rt2x00dev, HW_BEACON_BASE0, 0); + rt2x00usb_register_write(rt2x00dev, HW_BEACON_BASE1, 0); + rt2x00usb_register_write(rt2x00dev, HW_BEACON_BASE2, 0); + rt2x00usb_register_write(rt2x00dev, HW_BEACON_BASE3, 0); + + /* + * We must clear the error counters. + * These registers are cleared on read, + * so we may pass a useless variable to store the value. + */ + rt2x00usb_register_read(rt2x00dev, STA_CSR0, ®); + rt2x00usb_register_read(rt2x00dev, STA_CSR1, ®); + rt2x00usb_register_read(rt2x00dev, STA_CSR2, ®); + + /* + * Reset MAC and BBP registers. + */ + rt2x00usb_register_read(rt2x00dev, MAC_CSR1, ®); + rt2x00_set_field32(®, MAC_CSR1_SOFT_RESET, 1); + rt2x00_set_field32(®, MAC_CSR1_BBP_RESET, 1); + rt2x00usb_register_write(rt2x00dev, MAC_CSR1, reg); + + rt2x00usb_register_read(rt2x00dev, MAC_CSR1, ®); + rt2x00_set_field32(®, MAC_CSR1_SOFT_RESET, 0); + rt2x00_set_field32(®, MAC_CSR1_BBP_RESET, 0); + rt2x00usb_register_write(rt2x00dev, MAC_CSR1, reg); + + rt2x00usb_register_read(rt2x00dev, MAC_CSR1, ®); + rt2x00_set_field32(®, MAC_CSR1_HOST_READY, 1); + rt2x00usb_register_write(rt2x00dev, MAC_CSR1, reg); + + return 0; +} + +static int rt73usb_wait_bbp_ready(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i; + u8 value; + + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt73usb_bbp_read(rt2x00dev, 0, &value); + if ((value != 0xff) && (value != 0x00)) + return 0; + udelay(REGISTER_BUSY_DELAY); + } + + rt2x00_err(rt2x00dev, "BBP register access failed, aborting\n"); + return -EACCES; +} + +static int rt73usb_init_bbp(struct rt2x00_dev *rt2x00dev) +{ + unsigned int i; + u16 eeprom; + u8 reg_id; + u8 value; + + if (unlikely(rt73usb_wait_bbp_ready(rt2x00dev))) + return -EACCES; + + rt73usb_bbp_write(rt2x00dev, 3, 0x80); + rt73usb_bbp_write(rt2x00dev, 15, 0x30); + rt73usb_bbp_write(rt2x00dev, 21, 0xc8); + rt73usb_bbp_write(rt2x00dev, 22, 0x38); + rt73usb_bbp_write(rt2x00dev, 23, 0x06); + rt73usb_bbp_write(rt2x00dev, 24, 0xfe); + rt73usb_bbp_write(rt2x00dev, 25, 0x0a); + rt73usb_bbp_write(rt2x00dev, 26, 0x0d); + rt73usb_bbp_write(rt2x00dev, 32, 0x0b); + rt73usb_bbp_write(rt2x00dev, 34, 0x12); + rt73usb_bbp_write(rt2x00dev, 37, 0x07); + rt73usb_bbp_write(rt2x00dev, 39, 0xf8); + rt73usb_bbp_write(rt2x00dev, 41, 0x60); + rt73usb_bbp_write(rt2x00dev, 53, 0x10); + rt73usb_bbp_write(rt2x00dev, 54, 0x18); + rt73usb_bbp_write(rt2x00dev, 60, 0x10); + rt73usb_bbp_write(rt2x00dev, 61, 0x04); + rt73usb_bbp_write(rt2x00dev, 62, 0x04); + rt73usb_bbp_write(rt2x00dev, 75, 0xfe); + rt73usb_bbp_write(rt2x00dev, 86, 0xfe); + rt73usb_bbp_write(rt2x00dev, 88, 0xfe); + rt73usb_bbp_write(rt2x00dev, 90, 0x0f); + rt73usb_bbp_write(rt2x00dev, 99, 0x00); + rt73usb_bbp_write(rt2x00dev, 102, 0x16); + rt73usb_bbp_write(rt2x00dev, 107, 0x04); + + for (i = 0; i < EEPROM_BBP_SIZE; i++) { + rt2x00_eeprom_read(rt2x00dev, EEPROM_BBP_START + i, &eeprom); + + if (eeprom != 0xffff && eeprom != 0x0000) { + reg_id = rt2x00_get_field16(eeprom, EEPROM_BBP_REG_ID); + value = rt2x00_get_field16(eeprom, EEPROM_BBP_VALUE); + rt73usb_bbp_write(rt2x00dev, reg_id, value); + } + } + + return 0; +} + +/* + * Device state switch handlers. + */ +static int rt73usb_enable_radio(struct rt2x00_dev *rt2x00dev) +{ + /* + * Initialize all registers. + */ + if (unlikely(rt73usb_init_registers(rt2x00dev) || + rt73usb_init_bbp(rt2x00dev))) + return -EIO; + + return 0; +} + +static void rt73usb_disable_radio(struct rt2x00_dev *rt2x00dev) +{ + rt2x00usb_register_write(rt2x00dev, MAC_CSR10, 0x00001818); + + /* + * Disable synchronisation. + */ + rt2x00usb_register_write(rt2x00dev, TXRX_CSR9, 0); + + rt2x00usb_disable_radio(rt2x00dev); +} + +static int rt73usb_set_state(struct rt2x00_dev *rt2x00dev, enum dev_state state) +{ + u32 reg, reg2; + unsigned int i; + char put_to_sleep; + + put_to_sleep = (state != STATE_AWAKE); + + rt2x00usb_register_read(rt2x00dev, MAC_CSR12, ®); + rt2x00_set_field32(®, MAC_CSR12_FORCE_WAKEUP, !put_to_sleep); + rt2x00_set_field32(®, MAC_CSR12_PUT_TO_SLEEP, put_to_sleep); + rt2x00usb_register_write(rt2x00dev, MAC_CSR12, reg); + + /* + * Device is not guaranteed to be in the requested state yet. + * We must wait until the register indicates that the + * device has entered the correct state. + */ + for (i = 0; i < REGISTER_BUSY_COUNT; i++) { + rt2x00usb_register_read(rt2x00dev, MAC_CSR12, ®2); + state = rt2x00_get_field32(reg2, MAC_CSR12_BBP_CURRENT_STATE); + if (state == !put_to_sleep) + return 0; + rt2x00usb_register_write(rt2x00dev, MAC_CSR12, reg); + msleep(10); + } + + return -EBUSY; +} + +static int rt73usb_set_device_state(struct rt2x00_dev *rt2x00dev, + enum dev_state state) +{ + int retval = 0; + + switch (state) { + case STATE_RADIO_ON: + retval = rt73usb_enable_radio(rt2x00dev); + break; + case STATE_RADIO_OFF: + rt73usb_disable_radio(rt2x00dev); + break; + case STATE_RADIO_IRQ_ON: + case STATE_RADIO_IRQ_OFF: + /* No support, but no error either */ + break; + case STATE_DEEP_SLEEP: + case STATE_SLEEP: + case STATE_STANDBY: + case STATE_AWAKE: + retval = rt73usb_set_state(rt2x00dev, state); + break; + default: + retval = -ENOTSUPP; + break; + } + + if (unlikely(retval)) + rt2x00_err(rt2x00dev, "Device failed to enter state %d (%d)\n", + state, retval); + + return retval; +} + +/* + * TX descriptor initialization + */ +static void rt73usb_write_tx_desc(struct queue_entry *entry, + struct txentry_desc *txdesc) +{ + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + __le32 *txd = (__le32 *) entry->skb->data; + u32 word; + + /* + * Start writing the descriptor words. + */ + rt2x00_desc_read(txd, 0, &word); + rt2x00_set_field32(&word, TXD_W0_BURST, + test_bit(ENTRY_TXD_BURST, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_VALID, 1); + rt2x00_set_field32(&word, TXD_W0_MORE_FRAG, + test_bit(ENTRY_TXD_MORE_FRAG, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_ACK, + test_bit(ENTRY_TXD_ACK, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_TIMESTAMP, + test_bit(ENTRY_TXD_REQ_TIMESTAMP, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_OFDM, + (txdesc->rate_mode == RATE_MODE_OFDM)); + rt2x00_set_field32(&word, TXD_W0_IFS, txdesc->u.plcp.ifs); + rt2x00_set_field32(&word, TXD_W0_RETRY_MODE, + test_bit(ENTRY_TXD_RETRY_MODE, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_TKIP_MIC, + test_bit(ENTRY_TXD_ENCRYPT_MMIC, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_KEY_TABLE, + test_bit(ENTRY_TXD_ENCRYPT_PAIRWISE, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_KEY_INDEX, txdesc->key_idx); + rt2x00_set_field32(&word, TXD_W0_DATABYTE_COUNT, txdesc->length); + rt2x00_set_field32(&word, TXD_W0_BURST2, + test_bit(ENTRY_TXD_BURST, &txdesc->flags)); + rt2x00_set_field32(&word, TXD_W0_CIPHER_ALG, txdesc->cipher); + rt2x00_desc_write(txd, 0, word); + + rt2x00_desc_read(txd, 1, &word); + rt2x00_set_field32(&word, TXD_W1_HOST_Q_ID, entry->queue->qid); + rt2x00_set_field32(&word, TXD_W1_AIFSN, entry->queue->aifs); + rt2x00_set_field32(&word, TXD_W1_CWMIN, entry->queue->cw_min); + rt2x00_set_field32(&word, TXD_W1_CWMAX, entry->queue->cw_max); + rt2x00_set_field32(&word, TXD_W1_IV_OFFSET, txdesc->iv_offset); + rt2x00_set_field32(&word, TXD_W1_HW_SEQUENCE, + test_bit(ENTRY_TXD_GENERATE_SEQ, &txdesc->flags)); + rt2x00_desc_write(txd, 1, word); + + rt2x00_desc_read(txd, 2, &word); + rt2x00_set_field32(&word, TXD_W2_PLCP_SIGNAL, txdesc->u.plcp.signal); + rt2x00_set_field32(&word, TXD_W2_PLCP_SERVICE, txdesc->u.plcp.service); + rt2x00_set_field32(&word, TXD_W2_PLCP_LENGTH_LOW, + txdesc->u.plcp.length_low); + rt2x00_set_field32(&word, TXD_W2_PLCP_LENGTH_HIGH, + txdesc->u.plcp.length_high); + rt2x00_desc_write(txd, 2, word); + + if (test_bit(ENTRY_TXD_ENCRYPT, &txdesc->flags)) { + _rt2x00_desc_write(txd, 3, skbdesc->iv[0]); + _rt2x00_desc_write(txd, 4, skbdesc->iv[1]); + } + + rt2x00_desc_read(txd, 5, &word); + rt2x00_set_field32(&word, TXD_W5_TX_POWER, + TXPOWER_TO_DEV(entry->queue->rt2x00dev->tx_power)); + rt2x00_set_field32(&word, TXD_W5_WAITING_DMA_DONE_INT, 1); + rt2x00_desc_write(txd, 5, word); + + /* + * Register descriptor details in skb frame descriptor. + */ + skbdesc->flags |= SKBDESC_DESC_IN_SKB; + skbdesc->desc = txd; + skbdesc->desc_len = TXD_DESC_SIZE; +} + +/* + * TX data initialization + */ +static void rt73usb_write_beacon(struct queue_entry *entry, + struct txentry_desc *txdesc) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + unsigned int beacon_base; + unsigned int padding_len; + u32 orig_reg, reg; + + /* + * Disable beaconing while we are reloading the beacon data, + * otherwise we might be sending out invalid data. + */ + rt2x00usb_register_read(rt2x00dev, TXRX_CSR9, ®); + orig_reg = reg; + rt2x00_set_field32(®, TXRX_CSR9_BEACON_GEN, 0); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR9, reg); + + /* + * Add space for the descriptor in front of the skb. + */ + skb_push(entry->skb, TXD_DESC_SIZE); + memset(entry->skb->data, 0, TXD_DESC_SIZE); + + /* + * Write the TX descriptor for the beacon. + */ + rt73usb_write_tx_desc(entry, txdesc); + + /* + * Dump beacon to userspace through debugfs. + */ + rt2x00debug_dump_frame(rt2x00dev, DUMP_FRAME_BEACON, entry->skb); + + /* + * Write entire beacon with descriptor and padding to register. + */ + padding_len = roundup(entry->skb->len, 4) - entry->skb->len; + if (padding_len && skb_pad(entry->skb, padding_len)) { + rt2x00_err(rt2x00dev, "Failure padding beacon, aborting\n"); + /* skb freed by skb_pad() on failure */ + entry->skb = NULL; + rt2x00usb_register_write(rt2x00dev, TXRX_CSR9, orig_reg); + return; + } + + beacon_base = HW_BEACON_OFFSET(entry->entry_idx); + rt2x00usb_register_multiwrite(rt2x00dev, beacon_base, entry->skb->data, + entry->skb->len + padding_len); + + /* + * Enable beaconing again. + * + * For Wi-Fi faily generated beacons between participating stations. + * Set TBTT phase adaptive adjustment step to 8us (default 16us) + */ + rt2x00usb_register_write(rt2x00dev, TXRX_CSR10, 0x00001008); + + rt2x00_set_field32(®, TXRX_CSR9_BEACON_GEN, 1); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR9, reg); + + /* + * Clean up the beacon skb. + */ + dev_kfree_skb(entry->skb); + entry->skb = NULL; +} + +static void rt73usb_clear_beacon(struct queue_entry *entry) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + unsigned int beacon_base; + u32 reg; + + /* + * Disable beaconing while we are reloading the beacon data, + * otherwise we might be sending out invalid data. + */ + rt2x00usb_register_read(rt2x00dev, TXRX_CSR9, ®); + rt2x00_set_field32(®, TXRX_CSR9_BEACON_GEN, 0); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR9, reg); + + /* + * Clear beacon. + */ + beacon_base = HW_BEACON_OFFSET(entry->entry_idx); + rt2x00usb_register_write(rt2x00dev, beacon_base, 0); + + /* + * Enable beaconing again. + */ + rt2x00_set_field32(®, TXRX_CSR9_BEACON_GEN, 1); + rt2x00usb_register_write(rt2x00dev, TXRX_CSR9, reg); +} + +static int rt73usb_get_tx_data_len(struct queue_entry *entry) +{ + int length; + + /* + * The length _must_ be a multiple of 4, + * but it must _not_ be a multiple of the USB packet size. + */ + length = roundup(entry->skb->len, 4); + length += (4 * !(length % entry->queue->usb_maxpacket)); + + return length; +} + +/* + * RX control handlers + */ +static int rt73usb_agc_to_rssi(struct rt2x00_dev *rt2x00dev, int rxd_w1) +{ + u8 offset = rt2x00dev->lna_gain; + u8 lna; + + lna = rt2x00_get_field32(rxd_w1, RXD_W1_RSSI_LNA); + switch (lna) { + case 3: + offset += 90; + break; + case 2: + offset += 74; + break; + case 1: + offset += 64; + break; + default: + return 0; + } + + if (rt2x00dev->curr_band == IEEE80211_BAND_5GHZ) { + if (test_bit(CAPABILITY_EXTERNAL_LNA_A, &rt2x00dev->cap_flags)) { + if (lna == 3 || lna == 2) + offset += 10; + } else { + if (lna == 3) + offset += 6; + else if (lna == 2) + offset += 8; + } + } + + return rt2x00_get_field32(rxd_w1, RXD_W1_RSSI_AGC) * 2 - offset; +} + +static void rt73usb_fill_rxdone(struct queue_entry *entry, + struct rxdone_entry_desc *rxdesc) +{ + struct rt2x00_dev *rt2x00dev = entry->queue->rt2x00dev; + struct skb_frame_desc *skbdesc = get_skb_frame_desc(entry->skb); + __le32 *rxd = (__le32 *)entry->skb->data; + u32 word0; + u32 word1; + + /* + * Copy descriptor to the skbdesc->desc buffer, making it safe from moving of + * frame data in rt2x00usb. + */ + memcpy(skbdesc->desc, rxd, skbdesc->desc_len); + rxd = (__le32 *)skbdesc->desc; + + /* + * It is now safe to read the descriptor on all architectures. + */ + rt2x00_desc_read(rxd, 0, &word0); + rt2x00_desc_read(rxd, 1, &word1); + + if (rt2x00_get_field32(word0, RXD_W0_CRC_ERROR)) + rxdesc->flags |= RX_FLAG_FAILED_FCS_CRC; + + rxdesc->cipher = rt2x00_get_field32(word0, RXD_W0_CIPHER_ALG); + rxdesc->cipher_status = rt2x00_get_field32(word0, RXD_W0_CIPHER_ERROR); + + if (rxdesc->cipher != CIPHER_NONE) { + _rt2x00_desc_read(rxd, 2, &rxdesc->iv[0]); + _rt2x00_desc_read(rxd, 3, &rxdesc->iv[1]); + rxdesc->dev_flags |= RXDONE_CRYPTO_IV; + + _rt2x00_desc_read(rxd, 4, &rxdesc->icv); + rxdesc->dev_flags |= RXDONE_CRYPTO_ICV; + + /* + * Hardware has stripped IV/EIV data from 802.11 frame during + * decryption. It has provided the data separately but rt2x00lib + * should decide if it should be reinserted. + */ + rxdesc->flags |= RX_FLAG_IV_STRIPPED; + + /* + * The hardware has already checked the Michael Mic and has + * stripped it from the frame. Signal this to mac80211. + */ + rxdesc->flags |= RX_FLAG_MMIC_STRIPPED; + + if (rxdesc->cipher_status == RX_CRYPTO_SUCCESS) + rxdesc->flags |= RX_FLAG_DECRYPTED; + else if (rxdesc->cipher_status == RX_CRYPTO_FAIL_MIC) + rxdesc->flags |= RX_FLAG_MMIC_ERROR; + } + + /* + * Obtain the status about this packet. + * When frame was received with an OFDM bitrate, + * the signal is the PLCP value. If it was received with + * a CCK bitrate the signal is the rate in 100kbit/s. + */ + rxdesc->signal = rt2x00_get_field32(word1, RXD_W1_SIGNAL); + rxdesc->rssi = rt73usb_agc_to_rssi(rt2x00dev, word1); + rxdesc->size = rt2x00_get_field32(word0, RXD_W0_DATABYTE_COUNT); + + if (rt2x00_get_field32(word0, RXD_W0_OFDM)) + rxdesc->dev_flags |= RXDONE_SIGNAL_PLCP; + else + rxdesc->dev_flags |= RXDONE_SIGNAL_BITRATE; + if (rt2x00_get_field32(word0, RXD_W0_MY_BSS)) + rxdesc->dev_flags |= RXDONE_MY_BSS; + + /* + * Set skb pointers, and update frame information. + */ + skb_pull(entry->skb, entry->queue->desc_size); + skb_trim(entry->skb, rxdesc->size); +} + +/* + * Device probe functions. + */ +static int rt73usb_validate_eeprom(struct rt2x00_dev *rt2x00dev) +{ + u16 word; + u8 *mac; + s8 value; + + rt2x00usb_eeprom_read(rt2x00dev, rt2x00dev->eeprom, EEPROM_SIZE); + + /* + * Start validation of the data that has been read. + */ + mac = rt2x00_eeprom_addr(rt2x00dev, EEPROM_MAC_ADDR_0); + if (!is_valid_ether_addr(mac)) { + eth_random_addr(mac); + rt2x00_eeprom_dbg(rt2x00dev, "MAC: %pM\n", mac); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_ANTENNA, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_ANTENNA_NUM, 2); + rt2x00_set_field16(&word, EEPROM_ANTENNA_TX_DEFAULT, + ANTENNA_B); + rt2x00_set_field16(&word, EEPROM_ANTENNA_RX_DEFAULT, + ANTENNA_B); + rt2x00_set_field16(&word, EEPROM_ANTENNA_FRAME_TYPE, 0); + rt2x00_set_field16(&word, EEPROM_ANTENNA_DYN_TXAGC, 0); + rt2x00_set_field16(&word, EEPROM_ANTENNA_HARDWARE_RADIO, 0); + rt2x00_set_field16(&word, EEPROM_ANTENNA_RF_TYPE, RF5226); + rt2x00_eeprom_write(rt2x00dev, EEPROM_ANTENNA, word); + rt2x00_eeprom_dbg(rt2x00dev, "Antenna: 0x%04x\n", word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_NIC_EXTERNAL_LNA, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_NIC, word); + rt2x00_eeprom_dbg(rt2x00dev, "NIC: 0x%04x\n", word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_LED, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_LED_POLARITY_RDY_G, 0); + rt2x00_set_field16(&word, EEPROM_LED_POLARITY_RDY_A, 0); + rt2x00_set_field16(&word, EEPROM_LED_POLARITY_ACT, 0); + rt2x00_set_field16(&word, EEPROM_LED_POLARITY_GPIO_0, 0); + rt2x00_set_field16(&word, EEPROM_LED_POLARITY_GPIO_1, 0); + rt2x00_set_field16(&word, EEPROM_LED_POLARITY_GPIO_2, 0); + rt2x00_set_field16(&word, EEPROM_LED_POLARITY_GPIO_3, 0); + rt2x00_set_field16(&word, EEPROM_LED_POLARITY_GPIO_4, 0); + rt2x00_set_field16(&word, EEPROM_LED_LED_MODE, + LED_MODE_DEFAULT); + rt2x00_eeprom_write(rt2x00dev, EEPROM_LED, word); + rt2x00_eeprom_dbg(rt2x00dev, "Led: 0x%04x\n", word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_FREQ, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_FREQ_OFFSET, 0); + rt2x00_set_field16(&word, EEPROM_FREQ_SEQ, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_FREQ, word); + rt2x00_eeprom_dbg(rt2x00dev, "Freq: 0x%04x\n", word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_OFFSET_BG, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_RSSI_OFFSET_BG_1, 0); + rt2x00_set_field16(&word, EEPROM_RSSI_OFFSET_BG_2, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_RSSI_OFFSET_BG, word); + rt2x00_eeprom_dbg(rt2x00dev, "RSSI OFFSET BG: 0x%04x\n", word); + } else { + value = rt2x00_get_field16(word, EEPROM_RSSI_OFFSET_BG_1); + if (value < -10 || value > 10) + rt2x00_set_field16(&word, EEPROM_RSSI_OFFSET_BG_1, 0); + value = rt2x00_get_field16(word, EEPROM_RSSI_OFFSET_BG_2); + if (value < -10 || value > 10) + rt2x00_set_field16(&word, EEPROM_RSSI_OFFSET_BG_2, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_RSSI_OFFSET_BG, word); + } + + rt2x00_eeprom_read(rt2x00dev, EEPROM_RSSI_OFFSET_A, &word); + if (word == 0xffff) { + rt2x00_set_field16(&word, EEPROM_RSSI_OFFSET_A_1, 0); + rt2x00_set_field16(&word, EEPROM_RSSI_OFFSET_A_2, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_RSSI_OFFSET_A, word); + rt2x00_eeprom_dbg(rt2x00dev, "RSSI OFFSET A: 0x%04x\n", word); + } else { + value = rt2x00_get_field16(word, EEPROM_RSSI_OFFSET_A_1); + if (value < -10 || value > 10) + rt2x00_set_field16(&word, EEPROM_RSSI_OFFSET_A_1, 0); + value = rt2x00_get_field16(word, EEPROM_RSSI_OFFSET_A_2); + if (value < -10 || value > 10) + rt2x00_set_field16(&word, EEPROM_RSSI_OFFSET_A_2, 0); + rt2x00_eeprom_write(rt2x00dev, EEPROM_RSSI_OFFSET_A, word); + } + + return 0; +} + +static int rt73usb_init_eeprom(struct rt2x00_dev *rt2x00dev) +{ + u32 reg; + u16 value; + u16 eeprom; + + /* + * Read EEPROM word for configuration. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_ANTENNA, &eeprom); + + /* + * Identify RF chipset. + */ + value = rt2x00_get_field16(eeprom, EEPROM_ANTENNA_RF_TYPE); + rt2x00usb_register_read(rt2x00dev, MAC_CSR0, ®); + rt2x00_set_chip(rt2x00dev, rt2x00_get_field32(reg, MAC_CSR0_CHIPSET), + value, rt2x00_get_field32(reg, MAC_CSR0_REVISION)); + + if (!rt2x00_rt(rt2x00dev, RT2573) || (rt2x00_rev(rt2x00dev) == 0)) { + rt2x00_err(rt2x00dev, "Invalid RT chipset detected\n"); + return -ENODEV; + } + + if (!rt2x00_rf(rt2x00dev, RF5226) && + !rt2x00_rf(rt2x00dev, RF2528) && + !rt2x00_rf(rt2x00dev, RF5225) && + !rt2x00_rf(rt2x00dev, RF2527)) { + rt2x00_err(rt2x00dev, "Invalid RF chipset detected\n"); + return -ENODEV; + } + + /* + * Identify default antenna configuration. + */ + rt2x00dev->default_ant.tx = + rt2x00_get_field16(eeprom, EEPROM_ANTENNA_TX_DEFAULT); + rt2x00dev->default_ant.rx = + rt2x00_get_field16(eeprom, EEPROM_ANTENNA_RX_DEFAULT); + + /* + * Read the Frame type. + */ + if (rt2x00_get_field16(eeprom, EEPROM_ANTENNA_FRAME_TYPE)) + __set_bit(CAPABILITY_FRAME_TYPE, &rt2x00dev->cap_flags); + + /* + * Detect if this device has an hardware controlled radio. + */ + if (rt2x00_get_field16(eeprom, EEPROM_ANTENNA_HARDWARE_RADIO)) + __set_bit(CAPABILITY_HW_BUTTON, &rt2x00dev->cap_flags); + + /* + * Read frequency offset. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_FREQ, &eeprom); + rt2x00dev->freq_offset = rt2x00_get_field16(eeprom, EEPROM_FREQ_OFFSET); + + /* + * Read external LNA informations. + */ + rt2x00_eeprom_read(rt2x00dev, EEPROM_NIC, &eeprom); + + if (rt2x00_get_field16(eeprom, EEPROM_NIC_EXTERNAL_LNA)) { + __set_bit(CAPABILITY_EXTERNAL_LNA_A, &rt2x00dev->cap_flags); + __set_bit(CAPABILITY_EXTERNAL_LNA_BG, &rt2x00dev->cap_flags); + } + + /* + * Store led settings, for correct led behaviour. + */ +#ifdef CPTCFG_RT2X00_LIB_LEDS + rt2x00_eeprom_read(rt2x00dev, EEPROM_LED, &eeprom); + + rt73usb_init_led(rt2x00dev, &rt2x00dev->led_radio, LED_TYPE_RADIO); + rt73usb_init_led(rt2x00dev, &rt2x00dev->led_assoc, LED_TYPE_ASSOC); + if (value == LED_MODE_SIGNAL_STRENGTH) + rt73usb_init_led(rt2x00dev, &rt2x00dev->led_qual, + LED_TYPE_QUALITY); + + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_LED_MODE, value); + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_POLARITY_GPIO_0, + rt2x00_get_field16(eeprom, + EEPROM_LED_POLARITY_GPIO_0)); + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_POLARITY_GPIO_1, + rt2x00_get_field16(eeprom, + EEPROM_LED_POLARITY_GPIO_1)); + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_POLARITY_GPIO_2, + rt2x00_get_field16(eeprom, + EEPROM_LED_POLARITY_GPIO_2)); + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_POLARITY_GPIO_3, + rt2x00_get_field16(eeprom, + EEPROM_LED_POLARITY_GPIO_3)); + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_POLARITY_GPIO_4, + rt2x00_get_field16(eeprom, + EEPROM_LED_POLARITY_GPIO_4)); + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_POLARITY_ACT, + rt2x00_get_field16(eeprom, EEPROM_LED_POLARITY_ACT)); + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_POLARITY_READY_BG, + rt2x00_get_field16(eeprom, + EEPROM_LED_POLARITY_RDY_G)); + rt2x00_set_field16(&rt2x00dev->led_mcu_reg, MCU_LEDCS_POLARITY_READY_A, + rt2x00_get_field16(eeprom, + EEPROM_LED_POLARITY_RDY_A)); +#endif /* CPTCFG_RT2X00_LIB_LEDS */ + + return 0; +} + +/* + * RF value list for RF2528 + * Supports: 2.4 GHz + */ +static const struct rf_channel rf_vals_bg_2528[] = { + { 1, 0x00002c0c, 0x00000786, 0x00068255, 0x000fea0b }, + { 2, 0x00002c0c, 0x00000786, 0x00068255, 0x000fea1f }, + { 3, 0x00002c0c, 0x0000078a, 0x00068255, 0x000fea0b }, + { 4, 0x00002c0c, 0x0000078a, 0x00068255, 0x000fea1f }, + { 5, 0x00002c0c, 0x0000078e, 0x00068255, 0x000fea0b }, + { 6, 0x00002c0c, 0x0000078e, 0x00068255, 0x000fea1f }, + { 7, 0x00002c0c, 0x00000792, 0x00068255, 0x000fea0b }, + { 8, 0x00002c0c, 0x00000792, 0x00068255, 0x000fea1f }, + { 9, 0x00002c0c, 0x00000796, 0x00068255, 0x000fea0b }, + { 10, 0x00002c0c, 0x00000796, 0x00068255, 0x000fea1f }, + { 11, 0x00002c0c, 0x0000079a, 0x00068255, 0x000fea0b }, + { 12, 0x00002c0c, 0x0000079a, 0x00068255, 0x000fea1f }, + { 13, 0x00002c0c, 0x0000079e, 0x00068255, 0x000fea0b }, + { 14, 0x00002c0c, 0x000007a2, 0x00068255, 0x000fea13 }, +}; + +/* + * RF value list for RF5226 + * Supports: 2.4 GHz & 5.2 GHz + */ +static const struct rf_channel rf_vals_5226[] = { + { 1, 0x00002c0c, 0x00000786, 0x00068255, 0x000fea0b }, + { 2, 0x00002c0c, 0x00000786, 0x00068255, 0x000fea1f }, + { 3, 0x00002c0c, 0x0000078a, 0x00068255, 0x000fea0b }, + { 4, 0x00002c0c, 0x0000078a, 0x00068255, 0x000fea1f }, + { 5, 0x00002c0c, 0x0000078e, 0x00068255, 0x000fea0b }, + { 6, 0x00002c0c, 0x0000078e, 0x00068255, 0x000fea1f }, + { 7, 0x00002c0c, 0x00000792, 0x00068255, 0x000fea0b }, + { 8, 0x00002c0c, 0x00000792, 0x00068255, 0x000fea1f }, + { 9, 0x00002c0c, 0x00000796, 0x00068255, 0x000fea0b }, + { 10, 0x00002c0c, 0x00000796, 0x00068255, 0x000fea1f }, + { 11, 0x00002c0c, 0x0000079a, 0x00068255, 0x000fea0b }, + { 12, 0x00002c0c, 0x0000079a, 0x00068255, 0x000fea1f }, + { 13, 0x00002c0c, 0x0000079e, 0x00068255, 0x000fea0b }, + { 14, 0x00002c0c, 0x000007a2, 0x00068255, 0x000fea13 }, + + /* 802.11 UNI / HyperLan 2 */ + { 36, 0x00002c0c, 0x0000099a, 0x00098255, 0x000fea23 }, + { 40, 0x00002c0c, 0x000009a2, 0x00098255, 0x000fea03 }, + { 44, 0x00002c0c, 0x000009a6, 0x00098255, 0x000fea0b }, + { 48, 0x00002c0c, 0x000009aa, 0x00098255, 0x000fea13 }, + { 52, 0x00002c0c, 0x000009ae, 0x00098255, 0x000fea1b }, + { 56, 0x00002c0c, 0x000009b2, 0x00098255, 0x000fea23 }, + { 60, 0x00002c0c, 0x000009ba, 0x00098255, 0x000fea03 }, + { 64, 0x00002c0c, 0x000009be, 0x00098255, 0x000fea0b }, + + /* 802.11 HyperLan 2 */ + { 100, 0x00002c0c, 0x00000a2a, 0x000b8255, 0x000fea03 }, + { 104, 0x00002c0c, 0x00000a2e, 0x000b8255, 0x000fea0b }, + { 108, 0x00002c0c, 0x00000a32, 0x000b8255, 0x000fea13 }, + { 112, 0x00002c0c, 0x00000a36, 0x000b8255, 0x000fea1b }, + { 116, 0x00002c0c, 0x00000a3a, 0x000b8255, 0x000fea23 }, + { 120, 0x00002c0c, 0x00000a82, 0x000b8255, 0x000fea03 }, + { 124, 0x00002c0c, 0x00000a86, 0x000b8255, 0x000fea0b }, + { 128, 0x00002c0c, 0x00000a8a, 0x000b8255, 0x000fea13 }, + { 132, 0x00002c0c, 0x00000a8e, 0x000b8255, 0x000fea1b }, + { 136, 0x00002c0c, 0x00000a92, 0x000b8255, 0x000fea23 }, + + /* 802.11 UNII */ + { 140, 0x00002c0c, 0x00000a9a, 0x000b8255, 0x000fea03 }, + { 149, 0x00002c0c, 0x00000aa2, 0x000b8255, 0x000fea1f }, + { 153, 0x00002c0c, 0x00000aa6, 0x000b8255, 0x000fea27 }, + { 157, 0x00002c0c, 0x00000aae, 0x000b8255, 0x000fea07 }, + { 161, 0x00002c0c, 0x00000ab2, 0x000b8255, 0x000fea0f }, + { 165, 0x00002c0c, 0x00000ab6, 0x000b8255, 0x000fea17 }, + + /* MMAC(Japan)J52 ch 34,38,42,46 */ + { 34, 0x00002c0c, 0x0008099a, 0x000da255, 0x000d3a0b }, + { 38, 0x00002c0c, 0x0008099e, 0x000da255, 0x000d3a13 }, + { 42, 0x00002c0c, 0x000809a2, 0x000da255, 0x000d3a1b }, + { 46, 0x00002c0c, 0x000809a6, 0x000da255, 0x000d3a23 }, +}; + +/* + * RF value list for RF5225 & RF2527 + * Supports: 2.4 GHz & 5.2 GHz + */ +static const struct rf_channel rf_vals_5225_2527[] = { + { 1, 0x00002ccc, 0x00004786, 0x00068455, 0x000ffa0b }, + { 2, 0x00002ccc, 0x00004786, 0x00068455, 0x000ffa1f }, + { 3, 0x00002ccc, 0x0000478a, 0x00068455, 0x000ffa0b }, + { 4, 0x00002ccc, 0x0000478a, 0x00068455, 0x000ffa1f }, + { 5, 0x00002ccc, 0x0000478e, 0x00068455, 0x000ffa0b }, + { 6, 0x00002ccc, 0x0000478e, 0x00068455, 0x000ffa1f }, + { 7, 0x00002ccc, 0x00004792, 0x00068455, 0x000ffa0b }, + { 8, 0x00002ccc, 0x00004792, 0x00068455, 0x000ffa1f }, + { 9, 0x00002ccc, 0x00004796, 0x00068455, 0x000ffa0b }, + { 10, 0x00002ccc, 0x00004796, 0x00068455, 0x000ffa1f }, + { 11, 0x00002ccc, 0x0000479a, 0x00068455, 0x000ffa0b }, + { 12, 0x00002ccc, 0x0000479a, 0x00068455, 0x000ffa1f }, + { 13, 0x00002ccc, 0x0000479e, 0x00068455, 0x000ffa0b }, + { 14, 0x00002ccc, 0x000047a2, 0x00068455, 0x000ffa13 }, + + /* 802.11 UNI / HyperLan 2 */ + { 36, 0x00002ccc, 0x0000499a, 0x0009be55, 0x000ffa23 }, + { 40, 0x00002ccc, 0x000049a2, 0x0009be55, 0x000ffa03 }, + { 44, 0x00002ccc, 0x000049a6, 0x0009be55, 0x000ffa0b }, + { 48, 0x00002ccc, 0x000049aa, 0x0009be55, 0x000ffa13 }, + { 52, 0x00002ccc, 0x000049ae, 0x0009ae55, 0x000ffa1b }, + { 56, 0x00002ccc, 0x000049b2, 0x0009ae55, 0x000ffa23 }, + { 60, 0x00002ccc, 0x000049ba, 0x0009ae55, 0x000ffa03 }, + { 64, 0x00002ccc, 0x000049be, 0x0009ae55, 0x000ffa0b }, + + /* 802.11 HyperLan 2 */ + { 100, 0x00002ccc, 0x00004a2a, 0x000bae55, 0x000ffa03 }, + { 104, 0x00002ccc, 0x00004a2e, 0x000bae55, 0x000ffa0b }, + { 108, 0x00002ccc, 0x00004a32, 0x000bae55, 0x000ffa13 }, + { 112, 0x00002ccc, 0x00004a36, 0x000bae55, 0x000ffa1b }, + { 116, 0x00002ccc, 0x00004a3a, 0x000bbe55, 0x000ffa23 }, + { 120, 0x00002ccc, 0x00004a82, 0x000bbe55, 0x000ffa03 }, + { 124, 0x00002ccc, 0x00004a86, 0x000bbe55, 0x000ffa0b }, + { 128, 0x00002ccc, 0x00004a8a, 0x000bbe55, 0x000ffa13 }, + { 132, 0x00002ccc, 0x00004a8e, 0x000bbe55, 0x000ffa1b }, + { 136, 0x00002ccc, 0x00004a92, 0x000bbe55, 0x000ffa23 }, + + /* 802.11 UNII */ + { 140, 0x00002ccc, 0x00004a9a, 0x000bbe55, 0x000ffa03 }, + { 149, 0x00002ccc, 0x00004aa2, 0x000bbe55, 0x000ffa1f }, + { 153, 0x00002ccc, 0x00004aa6, 0x000bbe55, 0x000ffa27 }, + { 157, 0x00002ccc, 0x00004aae, 0x000bbe55, 0x000ffa07 }, + { 161, 0x00002ccc, 0x00004ab2, 0x000bbe55, 0x000ffa0f }, + { 165, 0x00002ccc, 0x00004ab6, 0x000bbe55, 0x000ffa17 }, + + /* MMAC(Japan)J52 ch 34,38,42,46 */ + { 34, 0x00002ccc, 0x0000499a, 0x0009be55, 0x000ffa0b }, + { 38, 0x00002ccc, 0x0000499e, 0x0009be55, 0x000ffa13 }, + { 42, 0x00002ccc, 0x000049a2, 0x0009be55, 0x000ffa1b }, + { 46, 0x00002ccc, 0x000049a6, 0x0009be55, 0x000ffa23 }, +}; + + +static int rt73usb_probe_hw_mode(struct rt2x00_dev *rt2x00dev) +{ + struct hw_mode_spec *spec = &rt2x00dev->spec; + struct channel_info *info; + char *tx_power; + unsigned int i; + + /* + * Initialize all hw fields. + * + * Don't set IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING unless we are + * capable of sending the buffered frames out after the DTIM + * transmission using rt2x00lib_beacondone. This will send out + * multicast and broadcast traffic immediately instead of buffering it + * infinitly and thus dropping it after some time. + */ + rt2x00dev->hw->flags = + IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_PS_NULLFUNC_STACK; + + SET_IEEE80211_DEV(rt2x00dev->hw, rt2x00dev->dev); + SET_IEEE80211_PERM_ADDR(rt2x00dev->hw, + rt2x00_eeprom_addr(rt2x00dev, + EEPROM_MAC_ADDR_0)); + + /* + * Initialize hw_mode information. + */ + spec->supported_bands = SUPPORT_BAND_2GHZ; + spec->supported_rates = SUPPORT_RATE_CCK | SUPPORT_RATE_OFDM; + + if (rt2x00_rf(rt2x00dev, RF2528)) { + spec->num_channels = ARRAY_SIZE(rf_vals_bg_2528); + spec->channels = rf_vals_bg_2528; + } else if (rt2x00_rf(rt2x00dev, RF5226)) { + spec->supported_bands |= SUPPORT_BAND_5GHZ; + spec->num_channels = ARRAY_SIZE(rf_vals_5226); + spec->channels = rf_vals_5226; + } else if (rt2x00_rf(rt2x00dev, RF2527)) { + spec->num_channels = 14; + spec->channels = rf_vals_5225_2527; + } else if (rt2x00_rf(rt2x00dev, RF5225)) { + spec->supported_bands |= SUPPORT_BAND_5GHZ; + spec->num_channels = ARRAY_SIZE(rf_vals_5225_2527); + spec->channels = rf_vals_5225_2527; + } + + /* + * Create channel information array + */ + info = kcalloc(spec->num_channels, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + spec->channels_info = info; + + tx_power = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_G_START); + for (i = 0; i < 14; i++) { + info[i].max_power = MAX_TXPOWER; + info[i].default_power1 = TXPOWER_FROM_DEV(tx_power[i]); + } + + if (spec->num_channels > 14) { + tx_power = rt2x00_eeprom_addr(rt2x00dev, EEPROM_TXPOWER_A_START); + for (i = 14; i < spec->num_channels; i++) { + info[i].max_power = MAX_TXPOWER; + info[i].default_power1 = + TXPOWER_FROM_DEV(tx_power[i - 14]); + } + } + + return 0; +} + +static int rt73usb_probe_hw(struct rt2x00_dev *rt2x00dev) +{ + int retval; + u32 reg; + + /* + * Allocate eeprom data. + */ + retval = rt73usb_validate_eeprom(rt2x00dev); + if (retval) + return retval; + + retval = rt73usb_init_eeprom(rt2x00dev); + if (retval) + return retval; + + /* + * Enable rfkill polling by setting GPIO direction of the + * rfkill switch GPIO pin correctly. + */ + rt2x00usb_register_read(rt2x00dev, MAC_CSR13, ®); + rt2x00_set_field32(®, MAC_CSR13_DIR7, 0); + rt2x00usb_register_write(rt2x00dev, MAC_CSR13, reg); + + /* + * Initialize hw specifications. + */ + retval = rt73usb_probe_hw_mode(rt2x00dev); + if (retval) + return retval; + + /* + * This device has multiple filters for control frames, + * but has no a separate filter for PS Poll frames. + */ + __set_bit(CAPABILITY_CONTROL_FILTERS, &rt2x00dev->cap_flags); + + /* + * This device requires firmware. + */ + __set_bit(REQUIRE_FIRMWARE, &rt2x00dev->cap_flags); + if (!modparam_nohwcrypt) + __set_bit(CAPABILITY_HW_CRYPTO, &rt2x00dev->cap_flags); + __set_bit(CAPABILITY_LINK_TUNING, &rt2x00dev->cap_flags); + __set_bit(REQUIRE_PS_AUTOWAKE, &rt2x00dev->cap_flags); + + /* + * Set the rssi offset. + */ + rt2x00dev->rssi_offset = DEFAULT_RSSI_OFFSET; + + return 0; +} + +/* + * IEEE80211 stack callback functions. + */ +static int rt73usb_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue_idx, + const struct ieee80211_tx_queue_params *params) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + struct data_queue *queue; + struct rt2x00_field32 field; + int retval; + u32 reg; + u32 offset; + + /* + * First pass the configuration through rt2x00lib, that will + * update the queue settings and validate the input. After that + * we are free to update the registers based on the value + * in the queue parameter. + */ + retval = rt2x00mac_conf_tx(hw, vif, queue_idx, params); + if (retval) + return retval; + + /* + * We only need to perform additional register initialization + * for WMM queues/ + */ + if (queue_idx >= 4) + return 0; + + queue = rt2x00queue_get_tx_queue(rt2x00dev, queue_idx); + + /* Update WMM TXOP register */ + offset = AC_TXOP_CSR0 + (sizeof(u32) * (!!(queue_idx & 2))); + field.bit_offset = (queue_idx & 1) * 16; + field.bit_mask = 0xffff << field.bit_offset; + + rt2x00usb_register_read(rt2x00dev, offset, ®); + rt2x00_set_field32(®, field, queue->txop); + rt2x00usb_register_write(rt2x00dev, offset, reg); + + /* Update WMM registers */ + field.bit_offset = queue_idx * 4; + field.bit_mask = 0xf << field.bit_offset; + + rt2x00usb_register_read(rt2x00dev, AIFSN_CSR, ®); + rt2x00_set_field32(®, field, queue->aifs); + rt2x00usb_register_write(rt2x00dev, AIFSN_CSR, reg); + + rt2x00usb_register_read(rt2x00dev, CWMIN_CSR, ®); + rt2x00_set_field32(®, field, queue->cw_min); + rt2x00usb_register_write(rt2x00dev, CWMIN_CSR, reg); + + rt2x00usb_register_read(rt2x00dev, CWMAX_CSR, ®); + rt2x00_set_field32(®, field, queue->cw_max); + rt2x00usb_register_write(rt2x00dev, CWMAX_CSR, reg); + + return 0; +} + +static u64 rt73usb_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct rt2x00_dev *rt2x00dev = hw->priv; + u64 tsf; + u32 reg; + + rt2x00usb_register_read(rt2x00dev, TXRX_CSR13, ®); + tsf = (u64) rt2x00_get_field32(reg, TXRX_CSR13_HIGH_TSFTIMER) << 32; + rt2x00usb_register_read(rt2x00dev, TXRX_CSR12, ®); + tsf |= rt2x00_get_field32(reg, TXRX_CSR12_LOW_TSFTIMER); + + return tsf; +} + +static const struct ieee80211_ops rt73usb_mac80211_ops = { + .tx = rt2x00mac_tx, + .start = rt2x00mac_start, + .stop = rt2x00mac_stop, + .add_interface = rt2x00mac_add_interface, + .remove_interface = rt2x00mac_remove_interface, + .config = rt2x00mac_config, + .configure_filter = rt2x00mac_configure_filter, + .set_tim = rt2x00mac_set_tim, + .set_key = rt2x00mac_set_key, + .sw_scan_start = rt2x00mac_sw_scan_start, + .sw_scan_complete = rt2x00mac_sw_scan_complete, + .get_stats = rt2x00mac_get_stats, + .bss_info_changed = rt2x00mac_bss_info_changed, + .conf_tx = rt73usb_conf_tx, + .get_tsf = rt73usb_get_tsf, + .rfkill_poll = rt2x00mac_rfkill_poll, + .flush = rt2x00mac_flush, + .set_antenna = rt2x00mac_set_antenna, + .get_antenna = rt2x00mac_get_antenna, + .get_ringparam = rt2x00mac_get_ringparam, + .tx_frames_pending = rt2x00mac_tx_frames_pending, +}; + +static const struct rt2x00lib_ops rt73usb_rt2x00_ops = { + .probe_hw = rt73usb_probe_hw, + .get_firmware_name = rt73usb_get_firmware_name, + .check_firmware = rt73usb_check_firmware, + .load_firmware = rt73usb_load_firmware, + .initialize = rt2x00usb_initialize, + .uninitialize = rt2x00usb_uninitialize, + .clear_entry = rt2x00usb_clear_entry, + .set_device_state = rt73usb_set_device_state, + .rfkill_poll = rt73usb_rfkill_poll, + .link_stats = rt73usb_link_stats, + .reset_tuner = rt73usb_reset_tuner, + .link_tuner = rt73usb_link_tuner, + .watchdog = rt2x00usb_watchdog, + .start_queue = rt73usb_start_queue, + .kick_queue = rt2x00usb_kick_queue, + .stop_queue = rt73usb_stop_queue, + .flush_queue = rt2x00usb_flush_queue, + .write_tx_desc = rt73usb_write_tx_desc, + .write_beacon = rt73usb_write_beacon, + .clear_beacon = rt73usb_clear_beacon, + .get_tx_data_len = rt73usb_get_tx_data_len, + .fill_rxdone = rt73usb_fill_rxdone, + .config_shared_key = rt73usb_config_shared_key, + .config_pairwise_key = rt73usb_config_pairwise_key, + .config_filter = rt73usb_config_filter, + .config_intf = rt73usb_config_intf, + .config_erp = rt73usb_config_erp, + .config_ant = rt73usb_config_ant, + .config = rt73usb_config, +}; + +static const struct data_queue_desc rt73usb_queue_rx = { + .entry_num = 32, + .data_size = DATA_FRAME_SIZE, + .desc_size = RXD_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_usb), +}; + +static const struct data_queue_desc rt73usb_queue_tx = { + .entry_num = 32, + .data_size = DATA_FRAME_SIZE, + .desc_size = TXD_DESC_SIZE, + .priv_size = sizeof(struct queue_entry_priv_usb), +}; + +static const struct data_queue_desc rt73usb_queue_bcn = { + .entry_num = 4, + .data_size = MGMT_FRAME_SIZE, + .desc_size = TXINFO_SIZE, + .priv_size = sizeof(struct queue_entry_priv_usb), +}; + +static const struct rt2x00_ops rt73usb_ops = { + .name = KBUILD_MODNAME, + .max_ap_intf = 4, + .eeprom_size = EEPROM_SIZE, + .rf_size = RF_SIZE, + .tx_queues = NUM_TX_QUEUES, + .extra_tx_headroom = TXD_DESC_SIZE, + .rx = &rt73usb_queue_rx, + .tx = &rt73usb_queue_tx, + .bcn = &rt73usb_queue_bcn, + .lib = &rt73usb_rt2x00_ops, + .hw = &rt73usb_mac80211_ops, +#ifdef CPTCFG_RT2X00_LIB_DEBUGFS + .debugfs = &rt73usb_rt2x00debug, +#endif /* CPTCFG_RT2X00_LIB_DEBUGFS */ +}; + +/* + * rt73usb module information. + */ +static struct usb_device_id rt73usb_device_table[] = { + /* AboCom */ + { USB_DEVICE(0x07b8, 0xb21b) }, + { USB_DEVICE(0x07b8, 0xb21c) }, + { USB_DEVICE(0x07b8, 0xb21d) }, + { USB_DEVICE(0x07b8, 0xb21e) }, + { USB_DEVICE(0x07b8, 0xb21f) }, + /* AL */ + { USB_DEVICE(0x14b2, 0x3c10) }, + /* Amigo */ + { USB_DEVICE(0x148f, 0x9021) }, + { USB_DEVICE(0x0eb0, 0x9021) }, + /* AMIT */ + { USB_DEVICE(0x18c5, 0x0002) }, + /* Askey */ + { USB_DEVICE(0x1690, 0x0722) }, + /* ASUS */ + { USB_DEVICE(0x0b05, 0x1723) }, + { USB_DEVICE(0x0b05, 0x1724) }, + /* Belkin */ + { USB_DEVICE(0x050d, 0x7050) }, /* FCC ID: K7SF5D7050B ver. 3.x */ + { USB_DEVICE(0x050d, 0x705a) }, + { USB_DEVICE(0x050d, 0x905b) }, + { USB_DEVICE(0x050d, 0x905c) }, + /* Billionton */ + { USB_DEVICE(0x1631, 0xc019) }, + { USB_DEVICE(0x08dd, 0x0120) }, + /* Buffalo */ + { USB_DEVICE(0x0411, 0x00d8) }, + { USB_DEVICE(0x0411, 0x00d9) }, + { USB_DEVICE(0x0411, 0x00e6) }, + { USB_DEVICE(0x0411, 0x00f4) }, + { USB_DEVICE(0x0411, 0x0116) }, + { USB_DEVICE(0x0411, 0x0119) }, + { USB_DEVICE(0x0411, 0x0137) }, + /* CEIVA */ + { USB_DEVICE(0x178d, 0x02be) }, + /* CNet */ + { USB_DEVICE(0x1371, 0x9022) }, + { USB_DEVICE(0x1371, 0x9032) }, + /* Conceptronic */ + { USB_DEVICE(0x14b2, 0x3c22) }, + /* Corega */ + { USB_DEVICE(0x07aa, 0x002e) }, + /* D-Link */ + { USB_DEVICE(0x07d1, 0x3c03) }, + { USB_DEVICE(0x07d1, 0x3c04) }, + { USB_DEVICE(0x07d1, 0x3c06) }, + { USB_DEVICE(0x07d1, 0x3c07) }, + /* Edimax */ + { USB_DEVICE(0x7392, 0x7318) }, + { USB_DEVICE(0x7392, 0x7618) }, + /* EnGenius */ + { USB_DEVICE(0x1740, 0x3701) }, + /* Gemtek */ + { USB_DEVICE(0x15a9, 0x0004) }, + /* Gigabyte */ + { USB_DEVICE(0x1044, 0x8008) }, + { USB_DEVICE(0x1044, 0x800a) }, + /* Huawei-3Com */ + { USB_DEVICE(0x1472, 0x0009) }, + /* Hercules */ + { USB_DEVICE(0x06f8, 0xe002) }, + { USB_DEVICE(0x06f8, 0xe010) }, + { USB_DEVICE(0x06f8, 0xe020) }, + /* Linksys */ + { USB_DEVICE(0x13b1, 0x0020) }, + { USB_DEVICE(0x13b1, 0x0023) }, + { USB_DEVICE(0x13b1, 0x0028) }, + /* MSI */ + { USB_DEVICE(0x0db0, 0x4600) }, + { USB_DEVICE(0x0db0, 0x6877) }, + { USB_DEVICE(0x0db0, 0x6874) }, + { USB_DEVICE(0x0db0, 0xa861) }, + { USB_DEVICE(0x0db0, 0xa874) }, + /* Ovislink */ + { USB_DEVICE(0x1b75, 0x7318) }, + /* Ralink */ + { USB_DEVICE(0x04bb, 0x093d) }, + { USB_DEVICE(0x148f, 0x2573) }, + { USB_DEVICE(0x148f, 0x2671) }, + { USB_DEVICE(0x0812, 0x3101) }, + /* Qcom */ + { USB_DEVICE(0x18e8, 0x6196) }, + { USB_DEVICE(0x18e8, 0x6229) }, + { USB_DEVICE(0x18e8, 0x6238) }, + /* Samsung */ + { USB_DEVICE(0x04e8, 0x4471) }, + /* Senao */ + { USB_DEVICE(0x1740, 0x7100) }, + /* Sitecom */ + { USB_DEVICE(0x0df6, 0x0024) }, + { USB_DEVICE(0x0df6, 0x0027) }, + { USB_DEVICE(0x0df6, 0x002f) }, + { USB_DEVICE(0x0df6, 0x90ac) }, + { USB_DEVICE(0x0df6, 0x9712) }, + /* Surecom */ + { USB_DEVICE(0x0769, 0x31f3) }, + /* Tilgin */ + { USB_DEVICE(0x6933, 0x5001) }, + /* Philips */ + { USB_DEVICE(0x0471, 0x200a) }, + /* Planex */ + { USB_DEVICE(0x2019, 0xab01) }, + { USB_DEVICE(0x2019, 0xab50) }, + /* WideTell */ + { USB_DEVICE(0x7167, 0x3840) }, + /* Zcom */ + { USB_DEVICE(0x0cde, 0x001c) }, + /* ZyXEL */ + { USB_DEVICE(0x0586, 0x3415) }, + { 0, } +}; + +MODULE_AUTHOR(DRV_PROJECT); +MODULE_VERSION(DRV_VERSION); +MODULE_DESCRIPTION("Ralink RT73 USB Wireless LAN driver."); +MODULE_SUPPORTED_DEVICE("Ralink RT2571W & RT2671 USB chipset based cards"); +MODULE_DEVICE_TABLE(usb, rt73usb_device_table); +MODULE_FIRMWARE(FIRMWARE_RT2571); +MODULE_LICENSE("GPL"); + +static int rt73usb_probe(struct usb_interface *usb_intf, + const struct usb_device_id *id) +{ + return rt2x00usb_probe(usb_intf, &rt73usb_ops); +} + +static struct usb_driver rt73usb_driver = { + .name = KBUILD_MODNAME, + .id_table = rt73usb_device_table, + .probe = rt73usb_probe, + .disconnect = rt2x00usb_disconnect, + .suspend = rt2x00usb_suspend, + .resume = rt2x00usb_resume, + .reset_resume = rt2x00usb_resume, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +module_usb_driver(rt73usb_driver); diff --git a/drivers/net/wireless/rt2x00/rt73usb.h b/drivers/net/wireless/rt2x00/rt73usb.h new file mode 100644 index 0000000..7577e0b --- /dev/null +++ b/drivers/net/wireless/rt2x00/rt73usb.h @@ -0,0 +1,1081 @@ +/* + Copyright (C) 2004 - 2009 Ivo van Doorn + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + Module: rt73usb + Abstract: Data structures and registers for the rt73usb module. + Supported chipsets: rt2571W & rt2671. + */ + +#ifndef RT73USB_H +#define RT73USB_H + +/* + * RF chip defines. + */ +#define RF5226 0x0001 +#define RF2528 0x0002 +#define RF5225 0x0003 +#define RF2527 0x0004 + +/* + * Signal information. + * Default offset is required for RSSI <-> dBm conversion. + */ +#define DEFAULT_RSSI_OFFSET 120 + +/* + * Register layout information. + */ +#define CSR_REG_BASE 0x3000 +#define CSR_REG_SIZE 0x04b0 +#define EEPROM_BASE 0x0000 +#define EEPROM_SIZE 0x0100 +#define BBP_BASE 0x0000 +#define BBP_SIZE 0x0080 +#define RF_BASE 0x0004 +#define RF_SIZE 0x0010 + +/* + * Number of TX queues. + */ +#define NUM_TX_QUEUES 4 + +/* + * USB registers. + */ + +/* + * MCU_LEDCS: LED control for MCU Mailbox. + */ +#define MCU_LEDCS_LED_MODE FIELD16(0x001f) +#define MCU_LEDCS_RADIO_STATUS FIELD16(0x0020) +#define MCU_LEDCS_LINK_BG_STATUS FIELD16(0x0040) +#define MCU_LEDCS_LINK_A_STATUS FIELD16(0x0080) +#define MCU_LEDCS_POLARITY_GPIO_0 FIELD16(0x0100) +#define MCU_LEDCS_POLARITY_GPIO_1 FIELD16(0x0200) +#define MCU_LEDCS_POLARITY_GPIO_2 FIELD16(0x0400) +#define MCU_LEDCS_POLARITY_GPIO_3 FIELD16(0x0800) +#define MCU_LEDCS_POLARITY_GPIO_4 FIELD16(0x1000) +#define MCU_LEDCS_POLARITY_ACT FIELD16(0x2000) +#define MCU_LEDCS_POLARITY_READY_BG FIELD16(0x4000) +#define MCU_LEDCS_POLARITY_READY_A FIELD16(0x8000) + +/* + * 8051 firmware image. + */ +#define FIRMWARE_RT2571 "rt73.bin" +#define FIRMWARE_IMAGE_BASE 0x0800 + +/* + * Security key table memory. + * 16 entries 32-byte for shared key table + * 64 entries 32-byte for pairwise key table + * 64 entries 8-byte for pairwise ta key table + */ +#define SHARED_KEY_TABLE_BASE 0x1000 +#define PAIRWISE_KEY_TABLE_BASE 0x1200 +#define PAIRWISE_TA_TABLE_BASE 0x1a00 + +#define SHARED_KEY_ENTRY(__idx) \ + ( SHARED_KEY_TABLE_BASE + \ + ((__idx) * sizeof(struct hw_key_entry)) ) +#define PAIRWISE_KEY_ENTRY(__idx) \ + ( PAIRWISE_KEY_TABLE_BASE + \ + ((__idx) * sizeof(struct hw_key_entry)) ) +#define PAIRWISE_TA_ENTRY(__idx) \ + ( PAIRWISE_TA_TABLE_BASE + \ + ((__idx) * sizeof(struct hw_pairwise_ta_entry)) ) + +struct hw_key_entry { + u8 key[16]; + u8 tx_mic[8]; + u8 rx_mic[8]; +} __packed; + +struct hw_pairwise_ta_entry { + u8 address[6]; + u8 cipher; + u8 reserved; +} __packed; + +/* + * Since NULL frame won't be that long (256 byte), + * We steal 16 tail bytes to save debugging settings. + */ +#define HW_DEBUG_SETTING_BASE 0x2bf0 + +/* + * On-chip BEACON frame space. + */ +#define HW_BEACON_BASE0 0x2400 +#define HW_BEACON_BASE1 0x2500 +#define HW_BEACON_BASE2 0x2600 +#define HW_BEACON_BASE3 0x2700 + +#define HW_BEACON_OFFSET(__index) \ + ( HW_BEACON_BASE0 + (__index * 0x0100) ) + +/* + * MAC Control/Status Registers(CSR). + * Some values are set in TU, whereas 1 TU == 1024 us. + */ + +/* + * MAC_CSR0: ASIC revision number. + */ +#define MAC_CSR0 0x3000 +#define MAC_CSR0_REVISION FIELD32(0x0000000f) +#define MAC_CSR0_CHIPSET FIELD32(0x000ffff0) + +/* + * MAC_CSR1: System control register. + * SOFT_RESET: Software reset bit, 1: reset, 0: normal. + * BBP_RESET: Hardware reset BBP. + * HOST_READY: Host is ready after initialization, 1: ready. + */ +#define MAC_CSR1 0x3004 +#define MAC_CSR1_SOFT_RESET FIELD32(0x00000001) +#define MAC_CSR1_BBP_RESET FIELD32(0x00000002) +#define MAC_CSR1_HOST_READY FIELD32(0x00000004) + +/* + * MAC_CSR2: STA MAC register 0. + */ +#define MAC_CSR2 0x3008 +#define MAC_CSR2_BYTE0 FIELD32(0x000000ff) +#define MAC_CSR2_BYTE1 FIELD32(0x0000ff00) +#define MAC_CSR2_BYTE2 FIELD32(0x00ff0000) +#define MAC_CSR2_BYTE3 FIELD32(0xff000000) + +/* + * MAC_CSR3: STA MAC register 1. + * UNICAST_TO_ME_MASK: + * Used to mask off bits from byte 5 of the MAC address + * to determine the UNICAST_TO_ME bit for RX frames. + * The full mask is complemented by BSS_ID_MASK: + * MASK = BSS_ID_MASK & UNICAST_TO_ME_MASK + */ +#define MAC_CSR3 0x300c +#define MAC_CSR3_BYTE4 FIELD32(0x000000ff) +#define MAC_CSR3_BYTE5 FIELD32(0x0000ff00) +#define MAC_CSR3_UNICAST_TO_ME_MASK FIELD32(0x00ff0000) + +/* + * MAC_CSR4: BSSID register 0. + */ +#define MAC_CSR4 0x3010 +#define MAC_CSR4_BYTE0 FIELD32(0x000000ff) +#define MAC_CSR4_BYTE1 FIELD32(0x0000ff00) +#define MAC_CSR4_BYTE2 FIELD32(0x00ff0000) +#define MAC_CSR4_BYTE3 FIELD32(0xff000000) + +/* + * MAC_CSR5: BSSID register 1. + * BSS_ID_MASK: + * This mask is used to mask off bits 0 and 1 of byte 5 of the + * BSSID. This will make sure that those bits will be ignored + * when determining the MY_BSS of RX frames. + * 0: 1-BSSID mode (BSS index = 0) + * 1: 2-BSSID mode (BSS index: Byte5, bit 0) + * 2: 2-BSSID mode (BSS index: byte5, bit 1) + * 3: 4-BSSID mode (BSS index: byte5, bit 0 - 1) + */ +#define MAC_CSR5 0x3014 +#define MAC_CSR5_BYTE4 FIELD32(0x000000ff) +#define MAC_CSR5_BYTE5 FIELD32(0x0000ff00) +#define MAC_CSR5_BSS_ID_MASK FIELD32(0x00ff0000) + +/* + * MAC_CSR6: Maximum frame length register. + */ +#define MAC_CSR6 0x3018 +#define MAC_CSR6_MAX_FRAME_UNIT FIELD32(0x00000fff) + +/* + * MAC_CSR7: Reserved + */ +#define MAC_CSR7 0x301c + +/* + * MAC_CSR8: SIFS/EIFS register. + * All units are in US. + */ +#define MAC_CSR8 0x3020 +#define MAC_CSR8_SIFS FIELD32(0x000000ff) +#define MAC_CSR8_SIFS_AFTER_RX_OFDM FIELD32(0x0000ff00) +#define MAC_CSR8_EIFS FIELD32(0xffff0000) + +/* + * MAC_CSR9: Back-Off control register. + * SLOT_TIME: Slot time, default is 20us for 802.11BG. + * CWMIN: Bit for Cwmin. default Cwmin is 31 (2^5 - 1). + * CWMAX: Bit for Cwmax, default Cwmax is 1023 (2^10 - 1). + * CW_SELECT: 1: CWmin/Cwmax select from register, 0:select from TxD. + */ +#define MAC_CSR9 0x3024 +#define MAC_CSR9_SLOT_TIME FIELD32(0x000000ff) +#define MAC_CSR9_CWMIN FIELD32(0x00000f00) +#define MAC_CSR9_CWMAX FIELD32(0x0000f000) +#define MAC_CSR9_CW_SELECT FIELD32(0x00010000) + +/* + * MAC_CSR10: Power state configuration. + */ +#define MAC_CSR10 0x3028 + +/* + * MAC_CSR11: Power saving transition time register. + * DELAY_AFTER_TBCN: Delay after Tbcn expired in units of TU. + * TBCN_BEFORE_WAKEUP: Number of beacon before wakeup. + * WAKEUP_LATENCY: In unit of TU. + */ +#define MAC_CSR11 0x302c +#define MAC_CSR11_DELAY_AFTER_TBCN FIELD32(0x000000ff) +#define MAC_CSR11_TBCN_BEFORE_WAKEUP FIELD32(0x00007f00) +#define MAC_CSR11_AUTOWAKE FIELD32(0x00008000) +#define MAC_CSR11_WAKEUP_LATENCY FIELD32(0x000f0000) + +/* + * MAC_CSR12: Manual power control / status register (merge CSR20 & PWRCSR1). + * CURRENT_STATE: 0:sleep, 1:awake. + * FORCE_WAKEUP: This has higher priority than PUT_TO_SLEEP. + * BBP_CURRENT_STATE: 0: BBP sleep, 1: BBP awake. + */ +#define MAC_CSR12 0x3030 +#define MAC_CSR12_CURRENT_STATE FIELD32(0x00000001) +#define MAC_CSR12_PUT_TO_SLEEP FIELD32(0x00000002) +#define MAC_CSR12_FORCE_WAKEUP FIELD32(0x00000004) +#define MAC_CSR12_BBP_CURRENT_STATE FIELD32(0x00000008) + +/* + * MAC_CSR13: GPIO. + * MAC_CSR13_VALx: GPIO value + * MAC_CSR13_DIRx: GPIO direction: 0 = input; 1 = output + */ +#define MAC_CSR13 0x3034 +#define MAC_CSR13_VAL0 FIELD32(0x00000001) +#define MAC_CSR13_VAL1 FIELD32(0x00000002) +#define MAC_CSR13_VAL2 FIELD32(0x00000004) +#define MAC_CSR13_VAL3 FIELD32(0x00000008) +#define MAC_CSR13_VAL4 FIELD32(0x00000010) +#define MAC_CSR13_VAL5 FIELD32(0x00000020) +#define MAC_CSR13_VAL6 FIELD32(0x00000040) +#define MAC_CSR13_VAL7 FIELD32(0x00000080) +#define MAC_CSR13_DIR0 FIELD32(0x00000100) +#define MAC_CSR13_DIR1 FIELD32(0x00000200) +#define MAC_CSR13_DIR2 FIELD32(0x00000400) +#define MAC_CSR13_DIR3 FIELD32(0x00000800) +#define MAC_CSR13_DIR4 FIELD32(0x00001000) +#define MAC_CSR13_DIR5 FIELD32(0x00002000) +#define MAC_CSR13_DIR6 FIELD32(0x00004000) +#define MAC_CSR13_DIR7 FIELD32(0x00008000) + +/* + * MAC_CSR14: LED control register. + * ON_PERIOD: On period, default 70ms. + * OFF_PERIOD: Off period, default 30ms. + * HW_LED: HW TX activity, 1: normal OFF, 0: normal ON. + * SW_LED: s/w LED, 1: ON, 0: OFF. + * HW_LED_POLARITY: 0: active low, 1: active high. + */ +#define MAC_CSR14 0x3038 +#define MAC_CSR14_ON_PERIOD FIELD32(0x000000ff) +#define MAC_CSR14_OFF_PERIOD FIELD32(0x0000ff00) +#define MAC_CSR14_HW_LED FIELD32(0x00010000) +#define MAC_CSR14_SW_LED FIELD32(0x00020000) +#define MAC_CSR14_HW_LED_POLARITY FIELD32(0x00040000) +#define MAC_CSR14_SW_LED2 FIELD32(0x00080000) + +/* + * MAC_CSR15: NAV control. + */ +#define MAC_CSR15 0x303c + +/* + * TXRX control registers. + * Some values are set in TU, whereas 1 TU == 1024 us. + */ + +/* + * TXRX_CSR0: TX/RX configuration register. + * TSF_OFFSET: Default is 24. + * AUTO_TX_SEQ: 1: ASIC auto replace sequence nr in outgoing frame. + * DISABLE_RX: Disable Rx engine. + * DROP_CRC: Drop CRC error. + * DROP_PHYSICAL: Drop physical error. + * DROP_CONTROL: Drop control frame. + * DROP_NOT_TO_ME: Drop not to me unicast frame. + * DROP_TO_DS: Drop fram ToDs bit is true. + * DROP_VERSION_ERROR: Drop version error frame. + * DROP_MULTICAST: Drop multicast frames. + * DROP_BORADCAST: Drop broadcast frames. + * DROP_ACK_CTS: Drop received ACK and CTS. + */ +#define TXRX_CSR0 0x3040 +#define TXRX_CSR0_RX_ACK_TIMEOUT FIELD32(0x000001ff) +#define TXRX_CSR0_TSF_OFFSET FIELD32(0x00007e00) +#define TXRX_CSR0_AUTO_TX_SEQ FIELD32(0x00008000) +#define TXRX_CSR0_DISABLE_RX FIELD32(0x00010000) +#define TXRX_CSR0_DROP_CRC FIELD32(0x00020000) +#define TXRX_CSR0_DROP_PHYSICAL FIELD32(0x00040000) +#define TXRX_CSR0_DROP_CONTROL FIELD32(0x00080000) +#define TXRX_CSR0_DROP_NOT_TO_ME FIELD32(0x00100000) +#define TXRX_CSR0_DROP_TO_DS FIELD32(0x00200000) +#define TXRX_CSR0_DROP_VERSION_ERROR FIELD32(0x00400000) +#define TXRX_CSR0_DROP_MULTICAST FIELD32(0x00800000) +#define TXRX_CSR0_DROP_BROADCAST FIELD32(0x01000000) +#define TXRX_CSR0_DROP_ACK_CTS FIELD32(0x02000000) +#define TXRX_CSR0_TX_WITHOUT_WAITING FIELD32(0x04000000) + +/* + * TXRX_CSR1 + */ +#define TXRX_CSR1 0x3044 +#define TXRX_CSR1_BBP_ID0 FIELD32(0x0000007f) +#define TXRX_CSR1_BBP_ID0_VALID FIELD32(0x00000080) +#define TXRX_CSR1_BBP_ID1 FIELD32(0x00007f00) +#define TXRX_CSR1_BBP_ID1_VALID FIELD32(0x00008000) +#define TXRX_CSR1_BBP_ID2 FIELD32(0x007f0000) +#define TXRX_CSR1_BBP_ID2_VALID FIELD32(0x00800000) +#define TXRX_CSR1_BBP_ID3 FIELD32(0x7f000000) +#define TXRX_CSR1_BBP_ID3_VALID FIELD32(0x80000000) + +/* + * TXRX_CSR2 + */ +#define TXRX_CSR2 0x3048 +#define TXRX_CSR2_BBP_ID0 FIELD32(0x0000007f) +#define TXRX_CSR2_BBP_ID0_VALID FIELD32(0x00000080) +#define TXRX_CSR2_BBP_ID1 FIELD32(0x00007f00) +#define TXRX_CSR2_BBP_ID1_VALID FIELD32(0x00008000) +#define TXRX_CSR2_BBP_ID2 FIELD32(0x007f0000) +#define TXRX_CSR2_BBP_ID2_VALID FIELD32(0x00800000) +#define TXRX_CSR2_BBP_ID3 FIELD32(0x7f000000) +#define TXRX_CSR2_BBP_ID3_VALID FIELD32(0x80000000) + +/* + * TXRX_CSR3 + */ +#define TXRX_CSR3 0x304c +#define TXRX_CSR3_BBP_ID0 FIELD32(0x0000007f) +#define TXRX_CSR3_BBP_ID0_VALID FIELD32(0x00000080) +#define TXRX_CSR3_BBP_ID1 FIELD32(0x00007f00) +#define TXRX_CSR3_BBP_ID1_VALID FIELD32(0x00008000) +#define TXRX_CSR3_BBP_ID2 FIELD32(0x007f0000) +#define TXRX_CSR3_BBP_ID2_VALID FIELD32(0x00800000) +#define TXRX_CSR3_BBP_ID3 FIELD32(0x7f000000) +#define TXRX_CSR3_BBP_ID3_VALID FIELD32(0x80000000) + +/* + * TXRX_CSR4: Auto-Responder/Tx-retry register. + * AUTORESPOND_PREAMBLE: 0:long, 1:short preamble. + * OFDM_TX_RATE_DOWN: 1:enable. + * OFDM_TX_RATE_STEP: 0:1-step, 1: 2-step, 2:3-step, 3:4-step. + * OFDM_TX_FALLBACK_CCK: 0: Fallback to OFDM 6M only, 1: Fallback to CCK 1M,2M. + */ +#define TXRX_CSR4 0x3050 +#define TXRX_CSR4_TX_ACK_TIMEOUT FIELD32(0x000000ff) +#define TXRX_CSR4_CNTL_ACK_POLICY FIELD32(0x00000700) +#define TXRX_CSR4_ACK_CTS_PSM FIELD32(0x00010000) +#define TXRX_CSR4_AUTORESPOND_ENABLE FIELD32(0x00020000) +#define TXRX_CSR4_AUTORESPOND_PREAMBLE FIELD32(0x00040000) +#define TXRX_CSR4_OFDM_TX_RATE_DOWN FIELD32(0x00080000) +#define TXRX_CSR4_OFDM_TX_RATE_STEP FIELD32(0x00300000) +#define TXRX_CSR4_OFDM_TX_FALLBACK_CCK FIELD32(0x00400000) +#define TXRX_CSR4_LONG_RETRY_LIMIT FIELD32(0x0f000000) +#define TXRX_CSR4_SHORT_RETRY_LIMIT FIELD32(0xf0000000) + +/* + * TXRX_CSR5 + */ +#define TXRX_CSR5 0x3054 + +/* + * TXRX_CSR6: ACK/CTS payload consumed time + */ +#define TXRX_CSR6 0x3058 + +/* + * TXRX_CSR7: OFDM ACK/CTS payload consumed time for 6/9/12/18 mbps. + */ +#define TXRX_CSR7 0x305c +#define TXRX_CSR7_ACK_CTS_6MBS FIELD32(0x000000ff) +#define TXRX_CSR7_ACK_CTS_9MBS FIELD32(0x0000ff00) +#define TXRX_CSR7_ACK_CTS_12MBS FIELD32(0x00ff0000) +#define TXRX_CSR7_ACK_CTS_18MBS FIELD32(0xff000000) + +/* + * TXRX_CSR8: OFDM ACK/CTS payload consumed time for 24/36/48/54 mbps. + */ +#define TXRX_CSR8 0x3060 +#define TXRX_CSR8_ACK_CTS_24MBS FIELD32(0x000000ff) +#define TXRX_CSR8_ACK_CTS_36MBS FIELD32(0x0000ff00) +#define TXRX_CSR8_ACK_CTS_48MBS FIELD32(0x00ff0000) +#define TXRX_CSR8_ACK_CTS_54MBS FIELD32(0xff000000) + +/* + * TXRX_CSR9: Synchronization control register. + * BEACON_INTERVAL: In unit of 1/16 TU. + * TSF_TICKING: Enable TSF auto counting. + * TSF_SYNC: Tsf sync, 0: disable, 1: infra, 2: ad-hoc/master mode. + * BEACON_GEN: Enable beacon generator. + */ +#define TXRX_CSR9 0x3064 +#define TXRX_CSR9_BEACON_INTERVAL FIELD32(0x0000ffff) +#define TXRX_CSR9_TSF_TICKING FIELD32(0x00010000) +#define TXRX_CSR9_TSF_SYNC FIELD32(0x00060000) +#define TXRX_CSR9_TBTT_ENABLE FIELD32(0x00080000) +#define TXRX_CSR9_BEACON_GEN FIELD32(0x00100000) +#define TXRX_CSR9_TIMESTAMP_COMPENSATE FIELD32(0xff000000) + +/* + * TXRX_CSR10: BEACON alignment. + */ +#define TXRX_CSR10 0x3068 + +/* + * TXRX_CSR11: AES mask. + */ +#define TXRX_CSR11 0x306c + +/* + * TXRX_CSR12: TSF low 32. + */ +#define TXRX_CSR12 0x3070 +#define TXRX_CSR12_LOW_TSFTIMER FIELD32(0xffffffff) + +/* + * TXRX_CSR13: TSF high 32. + */ +#define TXRX_CSR13 0x3074 +#define TXRX_CSR13_HIGH_TSFTIMER FIELD32(0xffffffff) + +/* + * TXRX_CSR14: TBTT timer. + */ +#define TXRX_CSR14 0x3078 + +/* + * TXRX_CSR15: TKIP MIC priority byte "AND" mask. + */ +#define TXRX_CSR15 0x307c + +/* + * PHY control registers. + * Some values are set in TU, whereas 1 TU == 1024 us. + */ + +/* + * PHY_CSR0: RF/PS control. + */ +#define PHY_CSR0 0x3080 +#define PHY_CSR0_PA_PE_BG FIELD32(0x00010000) +#define PHY_CSR0_PA_PE_A FIELD32(0x00020000) + +/* + * PHY_CSR1 + */ +#define PHY_CSR1 0x3084 +#define PHY_CSR1_RF_RPI FIELD32(0x00010000) + +/* + * PHY_CSR2: Pre-TX BBP control. + */ +#define PHY_CSR2 0x3088 + +/* + * PHY_CSR3: BBP serial control register. + * VALUE: Register value to program into BBP. + * REG_NUM: Selected BBP register. + * READ_CONTROL: 0: Write BBP, 1: Read BBP. + * BUSY: 1: ASIC is busy execute BBP programming. + */ +#define PHY_CSR3 0x308c +#define PHY_CSR3_VALUE FIELD32(0x000000ff) +#define PHY_CSR3_REGNUM FIELD32(0x00007f00) +#define PHY_CSR3_READ_CONTROL FIELD32(0x00008000) +#define PHY_CSR3_BUSY FIELD32(0x00010000) + +/* + * PHY_CSR4: RF serial control register + * VALUE: Register value (include register id) serial out to RF/IF chip. + * NUMBER_OF_BITS: Number of bits used in RFRegValue (I:20, RFMD:22). + * IF_SELECT: 1: select IF to program, 0: select RF to program. + * PLL_LD: RF PLL_LD status. + * BUSY: 1: ASIC is busy execute RF programming. + */ +#define PHY_CSR4 0x3090 +#define PHY_CSR4_VALUE FIELD32(0x00ffffff) +#define PHY_CSR4_NUMBER_OF_BITS FIELD32(0x1f000000) +#define PHY_CSR4_IF_SELECT FIELD32(0x20000000) +#define PHY_CSR4_PLL_LD FIELD32(0x40000000) +#define PHY_CSR4_BUSY FIELD32(0x80000000) + +/* + * PHY_CSR5: RX to TX signal switch timing control. + */ +#define PHY_CSR5 0x3094 +#define PHY_CSR5_IQ_FLIP FIELD32(0x00000004) + +/* + * PHY_CSR6: TX to RX signal timing control. + */ +#define PHY_CSR6 0x3098 +#define PHY_CSR6_IQ_FLIP FIELD32(0x00000004) + +/* + * PHY_CSR7: TX DAC switching timing control. + */ +#define PHY_CSR7 0x309c + +/* + * Security control register. + */ + +/* + * SEC_CSR0: Shared key table control. + */ +#define SEC_CSR0 0x30a0 +#define SEC_CSR0_BSS0_KEY0_VALID FIELD32(0x00000001) +#define SEC_CSR0_BSS0_KEY1_VALID FIELD32(0x00000002) +#define SEC_CSR0_BSS0_KEY2_VALID FIELD32(0x00000004) +#define SEC_CSR0_BSS0_KEY3_VALID FIELD32(0x00000008) +#define SEC_CSR0_BSS1_KEY0_VALID FIELD32(0x00000010) +#define SEC_CSR0_BSS1_KEY1_VALID FIELD32(0x00000020) +#define SEC_CSR0_BSS1_KEY2_VALID FIELD32(0x00000040) +#define SEC_CSR0_BSS1_KEY3_VALID FIELD32(0x00000080) +#define SEC_CSR0_BSS2_KEY0_VALID FIELD32(0x00000100) +#define SEC_CSR0_BSS2_KEY1_VALID FIELD32(0x00000200) +#define SEC_CSR0_BSS2_KEY2_VALID FIELD32(0x00000400) +#define SEC_CSR0_BSS2_KEY3_VALID FIELD32(0x00000800) +#define SEC_CSR0_BSS3_KEY0_VALID FIELD32(0x00001000) +#define SEC_CSR0_BSS3_KEY1_VALID FIELD32(0x00002000) +#define SEC_CSR0_BSS3_KEY2_VALID FIELD32(0x00004000) +#define SEC_CSR0_BSS3_KEY3_VALID FIELD32(0x00008000) + +/* + * SEC_CSR1: Shared key table security mode register. + */ +#define SEC_CSR1 0x30a4 +#define SEC_CSR1_BSS0_KEY0_CIPHER_ALG FIELD32(0x00000007) +#define SEC_CSR1_BSS0_KEY1_CIPHER_ALG FIELD32(0x00000070) +#define SEC_CSR1_BSS0_KEY2_CIPHER_ALG FIELD32(0x00000700) +#define SEC_CSR1_BSS0_KEY3_CIPHER_ALG FIELD32(0x00007000) +#define SEC_CSR1_BSS1_KEY0_CIPHER_ALG FIELD32(0x00070000) +#define SEC_CSR1_BSS1_KEY1_CIPHER_ALG FIELD32(0x00700000) +#define SEC_CSR1_BSS1_KEY2_CIPHER_ALG FIELD32(0x07000000) +#define SEC_CSR1_BSS1_KEY3_CIPHER_ALG FIELD32(0x70000000) + +/* + * Pairwise key table valid bitmap registers. + * SEC_CSR2: pairwise key table valid bitmap 0. + * SEC_CSR3: pairwise key table valid bitmap 1. + */ +#define SEC_CSR2 0x30a8 +#define SEC_CSR3 0x30ac + +/* + * SEC_CSR4: Pairwise key table lookup control. + */ +#define SEC_CSR4 0x30b0 +#define SEC_CSR4_ENABLE_BSS0 FIELD32(0x00000001) +#define SEC_CSR4_ENABLE_BSS1 FIELD32(0x00000002) +#define SEC_CSR4_ENABLE_BSS2 FIELD32(0x00000004) +#define SEC_CSR4_ENABLE_BSS3 FIELD32(0x00000008) + +/* + * SEC_CSR5: shared key table security mode register. + */ +#define SEC_CSR5 0x30b4 +#define SEC_CSR5_BSS2_KEY0_CIPHER_ALG FIELD32(0x00000007) +#define SEC_CSR5_BSS2_KEY1_CIPHER_ALG FIELD32(0x00000070) +#define SEC_CSR5_BSS2_KEY2_CIPHER_ALG FIELD32(0x00000700) +#define SEC_CSR5_BSS2_KEY3_CIPHER_ALG FIELD32(0x00007000) +#define SEC_CSR5_BSS3_KEY0_CIPHER_ALG FIELD32(0x00070000) +#define SEC_CSR5_BSS3_KEY1_CIPHER_ALG FIELD32(0x00700000) +#define SEC_CSR5_BSS3_KEY2_CIPHER_ALG FIELD32(0x07000000) +#define SEC_CSR5_BSS3_KEY3_CIPHER_ALG FIELD32(0x70000000) + +/* + * STA control registers. + */ + +/* + * STA_CSR0: RX PLCP error count & RX FCS error count. + */ +#define STA_CSR0 0x30c0 +#define STA_CSR0_FCS_ERROR FIELD32(0x0000ffff) +#define STA_CSR0_PLCP_ERROR FIELD32(0xffff0000) + +/* + * STA_CSR1: RX False CCA count & RX LONG frame count. + */ +#define STA_CSR1 0x30c4 +#define STA_CSR1_PHYSICAL_ERROR FIELD32(0x0000ffff) +#define STA_CSR1_FALSE_CCA_ERROR FIELD32(0xffff0000) + +/* + * STA_CSR2: TX Beacon count and RX FIFO overflow count. + */ +#define STA_CSR2 0x30c8 +#define STA_CSR2_RX_FIFO_OVERFLOW_COUNT FIELD32(0x0000ffff) +#define STA_CSR2_RX_OVERFLOW_COUNT FIELD32(0xffff0000) + +/* + * STA_CSR3: TX Beacon count. + */ +#define STA_CSR3 0x30cc +#define STA_CSR3_TX_BEACON_COUNT FIELD32(0x0000ffff) + +/* + * STA_CSR4: TX Retry count. + */ +#define STA_CSR4 0x30d0 +#define STA_CSR4_TX_NO_RETRY_COUNT FIELD32(0x0000ffff) +#define STA_CSR4_TX_ONE_RETRY_COUNT FIELD32(0xffff0000) + +/* + * STA_CSR5: TX Retry count. + */ +#define STA_CSR5 0x30d4 +#define STA_CSR4_TX_MULTI_RETRY_COUNT FIELD32(0x0000ffff) +#define STA_CSR4_TX_RETRY_FAIL_COUNT FIELD32(0xffff0000) + +/* + * QOS control registers. + */ + +/* + * QOS_CSR1: TXOP holder MAC address register. + */ +#define QOS_CSR1 0x30e4 +#define QOS_CSR1_BYTE4 FIELD32(0x000000ff) +#define QOS_CSR1_BYTE5 FIELD32(0x0000ff00) + +/* + * QOS_CSR2: TXOP holder timeout register. + */ +#define QOS_CSR2 0x30e8 + +/* + * RX QOS-CFPOLL MAC address register. + * QOS_CSR3: RX QOS-CFPOLL MAC address 0. + * QOS_CSR4: RX QOS-CFPOLL MAC address 1. + */ +#define QOS_CSR3 0x30ec +#define QOS_CSR4 0x30f0 + +/* + * QOS_CSR5: "QosControl" field of the RX QOS-CFPOLL. + */ +#define QOS_CSR5 0x30f4 + +/* + * WMM Scheduler Register + */ + +/* + * AIFSN_CSR: AIFSN for each EDCA AC. + * AIFSN0: For AC_VO. + * AIFSN1: For AC_VI. + * AIFSN2: For AC_BE. + * AIFSN3: For AC_BK. + */ +#define AIFSN_CSR 0x0400 +#define AIFSN_CSR_AIFSN0 FIELD32(0x0000000f) +#define AIFSN_CSR_AIFSN1 FIELD32(0x000000f0) +#define AIFSN_CSR_AIFSN2 FIELD32(0x00000f00) +#define AIFSN_CSR_AIFSN3 FIELD32(0x0000f000) + +/* + * CWMIN_CSR: CWmin for each EDCA AC. + * CWMIN0: For AC_VO. + * CWMIN1: For AC_VI. + * CWMIN2: For AC_BE. + * CWMIN3: For AC_BK. + */ +#define CWMIN_CSR 0x0404 +#define CWMIN_CSR_CWMIN0 FIELD32(0x0000000f) +#define CWMIN_CSR_CWMIN1 FIELD32(0x000000f0) +#define CWMIN_CSR_CWMIN2 FIELD32(0x00000f00) +#define CWMIN_CSR_CWMIN3 FIELD32(0x0000f000) + +/* + * CWMAX_CSR: CWmax for each EDCA AC. + * CWMAX0: For AC_VO. + * CWMAX1: For AC_VI. + * CWMAX2: For AC_BE. + * CWMAX3: For AC_BK. + */ +#define CWMAX_CSR 0x0408 +#define CWMAX_CSR_CWMAX0 FIELD32(0x0000000f) +#define CWMAX_CSR_CWMAX1 FIELD32(0x000000f0) +#define CWMAX_CSR_CWMAX2 FIELD32(0x00000f00) +#define CWMAX_CSR_CWMAX3 FIELD32(0x0000f000) + +/* + * AC_TXOP_CSR0: AC_VO/AC_VI TXOP register. + * AC0_TX_OP: For AC_VO, in unit of 32us. + * AC1_TX_OP: For AC_VI, in unit of 32us. + */ +#define AC_TXOP_CSR0 0x040c +#define AC_TXOP_CSR0_AC0_TX_OP FIELD32(0x0000ffff) +#define AC_TXOP_CSR0_AC1_TX_OP FIELD32(0xffff0000) + +/* + * AC_TXOP_CSR1: AC_BE/AC_BK TXOP register. + * AC2_TX_OP: For AC_BE, in unit of 32us. + * AC3_TX_OP: For AC_BK, in unit of 32us. + */ +#define AC_TXOP_CSR1 0x0410 +#define AC_TXOP_CSR1_AC2_TX_OP FIELD32(0x0000ffff) +#define AC_TXOP_CSR1_AC3_TX_OP FIELD32(0xffff0000) + +/* + * BBP registers. + * The wordsize of the BBP is 8 bits. + */ + +/* + * R2 + */ +#define BBP_R2_BG_MODE FIELD8(0x20) + +/* + * R3 + */ +#define BBP_R3_SMART_MODE FIELD8(0x01) + +/* + * R4: RX antenna control + * FRAME_END: 1 - DPDT, 0 - SPDT (Only valid for 802.11G, RF2527 & RF2529) + */ + +/* + * ANTENNA_CONTROL semantics (guessed): + * 0x1: Software controlled antenna switching (fixed or SW diversity) + * 0x2: Hardware diversity. + */ +#define BBP_R4_RX_ANTENNA_CONTROL FIELD8(0x03) +#define BBP_R4_RX_FRAME_END FIELD8(0x20) + +/* + * R77 + */ +#define BBP_R77_RX_ANTENNA FIELD8(0x03) + +/* + * RF registers + */ + +/* + * RF 3 + */ +#define RF3_TXPOWER FIELD32(0x00003e00) + +/* + * RF 4 + */ +#define RF4_FREQ_OFFSET FIELD32(0x0003f000) + +/* + * EEPROM content. + * The wordsize of the EEPROM is 16 bits. + */ + +/* + * HW MAC address. + */ +#define EEPROM_MAC_ADDR_0 0x0002 +#define EEPROM_MAC_ADDR_BYTE0 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE1 FIELD16(0xff00) +#define EEPROM_MAC_ADDR1 0x0003 +#define EEPROM_MAC_ADDR_BYTE2 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE3 FIELD16(0xff00) +#define EEPROM_MAC_ADDR_2 0x0004 +#define EEPROM_MAC_ADDR_BYTE4 FIELD16(0x00ff) +#define EEPROM_MAC_ADDR_BYTE5 FIELD16(0xff00) + +/* + * EEPROM antenna. + * ANTENNA_NUM: Number of antennas. + * TX_DEFAULT: Default antenna 0: diversity, 1: A, 2: B. + * RX_DEFAULT: Default antenna 0: diversity, 1: A, 2: B. + * FRAME_TYPE: 0: DPDT , 1: SPDT , noted this bit is valid for g only. + * DYN_TXAGC: Dynamic TX AGC control. + * HARDWARE_RADIO: 1: Hardware controlled radio. Read GPIO0. + * RF_TYPE: Rf_type of this adapter. + */ +#define EEPROM_ANTENNA 0x0010 +#define EEPROM_ANTENNA_NUM FIELD16(0x0003) +#define EEPROM_ANTENNA_TX_DEFAULT FIELD16(0x000c) +#define EEPROM_ANTENNA_RX_DEFAULT FIELD16(0x0030) +#define EEPROM_ANTENNA_FRAME_TYPE FIELD16(0x0040) +#define EEPROM_ANTENNA_DYN_TXAGC FIELD16(0x0200) +#define EEPROM_ANTENNA_HARDWARE_RADIO FIELD16(0x0400) +#define EEPROM_ANTENNA_RF_TYPE FIELD16(0xf800) + +/* + * EEPROM NIC config. + * EXTERNAL_LNA: External LNA. + */ +#define EEPROM_NIC 0x0011 +#define EEPROM_NIC_EXTERNAL_LNA FIELD16(0x0010) + +/* + * EEPROM geography. + * GEO_A: Default geographical setting for 5GHz band + * GEO: Default geographical setting. + */ +#define EEPROM_GEOGRAPHY 0x0012 +#define EEPROM_GEOGRAPHY_GEO_A FIELD16(0x00ff) +#define EEPROM_GEOGRAPHY_GEO FIELD16(0xff00) + +/* + * EEPROM BBP. + */ +#define EEPROM_BBP_START 0x0013 +#define EEPROM_BBP_SIZE 16 +#define EEPROM_BBP_VALUE FIELD16(0x00ff) +#define EEPROM_BBP_REG_ID FIELD16(0xff00) + +/* + * EEPROM TXPOWER 802.11G + */ +#define EEPROM_TXPOWER_G_START 0x0023 +#define EEPROM_TXPOWER_G_SIZE 7 +#define EEPROM_TXPOWER_G_1 FIELD16(0x00ff) +#define EEPROM_TXPOWER_G_2 FIELD16(0xff00) + +/* + * EEPROM Frequency + */ +#define EEPROM_FREQ 0x002f +#define EEPROM_FREQ_OFFSET FIELD16(0x00ff) +#define EEPROM_FREQ_SEQ_MASK FIELD16(0xff00) +#define EEPROM_FREQ_SEQ FIELD16(0x0300) + +/* + * EEPROM LED. + * POLARITY_RDY_G: Polarity RDY_G setting. + * POLARITY_RDY_A: Polarity RDY_A setting. + * POLARITY_ACT: Polarity ACT setting. + * POLARITY_GPIO_0: Polarity GPIO0 setting. + * POLARITY_GPIO_1: Polarity GPIO1 setting. + * POLARITY_GPIO_2: Polarity GPIO2 setting. + * POLARITY_GPIO_3: Polarity GPIO3 setting. + * POLARITY_GPIO_4: Polarity GPIO4 setting. + * LED_MODE: Led mode. + */ +#define EEPROM_LED 0x0030 +#define EEPROM_LED_POLARITY_RDY_G FIELD16(0x0001) +#define EEPROM_LED_POLARITY_RDY_A FIELD16(0x0002) +#define EEPROM_LED_POLARITY_ACT FIELD16(0x0004) +#define EEPROM_LED_POLARITY_GPIO_0 FIELD16(0x0008) +#define EEPROM_LED_POLARITY_GPIO_1 FIELD16(0x0010) +#define EEPROM_LED_POLARITY_GPIO_2 FIELD16(0x0020) +#define EEPROM_LED_POLARITY_GPIO_3 FIELD16(0x0040) +#define EEPROM_LED_POLARITY_GPIO_4 FIELD16(0x0080) +#define EEPROM_LED_LED_MODE FIELD16(0x1f00) + +/* + * EEPROM TXPOWER 802.11A + */ +#define EEPROM_TXPOWER_A_START 0x0031 +#define EEPROM_TXPOWER_A_SIZE 12 +#define EEPROM_TXPOWER_A_1 FIELD16(0x00ff) +#define EEPROM_TXPOWER_A_2 FIELD16(0xff00) + +/* + * EEPROM RSSI offset 802.11BG + */ +#define EEPROM_RSSI_OFFSET_BG 0x004d +#define EEPROM_RSSI_OFFSET_BG_1 FIELD16(0x00ff) +#define EEPROM_RSSI_OFFSET_BG_2 FIELD16(0xff00) + +/* + * EEPROM RSSI offset 802.11A + */ +#define EEPROM_RSSI_OFFSET_A 0x004e +#define EEPROM_RSSI_OFFSET_A_1 FIELD16(0x00ff) +#define EEPROM_RSSI_OFFSET_A_2 FIELD16(0xff00) + +/* + * DMA descriptor defines. + */ +#define TXD_DESC_SIZE ( 6 * sizeof(__le32) ) +#define TXINFO_SIZE ( 6 * sizeof(__le32) ) +#define RXD_DESC_SIZE ( 6 * sizeof(__le32) ) + +/* + * TX descriptor format for TX, PRIO and Beacon Ring. + */ + +/* + * Word0 + * BURST: Next frame belongs to same "burst" event. + * TKIP_MIC: ASIC appends TKIP MIC if TKIP is used. + * KEY_TABLE: Use per-client pairwise KEY table. + * KEY_INDEX: + * Key index (0~31) to the pairwise KEY table. + * 0~3 to shared KEY table 0 (BSS0). + * 4~7 to shared KEY table 1 (BSS1). + * 8~11 to shared KEY table 2 (BSS2). + * 12~15 to shared KEY table 3 (BSS3). + * BURST2: For backward compatibility, set to same value as BURST. + */ +#define TXD_W0_BURST FIELD32(0x00000001) +#define TXD_W0_VALID FIELD32(0x00000002) +#define TXD_W0_MORE_FRAG FIELD32(0x00000004) +#define TXD_W0_ACK FIELD32(0x00000008) +#define TXD_W0_TIMESTAMP FIELD32(0x00000010) +#define TXD_W0_OFDM FIELD32(0x00000020) +#define TXD_W0_IFS FIELD32(0x00000040) +#define TXD_W0_RETRY_MODE FIELD32(0x00000080) +#define TXD_W0_TKIP_MIC FIELD32(0x00000100) +#define TXD_W0_KEY_TABLE FIELD32(0x00000200) +#define TXD_W0_KEY_INDEX FIELD32(0x0000fc00) +#define TXD_W0_DATABYTE_COUNT FIELD32(0x0fff0000) +#define TXD_W0_BURST2 FIELD32(0x10000000) +#define TXD_W0_CIPHER_ALG FIELD32(0xe0000000) + +/* + * Word1 + * HOST_Q_ID: EDCA/HCCA queue ID. + * HW_SEQUENCE: MAC overwrites the frame sequence number. + * BUFFER_COUNT: Number of buffers in this TXD. + */ +#define TXD_W1_HOST_Q_ID FIELD32(0x0000000f) +#define TXD_W1_AIFSN FIELD32(0x000000f0) +#define TXD_W1_CWMIN FIELD32(0x00000f00) +#define TXD_W1_CWMAX FIELD32(0x0000f000) +#define TXD_W1_IV_OFFSET FIELD32(0x003f0000) +#define TXD_W1_HW_SEQUENCE FIELD32(0x10000000) +#define TXD_W1_BUFFER_COUNT FIELD32(0xe0000000) + +/* + * Word2: PLCP information + */ +#define TXD_W2_PLCP_SIGNAL FIELD32(0x000000ff) +#define TXD_W2_PLCP_SERVICE FIELD32(0x0000ff00) +#define TXD_W2_PLCP_LENGTH_LOW FIELD32(0x00ff0000) +#define TXD_W2_PLCP_LENGTH_HIGH FIELD32(0xff000000) + +/* + * Word3 + */ +#define TXD_W3_IV FIELD32(0xffffffff) + +/* + * Word4 + */ +#define TXD_W4_EIV FIELD32(0xffffffff) + +/* + * Word5 + * FRAME_OFFSET: Frame start offset inside ASIC TXFIFO (after TXINFO field). + * PACKET_ID: Driver assigned packet ID to categorize TXResult in interrupt. + * WAITING_DMA_DONE_INT: TXD been filled with data + * and waiting for TxDoneISR housekeeping. + */ +#define TXD_W5_FRAME_OFFSET FIELD32(0x000000ff) +#define TXD_W5_PACKET_ID FIELD32(0x0000ff00) +#define TXD_W5_TX_POWER FIELD32(0x00ff0000) +#define TXD_W5_WAITING_DMA_DONE_INT FIELD32(0x01000000) + +/* + * RX descriptor format for RX Ring. + */ + +/* + * Word0 + * CIPHER_ERROR: 1:ICV error, 2:MIC error, 3:invalid key. + * KEY_INDEX: Decryption key actually used. + */ +#define RXD_W0_OWNER_NIC FIELD32(0x00000001) +#define RXD_W0_DROP FIELD32(0x00000002) +#define RXD_W0_UNICAST_TO_ME FIELD32(0x00000004) +#define RXD_W0_MULTICAST FIELD32(0x00000008) +#define RXD_W0_BROADCAST FIELD32(0x00000010) +#define RXD_W0_MY_BSS FIELD32(0x00000020) +#define RXD_W0_CRC_ERROR FIELD32(0x00000040) +#define RXD_W0_OFDM FIELD32(0x00000080) +#define RXD_W0_CIPHER_ERROR FIELD32(0x00000300) +#define RXD_W0_KEY_INDEX FIELD32(0x0000fc00) +#define RXD_W0_DATABYTE_COUNT FIELD32(0x0fff0000) +#define RXD_W0_CIPHER_ALG FIELD32(0xe0000000) + +/* + * WORD1 + * SIGNAL: RX raw data rate reported by BBP. + * RSSI: RSSI reported by BBP. + */ +#define RXD_W1_SIGNAL FIELD32(0x000000ff) +#define RXD_W1_RSSI_AGC FIELD32(0x00001f00) +#define RXD_W1_RSSI_LNA FIELD32(0x00006000) +#define RXD_W1_FRAME_OFFSET FIELD32(0x7f000000) + +/* + * Word2 + * IV: Received IV of originally encrypted. + */ +#define RXD_W2_IV FIELD32(0xffffffff) + +/* + * Word3 + * EIV: Received EIV of originally encrypted. + */ +#define RXD_W3_EIV FIELD32(0xffffffff) + +/* + * Word4 + * ICV: Received ICV of originally encrypted. + * NOTE: This is a guess, the official definition is "reserved" + */ +#define RXD_W4_ICV FIELD32(0xffffffff) + +/* + * the above 20-byte is called RXINFO and will be DMAed to MAC RX block + * and passed to the HOST driver. + * The following fields are for DMA block and HOST usage only. + * Can't be touched by ASIC MAC block. + */ + +/* + * Word5 + */ +#define RXD_W5_RESERVED FIELD32(0xffffffff) + +/* + * Macros for converting txpower from EEPROM to mac80211 value + * and from mac80211 value to register value. + */ +#define MIN_TXPOWER 0 +#define MAX_TXPOWER 31 +#define DEFAULT_TXPOWER 24 + +#define TXPOWER_FROM_DEV(__txpower) \ + (((u8)(__txpower)) > MAX_TXPOWER) ? DEFAULT_TXPOWER : (__txpower) + +#define TXPOWER_TO_DEV(__txpower) \ + clamp_t(char, __txpower, MIN_TXPOWER, MAX_TXPOWER) + +#endif /* RT73USB_H */ diff --git a/drivers/net/wireless/rtl818x/Kconfig b/drivers/net/wireless/rtl818x/Kconfig new file mode 100644 index 0000000..ad8c81a --- /dev/null +++ b/drivers/net/wireless/rtl818x/Kconfig @@ -0,0 +1,90 @@ +# +# RTL818X Wireless LAN device configuration +# +config RTL8180 + tristate "Realtek 8180/8185 PCI support" + depends on m + depends on MAC80211 && PCI + depends on EEPROM_93CX6 + ---help--- + This is a driver for RTL8180 and RTL8185 based cards. + These are PCI based chips found in cards such as: + + (RTL8185 802.11g) + A-Link WL54PC + + (RTL8180 802.11b) + Belkin F5D6020 v3 + Belkin F5D6020 v3 + Dlink DWL-610 + Dlink DWL-510 + Netgear MA521 + Level-One WPC-0101 + Acer Aspire 1357 LMi + VCTnet PC-11B1 + Ovislink AirLive WL-1120PCM + Mentor WL-PCI + Linksys WPC11 v4 + TrendNET TEW-288PI + D-Link DWL-520 Rev D + Repotec RP-WP7126 + TP-Link TL-WN250/251 + Zonet ZEW1000 + Longshine LCS-8031-R + HomeLine HLW-PCC200 + GigaFast WF721-AEX + Planet WL-3553 + Encore ENLWI-PCI1-NT + TrendNET TEW-266PC + Gigabyte GN-WLMR101 + Siemens-fujitsu Amilo D1840W + Edimax EW-7126 + PheeNet WL-11PCIR + Tonze PC-2100T + Planet WL-8303 + Dlink DWL-650 v M1 + Edimax EW-7106 + Q-Tec 770WC + Topcom Skyr@cer 4011b + Roper FreeLan 802.11b (edition 2004) + Wistron Neweb Corp CB-200B + Pentagram HorNET + QTec 775WC + TwinMOS Booming B Series + Micronet SP906BB + Sweex LC700010 + Surecom EP-9428 + Safecom SWLCR-1100 + + Thanks to Realtek for their support! + +config RTL8187 + tristate "Realtek 8187 and 8187B USB support" + depends on m + depends on MAC80211 && USB + depends on EEPROM_93CX6 + ---help--- + This is a driver for RTL8187 and RTL8187B based cards. + These are USB based chips found in devices such as: + + Netgear WG111v2 + Level 1 WNC-0301USB + Micronet SP907GK V5 + Encore ENUWI-G2 + Trendnet TEW-424UB + ASUS P5B Deluxe/P5K Premium motherboards + Toshiba Satellite Pro series of laptops + Asus Wireless Link + Linksys WUSB54GC-EU v2 + (v1 = rt73usb; v3 is rt2070-based, + use staging/rt3070 or try rt2800usb) + + Thanks to Realtek for their support! + +# If possible, automatically enable LEDs for RTL8187. + +config RTL8187_LEDS + bool + depends on RTL8187 && MAC80211_LEDS && (LEDS_CLASS = y || LEDS_CLASS = RTL8187) + default y + diff --git a/drivers/net/wireless/rtl818x/Makefile b/drivers/net/wireless/rtl818x/Makefile new file mode 100644 index 0000000..4124bfa --- /dev/null +++ b/drivers/net/wireless/rtl818x/Makefile @@ -0,0 +1,2 @@ +obj-$(CPTCFG_RTL8180) += rtl8180/ +obj-$(CPTCFG_RTL8187) += rtl8187/ diff --git a/drivers/net/wireless/rtl818x/rtl8180/Makefile b/drivers/net/wireless/rtl818x/rtl8180/Makefile new file mode 100644 index 0000000..c0804b2 --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8180/Makefile @@ -0,0 +1,5 @@ +rtl8180-objs := dev.o rtl8225.o sa2400.o max2820.o grf5101.o + +obj-$(CPTCFG_RTL8180) += rtl8180.o + +ccflags-y += -I$(backport_srctree)/drivers/net/wireless/rtl818x diff --git a/drivers/net/wireless/rtl818x/rtl8180/dev.c b/drivers/net/wireless/rtl818x/rtl8180/dev.c new file mode 100644 index 0000000..380d15c --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8180/dev.c @@ -0,0 +1,1189 @@ + +/* + * Linux device driver for RTL8180 / RTL8185 + * + * Copyright 2007 Michael Wu + * Copyright 2007 Andrea Merello + * + * Based on the r8180 driver, which is: + * Copyright 2004-2005 Andrea Merello , et al. + * + * Thanks to Realtek for their support! + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8180.h" +#include "rtl8225.h" +#include "sa2400.h" +#include "max2820.h" +#include "grf5101.h" + +MODULE_AUTHOR("Michael Wu "); +MODULE_AUTHOR("Andrea Merello "); +MODULE_DESCRIPTION("RTL8180 / RTL8185 PCI wireless driver"); +MODULE_LICENSE("GPL"); + +static DEFINE_PCI_DEVICE_TABLE(rtl8180_table) = { + /* rtl8185 */ + { PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8185) }, + { PCI_DEVICE(PCI_VENDOR_ID_BELKIN, 0x700f) }, + { PCI_DEVICE(PCI_VENDOR_ID_BELKIN, 0x701f) }, + + /* rtl8180 */ + { PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8180) }, + { PCI_DEVICE(0x1799, 0x6001) }, + { PCI_DEVICE(0x1799, 0x6020) }, + { PCI_DEVICE(PCI_VENDOR_ID_DLINK, 0x3300) }, + { PCI_DEVICE(0x1186, 0x3301) }, + { PCI_DEVICE(0x1432, 0x7106) }, + { } +}; + +MODULE_DEVICE_TABLE(pci, rtl8180_table); + +static const struct ieee80211_rate rtl818x_rates[] = { + { .bitrate = 10, .hw_value = 0, }, + { .bitrate = 20, .hw_value = 1, }, + { .bitrate = 55, .hw_value = 2, }, + { .bitrate = 110, .hw_value = 3, }, + { .bitrate = 60, .hw_value = 4, }, + { .bitrate = 90, .hw_value = 5, }, + { .bitrate = 120, .hw_value = 6, }, + { .bitrate = 180, .hw_value = 7, }, + { .bitrate = 240, .hw_value = 8, }, + { .bitrate = 360, .hw_value = 9, }, + { .bitrate = 480, .hw_value = 10, }, + { .bitrate = 540, .hw_value = 11, }, +}; + +static const struct ieee80211_channel rtl818x_channels[] = { + { .center_freq = 2412 }, + { .center_freq = 2417 }, + { .center_freq = 2422 }, + { .center_freq = 2427 }, + { .center_freq = 2432 }, + { .center_freq = 2437 }, + { .center_freq = 2442 }, + { .center_freq = 2447 }, + { .center_freq = 2452 }, + { .center_freq = 2457 }, + { .center_freq = 2462 }, + { .center_freq = 2467 }, + { .center_freq = 2472 }, + { .center_freq = 2484 }, +}; + + +void rtl8180_write_phy(struct ieee80211_hw *dev, u8 addr, u32 data) +{ + struct rtl8180_priv *priv = dev->priv; + int i = 10; + u32 buf; + + buf = (data << 8) | addr; + + rtl818x_iowrite32(priv, (__le32 __iomem *)&priv->map->PHY[0], buf | 0x80); + while (i--) { + rtl818x_iowrite32(priv, (__le32 __iomem *)&priv->map->PHY[0], buf); + if (rtl818x_ioread8(priv, &priv->map->PHY[2]) == (data & 0xFF)) + return; + } +} + +static void rtl8180_handle_rx(struct ieee80211_hw *dev) +{ + struct rtl8180_priv *priv = dev->priv; + unsigned int count = 32; + u8 signal, agc, sq; + + while (count--) { + struct rtl8180_rx_desc *entry = &priv->rx_ring[priv->rx_idx]; + struct sk_buff *skb = priv->rx_buf[priv->rx_idx]; + u32 flags = le32_to_cpu(entry->flags); + + if (flags & RTL818X_RX_DESC_FLAG_OWN) + return; + + if (unlikely(flags & (RTL818X_RX_DESC_FLAG_DMA_FAIL | + RTL818X_RX_DESC_FLAG_FOF | + RTL818X_RX_DESC_FLAG_RX_ERR))) + goto done; + else { + u32 flags2 = le32_to_cpu(entry->flags2); + struct ieee80211_rx_status rx_status = {0}; + struct sk_buff *new_skb = dev_alloc_skb(MAX_RX_SIZE); + + if (unlikely(!new_skb)) + goto done; + + pci_unmap_single(priv->pdev, + *((dma_addr_t *)skb->cb), + MAX_RX_SIZE, PCI_DMA_FROMDEVICE); + skb_put(skb, flags & 0xFFF); + + rx_status.antenna = (flags2 >> 15) & 1; + rx_status.rate_idx = (flags >> 20) & 0xF; + agc = (flags2 >> 17) & 0x7F; + if (priv->r8185) { + if (rx_status.rate_idx > 3) + signal = 90 - clamp_t(u8, agc, 25, 90); + else + signal = 95 - clamp_t(u8, agc, 30, 95); + } else { + sq = flags2 & 0xff; + signal = priv->rf->calc_rssi(agc, sq); + } + rx_status.signal = signal; + rx_status.freq = dev->conf.chandef.chan->center_freq; + rx_status.band = dev->conf.chandef.chan->band; + rx_status.mactime = le64_to_cpu(entry->tsft); + rx_status.flag |= RX_FLAG_MACTIME_START; + if (flags & RTL818X_RX_DESC_FLAG_CRC32_ERR) + rx_status.flag |= RX_FLAG_FAILED_FCS_CRC; + + memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); + ieee80211_rx_irqsafe(dev, skb); + + skb = new_skb; + priv->rx_buf[priv->rx_idx] = skb; + *((dma_addr_t *) skb->cb) = + pci_map_single(priv->pdev, skb_tail_pointer(skb), + MAX_RX_SIZE, PCI_DMA_FROMDEVICE); + } + + done: + entry->rx_buf = cpu_to_le32(*((dma_addr_t *)skb->cb)); + entry->flags = cpu_to_le32(RTL818X_RX_DESC_FLAG_OWN | + MAX_RX_SIZE); + if (priv->rx_idx == 31) + entry->flags |= cpu_to_le32(RTL818X_RX_DESC_FLAG_EOR); + priv->rx_idx = (priv->rx_idx + 1) % 32; + } +} + +static void rtl8180_handle_tx(struct ieee80211_hw *dev, unsigned int prio) +{ + struct rtl8180_priv *priv = dev->priv; + struct rtl8180_tx_ring *ring = &priv->tx_ring[prio]; + + while (skb_queue_len(&ring->queue)) { + struct rtl8180_tx_desc *entry = &ring->desc[ring->idx]; + struct sk_buff *skb; + struct ieee80211_tx_info *info; + u32 flags = le32_to_cpu(entry->flags); + + if (flags & RTL818X_TX_DESC_FLAG_OWN) + return; + + ring->idx = (ring->idx + 1) % ring->entries; + skb = __skb_dequeue(&ring->queue); + pci_unmap_single(priv->pdev, le32_to_cpu(entry->tx_buf), + skb->len, PCI_DMA_TODEVICE); + + info = IEEE80211_SKB_CB(skb); + ieee80211_tx_info_clear_status(info); + + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK) && + (flags & RTL818X_TX_DESC_FLAG_TX_OK)) + info->flags |= IEEE80211_TX_STAT_ACK; + + info->status.rates[0].count = (flags & 0xFF) + 1; + info->status.rates[1].idx = -1; + + ieee80211_tx_status_irqsafe(dev, skb); + if (ring->entries - skb_queue_len(&ring->queue) == 2) + ieee80211_wake_queue(dev, prio); + } +} + +static irqreturn_t rtl8180_interrupt(int irq, void *dev_id) +{ + struct ieee80211_hw *dev = dev_id; + struct rtl8180_priv *priv = dev->priv; + u16 reg; + + spin_lock(&priv->lock); + reg = rtl818x_ioread16(priv, &priv->map->INT_STATUS); + if (unlikely(reg == 0xFFFF)) { + spin_unlock(&priv->lock); + return IRQ_HANDLED; + } + + rtl818x_iowrite16(priv, &priv->map->INT_STATUS, reg); + + if (reg & (RTL818X_INT_TXB_OK | RTL818X_INT_TXB_ERR)) + rtl8180_handle_tx(dev, 3); + + if (reg & (RTL818X_INT_TXH_OK | RTL818X_INT_TXH_ERR)) + rtl8180_handle_tx(dev, 2); + + if (reg & (RTL818X_INT_TXN_OK | RTL818X_INT_TXN_ERR)) + rtl8180_handle_tx(dev, 1); + + if (reg & (RTL818X_INT_TXL_OK | RTL818X_INT_TXL_ERR)) + rtl8180_handle_tx(dev, 0); + + if (reg & (RTL818X_INT_RX_OK | RTL818X_INT_RX_ERR)) + rtl8180_handle_rx(dev); + + spin_unlock(&priv->lock); + + return IRQ_HANDLED; +} + +static void rtl8180_tx(struct ieee80211_hw *dev, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct rtl8180_priv *priv = dev->priv; + struct rtl8180_tx_ring *ring; + struct rtl8180_tx_desc *entry; + unsigned long flags; + unsigned int idx, prio; + dma_addr_t mapping; + u32 tx_flags; + u8 rc_flags; + u16 plcp_len = 0; + __le16 rts_duration = 0; + + prio = skb_get_queue_mapping(skb); + ring = &priv->tx_ring[prio]; + + mapping = pci_map_single(priv->pdev, skb->data, + skb->len, PCI_DMA_TODEVICE); + + tx_flags = RTL818X_TX_DESC_FLAG_OWN | RTL818X_TX_DESC_FLAG_FS | + RTL818X_TX_DESC_FLAG_LS | + (ieee80211_get_tx_rate(dev, info)->hw_value << 24) | + skb->len; + + if (priv->r8185) + tx_flags |= RTL818X_TX_DESC_FLAG_DMA | + RTL818X_TX_DESC_FLAG_NO_ENC; + + rc_flags = info->control.rates[0].flags; + if (rc_flags & IEEE80211_TX_RC_USE_RTS_CTS) { + tx_flags |= RTL818X_TX_DESC_FLAG_RTS; + tx_flags |= ieee80211_get_rts_cts_rate(dev, info)->hw_value << 19; + } else if (rc_flags & IEEE80211_TX_RC_USE_CTS_PROTECT) { + tx_flags |= RTL818X_TX_DESC_FLAG_CTS; + tx_flags |= ieee80211_get_rts_cts_rate(dev, info)->hw_value << 19; + } + + if (rc_flags & IEEE80211_TX_RC_USE_RTS_CTS) + rts_duration = ieee80211_rts_duration(dev, priv->vif, skb->len, + info); + + if (!priv->r8185) { + unsigned int remainder; + + plcp_len = DIV_ROUND_UP(16 * (skb->len + 4), + (ieee80211_get_tx_rate(dev, info)->bitrate * 2) / 10); + remainder = (16 * (skb->len + 4)) % + ((ieee80211_get_tx_rate(dev, info)->bitrate * 2) / 10); + if (remainder <= 6) + plcp_len |= 1 << 15; + } + + spin_lock_irqsave(&priv->lock, flags); + + if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) { + if (info->flags & IEEE80211_TX_CTL_FIRST_FRAGMENT) + priv->seqno += 0x10; + hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); + hdr->seq_ctrl |= cpu_to_le16(priv->seqno); + } + + idx = (ring->idx + skb_queue_len(&ring->queue)) % ring->entries; + entry = &ring->desc[idx]; + + entry->rts_duration = rts_duration; + entry->plcp_len = cpu_to_le16(plcp_len); + entry->tx_buf = cpu_to_le32(mapping); + entry->frame_len = cpu_to_le32(skb->len); + entry->flags2 = info->control.rates[1].idx >= 0 ? + ieee80211_get_alt_retry_rate(dev, info, 0)->bitrate << 4 : 0; + entry->retry_limit = info->control.rates[0].count; + entry->flags = cpu_to_le32(tx_flags); + __skb_queue_tail(&ring->queue, skb); + if (ring->entries - skb_queue_len(&ring->queue) < 2) + ieee80211_stop_queue(dev, prio); + + spin_unlock_irqrestore(&priv->lock, flags); + + rtl818x_iowrite8(priv, &priv->map->TX_DMA_POLLING, (1 << (prio + 4))); +} + +void rtl8180_set_anaparam(struct rtl8180_priv *priv, u32 anaparam) +{ + u8 reg; + + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_CONFIG); + reg = rtl818x_ioread8(priv, &priv->map->CONFIG3); + rtl818x_iowrite8(priv, &priv->map->CONFIG3, + reg | RTL818X_CONFIG3_ANAPARAM_WRITE); + rtl818x_iowrite32(priv, &priv->map->ANAPARAM, anaparam); + rtl818x_iowrite8(priv, &priv->map->CONFIG3, + reg & ~RTL818X_CONFIG3_ANAPARAM_WRITE); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_NORMAL); +} + +static int rtl8180_init_hw(struct ieee80211_hw *dev) +{ + struct rtl8180_priv *priv = dev->priv; + u16 reg; + + rtl818x_iowrite8(priv, &priv->map->CMD, 0); + rtl818x_ioread8(priv, &priv->map->CMD); + msleep(10); + + /* reset */ + rtl818x_iowrite16(priv, &priv->map->INT_MASK, 0); + rtl818x_ioread8(priv, &priv->map->CMD); + + reg = rtl818x_ioread8(priv, &priv->map->CMD); + reg &= (1 << 1); + reg |= RTL818X_CMD_RESET; + rtl818x_iowrite8(priv, &priv->map->CMD, RTL818X_CMD_RESET); + rtl818x_ioread8(priv, &priv->map->CMD); + msleep(200); + + /* check success of reset */ + if (rtl818x_ioread8(priv, &priv->map->CMD) & RTL818X_CMD_RESET) { + wiphy_err(dev->wiphy, "reset timeout!\n"); + return -ETIMEDOUT; + } + + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_LOAD); + rtl818x_ioread8(priv, &priv->map->CMD); + msleep(200); + + if (rtl818x_ioread8(priv, &priv->map->CONFIG3) & (1 << 3)) { + /* For cardbus */ + reg = rtl818x_ioread8(priv, &priv->map->CONFIG3); + reg |= 1 << 1; + rtl818x_iowrite8(priv, &priv->map->CONFIG3, reg); + reg = rtl818x_ioread16(priv, &priv->map->FEMR); + reg |= (1 << 15) | (1 << 14) | (1 << 4); + rtl818x_iowrite16(priv, &priv->map->FEMR, reg); + } + + rtl818x_iowrite8(priv, &priv->map->MSR, 0); + + if (!priv->r8185) + rtl8180_set_anaparam(priv, priv->anaparam); + + rtl818x_iowrite32(priv, &priv->map->RDSAR, priv->rx_ring_dma); + rtl818x_iowrite32(priv, &priv->map->TBDA, priv->tx_ring[3].dma); + rtl818x_iowrite32(priv, &priv->map->THPDA, priv->tx_ring[2].dma); + rtl818x_iowrite32(priv, &priv->map->TNPDA, priv->tx_ring[1].dma); + rtl818x_iowrite32(priv, &priv->map->TLPDA, priv->tx_ring[0].dma); + + /* TODO: necessary? specs indicate not */ + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_CONFIG); + reg = rtl818x_ioread8(priv, &priv->map->CONFIG2); + rtl818x_iowrite8(priv, &priv->map->CONFIG2, reg & ~(1 << 3)); + if (priv->r8185) { + reg = rtl818x_ioread8(priv, &priv->map->CONFIG2); + rtl818x_iowrite8(priv, &priv->map->CONFIG2, reg | (1 << 4)); + } + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_NORMAL); + + /* TODO: set CONFIG5 for calibrating AGC on rtl8180 + philips radio? */ + + /* TODO: turn off hw wep on rtl8180 */ + + rtl818x_iowrite32(priv, &priv->map->INT_TIMEOUT, 0); + + if (priv->r8185) { + rtl818x_iowrite8(priv, &priv->map->WPA_CONF, 0); + rtl818x_iowrite8(priv, &priv->map->RATE_FALLBACK, 0x81); + rtl818x_iowrite8(priv, &priv->map->RESP_RATE, (8 << 4) | 0); + + rtl818x_iowrite16(priv, &priv->map->BRSR, 0x01F3); + + /* TODO: set ClkRun enable? necessary? */ + reg = rtl818x_ioread8(priv, &priv->map->GP_ENABLE); + rtl818x_iowrite8(priv, &priv->map->GP_ENABLE, reg & ~(1 << 6)); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_CONFIG); + reg = rtl818x_ioread8(priv, &priv->map->CONFIG3); + rtl818x_iowrite8(priv, &priv->map->CONFIG3, reg | (1 << 2)); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_NORMAL); + } else { + rtl818x_iowrite16(priv, &priv->map->BRSR, 0x1); + rtl818x_iowrite8(priv, &priv->map->SECURITY, 0); + + rtl818x_iowrite8(priv, &priv->map->PHY_DELAY, 0x6); + rtl818x_iowrite8(priv, &priv->map->CARRIER_SENSE_COUNTER, 0x4C); + } + + priv->rf->init(dev); + if (priv->r8185) + rtl818x_iowrite16(priv, &priv->map->BRSR, 0x01F3); + return 0; +} + +static int rtl8180_init_rx_ring(struct ieee80211_hw *dev) +{ + struct rtl8180_priv *priv = dev->priv; + struct rtl8180_rx_desc *entry; + int i; + + priv->rx_ring = pci_alloc_consistent(priv->pdev, + sizeof(*priv->rx_ring) * 32, + &priv->rx_ring_dma); + + if (!priv->rx_ring || (unsigned long)priv->rx_ring & 0xFF) { + wiphy_err(dev->wiphy, "Cannot allocate RX ring\n"); + return -ENOMEM; + } + + memset(priv->rx_ring, 0, sizeof(*priv->rx_ring) * 32); + priv->rx_idx = 0; + + for (i = 0; i < 32; i++) { + struct sk_buff *skb = dev_alloc_skb(MAX_RX_SIZE); + dma_addr_t *mapping; + entry = &priv->rx_ring[i]; + if (!skb) + return 0; + + priv->rx_buf[i] = skb; + mapping = (dma_addr_t *)skb->cb; + *mapping = pci_map_single(priv->pdev, skb_tail_pointer(skb), + MAX_RX_SIZE, PCI_DMA_FROMDEVICE); + entry->rx_buf = cpu_to_le32(*mapping); + entry->flags = cpu_to_le32(RTL818X_RX_DESC_FLAG_OWN | + MAX_RX_SIZE); + } + entry->flags |= cpu_to_le32(RTL818X_RX_DESC_FLAG_EOR); + return 0; +} + +static void rtl8180_free_rx_ring(struct ieee80211_hw *dev) +{ + struct rtl8180_priv *priv = dev->priv; + int i; + + for (i = 0; i < 32; i++) { + struct sk_buff *skb = priv->rx_buf[i]; + if (!skb) + continue; + + pci_unmap_single(priv->pdev, + *((dma_addr_t *)skb->cb), + MAX_RX_SIZE, PCI_DMA_FROMDEVICE); + kfree_skb(skb); + } + + pci_free_consistent(priv->pdev, sizeof(*priv->rx_ring) * 32, + priv->rx_ring, priv->rx_ring_dma); + priv->rx_ring = NULL; +} + +static int rtl8180_init_tx_ring(struct ieee80211_hw *dev, + unsigned int prio, unsigned int entries) +{ + struct rtl8180_priv *priv = dev->priv; + struct rtl8180_tx_desc *ring; + dma_addr_t dma; + int i; + + ring = pci_alloc_consistent(priv->pdev, sizeof(*ring) * entries, &dma); + if (!ring || (unsigned long)ring & 0xFF) { + wiphy_err(dev->wiphy, "Cannot allocate TX ring (prio = %d)\n", + prio); + return -ENOMEM; + } + + memset(ring, 0, sizeof(*ring)*entries); + priv->tx_ring[prio].desc = ring; + priv->tx_ring[prio].dma = dma; + priv->tx_ring[prio].idx = 0; + priv->tx_ring[prio].entries = entries; + skb_queue_head_init(&priv->tx_ring[prio].queue); + + for (i = 0; i < entries; i++) + ring[i].next_tx_desc = + cpu_to_le32((u32)dma + ((i + 1) % entries) * sizeof(*ring)); + + return 0; +} + +static void rtl8180_free_tx_ring(struct ieee80211_hw *dev, unsigned int prio) +{ + struct rtl8180_priv *priv = dev->priv; + struct rtl8180_tx_ring *ring = &priv->tx_ring[prio]; + + while (skb_queue_len(&ring->queue)) { + struct rtl8180_tx_desc *entry = &ring->desc[ring->idx]; + struct sk_buff *skb = __skb_dequeue(&ring->queue); + + pci_unmap_single(priv->pdev, le32_to_cpu(entry->tx_buf), + skb->len, PCI_DMA_TODEVICE); + kfree_skb(skb); + ring->idx = (ring->idx + 1) % ring->entries; + } + + pci_free_consistent(priv->pdev, sizeof(*ring->desc)*ring->entries, + ring->desc, ring->dma); + ring->desc = NULL; +} + +static int rtl8180_start(struct ieee80211_hw *dev) +{ + struct rtl8180_priv *priv = dev->priv; + int ret, i; + u32 reg; + + ret = rtl8180_init_rx_ring(dev); + if (ret) + return ret; + + for (i = 0; i < 4; i++) + if ((ret = rtl8180_init_tx_ring(dev, i, 16))) + goto err_free_rings; + + ret = rtl8180_init_hw(dev); + if (ret) + goto err_free_rings; + + rtl818x_iowrite32(priv, &priv->map->RDSAR, priv->rx_ring_dma); + rtl818x_iowrite32(priv, &priv->map->TBDA, priv->tx_ring[3].dma); + rtl818x_iowrite32(priv, &priv->map->THPDA, priv->tx_ring[2].dma); + rtl818x_iowrite32(priv, &priv->map->TNPDA, priv->tx_ring[1].dma); + rtl818x_iowrite32(priv, &priv->map->TLPDA, priv->tx_ring[0].dma); + + ret = request_irq(priv->pdev->irq, rtl8180_interrupt, + IRQF_SHARED, KBUILD_MODNAME, dev); + if (ret) { + wiphy_err(dev->wiphy, "failed to register IRQ handler\n"); + goto err_free_rings; + } + + rtl818x_iowrite16(priv, &priv->map->INT_MASK, 0xFFFF); + + rtl818x_iowrite32(priv, &priv->map->MAR[0], ~0); + rtl818x_iowrite32(priv, &priv->map->MAR[1], ~0); + + reg = RTL818X_RX_CONF_ONLYERLPKT | + RTL818X_RX_CONF_RX_AUTORESETPHY | + RTL818X_RX_CONF_MGMT | + RTL818X_RX_CONF_DATA | + (7 << 8 /* MAX RX DMA */) | + RTL818X_RX_CONF_BROADCAST | + RTL818X_RX_CONF_NICMAC; + + if (priv->r8185) + reg |= RTL818X_RX_CONF_CSDM1 | RTL818X_RX_CONF_CSDM2; + else { + reg |= (priv->rfparam & RF_PARAM_CARRIERSENSE1) + ? RTL818X_RX_CONF_CSDM1 : 0; + reg |= (priv->rfparam & RF_PARAM_CARRIERSENSE2) + ? RTL818X_RX_CONF_CSDM2 : 0; + } + + priv->rx_conf = reg; + rtl818x_iowrite32(priv, &priv->map->RX_CONF, reg); + + if (priv->r8185) { + reg = rtl818x_ioread8(priv, &priv->map->CW_CONF); + reg &= ~RTL818X_CW_CONF_PERPACKET_CW_SHIFT; + reg |= RTL818X_CW_CONF_PERPACKET_RETRY_SHIFT; + rtl818x_iowrite8(priv, &priv->map->CW_CONF, reg); + + reg = rtl818x_ioread8(priv, &priv->map->TX_AGC_CTL); + reg &= ~RTL818X_TX_AGC_CTL_PERPACKET_GAIN_SHIFT; + reg &= ~RTL818X_TX_AGC_CTL_PERPACKET_ANTSEL_SHIFT; + reg |= RTL818X_TX_AGC_CTL_FEEDBACK_ANT; + rtl818x_iowrite8(priv, &priv->map->TX_AGC_CTL, reg); + + /* disable early TX */ + rtl818x_iowrite8(priv, (u8 __iomem *)priv->map + 0xec, 0x3f); + } + + reg = rtl818x_ioread32(priv, &priv->map->TX_CONF); + reg |= (6 << 21 /* MAX TX DMA */) | + RTL818X_TX_CONF_NO_ICV; + + if (priv->r8185) + reg &= ~RTL818X_TX_CONF_PROBE_DTS; + else + reg &= ~RTL818X_TX_CONF_HW_SEQNUM; + + /* different meaning, same value on both rtl8185 and rtl8180 */ + reg &= ~RTL818X_TX_CONF_SAT_HWPLCP; + + rtl818x_iowrite32(priv, &priv->map->TX_CONF, reg); + + reg = rtl818x_ioread8(priv, &priv->map->CMD); + reg |= RTL818X_CMD_RX_ENABLE; + reg |= RTL818X_CMD_TX_ENABLE; + rtl818x_iowrite8(priv, &priv->map->CMD, reg); + + return 0; + + err_free_rings: + rtl8180_free_rx_ring(dev); + for (i = 0; i < 4; i++) + if (priv->tx_ring[i].desc) + rtl8180_free_tx_ring(dev, i); + + return ret; +} + +static void rtl8180_stop(struct ieee80211_hw *dev) +{ + struct rtl8180_priv *priv = dev->priv; + u8 reg; + int i; + + rtl818x_iowrite16(priv, &priv->map->INT_MASK, 0); + + reg = rtl818x_ioread8(priv, &priv->map->CMD); + reg &= ~RTL818X_CMD_TX_ENABLE; + reg &= ~RTL818X_CMD_RX_ENABLE; + rtl818x_iowrite8(priv, &priv->map->CMD, reg); + + priv->rf->stop(dev); + + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_CONFIG); + reg = rtl818x_ioread8(priv, &priv->map->CONFIG4); + rtl818x_iowrite8(priv, &priv->map->CONFIG4, reg | RTL818X_CONFIG4_VCOOFF); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_NORMAL); + + free_irq(priv->pdev->irq, dev); + + rtl8180_free_rx_ring(dev); + for (i = 0; i < 4; i++) + rtl8180_free_tx_ring(dev, i); +} + +static u64 rtl8180_get_tsf(struct ieee80211_hw *dev, + struct ieee80211_vif *vif) +{ + struct rtl8180_priv *priv = dev->priv; + + return rtl818x_ioread32(priv, &priv->map->TSFT[0]) | + (u64)(rtl818x_ioread32(priv, &priv->map->TSFT[1])) << 32; +} + +static void rtl8180_beacon_work(struct work_struct *work) +{ + struct rtl8180_vif *vif_priv = + container_of(work, struct rtl8180_vif, beacon_work.work); + struct ieee80211_vif *vif = + container_of((void *)vif_priv, struct ieee80211_vif, drv_priv); + struct ieee80211_hw *dev = vif_priv->dev; + struct ieee80211_mgmt *mgmt; + struct sk_buff *skb; + + /* don't overflow the tx ring */ + if (ieee80211_queue_stopped(dev, 0)) + goto resched; + + /* grab a fresh beacon */ + skb = ieee80211_beacon_get(dev, vif); + if (!skb) + goto resched; + + /* + * update beacon timestamp w/ TSF value + * TODO: make hardware update beacon timestamp + */ + mgmt = (struct ieee80211_mgmt *)skb->data; + mgmt->u.beacon.timestamp = cpu_to_le64(rtl8180_get_tsf(dev, vif)); + + /* TODO: use actual beacon queue */ + skb_set_queue_mapping(skb, 0); + + rtl8180_tx(dev, NULL, skb); + +resched: + /* + * schedule next beacon + * TODO: use hardware support for beacon timing + */ + schedule_delayed_work(&vif_priv->beacon_work, + usecs_to_jiffies(1024 * vif->bss_conf.beacon_int)); +} + +static int rtl8180_add_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif) +{ + struct rtl8180_priv *priv = dev->priv; + struct rtl8180_vif *vif_priv; + + /* + * We only support one active interface at a time. + */ + if (priv->vif) + return -EBUSY; + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: + break; + default: + return -EOPNOTSUPP; + } + + priv->vif = vif; + + /* Initialize driver private area */ + vif_priv = (struct rtl8180_vif *)&vif->drv_priv; + vif_priv->dev = dev; + INIT_DELAYED_WORK(&vif_priv->beacon_work, rtl8180_beacon_work); + vif_priv->enable_beacon = false; + + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_CONFIG); + rtl818x_iowrite32(priv, (__le32 __iomem *)&priv->map->MAC[0], + le32_to_cpu(*(__le32 *)vif->addr)); + rtl818x_iowrite16(priv, (__le16 __iomem *)&priv->map->MAC[4], + le16_to_cpu(*(__le16 *)(vif->addr + 4))); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_NORMAL); + + return 0; +} + +static void rtl8180_remove_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif) +{ + struct rtl8180_priv *priv = dev->priv; + priv->vif = NULL; +} + +static int rtl8180_config(struct ieee80211_hw *dev, u32 changed) +{ + struct rtl8180_priv *priv = dev->priv; + struct ieee80211_conf *conf = &dev->conf; + + priv->rf->set_chan(dev, conf); + + return 0; +} + +static void rtl8180_bss_info_changed(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u32 changed) +{ + struct rtl8180_priv *priv = dev->priv; + struct rtl8180_vif *vif_priv; + int i; + u8 reg; + + vif_priv = (struct rtl8180_vif *)&vif->drv_priv; + + if (changed & BSS_CHANGED_BSSID) { + for (i = 0; i < ETH_ALEN; i++) + rtl818x_iowrite8(priv, &priv->map->BSSID[i], + info->bssid[i]); + + if (is_valid_ether_addr(info->bssid)) { + if (vif->type == NL80211_IFTYPE_ADHOC) + reg = RTL818X_MSR_ADHOC; + else + reg = RTL818X_MSR_INFRA; + } else + reg = RTL818X_MSR_NO_LINK; + rtl818x_iowrite8(priv, &priv->map->MSR, reg); + } + + if (changed & BSS_CHANGED_ERP_SLOT && priv->rf->conf_erp) + priv->rf->conf_erp(dev, info); + + if (changed & BSS_CHANGED_BEACON_ENABLED) + vif_priv->enable_beacon = info->enable_beacon; + + if (changed & (BSS_CHANGED_BEACON_ENABLED | BSS_CHANGED_BEACON)) { + cancel_delayed_work_sync(&vif_priv->beacon_work); + if (vif_priv->enable_beacon) + schedule_work(&vif_priv->beacon_work.work); + } +} + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) +static u64 rtl8180_prepare_multicast(struct ieee80211_hw *dev, + struct netdev_hw_addr_list *mc_list) +#else +static u64 rtl8180_prepare_multicast(struct ieee80211_hw *dev, int mc_count, + struct dev_addr_list *mc_list) +#endif +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + return netdev_hw_addr_list_count(mc_list); +#else + return mc_count; +#endif +} + +static void rtl8180_configure_filter(struct ieee80211_hw *dev, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + struct rtl8180_priv *priv = dev->priv; + + if (changed_flags & FIF_FCSFAIL) + priv->rx_conf ^= RTL818X_RX_CONF_FCS; + if (changed_flags & FIF_CONTROL) + priv->rx_conf ^= RTL818X_RX_CONF_CTRL; + if (changed_flags & FIF_OTHER_BSS) + priv->rx_conf ^= RTL818X_RX_CONF_MONITOR; + if (*total_flags & FIF_ALLMULTI || multicast > 0) + priv->rx_conf |= RTL818X_RX_CONF_MULTICAST; + else + priv->rx_conf &= ~RTL818X_RX_CONF_MULTICAST; + + *total_flags = 0; + + if (priv->rx_conf & RTL818X_RX_CONF_FCS) + *total_flags |= FIF_FCSFAIL; + if (priv->rx_conf & RTL818X_RX_CONF_CTRL) + *total_flags |= FIF_CONTROL; + if (priv->rx_conf & RTL818X_RX_CONF_MONITOR) + *total_flags |= FIF_OTHER_BSS; + if (priv->rx_conf & RTL818X_RX_CONF_MULTICAST) + *total_flags |= FIF_ALLMULTI; + + rtl818x_iowrite32(priv, &priv->map->RX_CONF, priv->rx_conf); +} + +static const struct ieee80211_ops rtl8180_ops = { + .tx = rtl8180_tx, + .start = rtl8180_start, + .stop = rtl8180_stop, + .add_interface = rtl8180_add_interface, + .remove_interface = rtl8180_remove_interface, + .config = rtl8180_config, + .bss_info_changed = rtl8180_bss_info_changed, + .prepare_multicast = rtl8180_prepare_multicast, + .configure_filter = rtl8180_configure_filter, + .get_tsf = rtl8180_get_tsf, +}; + +static void rtl8180_eeprom_register_read(struct eeprom_93cx6 *eeprom) +{ + struct ieee80211_hw *dev = eeprom->data; + struct rtl8180_priv *priv = dev->priv; + u8 reg = rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + + eeprom->reg_data_in = reg & RTL818X_EEPROM_CMD_WRITE; + eeprom->reg_data_out = reg & RTL818X_EEPROM_CMD_READ; + eeprom->reg_data_clock = reg & RTL818X_EEPROM_CMD_CK; + eeprom->reg_chip_select = reg & RTL818X_EEPROM_CMD_CS; +} + +static void rtl8180_eeprom_register_write(struct eeprom_93cx6 *eeprom) +{ + struct ieee80211_hw *dev = eeprom->data; + struct rtl8180_priv *priv = dev->priv; + u8 reg = 2 << 6; + + if (eeprom->reg_data_in) + reg |= RTL818X_EEPROM_CMD_WRITE; + if (eeprom->reg_data_out) + reg |= RTL818X_EEPROM_CMD_READ; + if (eeprom->reg_data_clock) + reg |= RTL818X_EEPROM_CMD_CK; + if (eeprom->reg_chip_select) + reg |= RTL818X_EEPROM_CMD_CS; + + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, reg); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(10); +} + +static int rtl8180_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct ieee80211_hw *dev; + struct rtl8180_priv *priv; + unsigned long mem_addr, mem_len; + unsigned int io_addr, io_len; + int err, i; + struct eeprom_93cx6 eeprom; + const char *chip_name, *rf_name = NULL; + u32 reg; + u16 eeprom_val; + u8 mac_addr[ETH_ALEN]; + + err = pci_enable_device(pdev); + if (err) { + printk(KERN_ERR "%s (rtl8180): Cannot enable new PCI device\n", + pci_name(pdev)); + return err; + } + + err = pci_request_regions(pdev, KBUILD_MODNAME); + if (err) { + printk(KERN_ERR "%s (rtl8180): Cannot obtain PCI resources\n", + pci_name(pdev)); + return err; + } + + io_addr = pci_resource_start(pdev, 0); + io_len = pci_resource_len(pdev, 0); + mem_addr = pci_resource_start(pdev, 1); + mem_len = pci_resource_len(pdev, 1); + + if (mem_len < sizeof(struct rtl818x_csr) || + io_len < sizeof(struct rtl818x_csr)) { + printk(KERN_ERR "%s (rtl8180): Too short PCI resources\n", + pci_name(pdev)); + err = -ENOMEM; + goto err_free_reg; + } + + if ((err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32))) || + (err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)))) { + printk(KERN_ERR "%s (rtl8180): No suitable DMA available\n", + pci_name(pdev)); + goto err_free_reg; + } + + pci_set_master(pdev); + + dev = ieee80211_alloc_hw(sizeof(*priv), &rtl8180_ops); + if (!dev) { + printk(KERN_ERR "%s (rtl8180): ieee80211 alloc failed\n", + pci_name(pdev)); + err = -ENOMEM; + goto err_free_reg; + } + + priv = dev->priv; + priv->pdev = pdev; + + dev->max_rates = 2; + SET_IEEE80211_DEV(dev, &pdev->dev); + pci_set_drvdata(pdev, dev); + + priv->map = pci_iomap(pdev, 1, mem_len); + if (!priv->map) + priv->map = pci_iomap(pdev, 0, io_len); + + if (!priv->map) { + printk(KERN_ERR "%s (rtl8180): Cannot map device memory\n", + pci_name(pdev)); + goto err_free_dev; + } + + BUILD_BUG_ON(sizeof(priv->channels) != sizeof(rtl818x_channels)); + BUILD_BUG_ON(sizeof(priv->rates) != sizeof(rtl818x_rates)); + + memcpy(priv->channels, rtl818x_channels, sizeof(rtl818x_channels)); + memcpy(priv->rates, rtl818x_rates, sizeof(rtl818x_rates)); + + priv->band.band = IEEE80211_BAND_2GHZ; + priv->band.channels = priv->channels; + priv->band.n_channels = ARRAY_SIZE(rtl818x_channels); + priv->band.bitrates = priv->rates; + priv->band.n_bitrates = 4; + dev->wiphy->bands[IEEE80211_BAND_2GHZ] = &priv->band; + + dev->flags = IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING | + IEEE80211_HW_RX_INCLUDES_FCS | + IEEE80211_HW_SIGNAL_UNSPEC; + dev->vif_data_size = sizeof(struct rtl8180_vif); + dev->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC); + dev->queues = 1; + dev->max_signal = 65; + + reg = rtl818x_ioread32(priv, &priv->map->TX_CONF); + reg &= RTL818X_TX_CONF_HWVER_MASK; + switch (reg) { + case RTL818X_TX_CONF_R8180_ABCD: + chip_name = "RTL8180"; + break; + case RTL818X_TX_CONF_R8180_F: + chip_name = "RTL8180vF"; + break; + case RTL818X_TX_CONF_R8185_ABC: + chip_name = "RTL8185"; + break; + case RTL818X_TX_CONF_R8185_D: + chip_name = "RTL8185vD"; + break; + default: + printk(KERN_ERR "%s (rtl8180): Unknown chip! (0x%x)\n", + pci_name(pdev), reg >> 25); + goto err_iounmap; + } + + priv->r8185 = reg & RTL818X_TX_CONF_R8185_ABC; + if (priv->r8185) { + priv->band.n_bitrates = ARRAY_SIZE(rtl818x_rates); + pci_try_set_mwi(pdev); + } + + eeprom.data = dev; + eeprom.register_read = rtl8180_eeprom_register_read; + eeprom.register_write = rtl8180_eeprom_register_write; + if (rtl818x_ioread32(priv, &priv->map->RX_CONF) & (1 << 6)) + eeprom.width = PCI_EEPROM_WIDTH_93C66; + else + eeprom.width = PCI_EEPROM_WIDTH_93C46; + + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_PROGRAM); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(10); + + eeprom_93cx6_read(&eeprom, 0x06, &eeprom_val); + eeprom_val &= 0xFF; + switch (eeprom_val) { + case 1: rf_name = "Intersil"; + break; + case 2: rf_name = "RFMD"; + break; + case 3: priv->rf = &sa2400_rf_ops; + break; + case 4: priv->rf = &max2820_rf_ops; + break; + case 5: priv->rf = &grf5101_rf_ops; + break; + case 9: priv->rf = rtl8180_detect_rf(dev); + break; + case 10: + rf_name = "RTL8255"; + break; + default: + printk(KERN_ERR "%s (rtl8180): Unknown RF! (0x%x)\n", + pci_name(pdev), eeprom_val); + goto err_iounmap; + } + + if (!priv->rf) { + printk(KERN_ERR "%s (rtl8180): %s RF frontend not supported!\n", + pci_name(pdev), rf_name); + goto err_iounmap; + } + + eeprom_93cx6_read(&eeprom, 0x17, &eeprom_val); + priv->csthreshold = eeprom_val >> 8; + if (!priv->r8185) { + __le32 anaparam; + eeprom_93cx6_multiread(&eeprom, 0xD, (__le16 *)&anaparam, 2); + priv->anaparam = le32_to_cpu(anaparam); + eeprom_93cx6_read(&eeprom, 0x19, &priv->rfparam); + } + + eeprom_93cx6_multiread(&eeprom, 0x7, (__le16 *)mac_addr, 3); + if (!is_valid_ether_addr(mac_addr)) { + printk(KERN_WARNING "%s (rtl8180): Invalid hwaddr! Using" + " randomly generated MAC addr\n", pci_name(pdev)); + eth_random_addr(mac_addr); + } + SET_IEEE80211_PERM_ADDR(dev, mac_addr); + + /* CCK TX power */ + for (i = 0; i < 14; i += 2) { + u16 txpwr; + eeprom_93cx6_read(&eeprom, 0x10 + (i >> 1), &txpwr); + priv->channels[i].hw_value = txpwr & 0xFF; + priv->channels[i + 1].hw_value = txpwr >> 8; + } + + /* OFDM TX power */ + if (priv->r8185) { + for (i = 0; i < 14; i += 2) { + u16 txpwr; + eeprom_93cx6_read(&eeprom, 0x20 + (i >> 1), &txpwr); + priv->channels[i].hw_value |= (txpwr & 0xFF) << 8; + priv->channels[i + 1].hw_value |= txpwr & 0xFF00; + } + } + + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_NORMAL); + + spin_lock_init(&priv->lock); + + err = ieee80211_register_hw(dev); + if (err) { + printk(KERN_ERR "%s (rtl8180): Cannot register device\n", + pci_name(pdev)); + goto err_iounmap; + } + + wiphy_info(dev->wiphy, "hwaddr %pm, %s + %s\n", + mac_addr, chip_name, priv->rf->name); + + return 0; + + err_iounmap: + iounmap(priv->map); + + err_free_dev: + pci_set_drvdata(pdev, NULL); + ieee80211_free_hw(dev); + + err_free_reg: + pci_release_regions(pdev); + pci_disable_device(pdev); + return err; +} + +static void rtl8180_remove(struct pci_dev *pdev) +{ + struct ieee80211_hw *dev = pci_get_drvdata(pdev); + struct rtl8180_priv *priv; + + if (!dev) + return; + + ieee80211_unregister_hw(dev); + + priv = dev->priv; + + pci_iounmap(pdev, priv->map); + pci_release_regions(pdev); + pci_disable_device(pdev); + ieee80211_free_hw(dev); +} + +#ifdef CONFIG_PM +static int rtl8180_suspend(struct pci_dev *pdev, pm_message_t state) +{ + pci_save_state(pdev); + pci_set_power_state(pdev, pci_choose_state(pdev, state)); + return 0; +} + +static int rtl8180_resume(struct pci_dev *pdev) +{ + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + return 0; +} + +#endif /* CONFIG_PM */ + +static struct pci_driver rtl8180_driver = { + .name = KBUILD_MODNAME, + .id_table = rtl8180_table, + .probe = rtl8180_probe, + .remove = rtl8180_remove, +#ifdef CONFIG_PM + .suspend = rtl8180_suspend, + .resume = rtl8180_resume, +#endif /* CONFIG_PM */ +}; + +module_pci_driver(rtl8180_driver); diff --git a/drivers/net/wireless/rtl818x/rtl8180/grf5101.c b/drivers/net/wireless/rtl818x/rtl8180/grf5101.c new file mode 100644 index 0000000..077ff92 --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8180/grf5101.c @@ -0,0 +1,191 @@ + +/* + * Radio tuning for GCT GRF5101 on RTL8180 + * + * Copyright 2007 Andrea Merello + * + * Code from the BSD driver and the rtl8181 project have been + * very useful to understand certain things + * + * I want to thanks the Authors of such projects and the Ndiswrapper + * project Authors. + * + * A special Big Thanks also is for all people who donated me cards, + * making possible the creation of the original rtl8180 driver + * from which this code is derived! + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include + +#include "rtl8180.h" +#include "grf5101.h" + +static const int grf5101_encode[] = { + 0x0, 0x8, 0x4, 0xC, + 0x2, 0xA, 0x6, 0xE, + 0x1, 0x9, 0x5, 0xD, + 0x3, 0xB, 0x7, 0xF +}; + +static void write_grf5101(struct ieee80211_hw *dev, u8 addr, u32 data) +{ + struct rtl8180_priv *priv = dev->priv; + u32 phy_config; + + phy_config = grf5101_encode[(data >> 8) & 0xF]; + phy_config |= grf5101_encode[(data >> 4) & 0xF] << 4; + phy_config |= grf5101_encode[data & 0xF] << 8; + phy_config |= grf5101_encode[(addr >> 1) & 0xF] << 12; + phy_config |= (addr & 1) << 16; + phy_config |= grf5101_encode[(data & 0xf000) >> 12] << 24; + + /* MAC will bang bits to the chip */ + phy_config |= 0x90000000; + + rtl818x_iowrite32(priv, + (__le32 __iomem *) &priv->map->RFPinsOutput, phy_config); + + msleep(3); +} + +static void grf5101_write_phy_antenna(struct ieee80211_hw *dev, short chan) +{ + struct rtl8180_priv *priv = dev->priv; + u8 ant = GRF5101_ANTENNA; + + if (priv->rfparam & RF_PARAM_ANTBDEFAULT) + ant |= BB_ANTENNA_B; + + if (chan == 14) + ant |= BB_ANTATTEN_CHAN14; + + rtl8180_write_phy(dev, 0x10, ant); +} + +static u8 grf5101_rf_calc_rssi(u8 agc, u8 sq) +{ + if (agc > 60) + return 65; + + /* TODO(?): just return agc (or agc + 5) to avoid mult / div */ + return 65 * agc / 60; +} + +static void grf5101_rf_set_channel(struct ieee80211_hw *dev, + struct ieee80211_conf *conf) +{ + struct rtl8180_priv *priv = dev->priv; + int channel = + ieee80211_frequency_to_channel(conf->chandef.chan->center_freq); + u32 txpw = priv->channels[channel - 1].hw_value & 0xFF; + u32 chan = channel - 1; + + /* set TX power */ + write_grf5101(dev, 0x15, 0x0); + write_grf5101(dev, 0x06, txpw); + write_grf5101(dev, 0x15, 0x10); + write_grf5101(dev, 0x15, 0x0); + + /* set frequency */ + write_grf5101(dev, 0x07, 0x0); + write_grf5101(dev, 0x0B, chan); + write_grf5101(dev, 0x07, 0x1000); + + grf5101_write_phy_antenna(dev, channel); +} + +static void grf5101_rf_stop(struct ieee80211_hw *dev) +{ + struct rtl8180_priv *priv = dev->priv; + u32 anaparam; + + anaparam = priv->anaparam; + anaparam &= 0x000fffff; + anaparam |= 0x3f900000; + rtl8180_set_anaparam(priv, anaparam); + + write_grf5101(dev, 0x07, 0x0); + write_grf5101(dev, 0x1f, 0x45); + write_grf5101(dev, 0x1f, 0x5); + write_grf5101(dev, 0x00, 0x8e4); +} + +static void grf5101_rf_init(struct ieee80211_hw *dev) +{ + struct rtl8180_priv *priv = dev->priv; + + rtl8180_set_anaparam(priv, priv->anaparam); + + write_grf5101(dev, 0x1f, 0x0); + write_grf5101(dev, 0x1f, 0x0); + write_grf5101(dev, 0x1f, 0x40); + write_grf5101(dev, 0x1f, 0x60); + write_grf5101(dev, 0x1f, 0x61); + write_grf5101(dev, 0x1f, 0x61); + write_grf5101(dev, 0x00, 0xae4); + write_grf5101(dev, 0x1f, 0x1); + write_grf5101(dev, 0x1f, 0x41); + write_grf5101(dev, 0x1f, 0x61); + + write_grf5101(dev, 0x01, 0x1a23); + write_grf5101(dev, 0x02, 0x4971); + write_grf5101(dev, 0x03, 0x41de); + write_grf5101(dev, 0x04, 0x2d80); + write_grf5101(dev, 0x05, 0x68ff); /* 0x61ff original value */ + write_grf5101(dev, 0x06, 0x0); + write_grf5101(dev, 0x07, 0x0); + write_grf5101(dev, 0x08, 0x7533); + write_grf5101(dev, 0x09, 0xc401); + write_grf5101(dev, 0x0a, 0x0); + write_grf5101(dev, 0x0c, 0x1c7); + write_grf5101(dev, 0x0d, 0x29d3); + write_grf5101(dev, 0x0e, 0x2e8); + write_grf5101(dev, 0x10, 0x192); + write_grf5101(dev, 0x11, 0x248); + write_grf5101(dev, 0x12, 0x0); + write_grf5101(dev, 0x13, 0x20c4); + write_grf5101(dev, 0x14, 0xf4fc); + write_grf5101(dev, 0x15, 0x0); + write_grf5101(dev, 0x16, 0x1500); + + write_grf5101(dev, 0x07, 0x1000); + + /* baseband configuration */ + rtl8180_write_phy(dev, 0, 0xa8); + rtl8180_write_phy(dev, 3, 0x0); + rtl8180_write_phy(dev, 4, 0xc0); + rtl8180_write_phy(dev, 5, 0x90); + rtl8180_write_phy(dev, 6, 0x1e); + rtl8180_write_phy(dev, 7, 0x64); + + grf5101_write_phy_antenna(dev, 1); + + rtl8180_write_phy(dev, 0x11, 0x88); + + if (rtl818x_ioread8(priv, &priv->map->CONFIG2) & + RTL818X_CONFIG2_ANTENNA_DIV) + rtl8180_write_phy(dev, 0x12, 0xc0); /* enable ant diversity */ + else + rtl8180_write_phy(dev, 0x12, 0x40); /* disable ant diversity */ + + rtl8180_write_phy(dev, 0x13, 0x90 | priv->csthreshold); + + rtl8180_write_phy(dev, 0x19, 0x0); + rtl8180_write_phy(dev, 0x1a, 0xa0); + rtl8180_write_phy(dev, 0x1b, 0x44); +} + +const struct rtl818x_rf_ops grf5101_rf_ops = { + .name = "GCT", + .init = grf5101_rf_init, + .stop = grf5101_rf_stop, + .set_chan = grf5101_rf_set_channel, + .calc_rssi = grf5101_rf_calc_rssi, +}; diff --git a/drivers/net/wireless/rtl818x/rtl8180/grf5101.h b/drivers/net/wireless/rtl818x/rtl8180/grf5101.h new file mode 100644 index 0000000..7664711 --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8180/grf5101.h @@ -0,0 +1,28 @@ +#ifndef RTL8180_GRF5101_H +#define RTL8180_GRF5101_H + +/* + * Radio tuning for GCT GRF5101 on RTL8180 + * + * Copyright 2007 Andrea Merello + * + * Code from the BSD driver and the rtl8181 project have been + * very useful to understand certain things + * + * I want to thanks the Authors of such projects and the Ndiswrapper + * project Authors. + * + * A special Big Thanks also is for all people who donated me cards, + * making possible the creation of the original rtl8180 driver + * from which this code is derived! + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define GRF5101_ANTENNA 0xA3 + +extern const struct rtl818x_rf_ops grf5101_rf_ops; + +#endif /* RTL8180_GRF5101_H */ diff --git a/drivers/net/wireless/rtl818x/rtl8180/max2820.c b/drivers/net/wireless/rtl818x/rtl8180/max2820.c new file mode 100644 index 0000000..4715000 --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8180/max2820.c @@ -0,0 +1,169 @@ +/* + * Radio tuning for Maxim max2820 on RTL8180 + * + * Copyright 2007 Andrea Merello + * + * Code from the BSD driver and the rtl8181 project have been + * very useful to understand certain things + * + * I want to thanks the Authors of such projects and the Ndiswrapper + * project Authors. + * + * A special Big Thanks also is for all people who donated me cards, + * making possible the creation of the original rtl8180 driver + * from which this code is derived! + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include + +#include "rtl8180.h" +#include "max2820.h" + +static const u32 max2820_chan[] = { + 12, /* CH 1 */ + 17, + 22, + 27, + 32, + 37, + 42, + 47, + 52, + 57, + 62, + 67, + 72, + 84, /* CH 14 */ +}; + +static void write_max2820(struct ieee80211_hw *dev, u8 addr, u32 data) +{ + struct rtl8180_priv *priv = dev->priv; + u32 phy_config; + + phy_config = 0x90 + (data & 0xf); + phy_config <<= 16; + phy_config += addr; + phy_config <<= 8; + phy_config += (data >> 4) & 0xff; + + rtl818x_iowrite32(priv, + (__le32 __iomem *) &priv->map->RFPinsOutput, phy_config); + + msleep(1); +} + +static void max2820_write_phy_antenna(struct ieee80211_hw *dev, short chan) +{ + struct rtl8180_priv *priv = dev->priv; + u8 ant; + + ant = MAXIM_ANTENNA; + if (priv->rfparam & RF_PARAM_ANTBDEFAULT) + ant |= BB_ANTENNA_B; + if (chan == 14) + ant |= BB_ANTATTEN_CHAN14; + + rtl8180_write_phy(dev, 0x10, ant); +} + +static u8 max2820_rf_calc_rssi(u8 agc, u8 sq) +{ + bool odd; + + odd = !!(agc & 1); + + agc >>= 1; + if (odd) + agc += 76; + else + agc += 66; + + /* TODO: change addends above to avoid mult / div below */ + return 65 * agc / 100; +} + +static void max2820_rf_set_channel(struct ieee80211_hw *dev, + struct ieee80211_conf *conf) +{ + struct rtl8180_priv *priv = dev->priv; + int channel = conf ? + ieee80211_frequency_to_channel(conf->chandef.chan->center_freq) : 1; + unsigned int chan_idx = channel - 1; + u32 txpw = priv->channels[chan_idx].hw_value & 0xFF; + u32 chan = max2820_chan[chan_idx]; + + /* While philips SA2400 drive the PA bias from + * sa2400, for MAXIM we do this directly from BB */ + rtl8180_write_phy(dev, 3, txpw); + + max2820_write_phy_antenna(dev, channel); + write_max2820(dev, 3, chan); +} + +static void max2820_rf_stop(struct ieee80211_hw *dev) +{ + rtl8180_write_phy(dev, 3, 0x8); + write_max2820(dev, 1, 0); +} + + +static void max2820_rf_init(struct ieee80211_hw *dev) +{ + struct rtl8180_priv *priv = dev->priv; + + /* MAXIM from netbsd driver */ + write_max2820(dev, 0, 0x007); /* test mode as indicated in datasheet */ + write_max2820(dev, 1, 0x01e); /* enable register */ + write_max2820(dev, 2, 0x001); /* synt register */ + + max2820_rf_set_channel(dev, NULL); + + write_max2820(dev, 4, 0x313); /* rx register */ + + /* PA is driven directly by the BB, we keep the MAXIM bias + * at the highest value in case that setting it to lower + * values may introduce some further attenuation somewhere.. + */ + write_max2820(dev, 5, 0x00f); + + /* baseband configuration */ + rtl8180_write_phy(dev, 0, 0x88); /* sys1 */ + rtl8180_write_phy(dev, 3, 0x08); /* txagc */ + rtl8180_write_phy(dev, 4, 0xf8); /* lnadet */ + rtl8180_write_phy(dev, 5, 0x90); /* ifagcinit */ + rtl8180_write_phy(dev, 6, 0x1a); /* ifagclimit */ + rtl8180_write_phy(dev, 7, 0x64); /* ifagcdet */ + + max2820_write_phy_antenna(dev, 1); + + rtl8180_write_phy(dev, 0x11, 0x88); /* trl */ + + if (rtl818x_ioread8(priv, &priv->map->CONFIG2) & + RTL818X_CONFIG2_ANTENNA_DIV) + rtl8180_write_phy(dev, 0x12, 0xc7); + else + rtl8180_write_phy(dev, 0x12, 0x47); + + rtl8180_write_phy(dev, 0x13, 0x9b); + + rtl8180_write_phy(dev, 0x19, 0x0); /* CHESTLIM */ + rtl8180_write_phy(dev, 0x1a, 0x9f); /* CHSQLIM */ + + max2820_rf_set_channel(dev, NULL); +} + +const struct rtl818x_rf_ops max2820_rf_ops = { + .name = "Maxim", + .init = max2820_rf_init, + .stop = max2820_rf_stop, + .set_chan = max2820_rf_set_channel, + .calc_rssi = max2820_rf_calc_rssi, +}; diff --git a/drivers/net/wireless/rtl818x/rtl8180/max2820.h b/drivers/net/wireless/rtl818x/rtl8180/max2820.h new file mode 100644 index 0000000..61cf6d1 --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8180/max2820.h @@ -0,0 +1,28 @@ +#ifndef RTL8180_MAX2820_H +#define RTL8180_MAX2820_H + +/* + * Radio tuning for Maxim max2820 on RTL8180 + * + * Copyright 2007 Andrea Merello + * + * Code from the BSD driver and the rtl8181 project have been + * very useful to understand certain things + * + * I want to thanks the Authors of such projects and the Ndiswrapper + * project Authors. + * + * A special Big Thanks also is for all people who donated me cards, + * making possible the creation of the original rtl8180 driver + * from which this code is derived! + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define MAXIM_ANTENNA 0xb3 + +extern const struct rtl818x_rf_ops max2820_rf_ops; + +#endif /* RTL8180_MAX2820_H */ diff --git a/drivers/net/wireless/rtl818x/rtl8180/rtl8180.h b/drivers/net/wireless/rtl818x/rtl8180/rtl8180.h new file mode 100644 index 0000000..3052331 --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8180/rtl8180.h @@ -0,0 +1,130 @@ +#ifndef RTL8180_H +#define RTL8180_H + +#include "rtl818x.h" + +#define MAX_RX_SIZE IEEE80211_MAX_RTS_THRESHOLD + +#define RF_PARAM_ANALOGPHY (1 << 0) +#define RF_PARAM_ANTBDEFAULT (1 << 1) +#define RF_PARAM_CARRIERSENSE1 (1 << 2) +#define RF_PARAM_CARRIERSENSE2 (1 << 3) + +#define BB_ANTATTEN_CHAN14 0x0C +#define BB_ANTENNA_B 0x40 + +#define BB_HOST_BANG (1 << 30) +#define BB_HOST_BANG_EN (1 << 2) +#define BB_HOST_BANG_CLK (1 << 1) +#define BB_HOST_BANG_DATA 1 + +#define ANAPARAM_TXDACOFF_SHIFT 27 +#define ANAPARAM_PWR0_SHIFT 28 +#define ANAPARAM_PWR0_MASK (0x07 << ANAPARAM_PWR0_SHIFT) +#define ANAPARAM_PWR1_SHIFT 20 +#define ANAPARAM_PWR1_MASK (0x7F << ANAPARAM_PWR1_SHIFT) + +struct rtl8180_tx_desc { + __le32 flags; + __le16 rts_duration; + __le16 plcp_len; + __le32 tx_buf; + __le32 frame_len; + __le32 next_tx_desc; + u8 cw; + u8 retry_limit; + u8 agc; + u8 flags2; + u32 reserved[2]; +} __packed; + +struct rtl8180_rx_desc { + __le32 flags; + __le32 flags2; + union { + __le32 rx_buf; + __le64 tsft; + }; +} __packed; + +struct rtl8180_tx_ring { + struct rtl8180_tx_desc *desc; + dma_addr_t dma; + unsigned int idx; + unsigned int entries; + struct sk_buff_head queue; +}; + +struct rtl8180_vif { + struct ieee80211_hw *dev; + + /* beaconing */ + struct delayed_work beacon_work; + bool enable_beacon; +}; + +struct rtl8180_priv { + /* common between rtl818x drivers */ + struct rtl818x_csr __iomem *map; + const struct rtl818x_rf_ops *rf; + struct ieee80211_vif *vif; + + /* rtl8180 driver specific */ + spinlock_t lock; + struct rtl8180_rx_desc *rx_ring; + dma_addr_t rx_ring_dma; + unsigned int rx_idx; + struct sk_buff *rx_buf[32]; + struct rtl8180_tx_ring tx_ring[4]; + struct ieee80211_channel channels[14]; + struct ieee80211_rate rates[12]; + struct ieee80211_supported_band band; + struct pci_dev *pdev; + u32 rx_conf; + + int r8185; + u32 anaparam; + u16 rfparam; + u8 csthreshold; + + /* sequence # */ + u16 seqno; +}; + +void rtl8180_write_phy(struct ieee80211_hw *dev, u8 addr, u32 data); +void rtl8180_set_anaparam(struct rtl8180_priv *priv, u32 anaparam); + +static inline u8 rtl818x_ioread8(struct rtl8180_priv *priv, u8 __iomem *addr) +{ + return ioread8(addr); +} + +static inline u16 rtl818x_ioread16(struct rtl8180_priv *priv, __le16 __iomem *addr) +{ + return ioread16(addr); +} + +static inline u32 rtl818x_ioread32(struct rtl8180_priv *priv, __le32 __iomem *addr) +{ + return ioread32(addr); +} + +static inline void rtl818x_iowrite8(struct rtl8180_priv *priv, + u8 __iomem *addr, u8 val) +{ + iowrite8(val, addr); +} + +static inline void rtl818x_iowrite16(struct rtl8180_priv *priv, + __le16 __iomem *addr, u16 val) +{ + iowrite16(val, addr); +} + +static inline void rtl818x_iowrite32(struct rtl8180_priv *priv, + __le32 __iomem *addr, u32 val) +{ + iowrite32(val, addr); +} + +#endif /* RTL8180_H */ diff --git a/drivers/net/wireless/rtl818x/rtl8180/rtl8225.c b/drivers/net/wireless/rtl818x/rtl8180/rtl8225.c new file mode 100644 index 0000000..cc2a541 --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8180/rtl8225.c @@ -0,0 +1,792 @@ + +/* + * Radio tuning for RTL8225 on RTL8180 + * + * Copyright 2007 Michael Wu + * Copyright 2007 Andrea Merello + * + * Based on the r8180 driver, which is: + * Copyright 2005 Andrea Merello , et al. + * + * Thanks to Realtek for their support! + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include + +#include "rtl8180.h" +#include "rtl8225.h" + +static void rtl8225_write(struct ieee80211_hw *dev, u8 addr, u16 data) +{ + struct rtl8180_priv *priv = dev->priv; + u16 reg80, reg84, reg82; + u32 bangdata; + int i; + + bangdata = (data << 4) | (addr & 0xf); + + reg80 = rtl818x_ioread16(priv, &priv->map->RFPinsOutput) & 0xfff3; + reg82 = rtl818x_ioread16(priv, &priv->map->RFPinsEnable); + + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, reg82 | 0x7); + + reg84 = rtl818x_ioread16(priv, &priv->map->RFPinsSelect); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, reg84 | 0x7 | 0x400); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(10); + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg80 | (1 << 2)); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(2); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg80); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(10); + + for (i = 15; i >= 0; i--) { + u16 reg = reg80; + + if (bangdata & (1 << i)) + reg |= 1; + + if (i & 1) + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg); + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg | (1 << 1)); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg | (1 << 1)); + + if (!(i & 1)) + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg); + } + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg80 | (1 << 2)); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(10); + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg80 | (1 << 2)); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, reg84 | 0x400); + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, 0x1FFF); +} + +static u16 rtl8225_read(struct ieee80211_hw *dev, u8 addr) +{ + struct rtl8180_priv *priv = dev->priv; + u16 reg80, reg82, reg84, out; + int i; + + reg80 = rtl818x_ioread16(priv, &priv->map->RFPinsOutput); + reg82 = rtl818x_ioread16(priv, &priv->map->RFPinsEnable); + reg84 = rtl818x_ioread16(priv, &priv->map->RFPinsSelect) | 0x400; + + reg80 &= ~0xF; + + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, reg82 | 0x000F); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, reg84 | 0x000F); + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg80 | (1 << 2)); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(4); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg80); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(5); + + for (i = 4; i >= 0; i--) { + u16 reg = reg80 | ((addr >> i) & 1); + + if (!(i & 1)) { + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(1); + } + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg | (1 << 1)); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(2); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg | (1 << 1)); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(2); + + if (i & 1) { + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(1); + } + } + + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, 0x000E); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, 0x040E); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3) | (1 << 1)); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(2); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3)); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(2); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3)); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(2); + + out = 0; + for (i = 11; i >= 0; i--) { + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3)); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(1); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3) | (1 << 1)); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(2); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3) | (1 << 1)); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(2); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3) | (1 << 1)); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(2); + + if (rtl818x_ioread16(priv, &priv->map->RFPinsInput) & (1 << 1)) + out |= 1 << i; + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3)); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(2); + } + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3) | (1 << 2)); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + udelay(2); + + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, reg82); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, reg84); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, 0x03A0); + + return out; +} + +static const u16 rtl8225bcd_rxgain[] = { + 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0408, 0x0409, + 0x040a, 0x040b, 0x0502, 0x0503, 0x0504, 0x0505, 0x0540, 0x0541, + 0x0542, 0x0543, 0x0544, 0x0545, 0x0580, 0x0581, 0x0582, 0x0583, + 0x0584, 0x0585, 0x0588, 0x0589, 0x058a, 0x058b, 0x0643, 0x0644, + 0x0645, 0x0680, 0x0681, 0x0682, 0x0683, 0x0684, 0x0685, 0x0688, + 0x0689, 0x068a, 0x068b, 0x068c, 0x0742, 0x0743, 0x0744, 0x0745, + 0x0780, 0x0781, 0x0782, 0x0783, 0x0784, 0x0785, 0x0788, 0x0789, + 0x078a, 0x078b, 0x078c, 0x078d, 0x0790, 0x0791, 0x0792, 0x0793, + 0x0794, 0x0795, 0x0798, 0x0799, 0x079a, 0x079b, 0x079c, 0x079d, + 0x07a0, 0x07a1, 0x07a2, 0x07a3, 0x07a4, 0x07a5, 0x07a8, 0x07a9, + 0x07aa, 0x07ab, 0x07ac, 0x07ad, 0x07b0, 0x07b1, 0x07b2, 0x07b3, + 0x07b4, 0x07b5, 0x07b8, 0x07b9, 0x07ba, 0x07bb, 0x07bb +}; + +static const u8 rtl8225_agc[] = { + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9d, 0x9c, 0x9b, 0x9a, 0x99, 0x98, 0x97, 0x96, + 0x95, 0x94, 0x93, 0x92, 0x91, 0x90, 0x8f, 0x8e, + 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88, 0x87, 0x86, + 0x85, 0x84, 0x83, 0x82, 0x81, 0x80, 0x3f, 0x3e, + 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, 0x37, 0x36, + 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x2f, 0x2e, + 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, + 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, 0x1f, 0x1e, + 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, + 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x0e, + 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, + 0x05, 0x04, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 +}; + +static const u8 rtl8225_gain[] = { + 0x23, 0x88, 0x7c, 0xa5, /* -82dbm */ + 0x23, 0x88, 0x7c, 0xb5, /* -82dbm */ + 0x23, 0x88, 0x7c, 0xc5, /* -82dbm */ + 0x33, 0x80, 0x79, 0xc5, /* -78dbm */ + 0x43, 0x78, 0x76, 0xc5, /* -74dbm */ + 0x53, 0x60, 0x73, 0xc5, /* -70dbm */ + 0x63, 0x58, 0x70, 0xc5, /* -66dbm */ +}; + +static const u8 rtl8225_threshold[] = { + 0x8d, 0x8d, 0x8d, 0x8d, 0x9d, 0xad, 0xbd +}; + +static const u8 rtl8225_tx_gain_cck_ofdm[] = { + 0x02, 0x06, 0x0e, 0x1e, 0x3e, 0x7e +}; + +static const u8 rtl8225_tx_power_cck[] = { + 0x18, 0x17, 0x15, 0x11, 0x0c, 0x08, 0x04, 0x02, + 0x1b, 0x1a, 0x17, 0x13, 0x0e, 0x09, 0x04, 0x02, + 0x1f, 0x1e, 0x1a, 0x15, 0x10, 0x0a, 0x05, 0x02, + 0x22, 0x21, 0x1d, 0x18, 0x11, 0x0b, 0x06, 0x02, + 0x26, 0x25, 0x21, 0x1b, 0x14, 0x0d, 0x06, 0x03, + 0x2b, 0x2a, 0x25, 0x1e, 0x16, 0x0e, 0x07, 0x03 +}; + +static const u8 rtl8225_tx_power_cck_ch14[] = { + 0x18, 0x17, 0x15, 0x0c, 0x00, 0x00, 0x00, 0x00, + 0x1b, 0x1a, 0x17, 0x0e, 0x00, 0x00, 0x00, 0x00, + 0x1f, 0x1e, 0x1a, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x21, 0x1d, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x26, 0x25, 0x21, 0x13, 0x00, 0x00, 0x00, 0x00, + 0x2b, 0x2a, 0x25, 0x15, 0x00, 0x00, 0x00, 0x00 +}; + +static const u8 rtl8225_tx_power_ofdm[] = { + 0x80, 0x90, 0xa2, 0xb5, 0xcb, 0xe4 +}; + +static const u32 rtl8225_chan[] = { + 0x085c, 0x08dc, 0x095c, 0x09dc, 0x0a5c, 0x0adc, 0x0b5c, + 0x0bdc, 0x0c5c, 0x0cdc, 0x0d5c, 0x0ddc, 0x0e5c, 0x0f72 +}; + +static void rtl8225_rf_set_tx_power(struct ieee80211_hw *dev, int channel) +{ + struct rtl8180_priv *priv = dev->priv; + u8 cck_power, ofdm_power; + const u8 *tmp; + u32 reg; + int i; + + cck_power = priv->channels[channel - 1].hw_value & 0xFF; + ofdm_power = priv->channels[channel - 1].hw_value >> 8; + + cck_power = min(cck_power, (u8)35); + ofdm_power = min(ofdm_power, (u8)35); + + rtl818x_iowrite8(priv, &priv->map->TX_GAIN_CCK, + rtl8225_tx_gain_cck_ofdm[cck_power / 6] >> 1); + + if (channel == 14) + tmp = &rtl8225_tx_power_cck_ch14[(cck_power % 6) * 8]; + else + tmp = &rtl8225_tx_power_cck[(cck_power % 6) * 8]; + + for (i = 0; i < 8; i++) + rtl8225_write_phy_cck(dev, 0x44 + i, *tmp++); + + msleep(1); /* FIXME: optional? */ + + /* anaparam2 on */ + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_CONFIG); + reg = rtl818x_ioread8(priv, &priv->map->CONFIG3); + rtl818x_iowrite8(priv, &priv->map->CONFIG3, reg | RTL818X_CONFIG3_ANAPARAM_WRITE); + rtl818x_iowrite32(priv, &priv->map->ANAPARAM2, RTL8225_ANAPARAM2_ON); + rtl818x_iowrite8(priv, &priv->map->CONFIG3, reg & ~RTL818X_CONFIG3_ANAPARAM_WRITE); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_NORMAL); + + rtl818x_iowrite8(priv, &priv->map->TX_GAIN_OFDM, + rtl8225_tx_gain_cck_ofdm[ofdm_power/6] >> 1); + + tmp = &rtl8225_tx_power_ofdm[ofdm_power % 6]; + + rtl8225_write_phy_ofdm(dev, 5, *tmp); + rtl8225_write_phy_ofdm(dev, 7, *tmp); + + msleep(1); +} + +static void rtl8225_rf_init(struct ieee80211_hw *dev) +{ + struct rtl8180_priv *priv = dev->priv; + int i; + + rtl8180_set_anaparam(priv, RTL8225_ANAPARAM_ON); + + /* host_pci_init */ + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, 0x0480); + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, 0x1FFF); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, 0x0488); + rtl818x_iowrite8(priv, &priv->map->GP_ENABLE, 0); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + msleep(200); /* FIXME: ehh?? */ + rtl818x_iowrite8(priv, &priv->map->GP_ENABLE, 0xFF & ~(1 << 6)); + + rtl818x_iowrite32(priv, &priv->map->RF_TIMING, 0x000a8008); + + /* TODO: check if we need really to change BRSR to do RF config */ + rtl818x_ioread16(priv, &priv->map->BRSR); + rtl818x_iowrite16(priv, &priv->map->BRSR, 0xFFFF); + rtl818x_iowrite32(priv, &priv->map->RF_PARA, 0x00100044); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_CONFIG); + rtl818x_iowrite8(priv, &priv->map->CONFIG3, 0x44); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_NORMAL); + + rtl8225_write(dev, 0x0, 0x067); + rtl8225_write(dev, 0x1, 0xFE0); + rtl8225_write(dev, 0x2, 0x44D); + rtl8225_write(dev, 0x3, 0x441); + rtl8225_write(dev, 0x4, 0x8BE); + rtl8225_write(dev, 0x5, 0xBF0); /* TODO: minipci */ + rtl8225_write(dev, 0x6, 0xAE6); + rtl8225_write(dev, 0x7, rtl8225_chan[0]); + rtl8225_write(dev, 0x8, 0x01F); + rtl8225_write(dev, 0x9, 0x334); + rtl8225_write(dev, 0xA, 0xFD4); + rtl8225_write(dev, 0xB, 0x391); + rtl8225_write(dev, 0xC, 0x050); + rtl8225_write(dev, 0xD, 0x6DB); + rtl8225_write(dev, 0xE, 0x029); + rtl8225_write(dev, 0xF, 0x914); msleep(1); + + rtl8225_write(dev, 0x2, 0xC4D); msleep(100); + + rtl8225_write(dev, 0x0, 0x127); + + for (i = 0; i < ARRAY_SIZE(rtl8225bcd_rxgain); i++) { + rtl8225_write(dev, 0x1, i + 1); + rtl8225_write(dev, 0x2, rtl8225bcd_rxgain[i]); + } + + rtl8225_write(dev, 0x0, 0x027); + rtl8225_write(dev, 0x0, 0x22F); + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, 0x1FFF); + + for (i = 0; i < ARRAY_SIZE(rtl8225_agc); i++) { + rtl8225_write_phy_ofdm(dev, 0xB, rtl8225_agc[i]); + msleep(1); + rtl8225_write_phy_ofdm(dev, 0xA, 0x80 + i); + msleep(1); + } + + msleep(1); + + rtl8225_write_phy_ofdm(dev, 0x00, 0x01); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x01, 0x02); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x02, 0x62); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x03, 0x00); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x04, 0x00); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x05, 0x00); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x06, 0x00); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x07, 0x00); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x08, 0x00); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x09, 0xfe); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x0a, 0x09); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x0b, 0x80); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x0c, 0x01); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x0e, 0xd3); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x0f, 0x38); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x10, 0x84); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x11, 0x03); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x12, 0x20); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x13, 0x20); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x14, 0x00); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x15, 0x40); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x16, 0x00); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x17, 0x40); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x18, 0xef); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x19, 0x19); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x1a, 0x20); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x1b, 0x76); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x1c, 0x04); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x1e, 0x95); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x1f, 0x75); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x20, 0x1f); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x21, 0x27); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x22, 0x16); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x24, 0x46); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x25, 0x20); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x26, 0x90); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x27, 0x88); msleep(1); + + rtl8225_write_phy_cck(dev, 0x00, 0x98); msleep(1); + rtl8225_write_phy_cck(dev, 0x03, 0x20); msleep(1); + rtl8225_write_phy_cck(dev, 0x04, 0x7e); msleep(1); + rtl8225_write_phy_cck(dev, 0x05, 0x12); msleep(1); + rtl8225_write_phy_cck(dev, 0x06, 0xfc); msleep(1); + rtl8225_write_phy_cck(dev, 0x07, 0x78); msleep(1); + rtl8225_write_phy_cck(dev, 0x08, 0x2e); msleep(1); + rtl8225_write_phy_cck(dev, 0x10, 0x93); msleep(1); + rtl8225_write_phy_cck(dev, 0x11, 0x88); msleep(1); + rtl8225_write_phy_cck(dev, 0x12, 0x47); msleep(1); + rtl8225_write_phy_cck(dev, 0x13, 0xd0); + rtl8225_write_phy_cck(dev, 0x19, 0x00); + rtl8225_write_phy_cck(dev, 0x1a, 0xa0); + rtl8225_write_phy_cck(dev, 0x1b, 0x08); + rtl8225_write_phy_cck(dev, 0x40, 0x86); + rtl8225_write_phy_cck(dev, 0x41, 0x8d); msleep(1); + rtl8225_write_phy_cck(dev, 0x42, 0x15); msleep(1); + rtl8225_write_phy_cck(dev, 0x43, 0x18); msleep(1); + rtl8225_write_phy_cck(dev, 0x44, 0x1f); msleep(1); + rtl8225_write_phy_cck(dev, 0x45, 0x1e); msleep(1); + rtl8225_write_phy_cck(dev, 0x46, 0x1a); msleep(1); + rtl8225_write_phy_cck(dev, 0x47, 0x15); msleep(1); + rtl8225_write_phy_cck(dev, 0x48, 0x10); msleep(1); + rtl8225_write_phy_cck(dev, 0x49, 0x0a); msleep(1); + rtl8225_write_phy_cck(dev, 0x4a, 0x05); msleep(1); + rtl8225_write_phy_cck(dev, 0x4b, 0x02); msleep(1); + rtl8225_write_phy_cck(dev, 0x4c, 0x05); msleep(1); + + rtl818x_iowrite8(priv, &priv->map->TESTR, 0x0D); msleep(1); + + rtl8225_rf_set_tx_power(dev, 1); + + /* RX antenna default to A */ + rtl8225_write_phy_cck(dev, 0x10, 0x9b); msleep(1); /* B: 0xDB */ + rtl8225_write_phy_ofdm(dev, 0x26, 0x90); msleep(1); /* B: 0x10 */ + + rtl818x_iowrite8(priv, &priv->map->TX_ANTENNA, 0x03); /* B: 0x00 */ + msleep(1); + rtl818x_iowrite32(priv, (__le32 __iomem *)((void __iomem *)priv->map + 0x94), 0x15c00002); + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, 0x1FFF); + + rtl8225_write(dev, 0x0c, 0x50); + /* set OFDM initial gain */ + rtl8225_write_phy_ofdm(dev, 0x0d, rtl8225_gain[4 * 4]); + rtl8225_write_phy_ofdm(dev, 0x23, rtl8225_gain[4 * 4 + 1]); + rtl8225_write_phy_ofdm(dev, 0x1b, rtl8225_gain[4 * 4 + 2]); + rtl8225_write_phy_ofdm(dev, 0x1d, rtl8225_gain[4 * 4 + 3]); + /* set CCK threshold */ + rtl8225_write_phy_cck(dev, 0x41, rtl8225_threshold[0]); +} + +static const u8 rtl8225z2_tx_power_cck_ch14[] = { + 0x36, 0x35, 0x2e, 0x1b, 0x00, 0x00, 0x00, 0x00 +}; + +static const u8 rtl8225z2_tx_power_cck_B[] = { + 0x30, 0x2f, 0x29, 0x21, 0x19, 0x10, 0x08, 0x04 +}; + +static const u8 rtl8225z2_tx_power_cck_A[] = { + 0x33, 0x32, 0x2b, 0x23, 0x1a, 0x11, 0x08, 0x04 +}; + +static const u8 rtl8225z2_tx_power_cck[] = { + 0x36, 0x35, 0x2e, 0x25, 0x1c, 0x12, 0x09, 0x04 +}; + +static void rtl8225z2_rf_set_tx_power(struct ieee80211_hw *dev, int channel) +{ + struct rtl8180_priv *priv = dev->priv; + u8 cck_power, ofdm_power; + const u8 *tmp; + int i; + + cck_power = priv->channels[channel - 1].hw_value & 0xFF; + ofdm_power = priv->channels[channel - 1].hw_value >> 8; + + if (channel == 14) + tmp = rtl8225z2_tx_power_cck_ch14; + else if (cck_power == 12) + tmp = rtl8225z2_tx_power_cck_B; + else if (cck_power == 13) + tmp = rtl8225z2_tx_power_cck_A; + else + tmp = rtl8225z2_tx_power_cck; + + for (i = 0; i < 8; i++) + rtl8225_write_phy_cck(dev, 0x44 + i, *tmp++); + + cck_power = min(cck_power, (u8)35); + if (cck_power == 13 || cck_power == 14) + cck_power = 12; + if (cck_power >= 15) + cck_power -= 2; + + rtl818x_iowrite8(priv, &priv->map->TX_GAIN_CCK, cck_power); + rtl818x_ioread8(priv, &priv->map->TX_GAIN_CCK); + msleep(1); + + ofdm_power = min(ofdm_power, (u8)35); + rtl818x_iowrite8(priv, &priv->map->TX_GAIN_OFDM, ofdm_power); + + rtl8225_write_phy_ofdm(dev, 2, 0x62); + rtl8225_write_phy_ofdm(dev, 5, 0x00); + rtl8225_write_phy_ofdm(dev, 6, 0x40); + rtl8225_write_phy_ofdm(dev, 7, 0x00); + rtl8225_write_phy_ofdm(dev, 8, 0x40); + + msleep(1); +} + +static const u16 rtl8225z2_rxgain[] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0008, 0x0009, + 0x000a, 0x000b, 0x0102, 0x0103, 0x0104, 0x0105, 0x0140, 0x0141, + 0x0142, 0x0143, 0x0144, 0x0145, 0x0180, 0x0181, 0x0182, 0x0183, + 0x0184, 0x0185, 0x0188, 0x0189, 0x018a, 0x018b, 0x0243, 0x0244, + 0x0245, 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0288, + 0x0289, 0x028a, 0x028b, 0x028c, 0x0342, 0x0343, 0x0344, 0x0345, + 0x0380, 0x0381, 0x0382, 0x0383, 0x0384, 0x0385, 0x0388, 0x0389, + 0x038a, 0x038b, 0x038c, 0x038d, 0x0390, 0x0391, 0x0392, 0x0393, + 0x0394, 0x0395, 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, + 0x03a0, 0x03a1, 0x03a2, 0x03a3, 0x03a4, 0x03a5, 0x03a8, 0x03a9, + 0x03aa, 0x03ab, 0x03ac, 0x03ad, 0x03b0, 0x03b1, 0x03b2, 0x03b3, + 0x03b4, 0x03b5, 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bb +}; + +static void rtl8225z2_rf_init(struct ieee80211_hw *dev) +{ + struct rtl8180_priv *priv = dev->priv; + int i; + + rtl8180_set_anaparam(priv, RTL8225_ANAPARAM_ON); + + /* host_pci_init */ + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, 0x0480); + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, 0x1FFF); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, 0x0488); + rtl818x_iowrite8(priv, &priv->map->GP_ENABLE, 0); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + msleep(200); /* FIXME: ehh?? */ + rtl818x_iowrite8(priv, &priv->map->GP_ENABLE, 0xFF & ~(1 << 6)); + + rtl818x_iowrite32(priv, &priv->map->RF_TIMING, 0x00088008); + + /* TODO: check if we need really to change BRSR to do RF config */ + rtl818x_ioread16(priv, &priv->map->BRSR); + rtl818x_iowrite16(priv, &priv->map->BRSR, 0xFFFF); + rtl818x_iowrite32(priv, &priv->map->RF_PARA, 0x00100044); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_CONFIG); + rtl818x_iowrite8(priv, &priv->map->CONFIG3, 0x44); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_NORMAL); + + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, 0x1FFF); + + rtl8225_write(dev, 0x0, 0x0B7); msleep(1); + rtl8225_write(dev, 0x1, 0xEE0); msleep(1); + rtl8225_write(dev, 0x2, 0x44D); msleep(1); + rtl8225_write(dev, 0x3, 0x441); msleep(1); + rtl8225_write(dev, 0x4, 0x8C3); msleep(1); + rtl8225_write(dev, 0x5, 0xC72); msleep(1); + rtl8225_write(dev, 0x6, 0x0E6); msleep(1); + rtl8225_write(dev, 0x7, 0x82A); msleep(1); + rtl8225_write(dev, 0x8, 0x03F); msleep(1); + rtl8225_write(dev, 0x9, 0x335); msleep(1); + rtl8225_write(dev, 0xa, 0x9D4); msleep(1); + rtl8225_write(dev, 0xb, 0x7BB); msleep(1); + rtl8225_write(dev, 0xc, 0x850); msleep(1); + rtl8225_write(dev, 0xd, 0xCDF); msleep(1); + rtl8225_write(dev, 0xe, 0x02B); msleep(1); + rtl8225_write(dev, 0xf, 0x114); msleep(100); + + if (!(rtl8225_read(dev, 6) & (1 << 7))) { + rtl8225_write(dev, 0x02, 0x0C4D); + msleep(200); + rtl8225_write(dev, 0x02, 0x044D); + msleep(100); + /* TODO: readd calibration failure message when the calibration + check works */ + } + + rtl8225_write(dev, 0x0, 0x1B7); + rtl8225_write(dev, 0x3, 0x002); + rtl8225_write(dev, 0x5, 0x004); + + for (i = 0; i < ARRAY_SIZE(rtl8225z2_rxgain); i++) { + rtl8225_write(dev, 0x1, i + 1); + rtl8225_write(dev, 0x2, rtl8225z2_rxgain[i]); + } + + rtl8225_write(dev, 0x0, 0x0B7); msleep(100); + rtl8225_write(dev, 0x2, 0xC4D); + + msleep(200); + rtl8225_write(dev, 0x2, 0x44D); + msleep(100); + + rtl8225_write(dev, 0x00, 0x2BF); + rtl8225_write(dev, 0xFF, 0xFFFF); + + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, 0x1FFF); + + for (i = 0; i < ARRAY_SIZE(rtl8225_agc); i++) { + rtl8225_write_phy_ofdm(dev, 0xB, rtl8225_agc[i]); + msleep(1); + rtl8225_write_phy_ofdm(dev, 0xA, 0x80 + i); + msleep(1); + } + + msleep(1); + + rtl8225_write_phy_ofdm(dev, 0x00, 0x01); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x01, 0x02); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x02, 0x62); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x03, 0x00); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x04, 0x00); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x05, 0x00); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x06, 0x40); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x07, 0x00); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x08, 0x40); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x09, 0xfe); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x0a, 0x09); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x18, 0xef); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x0b, 0x80); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x0c, 0x01); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x0d, 0x43); + rtl8225_write_phy_ofdm(dev, 0x0e, 0xd3); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x0f, 0x38); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x10, 0x84); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x11, 0x06); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x12, 0x20); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x13, 0x20); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x14, 0x00); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x15, 0x40); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x16, 0x00); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x17, 0x40); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x18, 0xef); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x19, 0x19); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x1a, 0x20); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x1b, 0x11); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x1c, 0x04); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x1d, 0xc5); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x1e, 0xb3); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x1f, 0x75); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x20, 0x1f); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x21, 0x27); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x22, 0x16); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x23, 0x80); msleep(1); /* FIXME: not needed? */ + rtl8225_write_phy_ofdm(dev, 0x24, 0x46); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x25, 0x20); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x26, 0x90); msleep(1); + rtl8225_write_phy_ofdm(dev, 0x27, 0x88); msleep(1); + + rtl8225_write_phy_cck(dev, 0x00, 0x98); msleep(1); + rtl8225_write_phy_cck(dev, 0x03, 0x20); msleep(1); + rtl8225_write_phy_cck(dev, 0x04, 0x7e); msleep(1); + rtl8225_write_phy_cck(dev, 0x05, 0x12); msleep(1); + rtl8225_write_phy_cck(dev, 0x06, 0xfc); msleep(1); + rtl8225_write_phy_cck(dev, 0x07, 0x78); msleep(1); + rtl8225_write_phy_cck(dev, 0x08, 0x2e); msleep(1); + rtl8225_write_phy_cck(dev, 0x10, 0x93); msleep(1); + rtl8225_write_phy_cck(dev, 0x11, 0x88); msleep(1); + rtl8225_write_phy_cck(dev, 0x12, 0x47); msleep(1); + rtl8225_write_phy_cck(dev, 0x13, 0xd0); + rtl8225_write_phy_cck(dev, 0x19, 0x00); + rtl8225_write_phy_cck(dev, 0x1a, 0xa0); + rtl8225_write_phy_cck(dev, 0x1b, 0x08); + rtl8225_write_phy_cck(dev, 0x40, 0x86); + rtl8225_write_phy_cck(dev, 0x41, 0x8a); msleep(1); + rtl8225_write_phy_cck(dev, 0x42, 0x15); msleep(1); + rtl8225_write_phy_cck(dev, 0x43, 0x18); msleep(1); + rtl8225_write_phy_cck(dev, 0x44, 0x36); msleep(1); + rtl8225_write_phy_cck(dev, 0x45, 0x35); msleep(1); + rtl8225_write_phy_cck(dev, 0x46, 0x2e); msleep(1); + rtl8225_write_phy_cck(dev, 0x47, 0x25); msleep(1); + rtl8225_write_phy_cck(dev, 0x48, 0x1c); msleep(1); + rtl8225_write_phy_cck(dev, 0x49, 0x12); msleep(1); + rtl8225_write_phy_cck(dev, 0x4a, 0x09); msleep(1); + rtl8225_write_phy_cck(dev, 0x4b, 0x04); msleep(1); + rtl8225_write_phy_cck(dev, 0x4c, 0x05); msleep(1); + + rtl818x_iowrite8(priv, (u8 __iomem *)((void __iomem *)priv->map + 0x5B), 0x0D); msleep(1); + + rtl8225z2_rf_set_tx_power(dev, 1); + + /* RX antenna default to A */ + rtl8225_write_phy_cck(dev, 0x10, 0x9b); msleep(1); /* B: 0xDB */ + rtl8225_write_phy_ofdm(dev, 0x26, 0x90); msleep(1); /* B: 0x10 */ + + rtl818x_iowrite8(priv, &priv->map->TX_ANTENNA, 0x03); /* B: 0x00 */ + msleep(1); + rtl818x_iowrite32(priv, (__le32 __iomem *)((void __iomem *)priv->map + 0x94), 0x15c00002); + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, 0x1FFF); +} + +static void rtl8225_rf_stop(struct ieee80211_hw *dev) +{ + struct rtl8180_priv *priv = dev->priv; + u8 reg; + + rtl8225_write(dev, 0x4, 0x1f); msleep(1); + + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_CONFIG); + reg = rtl818x_ioread8(priv, &priv->map->CONFIG3); + rtl818x_iowrite8(priv, &priv->map->CONFIG3, reg | RTL818X_CONFIG3_ANAPARAM_WRITE); + rtl818x_iowrite32(priv, &priv->map->ANAPARAM2, RTL8225_ANAPARAM2_OFF); + rtl818x_iowrite32(priv, &priv->map->ANAPARAM, RTL8225_ANAPARAM_OFF); + rtl818x_iowrite8(priv, &priv->map->CONFIG3, reg & ~RTL818X_CONFIG3_ANAPARAM_WRITE); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_NORMAL); +} + +static void rtl8225_rf_set_channel(struct ieee80211_hw *dev, + struct ieee80211_conf *conf) +{ + struct rtl8180_priv *priv = dev->priv; + int chan = + ieee80211_frequency_to_channel(conf->chandef.chan->center_freq); + + if (priv->rf->init == rtl8225_rf_init) + rtl8225_rf_set_tx_power(dev, chan); + else + rtl8225z2_rf_set_tx_power(dev, chan); + + rtl8225_write(dev, 0x7, rtl8225_chan[chan - 1]); + msleep(10); +} + +static void rtl8225_rf_conf_erp(struct ieee80211_hw *dev, + struct ieee80211_bss_conf *info) +{ + struct rtl8180_priv *priv = dev->priv; + + if (info->use_short_slot) { + rtl818x_iowrite8(priv, &priv->map->SLOT, 0x9); + rtl818x_iowrite8(priv, &priv->map->SIFS, 0x22); + rtl818x_iowrite8(priv, &priv->map->DIFS, 0x14); + rtl818x_iowrite8(priv, &priv->map->EIFS, 81); + rtl818x_iowrite8(priv, &priv->map->CW_VAL, 0x73); + } else { + rtl818x_iowrite8(priv, &priv->map->SLOT, 0x14); + rtl818x_iowrite8(priv, &priv->map->SIFS, 0x44); + rtl818x_iowrite8(priv, &priv->map->DIFS, 0x24); + rtl818x_iowrite8(priv, &priv->map->EIFS, 81); + rtl818x_iowrite8(priv, &priv->map->CW_VAL, 0xa5); + } +} + +static const struct rtl818x_rf_ops rtl8225_ops = { + .name = "rtl8225", + .init = rtl8225_rf_init, + .stop = rtl8225_rf_stop, + .set_chan = rtl8225_rf_set_channel, + .conf_erp = rtl8225_rf_conf_erp, +}; + +static const struct rtl818x_rf_ops rtl8225z2_ops = { + .name = "rtl8225z2", + .init = rtl8225z2_rf_init, + .stop = rtl8225_rf_stop, + .set_chan = rtl8225_rf_set_channel, + .conf_erp = rtl8225_rf_conf_erp, +}; + +const struct rtl818x_rf_ops * rtl8180_detect_rf(struct ieee80211_hw *dev) +{ + struct rtl8180_priv *priv = dev->priv; + u16 reg8, reg9; + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, 0x0480); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, 0x0488); + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, 0x1FFF); + rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + msleep(100); + + rtl8225_write(dev, 0, 0x1B7); + + reg8 = rtl8225_read(dev, 8); + reg9 = rtl8225_read(dev, 9); + + rtl8225_write(dev, 0, 0x0B7); + + if (reg8 != 0x588 || reg9 != 0x700) + return &rtl8225_ops; + + return &rtl8225z2_ops; +} diff --git a/drivers/net/wireless/rtl818x/rtl8180/rtl8225.h b/drivers/net/wireless/rtl818x/rtl8180/rtl8225.h new file mode 100644 index 0000000..310013a --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8180/rtl8225.h @@ -0,0 +1,23 @@ +#ifndef RTL8180_RTL8225_H +#define RTL8180_RTL8225_H + +#define RTL8225_ANAPARAM_ON 0xa0000b59 +#define RTL8225_ANAPARAM2_ON 0x860dec11 +#define RTL8225_ANAPARAM_OFF 0xa00beb59 +#define RTL8225_ANAPARAM2_OFF 0x840dec11 + +const struct rtl818x_rf_ops * rtl8180_detect_rf(struct ieee80211_hw *); + +static inline void rtl8225_write_phy_ofdm(struct ieee80211_hw *dev, + u8 addr, u8 data) +{ + rtl8180_write_phy(dev, addr, data); +} + +static inline void rtl8225_write_phy_cck(struct ieee80211_hw *dev, + u8 addr, u8 data) +{ + rtl8180_write_phy(dev, addr, data | 0x10000); +} + +#endif /* RTL8180_RTL8225_H */ diff --git a/drivers/net/wireless/rtl818x/rtl8180/sa2400.c b/drivers/net/wireless/rtl818x/rtl8180/sa2400.c new file mode 100644 index 0000000..b3ec40f --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8180/sa2400.c @@ -0,0 +1,229 @@ + +/* + * Radio tuning for Philips SA2400 on RTL8180 + * + * Copyright 2007 Andrea Merello + * + * Code from the BSD driver and the rtl8181 project have been + * very useful to understand certain things + * + * I want to thanks the Authors of such projects and the Ndiswrapper + * project Authors. + * + * A special Big Thanks also is for all people who donated me cards, + * making possible the creation of the original rtl8180 driver + * from which this code is derived! + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include + +#include "rtl8180.h" +#include "sa2400.h" + +static const u32 sa2400_chan[] = { + 0x00096c, /* ch1 */ + 0x080970, + 0x100974, + 0x180978, + 0x000980, + 0x080984, + 0x100988, + 0x18098c, + 0x000994, + 0x080998, + 0x10099c, + 0x1809a0, + 0x0009a8, + 0x0009b4, /* ch 14 */ +}; + +static void write_sa2400(struct ieee80211_hw *dev, u8 addr, u32 data) +{ + struct rtl8180_priv *priv = dev->priv; + u32 phy_config; + + /* MAC will bang bits to the sa2400. sw 3-wire is NOT used */ + phy_config = 0xb0000000; + + phy_config |= ((u32)(addr & 0xf)) << 24; + phy_config |= data & 0xffffff; + + rtl818x_iowrite32(priv, + (__le32 __iomem *) &priv->map->RFPinsOutput, phy_config); + + msleep(3); +} + +static void sa2400_write_phy_antenna(struct ieee80211_hw *dev, short chan) +{ + struct rtl8180_priv *priv = dev->priv; + u8 ant = SA2400_ANTENNA; + + if (priv->rfparam & RF_PARAM_ANTBDEFAULT) + ant |= BB_ANTENNA_B; + + if (chan == 14) + ant |= BB_ANTATTEN_CHAN14; + + rtl8180_write_phy(dev, 0x10, ant); + +} + +static u8 sa2400_rf_rssi_map[] = { + 0x64, 0x64, 0x63, 0x62, 0x61, 0x60, 0x5f, 0x5e, + 0x5d, 0x5c, 0x5b, 0x5a, 0x57, 0x54, 0x52, 0x50, + 0x4e, 0x4c, 0x4a, 0x48, 0x46, 0x44, 0x41, 0x3f, + 0x3c, 0x3a, 0x37, 0x36, 0x36, 0x1c, 0x1c, 0x1b, + 0x1b, 0x1a, 0x1a, 0x19, 0x19, 0x18, 0x18, 0x17, + 0x17, 0x16, 0x16, 0x15, 0x15, 0x14, 0x14, 0x13, + 0x13, 0x12, 0x12, 0x11, 0x11, 0x10, 0x10, 0x0f, + 0x0f, 0x0e, 0x0e, 0x0d, 0x0d, 0x0c, 0x0c, 0x0b, + 0x0b, 0x0a, 0x0a, 0x09, 0x09, 0x08, 0x08, 0x07, + 0x07, 0x06, 0x06, 0x05, 0x04, 0x03, 0x02, +}; + +static u8 sa2400_rf_calc_rssi(u8 agc, u8 sq) +{ + if (sq == 0x80) + return 1; + + if (sq > 78) + return 32; + + /* TODO: recalc sa2400_rf_rssi_map to avoid mult / div */ + return 65 * sa2400_rf_rssi_map[sq] / 100; +} + +static void sa2400_rf_set_channel(struct ieee80211_hw *dev, + struct ieee80211_conf *conf) +{ + struct rtl8180_priv *priv = dev->priv; + int channel = + ieee80211_frequency_to_channel(conf->chandef.chan->center_freq); + u32 txpw = priv->channels[channel - 1].hw_value & 0xFF; + u32 chan = sa2400_chan[channel - 1]; + + write_sa2400(dev, 7, txpw); + + sa2400_write_phy_antenna(dev, channel); + + write_sa2400(dev, 0, chan); + write_sa2400(dev, 1, 0xbb50); + write_sa2400(dev, 2, 0x80); + write_sa2400(dev, 3, 0); +} + +static void sa2400_rf_stop(struct ieee80211_hw *dev) +{ + write_sa2400(dev, 4, 0); +} + +static void sa2400_rf_init(struct ieee80211_hw *dev) +{ + struct rtl8180_priv *priv = dev->priv; + u32 anaparam, txconf; + u8 firdac; + int analogphy = priv->rfparam & RF_PARAM_ANALOGPHY; + + anaparam = priv->anaparam; + anaparam &= ~(1 << ANAPARAM_TXDACOFF_SHIFT); + anaparam &= ~ANAPARAM_PWR1_MASK; + anaparam &= ~ANAPARAM_PWR0_MASK; + + if (analogphy) { + anaparam |= SA2400_ANA_ANAPARAM_PWR1_ON << ANAPARAM_PWR1_SHIFT; + firdac = 0; + } else { + anaparam |= (SA2400_DIG_ANAPARAM_PWR1_ON << ANAPARAM_PWR1_SHIFT); + anaparam |= (SA2400_ANAPARAM_PWR0_ON << ANAPARAM_PWR0_SHIFT); + firdac = 1 << SA2400_REG4_FIRDAC_SHIFT; + } + + rtl8180_set_anaparam(priv, anaparam); + + write_sa2400(dev, 0, sa2400_chan[0]); + write_sa2400(dev, 1, 0xbb50); + write_sa2400(dev, 2, 0x80); + write_sa2400(dev, 3, 0); + write_sa2400(dev, 4, 0x19340 | firdac); + write_sa2400(dev, 5, 0x1dfb | (SA2400_MAX_SENS - 54) << 15); + write_sa2400(dev, 4, 0x19348 | firdac); /* calibrate VCO */ + + if (!analogphy) + write_sa2400(dev, 4, 0x1938c); /*???*/ + + write_sa2400(dev, 4, 0x19340 | firdac); + + write_sa2400(dev, 0, sa2400_chan[0]); + write_sa2400(dev, 1, 0xbb50); + write_sa2400(dev, 2, 0x80); + write_sa2400(dev, 3, 0); + write_sa2400(dev, 4, 0x19344 | firdac); /* calibrate filter */ + + /* new from rtl8180 embedded driver (rtl8181 project) */ + write_sa2400(dev, 6, 0x13ff | (1 << 23)); /* MANRX */ + write_sa2400(dev, 8, 0); /* VCO */ + + if (analogphy) { + rtl8180_set_anaparam(priv, anaparam | + (1 << ANAPARAM_TXDACOFF_SHIFT)); + + txconf = rtl818x_ioread32(priv, &priv->map->TX_CONF); + rtl818x_iowrite32(priv, &priv->map->TX_CONF, + txconf | RTL818X_TX_CONF_LOOPBACK_CONT); + + write_sa2400(dev, 4, 0x19341); /* calibrates DC */ + + /* a 5us sleep is required here, + * we rely on the 3ms delay introduced in write_sa2400 */ + write_sa2400(dev, 4, 0x19345); + + /* a 20us sleep is required here, + * we rely on the 3ms delay introduced in write_sa2400 */ + + rtl818x_iowrite32(priv, &priv->map->TX_CONF, txconf); + + rtl8180_set_anaparam(priv, anaparam); + } + /* end new code */ + + write_sa2400(dev, 4, 0x19341 | firdac); /* RTX MODE */ + + /* baseband configuration */ + rtl8180_write_phy(dev, 0, 0x98); + rtl8180_write_phy(dev, 3, 0x38); + rtl8180_write_phy(dev, 4, 0xe0); + rtl8180_write_phy(dev, 5, 0x90); + rtl8180_write_phy(dev, 6, 0x1a); + rtl8180_write_phy(dev, 7, 0x64); + + sa2400_write_phy_antenna(dev, 1); + + rtl8180_write_phy(dev, 0x11, 0x80); + + if (rtl818x_ioread8(priv, &priv->map->CONFIG2) & + RTL818X_CONFIG2_ANTENNA_DIV) + rtl8180_write_phy(dev, 0x12, 0xc7); /* enable ant diversity */ + else + rtl8180_write_phy(dev, 0x12, 0x47); /* disable ant diversity */ + + rtl8180_write_phy(dev, 0x13, 0x90 | priv->csthreshold); + + rtl8180_write_phy(dev, 0x19, 0x0); + rtl8180_write_phy(dev, 0x1a, 0xa0); +} + +const struct rtl818x_rf_ops sa2400_rf_ops = { + .name = "Philips", + .init = sa2400_rf_init, + .stop = sa2400_rf_stop, + .set_chan = sa2400_rf_set_channel, + .calc_rssi = sa2400_rf_calc_rssi, +}; diff --git a/drivers/net/wireless/rtl818x/rtl8180/sa2400.h b/drivers/net/wireless/rtl818x/rtl8180/sa2400.h new file mode 100644 index 0000000..a4aaa0d --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8180/sa2400.h @@ -0,0 +1,36 @@ +#ifndef RTL8180_SA2400_H +#define RTL8180_SA2400_H + +/* + * Radio tuning for Philips SA2400 on RTL8180 + * + * Copyright 2007 Andrea Merello + * + * Code from the BSD driver and the rtl8181 project have been + * very useful to understand certain things + * + * I want to thanks the Authors of such projects and the Ndiswrapper + * project Authors. + * + * A special Big Thanks also is for all people who donated me cards, + * making possible the creation of the original rtl8180 driver + * from which this code is derived! + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define SA2400_ANTENNA 0x91 +#define SA2400_DIG_ANAPARAM_PWR1_ON 0x8 +#define SA2400_ANA_ANAPARAM_PWR1_ON 0x28 +#define SA2400_ANAPARAM_PWR0_ON 0x3 + +/* RX sensitivity in dbm */ +#define SA2400_MAX_SENS 85 + +#define SA2400_REG4_FIRDAC_SHIFT 7 + +extern const struct rtl818x_rf_ops sa2400_rf_ops; + +#endif /* RTL8180_SA2400_H */ diff --git a/drivers/net/wireless/rtl818x/rtl8187/Makefile b/drivers/net/wireless/rtl818x/rtl8187/Makefile new file mode 100644 index 0000000..0b93727 --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8187/Makefile @@ -0,0 +1,5 @@ +rtl8187-objs := dev.o rtl8225.o leds.o rfkill.o + +obj-$(CPTCFG_RTL8187) += rtl8187.o + +ccflags-y += -I$(backport_srctree)/drivers/net/wireless/rtl818x diff --git a/drivers/net/wireless/rtl818x/rtl8187/dev.c b/drivers/net/wireless/rtl818x/rtl8187/dev.c new file mode 100644 index 0000000..a4d1e8c --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8187/dev.c @@ -0,0 +1,1681 @@ +/* + * Linux device driver for RTL8187 + * + * Copyright 2007 Michael Wu + * Copyright 2007 Andrea Merello + * + * Based on the r8187 driver, which is: + * Copyright 2005 Andrea Merello , et al. + * + * The driver was extended to the RTL8187B in 2008 by: + * Herton Ronaldo Krzesinski + * Hin-Tak Leung + * Larry Finger + * + * Magic delays and register offsets below are taken from the original + * r8187 driver sources. Thanks to Realtek for their support! + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8187.h" +#include "rtl8225.h" +#ifdef CPTCFG_RTL8187_LEDS +#include "leds.h" +#endif +#include "rfkill.h" + +MODULE_AUTHOR("Michael Wu "); +MODULE_AUTHOR("Andrea Merello "); +MODULE_AUTHOR("Herton Ronaldo Krzesinski "); +MODULE_AUTHOR("Hin-Tak Leung "); +MODULE_AUTHOR("Larry Finger "); +MODULE_DESCRIPTION("RTL8187/RTL8187B USB wireless driver"); +MODULE_LICENSE("GPL"); + +static struct usb_device_id rtl8187_table[] = { + /* Asus */ + {USB_DEVICE(0x0b05, 0x171d), .driver_info = DEVICE_RTL8187}, + /* Belkin */ + {USB_DEVICE(0x050d, 0x705e), .driver_info = DEVICE_RTL8187B}, + /* Realtek */ + {USB_DEVICE(0x0bda, 0x8187), .driver_info = DEVICE_RTL8187}, + {USB_DEVICE(0x0bda, 0x8189), .driver_info = DEVICE_RTL8187B}, + {USB_DEVICE(0x0bda, 0x8197), .driver_info = DEVICE_RTL8187B}, + {USB_DEVICE(0x0bda, 0x8198), .driver_info = DEVICE_RTL8187B}, + /* Surecom */ + {USB_DEVICE(0x0769, 0x11F2), .driver_info = DEVICE_RTL8187}, + /* Logitech */ + {USB_DEVICE(0x0789, 0x010C), .driver_info = DEVICE_RTL8187}, + /* Netgear */ + {USB_DEVICE(0x0846, 0x6100), .driver_info = DEVICE_RTL8187}, + {USB_DEVICE(0x0846, 0x6a00), .driver_info = DEVICE_RTL8187}, + {USB_DEVICE(0x0846, 0x4260), .driver_info = DEVICE_RTL8187B}, + /* HP */ + {USB_DEVICE(0x03f0, 0xca02), .driver_info = DEVICE_RTL8187}, + /* Sitecom */ + {USB_DEVICE(0x0df6, 0x000d), .driver_info = DEVICE_RTL8187}, + {USB_DEVICE(0x0df6, 0x0028), .driver_info = DEVICE_RTL8187B}, + {USB_DEVICE(0x0df6, 0x0029), .driver_info = DEVICE_RTL8187B}, + /* Sphairon Access Systems GmbH */ + {USB_DEVICE(0x114B, 0x0150), .driver_info = DEVICE_RTL8187}, + /* Dick Smith Electronics */ + {USB_DEVICE(0x1371, 0x9401), .driver_info = DEVICE_RTL8187}, + /* Abocom */ + {USB_DEVICE(0x13d1, 0xabe6), .driver_info = DEVICE_RTL8187}, + /* Qcom */ + {USB_DEVICE(0x18E8, 0x6232), .driver_info = DEVICE_RTL8187}, + /* AirLive */ + {USB_DEVICE(0x1b75, 0x8187), .driver_info = DEVICE_RTL8187}, + /* Linksys */ + {USB_DEVICE(0x1737, 0x0073), .driver_info = DEVICE_RTL8187B}, + {} +}; + +MODULE_DEVICE_TABLE(usb, rtl8187_table); + +static const struct ieee80211_rate rtl818x_rates[] = { + { .bitrate = 10, .hw_value = 0, }, + { .bitrate = 20, .hw_value = 1, }, + { .bitrate = 55, .hw_value = 2, }, + { .bitrate = 110, .hw_value = 3, }, + { .bitrate = 60, .hw_value = 4, }, + { .bitrate = 90, .hw_value = 5, }, + { .bitrate = 120, .hw_value = 6, }, + { .bitrate = 180, .hw_value = 7, }, + { .bitrate = 240, .hw_value = 8, }, + { .bitrate = 360, .hw_value = 9, }, + { .bitrate = 480, .hw_value = 10, }, + { .bitrate = 540, .hw_value = 11, }, +}; + +static const struct ieee80211_channel rtl818x_channels[] = { + { .center_freq = 2412 }, + { .center_freq = 2417 }, + { .center_freq = 2422 }, + { .center_freq = 2427 }, + { .center_freq = 2432 }, + { .center_freq = 2437 }, + { .center_freq = 2442 }, + { .center_freq = 2447 }, + { .center_freq = 2452 }, + { .center_freq = 2457 }, + { .center_freq = 2462 }, + { .center_freq = 2467 }, + { .center_freq = 2472 }, + { .center_freq = 2484 }, +}; + +static void rtl8187_iowrite_async_cb(struct urb *urb) +{ + kfree(urb->context); +} + +static void rtl8187_iowrite_async(struct rtl8187_priv *priv, __le16 addr, + void *data, u16 len) +{ + struct usb_ctrlrequest *dr; + struct urb *urb; + struct rtl8187_async_write_data { + u8 data[4]; + struct usb_ctrlrequest dr; + } *buf; + int rc; + + buf = kmalloc(sizeof(*buf), GFP_ATOMIC); + if (!buf) + return; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + kfree(buf); + return; + } + + dr = &buf->dr; + + dr->bRequestType = RTL8187_REQT_WRITE; + dr->bRequest = RTL8187_REQ_SET_REG; + dr->wValue = addr; + dr->wIndex = 0; + dr->wLength = cpu_to_le16(len); + + memcpy(buf, data, len); + + usb_fill_control_urb(urb, priv->udev, usb_sndctrlpipe(priv->udev, 0), + (unsigned char *)dr, buf, len, + rtl8187_iowrite_async_cb, buf); + usb_anchor_urb(urb, &priv->anchored); + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (rc < 0) { + kfree(buf); + usb_unanchor_urb(urb); + } + usb_free_urb(urb); +} + +static inline void rtl818x_iowrite32_async(struct rtl8187_priv *priv, + __le32 *addr, u32 val) +{ + __le32 buf = cpu_to_le32(val); + + rtl8187_iowrite_async(priv, cpu_to_le16((unsigned long)addr), + &buf, sizeof(buf)); +} + +void rtl8187_write_phy(struct ieee80211_hw *dev, u8 addr, u32 data) +{ + struct rtl8187_priv *priv = dev->priv; + + data <<= 8; + data |= addr | 0x80; + + rtl818x_iowrite8(priv, &priv->map->PHY[3], (data >> 24) & 0xFF); + rtl818x_iowrite8(priv, &priv->map->PHY[2], (data >> 16) & 0xFF); + rtl818x_iowrite8(priv, &priv->map->PHY[1], (data >> 8) & 0xFF); + rtl818x_iowrite8(priv, &priv->map->PHY[0], data & 0xFF); +} + +static void rtl8187_tx_cb(struct urb *urb) +{ + struct sk_buff *skb = (struct sk_buff *)urb->context; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_hw *hw = info->rate_driver_data[0]; + struct rtl8187_priv *priv = hw->priv; + + skb_pull(skb, priv->is_rtl8187b ? sizeof(struct rtl8187b_tx_hdr) : + sizeof(struct rtl8187_tx_hdr)); + ieee80211_tx_info_clear_status(info); + + if (!(urb->status) && !(info->flags & IEEE80211_TX_CTL_NO_ACK)) { + if (priv->is_rtl8187b) { + skb_queue_tail(&priv->b_tx_status.queue, skb); + + /* queue is "full", discard last items */ + while (skb_queue_len(&priv->b_tx_status.queue) > 5) { + struct sk_buff *old_skb; + + dev_dbg(&priv->udev->dev, + "transmit status queue full\n"); + + old_skb = skb_dequeue(&priv->b_tx_status.queue); + ieee80211_tx_status_irqsafe(hw, old_skb); + } + return; + } else { + info->flags |= IEEE80211_TX_STAT_ACK; + } + } + if (priv->is_rtl8187b) + ieee80211_tx_status_irqsafe(hw, skb); + else { + /* Retry information for the RTI8187 is only available by + * reading a register in the device. We are in interrupt mode + * here, thus queue the skb and finish on a work queue. */ + skb_queue_tail(&priv->b_tx_status.queue, skb); + ieee80211_queue_delayed_work(hw, &priv->work, 0); + } +} + +static void rtl8187_tx(struct ieee80211_hw *dev, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct rtl8187_priv *priv = dev->priv; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *tx_hdr = (struct ieee80211_hdr *)(skb->data); + unsigned int ep; + void *buf; + struct urb *urb; + __le16 rts_dur = 0; + u32 flags; + int rc; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + kfree_skb(skb); + return; + } + + flags = skb->len; + flags |= RTL818X_TX_DESC_FLAG_NO_ENC; + + flags |= ieee80211_get_tx_rate(dev, info)->hw_value << 24; + if (ieee80211_has_morefrags(tx_hdr->frame_control)) + flags |= RTL818X_TX_DESC_FLAG_MOREFRAG; + if (info->control.rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS) { + flags |= RTL818X_TX_DESC_FLAG_RTS; + flags |= ieee80211_get_rts_cts_rate(dev, info)->hw_value << 19; + rts_dur = ieee80211_rts_duration(dev, priv->vif, + skb->len, info); + } else if (info->control.rates[0].flags & IEEE80211_TX_RC_USE_CTS_PROTECT) { + flags |= RTL818X_TX_DESC_FLAG_CTS; + flags |= ieee80211_get_rts_cts_rate(dev, info)->hw_value << 19; + } + + if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) { + if (info->flags & IEEE80211_TX_CTL_FIRST_FRAGMENT) + priv->seqno += 0x10; + tx_hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); + tx_hdr->seq_ctrl |= cpu_to_le16(priv->seqno); + } + + if (!priv->is_rtl8187b) { + struct rtl8187_tx_hdr *hdr = + (struct rtl8187_tx_hdr *)skb_push(skb, sizeof(*hdr)); + hdr->flags = cpu_to_le32(flags); + hdr->len = 0; + hdr->rts_duration = rts_dur; + hdr->retry = cpu_to_le32((info->control.rates[0].count - 1) << 8); + buf = hdr; + + ep = 2; + } else { + /* fc needs to be calculated before skb_push() */ + unsigned int epmap[4] = { 6, 7, 5, 4 }; + u16 fc = le16_to_cpu(tx_hdr->frame_control); + + struct rtl8187b_tx_hdr *hdr = + (struct rtl8187b_tx_hdr *)skb_push(skb, sizeof(*hdr)); + struct ieee80211_rate *txrate = + ieee80211_get_tx_rate(dev, info); + memset(hdr, 0, sizeof(*hdr)); + hdr->flags = cpu_to_le32(flags); + hdr->rts_duration = rts_dur; + hdr->retry = cpu_to_le32((info->control.rates[0].count - 1) << 8); + hdr->tx_duration = + ieee80211_generic_frame_duration(dev, priv->vif, + info->band, + skb->len, txrate); + buf = hdr; + + if ((fc & IEEE80211_FCTL_FTYPE) == IEEE80211_FTYPE_MGMT) + ep = 12; + else + ep = epmap[skb_get_queue_mapping(skb)]; + } + + info->rate_driver_data[0] = dev; + info->rate_driver_data[1] = urb; + + usb_fill_bulk_urb(urb, priv->udev, usb_sndbulkpipe(priv->udev, ep), + buf, skb->len, rtl8187_tx_cb, skb); + urb->transfer_flags |= URB_ZERO_PACKET; + usb_anchor_urb(urb, &priv->anchored); + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (rc < 0) { + usb_unanchor_urb(urb); + kfree_skb(skb); + } + usb_free_urb(urb); +} + +static void rtl8187_rx_cb(struct urb *urb) +{ + struct sk_buff *skb = (struct sk_buff *)urb->context; + struct rtl8187_rx_info *info = (struct rtl8187_rx_info *)skb->cb; + struct ieee80211_hw *dev = info->dev; + struct rtl8187_priv *priv = dev->priv; + struct ieee80211_rx_status rx_status = { 0 }; + int rate, signal; + u32 flags; + unsigned long f; + + spin_lock_irqsave(&priv->rx_queue.lock, f); + __skb_unlink(skb, &priv->rx_queue); + spin_unlock_irqrestore(&priv->rx_queue.lock, f); + skb_put(skb, urb->actual_length); + + if (unlikely(urb->status)) { + dev_kfree_skb_irq(skb); + return; + } + + if (!priv->is_rtl8187b) { + struct rtl8187_rx_hdr *hdr = + (typeof(hdr))(skb_tail_pointer(skb) - sizeof(*hdr)); + flags = le32_to_cpu(hdr->flags); + /* As with the RTL8187B below, the AGC is used to calculate + * signal strength. In this case, the scaling + * constants are derived from the output of p54usb. + */ + signal = -4 - ((27 * hdr->agc) >> 6); + rx_status.antenna = (hdr->signal >> 7) & 1; + rx_status.mactime = le64_to_cpu(hdr->mac_time); + } else { + struct rtl8187b_rx_hdr *hdr = + (typeof(hdr))(skb_tail_pointer(skb) - sizeof(*hdr)); + /* The Realtek datasheet for the RTL8187B shows that the RX + * header contains the following quantities: signal quality, + * RSSI, AGC, the received power in dB, and the measured SNR. + * In testing, none of these quantities show qualitative + * agreement with AP signal strength, except for the AGC, + * which is inversely proportional to the strength of the + * signal. In the following, the signal strength + * is derived from the AGC. The arbitrary scaling constants + * are chosen to make the results close to the values obtained + * for a BCM4312 using b43 as the driver. The noise is ignored + * for now. + */ + flags = le32_to_cpu(hdr->flags); + signal = 14 - hdr->agc / 2; + rx_status.antenna = (hdr->rssi >> 7) & 1; + rx_status.mactime = le64_to_cpu(hdr->mac_time); + } + + rx_status.signal = signal; + priv->signal = signal; + rate = (flags >> 20) & 0xF; + skb_trim(skb, flags & 0x0FFF); + rx_status.rate_idx = rate; + rx_status.freq = dev->conf.chandef.chan->center_freq; + rx_status.band = dev->conf.chandef.chan->band; + rx_status.flag |= RX_FLAG_MACTIME_START; + if (flags & RTL818X_RX_DESC_FLAG_CRC32_ERR) + rx_status.flag |= RX_FLAG_FAILED_FCS_CRC; + memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); + ieee80211_rx_irqsafe(dev, skb); + + skb = dev_alloc_skb(RTL8187_MAX_RX); + if (unlikely(!skb)) { + /* TODO check rx queue length and refill *somewhere* */ + return; + } + + info = (struct rtl8187_rx_info *)skb->cb; + info->urb = urb; + info->dev = dev; + urb->transfer_buffer = skb_tail_pointer(skb); + urb->context = skb; + skb_queue_tail(&priv->rx_queue, skb); + + usb_anchor_urb(urb, &priv->anchored); + if (usb_submit_urb(urb, GFP_ATOMIC)) { + usb_unanchor_urb(urb); + skb_unlink(skb, &priv->rx_queue); + dev_kfree_skb_irq(skb); + } +} + +static int rtl8187_init_urbs(struct ieee80211_hw *dev) +{ + struct rtl8187_priv *priv = dev->priv; + struct urb *entry = NULL; + struct sk_buff *skb; + struct rtl8187_rx_info *info; + int ret = 0; + + while (skb_queue_len(&priv->rx_queue) < 16) { + skb = __dev_alloc_skb(RTL8187_MAX_RX, GFP_KERNEL); + if (!skb) { + ret = -ENOMEM; + goto err; + } + entry = usb_alloc_urb(0, GFP_KERNEL); + if (!entry) { + ret = -ENOMEM; + goto err; + } + usb_fill_bulk_urb(entry, priv->udev, + usb_rcvbulkpipe(priv->udev, + priv->is_rtl8187b ? 3 : 1), + skb_tail_pointer(skb), + RTL8187_MAX_RX, rtl8187_rx_cb, skb); + info = (struct rtl8187_rx_info *)skb->cb; + info->urb = entry; + info->dev = dev; + skb_queue_tail(&priv->rx_queue, skb); + usb_anchor_urb(entry, &priv->anchored); + ret = usb_submit_urb(entry, GFP_KERNEL); + if (ret) { + skb_unlink(skb, &priv->rx_queue); + usb_unanchor_urb(entry); + goto err; + } + usb_free_urb(entry); + } + return ret; + +err: + usb_free_urb(entry); + kfree_skb(skb); + usb_kill_anchored_urbs(&priv->anchored); + return ret; +} + +static void rtl8187b_status_cb(struct urb *urb) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)urb->context; + struct rtl8187_priv *priv = hw->priv; + u64 val; + unsigned int cmd_type; + + if (unlikely(urb->status)) + return; + + /* + * Read from status buffer: + * + * bits [30:31] = cmd type: + * - 0 indicates tx beacon interrupt + * - 1 indicates tx close descriptor + * + * In the case of tx beacon interrupt: + * [0:9] = Last Beacon CW + * [10:29] = reserved + * [30:31] = 00b + * [32:63] = Last Beacon TSF + * + * If it's tx close descriptor: + * [0:7] = Packet Retry Count + * [8:14] = RTS Retry Count + * [15] = TOK + * [16:27] = Sequence No + * [28] = LS + * [29] = FS + * [30:31] = 01b + * [32:47] = unused (reserved?) + * [48:63] = MAC Used Time + */ + val = le64_to_cpu(priv->b_tx_status.buf); + + cmd_type = (val >> 30) & 0x3; + if (cmd_type == 1) { + unsigned int pkt_rc, seq_no; + bool tok; + struct sk_buff *skb; + struct ieee80211_hdr *ieee80211hdr; + unsigned long flags; + + pkt_rc = val & 0xFF; + tok = val & (1 << 15); + seq_no = (val >> 16) & 0xFFF; + + spin_lock_irqsave(&priv->b_tx_status.queue.lock, flags); + skb_queue_reverse_walk(&priv->b_tx_status.queue, skb) { + ieee80211hdr = (struct ieee80211_hdr *)skb->data; + + /* + * While testing, it was discovered that the seq_no + * doesn't actually contains the sequence number. + * Instead of returning just the 12 bits of sequence + * number, hardware is returning entire sequence control + * (fragment number plus sequence number) in a 12 bit + * only field overflowing after some time. As a + * workaround, just consider the lower bits, and expect + * it's unlikely we wrongly ack some sent data + */ + if ((le16_to_cpu(ieee80211hdr->seq_ctrl) + & 0xFFF) == seq_no) + break; + } + if (skb != (struct sk_buff *) &priv->b_tx_status.queue) { + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + __skb_unlink(skb, &priv->b_tx_status.queue); + if (tok) + info->flags |= IEEE80211_TX_STAT_ACK; + info->status.rates[0].count = pkt_rc + 1; + + ieee80211_tx_status_irqsafe(hw, skb); + } + spin_unlock_irqrestore(&priv->b_tx_status.queue.lock, flags); + } + + usb_anchor_urb(urb, &priv->anchored); + if (usb_submit_urb(urb, GFP_ATOMIC)) + usb_unanchor_urb(urb); +} + +static int rtl8187b_init_status_urb(struct ieee80211_hw *dev) +{ + struct rtl8187_priv *priv = dev->priv; + struct urb *entry; + int ret = 0; + + entry = usb_alloc_urb(0, GFP_KERNEL); + if (!entry) + return -ENOMEM; + + usb_fill_bulk_urb(entry, priv->udev, usb_rcvbulkpipe(priv->udev, 9), + &priv->b_tx_status.buf, sizeof(priv->b_tx_status.buf), + rtl8187b_status_cb, dev); + + usb_anchor_urb(entry, &priv->anchored); + ret = usb_submit_urb(entry, GFP_KERNEL); + if (ret) + usb_unanchor_urb(entry); + usb_free_urb(entry); + + return ret; +} + +static void rtl8187_set_anaparam(struct rtl8187_priv *priv, bool rfon) +{ + u32 anaparam, anaparam2; + u8 anaparam3, reg; + + if (!priv->is_rtl8187b) { + if (rfon) { + anaparam = RTL8187_RTL8225_ANAPARAM_ON; + anaparam2 = RTL8187_RTL8225_ANAPARAM2_ON; + } else { + anaparam = RTL8187_RTL8225_ANAPARAM_OFF; + anaparam2 = RTL8187_RTL8225_ANAPARAM2_OFF; + } + } else { + if (rfon) { + anaparam = RTL8187B_RTL8225_ANAPARAM_ON; + anaparam2 = RTL8187B_RTL8225_ANAPARAM2_ON; + anaparam3 = RTL8187B_RTL8225_ANAPARAM3_ON; + } else { + anaparam = RTL8187B_RTL8225_ANAPARAM_OFF; + anaparam2 = RTL8187B_RTL8225_ANAPARAM2_OFF; + anaparam3 = RTL8187B_RTL8225_ANAPARAM3_OFF; + } + } + + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, + RTL818X_EEPROM_CMD_CONFIG); + reg = rtl818x_ioread8(priv, &priv->map->CONFIG3); + reg |= RTL818X_CONFIG3_ANAPARAM_WRITE; + rtl818x_iowrite8(priv, &priv->map->CONFIG3, reg); + rtl818x_iowrite32(priv, &priv->map->ANAPARAM, anaparam); + rtl818x_iowrite32(priv, &priv->map->ANAPARAM2, anaparam2); + if (priv->is_rtl8187b) + rtl818x_iowrite8(priv, &priv->map->ANAPARAM3, anaparam3); + reg &= ~RTL818X_CONFIG3_ANAPARAM_WRITE; + rtl818x_iowrite8(priv, &priv->map->CONFIG3, reg); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, + RTL818X_EEPROM_CMD_NORMAL); +} + +static int rtl8187_cmd_reset(struct ieee80211_hw *dev) +{ + struct rtl8187_priv *priv = dev->priv; + u8 reg; + int i; + + reg = rtl818x_ioread8(priv, &priv->map->CMD); + reg &= (1 << 1); + reg |= RTL818X_CMD_RESET; + rtl818x_iowrite8(priv, &priv->map->CMD, reg); + + i = 10; + do { + msleep(2); + if (!(rtl818x_ioread8(priv, &priv->map->CMD) & + RTL818X_CMD_RESET)) + break; + } while (--i); + + if (!i) { + wiphy_err(dev->wiphy, "Reset timeout!\n"); + return -ETIMEDOUT; + } + + /* reload registers from eeprom */ + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_LOAD); + + i = 10; + do { + msleep(4); + if (!(rtl818x_ioread8(priv, &priv->map->EEPROM_CMD) & + RTL818X_EEPROM_CMD_CONFIG)) + break; + } while (--i); + + if (!i) { + wiphy_err(dev->wiphy, "eeprom reset timeout!\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int rtl8187_init_hw(struct ieee80211_hw *dev) +{ + struct rtl8187_priv *priv = dev->priv; + u8 reg; + int res; + + /* reset */ + rtl8187_set_anaparam(priv, true); + + rtl818x_iowrite16(priv, &priv->map->INT_MASK, 0); + + msleep(200); + rtl818x_iowrite8(priv, (u8 *)0xFE18, 0x10); + rtl818x_iowrite8(priv, (u8 *)0xFE18, 0x11); + rtl818x_iowrite8(priv, (u8 *)0xFE18, 0x00); + msleep(200); + + res = rtl8187_cmd_reset(dev); + if (res) + return res; + + rtl8187_set_anaparam(priv, true); + + /* setup card */ + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, 0); + rtl818x_iowrite8(priv, &priv->map->GPIO0, 0); + + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, (4 << 8)); + rtl818x_iowrite8(priv, &priv->map->GPIO0, 1); + rtl818x_iowrite8(priv, &priv->map->GP_ENABLE, 0); + + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_CONFIG); + + rtl818x_iowrite16(priv, (__le16 *)0xFFF4, 0xFFFF); + reg = rtl818x_ioread8(priv, &priv->map->CONFIG1); + reg &= 0x3F; + reg |= 0x80; + rtl818x_iowrite8(priv, &priv->map->CONFIG1, reg); + + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_NORMAL); + + rtl818x_iowrite32(priv, &priv->map->INT_TIMEOUT, 0); + rtl818x_iowrite8(priv, &priv->map->WPA_CONF, 0); + rtl818x_iowrite8(priv, &priv->map->RATE_FALLBACK, 0); + + // TODO: set RESP_RATE and BRSR properly + rtl818x_iowrite8(priv, &priv->map->RESP_RATE, (8 << 4) | 0); + rtl818x_iowrite16(priv, &priv->map->BRSR, 0x01F3); + + /* host_usb_init */ + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, 0); + rtl818x_iowrite8(priv, &priv->map->GPIO0, 0); + reg = rtl818x_ioread8(priv, (u8 *)0xFE53); + rtl818x_iowrite8(priv, (u8 *)0xFE53, reg | (1 << 7)); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, (4 << 8)); + rtl818x_iowrite8(priv, &priv->map->GPIO0, 0x20); + rtl818x_iowrite8(priv, &priv->map->GP_ENABLE, 0); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, 0x80); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, 0x80); + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, 0x80); + msleep(100); + + rtl818x_iowrite32(priv, &priv->map->RF_TIMING, 0x000a8008); + rtl818x_iowrite16(priv, &priv->map->BRSR, 0xFFFF); + rtl818x_iowrite32(priv, &priv->map->RF_PARA, 0x00100044); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, + RTL818X_EEPROM_CMD_CONFIG); + rtl818x_iowrite8(priv, &priv->map->CONFIG3, 0x44); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, + RTL818X_EEPROM_CMD_NORMAL); + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, 0x1FF7); + msleep(100); + + priv->rf->init(dev); + + rtl818x_iowrite16(priv, &priv->map->BRSR, 0x01F3); + reg = rtl818x_ioread8(priv, &priv->map->PGSELECT) & ~1; + rtl818x_iowrite8(priv, &priv->map->PGSELECT, reg | 1); + rtl818x_iowrite16(priv, (__le16 *)0xFFFE, 0x10); + rtl818x_iowrite8(priv, &priv->map->TALLY_SEL, 0x80); + rtl818x_iowrite8(priv, (u8 *)0xFFFF, 0x60); + rtl818x_iowrite8(priv, &priv->map->PGSELECT, reg); + + return 0; +} + +static const u8 rtl8187b_reg_table[][3] = { + {0xF0, 0x32, 0}, {0xF1, 0x32, 0}, {0xF2, 0x00, 0}, {0xF3, 0x00, 0}, + {0xF4, 0x32, 0}, {0xF5, 0x43, 0}, {0xF6, 0x00, 0}, {0xF7, 0x00, 0}, + {0xF8, 0x46, 0}, {0xF9, 0xA4, 0}, {0xFA, 0x00, 0}, {0xFB, 0x00, 0}, + {0xFC, 0x96, 0}, {0xFD, 0xA4, 0}, {0xFE, 0x00, 0}, {0xFF, 0x00, 0}, + + {0x58, 0x4B, 1}, {0x59, 0x00, 1}, {0x5A, 0x4B, 1}, {0x5B, 0x00, 1}, + {0x60, 0x4B, 1}, {0x61, 0x09, 1}, {0x62, 0x4B, 1}, {0x63, 0x09, 1}, + {0xCE, 0x0F, 1}, {0xCF, 0x00, 1}, {0xF0, 0x4E, 1}, {0xF1, 0x01, 1}, + {0xF2, 0x02, 1}, {0xF3, 0x03, 1}, {0xF4, 0x04, 1}, {0xF5, 0x05, 1}, + {0xF6, 0x06, 1}, {0xF7, 0x07, 1}, {0xF8, 0x08, 1}, + + {0x4E, 0x00, 2}, {0x0C, 0x04, 2}, {0x21, 0x61, 2}, {0x22, 0x68, 2}, + {0x23, 0x6F, 2}, {0x24, 0x76, 2}, {0x25, 0x7D, 2}, {0x26, 0x84, 2}, + {0x27, 0x8D, 2}, {0x4D, 0x08, 2}, {0x50, 0x05, 2}, {0x51, 0xF5, 2}, + {0x52, 0x04, 2}, {0x53, 0xA0, 2}, {0x54, 0x1F, 2}, {0x55, 0x23, 2}, + {0x56, 0x45, 2}, {0x57, 0x67, 2}, {0x58, 0x08, 2}, {0x59, 0x08, 2}, + {0x5A, 0x08, 2}, {0x5B, 0x08, 2}, {0x60, 0x08, 2}, {0x61, 0x08, 2}, + {0x62, 0x08, 2}, {0x63, 0x08, 2}, {0x64, 0xCF, 2}, + + {0x5B, 0x40, 0}, {0x84, 0x88, 0}, {0x85, 0x24, 0}, {0x88, 0x54, 0}, + {0x8B, 0xB8, 0}, {0x8C, 0x07, 0}, {0x8D, 0x00, 0}, {0x94, 0x1B, 0}, + {0x95, 0x12, 0}, {0x96, 0x00, 0}, {0x97, 0x06, 0}, {0x9D, 0x1A, 0}, + {0x9F, 0x10, 0}, {0xB4, 0x22, 0}, {0xBE, 0x80, 0}, {0xDB, 0x00, 0}, + {0xEE, 0x00, 0}, {0x4C, 0x00, 2}, + + {0x9F, 0x00, 3}, {0x8C, 0x01, 0}, {0x8D, 0x10, 0}, {0x8E, 0x08, 0}, + {0x8F, 0x00, 0} +}; + +static int rtl8187b_init_hw(struct ieee80211_hw *dev) +{ + struct rtl8187_priv *priv = dev->priv; + int res, i; + u8 reg; + + rtl8187_set_anaparam(priv, true); + + /* Reset PLL sequence on 8187B. Realtek note: reduces power + * consumption about 30 mA */ + rtl818x_iowrite8(priv, (u8 *)0xFF61, 0x10); + reg = rtl818x_ioread8(priv, (u8 *)0xFF62); + rtl818x_iowrite8(priv, (u8 *)0xFF62, reg & ~(1 << 5)); + rtl818x_iowrite8(priv, (u8 *)0xFF62, reg | (1 << 5)); + + res = rtl8187_cmd_reset(dev); + if (res) + return res; + + rtl8187_set_anaparam(priv, true); + + /* BRSR (Basic Rate Set Register) on 8187B looks to be the same as + * RESP_RATE on 8187L in Realtek sources: each bit should be each + * one of the 12 rates, all are enabled */ + rtl818x_iowrite16(priv, (__le16 *)0xFF34, 0x0FFF); + + reg = rtl818x_ioread8(priv, &priv->map->CW_CONF); + reg |= RTL818X_CW_CONF_PERPACKET_RETRY_SHIFT; + rtl818x_iowrite8(priv, &priv->map->CW_CONF, reg); + + /* Auto Rate Fallback Register (ARFR): 1M-54M setting */ + rtl818x_iowrite16_idx(priv, (__le16 *)0xFFE0, 0x0FFF, 1); + rtl818x_iowrite8_idx(priv, (u8 *)0xFFE2, 0x00, 1); + + rtl818x_iowrite16_idx(priv, (__le16 *)0xFFD4, 0xFFFF, 1); + + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, + RTL818X_EEPROM_CMD_CONFIG); + reg = rtl818x_ioread8(priv, &priv->map->CONFIG1); + rtl818x_iowrite8(priv, &priv->map->CONFIG1, (reg & 0x3F) | 0x80); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, + RTL818X_EEPROM_CMD_NORMAL); + + rtl818x_iowrite8(priv, &priv->map->WPA_CONF, 0); + for (i = 0; i < ARRAY_SIZE(rtl8187b_reg_table); i++) { + rtl818x_iowrite8_idx(priv, + (u8 *)(uintptr_t) + (rtl8187b_reg_table[i][0] | 0xFF00), + rtl8187b_reg_table[i][1], + rtl8187b_reg_table[i][2]); + } + + rtl818x_iowrite16(priv, &priv->map->TID_AC_MAP, 0xFA50); + rtl818x_iowrite16(priv, &priv->map->INT_MIG, 0); + + rtl818x_iowrite32_idx(priv, (__le32 *)0xFFF0, 0, 1); + rtl818x_iowrite32_idx(priv, (__le32 *)0xFFF4, 0, 1); + rtl818x_iowrite8_idx(priv, (u8 *)0xFFF8, 0, 1); + + rtl818x_iowrite32(priv, &priv->map->RF_TIMING, 0x00004001); + + /* RFSW_CTRL register */ + rtl818x_iowrite16_idx(priv, (__le16 *)0xFF72, 0x569A, 2); + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, 0x0480); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, 0x2488); + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, 0x1FFF); + msleep(100); + + priv->rf->init(dev); + + reg = RTL818X_CMD_TX_ENABLE | RTL818X_CMD_RX_ENABLE; + rtl818x_iowrite8(priv, &priv->map->CMD, reg); + rtl818x_iowrite16(priv, &priv->map->INT_MASK, 0xFFFF); + + rtl818x_iowrite8(priv, (u8 *)0xFE41, 0xF4); + rtl818x_iowrite8(priv, (u8 *)0xFE40, 0x00); + rtl818x_iowrite8(priv, (u8 *)0xFE42, 0x00); + rtl818x_iowrite8(priv, (u8 *)0xFE42, 0x01); + rtl818x_iowrite8(priv, (u8 *)0xFE40, 0x0F); + rtl818x_iowrite8(priv, (u8 *)0xFE42, 0x00); + rtl818x_iowrite8(priv, (u8 *)0xFE42, 0x01); + + reg = rtl818x_ioread8(priv, (u8 *)0xFFDB); + rtl818x_iowrite8(priv, (u8 *)0xFFDB, reg | (1 << 2)); + rtl818x_iowrite16_idx(priv, (__le16 *)0xFF72, 0x59FA, 3); + rtl818x_iowrite16_idx(priv, (__le16 *)0xFF74, 0x59D2, 3); + rtl818x_iowrite16_idx(priv, (__le16 *)0xFF76, 0x59D2, 3); + rtl818x_iowrite16_idx(priv, (__le16 *)0xFF78, 0x19FA, 3); + rtl818x_iowrite16_idx(priv, (__le16 *)0xFF7A, 0x19FA, 3); + rtl818x_iowrite16_idx(priv, (__le16 *)0xFF7C, 0x00D0, 3); + rtl818x_iowrite8(priv, (u8 *)0xFF61, 0); + rtl818x_iowrite8_idx(priv, (u8 *)0xFF80, 0x0F, 1); + rtl818x_iowrite8_idx(priv, (u8 *)0xFF83, 0x03, 1); + rtl818x_iowrite8(priv, (u8 *)0xFFDA, 0x10); + rtl818x_iowrite8_idx(priv, (u8 *)0xFF4D, 0x08, 2); + + rtl818x_iowrite32(priv, &priv->map->HSSI_PARA, 0x0600321B); + + rtl818x_iowrite16_idx(priv, (__le16 *)0xFFEC, 0x0800, 1); + + priv->slot_time = 0x9; + priv->aifsn[0] = 2; /* AIFSN[AC_VO] */ + priv->aifsn[1] = 2; /* AIFSN[AC_VI] */ + priv->aifsn[2] = 7; /* AIFSN[AC_BK] */ + priv->aifsn[3] = 3; /* AIFSN[AC_BE] */ + rtl818x_iowrite8(priv, &priv->map->ACM_CONTROL, 0); + + /* ENEDCA flag must always be set, transmit issues? */ + rtl818x_iowrite8(priv, &priv->map->MSR, RTL818X_MSR_ENEDCA); + + return 0; +} + +static void rtl8187_work(struct work_struct *work) +{ + /* The RTL8187 returns the retry count through register 0xFFFA. In + * addition, it appears to be a cumulative retry count, not the + * value for the current TX packet. When multiple TX entries are + * waiting in the queue, the retry count will be the total for all. + * The "error" may matter for purposes of rate setting, but there is + * no other choice with this hardware. + */ + struct rtl8187_priv *priv = container_of(work, struct rtl8187_priv, + work.work); + struct ieee80211_tx_info *info; + struct ieee80211_hw *dev = priv->dev; + static u16 retry; + u16 tmp; + u16 avg_retry; + int length; + + mutex_lock(&priv->conf_mutex); + tmp = rtl818x_ioread16(priv, (__le16 *)0xFFFA); + length = skb_queue_len(&priv->b_tx_status.queue); + if (unlikely(!length)) + length = 1; + if (unlikely(tmp < retry)) + tmp = retry; + avg_retry = (tmp - retry) / length; + while (skb_queue_len(&priv->b_tx_status.queue) > 0) { + struct sk_buff *old_skb; + + old_skb = skb_dequeue(&priv->b_tx_status.queue); + info = IEEE80211_SKB_CB(old_skb); + info->status.rates[0].count = avg_retry + 1; + if (info->status.rates[0].count > RETRY_COUNT) + info->flags &= ~IEEE80211_TX_STAT_ACK; + ieee80211_tx_status_irqsafe(dev, old_skb); + } + retry = tmp; + mutex_unlock(&priv->conf_mutex); +} + +static int rtl8187_start(struct ieee80211_hw *dev) +{ + struct rtl8187_priv *priv = dev->priv; + u32 reg; + int ret; + + mutex_lock(&priv->conf_mutex); + + ret = (!priv->is_rtl8187b) ? rtl8187_init_hw(dev) : + rtl8187b_init_hw(dev); + if (ret) + goto rtl8187_start_exit; + + init_usb_anchor(&priv->anchored); + priv->dev = dev; + + if (priv->is_rtl8187b) { + reg = RTL818X_RX_CONF_MGMT | + RTL818X_RX_CONF_DATA | + RTL818X_RX_CONF_BROADCAST | + RTL818X_RX_CONF_NICMAC | + RTL818X_RX_CONF_BSSID | + (7 << 13 /* RX FIFO threshold NONE */) | + (7 << 10 /* MAX RX DMA */) | + RTL818X_RX_CONF_RX_AUTORESETPHY | + RTL818X_RX_CONF_ONLYERLPKT | + RTL818X_RX_CONF_MULTICAST; + priv->rx_conf = reg; + rtl818x_iowrite32(priv, &priv->map->RX_CONF, reg); + + reg = rtl818x_ioread8(priv, &priv->map->TX_AGC_CTL); + reg &= ~RTL818X_TX_AGC_CTL_PERPACKET_GAIN_SHIFT; + reg &= ~RTL818X_TX_AGC_CTL_PERPACKET_ANTSEL_SHIFT; + reg &= ~RTL818X_TX_AGC_CTL_FEEDBACK_ANT; + rtl818x_iowrite8(priv, &priv->map->TX_AGC_CTL, reg); + + rtl818x_iowrite32(priv, &priv->map->TX_CONF, + RTL818X_TX_CONF_HW_SEQNUM | + RTL818X_TX_CONF_DISREQQSIZE | + (RETRY_COUNT << 8 /* short retry limit */) | + (RETRY_COUNT << 0 /* long retry limit */) | + (7 << 21 /* MAX TX DMA */)); + rtl8187_init_urbs(dev); + rtl8187b_init_status_urb(dev); + goto rtl8187_start_exit; + } + + rtl818x_iowrite16(priv, &priv->map->INT_MASK, 0xFFFF); + + rtl818x_iowrite32(priv, &priv->map->MAR[0], ~0); + rtl818x_iowrite32(priv, &priv->map->MAR[1], ~0); + + rtl8187_init_urbs(dev); + + reg = RTL818X_RX_CONF_ONLYERLPKT | + RTL818X_RX_CONF_RX_AUTORESETPHY | + RTL818X_RX_CONF_BSSID | + RTL818X_RX_CONF_MGMT | + RTL818X_RX_CONF_DATA | + (7 << 13 /* RX FIFO threshold NONE */) | + (7 << 10 /* MAX RX DMA */) | + RTL818X_RX_CONF_BROADCAST | + RTL818X_RX_CONF_NICMAC; + + priv->rx_conf = reg; + rtl818x_iowrite32(priv, &priv->map->RX_CONF, reg); + + reg = rtl818x_ioread8(priv, &priv->map->CW_CONF); + reg &= ~RTL818X_CW_CONF_PERPACKET_CW_SHIFT; + reg |= RTL818X_CW_CONF_PERPACKET_RETRY_SHIFT; + rtl818x_iowrite8(priv, &priv->map->CW_CONF, reg); + + reg = rtl818x_ioread8(priv, &priv->map->TX_AGC_CTL); + reg &= ~RTL818X_TX_AGC_CTL_PERPACKET_GAIN_SHIFT; + reg &= ~RTL818X_TX_AGC_CTL_PERPACKET_ANTSEL_SHIFT; + reg &= ~RTL818X_TX_AGC_CTL_FEEDBACK_ANT; + rtl818x_iowrite8(priv, &priv->map->TX_AGC_CTL, reg); + + reg = RTL818X_TX_CONF_CW_MIN | + (7 << 21 /* MAX TX DMA */) | + RTL818X_TX_CONF_NO_ICV; + rtl818x_iowrite32(priv, &priv->map->TX_CONF, reg); + + reg = rtl818x_ioread8(priv, &priv->map->CMD); + reg |= RTL818X_CMD_TX_ENABLE; + reg |= RTL818X_CMD_RX_ENABLE; + rtl818x_iowrite8(priv, &priv->map->CMD, reg); + INIT_DELAYED_WORK(&priv->work, rtl8187_work); + +rtl8187_start_exit: + mutex_unlock(&priv->conf_mutex); + return ret; +} + +static void rtl8187_stop(struct ieee80211_hw *dev) +{ + struct rtl8187_priv *priv = dev->priv; + struct sk_buff *skb; + u32 reg; + + mutex_lock(&priv->conf_mutex); + rtl818x_iowrite16(priv, &priv->map->INT_MASK, 0); + + reg = rtl818x_ioread8(priv, &priv->map->CMD); + reg &= ~RTL818X_CMD_TX_ENABLE; + reg &= ~RTL818X_CMD_RX_ENABLE; + rtl818x_iowrite8(priv, &priv->map->CMD, reg); + + priv->rf->stop(dev); + rtl8187_set_anaparam(priv, false); + + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_CONFIG); + reg = rtl818x_ioread8(priv, &priv->map->CONFIG4); + rtl818x_iowrite8(priv, &priv->map->CONFIG4, reg | RTL818X_CONFIG4_VCOOFF); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_NORMAL); + + while ((skb = skb_dequeue(&priv->b_tx_status.queue))) + dev_kfree_skb_any(skb); + + usb_kill_anchored_urbs(&priv->anchored); + mutex_unlock(&priv->conf_mutex); + + if (!priv->is_rtl8187b) + cancel_delayed_work_sync(&priv->work); +} + +static u64 rtl8187_get_tsf(struct ieee80211_hw *dev, struct ieee80211_vif *vif) +{ + struct rtl8187_priv *priv = dev->priv; + + return rtl818x_ioread32(priv, &priv->map->TSFT[0]) | + (u64)(rtl818x_ioread32(priv, &priv->map->TSFT[1])) << 32; +} + + +static void rtl8187_beacon_work(struct work_struct *work) +{ + struct rtl8187_vif *vif_priv = + container_of(work, struct rtl8187_vif, beacon_work.work); + struct ieee80211_vif *vif = + container_of((void *)vif_priv, struct ieee80211_vif, drv_priv); + struct ieee80211_hw *dev = vif_priv->dev; + struct ieee80211_mgmt *mgmt; + struct sk_buff *skb; + + /* don't overflow the tx ring */ + if (ieee80211_queue_stopped(dev, 0)) + goto resched; + + /* grab a fresh beacon */ + skb = ieee80211_beacon_get(dev, vif); + if (!skb) + goto resched; + + /* + * update beacon timestamp w/ TSF value + * TODO: make hardware update beacon timestamp + */ + mgmt = (struct ieee80211_mgmt *)skb->data; + mgmt->u.beacon.timestamp = cpu_to_le64(rtl8187_get_tsf(dev, vif)); + + /* TODO: use actual beacon queue */ + skb_set_queue_mapping(skb, 0); + + rtl8187_tx(dev, NULL, skb); + +resched: + /* + * schedule next beacon + * TODO: use hardware support for beacon timing + */ + schedule_delayed_work(&vif_priv->beacon_work, + usecs_to_jiffies(1024 * vif->bss_conf.beacon_int)); +} + + +static int rtl8187_add_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif) +{ + struct rtl8187_priv *priv = dev->priv; + struct rtl8187_vif *vif_priv; + int i; + int ret = -EOPNOTSUPP; + + mutex_lock(&priv->conf_mutex); + if (priv->vif) + goto exit; + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: + break; + default: + goto exit; + } + + ret = 0; + priv->vif = vif; + + /* Initialize driver private area */ + vif_priv = (struct rtl8187_vif *)&vif->drv_priv; + vif_priv->dev = dev; + INIT_DELAYED_WORK(&vif_priv->beacon_work, rtl8187_beacon_work); + vif_priv->enable_beacon = false; + + + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_CONFIG); + for (i = 0; i < ETH_ALEN; i++) + rtl818x_iowrite8(priv, &priv->map->MAC[i], + ((u8 *)vif->addr)[i]); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_NORMAL); + +exit: + mutex_unlock(&priv->conf_mutex); + return ret; +} + +static void rtl8187_remove_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif) +{ + struct rtl8187_priv *priv = dev->priv; + mutex_lock(&priv->conf_mutex); + priv->vif = NULL; + mutex_unlock(&priv->conf_mutex); +} + +static int rtl8187_config(struct ieee80211_hw *dev, u32 changed) +{ + struct rtl8187_priv *priv = dev->priv; + struct ieee80211_conf *conf = &dev->conf; + u32 reg; + + mutex_lock(&priv->conf_mutex); + reg = rtl818x_ioread32(priv, &priv->map->TX_CONF); + /* Enable TX loopback on MAC level to avoid TX during channel + * changes, as this has be seen to causes problems and the + * card will stop work until next reset + */ + rtl818x_iowrite32(priv, &priv->map->TX_CONF, + reg | RTL818X_TX_CONF_LOOPBACK_MAC); + priv->rf->set_chan(dev, conf); + msleep(10); + rtl818x_iowrite32(priv, &priv->map->TX_CONF, reg); + + rtl818x_iowrite16(priv, &priv->map->ATIM_WND, 2); + rtl818x_iowrite16(priv, &priv->map->ATIMTR_INTERVAL, 100); + rtl818x_iowrite16(priv, &priv->map->BEACON_INTERVAL, 100); + rtl818x_iowrite16(priv, &priv->map->BEACON_INTERVAL_TIME, 100); + mutex_unlock(&priv->conf_mutex); + return 0; +} + +/* + * With 8187B, AC_*_PARAM clashes with FEMR definition in struct rtl818x_csr for + * example. Thus we have to use raw values for AC_*_PARAM register addresses. + */ +static __le32 *rtl8187b_ac_addr[4] = { + (__le32 *) 0xFFF0, /* AC_VO */ + (__le32 *) 0xFFF4, /* AC_VI */ + (__le32 *) 0xFFFC, /* AC_BK */ + (__le32 *) 0xFFF8, /* AC_BE */ +}; + +#define SIFS_TIME 0xa + +static void rtl8187_conf_erp(struct rtl8187_priv *priv, bool use_short_slot, + bool use_short_preamble) +{ + if (priv->is_rtl8187b) { + u8 difs, eifs; + u16 ack_timeout; + int queue; + + if (use_short_slot) { + priv->slot_time = 0x9; + difs = 0x1c; + eifs = 0x53; + } else { + priv->slot_time = 0x14; + difs = 0x32; + eifs = 0x5b; + } + rtl818x_iowrite8(priv, &priv->map->SIFS, 0x22); + rtl818x_iowrite8(priv, &priv->map->SLOT, priv->slot_time); + rtl818x_iowrite8(priv, &priv->map->DIFS, difs); + + /* + * BRSR+1 on 8187B is in fact EIFS register + * Value in units of 4 us + */ + rtl818x_iowrite8(priv, (u8 *)&priv->map->BRSR + 1, eifs); + + /* + * For 8187B, CARRIER_SENSE_COUNTER is in fact ack timeout + * register. In units of 4 us like eifs register + * ack_timeout = ack duration + plcp + difs + preamble + */ + ack_timeout = 112 + 48 + difs; + if (use_short_preamble) + ack_timeout += 72; + else + ack_timeout += 144; + rtl818x_iowrite8(priv, &priv->map->CARRIER_SENSE_COUNTER, + DIV_ROUND_UP(ack_timeout, 4)); + + for (queue = 0; queue < 4; queue++) + rtl818x_iowrite8(priv, (u8 *) rtl8187b_ac_addr[queue], + priv->aifsn[queue] * priv->slot_time + + SIFS_TIME); + } else { + rtl818x_iowrite8(priv, &priv->map->SIFS, 0x22); + if (use_short_slot) { + rtl818x_iowrite8(priv, &priv->map->SLOT, 0x9); + rtl818x_iowrite8(priv, &priv->map->DIFS, 0x14); + rtl818x_iowrite8(priv, &priv->map->EIFS, 91 - 0x14); + } else { + rtl818x_iowrite8(priv, &priv->map->SLOT, 0x14); + rtl818x_iowrite8(priv, &priv->map->DIFS, 0x24); + rtl818x_iowrite8(priv, &priv->map->EIFS, 91 - 0x24); + } + } +} + +static void rtl8187_bss_info_changed(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u32 changed) +{ + struct rtl8187_priv *priv = dev->priv; + struct rtl8187_vif *vif_priv; + int i; + u8 reg; + + vif_priv = (struct rtl8187_vif *)&vif->drv_priv; + + if (changed & BSS_CHANGED_BSSID) { + mutex_lock(&priv->conf_mutex); + for (i = 0; i < ETH_ALEN; i++) + rtl818x_iowrite8(priv, &priv->map->BSSID[i], + info->bssid[i]); + + if (priv->is_rtl8187b) + reg = RTL818X_MSR_ENEDCA; + else + reg = 0; + + if (is_valid_ether_addr(info->bssid)) { + if (vif->type == NL80211_IFTYPE_ADHOC) + reg |= RTL818X_MSR_ADHOC; + else + reg |= RTL818X_MSR_INFRA; + } + else + reg |= RTL818X_MSR_NO_LINK; + + rtl818x_iowrite8(priv, &priv->map->MSR, reg); + + mutex_unlock(&priv->conf_mutex); + } + + if (changed & (BSS_CHANGED_ERP_SLOT | BSS_CHANGED_ERP_PREAMBLE)) + rtl8187_conf_erp(priv, info->use_short_slot, + info->use_short_preamble); + + if (changed & BSS_CHANGED_BEACON_ENABLED) + vif_priv->enable_beacon = info->enable_beacon; + + if (changed & (BSS_CHANGED_BEACON_ENABLED | BSS_CHANGED_BEACON)) { + cancel_delayed_work_sync(&vif_priv->beacon_work); + if (vif_priv->enable_beacon) + schedule_work(&vif_priv->beacon_work.work); + } + +} + +static u64 rtl8187_prepare_multicast(struct ieee80211_hw *dev, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + struct netdev_hw_addr_list *mc_list) +#else + int mc_count, struct dev_addr_list *mc_list) +#endif +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + return netdev_hw_addr_list_count(mc_list); +#else + return mc_count; +#endif +} + +static void rtl8187_configure_filter(struct ieee80211_hw *dev, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + struct rtl8187_priv *priv = dev->priv; + + if (changed_flags & FIF_FCSFAIL) + priv->rx_conf ^= RTL818X_RX_CONF_FCS; + if (changed_flags & FIF_CONTROL) + priv->rx_conf ^= RTL818X_RX_CONF_CTRL; + if (changed_flags & FIF_OTHER_BSS) + priv->rx_conf ^= RTL818X_RX_CONF_MONITOR; + if (*total_flags & FIF_ALLMULTI || multicast > 0) + priv->rx_conf |= RTL818X_RX_CONF_MULTICAST; + else + priv->rx_conf &= ~RTL818X_RX_CONF_MULTICAST; + + *total_flags = 0; + + if (priv->rx_conf & RTL818X_RX_CONF_FCS) + *total_flags |= FIF_FCSFAIL; + if (priv->rx_conf & RTL818X_RX_CONF_CTRL) + *total_flags |= FIF_CONTROL; + if (priv->rx_conf & RTL818X_RX_CONF_MONITOR) + *total_flags |= FIF_OTHER_BSS; + if (priv->rx_conf & RTL818X_RX_CONF_MULTICAST) + *total_flags |= FIF_ALLMULTI; + + rtl818x_iowrite32_async(priv, &priv->map->RX_CONF, priv->rx_conf); +} + +static int rtl8187_conf_tx(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct rtl8187_priv *priv = dev->priv; + u8 cw_min, cw_max; + + if (queue > 3) + return -EINVAL; + + cw_min = fls(params->cw_min); + cw_max = fls(params->cw_max); + + if (priv->is_rtl8187b) { + priv->aifsn[queue] = params->aifs; + + /* + * This is the structure of AC_*_PARAM registers in 8187B: + * - TXOP limit field, bit offset = 16 + * - ECWmax, bit offset = 12 + * - ECWmin, bit offset = 8 + * - AIFS, bit offset = 0 + */ + rtl818x_iowrite32(priv, rtl8187b_ac_addr[queue], + (params->txop << 16) | (cw_max << 12) | + (cw_min << 8) | (params->aifs * + priv->slot_time + SIFS_TIME)); + } else { + if (queue != 0) + return -EINVAL; + + rtl818x_iowrite8(priv, &priv->map->CW_VAL, + cw_min | (cw_max << 4)); + } + return 0; +} + + +static const struct ieee80211_ops rtl8187_ops = { + .tx = rtl8187_tx, + .start = rtl8187_start, + .stop = rtl8187_stop, + .add_interface = rtl8187_add_interface, + .remove_interface = rtl8187_remove_interface, + .config = rtl8187_config, + .bss_info_changed = rtl8187_bss_info_changed, + .prepare_multicast = rtl8187_prepare_multicast, + .configure_filter = rtl8187_configure_filter, + .conf_tx = rtl8187_conf_tx, + .rfkill_poll = rtl8187_rfkill_poll, + .get_tsf = rtl8187_get_tsf, +}; + +static void rtl8187_eeprom_register_read(struct eeprom_93cx6 *eeprom) +{ + struct ieee80211_hw *dev = eeprom->data; + struct rtl8187_priv *priv = dev->priv; + u8 reg = rtl818x_ioread8(priv, &priv->map->EEPROM_CMD); + + eeprom->reg_data_in = reg & RTL818X_EEPROM_CMD_WRITE; + eeprom->reg_data_out = reg & RTL818X_EEPROM_CMD_READ; + eeprom->reg_data_clock = reg & RTL818X_EEPROM_CMD_CK; + eeprom->reg_chip_select = reg & RTL818X_EEPROM_CMD_CS; +} + +static void rtl8187_eeprom_register_write(struct eeprom_93cx6 *eeprom) +{ + struct ieee80211_hw *dev = eeprom->data; + struct rtl8187_priv *priv = dev->priv; + u8 reg = RTL818X_EEPROM_CMD_PROGRAM; + + if (eeprom->reg_data_in) + reg |= RTL818X_EEPROM_CMD_WRITE; + if (eeprom->reg_data_out) + reg |= RTL818X_EEPROM_CMD_READ; + if (eeprom->reg_data_clock) + reg |= RTL818X_EEPROM_CMD_CK; + if (eeprom->reg_chip_select) + reg |= RTL818X_EEPROM_CMD_CS; + + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, reg); + udelay(10); +} + +static int rtl8187_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct ieee80211_hw *dev; + struct rtl8187_priv *priv; + struct eeprom_93cx6 eeprom; + struct ieee80211_channel *channel; + const char *chip_name; + u16 txpwr, reg; + u16 product_id = le16_to_cpu(udev->descriptor.idProduct); + int err, i; + u8 mac_addr[ETH_ALEN]; + + dev = ieee80211_alloc_hw(sizeof(*priv), &rtl8187_ops); + if (!dev) { + printk(KERN_ERR "rtl8187: ieee80211 alloc failed\n"); + return -ENOMEM; + } + + priv = dev->priv; + priv->is_rtl8187b = (id->driver_info == DEVICE_RTL8187B); + + /* allocate "DMA aware" buffer for register accesses */ + priv->io_dmabuf = kmalloc(sizeof(*priv->io_dmabuf), GFP_KERNEL); + if (!priv->io_dmabuf) { + err = -ENOMEM; + goto err_free_dev; + } + mutex_init(&priv->io_mutex); + + SET_IEEE80211_DEV(dev, &intf->dev); + usb_set_intfdata(intf, dev); + priv->udev = udev; + + usb_get_dev(udev); + + skb_queue_head_init(&priv->rx_queue); + + BUILD_BUG_ON(sizeof(priv->channels) != sizeof(rtl818x_channels)); + BUILD_BUG_ON(sizeof(priv->rates) != sizeof(rtl818x_rates)); + + memcpy(priv->channels, rtl818x_channels, sizeof(rtl818x_channels)); + memcpy(priv->rates, rtl818x_rates, sizeof(rtl818x_rates)); + priv->map = (struct rtl818x_csr *)0xFF00; + + priv->band.band = IEEE80211_BAND_2GHZ; + priv->band.channels = priv->channels; + priv->band.n_channels = ARRAY_SIZE(rtl818x_channels); + priv->band.bitrates = priv->rates; + priv->band.n_bitrates = ARRAY_SIZE(rtl818x_rates); + dev->wiphy->bands[IEEE80211_BAND_2GHZ] = &priv->band; + + + dev->flags = IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING | + IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_RX_INCLUDES_FCS; + /* Initialize rate-control variables */ + dev->max_rates = 1; + dev->max_rate_tries = RETRY_COUNT; + + eeprom.data = dev; + eeprom.register_read = rtl8187_eeprom_register_read; + eeprom.register_write = rtl8187_eeprom_register_write; + if (rtl818x_ioread32(priv, &priv->map->RX_CONF) & (1 << 6)) + eeprom.width = PCI_EEPROM_WIDTH_93C66; + else + eeprom.width = PCI_EEPROM_WIDTH_93C46; + + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_CONFIG); + udelay(10); + + eeprom_93cx6_multiread(&eeprom, RTL8187_EEPROM_MAC_ADDR, + (__le16 __force *)mac_addr, 3); + if (!is_valid_ether_addr(mac_addr)) { + printk(KERN_WARNING "rtl8187: Invalid hwaddr! Using randomly " + "generated MAC address\n"); + eth_random_addr(mac_addr); + } + SET_IEEE80211_PERM_ADDR(dev, mac_addr); + + channel = priv->channels; + for (i = 0; i < 3; i++) { + eeprom_93cx6_read(&eeprom, RTL8187_EEPROM_TXPWR_CHAN_1 + i, + &txpwr); + (*channel++).hw_value = txpwr & 0xFF; + (*channel++).hw_value = txpwr >> 8; + } + for (i = 0; i < 2; i++) { + eeprom_93cx6_read(&eeprom, RTL8187_EEPROM_TXPWR_CHAN_4 + i, + &txpwr); + (*channel++).hw_value = txpwr & 0xFF; + (*channel++).hw_value = txpwr >> 8; + } + + eeprom_93cx6_read(&eeprom, RTL8187_EEPROM_TXPWR_BASE, + &priv->txpwr_base); + + reg = rtl818x_ioread8(priv, &priv->map->PGSELECT) & ~1; + rtl818x_iowrite8(priv, &priv->map->PGSELECT, reg | 1); + /* 0 means asic B-cut, we should use SW 3 wire + * bit-by-bit banging for radio. 1 means we can use + * USB specific request to write radio registers */ + priv->asic_rev = rtl818x_ioread8(priv, (u8 *)0xFFFE) & 0x3; + rtl818x_iowrite8(priv, &priv->map->PGSELECT, reg); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_NORMAL); + + if (!priv->is_rtl8187b) { + u32 reg32; + reg32 = rtl818x_ioread32(priv, &priv->map->TX_CONF); + reg32 &= RTL818X_TX_CONF_HWVER_MASK; + switch (reg32) { + case RTL818X_TX_CONF_R8187vD_B: + /* Some RTL8187B devices have a USB ID of 0x8187 + * detect them here */ + chip_name = "RTL8187BvB(early)"; + priv->is_rtl8187b = 1; + priv->hw_rev = RTL8187BvB; + break; + case RTL818X_TX_CONF_R8187vD: + chip_name = "RTL8187vD"; + break; + default: + chip_name = "RTL8187vB (default)"; + } + } else { + /* + * Force USB request to write radio registers for 8187B, Realtek + * only uses it in their sources + */ + /*if (priv->asic_rev == 0) { + printk(KERN_WARNING "rtl8187: Forcing use of USB " + "requests to write to radio registers\n"); + priv->asic_rev = 1; + }*/ + switch (rtl818x_ioread8(priv, (u8 *)0xFFE1)) { + case RTL818X_R8187B_B: + chip_name = "RTL8187BvB"; + priv->hw_rev = RTL8187BvB; + break; + case RTL818X_R8187B_D: + chip_name = "RTL8187BvD"; + priv->hw_rev = RTL8187BvD; + break; + case RTL818X_R8187B_E: + chip_name = "RTL8187BvE"; + priv->hw_rev = RTL8187BvE; + break; + default: + chip_name = "RTL8187BvB (default)"; + priv->hw_rev = RTL8187BvB; + } + } + + if (!priv->is_rtl8187b) { + for (i = 0; i < 2; i++) { + eeprom_93cx6_read(&eeprom, + RTL8187_EEPROM_TXPWR_CHAN_6 + i, + &txpwr); + (*channel++).hw_value = txpwr & 0xFF; + (*channel++).hw_value = txpwr >> 8; + } + } else { + eeprom_93cx6_read(&eeprom, RTL8187_EEPROM_TXPWR_CHAN_6, + &txpwr); + (*channel++).hw_value = txpwr & 0xFF; + + eeprom_93cx6_read(&eeprom, 0x0A, &txpwr); + (*channel++).hw_value = txpwr & 0xFF; + + eeprom_93cx6_read(&eeprom, 0x1C, &txpwr); + (*channel++).hw_value = txpwr & 0xFF; + (*channel++).hw_value = txpwr >> 8; + } + /* Handle the differing rfkill GPIO bit in different models */ + priv->rfkill_mask = RFKILL_MASK_8187_89_97; + if (product_id == 0x8197 || product_id == 0x8198) { + eeprom_93cx6_read(&eeprom, RTL8187_EEPROM_SELECT_GPIO, ®); + if (reg & 0xFF00) + priv->rfkill_mask = RFKILL_MASK_8198; + } + dev->vif_data_size = sizeof(struct rtl8187_vif); + dev->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) ; + + if ((id->driver_info == DEVICE_RTL8187) && priv->is_rtl8187b) + printk(KERN_INFO "rtl8187: inconsistency between id with OEM" + " info!\n"); + + priv->rf = rtl8187_detect_rf(dev); + dev->extra_tx_headroom = (!priv->is_rtl8187b) ? + sizeof(struct rtl8187_tx_hdr) : + sizeof(struct rtl8187b_tx_hdr); + if (!priv->is_rtl8187b) + dev->queues = 1; + else + dev->queues = 4; + + err = ieee80211_register_hw(dev); + if (err) { + printk(KERN_ERR "rtl8187: Cannot register device\n"); + goto err_free_dmabuf; + } + mutex_init(&priv->conf_mutex); + skb_queue_head_init(&priv->b_tx_status.queue); + + wiphy_info(dev->wiphy, "hwaddr %pM, %s V%d + %s, rfkill mask %d\n", + mac_addr, chip_name, priv->asic_rev, priv->rf->name, + priv->rfkill_mask); + +#ifdef CPTCFG_RTL8187_LEDS + eeprom_93cx6_read(&eeprom, 0x3F, ®); + reg &= 0xFF; + rtl8187_leds_init(dev, reg); +#endif + rtl8187_rfkill_init(dev); + + return 0; + + err_free_dmabuf: + kfree(priv->io_dmabuf); + err_free_dev: + ieee80211_free_hw(dev); + usb_set_intfdata(intf, NULL); + usb_put_dev(udev); + return err; +} + +static void rtl8187_disconnect(struct usb_interface *intf) +{ + struct ieee80211_hw *dev = usb_get_intfdata(intf); + struct rtl8187_priv *priv; + + if (!dev) + return; + +#ifdef CPTCFG_RTL8187_LEDS + rtl8187_leds_exit(dev); +#endif + rtl8187_rfkill_exit(dev); + ieee80211_unregister_hw(dev); + + priv = dev->priv; + usb_reset_device(priv->udev); + usb_put_dev(interface_to_usbdev(intf)); + kfree(priv->io_dmabuf); + ieee80211_free_hw(dev); +} + +static struct usb_driver rtl8187_driver = { + .name = KBUILD_MODNAME, + .id_table = rtl8187_table, + .probe = rtl8187_probe, + .disconnect = rtl8187_disconnect, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +module_usb_driver(rtl8187_driver); diff --git a/drivers/net/wireless/rtl818x/rtl8187/leds.c b/drivers/net/wireless/rtl818x/rtl8187/leds.c new file mode 100644 index 0000000..5235a89 --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8187/leds.c @@ -0,0 +1,245 @@ +/* + * Linux LED driver for RTL8187 + * + * Copyright 2009 Larry Finger + * + * Based on the LED handling in the r8187 driver, which is: + * Copyright (c) Realtek Semiconductor Corp. All rights reserved. + * + * Thanks to Realtek for their support! + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifdef CPTCFG_RTL8187_LEDS + +#include +#include +#include + +#include "rtl8187.h" +#include "leds.h" + +static void led_turn_on(struct work_struct *work) +{ + /* As this routine does read/write operations on the hardware, it must + * be run from a work queue. + */ + u8 reg; + struct rtl8187_priv *priv = container_of(work, struct rtl8187_priv, + led_on.work); + struct rtl8187_led *led = &priv->led_tx; + + /* Don't change the LED, when the device is down. */ + if (!priv->vif || priv->vif->type == NL80211_IFTYPE_UNSPECIFIED) + return ; + + /* Skip if the LED is not registered. */ + if (!led->dev) + return; + mutex_lock(&priv->conf_mutex); + switch (led->ledpin) { + case LED_PIN_GPIO0: + rtl818x_iowrite8(priv, &priv->map->GPIO0, 0x01); + rtl818x_iowrite8(priv, &priv->map->GP_ENABLE, 0x00); + break; + case LED_PIN_LED0: + reg = rtl818x_ioread8(priv, &priv->map->PGSELECT) & ~(1 << 4); + rtl818x_iowrite8(priv, &priv->map->PGSELECT, reg); + break; + case LED_PIN_LED1: + reg = rtl818x_ioread8(priv, &priv->map->PGSELECT) & ~(1 << 5); + rtl818x_iowrite8(priv, &priv->map->PGSELECT, reg); + break; + case LED_PIN_HW: + default: + break; + } + mutex_unlock(&priv->conf_mutex); +} + +static void led_turn_off(struct work_struct *work) +{ + /* As this routine does read/write operations on the hardware, it must + * be run from a work queue. + */ + u8 reg; + struct rtl8187_priv *priv = container_of(work, struct rtl8187_priv, + led_off.work); + struct rtl8187_led *led = &priv->led_tx; + + /* Don't change the LED, when the device is down. */ + if (!priv->vif || priv->vif->type == NL80211_IFTYPE_UNSPECIFIED) + return ; + + /* Skip if the LED is not registered. */ + if (!led->dev) + return; + mutex_lock(&priv->conf_mutex); + switch (led->ledpin) { + case LED_PIN_GPIO0: + rtl818x_iowrite8(priv, &priv->map->GPIO0, 0x01); + rtl818x_iowrite8(priv, &priv->map->GP_ENABLE, 0x01); + break; + case LED_PIN_LED0: + reg = rtl818x_ioread8(priv, &priv->map->PGSELECT) | (1 << 4); + rtl818x_iowrite8(priv, &priv->map->PGSELECT, reg); + break; + case LED_PIN_LED1: + reg = rtl818x_ioread8(priv, &priv->map->PGSELECT) | (1 << 5); + rtl818x_iowrite8(priv, &priv->map->PGSELECT, reg); + break; + case LED_PIN_HW: + default: + break; + } + mutex_unlock(&priv->conf_mutex); +} + +/* Callback from the LED subsystem. */ +static void rtl8187_led_brightness_set(struct led_classdev *led_dev, + enum led_brightness brightness) +{ + struct rtl8187_led *led = container_of(led_dev, struct rtl8187_led, + led_dev); + struct ieee80211_hw *hw = led->dev; + struct rtl8187_priv *priv; + static bool radio_on; + + if (!hw) + return; + priv = hw->priv; + if (led->is_radio) { + if (brightness == LED_FULL) { + ieee80211_queue_delayed_work(hw, &priv->led_on, 0); + radio_on = true; + } else if (radio_on) { + radio_on = false; + cancel_delayed_work(&priv->led_on); + ieee80211_queue_delayed_work(hw, &priv->led_off, 0); + } + } else if (radio_on) { + if (brightness == LED_OFF) { + ieee80211_queue_delayed_work(hw, &priv->led_off, 0); + /* The LED is off for 1/20 sec - it just blinks. */ + ieee80211_queue_delayed_work(hw, &priv->led_on, + HZ / 20); + } else + ieee80211_queue_delayed_work(hw, &priv->led_on, 0); + } +} + +static int rtl8187_register_led(struct ieee80211_hw *dev, + struct rtl8187_led *led, const char *name, + const char *default_trigger, u8 ledpin, + bool is_radio) +{ + int err; + struct rtl8187_priv *priv = dev->priv; + + if (led->dev) + return -EEXIST; + if (!default_trigger) + return -EINVAL; + led->dev = dev; + led->ledpin = ledpin; + led->is_radio = is_radio; + strncpy(led->name, name, sizeof(led->name)); + + led->led_dev.name = led->name; + led->led_dev.default_trigger = default_trigger; + led->led_dev.brightness_set = rtl8187_led_brightness_set; + + err = led_classdev_register(&priv->udev->dev, &led->led_dev); + if (err) { + printk(KERN_INFO "LEDs: Failed to register %s\n", name); + led->dev = NULL; + return err; + } + return 0; +} + +static void rtl8187_unregister_led(struct rtl8187_led *led) +{ + struct ieee80211_hw *hw = led->dev; + struct rtl8187_priv *priv = hw->priv; + + led_classdev_unregister(&led->led_dev); + flush_delayed_work(&priv->led_off); + led->dev = NULL; +} + +void rtl8187_leds_init(struct ieee80211_hw *dev, u16 custid) +{ + struct rtl8187_priv *priv = dev->priv; + char name[RTL8187_LED_MAX_NAME_LEN + 1]; + u8 ledpin; + int err; + + /* According to the vendor driver, the LED operation depends on the + * customer ID encoded in the EEPROM + */ + printk(KERN_INFO "rtl8187: Customer ID is 0x%02X\n", custid); + switch (custid) { + case EEPROM_CID_RSVD0: + case EEPROM_CID_RSVD1: + case EEPROM_CID_SERCOMM_PS: + case EEPROM_CID_QMI: + case EEPROM_CID_DELL: + case EEPROM_CID_TOSHIBA: + ledpin = LED_PIN_GPIO0; + break; + case EEPROM_CID_ALPHA0: + ledpin = LED_PIN_LED0; + break; + case EEPROM_CID_HW: + ledpin = LED_PIN_HW; + break; + default: + ledpin = LED_PIN_GPIO0; + } + + INIT_DELAYED_WORK(&priv->led_on, led_turn_on); + INIT_DELAYED_WORK(&priv->led_off, led_turn_off); + + snprintf(name, sizeof(name), + "rtl8187-%s::radio", wiphy_name(dev->wiphy)); + err = rtl8187_register_led(dev, &priv->led_radio, name, + ieee80211_get_radio_led_name(dev), ledpin, true); + if (err) + return; + + snprintf(name, sizeof(name), + "rtl8187-%s::tx", wiphy_name(dev->wiphy)); + err = rtl8187_register_led(dev, &priv->led_tx, name, + ieee80211_get_tx_led_name(dev), ledpin, false); + if (err) + goto err_tx; + + snprintf(name, sizeof(name), + "rtl8187-%s::rx", wiphy_name(dev->wiphy)); + err = rtl8187_register_led(dev, &priv->led_rx, name, + ieee80211_get_rx_led_name(dev), ledpin, false); + if (!err) + return; + + /* registration of RX LED failed - unregister */ + rtl8187_unregister_led(&priv->led_tx); +err_tx: + rtl8187_unregister_led(&priv->led_radio); +} + +void rtl8187_leds_exit(struct ieee80211_hw *dev) +{ + struct rtl8187_priv *priv = dev->priv; + + rtl8187_unregister_led(&priv->led_radio); + rtl8187_unregister_led(&priv->led_rx); + rtl8187_unregister_led(&priv->led_tx); + cancel_delayed_work_sync(&priv->led_off); + cancel_delayed_work_sync(&priv->led_on); +} +#endif /* def CPTCFG_RTL8187_LEDS */ + diff --git a/drivers/net/wireless/rtl818x/rtl8187/leds.h b/drivers/net/wireless/rtl818x/rtl8187/leds.h new file mode 100644 index 0000000..2f0d505 --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8187/leds.h @@ -0,0 +1,59 @@ +/* + * Definitions for RTL8187 leds + * + * Copyright 2009 Larry Finger + * + * Based on the LED handling in the r8187 driver, which is: + * Copyright (c) Realtek Semiconductor Corp. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef RTL8187_LED_H +#define RTL8187_LED_H + +#ifdef CPTCFG_RTL8187_LEDS + +#define RTL8187_LED_MAX_NAME_LEN 21 + +#include +#include + +enum { + LED_PIN_LED0, + LED_PIN_LED1, + LED_PIN_GPIO0, + LED_PIN_HW +}; + +enum { + EEPROM_CID_RSVD0 = 0x00, + EEPROM_CID_RSVD1 = 0xFF, + EEPROM_CID_ALPHA0 = 0x01, + EEPROM_CID_SERCOMM_PS = 0x02, + EEPROM_CID_HW = 0x03, + EEPROM_CID_TOSHIBA = 0x04, + EEPROM_CID_QMI = 0x07, + EEPROM_CID_DELL = 0x08 +}; + +struct rtl8187_led { + struct ieee80211_hw *dev; + /* The LED class device */ + struct led_classdev led_dev; + /* The pin/method used to control the led */ + u8 ledpin; + /* The unique name string for this LED device. */ + char name[RTL8187_LED_MAX_NAME_LEN + 1]; + /* If the LED is radio or tx/rx */ + bool is_radio; +}; + +void rtl8187_leds_init(struct ieee80211_hw *dev, u16 code); +void rtl8187_leds_exit(struct ieee80211_hw *dev); + +#endif /* def CPTCFG_RTL8187_LEDS */ + +#endif /* RTL8187_LED_H */ diff --git a/drivers/net/wireless/rtl818x/rtl8187/rfkill.c b/drivers/net/wireless/rtl818x/rtl8187/rfkill.c new file mode 100644 index 0000000..3411671 --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8187/rfkill.c @@ -0,0 +1,64 @@ +/* + * Linux RFKILL support for RTL8187 + * + * Copyright (c) 2009 Herton Ronaldo Krzesinski + * + * Based on the RFKILL handling in the r8187 driver, which is: + * Copyright (c) Realtek Semiconductor Corp. All rights reserved. + * + * Thanks to Realtek for their support! + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#include "rtl8187.h" +#include "rfkill.h" + +static bool rtl8187_is_radio_enabled(struct rtl8187_priv *priv) +{ + u8 gpio; + + gpio = rtl818x_ioread8(priv, &priv->map->GPIO0); + rtl818x_iowrite8(priv, &priv->map->GPIO0, gpio & ~priv->rfkill_mask); + gpio = rtl818x_ioread8(priv, &priv->map->GPIO1); + + return gpio & priv->rfkill_mask; +} + +void rtl8187_rfkill_init(struct ieee80211_hw *hw) +{ + struct rtl8187_priv *priv = hw->priv; + + priv->rfkill_off = rtl8187_is_radio_enabled(priv); + printk(KERN_INFO "rtl8187: wireless switch is %s\n", + priv->rfkill_off ? "on" : "off"); + wiphy_rfkill_set_hw_state(hw->wiphy, !priv->rfkill_off); + wiphy_rfkill_start_polling(hw->wiphy); +} + +void rtl8187_rfkill_poll(struct ieee80211_hw *hw) +{ + bool enabled; + struct rtl8187_priv *priv = hw->priv; + + mutex_lock(&priv->conf_mutex); + enabled = rtl8187_is_radio_enabled(priv); + if (unlikely(enabled != priv->rfkill_off)) { + priv->rfkill_off = enabled; + printk(KERN_INFO "rtl8187: wireless radio switch turned %s\n", + enabled ? "on" : "off"); + wiphy_rfkill_set_hw_state(hw->wiphy, !enabled); + } + mutex_unlock(&priv->conf_mutex); +} + +void rtl8187_rfkill_exit(struct ieee80211_hw *hw) +{ + wiphy_rfkill_stop_polling(hw->wiphy); +} diff --git a/drivers/net/wireless/rtl818x/rtl8187/rfkill.h b/drivers/net/wireless/rtl818x/rtl8187/rfkill.h new file mode 100644 index 0000000..e12575e --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8187/rfkill.h @@ -0,0 +1,8 @@ +#ifndef RTL8187_RFKILL_H +#define RTL8187_RFKILL_H + +void rtl8187_rfkill_init(struct ieee80211_hw *hw); +void rtl8187_rfkill_poll(struct ieee80211_hw *hw); +void rtl8187_rfkill_exit(struct ieee80211_hw *hw); + +#endif /* RTL8187_RFKILL_H */ diff --git a/drivers/net/wireless/rtl818x/rtl8187/rtl8187.h b/drivers/net/wireless/rtl818x/rtl8187/rtl8187.h new file mode 100644 index 0000000..f47ea86 --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8187/rtl8187.h @@ -0,0 +1,282 @@ +/* + * Definitions for RTL8187 hardware + * + * Copyright 2007 Michael Wu + * Copyright 2007 Andrea Merello + * + * Based on the r8187 driver, which is: + * Copyright 2005 Andrea Merello , et al. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef RTL8187_H +#define RTL8187_H + +#include "rtl818x.h" +#include "leds.h" + +#define RTL8187_EEPROM_TXPWR_BASE 0x05 +#define RTL8187_EEPROM_MAC_ADDR 0x07 +#define RTL8187_EEPROM_TXPWR_CHAN_1 0x16 /* 3 channels */ +#define RTL8187_EEPROM_TXPWR_CHAN_6 0x1B /* 2 channels */ +#define RTL8187_EEPROM_TXPWR_CHAN_4 0x3D /* 2 channels */ +#define RTL8187_EEPROM_SELECT_GPIO 0x3B + +#define RTL8187_REQT_READ 0xC0 +#define RTL8187_REQT_WRITE 0x40 +#define RTL8187_REQ_GET_REG 0x05 +#define RTL8187_REQ_SET_REG 0x05 + +#define RTL8187_MAX_RX 0x9C4 + +#define RFKILL_MASK_8187_89_97 0x2 +#define RFKILL_MASK_8198 0x4 + +#define RETRY_COUNT 7 + +struct rtl8187_rx_info { + struct urb *urb; + struct ieee80211_hw *dev; +}; + +struct rtl8187_rx_hdr { + __le32 flags; + u8 noise; + u8 signal; + u8 agc; + u8 reserved; + __le64 mac_time; +} __packed; + +struct rtl8187b_rx_hdr { + __le32 flags; + __le64 mac_time; + u8 sq; + u8 rssi; + u8 agc; + u8 flags2; + __le16 snr_long2end; + s8 pwdb_g12; + u8 fot; +} __packed; + +/* {rtl8187,rtl8187b}_tx_info is in skb */ + +struct rtl8187_tx_hdr { + __le32 flags; + __le16 rts_duration; + __le16 len; + __le32 retry; +} __packed; + +struct rtl8187b_tx_hdr { + __le32 flags; + __le16 rts_duration; + __le16 len; + __le32 unused_1; + __le16 unused_2; + __le16 tx_duration; + __le32 unused_3; + __le32 retry; + __le32 unused_4[2]; +} __packed; + +enum { + DEVICE_RTL8187, + DEVICE_RTL8187B +}; + +struct rtl8187_vif { + struct ieee80211_hw *dev; + + /* beaconing */ + struct delayed_work beacon_work; + bool enable_beacon; +}; + +struct rtl8187_priv { + /* common between rtl818x drivers */ + struct rtl818x_csr *map; + const struct rtl818x_rf_ops *rf; + struct ieee80211_vif *vif; + + /* The mutex protects the TX loopback state. + * Any attempt to set channels concurrently locks the device. + */ + struct mutex conf_mutex; + + /* rtl8187 specific */ + struct ieee80211_channel channels[14]; + struct ieee80211_rate rates[12]; + struct ieee80211_supported_band band; + struct usb_device *udev; + u32 rx_conf; + struct usb_anchor anchored; + struct delayed_work work; + struct ieee80211_hw *dev; +#ifdef CPTCFG_RTL8187_LEDS + struct rtl8187_led led_radio; + struct rtl8187_led led_tx; + struct rtl8187_led led_rx; + struct delayed_work led_on; + struct delayed_work led_off; +#endif + u16 txpwr_base; + u8 asic_rev; + u8 is_rtl8187b; + enum { + RTL8187BvB, + RTL8187BvD, + RTL8187BvE + } hw_rev; + struct sk_buff_head rx_queue; + u8 signal; + u8 noise; + u8 slot_time; + u8 aifsn[4]; + u8 rfkill_mask; + struct { + __le64 buf; + struct sk_buff_head queue; + } b_tx_status; /* This queue is used by both -b and non-b devices */ + struct mutex io_mutex; + union { + u8 bits8; + __le16 bits16; + __le32 bits32; + } *io_dmabuf; + bool rfkill_off; + u16 seqno; +}; + +void rtl8187_write_phy(struct ieee80211_hw *dev, u8 addr, u32 data); + +static inline u8 rtl818x_ioread8_idx(struct rtl8187_priv *priv, + u8 *addr, u8 idx) +{ + u8 val; + + mutex_lock(&priv->io_mutex); + usb_control_msg(priv->udev, usb_rcvctrlpipe(priv->udev, 0), + RTL8187_REQ_GET_REG, RTL8187_REQT_READ, + (unsigned long)addr, idx & 0x03, + &priv->io_dmabuf->bits8, sizeof(val), HZ / 2); + + val = priv->io_dmabuf->bits8; + mutex_unlock(&priv->io_mutex); + + return val; +} + +static inline u8 rtl818x_ioread8(struct rtl8187_priv *priv, u8 *addr) +{ + return rtl818x_ioread8_idx(priv, addr, 0); +} + +static inline u16 rtl818x_ioread16_idx(struct rtl8187_priv *priv, + __le16 *addr, u8 idx) +{ + __le16 val; + + mutex_lock(&priv->io_mutex); + usb_control_msg(priv->udev, usb_rcvctrlpipe(priv->udev, 0), + RTL8187_REQ_GET_REG, RTL8187_REQT_READ, + (unsigned long)addr, idx & 0x03, + &priv->io_dmabuf->bits16, sizeof(val), HZ / 2); + + val = priv->io_dmabuf->bits16; + mutex_unlock(&priv->io_mutex); + + return le16_to_cpu(val); +} + +static inline u16 rtl818x_ioread16(struct rtl8187_priv *priv, __le16 *addr) +{ + return rtl818x_ioread16_idx(priv, addr, 0); +} + +static inline u32 rtl818x_ioread32_idx(struct rtl8187_priv *priv, + __le32 *addr, u8 idx) +{ + __le32 val; + + mutex_lock(&priv->io_mutex); + usb_control_msg(priv->udev, usb_rcvctrlpipe(priv->udev, 0), + RTL8187_REQ_GET_REG, RTL8187_REQT_READ, + (unsigned long)addr, idx & 0x03, + &priv->io_dmabuf->bits32, sizeof(val), HZ / 2); + + val = priv->io_dmabuf->bits32; + mutex_unlock(&priv->io_mutex); + + return le32_to_cpu(val); +} + +static inline u32 rtl818x_ioread32(struct rtl8187_priv *priv, __le32 *addr) +{ + return rtl818x_ioread32_idx(priv, addr, 0); +} + +static inline void rtl818x_iowrite8_idx(struct rtl8187_priv *priv, + u8 *addr, u8 val, u8 idx) +{ + mutex_lock(&priv->io_mutex); + + priv->io_dmabuf->bits8 = val; + usb_control_msg(priv->udev, usb_sndctrlpipe(priv->udev, 0), + RTL8187_REQ_SET_REG, RTL8187_REQT_WRITE, + (unsigned long)addr, idx & 0x03, + &priv->io_dmabuf->bits8, sizeof(val), HZ / 2); + + mutex_unlock(&priv->io_mutex); +} + +static inline void rtl818x_iowrite8(struct rtl8187_priv *priv, u8 *addr, u8 val) +{ + rtl818x_iowrite8_idx(priv, addr, val, 0); +} + +static inline void rtl818x_iowrite16_idx(struct rtl8187_priv *priv, + __le16 *addr, u16 val, u8 idx) +{ + mutex_lock(&priv->io_mutex); + + priv->io_dmabuf->bits16 = cpu_to_le16(val); + usb_control_msg(priv->udev, usb_sndctrlpipe(priv->udev, 0), + RTL8187_REQ_SET_REG, RTL8187_REQT_WRITE, + (unsigned long)addr, idx & 0x03, + &priv->io_dmabuf->bits16, sizeof(val), HZ / 2); + + mutex_unlock(&priv->io_mutex); +} + +static inline void rtl818x_iowrite16(struct rtl8187_priv *priv, __le16 *addr, + u16 val) +{ + rtl818x_iowrite16_idx(priv, addr, val, 0); +} + +static inline void rtl818x_iowrite32_idx(struct rtl8187_priv *priv, + __le32 *addr, u32 val, u8 idx) +{ + mutex_lock(&priv->io_mutex); + + priv->io_dmabuf->bits32 = cpu_to_le32(val); + usb_control_msg(priv->udev, usb_sndctrlpipe(priv->udev, 0), + RTL8187_REQ_SET_REG, RTL8187_REQT_WRITE, + (unsigned long)addr, idx & 0x03, + &priv->io_dmabuf->bits32, sizeof(val), HZ / 2); + + mutex_unlock(&priv->io_mutex); +} + +static inline void rtl818x_iowrite32(struct rtl8187_priv *priv, __le32 *addr, + u32 val) +{ + rtl818x_iowrite32_idx(priv, addr, val, 0); +} + +#endif /* RTL8187_H */ diff --git a/drivers/net/wireless/rtl818x/rtl8187/rtl8225.c b/drivers/net/wireless/rtl818x/rtl8187/rtl8225.c new file mode 100644 index 0000000..f0bf35f --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8187/rtl8225.c @@ -0,0 +1,962 @@ +/* + * Radio tuning for RTL8225 on RTL8187 + * + * Copyright 2007 Michael Wu + * Copyright 2007 Andrea Merello + * + * Based on the r8187 driver, which is: + * Copyright 2005 Andrea Merello , et al. + * + * Magic delays, register offsets, and phy value tables below are + * taken from the original r8187 driver sources. Thanks to Realtek + * for their support! + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#include "rtl8187.h" +#include "rtl8225.h" + +static void rtl8225_write_bitbang(struct ieee80211_hw *dev, u8 addr, u16 data) +{ + struct rtl8187_priv *priv = dev->priv; + u16 reg80, reg84, reg82; + u32 bangdata; + int i; + + bangdata = (data << 4) | (addr & 0xf); + + reg80 = rtl818x_ioread16(priv, &priv->map->RFPinsOutput) & 0xfff3; + reg82 = rtl818x_ioread16(priv, &priv->map->RFPinsEnable); + + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, reg82 | 0x7); + + reg84 = rtl818x_ioread16(priv, &priv->map->RFPinsSelect); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, reg84 | 0x7); + udelay(10); + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg80 | (1 << 2)); + udelay(2); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg80); + udelay(10); + + for (i = 15; i >= 0; i--) { + u16 reg = reg80 | (bangdata & (1 << i)) >> i; + + if (i & 1) + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg); + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg | (1 << 1)); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg | (1 << 1)); + + if (!(i & 1)) + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg); + } + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg80 | (1 << 2)); + udelay(10); + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg80 | (1 << 2)); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, reg84); +} + +static void rtl8225_write_8051(struct ieee80211_hw *dev, u8 addr, __le16 data) +{ + struct rtl8187_priv *priv = dev->priv; + u16 reg80, reg82, reg84; + + reg80 = rtl818x_ioread16(priv, &priv->map->RFPinsOutput); + reg82 = rtl818x_ioread16(priv, &priv->map->RFPinsEnable); + reg84 = rtl818x_ioread16(priv, &priv->map->RFPinsSelect); + + reg80 &= ~(0x3 << 2); + reg84 &= ~0xF; + + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, reg82 | 0x0007); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, reg84 | 0x0007); + udelay(10); + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg80 | (1 << 2)); + udelay(2); + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg80); + udelay(10); + + mutex_lock(&priv->io_mutex); + + priv->io_dmabuf->bits16 = data; + usb_control_msg(priv->udev, usb_sndctrlpipe(priv->udev, 0), + RTL8187_REQ_SET_REG, RTL8187_REQT_WRITE, + addr, 0x8225, &priv->io_dmabuf->bits16, sizeof(data), + HZ / 2); + + mutex_unlock(&priv->io_mutex); + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg80 | (1 << 2)); + udelay(10); + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg80 | (1 << 2)); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, reg84); +} + +static void rtl8225_write(struct ieee80211_hw *dev, u8 addr, u16 data) +{ + struct rtl8187_priv *priv = dev->priv; + + if (priv->asic_rev) + rtl8225_write_8051(dev, addr, cpu_to_le16(data)); + else + rtl8225_write_bitbang(dev, addr, data); +} + +static u16 rtl8225_read(struct ieee80211_hw *dev, u8 addr) +{ + struct rtl8187_priv *priv = dev->priv; + u16 reg80, reg82, reg84, out; + int i; + + reg80 = rtl818x_ioread16(priv, &priv->map->RFPinsOutput); + reg82 = rtl818x_ioread16(priv, &priv->map->RFPinsEnable); + reg84 = rtl818x_ioread16(priv, &priv->map->RFPinsSelect); + + reg80 &= ~0xF; + + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, reg82 | 0x000F); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, reg84 | 0x000F); + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg80 | (1 << 2)); + udelay(4); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg80); + udelay(5); + + for (i = 4; i >= 0; i--) { + u16 reg = reg80 | ((addr >> i) & 1); + + if (!(i & 1)) { + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg); + udelay(1); + } + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg | (1 << 1)); + udelay(2); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg | (1 << 1)); + udelay(2); + + if (i & 1) { + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, reg); + udelay(1); + } + } + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3) | (1 << 1)); + udelay(2); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3)); + udelay(2); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3)); + udelay(2); + + out = 0; + for (i = 11; i >= 0; i--) { + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3)); + udelay(1); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3) | (1 << 1)); + udelay(2); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3) | (1 << 1)); + udelay(2); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3) | (1 << 1)); + udelay(2); + + if (rtl818x_ioread16(priv, &priv->map->RFPinsInput) & (1 << 1)) + out |= 1 << i; + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3)); + udelay(2); + } + + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, + reg80 | (1 << 3) | (1 << 2)); + udelay(2); + + rtl818x_iowrite16(priv, &priv->map->RFPinsEnable, reg82); + rtl818x_iowrite16(priv, &priv->map->RFPinsSelect, reg84); + rtl818x_iowrite16(priv, &priv->map->RFPinsOutput, 0x03A0); + + return out; +} + +static const u16 rtl8225bcd_rxgain[] = { + 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0408, 0x0409, + 0x040a, 0x040b, 0x0502, 0x0503, 0x0504, 0x0505, 0x0540, 0x0541, + 0x0542, 0x0543, 0x0544, 0x0545, 0x0580, 0x0581, 0x0582, 0x0583, + 0x0584, 0x0585, 0x0588, 0x0589, 0x058a, 0x058b, 0x0643, 0x0644, + 0x0645, 0x0680, 0x0681, 0x0682, 0x0683, 0x0684, 0x0685, 0x0688, + 0x0689, 0x068a, 0x068b, 0x068c, 0x0742, 0x0743, 0x0744, 0x0745, + 0x0780, 0x0781, 0x0782, 0x0783, 0x0784, 0x0785, 0x0788, 0x0789, + 0x078a, 0x078b, 0x078c, 0x078d, 0x0790, 0x0791, 0x0792, 0x0793, + 0x0794, 0x0795, 0x0798, 0x0799, 0x079a, 0x079b, 0x079c, 0x079d, + 0x07a0, 0x07a1, 0x07a2, 0x07a3, 0x07a4, 0x07a5, 0x07a8, 0x07a9, + 0x07aa, 0x07ab, 0x07ac, 0x07ad, 0x07b0, 0x07b1, 0x07b2, 0x07b3, + 0x07b4, 0x07b5, 0x07b8, 0x07b9, 0x07ba, 0x07bb, 0x07bb +}; + +static const u8 rtl8225_agc[] = { + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9d, 0x9c, 0x9b, 0x9a, 0x99, 0x98, 0x97, 0x96, + 0x95, 0x94, 0x93, 0x92, 0x91, 0x90, 0x8f, 0x8e, + 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88, 0x87, 0x86, + 0x85, 0x84, 0x83, 0x82, 0x81, 0x80, 0x3f, 0x3e, + 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, 0x37, 0x36, + 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x2f, 0x2e, + 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, + 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, 0x1f, 0x1e, + 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, + 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x0e, + 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, + 0x05, 0x04, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 +}; + +static const u8 rtl8225_gain[] = { + 0x23, 0x88, 0x7c, 0xa5, /* -82dBm */ + 0x23, 0x88, 0x7c, 0xb5, /* -82dBm */ + 0x23, 0x88, 0x7c, 0xc5, /* -82dBm */ + 0x33, 0x80, 0x79, 0xc5, /* -78dBm */ + 0x43, 0x78, 0x76, 0xc5, /* -74dBm */ + 0x53, 0x60, 0x73, 0xc5, /* -70dBm */ + 0x63, 0x58, 0x70, 0xc5, /* -66dBm */ +}; + +static const u8 rtl8225_threshold[] = { + 0x8d, 0x8d, 0x8d, 0x8d, 0x9d, 0xad, 0xbd +}; + +static const u8 rtl8225_tx_gain_cck_ofdm[] = { + 0x02, 0x06, 0x0e, 0x1e, 0x3e, 0x7e +}; + +static const u8 rtl8225_tx_power_cck[] = { + 0x18, 0x17, 0x15, 0x11, 0x0c, 0x08, 0x04, 0x02, + 0x1b, 0x1a, 0x17, 0x13, 0x0e, 0x09, 0x04, 0x02, + 0x1f, 0x1e, 0x1a, 0x15, 0x10, 0x0a, 0x05, 0x02, + 0x22, 0x21, 0x1d, 0x18, 0x11, 0x0b, 0x06, 0x02, + 0x26, 0x25, 0x21, 0x1b, 0x14, 0x0d, 0x06, 0x03, + 0x2b, 0x2a, 0x25, 0x1e, 0x16, 0x0e, 0x07, 0x03 +}; + +static const u8 rtl8225_tx_power_cck_ch14[] = { + 0x18, 0x17, 0x15, 0x0c, 0x00, 0x00, 0x00, 0x00, + 0x1b, 0x1a, 0x17, 0x0e, 0x00, 0x00, 0x00, 0x00, + 0x1f, 0x1e, 0x1a, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x21, 0x1d, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x26, 0x25, 0x21, 0x13, 0x00, 0x00, 0x00, 0x00, + 0x2b, 0x2a, 0x25, 0x15, 0x00, 0x00, 0x00, 0x00 +}; + +static const u8 rtl8225_tx_power_ofdm[] = { + 0x80, 0x90, 0xa2, 0xb5, 0xcb, 0xe4 +}; + +static const u32 rtl8225_chan[] = { + 0x085c, 0x08dc, 0x095c, 0x09dc, 0x0a5c, 0x0adc, 0x0b5c, + 0x0bdc, 0x0c5c, 0x0cdc, 0x0d5c, 0x0ddc, 0x0e5c, 0x0f72 +}; + +static void rtl8225_rf_set_tx_power(struct ieee80211_hw *dev, int channel) +{ + struct rtl8187_priv *priv = dev->priv; + u8 cck_power, ofdm_power; + const u8 *tmp; + u32 reg; + int i; + + cck_power = priv->channels[channel - 1].hw_value & 0xF; + ofdm_power = priv->channels[channel - 1].hw_value >> 4; + + cck_power = min(cck_power, (u8)11); + if (ofdm_power > (u8)15) + ofdm_power = 25; + else + ofdm_power += 10; + + rtl818x_iowrite8(priv, &priv->map->TX_GAIN_CCK, + rtl8225_tx_gain_cck_ofdm[cck_power / 6] >> 1); + + if (channel == 14) + tmp = &rtl8225_tx_power_cck_ch14[(cck_power % 6) * 8]; + else + tmp = &rtl8225_tx_power_cck[(cck_power % 6) * 8]; + + for (i = 0; i < 8; i++) + rtl8225_write_phy_cck(dev, 0x44 + i, *tmp++); + + msleep(1); // FIXME: optional? + + /* anaparam2 on */ + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_CONFIG); + reg = rtl818x_ioread8(priv, &priv->map->CONFIG3); + rtl818x_iowrite8(priv, &priv->map->CONFIG3, + reg | RTL818X_CONFIG3_ANAPARAM_WRITE); + rtl818x_iowrite32(priv, &priv->map->ANAPARAM2, + RTL8187_RTL8225_ANAPARAM2_ON); + rtl818x_iowrite8(priv, &priv->map->CONFIG3, + reg & ~RTL818X_CONFIG3_ANAPARAM_WRITE); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_NORMAL); + + rtl8225_write_phy_ofdm(dev, 2, 0x42); + rtl8225_write_phy_ofdm(dev, 6, 0x00); + rtl8225_write_phy_ofdm(dev, 8, 0x00); + + rtl818x_iowrite8(priv, &priv->map->TX_GAIN_OFDM, + rtl8225_tx_gain_cck_ofdm[ofdm_power / 6] >> 1); + + tmp = &rtl8225_tx_power_ofdm[ofdm_power % 6]; + + rtl8225_write_phy_ofdm(dev, 5, *tmp); + rtl8225_write_phy_ofdm(dev, 7, *tmp); + + msleep(1); +} + +static void rtl8225_rf_init(struct ieee80211_hw *dev) +{ + struct rtl8187_priv *priv = dev->priv; + int i; + + rtl8225_write(dev, 0x0, 0x067); + rtl8225_write(dev, 0x1, 0xFE0); + rtl8225_write(dev, 0x2, 0x44D); + rtl8225_write(dev, 0x3, 0x441); + rtl8225_write(dev, 0x4, 0x486); + rtl8225_write(dev, 0x5, 0xBC0); + rtl8225_write(dev, 0x6, 0xAE6); + rtl8225_write(dev, 0x7, 0x82A); + rtl8225_write(dev, 0x8, 0x01F); + rtl8225_write(dev, 0x9, 0x334); + rtl8225_write(dev, 0xA, 0xFD4); + rtl8225_write(dev, 0xB, 0x391); + rtl8225_write(dev, 0xC, 0x050); + rtl8225_write(dev, 0xD, 0x6DB); + rtl8225_write(dev, 0xE, 0x029); + rtl8225_write(dev, 0xF, 0x914); msleep(100); + + rtl8225_write(dev, 0x2, 0xC4D); msleep(200); + rtl8225_write(dev, 0x2, 0x44D); msleep(200); + + if (!(rtl8225_read(dev, 6) & (1 << 7))) { + rtl8225_write(dev, 0x02, 0x0c4d); + msleep(200); + rtl8225_write(dev, 0x02, 0x044d); + msleep(100); + if (!(rtl8225_read(dev, 6) & (1 << 7))) + wiphy_warn(dev->wiphy, "RF Calibration Failed! %x\n", + rtl8225_read(dev, 6)); + } + + rtl8225_write(dev, 0x0, 0x127); + + for (i = 0; i < ARRAY_SIZE(rtl8225bcd_rxgain); i++) { + rtl8225_write(dev, 0x1, i + 1); + rtl8225_write(dev, 0x2, rtl8225bcd_rxgain[i]); + } + + rtl8225_write(dev, 0x0, 0x027); + rtl8225_write(dev, 0x0, 0x22F); + + for (i = 0; i < ARRAY_SIZE(rtl8225_agc); i++) { + rtl8225_write_phy_ofdm(dev, 0xB, rtl8225_agc[i]); + rtl8225_write_phy_ofdm(dev, 0xA, 0x80 + i); + } + + msleep(1); + + rtl8225_write_phy_ofdm(dev, 0x00, 0x01); + rtl8225_write_phy_ofdm(dev, 0x01, 0x02); + rtl8225_write_phy_ofdm(dev, 0x02, 0x42); + rtl8225_write_phy_ofdm(dev, 0x03, 0x00); + rtl8225_write_phy_ofdm(dev, 0x04, 0x00); + rtl8225_write_phy_ofdm(dev, 0x05, 0x00); + rtl8225_write_phy_ofdm(dev, 0x06, 0x40); + rtl8225_write_phy_ofdm(dev, 0x07, 0x00); + rtl8225_write_phy_ofdm(dev, 0x08, 0x40); + rtl8225_write_phy_ofdm(dev, 0x09, 0xfe); + rtl8225_write_phy_ofdm(dev, 0x0a, 0x09); + rtl8225_write_phy_ofdm(dev, 0x0b, 0x80); + rtl8225_write_phy_ofdm(dev, 0x0c, 0x01); + rtl8225_write_phy_ofdm(dev, 0x0e, 0xd3); + rtl8225_write_phy_ofdm(dev, 0x0f, 0x38); + rtl8225_write_phy_ofdm(dev, 0x10, 0x84); + rtl8225_write_phy_ofdm(dev, 0x11, 0x06); + rtl8225_write_phy_ofdm(dev, 0x12, 0x20); + rtl8225_write_phy_ofdm(dev, 0x13, 0x20); + rtl8225_write_phy_ofdm(dev, 0x14, 0x00); + rtl8225_write_phy_ofdm(dev, 0x15, 0x40); + rtl8225_write_phy_ofdm(dev, 0x16, 0x00); + rtl8225_write_phy_ofdm(dev, 0x17, 0x40); + rtl8225_write_phy_ofdm(dev, 0x18, 0xef); + rtl8225_write_phy_ofdm(dev, 0x19, 0x19); + rtl8225_write_phy_ofdm(dev, 0x1a, 0x20); + rtl8225_write_phy_ofdm(dev, 0x1b, 0x76); + rtl8225_write_phy_ofdm(dev, 0x1c, 0x04); + rtl8225_write_phy_ofdm(dev, 0x1e, 0x95); + rtl8225_write_phy_ofdm(dev, 0x1f, 0x75); + rtl8225_write_phy_ofdm(dev, 0x20, 0x1f); + rtl8225_write_phy_ofdm(dev, 0x21, 0x27); + rtl8225_write_phy_ofdm(dev, 0x22, 0x16); + rtl8225_write_phy_ofdm(dev, 0x24, 0x46); + rtl8225_write_phy_ofdm(dev, 0x25, 0x20); + rtl8225_write_phy_ofdm(dev, 0x26, 0x90); + rtl8225_write_phy_ofdm(dev, 0x27, 0x88); + + rtl8225_write_phy_ofdm(dev, 0x0d, rtl8225_gain[2 * 4]); + rtl8225_write_phy_ofdm(dev, 0x1b, rtl8225_gain[2 * 4 + 2]); + rtl8225_write_phy_ofdm(dev, 0x1d, rtl8225_gain[2 * 4 + 3]); + rtl8225_write_phy_ofdm(dev, 0x23, rtl8225_gain[2 * 4 + 1]); + + rtl8225_write_phy_cck(dev, 0x00, 0x98); + rtl8225_write_phy_cck(dev, 0x03, 0x20); + rtl8225_write_phy_cck(dev, 0x04, 0x7e); + rtl8225_write_phy_cck(dev, 0x05, 0x12); + rtl8225_write_phy_cck(dev, 0x06, 0xfc); + rtl8225_write_phy_cck(dev, 0x07, 0x78); + rtl8225_write_phy_cck(dev, 0x08, 0x2e); + rtl8225_write_phy_cck(dev, 0x10, 0x9b); + rtl8225_write_phy_cck(dev, 0x11, 0x88); + rtl8225_write_phy_cck(dev, 0x12, 0x47); + rtl8225_write_phy_cck(dev, 0x13, 0xd0); + rtl8225_write_phy_cck(dev, 0x19, 0x00); + rtl8225_write_phy_cck(dev, 0x1a, 0xa0); + rtl8225_write_phy_cck(dev, 0x1b, 0x08); + rtl8225_write_phy_cck(dev, 0x40, 0x86); + rtl8225_write_phy_cck(dev, 0x41, 0x8d); + rtl8225_write_phy_cck(dev, 0x42, 0x15); + rtl8225_write_phy_cck(dev, 0x43, 0x18); + rtl8225_write_phy_cck(dev, 0x44, 0x1f); + rtl8225_write_phy_cck(dev, 0x45, 0x1e); + rtl8225_write_phy_cck(dev, 0x46, 0x1a); + rtl8225_write_phy_cck(dev, 0x47, 0x15); + rtl8225_write_phy_cck(dev, 0x48, 0x10); + rtl8225_write_phy_cck(dev, 0x49, 0x0a); + rtl8225_write_phy_cck(dev, 0x4a, 0x05); + rtl8225_write_phy_cck(dev, 0x4b, 0x02); + rtl8225_write_phy_cck(dev, 0x4c, 0x05); + + rtl818x_iowrite8(priv, &priv->map->TESTR, 0x0D); + + rtl8225_rf_set_tx_power(dev, 1); + + /* RX antenna default to A */ + rtl8225_write_phy_cck(dev, 0x10, 0x9b); /* B: 0xDB */ + rtl8225_write_phy_ofdm(dev, 0x26, 0x90); /* B: 0x10 */ + + rtl818x_iowrite8(priv, &priv->map->TX_ANTENNA, 0x03); /* B: 0x00 */ + msleep(1); + rtl818x_iowrite32(priv, (__le32 *)0xFF94, 0x3dc00002); + + /* set sensitivity */ + rtl8225_write(dev, 0x0c, 0x50); + rtl8225_write_phy_ofdm(dev, 0x0d, rtl8225_gain[2 * 4]); + rtl8225_write_phy_ofdm(dev, 0x1b, rtl8225_gain[2 * 4 + 2]); + rtl8225_write_phy_ofdm(dev, 0x1d, rtl8225_gain[2 * 4 + 3]); + rtl8225_write_phy_ofdm(dev, 0x23, rtl8225_gain[2 * 4 + 1]); + rtl8225_write_phy_cck(dev, 0x41, rtl8225_threshold[2]); +} + +static const u8 rtl8225z2_agc[] = { + 0x5e, 0x5e, 0x5e, 0x5e, 0x5d, 0x5b, 0x59, 0x57, 0x55, 0x53, 0x51, 0x4f, + 0x4d, 0x4b, 0x49, 0x47, 0x45, 0x43, 0x41, 0x3f, 0x3d, 0x3b, 0x39, 0x37, + 0x35, 0x33, 0x31, 0x2f, 0x2d, 0x2b, 0x29, 0x27, 0x25, 0x23, 0x21, 0x1f, + 0x1d, 0x1b, 0x19, 0x17, 0x15, 0x13, 0x11, 0x0f, 0x0d, 0x0b, 0x09, 0x07, + 0x05, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x26, 0x27, 0x27, 0x28, + 0x28, 0x29, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b, 0x2c, 0x2c, 0x2c, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2e, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x2f, 0x30, 0x30, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31 +}; +static const u8 rtl8225z2_ofdm[] = { + 0x10, 0x0d, 0x01, 0x00, 0x14, 0xfb, 0xfb, 0x60, + 0x00, 0x60, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, + 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0xa8, 0x26, + 0x32, 0x33, 0x07, 0xa5, 0x6f, 0x55, 0xc8, 0xb3, + 0x0a, 0xe1, 0x2C, 0x8a, 0x86, 0x83, 0x34, 0x0f, + 0x4f, 0x24, 0x6f, 0xc2, 0x6b, 0x40, 0x80, 0x00, + 0xc0, 0xc1, 0x58, 0xf1, 0x00, 0xe4, 0x90, 0x3e, + 0x6d, 0x3c, 0xfb, 0x07 +}; + +static const u8 rtl8225z2_tx_power_cck_ch14[] = { + 0x36, 0x35, 0x2e, 0x1b, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x2f, 0x29, 0x15, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x2f, 0x29, 0x15, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x2f, 0x29, 0x15, 0x00, 0x00, 0x00, 0x00 +}; + +static const u8 rtl8225z2_tx_power_cck[] = { + 0x36, 0x35, 0x2e, 0x25, 0x1c, 0x12, 0x09, 0x04, + 0x30, 0x2f, 0x29, 0x21, 0x19, 0x10, 0x08, 0x03, + 0x2b, 0x2a, 0x25, 0x1e, 0x16, 0x0e, 0x07, 0x03, + 0x26, 0x25, 0x21, 0x1b, 0x14, 0x0d, 0x06, 0x03 +}; + +static const u8 rtl8225z2_tx_power_ofdm[] = { + 0x42, 0x00, 0x40, 0x00, 0x40 +}; + +static const u8 rtl8225z2_tx_gain_cck_ofdm[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23 +}; + +static void rtl8225z2_rf_set_tx_power(struct ieee80211_hw *dev, int channel) +{ + struct rtl8187_priv *priv = dev->priv; + u8 cck_power, ofdm_power; + const u8 *tmp; + u32 reg; + int i; + + cck_power = priv->channels[channel - 1].hw_value & 0xF; + ofdm_power = priv->channels[channel - 1].hw_value >> 4; + + cck_power = min(cck_power, (u8)15); + cck_power += priv->txpwr_base & 0xF; + cck_power = min(cck_power, (u8)35); + + if (ofdm_power > (u8)15) + ofdm_power = 25; + else + ofdm_power += 10; + ofdm_power += priv->txpwr_base >> 4; + ofdm_power = min(ofdm_power, (u8)35); + + if (channel == 14) + tmp = rtl8225z2_tx_power_cck_ch14; + else + tmp = rtl8225z2_tx_power_cck; + + for (i = 0; i < 8; i++) + rtl8225_write_phy_cck(dev, 0x44 + i, *tmp++); + + rtl818x_iowrite8(priv, &priv->map->TX_GAIN_CCK, + rtl8225z2_tx_gain_cck_ofdm[cck_power]); + msleep(1); + + /* anaparam2 on */ + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_CONFIG); + reg = rtl818x_ioread8(priv, &priv->map->CONFIG3); + rtl818x_iowrite8(priv, &priv->map->CONFIG3, + reg | RTL818X_CONFIG3_ANAPARAM_WRITE); + rtl818x_iowrite32(priv, &priv->map->ANAPARAM2, + RTL8187_RTL8225_ANAPARAM2_ON); + rtl818x_iowrite8(priv, &priv->map->CONFIG3, + reg & ~RTL818X_CONFIG3_ANAPARAM_WRITE); + rtl818x_iowrite8(priv, &priv->map->EEPROM_CMD, RTL818X_EEPROM_CMD_NORMAL); + + rtl8225_write_phy_ofdm(dev, 2, 0x42); + rtl8225_write_phy_ofdm(dev, 5, 0x00); + rtl8225_write_phy_ofdm(dev, 6, 0x40); + rtl8225_write_phy_ofdm(dev, 7, 0x00); + rtl8225_write_phy_ofdm(dev, 8, 0x40); + + rtl818x_iowrite8(priv, &priv->map->TX_GAIN_OFDM, + rtl8225z2_tx_gain_cck_ofdm[ofdm_power]); + msleep(1); +} + +static void rtl8225z2_b_rf_set_tx_power(struct ieee80211_hw *dev, int channel) +{ + struct rtl8187_priv *priv = dev->priv; + u8 cck_power, ofdm_power; + const u8 *tmp; + int i; + + cck_power = priv->channels[channel - 1].hw_value & 0xF; + ofdm_power = priv->channels[channel - 1].hw_value >> 4; + + if (cck_power > 15) + cck_power = (priv->hw_rev == RTL8187BvB) ? 15 : 22; + else + cck_power += (priv->hw_rev == RTL8187BvB) ? 0 : 7; + cck_power += priv->txpwr_base & 0xF; + cck_power = min(cck_power, (u8)35); + + if (ofdm_power > 15) + ofdm_power = (priv->hw_rev == RTL8187BvB) ? 17 : 25; + else + ofdm_power += (priv->hw_rev == RTL8187BvB) ? 2 : 10; + ofdm_power += (priv->txpwr_base >> 4) & 0xF; + ofdm_power = min(ofdm_power, (u8)35); + + if (channel == 14) + tmp = rtl8225z2_tx_power_cck_ch14; + else + tmp = rtl8225z2_tx_power_cck; + + if (priv->hw_rev == RTL8187BvB) { + if (cck_power <= 6) + ; /* do nothing */ + else if (cck_power <= 11) + tmp += 8; + else + tmp += 16; + } else { + if (cck_power <= 5) + ; /* do nothing */ + else if (cck_power <= 11) + tmp += 8; + else if (cck_power <= 17) + tmp += 16; + else + tmp += 24; + } + + for (i = 0; i < 8; i++) + rtl8225_write_phy_cck(dev, 0x44 + i, *tmp++); + + rtl818x_iowrite8(priv, &priv->map->TX_GAIN_CCK, + rtl8225z2_tx_gain_cck_ofdm[cck_power] << 1); + msleep(1); + + rtl818x_iowrite8(priv, &priv->map->TX_GAIN_OFDM, + rtl8225z2_tx_gain_cck_ofdm[ofdm_power] << 1); + if (priv->hw_rev == RTL8187BvB) { + if (ofdm_power <= 11) { + rtl8225_write_phy_ofdm(dev, 0x87, 0x60); + rtl8225_write_phy_ofdm(dev, 0x89, 0x60); + } else { + rtl8225_write_phy_ofdm(dev, 0x87, 0x5c); + rtl8225_write_phy_ofdm(dev, 0x89, 0x5c); + } + } else { + if (ofdm_power <= 11) { + rtl8225_write_phy_ofdm(dev, 0x87, 0x5c); + rtl8225_write_phy_ofdm(dev, 0x89, 0x5c); + } else if (ofdm_power <= 17) { + rtl8225_write_phy_ofdm(dev, 0x87, 0x54); + rtl8225_write_phy_ofdm(dev, 0x89, 0x54); + } else { + rtl8225_write_phy_ofdm(dev, 0x87, 0x50); + rtl8225_write_phy_ofdm(dev, 0x89, 0x50); + } + } + msleep(1); +} + +static const u16 rtl8225z2_rxgain[] = { + 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0408, 0x0409, + 0x040a, 0x040b, 0x0502, 0x0503, 0x0504, 0x0505, 0x0540, 0x0541, + 0x0542, 0x0543, 0x0544, 0x0545, 0x0580, 0x0581, 0x0582, 0x0583, + 0x0584, 0x0585, 0x0588, 0x0589, 0x058a, 0x058b, 0x0643, 0x0644, + 0x0645, 0x0680, 0x0681, 0x0682, 0x0683, 0x0684, 0x0685, 0x0688, + 0x0689, 0x068a, 0x068b, 0x068c, 0x0742, 0x0743, 0x0744, 0x0745, + 0x0780, 0x0781, 0x0782, 0x0783, 0x0784, 0x0785, 0x0788, 0x0789, + 0x078a, 0x078b, 0x078c, 0x078d, 0x0790, 0x0791, 0x0792, 0x0793, + 0x0794, 0x0795, 0x0798, 0x0799, 0x079a, 0x079b, 0x079c, 0x079d, + 0x07a0, 0x07a1, 0x07a2, 0x07a3, 0x07a4, 0x07a5, 0x07a8, 0x07a9, + 0x03aa, 0x03ab, 0x03ac, 0x03ad, 0x03b0, 0x03b1, 0x03b2, 0x03b3, + 0x03b4, 0x03b5, 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bb +}; + +static const u8 rtl8225z2_gain_bg[] = { + 0x23, 0x15, 0xa5, /* -82-1dBm */ + 0x23, 0x15, 0xb5, /* -82-2dBm */ + 0x23, 0x15, 0xc5, /* -82-3dBm */ + 0x33, 0x15, 0xc5, /* -78dBm */ + 0x43, 0x15, 0xc5, /* -74dBm */ + 0x53, 0x15, 0xc5, /* -70dBm */ + 0x63, 0x15, 0xc5 /* -66dBm */ +}; + +static void rtl8225z2_rf_init(struct ieee80211_hw *dev) +{ + struct rtl8187_priv *priv = dev->priv; + int i; + + rtl8225_write(dev, 0x0, 0x2BF); + rtl8225_write(dev, 0x1, 0xEE0); + rtl8225_write(dev, 0x2, 0x44D); + rtl8225_write(dev, 0x3, 0x441); + rtl8225_write(dev, 0x4, 0x8C3); + rtl8225_write(dev, 0x5, 0xC72); + rtl8225_write(dev, 0x6, 0x0E6); + rtl8225_write(dev, 0x7, 0x82A); + rtl8225_write(dev, 0x8, 0x03F); + rtl8225_write(dev, 0x9, 0x335); + rtl8225_write(dev, 0xa, 0x9D4); + rtl8225_write(dev, 0xb, 0x7BB); + rtl8225_write(dev, 0xc, 0x850); + rtl8225_write(dev, 0xd, 0xCDF); + rtl8225_write(dev, 0xe, 0x02B); + rtl8225_write(dev, 0xf, 0x114); + msleep(100); + + rtl8225_write(dev, 0x0, 0x1B7); + + for (i = 0; i < ARRAY_SIZE(rtl8225z2_rxgain); i++) { + rtl8225_write(dev, 0x1, i + 1); + rtl8225_write(dev, 0x2, rtl8225z2_rxgain[i]); + } + + rtl8225_write(dev, 0x3, 0x080); + rtl8225_write(dev, 0x5, 0x004); + rtl8225_write(dev, 0x0, 0x0B7); + rtl8225_write(dev, 0x2, 0xc4D); + + msleep(200); + rtl8225_write(dev, 0x2, 0x44D); + msleep(100); + + if (!(rtl8225_read(dev, 6) & (1 << 7))) { + rtl8225_write(dev, 0x02, 0x0C4D); + msleep(200); + rtl8225_write(dev, 0x02, 0x044D); + msleep(100); + if (!(rtl8225_read(dev, 6) & (1 << 7))) + wiphy_warn(dev->wiphy, "RF Calibration Failed! %x\n", + rtl8225_read(dev, 6)); + } + + msleep(200); + + rtl8225_write(dev, 0x0, 0x2BF); + + for (i = 0; i < ARRAY_SIZE(rtl8225_agc); i++) { + rtl8225_write_phy_ofdm(dev, 0xB, rtl8225_agc[i]); + rtl8225_write_phy_ofdm(dev, 0xA, 0x80 + i); + } + + msleep(1); + + rtl8225_write_phy_ofdm(dev, 0x00, 0x01); + rtl8225_write_phy_ofdm(dev, 0x01, 0x02); + rtl8225_write_phy_ofdm(dev, 0x02, 0x42); + rtl8225_write_phy_ofdm(dev, 0x03, 0x00); + rtl8225_write_phy_ofdm(dev, 0x04, 0x00); + rtl8225_write_phy_ofdm(dev, 0x05, 0x00); + rtl8225_write_phy_ofdm(dev, 0x06, 0x40); + rtl8225_write_phy_ofdm(dev, 0x07, 0x00); + rtl8225_write_phy_ofdm(dev, 0x08, 0x40); + rtl8225_write_phy_ofdm(dev, 0x09, 0xfe); + rtl8225_write_phy_ofdm(dev, 0x0a, 0x08); + rtl8225_write_phy_ofdm(dev, 0x0b, 0x80); + rtl8225_write_phy_ofdm(dev, 0x0c, 0x01); + rtl8225_write_phy_ofdm(dev, 0x0d, 0x43); + rtl8225_write_phy_ofdm(dev, 0x0e, 0xd3); + rtl8225_write_phy_ofdm(dev, 0x0f, 0x38); + rtl8225_write_phy_ofdm(dev, 0x10, 0x84); + rtl8225_write_phy_ofdm(dev, 0x11, 0x07); + rtl8225_write_phy_ofdm(dev, 0x12, 0x20); + rtl8225_write_phy_ofdm(dev, 0x13, 0x20); + rtl8225_write_phy_ofdm(dev, 0x14, 0x00); + rtl8225_write_phy_ofdm(dev, 0x15, 0x40); + rtl8225_write_phy_ofdm(dev, 0x16, 0x00); + rtl8225_write_phy_ofdm(dev, 0x17, 0x40); + rtl8225_write_phy_ofdm(dev, 0x18, 0xef); + rtl8225_write_phy_ofdm(dev, 0x19, 0x19); + rtl8225_write_phy_ofdm(dev, 0x1a, 0x20); + rtl8225_write_phy_ofdm(dev, 0x1b, 0x15); + rtl8225_write_phy_ofdm(dev, 0x1c, 0x04); + rtl8225_write_phy_ofdm(dev, 0x1d, 0xc5); + rtl8225_write_phy_ofdm(dev, 0x1e, 0x95); + rtl8225_write_phy_ofdm(dev, 0x1f, 0x75); + rtl8225_write_phy_ofdm(dev, 0x20, 0x1f); + rtl8225_write_phy_ofdm(dev, 0x21, 0x17); + rtl8225_write_phy_ofdm(dev, 0x22, 0x16); + rtl8225_write_phy_ofdm(dev, 0x23, 0x80); + rtl8225_write_phy_ofdm(dev, 0x24, 0x46); + rtl8225_write_phy_ofdm(dev, 0x25, 0x00); + rtl8225_write_phy_ofdm(dev, 0x26, 0x90); + rtl8225_write_phy_ofdm(dev, 0x27, 0x88); + + rtl8225_write_phy_ofdm(dev, 0x0b, rtl8225z2_gain_bg[4 * 3]); + rtl8225_write_phy_ofdm(dev, 0x1b, rtl8225z2_gain_bg[4 * 3 + 1]); + rtl8225_write_phy_ofdm(dev, 0x1d, rtl8225z2_gain_bg[4 * 3 + 2]); + rtl8225_write_phy_ofdm(dev, 0x21, 0x37); + + rtl8225_write_phy_cck(dev, 0x00, 0x98); + rtl8225_write_phy_cck(dev, 0x03, 0x20); + rtl8225_write_phy_cck(dev, 0x04, 0x7e); + rtl8225_write_phy_cck(dev, 0x05, 0x12); + rtl8225_write_phy_cck(dev, 0x06, 0xfc); + rtl8225_write_phy_cck(dev, 0x07, 0x78); + rtl8225_write_phy_cck(dev, 0x08, 0x2e); + rtl8225_write_phy_cck(dev, 0x10, 0x9b); + rtl8225_write_phy_cck(dev, 0x11, 0x88); + rtl8225_write_phy_cck(dev, 0x12, 0x47); + rtl8225_write_phy_cck(dev, 0x13, 0xd0); + rtl8225_write_phy_cck(dev, 0x19, 0x00); + rtl8225_write_phy_cck(dev, 0x1a, 0xa0); + rtl8225_write_phy_cck(dev, 0x1b, 0x08); + rtl8225_write_phy_cck(dev, 0x40, 0x86); + rtl8225_write_phy_cck(dev, 0x41, 0x8d); + rtl8225_write_phy_cck(dev, 0x42, 0x15); + rtl8225_write_phy_cck(dev, 0x43, 0x18); + rtl8225_write_phy_cck(dev, 0x44, 0x36); + rtl8225_write_phy_cck(dev, 0x45, 0x35); + rtl8225_write_phy_cck(dev, 0x46, 0x2e); + rtl8225_write_phy_cck(dev, 0x47, 0x25); + rtl8225_write_phy_cck(dev, 0x48, 0x1c); + rtl8225_write_phy_cck(dev, 0x49, 0x12); + rtl8225_write_phy_cck(dev, 0x4a, 0x09); + rtl8225_write_phy_cck(dev, 0x4b, 0x04); + rtl8225_write_phy_cck(dev, 0x4c, 0x05); + + rtl818x_iowrite8(priv, (u8 *)0xFF5B, 0x0D); msleep(1); + + rtl8225z2_rf_set_tx_power(dev, 1); + + /* RX antenna default to A */ + rtl8225_write_phy_cck(dev, 0x10, 0x9b); /* B: 0xDB */ + rtl8225_write_phy_ofdm(dev, 0x26, 0x90); /* B: 0x10 */ + + rtl818x_iowrite8(priv, &priv->map->TX_ANTENNA, 0x03); /* B: 0x00 */ + msleep(1); + rtl818x_iowrite32(priv, (__le32 *)0xFF94, 0x3dc00002); +} + +static void rtl8225z2_b_rf_init(struct ieee80211_hw *dev) +{ + struct rtl8187_priv *priv = dev->priv; + int i; + + rtl8225_write(dev, 0x0, 0x0B7); + rtl8225_write(dev, 0x1, 0xEE0); + rtl8225_write(dev, 0x2, 0x44D); + rtl8225_write(dev, 0x3, 0x441); + rtl8225_write(dev, 0x4, 0x8C3); + rtl8225_write(dev, 0x5, 0xC72); + rtl8225_write(dev, 0x6, 0x0E6); + rtl8225_write(dev, 0x7, 0x82A); + rtl8225_write(dev, 0x8, 0x03F); + rtl8225_write(dev, 0x9, 0x335); + rtl8225_write(dev, 0xa, 0x9D4); + rtl8225_write(dev, 0xb, 0x7BB); + rtl8225_write(dev, 0xc, 0x850); + rtl8225_write(dev, 0xd, 0xCDF); + rtl8225_write(dev, 0xe, 0x02B); + rtl8225_write(dev, 0xf, 0x114); + + rtl8225_write(dev, 0x0, 0x1B7); + + for (i = 0; i < ARRAY_SIZE(rtl8225z2_rxgain); i++) { + rtl8225_write(dev, 0x1, i + 1); + rtl8225_write(dev, 0x2, rtl8225z2_rxgain[i]); + } + + rtl8225_write(dev, 0x3, 0x080); + rtl8225_write(dev, 0x5, 0x004); + rtl8225_write(dev, 0x0, 0x0B7); + + rtl8225_write(dev, 0x2, 0xC4D); + + rtl8225_write(dev, 0x2, 0x44D); + rtl8225_write(dev, 0x0, 0x2BF); + + rtl818x_iowrite8(priv, &priv->map->TX_GAIN_CCK, 0x03); + rtl818x_iowrite8(priv, &priv->map->TX_GAIN_OFDM, 0x07); + rtl818x_iowrite8(priv, &priv->map->TX_ANTENNA, 0x03); + + rtl8225_write_phy_ofdm(dev, 0x80, 0x12); + for (i = 0; i < ARRAY_SIZE(rtl8225z2_agc); i++) { + rtl8225_write_phy_ofdm(dev, 0xF, rtl8225z2_agc[i]); + rtl8225_write_phy_ofdm(dev, 0xE, 0x80 + i); + rtl8225_write_phy_ofdm(dev, 0xE, 0); + } + rtl8225_write_phy_ofdm(dev, 0x80, 0x10); + + for (i = 0; i < ARRAY_SIZE(rtl8225z2_ofdm); i++) + rtl8225_write_phy_ofdm(dev, i, rtl8225z2_ofdm[i]); + + rtl8225_write_phy_ofdm(dev, 0x97, 0x46); + rtl8225_write_phy_ofdm(dev, 0xa4, 0xb6); + rtl8225_write_phy_ofdm(dev, 0x85, 0xfc); + rtl8225_write_phy_cck(dev, 0xc1, 0x88); +} + +static void rtl8225_rf_stop(struct ieee80211_hw *dev) +{ + rtl8225_write(dev, 0x4, 0x1f); +} + +static void rtl8225_rf_set_channel(struct ieee80211_hw *dev, + struct ieee80211_conf *conf) +{ + struct rtl8187_priv *priv = dev->priv; + int chan = + ieee80211_frequency_to_channel(conf->chandef.chan->center_freq); + + if (priv->rf->init == rtl8225_rf_init) + rtl8225_rf_set_tx_power(dev, chan); + else if (priv->rf->init == rtl8225z2_rf_init) + rtl8225z2_rf_set_tx_power(dev, chan); + else + rtl8225z2_b_rf_set_tx_power(dev, chan); + + rtl8225_write(dev, 0x7, rtl8225_chan[chan - 1]); + msleep(10); +} + +static const struct rtl818x_rf_ops rtl8225_ops = { + .name = "rtl8225", + .init = rtl8225_rf_init, + .stop = rtl8225_rf_stop, + .set_chan = rtl8225_rf_set_channel +}; + +static const struct rtl818x_rf_ops rtl8225z2_ops = { + .name = "rtl8225z2", + .init = rtl8225z2_rf_init, + .stop = rtl8225_rf_stop, + .set_chan = rtl8225_rf_set_channel +}; + +static const struct rtl818x_rf_ops rtl8225z2_b_ops = { + .name = "rtl8225z2", + .init = rtl8225z2_b_rf_init, + .stop = rtl8225_rf_stop, + .set_chan = rtl8225_rf_set_channel +}; + +const struct rtl818x_rf_ops * rtl8187_detect_rf(struct ieee80211_hw *dev) +{ + u16 reg8, reg9; + struct rtl8187_priv *priv = dev->priv; + + if (!priv->is_rtl8187b) { + rtl8225_write(dev, 0, 0x1B7); + + reg8 = rtl8225_read(dev, 8); + reg9 = rtl8225_read(dev, 9); + + rtl8225_write(dev, 0, 0x0B7); + + if (reg8 != 0x588 || reg9 != 0x700) + return &rtl8225_ops; + + return &rtl8225z2_ops; + } else + return &rtl8225z2_b_ops; +} diff --git a/drivers/net/wireless/rtl818x/rtl8187/rtl8225.h b/drivers/net/wireless/rtl818x/rtl8187/rtl8225.h new file mode 100644 index 0000000..20c5b6e --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl8187/rtl8225.h @@ -0,0 +1,44 @@ +/* + * Radio tuning definitions for RTL8225 on RTL8187 + * + * Copyright 2007 Michael Wu + * Copyright 2007 Andrea Merello + * + * Based on the r8187 driver, which is: + * Copyright 2005 Andrea Merello , et al. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef RTL8187_RTL8225_H +#define RTL8187_RTL8225_H + +#define RTL8187_RTL8225_ANAPARAM_ON 0xa0000a59 +#define RTL8187_RTL8225_ANAPARAM2_ON 0x860c7312 +#define RTL8187_RTL8225_ANAPARAM_OFF 0xa00beb59 +#define RTL8187_RTL8225_ANAPARAM2_OFF 0x840dec11 + +#define RTL8187B_RTL8225_ANAPARAM_ON 0x45090658 +#define RTL8187B_RTL8225_ANAPARAM2_ON 0x727f3f52 +#define RTL8187B_RTL8225_ANAPARAM3_ON 0x00 +#define RTL8187B_RTL8225_ANAPARAM_OFF 0x55480658 +#define RTL8187B_RTL8225_ANAPARAM2_OFF 0x72003f50 +#define RTL8187B_RTL8225_ANAPARAM3_OFF 0x00 + +const struct rtl818x_rf_ops * rtl8187_detect_rf(struct ieee80211_hw *); + +static inline void rtl8225_write_phy_ofdm(struct ieee80211_hw *dev, + u8 addr, u32 data) +{ + rtl8187_write_phy(dev, addr, data); +} + +static inline void rtl8225_write_phy_cck(struct ieee80211_hw *dev, + u8 addr, u32 data) +{ + rtl8187_write_phy(dev, addr, data | 0x10000); +} + +#endif /* RTL8187_RTL8225_H */ diff --git a/drivers/net/wireless/rtl818x/rtl818x.h b/drivers/net/wireless/rtl818x/rtl818x.h new file mode 100644 index 0000000..1615f63 --- /dev/null +++ b/drivers/net/wireless/rtl818x/rtl818x.h @@ -0,0 +1,244 @@ +/* + * Definitions for RTL818x hardware + * + * Copyright 2007 Michael Wu + * Copyright 2007 Andrea Merello + * + * Based on the r8187 driver, which is: + * Copyright 2005 Andrea Merello , et al. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef RTL818X_H +#define RTL818X_H + +struct rtl818x_csr { + u8 MAC[6]; + u8 reserved_0[2]; + __le32 MAR[2]; + u8 RX_FIFO_COUNT; + u8 reserved_1; + u8 TX_FIFO_COUNT; + u8 BQREQ; + u8 reserved_2[4]; + __le32 TSFT[2]; + __le32 TLPDA; + __le32 TNPDA; + __le32 THPDA; + __le16 BRSR; + u8 BSSID[6]; + u8 RESP_RATE; + u8 EIFS; + u8 reserved_3[1]; + u8 CMD; +#define RTL818X_CMD_TX_ENABLE (1 << 2) +#define RTL818X_CMD_RX_ENABLE (1 << 3) +#define RTL818X_CMD_RESET (1 << 4) + u8 reserved_4[4]; + __le16 INT_MASK; + __le16 INT_STATUS; +#define RTL818X_INT_RX_OK (1 << 0) +#define RTL818X_INT_RX_ERR (1 << 1) +#define RTL818X_INT_TXL_OK (1 << 2) +#define RTL818X_INT_TXL_ERR (1 << 3) +#define RTL818X_INT_RX_DU (1 << 4) +#define RTL818X_INT_RX_FO (1 << 5) +#define RTL818X_INT_TXN_OK (1 << 6) +#define RTL818X_INT_TXN_ERR (1 << 7) +#define RTL818X_INT_TXH_OK (1 << 8) +#define RTL818X_INT_TXH_ERR (1 << 9) +#define RTL818X_INT_TXB_OK (1 << 10) +#define RTL818X_INT_TXB_ERR (1 << 11) +#define RTL818X_INT_ATIM (1 << 12) +#define RTL818X_INT_BEACON (1 << 13) +#define RTL818X_INT_TIME_OUT (1 << 14) +#define RTL818X_INT_TX_FO (1 << 15) + __le32 TX_CONF; +#define RTL818X_TX_CONF_LOOPBACK_MAC (1 << 17) +#define RTL818X_TX_CONF_LOOPBACK_CONT (3 << 17) +#define RTL818X_TX_CONF_NO_ICV (1 << 19) +#define RTL818X_TX_CONF_DISCW (1 << 20) +#define RTL818X_TX_CONF_SAT_HWPLCP (1 << 24) +#define RTL818X_TX_CONF_R8180_ABCD (2 << 25) +#define RTL818X_TX_CONF_R8180_F (3 << 25) +#define RTL818X_TX_CONF_R8185_ABC (4 << 25) +#define RTL818X_TX_CONF_R8185_D (5 << 25) +#define RTL818X_TX_CONF_R8187vD (5 << 25) +#define RTL818X_TX_CONF_R8187vD_B (6 << 25) +#define RTL818X_TX_CONF_HWVER_MASK (7 << 25) +#define RTL818X_TX_CONF_DISREQQSIZE (1 << 28) +#define RTL818X_TX_CONF_PROBE_DTS (1 << 29) +#define RTL818X_TX_CONF_HW_SEQNUM (1 << 30) +#define RTL818X_TX_CONF_CW_MIN (1 << 31) + __le32 RX_CONF; +#define RTL818X_RX_CONF_MONITOR (1 << 0) +#define RTL818X_RX_CONF_NICMAC (1 << 1) +#define RTL818X_RX_CONF_MULTICAST (1 << 2) +#define RTL818X_RX_CONF_BROADCAST (1 << 3) +#define RTL818X_RX_CONF_FCS (1 << 5) +#define RTL818X_RX_CONF_DATA (1 << 18) +#define RTL818X_RX_CONF_CTRL (1 << 19) +#define RTL818X_RX_CONF_MGMT (1 << 20) +#define RTL818X_RX_CONF_ADDR3 (1 << 21) +#define RTL818X_RX_CONF_PM (1 << 22) +#define RTL818X_RX_CONF_BSSID (1 << 23) +#define RTL818X_RX_CONF_RX_AUTORESETPHY (1 << 28) +#define RTL818X_RX_CONF_CSDM1 (1 << 29) +#define RTL818X_RX_CONF_CSDM2 (1 << 30) +#define RTL818X_RX_CONF_ONLYERLPKT (1 << 31) + __le32 INT_TIMEOUT; + __le32 TBDA; + u8 EEPROM_CMD; +#define RTL818X_EEPROM_CMD_READ (1 << 0) +#define RTL818X_EEPROM_CMD_WRITE (1 << 1) +#define RTL818X_EEPROM_CMD_CK (1 << 2) +#define RTL818X_EEPROM_CMD_CS (1 << 3) +#define RTL818X_EEPROM_CMD_NORMAL (0 << 6) +#define RTL818X_EEPROM_CMD_LOAD (1 << 6) +#define RTL818X_EEPROM_CMD_PROGRAM (2 << 6) +#define RTL818X_EEPROM_CMD_CONFIG (3 << 6) + u8 CONFIG0; + u8 CONFIG1; + u8 CONFIG2; +#define RTL818X_CONFIG2_ANTENNA_DIV (1 << 6) + __le32 ANAPARAM; + u8 MSR; +#define RTL818X_MSR_NO_LINK (0 << 2) +#define RTL818X_MSR_ADHOC (1 << 2) +#define RTL818X_MSR_INFRA (2 << 2) +#define RTL818X_MSR_MASTER (3 << 2) +#define RTL818X_MSR_ENEDCA (4 << 2) + u8 CONFIG3; +#define RTL818X_CONFIG3_ANAPARAM_WRITE (1 << 6) +#define RTL818X_CONFIG3_GNT_SELECT (1 << 7) + u8 CONFIG4; +#define RTL818X_CONFIG4_POWEROFF (1 << 6) +#define RTL818X_CONFIG4_VCOOFF (1 << 7) + u8 TESTR; + u8 reserved_9[2]; + u8 PGSELECT; + u8 SECURITY; + __le32 ANAPARAM2; + u8 reserved_10[12]; + __le16 BEACON_INTERVAL; + __le16 ATIM_WND; + __le16 BEACON_INTERVAL_TIME; + __le16 ATIMTR_INTERVAL; + u8 PHY_DELAY; + u8 CARRIER_SENSE_COUNTER; + u8 reserved_11[2]; + u8 PHY[4]; + __le16 RFPinsOutput; + __le16 RFPinsEnable; + __le16 RFPinsSelect; + __le16 RFPinsInput; + __le32 RF_PARA; + __le32 RF_TIMING; + u8 GP_ENABLE; + u8 GPIO0; + u8 GPIO1; + u8 reserved_12; + __le32 HSSI_PARA; + u8 reserved_13[4]; + u8 TX_AGC_CTL; +#define RTL818X_TX_AGC_CTL_PERPACKET_GAIN_SHIFT (1 << 0) +#define RTL818X_TX_AGC_CTL_PERPACKET_ANTSEL_SHIFT (1 << 1) +#define RTL818X_TX_AGC_CTL_FEEDBACK_ANT (1 << 2) + u8 TX_GAIN_CCK; + u8 TX_GAIN_OFDM; + u8 TX_ANTENNA; + u8 reserved_14[16]; + u8 WPA_CONF; + u8 reserved_15[3]; + u8 SIFS; + u8 DIFS; + u8 SLOT; + u8 reserved_16[5]; + u8 CW_CONF; +#define RTL818X_CW_CONF_PERPACKET_CW_SHIFT (1 << 0) +#define RTL818X_CW_CONF_PERPACKET_RETRY_SHIFT (1 << 1) + u8 CW_VAL; + u8 RATE_FALLBACK; +#define RTL818X_RATE_FALLBACK_ENABLE (1 << 7) + u8 ACM_CONTROL; + u8 reserved_17[24]; + u8 CONFIG5; + u8 TX_DMA_POLLING; + u8 reserved_18[2]; + __le16 CWR; + u8 RETRY_CTR; + u8 reserved_19[3]; + __le16 INT_MIG; +/* RTL818X_R8187B_*: magic numbers from ioregisters */ +#define RTL818X_R8187B_B 0 +#define RTL818X_R8187B_D 1 +#define RTL818X_R8187B_E 2 + __le32 RDSAR; + __le16 TID_AC_MAP; + u8 reserved_20[4]; + u8 ANAPARAM3; + u8 reserved_21[5]; + __le16 FEMR; + u8 reserved_22[4]; + __le16 TALLY_CNT; + u8 TALLY_SEL; +} __packed; + +struct rtl818x_rf_ops { + char *name; + void (*init)(struct ieee80211_hw *); + void (*stop)(struct ieee80211_hw *); + void (*set_chan)(struct ieee80211_hw *, struct ieee80211_conf *); + void (*conf_erp)(struct ieee80211_hw *, struct ieee80211_bss_conf *); + u8 (*calc_rssi)(u8 agc, u8 sq); +}; + +/** + * enum rtl818x_tx_desc_flags - Tx/Rx flags are common between RTL818X chips + * + * @RTL818X_TX_DESC_FLAG_NO_ENC: Disable hardware based encryption. + * @RTL818X_TX_DESC_FLAG_TX_OK: TX frame was ACKed. + * @RTL818X_TX_DESC_FLAG_SPLCP: Use short preamble. + * @RTL818X_TX_DESC_FLAG_MOREFRAG: More fragments follow. + * @RTL818X_TX_DESC_FLAG_CTS: Use CTS-to-self protection. + * @RTL818X_TX_DESC_FLAG_RTS: Use RTS/CTS protection. + * @RTL818X_TX_DESC_FLAG_LS: Last segment of the frame. + * @RTL818X_TX_DESC_FLAG_FS: First segment of the frame. + */ +enum rtl818x_tx_desc_flags { + RTL818X_TX_DESC_FLAG_NO_ENC = (1 << 15), + RTL818X_TX_DESC_FLAG_TX_OK = (1 << 15), + RTL818X_TX_DESC_FLAG_SPLCP = (1 << 16), + RTL818X_TX_DESC_FLAG_RX_UNDER = (1 << 16), + RTL818X_TX_DESC_FLAG_MOREFRAG = (1 << 17), + RTL818X_TX_DESC_FLAG_CTS = (1 << 18), + RTL818X_TX_DESC_FLAG_RTS = (1 << 23), + RTL818X_TX_DESC_FLAG_LS = (1 << 28), + RTL818X_TX_DESC_FLAG_FS = (1 << 29), + RTL818X_TX_DESC_FLAG_DMA = (1 << 30), + RTL818X_TX_DESC_FLAG_OWN = (1 << 31) +}; + +enum rtl818x_rx_desc_flags { + RTL818X_RX_DESC_FLAG_ICV_ERR = (1 << 12), + RTL818X_RX_DESC_FLAG_CRC32_ERR = (1 << 13), + RTL818X_RX_DESC_FLAG_PM = (1 << 14), + RTL818X_RX_DESC_FLAG_RX_ERR = (1 << 15), + RTL818X_RX_DESC_FLAG_BCAST = (1 << 16), + RTL818X_RX_DESC_FLAG_PAM = (1 << 17), + RTL818X_RX_DESC_FLAG_MCAST = (1 << 18), + RTL818X_RX_DESC_FLAG_QOS = (1 << 19), /* RTL8187(B) only */ + RTL818X_RX_DESC_FLAG_TRSW = (1 << 24), /* RTL8187(B) only */ + RTL818X_RX_DESC_FLAG_SPLCP = (1 << 25), + RTL818X_RX_DESC_FLAG_FOF = (1 << 26), + RTL818X_RX_DESC_FLAG_DMA_FAIL = (1 << 27), + RTL818X_RX_DESC_FLAG_LS = (1 << 28), + RTL818X_RX_DESC_FLAG_FS = (1 << 29), + RTL818X_RX_DESC_FLAG_EOR = (1 << 30), + RTL818X_RX_DESC_FLAG_OWN = (1 << 31) +}; + +#endif /* RTL818X_H */ diff --git a/drivers/net/wireless/rtlwifi/Kconfig b/drivers/net/wireless/rtlwifi/Kconfig new file mode 100644 index 0000000..8e4963d --- /dev/null +++ b/drivers/net/wireless/rtlwifi/Kconfig @@ -0,0 +1,88 @@ +config RTLWIFI + tristate "Realtek wireless card support" + depends on m + depends on MAC80211 + select BACKPORT_FW_LOADER + ---help--- + This is common code for RTL8192CE/RTL8192CU/RTL8192SE/RTL8723AE + drivers. This module does nothing by itself - the various front-end + drivers need to be enabled to support any desired devices. + + If you choose to build as a module, it'll be called rtlwifi. + +config RTLWIFI_DEBUG + bool "Debugging output for rtlwifi driver family" + depends on RTLWIFI + default y + ---help--- + To use the module option that sets the dynamic-debugging level for, + the front-end driver, this parameter must be "Y". For memory-limited + systems, choose "N". If in doubt, choose "Y". + +config RTL8192CE + tristate "Realtek RTL8192CE/RTL8188CE Wireless Network Adapter" + depends on m + depends on RTLWIFI && PCI + select RTL8192C_COMMON + ---help--- + This is the driver for Realtek RTL8192CE/RTL8188CE 802.11n PCIe + wireless network adapters. + + If you choose to build it as a module, it will be called rtl8192ce + +config RTL8192SE + tristate "Realtek RTL8192SE/RTL8191SE PCIe Wireless Network Adapter" + depends on m + depends on RTLWIFI && PCI + ---help--- + This is the driver for Realtek RTL8192SE/RTL8191SE 802.11n PCIe + wireless network adapters. + + If you choose to build it as a module, it will be called rtl8192se + +config RTL8192DE + tristate "Realtek RTL8192DE/RTL8188DE PCIe Wireless Network Adapter" + depends on m + depends on RTLWIFI && PCI + ---help--- + This is the driver for Realtek RTL8192DE/RTL8188DE 802.11n PCIe + wireless network adapters. + + If you choose to build it as a module, it will be called rtl8192de + +config RTL8723AE + tristate "Realtek RTL8723AE PCIe Wireless Network Adapter" + depends on m + depends on RTLWIFI && PCI + ---help--- + This is the driver for Realtek RTL8723AE 802.11n PCIe + wireless network adapters. + + If you choose to build it as a module, it will be called rtl8723ae + +config RTL8188EE + tristate "Realtek RTL8188EE Wireless Network Adapter" + depends on m + depends on RTLWIFI && PCI + ---help--- + This is the driver for Realtek RTL8188EE 802.11n PCIe + wireless network adapters. + + If you choose to build it as a module, it will be called rtl8188ee + +config RTL8192CU + tristate "Realtek RTL8192CU/RTL8188CU USB Wireless Network Adapter" + depends on m + depends on RTLWIFI && USB + select RTL8192C_COMMON + ---help--- + This is the driver for Realtek RTL8192CU/RTL8188CU 802.11n USB + wireless network adapters. + + If you choose to build it as a module, it will be called rtl8192cu + +config RTL8192C_COMMON + tristate + depends on m + depends on RTL8192CE || RTL8192CU + default m diff --git a/drivers/net/wireless/rtlwifi/Makefile b/drivers/net/wireless/rtlwifi/Makefile new file mode 100644 index 0000000..9931bb7 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/Makefile @@ -0,0 +1,31 @@ +obj-$(CPTCFG_RTLWIFI) += rtlwifi.o +rtlwifi-objs := \ + base.o \ + cam.o \ + core.o \ + debug.o \ + efuse.o \ + ps.o \ + rc.o \ + regd.o \ + stats.o + +rtl8192c_common-objs += \ + +ifneq ($(CONFIG_PCI),) +rtlwifi-objs += pci.o +endif + +ifneq ($(CONFIG_USB),) +rtlwifi-objs += usb.o +endif + +obj-$(CPTCFG_RTL8192C_COMMON) += rtl8192c/ +obj-$(CPTCFG_RTL8192CE) += rtl8192ce/ +obj-$(CPTCFG_RTL8192CU) += rtl8192cu/ +obj-$(CPTCFG_RTL8192SE) += rtl8192se/ +obj-$(CPTCFG_RTL8192DE) += rtl8192de/ +obj-$(CPTCFG_RTL8723AE) += rtl8723ae/ +obj-$(CPTCFG_RTL8188EE) += rtl8188ee/ + +ccflags-y += -D__CHECK_ENDIAN__ diff --git a/drivers/net/wireless/rtlwifi/base.c b/drivers/net/wireless/rtlwifi/base.c new file mode 100644 index 0000000..eb36f68 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/base.c @@ -0,0 +1,1881 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "wifi.h" +#include "rc.h" +#include "base.h" +#include "efuse.h" +#include "cam.h" +#include "ps.h" +#include "regd.h" + +#include +#include +#include + +/* + *NOTICE!!!: This file will be very big, we should + *keep it clear under following roles: + * + *This file include following parts, so, if you add new + *functions into this file, please check which part it + *should includes. or check if you should add new part + *for this file: + * + *1) mac80211 init functions + *2) tx information functions + *3) functions called by core.c + *4) wq & timer callback functions + *5) frame process functions + *6) IOT functions + *7) sysfs functions + *8) vif functions + *9) ... + */ + +/********************************************************* + * + * mac80211 init functions + * + *********************************************************/ +static struct ieee80211_channel rtl_channeltable_2g[] = { + {.center_freq = 2412, .hw_value = 1,}, + {.center_freq = 2417, .hw_value = 2,}, + {.center_freq = 2422, .hw_value = 3,}, + {.center_freq = 2427, .hw_value = 4,}, + {.center_freq = 2432, .hw_value = 5,}, + {.center_freq = 2437, .hw_value = 6,}, + {.center_freq = 2442, .hw_value = 7,}, + {.center_freq = 2447, .hw_value = 8,}, + {.center_freq = 2452, .hw_value = 9,}, + {.center_freq = 2457, .hw_value = 10,}, + {.center_freq = 2462, .hw_value = 11,}, + {.center_freq = 2467, .hw_value = 12,}, + {.center_freq = 2472, .hw_value = 13,}, + {.center_freq = 2484, .hw_value = 14,}, +}; + +static struct ieee80211_channel rtl_channeltable_5g[] = { + {.center_freq = 5180, .hw_value = 36,}, + {.center_freq = 5200, .hw_value = 40,}, + {.center_freq = 5220, .hw_value = 44,}, + {.center_freq = 5240, .hw_value = 48,}, + {.center_freq = 5260, .hw_value = 52,}, + {.center_freq = 5280, .hw_value = 56,}, + {.center_freq = 5300, .hw_value = 60,}, + {.center_freq = 5320, .hw_value = 64,}, + {.center_freq = 5500, .hw_value = 100,}, + {.center_freq = 5520, .hw_value = 104,}, + {.center_freq = 5540, .hw_value = 108,}, + {.center_freq = 5560, .hw_value = 112,}, + {.center_freq = 5580, .hw_value = 116,}, + {.center_freq = 5600, .hw_value = 120,}, + {.center_freq = 5620, .hw_value = 124,}, + {.center_freq = 5640, .hw_value = 128,}, + {.center_freq = 5660, .hw_value = 132,}, + {.center_freq = 5680, .hw_value = 136,}, + {.center_freq = 5700, .hw_value = 140,}, + {.center_freq = 5745, .hw_value = 149,}, + {.center_freq = 5765, .hw_value = 153,}, + {.center_freq = 5785, .hw_value = 157,}, + {.center_freq = 5805, .hw_value = 161,}, + {.center_freq = 5825, .hw_value = 165,}, +}; + +static struct ieee80211_rate rtl_ratetable_2g[] = { + {.bitrate = 10, .hw_value = 0x00,}, + {.bitrate = 20, .hw_value = 0x01,}, + {.bitrate = 55, .hw_value = 0x02,}, + {.bitrate = 110, .hw_value = 0x03,}, + {.bitrate = 60, .hw_value = 0x04,}, + {.bitrate = 90, .hw_value = 0x05,}, + {.bitrate = 120, .hw_value = 0x06,}, + {.bitrate = 180, .hw_value = 0x07,}, + {.bitrate = 240, .hw_value = 0x08,}, + {.bitrate = 360, .hw_value = 0x09,}, + {.bitrate = 480, .hw_value = 0x0a,}, + {.bitrate = 540, .hw_value = 0x0b,}, +}; + +static struct ieee80211_rate rtl_ratetable_5g[] = { + {.bitrate = 60, .hw_value = 0x04,}, + {.bitrate = 90, .hw_value = 0x05,}, + {.bitrate = 120, .hw_value = 0x06,}, + {.bitrate = 180, .hw_value = 0x07,}, + {.bitrate = 240, .hw_value = 0x08,}, + {.bitrate = 360, .hw_value = 0x09,}, + {.bitrate = 480, .hw_value = 0x0a,}, + {.bitrate = 540, .hw_value = 0x0b,}, +}; + +static const struct ieee80211_supported_band rtl_band_2ghz = { + .band = IEEE80211_BAND_2GHZ, + + .channels = rtl_channeltable_2g, + .n_channels = ARRAY_SIZE(rtl_channeltable_2g), + + .bitrates = rtl_ratetable_2g, + .n_bitrates = ARRAY_SIZE(rtl_ratetable_2g), + + .ht_cap = {0}, +}; + +static struct ieee80211_supported_band rtl_band_5ghz = { + .band = IEEE80211_BAND_5GHZ, + + .channels = rtl_channeltable_5g, + .n_channels = ARRAY_SIZE(rtl_channeltable_5g), + + .bitrates = rtl_ratetable_5g, + .n_bitrates = ARRAY_SIZE(rtl_ratetable_5g), + + .ht_cap = {0}, +}; + +static const u8 tid_to_ac[] = { + 2, /* IEEE80211_AC_BE */ + 3, /* IEEE80211_AC_BK */ + 3, /* IEEE80211_AC_BK */ + 2, /* IEEE80211_AC_BE */ + 1, /* IEEE80211_AC_VI */ + 1, /* IEEE80211_AC_VI */ + 0, /* IEEE80211_AC_VO */ + 0, /* IEEE80211_AC_VO */ +}; + +u8 rtl_tid_to_ac(u8 tid) +{ + return tid_to_ac[tid]; +} + +static void _rtl_init_hw_ht_capab(struct ieee80211_hw *hw, + struct ieee80211_sta_ht_cap *ht_cap) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + ht_cap->ht_supported = true; + ht_cap->cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 | + IEEE80211_HT_CAP_SGI_40 | + IEEE80211_HT_CAP_SGI_20 | + IEEE80211_HT_CAP_DSSSCCK40 | IEEE80211_HT_CAP_MAX_AMSDU; + + if (rtlpriv->rtlhal.disable_amsdu_8k) + ht_cap->cap &= ~IEEE80211_HT_CAP_MAX_AMSDU; + + /* + *Maximum length of AMPDU that the STA can receive. + *Length = 2 ^ (13 + max_ampdu_length_exp) - 1 (octets) + */ + ht_cap->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + + /*Minimum MPDU start spacing , */ + ht_cap->ampdu_density = IEEE80211_HT_MPDU_DENSITY_16; + + ht_cap->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; + + /*hw->wiphy->bands[IEEE80211_BAND_2GHZ] + *base on ant_num + *rx_mask: RX mask + *if rx_ant = 1 rx_mask[0]= 0xff;==>MCS0-MCS7 + *if rx_ant = 2 rx_mask[1]= 0xff;==>MCS8-MCS15 + *if rx_ant >= 3 rx_mask[2]= 0xff; + *if BW_40 rx_mask[4]= 0x01; + *highest supported RX rate + */ + if (rtlpriv->dm.supp_phymode_switch) { + + RT_TRACE(rtlpriv, COMP_INIT, DBG_EMERG, + "Support phy mode switch\n"); + + ht_cap->mcs.rx_mask[0] = 0xFF; + ht_cap->mcs.rx_mask[1] = 0xFF; + ht_cap->mcs.rx_mask[4] = 0x01; + + ht_cap->mcs.rx_highest = cpu_to_le16(MAX_BIT_RATE_40MHZ_MCS15); + } else { + if (get_rf_type(rtlphy) == RF_1T2R || + get_rf_type(rtlphy) == RF_2T2R) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "1T2R or 2T2R\n"); + ht_cap->mcs.rx_mask[0] = 0xFF; + ht_cap->mcs.rx_mask[1] = 0xFF; + ht_cap->mcs.rx_mask[4] = 0x01; + + ht_cap->mcs.rx_highest = + cpu_to_le16(MAX_BIT_RATE_40MHZ_MCS15); + } else if (get_rf_type(rtlphy) == RF_1T1R) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "1T1R\n"); + + ht_cap->mcs.rx_mask[0] = 0xFF; + ht_cap->mcs.rx_mask[1] = 0x00; + ht_cap->mcs.rx_mask[4] = 0x01; + + ht_cap->mcs.rx_highest = + cpu_to_le16(MAX_BIT_RATE_40MHZ_MCS7); + } + } +} + +static void _rtl_init_mac80211(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + struct rtl_mac *rtlmac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct ieee80211_supported_band *sband; + + + if (rtlhal->macphymode == SINGLEMAC_SINGLEPHY && rtlhal->bandset == + BAND_ON_BOTH) { + /* 1: 2.4 G bands */ + /* <1> use mac->bands as mem for hw->wiphy->bands */ + sband = &(rtlmac->bands[IEEE80211_BAND_2GHZ]); + + /* <2> set hw->wiphy->bands[IEEE80211_BAND_2GHZ] + * to default value(1T1R) */ + memcpy(&(rtlmac->bands[IEEE80211_BAND_2GHZ]), &rtl_band_2ghz, + sizeof(struct ieee80211_supported_band)); + + /* <3> init ht cap base on ant_num */ + _rtl_init_hw_ht_capab(hw, &sband->ht_cap); + + /* <4> set mac->sband to wiphy->sband */ + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = sband; + + /* 2: 5 G bands */ + /* <1> use mac->bands as mem for hw->wiphy->bands */ + sband = &(rtlmac->bands[IEEE80211_BAND_5GHZ]); + + /* <2> set hw->wiphy->bands[IEEE80211_BAND_5GHZ] + * to default value(1T1R) */ + memcpy(&(rtlmac->bands[IEEE80211_BAND_5GHZ]), &rtl_band_5ghz, + sizeof(struct ieee80211_supported_band)); + + /* <3> init ht cap base on ant_num */ + _rtl_init_hw_ht_capab(hw, &sband->ht_cap); + + /* <4> set mac->sband to wiphy->sband */ + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = sband; + } else { + if (rtlhal->current_bandtype == BAND_ON_2_4G) { + /* <1> use mac->bands as mem for hw->wiphy->bands */ + sband = &(rtlmac->bands[IEEE80211_BAND_2GHZ]); + + /* <2> set hw->wiphy->bands[IEEE80211_BAND_2GHZ] + * to default value(1T1R) */ + memcpy(&(rtlmac->bands[IEEE80211_BAND_2GHZ]), + &rtl_band_2ghz, + sizeof(struct ieee80211_supported_band)); + + /* <3> init ht cap base on ant_num */ + _rtl_init_hw_ht_capab(hw, &sband->ht_cap); + + /* <4> set mac->sband to wiphy->sband */ + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = sband; + } else if (rtlhal->current_bandtype == BAND_ON_5G) { + /* <1> use mac->bands as mem for hw->wiphy->bands */ + sband = &(rtlmac->bands[IEEE80211_BAND_5GHZ]); + + /* <2> set hw->wiphy->bands[IEEE80211_BAND_5GHZ] + * to default value(1T1R) */ + memcpy(&(rtlmac->bands[IEEE80211_BAND_5GHZ]), + &rtl_band_5ghz, + sizeof(struct ieee80211_supported_band)); + + /* <3> init ht cap base on ant_num */ + _rtl_init_hw_ht_capab(hw, &sband->ht_cap); + + /* <4> set mac->sband to wiphy->sband */ + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = sband; + } else { + RT_TRACE(rtlpriv, COMP_INIT, DBG_EMERG, "Err BAND %d\n", + rtlhal->current_bandtype); + } + } + /* <5> set hw caps */ + hw->flags = IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_RX_INCLUDES_FCS | + IEEE80211_HW_AMPDU_AGGREGATION | + IEEE80211_HW_CONNECTION_MONITOR | + /* IEEE80211_HW_SUPPORTS_CQM_RSSI | */ + IEEE80211_HW_CONNECTION_MONITOR | + IEEE80211_HW_MFP_CAPABLE | + IEEE80211_HW_REPORTS_TX_ACK_STATUS | 0; + + /* swlps or hwlps has been set in diff chip in init_sw_vars */ + if (rtlpriv->psc.swctrl_lps) + hw->flags |= IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_PS_NULLFUNC_STACK | + /* IEEE80211_HW_SUPPORTS_DYNAMIC_PS | */ + 0; + + hw->wiphy->interface_modes = + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_MESH_POINT) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO); + + hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN; + hw->wiphy->rts_threshold = 2347; + + hw->queues = AC_MAX; + hw->extra_tx_headroom = RTL_TX_HEADER_SIZE; + + /* TODO: Correct this value for our hw */ + /* TODO: define these hard code value */ + hw->channel_change_time = 100; + hw->max_listen_interval = 10; + hw->max_rate_tries = 4; + /* hw->max_rates = 1; */ + hw->sta_data_size = sizeof(struct rtl_sta_info); + + /* <6> mac address */ + if (is_valid_ether_addr(rtlefuse->dev_addr)) { + SET_IEEE80211_PERM_ADDR(hw, rtlefuse->dev_addr); + } else { + u8 rtlmac1[] = { 0x00, 0xe0, 0x4c, 0x81, 0x92, 0x00 }; + get_random_bytes((rtlmac1 + (ETH_ALEN - 1)), 1); + SET_IEEE80211_PERM_ADDR(hw, rtlmac1); + } + +} + +static void _rtl_init_deferred_work(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + /* <1> timer */ + setup_timer(&rtlpriv->works.watchdog_timer, + rtl_watch_dog_timer_callback, (unsigned long)hw); + setup_timer(&rtlpriv->works.dualmac_easyconcurrent_retrytimer, + rtl_easy_concurrent_retrytimer_callback, (unsigned long)hw); + + /* <2> work queue */ + rtlpriv->works.hw = hw; + rtlpriv->works.rtl_wq = alloc_workqueue(rtlpriv->cfg->name, 0, 0); + INIT_DELAYED_WORK(&rtlpriv->works.watchdog_wq, + (void *)rtl_watchdog_wq_callback); + INIT_DELAYED_WORK(&rtlpriv->works.ips_nic_off_wq, + (void *)rtl_ips_nic_off_wq_callback); + INIT_DELAYED_WORK(&rtlpriv->works.ps_work, + (void *)rtl_swlps_wq_callback); + INIT_DELAYED_WORK(&rtlpriv->works.ps_rfon_wq, + (void *)rtl_swlps_rfon_wq_callback); + INIT_DELAYED_WORK(&rtlpriv->works.fwevt_wq, + (void *)rtl_fwevt_wq_callback); + +} + +void rtl_deinit_deferred_work(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + del_timer_sync(&rtlpriv->works.watchdog_timer); + + cancel_delayed_work(&rtlpriv->works.watchdog_wq); + cancel_delayed_work(&rtlpriv->works.ips_nic_off_wq); + cancel_delayed_work(&rtlpriv->works.ps_work); + cancel_delayed_work(&rtlpriv->works.ps_rfon_wq); + cancel_delayed_work(&rtlpriv->works.fwevt_wq); +} + +void rtl_init_rfkill(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + bool radio_state; + bool blocked; + u8 valid = 0; + + /*set init state to on */ + rtlpriv->rfkill.rfkill_state = true; + wiphy_rfkill_set_hw_state(hw->wiphy, 0); + + radio_state = rtlpriv->cfg->ops->radio_onoff_checking(hw, &valid); + + if (valid) { + pr_info("wireless switch is %s\n", + rtlpriv->rfkill.rfkill_state ? "on" : "off"); + + rtlpriv->rfkill.rfkill_state = radio_state; + + blocked = (rtlpriv->rfkill.rfkill_state == 1) ? 0 : 1; + wiphy_rfkill_set_hw_state(hw->wiphy, blocked); + } + + wiphy_rfkill_start_polling(hw->wiphy); +} +EXPORT_SYMBOL(rtl_init_rfkill); + +void rtl_deinit_rfkill(struct ieee80211_hw *hw) +{ + wiphy_rfkill_stop_polling(hw->wiphy); +} + +int rtl_init_core(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *rtlmac = rtl_mac(rtl_priv(hw)); + + /* <1> init mac80211 */ + _rtl_init_mac80211(hw); + rtlmac->hw = hw; + + /* <2> rate control register */ + hw->rate_control_algorithm = "rtl_rc"; + + /* + * <3> init CRDA must come after init + * mac80211 hw in _rtl_init_mac80211. + */ + if (rtl_regd_init(hw, rtl_reg_notifier)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "REGD init failed\n"); + return 1; + } + + /* <4> locks */ + mutex_init(&rtlpriv->locks.conf_mutex); + mutex_init(&rtlpriv->locks.ps_mutex); + spin_lock_init(&rtlpriv->locks.ips_lock); + spin_lock_init(&rtlpriv->locks.irq_th_lock); + spin_lock_init(&rtlpriv->locks.irq_pci_lock); + spin_lock_init(&rtlpriv->locks.tx_lock); + spin_lock_init(&rtlpriv->locks.h2c_lock); + spin_lock_init(&rtlpriv->locks.rf_ps_lock); + spin_lock_init(&rtlpriv->locks.rf_lock); + spin_lock_init(&rtlpriv->locks.waitq_lock); + spin_lock_init(&rtlpriv->locks.entry_list_lock); + spin_lock_init(&rtlpriv->locks.fw_ps_lock); + spin_lock_init(&rtlpriv->locks.cck_and_rw_pagea_lock); + spin_lock_init(&rtlpriv->locks.check_sendpkt_lock); + spin_lock_init(&rtlpriv->locks.fw_ps_lock); + spin_lock_init(&rtlpriv->locks.lps_lock); + + /* <5> init list */ + INIT_LIST_HEAD(&rtlpriv->entry_list); + + rtlmac->link_state = MAC80211_NOLINK; + + /* <6> init deferred work */ + _rtl_init_deferred_work(hw); + + return 0; +} + +void rtl_deinit_core(struct ieee80211_hw *hw) +{ +} + +void rtl_init_rx_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + + rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_RCR, (u8 *) (&mac->rx_conf)); +} + +/********************************************************* + * + * tx information functions + * + *********************************************************/ +static void _rtl_qurey_shortpreamble_mode(struct ieee80211_hw *hw, + struct rtl_tcb_desc *tcb_desc, + struct ieee80211_tx_info *info) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 rate_flag = info->control.rates[0].flags; + + tcb_desc->use_shortpreamble = false; + + /* 1M can only use Long Preamble. 11B spec */ + if (tcb_desc->hw_rate == rtlpriv->cfg->maps[RTL_RC_CCK_RATE1M]) + return; + else if (rate_flag & IEEE80211_TX_RC_USE_SHORT_PREAMBLE) + tcb_desc->use_shortpreamble = true; + + return; +} + +static void _rtl_query_shortgi(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + struct rtl_tcb_desc *tcb_desc, + struct ieee80211_tx_info *info) +{ + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u8 rate_flag = info->control.rates[0].flags; + u8 sgi_40 = 0, sgi_20 = 0, bw_40 = 0; + tcb_desc->use_shortgi = false; + + if (sta == NULL) + return; + + sgi_40 = sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_40; + sgi_20 = sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_20; + + if (!(sta->ht_cap.ht_supported)) + return; + + if (!sgi_40 && !sgi_20) + return; + + if (mac->opmode == NL80211_IFTYPE_STATION) + bw_40 = mac->bw_40; + else if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_ADHOC || + mac->opmode == NL80211_IFTYPE_MESH_POINT) + bw_40 = sta->bandwidth >= IEEE80211_STA_RX_BW_40; + + if (bw_40 && sgi_40) + tcb_desc->use_shortgi = true; + else if ((bw_40 == false) && sgi_20) + tcb_desc->use_shortgi = true; + + if (!(rate_flag & IEEE80211_TX_RC_SHORT_GI)) + tcb_desc->use_shortgi = false; +} + +static void _rtl_query_protection_mode(struct ieee80211_hw *hw, + struct rtl_tcb_desc *tcb_desc, + struct ieee80211_tx_info *info) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 rate_flag = info->control.rates[0].flags; + + /* Common Settings */ + tcb_desc->rts_stbc = false; + tcb_desc->cts_enable = false; + tcb_desc->rts_sc = 0; + tcb_desc->rts_bw = false; + tcb_desc->rts_use_shortpreamble = false; + tcb_desc->rts_use_shortgi = false; + + if (rate_flag & IEEE80211_TX_RC_USE_CTS_PROTECT) { + /* Use CTS-to-SELF in protection mode. */ + tcb_desc->rts_enable = true; + tcb_desc->cts_enable = true; + tcb_desc->rts_rate = rtlpriv->cfg->maps[RTL_RC_OFDM_RATE24M]; + } else if (rate_flag & IEEE80211_TX_RC_USE_RTS_CTS) { + /* Use RTS-CTS in protection mode. */ + tcb_desc->rts_enable = true; + tcb_desc->rts_rate = rtlpriv->cfg->maps[RTL_RC_OFDM_RATE24M]; + } +} + +static void _rtl_txrate_selectmode(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + struct rtl_tcb_desc *tcb_desc) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_sta_info *sta_entry = NULL; + u8 ratr_index = 7; + + if (sta) { + sta_entry = (struct rtl_sta_info *) sta->drv_priv; + ratr_index = sta_entry->ratr_index; + } + if (!tcb_desc->disable_ratefallback || !tcb_desc->use_driver_rate) { + if (mac->opmode == NL80211_IFTYPE_STATION) { + tcb_desc->ratr_index = 0; + } else if (mac->opmode == NL80211_IFTYPE_ADHOC || + mac->opmode == NL80211_IFTYPE_MESH_POINT) { + if (tcb_desc->multicast || tcb_desc->broadcast) { + tcb_desc->hw_rate = + rtlpriv->cfg->maps[RTL_RC_CCK_RATE2M]; + tcb_desc->use_driver_rate = 1; + tcb_desc->ratr_index = RATR_INX_WIRELESS_MC; + } else { + tcb_desc->ratr_index = ratr_index; + } + } else if (mac->opmode == NL80211_IFTYPE_AP) { + tcb_desc->ratr_index = ratr_index; + } + } + + if (rtlpriv->dm.useramask) { + tcb_desc->ratr_index = ratr_index; + /* TODO we will differentiate adhoc and station future */ + if (mac->opmode == NL80211_IFTYPE_STATION || + mac->opmode == NL80211_IFTYPE_MESH_POINT) { + tcb_desc->mac_id = 0; + + if (mac->mode == WIRELESS_MODE_N_24G) + tcb_desc->ratr_index = RATR_INX_WIRELESS_NGB; + else if (mac->mode == WIRELESS_MODE_N_5G) + tcb_desc->ratr_index = RATR_INX_WIRELESS_NG; + else if (mac->mode & WIRELESS_MODE_G) + tcb_desc->ratr_index = RATR_INX_WIRELESS_GB; + else if (mac->mode & WIRELESS_MODE_B) + tcb_desc->ratr_index = RATR_INX_WIRELESS_B; + else if (mac->mode & WIRELESS_MODE_A) + tcb_desc->ratr_index = RATR_INX_WIRELESS_G; + } else if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_ADHOC) { + if (NULL != sta) { + if (sta->aid > 0) + tcb_desc->mac_id = sta->aid + 1; + else + tcb_desc->mac_id = 1; + } else { + tcb_desc->mac_id = 0; + } + } + } +} + +static void _rtl_query_bandwidth_mode(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + struct rtl_tcb_desc *tcb_desc) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + + tcb_desc->packet_bw = false; + if (!sta) + return; + if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_ADHOC || + mac->opmode == NL80211_IFTYPE_MESH_POINT) { + if (sta->bandwidth == IEEE80211_STA_RX_BW_20) + return; + } else if (mac->opmode == NL80211_IFTYPE_STATION) { + if (!mac->bw_40 || !(sta->ht_cap.ht_supported)) + return; + } + if (tcb_desc->multicast || tcb_desc->broadcast) + return; + + /*use legency rate, shall use 20MHz */ + if (tcb_desc->hw_rate <= rtlpriv->cfg->maps[RTL_RC_OFDM_RATE54M]) + return; + + tcb_desc->packet_bw = true; +} + +static u8 _rtl_get_highest_n_rate(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u8 hw_rate; + + if (get_rf_type(rtlphy) == RF_2T2R) + hw_rate = rtlpriv->cfg->maps[RTL_RC_HT_RATEMCS15]; + else + hw_rate = rtlpriv->cfg->maps[RTL_RC_HT_RATEMCS7]; + + return hw_rate; +} + +/* mac80211's rate_idx is like this: + * + * 2.4G band:rx_status->band == IEEE80211_BAND_2GHZ + * + * B/G rate: + * (rx_status->flag & RX_FLAG_HT) = 0, + * DESC92_RATE1M-->DESC92_RATE54M ==> idx is 0-->11, + * + * N rate: + * (rx_status->flag & RX_FLAG_HT) = 1, + * DESC92_RATEMCS0-->DESC92_RATEMCS15 ==> idx is 0-->15 + * + * 5G band:rx_status->band == IEEE80211_BAND_5GHZ + * A rate: + * (rx_status->flag & RX_FLAG_HT) = 0, + * DESC92_RATE6M-->DESC92_RATE54M ==> idx is 0-->7, + * + * N rate: + * (rx_status->flag & RX_FLAG_HT) = 1, + * DESC92_RATEMCS0-->DESC92_RATEMCS15 ==> idx is 0-->15 + */ +int rtlwifi_rate_mapping(struct ieee80211_hw *hw, + bool isht, u8 desc_rate, bool first_ampdu) +{ + int rate_idx; + + if (false == isht) { + if (IEEE80211_BAND_2GHZ == hw->conf.chandef.chan->band) { + switch (desc_rate) { + case DESC92_RATE1M: + rate_idx = 0; + break; + case DESC92_RATE2M: + rate_idx = 1; + break; + case DESC92_RATE5_5M: + rate_idx = 2; + break; + case DESC92_RATE11M: + rate_idx = 3; + break; + case DESC92_RATE6M: + rate_idx = 4; + break; + case DESC92_RATE9M: + rate_idx = 5; + break; + case DESC92_RATE12M: + rate_idx = 6; + break; + case DESC92_RATE18M: + rate_idx = 7; + break; + case DESC92_RATE24M: + rate_idx = 8; + break; + case DESC92_RATE36M: + rate_idx = 9; + break; + case DESC92_RATE48M: + rate_idx = 10; + break; + case DESC92_RATE54M: + rate_idx = 11; + break; + default: + rate_idx = 0; + break; + } + } else { + switch (desc_rate) { + case DESC92_RATE6M: + rate_idx = 0; + break; + case DESC92_RATE9M: + rate_idx = 1; + break; + case DESC92_RATE12M: + rate_idx = 2; + break; + case DESC92_RATE18M: + rate_idx = 3; + break; + case DESC92_RATE24M: + rate_idx = 4; + break; + case DESC92_RATE36M: + rate_idx = 5; + break; + case DESC92_RATE48M: + rate_idx = 6; + break; + case DESC92_RATE54M: + rate_idx = 7; + break; + default: + rate_idx = 0; + break; + } + } + + } else { + + switch (desc_rate) { + case DESC92_RATEMCS0: + rate_idx = 0; + break; + case DESC92_RATEMCS1: + rate_idx = 1; + break; + case DESC92_RATEMCS2: + rate_idx = 2; + break; + case DESC92_RATEMCS3: + rate_idx = 3; + break; + case DESC92_RATEMCS4: + rate_idx = 4; + break; + case DESC92_RATEMCS5: + rate_idx = 5; + break; + case DESC92_RATEMCS6: + rate_idx = 6; + break; + case DESC92_RATEMCS7: + rate_idx = 7; + break; + case DESC92_RATEMCS8: + rate_idx = 8; + break; + case DESC92_RATEMCS9: + rate_idx = 9; + break; + case DESC92_RATEMCS10: + rate_idx = 10; + break; + case DESC92_RATEMCS11: + rate_idx = 11; + break; + case DESC92_RATEMCS12: + rate_idx = 12; + break; + case DESC92_RATEMCS13: + rate_idx = 13; + break; + case DESC92_RATEMCS14: + rate_idx = 14; + break; + case DESC92_RATEMCS15: + rate_idx = 15; + break; + default: + rate_idx = 0; + break; + } + } + return rate_idx; +} +EXPORT_SYMBOL(rtlwifi_rate_mapping); + +bool rtl_tx_mgmt_proc(struct ieee80211_hw *hw, struct sk_buff *skb) +{ + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_priv *rtlpriv = rtl_priv(hw); + __le16 fc = rtl_get_fc(skb); + + if (rtlpriv->dm.supp_phymode_switch && + mac->link_state < MAC80211_LINKED && + (ieee80211_is_auth(fc) || ieee80211_is_probe_req(fc))) { + if (rtlpriv->cfg->ops->chk_switch_dmdp) + rtlpriv->cfg->ops->chk_switch_dmdp(hw); + } + if (ieee80211_is_auth(fc)) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_DMESG, "MAC80211_LINKING\n"); + rtl_ips_nic_on(hw); + + mac->link_state = MAC80211_LINKING; + /* Dual mac */ + rtlpriv->phy.need_iqk = true; + } + + return true; +} + +void rtl_get_tcb_desc(struct ieee80211_hw *hw, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff *skb, struct rtl_tcb_desc *tcb_desc) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *rtlmac = rtl_mac(rtl_priv(hw)); + struct ieee80211_hdr *hdr = rtl_get_hdr(skb); + struct ieee80211_rate *txrate; + __le16 fc = hdr->frame_control; + + txrate = ieee80211_get_tx_rate(hw, info); + if (txrate) + tcb_desc->hw_rate = txrate->hw_value; + else + tcb_desc->hw_rate = 0; + + if (ieee80211_is_data(fc)) { + /* + *we set data rate INX 0 + *in rtl_rc.c if skb is special data or + *mgt which need low data rate. + */ + + /* + *So tcb_desc->hw_rate is just used for + *special data and mgt frames + */ + if (info->control.rates[0].idx == 0 || + ieee80211_is_nullfunc(fc)) { + tcb_desc->use_driver_rate = true; + tcb_desc->ratr_index = RATR_INX_WIRELESS_MC; + + tcb_desc->disable_ratefallback = 1; + } else { + /* + *because hw will nerver use hw_rate + *when tcb_desc->use_driver_rate = false + *so we never set highest N rate here, + *and N rate will all be controlled by FW + *when tcb_desc->use_driver_rate = false + */ + if (sta && (sta->ht_cap.ht_supported)) { + tcb_desc->hw_rate = _rtl_get_highest_n_rate(hw); + } else { + if (rtlmac->mode == WIRELESS_MODE_B) { + tcb_desc->hw_rate = + rtlpriv->cfg->maps[RTL_RC_CCK_RATE11M]; + } else { + tcb_desc->hw_rate = + rtlpriv->cfg->maps[RTL_RC_OFDM_RATE54M]; + } + } + } + + if (is_multicast_ether_addr(ieee80211_get_DA(hdr))) + tcb_desc->multicast = 1; + else if (is_broadcast_ether_addr(ieee80211_get_DA(hdr))) + tcb_desc->broadcast = 1; + + _rtl_txrate_selectmode(hw, sta, tcb_desc); + _rtl_query_bandwidth_mode(hw, sta, tcb_desc); + _rtl_qurey_shortpreamble_mode(hw, tcb_desc, info); + _rtl_query_shortgi(hw, sta, tcb_desc, info); + _rtl_query_protection_mode(hw, tcb_desc, info); + } else { + tcb_desc->use_driver_rate = true; + tcb_desc->ratr_index = RATR_INX_WIRELESS_MC; + tcb_desc->disable_ratefallback = 1; + tcb_desc->mac_id = 0; + tcb_desc->packet_bw = false; + } +} +EXPORT_SYMBOL(rtl_get_tcb_desc); + +static bool addbareq_rx(struct ieee80211_hw *hw, struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct ieee80211_sta *sta = NULL; + struct ieee80211_hdr *hdr = rtl_get_hdr(skb); + struct rtl_sta_info *sta_entry = NULL; + struct ieee80211_mgmt *mgmt = (void *)skb->data; + u16 capab = 0, tid = 0; + struct rtl_tid_data *tid_data; + struct sk_buff *skb_delba = NULL; + struct ieee80211_rx_status rx_status = { 0 }; + + rcu_read_lock(); + sta = rtl_find_sta(hw, hdr->addr3); + if (sta == NULL) { + RT_TRACE(rtlpriv, (COMP_SEND | COMP_RECV), DBG_EMERG, + "sta is NULL\n"); + rcu_read_unlock(); + return true; + } + + sta_entry = (struct rtl_sta_info *)sta->drv_priv; + if (!sta_entry) { + rcu_read_unlock(); + return true; + } + capab = le16_to_cpu(mgmt->u.action.u.addba_req.capab); + tid = (capab & IEEE80211_ADDBA_PARAM_TID_MASK) >> 2; + tid_data = &sta_entry->tids[tid]; + if (tid_data->agg.rx_agg_state == RTL_RX_AGG_START) { + skb_delba = rtl_make_del_ba(hw, hdr->addr2, hdr->addr3, tid); + if (skb_delba) { + rx_status.freq = hw->conf.chandef.chan->center_freq; + rx_status.band = hw->conf.chandef.chan->band; + rx_status.flag |= RX_FLAG_DECRYPTED; + rx_status.flag |= RX_FLAG_MACTIME_END; + rx_status.rate_idx = 0; + rx_status.signal = 50 + 10; + memcpy(IEEE80211_SKB_RXCB(skb_delba), &rx_status, + sizeof(rx_status)); + RT_PRINT_DATA(rtlpriv, COMP_INIT, DBG_DMESG, + "fake del\n", skb_delba->data, + skb_delba->len); + ieee80211_rx_irqsafe(hw, skb_delba); + } + } + rcu_read_unlock(); + return false; +} + +bool rtl_action_proc(struct ieee80211_hw *hw, struct sk_buff *skb, u8 is_tx) +{ + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct ieee80211_hdr *hdr = rtl_get_hdr(skb); + struct rtl_priv *rtlpriv = rtl_priv(hw); + __le16 fc = hdr->frame_control; + u8 *act = (u8 *)skb->data + MAC80211_3ADDR_LEN; + u8 category; + + if (!ieee80211_is_action(fc)) + return true; + + category = *act; + act++; + switch (category) { + case ACT_CAT_BA: + switch (*act) { + case ACT_ADDBAREQ: + if (mac->act_scanning) + return false; + + RT_TRACE(rtlpriv, (COMP_SEND | COMP_RECV), DBG_DMESG, + "%s ACT_ADDBAREQ From :%pM\n", + is_tx ? "Tx" : "Rx", hdr->addr2); + RT_PRINT_DATA(rtlpriv, COMP_INIT, DBG_DMESG, "req\n", + skb->data, skb->len); + if (!is_tx) + if (addbareq_rx(hw, skb)) + return true; + break; + case ACT_ADDBARSP: + RT_TRACE(rtlpriv, (COMP_SEND | COMP_RECV), DBG_DMESG, + "%s ACT_ADDBARSP From :%pM\n", + is_tx ? "Tx" : "Rx", hdr->addr2); + break; + case ACT_DELBA: + RT_TRACE(rtlpriv, (COMP_SEND | COMP_RECV), DBG_DMESG, + "ACT_ADDBADEL From :%pM\n", hdr->addr2); + break; + } + break; + default: + break; + } + + return true; +} + +/*should call before software enc*/ +u8 rtl_is_special_data(struct ieee80211_hw *hw, struct sk_buff *skb, u8 is_tx) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + __le16 fc = rtl_get_fc(skb); + u16 ether_type; + u8 mac_hdr_len = ieee80211_get_hdrlen_from_skb(skb); + const struct iphdr *ip; + + if (!ieee80211_is_data(fc)) + return false; + + + ip = (struct iphdr *)((u8 *) skb->data + mac_hdr_len + + SNAP_SIZE + PROTOC_TYPE_SIZE); + ether_type = *(u16 *) ((u8 *) skb->data + mac_hdr_len + SNAP_SIZE); + /* ether_type = ntohs(ether_type); */ + + if (ETH_P_IP == ether_type) { + if (IPPROTO_UDP == ip->protocol) { + struct udphdr *udp = (struct udphdr *)((u8 *) ip + + (ip->ihl << 2)); + if (((((u8 *) udp)[1] == 68) && + (((u8 *) udp)[3] == 67)) || + ((((u8 *) udp)[1] == 67) && + (((u8 *) udp)[3] == 68))) { + /* + * 68 : UDP BOOTP client + * 67 : UDP BOOTP server + */ + RT_TRACE(rtlpriv, (COMP_SEND | COMP_RECV), + DBG_DMESG, "dhcp %s !!\n", + is_tx ? "Tx" : "Rx"); + + if (is_tx) { + rtlpriv->enter_ps = false; + schedule_work(&rtlpriv-> + works.lps_change_work); + ppsc->last_delaylps_stamp_jiffies = + jiffies; + } + + return true; + } + } + } else if (ETH_P_ARP == ether_type) { + if (is_tx) { + rtlpriv->enter_ps = false; + schedule_work(&rtlpriv->works.lps_change_work); + ppsc->last_delaylps_stamp_jiffies = jiffies; + } + + return true; + } else if (ETH_P_PAE == ether_type) { + RT_TRACE(rtlpriv, (COMP_SEND | COMP_RECV), DBG_DMESG, + "802.1X %s EAPOL pkt!!\n", is_tx ? "Tx" : "Rx"); + + if (is_tx) { + rtlpriv->enter_ps = false; + schedule_work(&rtlpriv->works.lps_change_work); + ppsc->last_delaylps_stamp_jiffies = jiffies; + } + + return true; + } else if (ETH_P_IPV6 == ether_type) { + /* IPv6 */ + return true; + } + + return false; +} + +/********************************************************* + * + * functions called by core.c + * + *********************************************************/ +int rtl_tx_agg_start(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u16 tid, u16 *ssn) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_tid_data *tid_data; + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_sta_info *sta_entry = NULL; + + if (sta == NULL) + return -EINVAL; + + if (unlikely(tid >= MAX_TID_COUNT)) + return -EINVAL; + + sta_entry = (struct rtl_sta_info *)sta->drv_priv; + if (!sta_entry) + return -ENXIO; + tid_data = &sta_entry->tids[tid]; + + RT_TRACE(rtlpriv, COMP_SEND, DBG_DMESG, "on ra = %pM tid = %d seq:%d\n", + sta->addr, tid, tid_data->seq_number); + + *ssn = tid_data->seq_number; + tid_data->agg.agg_state = RTL_AGG_START; + + ieee80211_start_tx_ba_cb_irqsafe(mac->vif, sta->addr, tid); + + return 0; +} + +int rtl_tx_agg_stop(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u16 tid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_sta_info *sta_entry = NULL; + + if (sta == NULL) + return -EINVAL; + + if (!sta->addr) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "ra = NULL\n"); + return -EINVAL; + } + + RT_TRACE(rtlpriv, COMP_SEND, DBG_DMESG, "on ra = %pM tid = %d\n", + sta->addr, tid); + + if (unlikely(tid >= MAX_TID_COUNT)) + return -EINVAL; + + sta_entry = (struct rtl_sta_info *)sta->drv_priv; + sta_entry->tids[tid].agg.agg_state = RTL_AGG_STOP; + + ieee80211_stop_tx_ba_cb_irqsafe(mac->vif, sta->addr, tid); + + return 0; +} + +int rtl_rx_agg_start(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u16 tid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_tid_data *tid_data; + struct rtl_sta_info *sta_entry = NULL; + + if (sta == NULL) + return -EINVAL; + + if (unlikely(tid >= MAX_TID_COUNT)) + return -EINVAL; + + sta_entry = (struct rtl_sta_info *)sta->drv_priv; + if (!sta_entry) + return -ENXIO; + tid_data = &sta_entry->tids[tid]; + + RT_TRACE(rtlpriv, COMP_RECV, DBG_DMESG, + "on ra = %pM tid = %d seq:%d\n", sta->addr, tid, + tid_data->seq_number); + + tid_data->agg.rx_agg_state = RTL_RX_AGG_START; + return 0; +} + +int rtl_rx_agg_stop(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u16 tid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_sta_info *sta_entry = NULL; + + if (sta == NULL) + return -EINVAL; + + if (!sta->addr) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "ra = NULL\n"); + return -EINVAL; + } + + RT_TRACE(rtlpriv, COMP_SEND, DBG_DMESG, + "on ra = %pM tid = %d\n", sta->addr, tid); + + if (unlikely(tid >= MAX_TID_COUNT)) + return -EINVAL; + + sta_entry = (struct rtl_sta_info *)sta->drv_priv; + sta_entry->tids[tid].agg.rx_agg_state = RTL_RX_AGG_STOP; + + return 0; +} + +int rtl_tx_agg_oper(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u16 tid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_sta_info *sta_entry = NULL; + + if (sta == NULL) + return -EINVAL; + + if (!sta->addr) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "ra = NULL\n"); + return -EINVAL; + } + + RT_TRACE(rtlpriv, COMP_SEND, DBG_DMESG, "on ra = %pM tid = %d\n", + sta->addr, tid); + + if (unlikely(tid >= MAX_TID_COUNT)) + return -EINVAL; + + sta_entry = (struct rtl_sta_info *)sta->drv_priv; + sta_entry->tids[tid].agg.agg_state = RTL_AGG_OPERATIONAL; + + return 0; +} + +/********************************************************* + * + * wq & timer callback functions + * + *********************************************************/ +/* this function is used for roaming */ +void rtl_beacon_statistic(struct ieee80211_hw *hw, struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + + if (rtlpriv->mac80211.opmode != NL80211_IFTYPE_STATION) + return; + + if (rtlpriv->mac80211.link_state < MAC80211_LINKED) + return; + + /* check if this really is a beacon */ + if (!ieee80211_is_beacon(hdr->frame_control) && + !ieee80211_is_probe_resp(hdr->frame_control)) + return; + + /* min. beacon length + FCS_LEN */ + if (skb->len <= 40 + FCS_LEN) + return; + + /* and only beacons from the associated BSSID, please */ + if (compare_ether_addr(hdr->addr3, rtlpriv->mac80211.bssid)) + return; + + rtlpriv->link_info.bcn_rx_inperiod++; +} + +void rtl_watchdog_wq_callback(void *data) +{ + struct rtl_works *rtlworks = container_of_dwork_rtl(data, + struct rtl_works, + watchdog_wq); + struct ieee80211_hw *hw = rtlworks->hw; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + bool busytraffic = false; + bool tx_busy_traffic = false; + bool rx_busy_traffic = false; + bool higher_busytraffic = false; + bool higher_busyrxtraffic = false; + u8 idx, tid; + u32 rx_cnt_inp4eriod = 0; + u32 tx_cnt_inp4eriod = 0; + u32 aver_rx_cnt_inperiod = 0; + u32 aver_tx_cnt_inperiod = 0; + u32 aver_tidtx_inperiod[MAX_TID_COUNT] = {0}; + u32 tidtx_inp4eriod[MAX_TID_COUNT] = {0}; + + if (is_hal_stop(rtlhal)) + return; + + /* <1> Determine if action frame is allowed */ + if (mac->link_state > MAC80211_NOLINK) { + if (mac->cnt_after_linked < 20) + mac->cnt_after_linked++; + } else { + mac->cnt_after_linked = 0; + } + + /* + *<2> to check if traffic busy, if + * busytraffic we don't change channel + */ + if (mac->link_state >= MAC80211_LINKED) { + + /* (1) get aver_rx_cnt_inperiod & aver_tx_cnt_inperiod */ + for (idx = 0; idx <= 2; idx++) { + rtlpriv->link_info.num_rx_in4period[idx] = + rtlpriv->link_info.num_rx_in4period[idx + 1]; + rtlpriv->link_info.num_tx_in4period[idx] = + rtlpriv->link_info.num_tx_in4period[idx + 1]; + } + rtlpriv->link_info.num_rx_in4period[3] = + rtlpriv->link_info.num_rx_inperiod; + rtlpriv->link_info.num_tx_in4period[3] = + rtlpriv->link_info.num_tx_inperiod; + for (idx = 0; idx <= 3; idx++) { + rx_cnt_inp4eriod += + rtlpriv->link_info.num_rx_in4period[idx]; + tx_cnt_inp4eriod += + rtlpriv->link_info.num_tx_in4period[idx]; + } + aver_rx_cnt_inperiod = rx_cnt_inp4eriod / 4; + aver_tx_cnt_inperiod = tx_cnt_inp4eriod / 4; + + /* (2) check traffic busy */ + if (aver_rx_cnt_inperiod > 100 || aver_tx_cnt_inperiod > 100) { + busytraffic = true; + if (aver_rx_cnt_inperiod > aver_tx_cnt_inperiod) + rx_busy_traffic = true; + else + tx_busy_traffic = false; + } + + /* Higher Tx/Rx data. */ + if (aver_rx_cnt_inperiod > 4000 || + aver_tx_cnt_inperiod > 4000) { + higher_busytraffic = true; + + /* Extremely high Rx data. */ + if (aver_rx_cnt_inperiod > 5000) + higher_busyrxtraffic = true; + } + + /* check every tid's tx traffic */ + for (tid = 0; tid <= 7; tid++) { + for (idx = 0; idx <= 2; idx++) + rtlpriv->link_info.tidtx_in4period[tid][idx] = + rtlpriv->link_info.tidtx_in4period[tid] + [idx + 1]; + rtlpriv->link_info.tidtx_in4period[tid][3] = + rtlpriv->link_info.tidtx_inperiod[tid]; + + for (idx = 0; idx <= 3; idx++) + tidtx_inp4eriod[tid] += + rtlpriv->link_info.tidtx_in4period[tid][idx]; + aver_tidtx_inperiod[tid] = tidtx_inp4eriod[tid] / 4; + if (aver_tidtx_inperiod[tid] > 5000) + rtlpriv->link_info.higher_busytxtraffic[tid] = + true; + else + rtlpriv->link_info.higher_busytxtraffic[tid] = + false; + } + + if (((rtlpriv->link_info.num_rx_inperiod + + rtlpriv->link_info.num_tx_inperiod) > 8) || + (rtlpriv->link_info.num_rx_inperiod > 2)) + rtlpriv->enter_ps = true; + else + rtlpriv->enter_ps = false; + + /* LeisurePS only work in infra mode. */ + schedule_work(&rtlpriv->works.lps_change_work); + } + + rtlpriv->link_info.num_rx_inperiod = 0; + rtlpriv->link_info.num_tx_inperiod = 0; + for (tid = 0; tid <= 7; tid++) + rtlpriv->link_info.tidtx_inperiod[tid] = 0; + + rtlpriv->link_info.busytraffic = busytraffic; + rtlpriv->link_info.higher_busytraffic = higher_busytraffic; + rtlpriv->link_info.rx_busy_traffic = rx_busy_traffic; + rtlpriv->link_info.tx_busy_traffic = tx_busy_traffic; + rtlpriv->link_info.higher_busyrxtraffic = higher_busyrxtraffic; + + /* <3> DM */ + rtlpriv->cfg->ops->dm_watchdog(hw); + + /* <4> roaming */ + if (mac->link_state == MAC80211_LINKED && + mac->opmode == NL80211_IFTYPE_STATION) { + if ((rtlpriv->link_info.bcn_rx_inperiod + + rtlpriv->link_info.num_rx_inperiod) == 0) { + rtlpriv->link_info.roam_times++; + RT_TRACE(rtlpriv, COMP_ERR, DBG_DMESG, + "AP off for %d s\n", + (rtlpriv->link_info.roam_times * 2)); + + /* if we can't recv beacon for 6s, we should + * reconnect this AP + */ + if (rtlpriv->link_info.roam_times >= 3) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "AP off, try to reconnect now\n"); + rtlpriv->link_info.roam_times = 0; + ieee80211_connection_loss(rtlpriv->mac80211.vif); + } + } else { + rtlpriv->link_info.roam_times = 0; + } + } + rtlpriv->link_info.bcn_rx_inperiod = 0; +} + +void rtl_watch_dog_timer_callback(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + queue_delayed_work(rtlpriv->works.rtl_wq, + &rtlpriv->works.watchdog_wq, 0); + + mod_timer(&rtlpriv->works.watchdog_timer, + jiffies + MSECS(RTL_WATCH_DOG_TIME)); +} + +void rtl_fwevt_wq_callback(void *data) +{ + struct rtl_works *rtlworks = + container_of_dwork_rtl(data, struct rtl_works, fwevt_wq); + struct ieee80211_hw *hw = rtlworks->hw; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->cfg->ops->c2h_command_handle(hw); +} + +void rtl_easy_concurrent_retrytimer_callback(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_priv *buddy_priv = rtlpriv->buddy_priv; + + if (buddy_priv == NULL) + return; + + rtlpriv->cfg->ops->dualmac_easy_concurrent(hw); +} + +/********************************************************* + * + * frame process functions + * + *********************************************************/ +u8 *rtl_find_ie(u8 *data, unsigned int len, u8 ie) +{ + struct ieee80211_mgmt *mgmt = (void *)data; + u8 *pos, *end; + + pos = (u8 *)mgmt->u.beacon.variable; + end = data + len; + while (pos < end) { + if (pos + 2 + pos[1] > end) + return NULL; + + if (pos[0] == ie) + return pos; + + pos += 2 + pos[1]; + } + return NULL; +} + +/* when we use 2 rx ants we send IEEE80211_SMPS_OFF */ +/* when we use 1 rx ant we send IEEE80211_SMPS_STATIC */ +static struct sk_buff *rtl_make_smps_action(struct ieee80211_hw *hw, + enum ieee80211_smps_mode smps, u8 *da, u8 *bssid) +{ + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct sk_buff *skb; + struct ieee80211_mgmt *action_frame; + + /* 27 = header + category + action + smps mode */ + skb = dev_alloc_skb(27 + hw->extra_tx_headroom); + if (!skb) + return NULL; + + skb_reserve(skb, hw->extra_tx_headroom); + action_frame = (void *)skb_put(skb, 27); + memset(action_frame, 0, 27); + memcpy(action_frame->da, da, ETH_ALEN); + memcpy(action_frame->sa, rtlefuse->dev_addr, ETH_ALEN); + memcpy(action_frame->bssid, bssid, ETH_ALEN); + action_frame->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_ACTION); + action_frame->u.action.category = WLAN_CATEGORY_HT; + action_frame->u.action.u.ht_smps.action = WLAN_HT_ACTION_SMPS; + switch (smps) { + case IEEE80211_SMPS_AUTOMATIC:/* 0 */ + case IEEE80211_SMPS_NUM_MODES:/* 4 */ + WARN_ON(1); + case IEEE80211_SMPS_OFF:/* 1 */ /*MIMO_PS_NOLIMIT*/ + action_frame->u.action.u.ht_smps.smps_control = + WLAN_HT_SMPS_CONTROL_DISABLED;/* 0 */ + break; + case IEEE80211_SMPS_STATIC:/* 2 */ /*MIMO_PS_STATIC*/ + action_frame->u.action.u.ht_smps.smps_control = + WLAN_HT_SMPS_CONTROL_STATIC;/* 1 */ + break; + case IEEE80211_SMPS_DYNAMIC:/* 3 */ /*MIMO_PS_DYNAMIC*/ + action_frame->u.action.u.ht_smps.smps_control = + WLAN_HT_SMPS_CONTROL_DYNAMIC;/* 3 */ + break; + } + + return skb; +} + +int rtl_send_smps_action(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + enum ieee80211_smps_mode smps) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct sk_buff *skb = NULL; + struct rtl_tcb_desc tcb_desc; + u8 bssid[ETH_ALEN] = {0}; + + memset(&tcb_desc, 0, sizeof(struct rtl_tcb_desc)); + + if (rtlpriv->mac80211.act_scanning) + goto err_free; + + if (!sta) + goto err_free; + + if (unlikely(is_hal_stop(rtlhal) || ppsc->rfpwr_state != ERFON)) + goto err_free; + + if (!test_bit(RTL_STATUS_INTERFACE_START, &rtlpriv->status)) + goto err_free; + + if (rtlpriv->mac80211.opmode == NL80211_IFTYPE_AP) + memcpy(bssid, rtlpriv->efuse.dev_addr, ETH_ALEN); + else + memcpy(bssid, rtlpriv->mac80211.bssid, ETH_ALEN); + + skb = rtl_make_smps_action(hw, smps, sta->addr, bssid); + /* this is a type = mgmt * stype = action frame */ + if (skb) { + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct rtl_sta_info *sta_entry = + (struct rtl_sta_info *) sta->drv_priv; + sta_entry->mimo_ps = smps; + + info->control.rates[0].idx = 0; + info->band = hw->conf.chandef.chan->band; + rtlpriv->intf_ops->adapter_tx(hw, sta, skb, &tcb_desc); + } + return 1; + +err_free: + return 0; +} +EXPORT_SYMBOL(rtl_send_smps_action); + +/* There seem to be issues in mac80211 regarding when del ba frames can be + * received. As a work around, we make a fake del_ba if we receive a ba_req; + * however, rx_agg was opened to let mac80211 release some ba related + * resources. This del_ba is for tx only. + */ +struct sk_buff *rtl_make_del_ba(struct ieee80211_hw *hw, + u8 *sa, u8 *bssid, u16 tid) +{ + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct sk_buff *skb; + struct ieee80211_mgmt *action_frame; + u16 params; + + /* 27 = header + category + action + smps mode */ + skb = dev_alloc_skb(34 + hw->extra_tx_headroom); + if (!skb) + return NULL; + + skb_reserve(skb, hw->extra_tx_headroom); + action_frame = (void *)skb_put(skb, 34); + memset(action_frame, 0, 34); + memcpy(action_frame->sa, sa, ETH_ALEN); + memcpy(action_frame->da, rtlefuse->dev_addr, ETH_ALEN); + memcpy(action_frame->bssid, bssid, ETH_ALEN); + action_frame->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_ACTION); + action_frame->u.action.category = WLAN_CATEGORY_BACK; + action_frame->u.action.u.delba.action_code = WLAN_ACTION_DELBA; + params = (u16)(1 << 11); /* bit 11 initiator */ + params |= (u16)(tid << 12); /* bit 15:12 TID number */ + + action_frame->u.action.u.delba.params = cpu_to_le16(params); + action_frame->u.action.u.delba.reason_code = + cpu_to_le16(WLAN_REASON_QSTA_TIMEOUT); + + return skb; +} + +/********************************************************* + * + * IOT functions + * + *********************************************************/ +static bool rtl_chk_vendor_ouisub(struct ieee80211_hw *hw, + struct octet_string vendor_ie) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + bool matched = false; + static u8 athcap_1[] = { 0x00, 0x03, 0x7F }; + static u8 athcap_2[] = { 0x00, 0x13, 0x74 }; + static u8 broadcap_1[] = { 0x00, 0x10, 0x18 }; + static u8 broadcap_2[] = { 0x00, 0x0a, 0xf7 }; + static u8 broadcap_3[] = { 0x00, 0x05, 0xb5 }; + static u8 racap[] = { 0x00, 0x0c, 0x43 }; + static u8 ciscocap[] = { 0x00, 0x40, 0x96 }; + static u8 marvcap[] = { 0x00, 0x50, 0x43 }; + + if (memcmp(vendor_ie.octet, athcap_1, 3) == 0 || + memcmp(vendor_ie.octet, athcap_2, 3) == 0) { + rtlpriv->mac80211.vendor = PEER_ATH; + matched = true; + } else if (memcmp(vendor_ie.octet, broadcap_1, 3) == 0 || + memcmp(vendor_ie.octet, broadcap_2, 3) == 0 || + memcmp(vendor_ie.octet, broadcap_3, 3) == 0) { + rtlpriv->mac80211.vendor = PEER_BROAD; + matched = true; + } else if (memcmp(vendor_ie.octet, racap, 3) == 0) { + rtlpriv->mac80211.vendor = PEER_RAL; + matched = true; + } else if (memcmp(vendor_ie.octet, ciscocap, 3) == 0) { + rtlpriv->mac80211.vendor = PEER_CISCO; + matched = true; + } else if (memcmp(vendor_ie.octet, marvcap, 3) == 0) { + rtlpriv->mac80211.vendor = PEER_MARV; + matched = true; + } + + return matched; +} + +static bool rtl_find_221_ie(struct ieee80211_hw *hw, u8 *data, + unsigned int len) +{ + struct ieee80211_mgmt *mgmt = (void *)data; + struct octet_string vendor_ie; + u8 *pos, *end; + + pos = (u8 *)mgmt->u.beacon.variable; + end = data + len; + while (pos < end) { + if (pos[0] == 221) { + vendor_ie.length = pos[1]; + vendor_ie.octet = &pos[2]; + if (rtl_chk_vendor_ouisub(hw, vendor_ie)) + return true; + } + + if (pos + 2 + pos[1] > end) + return false; + + pos += 2 + pos[1]; + } + return false; +} + +void rtl_recognize_peer(struct ieee80211_hw *hw, u8 *data, unsigned int len) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct ieee80211_hdr *hdr = (void *)data; + u32 vendor = PEER_UNKNOWN; + + static u8 ap3_1[3] = { 0x00, 0x14, 0xbf }; + static u8 ap3_2[3] = { 0x00, 0x1a, 0x70 }; + static u8 ap3_3[3] = { 0x00, 0x1d, 0x7e }; + static u8 ap4_1[3] = { 0x00, 0x90, 0xcc }; + static u8 ap4_2[3] = { 0x00, 0x0e, 0x2e }; + static u8 ap4_3[3] = { 0x00, 0x18, 0x02 }; + static u8 ap4_4[3] = { 0x00, 0x17, 0x3f }; + static u8 ap4_5[3] = { 0x00, 0x1c, 0xdf }; + static u8 ap5_1[3] = { 0x00, 0x1c, 0xf0 }; + static u8 ap5_2[3] = { 0x00, 0x21, 0x91 }; + static u8 ap5_3[3] = { 0x00, 0x24, 0x01 }; + static u8 ap5_4[3] = { 0x00, 0x15, 0xe9 }; + static u8 ap5_5[3] = { 0x00, 0x17, 0x9A }; + static u8 ap5_6[3] = { 0x00, 0x18, 0xE7 }; + static u8 ap6_1[3] = { 0x00, 0x17, 0x94 }; + static u8 ap7_1[3] = { 0x00, 0x14, 0xa4 }; + + if (mac->opmode != NL80211_IFTYPE_STATION) + return; + + if (mac->link_state == MAC80211_NOLINK) { + mac->vendor = PEER_UNKNOWN; + return; + } + + if (mac->cnt_after_linked > 2) + return; + + /* check if this really is a beacon */ + if (!ieee80211_is_beacon(hdr->frame_control)) + return; + + /* min. beacon length + FCS_LEN */ + if (len <= 40 + FCS_LEN) + return; + + /* and only beacons from the associated BSSID, please */ + if (!ether_addr_equal(hdr->addr3, rtlpriv->mac80211.bssid)) + return; + + if (rtl_find_221_ie(hw, data, len)) + vendor = mac->vendor; + + if ((memcmp(mac->bssid, ap5_1, 3) == 0) || + (memcmp(mac->bssid, ap5_2, 3) == 0) || + (memcmp(mac->bssid, ap5_3, 3) == 0) || + (memcmp(mac->bssid, ap5_4, 3) == 0) || + (memcmp(mac->bssid, ap5_5, 3) == 0) || + (memcmp(mac->bssid, ap5_6, 3) == 0) || + vendor == PEER_ATH) { + vendor = PEER_ATH; + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, "=>ath find\n"); + } else if ((memcmp(mac->bssid, ap4_4, 3) == 0) || + (memcmp(mac->bssid, ap4_5, 3) == 0) || + (memcmp(mac->bssid, ap4_1, 3) == 0) || + (memcmp(mac->bssid, ap4_2, 3) == 0) || + (memcmp(mac->bssid, ap4_3, 3) == 0) || + vendor == PEER_RAL) { + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, "=>ral find\n"); + vendor = PEER_RAL; + } else if (memcmp(mac->bssid, ap6_1, 3) == 0 || + vendor == PEER_CISCO) { + vendor = PEER_CISCO; + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, "=>cisco find\n"); + } else if ((memcmp(mac->bssid, ap3_1, 3) == 0) || + (memcmp(mac->bssid, ap3_2, 3) == 0) || + (memcmp(mac->bssid, ap3_3, 3) == 0) || + vendor == PEER_BROAD) { + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, "=>broad find\n"); + vendor = PEER_BROAD; + } else if (memcmp(mac->bssid, ap7_1, 3) == 0 || + vendor == PEER_MARV) { + vendor = PEER_MARV; + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, "=>marv find\n"); + } + + mac->vendor = vendor; +} + +/********************************************************* + * + * sysfs functions + * + *********************************************************/ +static ssize_t rtl_show_debug_level(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct ieee80211_hw *hw = dev_get_drvdata(d); + struct rtl_priv *rtlpriv = rtl_priv(hw); + + return sprintf(buf, "0x%08X\n", rtlpriv->dbg.global_debuglevel); +} + +static ssize_t rtl_store_debug_level(struct device *d, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ieee80211_hw *hw = dev_get_drvdata(d); + struct rtl_priv *rtlpriv = rtl_priv(hw); + unsigned long val; + int ret; + + ret = strict_strtoul(buf, 0, &val); + if (ret) { + printk(KERN_DEBUG "%s is not in hex or decimal form.\n", buf); + } else { + rtlpriv->dbg.global_debuglevel = val; + printk(KERN_DEBUG "debuglevel:%x\n", + rtlpriv->dbg.global_debuglevel); + } + + return strnlen(buf, count); +} + +static DEVICE_ATTR(debug_level, S_IWUSR | S_IRUGO, + rtl_show_debug_level, rtl_store_debug_level); + +static struct attribute *rtl_sysfs_entries[] = { + + &dev_attr_debug_level.attr, + + NULL +}; + +/* + * "name" is folder name witch will be + * put in device directory like : + * sys/devices/pci0000:00/0000:00:1c.4/ + * 0000:06:00.0/rtl_sysfs + */ +struct attribute_group rtl_attribute_group = { + .name = "rtlsysfs", + .attrs = rtl_sysfs_entries, +}; + +MODULE_AUTHOR("lizhaoming "); +MODULE_AUTHOR("Realtek WlanFAE "); +MODULE_AUTHOR("Larry Finger "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Realtek 802.11n PCI wireless core"); + +struct rtl_global_var global_var = {}; + +static int __init rtl_core_module_init(void) +{ + if (rtl_rate_control_register()) + pr_err("Unable to register rtl_rc, use default RC !!\n"); + + /* init some global vars */ + INIT_LIST_HEAD(&global_var.glb_priv_list); + spin_lock_init(&global_var.glb_list_lock); + + return 0; +} + +static void __exit rtl_core_module_exit(void) +{ + /*RC*/ + rtl_rate_control_unregister(); +} + +module_init(rtl_core_module_init); +module_exit(rtl_core_module_exit); diff --git a/drivers/net/wireless/rtlwifi/base.h b/drivers/net/wireless/rtlwifi/base.h new file mode 100644 index 0000000..8576bc3 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/base.h @@ -0,0 +1,157 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL_BASE_H__ +#define __RTL_BASE_H__ + +enum ap_peer { + PEER_UNKNOWN = 0, + PEER_RTL = 1, + PEER_RTL_92SE = 2, + PEER_BROAD = 3, + PEER_RAL = 4, + PEER_ATH = 5, + PEER_CISCO = 6, + PEER_MARV = 7, + PEER_AIRGO = 9, + PEER_MAX = 10, +} ; + +#define RTL_DUMMY_OFFSET 0 +#define RTL_DUMMY_UNIT 8 +#define RTL_TX_DUMMY_SIZE (RTL_DUMMY_OFFSET * RTL_DUMMY_UNIT) +#define RTL_TX_DESC_SIZE 32 +#define RTL_TX_HEADER_SIZE (RTL_TX_DESC_SIZE + RTL_TX_DUMMY_SIZE) + +#define HT_AMSDU_SIZE_4K 3839 +#define HT_AMSDU_SIZE_8K 7935 + +#define MAX_BIT_RATE_40MHZ_MCS15 300 /* Mbps */ +#define MAX_BIT_RATE_40MHZ_MCS7 150 /* Mbps */ + +#define RTL_RATE_COUNT_LEGACY 12 +#define RTL_CHANNEL_COUNT 14 + +#define FRAME_OFFSET_FRAME_CONTROL 0 +#define FRAME_OFFSET_DURATION 2 +#define FRAME_OFFSET_ADDRESS1 4 +#define FRAME_OFFSET_ADDRESS2 10 +#define FRAME_OFFSET_ADDRESS3 16 +#define FRAME_OFFSET_SEQUENCE 22 +#define FRAME_OFFSET_ADDRESS4 24 + +#define SET_80211_HDR_FRAME_CONTROL(_hdr, _val) \ + WRITEEF2BYTE(_hdr, _val) +#define SET_80211_HDR_TYPE_AND_SUBTYPE(_hdr, _val) \ + WRITEEF1BYTE(_hdr, _val) +#define SET_80211_HDR_PWR_MGNT(_hdr, _val) \ + SET_BITS_TO_LE_2BYTE(_hdr, 12, 1, _val) +#define SET_80211_HDR_TO_DS(_hdr, _val) \ + SET_BITS_TO_LE_2BYTE(_hdr, 8, 1, _val) + +#define SET_80211_PS_POLL_AID(_hdr, _val) \ + (*(u16 *)((u8 *)(_hdr) + 2) = _val) +#define SET_80211_PS_POLL_BSSID(_hdr, _val) \ + memcpy(((u8 *)(_hdr)) + 4, (u8 *)(_val), ETH_ALEN) +#define SET_80211_PS_POLL_TA(_hdr, _val) \ + memcpy(((u8 *)(_hdr)) + 10, (u8 *)(_val), ETH_ALEN) + +#define SET_80211_HDR_DURATION(_hdr, _val) \ + (*(u16 *)((u8 *)(_hdr) + FRAME_OFFSET_DURATION) = le16_to_cpu(_val)) +#define SET_80211_HDR_ADDRESS1(_hdr, _val) \ + CP_MACADDR((u8 *)(_hdr)+FRAME_OFFSET_ADDRESS1, (u8 *)(_val)) +#define SET_80211_HDR_ADDRESS2(_hdr, _val) \ + CP_MACADDR((u8 *)(_hdr)+FRAME_OFFSET_ADDRESS2, (u8 *)(_val)) +#define SET_80211_HDR_ADDRESS3(_hdr, _val) \ + CP_MACADDR((u8 *)(_hdr)+FRAME_OFFSET_ADDRESS3, (u8 *)(_val)) +#define SET_80211_HDR_FRAGMENT_SEQUENCE(_hdr, _val) \ + WRITEEF2BYTE((u8 *)(_hdr)+FRAME_OFFSET_SEQUENCE, _val) + +#define SET_BEACON_PROBE_RSP_TIME_STAMP_LOW(__phdr, __val) \ + WRITEEF4BYTE(((u8 *)(__phdr)) + 24, __val) +#define SET_BEACON_PROBE_RSP_TIME_STAMP_HIGH(__phdr, __val) \ + WRITEEF4BYTE(((u8 *)(__phdr)) + 28, __val) +#define SET_BEACON_PROBE_RSP_BEACON_INTERVAL(__phdr, __val) \ + WRITEEF2BYTE(((u8 *)(__phdr)) + 32, __val) +#define GET_BEACON_PROBE_RSP_CAPABILITY_INFO(__phdr) \ + READEF2BYTE(((u8 *)(__phdr)) + 34) +#define SET_BEACON_PROBE_RSP_CAPABILITY_INFO(__phdr, __val) \ + WRITEEF2BYTE(((u8 *)(__phdr)) + 34, __val) +#define MASK_BEACON_PROBE_RSP_CAPABILITY_INFO(__phdr, __val) \ + SET_BEACON_PROBE_RSP_CAPABILITY_INFO(__phdr, \ + (GET_BEACON_PROBE_RSP_CAPABILITY_INFO(__phdr) & (~(__val)))) + +int rtl_init_core(struct ieee80211_hw *hw); +void rtl_deinit_core(struct ieee80211_hw *hw); +void rtl_init_rx_config(struct ieee80211_hw *hw); +void rtl_init_rfkill(struct ieee80211_hw *hw); +void rtl_deinit_rfkill(struct ieee80211_hw *hw); + +void rtl_beacon_statistic(struct ieee80211_hw *hw, struct sk_buff *skb); +void rtl_watch_dog_timer_callback(unsigned long data); +void rtl_deinit_deferred_work(struct ieee80211_hw *hw); + +bool rtl_action_proc(struct ieee80211_hw *hw, struct sk_buff *skb, u8 is_tx); +u8 rtl_is_special_data(struct ieee80211_hw *hw, struct sk_buff *skb, u8 is_tx); + +void rtl_watch_dog_timer_callback(unsigned long data); +int rtl_tx_agg_start(struct ieee80211_hw *hw, struct ieee80211_sta *sta, + u16 tid, u16 *ssn); +int rtl_tx_agg_stop(struct ieee80211_hw *hw, struct ieee80211_sta *sta, + u16 tid); +int rtl_tx_agg_oper(struct ieee80211_hw *hw, struct ieee80211_sta *sta, + u16 tid); +int rtl_rx_agg_start(struct ieee80211_hw *hw, struct ieee80211_sta *sta, + u16 tid); +int rtl_rx_agg_stop(struct ieee80211_hw *hw, struct ieee80211_sta *sta, + u16 tid); +void rtl_watchdog_wq_callback(void *data); +void rtl_fwevt_wq_callback(void *data); + +void rtl_get_tcb_desc(struct ieee80211_hw *hw, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff *skb, struct rtl_tcb_desc *tcb_desc); + +int rtl_send_smps_action(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + enum ieee80211_smps_mode smps); +u8 *rtl_find_ie(u8 *data, unsigned int len, u8 ie); +void rtl_recognize_peer(struct ieee80211_hw *hw, u8 *data, unsigned int len); +u8 rtl_tid_to_ac(u8 tid); +extern struct attribute_group rtl_attribute_group; +void rtl_easy_concurrent_retrytimer_callback(unsigned long data); +extern struct rtl_global_var global_var; +int rtlwifi_rate_mapping(struct ieee80211_hw *hw, + bool isht, u8 desc_rate, bool first_ampdu); +bool rtl_tx_mgmt_proc(struct ieee80211_hw *hw, struct sk_buff *skb); +struct sk_buff *rtl_make_del_ba(struct ieee80211_hw *hw, + u8 *sa, u8 *bssid, u16 tid); + +#endif diff --git a/drivers/net/wireless/rtlwifi/cam.c b/drivers/net/wireless/rtlwifi/cam.c new file mode 100644 index 0000000..0e510f7 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/cam.c @@ -0,0 +1,348 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include +#include "wifi.h" +#include "cam.h" + +void rtl_cam_reset_sec_info(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->sec.use_defaultkey = false; + rtlpriv->sec.pairwise_enc_algorithm = NO_ENCRYPTION; + rtlpriv->sec.group_enc_algorithm = NO_ENCRYPTION; + memset(rtlpriv->sec.key_buf, 0, KEY_BUF_SIZE * MAX_KEY_LEN); + memset(rtlpriv->sec.key_len, 0, KEY_BUF_SIZE); + rtlpriv->sec.pairwise_key = NULL; +} + +static void rtl_cam_program_entry(struct ieee80211_hw *hw, u32 entry_no, + u8 *mac_addr, u8 *key_cont_128, u16 us_config) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + u32 target_command; + u32 target_content = 0; + u8 entry_i; + + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, "key_cont_128: %6phC\n", + key_cont_128); + + for (entry_i = 0; entry_i < CAM_CONTENT_COUNT; entry_i++) { + target_command = entry_i + CAM_CONTENT_COUNT * entry_no; + target_command = target_command | BIT(31) | BIT(16); + + if (entry_i == 0) { + target_content = (u32) (*(mac_addr + 0)) << 16 | + (u32) (*(mac_addr + 1)) << 24 | (u32) us_config; + + rtl_write_dword(rtlpriv, rtlpriv->cfg->maps[WCAMI], + target_content); + rtl_write_dword(rtlpriv, rtlpriv->cfg->maps[RWCAM], + target_command); + + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, "WRITE %x: %x\n", + rtlpriv->cfg->maps[WCAMI], target_content); + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, + "The Key ID is %d\n", entry_no); + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, "WRITE %x: %x\n", + rtlpriv->cfg->maps[RWCAM], target_command); + + } else if (entry_i == 1) { + + target_content = (u32) (*(mac_addr + 5)) << 24 | + (u32) (*(mac_addr + 4)) << 16 | + (u32) (*(mac_addr + 3)) << 8 | + (u32) (*(mac_addr + 2)); + + rtl_write_dword(rtlpriv, rtlpriv->cfg->maps[WCAMI], + target_content); + rtl_write_dword(rtlpriv, rtlpriv->cfg->maps[RWCAM], + target_command); + + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, "WRITE A4: %x\n", + target_content); + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, "WRITE A0: %x\n", + target_command); + + } else { + + target_content = + (u32) (*(key_cont_128 + (entry_i * 4 - 8) + 3)) << + 24 | (u32) (*(key_cont_128 + (entry_i * 4 - 8) + 2)) + << 16 | + (u32) (*(key_cont_128 + (entry_i * 4 - 8) + 1)) << 8 + | (u32) (*(key_cont_128 + (entry_i * 4 - 8) + 0)); + + rtl_write_dword(rtlpriv, rtlpriv->cfg->maps[WCAMI], + target_content); + rtl_write_dword(rtlpriv, rtlpriv->cfg->maps[RWCAM], + target_command); + udelay(100); + + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, "WRITE A4: %x\n", + target_content); + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, "WRITE A0: %x\n", + target_command); + } + } + + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, "after set key, usconfig:%x\n", + us_config); +} + +u8 rtl_cam_add_one_entry(struct ieee80211_hw *hw, u8 *mac_addr, + u32 ul_key_id, u32 ul_entry_idx, u32 ul_enc_alg, + u32 ul_default_key, u8 *key_content) +{ + u32 us_config; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, + "EntryNo:%x, ulKeyId=%x, ulEncAlg=%x, ulUseDK=%x MacAddr %pM\n", + ul_entry_idx, ul_key_id, ul_enc_alg, + ul_default_key, mac_addr); + + if (ul_key_id == TOTAL_CAM_ENTRY) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "<=== ulKeyId exceed!\n"); + return 0; + } + + if (ul_default_key == 1) { + us_config = CFG_VALID | ((u16) (ul_enc_alg) << 2); + } else { + us_config = CFG_VALID | ((ul_enc_alg) << 2) | ul_key_id; + } + + rtl_cam_program_entry(hw, ul_entry_idx, mac_addr, + key_content, us_config); + + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, "<===\n"); + + return 1; + +} +EXPORT_SYMBOL(rtl_cam_add_one_entry); + +int rtl_cam_delete_one_entry(struct ieee80211_hw *hw, + u8 *mac_addr, u32 ul_key_id) +{ + u32 ul_command; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, "key_idx:%d\n", ul_key_id); + + ul_command = ul_key_id * CAM_CONTENT_COUNT; + ul_command = ul_command | BIT(31) | BIT(16); + + rtl_write_dword(rtlpriv, rtlpriv->cfg->maps[WCAMI], 0); + rtl_write_dword(rtlpriv, rtlpriv->cfg->maps[RWCAM], ul_command); + + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "rtl_cam_delete_one_entry(): WRITE A4: %x\n", 0); + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "rtl_cam_delete_one_entry(): WRITE A0: %x\n", ul_command); + + return 0; + +} +EXPORT_SYMBOL(rtl_cam_delete_one_entry); + +void rtl_cam_reset_all_entry(struct ieee80211_hw *hw) +{ + u32 ul_command; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + ul_command = BIT(31) | BIT(30); + rtl_write_dword(rtlpriv, rtlpriv->cfg->maps[RWCAM], ul_command); +} +EXPORT_SYMBOL(rtl_cam_reset_all_entry); + +void rtl_cam_mark_invalid(struct ieee80211_hw *hw, u8 uc_index) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + u32 ul_command; + u32 ul_content; + u32 ul_enc_algo = rtlpriv->cfg->maps[SEC_CAM_AES]; + + switch (rtlpriv->sec.pairwise_enc_algorithm) { + case WEP40_ENCRYPTION: + ul_enc_algo = rtlpriv->cfg->maps[SEC_CAM_WEP40]; + break; + case WEP104_ENCRYPTION: + ul_enc_algo = rtlpriv->cfg->maps[SEC_CAM_WEP104]; + break; + case TKIP_ENCRYPTION: + ul_enc_algo = rtlpriv->cfg->maps[SEC_CAM_TKIP]; + break; + case AESCCMP_ENCRYPTION: + ul_enc_algo = rtlpriv->cfg->maps[SEC_CAM_AES]; + break; + default: + ul_enc_algo = rtlpriv->cfg->maps[SEC_CAM_AES]; + } + + ul_content = (uc_index & 3) | ((u16) (ul_enc_algo) << 2); + + ul_content |= BIT(15); + ul_command = CAM_CONTENT_COUNT * uc_index; + ul_command = ul_command | BIT(31) | BIT(16); + + rtl_write_dword(rtlpriv, rtlpriv->cfg->maps[WCAMI], ul_content); + rtl_write_dword(rtlpriv, rtlpriv->cfg->maps[RWCAM], ul_command); + + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "rtl_cam_mark_invalid(): WRITE A4: %x\n", ul_content); + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "rtl_cam_mark_invalid(): WRITE A0: %x\n", ul_command); +} +EXPORT_SYMBOL(rtl_cam_mark_invalid); + +void rtl_cam_empty_entry(struct ieee80211_hw *hw, u8 uc_index) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + u32 ul_command; + u32 ul_content; + u32 ul_encalgo = rtlpriv->cfg->maps[SEC_CAM_AES]; + u8 entry_i; + + switch (rtlpriv->sec.pairwise_enc_algorithm) { + case WEP40_ENCRYPTION: + ul_encalgo = rtlpriv->cfg->maps[SEC_CAM_WEP40]; + break; + case WEP104_ENCRYPTION: + ul_encalgo = rtlpriv->cfg->maps[SEC_CAM_WEP104]; + break; + case TKIP_ENCRYPTION: + ul_encalgo = rtlpriv->cfg->maps[SEC_CAM_TKIP]; + break; + case AESCCMP_ENCRYPTION: + ul_encalgo = rtlpriv->cfg->maps[SEC_CAM_AES]; + break; + default: + ul_encalgo = rtlpriv->cfg->maps[SEC_CAM_AES]; + } + + for (entry_i = 0; entry_i < CAM_CONTENT_COUNT; entry_i++) { + + if (entry_i == 0) { + ul_content = + (uc_index & 0x03) | ((u16) (ul_encalgo) << 2); + ul_content |= BIT(15); + + } else { + ul_content = 0; + } + + ul_command = CAM_CONTENT_COUNT * uc_index + entry_i; + ul_command = ul_command | BIT(31) | BIT(16); + + rtl_write_dword(rtlpriv, rtlpriv->cfg->maps[WCAMI], ul_content); + rtl_write_dword(rtlpriv, rtlpriv->cfg->maps[RWCAM], ul_command); + + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, + "rtl_cam_empty_entry(): WRITE A4: %x\n", + ul_content); + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, + "rtl_cam_empty_entry(): WRITE A0: %x\n", + ul_command); + } + +} +EXPORT_SYMBOL(rtl_cam_empty_entry); + +u8 rtl_cam_get_free_entry(struct ieee80211_hw *hw, u8 *sta_addr) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 bitmap = (rtlpriv->sec.hwsec_cam_bitmap) >> 4; + u8 entry_idx = 0; + u8 i, *addr; + + if (NULL == sta_addr) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_EMERG, "sta_addr is NULL\n"); + return TOTAL_CAM_ENTRY; + } + /* Does STA already exist? */ + for (i = 4; i < TOTAL_CAM_ENTRY; i++) { + addr = rtlpriv->sec.hwsec_cam_sta_addr[i]; + if (memcmp(addr, sta_addr, ETH_ALEN) == 0) + return i; + } + /* Get a free CAM entry. */ + for (entry_idx = 4; entry_idx < TOTAL_CAM_ENTRY; entry_idx++) { + if ((bitmap & BIT(0)) == 0) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_EMERG, + "-----hwsec_cam_bitmap: 0x%x entry_idx=%d\n", + rtlpriv->sec.hwsec_cam_bitmap, entry_idx); + rtlpriv->sec.hwsec_cam_bitmap |= BIT(0) << entry_idx; + memcpy(rtlpriv->sec.hwsec_cam_sta_addr[entry_idx], + sta_addr, ETH_ALEN); + return entry_idx; + } + bitmap = bitmap >> 1; + } + return TOTAL_CAM_ENTRY; +} +EXPORT_SYMBOL(rtl_cam_get_free_entry); + +void rtl_cam_del_entry(struct ieee80211_hw *hw, u8 *sta_addr) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 bitmap; + u8 i, *addr; + + if (NULL == sta_addr) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_EMERG, "sta_addr is NULL\n"); + } + + if (is_zero_ether_addr(sta_addr)) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_EMERG, + "sta_addr is %pM\n", sta_addr); + return; + } + /* Does STA already exist? */ + for (i = 4; i < TOTAL_CAM_ENTRY; i++) { + addr = rtlpriv->sec.hwsec_cam_sta_addr[i]; + bitmap = (rtlpriv->sec.hwsec_cam_bitmap) >> i; + if (((bitmap & BIT(0)) == BIT(0)) && + (memcmp(addr, sta_addr, ETH_ALEN) == 0)) { + /* Remove from HW Security CAM */ + eth_zero_addr(rtlpriv->sec.hwsec_cam_sta_addr[i]); + rtlpriv->sec.hwsec_cam_bitmap &= ~(BIT(0) << i); + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, + "del CAM entry %d\n", i); + } + } + return; +} +EXPORT_SYMBOL(rtl_cam_del_entry); diff --git a/drivers/net/wireless/rtlwifi/cam.h b/drivers/net/wireless/rtlwifi/cam.h new file mode 100644 index 0000000..35e0008 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/cam.h @@ -0,0 +1,56 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL_CAM_H_ +#define __RTL_CAM_H_ + +#define CAM_CONTENT_COUNT 8 + +#define CFG_DEFAULT_KEY BIT(5) +#define CFG_VALID BIT(15) + +#define PAIRWISE_KEYIDX 0 +#define CAM_PAIRWISE_KEY_POSITION 4 + +#define CAM_CONFIG_USEDK 1 +#define CAM_CONFIG_NO_USEDK 0 + +extern void rtl_cam_reset_all_entry(struct ieee80211_hw *hw); +extern u8 rtl_cam_add_one_entry(struct ieee80211_hw *hw, u8 *mac_addr, + u32 ul_key_id, u32 ul_entry_idx, u32 ul_enc_alg, + u32 ul_default_key, u8 *key_content); +int rtl_cam_delete_one_entry(struct ieee80211_hw *hw, u8 *mac_addr, + u32 ul_key_id); +void rtl_cam_mark_invalid(struct ieee80211_hw *hw, u8 uc_index); +void rtl_cam_empty_entry(struct ieee80211_hw *hw, u8 uc_index); +void rtl_cam_reset_sec_info(struct ieee80211_hw *hw); +u8 rtl_cam_get_free_entry(struct ieee80211_hw *hw, u8 *sta_addr); +void rtl_cam_del_entry(struct ieee80211_hw *hw, u8 *sta_addr); + +#endif diff --git a/drivers/net/wireless/rtlwifi/core.c b/drivers/net/wireless/rtlwifi/core.c new file mode 100644 index 0000000..ee84844 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/core.c @@ -0,0 +1,1332 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "wifi.h" +#include "core.h" +#include "cam.h" +#include "base.h" +#include "pci.h" +#include "ps.h" + +#include + +void rtl_fw_cb(const struct firmware *firmware, void *context) +{ + struct ieee80211_hw *hw = context; + struct rtl_priv *rtlpriv = rtl_priv(hw); + int err; + + RT_TRACE(rtlpriv, COMP_ERR, DBG_LOUD, + "Firmware callback routine entered!\n"); + complete(&rtlpriv->firmware_loading_complete); + if (!firmware) { + pr_err("Firmware %s not available\n", rtlpriv->cfg->fw_name); + rtlpriv->max_fw_size = 0; + return; + } + if (firmware->size > rtlpriv->max_fw_size) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Firmware is too big!\n"); + release_firmware(firmware); + return; + } + memcpy(rtlpriv->rtlhal.pfirmware, firmware->data, firmware->size); + rtlpriv->rtlhal.fwsize = firmware->size; + release_firmware(firmware); + + err = ieee80211_register_hw(hw); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Can't register mac80211 hw\n"); + return; + } else { + rtlpriv->mac80211.mac80211_registered = 1; + } + set_bit(RTL_STATUS_INTERFACE_START, &rtlpriv->status); + + /*init rfkill */ + rtl_init_rfkill(hw); +} +EXPORT_SYMBOL(rtl_fw_cb); + +/*mutex for start & stop is must here. */ +static int rtl_op_start(struct ieee80211_hw *hw) +{ + int err; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (!is_hal_stop(rtlhal)) + return 0; + if (!test_bit(RTL_STATUS_INTERFACE_START, &rtlpriv->status)) + return 0; + mutex_lock(&rtlpriv->locks.conf_mutex); + err = rtlpriv->intf_ops->adapter_start(hw); + if (!err) + rtl_watch_dog_timer_callback((unsigned long)hw); + mutex_unlock(&rtlpriv->locks.conf_mutex); + return err; +} + +static void rtl_op_stop(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + if (is_hal_stop(rtlhal)) + return; + + /* here is must, because adhoc do stop and start, + * but stop with RFOFF may cause something wrong, + * like adhoc TP + */ + if (unlikely(ppsc->rfpwr_state == ERFOFF)) { + rtl_ips_nic_on(hw); + } + + mutex_lock(&rtlpriv->locks.conf_mutex); + + mac->link_state = MAC80211_NOLINK; + memset(mac->bssid, 0, 6); + mac->vendor = PEER_UNKNOWN; + + /*reset sec info */ + rtl_cam_reset_sec_info(hw); + + rtl_deinit_deferred_work(hw); + rtlpriv->intf_ops->adapter_stop(hw); + + mutex_unlock(&rtlpriv->locks.conf_mutex); +} + +static void rtl_op_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_tcb_desc tcb_desc; + memset(&tcb_desc, 0, sizeof(struct rtl_tcb_desc)); + + if (unlikely(is_hal_stop(rtlhal) || ppsc->rfpwr_state != ERFON)) + goto err_free; + + if (!test_bit(RTL_STATUS_INTERFACE_START, &rtlpriv->status)) + goto err_free; + + if (!rtlpriv->intf_ops->waitq_insert(hw, control->sta, skb)) + rtlpriv->intf_ops->adapter_tx(hw, control->sta, skb, &tcb_desc); + + return; + +err_free: + dev_kfree_skb_any(skb); +} + +static int rtl_op_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + int err = 0; + + vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER; + + if (mac->vif) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "vif has been set!! mac->vif = 0x%p\n", mac->vif); + return -EOPNOTSUPP; + } + + rtl_ips_nic_on(hw); + + mutex_lock(&rtlpriv->locks.conf_mutex); + + switch (ieee80211_vif_type_p2p(vif)) { + case NL80211_IFTYPE_P2P_CLIENT: + mac->p2p = P2P_ROLE_CLIENT; + /*fall through*/ + case NL80211_IFTYPE_STATION: + if (mac->beacon_enabled == 1) { + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "NL80211_IFTYPE_STATION\n"); + mac->beacon_enabled = 0; + rtlpriv->cfg->ops->update_interrupt_mask(hw, 0, + rtlpriv->cfg->maps + [RTL_IBSS_INT_MASKS]); + } + break; + case NL80211_IFTYPE_ADHOC: + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "NL80211_IFTYPE_ADHOC\n"); + + mac->link_state = MAC80211_LINKED; + rtlpriv->cfg->ops->set_bcn_reg(hw); + if (rtlpriv->rtlhal.current_bandtype == BAND_ON_2_4G) + mac->basic_rates = 0xfff; + else + mac->basic_rates = 0xff0; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_BASIC_RATE, + (u8 *) (&mac->basic_rates)); + + break; + case NL80211_IFTYPE_P2P_GO: + mac->p2p = P2P_ROLE_GO; + /*fall through*/ + case NL80211_IFTYPE_AP: + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "NL80211_IFTYPE_AP\n"); + + mac->link_state = MAC80211_LINKED; + rtlpriv->cfg->ops->set_bcn_reg(hw); + if (rtlpriv->rtlhal.current_bandtype == BAND_ON_2_4G) + mac->basic_rates = 0xfff; + else + mac->basic_rates = 0xff0; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_BASIC_RATE, + (u8 *) (&mac->basic_rates)); + break; + case NL80211_IFTYPE_MESH_POINT: + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "NL80211_IFTYPE_MESH_POINT\n"); + + mac->link_state = MAC80211_LINKED; + rtlpriv->cfg->ops->set_bcn_reg(hw); + if (rtlpriv->rtlhal.current_bandtype == BAND_ON_2_4G) + mac->basic_rates = 0xfff; + else + mac->basic_rates = 0xff0; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_BASIC_RATE, + (u8 *)(&mac->basic_rates)); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "operation mode %d is not supported!\n", vif->type); + err = -EOPNOTSUPP; + goto out; + } + + if (mac->p2p) { + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "p2p role %x\n", vif->type); + mac->basic_rates = 0xff0;/*disable cck rate for p2p*/ + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_BASIC_RATE, + (u8 *)(&mac->basic_rates)); + } + mac->vif = vif; + mac->opmode = vif->type; + rtlpriv->cfg->ops->set_network_type(hw, vif->type); + memcpy(mac->mac_addr, vif->addr, ETH_ALEN); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_ETHER_ADDR, mac->mac_addr); + +out: + mutex_unlock(&rtlpriv->locks.conf_mutex); + return err; +} + +static void rtl_op_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + + mutex_lock(&rtlpriv->locks.conf_mutex); + + /* Free beacon resources */ + if ((vif->type == NL80211_IFTYPE_AP) || + (vif->type == NL80211_IFTYPE_ADHOC) || + (vif->type == NL80211_IFTYPE_MESH_POINT)) { + if (mac->beacon_enabled == 1) { + mac->beacon_enabled = 0; + rtlpriv->cfg->ops->update_interrupt_mask(hw, 0, + rtlpriv->cfg->maps + [RTL_IBSS_INT_MASKS]); + } + } + + /* + *Note: We assume NL80211_IFTYPE_UNSPECIFIED as + *NO LINK for our hardware. + */ + mac->p2p = 0; + mac->vif = NULL; + mac->link_state = MAC80211_NOLINK; + memset(mac->bssid, 0, 6); + mac->vendor = PEER_UNKNOWN; + mac->opmode = NL80211_IFTYPE_UNSPECIFIED; + rtlpriv->cfg->ops->set_network_type(hw, mac->opmode); + mutex_unlock(&rtlpriv->locks.conf_mutex); +} + +static int rtl_op_change_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum nl80211_iftype new_type, bool p2p) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int ret; + rtl_op_remove_interface(hw, vif); + + vif->type = new_type; + vif->p2p = p2p; + ret = rtl_op_add_interface(hw, vif); + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "p2p %x\n", p2p); + return ret; +} + +static int rtl_op_config(struct ieee80211_hw *hw, u32 changed) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct ieee80211_conf *conf = &hw->conf; + + if (mac->skip_scan) + return 1; + + mutex_lock(&rtlpriv->locks.conf_mutex); + if (changed & IEEE80211_CONF_CHANGE_LISTEN_INTERVAL) { /*BIT(2)*/ + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "IEEE80211_CONF_CHANGE_LISTEN_INTERVAL\n"); + } + + /*For IPS */ + if (changed & IEEE80211_CONF_CHANGE_IDLE) { + if (hw->conf.flags & IEEE80211_CONF_IDLE) + rtl_ips_nic_off(hw); + else + rtl_ips_nic_on(hw); + } else { + /* + *although rfoff may not cause by ips, but we will + *check the reason in set_rf_power_state function + */ + if (unlikely(ppsc->rfpwr_state == ERFOFF)) + rtl_ips_nic_on(hw); + } + + /*For LPS */ + if (changed & IEEE80211_CONF_CHANGE_PS) { + cancel_delayed_work(&rtlpriv->works.ps_work); + cancel_delayed_work(&rtlpriv->works.ps_rfon_wq); + if (conf->flags & IEEE80211_CONF_PS) { + rtlpriv->psc.sw_ps_enabled = true; + /* sleep here is must, or we may recv the beacon and + * cause mac80211 into wrong ps state, this will cause + * power save nullfunc send fail, and further cause + * pkt loss, So sleep must quickly but not immediatly + * because that will cause nullfunc send by mac80211 + * fail, and cause pkt loss, we have tested that 5mA + * is worked very well */ + if (!rtlpriv->psc.multi_buffered) + queue_delayed_work(rtlpriv->works.rtl_wq, + &rtlpriv->works.ps_work, + MSECS(5)); + } else { + rtl_swlps_rf_awake(hw); + rtlpriv->psc.sw_ps_enabled = false; + } + } + + if (changed & IEEE80211_CONF_CHANGE_RETRY_LIMITS) { + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "IEEE80211_CONF_CHANGE_RETRY_LIMITS %x\n", + hw->conf.long_frame_max_tx_count); + mac->retry_long = hw->conf.long_frame_max_tx_count; + mac->retry_short = hw->conf.long_frame_max_tx_count; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_RETRY_LIMIT, + (u8 *) (&hw->conf. + long_frame_max_tx_count)); + } + + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + struct ieee80211_channel *channel = hw->conf.chandef.chan; + u8 wide_chan = (u8) channel->hw_value; + + if (mac->act_scanning) + mac->n_channels++; + + if (rtlpriv->dm.supp_phymode_switch && + mac->link_state < MAC80211_LINKED && + !mac->act_scanning) { + if (rtlpriv->cfg->ops->chk_switch_dmdp) + rtlpriv->cfg->ops->chk_switch_dmdp(hw); + } + + /* + *because we should back channel to + *current_network.chan in in scanning, + *So if set_chan == current_network.chan + *we should set it. + *because mac80211 tell us wrong bw40 + *info for cisco1253 bw20, so we modify + *it here based on UPPER & LOWER + */ + switch (cfg80211_get_chandef_type(&hw->conf.chandef)) { + case NL80211_CHAN_HT20: + case NL80211_CHAN_NO_HT: + /* SC */ + mac->cur_40_prime_sc = + PRIME_CHNL_OFFSET_DONT_CARE; + rtlphy->current_chan_bw = HT_CHANNEL_WIDTH_20; + mac->bw_40 = false; + break; + case NL80211_CHAN_HT40MINUS: + /* SC */ + mac->cur_40_prime_sc = PRIME_CHNL_OFFSET_UPPER; + rtlphy->current_chan_bw = + HT_CHANNEL_WIDTH_20_40; + mac->bw_40 = true; + + /*wide channel */ + wide_chan -= 2; + + break; + case NL80211_CHAN_HT40PLUS: + /* SC */ + mac->cur_40_prime_sc = PRIME_CHNL_OFFSET_LOWER; + rtlphy->current_chan_bw = + HT_CHANNEL_WIDTH_20_40; + mac->bw_40 = true; + + /*wide channel */ + wide_chan += 2; + + break; + default: + mac->bw_40 = false; + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + + if (wide_chan <= 0) + wide_chan = 1; + + /* In scanning, before we go offchannel we may send a ps = 1 + * null to AP, and then we may send a ps = 0 null to AP quickly, + * but first null may have caused AP to put lots of packet to + * hw tx buffer. These packets must be tx'd before we go off + * channel so we must delay more time to let AP flush these + * packets before going offchannel, or dis-association or + * delete BA will be caused by AP + */ + if (rtlpriv->mac80211.offchan_delay) { + rtlpriv->mac80211.offchan_delay = false; + mdelay(50); + } + rtlphy->current_channel = wide_chan; + + rtlpriv->cfg->ops->switch_channel(hw); + rtlpriv->cfg->ops->set_channel_access(hw); + rtlpriv->cfg->ops->set_bw_mode(hw, + cfg80211_get_chandef_type(&hw->conf.chandef)); + } + + mutex_unlock(&rtlpriv->locks.conf_mutex); + + return 0; +} + +static void rtl_op_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *new_flags, u64 multicast) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + + *new_flags &= RTL_SUPPORTED_FILTERS; + if (!changed_flags) + return; + + /*TODO: we disable broadcase now, so enable here */ + if (changed_flags & FIF_ALLMULTI) { + if (*new_flags & FIF_ALLMULTI) { + mac->rx_conf |= rtlpriv->cfg->maps[MAC_RCR_AM] | + rtlpriv->cfg->maps[MAC_RCR_AB]; + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "Enable receive multicast frame\n"); + } else { + mac->rx_conf &= ~(rtlpriv->cfg->maps[MAC_RCR_AM] | + rtlpriv->cfg->maps[MAC_RCR_AB]); + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "Disable receive multicast frame\n"); + } + } + + if (changed_flags & FIF_FCSFAIL) { + if (*new_flags & FIF_FCSFAIL) { + mac->rx_conf |= rtlpriv->cfg->maps[MAC_RCR_ACRC32]; + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "Enable receive FCS error frame\n"); + } else { + mac->rx_conf &= ~rtlpriv->cfg->maps[MAC_RCR_ACRC32]; + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "Disable receive FCS error frame\n"); + } + } + + /* if ssid not set to hw don't check bssid + * here just used for linked scanning, & linked + * and nolink check bssid is set in set network_type */ + if ((changed_flags & FIF_BCN_PRBRESP_PROMISC) && + (mac->link_state >= MAC80211_LINKED)) { + if (mac->opmode != NL80211_IFTYPE_AP && + mac->opmode != NL80211_IFTYPE_MESH_POINT) { + if (*new_flags & FIF_BCN_PRBRESP_PROMISC) { + rtlpriv->cfg->ops->set_chk_bssid(hw, false); + } else { + rtlpriv->cfg->ops->set_chk_bssid(hw, true); + } + } + } + + if (changed_flags & FIF_CONTROL) { + if (*new_flags & FIF_CONTROL) { + mac->rx_conf |= rtlpriv->cfg->maps[MAC_RCR_ACF]; + + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "Enable receive control frame\n"); + } else { + mac->rx_conf &= ~rtlpriv->cfg->maps[MAC_RCR_ACF]; + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "Disable receive control frame\n"); + } + } + + if (changed_flags & FIF_OTHER_BSS) { + if (*new_flags & FIF_OTHER_BSS) { + mac->rx_conf |= rtlpriv->cfg->maps[MAC_RCR_AAP]; + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "Enable receive other BSS's frame\n"); + } else { + mac->rx_conf &= ~rtlpriv->cfg->maps[MAC_RCR_AAP]; + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "Disable receive other BSS's frame\n"); + } + } +} +static int rtl_op_sta_add(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_sta_info *sta_entry; + + if (sta) { + sta_entry = (struct rtl_sta_info *) sta->drv_priv; + spin_lock_bh(&rtlpriv->locks.entry_list_lock); + list_add_tail(&sta_entry->list, &rtlpriv->entry_list); + spin_unlock_bh(&rtlpriv->locks.entry_list_lock); + if (rtlhal->current_bandtype == BAND_ON_2_4G) { + sta_entry->wireless_mode = WIRELESS_MODE_G; + if (sta->supp_rates[0] <= 0xf) + sta_entry->wireless_mode = WIRELESS_MODE_B; + if (sta->ht_cap.ht_supported == true) + sta_entry->wireless_mode = WIRELESS_MODE_N_24G; + + if (vif->type == NL80211_IFTYPE_ADHOC) + sta_entry->wireless_mode = WIRELESS_MODE_G; + } else if (rtlhal->current_bandtype == BAND_ON_5G) { + sta_entry->wireless_mode = WIRELESS_MODE_A; + if (sta->ht_cap.ht_supported == true) + sta_entry->wireless_mode = WIRELESS_MODE_N_24G; + + if (vif->type == NL80211_IFTYPE_ADHOC) + sta_entry->wireless_mode = WIRELESS_MODE_A; + } + /*disable cck rate for p2p*/ + if (mac->p2p) + sta->supp_rates[0] &= 0xfffffff0; + + memcpy(sta_entry->mac_addr, sta->addr, ETH_ALEN); + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_DMESG, + "Add sta addr is %pM\n", sta->addr); + rtlpriv->cfg->ops->update_rate_tbl(hw, sta, 0); + } + return 0; +} + +static int rtl_op_sta_remove(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_sta_info *sta_entry; + if (sta) { + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_DMESG, + "Remove sta addr is %pM\n", sta->addr); + sta_entry = (struct rtl_sta_info *) sta->drv_priv; + sta_entry->wireless_mode = 0; + sta_entry->ratr_index = 0; + + spin_lock_bh(&rtlpriv->locks.entry_list_lock); + list_del(&sta_entry->list); + spin_unlock_bh(&rtlpriv->locks.entry_list_lock); + } + return 0; +} + +static int _rtl_get_hal_qnum(u16 queue) +{ + int qnum; + + switch (queue) { + case 0: + qnum = AC3_VO; + break; + case 1: + qnum = AC2_VI; + break; + case 2: + qnum = AC0_BE; + break; + case 3: + qnum = AC1_BK; + break; + default: + qnum = AC0_BE; + break; + } + return qnum; +} + +/* + *for mac80211 VO = 0, VI = 1, BE = 2, BK = 3 + *for rtl819x BE = 0, BK = 1, VI = 2, VO = 3 + */ +static int rtl_op_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *param) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + int aci; + + if (queue >= AC_MAX) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "queue number %d is incorrect!\n", queue); + return -EINVAL; + } + + aci = _rtl_get_hal_qnum(queue); + mac->ac[aci].aifs = param->aifs; + mac->ac[aci].cw_min = cpu_to_le16(param->cw_min); + mac->ac[aci].cw_max = cpu_to_le16(param->cw_max); + mac->ac[aci].tx_op = cpu_to_le16(param->txop); + memcpy(&mac->edca_param[aci], param, sizeof(*param)); + rtlpriv->cfg->ops->set_qos(hw, aci); + return 0; +} + +static void rtl_op_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, u32 changed) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct ieee80211_sta *sta = NULL; + + mutex_lock(&rtlpriv->locks.conf_mutex); + if ((vif->type == NL80211_IFTYPE_ADHOC) || + (vif->type == NL80211_IFTYPE_AP) || + (vif->type == NL80211_IFTYPE_MESH_POINT)) { + if ((changed & BSS_CHANGED_BEACON) || + (changed & BSS_CHANGED_BEACON_ENABLED && + bss_conf->enable_beacon)) { + if (mac->beacon_enabled == 0) { + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_DMESG, + "BSS_CHANGED_BEACON_ENABLED\n"); + + /*start hw beacon interrupt. */ + /*rtlpriv->cfg->ops->set_bcn_reg(hw); */ + mac->beacon_enabled = 1; + rtlpriv->cfg->ops->update_interrupt_mask(hw, + rtlpriv->cfg->maps + [RTL_IBSS_INT_MASKS], + 0); + + if (rtlpriv->cfg->ops->linked_set_reg) + rtlpriv->cfg->ops->linked_set_reg(hw); + } + } + if ((changed & BSS_CHANGED_BEACON_ENABLED && + !bss_conf->enable_beacon)) { + if (mac->beacon_enabled == 1) { + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_DMESG, + "ADHOC DISABLE BEACON\n"); + + mac->beacon_enabled = 0; + rtlpriv->cfg->ops->update_interrupt_mask(hw, 0, + rtlpriv->cfg->maps + [RTL_IBSS_INT_MASKS]); + } + } + if (changed & BSS_CHANGED_BEACON_INT) { + RT_TRACE(rtlpriv, COMP_BEACON, DBG_TRACE, + "BSS_CHANGED_BEACON_INT\n"); + mac->beacon_interval = bss_conf->beacon_int; + rtlpriv->cfg->ops->set_bcn_intv(hw); + } + } + + /*TODO: reference to enum ieee80211_bss_change */ + if (changed & BSS_CHANGED_ASSOC) { + if (bss_conf->assoc) { + struct ieee80211_sta *sta = NULL; + /* we should reset all sec info & cam + * before set cam after linked, we should not + * reset in disassoc, that will cause tkip->wep + * fail because some flag will be wrong */ + /* reset sec info */ + rtl_cam_reset_sec_info(hw); + /* reset cam to fix wep fail issue + * when change from wpa to wep */ + rtl_cam_reset_all_entry(hw); + + mac->link_state = MAC80211_LINKED; + mac->cnt_after_linked = 0; + mac->assoc_id = bss_conf->aid; + memcpy(mac->bssid, bss_conf->bssid, 6); + + if (rtlpriv->cfg->ops->linked_set_reg) + rtlpriv->cfg->ops->linked_set_reg(hw); + rcu_read_lock(); + sta = ieee80211_find_sta(vif, (u8 *)bss_conf->bssid); + + if (vif->type == NL80211_IFTYPE_STATION && sta) + rtlpriv->cfg->ops->update_rate_tbl(hw, sta, 0); + RT_TRACE(rtlpriv, COMP_EASY_CONCURRENT, DBG_LOUD, + "send PS STATIC frame\n"); + if (rtlpriv->dm.supp_phymode_switch) { + if (sta->ht_cap.ht_supported) + rtl_send_smps_action(hw, sta, + IEEE80211_SMPS_STATIC); + } + rcu_read_unlock(); + + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_DMESG, + "BSS_CHANGED_ASSOC\n"); + } else { + if (mac->link_state == MAC80211_LINKED) { + rtlpriv->enter_ps = false; + schedule_work(&rtlpriv->works.lps_change_work); + } + + if (ppsc->p2p_ps_info.p2p_ps_mode > P2P_PS_NONE) + rtl_p2p_ps_cmd(hw, P2P_PS_DISABLE); + mac->link_state = MAC80211_NOLINK; + memset(mac->bssid, 0, 6); + mac->vendor = PEER_UNKNOWN; + + if (rtlpriv->dm.supp_phymode_switch) { + if (rtlpriv->cfg->ops->chk_switch_dmdp) + rtlpriv->cfg->ops->chk_switch_dmdp(hw); + } + + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_DMESG, + "BSS_CHANGED_UN_ASSOC\n"); + } + } + + if (changed & BSS_CHANGED_ERP_CTS_PROT) { + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_TRACE, + "BSS_CHANGED_ERP_CTS_PROT\n"); + mac->use_cts_protect = bss_conf->use_cts_prot; + } + + if (changed & BSS_CHANGED_ERP_PREAMBLE) { + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, + "BSS_CHANGED_ERP_PREAMBLE use short preamble:%x\n", + bss_conf->use_short_preamble); + + mac->short_preamble = bss_conf->use_short_preamble; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_ACK_PREAMBLE, + &mac->short_preamble); + } + + if (changed & BSS_CHANGED_ERP_SLOT) { + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_TRACE, + "BSS_CHANGED_ERP_SLOT\n"); + + if (bss_conf->use_short_slot) + mac->slot_time = RTL_SLOT_TIME_9; + else + mac->slot_time = RTL_SLOT_TIME_20; + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SLOT_TIME, + &mac->slot_time); + } + + if (changed & BSS_CHANGED_HT) { + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_TRACE, "BSS_CHANGED_HT\n"); + rcu_read_lock(); + sta = get_sta(hw, vif, bss_conf->bssid); + if (sta) { + if (sta->ht_cap.ampdu_density > + mac->current_ampdu_density) + mac->current_ampdu_density = + sta->ht_cap.ampdu_density; + if (sta->ht_cap.ampdu_factor < + mac->current_ampdu_factor) + mac->current_ampdu_factor = + sta->ht_cap.ampdu_factor; + } + rcu_read_unlock(); + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SHORTGI_DENSITY, + &mac->max_mss_density); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_AMPDU_FACTOR, + &mac->current_ampdu_factor); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_AMPDU_MIN_SPACE, + &mac->current_ampdu_density); + } + + if (changed & BSS_CHANGED_BSSID) { + u32 basic_rates; + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_BSSID, + (u8 *) bss_conf->bssid); + + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_DMESG, "%pM\n", + bss_conf->bssid); + + mac->vendor = PEER_UNKNOWN; + memcpy(mac->bssid, bss_conf->bssid, 6); + rtlpriv->cfg->ops->set_network_type(hw, vif->type); + + rcu_read_lock(); + sta = get_sta(hw, vif, bss_conf->bssid); + if (!sta) { + rcu_read_unlock(); + goto out; + } + + if (rtlhal->current_bandtype == BAND_ON_5G) { + mac->mode = WIRELESS_MODE_A; + } else { + if (sta->supp_rates[0] <= 0xf) + mac->mode = WIRELESS_MODE_B; + else + mac->mode = WIRELESS_MODE_G; + } + + if (sta->ht_cap.ht_supported) { + if (rtlhal->current_bandtype == BAND_ON_2_4G) + mac->mode = WIRELESS_MODE_N_24G; + else + mac->mode = WIRELESS_MODE_N_5G; + } + + /* just station need it, because ibss & ap mode will + * set in sta_add, and will be NULL here */ + if (mac->opmode == NL80211_IFTYPE_STATION) { + struct rtl_sta_info *sta_entry; + sta_entry = (struct rtl_sta_info *) sta->drv_priv; + sta_entry->wireless_mode = mac->mode; + } + + if (sta->ht_cap.ht_supported) { + mac->ht_enable = true; + + /* + * for cisco 1252 bw20 it's wrong + * if (ht_cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) { + * mac->bw_40 = true; + * } + * */ + } + + if (changed & BSS_CHANGED_BASIC_RATES) { + /* for 5G must << RATE_6M_INDEX = 4, + * because 5G have no cck rate*/ + if (rtlhal->current_bandtype == BAND_ON_5G) + basic_rates = sta->supp_rates[1] << 4; + else + basic_rates = sta->supp_rates[0]; + + mac->basic_rates = basic_rates; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_BASIC_RATE, + (u8 *) (&basic_rates)); + } + rcu_read_unlock(); + } + + /* + * For FW LPS: + * To tell firmware we have connected + * to an AP. For 92SE/CE power save v2. + */ + if (changed & BSS_CHANGED_ASSOC) { + if (bss_conf->assoc) { + if (ppsc->fwctrl_lps) { + u8 mstatus = RT_MEDIA_CONNECT; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_H2C_FW_JOINBSSRPT, + &mstatus); + ppsc->report_linked = true; + } + } else { + if (ppsc->fwctrl_lps) { + u8 mstatus = RT_MEDIA_DISCONNECT; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_H2C_FW_JOINBSSRPT, + &mstatus); + ppsc->report_linked = false; + } + } + if (rtlpriv->cfg->ops->bt_wifi_media_status_notify) + rtlpriv->cfg->ops->bt_wifi_media_status_notify(hw, + ppsc->report_linked); + } + +out: + mutex_unlock(&rtlpriv->locks.conf_mutex); +} + +static u64 rtl_op_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u64 tsf; + + rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_CORRECT_TSF, (u8 *) (&tsf)); + return tsf; +} + +static void rtl_op_set_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + u64 tsf) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u8 bibss = (mac->opmode == NL80211_IFTYPE_ADHOC) ? 1 : 0; + + mac->tsf = tsf; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_CORRECT_TSF, &bibss); +} + +static void rtl_op_reset_tsf(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tmp = 0; + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_DUAL_TSF_RST, &tmp); +} + +static void rtl_op_sta_notify(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum sta_notify_cmd cmd, + struct ieee80211_sta *sta) +{ + switch (cmd) { + case STA_NOTIFY_SLEEP: + break; + case STA_NOTIFY_AWAKE: + break; + default: + break; + } +} + +static int rtl_op_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, u16 *ssn, + u8 buf_size) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + switch (action) { + case IEEE80211_AMPDU_TX_START: + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_TRACE, + "IEEE80211_AMPDU_TX_START: TID:%d\n", tid); + return rtl_tx_agg_start(hw, sta, tid, ssn); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_TRACE, + "IEEE80211_AMPDU_TX_STOP: TID:%d\n", tid); + return rtl_tx_agg_stop(hw, sta, tid); + case IEEE80211_AMPDU_TX_OPERATIONAL: + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_TRACE, + "IEEE80211_AMPDU_TX_OPERATIONAL:TID:%d\n", tid); + rtl_tx_agg_oper(hw, sta, tid); + break; + case IEEE80211_AMPDU_RX_START: + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_TRACE, + "IEEE80211_AMPDU_RX_START:TID:%d\n", tid); + return rtl_rx_agg_start(hw, sta, tid); + case IEEE80211_AMPDU_RX_STOP: + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_TRACE, + "IEEE80211_AMPDU_RX_STOP:TID:%d\n", tid); + return rtl_rx_agg_stop(hw, sta, tid); + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "IEEE80211_AMPDU_ERR!!!!:\n"); + return -EOPNOTSUPP; + } + return 0; +} + +static void rtl_op_sw_scan_start(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, "\n"); + mac->act_scanning = true; + if (rtlpriv->link_info.higher_busytraffic) { + mac->skip_scan = true; + return; + } + + if (rtlpriv->dm.supp_phymode_switch) { + if (rtlpriv->cfg->ops->chk_switch_dmdp) + rtlpriv->cfg->ops->chk_switch_dmdp(hw); + } + if (mac->link_state == MAC80211_LINKED) { + rtlpriv->enter_ps = false; + schedule_work(&rtlpriv->works.lps_change_work); + mac->link_state = MAC80211_LINKED_SCANNING; + } else { + rtl_ips_nic_on(hw); + } + + /* Dual mac */ + rtlpriv->rtlhal.load_imrandiqk_setting_for2g = false; + + rtlpriv->cfg->ops->led_control(hw, LED_CTL_SITE_SURVEY); + rtlpriv->cfg->ops->scan_operation_backup(hw, SCAN_OPT_BACKUP); +} + +static void rtl_op_sw_scan_complete(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + + RT_TRACE(rtlpriv, COMP_MAC80211, DBG_LOUD, "\n"); + mac->act_scanning = false; + mac->skip_scan = false; + if (rtlpriv->link_info.higher_busytraffic) + return; + + /*p2p will use 1/6/11 to scan */ + if (mac->n_channels == 3) + mac->p2p_in_use = true; + else + mac->p2p_in_use = false; + mac->n_channels = 0; + /* Dual mac */ + rtlpriv->rtlhal.load_imrandiqk_setting_for2g = false; + + if (mac->link_state == MAC80211_LINKED_SCANNING) { + mac->link_state = MAC80211_LINKED; + if (mac->opmode == NL80211_IFTYPE_STATION) { + /* fix fwlps issue */ + rtlpriv->cfg->ops->set_network_type(hw, mac->opmode); + } + } + + rtlpriv->cfg->ops->scan_operation_backup(hw, SCAN_OPT_RESTORE); +} + +static int rtl_op_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u8 key_type = NO_ENCRYPTION; + u8 key_idx; + bool group_key = false; + bool wep_only = false; + int err = 0; + u8 mac_addr[ETH_ALEN]; + u8 bcast_addr[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + if (rtlpriv->cfg->mod_params->sw_crypto || rtlpriv->sec.use_sw_sec) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "not open hw encryption\n"); + return -ENOSPC; /*User disabled HW-crypto */ + } + /* To support IBSS, use sw-crypto for GTK */ + if (((vif->type == NL80211_IFTYPE_ADHOC) || + (vif->type == NL80211_IFTYPE_MESH_POINT)) && + !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE)) + return -ENOSPC; + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "%s hardware based encryption for keyidx: %d, mac: %pM\n", + cmd == SET_KEY ? "Using" : "Disabling", key->keyidx, + sta ? sta->addr : bcast_addr); + rtlpriv->sec.being_setkey = true; + rtl_ips_nic_on(hw); + mutex_lock(&rtlpriv->locks.conf_mutex); + /* <1> get encryption alg */ + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + key_type = WEP40_ENCRYPTION; + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, "alg:WEP40\n"); + break; + case WLAN_CIPHER_SUITE_WEP104: + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, "alg:WEP104\n"); + key_type = WEP104_ENCRYPTION; + break; + case WLAN_CIPHER_SUITE_TKIP: + key_type = TKIP_ENCRYPTION; + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, "alg:TKIP\n"); + break; + case WLAN_CIPHER_SUITE_CCMP: + key_type = AESCCMP_ENCRYPTION; + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, "alg:CCMP\n"); + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + /*HW doesn't support CMAC encryption, use software CMAC */ + key_type = AESCMAC_ENCRYPTION; + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, "alg:CMAC\n"); + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "HW don't support CMAC encryption, use software CMAC\n"); + err = -EOPNOTSUPP; + goto out_unlock; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "alg_err:%x!!!!\n", + key->cipher); + goto out_unlock; + } + if (key_type == WEP40_ENCRYPTION || + key_type == WEP104_ENCRYPTION || + mac->opmode == NL80211_IFTYPE_ADHOC) + rtlpriv->sec.use_defaultkey = true; + + /* <2> get key_idx */ + key_idx = (u8) (key->keyidx); + if (key_idx > 3) + goto out_unlock; + /* <3> if pairwise key enable_hw_sec */ + group_key = !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE); + + /* wep always be group key, but there are two conditions: + * 1) wep only: is just for wep enc, in this condition + * rtlpriv->sec.pairwise_enc_algorithm == NO_ENCRYPTION + * will be true & enable_hw_sec will be set when wep + * key setting. + * 2) wep(group) + AES(pairwise): some AP like cisco + * may use it, in this condition enable_hw_sec will not + * be set when wep key setting */ + /* we must reset sec_info after lingked before set key, + * or some flag will be wrong*/ + if (vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_MESH_POINT) { + if (!group_key || key_type == WEP40_ENCRYPTION || + key_type == WEP104_ENCRYPTION) { + if (group_key) + wep_only = true; + rtlpriv->cfg->ops->enable_hw_sec(hw); + } + } else { + if ((!group_key) || (mac->opmode == NL80211_IFTYPE_ADHOC) || + rtlpriv->sec.pairwise_enc_algorithm == NO_ENCRYPTION) { + if (rtlpriv->sec.pairwise_enc_algorithm == + NO_ENCRYPTION && + (key_type == WEP40_ENCRYPTION || + key_type == WEP104_ENCRYPTION)) + wep_only = true; + rtlpriv->sec.pairwise_enc_algorithm = key_type; + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "set enable_hw_sec, key_type:%x(OPEN:0 WEP40:1 TKIP:2 AES:4 WEP104:5)\n", + key_type); + rtlpriv->cfg->ops->enable_hw_sec(hw); + } + } + /* <4> set key based on cmd */ + switch (cmd) { + case SET_KEY: + if (wep_only) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "set WEP(group/pairwise) key\n"); + /* Pairwise key with an assigned MAC address. */ + rtlpriv->sec.pairwise_enc_algorithm = key_type; + rtlpriv->sec.group_enc_algorithm = key_type; + /*set local buf about wep key. */ + memcpy(rtlpriv->sec.key_buf[key_idx], + key->key, key->keylen); + rtlpriv->sec.key_len[key_idx] = key->keylen; + eth_zero_addr(mac_addr); + } else if (group_key) { /* group key */ + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "set group key\n"); + /* group key */ + rtlpriv->sec.group_enc_algorithm = key_type; + /*set local buf about group key. */ + memcpy(rtlpriv->sec.key_buf[key_idx], + key->key, key->keylen); + rtlpriv->sec.key_len[key_idx] = key->keylen; + memcpy(mac_addr, bcast_addr, ETH_ALEN); + } else { /* pairwise key */ + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "set pairwise key\n"); + if (!sta) { + RT_ASSERT(false, + "pairwise key without mac_addr\n"); + + err = -EOPNOTSUPP; + goto out_unlock; + } + /* Pairwise key with an assigned MAC address. */ + rtlpriv->sec.pairwise_enc_algorithm = key_type; + /*set local buf about pairwise key. */ + memcpy(rtlpriv->sec.key_buf[PAIRWISE_KEYIDX], + key->key, key->keylen); + rtlpriv->sec.key_len[PAIRWISE_KEYIDX] = key->keylen; + rtlpriv->sec.pairwise_key = + rtlpriv->sec.key_buf[PAIRWISE_KEYIDX]; + memcpy(mac_addr, sta->addr, ETH_ALEN); + } + rtlpriv->cfg->ops->set_key(hw, key_idx, mac_addr, + group_key, key_type, wep_only, + false); + /* <5> tell mac80211 do something: */ + /*must use sw generate IV, or can not work !!!!. */ + key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + key->hw_key_idx = key_idx; + if (key_type == TKIP_ENCRYPTION) + key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC; + /*use software CCMP encryption for management frames (MFP) */ + if (key_type == AESCCMP_ENCRYPTION) + key->flags |= IEEE80211_KEY_FLAG_SW_MGMT_TX; + break; + case DISABLE_KEY: + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "disable key delete one entry\n"); + /*set local buf about wep key. */ + if (vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_MESH_POINT) { + if (sta) + rtl_cam_del_entry(hw, sta->addr); + } + memset(rtlpriv->sec.key_buf[key_idx], 0, key->keylen); + rtlpriv->sec.key_len[key_idx] = 0; + eth_zero_addr(mac_addr); + /* + *mac80211 will delete entrys one by one, + *so don't use rtl_cam_reset_all_entry + *or clear all entry here. + */ + rtl_cam_delete_one_entry(hw, mac_addr, key_idx); + + rtl_cam_reset_sec_info(hw); + + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "cmd_err:%x!!!!\n", cmd); + } +out_unlock: + mutex_unlock(&rtlpriv->locks.conf_mutex); + rtlpriv->sec.being_setkey = false; + return err; +} + +static void rtl_op_rfkill_poll(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + bool radio_state; + bool blocked; + u8 valid = 0; + + if (!test_bit(RTL_STATUS_INTERFACE_START, &rtlpriv->status)) + return; + + mutex_lock(&rtlpriv->locks.conf_mutex); + + /*if Radio On return true here */ + radio_state = rtlpriv->cfg->ops->radio_onoff_checking(hw, &valid); + + if (valid) { + if (unlikely(radio_state != rtlpriv->rfkill.rfkill_state)) { + rtlpriv->rfkill.rfkill_state = radio_state; + + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "wireless radio switch turned %s\n", + radio_state ? "on" : "off"); + + blocked = (rtlpriv->rfkill.rfkill_state == 1) ? 0 : 1; + wiphy_rfkill_set_hw_state(hw->wiphy, blocked); + } + } + + mutex_unlock(&rtlpriv->locks.conf_mutex); +} + +/* this function is called by mac80211 to flush tx buffer + * before switch channel or power save, or tx buffer packet + * maybe send after offchannel or rf sleep, this may cause + * dis-association by AP */ +static void rtl_op_flush(struct ieee80211_hw *hw, u32 queues, bool drop) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (rtlpriv->intf_ops->flush) + rtlpriv->intf_ops->flush(hw, drop); +} + +const struct ieee80211_ops rtl_ops = { + .start = rtl_op_start, + .stop = rtl_op_stop, + .tx = rtl_op_tx, + .add_interface = rtl_op_add_interface, + .remove_interface = rtl_op_remove_interface, + .change_interface = rtl_op_change_interface, + .config = rtl_op_config, + .configure_filter = rtl_op_configure_filter, + .sta_add = rtl_op_sta_add, + .sta_remove = rtl_op_sta_remove, + .set_key = rtl_op_set_key, + .conf_tx = rtl_op_conf_tx, + .bss_info_changed = rtl_op_bss_info_changed, + .get_tsf = rtl_op_get_tsf, + .set_tsf = rtl_op_set_tsf, + .reset_tsf = rtl_op_reset_tsf, + .sta_notify = rtl_op_sta_notify, + .ampdu_action = rtl_op_ampdu_action, + .sw_scan_start = rtl_op_sw_scan_start, + .sw_scan_complete = rtl_op_sw_scan_complete, + .rfkill_poll = rtl_op_rfkill_poll, + .flush = rtl_op_flush, +}; diff --git a/drivers/net/wireless/rtlwifi/core.h b/drivers/net/wireless/rtlwifi/core.h new file mode 100644 index 0000000..2fe46a1 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/core.h @@ -0,0 +1,45 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * Tmis program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * Tmis program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * tmis program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * Tme full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL_CORE_H__ +#define __RTL_CORE_H__ + +#define RTL_SUPPORTED_FILTERS \ + (FIF_PROMISC_IN_BSS | \ + FIF_ALLMULTI | FIF_CONTROL | \ + FIF_OTHER_BSS | \ + FIF_FCSFAIL | \ + FIF_BCN_PRBRESP_PROMISC) + +#define RTL_SUPPORTED_CTRL_FILTER 0xFF + +extern const struct ieee80211_ops rtl_ops; +void rtl_fw_cb(const struct firmware *firmware, void *context); + +#endif diff --git a/drivers/net/wireless/rtlwifi/debug.c b/drivers/net/wireless/rtlwifi/debug.c new file mode 100644 index 0000000..7d52d3d --- /dev/null +++ b/drivers/net/wireless/rtlwifi/debug.c @@ -0,0 +1,53 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * Tmis program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * Tmis program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * tmis program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * Tme full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + *****************************************************************************/ + +#include "wifi.h" + +#include + +void rtl_dbgp_flag_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 i; + + rtlpriv->dbg.global_debugcomponents = + COMP_ERR | COMP_FW | COMP_INIT | COMP_RECV | COMP_SEND | + COMP_MLME | COMP_SCAN | COMP_INTR | COMP_LED | COMP_SEC | + COMP_BEACON | COMP_RATE | COMP_RXDESC | COMP_DIG | COMP_TXAGC | + COMP_POWER | COMP_POWER_TRACKING | COMP_BB_POWERSAVING | COMP_SWAS | + COMP_RF | COMP_TURBO | COMP_RATR | COMP_CMD | + COMP_EFUSE | COMP_QOS | COMP_MAC80211 | COMP_REGD | COMP_CHAN | + COMP_EASY_CONCURRENT | COMP_EFUSE | COMP_QOS | COMP_MAC80211 | + COMP_REGD | COMP_CHAN | COMP_BT_COEXIST; + + + for (i = 0; i < DBGP_TYPE_MAX; i++) + rtlpriv->dbg.dbgp_type[i] = 0; + + /*Init Debug flag enable condition */ +} diff --git a/drivers/net/wireless/rtlwifi/debug.h b/drivers/net/wireless/rtlwifi/debug.h new file mode 100644 index 0000000..2e8950d --- /dev/null +++ b/drivers/net/wireless/rtlwifi/debug.h @@ -0,0 +1,244 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * Tmis program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * Tmis program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * tmis program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * Tme full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + *****************************************************************************/ + +#ifndef __RTL_DEBUG_H__ +#define __RTL_DEBUG_H__ + +/*-------------------------------------------------------------- + Debug level +--------------------------------------------------------------*/ +/* + *Fatal bug. + *For example, Tx/Rx/IO locked up, + *memory access violation, + *resource allocation failed, + *unexpected HW behavior, HW BUG + *and so on. + */ +#define DBG_EMERG 0 + +/* + *Abnormal, rare, or unexpeted cases. + *For example, Packet/IO Ctl canceled, + *device suprisely unremoved and so on. + */ +#define DBG_WARNING 2 + +/* + *Normal case driver developer should + *open, we can see link status like + *assoc/AddBA/DHCP/adapter start and + *so on basic and useful infromations. + */ +#define DBG_DMESG 3 + +/* + *Normal case with useful information + *about current SW or HW state. + *For example, Tx/Rx descriptor to fill, + *Tx/Rx descriptor completed status, + *SW protocol state change, dynamic + *mechanism state change and so on. + */ +#define DBG_LOUD 4 + +/* + *Normal case with detail execution + *flow or information. + */ +#define DBG_TRACE 5 + +/*-------------------------------------------------------------- + Define the rt_trace components +--------------------------------------------------------------*/ +#define COMP_ERR BIT(0) +#define COMP_FW BIT(1) +#define COMP_INIT BIT(2) /*For init/deinit */ +#define COMP_RECV BIT(3) /*For Rx. */ +#define COMP_SEND BIT(4) /*For Tx. */ +#define COMP_MLME BIT(5) /*For MLME. */ +#define COMP_SCAN BIT(6) /*For Scan. */ +#define COMP_INTR BIT(7) /*For interrupt Related. */ +#define COMP_LED BIT(8) /*For LED. */ +#define COMP_SEC BIT(9) /*For sec. */ +#define COMP_BEACON BIT(10) /*For beacon. */ +#define COMP_RATE BIT(11) /*For rate. */ +#define COMP_RXDESC BIT(12) /*For rx desc. */ +#define COMP_DIG BIT(13) /*For DIG */ +#define COMP_TXAGC BIT(14) /*For Tx power */ +#define COMP_HIPWR BIT(15) /*For High Power Mechanism */ +#define COMP_POWER BIT(16) /*For lps/ips/aspm. */ +#define COMP_POWER_TRACKING BIT(17) /*For TX POWER TRACKING */ +#define COMP_BB_POWERSAVING BIT(18) +#define COMP_SWAS BIT(19) /*For SW Antenna Switch */ +#define COMP_RF BIT(20) /*For RF. */ +#define COMP_TURBO BIT(21) /*For EDCA TURBO. */ +#define COMP_RATR BIT(22) +#define COMP_CMD BIT(23) +#define COMP_EFUSE BIT(24) +#define COMP_QOS BIT(25) +#define COMP_MAC80211 BIT(26) +#define COMP_REGD BIT(27) +#define COMP_CHAN BIT(28) +#define COMP_USB BIT(29) +#define COMP_EASY_CONCURRENT COMP_USB /* reuse of this bit is OK */ +#define COMP_BT_COEXIST BIT(30) + +/*-------------------------------------------------------------- + Define the rt_print components +--------------------------------------------------------------*/ +/* Define EEPROM and EFUSE check module bit*/ +#define EEPROM_W BIT(0) +#define EFUSE_PG BIT(1) +#define EFUSE_READ_ALL BIT(2) + +/* Define init check for module bit*/ +#define INIT_EEPROM BIT(0) +#define INIT_TXPOWER BIT(1) +#define INIT_IQK BIT(2) +#define INIT_RF BIT(3) + +/* Define PHY-BB/RF/MAC check module bit */ +#define PHY_BBR BIT(0) +#define PHY_BBW BIT(1) +#define PHY_RFR BIT(2) +#define PHY_RFW BIT(3) +#define PHY_MACR BIT(4) +#define PHY_MACW BIT(5) +#define PHY_ALLR BIT(6) +#define PHY_ALLW BIT(7) +#define PHY_TXPWR BIT(8) +#define PHY_PWRDIFF BIT(9) + +/* Define Dynamic Mechanism check module bit --> FDM */ +#define WA_IOT BIT(0) +#define DM_PWDB BIT(1) +#define DM_MONITOR BIT(2) +#define DM_DIG BIT(3) +#define DM_EDCA_TURBO BIT(4) + +#define DM_PWDB BIT(1) + +enum dbgp_flag_e { + FQOS = 0, + FTX = 1, + FRX = 2, + FSEC = 3, + FMGNT = 4, + FMLME = 5, + FRESOURCE = 6, + FBEACON = 7, + FISR = 8, + FPHY = 9, + FMP = 10, + FEEPROM = 11, + FPWR = 12, + FDM = 13, + FDBGCtrl = 14, + FC2H = 15, + FBT = 16, + FINIT = 17, + FIOCTL = 18, + DBGP_TYPE_MAX +}; + +#ifdef CPTCFG_RTLWIFI_DEBUG + +#define RT_ASSERT(_exp, fmt, ...) \ +do { \ + if (!(_exp)) { \ + printk(KERN_DEBUG KBUILD_MODNAME ":%s(): " fmt, \ + __func__, ##__VA_ARGS__); \ + } \ +} while (0) + +#define RT_TRACE(rtlpriv, comp, level, fmt, ...) \ +do { \ + if (unlikely(((comp) & rtlpriv->dbg.global_debugcomponents) && \ + ((level) <= rtlpriv->dbg.global_debuglevel))) { \ + printk(KERN_DEBUG KBUILD_MODNAME ":%s():<%lx-%x> " fmt, \ + __func__, in_interrupt(), in_atomic(), \ + ##__VA_ARGS__); \ + } \ +} while (0) + +#define RTPRINT(rtlpriv, dbgtype, dbgflag, fmt, ...) \ +do { \ + if (unlikely(rtlpriv->dbg.dbgp_type[dbgtype] & dbgflag)) { \ + printk(KERN_DEBUG KBUILD_MODNAME ": " fmt, \ + ##__VA_ARGS__); \ + } \ +} while (0) + +#define RT_PRINT_DATA(rtlpriv, _comp, _level, _titlestring, _hexdata, \ + _hexdatalen) \ +do { \ + if (unlikely(((_comp) & rtlpriv->dbg.global_debugcomponents) && \ + (_level <= rtlpriv->dbg.global_debuglevel))) { \ + printk(KERN_DEBUG "%s: In process \"%s\" (pid %i): %s\n", \ + KBUILD_MODNAME, current->comm, current->pid, \ + _titlestring); \ + print_hex_dump_bytes("", DUMP_PREFIX_NONE, \ + _hexdata, _hexdatalen); \ + } \ +} while (0) + +#else + +struct rtl_priv; + +__printf(2, 3) +static inline void RT_ASSERT(int exp, const char *fmt, ...) +{ +} + +__printf(4, 5) +static inline void RT_TRACE(struct rtl_priv *rtlpriv, + int comp, int level, + const char *fmt, ...) +{ +} + +__printf(4, 5) +static inline void RTPRINT(struct rtl_priv *rtlpriv, + int dbgtype, int dbgflag, + const char *fmt, ...) +{ +} + +static inline void RT_PRINT_DATA(struct rtl_priv *rtlpriv, + int comp, int level, + const char *titlestring, + const void *hexdata, size_t hexdatalen) +{ +} + +#endif + +void rtl_dbgp_flag_init(struct ieee80211_hw *hw); +#endif diff --git a/drivers/net/wireless/rtlwifi/efuse.c b/drivers/net/wireless/rtlwifi/efuse.c new file mode 100644 index 0000000..9e38941 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/efuse.c @@ -0,0 +1,1237 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * Tmis program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * Tmis program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * tmis program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * Tme full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include +#include "wifi.h" +#include "efuse.h" + +static const u8 MAX_PGPKT_SIZE = 9; +static const u8 PGPKT_DATA_SIZE = 8; +static const int EFUSE_MAX_SIZE = 512; + +static const struct efuse_map RTL8712_SDIO_EFUSE_TABLE[] = { + {0, 0, 0, 2}, + {0, 1, 0, 2}, + {0, 2, 0, 2}, + {1, 0, 0, 1}, + {1, 0, 1, 1}, + {1, 1, 0, 1}, + {1, 1, 1, 3}, + {1, 3, 0, 17}, + {3, 3, 1, 48}, + {10, 0, 0, 6}, + {10, 3, 0, 1}, + {10, 3, 1, 1}, + {11, 0, 0, 28} +}; + +static void efuse_shadow_read_1byte(struct ieee80211_hw *hw, u16 offset, + u8 *value); +static void efuse_shadow_read_2byte(struct ieee80211_hw *hw, u16 offset, + u16 *value); +static void efuse_shadow_read_4byte(struct ieee80211_hw *hw, u16 offset, + u32 *value); +static void efuse_shadow_write_1byte(struct ieee80211_hw *hw, u16 offset, + u8 value); +static void efuse_shadow_write_2byte(struct ieee80211_hw *hw, u16 offset, + u16 value); +static void efuse_shadow_write_4byte(struct ieee80211_hw *hw, u16 offset, + u32 value); +static int efuse_one_byte_read(struct ieee80211_hw *hw, u16 addr, + u8 *data); +static int efuse_one_byte_write(struct ieee80211_hw *hw, u16 addr, + u8 data); +static void efuse_read_all_map(struct ieee80211_hw *hw, u8 *efuse); +static int efuse_pg_packet_read(struct ieee80211_hw *hw, u8 offset, + u8 *data); +static int efuse_pg_packet_write(struct ieee80211_hw *hw, u8 offset, + u8 word_en, u8 *data); +static void efuse_word_enable_data_read(u8 word_en, u8 *sourdata, + u8 *targetdata); +static u8 efuse_word_enable_data_write(struct ieee80211_hw *hw, + u16 efuse_addr, u8 word_en, u8 *data); +static void efuse_power_switch(struct ieee80211_hw *hw, u8 write, + u8 pwrstate); +static u16 efuse_get_current_size(struct ieee80211_hw *hw); +static u8 efuse_calculate_word_cnts(u8 word_en); + +void efuse_initialize(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 bytetemp; + u8 temp; + + bytetemp = rtl_read_byte(rtlpriv, rtlpriv->cfg->maps[SYS_FUNC_EN] + 1); + temp = bytetemp | 0x20; + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[SYS_FUNC_EN] + 1, temp); + + bytetemp = rtl_read_byte(rtlpriv, rtlpriv->cfg->maps[SYS_ISO_CTRL] + 1); + temp = bytetemp & 0xFE; + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[SYS_ISO_CTRL] + 1, temp); + + bytetemp = rtl_read_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_TEST] + 3); + temp = bytetemp | 0x80; + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_TEST] + 3, temp); + + rtl_write_byte(rtlpriv, 0x2F8, 0x3); + + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL] + 3, 0x72); + +} + +u8 efuse_read_1byte(struct ieee80211_hw *hw, u16 address) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 data; + u8 bytetemp; + u8 temp; + u32 k = 0; + const u32 efuse_len = + rtlpriv->cfg->maps[EFUSE_REAL_CONTENT_SIZE]; + + if (address < efuse_len) { + temp = address & 0xFF; + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL] + 1, + temp); + bytetemp = rtl_read_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_CTRL] + 2); + temp = ((address >> 8) & 0x03) | (bytetemp & 0xFC); + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL] + 2, + temp); + + bytetemp = rtl_read_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_CTRL] + 3); + temp = bytetemp & 0x7F; + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL] + 3, + temp); + + bytetemp = rtl_read_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_CTRL] + 3); + while (!(bytetemp & 0x80)) { + bytetemp = rtl_read_byte(rtlpriv, + rtlpriv->cfg-> + maps[EFUSE_CTRL] + 3); + k++; + if (k == 1000) { + k = 0; + break; + } + } + data = rtl_read_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL]); + return data; + } else + return 0xFF; + +} +EXPORT_SYMBOL(efuse_read_1byte); + +void efuse_write_1byte(struct ieee80211_hw *hw, u16 address, u8 value) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 bytetemp; + u8 temp; + u32 k = 0; + const u32 efuse_len = + rtlpriv->cfg->maps[EFUSE_REAL_CONTENT_SIZE]; + + RT_TRACE(rtlpriv, COMP_EFUSE, DBG_LOUD, "Addr=%x Data =%x\n", + address, value); + + if (address < efuse_len) { + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL], value); + + temp = address & 0xFF; + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL] + 1, + temp); + bytetemp = rtl_read_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_CTRL] + 2); + + temp = ((address >> 8) & 0x03) | (bytetemp & 0xFC); + rtl_write_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_CTRL] + 2, temp); + + bytetemp = rtl_read_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_CTRL] + 3); + temp = bytetemp | 0x80; + rtl_write_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_CTRL] + 3, temp); + + bytetemp = rtl_read_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_CTRL] + 3); + + while (bytetemp & 0x80) { + bytetemp = rtl_read_byte(rtlpriv, + rtlpriv->cfg-> + maps[EFUSE_CTRL] + 3); + k++; + if (k == 100) { + k = 0; + break; + } + } + } + +} + +void read_efuse_byte(struct ieee80211_hw *hw, u16 _offset, u8 *pbuf) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 value32; + u8 readbyte; + u16 retry; + + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL] + 1, + (_offset & 0xff)); + readbyte = rtl_read_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL] + 2); + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL] + 2, + ((_offset >> 8) & 0x03) | (readbyte & 0xfc)); + + readbyte = rtl_read_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL] + 3); + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL] + 3, + (readbyte & 0x7f)); + + retry = 0; + value32 = rtl_read_dword(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL]); + while (!(((value32 >> 24) & 0xff) & 0x80) && (retry < 10000)) { + value32 = rtl_read_dword(rtlpriv, + rtlpriv->cfg->maps[EFUSE_CTRL]); + retry++; + } + + udelay(50); + value32 = rtl_read_dword(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL]); + + *pbuf = (u8) (value32 & 0xff); +} + +void read_efuse(struct ieee80211_hw *hw, u16 _offset, u16 _size_byte, u8 *pbuf) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 *efuse_tbl; + u8 rtemp8[1]; + u16 efuse_addr = 0; + u8 offset, wren; + u8 u1temp = 0; + u16 i; + u16 j; + const u16 efuse_max_section = + rtlpriv->cfg->maps[EFUSE_MAX_SECTION_MAP]; + const u32 efuse_len = + rtlpriv->cfg->maps[EFUSE_REAL_CONTENT_SIZE]; + u16 **efuse_word; + u16 efuse_utilized = 0; + u8 efuse_usage; + + if ((_offset + _size_byte) > rtlpriv->cfg->maps[EFUSE_HWSET_MAX_SIZE]) { + RT_TRACE(rtlpriv, COMP_EFUSE, DBG_LOUD, + "read_efuse(): Invalid offset(%#x) with read bytes(%#x)!!\n", + _offset, _size_byte); + return; + } + + /* allocate memory for efuse_tbl and efuse_word */ + efuse_tbl = kmalloc(rtlpriv->cfg->maps[EFUSE_HWSET_MAX_SIZE] * + sizeof(u8), GFP_ATOMIC); + if (!efuse_tbl) + return; + efuse_word = kmalloc(EFUSE_MAX_WORD_UNIT * sizeof(u16 *), GFP_ATOMIC); + if (!efuse_word) + goto done; + for (i = 0; i < EFUSE_MAX_WORD_UNIT; i++) { + efuse_word[i] = kmalloc(efuse_max_section * sizeof(u16), + GFP_ATOMIC); + if (!efuse_word[i]) + goto done; + } + + for (i = 0; i < efuse_max_section; i++) + for (j = 0; j < EFUSE_MAX_WORD_UNIT; j++) + efuse_word[j][i] = 0xFFFF; + + read_efuse_byte(hw, efuse_addr, rtemp8); + if (*rtemp8 != 0xFF) { + efuse_utilized++; + RTPRINT(rtlpriv, FEEPROM, EFUSE_READ_ALL, + "Addr=%d\n", efuse_addr); + efuse_addr++; + } + + while ((*rtemp8 != 0xFF) && (efuse_addr < efuse_len)) { + /* Check PG header for section num. */ + if ((*rtemp8 & 0x1F) == 0x0F) {/* extended header */ + u1temp = ((*rtemp8 & 0xE0) >> 5); + read_efuse_byte(hw, efuse_addr, rtemp8); + + if ((*rtemp8 & 0x0F) == 0x0F) { + efuse_addr++; + read_efuse_byte(hw, efuse_addr, rtemp8); + + if (*rtemp8 != 0xFF && + (efuse_addr < efuse_len)) { + efuse_addr++; + } + continue; + } else { + offset = ((*rtemp8 & 0xF0) >> 1) | u1temp; + wren = (*rtemp8 & 0x0F); + efuse_addr++; + } + } else { + offset = ((*rtemp8 >> 4) & 0x0f); + wren = (*rtemp8 & 0x0f); + } + + if (offset < efuse_max_section) { + RTPRINT(rtlpriv, FEEPROM, EFUSE_READ_ALL, + "offset-%d Worden=%x\n", offset, wren); + + for (i = 0; i < EFUSE_MAX_WORD_UNIT; i++) { + if (!(wren & 0x01)) { + RTPRINT(rtlpriv, FEEPROM, + EFUSE_READ_ALL, + "Addr=%d\n", efuse_addr); + + read_efuse_byte(hw, efuse_addr, rtemp8); + efuse_addr++; + efuse_utilized++; + efuse_word[i][offset] = + (*rtemp8 & 0xff); + + if (efuse_addr >= efuse_len) + break; + + RTPRINT(rtlpriv, FEEPROM, + EFUSE_READ_ALL, + "Addr=%d\n", efuse_addr); + + read_efuse_byte(hw, efuse_addr, rtemp8); + efuse_addr++; + efuse_utilized++; + efuse_word[i][offset] |= + (((u16)*rtemp8 << 8) & 0xff00); + + if (efuse_addr >= efuse_len) + break; + } + + wren >>= 1; + } + } + + RTPRINT(rtlpriv, FEEPROM, EFUSE_READ_ALL, + "Addr=%d\n", efuse_addr); + read_efuse_byte(hw, efuse_addr, rtemp8); + if (*rtemp8 != 0xFF && (efuse_addr < efuse_len)) { + efuse_utilized++; + efuse_addr++; + } + } + + for (i = 0; i < efuse_max_section; i++) { + for (j = 0; j < EFUSE_MAX_WORD_UNIT; j++) { + efuse_tbl[(i * 8) + (j * 2)] = + (efuse_word[j][i] & 0xff); + efuse_tbl[(i * 8) + ((j * 2) + 1)] = + ((efuse_word[j][i] >> 8) & 0xff); + } + } + + for (i = 0; i < _size_byte; i++) + pbuf[i] = efuse_tbl[_offset + i]; + + rtlefuse->efuse_usedbytes = efuse_utilized; + efuse_usage = (u8) ((efuse_utilized * 100) / efuse_len); + rtlefuse->efuse_usedpercentage = efuse_usage; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_EFUSE_BYTES, + (u8 *)&efuse_utilized); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_EFUSE_USAGE, + &efuse_usage); +done: + for (i = 0; i < EFUSE_MAX_WORD_UNIT; i++) + kfree(efuse_word[i]); + kfree(efuse_word); + kfree(efuse_tbl); +} + +bool efuse_shadow_update_chk(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 section_idx, i, Base; + u16 words_need = 0, hdr_num = 0, totalbytes, efuse_used; + bool wordchanged, result = true; + + for (section_idx = 0; section_idx < 16; section_idx++) { + Base = section_idx * 8; + wordchanged = false; + + for (i = 0; i < 8; i = i + 2) { + if ((rtlefuse->efuse_map[EFUSE_INIT_MAP][Base + i] != + rtlefuse->efuse_map[EFUSE_MODIFY_MAP][Base + i]) || + (rtlefuse->efuse_map[EFUSE_INIT_MAP][Base + i + 1] != + rtlefuse->efuse_map[EFUSE_MODIFY_MAP][Base + i + + 1])) { + words_need++; + wordchanged = true; + } + } + + if (wordchanged) + hdr_num++; + } + + totalbytes = hdr_num + words_need * 2; + efuse_used = rtlefuse->efuse_usedbytes; + + if ((totalbytes + efuse_used) >= + (EFUSE_MAX_SIZE - + rtlpriv->cfg->maps[EFUSE_OOB_PROTECT_BYTES_LEN])) + result = false; + + RT_TRACE(rtlpriv, COMP_EFUSE, DBG_LOUD, + "efuse_shadow_update_chk(): totalbytes(%#x), hdr_num(%#x), words_need(%#x), efuse_used(%d)\n", + totalbytes, hdr_num, words_need, efuse_used); + + return result; +} + +void efuse_shadow_read(struct ieee80211_hw *hw, u8 type, + u16 offset, u32 *value) +{ + if (type == 1) + efuse_shadow_read_1byte(hw, offset, (u8 *) value); + else if (type == 2) + efuse_shadow_read_2byte(hw, offset, (u16 *) value); + else if (type == 4) + efuse_shadow_read_4byte(hw, offset, value); + +} + +void efuse_shadow_write(struct ieee80211_hw *hw, u8 type, u16 offset, + u32 value) +{ + if (type == 1) + efuse_shadow_write_1byte(hw, offset, (u8) value); + else if (type == 2) + efuse_shadow_write_2byte(hw, offset, (u16) value); + else if (type == 4) + efuse_shadow_write_4byte(hw, offset, value); + +} + +bool efuse_shadow_update(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u16 i, offset, base; + u8 word_en = 0x0F; + u8 first_pg = false; + + RT_TRACE(rtlpriv, COMP_EFUSE, DBG_LOUD, "--->\n"); + + if (!efuse_shadow_update_chk(hw)) { + efuse_read_all_map(hw, &rtlefuse->efuse_map[EFUSE_INIT_MAP][0]); + memcpy(&rtlefuse->efuse_map[EFUSE_MODIFY_MAP][0], + &rtlefuse->efuse_map[EFUSE_INIT_MAP][0], + rtlpriv->cfg->maps[EFUSE_HWSET_MAX_SIZE]); + + RT_TRACE(rtlpriv, COMP_EFUSE, DBG_LOUD, + "<---efuse out of capacity!!\n"); + return false; + } + efuse_power_switch(hw, true, true); + + for (offset = 0; offset < 16; offset++) { + + word_en = 0x0F; + base = offset * 8; + + for (i = 0; i < 8; i++) { + if (first_pg) { + + word_en &= ~(BIT(i / 2)); + + rtlefuse->efuse_map[EFUSE_INIT_MAP][base + i] = + rtlefuse->efuse_map[EFUSE_MODIFY_MAP][base + i]; + } else { + + if (rtlefuse->efuse_map[EFUSE_INIT_MAP][base + i] != + rtlefuse->efuse_map[EFUSE_MODIFY_MAP][base + i]) { + word_en &= ~(BIT(i / 2)); + + rtlefuse->efuse_map[EFUSE_INIT_MAP][base + i] = + rtlefuse->efuse_map[EFUSE_MODIFY_MAP][base + i]; + } + } + } + + if (word_en != 0x0F) { + u8 tmpdata[8]; + memcpy(tmpdata, + &rtlefuse->efuse_map[EFUSE_MODIFY_MAP][base], + 8); + RT_PRINT_DATA(rtlpriv, COMP_INIT, DBG_LOUD, + "U-efuse", tmpdata, 8); + + if (!efuse_pg_packet_write(hw, (u8) offset, word_en, + tmpdata)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "PG section(%#x) fail!!\n", offset); + break; + } + } + + } + + efuse_power_switch(hw, true, false); + efuse_read_all_map(hw, &rtlefuse->efuse_map[EFUSE_INIT_MAP][0]); + + memcpy(&rtlefuse->efuse_map[EFUSE_MODIFY_MAP][0], + &rtlefuse->efuse_map[EFUSE_INIT_MAP][0], + rtlpriv->cfg->maps[EFUSE_HWSET_MAX_SIZE]); + + RT_TRACE(rtlpriv, COMP_EFUSE, DBG_LOUD, "<---\n"); + return true; +} + +void rtl_efuse_shadow_map_update(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + + if (rtlefuse->autoload_failflag) + memset(&rtlefuse->efuse_map[EFUSE_INIT_MAP][0], 0xFF, + rtlpriv->cfg->maps[EFUSE_HWSET_MAX_SIZE]); + else + efuse_read_all_map(hw, &rtlefuse->efuse_map[EFUSE_INIT_MAP][0]); + + memcpy(&rtlefuse->efuse_map[EFUSE_MODIFY_MAP][0], + &rtlefuse->efuse_map[EFUSE_INIT_MAP][0], + rtlpriv->cfg->maps[EFUSE_HWSET_MAX_SIZE]); + +} +EXPORT_SYMBOL(rtl_efuse_shadow_map_update); + +void efuse_force_write_vendor_Id(struct ieee80211_hw *hw) +{ + u8 tmpdata[8] = { 0xFF, 0xFF, 0xEC, 0x10, 0xFF, 0xFF, 0xFF, 0xFF }; + + efuse_power_switch(hw, true, true); + + efuse_pg_packet_write(hw, 1, 0xD, tmpdata); + + efuse_power_switch(hw, true, false); + +} + +void efuse_re_pg_section(struct ieee80211_hw *hw, u8 section_idx) +{ +} + +static void efuse_shadow_read_1byte(struct ieee80211_hw *hw, + u16 offset, u8 *value) +{ + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + *value = rtlefuse->efuse_map[EFUSE_MODIFY_MAP][offset]; +} + +static void efuse_shadow_read_2byte(struct ieee80211_hw *hw, + u16 offset, u16 *value) +{ + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + + *value = rtlefuse->efuse_map[EFUSE_MODIFY_MAP][offset]; + *value |= rtlefuse->efuse_map[EFUSE_MODIFY_MAP][offset + 1] << 8; + +} + +static void efuse_shadow_read_4byte(struct ieee80211_hw *hw, + u16 offset, u32 *value) +{ + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + + *value = rtlefuse->efuse_map[EFUSE_MODIFY_MAP][offset]; + *value |= rtlefuse->efuse_map[EFUSE_MODIFY_MAP][offset + 1] << 8; + *value |= rtlefuse->efuse_map[EFUSE_MODIFY_MAP][offset + 2] << 16; + *value |= rtlefuse->efuse_map[EFUSE_MODIFY_MAP][offset + 3] << 24; +} + +static void efuse_shadow_write_1byte(struct ieee80211_hw *hw, + u16 offset, u8 value) +{ + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + + rtlefuse->efuse_map[EFUSE_MODIFY_MAP][offset] = value; +} + +static void efuse_shadow_write_2byte(struct ieee80211_hw *hw, + u16 offset, u16 value) +{ + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + + rtlefuse->efuse_map[EFUSE_MODIFY_MAP][offset] = value & 0x00FF; + rtlefuse->efuse_map[EFUSE_MODIFY_MAP][offset + 1] = value >> 8; + +} + +static void efuse_shadow_write_4byte(struct ieee80211_hw *hw, + u16 offset, u32 value) +{ + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + + rtlefuse->efuse_map[EFUSE_MODIFY_MAP][offset] = + (u8) (value & 0x000000FF); + rtlefuse->efuse_map[EFUSE_MODIFY_MAP][offset + 1] = + (u8) ((value >> 8) & 0x0000FF); + rtlefuse->efuse_map[EFUSE_MODIFY_MAP][offset + 2] = + (u8) ((value >> 16) & 0x00FF); + rtlefuse->efuse_map[EFUSE_MODIFY_MAP][offset + 3] = + (u8) ((value >> 24) & 0xFF); + +} + +static int efuse_one_byte_read(struct ieee80211_hw *hw, u16 addr, u8 *data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tmpidx = 0; + int result; + + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL] + 1, + (u8) (addr & 0xff)); + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL] + 2, + ((u8) ((addr >> 8) & 0x03)) | + (rtl_read_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_CTRL] + 2) & + 0xFC)); + + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL] + 3, 0x72); + + while (!(0x80 & rtl_read_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_CTRL] + 3)) + && (tmpidx < 100)) { + tmpidx++; + } + + if (tmpidx < 100) { + *data = rtl_read_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL]); + result = true; + } else { + *data = 0xff; + result = false; + } + return result; +} + +static int efuse_one_byte_write(struct ieee80211_hw *hw, u16 addr, u8 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tmpidx = 0; + + RT_TRACE(rtlpriv, COMP_EFUSE, DBG_LOUD, "Addr = %x Data=%x\n", + addr, data); + + rtl_write_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_CTRL] + 1, (u8) (addr & 0xff)); + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL] + 2, + (rtl_read_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_CTRL] + + 2) & 0xFC) | (u8) ((addr >> 8) & 0x03)); + + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL], data); + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CTRL] + 3, 0xF2); + + while ((0x80 & rtl_read_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_CTRL] + 3)) + && (tmpidx < 100)) { + tmpidx++; + } + + if (tmpidx < 100) + return true; + + return false; +} + +static void efuse_read_all_map(struct ieee80211_hw *hw, u8 * efuse) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + efuse_power_switch(hw, false, true); + read_efuse(hw, 0, rtlpriv->cfg->maps[EFUSE_HWSET_MAX_SIZE], efuse); + efuse_power_switch(hw, false, false); +} + +static void efuse_read_data_case1(struct ieee80211_hw *hw, u16 *efuse_addr, + u8 efuse_data, u8 offset, u8 *tmpdata, + u8 *readstate) +{ + bool dataempty = true; + u8 hoffset; + u8 tmpidx; + u8 hworden; + u8 word_cnts; + + hoffset = (efuse_data >> 4) & 0x0F; + hworden = efuse_data & 0x0F; + word_cnts = efuse_calculate_word_cnts(hworden); + + if (hoffset == offset) { + for (tmpidx = 0; tmpidx < word_cnts * 2; tmpidx++) { + if (efuse_one_byte_read(hw, *efuse_addr + 1 + tmpidx, + &efuse_data)) { + tmpdata[tmpidx] = efuse_data; + if (efuse_data != 0xff) + dataempty = true; + } + } + + if (dataempty) { + *readstate = PG_STATE_DATA; + } else { + *efuse_addr = *efuse_addr + (word_cnts * 2) + 1; + *readstate = PG_STATE_HEADER; + } + + } else { + *efuse_addr = *efuse_addr + (word_cnts * 2) + 1; + *readstate = PG_STATE_HEADER; + } +} + +static int efuse_pg_packet_read(struct ieee80211_hw *hw, u8 offset, u8 *data) +{ + u8 readstate = PG_STATE_HEADER; + bool continual = true; + u8 efuse_data, word_cnts = 0; + u16 efuse_addr = 0; + u8 tmpdata[8]; + + if (data == NULL) + return false; + if (offset > 15) + return false; + + memset(data, 0xff, PGPKT_DATA_SIZE * sizeof(u8)); + memset(tmpdata, 0xff, PGPKT_DATA_SIZE * sizeof(u8)); + + while (continual && (efuse_addr < EFUSE_MAX_SIZE)) { + if (readstate & PG_STATE_HEADER) { + if (efuse_one_byte_read(hw, efuse_addr, &efuse_data) + && (efuse_data != 0xFF)) + efuse_read_data_case1(hw, &efuse_addr, + efuse_data, + offset, tmpdata, + &readstate); + else + continual = false; + } else if (readstate & PG_STATE_DATA) { + efuse_word_enable_data_read(0, tmpdata, data); + efuse_addr = efuse_addr + (word_cnts * 2) + 1; + readstate = PG_STATE_HEADER; + } + + } + + if ((data[0] == 0xff) && (data[1] == 0xff) && + (data[2] == 0xff) && (data[3] == 0xff) && + (data[4] == 0xff) && (data[5] == 0xff) && + (data[6] == 0xff) && (data[7] == 0xff)) + return false; + else + return true; + +} + +static void efuse_write_data_case1(struct ieee80211_hw *hw, u16 *efuse_addr, + u8 efuse_data, u8 offset, int *continual, + u8 *write_state, struct pgpkt_struct *target_pkt, + int *repeat_times, int *result, u8 word_en) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct pgpkt_struct tmp_pkt; + bool dataempty = true; + u8 originaldata[8 * sizeof(u8)]; + u8 badworden = 0x0F; + u8 match_word_en, tmp_word_en; + u8 tmpindex; + u8 tmp_header = efuse_data; + u8 tmp_word_cnts; + + tmp_pkt.offset = (tmp_header >> 4) & 0x0F; + tmp_pkt.word_en = tmp_header & 0x0F; + tmp_word_cnts = efuse_calculate_word_cnts(tmp_pkt.word_en); + + if (tmp_pkt.offset != target_pkt->offset) { + *efuse_addr = *efuse_addr + (tmp_word_cnts * 2) + 1; + *write_state = PG_STATE_HEADER; + } else { + for (tmpindex = 0; tmpindex < (tmp_word_cnts * 2); tmpindex++) { + u16 address = *efuse_addr + 1 + tmpindex; + if (efuse_one_byte_read(hw, address, + &efuse_data) && (efuse_data != 0xFF)) + dataempty = false; + } + + if (!dataempty) { + *efuse_addr = *efuse_addr + (tmp_word_cnts * 2) + 1; + *write_state = PG_STATE_HEADER; + } else { + match_word_en = 0x0F; + if (!((target_pkt->word_en & BIT(0)) | + (tmp_pkt.word_en & BIT(0)))) + match_word_en &= (~BIT(0)); + + if (!((target_pkt->word_en & BIT(1)) | + (tmp_pkt.word_en & BIT(1)))) + match_word_en &= (~BIT(1)); + + if (!((target_pkt->word_en & BIT(2)) | + (tmp_pkt.word_en & BIT(2)))) + match_word_en &= (~BIT(2)); + + if (!((target_pkt->word_en & BIT(3)) | + (tmp_pkt.word_en & BIT(3)))) + match_word_en &= (~BIT(3)); + + if ((match_word_en & 0x0F) != 0x0F) { + badworden = efuse_word_enable_data_write( + hw, *efuse_addr + 1, + tmp_pkt.word_en, + target_pkt->data); + + if (0x0F != (badworden & 0x0F)) { + u8 reorg_offset = offset; + u8 reorg_worden = badworden; + efuse_pg_packet_write(hw, reorg_offset, + reorg_worden, + originaldata); + } + + tmp_word_en = 0x0F; + if ((target_pkt->word_en & BIT(0)) ^ + (match_word_en & BIT(0))) + tmp_word_en &= (~BIT(0)); + + if ((target_pkt->word_en & BIT(1)) ^ + (match_word_en & BIT(1))) + tmp_word_en &= (~BIT(1)); + + if ((target_pkt->word_en & BIT(2)) ^ + (match_word_en & BIT(2))) + tmp_word_en &= (~BIT(2)); + + if ((target_pkt->word_en & BIT(3)) ^ + (match_word_en & BIT(3))) + tmp_word_en &= (~BIT(3)); + + if ((tmp_word_en & 0x0F) != 0x0F) { + *efuse_addr = efuse_get_current_size(hw); + target_pkt->offset = offset; + target_pkt->word_en = tmp_word_en; + } else { + *continual = false; + } + *write_state = PG_STATE_HEADER; + *repeat_times += 1; + if (*repeat_times > EFUSE_REPEAT_THRESHOLD_) { + *continual = false; + *result = false; + } + } else { + *efuse_addr += (2 * tmp_word_cnts) + 1; + target_pkt->offset = offset; + target_pkt->word_en = word_en; + *write_state = PG_STATE_HEADER; + } + } + } + RTPRINT(rtlpriv, FEEPROM, EFUSE_PG, "efuse PG_STATE_HEADER-1\n"); +} + +static void efuse_write_data_case2(struct ieee80211_hw *hw, u16 *efuse_addr, + int *continual, u8 *write_state, + struct pgpkt_struct target_pkt, + int *repeat_times, int *result) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct pgpkt_struct tmp_pkt; + u8 pg_header; + u8 tmp_header; + u8 originaldata[8 * sizeof(u8)]; + u8 tmp_word_cnts; + u8 badworden = 0x0F; + + pg_header = ((target_pkt.offset << 4) & 0xf0) | target_pkt.word_en; + efuse_one_byte_write(hw, *efuse_addr, pg_header); + efuse_one_byte_read(hw, *efuse_addr, &tmp_header); + + if (tmp_header == pg_header) { + *write_state = PG_STATE_DATA; + } else if (tmp_header == 0xFF) { + *write_state = PG_STATE_HEADER; + *repeat_times += 1; + if (*repeat_times > EFUSE_REPEAT_THRESHOLD_) { + *continual = false; + *result = false; + } + } else { + tmp_pkt.offset = (tmp_header >> 4) & 0x0F; + tmp_pkt.word_en = tmp_header & 0x0F; + + tmp_word_cnts = efuse_calculate_word_cnts(tmp_pkt.word_en); + + memset(originaldata, 0xff, 8 * sizeof(u8)); + + if (efuse_pg_packet_read(hw, tmp_pkt.offset, originaldata)) { + badworden = efuse_word_enable_data_write(hw, + *efuse_addr + 1, tmp_pkt.word_en, + originaldata); + + if (0x0F != (badworden & 0x0F)) { + u8 reorg_offset = tmp_pkt.offset; + u8 reorg_worden = badworden; + efuse_pg_packet_write(hw, reorg_offset, + reorg_worden, + originaldata); + *efuse_addr = efuse_get_current_size(hw); + } else { + *efuse_addr = *efuse_addr + (tmp_word_cnts * 2) + + 1; + } + } else { + *efuse_addr = *efuse_addr + (tmp_word_cnts * 2) + 1; + } + + *write_state = PG_STATE_HEADER; + *repeat_times += 1; + if (*repeat_times > EFUSE_REPEAT_THRESHOLD_) { + *continual = false; + *result = false; + } + + RTPRINT(rtlpriv, FEEPROM, EFUSE_PG, + "efuse PG_STATE_HEADER-2\n"); + } +} + +static int efuse_pg_packet_write(struct ieee80211_hw *hw, + u8 offset, u8 word_en, u8 *data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct pgpkt_struct target_pkt; + u8 write_state = PG_STATE_HEADER; + int continual = true, result = true; + u16 efuse_addr = 0; + u8 efuse_data; + u8 target_word_cnts = 0; + u8 badworden = 0x0F; + static int repeat_times; + + if (efuse_get_current_size(hw) >= (EFUSE_MAX_SIZE - + rtlpriv->cfg->maps[EFUSE_OOB_PROTECT_BYTES_LEN])) { + RTPRINT(rtlpriv, FEEPROM, EFUSE_PG, + "efuse_pg_packet_write error\n"); + return false; + } + + target_pkt.offset = offset; + target_pkt.word_en = word_en; + + memset(target_pkt.data, 0xFF, 8 * sizeof(u8)); + + efuse_word_enable_data_read(word_en, data, target_pkt.data); + target_word_cnts = efuse_calculate_word_cnts(target_pkt.word_en); + + RTPRINT(rtlpriv, FEEPROM, EFUSE_PG, "efuse Power ON\n"); + + while (continual && (efuse_addr < (EFUSE_MAX_SIZE - + rtlpriv->cfg->maps[EFUSE_OOB_PROTECT_BYTES_LEN]))) { + + if (write_state == PG_STATE_HEADER) { + badworden = 0x0F; + RTPRINT(rtlpriv, FEEPROM, EFUSE_PG, + "efuse PG_STATE_HEADER\n"); + + if (efuse_one_byte_read(hw, efuse_addr, &efuse_data) && + (efuse_data != 0xFF)) + efuse_write_data_case1(hw, &efuse_addr, + efuse_data, offset, + &continual, + &write_state, &target_pkt, + &repeat_times, &result, + word_en); + else + efuse_write_data_case2(hw, &efuse_addr, + &continual, + &write_state, + target_pkt, + &repeat_times, + &result); + + } else if (write_state == PG_STATE_DATA) { + RTPRINT(rtlpriv, FEEPROM, EFUSE_PG, + "efuse PG_STATE_DATA\n"); + badworden = + efuse_word_enable_data_write(hw, efuse_addr + 1, + target_pkt.word_en, + target_pkt.data); + + if ((badworden & 0x0F) == 0x0F) { + continual = false; + } else { + efuse_addr += (2 * target_word_cnts) + 1; + + target_pkt.offset = offset; + target_pkt.word_en = badworden; + target_word_cnts = + efuse_calculate_word_cnts(target_pkt. + word_en); + write_state = PG_STATE_HEADER; + repeat_times++; + if (repeat_times > EFUSE_REPEAT_THRESHOLD_) { + continual = false; + result = false; + } + RTPRINT(rtlpriv, FEEPROM, EFUSE_PG, + "efuse PG_STATE_HEADER-3\n"); + } + } + } + + if (efuse_addr >= (EFUSE_MAX_SIZE - + rtlpriv->cfg->maps[EFUSE_OOB_PROTECT_BYTES_LEN])) { + RT_TRACE(rtlpriv, COMP_EFUSE, DBG_LOUD, + "efuse_addr(%#x) Out of size!!\n", efuse_addr); + } + + return true; +} + +static void efuse_word_enable_data_read(u8 word_en, + u8 *sourdata, u8 *targetdata) +{ + if (!(word_en & BIT(0))) { + targetdata[0] = sourdata[0]; + targetdata[1] = sourdata[1]; + } + + if (!(word_en & BIT(1))) { + targetdata[2] = sourdata[2]; + targetdata[3] = sourdata[3]; + } + + if (!(word_en & BIT(2))) { + targetdata[4] = sourdata[4]; + targetdata[5] = sourdata[5]; + } + + if (!(word_en & BIT(3))) { + targetdata[6] = sourdata[6]; + targetdata[7] = sourdata[7]; + } +} + +static u8 efuse_word_enable_data_write(struct ieee80211_hw *hw, + u16 efuse_addr, u8 word_en, u8 *data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u16 tmpaddr; + u16 start_addr = efuse_addr; + u8 badworden = 0x0F; + u8 tmpdata[8]; + + memset(tmpdata, 0xff, PGPKT_DATA_SIZE); + RT_TRACE(rtlpriv, COMP_EFUSE, DBG_LOUD, "word_en = %x efuse_addr=%x\n", + word_en, efuse_addr); + + if (!(word_en & BIT(0))) { + tmpaddr = start_addr; + efuse_one_byte_write(hw, start_addr++, data[0]); + efuse_one_byte_write(hw, start_addr++, data[1]); + + efuse_one_byte_read(hw, tmpaddr, &tmpdata[0]); + efuse_one_byte_read(hw, tmpaddr + 1, &tmpdata[1]); + if ((data[0] != tmpdata[0]) || (data[1] != tmpdata[1])) + badworden &= (~BIT(0)); + } + + if (!(word_en & BIT(1))) { + tmpaddr = start_addr; + efuse_one_byte_write(hw, start_addr++, data[2]); + efuse_one_byte_write(hw, start_addr++, data[3]); + + efuse_one_byte_read(hw, tmpaddr, &tmpdata[2]); + efuse_one_byte_read(hw, tmpaddr + 1, &tmpdata[3]); + if ((data[2] != tmpdata[2]) || (data[3] != tmpdata[3])) + badworden &= (~BIT(1)); + } + + if (!(word_en & BIT(2))) { + tmpaddr = start_addr; + efuse_one_byte_write(hw, start_addr++, data[4]); + efuse_one_byte_write(hw, start_addr++, data[5]); + + efuse_one_byte_read(hw, tmpaddr, &tmpdata[4]); + efuse_one_byte_read(hw, tmpaddr + 1, &tmpdata[5]); + if ((data[4] != tmpdata[4]) || (data[5] != tmpdata[5])) + badworden &= (~BIT(2)); + } + + if (!(word_en & BIT(3))) { + tmpaddr = start_addr; + efuse_one_byte_write(hw, start_addr++, data[6]); + efuse_one_byte_write(hw, start_addr++, data[7]); + + efuse_one_byte_read(hw, tmpaddr, &tmpdata[6]); + efuse_one_byte_read(hw, tmpaddr + 1, &tmpdata[7]); + if ((data[6] != tmpdata[6]) || (data[7] != tmpdata[7])) + badworden &= (~BIT(3)); + } + + return badworden; +} + +static void efuse_power_switch(struct ieee80211_hw *hw, u8 write, u8 pwrstate) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 tempval; + u16 tmpV16; + + if (pwrstate && (rtlhal->hw_type != HARDWARE_TYPE_RTL8192SE)) { + if (rtlhal->hw_type == HARDWARE_TYPE_RTL8188EE) + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_ACCESS], + 0x69); + + tmpV16 = rtl_read_word(rtlpriv, + rtlpriv->cfg->maps[SYS_ISO_CTRL]); + if (!(tmpV16 & rtlpriv->cfg->maps[EFUSE_PWC_EV12V])) { + tmpV16 |= rtlpriv->cfg->maps[EFUSE_PWC_EV12V]; + rtl_write_word(rtlpriv, + rtlpriv->cfg->maps[SYS_ISO_CTRL], + tmpV16); + } + + tmpV16 = rtl_read_word(rtlpriv, + rtlpriv->cfg->maps[SYS_FUNC_EN]); + if (!(tmpV16 & rtlpriv->cfg->maps[EFUSE_FEN_ELDR])) { + tmpV16 |= rtlpriv->cfg->maps[EFUSE_FEN_ELDR]; + rtl_write_word(rtlpriv, + rtlpriv->cfg->maps[SYS_FUNC_EN], tmpV16); + } + + tmpV16 = rtl_read_word(rtlpriv, rtlpriv->cfg->maps[SYS_CLK]); + if ((!(tmpV16 & rtlpriv->cfg->maps[EFUSE_LOADER_CLK_EN])) || + (!(tmpV16 & rtlpriv->cfg->maps[EFUSE_ANA8M]))) { + tmpV16 |= (rtlpriv->cfg->maps[EFUSE_LOADER_CLK_EN] | + rtlpriv->cfg->maps[EFUSE_ANA8M]); + rtl_write_word(rtlpriv, + rtlpriv->cfg->maps[SYS_CLK], tmpV16); + } + } + + if (pwrstate) { + if (write) { + tempval = rtl_read_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_TEST] + + 3); + + if (rtlhal->hw_type != HARDWARE_TYPE_RTL8192SE) { + tempval &= 0x0F; + tempval |= (VOLTAGE_V25 << 4); + } + + rtl_write_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_TEST] + 3, + (tempval | 0x80)); + } + + if (rtlhal->hw_type == HARDWARE_TYPE_RTL8192SE) { + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CLK], + 0x03); + } + + } else { + if (rtlhal->hw_type == HARDWARE_TYPE_RTL8188EE) + rtl_write_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_ACCESS], 0); + + if (write) { + tempval = rtl_read_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_TEST] + + 3); + rtl_write_byte(rtlpriv, + rtlpriv->cfg->maps[EFUSE_TEST] + 3, + (tempval & 0x7F)); + } + + if (rtlhal->hw_type == HARDWARE_TYPE_RTL8192SE) { + rtl_write_byte(rtlpriv, rtlpriv->cfg->maps[EFUSE_CLK], + 0x02); + } + + } + +} + +static u16 efuse_get_current_size(struct ieee80211_hw *hw) +{ + int continual = true; + u16 efuse_addr = 0; + u8 hworden; + u8 efuse_data, word_cnts; + + while (continual && efuse_one_byte_read(hw, efuse_addr, &efuse_data) + && (efuse_addr < EFUSE_MAX_SIZE)) { + if (efuse_data != 0xFF) { + hworden = efuse_data & 0x0F; + word_cnts = efuse_calculate_word_cnts(hworden); + efuse_addr = efuse_addr + (word_cnts * 2) + 1; + } else { + continual = false; + } + } + + return efuse_addr; +} + +static u8 efuse_calculate_word_cnts(u8 word_en) +{ + u8 word_cnts = 0; + if (!(word_en & BIT(0))) + word_cnts++; + if (!(word_en & BIT(1))) + word_cnts++; + if (!(word_en & BIT(2))) + word_cnts++; + if (!(word_en & BIT(3))) + word_cnts++; + return word_cnts; +} + diff --git a/drivers/net/wireless/rtlwifi/efuse.h b/drivers/net/wireless/rtlwifi/efuse.h new file mode 100644 index 0000000..395a326 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/efuse.h @@ -0,0 +1,123 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL_EFUSE_H_ +#define __RTL_EFUSE_H_ + +#define EFUSE_IC_ID_OFFSET 506 + +#define EFUSE_MAP_LEN 128 +#define EFUSE_MAX_WORD_UNIT 4 + +#define EFUSE_INIT_MAP 0 +#define EFUSE_MODIFY_MAP 1 + +#define PG_STATE_HEADER 0x01 +#define PG_STATE_WORD_0 0x02 +#define PG_STATE_WORD_1 0x04 +#define PG_STATE_WORD_2 0x08 +#define PG_STATE_WORD_3 0x10 +#define PG_STATE_DATA 0x20 + +#define PG_SWBYTE_H 0x01 +#define PG_SWBYTE_L 0x02 + +#define _POWERON_DELAY_ +#define _PRE_EXECUTE_READ_CMD_ + +#define EFUSE_REPEAT_THRESHOLD_ 3 +#define EFUSE_ERROE_HANDLE 1 + +struct efuse_map { + u8 offset; + u8 word_start; + u8 byte_start; + u8 byte_cnts; +}; + +struct pgpkt_struct { + u8 offset; + u8 word_en; + u8 data[8]; +}; + +enum efuse_data_item { + EFUSE_CHIP_ID = 0, + EFUSE_LDO_SETTING, + EFUSE_CLK_SETTING, + EFUSE_SDIO_SETTING, + EFUSE_CCCR, + EFUSE_SDIO_MODE, + EFUSE_OCR, + EFUSE_F0CIS, + EFUSE_F1CIS, + EFUSE_MAC_ADDR, + EFUSE_EEPROM_VER, + EFUSE_CHAN_PLAN, + EFUSE_TXPW_TAB +}; + +enum { + VOLTAGE_V25 = 0x03, + LDOE25_SHIFT = 28, +}; + +struct efuse_priv { + u8 id[2]; + u8 ldo_setting[2]; + u8 clk_setting[2]; + u8 cccr; + u8 sdio_mode; + u8 ocr[3]; + u8 cis0[17]; + u8 cis1[48]; + u8 mac_addr[6]; + u8 eeprom_verno; + u8 channel_plan; + u8 tx_power_b[14]; + u8 tx_power_g[14]; +}; + +extern void read_efuse_byte(struct ieee80211_hw *hw, u16 _offset, u8 *pbuf); +extern void efuse_initialize(struct ieee80211_hw *hw); +extern u8 efuse_read_1byte(struct ieee80211_hw *hw, u16 address); +extern void efuse_write_1byte(struct ieee80211_hw *hw, u16 address, u8 value); +extern void read_efuse(struct ieee80211_hw *hw, u16 _offset, + u16 _size_byte, u8 *pbuf); +extern void efuse_shadow_read(struct ieee80211_hw *hw, u8 type, + u16 offset, u32 *value); +extern void efuse_shadow_write(struct ieee80211_hw *hw, u8 type, + u16 offset, u32 value); +extern bool efuse_shadow_update(struct ieee80211_hw *hw); +extern bool efuse_shadow_update_chk(struct ieee80211_hw *hw); +extern void rtl_efuse_shadow_map_update(struct ieee80211_hw *hw); +extern void efuse_force_write_vendor_Id(struct ieee80211_hw *hw); +extern void efuse_re_pg_section(struct ieee80211_hw *hw, u8 section_idx); + +#endif diff --git a/drivers/net/wireless/rtlwifi/pci.c b/drivers/net/wireless/rtlwifi/pci.c new file mode 100644 index 0000000..e70b4ff --- /dev/null +++ b/drivers/net/wireless/rtlwifi/pci.c @@ -0,0 +1,2120 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "wifi.h" +#include "core.h" +#include "pci.h" +#include "base.h" +#include "ps.h" +#include "efuse.h" +#include +#include + +static const u16 pcibridge_vendors[PCI_BRIDGE_VENDOR_MAX] = { + PCI_VENDOR_ID_INTEL, + PCI_VENDOR_ID_ATI, + PCI_VENDOR_ID_AMD, + PCI_VENDOR_ID_SI +}; + +static const u8 ac_to_hwq[] = { + VO_QUEUE, + VI_QUEUE, + BE_QUEUE, + BK_QUEUE +}; + +static u8 _rtl_mac_to_hwqueue(struct ieee80211_hw *hw, + struct sk_buff *skb) +{ + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + __le16 fc = rtl_get_fc(skb); + u8 queue_index = skb_get_queue_mapping(skb); + + if (unlikely(ieee80211_is_beacon(fc))) + return BEACON_QUEUE; + if (ieee80211_is_mgmt(fc) || ieee80211_is_ctl(fc)) + return MGNT_QUEUE; + if (rtlhal->hw_type == HARDWARE_TYPE_RTL8192SE) + if (ieee80211_is_nullfunc(fc)) + return HIGH_QUEUE; + + return ac_to_hwq[queue_index]; +} + +/* Update PCI dependent default settings*/ +static void _rtl_pci_update_default_setting(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u8 pcibridge_vendor = pcipriv->ndis_adapter.pcibridge_vendor; + u8 init_aspm; + + ppsc->reg_rfps_level = 0; + ppsc->support_aspm = false; + + /*Update PCI ASPM setting */ + ppsc->const_amdpci_aspm = rtlpci->const_amdpci_aspm; + switch (rtlpci->const_pci_aspm) { + case 0: + /*No ASPM */ + break; + + case 1: + /*ASPM dynamically enabled/disable. */ + ppsc->reg_rfps_level |= RT_RF_LPS_LEVEL_ASPM; + break; + + case 2: + /*ASPM with Clock Req dynamically enabled/disable. */ + ppsc->reg_rfps_level |= (RT_RF_LPS_LEVEL_ASPM | + RT_RF_OFF_LEVL_CLK_REQ); + break; + + case 3: + /* + * Always enable ASPM and Clock Req + * from initialization to halt. + * */ + ppsc->reg_rfps_level &= ~(RT_RF_LPS_LEVEL_ASPM); + ppsc->reg_rfps_level |= (RT_RF_PS_LEVEL_ALWAYS_ASPM | + RT_RF_OFF_LEVL_CLK_REQ); + break; + + case 4: + /* + * Always enable ASPM without Clock Req + * from initialization to halt. + * */ + ppsc->reg_rfps_level &= ~(RT_RF_LPS_LEVEL_ASPM | + RT_RF_OFF_LEVL_CLK_REQ); + ppsc->reg_rfps_level |= RT_RF_PS_LEVEL_ALWAYS_ASPM; + break; + } + + ppsc->reg_rfps_level |= RT_RF_OFF_LEVL_HALT_NIC; + + /*Update Radio OFF setting */ + switch (rtlpci->const_hwsw_rfoff_d3) { + case 1: + if (ppsc->reg_rfps_level & RT_RF_LPS_LEVEL_ASPM) + ppsc->reg_rfps_level |= RT_RF_OFF_LEVL_ASPM; + break; + + case 2: + if (ppsc->reg_rfps_level & RT_RF_LPS_LEVEL_ASPM) + ppsc->reg_rfps_level |= RT_RF_OFF_LEVL_ASPM; + ppsc->reg_rfps_level |= RT_RF_OFF_LEVL_HALT_NIC; + break; + + case 3: + ppsc->reg_rfps_level |= RT_RF_OFF_LEVL_PCI_D3; + break; + } + + /*Set HW definition to determine if it supports ASPM. */ + switch (rtlpci->const_support_pciaspm) { + case 0:{ + /*Not support ASPM. */ + bool support_aspm = false; + ppsc->support_aspm = support_aspm; + break; + } + case 1:{ + /*Support ASPM. */ + bool support_aspm = true; + bool support_backdoor = true; + ppsc->support_aspm = support_aspm; + + /*if (priv->oem_id == RT_CID_TOSHIBA && + !priv->ndis_adapter.amd_l1_patch) + support_backdoor = false; */ + + ppsc->support_backdoor = support_backdoor; + + break; + } + case 2: + /*ASPM value set by chipset. */ + if (pcibridge_vendor == PCI_BRIDGE_VENDOR_INTEL) { + bool support_aspm = true; + ppsc->support_aspm = support_aspm; + } + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + + /* toshiba aspm issue, toshiba will set aspm selfly + * so we should not set aspm in driver */ + pci_read_config_byte(rtlpci->pdev, 0x80, &init_aspm); + if (rtlpriv->rtlhal.hw_type == HARDWARE_TYPE_RTL8192SE && + init_aspm == 0x43) + ppsc->support_aspm = false; +} + +static bool _rtl_pci_platform_switch_device_pci_aspm( + struct ieee80211_hw *hw, + u8 value) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (rtlhal->hw_type != HARDWARE_TYPE_RTL8192SE) + value |= 0x40; + + pci_write_config_byte(rtlpci->pdev, 0x80, value); + + return false; +} + +/*When we set 0x01 to enable clk request. Set 0x0 to disable clk req.*/ +static void _rtl_pci_switch_clk_req(struct ieee80211_hw *hw, u8 value) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + pci_write_config_byte(rtlpci->pdev, 0x81, value); + + if (rtlhal->hw_type == HARDWARE_TYPE_RTL8192SE) + udelay(100); +} + +/*Disable RTL8192SE ASPM & Disable Pci Bridge ASPM*/ +static void rtl_pci_disable_aspm(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u8 pcibridge_vendor = pcipriv->ndis_adapter.pcibridge_vendor; + u8 num4bytes = pcipriv->ndis_adapter.num4bytes; + /*Retrieve original configuration settings. */ + u8 linkctrl_reg = pcipriv->ndis_adapter.linkctrl_reg; + u16 pcibridge_linkctrlreg = pcipriv->ndis_adapter. + pcibridge_linkctrlreg; + u16 aspmlevel = 0; + u8 tmp_u1b = 0; + + if (!ppsc->support_aspm) + return; + + if (pcibridge_vendor == PCI_BRIDGE_VENDOR_UNKNOWN) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_TRACE, + "PCI(Bridge) UNKNOWN\n"); + + return; + } + + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_CLK_REQ) { + RT_CLEAR_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_CLK_REQ); + _rtl_pci_switch_clk_req(hw, 0x0); + } + + /*for promising device will in L0 state after an I/O. */ + pci_read_config_byte(rtlpci->pdev, 0x80, &tmp_u1b); + + /*Set corresponding value. */ + aspmlevel |= BIT(0) | BIT(1); + linkctrl_reg &= ~aspmlevel; + pcibridge_linkctrlreg &= ~(BIT(0) | BIT(1)); + + _rtl_pci_platform_switch_device_pci_aspm(hw, linkctrl_reg); + udelay(50); + + /*4 Disable Pci Bridge ASPM */ + pci_write_config_byte(rtlpci->pdev, (num4bytes << 2), + pcibridge_linkctrlreg); + + udelay(50); +} + +/* + *Enable RTL8192SE ASPM & Enable Pci Bridge ASPM for + *power saving We should follow the sequence to enable + *RTL8192SE first then enable Pci Bridge ASPM + *or the system will show bluescreen. + */ +static void rtl_pci_enable_aspm(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u8 pcibridge_vendor = pcipriv->ndis_adapter.pcibridge_vendor; + u8 num4bytes = pcipriv->ndis_adapter.num4bytes; + u16 aspmlevel; + u8 u_pcibridge_aspmsetting; + u8 u_device_aspmsetting; + + if (!ppsc->support_aspm) + return; + + if (pcibridge_vendor == PCI_BRIDGE_VENDOR_UNKNOWN) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_TRACE, + "PCI(Bridge) UNKNOWN\n"); + return; + } + + /*4 Enable Pci Bridge ASPM */ + + u_pcibridge_aspmsetting = + pcipriv->ndis_adapter.pcibridge_linkctrlreg | + rtlpci->const_hostpci_aspm_setting; + + if (pcibridge_vendor == PCI_BRIDGE_VENDOR_INTEL) + u_pcibridge_aspmsetting &= ~BIT(0); + + pci_write_config_byte(rtlpci->pdev, (num4bytes << 2), + u_pcibridge_aspmsetting); + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "PlatformEnableASPM(): Write reg[%x] = %x\n", + (pcipriv->ndis_adapter.pcibridge_pciehdr_offset + 0x10), + u_pcibridge_aspmsetting); + + udelay(50); + + /*Get ASPM level (with/without Clock Req) */ + aspmlevel = rtlpci->const_devicepci_aspm_setting; + u_device_aspmsetting = pcipriv->ndis_adapter.linkctrl_reg; + + /*_rtl_pci_platform_switch_device_pci_aspm(dev,*/ + /*(priv->ndis_adapter.linkctrl_reg | ASPMLevel)); */ + + u_device_aspmsetting |= aspmlevel; + + _rtl_pci_platform_switch_device_pci_aspm(hw, u_device_aspmsetting); + + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_CLK_REQ) { + _rtl_pci_switch_clk_req(hw, (ppsc->reg_rfps_level & + RT_RF_OFF_LEVL_CLK_REQ) ? 1 : 0); + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_CLK_REQ); + } + udelay(100); +} + +static bool rtl_pci_get_amd_l1_patch(struct ieee80211_hw *hw) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + bool status = false; + u8 offset_e0; + unsigned offset_e4; + + pci_write_config_byte(rtlpci->pdev, 0xe0, 0xa0); + + pci_read_config_byte(rtlpci->pdev, 0xe0, &offset_e0); + + if (offset_e0 == 0xA0) { + pci_read_config_dword(rtlpci->pdev, 0xe4, &offset_e4); + if (offset_e4 & BIT(23)) + status = true; + } + + return status; +} + +static bool rtl_pci_check_buddy_priv(struct ieee80211_hw *hw, + struct rtl_priv **buddy_priv) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + bool find_buddy_priv = false; + struct rtl_priv *tpriv = NULL; + struct rtl_pci_priv *tpcipriv = NULL; + + if (!list_empty(&rtlpriv->glb_var->glb_priv_list)) { + list_for_each_entry(tpriv, &rtlpriv->glb_var->glb_priv_list, + list) { + if (tpriv) { + tpcipriv = (struct rtl_pci_priv *)tpriv->priv; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "pcipriv->ndis_adapter.funcnumber %x\n", + pcipriv->ndis_adapter.funcnumber); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "tpcipriv->ndis_adapter.funcnumber %x\n", + tpcipriv->ndis_adapter.funcnumber); + + if ((pcipriv->ndis_adapter.busnumber == + tpcipriv->ndis_adapter.busnumber) && + (pcipriv->ndis_adapter.devnumber == + tpcipriv->ndis_adapter.devnumber) && + (pcipriv->ndis_adapter.funcnumber != + tpcipriv->ndis_adapter.funcnumber)) { + find_buddy_priv = true; + break; + } + } + } + } + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "find_buddy_priv %d\n", find_buddy_priv); + + if (find_buddy_priv) + *buddy_priv = tpriv; + + return find_buddy_priv; +} + +static void rtl_pci_get_linkcontrol_field(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(pcipriv); + u8 capabilityoffset = pcipriv->ndis_adapter.pcibridge_pciehdr_offset; + u8 linkctrl_reg; + u8 num4bbytes; + + num4bbytes = (capabilityoffset + 0x10) / 4; + + /*Read Link Control Register */ + pci_read_config_byte(rtlpci->pdev, (num4bbytes << 2), &linkctrl_reg); + + pcipriv->ndis_adapter.pcibridge_linkctrlreg = linkctrl_reg; +} + +static void rtl_pci_parse_configuration(struct pci_dev *pdev, + struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + + u8 tmp; + u16 linkctrl_reg; + + /*Link Control Register */ + pcie_capability_read_word(pdev, PCI_EXP_LNKCTL, &linkctrl_reg); + pcipriv->ndis_adapter.linkctrl_reg = (u8)linkctrl_reg; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "Link Control Register =%x\n", + pcipriv->ndis_adapter.linkctrl_reg); + + pci_read_config_byte(pdev, 0x98, &tmp); + tmp |= BIT(4); + pci_write_config_byte(pdev, 0x98, tmp); + + tmp = 0x17; + pci_write_config_byte(pdev, 0x70f, tmp); +} + +static void rtl_pci_init_aspm(struct ieee80211_hw *hw) +{ + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + _rtl_pci_update_default_setting(hw); + + if (ppsc->reg_rfps_level & RT_RF_PS_LEVEL_ALWAYS_ASPM) { + /*Always enable ASPM & Clock Req. */ + rtl_pci_enable_aspm(hw); + RT_SET_PS_LEVEL(ppsc, RT_RF_PS_LEVEL_ALWAYS_ASPM); + } + +} + +static void _rtl_pci_io_handler_init(struct device *dev, + struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->io.dev = dev; + + rtlpriv->io.write8_async = pci_write8_async; + rtlpriv->io.write16_async = pci_write16_async; + rtlpriv->io.write32_async = pci_write32_async; + + rtlpriv->io.read8_sync = pci_read8_sync; + rtlpriv->io.read16_sync = pci_read16_sync; + rtlpriv->io.read32_sync = pci_read32_sync; + +} + +static bool _rtl_update_earlymode_info(struct ieee80211_hw *hw, + struct sk_buff *skb, struct rtl_tcb_desc *tcb_desc, u8 tid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct sk_buff *next_skb; + u8 additionlen = FCS_LEN; + + /* here open is 4, wep/tkip is 8, aes is 12*/ + if (info->control.hw_key) + additionlen += info->control.hw_key->icv_len; + + /* The most skb num is 6 */ + tcb_desc->empkt_num = 0; + spin_lock_bh(&rtlpriv->locks.waitq_lock); + skb_queue_walk(&rtlpriv->mac80211.skb_waitq[tid], next_skb) { + struct ieee80211_tx_info *next_info; + + next_info = IEEE80211_SKB_CB(next_skb); + if (next_info->flags & IEEE80211_TX_CTL_AMPDU) { + tcb_desc->empkt_len[tcb_desc->empkt_num] = + next_skb->len + additionlen; + tcb_desc->empkt_num++; + } else { + break; + } + + if (skb_queue_is_last(&rtlpriv->mac80211.skb_waitq[tid], + next_skb)) + break; + + if (tcb_desc->empkt_num >= rtlhal->max_earlymode_num) + break; + } + spin_unlock_bh(&rtlpriv->locks.waitq_lock); + + return true; +} + +/* just for early mode now */ +static void _rtl_pci_tx_chk_waitq(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct sk_buff *skb = NULL; + struct ieee80211_tx_info *info = NULL; + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + int tid; + + if (!rtlpriv->rtlhal.earlymode_enable) + return; + + if (rtlpriv->dm.supp_phymode_switch && + (rtlpriv->easy_concurrent_ctl.switch_in_process || + (rtlpriv->buddy_priv && + rtlpriv->buddy_priv->easy_concurrent_ctl.switch_in_process))) + return; + /* we juse use em for BE/BK/VI/VO */ + for (tid = 7; tid >= 0; tid--) { + u8 hw_queue = ac_to_hwq[rtl_tid_to_ac(tid)]; + struct rtl8192_tx_ring *ring = &rtlpci->tx_ring[hw_queue]; + while (!mac->act_scanning && + rtlpriv->psc.rfpwr_state == ERFON) { + struct rtl_tcb_desc tcb_desc; + memset(&tcb_desc, 0, sizeof(struct rtl_tcb_desc)); + + spin_lock_bh(&rtlpriv->locks.waitq_lock); + if (!skb_queue_empty(&mac->skb_waitq[tid]) && + (ring->entries - skb_queue_len(&ring->queue) > + rtlhal->max_earlymode_num)) { + skb = skb_dequeue(&mac->skb_waitq[tid]); + } else { + spin_unlock_bh(&rtlpriv->locks.waitq_lock); + break; + } + spin_unlock_bh(&rtlpriv->locks.waitq_lock); + + /* Some macaddr can't do early mode. like + * multicast/broadcast/no_qos data */ + info = IEEE80211_SKB_CB(skb); + if (info->flags & IEEE80211_TX_CTL_AMPDU) + _rtl_update_earlymode_info(hw, skb, + &tcb_desc, tid); + + rtlpriv->intf_ops->adapter_tx(hw, NULL, skb, &tcb_desc); + } + } +} + + +static void _rtl_pci_tx_isr(struct ieee80211_hw *hw, int prio) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + struct rtl8192_tx_ring *ring = &rtlpci->tx_ring[prio]; + + while (skb_queue_len(&ring->queue)) { + struct rtl_tx_desc *entry = &ring->desc[ring->idx]; + struct sk_buff *skb; + struct ieee80211_tx_info *info; + __le16 fc; + u8 tid; + + u8 own = (u8) rtlpriv->cfg->ops->get_desc((u8 *) entry, true, + HW_DESC_OWN); + + /*beacon packet will only use the first + *descriptor by defaut, and the own may not + *be cleared by the hardware + */ + if (own) + return; + ring->idx = (ring->idx + 1) % ring->entries; + + skb = __skb_dequeue(&ring->queue); + pci_unmap_single(rtlpci->pdev, + rtlpriv->cfg->ops-> + get_desc((u8 *) entry, true, + HW_DESC_TXBUFF_ADDR), + skb->len, PCI_DMA_TODEVICE); + + /* remove early mode header */ + if (rtlpriv->rtlhal.earlymode_enable) + skb_pull(skb, EM_HDR_LEN); + + RT_TRACE(rtlpriv, (COMP_INTR | COMP_SEND), DBG_TRACE, + "new ring->idx:%d, free: skb_queue_len:%d, free: seq:%x\n", + ring->idx, + skb_queue_len(&ring->queue), + *(u16 *) (skb->data + 22)); + + if (prio == TXCMD_QUEUE) { + dev_kfree_skb(skb); + goto tx_status_ok; + + } + + /* for sw LPS, just after NULL skb send out, we can + * sure AP knows we are sleeping, we should not let + * rf sleep + */ + fc = rtl_get_fc(skb); + if (ieee80211_is_nullfunc(fc)) { + if (ieee80211_has_pm(fc)) { + rtlpriv->mac80211.offchan_delay = true; + rtlpriv->psc.state_inap = true; + } else { + rtlpriv->psc.state_inap = false; + } + } + if (ieee80211_is_action(fc)) { + struct ieee80211_mgmt *action_frame = + (struct ieee80211_mgmt *)skb->data; + if (action_frame->u.action.u.ht_smps.action == + WLAN_HT_ACTION_SMPS) { + dev_kfree_skb(skb); + goto tx_status_ok; + } + } + + /* update tid tx pkt num */ + tid = rtl_get_tid(skb); + if (tid <= 7) + rtlpriv->link_info.tidtx_inperiod[tid]++; + + info = IEEE80211_SKB_CB(skb); + ieee80211_tx_info_clear_status(info); + + info->flags |= IEEE80211_TX_STAT_ACK; + /*info->status.rates[0].count = 1; */ + + ieee80211_tx_status_irqsafe(hw, skb); + + if ((ring->entries - skb_queue_len(&ring->queue)) + == 2) { + + RT_TRACE(rtlpriv, COMP_ERR, DBG_LOUD, + "more desc left, wake skb_queue@%d, ring->idx = %d, skb_queue_len = 0x%d\n", + prio, ring->idx, + skb_queue_len(&ring->queue)); + + ieee80211_wake_queue(hw, + skb_get_queue_mapping + (skb)); + } +tx_status_ok: + skb = NULL; + } + + if (((rtlpriv->link_info.num_rx_inperiod + + rtlpriv->link_info.num_tx_inperiod) > 8) || + (rtlpriv->link_info.num_rx_inperiod > 2)) { + rtlpriv->enter_ps = false; + schedule_work(&rtlpriv->works.lps_change_work); + } +} + +static void _rtl_receive_one(struct ieee80211_hw *hw, struct sk_buff *skb, + struct ieee80211_rx_status rx_status) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct ieee80211_hdr *hdr = rtl_get_hdr(skb); + __le16 fc = rtl_get_fc(skb); + bool unicast = false; + struct sk_buff *uskb = NULL; + u8 *pdata; + + + memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); + + if (is_broadcast_ether_addr(hdr->addr1)) { + ;/*TODO*/ + } else if (is_multicast_ether_addr(hdr->addr1)) { + ;/*TODO*/ + } else { + unicast = true; + rtlpriv->stats.rxbytesunicast += skb->len; + } + + rtl_is_special_data(hw, skb, false); + + if (ieee80211_is_data(fc)) { + rtlpriv->cfg->ops->led_control(hw, LED_CTL_RX); + + if (unicast) + rtlpriv->link_info.num_rx_inperiod++; + } + + /* static bcn for roaming */ + rtl_beacon_statistic(hw, skb); + rtl_p2p_info(hw, (void *)skb->data, skb->len); + + /* for sw lps */ + rtl_swlps_beacon(hw, (void *)skb->data, skb->len); + rtl_recognize_peer(hw, (void *)skb->data, skb->len); + if ((rtlpriv->mac80211.opmode == NL80211_IFTYPE_AP) && + (rtlpriv->rtlhal.current_bandtype == BAND_ON_2_4G) && + (ieee80211_is_beacon(fc) || ieee80211_is_probe_resp(fc))) + return; + + if (unlikely(!rtl_action_proc(hw, skb, false))) + return; + + uskb = dev_alloc_skb(skb->len + 128); + if (!uskb) + return; /* exit if allocation failed */ + memcpy(IEEE80211_SKB_RXCB(uskb), &rx_status, sizeof(rx_status)); + pdata = (u8 *)skb_put(uskb, skb->len); + memcpy(pdata, skb->data, skb->len); + + ieee80211_rx_irqsafe(hw, uskb); +} + +static void _rtl_pci_rx_interrupt(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + int rx_queue_idx = RTL_PCI_RX_MPDU_QUEUE; + + struct ieee80211_rx_status rx_status = { 0 }; + unsigned int count = rtlpci->rxringcount; + u8 own; + u8 tmp_one; + u32 bufferaddress; + + struct rtl_stats stats = { + .signal = 0, + .noise = -98, + .rate = 0, + }; + int index = rtlpci->rx_ring[rx_queue_idx].idx; + + /*RX NORMAL PKT */ + while (count--) { + /*rx descriptor */ + struct rtl_rx_desc *pdesc = &rtlpci->rx_ring[rx_queue_idx].desc[ + index]; + /*rx pkt */ + struct sk_buff *skb = rtlpci->rx_ring[rx_queue_idx].rx_buf[ + index]; + struct sk_buff *new_skb = NULL; + + own = (u8) rtlpriv->cfg->ops->get_desc((u8 *) pdesc, + false, HW_DESC_OWN); + + /*wait data to be filled by hardware */ + if (own) + break; + + rtlpriv->cfg->ops->query_rx_desc(hw, &stats, + &rx_status, + (u8 *) pdesc, skb); + + if (stats.crc || stats.hwerror) + goto done; + + new_skb = dev_alloc_skb(rtlpci->rxbuffersize); + if (unlikely(!new_skb)) { + RT_TRACE(rtlpriv, (COMP_INTR | COMP_RECV), DBG_DMESG, + "can't alloc skb for rx\n"); + goto done; + } + kmemleak_not_leak(new_skb); + + pci_unmap_single(rtlpci->pdev, + *((dma_addr_t *) skb->cb), + rtlpci->rxbuffersize, + PCI_DMA_FROMDEVICE); + + skb_put(skb, rtlpriv->cfg->ops->get_desc((u8 *) pdesc, false, + HW_DESC_RXPKT_LEN)); + skb_reserve(skb, stats.rx_drvinfo_size + stats.rx_bufshift); + + /* + * NOTICE This can not be use for mac80211, + * this is done in mac80211 code, + * if you done here sec DHCP will fail + * skb_trim(skb, skb->len - 4); + */ + + _rtl_receive_one(hw, skb, rx_status); + + if (((rtlpriv->link_info.num_rx_inperiod + + rtlpriv->link_info.num_tx_inperiod) > 8) || + (rtlpriv->link_info.num_rx_inperiod > 2)) { + rtlpriv->enter_ps = false; + schedule_work(&rtlpriv->works.lps_change_work); + } + + dev_kfree_skb_any(skb); + skb = new_skb; + + rtlpci->rx_ring[rx_queue_idx].rx_buf[index] = skb; + *((dma_addr_t *) skb->cb) = + pci_map_single(rtlpci->pdev, skb_tail_pointer(skb), + rtlpci->rxbuffersize, + PCI_DMA_FROMDEVICE); + +done: + bufferaddress = (*((dma_addr_t *)skb->cb)); + if (pci_dma_mapping_error(rtlpci->pdev, bufferaddress)) + return; + tmp_one = 1; + rtlpriv->cfg->ops->set_desc((u8 *) pdesc, false, + HW_DESC_RXBUFF_ADDR, + (u8 *)&bufferaddress); + rtlpriv->cfg->ops->set_desc((u8 *)pdesc, false, + HW_DESC_RXPKT_LEN, + (u8 *)&rtlpci->rxbuffersize); + + if (index == rtlpci->rxringcount - 1) + rtlpriv->cfg->ops->set_desc((u8 *)pdesc, false, + HW_DESC_RXERO, + &tmp_one); + + rtlpriv->cfg->ops->set_desc((u8 *)pdesc, false, HW_DESC_RXOWN, + &tmp_one); + + index = (index + 1) % rtlpci->rxringcount; + } + + rtlpci->rx_ring[rx_queue_idx].idx = index; +} + +static irqreturn_t _rtl_pci_interrupt(int irq, void *dev_id) +{ + struct ieee80211_hw *hw = dev_id; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + unsigned long flags; + u32 inta = 0; + u32 intb = 0; + irqreturn_t ret = IRQ_HANDLED; + + spin_lock_irqsave(&rtlpriv->locks.irq_th_lock, flags); + + /*read ISR: 4/8bytes */ + rtlpriv->cfg->ops->interrupt_recognized(hw, &inta, &intb); + + /*Shared IRQ or HW disappared */ + if (!inta || inta == 0xffff) { + ret = IRQ_NONE; + goto done; + } + + /*<1> beacon related */ + if (inta & rtlpriv->cfg->maps[RTL_IMR_TBDOK]) { + RT_TRACE(rtlpriv, COMP_INTR, DBG_TRACE, + "beacon ok interrupt!\n"); + } + + if (unlikely(inta & rtlpriv->cfg->maps[RTL_IMR_TBDER])) { + RT_TRACE(rtlpriv, COMP_INTR, DBG_TRACE, + "beacon err interrupt!\n"); + } + + if (inta & rtlpriv->cfg->maps[RTL_IMR_BDOK]) { + RT_TRACE(rtlpriv, COMP_INTR, DBG_TRACE, "beacon interrupt!\n"); + } + + if (inta & rtlpriv->cfg->maps[RTL_IMR_BCNINT]) { + RT_TRACE(rtlpriv, COMP_INTR, DBG_TRACE, + "prepare beacon for interrupt!\n"); + tasklet_schedule(&rtlpriv->works.irq_prepare_bcn_tasklet); + } + + /*<3> Tx related */ + if (unlikely(inta & rtlpriv->cfg->maps[RTL_IMR_TXFOVW])) + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, "IMR_TXFOVW!\n"); + + if (inta & rtlpriv->cfg->maps[RTL_IMR_MGNTDOK]) { + RT_TRACE(rtlpriv, COMP_INTR, DBG_TRACE, + "Manage ok interrupt!\n"); + _rtl_pci_tx_isr(hw, MGNT_QUEUE); + } + + if (inta & rtlpriv->cfg->maps[RTL_IMR_HIGHDOK]) { + RT_TRACE(rtlpriv, COMP_INTR, DBG_TRACE, + "HIGH_QUEUE ok interrupt!\n"); + _rtl_pci_tx_isr(hw, HIGH_QUEUE); + } + + if (inta & rtlpriv->cfg->maps[RTL_IMR_BKDOK]) { + rtlpriv->link_info.num_tx_inperiod++; + + RT_TRACE(rtlpriv, COMP_INTR, DBG_TRACE, + "BK Tx OK interrupt!\n"); + _rtl_pci_tx_isr(hw, BK_QUEUE); + } + + if (inta & rtlpriv->cfg->maps[RTL_IMR_BEDOK]) { + rtlpriv->link_info.num_tx_inperiod++; + + RT_TRACE(rtlpriv, COMP_INTR, DBG_TRACE, + "BE TX OK interrupt!\n"); + _rtl_pci_tx_isr(hw, BE_QUEUE); + } + + if (inta & rtlpriv->cfg->maps[RTL_IMR_VIDOK]) { + rtlpriv->link_info.num_tx_inperiod++; + + RT_TRACE(rtlpriv, COMP_INTR, DBG_TRACE, + "VI TX OK interrupt!\n"); + _rtl_pci_tx_isr(hw, VI_QUEUE); + } + + if (inta & rtlpriv->cfg->maps[RTL_IMR_VODOK]) { + rtlpriv->link_info.num_tx_inperiod++; + + RT_TRACE(rtlpriv, COMP_INTR, DBG_TRACE, + "Vo TX OK interrupt!\n"); + _rtl_pci_tx_isr(hw, VO_QUEUE); + } + + if (rtlhal->hw_type == HARDWARE_TYPE_RTL8192SE) { + if (inta & rtlpriv->cfg->maps[RTL_IMR_COMDOK]) { + rtlpriv->link_info.num_tx_inperiod++; + + RT_TRACE(rtlpriv, COMP_INTR, DBG_TRACE, + "CMD TX OK interrupt!\n"); + _rtl_pci_tx_isr(hw, TXCMD_QUEUE); + } + } + + /*<2> Rx related */ + if (inta & rtlpriv->cfg->maps[RTL_IMR_ROK]) { + RT_TRACE(rtlpriv, COMP_INTR, DBG_TRACE, "Rx ok interrupt!\n"); + _rtl_pci_rx_interrupt(hw); + } + + if (unlikely(inta & rtlpriv->cfg->maps[RTL_IMR_RDU])) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "rx descriptor unavailable!\n"); + _rtl_pci_rx_interrupt(hw); + } + + if (unlikely(inta & rtlpriv->cfg->maps[RTL_IMR_RXFOVW])) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, "rx overflow !\n"); + _rtl_pci_rx_interrupt(hw); + } + + /*fw related*/ + if (rtlhal->hw_type == HARDWARE_TYPE_RTL8723AE) { + if (inta & rtlpriv->cfg->maps[RTL_IMR_C2HCMD]) { + RT_TRACE(rtlpriv, COMP_INTR, DBG_TRACE, + "firmware interrupt!\n"); + queue_delayed_work(rtlpriv->works.rtl_wq, + &rtlpriv->works.fwevt_wq, 0); + } + } + + if (rtlpriv->rtlhal.earlymode_enable) + tasklet_schedule(&rtlpriv->works.irq_tasklet); + +done: + spin_unlock_irqrestore(&rtlpriv->locks.irq_th_lock, flags); + return ret; +} + +static void _rtl_pci_irq_tasklet(struct ieee80211_hw *hw) +{ + _rtl_pci_tx_chk_waitq(hw); +} + +static void _rtl_pci_prepare_bcn_tasklet(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl8192_tx_ring *ring = NULL; + struct ieee80211_hdr *hdr = NULL; + struct ieee80211_tx_info *info = NULL; + struct sk_buff *pskb = NULL; + struct rtl_tx_desc *pdesc = NULL; + struct rtl_tcb_desc tcb_desc; + u8 temp_one = 1; + + memset(&tcb_desc, 0, sizeof(struct rtl_tcb_desc)); + ring = &rtlpci->tx_ring[BEACON_QUEUE]; + pskb = __skb_dequeue(&ring->queue); + if (pskb) { + struct rtl_tx_desc *entry = &ring->desc[ring->idx]; + pci_unmap_single(rtlpci->pdev, rtlpriv->cfg->ops->get_desc( + (u8 *) entry, true, HW_DESC_TXBUFF_ADDR), + pskb->len, PCI_DMA_TODEVICE); + kfree_skb(pskb); + } + + /*NB: the beacon data buffer must be 32-bit aligned. */ + pskb = ieee80211_beacon_get(hw, mac->vif); + if (pskb == NULL) + return; + hdr = rtl_get_hdr(pskb); + info = IEEE80211_SKB_CB(pskb); + pdesc = &ring->desc[0]; + rtlpriv->cfg->ops->fill_tx_desc(hw, hdr, (u8 *) pdesc, + info, NULL, pskb, BEACON_QUEUE, &tcb_desc); + + __skb_queue_tail(&ring->queue, pskb); + + rtlpriv->cfg->ops->set_desc((u8 *) pdesc, true, HW_DESC_OWN, + &temp_one); + + return; +} + +static void _rtl_pci_init_trx_var(struct ieee80211_hw *hw) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u8 i; + + for (i = 0; i < RTL_PCI_MAX_TX_QUEUE_COUNT; i++) + rtlpci->txringcount[i] = RT_TXDESC_NUM; + + /* + *we just alloc 2 desc for beacon queue, + *because we just need first desc in hw beacon. + */ + rtlpci->txringcount[BEACON_QUEUE] = 2; + + /* + *BE queue need more descriptor for performance + *consideration or, No more tx desc will happen, + *and may cause mac80211 mem leakage. + */ + rtlpci->txringcount[BE_QUEUE] = RT_TXDESC_NUM_BE_QUEUE; + + rtlpci->rxbuffersize = 9100; /*2048/1024; */ + rtlpci->rxringcount = RTL_PCI_MAX_RX_COUNT; /*64; */ +} + +static void _rtl_pci_init_struct(struct ieee80211_hw *hw, + struct pci_dev *pdev) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + rtlpci->up_first_time = true; + rtlpci->being_init_adapter = false; + + rtlhal->hw = hw; + rtlpci->pdev = pdev; + + /*Tx/Rx related var */ + _rtl_pci_init_trx_var(hw); + + /*IBSS*/ mac->beacon_interval = 100; + + /*AMPDU*/ + mac->min_space_cfg = 0; + mac->max_mss_density = 0; + /*set sane AMPDU defaults */ + mac->current_ampdu_density = 7; + mac->current_ampdu_factor = 3; + + /*QOS*/ + rtlpci->acm_method = eAcmWay2_SW; + + /*task */ + tasklet_init(&rtlpriv->works.irq_tasklet, + (void (*)(unsigned long))_rtl_pci_irq_tasklet, + (unsigned long)hw); + tasklet_init(&rtlpriv->works.irq_prepare_bcn_tasklet, + (void (*)(unsigned long))_rtl_pci_prepare_bcn_tasklet, + (unsigned long)hw); + INIT_WORK(&rtlpriv->works.lps_change_work, + rtl_lps_change_work_callback); +} + +static int _rtl_pci_init_tx_ring(struct ieee80211_hw *hw, + unsigned int prio, unsigned int entries) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_tx_desc *ring; + dma_addr_t dma; + u32 nextdescaddress; + int i; + + ring = pci_alloc_consistent(rtlpci->pdev, + sizeof(*ring) * entries, &dma); + + if (!ring || (unsigned long)ring & 0xFF) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Cannot allocate TX ring (prio = %d)\n", prio); + return -ENOMEM; + } + + memset(ring, 0, sizeof(*ring) * entries); + rtlpci->tx_ring[prio].desc = ring; + rtlpci->tx_ring[prio].dma = dma; + rtlpci->tx_ring[prio].idx = 0; + rtlpci->tx_ring[prio].entries = entries; + skb_queue_head_init(&rtlpci->tx_ring[prio].queue); + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "queue:%d, ring_addr:%p\n", + prio, ring); + + for (i = 0; i < entries; i++) { + nextdescaddress = (u32) dma + + ((i + 1) % entries) * + sizeof(*ring); + + rtlpriv->cfg->ops->set_desc((u8 *)&(ring[i]), + true, HW_DESC_TX_NEXTDESC_ADDR, + (u8 *)&nextdescaddress); + } + + return 0; +} + +static int _rtl_pci_init_rx_ring(struct ieee80211_hw *hw) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_rx_desc *entry = NULL; + int i, rx_queue_idx; + u8 tmp_one = 1; + + /* + *rx_queue_idx 0:RX_MPDU_QUEUE + *rx_queue_idx 1:RX_CMD_QUEUE + */ + for (rx_queue_idx = 0; rx_queue_idx < RTL_PCI_MAX_RX_QUEUE; + rx_queue_idx++) { + rtlpci->rx_ring[rx_queue_idx].desc = + pci_alloc_consistent(rtlpci->pdev, + sizeof(*rtlpci->rx_ring[rx_queue_idx]. + desc) * rtlpci->rxringcount, + &rtlpci->rx_ring[rx_queue_idx].dma); + + if (!rtlpci->rx_ring[rx_queue_idx].desc || + (unsigned long)rtlpci->rx_ring[rx_queue_idx].desc & 0xFF) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Cannot allocate RX ring\n"); + return -ENOMEM; + } + + memset(rtlpci->rx_ring[rx_queue_idx].desc, 0, + sizeof(*rtlpci->rx_ring[rx_queue_idx].desc) * + rtlpci->rxringcount); + + rtlpci->rx_ring[rx_queue_idx].idx = 0; + + /* If amsdu_8k is disabled, set buffersize to 4096. This + * change will reduce memory fragmentation. + */ + if (rtlpci->rxbuffersize > 4096 && + rtlpriv->rtlhal.disable_amsdu_8k) + rtlpci->rxbuffersize = 4096; + + for (i = 0; i < rtlpci->rxringcount; i++) { + struct sk_buff *skb = + dev_alloc_skb(rtlpci->rxbuffersize); + u32 bufferaddress; + if (!skb) + return 0; + kmemleak_not_leak(skb); + entry = &rtlpci->rx_ring[rx_queue_idx].desc[i]; + + /*skb->dev = dev; */ + + rtlpci->rx_ring[rx_queue_idx].rx_buf[i] = skb; + + /* + *just set skb->cb to mapping addr + *for pci_unmap_single use + */ + *((dma_addr_t *) skb->cb) = + pci_map_single(rtlpci->pdev, skb_tail_pointer(skb), + rtlpci->rxbuffersize, + PCI_DMA_FROMDEVICE); + + bufferaddress = (*((dma_addr_t *)skb->cb)); + if (pci_dma_mapping_error(rtlpci->pdev, bufferaddress)) { + dev_kfree_skb_any(skb); + return 1; + } + rtlpriv->cfg->ops->set_desc((u8 *)entry, false, + HW_DESC_RXBUFF_ADDR, + (u8 *)&bufferaddress); + rtlpriv->cfg->ops->set_desc((u8 *)entry, false, + HW_DESC_RXPKT_LEN, + (u8 *)&rtlpci-> + rxbuffersize); + rtlpriv->cfg->ops->set_desc((u8 *) entry, false, + HW_DESC_RXOWN, + &tmp_one); + } + + rtlpriv->cfg->ops->set_desc((u8 *) entry, false, + HW_DESC_RXERO, &tmp_one); + } + return 0; +} + +static void _rtl_pci_free_tx_ring(struct ieee80211_hw *hw, + unsigned int prio) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl8192_tx_ring *ring = &rtlpci->tx_ring[prio]; + + while (skb_queue_len(&ring->queue)) { + struct rtl_tx_desc *entry = &ring->desc[ring->idx]; + struct sk_buff *skb = __skb_dequeue(&ring->queue); + + pci_unmap_single(rtlpci->pdev, + rtlpriv->cfg-> + ops->get_desc((u8 *) entry, true, + HW_DESC_TXBUFF_ADDR), + skb->len, PCI_DMA_TODEVICE); + kfree_skb(skb); + ring->idx = (ring->idx + 1) % ring->entries; + } + + if (ring->desc) { + pci_free_consistent(rtlpci->pdev, + sizeof(*ring->desc) * ring->entries, + ring->desc, ring->dma); + ring->desc = NULL; + } +} + +static void _rtl_pci_free_rx_ring(struct rtl_pci *rtlpci) +{ + int i, rx_queue_idx; + + /*rx_queue_idx 0:RX_MPDU_QUEUE */ + /*rx_queue_idx 1:RX_CMD_QUEUE */ + for (rx_queue_idx = 0; rx_queue_idx < RTL_PCI_MAX_RX_QUEUE; + rx_queue_idx++) { + for (i = 0; i < rtlpci->rxringcount; i++) { + struct sk_buff *skb = + rtlpci->rx_ring[rx_queue_idx].rx_buf[i]; + if (!skb) + continue; + + pci_unmap_single(rtlpci->pdev, + *((dma_addr_t *) skb->cb), + rtlpci->rxbuffersize, + PCI_DMA_FROMDEVICE); + kfree_skb(skb); + } + + if (rtlpci->rx_ring[rx_queue_idx].desc) { + pci_free_consistent(rtlpci->pdev, + sizeof(*rtlpci->rx_ring[rx_queue_idx]. + desc) * rtlpci->rxringcount, + rtlpci->rx_ring[rx_queue_idx].desc, + rtlpci->rx_ring[rx_queue_idx].dma); + rtlpci->rx_ring[rx_queue_idx].desc = NULL; + } + } +} + +static int _rtl_pci_init_trx_ring(struct ieee80211_hw *hw) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + int ret; + int i; + + ret = _rtl_pci_init_rx_ring(hw); + if (ret) + return ret; + + for (i = 0; i < RTL_PCI_MAX_TX_QUEUE_COUNT; i++) { + ret = _rtl_pci_init_tx_ring(hw, i, + rtlpci->txringcount[i]); + if (ret) + goto err_free_rings; + } + + return 0; + +err_free_rings: + _rtl_pci_free_rx_ring(rtlpci); + + for (i = 0; i < RTL_PCI_MAX_TX_QUEUE_COUNT; i++) + if (rtlpci->tx_ring[i].desc) + _rtl_pci_free_tx_ring(hw, i); + + return 1; +} + +static int _rtl_pci_deinit_trx_ring(struct ieee80211_hw *hw) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u32 i; + + /*free rx rings */ + _rtl_pci_free_rx_ring(rtlpci); + + /*free tx rings */ + for (i = 0; i < RTL_PCI_MAX_TX_QUEUE_COUNT; i++) + _rtl_pci_free_tx_ring(hw, i); + + return 0; +} + +int rtl_pci_reset_trx_ring(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + int i, rx_queue_idx; + unsigned long flags; + u8 tmp_one = 1; + + /*rx_queue_idx 0:RX_MPDU_QUEUE */ + /*rx_queue_idx 1:RX_CMD_QUEUE */ + for (rx_queue_idx = 0; rx_queue_idx < RTL_PCI_MAX_RX_QUEUE; + rx_queue_idx++) { + /* + *force the rx_ring[RX_MPDU_QUEUE/ + *RX_CMD_QUEUE].idx to the first one + */ + if (rtlpci->rx_ring[rx_queue_idx].desc) { + struct rtl_rx_desc *entry = NULL; + + for (i = 0; i < rtlpci->rxringcount; i++) { + entry = &rtlpci->rx_ring[rx_queue_idx].desc[i]; + rtlpriv->cfg->ops->set_desc((u8 *) entry, + false, + HW_DESC_RXOWN, + &tmp_one); + } + rtlpci->rx_ring[rx_queue_idx].idx = 0; + } + } + + /* + *after reset, release previous pending packet, + *and force the tx idx to the first one + */ + for (i = 0; i < RTL_PCI_MAX_TX_QUEUE_COUNT; i++) { + if (rtlpci->tx_ring[i].desc) { + struct rtl8192_tx_ring *ring = &rtlpci->tx_ring[i]; + + while (skb_queue_len(&ring->queue)) { + struct rtl_tx_desc *entry; + struct sk_buff *skb; + + spin_lock_irqsave(&rtlpriv->locks.irq_th_lock, + flags); + entry = &ring->desc[ring->idx]; + skb = __skb_dequeue(&ring->queue); + pci_unmap_single(rtlpci->pdev, + rtlpriv->cfg->ops-> + get_desc((u8 *) + entry, + true, + HW_DESC_TXBUFF_ADDR), + skb->len, PCI_DMA_TODEVICE); + ring->idx = (ring->idx + 1) % ring->entries; + spin_unlock_irqrestore(&rtlpriv->locks.irq_th_lock, + flags); + kfree_skb(skb); + } + ring->idx = 0; + } + } + + return 0; +} + +static bool rtl_pci_tx_chk_waitq_insert(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_sta_info *sta_entry = NULL; + u8 tid = rtl_get_tid(skb); + __le16 fc = rtl_get_fc(skb); + + if (!sta) + return false; + sta_entry = (struct rtl_sta_info *)sta->drv_priv; + + if (!rtlpriv->rtlhal.earlymode_enable) + return false; + if (ieee80211_is_nullfunc(fc)) + return false; + if (ieee80211_is_qos_nullfunc(fc)) + return false; + if (ieee80211_is_pspoll(fc)) + return false; + if (sta_entry->tids[tid].agg.agg_state != RTL_AGG_OPERATIONAL) + return false; + if (_rtl_mac_to_hwqueue(hw, skb) > VO_QUEUE) + return false; + if (tid > 7) + return false; + + /* maybe every tid should be checked */ + if (!rtlpriv->link_info.higher_busytxtraffic[tid]) + return false; + + spin_lock_bh(&rtlpriv->locks.waitq_lock); + skb_queue_tail(&rtlpriv->mac80211.skb_waitq[tid], skb); + spin_unlock_bh(&rtlpriv->locks.waitq_lock); + + return true; +} + +static int rtl_pci_tx(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + struct sk_buff *skb, + struct rtl_tcb_desc *ptcb_desc) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_sta_info *sta_entry = NULL; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct rtl8192_tx_ring *ring; + struct rtl_tx_desc *pdesc; + u8 idx; + u8 hw_queue = _rtl_mac_to_hwqueue(hw, skb); + unsigned long flags; + struct ieee80211_hdr *hdr = rtl_get_hdr(skb); + __le16 fc = rtl_get_fc(skb); + u8 *pda_addr = hdr->addr1; + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + /*ssn */ + u8 tid = 0; + u16 seq_number = 0; + u8 own; + u8 temp_one = 1; + + if (ieee80211_is_mgmt(fc)) + rtl_tx_mgmt_proc(hw, skb); + + if (rtlpriv->psc.sw_ps_enabled) { + if (ieee80211_is_data(fc) && !ieee80211_is_nullfunc(fc) && + !ieee80211_has_pm(fc)) + hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PM); + } + + rtl_action_proc(hw, skb, true); + + if (is_multicast_ether_addr(pda_addr)) + rtlpriv->stats.txbytesmulticast += skb->len; + else if (is_broadcast_ether_addr(pda_addr)) + rtlpriv->stats.txbytesbroadcast += skb->len; + else + rtlpriv->stats.txbytesunicast += skb->len; + + spin_lock_irqsave(&rtlpriv->locks.irq_th_lock, flags); + ring = &rtlpci->tx_ring[hw_queue]; + if (hw_queue != BEACON_QUEUE) + idx = (ring->idx + skb_queue_len(&ring->queue)) % + ring->entries; + else + idx = 0; + + pdesc = &ring->desc[idx]; + own = (u8) rtlpriv->cfg->ops->get_desc((u8 *) pdesc, + true, HW_DESC_OWN); + + if ((own == 1) && (hw_queue != BEACON_QUEUE)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "No more TX desc@%d, ring->idx = %d, idx = %d, skb_queue_len = 0x%d\n", + hw_queue, ring->idx, idx, + skb_queue_len(&ring->queue)); + + spin_unlock_irqrestore(&rtlpriv->locks.irq_th_lock, flags); + return skb->len; + } + + if (ieee80211_is_data_qos(fc)) { + tid = rtl_get_tid(skb); + if (sta) { + sta_entry = (struct rtl_sta_info *)sta->drv_priv; + seq_number = (le16_to_cpu(hdr->seq_ctrl) & + IEEE80211_SCTL_SEQ) >> 4; + seq_number += 1; + + if (!ieee80211_has_morefrags(hdr->frame_control)) + sta_entry->tids[tid].seq_number = seq_number; + } + } + + if (ieee80211_is_data(fc)) + rtlpriv->cfg->ops->led_control(hw, LED_CTL_TX); + + rtlpriv->cfg->ops->fill_tx_desc(hw, hdr, (u8 *)pdesc, + info, sta, skb, hw_queue, ptcb_desc); + + __skb_queue_tail(&ring->queue, skb); + + rtlpriv->cfg->ops->set_desc((u8 *)pdesc, true, + HW_DESC_OWN, &temp_one); + + + if ((ring->entries - skb_queue_len(&ring->queue)) < 2 && + hw_queue != BEACON_QUEUE) { + + RT_TRACE(rtlpriv, COMP_ERR, DBG_LOUD, + "less desc left, stop skb_queue@%d, ring->idx = %d, idx = %d, skb_queue_len = 0x%d\n", + hw_queue, ring->idx, idx, + skb_queue_len(&ring->queue)); + + ieee80211_stop_queue(hw, skb_get_queue_mapping(skb)); + } + + spin_unlock_irqrestore(&rtlpriv->locks.irq_th_lock, flags); + + rtlpriv->cfg->ops->tx_polling(hw, hw_queue); + + return 0; +} + +static void rtl_pci_flush(struct ieee80211_hw *hw, bool drop) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 i = 0; + int queue_id; + struct rtl8192_tx_ring *ring; + + if (mac->skip_scan) + return; + + for (queue_id = RTL_PCI_MAX_TX_QUEUE_COUNT - 1; queue_id >= 0;) { + u32 queue_len; + ring = &pcipriv->dev.tx_ring[queue_id]; + queue_len = skb_queue_len(&ring->queue); + if (queue_len == 0 || queue_id == BEACON_QUEUE || + queue_id == TXCMD_QUEUE) { + queue_id--; + continue; + } else { + msleep(20); + i++; + } + + /* we just wait 1s for all queues */ + if (rtlpriv->psc.rfpwr_state == ERFOFF || + is_hal_stop(rtlhal) || i >= 200) + return; + } +} + +static void rtl_pci_deinit(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + _rtl_pci_deinit_trx_ring(hw); + + synchronize_irq(rtlpci->pdev->irq); + tasklet_kill(&rtlpriv->works.irq_tasklet); + cancel_work_sync(&rtlpriv->works.lps_change_work); + + flush_workqueue(rtlpriv->works.rtl_wq); + destroy_workqueue(rtlpriv->works.rtl_wq); + +} + +static int rtl_pci_init(struct ieee80211_hw *hw, struct pci_dev *pdev) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int err; + + _rtl_pci_init_struct(hw, pdev); + + err = _rtl_pci_init_trx_ring(hw); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "tx ring initialization failed\n"); + return err; + } + + return 0; +} + +static int rtl_pci_start(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + int err; + + rtl_pci_reset_trx_ring(hw); + + rtlpci->driver_is_goingto_unload = false; + err = rtlpriv->cfg->ops->hw_init(hw); + if (err) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "Failed to config hardware!\n"); + return err; + } + + rtlpriv->cfg->ops->enable_interrupt(hw); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "enable_interrupt OK\n"); + + rtl_init_rx_config(hw); + + /*should be after adapter start and interrupt enable. */ + set_hal_start(rtlhal); + + RT_CLEAR_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + + rtlpci->up_first_time = false; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "OK\n"); + return 0; +} + +static void rtl_pci_stop(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + unsigned long flags; + u8 RFInProgressTimeOut = 0; + + /* + *should be before disable interrupt&adapter + *and will do it immediately. + */ + set_hal_stop(rtlhal); + + rtlpriv->cfg->ops->disable_interrupt(hw); + cancel_work_sync(&rtlpriv->works.lps_change_work); + + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flags); + while (ppsc->rfchange_inprogress) { + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flags); + if (RFInProgressTimeOut > 100) { + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flags); + break; + } + mdelay(1); + RFInProgressTimeOut++; + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flags); + } + ppsc->rfchange_inprogress = true; + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flags); + + rtlpci->driver_is_goingto_unload = true; + rtlpriv->cfg->ops->hw_disable(hw); + /* some things are not needed if firmware not available */ + if (!rtlpriv->max_fw_size) + return; + rtlpriv->cfg->ops->led_control(hw, LED_CTL_POWER_OFF); + + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flags); + ppsc->rfchange_inprogress = false; + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flags); + + rtl_pci_enable_aspm(hw); +} + +static bool _rtl_pci_find_adapter(struct pci_dev *pdev, + struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct pci_dev *bridge_pdev = pdev->bus->self; + u16 venderid; + u16 deviceid; + u8 revisionid; + u16 irqline; + u8 tmp; + + pcipriv->ndis_adapter.pcibridge_vendor = PCI_BRIDGE_VENDOR_UNKNOWN; + venderid = pdev->vendor; + deviceid = pdev->device; + pci_read_config_byte(pdev, 0x8, &revisionid); + pci_read_config_word(pdev, 0x3C, &irqline); + + /* PCI ID 0x10ec:0x8192 occurs for both RTL8192E, which uses + * r8192e_pci, and RTL8192SE, which uses this driver. If the + * revision ID is RTL_PCI_REVISION_ID_8192PCIE (0x01), then + * the correct driver is r8192e_pci, thus this routine should + * return false. + */ + if (deviceid == RTL_PCI_8192SE_DID && + revisionid == RTL_PCI_REVISION_ID_8192PCIE) + return false; + + if (deviceid == RTL_PCI_8192_DID || + deviceid == RTL_PCI_0044_DID || + deviceid == RTL_PCI_0047_DID || + deviceid == RTL_PCI_8192SE_DID || + deviceid == RTL_PCI_8174_DID || + deviceid == RTL_PCI_8173_DID || + deviceid == RTL_PCI_8172_DID || + deviceid == RTL_PCI_8171_DID) { + switch (revisionid) { + case RTL_PCI_REVISION_ID_8192PCIE: + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "8192 PCI-E is found - vid/did=%x/%x\n", + venderid, deviceid); + rtlhal->hw_type = HARDWARE_TYPE_RTL8192E; + return false; + case RTL_PCI_REVISION_ID_8192SE: + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "8192SE is found - vid/did=%x/%x\n", + venderid, deviceid); + rtlhal->hw_type = HARDWARE_TYPE_RTL8192SE; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Err: Unknown device - vid/did=%x/%x\n", + venderid, deviceid); + rtlhal->hw_type = HARDWARE_TYPE_RTL8192SE; + break; + + } + } else if (deviceid == RTL_PCI_8723AE_DID) { + rtlhal->hw_type = HARDWARE_TYPE_RTL8723AE; + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "8723AE PCI-E is found - " + "vid/did=%x/%x\n", venderid, deviceid); + } else if (deviceid == RTL_PCI_8192CET_DID || + deviceid == RTL_PCI_8192CE_DID || + deviceid == RTL_PCI_8191CE_DID || + deviceid == RTL_PCI_8188CE_DID) { + rtlhal->hw_type = HARDWARE_TYPE_RTL8192CE; + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "8192C PCI-E is found - vid/did=%x/%x\n", + venderid, deviceid); + } else if (deviceid == RTL_PCI_8192DE_DID || + deviceid == RTL_PCI_8192DE_DID2) { + rtlhal->hw_type = HARDWARE_TYPE_RTL8192DE; + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "8192D PCI-E is found - vid/did=%x/%x\n", + venderid, deviceid); + } else if (deviceid == RTL_PCI_8188EE_DID) { + rtlhal->hw_type = HARDWARE_TYPE_RTL8188EE; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Find adapter, Hardware type is 8188EE\n"); + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Err: Unknown device - vid/did=%x/%x\n", + venderid, deviceid); + + rtlhal->hw_type = RTL_DEFAULT_HARDWARE_TYPE; + } + + if (rtlhal->hw_type == HARDWARE_TYPE_RTL8192DE) { + if (revisionid == 0 || revisionid == 1) { + if (revisionid == 0) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Find 92DE MAC0\n"); + rtlhal->interfaceindex = 0; + } else if (revisionid == 1) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Find 92DE MAC1\n"); + rtlhal->interfaceindex = 1; + } + } else { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Unknown device - VendorID/DeviceID=%x/%x, Revision=%x\n", + venderid, deviceid, revisionid); + rtlhal->interfaceindex = 0; + } + } + /*find bus info */ + pcipriv->ndis_adapter.busnumber = pdev->bus->number; + pcipriv->ndis_adapter.devnumber = PCI_SLOT(pdev->devfn); + pcipriv->ndis_adapter.funcnumber = PCI_FUNC(pdev->devfn); + + /* some ARM have no bridge_pdev and will crash here + * so we should check if bridge_pdev is NULL + */ + if (bridge_pdev) { + /*find bridge info if available */ + pcipriv->ndis_adapter.pcibridge_vendorid = bridge_pdev->vendor; + for (tmp = 0; tmp < PCI_BRIDGE_VENDOR_MAX; tmp++) { + if (bridge_pdev->vendor == pcibridge_vendors[tmp]) { + pcipriv->ndis_adapter.pcibridge_vendor = tmp; + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "Pci Bridge Vendor is found index: %d\n", + tmp); + break; + } + } + } + + if (pcipriv->ndis_adapter.pcibridge_vendor != + PCI_BRIDGE_VENDOR_UNKNOWN) { + pcipriv->ndis_adapter.pcibridge_busnum = + bridge_pdev->bus->number; + pcipriv->ndis_adapter.pcibridge_devnum = + PCI_SLOT(bridge_pdev->devfn); + pcipriv->ndis_adapter.pcibridge_funcnum = + PCI_FUNC(bridge_pdev->devfn); + pcipriv->ndis_adapter.pcibridge_pciehdr_offset = + pci_pcie_cap(bridge_pdev); + pcipriv->ndis_adapter.num4bytes = + (pcipriv->ndis_adapter.pcibridge_pciehdr_offset + 0x10) / 4; + + rtl_pci_get_linkcontrol_field(hw); + + if (pcipriv->ndis_adapter.pcibridge_vendor == + PCI_BRIDGE_VENDOR_AMD) { + pcipriv->ndis_adapter.amd_l1_patch = + rtl_pci_get_amd_l1_patch(hw); + } + } + + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "pcidev busnumber:devnumber:funcnumber:vendor:link_ctl %d:%d:%d:%x:%x\n", + pcipriv->ndis_adapter.busnumber, + pcipriv->ndis_adapter.devnumber, + pcipriv->ndis_adapter.funcnumber, + pdev->vendor, pcipriv->ndis_adapter.linkctrl_reg); + + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "pci_bridge busnumber:devnumber:funcnumber:vendor:pcie_cap:link_ctl_reg:amd %d:%d:%d:%x:%x:%x:%x\n", + pcipriv->ndis_adapter.pcibridge_busnum, + pcipriv->ndis_adapter.pcibridge_devnum, + pcipriv->ndis_adapter.pcibridge_funcnum, + pcibridge_vendors[pcipriv->ndis_adapter.pcibridge_vendor], + pcipriv->ndis_adapter.pcibridge_pciehdr_offset, + pcipriv->ndis_adapter.pcibridge_linkctrlreg, + pcipriv->ndis_adapter.amd_l1_patch); + + rtl_pci_parse_configuration(pdev, hw); + list_add_tail(&rtlpriv->list, &rtlpriv->glb_var->glb_priv_list); + + return true; +} + +int rtl_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct ieee80211_hw *hw = NULL; + + struct rtl_priv *rtlpriv = NULL; + struct rtl_pci_priv *pcipriv = NULL; + struct rtl_pci *rtlpci; + unsigned long pmem_start, pmem_len, pmem_flags; + int err; + + err = pci_enable_device(pdev); + if (err) { + RT_ASSERT(false, "%s : Cannot enable new PCI device\n", + pci_name(pdev)); + return err; + } + + if (!pci_set_dma_mask(pdev, DMA_BIT_MASK(32))) { + if (pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32))) { + RT_ASSERT(false, + "Unable to obtain 32bit DMA for consistent allocations\n"); + err = -ENOMEM; + goto fail1; + } + } + + pci_set_master(pdev); + + hw = ieee80211_alloc_hw(sizeof(struct rtl_pci_priv) + + sizeof(struct rtl_priv), &rtl_ops); + if (!hw) { + RT_ASSERT(false, + "%s : ieee80211 alloc failed\n", pci_name(pdev)); + err = -ENOMEM; + goto fail1; + } + + SET_IEEE80211_DEV(hw, &pdev->dev); + pci_set_drvdata(pdev, hw); + + rtlpriv = hw->priv; + rtlpriv->hw = hw; + pcipriv = (void *)rtlpriv->priv; + pcipriv->dev.pdev = pdev; + init_completion(&rtlpriv->firmware_loading_complete); + + /* init cfg & intf_ops */ + rtlpriv->rtlhal.interface = INTF_PCI; + rtlpriv->cfg = (struct rtl_hal_cfg *)(id->driver_data); + rtlpriv->intf_ops = &rtl_pci_ops; + rtlpriv->glb_var = &global_var; + + /* + *init dbgp flags before all + *other functions, because we will + *use it in other funtions like + *RT_TRACE/RT_PRINT/RTL_PRINT_DATA + *you can not use these macro + *before this + */ + rtl_dbgp_flag_init(hw); + + /* MEM map */ + err = pci_request_regions(pdev, KBUILD_MODNAME); + if (err) { + RT_ASSERT(false, "Can't obtain PCI resources\n"); + goto fail1; + } + + pmem_start = pci_resource_start(pdev, rtlpriv->cfg->bar_id); + pmem_len = pci_resource_len(pdev, rtlpriv->cfg->bar_id); + pmem_flags = pci_resource_flags(pdev, rtlpriv->cfg->bar_id); + + /*shared mem start */ + rtlpriv->io.pci_mem_start = + (unsigned long)pci_iomap(pdev, + rtlpriv->cfg->bar_id, pmem_len); + if (rtlpriv->io.pci_mem_start == 0) { + RT_ASSERT(false, "Can't map PCI mem\n"); + err = -ENOMEM; + goto fail2; + } + + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "mem mapped space: start: 0x%08lx len:%08lx flags:%08lx, after map:0x%08lx\n", + pmem_start, pmem_len, pmem_flags, + rtlpriv->io.pci_mem_start); + + /* Disable Clk Request */ + pci_write_config_byte(pdev, 0x81, 0); + /* leave D3 mode */ + pci_write_config_byte(pdev, 0x44, 0); + pci_write_config_byte(pdev, 0x04, 0x06); + pci_write_config_byte(pdev, 0x04, 0x07); + + /* find adapter */ + if (!_rtl_pci_find_adapter(pdev, hw)) { + err = -ENODEV; + goto fail3; + } + + /* Init IO handler */ + _rtl_pci_io_handler_init(&pdev->dev, hw); + + /*like read eeprom and so on */ + rtlpriv->cfg->ops->read_eeprom_info(hw); + + /*aspm */ + rtl_pci_init_aspm(hw); + + /* Init mac80211 sw */ + err = rtl_init_core(hw); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Can't allocate sw for mac80211\n"); + goto fail3; + } + + /* Init PCI sw */ + err = rtl_pci_init(hw, pdev); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Failed to init PCI\n"); + goto fail3; + } + + if (rtlpriv->cfg->ops->init_sw_vars(hw)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Can't init_sw_vars\n"); + err = -ENODEV; + goto fail3; + } + + rtlpriv->cfg->ops->init_sw_leds(hw); + + err = sysfs_create_group(&pdev->dev.kobj, &rtl_attribute_group); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "failed to create sysfs device attributes\n"); + goto fail3; + } + + rtlpci = rtl_pcidev(pcipriv); + err = request_irq(rtlpci->pdev->irq, &_rtl_pci_interrupt, + IRQF_SHARED, KBUILD_MODNAME, hw); + if (err) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "%s: failed to register IRQ handler\n", + wiphy_name(hw->wiphy)); + goto fail3; + } + rtlpci->irq_alloc = 1; + + return 0; + +fail3: + rtl_deinit_core(hw); + + if (rtlpriv->io.pci_mem_start != 0) + pci_iounmap(pdev, (void __iomem *)rtlpriv->io.pci_mem_start); + +fail2: + pci_release_regions(pdev); + complete(&rtlpriv->firmware_loading_complete); + +fail1: + if (hw) + ieee80211_free_hw(hw); + pci_set_drvdata(pdev, NULL); + pci_disable_device(pdev); + + return err; + +} +EXPORT_SYMBOL(rtl_pci_probe); + +void rtl_pci_disconnect(struct pci_dev *pdev) +{ + struct ieee80211_hw *hw = pci_get_drvdata(pdev); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(pcipriv); + struct rtl_mac *rtlmac = rtl_mac(rtlpriv); + + /* just in case driver is removed before firmware callback */ + wait_for_completion(&rtlpriv->firmware_loading_complete); + clear_bit(RTL_STATUS_INTERFACE_START, &rtlpriv->status); + + sysfs_remove_group(&pdev->dev.kobj, &rtl_attribute_group); + + /*ieee80211_unregister_hw will call ops_stop */ + if (rtlmac->mac80211_registered == 1) { + ieee80211_unregister_hw(hw); + rtlmac->mac80211_registered = 0; + } else { + rtl_deinit_deferred_work(hw); + rtlpriv->intf_ops->adapter_stop(hw); + } + rtlpriv->cfg->ops->disable_interrupt(hw); + + /*deinit rfkill */ + rtl_deinit_rfkill(hw); + + rtl_pci_deinit(hw); + rtl_deinit_core(hw); + rtlpriv->cfg->ops->deinit_sw_vars(hw); + + if (rtlpci->irq_alloc) { + synchronize_irq(rtlpci->pdev->irq); + free_irq(rtlpci->pdev->irq, hw); + rtlpci->irq_alloc = 0; + } + + list_del(&rtlpriv->list); + if (rtlpriv->io.pci_mem_start != 0) { + pci_iounmap(pdev, (void __iomem *)rtlpriv->io.pci_mem_start); + pci_release_regions(pdev); + } + + pci_disable_device(pdev); + + rtl_pci_disable_aspm(hw); + + pci_set_drvdata(pdev, NULL); + + ieee80211_free_hw(hw); +} +EXPORT_SYMBOL(rtl_pci_disconnect); + +#ifdef CONFIG_PM_SLEEP +/*************************************** +kernel pci power state define: +PCI_D0 ((pci_power_t __force) 0) +PCI_D1 ((pci_power_t __force) 1) +PCI_D2 ((pci_power_t __force) 2) +PCI_D3hot ((pci_power_t __force) 3) +PCI_D3cold ((pci_power_t __force) 4) +PCI_UNKNOWN ((pci_power_t __force) 5) + +This function is called when system +goes into suspend state mac80211 will +call rtl_mac_stop() from the mac80211 +suspend function first, So there is +no need to call hw_disable here. +****************************************/ +int rtl_pci_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct ieee80211_hw *hw = pci_get_drvdata(pdev); + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->cfg->ops->hw_suspend(hw); + rtl_deinit_rfkill(hw); + + return 0; +} +EXPORT_SYMBOL(rtl_pci_suspend); + +int rtl_pci_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct ieee80211_hw *hw = pci_get_drvdata(pdev); + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->cfg->ops->hw_resume(hw); + rtl_init_rfkill(hw); + return 0; +} +EXPORT_SYMBOL(rtl_pci_resume); +#endif /* CONFIG_PM_SLEEP */ + +struct rtl_intf_ops rtl_pci_ops = { + .read_efuse_byte = read_efuse_byte, + .adapter_start = rtl_pci_start, + .adapter_stop = rtl_pci_stop, + .check_buddy_priv = rtl_pci_check_buddy_priv, + .adapter_tx = rtl_pci_tx, + .flush = rtl_pci_flush, + .reset_trx_ring = rtl_pci_reset_trx_ring, + .waitq_insert = rtl_pci_tx_chk_waitq_insert, + + .disable_aspm = rtl_pci_disable_aspm, + .enable_aspm = rtl_pci_enable_aspm, +}; diff --git a/drivers/net/wireless/rtlwifi/pci.h b/drivers/net/wireless/rtlwifi/pci.h new file mode 100644 index 0000000..d3262ec --- /dev/null +++ b/drivers/net/wireless/rtlwifi/pci.h @@ -0,0 +1,280 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL_PCI_H__ +#define __RTL_PCI_H__ + +#include +/* +1: MSDU packet queue, +2: Rx Command Queue +*/ +#define RTL_PCI_RX_MPDU_QUEUE 0 +#define RTL_PCI_RX_CMD_QUEUE 1 +#define RTL_PCI_MAX_RX_QUEUE 2 + +#define RTL_PCI_MAX_RX_COUNT 64 +#define RTL_PCI_MAX_TX_QUEUE_COUNT 9 + +#define RT_TXDESC_NUM 128 +#define RT_TXDESC_NUM_BE_QUEUE 256 + +#define BK_QUEUE 0 +#define BE_QUEUE 1 +#define VI_QUEUE 2 +#define VO_QUEUE 3 +#define BEACON_QUEUE 4 +#define TXCMD_QUEUE 5 +#define MGNT_QUEUE 6 +#define HIGH_QUEUE 7 +#define HCCA_QUEUE 8 + +#define RTL_PCI_DEVICE(vend, dev, cfg) \ + .vendor = (vend), \ + .device = (dev), \ + .subvendor = PCI_ANY_ID, \ + .subdevice = PCI_ANY_ID,\ + .driver_data = (kernel_ulong_t)&(cfg) + +#define PCI_MAX_BRIDGE_NUMBER 255 +#define PCI_MAX_DEVICES 32 +#define PCI_MAX_FUNCTION 8 + +#define PCI_CONF_ADDRESS 0x0CF8 /*PCI Configuration Space Address */ +#define PCI_CONF_DATA 0x0CFC /*PCI Configuration Space Data */ + +#define U1DONTCARE 0xFF +#define U2DONTCARE 0xFFFF +#define U4DONTCARE 0xFFFFFFFF + +#define RTL_PCI_8192_DID 0x8192 /*8192 PCI-E */ +#define RTL_PCI_8192SE_DID 0x8192 /*8192 SE */ +#define RTL_PCI_8174_DID 0x8174 /*8192 SE */ +#define RTL_PCI_8173_DID 0x8173 /*8191 SE Crab */ +#define RTL_PCI_8172_DID 0x8172 /*8191 SE RE */ +#define RTL_PCI_8171_DID 0x8171 /*8191 SE Unicron */ +#define RTL_PCI_8723AE_DID 0x8723 /*8723AE */ +#define RTL_PCI_0045_DID 0x0045 /*8190 PCI for Ceraga */ +#define RTL_PCI_0046_DID 0x0046 /*8190 Cardbus for Ceraga */ +#define RTL_PCI_0044_DID 0x0044 /*8192e PCIE for Ceraga */ +#define RTL_PCI_0047_DID 0x0047 /*8192e Express Card for Ceraga */ +#define RTL_PCI_700F_DID 0x700F +#define RTL_PCI_701F_DID 0x701F +#define RTL_PCI_DLINK_DID 0x3304 +#define RTL_PCI_8192CET_DID 0x8191 /*8192ce */ +#define RTL_PCI_8192CE_DID 0x8178 /*8192ce */ +#define RTL_PCI_8191CE_DID 0x8177 /*8192ce */ +#define RTL_PCI_8188CE_DID 0x8176 /*8192ce */ +#define RTL_PCI_8192CU_DID 0x8191 /*8192ce */ +#define RTL_PCI_8192DE_DID 0x8193 /*8192de */ +#define RTL_PCI_8192DE_DID2 0x002B /*92DE*/ +#define RTL_PCI_8188EE_DID 0x8179 /*8188ee*/ + +/*8192 support 16 pages of IO registers*/ +#define RTL_MEM_MAPPED_IO_RANGE_8190PCI 0x1000 +#define RTL_MEM_MAPPED_IO_RANGE_8192PCIE 0x4000 +#define RTL_MEM_MAPPED_IO_RANGE_8192SE 0x4000 +#define RTL_MEM_MAPPED_IO_RANGE_8192CE 0x4000 +#define RTL_MEM_MAPPED_IO_RANGE_8192DE 0x4000 + +#define RTL_PCI_REVISION_ID_8190PCI 0x00 +#define RTL_PCI_REVISION_ID_8192PCIE 0x01 +#define RTL_PCI_REVISION_ID_8192SE 0x10 +#define RTL_PCI_REVISION_ID_8192CE 0x1 +#define RTL_PCI_REVISION_ID_8192DE 0x0 + +#define RTL_DEFAULT_HARDWARE_TYPE HARDWARE_TYPE_RTL8192CE + +enum pci_bridge_vendor { + PCI_BRIDGE_VENDOR_INTEL = 0x0, /*0b'0000,0001 */ + PCI_BRIDGE_VENDOR_ATI, /*0b'0000,0010*/ + PCI_BRIDGE_VENDOR_AMD, /*0b'0000,0100*/ + PCI_BRIDGE_VENDOR_SIS, /*0b'0000,1000*/ + PCI_BRIDGE_VENDOR_UNKNOWN, /*0b'0100,0000*/ + PCI_BRIDGE_VENDOR_MAX, +}; + +struct rtl_pci_capabilities_header { + u8 capability_id; + u8 next; +}; + +struct rtl_rx_desc { + u32 dword[8]; +} __packed; + +struct rtl_tx_desc { + u32 dword[16]; +} __packed; + +struct rtl_tx_cmd_desc { + u32 dword[16]; +} __packed; + +struct rtl8192_tx_ring { + struct rtl_tx_desc *desc; + dma_addr_t dma; + unsigned int idx; + unsigned int entries; + struct sk_buff_head queue; +}; + +struct rtl8192_rx_ring { + struct rtl_rx_desc *desc; + dma_addr_t dma; + unsigned int idx; + struct sk_buff *rx_buf[RTL_PCI_MAX_RX_COUNT]; +}; + +struct rtl_pci { + struct pci_dev *pdev; + bool irq_enabled; + + bool driver_is_goingto_unload; + bool up_first_time; + bool first_init; + bool being_init_adapter; + bool init_ready; + + /*Tx */ + struct rtl8192_tx_ring tx_ring[RTL_PCI_MAX_TX_QUEUE_COUNT]; + int txringcount[RTL_PCI_MAX_TX_QUEUE_COUNT]; + u32 transmit_config; + + /*Rx */ + struct rtl8192_rx_ring rx_ring[RTL_PCI_MAX_RX_QUEUE]; + int rxringcount; + u16 rxbuffersize; + u32 receive_config; + + /*irq */ + u8 irq_alloc; + u32 irq_mask[2]; + u32 sys_irq_mask; + + /*Bcn control register setting */ + u32 reg_bcn_ctrl_val; + + /*ASPM*/ u8 const_pci_aspm; + u8 const_amdpci_aspm; + u8 const_hwsw_rfoff_d3; + u8 const_support_pciaspm; + /*pci-e bridge */ + u8 const_hostpci_aspm_setting; + /*pci-e device */ + u8 const_devicepci_aspm_setting; + /*If it supports ASPM, Offset[560h] = 0x40, + otherwise Offset[560h] = 0x00. */ + bool support_aspm; + bool support_backdoor; + + /*QOS & EDCA */ + enum acm_method acm_method; + + u16 shortretry_limit; + u16 longretry_limit; +}; + +struct mp_adapter { + u8 linkctrl_reg; + + u8 busnumber; + u8 devnumber; + u8 funcnumber; + + u8 pcibridge_busnum; + u8 pcibridge_devnum; + u8 pcibridge_funcnum; + + u8 pcibridge_vendor; + u16 pcibridge_vendorid; + u16 pcibridge_deviceid; + + u8 num4bytes; + + u8 pcibridge_pciehdr_offset; + u8 pcibridge_linkctrlreg; + + bool amd_l1_patch; +}; + +struct rtl_pci_priv { + struct rtl_pci dev; + struct mp_adapter ndis_adapter; + struct rtl_led_ctl ledctl; + struct bt_coexist_info bt_coexist; +}; + +#define rtl_pcipriv(hw) (((struct rtl_pci_priv *)(rtl_priv(hw))->priv)) +#define rtl_pcidev(pcipriv) (&((pcipriv)->dev)) + +int rtl_pci_reset_trx_ring(struct ieee80211_hw *hw); + +extern struct rtl_intf_ops rtl_pci_ops; + +int rtl_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id); +void rtl_pci_disconnect(struct pci_dev *pdev); +#ifdef CONFIG_PM_SLEEP +int rtl_pci_suspend(struct device *dev); +int rtl_pci_resume(struct device *dev); +#endif /* CONFIG_PM_SLEEP */ +static inline u8 pci_read8_sync(struct rtl_priv *rtlpriv, u32 addr) +{ + return readb((u8 __iomem *) rtlpriv->io.pci_mem_start + addr); +} + +static inline u16 pci_read16_sync(struct rtl_priv *rtlpriv, u32 addr) +{ + return readw((u8 __iomem *) rtlpriv->io.pci_mem_start + addr); +} + +static inline u32 pci_read32_sync(struct rtl_priv *rtlpriv, u32 addr) +{ + return readl((u8 __iomem *) rtlpriv->io.pci_mem_start + addr); +} + +static inline void pci_write8_async(struct rtl_priv *rtlpriv, u32 addr, u8 val) +{ + writeb(val, (u8 __iomem *) rtlpriv->io.pci_mem_start + addr); +} + +static inline void pci_write16_async(struct rtl_priv *rtlpriv, + u32 addr, u16 val) +{ + writew(val, (u8 __iomem *) rtlpriv->io.pci_mem_start + addr); +} + +static inline void pci_write32_async(struct rtl_priv *rtlpriv, + u32 addr, u32 val) +{ + writel(val, (u8 __iomem *) rtlpriv->io.pci_mem_start + addr); +} + +#endif diff --git a/drivers/net/wireless/rtlwifi/ps.c b/drivers/net/wireless/rtlwifi/ps.c new file mode 100644 index 0000000..71e917d --- /dev/null +++ b/drivers/net/wireless/rtlwifi/ps.c @@ -0,0 +1,936 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include +#include "wifi.h" +#include "base.h" +#include "ps.h" + +bool rtl_ps_enable_nic(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + /*<1> reset trx ring */ + if (rtlhal->interface == INTF_PCI) + rtlpriv->intf_ops->reset_trx_ring(hw); + + if (is_hal_stop(rtlhal)) + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Driver is already down!\n"); + + /*<2> Enable Adapter */ + if (rtlpriv->cfg->ops->hw_init(hw)) + return 1; + RT_CLEAR_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + + /*<3> Enable Interrupt */ + rtlpriv->cfg->ops->enable_interrupt(hw); + + /* */ + rtl_watch_dog_timer_callback((unsigned long)hw); + + return true; +} +EXPORT_SYMBOL(rtl_ps_enable_nic); + +bool rtl_ps_disable_nic(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + /*<1> Stop all timer */ + rtl_deinit_deferred_work(hw); + + /*<2> Disable Interrupt */ + rtlpriv->cfg->ops->disable_interrupt(hw); + tasklet_kill(&rtlpriv->works.irq_tasklet); + + /*<3> Disable Adapter */ + rtlpriv->cfg->ops->hw_disable(hw); + + return true; +} +EXPORT_SYMBOL(rtl_ps_disable_nic); + +bool rtl_ps_set_rf_state(struct ieee80211_hw *hw, + enum rf_pwrstate state_toset, + u32 changesource) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + bool actionallowed = false; + + switch (state_toset) { + case ERFON: + ppsc->rfoff_reason &= (~changesource); + + if ((changesource == RF_CHANGE_BY_HW) && + (ppsc->hwradiooff)) { + ppsc->hwradiooff = false; + } + + if (!ppsc->rfoff_reason) { + ppsc->rfoff_reason = 0; + actionallowed = true; + } + + break; + + case ERFOFF: + + if ((changesource == RF_CHANGE_BY_HW) && !ppsc->hwradiooff) { + ppsc->hwradiooff = true; + } + + ppsc->rfoff_reason |= changesource; + actionallowed = true; + break; + + case ERFSLEEP: + ppsc->rfoff_reason |= changesource; + actionallowed = true; + break; + + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + + if (actionallowed) + rtlpriv->cfg->ops->set_rf_power_state(hw, state_toset); + + return actionallowed; +} +EXPORT_SYMBOL(rtl_ps_set_rf_state); + +static void _rtl_ps_inactive_ps(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + ppsc->swrf_processing = true; + + if (ppsc->inactive_pwrstate == ERFON && + rtlhal->interface == INTF_PCI) { + if ((ppsc->reg_rfps_level & RT_RF_OFF_LEVL_ASPM) && + RT_IN_PS_LEVEL(ppsc, RT_PS_LEVEL_ASPM) && + rtlhal->interface == INTF_PCI) { + rtlpriv->intf_ops->disable_aspm(hw); + RT_CLEAR_PS_LEVEL(ppsc, RT_PS_LEVEL_ASPM); + } + } + + rtl_ps_set_rf_state(hw, ppsc->inactive_pwrstate, RF_CHANGE_BY_IPS); + + if (ppsc->inactive_pwrstate == ERFOFF && + rtlhal->interface == INTF_PCI) { + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_ASPM && + !RT_IN_PS_LEVEL(ppsc, RT_PS_LEVEL_ASPM)) { + rtlpriv->intf_ops->enable_aspm(hw); + RT_SET_PS_LEVEL(ppsc, RT_PS_LEVEL_ASPM); + } + } + + ppsc->swrf_processing = false; +} + +void rtl_ips_nic_off_wq_callback(void *data) +{ + struct rtl_works *rtlworks = + container_of_dwork_rtl(data, struct rtl_works, ips_nic_off_wq); + struct ieee80211_hw *hw = rtlworks->hw; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + enum rf_pwrstate rtstate; + + if (mac->opmode != NL80211_IFTYPE_STATION) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "not station return\n"); + return; + } + + if (mac->p2p_in_use) + return; + + if (mac->link_state > MAC80211_NOLINK) + return; + + if (is_hal_stop(rtlhal)) + return; + + if (rtlpriv->sec.being_setkey) + return; + + if (rtlpriv->cfg->ops->bt_coex_off_before_lps) + rtlpriv->cfg->ops->bt_coex_off_before_lps(hw); + + if (ppsc->inactiveps) { + rtstate = ppsc->rfpwr_state; + + /* + *Do not enter IPS in the following conditions: + *(1) RF is already OFF or Sleep + *(2) swrf_processing (indicates the IPS is still under going) + *(3) Connectted (only disconnected can trigger IPS) + *(4) IBSS (send Beacon) + *(5) AP mode (send Beacon) + *(6) monitor mode (rcv packet) + */ + + if (rtstate == ERFON && + !ppsc->swrf_processing && + (mac->link_state == MAC80211_NOLINK) && + !mac->act_scanning) { + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "IPSEnter(): Turn off RF\n"); + + ppsc->inactive_pwrstate = ERFOFF; + ppsc->in_powersavemode = true; + + /*rtl_pci_reset_trx_ring(hw); */ + _rtl_ps_inactive_ps(hw); + } + } +} + +void rtl_ips_nic_off(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + /* + *because when link with ap, mac80211 will ask us + *to disable nic quickly after scan before linking, + *this will cause link failed, so we delay 100ms here + */ + queue_delayed_work(rtlpriv->works.rtl_wq, + &rtlpriv->works.ips_nic_off_wq, MSECS(100)); +} + +/* NOTICE: any opmode should exc nic_on, or disable without + * nic_on may something wrong, like adhoc TP + */ +void rtl_ips_nic_on(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + enum rf_pwrstate rtstate; + unsigned long flags; + + if (mac->opmode != NL80211_IFTYPE_STATION) + return; + + spin_lock_irqsave(&rtlpriv->locks.ips_lock, flags); + + if (ppsc->inactiveps) { + rtstate = ppsc->rfpwr_state; + + if (rtstate != ERFON && + !ppsc->swrf_processing && + ppsc->rfoff_reason <= RF_CHANGE_BY_IPS) { + + ppsc->inactive_pwrstate = ERFON; + ppsc->in_powersavemode = false; + + _rtl_ps_inactive_ps(hw); + } + } + + spin_unlock_irqrestore(&rtlpriv->locks.ips_lock, flags); +} + +/*for FW LPS*/ + +/* + *Determine if we can set Fw into PS mode + *in current condition.Return TRUE if it + *can enter PS mode. + */ +static bool rtl_get_fwlps_doze(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + u32 ps_timediff; + + ps_timediff = jiffies_to_msecs(jiffies - + ppsc->last_delaylps_stamp_jiffies); + + if (ps_timediff < 2000) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "Delay enter Fw LPS for DHCP, ARP, or EAPOL exchanging state\n"); + return false; + } + + if (mac->link_state != MAC80211_LINKED) + return false; + + if (mac->opmode == NL80211_IFTYPE_ADHOC) + return false; + + return true; +} + +/* Change current and default preamble mode.*/ +static void rtl_lps_set_psmode(struct ieee80211_hw *hw, u8 rt_psmode) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + bool enter_fwlps; + + if (mac->opmode == NL80211_IFTYPE_ADHOC) + return; + + if (mac->link_state != MAC80211_LINKED) + return; + + if (ppsc->dot11_psmode == rt_psmode) + return; + + /* Update power save mode configured. */ + ppsc->dot11_psmode = rt_psmode; + + /* + * + *1. Enter PS mode + * Set RPWM to Fw to turn RF off and send H2C fw_pwrmode + * cmd to set Fw into PS mode. + *2. Leave PS mode + * Send H2C fw_pwrmode cmd to Fw to set Fw into Active + * mode and set RPWM to turn RF on. + */ + + if ((ppsc->fwctrl_lps) && ppsc->report_linked) { + if (ppsc->dot11_psmode == EACTIVE) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "FW LPS leave ps_mode:%x\n", + FW_PS_ACTIVE_MODE); + enter_fwlps = false; + ppsc->pwr_mode = FW_PS_ACTIVE_MODE; + ppsc->smart_ps = 0; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_FW_LPS_ACTION, + (u8 *)(&enter_fwlps)); + if (ppsc->p2p_ps_info.opp_ps) + rtl_p2p_ps_cmd(hw, P2P_PS_ENABLE); + + } else { + if (rtl_get_fwlps_doze(hw)) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "FW LPS enter ps_mode:%x\n", + ppsc->fwctrl_psmode); + enter_fwlps = true; + ppsc->pwr_mode = ppsc->fwctrl_psmode; + ppsc->smart_ps = 2; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_FW_LPS_ACTION, + (u8 *)(&enter_fwlps)); + + } else { + /* Reset the power save related parameters. */ + ppsc->dot11_psmode = EACTIVE; + } + } + } +} + +/*Enter the leisure power save mode.*/ +void rtl_lps_enter(struct ieee80211_hw *hw) +{ + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (!ppsc->fwctrl_lps) + return; + + if (rtlpriv->sec.being_setkey) + return; + + if (rtlpriv->link_info.busytraffic) + return; + + /*sleep after linked 10s, to let DHCP and 4-way handshake ok enough!! */ + if (mac->cnt_after_linked < 5) + return; + + if (mac->opmode == NL80211_IFTYPE_ADHOC) + return; + + if (mac->link_state != MAC80211_LINKED) + return; + + mutex_lock(&rtlpriv->locks.ps_mutex); + + /* Idle for a while if we connect to AP a while ago. */ + if (mac->cnt_after_linked >= 2) { + if (ppsc->dot11_psmode == EACTIVE) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "Enter 802.11 power save mode...\n"); + + rtl_lps_set_psmode(hw, EAUTOPS); + } + } + + mutex_unlock(&rtlpriv->locks.ps_mutex); +} + +/*Leave the leisure power save mode.*/ +void rtl_lps_leave(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + mutex_lock(&rtlpriv->locks.ps_mutex); + + if (ppsc->fwctrl_lps) { + if (ppsc->dot11_psmode != EACTIVE) { + + /*FIX ME */ + rtlpriv->cfg->ops->enable_interrupt(hw); + + if (ppsc->reg_rfps_level & RT_RF_LPS_LEVEL_ASPM && + RT_IN_PS_LEVEL(ppsc, RT_PS_LEVEL_ASPM) && + rtlhal->interface == INTF_PCI) { + rtlpriv->intf_ops->disable_aspm(hw); + RT_CLEAR_PS_LEVEL(ppsc, RT_PS_LEVEL_ASPM); + } + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "Busy Traffic,Leave 802.11 power save..\n"); + + rtl_lps_set_psmode(hw, EACTIVE); + } + } + mutex_unlock(&rtlpriv->locks.ps_mutex); +} + +/* For sw LPS*/ +void rtl_swlps_beacon(struct ieee80211_hw *hw, void *data, unsigned int len) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct ieee80211_hdr *hdr = data; + struct ieee80211_tim_ie *tim_ie; + u8 *tim; + u8 tim_len; + bool u_buffed; + bool m_buffed; + + if (mac->opmode != NL80211_IFTYPE_STATION) + return; + + if (!rtlpriv->psc.swctrl_lps) + return; + + if (rtlpriv->mac80211.link_state != MAC80211_LINKED) + return; + + if (!rtlpriv->psc.sw_ps_enabled) + return; + + if (rtlpriv->psc.fwctrl_lps) + return; + + if (likely(!(hw->conf.flags & IEEE80211_CONF_PS))) + return; + + /* check if this really is a beacon */ + if (!ieee80211_is_beacon(hdr->frame_control)) + return; + + /* min. beacon length + FCS_LEN */ + if (len <= 40 + FCS_LEN) + return; + + /* and only beacons from the associated BSSID, please */ + if (!ether_addr_equal(hdr->addr3, rtlpriv->mac80211.bssid)) + return; + + rtlpriv->psc.last_beacon = jiffies; + + tim = rtl_find_ie(data, len - FCS_LEN, WLAN_EID_TIM); + if (!tim) + return; + + if (tim[1] < sizeof(*tim_ie)) + return; + + tim_len = tim[1]; + tim_ie = (struct ieee80211_tim_ie *) &tim[2]; + + if (!WARN_ON_ONCE(!hw->conf.ps_dtim_period)) + rtlpriv->psc.dtim_counter = tim_ie->dtim_count; + + /* Check whenever the PHY can be turned off again. */ + + /* 1. What about buffered unicast traffic for our AID? */ + u_buffed = ieee80211_check_tim(tim_ie, tim_len, + rtlpriv->mac80211.assoc_id); + + /* 2. Maybe the AP wants to send multicast/broadcast data? */ + m_buffed = tim_ie->bitmap_ctrl & 0x01; + rtlpriv->psc.multi_buffered = m_buffed; + + /* unicast will process by mac80211 through + * set ~IEEE80211_CONF_PS, So we just check + * multicast frames here */ + if (!m_buffed) { + /* back to low-power land. and delay is + * prevent null power save frame tx fail */ + queue_delayed_work(rtlpriv->works.rtl_wq, + &rtlpriv->works.ps_work, MSECS(5)); + } else { + RT_TRACE(rtlpriv, COMP_POWER, DBG_DMESG, + "u_bufferd: %x, m_buffered: %x\n", u_buffed, m_buffed); + } +} + +void rtl_swlps_rf_awake(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + + if (!rtlpriv->psc.swctrl_lps) + return; + if (mac->link_state != MAC80211_LINKED) + return; + + if (ppsc->reg_rfps_level & RT_RF_LPS_LEVEL_ASPM && + RT_IN_PS_LEVEL(ppsc, RT_PS_LEVEL_ASPM)) { + rtlpriv->intf_ops->disable_aspm(hw); + RT_CLEAR_PS_LEVEL(ppsc, RT_PS_LEVEL_ASPM); + } + + mutex_lock(&rtlpriv->locks.ps_mutex); + rtl_ps_set_rf_state(hw, ERFON, RF_CHANGE_BY_PS); + mutex_unlock(&rtlpriv->locks.ps_mutex); +} + +void rtl_swlps_rfon_wq_callback(void *data) +{ + struct rtl_works *rtlworks = + container_of_dwork_rtl(data, struct rtl_works, ps_rfon_wq); + struct ieee80211_hw *hw = rtlworks->hw; + + rtl_swlps_rf_awake(hw); +} + +void rtl_swlps_rf_sleep(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + u8 sleep_intv; + + if (!rtlpriv->psc.sw_ps_enabled) + return; + + if ((rtlpriv->sec.being_setkey) || + (mac->opmode == NL80211_IFTYPE_ADHOC)) + return; + + /*sleep after linked 10s, to let DHCP and 4-way handshake ok enough!! */ + if ((mac->link_state != MAC80211_LINKED) || (mac->cnt_after_linked < 5)) + return; + + if (rtlpriv->link_info.busytraffic) + return; + + mutex_lock(&rtlpriv->locks.ps_mutex); + rtl_ps_set_rf_state(hw, ERFSLEEP, RF_CHANGE_BY_PS); + mutex_unlock(&rtlpriv->locks.ps_mutex); + + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_ASPM && + !RT_IN_PS_LEVEL(ppsc, RT_PS_LEVEL_ASPM)) { + rtlpriv->intf_ops->enable_aspm(hw); + RT_SET_PS_LEVEL(ppsc, RT_PS_LEVEL_ASPM); + } + + /* here is power save alg, when this beacon is DTIM + * we will set sleep time to dtim_period * n; + * when this beacon is not DTIM, we will set sleep + * time to sleep_intv = rtlpriv->psc.dtim_counter or + * MAX_SW_LPS_SLEEP_INTV(default set to 5) */ + + if (rtlpriv->psc.dtim_counter == 0) { + if (hw->conf.ps_dtim_period == 1) + sleep_intv = hw->conf.ps_dtim_period * 2; + else + sleep_intv = hw->conf.ps_dtim_period; + } else { + sleep_intv = rtlpriv->psc.dtim_counter; + } + + if (sleep_intv > MAX_SW_LPS_SLEEP_INTV) + sleep_intv = MAX_SW_LPS_SLEEP_INTV; + + /* this print should always be dtim_conter = 0 & + * sleep = dtim_period, that meaons, we should + * awake before every dtim */ + RT_TRACE(rtlpriv, COMP_POWER, DBG_DMESG, + "dtim_counter:%x will sleep :%d beacon_intv\n", + rtlpriv->psc.dtim_counter, sleep_intv); + + /* we tested that 40ms is enough for sw & hw sw delay */ + queue_delayed_work(rtlpriv->works.rtl_wq, &rtlpriv->works.ps_rfon_wq, + MSECS(sleep_intv * mac->vif->bss_conf.beacon_int - 40)); +} + +void rtl_lps_change_work_callback(struct work_struct *work) +{ + struct rtl_works *rtlworks = + container_of(work, struct rtl_works, lps_change_work); + struct ieee80211_hw *hw = rtlworks->hw; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (rtlpriv->enter_ps) + rtl_lps_enter(hw); + else + rtl_lps_leave(hw); +} + +void rtl_swlps_wq_callback(void *data) +{ + struct rtl_works *rtlworks = container_of_dwork_rtl(data, + struct rtl_works, + ps_work); + struct ieee80211_hw *hw = rtlworks->hw; + struct rtl_priv *rtlpriv = rtl_priv(hw); + bool ps = false; + + ps = (hw->conf.flags & IEEE80211_CONF_PS); + + /* we can sleep after ps null send ok */ + if (rtlpriv->psc.state_inap) { + rtl_swlps_rf_sleep(hw); + + if (rtlpriv->psc.state && !ps) { + rtlpriv->psc.sleep_ms = jiffies_to_msecs(jiffies - + rtlpriv->psc.last_action); + } + + if (ps) + rtlpriv->psc.last_slept = jiffies; + + rtlpriv->psc.last_action = jiffies; + rtlpriv->psc.state = ps; + } +} + +static void rtl_p2p_noa_ie(struct ieee80211_hw *hw, void *data, + unsigned int len) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct ieee80211_mgmt *mgmt = (void *)data; + struct rtl_p2p_ps_info *p2pinfo = &(rtlpriv->psc.p2p_ps_info); + u8 *pos, *end, *ie; + u16 noa_len; + static u8 p2p_oui_ie_type[4] = {0x50, 0x6f, 0x9a, 0x09}; + u8 noa_num, index, i, noa_index = 0; + bool find_p2p_ie = false , find_p2p_ps_ie = false; + pos = (u8 *)mgmt->u.beacon.variable; + end = data + len; + ie = NULL; + + while (pos + 1 < end) { + if (pos + 2 + pos[1] > end) + return; + + if (pos[0] == 221 && pos[1] > 4) { + if (memcmp(&pos[2], p2p_oui_ie_type, 4) == 0) { + ie = pos + 2+4; + break; + } + } + pos += 2 + pos[1]; + } + + if (ie == NULL) + return; + find_p2p_ie = true; + /*to find noa ie*/ + while (ie + 1 < end) { + noa_len = READEF2BYTE(&ie[1]); + if (ie + 3 + ie[1] > end) + return; + + if (ie[0] == 12) { + find_p2p_ps_ie = true; + if ((noa_len - 2) % 13 != 0) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "P2P notice of absence: invalid length.%d\n", + noa_len); + return; + } else { + noa_num = (noa_len - 2) / 13; + } + noa_index = ie[3]; + if (rtlpriv->psc.p2p_ps_info.p2p_ps_mode == + P2P_PS_NONE || noa_index != p2pinfo->noa_index) { + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, + "update NOA ie.\n"); + p2pinfo->noa_index = noa_index; + p2pinfo->opp_ps = (ie[4] >> 7); + p2pinfo->ctwindow = ie[4] & 0x7F; + p2pinfo->noa_num = noa_num; + index = 5; + for (i = 0; i < noa_num; i++) { + p2pinfo->noa_count_type[i] = + READEF1BYTE(ie+index); + index += 1; + p2pinfo->noa_duration[i] = + READEF4BYTE(ie+index); + index += 4; + p2pinfo->noa_interval[i] = + READEF4BYTE(ie+index); + index += 4; + p2pinfo->noa_start_time[i] = + READEF4BYTE(ie+index); + index += 4; + } + + if (p2pinfo->opp_ps == 1) { + p2pinfo->p2p_ps_mode = P2P_PS_CTWINDOW; + /* Driver should wait LPS entering + * CTWindow + */ + if (rtlpriv->psc.fw_current_inpsmode) + rtl_p2p_ps_cmd(hw, + P2P_PS_ENABLE); + } else if (p2pinfo->noa_num > 0) { + p2pinfo->p2p_ps_mode = P2P_PS_NOA; + rtl_p2p_ps_cmd(hw, P2P_PS_ENABLE); + } else if (p2pinfo->p2p_ps_mode > P2P_PS_NONE) { + rtl_p2p_ps_cmd(hw, P2P_PS_DISABLE); + } + } + break; + } + ie += 3 + noa_len; + } + + if (find_p2p_ie == true) { + if ((p2pinfo->p2p_ps_mode > P2P_PS_NONE) && + (find_p2p_ps_ie == false)) + rtl_p2p_ps_cmd(hw, P2P_PS_DISABLE); + } +} + +static void rtl_p2p_action_ie(struct ieee80211_hw *hw, void *data, + unsigned int len) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct ieee80211_mgmt *mgmt = (void *)data; + struct rtl_p2p_ps_info *p2pinfo = &(rtlpriv->psc.p2p_ps_info); + u8 noa_num, index, i, noa_index = 0; + u8 *pos, *end, *ie; + u16 noa_len; + static u8 p2p_oui_ie_type[4] = {0x50, 0x6f, 0x9a, 0x09}; + + pos = (u8 *)&mgmt->u.action.category; + end = data + len; + ie = NULL; + + if (pos[0] == 0x7f) { + if (memcmp(&pos[1], p2p_oui_ie_type, 4) == 0) + ie = pos + 3+4; + } + + if (ie == NULL) + return; + + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, "action frame find P2P IE.\n"); + /*to find noa ie*/ + while (ie + 1 < end) { + noa_len = READEF2BYTE(&ie[1]); + if (ie + 3 + ie[1] > end) + return; + + if (ie[0] == 12) { + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, "find NOA IE.\n"); + RT_PRINT_DATA(rtlpriv, COMP_FW, DBG_LOUD, "noa ie ", + ie, noa_len); + if ((noa_len - 2) % 13 != 0) { + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, + "P2P notice of absence: invalid length.%d\n", + noa_len); + return; + } else { + noa_num = (noa_len - 2) / 13; + } + noa_index = ie[3]; + if (rtlpriv->psc.p2p_ps_info.p2p_ps_mode == + P2P_PS_NONE || noa_index != p2pinfo->noa_index) { + p2pinfo->noa_index = noa_index; + p2pinfo->opp_ps = (ie[4] >> 7); + p2pinfo->ctwindow = ie[4] & 0x7F; + p2pinfo->noa_num = noa_num; + index = 5; + for (i = 0; i < noa_num; i++) { + p2pinfo->noa_count_type[i] = + READEF1BYTE(ie+index); + index += 1; + p2pinfo->noa_duration[i] = + READEF4BYTE(ie+index); + index += 4; + p2pinfo->noa_interval[i] = + READEF4BYTE(ie+index); + index += 4; + p2pinfo->noa_start_time[i] = + READEF4BYTE(ie+index); + index += 4; + } + + if (p2pinfo->opp_ps == 1) { + p2pinfo->p2p_ps_mode = P2P_PS_CTWINDOW; + /* Driver should wait LPS entering + * CTWindow + */ + if (rtlpriv->psc.fw_current_inpsmode) + rtl_p2p_ps_cmd(hw, + P2P_PS_ENABLE); + } else if (p2pinfo->noa_num > 0) { + p2pinfo->p2p_ps_mode = P2P_PS_NOA; + rtl_p2p_ps_cmd(hw, P2P_PS_ENABLE); + } else if (p2pinfo->p2p_ps_mode > P2P_PS_NONE) { + rtl_p2p_ps_cmd(hw, P2P_PS_DISABLE); + } + } + break; + } + ie += 3 + noa_len; + } +} + +void rtl_p2p_ps_cmd(struct ieee80211_hw *hw, u8 p2p_ps_state) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *rtlps = rtl_psc(rtl_priv(hw)); + struct rtl_p2p_ps_info *p2pinfo = &(rtlpriv->psc.p2p_ps_info); + + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, " p2p state %x\n", p2p_ps_state); + switch (p2p_ps_state) { + case P2P_PS_DISABLE: + p2pinfo->p2p_ps_state = p2p_ps_state; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_H2C_FW_P2P_PS_OFFLOAD, + (u8 *)(&p2p_ps_state)); + + p2pinfo->noa_index = 0; + p2pinfo->ctwindow = 0; + p2pinfo->opp_ps = 0; + p2pinfo->noa_num = 0; + p2pinfo->p2p_ps_mode = P2P_PS_NONE; + if (rtlps->fw_current_inpsmode == true) { + if (rtlps->smart_ps == 0) { + rtlps->smart_ps = 2; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_H2C_FW_PWRMODE, + (u8 *)(&rtlps->pwr_mode)); + } + } + break; + case P2P_PS_ENABLE: + if (p2pinfo->p2p_ps_mode > P2P_PS_NONE) { + p2pinfo->p2p_ps_state = p2p_ps_state; + + if (p2pinfo->ctwindow > 0) { + if (rtlps->smart_ps != 0) { + rtlps->smart_ps = 0; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_H2C_FW_PWRMODE, + (u8 *)(&rtlps->pwr_mode)); + } + } + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_H2C_FW_P2P_PS_OFFLOAD, + (u8 *)(&p2p_ps_state)); + } + break; + case P2P_PS_SCAN: + case P2P_PS_SCAN_DONE: + case P2P_PS_ALLSTASLEEP: + if (p2pinfo->p2p_ps_mode > P2P_PS_NONE) { + p2pinfo->p2p_ps_state = p2p_ps_state; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_H2C_FW_P2P_PS_OFFLOAD, + (u8 *)(&p2p_ps_state)); + } + break; + default: + break; + } + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, + "ctwindow %x oppps %x\n", p2pinfo->ctwindow, p2pinfo->opp_ps); + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, + "count %x duration %x index %x interval %x start time %x noa num %x\n", + p2pinfo->noa_count_type[0], p2pinfo->noa_duration[0], + p2pinfo->noa_index, p2pinfo->noa_interval[0], + p2pinfo->noa_start_time[0], p2pinfo->noa_num); + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, "end\n"); +} + +void rtl_p2p_info(struct ieee80211_hw *hw, void *data, unsigned int len) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct ieee80211_hdr *hdr = (void *)data; + + if (!mac->p2p) + return; + if (mac->link_state != MAC80211_LINKED) + return; + /* min. beacon length + FCS_LEN */ + if (len <= 40 + FCS_LEN) + return; + + /* and only beacons from the associated BSSID, please */ + if (compare_ether_addr(hdr->addr3, rtlpriv->mac80211.bssid)) + return; + + /* check if this really is a beacon */ + if (!(ieee80211_is_beacon(hdr->frame_control) || + ieee80211_is_probe_resp(hdr->frame_control) || + ieee80211_is_action(hdr->frame_control))) + return; + + if (ieee80211_is_action(hdr->frame_control)) + rtl_p2p_action_ie(hw, data, len - FCS_LEN); + else + rtl_p2p_noa_ie(hw, data, len - FCS_LEN); +} diff --git a/drivers/net/wireless/rtlwifi/ps.h b/drivers/net/wireless/rtlwifi/ps.h new file mode 100644 index 0000000..88bd76e --- /dev/null +++ b/drivers/net/wireless/rtlwifi/ps.h @@ -0,0 +1,54 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __REALTEK_RTL_PCI_PS_H__ +#define __REALTEK_RTL_PCI_PS_H__ + +#define MAX_SW_LPS_SLEEP_INTV 5 + +bool rtl_ps_set_rf_state(struct ieee80211_hw *hw, + enum rf_pwrstate state_toset, u32 changesource); +bool rtl_ps_enable_nic(struct ieee80211_hw *hw); +bool rtl_ps_disable_nic(struct ieee80211_hw *hw); +void rtl_ips_nic_off(struct ieee80211_hw *hw); +void rtl_ips_nic_on(struct ieee80211_hw *hw); +void rtl_ips_nic_off_wq_callback(void *data); +void rtl_lps_enter(struct ieee80211_hw *hw); +void rtl_lps_leave(struct ieee80211_hw *hw); + +void rtl_swlps_beacon(struct ieee80211_hw *hw, void *data, unsigned int len); +void rtl_swlps_wq_callback(void *data); +void rtl_swlps_rfon_wq_callback(void *data); +void rtl_swlps_rf_awake(struct ieee80211_hw *hw); +void rtl_swlps_rf_sleep(struct ieee80211_hw *hw); +void rtl_p2p_ps_cmd(struct ieee80211_hw *hw, u8 p2p_ps_state); +void rtl_p2p_info(struct ieee80211_hw *hw, void *data, unsigned int len); +void rtl_lps_change_work_callback(struct work_struct *work); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rc.c b/drivers/net/wireless/rtlwifi/rc.c new file mode 100644 index 0000000..f9f059d --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rc.c @@ -0,0 +1,282 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "wifi.h" +#include "base.h" +#include "rc.h" + +/* + *Finds the highest rate index we can use + *if skb is special data like DHCP/EAPOL, we set should + *it to lowest rate CCK_1M, otherwise we set rate to + *CCK11M or OFDM_54M based on wireless mode. + */ +static u8 _rtl_rc_get_highest_rix(struct rtl_priv *rtlpriv, + struct ieee80211_sta *sta, + struct sk_buff *skb, bool not_data) +{ + struct rtl_mac *rtlmac = rtl_mac(rtlpriv); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_sta_info *sta_entry = NULL; + u8 wireless_mode = 0; + + /* + *this rate is no use for true rate, firmware + *will control rate at all it just used for + *1.show in iwconfig in B/G mode + *2.in rtl_get_tcb_desc when we check rate is + * 1M we will not use FW rate but user rate. + */ + if (rtlmac->opmode == NL80211_IFTYPE_AP || + rtlmac->opmode == NL80211_IFTYPE_ADHOC || + rtlmac->opmode == NL80211_IFTYPE_MESH_POINT) { + if (sta) { + sta_entry = (struct rtl_sta_info *) sta->drv_priv; + wireless_mode = sta_entry->wireless_mode; + } else { + return 0; + } + } else { + wireless_mode = rtlmac->mode; + } + + if (rtl_is_special_data(rtlpriv->mac80211.hw, skb, true) || + not_data) { + return 0; + } else { + if (rtlhal->current_bandtype == BAND_ON_2_4G) { + if (wireless_mode == WIRELESS_MODE_B) { + return B_MODE_MAX_RIX; + } else if (wireless_mode == WIRELESS_MODE_G) { + return G_MODE_MAX_RIX; + } else { + if (get_rf_type(rtlphy) != RF_2T2R) + return N_MODE_MCS7_RIX; + else + return N_MODE_MCS15_RIX; + } + } else { + if (wireless_mode == WIRELESS_MODE_A) { + return A_MODE_MAX_RIX; + } else { + if (get_rf_type(rtlphy) != RF_2T2R) + return N_MODE_MCS7_RIX; + else + return N_MODE_MCS15_RIX; + } + } + } +} + +static void _rtl_rc_rate_set_series(struct rtl_priv *rtlpriv, + struct ieee80211_sta *sta, + struct ieee80211_tx_rate *rate, + struct ieee80211_tx_rate_control *txrc, + u8 tries, char rix, int rtsctsenable, + bool not_data) +{ + struct rtl_mac *mac = rtl_mac(rtlpriv); + u8 sgi_20 = 0, sgi_40 = 0; + + if (sta) { + sgi_20 = sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_20; + sgi_40 = sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_40; + } + rate->count = tries; + rate->idx = rix >= 0x00 ? rix : 0x00; + + if (!not_data) { + if (txrc->short_preamble) + rate->flags |= IEEE80211_TX_RC_USE_SHORT_PREAMBLE; + if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_ADHOC) { + if (sta && (sta->bandwidth >= IEEE80211_STA_RX_BW_40)) + rate->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; + } else { + if (mac->bw_40) + rate->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; + } + if (sgi_20 || sgi_40) + rate->flags |= IEEE80211_TX_RC_SHORT_GI; + if (sta && sta->ht_cap.ht_supported) + rate->flags |= IEEE80211_TX_RC_MCS; + } +} + +static void rtl_get_rate(void *ppriv, struct ieee80211_sta *sta, + void *priv_sta, struct ieee80211_tx_rate_control *txrc) +{ + struct rtl_priv *rtlpriv = ppriv; + struct sk_buff *skb = txrc->skb; + struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + struct ieee80211_tx_rate *rates = tx_info->control.rates; + __le16 fc = rtl_get_fc(skb); + u8 try_per_rate, i, rix; + bool not_data = !ieee80211_is_data(fc); + + if (rate_control_send_low(sta, priv_sta, txrc)) + return; + + rix = _rtl_rc_get_highest_rix(rtlpriv, sta, skb, not_data); + try_per_rate = 1; + _rtl_rc_rate_set_series(rtlpriv, sta, &rates[0], txrc, + try_per_rate, rix, 1, not_data); + + if (!not_data) { + for (i = 1; i < 4; i++) + _rtl_rc_rate_set_series(rtlpriv, sta, &rates[i], + txrc, i, (rix - i), 1, + not_data); + } +} + +static bool _rtl_tx_aggr_check(struct rtl_priv *rtlpriv, + struct rtl_sta_info *sta_entry, u16 tid) +{ + struct rtl_mac *mac = rtl_mac(rtlpriv); + + if (mac->act_scanning) + return false; + + if (mac->opmode == NL80211_IFTYPE_STATION && + mac->cnt_after_linked < 3) + return false; + + if (sta_entry->tids[tid].agg.agg_state == RTL_AGG_STOP) + return true; + + return false; +} + +/*mac80211 Rate Control callbacks*/ +static void rtl_tx_status(void *ppriv, + struct ieee80211_supported_band *sband, + struct ieee80211_sta *sta, void *priv_sta, + struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = ppriv; + struct rtl_mac *mac = rtl_mac(rtlpriv); + struct ieee80211_hdr *hdr = rtl_get_hdr(skb); + __le16 fc = rtl_get_fc(skb); + struct rtl_sta_info *sta_entry; + + if (!priv_sta || !ieee80211_is_data(fc)) + return; + + if (rtl_is_special_data(mac->hw, skb, true)) + return; + + if (is_multicast_ether_addr(ieee80211_get_DA(hdr)) + || is_broadcast_ether_addr(ieee80211_get_DA(hdr))) + return; + + if (sta) { + /* Check if aggregation has to be enabled for this tid */ + sta_entry = (struct rtl_sta_info *) sta->drv_priv; + if ((sta->ht_cap.ht_supported) && + !(skb->protocol == cpu_to_be16(ETH_P_PAE))) { + if (ieee80211_is_data_qos(fc)) { + u8 tid = rtl_get_tid(skb); + if (_rtl_tx_aggr_check(rtlpriv, sta_entry, + tid)) { + sta_entry->tids[tid].agg.agg_state = + RTL_AGG_PROGRESS; + ieee80211_start_tx_ba_session(sta, + tid, 5000); + } + } + } + } +} + +static void rtl_rate_init(void *ppriv, + struct ieee80211_supported_band *sband, + struct ieee80211_sta *sta, void *priv_sta) +{ +} + +static void *rtl_rate_alloc(struct ieee80211_hw *hw, + struct dentry *debugfsdir) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + return rtlpriv; +} + +static void rtl_rate_free(void *rtlpriv) +{ + return; +} + +static void *rtl_rate_alloc_sta(void *ppriv, + struct ieee80211_sta *sta, gfp_t gfp) +{ + struct rtl_priv *rtlpriv = ppriv; + struct rtl_rate_priv *rate_priv; + + rate_priv = kzalloc(sizeof(struct rtl_rate_priv), gfp); + if (!rate_priv) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Unable to allocate private rc structure\n"); + return NULL; + } + + rtlpriv->rate_priv = rate_priv; + + return rate_priv; +} + +static void rtl_rate_free_sta(void *rtlpriv, + struct ieee80211_sta *sta, void *priv_sta) +{ + struct rtl_rate_priv *rate_priv = priv_sta; + kfree(rate_priv); +} + +static struct rate_control_ops rtl_rate_ops = { + .module = NULL, + .name = "rtl_rc", + .alloc = rtl_rate_alloc, + .free = rtl_rate_free, + .alloc_sta = rtl_rate_alloc_sta, + .free_sta = rtl_rate_free_sta, + .rate_init = rtl_rate_init, + .tx_status = rtl_tx_status, + .get_rate = rtl_get_rate, +}; + +int rtl_rate_control_register(void) +{ + return ieee80211_rate_control_register(&rtl_rate_ops); +} + +void rtl_rate_control_unregister(void) +{ + ieee80211_rate_control_unregister(&rtl_rate_ops); +} diff --git a/drivers/net/wireless/rtlwifi/rc.h b/drivers/net/wireless/rtlwifi/rc.h new file mode 100644 index 0000000..4d61761 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rc.h @@ -0,0 +1,47 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL_RC_H__ +#define __RTL_RC_H__ + +#define B_MODE_MAX_RIX 3 +#define G_MODE_MAX_RIX 11 +#define A_MODE_MAX_RIX 7 + +/* in mac80211 mcs0-mcs15 is idx0-idx15*/ +#define N_MODE_MCS7_RIX 7 +#define N_MODE_MCS15_RIX 15 + +struct rtl_rate_priv { + u8 ht_cap; +}; + +int rtl_rate_control_register(void); +void rtl_rate_control_unregister(void); +#endif diff --git a/drivers/net/wireless/rtlwifi/regd.c b/drivers/net/wireless/rtlwifi/regd.c new file mode 100644 index 0000000..d7d0d49 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/regd.c @@ -0,0 +1,430 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "wifi.h" +#include "regd.h" + +static struct country_code_to_enum_rd allCountries[] = { + {COUNTRY_CODE_FCC, "US"}, + {COUNTRY_CODE_IC, "US"}, + {COUNTRY_CODE_ETSI, "EC"}, + {COUNTRY_CODE_SPAIN, "EC"}, + {COUNTRY_CODE_FRANCE, "EC"}, + {COUNTRY_CODE_MKK, "JP"}, + {COUNTRY_CODE_MKK1, "JP"}, + {COUNTRY_CODE_ISRAEL, "EC"}, + {COUNTRY_CODE_TELEC, "JP"}, + {COUNTRY_CODE_MIC, "JP"}, + {COUNTRY_CODE_GLOBAL_DOMAIN, "JP"}, + {COUNTRY_CODE_WORLD_WIDE_13, "EC"}, + {COUNTRY_CODE_TELEC_NETGEAR, "EC"}, +}; + +/* + *Only these channels all allow active + *scan on all world regulatory domains + */ +#define RTL819x_2GHZ_CH01_11 \ + REG_RULE(2412-10, 2462+10, 40, 0, 20, 0) + +/* + *We enable active scan on these a case + *by case basis by regulatory domain + */ +#define RTL819x_2GHZ_CH12_13 \ + REG_RULE(2467-10, 2472+10, 40, 0, 20,\ + NL80211_RRF_PASSIVE_SCAN) + +#define RTL819x_2GHZ_CH14 \ + REG_RULE(2484-10, 2484+10, 40, 0, 20, \ + NL80211_RRF_PASSIVE_SCAN | \ + NL80211_RRF_NO_OFDM) + +/* 5G chan 36 - chan 64*/ +#define RTL819x_5GHZ_5150_5350 \ + REG_RULE(5150-10, 5350+10, 40, 0, 30, \ + NL80211_RRF_PASSIVE_SCAN | \ + NL80211_RRF_NO_IBSS) + +/* 5G chan 100 - chan 165*/ +#define RTL819x_5GHZ_5470_5850 \ + REG_RULE(5470-10, 5850+10, 40, 0, 30, \ + NL80211_RRF_PASSIVE_SCAN | \ + NL80211_RRF_NO_IBSS) + +/* 5G chan 149 - chan 165*/ +#define RTL819x_5GHZ_5725_5850 \ + REG_RULE(5725-10, 5850+10, 40, 0, 30, \ + NL80211_RRF_PASSIVE_SCAN | \ + NL80211_RRF_NO_IBSS) + +#define RTL819x_5GHZ_ALL \ + (RTL819x_5GHZ_5150_5350, RTL819x_5GHZ_5470_5850) + +static const struct ieee80211_regdomain rtl_regdom_11 = { + .n_reg_rules = 1, + .alpha2 = "99", + .reg_rules = { + RTL819x_2GHZ_CH01_11, + } +}; + +static const struct ieee80211_regdomain rtl_regdom_12_13 = { + .n_reg_rules = 2, + .alpha2 = "99", + .reg_rules = { + RTL819x_2GHZ_CH01_11, + RTL819x_2GHZ_CH12_13, + } +}; + +static const struct ieee80211_regdomain rtl_regdom_no_midband = { + .n_reg_rules = 3, + .alpha2 = "99", + .reg_rules = { + RTL819x_2GHZ_CH01_11, + RTL819x_5GHZ_5150_5350, + RTL819x_5GHZ_5725_5850, + } +}; + +static const struct ieee80211_regdomain rtl_regdom_60_64 = { + .n_reg_rules = 3, + .alpha2 = "99", + .reg_rules = { + RTL819x_2GHZ_CH01_11, + RTL819x_2GHZ_CH12_13, + RTL819x_5GHZ_5725_5850, + } +}; + +static const struct ieee80211_regdomain rtl_regdom_14_60_64 = { + .n_reg_rules = 4, + .alpha2 = "99", + .reg_rules = { + RTL819x_2GHZ_CH01_11, + RTL819x_2GHZ_CH12_13, + RTL819x_2GHZ_CH14, + RTL819x_5GHZ_5725_5850, + } +}; + +static const struct ieee80211_regdomain rtl_regdom_14 = { + .n_reg_rules = 3, + .alpha2 = "99", + .reg_rules = { + RTL819x_2GHZ_CH01_11, + RTL819x_2GHZ_CH12_13, + RTL819x_2GHZ_CH14, + } +}; + +static bool _rtl_is_radar_freq(u16 center_freq) +{ + return (center_freq >= 5260 && center_freq <= 5700); +} + +static void _rtl_reg_apply_beaconing_flags(struct wiphy *wiphy, + enum nl80211_reg_initiator initiator) +{ + enum ieee80211_band band; + struct ieee80211_supported_band *sband; + const struct ieee80211_reg_rule *reg_rule; + struct ieee80211_channel *ch; + unsigned int i; + + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { + + if (!wiphy->bands[band]) + continue; + + sband = wiphy->bands[band]; + + for (i = 0; i < sband->n_channels; i++) { + ch = &sband->channels[i]; + if (_rtl_is_radar_freq(ch->center_freq) || + (ch->flags & IEEE80211_CHAN_RADAR)) + continue; + if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) { + reg_rule = freq_reg_info(wiphy, ch->center_freq); + if (IS_ERR(reg_rule)) + continue; + + /* + *If 11d had a rule for this channel ensure + *we enable adhoc/beaconing if it allows us to + *use it. Note that we would have disabled it + *by applying our static world regdomain by + *default during init, prior to calling our + *regulatory_hint(). + */ + + if (!(reg_rule->flags & NL80211_RRF_NO_IBSS)) + ch->flags &= ~IEEE80211_CHAN_NO_IBSS; + if (!(reg_rule-> + flags & NL80211_RRF_PASSIVE_SCAN)) + ch->flags &= + ~IEEE80211_CHAN_PASSIVE_SCAN; + } else { + if (ch->beacon_found) + ch->flags &= ~(IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN); + } + } + } +} + +/* Allows active scan scan on Ch 12 and 13 */ +static void _rtl_reg_apply_active_scan_flags(struct wiphy *wiphy, + enum nl80211_reg_initiator + initiator) +{ + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + const struct ieee80211_reg_rule *reg_rule; + + if (!wiphy->bands[IEEE80211_BAND_2GHZ]) + return; + sband = wiphy->bands[IEEE80211_BAND_2GHZ]; + + /* + *If no country IE has been received always enable active scan + *on these channels. This is only done for specific regulatory SKUs + */ + if (initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) { + ch = &sband->channels[11]; /* CH 12 */ + if (ch->flags & IEEE80211_CHAN_PASSIVE_SCAN) + ch->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN; + ch = &sband->channels[12]; /* CH 13 */ + if (ch->flags & IEEE80211_CHAN_PASSIVE_SCAN) + ch->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN; + return; + } + + /* + *If a country IE has been received check its rule for this + *channel first before enabling active scan. The passive scan + *would have been enforced by the initial processing of our + *custom regulatory domain. + */ + + ch = &sband->channels[11]; /* CH 12 */ + reg_rule = freq_reg_info(wiphy, ch->center_freq); + if (!IS_ERR(reg_rule)) { + if (!(reg_rule->flags & NL80211_RRF_PASSIVE_SCAN)) + if (ch->flags & IEEE80211_CHAN_PASSIVE_SCAN) + ch->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN; + } + + ch = &sband->channels[12]; /* CH 13 */ + reg_rule = freq_reg_info(wiphy, ch->center_freq); + if (!IS_ERR(reg_rule)) { + if (!(reg_rule->flags & NL80211_RRF_PASSIVE_SCAN)) + if (ch->flags & IEEE80211_CHAN_PASSIVE_SCAN) + ch->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN; + } +} + +/* + *Always apply Radar/DFS rules on + *freq range 5260 MHz - 5700 MHz + */ +static void _rtl_reg_apply_radar_flags(struct wiphy *wiphy) +{ + struct ieee80211_supported_band *sband; + struct ieee80211_channel *ch; + unsigned int i; + + if (!wiphy->bands[IEEE80211_BAND_5GHZ]) + return; + + sband = wiphy->bands[IEEE80211_BAND_5GHZ]; + + for (i = 0; i < sband->n_channels; i++) { + ch = &sband->channels[i]; + if (!_rtl_is_radar_freq(ch->center_freq)) + continue; + + /* + *We always enable radar detection/DFS on this + *frequency range. Additionally we also apply on + *this frequency range: + *- If STA mode does not yet have DFS supports disable + * active scanning + *- If adhoc mode does not support DFS yet then disable + * adhoc in the frequency. + *- If AP mode does not yet support radar detection/DFS + *do not allow AP mode + */ + if (!(ch->flags & IEEE80211_CHAN_DISABLED)) + ch->flags |= IEEE80211_CHAN_RADAR | + IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN; + } +} + +static void _rtl_reg_apply_world_flags(struct wiphy *wiphy, + enum nl80211_reg_initiator initiator, + struct rtl_regulatory *reg) +{ + _rtl_reg_apply_beaconing_flags(wiphy, initiator); + _rtl_reg_apply_active_scan_flags(wiphy, initiator); + return; +} + +static void _rtl_reg_notifier_apply(struct wiphy *wiphy, + struct regulatory_request *request, + struct rtl_regulatory *reg) +{ + /* We always apply this */ + _rtl_reg_apply_radar_flags(wiphy); + + switch (request->initiator) { + case NL80211_REGDOM_SET_BY_DRIVER: + case NL80211_REGDOM_SET_BY_CORE: + case NL80211_REGDOM_SET_BY_USER: + break; + case NL80211_REGDOM_SET_BY_COUNTRY_IE: + _rtl_reg_apply_world_flags(wiphy, request->initiator, reg); + break; + } +} + +static const struct ieee80211_regdomain *_rtl_regdomain_select( + struct rtl_regulatory *reg) +{ + switch (reg->country_code) { + case COUNTRY_CODE_FCC: + return &rtl_regdom_no_midband; + case COUNTRY_CODE_IC: + return &rtl_regdom_11; + case COUNTRY_CODE_ETSI: + case COUNTRY_CODE_TELEC_NETGEAR: + return &rtl_regdom_60_64; + case COUNTRY_CODE_SPAIN: + case COUNTRY_CODE_FRANCE: + case COUNTRY_CODE_ISRAEL: + case COUNTRY_CODE_WORLD_WIDE_13: + return &rtl_regdom_12_13; + case COUNTRY_CODE_MKK: + case COUNTRY_CODE_MKK1: + case COUNTRY_CODE_TELEC: + case COUNTRY_CODE_MIC: + return &rtl_regdom_14_60_64; + case COUNTRY_CODE_GLOBAL_DOMAIN: + return &rtl_regdom_14; + default: + return &rtl_regdom_no_midband; + } +} + +static int _rtl_regd_init_wiphy(struct rtl_regulatory *reg, + struct wiphy *wiphy, + void (*reg_notifier) (struct wiphy *wiphy, + struct regulatory_request * + request)) +{ + const struct ieee80211_regdomain *regd; + + wiphy->reg_notifier = reg_notifier; + + wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY; + wiphy->flags &= ~WIPHY_FLAG_STRICT_REGULATORY; + wiphy->flags &= ~WIPHY_FLAG_DISABLE_BEACON_HINTS; + + regd = _rtl_regdomain_select(reg); + wiphy_apply_custom_regulatory(wiphy, regd); + _rtl_reg_apply_radar_flags(wiphy); + _rtl_reg_apply_world_flags(wiphy, NL80211_REGDOM_SET_BY_DRIVER, reg); + return 0; +} + +static struct country_code_to_enum_rd *_rtl_regd_find_country(u16 countrycode) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(allCountries); i++) { + if (allCountries[i].countrycode == countrycode) + return &allCountries[i]; + } + return NULL; +} + +int rtl_regd_init(struct ieee80211_hw *hw, + void (*reg_notifier) (struct wiphy *wiphy, + struct regulatory_request *request)) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct wiphy *wiphy = hw->wiphy; + struct country_code_to_enum_rd *country = NULL; + + if (wiphy == NULL || &rtlpriv->regd == NULL) + return -EINVAL; + + /* init country_code from efuse channel plan */ + rtlpriv->regd.country_code = rtlpriv->efuse.channel_plan; + + RT_TRACE(rtlpriv, COMP_REGD, DBG_TRACE, + "rtl: EEPROM regdomain: 0x%0x\n", rtlpriv->regd.country_code); + + if (rtlpriv->regd.country_code >= COUNTRY_CODE_MAX) { + RT_TRACE(rtlpriv, COMP_REGD, DBG_DMESG, + "rtl: EEPROM indicates invalid contry code, world wide 13 should be used\n"); + + rtlpriv->regd.country_code = COUNTRY_CODE_WORLD_WIDE_13; + } + + country = _rtl_regd_find_country(rtlpriv->regd.country_code); + + if (country) { + rtlpriv->regd.alpha2[0] = country->iso_name[0]; + rtlpriv->regd.alpha2[1] = country->iso_name[1]; + } else { + rtlpriv->regd.alpha2[0] = '0'; + rtlpriv->regd.alpha2[1] = '0'; + } + + RT_TRACE(rtlpriv, COMP_REGD, DBG_TRACE, + "rtl: Country alpha2 being used: %c%c\n", + rtlpriv->regd.alpha2[0], rtlpriv->regd.alpha2[1]); + + _rtl_regd_init_wiphy(&rtlpriv->regd, wiphy, reg_notifier); + + return 0; +} + +void rtl_reg_notifier(struct wiphy *wiphy, struct regulatory_request *request) +{ + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct rtl_priv *rtlpriv = rtl_priv(hw); + + RT_TRACE(rtlpriv, COMP_REGD, DBG_LOUD, "\n"); + + _rtl_reg_notifier_apply(wiphy, request, &rtlpriv->regd); +} diff --git a/drivers/net/wireless/rtlwifi/regd.h b/drivers/net/wireless/rtlwifi/regd.h new file mode 100644 index 0000000..4e1f4f0 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/regd.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL_REGD_H__ +#define __RTL_REGD_H__ + +struct country_code_to_enum_rd { + u16 countrycode; + const char *iso_name; +}; + +enum country_code_type_t { + COUNTRY_CODE_FCC = 0, + COUNTRY_CODE_IC = 1, + COUNTRY_CODE_ETSI = 2, + COUNTRY_CODE_SPAIN = 3, + COUNTRY_CODE_FRANCE = 4, + COUNTRY_CODE_MKK = 5, + COUNTRY_CODE_MKK1 = 6, + COUNTRY_CODE_ISRAEL = 7, + COUNTRY_CODE_TELEC = 8, + COUNTRY_CODE_MIC = 9, + COUNTRY_CODE_GLOBAL_DOMAIN = 10, + COUNTRY_CODE_WORLD_WIDE_13 = 11, + COUNTRY_CODE_TELEC_NETGEAR = 12, + + /*add new channel plan above this line */ + COUNTRY_CODE_MAX +}; + +int rtl_regd_init(struct ieee80211_hw *hw, + void (*reg_notifier) (struct wiphy *wiphy, + struct regulatory_request *request)); +void rtl_reg_notifier(struct wiphy *wiphy, struct regulatory_request *request); +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/Makefile b/drivers/net/wireless/rtlwifi/rtl8188ee/Makefile new file mode 100644 index 0000000..caf9c68 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/Makefile @@ -0,0 +1,16 @@ +rtl8188ee-objs := \ + dm.o \ + fw.o \ + hw.o \ + led.o \ + phy.o \ + pwrseq.o \ + pwrseqcmd.o \ + rf.o \ + sw.o \ + table.o \ + trx.o + +obj-$(CPTCFG_RTL8188EE) += rtl8188ee.o + +ccflags-y += -I$(backport_srctree)/drivers/net/wireless/rtlwifi -D__CHECK_ENDIAN__ diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/def.h b/drivers/net/wireless/rtlwifi/rtl8188ee/def.h new file mode 100644 index 0000000..c764fff --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/def.h @@ -0,0 +1,324 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92C_DEF_H__ +#define __RTL92C_DEF_H__ + +#define HAL_RETRY_LIMIT_INFRA 48 +#define HAL_RETRY_LIMIT_AP_ADHOC 7 + +#define RESET_DELAY_8185 20 + +#define RT_IBSS_INT_MASKS (IMR_BCNINT | IMR_TBDOK | IMR_TBDER) +#define RT_AC_INT_MASKS (IMR_VIDOK | IMR_VODOK | IMR_BEDOK|IMR_BKDOK) + +#define NUM_OF_FIRMWARE_QUEUE 10 +#define NUM_OF_PAGES_IN_FW 0x100 +#define NUM_OF_PAGE_IN_FW_QUEUE_BK 0x07 +#define NUM_OF_PAGE_IN_FW_QUEUE_BE 0x07 +#define NUM_OF_PAGE_IN_FW_QUEUE_VI 0x07 +#define NUM_OF_PAGE_IN_FW_QUEUE_VO 0x07 +#define NUM_OF_PAGE_IN_FW_QUEUE_HCCA 0x0 +#define NUM_OF_PAGE_IN_FW_QUEUE_CMD 0x0 +#define NUM_OF_PAGE_IN_FW_QUEUE_MGNT 0x02 +#define NUM_OF_PAGE_IN_FW_QUEUE_HIGH 0x02 +#define NUM_OF_PAGE_IN_FW_QUEUE_BCN 0x2 +#define NUM_OF_PAGE_IN_FW_QUEUE_PUB 0xA1 + +#define NUM_OF_PAGE_IN_FW_QUEUE_BK_DTM 0x026 +#define NUM_OF_PAGE_IN_FW_QUEUE_BE_DTM 0x048 +#define NUM_OF_PAGE_IN_FW_QUEUE_VI_DTM 0x048 +#define NUM_OF_PAGE_IN_FW_QUEUE_VO_DTM 0x026 +#define NUM_OF_PAGE_IN_FW_QUEUE_PUB_DTM 0x00 + +#define MAX_LINES_HWCONFIG_TXT 1000 +#define MAX_BYTES_LINE_HWCONFIG_TXT 256 + +#define SW_THREE_WIRE 0 +#define HW_THREE_WIRE 2 + +#define BT_DEMO_BOARD 0 +#define BT_QA_BOARD 1 +#define BT_FPGA 2 + +#define HAL_PRIME_CHNL_OFFSET_DONT_CARE 0 +#define HAL_PRIME_CHNL_OFFSET_LOWER 1 +#define HAL_PRIME_CHNL_OFFSET_UPPER 2 + +#define MAX_H2C_QUEUE_NUM 10 + +#define RX_MPDU_QUEUE 0 +#define RX_CMD_QUEUE 1 +#define RX_MAX_QUEUE 2 +#define AC2QUEUEID(_AC) (_AC) + +#define C2H_RX_CMD_HDR_LEN 8 +#define GET_C2H_CMD_CMD_LEN(__prxhdr) \ + LE_BITS_TO_4BYTE((__prxhdr), 0, 16) +#define GET_C2H_CMD_ELEMENT_ID(__prxhdr) \ + LE_BITS_TO_4BYTE((__prxhdr), 16, 8) +#define GET_C2H_CMD_CMD_SEQ(__prxhdr) \ + LE_BITS_TO_4BYTE((__prxhdr), 24, 7) +#define GET_C2H_CMD_CONTINUE(__prxhdr) \ + LE_BITS_TO_4BYTE((__prxhdr), 31, 1) +#define GET_C2H_CMD_CONTENT(__prxhdr) \ + ((u8 *)(__prxhdr) + C2H_RX_CMD_HDR_LEN) + +#define GET_C2H_CMD_FEEDBACK_ELEMENT_ID(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE((__pcmdfbhdr), 0, 8) +#define GET_C2H_CMD_FEEDBACK_CCX_LEN(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE((__pcmdfbhdr), 8, 8) +#define GET_C2H_CMD_FEEDBACK_CCX_CMD_CNT(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE((__pcmdfbhdr), 16, 16) +#define GET_C2H_CMD_FEEDBACK_CCX_MAC_ID(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 0, 5) +#define GET_C2H_CMD_FEEDBACK_CCX_VALID(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 7, 1) +#define GET_C2H_CMD_FEEDBACK_CCX_RETRY_CNT(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 8, 5) +#define GET_C2H_CMD_FEEDBACK_CCX_TOK(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 15, 1) +#define GET_C2H_CMD_FEEDBACK_CCX_QSEL(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 16, 4) +#define GET_C2H_CMD_FEEDBACK_CCX_SEQ(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 20, 12) + +#define CHIP_BONDING_IDENTIFIER(_value) (((_value)>>22)&0x3) + + +/* [15:12] IC version(CUT): A-cut=0, B-cut=1, C-cut=2, D-cut=3 + * [7] Manufacturer: TSMC=0, UMC=1 + * [6:4] RF type: 1T1R=0, 1T2R=1, 2T2R=2 + * [3] Chip type: TEST=0, NORMAL=1 + * [2:0] IC type: 81xxC=0, 8723=1, 92D=2 + */ +#define CHIP_8723 BIT(0) +#define CHIP_92D BIT(1) +#define NORMAL_CHIP BIT(3) +#define RF_TYPE_1T1R (~(BIT(4)|BIT(5)|BIT(6))) +#define RF_TYPE_1T2R BIT(4) +#define RF_TYPE_2T2R BIT(5) +#define CHIP_VENDOR_UMC BIT(7) +#define B_CUT_VERSION BIT(12) +#define C_CUT_VERSION BIT(13) +#define D_CUT_VERSION ((BIT(12)|BIT(13))) +#define E_CUT_VERSION BIT(14) + + +/* MASK */ +#define IC_TYPE_MASK (BIT(0)|BIT(1)|BIT(2)) +#define CHIP_TYPE_MASK BIT(3) +#define RF_TYPE_MASK (BIT(4)|BIT(5)|BIT(6)) +#define MANUFACTUER_MASK BIT(7) +#define ROM_VERSION_MASK (BIT(11)|BIT(10)|BIT(9)|BIT(8)) +#define CUT_VERSION_MASK (BIT(15)|BIT(14)|BIT(13)|BIT(12)) + +/* Get element */ +#define GET_CVID_IC_TYPE(version) ((version) & IC_TYPE_MASK) +#define GET_CVID_CHIP_TYPE(version) ((version) & CHIP_TYPE_MASK) +#define GET_CVID_RF_TYPE(version) ((version) & RF_TYPE_MASK) +#define GET_CVID_MANUFACTUER(version) ((version) & MANUFACTUER_MASK) +#define GET_CVID_ROM_VERSION(version) ((version) & ROM_VERSION_MASK) +#define GET_CVID_CUT_VERSION(version) ((version) & CUT_VERSION_MASK) + + +#define IS_81XXC(version) \ + ((GET_CVID_IC_TYPE(version) == 0) ? true : false) +#define IS_8723_SERIES(version) \ + ((GET_CVID_IC_TYPE(version) == CHIP_8723) ? true : false) +#define IS_92D(version) \ + ((GET_CVID_IC_TYPE(version) == CHIP_92D) ? true : false) + +#define IS_NORMAL_CHIP(version) \ + ((GET_CVID_CHIP_TYPE(version)) ? true : false) +#define IS_NORMAL_CHIP92D(version) \ + ((GET_CVID_CHIP_TYPE(version)) ? true : false) + +#define IS_1T1R(version) \ + ((GET_CVID_RF_TYPE(version)) ? false : true) +#define IS_1T2R(version) \ + ((GET_CVID_RF_TYPE(version) == RF_TYPE_1T2R) ? true : false) +#define IS_2T2R(version) \ + ((GET_CVID_RF_TYPE(version) == RF_TYPE_2T2R) ? true : false) +#define IS_CHIP_VENDOR_UMC(version) \ + ((GET_CVID_MANUFACTUER(version)) ? true : false) + +#define IS_92C_SERIAL(version) \ + ((IS_81XXC(version) && IS_2T2R(version)) ? true : false) +#define IS_81xxC_VENDOR_UMC_A_CUT(version) \ + (IS_81XXC(version) ? ((IS_CHIP_VENDOR_UMC(version)) ? \ + ((GET_CVID_CUT_VERSION(version)) ? false : true) : false) : false) +#define IS_81xxC_VENDOR_UMC_B_CUT(version) \ + (IS_81XXC(version) ? (IS_CHIP_VENDOR_UMC(version) ? \ + ((GET_CVID_CUT_VERSION(version) == B_CUT_VERSION) ? true \ + : false) : false) : false) + +enum version_8188e { + VERSION_TEST_CHIP_88E = 0x00, + VERSION_NORMAL_CHIP_88E = 0x01, + VERSION_UNKNOWN = 0xFF, +}; + +enum rx_packet_type { + NORMAL_RX, + TX_REPORT1, + TX_REPORT2, + HIS_REPORT, +}; + +enum rtl819x_loopback_e { + RTL819X_NO_LOOPBACK = 0, + RTL819X_MAC_LOOPBACK = 1, + RTL819X_DMA_LOOPBACK = 2, + RTL819X_CCK_LOOPBACK = 3, +}; + +enum rf_optype { + RF_OP_BY_SW_3WIRE = 0, + RF_OP_BY_FW, + RF_OP_MAX +}; + +enum rf_power_state { + RF_ON, + RF_OFF, + RF_SLEEP, + RF_SHUT_DOWN, +}; + +enum power_save_mode { + POWER_SAVE_MODE_ACTIVE, + POWER_SAVE_MODE_SAVE, +}; + +enum power_polocy_config { + POWERCFG_MAX_POWER_SAVINGS, + POWERCFG_GLOBAL_POWER_SAVINGS, + POWERCFG_LOCAL_POWER_SAVINGS, + POWERCFG_LENOVO, +}; + +enum interface_select_pci { + INTF_SEL1_MINICARD, + INTF_SEL0_PCIE, + INTF_SEL2_RSV, + INTF_SEL3_RSV, +}; + +enum hal_fw_c2h_cmd_id { + HAL_FW_C2H_CMD_Read_MACREG, + HAL_FW_C2H_CMD_Read_BBREG, + HAL_FW_C2H_CMD_Read_RFREG, + HAL_FW_C2H_CMD_Read_EEPROM, + HAL_FW_C2H_CMD_Read_EFUSE, + HAL_FW_C2H_CMD_Read_CAM, + HAL_FW_C2H_CMD_Get_BasicRate, + HAL_FW_C2H_CMD_Get_DataRate, + HAL_FW_C2H_CMD_Survey, + HAL_FW_C2H_CMD_SurveyDone, + HAL_FW_C2H_CMD_JoinBss, + HAL_FW_C2H_CMD_AddSTA, + HAL_FW_C2H_CMD_DelSTA, + HAL_FW_C2H_CMD_AtimDone, + HAL_FW_C2H_CMD_TX_Report, + HAL_FW_C2H_CMD_CCX_Report, + HAL_FW_C2H_CMD_DTM_Report, + HAL_FW_C2H_CMD_TX_Rate_Statistics, + HAL_FW_C2H_CMD_C2HLBK, + HAL_FW_C2H_CMD_C2HDBG, + HAL_FW_C2H_CMD_C2HFEEDBACK, + HAL_FW_C2H_CMD_MAX +}; + +enum wake_on_wlan_mode { + ewowlandisable, + ewakeonmagicpacketonly, + ewakeonpatternmatchonly, + ewakeonbothtypepacket +}; + +enum rtl_desc_qsel { + QSLT_BK = 0x2, + QSLT_BE = 0x0, + QSLT_VI = 0x5, + QSLT_VO = 0x7, + QSLT_BEACON = 0x10, + QSLT_HIGH = 0x11, + QSLT_MGNT = 0x12, + QSLT_CMD = 0x13, +}; + +enum rtl_desc92c_rate { + DESC92C_RATE1M = 0x00, + DESC92C_RATE2M = 0x01, + DESC92C_RATE5_5M = 0x02, + DESC92C_RATE11M = 0x03, + + DESC92C_RATE6M = 0x04, + DESC92C_RATE9M = 0x05, + DESC92C_RATE12M = 0x06, + DESC92C_RATE18M = 0x07, + DESC92C_RATE24M = 0x08, + DESC92C_RATE36M = 0x09, + DESC92C_RATE48M = 0x0a, + DESC92C_RATE54M = 0x0b, + + DESC92C_RATEMCS0 = 0x0c, + DESC92C_RATEMCS1 = 0x0d, + DESC92C_RATEMCS2 = 0x0e, + DESC92C_RATEMCS3 = 0x0f, + DESC92C_RATEMCS4 = 0x10, + DESC92C_RATEMCS5 = 0x11, + DESC92C_RATEMCS6 = 0x12, + DESC92C_RATEMCS7 = 0x13, + DESC92C_RATEMCS8 = 0x14, + DESC92C_RATEMCS9 = 0x15, + DESC92C_RATEMCS10 = 0x16, + DESC92C_RATEMCS11 = 0x17, + DESC92C_RATEMCS12 = 0x18, + DESC92C_RATEMCS13 = 0x19, + DESC92C_RATEMCS14 = 0x1a, + DESC92C_RATEMCS15 = 0x1b, + DESC92C_RATEMCS15_SG = 0x1c, + DESC92C_RATEMCS32 = 0x20, +}; + +struct phy_sts_cck_8192s_t { + u8 adc_pwdb_X[4]; + u8 sq_rpt; + u8 cck_agc_rpt; +}; + +struct h2c_cmd_8192c { + u8 element_id; + u32 cmd_len; + u8 *p_cmdbuffer; +}; + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/dm.c b/drivers/net/wireless/rtlwifi/rtl8188ee/dm.c new file mode 100644 index 0000000..21a5cf0 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/dm.c @@ -0,0 +1,1794 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../base.h" +#include "../pci.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "dm.h" +#include "fw.h" +#include "trx.h" + +static const u32 ofdmswing_table[OFDM_TABLE_SIZE] = { + 0x7f8001fe, /* 0, +6.0dB */ + 0x788001e2, /* 1, +5.5dB */ + 0x71c001c7, /* 2, +5.0dB */ + 0x6b8001ae, /* 3, +4.5dB */ + 0x65400195, /* 4, +4.0dB */ + 0x5fc0017f, /* 5, +3.5dB */ + 0x5a400169, /* 6, +3.0dB */ + 0x55400155, /* 7, +2.5dB */ + 0x50800142, /* 8, +2.0dB */ + 0x4c000130, /* 9, +1.5dB */ + 0x47c0011f, /* 10, +1.0dB */ + 0x43c0010f, /* 11, +0.5dB */ + 0x40000100, /* 12, +0dB */ + 0x3c8000f2, /* 13, -0.5dB */ + 0x390000e4, /* 14, -1.0dB */ + 0x35c000d7, /* 15, -1.5dB */ + 0x32c000cb, /* 16, -2.0dB */ + 0x300000c0, /* 17, -2.5dB */ + 0x2d4000b5, /* 18, -3.0dB */ + 0x2ac000ab, /* 19, -3.5dB */ + 0x288000a2, /* 20, -4.0dB */ + 0x26000098, /* 21, -4.5dB */ + 0x24000090, /* 22, -5.0dB */ + 0x22000088, /* 23, -5.5dB */ + 0x20000080, /* 24, -6.0dB */ + 0x1e400079, /* 25, -6.5dB */ + 0x1c800072, /* 26, -7.0dB */ + 0x1b00006c, /* 27. -7.5dB */ + 0x19800066, /* 28, -8.0dB */ + 0x18000060, /* 29, -8.5dB */ + 0x16c0005b, /* 30, -9.0dB */ + 0x15800056, /* 31, -9.5dB */ + 0x14400051, /* 32, -10.0dB */ + 0x1300004c, /* 33, -10.5dB */ + 0x12000048, /* 34, -11.0dB */ + 0x11000044, /* 35, -11.5dB */ + 0x10000040, /* 36, -12.0dB */ + 0x0f00003c, /* 37, -12.5dB */ + 0x0e400039, /* 38, -13.0dB */ + 0x0d800036, /* 39, -13.5dB */ + 0x0cc00033, /* 40, -14.0dB */ + 0x0c000030, /* 41, -14.5dB */ + 0x0b40002d, /* 42, -15.0dB */ +}; + +static const u8 cck_tbl_ch1_13[CCK_TABLE_SIZE][8] = { + {0x36, 0x35, 0x2e, 0x25, 0x1c, 0x12, 0x09, 0x04}, /* 0, +0dB */ + {0x33, 0x32, 0x2b, 0x23, 0x1a, 0x11, 0x08, 0x04}, /* 1, -0.5dB */ + {0x30, 0x2f, 0x29, 0x21, 0x19, 0x10, 0x08, 0x03}, /* 2, -1.0dB */ + {0x2d, 0x2d, 0x27, 0x1f, 0x18, 0x0f, 0x08, 0x03}, /* 3, -1.5dB */ + {0x2b, 0x2a, 0x25, 0x1e, 0x16, 0x0e, 0x07, 0x03}, /* 4, -2.0dB */ + {0x28, 0x28, 0x22, 0x1c, 0x15, 0x0d, 0x07, 0x03}, /* 5, -2.5dB */ + {0x26, 0x25, 0x21, 0x1b, 0x14, 0x0d, 0x06, 0x03}, /* 6, -3.0dB */ + {0x24, 0x23, 0x1f, 0x19, 0x13, 0x0c, 0x06, 0x03}, /* 7, -3.5dB */ + {0x22, 0x21, 0x1d, 0x18, 0x11, 0x0b, 0x06, 0x02}, /* 8, -4.0dB */ + {0x20, 0x20, 0x1b, 0x16, 0x11, 0x08, 0x05, 0x02}, /* 9, -4.5dB */ + {0x1f, 0x1e, 0x1a, 0x15, 0x10, 0x0a, 0x05, 0x02}, /* 10, -5.0dB */ + {0x1d, 0x1c, 0x18, 0x14, 0x0f, 0x0a, 0x05, 0x02}, /* 11, -5.5dB */ + {0x1b, 0x1a, 0x17, 0x13, 0x0e, 0x09, 0x04, 0x02}, /* 12, -6.0dB */ + {0x1a, 0x19, 0x16, 0x12, 0x0d, 0x09, 0x04, 0x02}, /* 13, -6.5dB */ + {0x18, 0x17, 0x15, 0x11, 0x0c, 0x08, 0x04, 0x02}, /* 14, -7.0dB */ + {0x17, 0x16, 0x13, 0x10, 0x0c, 0x08, 0x04, 0x02}, /* 15, -7.5dB */ + {0x16, 0x15, 0x12, 0x0f, 0x0b, 0x07, 0x04, 0x01}, /* 16, -8.0dB */ + {0x14, 0x14, 0x11, 0x0e, 0x0b, 0x07, 0x03, 0x02}, /* 17, -8.5dB */ + {0x13, 0x13, 0x10, 0x0d, 0x0a, 0x06, 0x03, 0x01}, /* 18, -9.0dB */ + {0x12, 0x12, 0x0f, 0x0c, 0x09, 0x06, 0x03, 0x01}, /* 19, -9.5dB */ + {0x11, 0x11, 0x0f, 0x0c, 0x09, 0x06, 0x03, 0x01}, /* 20, -10.0dB*/ + {0x10, 0x10, 0x0e, 0x0b, 0x08, 0x05, 0x03, 0x01}, /* 21, -10.5dB*/ + {0x0f, 0x0f, 0x0d, 0x0b, 0x08, 0x05, 0x03, 0x01}, /* 22, -11.0dB*/ + {0x0e, 0x0e, 0x0c, 0x0a, 0x08, 0x05, 0x02, 0x01}, /* 23, -11.5dB*/ + {0x0d, 0x0d, 0x0c, 0x0a, 0x07, 0x05, 0x02, 0x01}, /* 24, -12.0dB*/ + {0x0d, 0x0c, 0x0b, 0x09, 0x07, 0x04, 0x02, 0x01}, /* 25, -12.5dB*/ + {0x0c, 0x0c, 0x0a, 0x09, 0x06, 0x04, 0x02, 0x01}, /* 26, -13.0dB*/ + {0x0b, 0x0b, 0x0a, 0x08, 0x06, 0x04, 0x02, 0x01}, /* 27, -13.5dB*/ + {0x0b, 0x0a, 0x09, 0x08, 0x06, 0x04, 0x02, 0x01}, /* 28, -14.0dB*/ + {0x0a, 0x0a, 0x09, 0x07, 0x05, 0x03, 0x02, 0x01}, /* 29, -14.5dB*/ + {0x0a, 0x09, 0x08, 0x07, 0x05, 0x03, 0x02, 0x01}, /* 30, -15.0dB*/ + {0x09, 0x09, 0x08, 0x06, 0x05, 0x03, 0x01, 0x01}, /* 31, -15.5dB*/ + {0x09, 0x08, 0x07, 0x06, 0x04, 0x03, 0x01, 0x01} /* 32, -16.0dB*/ +}; + +static const u8 cck_tbl_ch14[CCK_TABLE_SIZE][8] = { + {0x36, 0x35, 0x2e, 0x1b, 0x00, 0x00, 0x00, 0x00}, /* 0, +0dB */ + {0x33, 0x32, 0x2b, 0x19, 0x00, 0x00, 0x00, 0x00}, /* 1, -0.5dB */ + {0x30, 0x2f, 0x29, 0x18, 0x00, 0x00, 0x00, 0x00}, /* 2, -1.0dB */ + {0x2d, 0x2d, 0x17, 0x17, 0x00, 0x00, 0x00, 0x00}, /* 3, -1.5dB */ + {0x2b, 0x2a, 0x25, 0x15, 0x00, 0x00, 0x00, 0x00}, /* 4, -2.0dB */ + {0x28, 0x28, 0x24, 0x14, 0x00, 0x00, 0x00, 0x00}, /* 5, -2.5dB */ + {0x26, 0x25, 0x21, 0x13, 0x00, 0x00, 0x00, 0x00}, /* 6, -3.0dB */ + {0x24, 0x23, 0x1f, 0x12, 0x00, 0x00, 0x00, 0x00}, /* 7, -3.5dB */ + {0x22, 0x21, 0x1d, 0x11, 0x00, 0x00, 0x00, 0x00}, /* 8, -4.0dB */ + {0x20, 0x20, 0x1b, 0x10, 0x00, 0x00, 0x00, 0x00}, /* 9, -4.5dB */ + {0x1f, 0x1e, 0x1a, 0x0f, 0x00, 0x00, 0x00, 0x00}, /* 10, -5.0dB */ + {0x1d, 0x1c, 0x18, 0x0e, 0x00, 0x00, 0x00, 0x00}, /* 11, -5.5dB */ + {0x1b, 0x1a, 0x17, 0x0e, 0x00, 0x00, 0x00, 0x00}, /* 12, -6.0dB */ + {0x1a, 0x19, 0x16, 0x0d, 0x00, 0x00, 0x00, 0x00}, /* 13, -6.5dB */ + {0x18, 0x17, 0x15, 0x0c, 0x00, 0x00, 0x00, 0x00}, /* 14, -7.0dB */ + {0x17, 0x16, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00}, /* 15, -7.5dB */ + {0x16, 0x15, 0x12, 0x0b, 0x00, 0x00, 0x00, 0x00}, /* 16, -8.0dB */ + {0x14, 0x14, 0x11, 0x0a, 0x00, 0x00, 0x00, 0x00}, /* 17, -8.5dB */ + {0x13, 0x13, 0x10, 0x0a, 0x00, 0x00, 0x00, 0x00}, /* 18, -9.0dB */ + {0x12, 0x12, 0x0f, 0x09, 0x00, 0x00, 0x00, 0x00}, /* 19, -9.5dB */ + {0x11, 0x11, 0x0f, 0x09, 0x00, 0x00, 0x00, 0x00}, /* 20, -10.0dB*/ + {0x10, 0x10, 0x0e, 0x08, 0x00, 0x00, 0x00, 0x00}, /* 21, -10.5dB*/ + {0x0f, 0x0f, 0x0d, 0x08, 0x00, 0x00, 0x00, 0x00}, /* 22, -11.0dB*/ + {0x0e, 0x0e, 0x0c, 0x07, 0x00, 0x00, 0x00, 0x00}, /* 23, -11.5dB*/ + {0x0d, 0x0d, 0x0c, 0x07, 0x00, 0x00, 0x00, 0x00}, /* 24, -12.0dB*/ + {0x0d, 0x0c, 0x0b, 0x06, 0x00, 0x00, 0x00, 0x00}, /* 25, -12.5dB*/ + {0x0c, 0x0c, 0x0a, 0x06, 0x00, 0x00, 0x00, 0x00}, /* 26, -13.0dB*/ + {0x0b, 0x0b, 0x0a, 0x06, 0x00, 0x00, 0x00, 0x00}, /* 27, -13.5dB*/ + {0x0b, 0x0a, 0x09, 0x05, 0x00, 0x00, 0x00, 0x00}, /* 28, -14.0dB*/ + {0x0a, 0x0a, 0x09, 0x05, 0x00, 0x00, 0x00, 0x00}, /* 29, -14.5dB*/ + {0x0a, 0x09, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00}, /* 30, -15.0dB*/ + {0x09, 0x09, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00}, /* 31, -15.5dB*/ + {0x09, 0x08, 0x07, 0x04, 0x00, 0x00, 0x00, 0x00} /* 32, -16.0dB*/ +}; + +#define CAL_SWING_OFF(_off, _dir, _size, _del) \ + do { \ + for (_off = 0; _off < _size; _off++) { \ + if (_del < thermal_threshold[_dir][_off]) { \ + if (_off != 0) \ + _off--; \ + break; \ + } \ + } \ + if (_off >= _size) \ + _off = _size - 1; \ + } while (0) + +static void rtl88e_set_iqk_matrix(struct ieee80211_hw *hw, + u8 ofdm_index, u8 rfpath, + long iqk_result_x, long iqk_result_y) +{ + long ele_a = 0, ele_d, ele_c = 0, value32; + + ele_d = (ofdmswing_table[ofdm_index] & 0xFFC00000)>>22; + + if (iqk_result_x != 0) { + if ((iqk_result_x & 0x00000200) != 0) + iqk_result_x = iqk_result_x | 0xFFFFFC00; + ele_a = ((iqk_result_x * ele_d)>>8)&0x000003FF; + + if ((iqk_result_y & 0x00000200) != 0) + iqk_result_y = iqk_result_y | 0xFFFFFC00; + ele_c = ((iqk_result_y * ele_d)>>8)&0x000003FF; + + switch (rfpath) { + case RF90_PATH_A: + value32 = (ele_d << 22)|((ele_c & 0x3F)<<16) | ele_a; + rtl_set_bbreg(hw, ROFDM0_XATXIQIMBAL, MASKDWORD, + value32); + value32 = (ele_c & 0x000003C0) >> 6; + rtl_set_bbreg(hw, ROFDM0_XCTXAFE, MASKH4BITS, value32); + value32 = ((iqk_result_x * ele_d) >> 7) & 0x01; + rtl_set_bbreg(hw, ROFDM0_ECCATHRES, BIT(24), value32); + break; + case RF90_PATH_B: + value32 = (ele_d << 22)|((ele_c & 0x3F)<<16) | ele_a; + rtl_set_bbreg(hw, ROFDM0_XBTXIQIMBAL, + MASKDWORD, value32); + value32 = (ele_c & 0x000003C0) >> 6; + rtl_set_bbreg(hw, ROFDM0_XDTXAFE, MASKH4BITS, value32); + value32 = ((iqk_result_x * ele_d) >> 7) & 0x01; + rtl_set_bbreg(hw, ROFDM0_ECCATHRES, BIT(28), value32); + break; + default: + break; + } + } else { + switch (rfpath) { + case RF90_PATH_A: + rtl_set_bbreg(hw, ROFDM0_XATXIQIMBAL, MASKDWORD, + ofdmswing_table[ofdm_index]); + rtl_set_bbreg(hw, ROFDM0_XCTXAFE, MASKH4BITS, 0x00); + rtl_set_bbreg(hw, ROFDM0_ECCATHRES, BIT(24), 0x00); + break; + case RF90_PATH_B: + rtl_set_bbreg(hw, ROFDM0_XBTXIQIMBAL, MASKDWORD, + ofdmswing_table[ofdm_index]); + rtl_set_bbreg(hw, ROFDM0_XDTXAFE, MASKH4BITS, 0x00); + rtl_set_bbreg(hw, ROFDM0_ECCATHRES, BIT(28), 0x00); + break; + default: + break; + } + } +} + +void rtl88e_dm_txpower_track_adjust(struct ieee80211_hw *hw, + u8 type, u8 *pdirection, u32 *poutwrite_val) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_dm *rtldm = rtl_dm(rtl_priv(hw)); + u8 pwr_val = 0; + u8 cck_base = rtldm->swing_idx_cck_base; + u8 cck_val = rtldm->swing_idx_cck; + u8 ofdm_base = rtldm->swing_idx_ofdm_base; + u8 ofdm_val = rtlpriv->dm.swing_idx_ofdm[RF90_PATH_A]; + + if (type == 0) { + if (ofdm_val <= ofdm_base) { + *pdirection = 1; + pwr_val = ofdm_base - ofdm_val; + } else { + *pdirection = 2; + pwr_val = ofdm_val - ofdm_base; + } + } else if (type == 1) { + if (cck_val <= cck_base) { + *pdirection = 1; + pwr_val = cck_base - cck_val; + } else { + *pdirection = 2; + pwr_val = cck_val - cck_base; + } + } + + if (pwr_val >= TXPWRTRACK_MAX_IDX && (*pdirection == 1)) + pwr_val = TXPWRTRACK_MAX_IDX; + + *poutwrite_val = pwr_val | (pwr_val << 8) | (pwr_val << 16) | + (pwr_val << 24); +} + + +static void rtl88e_chk_tx_track(struct ieee80211_hw *hw, + enum pwr_track_control_method method, + u8 rfpath, u8 index) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_dm *rtldm = rtl_dm(rtl_priv(hw)); + int jj = rtldm->swing_idx_cck; + int i; + + if (method == TXAGC) { + if (rtldm->swing_flag_ofdm == true || + rtldm->swing_flag_cck == true) { + u8 chan = rtlphy->current_channel; + rtl88e_phy_set_txpower_level(hw, chan); + rtldm->swing_flag_ofdm = false; + rtldm->swing_flag_cck = false; + } + } else if (method == BBSWING) { + if (!rtldm->cck_inch14) { + for (i = 0; i < 8; i++) + rtl_write_byte(rtlpriv, 0xa22 + i, + cck_tbl_ch1_13[jj][i]); + } else { + for (i = 0; i < 8; i++) + rtl_write_byte(rtlpriv, 0xa22 + i, + cck_tbl_ch14[jj][i]); + } + + if (rfpath == RF90_PATH_A) { + long x = rtlphy->iqk_matrix[index].value[0][0]; + long y = rtlphy->iqk_matrix[index].value[0][1]; + u8 indx = rtldm->swing_idx_ofdm[rfpath]; + rtl88e_set_iqk_matrix(hw, indx, rfpath, x, y); + } else if (rfpath == RF90_PATH_B) { + u8 indx = rtldm->swing_idx_ofdm[rfpath]; + long x = rtlphy->iqk_matrix[indx].value[0][4]; + long y = rtlphy->iqk_matrix[indx].value[0][5]; + rtl88e_set_iqk_matrix(hw, indx, rfpath, x, y); + } + } else { + return; + } +} + +static void rtl88e_dm_diginit(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_dig = &rtlpriv->dm_digtable; + + dm_dig->dig_enable_flag = true; + dm_dig->cur_igvalue = rtl_get_bbreg(hw, ROFDM0_XAAGCCORE1, 0x7f); + dm_dig->pre_igvalue = 0; + dm_dig->cursta_cstate = DIG_STA_DISCONNECT; + dm_dig->presta_cstate = DIG_STA_DISCONNECT; + dm_dig->curmultista_cstate = DIG_MULTISTA_DISCONNECT; + dm_dig->rssi_lowthresh = DM_DIG_THRESH_LOW; + dm_dig->rssi_highthresh = DM_DIG_THRESH_HIGH; + dm_dig->fa_lowthresh = DM_FALSEALARM_THRESH_LOW; + dm_dig->fa_highthresh = DM_FALSEALARM_THRESH_HIGH; + dm_dig->rx_gain_max = DM_DIG_MAX; + dm_dig->rx_gain_min = DM_DIG_MIN; + dm_dig->back_val = DM_DIG_BACKOFF_DEFAULT; + dm_dig->back_range_max = DM_DIG_BACKOFF_MAX; + dm_dig->back_range_min = DM_DIG_BACKOFF_MIN; + dm_dig->pre_cck_cca_thres = 0xff; + dm_dig->cur_cck_cca_thres = 0x83; + dm_dig->forbidden_igi = DM_DIG_MIN; + dm_dig->large_fa_hit = 0; + dm_dig->recover_cnt = 0; + dm_dig->dig_min_0 = 0x25; + dm_dig->dig_min_1 = 0x25; + dm_dig->media_connect_0 = false; + dm_dig->media_connect_1 = false; + rtlpriv->dm.dm_initialgain_enable = true; +} + +static u8 rtl88e_dm_initial_gain_min_pwdb(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_dig = &rtlpriv->dm_digtable; + long rssi_val_min = 0; + + if ((dm_dig->curmultista_cstate == DIG_MULTISTA_CONNECT) && + (dm_dig->cursta_cstate == DIG_STA_CONNECT)) { + if (rtlpriv->dm.entry_min_undec_sm_pwdb != 0) + rssi_val_min = + (rtlpriv->dm.entry_min_undec_sm_pwdb > + rtlpriv->dm.undec_sm_pwdb) ? + rtlpriv->dm.undec_sm_pwdb : + rtlpriv->dm.entry_min_undec_sm_pwdb; + else + rssi_val_min = rtlpriv->dm.undec_sm_pwdb; + } else if (dm_dig->cursta_cstate == DIG_STA_CONNECT || + dm_dig->cursta_cstate == DIG_STA_BEFORE_CONNECT) { + rssi_val_min = rtlpriv->dm.undec_sm_pwdb; + } else if (dm_dig->curmultista_cstate == + DIG_MULTISTA_CONNECT) { + rssi_val_min = rtlpriv->dm.entry_min_undec_sm_pwdb; + } + return (u8)rssi_val_min; +} + +static void rtl88e_dm_false_alarm_counter_statistics(struct ieee80211_hw *hw) +{ + u32 ret_value; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct false_alarm_statistics *alm_cnt = &(rtlpriv->falsealm_cnt); + + rtl_set_bbreg(hw, ROFDM0_LSTF, BIT(31), 1); + rtl_set_bbreg(hw, ROFDM1_LSTF, BIT(31), 1); + + ret_value = rtl_get_bbreg(hw, ROFDM0_FRAMESYNC, MASKDWORD); + alm_cnt->cnt_fast_fsync_fail = (ret_value&0xffff); + alm_cnt->cnt_sb_search_fail = ((ret_value&0xffff0000)>>16); + + ret_value = rtl_get_bbreg(hw, ROFDM_PHYCOUNTER1, MASKDWORD); + alm_cnt->cnt_ofdm_cca = (ret_value&0xffff); + alm_cnt->cnt_parity_fail = ((ret_value & 0xffff0000) >> 16); + + ret_value = rtl_get_bbreg(hw, ROFDM_PHYCOUNTER2, MASKDWORD); + alm_cnt->cnt_rate_illegal = (ret_value & 0xffff); + alm_cnt->cnt_crc8_fail = ((ret_value & 0xffff0000) >> 16); + + ret_value = rtl_get_bbreg(hw, ROFDM_PHYCOUNTER3, MASKDWORD); + alm_cnt->cnt_mcs_fail = (ret_value & 0xffff); + alm_cnt->cnt_ofdm_fail = alm_cnt->cnt_parity_fail + + alm_cnt->cnt_rate_illegal + + alm_cnt->cnt_crc8_fail + + alm_cnt->cnt_mcs_fail + + alm_cnt->cnt_fast_fsync_fail + + alm_cnt->cnt_sb_search_fail; + + ret_value = rtl_get_bbreg(hw, REG_SC_CNT, MASKDWORD); + alm_cnt->cnt_bw_lsc = (ret_value & 0xffff); + alm_cnt->cnt_bw_usc = ((ret_value & 0xffff0000) >> 16); + + rtl_set_bbreg(hw, RCCK0_FALSEALARMREPORT, BIT(12), 1); + rtl_set_bbreg(hw, RCCK0_FALSEALARMREPORT, BIT(14), 1); + + ret_value = rtl_get_bbreg(hw, RCCK0_FACOUNTERLOWER, MASKBYTE0); + alm_cnt->cnt_cck_fail = ret_value; + + ret_value = rtl_get_bbreg(hw, RCCK0_FACOUNTERUPPER, MASKBYTE3); + alm_cnt->cnt_cck_fail += (ret_value & 0xff) << 8; + + ret_value = rtl_get_bbreg(hw, RCCK0_CCA_CNT, MASKDWORD); + alm_cnt->cnt_cck_cca = ((ret_value & 0xff) << 8) | + ((ret_value&0xFF00)>>8); + + alm_cnt->cnt_all = alm_cnt->cnt_fast_fsync_fail + + alm_cnt->cnt_sb_search_fail + + alm_cnt->cnt_parity_fail + + alm_cnt->cnt_rate_illegal + + alm_cnt->cnt_crc8_fail + + alm_cnt->cnt_mcs_fail + + alm_cnt->cnt_cck_fail; + alm_cnt->cnt_cca_all = alm_cnt->cnt_ofdm_cca + alm_cnt->cnt_cck_cca; + + rtl_set_bbreg(hw, ROFDM0_TRSWISOLATION, BIT(31), 1); + rtl_set_bbreg(hw, ROFDM0_TRSWISOLATION, BIT(31), 0); + rtl_set_bbreg(hw, ROFDM1_LSTF, BIT(27), 1); + rtl_set_bbreg(hw, ROFDM1_LSTF, BIT(27), 0); + rtl_set_bbreg(hw, ROFDM0_LSTF, BIT(31), 0); + rtl_set_bbreg(hw, ROFDM1_LSTF, BIT(31), 0); + rtl_set_bbreg(hw, RCCK0_FALSEALARMREPORT, BIT(13)|BIT(12), 0); + rtl_set_bbreg(hw, RCCK0_FALSEALARMREPORT, BIT(13)|BIT(12), 2); + rtl_set_bbreg(hw, RCCK0_FALSEALARMREPORT, BIT(15)|BIT(14), 0); + rtl_set_bbreg(hw, RCCK0_FALSEALARMREPORT, BIT(15)|BIT(14), 2); + + RT_TRACE(rtlpriv, COMP_DIG, DBG_TRACE, + "cnt_parity_fail = %d, cnt_rate_illegal = %d, " + "cnt_crc8_fail = %d, cnt_mcs_fail = %d\n", + alm_cnt->cnt_parity_fail, + alm_cnt->cnt_rate_illegal, + alm_cnt->cnt_crc8_fail, alm_cnt->cnt_mcs_fail); + + RT_TRACE(rtlpriv, COMP_DIG, DBG_TRACE, + "cnt_ofdm_fail = %x, cnt_cck_fail = %x, cnt_all = %x\n", + alm_cnt->cnt_ofdm_fail, + alm_cnt->cnt_cck_fail, alm_cnt->cnt_all); +} + +static void rtl88e_dm_cck_packet_detection_thresh(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_dig = &rtlpriv->dm_digtable; + u8 cur_cck_cca_thresh; + + if (dm_dig->cursta_cstate == DIG_STA_CONNECT) { + dm_dig->rssi_val_min = rtl88e_dm_initial_gain_min_pwdb(hw); + if (dm_dig->rssi_val_min > 25) { + cur_cck_cca_thresh = 0xcd; + } else if ((dm_dig->rssi_val_min <= 25) && + (dm_dig->rssi_val_min > 10)) { + cur_cck_cca_thresh = 0x83; + } else { + if (rtlpriv->falsealm_cnt.cnt_cck_fail > 1000) + cur_cck_cca_thresh = 0x83; + else + cur_cck_cca_thresh = 0x40; + } + + } else { + if (rtlpriv->falsealm_cnt.cnt_cck_fail > 1000) + cur_cck_cca_thresh = 0x83; + else + cur_cck_cca_thresh = 0x40; + } + + if (dm_dig->cur_cck_cca_thres != cur_cck_cca_thresh) + rtl_set_bbreg(hw, RCCK0_CCA, MASKBYTE2, cur_cck_cca_thresh); + + dm_dig->cur_cck_cca_thres = cur_cck_cca_thresh; + dm_dig->pre_cck_cca_thres = dm_dig->cur_cck_cca_thres; + RT_TRACE(rtlpriv, COMP_DIG, DBG_TRACE, + "CCK cca thresh hold =%x\n", dm_dig->cur_cck_cca_thres); +} + +static void rtl88e_dm_dig(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_dig = &rtlpriv->dm_digtable; + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 dig_min, dig_maxofmin; + bool bfirstconnect; + u8 dm_dig_max, dm_dig_min; + u8 current_igi = dm_dig->cur_igvalue; + + if (rtlpriv->dm.dm_initialgain_enable == false) + return; + if (dm_dig->dig_enable_flag == false) + return; + if (mac->act_scanning == true) + return; + + if (mac->link_state >= MAC80211_LINKED) + dm_dig->cursta_cstate = DIG_STA_CONNECT; + else + dm_dig->cursta_cstate = DIG_STA_DISCONNECT; + if (rtlpriv->mac80211.opmode == NL80211_IFTYPE_AP || + rtlpriv->mac80211.opmode == NL80211_IFTYPE_ADHOC) + dm_dig->cursta_cstate = DIG_STA_DISCONNECT; + + dm_dig_max = DM_DIG_MAX; + dm_dig_min = DM_DIG_MIN; + dig_maxofmin = DM_DIG_MAX_AP; + dig_min = dm_dig->dig_min_0; + bfirstconnect = ((mac->link_state >= MAC80211_LINKED) ? true : false) && + (dm_dig->media_connect_0 == false); + + dm_dig->rssi_val_min = + rtl88e_dm_initial_gain_min_pwdb(hw); + + if (mac->link_state >= MAC80211_LINKED) { + if ((dm_dig->rssi_val_min + 20) > dm_dig_max) + dm_dig->rx_gain_max = dm_dig_max; + else if ((dm_dig->rssi_val_min + 20) < dm_dig_min) + dm_dig->rx_gain_max = dm_dig_min; + else + dm_dig->rx_gain_max = dm_dig->rssi_val_min + 20; + + if (rtlefuse->antenna_div_type == CG_TRX_HW_ANTDIV) { + dig_min = dm_dig->antdiv_rssi_max; + } else { + if (dm_dig->rssi_val_min < dm_dig_min) + dig_min = dm_dig_min; + else if (dm_dig->rssi_val_min < dig_maxofmin) + dig_min = dig_maxofmin; + else + dig_min = dm_dig->rssi_val_min; + } + } else { + dm_dig->rx_gain_max = dm_dig_max; + dig_min = dm_dig_min; + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, "no link\n"); + } + + if (rtlpriv->falsealm_cnt.cnt_all > 10000) { + dm_dig->large_fa_hit++; + if (dm_dig->forbidden_igi < current_igi) { + dm_dig->forbidden_igi = current_igi; + dm_dig->large_fa_hit = 1; + } + + if (dm_dig->large_fa_hit >= 3) { + if ((dm_dig->forbidden_igi + 1) > dm_dig->rx_gain_max) + dm_dig->rx_gain_min = dm_dig->rx_gain_max; + else + dm_dig->rx_gain_min = dm_dig->forbidden_igi + 1; + dm_dig->recover_cnt = 3600; + } + } else { + if (dm_dig->recover_cnt != 0) { + dm_dig->recover_cnt--; + } else { + if (dm_dig->large_fa_hit == 0) { + if ((dm_dig->forbidden_igi - 1) < dig_min) { + dm_dig->forbidden_igi = dig_min; + dm_dig->rx_gain_min = dig_min; + } else { + dm_dig->forbidden_igi--; + dm_dig->rx_gain_min = + dm_dig->forbidden_igi + 1; + } + } else if (dm_dig->large_fa_hit == 3) { + dm_dig->large_fa_hit = 0; + } + } + } + + if (dm_dig->cursta_cstate == DIG_STA_CONNECT) { + if (bfirstconnect) { + current_igi = dm_dig->rssi_val_min; + } else { + if (rtlpriv->falsealm_cnt.cnt_all > DM_DIG_FA_TH2) + current_igi += 2; + else if (rtlpriv->falsealm_cnt.cnt_all > DM_DIG_FA_TH1) + current_igi++; + else if (rtlpriv->falsealm_cnt.cnt_all < DM_DIG_FA_TH0) + current_igi--; + } + } else { + if (rtlpriv->falsealm_cnt.cnt_all > 10000) + current_igi += 2; + else if (rtlpriv->falsealm_cnt.cnt_all > 8000) + current_igi++; + else if (rtlpriv->falsealm_cnt.cnt_all < 500) + current_igi--; + } + + if (current_igi > DM_DIG_FA_UPPER) + current_igi = DM_DIG_FA_UPPER; + else if (current_igi < DM_DIG_FA_LOWER) + current_igi = DM_DIG_FA_LOWER; + + if (rtlpriv->falsealm_cnt.cnt_all > 10000) + current_igi = DM_DIG_FA_UPPER; + + dm_dig->cur_igvalue = current_igi; + rtl88e_dm_write_dig(hw); + dm_dig->media_connect_0 = ((mac->link_state >= MAC80211_LINKED) ? + true : false); + dm_dig->dig_min_0 = dig_min; + + rtl88e_dm_cck_packet_detection_thresh(hw); +} + +static void rtl88e_dm_init_dynamic_txpower(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.dynamic_txpower_enable = false; + + rtlpriv->dm.last_dtp_lvl = TXHIGHPWRLEVEL_NORMAL; + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; +} + +static void rtl92c_dm_dynamic_txpower(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + long undec_sm_pwdb; + + if (!rtlpriv->dm.dynamic_txpower_enable) + return; + + if (rtlpriv->dm.dm_flag & HAL_DM_HIPWR_DISABLE) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + return; + } + + if ((mac->link_state < MAC80211_LINKED) && + (rtlpriv->dm.entry_min_undec_sm_pwdb == 0)) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_TRACE, + "Not connected\n"); + + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + + rtlpriv->dm.last_dtp_lvl = TXHIGHPWRLEVEL_NORMAL; + return; + } + + if (mac->link_state >= MAC80211_LINKED) { + if (mac->opmode == NL80211_IFTYPE_ADHOC) { + undec_sm_pwdb = + rtlpriv->dm.entry_min_undec_sm_pwdb; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "AP Client PWDB = 0x%lx\n", + undec_sm_pwdb); + } else { + undec_sm_pwdb = + rtlpriv->dm.undec_sm_pwdb; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "STA Default Port PWDB = 0x%lx\n", + undec_sm_pwdb); + } + } else { + undec_sm_pwdb = rtlpriv->dm.entry_min_undec_sm_pwdb; + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "AP Ext Port PWDB = 0x%lx\n", undec_sm_pwdb); + } + + if (undec_sm_pwdb >= TX_POWER_NEAR_FIELD_THRESH_LVL2) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_LEVEL1; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_LEVEL1 (TxPwr = 0x0)\n"); + } else if ((undec_sm_pwdb < + (TX_POWER_NEAR_FIELD_THRESH_LVL2 - 3)) && + (undec_sm_pwdb >= TX_POWER_NEAR_FIELD_THRESH_LVL1)) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_LEVEL1; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_LEVEL1 (TxPwr = 0x10)\n"); + } else if (undec_sm_pwdb < (TX_POWER_NEAR_FIELD_THRESH_LVL1 - 5)) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_NORMAL\n"); + } + + if ((rtlpriv->dm.dynamic_txhighpower_lvl != rtlpriv->dm.last_dtp_lvl)) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "PHY_SetTxPowerLevel8192S() Channel = %d\n", + rtlphy->current_channel); + rtl88e_phy_set_txpower_level(hw, rtlphy->current_channel); + } + + rtlpriv->dm.last_dtp_lvl = rtlpriv->dm.dynamic_txhighpower_lvl; +} + +void rtl88e_dm_write_dig(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_dig = &rtlpriv->dm_digtable; + + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, + "cur_igvalue = 0x%x, " + "pre_igvalue = 0x%x, back_val = %d\n", + dm_dig->cur_igvalue, dm_dig->pre_igvalue, + dm_dig->back_val); + + if (dm_dig->cur_igvalue > 0x3f) + dm_dig->cur_igvalue = 0x3f; + if (dm_dig->pre_igvalue != dm_dig->cur_igvalue) { + rtl_set_bbreg(hw, ROFDM0_XAAGCCORE1, 0x7f, + dm_dig->cur_igvalue); + + dm_dig->pre_igvalue = dm_dig->cur_igvalue; + } +} + +static void rtl88e_dm_pwdb_monitor(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_sta_info *drv_priv; + static u64 last_txok; + static u64 last_rx; + long tmp_entry_max_pwdb = 0, tmp_entry_min_pwdb = 0xff; + + if (rtlhal->oem_id == RT_CID_819x_HP) { + u64 cur_txok_cnt = 0; + u64 cur_rxok_cnt = 0; + cur_txok_cnt = rtlpriv->stats.txbytesunicast - last_txok; + cur_rxok_cnt = rtlpriv->stats.rxbytesunicast - last_rx; + last_txok = cur_txok_cnt; + last_rx = cur_rxok_cnt; + + if (cur_rxok_cnt > (cur_txok_cnt * 6)) + rtl_write_dword(rtlpriv, REG_ARFR0, 0x8f015); + else + rtl_write_dword(rtlpriv, REG_ARFR0, 0xff015); + } + + /* AP & ADHOC & MESH */ + spin_lock_bh(&rtlpriv->locks.entry_list_lock); + list_for_each_entry(drv_priv, &rtlpriv->entry_list, list) { + if (drv_priv->rssi_stat.undec_sm_pwdb < tmp_entry_min_pwdb) + tmp_entry_min_pwdb = drv_priv->rssi_stat.undec_sm_pwdb; + if (drv_priv->rssi_stat.undec_sm_pwdb > tmp_entry_max_pwdb) + tmp_entry_max_pwdb = drv_priv->rssi_stat.undec_sm_pwdb; + } + spin_unlock_bh(&rtlpriv->locks.entry_list_lock); + + /* If associated entry is found */ + if (tmp_entry_max_pwdb != 0) { + rtlpriv->dm.entry_max_undec_sm_pwdb = tmp_entry_max_pwdb; + RTPRINT(rtlpriv, FDM, DM_PWDB, "EntryMaxPWDB = 0x%lx(%ld)\n", + tmp_entry_max_pwdb, tmp_entry_max_pwdb); + } else { + rtlpriv->dm.entry_max_undec_sm_pwdb = 0; + } + /* If associated entry is found */ + if (tmp_entry_min_pwdb != 0xff) { + rtlpriv->dm.entry_min_undec_sm_pwdb = tmp_entry_min_pwdb; + RTPRINT(rtlpriv, FDM, DM_PWDB, "EntryMinPWDB = 0x%lx(%ld)\n", + tmp_entry_min_pwdb, tmp_entry_min_pwdb); + } else { + rtlpriv->dm.entry_min_undec_sm_pwdb = 0; + } + /* Indicate Rx signal strength to FW. */ + if (!rtlpriv->dm.useramask) + rtl_write_byte(rtlpriv, 0x4fe, rtlpriv->dm.undec_sm_pwdb); +} + +void rtl88e_dm_init_edca_turbo(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.current_turbo_edca = false; + rtlpriv->dm.is_any_nonbepkts = false; + rtlpriv->dm.is_cur_rdlstate = false; +} + +static void rtl88e_dm_check_edca_turbo(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + static u64 last_txok_cnt; + static u64 last_rxok_cnt; + static u32 last_bt_edca_ul; + static u32 last_bt_edca_dl; + u64 cur_txok_cnt = 0; + u64 cur_rxok_cnt = 0; + u32 edca_be_ul = 0x5ea42b; + u32 edca_be_dl = 0x5ea42b; + bool change_edca = false; + + if ((last_bt_edca_ul != rtlpcipriv->bt_coexist.bt_edca_ul) || + (last_bt_edca_dl != rtlpcipriv->bt_coexist.bt_edca_dl)) { + rtlpriv->dm.current_turbo_edca = false; + last_bt_edca_ul = rtlpcipriv->bt_coexist.bt_edca_ul; + last_bt_edca_dl = rtlpcipriv->bt_coexist.bt_edca_dl; + } + + if (rtlpcipriv->bt_coexist.bt_edca_ul != 0) { + edca_be_ul = rtlpcipriv->bt_coexist.bt_edca_ul; + change_edca = true; + } + + if (rtlpcipriv->bt_coexist.bt_edca_dl != 0) { + edca_be_ul = rtlpcipriv->bt_coexist.bt_edca_dl; + change_edca = true; + } + + if (mac->link_state != MAC80211_LINKED) { + rtlpriv->dm.current_turbo_edca = false; + return; + } + + if ((!mac->ht_enable) && (!rtlpcipriv->bt_coexist.bt_coexistence)) { + if (!(edca_be_ul & 0xffff0000)) + edca_be_ul |= 0x005e0000; + + if (!(edca_be_dl & 0xffff0000)) + edca_be_dl |= 0x005e0000; + } + + if ((change_edca) || ((!rtlpriv->dm.is_any_nonbepkts) && + (!rtlpriv->dm.disable_framebursting))) { + cur_txok_cnt = rtlpriv->stats.txbytesunicast - last_txok_cnt; + cur_rxok_cnt = rtlpriv->stats.rxbytesunicast - last_rxok_cnt; + + if (cur_rxok_cnt > 4 * cur_txok_cnt) { + if (!rtlpriv->dm.is_cur_rdlstate || + !rtlpriv->dm.current_turbo_edca) { + rtl_write_dword(rtlpriv, + REG_EDCA_BE_PARAM, + edca_be_dl); + rtlpriv->dm.is_cur_rdlstate = true; + } + } else { + if (rtlpriv->dm.is_cur_rdlstate || + !rtlpriv->dm.current_turbo_edca) { + rtl_write_dword(rtlpriv, + REG_EDCA_BE_PARAM, + edca_be_ul); + rtlpriv->dm.is_cur_rdlstate = false; + } + } + rtlpriv->dm.current_turbo_edca = true; + } else { + if (rtlpriv->dm.current_turbo_edca) { + u8 tmp = AC0_BE; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_AC_PARAM, + (u8 *)(&tmp)); + rtlpriv->dm.current_turbo_edca = false; + } + } + + rtlpriv->dm.is_any_nonbepkts = false; + last_txok_cnt = rtlpriv->stats.txbytesunicast; + last_rxok_cnt = rtlpriv->stats.rxbytesunicast; +} + +static void rtl88e_dm_txpower_tracking_callback_thermalmeter(struct ieee80211_hw + *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_dm *rtldm = rtl_dm(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 thermalvalue = 0, delta, delta_lck, delta_iqk, off; + u8 th_avg_cnt = 0; + u32 thermalvalue_avg = 0; + long ele_d, temp_cck; + char ofdm_index[2], cck_index = 0, ofdm_old[2] = {0, 0}, cck_old = 0; + int i = 0; + bool is2t = false; + + u8 ofdm_min_index = 6, rf = (is2t) ? 2 : 1; + u8 index_for_channel; + enum _dec_inc {dec, power_inc}; + + /* 0.1 the following TWO tables decide the final index of + * OFDM/CCK swing table + */ + char del_tbl_idx[2][15] = { + {0, 0, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 10, 10, 11}, + {0, 0, -1, -2, -3, -4, -4, -4, -4, -5, -7, -8, -9, -9, -10} + }; + u8 thermal_threshold[2][15] = { + {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 27}, + {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 25, 25, 25} + }; + + /*Initilization (7 steps in total) */ + rtlpriv->dm.txpower_trackinginit = true; + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "rtl88e_dm_txpower_tracking_callback_thermalmeter\n"); + + thermalvalue = (u8) rtl_get_rfreg(hw, RF90_PATH_A, RF_T_METER, 0xfc00); + if (!thermalvalue) + return; + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Readback Thermal Meter = 0x%x pre thermal meter 0x%x eeprom_thermalmeter 0x%x\n", + thermalvalue, rtlpriv->dm.thermalvalue, + rtlefuse->eeprom_thermalmeter); + + /*1. Query OFDM Default Setting: Path A*/ + ele_d = rtl_get_bbreg(hw, ROFDM0_XATXIQIMBAL, MASKDWORD) & MASKOFDM_D; + for (i = 0; i < OFDM_TABLE_LENGTH; i++) { + if (ele_d == (ofdmswing_table[i] & MASKOFDM_D)) { + ofdm_old[0] = (u8) i; + rtldm->swing_idx_ofdm_base = (u8)i; + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Initial pathA ele_d reg0x%x = 0x%lx, ofdm_index = 0x%x\n", + ROFDM0_XATXIQIMBAL, + ele_d, ofdm_old[0]); + break; + } + } + + if (is2t) { + ele_d = rtl_get_bbreg(hw, ROFDM0_XBTXIQIMBAL, + MASKDWORD) & MASKOFDM_D; + for (i = 0; i < OFDM_TABLE_LENGTH; i++) { + if (ele_d == (ofdmswing_table[i] & MASKOFDM_D)) { + ofdm_old[1] = (u8)i; + + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, + DBG_LOUD, + "Initial pathB ele_d reg0x%x = 0x%lx, ofdm_index = 0x%x\n", + ROFDM0_XBTXIQIMBAL, ele_d, + ofdm_old[1]); + break; + } + } + } + /*2.Query CCK default setting From 0xa24*/ + temp_cck = rtl_get_bbreg(hw, RCCK0_TXFILTER2, MASKDWORD) & MASKCCK; + for (i = 0; i < CCK_TABLE_LENGTH; i++) { + if (rtlpriv->dm.cck_inch14) { + if (memcmp(&temp_cck, &cck_tbl_ch14[i][2], 4) == 0) { + cck_old = (u8)i; + rtldm->swing_idx_cck_base = (u8)i; + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Initial reg0x%x = 0x%lx, cck_index = 0x%x, ch 14 %d\n", + RCCK0_TXFILTER2, temp_cck, cck_old, + rtlpriv->dm.cck_inch14); + break; + } + } else { + if (memcmp(&temp_cck, &cck_tbl_ch1_13[i][2], 4) == 0) { + cck_old = (u8)i; + rtldm->swing_idx_cck_base = (u8)i; + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Initial reg0x%x = 0x%lx, cck_index = 0x%x, ch14 %d\n", + RCCK0_TXFILTER2, temp_cck, cck_old, + rtlpriv->dm.cck_inch14); + break; + } + } + } + + /*3 Initialize ThermalValues of RFCalibrateInfo*/ + if (!rtldm->thermalvalue) { + rtlpriv->dm.thermalvalue = rtlefuse->eeprom_thermalmeter; + rtlpriv->dm.thermalvalue_lck = thermalvalue; + rtlpriv->dm.thermalvalue_iqk = thermalvalue; + for (i = 0; i < rf; i++) + rtlpriv->dm.ofdm_index[i] = ofdm_old[i]; + rtlpriv->dm.cck_index = cck_old; + } + + /*4 Calculate average thermal meter*/ + rtldm->thermalvalue_avg[rtldm->thermalvalue_avg_index] = thermalvalue; + rtldm->thermalvalue_avg_index++; + if (rtldm->thermalvalue_avg_index == AVG_THERMAL_NUM_88E) + rtldm->thermalvalue_avg_index = 0; + + for (i = 0; i < AVG_THERMAL_NUM_88E; i++) { + if (rtldm->thermalvalue_avg[i]) { + thermalvalue_avg += rtldm->thermalvalue_avg[i]; + th_avg_cnt++; + } + } + + if (th_avg_cnt) + thermalvalue = (u8)(thermalvalue_avg / th_avg_cnt); + + /* 5 Calculate delta, delta_LCK, delta_IQK.*/ + if (rtlhal->reloadtxpowerindex) { + delta = (thermalvalue > rtlefuse->eeprom_thermalmeter) ? + (thermalvalue - rtlefuse->eeprom_thermalmeter) : + (rtlefuse->eeprom_thermalmeter - thermalvalue); + rtlhal->reloadtxpowerindex = false; + rtlpriv->dm.done_txpower = false; + } else if (rtlpriv->dm.done_txpower) { + delta = (thermalvalue > rtlpriv->dm.thermalvalue) ? + (thermalvalue - rtlpriv->dm.thermalvalue) : + (rtlpriv->dm.thermalvalue - thermalvalue); + } else { + delta = (thermalvalue > rtlefuse->eeprom_thermalmeter) ? + (thermalvalue - rtlefuse->eeprom_thermalmeter) : + (rtlefuse->eeprom_thermalmeter - thermalvalue); + } + delta_lck = (thermalvalue > rtlpriv->dm.thermalvalue_lck) ? + (thermalvalue - rtlpriv->dm.thermalvalue_lck) : + (rtlpriv->dm.thermalvalue_lck - thermalvalue); + delta_iqk = (thermalvalue > rtlpriv->dm.thermalvalue_iqk) ? + (thermalvalue - rtlpriv->dm.thermalvalue_iqk) : + (rtlpriv->dm.thermalvalue_iqk - thermalvalue); + + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Readback Thermal Meter = 0x%x pre thermal meter 0x%x " + "eeprom_thermalmeter 0x%x delta 0x%x " + "delta_lck 0x%x delta_iqk 0x%x\n", + thermalvalue, rtlpriv->dm.thermalvalue, + rtlefuse->eeprom_thermalmeter, delta, delta_lck, + delta_iqk); + /* 6 If necessary, do LCK.*/ + if (delta_lck >= 8) { + rtlpriv->dm.thermalvalue_lck = thermalvalue; + rtl88e_phy_lc_calibrate(hw); + } + + /* 7 If necessary, move the index of swing table to adjust Tx power. */ + if (delta > 0 && rtlpriv->dm.txpower_track_control) { + delta = (thermalvalue > rtlefuse->eeprom_thermalmeter) ? + (thermalvalue - rtlefuse->eeprom_thermalmeter) : + (rtlefuse->eeprom_thermalmeter - thermalvalue); + + /* 7.1 Get the final CCK_index and OFDM_index for each + * swing table. + */ + if (thermalvalue > rtlefuse->eeprom_thermalmeter) { + CAL_SWING_OFF(off, power_inc, IDX_MAP, delta); + for (i = 0; i < rf; i++) + ofdm_index[i] = rtldm->ofdm_index[i] + + del_tbl_idx[power_inc][off]; + cck_index = rtldm->cck_index + + del_tbl_idx[power_inc][off]; + } else { + CAL_SWING_OFF(off, dec, IDX_MAP, delta); + for (i = 0; i < rf; i++) + ofdm_index[i] = rtldm->ofdm_index[i] + + del_tbl_idx[dec][off]; + cck_index = rtldm->cck_index + del_tbl_idx[dec][off]; + } + + /* 7.2 Handle boundary conditions of index.*/ + for (i = 0; i < rf; i++) { + if (ofdm_index[i] > OFDM_TABLE_SIZE-1) + ofdm_index[i] = OFDM_TABLE_SIZE-1; + else if (rtldm->ofdm_index[i] < ofdm_min_index) + ofdm_index[i] = ofdm_min_index; + } + + if (cck_index > CCK_TABLE_SIZE - 1) + cck_index = CCK_TABLE_SIZE - 1; + else if (cck_index < 0) + cck_index = 0; + + /*7.3Configure the Swing Table to adjust Tx Power.*/ + if (rtlpriv->dm.txpower_track_control) { + rtldm->done_txpower = true; + rtldm->swing_idx_ofdm[RF90_PATH_A] = + (u8)ofdm_index[RF90_PATH_A]; + if (is2t) + rtldm->swing_idx_ofdm[RF90_PATH_B] = + (u8)ofdm_index[RF90_PATH_B]; + rtldm->swing_idx_cck = cck_index; + if (rtldm->swing_idx_ofdm_cur != + rtldm->swing_idx_ofdm[0]) { + rtldm->swing_idx_ofdm_cur = + rtldm->swing_idx_ofdm[0]; + rtldm->swing_flag_ofdm = true; + } + + if (rtldm->swing_idx_cck != rtldm->swing_idx_cck) { + rtldm->swing_idx_cck_cur = rtldm->swing_idx_cck; + rtldm->swing_flag_cck = true; + } + + rtl88e_chk_tx_track(hw, TXAGC, 0, 0); + + if (is2t) + rtl88e_chk_tx_track(hw, BBSWING, + RF90_PATH_B, + index_for_channel); + } + } + + if (delta_iqk >= 8) { + rtlpriv->dm.thermalvalue_iqk = thermalvalue; + rtl88e_phy_iq_calibrate(hw, false); + } + + if (rtldm->txpower_track_control) + rtldm->thermalvalue = thermalvalue; + rtldm->txpowercount = 0; + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, "end\n"); +} + +static void rtl88e_dm_init_txpower_tracking(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.txpower_tracking = true; + rtlpriv->dm.txpower_trackinginit = false; + rtlpriv->dm.txpowercount = 0; + rtlpriv->dm.txpower_track_control = true; + + rtlpriv->dm.swing_idx_ofdm[RF90_PATH_A] = 12; + rtlpriv->dm.swing_idx_ofdm_cur = 12; + rtlpriv->dm.swing_flag_ofdm = false; + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + " rtlpriv->dm.txpower_tracking = %d\n", + rtlpriv->dm.txpower_tracking); +} + +void rtl88e_dm_check_txpower_tracking(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + static u8 tm_trigger; + + if (!rtlpriv->dm.txpower_tracking) + return; + + if (!tm_trigger) { + rtl_set_rfreg(hw, RF90_PATH_A, RF_T_METER, BIT(17)|BIT(16), + 0x03); + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Trigger 88E Thermal Meter!!\n"); + tm_trigger = 1; + return; + } else { + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Schedule TxPowerTracking !!\n"); + rtl88e_dm_txpower_tracking_callback_thermalmeter(hw); + tm_trigger = 0; + } +} + +void rtl88e_dm_init_rate_adaptive_mask(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rate_adaptive *p_ra = &(rtlpriv->ra); + + p_ra->ratr_state = DM_RATR_STA_INIT; + p_ra->pre_ratr_state = DM_RATR_STA_INIT; + + if (rtlpriv->dm.dm_type == DM_TYPE_BYDRIVER) + rtlpriv->dm.useramask = true; + else + rtlpriv->dm.useramask = false; +} + +static void rtl88e_dm_refresh_rate_adaptive_mask(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rate_adaptive *p_ra = &(rtlpriv->ra); + struct ieee80211_sta *sta = NULL; + u32 low_rssi, hi_rssi; + + if (is_hal_stop(rtlhal)) { + RT_TRACE(rtlpriv, COMP_RATE, DBG_LOUD, + "driver is going to unload\n"); + return; + } + + if (!rtlpriv->dm.useramask) { + RT_TRACE(rtlpriv, COMP_RATE, DBG_LOUD, + "driver does not control rate adaptive mask\n"); + return; + } + + if (mac->link_state == MAC80211_LINKED && + mac->opmode == NL80211_IFTYPE_STATION) { + switch (p_ra->pre_ratr_state) { + case DM_RATR_STA_HIGH: + hi_rssi = 50; + low_rssi = 20; + break; + case DM_RATR_STA_MIDDLE: + hi_rssi = 55; + low_rssi = 20; + break; + case DM_RATR_STA_LOW: + hi_rssi = 50; + low_rssi = 25; + break; + default: + hi_rssi = 50; + low_rssi = 20; + break; + } + + if (rtlpriv->dm.undec_sm_pwdb > (long)hi_rssi) + p_ra->ratr_state = DM_RATR_STA_HIGH; + else if (rtlpriv->dm.undec_sm_pwdb > (long)low_rssi) + p_ra->ratr_state = DM_RATR_STA_MIDDLE; + else + p_ra->ratr_state = DM_RATR_STA_LOW; + + if (p_ra->pre_ratr_state != p_ra->ratr_state) { + RT_TRACE(rtlpriv, COMP_RATE, DBG_LOUD, + "RSSI = %ld\n", + rtlpriv->dm.undec_sm_pwdb); + RT_TRACE(rtlpriv, COMP_RATE, DBG_LOUD, + "RSSI_LEVEL = %d\n", p_ra->ratr_state); + RT_TRACE(rtlpriv, COMP_RATE, DBG_LOUD, + "PreState = %d, CurState = %d\n", + p_ra->pre_ratr_state, p_ra->ratr_state); + + rcu_read_lock(); + sta = rtl_find_sta(hw, mac->bssid); + if (sta) + rtlpriv->cfg->ops->update_rate_tbl(hw, sta, + p_ra->ratr_state); + rcu_read_unlock(); + + p_ra->pre_ratr_state = p_ra->ratr_state; + } + } +} + +static void rtl92c_dm_init_dynamic_bb_powersaving(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct ps_t *dm_pstable = &rtlpriv->dm_pstable; + + dm_pstable->pre_ccastate = CCA_MAX; + dm_pstable->cur_ccasate = CCA_MAX; + dm_pstable->pre_rfstate = RF_MAX; + dm_pstable->cur_rfstate = RF_MAX; + dm_pstable->rssi_val_min = 0; +} + +static void rtl88e_dm_update_rx_idle_ant(struct ieee80211_hw *hw, u8 ant) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_dm *rtldm = rtl_dm(rtl_priv(hw)); + struct fast_ant_training *fat_tbl = &(rtldm->fat_table); + u32 def_ant, opt_ant; + + if (fat_tbl->rx_idle_ant != ant) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "need to update rx idle ant\n"); + if (ant == MAIN_ANT) { + def_ant = (fat_tbl->rx_idle_ant == CG_TRX_HW_ANTDIV) ? + MAIN_ANT_CG_TRX : MAIN_ANT_CGCS_RX; + opt_ant = (fat_tbl->rx_idle_ant == CG_TRX_HW_ANTDIV) ? + AUX_ANT_CG_TRX : AUX_ANT_CGCS_RX; + } else { + def_ant = (fat_tbl->rx_idle_ant == CG_TRX_HW_ANTDIV) ? + AUX_ANT_CG_TRX : AUX_ANT_CGCS_RX; + opt_ant = (fat_tbl->rx_idle_ant == CG_TRX_HW_ANTDIV) ? + MAIN_ANT_CG_TRX : MAIN_ANT_CGCS_RX; + } + + if (rtlefuse->antenna_div_type == CG_TRX_HW_ANTDIV) { + rtl_set_bbreg(hw, DM_REG_RX_ANT_CTRL_11N, BIT(5) | + BIT(4) | BIT(3), def_ant); + rtl_set_bbreg(hw, DM_REG_RX_ANT_CTRL_11N, BIT(8) | + BIT(7) | BIT(6), opt_ant); + rtl_set_bbreg(hw, DM_REG_ANTSEL_CTRL_11N, BIT(14) | + BIT(13) | BIT(12), def_ant); + rtl_set_bbreg(hw, DM_REG_RESP_TX_11N, BIT(6) | BIT(7), + def_ant); + } else if (rtlefuse->antenna_div_type == CGCS_RX_HW_ANTDIV) { + rtl_set_bbreg(hw, DM_REG_RX_ANT_CTRL_11N, BIT(5) | + BIT(4) | BIT(3), def_ant); + rtl_set_bbreg(hw, DM_REG_RX_ANT_CTRL_11N, BIT(8) | + BIT(7) | BIT(6), opt_ant); + } + } + fat_tbl->rx_idle_ant = ant; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "RxIdleAnt %s\n", + ((ant == MAIN_ANT) ? ("MAIN_ANT") : ("AUX_ANT"))); +} + +static void rtl88e_dm_update_tx_ant(struct ieee80211_hw *hw, + u8 ant, u32 mac_id) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_dm *rtldm = rtl_dm(rtl_priv(hw)); + struct fast_ant_training *fat_tbl = &(rtldm->fat_table); + u8 target_ant; + + if (ant == MAIN_ANT) + target_ant = MAIN_ANT_CG_TRX; + else + target_ant = AUX_ANT_CG_TRX; + + fat_tbl->antsel_a[mac_id] = target_ant & BIT(0); + fat_tbl->antsel_b[mac_id] = (target_ant & BIT(1)) >> 1; + fat_tbl->antsel_c[mac_id] = (target_ant & BIT(2)) >> 2; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "txfrominfo target ant %s\n", + ((ant == MAIN_ANT) ? ("MAIN_ANT") : ("AUX_ANT"))); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "antsel_tr_mux = 3'b%d%d%d\n", + fat_tbl->antsel_c[mac_id], + fat_tbl->antsel_b[mac_id], fat_tbl->antsel_a[mac_id]); +} + +static void rtl88e_dm_rx_hw_antena_div_init(struct ieee80211_hw *hw) +{ + u32 value32; + /*MAC Setting*/ + value32 = rtl_get_bbreg(hw, DM_REG_ANTSEL_PIN_11N, MASKDWORD); + rtl_set_bbreg(hw, DM_REG_ANTSEL_PIN_11N, MASKDWORD, value32 | + (BIT(23) | BIT(25))); + /*Pin Setting*/ + rtl_set_bbreg(hw, DM_REG_PIN_CTRL_11N, BIT(9) | BIT(8), 0); + rtl_set_bbreg(hw, DM_REG_RX_ANT_CTRL_11N, BIT(10), 0); + rtl_set_bbreg(hw, DM_REG_LNA_SWITCH_11N, BIT(22), 1); + rtl_set_bbreg(hw, DM_REG_LNA_SWITCH_11N, BIT(31), 1); + /*OFDM Setting*/ + rtl_set_bbreg(hw, DM_REG_ANTDIV_PARA1_11N, MASKDWORD, 0x000000a0); + /*CCK Setting*/ + rtl_set_bbreg(hw, DM_REG_BB_PWR_SAV4_11N, BIT(7), 1); + rtl_set_bbreg(hw, DM_REG_CCK_ANTDIV_PARA2_11N, BIT(4), 1); + rtl88e_dm_update_rx_idle_ant(hw, MAIN_ANT); + rtl_set_bbreg(hw, DM_REG_ANT_MAPPING1_11N, MASKLWORD, 0x0201); +} + +static void rtl88e_dm_trx_hw_antenna_div_init(struct ieee80211_hw *hw) +{ + u32 value32; + + /*MAC Setting*/ + value32 = rtl_get_bbreg(hw, DM_REG_ANTSEL_PIN_11N, MASKDWORD); + rtl_set_bbreg(hw, DM_REG_ANTSEL_PIN_11N, MASKDWORD, value32 | + (BIT(23) | BIT(25))); + /*Pin Setting*/ + rtl_set_bbreg(hw, DM_REG_PIN_CTRL_11N, BIT(9) | BIT(8), 0); + rtl_set_bbreg(hw, DM_REG_RX_ANT_CTRL_11N, BIT(10), 0); + rtl_set_bbreg(hw, DM_REG_LNA_SWITCH_11N, BIT(22), 0); + rtl_set_bbreg(hw, DM_REG_LNA_SWITCH_11N, BIT(31), 1); + /*OFDM Setting*/ + rtl_set_bbreg(hw, DM_REG_ANTDIV_PARA1_11N, MASKDWORD, 0x000000a0); + /*CCK Setting*/ + rtl_set_bbreg(hw, DM_REG_BB_PWR_SAV4_11N, BIT(7), 1); + rtl_set_bbreg(hw, DM_REG_CCK_ANTDIV_PARA2_11N, BIT(4), 1); + /*TX Setting*/ + rtl_set_bbreg(hw, DM_REG_TX_ANT_CTRL_11N, BIT(21), 0); + rtl88e_dm_update_rx_idle_ant(hw, MAIN_ANT); + rtl_set_bbreg(hw, DM_REG_ANT_MAPPING1_11N, MASKLWORD, 0x0201); +} + +static void rtl88e_dm_fast_training_init(struct ieee80211_hw *hw) +{ + struct rtl_dm *rtldm = rtl_dm(rtl_priv(hw)); + struct fast_ant_training *fat_tbl = &(rtldm->fat_table); + u32 ant_combo = 2; + u32 value32, i; + + for (i = 0; i < 6; i++) { + fat_tbl->bssid[i] = 0; + fat_tbl->ant_sum[i] = 0; + fat_tbl->ant_cnt[i] = 0; + fat_tbl->ant_ave[i] = 0; + } + fat_tbl->train_idx = 0; + fat_tbl->fat_state = FAT_NORMAL_STATE; + + /*MAC Setting*/ + value32 = rtl_get_bbreg(hw, DM_REG_ANTSEL_PIN_11N, MASKDWORD); + rtl_set_bbreg(hw, DM_REG_ANTSEL_PIN_11N, MASKDWORD, value32 | (BIT(23) | + BIT(25))); + value32 = rtl_get_bbreg(hw, DM_REG_ANT_TRAIN_2, MASKDWORD); + rtl_set_bbreg(hw, DM_REG_ANT_TRAIN_2, MASKDWORD, value32 | (BIT(16) | + BIT(17))); + rtl_set_bbreg(hw, DM_REG_ANT_TRAIN_2, MASKLWORD, 0); + rtl_set_bbreg(hw, DM_REG_ANT_TRAIN_1, MASKDWORD, 0); + + /*Pin Setting*/ + rtl_set_bbreg(hw, DM_REG_PIN_CTRL_11N, BIT(9) | BIT(8), 0); + rtl_set_bbreg(hw, DM_REG_RX_ANT_CTRL_11N, BIT(10), 0); + rtl_set_bbreg(hw, DM_REG_LNA_SWITCH_11N, BIT(22), 0); + rtl_set_bbreg(hw, DM_REG_LNA_SWITCH_11N, BIT(31), 1); + + /*OFDM Setting*/ + rtl_set_bbreg(hw, DM_REG_ANTDIV_PARA1_11N, MASKDWORD, 0x000000a0); + /*antenna mapping table*/ + if (ant_combo == 2) { + rtl_set_bbreg(hw, DM_REG_ANT_MAPPING1_11N, MASKBYTE0, 1); + rtl_set_bbreg(hw, DM_REG_ANT_MAPPING1_11N, MASKBYTE1, 2); + } else if (ant_combo == 7) { + rtl_set_bbreg(hw, DM_REG_ANT_MAPPING1_11N, MASKBYTE0, 1); + rtl_set_bbreg(hw, DM_REG_ANT_MAPPING1_11N, MASKBYTE1, 2); + rtl_set_bbreg(hw, DM_REG_ANT_MAPPING1_11N, MASKBYTE2, 2); + rtl_set_bbreg(hw, DM_REG_ANT_MAPPING1_11N, MASKBYTE3, 3); + rtl_set_bbreg(hw, DM_REG_ANT_MAPPING2_11N, MASKBYTE0, 4); + rtl_set_bbreg(hw, DM_REG_ANT_MAPPING2_11N, MASKBYTE1, 5); + rtl_set_bbreg(hw, DM_REG_ANT_MAPPING2_11N, MASKBYTE2, 6); + rtl_set_bbreg(hw, DM_REG_ANT_MAPPING2_11N, MASKBYTE3, 7); + } + + /*TX Setting*/ + rtl_set_bbreg(hw, DM_REG_TX_ANT_CTRL_11N, BIT(21), 1); + rtl_set_bbreg(hw, DM_REG_RX_ANT_CTRL_11N, BIT(5) | BIT(4) | BIT(3), 0); + rtl_set_bbreg(hw, DM_REG_RX_ANT_CTRL_11N, BIT(8) | BIT(7) | BIT(6), 1); + rtl_set_bbreg(hw, DM_REG_RX_ANT_CTRL_11N, BIT(2) | BIT(1) | BIT(0), + (ant_combo - 1)); + + rtl_set_bbreg(hw, DM_REG_IGI_A_11N, BIT(7), 1); +} + +static void rtl88e_dm_antenna_div_init(struct ieee80211_hw *hw) +{ + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + + if (rtlefuse->antenna_div_type == CGCS_RX_HW_ANTDIV) + rtl88e_dm_rx_hw_antena_div_init(hw); + else if (rtlefuse->antenna_div_type == CG_TRX_HW_ANTDIV) + rtl88e_dm_trx_hw_antenna_div_init(hw); + else if (rtlefuse->antenna_div_type == CG_TRX_SMART_ANTDIV) + rtl88e_dm_fast_training_init(hw); +} + +void rtl88e_dm_set_tx_ant_by_tx_info(struct ieee80211_hw *hw, + u8 *pdesc, u32 mac_id) +{ + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_dm *rtldm = rtl_dm(rtl_priv(hw)); + struct fast_ant_training *fat_tbl = &(rtldm->fat_table); + + if ((rtlefuse->antenna_div_type == CG_TRX_HW_ANTDIV) || + (rtlefuse->antenna_div_type == CG_TRX_HW_ANTDIV)) { + SET_TX_DESC_ANTSEL_A(pdesc, fat_tbl->antsel_a[mac_id]); + SET_TX_DESC_ANTSEL_B(pdesc, fat_tbl->antsel_b[mac_id]); + SET_TX_DESC_ANTSEL_C(pdesc, fat_tbl->antsel_c[mac_id]); + } +} + +void rtl88e_dm_ant_sel_statistics(struct ieee80211_hw *hw, + u8 antsel_tr_mux, u32 mac_id, u32 rx_pwdb_all) +{ + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_dm *rtldm = rtl_dm(rtl_priv(hw)); + struct fast_ant_training *fat_tbl = &(rtldm->fat_table); + + if (rtlefuse->antenna_div_type == CG_TRX_HW_ANTDIV) { + if (antsel_tr_mux == MAIN_ANT_CG_TRX) { + fat_tbl->main_ant_sum[mac_id] += rx_pwdb_all; + fat_tbl->main_ant_cnt[mac_id]++; + } else { + fat_tbl->aux_ant_sum[mac_id] += rx_pwdb_all; + fat_tbl->aux_ant_cnt[mac_id]++; + } + } else if (rtlefuse->antenna_div_type == CGCS_RX_HW_ANTDIV) { + if (antsel_tr_mux == MAIN_ANT_CGCS_RX) { + fat_tbl->main_ant_sum[mac_id] += rx_pwdb_all; + fat_tbl->main_ant_cnt[mac_id]++; + } else { + fat_tbl->aux_ant_sum[mac_id] += rx_pwdb_all; + fat_tbl->aux_ant_cnt[mac_id]++; + } + } +} + +static void rtl88e_dm_hw_ant_div(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_dig = &rtlpriv->dm_digtable; + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_dm *rtldm = rtl_dm(rtl_priv(hw)); + struct rtl_sta_info *drv_priv; + struct fast_ant_training *fat_tbl = &(rtldm->fat_table); + u32 i, min_rssi = 0xff, ant_div_max_rssi = 0, max_rssi = 0; + u32 local_min_rssi, local_max_rssi; + u32 main_rssi, aux_rssi; + u8 rx_idle_ant = 0, target_ant = 7; + + i = 0; + main_rssi = (fat_tbl->main_ant_cnt[i] != 0) ? + (fat_tbl->main_ant_sum[i] / + fat_tbl->main_ant_cnt[i]) : 0; + aux_rssi = (fat_tbl->aux_ant_cnt[i] != 0) ? + (fat_tbl->aux_ant_sum[i] / fat_tbl->aux_ant_cnt[i]) : 0; + target_ant = (main_rssi == aux_rssi) ? + fat_tbl->rx_idle_ant : ((main_rssi >= aux_rssi) ? + MAIN_ANT : AUX_ANT); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "main_ant_sum %d main_ant_cnt %d\n", + fat_tbl->main_ant_sum[i], fat_tbl->main_ant_cnt[i]); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "aux_ant_sum %d aux_ant_cnt %d\n", + fat_tbl->aux_ant_sum[i], + fat_tbl->aux_ant_cnt[i]); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "main_rssi %d aux_rssi%d\n", main_rssi, aux_rssi); + local_max_rssi = (main_rssi > aux_rssi) ? main_rssi : aux_rssi; + if ((local_max_rssi > ant_div_max_rssi) && (local_max_rssi < 40)) + ant_div_max_rssi = local_max_rssi; + if (local_max_rssi > max_rssi) + max_rssi = local_max_rssi; + + if ((fat_tbl->rx_idle_ant == MAIN_ANT) && (main_rssi == 0)) + main_rssi = aux_rssi; + else if ((fat_tbl->rx_idle_ant == AUX_ANT) && (aux_rssi == 0)) + aux_rssi = main_rssi; + + local_min_rssi = (main_rssi > aux_rssi) ? aux_rssi : main_rssi; + if (local_min_rssi < min_rssi) { + min_rssi = local_min_rssi; + rx_idle_ant = target_ant; + } + if (rtlefuse->antenna_div_type == CG_TRX_HW_ANTDIV) + rtl88e_dm_update_tx_ant(hw, target_ant, i); + + if (rtlpriv->mac80211.opmode == NL80211_IFTYPE_AP || + rtlpriv->mac80211.opmode == NL80211_IFTYPE_ADHOC) { + spin_lock_bh(&rtlpriv->locks.entry_list_lock); + list_for_each_entry(drv_priv, &rtlpriv->entry_list, list) { + i++; + main_rssi = (fat_tbl->main_ant_cnt[i] != 0) ? + (fat_tbl->main_ant_sum[i] / + fat_tbl->main_ant_cnt[i]) : 0; + aux_rssi = (fat_tbl->aux_ant_cnt[i] != 0) ? + (fat_tbl->aux_ant_sum[i] / + fat_tbl->aux_ant_cnt[i]) : 0; + target_ant = (main_rssi == aux_rssi) ? + fat_tbl->rx_idle_ant : ((main_rssi >= + aux_rssi) ? MAIN_ANT : AUX_ANT); + + + local_max_rssi = max_t(u32, main_rssi, aux_rssi); + if ((local_max_rssi > ant_div_max_rssi) && + (local_max_rssi < 40)) + ant_div_max_rssi = local_max_rssi; + if (local_max_rssi > max_rssi) + max_rssi = local_max_rssi; + + if ((fat_tbl->rx_idle_ant == MAIN_ANT) && !main_rssi) + main_rssi = aux_rssi; + else if ((fat_tbl->rx_idle_ant == AUX_ANT) && + (aux_rssi == 0)) + aux_rssi = main_rssi; + + local_min_rssi = (main_rssi > aux_rssi) ? + aux_rssi : main_rssi; + if (local_min_rssi < min_rssi) { + min_rssi = local_min_rssi; + rx_idle_ant = target_ant; + } + if (rtlefuse->antenna_div_type == CG_TRX_HW_ANTDIV) + rtl88e_dm_update_tx_ant(hw, target_ant, i); + } + spin_unlock_bh(&rtlpriv->locks.entry_list_lock); + } + + for (i = 0; i < ASSOCIATE_ENTRY_NUM; i++) { + fat_tbl->main_ant_sum[i] = 0; + fat_tbl->aux_ant_sum[i] = 0; + fat_tbl->main_ant_cnt[i] = 0; + fat_tbl->aux_ant_cnt[i] = 0; + } + + rtl88e_dm_update_rx_idle_ant(hw, rx_idle_ant); + + dm_dig->antdiv_rssi_max = ant_div_max_rssi; + dm_dig->rssi_max = max_rssi; +} + +static void rtl88e_set_next_mac_address_target(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_dm *rtldm = rtl_dm(rtl_priv(hw)); + struct rtl_sta_info *drv_priv; + struct fast_ant_training *fat_tbl = &(rtldm->fat_table); + u32 value32, i, j = 0; + + if (mac->link_state >= MAC80211_LINKED) { + for (i = 0; i < ASSOCIATE_ENTRY_NUM; i++) { + if ((fat_tbl->train_idx + 1) == ASSOCIATE_ENTRY_NUM) + fat_tbl->train_idx = 0; + else + fat_tbl->train_idx++; + + if (fat_tbl->train_idx == 0) { + value32 = (mac->mac_addr[5] << 8) | + mac->mac_addr[4]; + rtl_set_bbreg(hw, DM_REG_ANT_TRAIN_2, + MASKLWORD, value32); + + value32 = (mac->mac_addr[3] << 24) | + (mac->mac_addr[2] << 16) | + (mac->mac_addr[1] << 8) | + mac->mac_addr[0]; + rtl_set_bbreg(hw, DM_REG_ANT_TRAIN_1, + MASKDWORD, value32); + break; + } + + if (rtlpriv->mac80211.opmode != + NL80211_IFTYPE_STATION) { + spin_lock_bh(&rtlpriv->locks.entry_list_lock); + list_for_each_entry(drv_priv, + &rtlpriv->entry_list, + list) { + j++; + if (j != fat_tbl->train_idx) + continue; + + value32 = (drv_priv->mac_addr[5] << 8) | + drv_priv->mac_addr[4]; + rtl_set_bbreg(hw, DM_REG_ANT_TRAIN_2, + MASKLWORD, value32); + + value32 = (drv_priv->mac_addr[3]<<24) | + (drv_priv->mac_addr[2]<<16) | + (drv_priv->mac_addr[1]<<8) | + drv_priv->mac_addr[0]; + rtl_set_bbreg(hw, DM_REG_ANT_TRAIN_1, + MASKDWORD, value32); + break; + } + spin_unlock_bh(&rtlpriv->locks.entry_list_lock); + /*find entry, break*/ + if (j == fat_tbl->train_idx) + break; + } + } + } +} + +static void rtl88e_dm_fast_ant_training(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_dm *rtldm = rtl_dm(rtl_priv(hw)); + struct fast_ant_training *fat_tbl = &(rtldm->fat_table); + u32 i, max_rssi = 0; + u8 target_ant = 2; + bool bpkt_filter_match = false; + + if (fat_tbl->fat_state == FAT_TRAINING_STATE) { + for (i = 0; i < 7; i++) { + if (fat_tbl->ant_cnt[i] == 0) { + fat_tbl->ant_ave[i] = 0; + } else { + fat_tbl->ant_ave[i] = fat_tbl->ant_sum[i] / + fat_tbl->ant_cnt[i]; + bpkt_filter_match = true; + } + + if (fat_tbl->ant_ave[i] > max_rssi) { + max_rssi = fat_tbl->ant_ave[i]; + target_ant = (u8) i; + } + } + + if (bpkt_filter_match == false) { + rtl_set_bbreg(hw, DM_REG_TXAGC_A_1_MCS32_11N, + BIT(16), 0); + rtl_set_bbreg(hw, DM_REG_IGI_A_11N, BIT(7), 0); + } else { + rtl_set_bbreg(hw, DM_REG_TXAGC_A_1_MCS32_11N, + BIT(16), 0); + rtl_set_bbreg(hw, DM_REG_RX_ANT_CTRL_11N, BIT(8) | + BIT(7) | BIT(6), target_ant); + rtl_set_bbreg(hw, DM_REG_TX_ANT_CTRL_11N, BIT(21), 1); + + fat_tbl->antsel_a[fat_tbl->train_idx] = + target_ant & BIT(0); + fat_tbl->antsel_b[fat_tbl->train_idx] = + (target_ant & BIT(1)) >> 1; + fat_tbl->antsel_c[fat_tbl->train_idx] = + (target_ant & BIT(2)) >> 2; + + if (target_ant == 0) + rtl_set_bbreg(hw, DM_REG_IGI_A_11N, BIT(7), 0); + } + + for (i = 0; i < 7; i++) { + fat_tbl->ant_sum[i] = 0; + fat_tbl->ant_cnt[i] = 0; + } + + fat_tbl->fat_state = FAT_NORMAL_STATE; + return; + } + + if (fat_tbl->fat_state == FAT_NORMAL_STATE) { + rtl88e_set_next_mac_address_target(hw); + + fat_tbl->fat_state = FAT_TRAINING_STATE; + rtl_set_bbreg(hw, DM_REG_TXAGC_A_1_MCS32_11N, BIT(16), 1); + rtl_set_bbreg(hw, DM_REG_IGI_A_11N, BIT(7), 1); + + mod_timer(&rtlpriv->works.fast_antenna_training_timer, + jiffies + MSECS(RTL_WATCH_DOG_TIME)); + } +} + +void rtl88e_dm_fast_antenna_training_callback(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + + rtl88e_dm_fast_ant_training(hw); +} + +static void rtl88e_dm_antenna_diversity(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_dm *rtldm = rtl_dm(rtl_priv(hw)); + struct fast_ant_training *fat_tbl = &(rtldm->fat_table); + + if (mac->link_state < MAC80211_LINKED) { + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, "No Link\n"); + if (fat_tbl->becomelinked == true) { + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, + "need to turn off HW AntDiv\n"); + rtl_set_bbreg(hw, DM_REG_IGI_A_11N, BIT(7), 0); + rtl_set_bbreg(hw, DM_REG_CCK_ANTDIV_PARA1_11N, + BIT(15), 0); + if (rtlefuse->antenna_div_type == CG_TRX_HW_ANTDIV) + rtl_set_bbreg(hw, DM_REG_TX_ANT_CTRL_11N, + BIT(21), 0); + fat_tbl->becomelinked = + (mac->link_state == MAC80211_LINKED) ? true : false; + } + return; + } else { + if (fat_tbl->becomelinked == false) { + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, + "Need to turn on HW AntDiv\n"); + rtl_set_bbreg(hw, DM_REG_IGI_A_11N, BIT(7), 1); + rtl_set_bbreg(hw, DM_REG_CCK_ANTDIV_PARA1_11N, + BIT(15), 1); + if (rtlefuse->antenna_div_type == CG_TRX_HW_ANTDIV) + rtl_set_bbreg(hw, DM_REG_TX_ANT_CTRL_11N, + BIT(21), 1); + fat_tbl->becomelinked = + (mac->link_state >= MAC80211_LINKED) ? true : false; + } + } + + if ((rtlefuse->antenna_div_type == CG_TRX_HW_ANTDIV) || + (rtlefuse->antenna_div_type == CGCS_RX_HW_ANTDIV)) + rtl88e_dm_hw_ant_div(hw); + else if (rtlefuse->antenna_div_type == CG_TRX_SMART_ANTDIV) + rtl88e_dm_fast_ant_training(hw); +} + +void rtl88e_dm_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.dm_type = DM_TYPE_BYDRIVER; + rtl88e_dm_diginit(hw); + rtl88e_dm_init_dynamic_txpower(hw); + rtl88e_dm_init_edca_turbo(hw); + rtl88e_dm_init_rate_adaptive_mask(hw); + rtl88e_dm_init_txpower_tracking(hw); + rtl92c_dm_init_dynamic_bb_powersaving(hw); + rtl88e_dm_antenna_div_init(hw); +} + +void rtl88e_dm_watchdog(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + bool fw_current_inpsmode = false; + bool fw_ps_awake = true; + + rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_FW_PSMODE_STATUS, + (u8 *)(&fw_current_inpsmode)); + rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_FWLPS_RF_ON, + (u8 *)(&fw_ps_awake)); + if (ppsc->p2p_ps_info.p2p_ps_mode) + fw_ps_awake = false; + + if ((ppsc->rfpwr_state == ERFON) && + ((!fw_current_inpsmode) && fw_ps_awake) && + (!ppsc->rfchange_inprogress)) { + rtl88e_dm_pwdb_monitor(hw); + rtl88e_dm_dig(hw); + rtl88e_dm_false_alarm_counter_statistics(hw); + rtl92c_dm_dynamic_txpower(hw); + rtl88e_dm_check_txpower_tracking(hw); + rtl88e_dm_refresh_rate_adaptive_mask(hw); + rtl88e_dm_check_edca_turbo(hw); + rtl88e_dm_antenna_diversity(hw); + } +} diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/dm.h b/drivers/net/wireless/rtlwifi/rtl8188ee/dm.h new file mode 100644 index 0000000..0e07f72 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/dm.h @@ -0,0 +1,326 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL88E_DM_H__ +#define __RTL88E_DM_H__ + +#define MAIN_ANT 0 +#define AUX_ANT 1 +#define MAIN_ANT_CG_TRX 1 +#define AUX_ANT_CG_TRX 0 +#define MAIN_ANT_CGCS_RX 0 +#define AUX_ANT_CGCS_RX 1 + +/*RF REG LIST*/ +#define DM_REG_RF_MODE_11N 0x00 +#define DM_REG_RF_0B_11N 0x0B +#define DM_REG_CHNBW_11N 0x18 +#define DM_REG_T_METER_11N 0x24 +#define DM_REG_RF_25_11N 0x25 +#define DM_REG_RF_26_11N 0x26 +#define DM_REG_RF_27_11N 0x27 +#define DM_REG_RF_2B_11N 0x2B +#define DM_REG_RF_2C_11N 0x2C +#define DM_REG_RXRF_A3_11N 0x3C +#define DM_REG_T_METER_92D_11N 0x42 +#define DM_REG_T_METER_88E_11N 0x42 + +/*BB REG LIST*/ +/*PAGE 8 */ +#define DM_REG_BB_CTRL_11N 0x800 +#define DM_REG_RF_PIN_11N 0x804 +#define DM_REG_PSD_CTRL_11N 0x808 +#define DM_REG_TX_ANT_CTRL_11N 0x80C +#define DM_REG_BB_PWR_SAV5_11N 0x818 +#define DM_REG_CCK_RPT_FORMAT_11N 0x824 +#define DM_REG_RX_DEFAULT_A_11N 0x858 +#define DM_REG_RX_DEFAULT_B_11N 0x85A +#define DM_REG_BB_PWR_SAV3_11N 0x85C +#define DM_REG_ANTSEL_CTRL_11N 0x860 +#define DM_REG_RX_ANT_CTRL_11N 0x864 +#define DM_REG_PIN_CTRL_11N 0x870 +#define DM_REG_BB_PWR_SAV1_11N 0x874 +#define DM_REG_ANTSEL_PATH_11N 0x878 +#define DM_REG_BB_3WIRE_11N 0x88C +#define DM_REG_SC_CNT_11N 0x8C4 +#define DM_REG_PSD_DATA_11N 0x8B4 +/*PAGE 9*/ +#define DM_REG_ANT_MAPPING1_11N 0x914 +#define DM_REG_ANT_MAPPING2_11N 0x918 +/*PAGE A*/ +#define DM_REG_CCK_ANTDIV_PARA1_11N 0xA00 +#define DM_REG_CCK_CCA_11N 0xA0A +#define DM_REG_CCK_ANTDIV_PARA2_11N 0xA0C +#define DM_REG_CCK_ANTDIV_PARA3_11N 0xA10 +#define DM_REG_CCK_ANTDIV_PARA4_11N 0xA14 +#define DM_REG_CCK_FILTER_PARA1_11N 0xA22 +#define DM_REG_CCK_FILTER_PARA2_11N 0xA23 +#define DM_REG_CCK_FILTER_PARA3_11N 0xA24 +#define DM_REG_CCK_FILTER_PARA4_11N 0xA25 +#define DM_REG_CCK_FILTER_PARA5_11N 0xA26 +#define DM_REG_CCK_FILTER_PARA6_11N 0xA27 +#define DM_REG_CCK_FILTER_PARA7_11N 0xA28 +#define DM_REG_CCK_FILTER_PARA8_11N 0xA29 +#define DM_REG_CCK_FA_RST_11N 0xA2C +#define DM_REG_CCK_FA_MSB_11N 0xA58 +#define DM_REG_CCK_FA_LSB_11N 0xA5C +#define DM_REG_CCK_CCA_CNT_11N 0xA60 +#define DM_REG_BB_PWR_SAV4_11N 0xA74 +/*PAGE B */ +#define DM_REG_LNA_SWITCH_11N 0xB2C +#define DM_REG_PATH_SWITCH_11N 0xB30 +#define DM_REG_RSSI_CTRL_11N 0xB38 +#define DM_REG_CONFIG_ANTA_11N 0xB68 +#define DM_REG_RSSI_BT_11N 0xB9C +/*PAGE C */ +#define DM_REG_OFDM_FA_HOLDC_11N 0xC00 +#define DM_REG_RX_PATH_11N 0xC04 +#define DM_REG_TRMUX_11N 0xC08 +#define DM_REG_OFDM_FA_RSTC_11N 0xC0C +#define DM_REG_RXIQI_MATRIX_11N 0xC14 +#define DM_REG_TXIQK_MATRIX_LSB1_11N 0xC4C +#define DM_REG_IGI_A_11N 0xC50 +#define DM_REG_ANTDIV_PARA2_11N 0xC54 +#define DM_REG_IGI_B_11N 0xC58 +#define DM_REG_ANTDIV_PARA3_11N 0xC5C +#define DM_REG_BB_PWR_SAV2_11N 0xC70 +#define DM_REG_RX_OFF_11N 0xC7C +#define DM_REG_TXIQK_MATRIXA_11N 0xC80 +#define DM_REG_TXIQK_MATRIXB_11N 0xC88 +#define DM_REG_TXIQK_MATRIXA_LSB2_11N 0xC94 +#define DM_REG_TXIQK_MATRIXB_LSB2_11N 0xC9C +#define DM_REG_RXIQK_MATRIX_LSB_11N 0xCA0 +#define DM_REG_ANTDIV_PARA1_11N 0xCA4 +#define DM_REG_OFDM_FA_TYPE1_11N 0xCF0 +/*PAGE D */ +#define DM_REG_OFDM_FA_RSTD_11N 0xD00 +#define DM_REG_OFDM_FA_TYPE2_11N 0xDA0 +#define DM_REG_OFDM_FA_TYPE3_11N 0xDA4 +#define DM_REG_OFDM_FA_TYPE4_11N 0xDA8 +/*PAGE E */ +#define DM_REG_TXAGC_A_6_18_11N 0xE00 +#define DM_REG_TXAGC_A_24_54_11N 0xE04 +#define DM_REG_TXAGC_A_1_MCS32_11N 0xE08 +#define DM_REG_TXAGC_A_MCS0_3_11N 0xE10 +#define DM_REG_TXAGC_A_MCS4_7_11N 0xE14 +#define DM_REG_TXAGC_A_MCS8_11_11N 0xE18 +#define DM_REG_TXAGC_A_MCS12_15_11N 0xE1C +#define DM_REG_FPGA0_IQK_11N 0xE28 +#define DM_REG_TXIQK_TONE_A_11N 0xE30 +#define DM_REG_RXIQK_TONE_A_11N 0xE34 +#define DM_REG_TXIQK_PI_A_11N 0xE38 +#define DM_REG_RXIQK_PI_A_11N 0xE3C +#define DM_REG_TXIQK_11N 0xE40 +#define DM_REG_RXIQK_11N 0xE44 +#define DM_REG_IQK_AGC_PTS_11N 0xE48 +#define DM_REG_IQK_AGC_RSP_11N 0xE4C +#define DM_REG_BLUETOOTH_11N 0xE6C +#define DM_REG_RX_WAIT_CCA_11N 0xE70 +#define DM_REG_TX_CCK_RFON_11N 0xE74 +#define DM_REG_TX_CCK_BBON_11N 0xE78 +#define DM_REG_OFDM_RFON_11N 0xE7C +#define DM_REG_OFDM_BBON_11N 0xE80 +#define DM_REG_TX2RX_11N 0xE84 +#define DM_REG_TX2TX_11N 0xE88 +#define DM_REG_RX_CCK_11N 0xE8C +#define DM_REG_RX_OFDM_11N 0xED0 +#define DM_REG_RX_WAIT_RIFS_11N 0xED4 +#define DM_REG_RX2RX_11N 0xED8 +#define DM_REG_STANDBY_11N 0xEDC +#define DM_REG_SLEEP_11N 0xEE0 +#define DM_REG_PMPD_ANAEN_11N 0xEEC + + +/*MAC REG LIST*/ +#define DM_REG_BB_RST_11N 0x02 +#define DM_REG_ANTSEL_PIN_11N 0x4C +#define DM_REG_EARLY_MODE_11N 0x4D0 +#define DM_REG_RSSI_MONITOR_11N 0x4FE +#define DM_REG_EDCA_VO_11N 0x500 +#define DM_REG_EDCA_VI_11N 0x504 +#define DM_REG_EDCA_BE_11N 0x508 +#define DM_REG_EDCA_BK_11N 0x50C +#define DM_REG_TXPAUSE_11N 0x522 +#define DM_REG_RESP_TX_11N 0x6D8 +#define DM_REG_ANT_TRAIN_1 0x7b0 +#define DM_REG_ANT_TRAIN_2 0x7b4 + +/*DIG Related*/ +#define DM_BIT_IGI_11N 0x0000007F + +#define HAL_DM_DIG_DISABLE BIT(0) +#define HAL_DM_HIPWR_DISABLE BIT(1) + +#define OFDM_TABLE_LENGTH 43 +#define CCK_TABLE_LENGTH 33 + +#define OFDM_TABLE_SIZE 43 +#define CCK_TABLE_SIZE 33 + +#define BW_AUTO_SWITCH_HIGH_LOW 25 +#define BW_AUTO_SWITCH_LOW_HIGH 30 + +#define DM_DIG_THRESH_HIGH 40 +#define DM_DIG_THRESH_LOW 35 + +#define DM_FALSEALARM_THRESH_LOW 400 +#define DM_FALSEALARM_THRESH_HIGH 1000 + +#define DM_DIG_MAX 0x3e +#define DM_DIG_MIN 0x1e + +#define DM_DIG_MAX_AP 0x32 +#define DM_DIG_MIN_AP 0x20 + +#define DM_DIG_FA_UPPER 0x3e +#define DM_DIG_FA_LOWER 0x1e +#define DM_DIG_FA_TH0 0x200 +#define DM_DIG_FA_TH1 0x300 +#define DM_DIG_FA_TH2 0x400 + +#define DM_DIG_BACKOFF_MAX 12 +#define DM_DIG_BACKOFF_MIN -4 +#define DM_DIG_BACKOFF_DEFAULT 10 + +#define RXPATHSELECTION_SS_TH_LOW 30 +#define RXPATHSELECTION_DIFF_TH 18 + +#define DM_RATR_STA_INIT 0 +#define DM_RATR_STA_HIGH 1 +#define DM_RATR_STA_MIDDLE 2 +#define DM_RATR_STA_LOW 3 + +#define CTS2SELF_THVAL 30 +#define REGC38_TH 20 + +#define WAIOTTHVAL 25 + +#define TXHIGHPWRLEVEL_NORMAL 0 +#define TXHIGHPWRLEVEL_LEVEL1 1 +#define TXHIGHPWRLEVEL_LEVEL2 2 +#define TXHIGHPWRLEVEL_BT1 3 +#define TXHIGHPWRLEVEL_BT2 4 + +#define DM_TYPE_BYFW 0 +#define DM_TYPE_BYDRIVER 1 + +#define TX_POWER_NEAR_FIELD_THRESH_LVL2 74 +#define TX_POWER_NEAR_FIELD_THRESH_LVL1 67 +#define TXPWRTRACK_MAX_IDX 6 + +struct swat_t { + u8 failure_cnt; + u8 try_flag; + u8 stop_trying; + long pre_rssi; + long trying_threshold; + u8 cur_antenna; + u8 pre_antenna; +}; + +enum FAT_STATE { + FAT_NORMAL_STATE = 0, + FAT_TRAINING_STATE = 1, +}; + +enum tag_dynamic_init_gain_operation_type_definition { + DIG_TYPE_THRESH_HIGH = 0, + DIG_TYPE_THRESH_LOW = 1, + DIG_TYPE_BACKOFF = 2, + DIG_TYPE_RX_GAIN_MIN = 3, + DIG_TYPE_RX_GAIN_MAX = 4, + DIG_TYPE_ENABLE = 5, + DIG_TYPE_DISABLE = 6, + DIG_OP_TYPE_MAX +}; + +enum tag_cck_packet_detection_threshold_type_definition { + CCK_PD_STAGE_LOWRSSI = 0, + CCK_PD_STAGE_HIGHRSSI = 1, + CCK_FA_STAGE_LOW = 2, + CCK_FA_STAGE_HIGH = 3, + CCK_PD_STAGE_MAX = 4, +}; + +enum dm_1r_cca_e { + CCA_1R = 0, + CCA_2R = 1, + CCA_MAX = 2, +}; + +enum dm_rf_e { + RF_SAVE = 0, + RF_NORMAL = 1, + RF_MAX = 2, +}; + +enum dm_sw_ant_switch_e { + ANS_ANTENNA_B = 1, + ANS_ANTENNA_A = 2, + ANS_ANTENNA_MAX = 3, +}; + +enum dm_dig_ext_port_alg_e { + DIG_EXT_PORT_STAGE_0 = 0, + DIG_EXT_PORT_STAGE_1 = 1, + DIG_EXT_PORT_STAGE_2 = 2, + DIG_EXT_PORT_STAGE_3 = 3, + DIG_EXT_PORT_STAGE_MAX = 4, +}; + +enum dm_dig_connect_e { + DIG_STA_DISCONNECT = 0, + DIG_STA_CONNECT = 1, + DIG_STA_BEFORE_CONNECT = 2, + DIG_MULTISTA_DISCONNECT = 3, + DIG_MULTISTA_CONNECT = 4, + DIG_CONNECT_MAX +}; + +enum pwr_track_control_method { + BBSWING, + TXAGC +}; + +void rtl88e_dm_set_tx_ant_by_tx_info(struct ieee80211_hw *hw, + u8 *pdesc, u32 mac_id); +void rtl88e_dm_ant_sel_statistics(struct ieee80211_hw *hw, u8 antsel_tr_mux, + u32 mac_id, u32 rx_pwdb_all); +void rtl88e_dm_fast_antenna_training_callback(unsigned long data); +void rtl88e_dm_init(struct ieee80211_hw *hw); +void rtl88e_dm_watchdog(struct ieee80211_hw *hw); +void rtl88e_dm_write_dig(struct ieee80211_hw *hw); +void rtl88e_dm_init_edca_turbo(struct ieee80211_hw *hw); +void rtl88e_dm_check_txpower_tracking(struct ieee80211_hw *hw); +void rtl88e_dm_init_rate_adaptive_mask(struct ieee80211_hw *hw); +void rtl88e_dm_txpower_track_adjust(struct ieee80211_hw *hw, + u8 type, u8 *pdirection, + u32 *poutwrite_val); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/fw.c b/drivers/net/wireless/rtlwifi/rtl8188ee/fw.c new file mode 100644 index 0000000..57e4cc5 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/fw.c @@ -0,0 +1,830 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "../base.h" +#include "reg.h" +#include "def.h" +#include "fw.h" + +#include + +static void _rtl88e_enable_fw_download(struct ieee80211_hw *hw, bool enable) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tmp; + + if (enable) { + tmp = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN + 1); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN + 1, tmp | 0x04); + + tmp = rtl_read_byte(rtlpriv, REG_MCUFWDL); + rtl_write_byte(rtlpriv, REG_MCUFWDL, tmp | 0x01); + + tmp = rtl_read_byte(rtlpriv, REG_MCUFWDL + 2); + rtl_write_byte(rtlpriv, REG_MCUFWDL + 2, tmp & 0xf7); + } else { + tmp = rtl_read_byte(rtlpriv, REG_MCUFWDL); + rtl_write_byte(rtlpriv, REG_MCUFWDL, tmp & 0xfe); + + rtl_write_byte(rtlpriv, REG_MCUFWDL + 1, 0x00); + } +} + +static void _rtl88e_fw_block_write(struct ieee80211_hw *hw, + const u8 *buffer, u32 size) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 blk_sz = sizeof(u32); + u8 *buf_ptr = (u8 *)buffer; + u32 *pu4BytePtr = (u32 *)buffer; + u32 i, offset, blk_cnt, remain; + + blk_cnt = size / blk_sz; + remain = size % blk_sz; + + for (i = 0; i < blk_cnt; i++) { + offset = i * blk_sz; + rtl_write_dword(rtlpriv, (FW_8192C_START_ADDRESS + offset), + *(pu4BytePtr + i)); + } + + if (remain) { + offset = blk_cnt * blk_sz; + buf_ptr += offset; + for (i = 0; i < remain; i++) { + rtl_write_byte(rtlpriv, (FW_8192C_START_ADDRESS + + offset + i), *(buf_ptr + i)); + } + } +} + +static void _rtl88e_fw_page_write(struct ieee80211_hw *hw, + u32 page, const u8 *buffer, u32 size) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 value8; + u8 u8page = (u8) (page & 0x07); + + value8 = (rtl_read_byte(rtlpriv, REG_MCUFWDL + 2) & 0xF8) | u8page; + + rtl_write_byte(rtlpriv, (REG_MCUFWDL + 2), value8); + _rtl88e_fw_block_write(hw, buffer, size); +} + +static void _rtl88e_fill_dummy(u8 *pfwbuf, u32 *pfwlen) +{ + u32 fwlen = *pfwlen; + u8 remain = (u8) (fwlen % 4); + + remain = (remain == 0) ? 0 : (4 - remain); + + while (remain > 0) { + pfwbuf[fwlen] = 0; + fwlen++; + remain--; + } + + *pfwlen = fwlen; +} + +static void _rtl88e_write_fw(struct ieee80211_hw *hw, + enum version_8188e version, u8 *buffer, u32 size) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 *buf_ptr = (u8 *)buffer; + u32 page_no, remain; + u32 page, offset; + + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, "FW size is %d bytes,\n", size); + + _rtl88e_fill_dummy(buf_ptr, &size); + + page_no = size / FW_8192C_PAGE_SIZE; + remain = size % FW_8192C_PAGE_SIZE; + + if (page_no > 8) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Page numbers should not greater then 8\n"); + } + + for (page = 0; page < page_no; page++) { + offset = page * FW_8192C_PAGE_SIZE; + _rtl88e_fw_page_write(hw, page, (buf_ptr + offset), + FW_8192C_PAGE_SIZE); + } + + if (remain) { + offset = page_no * FW_8192C_PAGE_SIZE; + page = page_no; + _rtl88e_fw_page_write(hw, page, (buf_ptr + offset), remain); + } +} + +static int _rtl88e_fw_free_to_go(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int err = -EIO; + u32 counter = 0; + u32 value32; + + do { + value32 = rtl_read_dword(rtlpriv, REG_MCUFWDL); + } while ((counter++ < FW_8192C_POLLING_TIMEOUT_COUNT) && + (!(value32 & FWDL_CHKSUM_RPT))); + + if (counter >= FW_8192C_POLLING_TIMEOUT_COUNT) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "chksum report faill ! REG_MCUFWDL:0x%08x .\n", + value32); + goto exit; + } + + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, + "Checksum report OK ! REG_MCUFWDL:0x%08x .\n", value32); + + value32 = rtl_read_dword(rtlpriv, REG_MCUFWDL); + value32 |= MCUFWDL_RDY; + value32 &= ~WINTINI_RDY; + rtl_write_dword(rtlpriv, REG_MCUFWDL, value32); + + rtl88e_firmware_selfreset(hw); + counter = 0; + + do { + value32 = rtl_read_dword(rtlpriv, REG_MCUFWDL); + if (value32 & WINTINI_RDY) { + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, + "Polling FW ready success!! REG_MCUFWDL:0x%08x.\n", + value32); + err = 0; + goto exit; + } + + udelay(FW_8192C_POLLING_DELAY); + + } while (counter++ < FW_8192C_POLLING_TIMEOUT_COUNT); + + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Polling FW ready fail!! REG_MCUFWDL:0x%08x .\n", value32); + +exit: + return err; +} + +int rtl88e_download_fw(struct ieee80211_hw *hw, bool buse_wake_on_wlan_fw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl92c_firmware_header *pfwheader; + u8 *pfwdata; + u32 fwsize; + int err; + enum version_8188e version = rtlhal->version; + + if (!rtlhal->pfirmware) + return 1; + + pfwheader = (struct rtl92c_firmware_header *)rtlhal->pfirmware; + pfwdata = (u8 *)rtlhal->pfirmware; + fwsize = rtlhal->fwsize; + RT_TRACE(rtlpriv, COMP_FW, DBG_DMESG, + "normal Firmware SIZE %d\n", fwsize); + + if (IS_FW_HEADER_EXIST(pfwheader)) { + RT_TRACE(rtlpriv, COMP_FW, DBG_DMESG, + "Firmware Version(%d), Signature(%#x), Size(%d)\n", + pfwheader->version, pfwheader->signature, + (int)sizeof(struct rtl92c_firmware_header)); + + pfwdata = pfwdata + sizeof(struct rtl92c_firmware_header); + fwsize = fwsize - sizeof(struct rtl92c_firmware_header); + } + + if (rtl_read_byte(rtlpriv, REG_MCUFWDL) & BIT(7)) { + rtl_write_byte(rtlpriv, REG_MCUFWDL, 0); + rtl88e_firmware_selfreset(hw); + } + _rtl88e_enable_fw_download(hw, true); + _rtl88e_write_fw(hw, version, pfwdata, fwsize); + _rtl88e_enable_fw_download(hw, false); + + err = _rtl88e_fw_free_to_go(hw); + + RT_TRACE(rtlpriv, COMP_FW, DBG_DMESG, + "Firmware is%s ready to run!\n", err ? " not" : ""); + return 0; +} + +static bool _rtl88e_check_fw_read_last_h2c(struct ieee80211_hw *hw, u8 boxnum) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 val_hmetfr; + + val_hmetfr = rtl_read_byte(rtlpriv, REG_HMETFR); + if (((val_hmetfr >> boxnum) & BIT(0)) == 0) + return true; + return false; +} + +static void _rtl88e_fill_h2c_command(struct ieee80211_hw *hw, + u8 element_id, u32 cmd_len, + u8 *cmd_b) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 boxnum; + u16 box_reg = 0, box_extreg = 0; + u8 u1b_tmp; + bool isfw_read = false; + u8 buf_index = 0; + bool write_sucess = false; + u8 wait_h2c_limit = 100; + u8 wait_writeh2c_limit = 100; + u8 boxc[4], boxext[2]; + u32 h2c_waitcounter = 0; + unsigned long flag; + u8 idx; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "come in\n"); + + while (true) { + spin_lock_irqsave(&rtlpriv->locks.h2c_lock, flag); + if (rtlhal->h2c_setinprogress) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "H2C set in progress! Wait to set..element_id(%d).\n", + element_id); + + while (rtlhal->h2c_setinprogress) { + spin_unlock_irqrestore(&rtlpriv->locks.h2c_lock, + flag); + h2c_waitcounter++; + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Wait 100 us (%d times)...\n", + h2c_waitcounter); + udelay(100); + + if (h2c_waitcounter > 1000) + return; + spin_lock_irqsave(&rtlpriv->locks.h2c_lock, + flag); + } + spin_unlock_irqrestore(&rtlpriv->locks.h2c_lock, flag); + } else { + rtlhal->h2c_setinprogress = true; + spin_unlock_irqrestore(&rtlpriv->locks.h2c_lock, flag); + break; + } + } + + while (!write_sucess) { + wait_writeh2c_limit--; + if (wait_writeh2c_limit == 0) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Write H2C fail because no trigger for FW INT!\n"); + break; + } + + boxnum = rtlhal->last_hmeboxnum; + switch (boxnum) { + case 0: + box_reg = REG_HMEBOX_0; + box_extreg = REG_HMEBOX_EXT_0; + break; + case 1: + box_reg = REG_HMEBOX_1; + box_extreg = REG_HMEBOX_EXT_1; + break; + case 2: + box_reg = REG_HMEBOX_2; + box_extreg = REG_HMEBOX_EXT_2; + break; + case 3: + box_reg = REG_HMEBOX_3; + box_extreg = REG_HMEBOX_EXT_3; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + + isfw_read = _rtl88e_check_fw_read_last_h2c(hw, boxnum); + while (!isfw_read) { + wait_h2c_limit--; + if (wait_h2c_limit == 0) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Wating too long for FW read " + "clear HMEBox(%d)!\n", boxnum); + break; + } + + udelay(10); + + isfw_read = _rtl88e_check_fw_read_last_h2c(hw, boxnum); + u1b_tmp = rtl_read_byte(rtlpriv, 0x130); + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Wating for FW read clear HMEBox(%d)!!! " + "0x130 = %2x\n", boxnum, u1b_tmp); + } + + if (!isfw_read) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Write H2C register BOX[%d] fail!!!!! " + "Fw do not read.\n", boxnum); + break; + } + + memset(boxc, 0, sizeof(boxc)); + memset(boxext, 0, sizeof(boxext)); + boxc[0] = element_id; + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Write element_id box_reg(%4x) = %2x\n", + box_reg, element_id); + + switch (cmd_len) { + case 1: + case 2: + case 3: + /*boxc[0] &= ~(BIT(7));*/ + memcpy((u8 *)(boxc) + 1, cmd_b + buf_index, cmd_len); + + for (idx = 0; idx < 4; idx++) + rtl_write_byte(rtlpriv, box_reg+idx, boxc[idx]); + break; + case 4: + case 5: + case 6: + case 7: + /*boxc[0] |= (BIT(7));*/ + memcpy((u8 *)(boxext), cmd_b + buf_index+3, cmd_len-3); + memcpy((u8 *)(boxc) + 1, cmd_b + buf_index, 3); + + for (idx = 0; idx < 2; idx++) { + rtl_write_byte(rtlpriv, box_extreg + idx, + boxext[idx]); + } + + for (idx = 0; idx < 4; idx++) { + rtl_write_byte(rtlpriv, box_reg + idx, + boxc[idx]); + } + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + + write_sucess = true; + + rtlhal->last_hmeboxnum = boxnum + 1; + if (rtlhal->last_hmeboxnum == 4) + rtlhal->last_hmeboxnum = 0; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "pHalData->last_hmeboxnum = %d\n", + rtlhal->last_hmeboxnum); + } + + spin_lock_irqsave(&rtlpriv->locks.h2c_lock, flag); + rtlhal->h2c_setinprogress = false; + spin_unlock_irqrestore(&rtlpriv->locks.h2c_lock, flag); + + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "go out\n"); +} + +void rtl88e_fill_h2c_cmd(struct ieee80211_hw *hw, + u8 element_id, u32 cmd_len, u8 *cmd_b) +{ + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u32 tmp_cmdbuf[2]; + + if (rtlhal->fw_ready == false) { + RT_ASSERT(false, "fail H2C cmd - Fw download fail!!!\n"); + return; + } + + memset(tmp_cmdbuf, 0, 8); + memcpy(tmp_cmdbuf, cmd_b, cmd_len); + _rtl88e_fill_h2c_command(hw, element_id, cmd_len, (u8 *)&tmp_cmdbuf); + + return; +} + +void rtl88e_firmware_selfreset(struct ieee80211_hw *hw) +{ + u8 u1b_tmp; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + u1b_tmp = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN+1); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN+1, (u1b_tmp & (~BIT(2)))); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN+1, (u1b_tmp | BIT(2))); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "8051Reset88E(): 8051 reset success.\n"); +} + +void rtl88e_set_fw_pwrmode_cmd(struct ieee80211_hw *hw, u8 mode) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 u1_h2c_set_pwrmode[H2C_88E_PWEMODE_LENGTH] = { 0 }; + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + u8 power_state = 0; + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, "FW LPS mode = %d\n", mode); + SET_H2CCMD_PWRMODE_PARM_MODE(u1_h2c_set_pwrmode, ((mode) ? 1 : 0)); + SET_H2CCMD_PWRMODE_PARM_RLBM(u1_h2c_set_pwrmode, 0); + SET_H2CCMD_PWRMODE_PARM_SMART_PS(u1_h2c_set_pwrmode, + (rtlpriv->mac80211.p2p) ? + ppsc->smart_ps : 1); + SET_H2CCMD_PWRMODE_PARM_AWAKE_INTERVAL(u1_h2c_set_pwrmode, + ppsc->reg_max_lps_awakeintvl); + SET_H2CCMD_PWRMODE_PARM_ALL_QUEUE_UAPSD(u1_h2c_set_pwrmode, 0); + if (mode == FW_PS_ACTIVE_MODE) + power_state |= FW_PWR_STATE_ACTIVE; + else + power_state |= FW_PWR_STATE_RF_OFF; + SET_H2CCMD_PWRMODE_PARM_PWR_STATE(u1_h2c_set_pwrmode, power_state); + + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_DMESG, + "rtl92c_set_fw_pwrmode(): u1_h2c_set_pwrmode\n", + u1_h2c_set_pwrmode, H2C_88E_PWEMODE_LENGTH); + rtl88e_fill_h2c_cmd(hw, H2C_88E_SETPWRMODE, H2C_88E_PWEMODE_LENGTH, + u1_h2c_set_pwrmode); +} + +void rtl88e_set_fw_joinbss_report_cmd(struct ieee80211_hw *hw, u8 mstatus) +{ + u8 u1_joinbssrpt_parm[1] = { 0 }; + + SET_H2CCMD_JOINBSSRPT_PARM_OPMODE(u1_joinbssrpt_parm, mstatus); + + rtl88e_fill_h2c_cmd(hw, H2C_88E_JOINBSSRPT, 1, u1_joinbssrpt_parm); +} + +void rtl88e_set_fw_ap_off_load_cmd(struct ieee80211_hw *hw, + u8 ap_offload_enable) +{ + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u8 u1_apoffload_parm[H2C_88E_AP_OFFLOAD_LENGTH] = { 0 }; + + SET_H2CCMD_AP_OFFLOAD_ON(u1_apoffload_parm, ap_offload_enable); + SET_H2CCMD_AP_OFFLOAD_HIDDEN(u1_apoffload_parm, mac->hiddenssid); + SET_H2CCMD_AP_OFFLOAD_DENYANY(u1_apoffload_parm, 0); + + rtl88e_fill_h2c_cmd(hw, H2C_88E_AP_OFFLOAD, H2C_88E_AP_OFFLOAD_LENGTH, + u1_apoffload_parm); +} + +static bool _rtl88e_cmd_send_packet(struct ieee80211_hw *hw, + struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl8192_tx_ring *ring; + struct rtl_tx_desc *pdesc; + struct sk_buff *pskb = NULL; + unsigned long flags; + + ring = &rtlpci->tx_ring[BEACON_QUEUE]; + + pskb = __skb_dequeue(&ring->queue); + if (pskb) + kfree_skb(pskb); + + spin_lock_irqsave(&rtlpriv->locks.irq_th_lock, flags); + + pdesc = &ring->desc[0]; + + rtlpriv->cfg->ops->fill_tx_cmddesc(hw, (u8 *)pdesc, 1, 1, skb); + + __skb_queue_tail(&ring->queue, skb); + + spin_unlock_irqrestore(&rtlpriv->locks.irq_th_lock, flags); + + rtlpriv->cfg->ops->tx_polling(hw, BEACON_QUEUE); + + return true; +} + +#define BEACON_PG 0 /* ->1 */ +#define PSPOLL_PG 2 +#define NULL_PG 3 +#define PROBERSP_PG 4 /* ->5 */ + +#define TOTAL_RESERVED_PKT_LEN 768 + +static u8 reserved_page_packet[TOTAL_RESERVED_PKT_LEN] = { + /* page 0 beacon */ + 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xE0, 0x4C, 0x76, 0x00, 0x42, + 0x00, 0x40, 0x10, 0x10, 0x00, 0x03, 0x50, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x64, 0x00, 0x00, 0x04, 0x00, 0x0C, 0x6C, 0x69, + 0x6E, 0x6B, 0x73, 0x79, 0x73, 0x5F, 0x77, 0x6C, + 0x61, 0x6E, 0x01, 0x04, 0x82, 0x84, 0x8B, 0x96, + 0x03, 0x01, 0x01, 0x06, 0x02, 0x00, 0x00, 0x2A, + 0x01, 0x00, 0x32, 0x08, 0x24, 0x30, 0x48, 0x6C, + 0x0C, 0x12, 0x18, 0x60, 0x2D, 0x1A, 0x6C, 0x18, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3D, 0x00, 0xDD, 0x06, 0x00, 0xE0, 0x4C, 0x02, + 0x01, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 1 beacon */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x20, 0x8C, 0x00, 0x12, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 2 ps-poll */ + 0xA4, 0x10, 0x01, 0xC0, 0x00, 0x40, 0x10, 0x10, + 0x00, 0x03, 0x00, 0xE0, 0x4C, 0x76, 0x00, 0x42, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x20, 0x8C, 0x00, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 3 null */ + 0x48, 0x01, 0x00, 0x00, 0x00, 0x40, 0x10, 0x10, + 0x00, 0x03, 0x00, 0xE0, 0x4C, 0x76, 0x00, 0x42, + 0x00, 0x40, 0x10, 0x10, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x72, 0x00, 0x20, 0x8C, 0x00, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 4 probe_resp */ + 0x50, 0x00, 0x00, 0x00, 0x00, 0x40, 0x10, 0x10, + 0x00, 0x03, 0x00, 0xE0, 0x4C, 0x76, 0x00, 0x42, + 0x00, 0x40, 0x10, 0x10, 0x00, 0x03, 0x00, 0x00, + 0x9E, 0x46, 0x15, 0x32, 0x27, 0xF2, 0x2D, 0x00, + 0x64, 0x00, 0x00, 0x04, 0x00, 0x0C, 0x6C, 0x69, + 0x6E, 0x6B, 0x73, 0x79, 0x73, 0x5F, 0x77, 0x6C, + 0x61, 0x6E, 0x01, 0x04, 0x82, 0x84, 0x8B, 0x96, + 0x03, 0x01, 0x01, 0x06, 0x02, 0x00, 0x00, 0x2A, + 0x01, 0x00, 0x32, 0x08, 0x24, 0x30, 0x48, 0x6C, + 0x0C, 0x12, 0x18, 0x60, 0x2D, 0x1A, 0x6C, 0x18, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3D, 0x00, 0xDD, 0x06, 0x00, 0xE0, 0x4C, 0x02, + 0x01, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 5 probe_resp */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +void rtl88e_set_fw_rsvdpagepkt(struct ieee80211_hw *hw, bool b_dl_finished) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct sk_buff *skb = NULL; + + u32 totalpacketlen; + u8 u1RsvdPageLoc[5] = { 0 }; + + u8 *beacon; + u8 *pspoll; + u8 *nullfunc; + u8 *probersp; + /*--------------------------------------------------------- + * (1) beacon + *--------------------------------------------------------- + */ + beacon = &reserved_page_packet[BEACON_PG * 128]; + SET_80211_HDR_ADDRESS2(beacon, mac->mac_addr); + SET_80211_HDR_ADDRESS3(beacon, mac->bssid); + + /*------------------------------------------------------- + * (2) ps-poll + *-------------------------------------------------------- + */ + pspoll = &reserved_page_packet[PSPOLL_PG * 128]; + SET_80211_PS_POLL_AID(pspoll, (mac->assoc_id | 0xc000)); + SET_80211_PS_POLL_BSSID(pspoll, mac->bssid); + SET_80211_PS_POLL_TA(pspoll, mac->mac_addr); + + SET_H2CCMD_RSVDPAGE_LOC_PSPOLL(u1RsvdPageLoc, PSPOLL_PG); + + /*-------------------------------------------------------- + * (3) null data + *--------------------------------------------------------- + */ + nullfunc = &reserved_page_packet[NULL_PG * 128]; + SET_80211_HDR_ADDRESS1(nullfunc, mac->bssid); + SET_80211_HDR_ADDRESS2(nullfunc, mac->mac_addr); + SET_80211_HDR_ADDRESS3(nullfunc, mac->bssid); + + SET_H2CCMD_RSVDPAGE_LOC_NULL_DATA(u1RsvdPageLoc, NULL_PG); + + /*--------------------------------------------------------- + * (4) probe response + *---------------------------------------------------------- + */ + probersp = &reserved_page_packet[PROBERSP_PG * 128]; + SET_80211_HDR_ADDRESS1(probersp, mac->bssid); + SET_80211_HDR_ADDRESS2(probersp, mac->mac_addr); + SET_80211_HDR_ADDRESS3(probersp, mac->bssid); + + SET_H2CCMD_RSVDPAGE_LOC_PROBE_RSP(u1RsvdPageLoc, PROBERSP_PG); + + totalpacketlen = TOTAL_RESERVED_PKT_LEN; + + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_LOUD, + "rtl88e_set_fw_rsvdpagepkt(): HW_VAR_SET_TX_CMD: ALL\n", + &reserved_page_packet[0], totalpacketlen); + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_DMESG, + "rtl88e_set_fw_rsvdpagepkt(): HW_VAR_SET_TX_CMD: ALL\n", + u1RsvdPageLoc, 3); + + skb = dev_alloc_skb(totalpacketlen); + if (!skb) + return; + kmemleak_not_leak(skb); + memcpy(skb_put(skb, totalpacketlen), + &reserved_page_packet, totalpacketlen); + + if (_rtl88e_cmd_send_packet(hw, skb)) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "Set RSVD page location to Fw.\n"); + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_DMESG, + "H2C_RSVDPAGE:\n", u1RsvdPageLoc, 3); + rtl88e_fill_h2c_cmd(hw, H2C_88E_RSVDPAGE, + sizeof(u1RsvdPageLoc), u1RsvdPageLoc); + } else + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Set RSVD page location to Fw FAIL!!!!!!.\n"); +} + +/*Shoud check FW support p2p or not.*/ +static void rtl88e_set_p2p_ctw_period_cmd(struct ieee80211_hw *hw, u8 ctwindow) +{ + u8 u1_ctwindow_period[1] = {ctwindow}; + + rtl88e_fill_h2c_cmd(hw, H2C_88E_P2P_PS_CTW_CMD, 1, u1_ctwindow_period); +} + +void rtl88e_set_p2p_ps_offload_cmd(struct ieee80211_hw *hw, u8 p2p_ps_state) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *rtlps = rtl_psc(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_p2p_ps_info *p2pinfo = &(rtlps->p2p_ps_info); + struct p2p_ps_offload_t *p2p_ps_offload = &rtlhal->p2p_ps_offload; + u8 i; + u16 ctwindow; + u32 start_time, tsf_low; + + switch (p2p_ps_state) { + case P2P_PS_DISABLE: + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, "P2P_PS_DISABLE\n"); + memset(p2p_ps_offload, 0, sizeof(struct p2p_ps_offload_t)); + break; + case P2P_PS_ENABLE: + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, "P2P_PS_ENABLE\n"); + /* update CTWindow value. */ + if (p2pinfo->ctwindow > 0) { + p2p_ps_offload->ctwindow_en = 1; + ctwindow = p2pinfo->ctwindow; + rtl88e_set_p2p_ctw_period_cmd(hw, ctwindow); + } + /* hw only support 2 set of NoA */ + for (i = 0; i < p2pinfo->noa_num; i++) { + /* To control the register setting for which NOA*/ + rtl_write_byte(rtlpriv, 0x5cf, (i << 4)); + if (i == 0) + p2p_ps_offload->noa0_en = 1; + else + p2p_ps_offload->noa1_en = 1; + + /* config P2P NoA Descriptor Register */ + rtl_write_dword(rtlpriv, 0x5E0, + p2pinfo->noa_duration[i]); + rtl_write_dword(rtlpriv, 0x5E4, + p2pinfo->noa_interval[i]); + + /*Get Current TSF value */ + tsf_low = rtl_read_dword(rtlpriv, REG_TSFTR); + + start_time = p2pinfo->noa_start_time[i]; + if (p2pinfo->noa_count_type[i] != 1) { + while (start_time <= (tsf_low + (50 * 1024))) { + start_time += p2pinfo->noa_interval[i]; + if (p2pinfo->noa_count_type[i] != 255) + p2pinfo->noa_count_type[i]--; + } + } + rtl_write_dword(rtlpriv, 0x5E8, start_time); + rtl_write_dword(rtlpriv, 0x5EC, + p2pinfo->noa_count_type[i]); + } + + if ((p2pinfo->opp_ps == 1) || (p2pinfo->noa_num > 0)) { + /* rst p2p circuit */ + rtl_write_byte(rtlpriv, REG_DUAL_TSF_RST, BIT(4)); + + p2p_ps_offload->offload_en = 1; + + if (P2P_ROLE_GO == rtlpriv->mac80211.p2p) { + p2p_ps_offload->role = 1; + p2p_ps_offload->allstasleep = 0; + } else { + p2p_ps_offload->role = 0; + } + + p2p_ps_offload->discovery = 0; + } + break; + case P2P_PS_SCAN: + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, "P2P_PS_SCAN\n"); + p2p_ps_offload->discovery = 1; + break; + case P2P_PS_SCAN_DONE: + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, "P2P_PS_SCAN_DONE\n"); + p2p_ps_offload->discovery = 0; + p2pinfo->p2p_ps_state = P2P_PS_ENABLE; + break; + default: + break; + } + + rtl88e_fill_h2c_cmd(hw, H2C_88E_P2P_PS_OFFLOAD, 1, + (u8 *)p2p_ps_offload); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/fw.h b/drivers/net/wireless/rtlwifi/rtl8188ee/fw.h new file mode 100644 index 0000000..854a987 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/fw.h @@ -0,0 +1,301 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92C__FW__H__ +#define __RTL92C__FW__H__ + +#define FW_8192C_SIZE 0x8000 +#define FW_8192C_START_ADDRESS 0x1000 +#define FW_8192C_END_ADDRESS 0x5FFF +#define FW_8192C_PAGE_SIZE 4096 +#define FW_8192C_POLLING_DELAY 5 +#define FW_8192C_POLLING_TIMEOUT_COUNT 3000 + +#define IS_FW_HEADER_EXIST(_pfwhdr) \ + ((_pfwhdr->signature&0xFFFF) == 0x88E1) +#define USE_OLD_WOWLAN_DEBUG_FW 0 + +#define H2C_88E_RSVDPAGE_LOC_LEN 5 +#define H2C_88E_PWEMODE_LENGTH 5 +#define H2C_88E_JOINBSSRPT_LENGTH 1 +#define H2C_88E_AP_OFFLOAD_LENGTH 3 +#define H2C_88E_WOWLAN_LENGTH 3 +#define H2C_88E_KEEP_ALIVE_CTRL_LENGTH 3 +#if (USE_OLD_WOWLAN_DEBUG_FW == 0) +#define H2C_88E_REMOTE_WAKE_CTRL_LEN 1 +#else +#define H2C_88E_REMOTE_WAKE_CTRL_LEN 3 +#endif +#define H2C_88E_AOAC_GLOBAL_INFO_LEN 2 +#define H2C_88E_AOAC_RSVDPAGE_LOC_LEN 7 + +/* Fw PS state for RPWM. + * BIT[2:0] = HW state + * BIT[3] = Protocol PS state, 1: register active state, 0: register sleep state + * BIT[4] = sub-state + */ +#define FW_PS_GO_ON BIT(0) +#define FW_PS_TX_NULL BIT(1) +#define FW_PS_RF_ON BIT(2) +#define FW_PS_REGISTER_ACTIVE BIT(3) + +#define FW_PS_DPS BIT(0) +#define FW_PS_LCLK (FW_PS_DPS) +#define FW_PS_RF_OFF BIT(1) +#define FW_PS_ALL_ON BIT(2) +#define FW_PS_ST_ACTIVE BIT(3) +#define FW_PS_ISR_ENABLE BIT(4) +#define FW_PS_IMR_ENABLE BIT(5) + + +#define FW_PS_ACK BIT(6) +#define FW_PS_TOGGLE BIT(7) + + /* 88E RPWM value*/ + /* BIT[0] = 1: 32k, 0: 40M*/ +#define FW_PS_CLOCK_OFF BIT(0) /* 32k*/ +#define FW_PS_CLOCK_ON 0 /*40M*/ + +#define FW_PS_STATE_MASK (0x0F) +#define FW_PS_STATE_HW_MASK (0x07) +/*ISR_ENABLE, IMR_ENABLE, and PS mode should be inherited.*/ +#define FW_PS_STATE_INT_MASK (0x3F) + +#define FW_PS_STATE(x) (FW_PS_STATE_MASK & (x)) +#define FW_PS_STATE_HW(x) (FW_PS_STATE_HW_MASK & (x)) +#define FW_PS_STATE_INT(x) (FW_PS_STATE_INT_MASK & (x)) +#define FW_PS_ISR_VAL(x) ((x) & 0x70) +#define FW_PS_IMR_MASK(x) ((x) & 0xDF) +#define FW_PS_KEEP_IMR(x) ((x) & 0x20) + +#define FW_PS_STATE_S0 (FW_PS_DPS) +#define FW_PS_STATE_S1 (FW_PS_LCLK) +#define FW_PS_STATE_S2 (FW_PS_RF_OFF) +#define FW_PS_STATE_S3 (FW_PS_ALL_ON) +#define FW_PS_STATE_S4 ((FW_PS_ST_ACTIVE) | (FW_PS_ALL_ON)) + +#define FW_PS_STATE_ALL_ON_88E (FW_PS_CLOCK_ON) +#define FW_PS_STATE_RF_ON_88E (FW_PS_CLOCK_ON) +#define FW_PS_STATE_RF_OFF_88E (FW_PS_CLOCK_ON) +#define FW_PS_STATE_RF_OFF_LOW_PWR_88E (FW_PS_CLOCK_OFF) + +#define FW_PS_STATE_ALL_ON_92C (FW_PS_STATE_S4) +#define FW_PS_STATE_RF_ON_92C (FW_PS_STATE_S3) +#define FW_PS_STATE_RF_OFF_92C (FW_PS_STATE_S2) +#define FW_PS_STATE_RF_OFF_LOW_PWR_92C (FW_PS_STATE_S1) + +/* For 88E H2C PwrMode Cmd ID 5.*/ +#define FW_PWR_STATE_ACTIVE ((FW_PS_RF_ON) | (FW_PS_REGISTER_ACTIVE)) +#define FW_PWR_STATE_RF_OFF 0 + +#define FW_PS_IS_ACK(x) ((x) & FW_PS_ACK) +#define FW_PS_IS_CLK_ON(x) ((x) & (FW_PS_RF_OFF | FW_PS_ALL_ON)) +#define FW_PS_IS_RF_ON(x) ((x) & (FW_PS_ALL_ON)) +#define FW_PS_IS_ACTIVE(x) ((x) & (FW_PS_ST_ACTIVE)) +#define FW_PS_IS_CPWM_INT(x) ((x) & 0x40) + +#define FW_CLR_PS_STATE(x) ((x) = ((x) & (0xF0))) + +#define IS_IN_LOW_POWER_STATE_88E(fwpsstate) \ + (FW_PS_STATE(fwpsstate) == FW_PS_CLOCK_OFF) + +#define FW_PWR_STATE_ACTIVE ((FW_PS_RF_ON) | (FW_PS_REGISTER_ACTIVE)) +#define FW_PWR_STATE_RF_OFF 0 + +struct rtl92c_firmware_header { + u16 signature; + u8 category; + u8 function; + u16 version; + u8 subversion; + u8 rsvd1; + u8 month; + u8 date; + u8 hour; + u8 minute; + u16 ramcodesize; + u16 rsvd2; + u32 svnindex; + u32 rsvd3; + u32 rsvd4; + u32 rsvd5; +}; + +enum rtl8192c_h2c_cmd { + H2C_88E_RSVDPAGE = 0, + H2C_88E_JOINBSSRPT = 1, + H2C_88E_SCAN = 2, + H2C_88E_KEEP_ALIVE_CTRL = 3, + H2C_88E_DISCONNECT_DECISION = 4, +#if (USE_OLD_WOWLAN_DEBUG_FW == 1) + H2C_88E_WO_WLAN = 5, +#endif + H2C_88E_INIT_OFFLOAD = 6, +#if (USE_OLD_WOWLAN_DEBUG_FW == 1) + H2C_88E_REMOTE_WAKE_CTRL = 7, +#endif + H2C_88E_AP_OFFLOAD = 8, + H2C_88E_BCN_RSVDPAGE = 9, + H2C_88E_PROBERSP_RSVDPAGE = 10, + + H2C_88E_SETPWRMODE = 0x20, + H2C_88E_PS_TUNING_PARA = 0x21, + H2C_88E_PS_TUNING_PARA2 = 0x22, + H2C_88E_PS_LPS_PARA = 0x23, + H2C_88E_P2P_PS_OFFLOAD = 024, + +#if (USE_OLD_WOWLAN_DEBUG_FW == 0) + H2C_88E_WO_WLAN = 0x80, + H2C_88E_REMOTE_WAKE_CTRL = 0x81, + H2C_88E_AOAC_GLOBAL_INFO = 0x82, + H2C_88E_AOAC_RSVDPAGE = 0x83, +#endif + /* Not defined in new 88E H2C CMD Format */ + H2C_88E_RA_MASK, + H2C_88E_SELECTIVE_SUSPEND_ROF_CMD, + H2C_88E_P2P_PS_MODE, + H2C_88E_PSD_RESULT, + /*Not defined CTW CMD for P2P yet*/ + H2C_88E_P2P_PS_CTW_CMD, + MAX_88E_H2CCMD +}; + +#define pagenum_128(_len) (u32)(((_len)>>7) + ((_len)&0x7F ? 1 : 0)) + +#define SET_88E_H2CCMD_WOWLAN_FUNC_ENABLE(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE(__cmd, 0, 1, __value) +#define SET_88E_H2CCMD_WOWLAN_PATTERN_MATCH_ENABLE(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE(__cmd, 1, 1, __value) +#define SET_88E_H2CCMD_WOWLAN_MAGIC_PKT_ENABLE(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE(__cmd, 2, 1, __value) +#define SET_88E_H2CCMD_WOWLAN_UNICAST_PKT_ENABLE(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE(__cmd, 3, 1, __value) +#define SET_88E_H2CCMD_WOWLAN_ALL_PKT_DROP(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE(__cmd, 4, 1, __value) +#define SET_88E_H2CCMD_WOWLAN_GPIO_ACTIVE(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE(__cmd, 5, 1, __value) +#define SET_88E_H2CCMD_WOWLAN_REKEY_WAKE_UP(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE(__cmd, 6, 1, __value) +#define SET_88E_H2CCMD_WOWLAN_DISCONNECT_WAKE_UP(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE(__cmd, 7, 1, __value) +#define SET_88E_H2CCMD_WOWLAN_GPIONUM(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+1, 0, 8, __value) +#define SET_88E_H2CCMD_WOWLAN_GPIO_DURATION(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+2, 0, 8, __value) + + +#define SET_H2CCMD_PWRMODE_PARM_MODE(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE(__ph2ccmd, 0, 8, __val) +#define SET_H2CCMD_PWRMODE_PARM_RLBM(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+1, 0, 4, __value) +#define SET_H2CCMD_PWRMODE_PARM_SMART_PS(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+1, 4, 4, __value) +#define SET_H2CCMD_PWRMODE_PARM_AWAKE_INTERVAL(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+2, 0, 8, __value) +#define SET_H2CCMD_PWRMODE_PARM_ALL_QUEUE_UAPSD(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+3, 0, 8, __value) +#define SET_H2CCMD_PWRMODE_PARM_PWR_STATE(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+4, 0, 8, __value) +#define GET_88E_H2CCMD_PWRMODE_PARM_MODE(__cmd) \ + LE_BITS_TO_1BYTE(__cmd, 0, 8) + +#define SET_H2CCMD_JOINBSSRPT_PARM_OPMODE(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE(__ph2ccmd, 0, 8, __val) +#define SET_H2CCMD_RSVDPAGE_LOC_PROBE_RSP(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE(__ph2ccmd, 0, 8, __val) +#define SET_H2CCMD_RSVDPAGE_LOC_PSPOLL(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE((__ph2ccmd)+1, 0, 8, __val) +#define SET_H2CCMD_RSVDPAGE_LOC_NULL_DATA(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE((__ph2ccmd)+2, 0, 8, __val) + +/* AP_OFFLOAD */ +#define SET_H2CCMD_AP_OFFLOAD_ON(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE(__cmd, 0, 8, __value) +#define SET_H2CCMD_AP_OFFLOAD_HIDDEN(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+1, 0, 8, __value) +#define SET_H2CCMD_AP_OFFLOAD_DENYANY(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+2, 0, 8, __value) +#define SET_H2CCMD_AP_OFFLOAD_WAKEUP_EVT_RPT(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+3, 0, 8, __value) + +/* Keep Alive Control*/ +#define SET_88E_H2CCMD_KEEP_ALIVE_ENABLE(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE(__cmd, 0, 1, __value) +#define SET_88E_H2CCMD_KEEP_ALIVE_ACCPEPT_USER_DEFINED(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE(__cmd, 1, 1, __value) +#define SET_88E_H2CCMD_KEEP_ALIVE_PERIOD(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+1, 0, 8, __value) + +/*REMOTE_WAKE_CTRL */ +#define SET_88E_H2CCMD_REMOTE_WAKE_CTRL_EN(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE(__cmd, 0, 1, __value) +#if (USE_OLD_WOWLAN_DEBUG_FW == 0) +#define SET_88E_H2CCMD_REMOTE_WAKE_CTRL_ARP_OFFLOAD_EN(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE(__cmd, 1, 1, __value) +#define SET_88E_H2CCMD_REMOTE_WAKE_CTRL_NDP_OFFLOAD_EN(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE(__cmd, 2, 1, __value) +#define SET_88E_H2CCMD_REMOTE_WAKE_CTRL_GTK_OFFLOAD_EN(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE(__cmd, 3, 1, __value) +#else +#define SET_88E_H2_REM_WAKE_ENC_ALG(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+1, 0, 8, __value) +#define SET_88E_H2CCMD_REMOTE_WAKE_CTRL_GROUP_ENC_ALG(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+2, 0, 8, __value) +#endif + +/* GTK_OFFLOAD */ +#define SET_88E_H2CCMD_AOAC_GLOBAL_INFO_PAIRWISE_ENC_ALG(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE(__cmd, 0, 8, __value) +#define SET_88E_H2CCMD_AOAC_GLOBAL_INFO_GROUP_ENC_ALG(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+1, 0, 8, __value) + +/* AOAC_RSVDPAGE_LOC */ +#define SET_88E_H2CCMD_AOAC_RSVD_LOC_REM_WAKE_CTRL_INFO(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd), 0, 8, __value) +#define SET_88E_H2CCMD_AOAC_RSVDPAGE_LOC_ARP_RSP(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+1, 0, 8, __value) +#define SET_88E_H2CCMD_AOAC_RSVDPAGE_LOC_NEIGHBOR_ADV(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+2, 0, 8, __value) +#define SET_88E_H2CCMD_AOAC_RSVDPAGE_LOC_GTK_RSP(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+3, 0, 8, __value) +#define SET_88E_H2CCMD_AOAC_RSVDPAGE_LOC_GTK_INFO(__cmd, __value) \ + SET_BITS_TO_LE_1BYTE((__cmd)+4, 0, 8, __value) + +int rtl88e_download_fw(struct ieee80211_hw *hw, + bool buse_wake_on_wlan_fw); +void rtl88e_fill_h2c_cmd(struct ieee80211_hw *hw, u8 element_id, + u32 cmd_len, u8 *p_cmdbuffer); +void rtl88e_firmware_selfreset(struct ieee80211_hw *hw); +void rtl88e_set_fw_pwrmode_cmd(struct ieee80211_hw *hw, u8 mode); +void rtl88e_set_fw_joinbss_report_cmd(struct ieee80211_hw *hw, + u8 mstatus); +void rtl88e_set_fw_ap_off_load_cmd(struct ieee80211_hw *hw, u8 enable); +void rtl88e_set_fw_rsvdpagepkt(struct ieee80211_hw *hw, bool b_dl_finished); +void rtl88e_set_p2p_ps_offload_cmd(struct ieee80211_hw *hw, u8 p2p_ps_state); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/hw.c b/drivers/net/wireless/rtlwifi/rtl8188ee/hw.c new file mode 100644 index 0000000..b68cae3 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/hw.c @@ -0,0 +1,2530 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../efuse.h" +#include "../base.h" +#include "../regd.h" +#include "../cam.h" +#include "../ps.h" +#include "../pci.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "dm.h" +#include "fw.h" +#include "led.h" +#include "hw.h" +#include "pwrseqcmd.h" +#include "pwrseq.h" + +#define LLT_CONFIG 5 + +static void _rtl88ee_set_bcn_ctrl_reg(struct ieee80211_hw *hw, + u8 set_bits, u8 clear_bits) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpci->reg_bcn_ctrl_val |= set_bits; + rtlpci->reg_bcn_ctrl_val &= ~clear_bits; + + rtl_write_byte(rtlpriv, REG_BCN_CTRL, (u8) rtlpci->reg_bcn_ctrl_val); +} + +static void _rtl88ee_stop_tx_beacon(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tmp1byte; + + tmp1byte = rtl_read_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2); + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, tmp1byte & (~BIT(6))); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 1, 0x64); + tmp1byte = rtl_read_byte(rtlpriv, REG_TBTT_PROHIBIT + 2); + tmp1byte &= ~(BIT(0)); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 2, tmp1byte); +} + +static void _rtl88ee_resume_tx_beacon(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tmp1byte; + + tmp1byte = rtl_read_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2); + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, tmp1byte | BIT(6)); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 1, 0xff); + tmp1byte = rtl_read_byte(rtlpriv, REG_TBTT_PROHIBIT + 2); + tmp1byte |= BIT(0); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 2, tmp1byte); +} + +static void _rtl88ee_enable_bcn_sub_func(struct ieee80211_hw *hw) +{ + _rtl88ee_set_bcn_ctrl_reg(hw, 0, BIT(1)); +} + +static void _rtl88ee_return_beacon_queue_skb(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl8192_tx_ring *ring = &rtlpci->tx_ring[BEACON_QUEUE]; + + while (skb_queue_len(&ring->queue)) { + struct rtl_tx_desc *entry = &ring->desc[ring->idx]; + struct sk_buff *skb = __skb_dequeue(&ring->queue); + + pci_unmap_single(rtlpci->pdev, + rtlpriv->cfg->ops->get_desc( + (u8 *)entry, true, HW_DESC_TXBUFF_ADDR), + skb->len, PCI_DMA_TODEVICE); + kfree_skb(skb); + ring->idx = (ring->idx + 1) % ring->entries; + } +} + +static void _rtl88ee_disable_bcn_sub_func(struct ieee80211_hw *hw) +{ + _rtl88ee_set_bcn_ctrl_reg(hw, BIT(1), 0); +} + +static void _rtl88ee_set_fw_clock_on(struct ieee80211_hw *hw, + u8 rpwm_val, bool need_turn_off_ckk) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + bool support_remote_wake_up; + u32 count = 0, isr_regaddr, content; + bool schedule_timer = need_turn_off_ckk; + + rtlpriv->cfg->ops->get_hw_reg(hw, HAL_DEF_WOWLAN, + (u8 *)(&support_remote_wake_up)); + if (!rtlhal->fw_ready) + return; + if (!rtlpriv->psc.fw_current_inpsmode) + return; + + while (1) { + spin_lock_bh(&rtlpriv->locks.fw_ps_lock); + if (rtlhal->fw_clk_change_in_progress) { + while (rtlhal->fw_clk_change_in_progress) { + spin_unlock_bh(&rtlpriv->locks.fw_ps_lock); + udelay(100); + if (++count > 1000) + return; + spin_lock_bh(&rtlpriv->locks.fw_ps_lock); + } + spin_unlock_bh(&rtlpriv->locks.fw_ps_lock); + } else { + rtlhal->fw_clk_change_in_progress = false; + spin_unlock_bh(&rtlpriv->locks.fw_ps_lock); + } + } + + if (IS_IN_LOW_POWER_STATE_88E(rtlhal->fw_ps_state)) { + rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_SET_RPWM, + (u8 *)(&rpwm_val)); + if (FW_PS_IS_ACK(rpwm_val)) { + isr_regaddr = REG_HISR; + content = rtl_read_dword(rtlpriv, isr_regaddr); + while (!(content & IMR_CPWM) && (count < 500)) { + udelay(50); + count++; + content = rtl_read_dword(rtlpriv, isr_regaddr); + } + + if (content & IMR_CPWM) { + rtl_write_word(rtlpriv, isr_regaddr, 0x0100); + rtlhal->fw_ps_state = FW_PS_STATE_RF_ON_88E; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "Receive CPWM INT!!! Set pHalData->FwPSState = %X\n", + rtlhal->fw_ps_state); + } + } + + spin_lock_bh(&rtlpriv->locks.fw_ps_lock); + rtlhal->fw_clk_change_in_progress = false; + spin_unlock_bh(&rtlpriv->locks.fw_ps_lock); + if (schedule_timer) { + mod_timer(&rtlpriv->works.fw_clockoff_timer, + jiffies + MSECS(10)); + } + } else { + spin_lock_bh(&rtlpriv->locks.fw_ps_lock); + rtlhal->fw_clk_change_in_progress = false; + spin_unlock_bh(&rtlpriv->locks.fw_ps_lock); + } +} + +static void _rtl88ee_set_fw_clock_off(struct ieee80211_hw *hw, + u8 rpwm_val) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl8192_tx_ring *ring; + enum rf_pwrstate rtstate; + bool schedule_timer = false; + u8 queue; + + if (!rtlhal->fw_ready) + return; + if (!rtlpriv->psc.fw_current_inpsmode) + return; + if (!rtlhal->allow_sw_to_change_hwclc) + return; + rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_RF_STATE, (u8 *)(&rtstate)); + if (rtstate == ERFOFF || rtlpriv->psc.inactive_pwrstate == ERFOFF) + return; + + for (queue = 0; queue < RTL_PCI_MAX_TX_QUEUE_COUNT; queue++) { + ring = &rtlpci->tx_ring[queue]; + if (skb_queue_len(&ring->queue)) { + schedule_timer = true; + break; + } + } + + if (schedule_timer) { + mod_timer(&rtlpriv->works.fw_clockoff_timer, + jiffies + MSECS(10)); + return; + } + + if (FW_PS_STATE(rtlhal->fw_ps_state) != + FW_PS_STATE_RF_OFF_LOW_PWR_88E) { + spin_lock_bh(&rtlpriv->locks.fw_ps_lock); + if (!rtlhal->fw_clk_change_in_progress) { + rtlhal->fw_clk_change_in_progress = true; + spin_unlock_bh(&rtlpriv->locks.fw_ps_lock); + rtlhal->fw_ps_state = FW_PS_STATE(rpwm_val); + rtl_write_word(rtlpriv, REG_HISR, 0x0100); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SET_RPWM, + (u8 *)(&rpwm_val)); + spin_lock_bh(&rtlpriv->locks.fw_ps_lock); + rtlhal->fw_clk_change_in_progress = false; + spin_unlock_bh(&rtlpriv->locks.fw_ps_lock); + } else { + spin_unlock_bh(&rtlpriv->locks.fw_ps_lock); + mod_timer(&rtlpriv->works.fw_clockoff_timer, + jiffies + MSECS(10)); + } + } +} + +static void _rtl88ee_set_fw_ps_rf_on(struct ieee80211_hw *hw) +{ + u8 rpwm_val = 0; + + rpwm_val |= (FW_PS_STATE_RF_OFF_88E | FW_PS_ACK); + _rtl88ee_set_fw_clock_on(hw, rpwm_val, true); +} + +static void _rtl88ee_set_fw_ps_rf_off_low_power(struct ieee80211_hw *hw) +{ + u8 rpwm_val = 0; + + rpwm_val |= FW_PS_STATE_RF_OFF_LOW_PWR_88E; + _rtl88ee_set_fw_clock_off(hw, rpwm_val); +} + +void rtl88ee_fw_clk_off_timer_callback(unsigned long data) +{ + struct ieee80211_hw *hw = (struct ieee80211_hw *)data; + + _rtl88ee_set_fw_ps_rf_off_low_power(hw); +} + +static void _rtl88ee_fwlps_leave(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + bool fw_current_inps = false; + u8 rpwm_val = 0, fw_pwrmode = FW_PS_ACTIVE_MODE; + + if (ppsc->low_power_enable) { + rpwm_val = (FW_PS_STATE_ALL_ON_88E|FW_PS_ACK);/* RF on */ + _rtl88ee_set_fw_clock_on(hw, rpwm_val, false); + rtlhal->allow_sw_to_change_hwclc = false; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_H2C_FW_PWRMODE, + (u8 *)(&fw_pwrmode)); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_FW_PSMODE_STATUS, + (u8 *)(&fw_current_inps)); + } else { + rpwm_val = FW_PS_STATE_ALL_ON_88E; /* RF on */ + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SET_RPWM, + (u8 *)(&rpwm_val)); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_H2C_FW_PWRMODE, + (u8 *)(&fw_pwrmode)); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_FW_PSMODE_STATUS, + (u8 *)(&fw_current_inps)); + } +} + +static void _rtl88ee_fwlps_enter(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + bool fw_current_inps = true; + u8 rpwm_val; + + if (ppsc->low_power_enable) { + rpwm_val = FW_PS_STATE_RF_OFF_LOW_PWR_88E; /* RF off */ + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_FW_PSMODE_STATUS, + (u8 *)(&fw_current_inps)); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_H2C_FW_PWRMODE, + (u8 *)(&ppsc->fwctrl_psmode)); + rtlhal->allow_sw_to_change_hwclc = true; + _rtl88ee_set_fw_clock_off(hw, rpwm_val); + } else { + rpwm_val = FW_PS_STATE_RF_OFF_88E; /* RF off */ + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_FW_PSMODE_STATUS, + (u8 *)(&fw_current_inps)); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_H2C_FW_PWRMODE, + (u8 *)(&ppsc->fwctrl_psmode)); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SET_RPWM, + (u8 *)(&rpwm_val)); + } +} + +void rtl88ee_get_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + switch (variable) { + case HW_VAR_RCR: + *((u32 *)(val)) = rtlpci->receive_config; + break; + case HW_VAR_RF_STATE: + *((enum rf_pwrstate *)(val)) = ppsc->rfpwr_state; + break; + case HW_VAR_FWLPS_RF_ON:{ + enum rf_pwrstate rfstate; + u32 val_rcr; + + rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_RF_STATE, + (u8 *)(&rfstate)); + if (rfstate == ERFOFF) { + *((bool *)(val)) = true; + } else { + val_rcr = rtl_read_dword(rtlpriv, REG_RCR); + val_rcr &= 0x00070000; + if (val_rcr) + *((bool *)(val)) = false; + else + *((bool *)(val)) = true; + } + break; + } + case HW_VAR_FW_PSMODE_STATUS: + *((bool *)(val)) = ppsc->fw_current_inpsmode; + break; + case HW_VAR_CORRECT_TSF:{ + u64 tsf; + u32 *ptsf_low = (u32 *)&tsf; + u32 *ptsf_high = ((u32 *)&tsf) + 1; + + *ptsf_high = rtl_read_dword(rtlpriv, (REG_TSFTR + 4)); + *ptsf_low = rtl_read_dword(rtlpriv, REG_TSFTR); + + *((u64 *)(val)) = tsf; + break; } + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not process %x\n", variable); + break; + } +} + +void rtl88ee_set_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + u8 idx; + + switch (variable) { + case HW_VAR_ETHER_ADDR: + for (idx = 0; idx < ETH_ALEN; idx++) + rtl_write_byte(rtlpriv, (REG_MACID + idx), val[idx]); + break; + case HW_VAR_BASIC_RATE:{ + u16 rate_cfg = ((u16 *)val)[0]; + u8 rate_index = 0; + rate_cfg = rate_cfg & 0x15f; + rate_cfg |= 0x01; + rtl_write_byte(rtlpriv, REG_RRSR, rate_cfg & 0xff); + rtl_write_byte(rtlpriv, REG_RRSR + 1, (rate_cfg >> 8) & 0xff); + while (rate_cfg > 0x1) { + rate_cfg = (rate_cfg >> 1); + rate_index++; + } + rtl_write_byte(rtlpriv, REG_INIRTS_RATE_SEL, rate_index); + break; } + case HW_VAR_BSSID: + for (idx = 0; idx < ETH_ALEN; idx++) + rtl_write_byte(rtlpriv, (REG_BSSID + idx), val[idx]); + break; + case HW_VAR_SIFS: + rtl_write_byte(rtlpriv, REG_SIFS_CTX + 1, val[0]); + rtl_write_byte(rtlpriv, REG_SIFS_TRX + 1, val[1]); + + rtl_write_byte(rtlpriv, REG_SPEC_SIFS + 1, val[0]); + rtl_write_byte(rtlpriv, REG_MAC_SPEC_SIFS + 1, val[0]); + + if (!mac->ht_enable) + rtl_write_word(rtlpriv, REG_RESP_SIFS_OFDM, 0x0e0e); + else + rtl_write_word(rtlpriv, REG_RESP_SIFS_OFDM, + *((u16 *)val)); + break; + case HW_VAR_SLOT_TIME:{ + u8 e_aci; + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "HW_VAR_SLOT_TIME %x\n", val[0]); + + rtl_write_byte(rtlpriv, REG_SLOT, val[0]); + + for (e_aci = 0; e_aci < AC_MAX; e_aci++) { + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_AC_PARAM, + (u8 *)(&e_aci)); + } + break; } + case HW_VAR_ACK_PREAMBLE:{ + u8 reg_tmp; + u8 short_preamble = (bool) (*(u8 *)val); + reg_tmp = rtl_read_byte(rtlpriv, REG_TRXPTCL_CTL+2); + if (short_preamble) { + reg_tmp |= 0x02; + rtl_write_byte(rtlpriv, REG_TRXPTCL_CTL + 2, reg_tmp); + } else { + reg_tmp |= 0xFD; + rtl_write_byte(rtlpriv, REG_TRXPTCL_CTL + 2, reg_tmp); + } + break; } + case HW_VAR_WPA_CONFIG: + rtl_write_byte(rtlpriv, REG_SECCFG, *((u8 *)val)); + break; + case HW_VAR_AMPDU_MIN_SPACE:{ + u8 min_spacing_to_set; + u8 sec_min_space; + + min_spacing_to_set = *((u8 *)val); + if (min_spacing_to_set <= 7) { + sec_min_space = 0; + + if (min_spacing_to_set < sec_min_space) + min_spacing_to_set = sec_min_space; + + mac->min_space_cfg = ((mac->min_space_cfg & + 0xf8) | min_spacing_to_set); + + *val = min_spacing_to_set; + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_AMPDU_MIN_SPACE: %#x\n", + mac->min_space_cfg); + + rtl_write_byte(rtlpriv, REG_AMPDU_MIN_SPACE, + mac->min_space_cfg); + } + break; } + case HW_VAR_SHORTGI_DENSITY:{ + u8 density_to_set; + + density_to_set = *((u8 *)val); + mac->min_space_cfg |= (density_to_set << 3); + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_SHORTGI_DENSITY: %#x\n", + mac->min_space_cfg); + + rtl_write_byte(rtlpriv, REG_AMPDU_MIN_SPACE, + mac->min_space_cfg); + break; } + case HW_VAR_AMPDU_FACTOR:{ + u8 regtoset_normal[4] = { 0x41, 0xa8, 0x72, 0xb9 }; + u8 factor; + u8 *reg = NULL; + u8 id = 0; + + reg = regtoset_normal; + + factor = *((u8 *)val); + if (factor <= 3) { + factor = (1 << (factor + 2)); + if (factor > 0xf) + factor = 0xf; + + for (id = 0; id < 4; id++) { + if ((reg[id] & 0xf0) > (factor << 4)) + reg[id] = (reg[id] & 0x0f) | + (factor << 4); + + if ((reg[id] & 0x0f) > factor) + reg[id] = (reg[id] & 0xf0) | (factor); + + rtl_write_byte(rtlpriv, (REG_AGGLEN_LMT + id), + reg[id]); + } + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_AMPDU_FACTOR: %#x\n", factor); + } + break; } + case HW_VAR_AC_PARAM:{ + u8 e_aci = *((u8 *)val); + rtl88e_dm_init_edca_turbo(hw); + + if (rtlpci->acm_method != eAcmWay2_SW) + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_ACM_CTRL, + (u8 *)(&e_aci)); + break; } + case HW_VAR_ACM_CTRL:{ + u8 e_aci = *((u8 *)val); + union aci_aifsn *p_aci_aifsn = + (union aci_aifsn *)(&(mac->ac[0].aifs)); + u8 acm = p_aci_aifsn->f.acm; + u8 acm_ctrl = rtl_read_byte(rtlpriv, REG_ACMHWCTRL); + + acm_ctrl = acm_ctrl | ((rtlpci->acm_method == 2) ? 0x0 : 0x1); + + if (acm) { + switch (e_aci) { + case AC0_BE: + acm_ctrl |= ACMHW_BEQEN; + break; + case AC2_VI: + acm_ctrl |= ACMHW_VIQEN; + break; + case AC3_VO: + acm_ctrl |= ACMHW_VOQEN; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "HW_VAR_ACM_CTRL acm set failed: eACI is %d\n", + acm); + break; + } + } else { + switch (e_aci) { + case AC0_BE: + acm_ctrl &= (~ACMHW_BEQEN); + break; + case AC2_VI: + acm_ctrl &= (~ACMHW_VIQEN); + break; + case AC3_VO: + acm_ctrl &= (~ACMHW_BEQEN); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not process\n"); + break; + } + } + + RT_TRACE(rtlpriv, COMP_QOS, DBG_TRACE, + "SetHwReg8190pci(): [HW_VAR_ACM_CTRL] Write 0x%X\n", + acm_ctrl); + rtl_write_byte(rtlpriv, REG_ACMHWCTRL, acm_ctrl); + break; } + case HW_VAR_RCR: + rtl_write_dword(rtlpriv, REG_RCR, ((u32 *)(val))[0]); + rtlpci->receive_config = ((u32 *)(val))[0]; + break; + case HW_VAR_RETRY_LIMIT:{ + u8 retry_limit = ((u8 *)(val))[0]; + + rtl_write_word(rtlpriv, REG_RL, + retry_limit << RETRY_LIMIT_SHORT_SHIFT | + retry_limit << RETRY_LIMIT_LONG_SHIFT); + break; } + case HW_VAR_DUAL_TSF_RST: + rtl_write_byte(rtlpriv, REG_DUAL_TSF_RST, (BIT(0) | BIT(1))); + break; + case HW_VAR_EFUSE_BYTES: + rtlefuse->efuse_usedbytes = *((u16 *)val); + break; + case HW_VAR_EFUSE_USAGE: + rtlefuse->efuse_usedpercentage = *((u8 *)val); + break; + case HW_VAR_IO_CMD: + rtl88e_phy_set_io_cmd(hw, (*(enum io_type *)val)); + break; + case HW_VAR_SET_RPWM:{ + u8 rpwm_val; + + rpwm_val = rtl_read_byte(rtlpriv, REG_PCIE_HRPWM); + udelay(1); + + if (rpwm_val & BIT(7)) { + rtl_write_byte(rtlpriv, REG_PCIE_HRPWM, + (*(u8 *)val)); + } else { + rtl_write_byte(rtlpriv, REG_PCIE_HRPWM, + ((*(u8 *)val) | BIT(7))); + } + break; } + case HW_VAR_H2C_FW_PWRMODE: + rtl88e_set_fw_pwrmode_cmd(hw, (*(u8 *)val)); + break; + case HW_VAR_FW_PSMODE_STATUS: + ppsc->fw_current_inpsmode = *((bool *)val); + break; + case HW_VAR_RESUME_CLK_ON: + _rtl88ee_set_fw_ps_rf_on(hw); + break; + case HW_VAR_FW_LPS_ACTION:{ + bool enter_fwlps = *((bool *)val); + + if (enter_fwlps) + _rtl88ee_fwlps_enter(hw); + else + _rtl88ee_fwlps_leave(hw); + break; } + case HW_VAR_H2C_FW_JOINBSSRPT:{ + u8 mstatus = (*(u8 *)val); + u8 tmp, tmp_reg422, uval; + u8 count = 0, dlbcn_count = 0; + bool recover = false; + + if (mstatus == RT_MEDIA_CONNECT) { + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_AID, NULL); + + tmp = rtl_read_byte(rtlpriv, REG_CR + 1); + rtl_write_byte(rtlpriv, REG_CR + 1, (tmp | BIT(0))); + + _rtl88ee_set_bcn_ctrl_reg(hw, 0, BIT(3)); + _rtl88ee_set_bcn_ctrl_reg(hw, BIT(4), 0); + + tmp_reg422 = rtl_read_byte(rtlpriv, + REG_FWHW_TXQ_CTRL + 2); + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, + tmp_reg422 & (~BIT(6))); + if (tmp_reg422 & BIT(6)) + recover = true; + + do { + uval = rtl_read_byte(rtlpriv, REG_TDECTRL+2); + rtl_write_byte(rtlpriv, REG_TDECTRL+2, + (uval | BIT(0))); + _rtl88ee_return_beacon_queue_skb(hw); + + rtl88e_set_fw_rsvdpagepkt(hw, 0); + uval = rtl_read_byte(rtlpriv, REG_TDECTRL+2); + count = 0; + while (!(uval & BIT(0)) && count < 20) { + count++; + udelay(10); + uval = rtl_read_byte(rtlpriv, + REG_TDECTRL+2); + } + dlbcn_count++; + } while (!(uval & BIT(0)) && dlbcn_count < 5); + + if (uval & BIT(0)) + rtl_write_byte(rtlpriv, REG_TDECTRL+2, BIT(0)); + + _rtl88ee_set_bcn_ctrl_reg(hw, BIT(3), 0); + _rtl88ee_set_bcn_ctrl_reg(hw, 0, BIT(4)); + + if (recover) { + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, + tmp_reg422); + } + rtl_write_byte(rtlpriv, REG_CR + 1, (tmp & ~(BIT(0)))); + } + rtl88e_set_fw_joinbss_report_cmd(hw, (*(u8 *)val)); + break; } + case HW_VAR_H2C_FW_P2P_PS_OFFLOAD: + rtl88e_set_p2p_ps_offload_cmd(hw, (*(u8 *)val)); + break; + case HW_VAR_AID:{ + u16 u2btmp; + u2btmp = rtl_read_word(rtlpriv, REG_BCN_PSR_RPT); + u2btmp &= 0xC000; + rtl_write_word(rtlpriv, REG_BCN_PSR_RPT, (u2btmp | + mac->assoc_id)); + break; } + case HW_VAR_CORRECT_TSF:{ + u8 btype_ibss = ((u8 *)(val))[0]; + + if (btype_ibss == true) + _rtl88ee_stop_tx_beacon(hw); + + _rtl88ee_set_bcn_ctrl_reg(hw, 0, BIT(3)); + + rtl_write_dword(rtlpriv, REG_TSFTR, + (u32) (mac->tsf & 0xffffffff)); + rtl_write_dword(rtlpriv, REG_TSFTR + 4, + (u32) ((mac->tsf >> 32) & 0xffffffff)); + + _rtl88ee_set_bcn_ctrl_reg(hw, BIT(3), 0); + + if (btype_ibss == true) + _rtl88ee_resume_tx_beacon(hw); + break; } + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not process %x\n", variable); + break; + } +} + +static bool _rtl88ee_llt_write(struct ieee80211_hw *hw, u32 address, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + bool status = true; + long count = 0; + u32 value = _LLT_INIT_ADDR(address) | _LLT_INIT_DATA(data) | + _LLT_OP(_LLT_WRITE_ACCESS); + + rtl_write_dword(rtlpriv, REG_LLT_INIT, value); + + do { + value = rtl_read_dword(rtlpriv, REG_LLT_INIT); + if (_LLT_NO_ACTIVE == _LLT_OP_VALUE(value)) + break; + + if (count > POLLING_LLT_THRESHOLD) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Failed to polling write LLT done at address %d!\n", + address); + status = false; + break; + } + } while (++count); + + return status; +} + +static bool _rtl88ee_llt_table_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + unsigned short i; + u8 txpktbuf_bndy; + u8 maxpage; + bool status; + + maxpage = 0xAF; + txpktbuf_bndy = 0xAB; + + rtl_write_byte(rtlpriv, REG_RQPN_NPQ, 0x01); + rtl_write_dword(rtlpriv, REG_RQPN, 0x80730d29); + + + rtl_write_dword(rtlpriv, REG_TRXFF_BNDY, (0x25FF0000 | txpktbuf_bndy)); + rtl_write_byte(rtlpriv, REG_TDECTRL + 1, txpktbuf_bndy); + + rtl_write_byte(rtlpriv, REG_TXPKTBUF_BCNQ_BDNY, txpktbuf_bndy); + rtl_write_byte(rtlpriv, REG_TXPKTBUF_MGQ_BDNY, txpktbuf_bndy); + + rtl_write_byte(rtlpriv, 0x45D, txpktbuf_bndy); + rtl_write_byte(rtlpriv, REG_PBP, 0x11); + rtl_write_byte(rtlpriv, REG_RX_DRVINFO_SZ, 0x4); + + for (i = 0; i < (txpktbuf_bndy - 1); i++) { + status = _rtl88ee_llt_write(hw, i, i + 1); + if (true != status) + return status; + } + + status = _rtl88ee_llt_write(hw, (txpktbuf_bndy - 1), 0xFF); + if (true != status) + return status; + + for (i = txpktbuf_bndy; i < maxpage; i++) { + status = _rtl88ee_llt_write(hw, i, (i + 1)); + if (true != status) + return status; + } + + status = _rtl88ee_llt_write(hw, maxpage, txpktbuf_bndy); + if (true != status) + return status; + + return true; +} + +static void _rtl88ee_gen_refresh_led_state(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_led *pLed0 = &(pcipriv->ledctl.sw_led0); + + if (rtlpriv->rtlhal.up_first_time) + return; + + if (ppsc->rfoff_reason == RF_CHANGE_BY_IPS) + rtl88ee_sw_led_on(hw, pLed0); + else if (ppsc->rfoff_reason == RF_CHANGE_BY_INIT) + rtl88ee_sw_led_on(hw, pLed0); + else + rtl88ee_sw_led_off(hw, pLed0); +} + +static bool _rtl88ee_init_mac(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 bytetmp; + u16 wordtmp; + + /*Disable XTAL OUTPUT for power saving. YJ, add, 111206. */ + bytetmp = rtl_read_byte(rtlpriv, REG_XCK_OUT_CTRL) & (~BIT(0)); + rtl_write_byte(rtlpriv, REG_XCK_OUT_CTRL, bytetmp); + /*Auto Power Down to CHIP-off State*/ + bytetmp = rtl_read_byte(rtlpriv, REG_APS_FSMCO + 1) & (~BIT(7)); + rtl_write_byte(rtlpriv, REG_APS_FSMCO + 1, bytetmp); + + rtl_write_byte(rtlpriv, REG_RSV_CTRL, 0x00); + /* HW Power on sequence */ + if (!rtl88_hal_pwrseqcmdparsing(rtlpriv, PWR_CUT_ALL_MSK, + PWR_FAB_ALL_MSK, PWR_INTF_PCI_MSK, + Rtl8188E_NIC_ENABLE_FLOW)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "init MAC Fail as rtl88_hal_pwrseqcmdparsing\n"); + return false; + } + + bytetmp = rtl_read_byte(rtlpriv, REG_APS_FSMCO) | BIT(4); + rtl_write_byte(rtlpriv, REG_APS_FSMCO, bytetmp); + + bytetmp = rtl_read_byte(rtlpriv, REG_PCIE_CTRL_REG+2); + rtl_write_byte(rtlpriv, REG_PCIE_CTRL_REG+2, bytetmp|BIT(2)); + + bytetmp = rtl_read_byte(rtlpriv, REG_WATCH_DOG+1); + rtl_write_byte(rtlpriv, REG_WATCH_DOG+1, bytetmp|BIT(7)); + + bytetmp = rtl_read_byte(rtlpriv, REG_AFE_XTAL_CTRL_EXT+1); + rtl_write_byte(rtlpriv, REG_AFE_XTAL_CTRL_EXT+1, bytetmp|BIT(1)); + + bytetmp = rtl_read_byte(rtlpriv, REG_TX_RPT_CTRL); + rtl_write_byte(rtlpriv, REG_TX_RPT_CTRL, bytetmp|BIT(1)|BIT(0)); + rtl_write_byte(rtlpriv, REG_TX_RPT_CTRL+1, 2); + rtl_write_word(rtlpriv, REG_TX_RPT_TIME, 0xcdf0); + + /*Add for wake up online*/ + bytetmp = rtl_read_byte(rtlpriv, REG_SYS_CLKR); + + rtl_write_byte(rtlpriv, REG_SYS_CLKR, bytetmp|BIT(3)); + bytetmp = rtl_read_byte(rtlpriv, REG_GPIO_MUXCFG+1); + rtl_write_byte(rtlpriv, REG_GPIO_MUXCFG+1, (bytetmp & (~BIT(4)))); + rtl_write_byte(rtlpriv, 0x367, 0x80); + + rtl_write_word(rtlpriv, REG_CR, 0x2ff); + rtl_write_byte(rtlpriv, REG_CR+1, 0x06); + rtl_write_byte(rtlpriv, REG_CR+2, 0x00); + + if (!rtlhal->mac_func_enable) { + if (_rtl88ee_llt_table_init(hw) == false) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "LLT table init fail\n"); + return false; + } + } + + + rtl_write_dword(rtlpriv, REG_HISR, 0xffffffff); + rtl_write_dword(rtlpriv, REG_HISRE, 0xffffffff); + + wordtmp = rtl_read_word(rtlpriv, REG_TRXDMA_CTRL); + wordtmp &= 0xf; + wordtmp |= 0xE771; + rtl_write_word(rtlpriv, REG_TRXDMA_CTRL, wordtmp); + + rtl_write_dword(rtlpriv, REG_RCR, rtlpci->receive_config); + rtl_write_word(rtlpriv, REG_RXFLTMAP2, 0xffff); + rtl_write_dword(rtlpriv, REG_TCR, rtlpci->transmit_config); + + rtl_write_dword(rtlpriv, REG_BCNQ_DESA, + ((u64) rtlpci->tx_ring[BEACON_QUEUE].dma) & + DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_MGQ_DESA, + (u64) rtlpci->tx_ring[MGNT_QUEUE].dma & + DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_VOQ_DESA, + (u64) rtlpci->tx_ring[VO_QUEUE].dma & DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_VIQ_DESA, + (u64) rtlpci->tx_ring[VI_QUEUE].dma & DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_BEQ_DESA, + (u64) rtlpci->tx_ring[BE_QUEUE].dma & DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_BKQ_DESA, + (u64) rtlpci->tx_ring[BK_QUEUE].dma & DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_HQ_DESA, + (u64) rtlpci->tx_ring[HIGH_QUEUE].dma & + DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_RX_DESA, + (u64) rtlpci->rx_ring[RX_MPDU_QUEUE].dma & + DMA_BIT_MASK(32)); + + /* if we want to support 64 bit DMA, we should set it here, + * but at the moment we do not support 64 bit DMA + */ + + rtl_write_dword(rtlpriv, REG_INT_MIG, 0); + + rtl_write_dword(rtlpriv, REG_MCUTST_1, 0x0); + rtl_write_byte(rtlpriv, REG_PCIE_CTRL_REG+1, 0);/*Enable RX DMA */ + + if (rtlhal->earlymode_enable) {/*Early mode enable*/ + bytetmp = rtl_read_byte(rtlpriv, REG_EARLY_MODE_CONTROL); + bytetmp |= 0x1f; + rtl_write_byte(rtlpriv, REG_EARLY_MODE_CONTROL, bytetmp); + rtl_write_byte(rtlpriv, REG_EARLY_MODE_CONTROL+3, 0x81); + } + _rtl88ee_gen_refresh_led_state(hw); + return true; +} + +static void _rtl88ee_hw_configure(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 reg_prsr; + + reg_prsr = RATE_ALL_CCK | RATE_ALL_OFDM_AG; + + rtl_write_dword(rtlpriv, REG_RRSR, reg_prsr); + rtl_write_byte(rtlpriv, REG_HWSEQ_CTRL, 0xFF); +} + +static void _rtl88ee_enable_aspm_back_door(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + u8 tmp1byte = 0; + u32 tmp4Byte = 0, count; + + rtl_write_word(rtlpriv, 0x354, 0x8104); + rtl_write_word(rtlpriv, 0x358, 0x24); + + rtl_write_word(rtlpriv, 0x350, 0x70c); + rtl_write_byte(rtlpriv, 0x352, 0x2); + tmp1byte = rtl_read_byte(rtlpriv, 0x352); + count = 0; + while (tmp1byte && count < 20) { + udelay(10); + tmp1byte = rtl_read_byte(rtlpriv, 0x352); + count++; + } + if (0 == tmp1byte) { + tmp4Byte = rtl_read_dword(rtlpriv, 0x34c); + rtl_write_dword(rtlpriv, 0x348, tmp4Byte|BIT(31)); + rtl_write_word(rtlpriv, 0x350, 0xf70c); + rtl_write_byte(rtlpriv, 0x352, 0x1); + } + + tmp1byte = rtl_read_byte(rtlpriv, 0x352); + count = 0; + while (tmp1byte && count < 20) { + udelay(10); + tmp1byte = rtl_read_byte(rtlpriv, 0x352); + count++; + } + + rtl_write_word(rtlpriv, 0x350, 0x718); + rtl_write_byte(rtlpriv, 0x352, 0x2); + tmp1byte = rtl_read_byte(rtlpriv, 0x352); + count = 0; + while (tmp1byte && count < 20) { + udelay(10); + tmp1byte = rtl_read_byte(rtlpriv, 0x352); + count++; + } + if (ppsc->support_backdoor || (0 == tmp1byte)) { + tmp4Byte = rtl_read_dword(rtlpriv, 0x34c); + rtl_write_dword(rtlpriv, 0x348, tmp4Byte|BIT(11)|BIT(12)); + rtl_write_word(rtlpriv, 0x350, 0xf718); + rtl_write_byte(rtlpriv, 0x352, 0x1); + } + tmp1byte = rtl_read_byte(rtlpriv, 0x352); + count = 0; + while (tmp1byte && count < 20) { + udelay(10); + tmp1byte = rtl_read_byte(rtlpriv, 0x352); + count++; + } +} + +void rtl88ee_enable_hw_security_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 sec_reg_value; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "PairwiseEncAlgorithm = %d GroupEncAlgorithm = %d\n", + rtlpriv->sec.pairwise_enc_algorithm, + rtlpriv->sec.group_enc_algorithm); + + if (rtlpriv->cfg->mod_params->sw_crypto || rtlpriv->sec.use_sw_sec) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "not open hw encryption\n"); + return; + } + sec_reg_value = SCR_TXENCENABLE | SCR_RXDECENABLE; + + if (rtlpriv->sec.use_defaultkey) { + sec_reg_value |= SCR_TXUSEDK; + sec_reg_value |= SCR_RXUSEDK; + } + + sec_reg_value |= (SCR_RXBCUSEDK | SCR_TXBCUSEDK); + + rtl_write_byte(rtlpriv, REG_CR + 1, 0x02); + + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "The SECR-value %x\n", sec_reg_value); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_WPA_CONFIG, &sec_reg_value); +} + +int rtl88ee_hw_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + bool rtstatus = true; + int err = 0; + u8 tmp_u1b, u1byte; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Rtl8188EE hw init\n"); + rtlpriv->rtlhal.being_init_adapter = true; + rtlpriv->intf_ops->disable_aspm(hw); + + tmp_u1b = rtl_read_byte(rtlpriv, REG_SYS_CLKR+1); + u1byte = rtl_read_byte(rtlpriv, REG_CR); + if ((tmp_u1b & BIT(3)) && (u1byte != 0 && u1byte != 0xEA)) { + rtlhal->mac_func_enable = true; + } else { + rtlhal->mac_func_enable = false; + rtlhal->fw_ps_state = FW_PS_STATE_ALL_ON_88E; + } + + rtstatus = _rtl88ee_init_mac(hw); + if (rtstatus != true) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Init MAC failed\n"); + err = 1; + return err; + } + + err = rtl88e_download_fw(hw, false); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Failed to download FW. Init HW without FW now..\n"); + err = 1; + rtlhal->fw_ready = false; + return err; + } else { + rtlhal->fw_ready = true; + } + /*fw related variable initialize */ + rtlhal->last_hmeboxnum = 0; + rtlhal->fw_ps_state = FW_PS_STATE_ALL_ON_88E; + rtlhal->fw_clk_change_in_progress = false; + rtlhal->allow_sw_to_change_hwclc = false; + ppsc->fw_current_inpsmode = false; + + rtl88e_phy_mac_config(hw); + /* because last function modifies RCR, we update + * rcr var here, or TP will be unstable for receive_config + * is wrong, RX RCR_ACRC32 will cause TP unstable & Rx + * RCR_APP_ICV will cause mac80211 disassoc for cisco 1252 + */ + rtlpci->receive_config &= ~(RCR_ACRC32 | RCR_AICV); + rtl_write_dword(rtlpriv, REG_RCR, rtlpci->receive_config); + + rtl88e_phy_bb_config(hw); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BCCKEN, 0x1); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BOFDMEN, 0x1); + + rtlphy->rf_mode = RF_OP_BY_SW_3WIRE; + rtl88e_phy_rf_config(hw); + + rtlphy->rfreg_chnlval[0] = rtl_get_rfreg(hw, (enum radio_path)0, + RF_CHNLBW, RFREG_OFFSET_MASK); + rtlphy->rfreg_chnlval[0] = rtlphy->rfreg_chnlval[0] & 0xfff00fff; + + _rtl88ee_hw_configure(hw); + rtl_cam_reset_all_entry(hw); + rtl88ee_enable_hw_security_config(hw); + + rtlhal->mac_func_enable = true; + ppsc->rfpwr_state = ERFON; + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_ETHER_ADDR, mac->mac_addr); + _rtl88ee_enable_aspm_back_door(hw); + rtlpriv->intf_ops->enable_aspm(hw); + + if (ppsc->rfpwr_state == ERFON) { + if ((rtlefuse->antenna_div_type == CGCS_RX_HW_ANTDIV) || + ((rtlefuse->antenna_div_type == CG_TRX_HW_ANTDIV) && + (rtlhal->oem_id == RT_CID_819x_HP))) { + rtl88e_phy_set_rfpath_switch(hw, true); + rtlpriv->dm.fat_table.rx_idle_ant = MAIN_ANT; + } else { + rtl88e_phy_set_rfpath_switch(hw, false); + rtlpriv->dm.fat_table.rx_idle_ant = AUX_ANT; + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "rx idle ant %s\n", + (rtlpriv->dm.fat_table.rx_idle_ant == MAIN_ANT) ? + ("MAIN_ANT") : ("AUX_ANT")); + + if (rtlphy->iqk_initialized) { + rtl88e_phy_iq_calibrate(hw, true); + } else { + rtl88e_phy_iq_calibrate(hw, false); + rtlphy->iqk_initialized = true; + } + rtl88e_dm_check_txpower_tracking(hw); + rtl88e_phy_lc_calibrate(hw); + } + + tmp_u1b = efuse_read_1byte(hw, 0x1FA); + if (!(tmp_u1b & BIT(0))) { + rtl_set_rfreg(hw, RF90_PATH_A, 0x15, 0x0F, 0x05); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "PA BIAS path A\n"); + } + + if (!(tmp_u1b & BIT(4))) { + tmp_u1b = rtl_read_byte(rtlpriv, 0x16); + tmp_u1b &= 0x0F; + rtl_write_byte(rtlpriv, 0x16, tmp_u1b | 0x80); + udelay(10); + rtl_write_byte(rtlpriv, 0x16, tmp_u1b | 0x90); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "under 1.5V\n"); + } + rtl_write_byte(rtlpriv, REG_NAV_CTRL+2, ((30000+127)/128)); + rtl88e_dm_init(hw); + rtlpriv->rtlhal.being_init_adapter = false; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "end of Rtl8188EE hw init %x\n", + err); + return 0; +} + +static enum version_8188e _rtl88ee_read_chip_version(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + enum version_8188e version = VERSION_UNKNOWN; + u32 value32; + + value32 = rtl_read_dword(rtlpriv, REG_SYS_CFG); + if (value32 & TRP_VAUX_EN) { + version = (enum version_8188e) VERSION_TEST_CHIP_88E; + } else { + version = NORMAL_CHIP; + version = version | ((value32 & TYPE_ID) ? RF_TYPE_2T2R : 0); + version = version | ((value32 & VENDOR_ID) ? + CHIP_VENDOR_UMC : 0); + } + + rtlphy->rf_type = RF_1T1R; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Chip RF Type: %s\n", (rtlphy->rf_type == RF_2T2R) ? + "RF_2T2R" : "RF_1T1R"); + + return version; +} + +static int _rtl88ee_set_media_status(struct ieee80211_hw *hw, + enum nl80211_iftype type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 bt_msr = rtl_read_byte(rtlpriv, MSR); + enum led_ctl_mode ledaction = LED_CTL_NO_LINK; + bt_msr &= 0xfc; + + if (type == NL80211_IFTYPE_UNSPECIFIED || + type == NL80211_IFTYPE_STATION) { + _rtl88ee_stop_tx_beacon(hw); + _rtl88ee_enable_bcn_sub_func(hw); + } else if (type == NL80211_IFTYPE_ADHOC || + type == NL80211_IFTYPE_AP || + type == NL80211_IFTYPE_MESH_POINT) { + _rtl88ee_resume_tx_beacon(hw); + _rtl88ee_disable_bcn_sub_func(hw); + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Set HW_VAR_MEDIA_STATUS: No such media status(%x).\n", + type); + } + + switch (type) { + case NL80211_IFTYPE_UNSPECIFIED: + bt_msr |= MSR_NOLINK; + ledaction = LED_CTL_LINK; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to NO LINK!\n"); + break; + case NL80211_IFTYPE_ADHOC: + bt_msr |= MSR_ADHOC; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to Ad Hoc!\n"); + break; + case NL80211_IFTYPE_STATION: + bt_msr |= MSR_INFRA; + ledaction = LED_CTL_LINK; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to STA!\n"); + break; + case NL80211_IFTYPE_AP: + bt_msr |= MSR_AP; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to AP!\n"); + break; + case NL80211_IFTYPE_MESH_POINT: + bt_msr |= MSR_ADHOC; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to Mesh Point!\n"); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Network type %d not support!\n", type); + return 1; + } + + rtl_write_byte(rtlpriv, (MSR), bt_msr); + rtlpriv->cfg->ops->led_control(hw, ledaction); + if ((bt_msr & 0xfc) == MSR_AP) + rtl_write_byte(rtlpriv, REG_BCNTCFG + 1, 0x00); + else + rtl_write_byte(rtlpriv, REG_BCNTCFG + 1, 0x66); + return 0; +} + +void rtl88ee_set_check_bssid(struct ieee80211_hw *hw, bool check_bssid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u32 reg_rcr = rtlpci->receive_config; + + if (rtlpriv->psc.rfpwr_state != ERFON) + return; + + if (check_bssid == true) { + reg_rcr |= (RCR_CBSSID_DATA | RCR_CBSSID_BCN); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_RCR, + (u8 *)(®_rcr)); + _rtl88ee_set_bcn_ctrl_reg(hw, 0, BIT(4)); + } else if (check_bssid == false) { + reg_rcr &= (~(RCR_CBSSID_DATA | RCR_CBSSID_BCN)); + _rtl88ee_set_bcn_ctrl_reg(hw, BIT(4), 0); + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_RCR, (u8 *)(®_rcr)); + } +} + +int rtl88ee_set_network_type(struct ieee80211_hw *hw, enum nl80211_iftype type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (_rtl88ee_set_media_status(hw, type)) + return -EOPNOTSUPP; + + if (rtlpriv->mac80211.link_state == MAC80211_LINKED) { + if (type != NL80211_IFTYPE_AP && + type != NL80211_IFTYPE_MESH_POINT) + rtl88ee_set_check_bssid(hw, true); + } else { + rtl88ee_set_check_bssid(hw, false); + } + + return 0; +} + +/* don't set REG_EDCA_BE_PARAM here because mac80211 will send pkt when scan */ +void rtl88ee_set_qos(struct ieee80211_hw *hw, int aci) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + rtl88e_dm_init_edca_turbo(hw); + switch (aci) { + case AC1_BK: + rtl_write_dword(rtlpriv, REG_EDCA_BK_PARAM, 0xa44f); + break; + case AC0_BE: + break; + case AC2_VI: + rtl_write_dword(rtlpriv, REG_EDCA_VI_PARAM, 0x5e4322); + break; + case AC3_VO: + rtl_write_dword(rtlpriv, REG_EDCA_VO_PARAM, 0x2f3222); + break; + default: + RT_ASSERT(false, "invalid aci: %d !\n", aci); + break; + } +} + +void rtl88ee_enable_interrupt(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + rtl_write_dword(rtlpriv, REG_HIMR, rtlpci->irq_mask[0] & 0xFFFFFFFF); + rtl_write_dword(rtlpriv, REG_HIMRE, rtlpci->irq_mask[1] & 0xFFFFFFFF); + rtlpci->irq_enabled = true; + /* there are some C2H CMDs have been sent before system interrupt + * is enabled, e.g., C2H, CPWM. + * So we need to clear all C2H events that FW has notified, otherwise + * FW won't schedule any commands anymore. + */ + rtl_write_byte(rtlpriv, REG_C2HEVT_CLEAR, 0); + /*enable system interrupt*/ + rtl_write_dword(rtlpriv, REG_HSIMR, rtlpci->sys_irq_mask & 0xFFFFFFFF); +} + +void rtl88ee_disable_interrupt(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + rtl_write_dword(rtlpriv, REG_HIMR, IMR_DISABLED); + rtl_write_dword(rtlpriv, REG_HIMRE, IMR_DISABLED); + rtlpci->irq_enabled = false; + synchronize_irq(rtlpci->pdev->irq); +} + +static void _rtl88ee_poweroff_adapter(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 u1b_tmp; + u32 count = 0; + rtlhal->mac_func_enable = false; + rtlpriv->intf_ops->enable_aspm(hw); + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "POWER OFF adapter\n"); + u1b_tmp = rtl_read_byte(rtlpriv, REG_TX_RPT_CTRL); + rtl_write_byte(rtlpriv, REG_TX_RPT_CTRL, u1b_tmp & (~BIT(1))); + + u1b_tmp = rtl_read_byte(rtlpriv, REG_RXDMA_CONTROL); + while (!(u1b_tmp & BIT(1)) && (count++ < 100)) { + udelay(10); + u1b_tmp = rtl_read_byte(rtlpriv, REG_RXDMA_CONTROL); + count++; + } + rtl_write_byte(rtlpriv, REG_PCIE_CTRL_REG+1, 0xFF); + + rtl88_hal_pwrseqcmdparsing(rtlpriv, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, + PWR_INTF_PCI_MSK, + Rtl8188E_NIC_LPS_ENTER_FLOW); + + rtl_write_byte(rtlpriv, REG_RF_CTRL, 0x00); + + if ((rtl_read_byte(rtlpriv, REG_MCUFWDL) & BIT(7)) && rtlhal->fw_ready) + rtl88e_firmware_selfreset(hw); + + u1b_tmp = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN+1); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN + 1, (u1b_tmp & (~BIT(2)))); + rtl_write_byte(rtlpriv, REG_MCUFWDL, 0x00); + + u1b_tmp = rtl_read_byte(rtlpriv, REG_32K_CTRL); + rtl_write_byte(rtlpriv, REG_32K_CTRL, (u1b_tmp & (~BIT(0)))); + + rtl88_hal_pwrseqcmdparsing(rtlpriv, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, + PWR_INTF_PCI_MSK, Rtl8188E_NIC_DISABLE_FLOW); + + u1b_tmp = rtl_read_byte(rtlpriv, REG_RSV_CTRL+1); + rtl_write_byte(rtlpriv, REG_RSV_CTRL+1, (u1b_tmp & (~BIT(3)))); + u1b_tmp = rtl_read_byte(rtlpriv, REG_RSV_CTRL+1); + rtl_write_byte(rtlpriv, REG_RSV_CTRL+1, (u1b_tmp | BIT(3))); + + rtl_write_byte(rtlpriv, REG_RSV_CTRL, 0x0E); + + u1b_tmp = rtl_read_byte(rtlpriv, GPIO_IN); + rtl_write_byte(rtlpriv, GPIO_OUT, u1b_tmp); + rtl_write_byte(rtlpriv, GPIO_IO_SEL, 0x7F); + + u1b_tmp = rtl_read_byte(rtlpriv, REG_GPIO_IO_SEL); + rtl_write_byte(rtlpriv, REG_GPIO_IO_SEL, (u1b_tmp << 4) | u1b_tmp); + u1b_tmp = rtl_read_byte(rtlpriv, REG_GPIO_IO_SEL+1); + rtl_write_byte(rtlpriv, REG_GPIO_IO_SEL+1, u1b_tmp | 0x0F); + + rtl_write_dword(rtlpriv, REG_GPIO_IO_SEL_2+2, 0x00080808); +} + +void rtl88ee_card_disable(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + enum nl80211_iftype opmode; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "RTL8188ee card disable\n"); + + mac->link_state = MAC80211_NOLINK; + opmode = NL80211_IFTYPE_UNSPECIFIED; + + _rtl88ee_set_media_status(hw, opmode); + + if (rtlpriv->rtlhal.driver_is_goingto_unload || + ppsc->rfoff_reason > RF_CHANGE_BY_PS) + rtlpriv->cfg->ops->led_control(hw, LED_CTL_POWER_OFF); + + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + _rtl88ee_poweroff_adapter(hw); + + /* after power off we should do iqk again */ + rtlpriv->phy.iqk_initialized = false; +} + +void rtl88ee_interrupt_recognized(struct ieee80211_hw *hw, + u32 *p_inta, u32 *p_intb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + *p_inta = rtl_read_dword(rtlpriv, ISR) & rtlpci->irq_mask[0]; + rtl_write_dword(rtlpriv, ISR, *p_inta); + + *p_intb = rtl_read_dword(rtlpriv, REG_HISRE) & rtlpci->irq_mask[1]; + rtl_write_dword(rtlpriv, REG_HISRE, *p_intb); +} + +void rtl88ee_set_beacon_related_registers(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u16 bcn_interval, atim_window; + + bcn_interval = mac->beacon_interval; + atim_window = 2; /*FIX MERGE */ + rtl88ee_disable_interrupt(hw); + rtl_write_word(rtlpriv, REG_ATIMWND, atim_window); + rtl_write_word(rtlpriv, REG_BCN_INTERVAL, bcn_interval); + rtl_write_word(rtlpriv, REG_BCNTCFG, 0x660f); + rtl_write_byte(rtlpriv, REG_RXTSF_OFFSET_CCK, 0x18); + rtl_write_byte(rtlpriv, REG_RXTSF_OFFSET_OFDM, 0x18); + rtl_write_byte(rtlpriv, 0x606, 0x30); + rtlpci->reg_bcn_ctrl_val |= BIT(3); + rtl_write_byte(rtlpriv, REG_BCN_CTRL, (u8) rtlpci->reg_bcn_ctrl_val); + /*rtl88ee_enable_interrupt(hw);*/ +} + +void rtl88ee_set_beacon_interval(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 bcn_interval = mac->beacon_interval; + + RT_TRACE(rtlpriv, COMP_BEACON, DBG_DMESG, + "beacon_interval:%d\n", bcn_interval); + /*rtl88ee_disable_interrupt(hw);*/ + rtl_write_word(rtlpriv, REG_BCN_INTERVAL, bcn_interval); + /*rtl88ee_enable_interrupt(hw);*/ +} + +void rtl88ee_update_interrupt_mask(struct ieee80211_hw *hw, + u32 add_msr, u32 rm_msr) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + RT_TRACE(rtlpriv, COMP_INTR, DBG_LOUD, + "add_msr:%x, rm_msr:%x\n", add_msr, rm_msr); + + rtl88ee_disable_interrupt(hw); + if (add_msr) + rtlpci->irq_mask[0] |= add_msr; + if (rm_msr) + rtlpci->irq_mask[0] &= (~rm_msr); + rtl88ee_enable_interrupt(hw); +} + +static inline u8 get_chnl_group(u8 chnl) +{ + u8 group; + + group = chnl / 3; + if (chnl == 14) + group = 5; + + return group; +} + +static void set_diff0_2g(struct txpower_info_2g *pwr2g, u8 *hwinfo, u32 path, + u32 i, u32 eadr) +{ + pwr2g->bw40_diff[path][i] = 0; + if (hwinfo[eadr] == 0xFF) { + pwr2g->bw20_diff[path][i] = 0x02; + } else { + pwr2g->bw20_diff[path][i] = (hwinfo[eadr]&0xf0)>>4; + /*bit sign number to 8 bit sign number*/ + if (pwr2g->bw20_diff[path][i] & BIT(3)) + pwr2g->bw20_diff[path][i] |= 0xF0; + } + + if (hwinfo[eadr] == 0xFF) { + pwr2g->ofdm_diff[path][i] = 0x04; + } else { + pwr2g->ofdm_diff[path][i] = (hwinfo[eadr] & 0x0f); + /*bit sign number to 8 bit sign number*/ + if (pwr2g->ofdm_diff[path][i] & BIT(3)) + pwr2g->ofdm_diff[path][i] |= 0xF0; + } + pwr2g->cck_diff[path][i] = 0; +} + +static void set_diff0_5g(struct txpower_info_5g *pwr5g, u8 *hwinfo, u32 path, + u32 i, u32 eadr) +{ + pwr5g->bw40_diff[path][i] = 0; + if (hwinfo[eadr] == 0xFF) { + pwr5g->bw20_diff[path][i] = 0; + } else { + pwr5g->bw20_diff[path][i] = (hwinfo[eadr]&0xf0)>>4; + /*bit sign number to 8 bit sign number*/ + if (pwr5g->bw20_diff[path][i] & BIT(3)) + pwr5g->bw20_diff[path][i] |= 0xF0; + } + + if (hwinfo[eadr] == 0xFF) { + pwr5g->ofdm_diff[path][i] = 0x04; + } else { + pwr5g->ofdm_diff[path][i] = (hwinfo[eadr] & 0x0f); + /*bit sign number to 8 bit sign number*/ + if (pwr5g->ofdm_diff[path][i] & BIT(3)) + pwr5g->ofdm_diff[path][i] |= 0xF0; + } +} + +static void set_diff1_2g(struct txpower_info_2g *pwr2g, u8 *hwinfo, u32 path, + u32 i, u32 eadr) +{ + if (hwinfo[eadr] == 0xFF) { + pwr2g->bw40_diff[path][i] = 0xFE; + } else { + pwr2g->bw40_diff[path][i] = (hwinfo[eadr]&0xf0)>>4; + if (pwr2g->bw40_diff[path][i] & BIT(3)) + pwr2g->bw40_diff[path][i] |= 0xF0; + } + + if (hwinfo[eadr] == 0xFF) { + pwr2g->bw20_diff[path][i] = 0xFE; + } else { + pwr2g->bw20_diff[path][i] = (hwinfo[eadr]&0x0f); + if (pwr2g->bw20_diff[path][i] & BIT(3)) + pwr2g->bw20_diff[path][i] |= 0xF0; + } +} + +static void set_diff1_5g(struct txpower_info_5g *pwr5g, u8 *hwinfo, u32 path, + u32 i, u32 eadr) +{ + if (hwinfo[eadr] == 0xFF) { + pwr5g->bw40_diff[path][i] = 0xFE; + } else { + pwr5g->bw40_diff[path][i] = (hwinfo[eadr]&0xf0)>>4; + if (pwr5g->bw40_diff[path][i] & BIT(3)) + pwr5g->bw40_diff[path][i] |= 0xF0; + } + + if (hwinfo[eadr] == 0xFF) { + pwr5g->bw20_diff[path][i] = 0xFE; + } else { + pwr5g->bw20_diff[path][i] = (hwinfo[eadr] & 0x0f); + if (pwr5g->bw20_diff[path][i] & BIT(3)) + pwr5g->bw20_diff[path][i] |= 0xF0; + } +} + +static void set_diff2_2g(struct txpower_info_2g *pwr2g, u8 *hwinfo, u32 path, + u32 i, u32 eadr) +{ + if (hwinfo[eadr] == 0xFF) { + pwr2g->ofdm_diff[path][i] = 0xFE; + } else { + pwr2g->ofdm_diff[path][i] = (hwinfo[eadr]&0xf0)>>4; + if (pwr2g->ofdm_diff[path][i] & BIT(3)) + pwr2g->ofdm_diff[path][i] |= 0xF0; + } + + if (hwinfo[eadr] == 0xFF) { + pwr2g->cck_diff[path][i] = 0xFE; + } else { + pwr2g->cck_diff[path][i] = (hwinfo[eadr]&0x0f); + if (pwr2g->cck_diff[path][i] & BIT(3)) + pwr2g->cck_diff[path][i] |= 0xF0; + } +} + +static void _rtl8188e_read_power_value_fromprom(struct ieee80211_hw *hw, + struct txpower_info_2g *pwr2g, + struct txpower_info_5g *pwr5g, + bool autoload_fail, + u8 *hwinfo) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 path, eadr = EEPROM_TX_PWR_INX, i; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "hal_ReadPowerValueFromPROM88E(): PROMContent[0x%x]= 0x%x\n", + (eadr+1), hwinfo[eadr+1]); + if (0xFF == hwinfo[eadr+1]) + autoload_fail = true; + + if (autoload_fail) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "auto load fail : Use Default value!\n"); + for (path = 0; path < MAX_RF_PATH; path++) { + /* 2.4G default value */ + for (i = 0; i < MAX_CHNL_GROUP_24G; i++) { + pwr2g->index_cck_base[path][i] = 0x2D; + pwr2g->index_bw40_base[path][i] = 0x2D; + } + for (i = 0; i < MAX_TX_COUNT; i++) { + if (i == 0) { + pwr2g->bw20_diff[path][0] = 0x02; + pwr2g->ofdm_diff[path][0] = 0x04; + } else { + pwr2g->bw20_diff[path][i] = 0xFE; + pwr2g->bw40_diff[path][i] = 0xFE; + pwr2g->cck_diff[path][i] = 0xFE; + pwr2g->ofdm_diff[path][i] = 0xFE; + } + } + } + return; + } + + for (path = 0; path < MAX_RF_PATH; path++) { + /*2.4G default value*/ + for (i = 0; i < MAX_CHNL_GROUP_24G; i++) { + pwr2g->index_cck_base[path][i] = hwinfo[eadr++]; + if (pwr2g->index_cck_base[path][i] == 0xFF) + pwr2g->index_cck_base[path][i] = 0x2D; + } + for (i = 0; i < MAX_CHNL_GROUP_24G; i++) { + pwr2g->index_bw40_base[path][i] = hwinfo[eadr++]; + if (pwr2g->index_bw40_base[path][i] == 0xFF) + pwr2g->index_bw40_base[path][i] = 0x2D; + } + for (i = 0; i < MAX_TX_COUNT; i++) { + if (i == 0) { + set_diff0_2g(pwr2g, hwinfo, path, i, eadr); + eadr++; + } else { + set_diff1_2g(pwr2g, hwinfo, path, i, eadr); + eadr++; + + set_diff2_2g(pwr2g, hwinfo, path, i, eadr); + eadr++; + } + } + + /*5G default value*/ + for (i = 0; i < MAX_CHNL_GROUP_5G; i++) { + pwr5g->index_bw40_base[path][i] = hwinfo[eadr++]; + if (pwr5g->index_bw40_base[path][i] == 0xFF) + pwr5g->index_bw40_base[path][i] = 0xFE; + } + + for (i = 0; i < MAX_TX_COUNT; i++) { + if (i == 0) { + set_diff0_5g(pwr5g, hwinfo, path, i, eadr); + eadr++; + } else { + set_diff1_5g(pwr5g, hwinfo, path, i, eadr); + eadr++; + } + } + + if (hwinfo[eadr] == 0xFF) { + pwr5g->ofdm_diff[path][1] = 0xFE; + pwr5g->ofdm_diff[path][2] = 0xFE; + } else { + pwr5g->ofdm_diff[path][1] = (hwinfo[eadr] & 0xf0) >> 4; + pwr5g->ofdm_diff[path][2] = (hwinfo[eadr] & 0x0f); + } + eadr++; + + if (hwinfo[eadr] == 0xFF) + pwr5g->ofdm_diff[path][3] = 0xFE; + else + pwr5g->ofdm_diff[path][3] = (hwinfo[eadr]&0x0f); + eadr++; + + for (i = 1; i < MAX_TX_COUNT; i++) { + if (pwr5g->ofdm_diff[path][i] == 0xFF) + pwr5g->ofdm_diff[path][i] = 0xFE; + else if (pwr5g->ofdm_diff[path][i] & BIT(3)) + pwr5g->ofdm_diff[path][i] |= 0xF0; + } + } +} + +static void _rtl88ee_read_txpower_info_from_hwpg(struct ieee80211_hw *hw, + bool autoload_fail, + u8 *hwinfo) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct txpower_info_2g pwrinfo24g; + struct txpower_info_5g pwrinfo5g; + u8 rf_path, index; + u8 i; + int jj = EEPROM_RF_BOARD_OPTION_88E; + int kk = EEPROM_THERMAL_METER_88E; + + _rtl8188e_read_power_value_fromprom(hw, &pwrinfo24g, &pwrinfo5g, + autoload_fail, hwinfo); + + for (rf_path = 0; rf_path < 2; rf_path++) { + for (i = 0; i < 14; i++) { + index = get_chnl_group(i+1); + + rtlefuse->txpwrlevel_cck[rf_path][i] = + pwrinfo24g.index_cck_base[rf_path][index]; + if (i == 13) + rtlefuse->txpwrlevel_ht40_1s[rf_path][i] = + pwrinfo24g.index_bw40_base[rf_path][4]; + else + rtlefuse->txpwrlevel_ht40_1s[rf_path][i] = + pwrinfo24g.index_bw40_base[rf_path][index]; + rtlefuse->txpwr_ht20diff[rf_path][i] = + pwrinfo24g.bw20_diff[rf_path][0]; + rtlefuse->txpwr_legacyhtdiff[rf_path][i] = + pwrinfo24g.ofdm_diff[rf_path][0]; + } + + for (i = 0; i < 14; i++) { + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF(%d)-Ch(%d) [CCK / HT40_1S ] = " + "[0x%x / 0x%x ]\n", rf_path, i, + rtlefuse->txpwrlevel_cck[rf_path][i], + rtlefuse->txpwrlevel_ht40_1s[rf_path][i]); + } + } + + if (!autoload_fail) + rtlefuse->eeprom_thermalmeter = hwinfo[kk]; + else + rtlefuse->eeprom_thermalmeter = EEPROM_DEFAULT_THERMALMETER; + + if (rtlefuse->eeprom_thermalmeter == 0xff || autoload_fail) { + rtlefuse->apk_thermalmeterignore = true; + rtlefuse->eeprom_thermalmeter = EEPROM_DEFAULT_THERMALMETER; + } + + rtlefuse->thermalmeter[0] = rtlefuse->eeprom_thermalmeter; + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "thermalmeter = 0x%x\n", rtlefuse->eeprom_thermalmeter); + + if (!autoload_fail) { + rtlefuse->eeprom_regulatory = hwinfo[jj] & 0x07;/*bit0~2*/ + if (hwinfo[jj] == 0xFF) + rtlefuse->eeprom_regulatory = 0; + } else { + rtlefuse->eeprom_regulatory = 0; + } + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "eeprom_regulatory = 0x%x\n", rtlefuse->eeprom_regulatory); +} + +static void _rtl88ee_read_adapter_info(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_pci_priv *rppriv = rtl_pcipriv(hw); + u16 i, usvalue; + u8 hwinfo[HWSET_MAX_SIZE]; + u16 eeprom_id; + int jj = EEPROM_RF_BOARD_OPTION_88E; + int kk = EEPROM_RF_FEATURE_OPTION_88E; + + if (rtlefuse->epromtype == EEPROM_BOOT_EFUSE) { + rtl_efuse_shadow_map_update(hw); + + memcpy(hwinfo, &rtlefuse->efuse_map[EFUSE_INIT_MAP][0], + HWSET_MAX_SIZE); + } else if (rtlefuse->epromtype == EEPROM_93C46) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "RTL819X Not boot from eeprom, check it !!"); + } + + RT_PRINT_DATA(rtlpriv, COMP_INIT, DBG_DMESG, ("MAP\n"), + hwinfo, HWSET_MAX_SIZE); + + eeprom_id = *((u16 *)&hwinfo[0]); + if (eeprom_id != RTL8188E_EEPROM_ID) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "EEPROM ID(%#x) is invalid!!\n", eeprom_id); + rtlefuse->autoload_failflag = true; + } else { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Autoload OK\n"); + rtlefuse->autoload_failflag = false; + } + + if (rtlefuse->autoload_failflag == true) + return; + /*VID DID SVID SDID*/ + rtlefuse->eeprom_vid = *(u16 *)&hwinfo[EEPROM_VID]; + rtlefuse->eeprom_did = *(u16 *)&hwinfo[EEPROM_DID]; + rtlefuse->eeprom_svid = *(u16 *)&hwinfo[EEPROM_SVID]; + rtlefuse->eeprom_smid = *(u16 *)&hwinfo[EEPROM_SMID]; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROMId = 0x%4x\n", eeprom_id); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM VID = 0x%4x\n", rtlefuse->eeprom_vid); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM DID = 0x%4x\n", rtlefuse->eeprom_did); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM SVID = 0x%4x\n", rtlefuse->eeprom_svid); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM SMID = 0x%4x\n", rtlefuse->eeprom_smid); + /*customer ID*/ + rtlefuse->eeprom_oemid = *(u8 *)&hwinfo[EEPROM_CUSTOMER_ID]; + if (rtlefuse->eeprom_oemid == 0xFF) + rtlefuse->eeprom_oemid = 0; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM Customer ID: 0x%2x\n", rtlefuse->eeprom_oemid); + /*EEPROM version*/ + rtlefuse->eeprom_version = *(u16 *)&hwinfo[EEPROM_VERSION]; + /*mac address*/ + for (i = 0; i < 6; i += 2) { + usvalue = *(u16 *)&hwinfo[EEPROM_MAC_ADDR + i]; + *((u16 *)(&rtlefuse->dev_addr[i])) = usvalue; + } + + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "dev_addr: %pM\n", rtlefuse->dev_addr); + /*channel plan */ + rtlefuse->eeprom_channelplan = *(u8 *)&hwinfo[EEPROM_CHANNELPLAN]; + /* set channel paln to world wide 13 */ + rtlefuse->channel_plan = COUNTRY_CODE_WORLD_WIDE_13; + /*tx power*/ + _rtl88ee_read_txpower_info_from_hwpg(hw, rtlefuse->autoload_failflag, + hwinfo); + rtlefuse->txpwr_fromeprom = true; + + rtl8188ee_read_bt_coexist_info_from_hwpg(hw, + rtlefuse->autoload_failflag, + hwinfo); + /*board type*/ + rtlefuse->board_type = (((*(u8 *)&hwinfo[jj]) & 0xE0) >> 5); + /*Wake on wlan*/ + rtlefuse->wowlan_enable = ((hwinfo[kk] & 0x40) >> 6); + /*parse xtal*/ + rtlefuse->crystalcap = hwinfo[EEPROM_XTAL_88E]; + if (hwinfo[EEPROM_XTAL_88E]) + rtlefuse->crystalcap = 0x20; + /*antenna diversity*/ + rtlefuse->antenna_div_cfg = (hwinfo[jj] & 0x18) >> 3; + if (hwinfo[jj] == 0xFF) + rtlefuse->antenna_div_cfg = 0; + if (rppriv->bt_coexist.eeprom_bt_coexist != 0 && + rppriv->bt_coexist.eeprom_bt_ant_num == ANT_X1) + rtlefuse->antenna_div_cfg = 0; + + rtlefuse->antenna_div_type = hwinfo[EEPROM_RF_ANTENNA_OPT_88E]; + if (rtlefuse->antenna_div_type == 0xFF) + rtlefuse->antenna_div_type = 0x01; + if (rtlefuse->antenna_div_type == CG_TRX_HW_ANTDIV || + rtlefuse->antenna_div_type == CGCS_RX_HW_ANTDIV) + rtlefuse->antenna_div_cfg = 1; + + if (rtlhal->oem_id == RT_CID_DEFAULT) { + switch (rtlefuse->eeprom_oemid) { + case EEPROM_CID_DEFAULT: + if (rtlefuse->eeprom_did == 0x8179) { + if (rtlefuse->eeprom_svid == 0x1025) { + rtlhal->oem_id = RT_CID_819x_Acer; + } else if ((rtlefuse->eeprom_svid == 0x10EC && + rtlefuse->eeprom_smid == 0x0179) || + (rtlefuse->eeprom_svid == 0x17AA && + rtlefuse->eeprom_smid == 0x0179)) { + rtlhal->oem_id = RT_CID_819x_Lenovo; + } else if (rtlefuse->eeprom_svid == 0x103c && + rtlefuse->eeprom_smid == 0x197d) { + rtlhal->oem_id = RT_CID_819x_HP; + } else { + rtlhal->oem_id = RT_CID_DEFAULT; + } + } else { + rtlhal->oem_id = RT_CID_DEFAULT; + } + break; + case EEPROM_CID_TOSHIBA: + rtlhal->oem_id = RT_CID_TOSHIBA; + break; + case EEPROM_CID_QMI: + rtlhal->oem_id = RT_CID_819x_QMI; + break; + case EEPROM_CID_WHQL: + default: + rtlhal->oem_id = RT_CID_DEFAULT; + break; + } + } +} + +static void _rtl88ee_hal_customized_behavior(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + pcipriv->ledctl.led_opendrain = true; + + switch (rtlhal->oem_id) { + case RT_CID_819x_HP: + pcipriv->ledctl.led_opendrain = true; + break; + case RT_CID_819x_Lenovo: + case RT_CID_DEFAULT: + case RT_CID_TOSHIBA: + case RT_CID_CCX: + case RT_CID_819x_Acer: + case RT_CID_WHQL: + default: + break; + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "RT Customized ID: 0x%02X\n", rtlhal->oem_id); +} + +void rtl88ee_read_eeprom_info(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 tmp_u1b; + + rtlhal->version = _rtl88ee_read_chip_version(hw); + if (get_rf_type(rtlphy) == RF_1T1R) { + rtlpriv->dm.rfpath_rxenable[0] = true; + } else { + rtlpriv->dm.rfpath_rxenable[0] = true; + rtlpriv->dm.rfpath_rxenable[1] = true; + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "VersionID = 0x%4x\n", + rtlhal->version); + tmp_u1b = rtl_read_byte(rtlpriv, REG_9346CR); + if (tmp_u1b & BIT(4)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "Boot from EEPROM\n"); + rtlefuse->epromtype = EEPROM_93C46; + } else { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "Boot from EFUSE\n"); + rtlefuse->epromtype = EEPROM_BOOT_EFUSE; + } + if (tmp_u1b & BIT(5)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Autoload OK\n"); + rtlefuse->autoload_failflag = false; + _rtl88ee_read_adapter_info(hw); + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Autoload ERR!!\n"); + } + _rtl88ee_hal_customized_behavior(hw); +} + +static void rtl88ee_update_hal_rate_table(struct ieee80211_hw *hw, + struct ieee80211_sta *sta) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rppriv = rtl_pcipriv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u32 ratr_value; + u8 ratr_index = 0; + u8 nmode = mac->ht_enable; + u8 mimo_ps = IEEE80211_SMPS_OFF; + u16 shortgi_rate; + u32 tmp_ratr_value; + u8 ctx40 = mac->bw_40; + u16 cap = sta->ht_cap.cap; + u8 short40 = (cap & IEEE80211_HT_CAP_SGI_40) ? 1 : 0; + u8 short20 = (cap & IEEE80211_HT_CAP_SGI_20) ? 1 : 0; + enum wireless_mode wirelessmode = mac->mode; + + if (rtlhal->current_bandtype == BAND_ON_5G) + ratr_value = sta->supp_rates[1] << 4; + else + ratr_value = sta->supp_rates[0]; + if (mac->opmode == NL80211_IFTYPE_ADHOC) + ratr_value = 0xfff; + ratr_value |= (sta->ht_cap.mcs.rx_mask[1] << 20 | + sta->ht_cap.mcs.rx_mask[0] << 12); + switch (wirelessmode) { + case WIRELESS_MODE_B: + if (ratr_value & 0x0000000c) + ratr_value &= 0x0000000d; + else + ratr_value &= 0x0000000f; + break; + case WIRELESS_MODE_G: + ratr_value &= 0x00000FF5; + break; + case WIRELESS_MODE_N_24G: + case WIRELESS_MODE_N_5G: + nmode = 1; + if (mimo_ps == IEEE80211_SMPS_STATIC) { + ratr_value &= 0x0007F005; + } else { + u32 ratr_mask; + + if (get_rf_type(rtlphy) == RF_1T2R || + get_rf_type(rtlphy) == RF_1T1R) + ratr_mask = 0x000ff005; + else + ratr_mask = 0x0f0ff005; + + ratr_value &= ratr_mask; + } + break; + default: + if (rtlphy->rf_type == RF_1T2R) + ratr_value &= 0x000ff0ff; + else + ratr_value &= 0x0f0ff0ff; + + break; + } + + if ((rppriv->bt_coexist.bt_coexistence) && + (rppriv->bt_coexist.bt_coexist_type == BT_CSR_BC4) && + (rppriv->bt_coexist.bt_cur_state) && + (rppriv->bt_coexist.bt_ant_isolation) && + ((rppriv->bt_coexist.bt_service == BT_SCO) || + (rppriv->bt_coexist.bt_service == BT_BUSY))) + ratr_value &= 0x0fffcfc0; + else + ratr_value &= 0x0FFFFFFF; + + if (nmode && ((ctx40 && short40) || + (!ctx40 && short20))) { + ratr_value |= 0x10000000; + tmp_ratr_value = (ratr_value >> 12); + + for (shortgi_rate = 15; shortgi_rate > 0; shortgi_rate--) { + if ((1 << shortgi_rate) & tmp_ratr_value) + break; + } + + shortgi_rate = (shortgi_rate << 12) | (shortgi_rate << 8) | + (shortgi_rate << 4) | (shortgi_rate); + } + + rtl_write_dword(rtlpriv, REG_ARFR0 + ratr_index * 4, ratr_value); + + RT_TRACE(rtlpriv, COMP_RATR, DBG_DMESG, + "%x\n", rtl_read_dword(rtlpriv, REG_ARFR0)); +} + +static void rtl88ee_update_hal_rate_mask(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 rssi) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_sta_info *sta_entry = NULL; + u32 ratr_bitmap; + u8 ratr_index; + u16 cap = sta->ht_cap.cap; + u8 ctx40 = (cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) ? 1 : 0; + u8 short40 = (cap & IEEE80211_HT_CAP_SGI_40) ? 1 : 0; + u8 short20 = (cap & IEEE80211_HT_CAP_SGI_20) ? 1 : 0; + enum wireless_mode wirelessmode = 0; + bool shortgi = false; + u8 rate_mask[5]; + u8 macid = 0; + u8 mimo_ps = IEEE80211_SMPS_OFF; + + sta_entry = (struct rtl_sta_info *)sta->drv_priv; + wirelessmode = sta_entry->wireless_mode; + if (mac->opmode == NL80211_IFTYPE_STATION || + mac->opmode == NL80211_IFTYPE_MESH_POINT) + ctx40 = mac->bw_40; + else if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_ADHOC) + macid = sta->aid + 1; + + if (rtlhal->current_bandtype == BAND_ON_5G) + ratr_bitmap = sta->supp_rates[1] << 4; + else + ratr_bitmap = sta->supp_rates[0]; + if (mac->opmode == NL80211_IFTYPE_ADHOC) + ratr_bitmap = 0xfff; + ratr_bitmap |= (sta->ht_cap.mcs.rx_mask[1] << 20 | + sta->ht_cap.mcs.rx_mask[0] << 12); + switch (wirelessmode) { + case WIRELESS_MODE_B: + ratr_index = RATR_INX_WIRELESS_B; + if (ratr_bitmap & 0x0000000c) + ratr_bitmap &= 0x0000000d; + else + ratr_bitmap &= 0x0000000f; + break; + case WIRELESS_MODE_G: + ratr_index = RATR_INX_WIRELESS_GB; + + if (rssi == 1) + ratr_bitmap &= 0x00000f00; + else if (rssi == 2) + ratr_bitmap &= 0x00000ff0; + else + ratr_bitmap &= 0x00000ff5; + break; + case WIRELESS_MODE_A: + ratr_index = RATR_INX_WIRELESS_A; + ratr_bitmap &= 0x00000ff0; + break; + case WIRELESS_MODE_N_24G: + case WIRELESS_MODE_N_5G: + ratr_index = RATR_INX_WIRELESS_NGB; + + if (mimo_ps == IEEE80211_SMPS_STATIC) { + if (rssi == 1) + ratr_bitmap &= 0x00070000; + else if (rssi == 2) + ratr_bitmap &= 0x0007f000; + else + ratr_bitmap &= 0x0007f005; + } else { + if (rtlphy->rf_type == RF_1T2R || + rtlphy->rf_type == RF_1T1R) { + if (ctx40) { + if (rssi == 1) + ratr_bitmap &= 0x000f0000; + else if (rssi == 2) + ratr_bitmap &= 0x000ff000; + else + ratr_bitmap &= 0x000ff015; + } else { + if (rssi == 1) + ratr_bitmap &= 0x000f0000; + else if (rssi == 2) + ratr_bitmap &= 0x000ff000; + else + ratr_bitmap &= 0x000ff005; + } + } else { + if (ctx40) { + if (rssi == 1) + ratr_bitmap &= 0x0f8f0000; + else if (rssi == 2) + ratr_bitmap &= 0x0f8ff000; + else + ratr_bitmap &= 0x0f8ff015; + } else { + if (rssi == 1) + ratr_bitmap &= 0x0f8f0000; + else if (rssi == 2) + ratr_bitmap &= 0x0f8ff000; + else + ratr_bitmap &= 0x0f8ff005; + } + } + } + + if ((ctx40 && short40) || (!ctx40 && short20)) { + if (macid == 0) + shortgi = true; + else if (macid == 1) + shortgi = false; + } + break; + default: + ratr_index = RATR_INX_WIRELESS_NGB; + + if (rtlphy->rf_type == RF_1T2R) + ratr_bitmap &= 0x000ff0ff; + else + ratr_bitmap &= 0x0f0ff0ff; + break; + } + sta_entry->ratr_index = ratr_index; + + RT_TRACE(rtlpriv, COMP_RATR, DBG_DMESG, + "ratr_bitmap :%x\n", ratr_bitmap); + *(u32 *)&rate_mask = (ratr_bitmap & 0x0fffffff) | + (ratr_index << 28); + rate_mask[4] = macid | (shortgi ? 0x20 : 0x00) | 0x80; + RT_TRACE(rtlpriv, COMP_RATR, DBG_DMESG, + "Rate_index:%x, ratr_val:%x, %x:%x:%x:%x:%x\n", + ratr_index, ratr_bitmap, rate_mask[0], rate_mask[1], + rate_mask[2], rate_mask[3], rate_mask[4]); + rtl88e_fill_h2c_cmd(hw, H2C_88E_RA_MASK, 5, rate_mask); + _rtl88ee_set_bcn_ctrl_reg(hw, BIT(3), 0); +} + +void rtl88ee_update_hal_rate_tbl(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 rssi) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (rtlpriv->dm.useramask) + rtl88ee_update_hal_rate_mask(hw, sta, rssi); + else + rtl88ee_update_hal_rate_table(hw, sta); +} + +void rtl88ee_update_channel_access_setting(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 sifs_timer; + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SLOT_TIME, + (u8 *)&mac->slot_time); + if (!mac->ht_enable) + sifs_timer = 0x0a0a; + else + sifs_timer = 0x0e0e; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SIFS, (u8 *)&sifs_timer); +} + +bool rtl88ee_gpio_radio_on_off_checking(struct ieee80211_hw *hw, u8 *valid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + enum rf_pwrstate state_toset; + u32 u4tmp; + bool actuallyset = false; + + if (rtlpriv->rtlhal.being_init_adapter) + return false; + + if (ppsc->swrf_processing) + return false; + + spin_lock(&rtlpriv->locks.rf_ps_lock); + if (ppsc->rfchange_inprogress) { + spin_unlock(&rtlpriv->locks.rf_ps_lock); + return false; + } else { + ppsc->rfchange_inprogress = true; + spin_unlock(&rtlpriv->locks.rf_ps_lock); + } + + u4tmp = rtl_read_dword(rtlpriv, REG_GPIO_OUTPUT); + state_toset = (u4tmp & BIT(31)) ? ERFON : ERFOFF; + + + if ((ppsc->hwradiooff == true) && (state_toset == ERFON)) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "GPIOChangeRF - HW Radio ON, RF ON\n"); + + state_toset = ERFON; + ppsc->hwradiooff = false; + actuallyset = true; + } else if ((ppsc->hwradiooff == false) && (state_toset == ERFOFF)) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "GPIOChangeRF - HW Radio OFF, RF OFF\n"); + + state_toset = ERFOFF; + ppsc->hwradiooff = true; + actuallyset = true; + } + + if (actuallyset) { + spin_lock(&rtlpriv->locks.rf_ps_lock); + ppsc->rfchange_inprogress = false; + spin_unlock(&rtlpriv->locks.rf_ps_lock); + } else { + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_HALT_NIC) + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + + spin_lock(&rtlpriv->locks.rf_ps_lock); + ppsc->rfchange_inprogress = false; + spin_unlock(&rtlpriv->locks.rf_ps_lock); + } + + *valid = 1; + return !ppsc->hwradiooff; +} + +static void add_one_key(struct ieee80211_hw *hw, u8 *macaddr, + struct rtl_mac *mac, u32 key, u32 id, + u8 enc_algo, bool is_pairwise) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, "add one entry\n"); + if (is_pairwise) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, "set Pairwise key\n"); + + rtl_cam_add_one_entry(hw, macaddr, key, id, enc_algo, + CAM_CONFIG_NO_USEDK, + rtlpriv->sec.key_buf[key]); + } else { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, "set group key\n"); + + if (mac->opmode == NL80211_IFTYPE_ADHOC) { + rtl_cam_add_one_entry(hw, rtlefuse->dev_addr, + PAIRWISE_KEYIDX, + CAM_PAIRWISE_KEY_POSITION, + enc_algo, + CAM_CONFIG_NO_USEDK, + rtlpriv->sec.key_buf[id]); + } + + rtl_cam_add_one_entry(hw, macaddr, key, id, enc_algo, + CAM_CONFIG_NO_USEDK, + rtlpriv->sec.key_buf[id]); + } +} + +void rtl88ee_set_key(struct ieee80211_hw *hw, u32 key, + u8 *mac_ad, bool is_group, u8 enc_algo, + bool is_wepkey, bool clear_all) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u8 *macaddr = mac_ad; + u32 id = 0; + bool is_pairwise = false; + + static u8 cam_const_addr[4][6] = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x03} + }; + static u8 cam_const_broad[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + if (clear_all) { + u8 idx = 0; + u8 cam_offset = 0; + u8 clear_number = 5; + + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, "clear_all\n"); + + for (idx = 0; idx < clear_number; idx++) { + rtl_cam_mark_invalid(hw, cam_offset + idx); + rtl_cam_empty_entry(hw, cam_offset + idx); + + if (idx < 5) { + memset(rtlpriv->sec.key_buf[idx], 0, + MAX_KEY_LEN); + rtlpriv->sec.key_len[idx] = 0; + } + } + + } else { + switch (enc_algo) { + case WEP40_ENCRYPTION: + enc_algo = CAM_WEP40; + break; + case WEP104_ENCRYPTION: + enc_algo = CAM_WEP104; + break; + case TKIP_ENCRYPTION: + enc_algo = CAM_TKIP; + break; + case AESCCMP_ENCRYPTION: + enc_algo = CAM_AES; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + enc_algo = CAM_TKIP; + break; + } + + if (is_wepkey || rtlpriv->sec.use_defaultkey) { + macaddr = cam_const_addr[key]; + id = key; + } else { + if (is_group) { + macaddr = cam_const_broad; + id = key; + } else { + if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_MESH_POINT) { + id = rtl_cam_get_free_entry(hw, mac_ad); + if (id >= TOTAL_CAM_ENTRY) { + RT_TRACE(rtlpriv, COMP_SEC, + DBG_EMERG, + "Can not find free hw security cam entry\n"); + return; + } + } else { + id = CAM_PAIRWISE_KEY_POSITION; + } + + key = PAIRWISE_KEYIDX; + is_pairwise = true; + } + } + + if (rtlpriv->sec.key_len[key] == 0) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "delete one entry, id is %d\n", id); + if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_MESH_POINT) + rtl_cam_del_entry(hw, mac_ad); + rtl_cam_delete_one_entry(hw, mac_ad, id); + } else { + add_one_key(hw, macaddr, mac, key, id, enc_algo, + is_pairwise); + } + } +} + +static void rtl8188ee_bt_var_init(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *rppriv = rtl_pcipriv(hw); + struct bt_coexist_info coexist = rppriv->bt_coexist; + + coexist.bt_coexistence = rppriv->bt_coexist.eeprom_bt_coexist; + coexist.bt_ant_num = coexist.eeprom_bt_ant_num; + coexist.bt_coexist_type = coexist.eeprom_bt_type; + + if (coexist.reg_bt_iso == 2) + coexist.bt_ant_isolation = coexist.eeprom_bt_ant_isol; + else + coexist.bt_ant_isolation = coexist.reg_bt_iso; + + coexist.bt_radio_shared_type = coexist.eeprom_bt_radio_shared; + + if (coexist.bt_coexistence) { + if (coexist.reg_bt_sco == 1) + coexist.bt_service = BT_OTHER_ACTION; + else if (coexist.reg_bt_sco == 2) + coexist.bt_service = BT_SCO; + else if (coexist.reg_bt_sco == 4) + coexist.bt_service = BT_BUSY; + else if (coexist.reg_bt_sco == 5) + coexist.bt_service = BT_OTHERBUSY; + else + coexist.bt_service = BT_IDLE; + + coexist.bt_edca_ul = 0; + coexist.bt_edca_dl = 0; + coexist.bt_rssi_state = 0xff; + } +} + +void rtl8188ee_read_bt_coexist_info_from_hwpg(struct ieee80211_hw *hw, + bool auto_load_fail, u8 *hwinfo) +{ + rtl8188ee_bt_var_init(hw); +} + +void rtl8188ee_bt_reg_init(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *rppriv = rtl_pcipriv(hw); + + /* 0:Low, 1:High, 2:From Efuse. */ + rppriv->bt_coexist.reg_bt_iso = 2; + /* 0:Idle, 1:None-SCO, 2:SCO, 3:From Counter. */ + rppriv->bt_coexist.reg_bt_sco = 3; + /* 0:Disable BT control A-MPDU, 1:Enable BT control A-MPDU. */ + rppriv->bt_coexist.reg_bt_sco = 0; +} + +void rtl8188ee_bt_hw_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_pci_priv *rppriv = rtl_pcipriv(hw); + struct bt_coexist_info coexist = rppriv->bt_coexist; + u8 u1_tmp; + + if (coexist.bt_coexistence && + ((coexist.bt_coexist_type == BT_CSR_BC4) || + coexist.bt_coexist_type == BT_CSR_BC8)) { + if (coexist.bt_ant_isolation) + rtl_write_byte(rtlpriv, REG_GPIO_MUXCFG, 0xa0); + + u1_tmp = rtl_read_byte(rtlpriv, 0x4fd) & + BIT_OFFSET_LEN_MASK_32(0, 1); + u1_tmp = u1_tmp | ((coexist.bt_ant_isolation == 1) ? + 0 : BIT_OFFSET_LEN_MASK_32(1, 1)) | + ((coexist.bt_service == BT_SCO) ? + 0 : BIT_OFFSET_LEN_MASK_32(2, 1)); + rtl_write_byte(rtlpriv, 0x4fd, u1_tmp); + + rtl_write_dword(rtlpriv, REG_BT_COEX_TABLE+4, 0xaaaa9aaa); + rtl_write_dword(rtlpriv, REG_BT_COEX_TABLE+8, 0xffbd0040); + rtl_write_dword(rtlpriv, REG_BT_COEX_TABLE+0xc, 0x40000010); + + /* Config to 1T1R. */ + if (rtlphy->rf_type == RF_1T1R) { + u1_tmp = rtl_read_byte(rtlpriv, ROFDM0_TRXPATHENABLE); + u1_tmp &= ~(BIT_OFFSET_LEN_MASK_32(1, 1)); + rtl_write_byte(rtlpriv, ROFDM0_TRXPATHENABLE, u1_tmp); + + u1_tmp = rtl_read_byte(rtlpriv, ROFDM1_TRXPATHENABLE); + u1_tmp &= ~(BIT_OFFSET_LEN_MASK_32(1, 1)); + rtl_write_byte(rtlpriv, ROFDM1_TRXPATHENABLE, u1_tmp); + } + } +} + +void rtl88ee_suspend(struct ieee80211_hw *hw) +{ +} + +void rtl88ee_resume(struct ieee80211_hw *hw) +{ +} + +/* Turn on AAP (RCR:bit 0) for promicuous mode. */ +void rtl88ee_allow_all_destaddr(struct ieee80211_hw *hw, + bool allow_all_da, bool write_into_reg) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + if (allow_all_da) /* Set BIT0 */ + rtlpci->receive_config |= RCR_AAP; + else /* Clear BIT0 */ + rtlpci->receive_config &= ~RCR_AAP; + + if (write_into_reg) + rtl_write_dword(rtlpriv, REG_RCR, rtlpci->receive_config); + + RT_TRACE(rtlpriv, COMP_TURBO | COMP_INIT, DBG_LOUD, + "receive_config = 0x%08X, write_into_reg =%d\n", + rtlpci->receive_config, write_into_reg); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/hw.h b/drivers/net/wireless/rtlwifi/rtl8188ee/hw.h new file mode 100644 index 0000000..b4460a4 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/hw.h @@ -0,0 +1,68 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CE_HW_H__ +#define __RTL92CE_HW_H__ + +void rtl88ee_get_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val); +void rtl88ee_read_eeprom_info(struct ieee80211_hw *hw); +void rtl88ee_interrupt_recognized(struct ieee80211_hw *hw, + u32 *p_inta, u32 *p_intb); +int rtl88ee_hw_init(struct ieee80211_hw *hw); +void rtl88ee_card_disable(struct ieee80211_hw *hw); +void rtl88ee_enable_interrupt(struct ieee80211_hw *hw); +void rtl88ee_disable_interrupt(struct ieee80211_hw *hw); +int rtl88ee_set_network_type(struct ieee80211_hw *hw, enum nl80211_iftype type); +void rtl88ee_set_check_bssid(struct ieee80211_hw *hw, bool check_bssid); +void rtl88ee_set_qos(struct ieee80211_hw *hw, int aci); +void rtl88ee_set_beacon_related_registers(struct ieee80211_hw *hw); +void rtl88ee_set_beacon_interval(struct ieee80211_hw *hw); +void rtl88ee_update_interrupt_mask(struct ieee80211_hw *hw, + u32 add_msr, u32 rm_msr); +void rtl88ee_set_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val); +void rtl88ee_update_hal_rate_tbl(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 rssi_level); +void rtl88ee_update_channel_access_setting(struct ieee80211_hw *hw); +bool rtl88ee_gpio_radio_on_off_checking(struct ieee80211_hw *hw, u8 *valid); +void rtl88ee_enable_hw_security_config(struct ieee80211_hw *hw); +void rtl88ee_set_key(struct ieee80211_hw *hw, u32 key_index, + u8 *p_macaddr, bool is_group, u8 enc_algo, + bool is_wepkey, bool clear_all); + +void rtl8188ee_read_bt_coexist_info_from_hwpg(struct ieee80211_hw *hw, + bool autoload_fail, u8 *hwinfo); +void rtl8188ee_bt_reg_init(struct ieee80211_hw *hw); +void rtl8188ee_bt_hw_init(struct ieee80211_hw *hw); +void rtl88ee_suspend(struct ieee80211_hw *hw); +void rtl88ee_resume(struct ieee80211_hw *hw); +void rtl88ee_allow_all_destaddr(struct ieee80211_hw *hw, + bool allow_all_da, bool write_into_reg); +void rtl88ee_fw_clk_off_timer_callback(unsigned long data); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/led.c b/drivers/net/wireless/rtlwifi/rtl8188ee/led.c new file mode 100644 index 0000000..c81a9cb --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/led.c @@ -0,0 +1,157 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "reg.h" +#include "led.h" + +static void rtl88ee_init_led(struct ieee80211_hw *hw, + struct rtl_led *pled, enum rtl_led_pin ledpin) +{ + pled->hw = hw; + pled->ledpin = ledpin; + pled->ledon = false; +} + +void rtl88ee_sw_led_on(struct ieee80211_hw *hw, struct rtl_led *pled) +{ + u8 ledcfg; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, + "LedAddr:%X ledpin =%d\n", REG_LEDCFG2, pled->ledpin); + + switch (pled->ledpin) { + case LED_PIN_GPIO0: + break; + case LED_PIN_LED0: + ledcfg = rtl_read_byte(rtlpriv, REG_LEDCFG2); + rtl_write_byte(rtlpriv, REG_LEDCFG2, + (ledcfg & 0xf0) | BIT(5) | BIT(6)); + break; + case LED_PIN_LED1: + ledcfg = rtl_read_byte(rtlpriv, REG_LEDCFG1); + rtl_write_byte(rtlpriv, REG_LEDCFG1, ledcfg & 0x10); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + pled->ledon = true; +} + +void rtl88ee_sw_led_off(struct ieee80211_hw *hw, struct rtl_led *pled) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + u8 ledcfg; + u8 val; + + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, + "LedAddr:%X ledpin =%d\n", REG_LEDCFG2, pled->ledpin); + + switch (pled->ledpin) { + case LED_PIN_GPIO0: + break; + case LED_PIN_LED0: + ledcfg = rtl_read_byte(rtlpriv, REG_LEDCFG2); + ledcfg &= 0xf0; + val = ledcfg | BIT(3) | BIT(5) | BIT(6); + if (pcipriv->ledctl.led_opendrain == true) { + rtl_write_byte(rtlpriv, REG_LEDCFG2, val); + ledcfg = rtl_read_byte(rtlpriv, REG_MAC_PINMUX_CFG); + val = ledcfg & 0xFE; + rtl_write_byte(rtlpriv, REG_MAC_PINMUX_CFG, val); + } else { + rtl_write_byte(rtlpriv, REG_LEDCFG2, val); + } + break; + case LED_PIN_LED1: + ledcfg = rtl_read_byte(rtlpriv, REG_LEDCFG1); + ledcfg &= 0x10; + rtl_write_byte(rtlpriv, REG_LEDCFG1, (ledcfg | BIT(3))); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + pled->ledon = false; +} + +void rtl88ee_init_sw_leds(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + + rtl88ee_init_led(hw, &(pcipriv->ledctl.sw_led0), LED_PIN_LED0); + rtl88ee_init_led(hw, &(pcipriv->ledctl.sw_led1), LED_PIN_LED1); +} + +static void rtl88ee_sw_led_control(struct ieee80211_hw *hw, + enum led_ctl_mode ledaction) +{ + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_led *pLed0 = &(pcipriv->ledctl.sw_led0); + + switch (ledaction) { + case LED_CTL_POWER_ON: + case LED_CTL_LINK: + case LED_CTL_NO_LINK: + rtl88ee_sw_led_on(hw, pLed0); + break; + case LED_CTL_POWER_OFF: + rtl88ee_sw_led_off(hw, pLed0); + break; + default: + break; + } +} + +void rtl88ee_led_control(struct ieee80211_hw *hw, + enum led_ctl_mode ledaction) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + if ((ppsc->rfoff_reason > RF_CHANGE_BY_PS) && + (ledaction == LED_CTL_TX || + ledaction == LED_CTL_RX || + ledaction == LED_CTL_SITE_SURVEY || + ledaction == LED_CTL_LINK || + ledaction == LED_CTL_NO_LINK || + ledaction == LED_CTL_START_TO_LINK || + ledaction == LED_CTL_POWER_ON)) { + return; + } + RT_TRACE(rtlpriv, COMP_LED, DBG_TRACE, "ledaction %d,\n", + ledaction); + rtl88ee_sw_led_control(hw, ledaction); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/led.h b/drivers/net/wireless/rtlwifi/rtl8188ee/led.h new file mode 100644 index 0000000..4073f6f --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/led.h @@ -0,0 +1,38 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CE_LED_H__ +#define __RTL92CE_LED_H__ + +void rtl88ee_init_sw_leds(struct ieee80211_hw *hw); +void rtl88ee_sw_led_on(struct ieee80211_hw *hw, struct rtl_led *pled); +void rtl88ee_sw_led_off(struct ieee80211_hw *hw, struct rtl_led *pled); +void rtl88ee_led_control(struct ieee80211_hw *hw, enum led_ctl_mode ledaction); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/phy.c b/drivers/net/wireless/rtlwifi/rtl8188ee/phy.c new file mode 100644 index 0000000..e655c04 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/phy.c @@ -0,0 +1,2202 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "../ps.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "rf.h" +#include "dm.h" +#include "table.h" + +static void set_baseband_phy_config(struct ieee80211_hw *hw); +static void set_baseband_agc_config(struct ieee80211_hw *hw); +static void store_pwrindex_offset(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask, + u32 data); +static bool check_cond(struct ieee80211_hw *hw, const u32 condition); + +static u32 rf_serial_read(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct bb_reg_def *phreg = &rtlphy->phyreg_def[rfpath]; + u32 newoffset; + u32 tmplong, tmplong2; + u8 rfpi_enable = 0; + u32 ret; + int jj = RF90_PATH_A; + int kk = RF90_PATH_B; + + offset &= 0xff; + newoffset = offset; + if (RT_CANNOT_IO(hw)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "return all one\n"); + return 0xFFFFFFFF; + } + tmplong = rtl_get_bbreg(hw, RFPGA0_XA_HSSIPARAMETER2, MASKDWORD); + if (rfpath == jj) + tmplong2 = tmplong; + else + tmplong2 = rtl_get_bbreg(hw, phreg->rfhssi_para2, MASKDWORD); + tmplong2 = (tmplong2 & (~BLSSIREADADDRESS)) | + (newoffset << 23) | BLSSIREADEDGE; + rtl_set_bbreg(hw, RFPGA0_XA_HSSIPARAMETER2, MASKDWORD, + tmplong & (~BLSSIREADEDGE)); + mdelay(1); + rtl_set_bbreg(hw, phreg->rfhssi_para2, MASKDWORD, tmplong2); + mdelay(2); + if (rfpath == jj) + rfpi_enable = (u8) rtl_get_bbreg(hw, RFPGA0_XA_HSSIPARAMETER1, + BIT(8)); + else if (rfpath == kk) + rfpi_enable = (u8) rtl_get_bbreg(hw, RFPGA0_XB_HSSIPARAMETER1, + BIT(8)); + if (rfpi_enable) + ret = rtl_get_bbreg(hw, phreg->rf_rbpi, BLSSIREADBACKDATA); + else + ret = rtl_get_bbreg(hw, phreg->rf_rb, BLSSIREADBACKDATA); + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, "RFR-%d Addr[0x%x]= 0x%x\n", + rfpath, phreg->rf_rb, ret); + return ret; +} + +static void rf_serial_write(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset, + u32 data) +{ + u32 data_and_addr; + u32 newoffset; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct bb_reg_def *phreg = &rtlphy->phyreg_def[rfpath]; + + if (RT_CANNOT_IO(hw)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "stop\n"); + return; + } + offset &= 0xff; + newoffset = offset; + data_and_addr = ((newoffset << 20) | (data & 0x000fffff)) & 0x0fffffff; + rtl_set_bbreg(hw, phreg->rf3wire_offset, MASKDWORD, data_and_addr); + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, "RFW-%d Addr[0x%x]= 0x%x\n", + rfpath, phreg->rf3wire_offset, data_and_addr); +} + +static u32 cal_bit_shift(u32 bitmask) +{ + u32 i; + + for (i = 0; i <= 31; i++) { + if (((bitmask >> i) & 0x1) == 1) + break; + } + return i; +} + +static bool config_bb_with_header(struct ieee80211_hw *hw, + u8 configtype) +{ + if (configtype == BASEBAND_CONFIG_PHY_REG) + set_baseband_phy_config(hw); + else if (configtype == BASEBAND_CONFIG_AGC_TAB) + set_baseband_agc_config(hw); + return true; +} + +static bool config_bb_with_pgheader(struct ieee80211_hw *hw, + u8 configtype) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int i; + u32 *table_pg; + u16 tbl_page_len; + u32 v1 = 0, v2 = 0; + + tbl_page_len = RTL8188EEPHY_REG_ARRAY_PGLEN; + table_pg = RTL8188EEPHY_REG_ARRAY_PG; + + if (configtype == BASEBAND_CONFIG_PHY_REG) { + for (i = 0; i < tbl_page_len; i = i + 3) { + v1 = table_pg[i]; + v2 = table_pg[i + 1]; + + if (v1 < 0xcdcdcdcd) { + if (table_pg[i] == 0xfe) + mdelay(50); + else if (table_pg[i] == 0xfd) + mdelay(5); + else if (table_pg[i] == 0xfc) + mdelay(1); + else if (table_pg[i] == 0xfb) + udelay(50); + else if (table_pg[i] == 0xfa) + udelay(5); + else if (table_pg[i] == 0xf9) + udelay(1); + + store_pwrindex_offset(hw, table_pg[i], + table_pg[i + 1], + table_pg[i + 2]); + continue; + } else { + if (!check_cond(hw, table_pg[i])) { + /*don't need the hw_body*/ + i += 2; /* skip the pair of expression*/ + v1 = table_pg[i]; + v2 = table_pg[i + 1]; + while (v2 != 0xDEAD) { + i += 3; + v1 = table_pg[i]; + v2 = table_pg[i + 1]; + } + } + } + } + } else { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "configtype != BaseBand_Config_PHY_REG\n"); + } + return true; +} + +static bool config_parafile(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *fuse = rtl_efuse(rtl_priv(hw)); + bool rtstatus; + + rtstatus = config_bb_with_header(hw, BASEBAND_CONFIG_PHY_REG); + if (rtstatus != true) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Write BB Reg Fail!!"); + return false; + } + + if (fuse->autoload_failflag == false) { + rtlphy->pwrgroup_cnt = 0; + rtstatus = config_bb_with_pgheader(hw, BASEBAND_CONFIG_PHY_REG); + } + if (rtstatus != true) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "BB_PG Reg Fail!!"); + return false; + } + rtstatus = config_bb_with_header(hw, BASEBAND_CONFIG_AGC_TAB); + if (rtstatus != true) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "AGC Table Fail\n"); + return false; + } + rtlphy->cck_high_power = (bool) (rtl_get_bbreg(hw, + RFPGA0_XA_HSSIPARAMETER2, 0x200)); + + return true; +} + +static void rtl88e_phy_init_bb_rf_register_definition(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + int jj = RF90_PATH_A; + int kk = RF90_PATH_B; + + rtlphy->phyreg_def[jj].rfintfs = RFPGA0_XAB_RFINTERFACESW; + rtlphy->phyreg_def[kk].rfintfs = RFPGA0_XAB_RFINTERFACESW; + rtlphy->phyreg_def[RF90_PATH_C].rfintfs = RFPGA0_XCD_RFINTERFACESW; + rtlphy->phyreg_def[RF90_PATH_D].rfintfs = RFPGA0_XCD_RFINTERFACESW; + + rtlphy->phyreg_def[jj].rfintfi = RFPGA0_XAB_RFINTERFACERB; + rtlphy->phyreg_def[kk].rfintfi = RFPGA0_XAB_RFINTERFACERB; + rtlphy->phyreg_def[RF90_PATH_C].rfintfi = RFPGA0_XCD_RFINTERFACERB; + rtlphy->phyreg_def[RF90_PATH_D].rfintfi = RFPGA0_XCD_RFINTERFACERB; + + rtlphy->phyreg_def[jj].rfintfo = RFPGA0_XA_RFINTERFACEOE; + rtlphy->phyreg_def[kk].rfintfo = RFPGA0_XB_RFINTERFACEOE; + + rtlphy->phyreg_def[jj].rfintfe = RFPGA0_XA_RFINTERFACEOE; + rtlphy->phyreg_def[kk].rfintfe = RFPGA0_XB_RFINTERFACEOE; + + rtlphy->phyreg_def[jj].rf3wire_offset = RFPGA0_XA_LSSIPARAMETER; + rtlphy->phyreg_def[kk].rf3wire_offset = RFPGA0_XB_LSSIPARAMETER; + + rtlphy->phyreg_def[jj].rflssi_select = rFPGA0_XAB_RFPARAMETER; + rtlphy->phyreg_def[kk].rflssi_select = rFPGA0_XAB_RFPARAMETER; + rtlphy->phyreg_def[RF90_PATH_C].rflssi_select = rFPGA0_XCD_RFPARAMETER; + rtlphy->phyreg_def[RF90_PATH_D].rflssi_select = rFPGA0_XCD_RFPARAMETER; + + rtlphy->phyreg_def[jj].rftxgain_stage = RFPGA0_TXGAINSTAGE; + rtlphy->phyreg_def[kk].rftxgain_stage = RFPGA0_TXGAINSTAGE; + rtlphy->phyreg_def[RF90_PATH_C].rftxgain_stage = RFPGA0_TXGAINSTAGE; + rtlphy->phyreg_def[RF90_PATH_D].rftxgain_stage = RFPGA0_TXGAINSTAGE; + + rtlphy->phyreg_def[jj].rfhssi_para1 = RFPGA0_XA_HSSIPARAMETER1; + rtlphy->phyreg_def[kk].rfhssi_para1 = RFPGA0_XB_HSSIPARAMETER1; + + rtlphy->phyreg_def[jj].rfhssi_para2 = RFPGA0_XA_HSSIPARAMETER2; + rtlphy->phyreg_def[kk].rfhssi_para2 = RFPGA0_XB_HSSIPARAMETER2; + + rtlphy->phyreg_def[jj].rfsw_ctrl = RFPGA0_XAB_SWITCHCONTROL; + rtlphy->phyreg_def[kk].rfsw_ctrl = RFPGA0_XAB_SWITCHCONTROL; + rtlphy->phyreg_def[RF90_PATH_C].rfsw_ctrl = RFPGA0_XCD_SWITCHCONTROL; + rtlphy->phyreg_def[RF90_PATH_D].rfsw_ctrl = RFPGA0_XCD_SWITCHCONTROL; + + rtlphy->phyreg_def[jj].rfagc_control1 = ROFDM0_XAAGCCORE1; + rtlphy->phyreg_def[kk].rfagc_control1 = ROFDM0_XBAGCCORE1; + rtlphy->phyreg_def[RF90_PATH_C].rfagc_control1 = ROFDM0_XCAGCCORE1; + rtlphy->phyreg_def[RF90_PATH_D].rfagc_control1 = ROFDM0_XDAGCCORE1; + + rtlphy->phyreg_def[jj].rfagc_control2 = ROFDM0_XAAGCCORE2; + rtlphy->phyreg_def[kk].rfagc_control2 = ROFDM0_XBAGCCORE2; + rtlphy->phyreg_def[RF90_PATH_C].rfagc_control2 = ROFDM0_XCAGCCORE2; + rtlphy->phyreg_def[RF90_PATH_D].rfagc_control2 = ROFDM0_XDAGCCORE2; + + rtlphy->phyreg_def[jj].rfrxiq_imbal = ROFDM0_XARXIQIMBAL; + rtlphy->phyreg_def[kk].rfrxiq_imbal = ROFDM0_XBRXIQIMBAL; + rtlphy->phyreg_def[RF90_PATH_C].rfrxiq_imbal = ROFDM0_XCRXIQIMBAL; + rtlphy->phyreg_def[RF90_PATH_D].rfrxiq_imbal = ROFDM0_XDRXIQIMBAL; + + rtlphy->phyreg_def[jj].rfrx_afe = ROFDM0_XARXAFE; + rtlphy->phyreg_def[kk].rfrx_afe = ROFDM0_XBRXAFE; + rtlphy->phyreg_def[RF90_PATH_C].rfrx_afe = ROFDM0_XCRXAFE; + rtlphy->phyreg_def[RF90_PATH_D].rfrx_afe = ROFDM0_XDRXAFE; + + rtlphy->phyreg_def[jj].rftxiq_imbal = ROFDM0_XATXIQIMBAL; + rtlphy->phyreg_def[kk].rftxiq_imbal = ROFDM0_XBTXIQIMBAL; + rtlphy->phyreg_def[RF90_PATH_C].rftxiq_imbal = ROFDM0_XCTXIQIMBAL; + rtlphy->phyreg_def[RF90_PATH_D].rftxiq_imbal = ROFDM0_XDTXIQIMBAL; + + rtlphy->phyreg_def[jj].rftx_afe = ROFDM0_XATXAFE; + rtlphy->phyreg_def[kk].rftx_afe = ROFDM0_XBTXAFE; + + rtlphy->phyreg_def[jj].rf_rb = RFPGA0_XA_LSSIREADBACK; + rtlphy->phyreg_def[kk].rf_rb = RFPGA0_XB_LSSIREADBACK; + + rtlphy->phyreg_def[jj].rf_rbpi = TRANSCEIVEA_HSPI_READBACK; + rtlphy->phyreg_def[kk].rf_rbpi = TRANSCEIVEB_HSPI_READBACK; +} + +static bool rtl88e_phy_set_sw_chnl_cmdarray(struct swchnlcmd *cmdtable, + u32 cmdtableidx, u32 cmdtablesz, + enum swchnlcmd_id cmdid, + u32 para1, u32 para2, u32 msdelay) +{ + struct swchnlcmd *pcmd; + + if (cmdtable == NULL) { + RT_ASSERT(false, "cmdtable cannot be NULL.\n"); + return false; + } + + if (cmdtableidx >= cmdtablesz) + return false; + + pcmd = cmdtable + cmdtableidx; + pcmd->cmdid = cmdid; + pcmd->para1 = para1; + pcmd->para2 = para2; + pcmd->msdelay = msdelay; + return true; +} + +static bool chnl_step_by_step(struct ieee80211_hw *hw, + u8 channel, u8 *stage, u8 *step, + u32 *delay) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct swchnlcmd precommoncmd[MAX_PRECMD_CNT]; + u32 precommoncmdcnt; + struct swchnlcmd postcommoncmd[MAX_POSTCMD_CNT]; + u32 postcommoncmdcnt; + struct swchnlcmd rfdependcmd[MAX_RFDEPENDCMD_CNT]; + u32 rfdependcmdcnt; + struct swchnlcmd *currentcmd = NULL; + u8 rfpath; + u8 num_total_rfpath = rtlphy->num_total_rfpath; + + precommoncmdcnt = 0; + rtl88e_phy_set_sw_chnl_cmdarray(precommoncmd, precommoncmdcnt++, + MAX_PRECMD_CNT, + CMDID_SET_TXPOWEROWER_LEVEL, 0, 0, 0); + rtl88e_phy_set_sw_chnl_cmdarray(precommoncmd, precommoncmdcnt++, + MAX_PRECMD_CNT, CMDID_END, 0, 0, 0); + + postcommoncmdcnt = 0; + + rtl88e_phy_set_sw_chnl_cmdarray(postcommoncmd, postcommoncmdcnt++, + MAX_POSTCMD_CNT, CMDID_END, 0, 0, 0); + + rfdependcmdcnt = 0; + + RT_ASSERT((channel >= 1 && channel <= 14), + "illegal channel for Zebra: %d\n", channel); + + rtl88e_phy_set_sw_chnl_cmdarray(rfdependcmd, rfdependcmdcnt++, + MAX_RFDEPENDCMD_CNT, CMDID_RF_WRITEREG, + RF_CHNLBW, channel, 10); + + rtl88e_phy_set_sw_chnl_cmdarray(rfdependcmd, rfdependcmdcnt++, + MAX_RFDEPENDCMD_CNT, CMDID_END, 0, 0, + 0); + + do { + switch (*stage) { + case 0: + currentcmd = &precommoncmd[*step]; + break; + case 1: + currentcmd = &rfdependcmd[*step]; + break; + case 2: + currentcmd = &postcommoncmd[*step]; + break; + } + + if (currentcmd->cmdid == CMDID_END) { + if ((*stage) == 2) { + return true; + } else { + (*stage)++; + (*step) = 0; + continue; + } + } + + switch (currentcmd->cmdid) { + case CMDID_SET_TXPOWEROWER_LEVEL: + rtl88e_phy_set_txpower_level(hw, channel); + break; + case CMDID_WRITEPORT_ULONG: + rtl_write_dword(rtlpriv, currentcmd->para1, + currentcmd->para2); + break; + case CMDID_WRITEPORT_USHORT: + rtl_write_word(rtlpriv, currentcmd->para1, + (u16) currentcmd->para2); + break; + case CMDID_WRITEPORT_UCHAR: + rtl_write_byte(rtlpriv, currentcmd->para1, + (u8) currentcmd->para2); + break; + case CMDID_RF_WRITEREG: + for (rfpath = 0; rfpath < num_total_rfpath; rfpath++) { + rtlphy->rfreg_chnlval[rfpath] = + ((rtlphy->rfreg_chnlval[rfpath] & + 0xfffffc00) | currentcmd->para2); + + rtl_set_rfreg(hw, (enum radio_path)rfpath, + currentcmd->para1, + RFREG_OFFSET_MASK, + rtlphy->rfreg_chnlval[rfpath]); + } + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + + break; + } while (true); + + (*delay) = currentcmd->msdelay; + (*step)++; + return false; +} + +static long rtl88e_pwr_idx_dbm(struct ieee80211_hw *hw, + enum wireless_mode wirelessmode, + u8 txpwridx) +{ + long offset; + long pwrout_dbm; + + switch (wirelessmode) { + case WIRELESS_MODE_B: + offset = -7; + break; + case WIRELESS_MODE_G: + case WIRELESS_MODE_N_24G: + offset = -8; + break; + default: + offset = -8; + break; + } + pwrout_dbm = txpwridx / 2 + offset; + return pwrout_dbm; +} + +static void rtl88e_phy_set_io(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "--->Cmd(%#x), set_io_inprogress(%d)\n", + rtlphy->current_io_type, rtlphy->set_io_inprogress); + switch (rtlphy->current_io_type) { + case IO_CMD_RESUME_DM_BY_SCAN: + dm_digtable->cur_igvalue = rtlphy->initgain_backup.xaagccore1; + /*rtl92c_dm_write_dig(hw);*/ + rtl88e_phy_set_txpower_level(hw, rtlphy->current_channel); + rtl_set_bbreg(hw, RCCK0_CCA, 0xff0000, 0x83); + break; + case IO_CMD_PAUSE_DM_BY_SCAN: + rtlphy->initgain_backup.xaagccore1 = dm_digtable->cur_igvalue; + dm_digtable->cur_igvalue = 0x17; + rtl_set_bbreg(hw, RCCK0_CCA, 0xff0000, 0x40); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + rtlphy->set_io_inprogress = false; + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "(%#x)\n", rtlphy->current_io_type); +} + +u32 rtl88e_phy_query_bb_reg(struct ieee80211_hw *hw, u32 regaddr, u32 bitmask) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 returnvalue, originalvalue, bitshift; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x)\n", regaddr, bitmask); + originalvalue = rtl_read_dword(rtlpriv, regaddr); + bitshift = cal_bit_shift(bitmask); + returnvalue = (originalvalue & bitmask) >> bitshift; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "BBR MASK = 0x%x Addr[0x%x]= 0x%x\n", bitmask, + regaddr, originalvalue); + + return returnvalue; +} + +void rtl88e_phy_set_bb_reg(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 originalvalue, bitshift; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x),data(%#x)\n", + regaddr, bitmask, data); + + if (bitmask != MASKDWORD) { + originalvalue = rtl_read_dword(rtlpriv, regaddr); + bitshift = cal_bit_shift(bitmask); + data = ((originalvalue & (~bitmask)) | (data << bitshift)); + } + + rtl_write_dword(rtlpriv, regaddr, data); + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x)\n", + regaddr, bitmask, data); +} + +u32 rtl88e_phy_query_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, u32 bitmask) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 original_value, readback_value, bitshift; + unsigned long flags; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), rfpath(%#x), bitmask(%#x)\n", + regaddr, rfpath, bitmask); + + spin_lock_irqsave(&rtlpriv->locks.rf_lock, flags); + + + original_value = rf_serial_read(hw, rfpath, regaddr); + bitshift = cal_bit_shift(bitmask); + readback_value = (original_value & bitmask) >> bitshift; + + spin_unlock_irqrestore(&rtlpriv->locks.rf_lock, flags); + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), rfpath(%#x), bitmask(%#x), original_value(%#x)\n", + regaddr, rfpath, bitmask, original_value); + + return readback_value; +} + +void rtl88e_phy_set_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, + u32 regaddr, u32 bitmask, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 original_value, bitshift; + unsigned long flags; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x), rfpath(%#x)\n", + regaddr, bitmask, data, rfpath); + + spin_lock_irqsave(&rtlpriv->locks.rf_lock, flags); + + if (bitmask != RFREG_OFFSET_MASK) { + original_value = rf_serial_read(hw, rfpath, regaddr); + bitshift = cal_bit_shift(bitmask); + data = ((original_value & (~bitmask)) | + (data << bitshift)); + } + + rf_serial_write(hw, rfpath, regaddr, data); + + + spin_unlock_irqrestore(&rtlpriv->locks.rf_lock, flags); + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x), rfpath(%#x)\n", + regaddr, bitmask, data, rfpath); +} + +static bool config_mac_with_header(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + u32 arraylength; + u32 *ptrarray; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "Read Rtl8188EMACPHY_Array\n"); + arraylength = RTL8188EEMAC_1T_ARRAYLEN; + ptrarray = RTL8188EEMAC_1T_ARRAY; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Img:RTL8188EEMAC_1T_ARRAY LEN %d\n", arraylength); + for (i = 0; i < arraylength; i = i + 2) + rtl_write_byte(rtlpriv, ptrarray[i], (u8) ptrarray[i + 1]); + return true; +} + +bool rtl88e_phy_mac_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + bool rtstatus = config_mac_with_header(hw); + + rtl_write_byte(rtlpriv, 0x04CA, 0x0B); + return rtstatus; +} + +bool rtl88e_phy_bb_config(struct ieee80211_hw *hw) +{ + bool rtstatus = true; + struct rtl_priv *rtlpriv = rtl_priv(hw); + u16 regval; + u8 reg_hwparafile = 1; + u32 tmp; + rtl88e_phy_init_bb_rf_register_definition(hw); + regval = rtl_read_word(rtlpriv, REG_SYS_FUNC_EN); + rtl_write_word(rtlpriv, REG_SYS_FUNC_EN, + regval | BIT(13) | BIT(0) | BIT(1)); + + rtl_write_byte(rtlpriv, REG_RF_CTRL, RF_EN | RF_RSTB | RF_SDMRSTB); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, + FEN_PPLL | FEN_PCIEA | FEN_DIO_PCIE | + FEN_BB_GLB_RSTN | FEN_BBRSTB); + tmp = rtl_read_dword(rtlpriv, 0x4c); + rtl_write_dword(rtlpriv, 0x4c, tmp | BIT(23)); + if (reg_hwparafile == 1) + rtstatus = config_parafile(hw); + return rtstatus; +} + +bool rtl88e_phy_rf_config(struct ieee80211_hw *hw) +{ + return rtl88e_phy_rf6052_config(hw); +} + +static bool check_cond(struct ieee80211_hw *hw, + const u32 condition) +{ + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_efuse *fuse = rtl_efuse(rtl_priv(hw)); + u32 _board = fuse->board_type; /*need efuse define*/ + u32 _interface = rtlhal->interface; + u32 _platform = 0x08;/*SupportPlatform */ + u32 cond = condition; + + if (condition == 0xCDCDCDCD) + return true; + + cond = condition & 0xFF; + if ((_board & cond) == 0 && cond != 0x1F) + return false; + + cond = condition & 0xFF00; + cond = cond >> 8; + if ((_interface & cond) == 0 && cond != 0x07) + return false; + + cond = condition & 0xFF0000; + cond = cond >> 16; + if ((_platform & cond) == 0 && cond != 0x0F) + return false; + return true; +} + +static void _rtl8188e_config_rf_reg(struct ieee80211_hw *hw, + u32 addr, u32 data, enum radio_path rfpath, + u32 regaddr) +{ + if (addr == 0xffe) { + mdelay(50); + } else if (addr == 0xfd) { + mdelay(5); + } else if (addr == 0xfc) { + mdelay(1); + } else if (addr == 0xfb) { + udelay(50); + } else if (addr == 0xfa) { + udelay(5); + } else if (addr == 0xf9) { + udelay(1); + } else { + rtl_set_rfreg(hw, rfpath, regaddr, + RFREG_OFFSET_MASK, + data); + udelay(1); + } +} + +static void rtl88_config_s(struct ieee80211_hw *hw, + u32 addr, u32 data) +{ + u32 content = 0x1000; /*RF Content: radio_a_txt*/ + u32 maskforphyset = (u32)(content & 0xE000); + + _rtl8188e_config_rf_reg(hw, addr, data, RF90_PATH_A, + addr | maskforphyset); +} + +static void _rtl8188e_config_bb_reg(struct ieee80211_hw *hw, + u32 addr, u32 data) +{ + if (addr == 0xfe) { + mdelay(50); + } else if (addr == 0xfd) { + mdelay(5); + } else if (addr == 0xfc) { + mdelay(1); + } else if (addr == 0xfb) { + udelay(50); + } else if (addr == 0xfa) { + udelay(5); + } else if (addr == 0xf9) { + udelay(1); + } else { + rtl_set_bbreg(hw, addr, MASKDWORD, data); + udelay(1); + } +} + + +#define NEXT_PAIR(v1, v2, i) \ + do { \ + i += 2; v1 = array_table[i]; \ + v2 = array_table[i + 1]; \ + } while (0) + +static void set_baseband_agc_config(struct ieee80211_hw *hw) +{ + int i; + u32 *array_table; + u16 arraylen; + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 v1 = 0, v2 = 0; + + arraylen = RTL8188EEAGCTAB_1TARRAYLEN; + array_table = RTL8188EEAGCTAB_1TARRAY; + + for (i = 0; i < arraylen; i += 2) { + v1 = array_table[i]; + v2 = array_table[i + 1]; + if (v1 < 0xCDCDCDCD) { + rtl_set_bbreg(hw, array_table[i], MASKDWORD, + array_table[i + 1]); + udelay(1); + continue; + } else {/*This line is the start line of branch.*/ + if (!check_cond(hw, array_table[i])) { + /*Discard the following (offset, data) pairs*/ + NEXT_PAIR(v1, v2, i); + while (v2 != 0xDEAD && v2 != 0xCDEF && + v2 != 0xCDCD && i < arraylen - 2) { + NEXT_PAIR(v1, v2, i); + } + i -= 2; /* compensate for loop's += 2*/ + } else { + /* Configure matched pairs and skip to end */ + NEXT_PAIR(v1, v2, i); + while (v2 != 0xDEAD && v2 != 0xCDEF && + v2 != 0xCDCD && i < arraylen - 2) { + rtl_set_bbreg(hw, array_table[i], + MASKDWORD, + array_table[i + 1]); + udelay(1); + NEXT_PAIR(v1, v2, i); + } + + while (v2 != 0xDEAD && i < arraylen - 2) + NEXT_PAIR(v1, v2, i); + } + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "The agctab_array_table[0] is %x Rtl818EEPHY_REGArray[1] is %x\n", + array_table[i], + array_table[i + 1]); + } +} + +static void set_baseband_phy_config(struct ieee80211_hw *hw) +{ + int i; + u32 *array_table; + u16 arraylen; + u32 v1 = 0, v2 = 0; + + arraylen = RTL8188EEPHY_REG_1TARRAYLEN; + array_table = RTL8188EEPHY_REG_1TARRAY; + + for (i = 0; i < arraylen; i += 2) { + v1 = array_table[i]; + v2 = array_table[i + 1]; + if (v1 < 0xcdcdcdcd) { + _rtl8188e_config_bb_reg(hw, v1, v2); + } else {/*This line is the start line of branch.*/ + if (!check_cond(hw, array_table[i])) { + /*Discard the following (offset, data) pairs*/ + NEXT_PAIR(v1, v2, i); + while (v2 != 0xDEAD && + v2 != 0xCDEF && + v2 != 0xCDCD && i < arraylen - 2) + NEXT_PAIR(v1, v2, i); + i -= 2; /* prevent from for-loop += 2*/ + } else { + /* Configure matched pairs and skip to end */ + NEXT_PAIR(v1, v2, i); + while (v2 != 0xDEAD && + v2 != 0xCDEF && + v2 != 0xCDCD && i < arraylen - 2) { + _rtl8188e_config_bb_reg(hw, v1, v2); + NEXT_PAIR(v1, v2, i); + } + + while (v2 != 0xDEAD && i < arraylen - 2) + NEXT_PAIR(v1, v2, i); + } + } + } +} + +static void store_pwrindex_offset(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask, + u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + if (regaddr == RTXAGC_A_RATE18_06) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][0] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][0] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][0]); + } + if (regaddr == RTXAGC_A_RATE54_24) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][1] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][1] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][1]); + } + if (regaddr == RTXAGC_A_CCK1_MCS32) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][6] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][6] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][6]); + } + if (regaddr == RTXAGC_B_CCK11_A_CCK2_11 && bitmask == 0xffffff00) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][7] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][7] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][7]); + } + if (regaddr == RTXAGC_A_MCS03_MCS00) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][2] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][2] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][2]); + } + if (regaddr == RTXAGC_A_MCS07_MCS04) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][3] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][3] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][3]); + } + if (regaddr == RTXAGC_A_MCS11_MCS08) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][4] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][4] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][4]); + } + if (regaddr == RTXAGC_A_MCS15_MCS12) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][5] = data; + if (get_rf_type(rtlphy) == RF_1T1R) + rtlphy->pwrgroup_cnt++; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][5] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][5]); + } + if (regaddr == RTXAGC_B_RATE18_06) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][8] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][8] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][8]); + } + if (regaddr == RTXAGC_B_RATE54_24) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][9] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][9] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][9]); + } + if (regaddr == RTXAGC_B_CCK1_55_MCS32) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][14] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][14] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][14]); + } + if (regaddr == RTXAGC_B_CCK11_A_CCK2_11 && bitmask == 0x000000ff) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][15] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][15] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][15]); + } + if (regaddr == RTXAGC_B_MCS03_MCS00) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][10] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][10] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][10]); + } + if (regaddr == RTXAGC_B_MCS07_MCS04) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][11] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][11] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][11]); + } + if (regaddr == RTXAGC_B_MCS11_MCS08) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][12] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][12] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][12]); + } + if (regaddr == RTXAGC_B_MCS15_MCS12) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][13] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][13] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][13]); + if (get_rf_type(rtlphy) != RF_1T1R) + rtlphy->pwrgroup_cnt++; + } +} + +#define READ_NEXT_RF_PAIR(v1, v2, i) \ + do { \ + i += 2; v1 = a_table[i]; \ + v2 = a_table[i + 1]; \ + } while (0) + +bool rtl88e_phy_config_rf_with_headerfile(struct ieee80211_hw *hw, + enum radio_path rfpath) +{ + int i; + u32 *a_table; + u16 a_len; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u32 v1 = 0, v2 = 0; + + a_len = RTL8188EE_RADIOA_1TARRAYLEN; + a_table = RTL8188EE_RADIOA_1TARRAY; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Radio_A:RTL8188EE_RADIOA_1TARRAY %d\n", a_len); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Radio No %x\n", rfpath); + switch (rfpath) { + case RF90_PATH_A: + for (i = 0; i < a_len; i = i + 2) { + v1 = a_table[i]; + v2 = a_table[i + 1]; + if (v1 < 0xcdcdcdcd) { + rtl88_config_s(hw, v1, v2); + } else {/*This line is the start line of branch.*/ + if (!check_cond(hw, a_table[i])) { + /* Discard the following (offset, data) + * pairs + */ + READ_NEXT_RF_PAIR(v1, v2, i); + while (v2 != 0xDEAD && v2 != 0xCDEF && + v2 != 0xCDCD && i < a_len - 2) + READ_NEXT_RF_PAIR(v1, v2, i); + i -= 2; /* prevent from for-loop += 2*/ + } else { + /* Configure matched pairs and skip to + * end of if-else. + */ + READ_NEXT_RF_PAIR(v1, v2, i); + while (v2 != 0xDEAD && v2 != 0xCDEF && + v2 != 0xCDCD && i < a_len - 2) { + rtl88_config_s(hw, v1, v2); + READ_NEXT_RF_PAIR(v1, v2, i); + } + + while (v2 != 0xDEAD && i < a_len - 2) + READ_NEXT_RF_PAIR(v1, v2, i); + } + } + } + + if (rtlhal->oem_id == RT_CID_819x_HP) + rtl88_config_s(hw, 0x52, 0x7E4BD); + + break; + + case RF90_PATH_B: + case RF90_PATH_C: + case RF90_PATH_D: + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + return true; +} + +void rtl88e_phy_get_hw_reg_originalvalue(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + rtlphy->default_initialgain[0] = rtl_get_bbreg(hw, ROFDM0_XAAGCCORE1, + MASKBYTE0); + rtlphy->default_initialgain[1] = rtl_get_bbreg(hw, ROFDM0_XBAGCCORE1, + MASKBYTE0); + rtlphy->default_initialgain[2] = rtl_get_bbreg(hw, ROFDM0_XCAGCCORE1, + MASKBYTE0); + rtlphy->default_initialgain[3] = rtl_get_bbreg(hw, ROFDM0_XDAGCCORE1, + MASKBYTE0); + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Default initial gain (c50 = 0x%x, c58 = 0x%x, c60 = 0x%x, c68 = 0x%x\n", + rtlphy->default_initialgain[0], + rtlphy->default_initialgain[1], + rtlphy->default_initialgain[2], + rtlphy->default_initialgain[3]); + + rtlphy->framesync = rtl_get_bbreg(hw, ROFDM0_RXDETECTOR3, + MASKBYTE0); + rtlphy->framesync_c34 = rtl_get_bbreg(hw, ROFDM0_RXDETECTOR2, + MASKDWORD); + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Default framesync (0x%x) = 0x%x\n", + ROFDM0_RXDETECTOR3, rtlphy->framesync); +} + +void rtl88e_phy_get_txpower_level(struct ieee80211_hw *hw, long *powerlevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u8 level; + long dbm; + + level = rtlphy->cur_cck_txpwridx; + dbm = rtl88e_pwr_idx_dbm(hw, WIRELESS_MODE_B, level); + level = rtlphy->cur_ofdm24g_txpwridx; + if (rtl88e_pwr_idx_dbm(hw, WIRELESS_MODE_G, level) > dbm) + dbm = rtl88e_pwr_idx_dbm(hw, WIRELESS_MODE_G, level); + level = rtlphy->cur_ofdm24g_txpwridx; + if (rtl88e_pwr_idx_dbm(hw, WIRELESS_MODE_N_24G, level) > dbm) + dbm = rtl88e_pwr_idx_dbm(hw, WIRELESS_MODE_N_24G, level); + *powerlevel = dbm; +} + +static void _rtl88e_get_txpower_index(struct ieee80211_hw *hw, u8 channel, + u8 *cckpower, u8 *ofdm, u8 *bw20_pwr, + u8 *bw40_pwr) +{ + struct rtl_efuse *fuse = rtl_efuse(rtl_priv(hw)); + u8 i = (channel - 1); + u8 rf_path = 0; + int jj = RF90_PATH_A; + int kk = RF90_PATH_B; + + for (rf_path = 0; rf_path < 2; rf_path++) { + if (rf_path == jj) { + cckpower[jj] = fuse->txpwrlevel_cck[jj][i]; + if (fuse->txpwr_ht20diff[jj][i] > 0x0f) /*-8~7 */ + bw20_pwr[jj] = fuse->txpwrlevel_ht40_1s[jj][i] - + (~(fuse->txpwr_ht20diff[jj][i]) + 1); + else + bw20_pwr[jj] = fuse->txpwrlevel_ht40_1s[jj][i] + + fuse->txpwr_ht20diff[jj][i]; + if (fuse->txpwr_legacyhtdiff[jj][i] > 0xf) + ofdm[jj] = fuse->txpwrlevel_ht40_1s[jj][i] - + (~(fuse->txpwr_legacyhtdiff[jj][i])+1); + else + ofdm[jj] = fuse->txpwrlevel_ht40_1s[jj][i] + + fuse->txpwr_legacyhtdiff[jj][i]; + bw40_pwr[jj] = fuse->txpwrlevel_ht40_1s[jj][i]; + + } else if (rf_path == kk) { + cckpower[kk] = fuse->txpwrlevel_cck[kk][i]; + bw20_pwr[kk] = fuse->txpwrlevel_ht40_1s[kk][i] + + fuse->txpwr_ht20diff[kk][i]; + ofdm[kk] = fuse->txpwrlevel_ht40_1s[kk][i] + + fuse->txpwr_legacyhtdiff[kk][i]; + bw40_pwr[kk] = fuse->txpwrlevel_ht40_1s[kk][i]; + } + } +} + +static void _rtl88e_ccxpower_index_check(struct ieee80211_hw *hw, + u8 channel, u8 *cckpower, + u8 *ofdm, u8 *bw20_pwr, + u8 *bw40_pwr) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + rtlphy->cur_cck_txpwridx = cckpower[0]; + rtlphy->cur_ofdm24g_txpwridx = ofdm[0]; + rtlphy->cur_bw20_txpwridx = bw20_pwr[0]; + rtlphy->cur_bw40_txpwridx = bw40_pwr[0]; +} + +void rtl88e_phy_set_txpower_level(struct ieee80211_hw *hw, u8 channel) +{ + struct rtl_efuse *fuse = rtl_efuse(rtl_priv(hw)); + u8 cckpower[MAX_TX_COUNT] = {0}, ofdm[MAX_TX_COUNT] = {0}; + u8 bw20_pwr[MAX_TX_COUNT] = {0}, bw40_pwr[MAX_TX_COUNT] = {0}; + + if (fuse->txpwr_fromeprom == false) + return; + _rtl88e_get_txpower_index(hw, channel, &cckpower[0], &ofdm[0], + &bw20_pwr[0], &bw40_pwr[0]); + _rtl88e_ccxpower_index_check(hw, channel, &cckpower[0], &ofdm[0], + &bw20_pwr[0], &bw40_pwr[0]); + rtl88e_phy_rf6052_set_cck_txpower(hw, &cckpower[0]); + rtl88e_phy_rf6052_set_ofdm_txpower(hw, &ofdm[0], &bw20_pwr[0], + &bw40_pwr[0], channel); +} + +void rtl88e_phy_scan_operation_backup(struct ieee80211_hw *hw, u8 operation) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + enum io_type iotype; + + if (!is_hal_stop(rtlhal)) { + switch (operation) { + case SCAN_OPT_BACKUP: + iotype = IO_CMD_PAUSE_DM_BY_SCAN; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_IO_CMD, + (u8 *)&iotype); + break; + case SCAN_OPT_RESTORE: + iotype = IO_CMD_RESUME_DM_BY_SCAN; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_IO_CMD, + (u8 *)&iotype); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Unknown Scan Backup operation.\n"); + break; + } + } +} + +void rtl88e_phy_set_bw_mode_callback(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u8 reg_bw_opmode; + u8 reg_prsr_rsc; + + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, + "Switch to %s bandwidth\n", + rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20 ? + "20MHz" : "40MHz"); + + if (is_hal_stop(rtlhal)) { + rtlphy->set_bwmode_inprogress = false; + return; + } + + reg_bw_opmode = rtl_read_byte(rtlpriv, REG_BWOPMODE); + reg_prsr_rsc = rtl_read_byte(rtlpriv, REG_RRSR + 2); + + switch (rtlphy->current_chan_bw) { + case HT_CHANNEL_WIDTH_20: + reg_bw_opmode |= BW_OPMODE_20MHZ; + rtl_write_byte(rtlpriv, REG_BWOPMODE, reg_bw_opmode); + break; + case HT_CHANNEL_WIDTH_20_40: + reg_bw_opmode &= ~BW_OPMODE_20MHZ; + rtl_write_byte(rtlpriv, REG_BWOPMODE, reg_bw_opmode); + reg_prsr_rsc = + (reg_prsr_rsc & 0x90) | (mac->cur_40_prime_sc << 5); + rtl_write_byte(rtlpriv, REG_RRSR + 2, reg_prsr_rsc); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", rtlphy->current_chan_bw); + break; + } + + switch (rtlphy->current_chan_bw) { + case HT_CHANNEL_WIDTH_20: + rtl_set_bbreg(hw, RFPGA0_RFMOD, BRFMOD, 0x0); + rtl_set_bbreg(hw, RFPGA1_RFMOD, BRFMOD, 0x0); + /* rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER2, BIT(10), 1);*/ + break; + case HT_CHANNEL_WIDTH_20_40: + rtl_set_bbreg(hw, RFPGA0_RFMOD, BRFMOD, 0x1); + rtl_set_bbreg(hw, RFPGA1_RFMOD, BRFMOD, 0x1); + + rtl_set_bbreg(hw, RCCK0_SYSTEM, BCCK_SIDEBAND, + (mac->cur_40_prime_sc >> 1)); + rtl_set_bbreg(hw, ROFDM1_LSTF, 0xC00, mac->cur_40_prime_sc); + /*rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER2, BIT(10), 0);*/ + + rtl_set_bbreg(hw, 0x818, (BIT(26) | BIT(27)), + (mac->cur_40_prime_sc == + HAL_PRIME_CHNL_OFFSET_LOWER) ? 2 : 1); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", rtlphy->current_chan_bw); + break; + } + rtl88e_phy_rf6052_set_bandwidth(hw, rtlphy->current_chan_bw); + rtlphy->set_bwmode_inprogress = false; + RT_TRACE(rtlpriv, COMP_SCAN, DBG_LOUD, "\n"); +} + +void rtl88e_phy_set_bw_mode(struct ieee80211_hw *hw, + enum nl80211_channel_type ch_type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 tmp_bw = rtlphy->current_chan_bw; + + if (rtlphy->set_bwmode_inprogress) + return; + rtlphy->set_bwmode_inprogress = true; + if ((!is_hal_stop(rtlhal)) && !(RT_CANNOT_IO(hw))) { + rtl88e_phy_set_bw_mode_callback(hw); + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "FALSE driver sleep or unload\n"); + rtlphy->set_bwmode_inprogress = false; + rtlphy->current_chan_bw = tmp_bw; + } +} + +void rtl88e_phy_sw_chnl_callback(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 delay; + + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, + "switch to channel%d\n", rtlphy->current_channel); + if (is_hal_stop(rtlhal)) + return; + do { + if (!rtlphy->sw_chnl_inprogress) + break; + if (!chnl_step_by_step(hw, rtlphy->current_channel, + &rtlphy->sw_chnl_stage, + &rtlphy->sw_chnl_step, &delay)) { + if (delay > 0) + mdelay(delay); + else + continue; + } else { + rtlphy->sw_chnl_inprogress = false; + } + break; + } while (true); + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, "\n"); +} + +u8 rtl88e_phy_sw_chnl(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (rtlphy->sw_chnl_inprogress) + return 0; + if (rtlphy->set_bwmode_inprogress) + return 0; + RT_ASSERT((rtlphy->current_channel <= 14), + "WIRELESS_MODE_G but channel>14"); + rtlphy->sw_chnl_inprogress = true; + rtlphy->sw_chnl_stage = 0; + rtlphy->sw_chnl_step = 0; + if (!(is_hal_stop(rtlhal)) && !(RT_CANNOT_IO(hw))) { + rtl88e_phy_sw_chnl_callback(hw); + RT_TRACE(rtlpriv, COMP_CHAN, DBG_LOUD, + "sw_chnl_inprogress false schdule workitem current channel %d\n", + rtlphy->current_channel); + rtlphy->sw_chnl_inprogress = false; + } else { + RT_TRACE(rtlpriv, COMP_CHAN, DBG_LOUD, + "sw_chnl_inprogress false driver sleep or unload\n"); + rtlphy->sw_chnl_inprogress = false; + } + return 1; +} + +static u8 _rtl88e_phy_path_a_iqk(struct ieee80211_hw *hw, bool config_pathb) +{ + u32 reg_eac, reg_e94, reg_e9c; + u8 result = 0x00; + + rtl_set_bbreg(hw, 0xe30, MASKDWORD, 0x10008c1c); + rtl_set_bbreg(hw, 0xe34, MASKDWORD, 0x30008c1c); + rtl_set_bbreg(hw, 0xe38, MASKDWORD, 0x8214032a); + rtl_set_bbreg(hw, 0xe3c, MASKDWORD, 0x28160000); + + rtl_set_bbreg(hw, 0xe4c, MASKDWORD, 0x00462911); + rtl_set_bbreg(hw, 0xe48, MASKDWORD, 0xf9000000); + rtl_set_bbreg(hw, 0xe48, MASKDWORD, 0xf8000000); + + mdelay(IQK_DELAY_TIME); + + reg_eac = rtl_get_bbreg(hw, 0xeac, MASKDWORD); + reg_e94 = rtl_get_bbreg(hw, 0xe94, MASKDWORD); + reg_e9c = rtl_get_bbreg(hw, 0xe9c, MASKDWORD); + + if (!(reg_eac & BIT(28)) && + (((reg_e94 & 0x03FF0000) >> 16) != 0x142) && + (((reg_e9c & 0x03FF0000) >> 16) != 0x42)) + result |= 0x01; + return result; +} + +static u8 _rtl88e_phy_path_b_iqk(struct ieee80211_hw *hw) +{ + u32 reg_eac, reg_eb4, reg_ebc, reg_ec4, reg_ecc; + u8 result = 0x00; + + rtl_set_bbreg(hw, 0xe60, MASKDWORD, 0x00000002); + rtl_set_bbreg(hw, 0xe60, MASKDWORD, 0x00000000); + mdelay(IQK_DELAY_TIME); + reg_eac = rtl_get_bbreg(hw, 0xeac, MASKDWORD); + reg_eb4 = rtl_get_bbreg(hw, 0xeb4, MASKDWORD); + reg_ebc = rtl_get_bbreg(hw, 0xebc, MASKDWORD); + reg_ec4 = rtl_get_bbreg(hw, 0xec4, MASKDWORD); + reg_ecc = rtl_get_bbreg(hw, 0xecc, MASKDWORD); + + if (!(reg_eac & BIT(31)) && + (((reg_eb4 & 0x03FF0000) >> 16) != 0x142) && + (((reg_ebc & 0x03FF0000) >> 16) != 0x42)) + result |= 0x01; + else + return result; + if (!(reg_eac & BIT(30)) && + (((reg_ec4 & 0x03FF0000) >> 16) != 0x132) && + (((reg_ecc & 0x03FF0000) >> 16) != 0x36)) + result |= 0x02; + return result; +} + +static u8 _rtl88e_phy_path_a_rx_iqk(struct ieee80211_hw *hw, bool config_pathb) +{ + u32 reg_eac, reg_e94, reg_e9c, reg_ea4, u32temp; + u8 result = 0x00; + int jj = RF90_PATH_A; + + /*Get TXIMR Setting*/ + /*Modify RX IQK mode table*/ + rtl_set_bbreg(hw, RFPGA0_IQK, MASKDWORD, 0x00000000); + rtl_set_rfreg(hw, jj, RF_WE_LUT, RFREG_OFFSET_MASK, 0x800a0); + rtl_set_rfreg(hw, jj, RF_RCK_OS, RFREG_OFFSET_MASK, 0x30000); + rtl_set_rfreg(hw, jj, RF_TXPA_G1, RFREG_OFFSET_MASK, 0x0000f); + rtl_set_rfreg(hw, jj, RF_TXPA_G2, RFREG_OFFSET_MASK, 0xf117b); + rtl_set_bbreg(hw, RFPGA0_IQK, MASKDWORD, 0x80800000); + + /*IQK Setting*/ + rtl_set_bbreg(hw, RTX_IQK, MASKDWORD, 0x01007c00); + rtl_set_bbreg(hw, RRX_IQK, MASKDWORD, 0x81004800); + + /*path a IQK setting*/ + rtl_set_bbreg(hw, RTX_IQK_TONE_A, MASKDWORD, 0x10008c1c); + rtl_set_bbreg(hw, RRX_IQK_TONE_A, MASKDWORD, 0x30008c1c); + rtl_set_bbreg(hw, RTX_IQK_PI_A, MASKDWORD, 0x82160804); + rtl_set_bbreg(hw, RRX_IQK_PI_A, MASKDWORD, 0x28160000); + + /*LO calibration Setting*/ + rtl_set_bbreg(hw, RIQK_AGC_RSP, MASKDWORD, 0x0046a911); + /*one shot, path A LOK & iqk*/ + rtl_set_bbreg(hw, RIQK_AGC_PTS, MASKDWORD, 0xf9000000); + rtl_set_bbreg(hw, RIQK_AGC_PTS, MASKDWORD, 0xf8000000); + + mdelay(IQK_DELAY_TIME); + + reg_eac = rtl_get_bbreg(hw, RRX_POWER_AFTER_IQK_A_2, MASKDWORD); + reg_e94 = rtl_get_bbreg(hw, RTX_POWER_BEFORE_IQK_A, MASKDWORD); + reg_e9c = rtl_get_bbreg(hw, RTX_POWER_AFTER_IQK_A, MASKDWORD); + + + if (!(reg_eac & BIT(28)) && + (((reg_e94 & 0x03FF0000) >> 16) != 0x142) && + (((reg_e9c & 0x03FF0000) >> 16) != 0x42)) + result |= 0x01; + else + return result; + + u32temp = 0x80007C00 | (reg_e94&0x3FF0000) | + ((reg_e9c&0x3FF0000) >> 16); + rtl_set_bbreg(hw, RTX_IQK, MASKDWORD, u32temp); + /*RX IQK*/ + /*Modify RX IQK mode table*/ + rtl_set_bbreg(hw, RFPGA0_IQK, MASKDWORD, 0x00000000); + rtl_set_rfreg(hw, jj, RF_WE_LUT, RFREG_OFFSET_MASK, 0x800a0); + rtl_set_rfreg(hw, jj, RF_RCK_OS, RFREG_OFFSET_MASK, 0x30000); + rtl_set_rfreg(hw, jj, RF_TXPA_G1, RFREG_OFFSET_MASK, 0x0000f); + rtl_set_rfreg(hw, jj, RF_TXPA_G2, RFREG_OFFSET_MASK, 0xf7ffa); + rtl_set_bbreg(hw, RFPGA0_IQK, MASKDWORD, 0x80800000); + + /*IQK Setting*/ + rtl_set_bbreg(hw, RRX_IQK, MASKDWORD, 0x01004800); + + /*path a IQK setting*/ + rtl_set_bbreg(hw, RTX_IQK_TONE_A, MASKDWORD, 0x30008c1c); + rtl_set_bbreg(hw, RRX_IQK_TONE_A, MASKDWORD, 0x10008c1c); + rtl_set_bbreg(hw, RTX_IQK_PI_A, MASKDWORD, 0x82160c05); + rtl_set_bbreg(hw, RRX_IQK_PI_A, MASKDWORD, 0x28160c05); + + /*LO calibration Setting*/ + rtl_set_bbreg(hw, RIQK_AGC_RSP, MASKDWORD, 0x0046a911); + /*one shot, path A LOK & iqk*/ + rtl_set_bbreg(hw, RIQK_AGC_PTS, MASKDWORD, 0xf9000000); + rtl_set_bbreg(hw, RIQK_AGC_PTS, MASKDWORD, 0xf8000000); + + mdelay(IQK_DELAY_TIME); + + reg_eac = rtl_get_bbreg(hw, RRX_POWER_AFTER_IQK_A_2, MASKDWORD); + reg_e94 = rtl_get_bbreg(hw, RTX_POWER_BEFORE_IQK_A, MASKDWORD); + reg_e9c = rtl_get_bbreg(hw, RTX_POWER_AFTER_IQK_A, MASKDWORD); + reg_ea4 = rtl_get_bbreg(hw, RRX_POWER_BEFORE_IQK_A_2, MASKDWORD); + + if (!(reg_eac & BIT(27)) && + (((reg_ea4 & 0x03FF0000) >> 16) != 0x132) && + (((reg_eac & 0x03FF0000) >> 16) != 0x36)) + result |= 0x02; + return result; +} + +static void fill_iqk(struct ieee80211_hw *hw, bool iqk_ok, long result[][8], + u8 final, bool btxonly) +{ + u32 oldval_0, x, tx0_a, reg; + long y, tx0_c; + + if (final == 0xFF) { + return; + } else if (iqk_ok) { + oldval_0 = (rtl_get_bbreg(hw, ROFDM0_XATXIQIMBAL, + MASKDWORD) >> 22) & 0x3FF; + x = result[final][0]; + if ((x & 0x00000200) != 0) + x = x | 0xFFFFFC00; + tx0_a = (x * oldval_0) >> 8; + rtl_set_bbreg(hw, ROFDM0_XATXIQIMBAL, 0x3FF, tx0_a); + rtl_set_bbreg(hw, ROFDM0_ECCATHRES, BIT(31), + ((x * oldval_0 >> 7) & 0x1)); + y = result[final][1]; + if ((y & 0x00000200) != 0) + y |= 0xFFFFFC00; + tx0_c = (y * oldval_0) >> 8; + rtl_set_bbreg(hw, ROFDM0_XCTXAFE, 0xF0000000, + ((tx0_c & 0x3C0) >> 6)); + rtl_set_bbreg(hw, ROFDM0_XATXIQIMBAL, 0x003F0000, + (tx0_c & 0x3F)); + rtl_set_bbreg(hw, ROFDM0_ECCATHRES, BIT(29), + ((y * oldval_0 >> 7) & 0x1)); + if (btxonly) + return; + reg = result[final][2]; + rtl_set_bbreg(hw, ROFDM0_XARXIQIMBAL, 0x3FF, reg); + reg = result[final][3] & 0x3F; + rtl_set_bbreg(hw, ROFDM0_XARXIQIMBAL, 0xFC00, reg); + reg = (result[final][3] >> 6) & 0xF; + rtl_set_bbreg(hw, 0xca0, 0xF0000000, reg); + } +} + +static void save_adda_reg(struct ieee80211_hw *hw, + const u32 *addareg, u32 *backup, + u32 registernum) +{ + u32 i; + + for (i = 0; i < registernum; i++) + backup[i] = rtl_get_bbreg(hw, addareg[i], MASKDWORD); +} + +static void save_mac_reg(struct ieee80211_hw *hw, const u32 *macreg, + u32 *macbackup) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + + for (i = 0; i < (IQK_MAC_REG_NUM - 1); i++) + macbackup[i] = rtl_read_byte(rtlpriv, macreg[i]); + macbackup[i] = rtl_read_dword(rtlpriv, macreg[i]); +} + +static void reload_adda(struct ieee80211_hw *hw, const u32 *addareg, + u32 *backup, u32 reg_num) +{ + u32 i; + + for (i = 0; i < reg_num; i++) + rtl_set_bbreg(hw, addareg[i], MASKDWORD, backup[i]); +} + +static void reload_mac(struct ieee80211_hw *hw, const u32 *macreg, + u32 *macbackup) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + + for (i = 0; i < (IQK_MAC_REG_NUM - 1); i++) + rtl_write_byte(rtlpriv, macreg[i], (u8) macbackup[i]); + rtl_write_dword(rtlpriv, macreg[i], macbackup[i]); +} + +static void _rtl88e_phy_path_adda_on(struct ieee80211_hw *hw, + const u32 *addareg, bool is_patha_on, + bool is2t) +{ + u32 pathon; + u32 i; + + pathon = is_patha_on ? 0x04db25a4 : 0x0b1b25a4; + if (false == is2t) { + pathon = 0x0bdb25a0; + rtl_set_bbreg(hw, addareg[0], MASKDWORD, 0x0b1b25a0); + } else { + rtl_set_bbreg(hw, addareg[0], MASKDWORD, pathon); + } + + for (i = 1; i < IQK_ADDA_REG_NUM; i++) + rtl_set_bbreg(hw, addareg[i], MASKDWORD, pathon); +} + +static void _rtl88e_phy_mac_setting_calibration(struct ieee80211_hw *hw, + const u32 *macreg, + u32 *macbackup) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i = 0; + + rtl_write_byte(rtlpriv, macreg[i], 0x3F); + + for (i = 1; i < (IQK_MAC_REG_NUM - 1); i++) + rtl_write_byte(rtlpriv, macreg[i], + (u8) (macbackup[i] & (~BIT(3)))); + rtl_write_byte(rtlpriv, macreg[i], (u8) (macbackup[i] & (~BIT(5)))); +} + +static void _rtl88e_phy_path_a_standby(struct ieee80211_hw *hw) +{ + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0x0); + rtl_set_bbreg(hw, 0x840, MASKDWORD, 0x00010000); + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0x80800000); +} + +static void _rtl88e_phy_pi_mode_switch(struct ieee80211_hw *hw, bool pi_mode) +{ + u32 mode; + + mode = pi_mode ? 0x01000100 : 0x01000000; + rtl_set_bbreg(hw, 0x820, MASKDWORD, mode); + rtl_set_bbreg(hw, 0x828, MASKDWORD, mode); +} + +static bool sim_comp(struct ieee80211_hw *hw, long result[][8], u8 c1, u8 c2) +{ + u32 i, j, diff, bitmap, bound; + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + u8 final[2] = {0xFF, 0xFF}; + bool bresult = true, is2t = IS_92C_SERIAL(rtlhal->version); + + if (is2t) + bound = 8; + else + bound = 4; + + bitmap = 0; + + for (i = 0; i < bound; i++) { + diff = (result[c1][i] > result[c2][i]) ? + (result[c1][i] - result[c2][i]) : + (result[c2][i] - result[c1][i]); + + if (diff > MAX_TOLERANCE) { + if ((i == 2 || i == 6) && !bitmap) { + if (result[c1][i] + result[c1][i + 1] == 0) + final[(i / 4)] = c2; + else if (result[c2][i] + result[c2][i + 1] == 0) + final[(i / 4)] = c1; + else + bitmap = bitmap | (1 << i); + } else { + bitmap = bitmap | (1 << i); + } + } + } + + if (bitmap == 0) { + for (i = 0; i < (bound / 4); i++) { + if (final[i] != 0xFF) { + for (j = i * 4; j < (i + 1) * 4 - 2; j++) + result[3][j] = result[final[i]][j]; + bresult = false; + } + } + return bresult; + } else if (!(bitmap & 0x0F)) { + for (i = 0; i < 4; i++) + result[3][i] = result[c1][i]; + return false; + } else if (!(bitmap & 0xF0) && is2t) { + for (i = 4; i < 8; i++) + result[3][i] = result[c1][i]; + return false; + } else { + return false; + } +} + +static void _rtl88e_phy_iq_calibrate(struct ieee80211_hw *hw, + long result[][8], u8 t, bool is2t) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 i; + u8 patha_ok, pathb_ok; + const u32 adda_reg[IQK_ADDA_REG_NUM] = { + 0x85c, 0xe6c, 0xe70, 0xe74, + 0xe78, 0xe7c, 0xe80, 0xe84, + 0xe88, 0xe8c, 0xed0, 0xed4, + 0xed8, 0xedc, 0xee0, 0xeec + }; + const u32 iqk_mac_reg[IQK_MAC_REG_NUM] = { + 0x522, 0x550, 0x551, 0x040 + }; + const u32 iqk_bb_reg[IQK_BB_REG_NUM] = { + ROFDM0_TRXPATHENABLE, ROFDM0_TRMUXPAR, RFPGA0_XCD_RFINTERFACESW, + 0xb68, 0xb6c, 0x870, 0x860, 0x864, 0x800 + }; + const u32 retrycount = 2; + + if (t == 0) { + save_adda_reg(hw, adda_reg, rtlphy->adda_backup, 16); + save_mac_reg(hw, iqk_mac_reg, rtlphy->iqk_mac_backup); + save_adda_reg(hw, iqk_bb_reg, rtlphy->iqk_bb_backup, + IQK_BB_REG_NUM); + } + _rtl88e_phy_path_adda_on(hw, adda_reg, true, is2t); + if (t == 0) { + rtlphy->rfpi_enable = (u8) rtl_get_bbreg(hw, + RFPGA0_XA_HSSIPARAMETER1, BIT(8)); + } + + if (!rtlphy->rfpi_enable) + _rtl88e_phy_pi_mode_switch(hw, true); + /*BB Setting*/ + rtl_set_bbreg(hw, 0x800, BIT(24), 0x00); + rtl_set_bbreg(hw, 0xc04, MASKDWORD, 0x03a05600); + rtl_set_bbreg(hw, 0xc08, MASKDWORD, 0x000800e4); + rtl_set_bbreg(hw, 0x874, MASKDWORD, 0x22204000); + + rtl_set_bbreg(hw, 0x870, BIT(10), 0x01); + rtl_set_bbreg(hw, 0x870, BIT(26), 0x01); + rtl_set_bbreg(hw, 0x860, BIT(10), 0x00); + rtl_set_bbreg(hw, 0x864, BIT(10), 0x00); + + if (is2t) { + rtl_set_bbreg(hw, 0x840, MASKDWORD, 0x00010000); + rtl_set_bbreg(hw, 0x844, MASKDWORD, 0x00010000); + } + _rtl88e_phy_mac_setting_calibration(hw, iqk_mac_reg, + rtlphy->iqk_mac_backup); + rtl_set_bbreg(hw, 0xb68, MASKDWORD, 0x0f600000); + if (is2t) + rtl_set_bbreg(hw, 0xb6c, MASKDWORD, 0x0f600000); + + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0x80800000); + rtl_set_bbreg(hw, 0xe40, MASKDWORD, 0x01007c00); + rtl_set_bbreg(hw, 0xe44, MASKDWORD, 0x81004800); + for (i = 0; i < retrycount; i++) { + patha_ok = _rtl88e_phy_path_a_iqk(hw, is2t); + if (patha_ok == 0x01) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Path A Tx IQK Success!!\n"); + result[t][0] = (rtl_get_bbreg(hw, 0xe94, MASKDWORD) & + 0x3FF0000) >> 16; + result[t][1] = (rtl_get_bbreg(hw, 0xe9c, MASKDWORD) & + 0x3FF0000) >> 16; + break; + } + } + + for (i = 0; i < retrycount; i++) { + patha_ok = _rtl88e_phy_path_a_rx_iqk(hw, is2t); + if (patha_ok == 0x03) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Path A Rx IQK Success!!\n"); + result[t][2] = (rtl_get_bbreg(hw, 0xea4, MASKDWORD) & + 0x3FF0000) >> 16; + result[t][3] = (rtl_get_bbreg(hw, 0xeac, MASKDWORD) & + 0x3FF0000) >> 16; + break; + } else { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Path a RX iqk fail!!!\n"); + } + } + + if (0 == patha_ok) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Path A IQK Success!!\n"); + } + if (is2t) { + _rtl88e_phy_path_a_standby(hw); + _rtl88e_phy_path_adda_on(hw, adda_reg, false, is2t); + for (i = 0; i < retrycount; i++) { + pathb_ok = _rtl88e_phy_path_b_iqk(hw); + if (pathb_ok == 0x03) { + result[t][4] = (rtl_get_bbreg(hw, + 0xeb4, MASKDWORD) & + 0x3FF0000) >> 16; + result[t][5] = + (rtl_get_bbreg(hw, 0xebc, MASKDWORD) & + 0x3FF0000) >> 16; + result[t][6] = + (rtl_get_bbreg(hw, 0xec4, MASKDWORD) & + 0x3FF0000) >> 16; + result[t][7] = + (rtl_get_bbreg(hw, 0xecc, MASKDWORD) & + 0x3FF0000) >> 16; + break; + } else if (i == (retrycount - 1) && pathb_ok == 0x01) { + result[t][4] = (rtl_get_bbreg(hw, + 0xeb4, MASKDWORD) & + 0x3FF0000) >> 16; + } + result[t][5] = (rtl_get_bbreg(hw, 0xebc, MASKDWORD) & + 0x3FF0000) >> 16; + } + } + + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0); + + if (t != 0) { + if (!rtlphy->rfpi_enable) + _rtl88e_phy_pi_mode_switch(hw, false); + reload_adda(hw, adda_reg, rtlphy->adda_backup, 16); + reload_mac(hw, iqk_mac_reg, rtlphy->iqk_mac_backup); + reload_adda(hw, iqk_bb_reg, rtlphy->iqk_bb_backup, + IQK_BB_REG_NUM); + + rtl_set_bbreg(hw, 0x840, MASKDWORD, 0x00032ed3); + if (is2t) + rtl_set_bbreg(hw, 0x844, MASKDWORD, 0x00032ed3); + rtl_set_bbreg(hw, 0xe30, MASKDWORD, 0x01008c00); + rtl_set_bbreg(hw, 0xe34, MASKDWORD, 0x01008c00); + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "88ee IQK Finish!!\n"); +} + +static void _rtl88e_phy_lc_calibrate(struct ieee80211_hw *hw, bool is2t) +{ + u8 tmpreg; + u32 rf_a_mode = 0, rf_b_mode = 0, lc_cal; + struct rtl_priv *rtlpriv = rtl_priv(hw); + int jj = RF90_PATH_A; + int kk = RF90_PATH_B; + + tmpreg = rtl_read_byte(rtlpriv, 0xd03); + + if ((tmpreg & 0x70) != 0) + rtl_write_byte(rtlpriv, 0xd03, tmpreg & 0x8F); + else + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0xFF); + + if ((tmpreg & 0x70) != 0) { + rf_a_mode = rtl_get_rfreg(hw, jj, 0x00, MASK12BITS); + + if (is2t) + rf_b_mode = rtl_get_rfreg(hw, kk, 0x00, + MASK12BITS); + + rtl_set_rfreg(hw, jj, 0x00, MASK12BITS, + (rf_a_mode & 0x8FFFF) | 0x10000); + + if (is2t) + rtl_set_rfreg(hw, kk, 0x00, MASK12BITS, + (rf_b_mode & 0x8FFFF) | 0x10000); + } + lc_cal = rtl_get_rfreg(hw, jj, 0x18, MASK12BITS); + + rtl_set_rfreg(hw, jj, 0x18, MASK12BITS, lc_cal | 0x08000); + + mdelay(100); + + if ((tmpreg & 0x70) != 0) { + rtl_write_byte(rtlpriv, 0xd03, tmpreg); + rtl_set_rfreg(hw, jj, 0x00, MASK12BITS, rf_a_mode); + + if (is2t) + rtl_set_rfreg(hw, kk, 0x00, MASK12BITS, + rf_b_mode); + } else { + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0x00); + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "\n"); +} + +static void rfpath_switch(struct ieee80211_hw *hw, + bool bmain, bool is2t) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_efuse *fuse = rtl_efuse(rtl_priv(hw)); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "\n"); + + if (is_hal_stop(rtlhal)) { + u8 u1btmp; + u1btmp = rtl_read_byte(rtlpriv, REG_LEDCFG0); + rtl_write_byte(rtlpriv, REG_LEDCFG0, u1btmp | BIT(7)); + rtl_set_bbreg(hw, rFPGA0_XAB_RFPARAMETER, BIT(13), 0x01); + } + if (is2t) { + if (bmain) + rtl_set_bbreg(hw, RFPGA0_XB_RFINTERFACEOE, + BIT(5) | BIT(6), 0x1); + else + rtl_set_bbreg(hw, RFPGA0_XB_RFINTERFACEOE, + BIT(5) | BIT(6), 0x2); + } else { + rtl_set_bbreg(hw, RFPGA0_XAB_RFINTERFACESW, BIT(8) | BIT(9), 0); + rtl_set_bbreg(hw, 0x914, MASKLWORD, 0x0201); + + /* We use the RF definition of MAIN and AUX, left antenna and + * right antenna repectively. + * Default output at AUX. + */ + if (bmain) { + rtl_set_bbreg(hw, RFPGA0_XA_RFINTERFACEOE, BIT(14) | + BIT(13) | BIT(12), 0); + rtl_set_bbreg(hw, RFPGA0_XB_RFINTERFACEOE, BIT(5) | + BIT(4) | BIT(3), 0); + if (fuse->antenna_div_type == CGCS_RX_HW_ANTDIV) + rtl_set_bbreg(hw, RCONFIG_RAM64X16, BIT(31), 0); + } else { + rtl_set_bbreg(hw, RFPGA0_XA_RFINTERFACEOE, BIT(14) | + BIT(13) | BIT(12), 1); + rtl_set_bbreg(hw, RFPGA0_XB_RFINTERFACEOE, BIT(5) | + BIT(4) | BIT(3), 1); + if (fuse->antenna_div_type == CGCS_RX_HW_ANTDIV) + rtl_set_bbreg(hw, RCONFIG_RAM64X16, BIT(31), 1); + } + } +} + +#undef IQK_ADDA_REG_NUM +#undef IQK_DELAY_TIME + +void rtl88e_phy_iq_calibrate(struct ieee80211_hw *hw, bool recovery) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + long result[4][8]; + u8 i, final; + bool patha_ok; + long reg_e94, reg_e9c, reg_ea4, reg_eb4, reg_ebc, reg_tmp = 0; + bool is12simular, is13simular, is23simular; + u32 iqk_bb_reg[9] = { + ROFDM0_XARXIQIMBAL, + ROFDM0_XBRXIQIMBAL, + ROFDM0_ECCATHRES, + ROFDM0_AGCRSSITABLE, + ROFDM0_XATXIQIMBAL, + ROFDM0_XBTXIQIMBAL, + ROFDM0_XCTXAFE, + ROFDM0_XDTXAFE, + ROFDM0_RXIQEXTANTA + }; + + if (recovery) { + reload_adda(hw, iqk_bb_reg, rtlphy->iqk_bb_backup, 9); + return; + } + + memset(result, 0, 32 * sizeof(long)); + final = 0xff; + patha_ok = false; + is12simular = false; + is23simular = false; + is13simular = false; + for (i = 0; i < 3; i++) { + if (get_rf_type(rtlphy) == RF_2T2R) + _rtl88e_phy_iq_calibrate(hw, result, i, true); + else + _rtl88e_phy_iq_calibrate(hw, result, i, false); + if (i == 1) { + is12simular = sim_comp(hw, result, 0, 1); + if (is12simular) { + final = 0; + break; + } + } + if (i == 2) { + is13simular = sim_comp(hw, result, 0, 2); + if (is13simular) { + final = 0; + break; + } + is23simular = sim_comp(hw, result, 1, 2); + if (is23simular) { + final = 1; + } else { + for (i = 0; i < 8; i++) + reg_tmp += result[3][i]; + + if (reg_tmp != 0) + final = 3; + else + final = 0xFF; + } + } + } + for (i = 0; i < 4; i++) { + reg_e94 = result[i][0]; + reg_e9c = result[i][1]; + reg_ea4 = result[i][2]; + reg_eb4 = result[i][4]; + reg_ebc = result[i][5]; + } + if (final != 0xff) { + reg_e94 = result[final][0]; + rtlphy->reg_e94 = reg_e94; + reg_e9c = result[final][1]; + rtlphy->reg_e9c = reg_e9c; + reg_ea4 = result[final][2]; + reg_eb4 = result[final][4]; + rtlphy->reg_eb4 = reg_eb4; + reg_ebc = result[final][5]; + rtlphy->reg_ebc = reg_ebc; + patha_ok = true; + } else { + rtlphy->reg_e94 = 0x100; + rtlphy->reg_eb4 = 0x100; + rtlphy->reg_ebc = 0x0; + rtlphy->reg_e9c = 0x0; + } + if (reg_e94 != 0) /*&&(reg_ea4 != 0) */ + fill_iqk(hw, patha_ok, result, final, (reg_ea4 == 0)); + if (final != 0xFF) { + for (i = 0; i < IQK_MATRIX_REG_NUM; i++) + rtlphy->iqk_matrix[0].value[0][i] = result[final][i]; + rtlphy->iqk_matrix[0].iqk_done = true; + } + save_adda_reg(hw, iqk_bb_reg, rtlphy->iqk_bb_backup, 9); +} + +void rtl88e_phy_lc_calibrate(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = &(rtlpriv->rtlhal); + bool start_conttx = false, singletone = false; + u32 timeout = 2000, timecount = 0; + + if (start_conttx || singletone) + return; + + while (rtlpriv->mac80211.act_scanning && timecount < timeout) { + udelay(50); + timecount += 50; + } + + rtlphy->lck_inprogress = true; + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "LCK:Start!!! currentband %x delay %d ms\n", + rtlhal->current_bandtype, timecount); + + _rtl88e_phy_lc_calibrate(hw, false); + + rtlphy->lck_inprogress = false; +} + +void rtl88e_phy_set_rfpath_switch(struct ieee80211_hw *hw, bool bmain) +{ + rfpath_switch(hw, bmain, false); +} + +bool rtl88e_phy_set_io_cmd(struct ieee80211_hw *hw, enum io_type iotype) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + bool postprocessing = false; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "-->IO Cmd(%#x), set_io_inprogress(%d)\n", + iotype, rtlphy->set_io_inprogress); + do { + switch (iotype) { + case IO_CMD_RESUME_DM_BY_SCAN: + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "[IO CMD] Resume DM after scan.\n"); + postprocessing = true; + break; + case IO_CMD_PAUSE_DM_BY_SCAN: + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "[IO CMD] Pause DM before scan.\n"); + postprocessing = true; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + } while (false); + if (postprocessing && !rtlphy->set_io_inprogress) { + rtlphy->set_io_inprogress = true; + rtlphy->current_io_type = iotype; + } else { + return false; + } + rtl88e_phy_set_io(hw); + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, "IO Type(%#x)\n", iotype); + return true; +} + +static void rtl88ee_phy_set_rf_on(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_byte(rtlpriv, REG_SPS0_CTRL, 0x2b); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE3); + /*rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x00);*/ + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE2); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE3); + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0x00); +} + +static void _rtl88ee_phy_set_rf_sleep(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int jj = RF90_PATH_A; + + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0xFF); + rtl_set_rfreg(hw, jj, 0x00, RFREG_OFFSET_MASK, 0x00); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE2); + rtl_write_byte(rtlpriv, REG_SPS0_CTRL, 0x22); +} + +static bool _rtl88ee_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl8192_tx_ring *ring = NULL; + bool bresult = true; + u8 i, queue_id; + + switch (rfpwr_state) { + case ERFON:{ + if ((ppsc->rfpwr_state == ERFOFF) && + RT_IN_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC)) { + bool rtstatus; + u32 init = 0; + do { + init++; + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "IPS Set eRf nic enable\n"); + rtstatus = rtl_ps_enable_nic(hw); + } while ((rtstatus != true) && (init < 10)); + RT_CLEAR_PS_LEVEL(ppsc, + RT_RF_OFF_LEVL_HALT_NIC); + } else { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "Set ERFON sleeped:%d ms\n", + jiffies_to_msecs(jiffies - ppsc-> + last_sleep_jiffies)); + ppsc->last_awake_jiffies = jiffies; + rtl88ee_phy_set_rf_on(hw); + } + if (mac->link_state == MAC80211_LINKED) + rtlpriv->cfg->ops->led_control(hw, LED_CTL_LINK); + else + rtlpriv->cfg->ops->led_control(hw, LED_CTL_NO_LINK); + break; } + case ERFOFF:{ + for (queue_id = 0, i = 0; + queue_id < RTL_PCI_MAX_TX_QUEUE_COUNT;) { + ring = &pcipriv->dev.tx_ring[queue_id]; + if (skb_queue_len(&ring->queue) == 0) { + queue_id++; + continue; + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "eRf Off/Sleep: %d times TcbBusyQueue[%d] =%d before doze!\n", + (i + 1), queue_id, + skb_queue_len(&ring->queue)); + + udelay(10); + i++; + } + if (i >= MAX_DOZE_WAITING_TIMES_9x) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "\n ERFSLEEP: %d times TcbBusyQueue[%d] = %d !\n", + MAX_DOZE_WAITING_TIMES_9x, + queue_id, + skb_queue_len(&ring->queue)); + break; + } + } + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_HALT_NIC) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "IPS Set eRf nic disable\n"); + rtl_ps_disable_nic(hw); + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + } else { + if (ppsc->rfoff_reason == RF_CHANGE_BY_IPS) { + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_NO_LINK); + } else { + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_POWER_OFF); + } + } + break; } + case ERFSLEEP:{ + if (ppsc->rfpwr_state == ERFOFF) + break; + for (queue_id = 0, i = 0; + queue_id < RTL_PCI_MAX_TX_QUEUE_COUNT;) { + ring = &pcipriv->dev.tx_ring[queue_id]; + if (skb_queue_len(&ring->queue) == 0) { + queue_id++; + continue; + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "eRf Off/Sleep: %d times TcbBusyQueue[%d] =%d before doze!\n", + (i + 1), queue_id, + skb_queue_len(&ring->queue)); + + udelay(10); + i++; + } + if (i >= MAX_DOZE_WAITING_TIMES_9x) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "\n ERFSLEEP: %d times TcbBusyQueue[%d] = %d !\n", + MAX_DOZE_WAITING_TIMES_9x, + queue_id, + skb_queue_len(&ring->queue)); + break; + } + } + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "Set ERFSLEEP awaked:%d ms\n", + jiffies_to_msecs(jiffies - ppsc->last_awake_jiffies)); + ppsc->last_sleep_jiffies = jiffies; + _rtl88ee_phy_set_rf_sleep(hw); + break; } + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + bresult = false; + break; + } + if (bresult) + ppsc->rfpwr_state = rfpwr_state; + return bresult; +} + +bool rtl88e_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state) +{ + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + bool bresult; + + if (rfpwr_state == ppsc->rfpwr_state) + return false; + bresult = _rtl88ee_phy_set_rf_power_state(hw, rfpwr_state); + return bresult; +} diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/phy.h b/drivers/net/wireless/rtlwifi/rtl8188ee/phy.h new file mode 100644 index 0000000..f1acd6d --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/phy.h @@ -0,0 +1,236 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92C_PHY_H__ +#define __RTL92C_PHY_H__ + +/*It must always set to 4, otherwise read efuse table secquence will be wrong.*/ +#define MAX_TX_COUNT 4 + +#define MAX_PRECMD_CNT 16 +#define MAX_RFDEPENDCMD_CNT 16 +#define MAX_POSTCMD_CNT 16 + +#define MAX_DOZE_WAITING_TIMES_9x 64 + +#define RT_CANNOT_IO(hw) false +#define HIGHPOWER_RADIOA_ARRAYLEN 22 + +#define IQK_ADDA_REG_NUM 16 +#define IQK_BB_REG_NUM 9 +#define MAX_TOLERANCE 5 +#define IQK_DELAY_TIME 10 +#define IDX_MAP 15 + +#define APK_BB_REG_NUM 5 +#define APK_AFE_REG_NUM 16 +#define APK_CURVE_REG_NUM 4 +#define PATH_NUM 2 + +#define LOOP_LIMIT 5 +#define MAX_STALL_TIME 50 +#define ANTENNADIVERSITYVALUE 0x80 +#define MAX_TXPWR_IDX_NMODE_92S 63 +#define RESET_CNT_LIMIT 3 + +#define IQK_ADDA_REG_NUM 16 +#define IQK_MAC_REG_NUM 4 + +#define RF6052_MAX_PATH 2 + +#define CT_OFFSET_MAC_ADDR 0X16 + +#define CT_OFFSET_CCK_TX_PWR_IDX 0x5A +#define CT_OFFSET_HT401S_TX_PWR_IDX 0x60 +#define CT_OFFSET_HT402S_TX_PWR_IDX_DIFF 0x66 +#define CT_OFFSET_HT20_TX_PWR_IDX_DIFF 0x69 +#define CT_OFFSET_OFDM_TX_PWR_IDX_DIFF 0x6C + +#define CT_OFFSET_HT40_MAX_PWR_OFFSET 0x6F +#define CT_OFFSET_HT20_MAX_PWR_OFFSET 0x72 + +#define CT_OFFSET_CHANNEL_PLAH 0x75 +#define CT_OFFSET_THERMAL_METER 0x78 +#define CT_OFFSET_RF_OPTION 0x79 +#define CT_OFFSET_VERSION 0x7E +#define CT_OFFSET_CUSTOMER_ID 0x7F + +#define RTL92C_MAX_PATH_NUM 2 + +enum swchnlcmd_id { + CMDID_END, + CMDID_SET_TXPOWEROWER_LEVEL, + CMDID_BBREGWRITE10, + CMDID_WRITEPORT_ULONG, + CMDID_WRITEPORT_USHORT, + CMDID_WRITEPORT_UCHAR, + CMDID_RF_WRITEREG, +}; + +struct swchnlcmd { + enum swchnlcmd_id cmdid; + u32 para1; + u32 para2; + u32 msdelay; +}; + +enum hw90_block_e { + HW90_BLOCK_MAC = 0, + HW90_BLOCK_PHY0 = 1, + HW90_BLOCK_PHY1 = 2, + HW90_BLOCK_RF = 3, + HW90_BLOCK_MAXIMUM = 4, +}; + +enum baseband_config_type { + BASEBAND_CONFIG_PHY_REG = 0, + BASEBAND_CONFIG_AGC_TAB = 1, +}; + +enum ra_offset_area { + RA_OFFSET_LEGACY_OFDM1, + RA_OFFSET_LEGACY_OFDM2, + RA_OFFSET_HT_OFDM1, + RA_OFFSET_HT_OFDM2, + RA_OFFSET_HT_OFDM3, + RA_OFFSET_HT_OFDM4, + RA_OFFSET_HT_CCK, +}; + +enum antenna_path { + ANTENNA_NONE, + ANTENNA_D, + ANTENNA_C, + ANTENNA_CD, + ANTENNA_B, + ANTENNA_BD, + ANTENNA_BC, + ANTENNA_BCD, + ANTENNA_A, + ANTENNA_AD, + ANTENNA_AC, + ANTENNA_ACD, + ANTENNA_AB, + ANTENNA_ABD, + ANTENNA_ABC, + ANTENNA_ABCD +}; + +struct r_antenna_select_ofdm { + u32 r_tx_antenna:4; + u32 r_ant_l:4; + u32 r_ant_non_ht:4; + u32 r_ant_ht1:4; + u32 r_ant_ht2:4; + u32 r_ant_ht_s1:4; + u32 r_ant_non_ht_s1:4; + u32 ofdm_txsc:2; + u32 reserved:2; +}; + +struct r_antenna_select_cck { + u8 r_cckrx_enable_2:2; + u8 r_cckrx_enable:2; + u8 r_ccktx_enable:4; +}; + + +struct efuse_contents { + u8 mac_addr[ETH_ALEN]; + u8 cck_tx_power_idx[6]; + u8 ht40_1s_tx_power_idx[6]; + u8 ht40_2s_tx_power_idx_diff[3]; + u8 ht20_tx_power_idx_diff[3]; + u8 ofdm_tx_power_idx_diff[3]; + u8 ht40_max_power_offset[3]; + u8 ht20_max_power_offset[3]; + u8 channel_plan; + u8 thermal_meter; + u8 rf_option[5]; + u8 version; + u8 oem_id; + u8 regulatory; +}; + +struct tx_power_struct { + u8 cck[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 ht40_1s[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 ht40_2s[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 ht20_diff[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 legacy_ht_diff[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 legacy_ht_txpowerdiff; + u8 groupht20[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 groupht40[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 pwrgroup_cnt; + u32 mcs_original_offset[4][16]; +}; + +enum _ANT_DIV_TYPE { + NO_ANTDIV = 0xFF, + CG_TRX_HW_ANTDIV = 0x01, + CGCS_RX_HW_ANTDIV = 0x02, + FIXED_HW_ANTDIV = 0x03, + CG_TRX_SMART_ANTDIV = 0x04, + CGCS_RX_SW_ANTDIV = 0x05, +}; + +extern u32 rtl88e_phy_query_bb_reg(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask); +extern void rtl88e_phy_set_bb_reg(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask, u32 data); +extern u32 rtl88e_phy_query_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, + u32 bitmask); +extern void rtl88e_phy_set_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, + u32 bitmask, u32 data); +extern bool rtl88e_phy_mac_config(struct ieee80211_hw *hw); +extern bool rtl88e_phy_bb_config(struct ieee80211_hw *hw); +extern bool rtl88e_phy_rf_config(struct ieee80211_hw *hw); +extern void rtl88e_phy_get_hw_reg_originalvalue(struct ieee80211_hw *hw); +extern void rtl88e_phy_get_txpower_level(struct ieee80211_hw *hw, + long *powerlevel); +extern void rtl88e_phy_set_txpower_level(struct ieee80211_hw *hw, u8 channel); +extern void rtl88e_phy_scan_operation_backup(struct ieee80211_hw *hw, + u8 operation); +extern void rtl88e_phy_set_bw_mode_callback(struct ieee80211_hw *hw); +extern void rtl88e_phy_set_bw_mode(struct ieee80211_hw *hw, + enum nl80211_channel_type ch_type); +extern void rtl88e_phy_sw_chnl_callback(struct ieee80211_hw *hw); +extern u8 rtl88e_phy_sw_chnl(struct ieee80211_hw *hw); +extern void rtl88e_phy_iq_calibrate(struct ieee80211_hw *hw, bool b_recovery); +void rtl88e_phy_lc_calibrate(struct ieee80211_hw *hw); +void rtl88e_phy_set_rfpath_switch(struct ieee80211_hw *hw, bool bmain); +bool rtl88e_phy_config_rf_with_headerfile(struct ieee80211_hw *hw, + enum radio_path rfpath); +bool rtl88e_phy_set_io_cmd(struct ieee80211_hw *hw, enum io_type iotype); +extern bool rtl88e_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/pwrseq.c b/drivers/net/wireless/rtlwifi/rtl8188ee/pwrseq.c new file mode 100644 index 0000000..6dc4e3a --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/pwrseq.c @@ -0,0 +1,109 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "pwrseqcmd.h" +#include "pwrseq.h" + +/* drivers should parse below arrays and do the corresponding actions */ +/*3 Power on Array*/ +struct wlan_pwr_cfg rtl8188e_power_on_flow[RTL8188E_TRANS_CARDEMU_TO_ACT_STEPS + + RTL8188E_TRANS_END_STEPS] = { + RTL8188E_TRANS_CARDEMU_TO_ACT + RTL8188E_TRANS_END +}; + +/*3Radio off GPIO Array */ +struct wlan_pwr_cfg rtl8188e_radio_off_flow[RTL8188E_TRANS_ACT_TO_CARDEMU_STEPS + + RTL8188E_TRANS_END_STEPS] = { + RTL8188E_TRANS_ACT_TO_CARDEMU + RTL8188E_TRANS_END +}; + +/*3Card Disable Array*/ +struct wlan_pwr_cfg rtl8188e_card_disable_flow + [RTL8188E_TRANS_ACT_TO_CARDEMU_STEPS + + RTL8188E_TRANS_CARDEMU_TO_PDN_STEPS + + RTL8188E_TRANS_END_STEPS] = { + RTL8188E_TRANS_ACT_TO_CARDEMU + RTL8188E_TRANS_CARDEMU_TO_CARDDIS + RTL8188E_TRANS_END +}; + +/*3 Card Enable Array*/ +struct wlan_pwr_cfg rtl8188e_card_enable_flow + [RTL8188E_TRANS_ACT_TO_CARDEMU_STEPS + + RTL8188E_TRANS_CARDEMU_TO_PDN_STEPS + + RTL8188E_TRANS_END_STEPS] = { + RTL8188E_TRANS_CARDDIS_TO_CARDEMU + RTL8188E_TRANS_CARDEMU_TO_ACT + RTL8188E_TRANS_END +}; + +/*3Suspend Array*/ +struct wlan_pwr_cfg rtl8188e_suspend_flow[RTL8188E_TRANS_ACT_TO_CARDEMU_STEPS + + RTL8188E_TRANS_CARDEMU_TO_SUS_STEPS + + RTL8188E_TRANS_END_STEPS] = { + RTL8188E_TRANS_ACT_TO_CARDEMU + RTL8188E_TRANS_CARDEMU_TO_SUS + RTL8188E_TRANS_END +}; + +/*3 Resume Array*/ +struct wlan_pwr_cfg rtl8188e_resume_flow[RTL8188E_TRANS_ACT_TO_CARDEMU_STEPS + + RTL8188E_TRANS_CARDEMU_TO_SUS_STEPS + + RTL8188E_TRANS_END_STEPS] = { + RTL8188E_TRANS_SUS_TO_CARDEMU + RTL8188E_TRANS_CARDEMU_TO_ACT + RTL8188E_TRANS_END +}; + +/*3HWPDN Array*/ +struct wlan_pwr_cfg rtl8188e_hwpdn_flow[RTL8188E_TRANS_ACT_TO_CARDEMU_STEPS + + RTL8188E_TRANS_CARDEMU_TO_PDN_STEPS + + RTL8188E_TRANS_END_STEPS] = { + RTL8188E_TRANS_ACT_TO_CARDEMU + RTL8188E_TRANS_CARDEMU_TO_PDN + RTL8188E_TRANS_END +}; + +/*3 Enter LPS */ +struct wlan_pwr_cfg rtl8188e_enter_lps_flow[RTL8188E_TRANS_ACT_TO_LPS_STEPS + + RTL8188E_TRANS_END_STEPS] = { + /*FW behavior*/ + RTL8188E_TRANS_ACT_TO_LPS + RTL8188E_TRANS_END +}; + +/*3 Leave LPS */ +struct wlan_pwr_cfg rtl8188e_leave_lps_flow[RTL8188E_TRANS_LPS_TO_ACT_STEPS + + RTL8188E_TRANS_END_STEPS] = { + /*FW behavior*/ + RTL8188E_TRANS_LPS_TO_ACT + RTL8188E_TRANS_END +}; diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/pwrseq.h b/drivers/net/wireless/rtlwifi/rtl8188ee/pwrseq.h new file mode 100644 index 0000000..028ec6d --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/pwrseq.h @@ -0,0 +1,327 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL8723E_PWRSEQ_H__ +#define __RTL8723E_PWRSEQ_H__ + +#include "pwrseqcmd.h" +/* + Check document WM-20110607-Paul-RTL8188E_Power_Architecture-R02.vsd + There are 6 HW Power States: + 0: POFF--Power Off + 1: PDN--Power Down + 2: CARDEMU--Card Emulation + 3: ACT--Active Mode + 4: LPS--Low Power State + 5: SUS--Suspend + + The transision from different states are defined below + TRANS_CARDEMU_TO_ACT + TRANS_ACT_TO_CARDEMU + TRANS_CARDEMU_TO_SUS + TRANS_SUS_TO_CARDEMU + TRANS_CARDEMU_TO_PDN + TRANS_ACT_TO_LPS + TRANS_LPS_TO_ACT + + TRANS_END + PWR SEQ Version: rtl8188e_PwrSeq_V09.h +*/ + +#define RTL8188E_TRANS_CARDEMU_TO_ACT_STEPS 10 +#define RTL8188E_TRANS_ACT_TO_CARDEMU_STEPS 10 +#define RTL8188E_TRANS_CARDEMU_TO_SUS_STEPS 10 +#define RTL8188E_TRANS_SUS_TO_CARDEMU_STEPS 10 +#define RTL8188E_TRANS_CARDEMU_TO_PDN_STEPS 10 +#define RTL8188E_TRANS_PDN_TO_CARDEMU_STEPS 10 +#define RTL8188E_TRANS_ACT_TO_LPS_STEPS 15 +#define RTL8188E_TRANS_LPS_TO_ACT_STEPS 15 +#define RTL8188E_TRANS_END_STEPS 1 + + +#define RTL8188E_TRANS_CARDEMU_TO_ACT \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value },*/\ + {0x0006, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /* wait till 0x04[17] = 1 power ready*/ \ + PWR_BASEADDR_MAC, PWR_CMD_POLLING, BIT(1), BIT(1)}, \ + {0x0002, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /* 0x02[1:0] = 0 reset BB*/ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(0)|BIT(1), 0}, \ + {0x0026, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*0x24[23] = 2b'01 schmit trigger */ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(7), BIT(7)}, \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /* 0x04[15] = 0 disable HWPDN (control by DRV)*/ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(7), 0}, \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*0x04[12:11] = 2b'00 disable WL suspend*/ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(4)|BIT(3), 0}, \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*0x04[8] = 1 polling until return 0*/ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(0), BIT(0)}, \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*wait till 0x04[8] = 0*/ \ + PWR_BASEADDR_MAC, PWR_CMD_POLLING, BIT(0), 0}, \ + {0x0023, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(4), 0}, /*LDO normal mode*/\ + {0x0074, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(4), BIT(4)}, /*SDIO Driving*/\ + +#define RTL8188E_TRANS_ACT_TO_CARDEMU \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value },*/\ + {0x001F, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0},/*0x1F[7:0] = 0 turn off RF*/\ + {0x0023, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(4), BIT(4)}, /*LDO Sleep mode*/\ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*0x04[9] = 1 turn off MAC by HW state machine*/ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(1), BIT(1)}, \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*wait till 0x04[9] = 0 polling until return 0 to disable*/ \ + PWR_BASEADDR_MAC, PWR_CMD_POLLING, BIT(1), 0}, \ + + +#define RTL8188E_TRANS_CARDEMU_TO_SUS \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value },*/\ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, \ + PWR_INTF_USB_MSK|PWR_INTF_SDIO_MSK, \ + /*0x04[12:11] = 2b'01enable WL suspend*/ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(3)|BIT(4), BIT(3)}, \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_PCI_MSK, \ + /*0x04[12:11] = 2b'11enable WL suspend for PCIe*/ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(3)|BIT(4), BIT(3)|BIT(4)},\ + {0x0007, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, \ + PWR_INTF_USB_MSK|PWR_INTF_SDIO_MSK, \ + /* 0x04[31:30] = 2b'10 enable enable bandgap mbias in suspend */\ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, BIT(7)}, \ + {0x0041, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, \ + PWR_INTF_USB_MSK|PWR_INTF_SDIO_MSK, \ + /*Clear SIC_EN register 0x40[12] = 1'b0 */ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(4), 0}, \ + {0xfe10, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, \ + PWR_INTF_USB_MSK|PWR_INTF_SDIO_MSK, \ + /*Set USB suspend enable local register 0xfe10[4]= 1 */ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(4), BIT(4)}, \ + {0x0086, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + /*Set SDIO suspend local register*/ \ + PWR_BASEADDR_SDIO, PWR_CMD_WRITE, BIT(0), BIT(0)}, \ + {0x0086, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + /*wait power state to suspend*/ \ + PWR_BASEADDR_SDIO, PWR_CMD_POLLING, BIT(1), 0}, + +#define RTL8188E_TRANS_SUS_TO_CARDEMU \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, */\ + {0x0086, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + /*Set SDIO suspend local register*/ \ + PWR_BASEADDR_SDIO, PWR_CMD_WRITE, BIT(0), 0}, \ + {0x0086, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + /*wait power state to suspend*/ \ + PWR_BASEADDR_SDIO, PWR_CMD_POLLING, BIT(1), BIT(1)}, \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*0x04[12:11] = 2b'01enable WL suspend*/ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(3)|BIT(4), 0}, + +#define RTL8188E_TRANS_CARDEMU_TO_CARDDIS \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, */\ + {0x0026, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*0x24[23] = 2b'01 schmit trigger */ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(7), BIT(7)}, \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, \ + PWR_INTF_USB_MSK|PWR_INTF_SDIO_MSK, \ + /*0x04[12:11] = 2b'01 enable WL suspend*/ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(3)|BIT(4), BIT(3)}, \ + {0x0007, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, \ + PWR_INTF_USB_MSK|PWR_INTF_SDIO_MSK, \ + /* 0x04[31:30] = 2b'10 enable enable bandgap mbias in suspend */\ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0}, \ + {0x0041, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, \ + PWR_INTF_USB_MSK|PWR_INTF_SDIO_MSK, \ + /*Clear SIC_EN register 0x40[12] = 1'b0 */ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(4), 0}, \ + {0xfe10, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_USB_MSK, \ + /*Set USB suspend enable local register 0xfe10[4]= 1 */ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(4), BIT(4)}, \ + {0x0086, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + /*Set SDIO suspend local register*/ \ + PWR_BASEADDR_SDIO, PWR_CMD_WRITE, BIT(0), BIT(0)}, \ + {0x0086, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + PWR_CMD_POLLING, BIT(1), 0}, /*wait power state to suspend*/ + +#define RTL8188E_TRANS_CARDDIS_TO_CARDEMU \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, */\ + {0x0086, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + PWR_BASEADDR_SDIO,\ + PWR_CMD_WRITE, BIT(0), 0}, /*Set SDIO suspend local register*/ \ + {0x0086, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + PWR_BASEADDR_SDIO,\ + PWR_CMD_POLLING, BIT(1), BIT(1)}, /*wait power state to suspend*/\ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, \ + PWR_CMD_WRITE, BIT(3)|BIT(4), 0}, \ + /*0x04[12:11] = 2b'01enable WL suspend*/ + + +#define RTL8188E_TRANS_CARDEMU_TO_PDN \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, */\ + {0x0006, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(0), 0},/* 0x04[16] = 0*/ \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(7), BIT(7)},/* 0x04[15] = 1*/ + + +#define RTL8188E_TRANS_PDN_TO_CARDEMU \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, */\ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(7), 0},/* 0x04[15] = 0*/ + + +#define RTL8188E_TRANS_ACT_TO_LPS \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value },*/\ + {0x0522, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0x7F},/*Tx Pause*/ \ + {0x05F8, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*zero if no pkt is tx*/\ + PWR_BASEADDR_MAC, PWR_CMD_POLLING, 0xFF, 0}, \ + {0x05F9, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*Should be zero if no packet is transmitting*/ \ + PWR_BASEADDR_MAC, PWR_CMD_POLLING, 0xFF, 0}, \ + {0x05FA, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*Should be zero if no packet is transmitting*/ \ + PWR_BASEADDR_MAC, PWR_CMD_POLLING, 0xFF, 0}, \ + {0x05FB, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*Should be zero if no packet is transmitting*/ \ + PWR_BASEADDR_MAC, PWR_CMD_POLLING, 0xFF, 0}, \ + {0x0002, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*CCK and OFDM are disabled, and clock are gated*/ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(0), 0}, \ + {0x0002, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_DELAY, 0, PWRSEQ_DELAY_US},/*Delay 1us*/\ + {0x0100, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0x3F},/*Reset MAC TRX*/ \ + {0x0101, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*check if removed later*/ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(1), 0}, \ + {0x0553, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*Respond TxOK to scheduler*/ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(5), BIT(5)}, \ + + +#define RTL8188E_TRANS_LPS_TO_ACT \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, */\ + {0x0080, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + PWR_BASEADDR_SDIO, PWR_CMD_WRITE, 0xFF, 0x84}, /*SDIO RPWM*/ \ + {0xFE58, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_USB_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0x84}, /*USB RPWM*/ \ + {0x0361, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_PCI_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0x84}, /*PCIe RPWM*/ \ + {0x0002, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_DELAY, 0, PWRSEQ_DELAY_MS}, /*Delay*/ \ + {0x0008, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*. 0x08[4] = 0 switch TSF to 40M*/ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(4), 0}, \ + {0x0109, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*Polling 0x109[7]= 0 TSF in 40M*/ \ + PWR_BASEADDR_MAC, PWR_CMD_POLLING, BIT(7), 0}, \ + {0x0029, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*. 0x29[7:6] = 2b'00 enable BB clock*/ \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(6)|BIT(7), 0}, \ + {0x0101, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*. 0x101[1] = 1*/\ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(1), BIT(1)}, \ + {0x0100, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*. 0x100[7:0] = 0xFF enable WMAC TRX*/\ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0xFF}, \ + {0x0002, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + /*. 0x02[1:0] = 2b'11 enable BB macro*/\ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(1)|BIT(0), BIT(1)|BIT(0)}, \ + {0x0522, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK,\ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0}, /*. 0x522 = 0*/ + + +#define RTL8188E_TRANS_END \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value },*/\ + {0xFFFF, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK,\ + 0, PWR_CMD_END, 0, 0} + +extern struct wlan_pwr_cfg rtl8188e_power_on_flow + [RTL8188E_TRANS_CARDEMU_TO_ACT_STEPS + + RTL8188E_TRANS_END_STEPS]; +extern struct wlan_pwr_cfg rtl8188e_radio_off_flow + [RTL8188E_TRANS_ACT_TO_CARDEMU_STEPS + + RTL8188E_TRANS_END_STEPS]; +extern struct wlan_pwr_cfg rtl8188e_card_disable_flow + [RTL8188E_TRANS_ACT_TO_CARDEMU_STEPS + + RTL8188E_TRANS_CARDEMU_TO_PDN_STEPS + + RTL8188E_TRANS_END_STEPS]; +extern struct wlan_pwr_cfg rtl8188e_card_enable_flow + [RTL8188E_TRANS_ACT_TO_CARDEMU_STEPS + + RTL8188E_TRANS_CARDEMU_TO_PDN_STEPS + + RTL8188E_TRANS_END_STEPS]; +extern struct wlan_pwr_cfg rtl8188e_suspend_flow + [RTL8188E_TRANS_ACT_TO_CARDEMU_STEPS + + RTL8188E_TRANS_CARDEMU_TO_SUS_STEPS + + RTL8188E_TRANS_END_STEPS]; +extern struct wlan_pwr_cfg rtl8188e_resume_flow + [RTL8188E_TRANS_ACT_TO_CARDEMU_STEPS + + RTL8188E_TRANS_CARDEMU_TO_SUS_STEPS + + RTL8188E_TRANS_END_STEPS]; +extern struct wlan_pwr_cfg rtl8188e_hwpdn_flow + [RTL8188E_TRANS_ACT_TO_CARDEMU_STEPS + + RTL8188E_TRANS_CARDEMU_TO_PDN_STEPS + + RTL8188E_TRANS_END_STEPS]; +extern struct wlan_pwr_cfg rtl8188e_enter_lps_flow + [RTL8188E_TRANS_ACT_TO_LPS_STEPS + + RTL8188E_TRANS_END_STEPS]; +extern struct wlan_pwr_cfg rtl8188e_leave_lps_flow + [RTL8188E_TRANS_LPS_TO_ACT_STEPS + + RTL8188E_TRANS_END_STEPS]; + +/* RTL8723 Power Configuration CMDs for PCIe interface */ +#define Rtl8188E_NIC_PWR_ON_FLOW rtl8188e_power_on_flow +#define Rtl8188E_NIC_RF_OFF_FLOW rtl8188e_radio_off_flow +#define Rtl8188E_NIC_DISABLE_FLOW rtl8188e_card_disable_flow +#define Rtl8188E_NIC_ENABLE_FLOW rtl8188e_card_enable_flow +#define Rtl8188E_NIC_SUSPEND_FLOW rtl8188e_suspend_flow +#define Rtl8188E_NIC_RESUME_FLOW rtl8188e_resume_flow +#define Rtl8188E_NIC_PDN_FLOW rtl8188e_hwpdn_flow +#define Rtl8188E_NIC_LPS_ENTER_FLOW rtl8188e_enter_lps_flow +#define Rtl8188E_NIC_LPS_LEAVE_FLOW rtl8188e_leave_lps_flow + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/pwrseqcmd.c b/drivers/net/wireless/rtlwifi/rtl8188ee/pwrseqcmd.c new file mode 100644 index 0000000..a9cfa13 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/pwrseqcmd.c @@ -0,0 +1,140 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "pwrseq.h" + + +/* Description: + * This routine deal with the Power Configuration CMDs + * parsing for RTL8723/RTL8188E Series IC. + * Assumption: + * We should follow specific format which was released from HW SD. + * + * 2011.07.07, added by Roger. + */ + +bool rtl88_hal_pwrseqcmdparsing(struct rtl_priv *rtlpriv, u8 cut_version, + u8 fab_version, u8 interface_type, + struct wlan_pwr_cfg pwrcfgcmd[]) +{ + struct wlan_pwr_cfg cmd = {0}; + bool polling_bit = false; + u32 ary_idx = 0; + u8 val = 0; + u32 offset = 0; + u32 polling_count = 0; + u32 max_polling_cnt = 5000; + + do { + cmd = pwrcfgcmd[ary_idx]; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "rtl88_hal_pwrseqcmdparsing(): offset(%#x), cut_msk(%#x), fab_msk(%#x)," + "interface_msk(%#x), base(%#x), cmd(%#x), msk(%#x), val(%#x)\n", + GET_PWR_CFG_OFFSET(cmd), + GET_PWR_CFG_CUT_MASK(cmd), + GET_PWR_CFG_FAB_MASK(cmd), + GET_PWR_CFG_INTF_MASK(cmd), + GET_PWR_CFG_BASE(cmd), + GET_PWR_CFG_CMD(cmd), + GET_PWR_CFG_MASK(cmd), + GET_PWR_CFG_VALUE(cmd)); + + if ((GET_PWR_CFG_FAB_MASK(cmd) & fab_version) && + (GET_PWR_CFG_CUT_MASK(cmd) & cut_version) && + (GET_PWR_CFG_INTF_MASK(cmd) & interface_type)) { + switch (GET_PWR_CFG_CMD(cmd)) { + case PWR_CMD_READ: + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "rtl88_hal_pwrseqcmdparsing(): PWR_CMD_READ\n"); + break; + case PWR_CMD_WRITE: { + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "rtl88_hal_pwrseqcmdparsing(): PWR_CMD_WRITE\n"); + offset = GET_PWR_CFG_OFFSET(cmd); + + /*Read the val from system register*/ + val = rtl_read_byte(rtlpriv, offset); + val &= (~(GET_PWR_CFG_MASK(cmd))); + val |= (GET_PWR_CFG_VALUE(cmd) & + GET_PWR_CFG_MASK(cmd)); + + /*Write the val back to sytem register*/ + rtl_write_byte(rtlpriv, offset, val); + } + break; + case PWR_CMD_POLLING: + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "rtl88_hal_pwrseqcmdparsing(): PWR_CMD_POLLING\n"); + polling_bit = false; + offset = GET_PWR_CFG_OFFSET(cmd); + + do { + val = rtl_read_byte(rtlpriv, offset); + + val = val & GET_PWR_CFG_MASK(cmd); + if (val == (GET_PWR_CFG_VALUE(cmd) & + GET_PWR_CFG_MASK(cmd))) + polling_bit = true; + else + udelay(10); + + if (polling_count++ > max_polling_cnt) { + RT_TRACE(rtlpriv, COMP_INIT, + DBG_LOUD, + "polling fail in pwrseqcmd\n"); + return false; + } + } while (!polling_bit); + + break; + case PWR_CMD_DELAY: + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "rtl88_hal_pwrseqcmdparsing(): PWR_CMD_DELAY\n"); + if (GET_PWR_CFG_VALUE(cmd) == PWRSEQ_DELAY_US) + udelay(GET_PWR_CFG_OFFSET(cmd)); + else + mdelay(GET_PWR_CFG_OFFSET(cmd)); + break; + case PWR_CMD_END: + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "rtl88_hal_pwrseqcmdparsing(): PWR_CMD_END\n"); + return true; + break; + default: + RT_ASSERT(false, + "rtl88_hal_pwrseqcmdparsing(): Unknown CMD!!\n"); + break; + } + } + + ary_idx++; + } while (1); + + return true; +} diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/pwrseqcmd.h b/drivers/net/wireless/rtlwifi/rtl8188ee/pwrseqcmd.h new file mode 100644 index 0000000..d9ae280 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/pwrseqcmd.h @@ -0,0 +1,97 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL8723E_PWRSEQCMD_H__ +#define __RTL8723E_PWRSEQCMD_H__ + +#include "../wifi.h" +/*---------------------------------------------*/ +/* The value of cmd: 4 bits */ +/*---------------------------------------------*/ +#define PWR_CMD_READ 0x00 +#define PWR_CMD_WRITE 0x01 +#define PWR_CMD_POLLING 0x02 +#define PWR_CMD_DELAY 0x03 +#define PWR_CMD_END 0x04 + +/* define the base address of each block */ +#define PWR_BASEADDR_MAC 0x00 +#define PWR_BASEADDR_USB 0x01 +#define PWR_BASEADDR_PCIE 0x02 +#define PWR_BASEADDR_SDIO 0x03 + +#define PWR_INTF_SDIO_MSK BIT(0) +#define PWR_INTF_USB_MSK BIT(1) +#define PWR_INTF_PCI_MSK BIT(2) +#define PWR_INTF_ALL_MSK (BIT(0)|BIT(1)|BIT(2)|BIT(3)) + +#define PWR_FAB_TSMC_MSK BIT(0) +#define PWR_FAB_UMC_MSK BIT(1) +#define PWR_FAB_ALL_MSK (BIT(0)|BIT(1)|BIT(2)|BIT(3)) + +#define PWR_CUT_TESTCHIP_MSK BIT(0) +#define PWR_CUT_A_MSK BIT(1) +#define PWR_CUT_B_MSK BIT(2) +#define PWR_CUT_C_MSK BIT(3) +#define PWR_CUT_D_MSK BIT(4) +#define PWR_CUT_E_MSK BIT(5) +#define PWR_CUT_F_MSK BIT(6) +#define PWR_CUT_G_MSK BIT(7) +#define PWR_CUT_ALL_MSK 0xFF + +enum pwrseq_delay_unit { + PWRSEQ_DELAY_US, + PWRSEQ_DELAY_MS, +}; + +struct wlan_pwr_cfg { + u16 offset; + u8 cut_msk; + u8 fab_msk:4; + u8 interface_msk:4; + u8 base:4; + u8 cmd:4; + u8 msk; + u8 value; +}; + +#define GET_PWR_CFG_OFFSET(__PWR) (__PWR.offset) +#define GET_PWR_CFG_CUT_MASK(__PWR) (__PWR.cut_msk) +#define GET_PWR_CFG_FAB_MASK(__PWR) (__PWR.fab_msk) +#define GET_PWR_CFG_INTF_MASK(__PWR) (__PWR.interface_msk) +#define GET_PWR_CFG_BASE(__PWR) (__PWR.base) +#define GET_PWR_CFG_CMD(__PWR) (__PWR.cmd) +#define GET_PWR_CFG_MASK(__PWR) (__PWR.msk) +#define GET_PWR_CFG_VALUE(__PWR) (__PWR.value) + +bool rtl88_hal_pwrseqcmdparsing(struct rtl_priv *rtlpriv, u8 cut_version, + u8 fab_version, u8 interface_type, + struct wlan_pwr_cfg pwrcfgcmd[]); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/reg.h b/drivers/net/wireless/rtlwifi/rtl8188ee/reg.h new file mode 100644 index 0000000..d849abf --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/reg.h @@ -0,0 +1,2258 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92C_REG_H__ +#define __RTL92C_REG_H__ + +#define TXPKT_BUF_SELECT 0x69 +#define RXPKT_BUF_SELECT 0xA5 +#define DISABLE_TRXPKT_BUF_ACCESS 0x0 + +#define REG_SYS_ISO_CTRL 0x0000 +#define REG_SYS_FUNC_EN 0x0002 +#define REG_APS_FSMCO 0x0004 +#define REG_SYS_CLKR 0x0008 +#define REG_9346CR 0x000A +#define REG_EE_VPD 0x000C +#define REG_AFE_MISC 0x0010 +#define REG_SPS0_CTRL 0x0011 +#define REG_SPS_OCP_CFG 0x0018 +#define REG_RSV_CTRL 0x001C +#define REG_RF_CTRL 0x001F +#define REG_LDOA15_CTRL 0x0020 +#define REG_LDOV12D_CTRL 0x0021 +#define REG_LDOHCI12_CTRL 0x0022 +#define REG_LPLDO_CTRL 0x0023 +#define REG_AFE_XTAL_CTRL 0x0024 +#define REG_AFE_LDO_CTRL 0x0027 /* 1.5v for 8188EE test + * chip, 1.4v for MP chip + */ +#define REG_AFE_PLL_CTRL 0x0028 +#define REG_EFUSE_CTRL 0x0030 +#define REG_EFUSE_TEST 0x0034 +#define REG_PWR_DATA 0x0038 +#define REG_CAL_TIMER 0x003C +#define REG_ACLK_MON 0x003E +#define REG_GPIO_MUXCFG 0x0040 +#define REG_GPIO_IO_SEL 0x0042 +#define REG_MAC_PINMUX_CFG 0x0043 +#define REG_GPIO_PIN_CTRL 0x0044 +#define REG_GPIO_INTM 0x0048 +#define REG_LEDCFG0 0x004C +#define REG_LEDCFG1 0x004D +#define REG_LEDCFG2 0x004E +#define REG_LEDCFG3 0x004F +#define REG_FSIMR 0x0050 +#define REG_FSISR 0x0054 +#define REG_HSIMR 0x0058 +#define REG_HSISR 0x005c +#define REG_GPIO_PIN_CTRL_2 0x0060 +#define REG_GPIO_IO_SEL_2 0x0062 +#define REG_GPIO_OUTPUT 0x006c +#define REG_AFE_XTAL_CTRL_EXT 0x0078 +#define REG_XCK_OUT_CTRL 0x007c +#define REG_MCUFWDL 0x0080 +#define REG_WOL_EVENT 0x0081 +#define REG_MCUTSTCFG 0x0084 + + +#define REG_HIMR 0x00B0 +#define REG_HISR 0x00B4 +#define REG_HIMRE 0x00B8 +#define REG_HISRE 0x00BC + +#define REG_EFUSE_ACCESS 0x00CF + +#define REG_BIST_SCAN 0x00D0 +#define REG_BIST_RPT 0x00D4 +#define REG_BIST_ROM_RPT 0x00D8 +#define REG_USB_SIE_INTF 0x00E0 +#define REG_PCIE_MIO_INTF 0x00E4 +#define REG_PCIE_MIO_INTD 0x00E8 +#define REG_HPON_FSM 0x00EC +#define REG_SYS_CFG 0x00F0 + +#define REG_CR 0x0100 +#define REG_PBP 0x0104 +#define REG_PKT_BUFF_ACCESS_CTRL 0x0106 +#define REG_TRXDMA_CTRL 0x010C +#define REG_TRXFF_BNDY 0x0114 +#define REG_TRXFF_STATUS 0x0118 +#define REG_RXFF_PTR 0x011C + +#define REG_CPWM 0x012F +#define REG_FWIMR 0x0130 +#define REG_FWISR 0x0134 +#define REG_PKTBUF_DBG_CTRL 0x0140 +#define REG_PKTBUF_DBG_DATA_L 0x0144 +#define REG_PKTBUF_DBG_DATA_H 0x0148 +#define REG_RXPKTBUF_CTRL (REG_PKTBUF_DBG_CTRL+2) + +#define REG_TC0_CTRL 0x0150 +#define REG_TC1_CTRL 0x0154 +#define REG_TC2_CTRL 0x0158 +#define REG_TC3_CTRL 0x015C +#define REG_TC4_CTRL 0x0160 +#define REG_TCUNIT_BASE 0x0164 +#define REG_MBIST_START 0x0174 +#define REG_MBIST_DONE 0x0178 +#define REG_MBIST_FAIL 0x017C +#define REG_32K_CTRL 0x0194 +#define REG_C2HEVT_MSG_NORMAL 0x01A0 +#define REG_C2HEVT_CLEAR 0x01AF +#define REG_C2HEVT_MSG_TEST 0x01B8 +#define REG_MCUTST_1 0x01c0 +#define REG_FMETHR 0x01C8 +#define REG_HMETFR 0x01CC +#define REG_HMEBOX_0 0x01D0 +#define REG_HMEBOX_1 0x01D4 +#define REG_HMEBOX_2 0x01D8 +#define REG_HMEBOX_3 0x01DC + +#define REG_LLT_INIT 0x01E0 +#define REG_BB_ACCEESS_CTRL 0x01E8 +#define REG_BB_ACCESS_DATA 0x01EC + +#define REG_HMEBOX_EXT_0 0x01F0 +#define REG_HMEBOX_EXT_1 0x01F4 +#define REG_HMEBOX_EXT_2 0x01F8 +#define REG_HMEBOX_EXT_3 0x01FC + +#define REG_RQPN 0x0200 +#define REG_FIFOPAGE 0x0204 +#define REG_TDECTRL 0x0208 +#define REG_TXDMA_OFFSET_CHK 0x020C +#define REG_TXDMA_STATUS 0x0210 +#define REG_RQPN_NPQ 0x0214 + +#define REG_RXDMA_AGG_PG_TH 0x0280 +#define REG_FW_UPD_RDPTR 0x0284 /* FW shall update this + * register before FW * write + * RXPKT_RELEASE_POLL to 1 + */ +#define REG_RXDMA_CONTROL 0x0286 /* Control the RX DMA.*/ +#define REG_RXPKT_NUM 0x0287 /* The number of packets + * in RXPKTBUF. + */ +#define REG_PCIE_CTRL_REG 0x0300 +#define REG_INT_MIG 0x0304 +#define REG_BCNQ_DESA 0x0308 +#define REG_HQ_DESA 0x0310 +#define REG_MGQ_DESA 0x0318 +#define REG_VOQ_DESA 0x0320 +#define REG_VIQ_DESA 0x0328 +#define REG_BEQ_DESA 0x0330 +#define REG_BKQ_DESA 0x0338 +#define REG_RX_DESA 0x0340 + +#define REG_DBI 0x0348 +#define REG_MDIO 0x0354 +#define REG_DBG_SEL 0x0360 +#define REG_PCIE_HRPWM 0x0361 +#define REG_PCIE_HCPWM 0x0363 +#define REG_UART_CTRL 0x0364 +#define REG_WATCH_DOG 0x0368 +#define REG_UART_TX_DESA 0x0370 +#define REG_UART_RX_DESA 0x0378 + + +#define REG_HDAQ_DESA_NODEF 0x0000 +#define REG_CMDQ_DESA_NODEF 0x0000 + +#define REG_VOQ_INFORMATION 0x0400 +#define REG_VIQ_INFORMATION 0x0404 +#define REG_BEQ_INFORMATION 0x0408 +#define REG_BKQ_INFORMATION 0x040C +#define REG_MGQ_INFORMATION 0x0410 +#define REG_HGQ_INFORMATION 0x0414 +#define REG_BCNQ_INFORMATION 0x0418 +#define REG_TXPKT_EMPTY 0x041A + + +#define REG_CPU_MGQ_INFORMATION 0x041C +#define REG_FWHW_TXQ_CTRL 0x0420 +#define REG_HWSEQ_CTRL 0x0423 +#define REG_TXPKTBUF_BCNQ_BDNY 0x0424 +#define REG_TXPKTBUF_MGQ_BDNY 0x0425 +#define REG_MULTI_BCNQ_EN 0x0426 +#define REG_MULTI_BCNQ_OFFSET 0x0427 +#define REG_SPEC_SIFS 0x0428 +#define REG_RL 0x042A +#define REG_DARFRC 0x0430 +#define REG_RARFRC 0x0438 +#define REG_RRSR 0x0440 +#define REG_ARFR0 0x0444 +#define REG_ARFR1 0x0448 +#define REG_ARFR2 0x044C +#define REG_ARFR3 0x0450 +#define REG_AGGLEN_LMT 0x0458 +#define REG_AMPDU_MIN_SPACE 0x045C +#define REG_TXPKTBUF_WMAC_LBK_BF_HD 0x045D +#define REG_FAST_EDCA_CTRL 0x0460 +#define REG_RD_RESP_PKT_TH 0x0463 +#define REG_INIRTS_RATE_SEL 0x0480 +#define REG_INIDATA_RATE_SEL 0x0484 +#define REG_POWER_STATUS 0x04A4 +#define REG_POWER_STAGE1 0x04B4 +#define REG_POWER_STAGE2 0x04B8 +#define REG_PKT_LIFE_TIME 0x04C0 +#define REG_STBC_SETTING 0x04C4 +#define REG_PROT_MODE_CTRL 0x04C8 +#define REG_BAR_MODE_CTRL 0x04CC +#define REG_RA_TRY_RATE_AGG_LMT 0x04CF +#define REG_EARLY_MODE_CONTROL 0x04D0 +#define REG_NQOS_SEQ 0x04DC +#define REG_QOS_SEQ 0x04DE +#define REG_NEED_CPU_HANDLE 0x04E0 +#define REG_PKT_LOSE_RPT 0x04E1 +#define REG_PTCL_ERR_STATUS 0x04E2 +#define REG_TX_RPT_CTRL 0x04EC +#define REG_TX_RPT_TIME 0x04F0 +#define REG_DUMMY 0x04FC + +#define REG_EDCA_VO_PARAM 0x0500 +#define REG_EDCA_VI_PARAM 0x0504 +#define REG_EDCA_BE_PARAM 0x0508 +#define REG_EDCA_BK_PARAM 0x050C +#define REG_BCNTCFG 0x0510 +#define REG_PIFS 0x0512 +#define REG_RDG_PIFS 0x0513 +#define REG_SIFS_CTX 0x0514 +#define REG_SIFS_TRX 0x0516 +#define REG_AGGR_BREAK_TIME 0x051A +#define REG_SLOT 0x051B +#define REG_TX_PTCL_CTRL 0x0520 +#define REG_TXPAUSE 0x0522 +#define REG_DIS_TXREQ_CLR 0x0523 +#define REG_RD_CTRL 0x0524 +#define REG_TBTT_PROHIBIT 0x0540 +#define REG_RD_NAV_NXT 0x0544 +#define REG_NAV_PROT_LEN 0x0546 +#define REG_BCN_CTRL 0x0550 +#define REG_USTIME_TSF 0x0551 +#define REG_MBID_NUM 0x0552 +#define REG_DUAL_TSF_RST 0x0553 +#define REG_BCN_INTERVAL 0x0554 +#define REG_MBSSID_BCN_SPACE 0x0554 +#define REG_DRVERLYINT 0x0558 +#define REG_BCNDMATIM 0x0559 +#define REG_ATIMWND 0x055A +#define REG_BCN_MAX_ERR 0x055D +#define REG_RXTSF_OFFSET_CCK 0x055E +#define REG_RXTSF_OFFSET_OFDM 0x055F +#define REG_TSFTR 0x0560 +#define REG_INIT_TSFTR 0x0564 +#define REG_PSTIMER 0x0580 +#define REG_TIMER0 0x0584 +#define REG_TIMER1 0x0588 +#define REG_ACMHWCTRL 0x05C0 +#define REG_ACMRSTCTRL 0x05C1 +#define REG_ACMAVG 0x05C2 +#define REG_VO_ADMTIME 0x05C4 +#define REG_VI_ADMTIME 0x05C6 +#define REG_BE_ADMTIME 0x05C8 +#define REG_EDCA_RANDOM_GEN 0x05CC +#define REG_SCH_TXCMD 0x05D0 + +#define REG_APSD_CTRL 0x0600 +#define REG_BWOPMODE 0x0603 +#define REG_TCR 0x0604 +#define REG_RCR 0x0608 +#define REG_RX_PKT_LIMIT 0x060C +#define REG_RX_DLK_TIME 0x060D +#define REG_RX_DRVINFO_SZ 0x060F + +#define REG_MACID 0x0610 +#define REG_BSSID 0x0618 +#define REG_MAR 0x0620 +#define REG_MBIDCAMCFG 0x0628 + +#define REG_USTIME_EDCA 0x0638 +#define REG_MAC_SPEC_SIFS 0x063A +#define REG_RESP_SIFS_CCK 0x063C +#define REG_RESP_SIFS_OFDM 0x063E +#define REG_ACKTO 0x0640 +#define REG_CTS2TO 0x0641 +#define REG_EIFS 0x0642 + +#define REG_NAV_CTRL 0x0650 +#define REG_BACAMCMD 0x0654 +#define REG_BACAMCONTENT 0x0658 +#define REG_LBDLY 0x0660 +#define REG_FWDLY 0x0661 +#define REG_RXERR_RPT 0x0664 +#define REG_TRXPTCL_CTL 0x0668 + +#define REG_CAMCMD 0x0670 +#define REG_CAMWRITE 0x0674 +#define REG_CAMREAD 0x0678 +#define REG_CAMDBG 0x067C +#define REG_SECCFG 0x0680 + +#define REG_WOW_CTRL 0x0690 +#define REG_PSSTATUS 0x0691 +#define REG_PS_RX_INFO 0x0692 +#define REG_UAPSD_TID 0x0693 +#define REG_LPNAV_CTRL 0x0694 +#define REG_WKFMCAM_NUM 0x0698 +#define REG_WKFMCAM_RWD 0x069C +#define REG_RXFLTMAP0 0x06A0 +#define REG_RXFLTMAP1 0x06A2 +#define REG_RXFLTMAP2 0x06A4 +#define REG_BCN_PSR_RPT 0x06A8 +#define REG_CALB32K_CTRL 0x06AC +#define REG_PKT_MON_CTRL 0x06B4 +#define REG_BT_COEX_TABLE 0x06C0 +#define REG_WMAC_RESP_TXINFO 0x06D8 + +#define REG_USB_INFO 0xFE17 +#define REG_USB_SPECIAL_OPTION 0xFE55 +#define REG_USB_DMA_AGG_TO 0xFE5B +#define REG_USB_AGG_TO 0xFE5C +#define REG_USB_AGG_TH 0xFE5D + +#define REG_TEST_USB_TXQS 0xFE48 +#define REG_TEST_SIE_VID 0xFE60 +#define REG_TEST_SIE_PID 0xFE62 +#define REG_TEST_SIE_OPTIONAL 0xFE64 +#define REG_TEST_SIE_CHIRP_K 0xFE65 +#define REG_TEST_SIE_PHY 0xFE66 +#define REG_TEST_SIE_MAC_ADDR 0xFE70 +#define REG_TEST_SIE_STRING 0xFE80 + +#define REG_NORMAL_SIE_VID 0xFE60 +#define REG_NORMAL_SIE_PID 0xFE62 +#define REG_NORMAL_SIE_OPTIONAL 0xFE64 +#define REG_NORMAL_SIE_EP 0xFE65 +#define REG_NORMAL_SIE_PHY 0xFE68 +#define REG_NORMAL_SIE_MAC_ADDR 0xFE70 +#define REG_NORMAL_SIE_STRING 0xFE80 + +#define CR9346 REG_9346CR +#define MSR (REG_CR + 2) +#define ISR REG_HISR +#define TSFR REG_TSFTR + +#define MACIDR0 REG_MACID +#define MACIDR4 (REG_MACID + 4) + +#define PBP REG_PBP + +#define IDR0 MACIDR0 +#define IDR4 MACIDR4 + +#define UNUSED_REGISTER 0x1BF +#define DCAM UNUSED_REGISTER +#define PSR UNUSED_REGISTER +#define BBADDR UNUSED_REGISTER +#define PHYDATAR UNUSED_REGISTER + +#define INVALID_BBRF_VALUE 0x12345678 + +#define MAX_MSS_DENSITY_2T 0x13 +#define MAX_MSS_DENSITY_1T 0x0A + +#define CMDEEPROM_EN BIT(5) +#define CMDEEPROM_SEL BIT(4) +#define CMD9346CR_9356SEL BIT(4) +#define AUTOLOAD_EEPROM (CMDEEPROM_EN|CMDEEPROM_SEL) +#define AUTOLOAD_EFUSE CMDEEPROM_EN + +#define GPIOSEL_GPIO 0 +#define GPIOSEL_ENBT BIT(5) + +#define GPIO_IN REG_GPIO_PIN_CTRL +#define GPIO_OUT (REG_GPIO_PIN_CTRL+1) +#define GPIO_IO_SEL (REG_GPIO_PIN_CTRL+2) +#define GPIO_MOD (REG_GPIO_PIN_CTRL+3) + +/* 8723/8188E Host System Interrupt Mask Register (offset 0x58, 32 byte) */ +#define HSIMR_GPIO12_0_INT_EN BIT(0) +#define HSIMR_SPS_OCP_INT_EN BIT(5) +#define HSIMR_RON_INT_EN BIT(6) +#define HSIMR_PDN_INT_EN BIT(7) +#define HSIMR_GPIO9_INT_EN BIT(25) + + +/* 8723/8188E Host System Interrupt Status Register (offset 0x5C, 32 byte) */ +#define HSISR_GPIO12_0_INT BIT(0) +#define HSISR_SPS_OCP_INT BIT(5) +#define HSISR_RON_INT_EN BIT(6) +#define HSISR_PDNINT BIT(7) +#define HSISR_GPIO9_INT BIT(25) + +#define MSR_NOLINK 0x00 +#define MSR_ADHOC 0x01 +#define MSR_INFRA 0x02 +#define MSR_AP 0x03 + +#define RRSR_RSC_OFFSET 21 +#define RRSR_SHORT_OFFSET 23 +#define RRSR_RSC_BW_40M 0x600000 +#define RRSR_RSC_UPSUBCHNL 0x400000 +#define RRSR_RSC_LOWSUBCHNL 0x200000 +#define RRSR_SHORT 0x800000 +#define RRSR_1M BIT(0) +#define RRSR_2M BIT(1) +#define RRSR_5_5M BIT(2) +#define RRSR_11M BIT(3) +#define RRSR_6M BIT(4) +#define RRSR_9M BIT(5) +#define RRSR_12M BIT(6) +#define RRSR_18M BIT(7) +#define RRSR_24M BIT(8) +#define RRSR_36M BIT(9) +#define RRSR_48M BIT(10) +#define RRSR_54M BIT(11) +#define RRSR_MCS0 BIT(12) +#define RRSR_MCS1 BIT(13) +#define RRSR_MCS2 BIT(14) +#define RRSR_MCS3 BIT(15) +#define RRSR_MCS4 BIT(16) +#define RRSR_MCS5 BIT(17) +#define RRSR_MCS6 BIT(18) +#define RRSR_MCS7 BIT(19) +#define BRSR_ACKSHORTPMB BIT(23) + +#define RATR_1M 0x00000001 +#define RATR_2M 0x00000002 +#define RATR_55M 0x00000004 +#define RATR_11M 0x00000008 +#define RATR_6M 0x00000010 +#define RATR_9M 0x00000020 +#define RATR_12M 0x00000040 +#define RATR_18M 0x00000080 +#define RATR_24M 0x00000100 +#define RATR_36M 0x00000200 +#define RATR_48M 0x00000400 +#define RATR_54M 0x00000800 +#define RATR_MCS0 0x00001000 +#define RATR_MCS1 0x00002000 +#define RATR_MCS2 0x00004000 +#define RATR_MCS3 0x00008000 +#define RATR_MCS4 0x00010000 +#define RATR_MCS5 0x00020000 +#define RATR_MCS6 0x00040000 +#define RATR_MCS7 0x00080000 +#define RATR_MCS8 0x00100000 +#define RATR_MCS9 0x00200000 +#define RATR_MCS10 0x00400000 +#define RATR_MCS11 0x00800000 +#define RATR_MCS12 0x01000000 +#define RATR_MCS13 0x02000000 +#define RATR_MCS14 0x04000000 +#define RATR_MCS15 0x08000000 + +#define RATE_1M BIT(0) +#define RATE_2M BIT(1) +#define RATE_5_5M BIT(2) +#define RATE_11M BIT(3) +#define RATE_6M BIT(4) +#define RATE_9M BIT(5) +#define RATE_12M BIT(6) +#define RATE_18M BIT(7) +#define RATE_24M BIT(8) +#define RATE_36M BIT(9) +#define RATE_48M BIT(10) +#define RATE_54M BIT(11) +#define RATE_MCS0 BIT(12) +#define RATE_MCS1 BIT(13) +#define RATE_MCS2 BIT(14) +#define RATE_MCS3 BIT(15) +#define RATE_MCS4 BIT(16) +#define RATE_MCS5 BIT(17) +#define RATE_MCS6 BIT(18) +#define RATE_MCS7 BIT(19) +#define RATE_MCS8 BIT(20) +#define RATE_MCS9 BIT(21) +#define RATE_MCS10 BIT(22) +#define RATE_MCS11 BIT(23) +#define RATE_MCS12 BIT(24) +#define RATE_MCS13 BIT(25) +#define RATE_MCS14 BIT(26) +#define RATE_MCS15 BIT(27) + +#define RATE_ALL_CCK (RATR_1M | RATR_2M | RATR_55M | RATR_11M) +#define RATE_ALL_OFDM_AG (RATR_6M | RATR_9M | RATR_12M | RATR_18M | \ + RATR_24M | RATR_36M | RATR_48M | RATR_54M) +#define RATE_ALL_OFDM_1SS (RATR_MCS0 | RATR_MCS1 | RATR_MCS2 | \ + RATR_MCS3 | RATR_MCS4 | RATR_MCS5 | \ + RATR_MCS6 | RATR_MCS7) +#define RATE_ALL_OFDM_2SS (RATR_MCS8 | RATR_MCS9 | RATR_MCS10 | \ + RATR_MCS11 | RATR_MCS12 | RATR_MCS13 | \ + RATR_MCS14 | RATR_MCS15) + +#define BW_OPMODE_20MHZ BIT(2) +#define BW_OPMODE_5G BIT(1) +#define BW_OPMODE_11J BIT(0) + +#define CAM_VALID BIT(15) +#define CAM_NOTVALID 0x0000 +#define CAM_USEDK BIT(5) + +#define CAM_NONE 0x0 +#define CAM_WEP40 0x01 +#define CAM_TKIP 0x02 +#define CAM_AES 0x04 +#define CAM_WEP104 0x05 + +#define TOTAL_CAM_ENTRY 32 +#define HALF_CAM_ENTRY 16 + +#define CAM_WRITE BIT(16) +#define CAM_READ 0x00000000 +#define CAM_POLLINIG BIT(31) + +#define SCR_USEDK 0x01 +#define SCR_TXSEC_ENABLE 0x02 +#define SCR_RXSEC_ENABLE 0x04 + +#define WOW_PMEN BIT(0) +#define WOW_WOMEN BIT(1) +#define WOW_MAGIC BIT(2) +#define WOW_UWF BIT(3) + +/********************************************* +* 8188 IMR/ISR bits +**********************************************/ +#define IMR_DISABLED 0x0 +/* IMR DW0(0x0060-0063) Bit 0-31 */ +#define IMR_TXCCK BIT(30) /* TXRPT interrupt when CCX bit of + * the packet is set + */ +#define IMR_PSTIMEOUT BIT(29) /* Power Save Time Out Interrupt */ +#define IMR_GTINT4 BIT(28) /* When GTIMER4 expires, + * this bit is set to 1 + */ +#define IMR_GTINT3 BIT(27) /* When GTIMER3 expires, + * this bit is set to 1 + */ +#define IMR_TBDER BIT(26) /* Transmit Beacon0 Error */ +#define IMR_TBDOK BIT(25) /* Transmit Beacon0 OK */ +#define IMR_TSF_BIT32_TOGGLE BIT(24) /* TSF Timer BIT32 toggle ind int */ +#define IMR_BCNDMAINT0 BIT(20) /* Beacon DMA Interrupt 0 */ +#define IMR_BCNDOK0 BIT(16) /* Beacon Queue DMA OK0 */ +#define IMR_HSISR_IND_ON_INT BIT(15) /* HSISR Indicator (HSIMR & HSISR is + * true, this bit is set to 1) + */ +#define IMR_BCNDMAINT_E BIT(14) /* Beacon DMA Int Extension for Win7 */ +#define IMR_ATIMEND BIT(12) /* CTWidnow End or ATIM Window End */ +#define IMR_HISR1_IND_INT BIT(11) /* HISR1 Indicator (HISR1 & HIMR1 is + * true, this bit is set to 1) + */ +#define IMR_C2HCMD BIT(10) /* CPU to Host Command INT Status, + * Write 1 clear + */ +#define IMR_CPWM2 BIT(9) /* CPU power Mode exchange INT Status, + * Write 1 clear + */ +#define IMR_CPWM BIT(8) /* CPU power Mode exchange INT Status, + * Write 1 clear + */ +#define IMR_HIGHDOK BIT(7) /* High Queue DMA OK */ +#define IMR_MGNTDOK BIT(6) /* Management Queue DMA OK */ +#define IMR_BKDOK BIT(5) /* AC_BK DMA OK */ +#define IMR_BEDOK BIT(4) /* AC_BE DMA OK */ +#define IMR_VIDOK BIT(3) /* AC_VI DMA OK */ +#define IMR_VODOK BIT(2) /* AC_VO DMA OK */ +#define IMR_RDU BIT(1) /* Rx Descriptor Unavailable */ +#define IMR_ROK BIT(0) /* Receive DMA OK */ + +/* IMR DW1(0x00B4-00B7) Bit 0-31 */ +#define IMR_BCNDMAINT7 BIT(27) /* Beacon DMA Interrupt 7 */ +#define IMR_BCNDMAINT6 BIT(26) /* Beacon DMA Interrupt 6 */ +#define IMR_BCNDMAINT5 BIT(25) /* Beacon DMA Interrupt 5 */ +#define IMR_BCNDMAINT4 BIT(24) /* Beacon DMA Interrupt 4 */ +#define IMR_BCNDMAINT3 BIT(23) /* Beacon DMA Interrupt 3 */ +#define IMR_BCNDMAINT2 BIT(22) /* Beacon DMA Interrupt 2 */ +#define IMR_BCNDMAINT1 BIT(21) /* Beacon DMA Interrupt 1 */ +#define IMR_BCNDOK7 BIT(20) /* Beacon Queue DMA OK Interrup 7 */ +#define IMR_BCNDOK6 BIT(19) /* Beacon Queue DMA OK Interrup 6 */ +#define IMR_BCNDOK5 BIT(18) /* Beacon Queue DMA OK Interrup 5 */ +#define IMR_BCNDOK4 BIT(17) /* Beacon Queue DMA OK Interrup 4 */ +#define IMR_BCNDOK3 BIT(16) /* Beacon Queue DMA OK Interrup 3 */ +#define IMR_BCNDOK2 BIT(15) /* Beacon Queue DMA OK Interrup 2 */ +#define IMR_BCNDOK1 BIT(14) /* Beacon Queue DMA OK Interrup 1 */ +#define IMR_ATIMEND_E BIT(13) /* ATIM Window End Extension for Win7 */ +#define IMR_TXERR BIT(11) /* Tx Err Flag Int Status, + * write 1 clear. + */ +#define IMR_RXERR BIT(10) /* Rx Err Flag INT Status, + * Write 1 clear + */ +#define IMR_TXFOVW BIT(9) /* Transmit FIFO Overflow */ +#define IMR_RXFOVW BIT(8) /* Receive FIFO Overflow */ + + +#define HWSET_MAX_SIZE 512 +#define EFUSE_MAX_SECTION 64 +#define EFUSE_REAL_CONTENT_LEN 256 +#define EFUSE_OOB_PROTECT_BYTES 18 /* PG data exclude header, + * dummy 7 bytes frome CP + * test and reserved 1byte. + */ + +#define EEPROM_DEFAULT_TSSI 0x0 +#define EEPROM_DEFAULT_TXPOWERDIFF 0x0 +#define EEPROM_DEFAULT_CRYSTALCAP 0x5 +#define EEPROM_DEFAULT_BOARDTYPE 0x02 +#define EEPROM_DEFAULT_TXPOWER 0x1010 +#define EEPROM_DEFAULT_HT2T_TXPWR 0x10 + +#define EEPROM_DEFAULT_LEGACYHTTXPOWERDIFF 0x3 +#define EEPROM_DEFAULT_THERMALMETER 0x18 +#define EEPROM_DEFAULT_ANTTXPOWERDIFF 0x0 +#define EEPROM_DEFAULT_TXPWDIFF_CRYSTALCAP 0x5 +#define EEPROM_DEFAULT_TXPOWERLEVEL 0x22 +#define EEPROM_DEFAULT_HT40_2SDIFF 0x0 +#define EEPROM_DEFAULT_HT20_DIFF 2 +#define EEPROM_DEFAULT_LEGACYHTTXPOWERDIFF 0x3 +#define EEPROM_DEFAULT_HT40_PWRMAXOFFSET 0 +#define EEPROM_DEFAULT_HT20_PWRMAXOFFSET 0 + +#define RF_OPTION1 0x79 +#define RF_OPTION2 0x7A +#define RF_OPTION3 0x7B +#define RF_OPTION4 0x7C + +#define EEPROM_DEFAULT_PID 0x1234 +#define EEPROM_DEFAULT_VID 0x5678 +#define EEPROM_DEFAULT_CUSTOMERID 0xAB +#define EEPROM_DEFAULT_SUBCUSTOMERID 0xCD +#define EEPROM_DEFAULT_VERSION 0 + +#define EEPROM_CHANNEL_PLAN_FCC 0x0 +#define EEPROM_CHANNEL_PLAN_IC 0x1 +#define EEPROM_CHANNEL_PLAN_ETSI 0x2 +#define EEPROM_CHANNEL_PLAN_SPAIN 0x3 +#define EEPROM_CHANNEL_PLAN_FRANCE 0x4 +#define EEPROM_CHANNEL_PLAN_MKK 0x5 +#define EEPROM_CHANNEL_PLAN_MKK1 0x6 +#define EEPROM_CHANNEL_PLAN_ISRAEL 0x7 +#define EEPROM_CHANNEL_PLAN_TELEC 0x8 +#define EEPROM_CHANNEL_PLAN_GLOBAL_DOMAIN 0x9 +#define EEPROM_CHANNEL_PLAN_WORLD_WIDE_13 0xA +#define EEPROM_CHANNEL_PLAN_NCC 0xB +#define EEPROM_CHANNEL_PLAN_BY_HW_MASK 0x80 + +#define EEPROM_CID_DEFAULT 0x0 +#define EEPROM_CID_TOSHIBA 0x4 +#define EEPROM_CID_CCX 0x10 +#define EEPROM_CID_QMI 0x0D +#define EEPROM_CID_WHQL 0xFE + +#define RTL8188E_EEPROM_ID 0x8129 + +#define EEPROM_HPON 0x02 +#define EEPROM_CLK 0x06 +#define EEPROM_TESTR 0x08 + +#define EEPROM_TXPOWERCCK 0x10 +#define EEPROM_TXPOWERHT40_1S 0x16 +#define EEPROM_TXPOWERHT20DIFF 0x1B +#define EEPROM_TXPOWER_OFDMDIFF 0x1B + +#define EEPROM_TX_PWR_INX 0x10 + +#define EEPROM_CHANNELPLAN 0xB8 +#define EEPROM_XTAL_88E 0xB9 +#define EEPROM_THERMAL_METER_88E 0xBA +#define EEPROM_IQK_LCK_88E 0xBB + +#define EEPROM_RF_BOARD_OPTION_88E 0xC1 +#define EEPROM_RF_FEATURE_OPTION_88E 0xC2 +#define EEPROM_RF_BT_SETTING_88E 0xC3 +#define EEPROM_VERSION 0xC4 +#define EEPROM_CUSTOMER_ID 0xC5 +#define EEPROM_RF_ANTENNA_OPT_88E 0xC9 + +#define EEPROM_MAC_ADDR 0xD0 +#define EEPROM_VID 0xD6 +#define EEPROM_DID 0xD8 +#define EEPROM_SVID 0xDA +#define EEPROM_SMID 0xDC + +#define STOPBECON BIT(6) +#define STOPHIGHT BIT(5) +#define STOPMGT BIT(4) +#define STOPVO BIT(3) +#define STOPVI BIT(2) +#define STOPBE BIT(1) +#define STOPBK BIT(0) + +#define RCR_APPFCS BIT(31) +#define RCR_APP_MIC BIT(30) +#define RCR_APP_ICV BIT(29) +#define RCR_APP_PHYST_RXFF BIT(28) +#define RCR_APP_BA_SSN BIT(27) +#define RCR_ENMBID BIT(24) +#define RCR_LSIGEN BIT(23) +#define RCR_MFBEN BIT(22) +#define RCR_HTC_LOC_CTRL BIT(14) +#define RCR_AMF BIT(13) +#define RCR_ACF BIT(12) +#define RCR_ADF BIT(11) +#define RCR_AICV BIT(9) +#define RCR_ACRC32 BIT(8) +#define RCR_CBSSID_BCN BIT(7) +#define RCR_CBSSID_DATA BIT(6) +#define RCR_CBSSID RCR_CBSSID_DATA +#define RCR_APWRMGT BIT(5) +#define RCR_ADD3 BIT(4) +#define RCR_AB BIT(3) +#define RCR_AM BIT(2) +#define RCR_APM BIT(1) +#define RCR_AAP BIT(0) +#define RCR_MXDMA_OFFSET 8 +#define RCR_FIFO_OFFSET 13 + +#define RSV_CTRL 0x001C +#define RD_CTRL 0x0524 + +#define REG_USB_INFO 0xFE17 +#define REG_USB_SPECIAL_OPTION 0xFE55 +#define REG_USB_DMA_AGG_TO 0xFE5B +#define REG_USB_AGG_TO 0xFE5C +#define REG_USB_AGG_TH 0xFE5D + +#define REG_USB_VID 0xFE60 +#define REG_USB_PID 0xFE62 +#define REG_USB_OPTIONAL 0xFE64 +#define REG_USB_CHIRP_K 0xFE65 +#define REG_USB_PHY 0xFE66 +#define REG_USB_MAC_ADDR 0xFE70 +#define REG_USB_HRPWM 0xFE58 +#define REG_USB_HCPWM 0xFE57 + +#define SW18_FPWM BIT(3) + +#define ISO_MD2PP BIT(0) +#define ISO_UA2USB BIT(1) +#define ISO_UD2CORE BIT(2) +#define ISO_PA2PCIE BIT(3) +#define ISO_PD2CORE BIT(4) +#define ISO_IP2MAC BIT(5) +#define ISO_DIOP BIT(6) +#define ISO_DIOE BIT(7) +#define ISO_EB2CORE BIT(8) +#define ISO_DIOR BIT(9) + +#define PWC_EV25V BIT(14) +#define PWC_EV12V BIT(15) + +#define FEN_BBRSTB BIT(0) +#define FEN_BB_GLB_RSTN BIT(1) +#define FEN_USBA BIT(2) +#define FEN_UPLL BIT(3) +#define FEN_USBD BIT(4) +#define FEN_DIO_PCIE BIT(5) +#define FEN_PCIEA BIT(6) +#define FEN_PPLL BIT(7) +#define FEN_PCIED BIT(8) +#define FEN_DIOE BIT(9) +#define FEN_CPUEN BIT(10) +#define FEN_DCORE BIT(11) +#define FEN_ELDR BIT(12) +#define FEN_DIO_RF BIT(13) +#define FEN_HWPDN BIT(14) +#define FEN_MREGEN BIT(15) + +#define PFM_LDALL BIT(0) +#define PFM_ALDN BIT(1) +#define PFM_LDKP BIT(2) +#define PFM_WOWL BIT(3) +#define ENPDN BIT(4) +#define PDN_PL BIT(5) +#define APFM_ONMAC BIT(8) +#define APFM_OFF BIT(9) +#define APFM_RSM BIT(10) +#define AFSM_HSUS BIT(11) +#define AFSM_PCIE BIT(12) +#define APDM_MAC BIT(13) +#define APDM_HOST BIT(14) +#define APDM_HPDN BIT(15) +#define RDY_MACON BIT(16) +#define SUS_HOST BIT(17) +#define ROP_ALD BIT(20) +#define ROP_PWR BIT(21) +#define ROP_SPS BIT(22) +#define SOP_MRST BIT(25) +#define SOP_FUSE BIT(26) +#define SOP_ABG BIT(27) +#define SOP_AMB BIT(28) +#define SOP_RCK BIT(29) +#define SOP_A8M BIT(30) +#define XOP_BTCK BIT(31) + +#define ANAD16V_EN BIT(0) +#define ANA8M BIT(1) +#define MACSLP BIT(4) +#define LOADER_CLK_EN BIT(5) +#define _80M_SSC_DIS BIT(7) +#define _80M_SSC_EN_HO BIT(8) +#define PHY_SSC_RSTB BIT(9) +#define SEC_CLK_EN BIT(10) +#define MAC_CLK_EN BIT(11) +#define SYS_CLK_EN BIT(12) +#define RING_CLK_EN BIT(13) + +#define BOOT_FROM_EEPROM BIT(4) +#define EEPROM_EN BIT(5) + +#define AFE_BGEN BIT(0) +#define AFE_MBEN BIT(1) +#define MAC_ID_EN BIT(7) + +#define WLOCK_ALL BIT(0) +#define WLOCK_00 BIT(1) +#define WLOCK_04 BIT(2) +#define WLOCK_08 BIT(3) +#define WLOCK_40 BIT(4) +#define R_DIS_PRST_0 BIT(5) +#define R_DIS_PRST_1 BIT(6) +#define LOCK_ALL_EN BIT(7) + +#define RF_EN BIT(0) +#define RF_RSTB BIT(1) +#define RF_SDMRSTB BIT(2) + +#define LDA15_EN BIT(0) +#define LDA15_STBY BIT(1) +#define LDA15_OBUF BIT(2) +#define LDA15_REG_VOS BIT(3) +#define _LDA15_VOADJ(x) (((x) & 0x7) << 4) + +#define LDV12_EN BIT(0) +#define LDV12_SDBY BIT(1) +#define LPLDO_HSM BIT(2) +#define LPLDO_LSM_DIS BIT(3) +#define _LDV12_VADJ(x) (((x) & 0xF) << 4) + +#define XTAL_EN BIT(0) +#define XTAL_BSEL BIT(1) +#define _XTAL_BOSC(x) (((x) & 0x3) << 2) +#define _XTAL_CADJ(x) (((x) & 0xF) << 4) +#define XTAL_GATE_USB BIT(8) +#define _XTAL_USB_DRV(x) (((x) & 0x3) << 9) +#define XTAL_GATE_AFE BIT(11) +#define _XTAL_AFE_DRV(x) (((x) & 0x3) << 12) +#define XTAL_RF_GATE BIT(14) +#define _XTAL_RF_DRV(x) (((x) & 0x3) << 15) +#define XTAL_GATE_DIG BIT(17) +#define _XTAL_DIG_DRV(x) (((x) & 0x3) << 18) +#define XTAL_BT_GATE BIT(20) +#define _XTAL_BT_DRV(x) (((x) & 0x3) << 21) +#define _XTAL_GPIO(x) (((x) & 0x7) << 23) + +#define CKDLY_AFE BIT(26) +#define CKDLY_USB BIT(27) +#define CKDLY_DIG BIT(28) +#define CKDLY_BT BIT(29) + +#define APLL_EN BIT(0) +#define APLL_320_EN BIT(1) +#define APLL_FREF_SEL BIT(2) +#define APLL_EDGE_SEL BIT(3) +#define APLL_WDOGB BIT(4) +#define APLL_LPFEN BIT(5) + +#define APLL_REF_CLK_13MHZ 0x1 +#define APLL_REF_CLK_19_2MHZ 0x2 +#define APLL_REF_CLK_20MHZ 0x3 +#define APLL_REF_CLK_25MHZ 0x4 +#define APLL_REF_CLK_26MHZ 0x5 +#define APLL_REF_CLK_38_4MHZ 0x6 +#define APLL_REF_CLK_40MHZ 0x7 + +#define APLL_320EN BIT(14) +#define APLL_80EN BIT(15) +#define APLL_1MEN BIT(24) + +#define ALD_EN BIT(18) +#define EF_PD BIT(19) +#define EF_FLAG BIT(31) + +#define EF_TRPT BIT(7) +#define LDOE25_EN BIT(31) + +#define RSM_EN BIT(0) +#define TIMER_EN BIT(4) + +#define TRSW0EN BIT(2) +#define TRSW1EN BIT(3) +#define EROM_EN BIT(4) +#define ENBT BIT(5) +#define ENUART BIT(8) +#define UART_910 BIT(9) +#define ENPMAC BIT(10) +#define SIC_SWRST BIT(11) +#define ENSIC BIT(12) +#define SIC_23 BIT(13) +#define ENHDP BIT(14) +#define SIC_LBK BIT(15) + +#define LED0PL BIT(4) +#define LED1PL BIT(12) +#define LED0DIS BIT(7) + +#define MCUFWDL_EN BIT(0) +#define MCUFWDL_RDY BIT(1) +#define FWDL_CHKSUM_RPT BIT(2) +#define MACINI_RDY BIT(3) +#define BBINI_RDY BIT(4) +#define RFINI_RDY BIT(5) +#define WINTINI_RDY BIT(6) +#define CPRST BIT(23) + +#define XCLK_VLD BIT(0) +#define ACLK_VLD BIT(1) +#define UCLK_VLD BIT(2) +#define PCLK_VLD BIT(3) +#define PCIRSTB BIT(4) +#define V15_VLD BIT(5) +#define TRP_B15V_EN BIT(7) +#define SIC_IDLE BIT(8) +#define BD_MAC2 BIT(9) +#define BD_MAC1 BIT(10) +#define IC_MACPHY_MODE BIT(11) +#define VENDOR_ID BIT(19) +#define PAD_HWPD_IDN BIT(22) +#define TRP_VAUX_EN BIT(23) +#define TRP_BT_EN BIT(24) +#define BD_PKG_SEL BIT(25) +#define BD_HCI_SEL BIT(26) +#define TYPE_ID BIT(27) + +#define CHIP_VER_RTL_MASK 0xF000 +#define CHIP_VER_RTL_SHIFT 12 + +#define REG_LBMODE (REG_CR + 3) + +#define HCI_TXDMA_EN BIT(0) +#define HCI_RXDMA_EN BIT(1) +#define TXDMA_EN BIT(2) +#define RXDMA_EN BIT(3) +#define PROTOCOL_EN BIT(4) +#define SCHEDULE_EN BIT(5) +#define MACTXEN BIT(6) +#define MACRXEN BIT(7) +#define ENSWBCN BIT(8) +#define ENSEC BIT(9) + +#define _NETTYPE(x) (((x) & 0x3) << 16) +#define MASK_NETTYPE 0x30000 +#define NT_NO_LINK 0x0 +#define NT_LINK_AD_HOC 0x1 +#define NT_LINK_AP 0x2 +#define NT_AS_AP 0x3 + +#define _LBMODE(x) (((x) & 0xF) << 24) +#define MASK_LBMODE 0xF000000 +#define LOOPBACK_NORMAL 0x0 +#define LOOPBACK_IMMEDIATELY 0xB +#define LOOPBACK_MAC_DELAY 0x3 +#define LOOPBACK_PHY 0x1 +#define LOOPBACK_DMA 0x7 + +#define GET_RX_PAGE_SIZE(value) ((value) & 0xF) +#define GET_TX_PAGE_SIZE(value) (((value) & 0xF0) >> 4) +#define _PSRX_MASK 0xF +#define _PSTX_MASK 0xF0 +#define _PSRX(x) (x) +#define _PSTX(x) ((x) << 4) + +#define PBP_64 0x0 +#define PBP_128 0x1 +#define PBP_256 0x2 +#define PBP_512 0x3 +#define PBP_1024 0x4 + +#define RXDMA_ARBBW_EN BIT(0) +#define RXSHFT_EN BIT(1) +#define RXDMA_AGG_EN BIT(2) +#define QS_VO_QUEUE BIT(8) +#define QS_VI_QUEUE BIT(9) +#define QS_BE_QUEUE BIT(10) +#define QS_BK_QUEUE BIT(11) +#define QS_MANAGER_QUEUE BIT(12) +#define QS_HIGH_QUEUE BIT(13) + +#define HQSEL_VOQ BIT(0) +#define HQSEL_VIQ BIT(1) +#define HQSEL_BEQ BIT(2) +#define HQSEL_BKQ BIT(3) +#define HQSEL_MGTQ BIT(4) +#define HQSEL_HIQ BIT(5) + +#define _TXDMA_HIQ_MAP(x) (((x)&0x3) << 14) +#define _TXDMA_MGQ_MAP(x) (((x)&0x3) << 12) +#define _TXDMA_BKQ_MAP(x) (((x)&0x3) << 10) +#define _TXDMA_BEQ_MAP(x) (((x)&0x3) << 8) +#define _TXDMA_VIQ_MAP(x) (((x)&0x3) << 6) +#define _TXDMA_VOQ_MAP(x) (((x)&0x3) << 4) + +#define QUEUE_LOW 1 +#define QUEUE_NORMAL 2 +#define QUEUE_HIGH 3 + +#define _LLT_NO_ACTIVE 0x0 +#define _LLT_WRITE_ACCESS 0x1 +#define _LLT_READ_ACCESS 0x2 + +#define _LLT_INIT_DATA(x) ((x) & 0xFF) +#define _LLT_INIT_ADDR(x) (((x) & 0xFF) << 8) +#define _LLT_OP(x) (((x) & 0x3) << 30) +#define _LLT_OP_VALUE(x) (((x) >> 30) & 0x3) + +#define BB_WRITE_READ_MASK (BIT(31) | BIT(30)) +#define BB_WRITE_EN BIT(30) +#define BB_READ_EN BIT(31) + +#define _HPQ(x) ((x) & 0xFF) +#define _LPQ(x) (((x) & 0xFF) << 8) +#define _PUBQ(x) (((x) & 0xFF) << 16) +#define _NPQ(x) ((x) & 0xFF) + +#define HPQ_PUBLIC_DIS BIT(24) +#define LPQ_PUBLIC_DIS BIT(25) +#define LD_RQPN BIT(31) + +#define BCN_VALID BIT(16) +#define BCN_HEAD(x) (((x) & 0xFF) << 8) +#define BCN_HEAD_MASK 0xFF00 + +#define BLK_DESC_NUM_SHIFT 4 +#define BLK_DESC_NUM_MASK 0xF + +#define DROP_DATA_EN BIT(9) + +#define EN_AMPDU_RTY_NEW BIT(7) + +#define _INIRTSMCS_SEL(x) ((x) & 0x3F) + +#define _SPEC_SIFS_CCK(x) ((x) & 0xFF) +#define _SPEC_SIFS_OFDM(x) (((x) & 0xFF) << 8) + +#define RATE_REG_BITMAP_ALL 0xFFFFF + +#define _RRSC_BITMAP(x) ((x) & 0xFFFFF) + +#define _RRSR_RSC(x) (((x) & 0x3) << 21) +#define RRSR_RSC_RESERVED 0x0 +#define RRSR_RSC_UPPER_SUBCHANNEL 0x1 +#define RRSR_RSC_LOWER_SUBCHANNEL 0x2 +#define RRSR_RSC_DUPLICATE_MODE 0x3 + +#define USE_SHORT_G1 BIT(20) + +#define _AGGLMT_MCS0(x) ((x) & 0xF) +#define _AGGLMT_MCS1(x) (((x) & 0xF) << 4) +#define _AGGLMT_MCS2(x) (((x) & 0xF) << 8) +#define _AGGLMT_MCS3(x) (((x) & 0xF) << 12) +#define _AGGLMT_MCS4(x) (((x) & 0xF) << 16) +#define _AGGLMT_MCS5(x) (((x) & 0xF) << 20) +#define _AGGLMT_MCS6(x) (((x) & 0xF) << 24) +#define _AGGLMT_MCS7(x) (((x) & 0xF) << 28) + +#define RETRY_LIMIT_SHORT_SHIFT 8 +#define RETRY_LIMIT_LONG_SHIFT 0 + +#define _DARF_RC1(x) ((x) & 0x1F) +#define _DARF_RC2(x) (((x) & 0x1F) << 8) +#define _DARF_RC3(x) (((x) & 0x1F) << 16) +#define _DARF_RC4(x) (((x) & 0x1F) << 24) +#define _DARF_RC5(x) ((x) & 0x1F) +#define _DARF_RC6(x) (((x) & 0x1F) << 8) +#define _DARF_RC7(x) (((x) & 0x1F) << 16) +#define _DARF_RC8(x) (((x) & 0x1F) << 24) + +#define _RARF_RC1(x) ((x) & 0x1F) +#define _RARF_RC2(x) (((x) & 0x1F) << 8) +#define _RARF_RC3(x) (((x) & 0x1F) << 16) +#define _RARF_RC4(x) (((x) & 0x1F) << 24) +#define _RARF_RC5(x) ((x) & 0x1F) +#define _RARF_RC6(x) (((x) & 0x1F) << 8) +#define _RARF_RC7(x) (((x) & 0x1F) << 16) +#define _RARF_RC8(x) (((x) & 0x1F) << 24) + +#define AC_PARAM_TXOP_LIMIT_OFFSET 16 +#define AC_PARAM_ECW_MAX_OFFSET 12 +#define AC_PARAM_ECW_MIN_OFFSET 8 +#define AC_PARAM_AIFS_OFFSET 0 + +#define _AIFS(x) (x) +#define _ECW_MAX_MIN(x) ((x) << 8) +#define _TXOP_LIMIT(x) ((x) << 16) + +#define _BCNIFS(x) ((x) & 0xFF) +#define _BCNECW(x) ((((x) & 0xF)) << 8) + +#define _LRL(x) ((x) & 0x3F) +#define _SRL(x) (((x) & 0x3F) << 8) + +#define _SIFS_CCK_CTX(x) ((x) & 0xFF) +#define _SIFS_CCK_TRX(x) (((x) & 0xFF) << 8); + +#define _SIFS_OFDM_CTX(x) ((x) & 0xFF) +#define _SIFS_OFDM_TRX(x) (((x) & 0xFF) << 8); + +#define _TBTT_PROHIBIT_HOLD(x) (((x) & 0xFF) << 8) + +#define DIS_EDCA_CNT_DWN BIT(11) + +#define EN_MBSSID BIT(1) +#define EN_TXBCN_RPT BIT(2) +#define EN_BCN_FUNCTION BIT(3) + +#define TSFTR_RST BIT(0) +#define TSFTR1_RST BIT(1) + +#define STOP_BCNQ BIT(6) + +#define DIS_TSF_UDT0_NORMAL_CHIP BIT(4) +#define DIS_TSF_UDT0_TEST_CHIP BIT(5) + +#define ACMHW_HWEN BIT(0) +#define ACMHW_BEQEN BIT(1) +#define ACMHW_VIQEN BIT(2) +#define ACMHW_VOQEN BIT(3) +#define ACMHW_BEQSTATUS BIT(4) +#define ACMHW_VIQSTATUS BIT(5) +#define ACMHW_VOQSTATUS BIT(6) + +#define APSDOFF BIT(6) +#define APSDOFF_STATUS BIT(7) + +#define BW_20MHZ BIT(2) + +#define RATE_BITMAP_ALL 0xFFFFF + +#define RATE_RRSR_CCK_ONLY_1M 0xFFFF1 + +#define TSFRST BIT(0) +#define DIS_GCLK BIT(1) +#define PAD_SEL BIT(2) +#define PWR_ST BIT(6) +#define PWRBIT_OW_EN BIT(7) +#define ACRC BIT(8) +#define CFENDFORM BIT(9) +#define ICV BIT(10) + +#define AAP BIT(0) +#define APM BIT(1) +#define AM BIT(2) +#define AB BIT(3) +#define ADD3 BIT(4) +#define APWRMGT BIT(5) +#define CBSSID BIT(6) +#define CBSSID_DATA BIT(6) +#define CBSSID_BCN BIT(7) +#define ACRC32 BIT(8) +#define AICV BIT(9) +#define ADF BIT(11) +#define ACF BIT(12) +#define AMF BIT(13) +#define HTC_LOC_CTRL BIT(14) +#define UC_DATA_EN BIT(16) +#define BM_DATA_EN BIT(17) +#define MFBEN BIT(22) +#define LSIGEN BIT(23) +#define ENMBID BIT(24) +#define APP_BASSN BIT(27) +#define APP_PHYSTS BIT(28) +#define APP_ICV BIT(29) +#define APP_MIC BIT(30) +#define APP_FCS BIT(31) + +#define _MIN_SPACE(x) ((x) & 0x7) +#define _SHORT_GI_PADDING(x) (((x) & 0x1F) << 3) + +#define RXERR_TYPE_OFDM_PPDU 0 +#define RXERR_TYPE_OFDM_FALSE_ALARM 1 +#define RXERR_TYPE_OFDM_MPDU_OK 2 +#define RXERR_TYPE_OFDM_MPDU_FAIL 3 +#define RXERR_TYPE_CCK_PPDU 4 +#define RXERR_TYPE_CCK_FALSE_ALARM 5 +#define RXERR_TYPE_CCK_MPDU_OK 6 +#define RXERR_TYPE_CCK_MPDU_FAIL 7 +#define RXERR_TYPE_HT_PPDU 8 +#define RXERR_TYPE_HT_FALSE_ALARM 9 +#define RXERR_TYPE_HT_MPDU_TOTAL 10 +#define RXERR_TYPE_HT_MPDU_OK 11 +#define RXERR_TYPE_HT_MPDU_FAIL 12 +#define RXERR_TYPE_RX_FULL_DROP 15 + +#define RXERR_COUNTER_MASK 0xFFFFF +#define RXERR_RPT_RST BIT(27) +#define _RXERR_RPT_SEL(type) ((type) << 28) + +#define SCR_TXUSEDK BIT(0) +#define SCR_RXUSEDK BIT(1) +#define SCR_TXENCENABLE BIT(2) +#define SCR_RXDECENABLE BIT(3) +#define SCR_SKBYA2 BIT(4) +#define SCR_NOSKMC BIT(5) +#define SCR_TXBCUSEDK BIT(6) +#define SCR_RXBCUSEDK BIT(7) + +#define USB_IS_HIGH_SPEED 0 +#define USB_IS_FULL_SPEED 1 +#define USB_SPEED_MASK BIT(5) + +#define USB_NORMAL_SIE_EP_MASK 0xF +#define USB_NORMAL_SIE_EP_SHIFT 4 + +#define USB_TEST_EP_MASK 0x30 +#define USB_TEST_EP_SHIFT 4 + +#define USB_AGG_EN BIT(3) + +#define MAC_ADDR_LEN 6 +#define LAST_ENTRY_OF_TX_PKT_BUFFER 175/*255 88e*/ + +#define POLLING_LLT_THRESHOLD 20 +#define POLLING_READY_TIMEOUT_COUNT 3000 + +#define MAX_MSS_DENSITY_2T 0x13 +#define MAX_MSS_DENSITY_1T 0x0A + +#define EPROM_CMD_OPERATING_MODE_MASK ((1<<7)|(1<<6)) +#define EPROM_CMD_CONFIG 0x3 +#define EPROM_CMD_LOAD 1 + +#define HWSET_MAX_SIZE_92S HWSET_MAX_SIZE + +#define HAL_8192C_HW_GPIO_WPS_BIT BIT(2) + +#define RPMAC_RESET 0x100 +#define RPMAC_TXSTART 0x104 +#define RPMAC_TXLEGACYSIG 0x108 +#define RPMAC_TXHTSIG1 0x10c +#define RPMAC_TXHTSIG2 0x110 +#define RPMAC_PHYDEBUG 0x114 +#define RPMAC_TXPACKETNUM 0x118 +#define RPMAC_TXIDLE 0x11c +#define RPMAC_TXMACHEADER0 0x120 +#define RPMAC_TXMACHEADER1 0x124 +#define RPMAC_TXMACHEADER2 0x128 +#define RPMAC_TXMACHEADER3 0x12c +#define RPMAC_TXMACHEADER4 0x130 +#define RPMAC_TXMACHEADER5 0x134 +#define RPMAC_TXDADATYPE 0x138 +#define RPMAC_TXRANDOMSEED 0x13c +#define RPMAC_CCKPLCPPREAMBLE 0x140 +#define RPMAC_CCKPLCPHEADER 0x144 +#define RPMAC_CCKCRC16 0x148 +#define RPMAC_OFDMRXCRC32OK 0x170 +#define RPMAC_OFDMRXCRC32Er 0x174 +#define RPMAC_OFDMRXPARITYER 0x178 +#define RPMAC_OFDMRXCRC8ER 0x17c +#define RPMAC_CCKCRXRC16ER 0x180 +#define RPMAC_CCKCRXRC32ER 0x184 +#define RPMAC_CCKCRXRC32OK 0x188 +#define RPMAC_TXSTATUS 0x18c + +#define RFPGA0_RFMOD 0x800 + +#define RFPGA0_TXINFO 0x804 +#define RFPGA0_PSDFUNCTION 0x808 + +#define RFPGA0_TXGAINSTAGE 0x80c + +#define RFPGA0_RFTIMING1 0x810 +#define RFPGA0_RFTIMING2 0x814 + +#define RFPGA0_XA_HSSIPARAMETER1 0x820 +#define RFPGA0_XA_HSSIPARAMETER2 0x824 +#define RFPGA0_XB_HSSIPARAMETER1 0x828 +#define RFPGA0_XB_HSSIPARAMETER2 0x82c + +#define RFPGA0_XA_LSSIPARAMETER 0x840 +#define RFPGA0_XB_LSSIPARAMETER 0x844 + +#define RFPGA0_RFWAKEUPPARAMETER 0x850 +#define RFPGA0_RFSLEEPUPPARAMETER 0x854 + +#define RFPGA0_XAB_SWITCHCONTROL 0x858 +#define RFPGA0_XCD_SWITCHCONTROL 0x85c + +#define RFPGA0_XA_RFINTERFACEOE 0x860 +#define RFPGA0_XB_RFINTERFACEOE 0x864 + +#define RFPGA0_XAB_RFINTERFACESW 0x870 +#define RFPGA0_XCD_RFINTERFACESW 0x874 + +#define rFPGA0_XAB_RFPARAMETER 0x878 +#define rFPGA0_XCD_RFPARAMETER 0x87c + +#define RFPGA0_ANALOGPARAMETER1 0x880 +#define RFPGA0_ANALOGPARAMETER2 0x884 +#define RFPGA0_ANALOGPARAMETER3 0x888 +#define RFPGA0_ANALOGPARAMETER4 0x88c + +#define RFPGA0_XA_LSSIREADBACK 0x8a0 +#define RFPGA0_XB_LSSIREADBACK 0x8a4 +#define RFPGA0_XC_LSSIREADBACK 0x8a8 +#define RFPGA0_XD_LSSIREADBACK 0x8ac + +#define RFPGA0_PSDREPORT 0x8b4 +#define TRANSCEIVEA_HSPI_READBACK 0x8b8 +#define TRANSCEIVEB_HSPI_READBACK 0x8bc +#define REG_SC_CNT 0x8c4 +#define RFPGA0_XAB_RFINTERFACERB 0x8e0 +#define RFPGA0_XCD_RFINTERFACERB 0x8e4 + +#define RFPGA1_RFMOD 0x900 + +#define RFPGA1_TXBLOCK 0x904 +#define RFPGA1_DEBUGSELECT 0x908 +#define RFPGA1_TXINFO 0x90c + +#define RCCK0_SYSTEM 0xa00 + +#define RCCK0_AFESETTING 0xa04 +#define RCCK0_CCA 0xa08 + +#define RCCK0_RXAGC1 0xa0c +#define RCCK0_RXAGC2 0xa10 + +#define RCCK0_RXHP 0xa14 + +#define RCCK0_DSPPARAMETER1 0xa18 +#define RCCK0_DSPPARAMETER2 0xa1c + +#define RCCK0_TXFILTER1 0xa20 +#define RCCK0_TXFILTER2 0xa24 +#define RCCK0_DEBUGPORT 0xa28 +#define RCCK0_FALSEALARMREPORT 0xa2c +#define RCCK0_TRSSIREPORT 0xa50 +#define RCCK0_RXREPORT 0xa54 +#define RCCK0_FACOUNTERLOWER 0xa5c +#define RCCK0_FACOUNTERUPPER 0xa58 +#define RCCK0_CCA_CNT 0xa60 + + +/* PageB(0xB00) */ +#define RPDP_ANTA 0xb00 +#define RPDP_ANTA_4 0xb04 +#define RPDP_ANTA_8 0xb08 +#define RPDP_ANTA_C 0xb0c +#define RPDP_ANTA_10 0xb10 +#define RPDP_ANTA_14 0xb14 +#define RPDP_ANTA_18 0xb18 +#define RPDP_ANTA_1C 0xb1c +#define RPDP_ANTA_20 0xb20 +#define RPDP_ANTA_24 0xb24 + +#define RCONFIG_PMPD_ANTA 0xb28 +#define RCONFIG_RAM64X16 0xb2c + +#define RBNDA 0xb30 +#define RHSSIPAR 0xb34 + +#define RCONFIG_ANTA 0xb68 +#define RCONFIG_ANTB 0xb6c + +#define RPDP_ANTB 0xb70 +#define RPDP_ANTB_4 0xb74 +#define RPDP_ANTB_8 0xb78 +#define RPDP_ANTB_C 0xb7c +#define RPDP_ANTB_10 0xb80 +#define RPDP_ANTB_14 0xb84 +#define RPDP_ANTB_18 0xb88 +#define RPDP_ANTB_1C 0xb8c +#define RPDP_ANTB_20 0xb90 +#define RPDP_ANTB_24 0xb94 + +#define RCONFIG_PMPD_ANTB 0xb98 + +#define RBNDB 0xba0 + +#define RAPK 0xbd8 +#define rPm_Rx0_AntA 0xbdc +#define rPm_Rx1_AntA 0xbe0 +#define rPm_Rx2_AntA 0xbe4 +#define rPm_Rx3_AntA 0xbe8 +#define rPm_Rx0_AntB 0xbec +#define rPm_Rx1_AntB 0xbf0 +#define rPm_Rx2_AntB 0xbf4 +#define rPm_Rx3_AntB 0xbf8 + +/*Page C*/ +#define ROFDM0_LSTF 0xc00 + +#define ROFDM0_TRXPATHENABLE 0xc04 +#define ROFDM0_TRMUXPAR 0xc08 +#define ROFDM0_TRSWISOLATION 0xc0c + +#define ROFDM0_XARXAFE 0xc10 +#define ROFDM0_XARXIQIMBAL 0xc14 +#define ROFDM0_XBRXAFE 0xc18 +#define ROFDM0_XBRXIQIMBAL 0xc1c +#define ROFDM0_XCRXAFE 0xc20 +#define ROFDM0_XCRXIQIMBAL 0xc24 +#define ROFDM0_XDRXAFE 0xc28 +#define ROFDM0_XDRXIQIMBAL 0xc2c + +#define ROFDM0_RXDETECTOR1 0xc30 +#define ROFDM0_RXDETECTOR2 0xc34 +#define ROFDM0_RXDETECTOR3 0xc38 +#define ROFDM0_RXDETECTOR4 0xc3c + +#define ROFDM0_RXDSP 0xc40 +#define ROFDM0_CFOANDDAGC 0xc44 +#define ROFDM0_CCADROPTHRES 0xc48 +#define ROFDM0_ECCATHRES 0xc4c + +#define ROFDM0_XAAGCCORE1 0xc50 +#define ROFDM0_XAAGCCORE2 0xc54 +#define ROFDM0_XBAGCCORE1 0xc58 +#define ROFDM0_XBAGCCORE2 0xc5c +#define ROFDM0_XCAGCCORE1 0xc60 +#define ROFDM0_XCAGCCORE2 0xc64 +#define ROFDM0_XDAGCCORE1 0xc68 +#define ROFDM0_XDAGCCORE2 0xc6c + +#define ROFDM0_AGCPARAMETER1 0xc70 +#define ROFDM0_AGCPARAMETER2 0xc74 +#define ROFDM0_AGCRSSITABLE 0xc78 +#define ROFDM0_HTSTFAGC 0xc7c + +#define ROFDM0_XATXIQIMBAL 0xc80 +#define ROFDM0_XATXAFE 0xc84 +#define ROFDM0_XBTXIQIMBAL 0xc88 +#define ROFDM0_XBTXAFE 0xc8c +#define ROFDM0_XCTXIQIMBAL 0xc90 +#define ROFDM0_XCTXAFE 0xc94 +#define ROFDM0_XDTXIQIMBAL 0xc98 +#define ROFDM0_XDTXAFE 0xc9c + +#define ROFDM0_RXIQEXTANTA 0xca0 +#define ROFDM0_TXCOEFF1 0xca4 +#define ROFDM0_TXCOEFF2 0xca8 +#define ROFDM0_TXCOEFF3 0xcac +#define ROFDM0_TXCOEFF4 0xcb0 +#define ROFDM0_TXCOEFF5 0xcb4 +#define ROFDM0_TXCOEFF6 0xcb8 + +#define ROFDM0_RXHPPARAMETER 0xce0 +#define ROFDM0_TXPSEUDONOISEWGT 0xce4 +#define ROFDM0_FRAMESYNC 0xcf0 +#define ROFDM0_DFSREPORT 0xcf4 + + +#define ROFDM1_LSTF 0xd00 +#define ROFDM1_TRXPATHENABLE 0xd04 + +#define ROFDM1_CF0 0xd08 +#define ROFDM1_CSI1 0xd10 +#define ROFDM1_SBD 0xd14 +#define ROFDM1_CSI2 0xd18 +#define ROFDM1_CFOTRACKING 0xd2c +#define ROFDM1_TRXMESAURE1 0xd34 +#define ROFDM1_INTFDET 0xd3c +#define ROFDM1_PSEUDONOISESTATEAB 0xd50 +#define ROFDM1_PSEUDONOISESTATECD 0xd54 +#define ROFDM1_RXPSEUDONOISEWGT 0xd58 + +#define ROFDM_PHYCOUNTER1 0xda0 +#define ROFDM_PHYCOUNTER2 0xda4 +#define ROFDM_PHYCOUNTER3 0xda8 + +#define ROFDM_SHORTCFOAB 0xdac +#define ROFDM_SHORTCFOCD 0xdb0 +#define ROFDM_LONGCFOAB 0xdb4 +#define ROFDM_LONGCFOCD 0xdb8 +#define ROFDM_TAILCF0AB 0xdbc +#define ROFDM_TAILCF0CD 0xdc0 +#define ROFDM_PWMEASURE1 0xdc4 +#define ROFDM_PWMEASURE2 0xdc8 +#define ROFDM_BWREPORT 0xdcc +#define ROFDM_AGCREPORT 0xdd0 +#define ROFDM_RXSNR 0xdd4 +#define ROFDM_RXEVMCSI 0xdd8 +#define ROFDM_SIGREPORT 0xddc + +#define RTXAGC_A_RATE18_06 0xe00 +#define RTXAGC_A_RATE54_24 0xe04 +#define RTXAGC_A_CCK1_MCS32 0xe08 +#define RTXAGC_A_MCS03_MCS00 0xe10 +#define RTXAGC_A_MCS07_MCS04 0xe14 +#define RTXAGC_A_MCS11_MCS08 0xe18 +#define RTXAGC_A_MCS15_MCS12 0xe1c + +#define RTXAGC_B_RATE18_06 0x830 +#define RTXAGC_B_RATE54_24 0x834 +#define RTXAGC_B_CCK1_55_MCS32 0x838 +#define RTXAGC_B_MCS03_MCS00 0x83c +#define RTXAGC_B_MCS07_MCS04 0x848 +#define RTXAGC_B_MCS11_MCS08 0x84c +#define RTXAGC_B_MCS15_MCS12 0x868 +#define RTXAGC_B_CCK11_A_CCK2_11 0x86c + +#define RFPGA0_IQK 0xe28 +#define RTX_IQK_TONE_A 0xe30 +#define RRX_IQK_TONE_A 0xe34 +#define RTX_IQK_PI_A 0xe38 +#define RRX_IQK_PI_A 0xe3c + +#define RTX_IQK 0xe40 +#define RRX_IQK 0xe44 +#define RIQK_AGC_PTS 0xe48 +#define RIQK_AGC_RSP 0xe4c +#define RTX_IQK_TONE_B 0xe50 +#define RRX_IQK_TONE_B 0xe54 +#define RTX_IQK_PI_B 0xe58 +#define RRX_IQK_PI_B 0xe5c +#define RIQK_AGC_CONT 0xe60 + +#define RBLUE_TOOTH 0xe6c +#define RRX_WAIT_CCA 0xe70 +#define RTX_CCK_RFON 0xe74 +#define RTX_CCK_BBON 0xe78 +#define RTX_OFDM_RFON 0xe7c +#define RTX_OFDM_BBON 0xe80 +#define RTX_TO_RX 0xe84 +#define RTX_TO_TX 0xe88 +#define RRX_CCK 0xe8c + +#define RTX_POWER_BEFORE_IQK_A 0xe94 +#define RTX_POWER_AFTER_IQK_A 0xe9c + +#define RRX_POWER_BEFORE_IQK_A 0xea0 +#define RRX_POWER_BEFORE_IQK_A_2 0xea4 +#define RRX_POWER_AFTER_IQK_A 0xea8 +#define RRX_POWER_AFTER_IQK_A_2 0xeac + +#define RTX_POWER_BEFORE_IQK_B 0xeb4 +#define RTX_POWER_AFTER_IQK_B 0xebc + +#define RRX_POWER_BEFORE_IQK_B 0xec0 +#define RRX_POWER_BEFORE_IQK_B_2 0xec4 +#define RRX_POWER_AFTER_IQK_B 0xec8 +#define RRX_POWER_AFTER_IQK_B_2 0xecc + +#define RRX_OFDM 0xed0 +#define RRX_WAIT_RIFS 0xed4 +#define RRX_TO_RX 0xed8 +#define RSTANDBY 0xedc +#define RSLEEP 0xee0 +#define RPMPD_ANAEN 0xeec + +#define RZEBRA1_HSSIENABLE 0x0 +#define RZEBRA1_TRXENABLE1 0x1 +#define RZEBRA1_TRXENABLE2 0x2 +#define RZEBRA1_AGC 0x4 +#define RZEBRA1_CHARGEPUMP 0x5 +#define RZEBRA1_CHANNEL 0x7 + +#define RZEBRA1_TXGAIN 0x8 +#define RZEBRA1_TXLPF 0x9 +#define RZEBRA1_RXLPF 0xb +#define RZEBRA1_RXHPFCORNER 0xc + +#define RGLOBALCTRL 0 +#define RRTL8256_TXLPF 19 +#define RRTL8256_RXLPF 11 +#define RRTL8258_TXLPF 0x11 +#define RRTL8258_RXLPF 0x13 +#define RRTL8258_RSSILPF 0xa + +#define RF_AC 0x00 + +#define RF_IQADJ_G1 0x01 +#define RF_IQADJ_G2 0x02 +#define RF_POW_TRSW 0x05 + +#define RF_GAIN_RX 0x06 +#define RF_GAIN_TX 0x07 + +#define RF_TXM_IDAC 0x08 +#define RF_BS_IQGEN 0x0F + +#define RF_MODE1 0x10 +#define RF_MODE2 0x11 + +#define RF_RX_AGC_HP 0x12 +#define RF_TX_AGC 0x13 +#define RF_BIAS 0x14 +#define RF_IPA 0x15 +#define RF_POW_ABILITY 0x17 +#define RF_MODE_AG 0x18 +#define RRFCHANNEL 0x18 +#define RF_CHNLBW 0x18 +#define RF_TOP 0x19 + +#define RF_RX_G1 0x1A +#define RF_RX_G2 0x1B + +#define RF_RX_BB2 0x1C +#define RF_RX_BB1 0x1D + +#define RF_RCK1 0x1E +#define RF_RCK2 0x1F + +#define RF_TX_G1 0x20 +#define RF_TX_G2 0x21 +#define RF_TX_G3 0x22 + +#define RF_TX_BB1 0x23 +#define RF_T_METER 0x42 + +#define RF_SYN_G1 0x25 +#define RF_SYN_G2 0x26 +#define RF_SYN_G3 0x27 +#define RF_SYN_G4 0x28 +#define RF_SYN_G5 0x29 +#define RF_SYN_G6 0x2A +#define RF_SYN_G7 0x2B +#define RF_SYN_G8 0x2C + +#define RF_RCK_OS 0x30 +#define RF_TXPA_G1 0x31 +#define RF_TXPA_G2 0x32 +#define RF_TXPA_G3 0x33 + +#define RF_TX_BIAS_A 0x35 +#define RF_TX_BIAS_D 0x36 +#define RF_LOBF_9 0x38 +#define RF_RXRF_A3 0x3C +#define RF_TRSW 0x3F + +#define RF_TXRF_A2 0x41 +#define RF_TXPA_G4 0x46 +#define RF_TXPA_A4 0x4B + +#define RF_WE_LUT 0xEF + +#define BBBRESETB 0x100 +#define BGLOBALRESETB 0x200 +#define BOFDMTXSTART 0x4 +#define BCCKTXSTART 0x8 +#define BCRC32DEBUG 0x100 +#define BPMACLOOPBACK 0x10 +#define BTXLSIG 0xffffff +#define BOFDMTXRATE 0xf +#define BOFDMTXRESERVED 0x10 +#define BOFDMTXLENGTH 0x1ffe0 +#define BOFDMTXPARITY 0x20000 +#define BTXHTSIG1 0xffffff +#define BTXHTMCSRATE 0x7f +#define BTXHTBW 0x80 +#define BTXHTLENGTH 0xffff00 +#define BTXHTSIG2 0xffffff +#define BTXHTSMOOTHING 0x1 +#define BTXHTSOUNDING 0x2 +#define BTXHTRESERVED 0x4 +#define BTXHTAGGREATION 0x8 +#define BTXHTSTBC 0x30 +#define BTXHTADVANCECODING 0x40 +#define BTXHTSHORTGI 0x80 +#define BTXHTNUMBERHT_LTF 0x300 +#define BTXHTCRC8 0x3fc00 +#define BCOUNTERRESET 0x10000 +#define BNUMOFOFDMTX 0xffff +#define BNUMOFCCKTX 0xffff0000 +#define BTXIDLEINTERVAL 0xffff +#define BOFDMSERVICE 0xffff0000 +#define BTXMACHEADER 0xffffffff +#define BTXDATAINIT 0xff +#define BTXHTMODE 0x100 +#define BTXDATATYPE 0x30000 +#define BTXRANDOMSEED 0xffffffff +#define BCCKTXPREAMBLE 0x1 +#define BCCKTXSFD 0xffff0000 +#define BCCKTXSIG 0xff +#define BCCKTXSERVICE 0xff00 +#define BCCKLENGTHEXT 0x8000 +#define BCCKTXLENGHT 0xffff0000 +#define BCCKTXCRC16 0xffff +#define BCCKTXSTATUS 0x1 +#define BOFDMTXSTATUS 0x2 +#define IS_BB_REG_OFFSET_92S(_offset) \ + ((_offset >= 0x800) && (_offset <= 0xfff)) + +#define BRFMOD 0x1 +#define BJAPANMODE 0x2 +#define BCCKTXSC 0x30 +#define BCCKEN 0x1000000 +#define BOFDMEN 0x2000000 + +#define BOFDMRXADCPHASE 0x10000 +#define BOFDMTXDACPHASE 0x40000 +#define BXATXAGC 0x3f + +#define BXBTXAGC 0xf00 +#define BXCTXAGC 0xf000 +#define BXDTXAGC 0xf0000 + +#define BPASTART 0xf0000000 +#define BTRSTART 0x00f00000 +#define BRFSTART 0x0000f000 +#define BBBSTART 0x000000f0 +#define BBBCCKSTART 0x0000000f +#define BPAEND 0xf +#define BTREND 0x0f000000 +#define BRFEND 0x000f0000 +#define BCCAMASK 0x000000f0 +#define BR2RCCAMASK 0x00000f00 +#define BHSSI_R2TDELAY 0xf8000000 +#define BHSSI_T2RDELAY 0xf80000 +#define BCONTXHSSI 0x400 +#define BIGFROMCCK 0x200 +#define BAGCADDRESS 0x3f +#define BRXHPTX 0x7000 +#define BRXHP2RX 0x38000 +#define BRXHPCCKINI 0xc0000 +#define BAGCTXCODE 0xc00000 +#define BAGCRXCODE 0x300000 + +#define B3WIREDATALENGTH 0x800 +#define B3WIREADDREAALENGTH 0x400 + +#define B3WIRERFPOWERDOWN 0x1 +#define B5GPAPEPOLARITY 0x40000000 +#define B2GPAPEPOLARITY 0x80000000 +#define BRFSW_TXDEFAULTANT 0x3 +#define BRFSW_TXOPTIONANT 0x30 +#define BRFSW_RXDEFAULTANT 0x300 +#define BRFSW_RXOPTIONANT 0x3000 +#define BRFSI_3WIREDATA 0x1 +#define BRFSI_3WIRECLOCK 0x2 +#define BRFSI_3WIRELOAD 0x4 +#define BRFSI_3WIRERW 0x8 +#define BRFSI_3WIRE 0xf + +#define BRFSI_RFENV 0x10 + +#define BRFSI_TRSW 0x20 +#define BRFSI_TRSWB 0x40 +#define BRFSI_ANTSW 0x100 +#define BRFSI_ANTSWB 0x200 +#define BRFSI_PAPE 0x400 +#define BRFSI_PAPE5G 0x800 +#define BBANDSELECT 0x1 +#define BHTSIG2_GI 0x80 +#define BHTSIG2_SMOOTHING 0x01 +#define BHTSIG2_SOUNDING 0x02 +#define BHTSIG2_AGGREATON 0x08 +#define BHTSIG2_STBC 0x30 +#define BHTSIG2_ADVCODING 0x40 +#define BHTSIG2_NUMOFHTLTF 0x300 +#define BHTSIG2_CRC8 0x3fc +#define BHTSIG1_MCS 0x7f +#define BHTSIG1_BANDWIDTH 0x80 +#define BHTSIG1_HTLENGTH 0xffff +#define BLSIG_RATE 0xf +#define BLSIG_RESERVED 0x10 +#define BLSIG_LENGTH 0x1fffe +#define BLSIG_PARITY 0x20 +#define BCCKRXPHASE 0x4 + +#define BLSSIREADADDRESS 0x7f800000 +#define BLSSIREADEDGE 0x80000000 + +#define BLSSIREADBACKDATA 0xfffff + +#define BLSSIREADOKFLAG 0x1000 +#define BCCKSAMPLERATE 0x8 +#define BREGULATOR0STANDBY 0x1 +#define BREGULATORPLLSTANDBY 0x2 +#define BREGULATOR1STANDBY 0x4 +#define BPLLPOWERUP 0x8 +#define BDPLLPOWERUP 0x10 +#define BDA10POWERUP 0x20 +#define BAD7POWERUP 0x200 +#define BDA6POWERUP 0x2000 +#define BXTALPOWERUP 0x4000 +#define B40MDCLKPOWERUP 0x8000 +#define BDA6DEBUGMODE 0x20000 +#define BDA6SWING 0x380000 + +#define BADCLKPHASE 0x4000000 +#define B80MCLKDELAY 0x18000000 +#define BAFEWATCHDOGENABLE 0x20000000 + +#define BXTALCAP01 0xc0000000 +#define BXTALCAP23 0x3 +#define BXTALCAP92X 0x0f000000 +#define BXTALCAP 0x0f000000 + +#define BINTDIFCLKENABLE 0x400 +#define BEXTSIGCLKENABLE 0x800 +#define BBANDGAP_MBIAS_POWERUP 0x10000 +#define BAD11SH_GAIN 0xc0000 +#define BAD11NPUT_RANGE 0x700000 +#define BAD110P_CURRENT 0x3800000 +#define BLPATH_LOOPBACK 0x4000000 +#define BQPATH_LOOPBACK 0x8000000 +#define BAFE_LOOPBACK 0x10000000 +#define BDA10_SWING 0x7e0 +#define BDA10_REVERSE 0x800 +#define BDA_CLK_SOURCE 0x1000 +#define BDA7INPUT_RANGE 0x6000 +#define BDA7_GAIN 0x38000 +#define BDA7OUTPUT_CM_MODE 0x40000 +#define BDA7INPUT_CM_MODE 0x380000 +#define BDA7CURRENT 0xc00000 +#define BREGULATOR_ADJUST 0x7000000 +#define BAD11POWERUP_ATTX 0x1 +#define BDA10PS_ATTX 0x10 +#define BAD11POWERUP_ATRX 0x100 +#define BDA10PS_ATRX 0x1000 +#define BCCKRX_AGC_FORMAT 0x200 +#define BPSDFFT_SAMPLE_POINT 0xc000 +#define BPSD_AVERAGE_NUM 0x3000 +#define BIQPATH_CONTROL 0xc00 +#define BPSD_FREQ 0x3ff +#define BPSD_ANTENNA_PATH 0x30 +#define BPSD_IQ_SWITCH 0x40 +#define BPSD_RX_TRIGGER 0x400000 +#define BPSD_TX_TRIGGERCW 0x80000000 +#define BPSD_SINE_TONE_SCALE 0x7f000000 +#define BPSD_REPORT 0xffff + +#define BOFDM_TXSC 0x30000000 +#define BCCK_TXON 0x1 +#define BOFDM_TXON 0x2 +#define BDEBUG_PAGE 0xfff +#define BDEBUG_ITEM 0xff +#define BANTL 0x10 +#define BANT_NONHT 0x100 +#define BANT_HT1 0x1000 +#define BANT_HT2 0x10000 +#define BANT_HT1S1 0x100000 +#define BANT_NONHTS1 0x1000000 + +#define BCCK_BBMODE 0x3 +#define BCCK_TXPOWERSAVING 0x80 +#define BCCK_RXPOWERSAVING 0x40 + +#define BCCK_SIDEBAND 0x10 + +#define BCCK_SCRAMBLE 0x8 +#define BCCK_ANTDIVERSITY 0x8000 +#define BCCK_CARRIER_RECOVERY 0x4000 +#define BCCK_TXRATE 0x3000 +#define BCCK_DCCANCEL 0x0800 +#define BCCK_ISICANCEL 0x0400 +#define BCCK_MATCH_FILTER 0x0200 +#define BCCK_EQUALIZER 0x0100 +#define BCCK_PREAMBLE_DETECT 0x800000 +#define BCCK_FAST_FALSECCA 0x400000 +#define BCCK_CH_ESTSTART 0x300000 +#define BCCK_CCA_COUNT 0x080000 +#define BCCK_CS_LIM 0x070000 +#define BCCK_BIST_MODE 0x80000000 +#define BCCK_CCAMASK 0x40000000 +#define BCCK_TX_DAC_PHASE 0x4 +#define BCCK_RX_ADC_PHASE 0x20000000 +#define BCCKR_CP_MODE 0x0100 +#define BCCK_TXDC_OFFSET 0xf0 +#define BCCK_RXDC_OFFSET 0xf +#define BCCK_CCA_MODE 0xc000 +#define BCCK_FALSECS_LIM 0x3f00 +#define BCCK_CS_RATIO 0xc00000 +#define BCCK_CORGBIT_SEL 0x300000 +#define BCCK_PD_LIM 0x0f0000 +#define BCCK_NEWCCA 0x80000000 +#define BCCK_RXHP_OF_IG 0x8000 +#define BCCK_RXIG 0x7f00 +#define BCCK_LNA_POLARITY 0x800000 +#define BCCK_RX1ST_BAIN 0x7f0000 +#define BCCK_RF_EXTEND 0x20000000 +#define BCCK_RXAGC_SATLEVEL 0x1f000000 +#define BCCK_RXAGC_SATCOUNT 0xe0 +#define BCCKRXRFSETTLE 0x1f +#define BCCK_FIXED_RXAGC 0x8000 +#define BCCK_ANTENNA_POLARITY 0x2000 +#define BCCK_TXFILTER_TYPE 0x0c00 +#define BCCK_RXAGC_REPORTTYPE 0x0300 +#define BCCK_RXDAGC_EN 0x80000000 +#define BCCK_RXDAGC_PERIOD 0x20000000 +#define BCCK_RXDAGC_SATLEVEL 0x1f000000 +#define BCCK_TIMING_RECOVERY 0x800000 +#define BCCK_TXC0 0x3f0000 +#define BCCK_TXC1 0x3f000000 +#define BCCK_TXC2 0x3f +#define BCCK_TXC3 0x3f00 +#define BCCK_TXC4 0x3f0000 +#define BCCK_TXC5 0x3f000000 +#define BCCK_TXC6 0x3f +#define BCCK_TXC7 0x3f00 +#define BCCK_DEBUGPORT 0xff0000 +#define BCCK_DAC_DEBUG 0x0f000000 +#define BCCK_FALSEALARM_ENABLE 0x8000 +#define BCCK_FALSEALARM_READ 0x4000 +#define BCCK_TRSSI 0x7f +#define BCCK_RXAGC_REPORT 0xfe +#define BCCK_RXREPORT_ANTSEL 0x80000000 +#define BCCK_RXREPORT_MFOFF 0x40000000 +#define BCCK_RXREPORT_SQLOSS 0x20000000 +#define BCCK_RXREPORT_PKTLOSS 0x10000000 +#define BCCK_RXREPORT_LOCKEDBIT 0x08000000 +#define BCCK_RXREPORT_RATEERROR 0x04000000 +#define BCCK_RXREPORT_RXRATE 0x03000000 +#define BCCK_RXFA_COUNTER_LOWER 0xff +#define BCCK_RXFA_COUNTER_UPPER 0xff000000 +#define BCCK_RXHPAGC_START 0xe000 +#define BCCK_RXHPAGC_FINAL 0x1c00 +#define BCCK_RXFALSEALARM_ENABLE 0x8000 +#define BCCK_FACOUNTER_FREEZE 0x4000 +#define BCCK_TXPATH_SEL 0x10000000 +#define BCCK_DEFAULT_RXPATH 0xc000000 +#define BCCK_OPTION_RXPATH 0x3000000 + +#define BNUM_OFSTF 0x3 +#define BSHIFT_L 0xc0 +#define BGI_TH 0xc +#define BRXPATH_A 0x1 +#define BRXPATH_B 0x2 +#define BRXPATH_C 0x4 +#define BRXPATH_D 0x8 +#define BTXPATH_A 0x1 +#define BTXPATH_B 0x2 +#define BTXPATH_C 0x4 +#define BTXPATH_D 0x8 +#define BTRSSI_FREQ 0x200 +#define BADC_BACKOFF 0x3000 +#define BDFIR_BACKOFF 0xc000 +#define BTRSSI_LATCH_PHASE 0x10000 +#define BRX_LDC_OFFSET 0xff +#define BRX_QDC_OFFSET 0xff00 +#define BRX_DFIR_MODE 0x1800000 +#define BRX_DCNF_TYPE 0xe000000 +#define BRXIQIMB_A 0x3ff +#define BRXIQIMB_B 0xfc00 +#define BRXIQIMB_C 0x3f0000 +#define BRXIQIMB_D 0xffc00000 +#define BDC_DC_NOTCH 0x60000 +#define BRXNB_NOTCH 0x1f000000 +#define BPD_TH 0xf +#define BPD_TH_OPT2 0xc000 +#define BPWED_TH 0x700 +#define BIFMF_WIN_L 0x800 +#define BPD_OPTION 0x1000 +#define BMF_WIN_L 0xe000 +#define BBW_SEARCH_L 0x30000 +#define BWIN_ENH_L 0xc0000 +#define BBW_TH 0x700000 +#define BED_TH2 0x3800000 +#define BBW_OPTION 0x4000000 +#define BRADIO_TH 0x18000000 +#define BWINDOW_L 0xe0000000 +#define BSBD_OPTION 0x1 +#define BFRAME_TH 0x1c +#define BFS_OPTION 0x60 +#define BDC_SLOPE_CHECK 0x80 +#define BFGUARD_COUNTER_DC_L 0xe00 +#define BFRAME_WEIGHT_SHORT 0x7000 +#define BSUB_TUNE 0xe00000 +#define BFRAME_DC_LENGTH 0xe000000 +#define BSBD_START_OFFSET 0x30000000 +#define BFRAME_TH_2 0x7 +#define BFRAME_GI2_TH 0x38 +#define BGI2_SYNC_EN 0x40 +#define BSARCH_SHORT_EARLY 0x300 +#define BSARCH_SHORT_LATE 0xc00 +#define BSARCH_GI2_LATE 0x70000 +#define BCFOANTSUM 0x1 +#define BCFOACC 0x2 +#define BCFOSTARTOFFSET 0xc +#define BCFOLOOPBACK 0x70 +#define BCFOSUMWEIGHT 0x80 +#define BDAGCENABLE 0x10000 +#define BTXIQIMB_A 0x3ff +#define BTXIQIMB_B 0xfc00 +#define BTXIQIMB_C 0x3f0000 +#define BTXIQIMB_D 0xffc00000 +#define BTXIDCOFFSET 0xff +#define BTXIQDCOFFSET 0xff00 +#define BTXDFIRMODE 0x10000 +#define BTXPESUDO_NOISEON 0x4000000 +#define BTXPESUDO_NOISE_A 0xff +#define BTXPESUDO_NOISE_B 0xff00 +#define BTXPESUDO_NOISE_C 0xff0000 +#define BTXPESUDO_NOISE_D 0xff000000 +#define BCCA_DROPOPTION 0x20000 +#define BCCA_DROPTHRES 0xfff00000 +#define BEDCCA_H 0xf +#define BEDCCA_L 0xf0 +#define BLAMBDA_ED 0x300 +#define BRX_INITIALGAIN 0x7f +#define BRX_ANTDIV_EN 0x80 +#define BRX_AGC_ADDRESS_FOR_LNA 0x7f00 +#define BRX_HIGHPOWER_FLOW 0x8000 +#define BRX_AGC_FREEZE_THRES 0xc0000 +#define BRX_FREEZESTEP_AGC1 0x300000 +#define BRX_FREEZESTEP_AGC2 0xc00000 +#define BRX_FREEZESTEP_AGC3 0x3000000 +#define BRX_FREEZESTEP_AGC0 0xc000000 +#define BRXRSSI_CMP_EN 0x10000000 +#define BRXQUICK_AGCEN 0x20000000 +#define BRXAGC_FREEZE_THRES_MODE 0x40000000 +#define BRX_OVERFLOW_CHECKTYPE 0x80000000 +#define BRX_AGCSHIFT 0x7f +#define BTRSW_TRI_ONLY 0x80 +#define BPOWER_THRES 0x300 +#define BRXAGC_EN 0x1 +#define BRXAGC_TOGETHER_EN 0x2 +#define BRXAGC_MIN 0x4 +#define BRXHP_INI 0x7 +#define BRXHP_TRLNA 0x70 +#define BRXHP_RSSI 0x700 +#define BRXHP_BBP1 0x7000 +#define BRXHP_BBP2 0x70000 +#define BRXHP_BBP3 0x700000 +#define BRSSI_H 0x7f0000 +#define BRSSI_GEN 0x7f000000 +#define BRXSETTLE_TRSW 0x7 +#define BRXSETTLE_LNA 0x38 +#define BRXSETTLE_RSSI 0x1c0 +#define BRXSETTLE_BBP 0xe00 +#define BRXSETTLE_RXHP 0x7000 +#define BRXSETTLE_ANTSW_RSSI 0x38000 +#define BRXSETTLE_ANTSW 0xc0000 +#define BRXPROCESS_TIME_DAGC 0x300000 +#define BRXSETTLE_HSSI 0x400000 +#define BRXPROCESS_TIME_BBPPW 0x800000 +#define BRXANTENNA_POWER_SHIFT 0x3000000 +#define BRSSI_TABLE_SELECT 0xc000000 +#define BRXHP_FINAL 0x7000000 +#define BRXHPSETTLE_BBP 0x7 +#define BRXHTSETTLE_HSSI 0x8 +#define BRXHTSETTLE_RXHP 0x70 +#define BRXHTSETTLE_BBPPW 0x80 +#define BRXHTSETTLE_IDLE 0x300 +#define BRXHTSETTLE_RESERVED 0x1c00 +#define BRXHT_RXHP_EN 0x8000 +#define BRXAGC_FREEZE_THRES 0x30000 +#define BRXAGC_TOGETHEREN 0x40000 +#define BRXHTAGC_MIN 0x80000 +#define BRXHTAGC_EN 0x100000 +#define BRXHTDAGC_EN 0x200000 +#define BRXHT_RXHP_BBP 0x1c00000 +#define BRXHT_RXHP_FINAL 0xe0000000 +#define BRXPW_RADIO_TH 0x3 +#define BRXPW_RADIO_EN 0x4 +#define BRXMF_HOLD 0x3800 +#define BRXPD_DELAY_TH1 0x38 +#define BRXPD_DELAY_TH2 0x1c0 +#define BRXPD_DC_COUNT_MAX 0x600 +#define BRXPD_DELAY_TH 0x8000 +#define BRXPROCESS_DELAY 0xf0000 +#define BRXSEARCHRANGE_GI2_EARLY 0x700000 +#define BRXFRAME_FUARD_COUNTER_L 0x3800000 +#define BRXSGI_GUARD_L 0xc000000 +#define BRXSGI_SEARCH_L 0x30000000 +#define BRXSGI_TH 0xc0000000 +#define BDFSCNT0 0xff +#define BDFSCNT1 0xff00 +#define BDFSFLAG 0xf0000 +#define BMF_WEIGHT_SUM 0x300000 +#define BMINIDX_TH 0x7f000000 +#define BDAFORMAT 0x40000 +#define BTXCH_EMU_ENABLE 0x01000000 +#define BTRSW_ISOLATION_A 0x7f +#define BTRSW_ISOLATION_B 0x7f00 +#define BTRSW_ISOLATION_C 0x7f0000 +#define BTRSW_ISOLATION_D 0x7f000000 +#define BEXT_LNA_GAIN 0x7c00 + +#define BSTBC_EN 0x4 +#define BANTENNA_MAPPING 0x10 +#define BNSS 0x20 +#define BCFO_ANTSUM_ID 0x200 +#define BPHY_COUNTER_RESET 0x8000000 +#define BCFO_REPORT_GET 0x4000000 +#define BOFDM_CONTINUE_TX 0x10000000 +#define BOFDM_SINGLE_CARRIER 0x20000000 +#define BOFDM_SINGLE_TONE 0x40000000 +#define BHT_DETECT 0x100 +#define BCFOEN 0x10000 +#define BCFOVALUE 0xfff00000 +#define BSIGTONE_RE 0x3f +#define BSIGTONE_IM 0x7f00 +#define BCOUNTER_CCA 0xffff +#define BCOUNTER_PARITYFAIL 0xffff0000 +#define BCOUNTER_RATEILLEGAL 0xffff +#define BCOUNTER_CRC8FAIL 0xffff0000 +#define BCOUNTER_MCSNOSUPPORT 0xffff +#define BCOUNTER_FASTSYNC 0xffff +#define BSHORTCFO 0xfff +#define BSHORTCFOT_LENGTH 12 +#define BSHORTCFOF_LENGTH 11 +#define BLONGCFO 0x7ff +#define BLONGCFOT_LENGTH 11 +#define BLONGCFOF_LENGTH 11 +#define BTAILCFO 0x1fff +#define BTAILCFOT_LENGTH 13 +#define BTAILCFOF_LENGTH 12 +#define BNOISE_EN_PWDB 0xffff +#define BCC_POWER_DB 0xffff0000 +#define BMOISE_PWDB 0xffff +#define BPOWERMEAST_LENGTH 10 +#define BPOWERMEASF_LENGTH 3 +#define BRX_HT_BW 0x1 +#define BRXSC 0x6 +#define BRX_HT 0x8 +#define BNB_INTF_DET_ON 0x1 +#define BINTF_WIN_LEN_CFG 0x30 +#define BNB_INTF_TH_CFG 0x1c0 +#define BRFGAIN 0x3f +#define BTABLESEL 0x40 +#define BTRSW 0x80 +#define BRXSNR_A 0xff +#define BRXSNR_B 0xff00 +#define BRXSNR_C 0xff0000 +#define BRXSNR_D 0xff000000 +#define BSNR_EVMT_LENGTH 8 +#define BSNR_EVMF_LENGTH 1 +#define BCSI1ST 0xff +#define BCSI2ND 0xff00 +#define BRXEVM1ST 0xff0000 +#define BRXEVM2ND 0xff000000 +#define BSIGEVM 0xff +#define BPWDB 0xff00 +#define BSGIEN 0x10000 + +#define BSFACTOR_QMA1 0xf +#define BSFACTOR_QMA2 0xf0 +#define BSFACTOR_QMA3 0xf00 +#define BSFACTOR_QMA4 0xf000 +#define BSFACTOR_QMA5 0xf0000 +#define BSFACTOR_QMA6 0xf0000 +#define BSFACTOR_QMA7 0xf00000 +#define BSFACTOR_QMA8 0xf000000 +#define BSFACTOR_QMA9 0xf0000000 +#define BCSI_SCHEME 0x100000 + +#define BNOISE_LVL_TOP_SET 0x3 +#define BCHSMOOTH 0x4 +#define BCHSMOOTH_CFG1 0x38 +#define BCHSMOOTH_CFG2 0x1c0 +#define BCHSMOOTH_CFG3 0xe00 +#define BCHSMOOTH_CFG4 0x7000 +#define BMRCMODE 0x800000 +#define BTHEVMCFG 0x7000000 + +#define BLOOP_FIT_TYPE 0x1 +#define BUPD_CFO 0x40 +#define BUPD_CFO_OFFDATA 0x80 +#define BADV_UPD_CFO 0x100 +#define BADV_TIME_CTRL 0x800 +#define BUPD_CLKO 0x1000 +#define BFC 0x6000 +#define BTRACKING_MODE 0x8000 +#define BPHCMP_ENABLE 0x10000 +#define BUPD_CLKO_LTF 0x20000 +#define BCOM_CH_CFO 0x40000 +#define BCSI_ESTI_MODE 0x80000 +#define BADV_UPD_EQZ 0x100000 +#define BUCHCFG 0x7000000 +#define BUPDEQZ 0x8000000 + +#define BRX_PESUDO_NOISE_ON 0x20000000 +#define BRX_PESUDO_NOISE_A 0xff +#define BRX_PESUDO_NOISE_B 0xff00 +#define BRX_PESUDO_NOISE_C 0xff0000 +#define BRX_PESUDO_NOISE_D 0xff000000 +#define BRX_PESUDO_NOISESTATE_A 0xffff +#define BRX_PESUDO_NOISESTATE_B 0xffff0000 +#define BRX_PESUDO_NOISESTATE_C 0xffff +#define BRX_PESUDO_NOISESTATE_D 0xffff0000 + +#define BZEBRA1_HSSIENABLE 0x8 +#define BZEBRA1_TRXCONTROL 0xc00 +#define BZEBRA1_TRXGAINSETTING 0x07f +#define BZEBRA1_RXCOUNTER 0xc00 +#define BZEBRA1_TXCHANGEPUMP 0x38 +#define BZEBRA1_RXCHANGEPUMP 0x7 +#define BZEBRA1_CHANNEL_NUM 0xf80 +#define BZEBRA1_TXLPFBW 0x400 +#define BZEBRA1_RXLPFBW 0x600 + +#define BRTL8256REG_MODE_CTRL1 0x100 +#define BRTL8256REG_MODE_CTRL0 0x40 +#define BRTL8256REG_TXLPFBW 0x18 +#define BRTL8256REG_RXLPFBW 0x600 + +#define BRTL8258_TXLPFBW 0xc +#define BRTL8258_RXLPFBW 0xc00 +#define BRTL8258_RSSILPFBW 0xc0 + +#define BBYTE0 0x1 +#define BBYTE1 0x2 +#define BBYTE2 0x4 +#define BBYTE3 0x8 +#define BWORD0 0x3 +#define BWORD1 0xc +#define BWORD 0xf + +#define MASKBYTE0 0xff +#define MASKBYTE1 0xff00 +#define MASKBYTE2 0xff0000 +#define MASKBYTE3 0xff000000 +#define MASKHWORD 0xffff0000 +#define MASKLWORD 0x0000ffff +#define MASKDWORD 0xffffffff +#define MASK12BITS 0xfff +#define MASKH4BITS 0xf0000000 +#define MASKOFDM_D 0xffc00000 +#define MASKCCK 0x3f3f3f3f + +#define MASK4BITS 0x0f +#define MASK20BITS 0xfffff +#define RFREG_OFFSET_MASK 0xfffff + +#define BENABLE 0x1 +#define BDISABLE 0x0 + +#define LEFT_ANTENNA 0x0 +#define RIGHT_ANTENNA 0x1 + +#define TCHECK_TXSTATUS 500 +#define TUPDATE_RXCOUNTER 100 + +#define REG_UN_USED_REGISTER 0x01bf + +/* WOL bit information */ +#define HAL92C_WOL_PTK_UPDATE_EVENT BIT(0) +#define HAL92C_WOL_GTK_UPDATE_EVENT BIT(1) +#define HAL92C_WOL_DISASSOC_EVENT BIT(2) +#define HAL92C_WOL_DEAUTH_EVENT BIT(3) +#define HAL92C_WOL_FW_DISCONNECT_EVENT BIT(4) + +#define WOL_REASON_PTK_UPDATE BIT(0) +#define WOL_REASON_GTK_UPDATE BIT(1) +#define WOL_REASON_DISASSOC BIT(2) +#define WOL_REASON_DEAUTH BIT(3) +#define WOL_REASON_FW_DISCONNECT BIT(4) + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/rf.c b/drivers/net/wireless/rtlwifi/rtl8188ee/rf.c new file mode 100644 index 0000000..4faafdb --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/rf.c @@ -0,0 +1,467 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "rf.h" +#include "dm.h" + +void rtl88e_phy_rf6052_set_bandwidth(struct ieee80211_hw *hw, u8 bandwidth) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + switch (bandwidth) { + case HT_CHANNEL_WIDTH_20: + rtlphy->rfreg_chnlval[0] = ((rtlphy->rfreg_chnlval[0] & + 0xfffff3ff) | BIT(10) | BIT(11)); + rtl_set_rfreg(hw, RF90_PATH_A, RF_CHNLBW, RFREG_OFFSET_MASK, + rtlphy->rfreg_chnlval[0]); + break; + case HT_CHANNEL_WIDTH_20_40: + rtlphy->rfreg_chnlval[0] = ((rtlphy->rfreg_chnlval[0] & + 0xfffff3ff) | BIT(10)); + rtl_set_rfreg(hw, RF90_PATH_A, RF_CHNLBW, RFREG_OFFSET_MASK, + rtlphy->rfreg_chnlval[0]); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", bandwidth); + break; + } +} + +void rtl88e_phy_rf6052_set_cck_txpower(struct ieee80211_hw *hw, + u8 *plevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u32 tx_agc[2] = {0, 0}, tmpval; + bool turbo_scanoff = false; + u8 idx1, idx2; + u8 *ptr; + u8 direction; + u32 pwrtrac_value; + + if (rtlefuse->eeprom_regulatory != 0) + turbo_scanoff = true; + + if (mac->act_scanning == true) { + tx_agc[RF90_PATH_A] = 0x3f3f3f3f; + tx_agc[RF90_PATH_B] = 0x3f3f3f3f; + + if (turbo_scanoff) { + for (idx1 = RF90_PATH_A; idx1 <= RF90_PATH_B; idx1++) { + tx_agc[idx1] = plevel[idx1] | + (plevel[idx1] << 8) | + (plevel[idx1] << 16) | + (plevel[idx1] << 24); + } + } + } else { + for (idx1 = RF90_PATH_A; idx1 <= RF90_PATH_B; idx1++) { + tx_agc[idx1] = plevel[idx1] | (plevel[idx1] << 8) | + (plevel[idx1] << 16) | + (plevel[idx1] << 24); + } + + if (rtlefuse->eeprom_regulatory == 0) { + tmpval = (rtlphy->mcs_offset[0][6]) + + (rtlphy->mcs_offset[0][7] << 8); + tx_agc[RF90_PATH_A] += tmpval; + + tmpval = (rtlphy->mcs_offset[0][14]) + + (rtlphy->mcs_offset[0][15] << 24); + tx_agc[RF90_PATH_B] += tmpval; + } + } + + for (idx1 = RF90_PATH_A; idx1 <= RF90_PATH_B; idx1++) { + ptr = (u8 *)(&(tx_agc[idx1])); + for (idx2 = 0; idx2 < 4; idx2++) { + if (*ptr > RF6052_MAX_TX_PWR) + *ptr = RF6052_MAX_TX_PWR; + ptr++; + } + } + rtl88e_dm_txpower_track_adjust(hw, 1, &direction, &pwrtrac_value); + if (direction == 1) { + tx_agc[0] += pwrtrac_value; + tx_agc[1] += pwrtrac_value; + } else if (direction == 2) { + tx_agc[0] -= pwrtrac_value; + tx_agc[1] -= pwrtrac_value; + } + tmpval = tx_agc[RF90_PATH_A] & 0xff; + rtl_set_bbreg(hw, RTXAGC_A_CCK1_MCS32, MASKBYTE1, tmpval); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 1M (rf-A) = 0x%x (reg 0x%x)\n", tmpval, + RTXAGC_A_CCK1_MCS32); + + tmpval = tx_agc[RF90_PATH_A] >> 8; + + rtl_set_bbreg(hw, RTXAGC_B_CCK11_A_CCK2_11, 0xffffff00, tmpval); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 2~11M (rf-A) = 0x%x (reg 0x%x)\n", tmpval, + RTXAGC_B_CCK11_A_CCK2_11); + + tmpval = tx_agc[RF90_PATH_B] >> 24; + rtl_set_bbreg(hw, RTXAGC_B_CCK11_A_CCK2_11, MASKBYTE0, tmpval); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 11M (rf-B) = 0x%x (reg 0x%x)\n", tmpval, + RTXAGC_B_CCK11_A_CCK2_11); + + tmpval = tx_agc[RF90_PATH_B] & 0x00ffffff; + rtl_set_bbreg(hw, RTXAGC_B_CCK1_55_MCS32, 0xffffff00, tmpval); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 1~5.5M (rf-B) = 0x%x (reg 0x%x)\n", tmpval, + RTXAGC_B_CCK1_55_MCS32); +} + +static void rtl88e_phy_get_power_base(struct ieee80211_hw *hw, + u8 *pwrlvlofdm, u8 *pwrlvlbw20, + u8 *pwrlvlbw40, u8 channel, + u32 *ofdmbase, u32 *mcsbase) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 base0, base1; + u8 i, powerlevel[2]; + + for (i = 0; i < 2; i++) { + base0 = pwrlvlofdm[i]; + + base0 = (base0 << 24) | (base0 << 16) | + (base0 << 8) | base0; + *(ofdmbase + i) = base0; + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "[OFDM power base index rf(%c) = 0x%x]\n", + ((i == 0) ? 'A' : 'B'), *(ofdmbase + i)); + } + + for (i = 0; i < 2; i++) { + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20) + powerlevel[i] = pwrlvlbw20[i]; + else + powerlevel[i] = pwrlvlbw40[i]; + base1 = powerlevel[i]; + base1 = (base1 << 24) | + (base1 << 16) | (base1 << 8) | base1; + + *(mcsbase + i) = base1; + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "[MCS power base index rf(%c) = 0x%x]\n", + ((i == 0) ? 'A' : 'B'), *(mcsbase + i)); + } +} + +static void get_txpwr_by_reg(struct ieee80211_hw *hw, u8 chan, u8 index, + u32 *base0, u32 *base1, u32 *outval) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 i, chg = 0, pwr_lim[4], pwr_diff = 0, cust_pwr_dif; + u32 writeval, cust_lim, rf, tmp; + u8 ch = chan - 1; + u8 j; + + for (rf = 0; rf < 2; rf++) { + j = index + (rf ? 8 : 0); + tmp = ((index < 2) ? base0[rf] : base1[rf]); + switch (rtlefuse->eeprom_regulatory) { + case 0: + chg = 0; + + writeval = rtlphy->mcs_offset[chg][j] + tmp; + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "RTK better performance, " + "writeval(%c) = 0x%x\n", + ((rf == 0) ? 'A' : 'B'), writeval); + break; + case 1: + if (rtlphy->pwrgroup_cnt == 1) { + chg = 0; + } else { + chg = chan / 3; + if (chan == 14) + chg = 5; + } + writeval = rtlphy->mcs_offset[chg][j] + tmp; + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Realtek regulatory, 20MHz, writeval(%c) = 0x%x\n", + ((rf == 0) ? 'A' : 'B'), writeval); + break; + case 2: + writeval = ((index < 2) ? base0[rf] : base1[rf]); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Better regulatory, writeval(%c) = 0x%x\n", + ((rf == 0) ? 'A' : 'B'), writeval); + break; + case 3: + chg = 0; + + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20_40) { + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "customer's limit, 40MHz rf(%c) = 0x%x\n", + ((rf == 0) ? 'A' : 'B'), + rtlefuse->pwrgroup_ht40[rf][ch]); + } else { + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "customer's limit, 20MHz rf(%c) = 0x%x\n", + ((rf == 0) ? 'A' : 'B'), + rtlefuse->pwrgroup_ht20[rf][ch]); + } + + if (index < 2) + pwr_diff = rtlefuse->txpwr_legacyhtdiff[rf][ch]; + else if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20) + pwr_diff = rtlefuse->txpwr_ht20diff[rf][ch]; + + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20_40) + cust_pwr_dif = rtlefuse->pwrgroup_ht40[rf][ch]; + else + cust_pwr_dif = rtlefuse->pwrgroup_ht20[rf][ch]; + + if (pwr_diff > cust_pwr_dif) + pwr_diff = 0; + else + pwr_diff = cust_pwr_dif - pwr_diff; + + for (i = 0; i < 4; i++) { + pwr_lim[i] = (u8)((rtlphy->mcs_offset[chg][j] & + (0x7f << (i * 8))) >> (i * 8)); + + if (pwr_lim[i] > pwr_diff) + pwr_lim[i] = pwr_diff; + } + + cust_lim = (pwr_lim[3] << 24) | (pwr_lim[2] << 16) | + (pwr_lim[1] << 8) | (pwr_lim[0]); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Customer's limit rf(%c) = 0x%x\n", + ((rf == 0) ? 'A' : 'B'), cust_lim); + + writeval = cust_lim + tmp; + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Customer, writeval rf(%c) = 0x%x\n", + ((rf == 0) ? 'A' : 'B'), writeval); + break; + default: + chg = 0; + writeval = rtlphy->mcs_offset[chg][j] + tmp; + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "RTK better performance, writeval " + "rf(%c) = 0x%x\n", + ((rf == 0) ? 'A' : 'B'), writeval); + break; + } + + if (rtlpriv->dm.dynamic_txhighpower_lvl == TXHIGHPWRLEVEL_BT1) + writeval = writeval - 0x06060606; + else if (rtlpriv->dm.dynamic_txhighpower_lvl == + TXHIGHPWRLEVEL_BT2) + writeval -= 0x0c0c0c0c; + *(outval + rf) = writeval; + } +} + +static void write_ofdm_pwr(struct ieee80211_hw *hw, u8 index, u32 *pvalue) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u16 regoffset_a[6] = { + RTXAGC_A_RATE18_06, RTXAGC_A_RATE54_24, + RTXAGC_A_MCS03_MCS00, RTXAGC_A_MCS07_MCS04, + RTXAGC_A_MCS11_MCS08, RTXAGC_A_MCS15_MCS12 + }; + u16 regoffset_b[6] = { + RTXAGC_B_RATE18_06, RTXAGC_B_RATE54_24, + RTXAGC_B_MCS03_MCS00, RTXAGC_B_MCS07_MCS04, + RTXAGC_B_MCS11_MCS08, RTXAGC_B_MCS15_MCS12 + }; + u8 i, rf, pwr_val[4]; + u32 writeval; + u16 regoffset; + + for (rf = 0; rf < 2; rf++) { + writeval = pvalue[rf]; + for (i = 0; i < 4; i++) { + pwr_val[i] = (u8) ((writeval & (0x7f << + (i * 8))) >> (i * 8)); + + if (pwr_val[i] > RF6052_MAX_TX_PWR) + pwr_val[i] = RF6052_MAX_TX_PWR; + } + writeval = (pwr_val[3] << 24) | (pwr_val[2] << 16) | + (pwr_val[1] << 8) | pwr_val[0]; + + if (rf == 0) + regoffset = regoffset_a[index]; + else + regoffset = regoffset_b[index]; + rtl_set_bbreg(hw, regoffset, MASKDWORD, writeval); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Set 0x%x = %08x\n", regoffset, writeval); + } +} + +void rtl88e_phy_rf6052_set_ofdm_txpower(struct ieee80211_hw *hw, + u8 *pwrlvlofdm, + u8 *pwrlvlbw20, + u8 *pwrlvlbw40, u8 chan) +{ + u32 writeval[2], base0[2], base1[2]; + u8 index; + u8 direction; + u32 pwrtrac_value; + + rtl88e_phy_get_power_base(hw, pwrlvlofdm, pwrlvlbw20, + pwrlvlbw40, chan, &base0[0], + &base1[0]); + + rtl88e_dm_txpower_track_adjust(hw, 1, &direction, &pwrtrac_value); + + for (index = 0; index < 6; index++) { + get_txpwr_by_reg(hw, chan, index, &base0[0], &base1[0], + &writeval[0]); + if (direction == 1) { + writeval[0] += pwrtrac_value; + writeval[1] += pwrtrac_value; + } else if (direction == 2) { + writeval[0] -= pwrtrac_value; + writeval[1] -= pwrtrac_value; + } + write_ofdm_pwr(hw, index, &writeval[0]); + } +} + +static bool rf6052_conf_para(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 u4val = 0; + u8 rfpath; + bool rtstatus = true; + struct bb_reg_def *pphyreg; + + for (rfpath = 0; rfpath < rtlphy->num_total_rfpath; rfpath++) { + pphyreg = &rtlphy->phyreg_def[rfpath]; + + switch (rfpath) { + case RF90_PATH_A: + case RF90_PATH_C: + u4val = rtl_get_bbreg(hw, pphyreg->rfintfs, + BRFSI_RFENV); + break; + case RF90_PATH_B: + case RF90_PATH_D: + u4val = rtl_get_bbreg(hw, pphyreg->rfintfs, + BRFSI_RFENV << 16); + break; + } + + rtl_set_bbreg(hw, pphyreg->rfintfe, BRFSI_RFENV << 16, 0x1); + udelay(1); + + rtl_set_bbreg(hw, pphyreg->rfintfo, BRFSI_RFENV, 0x1); + udelay(1); + + rtl_set_bbreg(hw, pphyreg->rfhssi_para2, + B3WIREADDREAALENGTH, 0x0); + udelay(1); + + rtl_set_bbreg(hw, pphyreg->rfhssi_para2, B3WIREDATALENGTH, 0x0); + udelay(1); + + switch (rfpath) { + case RF90_PATH_A: + rtstatus = rtl88e_phy_config_rf_with_headerfile(hw, + (enum radio_path)rfpath); + break; + case RF90_PATH_B: + rtstatus = rtl88e_phy_config_rf_with_headerfile(hw, + (enum radio_path)rfpath); + break; + case RF90_PATH_C: + break; + case RF90_PATH_D: + break; + } + + switch (rfpath) { + case RF90_PATH_A: + case RF90_PATH_C: + rtl_set_bbreg(hw, pphyreg->rfintfs, BRFSI_RFENV, u4val); + break; + case RF90_PATH_B: + case RF90_PATH_D: + rtl_set_bbreg(hw, pphyreg->rfintfs, BRFSI_RFENV << 16, + u4val); + break; + } + + if (rtstatus != true) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Radio[%d] Fail!!", rfpath); + return false; + } + } + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "\n"); + return rtstatus; +} + +bool rtl88e_phy_rf6052_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + if (rtlphy->rf_type == RF_1T1R) + rtlphy->num_total_rfpath = 1; + else + rtlphy->num_total_rfpath = 2; + + return rf6052_conf_para(hw); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/rf.h b/drivers/net/wireless/rtlwifi/rtl8188ee/rf.h new file mode 100644 index 0000000..a39a2a3 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/rf.h @@ -0,0 +1,46 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92C_RF_H__ +#define __RTL92C_RF_H__ + +#define RF6052_MAX_TX_PWR 0x3F +#define RF6052_MAX_REG 0x3F + +void rtl88e_phy_rf6052_set_bandwidth(struct ieee80211_hw *hw, + u8 bandwidth); +void rtl88e_phy_rf6052_set_cck_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel); +void rtl88e_phy_rf6052_set_ofdm_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel_ofdm, + u8 *ppowerlevel_bw20, + u8 *ppowerlevel_bw40, u8 channel); +bool rtl88e_phy_rf6052_config(struct ieee80211_hw *hw); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/sw.c b/drivers/net/wireless/rtlwifi/rtl8188ee/sw.c new file mode 100644 index 0000000..11b660d --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/sw.c @@ -0,0 +1,408 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../core.h" +#include "../pci.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "dm.h" +#include "hw.h" +#include "sw.h" +#include "trx.h" +#include "led.h" +#include "table.h" + +#include +#include + +static void rtl88e_init_aspm_vars(struct ieee80211_hw *hw) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + /*close ASPM for AMD defaultly */ + rtlpci->const_amdpci_aspm = 0; + + /* ASPM PS mode. + * 0 - Disable ASPM, + * 1 - Enable ASPM without Clock Req, + * 2 - Enable ASPM with Clock Req, + * 3 - Alwyas Enable ASPM with Clock Req, + * 4 - Always Enable ASPM without Clock Req. + * set defult to RTL8192CE:3 RTL8192E:2 + */ + rtlpci->const_pci_aspm = 3; + + /*Setting for PCI-E device */ + rtlpci->const_devicepci_aspm_setting = 0x03; + + /*Setting for PCI-E bridge */ + rtlpci->const_hostpci_aspm_setting = 0x02; + + /* In Hw/Sw Radio Off situation. + * 0 - Default, + * 1 - From ASPM setting without low Mac Pwr, + * 2 - From ASPM setting with low Mac Pwr, + * 3 - Bus D3 + * set default to RTL8192CE:0 RTL8192SE:2 + */ + rtlpci->const_hwsw_rfoff_d3 = 0; + + /* This setting works for those device with + * backdoor ASPM setting such as EPHY setting. + * 0 - Not support ASPM, + * 1 - Support ASPM, + * 2 - According to chipset. + */ + rtlpci->const_support_pciaspm = 1; +} + +int rtl88e_init_sw_vars(struct ieee80211_hw *hw) +{ + int err = 0; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u8 tid; + + rtl8188ee_bt_reg_init(hw); + + rtlpriv->dm.dm_initialgain_enable = 1; + rtlpriv->dm.dm_flag = 0; + rtlpriv->dm.disable_framebursting = 0; + rtlpriv->dm.thermalvalue = 0; + rtlpci->transmit_config = CFENDFORM | BIT(15); + + /* compatible 5G band 88ce just 2.4G band & smsp */ + rtlpriv->rtlhal.current_bandtype = BAND_ON_2_4G; + rtlpriv->rtlhal.bandset = BAND_ON_2_4G; + rtlpriv->rtlhal.macphymode = SINGLEMAC_SINGLEPHY; + + rtlpci->receive_config = (RCR_APPFCS | + RCR_APP_MIC | + RCR_APP_ICV | + RCR_APP_PHYST_RXFF | + RCR_HTC_LOC_CTRL | + RCR_AMF | + RCR_ACF | + RCR_ADF | + RCR_AICV | + RCR_ACRC32 | + RCR_AB | + RCR_AM | + RCR_APM | + 0); + + rtlpci->irq_mask[0] = + (u32) (IMR_PSTIMEOUT | + IMR_HSISR_IND_ON_INT | + IMR_C2HCMD | + IMR_HIGHDOK | + IMR_MGNTDOK | + IMR_BKDOK | + IMR_BEDOK | + IMR_VIDOK | + IMR_VODOK | + IMR_RDU | + IMR_ROK | + 0); + rtlpci->irq_mask[1] = (u32) (IMR_RXFOVW | 0); + rtlpci->sys_irq_mask = (u32) (HSIMR_PDN_INT_EN | HSIMR_RON_INT_EN); + + /* for debug level */ + rtlpriv->dbg.global_debuglevel = rtlpriv->cfg->mod_params->debug; + /* for LPS & IPS */ + rtlpriv->psc.inactiveps = rtlpriv->cfg->mod_params->inactiveps; + rtlpriv->psc.swctrl_lps = rtlpriv->cfg->mod_params->swctrl_lps; + rtlpriv->psc.fwctrl_lps = rtlpriv->cfg->mod_params->fwctrl_lps; + if (!rtlpriv->psc.inactiveps) + pr_info("rtl8188ee: Power Save off (module option)\n"); + if (!rtlpriv->psc.fwctrl_lps) + pr_info("rtl8188ee: FW Power Save off (module option)\n"); + rtlpriv->psc.reg_fwctrl_lps = 3; + rtlpriv->psc.reg_max_lps_awakeintvl = 5; + /* for ASPM, you can close aspm through + * set const_support_pciaspm = 0 + */ + rtl88e_init_aspm_vars(hw); + + if (rtlpriv->psc.reg_fwctrl_lps == 1) + rtlpriv->psc.fwctrl_psmode = FW_PS_MIN_MODE; + else if (rtlpriv->psc.reg_fwctrl_lps == 2) + rtlpriv->psc.fwctrl_psmode = FW_PS_MAX_MODE; + else if (rtlpriv->psc.reg_fwctrl_lps == 3) + rtlpriv->psc.fwctrl_psmode = FW_PS_DTIM_MODE; + + /* for firmware buf */ + rtlpriv->rtlhal.pfirmware = vmalloc(0x8000); + if (!rtlpriv->rtlhal.pfirmware) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Can't alloc buffer for fw.\n"); + return 1; + } + + rtlpriv->cfg->fw_name = "rtlwifi/rtl8188efw.bin"; + rtlpriv->max_fw_size = 0x8000; + pr_info("Using firmware %s\n", rtlpriv->cfg->fw_name); + err = request_firmware_nowait(THIS_MODULE, 1, rtlpriv->cfg->fw_name, + rtlpriv->io.dev, GFP_KERNEL, hw, + rtl_fw_cb); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Failed to request firmware!\n"); + return 1; + } + + /* for early mode */ + rtlpriv->rtlhal.earlymode_enable = false; + rtlpriv->rtlhal.max_earlymode_num = 10; + for (tid = 0; tid < 8; tid++) + skb_queue_head_init(&rtlpriv->mac80211.skb_waitq[tid]); + + /*low power */ + rtlpriv->psc.low_power_enable = false; + if (rtlpriv->psc.low_power_enable) { + init_timer(&rtlpriv->works.fw_clockoff_timer); + setup_timer(&rtlpriv->works.fw_clockoff_timer, + rtl88ee_fw_clk_off_timer_callback, + (unsigned long)hw); + } + + init_timer(&rtlpriv->works.fast_antenna_training_timer); + setup_timer(&rtlpriv->works.fast_antenna_training_timer, + rtl88e_dm_fast_antenna_training_callback, + (unsigned long)hw); + return err; +} + +void rtl88e_deinit_sw_vars(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (rtlpriv->rtlhal.pfirmware) { + vfree(rtlpriv->rtlhal.pfirmware); + rtlpriv->rtlhal.pfirmware = NULL; + } + + if (rtlpriv->psc.low_power_enable) + del_timer_sync(&rtlpriv->works.fw_clockoff_timer); + + del_timer_sync(&rtlpriv->works.fast_antenna_training_timer); +} + +static struct rtl_hal_ops rtl8188ee_hal_ops = { + .init_sw_vars = rtl88e_init_sw_vars, + .deinit_sw_vars = rtl88e_deinit_sw_vars, + .read_eeprom_info = rtl88ee_read_eeprom_info, + .interrupt_recognized = rtl88ee_interrupt_recognized,/*need check*/ + .hw_init = rtl88ee_hw_init, + .hw_disable = rtl88ee_card_disable, + .hw_suspend = rtl88ee_suspend, + .hw_resume = rtl88ee_resume, + .enable_interrupt = rtl88ee_enable_interrupt, + .disable_interrupt = rtl88ee_disable_interrupt, + .set_network_type = rtl88ee_set_network_type, + .set_chk_bssid = rtl88ee_set_check_bssid, + .set_qos = rtl88ee_set_qos, + .set_bcn_reg = rtl88ee_set_beacon_related_registers, + .set_bcn_intv = rtl88ee_set_beacon_interval, + .update_interrupt_mask = rtl88ee_update_interrupt_mask, + .get_hw_reg = rtl88ee_get_hw_reg, + .set_hw_reg = rtl88ee_set_hw_reg, + .update_rate_tbl = rtl88ee_update_hal_rate_tbl, + .fill_tx_desc = rtl88ee_tx_fill_desc, + .fill_tx_cmddesc = rtl88ee_tx_fill_cmddesc, + .query_rx_desc = rtl88ee_rx_query_desc, + .set_channel_access = rtl88ee_update_channel_access_setting, + .radio_onoff_checking = rtl88ee_gpio_radio_on_off_checking, + .set_bw_mode = rtl88e_phy_set_bw_mode, + .switch_channel = rtl88e_phy_sw_chnl, + .dm_watchdog = rtl88e_dm_watchdog, + .scan_operation_backup = rtl88e_phy_scan_operation_backup, + .set_rf_power_state = rtl88e_phy_set_rf_power_state, + .led_control = rtl88ee_led_control, + .set_desc = rtl88ee_set_desc, + .get_desc = rtl88ee_get_desc, + .tx_polling = rtl88ee_tx_polling, + .enable_hw_sec = rtl88ee_enable_hw_security_config, + .set_key = rtl88ee_set_key, + .init_sw_leds = rtl88ee_init_sw_leds, + .allow_all_destaddr = rtl88ee_allow_all_destaddr, + .get_bbreg = rtl88e_phy_query_bb_reg, + .set_bbreg = rtl88e_phy_set_bb_reg, + .get_rfreg = rtl88e_phy_query_rf_reg, + .set_rfreg = rtl88e_phy_set_rf_reg, +}; + +static struct rtl_mod_params rtl88ee_mod_params = { + .sw_crypto = false, + .inactiveps = true, + .swctrl_lps = false, + .fwctrl_lps = true, + .debug = DBG_EMERG, +}; + +static struct rtl_hal_cfg rtl88ee_hal_cfg = { + .bar_id = 2, + .write_readback = true, + .name = "rtl88e_pci", + .ops = &rtl8188ee_hal_ops, + .mod_params = &rtl88ee_mod_params, + + .maps[SYS_ISO_CTRL] = REG_SYS_ISO_CTRL, + .maps[SYS_FUNC_EN] = REG_SYS_FUNC_EN, + .maps[SYS_CLK] = REG_SYS_CLKR, + .maps[MAC_RCR_AM] = AM, + .maps[MAC_RCR_AB] = AB, + .maps[MAC_RCR_ACRC32] = ACRC32, + .maps[MAC_RCR_ACF] = ACF, + .maps[MAC_RCR_AAP] = AAP, + + .maps[EFUSE_ACCESS] = REG_EFUSE_ACCESS, + + .maps[EFUSE_TEST] = REG_EFUSE_TEST, + .maps[EFUSE_CTRL] = REG_EFUSE_CTRL, + .maps[EFUSE_CLK] = 0, + .maps[EFUSE_CLK_CTRL] = REG_EFUSE_CTRL, + .maps[EFUSE_PWC_EV12V] = PWC_EV12V, + .maps[EFUSE_FEN_ELDR] = FEN_ELDR, + .maps[EFUSE_LOADER_CLK_EN] = LOADER_CLK_EN, + .maps[EFUSE_ANA8M] = ANA8M, + .maps[EFUSE_HWSET_MAX_SIZE] = HWSET_MAX_SIZE, + .maps[EFUSE_MAX_SECTION_MAP] = EFUSE_MAX_SECTION, + .maps[EFUSE_REAL_CONTENT_SIZE] = EFUSE_REAL_CONTENT_LEN, + .maps[EFUSE_OOB_PROTECT_BYTES_LEN] = EFUSE_OOB_PROTECT_BYTES, + + .maps[RWCAM] = REG_CAMCMD, + .maps[WCAMI] = REG_CAMWRITE, + .maps[RCAMO] = REG_CAMREAD, + .maps[CAMDBG] = REG_CAMDBG, + .maps[SECR] = REG_SECCFG, + .maps[SEC_CAM_NONE] = CAM_NONE, + .maps[SEC_CAM_WEP40] = CAM_WEP40, + .maps[SEC_CAM_TKIP] = CAM_TKIP, + .maps[SEC_CAM_AES] = CAM_AES, + .maps[SEC_CAM_WEP104] = CAM_WEP104, + + .maps[RTL_IMR_BCNDMAINT6] = IMR_BCNDMAINT6, + .maps[RTL_IMR_BCNDMAINT5] = IMR_BCNDMAINT5, + .maps[RTL_IMR_BCNDMAINT4] = IMR_BCNDMAINT4, + .maps[RTL_IMR_BCNDMAINT3] = IMR_BCNDMAINT3, + .maps[RTL_IMR_BCNDMAINT2] = IMR_BCNDMAINT2, + .maps[RTL_IMR_BCNDMAINT1] = IMR_BCNDMAINT1, +/* .maps[RTL_IMR_BCNDOK8] = IMR_BCNDOK8, */ /*need check*/ + .maps[RTL_IMR_BCNDOK7] = IMR_BCNDOK7, + .maps[RTL_IMR_BCNDOK6] = IMR_BCNDOK6, + .maps[RTL_IMR_BCNDOK5] = IMR_BCNDOK5, + .maps[RTL_IMR_BCNDOK4] = IMR_BCNDOK4, + .maps[RTL_IMR_BCNDOK3] = IMR_BCNDOK3, + .maps[RTL_IMR_BCNDOK2] = IMR_BCNDOK2, + .maps[RTL_IMR_BCNDOK1] = IMR_BCNDOK1, +/* .maps[RTL_IMR_TIMEOUT2] = IMR_TIMEOUT2,*/ +/* .maps[RTL_IMR_TIMEOUT1] = IMR_TIMEOUT1,*/ + + .maps[RTL_IMR_TXFOVW] = IMR_TXFOVW, + .maps[RTL_IMR_PSTIMEOUT] = IMR_PSTIMEOUT, + .maps[RTL_IMR_BCNINT] = IMR_BCNDMAINT0, + .maps[RTL_IMR_RXFOVW] = IMR_RXFOVW, + .maps[RTL_IMR_RDU] = IMR_RDU, + .maps[RTL_IMR_ATIMEND] = IMR_ATIMEND, + .maps[RTL_IMR_BDOK] = IMR_BCNDOK0, + .maps[RTL_IMR_MGNTDOK] = IMR_MGNTDOK, + .maps[RTL_IMR_TBDER] = IMR_TBDER, + .maps[RTL_IMR_HIGHDOK] = IMR_HIGHDOK, + .maps[RTL_IMR_TBDOK] = IMR_TBDOK, + .maps[RTL_IMR_BKDOK] = IMR_BKDOK, + .maps[RTL_IMR_BEDOK] = IMR_BEDOK, + .maps[RTL_IMR_VIDOK] = IMR_VIDOK, + .maps[RTL_IMR_VODOK] = IMR_VODOK, + .maps[RTL_IMR_ROK] = IMR_ROK, + .maps[RTL_IBSS_INT_MASKS] = (IMR_BCNDMAINT0 | IMR_TBDOK | IMR_TBDER), + + .maps[RTL_RC_CCK_RATE1M] = DESC92C_RATE1M, + .maps[RTL_RC_CCK_RATE2M] = DESC92C_RATE2M, + .maps[RTL_RC_CCK_RATE5_5M] = DESC92C_RATE5_5M, + .maps[RTL_RC_CCK_RATE11M] = DESC92C_RATE11M, + .maps[RTL_RC_OFDM_RATE6M] = DESC92C_RATE6M, + .maps[RTL_RC_OFDM_RATE9M] = DESC92C_RATE9M, + .maps[RTL_RC_OFDM_RATE12M] = DESC92C_RATE12M, + .maps[RTL_RC_OFDM_RATE18M] = DESC92C_RATE18M, + .maps[RTL_RC_OFDM_RATE24M] = DESC92C_RATE24M, + .maps[RTL_RC_OFDM_RATE36M] = DESC92C_RATE36M, + .maps[RTL_RC_OFDM_RATE48M] = DESC92C_RATE48M, + .maps[RTL_RC_OFDM_RATE54M] = DESC92C_RATE54M, + + .maps[RTL_RC_HT_RATEMCS7] = DESC92C_RATEMCS7, + .maps[RTL_RC_HT_RATEMCS15] = DESC92C_RATEMCS15, +}; + +static DEFINE_PCI_DEVICE_TABLE(rtl88ee_pci_ids) = { + {RTL_PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8179, rtl88ee_hal_cfg)}, + {}, +}; + +MODULE_DEVICE_TABLE(pci, rtl88ee_pci_ids); + +MODULE_AUTHOR("zhiyuan_yang "); +MODULE_AUTHOR("Realtek WlanFAE "); +MODULE_AUTHOR("Larry Finger "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Realtek 8188E 802.11n PCI wireless"); +MODULE_FIRMWARE("rtlwifi/rtl8188efw.bin"); + +module_param_named(swenc, rtl88ee_mod_params.sw_crypto, bool, 0444); +module_param_named(debug, rtl88ee_mod_params.debug, int, 0444); +module_param_named(ips, rtl88ee_mod_params.inactiveps, bool, 0444); +module_param_named(swlps, rtl88ee_mod_params.swctrl_lps, bool, 0444); +module_param_named(fwlps, rtl88ee_mod_params.fwctrl_lps, bool, 0444); +MODULE_PARM_DESC(swenc, "Set to 1 for software crypto (default 0)\n"); +MODULE_PARM_DESC(ips, "Set to 0 to not use link power save (default 1)\n"); +MODULE_PARM_DESC(swlps, "Set to 1 to use SW control power save (default 0)\n"); +MODULE_PARM_DESC(fwlps, "Set to 1 to use FW control power save (default 1)\n"); +MODULE_PARM_DESC(debug, "Set debug level (0-5) (default 0)"); + +compat_pci_suspend(rtl_pci_suspend); +compat_pci_resume(rtl_pci_resume); + +static SIMPLE_DEV_PM_OPS(rtlwifi_pm_ops, rtl_pci_suspend, rtl_pci_resume); + +static struct pci_driver rtl88ee_driver = { + .name = KBUILD_MODNAME, + .id_table = rtl88ee_pci_ids, + .probe = rtl_pci_probe, + .remove = rtl_pci_disconnect, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) + .driver.pm = &rtlwifi_pm_ops, +#elif defined(CONFIG_PM_SLEEP) + .suspend = rtl_pci_suspend_compat, + .resume = rtl_pci_resume_compat, +#endif +}; + +module_pci_driver(rtl88ee_driver); diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/sw.h b/drivers/net/wireless/rtlwifi/rtl8188ee/sw.h new file mode 100644 index 0000000..85e02b3 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/sw.h @@ -0,0 +1,36 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CE_SW_H__ +#define __RTL92CE_SW_H__ + +int rtl88e_init_sw_vars(struct ieee80211_hw *hw); +void rtl88e_deinit_sw_vars(struct ieee80211_hw *hw); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/table.c b/drivers/net/wireless/rtlwifi/rtl8188ee/table.c new file mode 100644 index 0000000..fad373f --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/table.c @@ -0,0 +1,643 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Created on 2010/ 5/18, 1:41 + * + * Larry Finger + * + *****************************************************************************/ + +#include "table.h" + +u32 RTL8188EEPHY_REG_1TARRAY[] = { + 0x800, 0x80040000, + 0x804, 0x00000003, + 0x808, 0x0000FC00, + 0x80C, 0x0000000A, + 0x810, 0x10001331, + 0x814, 0x020C3D10, + 0x818, 0x02200385, + 0x81C, 0x00000000, + 0x820, 0x01000100, + 0x824, 0x00390204, + 0x828, 0x00000000, + 0x82C, 0x00000000, + 0x830, 0x00000000, + 0x834, 0x00000000, + 0x838, 0x00000000, + 0x83C, 0x00000000, + 0x840, 0x00010000, + 0x844, 0x00000000, + 0x848, 0x00000000, + 0x84C, 0x00000000, + 0x850, 0x00000000, + 0x854, 0x00000000, + 0x858, 0x569A11A9, + 0x85C, 0x01000014, + 0x860, 0x66F60110, + 0x864, 0x061F0649, + 0x868, 0x00000000, + 0x86C, 0x27272700, + 0x870, 0x07000760, + 0x874, 0x25004000, + 0x878, 0x00000808, + 0x87C, 0x00000000, + 0x880, 0xB0000C1C, + 0x884, 0x00000001, + 0x888, 0x00000000, + 0x88C, 0xCCC000C0, + 0x890, 0x00000800, + 0x894, 0xFFFFFFFE, + 0x898, 0x40302010, + 0x89C, 0x00706050, + 0x900, 0x00000000, + 0x904, 0x00000023, + 0x908, 0x00000000, + 0x90C, 0x81121111, + 0x910, 0x00000002, + 0x914, 0x00000201, + 0xA00, 0x00D047C8, + 0xA04, 0x80FF000C, + 0xA08, 0x8C838300, + 0xA0C, 0x2E7F120F, + 0xA10, 0x9500BB78, + 0xA14, 0x1114D028, + 0xA18, 0x00881117, + 0xA1C, 0x89140F00, + 0xA20, 0x1A1B0000, + 0xA24, 0x090E1317, + 0xA28, 0x00000204, + 0xA2C, 0x00D30000, + 0xA70, 0x101FBF00, + 0xA74, 0x00000007, + 0xA78, 0x00000900, + 0xA7C, 0x225B0606, + 0xA80, 0x218075B1, + 0xB2C, 0x80000000, + 0xC00, 0x48071D40, + 0xC04, 0x03A05611, + 0xC08, 0x000000E4, + 0xC0C, 0x6C6C6C6C, + 0xC10, 0x08800000, + 0xC14, 0x40000100, + 0xC18, 0x08800000, + 0xC1C, 0x40000100, + 0xC20, 0x00000000, + 0xC24, 0x00000000, + 0xC28, 0x00000000, + 0xC2C, 0x00000000, + 0xC30, 0x69E9AC47, + 0xC34, 0x469652AF, + 0xC38, 0x49795994, + 0xC3C, 0x0A97971C, + 0xC40, 0x1F7C403F, + 0xC44, 0x000100B7, + 0xC48, 0xEC020107, + 0xC4C, 0x007F037F, + 0xC50, 0x69553420, + 0xC54, 0x43BC0094, + 0xC58, 0x00013169, + 0xC5C, 0x00250492, + 0xC60, 0x00000000, + 0xC64, 0x7112848B, + 0xC68, 0x47C00BFF, + 0xC6C, 0x00000036, + 0xC70, 0x2C7F000D, + 0xC74, 0x020610DB, + 0xC78, 0x0000001F, + 0xC7C, 0x00B91612, + 0xC80, 0x390000E4, + 0xC84, 0x20F60000, + 0xC88, 0x40000100, + 0xC8C, 0x20200000, + 0xC90, 0x00091521, + 0xC94, 0x00000000, + 0xC98, 0x00121820, + 0xC9C, 0x00007F7F, + 0xCA0, 0x00000000, + 0xCA4, 0x000300A0, + 0xCA8, 0x00000000, + 0xCAC, 0x00000000, + 0xCB0, 0x00000000, + 0xCB4, 0x00000000, + 0xCB8, 0x00000000, + 0xCBC, 0x28000000, + 0xCC0, 0x00000000, + 0xCC4, 0x00000000, + 0xCC8, 0x00000000, + 0xCCC, 0x00000000, + 0xCD0, 0x00000000, + 0xCD4, 0x00000000, + 0xCD8, 0x64B22427, + 0xCDC, 0x00766932, + 0xCE0, 0x00222222, + 0xCE4, 0x00000000, + 0xCE8, 0x37644302, + 0xCEC, 0x2F97D40C, + 0xD00, 0x00000740, + 0xD04, 0x00020401, + 0xD08, 0x0000907F, + 0xD0C, 0x20010201, + 0xD10, 0xA0633333, + 0xD14, 0x3333BC43, + 0xD18, 0x7A8F5B6F, + 0xD2C, 0xCC979975, + 0xD30, 0x00000000, + 0xD34, 0x80608000, + 0xD38, 0x00000000, + 0xD3C, 0x00127353, + 0xD40, 0x00000000, + 0xD44, 0x00000000, + 0xD48, 0x00000000, + 0xD4C, 0x00000000, + 0xD50, 0x6437140A, + 0xD54, 0x00000000, + 0xD58, 0x00000282, + 0xD5C, 0x30032064, + 0xD60, 0x4653DE68, + 0xD64, 0x04518A3C, + 0xD68, 0x00002101, + 0xD6C, 0x2A201C16, + 0xD70, 0x1812362E, + 0xD74, 0x322C2220, + 0xD78, 0x000E3C24, + 0xE00, 0x2D2D2D2D, + 0xE04, 0x2D2D2D2D, + 0xE08, 0x0390272D, + 0xE10, 0x2D2D2D2D, + 0xE14, 0x2D2D2D2D, + 0xE18, 0x2D2D2D2D, + 0xE1C, 0x2D2D2D2D, + 0xE28, 0x00000000, + 0xE30, 0x1000DC1F, + 0xE34, 0x10008C1F, + 0xE38, 0x02140102, + 0xE3C, 0x681604C2, + 0xE40, 0x01007C00, + 0xE44, 0x01004800, + 0xE48, 0xFB000000, + 0xE4C, 0x000028D1, + 0xE50, 0x1000DC1F, + 0xE54, 0x10008C1F, + 0xE58, 0x02140102, + 0xE5C, 0x28160D05, + 0xE60, 0x00000008, + 0xE68, 0x001B25A4, + 0xE6C, 0x00C00014, + 0xE70, 0x00C00014, + 0xE74, 0x01000014, + 0xE78, 0x01000014, + 0xE7C, 0x01000014, + 0xE80, 0x01000014, + 0xE84, 0x00C00014, + 0xE88, 0x01000014, + 0xE8C, 0x00C00014, + 0xED0, 0x00C00014, + 0xED4, 0x00C00014, + 0xED8, 0x00C00014, + 0xEDC, 0x00000014, + 0xEE0, 0x00000014, + 0xEEC, 0x01C00014, + 0xF14, 0x00000003, + 0xF4C, 0x00000000, + 0xF00, 0x00000300, + +}; + +u32 RTL8188EEPHY_REG_ARRAY_PG[] = { + 0xE00, 0xFFFFFFFF, 0x06070809, + 0xE04, 0xFFFFFFFF, 0x02020405, + 0xE08, 0x0000FF00, 0x00000006, + 0x86C, 0xFFFFFF00, 0x00020400, + 0xE10, 0xFFFFFFFF, 0x08090A0B, + 0xE14, 0xFFFFFFFF, 0x01030607, + 0xE18, 0xFFFFFFFF, 0x08090A0B, + 0xE1C, 0xFFFFFFFF, 0x01030607, + 0xE00, 0xFFFFFFFF, 0x00000000, + 0xE04, 0xFFFFFFFF, 0x00000000, + 0xE08, 0x0000FF00, 0x00000000, + 0x86C, 0xFFFFFF00, 0x00000000, + 0xE10, 0xFFFFFFFF, 0x00000000, + 0xE14, 0xFFFFFFFF, 0x00000000, + 0xE18, 0xFFFFFFFF, 0x00000000, + 0xE1C, 0xFFFFFFFF, 0x00000000, + 0xE00, 0xFFFFFFFF, 0x02020202, + 0xE04, 0xFFFFFFFF, 0x00020202, + 0xE08, 0x0000FF00, 0x00000000, + 0x86C, 0xFFFFFF00, 0x00000000, + 0xE10, 0xFFFFFFFF, 0x04040404, + 0xE14, 0xFFFFFFFF, 0x00020404, + 0xE18, 0xFFFFFFFF, 0x00000000, + 0xE1C, 0xFFFFFFFF, 0x00000000, + 0xE00, 0xFFFFFFFF, 0x02020202, + 0xE04, 0xFFFFFFFF, 0x00020202, + 0xE08, 0x0000FF00, 0x00000000, + 0x86C, 0xFFFFFF00, 0x00000000, + 0xE10, 0xFFFFFFFF, 0x04040404, + 0xE14, 0xFFFFFFFF, 0x00020404, + 0xE18, 0xFFFFFFFF, 0x00000000, + 0xE1C, 0xFFFFFFFF, 0x00000000, + 0xE00, 0xFFFFFFFF, 0x00000000, + 0xE04, 0xFFFFFFFF, 0x00000000, + 0xE08, 0x0000FF00, 0x00000000, + 0x86C, 0xFFFFFF00, 0x00000000, + 0xE10, 0xFFFFFFFF, 0x00000000, + 0xE14, 0xFFFFFFFF, 0x00000000, + 0xE18, 0xFFFFFFFF, 0x00000000, + 0xE1C, 0xFFFFFFFF, 0x00000000, + 0xE00, 0xFFFFFFFF, 0x02020202, + 0xE04, 0xFFFFFFFF, 0x00020202, + 0xE08, 0x0000FF00, 0x00000000, + 0x86C, 0xFFFFFF00, 0x00000000, + 0xE10, 0xFFFFFFFF, 0x04040404, + 0xE14, 0xFFFFFFFF, 0x00020404, + 0xE18, 0xFFFFFFFF, 0x00000000, + 0xE1C, 0xFFFFFFFF, 0x00000000, + 0xE00, 0xFFFFFFFF, 0x00000000, + 0xE04, 0xFFFFFFFF, 0x00000000, + 0xE08, 0x0000FF00, 0x00000000, + 0x86C, 0xFFFFFF00, 0x00000000, + 0xE10, 0xFFFFFFFF, 0x00000000, + 0xE14, 0xFFFFFFFF, 0x00000000, + 0xE18, 0xFFFFFFFF, 0x00000000, + 0xE1C, 0xFFFFFFFF, 0x00000000, + 0xE00, 0xFFFFFFFF, 0x00000000, + 0xE04, 0xFFFFFFFF, 0x00000000, + 0xE08, 0x0000FF00, 0x00000000, + 0x86C, 0xFFFFFF00, 0x00000000, + 0xE10, 0xFFFFFFFF, 0x00000000, + 0xE14, 0xFFFFFFFF, 0x00000000, + 0xE18, 0xFFFFFFFF, 0x00000000, + 0xE1C, 0xFFFFFFFF, 0x00000000, + 0xE00, 0xFFFFFFFF, 0x00000000, + 0xE04, 0xFFFFFFFF, 0x00000000, + 0xE08, 0x0000FF00, 0x00000000, + 0x86C, 0xFFFFFF00, 0x00000000, + 0xE10, 0xFFFFFFFF, 0x00000000, + 0xE14, 0xFFFFFFFF, 0x00000000, + 0xE18, 0xFFFFFFFF, 0x00000000, + 0xE1C, 0xFFFFFFFF, 0x00000000, + 0xE00, 0xFFFFFFFF, 0x00000000, + 0xE04, 0xFFFFFFFF, 0x00000000, + 0xE08, 0x0000FF00, 0x00000000, + 0x86C, 0xFFFFFF00, 0x00000000, + 0xE10, 0xFFFFFFFF, 0x00000000, + 0xE14, 0xFFFFFFFF, 0x00000000, + 0xE18, 0xFFFFFFFF, 0x00000000, + 0xE1C, 0xFFFFFFFF, 0x00000000, + 0xE00, 0xFFFFFFFF, 0x00000000, + 0xE04, 0xFFFFFFFF, 0x00000000, + 0xE08, 0x0000FF00, 0x00000000, + 0x86C, 0xFFFFFF00, 0x00000000, + 0xE10, 0xFFFFFFFF, 0x00000000, + 0xE14, 0xFFFFFFFF, 0x00000000, + 0xE18, 0xFFFFFFFF, 0x00000000, + 0xE1C, 0xFFFFFFFF, 0x00000000, + +}; + +u32 RTL8188EE_RADIOA_1TARRAY[] = { + 0x000, 0x00030000, + 0x008, 0x00084000, + 0x018, 0x00000407, + 0x019, 0x00000012, + 0x01E, 0x00080009, + 0x01F, 0x00000880, + 0x02F, 0x0001A060, + 0x03F, 0x00000000, + 0x042, 0x000060C0, + 0x057, 0x000D0000, + 0x058, 0x000BE180, + 0x067, 0x00001552, + 0x083, 0x00000000, + 0x0B0, 0x000FF8FC, + 0x0B1, 0x00054400, + 0x0B2, 0x000CCC19, + 0x0B4, 0x00043003, + 0x0B6, 0x0004953E, + 0x0B7, 0x0001C718, + 0x0B8, 0x000060FF, + 0x0B9, 0x00080001, + 0x0BA, 0x00040000, + 0x0BB, 0x00000400, + 0x0BF, 0x000C0000, + 0x0C2, 0x00002400, + 0x0C3, 0x00000009, + 0x0C4, 0x00040C91, + 0x0C5, 0x00099999, + 0x0C6, 0x000000A3, + 0x0C7, 0x00088820, + 0x0C8, 0x00076C06, + 0x0C9, 0x00000000, + 0x0CA, 0x00080000, + 0x0DF, 0x00000180, + 0x0EF, 0x000001A0, + 0x051, 0x0006B27D, + 0x052, 0x0007E49D, + 0x053, 0x00000073, + 0x056, 0x00051FF3, + 0x035, 0x00000086, + 0x035, 0x00000186, + 0x035, 0x00000286, + 0x036, 0x00001C25, + 0x036, 0x00009C25, + 0x036, 0x00011C25, + 0x036, 0x00019C25, + 0x0B6, 0x00048538, + 0x018, 0x00000C07, + 0x05A, 0x0004BD00, + 0x019, 0x000739D0, + 0x034, 0x0000ADF3, + 0x034, 0x00009DF0, + 0x034, 0x00008DED, + 0x034, 0x00007DEA, + 0x034, 0x00006DE7, + 0x034, 0x000054EE, + 0x034, 0x000044EB, + 0x034, 0x000034E8, + 0x034, 0x0000246B, + 0x034, 0x00001468, + 0x034, 0x0000006D, + 0x000, 0x00030159, + 0x084, 0x00068200, + 0x086, 0x000000CE, + 0x087, 0x00048A00, + 0x08E, 0x00065540, + 0x08F, 0x00088000, + 0x0EF, 0x000020A0, + 0x03B, 0x000F02B0, + 0x03B, 0x000EF7B0, + 0x03B, 0x000D4FB0, + 0x03B, 0x000CF060, + 0x03B, 0x000B0090, + 0x03B, 0x000A0080, + 0x03B, 0x00090080, + 0x03B, 0x0008F780, + 0x03B, 0x000722B0, + 0x03B, 0x0006F7B0, + 0x03B, 0x00054FB0, + 0x03B, 0x0004F060, + 0x03B, 0x00030090, + 0x03B, 0x00020080, + 0x03B, 0x00010080, + 0x03B, 0x0000F780, + 0x0EF, 0x000000A0, + 0x000, 0x00010159, + 0x018, 0x0000F407, + 0xFFE, 0x00000000, + 0xFFE, 0x00000000, + 0x01F, 0x00080003, + 0xFFE, 0x00000000, + 0xFFE, 0x00000000, + 0x01E, 0x00000001, + 0x01F, 0x00080000, + 0x000, 0x00033E60, + +}; + +u32 RTL8188EEMAC_1T_ARRAY[] = { + 0x026, 0x00000041, + 0x027, 0x00000035, + 0x428, 0x0000000A, + 0x429, 0x00000010, + 0x430, 0x00000000, + 0x431, 0x00000001, + 0x432, 0x00000002, + 0x433, 0x00000004, + 0x434, 0x00000005, + 0x435, 0x00000006, + 0x436, 0x00000007, + 0x437, 0x00000008, + 0x438, 0x00000000, + 0x439, 0x00000000, + 0x43A, 0x00000001, + 0x43B, 0x00000002, + 0x43C, 0x00000004, + 0x43D, 0x00000005, + 0x43E, 0x00000006, + 0x43F, 0x00000007, + 0x440, 0x0000005D, + 0x441, 0x00000001, + 0x442, 0x00000000, + 0x444, 0x00000015, + 0x445, 0x000000F0, + 0x446, 0x0000000F, + 0x447, 0x00000000, + 0x458, 0x00000041, + 0x459, 0x000000A8, + 0x45A, 0x00000072, + 0x45B, 0x000000B9, + 0x460, 0x00000066, + 0x461, 0x00000066, + 0x480, 0x00000008, + 0x4C8, 0x000000FF, + 0x4C9, 0x00000008, + 0x4CC, 0x000000FF, + 0x4CD, 0x000000FF, + 0x4CE, 0x00000001, + 0x4D3, 0x00000001, + 0x500, 0x00000026, + 0x501, 0x000000A2, + 0x502, 0x0000002F, + 0x503, 0x00000000, + 0x504, 0x00000028, + 0x505, 0x000000A3, + 0x506, 0x0000005E, + 0x507, 0x00000000, + 0x508, 0x0000002B, + 0x509, 0x000000A4, + 0x50A, 0x0000005E, + 0x50B, 0x00000000, + 0x50C, 0x0000004F, + 0x50D, 0x000000A4, + 0x50E, 0x00000000, + 0x50F, 0x00000000, + 0x512, 0x0000001C, + 0x514, 0x0000000A, + 0x516, 0x0000000A, + 0x525, 0x0000004F, + 0x550, 0x00000010, + 0x551, 0x00000010, + 0x559, 0x00000002, + 0x55D, 0x000000FF, + 0x605, 0x00000030, + 0x608, 0x0000000E, + 0x609, 0x0000002A, + 0x620, 0x000000FF, + 0x621, 0x000000FF, + 0x622, 0x000000FF, + 0x623, 0x000000FF, + 0x624, 0x000000FF, + 0x625, 0x000000FF, + 0x626, 0x000000FF, + 0x627, 0x000000FF, + 0x652, 0x00000020, + 0x63C, 0x0000000A, + 0x63D, 0x0000000A, + 0x63E, 0x0000000E, + 0x63F, 0x0000000E, + 0x640, 0x00000040, + 0x66E, 0x00000005, + 0x700, 0x00000021, + 0x701, 0x00000043, + 0x702, 0x00000065, + 0x703, 0x00000087, + 0x708, 0x00000021, + 0x709, 0x00000043, + 0x70A, 0x00000065, + 0x70B, 0x00000087, + +}; + +u32 RTL8188EEAGCTAB_1TARRAY[] = { + 0xC78, 0xFB000001, + 0xC78, 0xFB010001, + 0xC78, 0xFB020001, + 0xC78, 0xFB030001, + 0xC78, 0xFB040001, + 0xC78, 0xFB050001, + 0xC78, 0xFA060001, + 0xC78, 0xF9070001, + 0xC78, 0xF8080001, + 0xC78, 0xF7090001, + 0xC78, 0xF60A0001, + 0xC78, 0xF50B0001, + 0xC78, 0xF40C0001, + 0xC78, 0xF30D0001, + 0xC78, 0xF20E0001, + 0xC78, 0xF10F0001, + 0xC78, 0xF0100001, + 0xC78, 0xEF110001, + 0xC78, 0xEE120001, + 0xC78, 0xED130001, + 0xC78, 0xEC140001, + 0xC78, 0xEB150001, + 0xC78, 0xEA160001, + 0xC78, 0xE9170001, + 0xC78, 0xE8180001, + 0xC78, 0xE7190001, + 0xC78, 0xE61A0001, + 0xC78, 0xE51B0001, + 0xC78, 0xE41C0001, + 0xC78, 0xE31D0001, + 0xC78, 0xE21E0001, + 0xC78, 0xE11F0001, + 0xC78, 0x8A200001, + 0xC78, 0x89210001, + 0xC78, 0x88220001, + 0xC78, 0x87230001, + 0xC78, 0x86240001, + 0xC78, 0x85250001, + 0xC78, 0x84260001, + 0xC78, 0x83270001, + 0xC78, 0x82280001, + 0xC78, 0x6B290001, + 0xC78, 0x6A2A0001, + 0xC78, 0x692B0001, + 0xC78, 0x682C0001, + 0xC78, 0x672D0001, + 0xC78, 0x662E0001, + 0xC78, 0x652F0001, + 0xC78, 0x64300001, + 0xC78, 0x63310001, + 0xC78, 0x62320001, + 0xC78, 0x61330001, + 0xC78, 0x46340001, + 0xC78, 0x45350001, + 0xC78, 0x44360001, + 0xC78, 0x43370001, + 0xC78, 0x42380001, + 0xC78, 0x41390001, + 0xC78, 0x403A0001, + 0xC78, 0x403B0001, + 0xC78, 0x403C0001, + 0xC78, 0x403D0001, + 0xC78, 0x403E0001, + 0xC78, 0x403F0001, + 0xC78, 0xFB400001, + 0xC78, 0xFB410001, + 0xC78, 0xFB420001, + 0xC78, 0xFB430001, + 0xC78, 0xFB440001, + 0xC78, 0xFB450001, + 0xC78, 0xFB460001, + 0xC78, 0xFB470001, + 0xC78, 0xFB480001, + 0xC78, 0xFA490001, + 0xC78, 0xF94A0001, + 0xC78, 0xF84B0001, + 0xC78, 0xF74C0001, + 0xC78, 0xF64D0001, + 0xC78, 0xF54E0001, + 0xC78, 0xF44F0001, + 0xC78, 0xF3500001, + 0xC78, 0xF2510001, + 0xC78, 0xF1520001, + 0xC78, 0xF0530001, + 0xC78, 0xEF540001, + 0xC78, 0xEE550001, + 0xC78, 0xED560001, + 0xC78, 0xEC570001, + 0xC78, 0xEB580001, + 0xC78, 0xEA590001, + 0xC78, 0xE95A0001, + 0xC78, 0xE85B0001, + 0xC78, 0xE75C0001, + 0xC78, 0xE65D0001, + 0xC78, 0xE55E0001, + 0xC78, 0xE45F0001, + 0xC78, 0xE3600001, + 0xC78, 0xE2610001, + 0xC78, 0xC3620001, + 0xC78, 0xC2630001, + 0xC78, 0xC1640001, + 0xC78, 0x8B650001, + 0xC78, 0x8A660001, + 0xC78, 0x89670001, + 0xC78, 0x88680001, + 0xC78, 0x87690001, + 0xC78, 0x866A0001, + 0xC78, 0x856B0001, + 0xC78, 0x846C0001, + 0xC78, 0x676D0001, + 0xC78, 0x666E0001, + 0xC78, 0x656F0001, + 0xC78, 0x64700001, + 0xC78, 0x63710001, + 0xC78, 0x62720001, + 0xC78, 0x61730001, + 0xC78, 0x60740001, + 0xC78, 0x46750001, + 0xC78, 0x45760001, + 0xC78, 0x44770001, + 0xC78, 0x43780001, + 0xC78, 0x42790001, + 0xC78, 0x417A0001, + 0xC78, 0x407B0001, + 0xC78, 0x407C0001, + 0xC78, 0x407D0001, + 0xC78, 0x407E0001, + 0xC78, 0x407F0001, +}; diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/table.h b/drivers/net/wireless/rtlwifi/rtl8188ee/table.h new file mode 100644 index 0000000..c1218e8 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/table.h @@ -0,0 +1,47 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Created on 2010/ 5/18, 1:41 + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CE_TABLE__H_ +#define __RTL92CE_TABLE__H_ + +#include +#define RTL8188EEPHY_REG_1TARRAYLEN 382 +extern u32 RTL8188EEPHY_REG_1TARRAY[]; +#define RTL8188EEPHY_REG_ARRAY_PGLEN 264 +extern u32 RTL8188EEPHY_REG_ARRAY_PG[]; +#define RTL8188EE_RADIOA_1TARRAYLEN 190 +extern u32 RTL8188EE_RADIOA_1TARRAY[]; +#define RTL8188EEMAC_1T_ARRAYLEN 180 +extern u32 RTL8188EEMAC_1T_ARRAY[]; +#define RTL8188EEAGCTAB_1TARRAYLEN 256 +extern u32 RTL8188EEAGCTAB_1TARRAY[]; + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/trx.c b/drivers/net/wireless/rtlwifi/rtl8188ee/trx.c new file mode 100644 index 0000000..a8871d6 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/trx.c @@ -0,0 +1,817 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "../base.h" +#include "../stats.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "trx.h" +#include "led.h" +#include "dm.h" + +static u8 _rtl88ee_map_hwqueue_to_fwqueue(struct sk_buff *skb, u8 hw_queue) +{ + __le16 fc = rtl_get_fc(skb); + + if (unlikely(ieee80211_is_beacon(fc))) + return QSLT_BEACON; + if (ieee80211_is_mgmt(fc) || ieee80211_is_ctl(fc)) + return QSLT_MGNT; + + return skb->priority; +} + +static void _rtl88ee_query_rxphystatus(struct ieee80211_hw *hw, + struct rtl_stats *pstatus, u8 *pdesc, + struct rx_fwinfo_88e *p_drvinfo, + bool bpacket_match_bssid, + bool bpacket_toself, bool packet_beacon) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtlpriv); + struct phy_sts_cck_8192s_t *cck_buf; + struct phy_status_rpt *phystrpt = (struct phy_status_rpt *)p_drvinfo; + struct rtl_dm *rtldm = rtl_dm(rtl_priv(hw)); + char rx_pwr_all = 0, rx_pwr[4]; + u8 rf_rx_num = 0, evm, pwdb_all; + u8 i, max_spatial_stream; + u32 rssi, total_rssi = 0; + bool is_cck = pstatus->is_cck; + u8 lan_idx, vga_idx; + + /* Record it for next packet processing */ + pstatus->packet_matchbssid = bpacket_match_bssid; + pstatus->packet_toself = bpacket_toself; + pstatus->packet_beacon = packet_beacon; + pstatus->rx_mimo_sig_qual[0] = -1; + pstatus->rx_mimo_sig_qual[1] = -1; + + if (is_cck) { + u8 cck_hipwr; + u8 cck_agc_rpt; + /* CCK Driver info Structure is not the same as OFDM packet. */ + cck_buf = (struct phy_sts_cck_8192s_t *)p_drvinfo; + cck_agc_rpt = cck_buf->cck_agc_rpt; + + /* (1)Hardware does not provide RSSI for CCK + * (2)PWDB, Average PWDB cacluated by + * hardware (for rate adaptive) + */ + if (ppsc->rfpwr_state == ERFON) + cck_hipwr = rtl_get_bbreg(hw, RFPGA0_XA_HSSIPARAMETER2, + BIT(9)); + else + cck_hipwr = false; + + lan_idx = ((cck_agc_rpt & 0xE0) >> 5); + vga_idx = (cck_agc_rpt & 0x1f); + switch (lan_idx) { + case 7: + if (vga_idx <= 27) + rx_pwr_all = -100 + 2 * (27 - vga_idx); + else + rx_pwr_all = -100; + break; + case 6: + rx_pwr_all = -48 + 2 * (2 - vga_idx); /*VGA_idx = 2~0*/ + break; + case 5: + rx_pwr_all = -42 + 2 * (7 - vga_idx); /*VGA_idx = 7~5*/ + break; + case 4: + rx_pwr_all = -36 + 2 * (7 - vga_idx); /*VGA_idx = 7~4*/ + break; + case 3: + rx_pwr_all = -24 + 2 * (7 - vga_idx); /*VGA_idx = 7~0*/ + break; + case 2: + if (cck_hipwr) + rx_pwr_all = -12 + 2 * (5 - vga_idx); + else + rx_pwr_all = -6 + 2 * (5 - vga_idx); + break; + case 1: + rx_pwr_all = 8 - 2 * vga_idx; + break; + case 0: + rx_pwr_all = 14 - 2 * vga_idx; + break; + default: + break; + } + rx_pwr_all += 6; + pwdb_all = rtl_query_rxpwrpercentage(rx_pwr_all); + /* CCK gain is smaller than OFDM/MCS gain, + * so we add gain diff by experiences, + * the val is 6 + */ + pwdb_all += 6; + if (pwdb_all > 100) + pwdb_all = 100; + /* modify the offset to make the same + * gain index with OFDM. + */ + if (pwdb_all > 34 && pwdb_all <= 42) + pwdb_all -= 2; + else if (pwdb_all > 26 && pwdb_all <= 34) + pwdb_all -= 6; + else if (pwdb_all > 14 && pwdb_all <= 26) + pwdb_all -= 8; + else if (pwdb_all > 4 && pwdb_all <= 14) + pwdb_all -= 4; + if (cck_hipwr == false) { + if (pwdb_all >= 80) + pwdb_all = ((pwdb_all - 80)<<1) + + ((pwdb_all - 80)>>1) + 80; + else if ((pwdb_all <= 78) && (pwdb_all >= 20)) + pwdb_all += 3; + if (pwdb_all > 100) + pwdb_all = 100; + } + + pstatus->rx_pwdb_all = pwdb_all; + pstatus->recvsignalpower = rx_pwr_all; + + /* (3) Get Signal Quality (EVM) */ + if (bpacket_match_bssid) { + u8 sq; + + if (pstatus->rx_pwdb_all > 40) { + sq = 100; + } else { + sq = cck_buf->sq_rpt; + if (sq > 64) + sq = 0; + else if (sq < 20) + sq = 100; + else + sq = ((64 - sq) * 100) / 44; + } + + pstatus->signalquality = sq; + pstatus->rx_mimo_sig_qual[0] = sq; + pstatus->rx_mimo_sig_qual[1] = -1; + } + } else { + rtlpriv->dm.rfpath_rxenable[0] = + rtlpriv->dm.rfpath_rxenable[1] = true; + + /* (1)Get RSSI for HT rate */ + for (i = RF90_PATH_A; i < RF6052_MAX_PATH; i++) { + /* we will judge RF RX path now. */ + if (rtlpriv->dm.rfpath_rxenable[i]) + rf_rx_num++; + + rx_pwr[i] = ((p_drvinfo->gain_trsw[i] & 0x3f) * 2)-110; + + /* Translate DBM to percentage. */ + rssi = rtl_query_rxpwrpercentage(rx_pwr[i]); + total_rssi += rssi; + + /* Get Rx snr value in DB */ + rtlpriv->stats.rx_snr_db[i] = p_drvinfo->rxsnr[i] / 2; + + /* Record Signal Strength for next packet */ + if (bpacket_match_bssid) + pstatus->rx_mimo_signalstrength[i] = (u8) rssi; + } + + /* (2)PWDB, Average PWDB cacluated by + * hardware (for rate adaptive) + */ + rx_pwr_all = ((p_drvinfo->pwdb_all >> 1) & 0x7f) - 110; + + pwdb_all = rtl_query_rxpwrpercentage(rx_pwr_all); + pstatus->rx_pwdb_all = pwdb_all; + pstatus->rxpower = rx_pwr_all; + pstatus->recvsignalpower = rx_pwr_all; + + /* (3)EVM of HT rate */ + if (pstatus->is_ht && pstatus->rate >= DESC92C_RATEMCS8 && + pstatus->rate <= DESC92C_RATEMCS15) + max_spatial_stream = 2; + else + max_spatial_stream = 1; + + for (i = 0; i < max_spatial_stream; i++) { + evm = rtl_evm_db_to_percentage(p_drvinfo->rxevm[i]); + + if (bpacket_match_bssid) { + /* Fill value in RFD, Get the first + * spatial stream only + */ + if (i == 0) + pstatus->signalquality = evm & 0xff; + pstatus->rx_mimo_sig_qual[i] = evm & 0xff; + } + } + } + + /* UI BSS List signal strength(in percentage), + * make it good looking, from 0~100. + */ + if (is_cck) + pstatus->signalstrength = (u8)(rtl_signal_scale_mapping(hw, + pwdb_all)); + else if (rf_rx_num != 0) + pstatus->signalstrength = (u8)(rtl_signal_scale_mapping(hw, + total_rssi /= rf_rx_num)); + /*HW antenna diversity*/ + rtldm->fat_table.antsel_rx_keep_0 = phystrpt->ant_sel; + rtldm->fat_table.antsel_rx_keep_1 = phystrpt->ant_sel_b; + rtldm->fat_table.antsel_rx_keep_2 = phystrpt->antsel_rx_keep_2; +} + +static void _rtl88ee_smart_antenna(struct ieee80211_hw *hw, + struct rtl_stats *pstatus) +{ + struct rtl_dm *rtldm = rtl_dm(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 ant_mux; + struct fast_ant_training *pfat = &(rtldm->fat_table); + + if (rtlefuse->antenna_div_type == CG_TRX_SMART_ANTDIV) { + if (pfat->fat_state == FAT_TRAINING_STATE) { + if (pstatus->packet_toself) { + ant_mux = (pfat->antsel_rx_keep_2 << 2) | + (pfat->antsel_rx_keep_1 << 1) | + pfat->antsel_rx_keep_0; + pfat->ant_sum[ant_mux] += pstatus->rx_pwdb_all; + pfat->ant_cnt[ant_mux]++; + } + } + } else if ((rtlefuse->antenna_div_type == CG_TRX_HW_ANTDIV) || + (rtlefuse->antenna_div_type == CGCS_RX_HW_ANTDIV)) { + if (pstatus->packet_toself || pstatus->packet_matchbssid) { + ant_mux = (pfat->antsel_rx_keep_2 << 2) | + (pfat->antsel_rx_keep_1 << 1) | + pfat->antsel_rx_keep_0; + rtl88e_dm_ant_sel_statistics(hw, ant_mux, 0, + pstatus->rx_pwdb_all); + } + } +} + +static void _rtl88ee_translate_rx_signal_stuff(struct ieee80211_hw *hw, + struct sk_buff *skb, struct rtl_stats *pstatus, + u8 *pdesc, struct rx_fwinfo_88e *p_drvinfo) +{ + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct ieee80211_hdr *hdr; + u8 *tmp_buf; + u8 *praddr; + u8 *psaddr; + __le16 fc; + u16 type, ufc; + bool match_bssid, packet_toself, packet_beacon, addr; + + tmp_buf = skb->data + pstatus->rx_drvinfo_size + pstatus->rx_bufshift; + + hdr = (struct ieee80211_hdr *)tmp_buf; + fc = hdr->frame_control; + ufc = le16_to_cpu(fc); + type = WLAN_FC_GET_TYPE(fc); + praddr = hdr->addr1; + psaddr = ieee80211_get_SA(hdr); + memcpy(pstatus->psaddr, psaddr, ETH_ALEN); + + addr = (!compare_ether_addr(mac->bssid, (ufc & IEEE80211_FCTL_TODS) ? + hdr->addr1 : (ufc & IEEE80211_FCTL_FROMDS) ? + hdr->addr2 : hdr->addr3)); + match_bssid = ((IEEE80211_FTYPE_CTL != type) && (!pstatus->hwerror) && + (!pstatus->crc) && (!pstatus->icv)) && addr; + + addr = (!compare_ether_addr(praddr, rtlefuse->dev_addr)); + packet_toself = match_bssid && addr; + + if (ieee80211_is_beacon(fc)) + packet_beacon = true; + + _rtl88ee_query_rxphystatus(hw, pstatus, pdesc, p_drvinfo, + match_bssid, packet_toself, packet_beacon); + _rtl88ee_smart_antenna(hw, pstatus); + rtl_process_phyinfo(hw, tmp_buf, pstatus); +} + +static void insert_em(struct rtl_tcb_desc *ptcb_desc, u8 *virtualaddress) +{ + u32 dwtmp = 0; + + memset(virtualaddress, 0, 8); + + SET_EARLYMODE_PKTNUM(virtualaddress, ptcb_desc->empkt_num); + if (ptcb_desc->empkt_num == 1) { + dwtmp = ptcb_desc->empkt_len[0]; + } else { + dwtmp = ptcb_desc->empkt_len[0]; + dwtmp += ((dwtmp % 4) ? (4 - dwtmp % 4) : 0) + 4; + dwtmp += ptcb_desc->empkt_len[1]; + } + SET_EARLYMODE_LEN0(virtualaddress, dwtmp); + + if (ptcb_desc->empkt_num <= 3) { + dwtmp = ptcb_desc->empkt_len[2]; + } else { + dwtmp = ptcb_desc->empkt_len[2]; + dwtmp += ((dwtmp % 4) ? (4 - dwtmp % 4) : 0) + 4; + dwtmp += ptcb_desc->empkt_len[3]; + } + SET_EARLYMODE_LEN1(virtualaddress, dwtmp); + if (ptcb_desc->empkt_num <= 5) { + dwtmp = ptcb_desc->empkt_len[4]; + } else { + dwtmp = ptcb_desc->empkt_len[4]; + dwtmp += ((dwtmp % 4) ? (4 - dwtmp % 4) : 0) + 4; + dwtmp += ptcb_desc->empkt_len[5]; + } + SET_EARLYMODE_LEN2_1(virtualaddress, dwtmp & 0xF); + SET_EARLYMODE_LEN2_2(virtualaddress, dwtmp >> 4); + if (ptcb_desc->empkt_num <= 7) { + dwtmp = ptcb_desc->empkt_len[6]; + } else { + dwtmp = ptcb_desc->empkt_len[6]; + dwtmp += ((dwtmp % 4) ? (4 - dwtmp % 4) : 0) + 4; + dwtmp += ptcb_desc->empkt_len[7]; + } + SET_EARLYMODE_LEN3(virtualaddress, dwtmp); + if (ptcb_desc->empkt_num <= 9) { + dwtmp = ptcb_desc->empkt_len[8]; + } else { + dwtmp = ptcb_desc->empkt_len[8]; + dwtmp += ((dwtmp % 4) ? (4 - dwtmp % 4) : 0) + 4; + dwtmp += ptcb_desc->empkt_len[9]; + } + SET_EARLYMODE_LEN4(virtualaddress, dwtmp); +} + +bool rtl88ee_rx_query_desc(struct ieee80211_hw *hw, + struct rtl_stats *status, + struct ieee80211_rx_status *rx_status, + u8 *pdesc, struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rx_fwinfo_88e *p_drvinfo; + struct ieee80211_hdr *hdr; + + u32 phystatus = GET_RX_DESC_PHYST(pdesc); + status->packet_report_type = (u8)GET_RX_STATUS_DESC_RPT_SEL(pdesc); + if (status->packet_report_type == TX_REPORT2) + status->length = (u16) GET_RX_RPT2_DESC_PKT_LEN(pdesc); + else + status->length = (u16) GET_RX_DESC_PKT_LEN(pdesc); + status->rx_drvinfo_size = (u8) GET_RX_DESC_DRV_INFO_SIZE(pdesc) * + RX_DRV_INFO_SIZE_UNIT; + status->rx_bufshift = (u8) (GET_RX_DESC_SHIFT(pdesc) & 0x03); + status->icv = (u16) GET_RX_DESC_ICV(pdesc); + status->crc = (u16) GET_RX_DESC_CRC32(pdesc); + status->hwerror = (status->crc | status->icv); + status->decrypted = !GET_RX_DESC_SWDEC(pdesc); + status->rate = (u8) GET_RX_DESC_RXMCS(pdesc); + status->shortpreamble = (u16) GET_RX_DESC_SPLCP(pdesc); + status->isampdu = (bool) (GET_RX_DESC_PAGGR(pdesc) == 1); + status->isfirst_ampdu = (bool) ((GET_RX_DESC_PAGGR(pdesc) == 1) && + (GET_RX_DESC_FAGGR(pdesc) == 1)); + if (status->packet_report_type == NORMAL_RX) + status->timestamp_low = GET_RX_DESC_TSFL(pdesc); + status->rx_is40Mhzpacket = (bool) GET_RX_DESC_BW(pdesc); + status->is_ht = (bool)GET_RX_DESC_RXHT(pdesc); + + status->is_cck = RTL8188_RX_HAL_IS_CCK_RATE(status->rate); + + status->macid = GET_RX_DESC_MACID(pdesc); + if (GET_RX_STATUS_DESC_MAGIC_MATCH(pdesc)) + status->wake_match = BIT(2); + else if (GET_RX_STATUS_DESC_MAGIC_MATCH(pdesc)) + status->wake_match = BIT(1); + else if (GET_RX_STATUS_DESC_UNICAST_MATCH(pdesc)) + status->wake_match = BIT(0); + else + status->wake_match = 0; + if (status->wake_match) + RT_TRACE(rtlpriv, COMP_RXDESC, DBG_LOUD, + "Get Wakeup Packet!! WakeMatch =%d\n", + status->wake_match); + rx_status->freq = hw->conf.chandef.chan->center_freq; + rx_status->band = hw->conf.chandef.chan->band; + + if (status->crc) + rx_status->flag |= RX_FLAG_FAILED_FCS_CRC; + + if (status->rx_is40Mhzpacket) + rx_status->flag |= RX_FLAG_40MHZ; + + if (status->is_ht) + rx_status->flag |= RX_FLAG_HT; + + rx_status->flag |= RX_FLAG_MACTIME_START; + + /* hw will set status->decrypted true, if it finds the + * frame is open data frame or mgmt frame. + * So hw will not decryption robust managment frame + * for IEEE80211w but still set status->decrypted + * true, so here we should set it back to undecrypted + * for IEEE80211w frame, and mac80211 sw will help + * to decrypt it + */ + if (status->decrypted) { + hdr = (struct ieee80211_hdr *)(skb->data + + status->rx_drvinfo_size + status->rx_bufshift); + + if (!hdr) { + /* During testing, hdr was NULL */ + return false; + } + if ((ieee80211_is_robust_mgmt_frame(hdr)) && + (ieee80211_has_protected(hdr->frame_control))) + rx_status->flag &= ~RX_FLAG_DECRYPTED; + else + rx_status->flag |= RX_FLAG_DECRYPTED; + } + + /* rate_idx: index of data rate into band's + * supported rates or MCS index if HT rates + * are use (RX_FLAG_HT) + * Notice: this is diff with windows define + */ + rx_status->rate_idx = rtlwifi_rate_mapping(hw, status->is_ht, + status->rate, false); + + rx_status->mactime = status->timestamp_low; + if (phystatus == true) { + p_drvinfo = (struct rx_fwinfo_88e *)(skb->data + + status->rx_bufshift); + + _rtl88ee_translate_rx_signal_stuff(hw, skb, status, pdesc, + p_drvinfo); + } + + /*rx_status->qual = status->signal; */ + rx_status->signal = status->recvsignalpower + 10; + /*rx_status->noise = -status->noise; */ + if (status->packet_report_type == TX_REPORT2) { + status->macid_valid_entry[0] = + GET_RX_RPT2_DESC_MACID_VALID_1(pdesc); + status->macid_valid_entry[1] = + GET_RX_RPT2_DESC_MACID_VALID_2(pdesc); + } + return true; +} + +void rtl88ee_tx_fill_desc(struct ieee80211_hw *hw, + struct ieee80211_hdr *hdr, u8 *pdesc_tx, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff *skb, + u8 hw_queue, struct rtl_tcb_desc *ptcb_desc) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + u8 *pdesc = (u8 *)pdesc_tx; + u16 seq_number; + __le16 fc = hdr->frame_control; + unsigned int buf_len = 0; + unsigned int skb_len = skb->len; + u8 fw_qsel = _rtl88ee_map_hwqueue_to_fwqueue(skb, hw_queue); + bool firstseg = ((hdr->seq_ctrl & + cpu_to_le16(IEEE80211_SCTL_FRAG)) == 0); + bool lastseg = ((hdr->frame_control & + cpu_to_le16(IEEE80211_FCTL_MOREFRAGS)) == 0); + dma_addr_t mapping; + u8 bw_40 = 0; + u8 short_gi = 0; + + if (mac->opmode == NL80211_IFTYPE_STATION) { + bw_40 = mac->bw_40; + } else if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_ADHOC) { + if (sta) + bw_40 = sta->ht_cap.cap & + IEEE80211_HT_CAP_SUP_WIDTH_20_40; + } + seq_number = (le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_SEQ) >> 4; + rtl_get_tcb_desc(hw, info, sta, skb, ptcb_desc); + /* reserve 8 byte for AMPDU early mode */ + if (rtlhal->earlymode_enable) { + skb_push(skb, EM_HDR_LEN); + memset(skb->data, 0, EM_HDR_LEN); + } + buf_len = skb->len; + mapping = pci_map_single(rtlpci->pdev, skb->data, skb->len, + PCI_DMA_TODEVICE); + if (pci_dma_mapping_error(rtlpci->pdev, mapping)) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "DMA mapping error"); + return; + } + CLEAR_PCI_TX_DESC_CONTENT(pdesc, sizeof(struct tx_desc_88e)); + if (ieee80211_is_nullfunc(fc) || ieee80211_is_ctl(fc)) { + firstseg = true; + lastseg = true; + } + if (firstseg) { + if (rtlhal->earlymode_enable) { + SET_TX_DESC_PKT_OFFSET(pdesc, 1); + SET_TX_DESC_OFFSET(pdesc, USB_HWDESC_HEADER_LEN + + EM_HDR_LEN); + if (ptcb_desc->empkt_num) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "Insert 8 byte.pTcb->EMPktNum:%d\n", + ptcb_desc->empkt_num); + insert_em(ptcb_desc, (u8 *)(skb->data)); + } + } else { + SET_TX_DESC_OFFSET(pdesc, USB_HWDESC_HEADER_LEN); + } + + ptcb_desc->use_driver_rate = true; + SET_TX_DESC_TX_RATE(pdesc, ptcb_desc->hw_rate); + if (ptcb_desc->hw_rate > DESC92C_RATEMCS0) + short_gi = (ptcb_desc->use_shortgi) ? 1 : 0; + else + short_gi = (ptcb_desc->use_shortpreamble) ? 1 : 0; + SET_TX_DESC_DATA_SHORTGI(pdesc, short_gi); + + if (info->flags & IEEE80211_TX_CTL_AMPDU) { + SET_TX_DESC_AGG_ENABLE(pdesc, 1); + SET_TX_DESC_MAX_AGG_NUM(pdesc, 0x14); + } + SET_TX_DESC_SEQ(pdesc, seq_number); + SET_TX_DESC_RTS_ENABLE(pdesc, ((ptcb_desc->rts_enable && + !ptcb_desc->cts_enable) ? 1 : 0)); + SET_TX_DESC_HW_RTS_ENABLE(pdesc, 0); + SET_TX_DESC_CTS2SELF(pdesc, ((ptcb_desc->cts_enable) ? 1 : 0)); + SET_TX_DESC_RTS_STBC(pdesc, ((ptcb_desc->rts_stbc) ? 1 : 0)); + + SET_TX_DESC_RTS_RATE(pdesc, ptcb_desc->rts_rate); + SET_TX_DESC_RTS_BW(pdesc, 0); + SET_TX_DESC_RTS_SC(pdesc, ptcb_desc->rts_sc); + SET_TX_DESC_RTS_SHORT(pdesc, + ((ptcb_desc->rts_rate <= DESC92C_RATE54M) ? + (ptcb_desc->rts_use_shortpreamble ? 1 : 0) : + (ptcb_desc->rts_use_shortgi ? 1 : 0))); + + if (ptcb_desc->btx_enable_sw_calc_duration) + SET_TX_DESC_NAV_USE_HDR(pdesc, 1); + + if (bw_40) { + if (ptcb_desc->packet_bw) { + SET_TX_DESC_DATA_BW(pdesc, 1); + SET_TX_DESC_TX_SUB_CARRIER(pdesc, 3); + } else { + SET_TX_DESC_DATA_BW(pdesc, 0); + SET_TX_DESC_TX_SUB_CARRIER(pdesc, + mac->cur_40_prime_sc); + } + } else { + SET_TX_DESC_DATA_BW(pdesc, 0); + SET_TX_DESC_TX_SUB_CARRIER(pdesc, 0); + } + + SET_TX_DESC_LINIP(pdesc, 0); + SET_TX_DESC_PKT_SIZE(pdesc, (u16) skb_len); + if (sta) { + u8 ampdu_density = sta->ht_cap.ampdu_density; + SET_TX_DESC_AMPDU_DENSITY(pdesc, ampdu_density); + } + if (info->control.hw_key) { + struct ieee80211_key_conf *keyconf; + keyconf = info->control.hw_key; + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + case WLAN_CIPHER_SUITE_TKIP: + SET_TX_DESC_SEC_TYPE(pdesc, 0x1); + break; + case WLAN_CIPHER_SUITE_CCMP: + SET_TX_DESC_SEC_TYPE(pdesc, 0x3); + break; + default: + SET_TX_DESC_SEC_TYPE(pdesc, 0x0); + break; + } + } + + SET_TX_DESC_QUEUE_SEL(pdesc, fw_qsel); + SET_TX_DESC_DATA_RATE_FB_LIMIT(pdesc, 0x1F); + SET_TX_DESC_RTS_RATE_FB_LIMIT(pdesc, 0xF); + SET_TX_DESC_DISABLE_FB(pdesc, ptcb_desc->disable_ratefallback ? + 1 : 0); + SET_TX_DESC_USE_RATE(pdesc, ptcb_desc->use_driver_rate ? 1 : 0); + + /* Set TxRate and RTSRate in TxDesc */ + /* This prevent Tx initial rate of new-coming packets */ + /* from being overwritten by retried packet rate.*/ + if (!ptcb_desc->use_driver_rate) { + /*SET_TX_DESC_RTS_RATE(pdesc, 0x08); */ + /* SET_TX_DESC_TX_RATE(pdesc, 0x0b); */ + } + if (ieee80211_is_data_qos(fc)) { + if (mac->rdg_en) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "Enable RDG function.\n"); + SET_TX_DESC_RDG_ENABLE(pdesc, 1); + SET_TX_DESC_HTC(pdesc, 1); + } + } + } + + SET_TX_DESC_FIRST_SEG(pdesc, (firstseg ? 1 : 0)); + SET_TX_DESC_LAST_SEG(pdesc, (lastseg ? 1 : 0)); + SET_TX_DESC_TX_BUFFER_SIZE(pdesc, (u16) buf_len); + SET_TX_DESC_TX_BUFFER_ADDRESS(pdesc, mapping); + if (rtlpriv->dm.useramask) { + SET_TX_DESC_RATE_ID(pdesc, ptcb_desc->ratr_index); + SET_TX_DESC_MACID(pdesc, ptcb_desc->mac_id); + } else { + SET_TX_DESC_RATE_ID(pdesc, 0xC + ptcb_desc->ratr_index); + SET_TX_DESC_MACID(pdesc, ptcb_desc->ratr_index); + } + if (ieee80211_is_data_qos(fc)) + SET_TX_DESC_QOS(pdesc, 1); + + if (!ieee80211_is_data_qos(fc)) + SET_TX_DESC_HWSEQ_EN(pdesc, 1); + SET_TX_DESC_MORE_FRAG(pdesc, (lastseg ? 0 : 1)); + if (is_multicast_ether_addr(ieee80211_get_DA(hdr)) || + is_broadcast_ether_addr(ieee80211_get_DA(hdr))) + SET_TX_DESC_BMC(pdesc, 1); + + rtl88e_dm_set_tx_ant_by_tx_info(hw, pdesc, ptcb_desc->mac_id); + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, "\n"); +} + +void rtl88ee_tx_fill_cmddesc(struct ieee80211_hw *hw, + u8 *pdesc, bool firstseg, + bool lastseg, struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u8 fw_queue = QSLT_BEACON; + + dma_addr_t mapping = pci_map_single(rtlpci->pdev, + skb->data, skb->len, + PCI_DMA_TODEVICE); + + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(skb->data); + __le16 fc = hdr->frame_control; + + if (pci_dma_mapping_error(rtlpci->pdev, mapping)) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "DMA mapping error"); + return; + } + CLEAR_PCI_TX_DESC_CONTENT(pdesc, TX_DESC_SIZE); + + if (firstseg) + SET_TX_DESC_OFFSET(pdesc, USB_HWDESC_HEADER_LEN); + + SET_TX_DESC_TX_RATE(pdesc, DESC92C_RATE1M); + + SET_TX_DESC_SEQ(pdesc, 0); + + SET_TX_DESC_LINIP(pdesc, 0); + + SET_TX_DESC_QUEUE_SEL(pdesc, fw_queue); + + SET_TX_DESC_FIRST_SEG(pdesc, 1); + SET_TX_DESC_LAST_SEG(pdesc, 1); + + SET_TX_DESC_TX_BUFFER_SIZE(pdesc, (u16)(skb->len)); + + SET_TX_DESC_TX_BUFFER_ADDRESS(pdesc, mapping); + + SET_TX_DESC_RATE_ID(pdesc, 7); + SET_TX_DESC_MACID(pdesc, 0); + + SET_TX_DESC_OWN(pdesc, 1); + + SET_TX_DESC_PKT_SIZE((u8 *)pdesc, (u16)(skb->len)); + + SET_TX_DESC_FIRST_SEG(pdesc, 1); + SET_TX_DESC_LAST_SEG(pdesc, 1); + + SET_TX_DESC_OFFSET(pdesc, 0x20); + + SET_TX_DESC_USE_RATE(pdesc, 1); + + if (!ieee80211_is_data_qos(fc)) + SET_TX_DESC_HWSEQ_EN(pdesc, 1); + + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_LOUD, + "H2C Tx Cmd Content\n", + pdesc, TX_DESC_SIZE); +} + +void rtl88ee_set_desc(u8 *pdesc, bool istx, u8 desc_name, u8 *val) +{ + if (istx == true) { + switch (desc_name) { + case HW_DESC_OWN: + SET_TX_DESC_OWN(pdesc, 1); + break; + case HW_DESC_TX_NEXTDESC_ADDR: + SET_TX_DESC_NEXT_DESC_ADDRESS(pdesc, *(u32 *)val); + break; + default: + RT_ASSERT(false, "ERR txdesc :%d not processed\n", + desc_name); + break; + } + } else { + switch (desc_name) { + case HW_DESC_RXOWN: + SET_RX_DESC_OWN(pdesc, 1); + break; + case HW_DESC_RXBUFF_ADDR: + SET_RX_DESC_BUFF_ADDR(pdesc, *(u32 *)val); + break; + case HW_DESC_RXPKT_LEN: + SET_RX_DESC_PKT_LEN(pdesc, *(u32 *)val); + break; + case HW_DESC_RXERO: + SET_RX_DESC_EOR(pdesc, 1); + break; + default: + RT_ASSERT(false, "ERR rxdesc :%d not processed\n", + desc_name); + break; + } + } +} + +u32 rtl88ee_get_desc(u8 *pdesc, bool istx, u8 desc_name) +{ + u32 ret = 0; + + if (istx == true) { + switch (desc_name) { + case HW_DESC_OWN: + ret = GET_TX_DESC_OWN(pdesc); + break; + case HW_DESC_TXBUFF_ADDR: + ret = GET_TX_DESC_TX_BUFFER_ADDRESS(pdesc); + break; + default: + RT_ASSERT(false, "ERR txdesc :%d not processed\n", + desc_name); + break; + } + } else { + switch (desc_name) { + case HW_DESC_OWN: + ret = GET_RX_DESC_OWN(pdesc); + break; + case HW_DESC_RXPKT_LEN: + ret = GET_RX_DESC_PKT_LEN(pdesc); + break; + default: + RT_ASSERT(false, "ERR rxdesc :%d not processed\n", + desc_name); + break; + } + } + return ret; +} + +void rtl88ee_tx_polling(struct ieee80211_hw *hw, u8 hw_queue) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + if (hw_queue == BEACON_QUEUE) { + rtl_write_word(rtlpriv, REG_PCIE_CTRL_REG, BIT(4)); + } else { + rtl_write_word(rtlpriv, REG_PCIE_CTRL_REG, + BIT(0) << (hw_queue)); + } +} diff --git a/drivers/net/wireless/rtlwifi/rtl8188ee/trx.h b/drivers/net/wireless/rtlwifi/rtl8188ee/trx.h new file mode 100644 index 0000000..21ca33a --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8188ee/trx.h @@ -0,0 +1,795 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2013 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CE_TRX_H__ +#define __RTL92CE_TRX_H__ + +#define TX_DESC_SIZE 64 +#define TX_DESC_AGGR_SUBFRAME_SIZE 32 + +#define RX_DESC_SIZE 32 +#define RX_DRV_INFO_SIZE_UNIT 8 + +#define TX_DESC_NEXT_DESC_OFFSET 40 +#define USB_HWDESC_HEADER_LEN 32 +#define CRCLENGTH 4 + +#define SET_TX_DESC_PKT_SIZE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 0, 16, __val) +#define SET_TX_DESC_OFFSET(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 16, 8, __val) +#define SET_TX_DESC_BMC(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 24, 1, __val) +#define SET_TX_DESC_HTC(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 25, 1, __val) +#define SET_TX_DESC_LAST_SEG(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 26, 1, __val) +#define SET_TX_DESC_FIRST_SEG(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 27, 1, __val) +#define SET_TX_DESC_LINIP(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 28, 1, __val) +#define SET_TX_DESC_NO_ACM(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 29, 1, __val) +#define SET_TX_DESC_GF(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 30, 1, __val) +#define SET_TX_DESC_OWN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 31, 1, __val) + +#define GET_TX_DESC_PKT_SIZE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 0, 16) +#define GET_TX_DESC_OFFSET(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 16, 8) +#define GET_TX_DESC_BMC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 24, 1) +#define GET_TX_DESC_HTC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 25, 1) +#define GET_TX_DESC_LAST_SEG(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 26, 1) +#define GET_TX_DESC_FIRST_SEG(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 27, 1) +#define GET_TX_DESC_LINIP(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 28, 1) +#define GET_TX_DESC_NO_ACM(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 29, 1) +#define GET_TX_DESC_GF(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 30, 1) +#define GET_TX_DESC_OWN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 31, 1) + +#define SET_TX_DESC_MACID(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 0, 6, __val) +#define SET_TX_DESC_QUEUE_SEL(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 8, 5, __val) +#define SET_TX_DESC_RDG_NAV_EXT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 13, 1, __val) +#define SET_TX_DESC_LSIG_TXOP_EN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 14, 1, __val) +#define SET_TX_DESC_PIFS(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 15, 1, __val) +#define SET_TX_DESC_RATE_ID(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 16, 4, __val) +#define SET_TX_DESC_NAV_USE_HDR(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 20, 1, __val) +#define SET_TX_DESC_EN_DESC_ID(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 21, 1, __val) +#define SET_TX_DESC_SEC_TYPE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 22, 2, __val) +#define SET_TX_DESC_PKT_OFFSET(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 26, 5, __val) +#define SET_TX_DESC_PADDING_LEN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 24, 8, __val) + +#define GET_TX_DESC_MACID(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 0, 5) +#define GET_TX_DESC_AGG_ENABLE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 5, 1) +#define GET_TX_DESC_AGG_BREAK(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 6, 1) +#define GET_TX_DESC_RDG_ENABLE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 7, 1) +#define GET_TX_DESC_QUEUE_SEL(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 8, 5) +#define GET_TX_DESC_RDG_NAV_EXT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 13, 1) +#define GET_TX_DESC_LSIG_TXOP_EN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 14, 1) +#define GET_TX_DESC_PIFS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 15, 1) +#define GET_TX_DESC_RATE_ID(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 16, 4) +#define GET_TX_DESC_NAV_USE_HDR(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 20, 1) +#define GET_TX_DESC_EN_DESC_ID(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 21, 1) +#define GET_TX_DESC_SEC_TYPE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 22, 2) +#define GET_TX_DESC_PKT_OFFSET(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 24, 8) + +#define SET_TX_DESC_RTS_RC(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 0, 6, __val) +#define SET_TX_DESC_DATA_RC(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 6, 6, __val) +#define SET_TX_DESC_AGG_ENABLE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 12, 1, __val) +#define SET_TX_DESC_RDG_ENABLE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 13, 1, __val) +#define SET_TX_DESC_BAR_RTY_TH(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 14, 2, __val) +#define SET_TX_DESC_AGG_BREAK(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 16, 1, __val) +#define SET_TX_DESC_MORE_FRAG(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 17, 1, __val) +#define SET_TX_DESC_RAW(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 18, 1, __val) +#define SET_TX_DESC_CCX(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 19, 1, __val) +#define SET_TX_DESC_AMPDU_DENSITY(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 20, 3, __val) +#define SET_TX_DESC_BT_INT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 23, 1, __val) +#define SET_TX_DESC_ANTSEL_A(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 24, 1, __val) +#define SET_TX_DESC_ANTSEL_B(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 25, 1, __val) +#define SET_TX_DESC_TX_ANT_CCK(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 26, 2, __val) +#define SET_TX_DESC_TX_ANTL(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 28, 2, __val) +#define SET_TX_DESC_TX_ANT_HT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 30, 2, __val) + +#define GET_TX_DESC_RTS_RC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 0, 6) +#define GET_TX_DESC_DATA_RC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 6, 6) +#define GET_TX_DESC_BAR_RTY_TH(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 14, 2) +#define GET_TX_DESC_MORE_FRAG(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 17, 1) +#define GET_TX_DESC_RAW(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 18, 1) +#define GET_TX_DESC_CCX(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 19, 1) +#define GET_TX_DESC_AMPDU_DENSITY(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 20, 3) +#define GET_TX_DESC_ANTSEL_A(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 24, 1) +#define GET_TX_DESC_ANTSEL_B(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 25, 1) +#define GET_TX_DESC_TX_ANT_CCK(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 26, 2) +#define GET_TX_DESC_TX_ANTL(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 28, 2) +#define GET_TX_DESC_TX_ANT_HT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 30, 2) + +#define SET_TX_DESC_NEXT_HEAP_PAGE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+12, 0, 8, __val) +#define SET_TX_DESC_TAIL_PAGE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+12, 8, 8, __val) +#define SET_TX_DESC_SEQ(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+12, 16, 12, __val) +#define SET_TX_DESC_CPU_HANDLE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+12, 28, 1, __val) +#define SET_TX_DESC_TAG1(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+12, 29, 1, __val) +#define SET_TX_DESC_TRIGGER_INT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+12, 30, 1, __val) +#define SET_TX_DESC_HWSEQ_EN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+12, 31, 1, __val) + + +#define GET_TX_DESC_NEXT_HEAP_PAGE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 0, 8) +#define GET_TX_DESC_TAIL_PAGE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 8, 8) +#define GET_TX_DESC_SEQ(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 16, 12) + + +#define SET_TX_DESC_RTS_RATE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 0, 5, __val) +#define SET_TX_DESC_AP_DCFE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 5, 1, __val) +#define SET_TX_DESC_QOS(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 6, 1, __val) +#define SET_TX_DESC_HWSEQ_SSN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 7, 1, __val) +#define SET_TX_DESC_USE_RATE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 8, 1, __val) +#define SET_TX_DESC_DISABLE_RTS_FB(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 9, 1, __val) +#define SET_TX_DESC_DISABLE_FB(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 10, 1, __val) +#define SET_TX_DESC_CTS2SELF(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 11, 1, __val) +#define SET_TX_DESC_RTS_ENABLE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 12, 1, __val) +#define SET_TX_DESC_HW_RTS_ENABLE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 13, 1, __val) +#define SET_TX_DESC_PORT_ID(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 14, 1, __val) +#define SET_TX_DESC_PWR_STATUS(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 15, 3, __val) +#define SET_TX_DESC_WAIT_DCTS(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 18, 1, __val) +#define SET_TX_DESC_CTS2AP_EN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 19, 1, __val) +#define SET_TX_DESC_TX_SUB_CARRIER(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 20, 2, __val) +#define SET_TX_DESC_TX_STBC(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 22, 2, __val) +#define SET_TX_DESC_DATA_SHORT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 24, 1, __val) +#define SET_TX_DESC_DATA_BW(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 25, 1, __val) +#define SET_TX_DESC_RTS_SHORT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 26, 1, __val) +#define SET_TX_DESC_RTS_BW(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 27, 1, __val) +#define SET_TX_DESC_RTS_SC(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 28, 2, __val) +#define SET_TX_DESC_RTS_STBC(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 30, 2, __val) + +#define GET_TX_DESC_RTS_RATE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 0, 5) +#define GET_TX_DESC_AP_DCFE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 5, 1) +#define GET_TX_DESC_QOS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 6, 1) +#define GET_TX_DESC_HWSEQ_EN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 7, 1) +#define GET_TX_DESC_USE_RATE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 8, 1) +#define GET_TX_DESC_DISABLE_RTS_FB(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 9, 1) +#define GET_TX_DESC_DISABLE_FB(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 10, 1) +#define GET_TX_DESC_CTS2SELF(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 11, 1) +#define GET_TX_DESC_RTS_ENABLE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 12, 1) +#define GET_TX_DESC_HW_RTS_ENABLE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 13, 1) +#define GET_TX_DESC_PORT_ID(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 14, 1) +#define GET_TX_DESC_WAIT_DCTS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 18, 1) +#define GET_TX_DESC_CTS2AP_EN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 19, 1) +#define GET_TX_DESC_TX_SUB_CARRIER(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 20, 2) +#define GET_TX_DESC_TX_STBC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 22, 2) +#define GET_TX_DESC_DATA_SHORT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 24, 1) +#define GET_TX_DESC_DATA_BW(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 25, 1) +#define GET_TX_DESC_RTS_SHORT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 26, 1) +#define GET_TX_DESC_RTS_BW(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 27, 1) +#define GET_TX_DESC_RTS_SC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 28, 2) +#define GET_TX_DESC_RTS_STBC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 30, 2) + +#define SET_TX_DESC_TX_RATE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+20, 0, 6, __val) +#define SET_TX_DESC_DATA_SHORTGI(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+20, 6, 1, __val) +#define SET_TX_DESC_CCX_TAG(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+20, 7, 1, __val) +#define SET_TX_DESC_DATA_RATE_FB_LIMIT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+20, 8, 5, __val) +#define SET_TX_DESC_RTS_RATE_FB_LIMIT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+20, 13, 4, __val) +#define SET_TX_DESC_RETRY_LIMIT_ENABLE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+20, 17, 1, __val) +#define SET_TX_DESC_DATA_RETRY_LIMIT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+20, 18, 6, __val) +#define SET_TX_DESC_USB_TXAGG_NUM(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+20, 24, 8, __val) + +#define GET_TX_DESC_TX_RATE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 0, 6) +#define GET_TX_DESC_DATA_SHORTGI(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 6, 1) +#define GET_TX_DESC_CCX_TAG(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 7, 1) +#define GET_TX_DESC_DATA_RATE_FB_LIMIT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 8, 5) +#define GET_TX_DESC_RTS_RATE_FB_LIMIT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 13, 4) +#define GET_TX_DESC_RETRY_LIMIT_ENABLE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 17, 1) +#define GET_TX_DESC_DATA_RETRY_LIMIT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 18, 6) +#define GET_TX_DESC_USB_TXAGG_NUM(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 24, 8) + +#define SET_TX_DESC_TXAGC_A(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 0, 5, __val) +#define SET_TX_DESC_TXAGC_B(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 5, 5, __val) +#define SET_TX_DESC_USE_MAX_LEN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 10, 1, __val) +#define SET_TX_DESC_MAX_AGG_NUM(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 11, 5, __val) +#define SET_TX_DESC_MCSG1_MAX_LEN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 16, 4, __val) +#define SET_TX_DESC_MCSG2_MAX_LEN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 20, 4, __val) +#define SET_TX_DESC_MCSG3_MAX_LEN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 24, 4, __val) +#define SET_TX_DESC_MCS7_SGI_MAX_LEN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 28, 4, __val) + +#define GET_TX_DESC_TXAGC_A(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 0, 5) +#define GET_TX_DESC_TXAGC_B(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 5, 5) +#define GET_TX_DESC_USE_MAX_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 10, 1) +#define GET_TX_DESC_MAX_AGG_NUM(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 11, 5) +#define GET_TX_DESC_MCSG1_MAX_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 16, 4) +#define GET_TX_DESC_MCSG2_MAX_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 20, 4) +#define GET_TX_DESC_MCSG3_MAX_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 24, 4) +#define GET_TX_DESC_MCS7_SGI_MAX_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 28, 4) + +#define SET_TX_DESC_TX_BUFFER_SIZE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+28, 0, 16, __val) +#define SET_TX_DESC_SW_OFFSET30(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+28, 16, 8, __val) +#define SET_TX_DESC_SW_OFFSET31(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+28, 24, 4, __val) +#define SET_TX_DESC_ANTSEL_C(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+28, 29, 1, __val) +#define SET_TX_DESC_NULL_0(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+28, 30, 1, __val) +#define SET_TX_DESC_NULL_1(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+28, 30, 1, __val) + +#define GET_TX_DESC_TX_BUFFER_SIZE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+28, 0, 16) + + +#define SET_TX_DESC_TX_BUFFER_ADDRESS(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+32, 0, 32, __val) +#define SET_TX_DESC_TX_BUFFER_ADDRESS64(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+36, 0, 32, __val) + +#define GET_TX_DESC_TX_BUFFER_ADDRESS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+32, 0, 32) +#define GET_TX_DESC_TX_BUFFER_ADDRESS64(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+36, 0, 32) + +#define SET_TX_DESC_NEXT_DESC_ADDRESS(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+40, 0, 32, __val) +#define SET_TX_DESC_NEXT_DESC_ADDRESS64(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+44, 0, 32, __val) + +#define GET_TX_DESC_NEXT_DESC_ADDRESS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+40, 0, 32) +#define GET_TX_DESC_NEXT_DESC_ADDRESS64(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+44, 0, 32) + +#define GET_RX_DESC_PKT_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 0, 14) +#define GET_RX_DESC_CRC32(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 14, 1) +#define GET_RX_DESC_ICV(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 15, 1) +#define GET_RX_DESC_DRV_INFO_SIZE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 16, 4) +#define GET_RX_DESC_SECURITY(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 20, 3) +#define GET_RX_DESC_QOS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 23, 1) +#define GET_RX_DESC_SHIFT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 24, 2) +#define GET_RX_DESC_PHYST(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 26, 1) +#define GET_RX_DESC_SWDEC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 27, 1) +#define GET_RX_DESC_LS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 28, 1) +#define GET_RX_DESC_FS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 29, 1) +#define GET_RX_DESC_EOR(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 30, 1) +#define GET_RX_DESC_OWN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 31, 1) + +#define SET_RX_DESC_PKT_LEN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 0, 14, __val) +#define SET_RX_DESC_EOR(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 30, 1, __val) +#define SET_RX_DESC_OWN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 31, 1, __val) + +#define GET_RX_DESC_MACID(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 0, 6) +#define GET_RX_DESC_PAGGR(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 14, 1) +#define GET_RX_DESC_FAGGR(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 15, 1) +#define GET_RX_DESC_A1_FIT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 16, 4) +#define GET_RX_DESC_A2_FIT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 20, 4) +#define GET_RX_DESC_PAM(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 24, 1) +#define GET_RX_DESC_PWR(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 25, 1) +#define GET_RX_DESC_MD(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 26, 1) +#define GET_RX_DESC_MF(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 27, 1) +#define GET_RX_DESC_TYPE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 28, 2) +#define GET_RX_DESC_MC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 30, 1) +#define GET_RX_DESC_BC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 31, 1) +#define GET_RX_DESC_SEQ(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 0, 12) +#define GET_RX_DESC_FRAG(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 12, 4) + +#define GET_RX_DESC_RXMCS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 0, 6) +#define GET_RX_DESC_RXHT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 6, 1) +#define GET_RX_STATUS_DESC_RX_GF(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 7, 1) +#define GET_RX_DESC_SPLCP(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 8, 1) +#define GET_RX_DESC_BW(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 9, 1) +#define GET_RX_DESC_HTC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 10, 1) +#define GET_RX_STATUS_DESC_EOSP(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 11, 1) +#define GET_RX_STATUS_DESC_BSSID_FIT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 12, 2) +#define GET_RX_STATUS_DESC_RPT_SEL(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 14, 2) + +#define GET_RX_STATUS_DESC_PATTERN_MATCH(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 29, 1) +#define GET_RX_STATUS_DESC_UNICAST_MATCH(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 30, 1) +#define GET_RX_STATUS_DESC_MAGIC_MATCH(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 31, 1) + +#define GET_RX_DESC_IV1(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 0, 32) +#define GET_RX_DESC_TSFL(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 0, 32) + +#define GET_RX_DESC_BUFF_ADDR(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 0, 32) +#define GET_RX_DESC_BUFF_ADDR64(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+28, 0, 32) + +#define SET_RX_DESC_BUFF_ADDR(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 0, 32, __val) +#define SET_RX_DESC_BUFF_ADDR64(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+28, 0, 32, __val) + +/* TX report 2 format in Rx desc*/ + +#define GET_RX_RPT2_DESC_PKT_LEN(__status) \ + LE_BITS_TO_4BYTE(__status, 0, 9) +#define GET_RX_RPT2_DESC_MACID_VALID_1(__status) \ + LE_BITS_TO_4BYTE(__status+16, 0, 32) +#define GET_RX_RPT2_DESC_MACID_VALID_2(__status) \ + LE_BITS_TO_4BYTE(__status+20, 0, 32) + +#define SET_EARLYMODE_PKTNUM(__paddr, __value) \ + SET_BITS_TO_LE_4BYTE(__paddr, 0, 4, __value) +#define SET_EARLYMODE_LEN0(__paddr, __value) \ + SET_BITS_TO_LE_4BYTE(__paddr, 4, 12, __value) +#define SET_EARLYMODE_LEN1(__paddr, __value) \ + SET_BITS_TO_LE_4BYTE(__paddr, 16, 12, __value) +#define SET_EARLYMODE_LEN2_1(__paddr, __value) \ + SET_BITS_TO_LE_4BYTE(__paddr, 28, 4, __value) +#define SET_EARLYMODE_LEN2_2(__paddr, __value) \ + SET_BITS_TO_LE_4BYTE(__paddr+4, 0, 8, __value) +#define SET_EARLYMODE_LEN3(__paddr, __value) \ + SET_BITS_TO_LE_4BYTE(__paddr+4, 8, 12, __value) +#define SET_EARLYMODE_LEN4(__paddr, __value) \ + SET_BITS_TO_LE_4BYTE(__paddr+4, 20, 12, __value) + +#define CLEAR_PCI_TX_DESC_CONTENT(__pdesc, _size) \ +do { \ + if (_size > TX_DESC_NEXT_DESC_OFFSET) \ + memset(__pdesc, 0, TX_DESC_NEXT_DESC_OFFSET); \ + else \ + memset(__pdesc, 0, _size); \ +} while (0) + +#define RTL8188_RX_HAL_IS_CCK_RATE(rxmcs)\ + (rxmcs == DESC92C_RATE1M ||\ + rxmcs == DESC92C_RATE2M ||\ + rxmcs == DESC92C_RATE5_5M ||\ + rxmcs == DESC92C_RATE11M) + +struct phy_rx_agc_info_t { + #ifdef __LITTLE_ENDIAN + u8 gain:7, trsw:1; + #else + u8 trsw:1, gain:7; + #endif +}; +struct phy_status_rpt { + struct phy_rx_agc_info_t path_agc[2]; + u8 ch_corr[2]; + u8 cck_sig_qual_ofdm_pwdb_all; + u8 cck_agc_rpt_ofdm_cfosho_a; + u8 cck_rpt_b_ofdm_cfosho_b; + u8 rsvd_1; + u8 noise_power_db_msb; + u8 path_cfotail[2]; + u8 pcts_mask[2]; + u8 stream_rxevm[2]; + u8 path_rxsnr[2]; + u8 noise_power_db_lsb; + u8 rsvd_2[3]; + u8 stream_csi[2]; + u8 stream_target_csi[2]; + u8 sig_evm; + u8 rsvd_3; +#ifdef __LITTLE_ENDIAN + u8 antsel_rx_keep_2:1; /*ex_intf_flg:1;*/ + u8 sgi_en:1; + u8 rxsc:2; + u8 idle_long:1; + u8 r_ant_train_en:1; + u8 ant_sel_b:1; + u8 ant_sel:1; +#else /* _BIG_ENDIAN_ */ + u8 ant_sel:1; + u8 ant_sel_b:1; + u8 r_ant_train_en:1; + u8 idle_long:1; + u8 rxsc:2; + u8 sgi_en:1; + u8 antsel_rx_keep_2:1; /*ex_intf_flg:1;*/ +#endif +} __packed; + +struct rx_fwinfo_88e { + u8 gain_trsw[4]; + u8 pwdb_all; + u8 cfosho[4]; + u8 cfotail[4]; + char rxevm[2]; + char rxsnr[4]; + u8 pdsnr[2]; + u8 csi_current[2]; + u8 csi_target[2]; + u8 sigevm; + u8 max_ex_pwr; + u8 ex_intf_flag:1; + u8 sgi_en:1; + u8 rxsc:2; + u8 reserve:4; +} __packed; + +struct tx_desc_88e { + u32 pktsize:16; + u32 offset:8; + u32 bmc:1; + u32 htc:1; + u32 lastseg:1; + u32 firstseg:1; + u32 linip:1; + u32 noacm:1; + u32 gf:1; + u32 own:1; + + u32 macid:6; + u32 rsvd0:2; + u32 queuesel:5; + u32 rd_nav_ext:1; + u32 lsig_txop_en:1; + u32 pifs:1; + u32 rateid:4; + u32 nav_usehdr:1; + u32 en_descid:1; + u32 sectype:2; + u32 pktoffset:8; + + u32 rts_rc:6; + u32 data_rc:6; + u32 agg_en:1; + u32 rdg_en:1; + u32 bar_retryht:2; + u32 agg_break:1; + u32 morefrag:1; + u32 raw:1; + u32 ccx:1; + u32 ampdudensity:3; + u32 bt_int:1; + u32 ant_sela:1; + u32 ant_selb:1; + u32 txant_cck:2; + u32 txant_l:2; + u32 txant_ht:2; + + u32 nextheadpage:8; + u32 tailpage:8; + u32 seq:12; + u32 cpu_handle:1; + u32 tag1:1; + u32 trigger_int:1; + u32 hwseq_en:1; + + u32 rtsrate:5; + u32 apdcfe:1; + u32 qos:1; + u32 hwseq_ssn:1; + u32 userrate:1; + u32 dis_rtsfb:1; + u32 dis_datafb:1; + u32 cts2self:1; + u32 rts_en:1; + u32 hwrts_en:1; + u32 portid:1; + u32 pwr_status:3; + u32 waitdcts:1; + u32 cts2ap_en:1; + u32 txsc:2; + u32 stbc:2; + u32 txshort:1; + u32 txbw:1; + u32 rtsshort:1; + u32 rtsbw:1; + u32 rtssc:2; + u32 rtsstbc:2; + + u32 txrate:6; + u32 shortgi:1; + u32 ccxt:1; + u32 txrate_fb_lmt:5; + u32 rtsrate_fb_lmt:4; + u32 retrylmt_en:1; + u32 txretrylmt:6; + u32 usb_txaggnum:8; + + u32 txagca:5; + u32 txagcb:5; + u32 usemaxlen:1; + u32 maxaggnum:5; + u32 mcsg1maxlen:4; + u32 mcsg2maxlen:4; + u32 mcsg3maxlen:4; + u32 mcs7sgimaxlen:4; + + u32 txbuffersize:16; + u32 sw_offset30:8; + u32 sw_offset31:4; + u32 rsvd1:1; + u32 antsel_c:1; + u32 null_0:1; + u32 null_1:1; + + u32 txbuffaddr; + u32 txbufferaddr64; + u32 nextdescaddress; + u32 nextdescaddress64; + + u32 reserve_pass_pcie_mm_limit[4]; +} __packed; + +struct rx_desc_88e { + u32 length:14; + u32 crc32:1; + u32 icverror:1; + u32 drv_infosize:4; + u32 security:3; + u32 qos:1; + u32 shift:2; + u32 phystatus:1; + u32 swdec:1; + u32 lastseg:1; + u32 firstseg:1; + u32 eor:1; + u32 own:1; + + u32 macid:6; + u32 tid:4; + u32 hwrsvd:5; + u32 paggr:1; + u32 faggr:1; + u32 a1_fit:4; + u32 a2_fit:4; + u32 pam:1; + u32 pwr:1; + u32 moredata:1; + u32 morefrag:1; + u32 type:2; + u32 mc:1; + u32 bc:1; + + u32 seq:12; + u32 frag:4; + u32 nextpktlen:14; + u32 nextind:1; + u32 rsvd:1; + + u32 rxmcs:6; + u32 rxht:1; + u32 amsdu:1; + u32 splcp:1; + u32 bandwidth:1; + u32 htc:1; + u32 tcpchk_rpt:1; + u32 ipcchk_rpt:1; + u32 tcpchk_valid:1; + u32 hwpcerr:1; + u32 hwpcind:1; + u32 iv0:16; + + u32 iv1; + + u32 tsfl; + + u32 bufferaddress; + u32 bufferaddress64; + +} __packed; + +void rtl88ee_tx_fill_desc(struct ieee80211_hw *hw, + struct ieee80211_hdr *hdr, u8 *pdesc_tx, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff *skb, + u8 hw_queue, struct rtl_tcb_desc *ptcb_desc); +bool rtl88ee_rx_query_desc(struct ieee80211_hw *hw, + struct rtl_stats *status, + struct ieee80211_rx_status *rx_status, + u8 *pdesc, struct sk_buff *skb); +void rtl88ee_set_desc(u8 *pdesc, bool istx, u8 desc_name, u8 *val); +u32 rtl88ee_get_desc(u8 *pdesc, bool istx, u8 desc_name); +void rtl88ee_tx_polling(struct ieee80211_hw *hw, u8 hw_queue); +void rtl88ee_tx_fill_cmddesc(struct ieee80211_hw *hw, u8 *pdesc, + bool b_firstseg, bool b_lastseg, + struct sk_buff *skb); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192c/Makefile b/drivers/net/wireless/rtlwifi/rtl8192c/Makefile new file mode 100644 index 0000000..4930856 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192c/Makefile @@ -0,0 +1,9 @@ +rtl8192c-common-objs := \ + main.o \ + dm_common.o \ + fw_common.o \ + phy_common.o + +obj-$(CPTCFG_RTL8192C_COMMON) += rtl8192c-common.o + +ccflags-y += -D__CHECK_ENDIAN__ diff --git a/drivers/net/wireless/rtlwifi/rtl8192c/dm_common.c b/drivers/net/wireless/rtlwifi/rtl8192c/dm_common.c new file mode 100644 index 0000000..d2d57a2 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192c/dm_common.c @@ -0,0 +1,1701 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include +#include "dm_common.h" +#include "phy_common.h" +#include "../pci.h" +#include "../base.h" + +#define BT_RSSI_STATE_NORMAL_POWER BIT_OFFSET_LEN_MASK_32(0, 1) +#define BT_RSSI_STATE_AMDPU_OFF BIT_OFFSET_LEN_MASK_32(1, 1) +#define BT_RSSI_STATE_SPECIAL_LOW BIT_OFFSET_LEN_MASK_32(2, 1) +#define BT_RSSI_STATE_BG_EDCA_LOW BIT_OFFSET_LEN_MASK_32(3, 1) +#define BT_RSSI_STATE_TXPOWER_LOW BIT_OFFSET_LEN_MASK_32(4, 1) + +#define RTLPRIV (struct rtl_priv *) +#define GET_UNDECORATED_AVERAGE_RSSI(_priv) \ + ((RTLPRIV(_priv))->mac80211.opmode == \ + NL80211_IFTYPE_ADHOC) ? \ + ((RTLPRIV(_priv))->dm.entry_min_undec_sm_pwdb) : \ + ((RTLPRIV(_priv))->dm.undec_sm_pwdb) + +static const u32 ofdmswing_table[OFDM_TABLE_SIZE] = { + 0x7f8001fe, + 0x788001e2, + 0x71c001c7, + 0x6b8001ae, + 0x65400195, + 0x5fc0017f, + 0x5a400169, + 0x55400155, + 0x50800142, + 0x4c000130, + 0x47c0011f, + 0x43c0010f, + 0x40000100, + 0x3c8000f2, + 0x390000e4, + 0x35c000d7, + 0x32c000cb, + 0x300000c0, + 0x2d4000b5, + 0x2ac000ab, + 0x288000a2, + 0x26000098, + 0x24000090, + 0x22000088, + 0x20000080, + 0x1e400079, + 0x1c800072, + 0x1b00006c, + 0x19800066, + 0x18000060, + 0x16c0005b, + 0x15800056, + 0x14400051, + 0x1300004c, + 0x12000048, + 0x11000044, + 0x10000040, +}; + +static const u8 cckswing_table_ch1ch13[CCK_TABLE_SIZE][8] = { + {0x36, 0x35, 0x2e, 0x25, 0x1c, 0x12, 0x09, 0x04}, + {0x33, 0x32, 0x2b, 0x23, 0x1a, 0x11, 0x08, 0x04}, + {0x30, 0x2f, 0x29, 0x21, 0x19, 0x10, 0x08, 0x03}, + {0x2d, 0x2d, 0x27, 0x1f, 0x18, 0x0f, 0x08, 0x03}, + {0x2b, 0x2a, 0x25, 0x1e, 0x16, 0x0e, 0x07, 0x03}, + {0x28, 0x28, 0x22, 0x1c, 0x15, 0x0d, 0x07, 0x03}, + {0x26, 0x25, 0x21, 0x1b, 0x14, 0x0d, 0x06, 0x03}, + {0x24, 0x23, 0x1f, 0x19, 0x13, 0x0c, 0x06, 0x03}, + {0x22, 0x21, 0x1d, 0x18, 0x11, 0x0b, 0x06, 0x02}, + {0x20, 0x20, 0x1b, 0x16, 0x11, 0x08, 0x05, 0x02}, + {0x1f, 0x1e, 0x1a, 0x15, 0x10, 0x0a, 0x05, 0x02}, + {0x1d, 0x1c, 0x18, 0x14, 0x0f, 0x0a, 0x05, 0x02}, + {0x1b, 0x1a, 0x17, 0x13, 0x0e, 0x09, 0x04, 0x02}, + {0x1a, 0x19, 0x16, 0x12, 0x0d, 0x09, 0x04, 0x02}, + {0x18, 0x17, 0x15, 0x11, 0x0c, 0x08, 0x04, 0x02}, + {0x17, 0x16, 0x13, 0x10, 0x0c, 0x08, 0x04, 0x02}, + {0x16, 0x15, 0x12, 0x0f, 0x0b, 0x07, 0x04, 0x01}, + {0x14, 0x14, 0x11, 0x0e, 0x0b, 0x07, 0x03, 0x02}, + {0x13, 0x13, 0x10, 0x0d, 0x0a, 0x06, 0x03, 0x01}, + {0x12, 0x12, 0x0f, 0x0c, 0x09, 0x06, 0x03, 0x01}, + {0x11, 0x11, 0x0f, 0x0c, 0x09, 0x06, 0x03, 0x01}, + {0x10, 0x10, 0x0e, 0x0b, 0x08, 0x05, 0x03, 0x01}, + {0x0f, 0x0f, 0x0d, 0x0b, 0x08, 0x05, 0x03, 0x01}, + {0x0e, 0x0e, 0x0c, 0x0a, 0x08, 0x05, 0x02, 0x01}, + {0x0d, 0x0d, 0x0c, 0x0a, 0x07, 0x05, 0x02, 0x01}, + {0x0d, 0x0c, 0x0b, 0x09, 0x07, 0x04, 0x02, 0x01}, + {0x0c, 0x0c, 0x0a, 0x09, 0x06, 0x04, 0x02, 0x01}, + {0x0b, 0x0b, 0x0a, 0x08, 0x06, 0x04, 0x02, 0x01}, + {0x0b, 0x0a, 0x09, 0x08, 0x06, 0x04, 0x02, 0x01}, + {0x0a, 0x0a, 0x09, 0x07, 0x05, 0x03, 0x02, 0x01}, + {0x0a, 0x09, 0x08, 0x07, 0x05, 0x03, 0x02, 0x01}, + {0x09, 0x09, 0x08, 0x06, 0x05, 0x03, 0x01, 0x01}, + {0x09, 0x08, 0x07, 0x06, 0x04, 0x03, 0x01, 0x01} +}; + +static const u8 cckswing_table_ch14[CCK_TABLE_SIZE][8] = { + {0x36, 0x35, 0x2e, 0x1b, 0x00, 0x00, 0x00, 0x00}, + {0x33, 0x32, 0x2b, 0x19, 0x00, 0x00, 0x00, 0x00}, + {0x30, 0x2f, 0x29, 0x18, 0x00, 0x00, 0x00, 0x00}, + {0x2d, 0x2d, 0x17, 0x17, 0x00, 0x00, 0x00, 0x00}, + {0x2b, 0x2a, 0x25, 0x15, 0x00, 0x00, 0x00, 0x00}, + {0x28, 0x28, 0x24, 0x14, 0x00, 0x00, 0x00, 0x00}, + {0x26, 0x25, 0x21, 0x13, 0x00, 0x00, 0x00, 0x00}, + {0x24, 0x23, 0x1f, 0x12, 0x00, 0x00, 0x00, 0x00}, + {0x22, 0x21, 0x1d, 0x11, 0x00, 0x00, 0x00, 0x00}, + {0x20, 0x20, 0x1b, 0x10, 0x00, 0x00, 0x00, 0x00}, + {0x1f, 0x1e, 0x1a, 0x0f, 0x00, 0x00, 0x00, 0x00}, + {0x1d, 0x1c, 0x18, 0x0e, 0x00, 0x00, 0x00, 0x00}, + {0x1b, 0x1a, 0x17, 0x0e, 0x00, 0x00, 0x00, 0x00}, + {0x1a, 0x19, 0x16, 0x0d, 0x00, 0x00, 0x00, 0x00}, + {0x18, 0x17, 0x15, 0x0c, 0x00, 0x00, 0x00, 0x00}, + {0x17, 0x16, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00}, + {0x16, 0x15, 0x12, 0x0b, 0x00, 0x00, 0x00, 0x00}, + {0x14, 0x14, 0x11, 0x0a, 0x00, 0x00, 0x00, 0x00}, + {0x13, 0x13, 0x10, 0x0a, 0x00, 0x00, 0x00, 0x00}, + {0x12, 0x12, 0x0f, 0x09, 0x00, 0x00, 0x00, 0x00}, + {0x11, 0x11, 0x0f, 0x09, 0x00, 0x00, 0x00, 0x00}, + {0x10, 0x10, 0x0e, 0x08, 0x00, 0x00, 0x00, 0x00}, + {0x0f, 0x0f, 0x0d, 0x08, 0x00, 0x00, 0x00, 0x00}, + {0x0e, 0x0e, 0x0c, 0x07, 0x00, 0x00, 0x00, 0x00}, + {0x0d, 0x0d, 0x0c, 0x07, 0x00, 0x00, 0x00, 0x00}, + {0x0d, 0x0c, 0x0b, 0x06, 0x00, 0x00, 0x00, 0x00}, + {0x0c, 0x0c, 0x0a, 0x06, 0x00, 0x00, 0x00, 0x00}, + {0x0b, 0x0b, 0x0a, 0x06, 0x00, 0x00, 0x00, 0x00}, + {0x0b, 0x0a, 0x09, 0x05, 0x00, 0x00, 0x00, 0x00}, + {0x0a, 0x0a, 0x09, 0x05, 0x00, 0x00, 0x00, 0x00}, + {0x0a, 0x09, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00}, + {0x09, 0x09, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00}, + {0x09, 0x08, 0x07, 0x04, 0x00, 0x00, 0x00, 0x00} +}; + +static void rtl92c_dm_diginit(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + + dm_digtable->dig_enable_flag = true; + dm_digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_MAX; + dm_digtable->cur_igvalue = 0x20; + dm_digtable->pre_igvalue = 0x0; + dm_digtable->cursta_cstate = DIG_STA_DISCONNECT; + dm_digtable->presta_cstate = DIG_STA_DISCONNECT; + dm_digtable->curmultista_cstate = DIG_MULTISTA_DISCONNECT; + dm_digtable->rssi_lowthresh = DM_DIG_THRESH_LOW; + dm_digtable->rssi_highthresh = DM_DIG_THRESH_HIGH; + dm_digtable->fa_lowthresh = DM_FALSEALARM_THRESH_LOW; + dm_digtable->fa_highthresh = DM_FALSEALARM_THRESH_HIGH; + dm_digtable->rx_gain_max = DM_DIG_MAX; + dm_digtable->rx_gain_min = DM_DIG_MIN; + dm_digtable->back_val = DM_DIG_BACKOFF_DEFAULT; + dm_digtable->back_range_max = DM_DIG_BACKOFF_MAX; + dm_digtable->back_range_min = DM_DIG_BACKOFF_MIN; + dm_digtable->pre_cck_pd_state = CCK_PD_STAGE_MAX; + dm_digtable->cur_cck_pd_state = CCK_PD_STAGE_MAX; +} + +static u8 rtl92c_dm_initial_gain_min_pwdb(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + long rssi_val_min = 0; + + if ((dm_digtable->curmultista_cstate == DIG_MULTISTA_CONNECT) && + (dm_digtable->cursta_cstate == DIG_STA_CONNECT)) { + if (rtlpriv->dm.entry_min_undec_sm_pwdb != 0) + rssi_val_min = + (rtlpriv->dm.entry_min_undec_sm_pwdb > + rtlpriv->dm.undec_sm_pwdb) ? + rtlpriv->dm.undec_sm_pwdb : + rtlpriv->dm.entry_min_undec_sm_pwdb; + else + rssi_val_min = rtlpriv->dm.undec_sm_pwdb; + } else if (dm_digtable->cursta_cstate == DIG_STA_CONNECT || + dm_digtable->cursta_cstate == DIG_STA_BEFORE_CONNECT) { + rssi_val_min = rtlpriv->dm.undec_sm_pwdb; + } else if (dm_digtable->curmultista_cstate == DIG_MULTISTA_CONNECT) { + rssi_val_min = rtlpriv->dm.entry_min_undec_sm_pwdb; + } + + return (u8) rssi_val_min; +} + +static void rtl92c_dm_false_alarm_counter_statistics(struct ieee80211_hw *hw) +{ + u32 ret_value; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct false_alarm_statistics *falsealm_cnt = &(rtlpriv->falsealm_cnt); + + ret_value = rtl_get_bbreg(hw, ROFDM_PHYCOUNTER1, MASKDWORD); + falsealm_cnt->cnt_parity_fail = ((ret_value & 0xffff0000) >> 16); + + ret_value = rtl_get_bbreg(hw, ROFDM_PHYCOUNTER2, MASKDWORD); + falsealm_cnt->cnt_rate_illegal = (ret_value & 0xffff); + falsealm_cnt->cnt_crc8_fail = ((ret_value & 0xffff0000) >> 16); + + ret_value = rtl_get_bbreg(hw, ROFDM_PHYCOUNTER3, MASKDWORD); + falsealm_cnt->cnt_mcs_fail = (ret_value & 0xffff); + falsealm_cnt->cnt_ofdm_fail = falsealm_cnt->cnt_parity_fail + + falsealm_cnt->cnt_rate_illegal + + falsealm_cnt->cnt_crc8_fail + falsealm_cnt->cnt_mcs_fail; + + rtl_set_bbreg(hw, RCCK0_FALSEALARMREPORT, BIT(14), 1); + ret_value = rtl_get_bbreg(hw, RCCK0_FACOUNTERLOWER, MASKBYTE0); + falsealm_cnt->cnt_cck_fail = ret_value; + + ret_value = rtl_get_bbreg(hw, RCCK0_FACOUNTERUPPER, MASKBYTE3); + falsealm_cnt->cnt_cck_fail += (ret_value & 0xff) << 8; + falsealm_cnt->cnt_all = (falsealm_cnt->cnt_parity_fail + + falsealm_cnt->cnt_rate_illegal + + falsealm_cnt->cnt_crc8_fail + + falsealm_cnt->cnt_mcs_fail + + falsealm_cnt->cnt_cck_fail); + + rtl_set_bbreg(hw, ROFDM1_LSTF, 0x08000000, 1); + rtl_set_bbreg(hw, ROFDM1_LSTF, 0x08000000, 0); + rtl_set_bbreg(hw, RCCK0_FALSEALARMREPORT, 0x0000c000, 0); + rtl_set_bbreg(hw, RCCK0_FALSEALARMREPORT, 0x0000c000, 2); + + RT_TRACE(rtlpriv, COMP_DIG, DBG_TRACE, + "cnt_parity_fail = %d, cnt_rate_illegal = %d, cnt_crc8_fail = %d, cnt_mcs_fail = %d\n", + falsealm_cnt->cnt_parity_fail, + falsealm_cnt->cnt_rate_illegal, + falsealm_cnt->cnt_crc8_fail, falsealm_cnt->cnt_mcs_fail); + + RT_TRACE(rtlpriv, COMP_DIG, DBG_TRACE, + "cnt_ofdm_fail = %x, cnt_cck_fail = %x, cnt_all = %x\n", + falsealm_cnt->cnt_ofdm_fail, + falsealm_cnt->cnt_cck_fail, falsealm_cnt->cnt_all); +} + +static void rtl92c_dm_ctrl_initgain_by_fa(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + u8 value_igi = dm_digtable->cur_igvalue; + + if (rtlpriv->falsealm_cnt.cnt_all < DM_DIG_FA_TH0) + value_igi--; + else if (rtlpriv->falsealm_cnt.cnt_all < DM_DIG_FA_TH1) + value_igi += 0; + else if (rtlpriv->falsealm_cnt.cnt_all < DM_DIG_FA_TH2) + value_igi++; + else if (rtlpriv->falsealm_cnt.cnt_all >= DM_DIG_FA_TH2) + value_igi += 2; + if (value_igi > DM_DIG_FA_UPPER) + value_igi = DM_DIG_FA_UPPER; + else if (value_igi < DM_DIG_FA_LOWER) + value_igi = DM_DIG_FA_LOWER; + if (rtlpriv->falsealm_cnt.cnt_all > 10000) + value_igi = 0x32; + + dm_digtable->cur_igvalue = value_igi; + rtl92c_dm_write_dig(hw); +} + +static void rtl92c_dm_ctrl_initgain_by_rssi(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *digtable = &rtlpriv->dm_digtable; + + if (rtlpriv->falsealm_cnt.cnt_all > digtable->fa_highthresh) { + if ((digtable->back_val - 2) < digtable->back_range_min) + digtable->back_val = digtable->back_range_min; + else + digtable->back_val -= 2; + } else if (rtlpriv->falsealm_cnt.cnt_all < digtable->fa_lowthresh) { + if ((digtable->back_val + 2) > digtable->back_range_max) + digtable->back_val = digtable->back_range_max; + else + digtable->back_val += 2; + } + + if ((digtable->rssi_val_min + 10 - digtable->back_val) > + digtable->rx_gain_max) + digtable->cur_igvalue = digtable->rx_gain_max; + else if ((digtable->rssi_val_min + 10 - + digtable->back_val) < digtable->rx_gain_min) + digtable->cur_igvalue = digtable->rx_gain_min; + else + digtable->cur_igvalue = digtable->rssi_val_min + 10 - + digtable->back_val; + + RT_TRACE(rtlpriv, COMP_DIG, DBG_TRACE, + "rssi_val_min = %x back_val %x\n", + digtable->rssi_val_min, digtable->back_val); + + rtl92c_dm_write_dig(hw); +} + +static void rtl92c_dm_initial_gain_multi_sta(struct ieee80211_hw *hw) +{ + static u8 initialized; /* initialized to false */ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + long rssi_strength = rtlpriv->dm.entry_min_undec_sm_pwdb; + bool multi_sta = false; + + if (mac->opmode == NL80211_IFTYPE_ADHOC) + multi_sta = true; + + if (!multi_sta || + dm_digtable->cursta_cstate != DIG_STA_DISCONNECT) { + initialized = false; + dm_digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_MAX; + return; + } else if (initialized == false) { + initialized = true; + dm_digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_0; + dm_digtable->cur_igvalue = 0x20; + rtl92c_dm_write_dig(hw); + } + + if (dm_digtable->curmultista_cstate == DIG_MULTISTA_CONNECT) { + if ((rssi_strength < dm_digtable->rssi_lowthresh) && + (dm_digtable->dig_ext_port_stage != DIG_EXT_PORT_STAGE_1)) { + + if (dm_digtable->dig_ext_port_stage == + DIG_EXT_PORT_STAGE_2) { + dm_digtable->cur_igvalue = 0x20; + rtl92c_dm_write_dig(hw); + } + + dm_digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_1; + } else if (rssi_strength > dm_digtable->rssi_highthresh) { + dm_digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_2; + rtl92c_dm_ctrl_initgain_by_fa(hw); + } + } else if (dm_digtable->dig_ext_port_stage != DIG_EXT_PORT_STAGE_0) { + dm_digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_0; + dm_digtable->cur_igvalue = 0x20; + rtl92c_dm_write_dig(hw); + } + + RT_TRACE(rtlpriv, COMP_DIG, DBG_TRACE, + "curmultista_cstate = %x dig_ext_port_stage %x\n", + dm_digtable->curmultista_cstate, + dm_digtable->dig_ext_port_stage); +} + +static void rtl92c_dm_initial_gain_sta(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + + RT_TRACE(rtlpriv, COMP_DIG, DBG_TRACE, + "presta_cstate = %x, cursta_cstate = %x\n", + dm_digtable->presta_cstate, dm_digtable->cursta_cstate); + + if (dm_digtable->presta_cstate == dm_digtable->cursta_cstate || + dm_digtable->cursta_cstate == DIG_STA_BEFORE_CONNECT || + dm_digtable->cursta_cstate == DIG_STA_CONNECT) { + + if (dm_digtable->cursta_cstate != DIG_STA_DISCONNECT) { + dm_digtable->rssi_val_min = + rtl92c_dm_initial_gain_min_pwdb(hw); + rtl92c_dm_ctrl_initgain_by_rssi(hw); + } + } else { + dm_digtable->rssi_val_min = 0; + dm_digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_MAX; + dm_digtable->back_val = DM_DIG_BACKOFF_DEFAULT; + dm_digtable->cur_igvalue = 0x20; + dm_digtable->pre_igvalue = 0; + rtl92c_dm_write_dig(hw); + } +} + +static void rtl92c_dm_cck_packet_detection_thresh(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + + if (dm_digtable->cursta_cstate == DIG_STA_CONNECT) { + dm_digtable->rssi_val_min = rtl92c_dm_initial_gain_min_pwdb(hw); + + if (dm_digtable->pre_cck_pd_state == CCK_PD_STAGE_LowRssi) { + if (dm_digtable->rssi_val_min <= 25) + dm_digtable->cur_cck_pd_state = + CCK_PD_STAGE_LowRssi; + else + dm_digtable->cur_cck_pd_state = + CCK_PD_STAGE_HighRssi; + } else { + if (dm_digtable->rssi_val_min <= 20) + dm_digtable->cur_cck_pd_state = + CCK_PD_STAGE_LowRssi; + else + dm_digtable->cur_cck_pd_state = + CCK_PD_STAGE_HighRssi; + } + } else { + dm_digtable->cur_cck_pd_state = CCK_PD_STAGE_MAX; + } + + if (dm_digtable->pre_cck_pd_state != dm_digtable->cur_cck_pd_state) { + if (dm_digtable->cur_cck_pd_state == CCK_PD_STAGE_LowRssi) { + if (rtlpriv->falsealm_cnt.cnt_cck_fail > 800) + dm_digtable->cur_cck_fa_state = + CCK_FA_STAGE_High; + else + dm_digtable->cur_cck_fa_state = CCK_FA_STAGE_Low; + + if (dm_digtable->pre_cck_fa_state != + dm_digtable->cur_cck_fa_state) { + if (dm_digtable->cur_cck_fa_state == + CCK_FA_STAGE_Low) + rtl_set_bbreg(hw, RCCK0_CCA, MASKBYTE2, + 0x83); + else + rtl_set_bbreg(hw, RCCK0_CCA, MASKBYTE2, + 0xcd); + + dm_digtable->pre_cck_fa_state = + dm_digtable->cur_cck_fa_state; + } + + rtl_set_bbreg(hw, RCCK0_SYSTEM, MASKBYTE1, 0x40); + + if (IS_92C_SERIAL(rtlhal->version)) + rtl_set_bbreg(hw, RCCK0_FALSEALARMREPORT, + MASKBYTE2, 0xd7); + } else { + rtl_set_bbreg(hw, RCCK0_CCA, MASKBYTE2, 0xcd); + rtl_set_bbreg(hw, RCCK0_SYSTEM, MASKBYTE1, 0x47); + + if (IS_92C_SERIAL(rtlhal->version)) + rtl_set_bbreg(hw, RCCK0_FALSEALARMREPORT, + MASKBYTE2, 0xd3); + } + dm_digtable->pre_cck_pd_state = dm_digtable->cur_cck_pd_state; + } + + RT_TRACE(rtlpriv, COMP_DIG, DBG_TRACE, "CCKPDStage=%x\n", + dm_digtable->cur_cck_pd_state); + + RT_TRACE(rtlpriv, COMP_DIG, DBG_TRACE, "is92C=%x\n", + IS_92C_SERIAL(rtlhal->version)); +} + +static void rtl92c_dm_ctrl_initgain_by_twoport(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + + if (mac->act_scanning) + return; + + if (mac->link_state >= MAC80211_LINKED) + dm_digtable->cursta_cstate = DIG_STA_CONNECT; + else + dm_digtable->cursta_cstate = DIG_STA_DISCONNECT; + + rtl92c_dm_initial_gain_sta(hw); + rtl92c_dm_initial_gain_multi_sta(hw); + rtl92c_dm_cck_packet_detection_thresh(hw); + + dm_digtable->presta_cstate = dm_digtable->cursta_cstate; + +} + +static void rtl92c_dm_dig(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + + if (rtlpriv->dm.dm_initialgain_enable == false) + return; + if (dm_digtable->dig_enable_flag == false) + return; + + rtl92c_dm_ctrl_initgain_by_twoport(hw); + +} + +static void rtl92c_dm_init_dynamic_txpower(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.dynamic_txpower_enable = false; + + rtlpriv->dm.last_dtp_lvl = TXHIGHPWRLEVEL_NORMAL; + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; +} + +void rtl92c_dm_write_dig(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, + "cur_igvalue = 0x%x, pre_igvalue = 0x%x, back_val = %d\n", + dm_digtable->cur_igvalue, dm_digtable->pre_igvalue, + dm_digtable->back_val); + + dm_digtable->cur_igvalue += 2; + if (dm_digtable->cur_igvalue > 0x3f) + dm_digtable->cur_igvalue = 0x3f; + + if (dm_digtable->pre_igvalue != dm_digtable->cur_igvalue) { + rtl_set_bbreg(hw, ROFDM0_XAAGCCORE1, 0x7f, + dm_digtable->cur_igvalue); + rtl_set_bbreg(hw, ROFDM0_XBAGCCORE1, 0x7f, + dm_digtable->cur_igvalue); + + dm_digtable->pre_igvalue = dm_digtable->cur_igvalue; + } +} +EXPORT_SYMBOL(rtl92c_dm_write_dig); + +static void rtl92c_dm_pwdb_monitor(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + long tmpentry_max_pwdb = 0, tmpentry_min_pwdb = 0xff; + + u8 h2c_parameter[3] = { 0 }; + + return; + + if (tmpentry_max_pwdb != 0) { + rtlpriv->dm.entry_max_undec_sm_pwdb = tmpentry_max_pwdb; + } else { + rtlpriv->dm.entry_max_undec_sm_pwdb = 0; + } + + if (tmpentry_min_pwdb != 0xff) { + rtlpriv->dm.entry_min_undec_sm_pwdb = tmpentry_min_pwdb; + } else { + rtlpriv->dm.entry_min_undec_sm_pwdb = 0; + } + + h2c_parameter[2] = (u8) (rtlpriv->dm.undec_sm_pwdb & 0xFF); + h2c_parameter[0] = 0; + + rtl92c_fill_h2c_cmd(hw, H2C_RSSI_REPORT, 3, h2c_parameter); +} + +void rtl92c_dm_init_edca_turbo(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + rtlpriv->dm.current_turbo_edca = false; + rtlpriv->dm.is_any_nonbepkts = false; + rtlpriv->dm.is_cur_rdlstate = false; +} +EXPORT_SYMBOL(rtl92c_dm_init_edca_turbo); + +static void rtl92c_dm_check_edca_turbo(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + + static u64 last_txok_cnt; + static u64 last_rxok_cnt; + static u32 last_bt_edca_ul; + static u32 last_bt_edca_dl; + u64 cur_txok_cnt = 0; + u64 cur_rxok_cnt = 0; + u32 edca_be_ul = 0x5ea42b; + u32 edca_be_dl = 0x5ea42b; + bool bt_change_edca = false; + + if ((last_bt_edca_ul != rtlpcipriv->bt_coexist.bt_edca_ul) || + (last_bt_edca_dl != rtlpcipriv->bt_coexist.bt_edca_dl)) { + rtlpriv->dm.current_turbo_edca = false; + last_bt_edca_ul = rtlpcipriv->bt_coexist.bt_edca_ul; + last_bt_edca_dl = rtlpcipriv->bt_coexist.bt_edca_dl; + } + + if (rtlpcipriv->bt_coexist.bt_edca_ul != 0) { + edca_be_ul = rtlpcipriv->bt_coexist.bt_edca_ul; + bt_change_edca = true; + } + + if (rtlpcipriv->bt_coexist.bt_edca_dl != 0) { + edca_be_ul = rtlpcipriv->bt_coexist.bt_edca_dl; + bt_change_edca = true; + } + + if (mac->link_state != MAC80211_LINKED) { + rtlpriv->dm.current_turbo_edca = false; + return; + } + + if ((!mac->ht_enable) && (!rtlpcipriv->bt_coexist.bt_coexistence)) { + if (!(edca_be_ul & 0xffff0000)) + edca_be_ul |= 0x005e0000; + + if (!(edca_be_dl & 0xffff0000)) + edca_be_dl |= 0x005e0000; + } + + if ((bt_change_edca) || ((!rtlpriv->dm.is_any_nonbepkts) && + (!rtlpriv->dm.disable_framebursting))) { + + cur_txok_cnt = rtlpriv->stats.txbytesunicast - last_txok_cnt; + cur_rxok_cnt = rtlpriv->stats.rxbytesunicast - last_rxok_cnt; + + if (cur_rxok_cnt > 4 * cur_txok_cnt) { + if (!rtlpriv->dm.is_cur_rdlstate || + !rtlpriv->dm.current_turbo_edca) { + rtl_write_dword(rtlpriv, + REG_EDCA_BE_PARAM, + edca_be_dl); + rtlpriv->dm.is_cur_rdlstate = true; + } + } else { + if (rtlpriv->dm.is_cur_rdlstate || + !rtlpriv->dm.current_turbo_edca) { + rtl_write_dword(rtlpriv, + REG_EDCA_BE_PARAM, + edca_be_ul); + rtlpriv->dm.is_cur_rdlstate = false; + } + } + rtlpriv->dm.current_turbo_edca = true; + } else { + if (rtlpriv->dm.current_turbo_edca) { + u8 tmp = AC0_BE; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_AC_PARAM, + &tmp); + rtlpriv->dm.current_turbo_edca = false; + } + } + + rtlpriv->dm.is_any_nonbepkts = false; + last_txok_cnt = rtlpriv->stats.txbytesunicast; + last_rxok_cnt = rtlpriv->stats.rxbytesunicast; +} + +static void rtl92c_dm_txpower_tracking_callback_thermalmeter(struct ieee80211_hw + *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 thermalvalue, delta, delta_lck, delta_iqk; + long ele_a, ele_d, temp_cck, val_x, value32; + long val_y, ele_c = 0; + u8 ofdm_index[2], ofdm_index_old[2] = {0, 0}, cck_index_old = 0; + s8 cck_index = 0; + int i; + bool is2t = IS_92C_SERIAL(rtlhal->version); + s8 txpwr_level[2] = {0, 0}; + u8 ofdm_min_index = 6, rf; + + rtlpriv->dm.txpower_trackinginit = true; + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "rtl92c_dm_txpower_tracking_callback_thermalmeter\n"); + + thermalvalue = (u8) rtl_get_rfreg(hw, RF90_PATH_A, RF_T_METER, 0x1f); + + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Readback Thermal Meter = 0x%x pre thermal meter 0x%x eeprom_thermalmeter 0x%x\n", + thermalvalue, rtlpriv->dm.thermalvalue, + rtlefuse->eeprom_thermalmeter); + + rtl92c_phy_ap_calibrate(hw, (thermalvalue - + rtlefuse->eeprom_thermalmeter)); + if (is2t) + rf = 2; + else + rf = 1; + + if (thermalvalue) { + ele_d = rtl_get_bbreg(hw, ROFDM0_XATXIQIMBALANCE, + MASKDWORD) & MASKOFDM_D; + + for (i = 0; i < OFDM_TABLE_LENGTH; i++) { + if (ele_d == (ofdmswing_table[i] & MASKOFDM_D)) { + ofdm_index_old[0] = (u8) i; + + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Initial pathA ele_d reg0x%x = 0x%lx, ofdm_index=0x%x\n", + ROFDM0_XATXIQIMBALANCE, + ele_d, ofdm_index_old[0]); + break; + } + } + + if (is2t) { + ele_d = rtl_get_bbreg(hw, ROFDM0_XBTXIQIMBALANCE, + MASKDWORD) & MASKOFDM_D; + + for (i = 0; i < OFDM_TABLE_LENGTH; i++) { + if (ele_d == (ofdmswing_table[i] & + MASKOFDM_D)) { + ofdm_index_old[1] = (u8) i; + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, + DBG_LOUD, + "Initial pathB ele_d reg0x%x = 0x%lx, ofdm_index=0x%x\n", + ROFDM0_XBTXIQIMBALANCE, ele_d, + ofdm_index_old[1]); + break; + } + } + } + + temp_cck = + rtl_get_bbreg(hw, RCCK0_TXFILTER2, MASKDWORD) & MASKCCK; + + for (i = 0; i < CCK_TABLE_LENGTH; i++) { + if (rtlpriv->dm.cck_inch14) { + if (memcmp((void *)&temp_cck, + (void *)&cckswing_table_ch14[i][2], + 4) == 0) { + cck_index_old = (u8) i; + + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, + DBG_LOUD, + "Initial reg0x%x = 0x%lx, cck_index=0x%x, ch 14 %d\n", + RCCK0_TXFILTER2, temp_cck, + cck_index_old, + rtlpriv->dm.cck_inch14); + break; + } + } else { + if (memcmp((void *)&temp_cck, + (void *) + &cckswing_table_ch1ch13[i][2], + 4) == 0) { + cck_index_old = (u8) i; + + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, + DBG_LOUD, + "Initial reg0x%x = 0x%lx, cck_index=0x%x, ch14 %d\n", + RCCK0_TXFILTER2, temp_cck, + cck_index_old, + rtlpriv->dm.cck_inch14); + break; + } + } + } + + if (!rtlpriv->dm.thermalvalue) { + rtlpriv->dm.thermalvalue = + rtlefuse->eeprom_thermalmeter; + rtlpriv->dm.thermalvalue_lck = thermalvalue; + rtlpriv->dm.thermalvalue_iqk = thermalvalue; + for (i = 0; i < rf; i++) + rtlpriv->dm.ofdm_index[i] = ofdm_index_old[i]; + rtlpriv->dm.cck_index = cck_index_old; + } + + delta = (thermalvalue > rtlpriv->dm.thermalvalue) ? + (thermalvalue - rtlpriv->dm.thermalvalue) : + (rtlpriv->dm.thermalvalue - thermalvalue); + + delta_lck = (thermalvalue > rtlpriv->dm.thermalvalue_lck) ? + (thermalvalue - rtlpriv->dm.thermalvalue_lck) : + (rtlpriv->dm.thermalvalue_lck - thermalvalue); + + delta_iqk = (thermalvalue > rtlpriv->dm.thermalvalue_iqk) ? + (thermalvalue - rtlpriv->dm.thermalvalue_iqk) : + (rtlpriv->dm.thermalvalue_iqk - thermalvalue); + + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Readback Thermal Meter = 0x%x pre thermal meter 0x%x eeprom_thermalmeter 0x%x delta 0x%x delta_lck 0x%x delta_iqk 0x%x\n", + thermalvalue, rtlpriv->dm.thermalvalue, + rtlefuse->eeprom_thermalmeter, delta, delta_lck, + delta_iqk); + + if (delta_lck > 1) { + rtlpriv->dm.thermalvalue_lck = thermalvalue; + rtl92c_phy_lc_calibrate(hw); + } + + if (delta > 0 && rtlpriv->dm.txpower_track_control) { + if (thermalvalue > rtlpriv->dm.thermalvalue) { + for (i = 0; i < rf; i++) + rtlpriv->dm.ofdm_index[i] -= delta; + rtlpriv->dm.cck_index -= delta; + } else { + for (i = 0; i < rf; i++) + rtlpriv->dm.ofdm_index[i] += delta; + rtlpriv->dm.cck_index += delta; + } + + if (is2t) { + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "temp OFDM_A_index=0x%x, OFDM_B_index=0x%x, cck_index=0x%x\n", + rtlpriv->dm.ofdm_index[0], + rtlpriv->dm.ofdm_index[1], + rtlpriv->dm.cck_index); + } else { + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "temp OFDM_A_index=0x%x, cck_index=0x%x\n", + rtlpriv->dm.ofdm_index[0], + rtlpriv->dm.cck_index); + } + + if (thermalvalue > rtlefuse->eeprom_thermalmeter) { + for (i = 0; i < rf; i++) + ofdm_index[i] = + rtlpriv->dm.ofdm_index[i] + + 1; + cck_index = rtlpriv->dm.cck_index + 1; + } else { + for (i = 0; i < rf; i++) + ofdm_index[i] = + rtlpriv->dm.ofdm_index[i]; + cck_index = rtlpriv->dm.cck_index; + } + + for (i = 0; i < rf; i++) { + if (txpwr_level[i] >= 0 && + txpwr_level[i] <= 26) { + if (thermalvalue > + rtlefuse->eeprom_thermalmeter) { + if (delta < 5) + ofdm_index[i] -= 1; + + else + ofdm_index[i] -= 2; + } else if (delta > 5 && thermalvalue < + rtlefuse-> + eeprom_thermalmeter) { + ofdm_index[i] += 1; + } + } else if (txpwr_level[i] >= 27 && + txpwr_level[i] <= 32 + && thermalvalue > + rtlefuse->eeprom_thermalmeter) { + if (delta < 5) + ofdm_index[i] -= 1; + + else + ofdm_index[i] -= 2; + } else if (txpwr_level[i] >= 32 && + txpwr_level[i] <= 38 && + thermalvalue > + rtlefuse->eeprom_thermalmeter + && delta > 5) { + ofdm_index[i] -= 1; + } + } + + if (txpwr_level[i] >= 0 && txpwr_level[i] <= 26) { + if (thermalvalue > + rtlefuse->eeprom_thermalmeter) { + if (delta < 5) + cck_index -= 1; + + else + cck_index -= 2; + } else if (delta > 5 && thermalvalue < + rtlefuse->eeprom_thermalmeter) { + cck_index += 1; + } + } else if (txpwr_level[i] >= 27 && + txpwr_level[i] <= 32 && + thermalvalue > + rtlefuse->eeprom_thermalmeter) { + if (delta < 5) + cck_index -= 1; + + else + cck_index -= 2; + } else if (txpwr_level[i] >= 32 && + txpwr_level[i] <= 38 && + thermalvalue > rtlefuse->eeprom_thermalmeter + && delta > 5) { + cck_index -= 1; + } + + for (i = 0; i < rf; i++) { + if (ofdm_index[i] > OFDM_TABLE_SIZE - 1) + ofdm_index[i] = OFDM_TABLE_SIZE - 1; + + else if (ofdm_index[i] < ofdm_min_index) + ofdm_index[i] = ofdm_min_index; + } + + if (cck_index > CCK_TABLE_SIZE - 1) + cck_index = CCK_TABLE_SIZE - 1; + else if (cck_index < 0) + cck_index = 0; + + if (is2t) { + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "new OFDM_A_index=0x%x, OFDM_B_index=0x%x, cck_index=0x%x\n", + ofdm_index[0], ofdm_index[1], + cck_index); + } else { + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "new OFDM_A_index=0x%x, cck_index=0x%x\n", + ofdm_index[0], cck_index); + } + } + + if (rtlpriv->dm.txpower_track_control && delta != 0) { + ele_d = + (ofdmswing_table[ofdm_index[0]] & 0xFFC00000) >> 22; + val_x = rtlphy->reg_e94; + val_y = rtlphy->reg_e9c; + + if (val_x != 0) { + if ((val_x & 0x00000200) != 0) + val_x = val_x | 0xFFFFFC00; + ele_a = ((val_x * ele_d) >> 8) & 0x000003FF; + + if ((val_y & 0x00000200) != 0) + val_y = val_y | 0xFFFFFC00; + ele_c = ((val_y * ele_d) >> 8) & 0x000003FF; + + value32 = (ele_d << 22) | + ((ele_c & 0x3F) << 16) | ele_a; + + rtl_set_bbreg(hw, ROFDM0_XATXIQIMBALANCE, + MASKDWORD, value32); + + value32 = (ele_c & 0x000003C0) >> 6; + rtl_set_bbreg(hw, ROFDM0_XCTXAFE, MASKH4BITS, + value32); + + value32 = ((val_x * ele_d) >> 7) & 0x01; + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, + BIT(31), value32); + + value32 = ((val_y * ele_d) >> 7) & 0x01; + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, + BIT(29), value32); + } else { + rtl_set_bbreg(hw, ROFDM0_XATXIQIMBALANCE, + MASKDWORD, + ofdmswing_table[ofdm_index[0]]); + + rtl_set_bbreg(hw, ROFDM0_XCTXAFE, MASKH4BITS, + 0x00); + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, + BIT(31) | BIT(29), 0x00); + } + + if (!rtlpriv->dm.cck_inch14) { + rtl_write_byte(rtlpriv, 0xa22, + cckswing_table_ch1ch13[cck_index] + [0]); + rtl_write_byte(rtlpriv, 0xa23, + cckswing_table_ch1ch13[cck_index] + [1]); + rtl_write_byte(rtlpriv, 0xa24, + cckswing_table_ch1ch13[cck_index] + [2]); + rtl_write_byte(rtlpriv, 0xa25, + cckswing_table_ch1ch13[cck_index] + [3]); + rtl_write_byte(rtlpriv, 0xa26, + cckswing_table_ch1ch13[cck_index] + [4]); + rtl_write_byte(rtlpriv, 0xa27, + cckswing_table_ch1ch13[cck_index] + [5]); + rtl_write_byte(rtlpriv, 0xa28, + cckswing_table_ch1ch13[cck_index] + [6]); + rtl_write_byte(rtlpriv, 0xa29, + cckswing_table_ch1ch13[cck_index] + [7]); + } else { + rtl_write_byte(rtlpriv, 0xa22, + cckswing_table_ch14[cck_index] + [0]); + rtl_write_byte(rtlpriv, 0xa23, + cckswing_table_ch14[cck_index] + [1]); + rtl_write_byte(rtlpriv, 0xa24, + cckswing_table_ch14[cck_index] + [2]); + rtl_write_byte(rtlpriv, 0xa25, + cckswing_table_ch14[cck_index] + [3]); + rtl_write_byte(rtlpriv, 0xa26, + cckswing_table_ch14[cck_index] + [4]); + rtl_write_byte(rtlpriv, 0xa27, + cckswing_table_ch14[cck_index] + [5]); + rtl_write_byte(rtlpriv, 0xa28, + cckswing_table_ch14[cck_index] + [6]); + rtl_write_byte(rtlpriv, 0xa29, + cckswing_table_ch14[cck_index] + [7]); + } + + if (is2t) { + ele_d = (ofdmswing_table[ofdm_index[1]] & + 0xFFC00000) >> 22; + + val_x = rtlphy->reg_eb4; + val_y = rtlphy->reg_ebc; + + if (val_x != 0) { + if ((val_x & 0x00000200) != 0) + val_x = val_x | 0xFFFFFC00; + ele_a = ((val_x * ele_d) >> 8) & + 0x000003FF; + + if ((val_y & 0x00000200) != 0) + val_y = val_y | 0xFFFFFC00; + ele_c = ((val_y * ele_d) >> 8) & + 0x00003FF; + + value32 = (ele_d << 22) | + ((ele_c & 0x3F) << 16) | ele_a; + rtl_set_bbreg(hw, + ROFDM0_XBTXIQIMBALANCE, + MASKDWORD, value32); + + value32 = (ele_c & 0x000003C0) >> 6; + rtl_set_bbreg(hw, ROFDM0_XDTXAFE, + MASKH4BITS, value32); + + value32 = ((val_x * ele_d) >> 7) & 0x01; + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, + BIT(27), value32); + + value32 = ((val_y * ele_d) >> 7) & 0x01; + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, + BIT(25), value32); + } else { + rtl_set_bbreg(hw, + ROFDM0_XBTXIQIMBALANCE, + MASKDWORD, + ofdmswing_table[ofdm_index + [1]]); + rtl_set_bbreg(hw, ROFDM0_XDTXAFE, + MASKH4BITS, 0x00); + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, + BIT(27) | BIT(25), 0x00); + } + + } + } + + if (delta_iqk > 3) { + rtlpriv->dm.thermalvalue_iqk = thermalvalue; + rtl92c_phy_iq_calibrate(hw, false); + } + + if (rtlpriv->dm.txpower_track_control) + rtlpriv->dm.thermalvalue = thermalvalue; + } + + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, "<===\n"); + +} + +static void rtl92c_dm_initialize_txpower_tracking_thermalmeter( + struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.txpower_tracking = true; + rtlpriv->dm.txpower_trackinginit = false; + + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "pMgntInfo->txpower_tracking = %d\n", + rtlpriv->dm.txpower_tracking); +} + +static void rtl92c_dm_initialize_txpower_tracking(struct ieee80211_hw *hw) +{ + rtl92c_dm_initialize_txpower_tracking_thermalmeter(hw); +} + +static void rtl92c_dm_txpower_tracking_directcall(struct ieee80211_hw *hw) +{ + rtl92c_dm_txpower_tracking_callback_thermalmeter(hw); +} + +static void rtl92c_dm_check_txpower_tracking_thermal_meter( + struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + static u8 tm_trigger; + + if (!rtlpriv->dm.txpower_tracking) + return; + + if (!tm_trigger) { + rtl_set_rfreg(hw, RF90_PATH_A, RF_T_METER, RFREG_OFFSET_MASK, + 0x60); + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Trigger 92S Thermal Meter!!\n"); + tm_trigger = 1; + return; + } else { + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Schedule TxPowerTracking direct call!!\n"); + rtl92c_dm_txpower_tracking_directcall(hw); + tm_trigger = 0; + } +} + +void rtl92c_dm_check_txpower_tracking(struct ieee80211_hw *hw) +{ + rtl92c_dm_check_txpower_tracking_thermal_meter(hw); +} +EXPORT_SYMBOL(rtl92c_dm_check_txpower_tracking); + +void rtl92c_dm_init_rate_adaptive_mask(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rate_adaptive *p_ra = &(rtlpriv->ra); + + p_ra->ratr_state = DM_RATR_STA_INIT; + p_ra->pre_ratr_state = DM_RATR_STA_INIT; + + if (rtlpriv->dm.dm_type == DM_TYPE_BYDRIVER) + rtlpriv->dm.useramask = true; + else + rtlpriv->dm.useramask = false; + +} +EXPORT_SYMBOL(rtl92c_dm_init_rate_adaptive_mask); + +static void rtl92c_dm_init_dynamic_bb_powersaving(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct ps_t *dm_pstable = &rtlpriv->dm_pstable; + + dm_pstable->pre_ccastate = CCA_MAX; + dm_pstable->cur_ccasate = CCA_MAX; + dm_pstable->pre_rfstate = RF_MAX; + dm_pstable->cur_rfstate = RF_MAX; + dm_pstable->rssi_val_min = 0; +} + +void rtl92c_dm_rf_saving(struct ieee80211_hw *hw, u8 bforce_in_normal) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct ps_t *dm_pstable = &rtlpriv->dm_pstable; + static u8 initialize; + static u32 reg_874, reg_c70, reg_85c, reg_a74; + + if (initialize == 0) { + reg_874 = (rtl_get_bbreg(hw, RFPGA0_XCD_RFINTERFACESW, + MASKDWORD) & 0x1CC000) >> 14; + + reg_c70 = (rtl_get_bbreg(hw, ROFDM0_AGCPARAMETER1, + MASKDWORD) & BIT(3)) >> 3; + + reg_85c = (rtl_get_bbreg(hw, RFPGA0_XCD_SWITCHCONTROL, + MASKDWORD) & 0xFF000000) >> 24; + + reg_a74 = (rtl_get_bbreg(hw, 0xa74, MASKDWORD) & 0xF000) >> 12; + + initialize = 1; + } + + if (!bforce_in_normal) { + if (dm_pstable->rssi_val_min != 0) { + if (dm_pstable->pre_rfstate == RF_NORMAL) { + if (dm_pstable->rssi_val_min >= 30) + dm_pstable->cur_rfstate = RF_SAVE; + else + dm_pstable->cur_rfstate = RF_NORMAL; + } else { + if (dm_pstable->rssi_val_min <= 25) + dm_pstable->cur_rfstate = RF_NORMAL; + else + dm_pstable->cur_rfstate = RF_SAVE; + } + } else { + dm_pstable->cur_rfstate = RF_MAX; + } + } else { + dm_pstable->cur_rfstate = RF_NORMAL; + } + + if (dm_pstable->pre_rfstate != dm_pstable->cur_rfstate) { + if (dm_pstable->cur_rfstate == RF_SAVE) { + rtl_set_bbreg(hw, RFPGA0_XCD_RFINTERFACESW, + 0x1C0000, 0x2); + rtl_set_bbreg(hw, ROFDM0_AGCPARAMETER1, BIT(3), 0); + rtl_set_bbreg(hw, RFPGA0_XCD_SWITCHCONTROL, + 0xFF000000, 0x63); + rtl_set_bbreg(hw, RFPGA0_XCD_RFINTERFACESW, + 0xC000, 0x2); + rtl_set_bbreg(hw, 0xa74, 0xF000, 0x3); + rtl_set_bbreg(hw, 0x818, BIT(28), 0x0); + rtl_set_bbreg(hw, 0x818, BIT(28), 0x1); + } else { + rtl_set_bbreg(hw, RFPGA0_XCD_RFINTERFACESW, + 0x1CC000, reg_874); + rtl_set_bbreg(hw, ROFDM0_AGCPARAMETER1, BIT(3), + reg_c70); + rtl_set_bbreg(hw, RFPGA0_XCD_SWITCHCONTROL, 0xFF000000, + reg_85c); + rtl_set_bbreg(hw, 0xa74, 0xF000, reg_a74); + rtl_set_bbreg(hw, 0x818, BIT(28), 0x0); + } + + dm_pstable->pre_rfstate = dm_pstable->cur_rfstate; + } +} +EXPORT_SYMBOL(rtl92c_dm_rf_saving); + +static void rtl92c_dm_dynamic_bb_powersaving(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct ps_t *dm_pstable = &rtlpriv->dm_pstable; + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (((mac->link_state == MAC80211_NOLINK)) && + (rtlpriv->dm.entry_min_undec_sm_pwdb == 0)) { + dm_pstable->rssi_val_min = 0; + RT_TRACE(rtlpriv, DBG_LOUD, DBG_LOUD, "Not connected to any\n"); + } + + if (mac->link_state == MAC80211_LINKED) { + if (mac->opmode == NL80211_IFTYPE_ADHOC) { + dm_pstable->rssi_val_min = + rtlpriv->dm.entry_min_undec_sm_pwdb; + RT_TRACE(rtlpriv, DBG_LOUD, DBG_LOUD, + "AP Client PWDB = 0x%lx\n", + dm_pstable->rssi_val_min); + } else { + dm_pstable->rssi_val_min = rtlpriv->dm.undec_sm_pwdb; + RT_TRACE(rtlpriv, DBG_LOUD, DBG_LOUD, + "STA Default Port PWDB = 0x%lx\n", + dm_pstable->rssi_val_min); + } + } else { + dm_pstable->rssi_val_min = + rtlpriv->dm.entry_min_undec_sm_pwdb; + + RT_TRACE(rtlpriv, DBG_LOUD, DBG_LOUD, + "AP Ext Port PWDB = 0x%lx\n", + dm_pstable->rssi_val_min); + } + + if (IS_92C_SERIAL(rtlhal->version)) + ;/* rtl92c_dm_1r_cca(hw); */ + else + rtl92c_dm_rf_saving(hw, false); +} + +void rtl92c_dm_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.dm_type = DM_TYPE_BYDRIVER; + rtl92c_dm_diginit(hw); + rtl92c_dm_init_dynamic_txpower(hw); + rtl92c_dm_init_edca_turbo(hw); + rtl92c_dm_init_rate_adaptive_mask(hw); + rtl92c_dm_initialize_txpower_tracking(hw); + rtl92c_dm_init_dynamic_bb_powersaving(hw); +} +EXPORT_SYMBOL(rtl92c_dm_init); + +void rtl92c_dm_dynamic_txpower(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + long undec_sm_pwdb; + + if (!rtlpriv->dm.dynamic_txpower_enable) + return; + + if (rtlpriv->dm.dm_flag & HAL_DM_HIPWR_DISABLE) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + return; + } + + if ((mac->link_state < MAC80211_LINKED) && + (rtlpriv->dm.entry_min_undec_sm_pwdb == 0)) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_TRACE, + "Not connected to any\n"); + + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + + rtlpriv->dm.last_dtp_lvl = TXHIGHPWRLEVEL_NORMAL; + return; + } + + if (mac->link_state >= MAC80211_LINKED) { + if (mac->opmode == NL80211_IFTYPE_ADHOC) { + undec_sm_pwdb = rtlpriv->dm.entry_min_undec_sm_pwdb; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "AP Client PWDB = 0x%lx\n", + undec_sm_pwdb); + } else { + undec_sm_pwdb = rtlpriv->dm.undec_sm_pwdb; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "STA Default Port PWDB = 0x%lx\n", + undec_sm_pwdb); + } + } else { + undec_sm_pwdb = rtlpriv->dm.entry_min_undec_sm_pwdb; + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "AP Ext Port PWDB = 0x%lx\n", + undec_sm_pwdb); + } + + if (undec_sm_pwdb >= TX_POWER_NEAR_FIELD_THRESH_LVL2) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_LEVEL1; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_LEVEL1 (TxPwr=0x0)\n"); + } else if ((undec_sm_pwdb < (TX_POWER_NEAR_FIELD_THRESH_LVL2 - 3)) && + (undec_sm_pwdb >= TX_POWER_NEAR_FIELD_THRESH_LVL1)) { + + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_LEVEL1; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_LEVEL1 (TxPwr=0x10)\n"); + } else if (undec_sm_pwdb < (TX_POWER_NEAR_FIELD_THRESH_LVL1 - 5)) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_NORMAL\n"); + } + + if ((rtlpriv->dm.dynamic_txhighpower_lvl != rtlpriv->dm.last_dtp_lvl)) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "PHY_SetTxPowerLevel8192S() Channel = %d\n", + rtlphy->current_channel); + rtl92c_phy_set_txpower_level(hw, rtlphy->current_channel); + } + + rtlpriv->dm.last_dtp_lvl = rtlpriv->dm.dynamic_txhighpower_lvl; +} + +void rtl92c_dm_watchdog(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + bool fw_current_inpsmode = false; + bool fw_ps_awake = true; + + rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_FW_PSMODE_STATUS, + (u8 *) (&fw_current_inpsmode)); + rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_FWLPS_RF_ON, + (u8 *) (&fw_ps_awake)); + + if (ppsc->p2p_ps_info.p2p_ps_mode) + fw_ps_awake = false; + + if ((ppsc->rfpwr_state == ERFON) && ((!fw_current_inpsmode) && + fw_ps_awake) + && (!ppsc->rfchange_inprogress)) { + rtl92c_dm_pwdb_monitor(hw); + rtl92c_dm_dig(hw); + rtl92c_dm_false_alarm_counter_statistics(hw); + rtl92c_dm_dynamic_bb_powersaving(hw); + rtl92c_dm_dynamic_txpower(hw); + rtl92c_dm_check_txpower_tracking(hw); + /* rtl92c_dm_refresh_rate_adaptive_mask(hw); */ + rtl92c_dm_bt_coexist(hw); + rtl92c_dm_check_edca_turbo(hw); + } +} +EXPORT_SYMBOL(rtl92c_dm_watchdog); + +u8 rtl92c_bt_rssi_state_change(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + long undec_sm_pwdb; + u8 curr_bt_rssi_state = 0x00; + + if (rtlpriv->mac80211.link_state == MAC80211_LINKED) { + undec_sm_pwdb = GET_UNDECORATED_AVERAGE_RSSI(rtlpriv); + } else { + if (rtlpriv->dm.entry_min_undec_sm_pwdb == 0) + undec_sm_pwdb = 100; + else + undec_sm_pwdb = rtlpriv->dm.entry_min_undec_sm_pwdb; + } + + /* Check RSSI to determine HighPower/NormalPower state for + * BT coexistence. */ + if (undec_sm_pwdb >= 67) + curr_bt_rssi_state &= (~BT_RSSI_STATE_NORMAL_POWER); + else if (undec_sm_pwdb < 62) + curr_bt_rssi_state |= BT_RSSI_STATE_NORMAL_POWER; + + /* Check RSSI to determine AMPDU setting for BT coexistence. */ + if (undec_sm_pwdb >= 40) + curr_bt_rssi_state &= (~BT_RSSI_STATE_AMDPU_OFF); + else if (undec_sm_pwdb <= 32) + curr_bt_rssi_state |= BT_RSSI_STATE_AMDPU_OFF; + + /* Marked RSSI state. It will be used to determine BT coexistence + * setting later. */ + if (undec_sm_pwdb < 35) + curr_bt_rssi_state |= BT_RSSI_STATE_SPECIAL_LOW; + else + curr_bt_rssi_state &= (~BT_RSSI_STATE_SPECIAL_LOW); + + /* Set Tx Power according to BT status. */ + if (undec_sm_pwdb >= 30) + curr_bt_rssi_state |= BT_RSSI_STATE_TXPOWER_LOW; + else if (undec_sm_pwdb < 25) + curr_bt_rssi_state &= (~BT_RSSI_STATE_TXPOWER_LOW); + + /* Check BT state related to BT_Idle in B/G mode. */ + if (undec_sm_pwdb < 15) + curr_bt_rssi_state |= BT_RSSI_STATE_BG_EDCA_LOW; + else + curr_bt_rssi_state &= (~BT_RSSI_STATE_BG_EDCA_LOW); + + if (curr_bt_rssi_state != rtlpcipriv->bt_coexist.bt_rssi_state) { + rtlpcipriv->bt_coexist.bt_rssi_state = curr_bt_rssi_state; + return true; + } else { + return false; + } +} +EXPORT_SYMBOL(rtl92c_bt_rssi_state_change); + +static bool rtl92c_bt_state_change(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + + u32 polling, ratio_tx, ratio_pri; + u32 bt_tx, bt_pri; + u8 bt_state; + u8 cur_service_type; + + if (rtlpriv->mac80211.link_state < MAC80211_LINKED) + return false; + + bt_state = rtl_read_byte(rtlpriv, 0x4fd); + bt_tx = rtl_read_dword(rtlpriv, 0x488); + bt_tx = bt_tx & 0x00ffffff; + bt_pri = rtl_read_dword(rtlpriv, 0x48c); + bt_pri = bt_pri & 0x00ffffff; + polling = rtl_read_dword(rtlpriv, 0x490); + + if (bt_tx == 0xffffffff && bt_pri == 0xffffffff && + polling == 0xffffffff && bt_state == 0xff) + return false; + + bt_state &= BIT_OFFSET_LEN_MASK_32(0, 1); + if (bt_state != rtlpcipriv->bt_coexist.bt_cur_state) { + rtlpcipriv->bt_coexist.bt_cur_state = bt_state; + + if (rtlpcipriv->bt_coexist.reg_bt_sco == 3) { + rtlpcipriv->bt_coexist.bt_service = BT_IDLE; + + bt_state = bt_state | + ((rtlpcipriv->bt_coexist.bt_ant_isolation == 1) ? + 0 : BIT_OFFSET_LEN_MASK_32(1, 1)) | + BIT_OFFSET_LEN_MASK_32(2, 1); + rtl_write_byte(rtlpriv, 0x4fd, bt_state); + } + return true; + } + + ratio_tx = bt_tx * 1000 / polling; + ratio_pri = bt_pri * 1000 / polling; + rtlpcipriv->bt_coexist.ratio_tx = ratio_tx; + rtlpcipriv->bt_coexist.ratio_pri = ratio_pri; + + if (bt_state && rtlpcipriv->bt_coexist.reg_bt_sco == 3) { + + if ((ratio_tx < 30) && (ratio_pri < 30)) + cur_service_type = BT_IDLE; + else if ((ratio_pri > 110) && (ratio_pri < 250)) + cur_service_type = BT_SCO; + else if ((ratio_tx >= 200) && (ratio_pri >= 200)) + cur_service_type = BT_BUSY; + else if ((ratio_tx >= 350) && (ratio_tx < 500)) + cur_service_type = BT_OTHERBUSY; + else if (ratio_tx >= 500) + cur_service_type = BT_PAN; + else + cur_service_type = BT_OTHER_ACTION; + + if (cur_service_type != rtlpcipriv->bt_coexist.bt_service) { + rtlpcipriv->bt_coexist.bt_service = cur_service_type; + bt_state = bt_state | + ((rtlpcipriv->bt_coexist.bt_ant_isolation == 1) ? + 0 : BIT_OFFSET_LEN_MASK_32(1, 1)) | + ((rtlpcipriv->bt_coexist.bt_service != BT_IDLE) ? + 0 : BIT_OFFSET_LEN_MASK_32(2, 1)); + + /* Add interrupt migration when bt is not ini + * idle state (no traffic). */ + if (rtlpcipriv->bt_coexist.bt_service != BT_IDLE) { + rtl_write_word(rtlpriv, 0x504, 0x0ccc); + rtl_write_byte(rtlpriv, 0x506, 0x54); + rtl_write_byte(rtlpriv, 0x507, 0x54); + } else { + rtl_write_byte(rtlpriv, 0x506, 0x00); + rtl_write_byte(rtlpriv, 0x507, 0x00); + } + + rtl_write_byte(rtlpriv, 0x4fd, bt_state); + return true; + } + } + + return false; + +} + +static bool rtl92c_bt_wifi_connect_change(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + static bool media_connect; + + if (rtlpriv->mac80211.link_state < MAC80211_LINKED) { + media_connect = false; + } else { + if (!media_connect) { + media_connect = true; + return true; + } + media_connect = true; + } + + return false; +} + +static void rtl92c_bt_set_normal(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + + + if (rtlpcipriv->bt_coexist.bt_service == BT_OTHERBUSY) { + rtlpcipriv->bt_coexist.bt_edca_ul = 0x5ea72b; + rtlpcipriv->bt_coexist.bt_edca_dl = 0x5ea72b; + } else if (rtlpcipriv->bt_coexist.bt_service == BT_BUSY) { + rtlpcipriv->bt_coexist.bt_edca_ul = 0x5eb82f; + rtlpcipriv->bt_coexist.bt_edca_dl = 0x5eb82f; + } else if (rtlpcipriv->bt_coexist.bt_service == BT_SCO) { + if (rtlpcipriv->bt_coexist.ratio_tx > 160) { + rtlpcipriv->bt_coexist.bt_edca_ul = 0x5ea72f; + rtlpcipriv->bt_coexist.bt_edca_dl = 0x5ea72f; + } else { + rtlpcipriv->bt_coexist.bt_edca_ul = 0x5ea32b; + rtlpcipriv->bt_coexist.bt_edca_dl = 0x5ea42b; + } + } else { + rtlpcipriv->bt_coexist.bt_edca_ul = 0; + rtlpcipriv->bt_coexist.bt_edca_dl = 0; + } + + if ((rtlpcipriv->bt_coexist.bt_service != BT_IDLE) && + (rtlpriv->mac80211.mode == WIRELESS_MODE_G || + (rtlpriv->mac80211.mode == (WIRELESS_MODE_G | WIRELESS_MODE_B))) && + (rtlpcipriv->bt_coexist.bt_rssi_state & + BT_RSSI_STATE_BG_EDCA_LOW)) { + rtlpcipriv->bt_coexist.bt_edca_ul = 0x5eb82b; + rtlpcipriv->bt_coexist.bt_edca_dl = 0x5eb82b; + } +} + +static void rtl92c_bt_ant_isolation(struct ieee80211_hw *hw, u8 tmp1byte) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + + + /* Only enable HW BT coexist when BT in "Busy" state. */ + if (rtlpriv->mac80211.vendor == PEER_CISCO && + rtlpcipriv->bt_coexist.bt_service == BT_OTHER_ACTION) { + rtl_write_byte(rtlpriv, REG_GPIO_MUXCFG, 0xa0); + } else { + if ((rtlpcipriv->bt_coexist.bt_service == BT_BUSY) && + (rtlpcipriv->bt_coexist.bt_rssi_state & + BT_RSSI_STATE_NORMAL_POWER)) { + rtl_write_byte(rtlpriv, REG_GPIO_MUXCFG, 0xa0); + } else if ((rtlpcipriv->bt_coexist.bt_service == + BT_OTHER_ACTION) && (rtlpriv->mac80211.mode < + WIRELESS_MODE_N_24G) && + (rtlpcipriv->bt_coexist.bt_rssi_state & + BT_RSSI_STATE_SPECIAL_LOW)) { + rtl_write_byte(rtlpriv, REG_GPIO_MUXCFG, 0xa0); + } else if (rtlpcipriv->bt_coexist.bt_service == BT_PAN) { + rtl_write_byte(rtlpriv, REG_GPIO_MUXCFG, tmp1byte); + } else { + rtl_write_byte(rtlpriv, REG_GPIO_MUXCFG, tmp1byte); + } + } + + if (rtlpcipriv->bt_coexist.bt_service == BT_PAN) + rtl_write_dword(rtlpriv, REG_GPIO_PIN_CTRL, 0x10100); + else + rtl_write_dword(rtlpriv, REG_GPIO_PIN_CTRL, 0x0); + + if (rtlpcipriv->bt_coexist.bt_rssi_state & + BT_RSSI_STATE_NORMAL_POWER) { + rtl92c_bt_set_normal(hw); + } else { + rtlpcipriv->bt_coexist.bt_edca_ul = 0; + rtlpcipriv->bt_coexist.bt_edca_dl = 0; + } + + if (rtlpcipriv->bt_coexist.bt_service != BT_IDLE) { + rtlpriv->cfg->ops->set_rfreg(hw, + RF90_PATH_A, + 0x1e, + 0xf0, 0xf); + } else { + rtlpriv->cfg->ops->set_rfreg(hw, + RF90_PATH_A, 0x1e, 0xf0, + rtlpcipriv->bt_coexist.bt_rfreg_origin_1e); + } + + if (!rtlpriv->dm.dynamic_txpower_enable) { + if (rtlpcipriv->bt_coexist.bt_service != BT_IDLE) { + if (rtlpcipriv->bt_coexist.bt_rssi_state & + BT_RSSI_STATE_TXPOWER_LOW) { + rtlpriv->dm.dynamic_txhighpower_lvl = + TXHIGHPWRLEVEL_BT2; + } else { + rtlpriv->dm.dynamic_txhighpower_lvl = + TXHIGHPWRLEVEL_BT1; + } + } else { + rtlpriv->dm.dynamic_txhighpower_lvl = + TXHIGHPWRLEVEL_NORMAL; + } + rtl92c_phy_set_txpower_level(hw, + rtlpriv->phy.current_channel); + } +} + +static void rtl92c_check_bt_change(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 tmp1byte = 0; + + if (IS_81xxC_VENDOR_UMC_B_CUT(rtlhal->version) && + rtlpcipriv->bt_coexist.bt_coexistence) + tmp1byte |= BIT(5); + if (rtlpcipriv->bt_coexist.bt_cur_state) { + if (rtlpcipriv->bt_coexist.bt_ant_isolation) + rtl92c_bt_ant_isolation(hw, tmp1byte); + } else { + rtl_write_byte(rtlpriv, REG_GPIO_MUXCFG, tmp1byte); + rtlpriv->cfg->ops->set_rfreg(hw, RF90_PATH_A, 0x1e, 0xf0, + rtlpcipriv->bt_coexist.bt_rfreg_origin_1e); + + rtlpcipriv->bt_coexist.bt_edca_ul = 0; + rtlpcipriv->bt_coexist.bt_edca_dl = 0; + } +} + +void rtl92c_dm_bt_coexist(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + + bool wifi_connect_change; + bool bt_state_change; + bool rssi_state_change; + + if ((rtlpcipriv->bt_coexist.bt_coexistence) && + (rtlpcipriv->bt_coexist.bt_coexist_type == BT_CSR_BC4)) { + + wifi_connect_change = rtl92c_bt_wifi_connect_change(hw); + bt_state_change = rtl92c_bt_state_change(hw); + rssi_state_change = rtl92c_bt_rssi_state_change(hw); + + if (wifi_connect_change || bt_state_change || rssi_state_change) + rtl92c_check_bt_change(hw); + } +} +EXPORT_SYMBOL(rtl92c_dm_bt_coexist); diff --git a/drivers/net/wireless/rtlwifi/rtl8192c/dm_common.h b/drivers/net/wireless/rtlwifi/rtl8192c/dm_common.h new file mode 100644 index 0000000..518e208 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192c/dm_common.h @@ -0,0 +1,171 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92COMMON_DM_H__ +#define __RTL92COMMON_DM_H__ + +#include "../wifi.h" +#include "../rtl8192ce/def.h" +#include "../rtl8192ce/reg.h" +#include "fw_common.h" + +#define HAL_DM_DIG_DISABLE BIT(0) +#define HAL_DM_HIPWR_DISABLE BIT(1) + +#define OFDM_TABLE_LENGTH 37 +#define CCK_TABLE_LENGTH 33 + +#define OFDM_TABLE_SIZE 37 +#define CCK_TABLE_SIZE 33 + +#define BW_AUTO_SWITCH_HIGH_LOW 25 +#define BW_AUTO_SWITCH_LOW_HIGH 30 + +#define DM_DIG_THRESH_HIGH 40 +#define DM_DIG_THRESH_LOW 35 + +#define DM_FALSEALARM_THRESH_LOW 400 +#define DM_FALSEALARM_THRESH_HIGH 1000 + +#define DM_DIG_MAX 0x3e +#define DM_DIG_MIN 0x1e + +#define DM_DIG_FA_UPPER 0x32 +#define DM_DIG_FA_LOWER 0x20 +#define DM_DIG_FA_TH0 0x20 +#define DM_DIG_FA_TH1 0x100 +#define DM_DIG_FA_TH2 0x200 + +#define DM_DIG_BACKOFF_MAX 12 +#define DM_DIG_BACKOFF_MIN -4 +#define DM_DIG_BACKOFF_DEFAULT 10 + +#define RXPATHSELECTION_SS_TH_lOW 30 +#define RXPATHSELECTION_DIFF_TH 18 + +#define DM_RATR_STA_INIT 0 +#define DM_RATR_STA_HIGH 1 +#define DM_RATR_STA_MIDDLE 2 +#define DM_RATR_STA_LOW 3 + +#define CTS2SELF_THVAL 30 +#define REGC38_TH 20 + +#define WAIOTTHVal 25 + +#define TXHIGHPWRLEVEL_NORMAL 0 +#define TXHIGHPWRLEVEL_LEVEL1 1 +#define TXHIGHPWRLEVEL_LEVEL2 2 +#define TXHIGHPWRLEVEL_BT1 3 +#define TXHIGHPWRLEVEL_BT2 4 + +#define DM_TYPE_BYFW 0 +#define DM_TYPE_BYDRIVER 1 + +#define TX_POWER_NEAR_FIELD_THRESH_LVL2 74 +#define TX_POWER_NEAR_FIELD_THRESH_LVL1 67 + +struct swat_t { + u8 failure_cnt; + u8 try_flag; + u8 stop_trying; + long pre_rssi; + long trying_threshold; + u8 cur_antenna; + u8 pre_antenna; +}; + +enum tag_dynamic_init_gain_operation_type_definition { + DIG_TYPE_THRESH_HIGH = 0, + DIG_TYPE_THRESH_LOW = 1, + DIG_TYPE_BACKOFF = 2, + DIG_TYPE_RX_GAIN_MIN = 3, + DIG_TYPE_RX_GAIN_MAX = 4, + DIG_TYPE_ENABLE = 5, + DIG_TYPE_DISABLE = 6, + DIG_OP_TYPE_MAX +}; + +enum tag_cck_packet_detection_threshold_type_definition { + CCK_PD_STAGE_LowRssi = 0, + CCK_PD_STAGE_HighRssi = 1, + CCK_FA_STAGE_Low = 2, + CCK_FA_STAGE_High = 3, + CCK_PD_STAGE_MAX = 4, +}; + +enum dm_1r_cca_e { + CCA_1R = 0, + CCA_2R = 1, + CCA_MAX = 2, +}; + +enum dm_rf_e { + RF_SAVE = 0, + RF_NORMAL = 1, + RF_MAX = 2, +}; + +enum dm_sw_ant_switch_e { + ANS_ANTENNA_B = 1, + ANS_ANTENNA_A = 2, + ANS_ANTENNA_MAX = 3, +}; + +enum dm_dig_ext_port_alg_e { + DIG_EXT_PORT_STAGE_0 = 0, + DIG_EXT_PORT_STAGE_1 = 1, + DIG_EXT_PORT_STAGE_2 = 2, + DIG_EXT_PORT_STAGE_3 = 3, + DIG_EXT_PORT_STAGE_MAX = 4, +}; + +enum dm_dig_connect_e { + DIG_STA_DISCONNECT = 0, + DIG_STA_CONNECT = 1, + DIG_STA_BEFORE_CONNECT = 2, + DIG_MULTISTA_DISCONNECT = 3, + DIG_MULTISTA_CONNECT = 4, + DIG_CONNECT_MAX +}; + +void rtl92c_dm_init(struct ieee80211_hw *hw); +void rtl92c_dm_watchdog(struct ieee80211_hw *hw); +void rtl92c_dm_write_dig(struct ieee80211_hw *hw); +void rtl92c_dm_init_edca_turbo(struct ieee80211_hw *hw); +void rtl92c_dm_check_txpower_tracking(struct ieee80211_hw *hw); +void rtl92c_dm_init_rate_adaptive_mask(struct ieee80211_hw *hw); +void rtl92c_dm_rf_saving(struct ieee80211_hw *hw, u8 bforce_in_normal); +void rtl92c_phy_ap_calibrate(struct ieee80211_hw *hw, char delta); +void rtl92c_phy_lc_calibrate(struct ieee80211_hw *hw); +void rtl92c_phy_iq_calibrate(struct ieee80211_hw *hw, bool recovery); +void rtl92c_dm_dynamic_txpower(struct ieee80211_hw *hw); +void rtl92c_dm_bt_coexist(struct ieee80211_hw *hw); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192c/fw_common.c b/drivers/net/wireless/rtlwifi/rtl8192c/fw_common.c new file mode 100644 index 0000000..04a4162 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192c/fw_common.c @@ -0,0 +1,907 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "../base.h" +#include "../rtl8192ce/reg.h" +#include "../rtl8192ce/def.h" +#include "fw_common.h" +#include +#include + +static void _rtl92c_enable_fw_download(struct ieee80211_hw *hw, bool enable) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (rtlhal->hw_type == HARDWARE_TYPE_RTL8192CU) { + u32 value32 = rtl_read_dword(rtlpriv, REG_MCUFWDL); + if (enable) + value32 |= MCUFWDL_EN; + else + value32 &= ~MCUFWDL_EN; + rtl_write_dword(rtlpriv, REG_MCUFWDL, value32); + } else if (rtlhal->hw_type == HARDWARE_TYPE_RTL8192CE) { + u8 tmp; + if (enable) { + + tmp = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN + 1); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN + 1, + tmp | 0x04); + + tmp = rtl_read_byte(rtlpriv, REG_MCUFWDL); + rtl_write_byte(rtlpriv, REG_MCUFWDL, tmp | 0x01); + + tmp = rtl_read_byte(rtlpriv, REG_MCUFWDL + 2); + rtl_write_byte(rtlpriv, REG_MCUFWDL + 2, tmp & 0xf7); + } else { + + tmp = rtl_read_byte(rtlpriv, REG_MCUFWDL); + rtl_write_byte(rtlpriv, REG_MCUFWDL, tmp & 0xfe); + + rtl_write_byte(rtlpriv, REG_MCUFWDL + 1, 0x00); + } + } +} + +static void rtl_block_fw_writeN(struct ieee80211_hw *hw, const u8 *buffer, + u32 size) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 blockSize = REALTEK_USB_VENQT_MAX_BUF_SIZE - 20; + u8 *bufferPtr = (u8 *) buffer; + u32 i, offset, blockCount, remainSize; + + blockCount = size / blockSize; + remainSize = size % blockSize; + + for (i = 0; i < blockCount; i++) { + offset = i * blockSize; + rtlpriv->io.writeN_sync(rtlpriv, + (FW_8192C_START_ADDRESS + offset), + (void *)(bufferPtr + offset), + blockSize); + } + + if (remainSize) { + offset = blockCount * blockSize; + rtlpriv->io.writeN_sync(rtlpriv, + (FW_8192C_START_ADDRESS + offset), + (void *)(bufferPtr + offset), + remainSize); + } +} + +static void _rtl92c_fw_block_write(struct ieee80211_hw *hw, + const u8 *buffer, u32 size) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 blockSize = sizeof(u32); + u8 *bufferPtr = (u8 *) buffer; + u32 *pu4BytePtr = (u32 *) buffer; + u32 i, offset, blockCount, remainSize; + u32 data; + + if (rtlpriv->io.writeN_sync) { + rtl_block_fw_writeN(hw, buffer, size); + return; + } + blockCount = size / blockSize; + remainSize = size % blockSize; + if (remainSize) { + /* the last word is < 4 bytes - pad it with zeros */ + for (i = 0; i < 4 - remainSize; i++) + *(bufferPtr + size + i) = 0; + blockCount++; + } + + for (i = 0; i < blockCount; i++) { + offset = i * blockSize; + /* for big-endian platforms, the firmware data need to be byte + * swapped as it was read as a byte string and will be written + * as 32-bit dwords and byte swapped when written + */ + data = le32_to_cpu(*(__le32 *)(pu4BytePtr + i)); + rtl_write_dword(rtlpriv, (FW_8192C_START_ADDRESS + offset), + data); + } +} + +static void _rtl92c_fw_page_write(struct ieee80211_hw *hw, + u32 page, const u8 *buffer, u32 size) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 value8; + u8 u8page = (u8) (page & 0x07); + + value8 = (rtl_read_byte(rtlpriv, REG_MCUFWDL + 2) & 0xF8) | u8page; + + rtl_write_byte(rtlpriv, (REG_MCUFWDL + 2), value8); + _rtl92c_fw_block_write(hw, buffer, size); +} + +static void _rtl92c_fill_dummy(u8 *pfwbuf, u32 *pfwlen) +{ + u32 fwlen = *pfwlen; + u8 remain = (u8) (fwlen % 4); + + remain = (remain == 0) ? 0 : (4 - remain); + + while (remain > 0) { + pfwbuf[fwlen] = 0; + fwlen++; + remain--; + } + + *pfwlen = fwlen; +} + +static void _rtl92c_write_fw(struct ieee80211_hw *hw, + enum version_8192c version, u8 *buffer, u32 size) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 *bufferPtr = buffer; + + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, "FW size is %d bytes\n", size); + + if (IS_CHIP_VER_B(version)) { + u32 pageNums, remainSize; + u32 page, offset; + + if (IS_HARDWARE_TYPE_8192CE(rtlhal)) + _rtl92c_fill_dummy(bufferPtr, &size); + + pageNums = size / FW_8192C_PAGE_SIZE; + remainSize = size % FW_8192C_PAGE_SIZE; + + if (pageNums > 4) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Page numbers should not greater then 4\n"); + } + + for (page = 0; page < pageNums; page++) { + offset = page * FW_8192C_PAGE_SIZE; + _rtl92c_fw_page_write(hw, page, (bufferPtr + offset), + FW_8192C_PAGE_SIZE); + } + + if (remainSize) { + offset = pageNums * FW_8192C_PAGE_SIZE; + page = pageNums; + _rtl92c_fw_page_write(hw, page, (bufferPtr + offset), + remainSize); + } + } else { + _rtl92c_fw_block_write(hw, buffer, size); + } +} + +static int _rtl92c_fw_free_to_go(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 counter = 0; + u32 value32; + + do { + value32 = rtl_read_dword(rtlpriv, REG_MCUFWDL); + } while ((counter++ < FW_8192C_POLLING_TIMEOUT_COUNT) && + (!(value32 & FWDL_ChkSum_rpt))); + + if (counter >= FW_8192C_POLLING_TIMEOUT_COUNT) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "chksum report faill ! REG_MCUFWDL:0x%08x\n", value32); + return -EIO; + } + + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, + "Checksum report OK ! REG_MCUFWDL:0x%08x\n", value32); + + value32 = rtl_read_dword(rtlpriv, REG_MCUFWDL); + value32 |= MCUFWDL_RDY; + value32 &= ~WINTINI_RDY; + rtl_write_dword(rtlpriv, REG_MCUFWDL, value32); + + counter = 0; + + do { + value32 = rtl_read_dword(rtlpriv, REG_MCUFWDL); + if (value32 & WINTINI_RDY) { + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, + "Polling FW ready success!! REG_MCUFWDL:0x%08x\n", + value32); + return 0; + } + + mdelay(FW_8192C_POLLING_DELAY); + + } while (counter++ < FW_8192C_POLLING_TIMEOUT_COUNT); + + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Polling FW ready fail!! REG_MCUFWDL:0x%08x\n", value32); + return -EIO; +} + +int rtl92c_download_fw(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl92c_firmware_header *pfwheader; + u8 *pfwdata; + u32 fwsize; + enum version_8192c version = rtlhal->version; + + if (rtlpriv->max_fw_size == 0 || !rtlhal->pfirmware) + return 1; + + pfwheader = (struct rtl92c_firmware_header *)rtlhal->pfirmware; + pfwdata = rtlhal->pfirmware; + fwsize = rtlhal->fwsize; + + if (IS_FW_HEADER_EXIST(pfwheader)) { + RT_TRACE(rtlpriv, COMP_FW, DBG_DMESG, + "Firmware Version(%d), Signature(%#x),Size(%d)\n", + le16_to_cpu(pfwheader->version), + le16_to_cpu(pfwheader->signature), + (uint)sizeof(struct rtl92c_firmware_header)); + + pfwdata = pfwdata + sizeof(struct rtl92c_firmware_header); + fwsize = fwsize - sizeof(struct rtl92c_firmware_header); + } + + _rtl92c_enable_fw_download(hw, true); + _rtl92c_write_fw(hw, version, pfwdata, fwsize); + _rtl92c_enable_fw_download(hw, false); + + if (_rtl92c_fw_free_to_go(hw)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Firmware is not ready to run!\n"); + } else { + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, + "Firmware is ready to run!\n"); + } + + return 0; +} +EXPORT_SYMBOL(rtl92c_download_fw); + +static bool _rtl92c_check_fw_read_last_h2c(struct ieee80211_hw *hw, u8 boxnum) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 val_hmetfr, val_mcutst_1; + bool result = false; + + val_hmetfr = rtl_read_byte(rtlpriv, REG_HMETFR); + val_mcutst_1 = rtl_read_byte(rtlpriv, (REG_MCUTST_1 + boxnum)); + + if (((val_hmetfr >> boxnum) & BIT(0)) == 0 && val_mcutst_1 == 0) + result = true; + return result; +} + +static void _rtl92c_fill_h2c_command(struct ieee80211_hw *hw, + u8 element_id, u32 cmd_len, u8 *p_cmdbuffer) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 boxnum; + u16 box_reg = 0, box_extreg = 0; + u8 u1b_tmp; + bool isfw_read = false; + bool bwrite_success = false; + u8 wait_h2c_limmit = 100; + u8 wait_writeh2c_limmit = 100; + u8 boxcontent[4], boxextcontent[2]; + u32 h2c_waitcounter = 0; + unsigned long flag; + u8 idx; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "come in\n"); + + while (true) { + spin_lock_irqsave(&rtlpriv->locks.h2c_lock, flag); + if (rtlhal->h2c_setinprogress) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "H2C set in progress! Wait to set..element_id(%d)\n", + element_id); + + while (rtlhal->h2c_setinprogress) { + spin_unlock_irqrestore(&rtlpriv->locks.h2c_lock, + flag); + h2c_waitcounter++; + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Wait 100 us (%d times)...\n", + h2c_waitcounter); + udelay(100); + + if (h2c_waitcounter > 1000) + return; + spin_lock_irqsave(&rtlpriv->locks.h2c_lock, + flag); + } + spin_unlock_irqrestore(&rtlpriv->locks.h2c_lock, flag); + } else { + rtlhal->h2c_setinprogress = true; + spin_unlock_irqrestore(&rtlpriv->locks.h2c_lock, flag); + break; + } + } + + while (!bwrite_success) { + wait_writeh2c_limmit--; + if (wait_writeh2c_limmit == 0) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Write H2C fail because no trigger for FW INT!\n"); + break; + } + + boxnum = rtlhal->last_hmeboxnum; + switch (boxnum) { + case 0: + box_reg = REG_HMEBOX_0; + box_extreg = REG_HMEBOX_EXT_0; + break; + case 1: + box_reg = REG_HMEBOX_1; + box_extreg = REG_HMEBOX_EXT_1; + break; + case 2: + box_reg = REG_HMEBOX_2; + box_extreg = REG_HMEBOX_EXT_2; + break; + case 3: + box_reg = REG_HMEBOX_3; + box_extreg = REG_HMEBOX_EXT_3; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + + isfw_read = _rtl92c_check_fw_read_last_h2c(hw, boxnum); + while (!isfw_read) { + + wait_h2c_limmit--; + if (wait_h2c_limmit == 0) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Waiting too long for FW read clear HMEBox(%d)!\n", + boxnum); + break; + } + + udelay(10); + + isfw_read = _rtl92c_check_fw_read_last_h2c(hw, boxnum); + u1b_tmp = rtl_read_byte(rtlpriv, 0x1BF); + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Waiting for FW read clear HMEBox(%d)!!! 0x1BF = %2x\n", + boxnum, u1b_tmp); + } + + if (!isfw_read) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Write H2C register BOX[%d] fail!!!!! Fw do not read\n", + boxnum); + break; + } + + memset(boxcontent, 0, sizeof(boxcontent)); + memset(boxextcontent, 0, sizeof(boxextcontent)); + boxcontent[0] = element_id; + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Write element_id box_reg(%4x) = %2x\n", + box_reg, element_id); + + switch (cmd_len) { + case 1: + boxcontent[0] &= ~(BIT(7)); + memcpy((u8 *) (boxcontent) + 1, + p_cmdbuffer, 1); + + for (idx = 0; idx < 4; idx++) { + rtl_write_byte(rtlpriv, box_reg + idx, + boxcontent[idx]); + } + break; + case 2: + boxcontent[0] &= ~(BIT(7)); + memcpy((u8 *) (boxcontent) + 1, + p_cmdbuffer, 2); + + for (idx = 0; idx < 4; idx++) { + rtl_write_byte(rtlpriv, box_reg + idx, + boxcontent[idx]); + } + break; + case 3: + boxcontent[0] &= ~(BIT(7)); + memcpy((u8 *) (boxcontent) + 1, + p_cmdbuffer, 3); + + for (idx = 0; idx < 4; idx++) { + rtl_write_byte(rtlpriv, box_reg + idx, + boxcontent[idx]); + } + break; + case 4: + boxcontent[0] |= (BIT(7)); + memcpy((u8 *) (boxextcontent), + p_cmdbuffer, 2); + memcpy((u8 *) (boxcontent) + 1, + p_cmdbuffer + 2, 2); + + for (idx = 0; idx < 2; idx++) { + rtl_write_byte(rtlpriv, box_extreg + idx, + boxextcontent[idx]); + } + + for (idx = 0; idx < 4; idx++) { + rtl_write_byte(rtlpriv, box_reg + idx, + boxcontent[idx]); + } + break; + case 5: + boxcontent[0] |= (BIT(7)); + memcpy((u8 *) (boxextcontent), + p_cmdbuffer, 2); + memcpy((u8 *) (boxcontent) + 1, + p_cmdbuffer + 2, 3); + + for (idx = 0; idx < 2; idx++) { + rtl_write_byte(rtlpriv, box_extreg + idx, + boxextcontent[idx]); + } + + for (idx = 0; idx < 4; idx++) { + rtl_write_byte(rtlpriv, box_reg + idx, + boxcontent[idx]); + } + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + + bwrite_success = true; + + rtlhal->last_hmeboxnum = boxnum + 1; + if (rtlhal->last_hmeboxnum == 4) + rtlhal->last_hmeboxnum = 0; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "pHalData->last_hmeboxnum = %d\n", + rtlhal->last_hmeboxnum); + } + + spin_lock_irqsave(&rtlpriv->locks.h2c_lock, flag); + rtlhal->h2c_setinprogress = false; + spin_unlock_irqrestore(&rtlpriv->locks.h2c_lock, flag); + + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "go out\n"); +} + +void rtl92c_fill_h2c_cmd(struct ieee80211_hw *hw, + u8 element_id, u32 cmd_len, u8 *p_cmdbuffer) +{ + u32 tmp_cmdbuf[2]; + + memset(tmp_cmdbuf, 0, 8); + memcpy(tmp_cmdbuf, p_cmdbuffer, cmd_len); + _rtl92c_fill_h2c_command(hw, element_id, cmd_len, (u8 *)&tmp_cmdbuf); + + return; +} +EXPORT_SYMBOL(rtl92c_fill_h2c_cmd); + +void rtl92c_firmware_selfreset(struct ieee80211_hw *hw) +{ + u8 u1b_tmp; + u8 delay = 100; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_byte(rtlpriv, REG_HMETFR + 3, 0x20); + u1b_tmp = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN + 1); + + while (u1b_tmp & BIT(2)) { + delay--; + if (delay == 0) { + RT_ASSERT(false, "8051 reset fail\n"); + break; + } + udelay(50); + u1b_tmp = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN + 1); + } +} +EXPORT_SYMBOL(rtl92c_firmware_selfreset); + +void rtl92c_set_fw_pwrmode_cmd(struct ieee80211_hw *hw, u8 mode) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 u1_h2c_set_pwrmode[3] = {0}; + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, "FW LPS mode = %d\n", mode); + + SET_H2CCMD_PWRMODE_PARM_MODE(u1_h2c_set_pwrmode, mode); + SET_H2CCMD_PWRMODE_PARM_SMART_PS(u1_h2c_set_pwrmode, + (rtlpriv->mac80211.p2p) ? + ppsc->smart_ps : 1); + SET_H2CCMD_PWRMODE_PARM_BCN_PASS_TIME(u1_h2c_set_pwrmode, + ppsc->reg_max_lps_awakeintvl); + + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_DMESG, + "rtl92c_set_fw_rsvdpagepkt(): u1_h2c_set_pwrmode", + u1_h2c_set_pwrmode, 3); + rtl92c_fill_h2c_cmd(hw, H2C_SETPWRMODE, 3, u1_h2c_set_pwrmode); + +} +EXPORT_SYMBOL(rtl92c_set_fw_pwrmode_cmd); + +static bool _rtl92c_cmd_send_packet(struct ieee80211_hw *hw, + struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl8192_tx_ring *ring; + struct rtl_tx_desc *pdesc; + unsigned long flags; + struct sk_buff *pskb = NULL; + + ring = &rtlpci->tx_ring[BEACON_QUEUE]; + + pskb = __skb_dequeue(&ring->queue); + kfree_skb(pskb); + + spin_lock_irqsave(&rtlpriv->locks.irq_th_lock, flags); + + pdesc = &ring->desc[0]; + + rtlpriv->cfg->ops->fill_tx_cmddesc(hw, (u8 *) pdesc, 1, 1, skb); + + __skb_queue_tail(&ring->queue, skb); + + spin_unlock_irqrestore(&rtlpriv->locks.irq_th_lock, flags); + + rtlpriv->cfg->ops->tx_polling(hw, BEACON_QUEUE); + + return true; +} + +#define BEACON_PG 0 /*->1*/ +#define PSPOLL_PG 2 +#define NULL_PG 3 +#define PROBERSP_PG 4 /*->5*/ + +#define TOTAL_RESERVED_PKT_LEN 768 + +static u8 reserved_page_packet[TOTAL_RESERVED_PKT_LEN] = { + /* page 0 beacon */ + 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xE0, 0x4C, 0x76, 0x00, 0x42, + 0x00, 0x40, 0x10, 0x10, 0x00, 0x03, 0x50, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x64, 0x00, 0x00, 0x04, 0x00, 0x0C, 0x6C, 0x69, + 0x6E, 0x6B, 0x73, 0x79, 0x73, 0x5F, 0x77, 0x6C, + 0x61, 0x6E, 0x01, 0x04, 0x82, 0x84, 0x8B, 0x96, + 0x03, 0x01, 0x01, 0x06, 0x02, 0x00, 0x00, 0x2A, + 0x01, 0x00, 0x32, 0x08, 0x24, 0x30, 0x48, 0x6C, + 0x0C, 0x12, 0x18, 0x60, 0x2D, 0x1A, 0x6C, 0x18, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3D, 0x00, 0xDD, 0x06, 0x00, 0xE0, 0x4C, 0x02, + 0x01, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 1 beacon */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x20, 0x8C, 0x00, 0x12, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 2 ps-poll */ + 0xA4, 0x10, 0x01, 0xC0, 0x00, 0x40, 0x10, 0x10, + 0x00, 0x03, 0x00, 0xE0, 0x4C, 0x76, 0x00, 0x42, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x20, 0x8C, 0x00, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 3 null */ + 0x48, 0x01, 0x00, 0x00, 0x00, 0x40, 0x10, 0x10, + 0x00, 0x03, 0x00, 0xE0, 0x4C, 0x76, 0x00, 0x42, + 0x00, 0x40, 0x10, 0x10, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x72, 0x00, 0x20, 0x8C, 0x00, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 4 probe_resp */ + 0x50, 0x00, 0x00, 0x00, 0x00, 0x40, 0x10, 0x10, + 0x00, 0x03, 0x00, 0xE0, 0x4C, 0x76, 0x00, 0x42, + 0x00, 0x40, 0x10, 0x10, 0x00, 0x03, 0x00, 0x00, + 0x9E, 0x46, 0x15, 0x32, 0x27, 0xF2, 0x2D, 0x00, + 0x64, 0x00, 0x00, 0x04, 0x00, 0x0C, 0x6C, 0x69, + 0x6E, 0x6B, 0x73, 0x79, 0x73, 0x5F, 0x77, 0x6C, + 0x61, 0x6E, 0x01, 0x04, 0x82, 0x84, 0x8B, 0x96, + 0x03, 0x01, 0x01, 0x06, 0x02, 0x00, 0x00, 0x2A, + 0x01, 0x00, 0x32, 0x08, 0x24, 0x30, 0x48, 0x6C, + 0x0C, 0x12, 0x18, 0x60, 0x2D, 0x1A, 0x6C, 0x18, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3D, 0x00, 0xDD, 0x06, 0x00, 0xE0, 0x4C, 0x02, + 0x01, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 5 probe_resp */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +void rtl92c_set_fw_rsvdpagepkt(struct ieee80211_hw *hw, bool dl_finished) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct sk_buff *skb = NULL; + + u32 totalpacketlen; + bool rtstatus; + u8 u1RsvdPageLoc[3] = {0}; + bool dlok = false; + + u8 *beacon; + u8 *pspoll; + u8 *nullfunc; + u8 *probersp; + /*--------------------------------------------------------- + (1) beacon + ---------------------------------------------------------*/ + beacon = &reserved_page_packet[BEACON_PG * 128]; + SET_80211_HDR_ADDRESS2(beacon, mac->mac_addr); + SET_80211_HDR_ADDRESS3(beacon, mac->bssid); + + /*------------------------------------------------------- + (2) ps-poll + --------------------------------------------------------*/ + pspoll = &reserved_page_packet[PSPOLL_PG * 128]; + SET_80211_PS_POLL_AID(pspoll, (mac->assoc_id | 0xc000)); + SET_80211_PS_POLL_BSSID(pspoll, mac->bssid); + SET_80211_PS_POLL_TA(pspoll, mac->mac_addr); + + SET_H2CCMD_RSVDPAGE_LOC_PSPOLL(u1RsvdPageLoc, PSPOLL_PG); + + /*-------------------------------------------------------- + (3) null data + ---------------------------------------------------------*/ + nullfunc = &reserved_page_packet[NULL_PG * 128]; + SET_80211_HDR_ADDRESS1(nullfunc, mac->bssid); + SET_80211_HDR_ADDRESS2(nullfunc, mac->mac_addr); + SET_80211_HDR_ADDRESS3(nullfunc, mac->bssid); + + SET_H2CCMD_RSVDPAGE_LOC_NULL_DATA(u1RsvdPageLoc, NULL_PG); + + /*--------------------------------------------------------- + (4) probe response + ----------------------------------------------------------*/ + probersp = &reserved_page_packet[PROBERSP_PG * 128]; + SET_80211_HDR_ADDRESS1(probersp, mac->bssid); + SET_80211_HDR_ADDRESS2(probersp, mac->mac_addr); + SET_80211_HDR_ADDRESS3(probersp, mac->bssid); + + SET_H2CCMD_RSVDPAGE_LOC_PROBE_RSP(u1RsvdPageLoc, PROBERSP_PG); + + totalpacketlen = TOTAL_RESERVED_PKT_LEN; + + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_LOUD, + "rtl92c_set_fw_rsvdpagepkt(): HW_VAR_SET_TX_CMD: ALL", + &reserved_page_packet[0], totalpacketlen); + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_DMESG, + "rtl92c_set_fw_rsvdpagepkt(): HW_VAR_SET_TX_CMD: ALL", + u1RsvdPageLoc, 3); + + + skb = dev_alloc_skb(totalpacketlen); + if (!skb) + return; + kmemleak_not_leak(skb); + + memcpy((u8 *) skb_put(skb, totalpacketlen), + &reserved_page_packet, totalpacketlen); + + rtstatus = _rtl92c_cmd_send_packet(hw, skb); + + if (rtstatus) + dlok = true; + + if (dlok) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "Set RSVD page location to Fw\n"); + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_DMESG, + "H2C_RSVDPAGE", u1RsvdPageLoc, 3); + rtl92c_fill_h2c_cmd(hw, H2C_RSVDPAGE, + sizeof(u1RsvdPageLoc), u1RsvdPageLoc); + } else + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Set RSVD page location to Fw FAIL!!!!!!\n"); +} +EXPORT_SYMBOL(rtl92c_set_fw_rsvdpagepkt); + +void rtl92c_set_fw_joinbss_report_cmd(struct ieee80211_hw *hw, u8 mstatus) +{ + u8 u1_joinbssrpt_parm[1] = {0}; + + SET_H2CCMD_JOINBSSRPT_PARM_OPMODE(u1_joinbssrpt_parm, mstatus); + + rtl92c_fill_h2c_cmd(hw, H2C_JOINBSSRPT, 1, u1_joinbssrpt_parm); +} +EXPORT_SYMBOL(rtl92c_set_fw_joinbss_report_cmd); + +static void rtl92c_set_p2p_ctw_period_cmd(struct ieee80211_hw *hw, u8 ctwindow) +{ + u8 u1_ctwindow_period[1] = {ctwindow}; + + rtl92c_fill_h2c_cmd(hw, H2C_P2P_PS_CTW_CMD, 1, u1_ctwindow_period); +} + +void rtl92c_set_p2p_ps_offload_cmd(struct ieee80211_hw *hw, u8 p2p_ps_state) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *rtlps = rtl_psc(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_p2p_ps_info *p2pinfo = &(rtlps->p2p_ps_info); + struct p2p_ps_offload_t *p2p_ps_offload = &rtlhal->p2p_ps_offload; + u8 i; + u16 ctwindow; + u32 start_time, tsf_low; + + switch (p2p_ps_state) { + case P2P_PS_DISABLE: + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, "P2P_PS_DISABLE\n"); + memset(p2p_ps_offload, 0, sizeof(struct p2p_ps_offload_t)); + break; + case P2P_PS_ENABLE: + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, "P2P_PS_ENABLE\n"); + /* update CTWindow value. */ + if (p2pinfo->ctwindow > 0) { + p2p_ps_offload->ctwindow_en = 1; + ctwindow = p2pinfo->ctwindow; + rtl92c_set_p2p_ctw_period_cmd(hw, ctwindow); + } + /* hw only support 2 set of NoA */ + for (i = 0; i < p2pinfo->noa_num; i++) { + /* To control the register setting for which NOA*/ + rtl_write_byte(rtlpriv, 0x5cf, (i << 4)); + if (i == 0) + p2p_ps_offload->noa0_en = 1; + else + p2p_ps_offload->noa1_en = 1; + + /* config P2P NoA Descriptor Register */ + rtl_write_dword(rtlpriv, 0x5E0, + p2pinfo->noa_duration[i]); + rtl_write_dword(rtlpriv, 0x5E4, + p2pinfo->noa_interval[i]); + + /*Get Current TSF value */ + tsf_low = rtl_read_dword(rtlpriv, REG_TSFTR); + + start_time = p2pinfo->noa_start_time[i]; + if (p2pinfo->noa_count_type[i] != 1) { + while (start_time <= (tsf_low+(50*1024))) { + start_time += p2pinfo->noa_interval[i]; + if (p2pinfo->noa_count_type[i] != 255) + p2pinfo->noa_count_type[i]--; + } + } + rtl_write_dword(rtlpriv, 0x5E8, start_time); + rtl_write_dword(rtlpriv, 0x5EC, + p2pinfo->noa_count_type[i]); + } + + if ((p2pinfo->opp_ps == 1) || (p2pinfo->noa_num > 0)) { + /* rst p2p circuit */ + rtl_write_byte(rtlpriv, REG_DUAL_TSF_RST, BIT(4)); + + p2p_ps_offload->offload_en = 1; + + if (P2P_ROLE_GO == rtlpriv->mac80211.p2p) { + p2p_ps_offload->role = 1; + p2p_ps_offload->allstasleep = 0; + } else { + p2p_ps_offload->role = 0; + } + + p2p_ps_offload->discovery = 0; + } + break; + case P2P_PS_SCAN: + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, "P2P_PS_SCAN\n"); + p2p_ps_offload->discovery = 1; + break; + case P2P_PS_SCAN_DONE: + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, "P2P_PS_SCAN_DONE\n"); + p2p_ps_offload->discovery = 0; + p2pinfo->p2p_ps_state = P2P_PS_ENABLE; + break; + default: + break; + } + + rtl92c_fill_h2c_cmd(hw, H2C_P2P_PS_OFFLOAD, 1, (u8 *)p2p_ps_offload); +} +EXPORT_SYMBOL_GPL(rtl92c_set_p2p_ps_offload_cmd); diff --git a/drivers/net/wireless/rtlwifi/rtl8192c/fw_common.h b/drivers/net/wireless/rtlwifi/rtl8192c/fw_common.h new file mode 100644 index 0000000..15b2055 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192c/fw_common.h @@ -0,0 +1,103 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92C__FW__COMMON__H__ +#define __RTL92C__FW__COMMON__H__ + +#define FW_8192C_SIZE 0x3000 +#define FW_8192C_START_ADDRESS 0x1000 +#define FW_8192C_END_ADDRESS 0x1FFF +#define FW_8192C_PAGE_SIZE 4096 +#define FW_8192C_POLLING_DELAY 5 +#define FW_8192C_POLLING_TIMEOUT_COUNT 100 + +#define IS_FW_HEADER_EXIST(_pfwhdr) \ + ((le16_to_cpu(_pfwhdr->signature)&0xFFF0) == 0x92C0 ||\ + (le16_to_cpu(_pfwhdr->signature)&0xFFF0) == 0x88C0) + +struct rtl92c_firmware_header { + __le16 signature; + u8 category; + u8 function; + __le16 version; + u8 subversion; + u8 rsvd1; + u8 month; + u8 date; + u8 hour; + u8 minute; + __le16 ramcodeSize; + __le16 rsvd2; + __le32 svnindex; + __le32 rsvd3; + __le32 rsvd4; + __le32 rsvd5; +}; + +enum rtl8192c_h2c_cmd { + H2C_AP_OFFLOAD = 0, + H2C_SETPWRMODE = 1, + H2C_JOINBSSRPT = 2, + H2C_RSVDPAGE = 3, + H2C_RSSI_REPORT = 5, + H2C_RA_MASK = 6, + H2C_MACID_PS_MODE = 7, + H2C_P2P_PS_OFFLOAD = 8, + H2C_P2P_PS_CTW_CMD = 32, + MAX_H2CCMD +}; + +#define pagenum_128(_len) (u32)(((_len)>>7) + ((_len)&0x7F ? 1 : 0)) + +#define SET_H2CCMD_PWRMODE_PARM_MODE(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE(__ph2ccmd, 0, 8, __val) +#define SET_H2CCMD_PWRMODE_PARM_SMART_PS(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE((__ph2ccmd)+1, 0, 8, __val) +#define SET_H2CCMD_PWRMODE_PARM_BCN_PASS_TIME(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE((__ph2ccmd)+2, 0, 8, __val) +#define SET_H2CCMD_JOINBSSRPT_PARM_OPMODE(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE(__ph2ccmd, 0, 8, __val) +#define SET_H2CCMD_RSVDPAGE_LOC_PROBE_RSP(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE(__ph2ccmd, 0, 8, __val) +#define SET_H2CCMD_RSVDPAGE_LOC_PSPOLL(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE((__ph2ccmd)+1, 0, 8, __val) +#define SET_H2CCMD_RSVDPAGE_LOC_NULL_DATA(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE((__ph2ccmd)+2, 0, 8, __val) + +int rtl92c_download_fw(struct ieee80211_hw *hw); +void rtl92c_fill_h2c_cmd(struct ieee80211_hw *hw, u8 element_id, + u32 cmd_len, u8 *p_cmdbuffer); +void rtl92c_firmware_selfreset(struct ieee80211_hw *hw); +void rtl92c_set_fw_pwrmode_cmd(struct ieee80211_hw *hw, u8 mode); +void rtl92c_set_fw_rsvdpagepkt(struct ieee80211_hw *hw, bool b_dl_finished); +void rtl92c_set_fw_joinbss_report_cmd(struct ieee80211_hw *hw, u8 mstatus); +void usb_writeN_async(struct rtl_priv *rtlpriv, u32 addr, void *data, u16 len); +void rtl92c_set_p2p_ps_offload_cmd(struct ieee80211_hw *hw, u8 p2p_ps_state); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192c/main.c b/drivers/net/wireless/rtlwifi/rtl8192c/main.c new file mode 100644 index 0000000..918b1d1 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192c/main.c @@ -0,0 +1,40 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include + + +MODULE_AUTHOR("lizhaoming "); +MODULE_AUTHOR("Realtek WlanFAE "); +MODULE_AUTHOR("Georgia "); +MODULE_AUTHOR("Ziv Huang "); +MODULE_AUTHOR("Larry Finger "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Realtek 8192C/8188C 802.11n PCI wireless"); diff --git a/drivers/net/wireless/rtlwifi/rtl8192c/phy_common.c b/drivers/net/wireless/rtlwifi/rtl8192c/phy_common.c new file mode 100644 index 0000000..246e535 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192c/phy_common.c @@ -0,0 +1,1953 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include +#include "../wifi.h" +#include "../rtl8192ce/reg.h" +#include "../rtl8192ce/def.h" +#include "dm_common.h" +#include "phy_common.h" + +u32 rtl92c_phy_query_bb_reg(struct ieee80211_hw *hw, u32 regaddr, u32 bitmask) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 returnvalue, originalvalue, bitshift; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, "regaddr(%#x), bitmask(%#x)\n", + regaddr, bitmask); + originalvalue = rtl_read_dword(rtlpriv, regaddr); + bitshift = _rtl92c_phy_calculate_bit_shift(bitmask); + returnvalue = (originalvalue & bitmask) >> bitshift; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "BBR MASK=0x%x Addr[0x%x]=0x%x\n", + bitmask, regaddr, originalvalue); + + return returnvalue; + +} +EXPORT_SYMBOL(rtl92c_phy_query_bb_reg); + +void rtl92c_phy_set_bb_reg(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 originalvalue, bitshift; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x)\n", + regaddr, bitmask, data); + + if (bitmask != MASKDWORD) { + originalvalue = rtl_read_dword(rtlpriv, regaddr); + bitshift = _rtl92c_phy_calculate_bit_shift(bitmask); + data = ((originalvalue & (~bitmask)) | (data << bitshift)); + } + + rtl_write_dword(rtlpriv, regaddr, data); + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x)\n", + regaddr, bitmask, data); + +} +EXPORT_SYMBOL(rtl92c_phy_set_bb_reg); + +u32 _rtl92c_phy_fw_rf_serial_read(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset) +{ + RT_ASSERT(false, "deprecated!\n"); + return 0; + +} +EXPORT_SYMBOL(_rtl92c_phy_fw_rf_serial_read); + +void _rtl92c_phy_fw_rf_serial_write(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset, + u32 data) +{ + RT_ASSERT(false, "deprecated!\n"); +} +EXPORT_SYMBOL(_rtl92c_phy_fw_rf_serial_write); + +u32 _rtl92c_phy_rf_serial_read(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct bb_reg_def *pphyreg = &rtlphy->phyreg_def[rfpath]; + u32 newoffset; + u32 tmplong, tmplong2; + u8 rfpi_enable = 0; + u32 retvalue; + + offset &= 0x3f; + newoffset = offset; + if (RT_CANNOT_IO(hw)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "return all one\n"); + return 0xFFFFFFFF; + } + tmplong = rtl_get_bbreg(hw, RFPGA0_XA_HSSIPARAMETER2, MASKDWORD); + if (rfpath == RF90_PATH_A) + tmplong2 = tmplong; + else + tmplong2 = rtl_get_bbreg(hw, pphyreg->rfhssi_para2, MASKDWORD); + tmplong2 = (tmplong2 & (~BLSSIREADADDRESS)) | + (newoffset << 23) | BLSSIREADEDGE; + rtl_set_bbreg(hw, RFPGA0_XA_HSSIPARAMETER2, MASKDWORD, + tmplong & (~BLSSIREADEDGE)); + mdelay(1); + rtl_set_bbreg(hw, pphyreg->rfhssi_para2, MASKDWORD, tmplong2); + mdelay(1); + rtl_set_bbreg(hw, RFPGA0_XA_HSSIPARAMETER2, MASKDWORD, + tmplong | BLSSIREADEDGE); + mdelay(1); + if (rfpath == RF90_PATH_A) + rfpi_enable = (u8) rtl_get_bbreg(hw, RFPGA0_XA_HSSIPARAMETER1, + BIT(8)); + else if (rfpath == RF90_PATH_B) + rfpi_enable = (u8) rtl_get_bbreg(hw, RFPGA0_XB_HSSIPARAMETER1, + BIT(8)); + if (rfpi_enable) + retvalue = rtl_get_bbreg(hw, pphyreg->rf_rbpi, + BLSSIREADBACKDATA); + else + retvalue = rtl_get_bbreg(hw, pphyreg->rf_rb, + BLSSIREADBACKDATA); + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, "RFR-%d Addr[0x%x]=0x%x\n", + rfpath, pphyreg->rf_rb, retvalue); + return retvalue; +} +EXPORT_SYMBOL(_rtl92c_phy_rf_serial_read); + +void _rtl92c_phy_rf_serial_write(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset, + u32 data) +{ + u32 data_and_addr; + u32 newoffset; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct bb_reg_def *pphyreg = &rtlphy->phyreg_def[rfpath]; + + if (RT_CANNOT_IO(hw)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "stop\n"); + return; + } + offset &= 0x3f; + newoffset = offset; + data_and_addr = ((newoffset << 20) | (data & 0x000fffff)) & 0x0fffffff; + rtl_set_bbreg(hw, pphyreg->rf3wire_offset, MASKDWORD, data_and_addr); + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, "RFW-%d Addr[0x%x]=0x%x\n", + rfpath, pphyreg->rf3wire_offset, data_and_addr); +} +EXPORT_SYMBOL(_rtl92c_phy_rf_serial_write); + +u32 _rtl92c_phy_calculate_bit_shift(u32 bitmask) +{ + u32 i; + + for (i = 0; i <= 31; i++) { + if ((bitmask >> i) & 0x1) + break; + } + return i; +} +EXPORT_SYMBOL(_rtl92c_phy_calculate_bit_shift); + +static void _rtl92c_phy_bb_config_1t(struct ieee80211_hw *hw) +{ + rtl_set_bbreg(hw, RFPGA0_TXINFO, 0x3, 0x2); + rtl_set_bbreg(hw, RFPGA1_TXINFO, 0x300033, 0x200022); + rtl_set_bbreg(hw, RCCK0_AFESETTING, MASKBYTE3, 0x45); + rtl_set_bbreg(hw, ROFDM0_TRXPATHENABLE, MASKBYTE0, 0x23); + rtl_set_bbreg(hw, ROFDM0_AGCPARAMETER1, 0x30, 0x1); + rtl_set_bbreg(hw, 0xe74, 0x0c000000, 0x2); + rtl_set_bbreg(hw, 0xe78, 0x0c000000, 0x2); + rtl_set_bbreg(hw, 0xe7c, 0x0c000000, 0x2); + rtl_set_bbreg(hw, 0xe80, 0x0c000000, 0x2); + rtl_set_bbreg(hw, 0xe88, 0x0c000000, 0x2); +} + +bool rtl92c_phy_rf_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + return rtlpriv->cfg->ops->phy_rf6052_config(hw); +} +EXPORT_SYMBOL(rtl92c_phy_rf_config); + +bool _rtl92c_phy_bb8192c_config_parafile(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + bool rtstatus; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "==>\n"); + rtstatus = rtlpriv->cfg->ops->config_bb_with_headerfile(hw, + BASEBAND_CONFIG_PHY_REG); + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Write BB Reg Fail!!\n"); + return false; + } + if (rtlphy->rf_type == RF_1T2R) { + _rtl92c_phy_bb_config_1t(hw); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "Config to 1T!!\n"); + } + if (rtlefuse->autoload_failflag == false) { + rtlphy->pwrgroup_cnt = 0; + rtstatus = rtlpriv->cfg->ops->config_bb_with_pgheaderfile(hw, + BASEBAND_CONFIG_PHY_REG); + } + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "BB_PG Reg Fail!!\n"); + return false; + } + rtstatus = rtlpriv->cfg->ops->config_bb_with_headerfile(hw, + BASEBAND_CONFIG_AGC_TAB); + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "AGC Table Fail\n"); + return false; + } + rtlphy->cck_high_power = (bool) (rtl_get_bbreg(hw, + RFPGA0_XA_HSSIPARAMETER2, + 0x200)); + + return true; +} +EXPORT_SYMBOL(_rtl92c_phy_bb8192c_config_parafile); + +void _rtl92c_store_pwrIndex_diffrate_offset(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask, + u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + int index; + + if (regaddr == RTXAGC_A_RATE18_06) + index = 0; + else if (regaddr == RTXAGC_A_RATE54_24) + index = 1; + else if (regaddr == RTXAGC_A_CCK1_MCS32) + index = 6; + else if (regaddr == RTXAGC_B_CCK11_A_CCK2_11 && bitmask == 0xffffff00) + index = 7; + else if (regaddr == RTXAGC_A_MCS03_MCS00) + index = 2; + else if (regaddr == RTXAGC_A_MCS07_MCS04) + index = 3; + else if (regaddr == RTXAGC_A_MCS11_MCS08) + index = 4; + else if (regaddr == RTXAGC_A_MCS15_MCS12) + index = 5; + else if (regaddr == RTXAGC_B_RATE18_06) + index = 8; + else if (regaddr == RTXAGC_B_RATE54_24) + index = 9; + else if (regaddr == RTXAGC_B_CCK1_55_MCS32) + index = 14; + else if (regaddr == RTXAGC_B_CCK11_A_CCK2_11 && bitmask == 0x000000ff) + index = 15; + else if (regaddr == RTXAGC_B_MCS03_MCS00) + index = 10; + else if (regaddr == RTXAGC_B_MCS07_MCS04) + index = 11; + else if (regaddr == RTXAGC_B_MCS11_MCS08) + index = 12; + else if (regaddr == RTXAGC_B_MCS15_MCS12) + index = 13; + else + return; + + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][index] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][%d] = 0x%x\n", + rtlphy->pwrgroup_cnt, index, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][index]); + + if (index == 13) + rtlphy->pwrgroup_cnt++; +} +EXPORT_SYMBOL(_rtl92c_store_pwrIndex_diffrate_offset); + +void rtl92c_phy_get_hw_reg_originalvalue(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + rtlphy->default_initialgain[0] = + (u8) rtl_get_bbreg(hw, ROFDM0_XAAGCCORE1, MASKBYTE0); + rtlphy->default_initialgain[1] = + (u8) rtl_get_bbreg(hw, ROFDM0_XBAGCCORE1, MASKBYTE0); + rtlphy->default_initialgain[2] = + (u8) rtl_get_bbreg(hw, ROFDM0_XCAGCCORE1, MASKBYTE0); + rtlphy->default_initialgain[3] = + (u8) rtl_get_bbreg(hw, ROFDM0_XDAGCCORE1, MASKBYTE0); + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Default initial gain (c50=0x%x, c58=0x%x, c60=0x%x, c68=0x%x\n", + rtlphy->default_initialgain[0], + rtlphy->default_initialgain[1], + rtlphy->default_initialgain[2], + rtlphy->default_initialgain[3]); + + rtlphy->framesync = (u8) rtl_get_bbreg(hw, + ROFDM0_RXDETECTOR3, MASKBYTE0); + rtlphy->framesync_c34 = rtl_get_bbreg(hw, + ROFDM0_RXDETECTOR2, MASKDWORD); + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Default framesync (0x%x) = 0x%x\n", + ROFDM0_RXDETECTOR3, rtlphy->framesync); +} + +void _rtl92c_phy_init_bb_rf_register_definition(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + rtlphy->phyreg_def[RF90_PATH_A].rfintfs = RFPGA0_XAB_RFINTERFACESW; + rtlphy->phyreg_def[RF90_PATH_B].rfintfs = RFPGA0_XAB_RFINTERFACESW; + rtlphy->phyreg_def[RF90_PATH_C].rfintfs = RFPGA0_XCD_RFINTERFACESW; + rtlphy->phyreg_def[RF90_PATH_D].rfintfs = RFPGA0_XCD_RFINTERFACESW; + + rtlphy->phyreg_def[RF90_PATH_A].rfintfi = RFPGA0_XAB_RFINTERFACERB; + rtlphy->phyreg_def[RF90_PATH_B].rfintfi = RFPGA0_XAB_RFINTERFACERB; + rtlphy->phyreg_def[RF90_PATH_C].rfintfi = RFPGA0_XCD_RFINTERFACERB; + rtlphy->phyreg_def[RF90_PATH_D].rfintfi = RFPGA0_XCD_RFINTERFACERB; + + rtlphy->phyreg_def[RF90_PATH_A].rfintfo = RFPGA0_XA_RFINTERFACEOE; + rtlphy->phyreg_def[RF90_PATH_B].rfintfo = RFPGA0_XB_RFINTERFACEOE; + + rtlphy->phyreg_def[RF90_PATH_A].rfintfe = RFPGA0_XA_RFINTERFACEOE; + rtlphy->phyreg_def[RF90_PATH_B].rfintfe = RFPGA0_XB_RFINTERFACEOE; + + rtlphy->phyreg_def[RF90_PATH_A].rf3wire_offset = + RFPGA0_XA_LSSIPARAMETER; + rtlphy->phyreg_def[RF90_PATH_B].rf3wire_offset = + RFPGA0_XB_LSSIPARAMETER; + + rtlphy->phyreg_def[RF90_PATH_A].rflssi_select = rFPGA0_XAB_RFPARAMETER; + rtlphy->phyreg_def[RF90_PATH_B].rflssi_select = rFPGA0_XAB_RFPARAMETER; + rtlphy->phyreg_def[RF90_PATH_C].rflssi_select = rFPGA0_XCD_RFPARAMETER; + rtlphy->phyreg_def[RF90_PATH_D].rflssi_select = rFPGA0_XCD_RFPARAMETER; + + rtlphy->phyreg_def[RF90_PATH_A].rftxgain_stage = RFPGA0_TXGAINSTAGE; + rtlphy->phyreg_def[RF90_PATH_B].rftxgain_stage = RFPGA0_TXGAINSTAGE; + rtlphy->phyreg_def[RF90_PATH_C].rftxgain_stage = RFPGA0_TXGAINSTAGE; + rtlphy->phyreg_def[RF90_PATH_D].rftxgain_stage = RFPGA0_TXGAINSTAGE; + + rtlphy->phyreg_def[RF90_PATH_A].rfhssi_para1 = RFPGA0_XA_HSSIPARAMETER1; + rtlphy->phyreg_def[RF90_PATH_B].rfhssi_para1 = RFPGA0_XB_HSSIPARAMETER1; + + rtlphy->phyreg_def[RF90_PATH_A].rfhssi_para2 = RFPGA0_XA_HSSIPARAMETER2; + rtlphy->phyreg_def[RF90_PATH_B].rfhssi_para2 = RFPGA0_XB_HSSIPARAMETER2; + + rtlphy->phyreg_def[RF90_PATH_A].rfsw_ctrl = RFPGA0_XAB_SWITCHCONTROL; + rtlphy->phyreg_def[RF90_PATH_B].rfsw_ctrl = RFPGA0_XAB_SWITCHCONTROL; + rtlphy->phyreg_def[RF90_PATH_C].rfsw_ctrl = RFPGA0_XCD_SWITCHCONTROL; + rtlphy->phyreg_def[RF90_PATH_D].rfsw_ctrl = RFPGA0_XCD_SWITCHCONTROL; + + rtlphy->phyreg_def[RF90_PATH_A].rfagc_control1 = ROFDM0_XAAGCCORE1; + rtlphy->phyreg_def[RF90_PATH_B].rfagc_control1 = ROFDM0_XBAGCCORE1; + rtlphy->phyreg_def[RF90_PATH_C].rfagc_control1 = ROFDM0_XCAGCCORE1; + rtlphy->phyreg_def[RF90_PATH_D].rfagc_control1 = ROFDM0_XDAGCCORE1; + + rtlphy->phyreg_def[RF90_PATH_A].rfagc_control2 = ROFDM0_XAAGCCORE2; + rtlphy->phyreg_def[RF90_PATH_B].rfagc_control2 = ROFDM0_XBAGCCORE2; + rtlphy->phyreg_def[RF90_PATH_C].rfagc_control2 = ROFDM0_XCAGCCORE2; + rtlphy->phyreg_def[RF90_PATH_D].rfagc_control2 = ROFDM0_XDAGCCORE2; + + rtlphy->phyreg_def[RF90_PATH_A].rfrxiq_imbal = ROFDM0_XARXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_B].rfrxiq_imbal = ROFDM0_XBRXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_C].rfrxiq_imbal = ROFDM0_XCRXIQIMBANLANCE; + rtlphy->phyreg_def[RF90_PATH_D].rfrxiq_imbal = ROFDM0_XDRXIQIMBALANCE; + + rtlphy->phyreg_def[RF90_PATH_A].rfrx_afe = ROFDM0_XARXAFE; + rtlphy->phyreg_def[RF90_PATH_B].rfrx_afe = ROFDM0_XBRXAFE; + rtlphy->phyreg_def[RF90_PATH_C].rfrx_afe = ROFDM0_XCRXAFE; + rtlphy->phyreg_def[RF90_PATH_D].rfrx_afe = ROFDM0_XDRXAFE; + + rtlphy->phyreg_def[RF90_PATH_A].rftxiq_imbal = ROFDM0_XATXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_B].rftxiq_imbal = ROFDM0_XBTXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_C].rftxiq_imbal = ROFDM0_XCTXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_D].rftxiq_imbal = ROFDM0_XDTXIQIMBALANCE; + + rtlphy->phyreg_def[RF90_PATH_A].rftx_afe = ROFDM0_XATXAFE; + rtlphy->phyreg_def[RF90_PATH_B].rftx_afe = ROFDM0_XBTXAFE; + rtlphy->phyreg_def[RF90_PATH_C].rftx_afe = ROFDM0_XCTXAFE; + rtlphy->phyreg_def[RF90_PATH_D].rftx_afe = ROFDM0_XDTXAFE; + + rtlphy->phyreg_def[RF90_PATH_A].rf_rb = RFPGA0_XA_LSSIREADBACK; + rtlphy->phyreg_def[RF90_PATH_B].rf_rb = RFPGA0_XB_LSSIREADBACK; + rtlphy->phyreg_def[RF90_PATH_C].rf_rb = RFPGA0_XC_LSSIREADBACK; + rtlphy->phyreg_def[RF90_PATH_D].rf_rb = RFPGA0_XD_LSSIREADBACK; + + rtlphy->phyreg_def[RF90_PATH_A].rf_rbpi = TRANSCEIVEA_HSPI_READBACK; + rtlphy->phyreg_def[RF90_PATH_B].rf_rbpi = TRANSCEIVEB_HSPI_READBACK; + +} +EXPORT_SYMBOL(_rtl92c_phy_init_bb_rf_register_definition); + +void rtl92c_phy_get_txpower_level(struct ieee80211_hw *hw, long *powerlevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 txpwr_level; + long txpwr_dbm; + + txpwr_level = rtlphy->cur_cck_txpwridx; + txpwr_dbm = _rtl92c_phy_txpwr_idx_to_dbm(hw, + WIRELESS_MODE_B, txpwr_level); + txpwr_level = rtlphy->cur_ofdm24g_txpwridx + + rtlefuse->legacy_ht_txpowerdiff; + if (_rtl92c_phy_txpwr_idx_to_dbm(hw, + WIRELESS_MODE_G, + txpwr_level) > txpwr_dbm) + txpwr_dbm = + _rtl92c_phy_txpwr_idx_to_dbm(hw, WIRELESS_MODE_G, + txpwr_level); + txpwr_level = rtlphy->cur_ofdm24g_txpwridx; + if (_rtl92c_phy_txpwr_idx_to_dbm(hw, + WIRELESS_MODE_N_24G, + txpwr_level) > txpwr_dbm) + txpwr_dbm = + _rtl92c_phy_txpwr_idx_to_dbm(hw, WIRELESS_MODE_N_24G, + txpwr_level); + *powerlevel = txpwr_dbm; +} + +static void _rtl92c_get_txpower_index(struct ieee80211_hw *hw, u8 channel, + u8 *cckpowerlevel, u8 *ofdmpowerlevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 index = (channel - 1); + + cckpowerlevel[RF90_PATH_A] = + rtlefuse->txpwrlevel_cck[RF90_PATH_A][index]; + cckpowerlevel[RF90_PATH_B] = + rtlefuse->txpwrlevel_cck[RF90_PATH_B][index]; + if (get_rf_type(rtlphy) == RF_1T2R || get_rf_type(rtlphy) == RF_1T1R) { + ofdmpowerlevel[RF90_PATH_A] = + rtlefuse->txpwrlevel_ht40_1s[RF90_PATH_A][index]; + ofdmpowerlevel[RF90_PATH_B] = + rtlefuse->txpwrlevel_ht40_1s[RF90_PATH_B][index]; + } else if (get_rf_type(rtlphy) == RF_2T2R) { + ofdmpowerlevel[RF90_PATH_A] = + rtlefuse->txpwrlevel_ht40_2s[RF90_PATH_A][index]; + ofdmpowerlevel[RF90_PATH_B] = + rtlefuse->txpwrlevel_ht40_2s[RF90_PATH_B][index]; + } +} + +static void _rtl92c_ccxpower_index_check(struct ieee80211_hw *hw, + u8 channel, u8 *cckpowerlevel, + u8 *ofdmpowerlevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + rtlphy->cur_cck_txpwridx = cckpowerlevel[0]; + rtlphy->cur_ofdm24g_txpwridx = ofdmpowerlevel[0]; + +} + +void rtl92c_phy_set_txpower_level(struct ieee80211_hw *hw, u8 channel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtlpriv); + u8 cckpowerlevel[2], ofdmpowerlevel[2]; + + if (!rtlefuse->txpwr_fromeprom) + return; + _rtl92c_get_txpower_index(hw, channel, + &cckpowerlevel[0], &ofdmpowerlevel[0]); + _rtl92c_ccxpower_index_check(hw, + channel, &cckpowerlevel[0], + &ofdmpowerlevel[0]); + rtlpriv->cfg->ops->phy_rf6052_set_cck_txpower(hw, &cckpowerlevel[0]); + rtlpriv->cfg->ops->phy_rf6052_set_ofdm_txpower(hw, &ofdmpowerlevel[0], + channel); +} +EXPORT_SYMBOL(rtl92c_phy_set_txpower_level); + +bool rtl92c_phy_update_txpower_dbm(struct ieee80211_hw *hw, long power_indbm) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 idx; + u8 rf_path; + u8 ccktxpwridx = _rtl92c_phy_dbm_to_txpwr_Idx(hw, + WIRELESS_MODE_B, + power_indbm); + u8 ofdmtxpwridx = _rtl92c_phy_dbm_to_txpwr_Idx(hw, + WIRELESS_MODE_N_24G, + power_indbm); + if (ofdmtxpwridx - rtlefuse->legacy_ht_txpowerdiff > 0) + ofdmtxpwridx -= rtlefuse->legacy_ht_txpowerdiff; + else + ofdmtxpwridx = 0; + RT_TRACE(rtlpriv, COMP_TXAGC, DBG_TRACE, + "%lx dBm, ccktxpwridx = %d, ofdmtxpwridx = %d\n", + power_indbm, ccktxpwridx, ofdmtxpwridx); + for (idx = 0; idx < 14; idx++) { + for (rf_path = 0; rf_path < 2; rf_path++) { + rtlefuse->txpwrlevel_cck[rf_path][idx] = ccktxpwridx; + rtlefuse->txpwrlevel_ht40_1s[rf_path][idx] = + ofdmtxpwridx; + rtlefuse->txpwrlevel_ht40_2s[rf_path][idx] = + ofdmtxpwridx; + } + } + rtl92c_phy_set_txpower_level(hw, rtlphy->current_channel); + return true; +} +EXPORT_SYMBOL(rtl92c_phy_update_txpower_dbm); + +u8 _rtl92c_phy_dbm_to_txpwr_Idx(struct ieee80211_hw *hw, + enum wireless_mode wirelessmode, + long power_indbm) +{ + u8 txpwridx; + long offset; + + switch (wirelessmode) { + case WIRELESS_MODE_B: + offset = -7; + break; + case WIRELESS_MODE_G: + case WIRELESS_MODE_N_24G: + offset = -8; + break; + default: + offset = -8; + break; + } + + if ((power_indbm - offset) > 0) + txpwridx = (u8) ((power_indbm - offset) * 2); + else + txpwridx = 0; + + if (txpwridx > MAX_TXPWR_IDX_NMODE_92S) + txpwridx = MAX_TXPWR_IDX_NMODE_92S; + + return txpwridx; +} +EXPORT_SYMBOL(_rtl92c_phy_dbm_to_txpwr_Idx); + +long _rtl92c_phy_txpwr_idx_to_dbm(struct ieee80211_hw *hw, + enum wireless_mode wirelessmode, + u8 txpwridx) +{ + long offset; + long pwrout_dbm; + + switch (wirelessmode) { + case WIRELESS_MODE_B: + offset = -7; + break; + case WIRELESS_MODE_G: + case WIRELESS_MODE_N_24G: + offset = -8; + break; + default: + offset = -8; + break; + } + pwrout_dbm = txpwridx / 2 + offset; + return pwrout_dbm; +} +EXPORT_SYMBOL(_rtl92c_phy_txpwr_idx_to_dbm); + +void rtl92c_phy_scan_operation_backup(struct ieee80211_hw *hw, u8 operation) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + enum io_type iotype; + + if (!is_hal_stop(rtlhal)) { + switch (operation) { + case SCAN_OPT_BACKUP: + iotype = IO_CMD_PAUSE_DM_BY_SCAN; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_IO_CMD, + (u8 *)&iotype); + + break; + case SCAN_OPT_RESTORE: + iotype = IO_CMD_RESUME_DM_BY_SCAN; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_IO_CMD, + (u8 *)&iotype); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Unknown Scan Backup operation\n"); + break; + } + } +} +EXPORT_SYMBOL(rtl92c_phy_scan_operation_backup); + +void rtl92c_phy_set_bw_mode(struct ieee80211_hw *hw, + enum nl80211_channel_type ch_type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 tmp_bw = rtlphy->current_chan_bw; + + if (rtlphy->set_bwmode_inprogress) + return; + rtlphy->set_bwmode_inprogress = true; + if ((!is_hal_stop(rtlhal)) && !(RT_CANNOT_IO(hw))) { + rtlpriv->cfg->ops->phy_set_bw_mode_callback(hw); + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "FALSE driver sleep or unload\n"); + rtlphy->set_bwmode_inprogress = false; + rtlphy->current_chan_bw = tmp_bw; + } +} +EXPORT_SYMBOL(rtl92c_phy_set_bw_mode); + +void rtl92c_phy_sw_chnl_callback(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 delay; + + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, + "switch to channel%d\n", rtlphy->current_channel); + if (is_hal_stop(rtlhal)) + return; + do { + if (!rtlphy->sw_chnl_inprogress) + break; + if (!_rtl92c_phy_sw_chnl_step_by_step + (hw, rtlphy->current_channel, &rtlphy->sw_chnl_stage, + &rtlphy->sw_chnl_step, &delay)) { + if (delay > 0) + mdelay(delay); + else + continue; + } else { + rtlphy->sw_chnl_inprogress = false; + } + break; + } while (true); + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, "<==\n"); +} +EXPORT_SYMBOL(rtl92c_phy_sw_chnl_callback); + +u8 rtl92c_phy_sw_chnl(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (rtlphy->sw_chnl_inprogress) + return 0; + if (rtlphy->set_bwmode_inprogress) + return 0; + RT_ASSERT((rtlphy->current_channel <= 14), + "WIRELESS_MODE_G but channel>14\n"); + rtlphy->sw_chnl_inprogress = true; + rtlphy->sw_chnl_stage = 0; + rtlphy->sw_chnl_step = 0; + if (!(is_hal_stop(rtlhal)) && !(RT_CANNOT_IO(hw))) { + rtl92c_phy_sw_chnl_callback(hw); + RT_TRACE(rtlpriv, COMP_CHAN, DBG_LOUD, + "sw_chnl_inprogress false schedule workitem\n"); + rtlphy->sw_chnl_inprogress = false; + } else { + RT_TRACE(rtlpriv, COMP_CHAN, DBG_LOUD, + "sw_chnl_inprogress false driver sleep or unload\n"); + rtlphy->sw_chnl_inprogress = false; + } + return 1; +} +EXPORT_SYMBOL(rtl92c_phy_sw_chnl); + +static void _rtl92c_phy_sw_rf_setting(struct ieee80211_hw *hw, u8 channel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (IS_81xxC_VENDOR_UMC_B_CUT(rtlhal->version)) { + if (channel == 6 && rtlphy->current_chan_bw == + HT_CHANNEL_WIDTH_20) + rtl_set_rfreg(hw, RF90_PATH_A, RF_RX_G1, MASKDWORD, + 0x00255); + else{ + u32 backupRF0x1A = (u32)rtl_get_rfreg(hw, RF90_PATH_A, + RF_RX_G1, RFREG_OFFSET_MASK); + rtl_set_rfreg(hw, RF90_PATH_A, RF_RX_G1, MASKDWORD, + backupRF0x1A); + } + } +} + +static bool _rtl92c_phy_set_sw_chnl_cmdarray(struct swchnlcmd *cmdtable, + u32 cmdtableidx, u32 cmdtablesz, + enum swchnlcmd_id cmdid, + u32 para1, u32 para2, u32 msdelay) +{ + struct swchnlcmd *pcmd; + + if (cmdtable == NULL) { + RT_ASSERT(false, "cmdtable cannot be NULL\n"); + return false; + } + + if (cmdtableidx >= cmdtablesz) + return false; + + pcmd = cmdtable + cmdtableidx; + pcmd->cmdid = cmdid; + pcmd->para1 = para1; + pcmd->para2 = para2; + pcmd->msdelay = msdelay; + return true; +} + +bool _rtl92c_phy_sw_chnl_step_by_step(struct ieee80211_hw *hw, + u8 channel, u8 *stage, u8 *step, + u32 *delay) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct swchnlcmd precommoncmd[MAX_PRECMD_CNT]; + u32 precommoncmdcnt; + struct swchnlcmd postcommoncmd[MAX_POSTCMD_CNT]; + u32 postcommoncmdcnt; + struct swchnlcmd rfdependcmd[MAX_RFDEPENDCMD_CNT]; + u32 rfdependcmdcnt; + struct swchnlcmd *currentcmd = NULL; + u8 rfpath; + u8 num_total_rfpath = rtlphy->num_total_rfpath; + + precommoncmdcnt = 0; + _rtl92c_phy_set_sw_chnl_cmdarray(precommoncmd, precommoncmdcnt++, + MAX_PRECMD_CNT, + CMDID_SET_TXPOWEROWER_LEVEL, 0, 0, 0); + _rtl92c_phy_set_sw_chnl_cmdarray(precommoncmd, precommoncmdcnt++, + MAX_PRECMD_CNT, CMDID_END, 0, 0, 0); + + postcommoncmdcnt = 0; + + _rtl92c_phy_set_sw_chnl_cmdarray(postcommoncmd, postcommoncmdcnt++, + MAX_POSTCMD_CNT, CMDID_END, 0, 0, 0); + + rfdependcmdcnt = 0; + + RT_ASSERT((channel >= 1 && channel <= 14), + "invalid channel for Zebra: %d\n", channel); + + _rtl92c_phy_set_sw_chnl_cmdarray(rfdependcmd, rfdependcmdcnt++, + MAX_RFDEPENDCMD_CNT, CMDID_RF_WRITEREG, + RF_CHNLBW, channel, 10); + + _rtl92c_phy_set_sw_chnl_cmdarray(rfdependcmd, rfdependcmdcnt++, + MAX_RFDEPENDCMD_CNT, CMDID_END, 0, 0, + 0); + + do { + switch (*stage) { + case 0: + currentcmd = &precommoncmd[*step]; + break; + case 1: + currentcmd = &rfdependcmd[*step]; + break; + case 2: + currentcmd = &postcommoncmd[*step]; + break; + } + + if (currentcmd->cmdid == CMDID_END) { + if ((*stage) == 2) { + return true; + } else { + (*stage)++; + (*step) = 0; + continue; + } + } + + switch (currentcmd->cmdid) { + case CMDID_SET_TXPOWEROWER_LEVEL: + rtl92c_phy_set_txpower_level(hw, channel); + break; + case CMDID_WRITEPORT_ULONG: + rtl_write_dword(rtlpriv, currentcmd->para1, + currentcmd->para2); + break; + case CMDID_WRITEPORT_USHORT: + rtl_write_word(rtlpriv, currentcmd->para1, + (u16) currentcmd->para2); + break; + case CMDID_WRITEPORT_UCHAR: + rtl_write_byte(rtlpriv, currentcmd->para1, + (u8) currentcmd->para2); + break; + case CMDID_RF_WRITEREG: + for (rfpath = 0; rfpath < num_total_rfpath; rfpath++) { + rtlphy->rfreg_chnlval[rfpath] = + ((rtlphy->rfreg_chnlval[rfpath] & + 0xfffffc00) | currentcmd->para2); + + rtl_set_rfreg(hw, (enum radio_path)rfpath, + currentcmd->para1, + RFREG_OFFSET_MASK, + rtlphy->rfreg_chnlval[rfpath]); + _rtl92c_phy_sw_rf_setting(hw, channel); + } + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + + break; + } while (true); + + (*delay) = currentcmd->msdelay; + (*step)++; + return false; +} + +bool rtl8192_phy_check_is_legal_rfpath(struct ieee80211_hw *hw, u32 rfpath) +{ + return true; +} +EXPORT_SYMBOL(rtl8192_phy_check_is_legal_rfpath); + +static u8 _rtl92c_phy_path_a_iqk(struct ieee80211_hw *hw, bool config_pathb) +{ + u32 reg_eac, reg_e94, reg_e9c, reg_ea4; + u8 result = 0x00; + + rtl_set_bbreg(hw, 0xe30, MASKDWORD, 0x10008c1f); + rtl_set_bbreg(hw, 0xe34, MASKDWORD, 0x10008c1f); + rtl_set_bbreg(hw, 0xe38, MASKDWORD, 0x82140102); + rtl_set_bbreg(hw, 0xe3c, MASKDWORD, + config_pathb ? 0x28160202 : 0x28160502); + + if (config_pathb) { + rtl_set_bbreg(hw, 0xe50, MASKDWORD, 0x10008c22); + rtl_set_bbreg(hw, 0xe54, MASKDWORD, 0x10008c22); + rtl_set_bbreg(hw, 0xe58, MASKDWORD, 0x82140102); + rtl_set_bbreg(hw, 0xe5c, MASKDWORD, 0x28160202); + } + + rtl_set_bbreg(hw, 0xe4c, MASKDWORD, 0x001028d1); + rtl_set_bbreg(hw, 0xe48, MASKDWORD, 0xf9000000); + rtl_set_bbreg(hw, 0xe48, MASKDWORD, 0xf8000000); + + mdelay(IQK_DELAY_TIME); + + reg_eac = rtl_get_bbreg(hw, 0xeac, MASKDWORD); + reg_e94 = rtl_get_bbreg(hw, 0xe94, MASKDWORD); + reg_e9c = rtl_get_bbreg(hw, 0xe9c, MASKDWORD); + reg_ea4 = rtl_get_bbreg(hw, 0xea4, MASKDWORD); + + if (!(reg_eac & BIT(28)) && + (((reg_e94 & 0x03FF0000) >> 16) != 0x142) && + (((reg_e9c & 0x03FF0000) >> 16) != 0x42)) + result |= 0x01; + else + return result; + + if (!(reg_eac & BIT(27)) && + (((reg_ea4 & 0x03FF0000) >> 16) != 0x132) && + (((reg_eac & 0x03FF0000) >> 16) != 0x36)) + result |= 0x02; + return result; +} + +static u8 _rtl92c_phy_path_b_iqk(struct ieee80211_hw *hw) +{ + u32 reg_eac, reg_eb4, reg_ebc, reg_ec4, reg_ecc; + u8 result = 0x00; + + rtl_set_bbreg(hw, 0xe60, MASKDWORD, 0x00000002); + rtl_set_bbreg(hw, 0xe60, MASKDWORD, 0x00000000); + mdelay(IQK_DELAY_TIME); + reg_eac = rtl_get_bbreg(hw, 0xeac, MASKDWORD); + reg_eb4 = rtl_get_bbreg(hw, 0xeb4, MASKDWORD); + reg_ebc = rtl_get_bbreg(hw, 0xebc, MASKDWORD); + reg_ec4 = rtl_get_bbreg(hw, 0xec4, MASKDWORD); + reg_ecc = rtl_get_bbreg(hw, 0xecc, MASKDWORD); + + if (!(reg_eac & BIT(31)) && + (((reg_eb4 & 0x03FF0000) >> 16) != 0x142) && + (((reg_ebc & 0x03FF0000) >> 16) != 0x42)) + result |= 0x01; + else + return result; + if (!(reg_eac & BIT(30)) && + (((reg_ec4 & 0x03FF0000) >> 16) != 0x132) && + (((reg_ecc & 0x03FF0000) >> 16) != 0x36)) + result |= 0x02; + return result; +} + +static void _rtl92c_phy_path_a_fill_iqk_matrix(struct ieee80211_hw *hw, + bool iqk_ok, long result[][8], + u8 final_candidate, bool btxonly) +{ + u32 oldval_0, x, tx0_a, reg; + long y, tx0_c; + + if (final_candidate == 0xFF) { + return; + } else if (iqk_ok) { + oldval_0 = (rtl_get_bbreg(hw, ROFDM0_XATXIQIMBALANCE, + MASKDWORD) >> 22) & 0x3FF; + x = result[final_candidate][0]; + if ((x & 0x00000200) != 0) + x = x | 0xFFFFFC00; + tx0_a = (x * oldval_0) >> 8; + rtl_set_bbreg(hw, ROFDM0_XATXIQIMBALANCE, 0x3FF, tx0_a); + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, BIT(31), + ((x * oldval_0 >> 7) & 0x1)); + y = result[final_candidate][1]; + if ((y & 0x00000200) != 0) + y = y | 0xFFFFFC00; + tx0_c = (y * oldval_0) >> 8; + rtl_set_bbreg(hw, ROFDM0_XCTXAFE, 0xF0000000, + ((tx0_c & 0x3C0) >> 6)); + rtl_set_bbreg(hw, ROFDM0_XATXIQIMBALANCE, 0x003F0000, + (tx0_c & 0x3F)); + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, BIT(29), + ((y * oldval_0 >> 7) & 0x1)); + if (btxonly) + return; + reg = result[final_candidate][2]; + rtl_set_bbreg(hw, ROFDM0_XARXIQIMBALANCE, 0x3FF, reg); + reg = result[final_candidate][3] & 0x3F; + rtl_set_bbreg(hw, ROFDM0_XARXIQIMBALANCE, 0xFC00, reg); + reg = (result[final_candidate][3] >> 6) & 0xF; + rtl_set_bbreg(hw, 0xca0, 0xF0000000, reg); + } +} + +static void _rtl92c_phy_path_b_fill_iqk_matrix(struct ieee80211_hw *hw, + bool iqk_ok, long result[][8], + u8 final_candidate, bool btxonly) +{ + u32 oldval_1, x, tx1_a, reg; + long y, tx1_c; + + if (final_candidate == 0xFF) { + return; + } else if (iqk_ok) { + oldval_1 = (rtl_get_bbreg(hw, ROFDM0_XBTXIQIMBALANCE, + MASKDWORD) >> 22) & 0x3FF; + x = result[final_candidate][4]; + if ((x & 0x00000200) != 0) + x = x | 0xFFFFFC00; + tx1_a = (x * oldval_1) >> 8; + rtl_set_bbreg(hw, ROFDM0_XBTXIQIMBALANCE, 0x3FF, tx1_a); + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, BIT(27), + ((x * oldval_1 >> 7) & 0x1)); + y = result[final_candidate][5]; + if ((y & 0x00000200) != 0) + y = y | 0xFFFFFC00; + tx1_c = (y * oldval_1) >> 8; + rtl_set_bbreg(hw, ROFDM0_XDTXAFE, 0xF0000000, + ((tx1_c & 0x3C0) >> 6)); + rtl_set_bbreg(hw, ROFDM0_XBTXIQIMBALANCE, 0x003F0000, + (tx1_c & 0x3F)); + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, BIT(25), + ((y * oldval_1 >> 7) & 0x1)); + if (btxonly) + return; + reg = result[final_candidate][6]; + rtl_set_bbreg(hw, ROFDM0_XBRXIQIMBALANCE, 0x3FF, reg); + reg = result[final_candidate][7] & 0x3F; + rtl_set_bbreg(hw, ROFDM0_XBRXIQIMBALANCE, 0xFC00, reg); + reg = (result[final_candidate][7] >> 6) & 0xF; + rtl_set_bbreg(hw, ROFDM0_AGCRSSITABLE, 0x0000F000, reg); + } +} + +static void _rtl92c_phy_save_adda_registers(struct ieee80211_hw *hw, + u32 *addareg, u32 *addabackup, + u32 registernum) +{ + u32 i; + + for (i = 0; i < registernum; i++) + addabackup[i] = rtl_get_bbreg(hw, addareg[i], MASKDWORD); +} + +static void _rtl92c_phy_save_mac_registers(struct ieee80211_hw *hw, + u32 *macreg, u32 *macbackup) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + + for (i = 0; i < (IQK_MAC_REG_NUM - 1); i++) + macbackup[i] = rtl_read_byte(rtlpriv, macreg[i]); + macbackup[i] = rtl_read_dword(rtlpriv, macreg[i]); +} + +static void _rtl92c_phy_reload_adda_registers(struct ieee80211_hw *hw, + u32 *addareg, u32 *addabackup, + u32 regiesternum) +{ + u32 i; + + for (i = 0; i < regiesternum; i++) + rtl_set_bbreg(hw, addareg[i], MASKDWORD, addabackup[i]); +} + +static void _rtl92c_phy_reload_mac_registers(struct ieee80211_hw *hw, + u32 *macreg, u32 *macbackup) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + + for (i = 0; i < (IQK_MAC_REG_NUM - 1); i++) + rtl_write_byte(rtlpriv, macreg[i], (u8) macbackup[i]); + rtl_write_dword(rtlpriv, macreg[i], macbackup[i]); +} + +static void _rtl92c_phy_path_adda_on(struct ieee80211_hw *hw, + u32 *addareg, bool is_patha_on, bool is2t) +{ + u32 pathOn; + u32 i; + + pathOn = is_patha_on ? 0x04db25a4 : 0x0b1b25a4; + if (false == is2t) { + pathOn = 0x0bdb25a0; + rtl_set_bbreg(hw, addareg[0], MASKDWORD, 0x0b1b25a0); + } else { + rtl_set_bbreg(hw, addareg[0], MASKDWORD, pathOn); + } + + for (i = 1; i < IQK_ADDA_REG_NUM; i++) + rtl_set_bbreg(hw, addareg[i], MASKDWORD, pathOn); +} + +static void _rtl92c_phy_mac_setting_calibration(struct ieee80211_hw *hw, + u32 *macreg, u32 *macbackup) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + + rtl_write_byte(rtlpriv, macreg[0], 0x3F); + + for (i = 1; i < (IQK_MAC_REG_NUM - 1); i++) + rtl_write_byte(rtlpriv, macreg[i], + (u8) (macbackup[i] & (~BIT(3)))); + rtl_write_byte(rtlpriv, macreg[i], (u8) (macbackup[i] & (~BIT(5)))); +} + +static void _rtl92c_phy_path_a_standby(struct ieee80211_hw *hw) +{ + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0x0); + rtl_set_bbreg(hw, 0x840, MASKDWORD, 0x00010000); + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0x80800000); +} + +static void _rtl92c_phy_pi_mode_switch(struct ieee80211_hw *hw, bool pi_mode) +{ + u32 mode; + + mode = pi_mode ? 0x01000100 : 0x01000000; + rtl_set_bbreg(hw, 0x820, MASKDWORD, mode); + rtl_set_bbreg(hw, 0x828, MASKDWORD, mode); +} + +static bool _rtl92c_phy_simularity_compare(struct ieee80211_hw *hw, + long result[][8], u8 c1, u8 c2) +{ + u32 i, j, diff, simularity_bitmap, bound; + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + u8 final_candidate[2] = { 0xFF, 0xFF }; + bool bresult = true, is2t = IS_92C_SERIAL(rtlhal->version); + + if (is2t) + bound = 8; + else + bound = 4; + + simularity_bitmap = 0; + + for (i = 0; i < bound; i++) { + diff = (result[c1][i] > result[c2][i]) ? + (result[c1][i] - result[c2][i]) : + (result[c2][i] - result[c1][i]); + + if (diff > MAX_TOLERANCE) { + if ((i == 2 || i == 6) && !simularity_bitmap) { + if (result[c1][i] + result[c1][i + 1] == 0) + final_candidate[(i / 4)] = c2; + else if (result[c2][i] + result[c2][i + 1] == 0) + final_candidate[(i / 4)] = c1; + else + simularity_bitmap = simularity_bitmap | + (1 << i); + } else + simularity_bitmap = + simularity_bitmap | (1 << i); + } + } + + if (simularity_bitmap == 0) { + for (i = 0; i < (bound / 4); i++) { + if (final_candidate[i] != 0xFF) { + for (j = i * 4; j < (i + 1) * 4 - 2; j++) + result[3][j] = + result[final_candidate[i]][j]; + bresult = false; + } + } + return bresult; + } else if (!(simularity_bitmap & 0x0F)) { + for (i = 0; i < 4; i++) + result[3][i] = result[c1][i]; + return false; + } else if (!(simularity_bitmap & 0xF0) && is2t) { + for (i = 4; i < 8; i++) + result[3][i] = result[c1][i]; + return false; + } else { + return false; + } + +} + +static void _rtl92c_phy_iq_calibrate(struct ieee80211_hw *hw, + long result[][8], u8 t, bool is2t) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 i; + u8 patha_ok, pathb_ok; + u32 adda_reg[IQK_ADDA_REG_NUM] = { + 0x85c, 0xe6c, 0xe70, 0xe74, + 0xe78, 0xe7c, 0xe80, 0xe84, + 0xe88, 0xe8c, 0xed0, 0xed4, + 0xed8, 0xedc, 0xee0, 0xeec + }; + + u32 iqk_mac_reg[IQK_MAC_REG_NUM] = { + 0x522, 0x550, 0x551, 0x040 + }; + + const u32 retrycount = 2; + + if (t == 0) { + /* dummy read */ + rtl_get_bbreg(hw, 0x800, MASKDWORD); + + _rtl92c_phy_save_adda_registers(hw, adda_reg, + rtlphy->adda_backup, 16); + _rtl92c_phy_save_mac_registers(hw, iqk_mac_reg, + rtlphy->iqk_mac_backup); + } + _rtl92c_phy_path_adda_on(hw, adda_reg, true, is2t); + if (t == 0) { + rtlphy->rfpi_enable = (u8) rtl_get_bbreg(hw, + RFPGA0_XA_HSSIPARAMETER1, + BIT(8)); + } + + if (!rtlphy->rfpi_enable) + _rtl92c_phy_pi_mode_switch(hw, true); + if (t == 0) { + rtlphy->reg_c04 = rtl_get_bbreg(hw, 0xc04, MASKDWORD); + rtlphy->reg_c08 = rtl_get_bbreg(hw, 0xc08, MASKDWORD); + rtlphy->reg_874 = rtl_get_bbreg(hw, 0x874, MASKDWORD); + } + rtl_set_bbreg(hw, 0xc04, MASKDWORD, 0x03a05600); + rtl_set_bbreg(hw, 0xc08, MASKDWORD, 0x000800e4); + rtl_set_bbreg(hw, 0x874, MASKDWORD, 0x22204000); + if (is2t) { + rtl_set_bbreg(hw, 0x840, MASKDWORD, 0x00010000); + rtl_set_bbreg(hw, 0x844, MASKDWORD, 0x00010000); + } + _rtl92c_phy_mac_setting_calibration(hw, iqk_mac_reg, + rtlphy->iqk_mac_backup); + rtl_set_bbreg(hw, 0xb68, MASKDWORD, 0x00080000); + if (is2t) + rtl_set_bbreg(hw, 0xb6c, MASKDWORD, 0x00080000); + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0x80800000); + rtl_set_bbreg(hw, 0xe40, MASKDWORD, 0x01007c00); + rtl_set_bbreg(hw, 0xe44, MASKDWORD, 0x01004800); + for (i = 0; i < retrycount; i++) { + patha_ok = _rtl92c_phy_path_a_iqk(hw, is2t); + if (patha_ok == 0x03) { + result[t][0] = (rtl_get_bbreg(hw, 0xe94, MASKDWORD) & + 0x3FF0000) >> 16; + result[t][1] = (rtl_get_bbreg(hw, 0xe9c, MASKDWORD) & + 0x3FF0000) >> 16; + result[t][2] = (rtl_get_bbreg(hw, 0xea4, MASKDWORD) & + 0x3FF0000) >> 16; + result[t][3] = (rtl_get_bbreg(hw, 0xeac, MASKDWORD) & + 0x3FF0000) >> 16; + break; + } else if (i == (retrycount - 1) && patha_ok == 0x01) + + result[t][0] = (rtl_get_bbreg(hw, 0xe94, + MASKDWORD) & 0x3FF0000) >> + 16; + result[t][1] = + (rtl_get_bbreg(hw, 0xe9c, MASKDWORD) & 0x3FF0000) >> 16; + + } + + if (is2t) { + _rtl92c_phy_path_a_standby(hw); + _rtl92c_phy_path_adda_on(hw, adda_reg, false, is2t); + for (i = 0; i < retrycount; i++) { + pathb_ok = _rtl92c_phy_path_b_iqk(hw); + if (pathb_ok == 0x03) { + result[t][4] = (rtl_get_bbreg(hw, + 0xeb4, + MASKDWORD) & + 0x3FF0000) >> 16; + result[t][5] = + (rtl_get_bbreg(hw, 0xebc, MASKDWORD) & + 0x3FF0000) >> 16; + result[t][6] = + (rtl_get_bbreg(hw, 0xec4, MASKDWORD) & + 0x3FF0000) >> 16; + result[t][7] = + (rtl_get_bbreg(hw, 0xecc, MASKDWORD) & + 0x3FF0000) >> 16; + break; + } else if (i == (retrycount - 1) && pathb_ok == 0x01) { + result[t][4] = (rtl_get_bbreg(hw, + 0xeb4, + MASKDWORD) & + 0x3FF0000) >> 16; + } + result[t][5] = (rtl_get_bbreg(hw, 0xebc, MASKDWORD) & + 0x3FF0000) >> 16; + } + } + rtl_set_bbreg(hw, 0xc04, MASKDWORD, rtlphy->reg_c04); + rtl_set_bbreg(hw, 0x874, MASKDWORD, rtlphy->reg_874); + rtl_set_bbreg(hw, 0xc08, MASKDWORD, rtlphy->reg_c08); + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0); + rtl_set_bbreg(hw, 0x840, MASKDWORD, 0x00032ed3); + if (is2t) + rtl_set_bbreg(hw, 0x844, MASKDWORD, 0x00032ed3); + if (t != 0) { + if (!rtlphy->rfpi_enable) + _rtl92c_phy_pi_mode_switch(hw, false); + _rtl92c_phy_reload_adda_registers(hw, adda_reg, + rtlphy->adda_backup, 16); + _rtl92c_phy_reload_mac_registers(hw, iqk_mac_reg, + rtlphy->iqk_mac_backup); + } +} + +static void _rtl92c_phy_ap_calibrate(struct ieee80211_hw *hw, + char delta, bool is2t) +{ +#if 0 /* This routine is deliberately dummied out for later fixes */ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + + u32 reg_d[PATH_NUM]; + u32 tmpreg, index, offset, path, i, pathbound = PATH_NUM, apkbound; + + u32 bb_backup[APK_BB_REG_NUM]; + u32 bb_reg[APK_BB_REG_NUM] = { + 0x904, 0xc04, 0x800, 0xc08, 0x874 + }; + u32 bb_ap_mode[APK_BB_REG_NUM] = { + 0x00000020, 0x00a05430, 0x02040000, + 0x000800e4, 0x00204000 + }; + u32 bb_normal_ap_mode[APK_BB_REG_NUM] = { + 0x00000020, 0x00a05430, 0x02040000, + 0x000800e4, 0x22204000 + }; + + u32 afe_backup[APK_AFE_REG_NUM]; + u32 afe_reg[APK_AFE_REG_NUM] = { + 0x85c, 0xe6c, 0xe70, 0xe74, 0xe78, + 0xe7c, 0xe80, 0xe84, 0xe88, 0xe8c, + 0xed0, 0xed4, 0xed8, 0xedc, 0xee0, + 0xeec + }; + + u32 mac_backup[IQK_MAC_REG_NUM]; + u32 mac_reg[IQK_MAC_REG_NUM] = { + 0x522, 0x550, 0x551, 0x040 + }; + + u32 apk_rf_init_value[PATH_NUM][APK_BB_REG_NUM] = { + {0x0852c, 0x1852c, 0x5852c, 0x1852c, 0x5852c}, + {0x2852e, 0x0852e, 0x3852e, 0x0852e, 0x0852e} + }; + + u32 apk_normal_rf_init_value[PATH_NUM][APK_BB_REG_NUM] = { + {0x0852c, 0x0a52c, 0x3a52c, 0x5a52c, 0x5a52c}, + {0x0852c, 0x0a52c, 0x5a52c, 0x5a52c, 0x5a52c} + }; + + u32 apk_rf_value_0[PATH_NUM][APK_BB_REG_NUM] = { + {0x52019, 0x52014, 0x52013, 0x5200f, 0x5208d}, + {0x5201a, 0x52019, 0x52016, 0x52033, 0x52050} + }; + + u32 apk_normal_rf_value_0[PATH_NUM][APK_BB_REG_NUM] = { + {0x52019, 0x52017, 0x52010, 0x5200d, 0x5206a}, + {0x52019, 0x52017, 0x52010, 0x5200d, 0x5206a} + }; + + u32 afe_on_off[PATH_NUM] = { + 0x04db25a4, 0x0b1b25a4 + }; + + const u32 apk_offset[PATH_NUM] = { 0xb68, 0xb6c }; + + u32 apk_normal_offset[PATH_NUM] = { 0xb28, 0xb98 }; + + u32 apk_value[PATH_NUM] = { 0x92fc0000, 0x12fc0000 }; + + u32 apk_normal_value[PATH_NUM] = { 0x92680000, 0x12680000 }; + + const char apk_delta_mapping[APK_BB_REG_NUM][13] = { + {-4, -3, -2, -2, -1, -1, 0, 1, 2, 3, 4, 5, 6}, + {-4, -3, -2, -2, -1, -1, 0, 1, 2, 3, 4, 5, 6}, + {-6, -4, -2, -2, -1, -1, 0, 1, 2, 3, 4, 5, 6}, + {-1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6}, + {-11, -9, -7, -5, -3, -1, 0, 0, 0, 0, 0, 0, 0} + }; + + const u32 apk_normal_setting_value_1[13] = { + 0x01017018, 0xf7ed8f84, 0x1b1a1816, 0x2522201e, 0x322e2b28, + 0x433f3a36, 0x5b544e49, 0x7b726a62, 0xa69a8f84, 0xdfcfc0b3, + 0x12680000, 0x00880000, 0x00880000 + }; + + const u32 apk_normal_setting_value_2[16] = { + 0x01c7021d, 0x01670183, 0x01000123, 0x00bf00e2, 0x008d00a3, + 0x0068007b, 0x004d0059, 0x003a0042, 0x002b0031, 0x001f0025, + 0x0017001b, 0x00110014, 0x000c000f, 0x0009000b, 0x00070008, + 0x00050006 + }; + + u32 apk_result[PATH_NUM][APK_BB_REG_NUM]; + + long bb_offset, delta_v, delta_offset; + + if (!is2t) + pathbound = 1; + + return; + + for (index = 0; index < PATH_NUM; index++) { + apk_offset[index] = apk_normal_offset[index]; + apk_value[index] = apk_normal_value[index]; + afe_on_off[index] = 0x6fdb25a4; + } + + for (index = 0; index < APK_BB_REG_NUM; index++) { + for (path = 0; path < pathbound; path++) { + apk_rf_init_value[path][index] = + apk_normal_rf_init_value[path][index]; + apk_rf_value_0[path][index] = + apk_normal_rf_value_0[path][index]; + } + bb_ap_mode[index] = bb_normal_ap_mode[index]; + + apkbound = 6; + } + + for (index = 0; index < APK_BB_REG_NUM; index++) { + if (index == 0) + continue; + bb_backup[index] = rtl_get_bbreg(hw, bb_reg[index], MASKDWORD); + } + + _rtl92c_phy_save_mac_registers(hw, mac_reg, mac_backup); + + _rtl92c_phy_save_adda_registers(hw, afe_reg, afe_backup, 16); + + for (path = 0; path < pathbound; path++) { + if (path == RF90_PATH_A) { + offset = 0xb00; + for (index = 0; index < 11; index++) { + rtl_set_bbreg(hw, offset, MASKDWORD, + apk_normal_setting_value_1 + [index]); + + offset += 0x04; + } + + rtl_set_bbreg(hw, 0xb98, MASKDWORD, 0x12680000); + + offset = 0xb68; + for (; index < 13; index++) { + rtl_set_bbreg(hw, offset, MASKDWORD, + apk_normal_setting_value_1 + [index]); + + offset += 0x04; + } + + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0x40000000); + + offset = 0xb00; + for (index = 0; index < 16; index++) { + rtl_set_bbreg(hw, offset, MASKDWORD, + apk_normal_setting_value_2 + [index]); + + offset += 0x04; + } + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0x00000000); + } else if (path == RF90_PATH_B) { + offset = 0xb70; + for (index = 0; index < 10; index++) { + rtl_set_bbreg(hw, offset, MASKDWORD, + apk_normal_setting_value_1 + [index]); + + offset += 0x04; + } + rtl_set_bbreg(hw, 0xb28, MASKDWORD, 0x12680000); + rtl_set_bbreg(hw, 0xb98, MASKDWORD, 0x12680000); + + offset = 0xb68; + index = 11; + for (; index < 13; index++) { + rtl_set_bbreg(hw, offset, MASKDWORD, + apk_normal_setting_value_1 + [index]); + + offset += 0x04; + } + + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0x40000000); + + offset = 0xb60; + for (index = 0; index < 16; index++) { + rtl_set_bbreg(hw, offset, MASKDWORD, + apk_normal_setting_value_2 + [index]); + + offset += 0x04; + } + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0x00000000); + } + + reg_d[path] = rtl_get_rfreg(hw, (enum radio_path)path, + 0xd, MASKDWORD); + + for (index = 0; index < APK_AFE_REG_NUM; index++) + rtl_set_bbreg(hw, afe_reg[index], MASKDWORD, + afe_on_off[path]); + + if (path == RF90_PATH_A) { + for (index = 0; index < APK_BB_REG_NUM; index++) { + if (index == 0) + continue; + rtl_set_bbreg(hw, bb_reg[index], MASKDWORD, + bb_ap_mode[index]); + } + } + + _rtl92c_phy_mac_setting_calibration(hw, mac_reg, mac_backup); + + if (path == 0) { + rtl_set_rfreg(hw, RF90_PATH_B, 0x0, MASKDWORD, 0x10000); + } else { + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, MASKDWORD, + 0x10000); + rtl_set_rfreg(hw, RF90_PATH_A, 0x10, MASKDWORD, + 0x1000f); + rtl_set_rfreg(hw, RF90_PATH_A, 0x11, MASKDWORD, + 0x20103); + } + + delta_offset = ((delta + 14) / 2); + if (delta_offset < 0) + delta_offset = 0; + else if (delta_offset > 12) + delta_offset = 12; + + for (index = 0; index < APK_BB_REG_NUM; index++) { + if (index != 1) + continue; + + tmpreg = apk_rf_init_value[path][index]; + + if (!rtlefuse->apk_thermalmeterignore) { + bb_offset = (tmpreg & 0xF0000) >> 16; + + if (!(tmpreg & BIT(15))) + bb_offset = -bb_offset; + + delta_v = + apk_delta_mapping[index][delta_offset]; + + bb_offset += delta_v; + + if (bb_offset < 0) { + tmpreg = tmpreg & (~BIT(15)); + bb_offset = -bb_offset; + } else { + tmpreg = tmpreg | BIT(15); + } + + tmpreg = + (tmpreg & 0xFFF0FFFF) | (bb_offset << 16); + } + + rtl_set_rfreg(hw, (enum radio_path)path, 0xc, + MASKDWORD, 0x8992e); + rtl_set_rfreg(hw, (enum radio_path)path, 0x0, + MASKDWORD, apk_rf_value_0[path][index]); + rtl_set_rfreg(hw, (enum radio_path)path, 0xd, + MASKDWORD, tmpreg); + + i = 0; + do { + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0x80000000); + rtl_set_bbreg(hw, apk_offset[path], + MASKDWORD, apk_value[0]); + RTPRINT(rtlpriv, FINIT, INIT_IQK, + ("PHY_APCalibrate() offset 0x%x " + "value 0x%x\n", + apk_offset[path], + rtl_get_bbreg(hw, apk_offset[path], + MASKDWORD))); + + mdelay(3); + + rtl_set_bbreg(hw, apk_offset[path], + MASKDWORD, apk_value[1]); + RTPRINT(rtlpriv, FINIT, INIT_IQK, + ("PHY_APCalibrate() offset 0x%x " + "value 0x%x\n", + apk_offset[path], + rtl_get_bbreg(hw, apk_offset[path], + MASKDWORD))); + + mdelay(20); + + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0x00000000); + + if (path == RF90_PATH_A) + tmpreg = rtl_get_bbreg(hw, 0xbd8, + 0x03E00000); + else + tmpreg = rtl_get_bbreg(hw, 0xbd8, + 0xF8000000); + + RTPRINT(rtlpriv, FINIT, INIT_IQK, + ("PHY_APCalibrate() offset " + "0xbd8[25:21] %x\n", tmpreg)); + + i++; + + } while (tmpreg > apkbound && i < 4); + + apk_result[path][index] = tmpreg; + } + } + + _rtl92c_phy_reload_mac_registers(hw, mac_reg, mac_backup); + + for (index = 0; index < APK_BB_REG_NUM; index++) { + if (index == 0) + continue; + rtl_set_bbreg(hw, bb_reg[index], MASKDWORD, bb_backup[index]); + } + + _rtl92c_phy_reload_adda_registers(hw, afe_reg, afe_backup, 16); + + for (path = 0; path < pathbound; path++) { + rtl_set_rfreg(hw, (enum radio_path)path, 0xd, + MASKDWORD, reg_d[path]); + + if (path == RF90_PATH_B) { + rtl_set_rfreg(hw, RF90_PATH_A, 0x10, MASKDWORD, + 0x1000f); + rtl_set_rfreg(hw, RF90_PATH_A, 0x11, MASKDWORD, + 0x20101); + } + + if (apk_result[path][1] > 6) + apk_result[path][1] = 6; + } + + for (path = 0; path < pathbound; path++) { + rtl_set_rfreg(hw, (enum radio_path)path, 0x3, MASKDWORD, + ((apk_result[path][1] << 15) | + (apk_result[path][1] << 10) | + (apk_result[path][1] << 5) | + apk_result[path][1])); + + if (path == RF90_PATH_A) + rtl_set_rfreg(hw, (enum radio_path)path, 0x4, MASKDWORD, + ((apk_result[path][1] << 15) | + (apk_result[path][1] << 10) | + (0x00 << 5) | 0x05)); + else + rtl_set_rfreg(hw, (enum radio_path)path, 0x4, MASKDWORD, + ((apk_result[path][1] << 15) | + (apk_result[path][1] << 10) | + (0x02 << 5) | 0x05)); + + rtl_set_rfreg(hw, (enum radio_path)path, 0xe, MASKDWORD, + ((0x08 << 15) | (0x08 << 10) | (0x08 << 5) | + 0x08)); + + } + rtlphy->b_apk_done = true; +#endif +} + +static void _rtl92c_phy_set_rfpath_switch(struct ieee80211_hw *hw, + bool bmain, bool is2t) +{ + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (is_hal_stop(rtlhal)) { + rtl_set_bbreg(hw, REG_LEDCFG0, BIT(23), 0x01); + rtl_set_bbreg(hw, rFPGA0_XAB_RFPARAMETER, BIT(13), 0x01); + } + if (is2t) { + if (bmain) + rtl_set_bbreg(hw, RFPGA0_XB_RFINTERFACEOE, + BIT(5) | BIT(6), 0x1); + else + rtl_set_bbreg(hw, RFPGA0_XB_RFINTERFACEOE, + BIT(5) | BIT(6), 0x2); + } else { + if (bmain) + rtl_set_bbreg(hw, RFPGA0_XA_RFINTERFACEOE, 0x300, 0x2); + else + rtl_set_bbreg(hw, RFPGA0_XA_RFINTERFACEOE, 0x300, 0x1); + + } + +} + +#undef IQK_ADDA_REG_NUM +#undef IQK_DELAY_TIME + +void rtl92c_phy_iq_calibrate(struct ieee80211_hw *hw, bool recovery) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + long result[4][8]; + u8 i, final_candidate; + bool patha_ok, pathb_ok; + long reg_e94, reg_e9c, reg_ea4, reg_eb4, reg_ebc, reg_ec4, reg_tmp = 0; + bool is12simular, is13simular, is23simular; + bool start_conttx = false, singletone = false; + u32 iqk_bb_reg[10] = { + ROFDM0_XARXIQIMBALANCE, + ROFDM0_XBRXIQIMBALANCE, + ROFDM0_ECCATHRESHOLD, + ROFDM0_AGCRSSITABLE, + ROFDM0_XATXIQIMBALANCE, + ROFDM0_XBTXIQIMBALANCE, + ROFDM0_XCTXIQIMBALANCE, + ROFDM0_XCTXAFE, + ROFDM0_XDTXAFE, + ROFDM0_RXIQEXTANTA + }; + + if (recovery) { + _rtl92c_phy_reload_adda_registers(hw, + iqk_bb_reg, + rtlphy->iqk_bb_backup, 10); + return; + } + if (start_conttx || singletone) + return; + for (i = 0; i < 8; i++) { + result[0][i] = 0; + result[1][i] = 0; + result[2][i] = 0; + result[3][i] = 0; + } + final_candidate = 0xff; + patha_ok = false; + pathb_ok = false; + is12simular = false; + is23simular = false; + is13simular = false; + for (i = 0; i < 3; i++) { + if (IS_92C_SERIAL(rtlhal->version)) + _rtl92c_phy_iq_calibrate(hw, result, i, true); + else + _rtl92c_phy_iq_calibrate(hw, result, i, false); + if (i == 1) { + is12simular = _rtl92c_phy_simularity_compare(hw, + result, 0, + 1); + if (is12simular) { + final_candidate = 0; + break; + } + } + if (i == 2) { + is13simular = _rtl92c_phy_simularity_compare(hw, + result, 0, + 2); + if (is13simular) { + final_candidate = 0; + break; + } + is23simular = _rtl92c_phy_simularity_compare(hw, + result, 1, + 2); + if (is23simular) + final_candidate = 1; + else { + for (i = 0; i < 8; i++) + reg_tmp += result[3][i]; + + if (reg_tmp != 0) + final_candidate = 3; + else + final_candidate = 0xFF; + } + } + } + for (i = 0; i < 4; i++) { + reg_e94 = result[i][0]; + reg_e9c = result[i][1]; + reg_ea4 = result[i][2]; + reg_eb4 = result[i][4]; + reg_ebc = result[i][5]; + reg_ec4 = result[i][6]; + } + if (final_candidate != 0xff) { + rtlphy->reg_e94 = reg_e94 = result[final_candidate][0]; + rtlphy->reg_e9c = reg_e9c = result[final_candidate][1]; + reg_ea4 = result[final_candidate][2]; + rtlphy->reg_eb4 = reg_eb4 = result[final_candidate][4]; + rtlphy->reg_ebc = reg_ebc = result[final_candidate][5]; + reg_ec4 = result[final_candidate][6]; + patha_ok = pathb_ok = true; + } else { + rtlphy->reg_e94 = rtlphy->reg_eb4 = 0x100; + rtlphy->reg_e9c = rtlphy->reg_ebc = 0x0; + } + if (reg_e94 != 0) /*&&(reg_ea4 != 0) */ + _rtl92c_phy_path_a_fill_iqk_matrix(hw, patha_ok, result, + final_candidate, + (reg_ea4 == 0)); + if (IS_92C_SERIAL(rtlhal->version)) { + if (reg_eb4 != 0) /*&&(reg_ec4 != 0) */ + _rtl92c_phy_path_b_fill_iqk_matrix(hw, pathb_ok, + result, + final_candidate, + (reg_ec4 == 0)); + } + _rtl92c_phy_save_adda_registers(hw, iqk_bb_reg, + rtlphy->iqk_bb_backup, 10); +} +EXPORT_SYMBOL(rtl92c_phy_iq_calibrate); + +void rtl92c_phy_lc_calibrate(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + bool start_conttx = false, singletone = false; + + if (start_conttx || singletone) + return; + if (IS_92C_SERIAL(rtlhal->version)) + rtlpriv->cfg->ops->phy_lc_calibrate(hw, true); + else + rtlpriv->cfg->ops->phy_lc_calibrate(hw, false); +} +EXPORT_SYMBOL(rtl92c_phy_lc_calibrate); + +void rtl92c_phy_ap_calibrate(struct ieee80211_hw *hw, char delta) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (rtlphy->apk_done) + return; + if (IS_92C_SERIAL(rtlhal->version)) + _rtl92c_phy_ap_calibrate(hw, delta, true); + else + _rtl92c_phy_ap_calibrate(hw, delta, false); +} +EXPORT_SYMBOL(rtl92c_phy_ap_calibrate); + +void rtl92c_phy_set_rfpath_switch(struct ieee80211_hw *hw, bool bmain) +{ + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (IS_92C_SERIAL(rtlhal->version)) + _rtl92c_phy_set_rfpath_switch(hw, bmain, true); + else + _rtl92c_phy_set_rfpath_switch(hw, bmain, false); +} +EXPORT_SYMBOL(rtl92c_phy_set_rfpath_switch); + +bool rtl92c_phy_set_io_cmd(struct ieee80211_hw *hw, enum io_type iotype) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + bool postprocessing = false; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "-->IO Cmd(%#x), set_io_inprogress(%d)\n", + iotype, rtlphy->set_io_inprogress); + do { + switch (iotype) { + case IO_CMD_RESUME_DM_BY_SCAN: + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "[IO CMD] Resume DM after scan\n"); + postprocessing = true; + break; + case IO_CMD_PAUSE_DM_BY_SCAN: + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "[IO CMD] Pause DM before scan\n"); + postprocessing = true; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + } while (false); + if (postprocessing && !rtlphy->set_io_inprogress) { + rtlphy->set_io_inprogress = true; + rtlphy->current_io_type = iotype; + } else { + return false; + } + rtl92c_phy_set_io(hw); + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, "<--IO Type(%#x)\n", iotype); + return true; +} +EXPORT_SYMBOL(rtl92c_phy_set_io_cmd); + +void rtl92c_phy_set_io(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct dig_t dm_digtable = rtlpriv->dm_digtable; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "--->Cmd(%#x), set_io_inprogress(%d)\n", + rtlphy->current_io_type, rtlphy->set_io_inprogress); + switch (rtlphy->current_io_type) { + case IO_CMD_RESUME_DM_BY_SCAN: + dm_digtable.cur_igvalue = rtlphy->initgain_backup.xaagccore1; + rtl92c_dm_write_dig(hw); + rtl92c_phy_set_txpower_level(hw, rtlphy->current_channel); + break; + case IO_CMD_PAUSE_DM_BY_SCAN: + rtlphy->initgain_backup.xaagccore1 = dm_digtable.cur_igvalue; + dm_digtable.cur_igvalue = 0x37; + rtl92c_dm_write_dig(hw); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + rtlphy->set_io_inprogress = false; + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, "<---(%#x)\n", + rtlphy->current_io_type); +} +EXPORT_SYMBOL(rtl92c_phy_set_io); + +void rtl92ce_phy_set_rf_on(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_byte(rtlpriv, REG_SPS0_CTRL, 0x2b); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE3); + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x00); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE2); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE3); + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0x00); +} +EXPORT_SYMBOL(rtl92ce_phy_set_rf_on); + +void _rtl92c_phy_set_rf_sleep(struct ieee80211_hw *hw) +{ + u32 u4b_tmp; + u8 delay = 5; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0xFF); + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, RFREG_OFFSET_MASK, 0x00); + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x40); + u4b_tmp = rtl_get_rfreg(hw, RF90_PATH_A, 0, RFREG_OFFSET_MASK); + while (u4b_tmp != 0 && delay > 0) { + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x0); + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, RFREG_OFFSET_MASK, 0x00); + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x40); + u4b_tmp = rtl_get_rfreg(hw, RF90_PATH_A, 0, RFREG_OFFSET_MASK); + delay--; + } + if (delay == 0) { + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x00); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE2); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE3); + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0x00); + RT_TRACE(rtlpriv, COMP_POWER, DBG_TRACE, + "Switch RF timeout !!!\n"); + return; + } + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE2); + rtl_write_byte(rtlpriv, REG_SPS0_CTRL, 0x22); +} +EXPORT_SYMBOL(_rtl92c_phy_set_rf_sleep); diff --git a/drivers/net/wireless/rtlwifi/rtl8192c/phy_common.h b/drivers/net/wireless/rtlwifi/rtl8192c/phy_common.h new file mode 100644 index 0000000..cec10d6 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192c/phy_common.h @@ -0,0 +1,258 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92C_PHY_COMMON_H__ +#define __RTL92C_PHY_COMMON_H__ + +#define MAX_PRECMD_CNT 16 +#define MAX_RFDEPENDCMD_CNT 16 +#define MAX_POSTCMD_CNT 16 + +#define MAX_DOZE_WAITING_TIMES_9x 64 + +#define RT_CANNOT_IO(hw) false +#define HIGHPOWER_RADIOA_ARRAYLEN 22 + +#define IQK_ADDA_REG_NUM 16 +#define MAX_TOLERANCE 5 +#define IQK_DELAY_TIME 1 + +#define APK_BB_REG_NUM 5 +#define APK_AFE_REG_NUM 16 +#define APK_CURVE_REG_NUM 4 +#define PATH_NUM 2 + +#define LOOP_LIMIT 5 +#define MAX_STALL_TIME 50 +#define AntennaDiversityValue 0x80 +#define MAX_TXPWR_IDX_NMODE_92S 63 +#define Reset_Cnt_Limit 3 + +#define IQK_ADDA_REG_NUM 16 +#define IQK_MAC_REG_NUM 4 + +#define IQK_DELAY_TIME 1 +#define RF90_PATH_MAX 2 + +#define CT_OFFSET_MAC_ADDR 0X16 + +#define CT_OFFSET_CCK_TX_PWR_IDX 0x5A +#define CT_OFFSET_HT401S_TX_PWR_IDX 0x60 +#define CT_OFFSET_HT402S_TX_PWR_IDX_DIF 0x66 +#define CT_OFFSET_HT20_TX_PWR_IDX_DIFF 0x69 +#define CT_OFFSET_OFDM_TX_PWR_IDX_DIFF 0x6C + +#define CT_OFFSET_HT40_MAX_PWR_OFFSET 0x6F +#define CT_OFFSET_HT20_MAX_PWR_OFFSET 0x72 + +#define CT_OFFSET_CHANNEL_PLAH 0x75 +#define CT_OFFSET_THERMAL_METER 0x78 +#define CT_OFFSET_RF_OPTION 0x79 +#define CT_OFFSET_VERSION 0x7E +#define CT_OFFSET_CUSTOMER_ID 0x7F + +#define RTL92C_MAX_PATH_NUM 2 +#define LLT_LAST_ENTRY_OF_TX_PKT_BUFFER 255 + +enum swchnlcmd_id { + CMDID_END, + CMDID_SET_TXPOWEROWER_LEVEL, + CMDID_BBREGWRITE10, + CMDID_WRITEPORT_ULONG, + CMDID_WRITEPORT_USHORT, + CMDID_WRITEPORT_UCHAR, + CMDID_RF_WRITEREG, +}; + +struct swchnlcmd { + enum swchnlcmd_id cmdid; + u32 para1; + u32 para2; + u32 msdelay; +}; + +enum hw90_block_e { + HW90_BLOCK_MAC = 0, + HW90_BLOCK_PHY0 = 1, + HW90_BLOCK_PHY1 = 2, + HW90_BLOCK_RF = 3, + HW90_BLOCK_MAXIMUM = 4, +}; + +enum baseband_config_type { + BASEBAND_CONFIG_PHY_REG = 0, + BASEBAND_CONFIG_AGC_TAB = 1, +}; + +enum ra_offset_area { + RA_OFFSET_LEGACY_OFDM1, + RA_OFFSET_LEGACY_OFDM2, + RA_OFFSET_HT_OFDM1, + RA_OFFSET_HT_OFDM2, + RA_OFFSET_HT_OFDM3, + RA_OFFSET_HT_OFDM4, + RA_OFFSET_HT_CCK, +}; + +enum antenna_path { + ANTENNA_NONE, + ANTENNA_D, + ANTENNA_C, + ANTENNA_CD, + ANTENNA_B, + ANTENNA_BD, + ANTENNA_BC, + ANTENNA_BCD, + ANTENNA_A, + ANTENNA_AD, + ANTENNA_AC, + ANTENNA_ACD, + ANTENNA_AB, + ANTENNA_ABD, + ANTENNA_ABC, + ANTENNA_ABCD +}; + +struct r_antenna_select_ofdm { + u32 r_tx_antenna:4; + u32 r_ant_l:4; + u32 r_ant_non_ht:4; + u32 r_ant_ht1:4; + u32 r_ant_ht2:4; + u32 r_ant_ht_s1:4; + u32 r_ant_non_ht_s1:4; + u32 ofdm_txsc:2; + u32 reserved:2; +}; + +struct r_antenna_select_cck { + u8 r_cckrx_enable_2:2; + u8 r_cckrx_enable:2; + u8 r_ccktx_enable:4; +}; + +struct efuse_contents { + u8 mac_addr[ETH_ALEN]; + u8 cck_tx_power_idx[6]; + u8 ht40_1s_tx_power_idx[6]; + u8 ht40_2s_tx_power_idx_diff[3]; + u8 ht20_tx_power_idx_diff[3]; + u8 ofdm_tx_power_idx_diff[3]; + u8 ht40_max_power_offset[3]; + u8 ht20_max_power_offset[3]; + u8 channel_plan; + u8 thermal_meter; + u8 rf_option[5]; + u8 version; + u8 oem_id; + u8 regulatory; +}; + +struct tx_power_struct { + u8 cck[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 ht40_1s[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 ht40_2s[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 ht20_diff[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 legacy_ht_diff[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 legacy_ht_txpowerdiff; + u8 groupht20[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 groupht40[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 pwrgroup_cnt; + u32 mcs_original_offset[4][16]; +}; + +u32 rtl92c_phy_query_bb_reg(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask); +void rtl92c_phy_set_bb_reg(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask, u32 data); +u32 rtl92c_phy_query_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, + u32 bitmask); +bool rtl92c_phy_mac_config(struct ieee80211_hw *hw); +bool rtl92c_phy_bb_config(struct ieee80211_hw *hw); +bool rtl92c_phy_rf_config(struct ieee80211_hw *hw); +bool rtl92c_phy_config_rf_with_feaderfile(struct ieee80211_hw *hw, + enum radio_path rfpath); +void rtl92c_phy_get_hw_reg_originalvalue(struct ieee80211_hw *hw); +void rtl92c_phy_get_txpower_level(struct ieee80211_hw *hw, + long *powerlevel); +void rtl92c_phy_set_txpower_level(struct ieee80211_hw *hw, u8 channel); +bool rtl92c_phy_update_txpower_dbm(struct ieee80211_hw *hw, + long power_indbm); +void rtl92c_phy_scan_operation_backup(struct ieee80211_hw *hw, + u8 operation); +void rtl92c_phy_set_bw_mode(struct ieee80211_hw *hw, + enum nl80211_channel_type ch_type); +void rtl92c_phy_sw_chnl_callback(struct ieee80211_hw *hw); +u8 rtl92c_phy_sw_chnl(struct ieee80211_hw *hw); +void rtl92c_phy_iq_calibrate(struct ieee80211_hw *hw, bool b_recovery); +void rtl92c_phy_set_beacon_hw_reg(struct ieee80211_hw *hw, + u16 beaconinterval); +void rtl92c_phy_ap_calibrate(struct ieee80211_hw *hw, char delta); +void rtl92c_phy_lc_calibrate(struct ieee80211_hw *hw); +void rtl92c_phy_set_rfpath_switch(struct ieee80211_hw *hw, bool bmain); +bool rtl92c_phy_config_rf_with_headerfile(struct ieee80211_hw *hw, + enum radio_path rfpath); +bool rtl8192_phy_check_is_legal_rfpath(struct ieee80211_hw *hw, + u32 rfpath); +bool rtl92c_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state); +void rtl92ce_phy_set_rf_on(struct ieee80211_hw *hw); +void rtl92c_phy_set_io(struct ieee80211_hw *hw); +void rtl92c_bb_block_on(struct ieee80211_hw *hw); +u32 _rtl92c_phy_calculate_bit_shift(u32 bitmask); +long _rtl92c_phy_txpwr_idx_to_dbm(struct ieee80211_hw *hw, + enum wireless_mode wirelessmode, + u8 txpwridx); +u8 _rtl92c_phy_dbm_to_txpwr_Idx(struct ieee80211_hw *hw, + enum wireless_mode wirelessmode, + long power_indbm); +void _rtl92c_phy_init_bb_rf_register_definition(struct ieee80211_hw *hw); +void _rtl92c_phy_set_rf_sleep(struct ieee80211_hw *hw); +bool _rtl92c_phy_sw_chnl_step_by_step(struct ieee80211_hw *hw, + u8 channel, u8 *stage, u8 *step, + u32 *delay); +u8 rtl92c_bt_rssi_state_change(struct ieee80211_hw *hw); +u32 _rtl92c_phy_fw_rf_serial_read(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset); +void _rtl92c_phy_fw_rf_serial_write(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset, + u32 data); +u32 _rtl92c_phy_rf_serial_read(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset); +void _rtl92c_phy_rf_serial_write(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset, + u32 data); +bool _rtl92c_phy_bb8192c_config_parafile(struct ieee80211_hw *hw); +void _rtl92c_store_pwrIndex_diffrate_offset(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask, + u32 data); +bool rtl92c_phy_set_io_cmd(struct ieee80211_hw *hw, enum io_type iotype); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/Makefile b/drivers/net/wireless/rtlwifi/rtl8192ce/Makefile new file mode 100644 index 0000000..6da664b --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/Makefile @@ -0,0 +1,13 @@ +rtl8192ce-objs := \ + dm.o \ + hw.o \ + led.o \ + phy.o \ + rf.o \ + sw.o \ + table.o \ + trx.o + +obj-$(CPTCFG_RTL8192CE) += rtl8192ce.o + +ccflags-y += -D__CHECK_ENDIAN__ diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/def.h b/drivers/net/wireless/rtlwifi/rtl8192ce/def.h new file mode 100644 index 0000000..3cfa1bb --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/def.h @@ -0,0 +1,253 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92C_DEF_H__ +#define __RTL92C_DEF_H__ + +#define HAL_RETRY_LIMIT_INFRA 48 +#define HAL_RETRY_LIMIT_AP_ADHOC 7 + +#define PHY_RSSI_SLID_WIN_MAX 100 +#define PHY_LINKQUALITY_SLID_WIN_MAX 20 +#define PHY_BEACON_RSSI_SLID_WIN_MAX 10 + +#define RESET_DELAY_8185 20 + +#define RT_IBSS_INT_MASKS (IMR_BCNINT | IMR_TBDOK | IMR_TBDER) +#define RT_AC_INT_MASKS (IMR_VIDOK | IMR_VODOK | IMR_BEDOK|IMR_BKDOK) + +#define NUM_OF_FIRMWARE_QUEUE 10 +#define NUM_OF_PAGES_IN_FW 0x100 +#define NUM_OF_PAGE_IN_FW_QUEUE_BK 0x07 +#define NUM_OF_PAGE_IN_FW_QUEUE_BE 0x07 +#define NUM_OF_PAGE_IN_FW_QUEUE_VI 0x07 +#define NUM_OF_PAGE_IN_FW_QUEUE_VO 0x07 +#define NUM_OF_PAGE_IN_FW_QUEUE_HCCA 0x0 +#define NUM_OF_PAGE_IN_FW_QUEUE_CMD 0x0 +#define NUM_OF_PAGE_IN_FW_QUEUE_MGNT 0x02 +#define NUM_OF_PAGE_IN_FW_QUEUE_HIGH 0x02 +#define NUM_OF_PAGE_IN_FW_QUEUE_BCN 0x2 +#define NUM_OF_PAGE_IN_FW_QUEUE_PUB 0xA1 + +#define NUM_OF_PAGE_IN_FW_QUEUE_BK_DTM 0x026 +#define NUM_OF_PAGE_IN_FW_QUEUE_BE_DTM 0x048 +#define NUM_OF_PAGE_IN_FW_QUEUE_VI_DTM 0x048 +#define NUM_OF_PAGE_IN_FW_QUEUE_VO_DTM 0x026 +#define NUM_OF_PAGE_IN_FW_QUEUE_PUB_DTM 0x00 + +#define MAX_LINES_HWCONFIG_TXT 1000 +#define MAX_BYTES_LINE_HWCONFIG_TXT 256 + +#define SW_THREE_WIRE 0 +#define HW_THREE_WIRE 2 + +#define BT_DEMO_BOARD 0 +#define BT_QA_BOARD 1 +#define BT_FPGA 2 + +#define RX_SMOOTH_FACTOR 20 + +#define HAL_PRIME_CHNL_OFFSET_DONT_CARE 0 +#define HAL_PRIME_CHNL_OFFSET_LOWER 1 +#define HAL_PRIME_CHNL_OFFSET_UPPER 2 + +#define MAX_H2C_QUEUE_NUM 10 + +#define RX_MPDU_QUEUE 0 +#define RX_CMD_QUEUE 1 +#define RX_MAX_QUEUE 2 +#define AC2QUEUEID(_AC) (_AC) + +#define C2H_RX_CMD_HDR_LEN 8 +#define GET_C2H_CMD_CMD_LEN(__prxhdr) \ + LE_BITS_TO_4BYTE((__prxhdr), 0, 16) +#define GET_C2H_CMD_ELEMENT_ID(__prxhdr) \ + LE_BITS_TO_4BYTE((__prxhdr), 16, 8) +#define GET_C2H_CMD_CMD_SEQ(__prxhdr) \ + LE_BITS_TO_4BYTE((__prxhdr), 24, 7) +#define GET_C2H_CMD_CONTINUE(__prxhdr) \ + LE_BITS_TO_4BYTE((__prxhdr), 31, 1) +#define GET_C2H_CMD_CONTENT(__prxhdr) \ + ((u8 *)(__prxhdr) + C2H_RX_CMD_HDR_LEN) + +#define GET_C2H_CMD_FEEDBACK_ELEMENT_ID(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE((__pcmdfbhdr), 0, 8) +#define GET_C2H_CMD_FEEDBACK_CCX_LEN(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE((__pcmdfbhdr), 8, 8) +#define GET_C2H_CMD_FEEDBACK_CCX_CMD_CNT(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE((__pcmdfbhdr), 16, 16) +#define GET_C2H_CMD_FEEDBACK_CCX_MAC_ID(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 0, 5) +#define GET_C2H_CMD_FEEDBACK_CCX_VALID(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 7, 1) +#define GET_C2H_CMD_FEEDBACK_CCX_RETRY_CNT(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 8, 5) +#define GET_C2H_CMD_FEEDBACK_CCX_TOK(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 15, 1) +#define GET_C2H_CMD_FEEDBACK_CCX_QSEL(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 16, 4) +#define GET_C2H_CMD_FEEDBACK_CCX_SEQ(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 20, 12) + +#define CHIP_VER_B BIT(4) +#define CHIP_BONDING_IDENTIFIER(_value) (((_value) >> 22) & 0x3) +#define CHIP_BONDING_92C_1T2R 0x1 +#define RF_TYPE_1T2R BIT(1) +#define CHIP_92C_BITMASK BIT(0) +#define CHIP_UNKNOWN BIT(7) +#define CHIP_92C_1T2R 0x03 +#define CHIP_92C 0x01 +#define CHIP_88C 0x00 + +enum version_8192c { + VERSION_A_CHIP_92C = 0x01, + VERSION_A_CHIP_88C = 0x00, + VERSION_B_CHIP_92C = 0x11, + VERSION_B_CHIP_88C = 0x10, + VERSION_TEST_CHIP_88C = 0x00, + VERSION_TEST_CHIP_92C = 0x01, + VERSION_NORMAL_TSMC_CHIP_88C = 0x10, + VERSION_NORMAL_TSMC_CHIP_92C = 0x11, + VERSION_NORMAL_TSMC_CHIP_92C_1T2R = 0x13, + VERSION_NORMAL_UMC_CHIP_88C_A_CUT = 0x30, + VERSION_NORMAL_UMC_CHIP_92C_A_CUT = 0x31, + VERSION_NORMAL_UMC_CHIP_92C_1T2R_A_CUT = 0x33, + VERSION_NORMA_UMC_CHIP_8723_1T1R_A_CUT = 0x34, + VERSION_NORMA_UMC_CHIP_8723_1T1R_B_CUT = 0x3c, + VERSION_NORMAL_UMC_CHIP_88C_B_CUT = 0x70, + VERSION_NORMAL_UMC_CHIP_92C_B_CUT = 0x71, + VERSION_NORMAL_UMC_CHIP_92C_1T2R_B_CUT = 0x73, + VERSION_UNKNOWN = 0x88, +}; + +#define CUT_VERSION_MASK (BIT(6)|BIT(7)) +#define CHIP_VENDOR_UMC BIT(5) +#define CHIP_VENDOR_UMC_B_CUT BIT(6) /* Chip version for ECO */ +#define IS_VENDOR_UMC_A_CUT(version) ((IS_CHIP_VENDOR_UMC(version)) ? \ + ((GET_CVID_CUT_VERSION(version)) ? false : true) : false) +#define IS_CHIP_VER_B(version) ((version & CHIP_VER_B) ? true : false) +#define IS_VENDOR_UMC_A_CUT(version) ((IS_CHIP_VENDOR_UMC(version)) ? \ + ((GET_CVID_CUT_VERSION(version)) ? false : true) : false) +#define IS_92C_SERIAL(version) ((version & CHIP_92C_BITMASK) ? true : false) +#define IS_CHIP_VENDOR_UMC(version) \ + ((version & CHIP_VENDOR_UMC) ? true : false) +#define GET_CVID_CUT_VERSION(version) ((version) & CUT_VERSION_MASK) +#define IS_81xxC_VENDOR_UMC_B_CUT(version) \ + ((IS_CHIP_VENDOR_UMC(version)) ? \ + ((GET_CVID_CUT_VERSION(version) == CHIP_VENDOR_UMC_B_CUT) ? \ + true : false) : false) + +enum rtl819x_loopback_e { + RTL819X_NO_LOOPBACK = 0, + RTL819X_MAC_LOOPBACK = 1, + RTL819X_DMA_LOOPBACK = 2, + RTL819X_CCK_LOOPBACK = 3, +}; + +enum rf_optype { + RF_OP_BY_SW_3WIRE = 0, + RF_OP_BY_FW, + RF_OP_MAX +}; + +enum rf_power_state { + RF_ON, + RF_OFF, + RF_SLEEP, + RF_SHUT_DOWN, +}; + +enum power_save_mode { + POWER_SAVE_MODE_ACTIVE, + POWER_SAVE_MODE_SAVE, +}; + +enum power_polocy_config { + POWERCFG_MAX_POWER_SAVINGS, + POWERCFG_GLOBAL_POWER_SAVINGS, + POWERCFG_LOCAL_POWER_SAVINGS, + POWERCFG_LENOVO, +}; + +enum interface_select_pci { + INTF_SEL1_MINICARD = 0, + INTF_SEL0_PCIE = 1, + INTF_SEL2_RSV = 2, + INTF_SEL3_RSV = 3, +}; + +enum hal_fw_c2h_cmd_id { + HAL_FW_C2H_CMD_Read_MACREG = 0, + HAL_FW_C2H_CMD_Read_BBREG = 1, + HAL_FW_C2H_CMD_Read_RFREG = 2, + HAL_FW_C2H_CMD_Read_EEPROM = 3, + HAL_FW_C2H_CMD_Read_EFUSE = 4, + HAL_FW_C2H_CMD_Read_CAM = 5, + HAL_FW_C2H_CMD_Get_BasicRate = 6, + HAL_FW_C2H_CMD_Get_DataRate = 7, + HAL_FW_C2H_CMD_Survey = 8, + HAL_FW_C2H_CMD_SurveyDone = 9, + HAL_FW_C2H_CMD_JoinBss = 10, + HAL_FW_C2H_CMD_AddSTA = 11, + HAL_FW_C2H_CMD_DelSTA = 12, + HAL_FW_C2H_CMD_AtimDone = 13, + HAL_FW_C2H_CMD_TX_Report = 14, + HAL_FW_C2H_CMD_CCX_Report = 15, + HAL_FW_C2H_CMD_DTM_Report = 16, + HAL_FW_C2H_CMD_TX_Rate_Statistics = 17, + HAL_FW_C2H_CMD_C2HLBK = 18, + HAL_FW_C2H_CMD_C2HDBG = 19, + HAL_FW_C2H_CMD_C2HFEEDBACK = 20, + HAL_FW_C2H_CMD_MAX +}; + +enum rtl_desc_qsel { + QSLT_BK = 0x2, + QSLT_BE = 0x0, + QSLT_VI = 0x5, + QSLT_VO = 0x7, + QSLT_BEACON = 0x10, + QSLT_HIGH = 0x11, + QSLT_MGNT = 0x12, + QSLT_CMD = 0x13, +}; + +struct phy_sts_cck_8192s_t { + u8 adc_pwdb_X[4]; + u8 sq_rpt; + u8 cck_agc_rpt; +}; + +struct h2c_cmd_8192c { + u8 element_id; + u32 cmd_len; + u8 *p_cmdbuffer; +}; + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/dm.c b/drivers/net/wireless/rtlwifi/rtl8192ce/dm.c new file mode 100644 index 0000000..74f9c08 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/dm.c @@ -0,0 +1,109 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../base.h" +#include "../pci.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "dm.h" +#include "../rtl8192c/fw_common.h" + +void rtl92ce_dm_dynamic_txpower(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + long undec_sm_pwdb; + + if (!rtlpriv->dm.dynamic_txpower_enable) + return; + + if (rtlpriv->dm.dm_flag & HAL_DM_HIPWR_DISABLE) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + return; + } + + if ((mac->link_state < MAC80211_LINKED) && + (rtlpriv->dm.entry_min_undec_sm_pwdb == 0)) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_TRACE, + "Not connected to any\n"); + + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + + rtlpriv->dm.last_dtp_lvl = TXHIGHPWRLEVEL_NORMAL; + return; + } + + if (mac->link_state >= MAC80211_LINKED) { + if (mac->opmode == NL80211_IFTYPE_ADHOC) { + undec_sm_pwdb = rtlpriv->dm.entry_min_undec_sm_pwdb; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "AP Client PWDB = 0x%lx\n", + undec_sm_pwdb); + } else { + undec_sm_pwdb = rtlpriv->dm.undec_sm_pwdb; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "STA Default Port PWDB = 0x%lx\n", + undec_sm_pwdb); + } + } else { + undec_sm_pwdb = rtlpriv->dm.entry_min_undec_sm_pwdb; + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "AP Ext Port PWDB = 0x%lx\n", + undec_sm_pwdb); + } + + if (undec_sm_pwdb >= TX_POWER_NEAR_FIELD_THRESH_LVL2) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_LEVEL1; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_LEVEL1 (TxPwr=0x0)\n"); + } else if ((undec_sm_pwdb < (TX_POWER_NEAR_FIELD_THRESH_LVL2 - 3)) && + (undec_sm_pwdb >= TX_POWER_NEAR_FIELD_THRESH_LVL1)) { + + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_LEVEL1; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_LEVEL1 (TxPwr=0x10)\n"); + } else if (undec_sm_pwdb < (TX_POWER_NEAR_FIELD_THRESH_LVL1 - 5)) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_NORMAL\n"); + } + + if ((rtlpriv->dm.dynamic_txhighpower_lvl != rtlpriv->dm.last_dtp_lvl)) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "PHY_SetTxPowerLevel8192S() Channel = %d\n", + rtlphy->current_channel); + rtl92c_phy_set_txpower_level(hw, rtlphy->current_channel); + } + + rtlpriv->dm.last_dtp_lvl = rtlpriv->dm.dynamic_txhighpower_lvl; +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/dm.h b/drivers/net/wireless/rtlwifi/rtl8192ce/dm.h new file mode 100644 index 0000000..d4a3d03 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/dm.h @@ -0,0 +1,163 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92C_DM_H__ +#define __RTL92C_DM_H__ + +#define HAL_DM_DIG_DISABLE BIT(0) +#define HAL_DM_HIPWR_DISABLE BIT(1) + +#define OFDM_TABLE_LENGTH 37 +#define CCK_TABLE_LENGTH 33 + +#define OFDM_TABLE_SIZE 37 +#define CCK_TABLE_SIZE 33 + +#define BW_AUTO_SWITCH_HIGH_LOW 25 +#define BW_AUTO_SWITCH_LOW_HIGH 30 + +#define DM_DIG_THRESH_HIGH 40 +#define DM_DIG_THRESH_LOW 35 + +#define DM_FALSEALARM_THRESH_LOW 400 +#define DM_FALSEALARM_THRESH_HIGH 1000 + +#define DM_DIG_MAX 0x3e +#define DM_DIG_MIN 0x1e + +#define DM_DIG_FA_UPPER 0x32 +#define DM_DIG_FA_LOWER 0x20 +#define DM_DIG_FA_TH0 0x20 +#define DM_DIG_FA_TH1 0x100 +#define DM_DIG_FA_TH2 0x200 + +#define DM_DIG_BACKOFF_MAX 12 +#define DM_DIG_BACKOFF_MIN -4 +#define DM_DIG_BACKOFF_DEFAULT 10 + +#define RXPATHSELECTION_SS_TH_lOW 30 +#define RXPATHSELECTION_DIFF_TH 18 + +#define DM_RATR_STA_INIT 0 +#define DM_RATR_STA_HIGH 1 +#define DM_RATR_STA_MIDDLE 2 +#define DM_RATR_STA_LOW 3 + +#define CTS2SELF_THVAL 30 +#define REGC38_TH 20 + +#define WAIOTTHVal 25 + +#define TXHIGHPWRLEVEL_NORMAL 0 +#define TXHIGHPWRLEVEL_LEVEL1 1 +#define TXHIGHPWRLEVEL_LEVEL2 2 +#define TXHIGHPWRLEVEL_BT1 3 +#define TXHIGHPWRLEVEL_BT2 4 + +#define DM_TYPE_BYFW 0 +#define DM_TYPE_BYDRIVER 1 + +#define TX_POWER_NEAR_FIELD_THRESH_LVL2 74 +#define TX_POWER_NEAR_FIELD_THRESH_LVL1 67 + +struct swat_t { + u8 failure_cnt; + u8 try_flag; + u8 stop_trying; + long pre_rssi; + long trying_threshold; + u8 cur_antenna; + u8 pre_antenna; +}; + +enum tag_dynamic_init_gain_operation_type_definition { + DIG_TYPE_THRESH_HIGH = 0, + DIG_TYPE_THRESH_LOW = 1, + DIG_TYPE_BACKOFF = 2, + DIG_TYPE_RX_GAIN_MIN = 3, + DIG_TYPE_RX_GAIN_MAX = 4, + DIG_TYPE_ENABLE = 5, + DIG_TYPE_DISABLE = 6, + DIG_OP_TYPE_MAX +}; + +enum tag_cck_packet_detection_threshold_type_definition { + CCK_PD_STAGE_LowRssi = 0, + CCK_PD_STAGE_HighRssi = 1, + CCK_FA_STAGE_Low = 2, + CCK_FA_STAGE_High = 3, + CCK_PD_STAGE_MAX = 4, +}; + +enum dm_1r_cca_e { + CCA_1R = 0, + CCA_2R = 1, + CCA_MAX = 2, +}; + +enum dm_rf_e { + RF_SAVE = 0, + RF_NORMAL = 1, + RF_MAX = 2, +}; + +enum dm_sw_ant_switch_e { + ANS_ANTENNA_B = 1, + ANS_ANTENNA_A = 2, + ANS_ANTENNA_MAX = 3, +}; + +enum dm_dig_ext_port_alg_e { + DIG_EXT_PORT_STAGE_0 = 0, + DIG_EXT_PORT_STAGE_1 = 1, + DIG_EXT_PORT_STAGE_2 = 2, + DIG_EXT_PORT_STAGE_3 = 3, + DIG_EXT_PORT_STAGE_MAX = 4, +}; + +enum dm_dig_connect_e { + DIG_STA_DISCONNECT = 0, + DIG_STA_CONNECT = 1, + DIG_STA_BEFORE_CONNECT = 2, + DIG_MULTISTA_DISCONNECT = 3, + DIG_MULTISTA_CONNECT = 4, + DIG_CONNECT_MAX +}; + +void rtl92c_dm_init(struct ieee80211_hw *hw); +void rtl92c_dm_watchdog(struct ieee80211_hw *hw); +void rtl92c_dm_write_dig(struct ieee80211_hw *hw); +void rtl92c_dm_init_edca_turbo(struct ieee80211_hw *hw); +void rtl92c_dm_check_txpower_tracking(struct ieee80211_hw *hw); +void rtl92c_dm_init_rate_adaptive_mask(struct ieee80211_hw *hw); +void rtl92c_dm_rf_saving(struct ieee80211_hw *hw, u8 bforce_in_normal); +void rtl92c_dm_bt_coexist(struct ieee80211_hw *hw); +void rtl92ce_dm_dynamic_txpower(struct ieee80211_hw *hw); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/hw.c b/drivers/net/wireless/rtlwifi/rtl8192ce/hw.c new file mode 100644 index 0000000..a82b30a --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/hw.c @@ -0,0 +1,2428 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../efuse.h" +#include "../base.h" +#include "../regd.h" +#include "../cam.h" +#include "../ps.h" +#include "../pci.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "../rtl8192c/fw_common.h" +#include "dm.h" +#include "led.h" +#include "hw.h" + +#define LLT_CONFIG 5 + +static void _rtl92ce_set_bcn_ctrl_reg(struct ieee80211_hw *hw, + u8 set_bits, u8 clear_bits) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpci->reg_bcn_ctrl_val |= set_bits; + rtlpci->reg_bcn_ctrl_val &= ~clear_bits; + + rtl_write_byte(rtlpriv, REG_BCN_CTRL, (u8) rtlpci->reg_bcn_ctrl_val); +} + +static void _rtl92ce_stop_tx_beacon(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tmp1byte; + + tmp1byte = rtl_read_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2); + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, tmp1byte & (~BIT(6))); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 1, 0x64); + tmp1byte = rtl_read_byte(rtlpriv, REG_TBTT_PROHIBIT + 2); + tmp1byte &= ~(BIT(0)); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 2, tmp1byte); +} + +static void _rtl92ce_resume_tx_beacon(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tmp1byte; + + tmp1byte = rtl_read_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2); + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, tmp1byte | BIT(6)); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 1, 0xff); + tmp1byte = rtl_read_byte(rtlpriv, REG_TBTT_PROHIBIT + 2); + tmp1byte |= BIT(0); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 2, tmp1byte); +} + +static void _rtl92ce_enable_bcn_sub_func(struct ieee80211_hw *hw) +{ + _rtl92ce_set_bcn_ctrl_reg(hw, 0, BIT(1)); +} + +static void _rtl92ce_disable_bcn_sub_func(struct ieee80211_hw *hw) +{ + _rtl92ce_set_bcn_ctrl_reg(hw, BIT(1), 0); +} + +void rtl92ce_get_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + switch (variable) { + case HW_VAR_RCR: + *((u32 *) (val)) = rtlpci->receive_config; + break; + case HW_VAR_RF_STATE: + *((enum rf_pwrstate *)(val)) = ppsc->rfpwr_state; + break; + case HW_VAR_FWLPS_RF_ON:{ + enum rf_pwrstate rfState; + u32 val_rcr; + + rtlpriv->cfg->ops->get_hw_reg(hw, + HW_VAR_RF_STATE, + (u8 *) (&rfState)); + if (rfState == ERFOFF) { + *((bool *) (val)) = true; + } else { + val_rcr = rtl_read_dword(rtlpriv, REG_RCR); + val_rcr &= 0x00070000; + if (val_rcr) + *((bool *) (val)) = false; + else + *((bool *) (val)) = true; + } + break; + } + case HW_VAR_FW_PSMODE_STATUS: + *((bool *) (val)) = ppsc->fw_current_inpsmode; + break; + case HW_VAR_CORRECT_TSF:{ + u64 tsf; + u32 *ptsf_low = (u32 *)&tsf; + u32 *ptsf_high = ((u32 *)&tsf) + 1; + + *ptsf_high = rtl_read_dword(rtlpriv, (REG_TSFTR + 4)); + *ptsf_low = rtl_read_dword(rtlpriv, REG_TSFTR); + + *((u64 *) (val)) = tsf; + + break; + } + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } +} + +void rtl92ce_set_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + u8 idx; + + switch (variable) { + case HW_VAR_ETHER_ADDR:{ + for (idx = 0; idx < ETH_ALEN; idx++) { + rtl_write_byte(rtlpriv, (REG_MACID + idx), + val[idx]); + } + break; + } + case HW_VAR_BASIC_RATE:{ + u16 rate_cfg = ((u16 *) val)[0]; + u8 rate_index = 0; + rate_cfg &= 0x15f; + rate_cfg |= 0x01; + rtl_write_byte(rtlpriv, REG_RRSR, rate_cfg & 0xff); + rtl_write_byte(rtlpriv, REG_RRSR + 1, + (rate_cfg >> 8) & 0xff); + while (rate_cfg > 0x1) { + rate_cfg = (rate_cfg >> 1); + rate_index++; + } + rtl_write_byte(rtlpriv, REG_INIRTS_RATE_SEL, + rate_index); + break; + } + case HW_VAR_BSSID:{ + for (idx = 0; idx < ETH_ALEN; idx++) { + rtl_write_byte(rtlpriv, (REG_BSSID + idx), + val[idx]); + } + break; + } + case HW_VAR_SIFS:{ + rtl_write_byte(rtlpriv, REG_SIFS_CTX + 1, val[0]); + rtl_write_byte(rtlpriv, REG_SIFS_TRX + 1, val[1]); + + rtl_write_byte(rtlpriv, REG_SPEC_SIFS + 1, val[0]); + rtl_write_byte(rtlpriv, REG_MAC_SPEC_SIFS + 1, val[0]); + + if (!mac->ht_enable) + rtl_write_word(rtlpriv, REG_RESP_SIFS_OFDM, + 0x0e0e); + else + rtl_write_word(rtlpriv, REG_RESP_SIFS_OFDM, + *((u16 *) val)); + break; + } + case HW_VAR_SLOT_TIME:{ + u8 e_aci; + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "HW_VAR_SLOT_TIME %x\n", val[0]); + + rtl_write_byte(rtlpriv, REG_SLOT, val[0]); + + for (e_aci = 0; e_aci < AC_MAX; e_aci++) { + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_AC_PARAM, + &e_aci); + } + break; + } + case HW_VAR_ACK_PREAMBLE:{ + u8 reg_tmp; + u8 short_preamble = (bool)*val; + reg_tmp = (mac->cur_40_prime_sc) << 5; + if (short_preamble) + reg_tmp |= 0x80; + + rtl_write_byte(rtlpriv, REG_RRSR + 2, reg_tmp); + break; + } + case HW_VAR_AMPDU_MIN_SPACE:{ + u8 min_spacing_to_set; + u8 sec_min_space; + + min_spacing_to_set = *val; + if (min_spacing_to_set <= 7) { + sec_min_space = 0; + + if (min_spacing_to_set < sec_min_space) + min_spacing_to_set = sec_min_space; + + mac->min_space_cfg = ((mac->min_space_cfg & + 0xf8) | + min_spacing_to_set); + + *val = min_spacing_to_set; + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_AMPDU_MIN_SPACE: %#x\n", + mac->min_space_cfg); + + rtl_write_byte(rtlpriv, REG_AMPDU_MIN_SPACE, + mac->min_space_cfg); + } + break; + } + case HW_VAR_SHORTGI_DENSITY:{ + u8 density_to_set; + + density_to_set = *val; + mac->min_space_cfg |= (density_to_set << 3); + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_SHORTGI_DENSITY: %#x\n", + mac->min_space_cfg); + + rtl_write_byte(rtlpriv, REG_AMPDU_MIN_SPACE, + mac->min_space_cfg); + + break; + } + case HW_VAR_AMPDU_FACTOR:{ + u8 regtoset_normal[4] = {0x41, 0xa8, 0x72, 0xb9}; + u8 regtoset_bt[4] = {0x31, 0x74, 0x42, 0x97}; + + u8 factor_toset; + u8 *p_regtoset = NULL; + u8 index = 0; + + if ((rtlpcipriv->bt_coexist.bt_coexistence) && + (rtlpcipriv->bt_coexist.bt_coexist_type == + BT_CSR_BC4)) + p_regtoset = regtoset_bt; + else + p_regtoset = regtoset_normal; + + factor_toset = *(val); + if (factor_toset <= 3) { + factor_toset = (1 << (factor_toset + 2)); + if (factor_toset > 0xf) + factor_toset = 0xf; + + for (index = 0; index < 4; index++) { + if ((p_regtoset[index] & 0xf0) > + (factor_toset << 4)) + p_regtoset[index] = + (p_regtoset[index] & 0x0f) | + (factor_toset << 4); + + if ((p_regtoset[index] & 0x0f) > + factor_toset) + p_regtoset[index] = + (p_regtoset[index] & 0xf0) | + (factor_toset); + + rtl_write_byte(rtlpriv, + (REG_AGGLEN_LMT + index), + p_regtoset[index]); + + } + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_AMPDU_FACTOR: %#x\n", + factor_toset); + } + break; + } + case HW_VAR_AC_PARAM:{ + u8 e_aci = *(val); + rtl92c_dm_init_edca_turbo(hw); + + if (rtlpci->acm_method != eAcmWay2_SW) + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_ACM_CTRL, + (&e_aci)); + break; + } + case HW_VAR_ACM_CTRL:{ + u8 e_aci = *(val); + union aci_aifsn *p_aci_aifsn = + (union aci_aifsn *)(&(mac->ac[0].aifs)); + u8 acm = p_aci_aifsn->f.acm; + u8 acm_ctrl = rtl_read_byte(rtlpriv, REG_ACMHWCTRL); + + acm_ctrl = + acm_ctrl | ((rtlpci->acm_method == 2) ? 0x0 : 0x1); + + if (acm) { + switch (e_aci) { + case AC0_BE: + acm_ctrl |= AcmHw_BeqEn; + break; + case AC2_VI: + acm_ctrl |= AcmHw_ViqEn; + break; + case AC3_VO: + acm_ctrl |= AcmHw_VoqEn; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "HW_VAR_ACM_CTRL acm set failed: eACI is %d\n", + acm); + break; + } + } else { + switch (e_aci) { + case AC0_BE: + acm_ctrl &= (~AcmHw_BeqEn); + break; + case AC2_VI: + acm_ctrl &= (~AcmHw_ViqEn); + break; + case AC3_VO: + acm_ctrl &= (~AcmHw_BeqEn); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + } + + RT_TRACE(rtlpriv, COMP_QOS, DBG_TRACE, + "SetHwReg8190pci(): [HW_VAR_ACM_CTRL] Write 0x%X\n", + acm_ctrl); + rtl_write_byte(rtlpriv, REG_ACMHWCTRL, acm_ctrl); + break; + } + case HW_VAR_RCR:{ + rtl_write_dword(rtlpriv, REG_RCR, ((u32 *) (val))[0]); + rtlpci->receive_config = ((u32 *) (val))[0]; + break; + } + case HW_VAR_RETRY_LIMIT:{ + u8 retry_limit = val[0]; + + rtl_write_word(rtlpriv, REG_RL, + retry_limit << RETRY_LIMIT_SHORT_SHIFT | + retry_limit << RETRY_LIMIT_LONG_SHIFT); + break; + } + case HW_VAR_DUAL_TSF_RST: + rtl_write_byte(rtlpriv, REG_DUAL_TSF_RST, (BIT(0) | BIT(1))); + break; + case HW_VAR_EFUSE_BYTES: + rtlefuse->efuse_usedbytes = *((u16 *) val); + break; + case HW_VAR_EFUSE_USAGE: + rtlefuse->efuse_usedpercentage = *val; + break; + case HW_VAR_IO_CMD: + rtl92c_phy_set_io_cmd(hw, (*(enum io_type *)val)); + break; + case HW_VAR_WPA_CONFIG: + rtl_write_byte(rtlpriv, REG_SECCFG, *val); + break; + case HW_VAR_SET_RPWM:{ + u8 rpwm_val; + + rpwm_val = rtl_read_byte(rtlpriv, REG_PCIE_HRPWM); + udelay(1); + + if (rpwm_val & BIT(7)) { + rtl_write_byte(rtlpriv, REG_PCIE_HRPWM, *val); + } else { + rtl_write_byte(rtlpriv, REG_PCIE_HRPWM, + *val | BIT(7)); + } + + break; + } + case HW_VAR_H2C_FW_PWRMODE:{ + u8 psmode = *val; + + if ((psmode != FW_PS_ACTIVE_MODE) && + (!IS_92C_SERIAL(rtlhal->version))) { + rtl92c_dm_rf_saving(hw, true); + } + + rtl92c_set_fw_pwrmode_cmd(hw, *val); + break; + } + case HW_VAR_FW_PSMODE_STATUS: + ppsc->fw_current_inpsmode = *((bool *) val); + break; + case HW_VAR_H2C_FW_JOINBSSRPT:{ + u8 mstatus = *val; + u8 tmp_regcr, tmp_reg422; + bool recover = false; + + if (mstatus == RT_MEDIA_CONNECT) { + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_AID, + NULL); + + tmp_regcr = rtl_read_byte(rtlpriv, REG_CR + 1); + rtl_write_byte(rtlpriv, REG_CR + 1, + (tmp_regcr | BIT(0))); + + _rtl92ce_set_bcn_ctrl_reg(hw, 0, BIT(3)); + _rtl92ce_set_bcn_ctrl_reg(hw, BIT(4), 0); + + tmp_reg422 = + rtl_read_byte(rtlpriv, + REG_FWHW_TXQ_CTRL + 2); + if (tmp_reg422 & BIT(6)) + recover = true; + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, + tmp_reg422 & (~BIT(6))); + + rtl92c_set_fw_rsvdpagepkt(hw, 0); + + _rtl92ce_set_bcn_ctrl_reg(hw, BIT(3), 0); + _rtl92ce_set_bcn_ctrl_reg(hw, 0, BIT(4)); + + if (recover) { + rtl_write_byte(rtlpriv, + REG_FWHW_TXQ_CTRL + 2, + tmp_reg422); + } + + rtl_write_byte(rtlpriv, REG_CR + 1, + (tmp_regcr & ~(BIT(0)))); + } + rtl92c_set_fw_joinbss_report_cmd(hw, *val); + + break; + } + case HW_VAR_H2C_FW_P2P_PS_OFFLOAD: + rtl92c_set_p2p_ps_offload_cmd(hw, (*(u8 *)val)); + break; + case HW_VAR_AID:{ + u16 u2btmp; + u2btmp = rtl_read_word(rtlpriv, REG_BCN_PSR_RPT); + u2btmp &= 0xC000; + rtl_write_word(rtlpriv, REG_BCN_PSR_RPT, (u2btmp | + mac->assoc_id)); + + break; + } + case HW_VAR_CORRECT_TSF:{ + u8 btype_ibss = val[0]; + + if (btype_ibss) + _rtl92ce_stop_tx_beacon(hw); + + _rtl92ce_set_bcn_ctrl_reg(hw, 0, BIT(3)); + + rtl_write_dword(rtlpriv, REG_TSFTR, + (u32) (mac->tsf & 0xffffffff)); + rtl_write_dword(rtlpriv, REG_TSFTR + 4, + (u32) ((mac->tsf >> 32) & 0xffffffff)); + + _rtl92ce_set_bcn_ctrl_reg(hw, BIT(3), 0); + + if (btype_ibss) + _rtl92ce_resume_tx_beacon(hw); + + break; + + } + case HW_VAR_FW_LPS_ACTION: { + bool enter_fwlps = *((bool *)val); + u8 rpwm_val, fw_pwrmode; + bool fw_current_inps; + + if (enter_fwlps) { + rpwm_val = 0x02; /* RF off */ + fw_current_inps = true; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_FW_PSMODE_STATUS, + (u8 *)(&fw_current_inps)); + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_H2C_FW_PWRMODE, + (u8 *)(&ppsc->fwctrl_psmode)); + + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_SET_RPWM, + (u8 *)(&rpwm_val)); + } else { + rpwm_val = 0x0C; /* RF on */ + fw_pwrmode = FW_PS_ACTIVE_MODE; + fw_current_inps = false; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_SET_RPWM, + (u8 *)(&rpwm_val)); + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_H2C_FW_PWRMODE, + (u8 *)(&fw_pwrmode)); + + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_FW_PSMODE_STATUS, + (u8 *)(&fw_current_inps)); + } + break; } + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } +} + +static bool _rtl92ce_llt_write(struct ieee80211_hw *hw, u32 address, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + bool status = true; + long count = 0; + u32 value = _LLT_INIT_ADDR(address) | + _LLT_INIT_DATA(data) | _LLT_OP(_LLT_WRITE_ACCESS); + + rtl_write_dword(rtlpriv, REG_LLT_INIT, value); + + do { + value = rtl_read_dword(rtlpriv, REG_LLT_INIT); + if (_LLT_NO_ACTIVE == _LLT_OP_VALUE(value)) + break; + + if (count > POLLING_LLT_THRESHOLD) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Failed to polling write LLT done at address %d!\n", + address); + status = false; + break; + } + } while (++count); + + return status; +} + +static bool _rtl92ce_llt_table_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + unsigned short i; + u8 txpktbuf_bndy; + u8 maxPage; + bool status; + +#if LLT_CONFIG == 1 + maxPage = 255; + txpktbuf_bndy = 252; +#elif LLT_CONFIG == 2 + maxPage = 127; + txpktbuf_bndy = 124; +#elif LLT_CONFIG == 3 + maxPage = 255; + txpktbuf_bndy = 174; +#elif LLT_CONFIG == 4 + maxPage = 255; + txpktbuf_bndy = 246; +#elif LLT_CONFIG == 5 + maxPage = 255; + txpktbuf_bndy = 246; +#endif + +#if LLT_CONFIG == 1 + rtl_write_byte(rtlpriv, REG_RQPN_NPQ, 0x1c); + rtl_write_dword(rtlpriv, REG_RQPN, 0x80a71c1c); +#elif LLT_CONFIG == 2 + rtl_write_dword(rtlpriv, REG_RQPN, 0x845B1010); +#elif LLT_CONFIG == 3 + rtl_write_dword(rtlpriv, REG_RQPN, 0x84838484); +#elif LLT_CONFIG == 4 + rtl_write_dword(rtlpriv, REG_RQPN, 0x80bd1c1c); +#elif LLT_CONFIG == 5 + rtl_write_word(rtlpriv, REG_RQPN_NPQ, 0x0000); + + rtl_write_dword(rtlpriv, REG_RQPN, 0x80b01c29); +#endif + + rtl_write_dword(rtlpriv, REG_TRXFF_BNDY, (0x27FF0000 | txpktbuf_bndy)); + rtl_write_byte(rtlpriv, REG_TDECTRL + 1, txpktbuf_bndy); + + rtl_write_byte(rtlpriv, REG_TXPKTBUF_BCNQ_BDNY, txpktbuf_bndy); + rtl_write_byte(rtlpriv, REG_TXPKTBUF_MGQ_BDNY, txpktbuf_bndy); + + rtl_write_byte(rtlpriv, 0x45D, txpktbuf_bndy); + rtl_write_byte(rtlpriv, REG_PBP, 0x11); + rtl_write_byte(rtlpriv, REG_RX_DRVINFO_SZ, 0x4); + + for (i = 0; i < (txpktbuf_bndy - 1); i++) { + status = _rtl92ce_llt_write(hw, i, i + 1); + if (true != status) + return status; + } + + status = _rtl92ce_llt_write(hw, (txpktbuf_bndy - 1), 0xFF); + if (true != status) + return status; + + for (i = txpktbuf_bndy; i < maxPage; i++) { + status = _rtl92ce_llt_write(hw, i, (i + 1)); + if (true != status) + return status; + } + + status = _rtl92ce_llt_write(hw, maxPage, txpktbuf_bndy); + if (true != status) + return status; + + return true; +} + +static void _rtl92ce_gen_refresh_led_state(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_led *pLed0 = &(pcipriv->ledctl.sw_led0); + + if (rtlpci->up_first_time) + return; + + if (ppsc->rfoff_reason == RF_CHANGE_BY_IPS) + rtl92ce_sw_led_on(hw, pLed0); + else if (ppsc->rfoff_reason == RF_CHANGE_BY_INIT) + rtl92ce_sw_led_on(hw, pLed0); + else + rtl92ce_sw_led_off(hw, pLed0); +} + +static bool _rtl92ce_init_mac(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + unsigned char bytetmp; + unsigned short wordtmp; + u16 retry; + + rtl_write_byte(rtlpriv, REG_RSV_CTRL, 0x00); + if (rtlpcipriv->bt_coexist.bt_coexistence) { + u32 value32; + value32 = rtl_read_dword(rtlpriv, REG_APS_FSMCO); + value32 |= (SOP_ABG | SOP_AMB | XOP_BTCK); + rtl_write_dword(rtlpriv, REG_APS_FSMCO, value32); + } + rtl_write_byte(rtlpriv, REG_SPS0_CTRL, 0x2b); + rtl_write_byte(rtlpriv, REG_AFE_XTAL_CTRL, 0x0F); + + if (rtlpcipriv->bt_coexist.bt_coexistence) { + u32 u4b_tmp = rtl_read_dword(rtlpriv, REG_AFE_XTAL_CTRL); + + u4b_tmp &= (~0x00024800); + rtl_write_dword(rtlpriv, REG_AFE_XTAL_CTRL, u4b_tmp); + } + + bytetmp = rtl_read_byte(rtlpriv, REG_APS_FSMCO + 1) | BIT(0); + udelay(2); + + rtl_write_byte(rtlpriv, REG_APS_FSMCO + 1, bytetmp); + udelay(2); + + bytetmp = rtl_read_byte(rtlpriv, REG_APS_FSMCO + 1); + udelay(2); + + retry = 0; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "reg0xec:%x:%x\n", + rtl_read_dword(rtlpriv, 0xEC), bytetmp); + + while ((bytetmp & BIT(0)) && retry < 1000) { + retry++; + udelay(50); + bytetmp = rtl_read_byte(rtlpriv, REG_APS_FSMCO + 1); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "reg0xec:%x:%x\n", + rtl_read_dword(rtlpriv, 0xEC), bytetmp); + udelay(50); + } + + rtl_write_word(rtlpriv, REG_APS_FSMCO, 0x1012); + + rtl_write_byte(rtlpriv, REG_SYS_ISO_CTRL + 1, 0x82); + udelay(2); + + if (rtlpcipriv->bt_coexist.bt_coexistence) { + bytetmp = rtl_read_byte(rtlpriv, REG_AFE_XTAL_CTRL+2) & 0xfd; + rtl_write_byte(rtlpriv, REG_AFE_XTAL_CTRL+2, bytetmp); + } + + rtl_write_word(rtlpriv, REG_CR, 0x2ff); + + if (!_rtl92ce_llt_table_init(hw)) + return false; + + rtl_write_dword(rtlpriv, REG_HISR, 0xffffffff); + rtl_write_byte(rtlpriv, REG_HISRE, 0xff); + + rtl_write_word(rtlpriv, REG_TRXFF_BNDY + 2, 0x27ff); + + wordtmp = rtl_read_word(rtlpriv, REG_TRXDMA_CTRL); + wordtmp &= 0xf; + wordtmp |= 0xF771; + rtl_write_word(rtlpriv, REG_TRXDMA_CTRL, wordtmp); + + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 1, 0x1F); + rtl_write_dword(rtlpriv, REG_RCR, rtlpci->receive_config); + rtl_write_dword(rtlpriv, REG_TCR, rtlpci->transmit_config); + + rtl_write_byte(rtlpriv, 0x4d0, 0x0); + + rtl_write_dword(rtlpriv, REG_BCNQ_DESA, + ((u64) rtlpci->tx_ring[BEACON_QUEUE].dma) & + DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_MGQ_DESA, + (u64) rtlpci->tx_ring[MGNT_QUEUE].dma & + DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_VOQ_DESA, + (u64) rtlpci->tx_ring[VO_QUEUE].dma & DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_VIQ_DESA, + (u64) rtlpci->tx_ring[VI_QUEUE].dma & DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_BEQ_DESA, + (u64) rtlpci->tx_ring[BE_QUEUE].dma & DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_BKQ_DESA, + (u64) rtlpci->tx_ring[BK_QUEUE].dma & DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_HQ_DESA, + (u64) rtlpci->tx_ring[HIGH_QUEUE].dma & + DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_RX_DESA, + (u64) rtlpci->rx_ring[RX_MPDU_QUEUE].dma & + DMA_BIT_MASK(32)); + + if (IS_92C_SERIAL(rtlhal->version)) + rtl_write_byte(rtlpriv, REG_PCIE_CTRL_REG + 3, 0x77); + else + rtl_write_byte(rtlpriv, REG_PCIE_CTRL_REG + 3, 0x22); + + rtl_write_dword(rtlpriv, REG_INT_MIG, 0); + + bytetmp = rtl_read_byte(rtlpriv, REG_APSD_CTRL); + rtl_write_byte(rtlpriv, REG_APSD_CTRL, bytetmp & ~BIT(6)); + do { + retry++; + bytetmp = rtl_read_byte(rtlpriv, REG_APSD_CTRL); + } while ((retry < 200) && (bytetmp & BIT(7))); + + _rtl92ce_gen_refresh_led_state(hw); + + rtl_write_dword(rtlpriv, REG_MCUTST_1, 0x0); + + return true; +} + +static void _rtl92ce_hw_configure(struct ieee80211_hw *hw) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + u8 reg_bw_opmode; + u32 reg_prsr; + + reg_bw_opmode = BW_OPMODE_20MHZ; + reg_prsr = RATE_ALL_CCK | RATE_ALL_OFDM_AG; + + rtl_write_byte(rtlpriv, REG_INIRTS_RATE_SEL, 0x8); + + rtl_write_byte(rtlpriv, REG_BWOPMODE, reg_bw_opmode); + + rtl_write_dword(rtlpriv, REG_RRSR, reg_prsr); + + rtl_write_byte(rtlpriv, REG_SLOT, 0x09); + + rtl_write_byte(rtlpriv, REG_AMPDU_MIN_SPACE, 0x0); + + rtl_write_word(rtlpriv, REG_FWHW_TXQ_CTRL, 0x1F80); + + rtl_write_word(rtlpriv, REG_RL, 0x0707); + + rtl_write_dword(rtlpriv, REG_BAR_MODE_CTRL, 0x02012802); + + rtl_write_byte(rtlpriv, REG_HWSEQ_CTRL, 0xFF); + + rtl_write_dword(rtlpriv, REG_DARFRC, 0x01000000); + rtl_write_dword(rtlpriv, REG_DARFRC + 4, 0x07060504); + rtl_write_dword(rtlpriv, REG_RARFRC, 0x01000000); + rtl_write_dword(rtlpriv, REG_RARFRC + 4, 0x07060504); + + if ((rtlpcipriv->bt_coexist.bt_coexistence) && + (rtlpcipriv->bt_coexist.bt_coexist_type == BT_CSR_BC4)) + rtl_write_dword(rtlpriv, REG_AGGLEN_LMT, 0x97427431); + else + rtl_write_dword(rtlpriv, REG_AGGLEN_LMT, 0xb972a841); + + rtl_write_byte(rtlpriv, REG_ATIMWND, 0x2); + + rtl_write_byte(rtlpriv, REG_BCN_MAX_ERR, 0xff); + + rtlpci->reg_bcn_ctrl_val = 0x1f; + rtl_write_byte(rtlpriv, REG_BCN_CTRL, rtlpci->reg_bcn_ctrl_val); + + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 1, 0xff); + + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 1, 0xff); + + rtl_write_byte(rtlpriv, REG_PIFS, 0x1C); + rtl_write_byte(rtlpriv, REG_AGGR_BREAK_TIME, 0x16); + + if ((rtlpcipriv->bt_coexist.bt_coexistence) && + (rtlpcipriv->bt_coexist.bt_coexist_type == BT_CSR_BC4)) { + rtl_write_word(rtlpriv, REG_NAV_PROT_LEN, 0x0020); + rtl_write_word(rtlpriv, REG_PROT_MODE_CTRL, 0x0402); + } else { + rtl_write_word(rtlpriv, REG_NAV_PROT_LEN, 0x0020); + rtl_write_word(rtlpriv, REG_NAV_PROT_LEN, 0x0020); + } + + if ((rtlpcipriv->bt_coexist.bt_coexistence) && + (rtlpcipriv->bt_coexist.bt_coexist_type == BT_CSR_BC4)) + rtl_write_dword(rtlpriv, REG_FAST_EDCA_CTRL, 0x03086666); + else + rtl_write_dword(rtlpriv, REG_FAST_EDCA_CTRL, 0x086666); + + rtl_write_byte(rtlpriv, REG_ACKTO, 0x40); + + rtl_write_word(rtlpriv, REG_SPEC_SIFS, 0x1010); + rtl_write_word(rtlpriv, REG_MAC_SPEC_SIFS, 0x1010); + + rtl_write_word(rtlpriv, REG_SIFS_CTX, 0x1010); + + rtl_write_word(rtlpriv, REG_SIFS_TRX, 0x1010); + + rtl_write_dword(rtlpriv, REG_MAR, 0xffffffff); + rtl_write_dword(rtlpriv, REG_MAR + 4, 0xffffffff); + +} + +static void _rtl92ce_enable_aspm_back_door(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + rtl_write_byte(rtlpriv, 0x34b, 0x93); + rtl_write_word(rtlpriv, 0x350, 0x870c); + rtl_write_byte(rtlpriv, 0x352, 0x1); + + if (ppsc->support_backdoor) + rtl_write_byte(rtlpriv, 0x349, 0x1b); + else + rtl_write_byte(rtlpriv, 0x349, 0x03); + + rtl_write_word(rtlpriv, 0x350, 0x2718); + rtl_write_byte(rtlpriv, 0x352, 0x1); +} + +void rtl92ce_enable_hw_security_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 sec_reg_value; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "PairwiseEncAlgorithm = %d GroupEncAlgorithm = %d\n", + rtlpriv->sec.pairwise_enc_algorithm, + rtlpriv->sec.group_enc_algorithm); + + if (rtlpriv->cfg->mod_params->sw_crypto || rtlpriv->sec.use_sw_sec) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "not open hw encryption\n"); + return; + } + + sec_reg_value = SCR_TxEncEnable | SCR_RxDecEnable; + + if (rtlpriv->sec.use_defaultkey) { + sec_reg_value |= SCR_TxUseDK; + sec_reg_value |= SCR_RxUseDK; + } + + sec_reg_value |= (SCR_RXBCUSEDK | SCR_TXBCUSEDK); + + rtl_write_byte(rtlpriv, REG_CR + 1, 0x02); + + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, + "The SECR-value %x\n", sec_reg_value); + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_WPA_CONFIG, &sec_reg_value); + +} + +int rtl92ce_hw_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + bool rtstatus = true; + bool is92c; + int err; + u8 tmp_u1b; + + rtlpci->being_init_adapter = true; + rtlpriv->intf_ops->disable_aspm(hw); + rtstatus = _rtl92ce_init_mac(hw); + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Init MAC failed\n"); + err = 1; + return err; + } + + err = rtl92c_download_fw(hw); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Failed to download FW. Init HW without FW now..\n"); + err = 1; + return err; + } + + rtlhal->last_hmeboxnum = 0; + rtl92c_phy_mac_config(hw); + /* because last function modify RCR, so we update + * rcr var here, or TP will unstable for receive_config + * is wrong, RX RCR_ACRC32 will cause TP unstabel & Rx + * RCR_APP_ICV will cause mac80211 unassoc for cisco 1252*/ + rtlpci->receive_config = rtl_read_dword(rtlpriv, REG_RCR); + rtlpci->receive_config &= ~(RCR_ACRC32 | RCR_AICV); + rtl_write_dword(rtlpriv, REG_RCR, rtlpci->receive_config); + rtl92c_phy_bb_config(hw); + rtlphy->rf_mode = RF_OP_BY_SW_3WIRE; + rtl92c_phy_rf_config(hw); + if (IS_VENDOR_UMC_A_CUT(rtlhal->version) && + !IS_92C_SERIAL(rtlhal->version)) { + rtl_set_rfreg(hw, RF90_PATH_A, RF_RX_G1, MASKDWORD, 0x30255); + rtl_set_rfreg(hw, RF90_PATH_A, RF_RX_G2, MASKDWORD, 0x50a00); + } else if (IS_81xxC_VENDOR_UMC_B_CUT(rtlhal->version)) { + rtl_set_rfreg(hw, RF90_PATH_A, 0x0C, MASKDWORD, 0x894AE); + rtl_set_rfreg(hw, RF90_PATH_A, 0x0A, MASKDWORD, 0x1AF31); + rtl_set_rfreg(hw, RF90_PATH_A, RF_IPA, MASKDWORD, 0x8F425); + rtl_set_rfreg(hw, RF90_PATH_A, RF_SYN_G2, MASKDWORD, 0x4F200); + rtl_set_rfreg(hw, RF90_PATH_A, RF_RCK1, MASKDWORD, 0x44053); + rtl_set_rfreg(hw, RF90_PATH_A, RF_RCK2, MASKDWORD, 0x80201); + } + rtlphy->rfreg_chnlval[0] = rtl_get_rfreg(hw, (enum radio_path)0, + RF_CHNLBW, RFREG_OFFSET_MASK); + rtlphy->rfreg_chnlval[1] = rtl_get_rfreg(hw, (enum radio_path)1, + RF_CHNLBW, RFREG_OFFSET_MASK); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BCCKEN, 0x1); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BOFDMEN, 0x1); + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER2, BIT(10), 1); + _rtl92ce_hw_configure(hw); + rtl_cam_reset_all_entry(hw); + rtl92ce_enable_hw_security_config(hw); + + ppsc->rfpwr_state = ERFON; + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_ETHER_ADDR, mac->mac_addr); + _rtl92ce_enable_aspm_back_door(hw); + rtlpriv->intf_ops->enable_aspm(hw); + + rtl8192ce_bt_hw_init(hw); + + if (ppsc->rfpwr_state == ERFON) { + rtl92c_phy_set_rfpath_switch(hw, 1); + if (rtlphy->iqk_initialized) { + rtl92c_phy_iq_calibrate(hw, true); + } else { + rtl92c_phy_iq_calibrate(hw, false); + rtlphy->iqk_initialized = true; + } + + rtl92c_dm_check_txpower_tracking(hw); + rtl92c_phy_lc_calibrate(hw); + } + + is92c = IS_92C_SERIAL(rtlhal->version); + tmp_u1b = efuse_read_1byte(hw, 0x1FA); + if (!(tmp_u1b & BIT(0))) { + rtl_set_rfreg(hw, RF90_PATH_A, 0x15, 0x0F, 0x05); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "PA BIAS path A\n"); + } + + if (!(tmp_u1b & BIT(1)) && is92c) { + rtl_set_rfreg(hw, RF90_PATH_B, 0x15, 0x0F, 0x05); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "PA BIAS path B\n"); + } + + if (!(tmp_u1b & BIT(4))) { + tmp_u1b = rtl_read_byte(rtlpriv, 0x16); + tmp_u1b &= 0x0F; + rtl_write_byte(rtlpriv, 0x16, tmp_u1b | 0x80); + udelay(10); + rtl_write_byte(rtlpriv, 0x16, tmp_u1b | 0x90); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "under 1.5V\n"); + } + rtl92c_dm_init(hw); + rtlpci->being_init_adapter = false; + return err; +} + +static enum version_8192c _rtl92ce_read_chip_version(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + enum version_8192c version = VERSION_UNKNOWN; + u32 value32; + const char *versionid; + + value32 = rtl_read_dword(rtlpriv, REG_SYS_CFG); + if (value32 & TRP_VAUX_EN) { + version = (value32 & TYPE_ID) ? VERSION_A_CHIP_92C : + VERSION_A_CHIP_88C; + } else { + version = (enum version_8192c) (CHIP_VER_B | + ((value32 & TYPE_ID) ? CHIP_92C_BITMASK : 0) | + ((value32 & VENDOR_ID) ? CHIP_VENDOR_UMC : 0)); + if ((!IS_CHIP_VENDOR_UMC(version)) && (value32 & + CHIP_VER_RTL_MASK)) { + version = (enum version_8192c)(version | + ((((value32 & CHIP_VER_RTL_MASK) == BIT(12)) + ? CHIP_VENDOR_UMC_B_CUT : CHIP_UNKNOWN) | + CHIP_VENDOR_UMC)); + } + if (IS_92C_SERIAL(version)) { + value32 = rtl_read_dword(rtlpriv, REG_HPON_FSM); + version = (enum version_8192c)(version | + ((CHIP_BONDING_IDENTIFIER(value32) + == CHIP_BONDING_92C_1T2R) ? + RF_TYPE_1T2R : 0)); + } + } + + switch (version) { + case VERSION_B_CHIP_92C: + versionid = "B_CHIP_92C"; + break; + case VERSION_B_CHIP_88C: + versionid = "B_CHIP_88C"; + break; + case VERSION_A_CHIP_92C: + versionid = "A_CHIP_92C"; + break; + case VERSION_A_CHIP_88C: + versionid = "A_CHIP_88C"; + break; + case VERSION_NORMAL_UMC_CHIP_92C_1T2R_A_CUT: + versionid = "A_CUT_92C_1T2R"; + break; + case VERSION_NORMAL_UMC_CHIP_92C_A_CUT: + versionid = "A_CUT_92C"; + break; + case VERSION_NORMAL_UMC_CHIP_88C_A_CUT: + versionid = "A_CUT_88C"; + break; + case VERSION_NORMAL_UMC_CHIP_92C_1T2R_B_CUT: + versionid = "B_CUT_92C_1T2R"; + break; + case VERSION_NORMAL_UMC_CHIP_92C_B_CUT: + versionid = "B_CUT_92C"; + break; + case VERSION_NORMAL_UMC_CHIP_88C_B_CUT: + versionid = "B_CUT_88C"; + break; + default: + versionid = "Unknown. Bug?"; + break; + } + + RT_TRACE(rtlpriv, COMP_INIT, DBG_EMERG, + "Chip Version ID: %s\n", versionid); + + switch (version & 0x3) { + case CHIP_88C: + rtlphy->rf_type = RF_1T1R; + break; + case CHIP_92C: + rtlphy->rf_type = RF_2T2R; + break; + case CHIP_92C_1T2R: + rtlphy->rf_type = RF_1T2R; + break; + default: + rtlphy->rf_type = RF_1T1R; + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "ERROR RF_Type is set!!\n"); + break; + } + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Chip RF Type: %s\n", + rtlphy->rf_type == RF_2T2R ? "RF_2T2R" : "RF_1T1R"); + + return version; +} + +static int _rtl92ce_set_media_status(struct ieee80211_hw *hw, + enum nl80211_iftype type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 bt_msr = rtl_read_byte(rtlpriv, MSR); + enum led_ctl_mode ledaction = LED_CTL_NO_LINK; + bt_msr &= 0xfc; + + if (type == NL80211_IFTYPE_UNSPECIFIED || + type == NL80211_IFTYPE_STATION) { + _rtl92ce_stop_tx_beacon(hw); + _rtl92ce_enable_bcn_sub_func(hw); + } else if (type == NL80211_IFTYPE_ADHOC || type == NL80211_IFTYPE_AP || + type == NL80211_IFTYPE_MESH_POINT) { + _rtl92ce_resume_tx_beacon(hw); + _rtl92ce_disable_bcn_sub_func(hw); + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Set HW_VAR_MEDIA_STATUS: No such media status(%x)\n", + type); + } + + switch (type) { + case NL80211_IFTYPE_UNSPECIFIED: + bt_msr |= MSR_NOLINK; + ledaction = LED_CTL_LINK; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to NO LINK!\n"); + break; + case NL80211_IFTYPE_ADHOC: + bt_msr |= MSR_ADHOC; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to Ad Hoc!\n"); + break; + case NL80211_IFTYPE_STATION: + bt_msr |= MSR_INFRA; + ledaction = LED_CTL_LINK; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to STA!\n"); + break; + case NL80211_IFTYPE_AP: + bt_msr |= MSR_AP; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to AP!\n"); + break; + case NL80211_IFTYPE_MESH_POINT: + bt_msr |= MSR_ADHOC; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to Mesh Point!\n"); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Network type %d not supported!\n", type); + return 1; + break; + + } + + rtl_write_byte(rtlpriv, (MSR), bt_msr); + rtlpriv->cfg->ops->led_control(hw, ledaction); + if ((bt_msr & 0xfc) == MSR_AP) + rtl_write_byte(rtlpriv, REG_BCNTCFG + 1, 0x00); + else + rtl_write_byte(rtlpriv, REG_BCNTCFG + 1, 0x66); + return 0; +} + +void rtl92ce_set_check_bssid(struct ieee80211_hw *hw, bool check_bssid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 reg_rcr = rtl_read_dword(rtlpriv, REG_RCR); + + if (rtlpriv->psc.rfpwr_state != ERFON) + return; + + if (check_bssid) { + reg_rcr |= (RCR_CBSSID_DATA | RCR_CBSSID_BCN); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_RCR, + (u8 *) (®_rcr)); + _rtl92ce_set_bcn_ctrl_reg(hw, 0, BIT(4)); + } else if (!check_bssid) { + reg_rcr &= (~(RCR_CBSSID_DATA | RCR_CBSSID_BCN)); + _rtl92ce_set_bcn_ctrl_reg(hw, BIT(4), 0); + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_RCR, (u8 *) (®_rcr)); + } + +} + +int rtl92ce_set_network_type(struct ieee80211_hw *hw, enum nl80211_iftype type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (_rtl92ce_set_media_status(hw, type)) + return -EOPNOTSUPP; + + if (rtlpriv->mac80211.link_state == MAC80211_LINKED) { + if (type != NL80211_IFTYPE_AP && + type != NL80211_IFTYPE_MESH_POINT) + rtl92ce_set_check_bssid(hw, true); + } else { + rtl92ce_set_check_bssid(hw, false); + } + + return 0; +} + +/* don't set REG_EDCA_BE_PARAM here because mac80211 will send pkt when scan */ +void rtl92ce_set_qos(struct ieee80211_hw *hw, int aci) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + rtl92c_dm_init_edca_turbo(hw); + switch (aci) { + case AC1_BK: + rtl_write_dword(rtlpriv, REG_EDCA_BK_PARAM, 0xa44f); + break; + case AC0_BE: + /* rtl_write_dword(rtlpriv, REG_EDCA_BE_PARAM, u4b_ac_param); */ + break; + case AC2_VI: + rtl_write_dword(rtlpriv, REG_EDCA_VI_PARAM, 0x5e4322); + break; + case AC3_VO: + rtl_write_dword(rtlpriv, REG_EDCA_VO_PARAM, 0x2f3222); + break; + default: + RT_ASSERT(false, "invalid aci: %d !\n", aci); + break; + } +} + +void rtl92ce_enable_interrupt(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + rtl_write_dword(rtlpriv, REG_HIMR, rtlpci->irq_mask[0] & 0xFFFFFFFF); + rtl_write_dword(rtlpriv, REG_HIMRE, rtlpci->irq_mask[1] & 0xFFFFFFFF); +} + +void rtl92ce_disable_interrupt(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + rtl_write_dword(rtlpriv, REG_HIMR, IMR8190_DISABLED); + rtl_write_dword(rtlpriv, REG_HIMRE, IMR8190_DISABLED); + synchronize_irq(rtlpci->pdev->irq); +} + +static void _rtl92ce_poweroff_adapter(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + u8 u1b_tmp; + u32 u4b_tmp; + + rtlpriv->intf_ops->enable_aspm(hw); + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0xFF); + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, RFREG_OFFSET_MASK, 0x00); + rtl_write_byte(rtlpriv, REG_RF_CTRL, 0x00); + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x40); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE2); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE0); + if (rtl_read_byte(rtlpriv, REG_MCUFWDL) & BIT(7)) + rtl92c_firmware_selfreset(hw); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN + 1, 0x51); + rtl_write_byte(rtlpriv, REG_MCUFWDL, 0x00); + rtl_write_dword(rtlpriv, REG_GPIO_PIN_CTRL, 0x00000000); + u1b_tmp = rtl_read_byte(rtlpriv, REG_GPIO_PIN_CTRL); + if ((rtlpcipriv->bt_coexist.bt_coexistence) && + ((rtlpcipriv->bt_coexist.bt_coexist_type == BT_CSR_BC4) || + (rtlpcipriv->bt_coexist.bt_coexist_type == BT_CSR_BC8))) { + rtl_write_dword(rtlpriv, REG_GPIO_PIN_CTRL, 0x00F30000 | + (u1b_tmp << 8)); + } else { + rtl_write_dword(rtlpriv, REG_GPIO_PIN_CTRL, 0x00FF0000 | + (u1b_tmp << 8)); + } + rtl_write_word(rtlpriv, REG_GPIO_IO_SEL, 0x0790); + rtl_write_word(rtlpriv, REG_LEDCFG0, 0x8080); + rtl_write_byte(rtlpriv, REG_AFE_PLL_CTRL, 0x80); + if (!IS_81xxC_VENDOR_UMC_B_CUT(rtlhal->version)) + rtl_write_byte(rtlpriv, REG_SPS0_CTRL, 0x23); + if (rtlpcipriv->bt_coexist.bt_coexistence) { + u4b_tmp = rtl_read_dword(rtlpriv, REG_AFE_XTAL_CTRL); + u4b_tmp |= 0x03824800; + rtl_write_dword(rtlpriv, REG_AFE_XTAL_CTRL, u4b_tmp); + } else { + rtl_write_dword(rtlpriv, REG_AFE_XTAL_CTRL, 0x0e); + } + + rtl_write_byte(rtlpriv, REG_RSV_CTRL, 0x0e); + rtl_write_byte(rtlpriv, REG_APS_FSMCO + 1, 0x10); +} + +void rtl92ce_card_disable(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + enum nl80211_iftype opmode; + + mac->link_state = MAC80211_NOLINK; + opmode = NL80211_IFTYPE_UNSPECIFIED; + _rtl92ce_set_media_status(hw, opmode); + if (rtlpci->driver_is_goingto_unload || + ppsc->rfoff_reason > RF_CHANGE_BY_PS) + rtlpriv->cfg->ops->led_control(hw, LED_CTL_POWER_OFF); + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + _rtl92ce_poweroff_adapter(hw); + + /* after power off we should do iqk again */ + rtlpriv->phy.iqk_initialized = false; +} + +void rtl92ce_interrupt_recognized(struct ieee80211_hw *hw, + u32 *p_inta, u32 *p_intb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + *p_inta = rtl_read_dword(rtlpriv, ISR) & rtlpci->irq_mask[0]; + rtl_write_dword(rtlpriv, ISR, *p_inta); + + /* + * *p_intb = rtl_read_dword(rtlpriv, REG_HISRE) & rtlpci->irq_mask[1]; + * rtl_write_dword(rtlpriv, ISR + 4, *p_intb); + */ +} + +void rtl92ce_set_beacon_related_registers(struct ieee80211_hw *hw) +{ + + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 bcn_interval, atim_window; + + bcn_interval = mac->beacon_interval; + atim_window = 2; /*FIX MERGE */ + rtl92ce_disable_interrupt(hw); + rtl_write_word(rtlpriv, REG_ATIMWND, atim_window); + rtl_write_word(rtlpriv, REG_BCN_INTERVAL, bcn_interval); + rtl_write_word(rtlpriv, REG_BCNTCFG, 0x660f); + rtl_write_byte(rtlpriv, REG_RXTSF_OFFSET_CCK, 0x18); + rtl_write_byte(rtlpriv, REG_RXTSF_OFFSET_OFDM, 0x18); + rtl_write_byte(rtlpriv, 0x606, 0x30); + rtl92ce_enable_interrupt(hw); +} + +void rtl92ce_set_beacon_interval(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 bcn_interval = mac->beacon_interval; + + RT_TRACE(rtlpriv, COMP_BEACON, DBG_DMESG, + "beacon_interval:%d\n", bcn_interval); + rtl92ce_disable_interrupt(hw); + rtl_write_word(rtlpriv, REG_BCN_INTERVAL, bcn_interval); + rtl92ce_enable_interrupt(hw); +} + +void rtl92ce_update_interrupt_mask(struct ieee80211_hw *hw, + u32 add_msr, u32 rm_msr) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + RT_TRACE(rtlpriv, COMP_INTR, DBG_LOUD, "add_msr:%x, rm_msr:%x\n", + add_msr, rm_msr); + + if (add_msr) + rtlpci->irq_mask[0] |= add_msr; + if (rm_msr) + rtlpci->irq_mask[0] &= (~rm_msr); + rtl92ce_disable_interrupt(hw); + rtl92ce_enable_interrupt(hw); +} + +static void _rtl92ce_read_txpower_info_from_hwpg(struct ieee80211_hw *hw, + bool autoload_fail, + u8 *hwinfo) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 rf_path, index, tempval; + u16 i; + + for (rf_path = 0; rf_path < 2; rf_path++) { + for (i = 0; i < 3; i++) { + if (!autoload_fail) { + rtlefuse-> + eeprom_chnlarea_txpwr_cck[rf_path][i] = + hwinfo[EEPROM_TXPOWERCCK + rf_path * 3 + i]; + rtlefuse-> + eeprom_chnlarea_txpwr_ht40_1s[rf_path][i] = + hwinfo[EEPROM_TXPOWERHT40_1S + rf_path * 3 + + i]; + } else { + rtlefuse-> + eeprom_chnlarea_txpwr_cck[rf_path][i] = + EEPROM_DEFAULT_TXPOWERLEVEL; + rtlefuse-> + eeprom_chnlarea_txpwr_ht40_1s[rf_path][i] = + EEPROM_DEFAULT_TXPOWERLEVEL; + } + } + } + + for (i = 0; i < 3; i++) { + if (!autoload_fail) + tempval = hwinfo[EEPROM_TXPOWERHT40_2SDIFF + i]; + else + tempval = EEPROM_DEFAULT_HT40_2SDIFF; + rtlefuse->eprom_chnl_txpwr_ht40_2sdf[RF90_PATH_A][i] = + (tempval & 0xf); + rtlefuse->eprom_chnl_txpwr_ht40_2sdf[RF90_PATH_B][i] = + ((tempval & 0xf0) >> 4); + } + + for (rf_path = 0; rf_path < 2; rf_path++) + for (i = 0; i < 3; i++) + RTPRINT(rtlpriv, FINIT, INIT_EEPROM, + "RF(%d) EEPROM CCK Area(%d) = 0x%x\n", + rf_path, i, + rtlefuse-> + eeprom_chnlarea_txpwr_cck[rf_path][i]); + for (rf_path = 0; rf_path < 2; rf_path++) + for (i = 0; i < 3; i++) + RTPRINT(rtlpriv, FINIT, INIT_EEPROM, + "RF(%d) EEPROM HT40 1S Area(%d) = 0x%x\n", + rf_path, i, + rtlefuse-> + eeprom_chnlarea_txpwr_ht40_1s[rf_path][i]); + for (rf_path = 0; rf_path < 2; rf_path++) + for (i = 0; i < 3; i++) + RTPRINT(rtlpriv, FINIT, INIT_EEPROM, + "RF(%d) EEPROM HT40 2S Diff Area(%d) = 0x%x\n", + rf_path, i, + rtlefuse-> + eprom_chnl_txpwr_ht40_2sdf[rf_path][i]); + + for (rf_path = 0; rf_path < 2; rf_path++) { + for (i = 0; i < 14; i++) { + index = _rtl92c_get_chnl_group((u8) i); + + rtlefuse->txpwrlevel_cck[rf_path][i] = + rtlefuse->eeprom_chnlarea_txpwr_cck[rf_path][index]; + rtlefuse->txpwrlevel_ht40_1s[rf_path][i] = + rtlefuse-> + eeprom_chnlarea_txpwr_ht40_1s[rf_path][index]; + + if ((rtlefuse-> + eeprom_chnlarea_txpwr_ht40_1s[rf_path][index] - + rtlefuse-> + eprom_chnl_txpwr_ht40_2sdf[rf_path][index]) + > 0) { + rtlefuse->txpwrlevel_ht40_2s[rf_path][i] = + rtlefuse-> + eeprom_chnlarea_txpwr_ht40_1s[rf_path] + [index] - + rtlefuse-> + eprom_chnl_txpwr_ht40_2sdf[rf_path] + [index]; + } else { + rtlefuse->txpwrlevel_ht40_2s[rf_path][i] = 0; + } + } + + for (i = 0; i < 14; i++) { + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF(%d)-Ch(%d) [CCK / HT40_1S / HT40_2S] = [0x%x / 0x%x / 0x%x]\n", + rf_path, i, + rtlefuse->txpwrlevel_cck[rf_path][i], + rtlefuse->txpwrlevel_ht40_1s[rf_path][i], + rtlefuse->txpwrlevel_ht40_2s[rf_path][i]); + } + } + + for (i = 0; i < 3; i++) { + if (!autoload_fail) { + rtlefuse->eeprom_pwrlimit_ht40[i] = + hwinfo[EEPROM_TXPWR_GROUP + i]; + rtlefuse->eeprom_pwrlimit_ht20[i] = + hwinfo[EEPROM_TXPWR_GROUP + 3 + i]; + } else { + rtlefuse->eeprom_pwrlimit_ht40[i] = 0; + rtlefuse->eeprom_pwrlimit_ht20[i] = 0; + } + } + + for (rf_path = 0; rf_path < 2; rf_path++) { + for (i = 0; i < 14; i++) { + index = _rtl92c_get_chnl_group((u8) i); + + if (rf_path == RF90_PATH_A) { + rtlefuse->pwrgroup_ht20[rf_path][i] = + (rtlefuse->eeprom_pwrlimit_ht20[index] + & 0xf); + rtlefuse->pwrgroup_ht40[rf_path][i] = + (rtlefuse->eeprom_pwrlimit_ht40[index] + & 0xf); + } else if (rf_path == RF90_PATH_B) { + rtlefuse->pwrgroup_ht20[rf_path][i] = + ((rtlefuse->eeprom_pwrlimit_ht20[index] + & 0xf0) >> 4); + rtlefuse->pwrgroup_ht40[rf_path][i] = + ((rtlefuse->eeprom_pwrlimit_ht40[index] + & 0xf0) >> 4); + } + + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-%d pwrgroup_ht20[%d] = 0x%x\n", + rf_path, i, + rtlefuse->pwrgroup_ht20[rf_path][i]); + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-%d pwrgroup_ht40[%d] = 0x%x\n", + rf_path, i, + rtlefuse->pwrgroup_ht40[rf_path][i]); + } + } + + for (i = 0; i < 14; i++) { + index = _rtl92c_get_chnl_group((u8) i); + + if (!autoload_fail) + tempval = hwinfo[EEPROM_TXPOWERHT20DIFF + index]; + else + tempval = EEPROM_DEFAULT_HT20_DIFF; + + rtlefuse->txpwr_ht20diff[RF90_PATH_A][i] = (tempval & 0xF); + rtlefuse->txpwr_ht20diff[RF90_PATH_B][i] = + ((tempval >> 4) & 0xF); + + if (rtlefuse->txpwr_ht20diff[RF90_PATH_A][i] & BIT(3)) + rtlefuse->txpwr_ht20diff[RF90_PATH_A][i] |= 0xF0; + + if (rtlefuse->txpwr_ht20diff[RF90_PATH_B][i] & BIT(3)) + rtlefuse->txpwr_ht20diff[RF90_PATH_B][i] |= 0xF0; + + index = _rtl92c_get_chnl_group((u8) i); + + if (!autoload_fail) + tempval = hwinfo[EEPROM_TXPOWER_OFDMDIFF + index]; + else + tempval = EEPROM_DEFAULT_LEGACYHTTXPOWERDIFF; + + rtlefuse->txpwr_legacyhtdiff[RF90_PATH_A][i] = (tempval & 0xF); + rtlefuse->txpwr_legacyhtdiff[RF90_PATH_B][i] = + ((tempval >> 4) & 0xF); + } + + rtlefuse->legacy_ht_txpowerdiff = + rtlefuse->txpwr_legacyhtdiff[RF90_PATH_A][7]; + + for (i = 0; i < 14; i++) + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-A Ht20 to HT40 Diff[%d] = 0x%x\n", + i, rtlefuse->txpwr_ht20diff[RF90_PATH_A][i]); + for (i = 0; i < 14; i++) + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-A Legacy to Ht40 Diff[%d] = 0x%x\n", + i, rtlefuse->txpwr_legacyhtdiff[RF90_PATH_A][i]); + for (i = 0; i < 14; i++) + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-B Ht20 to HT40 Diff[%d] = 0x%x\n", + i, rtlefuse->txpwr_ht20diff[RF90_PATH_B][i]); + for (i = 0; i < 14; i++) + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-B Legacy to HT40 Diff[%d] = 0x%x\n", + i, rtlefuse->txpwr_legacyhtdiff[RF90_PATH_B][i]); + + if (!autoload_fail) + rtlefuse->eeprom_regulatory = (hwinfo[RF_OPTION1] & 0x7); + else + rtlefuse->eeprom_regulatory = 0; + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "eeprom_regulatory = 0x%x\n", rtlefuse->eeprom_regulatory); + + if (!autoload_fail) { + rtlefuse->eeprom_tssi[RF90_PATH_A] = hwinfo[EEPROM_TSSI_A]; + rtlefuse->eeprom_tssi[RF90_PATH_B] = hwinfo[EEPROM_TSSI_B]; + } else { + rtlefuse->eeprom_tssi[RF90_PATH_A] = EEPROM_DEFAULT_TSSI; + rtlefuse->eeprom_tssi[RF90_PATH_B] = EEPROM_DEFAULT_TSSI; + } + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, "TSSI_A = 0x%x, TSSI_B = 0x%x\n", + rtlefuse->eeprom_tssi[RF90_PATH_A], + rtlefuse->eeprom_tssi[RF90_PATH_B]); + + if (!autoload_fail) + tempval = hwinfo[EEPROM_THERMAL_METER]; + else + tempval = EEPROM_DEFAULT_THERMALMETER; + rtlefuse->eeprom_thermalmeter = (tempval & 0x1f); + + if (rtlefuse->eeprom_thermalmeter == 0x1f || autoload_fail) + rtlefuse->apk_thermalmeterignore = true; + + rtlefuse->thermalmeter[0] = rtlefuse->eeprom_thermalmeter; + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "thermalmeter = 0x%x\n", rtlefuse->eeprom_thermalmeter); +} + +static void _rtl92ce_read_adapter_info(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u16 i, usvalue; + u8 hwinfo[HWSET_MAX_SIZE]; + u16 eeprom_id; + + if (rtlefuse->epromtype == EEPROM_BOOT_EFUSE) { + rtl_efuse_shadow_map_update(hw); + + memcpy((void *)hwinfo, + (void *)&rtlefuse->efuse_map[EFUSE_INIT_MAP][0], + HWSET_MAX_SIZE); + } else if (rtlefuse->epromtype == EEPROM_93C46) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "RTL819X Not boot from eeprom, check it !!"); + } + + RT_PRINT_DATA(rtlpriv, COMP_INIT, DBG_DMESG, "MAP", + hwinfo, HWSET_MAX_SIZE); + + eeprom_id = *((u16 *)&hwinfo[0]); + if (eeprom_id != RTL8190_EEPROM_ID) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "EEPROM ID(%#x) is invalid!!\n", eeprom_id); + rtlefuse->autoload_failflag = true; + } else { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Autoload OK\n"); + rtlefuse->autoload_failflag = false; + } + + if (rtlefuse->autoload_failflag) + return; + + rtlefuse->eeprom_vid = *(u16 *)&hwinfo[EEPROM_VID]; + rtlefuse->eeprom_did = *(u16 *)&hwinfo[EEPROM_DID]; + rtlefuse->eeprom_svid = *(u16 *)&hwinfo[EEPROM_SVID]; + rtlefuse->eeprom_smid = *(u16 *)&hwinfo[EEPROM_SMID]; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROMId = 0x%4x\n", eeprom_id); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM VID = 0x%4x\n", rtlefuse->eeprom_vid); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM DID = 0x%4x\n", rtlefuse->eeprom_did); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM SVID = 0x%4x\n", rtlefuse->eeprom_svid); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM SMID = 0x%4x\n", rtlefuse->eeprom_smid); + + for (i = 0; i < 6; i += 2) { + usvalue = *(u16 *)&hwinfo[EEPROM_MAC_ADDR + i]; + *((u16 *) (&rtlefuse->dev_addr[i])) = usvalue; + } + + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "%pM\n", rtlefuse->dev_addr); + + _rtl92ce_read_txpower_info_from_hwpg(hw, + rtlefuse->autoload_failflag, + hwinfo); + + rtl8192ce_read_bt_coexist_info_from_hwpg(hw, + rtlefuse->autoload_failflag, + hwinfo); + + rtlefuse->eeprom_channelplan = *&hwinfo[EEPROM_CHANNELPLAN]; + rtlefuse->eeprom_version = *(u16 *)&hwinfo[EEPROM_VERSION]; + rtlefuse->txpwr_fromeprom = true; + rtlefuse->eeprom_oemid = *&hwinfo[EEPROM_CUSTOMER_ID]; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM Customer ID: 0x%2x\n", rtlefuse->eeprom_oemid); + + /* set channel paln to world wide 13 */ + rtlefuse->channel_plan = COUNTRY_CODE_WORLD_WIDE_13; + + if (rtlhal->oem_id == RT_CID_DEFAULT) { + switch (rtlefuse->eeprom_oemid) { + case EEPROM_CID_DEFAULT: + if (rtlefuse->eeprom_did == 0x8176) { + if ((rtlefuse->eeprom_svid == 0x103C && + rtlefuse->eeprom_smid == 0x1629)) + rtlhal->oem_id = RT_CID_819x_HP; + else + rtlhal->oem_id = RT_CID_DEFAULT; + } else { + rtlhal->oem_id = RT_CID_DEFAULT; + } + break; + case EEPROM_CID_TOSHIBA: + rtlhal->oem_id = RT_CID_TOSHIBA; + break; + case EEPROM_CID_QMI: + rtlhal->oem_id = RT_CID_819x_QMI; + break; + case EEPROM_CID_WHQL: + default: + rtlhal->oem_id = RT_CID_DEFAULT; + break; + + } + } + +} + +static void _rtl92ce_hal_customized_behavior(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + switch (rtlhal->oem_id) { + case RT_CID_819x_HP: + pcipriv->ledctl.led_opendrain = true; + break; + case RT_CID_819x_Lenovo: + case RT_CID_DEFAULT: + case RT_CID_TOSHIBA: + case RT_CID_CCX: + case RT_CID_819x_Acer: + case RT_CID_WHQL: + default: + break; + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "RT Customized ID: 0x%02X\n", rtlhal->oem_id); +} + +void rtl92ce_read_eeprom_info(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 tmp_u1b; + + rtlhal->version = _rtl92ce_read_chip_version(hw); + if (get_rf_type(rtlphy) == RF_1T1R) + rtlpriv->dm.rfpath_rxenable[0] = true; + else + rtlpriv->dm.rfpath_rxenable[0] = + rtlpriv->dm.rfpath_rxenable[1] = true; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "VersionID = 0x%4x\n", + rtlhal->version); + tmp_u1b = rtl_read_byte(rtlpriv, REG_9346CR); + if (tmp_u1b & BIT(4)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "Boot from EEPROM\n"); + rtlefuse->epromtype = EEPROM_93C46; + } else { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "Boot from EFUSE\n"); + rtlefuse->epromtype = EEPROM_BOOT_EFUSE; + } + if (tmp_u1b & BIT(5)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Autoload OK\n"); + rtlefuse->autoload_failflag = false; + _rtl92ce_read_adapter_info(hw); + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Autoload ERR!!\n"); + } + _rtl92ce_hal_customized_behavior(hw); +} + +static void rtl92ce_update_hal_rate_table(struct ieee80211_hw *hw, + struct ieee80211_sta *sta) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u32 ratr_value; + u8 ratr_index = 0; + u8 nmode = mac->ht_enable; + u8 mimo_ps = IEEE80211_SMPS_OFF; + u16 shortgi_rate; + u32 tmp_ratr_value; + u8 curtxbw_40mhz = mac->bw_40; + u8 curshortgi_40mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_40) ? + 1 : 0; + u8 curshortgi_20mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_20) ? + 1 : 0; + enum wireless_mode wirelessmode = mac->mode; + + if (rtlhal->current_bandtype == BAND_ON_5G) + ratr_value = sta->supp_rates[1] << 4; + else + ratr_value = sta->supp_rates[0]; + if (mac->opmode == NL80211_IFTYPE_ADHOC) + ratr_value = 0xfff; + + ratr_value |= (sta->ht_cap.mcs.rx_mask[1] << 20 | + sta->ht_cap.mcs.rx_mask[0] << 12); + switch (wirelessmode) { + case WIRELESS_MODE_B: + if (ratr_value & 0x0000000c) + ratr_value &= 0x0000000d; + else + ratr_value &= 0x0000000f; + break; + case WIRELESS_MODE_G: + ratr_value &= 0x00000FF5; + break; + case WIRELESS_MODE_N_24G: + case WIRELESS_MODE_N_5G: + nmode = 1; + if (mimo_ps == IEEE80211_SMPS_STATIC) { + ratr_value &= 0x0007F005; + } else { + u32 ratr_mask; + + if (get_rf_type(rtlphy) == RF_1T2R || + get_rf_type(rtlphy) == RF_1T1R) + ratr_mask = 0x000ff005; + else + ratr_mask = 0x0f0ff005; + + ratr_value &= ratr_mask; + } + break; + default: + if (rtlphy->rf_type == RF_1T2R) + ratr_value &= 0x000ff0ff; + else + ratr_value &= 0x0f0ff0ff; + + break; + } + + if ((rtlpcipriv->bt_coexist.bt_coexistence) && + (rtlpcipriv->bt_coexist.bt_coexist_type == BT_CSR_BC4) && + (rtlpcipriv->bt_coexist.bt_cur_state) && + (rtlpcipriv->bt_coexist.bt_ant_isolation) && + ((rtlpcipriv->bt_coexist.bt_service == BT_SCO) || + (rtlpcipriv->bt_coexist.bt_service == BT_BUSY))) + ratr_value &= 0x0fffcfc0; + else + ratr_value &= 0x0FFFFFFF; + + if (nmode && ((curtxbw_40mhz && + curshortgi_40mhz) || (!curtxbw_40mhz && + curshortgi_20mhz))) { + + ratr_value |= 0x10000000; + tmp_ratr_value = (ratr_value >> 12); + + for (shortgi_rate = 15; shortgi_rate > 0; shortgi_rate--) { + if ((1 << shortgi_rate) & tmp_ratr_value) + break; + } + + shortgi_rate = (shortgi_rate << 12) | (shortgi_rate << 8) | + (shortgi_rate << 4) | (shortgi_rate); + } + + rtl_write_dword(rtlpriv, REG_ARFR0 + ratr_index * 4, ratr_value); + + RT_TRACE(rtlpriv, COMP_RATR, DBG_DMESG, "%x\n", + rtl_read_dword(rtlpriv, REG_ARFR0)); +} + +static void rtl92ce_update_hal_rate_mask(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 rssi_level) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_sta_info *sta_entry = NULL; + u32 ratr_bitmap; + u8 ratr_index; + u8 curtxbw_40mhz = (sta->bandwidth >= IEEE80211_STA_RX_BW_40) ? 1 : 0; + u8 curshortgi_40mhz = curtxbw_40mhz && + (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_40) ? + 1 : 0; + u8 curshortgi_20mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_20) ? + 1 : 0; + enum wireless_mode wirelessmode = 0; + bool shortgi = false; + u8 rate_mask[5]; + u8 macid = 0; + u8 mimo_ps = IEEE80211_SMPS_OFF; + + sta_entry = (struct rtl_sta_info *) sta->drv_priv; + wirelessmode = sta_entry->wireless_mode; + if (mac->opmode == NL80211_IFTYPE_STATION || + mac->opmode == NL80211_IFTYPE_MESH_POINT) + curtxbw_40mhz = mac->bw_40; + else if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_ADHOC) + macid = sta->aid + 1; + + if (rtlhal->current_bandtype == BAND_ON_5G) + ratr_bitmap = sta->supp_rates[1] << 4; + else + ratr_bitmap = sta->supp_rates[0]; + if (mac->opmode == NL80211_IFTYPE_ADHOC) + ratr_bitmap = 0xfff; + ratr_bitmap |= (sta->ht_cap.mcs.rx_mask[1] << 20 | + sta->ht_cap.mcs.rx_mask[0] << 12); + switch (wirelessmode) { + case WIRELESS_MODE_B: + ratr_index = RATR_INX_WIRELESS_B; + if (ratr_bitmap & 0x0000000c) + ratr_bitmap &= 0x0000000d; + else + ratr_bitmap &= 0x0000000f; + break; + case WIRELESS_MODE_G: + ratr_index = RATR_INX_WIRELESS_GB; + + if (rssi_level == 1) + ratr_bitmap &= 0x00000f00; + else if (rssi_level == 2) + ratr_bitmap &= 0x00000ff0; + else + ratr_bitmap &= 0x00000ff5; + break; + case WIRELESS_MODE_A: + ratr_index = RATR_INX_WIRELESS_A; + ratr_bitmap &= 0x00000ff0; + break; + case WIRELESS_MODE_N_24G: + case WIRELESS_MODE_N_5G: + ratr_index = RATR_INX_WIRELESS_NGB; + + if (mimo_ps == IEEE80211_SMPS_STATIC) { + if (rssi_level == 1) + ratr_bitmap &= 0x00070000; + else if (rssi_level == 2) + ratr_bitmap &= 0x0007f000; + else + ratr_bitmap &= 0x0007f005; + } else { + if (rtlphy->rf_type == RF_1T2R || + rtlphy->rf_type == RF_1T1R) { + if (curtxbw_40mhz) { + if (rssi_level == 1) + ratr_bitmap &= 0x000f0000; + else if (rssi_level == 2) + ratr_bitmap &= 0x000ff000; + else + ratr_bitmap &= 0x000ff015; + } else { + if (rssi_level == 1) + ratr_bitmap &= 0x000f0000; + else if (rssi_level == 2) + ratr_bitmap &= 0x000ff000; + else + ratr_bitmap &= 0x000ff005; + } + } else { + if (curtxbw_40mhz) { + if (rssi_level == 1) + ratr_bitmap &= 0x0f0f0000; + else if (rssi_level == 2) + ratr_bitmap &= 0x0f0ff000; + else + ratr_bitmap &= 0x0f0ff015; + } else { + if (rssi_level == 1) + ratr_bitmap &= 0x0f0f0000; + else if (rssi_level == 2) + ratr_bitmap &= 0x0f0ff000; + else + ratr_bitmap &= 0x0f0ff005; + } + } + } + + if ((curtxbw_40mhz && curshortgi_40mhz) || + (!curtxbw_40mhz && curshortgi_20mhz)) { + + if (macid == 0) + shortgi = true; + else if (macid == 1) + shortgi = false; + } + break; + default: + ratr_index = RATR_INX_WIRELESS_NGB; + + if (rtlphy->rf_type == RF_1T2R) + ratr_bitmap &= 0x000ff0ff; + else + ratr_bitmap &= 0x0f0ff0ff; + break; + } + sta_entry->ratr_index = ratr_index; + + RT_TRACE(rtlpriv, COMP_RATR, DBG_DMESG, + "ratr_bitmap :%x\n", ratr_bitmap); + *(u32 *)&rate_mask = (ratr_bitmap & 0x0fffffff) | + (ratr_index << 28); + rate_mask[4] = macid | (shortgi ? 0x20 : 0x00) | 0x80; + RT_TRACE(rtlpriv, COMP_RATR, DBG_DMESG, + "Rate_index:%x, ratr_val:%x, %5phC\n", + ratr_index, ratr_bitmap, rate_mask); + rtl92c_fill_h2c_cmd(hw, H2C_RA_MASK, 5, rate_mask); + + if (macid != 0) + sta_entry->ratr_index = ratr_index; +} + +void rtl92ce_update_hal_rate_tbl(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 rssi_level) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (rtlpriv->dm.useramask) + rtl92ce_update_hal_rate_mask(hw, sta, rssi_level); + else + rtl92ce_update_hal_rate_table(hw, sta); +} + +void rtl92ce_update_channel_access_setting(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 sifs_timer; + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SLOT_TIME, + &mac->slot_time); + if (!mac->ht_enable) + sifs_timer = 0x0a0a; + else + sifs_timer = 0x1010; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SIFS, (u8 *)&sifs_timer); +} + +bool rtl92ce_gpio_radio_on_off_checking(struct ieee80211_hw *hw, u8 *valid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + enum rf_pwrstate e_rfpowerstate_toset; + u8 u1tmp; + bool actuallyset = false; + unsigned long flag; + + if (rtlpci->being_init_adapter) + return false; + + if (ppsc->swrf_processing) + return false; + + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); + if (ppsc->rfchange_inprogress) { + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + return false; + } else { + ppsc->rfchange_inprogress = true; + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + } + + rtl_write_byte(rtlpriv, REG_MAC_PINMUX_CFG, rtl_read_byte(rtlpriv, + REG_MAC_PINMUX_CFG)&~(BIT(3))); + + u1tmp = rtl_read_byte(rtlpriv, REG_GPIO_IO_SEL); + e_rfpowerstate_toset = (u1tmp & BIT(3)) ? ERFON : ERFOFF; + + if ((ppsc->hwradiooff) && (e_rfpowerstate_toset == ERFON)) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "GPIOChangeRF - HW Radio ON, RF ON\n"); + + e_rfpowerstate_toset = ERFON; + ppsc->hwradiooff = false; + actuallyset = true; + } else if (!ppsc->hwradiooff && (e_rfpowerstate_toset == ERFOFF)) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "GPIOChangeRF - HW Radio OFF, RF OFF\n"); + + e_rfpowerstate_toset = ERFOFF; + ppsc->hwradiooff = true; + actuallyset = true; + } + + if (actuallyset) { + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); + ppsc->rfchange_inprogress = false; + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + } else { + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_HALT_NIC) + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); + ppsc->rfchange_inprogress = false; + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + } + + *valid = 1; + return !ppsc->hwradiooff; + +} + +void rtl92ce_set_key(struct ieee80211_hw *hw, u32 key_index, + u8 *p_macaddr, bool is_group, u8 enc_algo, + bool is_wepkey, bool clear_all) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 *macaddr = p_macaddr; + u32 entry_id = 0; + bool is_pairwise = false; + + static u8 cam_const_addr[4][6] = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x03} + }; + static u8 cam_const_broad[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + if (clear_all) { + u8 idx = 0; + u8 cam_offset = 0; + u8 clear_number = 5; + + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, "clear_all\n"); + + for (idx = 0; idx < clear_number; idx++) { + rtl_cam_mark_invalid(hw, cam_offset + idx); + rtl_cam_empty_entry(hw, cam_offset + idx); + + if (idx < 5) { + memset(rtlpriv->sec.key_buf[idx], 0, + MAX_KEY_LEN); + rtlpriv->sec.key_len[idx] = 0; + } + } + + } else { + switch (enc_algo) { + case WEP40_ENCRYPTION: + enc_algo = CAM_WEP40; + break; + case WEP104_ENCRYPTION: + enc_algo = CAM_WEP104; + break; + case TKIP_ENCRYPTION: + enc_algo = CAM_TKIP; + break; + case AESCCMP_ENCRYPTION: + enc_algo = CAM_AES; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + enc_algo = CAM_TKIP; + break; + } + + if (is_wepkey || rtlpriv->sec.use_defaultkey) { + macaddr = cam_const_addr[key_index]; + entry_id = key_index; + } else { + if (is_group) { + macaddr = cam_const_broad; + entry_id = key_index; + } else { + if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_MESH_POINT) { + entry_id = rtl_cam_get_free_entry(hw, + p_macaddr); + if (entry_id >= TOTAL_CAM_ENTRY) { + RT_TRACE(rtlpriv, COMP_SEC, + DBG_EMERG, + "Can not find free hw security cam entry\n"); + return; + } + } else { + entry_id = CAM_PAIRWISE_KEY_POSITION; + } + + key_index = PAIRWISE_KEYIDX; + is_pairwise = true; + } + } + + if (rtlpriv->sec.key_len[key_index] == 0) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "delete one entry, entry_id is %d\n", + entry_id); + if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_MESH_POINT) + rtl_cam_del_entry(hw, p_macaddr); + rtl_cam_delete_one_entry(hw, p_macaddr, entry_id); + } else { + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, + "The insert KEY length is %d\n", + rtlpriv->sec.key_len[PAIRWISE_KEYIDX]); + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, + "The insert KEY is %x %x\n", + rtlpriv->sec.key_buf[0][0], + rtlpriv->sec.key_buf[0][1]); + + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "add one entry\n"); + if (is_pairwise) { + RT_PRINT_DATA(rtlpriv, COMP_SEC, DBG_LOUD, + "Pairwise Key content", + rtlpriv->sec.pairwise_key, + rtlpriv->sec. + key_len[PAIRWISE_KEYIDX]); + + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "set Pairwise key\n"); + + rtl_cam_add_one_entry(hw, macaddr, key_index, + entry_id, enc_algo, + CAM_CONFIG_NO_USEDK, + rtlpriv->sec. + key_buf[key_index]); + } else { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "set group key\n"); + + if (mac->opmode == NL80211_IFTYPE_ADHOC) { + rtl_cam_add_one_entry(hw, + rtlefuse->dev_addr, + PAIRWISE_KEYIDX, + CAM_PAIRWISE_KEY_POSITION, + enc_algo, + CAM_CONFIG_NO_USEDK, + rtlpriv->sec.key_buf + [entry_id]); + } + + rtl_cam_add_one_entry(hw, macaddr, key_index, + entry_id, enc_algo, + CAM_CONFIG_NO_USEDK, + rtlpriv->sec.key_buf[entry_id]); + } + + } + } +} + +static void rtl8192ce_bt_var_init(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + + rtlpcipriv->bt_coexist.bt_coexistence = + rtlpcipriv->bt_coexist.eeprom_bt_coexist; + rtlpcipriv->bt_coexist.bt_ant_num = + rtlpcipriv->bt_coexist.eeprom_bt_ant_num; + rtlpcipriv->bt_coexist.bt_coexist_type = + rtlpcipriv->bt_coexist.eeprom_bt_type; + + if (rtlpcipriv->bt_coexist.reg_bt_iso == 2) + rtlpcipriv->bt_coexist.bt_ant_isolation = + rtlpcipriv->bt_coexist.eeprom_bt_ant_isol; + else + rtlpcipriv->bt_coexist.bt_ant_isolation = + rtlpcipriv->bt_coexist.reg_bt_iso; + + rtlpcipriv->bt_coexist.bt_radio_shared_type = + rtlpcipriv->bt_coexist.eeprom_bt_radio_shared; + + if (rtlpcipriv->bt_coexist.bt_coexistence) { + + if (rtlpcipriv->bt_coexist.reg_bt_sco == 1) + rtlpcipriv->bt_coexist.bt_service = BT_OTHER_ACTION; + else if (rtlpcipriv->bt_coexist.reg_bt_sco == 2) + rtlpcipriv->bt_coexist.bt_service = BT_SCO; + else if (rtlpcipriv->bt_coexist.reg_bt_sco == 4) + rtlpcipriv->bt_coexist.bt_service = BT_BUSY; + else if (rtlpcipriv->bt_coexist.reg_bt_sco == 5) + rtlpcipriv->bt_coexist.bt_service = BT_OTHERBUSY; + else + rtlpcipriv->bt_coexist.bt_service = BT_IDLE; + + rtlpcipriv->bt_coexist.bt_edca_ul = 0; + rtlpcipriv->bt_coexist.bt_edca_dl = 0; + rtlpcipriv->bt_coexist.bt_rssi_state = 0xff; + } +} + +void rtl8192ce_read_bt_coexist_info_from_hwpg(struct ieee80211_hw *hw, + bool auto_load_fail, u8 *hwinfo) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + u8 val; + + if (!auto_load_fail) { + rtlpcipriv->bt_coexist.eeprom_bt_coexist = + ((hwinfo[RF_OPTION1] & 0xe0) >> 5); + val = hwinfo[RF_OPTION4]; + rtlpcipriv->bt_coexist.eeprom_bt_type = ((val & 0xe) >> 1); + rtlpcipriv->bt_coexist.eeprom_bt_ant_num = (val & 0x1); + rtlpcipriv->bt_coexist.eeprom_bt_ant_isol = ((val & 0x10) >> 4); + rtlpcipriv->bt_coexist.eeprom_bt_radio_shared = + ((val & 0x20) >> 5); + } else { + rtlpcipriv->bt_coexist.eeprom_bt_coexist = 0; + rtlpcipriv->bt_coexist.eeprom_bt_type = BT_2WIRE; + rtlpcipriv->bt_coexist.eeprom_bt_ant_num = ANT_X2; + rtlpcipriv->bt_coexist.eeprom_bt_ant_isol = 0; + rtlpcipriv->bt_coexist.eeprom_bt_radio_shared = BT_RADIO_SHARED; + } + + rtl8192ce_bt_var_init(hw); +} + +void rtl8192ce_bt_reg_init(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + + /* 0:Low, 1:High, 2:From Efuse. */ + rtlpcipriv->bt_coexist.reg_bt_iso = 2; + /* 0:Idle, 1:None-SCO, 2:SCO, 3:From Counter. */ + rtlpcipriv->bt_coexist.reg_bt_sco = 3; + /* 0:Disable BT control A-MPDU, 1:Enable BT control A-MPDU. */ + rtlpcipriv->bt_coexist.reg_bt_sco = 0; +} + + +void rtl8192ce_bt_hw_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + + u8 u1_tmp; + + if (rtlpcipriv->bt_coexist.bt_coexistence && + ((rtlpcipriv->bt_coexist.bt_coexist_type == BT_CSR_BC4) || + rtlpcipriv->bt_coexist.bt_coexist_type == BT_CSR_BC8)) { + + if (rtlpcipriv->bt_coexist.bt_ant_isolation) + rtl_write_byte(rtlpriv, REG_GPIO_MUXCFG, 0xa0); + + u1_tmp = rtl_read_byte(rtlpriv, 0x4fd) & + BIT_OFFSET_LEN_MASK_32(0, 1); + u1_tmp = u1_tmp | + ((rtlpcipriv->bt_coexist.bt_ant_isolation == 1) ? + 0 : BIT_OFFSET_LEN_MASK_32(1, 1)) | + ((rtlpcipriv->bt_coexist.bt_service == BT_SCO) ? + 0 : BIT_OFFSET_LEN_MASK_32(2, 1)); + rtl_write_byte(rtlpriv, 0x4fd, u1_tmp); + + rtl_write_dword(rtlpriv, REG_BT_COEX_TABLE+4, 0xaaaa9aaa); + rtl_write_dword(rtlpriv, REG_BT_COEX_TABLE+8, 0xffbd0040); + rtl_write_dword(rtlpriv, REG_BT_COEX_TABLE+0xc, 0x40000010); + + /* Config to 1T1R. */ + if (rtlphy->rf_type == RF_1T1R) { + u1_tmp = rtl_read_byte(rtlpriv, ROFDM0_TRXPATHENABLE); + u1_tmp &= ~(BIT_OFFSET_LEN_MASK_32(1, 1)); + rtl_write_byte(rtlpriv, ROFDM0_TRXPATHENABLE, u1_tmp); + + u1_tmp = rtl_read_byte(rtlpriv, ROFDM1_TRXPATHENABLE); + u1_tmp &= ~(BIT_OFFSET_LEN_MASK_32(1, 1)); + rtl_write_byte(rtlpriv, ROFDM1_TRXPATHENABLE, u1_tmp); + } + } +} + +void rtl92ce_suspend(struct ieee80211_hw *hw) +{ +} + +void rtl92ce_resume(struct ieee80211_hw *hw) +{ +} + +/* Turn on AAP (RCR:bit 0) for promicuous mode. */ +void rtl92ce_allow_all_destaddr(struct ieee80211_hw *hw, + bool allow_all_da, bool write_into_reg) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + if (allow_all_da) {/* Set BIT0 */ + rtlpci->receive_config |= RCR_AAP; + } else {/* Clear BIT0 */ + rtlpci->receive_config &= ~RCR_AAP; + } + + if (write_into_reg) + rtl_write_dword(rtlpriv, REG_RCR, rtlpci->receive_config); + + RT_TRACE(rtlpriv, COMP_TURBO | COMP_INIT, DBG_LOUD, + "receive_config=0x%08X, write_into_reg=%d\n", + rtlpci->receive_config, write_into_reg); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/hw.h b/drivers/net/wireless/rtlwifi/rtl8192ce/hw.h new file mode 100644 index 0000000..2d063b0 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/hw.h @@ -0,0 +1,82 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CE_HW_H__ +#define __RTL92CE_HW_H__ + +static inline u8 _rtl92c_get_chnl_group(u8 chnl) +{ + u8 group; + + if (chnl < 3) + group = 0; + else if (chnl < 9) + group = 1; + else + group = 2; + return group; +} + +void rtl92ce_get_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val); +void rtl92ce_read_eeprom_info(struct ieee80211_hw *hw); +void rtl92ce_interrupt_recognized(struct ieee80211_hw *hw, + u32 *p_inta, u32 *p_intb); +int rtl92ce_hw_init(struct ieee80211_hw *hw); +void rtl92ce_card_disable(struct ieee80211_hw *hw); +void rtl92ce_enable_interrupt(struct ieee80211_hw *hw); +void rtl92ce_disable_interrupt(struct ieee80211_hw *hw); +int rtl92ce_set_network_type(struct ieee80211_hw *hw, enum nl80211_iftype type); +void rtl92ce_set_check_bssid(struct ieee80211_hw *hw, bool check_bssid); +void rtl92ce_set_qos(struct ieee80211_hw *hw, int aci); +void rtl92ce_set_beacon_related_registers(struct ieee80211_hw *hw); +void rtl92ce_set_beacon_interval(struct ieee80211_hw *hw); +void rtl92ce_update_interrupt_mask(struct ieee80211_hw *hw, + u32 add_msr, u32 rm_msr); +void rtl92ce_set_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val); +void rtl92ce_update_hal_rate_tbl(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 rssi_level); +void rtl92ce_update_hal_rate_tbl(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 rssi_level); +void rtl92ce_update_channel_access_setting(struct ieee80211_hw *hw); +bool rtl92ce_gpio_radio_on_off_checking(struct ieee80211_hw *hw, u8 *valid); +void rtl92ce_enable_hw_security_config(struct ieee80211_hw *hw); +void rtl92ce_set_key(struct ieee80211_hw *hw, u32 key_index, + u8 *p_macaddr, bool is_group, u8 enc_algo, + bool is_wepkey, bool clear_all); + +void rtl8192ce_read_bt_coexist_info_from_hwpg(struct ieee80211_hw *hw, + bool autoload_fail, u8 *hwinfo); +void rtl8192ce_bt_reg_init(struct ieee80211_hw *hw); +void rtl8192ce_bt_hw_init(struct ieee80211_hw *hw); +void rtl92ce_suspend(struct ieee80211_hw *hw); +void rtl92ce_resume(struct ieee80211_hw *hw); +void rtl92ce_allow_all_destaddr(struct ieee80211_hw *hw, + bool allow_all_da, bool write_into_reg); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/led.c b/drivers/net/wireless/rtlwifi/rtl8192ce/led.c new file mode 100644 index 0000000..8283e9b --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/led.c @@ -0,0 +1,151 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "reg.h" +#include "led.h" + +static void _rtl92ce_init_led(struct ieee80211_hw *hw, + struct rtl_led *pled, enum rtl_led_pin ledpin) +{ + pled->hw = hw; + pled->ledpin = ledpin; + pled->ledon = false; +} + +void rtl92ce_sw_led_on(struct ieee80211_hw *hw, struct rtl_led *pled) +{ + u8 ledcfg; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, "LedAddr:%X ledpin=%d\n", + REG_LEDCFG2, pled->ledpin); + + ledcfg = rtl_read_byte(rtlpriv, REG_LEDCFG2); + + switch (pled->ledpin) { + case LED_PIN_GPIO0: + break; + case LED_PIN_LED0: + rtl_write_byte(rtlpriv, + REG_LEDCFG2, (ledcfg & 0xf0) | BIT(5) | BIT(6)); + break; + case LED_PIN_LED1: + rtl_write_byte(rtlpriv, REG_LEDCFG2, (ledcfg & 0x0f) | BIT(5)); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + pled->ledon = true; +} + +void rtl92ce_sw_led_off(struct ieee80211_hw *hw, struct rtl_led *pled) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + u8 ledcfg; + + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, "LedAddr:%X ledpin=%d\n", + REG_LEDCFG2, pled->ledpin); + + ledcfg = rtl_read_byte(rtlpriv, REG_LEDCFG2); + + switch (pled->ledpin) { + case LED_PIN_GPIO0: + break; + case LED_PIN_LED0: + ledcfg &= 0xf0; + if (pcipriv->ledctl.led_opendrain) + rtl_write_byte(rtlpriv, REG_LEDCFG2, + (ledcfg | BIT(1) | BIT(5) | BIT(6))); + else + rtl_write_byte(rtlpriv, REG_LEDCFG2, + (ledcfg | BIT(3) | BIT(5) | BIT(6))); + break; + case LED_PIN_LED1: + ledcfg &= 0x0f; + rtl_write_byte(rtlpriv, REG_LEDCFG2, (ledcfg | BIT(3))); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + pled->ledon = false; +} + +void rtl92ce_init_sw_leds(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + _rtl92ce_init_led(hw, &(pcipriv->ledctl.sw_led0), LED_PIN_LED0); + _rtl92ce_init_led(hw, &(pcipriv->ledctl.sw_led1), LED_PIN_LED1); +} + +static void _rtl92ce_sw_led_control(struct ieee80211_hw *hw, + enum led_ctl_mode ledaction) +{ + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_led *pLed0 = &(pcipriv->ledctl.sw_led0); + switch (ledaction) { + case LED_CTL_POWER_ON: + case LED_CTL_LINK: + case LED_CTL_NO_LINK: + rtl92ce_sw_led_on(hw, pLed0); + break; + case LED_CTL_POWER_OFF: + rtl92ce_sw_led_off(hw, pLed0); + break; + default: + break; + } +} + +void rtl92ce_led_control(struct ieee80211_hw *hw, + enum led_ctl_mode ledaction) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + if ((ppsc->rfoff_reason > RF_CHANGE_BY_PS) && + (ledaction == LED_CTL_TX || + ledaction == LED_CTL_RX || + ledaction == LED_CTL_SITE_SURVEY || + ledaction == LED_CTL_LINK || + ledaction == LED_CTL_NO_LINK || + ledaction == LED_CTL_START_TO_LINK || + ledaction == LED_CTL_POWER_ON)) { + return; + } + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, "ledaction %d\n", + ledaction); + _rtl92ce_sw_led_control(hw, ledaction); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/led.h b/drivers/net/wireless/rtlwifi/rtl8192ce/led.h new file mode 100644 index 0000000..c576106 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/led.h @@ -0,0 +1,38 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CE_LED_H__ +#define __RTL92CE_LED_H__ + +void rtl92ce_init_sw_leds(struct ieee80211_hw *hw); +void rtl92ce_sw_led_on(struct ieee80211_hw *hw, struct rtl_led *pled); +void rtl92ce_sw_led_off(struct ieee80211_hw *hw, struct rtl_led *pled); +void rtl92ce_led_control(struct ieee80211_hw *hw, enum led_ctl_mode ledaction); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/phy.c b/drivers/net/wireless/rtlwifi/rtl8192ce/phy.c new file mode 100644 index 0000000..73262ca --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/phy.c @@ -0,0 +1,622 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "../ps.h" +#include "reg.h" +#include "def.h" +#include "hw.h" +#include "phy.h" +#include "rf.h" +#include "dm.h" +#include "table.h" + +static bool _rtl92c_phy_config_mac_with_headerfile(struct ieee80211_hw *hw); + +u32 rtl92c_phy_query_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, u32 bitmask) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 original_value, readback_value, bitshift; + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), rfpath(%#x), bitmask(%#x)\n", + regaddr, rfpath, bitmask); + + spin_lock(&rtlpriv->locks.rf_lock); + + if (rtlphy->rf_mode != RF_OP_BY_FW) { + original_value = _rtl92c_phy_rf_serial_read(hw, + rfpath, regaddr); + } else { + original_value = _rtl92c_phy_fw_rf_serial_read(hw, + rfpath, regaddr); + } + + bitshift = _rtl92c_phy_calculate_bit_shift(bitmask); + readback_value = (original_value & bitmask) >> bitshift; + + spin_unlock(&rtlpriv->locks.rf_lock); + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), rfpath(%#x), bitmask(%#x), original_value(%#x)\n", + regaddr, rfpath, bitmask, original_value); + + return readback_value; +} + +bool rtl92c_phy_mac_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + bool is92c = IS_92C_SERIAL(rtlhal->version); + bool rtstatus = _rtl92c_phy_config_mac_with_headerfile(hw); + + if (is92c) + rtl_write_byte(rtlpriv, 0x14, 0x71); + else + rtl_write_byte(rtlpriv, 0x04CA, 0x0A); + return rtstatus; +} + +bool rtl92c_phy_bb_config(struct ieee80211_hw *hw) +{ + bool rtstatus = true; + struct rtl_priv *rtlpriv = rtl_priv(hw); + u16 regval; + u32 regvaldw; + u8 reg_hwparafile = 1; + + _rtl92c_phy_init_bb_rf_register_definition(hw); + regval = rtl_read_word(rtlpriv, REG_SYS_FUNC_EN); + rtl_write_word(rtlpriv, REG_SYS_FUNC_EN, + regval | BIT(13) | BIT(0) | BIT(1)); + rtl_write_byte(rtlpriv, REG_AFE_PLL_CTRL, 0x83); + rtl_write_byte(rtlpriv, REG_AFE_PLL_CTRL + 1, 0xdb); + rtl_write_byte(rtlpriv, REG_RF_CTRL, RF_EN | RF_RSTB | RF_SDMRSTB); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, + FEN_PPLL | FEN_PCIEA | FEN_DIO_PCIE | + FEN_BB_GLB_RSTn | FEN_BBRSTB); + rtl_write_byte(rtlpriv, REG_AFE_XTAL_CTRL + 1, 0x80); + regvaldw = rtl_read_dword(rtlpriv, REG_LEDCFG0); + rtl_write_dword(rtlpriv, REG_LEDCFG0, regvaldw | BIT(23)); + if (reg_hwparafile == 1) + rtstatus = _rtl92c_phy_bb8192c_config_parafile(hw); + return rtstatus; +} + +void rtl92ce_phy_set_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, + u32 regaddr, u32 bitmask, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 original_value, bitshift; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x), rfpath(%#x)\n", + regaddr, bitmask, data, rfpath); + + spin_lock(&rtlpriv->locks.rf_lock); + + if (rtlphy->rf_mode != RF_OP_BY_FW) { + if (bitmask != RFREG_OFFSET_MASK) { + original_value = _rtl92c_phy_rf_serial_read(hw, + rfpath, + regaddr); + bitshift = _rtl92c_phy_calculate_bit_shift(bitmask); + data = + ((original_value & (~bitmask)) | + (data << bitshift)); + } + + _rtl92c_phy_rf_serial_write(hw, rfpath, regaddr, data); + } else { + if (bitmask != RFREG_OFFSET_MASK) { + original_value = _rtl92c_phy_fw_rf_serial_read(hw, + rfpath, + regaddr); + bitshift = _rtl92c_phy_calculate_bit_shift(bitmask); + data = + ((original_value & (~bitmask)) | + (data << bitshift)); + } + _rtl92c_phy_fw_rf_serial_write(hw, rfpath, regaddr, data); + } + + spin_unlock(&rtlpriv->locks.rf_lock); + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x), rfpath(%#x)\n", + regaddr, bitmask, data, rfpath); +} + +static bool _rtl92c_phy_config_mac_with_headerfile(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + u32 arraylength; + u32 *ptrarray; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "Read Rtl819XMACPHY_Array\n"); + arraylength = MAC_2T_ARRAYLENGTH; + ptrarray = RTL8192CEMAC_2T_ARRAY; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "Img:RTL8192CEMAC_2T_ARRAY\n"); + for (i = 0; i < arraylength; i = i + 2) + rtl_write_byte(rtlpriv, ptrarray[i], (u8) ptrarray[i + 1]); + return true; +} + +bool _rtl92ce_phy_config_bb_with_headerfile(struct ieee80211_hw *hw, + u8 configtype) +{ + int i; + u32 *phy_regarray_table; + u32 *agctab_array_table; + u16 phy_reg_arraylen, agctab_arraylen; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (IS_92C_SERIAL(rtlhal->version)) { + agctab_arraylen = AGCTAB_2TARRAYLENGTH; + agctab_array_table = RTL8192CEAGCTAB_2TARRAY; + phy_reg_arraylen = PHY_REG_2TARRAY_LENGTH; + phy_regarray_table = RTL8192CEPHY_REG_2TARRAY; + } else { + agctab_arraylen = AGCTAB_1TARRAYLENGTH; + agctab_array_table = RTL8192CEAGCTAB_1TARRAY; + phy_reg_arraylen = PHY_REG_1TARRAY_LENGTH; + phy_regarray_table = RTL8192CEPHY_REG_1TARRAY; + } + if (configtype == BASEBAND_CONFIG_PHY_REG) { + for (i = 0; i < phy_reg_arraylen; i = i + 2) { + if (phy_regarray_table[i] == 0xfe) + mdelay(50); + else if (phy_regarray_table[i] == 0xfd) + mdelay(5); + else if (phy_regarray_table[i] == 0xfc) + mdelay(1); + else if (phy_regarray_table[i] == 0xfb) + udelay(50); + else if (phy_regarray_table[i] == 0xfa) + udelay(5); + else if (phy_regarray_table[i] == 0xf9) + udelay(1); + rtl_set_bbreg(hw, phy_regarray_table[i], MASKDWORD, + phy_regarray_table[i + 1]); + udelay(1); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "The phy_regarray_table[0] is %x Rtl819XPHY_REGArray[1] is %x\n", + phy_regarray_table[i], + phy_regarray_table[i + 1]); + } + } else if (configtype == BASEBAND_CONFIG_AGC_TAB) { + for (i = 0; i < agctab_arraylen; i = i + 2) { + rtl_set_bbreg(hw, agctab_array_table[i], MASKDWORD, + agctab_array_table[i + 1]); + udelay(1); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "The agctab_array_table[0] is %x Rtl819XPHY_REGArray[1] is %x\n", + agctab_array_table[i], + agctab_array_table[i + 1]); + } + } + return true; +} + +bool _rtl92ce_phy_config_bb_with_pgheaderfile(struct ieee80211_hw *hw, + u8 configtype) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int i; + u32 *phy_regarray_table_pg; + u16 phy_regarray_pg_len; + + phy_regarray_pg_len = PHY_REG_ARRAY_PGLENGTH; + phy_regarray_table_pg = RTL8192CEPHY_REG_ARRAY_PG; + + if (configtype == BASEBAND_CONFIG_PHY_REG) { + for (i = 0; i < phy_regarray_pg_len; i = i + 3) { + if (phy_regarray_table_pg[i] == 0xfe) + mdelay(50); + else if (phy_regarray_table_pg[i] == 0xfd) + mdelay(5); + else if (phy_regarray_table_pg[i] == 0xfc) + mdelay(1); + else if (phy_regarray_table_pg[i] == 0xfb) + udelay(50); + else if (phy_regarray_table_pg[i] == 0xfa) + udelay(5); + else if (phy_regarray_table_pg[i] == 0xf9) + udelay(1); + + _rtl92c_store_pwrIndex_diffrate_offset(hw, + phy_regarray_table_pg[i], + phy_regarray_table_pg[i + 1], + phy_regarray_table_pg[i + 2]); + } + } else { + + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "configtype != BaseBand_Config_PHY_REG\n"); + } + return true; +} + +bool rtl92c_phy_config_rf_with_headerfile(struct ieee80211_hw *hw, + enum radio_path rfpath) +{ + + int i; + u32 *radioa_array_table; + u32 *radiob_array_table; + u16 radioa_arraylen, radiob_arraylen; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (IS_92C_SERIAL(rtlhal->version)) { + radioa_arraylen = RADIOA_2TARRAYLENGTH; + radioa_array_table = RTL8192CERADIOA_2TARRAY; + radiob_arraylen = RADIOB_2TARRAYLENGTH; + radiob_array_table = RTL8192CE_RADIOB_2TARRAY; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Radio_A:RTL8192CERADIOA_2TARRAY\n"); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Radio_B:RTL8192CE_RADIOB_2TARRAY\n"); + } else { + radioa_arraylen = RADIOA_1TARRAYLENGTH; + radioa_array_table = RTL8192CE_RADIOA_1TARRAY; + radiob_arraylen = RADIOB_1TARRAYLENGTH; + radiob_array_table = RTL8192CE_RADIOB_1TARRAY; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Radio_A:RTL8192CE_RADIOA_1TARRAY\n"); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Radio_B:RTL8192CE_RADIOB_1TARRAY\n"); + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "Radio No %x\n", rfpath); + switch (rfpath) { + case RF90_PATH_A: + for (i = 0; i < radioa_arraylen; i = i + 2) { + if (radioa_array_table[i] == 0xfe) + mdelay(50); + else if (radioa_array_table[i] == 0xfd) + mdelay(5); + else if (radioa_array_table[i] == 0xfc) + mdelay(1); + else if (radioa_array_table[i] == 0xfb) + udelay(50); + else if (radioa_array_table[i] == 0xfa) + udelay(5); + else if (radioa_array_table[i] == 0xf9) + udelay(1); + else { + rtl_set_rfreg(hw, rfpath, radioa_array_table[i], + RFREG_OFFSET_MASK, + radioa_array_table[i + 1]); + udelay(1); + } + } + break; + case RF90_PATH_B: + for (i = 0; i < radiob_arraylen; i = i + 2) { + if (radiob_array_table[i] == 0xfe) { + mdelay(50); + } else if (radiob_array_table[i] == 0xfd) + mdelay(5); + else if (radiob_array_table[i] == 0xfc) + mdelay(1); + else if (radiob_array_table[i] == 0xfb) + udelay(50); + else if (radiob_array_table[i] == 0xfa) + udelay(5); + else if (radiob_array_table[i] == 0xf9) + udelay(1); + else { + rtl_set_rfreg(hw, rfpath, radiob_array_table[i], + RFREG_OFFSET_MASK, + radiob_array_table[i + 1]); + udelay(1); + } + } + break; + case RF90_PATH_C: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + case RF90_PATH_D: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + return true; +} + +void rtl92ce_phy_set_bw_mode_callback(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u8 reg_bw_opmode; + u8 reg_prsr_rsc; + + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, "Switch to %s bandwidth\n", + rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20 ? + "20MHz" : "40MHz"); + + if (is_hal_stop(rtlhal)) { + rtlphy->set_bwmode_inprogress = false; + return; + } + + reg_bw_opmode = rtl_read_byte(rtlpriv, REG_BWOPMODE); + reg_prsr_rsc = rtl_read_byte(rtlpriv, REG_RRSR + 2); + + switch (rtlphy->current_chan_bw) { + case HT_CHANNEL_WIDTH_20: + reg_bw_opmode |= BW_OPMODE_20MHZ; + rtl_write_byte(rtlpriv, REG_BWOPMODE, reg_bw_opmode); + break; + case HT_CHANNEL_WIDTH_20_40: + reg_bw_opmode &= ~BW_OPMODE_20MHZ; + rtl_write_byte(rtlpriv, REG_BWOPMODE, reg_bw_opmode); + reg_prsr_rsc = + (reg_prsr_rsc & 0x90) | (mac->cur_40_prime_sc << 5); + rtl_write_byte(rtlpriv, REG_RRSR + 2, reg_prsr_rsc); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", rtlphy->current_chan_bw); + break; + } + + switch (rtlphy->current_chan_bw) { + case HT_CHANNEL_WIDTH_20: + rtl_set_bbreg(hw, RFPGA0_RFMOD, BRFMOD, 0x0); + rtl_set_bbreg(hw, RFPGA1_RFMOD, BRFMOD, 0x0); + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER2, BIT(10), 1); + break; + case HT_CHANNEL_WIDTH_20_40: + rtl_set_bbreg(hw, RFPGA0_RFMOD, BRFMOD, 0x1); + rtl_set_bbreg(hw, RFPGA1_RFMOD, BRFMOD, 0x1); + + rtl_set_bbreg(hw, RCCK0_SYSTEM, BCCK_SIDEBAND, + (mac->cur_40_prime_sc >> 1)); + rtl_set_bbreg(hw, ROFDM1_LSTF, 0xC00, mac->cur_40_prime_sc); + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER2, BIT(10), 0); + + rtl_set_bbreg(hw, 0x818, (BIT(26) | BIT(27)), + (mac->cur_40_prime_sc == + HAL_PRIME_CHNL_OFFSET_LOWER) ? 2 : 1); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", rtlphy->current_chan_bw); + break; + } + rtl92ce_phy_rf6052_set_bandwidth(hw, rtlphy->current_chan_bw); + rtlphy->set_bwmode_inprogress = false; + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, "<==\n"); +} + +void _rtl92ce_phy_lc_calibrate(struct ieee80211_hw *hw, bool is2t) +{ + u8 tmpreg; + u32 rf_a_mode = 0, rf_b_mode = 0, lc_cal; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + tmpreg = rtl_read_byte(rtlpriv, 0xd03); + + if ((tmpreg & 0x70) != 0) + rtl_write_byte(rtlpriv, 0xd03, tmpreg & 0x8F); + else + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0xFF); + + if ((tmpreg & 0x70) != 0) { + rf_a_mode = rtl_get_rfreg(hw, RF90_PATH_A, 0x00, MASK12BITS); + + if (is2t) + rf_b_mode = rtl_get_rfreg(hw, RF90_PATH_B, 0x00, + MASK12BITS); + + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, MASK12BITS, + (rf_a_mode & 0x8FFFF) | 0x10000); + + if (is2t) + rtl_set_rfreg(hw, RF90_PATH_B, 0x00, MASK12BITS, + (rf_b_mode & 0x8FFFF) | 0x10000); + } + lc_cal = rtl_get_rfreg(hw, RF90_PATH_A, 0x18, MASK12BITS); + + rtl_set_rfreg(hw, RF90_PATH_A, 0x18, MASK12BITS, lc_cal | 0x08000); + + mdelay(100); + + if ((tmpreg & 0x70) != 0) { + rtl_write_byte(rtlpriv, 0xd03, tmpreg); + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, MASK12BITS, rf_a_mode); + + if (is2t) + rtl_set_rfreg(hw, RF90_PATH_B, 0x00, MASK12BITS, + rf_b_mode); + } else { + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0x00); + } +} + +static void _rtl92ce_phy_set_rf_sleep(struct ieee80211_hw *hw) +{ + u32 u4b_tmp; + u8 delay = 5; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0xFF); + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, RFREG_OFFSET_MASK, 0x00); + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x40); + u4b_tmp = rtl_get_rfreg(hw, RF90_PATH_A, 0, RFREG_OFFSET_MASK); + while (u4b_tmp != 0 && delay > 0) { + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x0); + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, RFREG_OFFSET_MASK, 0x00); + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x40); + u4b_tmp = rtl_get_rfreg(hw, RF90_PATH_A, 0, RFREG_OFFSET_MASK); + delay--; + } + if (delay == 0) { + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x00); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE2); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE3); + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0x00); + RT_TRACE(rtlpriv, COMP_POWER, DBG_TRACE, + "Switch RF timeout !!!\n"); + return; + } + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE2); + rtl_write_byte(rtlpriv, REG_SPS0_CTRL, 0x22); +} + +static bool _rtl92ce_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + bool bresult = true; + u8 i, queue_id; + struct rtl8192_tx_ring *ring = NULL; + + switch (rfpwr_state) { + case ERFON:{ + if ((ppsc->rfpwr_state == ERFOFF) && + RT_IN_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC)) { + bool rtstatus; + u32 InitializeCount = 0; + do { + InitializeCount++; + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "IPS Set eRf nic enable\n"); + rtstatus = rtl_ps_enable_nic(hw); + } while (!rtstatus && (InitializeCount < 10)); + RT_CLEAR_PS_LEVEL(ppsc, + RT_RF_OFF_LEVL_HALT_NIC); + } else { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "Set ERFON sleeped:%d ms\n", + jiffies_to_msecs(jiffies - + ppsc-> + last_sleep_jiffies)); + ppsc->last_awake_jiffies = jiffies; + rtl92ce_phy_set_rf_on(hw); + } + if (mac->link_state == MAC80211_LINKED) { + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_LINK); + } else { + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_NO_LINK); + } + break; + } + case ERFOFF:{ + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_HALT_NIC) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "IPS Set eRf nic disable\n"); + rtl_ps_disable_nic(hw); + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + } else { + if (ppsc->rfoff_reason == RF_CHANGE_BY_IPS) { + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_NO_LINK); + } else { + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_POWER_OFF); + } + } + break; + } + case ERFSLEEP:{ + if (ppsc->rfpwr_state == ERFOFF) + return false; + for (queue_id = 0, i = 0; + queue_id < RTL_PCI_MAX_TX_QUEUE_COUNT;) { + ring = &pcipriv->dev.tx_ring[queue_id]; + if (skb_queue_len(&ring->queue) == 0) { + queue_id++; + continue; + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "eRf Off/Sleep: %d times TcbBusyQueue[%d] =%d before doze!\n", + i + 1, queue_id, + skb_queue_len(&ring->queue)); + + udelay(10); + i++; + } + if (i >= MAX_DOZE_WAITING_TIMES_9x) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "ERFSLEEP: %d times TcbBusyQueue[%d] = %d !\n", + MAX_DOZE_WAITING_TIMES_9x, + queue_id, + skb_queue_len(&ring->queue)); + break; + } + } + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "Set ERFSLEEP awaked:%d ms\n", + jiffies_to_msecs(jiffies - + ppsc->last_awake_jiffies)); + ppsc->last_sleep_jiffies = jiffies; + _rtl92ce_phy_set_rf_sleep(hw); + break; + } + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + bresult = false; + break; + } + if (bresult) + ppsc->rfpwr_state = rfpwr_state; + return bresult; +} + +bool rtl92c_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state) +{ + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + bool bresult = false; + + if (rfpwr_state == ppsc->rfpwr_state) + return bresult; + bresult = _rtl92ce_phy_set_rf_power_state(hw, rfpwr_state); + return bresult; +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/phy.h b/drivers/net/wireless/rtlwifi/rtl8192ce/phy.h new file mode 100644 index 0000000..d5e3b70 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/phy.h @@ -0,0 +1,262 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92C_PHY_H__ +#define __RTL92C_PHY_H__ + +#define MAX_PRECMD_CNT 16 +#define MAX_RFDEPENDCMD_CNT 16 +#define MAX_POSTCMD_CNT 16 + +#define MAX_DOZE_WAITING_TIMES_9x 64 + +#define RT_CANNOT_IO(hw) false +#define HIGHPOWER_RADIOA_ARRAYLEN 22 + +#define IQK_ADDA_REG_NUM 16 +#define MAX_TOLERANCE 5 +#define IQK_DELAY_TIME 1 + +#define APK_BB_REG_NUM 5 +#define APK_AFE_REG_NUM 16 +#define APK_CURVE_REG_NUM 4 +#define PATH_NUM 2 + +#define LOOP_LIMIT 5 +#define MAX_STALL_TIME 50 +#define AntennaDiversityValue 0x80 +#define MAX_TXPWR_IDX_NMODE_92S 63 +#define Reset_Cnt_Limit 3 + +#define IQK_ADDA_REG_NUM 16 +#define IQK_MAC_REG_NUM 4 + +#define IQK_DELAY_TIME 1 + +#define RF90_PATH_MAX 2 + +#define CT_OFFSET_MAC_ADDR 0X16 + +#define CT_OFFSET_CCK_TX_PWR_IDX 0x5A +#define CT_OFFSET_HT401S_TX_PWR_IDX 0x60 +#define CT_OFFSET_HT402S_TX_PWR_IDX_DIF 0x66 +#define CT_OFFSET_HT20_TX_PWR_IDX_DIFF 0x69 +#define CT_OFFSET_OFDM_TX_PWR_IDX_DIFF 0x6C + +#define CT_OFFSET_HT40_MAX_PWR_OFFSET 0x6F +#define CT_OFFSET_HT20_MAX_PWR_OFFSET 0x72 + +#define CT_OFFSET_CHANNEL_PLAH 0x75 +#define CT_OFFSET_THERMAL_METER 0x78 +#define CT_OFFSET_RF_OPTION 0x79 +#define CT_OFFSET_VERSION 0x7E +#define CT_OFFSET_CUSTOMER_ID 0x7F + +#define RTL92C_MAX_PATH_NUM 2 + +enum swchnlcmd_id { + CMDID_END, + CMDID_SET_TXPOWEROWER_LEVEL, + CMDID_BBREGWRITE10, + CMDID_WRITEPORT_ULONG, + CMDID_WRITEPORT_USHORT, + CMDID_WRITEPORT_UCHAR, + CMDID_RF_WRITEREG, +}; + +struct swchnlcmd { + enum swchnlcmd_id cmdid; + u32 para1; + u32 para2; + u32 msdelay; +}; + +enum hw90_block_e { + HW90_BLOCK_MAC = 0, + HW90_BLOCK_PHY0 = 1, + HW90_BLOCK_PHY1 = 2, + HW90_BLOCK_RF = 3, + HW90_BLOCK_MAXIMUM = 4, +}; + +enum baseband_config_type { + BASEBAND_CONFIG_PHY_REG = 0, + BASEBAND_CONFIG_AGC_TAB = 1, +}; + +enum ra_offset_area { + RA_OFFSET_LEGACY_OFDM1, + RA_OFFSET_LEGACY_OFDM2, + RA_OFFSET_HT_OFDM1, + RA_OFFSET_HT_OFDM2, + RA_OFFSET_HT_OFDM3, + RA_OFFSET_HT_OFDM4, + RA_OFFSET_HT_CCK, +}; + +enum antenna_path { + ANTENNA_NONE, + ANTENNA_D, + ANTENNA_C, + ANTENNA_CD, + ANTENNA_B, + ANTENNA_BD, + ANTENNA_BC, + ANTENNA_BCD, + ANTENNA_A, + ANTENNA_AD, + ANTENNA_AC, + ANTENNA_ACD, + ANTENNA_AB, + ANTENNA_ABD, + ANTENNA_ABC, + ANTENNA_ABCD +}; + +struct r_antenna_select_ofdm { + u32 r_tx_antenna:4; + u32 r_ant_l:4; + u32 r_ant_non_ht:4; + u32 r_ant_ht1:4; + u32 r_ant_ht2:4; + u32 r_ant_ht_s1:4; + u32 r_ant_non_ht_s1:4; + u32 ofdm_txsc:2; + u32 reserved:2; +}; + +struct r_antenna_select_cck { + u8 r_cckrx_enable_2:2; + u8 r_cckrx_enable:2; + u8 r_ccktx_enable:4; +}; + +struct efuse_contents { + u8 mac_addr[ETH_ALEN]; + u8 cck_tx_power_idx[6]; + u8 ht40_1s_tx_power_idx[6]; + u8 ht40_2s_tx_power_idx_diff[3]; + u8 ht20_tx_power_idx_diff[3]; + u8 ofdm_tx_power_idx_diff[3]; + u8 ht40_max_power_offset[3]; + u8 ht20_max_power_offset[3]; + u8 channel_plan; + u8 thermal_meter; + u8 rf_option[5]; + u8 version; + u8 oem_id; + u8 regulatory; +}; + +struct tx_power_struct { + u8 cck[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 ht40_1s[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 ht40_2s[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 ht20_diff[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 legacy_ht_diff[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 legacy_ht_txpowerdiff; + u8 groupht20[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 groupht40[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 pwrgroup_cnt; + u32 mcs_original_offset[4][16]; +}; + +bool rtl92c_phy_bb_config(struct ieee80211_hw *hw); +u32 rtl92c_phy_query_bb_reg(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask); +void rtl92c_phy_set_bb_reg(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask, u32 data); +u32 rtl92c_phy_query_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, + u32 bitmask); +extern void rtl92ce_phy_set_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, + u32 bitmask, u32 data); +bool rtl92c_phy_mac_config(struct ieee80211_hw *hw); +bool rtl92ce_phy_bb_config(struct ieee80211_hw *hw); +bool rtl92c_phy_rf_config(struct ieee80211_hw *hw); +bool rtl92c_phy_config_rf_with_feaderfile(struct ieee80211_hw *hw, + enum radio_path rfpath); +void rtl92c_phy_get_hw_reg_originalvalue(struct ieee80211_hw *hw); +void rtl92c_phy_get_txpower_level(struct ieee80211_hw *hw, + long *powerlevel); +void rtl92c_phy_set_txpower_level(struct ieee80211_hw *hw, u8 channel); +bool rtl92c_phy_update_txpower_dbm(struct ieee80211_hw *hw, + long power_indbm); +void rtl92c_phy_scan_operation_backup(struct ieee80211_hw *hw, + u8 operation); +void rtl92c_phy_set_bw_mode(struct ieee80211_hw *hw, + enum nl80211_channel_type ch_type); +void rtl92c_phy_sw_chnl_callback(struct ieee80211_hw *hw); +u8 rtl92c_phy_sw_chnl(struct ieee80211_hw *hw); +void rtl92c_phy_iq_calibrate(struct ieee80211_hw *hw, bool b_recovery); +void rtl92c_phy_set_beacon_hw_reg(struct ieee80211_hw *hw, + u16 beaconinterval); +void rtl92c_phy_ap_calibrate(struct ieee80211_hw *hw, char delta); +void rtl92c_phy_lc_calibrate(struct ieee80211_hw *hw); +void _rtl92ce_phy_lc_calibrate(struct ieee80211_hw *hw, bool is2t); +void rtl92c_phy_set_rfpath_switch(struct ieee80211_hw *hw, bool bmain); +bool rtl92c_phy_config_rf_with_headerfile(struct ieee80211_hw *hw, + enum radio_path rfpath); +bool rtl8192_phy_check_is_legal_rfpath(struct ieee80211_hw *hw, + u32 rfpath); +bool rtl92c_phy_set_io_cmd(struct ieee80211_hw *hw, enum io_type iotype); +bool rtl92ce_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state); +void rtl92ce_phy_set_rf_on(struct ieee80211_hw *hw); +bool rtl92c_phy_set_io_cmd(struct ieee80211_hw *hw, enum io_type iotype); +void rtl92c_phy_set_io(struct ieee80211_hw *hw); +void rtl92c_bb_block_on(struct ieee80211_hw *hw); +u32 _rtl92c_phy_rf_serial_read(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset); +u32 _rtl92c_phy_fw_rf_serial_read(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset); +u32 _rtl92c_phy_calculate_bit_shift(u32 bitmask); +void _rtl92c_phy_rf_serial_write(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset, + u32 data); +void _rtl92c_phy_fw_rf_serial_write(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset, + u32 data); +void _rtl92c_store_pwrIndex_diffrate_offset(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask, + u32 data); +bool _rtl92ce_phy_config_mac_with_headerfile(struct ieee80211_hw *hw); +void _rtl92c_phy_init_bb_rf_register_definition(struct ieee80211_hw *hw); +bool _rtl92c_phy_bb8192c_config_parafile(struct ieee80211_hw *hw); +void _rtl92c_phy_set_rf_sleep(struct ieee80211_hw *hw); +bool rtl92c_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state); +bool _rtl92ce_phy_config_bb_with_headerfile(struct ieee80211_hw *hw, + u8 configtype); +bool _rtl92ce_phy_config_bb_with_pgheaderfile(struct ieee80211_hw *hw, + u8 configtype); +void rtl92ce_phy_set_bw_mode_callback(struct ieee80211_hw *hw); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/reg.h b/drivers/net/wireless/rtlwifi/rtl8192ce/reg.h new file mode 100644 index 0000000..bd4aef7 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/reg.h @@ -0,0 +1,2092 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92C_REG_H__ +#define __RTL92C_REG_H__ + +#define REG_SYS_ISO_CTRL 0x0000 +#define REG_SYS_FUNC_EN 0x0002 +#define REG_APS_FSMCO 0x0004 +#define REG_SYS_CLKR 0x0008 +#define REG_9346CR 0x000A +#define REG_EE_VPD 0x000C +#define REG_AFE_MISC 0x0010 +#define REG_SPS0_CTRL 0x0011 +#define REG_SPS_OCP_CFG 0x0018 +#define REG_RSV_CTRL 0x001C +#define REG_RF_CTRL 0x001F +#define REG_LDOA15_CTRL 0x0020 +#define REG_LDOV12D_CTRL 0x0021 +#define REG_LDOHCI12_CTRL 0x0022 +#define REG_LPLDO_CTRL 0x0023 +#define REG_AFE_XTAL_CTRL 0x0024 +#define REG_AFE_PLL_CTRL 0x0028 +#define REG_EFUSE_CTRL 0x0030 +#define REG_EFUSE_TEST 0x0034 +#define REG_PWR_DATA 0x0038 +#define REG_CAL_TIMER 0x003C +#define REG_ACLK_MON 0x003E +#define REG_GPIO_MUXCFG 0x0040 +#define REG_GPIO_IO_SEL 0x0042 +#define REG_MAC_PINMUX_CFG 0x0043 +#define REG_GPIO_PIN_CTRL 0x0044 +#define REG_GPIO_INTM 0x0048 +#define REG_LEDCFG0 0x004C +#define REG_LEDCFG1 0x004D +#define REG_LEDCFG2 0x004E +#define REG_LEDCFG3 0x004F +#define REG_FSIMR 0x0050 +#define REG_FSISR 0x0054 +#define REG_HSIMR 0x0058 +#define REG_HSISR 0x005c + +/* RTL8723 WIFI/BT/GPS Multi-Function GPIO Pin Control. */ +#define REG_GPIO_PIN_CTRL_2 0x0060 +/* RTL8723 WIFI/BT/GPS Multi-Function GPIO Select. */ +#define REG_GPIO_IO_SEL_2 0x0062 +/* RTL8723 WIFI/BT/GPS Multi-Function control source. */ +#define REG_MULTI_FUNC_CTRL 0x0068 + +#define REG_MCUFWDL 0x0080 + +#define REG_HMEBOX_EXT_0 0x0088 +#define REG_HMEBOX_EXT_1 0x008A +#define REG_HMEBOX_EXT_2 0x008C +#define REG_HMEBOX_EXT_3 0x008E + +#define REG_BIST_SCAN 0x00D0 +#define REG_BIST_RPT 0x00D4 +#define REG_BIST_ROM_RPT 0x00D8 +#define REG_USB_SIE_INTF 0x00E0 +#define REG_PCIE_MIO_INTF 0x00E4 +#define REG_PCIE_MIO_INTD 0x00E8 +#define REG_HPON_FSM 0x00EC +#define REG_SYS_CFG 0x00F0 +#define REG_GPIO_OUTSTS 0x00F4 /* For RTL8723 only.*/ + +#define REG_CR 0x0100 +#define REG_PBP 0x0104 +#define REG_TRXDMA_CTRL 0x010C +#define REG_TRXFF_BNDY 0x0114 +#define REG_TRXFF_STATUS 0x0118 +#define REG_RXFF_PTR 0x011C +#define REG_HIMR 0x0120 +#define REG_HISR 0x0124 +#define REG_HIMRE 0x0128 +#define REG_HISRE 0x012C +#define REG_CPWM 0x012F +#define REG_FWIMR 0x0130 +#define REG_FWISR 0x0134 +#define REG_PKTBUF_DBG_CTRL 0x0140 +#define REG_PKTBUF_DBG_DATA_L 0x0144 +#define REG_PKTBUF_DBG_DATA_H 0x0148 + +#define REG_TC0_CTRL 0x0150 +#define REG_TC1_CTRL 0x0154 +#define REG_TC2_CTRL 0x0158 +#define REG_TC3_CTRL 0x015C +#define REG_TC4_CTRL 0x0160 +#define REG_TCUNIT_BASE 0x0164 +#define REG_MBIST_START 0x0174 +#define REG_MBIST_DONE 0x0178 +#define REG_MBIST_FAIL 0x017C +#define REG_C2HEVT_MSG_NORMAL 0x01A0 +#define REG_C2HEVT_MSG_TEST 0x01B8 +#define REG_C2HEVT_CLEAR 0x01BF +#define REG_MCUTST_1 0x01c0 +#define REG_FMETHR 0x01C8 +#define REG_HMETFR 0x01CC +#define REG_HMEBOX_0 0x01D0 +#define REG_HMEBOX_1 0x01D4 +#define REG_HMEBOX_2 0x01D8 +#define REG_HMEBOX_3 0x01DC + +#define REG_LLT_INIT 0x01E0 +#define REG_BB_ACCEESS_CTRL 0x01E8 +#define REG_BB_ACCESS_DATA 0x01EC + +#define REG_RQPN 0x0200 +#define REG_FIFOPAGE 0x0204 +#define REG_TDECTRL 0x0208 +#define REG_TXDMA_OFFSET_CHK 0x020C +#define REG_TXDMA_STATUS 0x0210 +#define REG_RQPN_NPQ 0x0214 + +#define REG_RXDMA_AGG_PG_TH 0x0280 +#define REG_RXPKT_NUM 0x0284 +#define REG_RXDMA_STATUS 0x0288 + +#define REG_PCIE_CTRL_REG 0x0300 +#define REG_INT_MIG 0x0304 +#define REG_BCNQ_DESA 0x0308 +#define REG_HQ_DESA 0x0310 +#define REG_MGQ_DESA 0x0318 +#define REG_VOQ_DESA 0x0320 +#define REG_VIQ_DESA 0x0328 +#define REG_BEQ_DESA 0x0330 +#define REG_BKQ_DESA 0x0338 +#define REG_RX_DESA 0x0340 +#define REG_DBI 0x0348 +#define REG_MDIO 0x0354 +#define REG_DBG_SEL 0x0360 +#define REG_PCIE_HRPWM 0x0361 +#define REG_PCIE_HCPWM 0x0363 +#define REG_UART_CTRL 0x0364 +#define REG_UART_TX_DESA 0x0370 +#define REG_UART_RX_DESA 0x0378 + +#define REG_HDAQ_DESA_NODEF 0x0000 +#define REG_CMDQ_DESA_NODEF 0x0000 + +#define REG_VOQ_INFORMATION 0x0400 +#define REG_VIQ_INFORMATION 0x0404 +#define REG_BEQ_INFORMATION 0x0408 +#define REG_BKQ_INFORMATION 0x040C +#define REG_MGQ_INFORMATION 0x0410 +#define REG_HGQ_INFORMATION 0x0414 +#define REG_BCNQ_INFORMATION 0x0418 + +#define REG_CPU_MGQ_INFORMATION 0x041C +#define REG_FWHW_TXQ_CTRL 0x0420 +#define REG_HWSEQ_CTRL 0x0423 +#define REG_TXPKTBUF_BCNQ_BDNY 0x0424 +#define REG_TXPKTBUF_MGQ_BDNY 0x0425 +#define REG_MULTI_BCNQ_EN 0x0426 +#define REG_MULTI_BCNQ_OFFSET 0x0427 +#define REG_SPEC_SIFS 0x0428 +#define REG_RL 0x042A +#define REG_DARFRC 0x0430 +#define REG_RARFRC 0x0438 +#define REG_RRSR 0x0440 +#define REG_ARFR0 0x0444 +#define REG_ARFR1 0x0448 +#define REG_ARFR2 0x044C +#define REG_ARFR3 0x0450 +#define REG_AGGLEN_LMT 0x0458 +#define REG_AMPDU_MIN_SPACE 0x045C +#define REG_TXPKTBUF_WMAC_LBK_BF_HD 0x045D +#define REG_FAST_EDCA_CTRL 0x0460 +#define REG_RD_RESP_PKT_TH 0x0463 +#define REG_INIRTS_RATE_SEL 0x0480 +#define REG_INIDATA_RATE_SEL 0x0484 +#define REG_POWER_STATUS 0x04A4 +#define REG_POWER_STAGE1 0x04B4 +#define REG_POWER_STAGE2 0x04B8 +#define REG_PKT_LIFE_TIME 0x04C0 +#define REG_STBC_SETTING 0x04C4 +#define REG_PROT_MODE_CTRL 0x04C8 +#define REG_BAR_MODE_CTRL 0x04CC +#define REG_RA_TRY_RATE_AGG_LMT 0x04CF +#define REG_NQOS_SEQ 0x04DC +#define REG_QOS_SEQ 0x04DE +#define REG_NEED_CPU_HANDLE 0x04E0 +#define REG_PKT_LOSE_RPT 0x04E1 +#define REG_PTCL_ERR_STATUS 0x04E2 +#define REG_DUMMY 0x04FC + +#define REG_EDCA_VO_PARAM 0x0500 +#define REG_EDCA_VI_PARAM 0x0504 +#define REG_EDCA_BE_PARAM 0x0508 +#define REG_EDCA_BK_PARAM 0x050C +#define REG_BCNTCFG 0x0510 +#define REG_PIFS 0x0512 +#define REG_RDG_PIFS 0x0513 +#define REG_SIFS_CTX 0x0514 +#define REG_SIFS_TRX 0x0516 +#define REG_SIFS_CCK 0x0514 +#define REG_SIFS_OFDM 0x0516 +#define REG_AGGR_BREAK_TIME 0x051A +#define REG_SLOT 0x051B +#define REG_TX_PTCL_CTRL 0x0520 +#define REG_TXPAUSE 0x0522 +#define REG_DIS_TXREQ_CLR 0x0523 +#define REG_RD_CTRL 0x0524 +#define REG_TBTT_PROHIBIT 0x0540 +#define REG_RD_NAV_NXT 0x0544 +#define REG_NAV_PROT_LEN 0x0546 +#define REG_BCN_CTRL 0x0550 +#define REG_USTIME_TSF 0x0551 +#define REG_MBID_NUM 0x0552 +#define REG_DUAL_TSF_RST 0x0553 +#define REG_BCN_INTERVAL 0x0554 +#define REG_MBSSID_BCN_SPACE 0x0554 +#define REG_DRVERLYINT 0x0558 +#define REG_BCNDMATIM 0x0559 +#define REG_ATIMWND 0x055A +#define REG_BCN_MAX_ERR 0x055D +#define REG_RXTSF_OFFSET_CCK 0x055E +#define REG_RXTSF_OFFSET_OFDM 0x055F +#define REG_TSFTR 0x0560 +#define REG_INIT_TSFTR 0x0564 +#define REG_PSTIMER 0x0580 +#define REG_TIMER0 0x0584 +#define REG_TIMER1 0x0588 +#define REG_ACMHWCTRL 0x05C0 +#define REG_ACMRSTCTRL 0x05C1 +#define REG_ACMAVG 0x05C2 +#define REG_VO_ADMTIME 0x05C4 +#define REG_VI_ADMTIME 0x05C6 +#define REG_BE_ADMTIME 0x05C8 +#define REG_EDCA_RANDOM_GEN 0x05CC +#define REG_SCH_TXCMD 0x05D0 + +#define REG_APSD_CTRL 0x0600 +#define REG_BWOPMODE 0x0603 +#define REG_TCR 0x0604 +#define REG_RCR 0x0608 +#define REG_RX_PKT_LIMIT 0x060C +#define REG_RX_DLK_TIME 0x060D +#define REG_RX_DRVINFO_SZ 0x060F + +#define REG_MACID 0x0610 +#define REG_BSSID 0x0618 +#define REG_MAR 0x0620 +#define REG_MBIDCAMCFG 0x0628 + +#define REG_USTIME_EDCA 0x0638 +#define REG_MAC_SPEC_SIFS 0x063A +#define REG_RESP_SIFS_CCK 0x063C +#define REG_RESP_SIFS_OFDM 0x063E +/* [15:8]SIFS_R2T_OFDM, [7:0]SIFS_R2T_CCK */ +#define REG_R2T_SIFS 0x063C +/* [15:8]SIFS_T2T_OFDM, [7:0]SIFS_T2T_CCK */ +#define REG_T2T_SIFS 0x063E +#define REG_ACKTO 0x0640 +#define REG_CTS2TO 0x0641 +#define REG_EIFS 0x0642 + +#define REG_NAV_CTRL 0x0650 +#define REG_BACAMCMD 0x0654 +#define REG_BACAMCONTENT 0x0658 +#define REG_LBDLY 0x0660 +#define REG_FWDLY 0x0661 +#define REG_RXERR_RPT 0x0664 +#define REG_WMAC_TRXPTCL_CTL 0x0668 + +#define REG_CAMCMD 0x0670 +#define REG_CAMWRITE 0x0674 +#define REG_CAMREAD 0x0678 +#define REG_CAMDBG 0x067C +#define REG_SECCFG 0x0680 + +#define REG_WOW_CTRL 0x0690 +#define REG_PSSTATUS 0x0691 +#define REG_PS_RX_INFO 0x0692 +#define REG_LPNAV_CTRL 0x0694 +#define REG_WKFMCAM_CMD 0x0698 +#define REG_WKFMCAM_RWD 0x069C +#define REG_RXFLTMAP0 0x06A0 +#define REG_RXFLTMAP1 0x06A2 +#define REG_RXFLTMAP2 0x06A4 +#define REG_BCN_PSR_RPT 0x06A8 +#define REG_CALB32K_CTRL 0x06AC +#define REG_PKT_MON_CTRL 0x06B4 +#define REG_BT_COEX_TABLE 0x06C0 +#define REG_WMAC_RESP_TXINFO 0x06D8 + +#define REG_USB_INFO 0xFE17 +#define REG_USB_SPECIAL_OPTION 0xFE55 +#define REG_USB_DMA_AGG_TO 0xFE5B +#define REG_USB_AGG_TO 0xFE5C +#define REG_USB_AGG_TH 0xFE5D + +#define REG_TEST_USB_TXQS 0xFE48 +#define REG_TEST_SIE_VID 0xFE60 +#define REG_TEST_SIE_PID 0xFE62 +#define REG_TEST_SIE_OPTIONAL 0xFE64 +#define REG_TEST_SIE_CHIRP_K 0xFE65 +#define REG_TEST_SIE_PHY 0xFE66 +#define REG_TEST_SIE_MAC_ADDR 0xFE70 +#define REG_TEST_SIE_STRING 0xFE80 + +#define REG_NORMAL_SIE_VID 0xFE60 +#define REG_NORMAL_SIE_PID 0xFE62 +#define REG_NORMAL_SIE_OPTIONAL 0xFE64 +#define REG_NORMAL_SIE_EP 0xFE65 +#define REG_NORMAL_SIE_PHY 0xFE68 +#define REG_NORMAL_SIE_MAC_ADDR 0xFE70 +#define REG_NORMAL_SIE_STRING 0xFE80 + +#define CR9346 REG_9346CR +#define MSR (REG_CR + 2) +#define ISR REG_HISR +#define TSFR REG_TSFTR + +#define MACIDR0 REG_MACID +#define MACIDR4 (REG_MACID + 4) + +#define PBP REG_PBP + +#define IDR0 MACIDR0 +#define IDR4 MACIDR4 + +#define UNUSED_REGISTER 0x1BF +#define DCAM UNUSED_REGISTER +#define PSR UNUSED_REGISTER +#define BBADDR UNUSED_REGISTER +#define PHYDATAR UNUSED_REGISTER + +#define INVALID_BBRF_VALUE 0x12345678 + +#define MAX_MSS_DENSITY_2T 0x13 +#define MAX_MSS_DENSITY_1T 0x0A + +#define CMDEEPROM_EN BIT(5) +#define CMDEEPROM_SEL BIT(4) +#define CMD9346CR_9356SEL BIT(4) +#define AUTOLOAD_EEPROM (CMDEEPROM_EN|CMDEEPROM_SEL) +#define AUTOLOAD_EFUSE CMDEEPROM_EN + +#define GPIOSEL_GPIO 0 +#define GPIOSEL_ENBT BIT(5) + +#define GPIO_IN REG_GPIO_PIN_CTRL +#define GPIO_OUT (REG_GPIO_PIN_CTRL+1) +#define GPIO_IO_SEL (REG_GPIO_PIN_CTRL+2) +#define GPIO_MOD (REG_GPIO_PIN_CTRL+3) + +#define MSR_NOLINK 0x00 +#define MSR_ADHOC 0x01 +#define MSR_INFRA 0x02 +#define MSR_AP 0x03 + +#define RRSR_RSC_OFFSET 21 +#define RRSR_SHORT_OFFSET 23 +#define RRSR_RSC_BW_40M 0x600000 +#define RRSR_RSC_UPSUBCHNL 0x400000 +#define RRSR_RSC_LOWSUBCHNL 0x200000 +#define RRSR_SHORT 0x800000 +#define RRSR_1M BIT(0) +#define RRSR_2M BIT(1) +#define RRSR_5_5M BIT(2) +#define RRSR_11M BIT(3) +#define RRSR_6M BIT(4) +#define RRSR_9M BIT(5) +#define RRSR_12M BIT(6) +#define RRSR_18M BIT(7) +#define RRSR_24M BIT(8) +#define RRSR_36M BIT(9) +#define RRSR_48M BIT(10) +#define RRSR_54M BIT(11) +#define RRSR_MCS0 BIT(12) +#define RRSR_MCS1 BIT(13) +#define RRSR_MCS2 BIT(14) +#define RRSR_MCS3 BIT(15) +#define RRSR_MCS4 BIT(16) +#define RRSR_MCS5 BIT(17) +#define RRSR_MCS6 BIT(18) +#define RRSR_MCS7 BIT(19) +#define BRSR_ACKSHORTPMB BIT(23) + +#define RATR_1M 0x00000001 +#define RATR_2M 0x00000002 +#define RATR_55M 0x00000004 +#define RATR_11M 0x00000008 +#define RATR_6M 0x00000010 +#define RATR_9M 0x00000020 +#define RATR_12M 0x00000040 +#define RATR_18M 0x00000080 +#define RATR_24M 0x00000100 +#define RATR_36M 0x00000200 +#define RATR_48M 0x00000400 +#define RATR_54M 0x00000800 +#define RATR_MCS0 0x00001000 +#define RATR_MCS1 0x00002000 +#define RATR_MCS2 0x00004000 +#define RATR_MCS3 0x00008000 +#define RATR_MCS4 0x00010000 +#define RATR_MCS5 0x00020000 +#define RATR_MCS6 0x00040000 +#define RATR_MCS7 0x00080000 +#define RATR_MCS8 0x00100000 +#define RATR_MCS9 0x00200000 +#define RATR_MCS10 0x00400000 +#define RATR_MCS11 0x00800000 +#define RATR_MCS12 0x01000000 +#define RATR_MCS13 0x02000000 +#define RATR_MCS14 0x04000000 +#define RATR_MCS15 0x08000000 + +#define RATE_1M BIT(0) +#define RATE_2M BIT(1) +#define RATE_5_5M BIT(2) +#define RATE_11M BIT(3) +#define RATE_6M BIT(4) +#define RATE_9M BIT(5) +#define RATE_12M BIT(6) +#define RATE_18M BIT(7) +#define RATE_24M BIT(8) +#define RATE_36M BIT(9) +#define RATE_48M BIT(10) +#define RATE_54M BIT(11) +#define RATE_MCS0 BIT(12) +#define RATE_MCS1 BIT(13) +#define RATE_MCS2 BIT(14) +#define RATE_MCS3 BIT(15) +#define RATE_MCS4 BIT(16) +#define RATE_MCS5 BIT(17) +#define RATE_MCS6 BIT(18) +#define RATE_MCS7 BIT(19) +#define RATE_MCS8 BIT(20) +#define RATE_MCS9 BIT(21) +#define RATE_MCS10 BIT(22) +#define RATE_MCS11 BIT(23) +#define RATE_MCS12 BIT(24) +#define RATE_MCS13 BIT(25) +#define RATE_MCS14 BIT(26) +#define RATE_MCS15 BIT(27) + +#define RATE_ALL_CCK (RATR_1M | RATR_2M | RATR_55M | RATR_11M) +#define RATE_ALL_OFDM_AG (RATR_6M | RATR_9M | RATR_12M | RATR_18M \ + | RATR_24M | RATR_36M | RATR_48M | RATR_54M) +#define RATE_ALL_OFDM_1SS (RATR_MCS0 | RATR_MCS1 | RATR_MCS2 | \ + RATR_MCS3 | RATR_MCS4 | RATR_MCS5 | \ + RATR_MCS6 | RATR_MCS7) +#define RATE_ALL_OFDM_2SS (RATR_MCS8 | RATR_MCS9 | RATR_MCS10 | \ + RATR_MCS11 | RATR_MCS12 | RATR_MCS13 | \ + RATR_MCS14 | RATR_MCS15) + +#define BW_OPMODE_20MHZ BIT(2) +#define BW_OPMODE_5G BIT(1) +#define BW_OPMODE_11J BIT(0) + +#define CAM_VALID BIT(15) +#define CAM_NOTVALID 0x0000 +#define CAM_USEDK BIT(5) + +#define CAM_NONE 0x0 +#define CAM_WEP40 0x01 +#define CAM_TKIP 0x02 +#define CAM_AES 0x04 +#define CAM_WEP104 0x05 + +#define TOTAL_CAM_ENTRY 32 +#define HALF_CAM_ENTRY 16 + +#define CAM_WRITE BIT(16) +#define CAM_READ 0x00000000 +#define CAM_POLLINIG BIT(31) + +#define SCR_USEDK 0x01 +#define SCR_TXSEC_ENABLE 0x02 +#define SCR_RXSEC_ENABLE 0x04 + +#define WOW_PMEN BIT(0) +#define WOW_WOMEN BIT(1) +#define WOW_MAGIC BIT(2) +#define WOW_UWF BIT(3) + +#define IMR8190_DISABLED 0x0 +#define IMR_BCNDMAINT6 BIT(31) +#define IMR_BCNDMAINT5 BIT(30) +#define IMR_BCNDMAINT4 BIT(29) +#define IMR_BCNDMAINT3 BIT(28) +#define IMR_BCNDMAINT2 BIT(27) +#define IMR_BCNDMAINT1 BIT(26) +#define IMR_BCNDOK8 BIT(25) +#define IMR_BCNDOK7 BIT(24) +#define IMR_BCNDOK6 BIT(23) +#define IMR_BCNDOK5 BIT(22) +#define IMR_BCNDOK4 BIT(21) +#define IMR_BCNDOK3 BIT(20) +#define IMR_BCNDOK2 BIT(19) +#define IMR_BCNDOK1 BIT(18) +#define IMR_TIMEOUT2 BIT(17) +#define IMR_TIMEOUT1 BIT(16) +#define IMR_TXFOVW BIT(15) +#define IMR_PSTIMEOUT BIT(14) +#define IMR_BCNINT BIT(13) +#define IMR_RXFOVW BIT(12) +#define IMR_RDU BIT(11) +#define IMR_ATIMEND BIT(10) +#define IMR_BDOK BIT(9) +#define IMR_HIGHDOK BIT(8) +#define IMR_TBDOK BIT(7) +#define IMR_MGNTDOK BIT(6) +#define IMR_TBDER BIT(5) +#define IMR_BKDOK BIT(4) +#define IMR_BEDOK BIT(3) +#define IMR_VIDOK BIT(2) +#define IMR_VODOK BIT(1) +#define IMR_ROK BIT(0) + +#define IMR_TXERR BIT(11) +#define IMR_RXERR BIT(10) +#define IMR_C2HCMD BIT(9) +#define IMR_CPWM BIT(8) +#define IMR_OCPINT BIT(1) +#define IMR_WLANOFF BIT(0) + +#define EFUSE_REAL_CONTENT_LEN 512 +#define EFUSE_OOB_PROTECT_BYTES 15 + +#define EEPROM_DEFAULT_TSSI 0x0 +#define EEPROM_DEFAULT_TXPOWERDIFF 0x0 +#define EEPROM_DEFAULT_CRYSTALCAP 0x5 +#define EEPROM_DEFAULT_BOARDTYPE 0x02 +#define EEPROM_DEFAULT_TXPOWER 0x1010 +#define EEPROM_DEFAULT_HT2T_TXPWR 0x10 + +#define EEPROM_DEFAULT_LEGACYHTTXPOWERDIFF 0x3 +#define EEPROM_DEFAULT_THERMALMETER 0x12 +#define EEPROM_DEFAULT_ANTTXPOWERDIFF 0x0 +#define EEPROM_DEFAULT_TXPWDIFF_CRYSTALCAP 0x5 +#define EEPROM_DEFAULT_TXPOWERLEVEL 0x22 +#define EEPROM_DEFAULT_HT40_2SDIFF 0x0 +#define EEPROM_DEFAULT_HT20_DIFF 2 +#define EEPROM_DEFAULT_LEGACYHTTXPOWERDIFF 0x3 +#define EEPROM_DEFAULT_HT40_PWRMAXOFFSET 0 +#define EEPROM_DEFAULT_HT20_PWRMAXOFFSET 0 + +#define RF_OPTION1 0x79 +#define RF_OPTION2 0x7A +#define RF_OPTION3 0x7B +#define RF_OPTION4 0x7C + +#define EEPROM_DEFAULT_PID 0x1234 +#define EEPROM_DEFAULT_VID 0x5678 +#define EEPROM_DEFAULT_CUSTOMERID 0xAB +#define EEPROM_DEFAULT_SUBCUSTOMERID 0xCD +#define EEPROM_DEFAULT_VERSION 0 + +#define EEPROM_CHANNEL_PLAN_FCC 0x0 +#define EEPROM_CHANNEL_PLAN_IC 0x1 +#define EEPROM_CHANNEL_PLAN_ETSI 0x2 +#define EEPROM_CHANNEL_PLAN_SPAIN 0x3 +#define EEPROM_CHANNEL_PLAN_FRANCE 0x4 +#define EEPROM_CHANNEL_PLAN_MKK 0x5 +#define EEPROM_CHANNEL_PLAN_MKK1 0x6 +#define EEPROM_CHANNEL_PLAN_ISRAEL 0x7 +#define EEPROM_CHANNEL_PLAN_TELEC 0x8 +#define EEPROM_CHANNEL_PLAN_GLOBAL_DOMAIN 0x9 +#define EEPROM_CHANNEL_PLAN_WORLD_WIDE_13 0xA +#define EEPROM_CHANNEL_PLAN_NCC 0xB +#define EEPROM_CHANNEL_PLAN_BY_HW_MASK 0x80 + +#define EEPROM_CID_DEFAULT 0x0 +#define EEPROM_CID_TOSHIBA 0x4 +#define EEPROM_CID_CCX 0x10 +#define EEPROM_CID_QMI 0x0D +#define EEPROM_CID_WHQL 0xFE + +#define RTL8192_EEPROM_ID 0x8129 + +#define RTL8190_EEPROM_ID 0x8129 +#define EEPROM_HPON 0x02 +#define EEPROM_CLK 0x06 +#define EEPROM_TESTR 0x08 + +#define EEPROM_VID 0x0A +#define EEPROM_DID 0x0C +#define EEPROM_SVID 0x0E +#define EEPROM_SMID 0x10 + +#define EEPROM_MAC_ADDR 0x16 + +#define EEPROM_CCK_TX_PWR_INX 0x5A +#define EEPROM_HT40_1S_TX_PWR_INX 0x60 +#define EEPROM_HT40_2S_TX_PWR_INX_DIFF 0x66 +#define EEPROM_HT20_TX_PWR_INX_DIFF 0x69 +#define EEPROM_OFDM_TX_PWR_INX_DIFF 0x6C +#define EEPROM_HT40_MAX_PWR_OFFSET 0x6F +#define EEPROM_HT20_MAX_PWR_OFFSET 0x72 + +#define EEPROM_TSSI_A 0x76 +#define EEPROM_TSSI_B 0x77 +#define EEPROM_THERMAL_METER 0x78 +#define EEPROM_XTAL_K 0x78 +#define EEPROM_RF_OPT1 0x79 +#define EEPROM_RF_OPT2 0x7A +#define EEPROM_RF_OPT3 0x7B +#define EEPROM_RF_OPT4 0x7C +#define EEPROM_CHANNEL_PLAN 0x7D +#define EEPROM_VERSION 0x7E +#define EEPROM_CUSTOMER_ID 0x7F + +#define EEPROM_PWRDIFF 0x54 + +#define EEPROM_TXPOWERCCK 0x5A +#define EEPROM_TXPOWERHT40_1S 0x60 +#define EEPROM_TXPOWERHT40_2SDIFF 0x66 +#define EEPROM_TXPOWERHT20DIFF 0x69 +#define EEPROM_TXPOWER_OFDMDIFF 0x6C + +#define EEPROM_TXPWR_GROUP 0x6F + +#define EEPROM_TSSI_A 0x76 +#define EEPROM_TSSI_B 0x77 +#define EEPROM_THERMAL_METER 0x78 + +#define EEPROM_CHANNELPLAN 0x75 + +#define RF_OPTION1 0x79 +#define RF_OPTION2 0x7A +#define RF_OPTION3 0x7B +#define RF_OPTION4 0x7C + +#define STOPBECON BIT(6) +#define STOPHIGHT BIT(5) +#define STOPMGT BIT(4) +#define STOPVO BIT(3) +#define STOPVI BIT(2) +#define STOPBE BIT(1) +#define STOPBK BIT(0) + +#define RCR_APPFCS BIT(31) +#define RCR_APP_FCS BIT(31) +#define RCR_APP_MIC BIT(30) +#define RCR_APP_ICV BIT(29) +#define RCR_APP_PHYSTS BIT(28) +#define RCR_APP_PHYST_RXFF BIT(28) +#define RCR_APP_BA_SSN BIT(27) +#define RCR_ENMBID BIT(24) +#define RCR_LSIGEN BIT(23) +#define RCR_MFBEN BIT(22) +#define RCR_HTC_LOC_CTRL BIT(14) +#define RCR_AMF BIT(13) +#define RCR_ACF BIT(12) +#define RCR_ADF BIT(11) +#define RCR_AICV BIT(9) +#define RCR_ACRC32 BIT(8) +#define RCR_CBSSID_BCN BIT(7) +#define RCR_CBSSID_DATA BIT(6) +#define RCR_CBSSID RCR_CBSSID_DATA +#define RCR_APWRMGT BIT(5) +#define RCR_ADD3 BIT(4) +#define RCR_AB BIT(3) +#define RCR_AM BIT(2) +#define RCR_APM BIT(1) +#define RCR_AAP BIT(0) +#define RCR_MXDMA_OFFSET 8 +#define RCR_FIFO_OFFSET 13 + +#define RSV_CTRL 0x001C +#define RD_CTRL 0x0524 + +#define REG_USB_INFO 0xFE17 +#define REG_USB_SPECIAL_OPTION 0xFE55 + +#define REG_USB_DMA_AGG_TO 0xFE5B +#define REG_USB_AGG_TO 0xFE5C +#define REG_USB_AGG_TH 0xFE5D + +#define REG_USB_VID 0xFE60 +#define REG_USB_PID 0xFE62 +#define REG_USB_OPTIONAL 0xFE64 +#define REG_USB_CHIRP_K 0xFE65 +#define REG_USB_PHY 0xFE66 +#define REG_USB_MAC_ADDR 0xFE70 +#define REG_USB_HRPWM 0xFE58 +#define REG_USB_HCPWM 0xFE57 + +#define SW18_FPWM BIT(3) + +#define ISO_MD2PP BIT(0) +#define ISO_UA2USB BIT(1) +#define ISO_UD2CORE BIT(2) +#define ISO_PA2PCIE BIT(3) +#define ISO_PD2CORE BIT(4) +#define ISO_IP2MAC BIT(5) +#define ISO_DIOP BIT(6) +#define ISO_DIOE BIT(7) +#define ISO_EB2CORE BIT(8) +#define ISO_DIOR BIT(9) + +#define PWC_EV25V BIT(14) +#define PWC_EV12V BIT(15) + +#define FEN_BBRSTB BIT(0) +#define FEN_BB_GLB_RSTn BIT(1) +#define FEN_USBA BIT(2) +#define FEN_UPLL BIT(3) +#define FEN_USBD BIT(4) +#define FEN_DIO_PCIE BIT(5) +#define FEN_PCIEA BIT(6) +#define FEN_PPLL BIT(7) +#define FEN_PCIED BIT(8) +#define FEN_DIOE BIT(9) +#define FEN_CPUEN BIT(10) +#define FEN_DCORE BIT(11) +#define FEN_ELDR BIT(12) +#define FEN_DIO_RF BIT(13) +#define FEN_HWPDN BIT(14) +#define FEN_MREGEN BIT(15) + +#define PFM_LDALL BIT(0) +#define PFM_ALDN BIT(1) +#define PFM_LDKP BIT(2) +#define PFM_WOWL BIT(3) +#define EnPDN BIT(4) +#define PDN_PL BIT(5) +#define APFM_ONMAC BIT(8) +#define APFM_OFF BIT(9) +#define APFM_RSM BIT(10) +#define AFSM_HSUS BIT(11) +#define AFSM_PCIE BIT(12) +#define APDM_MAC BIT(13) +#define APDM_HOST BIT(14) +#define APDM_HPDN BIT(15) +#define RDY_MACON BIT(16) +#define SUS_HOST BIT(17) +#define ROP_ALD BIT(20) +#define ROP_PWR BIT(21) +#define ROP_SPS BIT(22) +#define SOP_MRST BIT(25) +#define SOP_FUSE BIT(26) +#define SOP_ABG BIT(27) +#define SOP_AMB BIT(28) +#define SOP_RCK BIT(29) +#define SOP_A8M BIT(30) +#define XOP_BTCK BIT(31) + +#define ANAD16V_EN BIT(0) +#define ANA8M BIT(1) +#define MACSLP BIT(4) +#define LOADER_CLK_EN BIT(5) +#define _80M_SSC_DIS BIT(7) +#define _80M_SSC_EN_HO BIT(8) +#define PHY_SSC_RSTB BIT(9) +#define SEC_CLK_EN BIT(10) +#define MAC_CLK_EN BIT(11) +#define SYS_CLK_EN BIT(12) +#define RING_CLK_EN BIT(13) + +#define BOOT_FROM_EEPROM BIT(4) +#define EEPROM_EN BIT(5) + +#define AFE_BGEN BIT(0) +#define AFE_MBEN BIT(1) +#define MAC_ID_EN BIT(7) + +#define WLOCK_ALL BIT(0) +#define WLOCK_00 BIT(1) +#define WLOCK_04 BIT(2) +#define WLOCK_08 BIT(3) +#define WLOCK_40 BIT(4) +#define R_DIS_PRST_0 BIT(5) +#define R_DIS_PRST_1 BIT(6) +#define LOCK_ALL_EN BIT(7) + +#define RF_EN BIT(0) +#define RF_RSTB BIT(1) +#define RF_SDMRSTB BIT(2) + +#define LDA15_EN BIT(0) +#define LDA15_STBY BIT(1) +#define LDA15_OBUF BIT(2) +#define LDA15_REG_VOS BIT(3) +#define _LDA15_VOADJ(x) (((x) & 0x7) << 4) + +#define LDV12_EN BIT(0) +#define LDV12_SDBY BIT(1) +#define LPLDO_HSM BIT(2) +#define LPLDO_LSM_DIS BIT(3) +#define _LDV12_VADJ(x) (((x) & 0xF) << 4) + +#define XTAL_EN BIT(0) +#define XTAL_BSEL BIT(1) +#define _XTAL_BOSC(x) (((x) & 0x3) << 2) +#define _XTAL_CADJ(x) (((x) & 0xF) << 4) +#define XTAL_GATE_USB BIT(8) +#define _XTAL_USB_DRV(x) (((x) & 0x3) << 9) +#define XTAL_GATE_AFE BIT(11) +#define _XTAL_AFE_DRV(x) (((x) & 0x3) << 12) +#define XTAL_RF_GATE BIT(14) +#define _XTAL_RF_DRV(x) (((x) & 0x3) << 15) +#define XTAL_GATE_DIG BIT(17) +#define _XTAL_DIG_DRV(x) (((x) & 0x3) << 18) +#define XTAL_BT_GATE BIT(20) +#define _XTAL_BT_DRV(x) (((x) & 0x3) << 21) +#define _XTAL_GPIO(x) (((x) & 0x7) << 23) + +#define CKDLY_AFE BIT(26) +#define CKDLY_USB BIT(27) +#define CKDLY_DIG BIT(28) +#define CKDLY_BT BIT(29) + +#define APLL_EN BIT(0) +#define APLL_320_EN BIT(1) +#define APLL_FREF_SEL BIT(2) +#define APLL_EDGE_SEL BIT(3) +#define APLL_WDOGB BIT(4) +#define APLL_LPFEN BIT(5) + +#define APLL_REF_CLK_13MHZ 0x1 +#define APLL_REF_CLK_19_2MHZ 0x2 +#define APLL_REF_CLK_20MHZ 0x3 +#define APLL_REF_CLK_25MHZ 0x4 +#define APLL_REF_CLK_26MHZ 0x5 +#define APLL_REF_CLK_38_4MHZ 0x6 +#define APLL_REF_CLK_40MHZ 0x7 + +#define APLL_320EN BIT(14) +#define APLL_80EN BIT(15) +#define APLL_1MEN BIT(24) + +#define ALD_EN BIT(18) +#define EF_PD BIT(19) +#define EF_FLAG BIT(31) + +#define EF_TRPT BIT(7) +#define LDOE25_EN BIT(31) + +#define RSM_EN BIT(0) +#define Timer_EN BIT(4) + +#define TRSW0EN BIT(2) +#define TRSW1EN BIT(3) +#define EROM_EN BIT(4) +#define EnBT BIT(5) +#define EnUart BIT(8) +#define Uart_910 BIT(9) +#define EnPMAC BIT(10) +#define SIC_SWRST BIT(11) +#define EnSIC BIT(12) +#define SIC_23 BIT(13) +#define EnHDP BIT(14) +#define SIC_LBK BIT(15) + +#define LED0PL BIT(4) +#define LED1PL BIT(12) +#define LED0DIS BIT(7) + +#define MCUFWDL_EN BIT(0) +#define MCUFWDL_RDY BIT(1) +#define FWDL_ChkSum_rpt BIT(2) +#define MACINI_RDY BIT(3) +#define BBINI_RDY BIT(4) +#define RFINI_RDY BIT(5) +#define WINTINI_RDY BIT(6) +#define CPRST BIT(23) + +#define XCLK_VLD BIT(0) +#define ACLK_VLD BIT(1) +#define UCLK_VLD BIT(2) +#define PCLK_VLD BIT(3) +#define PCIRSTB BIT(4) +#define V15_VLD BIT(5) +#define TRP_B15V_EN BIT(7) +#define SIC_IDLE BIT(8) +#define BD_MAC2 BIT(9) +#define BD_MAC1 BIT(10) +#define IC_MACPHY_MODE BIT(11) +#define BT_FUNC BIT(16) +#define VENDOR_ID BIT(19) +#define PAD_HWPD_IDN BIT(22) +#define TRP_VAUX_EN BIT(23) +#define TRP_BT_EN BIT(24) +#define BD_PKG_SEL BIT(25) +#define BD_HCI_SEL BIT(26) +#define TYPE_ID BIT(27) +#define RF_RL_ID (BIT(31) | BIT(30) | BIT(29) | BIT(28)) + +#define CHIP_VER_RTL_MASK 0xF000 +#define CHIP_VER_RTL_SHIFT 12 + +#define REG_LBMODE (REG_CR + 3) + +#define HCI_TXDMA_EN BIT(0) +#define HCI_RXDMA_EN BIT(1) +#define TXDMA_EN BIT(2) +#define RXDMA_EN BIT(3) +#define PROTOCOL_EN BIT(4) +#define SCHEDULE_EN BIT(5) +#define MACTXEN BIT(6) +#define MACRXEN BIT(7) +#define ENSWBCN BIT(8) +#define ENSEC BIT(9) + +#define _NETTYPE(x) (((x) & 0x3) << 16) +#define MASK_NETTYPE 0x30000 +#define NT_NO_LINK 0x0 +#define NT_LINK_AD_HOC 0x1 +#define NT_LINK_AP 0x2 +#define NT_AS_AP 0x3 + +#define _LBMODE(x) (((x) & 0xF) << 24) +#define MASK_LBMODE 0xF000000 +#define LOOPBACK_NORMAL 0x0 +#define LOOPBACK_IMMEDIATELY 0xB +#define LOOPBACK_MAC_DELAY 0x3 +#define LOOPBACK_PHY 0x1 +#define LOOPBACK_DMA 0x7 + +#define GET_RX_PAGE_SIZE(value) ((value) & 0xF) +#define GET_TX_PAGE_SIZE(value) (((value) & 0xF0) >> 4) +#define _PSRX_MASK 0xF +#define _PSTX_MASK 0xF0 +#define _PSRX(x) (x) +#define _PSTX(x) ((x) << 4) + +#define PBP_64 0x0 +#define PBP_128 0x1 +#define PBP_256 0x2 +#define PBP_512 0x3 +#define PBP_1024 0x4 + +#define RXDMA_ARBBW_EN BIT(0) +#define RXSHFT_EN BIT(1) +#define RXDMA_AGG_EN BIT(2) +#define QS_VO_QUEUE BIT(8) +#define QS_VI_QUEUE BIT(9) +#define QS_BE_QUEUE BIT(10) +#define QS_BK_QUEUE BIT(11) +#define QS_MANAGER_QUEUE BIT(12) +#define QS_HIGH_QUEUE BIT(13) + +#define HQSEL_VOQ BIT(0) +#define HQSEL_VIQ BIT(1) +#define HQSEL_BEQ BIT(2) +#define HQSEL_BKQ BIT(3) +#define HQSEL_MGTQ BIT(4) +#define HQSEL_HIQ BIT(5) + +#define _TXDMA_HIQ_MAP(x) (((x)&0x3) << 14) +#define _TXDMA_MGQ_MAP(x) (((x)&0x3) << 12) +#define _TXDMA_BKQ_MAP(x) (((x)&0x3) << 10) +#define _TXDMA_BEQ_MAP(x) (((x)&0x3) << 8) +#define _TXDMA_VIQ_MAP(x) (((x)&0x3) << 6) +#define _TXDMA_VOQ_MAP(x) (((x)&0x3) << 4) + +#define QUEUE_LOW 1 +#define QUEUE_NORMAL 2 +#define QUEUE_HIGH 3 + +#define _LLT_NO_ACTIVE 0x0 +#define _LLT_WRITE_ACCESS 0x1 +#define _LLT_READ_ACCESS 0x2 + +#define _LLT_INIT_DATA(x) ((x) & 0xFF) +#define _LLT_INIT_ADDR(x) (((x) & 0xFF) << 8) +#define _LLT_OP(x) (((x) & 0x3) << 30) +#define _LLT_OP_VALUE(x) (((x) >> 30) & 0x3) + +#define BB_WRITE_READ_MASK (BIT(31) | BIT(30)) +#define BB_WRITE_EN BIT(30) +#define BB_READ_EN BIT(31) + +#define _HPQ(x) ((x) & 0xFF) +#define _LPQ(x) (((x) & 0xFF) << 8) +#define _PUBQ(x) (((x) & 0xFF) << 16) +#define _NPQ(x) ((x) & 0xFF) + +#define HPQ_PUBLIC_DIS BIT(24) +#define LPQ_PUBLIC_DIS BIT(25) +#define LD_RQPN BIT(31) + +#define BCN_VALID BIT(16) +#define BCN_HEAD(x) (((x) & 0xFF) << 8) +#define BCN_HEAD_MASK 0xFF00 + +#define BLK_DESC_NUM_SHIFT 4 +#define BLK_DESC_NUM_MASK 0xF + +#define DROP_DATA_EN BIT(9) + +#define EN_AMPDU_RTY_NEW BIT(7) + +#define _INIRTSMCS_SEL(x) ((x) & 0x3F) + +#define _SPEC_SIFS_CCK(x) ((x) & 0xFF) +#define _SPEC_SIFS_OFDM(x) (((x) & 0xFF) << 8) + +#define RATE_REG_BITMAP_ALL 0xFFFFF + +#define _RRSC_BITMAP(x) ((x) & 0xFFFFF) + +#define _RRSR_RSC(x) (((x) & 0x3) << 21) +#define RRSR_RSC_RESERVED 0x0 +#define RRSR_RSC_UPPER_SUBCHANNEL 0x1 +#define RRSR_RSC_LOWER_SUBCHANNEL 0x2 +#define RRSR_RSC_DUPLICATE_MODE 0x3 + +#define USE_SHORT_G1 BIT(20) + +#define _AGGLMT_MCS0(x) ((x) & 0xF) +#define _AGGLMT_MCS1(x) (((x) & 0xF) << 4) +#define _AGGLMT_MCS2(x) (((x) & 0xF) << 8) +#define _AGGLMT_MCS3(x) (((x) & 0xF) << 12) +#define _AGGLMT_MCS4(x) (((x) & 0xF) << 16) +#define _AGGLMT_MCS5(x) (((x) & 0xF) << 20) +#define _AGGLMT_MCS6(x) (((x) & 0xF) << 24) +#define _AGGLMT_MCS7(x) (((x) & 0xF) << 28) + +#define RETRY_LIMIT_SHORT_SHIFT 8 +#define RETRY_LIMIT_LONG_SHIFT 0 + +#define _DARF_RC1(x) ((x) & 0x1F) +#define _DARF_RC2(x) (((x) & 0x1F) << 8) +#define _DARF_RC3(x) (((x) & 0x1F) << 16) +#define _DARF_RC4(x) (((x) & 0x1F) << 24) +#define _DARF_RC5(x) ((x) & 0x1F) +#define _DARF_RC6(x) (((x) & 0x1F) << 8) +#define _DARF_RC7(x) (((x) & 0x1F) << 16) +#define _DARF_RC8(x) (((x) & 0x1F) << 24) + +#define _RARF_RC1(x) ((x) & 0x1F) +#define _RARF_RC2(x) (((x) & 0x1F) << 8) +#define _RARF_RC3(x) (((x) & 0x1F) << 16) +#define _RARF_RC4(x) (((x) & 0x1F) << 24) +#define _RARF_RC5(x) ((x) & 0x1F) +#define _RARF_RC6(x) (((x) & 0x1F) << 8) +#define _RARF_RC7(x) (((x) & 0x1F) << 16) +#define _RARF_RC8(x) (((x) & 0x1F) << 24) + +#define AC_PARAM_TXOP_OFFSET 16 +#define AC_PARAM_TXOP_LIMIT_OFFSET 16 +#define AC_PARAM_ECW_MAX_OFFSET 12 +#define AC_PARAM_ECW_MIN_OFFSET 8 +#define AC_PARAM_AIFS_OFFSET 0 + +#define _AIFS(x) (x) +#define _ECW_MAX_MIN(x) ((x) << 8) +#define _TXOP_LIMIT(x) ((x) << 16) + +#define _BCNIFS(x) ((x) & 0xFF) +#define _BCNECW(x) ((((x) & 0xF)) << 8) + +#define _LRL(x) ((x) & 0x3F) +#define _SRL(x) (((x) & 0x3F) << 8) + +#define _SIFS_CCK_CTX(x) ((x) & 0xFF) +#define _SIFS_CCK_TRX(x) (((x) & 0xFF) << 8) + +#define _SIFS_OFDM_CTX(x) ((x) & 0xFF) +#define _SIFS_OFDM_TRX(x) (((x) & 0xFF) << 8) + +#define _TBTT_PROHIBIT_HOLD(x) (((x) & 0xFF) << 8) + +#define DIS_EDCA_CNT_DWN BIT(11) + +#define EN_MBSSID BIT(1) +#define EN_TXBCN_RPT BIT(2) +#define EN_BCN_FUNCTION BIT(3) + +#define TSFTR_RST BIT(0) +#define TSFTR1_RST BIT(1) + +#define STOP_BCNQ BIT(6) + +#define DIS_TSF_UDT0_NORMAL_CHIP BIT(4) +#define DIS_TSF_UDT0_TEST_CHIP BIT(5) + +#define AcmHw_HwEn BIT(0) +#define AcmHw_BeqEn BIT(1) +#define AcmHw_ViqEn BIT(2) +#define AcmHw_VoqEn BIT(3) +#define AcmHw_BeqStatus BIT(4) +#define AcmHw_ViqStatus BIT(5) +#define AcmHw_VoqStatus BIT(6) + +#define APSDOFF BIT(6) +#define APSDOFF_STATUS BIT(7) + +#define BW_20MHZ BIT(2) + +#define RATE_BITMAP_ALL 0xFFFFF + +#define RATE_RRSR_CCK_ONLY_1M 0xFFFF1 + +#define TSFRST BIT(0) +#define DIS_GCLK BIT(1) +#define PAD_SEL BIT(2) +#define PWR_ST BIT(6) +#define PWRBIT_OW_EN BIT(7) +#define ACRC BIT(8) +#define CFENDFORM BIT(9) +#define ICV BIT(10) + +#define AAP BIT(0) +#define APM BIT(1) +#define AM BIT(2) +#define AB BIT(3) +#define ADD3 BIT(4) +#define APWRMGT BIT(5) +#define CBSSID BIT(6) +#define CBSSID_DATA BIT(6) +#define CBSSID_BCN BIT(7) +#define ACRC32 BIT(8) +#define AICV BIT(9) +#define ADF BIT(11) +#define ACF BIT(12) +#define AMF BIT(13) +#define HTC_LOC_CTRL BIT(14) +#define UC_DATA_EN BIT(16) +#define BM_DATA_EN BIT(17) +#define MFBEN BIT(22) +#define LSIGEN BIT(23) +#define EnMBID BIT(24) +#define APP_BASSN BIT(27) +#define APP_PHYSTS BIT(28) +#define APP_ICV BIT(29) +#define APP_MIC BIT(30) +#define APP_FCS BIT(31) + +#define _MIN_SPACE(x) ((x) & 0x7) +#define _SHORT_GI_PADDING(x) (((x) & 0x1F) << 3) + +#define RXERR_TYPE_OFDM_PPDU 0 +#define RXERR_TYPE_OFDM_FALSE_ALARM 1 +#define RXERR_TYPE_OFDM_MPDU_OK 2 +#define RXERR_TYPE_OFDM_MPDU_FAIL 3 +#define RXERR_TYPE_CCK_PPDU 4 +#define RXERR_TYPE_CCK_FALSE_ALARM 5 +#define RXERR_TYPE_CCK_MPDU_OK 6 +#define RXERR_TYPE_CCK_MPDU_FAIL 7 +#define RXERR_TYPE_HT_PPDU 8 +#define RXERR_TYPE_HT_FALSE_ALARM 9 +#define RXERR_TYPE_HT_MPDU_TOTAL 10 +#define RXERR_TYPE_HT_MPDU_OK 11 +#define RXERR_TYPE_HT_MPDU_FAIL 12 +#define RXERR_TYPE_RX_FULL_DROP 15 + +#define RXERR_COUNTER_MASK 0xFFFFF +#define RXERR_RPT_RST BIT(27) +#define _RXERR_RPT_SEL(type) ((type) << 28) + +#define SCR_TxUseDK BIT(0) +#define SCR_RxUseDK BIT(1) +#define SCR_TxEncEnable BIT(2) +#define SCR_RxDecEnable BIT(3) +#define SCR_SKByA2 BIT(4) +#define SCR_NoSKMC BIT(5) +#define SCR_TXBCUSEDK BIT(6) +#define SCR_RXBCUSEDK BIT(7) + +#define USB_IS_HIGH_SPEED 0 +#define USB_IS_FULL_SPEED 1 +#define USB_SPEED_MASK BIT(5) + +#define USB_NORMAL_SIE_EP_MASK 0xF +#define USB_NORMAL_SIE_EP_SHIFT 4 + +#define USB_TEST_EP_MASK 0x30 +#define USB_TEST_EP_SHIFT 4 + +#define USB_AGG_EN BIT(3) + +#define LAST_ENTRY_OF_TX_PKT_BUFFER 255 + +#define POLLING_LLT_THRESHOLD 20 +#define POLLING_READY_TIMEOUT_COUNT 1000 + +#define MAX_MSS_DENSITY_2T 0x13 +#define MAX_MSS_DENSITY_1T 0x0A + +#define EPROM_CMD_OPERATING_MODE_MASK ((1<<7)|(1<<6)) +#define EPROM_CMD_CONFIG 0x3 +#define EPROM_CMD_LOAD 1 + +#define HWSET_MAX_SIZE 128 +#define HWSET_MAX_SIZE_92S HWSET_MAX_SIZE +#define EFUSE_MAX_SECTION 16 + +#define WL_HWPDN_EN BIT(0) + +#define HAL_8192C_HW_GPIO_WPS_BIT BIT(2) + +#define RPMAC_RESET 0x100 +#define RPMAC_TXSTART 0x104 +#define RPMAC_TXLEGACYSIG 0x108 +#define RPMAC_TXHTSIG1 0x10c +#define RPMAC_TXHTSIG2 0x110 +#define RPMAC_PHYDEBUG 0x114 +#define RPMAC_TXPACKETNUM 0x118 +#define RPMAC_TXIDLE 0x11c +#define RPMAC_TXMACHEADER0 0x120 +#define RPMAC_TXMACHEADER1 0x124 +#define RPMAC_TXMACHEADER2 0x128 +#define RPMAC_TXMACHEADER3 0x12c +#define RPMAC_TXMACHEADER4 0x130 +#define RPMAC_TXMACHEADER5 0x134 +#define RPMAC_TXDADATYPE 0x138 +#define RPMAC_TXRANDOMSEED 0x13c +#define RPMAC_CCKPLCPPREAMBLE 0x140 +#define RPMAC_CCKPLCPHEADER 0x144 +#define RPMAC_CCKCRC16 0x148 +#define RPMAC_OFDMRXCRC32OK 0x170 +#define RPMAC_OFDMRXCRC32Er 0x174 +#define RPMAC_OFDMRXPARITYER 0x178 +#define RPMAC_OFDMRXCRC8ER 0x17c +#define RPMAC_CCKCRXRC16ER 0x180 +#define RPMAC_CCKCRXRC32ER 0x184 +#define RPMAC_CCKCRXRC32OK 0x188 +#define RPMAC_TXSTATUS 0x18c + +#define RFPGA0_RFMOD 0x800 + +#define RFPGA0_TXINFO 0x804 +#define RFPGA0_PSDFUNCTION 0x808 + +#define RFPGA0_TXGAINSTAGE 0x80c + +#define RFPGA0_RFTIMING1 0x810 +#define RFPGA0_RFTIMING2 0x814 + +#define RFPGA0_XA_HSSIPARAMETER1 0x820 +#define RFPGA0_XA_HSSIPARAMETER2 0x824 +#define RFPGA0_XB_HSSIPARAMETER1 0x828 +#define RFPGA0_XB_HSSIPARAMETER2 0x82c + +#define RFPGA0_XA_LSSIPARAMETER 0x840 +#define RFPGA0_XB_LSSIPARAMETER 0x844 + +#define RFPGA0_RFWAKEUPPARAMETER 0x850 +#define RFPGA0_RFSLEEPUPPARAMETER 0x854 + +#define RFPGA0_XAB_SWITCHCONTROL 0x858 +#define RFPGA0_XCD_SWITCHCONTROL 0x85c + +#define RFPGA0_XA_RFINTERFACEOE 0x860 +#define RFPGA0_XB_RFINTERFACEOE 0x864 + +#define RFPGA0_XAB_RFINTERFACESW 0x870 +#define RFPGA0_XCD_RFINTERFACESW 0x874 + +#define rFPGA0_XAB_RFPARAMETER 0x878 +#define rFPGA0_XCD_RFPARAMETER 0x87c + +#define RFPGA0_ANALOGPARAMETER1 0x880 +#define RFPGA0_ANALOGPARAMETER2 0x884 +#define RFPGA0_ANALOGPARAMETER3 0x888 +#define RFPGA0_ANALOGPARAMETER4 0x88c + +#define RFPGA0_XA_LSSIREADBACK 0x8a0 +#define RFPGA0_XB_LSSIREADBACK 0x8a4 +#define RFPGA0_XC_LSSIREADBACK 0x8a8 +#define RFPGA0_XD_LSSIREADBACK 0x8ac + +#define RFPGA0_PSDREPORT 0x8b4 +#define TRANSCEIVEA_HSPI_READBACK 0x8b8 +#define TRANSCEIVEB_HSPI_READBACK 0x8bc +#define RFPGA0_XAB_RFINTERFACERB 0x8e0 +#define RFPGA0_XCD_RFINTERFACERB 0x8e4 + +#define RFPGA1_RFMOD 0x900 + +#define RFPGA1_TXBLOCK 0x904 +#define RFPGA1_DEBUGSELECT 0x908 +#define RFPGA1_TXINFO 0x90c + +#define RCCK0_SYSTEM 0xa00 + +#define RCCK0_AFESETTING 0xa04 +#define RCCK0_CCA 0xa08 + +#define RCCK0_RXAGC1 0xa0c +#define RCCK0_RXAGC2 0xa10 + +#define RCCK0_RXHP 0xa14 + +#define RCCK0_DSPPARAMETER1 0xa18 +#define RCCK0_DSPPARAMETER2 0xa1c + +#define RCCK0_TXFILTER1 0xa20 +#define RCCK0_TXFILTER2 0xa24 +#define RCCK0_DEBUGPORT 0xa28 +#define RCCK0_FALSEALARMREPORT 0xa2c +#define RCCK0_TRSSIREPORT 0xa50 +#define RCCK0_RXREPORT 0xa54 +#define RCCK0_FACOUNTERLOWER 0xa5c +#define RCCK0_FACOUNTERUPPER 0xa58 + +#define ROFDM0_LSTF 0xc00 + +#define ROFDM0_TRXPATHENABLE 0xc04 +#define ROFDM0_TRMUXPAR 0xc08 +#define ROFDM0_TRSWISOLATION 0xc0c + +#define ROFDM0_XARXAFE 0xc10 +#define ROFDM0_XARXIQIMBALANCE 0xc14 +#define ROFDM0_XBRXAFE 0xc18 +#define ROFDM0_XBRXIQIMBALANCE 0xc1c +#define ROFDM0_XCRXAFE 0xc20 +#define ROFDM0_XCRXIQIMBANLANCE 0xc24 +#define ROFDM0_XDRXAFE 0xc28 +#define ROFDM0_XDRXIQIMBALANCE 0xc2c + +#define ROFDM0_RXDETECTOR1 0xc30 +#define ROFDM0_RXDETECTOR2 0xc34 +#define ROFDM0_RXDETECTOR3 0xc38 +#define ROFDM0_RXDETECTOR4 0xc3c + +#define ROFDM0_RXDSP 0xc40 +#define ROFDM0_CFOANDDAGC 0xc44 +#define ROFDM0_CCADROPTHRESHOLD 0xc48 +#define ROFDM0_ECCATHRESHOLD 0xc4c + +#define ROFDM0_XAAGCCORE1 0xc50 +#define ROFDM0_XAAGCCORE2 0xc54 +#define ROFDM0_XBAGCCORE1 0xc58 +#define ROFDM0_XBAGCCORE2 0xc5c +#define ROFDM0_XCAGCCORE1 0xc60 +#define ROFDM0_XCAGCCORE2 0xc64 +#define ROFDM0_XDAGCCORE1 0xc68 +#define ROFDM0_XDAGCCORE2 0xc6c + +#define ROFDM0_AGCPARAMETER1 0xc70 +#define ROFDM0_AGCPARAMETER2 0xc74 +#define ROFDM0_AGCRSSITABLE 0xc78 +#define ROFDM0_HTSTFAGC 0xc7c + +#define ROFDM0_XATXIQIMBALANCE 0xc80 +#define ROFDM0_XATXAFE 0xc84 +#define ROFDM0_XBTXIQIMBALANCE 0xc88 +#define ROFDM0_XBTXAFE 0xc8c +#define ROFDM0_XCTXIQIMBALANCE 0xc90 +#define ROFDM0_XCTXAFE 0xc94 +#define ROFDM0_XDTXIQIMBALANCE 0xc98 +#define ROFDM0_XDTXAFE 0xc9c + +#define ROFDM0_RXIQEXTANTA 0xca0 + +#define ROFDM0_RXHPPARAMETER 0xce0 +#define ROFDM0_TXPSEUDONOISEWGT 0xce4 +#define ROFDM0_FRAMESYNC 0xcf0 +#define ROFDM0_DFSREPORT 0xcf4 +#define ROFDM0_TXCOEFF1 0xca4 +#define ROFDM0_TXCOEFF2 0xca8 +#define ROFDM0_TXCOEFF3 0xcac +#define ROFDM0_TXCOEFF4 0xcb0 +#define ROFDM0_TXCOEFF5 0xcb4 +#define ROFDM0_TXCOEFF6 0xcb8 + +#define ROFDM1_LSTF 0xd00 +#define ROFDM1_TRXPATHENABLE 0xd04 + +#define ROFDM1_CF0 0xd08 +#define ROFDM1_CSI1 0xd10 +#define ROFDM1_SBD 0xd14 +#define ROFDM1_CSI2 0xd18 +#define ROFDM1_CFOTRACKING 0xd2c +#define ROFDM1_TRXMESAURE1 0xd34 +#define ROFDM1_INTFDET 0xd3c +#define ROFDM1_PSEUDONOISESTATEAB 0xd50 +#define ROFDM1_PSEUDONOISESTATECD 0xd54 +#define ROFDM1_RXPSEUDONOISEWGT 0xd58 + +#define ROFDM_PHYCOUNTER1 0xda0 +#define ROFDM_PHYCOUNTER2 0xda4 +#define ROFDM_PHYCOUNTER3 0xda8 + +#define ROFDM_SHORTCFOAB 0xdac +#define ROFDM_SHORTCFOCD 0xdb0 +#define ROFDM_LONGCFOAB 0xdb4 +#define ROFDM_LONGCFOCD 0xdb8 +#define ROFDM_TAILCF0AB 0xdbc +#define ROFDM_TAILCF0CD 0xdc0 +#define ROFDM_PWMEASURE1 0xdc4 +#define ROFDM_PWMEASURE2 0xdc8 +#define ROFDM_BWREPORT 0xdcc +#define ROFDM_AGCREPORT 0xdd0 +#define ROFDM_RXSNR 0xdd4 +#define ROFDM_RXEVMCSI 0xdd8 +#define ROFDM_SIGREPORT 0xddc + +#define RTXAGC_A_RATE18_06 0xe00 +#define RTXAGC_A_RATE54_24 0xe04 +#define RTXAGC_A_CCK1_MCS32 0xe08 +#define RTXAGC_A_MCS03_MCS00 0xe10 +#define RTXAGC_A_MCS07_MCS04 0xe14 +#define RTXAGC_A_MCS11_MCS08 0xe18 +#define RTXAGC_A_MCS15_MCS12 0xe1c + +#define RTXAGC_B_RATE18_06 0x830 +#define RTXAGC_B_RATE54_24 0x834 +#define RTXAGC_B_CCK1_55_MCS32 0x838 +#define RTXAGC_B_MCS03_MCS00 0x83c +#define RTXAGC_B_MCS07_MCS04 0x848 +#define RTXAGC_B_MCS11_MCS08 0x84c +#define RTXAGC_B_MCS15_MCS12 0x868 +#define RTXAGC_B_CCK11_A_CCK2_11 0x86c + +#define RZEBRA1_HSSIENABLE 0x0 +#define RZEBRA1_TRXENABLE1 0x1 +#define RZEBRA1_TRXENABLE2 0x2 +#define RZEBRA1_AGC 0x4 +#define RZEBRA1_CHARGEPUMP 0x5 +#define RZEBRA1_CHANNEL 0x7 + +#define RZEBRA1_TXGAIN 0x8 +#define RZEBRA1_TXLPF 0x9 +#define RZEBRA1_RXLPF 0xb +#define RZEBRA1_RXHPFCORNER 0xc + +#define RGLOBALCTRL 0 +#define RRTL8256_TXLPF 19 +#define RRTL8256_RXLPF 11 +#define RRTL8258_TXLPF 0x11 +#define RRTL8258_RXLPF 0x13 +#define RRTL8258_RSSILPF 0xa + +#define RF_AC 0x00 + +#define RF_IQADJ_G1 0x01 +#define RF_IQADJ_G2 0x02 +#define RF_POW_TRSW 0x05 + +#define RF_GAIN_RX 0x06 +#define RF_GAIN_TX 0x07 + +#define RF_TXM_IDAC 0x08 +#define RF_BS_IQGEN 0x0F + +#define RF_MODE1 0x10 +#define RF_MODE2 0x11 + +#define RF_RX_AGC_HP 0x12 +#define RF_TX_AGC 0x13 +#define RF_BIAS 0x14 +#define RF_IPA 0x15 +#define RF_POW_ABILITY 0x17 +#define RF_MODE_AG 0x18 +#define RRFCHANNEL 0x18 +#define RF_CHNLBW 0x18 +#define RF_TOP 0x19 + +#define RF_RX_G1 0x1A +#define RF_RX_G2 0x1B + +#define RF_RX_BB2 0x1C +#define RF_RX_BB1 0x1D + +#define RF_RCK1 0x1E +#define RF_RCK2 0x1F + +#define RF_TX_G1 0x20 +#define RF_TX_G2 0x21 +#define RF_TX_G3 0x22 + +#define RF_TX_BB1 0x23 +#define RF_T_METER 0x24 + +#define RF_SYN_G1 0x25 +#define RF_SYN_G2 0x26 +#define RF_SYN_G3 0x27 +#define RF_SYN_G4 0x28 +#define RF_SYN_G5 0x29 +#define RF_SYN_G6 0x2A +#define RF_SYN_G7 0x2B +#define RF_SYN_G8 0x2C + +#define RF_RCK_OS 0x30 +#define RF_TXPA_G1 0x31 +#define RF_TXPA_G2 0x32 +#define RF_TXPA_G3 0x33 + +#define BBBRESETB 0x100 +#define BGLOBALRESETB 0x200 +#define BOFDMTXSTART 0x4 +#define BCCKTXSTART 0x8 +#define BCRC32DEBUG 0x100 +#define BPMACLOOPBACK 0x10 +#define BTXLSIG 0xffffff +#define BOFDMTXRATE 0xf +#define BOFDMTXRESERVED 0x10 +#define BOFDMTXLENGTH 0x1ffe0 +#define BOFDMTXPARITY 0x20000 +#define BTXHTSIG1 0xffffff +#define BTXHTMCSRATE 0x7f +#define BTXHTBW 0x80 +#define BTXHTLENGTH 0xffff00 +#define BTXHTSIG2 0xffffff +#define BTXHTSMOOTHING 0x1 +#define BTXHTSOUNDING 0x2 +#define BTXHTRESERVED 0x4 +#define BTXHTAGGREATION 0x8 +#define BTXHTSTBC 0x30 +#define BTXHTADVANCECODING 0x40 +#define BTXHTSHORTGI 0x80 +#define BTXHTNUMBERHT_LTF 0x300 +#define BTXHTCRC8 0x3fc00 +#define BCOUNTERRESET 0x10000 +#define BNUMOFOFDMTX 0xffff +#define BNUMOFCCKTX 0xffff0000 +#define BTXIDLEINTERVAL 0xffff +#define BOFDMSERVICE 0xffff0000 +#define BTXMACHEADER 0xffffffff +#define BTXDATAINIT 0xff +#define BTXHTMODE 0x100 +#define BTXDATATYPE 0x30000 +#define BTXRANDOMSEED 0xffffffff +#define BCCKTXPREAMBLE 0x1 +#define BCCKTXSFD 0xffff0000 +#define BCCKTXSIG 0xff +#define BCCKTXSERVICE 0xff00 +#define BCCKLENGTHEXT 0x8000 +#define BCCKTXLENGHT 0xffff0000 +#define BCCKTXCRC16 0xffff +#define BCCKTXSTATUS 0x1 +#define BOFDMTXSTATUS 0x2 +#define IS_BB_REG_OFFSET_92S(_Offset) \ + ((_Offset >= 0x800) && (_Offset <= 0xfff)) + +#define BRFMOD 0x1 +#define BJAPANMODE 0x2 +#define BCCKTXSC 0x30 +#define BCCKEN 0x1000000 +#define BOFDMEN 0x2000000 + +#define BOFDMRXADCPHASE 0x10000 +#define BOFDMTXDACPHASE 0x40000 +#define BXATXAGC 0x3f + +#define BXBTXAGC 0xf00 +#define BXCTXAGC 0xf000 +#define BXDTXAGC 0xf0000 + +#define BPASTART 0xf0000000 +#define BTRSTART 0x00f00000 +#define BRFSTART 0x0000f000 +#define BBBSTART 0x000000f0 +#define BBBCCKSTART 0x0000000f +#define BPAEND 0xf +#define BTREND 0x0f000000 +#define BRFEND 0x000f0000 +#define BCCAMASK 0x000000f0 +#define BR2RCCAMASK 0x00000f00 +#define BHSSI_R2TDELAY 0xf8000000 +#define BHSSI_T2RDELAY 0xf80000 +#define BCONTXHSSI 0x400 +#define BIGFROMCCK 0x200 +#define BAGCADDRESS 0x3f +#define BRXHPTX 0x7000 +#define BRXHP2RX 0x38000 +#define BRXHPCCKINI 0xc0000 +#define BAGCTXCODE 0xc00000 +#define BAGCRXCODE 0x300000 + +#define B3WIREDATALENGTH 0x800 +#define B3WIREADDREAALENGTH 0x400 + +#define B3WIRERFPOWERDOWN 0x1 +#define B5GPAPEPOLARITY 0x40000000 +#define B2GPAPEPOLARITY 0x80000000 +#define BRFSW_TXDEFAULTANT 0x3 +#define BRFSW_TXOPTIONANT 0x30 +#define BRFSW_RXDEFAULTANT 0x300 +#define BRFSW_RXOPTIONANT 0x3000 +#define BRFSI_3WIREDATA 0x1 +#define BRFSI_3WIRECLOCK 0x2 +#define BRFSI_3WIRELOAD 0x4 +#define BRFSI_3WIRERW 0x8 +#define BRFSI_3WIRE 0xf + +#define BRFSI_RFENV 0x10 + +#define BRFSI_TRSW 0x20 +#define BRFSI_TRSWB 0x40 +#define BRFSI_ANTSW 0x100 +#define BRFSI_ANTSWB 0x200 +#define BRFSI_PAPE 0x400 +#define BRFSI_PAPE5G 0x800 +#define BBANDSELECT 0x1 +#define BHTSIG2_GI 0x80 +#define BHTSIG2_SMOOTHING 0x01 +#define BHTSIG2_SOUNDING 0x02 +#define BHTSIG2_AGGREATON 0x08 +#define BHTSIG2_STBC 0x30 +#define BHTSIG2_ADVCODING 0x40 +#define BHTSIG2_NUMOFHTLTF 0x300 +#define BHTSIG2_CRC8 0x3fc +#define BHTSIG1_MCS 0x7f +#define BHTSIG1_BANDWIDTH 0x80 +#define BHTSIG1_HTLENGTH 0xffff +#define BLSIG_RATE 0xf +#define BLSIG_RESERVED 0x10 +#define BLSIG_LENGTH 0x1fffe +#define BLSIG_PARITY 0x20 +#define BCCKRXPHASE 0x4 + +#define BLSSIREADADDRESS 0x7f800000 +#define BLSSIREADEDGE 0x80000000 + +#define BLSSIREADBACKDATA 0xfffff + +#define BLSSIREADOKFLAG 0x1000 +#define BCCKSAMPLERATE 0x8 +#define BREGULATOR0STANDBY 0x1 +#define BREGULATORPLLSTANDBY 0x2 +#define BREGULATOR1STANDBY 0x4 +#define BPLLPOWERUP 0x8 +#define BDPLLPOWERUP 0x10 +#define BDA10POWERUP 0x20 +#define BAD7POWERUP 0x200 +#define BDA6POWERUP 0x2000 +#define BXTALPOWERUP 0x4000 +#define B40MDCLKPOWERUP 0x8000 +#define BDA6DEBUGMODE 0x20000 +#define BDA6SWING 0x380000 + +#define BADCLKPHASE 0x4000000 +#define B80MCLKDELAY 0x18000000 +#define BAFEWATCHDOGENABLE 0x20000000 + +#define BXTALCAP01 0xc0000000 +#define BXTALCAP23 0x3 +#define BXTALCAP92X 0x0f000000 +#define BXTALCAP 0x0f000000 + +#define BINTDIFCLKENABLE 0x400 +#define BEXTSIGCLKENABLE 0x800 +#define BBANDGAP_MBIAS_POWERUP 0x10000 +#define BAD11SH_GAIN 0xc0000 +#define BAD11NPUT_RANGE 0x700000 +#define BAD110P_CURRENT 0x3800000 +#define BLPATH_LOOPBACK 0x4000000 +#define BQPATH_LOOPBACK 0x8000000 +#define BAFE_LOOPBACK 0x10000000 +#define BDA10_SWING 0x7e0 +#define BDA10_REVERSE 0x800 +#define BDA_CLK_SOURCE 0x1000 +#define BDA7INPUT_RANGE 0x6000 +#define BDA7_GAIN 0x38000 +#define BDA7OUTPUT_CM_MODE 0x40000 +#define BDA7INPUT_CM_MODE 0x380000 +#define BDA7CURRENT 0xc00000 +#define BREGULATOR_ADJUST 0x7000000 +#define BAD11POWERUP_ATTX 0x1 +#define BDA10PS_ATTX 0x10 +#define BAD11POWERUP_ATRX 0x100 +#define BDA10PS_ATRX 0x1000 +#define BCCKRX_AGC_FORMAT 0x200 +#define BPSDFFT_SAMPLE_POINT 0xc000 +#define BPSD_AVERAGE_NUM 0x3000 +#define BIQPATH_CONTROL 0xc00 +#define BPSD_FREQ 0x3ff +#define BPSD_ANTENNA_PATH 0x30 +#define BPSD_IQ_SWITCH 0x40 +#define BPSD_RX_TRIGGER 0x400000 +#define BPSD_TX_TRIGGER 0x80000000 +#define BPSD_SINE_TONE_SCALE 0x7f000000 +#define BPSD_REPORT 0xffff + +#define BOFDM_TXSC 0x30000000 +#define BCCK_TXON 0x1 +#define BOFDM_TXON 0x2 +#define BDEBUG_PAGE 0xfff +#define BDEBUG_ITEM 0xff +#define BANTL 0x10 +#define BANT_NONHT 0x100 +#define BANT_HT1 0x1000 +#define BANT_HT2 0x10000 +#define BANT_HT1S1 0x100000 +#define BANT_NONHTS1 0x1000000 + +#define BCCK_BBMODE 0x3 +#define BCCK_TXPOWERSAVING 0x80 +#define BCCK_RXPOWERSAVING 0x40 + +#define BCCK_SIDEBAND 0x10 + +#define BCCK_SCRAMBLE 0x8 +#define BCCK_ANTDIVERSITY 0x8000 +#define BCCK_CARRIER_RECOVERY 0x4000 +#define BCCK_TXRATE 0x3000 +#define BCCK_DCCANCEL 0x0800 +#define BCCK_ISICANCEL 0x0400 +#define BCCK_MATCH_FILTER 0x0200 +#define BCCK_EQUALIZER 0x0100 +#define BCCK_PREAMBLE_DETECT 0x800000 +#define BCCK_FAST_FALSECCA 0x400000 +#define BCCK_CH_ESTSTART 0x300000 +#define BCCK_CCA_COUNT 0x080000 +#define BCCK_CS_LIM 0x070000 +#define BCCK_BIST_MODE 0x80000000 +#define BCCK_CCAMASK 0x40000000 +#define BCCK_TX_DAC_PHASE 0x4 +#define BCCK_RX_ADC_PHASE 0x20000000 +#define BCCKR_CP_MODE 0x0100 +#define BCCK_TXDC_OFFSET 0xf0 +#define BCCK_RXDC_OFFSET 0xf +#define BCCK_CCA_MODE 0xc000 +#define BCCK_FALSECS_LIM 0x3f00 +#define BCCK_CS_RATIO 0xc00000 +#define BCCK_CORGBIT_SEL 0x300000 +#define BCCK_PD_LIM 0x0f0000 +#define BCCK_NEWCCA 0x80000000 +#define BCCK_RXHP_OF_IG 0x8000 +#define BCCK_RXIG 0x7f00 +#define BCCK_LNA_POLARITY 0x800000 +#define BCCK_RX1ST_BAIN 0x7f0000 +#define BCCK_RF_EXTEND 0x20000000 +#define BCCK_RXAGC_SATLEVEL 0x1f000000 +#define BCCK_RXAGC_SATCOUNT 0xe0 +#define bCCKRxRFSettle 0x1f +#define BCCK_FIXED_RXAGC 0x8000 +#define BCCK_ANTENNA_POLARITY 0x2000 +#define BCCK_TXFILTER_TYPE 0x0c00 +#define BCCK_RXAGC_REPORTTYPE 0x0300 +#define BCCK_RXDAGC_EN 0x80000000 +#define BCCK_RXDAGC_PERIOD 0x20000000 +#define BCCK_RXDAGC_SATLEVEL 0x1f000000 +#define BCCK_TIMING_RECOVERY 0x800000 +#define BCCK_TXC0 0x3f0000 +#define BCCK_TXC1 0x3f000000 +#define BCCK_TXC2 0x3f +#define BCCK_TXC3 0x3f00 +#define BCCK_TXC4 0x3f0000 +#define BCCK_TXC5 0x3f000000 +#define BCCK_TXC6 0x3f +#define BCCK_TXC7 0x3f00 +#define BCCK_DEBUGPORT 0xff0000 +#define BCCK_DAC_DEBUG 0x0f000000 +#define BCCK_FALSEALARM_ENABLE 0x8000 +#define BCCK_FALSEALARM_READ 0x4000 +#define BCCK_TRSSI 0x7f +#define BCCK_RXAGC_REPORT 0xfe +#define BCCK_RXREPORT_ANTSEL 0x80000000 +#define BCCK_RXREPORT_MFOFF 0x40000000 +#define BCCK_RXREPORT_SQLOSS 0x20000000 +#define BCCK_RXREPORT_PKTLOSS 0x10000000 +#define BCCK_RXREPORT_LOCKEDBIT 0x08000000 +#define BCCK_RXREPORT_RATEERROR 0x04000000 +#define BCCK_RXREPORT_RXRATE 0x03000000 +#define BCCK_RXFA_COUNTER_LOWER 0xff +#define BCCK_RXFA_COUNTER_UPPER 0xff000000 +#define BCCK_RXHPAGC_START 0xe000 +#define BCCK_RXHPAGC_FINAL 0x1c00 +#define BCCK_RXFALSEALARM_ENABLE 0x8000 +#define BCCK_FACOUNTER_FREEZE 0x4000 +#define BCCK_TXPATH_SEL 0x10000000 +#define BCCK_DEFAULT_RXPATH 0xc000000 +#define BCCK_OPTION_RXPATH 0x3000000 + +#define BNUM_OFSTF 0x3 +#define BSHIFT_L 0xc0 +#define BGI_TH 0xc +#define BRXPATH_A 0x1 +#define BRXPATH_B 0x2 +#define BRXPATH_C 0x4 +#define BRXPATH_D 0x8 +#define BTXPATH_A 0x1 +#define BTXPATH_B 0x2 +#define BTXPATH_C 0x4 +#define BTXPATH_D 0x8 +#define BTRSSI_FREQ 0x200 +#define BADC_BACKOFF 0x3000 +#define BDFIR_BACKOFF 0xc000 +#define BTRSSI_LATCH_PHASE 0x10000 +#define BRX_LDC_OFFSET 0xff +#define BRX_QDC_OFFSET 0xff00 +#define BRX_DFIR_MODE 0x1800000 +#define BRX_DCNF_TYPE 0xe000000 +#define BRXIQIMB_A 0x3ff +#define BRXIQIMB_B 0xfc00 +#define BRXIQIMB_C 0x3f0000 +#define BRXIQIMB_D 0xffc00000 +#define BDC_DC_NOTCH 0x60000 +#define BRXNB_NOTCH 0x1f000000 +#define BPD_TH 0xf +#define BPD_TH_OPT2 0xc000 +#define BPWED_TH 0x700 +#define BIFMF_WIN_L 0x800 +#define BPD_OPTION 0x1000 +#define BMF_WIN_L 0xe000 +#define BBW_SEARCH_L 0x30000 +#define BWIN_ENH_L 0xc0000 +#define BBW_TH 0x700000 +#define BED_TH2 0x3800000 +#define BBW_OPTION 0x4000000 +#define BRADIO_TH 0x18000000 +#define BWINDOW_L 0xe0000000 +#define BSBD_OPTION 0x1 +#define BFRAME_TH 0x1c +#define BFS_OPTION 0x60 +#define BDC_SLOPE_CHECK 0x80 +#define BFGUARD_COUNTER_DC_L 0xe00 +#define BFRAME_WEIGHT_SHORT 0x7000 +#define BSUB_TUNE 0xe00000 +#define BFRAME_DC_LENGTH 0xe000000 +#define BSBD_START_OFFSET 0x30000000 +#define BFRAME_TH_2 0x7 +#define BFRAME_GI2_TH 0x38 +#define BGI2_SYNC_EN 0x40 +#define BSARCH_SHORT_EARLY 0x300 +#define BSARCH_SHORT_LATE 0xc00 +#define BSARCH_GI2_LATE 0x70000 +#define BCFOANTSUM 0x1 +#define BCFOACC 0x2 +#define BCFOSTARTOFFSET 0xc +#define BCFOLOOPBACK 0x70 +#define BCFOSUMWEIGHT 0x80 +#define BDAGCENABLE 0x10000 +#define BTXIQIMB_A 0x3ff +#define BTXIQIMB_b 0xfc00 +#define BTXIQIMB_C 0x3f0000 +#define BTXIQIMB_D 0xffc00000 +#define BTXIDCOFFSET 0xff +#define BTXIQDCOFFSET 0xff00 +#define BTXDFIRMODE 0x10000 +#define BTXPESUDO_NOISEON 0x4000000 +#define BTXPESUDO_NOISE_A 0xff +#define BTXPESUDO_NOISE_B 0xff00 +#define BTXPESUDO_NOISE_C 0xff0000 +#define BTXPESUDO_NOISE_D 0xff000000 +#define BCCA_DROPOPTION 0x20000 +#define BCCA_DROPTHRES 0xfff00000 +#define BEDCCA_H 0xf +#define BEDCCA_L 0xf0 +#define BLAMBDA_ED 0x300 +#define BRX_INITIALGAIN 0x7f +#define BRX_ANTDIV_EN 0x80 +#define BRX_AGC_ADDRESS_FOR_LNA 0x7f00 +#define BRX_HIGHPOWER_FLOW 0x8000 +#define BRX_AGC_FREEZE_THRES 0xc0000 +#define BRX_FREEZESTEP_AGC1 0x300000 +#define BRX_FREEZESTEP_AGC2 0xc00000 +#define BRX_FREEZESTEP_AGC3 0x3000000 +#define BRX_FREEZESTEP_AGC0 0xc000000 +#define BRXRSSI_CMP_EN 0x10000000 +#define BRXQUICK_AGCEN 0x20000000 +#define BRXAGC_FREEZE_THRES_MODE 0x40000000 +#define BRX_OVERFLOW_CHECKTYPE 0x80000000 +#define BRX_AGCSHIFT 0x7f +#define BTRSW_TRI_ONLY 0x80 +#define BPOWER_THRES 0x300 +#define BRXAGC_EN 0x1 +#define BRXAGC_TOGETHER_EN 0x2 +#define BRXAGC_MIN 0x4 +#define BRXHP_INI 0x7 +#define BRXHP_TRLNA 0x70 +#define BRXHP_RSSI 0x700 +#define BRXHP_BBP1 0x7000 +#define BRXHP_BBP2 0x70000 +#define BRXHP_BBP3 0x700000 +#define BRSSI_H 0x7f0000 +#define BRSSI_GEN 0x7f000000 +#define BRXSETTLE_TRSW 0x7 +#define BRXSETTLE_LNA 0x38 +#define BRXSETTLE_RSSI 0x1c0 +#define BRXSETTLE_BBP 0xe00 +#define BRXSETTLE_RXHP 0x7000 +#define BRXSETTLE_ANTSW_RSSI 0x38000 +#define BRXSETTLE_ANTSW 0xc0000 +#define BRXPROCESS_TIME_DAGC 0x300000 +#define BRXSETTLE_HSSI 0x400000 +#define BRXPROCESS_TIME_BBPPW 0x800000 +#define BRXANTENNA_POWER_SHIFT 0x3000000 +#define BRSSI_TABLE_SELECT 0xc000000 +#define BRXHP_FINAL 0x7000000 +#define BRXHPSETTLE_BBP 0x7 +#define BRXHTSETTLE_HSSI 0x8 +#define BRXHTSETTLE_RXHP 0x70 +#define BRXHTSETTLE_BBPPW 0x80 +#define BRXHTSETTLE_IDLE 0x300 +#define BRXHTSETTLE_RESERVED 0x1c00 +#define BRXHT_RXHP_EN 0x8000 +#define BRXAGC_FREEZE_THRES 0x30000 +#define BRXAGC_TOGETHEREN 0x40000 +#define BRXHTAGC_MIN 0x80000 +#define BRXHTAGC_EN 0x100000 +#define BRXHTDAGC_EN 0x200000 +#define BRXHT_RXHP_BBP 0x1c00000 +#define BRXHT_RXHP_FINAL 0xe0000000 +#define BRXPW_RADIO_TH 0x3 +#define BRXPW_RADIO_EN 0x4 +#define BRXMF_HOLD 0x3800 +#define BRXPD_DELAY_TH1 0x38 +#define BRXPD_DELAY_TH2 0x1c0 +#define BRXPD_DC_COUNT_MAX 0x600 +#define BRXPD_DELAY_TH 0x8000 +#define BRXPROCESS_DELAY 0xf0000 +#define BRXSEARCHRANGE_GI2_EARLY 0x700000 +#define BRXFRAME_FUARD_COUNTER_L 0x3800000 +#define BRXSGI_GUARD_L 0xc000000 +#define BRXSGI_SEARCH_L 0x30000000 +#define BRXSGI_TH 0xc0000000 +#define BDFSCNT0 0xff +#define BDFSCNT1 0xff00 +#define BDFSFLAG 0xf0000 +#define BMF_WEIGHT_SUM 0x300000 +#define BMINIDX_TH 0x7f000000 +#define BDAFORMAT 0x40000 +#define BTXCH_EMU_ENABLE 0x01000000 +#define BTRSW_ISOLATION_A 0x7f +#define BTRSW_ISOLATION_B 0x7f00 +#define BTRSW_ISOLATION_C 0x7f0000 +#define BTRSW_ISOLATION_D 0x7f000000 +#define BEXT_LNA_GAIN 0x7c00 + +#define BSTBC_EN 0x4 +#define BANTENNA_MAPPING 0x10 +#define BNSS 0x20 +#define BCFO_ANTSUM_ID 0x200 +#define BPHY_COUNTER_RESET 0x8000000 +#define BCFO_REPORT_GET 0x4000000 +#define BOFDM_CONTINUE_TX 0x10000000 +#define BOFDM_SINGLE_CARRIER 0x20000000 +#define BOFDM_SINGLE_TONE 0x40000000 +#define BHT_DETECT 0x100 +#define BCFOEN 0x10000 +#define BCFOVALUE 0xfff00000 +#define BSIGTONE_RE 0x3f +#define BSIGTONE_IM 0x7f00 +#define BCOUNTER_CCA 0xffff +#define BCOUNTER_PARITYFAIL 0xffff0000 +#define BCOUNTER_RATEILLEGAL 0xffff +#define BCOUNTER_CRC8FAIL 0xffff0000 +#define BCOUNTER_MCSNOSUPPORT 0xffff +#define BCOUNTER_FASTSYNC 0xffff +#define BSHORTCFO 0xfff +#define BSHORTCFOT_LENGTH 12 +#define BSHORTCFOF_LENGTH 11 +#define BLONGCFO 0x7ff +#define BLONGCFOT_LENGTH 11 +#define BLONGCFOF_LENGTH 11 +#define BTAILCFO 0x1fff +#define BTAILCFOT_LENGTH 13 +#define BTAILCFOF_LENGTH 12 +#define BNOISE_EN_PWDB 0xffff +#define BCC_POWER_DB 0xffff0000 +#define BMOISE_PWDB 0xffff +#define BPOWERMEAST_LENGTH 10 +#define BPOWERMEASF_LENGTH 3 +#define BRX_HT_BW 0x1 +#define BRXSC 0x6 +#define BRX_HT 0x8 +#define BNB_INTF_DET_ON 0x1 +#define BINTF_WIN_LEN_CFG 0x30 +#define BNB_INTF_TH_CFG 0x1c0 +#define BRFGAIN 0x3f +#define BTABLESEL 0x40 +#define BTRSW 0x80 +#define BRXSNR_A 0xff +#define BRXSNR_B 0xff00 +#define BRXSNR_C 0xff0000 +#define BRXSNR_D 0xff000000 +#define BSNR_EVMT_LENGTH 8 +#define BSNR_EVMF_LENGTH 1 +#define BCSI1ST 0xff +#define BCSI2ND 0xff00 +#define BRXEVM1ST 0xff0000 +#define BRXEVM2ND 0xff000000 +#define BSIGEVM 0xff +#define BPWDB 0xff00 +#define BSGIEN 0x10000 + +#define BSFACTOR_QMA1 0xf +#define BSFACTOR_QMA2 0xf0 +#define BSFACTOR_QMA3 0xf00 +#define BSFACTOR_QMA4 0xf000 +#define BSFACTOR_QMA5 0xf0000 +#define BSFACTOR_QMA6 0xf0000 +#define BSFACTOR_QMA7 0xf00000 +#define BSFACTOR_QMA8 0xf000000 +#define BSFACTOR_QMA9 0xf0000000 +#define BCSI_SCHEME 0x100000 + +#define BNOISE_LVL_TOP_SET 0x3 +#define BCHSMOOTH 0x4 +#define BCHSMOOTH_CFG1 0x38 +#define BCHSMOOTH_CFG2 0x1c0 +#define BCHSMOOTH_CFG3 0xe00 +#define BCHSMOOTH_CFG4 0x7000 +#define BMRCMODE 0x800000 +#define BTHEVMCFG 0x7000000 + +#define BLOOP_FIT_TYPE 0x1 +#define BUPD_CFO 0x40 +#define BUPD_CFO_OFFDATA 0x80 +#define BADV_UPD_CFO 0x100 +#define BADV_TIME_CTRL 0x800 +#define BUPD_CLKO 0x1000 +#define BFC 0x6000 +#define BTRACKING_MODE 0x8000 +#define BPHCMP_ENABLE 0x10000 +#define BUPD_CLKO_LTF 0x20000 +#define BCOM_CH_CFO 0x40000 +#define BCSI_ESTI_MODE 0x80000 +#define BADV_UPD_EQZ 0x100000 +#define BUCHCFG 0x7000000 +#define BUPDEQZ 0x8000000 + +#define BRX_PESUDO_NOISE_ON 0x20000000 +#define BRX_PESUDO_NOISE_A 0xff +#define BRX_PESUDO_NOISE_B 0xff00 +#define BRX_PESUDO_NOISE_C 0xff0000 +#define BRX_PESUDO_NOISE_D 0xff000000 +#define BRX_PESUDO_NOISESTATE_A 0xffff +#define BRX_PESUDO_NOISESTATE_B 0xffff0000 +#define BRX_PESUDO_NOISESTATE_C 0xffff +#define BRX_PESUDO_NOISESTATE_D 0xffff0000 + +#define BZEBRA1_HSSIENABLE 0x8 +#define BZEBRA1_TRXCONTROL 0xc00 +#define BZEBRA1_TRXGAINSETTING 0x07f +#define BZEBRA1_RXCOUNTER 0xc00 +#define BZEBRA1_TXCHANGEPUMP 0x38 +#define BZEBRA1_RXCHANGEPUMP 0x7 +#define BZEBRA1_CHANNEL_NUM 0xf80 +#define BZEBRA1_TXLPFBW 0x400 +#define BZEBRA1_RXLPFBW 0x600 + +#define BRTL8256REG_MODE_CTRL1 0x100 +#define BRTL8256REG_MODE_CTRL0 0x40 +#define BRTL8256REG_TXLPFBW 0x18 +#define BRTL8256REG_RXLPFBW 0x600 + +#define BRTL8258_TXLPFBW 0xc +#define BRTL8258_RXLPFBW 0xc00 +#define BRTL8258_RSSILPFBW 0xc0 + +#define BBYTE0 0x1 +#define BBYTE1 0x2 +#define BBYTE2 0x4 +#define BBYTE3 0x8 +#define BWORD0 0x3 +#define BWORD1 0xc +#define BWORD 0xf + +#define MASKBYTE0 0xff +#define MASKBYTE1 0xff00 +#define MASKBYTE2 0xff0000 +#define MASKBYTE3 0xff000000 +#define MASKHWORD 0xffff0000 +#define MASKLWORD 0x0000ffff +#define MASKDWORD 0xffffffff +#define MASK12BITS 0xfff +#define MASKH4BITS 0xf0000000 +#define MASKOFDM_D 0xffc00000 +#define MASKCCK 0x3f3f3f3f + +#define MASK4BITS 0x0f +#define MASK20BITS 0xfffff +#define RFREG_OFFSET_MASK 0xfffff + +#define BENABLE 0x1 +#define BDISABLE 0x0 + +#define LEFT_ANTENNA 0x0 +#define RIGHT_ANTENNA 0x1 + +#define TCHECK_TXSTATUS 500 +#define TUPDATE_RXCOUNTER 100 + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/rf.c b/drivers/net/wireless/rtlwifi/rtl8192ce/rf.c new file mode 100644 index 0000000..a9c406f --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/rf.c @@ -0,0 +1,509 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "rf.h" +#include "dm.h" + +static bool _rtl92ce_phy_rf6052_config_parafile(struct ieee80211_hw *hw); + +void rtl92ce_phy_rf6052_set_bandwidth(struct ieee80211_hw *hw, u8 bandwidth) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + switch (bandwidth) { + case HT_CHANNEL_WIDTH_20: + rtlphy->rfreg_chnlval[0] = ((rtlphy->rfreg_chnlval[0] & + 0xfffff3ff) | 0x0400); + rtl_set_rfreg(hw, RF90_PATH_A, RF_CHNLBW, RFREG_OFFSET_MASK, + rtlphy->rfreg_chnlval[0]); + break; + case HT_CHANNEL_WIDTH_20_40: + rtlphy->rfreg_chnlval[0] = ((rtlphy->rfreg_chnlval[0] & + 0xfffff3ff)); + rtl_set_rfreg(hw, RF90_PATH_A, RF_CHNLBW, RFREG_OFFSET_MASK, + rtlphy->rfreg_chnlval[0]); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", bandwidth); + break; + } +} + +void rtl92ce_phy_rf6052_set_cck_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u32 tx_agc[2] = {0, 0}, tmpval; + bool turbo_scanoff = false; + u8 idx1, idx2; + u8 *ptr; + + if (rtlefuse->eeprom_regulatory != 0) + turbo_scanoff = true; + + if (mac->act_scanning) { + tx_agc[RF90_PATH_A] = 0x3f3f3f3f; + tx_agc[RF90_PATH_B] = 0x3f3f3f3f; + + if (turbo_scanoff) { + for (idx1 = RF90_PATH_A; idx1 <= RF90_PATH_B; idx1++) { + tx_agc[idx1] = ppowerlevel[idx1] | + (ppowerlevel[idx1] << 8) | + (ppowerlevel[idx1] << 16) | + (ppowerlevel[idx1] << 24); + } + } + } else { + for (idx1 = RF90_PATH_A; idx1 <= RF90_PATH_B; idx1++) { + tx_agc[idx1] = ppowerlevel[idx1] | + (ppowerlevel[idx1] << 8) | + (ppowerlevel[idx1] << 16) | + (ppowerlevel[idx1] << 24); + } + + if (rtlefuse->eeprom_regulatory == 0) { + tmpval = (rtlphy->mcs_offset[0][6]) + + (rtlphy->mcs_offset[0][7] << 8); + tx_agc[RF90_PATH_A] += tmpval; + + tmpval = (rtlphy->mcs_offset[0][14]) + + (rtlphy->mcs_offset[0][15] << 24); + tx_agc[RF90_PATH_B] += tmpval; + } + } + + for (idx1 = RF90_PATH_A; idx1 <= RF90_PATH_B; idx1++) { + ptr = (u8 *) (&(tx_agc[idx1])); + for (idx2 = 0; idx2 < 4; idx2++) { + if (*ptr > RF6052_MAX_TX_PWR) + *ptr = RF6052_MAX_TX_PWR; + ptr++; + } + } + + tmpval = tx_agc[RF90_PATH_A] & 0xff; + rtl_set_bbreg(hw, RTXAGC_A_CCK1_MCS32, MASKBYTE1, tmpval); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 1M (rf-A) = 0x%x (reg 0x%x)\n", + tmpval, RTXAGC_A_CCK1_MCS32); + + tmpval = tx_agc[RF90_PATH_A] >> 8; + + tmpval = tmpval & 0xff00ffff; + + rtl_set_bbreg(hw, RTXAGC_B_CCK11_A_CCK2_11, 0xffffff00, tmpval); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 2~11M (rf-A) = 0x%x (reg 0x%x)\n", + tmpval, RTXAGC_B_CCK11_A_CCK2_11); + + tmpval = tx_agc[RF90_PATH_B] >> 24; + rtl_set_bbreg(hw, RTXAGC_B_CCK11_A_CCK2_11, MASKBYTE0, tmpval); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 11M (rf-B) = 0x%x (reg 0x%x)\n", + tmpval, RTXAGC_B_CCK11_A_CCK2_11); + + tmpval = tx_agc[RF90_PATH_B] & 0x00ffffff; + rtl_set_bbreg(hw, RTXAGC_B_CCK1_55_MCS32, 0xffffff00, tmpval); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 1~5.5M (rf-B) = 0x%x (reg 0x%x)\n", + tmpval, RTXAGC_B_CCK1_55_MCS32); +} + +static void rtl92c_phy_get_power_base(struct ieee80211_hw *hw, + u8 *ppowerlevel, u8 channel, + u32 *ofdmbase, u32 *mcsbase) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u32 powerBase0, powerBase1; + u8 legacy_pwrdiff, ht20_pwrdiff; + u8 i, powerlevel[2]; + + for (i = 0; i < 2; i++) { + powerlevel[i] = ppowerlevel[i]; + legacy_pwrdiff = rtlefuse->txpwr_legacyhtdiff[i][channel - 1]; + powerBase0 = powerlevel[i] + legacy_pwrdiff; + + powerBase0 = (powerBase0 << 24) | (powerBase0 << 16) | + (powerBase0 << 8) | powerBase0; + *(ofdmbase + i) = powerBase0; + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + " [OFDM power base index rf(%c) = 0x%x]\n", + i == 0 ? 'A' : 'B', *(ofdmbase + i)); + } + + for (i = 0; i < 2; i++) { + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20) { + ht20_pwrdiff = rtlefuse->txpwr_ht20diff[i][channel - 1]; + powerlevel[i] += ht20_pwrdiff; + } + powerBase1 = powerlevel[i]; + powerBase1 = (powerBase1 << 24) | + (powerBase1 << 16) | (powerBase1 << 8) | powerBase1; + + *(mcsbase + i) = powerBase1; + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + " [MCS power base index rf(%c) = 0x%x]\n", + i == 0 ? 'A' : 'B', *(mcsbase + i)); + } +} + +static void _rtl92c_get_txpower_writeval_by_regulatory(struct ieee80211_hw *hw, + u8 channel, u8 index, + u32 *powerBase0, + u32 *powerBase1, + u32 *p_outwriteval) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 i, chnlgroup = 0, pwr_diff_limit[4]; + u32 writeVal, customer_limit, rf; + + for (rf = 0; rf < 2; rf++) { + switch (rtlefuse->eeprom_regulatory) { + case 0: + chnlgroup = 0; + + writeVal = rtlphy->mcs_offset[chnlgroup][index + + (rf ? 8 : 0)] + + ((index < 2) ? powerBase0[rf] : powerBase1[rf]); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "RTK better performance, writeVal(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', writeVal); + break; + case 1: + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20_40) { + writeVal = ((index < 2) ? powerBase0[rf] : + powerBase1[rf]); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Realtek regulatory, 40MHz, writeVal(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', writeVal); + } else { + if (rtlphy->pwrgroup_cnt == 1) + chnlgroup = 0; + if (rtlphy->pwrgroup_cnt >= 3) { + if (channel <= 3) + chnlgroup = 0; + else if (channel >= 4 && channel <= 9) + chnlgroup = 1; + else if (channel > 9) + chnlgroup = 2; + if (rtlphy->pwrgroup_cnt == 4) + chnlgroup++; + } + + writeVal = rtlphy->mcs_offset[chnlgroup] + [index + (rf ? 8 : 0)] + ((index < 2) ? + powerBase0[rf] : + powerBase1[rf]); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Realtek regulatory, 20MHz, writeVal(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', writeVal); + } + break; + case 2: + writeVal = + ((index < 2) ? powerBase0[rf] : powerBase1[rf]); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Better regulatory, writeVal(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', writeVal); + break; + case 3: + chnlgroup = 0; + + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20_40) { + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "customer's limit, 40MHz rf(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', + rtlefuse->pwrgroup_ht40[rf][channel - + 1]); + } else { + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "customer's limit, 20MHz rf(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', + rtlefuse->pwrgroup_ht20[rf][channel - + 1]); + } + for (i = 0; i < 4; i++) { + pwr_diff_limit[i] = (u8) ((rtlphy->mcs_offset + [chnlgroup][index + + (rf ? 8 : 0)] & (0x7f << (i * 8))) >> + (i * 8)); + + if (rtlphy->current_chan_bw == + HT_CHANNEL_WIDTH_20_40) { + if (pwr_diff_limit[i] > + rtlefuse-> + pwrgroup_ht40[rf][channel - 1]) + pwr_diff_limit[i] = + rtlefuse->pwrgroup_ht40[rf] + [channel - 1]; + } else { + if (pwr_diff_limit[i] > + rtlefuse-> + pwrgroup_ht20[rf][channel - 1]) + pwr_diff_limit[i] = + rtlefuse->pwrgroup_ht20[rf] + [channel - 1]; + } + } + + customer_limit = (pwr_diff_limit[3] << 24) | + (pwr_diff_limit[2] << 16) | + (pwr_diff_limit[1] << 8) | (pwr_diff_limit[0]); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Customer's limit rf(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', customer_limit); + + writeVal = customer_limit + + ((index < 2) ? powerBase0[rf] : powerBase1[rf]); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Customer, writeVal rf(%c)= 0x%x\n", + rf == 0 ? 'A' : 'B', writeVal); + break; + default: + chnlgroup = 0; + writeVal = rtlphy->mcs_offset[chnlgroup] + [index + (rf ? 8 : 0)] + + ((index < 2) ? powerBase0[rf] : powerBase1[rf]); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "RTK better performance, writeVal rf(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', writeVal); + break; + } + + if (rtlpriv->dm.dynamic_txhighpower_lvl == TXHIGHPWRLEVEL_BT1) + writeVal = writeVal - 0x06060606; + else if (rtlpriv->dm.dynamic_txhighpower_lvl == + TXHIGHPWRLEVEL_BT2) + writeVal = writeVal - 0x0c0c0c0c; + *(p_outwriteval + rf) = writeVal; + } +} + +static void _rtl92c_write_ofdm_power_reg(struct ieee80211_hw *hw, + u8 index, u32 *pValue) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + u16 regoffset_a[6] = { + RTXAGC_A_RATE18_06, RTXAGC_A_RATE54_24, + RTXAGC_A_MCS03_MCS00, RTXAGC_A_MCS07_MCS04, + RTXAGC_A_MCS11_MCS08, RTXAGC_A_MCS15_MCS12 + }; + u16 regoffset_b[6] = { + RTXAGC_B_RATE18_06, RTXAGC_B_RATE54_24, + RTXAGC_B_MCS03_MCS00, RTXAGC_B_MCS07_MCS04, + RTXAGC_B_MCS11_MCS08, RTXAGC_B_MCS15_MCS12 + }; + u8 i, rf, pwr_val[4]; + u32 writeVal; + u16 regoffset; + + for (rf = 0; rf < 2; rf++) { + writeVal = pValue[rf]; + for (i = 0; i < 4; i++) { + pwr_val[i] = (u8) ((writeVal & (0x7f << + (i * 8))) >> (i * 8)); + + if (pwr_val[i] > RF6052_MAX_TX_PWR) + pwr_val[i] = RF6052_MAX_TX_PWR; + } + writeVal = (pwr_val[3] << 24) | (pwr_val[2] << 16) | + (pwr_val[1] << 8) | pwr_val[0]; + + if (rf == 0) + regoffset = regoffset_a[index]; + else + regoffset = regoffset_b[index]; + rtl_set_bbreg(hw, regoffset, MASKDWORD, writeVal); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Set 0x%x = %08x\n", regoffset, writeVal); + + if (((get_rf_type(rtlphy) == RF_2T2R) && + (regoffset == RTXAGC_A_MCS15_MCS12 || + regoffset == RTXAGC_B_MCS15_MCS12)) || + ((get_rf_type(rtlphy) != RF_2T2R) && + (regoffset == RTXAGC_A_MCS07_MCS04 || + regoffset == RTXAGC_B_MCS07_MCS04))) { + + writeVal = pwr_val[3]; + if (regoffset == RTXAGC_A_MCS15_MCS12 || + regoffset == RTXAGC_A_MCS07_MCS04) + regoffset = 0xc90; + if (regoffset == RTXAGC_B_MCS15_MCS12 || + regoffset == RTXAGC_B_MCS07_MCS04) + regoffset = 0xc98; + + for (i = 0; i < 3; i++) { + writeVal = (writeVal > 6) ? (writeVal - 6) : 0; + rtl_write_byte(rtlpriv, (u32) (regoffset + i), + (u8) writeVal); + } + } + } +} + +void rtl92ce_phy_rf6052_set_ofdm_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel, u8 channel) +{ + u32 writeVal[2], powerBase0[2], powerBase1[2]; + u8 index; + + rtl92c_phy_get_power_base(hw, ppowerlevel, + channel, &powerBase0[0], &powerBase1[0]); + + for (index = 0; index < 6; index++) { + _rtl92c_get_txpower_writeval_by_regulatory(hw, + channel, index, + &powerBase0[0], + &powerBase1[0], + &writeVal[0]); + + _rtl92c_write_ofdm_power_reg(hw, index, &writeVal[0]); + } +} + +bool rtl92ce_phy_rf6052_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + if (rtlphy->rf_type == RF_1T1R) + rtlphy->num_total_rfpath = 1; + else + rtlphy->num_total_rfpath = 2; + + return _rtl92ce_phy_rf6052_config_parafile(hw); + +} + +static bool _rtl92ce_phy_rf6052_config_parafile(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 u4_regvalue = 0; + u8 rfpath; + bool rtstatus = true; + struct bb_reg_def *pphyreg; + + for (rfpath = 0; rfpath < rtlphy->num_total_rfpath; rfpath++) { + + pphyreg = &rtlphy->phyreg_def[rfpath]; + + switch (rfpath) { + case RF90_PATH_A: + case RF90_PATH_C: + u4_regvalue = rtl_get_bbreg(hw, pphyreg->rfintfs, + BRFSI_RFENV); + break; + case RF90_PATH_B: + case RF90_PATH_D: + u4_regvalue = rtl_get_bbreg(hw, pphyreg->rfintfs, + BRFSI_RFENV << 16); + break; + } + + rtl_set_bbreg(hw, pphyreg->rfintfe, BRFSI_RFENV << 16, 0x1); + udelay(1); + + rtl_set_bbreg(hw, pphyreg->rfintfo, BRFSI_RFENV, 0x1); + udelay(1); + + rtl_set_bbreg(hw, pphyreg->rfhssi_para2, + B3WIREADDREAALENGTH, 0x0); + udelay(1); + + rtl_set_bbreg(hw, pphyreg->rfhssi_para2, B3WIREDATALENGTH, 0x0); + udelay(1); + + switch (rfpath) { + case RF90_PATH_A: + rtstatus = rtl92c_phy_config_rf_with_headerfile(hw, + (enum radio_path)rfpath); + break; + case RF90_PATH_B: + rtstatus = rtl92c_phy_config_rf_with_headerfile(hw, + (enum radio_path)rfpath); + break; + case RF90_PATH_C: + break; + case RF90_PATH_D: + break; + } + + switch (rfpath) { + case RF90_PATH_A: + case RF90_PATH_C: + rtl_set_bbreg(hw, pphyreg->rfintfs, + BRFSI_RFENV, u4_regvalue); + break; + case RF90_PATH_B: + case RF90_PATH_D: + rtl_set_bbreg(hw, pphyreg->rfintfs, + BRFSI_RFENV << 16, u4_regvalue); + break; + } + + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Radio[%d] Fail!!\n", rfpath); + return false; + } + + } + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "<---\n"); + return rtstatus; +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/rf.h b/drivers/net/wireless/rtlwifi/rtl8192ce/rf.h new file mode 100644 index 0000000..6c8d56e --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/rf.h @@ -0,0 +1,44 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92C_RF_H__ +#define __RTL92C_RF_H__ + +#define RF6052_MAX_TX_PWR 0x3F +#define RF6052_MAX_REG 0x3F +#define RF6052_MAX_PATH 2 + +extern void rtl92ce_phy_rf6052_set_bandwidth(struct ieee80211_hw *hw, + u8 bandwidth); +extern void rtl92ce_phy_rf6052_set_cck_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel); +extern void rtl92ce_phy_rf6052_set_ofdm_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel, u8 channel); +extern bool rtl92ce_phy_rf6052_config(struct ieee80211_hw *hw); +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/sw.c b/drivers/net/wireless/rtlwifi/rtl8192ce/sw.c new file mode 100644 index 0000000..7ffed3e --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/sw.c @@ -0,0 +1,395 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../core.h" +#include "../pci.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "dm.h" +#include "hw.h" +#include "rf.h" +#include "sw.h" +#include "trx.h" +#include "led.h" + +#include + +static void rtl92c_init_aspm_vars(struct ieee80211_hw *hw) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + /*close ASPM for AMD defaultly */ + rtlpci->const_amdpci_aspm = 0; + + /* + * ASPM PS mode. + * 0 - Disable ASPM, + * 1 - Enable ASPM without Clock Req, + * 2 - Enable ASPM with Clock Req, + * 3 - Alwyas Enable ASPM with Clock Req, + * 4 - Always Enable ASPM without Clock Req. + * set defult to RTL8192CE:3 RTL8192E:2 + * */ + rtlpci->const_pci_aspm = 3; + + /*Setting for PCI-E device */ + rtlpci->const_devicepci_aspm_setting = 0x03; + + /*Setting for PCI-E bridge */ + rtlpci->const_hostpci_aspm_setting = 0x02; + + /* + * In Hw/Sw Radio Off situation. + * 0 - Default, + * 1 - From ASPM setting without low Mac Pwr, + * 2 - From ASPM setting with low Mac Pwr, + * 3 - Bus D3 + * set default to RTL8192CE:0 RTL8192SE:2 + */ + rtlpci->const_hwsw_rfoff_d3 = 0; + + /* + * This setting works for those device with + * backdoor ASPM setting such as EPHY setting. + * 0 - Not support ASPM, + * 1 - Support ASPM, + * 2 - According to chipset. + */ + rtlpci->const_support_pciaspm = 1; +} + +int rtl92c_init_sw_vars(struct ieee80211_hw *hw) +{ + int err; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + rtl8192ce_bt_reg_init(hw); + + rtlpriv->dm.dm_initialgain_enable = true; + rtlpriv->dm.dm_flag = 0; + rtlpriv->dm.disable_framebursting = false; + rtlpriv->dm.thermalvalue = 0; + rtlpci->transmit_config = CFENDFORM | BIT(12) | BIT(13); + + /* compatible 5G band 88ce just 2.4G band & smsp */ + rtlpriv->rtlhal.current_bandtype = BAND_ON_2_4G; + rtlpriv->rtlhal.bandset = BAND_ON_2_4G; + rtlpriv->rtlhal.macphymode = SINGLEMAC_SINGLEPHY; + + rtlpci->receive_config = (RCR_APPFCS | + RCR_AMF | + RCR_ADF | + RCR_APP_MIC | + RCR_APP_ICV | + RCR_AICV | + RCR_ACRC32 | + RCR_AB | + RCR_AM | + RCR_APM | + RCR_APP_PHYST_RXFF | RCR_HTC_LOC_CTRL | 0); + + rtlpci->irq_mask[0] = + (u32) (IMR_ROK | + IMR_VODOK | + IMR_VIDOK | + IMR_BEDOK | + IMR_BKDOK | + IMR_MGNTDOK | + IMR_HIGHDOK | IMR_BDOK | IMR_RDU | IMR_RXFOVW | 0); + + rtlpci->irq_mask[1] = (u32) (IMR_CPWM | IMR_C2HCMD | 0); + + /* for debug level */ + rtlpriv->dbg.global_debuglevel = rtlpriv->cfg->mod_params->debug; + /* for LPS & IPS */ + rtlpriv->psc.inactiveps = rtlpriv->cfg->mod_params->inactiveps; + rtlpriv->psc.swctrl_lps = rtlpriv->cfg->mod_params->swctrl_lps; + rtlpriv->psc.fwctrl_lps = rtlpriv->cfg->mod_params->fwctrl_lps; + if (!rtlpriv->psc.inactiveps) + pr_info("rtl8192ce: Power Save off (module option)\n"); + if (!rtlpriv->psc.fwctrl_lps) + pr_info("rtl8192ce: FW Power Save off (module option)\n"); + rtlpriv->psc.reg_fwctrl_lps = 3; + rtlpriv->psc.reg_max_lps_awakeintvl = 5; + /* for ASPM, you can close aspm through + * set const_support_pciaspm = 0 */ + rtl92c_init_aspm_vars(hw); + + if (rtlpriv->psc.reg_fwctrl_lps == 1) + rtlpriv->psc.fwctrl_psmode = FW_PS_MIN_MODE; + else if (rtlpriv->psc.reg_fwctrl_lps == 2) + rtlpriv->psc.fwctrl_psmode = FW_PS_MAX_MODE; + else if (rtlpriv->psc.reg_fwctrl_lps == 3) + rtlpriv->psc.fwctrl_psmode = FW_PS_DTIM_MODE; + + /* for firmware buf */ + rtlpriv->rtlhal.pfirmware = vzalloc(0x4000); + if (!rtlpriv->rtlhal.pfirmware) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Can't alloc buffer for fw\n"); + return 1; + } + + /* request fw */ + if (IS_VENDOR_UMC_A_CUT(rtlhal->version) && + !IS_92C_SERIAL(rtlhal->version)) + rtlpriv->cfg->fw_name = "rtlwifi/rtl8192cfwU.bin"; + else if (IS_81xxC_VENDOR_UMC_B_CUT(rtlhal->version)) + rtlpriv->cfg->fw_name = "rtlwifi/rtl8192cfwU_B.bin"; + + rtlpriv->max_fw_size = 0x4000; + pr_info("Using firmware %s\n", rtlpriv->cfg->fw_name); + err = request_firmware_nowait(THIS_MODULE, 1, rtlpriv->cfg->fw_name, + rtlpriv->io.dev, GFP_KERNEL, hw, + rtl_fw_cb); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Failed to request firmware!\n"); + return 1; + } + + return 0; +} + +void rtl92c_deinit_sw_vars(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (rtlpriv->rtlhal.pfirmware) { + vfree(rtlpriv->rtlhal.pfirmware); + rtlpriv->rtlhal.pfirmware = NULL; + } +} + +static struct rtl_hal_ops rtl8192ce_hal_ops = { + .init_sw_vars = rtl92c_init_sw_vars, + .deinit_sw_vars = rtl92c_deinit_sw_vars, + .read_eeprom_info = rtl92ce_read_eeprom_info, + .interrupt_recognized = rtl92ce_interrupt_recognized, + .hw_init = rtl92ce_hw_init, + .hw_disable = rtl92ce_card_disable, + .hw_suspend = rtl92ce_suspend, + .hw_resume = rtl92ce_resume, + .enable_interrupt = rtl92ce_enable_interrupt, + .disable_interrupt = rtl92ce_disable_interrupt, + .set_network_type = rtl92ce_set_network_type, + .set_chk_bssid = rtl92ce_set_check_bssid, + .set_qos = rtl92ce_set_qos, + .set_bcn_reg = rtl92ce_set_beacon_related_registers, + .set_bcn_intv = rtl92ce_set_beacon_interval, + .update_interrupt_mask = rtl92ce_update_interrupt_mask, + .get_hw_reg = rtl92ce_get_hw_reg, + .set_hw_reg = rtl92ce_set_hw_reg, + .update_rate_tbl = rtl92ce_update_hal_rate_tbl, + .fill_tx_desc = rtl92ce_tx_fill_desc, + .fill_tx_cmddesc = rtl92ce_tx_fill_cmddesc, + .query_rx_desc = rtl92ce_rx_query_desc, + .set_channel_access = rtl92ce_update_channel_access_setting, + .radio_onoff_checking = rtl92ce_gpio_radio_on_off_checking, + .set_bw_mode = rtl92c_phy_set_bw_mode, + .switch_channel = rtl92c_phy_sw_chnl, + .dm_watchdog = rtl92c_dm_watchdog, + .scan_operation_backup = rtl92c_phy_scan_operation_backup, + .set_rf_power_state = rtl92c_phy_set_rf_power_state, + .led_control = rtl92ce_led_control, + .set_desc = rtl92ce_set_desc, + .get_desc = rtl92ce_get_desc, + .tx_polling = rtl92ce_tx_polling, + .enable_hw_sec = rtl92ce_enable_hw_security_config, + .set_key = rtl92ce_set_key, + .init_sw_leds = rtl92ce_init_sw_leds, + .allow_all_destaddr = rtl92ce_allow_all_destaddr, + .get_bbreg = rtl92c_phy_query_bb_reg, + .set_bbreg = rtl92c_phy_set_bb_reg, + .set_rfreg = rtl92ce_phy_set_rf_reg, + .get_rfreg = rtl92c_phy_query_rf_reg, + .phy_rf6052_config = rtl92ce_phy_rf6052_config, + .phy_rf6052_set_cck_txpower = rtl92ce_phy_rf6052_set_cck_txpower, + .phy_rf6052_set_ofdm_txpower = rtl92ce_phy_rf6052_set_ofdm_txpower, + .config_bb_with_headerfile = _rtl92ce_phy_config_bb_with_headerfile, + .config_bb_with_pgheaderfile = _rtl92ce_phy_config_bb_with_pgheaderfile, + .phy_lc_calibrate = _rtl92ce_phy_lc_calibrate, + .phy_set_bw_mode_callback = rtl92ce_phy_set_bw_mode_callback, + .dm_dynamic_txpower = rtl92ce_dm_dynamic_txpower, +}; + +static struct rtl_mod_params rtl92ce_mod_params = { + .sw_crypto = false, + .inactiveps = true, + .swctrl_lps = false, + .fwctrl_lps = true, + .debug = DBG_EMERG, +}; + +static struct rtl_hal_cfg rtl92ce_hal_cfg = { + .bar_id = 2, + .write_readback = true, + .name = "rtl92c_pci", + .fw_name = "rtlwifi/rtl8192cfw.bin", + .ops = &rtl8192ce_hal_ops, + .mod_params = &rtl92ce_mod_params, + + .maps[SYS_ISO_CTRL] = REG_SYS_ISO_CTRL, + .maps[SYS_FUNC_EN] = REG_SYS_FUNC_EN, + .maps[SYS_CLK] = REG_SYS_CLKR, + .maps[MAC_RCR_AM] = AM, + .maps[MAC_RCR_AB] = AB, + .maps[MAC_RCR_ACRC32] = ACRC32, + .maps[MAC_RCR_ACF] = ACF, + .maps[MAC_RCR_AAP] = AAP, + + .maps[EFUSE_TEST] = REG_EFUSE_TEST, + .maps[EFUSE_CTRL] = REG_EFUSE_CTRL, + .maps[EFUSE_CLK] = 0, + .maps[EFUSE_CLK_CTRL] = REG_EFUSE_CTRL, + .maps[EFUSE_PWC_EV12V] = PWC_EV12V, + .maps[EFUSE_FEN_ELDR] = FEN_ELDR, + .maps[EFUSE_LOADER_CLK_EN] = LOADER_CLK_EN, + .maps[EFUSE_ANA8M] = EFUSE_ANA8M, + .maps[EFUSE_HWSET_MAX_SIZE] = HWSET_MAX_SIZE, + .maps[EFUSE_MAX_SECTION_MAP] = EFUSE_MAX_SECTION, + .maps[EFUSE_REAL_CONTENT_SIZE] = EFUSE_REAL_CONTENT_LEN, + .maps[EFUSE_OOB_PROTECT_BYTES_LEN] = EFUSE_OOB_PROTECT_BYTES, + + .maps[RWCAM] = REG_CAMCMD, + .maps[WCAMI] = REG_CAMWRITE, + .maps[RCAMO] = REG_CAMREAD, + .maps[CAMDBG] = REG_CAMDBG, + .maps[SECR] = REG_SECCFG, + .maps[SEC_CAM_NONE] = CAM_NONE, + .maps[SEC_CAM_WEP40] = CAM_WEP40, + .maps[SEC_CAM_TKIP] = CAM_TKIP, + .maps[SEC_CAM_AES] = CAM_AES, + .maps[SEC_CAM_WEP104] = CAM_WEP104, + + .maps[RTL_IMR_BCNDMAINT6] = IMR_BCNDMAINT6, + .maps[RTL_IMR_BCNDMAINT5] = IMR_BCNDMAINT5, + .maps[RTL_IMR_BCNDMAINT4] = IMR_BCNDMAINT4, + .maps[RTL_IMR_BCNDMAINT3] = IMR_BCNDMAINT3, + .maps[RTL_IMR_BCNDMAINT2] = IMR_BCNDMAINT2, + .maps[RTL_IMR_BCNDMAINT1] = IMR_BCNDMAINT1, + .maps[RTL_IMR_BCNDOK8] = IMR_BCNDOK8, + .maps[RTL_IMR_BCNDOK7] = IMR_BCNDOK7, + .maps[RTL_IMR_BCNDOK6] = IMR_BCNDOK6, + .maps[RTL_IMR_BCNDOK5] = IMR_BCNDOK5, + .maps[RTL_IMR_BCNDOK4] = IMR_BCNDOK4, + .maps[RTL_IMR_BCNDOK3] = IMR_BCNDOK3, + .maps[RTL_IMR_BCNDOK2] = IMR_BCNDOK2, + .maps[RTL_IMR_BCNDOK1] = IMR_BCNDOK1, + .maps[RTL_IMR_TIMEOUT2] = IMR_TIMEOUT2, + .maps[RTL_IMR_TIMEOUT1] = IMR_TIMEOUT1, + + .maps[RTL_IMR_TXFOVW] = IMR_TXFOVW, + .maps[RTL_IMR_PSTIMEOUT] = IMR_PSTIMEOUT, + .maps[RTL_IMR_BCNINT] = IMR_BCNINT, + .maps[RTL_IMR_RXFOVW] = IMR_RXFOVW, + .maps[RTL_IMR_RDU] = IMR_RDU, + .maps[RTL_IMR_ATIMEND] = IMR_ATIMEND, + .maps[RTL_IMR_BDOK] = IMR_BDOK, + .maps[RTL_IMR_MGNTDOK] = IMR_MGNTDOK, + .maps[RTL_IMR_TBDER] = IMR_TBDER, + .maps[RTL_IMR_HIGHDOK] = IMR_HIGHDOK, + .maps[RTL_IMR_TBDOK] = IMR_TBDOK, + .maps[RTL_IMR_BKDOK] = IMR_BKDOK, + .maps[RTL_IMR_BEDOK] = IMR_BEDOK, + .maps[RTL_IMR_VIDOK] = IMR_VIDOK, + .maps[RTL_IMR_VODOK] = IMR_VODOK, + .maps[RTL_IMR_ROK] = IMR_ROK, + .maps[RTL_IBSS_INT_MASKS] = (IMR_BCNINT | IMR_TBDOK | IMR_TBDER), + + .maps[RTL_RC_CCK_RATE1M] = DESC92_RATE1M, + .maps[RTL_RC_CCK_RATE2M] = DESC92_RATE2M, + .maps[RTL_RC_CCK_RATE5_5M] = DESC92_RATE5_5M, + .maps[RTL_RC_CCK_RATE11M] = DESC92_RATE11M, + .maps[RTL_RC_OFDM_RATE6M] = DESC92_RATE6M, + .maps[RTL_RC_OFDM_RATE9M] = DESC92_RATE9M, + .maps[RTL_RC_OFDM_RATE12M] = DESC92_RATE12M, + .maps[RTL_RC_OFDM_RATE18M] = DESC92_RATE18M, + .maps[RTL_RC_OFDM_RATE24M] = DESC92_RATE24M, + .maps[RTL_RC_OFDM_RATE36M] = DESC92_RATE36M, + .maps[RTL_RC_OFDM_RATE48M] = DESC92_RATE48M, + .maps[RTL_RC_OFDM_RATE54M] = DESC92_RATE54M, + + .maps[RTL_RC_HT_RATEMCS7] = DESC92_RATEMCS7, + .maps[RTL_RC_HT_RATEMCS15] = DESC92_RATEMCS15, +}; + +static DEFINE_PCI_DEVICE_TABLE(rtl92ce_pci_ids) = { + {RTL_PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8191, rtl92ce_hal_cfg)}, + {RTL_PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8178, rtl92ce_hal_cfg)}, + {RTL_PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8177, rtl92ce_hal_cfg)}, + {RTL_PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8176, rtl92ce_hal_cfg)}, + {}, +}; + +MODULE_DEVICE_TABLE(pci, rtl92ce_pci_ids); + +MODULE_AUTHOR("lizhaoming "); +MODULE_AUTHOR("Realtek WlanFAE "); +MODULE_AUTHOR("Larry Finger "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Realtek 8192C/8188C 802.11n PCI wireless"); +MODULE_FIRMWARE("rtlwifi/rtl8192cfw.bin"); +MODULE_FIRMWARE("rtlwifi/rtl8192cfwU.bin"); +MODULE_FIRMWARE("rtlwifi/rtl8192cfwU_B.bin"); + +module_param_named(swenc, rtl92ce_mod_params.sw_crypto, bool, 0444); +module_param_named(debug, rtl92ce_mod_params.debug, int, 0444); +module_param_named(ips, rtl92ce_mod_params.inactiveps, bool, 0444); +module_param_named(swlps, rtl92ce_mod_params.swctrl_lps, bool, 0444); +module_param_named(fwlps, rtl92ce_mod_params.fwctrl_lps, bool, 0444); +MODULE_PARM_DESC(swenc, "Set to 1 for software crypto (default 0)\n"); +MODULE_PARM_DESC(ips, "Set to 0 to not use link power save (default 1)\n"); +MODULE_PARM_DESC(swlps, "Set to 1 to use SW control power save (default 0)\n"); +MODULE_PARM_DESC(fwlps, "Set to 1 to use FW control power save (default 1)\n"); +MODULE_PARM_DESC(debug, "Set debug level (0-5) (default 0)"); + +compat_pci_suspend(rtl_pci_suspend); +compat_pci_resume(rtl_pci_resume); + +static SIMPLE_DEV_PM_OPS(rtlwifi_pm_ops, rtl_pci_suspend, rtl_pci_resume); + +static struct pci_driver rtl92ce_driver = { + .name = KBUILD_MODNAME, + .id_table = rtl92ce_pci_ids, + .probe = rtl_pci_probe, + .remove = rtl_pci_disconnect, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) + .driver.pm = &rtlwifi_pm_ops, +#elif defined(CONFIG_PM_SLEEP) + .suspend = rtl_pci_suspend_compat, + .resume = rtl_pci_resume_compat, +#endif +}; + +module_pci_driver(rtl92ce_driver); diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/sw.h b/drivers/net/wireless/rtlwifi/rtl8192ce/sw.h new file mode 100644 index 0000000..d2367a5 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/sw.h @@ -0,0 +1,41 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CE_SW_H__ +#define __RTL92CE_SW_H__ + +int rtl92c_init_sw_vars(struct ieee80211_hw *hw); +void rtl92c_deinit_sw_vars(struct ieee80211_hw *hw); +void rtl92c_init_var_map(struct ieee80211_hw *hw); +bool _rtl92ce_phy_config_bb_with_headerfile(struct ieee80211_hw *hw, + u8 configtype); +bool _rtl92ce_phy_config_bb_with_pgheaderfile(struct ieee80211_hw *hw, + u8 configtype); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/table.c b/drivers/net/wireless/rtlwifi/rtl8192ce/table.c new file mode 100644 index 0000000..752f943 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/table.c @@ -0,0 +1,1224 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Created on 2010/ 5/18, 1:41 + * + * Larry Finger + * + *****************************************************************************/ + +#include "table.h" + + +u32 RTL8192CEPHY_REG_2TARRAY[PHY_REG_2TARRAY_LENGTH] = { + 0x024, 0x0011800f, + 0x028, 0x00ffdb83, + 0x800, 0x80040002, + 0x804, 0x00000003, + 0x808, 0x0000fc00, + 0x80c, 0x0000000a, + 0x810, 0x10005388, + 0x814, 0x020c3d10, + 0x818, 0x02200385, + 0x81c, 0x00000000, + 0x820, 0x01000100, + 0x824, 0x00390004, + 0x828, 0x01000100, + 0x82c, 0x00390004, + 0x830, 0x27272727, + 0x834, 0x27272727, + 0x838, 0x27272727, + 0x83c, 0x27272727, + 0x840, 0x00010000, + 0x844, 0x00010000, + 0x848, 0x27272727, + 0x84c, 0x27272727, + 0x850, 0x00000000, + 0x854, 0x00000000, + 0x858, 0x569a569a, + 0x85c, 0x0c1b25a4, + 0x860, 0x66e60230, + 0x864, 0x061f0130, + 0x868, 0x27272727, + 0x86c, 0x2b2b2b27, + 0x870, 0x07000700, + 0x874, 0x22184000, + 0x878, 0x08080808, + 0x87c, 0x00000000, + 0x880, 0xc0083070, + 0x884, 0x000004d5, + 0x888, 0x00000000, + 0x88c, 0xcc0000c0, + 0x890, 0x00000800, + 0x894, 0xfffffffe, + 0x898, 0x40302010, + 0x89c, 0x00706050, + 0x900, 0x00000000, + 0x904, 0x00000023, + 0x908, 0x00000000, + 0x90c, 0x81121313, + 0xa00, 0x00d047c8, + 0xa04, 0x80ff000c, + 0xa08, 0x8c838300, + 0xa0c, 0x2e68120f, + 0xa10, 0x9500bb78, + 0xa14, 0x11144028, + 0xa18, 0x00881117, + 0xa1c, 0x89140f00, + 0xa20, 0x1a1b0000, + 0xa24, 0x090e1317, + 0xa28, 0x00000204, + 0xa2c, 0x00d30000, + 0xa70, 0x101fbf00, + 0xa74, 0x00000007, + 0xc00, 0x48071d40, + 0xc04, 0x03a05633, + 0xc08, 0x000000e4, + 0xc0c, 0x6c6c6c6c, + 0xc10, 0x08800000, + 0xc14, 0x40000100, + 0xc18, 0x08800000, + 0xc1c, 0x40000100, + 0xc20, 0x00000000, + 0xc24, 0x00000000, + 0xc28, 0x00000000, + 0xc2c, 0x00000000, + 0xc30, 0x69e9ac44, + 0xc34, 0x469652cf, + 0xc38, 0x49795994, + 0xc3c, 0x0a97971c, + 0xc40, 0x1f7c403f, + 0xc44, 0x000100b7, + 0xc48, 0xec020107, + 0xc4c, 0x007f037f, + 0xc50, 0x69543420, + 0xc54, 0x43bc0094, + 0xc58, 0x69543420, + 0xc5c, 0x433c0094, + 0xc60, 0x00000000, + 0xc64, 0x5116848b, + 0xc68, 0x47c00bff, + 0xc6c, 0x00000036, + 0xc70, 0x2c7f000d, + 0xc74, 0x018610db, + 0xc78, 0x0000001f, + 0xc7c, 0x00b91612, + 0xc80, 0x40000100, + 0xc84, 0x20f60000, + 0xc88, 0x40000100, + 0xc8c, 0x20200000, + 0xc90, 0x00121820, + 0xc94, 0x00000000, + 0xc98, 0x00121820, + 0xc9c, 0x00007f7f, + 0xca0, 0x00000000, + 0xca4, 0x00000080, + 0xca8, 0x00000000, + 0xcac, 0x00000000, + 0xcb0, 0x00000000, + 0xcb4, 0x00000000, + 0xcb8, 0x00000000, + 0xcbc, 0x28000000, + 0xcc0, 0x00000000, + 0xcc4, 0x00000000, + 0xcc8, 0x00000000, + 0xccc, 0x00000000, + 0xcd0, 0x00000000, + 0xcd4, 0x00000000, + 0xcd8, 0x64b22427, + 0xcdc, 0x00766932, + 0xce0, 0x00222222, + 0xce4, 0x00000000, + 0xce8, 0x37644302, + 0xcec, 0x2f97d40c, + 0xd00, 0x00080740, + 0xd04, 0x00020403, + 0xd08, 0x0000907f, + 0xd0c, 0x20010201, + 0xd10, 0xa0633333, + 0xd14, 0x3333bc43, + 0xd18, 0x7a8f5b6b, + 0xd2c, 0xcc979975, + 0xd30, 0x00000000, + 0xd34, 0x80608000, + 0xd38, 0x00000000, + 0xd3c, 0x00027293, + 0xd40, 0x00000000, + 0xd44, 0x00000000, + 0xd48, 0x00000000, + 0xd4c, 0x00000000, + 0xd50, 0x6437140a, + 0xd54, 0x00000000, + 0xd58, 0x00000000, + 0xd5c, 0x30032064, + 0xd60, 0x4653de68, + 0xd64, 0x04518a3c, + 0xd68, 0x00002101, + 0xd6c, 0x2a201c16, + 0xd70, 0x1812362e, + 0xd74, 0x322c2220, + 0xd78, 0x000e3c24, + 0xe00, 0x2a2a2a2a, + 0xe04, 0x2a2a2a2a, + 0xe08, 0x03902a2a, + 0xe10, 0x2a2a2a2a, + 0xe14, 0x2a2a2a2a, + 0xe18, 0x2a2a2a2a, + 0xe1c, 0x2a2a2a2a, + 0xe28, 0x00000000, + 0xe30, 0x1000dc1f, + 0xe34, 0x10008c1f, + 0xe38, 0x02140102, + 0xe3c, 0x681604c2, + 0xe40, 0x01007c00, + 0xe44, 0x01004800, + 0xe48, 0xfb000000, + 0xe4c, 0x000028d1, + 0xe50, 0x1000dc1f, + 0xe54, 0x10008c1f, + 0xe58, 0x02140102, + 0xe5c, 0x28160d05, + 0xe60, 0x00000010, + 0xe68, 0x001b25a4, + 0xe6c, 0x63db25a4, + 0xe70, 0x63db25a4, + 0xe74, 0x0c1b25a4, + 0xe78, 0x0c1b25a4, + 0xe7c, 0x0c1b25a4, + 0xe80, 0x0c1b25a4, + 0xe84, 0x63db25a4, + 0xe88, 0x0c1b25a4, + 0xe8c, 0x63db25a4, + 0xed0, 0x63db25a4, + 0xed4, 0x63db25a4, + 0xed8, 0x63db25a4, + 0xedc, 0x001b25a4, + 0xee0, 0x001b25a4, + 0xeec, 0x6fdb25a4, + 0xf14, 0x00000003, + 0xf4c, 0x00000000, + 0xf00, 0x00000300, +}; + +u32 RTL8192CEPHY_REG_1TARRAY[PHY_REG_1TARRAY_LENGTH] = { + 0x024, 0x0011800f, + 0x028, 0x00ffdb83, + 0x800, 0x80040000, + 0x804, 0x00000001, + 0x808, 0x0000fc00, + 0x80c, 0x0000000a, + 0x810, 0x10005388, + 0x814, 0x020c3d10, + 0x818, 0x02200385, + 0x81c, 0x00000000, + 0x820, 0x01000100, + 0x824, 0x00390004, + 0x828, 0x00000000, + 0x82c, 0x00000000, + 0x830, 0x00000000, + 0x834, 0x00000000, + 0x838, 0x00000000, + 0x83c, 0x00000000, + 0x840, 0x00010000, + 0x844, 0x00000000, + 0x848, 0x00000000, + 0x84c, 0x00000000, + 0x850, 0x00000000, + 0x854, 0x00000000, + 0x858, 0x569a569a, + 0x85c, 0x001b25a4, + 0x860, 0x66e60230, + 0x864, 0x061f0130, + 0x868, 0x00000000, + 0x86c, 0x32323200, + 0x870, 0x07000700, + 0x874, 0x22004000, + 0x878, 0x00000808, + 0x87c, 0x00000000, + 0x880, 0xc0083070, + 0x884, 0x000004d5, + 0x888, 0x00000000, + 0x88c, 0xccc000c0, + 0x890, 0x00000800, + 0x894, 0xfffffffe, + 0x898, 0x40302010, + 0x89c, 0x00706050, + 0x900, 0x00000000, + 0x904, 0x00000023, + 0x908, 0x00000000, + 0x90c, 0x81121111, + 0xa00, 0x00d047c8, + 0xa04, 0x80ff000c, + 0xa08, 0x8c838300, + 0xa0c, 0x2e68120f, + 0xa10, 0x9500bb78, + 0xa14, 0x11144028, + 0xa18, 0x00881117, + 0xa1c, 0x89140f00, + 0xa20, 0x1a1b0000, + 0xa24, 0x090e1317, + 0xa28, 0x00000204, + 0xa2c, 0x00d30000, + 0xa70, 0x101fbf00, + 0xa74, 0x00000007, + 0xc00, 0x48071d40, + 0xc04, 0x03a05611, + 0xc08, 0x000000e4, + 0xc0c, 0x6c6c6c6c, + 0xc10, 0x08800000, + 0xc14, 0x40000100, + 0xc18, 0x08800000, + 0xc1c, 0x40000100, + 0xc20, 0x00000000, + 0xc24, 0x00000000, + 0xc28, 0x00000000, + 0xc2c, 0x00000000, + 0xc30, 0x69e9ac44, + 0xc34, 0x469652cf, + 0xc38, 0x49795994, + 0xc3c, 0x0a97971c, + 0xc40, 0x1f7c403f, + 0xc44, 0x000100b7, + 0xc48, 0xec020107, + 0xc4c, 0x007f037f, + 0xc50, 0x69543420, + 0xc54, 0x43bc0094, + 0xc58, 0x69543420, + 0xc5c, 0x433c0094, + 0xc60, 0x00000000, + 0xc64, 0x5116848b, + 0xc68, 0x47c00bff, + 0xc6c, 0x00000036, + 0xc70, 0x2c7f000d, + 0xc74, 0x018610db, + 0xc78, 0x0000001f, + 0xc7c, 0x00b91612, + 0xc80, 0x40000100, + 0xc84, 0x20f60000, + 0xc88, 0x40000100, + 0xc8c, 0x20200000, + 0xc90, 0x00121820, + 0xc94, 0x00000000, + 0xc98, 0x00121820, + 0xc9c, 0x00007f7f, + 0xca0, 0x00000000, + 0xca4, 0x00000080, + 0xca8, 0x00000000, + 0xcac, 0x00000000, + 0xcb0, 0x00000000, + 0xcb4, 0x00000000, + 0xcb8, 0x00000000, + 0xcbc, 0x28000000, + 0xcc0, 0x00000000, + 0xcc4, 0x00000000, + 0xcc8, 0x00000000, + 0xccc, 0x00000000, + 0xcd0, 0x00000000, + 0xcd4, 0x00000000, + 0xcd8, 0x64b22427, + 0xcdc, 0x00766932, + 0xce0, 0x00222222, + 0xce4, 0x00000000, + 0xce8, 0x37644302, + 0xcec, 0x2f97d40c, + 0xd00, 0x00080740, + 0xd04, 0x00020401, + 0xd08, 0x0000907f, + 0xd0c, 0x20010201, + 0xd10, 0xa0633333, + 0xd14, 0x3333bc43, + 0xd18, 0x7a8f5b6b, + 0xd2c, 0xcc979975, + 0xd30, 0x00000000, + 0xd34, 0x80608000, + 0xd38, 0x00000000, + 0xd3c, 0x00027293, + 0xd40, 0x00000000, + 0xd44, 0x00000000, + 0xd48, 0x00000000, + 0xd4c, 0x00000000, + 0xd50, 0x6437140a, + 0xd54, 0x00000000, + 0xd58, 0x00000000, + 0xd5c, 0x30032064, + 0xd60, 0x4653de68, + 0xd64, 0x04518a3c, + 0xd68, 0x00002101, + 0xd6c, 0x2a201c16, + 0xd70, 0x1812362e, + 0xd74, 0x322c2220, + 0xd78, 0x000e3c24, + 0xe00, 0x2a2a2a2a, + 0xe04, 0x2a2a2a2a, + 0xe08, 0x03902a2a, + 0xe10, 0x2a2a2a2a, + 0xe14, 0x2a2a2a2a, + 0xe18, 0x2a2a2a2a, + 0xe1c, 0x2a2a2a2a, + 0xe28, 0x00000000, + 0xe30, 0x1000dc1f, + 0xe34, 0x10008c1f, + 0xe38, 0x02140102, + 0xe3c, 0x681604c2, + 0xe40, 0x01007c00, + 0xe44, 0x01004800, + 0xe48, 0xfb000000, + 0xe4c, 0x000028d1, + 0xe50, 0x1000dc1f, + 0xe54, 0x10008c1f, + 0xe58, 0x02140102, + 0xe5c, 0x28160d05, + 0xe60, 0x00000010, + 0xe68, 0x001b25a4, + 0xe6c, 0x631b25a0, + 0xe70, 0x631b25a0, + 0xe74, 0x081b25a0, + 0xe78, 0x081b25a0, + 0xe7c, 0x081b25a0, + 0xe80, 0x081b25a0, + 0xe84, 0x631b25a0, + 0xe88, 0x081b25a0, + 0xe8c, 0x631b25a0, + 0xed0, 0x631b25a0, + 0xed4, 0x631b25a0, + 0xed8, 0x631b25a0, + 0xedc, 0x001b25a0, + 0xee0, 0x001b25a0, + 0xeec, 0x6b1b25a0, + 0xf14, 0x00000003, + 0xf4c, 0x00000000, + 0xf00, 0x00000300, +}; + +u32 RTL8192CEPHY_REG_ARRAY_PG[PHY_REG_ARRAY_PGLENGTH] = { + 0xe00, 0xffffffff, 0x0a0c0c0c, + 0xe04, 0xffffffff, 0x02040608, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x0a0c0d0e, + 0xe14, 0xffffffff, 0x02040608, + 0xe18, 0xffffffff, 0x0a0c0d0e, + 0xe1c, 0xffffffff, 0x02040608, + 0x830, 0xffffffff, 0x0a0c0c0c, + 0x834, 0xffffffff, 0x02040608, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x0a0c0d0e, + 0x848, 0xffffffff, 0x02040608, + 0x84c, 0xffffffff, 0x0a0c0d0e, + 0x868, 0xffffffff, 0x02040608, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x04040404, + 0xe04, 0xffffffff, 0x00020204, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x06060606, + 0xe14, 0xffffffff, 0x00020406, + 0xe18, 0xffffffff, 0x06060606, + 0xe1c, 0xffffffff, 0x00020406, + 0x830, 0xffffffff, 0x04040404, + 0x834, 0xffffffff, 0x00020204, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x06060606, + 0x848, 0xffffffff, 0x00020406, + 0x84c, 0xffffffff, 0x06060606, + 0x868, 0xffffffff, 0x00020406, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, +}; + +u32 RTL8192CERADIOA_2TARRAY[RADIOA_2TARRAYLENGTH] = { + 0x000, 0x00030159, + 0x001, 0x00031284, + 0x002, 0x00098000, + 0x003, 0x00018c63, + 0x004, 0x000210e7, + 0x009, 0x0002044f, + 0x00a, 0x0001adb0, + 0x00b, 0x00054867, + 0x00c, 0x0008992e, + 0x00d, 0x0000e52c, + 0x00e, 0x00039ce7, + 0x00f, 0x00000451, + 0x019, 0x00000000, + 0x01a, 0x00010255, + 0x01b, 0x00060a00, + 0x01c, 0x000fc378, + 0x01d, 0x000a1250, + 0x01e, 0x0004445f, + 0x01f, 0x00080001, + 0x020, 0x0000b614, + 0x021, 0x0006c000, + 0x022, 0x00000000, + 0x023, 0x00001558, + 0x024, 0x00000060, + 0x025, 0x00000483, + 0x026, 0x0004f000, + 0x027, 0x000ec7d9, + 0x028, 0x000977c0, + 0x029, 0x00004783, + 0x02a, 0x00000001, + 0x02b, 0x00021334, + 0x02a, 0x00000000, + 0x02b, 0x00000054, + 0x02a, 0x00000001, + 0x02b, 0x00000808, + 0x02b, 0x00053333, + 0x02c, 0x0000000c, + 0x02a, 0x00000002, + 0x02b, 0x00000808, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000003, + 0x02b, 0x00000808, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x00000004, + 0x02b, 0x00000808, + 0x02b, 0x0006b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000005, + 0x02b, 0x00000808, + 0x02b, 0x00073333, + 0x02c, 0x0000000d, + 0x02a, 0x00000006, + 0x02b, 0x00000709, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000007, + 0x02b, 0x00000709, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x00000008, + 0x02b, 0x0000060a, + 0x02b, 0x0004b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000009, + 0x02b, 0x0000060a, + 0x02b, 0x00053333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000a, + 0x02b, 0x0000060a, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000b, + 0x02b, 0x0000060a, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000c, + 0x02b, 0x0000060a, + 0x02b, 0x0006b333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000d, + 0x02b, 0x0000060a, + 0x02b, 0x00073333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000e, + 0x02b, 0x0000050b, + 0x02b, 0x00066666, + 0x02c, 0x0000001a, + 0x02a, 0x000e0000, + 0x010, 0x0004000f, + 0x011, 0x000e31fc, + 0x010, 0x0006000f, + 0x011, 0x000ff9f8, + 0x010, 0x0002000f, + 0x011, 0x000203f9, + 0x010, 0x0003000f, + 0x011, 0x000ff500, + 0x010, 0x00000000, + 0x011, 0x00000000, + 0x010, 0x0008000f, + 0x011, 0x0003f100, + 0x010, 0x0009000f, + 0x011, 0x00023100, + 0x012, 0x00032000, + 0x012, 0x00071000, + 0x012, 0x000b0000, + 0x012, 0x000fc000, + 0x013, 0x000287af, + 0x013, 0x000244b7, + 0x013, 0x000204ab, + 0x013, 0x0001c49f, + 0x013, 0x00018493, + 0x013, 0x00014297, + 0x013, 0x00010295, + 0x013, 0x0000c298, + 0x013, 0x0000819c, + 0x013, 0x000040a8, + 0x013, 0x0000001c, + 0x014, 0x0001944c, + 0x014, 0x00059444, + 0x014, 0x0009944c, + 0x014, 0x000d9444, + 0x015, 0x0000f424, + 0x015, 0x0004f424, + 0x015, 0x0008f424, + 0x015, 0x000cf424, + 0x016, 0x000e0330, + 0x016, 0x000a0330, + 0x016, 0x00060330, + 0x016, 0x00020330, + 0x000, 0x00010159, + 0x018, 0x0000f401, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x01f, 0x00080003, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x01e, 0x00044457, + 0x01f, 0x00080000, + 0x000, 0x00030159, +}; + +u32 RTL8192CE_RADIOB_2TARRAY[RADIOB_2TARRAYLENGTH] = { + 0x000, 0x00030159, + 0x001, 0x00031284, + 0x002, 0x00098000, + 0x003, 0x00018c63, + 0x004, 0x000210e7, + 0x009, 0x0002044f, + 0x00a, 0x0001adb0, + 0x00b, 0x00054867, + 0x00c, 0x0008992e, + 0x00d, 0x0000e52c, + 0x00e, 0x00039ce7, + 0x00f, 0x00000451, + 0x012, 0x00032000, + 0x012, 0x00071000, + 0x012, 0x000b0000, + 0x012, 0x000fc000, + 0x013, 0x000287af, + 0x013, 0x000244b7, + 0x013, 0x000204ab, + 0x013, 0x0001c49f, + 0x013, 0x00018493, + 0x013, 0x00014297, + 0x013, 0x00010295, + 0x013, 0x0000c298, + 0x013, 0x0000819c, + 0x013, 0x000040a8, + 0x013, 0x0000001c, + 0x014, 0x0001944c, + 0x014, 0x00059444, + 0x014, 0x0009944c, + 0x014, 0x000d9444, + 0x015, 0x0000f424, + 0x015, 0x0004f424, + 0x015, 0x0008f424, + 0x015, 0x000cf424, + 0x016, 0x000e0330, + 0x016, 0x000a0330, + 0x016, 0x00060330, + 0x016, 0x00020330, +}; + +u32 RTL8192CE_RADIOA_1TARRAY[RADIOA_1TARRAYLENGTH] = { + 0x000, 0x00030159, + 0x001, 0x00031284, + 0x002, 0x00098000, + 0x003, 0x00018c63, + 0x004, 0x000210e7, + 0x009, 0x0002044f, + 0x00a, 0x0001adb0, + 0x00b, 0x00054867, + 0x00c, 0x0008992e, + 0x00d, 0x0000e52c, + 0x00e, 0x00039ce7, + 0x00f, 0x00000451, + 0x019, 0x00000000, + 0x01a, 0x00010255, + 0x01b, 0x00060a00, + 0x01c, 0x000fc378, + 0x01d, 0x000a1250, + 0x01e, 0x0004445f, + 0x01f, 0x00080001, + 0x020, 0x0000b614, + 0x021, 0x0006c000, + 0x022, 0x00000000, + 0x023, 0x00001558, + 0x024, 0x00000060, + 0x025, 0x00000483, + 0x026, 0x0004f000, + 0x027, 0x000ec7d9, + 0x028, 0x000977c0, + 0x029, 0x00004783, + 0x02a, 0x00000001, + 0x02b, 0x00021334, + 0x02a, 0x00000000, + 0x02b, 0x00000054, + 0x02a, 0x00000001, + 0x02b, 0x00000808, + 0x02b, 0x00053333, + 0x02c, 0x0000000c, + 0x02a, 0x00000002, + 0x02b, 0x00000808, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000003, + 0x02b, 0x00000808, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x00000004, + 0x02b, 0x00000808, + 0x02b, 0x0006b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000005, + 0x02b, 0x00000808, + 0x02b, 0x00073333, + 0x02c, 0x0000000d, + 0x02a, 0x00000006, + 0x02b, 0x00000709, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000007, + 0x02b, 0x00000709, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x00000008, + 0x02b, 0x0000060a, + 0x02b, 0x0004b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000009, + 0x02b, 0x0000060a, + 0x02b, 0x00053333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000a, + 0x02b, 0x0000060a, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000b, + 0x02b, 0x0000060a, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000c, + 0x02b, 0x0000060a, + 0x02b, 0x0006b333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000d, + 0x02b, 0x0000060a, + 0x02b, 0x00073333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000e, + 0x02b, 0x0000050b, + 0x02b, 0x00066666, + 0x02c, 0x0000001a, + 0x02a, 0x000e0000, + 0x010, 0x0004000f, + 0x011, 0x000e31fc, + 0x010, 0x0006000f, + 0x011, 0x000ff9f8, + 0x010, 0x0002000f, + 0x011, 0x000203f9, + 0x010, 0x0003000f, + 0x011, 0x000ff500, + 0x010, 0x00000000, + 0x011, 0x00000000, + 0x010, 0x0008000f, + 0x011, 0x0003f100, + 0x010, 0x0009000f, + 0x011, 0x00023100, + 0x012, 0x00032000, + 0x012, 0x00071000, + 0x012, 0x000b0000, + 0x012, 0x000fc000, + 0x013, 0x000287af, + 0x013, 0x000244b7, + 0x013, 0x000204ab, + 0x013, 0x0001c49f, + 0x013, 0x00018493, + 0x013, 0x00014297, + 0x013, 0x00010295, + 0x013, 0x0000c298, + 0x013, 0x0000819c, + 0x013, 0x000040a8, + 0x013, 0x0000001c, + 0x014, 0x0001944c, + 0x014, 0x00059444, + 0x014, 0x0009944c, + 0x014, 0x000d9444, + 0x015, 0x0000f424, + 0x015, 0x0004f424, + 0x015, 0x0008f424, + 0x015, 0x000cf424, + 0x016, 0x000e0330, + 0x016, 0x000a0330, + 0x016, 0x00060330, + 0x016, 0x00020330, + 0x000, 0x00010159, + 0x018, 0x0000f401, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x01f, 0x00080003, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x01e, 0x00044457, + 0x01f, 0x00080000, + 0x000, 0x00030159, +}; + +u32 RTL8192CE_RADIOB_1TARRAY[RADIOB_1TARRAYLENGTH] = { + 0x0, +}; + +u32 RTL8192CEMAC_2T_ARRAY[MAC_2T_ARRAYLENGTH] = { + 0x420, 0x00000080, + 0x423, 0x00000000, + 0x430, 0x00000000, + 0x431, 0x00000000, + 0x432, 0x00000000, + 0x433, 0x00000001, + 0x434, 0x00000004, + 0x435, 0x00000005, + 0x436, 0x00000006, + 0x437, 0x00000007, + 0x438, 0x00000000, + 0x439, 0x00000000, + 0x43a, 0x00000000, + 0x43b, 0x00000001, + 0x43c, 0x00000004, + 0x43d, 0x00000005, + 0x43e, 0x00000006, + 0x43f, 0x00000007, + 0x440, 0x0000005d, + 0x441, 0x00000001, + 0x442, 0x00000000, + 0x444, 0x00000015, + 0x445, 0x000000f0, + 0x446, 0x0000000f, + 0x447, 0x00000000, + 0x458, 0x00000041, + 0x459, 0x000000a8, + 0x45a, 0x00000072, + 0x45b, 0x000000b9, + 0x460, 0x00000088, + 0x461, 0x00000088, + 0x462, 0x00000006, + 0x463, 0x00000003, + 0x4c8, 0x00000004, + 0x4c9, 0x00000008, + 0x4cc, 0x00000002, + 0x4cd, 0x00000028, + 0x4ce, 0x00000001, + 0x500, 0x00000026, + 0x501, 0x000000a2, + 0x502, 0x0000002f, + 0x503, 0x00000000, + 0x504, 0x00000028, + 0x505, 0x000000a3, + 0x506, 0x0000005e, + 0x507, 0x00000000, + 0x508, 0x0000002b, + 0x509, 0x000000a4, + 0x50a, 0x0000005e, + 0x50b, 0x00000000, + 0x50c, 0x0000004f, + 0x50d, 0x000000a4, + 0x50e, 0x00000000, + 0x50f, 0x00000000, + 0x512, 0x0000001c, + 0x514, 0x0000000a, + 0x515, 0x00000010, + 0x516, 0x0000000a, + 0x517, 0x00000010, + 0x51a, 0x00000016, + 0x524, 0x0000000f, + 0x525, 0x0000004f, + 0x546, 0x00000020, + 0x547, 0x00000000, + 0x559, 0x00000002, + 0x55a, 0x00000002, + 0x55d, 0x000000ff, + 0x605, 0x00000030, + 0x608, 0x0000000e, + 0x609, 0x0000002a, + 0x652, 0x00000020, + 0x63c, 0x0000000a, + 0x63d, 0x0000000a, + 0x700, 0x00000021, + 0x701, 0x00000043, + 0x702, 0x00000065, + 0x703, 0x00000087, + 0x708, 0x00000021, + 0x709, 0x00000043, + 0x70a, 0x00000065, + 0x70b, 0x00000087, +}; + +u32 RTL8192CEAGCTAB_2TARRAY[AGCTAB_2TARRAYLENGTH] = { + 0xc78, 0x7b000001, + 0xc78, 0x7b010001, + 0xc78, 0x7b020001, + 0xc78, 0x7b030001, + 0xc78, 0x7b040001, + 0xc78, 0x7b050001, + 0xc78, 0x7a060001, + 0xc78, 0x79070001, + 0xc78, 0x78080001, + 0xc78, 0x77090001, + 0xc78, 0x760a0001, + 0xc78, 0x750b0001, + 0xc78, 0x740c0001, + 0xc78, 0x730d0001, + 0xc78, 0x720e0001, + 0xc78, 0x710f0001, + 0xc78, 0x70100001, + 0xc78, 0x6f110001, + 0xc78, 0x6e120001, + 0xc78, 0x6d130001, + 0xc78, 0x6c140001, + 0xc78, 0x6b150001, + 0xc78, 0x6a160001, + 0xc78, 0x69170001, + 0xc78, 0x68180001, + 0xc78, 0x67190001, + 0xc78, 0x661a0001, + 0xc78, 0x651b0001, + 0xc78, 0x641c0001, + 0xc78, 0x631d0001, + 0xc78, 0x621e0001, + 0xc78, 0x611f0001, + 0xc78, 0x60200001, + 0xc78, 0x49210001, + 0xc78, 0x48220001, + 0xc78, 0x47230001, + 0xc78, 0x46240001, + 0xc78, 0x45250001, + 0xc78, 0x44260001, + 0xc78, 0x43270001, + 0xc78, 0x42280001, + 0xc78, 0x41290001, + 0xc78, 0x402a0001, + 0xc78, 0x262b0001, + 0xc78, 0x252c0001, + 0xc78, 0x242d0001, + 0xc78, 0x232e0001, + 0xc78, 0x222f0001, + 0xc78, 0x21300001, + 0xc78, 0x20310001, + 0xc78, 0x06320001, + 0xc78, 0x05330001, + 0xc78, 0x04340001, + 0xc78, 0x03350001, + 0xc78, 0x02360001, + 0xc78, 0x01370001, + 0xc78, 0x00380001, + 0xc78, 0x00390001, + 0xc78, 0x003a0001, + 0xc78, 0x003b0001, + 0xc78, 0x003c0001, + 0xc78, 0x003d0001, + 0xc78, 0x003e0001, + 0xc78, 0x003f0001, + 0xc78, 0x7b400001, + 0xc78, 0x7b410001, + 0xc78, 0x7b420001, + 0xc78, 0x7b430001, + 0xc78, 0x7b440001, + 0xc78, 0x7b450001, + 0xc78, 0x7a460001, + 0xc78, 0x79470001, + 0xc78, 0x78480001, + 0xc78, 0x77490001, + 0xc78, 0x764a0001, + 0xc78, 0x754b0001, + 0xc78, 0x744c0001, + 0xc78, 0x734d0001, + 0xc78, 0x724e0001, + 0xc78, 0x714f0001, + 0xc78, 0x70500001, + 0xc78, 0x6f510001, + 0xc78, 0x6e520001, + 0xc78, 0x6d530001, + 0xc78, 0x6c540001, + 0xc78, 0x6b550001, + 0xc78, 0x6a560001, + 0xc78, 0x69570001, + 0xc78, 0x68580001, + 0xc78, 0x67590001, + 0xc78, 0x665a0001, + 0xc78, 0x655b0001, + 0xc78, 0x645c0001, + 0xc78, 0x635d0001, + 0xc78, 0x625e0001, + 0xc78, 0x615f0001, + 0xc78, 0x60600001, + 0xc78, 0x49610001, + 0xc78, 0x48620001, + 0xc78, 0x47630001, + 0xc78, 0x46640001, + 0xc78, 0x45650001, + 0xc78, 0x44660001, + 0xc78, 0x43670001, + 0xc78, 0x42680001, + 0xc78, 0x41690001, + 0xc78, 0x406a0001, + 0xc78, 0x266b0001, + 0xc78, 0x256c0001, + 0xc78, 0x246d0001, + 0xc78, 0x236e0001, + 0xc78, 0x226f0001, + 0xc78, 0x21700001, + 0xc78, 0x20710001, + 0xc78, 0x06720001, + 0xc78, 0x05730001, + 0xc78, 0x04740001, + 0xc78, 0x03750001, + 0xc78, 0x02760001, + 0xc78, 0x01770001, + 0xc78, 0x00780001, + 0xc78, 0x00790001, + 0xc78, 0x007a0001, + 0xc78, 0x007b0001, + 0xc78, 0x007c0001, + 0xc78, 0x007d0001, + 0xc78, 0x007e0001, + 0xc78, 0x007f0001, + 0xc78, 0x3800001e, + 0xc78, 0x3801001e, + 0xc78, 0x3802001e, + 0xc78, 0x3803001e, + 0xc78, 0x3804001e, + 0xc78, 0x3805001e, + 0xc78, 0x3806001e, + 0xc78, 0x3807001e, + 0xc78, 0x3808001e, + 0xc78, 0x3c09001e, + 0xc78, 0x3e0a001e, + 0xc78, 0x400b001e, + 0xc78, 0x440c001e, + 0xc78, 0x480d001e, + 0xc78, 0x4c0e001e, + 0xc78, 0x500f001e, + 0xc78, 0x5210001e, + 0xc78, 0x5611001e, + 0xc78, 0x5a12001e, + 0xc78, 0x5e13001e, + 0xc78, 0x6014001e, + 0xc78, 0x6015001e, + 0xc78, 0x6016001e, + 0xc78, 0x6217001e, + 0xc78, 0x6218001e, + 0xc78, 0x6219001e, + 0xc78, 0x621a001e, + 0xc78, 0x621b001e, + 0xc78, 0x621c001e, + 0xc78, 0x621d001e, + 0xc78, 0x621e001e, + 0xc78, 0x621f001e, +}; + +u32 RTL8192CEAGCTAB_1TARRAY[AGCTAB_1TARRAYLENGTH] = { + 0xc78, 0x7b000001, + 0xc78, 0x7b010001, + 0xc78, 0x7b020001, + 0xc78, 0x7b030001, + 0xc78, 0x7b040001, + 0xc78, 0x7b050001, + 0xc78, 0x7a060001, + 0xc78, 0x79070001, + 0xc78, 0x78080001, + 0xc78, 0x77090001, + 0xc78, 0x760a0001, + 0xc78, 0x750b0001, + 0xc78, 0x740c0001, + 0xc78, 0x730d0001, + 0xc78, 0x720e0001, + 0xc78, 0x710f0001, + 0xc78, 0x70100001, + 0xc78, 0x6f110001, + 0xc78, 0x6e120001, + 0xc78, 0x6d130001, + 0xc78, 0x6c140001, + 0xc78, 0x6b150001, + 0xc78, 0x6a160001, + 0xc78, 0x69170001, + 0xc78, 0x68180001, + 0xc78, 0x67190001, + 0xc78, 0x661a0001, + 0xc78, 0x651b0001, + 0xc78, 0x641c0001, + 0xc78, 0x631d0001, + 0xc78, 0x621e0001, + 0xc78, 0x611f0001, + 0xc78, 0x60200001, + 0xc78, 0x49210001, + 0xc78, 0x48220001, + 0xc78, 0x47230001, + 0xc78, 0x46240001, + 0xc78, 0x45250001, + 0xc78, 0x44260001, + 0xc78, 0x43270001, + 0xc78, 0x42280001, + 0xc78, 0x41290001, + 0xc78, 0x402a0001, + 0xc78, 0x262b0001, + 0xc78, 0x252c0001, + 0xc78, 0x242d0001, + 0xc78, 0x232e0001, + 0xc78, 0x222f0001, + 0xc78, 0x21300001, + 0xc78, 0x20310001, + 0xc78, 0x06320001, + 0xc78, 0x05330001, + 0xc78, 0x04340001, + 0xc78, 0x03350001, + 0xc78, 0x02360001, + 0xc78, 0x01370001, + 0xc78, 0x00380001, + 0xc78, 0x00390001, + 0xc78, 0x003a0001, + 0xc78, 0x003b0001, + 0xc78, 0x003c0001, + 0xc78, 0x003d0001, + 0xc78, 0x003e0001, + 0xc78, 0x003f0001, + 0xc78, 0x7b400001, + 0xc78, 0x7b410001, + 0xc78, 0x7b420001, + 0xc78, 0x7b430001, + 0xc78, 0x7b440001, + 0xc78, 0x7b450001, + 0xc78, 0x7a460001, + 0xc78, 0x79470001, + 0xc78, 0x78480001, + 0xc78, 0x77490001, + 0xc78, 0x764a0001, + 0xc78, 0x754b0001, + 0xc78, 0x744c0001, + 0xc78, 0x734d0001, + 0xc78, 0x724e0001, + 0xc78, 0x714f0001, + 0xc78, 0x70500001, + 0xc78, 0x6f510001, + 0xc78, 0x6e520001, + 0xc78, 0x6d530001, + 0xc78, 0x6c540001, + 0xc78, 0x6b550001, + 0xc78, 0x6a560001, + 0xc78, 0x69570001, + 0xc78, 0x68580001, + 0xc78, 0x67590001, + 0xc78, 0x665a0001, + 0xc78, 0x655b0001, + 0xc78, 0x645c0001, + 0xc78, 0x635d0001, + 0xc78, 0x625e0001, + 0xc78, 0x615f0001, + 0xc78, 0x60600001, + 0xc78, 0x49610001, + 0xc78, 0x48620001, + 0xc78, 0x47630001, + 0xc78, 0x46640001, + 0xc78, 0x45650001, + 0xc78, 0x44660001, + 0xc78, 0x43670001, + 0xc78, 0x42680001, + 0xc78, 0x41690001, + 0xc78, 0x406a0001, + 0xc78, 0x266b0001, + 0xc78, 0x256c0001, + 0xc78, 0x246d0001, + 0xc78, 0x236e0001, + 0xc78, 0x226f0001, + 0xc78, 0x21700001, + 0xc78, 0x20710001, + 0xc78, 0x06720001, + 0xc78, 0x05730001, + 0xc78, 0x04740001, + 0xc78, 0x03750001, + 0xc78, 0x02760001, + 0xc78, 0x01770001, + 0xc78, 0x00780001, + 0xc78, 0x00790001, + 0xc78, 0x007a0001, + 0xc78, 0x007b0001, + 0xc78, 0x007c0001, + 0xc78, 0x007d0001, + 0xc78, 0x007e0001, + 0xc78, 0x007f0001, + 0xc78, 0x3800001e, + 0xc78, 0x3801001e, + 0xc78, 0x3802001e, + 0xc78, 0x3803001e, + 0xc78, 0x3804001e, + 0xc78, 0x3805001e, + 0xc78, 0x3806001e, + 0xc78, 0x3807001e, + 0xc78, 0x3808001e, + 0xc78, 0x3c09001e, + 0xc78, 0x3e0a001e, + 0xc78, 0x400b001e, + 0xc78, 0x440c001e, + 0xc78, 0x480d001e, + 0xc78, 0x4c0e001e, + 0xc78, 0x500f001e, + 0xc78, 0x5210001e, + 0xc78, 0x5611001e, + 0xc78, 0x5a12001e, + 0xc78, 0x5e13001e, + 0xc78, 0x6014001e, + 0xc78, 0x6015001e, + 0xc78, 0x6016001e, + 0xc78, 0x6217001e, + 0xc78, 0x6218001e, + 0xc78, 0x6219001e, + 0xc78, 0x621a001e, + 0xc78, 0x621b001e, + 0xc78, 0x621c001e, + 0xc78, 0x621d001e, + 0xc78, 0x621e001e, + 0xc78, 0x621f001e, +}; diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/table.h b/drivers/net/wireless/rtlwifi/rtl8192ce/table.h new file mode 100644 index 0000000..8b79161 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/table.h @@ -0,0 +1,58 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Created on 2010/ 5/18, 1:41 + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CE_TABLE__H_ +#define __RTL92CE_TABLE__H_ + +#include + +#define PHY_REG_2TARRAY_LENGTH 374 +extern u32 RTL8192CEPHY_REG_2TARRAY[PHY_REG_2TARRAY_LENGTH]; +#define PHY_REG_1TARRAY_LENGTH 374 +extern u32 RTL8192CEPHY_REG_1TARRAY[PHY_REG_1TARRAY_LENGTH]; +#define PHY_REG_ARRAY_PGLENGTH 192 +extern u32 RTL8192CEPHY_REG_ARRAY_PG[PHY_REG_ARRAY_PGLENGTH]; +#define RADIOA_2TARRAYLENGTH 282 +extern u32 RTL8192CERADIOA_2TARRAY[RADIOA_2TARRAYLENGTH]; +#define RADIOB_2TARRAYLENGTH 78 +extern u32 RTL8192CE_RADIOB_2TARRAY[RADIOB_2TARRAYLENGTH]; +#define RADIOA_1TARRAYLENGTH 282 +extern u32 RTL8192CE_RADIOA_1TARRAY[RADIOA_1TARRAYLENGTH]; +#define RADIOB_1TARRAYLENGTH 1 +extern u32 RTL8192CE_RADIOB_1TARRAY[RADIOB_1TARRAYLENGTH]; +#define MAC_2T_ARRAYLENGTH 162 +extern u32 RTL8192CEMAC_2T_ARRAY[MAC_2T_ARRAYLENGTH]; +#define AGCTAB_2TARRAYLENGTH 320 +extern u32 RTL8192CEAGCTAB_2TARRAY[AGCTAB_2TARRAYLENGTH]; +#define AGCTAB_1TARRAYLENGTH 320 +extern u32 RTL8192CEAGCTAB_1TARRAY[AGCTAB_1TARRAYLENGTH]; + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/trx.c b/drivers/net/wireless/rtlwifi/rtl8192ce/trx.c new file mode 100644 index 0000000..6ad23b4 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/trx.c @@ -0,0 +1,754 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "../base.h" +#include "../stats.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "trx.h" +#include "led.h" + +static u8 _rtl92ce_map_hwqueue_to_fwqueue(struct sk_buff *skb, u8 hw_queue) +{ + __le16 fc = rtl_get_fc(skb); + + if (unlikely(ieee80211_is_beacon(fc))) + return QSLT_BEACON; + if (ieee80211_is_mgmt(fc) || ieee80211_is_ctl(fc)) + return QSLT_MGNT; + + return skb->priority; +} + +static u8 _rtl92c_query_rxpwrpercentage(char antpower) +{ + if ((antpower <= -100) || (antpower >= 20)) + return 0; + else if (antpower >= 0) + return 100; + else + return 100 + antpower; +} + +static u8 _rtl92c_evm_db_to_percentage(char value) +{ + char ret_val; + ret_val = value; + + if (ret_val >= 0) + ret_val = 0; + + if (ret_val <= -33) + ret_val = -33; + + ret_val = 0 - ret_val; + ret_val *= 3; + + if (ret_val == 99) + ret_val = 100; + + return ret_val; +} + +static long _rtl92ce_signal_scale_mapping(struct ieee80211_hw *hw, + long currsig) +{ + long retsig; + + if (currsig >= 61 && currsig <= 100) + retsig = 90 + ((currsig - 60) / 4); + else if (currsig >= 41 && currsig <= 60) + retsig = 78 + ((currsig - 40) / 2); + else if (currsig >= 31 && currsig <= 40) + retsig = 66 + (currsig - 30); + else if (currsig >= 21 && currsig <= 30) + retsig = 54 + (currsig - 20); + else if (currsig >= 5 && currsig <= 20) + retsig = 42 + (((currsig - 5) * 2) / 3); + else if (currsig == 4) + retsig = 36; + else if (currsig == 3) + retsig = 27; + else if (currsig == 2) + retsig = 18; + else if (currsig == 1) + retsig = 9; + else + retsig = currsig; + + return retsig; +} + +static void _rtl92ce_query_rxphystatus(struct ieee80211_hw *hw, + struct rtl_stats *pstats, + struct rx_desc_92c *pdesc, + struct rx_fwinfo_92c *p_drvinfo, + bool packet_match_bssid, + bool packet_toself, + bool packet_beacon) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct phy_sts_cck_8192s_t *cck_buf; + struct rtl_ps_ctl *ppsc = rtl_psc(rtlpriv); + s8 rx_pwr_all = 0, rx_pwr[4]; + u8 evm, pwdb_all, rf_rx_num = 0; + u8 i, max_spatial_stream; + u32 rssi, total_rssi = 0; + bool is_cck_rate; + + is_cck_rate = RX_HAL_IS_CCK_RATE(pdesc); + pstats->packet_matchbssid = packet_match_bssid; + pstats->packet_toself = packet_toself; + pstats->is_cck = is_cck_rate; + pstats->packet_beacon = packet_beacon; + pstats->rx_mimo_sig_qual[0] = -1; + pstats->rx_mimo_sig_qual[1] = -1; + + if (is_cck_rate) { + u8 report, cck_highpwr; + cck_buf = (struct phy_sts_cck_8192s_t *)p_drvinfo; + + if (ppsc->rfpwr_state == ERFON) + cck_highpwr = (u8) rtl_get_bbreg(hw, + RFPGA0_XA_HSSIPARAMETER2, + BIT(9)); + else + cck_highpwr = false; + + if (!cck_highpwr) { + u8 cck_agc_rpt = cck_buf->cck_agc_rpt; + report = cck_buf->cck_agc_rpt & 0xc0; + report = report >> 6; + switch (report) { + case 0x3: + rx_pwr_all = -46 - (cck_agc_rpt & 0x3e); + break; + case 0x2: + rx_pwr_all = -26 - (cck_agc_rpt & 0x3e); + break; + case 0x1: + rx_pwr_all = -12 - (cck_agc_rpt & 0x3e); + break; + case 0x0: + rx_pwr_all = 16 - (cck_agc_rpt & 0x3e); + break; + } + } else { + u8 cck_agc_rpt = cck_buf->cck_agc_rpt; + report = p_drvinfo->cfosho[0] & 0x60; + report = report >> 5; + switch (report) { + case 0x3: + rx_pwr_all = -46 - ((cck_agc_rpt & 0x1f) << 1); + break; + case 0x2: + rx_pwr_all = -26 - ((cck_agc_rpt & 0x1f) << 1); + break; + case 0x1: + rx_pwr_all = -12 - ((cck_agc_rpt & 0x1f) << 1); + break; + case 0x0: + rx_pwr_all = 16 - ((cck_agc_rpt & 0x1f) << 1); + break; + } + } + + pwdb_all = rtl_query_rxpwrpercentage(rx_pwr_all); + /* CCK gain is smaller than OFDM/MCS gain, + * so we add gain diff by experiences, + * the val is 6 + */ + pwdb_all += 6; + if (pwdb_all > 100) + pwdb_all = 100; + /* modify the offset to make the same + * gain index with OFDM. + */ + if (pwdb_all > 34 && pwdb_all <= 42) + pwdb_all -= 2; + else if (pwdb_all > 26 && pwdb_all <= 34) + pwdb_all -= 6; + else if (pwdb_all > 14 && pwdb_all <= 26) + pwdb_all -= 8; + else if (pwdb_all > 4 && pwdb_all <= 14) + pwdb_all -= 4; + + pstats->rx_pwdb_all = pwdb_all; + pstats->recvsignalpower = rx_pwr_all; + + /* (3) Get Signal Quality (EVM) */ + if (packet_match_bssid) { + u8 sq; + if (pstats->rx_pwdb_all > 40) + sq = 100; + else { + sq = cck_buf->sq_rpt; + if (sq > 64) + sq = 0; + else if (sq < 20) + sq = 100; + else + sq = ((64 - sq) * 100) / 44; + } + + pstats->signalquality = sq; + pstats->rx_mimo_sig_qual[0] = sq; + pstats->rx_mimo_sig_qual[1] = -1; + } + } else { + rtlpriv->dm.rfpath_rxenable[0] = + rtlpriv->dm.rfpath_rxenable[1] = true; + /* (1)Get RSSI for HT rate */ + for (i = RF90_PATH_A; i < RF90_PATH_MAX; i++) { + /* we will judge RF RX path now. */ + if (rtlpriv->dm.rfpath_rxenable[i]) + rf_rx_num++; + + rx_pwr[i] = + ((p_drvinfo->gain_trsw[i] & 0x3f) * 2) - 110; + /* Translate DBM to percentage. */ + rssi = _rtl92c_query_rxpwrpercentage(rx_pwr[i]); + total_rssi += rssi; + /* Get Rx snr value in DB */ + rtlpriv->stats.rx_snr_db[i] = + (long)(p_drvinfo->rxsnr[i] / 2); + + /* Record Signal Strength for next packet */ + if (packet_match_bssid) + pstats->rx_mimo_signalstrength[i] = (u8) rssi; + } + + /* (2)PWDB, Average PWDB cacluated by + * hardware (for rate adaptive) + */ + rx_pwr_all = ((p_drvinfo->pwdb_all >> 1) & 0x7f) - 110; + pwdb_all = _rtl92c_query_rxpwrpercentage(rx_pwr_all); + pstats->rx_pwdb_all = pwdb_all; + pstats->rxpower = rx_pwr_all; + pstats->recvsignalpower = rx_pwr_all; + + /* (3)EVM of HT rate */ + if (pstats->is_ht && pstats->rate >= DESC92_RATEMCS8 && + pstats->rate <= DESC92_RATEMCS15) + max_spatial_stream = 2; + else + max_spatial_stream = 1; + + for (i = 0; i < max_spatial_stream; i++) { + evm = _rtl92c_evm_db_to_percentage(p_drvinfo->rxevm[i]); + + if (packet_match_bssid) { + /* Fill value in RFD, Get the first + * spatial stream only + */ + if (i == 0) + pstats->signalquality = + (u8) (evm & 0xff); + pstats->rx_mimo_sig_qual[i] = (u8) (evm & 0xff); + } + } + } + + /* UI BSS List signal strength(in percentage), + * make it good looking, from 0~100. + */ + if (is_cck_rate) + pstats->signalstrength = + (u8) (_rtl92ce_signal_scale_mapping(hw, pwdb_all)); + else if (rf_rx_num != 0) + pstats->signalstrength = + (u8) (_rtl92ce_signal_scale_mapping + (hw, total_rssi /= rf_rx_num)); +} + +static void _rtl92ce_translate_rx_signal_stuff(struct ieee80211_hw *hw, + struct sk_buff *skb, + struct rtl_stats *pstats, + struct rx_desc_92c *pdesc, + struct rx_fwinfo_92c *p_drvinfo) +{ + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + + struct ieee80211_hdr *hdr; + u8 *tmp_buf; + u8 *praddr; + __le16 fc; + u16 type, c_fc; + bool packet_matchbssid, packet_toself, packet_beacon = false; + + tmp_buf = skb->data + pstats->rx_drvinfo_size + pstats->rx_bufshift; + + hdr = (struct ieee80211_hdr *)tmp_buf; + fc = hdr->frame_control; + c_fc = le16_to_cpu(fc); + type = WLAN_FC_GET_TYPE(fc); + praddr = hdr->addr1; + + packet_matchbssid = + ((IEEE80211_FTYPE_CTL != type) && + ether_addr_equal(mac->bssid, + (c_fc & IEEE80211_FCTL_TODS) ? hdr->addr1 : + (c_fc & IEEE80211_FCTL_FROMDS) ? hdr->addr2 : + hdr->addr3) && + (!pstats->hwerror) && (!pstats->crc) && (!pstats->icv)); + + packet_toself = packet_matchbssid && + ether_addr_equal(praddr, rtlefuse->dev_addr); + + if (ieee80211_is_beacon(fc)) + packet_beacon = true; + + _rtl92ce_query_rxphystatus(hw, pstats, pdesc, p_drvinfo, + packet_matchbssid, packet_toself, + packet_beacon); + + rtl_process_phyinfo(hw, tmp_buf, pstats); +} + +bool rtl92ce_rx_query_desc(struct ieee80211_hw *hw, + struct rtl_stats *stats, + struct ieee80211_rx_status *rx_status, + u8 *p_desc, struct sk_buff *skb) +{ + struct rx_fwinfo_92c *p_drvinfo; + struct rx_desc_92c *pdesc = (struct rx_desc_92c *)p_desc; + struct ieee80211_hdr *hdr; + u32 phystatus = GET_RX_DESC_PHYST(pdesc); + stats->length = (u16) GET_RX_DESC_PKT_LEN(pdesc); + stats->rx_drvinfo_size = (u8) GET_RX_DESC_DRV_INFO_SIZE(pdesc) * + RX_DRV_INFO_SIZE_UNIT; + stats->rx_bufshift = (u8) (GET_RX_DESC_SHIFT(pdesc) & 0x03); + stats->icv = (u16) GET_RX_DESC_ICV(pdesc); + stats->crc = (u16) GET_RX_DESC_CRC32(pdesc); + stats->hwerror = (stats->crc | stats->icv); + stats->decrypted = !GET_RX_DESC_SWDEC(pdesc); + stats->rate = (u8) GET_RX_DESC_RXMCS(pdesc); + stats->shortpreamble = (u16) GET_RX_DESC_SPLCP(pdesc); + stats->isampdu = (bool) (GET_RX_DESC_PAGGR(pdesc) == 1); + stats->isfirst_ampdu = (bool) ((GET_RX_DESC_PAGGR(pdesc) == 1) + && (GET_RX_DESC_FAGGR(pdesc) == 1)); + stats->timestamp_low = GET_RX_DESC_TSFL(pdesc); + stats->rx_is40Mhzpacket = (bool) GET_RX_DESC_BW(pdesc); + stats->is_ht = (bool)GET_RX_DESC_RXHT(pdesc); + + stats->is_cck = RX_HAL_IS_CCK_RATE(pdesc); + + rx_status->freq = hw->conf.chandef.chan->center_freq; + rx_status->band = hw->conf.chandef.chan->band; + + hdr = (struct ieee80211_hdr *)(skb->data + stats->rx_drvinfo_size + + stats->rx_bufshift); + + if (stats->crc) + rx_status->flag |= RX_FLAG_FAILED_FCS_CRC; + + if (stats->rx_is40Mhzpacket) + rx_status->flag |= RX_FLAG_40MHZ; + + if (stats->is_ht) + rx_status->flag |= RX_FLAG_HT; + + rx_status->flag |= RX_FLAG_MACTIME_START; + + /* hw will set stats->decrypted true, if it finds the + * frame is open data frame or mgmt frame. + * So hw will not decryption robust managment frame + * for IEEE80211w but still set status->decrypted + * true, so here we should set it back to undecrypted + * for IEEE80211w frame, and mac80211 sw will help + * to decrypt it + */ + if (stats->decrypted) { + if (!hdr) { + /* In testing, hdr was NULL here */ + return false; + } + if ((ieee80211_is_robust_mgmt_frame(hdr)) && + (ieee80211_has_protected(hdr->frame_control))) + rx_status->flag &= ~RX_FLAG_DECRYPTED; + else + rx_status->flag |= RX_FLAG_DECRYPTED; + } + /* rate_idx: index of data rate into band's + * supported rates or MCS index if HT rates + * are use (RX_FLAG_HT) + * Notice: this is diff with windows define + */ + rx_status->rate_idx = rtlwifi_rate_mapping(hw, + stats->is_ht, stats->rate, + stats->isfirst_ampdu); + + rx_status->mactime = stats->timestamp_low; + if (phystatus) { + p_drvinfo = (struct rx_fwinfo_92c *)(skb->data + + stats->rx_bufshift); + + _rtl92ce_translate_rx_signal_stuff(hw, + skb, stats, pdesc, + p_drvinfo); + } + + /*rx_status->qual = stats->signal; */ + rx_status->signal = stats->recvsignalpower + 10; + /*rx_status->noise = -stats->noise; */ + + return true; +} + +void rtl92ce_tx_fill_desc(struct ieee80211_hw *hw, + struct ieee80211_hdr *hdr, u8 *pdesc_tx, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff *skb, + u8 hw_queue, struct rtl_tcb_desc *tcb_desc) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + bool defaultadapter = true; + u8 *pdesc = pdesc_tx; + u16 seq_number; + __le16 fc = hdr->frame_control; + u8 fw_qsel = _rtl92ce_map_hwqueue_to_fwqueue(skb, hw_queue); + bool firstseg = ((hdr->seq_ctrl & + cpu_to_le16(IEEE80211_SCTL_FRAG)) == 0); + + bool lastseg = ((hdr->frame_control & + cpu_to_le16(IEEE80211_FCTL_MOREFRAGS)) == 0); + + dma_addr_t mapping = pci_map_single(rtlpci->pdev, + skb->data, skb->len, + PCI_DMA_TODEVICE); + + u8 bw_40 = 0; + + if (pci_dma_mapping_error(rtlpci->pdev, mapping)) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "DMA mapping error"); + return; + } + rcu_read_lock(); + sta = get_sta(hw, mac->vif, mac->bssid); + if (mac->opmode == NL80211_IFTYPE_STATION) { + bw_40 = mac->bw_40; + } else if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_ADHOC || + mac->opmode == NL80211_IFTYPE_MESH_POINT) { + if (sta) + bw_40 = sta->bandwidth >= IEEE80211_STA_RX_BW_40; + } + + seq_number = (le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_SEQ) >> 4; + + rtl_get_tcb_desc(hw, info, sta, skb, tcb_desc); + + CLEAR_PCI_TX_DESC_CONTENT(pdesc, sizeof(struct tx_desc_92c)); + + if (ieee80211_is_nullfunc(fc) || ieee80211_is_ctl(fc)) { + firstseg = true; + lastseg = true; + } + if (firstseg) { + SET_TX_DESC_OFFSET(pdesc, USB_HWDESC_HEADER_LEN); + + SET_TX_DESC_TX_RATE(pdesc, tcb_desc->hw_rate); + + if (tcb_desc->use_shortgi || tcb_desc->use_shortpreamble) + SET_TX_DESC_DATA_SHORTGI(pdesc, 1); + + if (info->flags & IEEE80211_TX_CTL_AMPDU) { + SET_TX_DESC_AGG_BREAK(pdesc, 1); + SET_TX_DESC_MAX_AGG_NUM(pdesc, 0x14); + } + SET_TX_DESC_SEQ(pdesc, seq_number); + + SET_TX_DESC_RTS_ENABLE(pdesc, ((tcb_desc->rts_enable && + !tcb_desc-> + cts_enable) ? 1 : 0)); + SET_TX_DESC_HW_RTS_ENABLE(pdesc, + ((tcb_desc->rts_enable + || tcb_desc->cts_enable) ? 1 : 0)); + SET_TX_DESC_CTS2SELF(pdesc, ((tcb_desc->cts_enable) ? 1 : 0)); + SET_TX_DESC_RTS_STBC(pdesc, ((tcb_desc->rts_stbc) ? 1 : 0)); + + SET_TX_DESC_RTS_RATE(pdesc, tcb_desc->rts_rate); + SET_TX_DESC_RTS_BW(pdesc, 0); + SET_TX_DESC_RTS_SC(pdesc, tcb_desc->rts_sc); + SET_TX_DESC_RTS_SHORT(pdesc, + ((tcb_desc->rts_rate <= DESC92_RATE54M) ? + (tcb_desc->rts_use_shortpreamble ? 1 : 0) + : (tcb_desc->rts_use_shortgi ? 1 : 0))); + + if (bw_40) { + if (tcb_desc->packet_bw) { + SET_TX_DESC_DATA_BW(pdesc, 1); + SET_TX_DESC_TX_SUB_CARRIER(pdesc, 3); + } else { + SET_TX_DESC_DATA_BW(pdesc, 0); + SET_TX_DESC_TX_SUB_CARRIER(pdesc, + mac->cur_40_prime_sc); + } + } else { + SET_TX_DESC_DATA_BW(pdesc, 0); + SET_TX_DESC_TX_SUB_CARRIER(pdesc, 0); + } + + SET_TX_DESC_LINIP(pdesc, 0); + SET_TX_DESC_PKT_SIZE(pdesc, (u16) skb->len); + + if (sta) { + u8 ampdu_density = sta->ht_cap.ampdu_density; + SET_TX_DESC_AMPDU_DENSITY(pdesc, ampdu_density); + } + + if (info->control.hw_key) { + struct ieee80211_key_conf *keyconf = + info->control.hw_key; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + case WLAN_CIPHER_SUITE_TKIP: + SET_TX_DESC_SEC_TYPE(pdesc, 0x1); + break; + case WLAN_CIPHER_SUITE_CCMP: + SET_TX_DESC_SEC_TYPE(pdesc, 0x3); + break; + default: + SET_TX_DESC_SEC_TYPE(pdesc, 0x0); + break; + + } + } + + SET_TX_DESC_PKT_ID(pdesc, 0); + SET_TX_DESC_QUEUE_SEL(pdesc, fw_qsel); + + SET_TX_DESC_DATA_RATE_FB_LIMIT(pdesc, 0x1F); + SET_TX_DESC_RTS_RATE_FB_LIMIT(pdesc, 0xF); + SET_TX_DESC_DISABLE_FB(pdesc, 0); + SET_TX_DESC_USE_RATE(pdesc, tcb_desc->use_driver_rate ? 1 : 0); + + if (ieee80211_is_data_qos(fc)) { + if (mac->rdg_en) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "Enable RDG function\n"); + SET_TX_DESC_RDG_ENABLE(pdesc, 1); + SET_TX_DESC_HTC(pdesc, 1); + } + } + } + rcu_read_unlock(); + + SET_TX_DESC_FIRST_SEG(pdesc, (firstseg ? 1 : 0)); + SET_TX_DESC_LAST_SEG(pdesc, (lastseg ? 1 : 0)); + + SET_TX_DESC_TX_BUFFER_SIZE(pdesc, (u16) skb->len); + + SET_TX_DESC_TX_BUFFER_ADDRESS(pdesc, mapping); + + if (rtlpriv->dm.useramask) { + SET_TX_DESC_RATE_ID(pdesc, tcb_desc->ratr_index); + SET_TX_DESC_MACID(pdesc, tcb_desc->mac_id); + } else { + SET_TX_DESC_RATE_ID(pdesc, 0xC + tcb_desc->ratr_index); + SET_TX_DESC_MACID(pdesc, tcb_desc->ratr_index); + } + + if ((!ieee80211_is_data_qos(fc)) && ppsc->fwctrl_lps) { + SET_TX_DESC_HWSEQ_EN(pdesc, 1); + SET_TX_DESC_PKT_ID(pdesc, 8); + + if (!defaultadapter) + SET_TX_DESC_QOS(pdesc, 1); + } + + SET_TX_DESC_MORE_FRAG(pdesc, (lastseg ? 0 : 1)); + + if (is_multicast_ether_addr(ieee80211_get_DA(hdr)) || + is_broadcast_ether_addr(ieee80211_get_DA(hdr))) { + SET_TX_DESC_BMC(pdesc, 1); + } + + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, "\n"); +} + +void rtl92ce_tx_fill_cmddesc(struct ieee80211_hw *hw, + u8 *pdesc, bool firstseg, + bool lastseg, struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u8 fw_queue = QSLT_BEACON; + + dma_addr_t mapping = pci_map_single(rtlpci->pdev, + skb->data, skb->len, + PCI_DMA_TODEVICE); + + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(skb->data); + __le16 fc = hdr->frame_control; + + if (pci_dma_mapping_error(rtlpci->pdev, mapping)) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "DMA mapping error"); + return; + } + CLEAR_PCI_TX_DESC_CONTENT(pdesc, TX_DESC_SIZE); + + if (firstseg) + SET_TX_DESC_OFFSET(pdesc, USB_HWDESC_HEADER_LEN); + + SET_TX_DESC_TX_RATE(pdesc, DESC92_RATE1M); + + SET_TX_DESC_SEQ(pdesc, 0); + + SET_TX_DESC_LINIP(pdesc, 0); + + SET_TX_DESC_QUEUE_SEL(pdesc, fw_queue); + + SET_TX_DESC_FIRST_SEG(pdesc, 1); + SET_TX_DESC_LAST_SEG(pdesc, 1); + + SET_TX_DESC_TX_BUFFER_SIZE(pdesc, (u16) (skb->len)); + + SET_TX_DESC_TX_BUFFER_ADDRESS(pdesc, mapping); + + SET_TX_DESC_RATE_ID(pdesc, 7); + SET_TX_DESC_MACID(pdesc, 0); + + SET_TX_DESC_OWN(pdesc, 1); + + SET_TX_DESC_PKT_SIZE(pdesc, (u16) (skb->len)); + + SET_TX_DESC_FIRST_SEG(pdesc, 1); + SET_TX_DESC_LAST_SEG(pdesc, 1); + + SET_TX_DESC_OFFSET(pdesc, 0x20); + + SET_TX_DESC_USE_RATE(pdesc, 1); + + if (!ieee80211_is_data_qos(fc)) { + SET_TX_DESC_HWSEQ_EN(pdesc, 1); + SET_TX_DESC_PKT_ID(pdesc, 8); + } + + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_LOUD, + "H2C Tx Cmd Content", pdesc, TX_DESC_SIZE); +} + +void rtl92ce_set_desc(u8 *pdesc, bool istx, u8 desc_name, u8 *val) +{ + if (istx) { + switch (desc_name) { + case HW_DESC_OWN: + wmb(); + SET_TX_DESC_OWN(pdesc, 1); + break; + case HW_DESC_TX_NEXTDESC_ADDR: + SET_TX_DESC_NEXT_DESC_ADDRESS(pdesc, *(u32 *) val); + break; + default: + RT_ASSERT(false, "ERR txdesc :%d not process\n", + desc_name); + break; + } + } else { + switch (desc_name) { + case HW_DESC_RXOWN: + wmb(); + SET_RX_DESC_OWN(pdesc, 1); + break; + case HW_DESC_RXBUFF_ADDR: + SET_RX_DESC_BUFF_ADDR(pdesc, *(u32 *) val); + break; + case HW_DESC_RXPKT_LEN: + SET_RX_DESC_PKT_LEN(pdesc, *(u32 *) val); + break; + case HW_DESC_RXERO: + SET_RX_DESC_EOR(pdesc, 1); + break; + default: + RT_ASSERT(false, "ERR rxdesc :%d not process\n", + desc_name); + break; + } + } +} + +u32 rtl92ce_get_desc(u8 *p_desc, bool istx, u8 desc_name) +{ + u32 ret = 0; + + if (istx) { + switch (desc_name) { + case HW_DESC_OWN: + ret = GET_TX_DESC_OWN(p_desc); + break; + case HW_DESC_TXBUFF_ADDR: + ret = GET_TX_DESC_TX_BUFFER_ADDRESS(p_desc); + break; + default: + RT_ASSERT(false, "ERR txdesc :%d not process\n", + desc_name); + break; + } + } else { + struct rx_desc_92c *pdesc = (struct rx_desc_92c *)p_desc; + switch (desc_name) { + case HW_DESC_OWN: + ret = GET_RX_DESC_OWN(pdesc); + break; + case HW_DESC_RXPKT_LEN: + ret = GET_RX_DESC_PKT_LEN(pdesc); + break; + default: + RT_ASSERT(false, "ERR rxdesc :%d not process\n", + desc_name); + break; + } + } + return ret; +} + +void rtl92ce_tx_polling(struct ieee80211_hw *hw, u8 hw_queue) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + if (hw_queue == BEACON_QUEUE) { + rtl_write_word(rtlpriv, REG_PCIE_CTRL_REG, BIT(4)); + } else { + rtl_write_word(rtlpriv, REG_PCIE_CTRL_REG, + BIT(0) << (hw_queue)); + } +} + diff --git a/drivers/net/wireless/rtlwifi/rtl8192ce/trx.h b/drivers/net/wireless/rtlwifi/rtl8192ce/trx.h new file mode 100644 index 0000000..a7cdd51 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192ce/trx.h @@ -0,0 +1,729 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CE_TRX_H__ +#define __RTL92CE_TRX_H__ + +#define TX_DESC_SIZE 64 +#define TX_DESC_AGGR_SUBFRAME_SIZE 32 + +#define RX_DESC_SIZE 32 +#define RX_DRV_INFO_SIZE_UNIT 8 + +#define TX_DESC_NEXT_DESC_OFFSET 40 +#define USB_HWDESC_HEADER_LEN 32 +#define CRCLENGTH 4 + +/* Define a macro that takes a le32 word, converts it to host ordering, + * right shifts by a specified count, creates a mask of the specified + * bit count, and extracts that number of bits. + */ + +#define SHIFT_AND_MASK_LE(__pdesc, __shift, __mask) \ + ((le32_to_cpu(*(((__le32 *)(__pdesc)))) >> (__shift)) & \ + BIT_LEN_MASK_32(__mask)) + +/* Define a macro that clears a bit field in an le32 word and + * sets the specified value into that bit field. The resulting + * value remains in le32 ordering; however, it is properly converted + * to host ordering for the clear and set operations before conversion + * back to le32. + */ + +#define SET_BITS_OFFSET_LE(__pdesc, __shift, __len, __val) \ + (*(__le32 *)(__pdesc) = \ + (cpu_to_le32((le32_to_cpu(*((__le32 *)(__pdesc))) & \ + (~(BIT_OFFSET_LEN_MASK_32((__shift), __len)))) | \ + (((u32)(__val) & BIT_LEN_MASK_32(__len)) << (__shift))))); + +/* macros to read/write various fields in RX or TX descriptors */ + +#define SET_TX_DESC_PKT_SIZE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 0, 16, __val) +#define SET_TX_DESC_OFFSET(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 16, 8, __val) +#define SET_TX_DESC_BMC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 24, 1, __val) +#define SET_TX_DESC_HTC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 25, 1, __val) +#define SET_TX_DESC_LAST_SEG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 26, 1, __val) +#define SET_TX_DESC_FIRST_SEG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 27, 1, __val) +#define SET_TX_DESC_LINIP(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 28, 1, __val) +#define SET_TX_DESC_NO_ACM(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 29, 1, __val) +#define SET_TX_DESC_GF(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 30, 1, __val) +#define SET_TX_DESC_OWN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 31, 1, __val) + +#define GET_TX_DESC_PKT_SIZE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 0, 16) +#define GET_TX_DESC_OFFSET(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 16, 8) +#define GET_TX_DESC_BMC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 24, 1) +#define GET_TX_DESC_HTC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 25, 1) +#define GET_TX_DESC_LAST_SEG(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 26, 1) +#define GET_TX_DESC_FIRST_SEG(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 27, 1) +#define GET_TX_DESC_LINIP(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 28, 1) +#define GET_TX_DESC_NO_ACM(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 29, 1) +#define GET_TX_DESC_GF(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 30, 1) +#define GET_TX_DESC_OWN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 31, 1) + +#define SET_TX_DESC_MACID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 0, 5, __val) +#define SET_TX_DESC_AGG_BREAK(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 5, 1, __val) +#define SET_TX_DESC_BK(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 6, 1, __val) +#define SET_TX_DESC_RDG_ENABLE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 7, 1, __val) +#define SET_TX_DESC_QUEUE_SEL(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 8, 5, __val) +#define SET_TX_DESC_RDG_NAV_EXT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 13, 1, __val) +#define SET_TX_DESC_LSIG_TXOP_EN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 14, 1, __val) +#define SET_TX_DESC_PIFS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 15, 1, __val) +#define SET_TX_DESC_RATE_ID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 16, 4, __val) +#define SET_TX_DESC_NAV_USE_HDR(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 20, 1, __val) +#define SET_TX_DESC_EN_DESC_ID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 21, 1, __val) +#define SET_TX_DESC_SEC_TYPE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 22, 2, __val) +#define SET_TX_DESC_PKT_OFFSET(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 24, 8, __val) + +#define GET_TX_DESC_MACID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 0, 5) +#define GET_TX_DESC_AGG_ENABLE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 5, 1) +#define GET_TX_DESC_AGG_BREAK(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 6, 1) +#define GET_TX_DESC_RDG_ENABLE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 7, 1) +#define GET_TX_DESC_QUEUE_SEL(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 8, 5) +#define GET_TX_DESC_RDG_NAV_EXT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 13, 1) +#define GET_TX_DESC_LSIG_TXOP_EN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 14, 1) +#define GET_TX_DESC_PIFS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 15, 1) +#define GET_TX_DESC_RATE_ID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 16, 4) +#define GET_TX_DESC_NAV_USE_HDR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 20, 1) +#define GET_TX_DESC_EN_DESC_ID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 21, 1) +#define GET_TX_DESC_SEC_TYPE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 22, 2) +#define GET_TX_DESC_PKT_OFFSET(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 24, 8) + +#define SET_TX_DESC_RTS_RC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 0, 6, __val) +#define SET_TX_DESC_DATA_RC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 6, 6, __val) +#define SET_TX_DESC_BAR_RTY_TH(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 14, 2, __val) +#define SET_TX_DESC_MORE_FRAG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 17, 1, __val) +#define SET_TX_DESC_RAW(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 18, 1, __val) +#define SET_TX_DESC_CCX(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 19, 1, __val) +#define SET_TX_DESC_AMPDU_DENSITY(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 20, 3, __val) +#define SET_TX_DESC_ANTSEL_A(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 24, 1, __val) +#define SET_TX_DESC_ANTSEL_B(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 25, 1, __val) +#define SET_TX_DESC_TX_ANT_CCK(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 26, 2, __val) +#define SET_TX_DESC_TX_ANTL(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 28, 2, __val) +#define SET_TX_DESC_TX_ANT_HT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 30, 2, __val) + +#define GET_TX_DESC_RTS_RC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 0, 6) +#define GET_TX_DESC_DATA_RC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 6, 6) +#define GET_TX_DESC_BAR_RTY_TH(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 14, 2) +#define GET_TX_DESC_MORE_FRAG(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 17, 1) +#define GET_TX_DESC_RAW(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 18, 1) +#define GET_TX_DESC_CCX(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 19, 1) +#define GET_TX_DESC_AMPDU_DENSITY(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 20, 3) +#define GET_TX_DESC_ANTSEL_A(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 24, 1) +#define GET_TX_DESC_ANTSEL_B(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 25, 1) +#define GET_TX_DESC_TX_ANT_CCK(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 26, 2) +#define GET_TX_DESC_TX_ANTL(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 28, 2) +#define GET_TX_DESC_TX_ANT_HT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 30, 2) + +#define SET_TX_DESC_NEXT_HEAP_PAGE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+12, 0, 8, __val) +#define SET_TX_DESC_TAIL_PAGE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+12, 8, 8, __val) +#define SET_TX_DESC_SEQ(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+12, 16, 12, __val) +#define SET_TX_DESC_PKT_ID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+12, 28, 4, __val) + +#define GET_TX_DESC_NEXT_HEAP_PAGE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 0, 8) +#define GET_TX_DESC_TAIL_PAGE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 8, 8) +#define GET_TX_DESC_SEQ(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 16, 12) +#define GET_TX_DESC_PKT_ID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 28, 4) + +#define SET_TX_DESC_RTS_RATE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 0, 5, __val) +#define SET_TX_DESC_AP_DCFE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 5, 1, __val) +#define SET_TX_DESC_QOS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 6, 1, __val) +#define SET_TX_DESC_HWSEQ_EN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 7, 1, __val) +#define SET_TX_DESC_USE_RATE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 8, 1, __val) +#define SET_TX_DESC_DISABLE_RTS_FB(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 9, 1, __val) +#define SET_TX_DESC_DISABLE_FB(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 10, 1, __val) +#define SET_TX_DESC_CTS2SELF(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 11, 1, __val) +#define SET_TX_DESC_RTS_ENABLE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 12, 1, __val) +#define SET_TX_DESC_HW_RTS_ENABLE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 13, 1, __val) +#define SET_TX_DESC_PORT_ID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 14, 1, __val) +#define SET_TX_DESC_WAIT_DCTS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 18, 1, __val) +#define SET_TX_DESC_CTS2AP_EN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 19, 1, __val) +#define SET_TX_DESC_TX_SUB_CARRIER(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 20, 2, __val) +#define SET_TX_DESC_TX_STBC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 22, 2, __val) +#define SET_TX_DESC_DATA_SHORT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 24, 1, __val) +#define SET_TX_DESC_DATA_BW(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 25, 1, __val) +#define SET_TX_DESC_RTS_SHORT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 26, 1, __val) +#define SET_TX_DESC_RTS_BW(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 27, 1, __val) +#define SET_TX_DESC_RTS_SC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 28, 2, __val) +#define SET_TX_DESC_RTS_STBC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 30, 2, __val) + +#define GET_TX_DESC_RTS_RATE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 0, 5) +#define GET_TX_DESC_AP_DCFE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 5, 1) +#define GET_TX_DESC_QOS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 6, 1) +#define GET_TX_DESC_HWSEQ_EN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 7, 1) +#define GET_TX_DESC_USE_RATE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 8, 1) +#define GET_TX_DESC_DISABLE_RTS_FB(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 9, 1) +#define GET_TX_DESC_DISABLE_FB(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 10, 1) +#define GET_TX_DESC_CTS2SELF(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 11, 1) +#define GET_TX_DESC_RTS_ENABLE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 12, 1) +#define GET_TX_DESC_HW_RTS_ENABLE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 13, 1) +#define GET_TX_DESC_PORT_ID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 14, 1) +#define GET_TX_DESC_WAIT_DCTS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 18, 1) +#define GET_TX_DESC_CTS2AP_EN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 19, 1) +#define GET_TX_DESC_TX_SUB_CARRIER(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 20, 2) +#define GET_TX_DESC_TX_STBC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 22, 2) +#define GET_TX_DESC_DATA_SHORT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 24, 1) +#define GET_TX_DESC_DATA_BW(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 25, 1) +#define GET_TX_DESC_RTS_SHORT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 26, 1) +#define GET_TX_DESC_RTS_BW(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 27, 1) +#define GET_TX_DESC_RTS_SC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 28, 2) +#define GET_TX_DESC_RTS_STBC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 30, 2) + +#define SET_TX_DESC_TX_RATE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 0, 6, __val) +#define SET_TX_DESC_DATA_SHORTGI(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 6, 1, __val) +#define SET_TX_DESC_CCX_TAG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 7, 1, __val) +#define SET_TX_DESC_DATA_RATE_FB_LIMIT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 8, 5, __val) +#define SET_TX_DESC_RTS_RATE_FB_LIMIT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 13, 4, __val) +#define SET_TX_DESC_RETRY_LIMIT_ENABLE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 17, 1, __val) +#define SET_TX_DESC_DATA_RETRY_LIMIT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 18, 6, __val) +#define SET_TX_DESC_USB_TXAGG_NUM(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 24, 8, __val) + +#define GET_TX_DESC_TX_RATE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 0, 6) +#define GET_TX_DESC_DATA_SHORTGI(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 6, 1) +#define GET_TX_DESC_CCX_TAG(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 7, 1) +#define GET_TX_DESC_DATA_RATE_FB_LIMIT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 8, 5) +#define GET_TX_DESC_RTS_RATE_FB_LIMIT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 13, 4) +#define GET_TX_DESC_RETRY_LIMIT_ENABLE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 17, 1) +#define GET_TX_DESC_DATA_RETRY_LIMIT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 18, 6) +#define GET_TX_DESC_USB_TXAGG_NUM(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 24, 8) + +#define SET_TX_DESC_TXAGC_A(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 0, 5, __val) +#define SET_TX_DESC_TXAGC_B(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 5, 5, __val) +#define SET_TX_DESC_USE_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 10, 1, __val) +#define SET_TX_DESC_MAX_AGG_NUM(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 11, 5, __val) +#define SET_TX_DESC_MCSG1_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 16, 4, __val) +#define SET_TX_DESC_MCSG2_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 20, 4, __val) +#define SET_TX_DESC_MCSG3_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 24, 4, __val) +#define SET_TX_DESC_MCS7_SGI_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 28, 4, __val) + +#define GET_TX_DESC_TXAGC_A(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 0, 5) +#define GET_TX_DESC_TXAGC_B(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 5, 5) +#define GET_TX_DESC_USE_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 10, 1) +#define GET_TX_DESC_MAX_AGG_NUM(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 11, 5) +#define GET_TX_DESC_MCSG1_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 16, 4) +#define GET_TX_DESC_MCSG2_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 20, 4) +#define GET_TX_DESC_MCSG3_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 24, 4) +#define GET_TX_DESC_MCS7_SGI_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 28, 4) + +#define SET_TX_DESC_TX_BUFFER_SIZE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+28, 0, 16, __val) +#define SET_TX_DESC_MCSG4_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+28, 16, 4, __val) +#define SET_TX_DESC_MCSG5_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+28, 20, 4, __val) +#define SET_TX_DESC_MCSG6_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+28, 24, 4, __val) +#define SET_TX_DESC_MCS15_SGI_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+28, 28, 4, __val) + +#define GET_TX_DESC_TX_BUFFER_SIZE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+28, 0, 16) +#define GET_TX_DESC_MCSG4_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+28, 16, 4) +#define GET_TX_DESC_MCSG5_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+28, 20, 4) +#define GET_TX_DESC_MCSG6_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+28, 24, 4) +#define GET_TX_DESC_MCS15_SGI_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+28, 28, 4) + +#define SET_TX_DESC_TX_BUFFER_ADDRESS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+32, 0, 32, __val) +#define SET_TX_DESC_TX_BUFFER_ADDRESS64(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+36, 0, 32, __val) + +#define GET_TX_DESC_TX_BUFFER_ADDRESS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+32, 0, 32) +#define GET_TX_DESC_TX_BUFFER_ADDRESS64(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+36, 0, 32) + +#define SET_TX_DESC_NEXT_DESC_ADDRESS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+40, 0, 32, __val) +#define SET_TX_DESC_NEXT_DESC_ADDRESS64(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+44, 0, 32, __val) + +#define GET_TX_DESC_NEXT_DESC_ADDRESS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+40, 0, 32) +#define GET_TX_DESC_NEXT_DESC_ADDRESS64(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+44, 0, 32) + +#define GET_RX_DESC_PKT_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 0, 14) +#define GET_RX_DESC_CRC32(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 14, 1) +#define GET_RX_DESC_ICV(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 15, 1) +#define GET_RX_DESC_DRV_INFO_SIZE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 16, 4) +#define GET_RX_DESC_SECURITY(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 20, 3) +#define GET_RX_DESC_QOS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 23, 1) +#define GET_RX_DESC_SHIFT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 24, 2) +#define GET_RX_DESC_PHYST(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 26, 1) +#define GET_RX_DESC_SWDEC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 27, 1) +#define GET_RX_DESC_LS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 28, 1) +#define GET_RX_DESC_FS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 29, 1) +#define GET_RX_DESC_EOR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 30, 1) +#define GET_RX_DESC_OWN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 31, 1) + +#define SET_RX_DESC_PKT_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 0, 14, __val) +#define SET_RX_DESC_EOR(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 30, 1, __val) +#define SET_RX_DESC_OWN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 31, 1, __val) + +#define GET_RX_DESC_MACID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 0, 5) +#define GET_RX_DESC_TID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 5, 4) +#define GET_RX_DESC_HWRSVD(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 9, 5) +#define GET_RX_DESC_PAGGR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 14, 1) +#define GET_RX_DESC_FAGGR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 15, 1) +#define GET_RX_DESC_A1_FIT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 16, 4) +#define GET_RX_DESC_A2_FIT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 20, 4) +#define GET_RX_DESC_PAM(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 24, 1) +#define GET_RX_DESC_PWR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 25, 1) +#define GET_RX_DESC_MD(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 26, 1) +#define GET_RX_DESC_MF(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 27, 1) +#define GET_RX_DESC_TYPE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 28, 2) +#define GET_RX_DESC_MC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 30, 1) +#define GET_RX_DESC_BC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 31, 1) +#define GET_RX_DESC_SEQ(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 0, 12) +#define GET_RX_DESC_FRAG(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 12, 4) +#define GET_RX_DESC_NEXT_PKT_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 16, 14) +#define GET_RX_DESC_NEXT_IND(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 30, 1) +#define GET_RX_DESC_RSVD(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 31, 1) + +#define GET_RX_DESC_RXMCS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 0, 6) +#define GET_RX_DESC_RXHT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 6, 1) +#define GET_RX_DESC_SPLCP(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 8, 1) +#define GET_RX_DESC_BW(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 9, 1) +#define GET_RX_DESC_HTC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 10, 1) +#define GET_RX_DESC_HWPC_ERR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 14, 1) +#define GET_RX_DESC_HWPC_IND(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 15, 1) +#define GET_RX_DESC_IV0(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 16, 16) + +#define GET_RX_DESC_IV1(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 0, 32) +#define GET_RX_DESC_TSFL(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 0, 32) + +#define GET_RX_DESC_BUFF_ADDR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 0, 32) +#define GET_RX_DESC_BUFF_ADDR64(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+28, 0, 32) + +#define SET_RX_DESC_BUFF_ADDR(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 0, 32, __val) +#define SET_RX_DESC_BUFF_ADDR64(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+28, 0, 32, __val) + +#define CLEAR_PCI_TX_DESC_CONTENT(__pdesc, _size) \ + memset(__pdesc, 0, min_t(size_t, _size, TX_DESC_NEXT_DESC_OFFSET)) + +struct rx_fwinfo_92c { + u8 gain_trsw[4]; + u8 pwdb_all; + u8 cfosho[4]; + u8 cfotail[4]; + char rxevm[2]; + char rxsnr[4]; + u8 pdsnr[2]; + u8 csi_current[2]; + u8 csi_target[2]; + u8 sigevm; + u8 max_ex_pwr; + u8 ex_intf_flag:1; + u8 sgi_en:1; + u8 rxsc:2; + u8 reserve:4; +} __packed; + +struct tx_desc_92c { + u32 pktsize:16; + u32 offset:8; + u32 bmc:1; + u32 htc:1; + u32 lastseg:1; + u32 firstseg:1; + u32 linip:1; + u32 noacm:1; + u32 gf:1; + u32 own:1; + + u32 macid:5; + u32 agg_en:1; + u32 bk:1; + u32 rdg_en:1; + u32 queuesel:5; + u32 rd_nav_ext:1; + u32 lsig_txop_en:1; + u32 pifs:1; + u32 rateid:4; + u32 nav_usehdr:1; + u32 en_descid:1; + u32 sectype:2; + u32 pktoffset:8; + + u32 rts_rc:6; + u32 data_rc:6; + u32 rsvd0:2; + u32 bar_retryht:2; + u32 rsvd1:1; + u32 morefrag:1; + u32 raw:1; + u32 ccx:1; + u32 ampdudensity:3; + u32 rsvd2:1; + u32 ant_sela:1; + u32 ant_selb:1; + u32 txant_cck:2; + u32 txant_l:2; + u32 txant_ht:2; + + u32 nextheadpage:8; + u32 tailpage:8; + u32 seq:12; + u32 pktid:4; + + u32 rtsrate:5; + u32 apdcfe:1; + u32 qos:1; + u32 hwseq_enable:1; + u32 userrate:1; + u32 dis_rtsfb:1; + u32 dis_datafb:1; + u32 cts2self:1; + u32 rts_en:1; + u32 hwrts_en:1; + u32 portid:1; + u32 rsvd3:3; + u32 waitdcts:1; + u32 cts2ap_en:1; + u32 txsc:2; + u32 stbc:2; + u32 txshort:1; + u32 txbw:1; + u32 rtsshort:1; + u32 rtsbw:1; + u32 rtssc:2; + u32 rtsstbc:2; + + u32 txrate:6; + u32 shortgi:1; + u32 ccxt:1; + u32 txrate_fb_lmt:5; + u32 rtsrate_fb_lmt:4; + u32 retrylmt_en:1; + u32 txretrylmt:6; + u32 usb_txaggnum:8; + + u32 txagca:5; + u32 txagcb:5; + u32 usemaxlen:1; + u32 maxaggnum:5; + u32 mcsg1maxlen:4; + u32 mcsg2maxlen:4; + u32 mcsg3maxlen:4; + u32 mcs7sgimaxlen:4; + + u32 txbuffersize:16; + u32 mcsg4maxlen:4; + u32 mcsg5maxlen:4; + u32 mcsg6maxlen:4; + u32 mcsg15sgimaxlen:4; + + u32 txbuffaddr; + u32 txbufferaddr64; + u32 nextdescaddress; + u32 nextdescaddress64; + + u32 reserve_pass_pcie_mm_limit[4]; +} __packed; + +struct rx_desc_92c { + u32 length:14; + u32 crc32:1; + u32 icverror:1; + u32 drv_infosize:4; + u32 security:3; + u32 qos:1; + u32 shift:2; + u32 phystatus:1; + u32 swdec:1; + u32 lastseg:1; + u32 firstseg:1; + u32 eor:1; + u32 own:1; + + u32 macid:5; + u32 tid:4; + u32 hwrsvd:5; + u32 paggr:1; + u32 faggr:1; + u32 a1_fit:4; + u32 a2_fit:4; + u32 pam:1; + u32 pwr:1; + u32 moredata:1; + u32 morefrag:1; + u32 type:2; + u32 mc:1; + u32 bc:1; + + u32 seq:12; + u32 frag:4; + u32 nextpktlen:14; + u32 nextind:1; + u32 rsvd:1; + + u32 rxmcs:6; + u32 rxht:1; + u32 amsdu:1; + u32 splcp:1; + u32 bandwidth:1; + u32 htc:1; + u32 tcpchk_rpt:1; + u32 ipcchk_rpt:1; + u32 tcpchk_valid:1; + u32 hwpcerr:1; + u32 hwpcind:1; + u32 iv0:16; + + u32 iv1; + + u32 tsfl; + + u32 bufferaddress; + u32 bufferaddress64; + +} __packed; + +void rtl92ce_tx_fill_desc(struct ieee80211_hw *hw, + struct ieee80211_hdr *hdr, + u8 *pdesc, struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff *skb, u8 hw_queue, + struct rtl_tcb_desc *ptcb_desc); +bool rtl92ce_rx_query_desc(struct ieee80211_hw *hw, + struct rtl_stats *stats, + struct ieee80211_rx_status *rx_status, + u8 *pdesc, struct sk_buff *skb); +void rtl92ce_set_desc(u8 *pdesc, bool istx, u8 desc_name, u8 *val); +u32 rtl92ce_get_desc(u8 *pdesc, bool istx, u8 desc_name); +void rtl92ce_tx_polling(struct ieee80211_hw *hw, u8 hw_queue); +void rtl92ce_tx_fill_cmddesc(struct ieee80211_hw *hw, u8 *pdesc, + bool b_firstseg, bool b_lastseg, + struct sk_buff *skb); +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/Makefile b/drivers/net/wireless/rtlwifi/rtl8192cu/Makefile new file mode 100644 index 0000000..e73840f --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/Makefile @@ -0,0 +1,14 @@ +rtl8192cu-objs := \ + dm.o \ + hw.o \ + led.o \ + mac.o \ + phy.o \ + rf.o \ + sw.o \ + table.o \ + trx.o + +obj-$(CPTCFG_RTL8192CU) += rtl8192cu.o + +ccflags-y += -D__CHECK_ENDIAN__ diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/def.h b/drivers/net/wireless/rtlwifi/rtl8192cu/def.h new file mode 100644 index 0000000..f916555 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/def.h @@ -0,0 +1,58 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../rtl8192ce/def.h" + +/*------------------------------------------------------------------------- + * Chip specific + *-------------------------------------------------------------------------*/ +#define CHIP_8723 BIT(2) /* RTL8723 With BT feature */ +#define CHIP_8723_DRV_REV BIT(3) /* RTL8723 Driver Revised */ +#define NORMAL_CHIP BIT(4) +#define CHIP_VENDOR_UMC BIT(5) +#define CHIP_VENDOR_UMC_B_CUT BIT(6) + +#define IS_NORMAL_CHIP(version) \ + (((version) & NORMAL_CHIP) ? true : false) + +#define IS_8723_SERIES(version) \ + (((version) & CHIP_8723) ? true : false) + +#define IS_92C_1T2R(version) \ + (((version) & CHIP_92C) && ((version) & CHIP_92C_1T2R)) + +#define IS_VENDOR_UMC(version) \ + (((version) & CHIP_VENDOR_UMC) ? true : false) + +#define IS_VENDOR_8723_A_CUT(version) \ + (((version) & CHIP_VENDOR_UMC) ? (((version) & (BIT(6))) ? \ + false : true) : false) + +#define CHIP_BONDING_92C_1T2R 0x1 +#define CHIP_BONDING_IDENTIFIER(_value) (((_value) >> 22) & 0x3) diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/dm.c b/drivers/net/wireless/rtlwifi/rtl8192cu/dm.c new file mode 100644 index 0000000..16a0b9e --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/dm.c @@ -0,0 +1,107 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../base.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "dm.h" + +void rtl92cu_dm_dynamic_txpower(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + long undec_sm_pwdb; + + if (!rtlpriv->dm.dynamic_txpower_enable) + return; + + if (rtlpriv->dm.dm_flag & HAL_DM_HIPWR_DISABLE) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + return; + } + + if ((mac->link_state < MAC80211_LINKED) && + (rtlpriv->dm.entry_min_undec_sm_pwdb == 0)) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_TRACE, + "Not connected to any\n"); + + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + + rtlpriv->dm.last_dtp_lvl = TXHIGHPWRLEVEL_NORMAL; + return; + } + + if (mac->link_state >= MAC80211_LINKED) { + if (mac->opmode == NL80211_IFTYPE_ADHOC) { + undec_sm_pwdb = rtlpriv->dm.entry_min_undec_sm_pwdb; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "AP Client PWDB = 0x%lx\n", + undec_sm_pwdb); + } else { + undec_sm_pwdb = rtlpriv->dm.undec_sm_pwdb; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "STA Default Port PWDB = 0x%lx\n", + undec_sm_pwdb); + } + } else { + undec_sm_pwdb = rtlpriv->dm.entry_min_undec_sm_pwdb; + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "AP Ext Port PWDB = 0x%lx\n", + undec_sm_pwdb); + } + + if (undec_sm_pwdb >= TX_POWER_NEAR_FIELD_THRESH_LVL2) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_LEVEL1; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_LEVEL1 (TxPwr=0x0)\n"); + } else if ((undec_sm_pwdb < (TX_POWER_NEAR_FIELD_THRESH_LVL2 - 3)) && + (undec_sm_pwdb >= TX_POWER_NEAR_FIELD_THRESH_LVL1)) { + + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_LEVEL1; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_LEVEL1 (TxPwr=0x10)\n"); + } else if (undec_sm_pwdb < (TX_POWER_NEAR_FIELD_THRESH_LVL1 - 5)) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_NORMAL\n"); + } + + if ((rtlpriv->dm.dynamic_txhighpower_lvl != rtlpriv->dm.last_dtp_lvl)) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "PHY_SetTxPowerLevel8192S() Channel = %d\n", + rtlphy->current_channel); + rtl92c_phy_set_txpower_level(hw, rtlphy->current_channel); + } + + rtlpriv->dm.last_dtp_lvl = rtlpriv->dm.dynamic_txhighpower_lvl; +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/dm.h b/drivers/net/wireless/rtlwifi/rtl8192cu/dm.h new file mode 100644 index 0000000..d947e7d --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/dm.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../rtl8192ce/dm.h" + +void rtl92cu_dm_dynamic_txpower(struct ieee80211_hw *hw); diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/hw.c b/drivers/net/wireless/rtlwifi/rtl8192cu/hw.c new file mode 100644 index 0000000..189ba12 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/hw.c @@ -0,0 +1,2357 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../efuse.h" +#include "../base.h" +#include "../cam.h" +#include "../ps.h" +#include "../usb.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "mac.h" +#include "dm.h" +#include "hw.h" +#include "../rtl8192ce/hw.h" +#include "trx.h" +#include "led.h" +#include "table.h" + +static void _rtl92cu_phy_param_tab_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtlpriv); + + rtlphy->hwparam_tables[MAC_REG].length = RTL8192CUMAC_2T_ARRAYLENGTH; + rtlphy->hwparam_tables[MAC_REG].pdata = RTL8192CUMAC_2T_ARRAY; + if (IS_HIGHT_PA(rtlefuse->board_type)) { + rtlphy->hwparam_tables[PHY_REG_PG].length = + RTL8192CUPHY_REG_Array_PG_HPLength; + rtlphy->hwparam_tables[PHY_REG_PG].pdata = + RTL8192CUPHY_REG_Array_PG_HP; + } else { + rtlphy->hwparam_tables[PHY_REG_PG].length = + RTL8192CUPHY_REG_ARRAY_PGLENGTH; + rtlphy->hwparam_tables[PHY_REG_PG].pdata = + RTL8192CUPHY_REG_ARRAY_PG; + } + /* 2T */ + rtlphy->hwparam_tables[PHY_REG_2T].length = + RTL8192CUPHY_REG_2TARRAY_LENGTH; + rtlphy->hwparam_tables[PHY_REG_2T].pdata = + RTL8192CUPHY_REG_2TARRAY; + rtlphy->hwparam_tables[RADIOA_2T].length = + RTL8192CURADIOA_2TARRAYLENGTH; + rtlphy->hwparam_tables[RADIOA_2T].pdata = + RTL8192CURADIOA_2TARRAY; + rtlphy->hwparam_tables[RADIOB_2T].length = + RTL8192CURADIOB_2TARRAYLENGTH; + rtlphy->hwparam_tables[RADIOB_2T].pdata = + RTL8192CU_RADIOB_2TARRAY; + rtlphy->hwparam_tables[AGCTAB_2T].length = + RTL8192CUAGCTAB_2TARRAYLENGTH; + rtlphy->hwparam_tables[AGCTAB_2T].pdata = + RTL8192CUAGCTAB_2TARRAY; + /* 1T */ + if (IS_HIGHT_PA(rtlefuse->board_type)) { + rtlphy->hwparam_tables[PHY_REG_1T].length = + RTL8192CUPHY_REG_1T_HPArrayLength; + rtlphy->hwparam_tables[PHY_REG_1T].pdata = + RTL8192CUPHY_REG_1T_HPArray; + rtlphy->hwparam_tables[RADIOA_1T].length = + RTL8192CURadioA_1T_HPArrayLength; + rtlphy->hwparam_tables[RADIOA_1T].pdata = + RTL8192CURadioA_1T_HPArray; + rtlphy->hwparam_tables[RADIOB_1T].length = + RTL8192CURADIOB_1TARRAYLENGTH; + rtlphy->hwparam_tables[RADIOB_1T].pdata = + RTL8192CU_RADIOB_1TARRAY; + rtlphy->hwparam_tables[AGCTAB_1T].length = + RTL8192CUAGCTAB_1T_HPArrayLength; + rtlphy->hwparam_tables[AGCTAB_1T].pdata = + Rtl8192CUAGCTAB_1T_HPArray; + } else { + rtlphy->hwparam_tables[PHY_REG_1T].length = + RTL8192CUPHY_REG_1TARRAY_LENGTH; + rtlphy->hwparam_tables[PHY_REG_1T].pdata = + RTL8192CUPHY_REG_1TARRAY; + rtlphy->hwparam_tables[RADIOA_1T].length = + RTL8192CURADIOA_1TARRAYLENGTH; + rtlphy->hwparam_tables[RADIOA_1T].pdata = + RTL8192CU_RADIOA_1TARRAY; + rtlphy->hwparam_tables[RADIOB_1T].length = + RTL8192CURADIOB_1TARRAYLENGTH; + rtlphy->hwparam_tables[RADIOB_1T].pdata = + RTL8192CU_RADIOB_1TARRAY; + rtlphy->hwparam_tables[AGCTAB_1T].length = + RTL8192CUAGCTAB_1TARRAYLENGTH; + rtlphy->hwparam_tables[AGCTAB_1T].pdata = + RTL8192CUAGCTAB_1TARRAY; + } +} + +static void _rtl92cu_read_txpower_info_from_hwpg(struct ieee80211_hw *hw, + bool autoload_fail, + u8 *hwinfo) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 rf_path, index, tempval; + u16 i; + + for (rf_path = 0; rf_path < 2; rf_path++) { + for (i = 0; i < 3; i++) { + if (!autoload_fail) { + rtlefuse-> + eeprom_chnlarea_txpwr_cck[rf_path][i] = + hwinfo[EEPROM_TXPOWERCCK + rf_path * 3 + i]; + rtlefuse-> + eeprom_chnlarea_txpwr_ht40_1s[rf_path][i] = + hwinfo[EEPROM_TXPOWERHT40_1S + rf_path * 3 + + i]; + } else { + rtlefuse-> + eeprom_chnlarea_txpwr_cck[rf_path][i] = + EEPROM_DEFAULT_TXPOWERLEVEL; + rtlefuse-> + eeprom_chnlarea_txpwr_ht40_1s[rf_path][i] = + EEPROM_DEFAULT_TXPOWERLEVEL; + } + } + } + for (i = 0; i < 3; i++) { + if (!autoload_fail) + tempval = hwinfo[EEPROM_TXPOWERHT40_2SDIFF + i]; + else + tempval = EEPROM_DEFAULT_HT40_2SDIFF; + rtlefuse->eprom_chnl_txpwr_ht40_2sdf[RF90_PATH_A][i] = + (tempval & 0xf); + rtlefuse->eprom_chnl_txpwr_ht40_2sdf[RF90_PATH_B][i] = + ((tempval & 0xf0) >> 4); + } + for (rf_path = 0; rf_path < 2; rf_path++) + for (i = 0; i < 3; i++) + RTPRINT(rtlpriv, FINIT, INIT_EEPROM, + "RF(%d) EEPROM CCK Area(%d) = 0x%x\n", + rf_path, i, + rtlefuse-> + eeprom_chnlarea_txpwr_cck[rf_path][i]); + for (rf_path = 0; rf_path < 2; rf_path++) + for (i = 0; i < 3; i++) + RTPRINT(rtlpriv, FINIT, INIT_EEPROM, + "RF(%d) EEPROM HT40 1S Area(%d) = 0x%x\n", + rf_path, i, + rtlefuse-> + eeprom_chnlarea_txpwr_ht40_1s[rf_path][i]); + for (rf_path = 0; rf_path < 2; rf_path++) + for (i = 0; i < 3; i++) + RTPRINT(rtlpriv, FINIT, INIT_EEPROM, + "RF(%d) EEPROM HT40 2S Diff Area(%d) = 0x%x\n", + rf_path, i, + rtlefuse-> + eprom_chnl_txpwr_ht40_2sdf[rf_path][i]); + for (rf_path = 0; rf_path < 2; rf_path++) { + for (i = 0; i < 14; i++) { + index = _rtl92c_get_chnl_group((u8) i); + rtlefuse->txpwrlevel_cck[rf_path][i] = + rtlefuse->eeprom_chnlarea_txpwr_cck[rf_path][index]; + rtlefuse->txpwrlevel_ht40_1s[rf_path][i] = + rtlefuse-> + eeprom_chnlarea_txpwr_ht40_1s[rf_path][index]; + if ((rtlefuse-> + eeprom_chnlarea_txpwr_ht40_1s[rf_path][index] - + rtlefuse-> + eprom_chnl_txpwr_ht40_2sdf[rf_path][index]) + > 0) { + rtlefuse->txpwrlevel_ht40_2s[rf_path][i] = + rtlefuse-> + eeprom_chnlarea_txpwr_ht40_1s[rf_path] + [index] - rtlefuse-> + eprom_chnl_txpwr_ht40_2sdf[rf_path] + [index]; + } else { + rtlefuse->txpwrlevel_ht40_2s[rf_path][i] = 0; + } + } + for (i = 0; i < 14; i++) { + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF(%d)-Ch(%d) [CCK / HT40_1S / HT40_2S] = [0x%x / 0x%x / 0x%x]\n", rf_path, i, + rtlefuse->txpwrlevel_cck[rf_path][i], + rtlefuse->txpwrlevel_ht40_1s[rf_path][i], + rtlefuse->txpwrlevel_ht40_2s[rf_path][i]); + } + } + for (i = 0; i < 3; i++) { + if (!autoload_fail) { + rtlefuse->eeprom_pwrlimit_ht40[i] = + hwinfo[EEPROM_TXPWR_GROUP + i]; + rtlefuse->eeprom_pwrlimit_ht20[i] = + hwinfo[EEPROM_TXPWR_GROUP + 3 + i]; + } else { + rtlefuse->eeprom_pwrlimit_ht40[i] = 0; + rtlefuse->eeprom_pwrlimit_ht20[i] = 0; + } + } + for (rf_path = 0; rf_path < 2; rf_path++) { + for (i = 0; i < 14; i++) { + index = _rtl92c_get_chnl_group((u8) i); + if (rf_path == RF90_PATH_A) { + rtlefuse->pwrgroup_ht20[rf_path][i] = + (rtlefuse->eeprom_pwrlimit_ht20[index] + & 0xf); + rtlefuse->pwrgroup_ht40[rf_path][i] = + (rtlefuse->eeprom_pwrlimit_ht40[index] + & 0xf); + } else if (rf_path == RF90_PATH_B) { + rtlefuse->pwrgroup_ht20[rf_path][i] = + ((rtlefuse->eeprom_pwrlimit_ht20[index] + & 0xf0) >> 4); + rtlefuse->pwrgroup_ht40[rf_path][i] = + ((rtlefuse->eeprom_pwrlimit_ht40[index] + & 0xf0) >> 4); + } + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-%d pwrgroup_ht20[%d] = 0x%x\n", + rf_path, i, + rtlefuse->pwrgroup_ht20[rf_path][i]); + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-%d pwrgroup_ht40[%d] = 0x%x\n", + rf_path, i, + rtlefuse->pwrgroup_ht40[rf_path][i]); + } + } + for (i = 0; i < 14; i++) { + index = _rtl92c_get_chnl_group((u8) i); + if (!autoload_fail) + tempval = hwinfo[EEPROM_TXPOWERHT20DIFF + index]; + else + tempval = EEPROM_DEFAULT_HT20_DIFF; + rtlefuse->txpwr_ht20diff[RF90_PATH_A][i] = (tempval & 0xF); + rtlefuse->txpwr_ht20diff[RF90_PATH_B][i] = + ((tempval >> 4) & 0xF); + if (rtlefuse->txpwr_ht20diff[RF90_PATH_A][i] & BIT(3)) + rtlefuse->txpwr_ht20diff[RF90_PATH_A][i] |= 0xF0; + if (rtlefuse->txpwr_ht20diff[RF90_PATH_B][i] & BIT(3)) + rtlefuse->txpwr_ht20diff[RF90_PATH_B][i] |= 0xF0; + index = _rtl92c_get_chnl_group((u8) i); + if (!autoload_fail) + tempval = hwinfo[EEPROM_TXPOWER_OFDMDIFF + index]; + else + tempval = EEPROM_DEFAULT_LEGACYHTTXPOWERDIFF; + rtlefuse->txpwr_legacyhtdiff[RF90_PATH_A][i] = (tempval & 0xF); + rtlefuse->txpwr_legacyhtdiff[RF90_PATH_B][i] = + ((tempval >> 4) & 0xF); + } + rtlefuse->legacy_ht_txpowerdiff = + rtlefuse->txpwr_legacyhtdiff[RF90_PATH_A][7]; + for (i = 0; i < 14; i++) + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-A Ht20 to HT40 Diff[%d] = 0x%x\n", + i, rtlefuse->txpwr_ht20diff[RF90_PATH_A][i]); + for (i = 0; i < 14; i++) + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-A Legacy to Ht40 Diff[%d] = 0x%x\n", + i, rtlefuse->txpwr_legacyhtdiff[RF90_PATH_A][i]); + for (i = 0; i < 14; i++) + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-B Ht20 to HT40 Diff[%d] = 0x%x\n", + i, rtlefuse->txpwr_ht20diff[RF90_PATH_B][i]); + for (i = 0; i < 14; i++) + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-B Legacy to HT40 Diff[%d] = 0x%x\n", + i, rtlefuse->txpwr_legacyhtdiff[RF90_PATH_B][i]); + if (!autoload_fail) + rtlefuse->eeprom_regulatory = (hwinfo[RF_OPTION1] & 0x7); + else + rtlefuse->eeprom_regulatory = 0; + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "eeprom_regulatory = 0x%x\n", rtlefuse->eeprom_regulatory); + if (!autoload_fail) { + rtlefuse->eeprom_tssi[RF90_PATH_A] = hwinfo[EEPROM_TSSI_A]; + rtlefuse->eeprom_tssi[RF90_PATH_B] = hwinfo[EEPROM_TSSI_B]; + } else { + rtlefuse->eeprom_tssi[RF90_PATH_A] = EEPROM_DEFAULT_TSSI; + rtlefuse->eeprom_tssi[RF90_PATH_B] = EEPROM_DEFAULT_TSSI; + } + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "TSSI_A = 0x%x, TSSI_B = 0x%x\n", + rtlefuse->eeprom_tssi[RF90_PATH_A], + rtlefuse->eeprom_tssi[RF90_PATH_B]); + if (!autoload_fail) + tempval = hwinfo[EEPROM_THERMAL_METER]; + else + tempval = EEPROM_DEFAULT_THERMALMETER; + rtlefuse->eeprom_thermalmeter = (tempval & 0x1f); + if (rtlefuse->eeprom_thermalmeter < 0x06 || + rtlefuse->eeprom_thermalmeter > 0x1c) + rtlefuse->eeprom_thermalmeter = 0x12; + if (rtlefuse->eeprom_thermalmeter == 0x1f || autoload_fail) + rtlefuse->apk_thermalmeterignore = true; + rtlefuse->thermalmeter[0] = rtlefuse->eeprom_thermalmeter; + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "thermalmeter = 0x%x\n", rtlefuse->eeprom_thermalmeter); +} + +static void _rtl92cu_read_board_type(struct ieee80211_hw *hw, u8 *contents) +{ + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 boardType; + + if (IS_NORMAL_CHIP(rtlhal->version)) { + boardType = ((contents[EEPROM_RF_OPT1]) & + BOARD_TYPE_NORMAL_MASK) >> 5; /*bit[7:5]*/ + } else { + boardType = contents[EEPROM_RF_OPT4]; + boardType &= BOARD_TYPE_TEST_MASK; + } + rtlefuse->board_type = boardType; + if (IS_HIGHT_PA(rtlefuse->board_type)) + rtlefuse->external_pa = 1; + pr_info("Board Type %x\n", rtlefuse->board_type); +} + +static void _rtl92cu_read_adapter_info(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u16 i, usvalue; + u8 hwinfo[HWSET_MAX_SIZE] = {0}; + u16 eeprom_id; + + if (rtlefuse->epromtype == EEPROM_BOOT_EFUSE) { + rtl_efuse_shadow_map_update(hw); + memcpy((void *)hwinfo, + (void *)&rtlefuse->efuse_map[EFUSE_INIT_MAP][0], + HWSET_MAX_SIZE); + } else if (rtlefuse->epromtype == EEPROM_93C46) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "RTL819X Not boot from eeprom, check it !!\n"); + } + RT_PRINT_DATA(rtlpriv, COMP_INIT, DBG_LOUD, "MAP", + hwinfo, HWSET_MAX_SIZE); + eeprom_id = le16_to_cpu(*((__le16 *)&hwinfo[0])); + if (eeprom_id != RTL8190_EEPROM_ID) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "EEPROM ID(%#x) is invalid!!\n", eeprom_id); + rtlefuse->autoload_failflag = true; + } else { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Autoload OK\n"); + rtlefuse->autoload_failflag = false; + } + if (rtlefuse->autoload_failflag) + return; + for (i = 0; i < 6; i += 2) { + usvalue = *(u16 *)&hwinfo[EEPROM_MAC_ADDR + i]; + *((u16 *) (&rtlefuse->dev_addr[i])) = usvalue; + } + pr_info("MAC address: %pM\n", rtlefuse->dev_addr); + _rtl92cu_read_txpower_info_from_hwpg(hw, + rtlefuse->autoload_failflag, hwinfo); + rtlefuse->eeprom_vid = le16_to_cpu(*(__le16 *)&hwinfo[EEPROM_VID]); + rtlefuse->eeprom_did = le16_to_cpu(*(__le16 *)&hwinfo[EEPROM_DID]); + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, " VID = 0x%02x PID = 0x%02x\n", + rtlefuse->eeprom_vid, rtlefuse->eeprom_did); + rtlefuse->eeprom_channelplan = hwinfo[EEPROM_CHANNELPLAN]; + rtlefuse->eeprom_version = + le16_to_cpu(*(__le16 *)&hwinfo[EEPROM_VERSION]); + rtlefuse->txpwr_fromeprom = true; + rtlefuse->eeprom_oemid = hwinfo[EEPROM_CUSTOMER_ID]; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "EEPROM Customer ID: 0x%2x\n", + rtlefuse->eeprom_oemid); + if (rtlhal->oem_id == RT_CID_DEFAULT) { + switch (rtlefuse->eeprom_oemid) { + case EEPROM_CID_DEFAULT: + if (rtlefuse->eeprom_did == 0x8176) { + if ((rtlefuse->eeprom_svid == 0x103C && + rtlefuse->eeprom_smid == 0x1629)) + rtlhal->oem_id = RT_CID_819x_HP; + else + rtlhal->oem_id = RT_CID_DEFAULT; + } else { + rtlhal->oem_id = RT_CID_DEFAULT; + } + break; + case EEPROM_CID_TOSHIBA: + rtlhal->oem_id = RT_CID_TOSHIBA; + break; + case EEPROM_CID_QMI: + rtlhal->oem_id = RT_CID_819x_QMI; + break; + case EEPROM_CID_WHQL: + default: + rtlhal->oem_id = RT_CID_DEFAULT; + break; + } + } + _rtl92cu_read_board_type(hw, hwinfo); +} + +static void _rtl92cu_hal_customized_behavior(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_usb_priv *usb_priv = rtl_usbpriv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + switch (rtlhal->oem_id) { + case RT_CID_819x_HP: + usb_priv->ledctl.led_opendrain = true; + break; + case RT_CID_819x_Lenovo: + case RT_CID_DEFAULT: + case RT_CID_TOSHIBA: + case RT_CID_CCX: + case RT_CID_819x_Acer: + case RT_CID_WHQL: + default: + break; + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "RT Customized ID: 0x%02X\n", + rtlhal->oem_id); +} + +void rtl92cu_read_eeprom_info(struct ieee80211_hw *hw) +{ + + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 tmp_u1b; + + if (!IS_NORMAL_CHIP(rtlhal->version)) + return; + tmp_u1b = rtl_read_byte(rtlpriv, REG_9346CR); + rtlefuse->epromtype = (tmp_u1b & BOOT_FROM_EEPROM) ? + EEPROM_93C46 : EEPROM_BOOT_EFUSE; + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "Boot from %s\n", + tmp_u1b & BOOT_FROM_EEPROM ? "EERROM" : "EFUSE"); + rtlefuse->autoload_failflag = (tmp_u1b & EEPROM_EN) ? false : true; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Autoload %s\n", + tmp_u1b & EEPROM_EN ? "OK!!" : "ERR!!"); + _rtl92cu_read_adapter_info(hw); + _rtl92cu_hal_customized_behavior(hw); + return; +} + +static int _rtl92cu_init_power_on(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int status = 0; + u16 value16; + u8 value8; + /* polling autoload done. */ + u32 pollingCount = 0; + + do { + if (rtl_read_byte(rtlpriv, REG_APS_FSMCO) & PFM_ALDN) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "Autoload Done!\n"); + break; + } + if (pollingCount++ > 100) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_EMERG, + "Failed to polling REG_APS_FSMCO[PFM_ALDN] done!\n"); + return -ENODEV; + } + } while (true); + /* 0. RSV_CTRL 0x1C[7:0] = 0 unlock ISO/CLK/Power control register */ + rtl_write_byte(rtlpriv, REG_RSV_CTRL, 0x0); + /* Power on when re-enter from IPS/Radio off/card disable */ + /* enable SPS into PWM mode */ + rtl_write_byte(rtlpriv, REG_SPS0_CTRL, 0x2b); + udelay(100); + value8 = rtl_read_byte(rtlpriv, REG_LDOV12D_CTRL); + if (0 == (value8 & LDV12_EN)) { + value8 |= LDV12_EN; + rtl_write_byte(rtlpriv, REG_LDOV12D_CTRL, value8); + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + " power-on :REG_LDOV12D_CTRL Reg0x21:0x%02x\n", + value8); + udelay(100); + value8 = rtl_read_byte(rtlpriv, REG_SYS_ISO_CTRL); + value8 &= ~ISO_MD2PP; + rtl_write_byte(rtlpriv, REG_SYS_ISO_CTRL, value8); + } + /* auto enable WLAN */ + pollingCount = 0; + value16 = rtl_read_word(rtlpriv, REG_APS_FSMCO); + value16 |= APFM_ONMAC; + rtl_write_word(rtlpriv, REG_APS_FSMCO, value16); + do { + if (!(rtl_read_word(rtlpriv, REG_APS_FSMCO) & APFM_ONMAC)) { + pr_info("MAC auto ON okay!\n"); + break; + } + if (pollingCount++ > 100) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_EMERG, + "Failed to polling REG_APS_FSMCO[APFM_ONMAC] done!\n"); + return -ENODEV; + } + } while (true); + /* Enable Radio ,GPIO ,and LED function */ + rtl_write_word(rtlpriv, REG_APS_FSMCO, 0x0812); + /* release RF digital isolation */ + value16 = rtl_read_word(rtlpriv, REG_SYS_ISO_CTRL); + value16 &= ~ISO_DIOR; + rtl_write_word(rtlpriv, REG_SYS_ISO_CTRL, value16); + /* Reconsider when to do this operation after asking HWSD. */ + pollingCount = 0; + rtl_write_byte(rtlpriv, REG_APSD_CTRL, (rtl_read_byte(rtlpriv, + REG_APSD_CTRL) & ~BIT(6))); + do { + pollingCount++; + } while ((pollingCount < 200) && + (rtl_read_byte(rtlpriv, REG_APSD_CTRL) & BIT(7))); + /* Enable MAC DMA/WMAC/SCHEDULE/SEC block */ + value16 = rtl_read_word(rtlpriv, REG_CR); + value16 |= (HCI_TXDMA_EN | HCI_RXDMA_EN | TXDMA_EN | RXDMA_EN | + PROTOCOL_EN | SCHEDULE_EN | MACTXEN | MACRXEN | ENSEC); + rtl_write_word(rtlpriv, REG_CR, value16); + return status; +} + +static void _rtl92cu_init_queue_reserved_page(struct ieee80211_hw *hw, + bool wmm_enable, + u8 out_ep_num, + u8 queue_sel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + bool isChipN = IS_NORMAL_CHIP(rtlhal->version); + u32 outEPNum = (u32)out_ep_num; + u32 numHQ = 0; + u32 numLQ = 0; + u32 numNQ = 0; + u32 numPubQ; + u32 value32; + u8 value8; + u32 txQPageNum, txQPageUnit, txQRemainPage; + + if (!wmm_enable) { + numPubQ = (isChipN) ? CHIP_B_PAGE_NUM_PUBQ : + CHIP_A_PAGE_NUM_PUBQ; + txQPageNum = TX_TOTAL_PAGE_NUMBER - numPubQ; + + txQPageUnit = txQPageNum/outEPNum; + txQRemainPage = txQPageNum % outEPNum; + if (queue_sel & TX_SELE_HQ) + numHQ = txQPageUnit; + if (queue_sel & TX_SELE_LQ) + numLQ = txQPageUnit; + /* HIGH priority queue always present in the configuration of + * 2 out-ep. Remainder pages have assigned to High queue */ + if ((outEPNum > 1) && (txQRemainPage)) + numHQ += txQRemainPage; + /* NOTE: This step done before writting REG_RQPN. */ + if (isChipN) { + if (queue_sel & TX_SELE_NQ) + numNQ = txQPageUnit; + value8 = (u8)_NPQ(numNQ); + rtl_write_byte(rtlpriv, REG_RQPN_NPQ, value8); + } + } else { + /* for WMM ,number of out-ep must more than or equal to 2! */ + numPubQ = isChipN ? WMM_CHIP_B_PAGE_NUM_PUBQ : + WMM_CHIP_A_PAGE_NUM_PUBQ; + if (queue_sel & TX_SELE_HQ) { + numHQ = isChipN ? WMM_CHIP_B_PAGE_NUM_HPQ : + WMM_CHIP_A_PAGE_NUM_HPQ; + } + if (queue_sel & TX_SELE_LQ) { + numLQ = isChipN ? WMM_CHIP_B_PAGE_NUM_LPQ : + WMM_CHIP_A_PAGE_NUM_LPQ; + } + /* NOTE: This step done before writting REG_RQPN. */ + if (isChipN) { + if (queue_sel & TX_SELE_NQ) + numNQ = WMM_CHIP_B_PAGE_NUM_NPQ; + value8 = (u8)_NPQ(numNQ); + rtl_write_byte(rtlpriv, REG_RQPN_NPQ, value8); + } + } + /* TX DMA */ + value32 = _HPQ(numHQ) | _LPQ(numLQ) | _PUBQ(numPubQ) | LD_RQPN; + rtl_write_dword(rtlpriv, REG_RQPN, value32); +} + +static void _rtl92c_init_trx_buffer(struct ieee80211_hw *hw, bool wmm_enable) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 txpktbuf_bndy; + u8 value8; + + if (!wmm_enable) + txpktbuf_bndy = TX_PAGE_BOUNDARY; + else /* for WMM */ + txpktbuf_bndy = (IS_NORMAL_CHIP(rtlhal->version)) + ? WMM_CHIP_B_TX_PAGE_BOUNDARY + : WMM_CHIP_A_TX_PAGE_BOUNDARY; + rtl_write_byte(rtlpriv, REG_TXPKTBUF_BCNQ_BDNY, txpktbuf_bndy); + rtl_write_byte(rtlpriv, REG_TXPKTBUF_MGQ_BDNY, txpktbuf_bndy); + rtl_write_byte(rtlpriv, REG_TXPKTBUF_WMAC_LBK_BF_HD, txpktbuf_bndy); + rtl_write_byte(rtlpriv, REG_TRXFF_BNDY, txpktbuf_bndy); + rtl_write_byte(rtlpriv, REG_TDECTRL+1, txpktbuf_bndy); + rtl_write_word(rtlpriv, (REG_TRXFF_BNDY + 2), 0x27FF); + value8 = _PSRX(RX_PAGE_SIZE_REG_VALUE) | _PSTX(PBP_128); + rtl_write_byte(rtlpriv, REG_PBP, value8); +} + +static void _rtl92c_init_chipN_reg_priority(struct ieee80211_hw *hw, u16 beQ, + u16 bkQ, u16 viQ, u16 voQ, + u16 mgtQ, u16 hiQ) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u16 value16 = (rtl_read_word(rtlpriv, REG_TRXDMA_CTRL) & 0x7); + + value16 |= _TXDMA_BEQ_MAP(beQ) | _TXDMA_BKQ_MAP(bkQ) | + _TXDMA_VIQ_MAP(viQ) | _TXDMA_VOQ_MAP(voQ) | + _TXDMA_MGQ_MAP(mgtQ) | _TXDMA_HIQ_MAP(hiQ); + rtl_write_word(rtlpriv, REG_TRXDMA_CTRL, value16); +} + +static void _rtl92cu_init_chipN_one_out_ep_priority(struct ieee80211_hw *hw, + bool wmm_enable, + u8 queue_sel) +{ + u16 uninitialized_var(value); + + switch (queue_sel) { + case TX_SELE_HQ: + value = QUEUE_HIGH; + break; + case TX_SELE_LQ: + value = QUEUE_LOW; + break; + case TX_SELE_NQ: + value = QUEUE_NORMAL; + break; + default: + WARN_ON(1); /* Shall not reach here! */ + break; + } + _rtl92c_init_chipN_reg_priority(hw, value, value, value, value, + value, value); + pr_info("Tx queue select: 0x%02x\n", queue_sel); +} + +static void _rtl92cu_init_chipN_two_out_ep_priority(struct ieee80211_hw *hw, + bool wmm_enable, + u8 queue_sel) +{ + u16 beQ, bkQ, viQ, voQ, mgtQ, hiQ; + u16 uninitialized_var(valueHi); + u16 uninitialized_var(valueLow); + + switch (queue_sel) { + case (TX_SELE_HQ | TX_SELE_LQ): + valueHi = QUEUE_HIGH; + valueLow = QUEUE_LOW; + break; + case (TX_SELE_NQ | TX_SELE_LQ): + valueHi = QUEUE_NORMAL; + valueLow = QUEUE_LOW; + break; + case (TX_SELE_HQ | TX_SELE_NQ): + valueHi = QUEUE_HIGH; + valueLow = QUEUE_NORMAL; + break; + default: + WARN_ON(1); + break; + } + if (!wmm_enable) { + beQ = valueLow; + bkQ = valueLow; + viQ = valueHi; + voQ = valueHi; + mgtQ = valueHi; + hiQ = valueHi; + } else {/* for WMM ,CONFIG_OUT_EP_WIFI_MODE */ + beQ = valueHi; + bkQ = valueLow; + viQ = valueLow; + voQ = valueHi; + mgtQ = valueHi; + hiQ = valueHi; + } + _rtl92c_init_chipN_reg_priority(hw, beQ, bkQ, viQ, voQ, mgtQ, hiQ); + pr_info("Tx queue select: 0x%02x\n", queue_sel); +} + +static void _rtl92cu_init_chipN_three_out_ep_priority(struct ieee80211_hw *hw, + bool wmm_enable, + u8 queue_sel) +{ + u16 beQ, bkQ, viQ, voQ, mgtQ, hiQ; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (!wmm_enable) { /* typical setting */ + beQ = QUEUE_LOW; + bkQ = QUEUE_LOW; + viQ = QUEUE_NORMAL; + voQ = QUEUE_HIGH; + mgtQ = QUEUE_HIGH; + hiQ = QUEUE_HIGH; + } else { /* for WMM */ + beQ = QUEUE_LOW; + bkQ = QUEUE_NORMAL; + viQ = QUEUE_NORMAL; + voQ = QUEUE_HIGH; + mgtQ = QUEUE_HIGH; + hiQ = QUEUE_HIGH; + } + _rtl92c_init_chipN_reg_priority(hw, beQ, bkQ, viQ, voQ, mgtQ, hiQ); + RT_TRACE(rtlpriv, COMP_INIT, DBG_EMERG, "Tx queue select :0x%02x..\n", + queue_sel); +} + +static void _rtl92cu_init_chipN_queue_priority(struct ieee80211_hw *hw, + bool wmm_enable, + u8 out_ep_num, + u8 queue_sel) +{ + switch (out_ep_num) { + case 1: + _rtl92cu_init_chipN_one_out_ep_priority(hw, wmm_enable, + queue_sel); + break; + case 2: + _rtl92cu_init_chipN_two_out_ep_priority(hw, wmm_enable, + queue_sel); + break; + case 3: + _rtl92cu_init_chipN_three_out_ep_priority(hw, wmm_enable, + queue_sel); + break; + default: + WARN_ON(1); /* Shall not reach here! */ + break; + } +} + +static void _rtl92cu_init_chipT_queue_priority(struct ieee80211_hw *hw, + bool wmm_enable, + u8 out_ep_num, + u8 queue_sel) +{ + u8 hq_sele = 0; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + switch (out_ep_num) { + case 2: /* (TX_SELE_HQ|TX_SELE_LQ) */ + if (!wmm_enable) /* typical setting */ + hq_sele = HQSEL_VOQ | HQSEL_VIQ | HQSEL_MGTQ | + HQSEL_HIQ; + else /* for WMM */ + hq_sele = HQSEL_VOQ | HQSEL_BEQ | HQSEL_MGTQ | + HQSEL_HIQ; + break; + case 1: + if (TX_SELE_LQ == queue_sel) { + /* map all endpoint to Low queue */ + hq_sele = 0; + } else if (TX_SELE_HQ == queue_sel) { + /* map all endpoint to High queue */ + hq_sele = HQSEL_VOQ | HQSEL_VIQ | HQSEL_BEQ | + HQSEL_BKQ | HQSEL_MGTQ | HQSEL_HIQ; + } + break; + default: + WARN_ON(1); /* Shall not reach here! */ + break; + } + rtl_write_byte(rtlpriv, (REG_TRXDMA_CTRL+1), hq_sele); + RT_TRACE(rtlpriv, COMP_INIT, DBG_EMERG, "Tx queue select :0x%02x..\n", + hq_sele); +} + +static void _rtl92cu_init_queue_priority(struct ieee80211_hw *hw, + bool wmm_enable, + u8 out_ep_num, + u8 queue_sel) +{ + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + if (IS_NORMAL_CHIP(rtlhal->version)) + _rtl92cu_init_chipN_queue_priority(hw, wmm_enable, out_ep_num, + queue_sel); + else + _rtl92cu_init_chipT_queue_priority(hw, wmm_enable, out_ep_num, + queue_sel); +} + +static void _rtl92cu_init_usb_aggregation(struct ieee80211_hw *hw) +{ +} + +static void _rtl92cu_init_wmac_setting(struct ieee80211_hw *hw) +{ + u16 value16; + + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + + mac->rx_conf = (RCR_APM | RCR_AM | RCR_ADF | RCR_AB | RCR_APPFCS | + RCR_APP_ICV | RCR_AMF | RCR_HTC_LOC_CTRL | + RCR_APP_MIC | RCR_APP_PHYSTS | RCR_ACRC32); + rtl_write_dword(rtlpriv, REG_RCR, mac->rx_conf); + /* Accept all multicast address */ + rtl_write_dword(rtlpriv, REG_MAR, 0xFFFFFFFF); + rtl_write_dword(rtlpriv, REG_MAR + 4, 0xFFFFFFFF); + /* Accept all management frames */ + value16 = 0xFFFF; + rtl92c_set_mgt_filter(hw, value16); + /* Reject all control frame - default value is 0 */ + rtl92c_set_ctrl_filter(hw, 0x0); + /* Accept all data frames */ + value16 = 0xFFFF; + rtl92c_set_data_filter(hw, value16); +} + +static int _rtl92cu_init_mac(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_usb_priv *usb_priv = rtl_usbpriv(hw); + struct rtl_usb *rtlusb = rtl_usbdev(usb_priv); + int err = 0; + u32 boundary = 0; + u8 wmm_enable = false; /* TODO */ + u8 out_ep_nums = rtlusb->out_ep_nums; + u8 queue_sel = rtlusb->out_queue_sel; + err = _rtl92cu_init_power_on(hw); + + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Failed to init power on!\n"); + return err; + } + if (!wmm_enable) { + boundary = TX_PAGE_BOUNDARY; + } else { /* for WMM */ + boundary = (IS_NORMAL_CHIP(rtlhal->version)) + ? WMM_CHIP_B_TX_PAGE_BOUNDARY + : WMM_CHIP_A_TX_PAGE_BOUNDARY; + } + if (false == rtl92c_init_llt_table(hw, boundary)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Failed to init LLT Table!\n"); + return -EINVAL; + } + _rtl92cu_init_queue_reserved_page(hw, wmm_enable, out_ep_nums, + queue_sel); + _rtl92c_init_trx_buffer(hw, wmm_enable); + _rtl92cu_init_queue_priority(hw, wmm_enable, out_ep_nums, + queue_sel); + /* Get Rx PHY status in order to report RSSI and others. */ + rtl92c_init_driver_info_size(hw, RTL92C_DRIVER_INFO_SIZE); + rtl92c_init_interrupt(hw); + rtl92c_init_network_type(hw); + _rtl92cu_init_wmac_setting(hw); + rtl92c_init_adaptive_ctrl(hw); + rtl92c_init_edca(hw); + rtl92c_init_rate_fallback(hw); + rtl92c_init_retry_function(hw); + _rtl92cu_init_usb_aggregation(hw); + rtlpriv->cfg->ops->set_bw_mode(hw, NL80211_CHAN_HT20); + rtl92c_set_min_space(hw, IS_92C_SERIAL(rtlhal->version)); + rtl92c_init_beacon_parameters(hw, rtlhal->version); + rtl92c_init_ampdu_aggregation(hw); + rtl92c_init_beacon_max_error(hw, true); + return err; +} + +void rtl92cu_enable_hw_security_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 sec_reg_value = 0x0; + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "PairwiseEncAlgorithm = %d GroupEncAlgorithm = %d\n", + rtlpriv->sec.pairwise_enc_algorithm, + rtlpriv->sec.group_enc_algorithm); + if (rtlpriv->cfg->mod_params->sw_crypto || rtlpriv->sec.use_sw_sec) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "not open sw encryption\n"); + return; + } + sec_reg_value = SCR_TxEncEnable | SCR_RxDecEnable; + if (rtlpriv->sec.use_defaultkey) { + sec_reg_value |= SCR_TxUseDK; + sec_reg_value |= SCR_RxUseDK; + } + if (IS_NORMAL_CHIP(rtlhal->version)) + sec_reg_value |= (SCR_RXBCUSEDK | SCR_TXBCUSEDK); + rtl_write_byte(rtlpriv, REG_CR + 1, 0x02); + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, "The SECR-value %x\n", + sec_reg_value); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_WPA_CONFIG, &sec_reg_value); +} + +static void _rtl92cu_hw_configure(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + + /* To Fix MAC loopback mode fail. */ + rtl_write_byte(rtlpriv, REG_LDOHCI12_CTRL, 0x0f); + rtl_write_byte(rtlpriv, 0x15, 0xe9); + /* HW SEQ CTRL */ + /* set 0x0 to 0xFF by tynli. Default enable HW SEQ NUM. */ + rtl_write_byte(rtlpriv, REG_HWSEQ_CTRL, 0xFF); + /* fixed USB interface interference issue */ + rtl_write_byte(rtlpriv, 0xfe40, 0xe0); + rtl_write_byte(rtlpriv, 0xfe41, 0x8d); + rtl_write_byte(rtlpriv, 0xfe42, 0x80); + rtlusb->reg_bcn_ctrl_val = 0x18; + rtl_write_byte(rtlpriv, REG_BCN_CTRL, (u8)rtlusb->reg_bcn_ctrl_val); +} + +static void _InitPABias(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 pa_setting; + + /* FIXED PA current issue */ + pa_setting = efuse_read_1byte(hw, 0x1FA); + if (!(pa_setting & BIT(0))) { + rtl_set_rfreg(hw, RF90_PATH_A, 0x15, 0x0FFFFF, 0x0F406); + rtl_set_rfreg(hw, RF90_PATH_A, 0x15, 0x0FFFFF, 0x4F406); + rtl_set_rfreg(hw, RF90_PATH_A, 0x15, 0x0FFFFF, 0x8F406); + rtl_set_rfreg(hw, RF90_PATH_A, 0x15, 0x0FFFFF, 0xCF406); + } + if (!(pa_setting & BIT(1)) && IS_NORMAL_CHIP(rtlhal->version) && + IS_92C_SERIAL(rtlhal->version)) { + rtl_set_rfreg(hw, RF90_PATH_B, 0x15, 0x0FFFFF, 0x0F406); + rtl_set_rfreg(hw, RF90_PATH_B, 0x15, 0x0FFFFF, 0x4F406); + rtl_set_rfreg(hw, RF90_PATH_B, 0x15, 0x0FFFFF, 0x8F406); + rtl_set_rfreg(hw, RF90_PATH_B, 0x15, 0x0FFFFF, 0xCF406); + } + if (!(pa_setting & BIT(4))) { + pa_setting = rtl_read_byte(rtlpriv, 0x16); + pa_setting &= 0x0F; + rtl_write_byte(rtlpriv, 0x16, pa_setting | 0x90); + } +} + +static void _update_mac_setting(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + + mac->rx_conf = rtl_read_dword(rtlpriv, REG_RCR); + mac->rx_mgt_filter = rtl_read_word(rtlpriv, REG_RXFLTMAP0); + mac->rx_ctrl_filter = rtl_read_word(rtlpriv, REG_RXFLTMAP1); + mac->rx_data_filter = rtl_read_word(rtlpriv, REG_RXFLTMAP2); +} + +int rtl92cu_hw_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + int err = 0; + static bool iqk_initialized; + + rtlhal->hw_type = HARDWARE_TYPE_RTL8192CU; + err = _rtl92cu_init_mac(hw); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "init mac failed!\n"); + return err; + } + err = rtl92c_download_fw(hw); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Failed to download FW. Init HW without FW now..\n"); + err = 1; + return err; + } + rtlhal->last_hmeboxnum = 0; /* h2c */ + _rtl92cu_phy_param_tab_init(hw); + rtl92cu_phy_mac_config(hw); + rtl92cu_phy_bb_config(hw); + rtlphy->rf_mode = RF_OP_BY_SW_3WIRE; + rtl92c_phy_rf_config(hw); + if (IS_VENDOR_UMC_A_CUT(rtlhal->version) && + !IS_92C_SERIAL(rtlhal->version)) { + rtl_set_rfreg(hw, RF90_PATH_A, RF_RX_G1, MASKDWORD, 0x30255); + rtl_set_rfreg(hw, RF90_PATH_A, RF_RX_G2, MASKDWORD, 0x50a00); + } + rtlphy->rfreg_chnlval[0] = rtl_get_rfreg(hw, (enum radio_path)0, + RF_CHNLBW, RFREG_OFFSET_MASK); + rtlphy->rfreg_chnlval[1] = rtl_get_rfreg(hw, (enum radio_path)1, + RF_CHNLBW, RFREG_OFFSET_MASK); + rtl92cu_bb_block_on(hw); + rtl_cam_reset_all_entry(hw); + rtl92cu_enable_hw_security_config(hw); + ppsc->rfpwr_state = ERFON; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_ETHER_ADDR, mac->mac_addr); + if (ppsc->rfpwr_state == ERFON) { + rtl92c_phy_set_rfpath_switch(hw, 1); + if (iqk_initialized) { + rtl92c_phy_iq_calibrate(hw, false); + } else { + rtl92c_phy_iq_calibrate(hw, false); + iqk_initialized = true; + } + rtl92c_dm_check_txpower_tracking(hw); + rtl92c_phy_lc_calibrate(hw); + } + _rtl92cu_hw_configure(hw); + _InitPABias(hw); + _update_mac_setting(hw); + rtl92c_dm_init(hw); + return err; +} + +static void _DisableRFAFEAndResetBB(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); +/************************************** +a. TXPAUSE 0x522[7:0] = 0xFF Pause MAC TX queue +b. RF path 0 offset 0x00 = 0x00 disable RF +c. APSD_CTRL 0x600[7:0] = 0x40 +d. SYS_FUNC_EN 0x02[7:0] = 0x16 reset BB state machine +e. SYS_FUNC_EN 0x02[7:0] = 0x14 reset BB state machine +***************************************/ + u8 eRFPath = 0, value8 = 0; + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0xFF); + rtl_set_rfreg(hw, (enum radio_path)eRFPath, 0x0, MASKBYTE0, 0x0); + + value8 |= APSDOFF; + rtl_write_byte(rtlpriv, REG_APSD_CTRL, value8); /*0x40*/ + value8 = 0; + value8 |= (FEN_USBD | FEN_USBA | FEN_BB_GLB_RSTn); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, value8);/*0x16*/ + value8 &= (~FEN_BB_GLB_RSTn); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, value8); /*0x14*/ +} + +static void _ResetDigitalProcedure1(struct ieee80211_hw *hw, bool bWithoutHWSM) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (rtlhal->fw_version <= 0x20) { + /***************************** + f. MCUFWDL 0x80[7:0]=0 reset MCU ready status + g. SYS_FUNC_EN 0x02[10]= 0 reset MCU reg, (8051 reset) + h. SYS_FUNC_EN 0x02[15-12]= 5 reset MAC reg, DCORE + i. SYS_FUNC_EN 0x02[10]= 1 enable MCU reg, (8051 enable) + ******************************/ + u16 valu16 = 0; + + rtl_write_byte(rtlpriv, REG_MCUFWDL, 0); + valu16 = rtl_read_word(rtlpriv, REG_SYS_FUNC_EN); + rtl_write_word(rtlpriv, REG_SYS_FUNC_EN, (valu16 & + (~FEN_CPUEN))); /* reset MCU ,8051 */ + valu16 = rtl_read_word(rtlpriv, REG_SYS_FUNC_EN)&0x0FFF; + rtl_write_word(rtlpriv, REG_SYS_FUNC_EN, (valu16 | + (FEN_HWPDN|FEN_ELDR))); /* reset MAC */ + valu16 = rtl_read_word(rtlpriv, REG_SYS_FUNC_EN); + rtl_write_word(rtlpriv, REG_SYS_FUNC_EN, (valu16 | + FEN_CPUEN)); /* enable MCU ,8051 */ + } else { + u8 retry_cnts = 0; + + /* IF fw in RAM code, do reset */ + if (rtl_read_byte(rtlpriv, REG_MCUFWDL) & BIT(1)) { + /* reset MCU ready status */ + rtl_write_byte(rtlpriv, REG_MCUFWDL, 0); + /* 8051 reset by self */ + rtl_write_byte(rtlpriv, REG_HMETFR+3, 0x20); + while ((retry_cnts++ < 100) && + (FEN_CPUEN & rtl_read_word(rtlpriv, + REG_SYS_FUNC_EN))) { + udelay(50); + } + if (retry_cnts >= 100) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "#####=> 8051 reset failed!.........................\n"); + /* if 8051 reset fail, reset MAC. */ + rtl_write_byte(rtlpriv, + REG_SYS_FUNC_EN + 1, + 0x50); + udelay(100); + } + } + /* Reset MAC and Enable 8051 */ + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN + 1, 0x54); + rtl_write_byte(rtlpriv, REG_MCUFWDL, 0); + } + if (bWithoutHWSM) { + /***************************** + Without HW auto state machine + g.SYS_CLKR 0x08[15:0] = 0x30A3 disable MAC clock + h.AFE_PLL_CTRL 0x28[7:0] = 0x80 disable AFE PLL + i.AFE_XTAL_CTRL 0x24[15:0] = 0x880F gated AFE DIG_CLOCK + j.SYS_ISu_CTRL 0x00[7:0] = 0xF9 isolated digital to PON + ******************************/ + rtl_write_word(rtlpriv, REG_SYS_CLKR, 0x70A3); + rtl_write_byte(rtlpriv, REG_AFE_PLL_CTRL, 0x80); + rtl_write_word(rtlpriv, REG_AFE_XTAL_CTRL, 0x880F); + rtl_write_byte(rtlpriv, REG_SYS_ISO_CTRL, 0xF9); + } +} + +static void _ResetDigitalProcedure2(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); +/***************************** +k. SYS_FUNC_EN 0x03[7:0] = 0x44 disable ELDR runction +l. SYS_CLKR 0x08[15:0] = 0x3083 disable ELDR clock +m. SYS_ISO_CTRL 0x01[7:0] = 0x83 isolated ELDR to PON +******************************/ + rtl_write_word(rtlpriv, REG_SYS_CLKR, 0x70A3); + rtl_write_byte(rtlpriv, REG_SYS_ISO_CTRL+1, 0x82); +} + +static void _DisableGPIO(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); +/*************************************** +j. GPIO_PIN_CTRL 0x44[31:0]=0x000 +k. Value = GPIO_PIN_CTRL[7:0] +l. GPIO_PIN_CTRL 0x44[31:0] = 0x00FF0000 | (value <<8); write ext PIN level +m. GPIO_MUXCFG 0x42 [15:0] = 0x0780 +n. LEDCFG 0x4C[15:0] = 0x8080 +***************************************/ + u8 value8; + u16 value16; + u32 value32; + + /* 1. Disable GPIO[7:0] */ + rtl_write_word(rtlpriv, REG_GPIO_PIN_CTRL+2, 0x0000); + value32 = rtl_read_dword(rtlpriv, REG_GPIO_PIN_CTRL) & 0xFFFF00FF; + value8 = (u8) (value32&0x000000FF); + value32 |= ((value8<<8) | 0x00FF0000); + rtl_write_dword(rtlpriv, REG_GPIO_PIN_CTRL, value32); + /* 2. Disable GPIO[10:8] */ + rtl_write_byte(rtlpriv, REG_GPIO_MUXCFG+3, 0x00); + value16 = rtl_read_word(rtlpriv, REG_GPIO_MUXCFG+2) & 0xFF0F; + value8 = (u8) (value16&0x000F); + value16 |= ((value8<<4) | 0x0780); + rtl_write_word(rtlpriv, REG_GPIO_PIN_CTRL+2, value16); + /* 3. Disable LED0 & 1 */ + rtl_write_word(rtlpriv, REG_LEDCFG0, 0x8080); +} + +static void _DisableAnalog(struct ieee80211_hw *hw, bool bWithoutHWSM) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u16 value16 = 0; + u8 value8 = 0; + + if (bWithoutHWSM) { + /***************************** + n. LDOA15_CTRL 0x20[7:0] = 0x04 disable A15 power + o. LDOV12D_CTRL 0x21[7:0] = 0x54 disable digital core power + r. When driver call disable, the ASIC will turn off remaining + clock automatically + ******************************/ + rtl_write_byte(rtlpriv, REG_LDOA15_CTRL, 0x04); + value8 = rtl_read_byte(rtlpriv, REG_LDOV12D_CTRL); + value8 &= (~LDV12_EN); + rtl_write_byte(rtlpriv, REG_LDOV12D_CTRL, value8); + } + +/***************************** +h. SPS0_CTRL 0x11[7:0] = 0x23 enter PFM mode +i. APS_FSMCO 0x04[15:0] = 0x4802 set USB suspend +******************************/ + rtl_write_byte(rtlpriv, REG_SPS0_CTRL, 0x23); + value16 |= (APDM_HOST | AFSM_HSUS | PFM_ALDN); + rtl_write_word(rtlpriv, REG_APS_FSMCO, (u16)value16); + rtl_write_byte(rtlpriv, REG_RSV_CTRL, 0x0E); +} + +static void _CardDisableHWSM(struct ieee80211_hw *hw) +{ + /* ==== RF Off Sequence ==== */ + _DisableRFAFEAndResetBB(hw); + /* ==== Reset digital sequence ====== */ + _ResetDigitalProcedure1(hw, false); + /* ==== Pull GPIO PIN to balance level and LED control ====== */ + _DisableGPIO(hw); + /* ==== Disable analog sequence === */ + _DisableAnalog(hw, false); +} + +static void _CardDisableWithoutHWSM(struct ieee80211_hw *hw) +{ + /*==== RF Off Sequence ==== */ + _DisableRFAFEAndResetBB(hw); + /* ==== Reset digital sequence ====== */ + _ResetDigitalProcedure1(hw, true); + /* ==== Pull GPIO PIN to balance level and LED control ====== */ + _DisableGPIO(hw); + /* ==== Reset digital sequence ====== */ + _ResetDigitalProcedure2(hw); + /* ==== Disable analog sequence === */ + _DisableAnalog(hw, true); +} + +static void _rtl92cu_set_bcn_ctrl_reg(struct ieee80211_hw *hw, + u8 set_bits, u8 clear_bits) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + + rtlusb->reg_bcn_ctrl_val |= set_bits; + rtlusb->reg_bcn_ctrl_val &= ~clear_bits; + rtl_write_byte(rtlpriv, REG_BCN_CTRL, (u8) rtlusb->reg_bcn_ctrl_val); +} + +static void _rtl92cu_stop_tx_beacon(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + u8 tmp1byte = 0; + if (IS_NORMAL_CHIP(rtlhal->version)) { + tmp1byte = rtl_read_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2); + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, + tmp1byte & (~BIT(6))); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 1, 0x64); + tmp1byte = rtl_read_byte(rtlpriv, REG_TBTT_PROHIBIT + 2); + tmp1byte &= ~(BIT(0)); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 2, tmp1byte); + } else { + rtl_write_byte(rtlpriv, REG_TXPAUSE, + rtl_read_byte(rtlpriv, REG_TXPAUSE) | BIT(6)); + } +} + +static void _rtl92cu_resume_tx_beacon(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + u8 tmp1byte = 0; + + if (IS_NORMAL_CHIP(rtlhal->version)) { + tmp1byte = rtl_read_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2); + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, + tmp1byte | BIT(6)); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 1, 0xff); + tmp1byte = rtl_read_byte(rtlpriv, REG_TBTT_PROHIBIT + 2); + tmp1byte |= BIT(0); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 2, tmp1byte); + } else { + rtl_write_byte(rtlpriv, REG_TXPAUSE, + rtl_read_byte(rtlpriv, REG_TXPAUSE) & (~BIT(6))); + } +} + +static void _rtl92cu_enable_bcn_sub_func(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + + if (IS_NORMAL_CHIP(rtlhal->version)) + _rtl92cu_set_bcn_ctrl_reg(hw, 0, BIT(1)); + else + _rtl92cu_set_bcn_ctrl_reg(hw, 0, BIT(4)); +} + +static void _rtl92cu_disable_bcn_sub_func(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + + if (IS_NORMAL_CHIP(rtlhal->version)) + _rtl92cu_set_bcn_ctrl_reg(hw, BIT(1), 0); + else + _rtl92cu_set_bcn_ctrl_reg(hw, BIT(4), 0); +} + +static int _rtl92cu_set_media_status(struct ieee80211_hw *hw, + enum nl80211_iftype type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 bt_msr = rtl_read_byte(rtlpriv, MSR); + enum led_ctl_mode ledaction = LED_CTL_NO_LINK; + + bt_msr &= 0xfc; + rtl_write_byte(rtlpriv, REG_BCN_MAX_ERR, 0xFF); + if (type == NL80211_IFTYPE_UNSPECIFIED || type == + NL80211_IFTYPE_STATION) { + _rtl92cu_stop_tx_beacon(hw); + _rtl92cu_enable_bcn_sub_func(hw); + } else if (type == NL80211_IFTYPE_ADHOC || type == NL80211_IFTYPE_AP) { + _rtl92cu_resume_tx_beacon(hw); + _rtl92cu_disable_bcn_sub_func(hw); + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Set HW_VAR_MEDIA_STATUS:No such media status(%x)\n", + type); + } + switch (type) { + case NL80211_IFTYPE_UNSPECIFIED: + bt_msr |= MSR_NOLINK; + ledaction = LED_CTL_LINK; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to NO LINK!\n"); + break; + case NL80211_IFTYPE_ADHOC: + bt_msr |= MSR_ADHOC; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to Ad Hoc!\n"); + break; + case NL80211_IFTYPE_STATION: + bt_msr |= MSR_INFRA; + ledaction = LED_CTL_LINK; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to STA!\n"); + break; + case NL80211_IFTYPE_AP: + bt_msr |= MSR_AP; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to AP!\n"); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Network type %d not supported!\n", type); + goto error_out; + } + rtl_write_byte(rtlpriv, (MSR), bt_msr); + rtlpriv->cfg->ops->led_control(hw, ledaction); + if ((bt_msr & 0xfc) == MSR_AP) + rtl_write_byte(rtlpriv, REG_BCNTCFG + 1, 0x00); + else + rtl_write_byte(rtlpriv, REG_BCNTCFG + 1, 0x66); + return 0; +error_out: + return 1; +} + +void rtl92cu_card_disable(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + enum nl80211_iftype opmode; + + mac->link_state = MAC80211_NOLINK; + opmode = NL80211_IFTYPE_UNSPECIFIED; + _rtl92cu_set_media_status(hw, opmode); + rtlpriv->cfg->ops->led_control(hw, LED_CTL_POWER_OFF); + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + if (rtlusb->disableHWSM) + _CardDisableHWSM(hw); + else + _CardDisableWithoutHWSM(hw); +} + +void rtl92cu_set_check_bssid(struct ieee80211_hw *hw, bool check_bssid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + u32 reg_rcr = rtl_read_dword(rtlpriv, REG_RCR); + + if (rtlpriv->psc.rfpwr_state != ERFON) + return; + + if (check_bssid) { + u8 tmp; + if (IS_NORMAL_CHIP(rtlhal->version)) { + reg_rcr |= (RCR_CBSSID_DATA | RCR_CBSSID_BCN); + tmp = BIT(4); + } else { + reg_rcr |= RCR_CBSSID; + tmp = BIT(4) | BIT(5); + } + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_RCR, + (u8 *) (®_rcr)); + _rtl92cu_set_bcn_ctrl_reg(hw, 0, tmp); + } else { + u8 tmp; + if (IS_NORMAL_CHIP(rtlhal->version)) { + reg_rcr &= ~(RCR_CBSSID_DATA | RCR_CBSSID_BCN); + tmp = BIT(4); + } else { + reg_rcr &= ~RCR_CBSSID; + tmp = BIT(4) | BIT(5); + } + reg_rcr &= (~(RCR_CBSSID_DATA | RCR_CBSSID_BCN)); + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_RCR, (u8 *) (®_rcr)); + _rtl92cu_set_bcn_ctrl_reg(hw, tmp, 0); + } +} + +/*========================================================================== */ + +int rtl92cu_set_network_type(struct ieee80211_hw *hw, enum nl80211_iftype type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (_rtl92cu_set_media_status(hw, type)) + return -EOPNOTSUPP; + + if (rtlpriv->mac80211.link_state == MAC80211_LINKED) { + if (type != NL80211_IFTYPE_AP) + rtl92cu_set_check_bssid(hw, true); + } else { + rtl92cu_set_check_bssid(hw, false); + } + + return 0; +} + +static void _InitBeaconParameters(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + + rtl_write_word(rtlpriv, REG_BCN_CTRL, 0x1010); + + /* TODO: Remove these magic number */ + rtl_write_word(rtlpriv, REG_TBTT_PROHIBIT, 0x6404); + rtl_write_byte(rtlpriv, REG_DRVERLYINT, DRIVER_EARLY_INT_TIME); + rtl_write_byte(rtlpriv, REG_BCNDMATIM, BCN_DMA_ATIME_INT_TIME); + /* Change beacon AIFS to the largest number + * beacause test chip does not contension before sending beacon. */ + if (IS_NORMAL_CHIP(rtlhal->version)) + rtl_write_word(rtlpriv, REG_BCNTCFG, 0x660F); + else + rtl_write_word(rtlpriv, REG_BCNTCFG, 0x66FF); +} + +static void _beacon_function_enable(struct ieee80211_hw *hw, bool Enable, + bool Linked) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + _rtl92cu_set_bcn_ctrl_reg(hw, (BIT(4) | BIT(3) | BIT(1)), 0x00); + rtl_write_byte(rtlpriv, REG_RD_CTRL+1, 0x6F); +} + +void rtl92cu_set_beacon_related_registers(struct ieee80211_hw *hw) +{ + + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 bcn_interval, atim_window; + u32 value32; + + bcn_interval = mac->beacon_interval; + atim_window = 2; /*FIX MERGE */ + rtl_write_word(rtlpriv, REG_ATIMWND, atim_window); + rtl_write_word(rtlpriv, REG_BCN_INTERVAL, bcn_interval); + _InitBeaconParameters(hw); + rtl_write_byte(rtlpriv, REG_SLOT, 0x09); + /* + * Force beacon frame transmission even after receiving beacon frame + * from other ad hoc STA + * + * + * Reset TSF Timer to zero, added by Roger. 2008.06.24 + */ + value32 = rtl_read_dword(rtlpriv, REG_TCR); + value32 &= ~TSFRST; + rtl_write_dword(rtlpriv, REG_TCR, value32); + value32 |= TSFRST; + rtl_write_dword(rtlpriv, REG_TCR, value32); + RT_TRACE(rtlpriv, COMP_INIT|COMP_BEACON, DBG_LOUD, + "SetBeaconRelatedRegisters8192CUsb(): Set TCR(%x)\n", + value32); + /* TODO: Modify later (Find the right parameters) + * NOTE: Fix test chip's bug (about contention windows's randomness) */ + if ((mac->opmode == NL80211_IFTYPE_ADHOC) || + (mac->opmode == NL80211_IFTYPE_AP)) { + rtl_write_byte(rtlpriv, REG_RXTSF_OFFSET_CCK, 0x50); + rtl_write_byte(rtlpriv, REG_RXTSF_OFFSET_OFDM, 0x50); + } + _beacon_function_enable(hw, true, true); +} + +void rtl92cu_set_beacon_interval(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 bcn_interval = mac->beacon_interval; + + RT_TRACE(rtlpriv, COMP_BEACON, DBG_DMESG, "beacon_interval:%d\n", + bcn_interval); + rtl_write_word(rtlpriv, REG_BCN_INTERVAL, bcn_interval); +} + +void rtl92cu_update_interrupt_mask(struct ieee80211_hw *hw, + u32 add_msr, u32 rm_msr) +{ +} + +void rtl92cu_get_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + + switch (variable) { + case HW_VAR_RCR: + *((u32 *)(val)) = mac->rx_conf; + break; + case HW_VAR_RF_STATE: + *((enum rf_pwrstate *)(val)) = ppsc->rfpwr_state; + break; + case HW_VAR_FWLPS_RF_ON:{ + enum rf_pwrstate rfState; + u32 val_rcr; + + rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_RF_STATE, + (u8 *)(&rfState)); + if (rfState == ERFOFF) { + *((bool *) (val)) = true; + } else { + val_rcr = rtl_read_dword(rtlpriv, REG_RCR); + val_rcr &= 0x00070000; + if (val_rcr) + *((bool *) (val)) = false; + else + *((bool *) (val)) = true; + } + break; + } + case HW_VAR_FW_PSMODE_STATUS: + *((bool *) (val)) = ppsc->fw_current_inpsmode; + break; + case HW_VAR_CORRECT_TSF:{ + u64 tsf; + u32 *ptsf_low = (u32 *)&tsf; + u32 *ptsf_high = ((u32 *)&tsf) + 1; + + *ptsf_high = rtl_read_dword(rtlpriv, (REG_TSFTR + 4)); + *ptsf_low = rtl_read_dword(rtlpriv, REG_TSFTR); + *((u64 *)(val)) = tsf; + break; + } + case HW_VAR_MGT_FILTER: + *((u16 *) (val)) = rtl_read_word(rtlpriv, REG_RXFLTMAP0); + break; + case HW_VAR_CTRL_FILTER: + *((u16 *) (val)) = rtl_read_word(rtlpriv, REG_RXFLTMAP1); + break; + case HW_VAR_DATA_FILTER: + *((u16 *) (val)) = rtl_read_word(rtlpriv, REG_RXFLTMAP2); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } +} + +void rtl92cu_set_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + enum wireless_mode wirelessmode = mac->mode; + u8 idx = 0; + + switch (variable) { + case HW_VAR_ETHER_ADDR:{ + for (idx = 0; idx < ETH_ALEN; idx++) { + rtl_write_byte(rtlpriv, (REG_MACID + idx), + val[idx]); + } + break; + } + case HW_VAR_BASIC_RATE:{ + u16 rate_cfg = ((u16 *) val)[0]; + u8 rate_index = 0; + + rate_cfg &= 0x15f; + /* TODO */ + /* if (mac->current_network.vender == HT_IOT_PEER_CISCO + * && ((rate_cfg & 0x150) == 0)) { + * rate_cfg |= 0x010; + * } */ + rate_cfg |= 0x01; + rtl_write_byte(rtlpriv, REG_RRSR, rate_cfg & 0xff); + rtl_write_byte(rtlpriv, REG_RRSR + 1, + (rate_cfg >> 8) & 0xff); + while (rate_cfg > 0x1) { + rate_cfg >>= 1; + rate_index++; + } + rtl_write_byte(rtlpriv, REG_INIRTS_RATE_SEL, + rate_index); + break; + } + case HW_VAR_BSSID:{ + for (idx = 0; idx < ETH_ALEN; idx++) { + rtl_write_byte(rtlpriv, (REG_BSSID + idx), + val[idx]); + } + break; + } + case HW_VAR_SIFS:{ + rtl_write_byte(rtlpriv, REG_SIFS_CCK + 1, val[0]); + rtl_write_byte(rtlpriv, REG_SIFS_OFDM + 1, val[1]); + rtl_write_byte(rtlpriv, REG_SPEC_SIFS + 1, val[0]); + rtl_write_byte(rtlpriv, REG_MAC_SPEC_SIFS + 1, val[0]); + rtl_write_byte(rtlpriv, REG_R2T_SIFS+1, val[0]); + rtl_write_byte(rtlpriv, REG_T2T_SIFS+1, val[0]); + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, "HW_VAR_SIFS\n"); + break; + } + case HW_VAR_SLOT_TIME:{ + u8 e_aci; + u8 QOS_MODE = 1; + + rtl_write_byte(rtlpriv, REG_SLOT, val[0]); + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "HW_VAR_SLOT_TIME %x\n", val[0]); + if (QOS_MODE) { + for (e_aci = 0; e_aci < AC_MAX; e_aci++) + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_AC_PARAM, + &e_aci); + } else { + u8 sifstime = 0; + u8 u1bAIFS; + + if (IS_WIRELESS_MODE_A(wirelessmode) || + IS_WIRELESS_MODE_N_24G(wirelessmode) || + IS_WIRELESS_MODE_N_5G(wirelessmode)) + sifstime = 16; + else + sifstime = 10; + u1bAIFS = sifstime + (2 * val[0]); + rtl_write_byte(rtlpriv, REG_EDCA_VO_PARAM, + u1bAIFS); + rtl_write_byte(rtlpriv, REG_EDCA_VI_PARAM, + u1bAIFS); + rtl_write_byte(rtlpriv, REG_EDCA_BE_PARAM, + u1bAIFS); + rtl_write_byte(rtlpriv, REG_EDCA_BK_PARAM, + u1bAIFS); + } + break; + } + case HW_VAR_ACK_PREAMBLE:{ + u8 reg_tmp; + u8 short_preamble = (bool)*val; + reg_tmp = 0; + if (short_preamble) + reg_tmp |= 0x80; + rtl_write_byte(rtlpriv, REG_RRSR + 2, reg_tmp); + break; + } + case HW_VAR_AMPDU_MIN_SPACE:{ + u8 min_spacing_to_set; + u8 sec_min_space; + + min_spacing_to_set = *val; + if (min_spacing_to_set <= 7) { + switch (rtlpriv->sec.pairwise_enc_algorithm) { + case NO_ENCRYPTION: + case AESCCMP_ENCRYPTION: + sec_min_space = 0; + break; + case WEP40_ENCRYPTION: + case WEP104_ENCRYPTION: + case TKIP_ENCRYPTION: + sec_min_space = 6; + break; + default: + sec_min_space = 7; + break; + } + if (min_spacing_to_set < sec_min_space) + min_spacing_to_set = sec_min_space; + mac->min_space_cfg = ((mac->min_space_cfg & + 0xf8) | + min_spacing_to_set); + *val = min_spacing_to_set; + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_AMPDU_MIN_SPACE: %#x\n", + mac->min_space_cfg); + rtl_write_byte(rtlpriv, REG_AMPDU_MIN_SPACE, + mac->min_space_cfg); + } + break; + } + case HW_VAR_SHORTGI_DENSITY:{ + u8 density_to_set; + + density_to_set = *val; + density_to_set &= 0x1f; + mac->min_space_cfg &= 0x07; + mac->min_space_cfg |= (density_to_set << 3); + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_SHORTGI_DENSITY: %#x\n", + mac->min_space_cfg); + rtl_write_byte(rtlpriv, REG_AMPDU_MIN_SPACE, + mac->min_space_cfg); + break; + } + case HW_VAR_AMPDU_FACTOR:{ + u8 regtoset_normal[4] = {0x41, 0xa8, 0x72, 0xb9}; + u8 factor_toset; + u8 *p_regtoset = NULL; + u8 index = 0; + + p_regtoset = regtoset_normal; + factor_toset = *val; + if (factor_toset <= 3) { + factor_toset = (1 << (factor_toset + 2)); + if (factor_toset > 0xf) + factor_toset = 0xf; + for (index = 0; index < 4; index++) { + if ((p_regtoset[index] & 0xf0) > + (factor_toset << 4)) + p_regtoset[index] = + (p_regtoset[index] & 0x0f) + | (factor_toset << 4); + if ((p_regtoset[index] & 0x0f) > + factor_toset) + p_regtoset[index] = + (p_regtoset[index] & 0xf0) + | (factor_toset); + rtl_write_byte(rtlpriv, + (REG_AGGLEN_LMT + index), + p_regtoset[index]); + } + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_AMPDU_FACTOR: %#x\n", + factor_toset); + } + break; + } + case HW_VAR_AC_PARAM:{ + u8 e_aci = *val; + u32 u4b_ac_param; + u16 cw_min = le16_to_cpu(mac->ac[e_aci].cw_min); + u16 cw_max = le16_to_cpu(mac->ac[e_aci].cw_max); + u16 tx_op = le16_to_cpu(mac->ac[e_aci].tx_op); + + u4b_ac_param = (u32) mac->ac[e_aci].aifs; + u4b_ac_param |= (u32) ((cw_min & 0xF) << + AC_PARAM_ECW_MIN_OFFSET); + u4b_ac_param |= (u32) ((cw_max & 0xF) << + AC_PARAM_ECW_MAX_OFFSET); + u4b_ac_param |= (u32) tx_op << AC_PARAM_TXOP_OFFSET; + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "queue:%x, ac_param:%x\n", + e_aci, u4b_ac_param); + switch (e_aci) { + case AC1_BK: + rtl_write_dword(rtlpriv, REG_EDCA_BK_PARAM, + u4b_ac_param); + break; + case AC0_BE: + rtl_write_dword(rtlpriv, REG_EDCA_BE_PARAM, + u4b_ac_param); + break; + case AC2_VI: + rtl_write_dword(rtlpriv, REG_EDCA_VI_PARAM, + u4b_ac_param); + break; + case AC3_VO: + rtl_write_dword(rtlpriv, REG_EDCA_VO_PARAM, + u4b_ac_param); + break; + default: + RT_ASSERT(false, + "SetHwReg8185(): invalid aci: %d !\n", + e_aci); + break; + } + if (rtlusb->acm_method != eAcmWay2_SW) + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_ACM_CTRL, &e_aci); + break; + } + case HW_VAR_ACM_CTRL:{ + u8 e_aci = *val; + union aci_aifsn *p_aci_aifsn = (union aci_aifsn *) + (&(mac->ac[0].aifs)); + u8 acm = p_aci_aifsn->f.acm; + u8 acm_ctrl = rtl_read_byte(rtlpriv, REG_ACMHWCTRL); + + acm_ctrl = + acm_ctrl | ((rtlusb->acm_method == 2) ? 0x0 : 0x1); + if (acm) { + switch (e_aci) { + case AC0_BE: + acm_ctrl |= AcmHw_BeqEn; + break; + case AC2_VI: + acm_ctrl |= AcmHw_ViqEn; + break; + case AC3_VO: + acm_ctrl |= AcmHw_VoqEn; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "HW_VAR_ACM_CTRL acm set failed: eACI is %d\n", + acm); + break; + } + } else { + switch (e_aci) { + case AC0_BE: + acm_ctrl &= (~AcmHw_BeqEn); + break; + case AC2_VI: + acm_ctrl &= (~AcmHw_ViqEn); + break; + case AC3_VO: + acm_ctrl &= (~AcmHw_BeqEn); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + } + RT_TRACE(rtlpriv, COMP_QOS, DBG_TRACE, + "SetHwReg8190pci(): [HW_VAR_ACM_CTRL] Write 0x%X\n", + acm_ctrl); + rtl_write_byte(rtlpriv, REG_ACMHWCTRL, acm_ctrl); + break; + } + case HW_VAR_RCR:{ + rtl_write_dword(rtlpriv, REG_RCR, ((u32 *) (val))[0]); + mac->rx_conf = ((u32 *) (val))[0]; + RT_TRACE(rtlpriv, COMP_RECV, DBG_DMESG, + "### Set RCR(0x%08x) ###\n", mac->rx_conf); + break; + } + case HW_VAR_RETRY_LIMIT:{ + u8 retry_limit = val[0]; + + rtl_write_word(rtlpriv, REG_RL, + retry_limit << RETRY_LIMIT_SHORT_SHIFT | + retry_limit << RETRY_LIMIT_LONG_SHIFT); + RT_TRACE(rtlpriv, COMP_MLME, DBG_DMESG, + "Set HW_VAR_RETRY_LIMIT(0x%08x)\n", + retry_limit); + break; + } + case HW_VAR_DUAL_TSF_RST: + rtl_write_byte(rtlpriv, REG_DUAL_TSF_RST, (BIT(0) | BIT(1))); + break; + case HW_VAR_EFUSE_BYTES: + rtlefuse->efuse_usedbytes = *((u16 *) val); + break; + case HW_VAR_EFUSE_USAGE: + rtlefuse->efuse_usedpercentage = *val; + break; + case HW_VAR_IO_CMD: + rtl92c_phy_set_io_cmd(hw, (*(enum io_type *)val)); + break; + case HW_VAR_WPA_CONFIG: + rtl_write_byte(rtlpriv, REG_SECCFG, *val); + break; + case HW_VAR_SET_RPWM:{ + u8 rpwm_val = rtl_read_byte(rtlpriv, REG_USB_HRPWM); + + if (rpwm_val & BIT(7)) + rtl_write_byte(rtlpriv, REG_USB_HRPWM, *val); + else + rtl_write_byte(rtlpriv, REG_USB_HRPWM, + *val | BIT(7)); + break; + } + case HW_VAR_H2C_FW_PWRMODE:{ + u8 psmode = *val; + + if ((psmode != FW_PS_ACTIVE_MODE) && + (!IS_92C_SERIAL(rtlhal->version))) + rtl92c_dm_rf_saving(hw, true); + rtl92c_set_fw_pwrmode_cmd(hw, (*val)); + break; + } + case HW_VAR_FW_PSMODE_STATUS: + ppsc->fw_current_inpsmode = *((bool *) val); + break; + case HW_VAR_H2C_FW_JOINBSSRPT:{ + u8 mstatus = *val; + u8 tmp_reg422; + bool recover = false; + + if (mstatus == RT_MEDIA_CONNECT) { + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_AID, NULL); + rtl_write_byte(rtlpriv, REG_CR + 1, 0x03); + _rtl92cu_set_bcn_ctrl_reg(hw, 0, BIT(3)); + _rtl92cu_set_bcn_ctrl_reg(hw, BIT(4), 0); + tmp_reg422 = rtl_read_byte(rtlpriv, + REG_FWHW_TXQ_CTRL + 2); + if (tmp_reg422 & BIT(6)) + recover = true; + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, + tmp_reg422 & (~BIT(6))); + rtl92c_set_fw_rsvdpagepkt(hw, 0); + _rtl92cu_set_bcn_ctrl_reg(hw, BIT(3), 0); + _rtl92cu_set_bcn_ctrl_reg(hw, 0, BIT(4)); + if (recover) + rtl_write_byte(rtlpriv, + REG_FWHW_TXQ_CTRL + 2, + tmp_reg422 | BIT(6)); + rtl_write_byte(rtlpriv, REG_CR + 1, 0x02); + } + rtl92c_set_fw_joinbss_report_cmd(hw, (*val)); + break; + } + case HW_VAR_AID:{ + u16 u2btmp; + + u2btmp = rtl_read_word(rtlpriv, REG_BCN_PSR_RPT); + u2btmp &= 0xC000; + rtl_write_word(rtlpriv, REG_BCN_PSR_RPT, + (u2btmp | mac->assoc_id)); + break; + } + case HW_VAR_CORRECT_TSF:{ + u8 btype_ibss = val[0]; + + if (btype_ibss) + _rtl92cu_stop_tx_beacon(hw); + _rtl92cu_set_bcn_ctrl_reg(hw, 0, BIT(3)); + rtl_write_dword(rtlpriv, REG_TSFTR, (u32)(mac->tsf & + 0xffffffff)); + rtl_write_dword(rtlpriv, REG_TSFTR + 4, + (u32)((mac->tsf >> 32) & 0xffffffff)); + _rtl92cu_set_bcn_ctrl_reg(hw, BIT(3), 0); + if (btype_ibss) + _rtl92cu_resume_tx_beacon(hw); + break; + } + case HW_VAR_MGT_FILTER: + rtl_write_word(rtlpriv, REG_RXFLTMAP0, *(u16 *)val); + break; + case HW_VAR_CTRL_FILTER: + rtl_write_word(rtlpriv, REG_RXFLTMAP1, *(u16 *)val); + break; + case HW_VAR_DATA_FILTER: + rtl_write_word(rtlpriv, REG_RXFLTMAP2, *(u16 *)val); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } +} + +static void rtl92cu_update_hal_rate_table(struct ieee80211_hw *hw, + struct ieee80211_sta *sta) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u32 ratr_value; + u8 ratr_index = 0; + u8 nmode = mac->ht_enable; + u8 mimo_ps = IEEE80211_SMPS_OFF; + u16 shortgi_rate; + u32 tmp_ratr_value; + u8 curtxbw_40mhz = mac->bw_40; + u8 curshortgi_40mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_40) ? + 1 : 0; + u8 curshortgi_20mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_20) ? + 1 : 0; + enum wireless_mode wirelessmode = mac->mode; + + if (rtlhal->current_bandtype == BAND_ON_5G) + ratr_value = sta->supp_rates[1] << 4; + else + ratr_value = sta->supp_rates[0]; + if (mac->opmode == NL80211_IFTYPE_ADHOC) + ratr_value = 0xfff; + + ratr_value |= (sta->ht_cap.mcs.rx_mask[1] << 20 | + sta->ht_cap.mcs.rx_mask[0] << 12); + switch (wirelessmode) { + case WIRELESS_MODE_B: + if (ratr_value & 0x0000000c) + ratr_value &= 0x0000000d; + else + ratr_value &= 0x0000000f; + break; + case WIRELESS_MODE_G: + ratr_value &= 0x00000FF5; + break; + case WIRELESS_MODE_N_24G: + case WIRELESS_MODE_N_5G: + nmode = 1; + if (mimo_ps == IEEE80211_SMPS_STATIC) { + ratr_value &= 0x0007F005; + } else { + u32 ratr_mask; + + if (get_rf_type(rtlphy) == RF_1T2R || + get_rf_type(rtlphy) == RF_1T1R) + ratr_mask = 0x000ff005; + else + ratr_mask = 0x0f0ff005; + + ratr_value &= ratr_mask; + } + break; + default: + if (rtlphy->rf_type == RF_1T2R) + ratr_value &= 0x000ff0ff; + else + ratr_value &= 0x0f0ff0ff; + + break; + } + + ratr_value &= 0x0FFFFFFF; + + if (nmode && ((curtxbw_40mhz && + curshortgi_40mhz) || (!curtxbw_40mhz && + curshortgi_20mhz))) { + + ratr_value |= 0x10000000; + tmp_ratr_value = (ratr_value >> 12); + + for (shortgi_rate = 15; shortgi_rate > 0; shortgi_rate--) { + if ((1 << shortgi_rate) & tmp_ratr_value) + break; + } + + shortgi_rate = (shortgi_rate << 12) | (shortgi_rate << 8) | + (shortgi_rate << 4) | (shortgi_rate); + } + + rtl_write_dword(rtlpriv, REG_ARFR0 + ratr_index * 4, ratr_value); + + RT_TRACE(rtlpriv, COMP_RATR, DBG_DMESG, "%x\n", + rtl_read_dword(rtlpriv, REG_ARFR0)); +} + +static void rtl92cu_update_hal_rate_mask(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + u8 rssi_level) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_sta_info *sta_entry = NULL; + u32 ratr_bitmap; + u8 ratr_index; + u8 curtxbw_40mhz = (sta->bandwidth >= IEEE80211_STA_RX_BW_40) ? 1 : 0; + u8 curshortgi_40mhz = curtxbw_40mhz && + (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_40) ? + 1 : 0; + u8 curshortgi_20mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_20) ? + 1 : 0; + enum wireless_mode wirelessmode = 0; + bool shortgi = false; + u8 rate_mask[5]; + u8 macid = 0; + u8 mimo_ps = IEEE80211_SMPS_OFF; + + sta_entry = (struct rtl_sta_info *) sta->drv_priv; + wirelessmode = sta_entry->wireless_mode; + if (mac->opmode == NL80211_IFTYPE_STATION || + mac->opmode == NL80211_IFTYPE_MESH_POINT) + curtxbw_40mhz = mac->bw_40; + else if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_ADHOC) + macid = sta->aid + 1; + + if (rtlhal->current_bandtype == BAND_ON_5G) + ratr_bitmap = sta->supp_rates[1] << 4; + else + ratr_bitmap = sta->supp_rates[0]; + if (mac->opmode == NL80211_IFTYPE_ADHOC) + ratr_bitmap = 0xfff; + ratr_bitmap |= (sta->ht_cap.mcs.rx_mask[1] << 20 | + sta->ht_cap.mcs.rx_mask[0] << 12); + switch (wirelessmode) { + case WIRELESS_MODE_B: + ratr_index = RATR_INX_WIRELESS_B; + if (ratr_bitmap & 0x0000000c) + ratr_bitmap &= 0x0000000d; + else + ratr_bitmap &= 0x0000000f; + break; + case WIRELESS_MODE_G: + ratr_index = RATR_INX_WIRELESS_GB; + + if (rssi_level == 1) + ratr_bitmap &= 0x00000f00; + else if (rssi_level == 2) + ratr_bitmap &= 0x00000ff0; + else + ratr_bitmap &= 0x00000ff5; + break; + case WIRELESS_MODE_A: + ratr_index = RATR_INX_WIRELESS_A; + ratr_bitmap &= 0x00000ff0; + break; + case WIRELESS_MODE_N_24G: + case WIRELESS_MODE_N_5G: + ratr_index = RATR_INX_WIRELESS_NGB; + + if (mimo_ps == IEEE80211_SMPS_STATIC) { + if (rssi_level == 1) + ratr_bitmap &= 0x00070000; + else if (rssi_level == 2) + ratr_bitmap &= 0x0007f000; + else + ratr_bitmap &= 0x0007f005; + } else { + if (rtlphy->rf_type == RF_1T2R || + rtlphy->rf_type == RF_1T1R) { + if (curtxbw_40mhz) { + if (rssi_level == 1) + ratr_bitmap &= 0x000f0000; + else if (rssi_level == 2) + ratr_bitmap &= 0x000ff000; + else + ratr_bitmap &= 0x000ff015; + } else { + if (rssi_level == 1) + ratr_bitmap &= 0x000f0000; + else if (rssi_level == 2) + ratr_bitmap &= 0x000ff000; + else + ratr_bitmap &= 0x000ff005; + } + } else { + if (curtxbw_40mhz) { + if (rssi_level == 1) + ratr_bitmap &= 0x0f0f0000; + else if (rssi_level == 2) + ratr_bitmap &= 0x0f0ff000; + else + ratr_bitmap &= 0x0f0ff015; + } else { + if (rssi_level == 1) + ratr_bitmap &= 0x0f0f0000; + else if (rssi_level == 2) + ratr_bitmap &= 0x0f0ff000; + else + ratr_bitmap &= 0x0f0ff005; + } + } + } + + if ((curtxbw_40mhz && curshortgi_40mhz) || + (!curtxbw_40mhz && curshortgi_20mhz)) { + + if (macid == 0) + shortgi = true; + else if (macid == 1) + shortgi = false; + } + break; + default: + ratr_index = RATR_INX_WIRELESS_NGB; + + if (rtlphy->rf_type == RF_1T2R) + ratr_bitmap &= 0x000ff0ff; + else + ratr_bitmap &= 0x0f0ff0ff; + break; + } + sta_entry->ratr_index = ratr_index; + + RT_TRACE(rtlpriv, COMP_RATR, DBG_DMESG, + "ratr_bitmap :%x\n", ratr_bitmap); + *(u32 *)&rate_mask = (ratr_bitmap & 0x0fffffff) | + (ratr_index << 28); + rate_mask[4] = macid | (shortgi ? 0x20 : 0x00) | 0x80; + RT_TRACE(rtlpriv, COMP_RATR, DBG_DMESG, + "Rate_index:%x, ratr_val:%x, %5phC\n", + ratr_index, ratr_bitmap, rate_mask); + memcpy(rtlpriv->rate_mask, rate_mask, 5); + /* rtl92c_fill_h2c_cmd() does USB I/O and will result in a + * "scheduled while atomic" if called directly */ + schedule_work(&rtlpriv->works.fill_h2c_cmd); + + if (macid != 0) + sta_entry->ratr_index = ratr_index; +} + +void rtl92cu_update_hal_rate_tbl(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + u8 rssi_level) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (rtlpriv->dm.useramask) + rtl92cu_update_hal_rate_mask(hw, sta, rssi_level); + else + rtl92cu_update_hal_rate_table(hw, sta); +} + +void rtl92cu_update_channel_access_setting(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 sifs_timer; + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SLOT_TIME, + &mac->slot_time); + if (!mac->ht_enable) + sifs_timer = 0x0a0a; + else + sifs_timer = 0x0e0e; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SIFS, (u8 *)&sifs_timer); +} + +bool rtl92cu_gpio_radio_on_off_checking(struct ieee80211_hw *hw, u8 * valid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + enum rf_pwrstate e_rfpowerstate_toset, cur_rfstate; + u8 u1tmp = 0; + bool actuallyset = false; + unsigned long flag = 0; + /* to do - usb autosuspend */ + u8 usb_autosuspend = 0; + + if (ppsc->swrf_processing) + return false; + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); + if (ppsc->rfchange_inprogress) { + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + return false; + } else { + ppsc->rfchange_inprogress = true; + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + } + cur_rfstate = ppsc->rfpwr_state; + if (usb_autosuspend) { + /* to do................... */ + } else { + if (ppsc->pwrdown_mode) { + u1tmp = rtl_read_byte(rtlpriv, REG_HSISR); + e_rfpowerstate_toset = (u1tmp & BIT(7)) ? + ERFOFF : ERFON; + RT_TRACE(rtlpriv, COMP_POWER, DBG_DMESG, + "pwrdown, 0x5c(BIT7)=%02x\n", u1tmp); + } else { + rtl_write_byte(rtlpriv, REG_MAC_PINMUX_CFG, + rtl_read_byte(rtlpriv, + REG_MAC_PINMUX_CFG) & ~(BIT(3))); + u1tmp = rtl_read_byte(rtlpriv, REG_GPIO_IO_SEL); + e_rfpowerstate_toset = (u1tmp & BIT(3)) ? + ERFON : ERFOFF; + RT_TRACE(rtlpriv, COMP_POWER, DBG_DMESG, + "GPIO_IN=%02x\n", u1tmp); + } + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, "N-SS RF =%x\n", + e_rfpowerstate_toset); + } + if ((ppsc->hwradiooff) && (e_rfpowerstate_toset == ERFON)) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "GPIOChangeRF - HW Radio ON, RF ON\n"); + ppsc->hwradiooff = false; + actuallyset = true; + } else if ((!ppsc->hwradiooff) && (e_rfpowerstate_toset == + ERFOFF)) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "GPIOChangeRF - HW Radio OFF\n"); + ppsc->hwradiooff = true; + actuallyset = true; + } else { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "pHalData->bHwRadioOff and eRfPowerStateToSet do not match: pHalData->bHwRadioOff %x, eRfPowerStateToSet %x\n", + ppsc->hwradiooff, e_rfpowerstate_toset); + } + if (actuallyset) { + ppsc->hwradiooff = true; + if (e_rfpowerstate_toset == ERFON) { + if ((ppsc->reg_rfps_level & RT_RF_OFF_LEVL_ASPM) && + RT_IN_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_ASPM)) + RT_CLEAR_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_ASPM); + else if ((ppsc->reg_rfps_level & RT_RF_OFF_LEVL_PCI_D3) + && RT_IN_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_PCI_D3)) + RT_CLEAR_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_PCI_D3); + } + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); + ppsc->rfchange_inprogress = false; + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + /* For power down module, we need to enable register block + * contrl reg at 0x1c. Then enable power down control bit + * of register 0x04 BIT4 and BIT15 as 1. + */ + if (ppsc->pwrdown_mode && e_rfpowerstate_toset == ERFOFF) { + /* Enable register area 0x0-0xc. */ + rtl_write_byte(rtlpriv, REG_RSV_CTRL, 0x0); + if (IS_HARDWARE_TYPE_8723U(rtlhal)) { + /* + * We should configure HW PDn source for WiFi + * ONLY, and then our HW will be set in + * power-down mode if PDn source from all + * functions are configured. + */ + u1tmp = rtl_read_byte(rtlpriv, + REG_MULTI_FUNC_CTRL); + rtl_write_byte(rtlpriv, REG_MULTI_FUNC_CTRL, + (u1tmp|WL_HWPDN_EN)); + } else { + rtl_write_word(rtlpriv, REG_APS_FSMCO, 0x8812); + } + } + if (e_rfpowerstate_toset == ERFOFF) { + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_ASPM) + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_ASPM); + else if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_PCI_D3) + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_PCI_D3); + } + } else if (e_rfpowerstate_toset == ERFOFF || cur_rfstate == ERFOFF) { + /* Enter D3 or ASPM after GPIO had been done. */ + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_ASPM) + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_ASPM); + else if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_PCI_D3) + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_PCI_D3); + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); + ppsc->rfchange_inprogress = false; + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + } else { + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); + ppsc->rfchange_inprogress = false; + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + } + *valid = 1; + return !ppsc->hwradiooff; +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/hw.h b/drivers/net/wireless/rtlwifi/rtl8192cu/hw.h new file mode 100644 index 0000000..8e3ec1e --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/hw.h @@ -0,0 +1,113 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CU_HW_H__ +#define __RTL92CU_HW_H__ + +#define H2C_RA_MASK 6 + +#define LLT_POLLING_LLT_THRESHOLD 20 +#define LLT_POLLING_READY_TIMEOUT_COUNT 100 +#define LLT_LAST_ENTRY_OF_TX_PKT_BUFFER 255 + +#define RX_PAGE_SIZE_REG_VALUE PBP_128 +/* Note: We will divide number of page equally for each queue + * other than public queue! */ +#define TX_TOTAL_PAGE_NUMBER 0xF8 +#define TX_PAGE_BOUNDARY (TX_TOTAL_PAGE_NUMBER + 1) + + +#define CHIP_B_PAGE_NUM_PUBQ 0xE7 + +/* For Test Chip Setting + * (HPQ + LPQ + PUBQ) shall be TX_TOTAL_PAGE_NUMBER */ +#define CHIP_A_PAGE_NUM_PUBQ 0x7E + + +/* For Chip A Setting */ +#define WMM_CHIP_A_TX_TOTAL_PAGE_NUMBER 0xF5 +#define WMM_CHIP_A_TX_PAGE_BOUNDARY \ + (WMM_CHIP_A_TX_TOTAL_PAGE_NUMBER + 1) /* F6 */ + +#define WMM_CHIP_A_PAGE_NUM_PUBQ 0xA3 +#define WMM_CHIP_A_PAGE_NUM_HPQ 0x29 +#define WMM_CHIP_A_PAGE_NUM_LPQ 0x29 + + + +/* Note: For Chip B Setting ,modify later */ +#define WMM_CHIP_B_TX_TOTAL_PAGE_NUMBER 0xF5 +#define WMM_CHIP_B_TX_PAGE_BOUNDARY \ + (WMM_CHIP_B_TX_TOTAL_PAGE_NUMBER + 1) /* F6 */ + +#define WMM_CHIP_B_PAGE_NUM_PUBQ 0xB0 +#define WMM_CHIP_B_PAGE_NUM_HPQ 0x29 +#define WMM_CHIP_B_PAGE_NUM_LPQ 0x1C +#define WMM_CHIP_B_PAGE_NUM_NPQ 0x1C + +#define BOARD_TYPE_NORMAL_MASK 0xE0 +#define BOARD_TYPE_TEST_MASK 0x0F + +/* should be renamed and moved to another file */ +enum _BOARD_TYPE_8192CUSB { + BOARD_USB_DONGLE = 0, /* USB dongle */ + BOARD_USB_High_PA = 1, /* USB dongle - high power PA */ + BOARD_MINICARD = 2, /* Minicard */ + BOARD_USB_SOLO = 3, /* USB solo-Slim module */ + BOARD_USB_COMBO = 4, /* USB Combo-Slim module */ +}; + +#define IS_HIGHT_PA(boardtype) \ + ((boardtype == BOARD_USB_High_PA) ? true : false) + +#define RTL92C_DRIVER_INFO_SIZE 4 +void rtl92cu_read_eeprom_info(struct ieee80211_hw *hw); +void rtl92cu_enable_hw_security_config(struct ieee80211_hw *hw); +int rtl92cu_hw_init(struct ieee80211_hw *hw); +void rtl92cu_card_disable(struct ieee80211_hw *hw); +int rtl92cu_set_network_type(struct ieee80211_hw *hw, enum nl80211_iftype type); +void rtl92cu_set_beacon_related_registers(struct ieee80211_hw *hw); +void rtl92cu_set_beacon_interval(struct ieee80211_hw *hw); +void rtl92cu_update_interrupt_mask(struct ieee80211_hw *hw, + u32 add_msr, u32 rm_msr); +void rtl92cu_get_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val); +void rtl92cu_set_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val); + +void rtl92cu_update_channel_access_setting(struct ieee80211_hw *hw); +bool rtl92cu_gpio_radio_on_off_checking(struct ieee80211_hw *hw, u8 * valid); +void rtl92cu_set_check_bssid(struct ieee80211_hw *hw, bool check_bssid); +int rtl92c_download_fw(struct ieee80211_hw *hw); +void rtl92c_set_fw_pwrmode_cmd(struct ieee80211_hw *hw, u8 mode); +void rtl92c_set_fw_rsvdpagepkt(struct ieee80211_hw *hw, bool dl_finished); +void rtl92c_set_fw_joinbss_report_cmd(struct ieee80211_hw *hw, u8 mstatus); +void rtl92c_fill_h2c_cmd(struct ieee80211_hw *hw, + u8 element_id, u32 cmd_len, u8 *p_cmdbuffer); +bool rtl92cu_phy_mac_config(struct ieee80211_hw *hw); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/led.c b/drivers/net/wireless/rtlwifi/rtl8192cu/led.c new file mode 100644 index 0000000..75a2deb --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/led.c @@ -0,0 +1,141 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../usb.h" +#include "reg.h" +#include "led.h" + +static void _rtl92cu_init_led(struct ieee80211_hw *hw, + struct rtl_led *pled, enum rtl_led_pin ledpin) +{ + pled->hw = hw; + pled->ledpin = ledpin; + pled->ledon = false; +} + +static void _rtl92cu_deInit_led(struct rtl_led *pled) +{ +} + +void rtl92cu_sw_led_on(struct ieee80211_hw *hw, struct rtl_led *pled) +{ + u8 ledcfg; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, "LedAddr:%X ledpin=%d\n", + REG_LEDCFG2, pled->ledpin); + ledcfg = rtl_read_byte(rtlpriv, REG_LEDCFG2); + switch (pled->ledpin) { + case LED_PIN_GPIO0: + break; + case LED_PIN_LED0: + rtl_write_byte(rtlpriv, + REG_LEDCFG2, (ledcfg & 0xf0) | BIT(5) | BIT(6)); + break; + case LED_PIN_LED1: + rtl_write_byte(rtlpriv, REG_LEDCFG2, (ledcfg & 0x0f) | BIT(5)); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + pled->ledon = true; +} + +void rtl92cu_sw_led_off(struct ieee80211_hw *hw, struct rtl_led *pled) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_usb_priv *usbpriv = rtl_usbpriv(hw); + u8 ledcfg; + + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, "LedAddr:%X ledpin=%d\n", + REG_LEDCFG2, pled->ledpin); + ledcfg = rtl_read_byte(rtlpriv, REG_LEDCFG2); + switch (pled->ledpin) { + case LED_PIN_GPIO0: + break; + case LED_PIN_LED0: + ledcfg &= 0xf0; + if (usbpriv->ledctl.led_opendrain) + rtl_write_byte(rtlpriv, REG_LEDCFG2, + (ledcfg | BIT(1) | BIT(5) | BIT(6))); + else + rtl_write_byte(rtlpriv, REG_LEDCFG2, + (ledcfg | BIT(3) | BIT(5) | BIT(6))); + break; + case LED_PIN_LED1: + ledcfg &= 0x0f; + rtl_write_byte(rtlpriv, REG_LEDCFG2, (ledcfg | BIT(3))); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + pled->ledon = false; +} + +void rtl92cu_init_sw_leds(struct ieee80211_hw *hw) +{ + struct rtl_usb_priv *usbpriv = rtl_usbpriv(hw); + _rtl92cu_init_led(hw, &(usbpriv->ledctl.sw_led0), LED_PIN_LED0); + _rtl92cu_init_led(hw, &(usbpriv->ledctl.sw_led1), LED_PIN_LED1); +} + +void rtl92cu_deinit_sw_leds(struct ieee80211_hw *hw) +{ + struct rtl_usb_priv *usbpriv = rtl_usbpriv(hw); + _rtl92cu_deInit_led(&(usbpriv->ledctl.sw_led0)); + _rtl92cu_deInit_led(&(usbpriv->ledctl.sw_led1)); +} + +static void _rtl92cu_sw_led_control(struct ieee80211_hw *hw, + enum led_ctl_mode ledaction) +{ +} + +void rtl92cu_led_control(struct ieee80211_hw *hw, + enum led_ctl_mode ledaction) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + if ((ppsc->rfoff_reason > RF_CHANGE_BY_PS) && + (ledaction == LED_CTL_TX || + ledaction == LED_CTL_RX || + ledaction == LED_CTL_SITE_SURVEY || + ledaction == LED_CTL_LINK || + ledaction == LED_CTL_NO_LINK || + ledaction == LED_CTL_START_TO_LINK || + ledaction == LED_CTL_POWER_ON)) { + return; + } + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, "ledaction %d\n", ledaction); + _rtl92cu_sw_led_control(hw, ledaction); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/led.h b/drivers/net/wireless/rtlwifi/rtl8192cu/led.h new file mode 100644 index 0000000..0f37227 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/led.h @@ -0,0 +1,37 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + *****************************************************************************/ + +#ifndef __RTL92CU_LED_H__ +#define __RTL92CU_LED_H__ + +void rtl92cu_init_sw_leds(struct ieee80211_hw *hw); +void rtl92cu_deinit_sw_leds(struct ieee80211_hw *hw); +void rtl92cu_sw_led_on(struct ieee80211_hw *hw, struct rtl_led *pled); +void rtl92cu_sw_led_off(struct ieee80211_hw *hw, struct rtl_led *pled); +void rtl92cu_led_control(struct ieee80211_hw *hw, enum led_ctl_mode ledaction); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/mac.c b/drivers/net/wireless/rtlwifi/rtl8192cu/mac.c new file mode 100644 index 0000000..da4f587 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/mac.c @@ -0,0 +1,1127 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * +****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "../usb.h" +#include "../ps.h" +#include "../cam.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "rf.h" +#include "dm.h" +#include "mac.h" +#include "trx.h" + +#include + +/* macro to shorten lines */ + +#define LINK_Q ui_link_quality +#define RX_EVM rx_evm_percentage +#define RX_SIGQ rx_mimo_sig_qual + + +void rtl92c_read_chip_version(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + enum version_8192c chip_version = VERSION_UNKNOWN; + const char *versionid; + u32 value32; + + value32 = rtl_read_dword(rtlpriv, REG_SYS_CFG); + if (value32 & TRP_VAUX_EN) { + chip_version = (value32 & TYPE_ID) ? VERSION_TEST_CHIP_92C : + VERSION_TEST_CHIP_88C; + } else { + /* Normal mass production chip. */ + chip_version = NORMAL_CHIP; + chip_version |= ((value32 & TYPE_ID) ? CHIP_92C : 0); + chip_version |= ((value32 & VENDOR_ID) ? CHIP_VENDOR_UMC : 0); + /* RTL8723 with BT function. */ + chip_version |= ((value32 & BT_FUNC) ? CHIP_8723 : 0); + if (IS_VENDOR_UMC(chip_version)) + chip_version |= ((value32 & CHIP_VER_RTL_MASK) ? + CHIP_VENDOR_UMC_B_CUT : 0); + if (IS_92C_SERIAL(chip_version)) { + value32 = rtl_read_dword(rtlpriv, REG_HPON_FSM); + chip_version |= ((CHIP_BONDING_IDENTIFIER(value32) == + CHIP_BONDING_92C_1T2R) ? CHIP_92C_1T2R : 0); + } else if (IS_8723_SERIES(chip_version)) { + value32 = rtl_read_dword(rtlpriv, REG_GPIO_OUTSTS); + chip_version |= ((value32 & RF_RL_ID) ? + CHIP_8723_DRV_REV : 0); + } + } + rtlhal->version = (enum version_8192c)chip_version; + pr_info("Chip version 0x%x\n", chip_version); + switch (rtlhal->version) { + case VERSION_NORMAL_TSMC_CHIP_92C_1T2R: + versionid = "NORMAL_B_CHIP_92C"; + break; + case VERSION_NORMAL_TSMC_CHIP_92C: + versionid = "NORMAL_TSMC_CHIP_92C"; + break; + case VERSION_NORMAL_TSMC_CHIP_88C: + versionid = "NORMAL_TSMC_CHIP_88C"; + break; + case VERSION_NORMAL_UMC_CHIP_92C_1T2R_A_CUT: + versionid = "NORMAL_UMC_CHIP_i92C_1T2R_A_CUT"; + break; + case VERSION_NORMAL_UMC_CHIP_92C_A_CUT: + versionid = "NORMAL_UMC_CHIP_92C_A_CUT"; + break; + case VERSION_NORMAL_UMC_CHIP_88C_A_CUT: + versionid = "NORMAL_UMC_CHIP_88C_A_CUT"; + break; + case VERSION_NORMAL_UMC_CHIP_92C_1T2R_B_CUT: + versionid = "NORMAL_UMC_CHIP_92C_1T2R_B_CUT"; + break; + case VERSION_NORMAL_UMC_CHIP_92C_B_CUT: + versionid = "NORMAL_UMC_CHIP_92C_B_CUT"; + break; + case VERSION_NORMAL_UMC_CHIP_88C_B_CUT: + versionid = "NORMAL_UMC_CHIP_88C_B_CUT"; + break; + case VERSION_NORMA_UMC_CHIP_8723_1T1R_A_CUT: + versionid = "NORMAL_UMC_CHIP_8723_1T1R_A_CUT"; + break; + case VERSION_NORMA_UMC_CHIP_8723_1T1R_B_CUT: + versionid = "NORMAL_UMC_CHIP_8723_1T1R_B_CUT"; + break; + case VERSION_TEST_CHIP_92C: + versionid = "TEST_CHIP_92C"; + break; + case VERSION_TEST_CHIP_88C: + versionid = "TEST_CHIP_88C"; + break; + default: + versionid = "UNKNOWN"; + break; + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Chip Version ID: %s\n", versionid); + + if (IS_92C_SERIAL(rtlhal->version)) + rtlphy->rf_type = + (IS_92C_1T2R(rtlhal->version)) ? RF_1T2R : RF_2T2R; + else + rtlphy->rf_type = RF_1T1R; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Chip RF Type: %s\n", + rtlphy->rf_type == RF_2T2R ? "RF_2T2R" : "RF_1T1R"); + if (get_rf_type(rtlphy) == RF_1T1R) + rtlpriv->dm.rfpath_rxenable[0] = true; + else + rtlpriv->dm.rfpath_rxenable[0] = + rtlpriv->dm.rfpath_rxenable[1] = true; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "VersionID = 0x%4x\n", + rtlhal->version); +} + +/** + * writeLLT - LLT table write access + * @io: io callback + * @address: LLT logical address. + * @data: LLT data content + * + * Realtek hardware access function. + * + */ +bool rtl92c_llt_write(struct ieee80211_hw *hw, u32 address, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + bool status = true; + long count = 0; + u32 value = _LLT_INIT_ADDR(address) | + _LLT_INIT_DATA(data) | _LLT_OP(_LLT_WRITE_ACCESS); + + rtl_write_dword(rtlpriv, REG_LLT_INIT, value); + do { + value = rtl_read_dword(rtlpriv, REG_LLT_INIT); + if (_LLT_NO_ACTIVE == _LLT_OP_VALUE(value)) + break; + if (count > POLLING_LLT_THRESHOLD) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Failed to polling write LLT done at address %d! _LLT_OP_VALUE(%x)\n", + address, _LLT_OP_VALUE(value)); + status = false; + break; + } + } while (++count); + return status; +} +/** + * rtl92c_init_LLT_table - Init LLT table + * @io: io callback + * @boundary: + * + * Realtek hardware access function. + * + */ +bool rtl92c_init_llt_table(struct ieee80211_hw *hw, u32 boundary) +{ + bool rst = true; + u32 i; + + for (i = 0; i < (boundary - 1); i++) { + rst = rtl92c_llt_write(hw, i , i + 1); + if (true != rst) { + pr_err("===> %s #1 fail\n", __func__); + return rst; + } + } + /* end of list */ + rst = rtl92c_llt_write(hw, (boundary - 1), 0xFF); + if (true != rst) { + pr_err("===> %s #2 fail\n", __func__); + return rst; + } + /* Make the other pages as ring buffer + * This ring buffer is used as beacon buffer if we config this MAC + * as two MAC transfer. + * Otherwise used as local loopback buffer. + */ + for (i = boundary; i < LLT_LAST_ENTRY_OF_TX_PKT_BUFFER; i++) { + rst = rtl92c_llt_write(hw, i, (i + 1)); + if (true != rst) { + pr_err("===> %s #3 fail\n", __func__); + return rst; + } + } + /* Let last entry point to the start entry of ring buffer */ + rst = rtl92c_llt_write(hw, LLT_LAST_ENTRY_OF_TX_PKT_BUFFER, boundary); + if (true != rst) { + pr_err("===> %s #4 fail\n", __func__); + return rst; + } + return rst; +} +void rtl92c_set_key(struct ieee80211_hw *hw, u32 key_index, + u8 *p_macaddr, bool is_group, u8 enc_algo, + bool is_wepkey, bool clear_all) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 *macaddr = p_macaddr; + u32 entry_id = 0; + bool is_pairwise = false; + static u8 cam_const_addr[4][6] = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x03} + }; + static u8 cam_const_broad[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + if (clear_all) { + u8 idx = 0; + u8 cam_offset = 0; + u8 clear_number = 5; + + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, "clear_all\n"); + for (idx = 0; idx < clear_number; idx++) { + rtl_cam_mark_invalid(hw, cam_offset + idx); + rtl_cam_empty_entry(hw, cam_offset + idx); + if (idx < 5) { + memset(rtlpriv->sec.key_buf[idx], 0, + MAX_KEY_LEN); + rtlpriv->sec.key_len[idx] = 0; + } + } + } else { + switch (enc_algo) { + case WEP40_ENCRYPTION: + enc_algo = CAM_WEP40; + break; + case WEP104_ENCRYPTION: + enc_algo = CAM_WEP104; + break; + case TKIP_ENCRYPTION: + enc_algo = CAM_TKIP; + break; + case AESCCMP_ENCRYPTION: + enc_algo = CAM_AES; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "illegal switch case\n"); + enc_algo = CAM_TKIP; + break; + } + if (is_wepkey || rtlpriv->sec.use_defaultkey) { + macaddr = cam_const_addr[key_index]; + entry_id = key_index; + } else { + if (is_group) { + macaddr = cam_const_broad; + entry_id = key_index; + } else { + if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_MESH_POINT) { + entry_id = rtl_cam_get_free_entry(hw, + p_macaddr); + if (entry_id >= TOTAL_CAM_ENTRY) { + RT_TRACE(rtlpriv, COMP_SEC, + DBG_EMERG, + "Can not find free hw security cam entry\n"); + return; + } + } else { + entry_id = CAM_PAIRWISE_KEY_POSITION; + } + + key_index = PAIRWISE_KEYIDX; + is_pairwise = true; + } + } + if (rtlpriv->sec.key_len[key_index] == 0) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "delete one entry\n"); + if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_MESH_POINT) + rtl_cam_del_entry(hw, p_macaddr); + rtl_cam_delete_one_entry(hw, p_macaddr, entry_id); + } else { + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, + "The insert KEY length is %d\n", + rtlpriv->sec.key_len[PAIRWISE_KEYIDX]); + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, + "The insert KEY is %x %x\n", + rtlpriv->sec.key_buf[0][0], + rtlpriv->sec.key_buf[0][1]); + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "add one entry\n"); + if (is_pairwise) { + RT_PRINT_DATA(rtlpriv, COMP_SEC, DBG_LOUD, + "Pairwise Key content", + rtlpriv->sec.pairwise_key, + rtlpriv->sec. + key_len[PAIRWISE_KEYIDX]); + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "set Pairwise key\n"); + + rtl_cam_add_one_entry(hw, macaddr, key_index, + entry_id, enc_algo, + CAM_CONFIG_NO_USEDK, + rtlpriv->sec. + key_buf[key_index]); + } else { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "set group key\n"); + if (mac->opmode == NL80211_IFTYPE_ADHOC) { + rtl_cam_add_one_entry(hw, + rtlefuse->dev_addr, + PAIRWISE_KEYIDX, + CAM_PAIRWISE_KEY_POSITION, + enc_algo, + CAM_CONFIG_NO_USEDK, + rtlpriv->sec.key_buf + [entry_id]); + } + rtl_cam_add_one_entry(hw, macaddr, key_index, + entry_id, enc_algo, + CAM_CONFIG_NO_USEDK, + rtlpriv->sec.key_buf[entry_id]); + } + } + } +} + +u32 rtl92c_get_txdma_status(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + return rtl_read_dword(rtlpriv, REG_TXDMA_STATUS); +} + +void rtl92c_enable_interrupt(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + + if (IS_HARDWARE_TYPE_8192CE(rtlhal)) { + rtl_write_dword(rtlpriv, REG_HIMR, rtlpci->irq_mask[0] & + 0xFFFFFFFF); + rtl_write_dword(rtlpriv, REG_HIMRE, rtlpci->irq_mask[1] & + 0xFFFFFFFF); + } else { + rtl_write_dword(rtlpriv, REG_HIMR, rtlusb->irq_mask[0] & + 0xFFFFFFFF); + rtl_write_dword(rtlpriv, REG_HIMRE, rtlusb->irq_mask[1] & + 0xFFFFFFFF); + } +} + +void rtl92c_init_interrupt(struct ieee80211_hw *hw) +{ + rtl92c_enable_interrupt(hw); +} + +void rtl92c_disable_interrupt(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_dword(rtlpriv, REG_HIMR, IMR8190_DISABLED); + rtl_write_dword(rtlpriv, REG_HIMRE, IMR8190_DISABLED); +} + +void rtl92c_set_qos(struct ieee80211_hw *hw, int aci) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u32 u4b_ac_param; + + rtl92c_dm_init_edca_turbo(hw); + u4b_ac_param = (u32) mac->ac[aci].aifs; + u4b_ac_param |= + ((u32) le16_to_cpu(mac->ac[aci].cw_min) & 0xF) << + AC_PARAM_ECW_MIN_OFFSET; + u4b_ac_param |= + ((u32) le16_to_cpu(mac->ac[aci].cw_max) & 0xF) << + AC_PARAM_ECW_MAX_OFFSET; + u4b_ac_param |= (u32) le16_to_cpu(mac->ac[aci].tx_op) << + AC_PARAM_TXOP_OFFSET; + RT_TRACE(rtlpriv, COMP_QOS, DBG_LOUD, "queue:%x, ac_param:%x\n", + aci, u4b_ac_param); + switch (aci) { + case AC1_BK: + rtl_write_dword(rtlpriv, REG_EDCA_BK_PARAM, u4b_ac_param); + break; + case AC0_BE: + rtl_write_dword(rtlpriv, REG_EDCA_BE_PARAM, u4b_ac_param); + break; + case AC2_VI: + rtl_write_dword(rtlpriv, REG_EDCA_VI_PARAM, u4b_ac_param); + break; + case AC3_VO: + rtl_write_dword(rtlpriv, REG_EDCA_VO_PARAM, u4b_ac_param); + break; + default: + RT_ASSERT(false, "invalid aci: %d !\n", aci); + break; + } +} + +/*------------------------------------------------------------------------- + * HW MAC Address + *-------------------------------------------------------------------------*/ +void rtl92c_set_mac_addr(struct ieee80211_hw *hw, const u8 *addr) +{ + u32 i; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + for (i = 0 ; i < ETH_ALEN ; i++) + rtl_write_byte(rtlpriv, (REG_MACID + i), *(addr+i)); + + RT_TRACE(rtlpriv, COMP_CMD, DBG_DMESG, + "MAC Address: %02X-%02X-%02X-%02X-%02X-%02X\n", + rtl_read_byte(rtlpriv, REG_MACID), + rtl_read_byte(rtlpriv, REG_MACID+1), + rtl_read_byte(rtlpriv, REG_MACID+2), + rtl_read_byte(rtlpriv, REG_MACID+3), + rtl_read_byte(rtlpriv, REG_MACID+4), + rtl_read_byte(rtlpriv, REG_MACID+5)); +} + +void rtl92c_init_driver_info_size(struct ieee80211_hw *hw, u8 size) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + rtl_write_byte(rtlpriv, REG_RX_DRVINFO_SZ, size); +} + +int rtl92c_set_network_type(struct ieee80211_hw *hw, enum nl80211_iftype type) +{ + u8 value; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + switch (type) { + case NL80211_IFTYPE_UNSPECIFIED: + value = NT_NO_LINK; + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "Set Network type to NO LINK!\n"); + break; + case NL80211_IFTYPE_ADHOC: + value = NT_LINK_AD_HOC; + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "Set Network type to Ad Hoc!\n"); + break; + case NL80211_IFTYPE_STATION: + value = NT_LINK_AP; + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "Set Network type to STA!\n"); + break; + case NL80211_IFTYPE_AP: + value = NT_AS_AP; + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "Set Network type to AP!\n"); + break; + default: + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "Network type %d not supported!\n", type); + return -EOPNOTSUPP; + } + rtl_write_byte(rtlpriv, (REG_CR + 2), value); + return 0; +} + +void rtl92c_init_network_type(struct ieee80211_hw *hw) +{ + rtl92c_set_network_type(hw, NL80211_IFTYPE_UNSPECIFIED); +} + +void rtl92c_init_adaptive_ctrl(struct ieee80211_hw *hw) +{ + u16 value16; + u32 value32; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + /* Response Rate Set */ + value32 = rtl_read_dword(rtlpriv, REG_RRSR); + value32 &= ~RATE_BITMAP_ALL; + value32 |= RATE_RRSR_CCK_ONLY_1M; + rtl_write_dword(rtlpriv, REG_RRSR, value32); + /* SIFS (used in NAV) */ + value16 = _SPEC_SIFS_CCK(0x10) | _SPEC_SIFS_OFDM(0x10); + rtl_write_word(rtlpriv, REG_SPEC_SIFS, value16); + /* Retry Limit */ + value16 = _LRL(0x30) | _SRL(0x30); + rtl_write_dword(rtlpriv, REG_RL, value16); +} + +void rtl92c_init_rate_fallback(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + /* Set Data Auto Rate Fallback Retry Count register. */ + rtl_write_dword(rtlpriv, REG_DARFRC, 0x00000000); + rtl_write_dword(rtlpriv, REG_DARFRC+4, 0x10080404); + rtl_write_dword(rtlpriv, REG_RARFRC, 0x04030201); + rtl_write_dword(rtlpriv, REG_RARFRC+4, 0x08070605); +} + +static void rtl92c_set_cck_sifs(struct ieee80211_hw *hw, u8 trx_sifs, + u8 ctx_sifs) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_byte(rtlpriv, REG_SIFS_CCK, trx_sifs); + rtl_write_byte(rtlpriv, (REG_SIFS_CCK + 1), ctx_sifs); +} + +static void rtl92c_set_ofdm_sifs(struct ieee80211_hw *hw, u8 trx_sifs, + u8 ctx_sifs) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_byte(rtlpriv, REG_SIFS_OFDM, trx_sifs); + rtl_write_byte(rtlpriv, (REG_SIFS_OFDM + 1), ctx_sifs); +} + +void rtl92c_init_edca_param(struct ieee80211_hw *hw, + u16 queue, u16 txop, u8 cw_min, u8 cw_max, u8 aifs) +{ + /* sequence: VO, VI, BE, BK ==> the same as 92C hardware design. + * referenc : enum nl80211_txq_q or ieee80211_set_wmm_default function. + */ + u32 value; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + value = (u32)aifs; + value |= ((u32)cw_min & 0xF) << 8; + value |= ((u32)cw_max & 0xF) << 12; + value |= (u32)txop << 16; + /* 92C hardware register sequence is the same as queue number. */ + rtl_write_dword(rtlpriv, (REG_EDCA_VO_PARAM + (queue * 4)), value); +} + +void rtl92c_init_edca(struct ieee80211_hw *hw) +{ + u16 value16; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + /* disable EDCCA count down, to reduce collison and retry */ + value16 = rtl_read_word(rtlpriv, REG_RD_CTRL); + value16 |= DIS_EDCA_CNT_DWN; + rtl_write_word(rtlpriv, REG_RD_CTRL, value16); + /* Update SIFS timing. ?????????? + * pHalData->SifsTime = 0x0e0e0a0a; */ + rtl92c_set_cck_sifs(hw, 0xa, 0xa); + rtl92c_set_ofdm_sifs(hw, 0xe, 0xe); + /* Set CCK/OFDM SIFS to be 10us. */ + rtl_write_word(rtlpriv, REG_SIFS_CCK, 0x0a0a); + rtl_write_word(rtlpriv, REG_SIFS_OFDM, 0x1010); + rtl_write_word(rtlpriv, REG_PROT_MODE_CTRL, 0x0204); + rtl_write_dword(rtlpriv, REG_BAR_MODE_CTRL, 0x014004); + /* TXOP */ + rtl_write_dword(rtlpriv, REG_EDCA_BE_PARAM, 0x005EA42B); + rtl_write_dword(rtlpriv, REG_EDCA_BK_PARAM, 0x0000A44F); + rtl_write_dword(rtlpriv, REG_EDCA_VI_PARAM, 0x005EA324); + rtl_write_dword(rtlpriv, REG_EDCA_VO_PARAM, 0x002FA226); + /* PIFS */ + rtl_write_byte(rtlpriv, REG_PIFS, 0x1C); + /* AGGR BREAK TIME Register */ + rtl_write_byte(rtlpriv, REG_AGGR_BREAK_TIME, 0x16); + rtl_write_word(rtlpriv, REG_NAV_PROT_LEN, 0x0040); + rtl_write_byte(rtlpriv, REG_BCNDMATIM, 0x02); + rtl_write_byte(rtlpriv, REG_ATIMWND, 0x02); +} + +void rtl92c_init_ampdu_aggregation(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_dword(rtlpriv, REG_AGGLEN_LMT, 0x99997631); + rtl_write_byte(rtlpriv, REG_AGGR_BREAK_TIME, 0x16); + /* init AMPDU aggregation number, tuning for Tx's TP, */ + rtl_write_word(rtlpriv, 0x4CA, 0x0708); +} + +void rtl92c_init_beacon_max_error(struct ieee80211_hw *hw, bool infra_mode) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_byte(rtlpriv, REG_BCN_MAX_ERR, 0xFF); +} + +void rtl92c_init_rdg_setting(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_byte(rtlpriv, REG_RD_CTRL, 0xFF); + rtl_write_word(rtlpriv, REG_RD_NAV_NXT, 0x200); + rtl_write_byte(rtlpriv, REG_RD_RESP_PKT_TH, 0x05); +} + +void rtl92c_init_retry_function(struct ieee80211_hw *hw) +{ + u8 value8; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + value8 = rtl_read_byte(rtlpriv, REG_FWHW_TXQ_CTRL); + value8 |= EN_AMPDU_RTY_NEW; + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL, value8); + /* Set ACK timeout */ + rtl_write_byte(rtlpriv, REG_ACKTO, 0x40); +} + +void rtl92c_init_beacon_parameters(struct ieee80211_hw *hw, + enum version_8192c version) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + + rtl_write_word(rtlpriv, REG_TBTT_PROHIBIT, 0x6404);/* ms */ + rtl_write_byte(rtlpriv, REG_DRVERLYINT, DRIVER_EARLY_INT_TIME);/*ms*/ + rtl_write_byte(rtlpriv, REG_BCNDMATIM, BCN_DMA_ATIME_INT_TIME); + if (IS_NORMAL_CHIP(rtlhal->version)) + rtl_write_word(rtlpriv, REG_BCNTCFG, 0x660F); + else + rtl_write_word(rtlpriv, REG_BCNTCFG, 0x66FF); +} + +void rtl92c_disable_fast_edca(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_word(rtlpriv, REG_FAST_EDCA_CTRL, 0); +} + +void rtl92c_set_min_space(struct ieee80211_hw *hw, bool is2T) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 value = is2T ? MAX_MSS_DENSITY_2T : MAX_MSS_DENSITY_1T; + + rtl_write_byte(rtlpriv, REG_AMPDU_MIN_SPACE, value); +} + +u16 rtl92c_get_mgt_filter(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + return rtl_read_word(rtlpriv, REG_RXFLTMAP0); +} + +void rtl92c_set_mgt_filter(struct ieee80211_hw *hw, u16 filter) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_word(rtlpriv, REG_RXFLTMAP0, filter); +} + +u16 rtl92c_get_ctrl_filter(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + return rtl_read_word(rtlpriv, REG_RXFLTMAP1); +} + +void rtl92c_set_ctrl_filter(struct ieee80211_hw *hw, u16 filter) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_word(rtlpriv, REG_RXFLTMAP1, filter); +} + +u16 rtl92c_get_data_filter(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + return rtl_read_word(rtlpriv, REG_RXFLTMAP2); +} + +void rtl92c_set_data_filter(struct ieee80211_hw *hw, u16 filter) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_word(rtlpriv, REG_RXFLTMAP2, filter); +} +/*==============================================================*/ + +static u8 _rtl92c_query_rxpwrpercentage(char antpower) +{ + if ((antpower <= -100) || (antpower >= 20)) + return 0; + else if (antpower >= 0) + return 100; + else + return 100 + antpower; +} + +static u8 _rtl92c_evm_db_to_percentage(char value) +{ + char ret_val; + + ret_val = value; + if (ret_val >= 0) + ret_val = 0; + if (ret_val <= -33) + ret_val = -33; + ret_val = 0 - ret_val; + ret_val *= 3; + if (ret_val == 99) + ret_val = 100; + return ret_val; +} + +static long _rtl92c_translate_todbm(struct ieee80211_hw *hw, + u8 signal_strength_index) +{ + long signal_power; + + signal_power = (long)((signal_strength_index + 1) >> 1); + signal_power -= 95; + return signal_power; +} + +static long _rtl92c_signal_scale_mapping(struct ieee80211_hw *hw, + long currsig) +{ + long retsig; + + if (currsig >= 61 && currsig <= 100) + retsig = 90 + ((currsig - 60) / 4); + else if (currsig >= 41 && currsig <= 60) + retsig = 78 + ((currsig - 40) / 2); + else if (currsig >= 31 && currsig <= 40) + retsig = 66 + (currsig - 30); + else if (currsig >= 21 && currsig <= 30) + retsig = 54 + (currsig - 20); + else if (currsig >= 5 && currsig <= 20) + retsig = 42 + (((currsig - 5) * 2) / 3); + else if (currsig == 4) + retsig = 36; + else if (currsig == 3) + retsig = 27; + else if (currsig == 2) + retsig = 18; + else if (currsig == 1) + retsig = 9; + else + retsig = currsig; + return retsig; +} + +static void _rtl92c_query_rxphystatus(struct ieee80211_hw *hw, + struct rtl_stats *pstats, + struct rx_desc_92c *pdesc, + struct rx_fwinfo_92c *p_drvinfo, + bool packet_match_bssid, + bool packet_toself, + bool packet_beacon) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct phy_sts_cck_8192s_t *cck_buf; + s8 rx_pwr_all = 0, rx_pwr[4]; + u8 rf_rx_num = 0, evm, pwdb_all; + u8 i, max_spatial_stream; + u32 rssi, total_rssi = 0; + bool in_powersavemode = false; + bool is_cck_rate; + + is_cck_rate = RX_HAL_IS_CCK_RATE(pdesc); + pstats->packet_matchbssid = packet_match_bssid; + pstats->packet_toself = packet_toself; + pstats->is_cck = is_cck_rate; + pstats->packet_beacon = packet_beacon; + pstats->is_cck = is_cck_rate; + pstats->RX_SIGQ[0] = -1; + pstats->RX_SIGQ[1] = -1; + if (is_cck_rate) { + u8 report, cck_highpwr; + cck_buf = (struct phy_sts_cck_8192s_t *)p_drvinfo; + if (!in_powersavemode) + cck_highpwr = rtlphy->cck_high_power; + else + cck_highpwr = false; + if (!cck_highpwr) { + u8 cck_agc_rpt = cck_buf->cck_agc_rpt; + report = cck_buf->cck_agc_rpt & 0xc0; + report = report >> 6; + switch (report) { + case 0x3: + rx_pwr_all = -46 - (cck_agc_rpt & 0x3e); + break; + case 0x2: + rx_pwr_all = -26 - (cck_agc_rpt & 0x3e); + break; + case 0x1: + rx_pwr_all = -12 - (cck_agc_rpt & 0x3e); + break; + case 0x0: + rx_pwr_all = 16 - (cck_agc_rpt & 0x3e); + break; + } + } else { + u8 cck_agc_rpt = cck_buf->cck_agc_rpt; + report = p_drvinfo->cfosho[0] & 0x60; + report = report >> 5; + switch (report) { + case 0x3: + rx_pwr_all = -46 - ((cck_agc_rpt & 0x1f) << 1); + break; + case 0x2: + rx_pwr_all = -26 - ((cck_agc_rpt & 0x1f) << 1); + break; + case 0x1: + rx_pwr_all = -12 - ((cck_agc_rpt & 0x1f) << 1); + break; + case 0x0: + rx_pwr_all = 16 - ((cck_agc_rpt & 0x1f) << 1); + break; + } + } + pwdb_all = _rtl92c_query_rxpwrpercentage(rx_pwr_all); + pstats->rx_pwdb_all = pwdb_all; + pstats->recvsignalpower = rx_pwr_all; + if (packet_match_bssid) { + u8 sq; + if (pstats->rx_pwdb_all > 40) + sq = 100; + else { + sq = cck_buf->sq_rpt; + if (sq > 64) + sq = 0; + else if (sq < 20) + sq = 100; + else + sq = ((64 - sq) * 100) / 44; + } + pstats->signalquality = sq; + pstats->RX_SIGQ[0] = sq; + pstats->RX_SIGQ[1] = -1; + } + } else { + rtlpriv->dm.rfpath_rxenable[0] = + rtlpriv->dm.rfpath_rxenable[1] = true; + for (i = RF90_PATH_A; i < RF90_PATH_MAX; i++) { + if (rtlpriv->dm.rfpath_rxenable[i]) + rf_rx_num++; + rx_pwr[i] = + ((p_drvinfo->gain_trsw[i] & 0x3f) * 2) - 110; + rssi = _rtl92c_query_rxpwrpercentage(rx_pwr[i]); + total_rssi += rssi; + rtlpriv->stats.rx_snr_db[i] = + (long)(p_drvinfo->rxsnr[i] / 2); + + if (packet_match_bssid) + pstats->rx_mimo_signalstrength[i] = (u8) rssi; + } + rx_pwr_all = ((p_drvinfo->pwdb_all >> 1) & 0x7f) - 110; + pwdb_all = _rtl92c_query_rxpwrpercentage(rx_pwr_all); + pstats->rx_pwdb_all = pwdb_all; + pstats->rxpower = rx_pwr_all; + pstats->recvsignalpower = rx_pwr_all; + if (GET_RX_DESC_RX_MCS(pdesc) && + GET_RX_DESC_RX_MCS(pdesc) >= DESC92_RATEMCS8 && + GET_RX_DESC_RX_MCS(pdesc) <= DESC92_RATEMCS15) + max_spatial_stream = 2; + else + max_spatial_stream = 1; + for (i = 0; i < max_spatial_stream; i++) { + evm = _rtl92c_evm_db_to_percentage(p_drvinfo->rxevm[i]); + if (packet_match_bssid) { + if (i == 0) + pstats->signalquality = + (u8) (evm & 0xff); + pstats->RX_SIGQ[i] = + (u8) (evm & 0xff); + } + } + } + if (is_cck_rate) + pstats->signalstrength = + (u8) (_rtl92c_signal_scale_mapping(hw, pwdb_all)); + else if (rf_rx_num != 0) + pstats->signalstrength = + (u8) (_rtl92c_signal_scale_mapping + (hw, total_rssi /= rf_rx_num)); +} + +static void _rtl92c_process_ui_rssi(struct ieee80211_hw *hw, + struct rtl_stats *pstats) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u8 rfpath; + u32 last_rssi, tmpval; + + if (pstats->packet_toself || pstats->packet_beacon) { + rtlpriv->stats.rssi_calculate_cnt++; + if (rtlpriv->stats.ui_rssi.total_num++ >= + PHY_RSSI_SLID_WIN_MAX) { + rtlpriv->stats.ui_rssi.total_num = + PHY_RSSI_SLID_WIN_MAX; + last_rssi = + rtlpriv->stats.ui_rssi.elements[rtlpriv-> + stats.ui_rssi.index]; + rtlpriv->stats.ui_rssi.total_val -= last_rssi; + } + rtlpriv->stats.ui_rssi.total_val += pstats->signalstrength; + rtlpriv->stats.ui_rssi.elements[rtlpriv->stats.ui_rssi. + index++] = pstats->signalstrength; + if (rtlpriv->stats.ui_rssi.index >= PHY_RSSI_SLID_WIN_MAX) + rtlpriv->stats.ui_rssi.index = 0; + tmpval = rtlpriv->stats.ui_rssi.total_val / + rtlpriv->stats.ui_rssi.total_num; + rtlpriv->stats.signal_strength = + _rtl92c_translate_todbm(hw, (u8) tmpval); + pstats->rssi = rtlpriv->stats.signal_strength; + } + if (!pstats->is_cck && pstats->packet_toself) { + for (rfpath = RF90_PATH_A; rfpath < rtlphy->num_total_rfpath; + rfpath++) { + if (!rtl8192_phy_check_is_legal_rfpath(hw, rfpath)) + continue; + if (rtlpriv->stats.rx_rssi_percentage[rfpath] == 0) { + rtlpriv->stats.rx_rssi_percentage[rfpath] = + pstats->rx_mimo_signalstrength[rfpath]; + } + if (pstats->rx_mimo_signalstrength[rfpath] > + rtlpriv->stats.rx_rssi_percentage[rfpath]) { + rtlpriv->stats.rx_rssi_percentage[rfpath] = + ((rtlpriv->stats. + rx_rssi_percentage[rfpath] * + (RX_SMOOTH_FACTOR - 1)) + + (pstats->rx_mimo_signalstrength[rfpath])) / + (RX_SMOOTH_FACTOR); + + rtlpriv->stats.rx_rssi_percentage[rfpath] = + rtlpriv->stats.rx_rssi_percentage[rfpath] + + 1; + } else { + rtlpriv->stats.rx_rssi_percentage[rfpath] = + ((rtlpriv->stats. + rx_rssi_percentage[rfpath] * + (RX_SMOOTH_FACTOR - 1)) + + (pstats->rx_mimo_signalstrength[rfpath])) / + (RX_SMOOTH_FACTOR); + } + } + } +} + +static void _rtl92c_update_rxsignalstatistics(struct ieee80211_hw *hw, + struct rtl_stats *pstats) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int weighting = 0; + + if (rtlpriv->stats.recv_signal_power == 0) + rtlpriv->stats.recv_signal_power = pstats->recvsignalpower; + if (pstats->recvsignalpower > rtlpriv->stats.recv_signal_power) + weighting = 5; + else if (pstats->recvsignalpower < rtlpriv->stats.recv_signal_power) + weighting = (-5); + rtlpriv->stats.recv_signal_power = + (rtlpriv->stats.recv_signal_power * 5 + + pstats->recvsignalpower + weighting) / 6; +} + +static void _rtl92c_process_pwdb(struct ieee80211_hw *hw, + struct rtl_stats *pstats) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + long undec_sm_pwdb = 0; + + if (mac->opmode == NL80211_IFTYPE_ADHOC) { + return; + } else { + undec_sm_pwdb = rtlpriv->dm.undec_sm_pwdb; + } + if (pstats->packet_toself || pstats->packet_beacon) { + if (undec_sm_pwdb < 0) + undec_sm_pwdb = pstats->rx_pwdb_all; + if (pstats->rx_pwdb_all > (u32) undec_sm_pwdb) { + undec_sm_pwdb = (((undec_sm_pwdb) * + (RX_SMOOTH_FACTOR - 1)) + + (pstats->rx_pwdb_all)) / (RX_SMOOTH_FACTOR); + undec_sm_pwdb += 1; + } else { + undec_sm_pwdb = (((undec_sm_pwdb) * + (RX_SMOOTH_FACTOR - 1)) + + (pstats->rx_pwdb_all)) / (RX_SMOOTH_FACTOR); + } + rtlpriv->dm.undec_sm_pwdb = undec_sm_pwdb; + _rtl92c_update_rxsignalstatistics(hw, pstats); + } +} + +static void _rtl92c_process_LINK_Q(struct ieee80211_hw *hw, + struct rtl_stats *pstats) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 last_evm = 0, n_stream, tmpval; + + if (pstats->signalquality != 0) { + if (pstats->packet_toself || pstats->packet_beacon) { + if (rtlpriv->stats.LINK_Q.total_num++ >= + PHY_LINKQUALITY_SLID_WIN_MAX) { + rtlpriv->stats.LINK_Q.total_num = + PHY_LINKQUALITY_SLID_WIN_MAX; + last_evm = + rtlpriv->stats.LINK_Q.elements + [rtlpriv->stats.LINK_Q.index]; + rtlpriv->stats.LINK_Q.total_val -= + last_evm; + } + rtlpriv->stats.LINK_Q.total_val += + pstats->signalquality; + rtlpriv->stats.LINK_Q.elements + [rtlpriv->stats.LINK_Q.index++] = + pstats->signalquality; + if (rtlpriv->stats.LINK_Q.index >= + PHY_LINKQUALITY_SLID_WIN_MAX) + rtlpriv->stats.LINK_Q.index = 0; + tmpval = rtlpriv->stats.LINK_Q.total_val / + rtlpriv->stats.LINK_Q.total_num; + rtlpriv->stats.signal_quality = tmpval; + rtlpriv->stats.last_sigstrength_inpercent = tmpval; + for (n_stream = 0; n_stream < 2; + n_stream++) { + if (pstats->RX_SIGQ[n_stream] != -1) { + if (!rtlpriv->stats.RX_EVM[n_stream]) { + rtlpriv->stats.RX_EVM[n_stream] + = pstats->RX_SIGQ[n_stream]; + } + rtlpriv->stats.RX_EVM[n_stream] = + ((rtlpriv->stats.RX_EVM + [n_stream] * + (RX_SMOOTH_FACTOR - 1)) + + (pstats->RX_SIGQ + [n_stream] * 1)) / + (RX_SMOOTH_FACTOR); + } + } + } + } else { + ; + } +} + +static void _rtl92c_process_phyinfo(struct ieee80211_hw *hw, + u8 *buffer, + struct rtl_stats *pcurrent_stats) +{ + if (!pcurrent_stats->packet_matchbssid && + !pcurrent_stats->packet_beacon) + return; + _rtl92c_process_ui_rssi(hw, pcurrent_stats); + _rtl92c_process_pwdb(hw, pcurrent_stats); + _rtl92c_process_LINK_Q(hw, pcurrent_stats); +} + +void rtl92c_translate_rx_signal_stuff(struct ieee80211_hw *hw, + struct sk_buff *skb, + struct rtl_stats *pstats, + struct rx_desc_92c *pdesc, + struct rx_fwinfo_92c *p_drvinfo) +{ + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct ieee80211_hdr *hdr; + u8 *tmp_buf; + u8 *praddr; + __le16 fc; + u16 type, cpu_fc; + bool packet_matchbssid, packet_toself, packet_beacon = false; + + tmp_buf = skb->data + pstats->rx_drvinfo_size + pstats->rx_bufshift; + hdr = (struct ieee80211_hdr *)tmp_buf; + fc = hdr->frame_control; + cpu_fc = le16_to_cpu(fc); + type = WLAN_FC_GET_TYPE(fc); + praddr = hdr->addr1; + packet_matchbssid = + ((IEEE80211_FTYPE_CTL != type) && + ether_addr_equal(mac->bssid, + (cpu_fc & IEEE80211_FCTL_TODS) ? hdr->addr1 : + (cpu_fc & IEEE80211_FCTL_FROMDS) ? hdr->addr2 : + hdr->addr3) && + (!pstats->hwerror) && (!pstats->crc) && (!pstats->icv)); + + packet_toself = packet_matchbssid && + ether_addr_equal(praddr, rtlefuse->dev_addr); + if (ieee80211_is_beacon(fc)) + packet_beacon = true; + _rtl92c_query_rxphystatus(hw, pstats, pdesc, p_drvinfo, + packet_matchbssid, packet_toself, + packet_beacon); + _rtl92c_process_phyinfo(hw, tmp_buf, pstats); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/mac.h b/drivers/net/wireless/rtlwifi/rtl8192cu/mac.h new file mode 100644 index 0000000..bf53652 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/mac.h @@ -0,0 +1,174 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92C_MAC_H__ +#define __RTL92C_MAC_H__ + +#define LLT_LAST_ENTRY_OF_TX_PKT_BUFFER 255 +#define DRIVER_EARLY_INT_TIME 0x05 +#define BCN_DMA_ATIME_INT_TIME 0x02 + +void rtl92c_read_chip_version(struct ieee80211_hw *hw); +bool rtl92c_llt_write(struct ieee80211_hw *hw, u32 address, u32 data); +bool rtl92c_init_llt_table(struct ieee80211_hw *hw, u32 boundary); +void rtl92c_set_key(struct ieee80211_hw *hw, u32 key_index, + u8 *p_macaddr, bool is_group, u8 enc_algo, + bool is_wepkey, bool clear_all); +void rtl92c_enable_interrupt(struct ieee80211_hw *hw); +void rtl92c_disable_interrupt(struct ieee80211_hw *hw); +void rtl92c_set_qos(struct ieee80211_hw *hw, int aci); + + +/*--------------------------------------------------------------- + * Hardware init functions + *---------------------------------------------------------------*/ +void rtl92c_set_mac_addr(struct ieee80211_hw *hw, const u8 *addr); +void rtl92c_init_interrupt(struct ieee80211_hw *hw); +void rtl92c_init_driver_info_size(struct ieee80211_hw *hw, u8 size); + +int rtl92c_set_network_type(struct ieee80211_hw *hw, enum nl80211_iftype type); +void rtl92c_init_network_type(struct ieee80211_hw *hw); +void rtl92c_init_adaptive_ctrl(struct ieee80211_hw *hw); +void rtl92c_init_rate_fallback(struct ieee80211_hw *hw); + +void rtl92c_init_edca_param(struct ieee80211_hw *hw, + u16 queue, + u16 txop, + u8 ecwmax, + u8 ecwmin, + u8 aifs); + +void rtl92c_init_edca(struct ieee80211_hw *hw); +void rtl92c_init_ampdu_aggregation(struct ieee80211_hw *hw); +void rtl92c_init_beacon_max_error(struct ieee80211_hw *hw, bool infra_mode); +void rtl92c_init_rdg_setting(struct ieee80211_hw *hw); +void rtl92c_init_retry_function(struct ieee80211_hw *hw); + +void rtl92c_init_beacon_parameters(struct ieee80211_hw *hw, + enum version_8192c version); + +void rtl92c_disable_fast_edca(struct ieee80211_hw *hw); +void rtl92c_set_min_space(struct ieee80211_hw *hw, bool is2T); + +/* For filter */ +u16 rtl92c_get_mgt_filter(struct ieee80211_hw *hw); +void rtl92c_set_mgt_filter(struct ieee80211_hw *hw, u16 filter); +u16 rtl92c_get_ctrl_filter(struct ieee80211_hw *hw); +void rtl92c_set_ctrl_filter(struct ieee80211_hw *hw, u16 filter); +u16 rtl92c_get_data_filter(struct ieee80211_hw *hw); +void rtl92c_set_data_filter(struct ieee80211_hw *hw, u16 filter); + + +u32 rtl92c_get_txdma_status(struct ieee80211_hw *hw); + +struct rx_fwinfo_92c { + u8 gain_trsw[4]; + u8 pwdb_all; + u8 cfosho[4]; + u8 cfotail[4]; + char rxevm[2]; + char rxsnr[4]; + u8 pdsnr[2]; + u8 csi_current[2]; + u8 csi_target[2]; + u8 sigevm; + u8 max_ex_pwr; + u8 ex_intf_flag:1; + u8 sgi_en:1; + u8 rxsc:2; + u8 reserve:4; +} __packed; + +struct rx_desc_92c { + u32 length:14; + u32 crc32:1; + u32 icverror:1; + u32 drv_infosize:4; + u32 security:3; + u32 qos:1; + u32 shift:2; + u32 phystatus:1; + u32 swdec:1; + u32 lastseg:1; + u32 firstseg:1; + u32 eor:1; + u32 own:1; + u32 macid:5; /* word 1 */ + u32 tid:4; + u32 hwrsvd:5; + u32 paggr:1; + u32 faggr:1; + u32 a1_fit:4; + u32 a2_fit:4; + u32 pam:1; + u32 pwr:1; + u32 moredata:1; + u32 morefrag:1; + u32 type:2; + u32 mc:1; + u32 bc:1; + u32 seq:12; /* word 2 */ + u32 frag:4; + u32 nextpktlen:14; + u32 nextind:1; + u32 rsvd:1; + u32 rxmcs:6; /* word 3 */ + u32 rxht:1; + u32 amsdu:1; + u32 splcp:1; + u32 bandwidth:1; + u32 htc:1; + u32 tcpchk_rpt:1; + u32 ipcchk_rpt:1; + u32 tcpchk_valid:1; + u32 hwpcerr:1; + u32 hwpcind:1; + u32 iv0:16; + u32 iv1; /* word 4 */ + u32 tsfl; /* word 5 */ + u32 bufferaddress; /* word 6 */ + u32 bufferaddress64; /* word 7 */ +} __packed; + +enum rtl_desc_qsel rtl92c_map_hwqueue_to_fwqueue(u16 fc, + unsigned int + skb_queue); +void rtl92c_translate_rx_signal_stuff(struct ieee80211_hw *hw, + struct sk_buff *skb, + struct rtl_stats *pstats, + struct rx_desc_92c *pdesc, + struct rx_fwinfo_92c *p_drvinfo); + +/*--------------------------------------------------------------- + * Card disable functions + *---------------------------------------------------------------*/ + + + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/phy.c b/drivers/net/wireless/rtlwifi/rtl8192cu/phy.c new file mode 100644 index 0000000..34e5630 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/phy.c @@ -0,0 +1,593 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "../ps.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "rf.h" +#include "dm.h" +#include "table.h" + +u32 rtl92cu_phy_query_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, u32 bitmask) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 original_value, readback_value, bitshift; + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), rfpath(%#x), bitmask(%#x)\n", + regaddr, rfpath, bitmask); + if (rtlphy->rf_mode != RF_OP_BY_FW) { + original_value = _rtl92c_phy_rf_serial_read(hw, + rfpath, regaddr); + } else { + original_value = _rtl92c_phy_fw_rf_serial_read(hw, + rfpath, regaddr); + } + bitshift = _rtl92c_phy_calculate_bit_shift(bitmask); + readback_value = (original_value & bitmask) >> bitshift; + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), rfpath(%#x), bitmask(%#x), original_value(%#x)\n", + regaddr, rfpath, bitmask, original_value); + return readback_value; +} + +void rtl92cu_phy_set_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, + u32 regaddr, u32 bitmask, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 original_value, bitshift; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x), rfpath(%#x)\n", + regaddr, bitmask, data, rfpath); + if (rtlphy->rf_mode != RF_OP_BY_FW) { + if (bitmask != RFREG_OFFSET_MASK) { + original_value = _rtl92c_phy_rf_serial_read(hw, + rfpath, + regaddr); + bitshift = _rtl92c_phy_calculate_bit_shift(bitmask); + data = + ((original_value & (~bitmask)) | + (data << bitshift)); + } + _rtl92c_phy_rf_serial_write(hw, rfpath, regaddr, data); + } else { + if (bitmask != RFREG_OFFSET_MASK) { + original_value = _rtl92c_phy_fw_rf_serial_read(hw, + rfpath, + regaddr); + bitshift = _rtl92c_phy_calculate_bit_shift(bitmask); + data = + ((original_value & (~bitmask)) | + (data << bitshift)); + } + _rtl92c_phy_fw_rf_serial_write(hw, rfpath, regaddr, data); + } + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x), rfpath(%#x)\n", + regaddr, bitmask, data, rfpath); +} + +bool rtl92cu_phy_mac_config(struct ieee80211_hw *hw) +{ + bool rtstatus; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + bool is92c = IS_92C_SERIAL(rtlhal->version); + + rtstatus = _rtl92cu_phy_config_mac_with_headerfile(hw); + if (is92c && IS_HARDWARE_TYPE_8192CE(rtlhal)) + rtl_write_byte(rtlpriv, 0x14, 0x71); + return rtstatus; +} + +bool rtl92cu_phy_bb_config(struct ieee80211_hw *hw) +{ + bool rtstatus = true; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u16 regval; + u8 b_reg_hwparafile = 1; + + _rtl92c_phy_init_bb_rf_register_definition(hw); + regval = rtl_read_word(rtlpriv, REG_SYS_FUNC_EN); + rtl_write_word(rtlpriv, REG_SYS_FUNC_EN, regval | BIT(13) | + BIT(0) | BIT(1)); + rtl_write_byte(rtlpriv, REG_AFE_PLL_CTRL, 0x83); + rtl_write_byte(rtlpriv, REG_AFE_PLL_CTRL + 1, 0xdb); + rtl_write_byte(rtlpriv, REG_RF_CTRL, RF_EN | RF_RSTB | RF_SDMRSTB); + if (IS_HARDWARE_TYPE_8192CE(rtlhal)) { + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, FEN_PPLL | FEN_PCIEA | + FEN_DIO_PCIE | FEN_BB_GLB_RSTn | FEN_BBRSTB); + } else if (IS_HARDWARE_TYPE_8192CU(rtlhal)) { + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, FEN_USBA | FEN_USBD | + FEN_BB_GLB_RSTn | FEN_BBRSTB); + rtl_write_byte(rtlpriv, REG_LDOHCI12_CTRL, 0x0f); + } + rtl_write_byte(rtlpriv, REG_AFE_XTAL_CTRL + 1, 0x80); + if (b_reg_hwparafile == 1) + rtstatus = _rtl92c_phy_bb8192c_config_parafile(hw); + return rtstatus; +} + +bool _rtl92cu_phy_config_mac_with_headerfile(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 i; + u32 arraylength; + u32 *ptrarray; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "Read Rtl819XMACPHY_Array\n"); + arraylength = rtlphy->hwparam_tables[MAC_REG].length ; + ptrarray = rtlphy->hwparam_tables[MAC_REG].pdata; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "Img:RTL8192CEMAC_2T_ARRAY\n"); + for (i = 0; i < arraylength; i = i + 2) + rtl_write_byte(rtlpriv, ptrarray[i], (u8) ptrarray[i + 1]); + return true; +} + +bool _rtl92cu_phy_config_bb_with_headerfile(struct ieee80211_hw *hw, + u8 configtype) +{ + int i; + u32 *phy_regarray_table; + u32 *agctab_array_table; + u16 phy_reg_arraylen, agctab_arraylen; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + if (IS_92C_SERIAL(rtlhal->version)) { + agctab_arraylen = rtlphy->hwparam_tables[AGCTAB_2T].length; + agctab_array_table = rtlphy->hwparam_tables[AGCTAB_2T].pdata; + phy_reg_arraylen = rtlphy->hwparam_tables[PHY_REG_2T].length; + phy_regarray_table = rtlphy->hwparam_tables[PHY_REG_2T].pdata; + } else { + agctab_arraylen = rtlphy->hwparam_tables[AGCTAB_1T].length; + agctab_array_table = rtlphy->hwparam_tables[AGCTAB_1T].pdata; + phy_reg_arraylen = rtlphy->hwparam_tables[PHY_REG_1T].length; + phy_regarray_table = rtlphy->hwparam_tables[PHY_REG_1T].pdata; + } + if (configtype == BASEBAND_CONFIG_PHY_REG) { + for (i = 0; i < phy_reg_arraylen; i = i + 2) { + if (phy_regarray_table[i] == 0xfe) + mdelay(50); + else if (phy_regarray_table[i] == 0xfd) + mdelay(5); + else if (phy_regarray_table[i] == 0xfc) + mdelay(1); + else if (phy_regarray_table[i] == 0xfb) + udelay(50); + else if (phy_regarray_table[i] == 0xfa) + udelay(5); + else if (phy_regarray_table[i] == 0xf9) + udelay(1); + rtl_set_bbreg(hw, phy_regarray_table[i], MASKDWORD, + phy_regarray_table[i + 1]); + udelay(1); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "The phy_regarray_table[0] is %x Rtl819XPHY_REGArray[1] is %x\n", + phy_regarray_table[i], + phy_regarray_table[i + 1]); + } + } else if (configtype == BASEBAND_CONFIG_AGC_TAB) { + for (i = 0; i < agctab_arraylen; i = i + 2) { + rtl_set_bbreg(hw, agctab_array_table[i], MASKDWORD, + agctab_array_table[i + 1]); + udelay(1); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "The agctab_array_table[0] is %x Rtl819XPHY_REGArray[1] is %x\n", + agctab_array_table[i], + agctab_array_table[i + 1]); + } + } + return true; +} + +bool _rtl92cu_phy_config_bb_with_pgheaderfile(struct ieee80211_hw *hw, + u8 configtype) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + int i; + u32 *phy_regarray_table_pg; + u16 phy_regarray_pg_len; + + rtlphy->pwrgroup_cnt = 0; + phy_regarray_pg_len = rtlphy->hwparam_tables[PHY_REG_PG].length; + phy_regarray_table_pg = rtlphy->hwparam_tables[PHY_REG_PG].pdata; + if (configtype == BASEBAND_CONFIG_PHY_REG) { + for (i = 0; i < phy_regarray_pg_len; i = i + 3) { + if (phy_regarray_table_pg[i] == 0xfe) + mdelay(50); + else if (phy_regarray_table_pg[i] == 0xfd) + mdelay(5); + else if (phy_regarray_table_pg[i] == 0xfc) + mdelay(1); + else if (phy_regarray_table_pg[i] == 0xfb) + udelay(50); + else if (phy_regarray_table_pg[i] == 0xfa) + udelay(5); + else if (phy_regarray_table_pg[i] == 0xf9) + udelay(1); + _rtl92c_store_pwrIndex_diffrate_offset(hw, + phy_regarray_table_pg[i], + phy_regarray_table_pg[i + 1], + phy_regarray_table_pg[i + 2]); + } + } else { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "configtype != BaseBand_Config_PHY_REG\n"); + } + return true; +} + +bool rtl92cu_phy_config_rf_with_headerfile(struct ieee80211_hw *hw, + enum radio_path rfpath) +{ + int i; + u32 *radioa_array_table; + u32 *radiob_array_table; + u16 radioa_arraylen, radiob_arraylen; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + if (IS_92C_SERIAL(rtlhal->version)) { + radioa_arraylen = rtlphy->hwparam_tables[RADIOA_2T].length; + radioa_array_table = rtlphy->hwparam_tables[RADIOA_2T].pdata; + radiob_arraylen = rtlphy->hwparam_tables[RADIOB_2T].length; + radiob_array_table = rtlphy->hwparam_tables[RADIOB_2T].pdata; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Radio_A:RTL8192CERADIOA_2TARRAY\n"); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Radio_B:RTL8192CE_RADIOB_2TARRAY\n"); + } else { + radioa_arraylen = rtlphy->hwparam_tables[RADIOA_1T].length; + radioa_array_table = rtlphy->hwparam_tables[RADIOA_1T].pdata; + radiob_arraylen = rtlphy->hwparam_tables[RADIOB_1T].length; + radiob_array_table = rtlphy->hwparam_tables[RADIOB_1T].pdata; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Radio_A:RTL8192CE_RADIOA_1TARRAY\n"); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Radio_B:RTL8192CE_RADIOB_1TARRAY\n"); + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "Radio No %x\n", rfpath); + switch (rfpath) { + case RF90_PATH_A: + for (i = 0; i < radioa_arraylen; i = i + 2) { + if (radioa_array_table[i] == 0xfe) + mdelay(50); + else if (radioa_array_table[i] == 0xfd) + mdelay(5); + else if (radioa_array_table[i] == 0xfc) + mdelay(1); + else if (radioa_array_table[i] == 0xfb) + udelay(50); + else if (radioa_array_table[i] == 0xfa) + udelay(5); + else if (radioa_array_table[i] == 0xf9) + udelay(1); + else { + rtl_set_rfreg(hw, rfpath, radioa_array_table[i], + RFREG_OFFSET_MASK, + radioa_array_table[i + 1]); + udelay(1); + } + } + break; + case RF90_PATH_B: + for (i = 0; i < radiob_arraylen; i = i + 2) { + if (radiob_array_table[i] == 0xfe) { + mdelay(50); + } else if (radiob_array_table[i] == 0xfd) + mdelay(5); + else if (radiob_array_table[i] == 0xfc) + mdelay(1); + else if (radiob_array_table[i] == 0xfb) + udelay(50); + else if (radiob_array_table[i] == 0xfa) + udelay(5); + else if (radiob_array_table[i] == 0xf9) + udelay(1); + else { + rtl_set_rfreg(hw, rfpath, radiob_array_table[i], + RFREG_OFFSET_MASK, + radiob_array_table[i + 1]); + udelay(1); + } + } + break; + case RF90_PATH_C: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + case RF90_PATH_D: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + return true; +} + +void rtl92cu_phy_set_bw_mode_callback(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u8 reg_bw_opmode; + u8 reg_prsr_rsc; + + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, "Switch to %s bandwidth\n", + rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20 ? + "20MHz" : "40MHz"); + if (is_hal_stop(rtlhal)) { + rtlphy->set_bwmode_inprogress = false; + return; + } + reg_bw_opmode = rtl_read_byte(rtlpriv, REG_BWOPMODE); + reg_prsr_rsc = rtl_read_byte(rtlpriv, REG_RRSR + 2); + switch (rtlphy->current_chan_bw) { + case HT_CHANNEL_WIDTH_20: + reg_bw_opmode |= BW_OPMODE_20MHZ; + rtl_write_byte(rtlpriv, REG_BWOPMODE, reg_bw_opmode); + break; + case HT_CHANNEL_WIDTH_20_40: + reg_bw_opmode &= ~BW_OPMODE_20MHZ; + rtl_write_byte(rtlpriv, REG_BWOPMODE, reg_bw_opmode); + reg_prsr_rsc = + (reg_prsr_rsc & 0x90) | (mac->cur_40_prime_sc << 5); + rtl_write_byte(rtlpriv, REG_RRSR + 2, reg_prsr_rsc); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", rtlphy->current_chan_bw); + break; + } + switch (rtlphy->current_chan_bw) { + case HT_CHANNEL_WIDTH_20: + rtl_set_bbreg(hw, RFPGA0_RFMOD, BRFMOD, 0x0); + rtl_set_bbreg(hw, RFPGA1_RFMOD, BRFMOD, 0x0); + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER2, BIT(10), 1); + break; + case HT_CHANNEL_WIDTH_20_40: + rtl_set_bbreg(hw, RFPGA0_RFMOD, BRFMOD, 0x1); + rtl_set_bbreg(hw, RFPGA1_RFMOD, BRFMOD, 0x1); + rtl_set_bbreg(hw, RCCK0_SYSTEM, BCCK_SIDEBAND, + (mac->cur_40_prime_sc >> 1)); + rtl_set_bbreg(hw, ROFDM1_LSTF, 0xC00, mac->cur_40_prime_sc); + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER2, BIT(10), 0); + rtl_set_bbreg(hw, 0x818, (BIT(26) | BIT(27)), + (mac->cur_40_prime_sc == + HAL_PRIME_CHNL_OFFSET_LOWER) ? 2 : 1); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", rtlphy->current_chan_bw); + break; + } + rtl92cu_phy_rf6052_set_bandwidth(hw, rtlphy->current_chan_bw); + rtlphy->set_bwmode_inprogress = false; + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, "<==\n"); +} + +void rtl92cu_bb_block_on(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + mutex_lock(&rtlpriv->io.bb_mutex); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BCCKEN, 0x1); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BOFDMEN, 0x1); + mutex_unlock(&rtlpriv->io.bb_mutex); +} + +void _rtl92cu_phy_lc_calibrate(struct ieee80211_hw *hw, bool is2t) +{ + u8 tmpreg; + u32 rf_a_mode = 0, rf_b_mode = 0, lc_cal; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + tmpreg = rtl_read_byte(rtlpriv, 0xd03); + + if ((tmpreg & 0x70) != 0) + rtl_write_byte(rtlpriv, 0xd03, tmpreg & 0x8F); + else + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0xFF); + + if ((tmpreg & 0x70) != 0) { + rf_a_mode = rtl_get_rfreg(hw, RF90_PATH_A, 0x00, MASK12BITS); + if (is2t) + rf_b_mode = rtl_get_rfreg(hw, RF90_PATH_B, 0x00, + MASK12BITS); + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, MASK12BITS, + (rf_a_mode & 0x8FFFF) | 0x10000); + if (is2t) + rtl_set_rfreg(hw, RF90_PATH_B, 0x00, MASK12BITS, + (rf_b_mode & 0x8FFFF) | 0x10000); + } + lc_cal = rtl_get_rfreg(hw, RF90_PATH_A, 0x18, MASK12BITS); + rtl_set_rfreg(hw, RF90_PATH_A, 0x18, MASK12BITS, lc_cal | 0x08000); + mdelay(100); + if ((tmpreg & 0x70) != 0) { + rtl_write_byte(rtlpriv, 0xd03, tmpreg); + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, MASK12BITS, rf_a_mode); + if (is2t) + rtl_set_rfreg(hw, RF90_PATH_B, 0x00, MASK12BITS, + rf_b_mode); + } else { + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0x00); + } +} + +static bool _rtl92cu_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + bool bresult = true; + u8 i, queue_id; + struct rtl8192_tx_ring *ring = NULL; + + switch (rfpwr_state) { + case ERFON: + if ((ppsc->rfpwr_state == ERFOFF) && + RT_IN_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC)) { + bool rtstatus; + u32 InitializeCount = 0; + + do { + InitializeCount++; + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "IPS Set eRf nic enable\n"); + rtstatus = rtl_ps_enable_nic(hw); + } while (!rtstatus && (InitializeCount < 10)); + RT_CLEAR_PS_LEVEL(ppsc, + RT_RF_OFF_LEVL_HALT_NIC); + } else { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "Set ERFON sleeped:%d ms\n", + jiffies_to_msecs(jiffies - + ppsc->last_sleep_jiffies)); + ppsc->last_awake_jiffies = jiffies; + rtl92ce_phy_set_rf_on(hw); + } + if (mac->link_state == MAC80211_LINKED) { + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_LINK); + } else { + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_NO_LINK); + } + break; + case ERFOFF: + for (queue_id = 0, i = 0; + queue_id < RTL_PCI_MAX_TX_QUEUE_COUNT;) { + ring = &pcipriv->dev.tx_ring[queue_id]; + if (skb_queue_len(&ring->queue) == 0 || + queue_id == BEACON_QUEUE) { + queue_id++; + continue; + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "eRf Off/Sleep: %d times TcbBusyQueue[%d] =%d before doze!\n", + i + 1, + queue_id, + skb_queue_len(&ring->queue)); + udelay(10); + i++; + } + if (i >= MAX_DOZE_WAITING_TIMES_9x) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "ERFOFF: %d times TcbBusyQueue[%d] = %d !\n", + MAX_DOZE_WAITING_TIMES_9x, + queue_id, + skb_queue_len(&ring->queue)); + break; + } + } + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_HALT_NIC) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "IPS Set eRf nic disable\n"); + rtl_ps_disable_nic(hw); + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + } else { + if (ppsc->rfoff_reason == RF_CHANGE_BY_IPS) { + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_NO_LINK); + } else { + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_POWER_OFF); + } + } + break; + case ERFSLEEP: + if (ppsc->rfpwr_state == ERFOFF) + return false; + for (queue_id = 0, i = 0; + queue_id < RTL_PCI_MAX_TX_QUEUE_COUNT;) { + ring = &pcipriv->dev.tx_ring[queue_id]; + if (skb_queue_len(&ring->queue) == 0) { + queue_id++; + continue; + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "eRf Off/Sleep: %d times TcbBusyQueue[%d] =%d before doze!\n", + i + 1, queue_id, + skb_queue_len(&ring->queue)); + udelay(10); + i++; + } + if (i >= MAX_DOZE_WAITING_TIMES_9x) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "ERFSLEEP: %d times TcbBusyQueue[%d] = %d !\n", + MAX_DOZE_WAITING_TIMES_9x, + queue_id, + skb_queue_len(&ring->queue)); + break; + } + } + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "Set ERFSLEEP awaked:%d ms\n", + jiffies_to_msecs(jiffies - ppsc->last_awake_jiffies)); + ppsc->last_sleep_jiffies = jiffies; + _rtl92c_phy_set_rf_sleep(hw); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + bresult = false; + break; + } + if (bresult) + ppsc->rfpwr_state = rfpwr_state; + return bresult; +} + +bool rtl92cu_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state) +{ + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + bool bresult = false; + + if (rfpwr_state == ppsc->rfpwr_state) + return bresult; + bresult = _rtl92cu_phy_set_rf_power_state(hw, rfpwr_state); + return bresult; +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/phy.h b/drivers/net/wireless/rtlwifi/rtl8192cu/phy.h new file mode 100644 index 0000000..42b0686 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/phy.h @@ -0,0 +1,50 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../rtl8192ce/phy.h" + +void rtl92cu_bb_block_on(struct ieee80211_hw *hw); +bool rtl8192_phy_check_is_legal_rfpath(struct ieee80211_hw *hw, u32 rfpath); +void rtl92c_phy_set_io(struct ieee80211_hw *hw); +bool _rtl92cu_phy_config_mac_with_headerfile(struct ieee80211_hw *hw); +bool rtl92cu_phy_bb_config(struct ieee80211_hw *hw); +u32 rtl92cu_phy_query_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, u32 bitmask); +void rtl92cu_phy_set_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, + u32 regaddr, u32 bitmask, u32 data); +bool rtl92cu_phy_mac_config(struct ieee80211_hw *hw); +bool _rtl92cu_phy_config_bb_with_pgheaderfile(struct ieee80211_hw *hw, + u8 configtype); +void _rtl92cu_phy_lc_calibrate(struct ieee80211_hw *hw, bool is2t); +bool _rtl92cu_phy_config_bb_with_headerfile(struct ieee80211_hw *hw, + u8 configtype); +void rtl92cu_phy_set_bw_mode_callback(struct ieee80211_hw *hw); +bool rtl92cu_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state); diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/reg.h b/drivers/net/wireless/rtlwifi/rtl8192cu/reg.h new file mode 100644 index 0000000..8b81465 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/reg.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../rtl8192ce/reg.h" diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/rf.c b/drivers/net/wireless/rtlwifi/rtl8192cu/rf.c new file mode 100644 index 0000000..2119313 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/rf.c @@ -0,0 +1,486 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "rf.h" +#include "dm.h" + +static bool _rtl92c_phy_rf6052_config_parafile(struct ieee80211_hw *hw); + +void rtl92cu_phy_rf6052_set_bandwidth(struct ieee80211_hw *hw, u8 bandwidth) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + switch (bandwidth) { + case HT_CHANNEL_WIDTH_20: + rtlphy->rfreg_chnlval[0] = ((rtlphy->rfreg_chnlval[0] & + 0xfffff3ff) | 0x0400); + rtl_set_rfreg(hw, RF90_PATH_A, RF_CHNLBW, RFREG_OFFSET_MASK, + rtlphy->rfreg_chnlval[0]); + break; + case HT_CHANNEL_WIDTH_20_40: + rtlphy->rfreg_chnlval[0] = ((rtlphy->rfreg_chnlval[0] & + 0xfffff3ff)); + rtl_set_rfreg(hw, RF90_PATH_A, RF_CHNLBW, RFREG_OFFSET_MASK, + rtlphy->rfreg_chnlval[0]); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", bandwidth); + break; + } +} + +void rtl92cu_phy_rf6052_set_cck_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u32 tx_agc[2] = { 0, 0 }, tmpval = 0; + bool turbo_scanoff = false; + u8 idx1, idx2; + u8 *ptr; + + if (rtlhal->interface == INTF_PCI) { + if (rtlefuse->eeprom_regulatory != 0) + turbo_scanoff = true; + } else { + if ((rtlefuse->eeprom_regulatory != 0) || + (rtlefuse->external_pa)) + turbo_scanoff = true; + } + if (mac->act_scanning) { + tx_agc[RF90_PATH_A] = 0x3f3f3f3f; + tx_agc[RF90_PATH_B] = 0x3f3f3f3f; + if (turbo_scanoff) { + for (idx1 = RF90_PATH_A; idx1 <= RF90_PATH_B; idx1++) { + tx_agc[idx1] = ppowerlevel[idx1] | + (ppowerlevel[idx1] << 8) | + (ppowerlevel[idx1] << 16) | + (ppowerlevel[idx1] << 24); + if (rtlhal->interface == INTF_USB) { + if (tx_agc[idx1] > 0x20 && + rtlefuse->external_pa) + tx_agc[idx1] = 0x20; + } + } + } + } else { + if (rtlpriv->dm.dynamic_txhighpower_lvl == + TXHIGHPWRLEVEL_LEVEL1) { + tx_agc[RF90_PATH_A] = 0x10101010; + tx_agc[RF90_PATH_B] = 0x10101010; + } else if (rtlpriv->dm.dynamic_txhighpower_lvl == + TXHIGHPWRLEVEL_LEVEL2) { + tx_agc[RF90_PATH_A] = 0x00000000; + tx_agc[RF90_PATH_B] = 0x00000000; + } else{ + for (idx1 = RF90_PATH_A; idx1 <= RF90_PATH_B; idx1++) { + tx_agc[idx1] = ppowerlevel[idx1] | + (ppowerlevel[idx1] << 8) | + (ppowerlevel[idx1] << 16) | + (ppowerlevel[idx1] << 24); + } + if (rtlefuse->eeprom_regulatory == 0) { + tmpval = (rtlphy->mcs_offset[0][6]) + + (rtlphy->mcs_offset[0][7] << 8); + tx_agc[RF90_PATH_A] += tmpval; + tmpval = (rtlphy->mcs_offset[0][14]) + + (rtlphy->mcs_offset[0][15] << 24); + tx_agc[RF90_PATH_B] += tmpval; + } + } + } + for (idx1 = RF90_PATH_A; idx1 <= RF90_PATH_B; idx1++) { + ptr = (u8 *) (&(tx_agc[idx1])); + for (idx2 = 0; idx2 < 4; idx2++) { + if (*ptr > RF6052_MAX_TX_PWR) + *ptr = RF6052_MAX_TX_PWR; + ptr++; + } + } + tmpval = tx_agc[RF90_PATH_A] & 0xff; + rtl_set_bbreg(hw, RTXAGC_A_CCK1_MCS32, MASKBYTE1, tmpval); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 1M (rf-A) = 0x%x (reg 0x%x)\n", + tmpval, RTXAGC_A_CCK1_MCS32); + + tmpval = tx_agc[RF90_PATH_A] >> 8; + if (mac->mode == WIRELESS_MODE_B) + tmpval = tmpval & 0xff00ffff; + rtl_set_bbreg(hw, RTXAGC_B_CCK11_A_CCK2_11, 0xffffff00, tmpval); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 2~11M (rf-A) = 0x%x (reg 0x%x)\n", + tmpval, RTXAGC_B_CCK11_A_CCK2_11); + tmpval = tx_agc[RF90_PATH_B] >> 24; + rtl_set_bbreg(hw, RTXAGC_B_CCK11_A_CCK2_11, MASKBYTE0, tmpval); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 11M (rf-B) = 0x%x (reg 0x%x)\n", + tmpval, RTXAGC_B_CCK11_A_CCK2_11); + tmpval = tx_agc[RF90_PATH_B] & 0x00ffffff; + rtl_set_bbreg(hw, RTXAGC_B_CCK1_55_MCS32, 0xffffff00, tmpval); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 1~5.5M (rf-B) = 0x%x (reg 0x%x)\n", + tmpval, RTXAGC_B_CCK1_55_MCS32); +} + +static void rtl92c_phy_get_power_base(struct ieee80211_hw *hw, + u8 *ppowerlevel, u8 channel, + u32 *ofdmbase, u32 *mcsbase) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u32 powerBase0, powerBase1; + u8 legacy_pwrdiff = 0, ht20_pwrdiff = 0; + u8 i, powerlevel[2]; + + for (i = 0; i < 2; i++) { + powerlevel[i] = ppowerlevel[i]; + legacy_pwrdiff = rtlefuse->txpwr_legacyhtdiff[i][channel - 1]; + powerBase0 = powerlevel[i] + legacy_pwrdiff; + powerBase0 = (powerBase0 << 24) | (powerBase0 << 16) | + (powerBase0 << 8) | powerBase0; + *(ofdmbase + i) = powerBase0; + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + " [OFDM power base index rf(%c) = 0x%x]\n", + i == 0 ? 'A' : 'B', *(ofdmbase + i)); + } + for (i = 0; i < 2; i++) { + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20) { + ht20_pwrdiff = rtlefuse->txpwr_ht20diff[i][channel - 1]; + powerlevel[i] += ht20_pwrdiff; + } + powerBase1 = powerlevel[i]; + powerBase1 = (powerBase1 << 24) | + (powerBase1 << 16) | (powerBase1 << 8) | powerBase1; + *(mcsbase + i) = powerBase1; + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + " [MCS power base index rf(%c) = 0x%x]\n", + i == 0 ? 'A' : 'B', *(mcsbase + i)); + } +} + +static void _rtl92c_get_txpower_writeval_by_regulatory(struct ieee80211_hw *hw, + u8 channel, u8 index, + u32 *powerBase0, + u32 *powerBase1, + u32 *p_outwriteval) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 i, chnlgroup = 0, pwr_diff_limit[4]; + u32 writeVal, customer_limit, rf; + + for (rf = 0; rf < 2; rf++) { + switch (rtlefuse->eeprom_regulatory) { + case 0: + chnlgroup = 0; + writeVal = rtlphy->mcs_offset + [chnlgroup][index + (rf ? 8 : 0)] + + ((index < 2) ? powerBase0[rf] : powerBase1[rf]); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "RTK better performance,writeVal(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', writeVal); + break; + case 1: + if (rtlphy->pwrgroup_cnt == 1) + chnlgroup = 0; + if (rtlphy->pwrgroup_cnt >= 3) { + if (channel <= 3) + chnlgroup = 0; + else if (channel >= 4 && channel <= 9) + chnlgroup = 1; + else if (channel > 9) + chnlgroup = 2; + if (rtlphy->current_chan_bw == + HT_CHANNEL_WIDTH_20) + chnlgroup++; + else + chnlgroup += 4; + } + writeVal = rtlphy->mcs_offset[chnlgroup][index + + (rf ? 8 : 0)] + + ((index < 2) ? powerBase0[rf] : + powerBase1[rf]); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Realtek regulatory, 20MHz, writeVal(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', writeVal); + break; + case 2: + writeVal = ((index < 2) ? powerBase0[rf] : + powerBase1[rf]); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Better regulatory,writeVal(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', writeVal); + break; + case 3: + chnlgroup = 0; + if (rtlphy->current_chan_bw == + HT_CHANNEL_WIDTH_20_40) { + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "customer's limit, 40MHzrf(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', + rtlefuse->pwrgroup_ht40[rf] + [channel - 1]); + } else { + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "customer's limit, 20MHz rf(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', + rtlefuse->pwrgroup_ht20[rf] + [channel - 1]); + } + for (i = 0; i < 4; i++) { + pwr_diff_limit[i] = (u8) ((rtlphy->mcs_offset + [chnlgroup][index + (rf ? 8 : 0)] + & (0x7f << (i * 8))) >> (i * 8)); + if (rtlphy->current_chan_bw == + HT_CHANNEL_WIDTH_20_40) { + if (pwr_diff_limit[i] > + rtlefuse->pwrgroup_ht40[rf] + [channel - 1]) + pwr_diff_limit[i] = rtlefuse-> + pwrgroup_ht40[rf] + [channel - 1]; + } else { + if (pwr_diff_limit[i] > + rtlefuse->pwrgroup_ht20[rf] + [channel - 1]) + pwr_diff_limit[i] = + rtlefuse->pwrgroup_ht20[rf] + [channel - 1]; + } + } + customer_limit = (pwr_diff_limit[3] << 24) | + (pwr_diff_limit[2] << 16) | + (pwr_diff_limit[1] << 8) | (pwr_diff_limit[0]); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Customer's limit rf(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', customer_limit); + writeVal = customer_limit + ((index < 2) ? + powerBase0[rf] : powerBase1[rf]); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Customer, writeVal rf(%c)= 0x%x\n", + rf == 0 ? 'A' : 'B', writeVal); + break; + default: + chnlgroup = 0; + writeVal = rtlphy->mcs_offset[chnlgroup] + [index + (rf ? 8 : 0)] + ((index < 2) ? + powerBase0[rf] : powerBase1[rf]); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "RTK better performance, writeValrf(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', writeVal); + break; + } + if (rtlpriv->dm.dynamic_txhighpower_lvl == + TXHIGHPWRLEVEL_LEVEL1) + writeVal = 0x14141414; + else if (rtlpriv->dm.dynamic_txhighpower_lvl == + TXHIGHPWRLEVEL_LEVEL2) + writeVal = 0x00000000; + if (rtlpriv->dm.dynamic_txhighpower_lvl == TXHIGHPWRLEVEL_BT1) + writeVal = writeVal - 0x06060606; + else if (rtlpriv->dm.dynamic_txhighpower_lvl == + TXHIGHPWRLEVEL_BT2) + writeVal = writeVal; + *(p_outwriteval + rf) = writeVal; + } +} + +static void _rtl92c_write_ofdm_power_reg(struct ieee80211_hw *hw, + u8 index, u32 *pValue) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u16 regoffset_a[6] = { + RTXAGC_A_RATE18_06, RTXAGC_A_RATE54_24, + RTXAGC_A_MCS03_MCS00, RTXAGC_A_MCS07_MCS04, + RTXAGC_A_MCS11_MCS08, RTXAGC_A_MCS15_MCS12 + }; + u16 regoffset_b[6] = { + RTXAGC_B_RATE18_06, RTXAGC_B_RATE54_24, + RTXAGC_B_MCS03_MCS00, RTXAGC_B_MCS07_MCS04, + RTXAGC_B_MCS11_MCS08, RTXAGC_B_MCS15_MCS12 + }; + u8 i, rf, pwr_val[4]; + u32 writeVal; + u16 regoffset; + + for (rf = 0; rf < 2; rf++) { + writeVal = pValue[rf]; + for (i = 0; i < 4; i++) { + pwr_val[i] = (u8)((writeVal & (0x7f << (i * 8))) >> + (i * 8)); + if (pwr_val[i] > RF6052_MAX_TX_PWR) + pwr_val[i] = RF6052_MAX_TX_PWR; + } + writeVal = (pwr_val[3] << 24) | (pwr_val[2] << 16) | + (pwr_val[1] << 8) | pwr_val[0]; + if (rf == 0) + regoffset = regoffset_a[index]; + else + regoffset = regoffset_b[index]; + rtl_set_bbreg(hw, regoffset, MASKDWORD, writeVal); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Set 0x%x = %08x\n", regoffset, writeVal); + if (((get_rf_type(rtlphy) == RF_2T2R) && + (regoffset == RTXAGC_A_MCS15_MCS12 || + regoffset == RTXAGC_B_MCS15_MCS12)) || + ((get_rf_type(rtlphy) != RF_2T2R) && + (regoffset == RTXAGC_A_MCS07_MCS04 || + regoffset == RTXAGC_B_MCS07_MCS04))) { + writeVal = pwr_val[3]; + if (regoffset == RTXAGC_A_MCS15_MCS12 || + regoffset == RTXAGC_A_MCS07_MCS04) + regoffset = 0xc90; + if (regoffset == RTXAGC_B_MCS15_MCS12 || + regoffset == RTXAGC_B_MCS07_MCS04) + regoffset = 0xc98; + for (i = 0; i < 3; i++) { + writeVal = (writeVal > 6) ? (writeVal - 6) : 0; + rtl_write_byte(rtlpriv, (u32)(regoffset + i), + (u8)writeVal); + } + } + } +} + +void rtl92cu_phy_rf6052_set_ofdm_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel, u8 channel) +{ + u32 writeVal[2], powerBase0[2], powerBase1[2]; + u8 index = 0; + + rtl92c_phy_get_power_base(hw, ppowerlevel, + channel, &powerBase0[0], &powerBase1[0]); + for (index = 0; index < 6; index++) { + _rtl92c_get_txpower_writeval_by_regulatory(hw, + channel, index, + &powerBase0[0], + &powerBase1[0], + &writeVal[0]); + _rtl92c_write_ofdm_power_reg(hw, index, &writeVal[0]); + } +} + +bool rtl92cu_phy_rf6052_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + bool rtstatus = true; + u8 b_reg_hwparafile = 1; + + if (rtlphy->rf_type == RF_1T1R) + rtlphy->num_total_rfpath = 1; + else + rtlphy->num_total_rfpath = 2; + if (b_reg_hwparafile == 1) + rtstatus = _rtl92c_phy_rf6052_config_parafile(hw); + return rtstatus; +} + +static bool _rtl92c_phy_rf6052_config_parafile(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 u4_regvalue = 0; + u8 rfpath; + bool rtstatus = true; + struct bb_reg_def *pphyreg; + + for (rfpath = 0; rfpath < rtlphy->num_total_rfpath; rfpath++) { + pphyreg = &rtlphy->phyreg_def[rfpath]; + switch (rfpath) { + case RF90_PATH_A: + case RF90_PATH_C: + u4_regvalue = rtl_get_bbreg(hw, pphyreg->rfintfs, + BRFSI_RFENV); + break; + case RF90_PATH_B: + case RF90_PATH_D: + u4_regvalue = rtl_get_bbreg(hw, pphyreg->rfintfs, + BRFSI_RFENV << 16); + break; + } + rtl_set_bbreg(hw, pphyreg->rfintfe, BRFSI_RFENV << 16, 0x1); + udelay(1); + rtl_set_bbreg(hw, pphyreg->rfintfo, BRFSI_RFENV, 0x1); + udelay(1); + rtl_set_bbreg(hw, pphyreg->rfhssi_para2, + B3WIREADDREAALENGTH, 0x0); + udelay(1); + rtl_set_bbreg(hw, pphyreg->rfhssi_para2, B3WIREDATALENGTH, 0x0); + udelay(1); + switch (rfpath) { + case RF90_PATH_A: + rtstatus = rtl92cu_phy_config_rf_with_headerfile(hw, + (enum radio_path) rfpath); + break; + case RF90_PATH_B: + rtstatus = rtl92cu_phy_config_rf_with_headerfile(hw, + (enum radio_path) rfpath); + break; + case RF90_PATH_C: + break; + case RF90_PATH_D: + break; + } + switch (rfpath) { + case RF90_PATH_A: + case RF90_PATH_C: + rtl_set_bbreg(hw, pphyreg->rfintfs, + BRFSI_RFENV, u4_regvalue); + break; + case RF90_PATH_B: + case RF90_PATH_D: + rtl_set_bbreg(hw, pphyreg->rfintfs, + BRFSI_RFENV << 16, u4_regvalue); + break; + } + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Radio[%d] Fail!!", rfpath); + goto phy_rf_cfg_fail; + } + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "<---\n"); + return rtstatus; +phy_rf_cfg_fail: + return rtstatus; +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/rf.h b/drivers/net/wireless/rtlwifi/rtl8192cu/rf.h new file mode 100644 index 0000000..090fd33 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/rf.h @@ -0,0 +1,51 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CU_RF_H__ +#define __RTL92CU_RF_H__ + +#define RF6052_MAX_TX_PWR 0x3F +#define RF6052_MAX_REG 0x3F +#define RF6052_MAX_PATH 2 + +extern void rtl92cu_phy_rf6052_set_bandwidth(struct ieee80211_hw *hw, + u8 bandwidth); +extern void rtl92c_phy_rf6052_set_cck_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel); +extern void rtl92c_phy_rf6052_set_ofdm_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel, u8 channel); +bool rtl92cu_phy_rf6052_config(struct ieee80211_hw *hw); +bool rtl92cu_phy_config_rf_with_headerfile(struct ieee80211_hw *hw, + enum radio_path rfpath); +void rtl92cu_phy_rf6052_set_cck_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel); +void rtl92cu_phy_rf6052_set_ofdm_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel, u8 channel); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/sw.c b/drivers/net/wireless/rtlwifi/rtl8192cu/sw.c new file mode 100644 index 0000000..22b76af --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/sw.c @@ -0,0 +1,394 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../core.h" +#include "../usb.h" +#include "../efuse.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "mac.h" +#include "dm.h" +#include "rf.h" +#include "sw.h" +#include "trx.h" +#include "led.h" +#include "hw.h" +#include + +MODULE_AUTHOR("Georgia "); +MODULE_AUTHOR("Ziv Huang "); +MODULE_AUTHOR("Larry Finger "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Realtek 8192C/8188C 802.11n USB wireless"); +MODULE_FIRMWARE("rtlwifi/rtl8192cufw.bin"); + +static int rtl92cu_init_sw_vars(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int err; + + rtlpriv->dm.dm_initialgain_enable = true; + rtlpriv->dm.dm_flag = 0; + rtlpriv->dm.disable_framebursting = false; + rtlpriv->dm.thermalvalue = 0; + rtlpriv->dbg.global_debuglevel = rtlpriv->cfg->mod_params->debug; + + /* for firmware buf */ + rtlpriv->rtlhal.pfirmware = vzalloc(0x4000); + if (!rtlpriv->rtlhal.pfirmware) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Can't alloc buffer for fw\n"); + return 1; + } + + pr_info("Loading firmware %s\n", rtlpriv->cfg->fw_name); + rtlpriv->max_fw_size = 0x4000; + err = request_firmware_nowait(THIS_MODULE, 1, + rtlpriv->cfg->fw_name, rtlpriv->io.dev, + GFP_KERNEL, hw, rtl_fw_cb); + + + return err; +} + +static void rtl92cu_deinit_sw_vars(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (rtlpriv->rtlhal.pfirmware) { + vfree(rtlpriv->rtlhal.pfirmware); + rtlpriv->rtlhal.pfirmware = NULL; + } +} + +static struct rtl_hal_ops rtl8192cu_hal_ops = { + .init_sw_vars = rtl92cu_init_sw_vars, + .deinit_sw_vars = rtl92cu_deinit_sw_vars, + .read_chip_version = rtl92c_read_chip_version, + .read_eeprom_info = rtl92cu_read_eeprom_info, + .enable_interrupt = rtl92c_enable_interrupt, + .disable_interrupt = rtl92c_disable_interrupt, + .hw_init = rtl92cu_hw_init, + .hw_disable = rtl92cu_card_disable, + .set_network_type = rtl92cu_set_network_type, + .set_chk_bssid = rtl92cu_set_check_bssid, + .set_qos = rtl92c_set_qos, + .set_bcn_reg = rtl92cu_set_beacon_related_registers, + .set_bcn_intv = rtl92cu_set_beacon_interval, + .update_interrupt_mask = rtl92cu_update_interrupt_mask, + .get_hw_reg = rtl92cu_get_hw_reg, + .set_hw_reg = rtl92cu_set_hw_reg, + .update_rate_tbl = rtl92cu_update_hal_rate_tbl, + .fill_tx_desc = rtl92cu_tx_fill_desc, + .fill_fake_txdesc = rtl92cu_fill_fake_txdesc, + .fill_tx_cmddesc = rtl92cu_tx_fill_cmddesc, + .cmd_send_packet = rtl92cu_cmd_send_packet, + .query_rx_desc = rtl92cu_rx_query_desc, + .set_channel_access = rtl92cu_update_channel_access_setting, + .radio_onoff_checking = rtl92cu_gpio_radio_on_off_checking, + .set_bw_mode = rtl92c_phy_set_bw_mode, + .switch_channel = rtl92c_phy_sw_chnl, + .dm_watchdog = rtl92c_dm_watchdog, + .scan_operation_backup = rtl92c_phy_scan_operation_backup, + .set_rf_power_state = rtl92cu_phy_set_rf_power_state, + .led_control = rtl92cu_led_control, + .enable_hw_sec = rtl92cu_enable_hw_security_config, + .set_key = rtl92c_set_key, + .init_sw_leds = rtl92cu_init_sw_leds, + .deinit_sw_leds = rtl92cu_deinit_sw_leds, + .get_bbreg = rtl92c_phy_query_bb_reg, + .set_bbreg = rtl92c_phy_set_bb_reg, + .get_rfreg = rtl92cu_phy_query_rf_reg, + .set_rfreg = rtl92cu_phy_set_rf_reg, + .phy_rf6052_config = rtl92cu_phy_rf6052_config, + .phy_rf6052_set_cck_txpower = rtl92cu_phy_rf6052_set_cck_txpower, + .phy_rf6052_set_ofdm_txpower = rtl92cu_phy_rf6052_set_ofdm_txpower, + .config_bb_with_headerfile = _rtl92cu_phy_config_bb_with_headerfile, + .config_bb_with_pgheaderfile = _rtl92cu_phy_config_bb_with_pgheaderfile, + .phy_lc_calibrate = _rtl92cu_phy_lc_calibrate, + .phy_set_bw_mode_callback = rtl92cu_phy_set_bw_mode_callback, + .dm_dynamic_txpower = rtl92cu_dm_dynamic_txpower, + .fill_h2c_cmd = rtl92c_fill_h2c_cmd, +}; + +static struct rtl_mod_params rtl92cu_mod_params = { + .sw_crypto = 0, + .debug = DBG_EMERG, +}; + +module_param_named(swenc, rtl92cu_mod_params.sw_crypto, bool, 0444); +module_param_named(debug, rtl92cu_mod_params.debug, int, 0444); +MODULE_PARM_DESC(swenc, "Set to 1 for software crypto (default 0)\n"); +MODULE_PARM_DESC(debug, "Set debug level (0-5) (default 0)"); + +static struct rtl_hal_usbint_cfg rtl92cu_interface_cfg = { + /* rx */ + .in_ep_num = RTL92C_USB_BULK_IN_NUM, + .rx_urb_num = RTL92C_NUM_RX_URBS, + .rx_max_size = RTL92C_SIZE_MAX_RX_BUFFER, + .usb_rx_hdl = rtl8192cu_rx_hdl, + .usb_rx_segregate_hdl = NULL, /* rtl8192c_rx_segregate_hdl; */ + /* tx */ + .usb_tx_cleanup = rtl8192c_tx_cleanup, + .usb_tx_post_hdl = rtl8192c_tx_post_hdl, + .usb_tx_aggregate_hdl = rtl8192c_tx_aggregate_hdl, + /* endpoint mapping */ + .usb_endpoint_mapping = rtl8192cu_endpoint_mapping, + .usb_mq_to_hwq = rtl8192cu_mq_to_hwq, +}; + +static struct rtl_hal_cfg rtl92cu_hal_cfg = { + .name = "rtl92c_usb", + .fw_name = "rtlwifi/rtl8192cufw.bin", + .ops = &rtl8192cu_hal_ops, + .mod_params = &rtl92cu_mod_params, + .usb_interface_cfg = &rtl92cu_interface_cfg, + + .maps[SYS_ISO_CTRL] = REG_SYS_ISO_CTRL, + .maps[SYS_FUNC_EN] = REG_SYS_FUNC_EN, + .maps[SYS_CLK] = REG_SYS_CLKR, + .maps[MAC_RCR_AM] = AM, + .maps[MAC_RCR_AB] = AB, + .maps[MAC_RCR_ACRC32] = ACRC32, + .maps[MAC_RCR_ACF] = ACF, + .maps[MAC_RCR_AAP] = AAP, + + .maps[EFUSE_TEST] = REG_EFUSE_TEST, + .maps[EFUSE_CTRL] = REG_EFUSE_CTRL, + .maps[EFUSE_CLK] = 0, + .maps[EFUSE_CLK_CTRL] = REG_EFUSE_CTRL, + .maps[EFUSE_PWC_EV12V] = PWC_EV12V, + .maps[EFUSE_FEN_ELDR] = FEN_ELDR, + .maps[EFUSE_LOADER_CLK_EN] = LOADER_CLK_EN, + .maps[EFUSE_ANA8M] = EFUSE_ANA8M, + .maps[EFUSE_HWSET_MAX_SIZE] = HWSET_MAX_SIZE, + .maps[EFUSE_MAX_SECTION_MAP] = EFUSE_MAX_SECTION, + .maps[EFUSE_REAL_CONTENT_SIZE] = EFUSE_REAL_CONTENT_LEN, + + .maps[RWCAM] = REG_CAMCMD, + .maps[WCAMI] = REG_CAMWRITE, + .maps[RCAMO] = REG_CAMREAD, + .maps[CAMDBG] = REG_CAMDBG, + .maps[SECR] = REG_SECCFG, + .maps[SEC_CAM_NONE] = CAM_NONE, + .maps[SEC_CAM_WEP40] = CAM_WEP40, + .maps[SEC_CAM_TKIP] = CAM_TKIP, + .maps[SEC_CAM_AES] = CAM_AES, + .maps[SEC_CAM_WEP104] = CAM_WEP104, + + .maps[RTL_IMR_BCNDMAINT6] = IMR_BCNDMAINT6, + .maps[RTL_IMR_BCNDMAINT5] = IMR_BCNDMAINT5, + .maps[RTL_IMR_BCNDMAINT4] = IMR_BCNDMAINT4, + .maps[RTL_IMR_BCNDMAINT3] = IMR_BCNDMAINT3, + .maps[RTL_IMR_BCNDMAINT2] = IMR_BCNDMAINT2, + .maps[RTL_IMR_BCNDMAINT1] = IMR_BCNDMAINT1, + .maps[RTL_IMR_BCNDOK8] = IMR_BCNDOK8, + .maps[RTL_IMR_BCNDOK7] = IMR_BCNDOK7, + .maps[RTL_IMR_BCNDOK6] = IMR_BCNDOK6, + .maps[RTL_IMR_BCNDOK5] = IMR_BCNDOK5, + .maps[RTL_IMR_BCNDOK4] = IMR_BCNDOK4, + .maps[RTL_IMR_BCNDOK3] = IMR_BCNDOK3, + .maps[RTL_IMR_BCNDOK2] = IMR_BCNDOK2, + .maps[RTL_IMR_BCNDOK1] = IMR_BCNDOK1, + .maps[RTL_IMR_TIMEOUT2] = IMR_TIMEOUT2, + .maps[RTL_IMR_TIMEOUT1] = IMR_TIMEOUT1, + + .maps[RTL_IMR_TXFOVW] = IMR_TXFOVW, + .maps[RTL_IMR_PSTIMEOUT] = IMR_PSTIMEOUT, + .maps[RTL_IMR_BCNINT] = IMR_BCNINT, + .maps[RTL_IMR_RXFOVW] = IMR_RXFOVW, + .maps[RTL_IMR_RDU] = IMR_RDU, + .maps[RTL_IMR_ATIMEND] = IMR_ATIMEND, + .maps[RTL_IMR_BDOK] = IMR_BDOK, + .maps[RTL_IMR_MGNTDOK] = IMR_MGNTDOK, + .maps[RTL_IMR_TBDER] = IMR_TBDER, + .maps[RTL_IMR_HIGHDOK] = IMR_HIGHDOK, + .maps[RTL_IMR_TBDOK] = IMR_TBDOK, + .maps[RTL_IMR_BKDOK] = IMR_BKDOK, + .maps[RTL_IMR_BEDOK] = IMR_BEDOK, + .maps[RTL_IMR_VIDOK] = IMR_VIDOK, + .maps[RTL_IMR_VODOK] = IMR_VODOK, + .maps[RTL_IMR_ROK] = IMR_ROK, + .maps[RTL_IBSS_INT_MASKS] = (IMR_BCNINT | IMR_TBDOK | IMR_TBDER), + + .maps[RTL_RC_CCK_RATE1M] = DESC92_RATE1M, + .maps[RTL_RC_CCK_RATE2M] = DESC92_RATE2M, + .maps[RTL_RC_CCK_RATE5_5M] = DESC92_RATE5_5M, + .maps[RTL_RC_CCK_RATE11M] = DESC92_RATE11M, + .maps[RTL_RC_OFDM_RATE6M] = DESC92_RATE6M, + .maps[RTL_RC_OFDM_RATE9M] = DESC92_RATE9M, + .maps[RTL_RC_OFDM_RATE12M] = DESC92_RATE12M, + .maps[RTL_RC_OFDM_RATE18M] = DESC92_RATE18M, + .maps[RTL_RC_OFDM_RATE24M] = DESC92_RATE24M, + .maps[RTL_RC_OFDM_RATE36M] = DESC92_RATE36M, + .maps[RTL_RC_OFDM_RATE48M] = DESC92_RATE48M, + .maps[RTL_RC_OFDM_RATE54M] = DESC92_RATE54M, + .maps[RTL_RC_HT_RATEMCS7] = DESC92_RATEMCS7, + .maps[RTL_RC_HT_RATEMCS15] = DESC92_RATEMCS15, +}; + +#define USB_VENDER_ID_REALTEK 0x0bda + +/* 2010-10-19 DID_USB_V3.4 */ +static struct usb_device_id rtl8192c_usb_ids[] = { + + /*=== Realtek demoboard ===*/ + /* Default ID */ + {RTL_USB_DEVICE(USB_VENDER_ID_REALTEK, 0x8191, rtl92cu_hal_cfg)}, + + /****** 8188CU ********/ + /* RTL8188CTV */ + {RTL_USB_DEVICE(USB_VENDER_ID_REALTEK, 0x018a, rtl92cu_hal_cfg)}, + /* 8188CE-VAU USB minCard */ + {RTL_USB_DEVICE(USB_VENDER_ID_REALTEK, 0x8170, rtl92cu_hal_cfg)}, + /* 8188cu 1*1 dongle */ + {RTL_USB_DEVICE(USB_VENDER_ID_REALTEK, 0x8176, rtl92cu_hal_cfg)}, + /* 8188cu 1*1 dongle, (b/g mode only) */ + {RTL_USB_DEVICE(USB_VENDER_ID_REALTEK, 0x8177, rtl92cu_hal_cfg)}, + /* 8188cu Slim Solo */ + {RTL_USB_DEVICE(USB_VENDER_ID_REALTEK, 0x817a, rtl92cu_hal_cfg)}, + /* 8188cu Slim Combo */ + {RTL_USB_DEVICE(USB_VENDER_ID_REALTEK, 0x817b, rtl92cu_hal_cfg)}, + /* 8188RU High-power USB Dongle */ + {RTL_USB_DEVICE(USB_VENDER_ID_REALTEK, 0x817d, rtl92cu_hal_cfg)}, + /* 8188CE-VAU USB minCard (b/g mode only) */ + {RTL_USB_DEVICE(USB_VENDER_ID_REALTEK, 0x817e, rtl92cu_hal_cfg)}, + /* 8188RU in Alfa AWUS036NHR */ + {RTL_USB_DEVICE(USB_VENDER_ID_REALTEK, 0x817f, rtl92cu_hal_cfg)}, + /* RTL8188CUS-VL */ + {RTL_USB_DEVICE(USB_VENDER_ID_REALTEK, 0x818a, rtl92cu_hal_cfg)}, + {RTL_USB_DEVICE(USB_VENDER_ID_REALTEK, 0x819a, rtl92cu_hal_cfg)}, + /* 8188 Combo for BC4 */ + {RTL_USB_DEVICE(USB_VENDER_ID_REALTEK, 0x8754, rtl92cu_hal_cfg)}, + + /****** 8192CU ********/ + /* 8192cu 2*2 */ + {RTL_USB_DEVICE(USB_VENDER_ID_REALTEK, 0x8178, rtl92cu_hal_cfg)}, + /* 8192CE-VAU USB minCard */ + {RTL_USB_DEVICE(USB_VENDER_ID_REALTEK, 0x817c, rtl92cu_hal_cfg)}, + + /*=== Customer ID ===*/ + /****** 8188CU ********/ + {RTL_USB_DEVICE(0x050d, 0x1102, rtl92cu_hal_cfg)}, /*Belkin - Edimax*/ + {RTL_USB_DEVICE(0x050d, 0x11f2, rtl92cu_hal_cfg)}, /*Belkin - ISY*/ + {RTL_USB_DEVICE(0x06f8, 0xe033, rtl92cu_hal_cfg)}, /*Hercules - Edimax*/ + {RTL_USB_DEVICE(0x07b8, 0x8188, rtl92cu_hal_cfg)}, /*Abocom - Abocom*/ + {RTL_USB_DEVICE(0x07b8, 0x8189, rtl92cu_hal_cfg)}, /*Funai - Abocom*/ + {RTL_USB_DEVICE(0x0846, 0x9041, rtl92cu_hal_cfg)}, /*NetGear WNA1000M*/ + {RTL_USB_DEVICE(0x0bda, 0x5088, rtl92cu_hal_cfg)}, /*Thinkware-CC&C*/ + {RTL_USB_DEVICE(0x0df6, 0x0052, rtl92cu_hal_cfg)}, /*Sitecom - Edimax*/ + {RTL_USB_DEVICE(0x0df6, 0x005c, rtl92cu_hal_cfg)}, /*Sitecom - Edimax*/ + {RTL_USB_DEVICE(0x0eb0, 0x9071, rtl92cu_hal_cfg)}, /*NO Brand - Etop*/ + {RTL_USB_DEVICE(0x4856, 0x0091, rtl92cu_hal_cfg)}, /*NetweeN - Feixun*/ + /* HP - Lite-On ,8188CUS Slim Combo */ + {RTL_USB_DEVICE(0x103c, 0x1629, rtl92cu_hal_cfg)}, + {RTL_USB_DEVICE(0x13d3, 0x3357, rtl92cu_hal_cfg)}, /* AzureWave */ + {RTL_USB_DEVICE(0x2001, 0x3308, rtl92cu_hal_cfg)}, /*D-Link - Alpha*/ + {RTL_USB_DEVICE(0x2019, 0x4902, rtl92cu_hal_cfg)}, /*Planex - Etop*/ + {RTL_USB_DEVICE(0x2019, 0xab2a, rtl92cu_hal_cfg)}, /*Planex - Abocom*/ + /*SW-WF02-AD15 -Abocom*/ + {RTL_USB_DEVICE(0x2019, 0xab2e, rtl92cu_hal_cfg)}, + {RTL_USB_DEVICE(0x2019, 0xed17, rtl92cu_hal_cfg)}, /*PCI - Edimax*/ + {RTL_USB_DEVICE(0x20f4, 0x648b, rtl92cu_hal_cfg)}, /*TRENDnet - Cameo*/ + {RTL_USB_DEVICE(0x7392, 0x7811, rtl92cu_hal_cfg)}, /*Edimax - Edimax*/ + {RTL_USB_DEVICE(0x13d3, 0x3358, rtl92cu_hal_cfg)}, /*Azwave 8188CE-VAU*/ + /* Russian customer -Azwave (8188CE-VAU b/g mode only) */ + {RTL_USB_DEVICE(0x13d3, 0x3359, rtl92cu_hal_cfg)}, + {RTL_USB_DEVICE(0x4855, 0x0090, rtl92cu_hal_cfg)}, /* Feixun */ + {RTL_USB_DEVICE(0x4855, 0x0091, rtl92cu_hal_cfg)}, /* NetweeN-Feixun */ + {RTL_USB_DEVICE(0x9846, 0x9041, rtl92cu_hal_cfg)}, /* Netgear Cameo */ + + /****** 8188 RU ********/ + /* Netcore */ + {RTL_USB_DEVICE(USB_VENDER_ID_REALTEK, 0x317f, rtl92cu_hal_cfg)}, + + /****** 8188CUS Slim Solo********/ + {RTL_USB_DEVICE(0x04f2, 0xaff7, rtl92cu_hal_cfg)}, /*Xavi*/ + {RTL_USB_DEVICE(0x04f2, 0xaff9, rtl92cu_hal_cfg)}, /*Xavi*/ + {RTL_USB_DEVICE(0x04f2, 0xaffa, rtl92cu_hal_cfg)}, /*Xavi*/ + + /****** 8188CUS Slim Combo ********/ + {RTL_USB_DEVICE(0x04f2, 0xaff8, rtl92cu_hal_cfg)}, /*Xavi*/ + {RTL_USB_DEVICE(0x04f2, 0xaffb, rtl92cu_hal_cfg)}, /*Xavi*/ + {RTL_USB_DEVICE(0x04f2, 0xaffc, rtl92cu_hal_cfg)}, /*Xavi*/ + {RTL_USB_DEVICE(0x2019, 0x1201, rtl92cu_hal_cfg)}, /*Planex-Vencer*/ + + /****** 8192CU ********/ + {RTL_USB_DEVICE(0x050d, 0x1004, rtl92cu_hal_cfg)}, /*Belcom-SurfN300*/ + {RTL_USB_DEVICE(0x050d, 0x2102, rtl92cu_hal_cfg)}, /*Belcom-Sercomm*/ + {RTL_USB_DEVICE(0x050d, 0x2103, rtl92cu_hal_cfg)}, /*Belcom-Edimax*/ + {RTL_USB_DEVICE(0x0586, 0x341f, rtl92cu_hal_cfg)}, /*Zyxel -Abocom*/ + {RTL_USB_DEVICE(0x07aa, 0x0056, rtl92cu_hal_cfg)}, /*ATKK-Gemtek*/ + {RTL_USB_DEVICE(0x07b8, 0x8178, rtl92cu_hal_cfg)}, /*Funai -Abocom*/ + {RTL_USB_DEVICE(0x0846, 0x9021, rtl92cu_hal_cfg)}, /*Netgear-Sercomm*/ + {RTL_USB_DEVICE(0x0846, 0xf001, rtl92cu_hal_cfg)}, /*On Netwrks N300MA*/ + {RTL_USB_DEVICE(0x0b05, 0x17ab, rtl92cu_hal_cfg)}, /*ASUS-Edimax*/ + {RTL_USB_DEVICE(0x0bda, 0x8186, rtl92cu_hal_cfg)}, /*Realtek 92CE-VAU*/ + {RTL_USB_DEVICE(0x0df6, 0x0061, rtl92cu_hal_cfg)}, /*Sitecom-Edimax*/ + {RTL_USB_DEVICE(0x0e66, 0x0019, rtl92cu_hal_cfg)}, /*Hawking-Edimax*/ + {RTL_USB_DEVICE(0x2001, 0x3307, rtl92cu_hal_cfg)}, /*D-Link-Cameo*/ + {RTL_USB_DEVICE(0x2001, 0x3309, rtl92cu_hal_cfg)}, /*D-Link-Alpha*/ + {RTL_USB_DEVICE(0x2001, 0x330a, rtl92cu_hal_cfg)}, /*D-Link-Alpha*/ + {RTL_USB_DEVICE(0x2019, 0xab2b, rtl92cu_hal_cfg)}, /*Planex -Abocom*/ + {RTL_USB_DEVICE(0x20f4, 0x624d, rtl92cu_hal_cfg)}, /*TRENDNet*/ + {RTL_USB_DEVICE(0x2357, 0x0100, rtl92cu_hal_cfg)}, /*TP-Link WN8200ND*/ + {RTL_USB_DEVICE(0x7392, 0x7822, rtl92cu_hal_cfg)}, /*Edimax -Edimax*/ + {} +}; + +MODULE_DEVICE_TABLE(usb, rtl8192c_usb_ids); + +static int rtl8192cu_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + return rtl_usb_probe(intf, id, &rtl92cu_hal_cfg); +} + +static struct usb_driver rtl8192cu_driver = { + .name = "rtl8192cu", + .probe = rtl8192cu_probe, + .disconnect = rtl_usb_disconnect, + .id_table = rtl8192c_usb_ids, + +#ifdef CONFIG_PM + /* .suspend = rtl_usb_suspend, */ + /* .resume = rtl_usb_resume, */ + /* .reset_resume = rtl8192c_resume, */ +#endif /* CONFIG_PM */ +#ifdef CONFIG_AUTOSUSPEND + .supports_autosuspend = 1, +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +module_usb_driver(rtl8192cu_driver); diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/sw.h b/drivers/net/wireless/rtlwifi/rtl8192cu/sw.h new file mode 100644 index 0000000..262e1e4 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/sw.h @@ -0,0 +1,56 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CU_SW_H__ +#define __RTL92CU_SW_H__ + +#define EFUSE_MAX_SECTION 16 + +void rtl92cu_phy_rf6052_set_cck_txpower(struct ieee80211_hw *hw, + u8 *powerlevel); +void rtl92cu_phy_rf6052_set_ofdm_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel, u8 channel); +bool _rtl92cu_phy_config_bb_with_headerfile(struct ieee80211_hw *hw, + u8 configtype); +bool _rtl92cu_phy_config_bb_with_pgheaderfile(struct ieee80211_hw *hw, + u8 configtype); +void _rtl92cu_phy_lc_calibrate(struct ieee80211_hw *hw, bool is2t); +void rtl92cu_phy_set_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, + u32 regaddr, u32 bitmask, u32 data); +bool rtl92cu_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state); +u32 rtl92cu_phy_query_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, u32 bitmask); +void rtl92cu_phy_set_bw_mode_callback(struct ieee80211_hw *hw); +void rtl92cu_update_hal_rate_tbl(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + u8 rssi_level); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/table.c b/drivers/net/wireless/rtlwifi/rtl8192cu/table.c new file mode 100644 index 0000000..966be51 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/table.c @@ -0,0 +1,1888 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "table.h" + +u32 RTL8192CUPHY_REG_2TARRAY[RTL8192CUPHY_REG_2TARRAY_LENGTH] = { + 0x024, 0x0011800f, + 0x028, 0x00ffdb83, + 0x800, 0x80040002, + 0x804, 0x00000003, + 0x808, 0x0000fc00, + 0x80c, 0x0000000a, + 0x810, 0x10005388, + 0x814, 0x020c3d10, + 0x818, 0x02200385, + 0x81c, 0x00000000, + 0x820, 0x01000100, + 0x824, 0x00390004, + 0x828, 0x01000100, + 0x82c, 0x00390004, + 0x830, 0x27272727, + 0x834, 0x27272727, + 0x838, 0x27272727, + 0x83c, 0x27272727, + 0x840, 0x00010000, + 0x844, 0x00010000, + 0x848, 0x27272727, + 0x84c, 0x27272727, + 0x850, 0x00000000, + 0x854, 0x00000000, + 0x858, 0x569a569a, + 0x85c, 0x0c1b25a4, + 0x860, 0x66e60230, + 0x864, 0x061f0130, + 0x868, 0x27272727, + 0x86c, 0x2b2b2b27, + 0x870, 0x07000700, + 0x874, 0x22184000, + 0x878, 0x08080808, + 0x87c, 0x00000000, + 0x880, 0xc0083070, + 0x884, 0x000004d5, + 0x888, 0x00000000, + 0x88c, 0xcc0000c0, + 0x890, 0x00000800, + 0x894, 0xfffffffe, + 0x898, 0x40302010, + 0x89c, 0x00706050, + 0x900, 0x00000000, + 0x904, 0x00000023, + 0x908, 0x00000000, + 0x90c, 0x81121313, + 0xa00, 0x00d047c8, + 0xa04, 0x80ff000c, + 0xa08, 0x8c838300, + 0xa0c, 0x2e68120f, + 0xa10, 0x9500bb78, + 0xa14, 0x11144028, + 0xa18, 0x00881117, + 0xa1c, 0x89140f00, + 0xa20, 0x1a1b0000, + 0xa24, 0x090e1317, + 0xa28, 0x00000204, + 0xa2c, 0x00d30000, + 0xa70, 0x101fbf00, + 0xa74, 0x00000007, + 0xc00, 0x48071d40, + 0xc04, 0x03a05633, + 0xc08, 0x000000e4, + 0xc0c, 0x6c6c6c6c, + 0xc10, 0x08800000, + 0xc14, 0x40000100, + 0xc18, 0x08800000, + 0xc1c, 0x40000100, + 0xc20, 0x00000000, + 0xc24, 0x00000000, + 0xc28, 0x00000000, + 0xc2c, 0x00000000, + 0xc30, 0x69e9ac44, + 0xc34, 0x469652cf, + 0xc38, 0x49795994, + 0xc3c, 0x0a97971c, + 0xc40, 0x1f7c403f, + 0xc44, 0x000100b7, + 0xc48, 0xec020107, + 0xc4c, 0x007f037f, + 0xc50, 0x6954341e, + 0xc54, 0x43bc0094, + 0xc58, 0x6954341e, + 0xc5c, 0x433c0094, + 0xc60, 0x00000000, + 0xc64, 0x5116848b, + 0xc68, 0x47c00bff, + 0xc6c, 0x00000036, + 0xc70, 0x2c7f000d, + 0xc74, 0x0186115b, + 0xc78, 0x0000001f, + 0xc7c, 0x00b99612, + 0xc80, 0x40000100, + 0xc84, 0x20f60000, + 0xc88, 0x40000100, + 0xc8c, 0x20200000, + 0xc90, 0x00121820, + 0xc94, 0x00000000, + 0xc98, 0x00121820, + 0xc9c, 0x00007f7f, + 0xca0, 0x00000000, + 0xca4, 0x00000080, + 0xca8, 0x00000000, + 0xcac, 0x00000000, + 0xcb0, 0x00000000, + 0xcb4, 0x00000000, + 0xcb8, 0x00000000, + 0xcbc, 0x28000000, + 0xcc0, 0x00000000, + 0xcc4, 0x00000000, + 0xcc8, 0x00000000, + 0xccc, 0x00000000, + 0xcd0, 0x00000000, + 0xcd4, 0x00000000, + 0xcd8, 0x64b22427, + 0xcdc, 0x00766932, + 0xce0, 0x00222222, + 0xce4, 0x00000000, + 0xce8, 0x37644302, + 0xcec, 0x2f97d40c, + 0xd00, 0x00080740, + 0xd04, 0x00020403, + 0xd08, 0x0000907f, + 0xd0c, 0x20010201, + 0xd10, 0xa0633333, + 0xd14, 0x3333bc43, + 0xd18, 0x7a8f5b6b, + 0xd2c, 0xcc979975, + 0xd30, 0x00000000, + 0xd34, 0x80608000, + 0xd38, 0x00000000, + 0xd3c, 0x00027293, + 0xd40, 0x00000000, + 0xd44, 0x00000000, + 0xd48, 0x00000000, + 0xd4c, 0x00000000, + 0xd50, 0x6437140a, + 0xd54, 0x00000000, + 0xd58, 0x00000000, + 0xd5c, 0x30032064, + 0xd60, 0x4653de68, + 0xd64, 0x04518a3c, + 0xd68, 0x00002101, + 0xd6c, 0x2a201c16, + 0xd70, 0x1812362e, + 0xd74, 0x322c2220, + 0xd78, 0x000e3c24, + 0xe00, 0x2a2a2a2a, + 0xe04, 0x2a2a2a2a, + 0xe08, 0x03902a2a, + 0xe10, 0x2a2a2a2a, + 0xe14, 0x2a2a2a2a, + 0xe18, 0x2a2a2a2a, + 0xe1c, 0x2a2a2a2a, + 0xe28, 0x00000000, + 0xe30, 0x1000dc1f, + 0xe34, 0x10008c1f, + 0xe38, 0x02140102, + 0xe3c, 0x681604c2, + 0xe40, 0x01007c00, + 0xe44, 0x01004800, + 0xe48, 0xfb000000, + 0xe4c, 0x000028d1, + 0xe50, 0x1000dc1f, + 0xe54, 0x10008c1f, + 0xe58, 0x02140102, + 0xe5c, 0x28160d05, + 0xe60, 0x00000010, + 0xe68, 0x001b25a4, + 0xe6c, 0x63db25a4, + 0xe70, 0x63db25a4, + 0xe74, 0x0c1b25a4, + 0xe78, 0x0c1b25a4, + 0xe7c, 0x0c1b25a4, + 0xe80, 0x0c1b25a4, + 0xe84, 0x63db25a4, + 0xe88, 0x0c1b25a4, + 0xe8c, 0x63db25a4, + 0xed0, 0x63db25a4, + 0xed4, 0x63db25a4, + 0xed8, 0x63db25a4, + 0xedc, 0x001b25a4, + 0xee0, 0x001b25a4, + 0xeec, 0x6fdb25a4, + 0xf14, 0x00000003, + 0xf4c, 0x00000000, + 0xf00, 0x00000300, +}; + +u32 RTL8192CUPHY_REG_1TARRAY[RTL8192CUPHY_REG_1TARRAY_LENGTH] = { + 0x024, 0x0011800f, + 0x028, 0x00ffdb83, + 0x800, 0x80040000, + 0x804, 0x00000001, + 0x808, 0x0000fc00, + 0x80c, 0x0000000a, + 0x810, 0x10005388, + 0x814, 0x020c3d10, + 0x818, 0x02200385, + 0x81c, 0x00000000, + 0x820, 0x01000100, + 0x824, 0x00390004, + 0x828, 0x00000000, + 0x82c, 0x00000000, + 0x830, 0x00000000, + 0x834, 0x00000000, + 0x838, 0x00000000, + 0x83c, 0x00000000, + 0x840, 0x00010000, + 0x844, 0x00000000, + 0x848, 0x00000000, + 0x84c, 0x00000000, + 0x850, 0x00000000, + 0x854, 0x00000000, + 0x858, 0x569a569a, + 0x85c, 0x001b25a4, + 0x860, 0x66e60230, + 0x864, 0x061f0130, + 0x868, 0x00000000, + 0x86c, 0x32323200, + 0x870, 0x07000700, + 0x874, 0x22004000, + 0x878, 0x00000808, + 0x87c, 0x00000000, + 0x880, 0xc0083070, + 0x884, 0x000004d5, + 0x888, 0x00000000, + 0x88c, 0xccc000c0, + 0x890, 0x00000800, + 0x894, 0xfffffffe, + 0x898, 0x40302010, + 0x89c, 0x00706050, + 0x900, 0x00000000, + 0x904, 0x00000023, + 0x908, 0x00000000, + 0x90c, 0x81121111, + 0xa00, 0x00d047c8, + 0xa04, 0x80ff000c, + 0xa08, 0x8c838300, + 0xa0c, 0x2e68120f, + 0xa10, 0x9500bb78, + 0xa14, 0x11144028, + 0xa18, 0x00881117, + 0xa1c, 0x89140f00, + 0xa20, 0x1a1b0000, + 0xa24, 0x090e1317, + 0xa28, 0x00000204, + 0xa2c, 0x00d30000, + 0xa70, 0x101fbf00, + 0xa74, 0x00000007, + 0xc00, 0x48071d40, + 0xc04, 0x03a05611, + 0xc08, 0x000000e4, + 0xc0c, 0x6c6c6c6c, + 0xc10, 0x08800000, + 0xc14, 0x40000100, + 0xc18, 0x08800000, + 0xc1c, 0x40000100, + 0xc20, 0x00000000, + 0xc24, 0x00000000, + 0xc28, 0x00000000, + 0xc2c, 0x00000000, + 0xc30, 0x69e9ac44, + 0xc34, 0x469652cf, + 0xc38, 0x49795994, + 0xc3c, 0x0a97971c, + 0xc40, 0x1f7c403f, + 0xc44, 0x000100b7, + 0xc48, 0xec020107, + 0xc4c, 0x007f037f, + 0xc50, 0x6954341e, + 0xc54, 0x43bc0094, + 0xc58, 0x6954341e, + 0xc5c, 0x433c0094, + 0xc60, 0x00000000, + 0xc64, 0x5116848b, + 0xc68, 0x47c00bff, + 0xc6c, 0x00000036, + 0xc70, 0x2c7f000d, + 0xc74, 0x018610db, + 0xc78, 0x0000001f, + 0xc7c, 0x00b91612, + 0xc80, 0x40000100, + 0xc84, 0x20f60000, + 0xc88, 0x40000100, + 0xc8c, 0x20200000, + 0xc90, 0x00121820, + 0xc94, 0x00000000, + 0xc98, 0x00121820, + 0xc9c, 0x00007f7f, + 0xca0, 0x00000000, + 0xca4, 0x00000080, + 0xca8, 0x00000000, + 0xcac, 0x00000000, + 0xcb0, 0x00000000, + 0xcb4, 0x00000000, + 0xcb8, 0x00000000, + 0xcbc, 0x28000000, + 0xcc0, 0x00000000, + 0xcc4, 0x00000000, + 0xcc8, 0x00000000, + 0xccc, 0x00000000, + 0xcd0, 0x00000000, + 0xcd4, 0x00000000, + 0xcd8, 0x64b22427, + 0xcdc, 0x00766932, + 0xce0, 0x00222222, + 0xce4, 0x00000000, + 0xce8, 0x37644302, + 0xcec, 0x2f97d40c, + 0xd00, 0x00080740, + 0xd04, 0x00020401, + 0xd08, 0x0000907f, + 0xd0c, 0x20010201, + 0xd10, 0xa0633333, + 0xd14, 0x3333bc43, + 0xd18, 0x7a8f5b6b, + 0xd2c, 0xcc979975, + 0xd30, 0x00000000, + 0xd34, 0x80608000, + 0xd38, 0x00000000, + 0xd3c, 0x00027293, + 0xd40, 0x00000000, + 0xd44, 0x00000000, + 0xd48, 0x00000000, + 0xd4c, 0x00000000, + 0xd50, 0x6437140a, + 0xd54, 0x00000000, + 0xd58, 0x00000000, + 0xd5c, 0x30032064, + 0xd60, 0x4653de68, + 0xd64, 0x04518a3c, + 0xd68, 0x00002101, + 0xd6c, 0x2a201c16, + 0xd70, 0x1812362e, + 0xd74, 0x322c2220, + 0xd78, 0x000e3c24, + 0xe00, 0x2a2a2a2a, + 0xe04, 0x2a2a2a2a, + 0xe08, 0x03902a2a, + 0xe10, 0x2a2a2a2a, + 0xe14, 0x2a2a2a2a, + 0xe18, 0x2a2a2a2a, + 0xe1c, 0x2a2a2a2a, + 0xe28, 0x00000000, + 0xe30, 0x1000dc1f, + 0xe34, 0x10008c1f, + 0xe38, 0x02140102, + 0xe3c, 0x681604c2, + 0xe40, 0x01007c00, + 0xe44, 0x01004800, + 0xe48, 0xfb000000, + 0xe4c, 0x000028d1, + 0xe50, 0x1000dc1f, + 0xe54, 0x10008c1f, + 0xe58, 0x02140102, + 0xe5c, 0x28160d05, + 0xe60, 0x00000008, + 0xe68, 0x001b25a4, + 0xe6c, 0x631b25a0, + 0xe70, 0x631b25a0, + 0xe74, 0x081b25a0, + 0xe78, 0x081b25a0, + 0xe7c, 0x081b25a0, + 0xe80, 0x081b25a0, + 0xe84, 0x631b25a0, + 0xe88, 0x081b25a0, + 0xe8c, 0x631b25a0, + 0xed0, 0x631b25a0, + 0xed4, 0x631b25a0, + 0xed8, 0x631b25a0, + 0xedc, 0x001b25a0, + 0xee0, 0x001b25a0, + 0xeec, 0x6b1b25a0, + 0xf14, 0x00000003, + 0xf4c, 0x00000000, + 0xf00, 0x00000300, +}; + +u32 RTL8192CUPHY_REG_ARRAY_PG[RTL8192CUPHY_REG_ARRAY_PGLENGTH] = { + 0xe00, 0xffffffff, 0x07090c0c, + 0xe04, 0xffffffff, 0x01020405, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x0b0c0c0e, + 0xe14, 0xffffffff, 0x01030506, + 0xe18, 0xffffffff, 0x0b0c0d0e, + 0xe1c, 0xffffffff, 0x01030509, + 0x830, 0xffffffff, 0x07090c0c, + 0x834, 0xffffffff, 0x01020405, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x0b0c0d0e, + 0x848, 0xffffffff, 0x01030509, + 0x84c, 0xffffffff, 0x0b0c0d0e, + 0x868, 0xffffffff, 0x01030509, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x04040404, + 0xe04, 0xffffffff, 0x00020204, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x06060606, + 0xe14, 0xffffffff, 0x00020406, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x04040404, + 0x834, 0xffffffff, 0x00020204, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x06060606, + 0x848, 0xffffffff, 0x00020406, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x04040404, + 0xe04, 0xffffffff, 0x00020204, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x04040404, + 0x834, 0xffffffff, 0x00020204, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, +}; + +u32 RTL8192CURADIOA_2TARRAY[RTL8192CURADIOA_2TARRAYLENGTH] = { + 0x000, 0x00030159, + 0x001, 0x00031284, + 0x002, 0x00098000, + 0x003, 0x00018c63, + 0x004, 0x000210e7, + 0x009, 0x0002044f, + 0x00a, 0x0001adb1, + 0x00b, 0x00054867, + 0x00c, 0x0008992e, + 0x00d, 0x0000e52c, + 0x00e, 0x00039ce7, + 0x00f, 0x00000451, + 0x019, 0x00000000, + 0x01a, 0x00010255, + 0x01b, 0x00060a00, + 0x01c, 0x000fc378, + 0x01d, 0x000a1250, + 0x01e, 0x0004445f, + 0x01f, 0x00080001, + 0x020, 0x0000b614, + 0x021, 0x0006c000, + 0x022, 0x00000000, + 0x023, 0x00001558, + 0x024, 0x00000060, + 0x025, 0x00000483, + 0x026, 0x0004f000, + 0x027, 0x000ec7d9, + 0x028, 0x000577c0, + 0x029, 0x00004783, + 0x02a, 0x00000001, + 0x02b, 0x00021334, + 0x02a, 0x00000000, + 0x02b, 0x00000054, + 0x02a, 0x00000001, + 0x02b, 0x00000808, + 0x02b, 0x00053333, + 0x02c, 0x0000000c, + 0x02a, 0x00000002, + 0x02b, 0x00000808, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000003, + 0x02b, 0x00000808, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x00000004, + 0x02b, 0x00000808, + 0x02b, 0x0006b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000005, + 0x02b, 0x00000808, + 0x02b, 0x00073333, + 0x02c, 0x0000000d, + 0x02a, 0x00000006, + 0x02b, 0x00000709, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000007, + 0x02b, 0x00000709, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x00000008, + 0x02b, 0x0000060a, + 0x02b, 0x0004b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000009, + 0x02b, 0x0000060a, + 0x02b, 0x00053333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000a, + 0x02b, 0x0000060a, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000b, + 0x02b, 0x0000060a, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000c, + 0x02b, 0x0000060a, + 0x02b, 0x0006b333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000d, + 0x02b, 0x0000060a, + 0x02b, 0x00073333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000e, + 0x02b, 0x0000050b, + 0x02b, 0x00066666, + 0x02c, 0x0000001a, + 0x02a, 0x000e0000, + 0x010, 0x0004000f, + 0x011, 0x000e31fc, + 0x010, 0x0006000f, + 0x011, 0x000ff9f8, + 0x010, 0x0002000f, + 0x011, 0x000203f9, + 0x010, 0x0003000f, + 0x011, 0x000ff500, + 0x010, 0x00000000, + 0x011, 0x00000000, + 0x010, 0x0008000f, + 0x011, 0x0003f100, + 0x010, 0x0009000f, + 0x011, 0x00023100, + 0x012, 0x00032000, + 0x012, 0x00071000, + 0x012, 0x000b0000, + 0x012, 0x000fc000, + 0x013, 0x000287af, + 0x013, 0x000244b7, + 0x013, 0x000204ab, + 0x013, 0x0001c49f, + 0x013, 0x00018493, + 0x013, 0x00014297, + 0x013, 0x00010295, + 0x013, 0x0000c298, + 0x013, 0x0000819c, + 0x013, 0x000040a8, + 0x013, 0x0000001c, + 0x014, 0x0001944c, + 0x014, 0x00059444, + 0x014, 0x0009944c, + 0x014, 0x000d9444, + 0x015, 0x0000f424, + 0x015, 0x0004f424, + 0x015, 0x0008f424, + 0x015, 0x000cf424, + 0x016, 0x000e0330, + 0x016, 0x000a0330, + 0x016, 0x00060330, + 0x016, 0x00020330, + 0x000, 0x00010159, + 0x018, 0x0000f401, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x01f, 0x00080003, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x01e, 0x00044457, + 0x01f, 0x00080000, + 0x000, 0x00030159, +}; + +u32 RTL8192CU_RADIOB_2TARRAY[RTL8192CURADIOB_2TARRAYLENGTH] = { + 0x000, 0x00030159, + 0x001, 0x00031284, + 0x002, 0x00098000, + 0x003, 0x00018c63, + 0x004, 0x000210e7, + 0x009, 0x0002044f, + 0x00a, 0x0001adb1, + 0x00b, 0x00054867, + 0x00c, 0x0008992e, + 0x00d, 0x0000e52c, + 0x00e, 0x00039ce7, + 0x00f, 0x00000451, + 0x012, 0x00032000, + 0x012, 0x00071000, + 0x012, 0x000b0000, + 0x012, 0x000fc000, + 0x013, 0x000287af, + 0x013, 0x000244b7, + 0x013, 0x000204ab, + 0x013, 0x0001c49f, + 0x013, 0x00018493, + 0x013, 0x00014297, + 0x013, 0x00010295, + 0x013, 0x0000c298, + 0x013, 0x0000819c, + 0x013, 0x000040a8, + 0x013, 0x0000001c, + 0x014, 0x0001944c, + 0x014, 0x00059444, + 0x014, 0x0009944c, + 0x014, 0x000d9444, + 0x015, 0x0000f424, + 0x015, 0x0004f424, + 0x015, 0x0008f424, + 0x015, 0x000cf424, + 0x016, 0x000e0330, + 0x016, 0x000a0330, + 0x016, 0x00060330, + 0x016, 0x00020330, +}; + +u32 RTL8192CU_RADIOA_1TARRAY[RTL8192CURADIOA_1TARRAYLENGTH] = { + 0x000, 0x00030159, + 0x001, 0x00031284, + 0x002, 0x00098000, + 0x003, 0x00018c63, + 0x004, 0x000210e7, + 0x009, 0x0002044f, + 0x00a, 0x0001adb1, + 0x00b, 0x00054867, + 0x00c, 0x0008992e, + 0x00d, 0x0000e52c, + 0x00e, 0x00039ce7, + 0x00f, 0x00000451, + 0x019, 0x00000000, + 0x01a, 0x00010255, + 0x01b, 0x00060a00, + 0x01c, 0x000fc378, + 0x01d, 0x000a1250, + 0x01e, 0x0004445f, + 0x01f, 0x00080001, + 0x020, 0x0000b614, + 0x021, 0x0006c000, + 0x022, 0x00000000, + 0x023, 0x00001558, + 0x024, 0x00000060, + 0x025, 0x00000483, + 0x026, 0x0004f000, + 0x027, 0x000ec7d9, + 0x028, 0x000577c0, + 0x029, 0x00004783, + 0x02a, 0x00000001, + 0x02b, 0x00021334, + 0x02a, 0x00000000, + 0x02b, 0x00000054, + 0x02a, 0x00000001, + 0x02b, 0x00000808, + 0x02b, 0x00053333, + 0x02c, 0x0000000c, + 0x02a, 0x00000002, + 0x02b, 0x00000808, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000003, + 0x02b, 0x00000808, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x00000004, + 0x02b, 0x00000808, + 0x02b, 0x0006b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000005, + 0x02b, 0x00000808, + 0x02b, 0x00073333, + 0x02c, 0x0000000d, + 0x02a, 0x00000006, + 0x02b, 0x00000709, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000007, + 0x02b, 0x00000709, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x00000008, + 0x02b, 0x0000060a, + 0x02b, 0x0004b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000009, + 0x02b, 0x0000060a, + 0x02b, 0x00053333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000a, + 0x02b, 0x0000060a, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000b, + 0x02b, 0x0000060a, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000c, + 0x02b, 0x0000060a, + 0x02b, 0x0006b333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000d, + 0x02b, 0x0000060a, + 0x02b, 0x00073333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000e, + 0x02b, 0x0000050b, + 0x02b, 0x00066666, + 0x02c, 0x0000001a, + 0x02a, 0x000e0000, + 0x010, 0x0004000f, + 0x011, 0x000e31fc, + 0x010, 0x0006000f, + 0x011, 0x000ff9f8, + 0x010, 0x0002000f, + 0x011, 0x000203f9, + 0x010, 0x0003000f, + 0x011, 0x000ff500, + 0x010, 0x00000000, + 0x011, 0x00000000, + 0x010, 0x0008000f, + 0x011, 0x0003f100, + 0x010, 0x0009000f, + 0x011, 0x00023100, + 0x012, 0x00032000, + 0x012, 0x00071000, + 0x012, 0x000b0000, + 0x012, 0x000fc000, + 0x013, 0x000287b3, + 0x013, 0x000244b7, + 0x013, 0x000204ab, + 0x013, 0x0001c49f, + 0x013, 0x00018493, + 0x013, 0x0001429b, + 0x013, 0x00010299, + 0x013, 0x0000c29c, + 0x013, 0x000081a0, + 0x013, 0x000040ac, + 0x013, 0x00000020, + 0x014, 0x0001944c, + 0x014, 0x00059444, + 0x014, 0x0009944c, + 0x014, 0x000d9444, + 0x015, 0x0000f405, + 0x015, 0x0004f405, + 0x015, 0x0008f405, + 0x015, 0x000cf405, + 0x016, 0x000e0330, + 0x016, 0x000a0330, + 0x016, 0x00060330, + 0x016, 0x00020330, + 0x000, 0x00010159, + 0x018, 0x0000f401, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x01f, 0x00080003, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x01e, 0x00044457, + 0x01f, 0x00080000, + 0x000, 0x00030159, +}; + +u32 RTL8192CU_RADIOB_1TARRAY[RTL8192CURADIOB_1TARRAYLENGTH] = { + 0x0, +}; + +u32 RTL8192CUMAC_2T_ARRAY[RTL8192CUMAC_2T_ARRAYLENGTH] = { + 0x420, 0x00000080, + 0x423, 0x00000000, + 0x430, 0x00000000, + 0x431, 0x00000000, + 0x432, 0x00000000, + 0x433, 0x00000001, + 0x434, 0x00000004, + 0x435, 0x00000005, + 0x436, 0x00000006, + 0x437, 0x00000007, + 0x438, 0x00000000, + 0x439, 0x00000000, + 0x43a, 0x00000000, + 0x43b, 0x00000001, + 0x43c, 0x00000004, + 0x43d, 0x00000005, + 0x43e, 0x00000006, + 0x43f, 0x00000007, + 0x440, 0x0000005d, + 0x441, 0x00000001, + 0x442, 0x00000000, + 0x444, 0x00000015, + 0x445, 0x000000f0, + 0x446, 0x0000000f, + 0x447, 0x00000000, + 0x458, 0x00000041, + 0x459, 0x000000a8, + 0x45a, 0x00000072, + 0x45b, 0x000000b9, + 0x460, 0x00000066, + 0x461, 0x00000066, + 0x462, 0x00000008, + 0x463, 0x00000003, + 0x4c8, 0x000000ff, + 0x4c9, 0x00000008, + 0x4cc, 0x000000ff, + 0x4cd, 0x000000ff, + 0x4ce, 0x00000001, + 0x500, 0x00000026, + 0x501, 0x000000a2, + 0x502, 0x0000002f, + 0x503, 0x00000000, + 0x504, 0x00000028, + 0x505, 0x000000a3, + 0x506, 0x0000005e, + 0x507, 0x00000000, + 0x508, 0x0000002b, + 0x509, 0x000000a4, + 0x50a, 0x0000005e, + 0x50b, 0x00000000, + 0x50c, 0x0000004f, + 0x50d, 0x000000a4, + 0x50e, 0x00000000, + 0x50f, 0x00000000, + 0x512, 0x0000001c, + 0x514, 0x0000000a, + 0x515, 0x00000010, + 0x516, 0x0000000a, + 0x517, 0x00000010, + 0x51a, 0x00000016, + 0x524, 0x0000000f, + 0x525, 0x0000004f, + 0x546, 0x00000040, + 0x547, 0x00000000, + 0x550, 0x00000010, + 0x551, 0x00000010, + 0x559, 0x00000002, + 0x55a, 0x00000002, + 0x55d, 0x000000ff, + 0x605, 0x00000030, + 0x608, 0x0000000e, + 0x609, 0x0000002a, + 0x652, 0x00000020, + 0x63c, 0x0000000a, + 0x63d, 0x0000000e, + 0x63e, 0x0000000a, + 0x63f, 0x0000000e, + 0x66e, 0x00000005, + 0x700, 0x00000021, + 0x701, 0x00000043, + 0x702, 0x00000065, + 0x703, 0x00000087, + 0x708, 0x00000021, + 0x709, 0x00000043, + 0x70a, 0x00000065, + 0x70b, 0x00000087, +}; + +u32 RTL8192CUAGCTAB_2TARRAY[RTL8192CUAGCTAB_2TARRAYLENGTH] = { + 0xc78, 0x7b000001, + 0xc78, 0x7b010001, + 0xc78, 0x7b020001, + 0xc78, 0x7b030001, + 0xc78, 0x7b040001, + 0xc78, 0x7b050001, + 0xc78, 0x7a060001, + 0xc78, 0x79070001, + 0xc78, 0x78080001, + 0xc78, 0x77090001, + 0xc78, 0x760a0001, + 0xc78, 0x750b0001, + 0xc78, 0x740c0001, + 0xc78, 0x730d0001, + 0xc78, 0x720e0001, + 0xc78, 0x710f0001, + 0xc78, 0x70100001, + 0xc78, 0x6f110001, + 0xc78, 0x6e120001, + 0xc78, 0x6d130001, + 0xc78, 0x6c140001, + 0xc78, 0x6b150001, + 0xc78, 0x6a160001, + 0xc78, 0x69170001, + 0xc78, 0x68180001, + 0xc78, 0x67190001, + 0xc78, 0x661a0001, + 0xc78, 0x651b0001, + 0xc78, 0x641c0001, + 0xc78, 0x631d0001, + 0xc78, 0x621e0001, + 0xc78, 0x611f0001, + 0xc78, 0x60200001, + 0xc78, 0x49210001, + 0xc78, 0x48220001, + 0xc78, 0x47230001, + 0xc78, 0x46240001, + 0xc78, 0x45250001, + 0xc78, 0x44260001, + 0xc78, 0x43270001, + 0xc78, 0x42280001, + 0xc78, 0x41290001, + 0xc78, 0x402a0001, + 0xc78, 0x262b0001, + 0xc78, 0x252c0001, + 0xc78, 0x242d0001, + 0xc78, 0x232e0001, + 0xc78, 0x222f0001, + 0xc78, 0x21300001, + 0xc78, 0x20310001, + 0xc78, 0x06320001, + 0xc78, 0x05330001, + 0xc78, 0x04340001, + 0xc78, 0x03350001, + 0xc78, 0x02360001, + 0xc78, 0x01370001, + 0xc78, 0x00380001, + 0xc78, 0x00390001, + 0xc78, 0x003a0001, + 0xc78, 0x003b0001, + 0xc78, 0x003c0001, + 0xc78, 0x003d0001, + 0xc78, 0x003e0001, + 0xc78, 0x003f0001, + 0xc78, 0x7b400001, + 0xc78, 0x7b410001, + 0xc78, 0x7b420001, + 0xc78, 0x7b430001, + 0xc78, 0x7b440001, + 0xc78, 0x7b450001, + 0xc78, 0x7a460001, + 0xc78, 0x79470001, + 0xc78, 0x78480001, + 0xc78, 0x77490001, + 0xc78, 0x764a0001, + 0xc78, 0x754b0001, + 0xc78, 0x744c0001, + 0xc78, 0x734d0001, + 0xc78, 0x724e0001, + 0xc78, 0x714f0001, + 0xc78, 0x70500001, + 0xc78, 0x6f510001, + 0xc78, 0x6e520001, + 0xc78, 0x6d530001, + 0xc78, 0x6c540001, + 0xc78, 0x6b550001, + 0xc78, 0x6a560001, + 0xc78, 0x69570001, + 0xc78, 0x68580001, + 0xc78, 0x67590001, + 0xc78, 0x665a0001, + 0xc78, 0x655b0001, + 0xc78, 0x645c0001, + 0xc78, 0x635d0001, + 0xc78, 0x625e0001, + 0xc78, 0x615f0001, + 0xc78, 0x60600001, + 0xc78, 0x49610001, + 0xc78, 0x48620001, + 0xc78, 0x47630001, + 0xc78, 0x46640001, + 0xc78, 0x45650001, + 0xc78, 0x44660001, + 0xc78, 0x43670001, + 0xc78, 0x42680001, + 0xc78, 0x41690001, + 0xc78, 0x406a0001, + 0xc78, 0x266b0001, + 0xc78, 0x256c0001, + 0xc78, 0x246d0001, + 0xc78, 0x236e0001, + 0xc78, 0x226f0001, + 0xc78, 0x21700001, + 0xc78, 0x20710001, + 0xc78, 0x06720001, + 0xc78, 0x05730001, + 0xc78, 0x04740001, + 0xc78, 0x03750001, + 0xc78, 0x02760001, + 0xc78, 0x01770001, + 0xc78, 0x00780001, + 0xc78, 0x00790001, + 0xc78, 0x007a0001, + 0xc78, 0x007b0001, + 0xc78, 0x007c0001, + 0xc78, 0x007d0001, + 0xc78, 0x007e0001, + 0xc78, 0x007f0001, + 0xc78, 0x3800001e, + 0xc78, 0x3801001e, + 0xc78, 0x3802001e, + 0xc78, 0x3803001e, + 0xc78, 0x3804001e, + 0xc78, 0x3805001e, + 0xc78, 0x3806001e, + 0xc78, 0x3807001e, + 0xc78, 0x3808001e, + 0xc78, 0x3c09001e, + 0xc78, 0x3e0a001e, + 0xc78, 0x400b001e, + 0xc78, 0x440c001e, + 0xc78, 0x480d001e, + 0xc78, 0x4c0e001e, + 0xc78, 0x500f001e, + 0xc78, 0x5210001e, + 0xc78, 0x5611001e, + 0xc78, 0x5a12001e, + 0xc78, 0x5e13001e, + 0xc78, 0x6014001e, + 0xc78, 0x6015001e, + 0xc78, 0x6016001e, + 0xc78, 0x6217001e, + 0xc78, 0x6218001e, + 0xc78, 0x6219001e, + 0xc78, 0x621a001e, + 0xc78, 0x621b001e, + 0xc78, 0x621c001e, + 0xc78, 0x621d001e, + 0xc78, 0x621e001e, + 0xc78, 0x621f001e, +}; + +u32 RTL8192CUAGCTAB_1TARRAY[RTL8192CUAGCTAB_1TARRAYLENGTH] = { + 0xc78, 0x7b000001, + 0xc78, 0x7b010001, + 0xc78, 0x7b020001, + 0xc78, 0x7b030001, + 0xc78, 0x7b040001, + 0xc78, 0x7b050001, + 0xc78, 0x7a060001, + 0xc78, 0x79070001, + 0xc78, 0x78080001, + 0xc78, 0x77090001, + 0xc78, 0x760a0001, + 0xc78, 0x750b0001, + 0xc78, 0x740c0001, + 0xc78, 0x730d0001, + 0xc78, 0x720e0001, + 0xc78, 0x710f0001, + 0xc78, 0x70100001, + 0xc78, 0x6f110001, + 0xc78, 0x6e120001, + 0xc78, 0x6d130001, + 0xc78, 0x6c140001, + 0xc78, 0x6b150001, + 0xc78, 0x6a160001, + 0xc78, 0x69170001, + 0xc78, 0x68180001, + 0xc78, 0x67190001, + 0xc78, 0x661a0001, + 0xc78, 0x651b0001, + 0xc78, 0x641c0001, + 0xc78, 0x631d0001, + 0xc78, 0x621e0001, + 0xc78, 0x611f0001, + 0xc78, 0x60200001, + 0xc78, 0x49210001, + 0xc78, 0x48220001, + 0xc78, 0x47230001, + 0xc78, 0x46240001, + 0xc78, 0x45250001, + 0xc78, 0x44260001, + 0xc78, 0x43270001, + 0xc78, 0x42280001, + 0xc78, 0x41290001, + 0xc78, 0x402a0001, + 0xc78, 0x262b0001, + 0xc78, 0x252c0001, + 0xc78, 0x242d0001, + 0xc78, 0x232e0001, + 0xc78, 0x222f0001, + 0xc78, 0x21300001, + 0xc78, 0x20310001, + 0xc78, 0x06320001, + 0xc78, 0x05330001, + 0xc78, 0x04340001, + 0xc78, 0x03350001, + 0xc78, 0x02360001, + 0xc78, 0x01370001, + 0xc78, 0x00380001, + 0xc78, 0x00390001, + 0xc78, 0x003a0001, + 0xc78, 0x003b0001, + 0xc78, 0x003c0001, + 0xc78, 0x003d0001, + 0xc78, 0x003e0001, + 0xc78, 0x003f0001, + 0xc78, 0x7b400001, + 0xc78, 0x7b410001, + 0xc78, 0x7b420001, + 0xc78, 0x7b430001, + 0xc78, 0x7b440001, + 0xc78, 0x7b450001, + 0xc78, 0x7a460001, + 0xc78, 0x79470001, + 0xc78, 0x78480001, + 0xc78, 0x77490001, + 0xc78, 0x764a0001, + 0xc78, 0x754b0001, + 0xc78, 0x744c0001, + 0xc78, 0x734d0001, + 0xc78, 0x724e0001, + 0xc78, 0x714f0001, + 0xc78, 0x70500001, + 0xc78, 0x6f510001, + 0xc78, 0x6e520001, + 0xc78, 0x6d530001, + 0xc78, 0x6c540001, + 0xc78, 0x6b550001, + 0xc78, 0x6a560001, + 0xc78, 0x69570001, + 0xc78, 0x68580001, + 0xc78, 0x67590001, + 0xc78, 0x665a0001, + 0xc78, 0x655b0001, + 0xc78, 0x645c0001, + 0xc78, 0x635d0001, + 0xc78, 0x625e0001, + 0xc78, 0x615f0001, + 0xc78, 0x60600001, + 0xc78, 0x49610001, + 0xc78, 0x48620001, + 0xc78, 0x47630001, + 0xc78, 0x46640001, + 0xc78, 0x45650001, + 0xc78, 0x44660001, + 0xc78, 0x43670001, + 0xc78, 0x42680001, + 0xc78, 0x41690001, + 0xc78, 0x406a0001, + 0xc78, 0x266b0001, + 0xc78, 0x256c0001, + 0xc78, 0x246d0001, + 0xc78, 0x236e0001, + 0xc78, 0x226f0001, + 0xc78, 0x21700001, + 0xc78, 0x20710001, + 0xc78, 0x06720001, + 0xc78, 0x05730001, + 0xc78, 0x04740001, + 0xc78, 0x03750001, + 0xc78, 0x02760001, + 0xc78, 0x01770001, + 0xc78, 0x00780001, + 0xc78, 0x00790001, + 0xc78, 0x007a0001, + 0xc78, 0x007b0001, + 0xc78, 0x007c0001, + 0xc78, 0x007d0001, + 0xc78, 0x007e0001, + 0xc78, 0x007f0001, + 0xc78, 0x3800001e, + 0xc78, 0x3801001e, + 0xc78, 0x3802001e, + 0xc78, 0x3803001e, + 0xc78, 0x3804001e, + 0xc78, 0x3805001e, + 0xc78, 0x3806001e, + 0xc78, 0x3807001e, + 0xc78, 0x3808001e, + 0xc78, 0x3c09001e, + 0xc78, 0x3e0a001e, + 0xc78, 0x400b001e, + 0xc78, 0x440c001e, + 0xc78, 0x480d001e, + 0xc78, 0x4c0e001e, + 0xc78, 0x500f001e, + 0xc78, 0x5210001e, + 0xc78, 0x5611001e, + 0xc78, 0x5a12001e, + 0xc78, 0x5e13001e, + 0xc78, 0x6014001e, + 0xc78, 0x6015001e, + 0xc78, 0x6016001e, + 0xc78, 0x6217001e, + 0xc78, 0x6218001e, + 0xc78, 0x6219001e, + 0xc78, 0x621a001e, + 0xc78, 0x621b001e, + 0xc78, 0x621c001e, + 0xc78, 0x621d001e, + 0xc78, 0x621e001e, + 0xc78, 0x621f001e, +}; + +u32 RTL8192CUPHY_REG_1T_HPArray[RTL8192CUPHY_REG_1T_HPArrayLength] = { + 0x024, 0x0011800f, + 0x028, 0x00ffdb83, + 0x040, 0x000c0004, + 0x800, 0x80040000, + 0x804, 0x00000001, + 0x808, 0x0000fc00, + 0x80c, 0x0000000a, + 0x810, 0x10005388, + 0x814, 0x020c3d10, + 0x818, 0x02200385, + 0x81c, 0x00000000, + 0x820, 0x01000100, + 0x824, 0x00390204, + 0x828, 0x00000000, + 0x82c, 0x00000000, + 0x830, 0x00000000, + 0x834, 0x00000000, + 0x838, 0x00000000, + 0x83c, 0x00000000, + 0x840, 0x00010000, + 0x844, 0x00000000, + 0x848, 0x00000000, + 0x84c, 0x00000000, + 0x850, 0x00000000, + 0x854, 0x00000000, + 0x858, 0x569a569a, + 0x85c, 0x001b25a4, + 0x860, 0x66e60230, + 0x864, 0x061f0130, + 0x868, 0x00000000, + 0x86c, 0x20202000, + 0x870, 0x03000300, + 0x874, 0x22004000, + 0x878, 0x00000808, + 0x87c, 0x00ffc3f1, + 0x880, 0xc0083070, + 0x884, 0x000004d5, + 0x888, 0x00000000, + 0x88c, 0xccc000c0, + 0x890, 0x00000800, + 0x894, 0xfffffffe, + 0x898, 0x40302010, + 0x89c, 0x00706050, + 0x900, 0x00000000, + 0x904, 0x00000023, + 0x908, 0x00000000, + 0x90c, 0x81121111, + 0xa00, 0x00d047c8, + 0xa04, 0x80ff000c, + 0xa08, 0x8c838300, + 0xa0c, 0x2e68120f, + 0xa10, 0x9500bb78, + 0xa14, 0x11144028, + 0xa18, 0x00881117, + 0xa1c, 0x89140f00, + 0xa20, 0x15160000, + 0xa24, 0x070b0f12, + 0xa28, 0x00000104, + 0xa2c, 0x00d30000, + 0xa70, 0x101fbf00, + 0xa74, 0x00000007, + 0xc00, 0x48071d40, + 0xc04, 0x03a05611, + 0xc08, 0x000000e4, + 0xc0c, 0x6c6c6c6c, + 0xc10, 0x08800000, + 0xc14, 0x40000100, + 0xc18, 0x08800000, + 0xc1c, 0x40000100, + 0xc20, 0x00000000, + 0xc24, 0x00000000, + 0xc28, 0x00000000, + 0xc2c, 0x00000000, + 0xc30, 0x69e9ac44, + 0xc34, 0x469652cf, + 0xc38, 0x49795994, + 0xc3c, 0x0a97971c, + 0xc40, 0x1f7c403f, + 0xc44, 0x000100b7, + 0xc48, 0xec020107, + 0xc4c, 0x007f037f, + 0xc50, 0x6954342e, + 0xc54, 0x43bc0094, + 0xc58, 0x6954342f, + 0xc5c, 0x433c0094, + 0xc60, 0x00000000, + 0xc64, 0x5116848b, + 0xc68, 0x47c00bff, + 0xc6c, 0x00000036, + 0xc70, 0x2c46000d, + 0xc74, 0x018610db, + 0xc78, 0x0000001f, + 0xc7c, 0x00b91612, + 0xc80, 0x24000090, + 0xc84, 0x20f60000, + 0xc88, 0x24000090, + 0xc8c, 0x20200000, + 0xc90, 0x00121820, + 0xc94, 0x00000000, + 0xc98, 0x00121820, + 0xc9c, 0x00007f7f, + 0xca0, 0x00000000, + 0xca4, 0x00000080, + 0xca8, 0x00000000, + 0xcac, 0x00000000, + 0xcb0, 0x00000000, + 0xcb4, 0x00000000, + 0xcb8, 0x00000000, + 0xcbc, 0x28000000, + 0xcc0, 0x00000000, + 0xcc4, 0x00000000, + 0xcc8, 0x00000000, + 0xccc, 0x00000000, + 0xcd0, 0x00000000, + 0xcd4, 0x00000000, + 0xcd8, 0x64b22427, + 0xcdc, 0x00766932, + 0xce0, 0x00222222, + 0xce4, 0x00000000, + 0xce8, 0x37644302, + 0xcec, 0x2f97d40c, + 0xd00, 0x00080740, + 0xd04, 0x00020401, + 0xd08, 0x0000907f, + 0xd0c, 0x20010201, + 0xd10, 0xa0633333, + 0xd14, 0x3333bc43, + 0xd18, 0x7a8f5b6b, + 0xd2c, 0xcc979975, + 0xd30, 0x00000000, + 0xd34, 0x80608000, + 0xd38, 0x00000000, + 0xd3c, 0x00027293, + 0xd40, 0x00000000, + 0xd44, 0x00000000, + 0xd48, 0x00000000, + 0xd4c, 0x00000000, + 0xd50, 0x6437140a, + 0xd54, 0x00000000, + 0xd58, 0x00000000, + 0xd5c, 0x30032064, + 0xd60, 0x4653de68, + 0xd64, 0x04518a3c, + 0xd68, 0x00002101, + 0xd6c, 0x2a201c16, + 0xd70, 0x1812362e, + 0xd74, 0x322c2220, + 0xd78, 0x000e3c24, + 0xe00, 0x24242424, + 0xe04, 0x24242424, + 0xe08, 0x03902024, + 0xe10, 0x24242424, + 0xe14, 0x24242424, + 0xe18, 0x24242424, + 0xe1c, 0x24242424, + 0xe28, 0x00000000, + 0xe30, 0x1000dc1f, + 0xe34, 0x10008c1f, + 0xe38, 0x02140102, + 0xe3c, 0x681604c2, + 0xe40, 0x01007c00, + 0xe44, 0x01004800, + 0xe48, 0xfb000000, + 0xe4c, 0x000028d1, + 0xe50, 0x1000dc1f, + 0xe54, 0x10008c1f, + 0xe58, 0x02140102, + 0xe5c, 0x28160d05, + 0xe60, 0x00000008, + 0xe68, 0x001b25a4, + 0xe6c, 0x631b25a0, + 0xe70, 0x631b25a0, + 0xe74, 0x081b25a0, + 0xe78, 0x081b25a0, + 0xe7c, 0x081b25a0, + 0xe80, 0x081b25a0, + 0xe84, 0x631b25a0, + 0xe88, 0x081b25a0, + 0xe8c, 0x631b25a0, + 0xed0, 0x631b25a0, + 0xed4, 0x631b25a0, + 0xed8, 0x631b25a0, + 0xedc, 0x001b25a0, + 0xee0, 0x001b25a0, + 0xeec, 0x6b1b25a0, + 0xee8, 0x31555448, + 0xf14, 0x00000003, + 0xf4c, 0x00000000, + 0xf00, 0x00000300, +}; + +u32 RTL8192CUPHY_REG_Array_PG_HP[RTL8192CUPHY_REG_Array_PG_HPLength] = { + 0xe00, 0xffffffff, 0x06080808, + 0xe04, 0xffffffff, 0x00040406, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x04060608, + 0xe14, 0xffffffff, 0x00020204, + 0xe18, 0xffffffff, 0x04060608, + 0xe1c, 0xffffffff, 0x00020204, + 0x830, 0xffffffff, 0x06080808, + 0x834, 0xffffffff, 0x00040406, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x04060608, + 0x848, 0xffffffff, 0x00020204, + 0x84c, 0xffffffff, 0x04060608, + 0x868, 0xffffffff, 0x00020204, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, +}; + +u32 RTL8192CURadioA_1T_HPArray[RTL8192CURadioA_1T_HPArrayLength] = { + 0x000, 0x00030159, + 0x001, 0x00031284, + 0x002, 0x00098000, + 0x003, 0x00018c63, + 0x004, 0x000210e7, + 0x009, 0x0002044f, + 0x00a, 0x0001adb0, + 0x00b, 0x00054867, + 0x00c, 0x0008992e, + 0x00d, 0x0000e529, + 0x00e, 0x00039ce7, + 0x00f, 0x00000451, + 0x019, 0x00000000, + 0x01a, 0x00000255, + 0x01b, 0x00060a00, + 0x01c, 0x000fc378, + 0x01d, 0x000a1250, + 0x01e, 0x0004445f, + 0x01f, 0x00080001, + 0x020, 0x0000b614, + 0x021, 0x0006c000, + 0x022, 0x0000083c, + 0x023, 0x00001558, + 0x024, 0x00000060, + 0x025, 0x00000483, + 0x026, 0x0004f000, + 0x027, 0x000ec7d9, + 0x028, 0x000977c0, + 0x029, 0x00004783, + 0x02a, 0x00000001, + 0x02b, 0x00021334, + 0x02a, 0x00000000, + 0x02b, 0x00000054, + 0x02a, 0x00000001, + 0x02b, 0x00000808, + 0x02b, 0x00053333, + 0x02c, 0x0000000c, + 0x02a, 0x00000002, + 0x02b, 0x00000808, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000003, + 0x02b, 0x00000808, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x00000004, + 0x02b, 0x00000808, + 0x02b, 0x0006b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000005, + 0x02b, 0x00000808, + 0x02b, 0x00073333, + 0x02c, 0x0000000d, + 0x02a, 0x00000006, + 0x02b, 0x00000709, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000007, + 0x02b, 0x00000709, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x00000008, + 0x02b, 0x0000060a, + 0x02b, 0x0004b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000009, + 0x02b, 0x0000060a, + 0x02b, 0x00053333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000a, + 0x02b, 0x0000060a, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000b, + 0x02b, 0x0000060a, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000c, + 0x02b, 0x0000060a, + 0x02b, 0x0006b333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000d, + 0x02b, 0x0000060a, + 0x02b, 0x00073333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000e, + 0x02b, 0x0000050b, + 0x02b, 0x00066666, + 0x02c, 0x0000001a, + 0x02a, 0x000e0000, + 0x010, 0x0004000f, + 0x011, 0x000e31fc, + 0x010, 0x0006000f, + 0x011, 0x000ff9f8, + 0x010, 0x0002000f, + 0x011, 0x000203f9, + 0x010, 0x0003000f, + 0x011, 0x000ff500, + 0x010, 0x00000000, + 0x011, 0x00000000, + 0x010, 0x0008000f, + 0x011, 0x0003f100, + 0x010, 0x0009000f, + 0x011, 0x00023100, + 0x012, 0x000d8000, + 0x012, 0x00090000, + 0x012, 0x00051000, + 0x012, 0x00012000, + 0x013, 0x00028fb4, + 0x013, 0x00024fa8, + 0x013, 0x000207a4, + 0x013, 0x0001c798, + 0x013, 0x000183a4, + 0x013, 0x00014398, + 0x013, 0x000101a4, + 0x013, 0x0000c198, + 0x013, 0x000080a4, + 0x013, 0x00004098, + 0x013, 0x00000000, + 0x014, 0x0001944c, + 0x014, 0x00059444, + 0x014, 0x0009944c, + 0x014, 0x000d9444, + 0x015, 0x0000f405, + 0x015, 0x0004f405, + 0x015, 0x0008f405, + 0x015, 0x000cf405, + 0x016, 0x000e0330, + 0x016, 0x000a0330, + 0x016, 0x00060330, + 0x016, 0x00020330, + 0x000, 0x00010159, + 0x018, 0x0000f401, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x01f, 0x00080003, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x01e, 0x00044457, + 0x01f, 0x00080000, + 0x000, 0x00030159, +}; + +u32 Rtl8192CUAGCTAB_1T_HPArray[RTL8192CUAGCTAB_1T_HPArrayLength] = { + 0xc78, 0x7b000001, + 0xc78, 0x7b010001, + 0xc78, 0x7b020001, + 0xc78, 0x7b030001, + 0xc78, 0x7b040001, + 0xc78, 0x7b050001, + 0xc78, 0x7b060001, + 0xc78, 0x7b070001, + 0xc78, 0x7b080001, + 0xc78, 0x7a090001, + 0xc78, 0x790a0001, + 0xc78, 0x780b0001, + 0xc78, 0x770c0001, + 0xc78, 0x760d0001, + 0xc78, 0x750e0001, + 0xc78, 0x740f0001, + 0xc78, 0x73100001, + 0xc78, 0x72110001, + 0xc78, 0x71120001, + 0xc78, 0x70130001, + 0xc78, 0x6f140001, + 0xc78, 0x6e150001, + 0xc78, 0x6d160001, + 0xc78, 0x6c170001, + 0xc78, 0x6b180001, + 0xc78, 0x6a190001, + 0xc78, 0x691a0001, + 0xc78, 0x681b0001, + 0xc78, 0x671c0001, + 0xc78, 0x661d0001, + 0xc78, 0x651e0001, + 0xc78, 0x641f0001, + 0xc78, 0x63200001, + 0xc78, 0x62210001, + 0xc78, 0x61220001, + 0xc78, 0x60230001, + 0xc78, 0x46240001, + 0xc78, 0x45250001, + 0xc78, 0x44260001, + 0xc78, 0x43270001, + 0xc78, 0x42280001, + 0xc78, 0x41290001, + 0xc78, 0x402a0001, + 0xc78, 0x262b0001, + 0xc78, 0x252c0001, + 0xc78, 0x242d0001, + 0xc78, 0x232e0001, + 0xc78, 0x222f0001, + 0xc78, 0x21300001, + 0xc78, 0x20310001, + 0xc78, 0x06320001, + 0xc78, 0x05330001, + 0xc78, 0x04340001, + 0xc78, 0x03350001, + 0xc78, 0x02360001, + 0xc78, 0x01370001, + 0xc78, 0x00380001, + 0xc78, 0x00390001, + 0xc78, 0x003a0001, + 0xc78, 0x003b0001, + 0xc78, 0x003c0001, + 0xc78, 0x003d0001, + 0xc78, 0x003e0001, + 0xc78, 0x003f0001, + 0xc78, 0x7b400001, + 0xc78, 0x7b410001, + 0xc78, 0x7b420001, + 0xc78, 0x7b430001, + 0xc78, 0x7b440001, + 0xc78, 0x7b450001, + 0xc78, 0x7b460001, + 0xc78, 0x7b470001, + 0xc78, 0x7b480001, + 0xc78, 0x7a490001, + 0xc78, 0x794a0001, + 0xc78, 0x784b0001, + 0xc78, 0x774c0001, + 0xc78, 0x764d0001, + 0xc78, 0x754e0001, + 0xc78, 0x744f0001, + 0xc78, 0x73500001, + 0xc78, 0x72510001, + 0xc78, 0x71520001, + 0xc78, 0x70530001, + 0xc78, 0x6f540001, + 0xc78, 0x6e550001, + 0xc78, 0x6d560001, + 0xc78, 0x6c570001, + 0xc78, 0x6b580001, + 0xc78, 0x6a590001, + 0xc78, 0x695a0001, + 0xc78, 0x685b0001, + 0xc78, 0x675c0001, + 0xc78, 0x665d0001, + 0xc78, 0x655e0001, + 0xc78, 0x645f0001, + 0xc78, 0x63600001, + 0xc78, 0x62610001, + 0xc78, 0x61620001, + 0xc78, 0x60630001, + 0xc78, 0x46640001, + 0xc78, 0x45650001, + 0xc78, 0x44660001, + 0xc78, 0x43670001, + 0xc78, 0x42680001, + 0xc78, 0x41690001, + 0xc78, 0x406a0001, + 0xc78, 0x266b0001, + 0xc78, 0x256c0001, + 0xc78, 0x246d0001, + 0xc78, 0x236e0001, + 0xc78, 0x226f0001, + 0xc78, 0x21700001, + 0xc78, 0x20710001, + 0xc78, 0x06720001, + 0xc78, 0x05730001, + 0xc78, 0x04740001, + 0xc78, 0x03750001, + 0xc78, 0x02760001, + 0xc78, 0x01770001, + 0xc78, 0x00780001, + 0xc78, 0x00790001, + 0xc78, 0x007a0001, + 0xc78, 0x007b0001, + 0xc78, 0x007c0001, + 0xc78, 0x007d0001, + 0xc78, 0x007e0001, + 0xc78, 0x007f0001, + 0xc78, 0x3800001e, + 0xc78, 0x3801001e, + 0xc78, 0x3802001e, + 0xc78, 0x3803001e, + 0xc78, 0x3804001e, + 0xc78, 0x3805001e, + 0xc78, 0x3806001e, + 0xc78, 0x3807001e, + 0xc78, 0x3808001e, + 0xc78, 0x3c09001e, + 0xc78, 0x3e0a001e, + 0xc78, 0x400b001e, + 0xc78, 0x440c001e, + 0xc78, 0x480d001e, + 0xc78, 0x4c0e001e, + 0xc78, 0x500f001e, + 0xc78, 0x5210001e, + 0xc78, 0x5611001e, + 0xc78, 0x5a12001e, + 0xc78, 0x5e13001e, + 0xc78, 0x6014001e, + 0xc78, 0x6015001e, + 0xc78, 0x6016001e, + 0xc78, 0x6217001e, + 0xc78, 0x6218001e, + 0xc78, 0x6219001e, + 0xc78, 0x621a001e, + 0xc78, 0x621b001e, + 0xc78, 0x621c001e, + 0xc78, 0x621d001e, + 0xc78, 0x621e001e, + 0xc78, 0x621f001e, +}; diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/table.h b/drivers/net/wireless/rtlwifi/rtl8192cu/table.h new file mode 100644 index 0000000..4b020e9 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/table.h @@ -0,0 +1,71 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CU_TABLE__H_ +#define __RTL92CU_TABLE__H_ + +#include + +#define RTL8192CUPHY_REG_2TARRAY_LENGTH 374 +extern u32 RTL8192CUPHY_REG_2TARRAY[RTL8192CUPHY_REG_2TARRAY_LENGTH]; +#define RTL8192CUPHY_REG_1TARRAY_LENGTH 374 +extern u32 RTL8192CUPHY_REG_1TARRAY[RTL8192CUPHY_REG_1TARRAY_LENGTH]; + +#define RTL8192CUPHY_REG_ARRAY_PGLENGTH 336 +extern u32 RTL8192CUPHY_REG_ARRAY_PG[RTL8192CUPHY_REG_ARRAY_PGLENGTH]; + +#define RTL8192CURADIOA_2TARRAYLENGTH 282 +extern u32 RTL8192CURADIOA_2TARRAY[RTL8192CURADIOA_2TARRAYLENGTH]; +#define RTL8192CURADIOB_2TARRAYLENGTH 78 +extern u32 RTL8192CU_RADIOB_2TARRAY[RTL8192CURADIOB_2TARRAYLENGTH]; +#define RTL8192CURADIOA_1TARRAYLENGTH 282 +extern u32 RTL8192CU_RADIOA_1TARRAY[RTL8192CURADIOA_1TARRAYLENGTH]; +#define RTL8192CURADIOB_1TARRAYLENGTH 1 +extern u32 RTL8192CU_RADIOB_1TARRAY[RTL8192CURADIOB_1TARRAYLENGTH]; + +#define RTL8192CUMAC_2T_ARRAYLENGTH 172 +extern u32 RTL8192CUMAC_2T_ARRAY[RTL8192CUMAC_2T_ARRAYLENGTH]; + +#define RTL8192CUAGCTAB_2TARRAYLENGTH 320 +extern u32 RTL8192CUAGCTAB_2TARRAY[RTL8192CUAGCTAB_2TARRAYLENGTH]; +#define RTL8192CUAGCTAB_1TARRAYLENGTH 320 +extern u32 RTL8192CUAGCTAB_1TARRAY[RTL8192CUAGCTAB_1TARRAYLENGTH]; + +#define RTL8192CUPHY_REG_1T_HPArrayLength 378 +extern u32 RTL8192CUPHY_REG_1T_HPArray[RTL8192CUPHY_REG_1T_HPArrayLength]; + +#define RTL8192CUPHY_REG_Array_PG_HPLength 336 +extern u32 RTL8192CUPHY_REG_Array_PG_HP[RTL8192CUPHY_REG_Array_PG_HPLength]; + +#define RTL8192CURadioA_1T_HPArrayLength 282 +extern u32 RTL8192CURadioA_1T_HPArray[RTL8192CURadioA_1T_HPArrayLength]; +#define RTL8192CUAGCTAB_1T_HPArrayLength 320 +extern u32 Rtl8192CUAGCTAB_1T_HPArray[RTL8192CUAGCTAB_1T_HPArrayLength]; + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/trx.c b/drivers/net/wireless/rtlwifi/rtl8192cu/trx.c new file mode 100644 index 0000000..5a060e5 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/trx.c @@ -0,0 +1,689 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../usb.h" +#include "../ps.h" +#include "../base.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "rf.h" +#include "dm.h" +#include "mac.h" +#include "trx.h" + +static int _ConfigVerTOutEP(struct ieee80211_hw *hw) +{ + u8 ep_cfg, txqsele; + u8 ep_nums = 0; + + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_usb_priv *usb_priv = rtl_usbpriv(hw); + struct rtl_usb *rtlusb = rtl_usbdev(usb_priv); + + rtlusb->out_queue_sel = 0; + ep_cfg = rtl_read_byte(rtlpriv, REG_TEST_SIE_OPTIONAL); + ep_cfg = (ep_cfg & USB_TEST_EP_MASK) >> USB_TEST_EP_SHIFT; + switch (ep_cfg) { + case 0: /* 2 bulk OUT, 1 bulk IN */ + case 3: + rtlusb->out_queue_sel = TX_SELE_HQ | TX_SELE_LQ; + ep_nums = 2; + break; + case 1: /* 1 bulk IN/OUT => map all endpoint to Low queue */ + case 2: /* 1 bulk IN, 1 bulk OUT => map all endpoint to High queue */ + txqsele = rtl_read_byte(rtlpriv, REG_TEST_USB_TXQS); + if (txqsele & 0x0F) /* /map all endpoint to High queue */ + rtlusb->out_queue_sel = TX_SELE_HQ; + else if (txqsele&0xF0) /* map all endpoint to Low queue */ + rtlusb->out_queue_sel = TX_SELE_LQ; + ep_nums = 1; + break; + default: + break; + } + return (rtlusb->out_ep_nums == ep_nums) ? 0 : -EINVAL; +} + +static int _ConfigVerNOutEP(struct ieee80211_hw *hw) +{ + u8 ep_cfg; + u8 ep_nums = 0; + + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_usb_priv *usb_priv = rtl_usbpriv(hw); + struct rtl_usb *rtlusb = rtl_usbdev(usb_priv); + + rtlusb->out_queue_sel = 0; + /* Normal and High queue */ + ep_cfg = rtl_read_byte(rtlpriv, (REG_NORMAL_SIE_EP + 1)); + if (ep_cfg & USB_NORMAL_SIE_EP_MASK) { + rtlusb->out_queue_sel |= TX_SELE_HQ; + ep_nums++; + } + if ((ep_cfg >> USB_NORMAL_SIE_EP_SHIFT) & USB_NORMAL_SIE_EP_MASK) { + rtlusb->out_queue_sel |= TX_SELE_NQ; + ep_nums++; + } + /* Low queue */ + ep_cfg = rtl_read_byte(rtlpriv, (REG_NORMAL_SIE_EP + 2)); + if (ep_cfg & USB_NORMAL_SIE_EP_MASK) { + rtlusb->out_queue_sel |= TX_SELE_LQ; + ep_nums++; + } + return (rtlusb->out_ep_nums == ep_nums) ? 0 : -EINVAL; +} + +static void _TwoOutEpMapping(struct ieee80211_hw *hw, bool bIsChipB, + bool bwificfg, struct rtl_ep_map *ep_map) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (bwificfg) { /* for WMM */ + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "USB Chip-B & WMM Setting.....\n"); + ep_map->ep_mapping[RTL_TXQ_BE] = 2; + ep_map->ep_mapping[RTL_TXQ_BK] = 3; + ep_map->ep_mapping[RTL_TXQ_VI] = 3; + ep_map->ep_mapping[RTL_TXQ_VO] = 2; + ep_map->ep_mapping[RTL_TXQ_MGT] = 2; + ep_map->ep_mapping[RTL_TXQ_BCN] = 2; + ep_map->ep_mapping[RTL_TXQ_HI] = 2; + } else { /* typical setting */ + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "USB typical Setting.....\n"); + ep_map->ep_mapping[RTL_TXQ_BE] = 3; + ep_map->ep_mapping[RTL_TXQ_BK] = 3; + ep_map->ep_mapping[RTL_TXQ_VI] = 2; + ep_map->ep_mapping[RTL_TXQ_VO] = 2; + ep_map->ep_mapping[RTL_TXQ_MGT] = 2; + ep_map->ep_mapping[RTL_TXQ_BCN] = 2; + ep_map->ep_mapping[RTL_TXQ_HI] = 2; + } +} + +static void _ThreeOutEpMapping(struct ieee80211_hw *hw, bool bwificfg, + struct rtl_ep_map *ep_map) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + if (bwificfg) { /* for WMM */ + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "USB 3EP Setting for WMM.....\n"); + ep_map->ep_mapping[RTL_TXQ_BE] = 5; + ep_map->ep_mapping[RTL_TXQ_BK] = 3; + ep_map->ep_mapping[RTL_TXQ_VI] = 3; + ep_map->ep_mapping[RTL_TXQ_VO] = 2; + ep_map->ep_mapping[RTL_TXQ_MGT] = 2; + ep_map->ep_mapping[RTL_TXQ_BCN] = 2; + ep_map->ep_mapping[RTL_TXQ_HI] = 2; + } else { /* typical setting */ + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "USB 3EP Setting for typical.....\n"); + ep_map->ep_mapping[RTL_TXQ_BE] = 5; + ep_map->ep_mapping[RTL_TXQ_BK] = 5; + ep_map->ep_mapping[RTL_TXQ_VI] = 3; + ep_map->ep_mapping[RTL_TXQ_VO] = 2; + ep_map->ep_mapping[RTL_TXQ_MGT] = 2; + ep_map->ep_mapping[RTL_TXQ_BCN] = 2; + ep_map->ep_mapping[RTL_TXQ_HI] = 2; + } +} + +static void _OneOutEpMapping(struct ieee80211_hw *hw, struct rtl_ep_map *ep_map) +{ + ep_map->ep_mapping[RTL_TXQ_BE] = 2; + ep_map->ep_mapping[RTL_TXQ_BK] = 2; + ep_map->ep_mapping[RTL_TXQ_VI] = 2; + ep_map->ep_mapping[RTL_TXQ_VO] = 2; + ep_map->ep_mapping[RTL_TXQ_MGT] = 2; + ep_map->ep_mapping[RTL_TXQ_BCN] = 2; + ep_map->ep_mapping[RTL_TXQ_HI] = 2; +} +static int _out_ep_mapping(struct ieee80211_hw *hw) +{ + int err = 0; + bool bIsChipN, bwificfg = false; + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_usb_priv *usb_priv = rtl_usbpriv(hw); + struct rtl_usb *rtlusb = rtl_usbdev(usb_priv); + struct rtl_ep_map *ep_map = &(rtlusb->ep_map); + + bIsChipN = IS_NORMAL_CHIP(rtlhal->version); + switch (rtlusb->out_ep_nums) { + case 2: + _TwoOutEpMapping(hw, bIsChipN, bwificfg, ep_map); + break; + case 3: + /* Test chip doesn't support three out EPs. */ + if (!bIsChipN) { + err = -EINVAL; + goto err_out; + } + _ThreeOutEpMapping(hw, bIsChipN, ep_map); + break; + case 1: + _OneOutEpMapping(hw, ep_map); + break; + default: + err = -EINVAL; + break; + } +err_out: + return err; + +} +/* endpoint mapping */ +int rtl8192cu_endpoint_mapping(struct ieee80211_hw *hw) +{ + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + int error = 0; + if (likely(IS_NORMAL_CHIP(rtlhal->version))) + error = _ConfigVerNOutEP(hw); + else + error = _ConfigVerTOutEP(hw); + if (error) + goto err_out; + error = _out_ep_mapping(hw); + if (error) + goto err_out; +err_out: + return error; +} + +u16 rtl8192cu_mq_to_hwq(__le16 fc, u16 mac80211_queue_index) +{ + u16 hw_queue_index; + + if (unlikely(ieee80211_is_beacon(fc))) { + hw_queue_index = RTL_TXQ_BCN; + goto out; + } + if (ieee80211_is_mgmt(fc)) { + hw_queue_index = RTL_TXQ_MGT; + goto out; + } + switch (mac80211_queue_index) { + case 0: + hw_queue_index = RTL_TXQ_VO; + break; + case 1: + hw_queue_index = RTL_TXQ_VI; + break; + case 2: + hw_queue_index = RTL_TXQ_BE; + break; + case 3: + hw_queue_index = RTL_TXQ_BK; + break; + default: + hw_queue_index = RTL_TXQ_BE; + RT_ASSERT(false, "QSLT_BE queue, skb_queue:%d\n", + mac80211_queue_index); + break; + } +out: + return hw_queue_index; +} + +static enum rtl_desc_qsel _rtl8192cu_mq_to_descq(struct ieee80211_hw *hw, + __le16 fc, u16 mac80211_queue_index) +{ + enum rtl_desc_qsel qsel; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (unlikely(ieee80211_is_beacon(fc))) { + qsel = QSLT_BEACON; + goto out; + } + if (ieee80211_is_mgmt(fc)) { + qsel = QSLT_MGNT; + goto out; + } + switch (mac80211_queue_index) { + case 0: /* VO */ + qsel = QSLT_VO; + RT_TRACE(rtlpriv, COMP_USB, DBG_DMESG, + "VO queue, set qsel = 0x%x\n", QSLT_VO); + break; + case 1: /* VI */ + qsel = QSLT_VI; + RT_TRACE(rtlpriv, COMP_USB, DBG_DMESG, + "VI queue, set qsel = 0x%x\n", QSLT_VI); + break; + case 3: /* BK */ + qsel = QSLT_BK; + RT_TRACE(rtlpriv, COMP_USB, DBG_DMESG, + "BK queue, set qsel = 0x%x\n", QSLT_BK); + break; + case 2: /* BE */ + default: + qsel = QSLT_BE; + RT_TRACE(rtlpriv, COMP_USB, DBG_DMESG, + "BE queue, set qsel = 0x%x\n", QSLT_BE); + break; + } +out: + return qsel; +} + +/* =============================================================== */ + +/*---------------------------------------------------------------------- + * + * Rx handler + * + *---------------------------------------------------------------------- */ +bool rtl92cu_rx_query_desc(struct ieee80211_hw *hw, + struct rtl_stats *stats, + struct ieee80211_rx_status *rx_status, + u8 *p_desc, struct sk_buff *skb) +{ + struct rx_fwinfo_92c *p_drvinfo; + struct rx_desc_92c *pdesc = (struct rx_desc_92c *)p_desc; + u32 phystatus = GET_RX_DESC_PHY_STATUS(pdesc); + + stats->length = (u16) GET_RX_DESC_PKT_LEN(pdesc); + stats->rx_drvinfo_size = (u8)GET_RX_DESC_DRVINFO_SIZE(pdesc) * + RX_DRV_INFO_SIZE_UNIT; + stats->rx_bufshift = (u8) (GET_RX_DESC_SHIFT(pdesc) & 0x03); + stats->icv = (u16) GET_RX_DESC_ICV(pdesc); + stats->crc = (u16) GET_RX_DESC_CRC32(pdesc); + stats->hwerror = (stats->crc | stats->icv); + stats->decrypted = !GET_RX_DESC_SWDEC(pdesc); + stats->rate = (u8) GET_RX_DESC_RX_MCS(pdesc); + stats->shortpreamble = (u16) GET_RX_DESC_SPLCP(pdesc); + stats->isampdu = (bool) (GET_RX_DESC_PAGGR(pdesc) == 1); + stats->isampdu = (bool) ((GET_RX_DESC_PAGGR(pdesc) == 1) + && (GET_RX_DESC_FAGGR(pdesc) == 1)); + stats->timestamp_low = GET_RX_DESC_TSFL(pdesc); + stats->rx_is40Mhzpacket = (bool) GET_RX_DESC_BW(pdesc); + rx_status->freq = hw->conf.chandef.chan->center_freq; + rx_status->band = hw->conf.chandef.chan->band; + if (GET_RX_DESC_CRC32(pdesc)) + rx_status->flag |= RX_FLAG_FAILED_FCS_CRC; + if (!GET_RX_DESC_SWDEC(pdesc)) + rx_status->flag |= RX_FLAG_DECRYPTED; + if (GET_RX_DESC_BW(pdesc)) + rx_status->flag |= RX_FLAG_40MHZ; + if (GET_RX_DESC_RX_HT(pdesc)) + rx_status->flag |= RX_FLAG_HT; + rx_status->flag |= RX_FLAG_MACTIME_START; + if (stats->decrypted) + rx_status->flag |= RX_FLAG_DECRYPTED; + rx_status->rate_idx = rtlwifi_rate_mapping(hw, + (bool)GET_RX_DESC_RX_HT(pdesc), + (u8)GET_RX_DESC_RX_MCS(pdesc), + (bool)GET_RX_DESC_PAGGR(pdesc)); + rx_status->mactime = GET_RX_DESC_TSFL(pdesc); + if (phystatus) { + p_drvinfo = (struct rx_fwinfo_92c *)(skb->data + + stats->rx_bufshift); + rtl92c_translate_rx_signal_stuff(hw, skb, stats, pdesc, + p_drvinfo); + } + /*rx_status->qual = stats->signal; */ + rx_status->signal = stats->rssi + 10; + /*rx_status->noise = -stats->noise; */ + return true; +} + +#define RTL_RX_DRV_INFO_UNIT 8 + +static void _rtl_rx_process(struct ieee80211_hw *hw, struct sk_buff *skb) +{ + struct ieee80211_rx_status *rx_status = + (struct ieee80211_rx_status *)IEEE80211_SKB_RXCB(skb); + u32 skb_len, pkt_len, drvinfo_len; + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 *rxdesc; + struct rtl_stats stats = { + .signal = 0, + .noise = -98, + .rate = 0, + }; + struct rx_fwinfo_92c *p_drvinfo; + bool bv; + __le16 fc; + struct ieee80211_hdr *hdr; + + memset(rx_status, 0, sizeof(*rx_status)); + rxdesc = skb->data; + skb_len = skb->len; + drvinfo_len = (GET_RX_DESC_DRVINFO_SIZE(rxdesc) * RTL_RX_DRV_INFO_UNIT); + pkt_len = GET_RX_DESC_PKT_LEN(rxdesc); + /* TODO: Error recovery. drop this skb or something. */ + WARN_ON(skb_len < (pkt_len + RTL_RX_DESC_SIZE + drvinfo_len)); + stats.length = (u16) GET_RX_DESC_PKT_LEN(rxdesc); + stats.rx_drvinfo_size = (u8)GET_RX_DESC_DRVINFO_SIZE(rxdesc) * + RX_DRV_INFO_SIZE_UNIT; + stats.rx_bufshift = (u8) (GET_RX_DESC_SHIFT(rxdesc) & 0x03); + stats.icv = (u16) GET_RX_DESC_ICV(rxdesc); + stats.crc = (u16) GET_RX_DESC_CRC32(rxdesc); + stats.hwerror = (stats.crc | stats.icv); + stats.decrypted = !GET_RX_DESC_SWDEC(rxdesc); + stats.rate = (u8) GET_RX_DESC_RX_MCS(rxdesc); + stats.shortpreamble = (u16) GET_RX_DESC_SPLCP(rxdesc); + stats.isampdu = (bool) ((GET_RX_DESC_PAGGR(rxdesc) == 1) + && (GET_RX_DESC_FAGGR(rxdesc) == 1)); + stats.timestamp_low = GET_RX_DESC_TSFL(rxdesc); + stats.rx_is40Mhzpacket = (bool) GET_RX_DESC_BW(rxdesc); + /* TODO: is center_freq changed when doing scan? */ + /* TODO: Shall we add protection or just skip those two step? */ + rx_status->freq = hw->conf.chandef.chan->center_freq; + rx_status->band = hw->conf.chandef.chan->band; + if (GET_RX_DESC_CRC32(rxdesc)) + rx_status->flag |= RX_FLAG_FAILED_FCS_CRC; + if (!GET_RX_DESC_SWDEC(rxdesc)) + rx_status->flag |= RX_FLAG_DECRYPTED; + if (GET_RX_DESC_BW(rxdesc)) + rx_status->flag |= RX_FLAG_40MHZ; + if (GET_RX_DESC_RX_HT(rxdesc)) + rx_status->flag |= RX_FLAG_HT; + /* Data rate */ + rx_status->rate_idx = rtlwifi_rate_mapping(hw, + (bool)GET_RX_DESC_RX_HT(rxdesc), + (u8)GET_RX_DESC_RX_MCS(rxdesc), + (bool)GET_RX_DESC_PAGGR(rxdesc)); + /* There is a phy status after this rx descriptor. */ + if (GET_RX_DESC_PHY_STATUS(rxdesc)) { + p_drvinfo = (struct rx_fwinfo_92c *)(rxdesc + RTL_RX_DESC_SIZE); + rtl92c_translate_rx_signal_stuff(hw, skb, &stats, + (struct rx_desc_92c *)rxdesc, p_drvinfo); + } + skb_pull(skb, (drvinfo_len + RTL_RX_DESC_SIZE)); + hdr = (struct ieee80211_hdr *)(skb->data); + fc = hdr->frame_control; + bv = ieee80211_is_probe_resp(fc); + if (bv) + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "Got probe response frame\n"); + if (ieee80211_is_beacon(fc)) + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "Got beacon frame\n"); + if (ieee80211_is_data(fc)) + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "Got data frame\n"); + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "Fram: fc = 0x%X addr1 = 0x%02X:0x%02X:0x%02X:0x%02X:0x%02X:0x%02X\n", + fc, + (u32)hdr->addr1[0], (u32)hdr->addr1[1], + (u32)hdr->addr1[2], (u32)hdr->addr1[3], + (u32)hdr->addr1[4], (u32)hdr->addr1[5]); + memcpy(IEEE80211_SKB_RXCB(skb), rx_status, sizeof(*rx_status)); + ieee80211_rx(hw, skb); +} + +void rtl8192cu_rx_hdl(struct ieee80211_hw *hw, struct sk_buff * skb) +{ + _rtl_rx_process(hw, skb); +} + +void rtl8192c_rx_segregate_hdl( + struct ieee80211_hw *hw, + struct sk_buff *skb, + struct sk_buff_head *skb_list) +{ +} + +/*---------------------------------------------------------------------- + * + * Tx handler + * + *---------------------------------------------------------------------- */ +void rtl8192c_tx_cleanup(struct ieee80211_hw *hw, struct sk_buff *skb) +{ +} + +int rtl8192c_tx_post_hdl(struct ieee80211_hw *hw, struct urb *urb, + struct sk_buff *skb) +{ + return 0; +} + +struct sk_buff *rtl8192c_tx_aggregate_hdl(struct ieee80211_hw *hw, + struct sk_buff_head *list) +{ + return skb_dequeue(list); +} + +/*======================================== trx ===============================*/ + +static void _rtl_fill_usb_tx_desc(u8 *txdesc) +{ + SET_TX_DESC_OWN(txdesc, 1); + SET_TX_DESC_LAST_SEG(txdesc, 1); + SET_TX_DESC_FIRST_SEG(txdesc, 1); +} +/** + * For HW recovery information + */ +static void _rtl_tx_desc_checksum(u8 *txdesc) +{ + u16 *ptr = (u16 *)txdesc; + u16 checksum = 0; + u32 index; + + /* Clear first */ + SET_TX_DESC_TX_DESC_CHECKSUM(txdesc, 0); + for (index = 0; index < 16; index++) + checksum = checksum ^ (*(ptr + index)); + SET_TX_DESC_TX_DESC_CHECKSUM(txdesc, checksum); +} + +void rtl92cu_tx_fill_desc(struct ieee80211_hw *hw, + struct ieee80211_hdr *hdr, u8 *pdesc_tx, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff *skb, + u8 queue_index, + struct rtl_tcb_desc *tcb_desc) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + bool defaultadapter = true; + u8 *qc = ieee80211_get_qos_ctl(hdr); + u8 tid = qc[0] & IEEE80211_QOS_CTL_TID_MASK; + u16 seq_number; + __le16 fc = hdr->frame_control; + u8 rate_flag = info->control.rates[0].flags; + u16 pktlen = skb->len; + enum rtl_desc_qsel fw_qsel = _rtl8192cu_mq_to_descq(hw, fc, + skb_get_queue_mapping(skb)); + u8 *txdesc; + + seq_number = (le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_SEQ) >> 4; + rtl_get_tcb_desc(hw, info, sta, skb, tcb_desc); + txdesc = (u8 *)skb_push(skb, RTL_TX_HEADER_SIZE); + memset(txdesc, 0, RTL_TX_HEADER_SIZE); + SET_TX_DESC_PKT_SIZE(txdesc, pktlen); + SET_TX_DESC_LINIP(txdesc, 0); + SET_TX_DESC_PKT_OFFSET(txdesc, RTL_DUMMY_OFFSET); + SET_TX_DESC_OFFSET(txdesc, RTL_TX_HEADER_SIZE); + SET_TX_DESC_TX_RATE(txdesc, tcb_desc->hw_rate); + if (tcb_desc->use_shortgi || tcb_desc->use_shortpreamble) + SET_TX_DESC_DATA_SHORTGI(txdesc, 1); + if (mac->tids[tid].agg.agg_state == RTL_AGG_ON && + info->flags & IEEE80211_TX_CTL_AMPDU) { + SET_TX_DESC_AGG_ENABLE(txdesc, 1); + SET_TX_DESC_MAX_AGG_NUM(txdesc, 0x14); + } else { + SET_TX_DESC_AGG_BREAK(txdesc, 1); + } + SET_TX_DESC_SEQ(txdesc, seq_number); + SET_TX_DESC_RTS_ENABLE(txdesc, ((tcb_desc->rts_enable && + !tcb_desc->cts_enable) ? 1 : 0)); + SET_TX_DESC_HW_RTS_ENABLE(txdesc, ((tcb_desc->rts_enable || + tcb_desc->cts_enable) ? 1 : 0)); + SET_TX_DESC_CTS2SELF(txdesc, ((tcb_desc->cts_enable) ? 1 : 0)); + SET_TX_DESC_RTS_STBC(txdesc, ((tcb_desc->rts_stbc) ? 1 : 0)); + SET_TX_DESC_RTS_RATE(txdesc, tcb_desc->rts_rate); + SET_TX_DESC_RTS_BW(txdesc, 0); + SET_TX_DESC_RTS_SC(txdesc, tcb_desc->rts_sc); + SET_TX_DESC_RTS_SHORT(txdesc, + ((tcb_desc->rts_rate <= DESC92_RATE54M) ? + (tcb_desc->rts_use_shortpreamble ? 1 : 0) + : (tcb_desc->rts_use_shortgi ? 1 : 0))); + if (mac->bw_40) { + if (rate_flag & IEEE80211_TX_RC_DUP_DATA) { + SET_TX_DESC_DATA_BW(txdesc, 1); + SET_TX_DESC_DATA_SC(txdesc, 3); + } else if(rate_flag & IEEE80211_TX_RC_40_MHZ_WIDTH){ + SET_TX_DESC_DATA_BW(txdesc, 1); + SET_TX_DESC_DATA_SC(txdesc, mac->cur_40_prime_sc); + } else { + SET_TX_DESC_DATA_BW(txdesc, 0); + SET_TX_DESC_DATA_SC(txdesc, 0); + } + } else { + SET_TX_DESC_DATA_BW(txdesc, 0); + SET_TX_DESC_DATA_SC(txdesc, 0); + } + rcu_read_lock(); + sta = ieee80211_find_sta(mac->vif, mac->bssid); + if (sta) { + u8 ampdu_density = sta->ht_cap.ampdu_density; + SET_TX_DESC_AMPDU_DENSITY(txdesc, ampdu_density); + } + rcu_read_unlock(); + if (info->control.hw_key) { + struct ieee80211_key_conf *keyconf = info->control.hw_key; + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + case WLAN_CIPHER_SUITE_TKIP: + SET_TX_DESC_SEC_TYPE(txdesc, 0x1); + break; + case WLAN_CIPHER_SUITE_CCMP: + SET_TX_DESC_SEC_TYPE(txdesc, 0x3); + break; + default: + SET_TX_DESC_SEC_TYPE(txdesc, 0x0); + break; + } + } + SET_TX_DESC_PKT_ID(txdesc, 0); + SET_TX_DESC_QUEUE_SEL(txdesc, fw_qsel); + SET_TX_DESC_DATA_RATE_FB_LIMIT(txdesc, 0x1F); + SET_TX_DESC_RTS_RATE_FB_LIMIT(txdesc, 0xF); + SET_TX_DESC_DISABLE_FB(txdesc, 0); + SET_TX_DESC_USE_RATE(txdesc, tcb_desc->use_driver_rate ? 1 : 0); + if (ieee80211_is_data_qos(fc)) { + if (mac->rdg_en) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "Enable RDG function\n"); + SET_TX_DESC_RDG_ENABLE(txdesc, 1); + SET_TX_DESC_HTC(txdesc, 1); + } + } + if (rtlpriv->dm.useramask) { + SET_TX_DESC_RATE_ID(txdesc, tcb_desc->ratr_index); + SET_TX_DESC_MACID(txdesc, tcb_desc->mac_id); + } else { + SET_TX_DESC_RATE_ID(txdesc, 0xC + tcb_desc->ratr_index); + SET_TX_DESC_MACID(txdesc, tcb_desc->ratr_index); + } + if ((!ieee80211_is_data_qos(fc)) && ppsc->leisure_ps && + ppsc->fwctrl_lps) { + SET_TX_DESC_HWSEQ_EN(txdesc, 1); + SET_TX_DESC_PKT_ID(txdesc, 8); + if (!defaultadapter) + SET_TX_DESC_QOS(txdesc, 1); + } + if (ieee80211_has_morefrags(fc)) + SET_TX_DESC_MORE_FRAG(txdesc, 1); + if (is_multicast_ether_addr(ieee80211_get_DA(hdr)) || + is_broadcast_ether_addr(ieee80211_get_DA(hdr))) + SET_TX_DESC_BMC(txdesc, 1); + _rtl_fill_usb_tx_desc(txdesc); + _rtl_tx_desc_checksum(txdesc); + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, "==>\n"); +} + +void rtl92cu_fill_fake_txdesc(struct ieee80211_hw *hw, u8 * pDesc, + u32 buffer_len, bool bIsPsPoll) +{ + /* Clear all status */ + memset(pDesc, 0, RTL_TX_HEADER_SIZE); + SET_TX_DESC_FIRST_SEG(pDesc, 1); /* bFirstSeg; */ + SET_TX_DESC_LAST_SEG(pDesc, 1); /* bLastSeg; */ + SET_TX_DESC_OFFSET(pDesc, RTL_TX_HEADER_SIZE); /* Offset = 32 */ + SET_TX_DESC_PKT_SIZE(pDesc, buffer_len); /* Buffer size + command hdr */ + SET_TX_DESC_QUEUE_SEL(pDesc, QSLT_MGNT); /* Fixed queue of Mgnt queue */ + /* Set NAVUSEHDR to prevent Ps-poll AId filed to be changed to error + * vlaue by Hw. */ + if (bIsPsPoll) { + SET_TX_DESC_NAV_USE_HDR(pDesc, 1); + } else { + SET_TX_DESC_HWSEQ_EN(pDesc, 1); /* Hw set sequence number */ + SET_TX_DESC_PKT_ID(pDesc, 0x100); /* set bit3 to 1. */ + } + SET_TX_DESC_USE_RATE(pDesc, 1); /* use data rate which is set by Sw */ + SET_TX_DESC_OWN(pDesc, 1); + SET_TX_DESC_TX_RATE(pDesc, DESC92_RATE1M); + _rtl_tx_desc_checksum(pDesc); +} + +void rtl92cu_tx_fill_cmddesc(struct ieee80211_hw *hw, + u8 *pdesc, bool firstseg, + bool lastseg, struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 fw_queue = QSLT_BEACON; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(skb->data); + __le16 fc = hdr->frame_control; + + memset((void *)pdesc, 0, RTL_TX_HEADER_SIZE); + if (firstseg) + SET_TX_DESC_OFFSET(pdesc, RTL_TX_HEADER_SIZE); + SET_TX_DESC_TX_RATE(pdesc, DESC92_RATE1M); + SET_TX_DESC_SEQ(pdesc, 0); + SET_TX_DESC_LINIP(pdesc, 0); + SET_TX_DESC_QUEUE_SEL(pdesc, fw_queue); + SET_TX_DESC_FIRST_SEG(pdesc, 1); + SET_TX_DESC_LAST_SEG(pdesc, 1); + SET_TX_DESC_RATE_ID(pdesc, 7); + SET_TX_DESC_MACID(pdesc, 0); + SET_TX_DESC_OWN(pdesc, 1); + SET_TX_DESC_PKT_SIZE(pdesc, (u16)skb->len); + SET_TX_DESC_FIRST_SEG(pdesc, 1); + SET_TX_DESC_LAST_SEG(pdesc, 1); + SET_TX_DESC_OFFSET(pdesc, 0x20); + SET_TX_DESC_USE_RATE(pdesc, 1); + if (!ieee80211_is_data_qos(fc)) { + SET_TX_DESC_HWSEQ_EN(pdesc, 1); + SET_TX_DESC_PKT_ID(pdesc, 8); + } + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_LOUD, "H2C Tx Cmd Content", + pdesc, RTL_TX_DESC_SIZE); +} + +bool rtl92cu_cmd_send_packet(struct ieee80211_hw *hw, struct sk_buff *skb) +{ + return true; +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192cu/trx.h b/drivers/net/wireless/rtlwifi/rtl8192cu/trx.h new file mode 100644 index 0000000..725c53a --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192cu/trx.h @@ -0,0 +1,435 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CU_TRX_H__ +#define __RTL92CU_TRX_H__ + +#define RTL92C_USB_BULK_IN_NUM 1 +#define RTL92C_NUM_RX_URBS 8 +#define RTL92C_NUM_TX_URBS 32 + +#define RTL92C_SIZE_MAX_RX_BUFFER 15360 /* 8192 */ +#define RX_DRV_INFO_SIZE_UNIT 8 + +#define RTL_AGG_ON 1 + +enum usb_rx_agg_mode { + USB_RX_AGG_DISABLE, + USB_RX_AGG_DMA, + USB_RX_AGG_USB, + USB_RX_AGG_DMA_USB +}; + +#define TX_SELE_HQ BIT(0) /* High Queue */ +#define TX_SELE_LQ BIT(1) /* Low Queue */ +#define TX_SELE_NQ BIT(2) /* Normal Queue */ + +#define RTL_USB_TX_AGG_NUM_DESC 5 + +#define RTL_USB_RX_AGG_PAGE_NUM 4 +#define RTL_USB_RX_AGG_PAGE_TIMEOUT 3 + +#define RTL_USB_RX_AGG_BLOCK_NUM 5 +#define RTL_USB_RX_AGG_BLOCK_TIMEOUT 3 + +/*======================== rx status =========================================*/ + +struct rx_drv_info_92c { + /* + * Driver info contain PHY status and other variabel size info + * PHY Status content as below + */ + + /* DWORD 0 */ + u8 gain_trsw[4]; + + /* DWORD 1 */ + u8 pwdb_all; + u8 cfosho[4]; + + /* DWORD 2 */ + u8 cfotail[4]; + + /* DWORD 3 */ + s8 rxevm[2]; + s8 rxsnr[4]; + + /* DWORD 4 */ + u8 pdsnr[2]; + + /* DWORD 5 */ + u8 csi_current[2]; + u8 csi_target[2]; + + /* DWORD 6 */ + u8 sigevm; + u8 max_ex_pwr; + u8 ex_intf_flag:1; + u8 sgi_en:1; + u8 rxsc:2; + u8 reserve:4; +} __packed; + +/* Define a macro that takes a le32 word, converts it to host ordering, + * right shifts by a specified count, creates a mask of the specified + * bit count, and extracts that number of bits. + */ + +#define SHIFT_AND_MASK_LE(__pdesc, __shift, __bits) \ + ((le32_to_cpu(*(((__le32 *)(__pdesc)))) >> (__shift)) & \ + BIT_LEN_MASK_32(__bits)) + +/* Define a macro that clears a bit field in an le32 word and + * sets the specified value into that bit field. The resulting + * value remains in le32 ordering; however, it is properly converted + * to host ordering for the clear and set operations before conversion + * back to le32. + */ + +#define SET_BITS_OFFSET_LE(__pdesc, __shift, __len, __val) \ + (*(__le32 *)(__pdesc) = \ + (cpu_to_le32((le32_to_cpu(*((__le32 *)(__pdesc))) & \ + (~(BIT_OFFSET_LEN_MASK_32((__shift), __len)))) | \ + (((u32)(__val) & BIT_LEN_MASK_32(__len)) << (__shift))))); + +/* macros to read various fields in RX descriptor */ + +/* DWORD 0 */ +#define GET_RX_DESC_PKT_LEN(__rxdesc) \ + SHIFT_AND_MASK_LE((__rxdesc), 0, 14) +#define GET_RX_DESC_CRC32(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc, 14, 1) +#define GET_RX_DESC_ICV(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc, 15, 1) +#define GET_RX_DESC_DRVINFO_SIZE(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc, 16, 4) +#define GET_RX_DESC_SECURITY(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc, 20, 3) +#define GET_RX_DESC_QOS(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc, 23, 1) +#define GET_RX_DESC_SHIFT(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc, 24, 2) +#define GET_RX_DESC_PHY_STATUS(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc, 26, 1) +#define GET_RX_DESC_SWDEC(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc, 27, 1) +#define GET_RX_DESC_LAST_SEG(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc, 28, 1) +#define GET_RX_DESC_FIRST_SEG(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc, 29, 1) +#define GET_RX_DESC_EOR(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc, 30, 1) +#define GET_RX_DESC_OWN(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc, 31, 1) + +/* DWORD 1 */ +#define GET_RX_DESC_MACID(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+4, 0, 5) +#define GET_RX_DESC_TID(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+4, 5, 4) +#define GET_RX_DESC_PAGGR(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+4, 14, 1) +#define GET_RX_DESC_FAGGR(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+4, 15, 1) +#define GET_RX_DESC_A1_FIT(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+4, 16, 4) +#define GET_RX_DESC_A2_FIT(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+4, 20, 4) +#define GET_RX_DESC_PAM(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+4, 24, 1) +#define GET_RX_DESC_PWR(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+4, 25, 1) +#define GET_RX_DESC_MORE_DATA(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+4, 26, 1) +#define GET_RX_DESC_MORE_FRAG(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+4, 27, 1) +#define GET_RX_DESC_TYPE(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+4, 28, 2) +#define GET_RX_DESC_MC(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+4, 30, 1) +#define GET_RX_DESC_BC(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+4, 31, 1) + +/* DWORD 2 */ +#define GET_RX_DESC_SEQ(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+8, 0, 12) +#define GET_RX_DESC_FRAG(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+8, 12, 4) +#define GET_RX_DESC_USB_AGG_PKTNUM(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+8, 16, 8) +#define GET_RX_DESC_NEXT_IND(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+8, 30, 1) + +/* DWORD 3 */ +#define GET_RX_DESC_RX_MCS(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+12, 0, 6) +#define GET_RX_DESC_RX_HT(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+12, 6, 1) +#define GET_RX_DESC_AMSDU(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+12, 7, 1) +#define GET_RX_DESC_SPLCP(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+12, 8, 1) +#define GET_RX_DESC_BW(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+12, 9, 1) +#define GET_RX_DESC_HTC(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+12, 10, 1) +#define GET_RX_DESC_TCP_CHK_RPT(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+12, 11, 1) +#define GET_RX_DESC_IP_CHK_RPT(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+12, 12, 1) +#define GET_RX_DESC_TCP_CHK_VALID(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+12, 13, 1) +#define GET_RX_DESC_HWPC_ERR(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+12, 14, 1) +#define GET_RX_DESC_HWPC_IND(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+12, 15, 1) +#define GET_RX_DESC_IV0(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+12, 16, 16) + +/* DWORD 4 */ +#define GET_RX_DESC_IV1(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+16, 0, 32) + +/* DWORD 5 */ +#define GET_RX_DESC_TSFL(__rxdesc) \ + SHIFT_AND_MASK_LE(__rxdesc+20, 0, 32) + +/*======================= tx desc ============================================*/ + +/* macros to set various fields in TX descriptor */ + +/* Dword 0 */ +#define SET_TX_DESC_PKT_SIZE(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc, 0, 16, __value) +#define SET_TX_DESC_OFFSET(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc, 16, 8, __value) +#define SET_TX_DESC_BMC(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc, 24, 1, __value) +#define SET_TX_DESC_HTC(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc, 25, 1, __value) +#define SET_TX_DESC_LAST_SEG(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc, 26, 1, __value) +#define SET_TX_DESC_FIRST_SEG(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc, 27, 1, __value) +#define SET_TX_DESC_LINIP(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc, 28, 1, __value) +#define SET_TX_DESC_NO_ACM(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc, 29, 1, __value) +#define SET_TX_DESC_GF(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc, 30, 1, __value) +#define SET_TX_DESC_OWN(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc, 31, 1, __value) + + +/* Dword 1 */ +#define SET_TX_DESC_MACID(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+4, 0, 5, __value) +#define SET_TX_DESC_AGG_ENABLE(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+4, 5, 1, __value) +#define SET_TX_DESC_AGG_BREAK(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+4, 6, 1, __value) +#define SET_TX_DESC_RDG_ENABLE(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+4, 7, 1, __value) +#define SET_TX_DESC_QUEUE_SEL(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+4, 8, 5, __value) +#define SET_TX_DESC_RDG_NAV_EXT(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+4, 13, 1, __value) +#define SET_TX_DESC_LSIG_TXOP_EN(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+4, 14, 1, __value) +#define SET_TX_DESC_PIFS(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+4, 15, 1, __value) +#define SET_TX_DESC_RATE_ID(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+4, 16, 4, __value) +#define SET_TX_DESC_RA_BRSR_ID(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+4, 16, 4, __value) +#define SET_TX_DESC_NAV_USE_HDR(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+4, 20, 1, __value) +#define SET_TX_DESC_EN_DESC_ID(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+4, 21, 1, __value) +#define SET_TX_DESC_SEC_TYPE(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+4, 22, 2, __value) +#define SET_TX_DESC_PKT_OFFSET(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+4, 26, 5, __value) + +/* Dword 2 */ +#define SET_TX_DESC_RTS_RC(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+8, 0, 6, __value) +#define SET_TX_DESC_DATA_RC(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+8, 6, 6, __value) +#define SET_TX_DESC_BAR_RTY_TH(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+8, 14, 2, __value) +#define SET_TX_DESC_MORE_FRAG(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+8, 17, 1, __value) +#define SET_TX_DESC_RAW(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+8, 18, 1, __value) +#define SET_TX_DESC_CCX(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+8, 19, 1, __value) +#define SET_TX_DESC_AMPDU_DENSITY(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+8, 20, 3, __value) +#define SET_TX_DESC_ANTSEL_A(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+8, 24, 1, __value) +#define SET_TX_DESC_ANTSEL_B(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+8, 25, 1, __value) +#define SET_TX_DESC_TX_ANT_CCK(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+8, 26, 2, __value) +#define SET_TX_DESC_TX_ANTL(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+8, 28, 2, __value) +#define SET_TX_DESC_TX_ANT_HT(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+8, 30, 2, __value) + +/* Dword 3 */ +#define SET_TX_DESC_NEXT_HEAP_PAGE(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+12, 0, 8, __value) +#define SET_TX_DESC_TAIL_PAGE(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+12, 8, 8, __value) +#define SET_TX_DESC_SEQ(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+12, 16, 12, __value) +#define SET_TX_DESC_PKT_ID(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+12, 28, 4, __value) + +/* Dword 4 */ +#define SET_TX_DESC_RTS_RATE(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 0, 5, __value) +#define SET_TX_DESC_AP_DCFE(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 5, 1, __value) +#define SET_TX_DESC_QOS(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 6, 1, __value) +#define SET_TX_DESC_HWSEQ_EN(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 7, 1, __value) +#define SET_TX_DESC_USE_RATE(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 8, 1, __value) +#define SET_TX_DESC_DISABLE_RTS_FB(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 9, 1, __value) +#define SET_TX_DESC_DISABLE_FB(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 10, 1, __value) +#define SET_TX_DESC_CTS2SELF(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 11, 1, __value) +#define SET_TX_DESC_RTS_ENABLE(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 12, 1, __value) +#define SET_TX_DESC_HW_RTS_ENABLE(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 13, 1, __value) +#define SET_TX_DESC_WAIT_DCTS(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 18, 1, __value) +#define SET_TX_DESC_CTS2AP_EN(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 19, 1, __value) +#define SET_TX_DESC_DATA_SC(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 20, 2, __value) +#define SET_TX_DESC_DATA_STBC(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 22, 2, __value) +#define SET_TX_DESC_DATA_SHORT(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 24, 1, __value) +#define SET_TX_DESC_DATA_BW(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 25, 1, __value) +#define SET_TX_DESC_RTS_SHORT(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 26, 1, __value) +#define SET_TX_DESC_RTS_BW(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 27, 1, __value) +#define SET_TX_DESC_RTS_SC(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 28, 2, __value) +#define SET_TX_DESC_RTS_STBC(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+16, 30, 2, __value) + +/* Dword 5 */ +#define SET_TX_DESC_TX_RATE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 0, 6, __val) +#define SET_TX_DESC_DATA_SHORTGI(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 6, 1, __val) +#define SET_TX_DESC_CCX_TAG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 7, 1, __val) +#define SET_TX_DESC_DATA_RATE_FB_LIMIT(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+20, 8, 5, __value) +#define SET_TX_DESC_RTS_RATE_FB_LIMIT(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+20, 13, 4, __value) +#define SET_TX_DESC_RETRY_LIMIT_ENABLE(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+20, 17, 1, __value) +#define SET_TX_DESC_DATA_RETRY_LIMIT(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+20, 18, 6, __value) +#define SET_TX_DESC_USB_TXAGG_NUM(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+20, 24, 8, __value) + +/* Dword 6 */ +#define SET_TX_DESC_TXAGC_A(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+24, 0, 5, __value) +#define SET_TX_DESC_TXAGC_B(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+24, 5, 5, __value) +#define SET_TX_DESC_USB_MAX_LEN(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+24, 10, 1, __value) +#define SET_TX_DESC_MAX_AGG_NUM(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+24, 11, 5, __value) +#define SET_TX_DESC_MCSG1_MAX_LEN(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+24, 16, 4, __value) +#define SET_TX_DESC_MCSG2_MAX_LEN(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+24, 20, 4, __value) +#define SET_TX_DESC_MCSG3_MAX_LEN(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+24, 24, 4, __value) +#define SET_TX_DESC_MCSG7_MAX_LEN(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+24, 28, 4, __value) + +/* Dword 7 */ +#define SET_TX_DESC_TX_DESC_CHECKSUM(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+28, 0, 16, __value) +#define SET_TX_DESC_MCSG4_MAX_LEN(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+28, 16, 4, __value) +#define SET_TX_DESC_MCSG5_MAX_LEN(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+28, 20, 4, __value) +#define SET_TX_DESC_MCSG6_MAX_LEN(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+28, 24, 4, __value) +#define SET_TX_DESC_MCSG15_MAX_LEN(__txdesc, __value) \ + SET_BITS_OFFSET_LE(__txdesc+28, 28, 4, __value) + + +int rtl8192cu_endpoint_mapping(struct ieee80211_hw *hw); +u16 rtl8192cu_mq_to_hwq(__le16 fc, u16 mac80211_queue_index); +bool rtl92cu_rx_query_desc(struct ieee80211_hw *hw, + struct rtl_stats *stats, + struct ieee80211_rx_status *rx_status, + u8 *p_desc, struct sk_buff *skb); +void rtl8192cu_rx_hdl(struct ieee80211_hw *hw, struct sk_buff * skb); +void rtl8192c_rx_segregate_hdl(struct ieee80211_hw *, struct sk_buff *, + struct sk_buff_head *); +void rtl8192c_tx_cleanup(struct ieee80211_hw *hw, struct sk_buff *skb); +int rtl8192c_tx_post_hdl(struct ieee80211_hw *hw, struct urb *urb, + struct sk_buff *skb); +struct sk_buff *rtl8192c_tx_aggregate_hdl(struct ieee80211_hw *, + struct sk_buff_head *); +void rtl92cu_tx_fill_desc(struct ieee80211_hw *hw, + struct ieee80211_hdr *hdr, u8 *pdesc_tx, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff *skb, + u8 queue_index, + struct rtl_tcb_desc *tcb_desc); +void rtl92cu_fill_fake_txdesc(struct ieee80211_hw *hw, u8 * pDesc, + u32 buffer_len, bool bIsPsPoll); +void rtl92cu_tx_fill_cmddesc(struct ieee80211_hw *hw, + u8 *pdesc, bool b_firstseg, + bool b_lastseg, struct sk_buff *skb); +bool rtl92cu_cmd_send_packet(struct ieee80211_hw *hw, struct sk_buff *skb); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/Makefile b/drivers/net/wireless/rtlwifi/rtl8192de/Makefile new file mode 100644 index 0000000..0e0b63f --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/Makefile @@ -0,0 +1,14 @@ +rtl8192de-objs := \ + dm.o \ + fw.o \ + hw.o \ + led.o \ + phy.o \ + rf.o \ + sw.o \ + table.o \ + trx.o + +obj-$(CPTCFG_RTL8192DE) += rtl8192de.o + +ccflags-y += -D__CHECK_ENDIAN__ diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/def.h b/drivers/net/wireless/rtlwifi/rtl8192de/def.h new file mode 100644 index 0000000..939c905 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/def.h @@ -0,0 +1,271 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92D_DEF_H__ +#define __RTL92D_DEF_H__ + +/* Min Spacing related settings. */ +#define MAX_MSS_DENSITY_2T 0x13 +#define MAX_MSS_DENSITY_1T 0x0A + +#define RF6052_MAX_TX_PWR 0x3F +#define RF6052_MAX_REG 0x3F +#define RF6052_MAX_PATH 2 + +#define HAL_RETRY_LIMIT_INFRA 48 +#define HAL_RETRY_LIMIT_AP_ADHOC 7 + +#define PHY_RSSI_SLID_WIN_MAX 100 +#define PHY_LINKQUALITY_SLID_WIN_MAX 20 +#define PHY_BEACON_RSSI_SLID_WIN_MAX 10 + +#define RESET_DELAY_8185 20 + +#define RT_IBSS_INT_MASKS (IMR_BCNINT | IMR_TBDOK | IMR_TBDER) +#define RT_AC_INT_MASKS (IMR_VIDOK | IMR_VODOK | IMR_BEDOK|IMR_BKDOK) + +#define NUM_OF_FIRMWARE_QUEUE 10 +#define NUM_OF_PAGES_IN_FW 0x100 +#define NUM_OF_PAGE_IN_FW_QUEUE_BK 0x07 +#define NUM_OF_PAGE_IN_FW_QUEUE_BE 0x07 +#define NUM_OF_PAGE_IN_FW_QUEUE_VI 0x07 +#define NUM_OF_PAGE_IN_FW_QUEUE_VO 0x07 +#define NUM_OF_PAGE_IN_FW_QUEUE_HCCA 0x0 +#define NUM_OF_PAGE_IN_FW_QUEUE_CMD 0x0 +#define NUM_OF_PAGE_IN_FW_QUEUE_MGNT 0x02 +#define NUM_OF_PAGE_IN_FW_QUEUE_HIGH 0x02 +#define NUM_OF_PAGE_IN_FW_QUEUE_BCN 0x2 +#define NUM_OF_PAGE_IN_FW_QUEUE_PUB 0xA1 + +#define NUM_OF_PAGE_IN_FW_QUEUE_BK_DTM 0x026 +#define NUM_OF_PAGE_IN_FW_QUEUE_BE_DTM 0x048 +#define NUM_OF_PAGE_IN_FW_QUEUE_VI_DTM 0x048 +#define NUM_OF_PAGE_IN_FW_QUEUE_VO_DTM 0x026 +#define NUM_OF_PAGE_IN_FW_QUEUE_PUB_DTM 0x00 + +#define MAX_LINES_HWCONFIG_TXT 1000 +#define MAX_BYTES_LINE_HWCONFIG_TXT 256 + +#define SW_THREE_WIRE 0 +#define HW_THREE_WIRE 2 + +#define BT_DEMO_BOARD 0 +#define BT_QA_BOARD 1 +#define BT_FPGA 2 + +#define RX_SMOOTH_FACTOR 20 + +#define HAL_PRIME_CHNL_OFFSET_DONT_CARE 0 +#define HAL_PRIME_CHNL_OFFSET_LOWER 1 +#define HAL_PRIME_CHNL_OFFSET_UPPER 2 + +#define MAX_H2C_QUEUE_NUM 10 + +#define RX_MPDU_QUEUE 0 +#define RX_CMD_QUEUE 1 +#define RX_MAX_QUEUE 2 + +#define C2H_RX_CMD_HDR_LEN 8 +#define GET_C2H_CMD_CMD_LEN(__prxhdr) \ + LE_BITS_TO_4BYTE((__prxhdr), 0, 16) +#define GET_C2H_CMD_ELEMENT_ID(__prxhdr) \ + LE_BITS_TO_4BYTE((__prxhdr), 16, 8) +#define GET_C2H_CMD_CMD_SEQ(__prxhdr) \ + LE_BITS_TO_4BYTE((__prxhdr), 24, 7) +#define GET_C2H_CMD_CONTINUE(__prxhdr) \ + LE_BITS_TO_4BYTE((__prxhdr), 31, 1) +#define GET_C2H_CMD_CONTENT(__prxhdr) \ + ((u8 *)(__prxhdr) + C2H_RX_CMD_HDR_LEN) + +#define GET_C2H_CMD_FEEDBACK_ELEMENT_ID(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE((__pcmdfbhdr), 0, 8) +#define GET_C2H_CMD_FEEDBACK_CCX_LEN(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE((__pcmdfbhdr), 8, 8) +#define GET_C2H_CMD_FEEDBACK_CCX_CMD_CNT(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE((__pcmdfbhdr), 16, 16) +#define GET_C2H_CMD_FEEDBACK_CCX_MAC_ID(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 0, 5) +#define GET_C2H_CMD_FEEDBACK_CCX_VALID(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 7, 1) +#define GET_C2H_CMD_FEEDBACK_CCX_RETRY_CNT(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 8, 5) +#define GET_C2H_CMD_FEEDBACK_CCX_TOK(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 15, 1) +#define GET_C2H_CMD_FEEDBACK_CCX_QSEL(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 16, 4) +#define GET_C2H_CMD_FEEDBACK_CCX_SEQ(__pcmdfbhdr) \ + LE_BITS_TO_4BYTE(((__pcmdfbhdr) + 4), 20, 12) + +enum version_8192d { + VERSION_TEST_CHIP_88C = 0x0000, + VERSION_TEST_CHIP_92C = 0x0020, + VERSION_TEST_UMC_CHIP_8723 = 0x0081, + VERSION_NORMAL_TSMC_CHIP_88C = 0x0008, + VERSION_NORMAL_TSMC_CHIP_92C = 0x0028, + VERSION_NORMAL_TSMC_CHIP_92C_1T2R = 0x0018, + VERSION_NORMAL_UMC_CHIP_88C_A_CUT = 0x0088, + VERSION_NORMAL_UMC_CHIP_92C_A_CUT = 0x00a8, + VERSION_NORMAL_UMC_CHIP_92C_1T2R_A_CUT = 0x0098, + VERSION_NORMAL_UMC_CHIP_8723_1T1R_A_CUT = 0x0089, + VERSION_NORMAL_UMC_CHIP_8723_1T1R_B_CUT = 0x1089, + VERSION_NORMAL_UMC_CHIP_88C_B_CUT = 0x1088, + VERSION_NORMAL_UMC_CHIP_92C_B_CUT = 0x10a8, + VERSION_NORMAL_UMC_CHIP_92C_1T2R_B_CUT = 0x1090, + VERSION_TEST_CHIP_92D_SINGLEPHY = 0x0022, + VERSION_TEST_CHIP_92D_DUALPHY = 0x0002, + VERSION_NORMAL_CHIP_92D_SINGLEPHY = 0x002a, + VERSION_NORMAL_CHIP_92D_DUALPHY = 0x000a, + VERSION_NORMAL_CHIP_92D_C_CUT_SINGLEPHY = 0x202a, + VERSION_NORMAL_CHIP_92D_C_CUT_DUALPHY = 0x200a, + VERSION_NORMAL_CHIP_92D_D_CUT_SINGLEPHY = 0x302a, + VERSION_NORMAL_CHIP_92D_D_CUT_DUALPHY = 0x300a, + VERSION_NORMAL_CHIP_92D_E_CUT_SINGLEPHY = 0x402a, + VERSION_NORMAL_CHIP_92D_E_CUT_DUALPHY = 0x400a, +}; + +/* for 92D */ +#define CHIP_92D_SINGLEPHY BIT(9) + +/* Chip specific */ +#define CHIP_BONDING_IDENTIFIER(_value) (((_value)>>22)&0x3) +#define CHIP_BONDING_92C_1T2R 0x1 +#define CHIP_BONDING_88C_USB_MCARD 0x2 +#define CHIP_BONDING_88C_USB_HP 0x1 + +/* [15:12] IC version(CUT): A-cut=0, B-cut=1, C-cut=2, D-cut=3 */ +/* [7] Manufacturer: TSMC=0, UMC=1 */ +/* [6:4] RF type: 1T1R=0, 1T2R=1, 2T2R=2 */ +/* [3] Chip type: TEST=0, NORMAL=1 */ +/* [2:0] IC type: 81xxC=0, 8723=1, 92D=2 */ +#define CHIP_8723 BIT(0) +#define CHIP_92D BIT(1) +#define NORMAL_CHIP BIT(3) +#define RF_TYPE_1T1R (~(BIT(4)|BIT(5)|BIT(6))) +#define RF_TYPE_1T2R BIT(4) +#define RF_TYPE_2T2R BIT(5) +#define CHIP_VENDOR_UMC BIT(7) +#define CHIP_92D_B_CUT BIT(12) +#define CHIP_92D_C_CUT BIT(13) +#define CHIP_92D_D_CUT (BIT(13)|BIT(12)) +#define CHIP_92D_E_CUT BIT(14) + +/* MASK */ +#define IC_TYPE_MASK (BIT(0)|BIT(1)|BIT(2)) +#define CHIP_TYPE_MASK BIT(3) +#define RF_TYPE_MASK (BIT(4)|BIT(5)|BIT(6)) +#define MANUFACTUER_MASK BIT(7) +#define ROM_VERSION_MASK (BIT(11)|BIT(10)|BIT(9)|BIT(8)) +#define CUT_VERSION_MASK (BIT(15)|BIT(14)|BIT(13)|BIT(12)) + + +/* Get element */ +#define GET_CVID_IC_TYPE(version) ((version) & IC_TYPE_MASK) +#define GET_CVID_CHIP_TYPE(version) ((version) & CHIP_TYPE_MASK) +#define GET_CVID_RF_TYPE(version) ((version) & RF_TYPE_MASK) +#define GET_CVID_MANUFACTUER(version) ((version) & MANUFACTUER_MASK) +#define GET_CVID_ROM_VERSION(version) ((version) & ROM_VERSION_MASK) +#define GET_CVID_CUT_VERSION(version) ((version) & CUT_VERSION_MASK) + +#define IS_1T1R(version) ((GET_CVID_RF_TYPE(version)) ? \ + false : true) +#define IS_1T2R(version) ((GET_CVID_RF_TYPE(version) == \ + RF_TYPE_1T2R) ? true : false) +#define IS_2T2R(version) ((GET_CVID_RF_TYPE(version) == \ + RF_TYPE_2T2R) ? true : false) + +#define IS_92D_SINGLEPHY(version) ((IS_92D(version)) ? \ + (IS_2T2R(version) ? true : false) : false) +#define IS_92D(version) ((GET_CVID_IC_TYPE(version) == \ + CHIP_92D) ? true : false) +#define IS_92D_C_CUT(version) ((IS_92D(version)) ? \ + ((GET_CVID_CUT_VERSION(version) == \ + CHIP_92D_C_CUT) ? true : false) : false) +#define IS_92D_D_CUT(version) ((IS_92D(version)) ? \ + ((GET_CVID_CUT_VERSION(version) == \ + CHIP_92D_D_CUT) ? true : false) : false) +#define IS_92D_E_CUT(version) ((IS_92D(version)) ? \ + ((GET_CVID_CUT_VERSION(version) == \ + CHIP_92D_E_CUT) ? true : false) : false) + +enum rf_optype { + RF_OP_BY_SW_3WIRE = 0, + RF_OP_BY_FW, + RF_OP_MAX +}; + +enum rtl_desc_qsel { + QSLT_BK = 0x2, + QSLT_BE = 0x0, + QSLT_VI = 0x5, + QSLT_VO = 0x7, + QSLT_BEACON = 0x10, + QSLT_HIGH = 0x11, + QSLT_MGNT = 0x12, + QSLT_CMD = 0x13, +}; + +enum channel_plan { + CHPL_FCC = 0, + CHPL_IC = 1, + CHPL_ETSI = 2, + CHPL_SPAIN = 3, + CHPL_FRANCE = 4, + CHPL_MKK = 5, + CHPL_MKK1 = 6, + CHPL_ISRAEL = 7, + CHPL_TELEC = 8, + CHPL_GLOBAL = 9, + CHPL_WORLD = 10, +}; + +struct phy_sts_cck_8192d { + u8 adc_pwdb_X[4]; + u8 sq_rpt; + u8 cck_agc_rpt; +}; + +struct h2c_cmd_8192c { + u8 element_id; + u32 cmd_len; + u8 *p_cmdbuffer; +}; + +struct txpower_info { + u8 cck_index[RF6052_MAX_PATH][CHANNEL_GROUP_MAX]; + u8 ht40_1sindex[RF6052_MAX_PATH][CHANNEL_GROUP_MAX]; + u8 ht40_2sindexdiff[RF6052_MAX_PATH][CHANNEL_GROUP_MAX]; + u8 ht20indexdiff[RF6052_MAX_PATH][CHANNEL_GROUP_MAX]; + u8 ofdmindexdiff[RF6052_MAX_PATH][CHANNEL_GROUP_MAX]; + u8 ht40maxoffset[RF6052_MAX_PATH][CHANNEL_GROUP_MAX]; + u8 ht20maxoffset[RF6052_MAX_PATH][CHANNEL_GROUP_MAX]; + u8 tssi_a[3]; /* 5GL/5GM/5GH */ + u8 tssi_b[3]; +}; + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/dm.c b/drivers/net/wireless/rtlwifi/rtl8192de/dm.c new file mode 100644 index 0000000..19a7655 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/dm.c @@ -0,0 +1,1338 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../base.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "dm.h" +#include "fw.h" + +#define UNDEC_SM_PWDB entry_min_undec_sm_pwdb + +static const u32 ofdmswing_table[OFDM_TABLE_SIZE_92D] = { + 0x7f8001fe, /* 0, +6.0dB */ + 0x788001e2, /* 1, +5.5dB */ + 0x71c001c7, /* 2, +5.0dB */ + 0x6b8001ae, /* 3, +4.5dB */ + 0x65400195, /* 4, +4.0dB */ + 0x5fc0017f, /* 5, +3.5dB */ + 0x5a400169, /* 6, +3.0dB */ + 0x55400155, /* 7, +2.5dB */ + 0x50800142, /* 8, +2.0dB */ + 0x4c000130, /* 9, +1.5dB */ + 0x47c0011f, /* 10, +1.0dB */ + 0x43c0010f, /* 11, +0.5dB */ + 0x40000100, /* 12, +0dB */ + 0x3c8000f2, /* 13, -0.5dB */ + 0x390000e4, /* 14, -1.0dB */ + 0x35c000d7, /* 15, -1.5dB */ + 0x32c000cb, /* 16, -2.0dB */ + 0x300000c0, /* 17, -2.5dB */ + 0x2d4000b5, /* 18, -3.0dB */ + 0x2ac000ab, /* 19, -3.5dB */ + 0x288000a2, /* 20, -4.0dB */ + 0x26000098, /* 21, -4.5dB */ + 0x24000090, /* 22, -5.0dB */ + 0x22000088, /* 23, -5.5dB */ + 0x20000080, /* 24, -6.0dB */ + 0x1e400079, /* 25, -6.5dB */ + 0x1c800072, /* 26, -7.0dB */ + 0x1b00006c, /* 27. -7.5dB */ + 0x19800066, /* 28, -8.0dB */ + 0x18000060, /* 29, -8.5dB */ + 0x16c0005b, /* 30, -9.0dB */ + 0x15800056, /* 31, -9.5dB */ + 0x14400051, /* 32, -10.0dB */ + 0x1300004c, /* 33, -10.5dB */ + 0x12000048, /* 34, -11.0dB */ + 0x11000044, /* 35, -11.5dB */ + 0x10000040, /* 36, -12.0dB */ + 0x0f00003c, /* 37, -12.5dB */ + 0x0e400039, /* 38, -13.0dB */ + 0x0d800036, /* 39, -13.5dB */ + 0x0cc00033, /* 40, -14.0dB */ + 0x0c000030, /* 41, -14.5dB */ + 0x0b40002d, /* 42, -15.0dB */ +}; + +static const u8 cckswing_table_ch1ch13[CCK_TABLE_SIZE][8] = { + {0x36, 0x35, 0x2e, 0x25, 0x1c, 0x12, 0x09, 0x04}, /* 0, +0dB */ + {0x33, 0x32, 0x2b, 0x23, 0x1a, 0x11, 0x08, 0x04}, /* 1, -0.5dB */ + {0x30, 0x2f, 0x29, 0x21, 0x19, 0x10, 0x08, 0x03}, /* 2, -1.0dB */ + {0x2d, 0x2d, 0x27, 0x1f, 0x18, 0x0f, 0x08, 0x03}, /* 3, -1.5dB */ + {0x2b, 0x2a, 0x25, 0x1e, 0x16, 0x0e, 0x07, 0x03}, /* 4, -2.0dB */ + {0x28, 0x28, 0x22, 0x1c, 0x15, 0x0d, 0x07, 0x03}, /* 5, -2.5dB */ + {0x26, 0x25, 0x21, 0x1b, 0x14, 0x0d, 0x06, 0x03}, /* 6, -3.0dB */ + {0x24, 0x23, 0x1f, 0x19, 0x13, 0x0c, 0x06, 0x03}, /* 7, -3.5dB */ + {0x22, 0x21, 0x1d, 0x18, 0x11, 0x0b, 0x06, 0x02}, /* 8, -4.0dB */ + {0x20, 0x20, 0x1b, 0x16, 0x11, 0x08, 0x05, 0x02}, /* 9, -4.5dB */ + {0x1f, 0x1e, 0x1a, 0x15, 0x10, 0x0a, 0x05, 0x02}, /* 10, -5.0dB */ + {0x1d, 0x1c, 0x18, 0x14, 0x0f, 0x0a, 0x05, 0x02}, /* 11, -5.5dB */ + {0x1b, 0x1a, 0x17, 0x13, 0x0e, 0x09, 0x04, 0x02}, /* 12, -6.0dB */ + {0x1a, 0x19, 0x16, 0x12, 0x0d, 0x09, 0x04, 0x02}, /* 13, -6.5dB */ + {0x18, 0x17, 0x15, 0x11, 0x0c, 0x08, 0x04, 0x02}, /* 14, -7.0dB */ + {0x17, 0x16, 0x13, 0x10, 0x0c, 0x08, 0x04, 0x02}, /* 15, -7.5dB */ + {0x16, 0x15, 0x12, 0x0f, 0x0b, 0x07, 0x04, 0x01}, /* 16, -8.0dB */ + {0x14, 0x14, 0x11, 0x0e, 0x0b, 0x07, 0x03, 0x02}, /* 17, -8.5dB */ + {0x13, 0x13, 0x10, 0x0d, 0x0a, 0x06, 0x03, 0x01}, /* 18, -9.0dB */ + {0x12, 0x12, 0x0f, 0x0c, 0x09, 0x06, 0x03, 0x01}, /* 19, -9.5dB */ + {0x11, 0x11, 0x0f, 0x0c, 0x09, 0x06, 0x03, 0x01}, /* 20, -10.0dB */ + {0x10, 0x10, 0x0e, 0x0b, 0x08, 0x05, 0x03, 0x01}, /* 21, -10.5dB */ + {0x0f, 0x0f, 0x0d, 0x0b, 0x08, 0x05, 0x03, 0x01}, /* 22, -11.0dB */ + {0x0e, 0x0e, 0x0c, 0x0a, 0x08, 0x05, 0x02, 0x01}, /* 23, -11.5dB */ + {0x0d, 0x0d, 0x0c, 0x0a, 0x07, 0x05, 0x02, 0x01}, /* 24, -12.0dB */ + {0x0d, 0x0c, 0x0b, 0x09, 0x07, 0x04, 0x02, 0x01}, /* 25, -12.5dB */ + {0x0c, 0x0c, 0x0a, 0x09, 0x06, 0x04, 0x02, 0x01}, /* 26, -13.0dB */ + {0x0b, 0x0b, 0x0a, 0x08, 0x06, 0x04, 0x02, 0x01}, /* 27, -13.5dB */ + {0x0b, 0x0a, 0x09, 0x08, 0x06, 0x04, 0x02, 0x01}, /* 28, -14.0dB */ + {0x0a, 0x0a, 0x09, 0x07, 0x05, 0x03, 0x02, 0x01}, /* 29, -14.5dB */ + {0x0a, 0x09, 0x08, 0x07, 0x05, 0x03, 0x02, 0x01}, /* 30, -15.0dB */ + {0x09, 0x09, 0x08, 0x06, 0x05, 0x03, 0x01, 0x01}, /* 31, -15.5dB */ + {0x09, 0x08, 0x07, 0x06, 0x04, 0x03, 0x01, 0x01} /* 32, -16.0dB */ +}; + +static const u8 cckswing_table_ch14[CCK_TABLE_SIZE][8] = { + {0x36, 0x35, 0x2e, 0x1b, 0x00, 0x00, 0x00, 0x00}, /* 0, +0dB */ + {0x33, 0x32, 0x2b, 0x19, 0x00, 0x00, 0x00, 0x00}, /* 1, -0.5dB */ + {0x30, 0x2f, 0x29, 0x18, 0x00, 0x00, 0x00, 0x00}, /* 2, -1.0dB */ + {0x2d, 0x2d, 0x17, 0x17, 0x00, 0x00, 0x00, 0x00}, /* 3, -1.5dB */ + {0x2b, 0x2a, 0x25, 0x15, 0x00, 0x00, 0x00, 0x00}, /* 4, -2.0dB */ + {0x28, 0x28, 0x24, 0x14, 0x00, 0x00, 0x00, 0x00}, /* 5, -2.5dB */ + {0x26, 0x25, 0x21, 0x13, 0x00, 0x00, 0x00, 0x00}, /* 6, -3.0dB */ + {0x24, 0x23, 0x1f, 0x12, 0x00, 0x00, 0x00, 0x00}, /* 7, -3.5dB */ + {0x22, 0x21, 0x1d, 0x11, 0x00, 0x00, 0x00, 0x00}, /* 8, -4.0dB */ + {0x20, 0x20, 0x1b, 0x10, 0x00, 0x00, 0x00, 0x00}, /* 9, -4.5dB */ + {0x1f, 0x1e, 0x1a, 0x0f, 0x00, 0x00, 0x00, 0x00}, /* 10, -5.0dB */ + {0x1d, 0x1c, 0x18, 0x0e, 0x00, 0x00, 0x00, 0x00}, /* 11, -5.5dB */ + {0x1b, 0x1a, 0x17, 0x0e, 0x00, 0x00, 0x00, 0x00}, /* 12, -6.0dB */ + {0x1a, 0x19, 0x16, 0x0d, 0x00, 0x00, 0x00, 0x00}, /* 13, -6.5dB */ + {0x18, 0x17, 0x15, 0x0c, 0x00, 0x00, 0x00, 0x00}, /* 14, -7.0dB */ + {0x17, 0x16, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00}, /* 15, -7.5dB */ + {0x16, 0x15, 0x12, 0x0b, 0x00, 0x00, 0x00, 0x00}, /* 16, -8.0dB */ + {0x14, 0x14, 0x11, 0x0a, 0x00, 0x00, 0x00, 0x00}, /* 17, -8.5dB */ + {0x13, 0x13, 0x10, 0x0a, 0x00, 0x00, 0x00, 0x00}, /* 18, -9.0dB */ + {0x12, 0x12, 0x0f, 0x09, 0x00, 0x00, 0x00, 0x00}, /* 19, -9.5dB */ + {0x11, 0x11, 0x0f, 0x09, 0x00, 0x00, 0x00, 0x00}, /* 20, -10.0dB */ + {0x10, 0x10, 0x0e, 0x08, 0x00, 0x00, 0x00, 0x00}, /* 21, -10.5dB */ + {0x0f, 0x0f, 0x0d, 0x08, 0x00, 0x00, 0x00, 0x00}, /* 22, -11.0dB */ + {0x0e, 0x0e, 0x0c, 0x07, 0x00, 0x00, 0x00, 0x00}, /* 23, -11.5dB */ + {0x0d, 0x0d, 0x0c, 0x07, 0x00, 0x00, 0x00, 0x00}, /* 24, -12.0dB */ + {0x0d, 0x0c, 0x0b, 0x06, 0x00, 0x00, 0x00, 0x00}, /* 25, -12.5dB */ + {0x0c, 0x0c, 0x0a, 0x06, 0x00, 0x00, 0x00, 0x00}, /* 26, -13.0dB */ + {0x0b, 0x0b, 0x0a, 0x06, 0x00, 0x00, 0x00, 0x00}, /* 27, -13.5dB */ + {0x0b, 0x0a, 0x09, 0x05, 0x00, 0x00, 0x00, 0x00}, /* 28, -14.0dB */ + {0x0a, 0x0a, 0x09, 0x05, 0x00, 0x00, 0x00, 0x00}, /* 29, -14.5dB */ + {0x0a, 0x09, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00}, /* 30, -15.0dB */ + {0x09, 0x09, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00}, /* 31, -15.5dB */ + {0x09, 0x08, 0x07, 0x04, 0x00, 0x00, 0x00, 0x00} /* 32, -16.0dB */ +}; + +static void rtl92d_dm_diginit(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *de_digtable = &rtlpriv->dm_digtable; + + de_digtable->dig_enable_flag = true; + de_digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_MAX; + de_digtable->cur_igvalue = 0x20; + de_digtable->pre_igvalue = 0x0; + de_digtable->cursta_cstate = DIG_STA_DISCONNECT; + de_digtable->presta_cstate = DIG_STA_DISCONNECT; + de_digtable->curmultista_cstate = DIG_MULTISTA_DISCONNECT; + de_digtable->rssi_lowthresh = DM_DIG_THRESH_LOW; + de_digtable->rssi_highthresh = DM_DIG_THRESH_HIGH; + de_digtable->fa_lowthresh = DM_FALSEALARM_THRESH_LOW; + de_digtable->fa_highthresh = DM_FALSEALARM_THRESH_HIGH; + de_digtable->rx_gain_max = DM_DIG_FA_UPPER; + de_digtable->rx_gain_min = DM_DIG_FA_LOWER; + de_digtable->back_val = DM_DIG_BACKOFF_DEFAULT; + de_digtable->back_range_max = DM_DIG_BACKOFF_MAX; + de_digtable->back_range_min = DM_DIG_BACKOFF_MIN; + de_digtable->pre_cck_pd_state = CCK_PD_STAGE_LOWRSSI; + de_digtable->cur_cck_pd_state = CCK_PD_STAGE_MAX; + de_digtable->large_fa_hit = 0; + de_digtable->recover_cnt = 0; + de_digtable->forbidden_igi = DM_DIG_FA_LOWER; +} + +static void rtl92d_dm_false_alarm_counter_statistics(struct ieee80211_hw *hw) +{ + u32 ret_value; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct false_alarm_statistics *falsealm_cnt = &(rtlpriv->falsealm_cnt); + unsigned long flag = 0; + + /* hold ofdm counter */ + rtl_set_bbreg(hw, ROFDM0_LSTF, BIT(31), 1); /* hold page C counter */ + rtl_set_bbreg(hw, ROFDM1_LSTF, BIT(31), 1); /*hold page D counter */ + + ret_value = rtl_get_bbreg(hw, ROFDM0_FRAMESYNC, BMASKDWORD); + falsealm_cnt->cnt_fast_fsync_fail = (ret_value & 0xffff); + falsealm_cnt->cnt_sb_search_fail = ((ret_value & 0xffff0000) >> 16); + ret_value = rtl_get_bbreg(hw, ROFDM_PHYCOUNTER1, BMASKDWORD); + falsealm_cnt->cnt_parity_fail = ((ret_value & 0xffff0000) >> 16); + ret_value = rtl_get_bbreg(hw, ROFDM_PHYCOUNTER2, BMASKDWORD); + falsealm_cnt->cnt_rate_illegal = (ret_value & 0xffff); + falsealm_cnt->cnt_crc8_fail = ((ret_value & 0xffff0000) >> 16); + ret_value = rtl_get_bbreg(hw, ROFDM_PHYCOUNTER3, BMASKDWORD); + falsealm_cnt->cnt_mcs_fail = (ret_value & 0xffff); + falsealm_cnt->cnt_ofdm_fail = falsealm_cnt->cnt_parity_fail + + falsealm_cnt->cnt_rate_illegal + + falsealm_cnt->cnt_crc8_fail + + falsealm_cnt->cnt_mcs_fail + + falsealm_cnt->cnt_fast_fsync_fail + + falsealm_cnt->cnt_sb_search_fail; + + if (rtlpriv->rtlhal.current_bandtype != BAND_ON_5G) { + /* hold cck counter */ + rtl92d_acquire_cckandrw_pagea_ctl(hw, &flag); + ret_value = rtl_get_bbreg(hw, RCCK0_FACOUNTERLOWER, BMASKBYTE0); + falsealm_cnt->cnt_cck_fail = ret_value; + ret_value = rtl_get_bbreg(hw, RCCK0_FACOUNTERUPPER, BMASKBYTE3); + falsealm_cnt->cnt_cck_fail += (ret_value & 0xff) << 8; + rtl92d_release_cckandrw_pagea_ctl(hw, &flag); + } else { + falsealm_cnt->cnt_cck_fail = 0; + } + + /* reset false alarm counter registers */ + falsealm_cnt->cnt_all = falsealm_cnt->cnt_fast_fsync_fail + + falsealm_cnt->cnt_sb_search_fail + + falsealm_cnt->cnt_parity_fail + + falsealm_cnt->cnt_rate_illegal + + falsealm_cnt->cnt_crc8_fail + + falsealm_cnt->cnt_mcs_fail + + falsealm_cnt->cnt_cck_fail; + + rtl_set_bbreg(hw, ROFDM1_LSTF, 0x08000000, 1); + /* update ofdm counter */ + rtl_set_bbreg(hw, ROFDM1_LSTF, 0x08000000, 0); + /* update page C counter */ + rtl_set_bbreg(hw, ROFDM0_LSTF, BIT(31), 0); + /* update page D counter */ + rtl_set_bbreg(hw, ROFDM1_LSTF, BIT(31), 0); + if (rtlpriv->rtlhal.current_bandtype != BAND_ON_5G) { + /* reset cck counter */ + rtl92d_acquire_cckandrw_pagea_ctl(hw, &flag); + rtl_set_bbreg(hw, RCCK0_FALSEALARMREPORT, 0x0000c000, 0); + /* enable cck counter */ + rtl_set_bbreg(hw, RCCK0_FALSEALARMREPORT, 0x0000c000, 2); + rtl92d_release_cckandrw_pagea_ctl(hw, &flag); + } + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, + "Cnt_Fast_Fsync_fail = %x, Cnt_SB_Search_fail = %x\n", + falsealm_cnt->cnt_fast_fsync_fail, + falsealm_cnt->cnt_sb_search_fail); + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, + "Cnt_Parity_Fail = %x, Cnt_Rate_Illegal = %x, Cnt_Crc8_fail = %x, Cnt_Mcs_fail = %x\n", + falsealm_cnt->cnt_parity_fail, + falsealm_cnt->cnt_rate_illegal, + falsealm_cnt->cnt_crc8_fail, + falsealm_cnt->cnt_mcs_fail); + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, + "Cnt_Ofdm_fail = %x, Cnt_Cck_fail = %x, Cnt_all = %x\n", + falsealm_cnt->cnt_ofdm_fail, + falsealm_cnt->cnt_cck_fail, + falsealm_cnt->cnt_all); +} + +static void rtl92d_dm_find_minimum_rssi(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *de_digtable = &rtlpriv->dm_digtable; + struct rtl_mac *mac = rtl_mac(rtlpriv); + + /* Determine the minimum RSSI */ + if ((mac->link_state < MAC80211_LINKED) && + (rtlpriv->dm.UNDEC_SM_PWDB == 0)) { + de_digtable->min_undec_pwdb_for_dm = 0; + RT_TRACE(rtlpriv, COMP_BB_POWERSAVING, DBG_LOUD, + "Not connected to any\n"); + } + if (mac->link_state >= MAC80211_LINKED) { + if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_ADHOC) { + de_digtable->min_undec_pwdb_for_dm = + rtlpriv->dm.UNDEC_SM_PWDB; + RT_TRACE(rtlpriv, COMP_BB_POWERSAVING, DBG_LOUD, + "AP Client PWDB = 0x%lx\n", + rtlpriv->dm.UNDEC_SM_PWDB); + } else { + de_digtable->min_undec_pwdb_for_dm = + rtlpriv->dm.undec_sm_pwdb; + RT_TRACE(rtlpriv, COMP_BB_POWERSAVING, DBG_LOUD, + "STA Default Port PWDB = 0x%x\n", + de_digtable->min_undec_pwdb_for_dm); + } + } else { + de_digtable->min_undec_pwdb_for_dm = rtlpriv->dm.UNDEC_SM_PWDB; + RT_TRACE(rtlpriv, COMP_BB_POWERSAVING, DBG_LOUD, + "AP Ext Port or disconnect PWDB = 0x%x\n", + de_digtable->min_undec_pwdb_for_dm); + } + + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, "MinUndecoratedPWDBForDM =%d\n", + de_digtable->min_undec_pwdb_for_dm); +} + +static void rtl92d_dm_cck_packet_detection_thresh(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *de_digtable = &rtlpriv->dm_digtable; + unsigned long flag = 0; + + if (de_digtable->cursta_cstate == DIG_STA_CONNECT) { + if (de_digtable->pre_cck_pd_state == CCK_PD_STAGE_LOWRSSI) { + if (de_digtable->min_undec_pwdb_for_dm <= 25) + de_digtable->cur_cck_pd_state = + CCK_PD_STAGE_LOWRSSI; + else + de_digtable->cur_cck_pd_state = + CCK_PD_STAGE_HIGHRSSI; + } else { + if (de_digtable->min_undec_pwdb_for_dm <= 20) + de_digtable->cur_cck_pd_state = + CCK_PD_STAGE_LOWRSSI; + else + de_digtable->cur_cck_pd_state = + CCK_PD_STAGE_HIGHRSSI; + } + } else { + de_digtable->cur_cck_pd_state = CCK_PD_STAGE_LOWRSSI; + } + if (de_digtable->pre_cck_pd_state != de_digtable->cur_cck_pd_state) { + if (de_digtable->cur_cck_pd_state == CCK_PD_STAGE_LOWRSSI) { + rtl92d_acquire_cckandrw_pagea_ctl(hw, &flag); + rtl_set_bbreg(hw, RCCK0_CCA, BMASKBYTE2, 0x83); + rtl92d_release_cckandrw_pagea_ctl(hw, &flag); + } else { + rtl92d_acquire_cckandrw_pagea_ctl(hw, &flag); + rtl_set_bbreg(hw, RCCK0_CCA, BMASKBYTE2, 0xcd); + rtl92d_release_cckandrw_pagea_ctl(hw, &flag); + } + de_digtable->pre_cck_pd_state = de_digtable->cur_cck_pd_state; + } + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, "CurSTAConnectState=%s\n", + de_digtable->cursta_cstate == DIG_STA_CONNECT ? + "DIG_STA_CONNECT " : "DIG_STA_DISCONNECT"); + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, "CCKPDStage=%s\n", + de_digtable->cur_cck_pd_state == CCK_PD_STAGE_LOWRSSI ? + "Low RSSI " : "High RSSI "); + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, "is92d single phy =%x\n", + IS_92D_SINGLEPHY(rtlpriv->rtlhal.version)); + +} + +void rtl92d_dm_write_dig(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *de_digtable = &rtlpriv->dm_digtable; + + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, + "cur_igvalue = 0x%x, pre_igvalue = 0x%x, back_val = %d\n", + de_digtable->cur_igvalue, de_digtable->pre_igvalue, + de_digtable->back_val); + if (de_digtable->dig_enable_flag == false) { + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, "DIG is disabled\n"); + de_digtable->pre_igvalue = 0x17; + return; + } + if (de_digtable->pre_igvalue != de_digtable->cur_igvalue) { + rtl_set_bbreg(hw, ROFDM0_XAAGCCORE1, 0x7f, + de_digtable->cur_igvalue); + rtl_set_bbreg(hw, ROFDM0_XBAGCCORE1, 0x7f, + de_digtable->cur_igvalue); + de_digtable->pre_igvalue = de_digtable->cur_igvalue; + } +} + +static void rtl92d_early_mode_enabled(struct rtl_priv *rtlpriv) +{ + struct dig_t *de_digtable = &rtlpriv->dm_digtable; + + if ((rtlpriv->mac80211.link_state >= MAC80211_LINKED) && + (rtlpriv->mac80211.vendor == PEER_CISCO)) { + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, "IOT_PEER = CISCO\n"); + if (de_digtable->last_min_undec_pwdb_for_dm >= 50 + && de_digtable->min_undec_pwdb_for_dm < 50) { + rtl_write_byte(rtlpriv, REG_EARLY_MODE_CONTROL, 0x00); + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, + "Early Mode Off\n"); + } else if (de_digtable->last_min_undec_pwdb_for_dm <= 55 && + de_digtable->min_undec_pwdb_for_dm > 55) { + rtl_write_byte(rtlpriv, REG_EARLY_MODE_CONTROL, 0x0f); + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, + "Early Mode On\n"); + } + } else if (!(rtl_read_byte(rtlpriv, REG_EARLY_MODE_CONTROL) & 0xf)) { + rtl_write_byte(rtlpriv, REG_EARLY_MODE_CONTROL, 0x0f); + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, "Early Mode On\n"); + } +} + +static void rtl92d_dm_dig(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *de_digtable = &rtlpriv->dm_digtable; + u8 value_igi = de_digtable->cur_igvalue; + struct false_alarm_statistics *falsealm_cnt = &(rtlpriv->falsealm_cnt); + + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, "==>\n"); + if (rtlpriv->rtlhal.earlymode_enable) { + rtl92d_early_mode_enabled(rtlpriv); + de_digtable->last_min_undec_pwdb_for_dm = + de_digtable->min_undec_pwdb_for_dm; + } + if (!rtlpriv->dm.dm_initialgain_enable) + return; + + /* because we will send data pkt when scanning + * this will cause some ap like gear-3700 wep TP + * lower if we retrun here, this is the diff of + * mac80211 driver vs ieee80211 driver */ + /* if (rtlpriv->mac80211.act_scanning) + * return; */ + + /* Not STA mode return tmp */ + if (rtlpriv->mac80211.opmode != NL80211_IFTYPE_STATION) + return; + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, "progress\n"); + /* Decide the current status and if modify initial gain or not */ + if (rtlpriv->mac80211.link_state >= MAC80211_LINKED) + de_digtable->cursta_cstate = DIG_STA_CONNECT; + else + de_digtable->cursta_cstate = DIG_STA_DISCONNECT; + + /* adjust initial gain according to false alarm counter */ + if (falsealm_cnt->cnt_all < DM_DIG_FA_TH0) + value_igi--; + else if (falsealm_cnt->cnt_all < DM_DIG_FA_TH1) + value_igi += 0; + else if (falsealm_cnt->cnt_all < DM_DIG_FA_TH2) + value_igi++; + else if (falsealm_cnt->cnt_all >= DM_DIG_FA_TH2) + value_igi += 2; + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, + "dm_DIG() Before: large_fa_hit=%d, forbidden_igi=%x\n", + de_digtable->large_fa_hit, de_digtable->forbidden_igi); + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, + "dm_DIG() Before: Recover_cnt=%d, rx_gain_min=%x\n", + de_digtable->recover_cnt, de_digtable->rx_gain_min); + + /* deal with abnorally large false alarm */ + if (falsealm_cnt->cnt_all > 10000) { + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, + "dm_DIG(): Abnormally false alarm case\n"); + + de_digtable->large_fa_hit++; + if (de_digtable->forbidden_igi < de_digtable->cur_igvalue) { + de_digtable->forbidden_igi = de_digtable->cur_igvalue; + de_digtable->large_fa_hit = 1; + } + if (de_digtable->large_fa_hit >= 3) { + if ((de_digtable->forbidden_igi + 1) > DM_DIG_MAX) + de_digtable->rx_gain_min = DM_DIG_MAX; + else + de_digtable->rx_gain_min = + (de_digtable->forbidden_igi + 1); + de_digtable->recover_cnt = 3600; /* 3600=2hr */ + } + } else { + /* Recovery mechanism for IGI lower bound */ + if (de_digtable->recover_cnt != 0) { + de_digtable->recover_cnt--; + } else { + if (de_digtable->large_fa_hit == 0) { + if ((de_digtable->forbidden_igi - 1) < + DM_DIG_FA_LOWER) { + de_digtable->forbidden_igi = + DM_DIG_FA_LOWER; + de_digtable->rx_gain_min = + DM_DIG_FA_LOWER; + + } else { + de_digtable->forbidden_igi--; + de_digtable->rx_gain_min = + (de_digtable->forbidden_igi + 1); + } + } else if (de_digtable->large_fa_hit == 3) { + de_digtable->large_fa_hit = 0; + } + } + } + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, + "dm_DIG() After: large_fa_hit=%d, forbidden_igi=%x\n", + de_digtable->large_fa_hit, de_digtable->forbidden_igi); + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, + "dm_DIG() After: recover_cnt=%d, rx_gain_min=%x\n", + de_digtable->recover_cnt, de_digtable->rx_gain_min); + + if (value_igi > DM_DIG_MAX) + value_igi = DM_DIG_MAX; + else if (value_igi < de_digtable->rx_gain_min) + value_igi = de_digtable->rx_gain_min; + de_digtable->cur_igvalue = value_igi; + rtl92d_dm_write_dig(hw); + if (rtlpriv->rtlhal.current_bandtype != BAND_ON_5G) + rtl92d_dm_cck_packet_detection_thresh(hw); + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, "<<==\n"); +} + +static void rtl92d_dm_init_dynamic_txpower(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.dynamic_txpower_enable = true; + rtlpriv->dm.last_dtp_lvl = TXHIGHPWRLEVEL_NORMAL; + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; +} + +static void rtl92d_dm_dynamic_txpower(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + long undec_sm_pwdb; + + if ((!rtlpriv->dm.dynamic_txpower_enable) + || rtlpriv->dm.dm_flag & HAL_DM_HIPWR_DISABLE) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + return; + } + if ((mac->link_state < MAC80211_LINKED) && + (rtlpriv->dm.UNDEC_SM_PWDB == 0)) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_TRACE, + "Not connected to any\n"); + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + rtlpriv->dm.last_dtp_lvl = TXHIGHPWRLEVEL_NORMAL; + return; + } + if (mac->link_state >= MAC80211_LINKED) { + if (mac->opmode == NL80211_IFTYPE_ADHOC) { + undec_sm_pwdb = + rtlpriv->dm.UNDEC_SM_PWDB; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "IBSS Client PWDB = 0x%lx\n", + undec_sm_pwdb); + } else { + undec_sm_pwdb = + rtlpriv->dm.undec_sm_pwdb; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "STA Default Port PWDB = 0x%lx\n", + undec_sm_pwdb); + } + } else { + undec_sm_pwdb = + rtlpriv->dm.UNDEC_SM_PWDB; + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "AP Ext Port PWDB = 0x%lx\n", + undec_sm_pwdb); + } + if (rtlhal->current_bandtype == BAND_ON_5G) { + if (undec_sm_pwdb >= 0x33) { + rtlpriv->dm.dynamic_txhighpower_lvl = + TXHIGHPWRLEVEL_LEVEL2; + RT_TRACE(rtlpriv, COMP_HIPWR, DBG_LOUD, + "5G:TxHighPwrLevel_Level2 (TxPwr=0x0)\n"); + } else if ((undec_sm_pwdb < 0x33) + && (undec_sm_pwdb >= 0x2b)) { + rtlpriv->dm.dynamic_txhighpower_lvl = + TXHIGHPWRLEVEL_LEVEL1; + RT_TRACE(rtlpriv, COMP_HIPWR, DBG_LOUD, + "5G:TxHighPwrLevel_Level1 (TxPwr=0x10)\n"); + } else if (undec_sm_pwdb < 0x2b) { + rtlpriv->dm.dynamic_txhighpower_lvl = + TXHIGHPWRLEVEL_NORMAL; + RT_TRACE(rtlpriv, COMP_HIPWR, DBG_LOUD, + "5G:TxHighPwrLevel_Normal\n"); + } + } else { + if (undec_sm_pwdb >= + TX_POWER_NEAR_FIELD_THRESH_LVL2) { + rtlpriv->dm.dynamic_txhighpower_lvl = + TXHIGHPWRLEVEL_LEVEL2; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_LEVEL1 (TxPwr=0x0)\n"); + } else + if ((undec_sm_pwdb < + (TX_POWER_NEAR_FIELD_THRESH_LVL2 - 3)) + && (undec_sm_pwdb >= + TX_POWER_NEAR_FIELD_THRESH_LVL1)) { + + rtlpriv->dm.dynamic_txhighpower_lvl = + TXHIGHPWRLEVEL_LEVEL1; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_LEVEL1 (TxPwr=0x10)\n"); + } else if (undec_sm_pwdb < + (TX_POWER_NEAR_FIELD_THRESH_LVL1 - 5)) { + rtlpriv->dm.dynamic_txhighpower_lvl = + TXHIGHPWRLEVEL_NORMAL; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_NORMAL\n"); + } + } + if ((rtlpriv->dm.dynamic_txhighpower_lvl != rtlpriv->dm.last_dtp_lvl)) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "PHY_SetTxPowerLevel8192S() Channel = %d\n", + rtlphy->current_channel); + rtl92d_phy_set_txpower_level(hw, rtlphy->current_channel); + } + rtlpriv->dm.last_dtp_lvl = rtlpriv->dm.dynamic_txhighpower_lvl; +} + +static void rtl92d_dm_pwdb_monitor(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + /* AP & ADHOC & MESH will return tmp */ + if (rtlpriv->mac80211.opmode != NL80211_IFTYPE_STATION) + return; + /* Indicate Rx signal strength to FW. */ + if (rtlpriv->dm.useramask) { + u32 temp = rtlpriv->dm.undec_sm_pwdb; + + temp <<= 16; + temp |= 0x100; + /* fw v12 cmdid 5:use max macid ,for nic , + * default macid is 0 ,max macid is 1 */ + rtl92d_fill_h2c_cmd(hw, H2C_RSSI_REPORT, 3, (u8 *) (&temp)); + } else { + rtl_write_byte(rtlpriv, 0x4fe, + (u8) rtlpriv->dm.undec_sm_pwdb); + } +} + +void rtl92d_dm_init_edca_turbo(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.current_turbo_edca = false; + rtlpriv->dm.is_any_nonbepkts = false; + rtlpriv->dm.is_cur_rdlstate = false; +} + +static void rtl92d_dm_check_edca_turbo(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + static u64 last_txok_cnt; + static u64 last_rxok_cnt; + u64 cur_txok_cnt; + u64 cur_rxok_cnt; + u32 edca_be_ul = 0x5ea42b; + u32 edca_be_dl = 0x5ea42b; + + if (mac->link_state != MAC80211_LINKED) { + rtlpriv->dm.current_turbo_edca = false; + goto exit; + } + + /* Enable BEQ TxOP limit configuration in wireless G-mode. */ + /* To check whether we shall force turn on TXOP configuration. */ + if ((!rtlpriv->dm.disable_framebursting) && + (rtlpriv->sec.pairwise_enc_algorithm == WEP40_ENCRYPTION || + rtlpriv->sec.pairwise_enc_algorithm == WEP104_ENCRYPTION || + rtlpriv->sec.pairwise_enc_algorithm == TKIP_ENCRYPTION)) { + /* Force TxOP limit to 0x005e for UL. */ + if (!(edca_be_ul & 0xffff0000)) + edca_be_ul |= 0x005e0000; + /* Force TxOP limit to 0x005e for DL. */ + if (!(edca_be_dl & 0xffff0000)) + edca_be_dl |= 0x005e0000; + } + + if ((!rtlpriv->dm.is_any_nonbepkts) && + (!rtlpriv->dm.disable_framebursting)) { + cur_txok_cnt = rtlpriv->stats.txbytesunicast - last_txok_cnt; + cur_rxok_cnt = rtlpriv->stats.rxbytesunicast - last_rxok_cnt; + if (cur_rxok_cnt > 4 * cur_txok_cnt) { + if (!rtlpriv->dm.is_cur_rdlstate || + !rtlpriv->dm.current_turbo_edca) { + rtl_write_dword(rtlpriv, REG_EDCA_BE_PARAM, + edca_be_dl); + rtlpriv->dm.is_cur_rdlstate = true; + } + } else { + if (rtlpriv->dm.is_cur_rdlstate || + !rtlpriv->dm.current_turbo_edca) { + rtl_write_dword(rtlpriv, REG_EDCA_BE_PARAM, + edca_be_ul); + rtlpriv->dm.is_cur_rdlstate = false; + } + } + rtlpriv->dm.current_turbo_edca = true; + } else { + if (rtlpriv->dm.current_turbo_edca) { + u8 tmp = AC0_BE; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_AC_PARAM, + &tmp); + rtlpriv->dm.current_turbo_edca = false; + } + } + +exit: + rtlpriv->dm.is_any_nonbepkts = false; + last_txok_cnt = rtlpriv->stats.txbytesunicast; + last_rxok_cnt = rtlpriv->stats.rxbytesunicast; +} + +static void rtl92d_dm_rxgain_tracking_thermalmeter(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 index_mapping[RX_INDEX_MAPPING_NUM] = { + 0x0f, 0x0f, 0x0d, 0x0c, 0x0b, + 0x0a, 0x09, 0x08, 0x07, 0x06, + 0x05, 0x04, 0x04, 0x03, 0x02 + }; + int i; + u32 u4tmp; + + u4tmp = (index_mapping[(rtlpriv->efuse.eeprom_thermalmeter - + rtlpriv->dm.thermalvalue_rxgain)]) << 12; + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "===> Rx Gain %x\n", u4tmp); + for (i = RF90_PATH_A; i < rtlpriv->phy.num_total_rfpath; i++) + rtl_set_rfreg(hw, i, 0x3C, BRFREGOFFSETMASK, + (rtlpriv->phy.reg_rf3c[i] & (~(0xF000))) | u4tmp); +} + +static void rtl92d_bandtype_2_4G(struct ieee80211_hw *hw, long *temp_cckg, + u8 *cck_index_old) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int i; + unsigned long flag = 0; + long temp_cck; + + /* Query CCK default setting From 0xa24 */ + rtl92d_acquire_cckandrw_pagea_ctl(hw, &flag); + temp_cck = rtl_get_bbreg(hw, RCCK0_TXFILTER2, + BMASKDWORD) & BMASKCCK; + rtl92d_release_cckandrw_pagea_ctl(hw, &flag); + for (i = 0; i < CCK_TABLE_LENGTH; i++) { + if (rtlpriv->dm.cck_inch14) { + if (!memcmp((void *)&temp_cck, + (void *)&cckswing_table_ch14[i][2], 4)) { + *cck_index_old = (u8) i; + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Initial reg0x%x = 0x%lx, cck_index=0x%x, ch 14 %d\n", + RCCK0_TXFILTER2, temp_cck, + *cck_index_old, + rtlpriv->dm.cck_inch14); + break; + } + } else { + if (!memcmp((void *) &temp_cck, + &cckswing_table_ch1ch13[i][2], 4)) { + *cck_index_old = (u8) i; + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Initial reg0x%x = 0x%lx, cck_index = 0x%x, ch14 %d\n", + RCCK0_TXFILTER2, temp_cck, + *cck_index_old, + rtlpriv->dm.cck_inch14); + break; + } + } + } + *temp_cckg = temp_cck; +} + +static void rtl92d_bandtype_5G(struct rtl_hal *rtlhal, u8 *ofdm_index, + bool *internal_pa, u8 thermalvalue, u8 delta, + u8 rf, struct rtl_efuse *rtlefuse, + struct rtl_priv *rtlpriv, struct rtl_phy *rtlphy, + u8 index_mapping[5][INDEX_MAPPING_NUM], + u8 index_mapping_pa[8][INDEX_MAPPING_NUM]) +{ + int i; + u8 index; + u8 offset = 0; + + for (i = 0; i < rf; i++) { + if (rtlhal->macphymode == DUALMAC_DUALPHY && + rtlhal->interfaceindex == 1) /* MAC 1 5G */ + *internal_pa = rtlefuse->internal_pa_5g[1]; + else + *internal_pa = rtlefuse->internal_pa_5g[i]; + if (*internal_pa) { + if (rtlhal->interfaceindex == 1 || i == rf) + offset = 4; + else + offset = 0; + if (rtlphy->current_channel >= 100 && + rtlphy->current_channel <= 165) + offset += 2; + } else { + if (rtlhal->interfaceindex == 1 || i == rf) + offset = 2; + else + offset = 0; + } + if (thermalvalue > rtlefuse->eeprom_thermalmeter) + offset++; + if (*internal_pa) { + if (delta > INDEX_MAPPING_NUM - 1) + index = index_mapping_pa[offset] + [INDEX_MAPPING_NUM - 1]; + else + index = + index_mapping_pa[offset][delta]; + } else { + if (delta > INDEX_MAPPING_NUM - 1) + index = + index_mapping[offset][INDEX_MAPPING_NUM - 1]; + else + index = index_mapping[offset][delta]; + } + if (thermalvalue > rtlefuse->eeprom_thermalmeter) { + if (*internal_pa && thermalvalue > 0x12) { + ofdm_index[i] = rtlpriv->dm.ofdm_index[i] - + ((delta / 2) * 3 + (delta % 2)); + } else { + ofdm_index[i] -= index; + } + } else { + ofdm_index[i] += index; + } + } +} + +static void rtl92d_dm_txpower_tracking_callback_thermalmeter( + struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 thermalvalue, delta, delta_lck, delta_iqk, delta_rxgain; + u8 offset, thermalvalue_avg_count = 0; + u32 thermalvalue_avg = 0; + bool internal_pa = false; + long ele_a = 0, ele_d, temp_cck, val_x, value32; + long val_y, ele_c = 0; + u8 ofdm_index[2]; + s8 cck_index = 0; + u8 ofdm_index_old[2]; + s8 cck_index_old = 0; + u8 index; + int i; + bool is2t = IS_92D_SINGLEPHY(rtlhal->version); + u8 ofdm_min_index = 6, ofdm_min_index_internal_pa = 3, rf; + u8 indexforchannel = + rtl92d_get_rightchnlplace_for_iqk(rtlphy->current_channel); + u8 index_mapping[5][INDEX_MAPPING_NUM] = { + /* 5G, path A/MAC 0, decrease power */ + {0, 1, 3, 6, 8, 9, 11, 13, 14, 16, 17, 18, 18}, + /* 5G, path A/MAC 0, increase power */ + {0, 2, 4, 5, 7, 10, 12, 14, 16, 18, 18, 18, 18}, + /* 5G, path B/MAC 1, decrease power */ + {0, 2, 3, 6, 8, 9, 11, 13, 14, 16, 17, 18, 18}, + /* 5G, path B/MAC 1, increase power */ + {0, 2, 4, 5, 7, 10, 13, 16, 16, 18, 18, 18, 18}, + /* 2.4G, for decreas power */ + {0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9, 10, 10}, + }; + u8 index_mapping_internal_pa[8][INDEX_MAPPING_NUM] = { + /* 5G, path A/MAC 0, ch36-64, decrease power */ + {0, 1, 2, 4, 6, 7, 9, 11, 12, 14, 15, 16, 16}, + /* 5G, path A/MAC 0, ch36-64, increase power */ + {0, 2, 4, 5, 7, 10, 12, 14, 16, 18, 18, 18, 18}, + /* 5G, path A/MAC 0, ch100-165, decrease power */ + {0, 1, 2, 3, 5, 6, 8, 10, 11, 13, 14, 15, 15}, + /* 5G, path A/MAC 0, ch100-165, increase power */ + {0, 2, 4, 5, 7, 10, 12, 14, 16, 18, 18, 18, 18}, + /* 5G, path B/MAC 1, ch36-64, decrease power */ + {0, 1, 2, 4, 6, 7, 9, 11, 12, 14, 15, 16, 16}, + /* 5G, path B/MAC 1, ch36-64, increase power */ + {0, 2, 4, 5, 7, 10, 13, 16, 16, 18, 18, 18, 18}, + /* 5G, path B/MAC 1, ch100-165, decrease power */ + {0, 1, 2, 3, 5, 6, 8, 9, 10, 12, 13, 14, 14}, + /* 5G, path B/MAC 1, ch100-165, increase power */ + {0, 2, 4, 5, 7, 10, 13, 16, 16, 18, 18, 18, 18}, + }; + + rtlpriv->dm.txpower_trackinginit = true; + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, "\n"); + thermalvalue = (u8) rtl_get_rfreg(hw, RF90_PATH_A, RF_T_METER, 0xf800); + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Readback Thermal Meter = 0x%x pre thermal meter 0x%x eeprom_thermalmeter 0x%x\n", + thermalvalue, + rtlpriv->dm.thermalvalue, rtlefuse->eeprom_thermalmeter); + rtl92d_phy_ap_calibrate(hw, (thermalvalue - + rtlefuse->eeprom_thermalmeter)); + if (is2t) + rf = 2; + else + rf = 1; + if (thermalvalue) { + ele_d = rtl_get_bbreg(hw, ROFDM0_XATxIQIMBALANCE, + BMASKDWORD) & BMASKOFDM_D; + for (i = 0; i < OFDM_TABLE_SIZE_92D; i++) { + if (ele_d == (ofdmswing_table[i] & BMASKOFDM_D)) { + ofdm_index_old[0] = (u8) i; + + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Initial pathA ele_d reg0x%x = 0x%lx, ofdm_index=0x%x\n", + ROFDM0_XATxIQIMBALANCE, + ele_d, ofdm_index_old[0]); + break; + } + } + if (is2t) { + ele_d = rtl_get_bbreg(hw, ROFDM0_XBTxIQIMBALANCE, + BMASKDWORD) & BMASKOFDM_D; + for (i = 0; i < OFDM_TABLE_SIZE_92D; i++) { + if (ele_d == + (ofdmswing_table[i] & BMASKOFDM_D)) { + ofdm_index_old[1] = (u8) i; + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, + DBG_LOUD, + "Initial pathB ele_d reg 0x%x = 0x%lx, ofdm_index = 0x%x\n", + ROFDM0_XBTxIQIMBALANCE, ele_d, + ofdm_index_old[1]); + break; + } + } + } + if (rtlhal->current_bandtype == BAND_ON_2_4G) { + rtl92d_bandtype_2_4G(hw, &temp_cck, &cck_index_old); + } else { + temp_cck = 0x090e1317; + cck_index_old = 12; + } + + if (!rtlpriv->dm.thermalvalue) { + rtlpriv->dm.thermalvalue = + rtlefuse->eeprom_thermalmeter; + rtlpriv->dm.thermalvalue_lck = thermalvalue; + rtlpriv->dm.thermalvalue_iqk = thermalvalue; + rtlpriv->dm.thermalvalue_rxgain = + rtlefuse->eeprom_thermalmeter; + for (i = 0; i < rf; i++) + rtlpriv->dm.ofdm_index[i] = ofdm_index_old[i]; + rtlpriv->dm.cck_index = cck_index_old; + } + if (rtlhal->reloadtxpowerindex) { + for (i = 0; i < rf; i++) + rtlpriv->dm.ofdm_index[i] = ofdm_index_old[i]; + rtlpriv->dm.cck_index = cck_index_old; + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "reload ofdm index for band switch\n"); + } + rtlpriv->dm.thermalvalue_avg + [rtlpriv->dm.thermalvalue_avg_index] = thermalvalue; + rtlpriv->dm.thermalvalue_avg_index++; + if (rtlpriv->dm.thermalvalue_avg_index == AVG_THERMAL_NUM) + rtlpriv->dm.thermalvalue_avg_index = 0; + for (i = 0; i < AVG_THERMAL_NUM; i++) { + if (rtlpriv->dm.thermalvalue_avg[i]) { + thermalvalue_avg += + rtlpriv->dm.thermalvalue_avg[i]; + thermalvalue_avg_count++; + } + } + if (thermalvalue_avg_count) + thermalvalue = (u8) (thermalvalue_avg / + thermalvalue_avg_count); + if (rtlhal->reloadtxpowerindex) { + delta = (thermalvalue > rtlefuse->eeprom_thermalmeter) ? + (thermalvalue - rtlefuse->eeprom_thermalmeter) : + (rtlefuse->eeprom_thermalmeter - thermalvalue); + rtlhal->reloadtxpowerindex = false; + rtlpriv->dm.done_txpower = false; + } else if (rtlpriv->dm.done_txpower) { + delta = (thermalvalue > rtlpriv->dm.thermalvalue) ? + (thermalvalue - rtlpriv->dm.thermalvalue) : + (rtlpriv->dm.thermalvalue - thermalvalue); + } else { + delta = (thermalvalue > rtlefuse->eeprom_thermalmeter) ? + (thermalvalue - rtlefuse->eeprom_thermalmeter) : + (rtlefuse->eeprom_thermalmeter - thermalvalue); + } + delta_lck = (thermalvalue > rtlpriv->dm.thermalvalue_lck) ? + (thermalvalue - rtlpriv->dm.thermalvalue_lck) : + (rtlpriv->dm.thermalvalue_lck - thermalvalue); + delta_iqk = (thermalvalue > rtlpriv->dm.thermalvalue_iqk) ? + (thermalvalue - rtlpriv->dm.thermalvalue_iqk) : + (rtlpriv->dm.thermalvalue_iqk - thermalvalue); + delta_rxgain = + (thermalvalue > rtlpriv->dm.thermalvalue_rxgain) ? + (thermalvalue - rtlpriv->dm.thermalvalue_rxgain) : + (rtlpriv->dm.thermalvalue_rxgain - thermalvalue); + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Readback Thermal Meter = 0x%x pre thermal meter 0x%x eeprom_thermalmeter 0x%x delta 0x%x delta_lck 0x%x delta_iqk 0x%x\n", + thermalvalue, rtlpriv->dm.thermalvalue, + rtlefuse->eeprom_thermalmeter, delta, delta_lck, + delta_iqk); + if ((delta_lck > rtlefuse->delta_lck) && + (rtlefuse->delta_lck != 0)) { + rtlpriv->dm.thermalvalue_lck = thermalvalue; + rtl92d_phy_lc_calibrate(hw); + } + if (delta > 0 && rtlpriv->dm.txpower_track_control) { + rtlpriv->dm.done_txpower = true; + delta = (thermalvalue > rtlefuse->eeprom_thermalmeter) ? + (thermalvalue - rtlefuse->eeprom_thermalmeter) : + (rtlefuse->eeprom_thermalmeter - thermalvalue); + if (rtlhal->current_bandtype == BAND_ON_2_4G) { + offset = 4; + if (delta > INDEX_MAPPING_NUM - 1) + index = index_mapping[offset] + [INDEX_MAPPING_NUM - 1]; + else + index = index_mapping[offset][delta]; + if (thermalvalue > rtlpriv->dm.thermalvalue) { + for (i = 0; i < rf; i++) + ofdm_index[i] -= delta; + cck_index -= delta; + } else { + for (i = 0; i < rf; i++) + ofdm_index[i] += index; + cck_index += index; + } + } else if (rtlhal->current_bandtype == BAND_ON_5G) { + rtl92d_bandtype_5G(rtlhal, ofdm_index, + &internal_pa, thermalvalue, + delta, rf, rtlefuse, rtlpriv, + rtlphy, index_mapping, + index_mapping_internal_pa); + } + if (is2t) { + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "temp OFDM_A_index=0x%x, OFDM_B_index = 0x%x,cck_index=0x%x\n", + rtlpriv->dm.ofdm_index[0], + rtlpriv->dm.ofdm_index[1], + rtlpriv->dm.cck_index); + } else { + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "temp OFDM_A_index=0x%x,cck_index = 0x%x\n", + rtlpriv->dm.ofdm_index[0], + rtlpriv->dm.cck_index); + } + for (i = 0; i < rf; i++) { + if (ofdm_index[i] > OFDM_TABLE_SIZE_92D - 1) + ofdm_index[i] = OFDM_TABLE_SIZE_92D - 1; + else if (ofdm_index[i] < ofdm_min_index) + ofdm_index[i] = ofdm_min_index; + } + if (rtlhal->current_bandtype == BAND_ON_2_4G) { + if (cck_index > CCK_TABLE_SIZE - 1) { + cck_index = CCK_TABLE_SIZE - 1; + } else if (internal_pa || + rtlhal->current_bandtype == + BAND_ON_2_4G) { + if (ofdm_index[i] < + ofdm_min_index_internal_pa) + ofdm_index[i] = + ofdm_min_index_internal_pa; + } else if (cck_index < 0) { + cck_index = 0; + } + } + if (is2t) { + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "new OFDM_A_index=0x%x, OFDM_B_index = 0x%x, cck_index=0x%x\n", + ofdm_index[0], ofdm_index[1], + cck_index); + } else { + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "new OFDM_A_index=0x%x,cck_index = 0x%x\n", + ofdm_index[0], cck_index); + } + ele_d = (ofdmswing_table[(u8) ofdm_index[0]] & + 0xFFC00000) >> 22; + val_x = rtlphy->iqk_matrix + [indexforchannel].value[0][0]; + val_y = rtlphy->iqk_matrix + [indexforchannel].value[0][1]; + if (val_x != 0) { + if ((val_x & 0x00000200) != 0) + val_x = val_x | 0xFFFFFC00; + ele_a = + ((val_x * ele_d) >> 8) & 0x000003FF; + + /* new element C = element D x Y */ + if ((val_y & 0x00000200) != 0) + val_y = val_y | 0xFFFFFC00; + ele_c = ((val_y * ele_d) >> 8) & 0x000003FF; + + /* wirte new elements A, C, D to regC80 and + * regC94, element B is always 0 */ + value32 = (ele_d << 22) | ((ele_c & 0x3F) << + 16) | ele_a; + rtl_set_bbreg(hw, ROFDM0_XATxIQIMBALANCE, + BMASKDWORD, value32); + + value32 = (ele_c & 0x000003C0) >> 6; + rtl_set_bbreg(hw, ROFDM0_XCTxAFE, BMASKH4BITS, + value32); + + value32 = ((val_x * ele_d) >> 7) & 0x01; + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, BIT(24), + value32); + + } else { + rtl_set_bbreg(hw, ROFDM0_XATxIQIMBALANCE, + BMASKDWORD, + ofdmswing_table + [(u8)ofdm_index[0]]); + rtl_set_bbreg(hw, ROFDM0_XCTxAFE, BMASKH4BITS, + 0x00); + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, + BIT(24), 0x00); + } + + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "TxPwrTracking for interface %d path A: X = 0x%lx, Y = 0x%lx ele_A = 0x%lx ele_C = 0x%lx ele_D = 0x%lx 0xe94 = 0x%lx 0xe9c = 0x%lx\n", + rtlhal->interfaceindex, + val_x, val_y, ele_a, ele_c, ele_d, + val_x, val_y); + + if (rtlhal->current_bandtype == BAND_ON_2_4G) { + /* Adjust CCK according to IQK result */ + if (!rtlpriv->dm.cck_inch14) { + rtl_write_byte(rtlpriv, 0xa22, + cckswing_table_ch1ch13 + [(u8)cck_index][0]); + rtl_write_byte(rtlpriv, 0xa23, + cckswing_table_ch1ch13 + [(u8)cck_index][1]); + rtl_write_byte(rtlpriv, 0xa24, + cckswing_table_ch1ch13 + [(u8)cck_index][2]); + rtl_write_byte(rtlpriv, 0xa25, + cckswing_table_ch1ch13 + [(u8)cck_index][3]); + rtl_write_byte(rtlpriv, 0xa26, + cckswing_table_ch1ch13 + [(u8)cck_index][4]); + rtl_write_byte(rtlpriv, 0xa27, + cckswing_table_ch1ch13 + [(u8)cck_index][5]); + rtl_write_byte(rtlpriv, 0xa28, + cckswing_table_ch1ch13 + [(u8)cck_index][6]); + rtl_write_byte(rtlpriv, 0xa29, + cckswing_table_ch1ch13 + [(u8)cck_index][7]); + } else { + rtl_write_byte(rtlpriv, 0xa22, + cckswing_table_ch14 + [(u8)cck_index][0]); + rtl_write_byte(rtlpriv, 0xa23, + cckswing_table_ch14 + [(u8)cck_index][1]); + rtl_write_byte(rtlpriv, 0xa24, + cckswing_table_ch14 + [(u8)cck_index][2]); + rtl_write_byte(rtlpriv, 0xa25, + cckswing_table_ch14 + [(u8)cck_index][3]); + rtl_write_byte(rtlpriv, 0xa26, + cckswing_table_ch14 + [(u8)cck_index][4]); + rtl_write_byte(rtlpriv, 0xa27, + cckswing_table_ch14 + [(u8)cck_index][5]); + rtl_write_byte(rtlpriv, 0xa28, + cckswing_table_ch14 + [(u8)cck_index][6]); + rtl_write_byte(rtlpriv, 0xa29, + cckswing_table_ch14 + [(u8)cck_index][7]); + } + } + if (is2t) { + ele_d = (ofdmswing_table[(u8) ofdm_index[1]] & + 0xFFC00000) >> 22; + val_x = rtlphy->iqk_matrix + [indexforchannel].value[0][4]; + val_y = rtlphy->iqk_matrix + [indexforchannel].value[0][5]; + if (val_x != 0) { + if ((val_x & 0x00000200) != 0) + /* consider minus */ + val_x = val_x | 0xFFFFFC00; + ele_a = ((val_x * ele_d) >> 8) & + 0x000003FF; + /* new element C = element D x Y */ + if ((val_y & 0x00000200) != 0) + val_y = + val_y | 0xFFFFFC00; + ele_c = + ((val_y * + ele_d) >> 8) & 0x00003FF; + /* write new elements A, C, D to regC88 + * and regC9C, element B is always 0 + */ + value32 = (ele_d << 22) | + ((ele_c & 0x3F) << 16) | + ele_a; + rtl_set_bbreg(hw, + ROFDM0_XBTxIQIMBALANCE, + BMASKDWORD, value32); + value32 = (ele_c & 0x000003C0) >> 6; + rtl_set_bbreg(hw, ROFDM0_XDTxAFE, + BMASKH4BITS, value32); + value32 = ((val_x * ele_d) >> 7) & 0x01; + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, + BIT(28), value32); + } else { + rtl_set_bbreg(hw, + ROFDM0_XBTxIQIMBALANCE, + BMASKDWORD, + ofdmswing_table + [(u8) ofdm_index[1]]); + rtl_set_bbreg(hw, ROFDM0_XDTxAFE, + BMASKH4BITS, 0x00); + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, + BIT(28), 0x00); + } + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "TxPwrTracking path B: X = 0x%lx, Y = 0x%lx ele_A = 0x%lx ele_C = 0x%lx ele_D = 0x%lx 0xeb4 = 0x%lx 0xebc = 0x%lx\n", + val_x, val_y, ele_a, ele_c, + ele_d, val_x, val_y); + } + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "TxPwrTracking 0xc80 = 0x%x, 0xc94 = 0x%x RF 0x24 = 0x%x\n", + rtl_get_bbreg(hw, 0xc80, BMASKDWORD), + rtl_get_bbreg(hw, 0xc94, BMASKDWORD), + rtl_get_rfreg(hw, RF90_PATH_A, 0x24, + BRFREGOFFSETMASK)); + } + if ((delta_iqk > rtlefuse->delta_iqk) && + (rtlefuse->delta_iqk != 0)) { + rtl92d_phy_reset_iqk_result(hw); + rtlpriv->dm.thermalvalue_iqk = thermalvalue; + rtl92d_phy_iq_calibrate(hw); + } + if (delta_rxgain > 0 && rtlhal->current_bandtype == BAND_ON_5G + && thermalvalue <= rtlefuse->eeprom_thermalmeter) { + rtlpriv->dm.thermalvalue_rxgain = thermalvalue; + rtl92d_dm_rxgain_tracking_thermalmeter(hw); + } + if (rtlpriv->dm.txpower_track_control) + rtlpriv->dm.thermalvalue = thermalvalue; + } + + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, "<===\n"); +} + +static void rtl92d_dm_initialize_txpower_tracking(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.txpower_tracking = true; + rtlpriv->dm.txpower_trackinginit = false; + rtlpriv->dm.txpower_track_control = true; + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "pMgntInfo->txpower_tracking = %d\n", + rtlpriv->dm.txpower_tracking); +} + +void rtl92d_dm_check_txpower_tracking_thermal_meter(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + static u8 tm_trigger; + + if (!rtlpriv->dm.txpower_tracking) + return; + + if (!tm_trigger) { + rtl_set_rfreg(hw, RF90_PATH_A, RF_T_METER, BIT(17) | + BIT(16), 0x03); + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Trigger 92S Thermal Meter!!\n"); + tm_trigger = 1; + return; + } else { + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Schedule TxPowerTracking direct call!!\n"); + rtl92d_dm_txpower_tracking_callback_thermalmeter(hw); + tm_trigger = 0; + } +} + +void rtl92d_dm_init_rate_adaptive_mask(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rate_adaptive *ra = &(rtlpriv->ra); + + ra->ratr_state = DM_RATR_STA_INIT; + ra->pre_ratr_state = DM_RATR_STA_INIT; + if (rtlpriv->dm.dm_type == DM_TYPE_BYDRIVER) + rtlpriv->dm.useramask = true; + else + rtlpriv->dm.useramask = false; +} + +void rtl92d_dm_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.dm_type = DM_TYPE_BYDRIVER; + rtl92d_dm_diginit(hw); + rtl92d_dm_init_dynamic_txpower(hw); + rtl92d_dm_init_edca_turbo(hw); + rtl92d_dm_init_rate_adaptive_mask(hw); + rtl92d_dm_initialize_txpower_tracking(hw); +} + +void rtl92d_dm_watchdog(struct ieee80211_hw *hw) +{ + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + bool fw_current_inpsmode = false; + bool fwps_awake = true; + + /* 1. RF is OFF. (No need to do DM.) + * 2. Fw is under power saving mode for FwLPS. + * (Prevent from SW/FW I/O racing.) + * 3. IPS workitem is scheduled. (Prevent from IPS sequence + * to be swapped with DM. + * 4. RFChangeInProgress is TRUE. + * (Prevent from broken by IPS/HW/SW Rf off.) */ + + if ((ppsc->rfpwr_state == ERFON) && ((!fw_current_inpsmode) && + fwps_awake) && (!ppsc->rfchange_inprogress)) { + rtl92d_dm_pwdb_monitor(hw); + rtl92d_dm_false_alarm_counter_statistics(hw); + rtl92d_dm_find_minimum_rssi(hw); + rtl92d_dm_dig(hw); + /* rtl92d_dm_dynamic_bb_powersaving(hw); */ + rtl92d_dm_dynamic_txpower(hw); + /* rtl92d_dm_check_txpower_tracking_thermal_meter(hw); */ + /* rtl92d_dm_refresh_rate_adaptive_mask(hw); */ + /* rtl92d_dm_interrupt_migration(hw); */ + rtl92d_dm_check_edca_turbo(hw); + } +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/dm.h b/drivers/net/wireless/rtlwifi/rtl8192de/dm.h new file mode 100644 index 0000000..3fea0c1 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/dm.h @@ -0,0 +1,161 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92C_DM_H__ +#define __RTL92C_DM_H__ + +#define HAL_DM_DIG_DISABLE BIT(0) +#define HAL_DM_HIPWR_DISABLE BIT(1) + +#define OFDM_TABLE_LENGTH 37 +#define OFDM_TABLE_SIZE_92D 43 +#define CCK_TABLE_LENGTH 33 + +#define CCK_TABLE_SIZE 33 + +#define BW_AUTO_SWITCH_HIGH_LOW 25 +#define BW_AUTO_SWITCH_LOW_HIGH 30 + +#define DM_DIG_THRESH_HIGH 40 +#define DM_DIG_THRESH_LOW 35 + +#define DM_FALSEALARM_THRESH_LOW 400 +#define DM_FALSEALARM_THRESH_HIGH 1000 + +#define DM_DIG_MAX 0x3e +#define DM_DIG_MIN 0x1c + +#define DM_DIG_FA_UPPER 0x32 +#define DM_DIG_FA_LOWER 0x20 +#define DM_DIG_FA_TH0 0x100 +#define DM_DIG_FA_TH1 0x400 +#define DM_DIG_FA_TH2 0x600 + +#define DM_DIG_BACKOFF_MAX 12 +#define DM_DIG_BACKOFF_MIN -4 +#define DM_DIG_BACKOFF_DEFAULT 10 + +#define RXPATHSELECTION_SS_TH_lOW 30 +#define RXPATHSELECTION_DIFF_TH 18 + +#define DM_RATR_STA_INIT 0 +#define DM_RATR_STA_HIGH 1 +#define DM_RATR_STA_MIDDLE 2 +#define DM_RATR_STA_LOW 3 + +#define CTS2SELF_THVAL 30 +#define REGC38_TH 20 + +#define WAIOTTHVAL 25 + +#define TXHIGHPWRLEVEL_NORMAL 0 +#define TXHIGHPWRLEVEL_LEVEL1 1 +#define TXHIGHPWRLEVEL_LEVEL2 2 +#define TXHIGHPWRLEVEL_BT1 3 +#define TXHIGHPWRLEVEL_BT2 4 + +#define DM_TYPE_BYFW 0 +#define DM_TYPE_BYDRIVER 1 + +#define TX_POWER_NEAR_FIELD_THRESH_LVL2 74 +#define TX_POWER_NEAR_FIELD_THRESH_LVL1 67 +#define INDEX_MAPPING_NUM 13 + +struct swat { + u8 failure_cnt; + u8 try_flag; + u8 stop_trying; + long pre_rssi; + long trying_threshold; + u8 cur_antenna; + u8 pre_antenna; +}; + +enum tag_dynamic_init_gain_operation_type_definition { + DIG_TYPE_THRESH_HIGH = 0, + DIG_TYPE_THRESH_LOW = 1, + DIG_TYPE_BACKOFF = 2, + DIG_TYPE_RX_GAIN_MIN = 3, + DIG_TYPE_RX_GAIN_MAX = 4, + DIG_TYPE_ENABLE = 5, + DIG_TYPE_DISABLE = 6, + DIG_OP_TYPE_MAX +}; + +enum tag_cck_packet_detection_threshold_type_definition { + CCK_PD_STAGE_LOWRSSI = 0, + CCK_PD_STAGE_HIGHRSSI = 1, + CCK_FA_STAGE_LOW = 2, + CCK_FA_STAGE_HIGH = 3, + CCK_PD_STAGE_MAX = 4, +}; + +enum dm_1r_cca { + CCA_1R = 0, + CCA_2R = 1, + CCA_MAX = 2, +}; + +enum dm_rf { + RF_SAVE = 0, + RF_NORMAL = 1, + RF_MAX = 2, +}; + +enum dm_sw_ant_switch { + ANS_ANTENNA_B = 1, + ANS_ANTENNA_A = 2, + ANS_ANTENNA_MAX = 3, +}; + +enum dm_dig_ext_port_alg { + DIG_EXT_PORT_STAGE_0 = 0, + DIG_EXT_PORT_STAGE_1 = 1, + DIG_EXT_PORT_STAGE_2 = 2, + DIG_EXT_PORT_STAGE_3 = 3, + DIG_EXT_PORT_STAGE_MAX = 4, +}; + +enum dm_dig_connect { + DIG_STA_DISCONNECT = 0, + DIG_STA_CONNECT = 1, + DIG_STA_BEFORE_CONNECT = 2, + DIG_MULTISTA_DISCONNECT = 3, + DIG_MULTISTA_CONNECT = 4, + DIG_CONNECT_MAX +}; + +void rtl92d_dm_init(struct ieee80211_hw *hw); +void rtl92d_dm_watchdog(struct ieee80211_hw *hw); +void rtl92d_dm_init_edca_turbo(struct ieee80211_hw *hw); +void rtl92d_dm_write_dig(struct ieee80211_hw *hw); +void rtl92d_dm_check_txpower_tracking_thermal_meter(struct ieee80211_hw *hw); +void rtl92d_dm_init_rate_adaptive_mask(struct ieee80211_hw *hw); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/fw.c b/drivers/net/wireless/rtlwifi/rtl8192de/fw.c new file mode 100644 index 0000000..2317707 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/fw.c @@ -0,0 +1,780 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "../base.h" +#include "reg.h" +#include "def.h" +#include "fw.h" +#include "sw.h" + +static bool _rtl92d_is_fw_downloaded(struct rtl_priv *rtlpriv) +{ + return (rtl_read_dword(rtlpriv, REG_MCUFWDL) & MCUFWDL_RDY) ? + true : false; +} + +static void _rtl92d_enable_fw_download(struct ieee80211_hw *hw, bool enable) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tmp; + + if (enable) { + tmp = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN + 1); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN + 1, tmp | 0x04); + tmp = rtl_read_byte(rtlpriv, REG_MCUFWDL); + rtl_write_byte(rtlpriv, REG_MCUFWDL, tmp | 0x01); + tmp = rtl_read_byte(rtlpriv, REG_MCUFWDL + 2); + rtl_write_byte(rtlpriv, REG_MCUFWDL + 2, tmp & 0xf7); + } else { + tmp = rtl_read_byte(rtlpriv, REG_MCUFWDL); + rtl_write_byte(rtlpriv, REG_MCUFWDL, tmp & 0xfe); + /* Reserved for fw extension. + * 0x81[7] is used for mac0 status , + * so don't write this reg here + * rtl_write_byte(rtlpriv, REG_MCUFWDL + 1, 0x00);*/ + } +} + +static void _rtl92d_fw_block_write(struct ieee80211_hw *hw, + const u8 *buffer, u32 size) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 blocksize = sizeof(u32); + u8 *bufferptr = (u8 *) buffer; + u32 *pu4BytePtr = (u32 *) buffer; + u32 i, offset, blockCount, remainSize; + + blockCount = size / blocksize; + remainSize = size % blocksize; + for (i = 0; i < blockCount; i++) { + offset = i * blocksize; + rtl_write_dword(rtlpriv, (FW_8192D_START_ADDRESS + offset), + *(pu4BytePtr + i)); + } + if (remainSize) { + offset = blockCount * blocksize; + bufferptr += offset; + for (i = 0; i < remainSize; i++) { + rtl_write_byte(rtlpriv, (FW_8192D_START_ADDRESS + + offset + i), *(bufferptr + i)); + } + } +} + +static void _rtl92d_fw_page_write(struct ieee80211_hw *hw, + u32 page, const u8 *buffer, u32 size) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 value8; + u8 u8page = (u8) (page & 0x07); + + value8 = (rtl_read_byte(rtlpriv, REG_MCUFWDL + 2) & 0xF8) | u8page; + rtl_write_byte(rtlpriv, (REG_MCUFWDL + 2), value8); + _rtl92d_fw_block_write(hw, buffer, size); +} + +static void _rtl92d_fill_dummy(u8 *pfwbuf, u32 *pfwlen) +{ + u32 fwlen = *pfwlen; + u8 remain = (u8) (fwlen % 4); + + remain = (remain == 0) ? 0 : (4 - remain); + while (remain > 0) { + pfwbuf[fwlen] = 0; + fwlen++; + remain--; + } + *pfwlen = fwlen; +} + +static void _rtl92d_write_fw(struct ieee80211_hw *hw, + enum version_8192d version, u8 *buffer, u32 size) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 *bufferPtr = buffer; + u32 pagenums, remainSize; + u32 page, offset; + + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, "FW size is %d bytes,\n", size); + if (rtlhal->hw_type == HARDWARE_TYPE_RTL8192DE) + _rtl92d_fill_dummy(bufferPtr, &size); + pagenums = size / FW_8192D_PAGE_SIZE; + remainSize = size % FW_8192D_PAGE_SIZE; + if (pagenums > 8) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Page numbers should not greater then 8\n"); + } + for (page = 0; page < pagenums; page++) { + offset = page * FW_8192D_PAGE_SIZE; + _rtl92d_fw_page_write(hw, page, (bufferPtr + offset), + FW_8192D_PAGE_SIZE); + } + if (remainSize) { + offset = pagenums * FW_8192D_PAGE_SIZE; + page = pagenums; + _rtl92d_fw_page_write(hw, page, (bufferPtr + offset), + remainSize); + } +} + +static int _rtl92d_fw_free_to_go(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 counter = 0; + u32 value32; + + do { + value32 = rtl_read_dword(rtlpriv, REG_MCUFWDL); + } while ((counter++ < FW_8192D_POLLING_TIMEOUT_COUNT) && + (!(value32 & FWDL_ChkSum_rpt))); + if (counter >= FW_8192D_POLLING_TIMEOUT_COUNT) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "chksum report faill ! REG_MCUFWDL:0x%08x\n", + value32); + return -EIO; + } + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, + "Checksum report OK ! REG_MCUFWDL:0x%08x\n", value32); + value32 = rtl_read_dword(rtlpriv, REG_MCUFWDL); + value32 |= MCUFWDL_RDY; + rtl_write_dword(rtlpriv, REG_MCUFWDL, value32); + return 0; +} + +void rtl92d_firmware_selfreset(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 u1b_tmp; + u8 delay = 100; + + /* Set (REG_HMETFR + 3) to 0x20 is reset 8051 */ + rtl_write_byte(rtlpriv, REG_HMETFR + 3, 0x20); + u1b_tmp = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN + 1); + while (u1b_tmp & BIT(2)) { + delay--; + if (delay == 0) + break; + udelay(50); + u1b_tmp = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN + 1); + } + RT_ASSERT((delay > 0), "8051 reset failed!\n"); + RT_TRACE(rtlpriv, COMP_FW, DBG_DMESG, + "=====> 8051 reset success (%d)\n", delay); +} + +static int _rtl92d_fw_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u32 counter; + + RT_TRACE(rtlpriv, COMP_FW, DBG_DMESG, "FW already have download\n"); + /* polling for FW ready */ + counter = 0; + do { + if (rtlhal->interfaceindex == 0) { + if (rtl_read_byte(rtlpriv, FW_MAC0_READY) & + MAC0_READY) { + RT_TRACE(rtlpriv, COMP_FW, DBG_DMESG, + "Polling FW ready success!! REG_MCUFWDL: 0x%x\n", + rtl_read_byte(rtlpriv, + FW_MAC0_READY)); + return 0; + } + udelay(5); + } else { + if (rtl_read_byte(rtlpriv, FW_MAC1_READY) & + MAC1_READY) { + RT_TRACE(rtlpriv, COMP_FW, DBG_DMESG, + "Polling FW ready success!! REG_MCUFWDL: 0x%x\n", + rtl_read_byte(rtlpriv, + FW_MAC1_READY)); + return 0; + } + udelay(5); + } + } while (counter++ < POLLING_READY_TIMEOUT_COUNT); + + if (rtlhal->interfaceindex == 0) { + RT_TRACE(rtlpriv, COMP_FW, DBG_DMESG, + "Polling FW ready fail!! MAC0 FW init not ready: 0x%x\n", + rtl_read_byte(rtlpriv, FW_MAC0_READY)); + } else { + RT_TRACE(rtlpriv, COMP_FW, DBG_DMESG, + "Polling FW ready fail!! MAC1 FW init not ready: 0x%x\n", + rtl_read_byte(rtlpriv, FW_MAC1_READY)); + } + RT_TRACE(rtlpriv, COMP_FW, DBG_DMESG, + "Polling FW ready fail!! REG_MCUFWDL:0x%08ul\n", + rtl_read_dword(rtlpriv, REG_MCUFWDL)); + return -1; +} + +int rtl92d_download_fw(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 *pfwheader; + u8 *pfwdata; + u32 fwsize; + int err; + enum version_8192d version = rtlhal->version; + u8 value; + u32 count; + bool fw_downloaded = false, fwdl_in_process = false; + unsigned long flags; + + if (rtlpriv->max_fw_size == 0 || !rtlhal->pfirmware) + return 1; + fwsize = rtlhal->fwsize; + pfwheader = rtlhal->pfirmware; + pfwdata = rtlhal->pfirmware; + rtlhal->fw_version = (u16) GET_FIRMWARE_HDR_VERSION(pfwheader); + rtlhal->fw_subversion = (u16) GET_FIRMWARE_HDR_SUB_VER(pfwheader); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "FirmwareVersion(%d), FirmwareSubVersion(%d), Signature(%#x)\n", + rtlhal->fw_version, rtlhal->fw_subversion, + GET_FIRMWARE_HDR_SIGNATURE(pfwheader)); + if (IS_FW_HEADER_EXIST(pfwheader)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Shift 32 bytes for FW header!!\n"); + pfwdata = pfwdata + 32; + fwsize = fwsize - 32; + } + + spin_lock_irqsave(&globalmutex_for_fwdownload, flags); + fw_downloaded = _rtl92d_is_fw_downloaded(rtlpriv); + if ((rtl_read_byte(rtlpriv, 0x1f) & BIT(5)) == BIT(5)) + fwdl_in_process = true; + else + fwdl_in_process = false; + if (fw_downloaded) { + spin_unlock_irqrestore(&globalmutex_for_fwdownload, flags); + goto exit; + } else if (fwdl_in_process) { + spin_unlock_irqrestore(&globalmutex_for_fwdownload, flags); + for (count = 0; count < 5000; count++) { + udelay(500); + spin_lock_irqsave(&globalmutex_for_fwdownload, flags); + fw_downloaded = _rtl92d_is_fw_downloaded(rtlpriv); + if ((rtl_read_byte(rtlpriv, 0x1f) & BIT(5)) == BIT(5)) + fwdl_in_process = true; + else + fwdl_in_process = false; + spin_unlock_irqrestore(&globalmutex_for_fwdownload, + flags); + if (fw_downloaded) + goto exit; + else if (!fwdl_in_process) + break; + else + RT_TRACE(rtlpriv, COMP_FW, DBG_DMESG, + "Wait for another mac download fw\n"); + } + spin_lock_irqsave(&globalmutex_for_fwdownload, flags); + value = rtl_read_byte(rtlpriv, 0x1f); + value |= BIT(5); + rtl_write_byte(rtlpriv, 0x1f, value); + spin_unlock_irqrestore(&globalmutex_for_fwdownload, flags); + } else { + value = rtl_read_byte(rtlpriv, 0x1f); + value |= BIT(5); + rtl_write_byte(rtlpriv, 0x1f, value); + spin_unlock_irqrestore(&globalmutex_for_fwdownload, flags); + } + + /* If 8051 is running in RAM code, driver should + * inform Fw to reset by itself, or it will cause + * download Fw fail.*/ + /* 8051 RAM code */ + if (rtl_read_byte(rtlpriv, REG_MCUFWDL) & BIT(7)) { + rtl92d_firmware_selfreset(hw); + rtl_write_byte(rtlpriv, REG_MCUFWDL, 0x00); + } + _rtl92d_enable_fw_download(hw, true); + _rtl92d_write_fw(hw, version, pfwdata, fwsize); + _rtl92d_enable_fw_download(hw, false); + spin_lock_irqsave(&globalmutex_for_fwdownload, flags); + err = _rtl92d_fw_free_to_go(hw); + /* download fw over,clear 0x1f[5] */ + value = rtl_read_byte(rtlpriv, 0x1f); + value &= (~BIT(5)); + rtl_write_byte(rtlpriv, 0x1f, value); + spin_unlock_irqrestore(&globalmutex_for_fwdownload, flags); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "fw is not ready to run!\n"); + goto exit; + } else { + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, "fw is ready to run!\n"); + } +exit: + err = _rtl92d_fw_init(hw); + return err; +} + +static bool _rtl92d_check_fw_read_last_h2c(struct ieee80211_hw *hw, u8 boxnum) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 val_hmetfr; + bool result = false; + + val_hmetfr = rtl_read_byte(rtlpriv, REG_HMETFR); + if (((val_hmetfr >> boxnum) & BIT(0)) == 0) + result = true; + return result; +} + +static void _rtl92d_fill_h2c_command(struct ieee80211_hw *hw, + u8 element_id, u32 cmd_len, u8 *cmdbuffer) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + u8 boxnum; + u16 box_reg = 0, box_extreg = 0; + u8 u1b_tmp; + bool isfw_read = false; + u8 buf_index = 0; + bool bwrite_success = false; + u8 wait_h2c_limmit = 100; + u8 wait_writeh2c_limmit = 100; + u8 boxcontent[4], boxextcontent[2]; + u32 h2c_waitcounter = 0; + unsigned long flag; + u8 idx; + + if (ppsc->rfpwr_state == ERFOFF || ppsc->inactive_pwrstate == ERFOFF) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Return as RF is off!!!\n"); + return; + } + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "come in\n"); + while (true) { + spin_lock_irqsave(&rtlpriv->locks.h2c_lock, flag); + if (rtlhal->h2c_setinprogress) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "H2C set in progress! Wait to set..element_id(%d)\n", + element_id); + + while (rtlhal->h2c_setinprogress) { + spin_unlock_irqrestore(&rtlpriv->locks.h2c_lock, + flag); + h2c_waitcounter++; + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Wait 100 us (%d times)...\n", + h2c_waitcounter); + udelay(100); + + if (h2c_waitcounter > 1000) + return; + + spin_lock_irqsave(&rtlpriv->locks.h2c_lock, + flag); + } + spin_unlock_irqrestore(&rtlpriv->locks.h2c_lock, flag); + } else { + rtlhal->h2c_setinprogress = true; + spin_unlock_irqrestore(&rtlpriv->locks.h2c_lock, flag); + break; + } + } + while (!bwrite_success) { + wait_writeh2c_limmit--; + if (wait_writeh2c_limmit == 0) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Write H2C fail because no trigger for FW INT!\n"); + break; + } + boxnum = rtlhal->last_hmeboxnum; + switch (boxnum) { + case 0: + box_reg = REG_HMEBOX_0; + box_extreg = REG_HMEBOX_EXT_0; + break; + case 1: + box_reg = REG_HMEBOX_1; + box_extreg = REG_HMEBOX_EXT_1; + break; + case 2: + box_reg = REG_HMEBOX_2; + box_extreg = REG_HMEBOX_EXT_2; + break; + case 3: + box_reg = REG_HMEBOX_3; + box_extreg = REG_HMEBOX_EXT_3; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + isfw_read = _rtl92d_check_fw_read_last_h2c(hw, boxnum); + while (!isfw_read) { + wait_h2c_limmit--; + if (wait_h2c_limmit == 0) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Waiting too long for FW read clear HMEBox(%d)!\n", + boxnum); + break; + } + udelay(10); + isfw_read = _rtl92d_check_fw_read_last_h2c(hw, boxnum); + u1b_tmp = rtl_read_byte(rtlpriv, 0x1BF); + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Waiting for FW read clear HMEBox(%d)!!! 0x1BF = %2x\n", + boxnum, u1b_tmp); + } + if (!isfw_read) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Write H2C register BOX[%d] fail!!!!! Fw do not read.\n", + boxnum); + break; + } + memset(boxcontent, 0, sizeof(boxcontent)); + memset(boxextcontent, 0, sizeof(boxextcontent)); + boxcontent[0] = element_id; + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Write element_id box_reg(%4x) = %2x\n", + box_reg, element_id); + switch (cmd_len) { + case 1: + boxcontent[0] &= ~(BIT(7)); + memcpy(boxcontent + 1, cmdbuffer + buf_index, 1); + for (idx = 0; idx < 4; idx++) + rtl_write_byte(rtlpriv, box_reg + idx, + boxcontent[idx]); + break; + case 2: + boxcontent[0] &= ~(BIT(7)); + memcpy(boxcontent + 1, cmdbuffer + buf_index, 2); + for (idx = 0; idx < 4; idx++) + rtl_write_byte(rtlpriv, box_reg + idx, + boxcontent[idx]); + break; + case 3: + boxcontent[0] &= ~(BIT(7)); + memcpy(boxcontent + 1, cmdbuffer + buf_index, 3); + for (idx = 0; idx < 4; idx++) + rtl_write_byte(rtlpriv, box_reg + idx, + boxcontent[idx]); + break; + case 4: + boxcontent[0] |= (BIT(7)); + memcpy(boxextcontent, cmdbuffer + buf_index, 2); + memcpy(boxcontent + 1, cmdbuffer + buf_index + 2, 2); + for (idx = 0; idx < 2; idx++) + rtl_write_byte(rtlpriv, box_extreg + idx, + boxextcontent[idx]); + for (idx = 0; idx < 4; idx++) + rtl_write_byte(rtlpriv, box_reg + idx, + boxcontent[idx]); + break; + case 5: + boxcontent[0] |= (BIT(7)); + memcpy(boxextcontent, cmdbuffer + buf_index, 2); + memcpy(boxcontent + 1, cmdbuffer + buf_index + 2, 3); + for (idx = 0; idx < 2; idx++) + rtl_write_byte(rtlpriv, box_extreg + idx, + boxextcontent[idx]); + for (idx = 0; idx < 4; idx++) + rtl_write_byte(rtlpriv, box_reg + idx, + boxcontent[idx]); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + bwrite_success = true; + rtlhal->last_hmeboxnum = boxnum + 1; + if (rtlhal->last_hmeboxnum == 4) + rtlhal->last_hmeboxnum = 0; + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "pHalData->last_hmeboxnum = %d\n", + rtlhal->last_hmeboxnum); + } + spin_lock_irqsave(&rtlpriv->locks.h2c_lock, flag); + rtlhal->h2c_setinprogress = false; + spin_unlock_irqrestore(&rtlpriv->locks.h2c_lock, flag); + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "go out\n"); +} + +void rtl92d_fill_h2c_cmd(struct ieee80211_hw *hw, + u8 element_id, u32 cmd_len, u8 *cmdbuffer) +{ + u32 tmp_cmdbuf[2]; + + memset(tmp_cmdbuf, 0, 8); + memcpy(tmp_cmdbuf, cmdbuffer, cmd_len); + _rtl92d_fill_h2c_command(hw, element_id, cmd_len, (u8 *)&tmp_cmdbuf); + return; +} + +void rtl92d_set_fw_pwrmode_cmd(struct ieee80211_hw *hw, u8 mode) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 u1_h2c_set_pwrmode[3] = { 0 }; + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, "FW LPS mode = %d\n", mode); + SET_H2CCMD_PWRMODE_PARM_MODE(u1_h2c_set_pwrmode, mode); + SET_H2CCMD_PWRMODE_PARM_SMART_PS(u1_h2c_set_pwrmode, 1); + SET_H2CCMD_PWRMODE_PARM_BCN_PASS_TIME(u1_h2c_set_pwrmode, + ppsc->reg_max_lps_awakeintvl); + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_DMESG, + "rtl92d_set_fw_rsvdpagepkt(): u1_h2c_set_pwrmode", + u1_h2c_set_pwrmode, 3); + rtl92d_fill_h2c_cmd(hw, H2C_SETPWRMODE, 3, u1_h2c_set_pwrmode); +} + +static bool _rtl92d_cmd_send_packet(struct ieee80211_hw *hw, + struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl8192_tx_ring *ring; + struct rtl_tx_desc *pdesc; + u8 idx = 0; + unsigned long flags; + struct sk_buff *pskb; + + ring = &rtlpci->tx_ring[BEACON_QUEUE]; + pskb = __skb_dequeue(&ring->queue); + kfree_skb(pskb); + spin_lock_irqsave(&rtlpriv->locks.irq_th_lock, flags); + pdesc = &ring->desc[idx]; + /* discard output from call below */ + rtlpriv->cfg->ops->get_desc((u8 *) pdesc, true, HW_DESC_OWN); + rtlpriv->cfg->ops->fill_tx_cmddesc(hw, (u8 *) pdesc, 1, 1, skb); + __skb_queue_tail(&ring->queue, skb); + spin_unlock_irqrestore(&rtlpriv->locks.irq_th_lock, flags); + rtlpriv->cfg->ops->tx_polling(hw, BEACON_QUEUE); + return true; +} + +#define BEACON_PG 0 /*->1 */ +#define PSPOLL_PG 2 +#define NULL_PG 3 +#define PROBERSP_PG 4 /*->5 */ +#define TOTAL_RESERVED_PKT_LEN 768 + +static u8 reserved_page_packet[TOTAL_RESERVED_PKT_LEN] = { + /* page 0 beacon */ + 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xE0, 0x4C, 0x76, 0x00, 0x42, + 0x00, 0x40, 0x10, 0x10, 0x00, 0x03, 0x50, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x64, 0x00, 0x00, 0x04, 0x00, 0x0C, 0x6C, 0x69, + 0x6E, 0x6B, 0x73, 0x79, 0x73, 0x5F, 0x77, 0x6C, + 0x61, 0x6E, 0x01, 0x04, 0x82, 0x84, 0x8B, 0x96, + 0x03, 0x01, 0x01, 0x06, 0x02, 0x00, 0x00, 0x2A, + 0x01, 0x00, 0x32, 0x08, 0x24, 0x30, 0x48, 0x6C, + 0x0C, 0x12, 0x18, 0x60, 0x2D, 0x1A, 0x6C, 0x18, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3D, 0x00, 0xDD, 0x06, 0x00, 0xE0, 0x4C, 0x02, + 0x01, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 1 beacon */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x20, 0x8C, 0x00, 0x12, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 2 ps-poll */ + 0xA4, 0x10, 0x01, 0xC0, 0x00, 0x40, 0x10, 0x10, + 0x00, 0x03, 0x00, 0xE0, 0x4C, 0x76, 0x00, 0x42, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x20, 0x8C, 0x00, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 3 null */ + 0x48, 0x01, 0x00, 0x00, 0x00, 0x40, 0x10, 0x10, + 0x00, 0x03, 0x00, 0xE0, 0x4C, 0x76, 0x00, 0x42, + 0x00, 0x40, 0x10, 0x10, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x72, 0x00, 0x20, 0x8C, 0x00, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 4 probe_resp */ + 0x50, 0x00, 0x00, 0x00, 0x00, 0x40, 0x10, 0x10, + 0x00, 0x03, 0x00, 0xE0, 0x4C, 0x76, 0x00, 0x42, + 0x00, 0x40, 0x10, 0x10, 0x00, 0x03, 0x00, 0x00, + 0x9E, 0x46, 0x15, 0x32, 0x27, 0xF2, 0x2D, 0x00, + 0x64, 0x00, 0x00, 0x04, 0x00, 0x0C, 0x6C, 0x69, + 0x6E, 0x6B, 0x73, 0x79, 0x73, 0x5F, 0x77, 0x6C, + 0x61, 0x6E, 0x01, 0x04, 0x82, 0x84, 0x8B, 0x96, + 0x03, 0x01, 0x01, 0x06, 0x02, 0x00, 0x00, 0x2A, + 0x01, 0x00, 0x32, 0x08, 0x24, 0x30, 0x48, 0x6C, + 0x0C, 0x12, 0x18, 0x60, 0x2D, 0x1A, 0x6C, 0x18, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3D, 0x00, 0xDD, 0x06, 0x00, 0xE0, 0x4C, 0x02, + 0x01, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 5 probe_resp */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +void rtl92d_set_fw_rsvdpagepkt(struct ieee80211_hw *hw, bool dl_finished) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct sk_buff *skb = NULL; + u32 totalpacketlen; + bool rtstatus; + u8 u1RsvdPageLoc[3] = { 0 }; + bool dlok = false; + u8 *beacon; + u8 *p_pspoll; + u8 *nullfunc; + u8 *p_probersp; + /*--------------------------------------------------------- + (1) beacon + ---------------------------------------------------------*/ + beacon = &reserved_page_packet[BEACON_PG * 128]; + SET_80211_HDR_ADDRESS2(beacon, mac->mac_addr); + SET_80211_HDR_ADDRESS3(beacon, mac->bssid); + /*------------------------------------------------------- + (2) ps-poll + --------------------------------------------------------*/ + p_pspoll = &reserved_page_packet[PSPOLL_PG * 128]; + SET_80211_PS_POLL_AID(p_pspoll, (mac->assoc_id | 0xc000)); + SET_80211_PS_POLL_BSSID(p_pspoll, mac->bssid); + SET_80211_PS_POLL_TA(p_pspoll, mac->mac_addr); + SET_H2CCMD_RSVDPAGE_LOC_PSPOLL(u1RsvdPageLoc, PSPOLL_PG); + /*-------------------------------------------------------- + (3) null data + ---------------------------------------------------------*/ + nullfunc = &reserved_page_packet[NULL_PG * 128]; + SET_80211_HDR_ADDRESS1(nullfunc, mac->bssid); + SET_80211_HDR_ADDRESS2(nullfunc, mac->mac_addr); + SET_80211_HDR_ADDRESS3(nullfunc, mac->bssid); + SET_H2CCMD_RSVDPAGE_LOC_NULL_DATA(u1RsvdPageLoc, NULL_PG); + /*--------------------------------------------------------- + (4) probe response + ----------------------------------------------------------*/ + p_probersp = &reserved_page_packet[PROBERSP_PG * 128]; + SET_80211_HDR_ADDRESS1(p_probersp, mac->bssid); + SET_80211_HDR_ADDRESS2(p_probersp, mac->mac_addr); + SET_80211_HDR_ADDRESS3(p_probersp, mac->bssid); + SET_H2CCMD_RSVDPAGE_LOC_PROBE_RSP(u1RsvdPageLoc, PROBERSP_PG); + totalpacketlen = TOTAL_RESERVED_PKT_LEN; + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_LOUD, + "rtl92d_set_fw_rsvdpagepkt(): HW_VAR_SET_TX_CMD: ALL", + &reserved_page_packet[0], totalpacketlen); + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_DMESG, + "rtl92d_set_fw_rsvdpagepkt(): HW_VAR_SET_TX_CMD: ALL", + u1RsvdPageLoc, 3); + skb = dev_alloc_skb(totalpacketlen); + if (!skb) { + dlok = false; + } else { + memcpy((u8 *) skb_put(skb, totalpacketlen), + &reserved_page_packet, totalpacketlen); + rtstatus = _rtl92d_cmd_send_packet(hw, skb); + + if (rtstatus) + dlok = true; + } + if (dlok) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "Set RSVD page location to Fw\n"); + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_DMESG, + "H2C_RSVDPAGE", u1RsvdPageLoc, 3); + rtl92d_fill_h2c_cmd(hw, H2C_RSVDPAGE, + sizeof(u1RsvdPageLoc), u1RsvdPageLoc); + } else + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Set RSVD page location to Fw FAIL!!!!!!\n"); +} + +void rtl92d_set_fw_joinbss_report_cmd(struct ieee80211_hw *hw, u8 mstatus) +{ + u8 u1_joinbssrpt_parm[1] = {0}; + + SET_H2CCMD_JOINBSSRPT_PARM_OPMODE(u1_joinbssrpt_parm, mstatus); + rtl92d_fill_h2c_cmd(hw, H2C_JOINBSSRPT, 1, u1_joinbssrpt_parm); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/fw.h b/drivers/net/wireless/rtlwifi/rtl8192de/fw.h new file mode 100644 index 0000000..1ffacdd --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/fw.h @@ -0,0 +1,155 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92D__FW__H__ +#define __RTL92D__FW__H__ + +#define FW_8192D_START_ADDRESS 0x1000 +#define FW_8192D_PAGE_SIZE 4096 +#define FW_8192D_POLLING_TIMEOUT_COUNT 1000 + +#define IS_FW_HEADER_EXIST(_pfwhdr) \ + ((GET_FIRMWARE_HDR_SIGNATURE(_pfwhdr) & 0xFFF0) == 0x92C0 || \ + (GET_FIRMWARE_HDR_SIGNATURE(_pfwhdr) & 0xFFF0) == 0x88C0 || \ + (GET_FIRMWARE_HDR_SIGNATURE(_pfwhdr) & 0xFFFF) == 0x92D0 || \ + (GET_FIRMWARE_HDR_SIGNATURE(_pfwhdr) & 0xFFFF) == 0x92D1 || \ + (GET_FIRMWARE_HDR_SIGNATURE(_pfwhdr) & 0xFFFF) == 0x92D2 || \ + (GET_FIRMWARE_HDR_SIGNATURE(_pfwhdr) & 0xFFFF) == 0x92D3) + +/* Define a macro that takes an le32 word, converts it to host ordering, + * right shifts by a specified count, creates a mask of the specified + * bit count, and extracts that number of bits. + */ + +#define SHIFT_AND_MASK_LE(__pdesc, __shift, __mask) \ + ((le32_to_cpu(*(((__le32 *)(__pdesc)))) >> (__shift)) & \ + BIT_LEN_MASK_32(__mask)) + +/* Firmware Header(8-byte alinment required) */ +/* --- LONG WORD 0 ---- */ +#define GET_FIRMWARE_HDR_SIGNATURE(__fwhdr) \ + SHIFT_AND_MASK_LE(__fwhdr, 0, 16) +#define GET_FIRMWARE_HDR_CATEGORY(__fwhdr) \ + SHIFT_AND_MASK_LE(__fwhdr, 16, 8) +#define GET_FIRMWARE_HDR_FUNCTION(__fwhdr) \ + SHIFT_AND_MASK_LE(__fwhdr, 24, 8) +#define GET_FIRMWARE_HDR_VERSION(__fwhdr) \ + SHIFT_AND_MASK_LE(__fwhdr + 4, 0, 16) +#define GET_FIRMWARE_HDR_SUB_VER(__fwhdr) \ + SHIFT_AND_MASK_LE(__fwhdr + 4, 16, 8) +#define GET_FIRMWARE_HDR_RSVD1(__fwhdr) \ + SHIFT_AND_MASK_LE(__fwhdr + 4, 24, 8) + +/* --- LONG WORD 1 ---- */ +#define GET_FIRMWARE_HDR_MONTH(__fwhdr) \ + SHIFT_AND_MASK_LE(__fwhdr + 8, 0, 8) +#define GET_FIRMWARE_HDR_DATE(__fwhdr) \ + SHIFT_AND_MASK_LE(__fwhdr + 8, 8, 8) +#define GET_FIRMWARE_HDR_HOUR(__fwhdr) \ + SHIFT_AND_MASK_LE(__fwhdr + 8, 16, 8) +#define GET_FIRMWARE_HDR_MINUTE(__fwhdr) \ + SHIFT_AND_MASK_LE(__fwhdr + 8, 24, 8) +#define GET_FIRMWARE_HDR_ROMCODE_SIZE(__fwhdr) \ + SHIFT_AND_MASK_LE(__fwhdr + 12, 0, 16) +#define GET_FIRMWARE_HDR_RSVD2(__fwhdr) \ + SHIFT_AND_MASK_LE(__fwhdr + 12, 16, 16) + +/* --- LONG WORD 2 ---- */ +#define GET_FIRMWARE_HDR_SVN_IDX(__fwhdr) \ + SHIFT_AND_MASK_LE(__fwhdr + 16, 0, 32) +#define GET_FIRMWARE_HDR_RSVD3(__fwhdr) \ + SHIFT_AND_MASK_LE(__fwhdr + 20, 0, 32) + +/* --- LONG WORD 3 ---- */ +#define GET_FIRMWARE_HDR_RSVD4(__fwhdr) \ + SHIFT_AND_MASK_LE(__fwhdr + 24, 0, 32) +#define GET_FIRMWARE_HDR_RSVD5(__fwhdr) \ + SHIFT_AND_MASK_LE(__fwhdr + 28, 0, 32) + +#define pagenum_128(_len) \ + (u32)(((_len) >> 7) + ((_len) & 0x7F ? 1 : 0)) + +#define SET_H2CCMD_PWRMODE_PARM_MODE(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE(__ph2ccmd, 0, 8, __val) +#define SET_H2CCMD_PWRMODE_PARM_SMART_PS(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE((__ph2ccmd) + 1, 0, 8, __val) +#define SET_H2CCMD_PWRMODE_PARM_BCN_PASS_TIME(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE((__ph2ccmd) + 2, 0, 8, __val) +#define SET_H2CCMD_JOINBSSRPT_PARM_OPMODE(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE(__ph2ccmd, 0, 8, __val) +#define SET_H2CCMD_RSVDPAGE_LOC_PROBE_RSP(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE(__ph2ccmd, 0, 8, __val) +#define SET_H2CCMD_RSVDPAGE_LOC_PSPOLL(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE((__ph2ccmd) + 1, 0, 8, __val) +#define SET_H2CCMD_RSVDPAGE_LOC_NULL_DATA(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE((__ph2ccmd) + 2, 0, 8, __val) + +struct rtl92d_firmware_header { + u16 signature; + u8 category; + u8 function; + u16 version; + u8 subversion; + u8 rsvd1; + + u8 month; + u8 date; + u8 hour; + u8 minute; + u16 ramcodeSize; + u16 rsvd2; + + u32 svnindex; + u32 rsvd3; + + u32 rsvd4; + u32 rsvd5; +}; + +enum rtl8192d_h2c_cmd { + H2C_AP_OFFLOAD = 0, + H2C_SETPWRMODE = 1, + H2C_JOINBSSRPT = 2, + H2C_RSVDPAGE = 3, + H2C_RSSI_REPORT = 5, + H2C_RA_MASK = 6, + H2C_MAC_MODE_SEL = 9, + H2C_PWRM = 15, + MAX_H2CCMD +}; + +int rtl92d_download_fw(struct ieee80211_hw *hw); +void rtl92d_fill_h2c_cmd(struct ieee80211_hw *hw, u8 element_id, + u32 cmd_len, u8 *p_cmdbuffer); +void rtl92d_firmware_selfreset(struct ieee80211_hw *hw); +void rtl92d_set_fw_pwrmode_cmd(struct ieee80211_hw *hw, u8 mode); +void rtl92d_set_fw_rsvdpagepkt(struct ieee80211_hw *hw, bool b_dl_finished); +void rtl92d_set_fw_joinbss_report_cmd(struct ieee80211_hw *hw, u8 mstatus); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/hw.c b/drivers/net/wireless/rtlwifi/rtl8192de/hw.c new file mode 100644 index 0000000..7dd8f6d --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/hw.c @@ -0,0 +1,2323 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../efuse.h" +#include "../base.h" +#include "../regd.h" +#include "../cam.h" +#include "../ps.h" +#include "../pci.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "dm.h" +#include "fw.h" +#include "led.h" +#include "sw.h" +#include "hw.h" + +u32 rtl92de_read_dword_dbi(struct ieee80211_hw *hw, u16 offset, u8 direct) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 value; + + rtl_write_word(rtlpriv, REG_DBI_CTRL, (offset & 0xFFC)); + rtl_write_byte(rtlpriv, REG_DBI_FLAG, BIT(1) | direct); + udelay(10); + value = rtl_read_dword(rtlpriv, REG_DBI_RDATA); + return value; +} + +void rtl92de_write_dword_dbi(struct ieee80211_hw *hw, + u16 offset, u32 value, u8 direct) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_word(rtlpriv, REG_DBI_CTRL, ((offset & 0xFFC) | 0xF000)); + rtl_write_dword(rtlpriv, REG_DBI_WDATA, value); + rtl_write_byte(rtlpriv, REG_DBI_FLAG, BIT(0) | direct); +} + +static void _rtl92de_set_bcn_ctrl_reg(struct ieee80211_hw *hw, + u8 set_bits, u8 clear_bits) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpci->reg_bcn_ctrl_val |= set_bits; + rtlpci->reg_bcn_ctrl_val &= ~clear_bits; + rtl_write_byte(rtlpriv, REG_BCN_CTRL, (u8) rtlpci->reg_bcn_ctrl_val); +} + +static void _rtl92de_stop_tx_beacon(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tmp1byte; + + tmp1byte = rtl_read_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2); + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, tmp1byte & (~BIT(6))); + rtl_write_byte(rtlpriv, REG_BCN_MAX_ERR, 0xff); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 1, 0x64); + tmp1byte = rtl_read_byte(rtlpriv, REG_TBTT_PROHIBIT + 2); + tmp1byte &= ~(BIT(0)); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 2, tmp1byte); +} + +static void _rtl92de_resume_tx_beacon(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tmp1byte; + + tmp1byte = rtl_read_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2); + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, tmp1byte | BIT(6)); + rtl_write_byte(rtlpriv, REG_BCN_MAX_ERR, 0x0a); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 1, 0xff); + tmp1byte = rtl_read_byte(rtlpriv, REG_TBTT_PROHIBIT + 2); + tmp1byte |= BIT(0); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 2, tmp1byte); +} + +static void _rtl92de_enable_bcn_sub_func(struct ieee80211_hw *hw) +{ + _rtl92de_set_bcn_ctrl_reg(hw, 0, BIT(1)); +} + +static void _rtl92de_disable_bcn_sub_func(struct ieee80211_hw *hw) +{ + _rtl92de_set_bcn_ctrl_reg(hw, BIT(1), 0); +} + +void rtl92de_get_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + switch (variable) { + case HW_VAR_RCR: + *((u32 *) (val)) = rtlpci->receive_config; + break; + case HW_VAR_RF_STATE: + *((enum rf_pwrstate *)(val)) = ppsc->rfpwr_state; + break; + case HW_VAR_FWLPS_RF_ON:{ + enum rf_pwrstate rfState; + u32 val_rcr; + + rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_RF_STATE, + (u8 *) (&rfState)); + if (rfState == ERFOFF) { + *((bool *) (val)) = true; + } else { + val_rcr = rtl_read_dword(rtlpriv, REG_RCR); + val_rcr &= 0x00070000; + if (val_rcr) + *((bool *) (val)) = false; + else + *((bool *) (val)) = true; + } + break; + } + case HW_VAR_FW_PSMODE_STATUS: + *((bool *) (val)) = ppsc->fw_current_inpsmode; + break; + case HW_VAR_CORRECT_TSF:{ + u64 tsf; + u32 *ptsf_low = (u32 *)&tsf; + u32 *ptsf_high = ((u32 *)&tsf) + 1; + + *ptsf_high = rtl_read_dword(rtlpriv, (REG_TSFTR + 4)); + *ptsf_low = rtl_read_dword(rtlpriv, REG_TSFTR); + *((u64 *) (val)) = tsf; + break; + } + case HW_VAR_INT_MIGRATION: + *((bool *)(val)) = rtlpriv->dm.interrupt_migration; + break; + case HW_VAR_INT_AC: + *((bool *)(val)) = rtlpriv->dm.disable_tx_int; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } +} + +void rtl92de_set_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + u8 idx; + + switch (variable) { + case HW_VAR_ETHER_ADDR: + for (idx = 0; idx < ETH_ALEN; idx++) { + rtl_write_byte(rtlpriv, (REG_MACID + idx), + val[idx]); + } + break; + case HW_VAR_BASIC_RATE: { + u16 rate_cfg = ((u16 *) val)[0]; + u8 rate_index = 0; + + rate_cfg = rate_cfg & 0x15f; + if (mac->vendor == PEER_CISCO && + ((rate_cfg & 0x150) == 0)) + rate_cfg |= 0x01; + rtl_write_byte(rtlpriv, REG_RRSR, rate_cfg & 0xff); + rtl_write_byte(rtlpriv, REG_RRSR + 1, + (rate_cfg >> 8) & 0xff); + while (rate_cfg > 0x1) { + rate_cfg = (rate_cfg >> 1); + rate_index++; + } + if (rtlhal->fw_version > 0xe) + rtl_write_byte(rtlpriv, REG_INIRTS_RATE_SEL, + rate_index); + break; + } + case HW_VAR_BSSID: + for (idx = 0; idx < ETH_ALEN; idx++) { + rtl_write_byte(rtlpriv, (REG_BSSID + idx), + val[idx]); + } + break; + case HW_VAR_SIFS: + rtl_write_byte(rtlpriv, REG_SIFS_CTX + 1, val[0]); + rtl_write_byte(rtlpriv, REG_SIFS_TRX + 1, val[1]); + rtl_write_byte(rtlpriv, REG_SPEC_SIFS + 1, val[0]); + rtl_write_byte(rtlpriv, REG_MAC_SPEC_SIFS + 1, val[0]); + if (!mac->ht_enable) + rtl_write_word(rtlpriv, REG_RESP_SIFS_OFDM, + 0x0e0e); + else + rtl_write_word(rtlpriv, REG_RESP_SIFS_OFDM, + *((u16 *) val)); + break; + case HW_VAR_SLOT_TIME: { + u8 e_aci; + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "HW_VAR_SLOT_TIME %x\n", val[0]); + rtl_write_byte(rtlpriv, REG_SLOT, val[0]); + for (e_aci = 0; e_aci < AC_MAX; e_aci++) + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_AC_PARAM, + (&e_aci)); + break; + } + case HW_VAR_ACK_PREAMBLE: { + u8 reg_tmp; + u8 short_preamble = (bool) (*val); + + reg_tmp = (mac->cur_40_prime_sc) << 5; + if (short_preamble) + reg_tmp |= 0x80; + rtl_write_byte(rtlpriv, REG_RRSR + 2, reg_tmp); + break; + } + case HW_VAR_AMPDU_MIN_SPACE: { + u8 min_spacing_to_set; + u8 sec_min_space; + + min_spacing_to_set = *val; + if (min_spacing_to_set <= 7) { + sec_min_space = 0; + if (min_spacing_to_set < sec_min_space) + min_spacing_to_set = sec_min_space; + mac->min_space_cfg = ((mac->min_space_cfg & 0xf8) | + min_spacing_to_set); + *val = min_spacing_to_set; + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_AMPDU_MIN_SPACE: %#x\n", + mac->min_space_cfg); + rtl_write_byte(rtlpriv, REG_AMPDU_MIN_SPACE, + mac->min_space_cfg); + } + break; + } + case HW_VAR_SHORTGI_DENSITY: { + u8 density_to_set; + + density_to_set = *val; + mac->min_space_cfg = rtlpriv->rtlhal.minspace_cfg; + mac->min_space_cfg |= (density_to_set << 3); + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_SHORTGI_DENSITY: %#x\n", + mac->min_space_cfg); + rtl_write_byte(rtlpriv, REG_AMPDU_MIN_SPACE, + mac->min_space_cfg); + break; + } + case HW_VAR_AMPDU_FACTOR: { + u8 factor_toset; + u32 regtoSet; + u8 *ptmp_byte = NULL; + u8 index; + + if (rtlhal->macphymode == DUALMAC_DUALPHY) + regtoSet = 0xb9726641; + else if (rtlhal->macphymode == DUALMAC_SINGLEPHY) + regtoSet = 0x66626641; + else + regtoSet = 0xb972a841; + factor_toset = *val; + if (factor_toset <= 3) { + factor_toset = (1 << (factor_toset + 2)); + if (factor_toset > 0xf) + factor_toset = 0xf; + for (index = 0; index < 4; index++) { + ptmp_byte = (u8 *) (®toSet) + index; + if ((*ptmp_byte & 0xf0) > + (factor_toset << 4)) + *ptmp_byte = (*ptmp_byte & 0x0f) + | (factor_toset << 4); + if ((*ptmp_byte & 0x0f) > factor_toset) + *ptmp_byte = (*ptmp_byte & 0xf0) + | (factor_toset); + } + rtl_write_dword(rtlpriv, REG_AGGLEN_LMT, regtoSet); + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_AMPDU_FACTOR: %#x\n", + factor_toset); + } + break; + } + case HW_VAR_AC_PARAM: { + u8 e_aci = *val; + rtl92d_dm_init_edca_turbo(hw); + if (rtlpci->acm_method != eAcmWay2_SW) + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_ACM_CTRL, + &e_aci); + break; + } + case HW_VAR_ACM_CTRL: { + u8 e_aci = *val; + union aci_aifsn *p_aci_aifsn = + (union aci_aifsn *)(&(mac->ac[0].aifs)); + u8 acm = p_aci_aifsn->f.acm; + u8 acm_ctrl = rtl_read_byte(rtlpriv, REG_ACMHWCTRL); + + acm_ctrl = acm_ctrl | ((rtlpci->acm_method == 2) ? 0x0 : 0x1); + if (acm) { + switch (e_aci) { + case AC0_BE: + acm_ctrl |= ACMHW_BEQEN; + break; + case AC2_VI: + acm_ctrl |= ACMHW_VIQEN; + break; + case AC3_VO: + acm_ctrl |= ACMHW_VOQEN; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "HW_VAR_ACM_CTRL acm set failed: eACI is %d\n", + acm); + break; + } + } else { + switch (e_aci) { + case AC0_BE: + acm_ctrl &= (~ACMHW_BEQEN); + break; + case AC2_VI: + acm_ctrl &= (~ACMHW_VIQEN); + break; + case AC3_VO: + acm_ctrl &= (~ACMHW_VOQEN); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + } + RT_TRACE(rtlpriv, COMP_QOS, DBG_TRACE, + "SetHwReg8190pci(): [HW_VAR_ACM_CTRL] Write 0x%X\n", + acm_ctrl); + rtl_write_byte(rtlpriv, REG_ACMHWCTRL, acm_ctrl); + break; + } + case HW_VAR_RCR: + rtl_write_dword(rtlpriv, REG_RCR, ((u32 *) (val))[0]); + rtlpci->receive_config = ((u32 *) (val))[0]; + break; + case HW_VAR_RETRY_LIMIT: { + u8 retry_limit = val[0]; + + rtl_write_word(rtlpriv, REG_RL, + retry_limit << RETRY_LIMIT_SHORT_SHIFT | + retry_limit << RETRY_LIMIT_LONG_SHIFT); + break; + } + case HW_VAR_DUAL_TSF_RST: + rtl_write_byte(rtlpriv, REG_DUAL_TSF_RST, (BIT(0) | BIT(1))); + break; + case HW_VAR_EFUSE_BYTES: + rtlefuse->efuse_usedbytes = *((u16 *) val); + break; + case HW_VAR_EFUSE_USAGE: + rtlefuse->efuse_usedpercentage = *val; + break; + case HW_VAR_IO_CMD: + rtl92d_phy_set_io_cmd(hw, (*(enum io_type *)val)); + break; + case HW_VAR_WPA_CONFIG: + rtl_write_byte(rtlpriv, REG_SECCFG, *val); + break; + case HW_VAR_SET_RPWM: + rtl92d_fill_h2c_cmd(hw, H2C_PWRM, 1, (val)); + break; + case HW_VAR_H2C_FW_PWRMODE: + break; + case HW_VAR_FW_PSMODE_STATUS: + ppsc->fw_current_inpsmode = *((bool *) val); + break; + case HW_VAR_H2C_FW_JOINBSSRPT: { + u8 mstatus = (*val); + u8 tmp_regcr, tmp_reg422; + bool recover = false; + + if (mstatus == RT_MEDIA_CONNECT) { + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_AID, NULL); + tmp_regcr = rtl_read_byte(rtlpriv, REG_CR + 1); + rtl_write_byte(rtlpriv, REG_CR + 1, + (tmp_regcr | BIT(0))); + _rtl92de_set_bcn_ctrl_reg(hw, 0, BIT(3)); + _rtl92de_set_bcn_ctrl_reg(hw, BIT(4), 0); + tmp_reg422 = rtl_read_byte(rtlpriv, + REG_FWHW_TXQ_CTRL + 2); + if (tmp_reg422 & BIT(6)) + recover = true; + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, + tmp_reg422 & (~BIT(6))); + rtl92d_set_fw_rsvdpagepkt(hw, 0); + _rtl92de_set_bcn_ctrl_reg(hw, BIT(3), 0); + _rtl92de_set_bcn_ctrl_reg(hw, 0, BIT(4)); + if (recover) + rtl_write_byte(rtlpriv, + REG_FWHW_TXQ_CTRL + 2, + tmp_reg422); + rtl_write_byte(rtlpriv, REG_CR + 1, + (tmp_regcr & ~(BIT(0)))); + } + rtl92d_set_fw_joinbss_report_cmd(hw, (*val)); + break; + } + case HW_VAR_AID: { + u16 u2btmp; + u2btmp = rtl_read_word(rtlpriv, REG_BCN_PSR_RPT); + u2btmp &= 0xC000; + rtl_write_word(rtlpriv, REG_BCN_PSR_RPT, (u2btmp | + mac->assoc_id)); + break; + } + case HW_VAR_CORRECT_TSF: { + u8 btype_ibss = val[0]; + + if (btype_ibss) + _rtl92de_stop_tx_beacon(hw); + _rtl92de_set_bcn_ctrl_reg(hw, 0, BIT(3)); + rtl_write_dword(rtlpriv, REG_TSFTR, + (u32) (mac->tsf & 0xffffffff)); + rtl_write_dword(rtlpriv, REG_TSFTR + 4, + (u32) ((mac->tsf >> 32) & 0xffffffff)); + _rtl92de_set_bcn_ctrl_reg(hw, BIT(3), 0); + if (btype_ibss) + _rtl92de_resume_tx_beacon(hw); + + break; + } + case HW_VAR_INT_MIGRATION: { + bool int_migration = *(bool *) (val); + + if (int_migration) { + /* Set interrupt migration timer and + * corresponding Tx/Rx counter. + * timer 25ns*0xfa0=100us for 0xf packets. + * 0x306:Rx, 0x307:Tx */ + rtl_write_dword(rtlpriv, REG_INT_MIG, 0xfe000fa0); + rtlpriv->dm.interrupt_migration = int_migration; + } else { + /* Reset all interrupt migration settings. */ + rtl_write_dword(rtlpriv, REG_INT_MIG, 0); + rtlpriv->dm.interrupt_migration = int_migration; + } + break; + } + case HW_VAR_INT_AC: { + bool disable_ac_int = *((bool *) val); + + /* Disable four ACs interrupts. */ + if (disable_ac_int) { + /* Disable VO, VI, BE and BK four AC interrupts + * to gain more efficient CPU utilization. + * When extremely highly Rx OK occurs, + * we will disable Tx interrupts. + */ + rtlpriv->cfg->ops->update_interrupt_mask(hw, 0, + RT_AC_INT_MASKS); + rtlpriv->dm.disable_tx_int = disable_ac_int; + /* Enable four ACs interrupts. */ + } else { + rtlpriv->cfg->ops->update_interrupt_mask(hw, + RT_AC_INT_MASKS, 0); + rtlpriv->dm.disable_tx_int = disable_ac_int; + } + break; + } + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } +} + +static bool _rtl92de_llt_write(struct ieee80211_hw *hw, u32 address, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + bool status = true; + long count = 0; + u32 value = _LLT_INIT_ADDR(address) | + _LLT_INIT_DATA(data) | _LLT_OP(_LLT_WRITE_ACCESS); + + rtl_write_dword(rtlpriv, REG_LLT_INIT, value); + do { + value = rtl_read_dword(rtlpriv, REG_LLT_INIT); + if (_LLT_NO_ACTIVE == _LLT_OP_VALUE(value)) + break; + if (count > POLLING_LLT_THRESHOLD) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Failed to polling write LLT done at address %d!\n", + address); + status = false; + break; + } + } while (++count); + return status; +} + +static bool _rtl92de_llt_table_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + unsigned short i; + u8 txpktbuf_bndy; + u8 maxPage; + bool status; + u32 value32; /* High+low page number */ + u8 value8; /* normal page number */ + + if (rtlpriv->rtlhal.macphymode == SINGLEMAC_SINGLEPHY) { + maxPage = 255; + txpktbuf_bndy = 246; + value8 = 0; + value32 = 0x80bf0d29; + } else if (rtlpriv->rtlhal.macphymode != SINGLEMAC_SINGLEPHY) { + maxPage = 127; + txpktbuf_bndy = 123; + value8 = 0; + value32 = 0x80750005; + } + + /* Set reserved page for each queue */ + /* 11. RQPN 0x200[31:0] = 0x80BD1C1C */ + /* load RQPN */ + rtl_write_byte(rtlpriv, REG_RQPN_NPQ, value8); + rtl_write_dword(rtlpriv, REG_RQPN, value32); + + /* 12. TXRKTBUG_PG_BNDY 0x114[31:0] = 0x27FF00F6 */ + /* TXRKTBUG_PG_BNDY */ + rtl_write_dword(rtlpriv, REG_TRXFF_BNDY, + (rtl_read_word(rtlpriv, REG_TRXFF_BNDY + 2) << 16 | + txpktbuf_bndy)); + + /* 13. TDECTRL[15:8] 0x209[7:0] = 0xF6 */ + /* Beacon Head for TXDMA */ + rtl_write_byte(rtlpriv, REG_TDECTRL + 1, txpktbuf_bndy); + + /* 14. BCNQ_PGBNDY 0x424[7:0] = 0xF6 */ + /* BCNQ_PGBNDY */ + rtl_write_byte(rtlpriv, REG_TXPKTBUF_BCNQ_BDNY, txpktbuf_bndy); + rtl_write_byte(rtlpriv, REG_TXPKTBUF_MGQ_BDNY, txpktbuf_bndy); + + /* 15. WMAC_LBK_BF_HD 0x45D[7:0] = 0xF6 */ + /* WMAC_LBK_BF_HD */ + rtl_write_byte(rtlpriv, 0x45D, txpktbuf_bndy); + + /* Set Tx/Rx page size (Tx must be 128 Bytes, */ + /* Rx can be 64,128,256,512,1024 bytes) */ + /* 16. PBP [7:0] = 0x11 */ + /* TRX page size */ + rtl_write_byte(rtlpriv, REG_PBP, 0x11); + + /* 17. DRV_INFO_SZ = 0x04 */ + rtl_write_byte(rtlpriv, REG_RX_DRVINFO_SZ, 0x4); + + /* 18. LLT_table_init(Adapter); */ + for (i = 0; i < (txpktbuf_bndy - 1); i++) { + status = _rtl92de_llt_write(hw, i, i + 1); + if (true != status) + return status; + } + + /* end of list */ + status = _rtl92de_llt_write(hw, (txpktbuf_bndy - 1), 0xFF); + if (true != status) + return status; + + /* Make the other pages as ring buffer */ + /* This ring buffer is used as beacon buffer if we */ + /* config this MAC as two MAC transfer. */ + /* Otherwise used as local loopback buffer. */ + for (i = txpktbuf_bndy; i < maxPage; i++) { + status = _rtl92de_llt_write(hw, i, (i + 1)); + if (true != status) + return status; + } + + /* Let last entry point to the start entry of ring buffer */ + status = _rtl92de_llt_write(hw, maxPage, txpktbuf_bndy); + if (true != status) + return status; + + return true; +} + +static void _rtl92de_gen_refresh_led_state(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_led *pLed0 = &(pcipriv->ledctl.sw_led0); + + if (rtlpci->up_first_time) + return; + if (ppsc->rfoff_reason == RF_CHANGE_BY_IPS) + rtl92de_sw_led_on(hw, pLed0); + else if (ppsc->rfoff_reason == RF_CHANGE_BY_INIT) + rtl92de_sw_led_on(hw, pLed0); + else + rtl92de_sw_led_off(hw, pLed0); +} + +static bool _rtl92de_init_mac(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + unsigned char bytetmp; + unsigned short wordtmp; + u16 retry; + + rtl92d_phy_set_poweron(hw); + /* Add for resume sequence of power domain according + * to power document V11. Chapter V.11.... */ + /* 0. RSV_CTRL 0x1C[7:0] = 0x00 */ + /* unlock ISO/CLK/Power control register */ + rtl_write_byte(rtlpriv, REG_RSV_CTRL, 0x00); + rtl_write_byte(rtlpriv, REG_LDOA15_CTRL, 0x05); + + /* 1. AFE_XTAL_CTRL [7:0] = 0x0F enable XTAL */ + /* 2. SPS0_CTRL 0x11[7:0] = 0x2b enable SPS into PWM mode */ + /* 3. delay (1ms) this is not necessary when initially power on */ + + /* C. Resume Sequence */ + /* a. SPS0_CTRL 0x11[7:0] = 0x2b */ + rtl_write_byte(rtlpriv, REG_SPS0_CTRL, 0x2b); + + /* b. AFE_XTAL_CTRL [7:0] = 0x0F */ + rtl_write_byte(rtlpriv, REG_AFE_XTAL_CTRL, 0x0F); + + /* c. DRV runs power on init flow */ + + /* auto enable WLAN */ + /* 4. APS_FSMCO 0x04[8] = 1; wait till 0x04[8] = 0 */ + /* Power On Reset for MAC Block */ + bytetmp = rtl_read_byte(rtlpriv, REG_APS_FSMCO + 1) | BIT(0); + udelay(2); + rtl_write_byte(rtlpriv, REG_APS_FSMCO + 1, bytetmp); + udelay(2); + + /* 5. Wait while 0x04[8] == 0 goto 2, otherwise goto 1 */ + bytetmp = rtl_read_byte(rtlpriv, REG_APS_FSMCO + 1); + udelay(50); + retry = 0; + while ((bytetmp & BIT(0)) && retry < 1000) { + retry++; + bytetmp = rtl_read_byte(rtlpriv, REG_APS_FSMCO + 1); + udelay(50); + } + + /* Enable Radio off, GPIO, and LED function */ + /* 6. APS_FSMCO 0x04[15:0] = 0x0012 when enable HWPDN */ + rtl_write_word(rtlpriv, REG_APS_FSMCO, 0x1012); + + /* release RF digital isolation */ + /* 7. SYS_ISO_CTRL 0x01[1] = 0x0; */ + /*Set REG_SYS_ISO_CTRL 0x1=0x82 to prevent wake# problem. */ + rtl_write_byte(rtlpriv, REG_SYS_ISO_CTRL + 1, 0x82); + udelay(2); + + /* make sure that BB reset OK. */ + /* rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE3); */ + + /* Disable REG_CR before enable it to assure reset */ + rtl_write_word(rtlpriv, REG_CR, 0x0); + + /* Release MAC IO register reset */ + rtl_write_word(rtlpriv, REG_CR, 0x2ff); + + /* clear stopping tx/rx dma */ + rtl_write_byte(rtlpriv, REG_PCIE_CTRL_REG + 1, 0x0); + + /* rtl_write_word(rtlpriv,REG_CR+2, 0x2); */ + + /* System init */ + /* 18. LLT_table_init(Adapter); */ + if (!_rtl92de_llt_table_init(hw)) + return false; + + /* Clear interrupt and enable interrupt */ + /* 19. HISR 0x124[31:0] = 0xffffffff; */ + /* HISRE 0x12C[7:0] = 0xFF */ + rtl_write_dword(rtlpriv, REG_HISR, 0xffffffff); + rtl_write_byte(rtlpriv, REG_HISRE, 0xff); + + /* 20. HIMR 0x120[31:0] |= [enable INT mask bit map]; */ + /* 21. HIMRE 0x128[7:0] = [enable INT mask bit map] */ + /* The IMR should be enabled later after all init sequence + * is finished. */ + + /* 22. PCIE configuration space configuration */ + /* 23. Ensure PCIe Device 0x80[15:0] = 0x0143 (ASPM+CLKREQ), */ + /* and PCIe gated clock function is enabled. */ + /* PCIE configuration space will be written after + * all init sequence.(Or by BIOS) */ + + rtl92d_phy_config_maccoexist_rfpage(hw); + + /* THe below section is not related to power document Vxx . */ + /* This is only useful for driver and OS setting. */ + /* -------------------Software Relative Setting---------------------- */ + wordtmp = rtl_read_word(rtlpriv, REG_TRXDMA_CTRL); + wordtmp &= 0xf; + wordtmp |= 0xF771; + rtl_write_word(rtlpriv, REG_TRXDMA_CTRL, wordtmp); + + /* Reported Tx status from HW for rate adaptive. */ + /* This should be realtive to power on step 14. But in document V11 */ + /* still not contain the description.!!! */ + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 1, 0x1F); + + /* Set Tx/Rx page size (Tx must be 128 Bytes, + * Rx can be 64,128,256,512,1024 bytes) */ + /* rtl_write_byte(rtlpriv,REG_PBP, 0x11); */ + + /* Set RCR register */ + rtl_write_dword(rtlpriv, REG_RCR, rtlpci->receive_config); + /* rtl_write_byte(rtlpriv,REG_RX_DRVINFO_SZ, 4); */ + + /* Set TCR register */ + rtl_write_dword(rtlpriv, REG_TCR, rtlpci->transmit_config); + + /* disable earlymode */ + rtl_write_byte(rtlpriv, 0x4d0, 0x0); + + /* Set TX/RX descriptor physical address(from OS API). */ + rtl_write_dword(rtlpriv, REG_BCNQ_DESA, + rtlpci->tx_ring[BEACON_QUEUE].dma); + rtl_write_dword(rtlpriv, REG_MGQ_DESA, rtlpci->tx_ring[MGNT_QUEUE].dma); + rtl_write_dword(rtlpriv, REG_VOQ_DESA, rtlpci->tx_ring[VO_QUEUE].dma); + rtl_write_dword(rtlpriv, REG_VIQ_DESA, rtlpci->tx_ring[VI_QUEUE].dma); + rtl_write_dword(rtlpriv, REG_BEQ_DESA, rtlpci->tx_ring[BE_QUEUE].dma); + rtl_write_dword(rtlpriv, REG_BKQ_DESA, rtlpci->tx_ring[BK_QUEUE].dma); + rtl_write_dword(rtlpriv, REG_HQ_DESA, rtlpci->tx_ring[HIGH_QUEUE].dma); + /* Set RX Desc Address */ + rtl_write_dword(rtlpriv, REG_RX_DESA, + rtlpci->rx_ring[RX_MPDU_QUEUE].dma); + + /* if we want to support 64 bit DMA, we should set it here, + * but now we do not support 64 bit DMA*/ + + rtl_write_byte(rtlpriv, REG_PCIE_CTRL_REG + 3, 0x33); + + /* Reset interrupt migration setting when initialization */ + rtl_write_dword(rtlpriv, REG_INT_MIG, 0); + + /* Reconsider when to do this operation after asking HWSD. */ + bytetmp = rtl_read_byte(rtlpriv, REG_APSD_CTRL); + rtl_write_byte(rtlpriv, REG_APSD_CTRL, bytetmp & ~BIT(6)); + do { + retry++; + bytetmp = rtl_read_byte(rtlpriv, REG_APSD_CTRL); + } while ((retry < 200) && !(bytetmp & BIT(7))); + + /* After MACIO reset,we must refresh LED state. */ + _rtl92de_gen_refresh_led_state(hw); + + /* Reset H2C protection register */ + rtl_write_dword(rtlpriv, REG_MCUTST_1, 0x0); + + return true; +} + +static void _rtl92de_hw_configure(struct ieee80211_hw *hw) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 reg_bw_opmode = BW_OPMODE_20MHZ; + u32 reg_rrsr; + + reg_rrsr = RATE_ALL_CCK | RATE_ALL_OFDM_AG; + rtl_write_byte(rtlpriv, REG_INIRTS_RATE_SEL, 0x8); + rtl_write_byte(rtlpriv, REG_BWOPMODE, reg_bw_opmode); + rtl_write_dword(rtlpriv, REG_RRSR, reg_rrsr); + rtl_write_byte(rtlpriv, REG_SLOT, 0x09); + rtl_write_byte(rtlpriv, REG_AMPDU_MIN_SPACE, 0x0); + rtl_write_word(rtlpriv, REG_FWHW_TXQ_CTRL, 0x1F80); + rtl_write_word(rtlpriv, REG_RL, 0x0707); + rtl_write_dword(rtlpriv, REG_BAR_MODE_CTRL, 0x02012802); + rtl_write_byte(rtlpriv, REG_HWSEQ_CTRL, 0xFF); + rtl_write_dword(rtlpriv, REG_DARFRC, 0x01000000); + rtl_write_dword(rtlpriv, REG_DARFRC + 4, 0x07060504); + rtl_write_dword(rtlpriv, REG_RARFRC, 0x01000000); + rtl_write_dword(rtlpriv, REG_RARFRC + 4, 0x07060504); + /* Aggregation threshold */ + if (rtlhal->macphymode == DUALMAC_DUALPHY) + rtl_write_dword(rtlpriv, REG_AGGLEN_LMT, 0xb9726641); + else if (rtlhal->macphymode == DUALMAC_SINGLEPHY) + rtl_write_dword(rtlpriv, REG_AGGLEN_LMT, 0x66626641); + else + rtl_write_dword(rtlpriv, REG_AGGLEN_LMT, 0xb972a841); + rtl_write_byte(rtlpriv, REG_ATIMWND, 0x2); + rtl_write_byte(rtlpriv, REG_BCN_MAX_ERR, 0x0a); + rtlpci->reg_bcn_ctrl_val = 0x1f; + rtl_write_byte(rtlpriv, REG_BCN_CTRL, rtlpci->reg_bcn_ctrl_val); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 1, 0xff); + rtl_write_byte(rtlpriv, REG_PIFS, 0x1C); + rtl_write_byte(rtlpriv, REG_AGGR_BREAK_TIME, 0x16); + rtl_write_word(rtlpriv, REG_NAV_PROT_LEN, 0x0020); + /* For throughput */ + rtl_write_word(rtlpriv, REG_FAST_EDCA_CTRL, 0x6666); + /* ACKTO for IOT issue. */ + rtl_write_byte(rtlpriv, REG_ACKTO, 0x40); + /* Set Spec SIFS (used in NAV) */ + rtl_write_word(rtlpriv, REG_SPEC_SIFS, 0x1010); + rtl_write_word(rtlpriv, REG_MAC_SPEC_SIFS, 0x1010); + /* Set SIFS for CCK */ + rtl_write_word(rtlpriv, REG_SIFS_CTX, 0x1010); + /* Set SIFS for OFDM */ + rtl_write_word(rtlpriv, REG_SIFS_TRX, 0x1010); + /* Set Multicast Address. */ + rtl_write_dword(rtlpriv, REG_MAR, 0xffffffff); + rtl_write_dword(rtlpriv, REG_MAR + 4, 0xffffffff); + switch (rtlpriv->phy.rf_type) { + case RF_1T2R: + case RF_1T1R: + rtlhal->minspace_cfg = (MAX_MSS_DENSITY_1T << 3); + break; + case RF_2T2R: + case RF_2T2R_GREEN: + rtlhal->minspace_cfg = (MAX_MSS_DENSITY_2T << 3); + break; + } +} + +static void _rtl92de_enable_aspm_back_door(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + rtl_write_byte(rtlpriv, 0x34b, 0x93); + rtl_write_word(rtlpriv, 0x350, 0x870c); + rtl_write_byte(rtlpriv, 0x352, 0x1); + if (ppsc->support_backdoor) + rtl_write_byte(rtlpriv, 0x349, 0x1b); + else + rtl_write_byte(rtlpriv, 0x349, 0x03); + rtl_write_word(rtlpriv, 0x350, 0x2718); + rtl_write_byte(rtlpriv, 0x352, 0x1); +} + +void rtl92de_enable_hw_security_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 sec_reg_value; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "PairwiseEncAlgorithm = %d GroupEncAlgorithm = %d\n", + rtlpriv->sec.pairwise_enc_algorithm, + rtlpriv->sec.group_enc_algorithm); + if (rtlpriv->cfg->mod_params->sw_crypto || rtlpriv->sec.use_sw_sec) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "not open hw encryption\n"); + return; + } + sec_reg_value = SCR_TXENCENABLE | SCR_RXENCENABLE; + if (rtlpriv->sec.use_defaultkey) { + sec_reg_value |= SCR_TXUSEDK; + sec_reg_value |= SCR_RXUSEDK; + } + sec_reg_value |= (SCR_RXBCUSEDK | SCR_TXBCUSEDK); + rtl_write_byte(rtlpriv, REG_CR + 1, 0x02); + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, + "The SECR-value %x\n", sec_reg_value); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_WPA_CONFIG, &sec_reg_value); +} + +int rtl92de_hw_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + bool rtstatus = true; + u8 tmp_u1b; + int i; + int err; + unsigned long flags; + + rtlpci->being_init_adapter = true; + rtlpci->init_ready = false; + spin_lock_irqsave(&globalmutex_for_power_and_efuse, flags); + /* we should do iqk after disable/enable */ + rtl92d_phy_reset_iqk_result(hw); + /* rtlpriv->intf_ops->disable_aspm(hw); */ + rtstatus = _rtl92de_init_mac(hw); + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Init MAC failed\n"); + err = 1; + spin_unlock_irqrestore(&globalmutex_for_power_and_efuse, flags); + return err; + } + err = rtl92d_download_fw(hw); + spin_unlock_irqrestore(&globalmutex_for_power_and_efuse, flags); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Failed to download FW. Init HW without FW..\n"); + return 1; + } + rtlhal->last_hmeboxnum = 0; + rtlpriv->psc.fw_current_inpsmode = false; + + tmp_u1b = rtl_read_byte(rtlpriv, 0x605); + tmp_u1b = tmp_u1b | 0x30; + rtl_write_byte(rtlpriv, 0x605, tmp_u1b); + + if (rtlhal->earlymode_enable) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EarlyMode Enabled!!!\n"); + + tmp_u1b = rtl_read_byte(rtlpriv, 0x4d0); + tmp_u1b = tmp_u1b | 0x1f; + rtl_write_byte(rtlpriv, 0x4d0, tmp_u1b); + + rtl_write_byte(rtlpriv, 0x4d3, 0x80); + + tmp_u1b = rtl_read_byte(rtlpriv, 0x605); + tmp_u1b = tmp_u1b | 0x40; + rtl_write_byte(rtlpriv, 0x605, tmp_u1b); + } + + if (mac->rdg_en) { + rtl_write_byte(rtlpriv, REG_RD_CTRL, 0xff); + rtl_write_word(rtlpriv, REG_RD_NAV_NXT, 0x200); + rtl_write_byte(rtlpriv, REG_RD_RESP_PKT_TH, 0x05); + } + + rtl92d_phy_mac_config(hw); + /* because last function modify RCR, so we update + * rcr var here, or TP will unstable for receive_config + * is wrong, RX RCR_ACRC32 will cause TP unstabel & Rx + * RCR_APP_ICV will cause mac80211 unassoc for cisco 1252*/ + rtlpci->receive_config = rtl_read_dword(rtlpriv, REG_RCR); + rtlpci->receive_config &= ~(RCR_ACRC32 | RCR_AICV); + + rtl92d_phy_bb_config(hw); + + rtlphy->rf_mode = RF_OP_BY_SW_3WIRE; + /* set before initialize RF */ + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER4, 0x00f00000, 0xf); + + /* config RF */ + rtl92d_phy_rf_config(hw); + + /* After read predefined TXT, we must set BB/MAC/RF + * register as our requirement */ + /* After load BB,RF params,we need do more for 92D. */ + rtl92d_update_bbrf_configuration(hw); + /* set default value after initialize RF, */ + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER4, 0x00f00000, 0); + rtlphy->rfreg_chnlval[0] = rtl_get_rfreg(hw, (enum radio_path)0, + RF_CHNLBW, BRFREGOFFSETMASK); + rtlphy->rfreg_chnlval[1] = rtl_get_rfreg(hw, (enum radio_path)1, + RF_CHNLBW, BRFREGOFFSETMASK); + + /*---- Set CCK and OFDM Block "ON"----*/ + if (rtlhal->current_bandtype == BAND_ON_2_4G) + rtl_set_bbreg(hw, RFPGA0_RFMOD, BCCKEN, 0x1); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BOFDMEN, 0x1); + if (rtlhal->interfaceindex == 0) { + /* RFPGA0_ANALOGPARAMETER2: cck clock select, + * set to 20MHz by default */ + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER2, BIT(10) | + BIT(11), 3); + } else { + /* Mac1 */ + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER2, BIT(11) | + BIT(10), 3); + } + + _rtl92de_hw_configure(hw); + + /* reset hw sec */ + rtl_cam_reset_all_entry(hw); + rtl92de_enable_hw_security_config(hw); + + /* Read EEPROM TX power index and PHY_REG_PG.txt to capture correct */ + /* TX power index for different rate set. */ + rtl92d_phy_get_hw_reg_originalvalue(hw); + rtl92d_phy_set_txpower_level(hw, rtlphy->current_channel); + + ppsc->rfpwr_state = ERFON; + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_ETHER_ADDR, mac->mac_addr); + + _rtl92de_enable_aspm_back_door(hw); + /* rtlpriv->intf_ops->enable_aspm(hw); */ + + rtl92d_dm_init(hw); + rtlpci->being_init_adapter = false; + + if (ppsc->rfpwr_state == ERFON) { + rtl92d_phy_lc_calibrate(hw); + /* 5G and 2.4G must wait sometime to let RF LO ready */ + if (rtlhal->macphymode == DUALMAC_DUALPHY) { + u32 tmp_rega; + for (i = 0; i < 10000; i++) { + udelay(MAX_STALL_TIME); + + tmp_rega = rtl_get_rfreg(hw, + (enum radio_path)RF90_PATH_A, + 0x2a, BMASKDWORD); + + if (((tmp_rega & BIT(11)) == BIT(11))) + break; + } + /* check that loop was successful. If not, exit now */ + if (i == 10000) { + rtlpci->init_ready = false; + return 1; + } + } + } + rtlpci->init_ready = true; + return err; +} + +static enum version_8192d _rtl92de_read_chip_version(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + enum version_8192d version = VERSION_NORMAL_CHIP_92D_SINGLEPHY; + u32 value32; + + value32 = rtl_read_dword(rtlpriv, REG_SYS_CFG); + if (!(value32 & 0x000f0000)) { + version = VERSION_TEST_CHIP_92D_SINGLEPHY; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "TEST CHIP!!!\n"); + } else { + version = VERSION_NORMAL_CHIP_92D_SINGLEPHY; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Normal CHIP!!!\n"); + } + return version; +} + +static int _rtl92de_set_media_status(struct ieee80211_hw *hw, + enum nl80211_iftype type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 bt_msr = rtl_read_byte(rtlpriv, MSR); + enum led_ctl_mode ledaction = LED_CTL_NO_LINK; + u8 bcnfunc_enable; + + bt_msr &= 0xfc; + + if (type == NL80211_IFTYPE_UNSPECIFIED || + type == NL80211_IFTYPE_STATION) { + _rtl92de_stop_tx_beacon(hw); + _rtl92de_enable_bcn_sub_func(hw); + } else if (type == NL80211_IFTYPE_ADHOC || + type == NL80211_IFTYPE_AP) { + _rtl92de_resume_tx_beacon(hw); + _rtl92de_disable_bcn_sub_func(hw); + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Set HW_VAR_MEDIA_STATUS: No such media status(%x)\n", + type); + } + bcnfunc_enable = rtl_read_byte(rtlpriv, REG_BCN_CTRL); + switch (type) { + case NL80211_IFTYPE_UNSPECIFIED: + bt_msr |= MSR_NOLINK; + ledaction = LED_CTL_LINK; + bcnfunc_enable &= 0xF7; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to NO LINK!\n"); + break; + case NL80211_IFTYPE_ADHOC: + bt_msr |= MSR_ADHOC; + bcnfunc_enable |= 0x08; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to Ad Hoc!\n"); + break; + case NL80211_IFTYPE_STATION: + bt_msr |= MSR_INFRA; + ledaction = LED_CTL_LINK; + bcnfunc_enable &= 0xF7; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to STA!\n"); + break; + case NL80211_IFTYPE_AP: + bt_msr |= MSR_AP; + bcnfunc_enable |= 0x08; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to AP!\n"); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Network type %d not supported!\n", type); + return 1; + break; + + } + rtl_write_byte(rtlpriv, REG_CR + 2, bt_msr); + rtlpriv->cfg->ops->led_control(hw, ledaction); + if ((bt_msr & 0xfc) == MSR_AP) + rtl_write_byte(rtlpriv, REG_BCNTCFG + 1, 0x00); + else + rtl_write_byte(rtlpriv, REG_BCNTCFG + 1, 0x66); + return 0; +} + +void rtl92de_set_check_bssid(struct ieee80211_hw *hw, bool check_bssid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u32 reg_rcr = rtlpci->receive_config; + + if (rtlpriv->psc.rfpwr_state != ERFON) + return; + if (check_bssid) { + reg_rcr |= (RCR_CBSSID_DATA | RCR_CBSSID_BCN); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_RCR, (u8 *)(®_rcr)); + _rtl92de_set_bcn_ctrl_reg(hw, 0, BIT(4)); + } else if (!check_bssid) { + reg_rcr &= (~(RCR_CBSSID_DATA | RCR_CBSSID_BCN)); + _rtl92de_set_bcn_ctrl_reg(hw, BIT(4), 0); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_RCR, (u8 *)(®_rcr)); + } +} + +int rtl92de_set_network_type(struct ieee80211_hw *hw, enum nl80211_iftype type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (_rtl92de_set_media_status(hw, type)) + return -EOPNOTSUPP; + + /* check bssid */ + if (rtlpriv->mac80211.link_state == MAC80211_LINKED) { + if (type != NL80211_IFTYPE_AP) + rtl92de_set_check_bssid(hw, true); + } else { + rtl92de_set_check_bssid(hw, false); + } + return 0; +} + +/* do iqk or reload iqk */ +/* windows just rtl92d_phy_reload_iqk_setting in set channel, + * but it's very strict for time sequence so we add + * rtl92d_phy_reload_iqk_setting here */ +void rtl92d_linked_set_reg(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u8 indexforchannel; + u8 channel = rtlphy->current_channel; + + indexforchannel = rtl92d_get_rightchnlplace_for_iqk(channel); + if (!rtlphy->iqk_matrix[indexforchannel].iqk_done) { + RT_TRACE(rtlpriv, COMP_SCAN | COMP_INIT, DBG_DMESG, + "Do IQK for channel:%d\n", channel); + rtl92d_phy_iq_calibrate(hw); + } +} + +/* don't set REG_EDCA_BE_PARAM here because + * mac80211 will send pkt when scan */ +void rtl92de_set_qos(struct ieee80211_hw *hw, int aci) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + rtl92d_dm_init_edca_turbo(hw); + return; + switch (aci) { + case AC1_BK: + rtl_write_dword(rtlpriv, REG_EDCA_BK_PARAM, 0xa44f); + break; + case AC0_BE: + break; + case AC2_VI: + rtl_write_dword(rtlpriv, REG_EDCA_VI_PARAM, 0x5e4322); + break; + case AC3_VO: + rtl_write_dword(rtlpriv, REG_EDCA_VO_PARAM, 0x2f3222); + break; + default: + RT_ASSERT(false, "invalid aci: %d !\n", aci); + break; + } +} + +void rtl92de_enable_interrupt(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + rtl_write_dword(rtlpriv, REG_HIMR, rtlpci->irq_mask[0] & 0xFFFFFFFF); + rtl_write_dword(rtlpriv, REG_HIMRE, rtlpci->irq_mask[1] & 0xFFFFFFFF); +} + +void rtl92de_disable_interrupt(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + rtl_write_dword(rtlpriv, REG_HIMR, IMR8190_DISABLED); + rtl_write_dword(rtlpriv, REG_HIMRE, IMR8190_DISABLED); + synchronize_irq(rtlpci->pdev->irq); +} + +static void _rtl92de_poweroff_adapter(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 u1b_tmp; + unsigned long flags; + + rtlpriv->intf_ops->enable_aspm(hw); + rtl_write_byte(rtlpriv, REG_RF_CTRL, 0x00); + rtl_set_bbreg(hw, RFPGA0_XCD_RFPARAMETER, BIT(3), 0); + rtl_set_bbreg(hw, RFPGA0_XCD_RFPARAMETER, BIT(15), 0); + + /* 0x20:value 05-->04 */ + rtl_write_byte(rtlpriv, REG_LDOA15_CTRL, 0x04); + + /* ==== Reset digital sequence ====== */ + rtl92d_firmware_selfreset(hw); + + /* f. SYS_FUNC_EN 0x03[7:0]=0x51 reset MCU, MAC register, DCORE */ + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN + 1, 0x51); + + /* g. MCUFWDL 0x80[1:0]=0 reset MCU ready status */ + rtl_write_byte(rtlpriv, REG_MCUFWDL, 0x00); + + /* ==== Pull GPIO PIN to balance level and LED control ====== */ + + /* h. GPIO_PIN_CTRL 0x44[31:0]=0x000 */ + rtl_write_dword(rtlpriv, REG_GPIO_PIN_CTRL, 0x00000000); + + /* i. Value = GPIO_PIN_CTRL[7:0] */ + u1b_tmp = rtl_read_byte(rtlpriv, REG_GPIO_PIN_CTRL); + + /* j. GPIO_PIN_CTRL 0x44[31:0] = 0x00FF0000 | (value <<8); */ + /* write external PIN level */ + rtl_write_dword(rtlpriv, REG_GPIO_PIN_CTRL, + 0x00FF0000 | (u1b_tmp << 8)); + + /* k. GPIO_MUXCFG 0x42 [15:0] = 0x0780 */ + rtl_write_word(rtlpriv, REG_GPIO_IO_SEL, 0x0790); + + /* l. LEDCFG 0x4C[15:0] = 0x8080 */ + rtl_write_word(rtlpriv, REG_LEDCFG0, 0x8080); + + /* ==== Disable analog sequence === */ + + /* m. AFE_PLL_CTRL[7:0] = 0x80 disable PLL */ + rtl_write_byte(rtlpriv, REG_AFE_PLL_CTRL, 0x80); + + /* n. SPS0_CTRL 0x11[7:0] = 0x22 enter PFM mode */ + rtl_write_byte(rtlpriv, REG_SPS0_CTRL, 0x23); + + /* o. AFE_XTAL_CTRL 0x24[7:0] = 0x0E disable XTAL, if No BT COEX */ + rtl_write_byte(rtlpriv, REG_AFE_XTAL_CTRL, 0x0e); + + /* p. RSV_CTRL 0x1C[7:0] = 0x0E lock ISO/CLK/Power control register */ + rtl_write_byte(rtlpriv, REG_RSV_CTRL, 0x0e); + + /* ==== interface into suspend === */ + + /* q. APS_FSMCO[15:8] = 0x58 PCIe suspend mode */ + /* According to power document V11, we need to set this */ + /* value as 0x18. Otherwise, we may not L0s sometimes. */ + /* This indluences power consumption. Bases on SD1's test, */ + /* set as 0x00 do not affect power current. And if it */ + /* is set as 0x18, they had ever met auto load fail problem. */ + rtl_write_byte(rtlpriv, REG_APS_FSMCO + 1, 0x10); + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "In PowerOff,reg0x%x=%X\n", + REG_SPS0_CTRL, rtl_read_byte(rtlpriv, REG_SPS0_CTRL)); + /* r. Note: for PCIe interface, PON will not turn */ + /* off m-bias and BandGap in PCIe suspend mode. */ + + /* 0x17[7] 1b': power off in process 0b' : power off over */ + if (rtlpriv->rtlhal.macphymode != SINGLEMAC_SINGLEPHY) { + spin_lock_irqsave(&globalmutex_power, flags); + u1b_tmp = rtl_read_byte(rtlpriv, REG_POWER_OFF_IN_PROCESS); + u1b_tmp &= (~BIT(7)); + rtl_write_byte(rtlpriv, REG_POWER_OFF_IN_PROCESS, u1b_tmp); + spin_unlock_irqrestore(&globalmutex_power, flags); + } + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "<=======\n"); +} + +void rtl92de_card_disable(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + enum nl80211_iftype opmode; + + mac->link_state = MAC80211_NOLINK; + opmode = NL80211_IFTYPE_UNSPECIFIED; + _rtl92de_set_media_status(hw, opmode); + + if (rtlpci->driver_is_goingto_unload || + ppsc->rfoff_reason > RF_CHANGE_BY_PS) + rtlpriv->cfg->ops->led_control(hw, LED_CTL_POWER_OFF); + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + /* Power sequence for each MAC. */ + /* a. stop tx DMA */ + /* b. close RF */ + /* c. clear rx buf */ + /* d. stop rx DMA */ + /* e. reset MAC */ + + /* a. stop tx DMA */ + rtl_write_byte(rtlpriv, REG_PCIE_CTRL_REG + 1, 0xFE); + udelay(50); + + /* b. TXPAUSE 0x522[7:0] = 0xFF Pause MAC TX queue */ + + /* c. ========RF OFF sequence========== */ + /* 0x88c[23:20] = 0xf. */ + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER4, 0x00f00000, 0xf); + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, BRFREGOFFSETMASK, 0x00); + + /* APSD_CTRL 0x600[7:0] = 0x40 */ + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x40); + + /* Close antenna 0,0xc04,0xd04 */ + rtl_set_bbreg(hw, ROFDM0_TRXPATHENABLE, BMASKBYTE0, 0); + rtl_set_bbreg(hw, ROFDM1_TRXPATHENABLE, BDWORD, 0); + + /* SYS_FUNC_EN 0x02[7:0] = 0xE2 reset BB state machine */ + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE2); + + /* Mac0 can not do Global reset. Mac1 can do. */ + /* SYS_FUNC_EN 0x02[7:0] = 0xE0 reset BB state machine */ + if (rtlpriv->rtlhal.interfaceindex == 1) + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE0); + udelay(50); + + /* d. stop tx/rx dma before disable REG_CR (0x100) to fix */ + /* dma hang issue when disable/enable device. */ + rtl_write_byte(rtlpriv, REG_PCIE_CTRL_REG + 1, 0xff); + udelay(50); + rtl_write_byte(rtlpriv, REG_CR, 0x0); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "==> Do power off.......\n"); + if (rtl92d_phy_check_poweroff(hw)) + _rtl92de_poweroff_adapter(hw); + return; +} + +void rtl92de_interrupt_recognized(struct ieee80211_hw *hw, + u32 *p_inta, u32 *p_intb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + *p_inta = rtl_read_dword(rtlpriv, ISR) & rtlpci->irq_mask[0]; + rtl_write_dword(rtlpriv, ISR, *p_inta); + + /* + * *p_intb = rtl_read_dword(rtlpriv, REG_HISRE) & rtlpci->irq_mask[1]; + * rtl_write_dword(rtlpriv, ISR + 4, *p_intb); + */ +} + +void rtl92de_set_beacon_related_registers(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 bcn_interval, atim_window; + + bcn_interval = mac->beacon_interval; + atim_window = 2; + /*rtl92de_disable_interrupt(hw); */ + rtl_write_word(rtlpriv, REG_ATIMWND, atim_window); + rtl_write_word(rtlpriv, REG_BCN_INTERVAL, bcn_interval); + rtl_write_word(rtlpriv, REG_BCNTCFG, 0x660f); + rtl_write_byte(rtlpriv, REG_RXTSF_OFFSET_CCK, 0x20); + if (rtlpriv->rtlhal.current_bandtype == BAND_ON_5G) + rtl_write_byte(rtlpriv, REG_RXTSF_OFFSET_OFDM, 0x30); + else + rtl_write_byte(rtlpriv, REG_RXTSF_OFFSET_OFDM, 0x20); + rtl_write_byte(rtlpriv, 0x606, 0x30); +} + +void rtl92de_set_beacon_interval(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 bcn_interval = mac->beacon_interval; + + RT_TRACE(rtlpriv, COMP_BEACON, DBG_DMESG, + "beacon_interval:%d\n", bcn_interval); + /* rtl92de_disable_interrupt(hw); */ + rtl_write_word(rtlpriv, REG_BCN_INTERVAL, bcn_interval); + /* rtl92de_enable_interrupt(hw); */ +} + +void rtl92de_update_interrupt_mask(struct ieee80211_hw *hw, + u32 add_msr, u32 rm_msr) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + RT_TRACE(rtlpriv, COMP_INTR, DBG_LOUD, "add_msr:%x, rm_msr:%x\n", + add_msr, rm_msr); + if (add_msr) + rtlpci->irq_mask[0] |= add_msr; + if (rm_msr) + rtlpci->irq_mask[0] &= (~rm_msr); + rtl92de_disable_interrupt(hw); + rtl92de_enable_interrupt(hw); +} + +static void _rtl92de_readpowervalue_fromprom(struct txpower_info *pwrinfo, + u8 *rom_content, bool autoLoadfail) +{ + u32 rfpath, eeaddr, group, offset1, offset2; + u8 i; + + memset(pwrinfo, 0, sizeof(struct txpower_info)); + if (autoLoadfail) { + for (group = 0; group < CHANNEL_GROUP_MAX; group++) { + for (rfpath = 0; rfpath < RF6052_MAX_PATH; rfpath++) { + if (group < CHANNEL_GROUP_MAX_2G) { + pwrinfo->cck_index[rfpath][group] = + EEPROM_DEFAULT_TXPOWERLEVEL_2G; + pwrinfo->ht40_1sindex[rfpath][group] = + EEPROM_DEFAULT_TXPOWERLEVEL_2G; + } else { + pwrinfo->ht40_1sindex[rfpath][group] = + EEPROM_DEFAULT_TXPOWERLEVEL_5G; + } + pwrinfo->ht40_2sindexdiff[rfpath][group] = + EEPROM_DEFAULT_HT40_2SDIFF; + pwrinfo->ht20indexdiff[rfpath][group] = + EEPROM_DEFAULT_HT20_DIFF; + pwrinfo->ofdmindexdiff[rfpath][group] = + EEPROM_DEFAULT_LEGACYHTTXPOWERDIFF; + pwrinfo->ht40maxoffset[rfpath][group] = + EEPROM_DEFAULT_HT40_PWRMAXOFFSET; + pwrinfo->ht20maxoffset[rfpath][group] = + EEPROM_DEFAULT_HT20_PWRMAXOFFSET; + } + } + for (i = 0; i < 3; i++) { + pwrinfo->tssi_a[i] = EEPROM_DEFAULT_TSSI; + pwrinfo->tssi_b[i] = EEPROM_DEFAULT_TSSI; + } + return; + } + + /* Maybe autoload OK,buf the tx power index value is not filled. + * If we find it, we set it to default value. */ + for (rfpath = 0; rfpath < RF6052_MAX_PATH; rfpath++) { + for (group = 0; group < CHANNEL_GROUP_MAX_2G; group++) { + eeaddr = EEPROM_CCK_TX_PWR_INX_2G + (rfpath * 3) + + group; + pwrinfo->cck_index[rfpath][group] = + (rom_content[eeaddr] == 0xFF) ? + (eeaddr > 0x7B ? + EEPROM_DEFAULT_TXPOWERLEVEL_5G : + EEPROM_DEFAULT_TXPOWERLEVEL_2G) : + rom_content[eeaddr]; + } + } + for (rfpath = 0; rfpath < RF6052_MAX_PATH; rfpath++) { + for (group = 0; group < CHANNEL_GROUP_MAX; group++) { + offset1 = group / 3; + offset2 = group % 3; + eeaddr = EEPROM_HT40_1S_TX_PWR_INX_2G + (rfpath * 3) + + offset2 + offset1 * 21; + pwrinfo->ht40_1sindex[rfpath][group] = + (rom_content[eeaddr] == 0xFF) ? (eeaddr > 0x7B ? + EEPROM_DEFAULT_TXPOWERLEVEL_5G : + EEPROM_DEFAULT_TXPOWERLEVEL_2G) : + rom_content[eeaddr]; + } + } + /* These just for 92D efuse offset. */ + for (group = 0; group < CHANNEL_GROUP_MAX; group++) { + for (rfpath = 0; rfpath < RF6052_MAX_PATH; rfpath++) { + int base1 = EEPROM_HT40_2S_TX_PWR_INX_DIFF_2G; + + offset1 = group / 3; + offset2 = group % 3; + + if (rom_content[base1 + offset2 + offset1 * 21] != 0xFF) + pwrinfo->ht40_2sindexdiff[rfpath][group] = + (rom_content[base1 + + offset2 + offset1 * 21] >> (rfpath * 4)) + & 0xF; + else + pwrinfo->ht40_2sindexdiff[rfpath][group] = + EEPROM_DEFAULT_HT40_2SDIFF; + if (rom_content[EEPROM_HT20_TX_PWR_INX_DIFF_2G + offset2 + + offset1 * 21] != 0xFF) + pwrinfo->ht20indexdiff[rfpath][group] = + (rom_content[EEPROM_HT20_TX_PWR_INX_DIFF_2G + + offset2 + offset1 * 21] >> (rfpath * 4)) + & 0xF; + else + pwrinfo->ht20indexdiff[rfpath][group] = + EEPROM_DEFAULT_HT20_DIFF; + if (rom_content[EEPROM_OFDM_TX_PWR_INX_DIFF_2G + offset2 + + offset1 * 21] != 0xFF) + pwrinfo->ofdmindexdiff[rfpath][group] = + (rom_content[EEPROM_OFDM_TX_PWR_INX_DIFF_2G + + offset2 + offset1 * 21] >> (rfpath * 4)) + & 0xF; + else + pwrinfo->ofdmindexdiff[rfpath][group] = + EEPROM_DEFAULT_LEGACYHTTXPOWERDIFF; + if (rom_content[EEPROM_HT40_MAX_PWR_OFFSET_2G + offset2 + + offset1 * 21] != 0xFF) + pwrinfo->ht40maxoffset[rfpath][group] = + (rom_content[EEPROM_HT40_MAX_PWR_OFFSET_2G + + offset2 + offset1 * 21] >> (rfpath * 4)) + & 0xF; + else + pwrinfo->ht40maxoffset[rfpath][group] = + EEPROM_DEFAULT_HT40_PWRMAXOFFSET; + if (rom_content[EEPROM_HT20_MAX_PWR_OFFSET_2G + offset2 + + offset1 * 21] != 0xFF) + pwrinfo->ht20maxoffset[rfpath][group] = + (rom_content[EEPROM_HT20_MAX_PWR_OFFSET_2G + + offset2 + offset1 * 21] >> (rfpath * 4)) & + 0xF; + else + pwrinfo->ht20maxoffset[rfpath][group] = + EEPROM_DEFAULT_HT20_PWRMAXOFFSET; + } + } + if (rom_content[EEPROM_TSSI_A_5G] != 0xFF) { + /* 5GL */ + pwrinfo->tssi_a[0] = rom_content[EEPROM_TSSI_A_5G] & 0x3F; + pwrinfo->tssi_b[0] = rom_content[EEPROM_TSSI_B_5G] & 0x3F; + /* 5GM */ + pwrinfo->tssi_a[1] = rom_content[EEPROM_TSSI_AB_5G] & 0x3F; + pwrinfo->tssi_b[1] = + (rom_content[EEPROM_TSSI_AB_5G] & 0xC0) >> 6 | + (rom_content[EEPROM_TSSI_AB_5G + 1] & 0x0F) << 2; + /* 5GH */ + pwrinfo->tssi_a[2] = (rom_content[EEPROM_TSSI_AB_5G + 1] & + 0xF0) >> 4 | + (rom_content[EEPROM_TSSI_AB_5G + 2] & 0x03) << 4; + pwrinfo->tssi_b[2] = (rom_content[EEPROM_TSSI_AB_5G + 2] & + 0xFC) >> 2; + } else { + for (i = 0; i < 3; i++) { + pwrinfo->tssi_a[i] = EEPROM_DEFAULT_TSSI; + pwrinfo->tssi_b[i] = EEPROM_DEFAULT_TSSI; + } + } +} + +static void _rtl92de_read_txpower_info(struct ieee80211_hw *hw, + bool autoload_fail, u8 *hwinfo) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct txpower_info pwrinfo; + u8 tempval[2], i, pwr, diff; + u32 ch, rfPath, group; + + _rtl92de_readpowervalue_fromprom(&pwrinfo, hwinfo, autoload_fail); + if (!autoload_fail) { + /* bit0~2 */ + rtlefuse->eeprom_regulatory = (hwinfo[EEPROM_RF_OPT1] & 0x7); + rtlefuse->eeprom_thermalmeter = + hwinfo[EEPROM_THERMAL_METER] & 0x1f; + rtlefuse->crystalcap = hwinfo[EEPROM_XTAL_K]; + tempval[0] = hwinfo[EEPROM_IQK_DELTA] & 0x03; + tempval[1] = (hwinfo[EEPROM_LCK_DELTA] & 0x0C) >> 2; + rtlefuse->txpwr_fromeprom = true; + if (IS_92D_D_CUT(rtlpriv->rtlhal.version) || + IS_92D_E_CUT(rtlpriv->rtlhal.version)) { + rtlefuse->internal_pa_5g[0] = + !((hwinfo[EEPROM_TSSI_A_5G] & BIT(6)) >> 6); + rtlefuse->internal_pa_5g[1] = + !((hwinfo[EEPROM_TSSI_B_5G] & BIT(6)) >> 6); + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "Is D cut,Internal PA0 %d Internal PA1 %d\n", + rtlefuse->internal_pa_5g[0], + rtlefuse->internal_pa_5g[1]); + } + rtlefuse->eeprom_c9 = hwinfo[EEPROM_RF_OPT6]; + rtlefuse->eeprom_cc = hwinfo[EEPROM_RF_OPT7]; + } else { + rtlefuse->eeprom_regulatory = 0; + rtlefuse->eeprom_thermalmeter = EEPROM_DEFAULT_THERMALMETER; + rtlefuse->crystalcap = EEPROM_DEFAULT_CRYSTALCAP; + tempval[0] = tempval[1] = 3; + } + + /* Use default value to fill parameters if + * efuse is not filled on some place. */ + + /* ThermalMeter from EEPROM */ + if (rtlefuse->eeprom_thermalmeter < 0x06 || + rtlefuse->eeprom_thermalmeter > 0x1c) + rtlefuse->eeprom_thermalmeter = 0x12; + rtlefuse->thermalmeter[0] = rtlefuse->eeprom_thermalmeter; + + /* check XTAL_K */ + if (rtlefuse->crystalcap == 0xFF) + rtlefuse->crystalcap = 0; + if (rtlefuse->eeprom_regulatory > 3) + rtlefuse->eeprom_regulatory = 0; + + for (i = 0; i < 2; i++) { + switch (tempval[i]) { + case 0: + tempval[i] = 5; + break; + case 1: + tempval[i] = 4; + break; + case 2: + tempval[i] = 3; + break; + case 3: + default: + tempval[i] = 0; + break; + } + } + + rtlefuse->delta_iqk = tempval[0]; + if (tempval[1] > 0) + rtlefuse->delta_lck = tempval[1] - 1; + if (rtlefuse->eeprom_c9 == 0xFF) + rtlefuse->eeprom_c9 = 0x00; + RT_TRACE(rtlpriv, COMP_INTR, DBG_LOUD, + "EEPROMRegulatory = 0x%x\n", rtlefuse->eeprom_regulatory); + RT_TRACE(rtlpriv, COMP_INTR, DBG_LOUD, + "ThermalMeter = 0x%x\n", rtlefuse->eeprom_thermalmeter); + RT_TRACE(rtlpriv, COMP_INTR, DBG_LOUD, + "CrystalCap = 0x%x\n", rtlefuse->crystalcap); + RT_TRACE(rtlpriv, COMP_INTR, DBG_LOUD, + "Delta_IQK = 0x%x Delta_LCK = 0x%x\n", + rtlefuse->delta_iqk, rtlefuse->delta_lck); + + for (rfPath = 0; rfPath < RF6052_MAX_PATH; rfPath++) { + for (ch = 0; ch < CHANNEL_MAX_NUMBER; ch++) { + group = rtl92d_get_chnlgroup_fromarray((u8) ch); + if (ch < CHANNEL_MAX_NUMBER_2G) + rtlefuse->txpwrlevel_cck[rfPath][ch] = + pwrinfo.cck_index[rfPath][group]; + rtlefuse->txpwrlevel_ht40_1s[rfPath][ch] = + pwrinfo.ht40_1sindex[rfPath][group]; + rtlefuse->txpwr_ht20diff[rfPath][ch] = + pwrinfo.ht20indexdiff[rfPath][group]; + rtlefuse->txpwr_legacyhtdiff[rfPath][ch] = + pwrinfo.ofdmindexdiff[rfPath][group]; + rtlefuse->pwrgroup_ht20[rfPath][ch] = + pwrinfo.ht20maxoffset[rfPath][group]; + rtlefuse->pwrgroup_ht40[rfPath][ch] = + pwrinfo.ht40maxoffset[rfPath][group]; + pwr = pwrinfo.ht40_1sindex[rfPath][group]; + diff = pwrinfo.ht40_2sindexdiff[rfPath][group]; + rtlefuse->txpwrlevel_ht40_2s[rfPath][ch] = + (pwr > diff) ? (pwr - diff) : 0; + } + } +} + +static void _rtl92de_read_macphymode_from_prom(struct ieee80211_hw *hw, + u8 *content) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 macphy_crvalue = content[EEPROM_MAC_FUNCTION]; + + if (macphy_crvalue & BIT(3)) { + rtlhal->macphymode = SINGLEMAC_SINGLEPHY; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "MacPhyMode SINGLEMAC_SINGLEPHY\n"); + } else { + rtlhal->macphymode = DUALMAC_DUALPHY; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "MacPhyMode DUALMAC_DUALPHY\n"); + } +} + +static void _rtl92de_read_macphymode_and_bandtype(struct ieee80211_hw *hw, + u8 *content) +{ + _rtl92de_read_macphymode_from_prom(hw, content); + rtl92d_phy_config_macphymode(hw); + rtl92d_phy_config_macphymode_info(hw); +} + +static void _rtl92de_efuse_update_chip_version(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + enum version_8192d chipver = rtlpriv->rtlhal.version; + u8 cutvalue[2]; + u16 chipvalue; + + rtlpriv->intf_ops->read_efuse_byte(hw, EEPROME_CHIP_VERSION_H, + &cutvalue[1]); + rtlpriv->intf_ops->read_efuse_byte(hw, EEPROME_CHIP_VERSION_L, + &cutvalue[0]); + chipvalue = (cutvalue[1] << 8) | cutvalue[0]; + switch (chipvalue) { + case 0xAA55: + chipver |= CHIP_92D_C_CUT; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "C-CUT!!!\n"); + break; + case 0x9966: + chipver |= CHIP_92D_D_CUT; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "D-CUT!!!\n"); + break; + case 0xCC33: + chipver |= CHIP_92D_E_CUT; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "E-CUT!!!\n"); + break; + default: + chipver |= CHIP_92D_D_CUT; + RT_TRACE(rtlpriv, COMP_INIT, DBG_EMERG, "Unknown CUT!\n"); + break; + } + rtlpriv->rtlhal.version = chipver; +} + +static void _rtl92de_read_adapter_info(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u16 i, usvalue; + u8 hwinfo[HWSET_MAX_SIZE]; + u16 eeprom_id; + unsigned long flags; + + if (rtlefuse->epromtype == EEPROM_BOOT_EFUSE) { + spin_lock_irqsave(&globalmutex_for_power_and_efuse, flags); + rtl_efuse_shadow_map_update(hw); + _rtl92de_efuse_update_chip_version(hw); + spin_unlock_irqrestore(&globalmutex_for_power_and_efuse, flags); + memcpy((void *)hwinfo, (void *)&rtlefuse->efuse_map + [EFUSE_INIT_MAP][0], + HWSET_MAX_SIZE); + } else if (rtlefuse->epromtype == EEPROM_93C46) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "RTL819X Not boot from eeprom, check it !!\n"); + } + RT_PRINT_DATA(rtlpriv, COMP_INIT, DBG_DMESG, "MAP", + hwinfo, HWSET_MAX_SIZE); + + eeprom_id = *((u16 *)&hwinfo[0]); + if (eeprom_id != RTL8190_EEPROM_ID) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "EEPROM ID(%#x) is invalid!!\n", eeprom_id); + rtlefuse->autoload_failflag = true; + } else { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Autoload OK\n"); + rtlefuse->autoload_failflag = false; + } + if (rtlefuse->autoload_failflag) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "RTL819X Not boot from eeprom, check it !!\n"); + return; + } + rtlefuse->eeprom_oemid = hwinfo[EEPROM_CUSTOMER_ID]; + _rtl92de_read_macphymode_and_bandtype(hw, hwinfo); + + /* VID, DID SE 0xA-D */ + rtlefuse->eeprom_vid = *(u16 *)&hwinfo[EEPROM_VID]; + rtlefuse->eeprom_did = *(u16 *)&hwinfo[EEPROM_DID]; + rtlefuse->eeprom_svid = *(u16 *)&hwinfo[EEPROM_SVID]; + rtlefuse->eeprom_smid = *(u16 *)&hwinfo[EEPROM_SMID]; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "EEPROMId = 0x%4x\n", eeprom_id); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM VID = 0x%4x\n", rtlefuse->eeprom_vid); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM DID = 0x%4x\n", rtlefuse->eeprom_did); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM SVID = 0x%4x\n", rtlefuse->eeprom_svid); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM SMID = 0x%4x\n", rtlefuse->eeprom_smid); + + /* Read Permanent MAC address */ + if (rtlhal->interfaceindex == 0) { + for (i = 0; i < 6; i += 2) { + usvalue = *(u16 *)&hwinfo[EEPROM_MAC_ADDR_MAC0_92D + i]; + *((u16 *) (&rtlefuse->dev_addr[i])) = usvalue; + } + } else { + for (i = 0; i < 6; i += 2) { + usvalue = *(u16 *)&hwinfo[EEPROM_MAC_ADDR_MAC1_92D + i]; + *((u16 *) (&rtlefuse->dev_addr[i])) = usvalue; + } + } + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_ETHER_ADDR, + rtlefuse->dev_addr); + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "%pM\n", rtlefuse->dev_addr); + _rtl92de_read_txpower_info(hw, rtlefuse->autoload_failflag, hwinfo); + + /* Read Channel Plan */ + switch (rtlhal->bandset) { + case BAND_ON_2_4G: + rtlefuse->channel_plan = COUNTRY_CODE_TELEC; + break; + case BAND_ON_5G: + rtlefuse->channel_plan = COUNTRY_CODE_FCC; + break; + case BAND_ON_BOTH: + rtlefuse->channel_plan = COUNTRY_CODE_FCC; + break; + default: + rtlefuse->channel_plan = COUNTRY_CODE_FCC; + break; + } + rtlefuse->eeprom_version = *(u16 *)&hwinfo[EEPROM_VERSION]; + rtlefuse->txpwr_fromeprom = true; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM Customer ID: 0x%2x\n", rtlefuse->eeprom_oemid); +} + +void rtl92de_read_eeprom_info(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 tmp_u1b; + + rtlhal->version = _rtl92de_read_chip_version(hw); + tmp_u1b = rtl_read_byte(rtlpriv, REG_9346CR); + rtlefuse->autoload_status = tmp_u1b; + if (tmp_u1b & BIT(4)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "Boot from EEPROM\n"); + rtlefuse->epromtype = EEPROM_93C46; + } else { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "Boot from EFUSE\n"); + rtlefuse->epromtype = EEPROM_BOOT_EFUSE; + } + if (tmp_u1b & BIT(5)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Autoload OK\n"); + + rtlefuse->autoload_failflag = false; + _rtl92de_read_adapter_info(hw); + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Autoload ERR!!\n"); + } + return; +} + +static void rtl92de_update_hal_rate_table(struct ieee80211_hw *hw, + struct ieee80211_sta *sta) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u32 ratr_value; + u8 ratr_index = 0; + u8 nmode = mac->ht_enable; + u8 mimo_ps = IEEE80211_SMPS_OFF; + u16 shortgi_rate; + u32 tmp_ratr_value; + u8 curtxbw_40mhz = mac->bw_40; + u8 curshortgi_40mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_40) ? + 1 : 0; + u8 curshortgi_20mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_20) ? + 1 : 0; + enum wireless_mode wirelessmode = mac->mode; + + if (rtlhal->current_bandtype == BAND_ON_5G) + ratr_value = sta->supp_rates[1] << 4; + else + ratr_value = sta->supp_rates[0]; + ratr_value |= (sta->ht_cap.mcs.rx_mask[1] << 20 | + sta->ht_cap.mcs.rx_mask[0] << 12); + switch (wirelessmode) { + case WIRELESS_MODE_A: + ratr_value &= 0x00000FF0; + break; + case WIRELESS_MODE_B: + if (ratr_value & 0x0000000c) + ratr_value &= 0x0000000d; + else + ratr_value &= 0x0000000f; + break; + case WIRELESS_MODE_G: + ratr_value &= 0x00000FF5; + break; + case WIRELESS_MODE_N_24G: + case WIRELESS_MODE_N_5G: + nmode = 1; + if (mimo_ps == IEEE80211_SMPS_STATIC) { + ratr_value &= 0x0007F005; + } else { + u32 ratr_mask; + + if (get_rf_type(rtlphy) == RF_1T2R || + get_rf_type(rtlphy) == RF_1T1R) { + ratr_mask = 0x000ff005; + } else { + ratr_mask = 0x0f0ff005; + } + + ratr_value &= ratr_mask; + } + break; + default: + if (rtlphy->rf_type == RF_1T2R) + ratr_value &= 0x000ff0ff; + else + ratr_value &= 0x0f0ff0ff; + + break; + } + ratr_value &= 0x0FFFFFFF; + if (nmode && ((curtxbw_40mhz && curshortgi_40mhz) || + (!curtxbw_40mhz && curshortgi_20mhz))) { + ratr_value |= 0x10000000; + tmp_ratr_value = (ratr_value >> 12); + for (shortgi_rate = 15; shortgi_rate > 0; shortgi_rate--) { + if ((1 << shortgi_rate) & tmp_ratr_value) + break; + } + shortgi_rate = (shortgi_rate << 12) | (shortgi_rate << 8) | + (shortgi_rate << 4) | (shortgi_rate); + } + rtl_write_dword(rtlpriv, REG_ARFR0 + ratr_index * 4, ratr_value); + RT_TRACE(rtlpriv, COMP_RATR, DBG_DMESG, "%x\n", + rtl_read_dword(rtlpriv, REG_ARFR0)); +} + +static void rtl92de_update_hal_rate_mask(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 rssi_level) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_sta_info *sta_entry = NULL; + u32 ratr_bitmap; + u8 ratr_index; + u8 curtxbw_40mhz = (sta->bandwidth >= IEEE80211_STA_RX_BW_40) ? 1 : 0; + u8 curshortgi_40mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_40) ? + 1 : 0; + u8 curshortgi_20mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_20) ? + 1 : 0; + enum wireless_mode wirelessmode = 0; + bool shortgi = false; + u32 value[2]; + u8 macid = 0; + u8 mimo_ps = IEEE80211_SMPS_OFF; + + sta_entry = (struct rtl_sta_info *) sta->drv_priv; + mimo_ps = sta_entry->mimo_ps; + wirelessmode = sta_entry->wireless_mode; + if (mac->opmode == NL80211_IFTYPE_STATION) + curtxbw_40mhz = mac->bw_40; + else if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_ADHOC) + macid = sta->aid + 1; + + if (rtlhal->current_bandtype == BAND_ON_5G) + ratr_bitmap = sta->supp_rates[1] << 4; + else + ratr_bitmap = sta->supp_rates[0]; + ratr_bitmap |= (sta->ht_cap.mcs.rx_mask[1] << 20 | + sta->ht_cap.mcs.rx_mask[0] << 12); + switch (wirelessmode) { + case WIRELESS_MODE_B: + ratr_index = RATR_INX_WIRELESS_B; + if (ratr_bitmap & 0x0000000c) + ratr_bitmap &= 0x0000000d; + else + ratr_bitmap &= 0x0000000f; + break; + case WIRELESS_MODE_G: + ratr_index = RATR_INX_WIRELESS_GB; + + if (rssi_level == 1) + ratr_bitmap &= 0x00000f00; + else if (rssi_level == 2) + ratr_bitmap &= 0x00000ff0; + else + ratr_bitmap &= 0x00000ff5; + break; + case WIRELESS_MODE_A: + ratr_index = RATR_INX_WIRELESS_G; + ratr_bitmap &= 0x00000ff0; + break; + case WIRELESS_MODE_N_24G: + case WIRELESS_MODE_N_5G: + if (wirelessmode == WIRELESS_MODE_N_24G) + ratr_index = RATR_INX_WIRELESS_NGB; + else + ratr_index = RATR_INX_WIRELESS_NG; + if (mimo_ps == IEEE80211_SMPS_STATIC) { + if (rssi_level == 1) + ratr_bitmap &= 0x00070000; + else if (rssi_level == 2) + ratr_bitmap &= 0x0007f000; + else + ratr_bitmap &= 0x0007f005; + } else { + if (rtlphy->rf_type == RF_1T2R || + rtlphy->rf_type == RF_1T1R) { + if (curtxbw_40mhz) { + if (rssi_level == 1) + ratr_bitmap &= 0x000f0000; + else if (rssi_level == 2) + ratr_bitmap &= 0x000ff000; + else + ratr_bitmap &= 0x000ff015; + } else { + if (rssi_level == 1) + ratr_bitmap &= 0x000f0000; + else if (rssi_level == 2) + ratr_bitmap &= 0x000ff000; + else + ratr_bitmap &= 0x000ff005; + } + } else { + if (curtxbw_40mhz) { + if (rssi_level == 1) + ratr_bitmap &= 0x0f0f0000; + else if (rssi_level == 2) + ratr_bitmap &= 0x0f0ff000; + else + ratr_bitmap &= 0x0f0ff015; + } else { + if (rssi_level == 1) + ratr_bitmap &= 0x0f0f0000; + else if (rssi_level == 2) + ratr_bitmap &= 0x0f0ff000; + else + ratr_bitmap &= 0x0f0ff005; + } + } + } + if ((curtxbw_40mhz && curshortgi_40mhz) || + (!curtxbw_40mhz && curshortgi_20mhz)) { + + if (macid == 0) + shortgi = true; + else if (macid == 1) + shortgi = false; + } + break; + default: + ratr_index = RATR_INX_WIRELESS_NGB; + + if (rtlphy->rf_type == RF_1T2R) + ratr_bitmap &= 0x000ff0ff; + else + ratr_bitmap &= 0x0f0ff0ff; + break; + } + + value[0] = (ratr_bitmap & 0x0fffffff) | (ratr_index << 28); + value[1] = macid | (shortgi ? 0x20 : 0x00) | 0x80; + RT_TRACE(rtlpriv, COMP_RATR, DBG_DMESG, + "ratr_bitmap :%x value0:%x value1:%x\n", + ratr_bitmap, value[0], value[1]); + rtl92d_fill_h2c_cmd(hw, H2C_RA_MASK, 5, (u8 *) value); + if (macid != 0) + sta_entry->ratr_index = ratr_index; +} + +void rtl92de_update_hal_rate_tbl(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 rssi_level) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (rtlpriv->dm.useramask) + rtl92de_update_hal_rate_mask(hw, sta, rssi_level); + else + rtl92de_update_hal_rate_table(hw, sta); +} + +void rtl92de_update_channel_access_setting(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 sifs_timer; + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SLOT_TIME, + &mac->slot_time); + if (!mac->ht_enable) + sifs_timer = 0x0a0a; + else + sifs_timer = 0x1010; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SIFS, (u8 *)&sifs_timer); +} + +bool rtl92de_gpio_radio_on_off_checking(struct ieee80211_hw *hw, u8 *valid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + enum rf_pwrstate e_rfpowerstate_toset; + u8 u1tmp; + bool actuallyset = false; + unsigned long flag; + + if (rtlpci->being_init_adapter) + return false; + if (ppsc->swrf_processing) + return false; + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); + if (ppsc->rfchange_inprogress) { + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + return false; + } else { + ppsc->rfchange_inprogress = true; + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + } + rtl_write_byte(rtlpriv, REG_MAC_PINMUX_CFG, rtl_read_byte(rtlpriv, + REG_MAC_PINMUX_CFG) & ~(BIT(3))); + u1tmp = rtl_read_byte(rtlpriv, REG_GPIO_IO_SEL); + e_rfpowerstate_toset = (u1tmp & BIT(3)) ? ERFON : ERFOFF; + if (ppsc->hwradiooff && (e_rfpowerstate_toset == ERFON)) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "GPIOChangeRF - HW Radio ON, RF ON\n"); + e_rfpowerstate_toset = ERFON; + ppsc->hwradiooff = false; + actuallyset = true; + } else if (!ppsc->hwradiooff && (e_rfpowerstate_toset == ERFOFF)) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "GPIOChangeRF - HW Radio OFF, RF OFF\n"); + e_rfpowerstate_toset = ERFOFF; + ppsc->hwradiooff = true; + actuallyset = true; + } + if (actuallyset) { + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); + ppsc->rfchange_inprogress = false; + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + } else { + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_HALT_NIC) + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); + ppsc->rfchange_inprogress = false; + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + } + *valid = 1; + return !ppsc->hwradiooff; +} + +void rtl92de_set_key(struct ieee80211_hw *hw, u32 key_index, + u8 *p_macaddr, bool is_group, u8 enc_algo, + bool is_wepkey, bool clear_all) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 *macaddr = p_macaddr; + u32 entry_id; + bool is_pairwise = false; + static u8 cam_const_addr[4][6] = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x03} + }; + static u8 cam_const_broad[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + if (clear_all) { + u8 idx; + u8 cam_offset = 0; + u8 clear_number = 5; + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, "clear_all\n"); + for (idx = 0; idx < clear_number; idx++) { + rtl_cam_mark_invalid(hw, cam_offset + idx); + rtl_cam_empty_entry(hw, cam_offset + idx); + + if (idx < 5) { + memset(rtlpriv->sec.key_buf[idx], 0, + MAX_KEY_LEN); + rtlpriv->sec.key_len[idx] = 0; + } + } + } else { + switch (enc_algo) { + case WEP40_ENCRYPTION: + enc_algo = CAM_WEP40; + break; + case WEP104_ENCRYPTION: + enc_algo = CAM_WEP104; + break; + case TKIP_ENCRYPTION: + enc_algo = CAM_TKIP; + break; + case AESCCMP_ENCRYPTION: + enc_algo = CAM_AES; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + enc_algo = CAM_TKIP; + break; + } + if (is_wepkey || rtlpriv->sec.use_defaultkey) { + macaddr = cam_const_addr[key_index]; + entry_id = key_index; + } else { + if (is_group) { + macaddr = cam_const_broad; + entry_id = key_index; + } else { + if (mac->opmode == NL80211_IFTYPE_AP) { + entry_id = rtl_cam_get_free_entry(hw, + p_macaddr); + if (entry_id >= TOTAL_CAM_ENTRY) { + RT_TRACE(rtlpriv, COMP_SEC, + DBG_EMERG, + "Can not find free hw security cam entry\n"); + return; + } + } else { + entry_id = CAM_PAIRWISE_KEY_POSITION; + } + key_index = PAIRWISE_KEYIDX; + is_pairwise = true; + } + } + if (rtlpriv->sec.key_len[key_index] == 0) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "delete one entry, entry_id is %d\n", + entry_id); + if (mac->opmode == NL80211_IFTYPE_AP) + rtl_cam_del_entry(hw, p_macaddr); + rtl_cam_delete_one_entry(hw, p_macaddr, entry_id); + } else { + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, + "The insert KEY length is %d\n", + rtlpriv->sec.key_len[PAIRWISE_KEYIDX]); + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, + "The insert KEY is %x %x\n", + rtlpriv->sec.key_buf[0][0], + rtlpriv->sec.key_buf[0][1]); + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "add one entry\n"); + if (is_pairwise) { + RT_PRINT_DATA(rtlpriv, COMP_SEC, DBG_LOUD, + "Pairwise Key content", + rtlpriv->sec.pairwise_key, + rtlpriv-> + sec.key_len[PAIRWISE_KEYIDX]); + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "set Pairwise key\n"); + rtl_cam_add_one_entry(hw, macaddr, key_index, + entry_id, enc_algo, + CAM_CONFIG_NO_USEDK, + rtlpriv-> + sec.key_buf[key_index]); + } else { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "set group key\n"); + if (mac->opmode == NL80211_IFTYPE_ADHOC) { + rtl_cam_add_one_entry(hw, + rtlefuse->dev_addr, + PAIRWISE_KEYIDX, + CAM_PAIRWISE_KEY_POSITION, + enc_algo, CAM_CONFIG_NO_USEDK, + rtlpriv->sec.key_buf[entry_id]); + } + rtl_cam_add_one_entry(hw, macaddr, key_index, + entry_id, enc_algo, + CAM_CONFIG_NO_USEDK, + rtlpriv->sec.key_buf + [entry_id]); + } + } + } +} + +void rtl92de_suspend(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->rtlhal.macphyctl_reg = rtl_read_byte(rtlpriv, + REG_MAC_PHY_CTRL_NORMAL); +} + +void rtl92de_resume(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_byte(rtlpriv, REG_MAC_PHY_CTRL_NORMAL, + rtlpriv->rtlhal.macphyctl_reg); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/hw.h b/drivers/net/wireless/rtlwifi/rtl8192de/hw.h new file mode 100644 index 0000000..7c9f7a2 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/hw.h @@ -0,0 +1,66 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92DE_HW_H__ +#define __RTL92DE_HW_H__ + +void rtl92de_get_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val); +void rtl92de_read_eeprom_info(struct ieee80211_hw *hw); +void rtl92de_interrupt_recognized(struct ieee80211_hw *hw, + u32 *p_inta, u32 *p_intb); +int rtl92de_hw_init(struct ieee80211_hw *hw); +void rtl92de_card_disable(struct ieee80211_hw *hw); +void rtl92de_enable_interrupt(struct ieee80211_hw *hw); +void rtl92de_disable_interrupt(struct ieee80211_hw *hw); +int rtl92de_set_network_type(struct ieee80211_hw *hw, enum nl80211_iftype type); +void rtl92de_set_check_bssid(struct ieee80211_hw *hw, bool check_bssid); +void rtl92de_set_qos(struct ieee80211_hw *hw, int aci); +void rtl92de_set_beacon_related_registers(struct ieee80211_hw *hw); +void rtl92de_set_beacon_interval(struct ieee80211_hw *hw); +void rtl92de_update_interrupt_mask(struct ieee80211_hw *hw, + u32 add_msr, u32 rm_msr); +void rtl92de_set_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val); +void rtl92de_update_hal_rate_tbl(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 rssi_level); +void rtl92de_update_channel_access_setting(struct ieee80211_hw *hw); +bool rtl92de_gpio_radio_on_off_checking(struct ieee80211_hw *hw, u8 *valid); +void rtl92de_enable_hw_security_config(struct ieee80211_hw *hw); +void rtl92de_set_key(struct ieee80211_hw *hw, u32 key_index, + u8 *p_macaddr, bool is_group, u8 enc_algo, + bool is_wepkey, bool clear_all); + +extern void rtl92de_write_dword_dbi(struct ieee80211_hw *hw, u16 offset, + u32 value, u8 direct); +extern u32 rtl92de_read_dword_dbi(struct ieee80211_hw *hw, u16 offset, + u8 direct); +void rtl92de_suspend(struct ieee80211_hw *hw); +void rtl92de_resume(struct ieee80211_hw *hw); +void rtl92d_linked_set_reg(struct ieee80211_hw *hw); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/led.c b/drivers/net/wireless/rtlwifi/rtl8192de/led.c new file mode 100644 index 0000000..76a57ae --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/led.c @@ -0,0 +1,159 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "reg.h" +#include "led.h" + +static void _rtl92ce_init_led(struct ieee80211_hw *hw, + struct rtl_led *pled, enum rtl_led_pin ledpin) +{ + pled->hw = hw; + pled->ledpin = ledpin; + pled->ledon = false; +} + +void rtl92de_sw_led_on(struct ieee80211_hw *hw, struct rtl_led *pled) +{ + u8 ledcfg; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, "LedAddr:%X ledpin=%d\n", + REG_LEDCFG2, pled->ledpin); + + switch (pled->ledpin) { + case LED_PIN_GPIO0: + break; + case LED_PIN_LED0: + ledcfg = rtl_read_byte(rtlpriv, REG_LEDCFG2); + + if ((rtlpriv->efuse.eeprom_did == 0x8176) || + (rtlpriv->efuse.eeprom_did == 0x8193)) + /* BIT7 of REG_LEDCFG2 should be set to + * make sure we could emit the led2. */ + rtl_write_byte(rtlpriv, REG_LEDCFG2, (ledcfg & 0xf0) | + BIT(7) | BIT(5) | BIT(6)); + else + rtl_write_byte(rtlpriv, REG_LEDCFG2, (ledcfg & 0xf0) | + BIT(7) | BIT(5)); + break; + case LED_PIN_LED1: + ledcfg = rtl_read_byte(rtlpriv, REG_LEDCFG1); + + rtl_write_byte(rtlpriv, REG_LEDCFG2, (ledcfg & 0x0f) | BIT(5)); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + pled->ledon = true; +} + +void rtl92de_sw_led_off(struct ieee80211_hw *hw, struct rtl_led *pled) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + u8 ledcfg; + + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, "LedAddr:%X ledpin=%d\n", + REG_LEDCFG2, pled->ledpin); + + ledcfg = rtl_read_byte(rtlpriv, REG_LEDCFG2); + + switch (pled->ledpin) { + case LED_PIN_GPIO0: + break; + case LED_PIN_LED0: + ledcfg &= 0xf0; + if (pcipriv->ledctl.led_opendrain) + rtl_write_byte(rtlpriv, REG_LEDCFG2, + (ledcfg | BIT(1) | BIT(5) | BIT(6))); + else + rtl_write_byte(rtlpriv, REG_LEDCFG2, + (ledcfg | BIT(3) | BIT(5) | BIT(6))); + break; + case LED_PIN_LED1: + ledcfg &= 0x0f; + rtl_write_byte(rtlpriv, REG_LEDCFG2, (ledcfg | BIT(3))); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + pled->ledon = false; +} + +void rtl92de_init_sw_leds(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + _rtl92ce_init_led(hw, &(pcipriv->ledctl.sw_led0), LED_PIN_LED0); + _rtl92ce_init_led(hw, &(pcipriv->ledctl.sw_led1), LED_PIN_LED1); +} + +static void _rtl92ce_sw_led_control(struct ieee80211_hw *hw, + enum led_ctl_mode ledaction) +{ + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_led *pLed0 = &(pcipriv->ledctl.sw_led0); + switch (ledaction) { + case LED_CTL_POWER_ON: + case LED_CTL_LINK: + case LED_CTL_NO_LINK: + rtl92de_sw_led_on(hw, pLed0); + break; + case LED_CTL_POWER_OFF: + rtl92de_sw_led_off(hw, pLed0); + break; + default: + break; + } +} + +void rtl92de_led_control(struct ieee80211_hw *hw, enum led_ctl_mode ledaction) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + if ((ppsc->rfoff_reason > RF_CHANGE_BY_PS) && + (ledaction == LED_CTL_TX || + ledaction == LED_CTL_RX || + ledaction == LED_CTL_SITE_SURVEY || + ledaction == LED_CTL_LINK || + ledaction == LED_CTL_NO_LINK || + ledaction == LED_CTL_START_TO_LINK || + ledaction == LED_CTL_POWER_ON)) { + return; + } + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, "ledaction %d,\n", ledaction); + + _rtl92ce_sw_led_control(hw, ledaction); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/led.h b/drivers/net/wireless/rtlwifi/rtl8192de/led.h new file mode 100644 index 0000000..a29df30 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/led.h @@ -0,0 +1,38 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CE_LED_H__ +#define __RTL92CE_LED_H__ + +void rtl92de_init_sw_leds(struct ieee80211_hw *hw); +void rtl92de_sw_led_on(struct ieee80211_hw *hw, struct rtl_led *pled); +void rtl92de_sw_led_off(struct ieee80211_hw *hw, struct rtl_led *pled); +void rtl92de_led_control(struct ieee80211_hw *hw, enum led_ctl_mode ledaction); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/phy.c b/drivers/net/wireless/rtlwifi/rtl8192de/phy.c new file mode 100644 index 0000000..840bac5 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/phy.c @@ -0,0 +1,3695 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "../ps.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "rf.h" +#include "dm.h" +#include "table.h" +#include "sw.h" +#include "hw.h" + +#define MAX_RF_IMR_INDEX 12 +#define MAX_RF_IMR_INDEX_NORMAL 13 +#define RF_REG_NUM_FOR_C_CUT_5G 6 +#define RF_REG_NUM_FOR_C_CUT_5G_INTERNALPA 7 +#define RF_REG_NUM_FOR_C_CUT_2G 5 +#define RF_CHNL_NUM_5G 19 +#define RF_CHNL_NUM_5G_40M 17 +#define TARGET_CHNL_NUM_5G 221 +#define TARGET_CHNL_NUM_2G 14 +#define CV_CURVE_CNT 64 + +static u32 rf_reg_for_5g_swchnl_normal[MAX_RF_IMR_INDEX_NORMAL] = { + 0, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x0 +}; + +static u8 rf_reg_for_c_cut_5g[RF_REG_NUM_FOR_C_CUT_5G] = { + RF_SYN_G1, RF_SYN_G2, RF_SYN_G3, RF_SYN_G4, RF_SYN_G5, RF_SYN_G6 +}; + +static u8 rf_reg_for_c_cut_2g[RF_REG_NUM_FOR_C_CUT_2G] = { + RF_SYN_G1, RF_SYN_G2, RF_SYN_G3, RF_SYN_G7, RF_SYN_G8 +}; + +static u8 rf_for_c_cut_5g_internal_pa[RF_REG_NUM_FOR_C_CUT_5G_INTERNALPA] = { + 0x0B, 0x48, 0x49, 0x4B, 0x03, 0x04, 0x0E +}; + +static u32 rf_reg_mask_for_c_cut_2g[RF_REG_NUM_FOR_C_CUT_2G] = { + BIT(19) | BIT(18) | BIT(17) | BIT(14) | BIT(1), + BIT(10) | BIT(9), + BIT(18) | BIT(17) | BIT(16) | BIT(1), + BIT(2) | BIT(1), + BIT(15) | BIT(14) | BIT(13) | BIT(12) | BIT(11) +}; + +static u8 rf_chnl_5g[RF_CHNL_NUM_5G] = { + 36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, + 112, 116, 120, 124, 128, 132, 136, 140 +}; + +static u8 rf_chnl_5g_40m[RF_CHNL_NUM_5G_40M] = { + 38, 42, 46, 50, 54, 58, 62, 102, 106, 110, 114, + 118, 122, 126, 130, 134, 138 +}; +static u32 rf_reg_pram_c_5g[5][RF_REG_NUM_FOR_C_CUT_5G] = { + {0xE43BE, 0xFC638, 0x77C0A, 0xDE471, 0xd7110, 0x8EB04}, + {0xE43BE, 0xFC078, 0xF7C1A, 0xE0C71, 0xD7550, 0xAEB04}, + {0xE43BF, 0xFF038, 0xF7C0A, 0xDE471, 0xE5550, 0xAEB04}, + {0xE43BF, 0xFF079, 0xF7C1A, 0xDE471, 0xE5550, 0xAEB04}, + {0xE43BF, 0xFF038, 0xF7C1A, 0xDE471, 0xd7550, 0xAEB04} +}; + +static u32 rf_reg_param_for_c_cut_2g[3][RF_REG_NUM_FOR_C_CUT_2G] = { + {0x643BC, 0xFC038, 0x77C1A, 0x41289, 0x01840}, + {0x643BC, 0xFC038, 0x07C1A, 0x41289, 0x01840}, + {0x243BC, 0xFC438, 0x07C1A, 0x4128B, 0x0FC41} +}; + +static u32 rf_syn_g4_for_c_cut_2g = 0xD1C31 & 0x7FF; + +static u32 rf_pram_c_5g_int_pa[3][RF_REG_NUM_FOR_C_CUT_5G_INTERNALPA] = { + {0x01a00, 0x40443, 0x00eb5, 0x89bec, 0x94a12, 0x94a12, 0x94a12}, + {0x01800, 0xc0443, 0x00730, 0x896ee, 0x94a52, 0x94a52, 0x94a52}, + {0x01800, 0xc0443, 0x00730, 0x896ee, 0x94a12, 0x94a12, 0x94a12} +}; + +/* [mode][patha+b][reg] */ +static u32 rf_imr_param_normal[1][3][MAX_RF_IMR_INDEX_NORMAL] = { + { + /* channel 1-14. */ + { + 0x70000, 0x00ff0, 0x4400f, 0x00ff0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x64888, 0xe266c, 0x00090, 0x22fff + }, + /* path 36-64 */ + { + 0x70000, 0x22880, 0x4470f, 0x55880, 0x00070, 0x88000, + 0x0, 0x88080, 0x70000, 0x64a82, 0xe466c, 0x00090, + 0x32c9a + }, + /* 100 -165 */ + { + 0x70000, 0x44880, 0x4477f, 0x77880, 0x00070, 0x88000, + 0x0, 0x880b0, 0x0, 0x64b82, 0xe466c, 0x00090, 0x32c9a + } + } +}; + +static u32 curveindex_5g[TARGET_CHNL_NUM_5G] = {0}; + +static u32 curveindex_2g[TARGET_CHNL_NUM_2G] = {0}; + +static u32 targetchnl_5g[TARGET_CHNL_NUM_5G] = { + 25141, 25116, 25091, 25066, 25041, + 25016, 24991, 24966, 24941, 24917, + 24892, 24867, 24843, 24818, 24794, + 24770, 24765, 24721, 24697, 24672, + 24648, 24624, 24600, 24576, 24552, + 24528, 24504, 24480, 24457, 24433, + 24409, 24385, 24362, 24338, 24315, + 24291, 24268, 24245, 24221, 24198, + 24175, 24151, 24128, 24105, 24082, + 24059, 24036, 24013, 23990, 23967, + 23945, 23922, 23899, 23876, 23854, + 23831, 23809, 23786, 23764, 23741, + 23719, 23697, 23674, 23652, 23630, + 23608, 23586, 23564, 23541, 23519, + 23498, 23476, 23454, 23432, 23410, + 23388, 23367, 23345, 23323, 23302, + 23280, 23259, 23237, 23216, 23194, + 23173, 23152, 23130, 23109, 23088, + 23067, 23046, 23025, 23003, 22982, + 22962, 22941, 22920, 22899, 22878, + 22857, 22837, 22816, 22795, 22775, + 22754, 22733, 22713, 22692, 22672, + 22652, 22631, 22611, 22591, 22570, + 22550, 22530, 22510, 22490, 22469, + 22449, 22429, 22409, 22390, 22370, + 22350, 22336, 22310, 22290, 22271, + 22251, 22231, 22212, 22192, 22173, + 22153, 22134, 22114, 22095, 22075, + 22056, 22037, 22017, 21998, 21979, + 21960, 21941, 21921, 21902, 21883, + 21864, 21845, 21826, 21807, 21789, + 21770, 21751, 21732, 21713, 21695, + 21676, 21657, 21639, 21620, 21602, + 21583, 21565, 21546, 21528, 21509, + 21491, 21473, 21454, 21436, 21418, + 21400, 21381, 21363, 21345, 21327, + 21309, 21291, 21273, 21255, 21237, + 21219, 21201, 21183, 21166, 21148, + 21130, 21112, 21095, 21077, 21059, + 21042, 21024, 21007, 20989, 20972, + 25679, 25653, 25627, 25601, 25575, + 25549, 25523, 25497, 25471, 25446, + 25420, 25394, 25369, 25343, 25318, + 25292, 25267, 25242, 25216, 25191, + 25166 +}; + +/* channel 1~14 */ +static u32 targetchnl_2g[TARGET_CHNL_NUM_2G] = { + 26084, 26030, 25976, 25923, 25869, 25816, 25764, + 25711, 25658, 25606, 25554, 25502, 25451, 25328 +}; + +static u32 _rtl92d_phy_calculate_bit_shift(u32 bitmask) +{ + u32 i; + + for (i = 0; i <= 31; i++) { + if (((bitmask >> i) & 0x1) == 1) + break; + } + + return i; +} + +u32 rtl92d_phy_query_bb_reg(struct ieee80211_hw *hw, u32 regaddr, u32 bitmask) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + u32 returnvalue, originalvalue, bitshift; + u8 dbi_direct; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, "regaddr(%#x), bitmask(%#x)\n", + regaddr, bitmask); + if (rtlhal->during_mac1init_radioa || rtlhal->during_mac0init_radiob) { + /* mac1 use phy0 read radio_b. */ + /* mac0 use phy1 read radio_b. */ + if (rtlhal->during_mac1init_radioa) + dbi_direct = BIT(3); + else if (rtlhal->during_mac0init_radiob) + dbi_direct = BIT(3) | BIT(2); + originalvalue = rtl92de_read_dword_dbi(hw, (u16)regaddr, + dbi_direct); + } else { + originalvalue = rtl_read_dword(rtlpriv, regaddr); + } + bitshift = _rtl92d_phy_calculate_bit_shift(bitmask); + returnvalue = (originalvalue & bitmask) >> bitshift; + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "BBR MASK=0x%x Addr[0x%x]=0x%x\n", + bitmask, regaddr, originalvalue); + return returnvalue; +} + +void rtl92d_phy_set_bb_reg(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + u8 dbi_direct = 0; + u32 originalvalue, bitshift; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x)\n", + regaddr, bitmask, data); + if (rtlhal->during_mac1init_radioa) + dbi_direct = BIT(3); + else if (rtlhal->during_mac0init_radiob) + /* mac0 use phy1 write radio_b. */ + dbi_direct = BIT(3) | BIT(2); + if (bitmask != BMASKDWORD) { + if (rtlhal->during_mac1init_radioa || + rtlhal->during_mac0init_radiob) + originalvalue = rtl92de_read_dword_dbi(hw, + (u16) regaddr, + dbi_direct); + else + originalvalue = rtl_read_dword(rtlpriv, regaddr); + bitshift = _rtl92d_phy_calculate_bit_shift(bitmask); + data = ((originalvalue & (~bitmask)) | (data << bitshift)); + } + if (rtlhal->during_mac1init_radioa || rtlhal->during_mac0init_radiob) + rtl92de_write_dword_dbi(hw, (u16) regaddr, data, dbi_direct); + else + rtl_write_dword(rtlpriv, regaddr, data); + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x)\n", + regaddr, bitmask, data); +} + +static u32 _rtl92d_phy_rf_serial_read(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset) +{ + + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct bb_reg_def *pphyreg = &rtlphy->phyreg_def[rfpath]; + u32 newoffset; + u32 tmplong, tmplong2; + u8 rfpi_enable = 0; + u32 retvalue; + + newoffset = offset; + tmplong = rtl_get_bbreg(hw, RFPGA0_XA_HSSIPARAMETER2, BMASKDWORD); + if (rfpath == RF90_PATH_A) + tmplong2 = tmplong; + else + tmplong2 = rtl_get_bbreg(hw, pphyreg->rfhssi_para2, BMASKDWORD); + tmplong2 = (tmplong2 & (~BLSSIREADADDRESS)) | + (newoffset << 23) | BLSSIREADEDGE; + rtl_set_bbreg(hw, RFPGA0_XA_HSSIPARAMETER2, BMASKDWORD, + tmplong & (~BLSSIREADEDGE)); + udelay(10); + rtl_set_bbreg(hw, pphyreg->rfhssi_para2, BMASKDWORD, tmplong2); + udelay(50); + udelay(50); + rtl_set_bbreg(hw, RFPGA0_XA_HSSIPARAMETER2, BMASKDWORD, + tmplong | BLSSIREADEDGE); + udelay(10); + if (rfpath == RF90_PATH_A) + rfpi_enable = (u8) rtl_get_bbreg(hw, RFPGA0_XA_HSSIPARAMETER1, + BIT(8)); + else if (rfpath == RF90_PATH_B) + rfpi_enable = (u8) rtl_get_bbreg(hw, RFPGA0_XB_HSSIPARAMETER1, + BIT(8)); + if (rfpi_enable) + retvalue = rtl_get_bbreg(hw, pphyreg->rf_rbpi, + BLSSIREADBACKDATA); + else + retvalue = rtl_get_bbreg(hw, pphyreg->rf_rb, + BLSSIREADBACKDATA); + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, "RFR-%d Addr[0x%x] = 0x%x\n", + rfpath, pphyreg->rf_rb, retvalue); + return retvalue; +} + +static void _rtl92d_phy_rf_serial_write(struct ieee80211_hw *hw, + enum radio_path rfpath, + u32 offset, u32 data) +{ + u32 data_and_addr; + u32 newoffset; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct bb_reg_def *pphyreg = &rtlphy->phyreg_def[rfpath]; + + newoffset = offset; + /* T65 RF */ + data_and_addr = ((newoffset << 20) | (data & 0x000fffff)) & 0x0fffffff; + rtl_set_bbreg(hw, pphyreg->rf3wire_offset, BMASKDWORD, data_and_addr); + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, "RFW-%d Addr[0x%x]=0x%x\n", + rfpath, pphyreg->rf3wire_offset, data_and_addr); +} + +u32 rtl92d_phy_query_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, u32 bitmask) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 original_value, readback_value, bitshift; + unsigned long flags; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), rfpath(%#x), bitmask(%#x)\n", + regaddr, rfpath, bitmask); + spin_lock_irqsave(&rtlpriv->locks.rf_lock, flags); + original_value = _rtl92d_phy_rf_serial_read(hw, rfpath, regaddr); + bitshift = _rtl92d_phy_calculate_bit_shift(bitmask); + readback_value = (original_value & bitmask) >> bitshift; + spin_unlock_irqrestore(&rtlpriv->locks.rf_lock, flags); + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), rfpath(%#x), bitmask(%#x), original_value(%#x)\n", + regaddr, rfpath, bitmask, original_value); + return readback_value; +} + +void rtl92d_phy_set_rf_reg(struct ieee80211_hw *hw, enum radio_path rfpath, + u32 regaddr, u32 bitmask, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 original_value, bitshift; + unsigned long flags; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x), rfpath(%#x)\n", + regaddr, bitmask, data, rfpath); + if (bitmask == 0) + return; + spin_lock_irqsave(&rtlpriv->locks.rf_lock, flags); + if (rtlphy->rf_mode != RF_OP_BY_FW) { + if (bitmask != BRFREGOFFSETMASK) { + original_value = _rtl92d_phy_rf_serial_read(hw, + rfpath, regaddr); + bitshift = _rtl92d_phy_calculate_bit_shift(bitmask); + data = ((original_value & (~bitmask)) | + (data << bitshift)); + } + _rtl92d_phy_rf_serial_write(hw, rfpath, regaddr, data); + } + spin_unlock_irqrestore(&rtlpriv->locks.rf_lock, flags); + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x), rfpath(%#x)\n", + regaddr, bitmask, data, rfpath); +} + +bool rtl92d_phy_mac_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + u32 arraylength; + u32 *ptrarray; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "Read Rtl819XMACPHY_Array\n"); + arraylength = MAC_2T_ARRAYLENGTH; + ptrarray = rtl8192de_mac_2tarray; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "Img:Rtl819XMAC_Array\n"); + for (i = 0; i < arraylength; i = i + 2) + rtl_write_byte(rtlpriv, ptrarray[i], (u8) ptrarray[i + 1]); + if (rtlpriv->rtlhal.macphymode == SINGLEMAC_SINGLEPHY) { + /* improve 2-stream TX EVM */ + /* rtl_write_byte(rtlpriv, 0x14,0x71); */ + /* AMPDU aggregation number 9 */ + /* rtl_write_word(rtlpriv, REG_MAX_AGGR_NUM, MAX_AGGR_NUM); */ + rtl_write_byte(rtlpriv, REG_MAX_AGGR_NUM, 0x0B); + } else { + /* 92D need to test to decide the num. */ + rtl_write_byte(rtlpriv, REG_MAX_AGGR_NUM, 0x07); + } + return true; +} + +static void _rtl92d_phy_init_bb_rf_register_definition(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + /* RF Interface Sowrtware Control */ + /* 16 LSBs if read 32-bit from 0x870 */ + rtlphy->phyreg_def[RF90_PATH_A].rfintfs = RFPGA0_XAB_RFINTERFACESW; + /* 16 MSBs if read 32-bit from 0x870 (16-bit for 0x872) */ + rtlphy->phyreg_def[RF90_PATH_B].rfintfs = RFPGA0_XAB_RFINTERFACESW; + /* 16 LSBs if read 32-bit from 0x874 */ + rtlphy->phyreg_def[RF90_PATH_C].rfintfs = RFPGA0_XCD_RFINTERFACESW; + /* 16 MSBs if read 32-bit from 0x874 (16-bit for 0x876) */ + + rtlphy->phyreg_def[RF90_PATH_D].rfintfs = RFPGA0_XCD_RFINTERFACESW; + /* RF Interface Readback Value */ + /* 16 LSBs if read 32-bit from 0x8E0 */ + rtlphy->phyreg_def[RF90_PATH_A].rfintfi = RFPGA0_XAB_RFINTERFACERB; + /* 16 MSBs if read 32-bit from 0x8E0 (16-bit for 0x8E2) */ + rtlphy->phyreg_def[RF90_PATH_B].rfintfi = RFPGA0_XAB_RFINTERFACERB; + /* 16 LSBs if read 32-bit from 0x8E4 */ + rtlphy->phyreg_def[RF90_PATH_C].rfintfi = RFPGA0_XCD_RFINTERFACERB; + /* 16 MSBs if read 32-bit from 0x8E4 (16-bit for 0x8E6) */ + rtlphy->phyreg_def[RF90_PATH_D].rfintfi = RFPGA0_XCD_RFINTERFACERB; + + /* RF Interface Output (and Enable) */ + /* 16 LSBs if read 32-bit from 0x860 */ + rtlphy->phyreg_def[RF90_PATH_A].rfintfo = RFPGA0_XA_RFINTERFACEOE; + /* 16 LSBs if read 32-bit from 0x864 */ + rtlphy->phyreg_def[RF90_PATH_B].rfintfo = RFPGA0_XB_RFINTERFACEOE; + + /* RF Interface (Output and) Enable */ + /* 16 MSBs if read 32-bit from 0x860 (16-bit for 0x862) */ + rtlphy->phyreg_def[RF90_PATH_A].rfintfe = RFPGA0_XA_RFINTERFACEOE; + /* 16 MSBs if read 32-bit from 0x864 (16-bit for 0x866) */ + rtlphy->phyreg_def[RF90_PATH_B].rfintfe = RFPGA0_XB_RFINTERFACEOE; + + /* Addr of LSSI. Wirte RF register by driver */ + /* LSSI Parameter */ + rtlphy->phyreg_def[RF90_PATH_A].rf3wire_offset = + RFPGA0_XA_LSSIPARAMETER; + rtlphy->phyreg_def[RF90_PATH_B].rf3wire_offset = + RFPGA0_XB_LSSIPARAMETER; + + /* RF parameter */ + /* BB Band Select */ + rtlphy->phyreg_def[RF90_PATH_A].rflssi_select = RFPGA0_XAB_RFPARAMETER; + rtlphy->phyreg_def[RF90_PATH_B].rflssi_select = RFPGA0_XAB_RFPARAMETER; + rtlphy->phyreg_def[RF90_PATH_C].rflssi_select = RFPGA0_XCD_RFPARAMETER; + rtlphy->phyreg_def[RF90_PATH_D].rflssi_select = RFPGA0_XCD_RFPARAMETER; + + /* Tx AGC Gain Stage (same for all path. Should we remove this?) */ + /* Tx gain stage */ + rtlphy->phyreg_def[RF90_PATH_A].rftxgain_stage = RFPGA0_TXGAINSTAGE; + /* Tx gain stage */ + rtlphy->phyreg_def[RF90_PATH_B].rftxgain_stage = RFPGA0_TXGAINSTAGE; + /* Tx gain stage */ + rtlphy->phyreg_def[RF90_PATH_C].rftxgain_stage = RFPGA0_TXGAINSTAGE; + /* Tx gain stage */ + rtlphy->phyreg_def[RF90_PATH_D].rftxgain_stage = RFPGA0_TXGAINSTAGE; + + /* Tranceiver A~D HSSI Parameter-1 */ + /* wire control parameter1 */ + rtlphy->phyreg_def[RF90_PATH_A].rfhssi_para1 = RFPGA0_XA_HSSIPARAMETER1; + /* wire control parameter1 */ + rtlphy->phyreg_def[RF90_PATH_B].rfhssi_para1 = RFPGA0_XB_HSSIPARAMETER1; + + /* Tranceiver A~D HSSI Parameter-2 */ + /* wire control parameter2 */ + rtlphy->phyreg_def[RF90_PATH_A].rfhssi_para2 = RFPGA0_XA_HSSIPARAMETER2; + /* wire control parameter2 */ + rtlphy->phyreg_def[RF90_PATH_B].rfhssi_para2 = RFPGA0_XB_HSSIPARAMETER2; + + /* RF switch Control */ + /* TR/Ant switch control */ + rtlphy->phyreg_def[RF90_PATH_A].rfsw_ctrl = RFPGA0_XAB_SWITCHCONTROL; + rtlphy->phyreg_def[RF90_PATH_B].rfsw_ctrl = RFPGA0_XAB_SWITCHCONTROL; + rtlphy->phyreg_def[RF90_PATH_C].rfsw_ctrl = RFPGA0_XCD_SWITCHCONTROL; + rtlphy->phyreg_def[RF90_PATH_D].rfsw_ctrl = RFPGA0_XCD_SWITCHCONTROL; + + /* AGC control 1 */ + rtlphy->phyreg_def[RF90_PATH_A].rfagc_control1 = ROFDM0_XAAGCCORE1; + rtlphy->phyreg_def[RF90_PATH_B].rfagc_control1 = ROFDM0_XBAGCCORE1; + rtlphy->phyreg_def[RF90_PATH_C].rfagc_control1 = ROFDM0_XCAGCCORE1; + rtlphy->phyreg_def[RF90_PATH_D].rfagc_control1 = ROFDM0_XDAGCCORE1; + + /* AGC control 2 */ + rtlphy->phyreg_def[RF90_PATH_A].rfagc_control2 = ROFDM0_XAAGCCORE2; + rtlphy->phyreg_def[RF90_PATH_B].rfagc_control2 = ROFDM0_XBAGCCORE2; + rtlphy->phyreg_def[RF90_PATH_C].rfagc_control2 = ROFDM0_XCAGCCORE2; + rtlphy->phyreg_def[RF90_PATH_D].rfagc_control2 = ROFDM0_XDAGCCORE2; + + /* RX AFE control 1 */ + rtlphy->phyreg_def[RF90_PATH_A].rfrxiq_imbal = ROFDM0_XARXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_B].rfrxiq_imbal = ROFDM0_XBRXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_C].rfrxiq_imbal = ROFDM0_XCRXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_D].rfrxiq_imbal = ROFDM0_XDRXIQIMBALANCE; + + /*RX AFE control 1 */ + rtlphy->phyreg_def[RF90_PATH_A].rfrx_afe = ROFDM0_XARXAFE; + rtlphy->phyreg_def[RF90_PATH_B].rfrx_afe = ROFDM0_XBRXAFE; + rtlphy->phyreg_def[RF90_PATH_C].rfrx_afe = ROFDM0_XCRXAFE; + rtlphy->phyreg_def[RF90_PATH_D].rfrx_afe = ROFDM0_XDRXAFE; + + /* Tx AFE control 1 */ + rtlphy->phyreg_def[RF90_PATH_A].rftxiq_imbal = ROFDM0_XATxIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_B].rftxiq_imbal = ROFDM0_XBTxIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_C].rftxiq_imbal = ROFDM0_XCTxIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_D].rftxiq_imbal = ROFDM0_XDTxIQIMBALANCE; + + /* Tx AFE control 2 */ + rtlphy->phyreg_def[RF90_PATH_A].rftx_afe = ROFDM0_XATxAFE; + rtlphy->phyreg_def[RF90_PATH_B].rftx_afe = ROFDM0_XBTxAFE; + rtlphy->phyreg_def[RF90_PATH_C].rftx_afe = ROFDM0_XCTxAFE; + rtlphy->phyreg_def[RF90_PATH_D].rftx_afe = ROFDM0_XDTxAFE; + + /* Tranceiver LSSI Readback SI mode */ + rtlphy->phyreg_def[RF90_PATH_A].rf_rb = RFPGA0_XA_LSSIREADBACK; + rtlphy->phyreg_def[RF90_PATH_B].rf_rb = RFPGA0_XB_LSSIREADBACK; + rtlphy->phyreg_def[RF90_PATH_C].rf_rb = RFPGA0_XC_LSSIREADBACK; + rtlphy->phyreg_def[RF90_PATH_D].rf_rb = RFPGA0_XD_LSSIREADBACK; + + /* Tranceiver LSSI Readback PI mode */ + rtlphy->phyreg_def[RF90_PATH_A].rf_rbpi = TRANSCEIVERA_HSPI_READBACK; + rtlphy->phyreg_def[RF90_PATH_B].rf_rbpi = TRANSCEIVERB_HSPI_READBACK; +} + +static bool _rtl92d_phy_config_bb_with_headerfile(struct ieee80211_hw *hw, + u8 configtype) +{ + int i; + u32 *phy_regarray_table; + u32 *agctab_array_table = NULL; + u32 *agctab_5garray_table; + u16 phy_reg_arraylen, agctab_arraylen = 0, agctab_5garraylen; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + /* Normal chip,Mac0 use AGC_TAB.txt for 2G and 5G band. */ + if (rtlhal->interfaceindex == 0) { + agctab_arraylen = AGCTAB_ARRAYLENGTH; + agctab_array_table = rtl8192de_agctab_array; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + " ===> phy:MAC0, Rtl819XAGCTAB_Array\n"); + } else { + if (rtlhal->current_bandtype == BAND_ON_2_4G) { + agctab_arraylen = AGCTAB_2G_ARRAYLENGTH; + agctab_array_table = rtl8192de_agctab_2garray; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + " ===> phy:MAC1, Rtl819XAGCTAB_2GArray\n"); + } else { + agctab_5garraylen = AGCTAB_5G_ARRAYLENGTH; + agctab_5garray_table = rtl8192de_agctab_5garray; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + " ===> phy:MAC1, Rtl819XAGCTAB_5GArray\n"); + + } + } + phy_reg_arraylen = PHY_REG_2T_ARRAYLENGTH; + phy_regarray_table = rtl8192de_phy_reg_2tarray; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + " ===> phy:Rtl819XPHY_REG_Array_PG\n"); + if (configtype == BASEBAND_CONFIG_PHY_REG) { + for (i = 0; i < phy_reg_arraylen; i = i + 2) { + if (phy_regarray_table[i] == 0xfe) + mdelay(50); + else if (phy_regarray_table[i] == 0xfd) + mdelay(5); + else if (phy_regarray_table[i] == 0xfc) + mdelay(1); + else if (phy_regarray_table[i] == 0xfb) + udelay(50); + else if (phy_regarray_table[i] == 0xfa) + udelay(5); + else if (phy_regarray_table[i] == 0xf9) + udelay(1); + rtl_set_bbreg(hw, phy_regarray_table[i], BMASKDWORD, + phy_regarray_table[i + 1]); + udelay(1); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "The phy_regarray_table[0] is %x Rtl819XPHY_REGArray[1] is %x\n", + phy_regarray_table[i], + phy_regarray_table[i + 1]); + } + } else if (configtype == BASEBAND_CONFIG_AGC_TAB) { + if (rtlhal->interfaceindex == 0) { + for (i = 0; i < agctab_arraylen; i = i + 2) { + rtl_set_bbreg(hw, agctab_array_table[i], + BMASKDWORD, + agctab_array_table[i + 1]); + /* Add 1us delay between BB/RF register + * setting. */ + udelay(1); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "The Rtl819XAGCTAB_Array_Table[0] is %ul Rtl819XPHY_REGArray[1] is %ul\n", + agctab_array_table[i], + agctab_array_table[i + 1]); + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Normal Chip, MAC0, load Rtl819XAGCTAB_Array\n"); + } else { + if (rtlhal->current_bandtype == BAND_ON_2_4G) { + for (i = 0; i < agctab_arraylen; i = i + 2) { + rtl_set_bbreg(hw, agctab_array_table[i], + BMASKDWORD, + agctab_array_table[i + 1]); + /* Add 1us delay between BB/RF register + * setting. */ + udelay(1); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "The Rtl819XAGCTAB_Array_Table[0] is %ul Rtl819XPHY_REGArray[1] is %ul\n", + agctab_array_table[i], + agctab_array_table[i + 1]); + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Load Rtl819XAGCTAB_2GArray\n"); + } else { + for (i = 0; i < agctab_5garraylen; i = i + 2) { + rtl_set_bbreg(hw, + agctab_5garray_table[i], + BMASKDWORD, + agctab_5garray_table[i + 1]); + /* Add 1us delay between BB/RF registeri + * setting. */ + udelay(1); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "The Rtl819XAGCTAB_5GArray_Table[0] is %ul Rtl819XPHY_REGArray[1] is %ul\n", + agctab_5garray_table[i], + agctab_5garray_table[i + 1]); + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Load Rtl819XAGCTAB_5GArray\n"); + } + } + } + return true; +} + +static void _rtl92d_store_pwrindex_diffrate_offset(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask, + u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + int index; + + if (regaddr == RTXAGC_A_RATE18_06) + index = 0; + else if (regaddr == RTXAGC_A_RATE54_24) + index = 1; + else if (regaddr == RTXAGC_A_CCK1_MCS32) + index = 6; + else if (regaddr == RTXAGC_B_CCK11_A_CCK2_11 && bitmask == 0xffffff00) + index = 7; + else if (regaddr == RTXAGC_A_MCS03_MCS00) + index = 2; + else if (regaddr == RTXAGC_A_MCS07_MCS04) + index = 3; + else if (regaddr == RTXAGC_A_MCS11_MCS08) + index = 4; + else if (regaddr == RTXAGC_A_MCS15_MCS12) + index = 5; + else if (regaddr == RTXAGC_B_RATE18_06) + index = 8; + else if (regaddr == RTXAGC_B_RATE54_24) + index = 9; + else if (regaddr == RTXAGC_B_CCK1_55_MCS32) + index = 14; + else if (regaddr == RTXAGC_B_CCK11_A_CCK2_11 && bitmask == 0x000000ff) + index = 15; + else if (regaddr == RTXAGC_B_MCS03_MCS00) + index = 10; + else if (regaddr == RTXAGC_B_MCS07_MCS04) + index = 11; + else if (regaddr == RTXAGC_B_MCS11_MCS08) + index = 12; + else if (regaddr == RTXAGC_B_MCS15_MCS12) + index = 13; + else + return; + + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][index] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][%d] = 0x%ulx\n", + rtlphy->pwrgroup_cnt, index, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][index]); + if (index == 13) + rtlphy->pwrgroup_cnt++; +} + +static bool _rtl92d_phy_config_bb_with_pgheaderfile(struct ieee80211_hw *hw, + u8 configtype) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int i; + u32 *phy_regarray_table_pg; + u16 phy_regarray_pg_len; + + phy_regarray_pg_len = PHY_REG_ARRAY_PG_LENGTH; + phy_regarray_table_pg = rtl8192de_phy_reg_array_pg; + if (configtype == BASEBAND_CONFIG_PHY_REG) { + for (i = 0; i < phy_regarray_pg_len; i = i + 3) { + if (phy_regarray_table_pg[i] == 0xfe) + mdelay(50); + else if (phy_regarray_table_pg[i] == 0xfd) + mdelay(5); + else if (phy_regarray_table_pg[i] == 0xfc) + mdelay(1); + else if (phy_regarray_table_pg[i] == 0xfb) + udelay(50); + else if (phy_regarray_table_pg[i] == 0xfa) + udelay(5); + else if (phy_regarray_table_pg[i] == 0xf9) + udelay(1); + _rtl92d_store_pwrindex_diffrate_offset(hw, + phy_regarray_table_pg[i], + phy_regarray_table_pg[i + 1], + phy_regarray_table_pg[i + 2]); + } + } else { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "configtype != BaseBand_Config_PHY_REG\n"); + } + return true; +} + +static bool _rtl92d_phy_bb_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + bool rtstatus = true; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "==>\n"); + rtstatus = _rtl92d_phy_config_bb_with_headerfile(hw, + BASEBAND_CONFIG_PHY_REG); + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Write BB Reg Fail!!\n"); + return false; + } + + /* if (rtlphy->rf_type == RF_1T2R) { + * _rtl92c_phy_bb_config_1t(hw); + * RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "Config to 1T!!\n"); + *} */ + + if (rtlefuse->autoload_failflag == false) { + rtlphy->pwrgroup_cnt = 0; + rtstatus = _rtl92d_phy_config_bb_with_pgheaderfile(hw, + BASEBAND_CONFIG_PHY_REG); + } + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "BB_PG Reg Fail!!\n"); + return false; + } + rtstatus = _rtl92d_phy_config_bb_with_headerfile(hw, + BASEBAND_CONFIG_AGC_TAB); + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "AGC Table Fail\n"); + return false; + } + rtlphy->cck_high_power = (bool) (rtl_get_bbreg(hw, + RFPGA0_XA_HSSIPARAMETER2, 0x200)); + + return true; +} + +bool rtl92d_phy_bb_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u16 regval; + u32 regvaldw; + u8 value; + + _rtl92d_phy_init_bb_rf_register_definition(hw); + regval = rtl_read_word(rtlpriv, REG_SYS_FUNC_EN); + rtl_write_word(rtlpriv, REG_SYS_FUNC_EN, + regval | BIT(13) | BIT(0) | BIT(1)); + rtl_write_byte(rtlpriv, REG_AFE_PLL_CTRL, 0x83); + rtl_write_byte(rtlpriv, REG_AFE_PLL_CTRL + 1, 0xdb); + /* 0x1f bit7 bit6 represent for mac0/mac1 driver ready */ + value = rtl_read_byte(rtlpriv, REG_RF_CTRL); + rtl_write_byte(rtlpriv, REG_RF_CTRL, value | RF_EN | RF_RSTB | + RF_SDMRSTB); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, FEN_PPLL | FEN_PCIEA | + FEN_DIO_PCIE | FEN_BB_GLB_RSTn | FEN_BBRSTB); + rtl_write_byte(rtlpriv, REG_AFE_XTAL_CTRL + 1, 0x80); + if (!(IS_92D_SINGLEPHY(rtlpriv->rtlhal.version))) { + regvaldw = rtl_read_dword(rtlpriv, REG_LEDCFG0); + rtl_write_dword(rtlpriv, REG_LEDCFG0, regvaldw | BIT(23)); + } + + return _rtl92d_phy_bb_config(hw); +} + +bool rtl92d_phy_rf_config(struct ieee80211_hw *hw) +{ + return rtl92d_phy_rf6052_config(hw); +} + +bool rtl92d_phy_config_rf_with_headerfile(struct ieee80211_hw *hw, + enum rf_content content, + enum radio_path rfpath) +{ + int i; + u32 *radioa_array_table; + u32 *radiob_array_table; + u16 radioa_arraylen, radiob_arraylen; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + radioa_arraylen = RADIOA_2T_ARRAYLENGTH; + radioa_array_table = rtl8192de_radioa_2tarray; + radiob_arraylen = RADIOB_2T_ARRAYLENGTH; + radiob_array_table = rtl8192de_radiob_2tarray; + if (rtlpriv->efuse.internal_pa_5g[0]) { + radioa_arraylen = RADIOA_2T_INT_PA_ARRAYLENGTH; + radioa_array_table = rtl8192de_radioa_2t_int_paarray; + } + if (rtlpriv->efuse.internal_pa_5g[1]) { + radiob_arraylen = RADIOB_2T_INT_PA_ARRAYLENGTH; + radiob_array_table = rtl8192de_radiob_2t_int_paarray; + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "PHY_ConfigRFWithHeaderFile() Radio_A:Rtl819XRadioA_1TArray\n"); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "PHY_ConfigRFWithHeaderFile() Radio_B:Rtl819XRadioB_1TArray\n"); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "Radio No %x\n", rfpath); + + /* this only happens when DMDP, mac0 start on 2.4G, + * mac1 start on 5G, mac 0 has to set phy0&phy1 + * pathA or mac1 has to set phy0&phy1 pathA */ + if ((content == radiob_txt) && (rfpath == RF90_PATH_A)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + " ===> althougth Path A, we load radiob.txt\n"); + radioa_arraylen = radiob_arraylen; + radioa_array_table = radiob_array_table; + } + switch (rfpath) { + case RF90_PATH_A: + for (i = 0; i < radioa_arraylen; i = i + 2) { + if (radioa_array_table[i] == 0xfe) { + mdelay(50); + } else if (radioa_array_table[i] == 0xfd) { + /* delay_ms(5); */ + mdelay(5); + } else if (radioa_array_table[i] == 0xfc) { + /* delay_ms(1); */ + mdelay(1); + } else if (radioa_array_table[i] == 0xfb) { + udelay(50); + } else if (radioa_array_table[i] == 0xfa) { + udelay(5); + } else if (radioa_array_table[i] == 0xf9) { + udelay(1); + } else { + rtl_set_rfreg(hw, rfpath, radioa_array_table[i], + BRFREGOFFSETMASK, + radioa_array_table[i + 1]); + /* Add 1us delay between BB/RF register set. */ + udelay(1); + } + } + break; + case RF90_PATH_B: + for (i = 0; i < radiob_arraylen; i = i + 2) { + if (radiob_array_table[i] == 0xfe) { + /* Delay specific ms. Only RF configuration + * requires delay. */ + mdelay(50); + } else if (radiob_array_table[i] == 0xfd) { + /* delay_ms(5); */ + mdelay(5); + } else if (radiob_array_table[i] == 0xfc) { + /* delay_ms(1); */ + mdelay(1); + } else if (radiob_array_table[i] == 0xfb) { + udelay(50); + } else if (radiob_array_table[i] == 0xfa) { + udelay(5); + } else if (radiob_array_table[i] == 0xf9) { + udelay(1); + } else { + rtl_set_rfreg(hw, rfpath, radiob_array_table[i], + BRFREGOFFSETMASK, + radiob_array_table[i + 1]); + /* Add 1us delay between BB/RF register set. */ + udelay(1); + } + } + break; + case RF90_PATH_C: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + case RF90_PATH_D: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + return true; +} + +void rtl92d_phy_get_hw_reg_originalvalue(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + rtlphy->default_initialgain[0] = + (u8) rtl_get_bbreg(hw, ROFDM0_XAAGCCORE1, BMASKBYTE0); + rtlphy->default_initialgain[1] = + (u8) rtl_get_bbreg(hw, ROFDM0_XBAGCCORE1, BMASKBYTE0); + rtlphy->default_initialgain[2] = + (u8) rtl_get_bbreg(hw, ROFDM0_XCAGCCORE1, BMASKBYTE0); + rtlphy->default_initialgain[3] = + (u8) rtl_get_bbreg(hw, ROFDM0_XDAGCCORE1, BMASKBYTE0); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Default initial gain (c50=0x%x, c58=0x%x, c60=0x%x, c68=0x%x\n", + rtlphy->default_initialgain[0], + rtlphy->default_initialgain[1], + rtlphy->default_initialgain[2], + rtlphy->default_initialgain[3]); + rtlphy->framesync = (u8)rtl_get_bbreg(hw, ROFDM0_RXDETECTOR3, + BMASKBYTE0); + rtlphy->framesync_c34 = rtl_get_bbreg(hw, ROFDM0_RXDETECTOR2, + BMASKDWORD); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Default framesync (0x%x) = 0x%x\n", + ROFDM0_RXDETECTOR3, rtlphy->framesync); +} + +static void _rtl92d_get_txpower_index(struct ieee80211_hw *hw, u8 channel, + u8 *cckpowerlevel, u8 *ofdmpowerlevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = &(rtlpriv->rtlhal); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 index = (channel - 1); + + /* 1. CCK */ + if (rtlhal->current_bandtype == BAND_ON_2_4G) { + /* RF-A */ + cckpowerlevel[RF90_PATH_A] = + rtlefuse->txpwrlevel_cck[RF90_PATH_A][index]; + /* RF-B */ + cckpowerlevel[RF90_PATH_B] = + rtlefuse->txpwrlevel_cck[RF90_PATH_B][index]; + } else { + cckpowerlevel[RF90_PATH_A] = 0; + cckpowerlevel[RF90_PATH_B] = 0; + } + /* 2. OFDM for 1S or 2S */ + if (rtlphy->rf_type == RF_1T2R || rtlphy->rf_type == RF_1T1R) { + /* Read HT 40 OFDM TX power */ + ofdmpowerlevel[RF90_PATH_A] = + rtlefuse->txpwrlevel_ht40_1s[RF90_PATH_A][index]; + ofdmpowerlevel[RF90_PATH_B] = + rtlefuse->txpwrlevel_ht40_1s[RF90_PATH_B][index]; + } else if (rtlphy->rf_type == RF_2T2R) { + /* Read HT 40 OFDM TX power */ + ofdmpowerlevel[RF90_PATH_A] = + rtlefuse->txpwrlevel_ht40_2s[RF90_PATH_A][index]; + ofdmpowerlevel[RF90_PATH_B] = + rtlefuse->txpwrlevel_ht40_2s[RF90_PATH_B][index]; + } +} + +static void _rtl92d_ccxpower_index_check(struct ieee80211_hw *hw, + u8 channel, u8 *cckpowerlevel, u8 *ofdmpowerlevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + rtlphy->cur_cck_txpwridx = cckpowerlevel[0]; + rtlphy->cur_ofdm24g_txpwridx = ofdmpowerlevel[0]; +} + +static u8 _rtl92c_phy_get_rightchnlplace(u8 chnl) +{ + u8 channel_5g[59] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, + 60, 62, 64, 100, 102, 104, 106, 108, 110, 112, + 114, 116, 118, 120, 122, 124, 126, 128, + 130, 132, 134, 136, 138, 140, 149, 151, + 153, 155, 157, 159, 161, 163, 165 + }; + u8 place = chnl; + + if (chnl > 14) { + for (place = 14; place < sizeof(channel_5g); place++) { + if (channel_5g[place] == chnl) { + place++; + break; + } + } + } + return place; +} + +void rtl92d_phy_set_txpower_level(struct ieee80211_hw *hw, u8 channel) +{ + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 cckpowerlevel[2], ofdmpowerlevel[2]; + + if (!rtlefuse->txpwr_fromeprom) + return; + channel = _rtl92c_phy_get_rightchnlplace(channel); + _rtl92d_get_txpower_index(hw, channel, &cckpowerlevel[0], + &ofdmpowerlevel[0]); + if (rtlpriv->rtlhal.current_bandtype == BAND_ON_2_4G) + _rtl92d_ccxpower_index_check(hw, channel, &cckpowerlevel[0], + &ofdmpowerlevel[0]); + if (rtlpriv->rtlhal.current_bandtype == BAND_ON_2_4G) + rtl92d_phy_rf6052_set_cck_txpower(hw, &cckpowerlevel[0]); + rtl92d_phy_rf6052_set_ofdm_txpower(hw, &ofdmpowerlevel[0], channel); +} + +void rtl92d_phy_scan_operation_backup(struct ieee80211_hw *hw, u8 operation) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + enum io_type iotype; + + if (!is_hal_stop(rtlhal)) { + switch (operation) { + case SCAN_OPT_BACKUP: + rtlhal->current_bandtypebackup = + rtlhal->current_bandtype; + iotype = IO_CMD_PAUSE_DM_BY_SCAN; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_IO_CMD, + (u8 *)&iotype); + break; + case SCAN_OPT_RESTORE: + iotype = IO_CMD_RESUME_DM_BY_SCAN; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_IO_CMD, + (u8 *)&iotype); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Unknown Scan Backup operation\n"); + break; + } + } +} + +void rtl92d_phy_set_bw_mode(struct ieee80211_hw *hw, + enum nl80211_channel_type ch_type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + unsigned long flag = 0; + u8 reg_prsr_rsc; + u8 reg_bw_opmode; + + if (rtlphy->set_bwmode_inprogress) + return; + if ((is_hal_stop(rtlhal)) || (RT_CANNOT_IO(hw))) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "FALSE driver sleep or unload\n"); + return; + } + rtlphy->set_bwmode_inprogress = true; + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, "Switch to %s bandwidth\n", + rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20 ? + "20MHz" : "40MHz"); + reg_bw_opmode = rtl_read_byte(rtlpriv, REG_BWOPMODE); + reg_prsr_rsc = rtl_read_byte(rtlpriv, REG_RRSR + 2); + switch (rtlphy->current_chan_bw) { + case HT_CHANNEL_WIDTH_20: + reg_bw_opmode |= BW_OPMODE_20MHZ; + rtl_write_byte(rtlpriv, REG_BWOPMODE, reg_bw_opmode); + break; + case HT_CHANNEL_WIDTH_20_40: + reg_bw_opmode &= ~BW_OPMODE_20MHZ; + rtl_write_byte(rtlpriv, REG_BWOPMODE, reg_bw_opmode); + + reg_prsr_rsc = (reg_prsr_rsc & 0x90) | + (mac->cur_40_prime_sc << 5); + rtl_write_byte(rtlpriv, REG_RRSR + 2, reg_prsr_rsc); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", rtlphy->current_chan_bw); + break; + } + switch (rtlphy->current_chan_bw) { + case HT_CHANNEL_WIDTH_20: + rtl_set_bbreg(hw, RFPGA0_RFMOD, BRFMOD, 0x0); + rtl_set_bbreg(hw, RFPGA1_RFMOD, BRFMOD, 0x0); + /* SET BIT10 BIT11 for receive cck */ + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER2, BIT(10) | + BIT(11), 3); + break; + case HT_CHANNEL_WIDTH_20_40: + rtl_set_bbreg(hw, RFPGA0_RFMOD, BRFMOD, 0x1); + rtl_set_bbreg(hw, RFPGA1_RFMOD, BRFMOD, 0x1); + /* Set Control channel to upper or lower. + * These settings are required only for 40MHz */ + if (rtlhal->current_bandtype == BAND_ON_2_4G) { + rtl92d_acquire_cckandrw_pagea_ctl(hw, &flag); + rtl_set_bbreg(hw, RCCK0_SYSTEM, BCCKSIDEBAND, + (mac->cur_40_prime_sc >> 1)); + rtl92d_release_cckandrw_pagea_ctl(hw, &flag); + } + rtl_set_bbreg(hw, ROFDM1_LSTF, 0xC00, mac->cur_40_prime_sc); + /* SET BIT10 BIT11 for receive cck */ + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER2, BIT(10) | + BIT(11), 0); + rtl_set_bbreg(hw, 0x818, (BIT(26) | BIT(27)), + (mac->cur_40_prime_sc == + HAL_PRIME_CHNL_OFFSET_LOWER) ? 2 : 1); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", rtlphy->current_chan_bw); + break; + + } + rtl92d_phy_rf6052_set_bandwidth(hw, rtlphy->current_chan_bw); + rtlphy->set_bwmode_inprogress = false; + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, "<==\n"); +} + +static void _rtl92d_phy_stop_trx_before_changeband(struct ieee80211_hw *hw) +{ + rtl_set_bbreg(hw, RFPGA0_RFMOD, BCCKEN, 0); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BOFDMEN, 0); + rtl_set_bbreg(hw, ROFDM0_TRXPATHENABLE, BMASKBYTE0, 0x00); + rtl_set_bbreg(hw, ROFDM1_TRXPATHENABLE, BDWORD, 0x0); +} + +static void rtl92d_phy_switch_wirelessband(struct ieee80211_hw *hw, u8 band) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 value8; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "==>\n"); + rtlhal->bandset = band; + rtlhal->current_bandtype = band; + if (IS_92D_SINGLEPHY(rtlhal->version)) + rtlhal->bandset = BAND_ON_BOTH; + /* stop RX/Tx */ + _rtl92d_phy_stop_trx_before_changeband(hw); + /* reconfig BB/RF according to wireless mode */ + if (rtlhal->current_bandtype == BAND_ON_2_4G) { + /* BB & RF Config */ + RT_TRACE(rtlpriv, COMP_CMD, DBG_DMESG, "====>2.4G\n"); + if (rtlhal->interfaceindex == 1) + _rtl92d_phy_config_bb_with_headerfile(hw, + BASEBAND_CONFIG_AGC_TAB); + } else { + /* 5G band */ + RT_TRACE(rtlpriv, COMP_CMD, DBG_DMESG, "====>5G\n"); + if (rtlhal->interfaceindex == 1) + _rtl92d_phy_config_bb_with_headerfile(hw, + BASEBAND_CONFIG_AGC_TAB); + } + rtl92d_update_bbrf_configuration(hw); + if (rtlhal->current_bandtype == BAND_ON_2_4G) + rtl_set_bbreg(hw, RFPGA0_RFMOD, BCCKEN, 0x1); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BOFDMEN, 0x1); + + /* 20M BW. */ + /* rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER2, BIT(10), 1); */ + rtlhal->reloadtxpowerindex = true; + /* notice fw know band status 0x81[1]/0x53[1] = 0: 5G, 1: 2G */ + if (rtlhal->current_bandtype == BAND_ON_2_4G) { + value8 = rtl_read_byte(rtlpriv, (rtlhal->interfaceindex == + 0 ? REG_MAC0 : REG_MAC1)); + value8 |= BIT(1); + rtl_write_byte(rtlpriv, (rtlhal->interfaceindex == + 0 ? REG_MAC0 : REG_MAC1), value8); + } else { + value8 = rtl_read_byte(rtlpriv, (rtlhal->interfaceindex == + 0 ? REG_MAC0 : REG_MAC1)); + value8 &= (~BIT(1)); + rtl_write_byte(rtlpriv, (rtlhal->interfaceindex == + 0 ? REG_MAC0 : REG_MAC1), value8); + } + mdelay(1); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "<==Switch Band OK\n"); +} + +static void _rtl92d_phy_reload_imr_setting(struct ieee80211_hw *hw, + u8 channel, u8 rfpath) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 imr_num = MAX_RF_IMR_INDEX; + u32 rfmask = BRFREGOFFSETMASK; + u8 group, i; + unsigned long flag = 0; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "====>path %d\n", rfpath); + if (rtlpriv->rtlhal.current_bandtype == BAND_ON_5G) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "====>5G\n"); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BIT(25) | BIT(24), 0); + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER4, 0x00f00000, 0xf); + /* fc area 0xd2c */ + if (channel > 99) + rtl_set_bbreg(hw, ROFDM1_CFOTRACKING, BIT(13) | + BIT(14), 2); + else + rtl_set_bbreg(hw, ROFDM1_CFOTRACKING, BIT(13) | + BIT(14), 1); + /* leave 0 for channel1-14. */ + group = channel <= 64 ? 1 : 2; + imr_num = MAX_RF_IMR_INDEX_NORMAL; + for (i = 0; i < imr_num; i++) + rtl_set_rfreg(hw, (enum radio_path)rfpath, + rf_reg_for_5g_swchnl_normal[i], rfmask, + rf_imr_param_normal[0][group][i]); + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER4, 0x00f00000, 0); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BOFDMEN, 1); + } else { + /* G band. */ + RT_TRACE(rtlpriv, COMP_SCAN, DBG_LOUD, + "Load RF IMR parameters for G band. IMR already setting %d\n", + rtlpriv->rtlhal.load_imrandiqk_setting_for2g); + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "====>2.4G\n"); + if (!rtlpriv->rtlhal.load_imrandiqk_setting_for2g) { + RT_TRACE(rtlpriv, COMP_SCAN, DBG_LOUD, + "Load RF IMR parameters for G band. %d\n", + rfpath); + rtl92d_acquire_cckandrw_pagea_ctl(hw, &flag); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BIT(25) | BIT(24), 0); + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER4, + 0x00f00000, 0xf); + imr_num = MAX_RF_IMR_INDEX_NORMAL; + for (i = 0; i < imr_num; i++) { + rtl_set_rfreg(hw, (enum radio_path)rfpath, + rf_reg_for_5g_swchnl_normal[i], + BRFREGOFFSETMASK, + rf_imr_param_normal[0][0][i]); + } + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER4, + 0x00f00000, 0); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BOFDMEN | BCCKEN, 3); + rtl92d_release_cckandrw_pagea_ctl(hw, &flag); + } + } + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "<====\n"); +} + +static void _rtl92d_phy_enable_rf_env(struct ieee80211_hw *hw, + u8 rfpath, u32 *pu4_regval) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct bb_reg_def *pphyreg = &rtlphy->phyreg_def[rfpath]; + + RT_TRACE(rtlpriv, COMP_RF, DBG_LOUD, "====>\n"); + /*----Store original RFENV control type----*/ + switch (rfpath) { + case RF90_PATH_A: + case RF90_PATH_C: + *pu4_regval = rtl_get_bbreg(hw, pphyreg->rfintfs, BRFSI_RFENV); + break; + case RF90_PATH_B: + case RF90_PATH_D: + *pu4_regval = + rtl_get_bbreg(hw, pphyreg->rfintfs, BRFSI_RFENV << 16); + break; + } + /*----Set RF_ENV enable----*/ + rtl_set_bbreg(hw, pphyreg->rfintfe, BRFSI_RFENV << 16, 0x1); + udelay(1); + /*----Set RF_ENV output high----*/ + rtl_set_bbreg(hw, pphyreg->rfintfo, BRFSI_RFENV, 0x1); + udelay(1); + /* Set bit number of Address and Data for RF register */ + /* Set 1 to 4 bits for 8255 */ + rtl_set_bbreg(hw, pphyreg->rfhssi_para2, B3WIREADDRESSLENGTH, 0x0); + udelay(1); + /*Set 0 to 12 bits for 8255 */ + rtl_set_bbreg(hw, pphyreg->rfhssi_para2, B3WIREDATALENGTH, 0x0); + udelay(1); + RT_TRACE(rtlpriv, COMP_RF, DBG_LOUD, "<====\n"); +} + +static void _rtl92d_phy_restore_rf_env(struct ieee80211_hw *hw, u8 rfpath, + u32 *pu4_regval) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct bb_reg_def *pphyreg = &rtlphy->phyreg_def[rfpath]; + + RT_TRACE(rtlpriv, COMP_RF, DBG_LOUD, "=====>\n"); + /*----Restore RFENV control type----*/ + switch (rfpath) { + case RF90_PATH_A: + case RF90_PATH_C: + rtl_set_bbreg(hw, pphyreg->rfintfs, BRFSI_RFENV, *pu4_regval); + break; + case RF90_PATH_B: + case RF90_PATH_D: + rtl_set_bbreg(hw, pphyreg->rfintfs, BRFSI_RFENV << 16, + *pu4_regval); + break; + } + RT_TRACE(rtlpriv, COMP_RF, DBG_LOUD, "<=====\n"); +} + +static void _rtl92d_phy_switch_rf_setting(struct ieee80211_hw *hw, u8 channel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = &(rtlpriv->rtlhal); + u8 path = rtlhal->current_bandtype == + BAND_ON_5G ? RF90_PATH_A : RF90_PATH_B; + u8 index = 0, i = 0, rfpath = RF90_PATH_A; + bool need_pwr_down = false, internal_pa = false; + u32 u4regvalue, mask = 0x1C000, value = 0, u4tmp, u4tmp2; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "====>\n"); + /* config path A for 5G */ + if (rtlhal->current_bandtype == BAND_ON_5G) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "====>5G\n"); + u4tmp = curveindex_5g[channel - 1]; + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "ver 1 set RF-A, 5G, 0x28 = 0x%x !!\n", u4tmp); + for (i = 0; i < RF_CHNL_NUM_5G; i++) { + if (channel == rf_chnl_5g[i] && channel <= 140) + index = 0; + } + for (i = 0; i < RF_CHNL_NUM_5G_40M; i++) { + if (channel == rf_chnl_5g_40m[i] && channel <= 140) + index = 1; + } + if (channel == 149 || channel == 155 || channel == 161) + index = 2; + else if (channel == 151 || channel == 153 || channel == 163 + || channel == 165) + index = 3; + else if (channel == 157 || channel == 159) + index = 4; + + if (rtlhal->macphymode == DUALMAC_DUALPHY + && rtlhal->interfaceindex == 1) { + need_pwr_down = rtl92d_phy_enable_anotherphy(hw, false); + rtlhal->during_mac1init_radioa = true; + /* asume no this case */ + if (need_pwr_down) + _rtl92d_phy_enable_rf_env(hw, path, + &u4regvalue); + } + for (i = 0; i < RF_REG_NUM_FOR_C_CUT_5G; i++) { + if (i == 0 && (rtlhal->macphymode == DUALMAC_DUALPHY)) { + rtl_set_rfreg(hw, (enum radio_path)path, + rf_reg_for_c_cut_5g[i], + BRFREGOFFSETMASK, 0xE439D); + } else if (rf_reg_for_c_cut_5g[i] == RF_SYN_G4) { + u4tmp2 = (rf_reg_pram_c_5g[index][i] & + 0x7FF) | (u4tmp << 11); + if (channel == 36) + u4tmp2 &= ~(BIT(7) | BIT(6)); + rtl_set_rfreg(hw, (enum radio_path)path, + rf_reg_for_c_cut_5g[i], + BRFREGOFFSETMASK, u4tmp2); + } else { + rtl_set_rfreg(hw, (enum radio_path)path, + rf_reg_for_c_cut_5g[i], + BRFREGOFFSETMASK, + rf_reg_pram_c_5g[index][i]); + } + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "offset 0x%x value 0x%x path %d index %d readback 0x%x\n", + rf_reg_for_c_cut_5g[i], + rf_reg_pram_c_5g[index][i], + path, index, + rtl_get_rfreg(hw, (enum radio_path)path, + rf_reg_for_c_cut_5g[i], + BRFREGOFFSETMASK)); + } + if (need_pwr_down) + _rtl92d_phy_restore_rf_env(hw, path, &u4regvalue); + if (rtlhal->during_mac1init_radioa) + rtl92d_phy_powerdown_anotherphy(hw, false); + if (channel < 149) + value = 0x07; + else if (channel >= 149) + value = 0x02; + if (channel >= 36 && channel <= 64) + index = 0; + else if (channel >= 100 && channel <= 140) + index = 1; + else + index = 2; + for (rfpath = RF90_PATH_A; rfpath < rtlphy->num_total_rfpath; + rfpath++) { + if (rtlhal->macphymode == DUALMAC_DUALPHY && + rtlhal->interfaceindex == 1) /* MAC 1 5G */ + internal_pa = rtlpriv->efuse.internal_pa_5g[1]; + else + internal_pa = + rtlpriv->efuse.internal_pa_5g[rfpath]; + if (internal_pa) { + for (i = 0; + i < RF_REG_NUM_FOR_C_CUT_5G_INTERNALPA; + i++) { + rtl_set_rfreg(hw, rfpath, + rf_for_c_cut_5g_internal_pa[i], + BRFREGOFFSETMASK, + rf_pram_c_5g_int_pa[index][i]); + RT_TRACE(rtlpriv, COMP_RF, DBG_LOUD, + "offset 0x%x value 0x%x path %d index %d\n", + rf_for_c_cut_5g_internal_pa[i], + rf_pram_c_5g_int_pa[index][i], + rfpath, index); + } + } else { + rtl_set_rfreg(hw, (enum radio_path)rfpath, 0x0B, + mask, value); + } + } + } else if (rtlhal->current_bandtype == BAND_ON_2_4G) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "====>2.4G\n"); + u4tmp = curveindex_2g[channel - 1]; + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "ver 3 set RF-B, 2G, 0x28 = 0x%x !!\n", u4tmp); + if (channel == 1 || channel == 2 || channel == 4 || channel == 9 + || channel == 10 || channel == 11 || channel == 12) + index = 0; + else if (channel == 3 || channel == 13 || channel == 14) + index = 1; + else if (channel >= 5 && channel <= 8) + index = 2; + if (rtlhal->macphymode == DUALMAC_DUALPHY) { + path = RF90_PATH_A; + if (rtlhal->interfaceindex == 0) { + need_pwr_down = + rtl92d_phy_enable_anotherphy(hw, true); + rtlhal->during_mac0init_radiob = true; + + if (need_pwr_down) + _rtl92d_phy_enable_rf_env(hw, path, + &u4regvalue); + } + } + for (i = 0; i < RF_REG_NUM_FOR_C_CUT_2G; i++) { + if (rf_reg_for_c_cut_2g[i] == RF_SYN_G7) + rtl_set_rfreg(hw, (enum radio_path)path, + rf_reg_for_c_cut_2g[i], + BRFREGOFFSETMASK, + (rf_reg_param_for_c_cut_2g[index][i] | + BIT(17))); + else + rtl_set_rfreg(hw, (enum radio_path)path, + rf_reg_for_c_cut_2g[i], + BRFREGOFFSETMASK, + rf_reg_param_for_c_cut_2g + [index][i]); + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "offset 0x%x value 0x%x mak 0x%x path %d index %d readback 0x%x\n", + rf_reg_for_c_cut_2g[i], + rf_reg_param_for_c_cut_2g[index][i], + rf_reg_mask_for_c_cut_2g[i], path, index, + rtl_get_rfreg(hw, (enum radio_path)path, + rf_reg_for_c_cut_2g[i], + BRFREGOFFSETMASK)); + } + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "cosa ver 3 set RF-B, 2G, 0x28 = 0x%x !!\n", + rf_syn_g4_for_c_cut_2g | (u4tmp << 11)); + + rtl_set_rfreg(hw, (enum radio_path)path, RF_SYN_G4, + BRFREGOFFSETMASK, + rf_syn_g4_for_c_cut_2g | (u4tmp << 11)); + if (need_pwr_down) + _rtl92d_phy_restore_rf_env(hw, path, &u4regvalue); + if (rtlhal->during_mac0init_radiob) + rtl92d_phy_powerdown_anotherphy(hw, true); + } + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "<====\n"); +} + +u8 rtl92d_get_rightchnlplace_for_iqk(u8 chnl) +{ + u8 channel_all[59] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, + 60, 62, 64, 100, 102, 104, 106, 108, 110, 112, + 114, 116, 118, 120, 122, 124, 126, 128, 130, + 132, 134, 136, 138, 140, 149, 151, 153, 155, + 157, 159, 161, 163, 165 + }; + u8 place = chnl; + + if (chnl > 14) { + for (place = 14; place < sizeof(channel_all); place++) { + if (channel_all[place] == chnl) + return place - 13; + } + } + + return 0; +} + +#define MAX_TOLERANCE 5 +#define IQK_DELAY_TIME 1 /* ms */ +#define MAX_TOLERANCE_92D 3 + +/* bit0 = 1 => Tx OK, bit1 = 1 => Rx OK */ +static u8 _rtl92d_phy_patha_iqk(struct ieee80211_hw *hw, bool configpathb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u32 regeac, rege94, rege9c, regea4; + u8 result = 0; + + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Path A IQK!\n"); + /* path-A IQK setting */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Path-A IQK setting!\n"); + if (rtlhal->interfaceindex == 0) { + rtl_set_bbreg(hw, 0xe30, BMASKDWORD, 0x10008c1f); + rtl_set_bbreg(hw, 0xe34, BMASKDWORD, 0x10008c1f); + } else { + rtl_set_bbreg(hw, 0xe30, BMASKDWORD, 0x10008c22); + rtl_set_bbreg(hw, 0xe34, BMASKDWORD, 0x10008c22); + } + rtl_set_bbreg(hw, 0xe38, BMASKDWORD, 0x82140102); + rtl_set_bbreg(hw, 0xe3c, BMASKDWORD, 0x28160206); + /* path-B IQK setting */ + if (configpathb) { + rtl_set_bbreg(hw, 0xe50, BMASKDWORD, 0x10008c22); + rtl_set_bbreg(hw, 0xe54, BMASKDWORD, 0x10008c22); + rtl_set_bbreg(hw, 0xe58, BMASKDWORD, 0x82140102); + rtl_set_bbreg(hw, 0xe5c, BMASKDWORD, 0x28160206); + } + /* LO calibration setting */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, "LO calibration setting!\n"); + rtl_set_bbreg(hw, 0xe4c, BMASKDWORD, 0x00462911); + /* One shot, path A LOK & IQK */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, "One shot, path A LOK & IQK!\n"); + rtl_set_bbreg(hw, 0xe48, BMASKDWORD, 0xf9000000); + rtl_set_bbreg(hw, 0xe48, BMASKDWORD, 0xf8000000); + /* delay x ms */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Delay %d ms for One shot, path A LOK & IQK\n", + IQK_DELAY_TIME); + mdelay(IQK_DELAY_TIME); + /* Check failed */ + regeac = rtl_get_bbreg(hw, 0xeac, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xeac = 0x%x\n", regeac); + rege94 = rtl_get_bbreg(hw, 0xe94, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xe94 = 0x%x\n", rege94); + rege9c = rtl_get_bbreg(hw, 0xe9c, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xe9c = 0x%x\n", rege9c); + regea4 = rtl_get_bbreg(hw, 0xea4, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xea4 = 0x%x\n", regea4); + if (!(regeac & BIT(28)) && (((rege94 & 0x03FF0000) >> 16) != 0x142) && + (((rege9c & 0x03FF0000) >> 16) != 0x42)) + result |= 0x01; + else /* if Tx not OK, ignore Rx */ + return result; + /* if Tx is OK, check whether Rx is OK */ + if (!(regeac & BIT(27)) && (((regea4 & 0x03FF0000) >> 16) != 0x132) && + (((regeac & 0x03FF0000) >> 16) != 0x36)) + result |= 0x02; + else + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Path A Rx IQK fail!!\n"); + return result; +} + +/* bit0 = 1 => Tx OK, bit1 = 1 => Rx OK */ +static u8 _rtl92d_phy_patha_iqk_5g_normal(struct ieee80211_hw *hw, + bool configpathb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 regeac, rege94, rege9c, regea4; + u8 result = 0; + u8 i; + u8 retrycount = 2; + u32 TxOKBit = BIT(28), RxOKBit = BIT(27); + + if (rtlhal->interfaceindex == 1) { /* PHY1 */ + TxOKBit = BIT(31); + RxOKBit = BIT(30); + } + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Path A IQK!\n"); + /* path-A IQK setting */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Path-A IQK setting!\n"); + rtl_set_bbreg(hw, 0xe30, BMASKDWORD, 0x18008c1f); + rtl_set_bbreg(hw, 0xe34, BMASKDWORD, 0x18008c1f); + rtl_set_bbreg(hw, 0xe38, BMASKDWORD, 0x82140307); + rtl_set_bbreg(hw, 0xe3c, BMASKDWORD, 0x68160960); + /* path-B IQK setting */ + if (configpathb) { + rtl_set_bbreg(hw, 0xe50, BMASKDWORD, 0x18008c2f); + rtl_set_bbreg(hw, 0xe54, BMASKDWORD, 0x18008c2f); + rtl_set_bbreg(hw, 0xe58, BMASKDWORD, 0x82110000); + rtl_set_bbreg(hw, 0xe5c, BMASKDWORD, 0x68110000); + } + /* LO calibration setting */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, "LO calibration setting!\n"); + rtl_set_bbreg(hw, 0xe4c, BMASKDWORD, 0x00462911); + /* path-A PA on */ + rtl_set_bbreg(hw, RFPGA0_XAB_RFINTERFACESW, BMASKDWORD, 0x07000f60); + rtl_set_bbreg(hw, RFPGA0_XA_RFINTERFACEOE, BMASKDWORD, 0x66e60e30); + for (i = 0; i < retrycount; i++) { + /* One shot, path A LOK & IQK */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "One shot, path A LOK & IQK!\n"); + rtl_set_bbreg(hw, 0xe48, BMASKDWORD, 0xf9000000); + rtl_set_bbreg(hw, 0xe48, BMASKDWORD, 0xf8000000); + /* delay x ms */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Delay %d ms for One shot, path A LOK & IQK.\n", + IQK_DELAY_TIME); + mdelay(IQK_DELAY_TIME * 10); + /* Check failed */ + regeac = rtl_get_bbreg(hw, 0xeac, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xeac = 0x%x\n", regeac); + rege94 = rtl_get_bbreg(hw, 0xe94, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xe94 = 0x%x\n", rege94); + rege9c = rtl_get_bbreg(hw, 0xe9c, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xe9c = 0x%x\n", rege9c); + regea4 = rtl_get_bbreg(hw, 0xea4, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xea4 = 0x%x\n", regea4); + if (!(regeac & TxOKBit) && + (((rege94 & 0x03FF0000) >> 16) != 0x142)) { + result |= 0x01; + } else { /* if Tx not OK, ignore Rx */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Path A Tx IQK fail!!\n"); + continue; + } + + /* if Tx is OK, check whether Rx is OK */ + if (!(regeac & RxOKBit) && + (((regea4 & 0x03FF0000) >> 16) != 0x132)) { + result |= 0x02; + break; + } else { + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Path A Rx IQK fail!!\n"); + } + } + /* path A PA off */ + rtl_set_bbreg(hw, RFPGA0_XAB_RFINTERFACESW, BMASKDWORD, + rtlphy->iqk_bb_backup[0]); + rtl_set_bbreg(hw, RFPGA0_XA_RFINTERFACEOE, BMASKDWORD, + rtlphy->iqk_bb_backup[1]); + return result; +} + +/* bit0 = 1 => Tx OK, bit1 = 1 => Rx OK */ +static u8 _rtl92d_phy_pathb_iqk(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 regeac, regeb4, regebc, regec4, regecc; + u8 result = 0; + + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Path B IQK!\n"); + /* One shot, path B LOK & IQK */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, "One shot, path A LOK & IQK!\n"); + rtl_set_bbreg(hw, 0xe60, BMASKDWORD, 0x00000002); + rtl_set_bbreg(hw, 0xe60, BMASKDWORD, 0x00000000); + /* delay x ms */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Delay %d ms for One shot, path B LOK & IQK\n", IQK_DELAY_TIME); + mdelay(IQK_DELAY_TIME); + /* Check failed */ + regeac = rtl_get_bbreg(hw, 0xeac, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xeac = 0x%x\n", regeac); + regeb4 = rtl_get_bbreg(hw, 0xeb4, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xeb4 = 0x%x\n", regeb4); + regebc = rtl_get_bbreg(hw, 0xebc, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xebc = 0x%x\n", regebc); + regec4 = rtl_get_bbreg(hw, 0xec4, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xec4 = 0x%x\n", regec4); + regecc = rtl_get_bbreg(hw, 0xecc, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xecc = 0x%x\n", regecc); + if (!(regeac & BIT(31)) && (((regeb4 & 0x03FF0000) >> 16) != 0x142) && + (((regebc & 0x03FF0000) >> 16) != 0x42)) + result |= 0x01; + else + return result; + if (!(regeac & BIT(30)) && (((regec4 & 0x03FF0000) >> 16) != 0x132) && + (((regecc & 0x03FF0000) >> 16) != 0x36)) + result |= 0x02; + else + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Path B Rx IQK fail!!\n"); + return result; +} + +/* bit0 = 1 => Tx OK, bit1 = 1 => Rx OK */ +static u8 _rtl92d_phy_pathb_iqk_5g_normal(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 regeac, regeb4, regebc, regec4, regecc; + u8 result = 0; + u8 i; + u8 retrycount = 2; + + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Path B IQK!\n"); + /* path-A IQK setting */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Path-A IQK setting!\n"); + rtl_set_bbreg(hw, 0xe30, BMASKDWORD, 0x18008c1f); + rtl_set_bbreg(hw, 0xe34, BMASKDWORD, 0x18008c1f); + rtl_set_bbreg(hw, 0xe38, BMASKDWORD, 0x82110000); + rtl_set_bbreg(hw, 0xe3c, BMASKDWORD, 0x68110000); + + /* path-B IQK setting */ + rtl_set_bbreg(hw, 0xe50, BMASKDWORD, 0x18008c2f); + rtl_set_bbreg(hw, 0xe54, BMASKDWORD, 0x18008c2f); + rtl_set_bbreg(hw, 0xe58, BMASKDWORD, 0x82140307); + rtl_set_bbreg(hw, 0xe5c, BMASKDWORD, 0x68160960); + + /* LO calibration setting */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, "LO calibration setting!\n"); + rtl_set_bbreg(hw, 0xe4c, BMASKDWORD, 0x00462911); + + /* path-B PA on */ + rtl_set_bbreg(hw, RFPGA0_XAB_RFINTERFACESW, BMASKDWORD, 0x0f600700); + rtl_set_bbreg(hw, RFPGA0_XB_RFINTERFACEOE, BMASKDWORD, 0x061f0d30); + + for (i = 0; i < retrycount; i++) { + /* One shot, path B LOK & IQK */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "One shot, path A LOK & IQK!\n"); + rtl_set_bbreg(hw, 0xe48, BMASKDWORD, 0xfa000000); + rtl_set_bbreg(hw, 0xe48, BMASKDWORD, 0xf8000000); + + /* delay x ms */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Delay %d ms for One shot, path B LOK & IQK.\n", 10); + mdelay(IQK_DELAY_TIME * 10); + + /* Check failed */ + regeac = rtl_get_bbreg(hw, 0xeac, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xeac = 0x%x\n", regeac); + regeb4 = rtl_get_bbreg(hw, 0xeb4, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xeb4 = 0x%x\n", regeb4); + regebc = rtl_get_bbreg(hw, 0xebc, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xebc = 0x%x\n", regebc); + regec4 = rtl_get_bbreg(hw, 0xec4, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xec4 = 0x%x\n", regec4); + regecc = rtl_get_bbreg(hw, 0xecc, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xecc = 0x%x\n", regecc); + if (!(regeac & BIT(31)) && + (((regeb4 & 0x03FF0000) >> 16) != 0x142)) + result |= 0x01; + else + continue; + if (!(regeac & BIT(30)) && + (((regec4 & 0x03FF0000) >> 16) != 0x132)) { + result |= 0x02; + break; + } else { + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Path B Rx IQK fail!!\n"); + } + } + + /* path B PA off */ + rtl_set_bbreg(hw, RFPGA0_XAB_RFINTERFACESW, BMASKDWORD, + rtlphy->iqk_bb_backup[0]); + rtl_set_bbreg(hw, RFPGA0_XB_RFINTERFACEOE, BMASKDWORD, + rtlphy->iqk_bb_backup[2]); + return result; +} + +static void _rtl92d_phy_save_adda_registers(struct ieee80211_hw *hw, + u32 *adda_reg, u32 *adda_backup, + u32 regnum) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Save ADDA parameters.\n"); + for (i = 0; i < regnum; i++) + adda_backup[i] = rtl_get_bbreg(hw, adda_reg[i], BMASKDWORD); +} + +static void _rtl92d_phy_save_mac_registers(struct ieee80211_hw *hw, + u32 *macreg, u32 *macbackup) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Save MAC parameters.\n"); + for (i = 0; i < (IQK_MAC_REG_NUM - 1); i++) + macbackup[i] = rtl_read_byte(rtlpriv, macreg[i]); + macbackup[i] = rtl_read_dword(rtlpriv, macreg[i]); +} + +static void _rtl92d_phy_reload_adda_registers(struct ieee80211_hw *hw, + u32 *adda_reg, u32 *adda_backup, + u32 regnum) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Reload ADDA power saving parameters !\n"); + for (i = 0; i < regnum; i++) + rtl_set_bbreg(hw, adda_reg[i], BMASKDWORD, adda_backup[i]); +} + +static void _rtl92d_phy_reload_mac_registers(struct ieee80211_hw *hw, + u32 *macreg, u32 *macbackup) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Reload MAC parameters !\n"); + for (i = 0; i < (IQK_MAC_REG_NUM - 1); i++) + rtl_write_byte(rtlpriv, macreg[i], (u8) macbackup[i]); + rtl_write_byte(rtlpriv, macreg[i], macbackup[i]); +} + +static void _rtl92d_phy_path_adda_on(struct ieee80211_hw *hw, + u32 *adda_reg, bool patha_on, bool is2t) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 pathon; + u32 i; + + RTPRINT(rtlpriv, FINIT, INIT_IQK, "ADDA ON.\n"); + pathon = patha_on ? 0x04db25a4 : 0x0b1b25a4; + if (patha_on) + pathon = rtlpriv->rtlhal.interfaceindex == 0 ? + 0x04db25a4 : 0x0b1b25a4; + for (i = 0; i < IQK_ADDA_REG_NUM; i++) + rtl_set_bbreg(hw, adda_reg[i], BMASKDWORD, pathon); +} + +static void _rtl92d_phy_mac_setting_calibration(struct ieee80211_hw *hw, + u32 *macreg, u32 *macbackup) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + + RTPRINT(rtlpriv, FINIT, INIT_IQK, "MAC settings for Calibration.\n"); + rtl_write_byte(rtlpriv, macreg[0], 0x3F); + + for (i = 1; i < (IQK_MAC_REG_NUM - 1); i++) + rtl_write_byte(rtlpriv, macreg[i], (u8)(macbackup[i] & + (~BIT(3)))); + rtl_write_byte(rtlpriv, macreg[i], (u8) (macbackup[i] & (~BIT(5)))); +} + +static void _rtl92d_phy_patha_standby(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Path-A standby mode!\n"); + + rtl_set_bbreg(hw, 0xe28, BMASKDWORD, 0x0); + rtl_set_bbreg(hw, RFPGA0_XA_LSSIPARAMETER, BMASKDWORD, 0x00010000); + rtl_set_bbreg(hw, 0xe28, BMASKDWORD, 0x80800000); +} + +static void _rtl92d_phy_pimode_switch(struct ieee80211_hw *hw, bool pi_mode) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 mode; + + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "BB Switch to %s mode!\n", pi_mode ? "PI" : "SI"); + mode = pi_mode ? 0x01000100 : 0x01000000; + rtl_set_bbreg(hw, 0x820, BMASKDWORD, mode); + rtl_set_bbreg(hw, 0x828, BMASKDWORD, mode); +} + +static void _rtl92d_phy_iq_calibrate(struct ieee80211_hw *hw, long result[][8], + u8 t, bool is2t) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 i; + u8 patha_ok, pathb_ok; + static u32 adda_reg[IQK_ADDA_REG_NUM] = { + RFPGA0_XCD_SWITCHCONTROL, 0xe6c, 0xe70, 0xe74, + 0xe78, 0xe7c, 0xe80, 0xe84, + 0xe88, 0xe8c, 0xed0, 0xed4, + 0xed8, 0xedc, 0xee0, 0xeec + }; + static u32 iqk_mac_reg[IQK_MAC_REG_NUM] = { + 0x522, 0x550, 0x551, 0x040 + }; + static u32 iqk_bb_reg[IQK_BB_REG_NUM] = { + RFPGA0_XAB_RFINTERFACESW, RFPGA0_XA_RFINTERFACEOE, + RFPGA0_XB_RFINTERFACEOE, ROFDM0_TRMUXPAR, + RFPGA0_XCD_RFINTERFACESW, ROFDM0_TRXPATHENABLE, + RFPGA0_RFMOD, RFPGA0_ANALOGPARAMETER4, + ROFDM0_XAAGCCORE1, ROFDM0_XBAGCCORE1 + }; + const u32 retrycount = 2; + u32 bbvalue; + + RTPRINT(rtlpriv, FINIT, INIT_IQK, "IQK for 2.4G :Start!!!\n"); + if (t == 0) { + bbvalue = rtl_get_bbreg(hw, RFPGA0_RFMOD, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "==>0x%08x\n", bbvalue); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "IQ Calibration for %s\n", + is2t ? "2T2R" : "1T1R"); + + /* Save ADDA parameters, turn Path A ADDA on */ + _rtl92d_phy_save_adda_registers(hw, adda_reg, + rtlphy->adda_backup, IQK_ADDA_REG_NUM); + _rtl92d_phy_save_mac_registers(hw, iqk_mac_reg, + rtlphy->iqk_mac_backup); + _rtl92d_phy_save_adda_registers(hw, iqk_bb_reg, + rtlphy->iqk_bb_backup, IQK_BB_REG_NUM); + } + _rtl92d_phy_path_adda_on(hw, adda_reg, true, is2t); + if (t == 0) + rtlphy->rfpi_enable = (u8) rtl_get_bbreg(hw, + RFPGA0_XA_HSSIPARAMETER1, BIT(8)); + + /* Switch BB to PI mode to do IQ Calibration. */ + if (!rtlphy->rfpi_enable) + _rtl92d_phy_pimode_switch(hw, true); + + rtl_set_bbreg(hw, RFPGA0_RFMOD, BIT(24), 0x00); + rtl_set_bbreg(hw, ROFDM0_TRXPATHENABLE, BMASKDWORD, 0x03a05600); + rtl_set_bbreg(hw, ROFDM0_TRMUXPAR, BMASKDWORD, 0x000800e4); + rtl_set_bbreg(hw, RFPGA0_XCD_RFINTERFACESW, BMASKDWORD, 0x22204000); + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER4, 0xf00000, 0x0f); + if (is2t) { + rtl_set_bbreg(hw, RFPGA0_XA_LSSIPARAMETER, BMASKDWORD, + 0x00010000); + rtl_set_bbreg(hw, RFPGA0_XB_LSSIPARAMETER, BMASKDWORD, + 0x00010000); + } + /* MAC settings */ + _rtl92d_phy_mac_setting_calibration(hw, iqk_mac_reg, + rtlphy->iqk_mac_backup); + /* Page B init */ + rtl_set_bbreg(hw, 0xb68, BMASKDWORD, 0x0f600000); + if (is2t) + rtl_set_bbreg(hw, 0xb6c, BMASKDWORD, 0x0f600000); + /* IQ calibration setting */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, "IQK setting!\n"); + rtl_set_bbreg(hw, 0xe28, BMASKDWORD, 0x80800000); + rtl_set_bbreg(hw, 0xe40, BMASKDWORD, 0x01007c00); + rtl_set_bbreg(hw, 0xe44, BMASKDWORD, 0x01004800); + for (i = 0; i < retrycount; i++) { + patha_ok = _rtl92d_phy_patha_iqk(hw, is2t); + if (patha_ok == 0x03) { + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Path A IQK Success!!\n"); + result[t][0] = (rtl_get_bbreg(hw, 0xe94, BMASKDWORD) & + 0x3FF0000) >> 16; + result[t][1] = (rtl_get_bbreg(hw, 0xe9c, BMASKDWORD) & + 0x3FF0000) >> 16; + result[t][2] = (rtl_get_bbreg(hw, 0xea4, BMASKDWORD) & + 0x3FF0000) >> 16; + result[t][3] = (rtl_get_bbreg(hw, 0xeac, BMASKDWORD) & + 0x3FF0000) >> 16; + break; + } else if (i == (retrycount - 1) && patha_ok == 0x01) { + /* Tx IQK OK */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Path A IQK Only Tx Success!!\n"); + + result[t][0] = (rtl_get_bbreg(hw, 0xe94, BMASKDWORD) & + 0x3FF0000) >> 16; + result[t][1] = (rtl_get_bbreg(hw, 0xe9c, BMASKDWORD) & + 0x3FF0000) >> 16; + } + } + if (0x00 == patha_ok) + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Path A IQK failed!!\n"); + if (is2t) { + _rtl92d_phy_patha_standby(hw); + /* Turn Path B ADDA on */ + _rtl92d_phy_path_adda_on(hw, adda_reg, false, is2t); + for (i = 0; i < retrycount; i++) { + pathb_ok = _rtl92d_phy_pathb_iqk(hw); + if (pathb_ok == 0x03) { + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Path B IQK Success!!\n"); + result[t][4] = (rtl_get_bbreg(hw, 0xeb4, + BMASKDWORD) & 0x3FF0000) >> 16; + result[t][5] = (rtl_get_bbreg(hw, 0xebc, + BMASKDWORD) & 0x3FF0000) >> 16; + result[t][6] = (rtl_get_bbreg(hw, 0xec4, + BMASKDWORD) & 0x3FF0000) >> 16; + result[t][7] = (rtl_get_bbreg(hw, 0xecc, + BMASKDWORD) & 0x3FF0000) >> 16; + break; + } else if (i == (retrycount - 1) && pathb_ok == 0x01) { + /* Tx IQK OK */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Path B Only Tx IQK Success!!\n"); + result[t][4] = (rtl_get_bbreg(hw, 0xeb4, + BMASKDWORD) & 0x3FF0000) >> 16; + result[t][5] = (rtl_get_bbreg(hw, 0xebc, + BMASKDWORD) & 0x3FF0000) >> 16; + } + } + if (0x00 == pathb_ok) + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Path B IQK failed!!\n"); + } + + /* Back to BB mode, load original value */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "IQK:Back to BB mode, load original value!\n"); + + rtl_set_bbreg(hw, 0xe28, BMASKDWORD, 0); + if (t != 0) { + /* Switch back BB to SI mode after finish IQ Calibration. */ + if (!rtlphy->rfpi_enable) + _rtl92d_phy_pimode_switch(hw, false); + /* Reload ADDA power saving parameters */ + _rtl92d_phy_reload_adda_registers(hw, adda_reg, + rtlphy->adda_backup, IQK_ADDA_REG_NUM); + /* Reload MAC parameters */ + _rtl92d_phy_reload_mac_registers(hw, iqk_mac_reg, + rtlphy->iqk_mac_backup); + if (is2t) + _rtl92d_phy_reload_adda_registers(hw, iqk_bb_reg, + rtlphy->iqk_bb_backup, + IQK_BB_REG_NUM); + else + _rtl92d_phy_reload_adda_registers(hw, iqk_bb_reg, + rtlphy->iqk_bb_backup, + IQK_BB_REG_NUM - 1); + /* load 0xe30 IQC default value */ + rtl_set_bbreg(hw, 0xe30, BMASKDWORD, 0x01008c00); + rtl_set_bbreg(hw, 0xe34, BMASKDWORD, 0x01008c00); + } + RTPRINT(rtlpriv, FINIT, INIT_IQK, "<==\n"); +} + +static void _rtl92d_phy_iq_calibrate_5g_normal(struct ieee80211_hw *hw, + long result[][8], u8 t) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = &(rtlpriv->rtlhal); + u8 patha_ok, pathb_ok; + static u32 adda_reg[IQK_ADDA_REG_NUM] = { + RFPGA0_XCD_SWITCHCONTROL, 0xe6c, 0xe70, 0xe74, + 0xe78, 0xe7c, 0xe80, 0xe84, + 0xe88, 0xe8c, 0xed0, 0xed4, + 0xed8, 0xedc, 0xee0, 0xeec + }; + static u32 iqk_mac_reg[IQK_MAC_REG_NUM] = { + 0x522, 0x550, 0x551, 0x040 + }; + static u32 iqk_bb_reg[IQK_BB_REG_NUM] = { + RFPGA0_XAB_RFINTERFACESW, RFPGA0_XA_RFINTERFACEOE, + RFPGA0_XB_RFINTERFACEOE, ROFDM0_TRMUXPAR, + RFPGA0_XCD_RFINTERFACESW, ROFDM0_TRXPATHENABLE, + RFPGA0_RFMOD, RFPGA0_ANALOGPARAMETER4, + ROFDM0_XAAGCCORE1, ROFDM0_XBAGCCORE1 + }; + u32 bbvalue; + bool is2t = IS_92D_SINGLEPHY(rtlhal->version); + + /* Note: IQ calibration must be performed after loading + * PHY_REG.txt , and radio_a, radio_b.txt */ + + RTPRINT(rtlpriv, FINIT, INIT_IQK, "IQK for 5G NORMAL:Start!!!\n"); + mdelay(IQK_DELAY_TIME * 20); + if (t == 0) { + bbvalue = rtl_get_bbreg(hw, RFPGA0_RFMOD, BMASKDWORD); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "==>0x%08x\n", bbvalue); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "IQ Calibration for %s\n", + is2t ? "2T2R" : "1T1R"); + /* Save ADDA parameters, turn Path A ADDA on */ + _rtl92d_phy_save_adda_registers(hw, adda_reg, + rtlphy->adda_backup, + IQK_ADDA_REG_NUM); + _rtl92d_phy_save_mac_registers(hw, iqk_mac_reg, + rtlphy->iqk_mac_backup); + if (is2t) + _rtl92d_phy_save_adda_registers(hw, iqk_bb_reg, + rtlphy->iqk_bb_backup, + IQK_BB_REG_NUM); + else + _rtl92d_phy_save_adda_registers(hw, iqk_bb_reg, + rtlphy->iqk_bb_backup, + IQK_BB_REG_NUM - 1); + } + _rtl92d_phy_path_adda_on(hw, adda_reg, true, is2t); + /* MAC settings */ + _rtl92d_phy_mac_setting_calibration(hw, iqk_mac_reg, + rtlphy->iqk_mac_backup); + if (t == 0) + rtlphy->rfpi_enable = (u8) rtl_get_bbreg(hw, + RFPGA0_XA_HSSIPARAMETER1, BIT(8)); + /* Switch BB to PI mode to do IQ Calibration. */ + if (!rtlphy->rfpi_enable) + _rtl92d_phy_pimode_switch(hw, true); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BIT(24), 0x00); + rtl_set_bbreg(hw, ROFDM0_TRXPATHENABLE, BMASKDWORD, 0x03a05600); + rtl_set_bbreg(hw, ROFDM0_TRMUXPAR, BMASKDWORD, 0x000800e4); + rtl_set_bbreg(hw, RFPGA0_XCD_RFINTERFACESW, BMASKDWORD, 0x22208000); + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER4, 0xf00000, 0x0f); + + /* Page B init */ + rtl_set_bbreg(hw, 0xb68, BMASKDWORD, 0x0f600000); + if (is2t) + rtl_set_bbreg(hw, 0xb6c, BMASKDWORD, 0x0f600000); + /* IQ calibration setting */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, "IQK setting!\n"); + rtl_set_bbreg(hw, 0xe28, BMASKDWORD, 0x80800000); + rtl_set_bbreg(hw, 0xe40, BMASKDWORD, 0x10007c00); + rtl_set_bbreg(hw, 0xe44, BMASKDWORD, 0x01004800); + patha_ok = _rtl92d_phy_patha_iqk_5g_normal(hw, is2t); + if (patha_ok == 0x03) { + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Path A IQK Success!!\n"); + result[t][0] = (rtl_get_bbreg(hw, 0xe94, BMASKDWORD) & + 0x3FF0000) >> 16; + result[t][1] = (rtl_get_bbreg(hw, 0xe9c, BMASKDWORD) & + 0x3FF0000) >> 16; + result[t][2] = (rtl_get_bbreg(hw, 0xea4, BMASKDWORD) & + 0x3FF0000) >> 16; + result[t][3] = (rtl_get_bbreg(hw, 0xeac, BMASKDWORD) & + 0x3FF0000) >> 16; + } else if (patha_ok == 0x01) { /* Tx IQK OK */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Path A IQK Only Tx Success!!\n"); + + result[t][0] = (rtl_get_bbreg(hw, 0xe94, BMASKDWORD) & + 0x3FF0000) >> 16; + result[t][1] = (rtl_get_bbreg(hw, 0xe9c, BMASKDWORD) & + 0x3FF0000) >> 16; + } else { + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Path A IQK Fail!!\n"); + } + if (is2t) { + /* _rtl92d_phy_patha_standby(hw); */ + /* Turn Path B ADDA on */ + _rtl92d_phy_path_adda_on(hw, adda_reg, false, is2t); + pathb_ok = _rtl92d_phy_pathb_iqk_5g_normal(hw); + if (pathb_ok == 0x03) { + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Path B IQK Success!!\n"); + result[t][4] = (rtl_get_bbreg(hw, 0xeb4, BMASKDWORD) & + 0x3FF0000) >> 16; + result[t][5] = (rtl_get_bbreg(hw, 0xebc, BMASKDWORD) & + 0x3FF0000) >> 16; + result[t][6] = (rtl_get_bbreg(hw, 0xec4, BMASKDWORD) & + 0x3FF0000) >> 16; + result[t][7] = (rtl_get_bbreg(hw, 0xecc, BMASKDWORD) & + 0x3FF0000) >> 16; + } else if (pathb_ok == 0x01) { /* Tx IQK OK */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Path B Only Tx IQK Success!!\n"); + result[t][4] = (rtl_get_bbreg(hw, 0xeb4, BMASKDWORD) & + 0x3FF0000) >> 16; + result[t][5] = (rtl_get_bbreg(hw, 0xebc, BMASKDWORD) & + 0x3FF0000) >> 16; + } else { + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Path B IQK failed!!\n"); + } + } + + /* Back to BB mode, load original value */ + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "IQK:Back to BB mode, load original value!\n"); + rtl_set_bbreg(hw, 0xe28, BMASKDWORD, 0); + if (t != 0) { + if (is2t) + _rtl92d_phy_reload_adda_registers(hw, iqk_bb_reg, + rtlphy->iqk_bb_backup, + IQK_BB_REG_NUM); + else + _rtl92d_phy_reload_adda_registers(hw, iqk_bb_reg, + rtlphy->iqk_bb_backup, + IQK_BB_REG_NUM - 1); + /* Reload MAC parameters */ + _rtl92d_phy_reload_mac_registers(hw, iqk_mac_reg, + rtlphy->iqk_mac_backup); + /* Switch back BB to SI mode after finish IQ Calibration. */ + if (!rtlphy->rfpi_enable) + _rtl92d_phy_pimode_switch(hw, false); + /* Reload ADDA power saving parameters */ + _rtl92d_phy_reload_adda_registers(hw, adda_reg, + rtlphy->adda_backup, + IQK_ADDA_REG_NUM); + } + RTPRINT(rtlpriv, FINIT, INIT_IQK, "<==\n"); +} + +static bool _rtl92d_phy_simularity_compare(struct ieee80211_hw *hw, + long result[][8], u8 c1, u8 c2) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = &(rtlpriv->rtlhal); + u32 i, j, diff, sim_bitmap, bound; + u8 final_candidate[2] = {0xFF, 0xFF}; /* for path A and path B */ + bool bresult = true; + bool is2t = IS_92D_SINGLEPHY(rtlhal->version); + + if (is2t) + bound = 8; + else + bound = 4; + sim_bitmap = 0; + for (i = 0; i < bound; i++) { + diff = (result[c1][i] > result[c2][i]) ? (result[c1][i] - + result[c2][i]) : (result[c2][i] - result[c1][i]); + if (diff > MAX_TOLERANCE_92D) { + if ((i == 2 || i == 6) && !sim_bitmap) { + if (result[c1][i] + result[c1][i + 1] == 0) + final_candidate[(i / 4)] = c2; + else if (result[c2][i] + result[c2][i + 1] == 0) + final_candidate[(i / 4)] = c1; + else + sim_bitmap = sim_bitmap | (1 << i); + } else { + sim_bitmap = sim_bitmap | (1 << i); + } + } + } + if (sim_bitmap == 0) { + for (i = 0; i < (bound / 4); i++) { + if (final_candidate[i] != 0xFF) { + for (j = i * 4; j < (i + 1) * 4 - 2; j++) + result[3][j] = + result[final_candidate[i]][j]; + bresult = false; + } + } + return bresult; + } + if (!(sim_bitmap & 0x0F)) { /* path A OK */ + for (i = 0; i < 4; i++) + result[3][i] = result[c1][i]; + } else if (!(sim_bitmap & 0x03)) { /* path A, Tx OK */ + for (i = 0; i < 2; i++) + result[3][i] = result[c1][i]; + } + if (!(sim_bitmap & 0xF0) && is2t) { /* path B OK */ + for (i = 4; i < 8; i++) + result[3][i] = result[c1][i]; + } else if (!(sim_bitmap & 0x30)) { /* path B, Tx OK */ + for (i = 4; i < 6; i++) + result[3][i] = result[c1][i]; + } + return false; +} + +static void _rtl92d_phy_patha_fill_iqk_matrix(struct ieee80211_hw *hw, + bool iqk_ok, long result[][8], + u8 final_candidate, bool txonly) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = &(rtlpriv->rtlhal); + u32 oldval_0, val_x, tx0_a, reg; + long val_y, tx0_c; + bool is2t = IS_92D_SINGLEPHY(rtlhal->version) || + rtlhal->macphymode == DUALMAC_DUALPHY; + + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Path A IQ Calibration %s !\n", iqk_ok ? "Success" : "Failed"); + if (final_candidate == 0xFF) { + return; + } else if (iqk_ok) { + oldval_0 = (rtl_get_bbreg(hw, ROFDM0_XATxIQIMBALANCE, + BMASKDWORD) >> 22) & 0x3FF; /* OFDM0_D */ + val_x = result[final_candidate][0]; + if ((val_x & 0x00000200) != 0) + val_x = val_x | 0xFFFFFC00; + tx0_a = (val_x * oldval_0) >> 8; + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "X = 0x%x, tx0_a = 0x%x, oldval_0 0x%x\n", + val_x, tx0_a, oldval_0); + rtl_set_bbreg(hw, ROFDM0_XATxIQIMBALANCE, 0x3FF, tx0_a); + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, BIT(24), + ((val_x * oldval_0 >> 7) & 0x1)); + val_y = result[final_candidate][1]; + if ((val_y & 0x00000200) != 0) + val_y = val_y | 0xFFFFFC00; + /* path B IQK result + 3 */ + if (rtlhal->interfaceindex == 1 && + rtlhal->current_bandtype == BAND_ON_5G) + val_y += 3; + tx0_c = (val_y * oldval_0) >> 8; + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "Y = 0x%lx, tx0_c = 0x%lx\n", + val_y, tx0_c); + rtl_set_bbreg(hw, ROFDM0_XCTxAFE, 0xF0000000, + ((tx0_c & 0x3C0) >> 6)); + rtl_set_bbreg(hw, ROFDM0_XATxIQIMBALANCE, 0x003F0000, + (tx0_c & 0x3F)); + if (is2t) + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, BIT(26), + ((val_y * oldval_0 >> 7) & 0x1)); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "0xC80 = 0x%x\n", + rtl_get_bbreg(hw, ROFDM0_XATxIQIMBALANCE, + BMASKDWORD)); + if (txonly) { + RTPRINT(rtlpriv, FINIT, INIT_IQK, "only Tx OK\n"); + return; + } + reg = result[final_candidate][2]; + rtl_set_bbreg(hw, ROFDM0_XARXIQIMBALANCE, 0x3FF, reg); + reg = result[final_candidate][3] & 0x3F; + rtl_set_bbreg(hw, ROFDM0_XARXIQIMBALANCE, 0xFC00, reg); + reg = (result[final_candidate][3] >> 6) & 0xF; + rtl_set_bbreg(hw, 0xca0, 0xF0000000, reg); + } +} + +static void _rtl92d_phy_pathb_fill_iqk_matrix(struct ieee80211_hw *hw, + bool iqk_ok, long result[][8], u8 final_candidate, bool txonly) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = &(rtlpriv->rtlhal); + u32 oldval_1, val_x, tx1_a, reg; + long val_y, tx1_c; + + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Path B IQ Calibration %s !\n", + iqk_ok ? "Success" : "Failed"); + if (final_candidate == 0xFF) { + return; + } else if (iqk_ok) { + oldval_1 = (rtl_get_bbreg(hw, ROFDM0_XBTxIQIMBALANCE, + BMASKDWORD) >> 22) & 0x3FF; + val_x = result[final_candidate][4]; + if ((val_x & 0x00000200) != 0) + val_x = val_x | 0xFFFFFC00; + tx1_a = (val_x * oldval_1) >> 8; + RTPRINT(rtlpriv, FINIT, INIT_IQK, "X = 0x%x, tx1_a = 0x%x\n", + val_x, tx1_a); + rtl_set_bbreg(hw, ROFDM0_XBTxIQIMBALANCE, 0x3FF, tx1_a); + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, BIT(28), + ((val_x * oldval_1 >> 7) & 0x1)); + val_y = result[final_candidate][5]; + if ((val_y & 0x00000200) != 0) + val_y = val_y | 0xFFFFFC00; + if (rtlhal->current_bandtype == BAND_ON_5G) + val_y += 3; + tx1_c = (val_y * oldval_1) >> 8; + RTPRINT(rtlpriv, FINIT, INIT_IQK, "Y = 0x%lx, tx1_c = 0x%lx\n", + val_y, tx1_c); + rtl_set_bbreg(hw, ROFDM0_XDTxAFE, 0xF0000000, + ((tx1_c & 0x3C0) >> 6)); + rtl_set_bbreg(hw, ROFDM0_XBTxIQIMBALANCE, 0x003F0000, + (tx1_c & 0x3F)); + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, BIT(30), + ((val_y * oldval_1 >> 7) & 0x1)); + if (txonly) + return; + reg = result[final_candidate][6]; + rtl_set_bbreg(hw, ROFDM0_XBRXIQIMBALANCE, 0x3FF, reg); + reg = result[final_candidate][7] & 0x3F; + rtl_set_bbreg(hw, ROFDM0_XBRXIQIMBALANCE, 0xFC00, reg); + reg = (result[final_candidate][7] >> 6) & 0xF; + rtl_set_bbreg(hw, ROFDM0_AGCRSSITABLE, 0x0000F000, reg); + } +} + +void rtl92d_phy_iq_calibrate(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = &(rtlpriv->rtlhal); + long result[4][8]; + u8 i, final_candidate, indexforchannel; + bool patha_ok, pathb_ok; + long rege94, rege9c, regea4, regeac, regeb4; + long regebc, regec4, regecc, regtmp = 0; + bool is12simular, is13simular, is23simular; + unsigned long flag = 0; + + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "IQK:Start!!!channel %d\n", rtlphy->current_channel); + for (i = 0; i < 8; i++) { + result[0][i] = 0; + result[1][i] = 0; + result[2][i] = 0; + result[3][i] = 0; + } + final_candidate = 0xff; + patha_ok = false; + pathb_ok = false; + is12simular = false; + is23simular = false; + is13simular = false; + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "IQK !!!currentband %d\n", rtlhal->current_bandtype); + rtl92d_acquire_cckandrw_pagea_ctl(hw, &flag); + for (i = 0; i < 3; i++) { + if (rtlhal->current_bandtype == BAND_ON_5G) { + _rtl92d_phy_iq_calibrate_5g_normal(hw, result, i); + } else if (rtlhal->current_bandtype == BAND_ON_2_4G) { + if (IS_92D_SINGLEPHY(rtlhal->version)) + _rtl92d_phy_iq_calibrate(hw, result, i, true); + else + _rtl92d_phy_iq_calibrate(hw, result, i, false); + } + if (i == 1) { + is12simular = _rtl92d_phy_simularity_compare(hw, result, + 0, 1); + if (is12simular) { + final_candidate = 0; + break; + } + } + if (i == 2) { + is13simular = _rtl92d_phy_simularity_compare(hw, result, + 0, 2); + if (is13simular) { + final_candidate = 0; + break; + } + is23simular = _rtl92d_phy_simularity_compare(hw, result, + 1, 2); + if (is23simular) { + final_candidate = 1; + } else { + for (i = 0; i < 8; i++) + regtmp += result[3][i]; + + if (regtmp != 0) + final_candidate = 3; + else + final_candidate = 0xFF; + } + } + } + rtl92d_release_cckandrw_pagea_ctl(hw, &flag); + for (i = 0; i < 4; i++) { + rege94 = result[i][0]; + rege9c = result[i][1]; + regea4 = result[i][2]; + regeac = result[i][3]; + regeb4 = result[i][4]; + regebc = result[i][5]; + regec4 = result[i][6]; + regecc = result[i][7]; + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "IQK: rege94=%lx rege9c=%lx regea4=%lx regeac=%lx regeb4=%lx regebc=%lx regec4=%lx regecc=%lx\n", + rege94, rege9c, regea4, regeac, regeb4, regebc, regec4, + regecc); + } + if (final_candidate != 0xff) { + rtlphy->reg_e94 = rege94 = result[final_candidate][0]; + rtlphy->reg_e9c = rege9c = result[final_candidate][1]; + regea4 = result[final_candidate][2]; + regeac = result[final_candidate][3]; + rtlphy->reg_eb4 = regeb4 = result[final_candidate][4]; + rtlphy->reg_ebc = regebc = result[final_candidate][5]; + regec4 = result[final_candidate][6]; + regecc = result[final_candidate][7]; + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "IQK: final_candidate is %x\n", final_candidate); + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "IQK: rege94=%lx rege9c=%lx regea4=%lx regeac=%lx regeb4=%lx regebc=%lx regec4=%lx regecc=%lx\n", + rege94, rege9c, regea4, regeac, regeb4, regebc, regec4, + regecc); + patha_ok = pathb_ok = true; + } else { + rtlphy->reg_e94 = rtlphy->reg_eb4 = 0x100; /* X default value */ + rtlphy->reg_e9c = rtlphy->reg_ebc = 0x0; /* Y default value */ + } + if ((rege94 != 0) /*&&(regea4 != 0) */) + _rtl92d_phy_patha_fill_iqk_matrix(hw, patha_ok, result, + final_candidate, (regea4 == 0)); + if (IS_92D_SINGLEPHY(rtlhal->version)) { + if ((regeb4 != 0) /*&&(regec4 != 0) */) + _rtl92d_phy_pathb_fill_iqk_matrix(hw, pathb_ok, result, + final_candidate, (regec4 == 0)); + } + if (final_candidate != 0xFF) { + indexforchannel = rtl92d_get_rightchnlplace_for_iqk( + rtlphy->current_channel); + + for (i = 0; i < IQK_MATRIX_REG_NUM; i++) + rtlphy->iqk_matrix[indexforchannel]. + value[0][i] = result[final_candidate][i]; + rtlphy->iqk_matrix[indexforchannel].iqk_done = + true; + + RT_TRACE(rtlpriv, COMP_SCAN | COMP_MLME, DBG_LOUD, + "IQK OK indexforchannel %d\n", indexforchannel); + } +} + +void rtl92d_phy_reload_iqk_setting(struct ieee80211_hw *hw, u8 channel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = &(rtlpriv->rtlhal); + u8 indexforchannel; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "channel %d\n", channel); + /*------Do IQK for normal chip and test chip 5G band------- */ + indexforchannel = rtl92d_get_rightchnlplace_for_iqk(channel); + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "indexforchannel %d done %d\n", + indexforchannel, + rtlphy->iqk_matrix[indexforchannel].iqk_done); + if (0 && !rtlphy->iqk_matrix[indexforchannel].iqk_done && + rtlphy->need_iqk) { + /* Re Do IQK. */ + RT_TRACE(rtlpriv, COMP_SCAN | COMP_INIT, DBG_LOUD, + "Do IQK Matrix reg for channel:%d....\n", channel); + rtl92d_phy_iq_calibrate(hw); + } else { + /* Just load the value. */ + /* 2G band just load once. */ + if (((!rtlhal->load_imrandiqk_setting_for2g) && + indexforchannel == 0) || indexforchannel > 0) { + RT_TRACE(rtlpriv, COMP_SCAN, DBG_LOUD, + "Just Read IQK Matrix reg for channel:%d....\n", + channel); + if ((rtlphy->iqk_matrix[indexforchannel]. + value[0] != NULL) + /*&&(regea4 != 0) */) + _rtl92d_phy_patha_fill_iqk_matrix(hw, true, + rtlphy->iqk_matrix[ + indexforchannel].value, 0, + (rtlphy->iqk_matrix[ + indexforchannel].value[0][2] == 0)); + if (IS_92D_SINGLEPHY(rtlhal->version)) { + if ((rtlphy->iqk_matrix[ + indexforchannel].value[0][4] != 0) + /*&&(regec4 != 0) */) + _rtl92d_phy_pathb_fill_iqk_matrix(hw, + true, + rtlphy->iqk_matrix[ + indexforchannel].value, 0, + (rtlphy->iqk_matrix[ + indexforchannel].value[0][6] + == 0)); + } + } + } + rtlphy->need_iqk = false; + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "<====\n"); +} + +static u32 _rtl92d_phy_get_abs(u32 val1, u32 val2) +{ + u32 ret; + + if (val1 >= val2) + ret = val1 - val2; + else + ret = val2 - val1; + return ret; +} + +static bool _rtl92d_is_legal_5g_channel(struct ieee80211_hw *hw, u8 channel) +{ + + int i; + u8 channel_5g[45] = { + 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, + 60, 62, 64, 100, 102, 104, 106, 108, 110, 112, + 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, + 134, 136, 138, 140, 149, 151, 153, 155, 157, 159, + 161, 163, 165 + }; + + for (i = 0; i < sizeof(channel_5g); i++) + if (channel == channel_5g[i]) + return true; + return false; +} + +static void _rtl92d_phy_calc_curvindex(struct ieee80211_hw *hw, + u32 *targetchnl, u32 * curvecount_val, + bool is5g, u32 *curveindex) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 smallest_abs_val = 0xffffffff, u4tmp; + u8 i, j; + u8 chnl_num = is5g ? TARGET_CHNL_NUM_5G : TARGET_CHNL_NUM_2G; + + for (i = 0; i < chnl_num; i++) { + if (is5g && !_rtl92d_is_legal_5g_channel(hw, i + 1)) + continue; + curveindex[i] = 0; + for (j = 0; j < (CV_CURVE_CNT * 2); j++) { + u4tmp = _rtl92d_phy_get_abs(targetchnl[i], + curvecount_val[j]); + + if (u4tmp < smallest_abs_val) { + curveindex[i] = j; + smallest_abs_val = u4tmp; + } + } + smallest_abs_val = 0xffffffff; + RTPRINT(rtlpriv, FINIT, INIT_IQK, "curveindex[%d] = %x\n", + i, curveindex[i]); + } +} + +static void _rtl92d_phy_reload_lck_setting(struct ieee80211_hw *hw, + u8 channel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 erfpath = rtlpriv->rtlhal.current_bandtype == + BAND_ON_5G ? RF90_PATH_A : + IS_92D_SINGLEPHY(rtlpriv->rtlhal.version) ? + RF90_PATH_B : RF90_PATH_A; + u32 u4tmp = 0, u4regvalue = 0; + bool bneed_powerdown_radio = false; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "path %d\n", erfpath); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "band type = %d\n", + rtlpriv->rtlhal.current_bandtype); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "channel = %d\n", channel); + if (rtlpriv->rtlhal.current_bandtype == BAND_ON_5G) {/* Path-A for 5G */ + u4tmp = curveindex_5g[channel-1]; + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "ver 1 set RF-A, 5G, 0x28 = 0x%ulx !!\n", u4tmp); + if (rtlpriv->rtlhal.macphymode == DUALMAC_DUALPHY && + rtlpriv->rtlhal.interfaceindex == 1) { + bneed_powerdown_radio = + rtl92d_phy_enable_anotherphy(hw, false); + rtlpriv->rtlhal.during_mac1init_radioa = true; + /* asume no this case */ + if (bneed_powerdown_radio) + _rtl92d_phy_enable_rf_env(hw, erfpath, + &u4regvalue); + } + rtl_set_rfreg(hw, erfpath, RF_SYN_G4, 0x3f800, u4tmp); + if (bneed_powerdown_radio) + _rtl92d_phy_restore_rf_env(hw, erfpath, &u4regvalue); + if (rtlpriv->rtlhal.during_mac1init_radioa) + rtl92d_phy_powerdown_anotherphy(hw, false); + } else if (rtlpriv->rtlhal.current_bandtype == BAND_ON_2_4G) { + u4tmp = curveindex_2g[channel-1]; + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "ver 3 set RF-B, 2G, 0x28 = 0x%ulx !!\n", u4tmp); + if (rtlpriv->rtlhal.macphymode == DUALMAC_DUALPHY && + rtlpriv->rtlhal.interfaceindex == 0) { + bneed_powerdown_radio = + rtl92d_phy_enable_anotherphy(hw, true); + rtlpriv->rtlhal.during_mac0init_radiob = true; + if (bneed_powerdown_radio) + _rtl92d_phy_enable_rf_env(hw, erfpath, + &u4regvalue); + } + rtl_set_rfreg(hw, erfpath, RF_SYN_G4, 0x3f800, u4tmp); + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "ver 3 set RF-B, 2G, 0x28 = 0x%ulx !!\n", + rtl_get_rfreg(hw, erfpath, RF_SYN_G4, 0x3f800)); + if (bneed_powerdown_radio) + _rtl92d_phy_restore_rf_env(hw, erfpath, &u4regvalue); + if (rtlpriv->rtlhal.during_mac0init_radiob) + rtl92d_phy_powerdown_anotherphy(hw, true); + } + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "<====\n"); +} + +static void _rtl92d_phy_lc_calibrate_sw(struct ieee80211_hw *hw, bool is2t) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = &(rtlpriv->rtlhal); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u8 tmpreg, index, rf_mode[2]; + u8 path = is2t ? 2 : 1; + u8 i; + u32 u4tmp, offset; + u32 curvecount_val[CV_CURVE_CNT * 2] = {0}; + u16 timeout = 800, timecount = 0; + + /* Check continuous TX and Packet TX */ + tmpreg = rtl_read_byte(rtlpriv, 0xd03); + /* if Deal with contisuous TX case, disable all continuous TX */ + /* if Deal with Packet TX case, block all queues */ + if ((tmpreg & 0x70) != 0) + rtl_write_byte(rtlpriv, 0xd03, tmpreg & 0x8F); + else + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0xFF); + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER4, 0xF00000, 0x0F); + for (index = 0; index < path; index++) { + /* 1. Read original RF mode */ + offset = index == 0 ? ROFDM0_XAAGCCORE1 : ROFDM0_XBAGCCORE1; + rf_mode[index] = rtl_read_byte(rtlpriv, offset); + /* 2. Set RF mode = standby mode */ + rtl_set_rfreg(hw, (enum radio_path)index, RF_AC, + BRFREGOFFSETMASK, 0x010000); + if (rtlpci->init_ready) { + /* switch CV-curve control by LC-calibration */ + rtl_set_rfreg(hw, (enum radio_path)index, RF_SYN_G7, + BIT(17), 0x0); + /* 4. Set LC calibration begin */ + rtl_set_rfreg(hw, (enum radio_path)index, RF_CHNLBW, + 0x08000, 0x01); + } + u4tmp = rtl_get_rfreg(hw, (enum radio_path)index, RF_SYN_G6, + BRFREGOFFSETMASK); + while ((!(u4tmp & BIT(11))) && timecount <= timeout) { + mdelay(50); + timecount += 50; + u4tmp = rtl_get_rfreg(hw, (enum radio_path)index, + RF_SYN_G6, BRFREGOFFSETMASK); + } + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "PHY_LCK finish delay for %d ms=2\n", timecount); + u4tmp = rtl_get_rfreg(hw, index, RF_SYN_G4, BRFREGOFFSETMASK); + if (index == 0 && rtlhal->interfaceindex == 0) { + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "path-A / 5G LCK\n"); + } else { + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "path-B / 2.4G LCK\n"); + } + memset(&curvecount_val[0], 0, CV_CURVE_CNT * 2); + /* Set LC calibration off */ + rtl_set_rfreg(hw, (enum radio_path)index, RF_CHNLBW, + 0x08000, 0x0); + RTPRINT(rtlpriv, FINIT, INIT_IQK, "set RF 0x18[15] = 0\n"); + /* save Curve-counting number */ + for (i = 0; i < CV_CURVE_CNT; i++) { + u32 readval = 0, readval2 = 0; + rtl_set_rfreg(hw, (enum radio_path)index, 0x3F, + 0x7f, i); + + rtl_set_rfreg(hw, (enum radio_path)index, 0x4D, + BRFREGOFFSETMASK, 0x0); + readval = rtl_get_rfreg(hw, (enum radio_path)index, + 0x4F, BRFREGOFFSETMASK); + curvecount_val[2 * i + 1] = (readval & 0xfffe0) >> 5; + /* reg 0x4f [4:0] */ + /* reg 0x50 [19:10] */ + readval2 = rtl_get_rfreg(hw, (enum radio_path)index, + 0x50, 0xffc00); + curvecount_val[2 * i] = (((readval & 0x1F) << 10) | + readval2); + } + if (index == 0 && rtlhal->interfaceindex == 0) + _rtl92d_phy_calc_curvindex(hw, targetchnl_5g, + curvecount_val, + true, curveindex_5g); + else + _rtl92d_phy_calc_curvindex(hw, targetchnl_2g, + curvecount_val, + false, curveindex_2g); + /* switch CV-curve control mode */ + rtl_set_rfreg(hw, (enum radio_path)index, RF_SYN_G7, + BIT(17), 0x1); + } + + /* Restore original situation */ + for (index = 0; index < path; index++) { + offset = index == 0 ? ROFDM0_XAAGCCORE1 : ROFDM0_XBAGCCORE1; + rtl_write_byte(rtlpriv, offset, 0x50); + rtl_write_byte(rtlpriv, offset, rf_mode[index]); + } + if ((tmpreg & 0x70) != 0) + rtl_write_byte(rtlpriv, 0xd03, tmpreg); + else /*Deal with Packet TX case */ + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0x00); + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER4, 0xF00000, 0x00); + _rtl92d_phy_reload_lck_setting(hw, rtlpriv->phy.current_channel); +} + +static void _rtl92d_phy_lc_calibrate(struct ieee80211_hw *hw, bool is2t) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + RTPRINT(rtlpriv, FINIT, INIT_IQK, "cosa PHY_LCK ver=2\n"); + _rtl92d_phy_lc_calibrate_sw(hw, is2t); +} + +void rtl92d_phy_lc_calibrate(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = &(rtlpriv->rtlhal); + u32 timeout = 2000, timecount = 0; + + while (rtlpriv->mac80211.act_scanning && timecount < timeout) { + udelay(50); + timecount += 50; + } + + rtlphy->lck_inprogress = true; + RTPRINT(rtlpriv, FINIT, INIT_IQK, + "LCK:Start!!! currentband %x delay %d ms\n", + rtlhal->current_bandtype, timecount); + if (IS_92D_SINGLEPHY(rtlhal->version)) { + _rtl92d_phy_lc_calibrate(hw, true); + } else { + /* For 1T1R */ + _rtl92d_phy_lc_calibrate(hw, false); + } + rtlphy->lck_inprogress = false; + RTPRINT(rtlpriv, FINIT, INIT_IQK, "LCK:Finish!!!\n"); +} + +void rtl92d_phy_ap_calibrate(struct ieee80211_hw *hw, char delta) +{ + return; +} + +static bool _rtl92d_phy_set_sw_chnl_cmdarray(struct swchnlcmd *cmdtable, + u32 cmdtableidx, u32 cmdtablesz, enum swchnlcmd_id cmdid, + u32 para1, u32 para2, u32 msdelay) +{ + struct swchnlcmd *pcmd; + + if (cmdtable == NULL) { + RT_ASSERT(false, "cmdtable cannot be NULL\n"); + return false; + } + if (cmdtableidx >= cmdtablesz) + return false; + + pcmd = cmdtable + cmdtableidx; + pcmd->cmdid = cmdid; + pcmd->para1 = para1; + pcmd->para2 = para2; + pcmd->msdelay = msdelay; + return true; +} + +void rtl92d_phy_reset_iqk_result(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u8 i; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "settings regs %d default regs %d\n", + (int)(sizeof(rtlphy->iqk_matrix) / + sizeof(struct iqk_matrix_regs)), + IQK_MATRIX_REG_NUM); + /* 0xe94, 0xe9c, 0xea4, 0xeac, 0xeb4, 0xebc, 0xec4, 0xecc */ + for (i = 0; i < IQK_MATRIX_SETTINGS_NUM; i++) { + rtlphy->iqk_matrix[i].value[0][0] = 0x100; + rtlphy->iqk_matrix[i].value[0][2] = 0x100; + rtlphy->iqk_matrix[i].value[0][4] = 0x100; + rtlphy->iqk_matrix[i].value[0][6] = 0x100; + rtlphy->iqk_matrix[i].value[0][1] = 0x0; + rtlphy->iqk_matrix[i].value[0][3] = 0x0; + rtlphy->iqk_matrix[i].value[0][5] = 0x0; + rtlphy->iqk_matrix[i].value[0][7] = 0x0; + rtlphy->iqk_matrix[i].iqk_done = false; + } +} + +static bool _rtl92d_phy_sw_chnl_step_by_step(struct ieee80211_hw *hw, + u8 channel, u8 *stage, u8 *step, + u32 *delay) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct swchnlcmd precommoncmd[MAX_PRECMD_CNT]; + u32 precommoncmdcnt; + struct swchnlcmd postcommoncmd[MAX_POSTCMD_CNT]; + u32 postcommoncmdcnt; + struct swchnlcmd rfdependcmd[MAX_RFDEPENDCMD_CNT]; + u32 rfdependcmdcnt; + struct swchnlcmd *currentcmd = NULL; + u8 rfpath; + u8 num_total_rfpath = rtlphy->num_total_rfpath; + + precommoncmdcnt = 0; + _rtl92d_phy_set_sw_chnl_cmdarray(precommoncmd, precommoncmdcnt++, + MAX_PRECMD_CNT, + CMDID_SET_TXPOWEROWER_LEVEL, 0, 0, 0); + _rtl92d_phy_set_sw_chnl_cmdarray(precommoncmd, precommoncmdcnt++, + MAX_PRECMD_CNT, CMDID_END, 0, 0, 0); + postcommoncmdcnt = 0; + _rtl92d_phy_set_sw_chnl_cmdarray(postcommoncmd, postcommoncmdcnt++, + MAX_POSTCMD_CNT, CMDID_END, 0, 0, 0); + rfdependcmdcnt = 0; + _rtl92d_phy_set_sw_chnl_cmdarray(rfdependcmd, rfdependcmdcnt++, + MAX_RFDEPENDCMD_CNT, CMDID_RF_WRITEREG, + RF_CHNLBW, channel, 0); + _rtl92d_phy_set_sw_chnl_cmdarray(rfdependcmd, rfdependcmdcnt++, + MAX_RFDEPENDCMD_CNT, CMDID_END, + 0, 0, 0); + + do { + switch (*stage) { + case 0: + currentcmd = &precommoncmd[*step]; + break; + case 1: + currentcmd = &rfdependcmd[*step]; + break; + case 2: + currentcmd = &postcommoncmd[*step]; + break; + } + if (currentcmd->cmdid == CMDID_END) { + if ((*stage) == 2) { + return true; + } else { + (*stage)++; + (*step) = 0; + continue; + } + } + switch (currentcmd->cmdid) { + case CMDID_SET_TXPOWEROWER_LEVEL: + rtl92d_phy_set_txpower_level(hw, channel); + break; + case CMDID_WRITEPORT_ULONG: + rtl_write_dword(rtlpriv, currentcmd->para1, + currentcmd->para2); + break; + case CMDID_WRITEPORT_USHORT: + rtl_write_word(rtlpriv, currentcmd->para1, + (u16)currentcmd->para2); + break; + case CMDID_WRITEPORT_UCHAR: + rtl_write_byte(rtlpriv, currentcmd->para1, + (u8)currentcmd->para2); + break; + case CMDID_RF_WRITEREG: + for (rfpath = 0; rfpath < num_total_rfpath; rfpath++) { + rtlphy->rfreg_chnlval[rfpath] = + ((rtlphy->rfreg_chnlval[rfpath] & + 0xffffff00) | currentcmd->para2); + if (rtlpriv->rtlhal.current_bandtype == + BAND_ON_5G) { + if (currentcmd->para2 > 99) + rtlphy->rfreg_chnlval[rfpath] = + rtlphy->rfreg_chnlval + [rfpath] | (BIT(18)); + else + rtlphy->rfreg_chnlval[rfpath] = + rtlphy->rfreg_chnlval + [rfpath] & (~BIT(18)); + rtlphy->rfreg_chnlval[rfpath] |= + (BIT(16) | BIT(8)); + } else { + rtlphy->rfreg_chnlval[rfpath] &= + ~(BIT(8) | BIT(16) | BIT(18)); + } + rtl_set_rfreg(hw, (enum radio_path)rfpath, + currentcmd->para1, + BRFREGOFFSETMASK, + rtlphy->rfreg_chnlval[rfpath]); + _rtl92d_phy_reload_imr_setting(hw, channel, + rfpath); + } + _rtl92d_phy_switch_rf_setting(hw, channel); + /* do IQK when all parameters are ready */ + rtl92d_phy_reload_iqk_setting(hw, channel); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + break; + } while (true); + (*delay) = currentcmd->msdelay; + (*step)++; + return false; +} + +u8 rtl92d_phy_sw_chnl(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u32 delay; + u32 timeout = 1000, timecount = 0; + u8 channel = rtlphy->current_channel; + u32 ret_value; + + if (rtlphy->sw_chnl_inprogress) + return 0; + if (rtlphy->set_bwmode_inprogress) + return 0; + + if ((is_hal_stop(rtlhal)) || (RT_CANNOT_IO(hw))) { + RT_TRACE(rtlpriv, COMP_CHAN, DBG_LOUD, + "sw_chnl_inprogress false driver sleep or unload\n"); + return 0; + } + while (rtlphy->lck_inprogress && timecount < timeout) { + mdelay(50); + timecount += 50; + } + if (rtlhal->macphymode == SINGLEMAC_SINGLEPHY && + rtlhal->bandset == BAND_ON_BOTH) { + ret_value = rtl_get_bbreg(hw, RFPGA0_XAB_RFPARAMETER, + BMASKDWORD); + if (rtlphy->current_channel > 14 && !(ret_value & BIT(0))) + rtl92d_phy_switch_wirelessband(hw, BAND_ON_5G); + else if (rtlphy->current_channel <= 14 && (ret_value & BIT(0))) + rtl92d_phy_switch_wirelessband(hw, BAND_ON_2_4G); + } + switch (rtlhal->current_bandtype) { + case BAND_ON_5G: + /* Get first channel error when change between + * 5G and 2.4G band. */ + if (channel <= 14) + return 0; + RT_ASSERT((channel > 14), "5G but channel<=14\n"); + break; + case BAND_ON_2_4G: + /* Get first channel error when change between + * 5G and 2.4G band. */ + if (channel > 14) + return 0; + RT_ASSERT((channel <= 14), "2G but channel>14\n"); + break; + default: + RT_ASSERT(false, "Invalid WirelessMode(%#x)!!\n", + rtlpriv->mac80211.mode); + break; + } + rtlphy->sw_chnl_inprogress = true; + if (channel == 0) + channel = 1; + rtlphy->sw_chnl_stage = 0; + rtlphy->sw_chnl_step = 0; + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, + "switch to channel%d\n", rtlphy->current_channel); + + do { + if (!rtlphy->sw_chnl_inprogress) + break; + if (!_rtl92d_phy_sw_chnl_step_by_step(hw, + rtlphy->current_channel, + &rtlphy->sw_chnl_stage, &rtlphy->sw_chnl_step, &delay)) { + if (delay > 0) + mdelay(delay); + else + continue; + } else { + rtlphy->sw_chnl_inprogress = false; + } + break; + } while (true); + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, "<==\n"); + rtlphy->sw_chnl_inprogress = false; + return 1; +} + +static void rtl92d_phy_set_io(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *de_digtable = &rtlpriv->dm_digtable; + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "--->Cmd(%#x), set_io_inprogress(%d)\n", + rtlphy->current_io_type, rtlphy->set_io_inprogress); + switch (rtlphy->current_io_type) { + case IO_CMD_RESUME_DM_BY_SCAN: + de_digtable->cur_igvalue = rtlphy->initgain_backup.xaagccore1; + rtl92d_dm_write_dig(hw); + rtl92d_phy_set_txpower_level(hw, rtlphy->current_channel); + break; + case IO_CMD_PAUSE_DM_BY_SCAN: + rtlphy->initgain_backup.xaagccore1 = de_digtable->cur_igvalue; + de_digtable->cur_igvalue = 0x37; + rtl92d_dm_write_dig(hw); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + rtlphy->set_io_inprogress = false; + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, "<---(%#x)\n", + rtlphy->current_io_type); +} + +bool rtl92d_phy_set_io_cmd(struct ieee80211_hw *hw, enum io_type iotype) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + bool postprocessing = false; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "-->IO Cmd(%#x), set_io_inprogress(%d)\n", + iotype, rtlphy->set_io_inprogress); + do { + switch (iotype) { + case IO_CMD_RESUME_DM_BY_SCAN: + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "[IO CMD] Resume DM after scan\n"); + postprocessing = true; + break; + case IO_CMD_PAUSE_DM_BY_SCAN: + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "[IO CMD] Pause DM before scan\n"); + postprocessing = true; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + } while (false); + if (postprocessing && !rtlphy->set_io_inprogress) { + rtlphy->set_io_inprogress = true; + rtlphy->current_io_type = iotype; + } else { + return false; + } + rtl92d_phy_set_io(hw); + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, "<--IO Type(%#x)\n", iotype); + return true; +} + +static void _rtl92d_phy_set_rfon(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + /* a. SYS_CLKR 0x08[11] = 1 restore MAC clock */ + /* b. SPS_CTRL 0x11[7:0] = 0x2b */ + if (rtlpriv->rtlhal.macphymode == SINGLEMAC_SINGLEPHY) + rtl_write_byte(rtlpriv, REG_SPS0_CTRL, 0x2b); + /* c. For PCIE: SYS_FUNC_EN 0x02[7:0] = 0xE3 enable BB TRX function */ + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE3); + /* RF_ON_EXCEP(d~g): */ + /* d. APSD_CTRL 0x600[7:0] = 0x00 */ + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x00); + /* e. SYS_FUNC_EN 0x02[7:0] = 0xE2 reset BB TRX function again */ + /* f. SYS_FUNC_EN 0x02[7:0] = 0xE3 enable BB TRX function*/ + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE2); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE3); + /* g. txpause 0x522[7:0] = 0x00 enable mac tx queue */ + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0x00); +} + +static void _rtl92d_phy_set_rfsleep(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 u4btmp; + u8 delay = 5; + + /* a. TXPAUSE 0x522[7:0] = 0xFF Pause MAC TX queue */ + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0xFF); + /* b. RF path 0 offset 0x00 = 0x00 disable RF */ + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, BRFREGOFFSETMASK, 0x00); + /* c. APSD_CTRL 0x600[7:0] = 0x40 */ + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x40); + /* d. APSD_CTRL 0x600[7:0] = 0x00 + * APSD_CTRL 0x600[7:0] = 0x00 + * RF path 0 offset 0x00 = 0x00 + * APSD_CTRL 0x600[7:0] = 0x40 + * */ + u4btmp = rtl_get_rfreg(hw, RF90_PATH_A, 0, BRFREGOFFSETMASK); + while (u4btmp != 0 && delay > 0) { + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x0); + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, BRFREGOFFSETMASK, 0x00); + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x40); + u4btmp = rtl_get_rfreg(hw, RF90_PATH_A, 0, BRFREGOFFSETMASK); + delay--; + } + if (delay == 0) { + /* Jump out the LPS turn off sequence to RF_ON_EXCEP */ + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x00); + + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE2); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE3); + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0x00); + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "Fail !!! Switch RF timeout\n"); + return; + } + /* e. For PCIE: SYS_FUNC_EN 0x02[7:0] = 0xE2 reset BB TRX function */ + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE2); + /* f. SPS_CTRL 0x11[7:0] = 0x22 */ + if (rtlpriv->rtlhal.macphymode == SINGLEMAC_SINGLEPHY) + rtl_write_byte(rtlpriv, REG_SPS0_CTRL, 0x22); + /* g. SYS_CLKR 0x08[11] = 0 gated MAC clock */ +} + +bool rtl92d_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state) +{ + + bool bresult = true; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u8 i, queue_id; + struct rtl8192_tx_ring *ring = NULL; + + if (rfpwr_state == ppsc->rfpwr_state) + return false; + switch (rfpwr_state) { + case ERFON: + if ((ppsc->rfpwr_state == ERFOFF) && + RT_IN_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC)) { + bool rtstatus; + u32 InitializeCount = 0; + do { + InitializeCount++; + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "IPS Set eRf nic enable\n"); + rtstatus = rtl_ps_enable_nic(hw); + } while (!rtstatus && (InitializeCount < 10)); + + RT_CLEAR_PS_LEVEL(ppsc, + RT_RF_OFF_LEVL_HALT_NIC); + } else { + RT_TRACE(rtlpriv, COMP_POWER, DBG_DMESG, + "awake, sleeped:%d ms state_inap:%x\n", + jiffies_to_msecs(jiffies - + ppsc->last_sleep_jiffies), + rtlpriv->psc.state_inap); + ppsc->last_awake_jiffies = jiffies; + _rtl92d_phy_set_rfon(hw); + } + + if (mac->link_state == MAC80211_LINKED) + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_LINK); + else + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_NO_LINK); + break; + case ERFOFF: + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_HALT_NIC) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "IPS Set eRf nic disable\n"); + rtl_ps_disable_nic(hw); + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + } else { + if (ppsc->rfoff_reason == RF_CHANGE_BY_IPS) + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_NO_LINK); + else + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_POWER_OFF); + } + break; + case ERFSLEEP: + if (ppsc->rfpwr_state == ERFOFF) + return false; + + for (queue_id = 0, i = 0; + queue_id < RTL_PCI_MAX_TX_QUEUE_COUNT;) { + ring = &pcipriv->dev.tx_ring[queue_id]; + if (skb_queue_len(&ring->queue) == 0 || + queue_id == BEACON_QUEUE) { + queue_id++; + continue; + } else if (rtlpci->pdev->current_state != PCI_D0) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "eRf Off/Sleep: %d times TcbBusyQueue[%d] !=0 but lower power state!\n", + i + 1, queue_id); + break; + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "eRf Off/Sleep: %d times TcbBusyQueue[%d] =%d before doze!\n", + i + 1, queue_id, + skb_queue_len(&ring->queue)); + udelay(10); + i++; + } + + if (i >= MAX_DOZE_WAITING_TIMES_9x) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "ERFOFF: %d times TcbBusyQueue[%d] = %d !\n", + MAX_DOZE_WAITING_TIMES_9x, queue_id, + skb_queue_len(&ring->queue)); + break; + } + } + RT_TRACE(rtlpriv, COMP_POWER, DBG_DMESG, + "Set rfsleep awaked:%d ms\n", + jiffies_to_msecs(jiffies - ppsc->last_awake_jiffies)); + RT_TRACE(rtlpriv, COMP_POWER, DBG_DMESG, + "sleep awaked:%d ms state_inap:%x\n", + jiffies_to_msecs(jiffies - + ppsc->last_awake_jiffies), + rtlpriv->psc.state_inap); + ppsc->last_sleep_jiffies = jiffies; + _rtl92d_phy_set_rfsleep(hw); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + bresult = false; + break; + } + if (bresult) + ppsc->rfpwr_state = rfpwr_state; + return bresult; +} + +void rtl92d_phy_config_macphymode(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 offset = REG_MAC_PHY_CTRL_NORMAL; + + switch (rtlhal->macphymode) { + case DUALMAC_DUALPHY: + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "MacPhyMode: DUALMAC_DUALPHY\n"); + rtl_write_byte(rtlpriv, offset, 0xF3); + break; + case SINGLEMAC_SINGLEPHY: + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "MacPhyMode: SINGLEMAC_SINGLEPHY\n"); + rtl_write_byte(rtlpriv, offset, 0xF4); + break; + case DUALMAC_SINGLEPHY: + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "MacPhyMode: DUALMAC_SINGLEPHY\n"); + rtl_write_byte(rtlpriv, offset, 0xF1); + break; + } +} + +void rtl92d_phy_config_macphymode_info(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + switch (rtlhal->macphymode) { + case DUALMAC_SINGLEPHY: + rtlphy->rf_type = RF_2T2R; + rtlhal->version |= RF_TYPE_2T2R; + rtlhal->bandset = BAND_ON_BOTH; + rtlhal->current_bandtype = BAND_ON_2_4G; + break; + + case SINGLEMAC_SINGLEPHY: + rtlphy->rf_type = RF_2T2R; + rtlhal->version |= RF_TYPE_2T2R; + rtlhal->bandset = BAND_ON_BOTH; + rtlhal->current_bandtype = BAND_ON_2_4G; + break; + + case DUALMAC_DUALPHY: + rtlphy->rf_type = RF_1T1R; + rtlhal->version &= RF_TYPE_1T1R; + /* Now we let MAC0 run on 5G band. */ + if (rtlhal->interfaceindex == 0) { + rtlhal->bandset = BAND_ON_5G; + rtlhal->current_bandtype = BAND_ON_5G; + } else { + rtlhal->bandset = BAND_ON_2_4G; + rtlhal->current_bandtype = BAND_ON_2_4G; + } + break; + default: + break; + } +} + +u8 rtl92d_get_chnlgroup_fromarray(u8 chnl) +{ + u8 group; + u8 channel_info[59] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, + 58, 60, 62, 64, 100, 102, 104, 106, 108, + 110, 112, 114, 116, 118, 120, 122, 124, + 126, 128, 130, 132, 134, 136, 138, 140, + 149, 151, 153, 155, 157, 159, 161, 163, + 165 + }; + + if (channel_info[chnl] <= 3) + group = 0; + else if (channel_info[chnl] <= 9) + group = 1; + else if (channel_info[chnl] <= 14) + group = 2; + else if (channel_info[chnl] <= 44) + group = 3; + else if (channel_info[chnl] <= 54) + group = 4; + else if (channel_info[chnl] <= 64) + group = 5; + else if (channel_info[chnl] <= 112) + group = 6; + else if (channel_info[chnl] <= 126) + group = 7; + else if (channel_info[chnl] <= 140) + group = 8; + else if (channel_info[chnl] <= 153) + group = 9; + else if (channel_info[chnl] <= 159) + group = 10; + else + group = 11; + return group; +} + +void rtl92d_phy_set_poweron(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + unsigned long flags; + u8 value8; + u16 i; + u32 mac_reg = (rtlhal->interfaceindex == 0 ? REG_MAC0 : REG_MAC1); + + /* notice fw know band status 0x81[1]/0x53[1] = 0: 5G, 1: 2G */ + if (rtlhal->current_bandtype == BAND_ON_2_4G) { + value8 = rtl_read_byte(rtlpriv, mac_reg); + value8 |= BIT(1); + rtl_write_byte(rtlpriv, mac_reg, value8); + } else { + value8 = rtl_read_byte(rtlpriv, mac_reg); + value8 &= (~BIT(1)); + rtl_write_byte(rtlpriv, mac_reg, value8); + } + + if (rtlhal->macphymode == SINGLEMAC_SINGLEPHY) { + value8 = rtl_read_byte(rtlpriv, REG_MAC0); + rtl_write_byte(rtlpriv, REG_MAC0, value8 | MAC0_ON); + } else { + spin_lock_irqsave(&globalmutex_power, flags); + if (rtlhal->interfaceindex == 0) { + value8 = rtl_read_byte(rtlpriv, REG_MAC0); + rtl_write_byte(rtlpriv, REG_MAC0, value8 | MAC0_ON); + } else { + value8 = rtl_read_byte(rtlpriv, REG_MAC1); + rtl_write_byte(rtlpriv, REG_MAC1, value8 | MAC1_ON); + } + value8 = rtl_read_byte(rtlpriv, REG_POWER_OFF_IN_PROCESS); + spin_unlock_irqrestore(&globalmutex_power, flags); + for (i = 0; i < 200; i++) { + if ((value8 & BIT(7)) == 0) { + break; + } else { + udelay(500); + spin_lock_irqsave(&globalmutex_power, flags); + value8 = rtl_read_byte(rtlpriv, + REG_POWER_OFF_IN_PROCESS); + spin_unlock_irqrestore(&globalmutex_power, + flags); + } + } + if (i == 200) + RT_ASSERT(false, "Another mac power off over time\n"); + } +} + +void rtl92d_phy_config_maccoexist_rfpage(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + switch (rtlpriv->rtlhal.macphymode) { + case DUALMAC_DUALPHY: + rtl_write_byte(rtlpriv, REG_DMC, 0x0); + rtl_write_byte(rtlpriv, REG_RX_PKT_LIMIT, 0x08); + rtl_write_word(rtlpriv, REG_TRXFF_BNDY + 2, 0x13ff); + break; + case DUALMAC_SINGLEPHY: + rtl_write_byte(rtlpriv, REG_DMC, 0xf8); + rtl_write_byte(rtlpriv, REG_RX_PKT_LIMIT, 0x08); + rtl_write_word(rtlpriv, REG_TRXFF_BNDY + 2, 0x13ff); + break; + case SINGLEMAC_SINGLEPHY: + rtl_write_byte(rtlpriv, REG_DMC, 0x0); + rtl_write_byte(rtlpriv, REG_RX_PKT_LIMIT, 0x10); + rtl_write_word(rtlpriv, (REG_TRXFF_BNDY + 2), 0x27FF); + break; + default: + break; + } +} + +void rtl92d_update_bbrf_configuration(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 rfpath, i; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "==>\n"); + /* r_select_5G for path_A/B 0 for 2.4G, 1 for 5G */ + if (rtlhal->current_bandtype == BAND_ON_2_4G) { + /* r_select_5G for path_A/B,0x878 */ + rtl_set_bbreg(hw, RFPGA0_XAB_RFPARAMETER, BIT(0), 0x0); + rtl_set_bbreg(hw, RFPGA0_XAB_RFPARAMETER, BIT(15), 0x0); + if (rtlhal->macphymode != DUALMAC_DUALPHY) { + rtl_set_bbreg(hw, RFPGA0_XAB_RFPARAMETER, BIT(16), 0x0); + rtl_set_bbreg(hw, RFPGA0_XAB_RFPARAMETER, BIT(31), 0x0); + } + /* rssi_table_select:index 0 for 2.4G.1~3 for 5G,0xc78 */ + rtl_set_bbreg(hw, ROFDM0_AGCRSSITABLE, BIT(6) | BIT(7), 0x0); + /* fc_area 0xd2c */ + rtl_set_bbreg(hw, ROFDM1_CFOTRACKING, BIT(14) | BIT(13), 0x0); + /* 5G LAN ON */ + rtl_set_bbreg(hw, 0xB30, 0x00F00000, 0xa); + /* TX BB gain shift*1,Just for testchip,0xc80,0xc88 */ + rtl_set_bbreg(hw, ROFDM0_XATxIQIMBALANCE, BMASKDWORD, + 0x40000100); + rtl_set_bbreg(hw, ROFDM0_XBTxIQIMBALANCE, BMASKDWORD, + 0x40000100); + if (rtlhal->macphymode == DUALMAC_DUALPHY) { + rtl_set_bbreg(hw, RFPGA0_XAB_RFINTERFACESW, + BIT(10) | BIT(6) | BIT(5), + ((rtlefuse->eeprom_c9 & BIT(3)) >> 3) | + (rtlefuse->eeprom_c9 & BIT(1)) | + ((rtlefuse->eeprom_cc & BIT(1)) << 4)); + rtl_set_bbreg(hw, RFPGA0_XA_RFINTERFACEOE, + BIT(10) | BIT(6) | BIT(5), + ((rtlefuse->eeprom_c9 & BIT(2)) >> 2) | + ((rtlefuse->eeprom_c9 & BIT(0)) << 1) | + ((rtlefuse->eeprom_cc & BIT(0)) << 5)); + rtl_set_bbreg(hw, RFPGA0_XAB_RFPARAMETER, BIT(15), 0); + } else { + rtl_set_bbreg(hw, RFPGA0_XAB_RFINTERFACESW, + BIT(26) | BIT(22) | BIT(21) | BIT(10) | + BIT(6) | BIT(5), + ((rtlefuse->eeprom_c9 & BIT(3)) >> 3) | + (rtlefuse->eeprom_c9 & BIT(1)) | + ((rtlefuse->eeprom_cc & BIT(1)) << 4) | + ((rtlefuse->eeprom_c9 & BIT(7)) << 9) | + ((rtlefuse->eeprom_c9 & BIT(5)) << 12) | + ((rtlefuse->eeprom_cc & BIT(3)) << 18)); + rtl_set_bbreg(hw, RFPGA0_XA_RFINTERFACEOE, + BIT(10) | BIT(6) | BIT(5), + ((rtlefuse->eeprom_c9 & BIT(2)) >> 2) | + ((rtlefuse->eeprom_c9 & BIT(0)) << 1) | + ((rtlefuse->eeprom_cc & BIT(0)) << 5)); + rtl_set_bbreg(hw, RFPGA0_XB_RFINTERFACEOE, + BIT(10) | BIT(6) | BIT(5), + ((rtlefuse->eeprom_c9 & BIT(6)) >> 6) | + ((rtlefuse->eeprom_c9 & BIT(4)) >> 3) | + ((rtlefuse->eeprom_cc & BIT(2)) << 3)); + rtl_set_bbreg(hw, RFPGA0_XAB_RFPARAMETER, + BIT(31) | BIT(15), 0); + } + /* 1.5V_LDO */ + } else { + /* r_select_5G for path_A/B */ + rtl_set_bbreg(hw, RFPGA0_XAB_RFPARAMETER, BIT(0), 0x1); + rtl_set_bbreg(hw, RFPGA0_XAB_RFPARAMETER, BIT(15), 0x1); + if (rtlhal->macphymode != DUALMAC_DUALPHY) { + rtl_set_bbreg(hw, RFPGA0_XAB_RFPARAMETER, BIT(16), 0x1); + rtl_set_bbreg(hw, RFPGA0_XAB_RFPARAMETER, BIT(31), 0x1); + } + /* rssi_table_select:index 0 for 2.4G.1~3 for 5G */ + rtl_set_bbreg(hw, ROFDM0_AGCRSSITABLE, BIT(6) | BIT(7), 0x1); + /* fc_area */ + rtl_set_bbreg(hw, ROFDM1_CFOTRACKING, BIT(14) | BIT(13), 0x1); + /* 5G LAN ON */ + rtl_set_bbreg(hw, 0xB30, 0x00F00000, 0x0); + /* TX BB gain shift,Just for testchip,0xc80,0xc88 */ + if (rtlefuse->internal_pa_5g[0]) + rtl_set_bbreg(hw, ROFDM0_XATxIQIMBALANCE, BMASKDWORD, + 0x2d4000b5); + else + rtl_set_bbreg(hw, ROFDM0_XATxIQIMBALANCE, BMASKDWORD, + 0x20000080); + if (rtlefuse->internal_pa_5g[1]) + rtl_set_bbreg(hw, ROFDM0_XBTxIQIMBALANCE, BMASKDWORD, + 0x2d4000b5); + else + rtl_set_bbreg(hw, ROFDM0_XBTxIQIMBALANCE, BMASKDWORD, + 0x20000080); + if (rtlhal->macphymode == DUALMAC_DUALPHY) { + rtl_set_bbreg(hw, RFPGA0_XAB_RFINTERFACESW, + BIT(10) | BIT(6) | BIT(5), + (rtlefuse->eeprom_cc & BIT(5))); + rtl_set_bbreg(hw, RFPGA0_XA_RFINTERFACEOE, BIT(10), + ((rtlefuse->eeprom_cc & BIT(4)) >> 4)); + rtl_set_bbreg(hw, RFPGA0_XAB_RFPARAMETER, BIT(15), + (rtlefuse->eeprom_cc & BIT(4)) >> 4); + } else { + rtl_set_bbreg(hw, RFPGA0_XAB_RFINTERFACESW, + BIT(26) | BIT(22) | BIT(21) | BIT(10) | + BIT(6) | BIT(5), + (rtlefuse->eeprom_cc & BIT(5)) | + ((rtlefuse->eeprom_cc & BIT(7)) << 14)); + rtl_set_bbreg(hw, RFPGA0_XA_RFINTERFACEOE, BIT(10), + ((rtlefuse->eeprom_cc & BIT(4)) >> 4)); + rtl_set_bbreg(hw, RFPGA0_XB_RFINTERFACEOE, BIT(10), + ((rtlefuse->eeprom_cc & BIT(6)) >> 6)); + rtl_set_bbreg(hw, RFPGA0_XAB_RFPARAMETER, + BIT(31) | BIT(15), + ((rtlefuse->eeprom_cc & BIT(4)) >> 4) | + ((rtlefuse->eeprom_cc & BIT(6)) << 10)); + } + } + /* update IQK related settings */ + rtl_set_bbreg(hw, ROFDM0_XARXIQIMBALANCE, BMASKDWORD, 0x40000100); + rtl_set_bbreg(hw, ROFDM0_XBRXIQIMBALANCE, BMASKDWORD, 0x40000100); + rtl_set_bbreg(hw, ROFDM0_XCTxAFE, 0xF0000000, 0x00); + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, BIT(30) | BIT(28) | + BIT(26) | BIT(24), 0x00); + rtl_set_bbreg(hw, ROFDM0_XDTxAFE, 0xF0000000, 0x00); + rtl_set_bbreg(hw, 0xca0, 0xF0000000, 0x00); + rtl_set_bbreg(hw, ROFDM0_AGCRSSITABLE, 0x0000F000, 0x00); + + /* Update RF */ + for (rfpath = RF90_PATH_A; rfpath < rtlphy->num_total_rfpath; + rfpath++) { + if (rtlhal->current_bandtype == BAND_ON_2_4G) { + /* MOD_AG for RF paht_A 0x18 BIT8,BIT16 */ + rtl_set_rfreg(hw, rfpath, RF_CHNLBW, BIT(8) | BIT(16) | + BIT(18), 0); + /* RF0x0b[16:14] =3b'111 */ + rtl_set_rfreg(hw, (enum radio_path)rfpath, 0x0B, + 0x1c000, 0x07); + } else { + /* MOD_AG for RF paht_A 0x18 BIT8,BIT16 */ + rtl_set_rfreg(hw, rfpath, RF_CHNLBW, BIT(8) | + BIT(16) | BIT(18), + (BIT(16) | BIT(8)) >> 8); + } + } + /* Update for all band. */ + /* DMDP */ + if (rtlphy->rf_type == RF_1T1R) { + /* Use antenna 0,0xc04,0xd04 */ + rtl_set_bbreg(hw, ROFDM0_TRXPATHENABLE, BMASKBYTE0, 0x11); + rtl_set_bbreg(hw, ROFDM1_TRXPATHENABLE, BDWORD, 0x1); + + /* enable ad/da clock1 for dual-phy reg0x888 */ + if (rtlhal->interfaceindex == 0) { + rtl_set_bbreg(hw, RFPGA0_ADDALLOCKEN, BIT(12) | + BIT(13), 0x3); + } else { + rtl92d_phy_enable_anotherphy(hw, false); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "MAC1 use DBI to update 0x888\n"); + /* 0x888 */ + rtl92de_write_dword_dbi(hw, RFPGA0_ADDALLOCKEN, + rtl92de_read_dword_dbi(hw, + RFPGA0_ADDALLOCKEN, + BIT(3)) | BIT(12) | BIT(13), + BIT(3)); + rtl92d_phy_powerdown_anotherphy(hw, false); + } + } else { + /* Single PHY */ + /* Use antenna 0 & 1,0xc04,0xd04 */ + rtl_set_bbreg(hw, ROFDM0_TRXPATHENABLE, BMASKBYTE0, 0x33); + rtl_set_bbreg(hw, ROFDM1_TRXPATHENABLE, BDWORD, 0x3); + /* disable ad/da clock1,0x888 */ + rtl_set_bbreg(hw, RFPGA0_ADDALLOCKEN, BIT(12) | BIT(13), 0); + } + for (rfpath = RF90_PATH_A; rfpath < rtlphy->num_total_rfpath; + rfpath++) { + rtlphy->rfreg_chnlval[rfpath] = rtl_get_rfreg(hw, rfpath, + RF_CHNLBW, BRFREGOFFSETMASK); + rtlphy->reg_rf3c[rfpath] = rtl_get_rfreg(hw, rfpath, 0x3C, + BRFREGOFFSETMASK); + } + for (i = 0; i < 2; i++) + RT_TRACE(rtlpriv, COMP_RF, DBG_LOUD, "RF 0x18 = 0x%x\n", + rtlphy->rfreg_chnlval[i]); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "<==\n"); + +} + +bool rtl92d_phy_check_poweroff(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 u1btmp; + unsigned long flags; + + if (rtlhal->macphymode == SINGLEMAC_SINGLEPHY) { + u1btmp = rtl_read_byte(rtlpriv, REG_MAC0); + rtl_write_byte(rtlpriv, REG_MAC0, u1btmp & (~MAC0_ON)); + return true; + } + spin_lock_irqsave(&globalmutex_power, flags); + if (rtlhal->interfaceindex == 0) { + u1btmp = rtl_read_byte(rtlpriv, REG_MAC0); + rtl_write_byte(rtlpriv, REG_MAC0, u1btmp & (~MAC0_ON)); + u1btmp = rtl_read_byte(rtlpriv, REG_MAC1); + u1btmp &= MAC1_ON; + } else { + u1btmp = rtl_read_byte(rtlpriv, REG_MAC1); + rtl_write_byte(rtlpriv, REG_MAC1, u1btmp & (~MAC1_ON)); + u1btmp = rtl_read_byte(rtlpriv, REG_MAC0); + u1btmp &= MAC0_ON; + } + if (u1btmp) { + spin_unlock_irqrestore(&globalmutex_power, flags); + return false; + } + u1btmp = rtl_read_byte(rtlpriv, REG_POWER_OFF_IN_PROCESS); + u1btmp |= BIT(7); + rtl_write_byte(rtlpriv, REG_POWER_OFF_IN_PROCESS, u1btmp); + spin_unlock_irqrestore(&globalmutex_power, flags); + return true; +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/phy.h b/drivers/net/wireless/rtlwifi/rtl8192de/phy.h new file mode 100644 index 0000000..f074952 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/phy.h @@ -0,0 +1,178 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92D_PHY_H__ +#define __RTL92D_PHY_H__ + +#define MAX_PRECMD_CNT 16 +#define MAX_RFDEPENDCMD_CNT 16 +#define MAX_POSTCMD_CNT 16 + +#define MAX_DOZE_WAITING_TIMES_9x 64 + +#define RT_CANNOT_IO(hw) false +#define HIGHPOWER_RADIOA_ARRAYLEN 22 + +#define IQK_ADDA_REG_NUM 16 +#define MAX_TOLERANCE 5 +#define IQK_DELAY_TIME 1 + +#define APK_BB_REG_NUM 5 +#define APK_AFE_REG_NUM 16 +#define APK_CURVE_REG_NUM 4 +#define PATH_NUM 2 + +#define LOOP_LIMIT 5 +#define MAX_STALL_TIME 50 +#define ANTENNA_DIVERSITY_VALUE 0x80 +#define MAX_TXPWR_IDX_NMODE_92S 63 +#define RESET_CNT_LIMIT 3 + +#define IQK_ADDA_REG_NUM 16 +#define IQK_BB_REG_NUM 10 +#define IQK_BB_REG_NUM_test 6 +#define IQK_MAC_REG_NUM 4 +#define RX_INDEX_MAPPING_NUM 15 + +#define IQK_DELAY_TIME 1 + +#define CT_OFFSET_MAC_ADDR 0X16 + +#define CT_OFFSET_CCK_TX_PWR_IDX 0x5A +#define CT_OFFSET_HT401S_TX_PWR_IDX 0x60 +#define CT_OFFSET_HT402S_TX_PWR_IDX_DIFF 0x66 +#define CT_OFFSET_HT20_TX_PWR_IDX_DIFF 0x69 +#define CT_OFFSET_OFDM_TX_PWR_IDX_DIFF 0x6C + +#define CT_OFFSET_HT40_MAX_PWR_OFFSET 0x6F +#define CT_OFFSET_HT20_MAX_PWR_OFFSET 0x72 + +#define CT_OFFSET_CHANNEL_PLAH 0x75 +#define CT_OFFSET_THERMAL_METER 0x78 +#define CT_OFFSET_RF_OPTION 0x79 +#define CT_OFFSET_VERSION 0x7E +#define CT_OFFSET_CUSTOMER_ID 0x7F + +enum swchnlcmd_id { + CMDID_END, + CMDID_SET_TXPOWEROWER_LEVEL, + CMDID_BBREGWRITE10, + CMDID_WRITEPORT_ULONG, + CMDID_WRITEPORT_USHORT, + CMDID_WRITEPORT_UCHAR, + CMDID_RF_WRITEREG, +}; + +struct swchnlcmd { + enum swchnlcmd_id cmdid; + u32 para1; + u32 para2; + u32 msdelay; +}; + +enum baseband_config_type { + BASEBAND_CONFIG_PHY_REG = 0, + BASEBAND_CONFIG_AGC_TAB = 1, +}; + +enum rf_content { + radioa_txt = 0, + radiob_txt = 1, + radioc_txt = 2, + radiod_txt = 3 +}; + +static inline void rtl92d_acquire_cckandrw_pagea_ctl(struct ieee80211_hw *hw, + unsigned long *flag) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (rtlpriv->rtlhal.interfaceindex == 1) + spin_lock_irqsave(&rtlpriv->locks.cck_and_rw_pagea_lock, *flag); +} + +static inline void rtl92d_release_cckandrw_pagea_ctl(struct ieee80211_hw *hw, + unsigned long *flag) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (rtlpriv->rtlhal.interfaceindex == 1) + spin_unlock_irqrestore(&rtlpriv->locks.cck_and_rw_pagea_lock, + *flag); +} + +extern u32 rtl92d_phy_query_bb_reg(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask); +extern void rtl92d_phy_set_bb_reg(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask, u32 data); +extern u32 rtl92d_phy_query_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, + u32 bitmask); +extern void rtl92d_phy_set_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, + u32 bitmask, u32 data); +extern bool rtl92d_phy_mac_config(struct ieee80211_hw *hw); +extern bool rtl92d_phy_bb_config(struct ieee80211_hw *hw); +extern bool rtl92d_phy_rf_config(struct ieee80211_hw *hw); +extern bool rtl92c_phy_config_rf_with_feaderfile(struct ieee80211_hw *hw, + enum radio_path rfpath); +extern void rtl92d_phy_get_hw_reg_originalvalue(struct ieee80211_hw *hw); +extern void rtl92d_phy_set_txpower_level(struct ieee80211_hw *hw, u8 channel); +extern void rtl92d_phy_scan_operation_backup(struct ieee80211_hw *hw, + u8 operation); +extern void rtl92d_phy_set_bw_mode(struct ieee80211_hw *hw, + enum nl80211_channel_type ch_type); +extern u8 rtl92d_phy_sw_chnl(struct ieee80211_hw *hw); +bool rtl92d_phy_config_rf_with_headerfile(struct ieee80211_hw *hw, + enum rf_content content, + enum radio_path rfpath); +bool rtl92d_phy_set_io_cmd(struct ieee80211_hw *hw, enum io_type iotype); +extern bool rtl92d_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state); + +void rtl92d_phy_config_macphymode(struct ieee80211_hw *hw); +void rtl92d_phy_config_macphymode_info(struct ieee80211_hw *hw); +u8 rtl92d_get_chnlgroup_fromarray(u8 chnl); +void rtl92d_phy_set_poweron(struct ieee80211_hw *hw); +void rtl92d_phy_config_maccoexist_rfpage(struct ieee80211_hw *hw); +bool rtl92d_phy_check_poweroff(struct ieee80211_hw *hw); +void rtl92d_phy_lc_calibrate(struct ieee80211_hw *hw); +void rtl92d_update_bbrf_configuration(struct ieee80211_hw *hw); +void rtl92d_phy_ap_calibrate(struct ieee80211_hw *hw, char delta); +void rtl92d_phy_iq_calibrate(struct ieee80211_hw *hw); +void rtl92d_phy_reset_iqk_result(struct ieee80211_hw *hw); +void rtl92d_release_cckandrw_pagea_ctl(struct ieee80211_hw *hw, + unsigned long *flag); +void rtl92d_acquire_cckandrw_pagea_ctl(struct ieee80211_hw *hw, + unsigned long *flag); +u8 rtl92d_get_rightchnlplace_for_iqk(u8 chnl); +void rtl92d_phy_reload_iqk_setting(struct ieee80211_hw *hw, u8 channel); +void rtl92d_phy_iq_calibrate(struct ieee80211_hw *hw); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/reg.h b/drivers/net/wireless/rtlwifi/rtl8192de/reg.h new file mode 100644 index 0000000..b7498c5 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/reg.h @@ -0,0 +1,1312 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92D_REG_H__ +#define __RTL92D_REG_H__ + +/* ----------------------------------------------------- */ +/* 0x0000h ~ 0x00FFh System Configuration */ +/* ----------------------------------------------------- */ +#define REG_SYS_ISO_CTRL 0x0000 +#define REG_SYS_FUNC_EN 0x0002 +#define REG_APS_FSMCO 0x0004 +#define REG_SYS_CLKR 0x0008 +#define REG_9346CR 0x000A +#define REG_EE_VPD 0x000C +#define REG_AFE_MISC 0x0010 +#define REG_SPS0_CTRL 0x0011 +#define REG_POWER_OFF_IN_PROCESS 0x0017 +#define REG_SPS_OCP_CFG 0x0018 +#define REG_RSV_CTRL 0x001C +#define REG_RF_CTRL 0x001F +#define REG_LDOA15_CTRL 0x0020 +#define REG_LDOV12D_CTRL 0x0021 +#define REG_LDOHCI12_CTRL 0x0022 +#define REG_LPLDO_CTRL 0x0023 +#define REG_AFE_XTAL_CTRL 0x0024 +#define REG_AFE_PLL_CTRL 0x0028 +/* for 92d, DMDP,SMSP,DMSP contrl */ +#define REG_MAC_PHY_CTRL 0x002c +#define REG_EFUSE_CTRL 0x0030 +#define REG_EFUSE_TEST 0x0034 +#define REG_PWR_DATA 0x0038 +#define REG_CAL_TIMER 0x003C +#define REG_ACLK_MON 0x003E +#define REG_GPIO_MUXCFG 0x0040 +#define REG_GPIO_IO_SEL 0x0042 +#define REG_MAC_PINMUX_CFG 0x0043 +#define REG_GPIO_PIN_CTRL 0x0044 +#define REG_GPIO_INTM 0x0048 +#define REG_LEDCFG0 0x004C +#define REG_LEDCFG1 0x004D +#define REG_LEDCFG2 0x004E +#define REG_LEDCFG3 0x004F +#define REG_FSIMR 0x0050 +#define REG_FSISR 0x0054 + +#define REG_MCUFWDL 0x0080 + +#define REG_HMEBOX_EXT_0 0x0088 +#define REG_HMEBOX_EXT_1 0x008A +#define REG_HMEBOX_EXT_2 0x008C +#define REG_HMEBOX_EXT_3 0x008E + +#define REG_BIST_SCAN 0x00D0 +#define REG_BIST_RPT 0x00D4 +#define REG_BIST_ROM_RPT 0x00D8 +#define REG_USB_SIE_INTF 0x00E0 +#define REG_PCIE_MIO_INTF 0x00E4 +#define REG_PCIE_MIO_INTD 0x00E8 +#define REG_HPON_FSM 0x00EC +#define REG_SYS_CFG 0x00F0 +#define REG_MAC_PHY_CTRL_NORMAL 0x00f8 + +#define REG_MAC0 0x0081 +#define REG_MAC1 0x0053 +#define FW_MAC0_READY 0x18 +#define FW_MAC1_READY 0x1A +#define MAC0_ON BIT(7) +#define MAC1_ON BIT(0) +#define MAC0_READY BIT(0) +#define MAC1_READY BIT(0) + +/* ----------------------------------------------------- */ +/* 0x0100h ~ 0x01FFh MACTOP General Configuration */ +/* ----------------------------------------------------- */ +#define REG_CR 0x0100 +#define REG_PBP 0x0104 +#define REG_TRXDMA_CTRL 0x010C +#define REG_TRXFF_BNDY 0x0114 +#define REG_TRXFF_STATUS 0x0118 +#define REG_RXFF_PTR 0x011C +#define REG_HIMR 0x0120 +#define REG_HISR 0x0124 +#define REG_HIMRE 0x0128 +#define REG_HISRE 0x012C +#define REG_CPWM 0x012F +#define REG_FWIMR 0x0130 +#define REG_FWISR 0x0134 +#define REG_PKTBUF_DBG_CTRL 0x0140 +#define REG_PKTBUF_DBG_DATA_L 0x0144 +#define REG_PKTBUF_DBG_DATA_H 0x0148 + +#define REG_TC0_CTRL 0x0150 +#define REG_TC1_CTRL 0x0154 +#define REG_TC2_CTRL 0x0158 +#define REG_TC3_CTRL 0x015C +#define REG_TC4_CTRL 0x0160 +#define REG_TCUNIT_BASE 0x0164 +#define REG_MBIST_START 0x0174 +#define REG_MBIST_DONE 0x0178 +#define REG_MBIST_FAIL 0x017C +#define REG_C2HEVT_MSG_NORMAL 0x01A0 +#define REG_C2HEVT_MSG_TEST 0x01B8 +#define REG_C2HEVT_CLEAR 0x01BF +#define REG_MCUTST_1 0x01c0 +#define REG_FMETHR 0x01C8 +#define REG_HMETFR 0x01CC +#define REG_HMEBOX_0 0x01D0 +#define REG_HMEBOX_1 0x01D4 +#define REG_HMEBOX_2 0x01D8 +#define REG_HMEBOX_3 0x01DC + +#define REG_LLT_INIT 0x01E0 +#define REG_BB_ACCEESS_CTRL 0x01E8 +#define REG_BB_ACCESS_DATA 0x01EC + + +/* ----------------------------------------------------- */ +/* 0x0200h ~ 0x027Fh TXDMA Configuration */ +/* ----------------------------------------------------- */ +#define REG_RQPN 0x0200 +#define REG_FIFOPAGE 0x0204 +#define REG_TDECTRL 0x0208 +#define REG_TXDMA_OFFSET_CHK 0x020C +#define REG_TXDMA_STATUS 0x0210 +#define REG_RQPN_NPQ 0x0214 + +/* ----------------------------------------------------- */ +/* 0x0280h ~ 0x02FFh RXDMA Configuration */ +/* ----------------------------------------------------- */ +#define REG_RXDMA_AGG_PG_TH 0x0280 +#define REG_RXPKT_NUM 0x0284 +#define REG_RXDMA_STATUS 0x0288 + +/* ----------------------------------------------------- */ +/* 0x0300h ~ 0x03FFh PCIe */ +/* ----------------------------------------------------- */ +#define REG_PCIE_CTRL_REG 0x0300 +#define REG_INT_MIG 0x0304 +#define REG_BCNQ_DESA 0x0308 +#define REG_HQ_DESA 0x0310 +#define REG_MGQ_DESA 0x0318 +#define REG_VOQ_DESA 0x0320 +#define REG_VIQ_DESA 0x0328 +#define REG_BEQ_DESA 0x0330 +#define REG_BKQ_DESA 0x0338 +#define REG_RX_DESA 0x0340 +#define REG_DBI 0x0348 +#define REG_DBI_WDATA 0x0348 +#define REG_DBI_RDATA 0x034C +#define REG_DBI_CTRL 0x0350 +#define REG_DBI_FLAG 0x0352 +#define REG_MDIO 0x0354 +#define REG_DBG_SEL 0x0360 +#define REG_PCIE_HRPWM 0x0361 +#define REG_PCIE_HCPWM 0x0363 +#define REG_UART_CTRL 0x0364 +#define REG_UART_TX_DESA 0x0370 +#define REG_UART_RX_DESA 0x0378 + +/* ----------------------------------------------------- */ +/* 0x0400h ~ 0x047Fh Protocol Configuration */ +/* ----------------------------------------------------- */ +#define REG_VOQ_INFORMATION 0x0400 +#define REG_VIQ_INFORMATION 0x0404 +#define REG_BEQ_INFORMATION 0x0408 +#define REG_BKQ_INFORMATION 0x040C +#define REG_MGQ_INFORMATION 0x0410 +#define REG_HGQ_INFORMATION 0x0414 +#define REG_BCNQ_INFORMATION 0x0418 + + +#define REG_CPU_MGQ_INFORMATION 0x041C +#define REG_FWHW_TXQ_CTRL 0x0420 +#define REG_HWSEQ_CTRL 0x0423 +#define REG_TXPKTBUF_BCNQ_BDNY 0x0424 +#define REG_TXPKTBUF_MGQ_BDNY 0x0425 +#define REG_MULTI_BCNQ_EN 0x0426 +#define REG_MULTI_BCNQ_OFFSET 0x0427 +#define REG_SPEC_SIFS 0x0428 +#define REG_RL 0x042A +#define REG_DARFRC 0x0430 +#define REG_RARFRC 0x0438 +#define REG_RRSR 0x0440 +#define REG_ARFR0 0x0444 +#define REG_ARFR1 0x0448 +#define REG_ARFR2 0x044C +#define REG_ARFR3 0x0450 +#define REG_AGGLEN_LMT 0x0458 +#define REG_AMPDU_MIN_SPACE 0x045C +#define REG_TXPKTBUF_WMAC_LBK_BF_HD 0x045D +#define REG_FAST_EDCA_CTRL 0x0460 +#define REG_RD_RESP_PKT_TH 0x0463 +#define REG_INIRTS_RATE_SEL 0x0480 +#define REG_INIDATA_RATE_SEL 0x0484 +#define REG_POWER_STATUS 0x04A4 +#define REG_POWER_STAGE1 0x04B4 +#define REG_POWER_STAGE2 0x04B8 +#define REG_PKT_LIFE_TIME 0x04C0 +#define REG_STBC_SETTING 0x04C4 +#define REG_PROT_MODE_CTRL 0x04C8 +#define REG_MAX_AGGR_NUM 0x04CA +#define REG_RTS_MAX_AGGR_NUM 0x04CB +#define REG_BAR_MODE_CTRL 0x04CC +#define REG_RA_TRY_RATE_AGG_LMT 0x04CF +#define REG_EARLY_MODE_CONTROL 0x4D0 +#define REG_NQOS_SEQ 0x04DC +#define REG_QOS_SEQ 0x04DE +#define REG_NEED_CPU_HANDLE 0x04E0 +#define REG_PKT_LOSE_RPT 0x04E1 +#define REG_PTCL_ERR_STATUS 0x04E2 +#define REG_DUMMY 0x04FC + +/* ----------------------------------------------------- */ +/* 0x0500h ~ 0x05FFh EDCA Configuration */ +/* ----------------------------------------------------- */ +#define REG_EDCA_VO_PARAM 0x0500 +#define REG_EDCA_VI_PARAM 0x0504 +#define REG_EDCA_BE_PARAM 0x0508 +#define REG_EDCA_BK_PARAM 0x050C +#define REG_BCNTCFG 0x0510 +#define REG_PIFS 0x0512 +#define REG_RDG_PIFS 0x0513 +#define REG_SIFS_CTX 0x0514 +#define REG_SIFS_TRX 0x0516 +#define REG_AGGR_BREAK_TIME 0x051A +#define REG_SLOT 0x051B +#define REG_TX_PTCL_CTRL 0x0520 +#define REG_TXPAUSE 0x0522 +#define REG_DIS_TXREQ_CLR 0x0523 +#define REG_RD_CTRL 0x0524 +#define REG_TBTT_PROHIBIT 0x0540 +#define REG_RD_NAV_NXT 0x0544 +#define REG_NAV_PROT_LEN 0x0546 +#define REG_BCN_CTRL 0x0550 +#define REG_USTIME_TSF 0x0551 +#define REG_MBID_NUM 0x0552 +#define REG_DUAL_TSF_RST 0x0553 +#define REG_BCN_INTERVAL 0x0554 +#define REG_MBSSID_BCN_SPACE 0x0554 +#define REG_DRVERLYINT 0x0558 +#define REG_BCNDMATIM 0x0559 +#define REG_ATIMWND 0x055A +#define REG_BCN_MAX_ERR 0x055D +#define REG_RXTSF_OFFSET_CCK 0x055E +#define REG_RXTSF_OFFSET_OFDM 0x055F +#define REG_TSFTR 0x0560 +#define REG_INIT_TSFTR 0x0564 +#define REG_PSTIMER 0x0580 +#define REG_TIMER0 0x0584 +#define REG_TIMER1 0x0588 +#define REG_ACMHWCTRL 0x05C0 +#define REG_ACMRSTCTRL 0x05C1 +#define REG_ACMAVG 0x05C2 +#define REG_VO_ADMTIME 0x05C4 +#define REG_VI_ADMTIME 0x05C6 +#define REG_BE_ADMTIME 0x05C8 +#define REG_EDCA_RANDOM_GEN 0x05CC +#define REG_SCH_TXCMD 0x05D0 + +/* Dual MAC Co-Existence Register */ +#define REG_DMC 0x05F0 + +/* ----------------------------------------------------- */ +/* 0x0600h ~ 0x07FFh WMAC Configuration */ +/* ----------------------------------------------------- */ +#define REG_APSD_CTRL 0x0600 +#define REG_BWOPMODE 0x0603 +#define REG_TCR 0x0604 +#define REG_RCR 0x0608 +#define REG_RX_PKT_LIMIT 0x060C +#define REG_RX_DLK_TIME 0x060D +#define REG_RX_DRVINFO_SZ 0x060F + +#define REG_MACID 0x0610 +#define REG_BSSID 0x0618 +#define REG_MAR 0x0620 +#define REG_MBIDCAMCFG 0x0628 + +#define REG_USTIME_EDCA 0x0638 +#define REG_MAC_SPEC_SIFS 0x063A +#define REG_RESP_SIFS_CCK 0x063C +#define REG_RESP_SIFS_OFDM 0x063E +#define REG_ACKTO 0x0640 +#define REG_CTS2TO 0x0641 +#define REG_EIFS 0x0642 + + +/* WMA, BA, CCX */ +#define REG_NAV_CTRL 0x0650 +#define REG_BACAMCMD 0x0654 +#define REG_BACAMCONTENT 0x0658 +#define REG_LBDLY 0x0660 +#define REG_FWDLY 0x0661 +#define REG_RXERR_RPT 0x0664 +#define REG_WMAC_TRXPTCL_CTL 0x0668 + + +/* Security */ +#define REG_CAMCMD 0x0670 +#define REG_CAMWRITE 0x0674 +#define REG_CAMREAD 0x0678 +#define REG_CAMDBG 0x067C +#define REG_SECCFG 0x0680 + +/* Power */ +#define REG_WOW_CTRL 0x0690 +#define REG_PSSTATUS 0x0691 +#define REG_PS_RX_INFO 0x0692 +#define REG_LPNAV_CTRL 0x0694 +#define REG_WKFMCAM_CMD 0x0698 +#define REG_WKFMCAM_RWD 0x069C +#define REG_RXFLTMAP0 0x06A0 +#define REG_RXFLTMAP1 0x06A2 +#define REG_RXFLTMAP2 0x06A4 +#define REG_BCN_PSR_RPT 0x06A8 +#define REG_CALB32K_CTRL 0x06AC +#define REG_PKT_MON_CTRL 0x06B4 +#define REG_BT_COEX_TABLE 0x06C0 +#define REG_WMAC_RESP_TXINFO 0x06D8 + + +/* ----------------------------------------------------- */ +/* Redifine 8192C register definition for compatibility */ +/* ----------------------------------------------------- */ +#define CR9346 REG_9346CR +#define MSR (REG_CR + 2) +#define ISR REG_HISR +#define TSFR REG_TSFTR + +#define MACIDR0 REG_MACID +#define MACIDR4 (REG_MACID + 4) + +#define PBP REG_PBP + +#define IDR0 MACIDR0 +#define IDR4 MACIDR4 + +/* ----------------------------------------------------- */ +/* 8192C (MSR) Media Status Register(Offset 0x4C, 8 bits)*/ +/* ----------------------------------------------------- */ +#define MSR_NOLINK 0x00 +#define MSR_ADHOC 0x01 +#define MSR_INFRA 0x02 +#define MSR_AP 0x03 + +/* 6. Adaptive Control Registers (Offset: 0x0160 - 0x01CF) */ +/* ----------------------------------------------------- */ +/* 8192C Response Rate Set Register(offset 0x181, 24bits)*/ +/* ----------------------------------------------------- */ +#define RRSR_RSC_OFFSET 21 +#define RRSR_SHORT_OFFSET 23 +#define RRSR_RSC_BW_40M 0x600000 +#define RRSR_RSC_UPSUBCHNL 0x400000 +#define RRSR_RSC_LOWSUBCHNL 0x200000 +#define RRSR_SHORT 0x800000 +#define RRSR_1M BIT0 +#define RRSR_2M BIT1 +#define RRSR_5_5M BIT2 +#define RRSR_11M BIT3 +#define RRSR_6M BIT4 +#define RRSR_9M BIT5 +#define RRSR_12M BIT6 +#define RRSR_18M BIT7 +#define RRSR_24M BIT8 +#define RRSR_36M BIT9 +#define RRSR_48M BIT10 +#define RRSR_54M BIT11 +#define RRSR_MCS0 BIT12 +#define RRSR_MCS1 BIT13 +#define RRSR_MCS2 BIT14 +#define RRSR_MCS3 BIT15 +#define RRSR_MCS4 BIT16 +#define RRSR_MCS5 BIT17 +#define RRSR_MCS6 BIT18 +#define RRSR_MCS7 BIT19 +#define BRSR_ACKSHORTPMB BIT23 + +/* ----------------------------------------------------- */ +/* 8192C Rate Definition */ +/* ----------------------------------------------------- */ +/* CCK */ +#define RATR_1M 0x00000001 +#define RATR_2M 0x00000002 +#define RATR_55M 0x00000004 +#define RATR_11M 0x00000008 +/* OFDM */ +#define RATR_6M 0x00000010 +#define RATR_9M 0x00000020 +#define RATR_12M 0x00000040 +#define RATR_18M 0x00000080 +#define RATR_24M 0x00000100 +#define RATR_36M 0x00000200 +#define RATR_48M 0x00000400 +#define RATR_54M 0x00000800 +/* MCS 1 Spatial Stream */ +#define RATR_MCS0 0x00001000 +#define RATR_MCS1 0x00002000 +#define RATR_MCS2 0x00004000 +#define RATR_MCS3 0x00008000 +#define RATR_MCS4 0x00010000 +#define RATR_MCS5 0x00020000 +#define RATR_MCS6 0x00040000 +#define RATR_MCS7 0x00080000 +/* MCS 2 Spatial Stream */ +#define RATR_MCS8 0x00100000 +#define RATR_MCS9 0x00200000 +#define RATR_MCS10 0x00400000 +#define RATR_MCS11 0x00800000 +#define RATR_MCS12 0x01000000 +#define RATR_MCS13 0x02000000 +#define RATR_MCS14 0x04000000 +#define RATR_MCS15 0x08000000 + +/* CCK */ +#define RATE_1M BIT(0) +#define RATE_2M BIT(1) +#define RATE_5_5M BIT(2) +#define RATE_11M BIT(3) +/* OFDM */ +#define RATE_6M BIT(4) +#define RATE_9M BIT(5) +#define RATE_12M BIT(6) +#define RATE_18M BIT(7) +#define RATE_24M BIT(8) +#define RATE_36M BIT(9) +#define RATE_48M BIT(10) +#define RATE_54M BIT(11) +/* MCS 1 Spatial Stream */ +#define RATE_MCS0 BIT(12) +#define RATE_MCS1 BIT(13) +#define RATE_MCS2 BIT(14) +#define RATE_MCS3 BIT(15) +#define RATE_MCS4 BIT(16) +#define RATE_MCS5 BIT(17) +#define RATE_MCS6 BIT(18) +#define RATE_MCS7 BIT(19) +/* MCS 2 Spatial Stream */ +#define RATE_MCS8 BIT(20) +#define RATE_MCS9 BIT(21) +#define RATE_MCS10 BIT(22) +#define RATE_MCS11 BIT(23) +#define RATE_MCS12 BIT(24) +#define RATE_MCS13 BIT(25) +#define RATE_MCS14 BIT(26) +#define RATE_MCS15 BIT(27) + +/* ALL CCK Rate */ +#define RATE_ALL_CCK (RATR_1M | RATR_2M | RATR_55M | \ + RATR_11M) +#define RATE_ALL_OFDM_AG (RATR_6M | RATR_9M | RATR_12M | \ + RATR_18M | RATR_24M | \ + RATR_36M | RATR_48M | RATR_54M) +#define RATE_ALL_OFDM_1SS (RATR_MCS0 | RATR_MCS1 | RATR_MCS2 | \ + RATR_MCS3 | RATR_MCS4 | RATR_MCS5 | \ + RATR_MCS6 | RATR_MCS7) +#define RATE_ALL_OFDM_2SS (RATR_MCS8 | RATR_MCS9 | RATR_MCS10 | \ + RATR_MCS11 | RATR_MCS12 | RATR_MCS13 | \ + RATR_MCS14 | RATR_MCS15) + +/* ----------------------------------------------------- */ +/* 8192C BW_OPMODE bits (Offset 0x203, 8bit) */ +/* ----------------------------------------------------- */ +#define BW_OPMODE_20MHZ BIT(2) +#define BW_OPMODE_5G BIT(1) +#define BW_OPMODE_11J BIT(0) + + +/* ----------------------------------------------------- */ +/* 8192C CAM Config Setting (offset 0x250, 1 byte) */ +/* ----------------------------------------------------- */ +#define CAM_VALID BIT(15) +#define CAM_NOTVALID 0x0000 +#define CAM_USEDK BIT(5) + +#define CAM_NONE 0x0 +#define CAM_WEP40 0x01 +#define CAM_TKIP 0x02 +#define CAM_AES 0x04 +#define CAM_WEP104 0x05 +#define CAM_SMS4 0x6 + + +#define TOTAL_CAM_ENTRY 32 +#define HALF_CAM_ENTRY 16 + +#define CAM_WRITE BIT(16) +#define CAM_READ 0x00000000 +#define CAM_POLLINIG BIT(31) + +/* 10. Power Save Control Registers (Offset: 0x0260 - 0x02DF) */ +#define WOW_PMEN BIT0 /* Power management Enable. */ +#define WOW_WOMEN BIT1 /* WoW function on or off. */ +#define WOW_MAGIC BIT2 /* Magic packet */ +#define WOW_UWF BIT3 /* Unicast Wakeup frame. */ + +/* 12. Host Interrupt Status Registers (Offset: 0x0300 - 0x030F) */ +/* ----------------------------------------------------- */ +/* 8190 IMR/ISR bits (offset 0xfd, 8bits) */ +/* ----------------------------------------------------- */ +#define IMR8190_DISABLED 0x0 +#define IMR_BCNDMAINT6 BIT(31) +#define IMR_BCNDMAINT5 BIT(30) +#define IMR_BCNDMAINT4 BIT(29) +#define IMR_BCNDMAINT3 BIT(28) +#define IMR_BCNDMAINT2 BIT(27) +#define IMR_BCNDMAINT1 BIT(26) +#define IMR_BCNDOK8 BIT(25) +#define IMR_BCNDOK7 BIT(24) +#define IMR_BCNDOK6 BIT(23) +#define IMR_BCNDOK5 BIT(22) +#define IMR_BCNDOK4 BIT(21) +#define IMR_BCNDOK3 BIT(20) +#define IMR_BCNDOK2 BIT(19) +#define IMR_BCNDOK1 BIT(18) +#define IMR_TIMEOUT2 BIT(17) +#define IMR_TIMEOUT1 BIT(16) +#define IMR_TXFOVW BIT(15) +#define IMR_PSTIMEOUT BIT(14) +#define IMR_BCNINT BIT(13) +#define IMR_RXFOVW BIT(12) +#define IMR_RDU BIT(11) +#define IMR_ATIMEND BIT(10) +#define IMR_BDOK BIT(9) +#define IMR_HIGHDOK BIT(8) +#define IMR_TBDOK BIT(7) +#define IMR_MGNTDOK BIT(6) +#define IMR_TBDER BIT(5) +#define IMR_BKDOK BIT(4) +#define IMR_BEDOK BIT(3) +#define IMR_VIDOK BIT(2) +#define IMR_VODOK BIT(1) +#define IMR_ROK BIT(0) + +#define IMR_TXERR BIT(11) +#define IMR_RXERR BIT(10) +#define IMR_C2HCMD BIT(9) +#define IMR_CPWM BIT(8) +#define IMR_OCPINT BIT(1) +#define IMR_WLANOFF BIT(0) + +/* ----------------------------------------------------- */ +/* 8192C EFUSE */ +/* ----------------------------------------------------- */ +#define HWSET_MAX_SIZE 256 +#define EFUSE_MAX_SECTION 32 +#define EFUSE_REAL_CONTENT_LEN 512 + +/* ----------------------------------------------------- */ +/* 8192C EEPROM/EFUSE share register definition. */ +/* ----------------------------------------------------- */ +#define EEPROM_DEFAULT_TSSI 0x0 +#define EEPROM_DEFAULT_CRYSTALCAP 0x0 +#define EEPROM_DEFAULT_THERMALMETER 0x12 + +#define EEPROM_DEFAULT_TXPOWERLEVEL_2G 0x2C +#define EEPROM_DEFAULT_TXPOWERLEVEL_5G 0x22 + +#define EEPROM_DEFAULT_HT40_2SDIFF 0x0 +/* HT20<->40 default Tx Power Index Difference */ +#define EEPROM_DEFAULT_HT20_DIFF 2 +/* OFDM Tx Power index diff */ +#define EEPROM_DEFAULT_LEGACYHTTXPOWERDIFF 0x4 +#define EEPROM_DEFAULT_HT40_PWRMAXOFFSET 0 +#define EEPROM_DEFAULT_HT20_PWRMAXOFFSET 0 + +#define EEPROM_CHANNEL_PLAN_FCC 0x0 +#define EEPROM_CHANNEL_PLAN_IC 0x1 +#define EEPROM_CHANNEL_PLAN_ETSI 0x2 +#define EEPROM_CHANNEL_PLAN_SPAIN 0x3 +#define EEPROM_CHANNEL_PLAN_FRANCE 0x4 +#define EEPROM_CHANNEL_PLAN_MKK 0x5 +#define EEPROM_CHANNEL_PLAN_MKK1 0x6 +#define EEPROM_CHANNEL_PLAN_ISRAEL 0x7 +#define EEPROM_CHANNEL_PLAN_TELEC 0x8 +#define EEPROM_CHANNEL_PLAN_GLOBAL_DOMAIN 0x9 +#define EEPROM_CHANNEL_PLAN_WORLD_WIDE_13 0xA +#define EEPROM_CHANNEL_PLAN_NCC 0xB +#define EEPROM_CHANNEL_PLAN_BY_HW_MASK 0x80 + +#define EEPROM_CID_DEFAULT 0x0 +#define EEPROM_CID_TOSHIBA 0x4 +#define EEPROM_CID_CCX 0x10 +#define EEPROM_CID_QMI 0x0D +#define EEPROM_CID_WHQL 0xFE + + +#define RTL8192_EEPROM_ID 0x8129 +#define EEPROM_WAPI_SUPPORT 0x78 + + +#define RTL8190_EEPROM_ID 0x8129 /* 0-1 */ +#define EEPROM_HPON 0x02 /* LDO settings.2-5 */ +#define EEPROM_CLK 0x06 /* Clock settings.6-7 */ +#define EEPROM_MAC_FUNCTION 0x08 /* SE Test mode.8 */ + +#define EEPROM_VID 0x28 /* SE Vendor ID.A-B */ +#define EEPROM_DID 0x2A /* SE Device ID. C-D */ +#define EEPROM_SVID 0x2C /* SE Vendor ID.E-F */ +#define EEPROM_SMID 0x2E /* SE PCI Subsystem ID. 10-11 */ + +#define EEPROM_MAC_ADDR 0x16 /* SEMAC Address. 12-17 */ +#define EEPROM_MAC_ADDR_MAC0_92D 0x55 +#define EEPROM_MAC_ADDR_MAC1_92D 0x5B + +/* 2.4G band Tx power index setting */ +#define EEPROM_CCK_TX_PWR_INX_2G 0x61 +#define EEPROM_HT40_1S_TX_PWR_INX_2G 0x67 +#define EEPROM_HT40_2S_TX_PWR_INX_DIFF_2G 0x6D +#define EEPROM_HT20_TX_PWR_INX_DIFF_2G 0x70 +#define EEPROM_OFDM_TX_PWR_INX_DIFF_2G 0x73 +#define EEPROM_HT40_MAX_PWR_OFFSET_2G 0x76 +#define EEPROM_HT20_MAX_PWR_OFFSET_2G 0x79 + +/*5GL channel 32-64 */ +#define EEPROM_HT40_1S_TX_PWR_INX_5GL 0x7C +#define EEPROM_HT40_2S_TX_PWR_INX_DIFF_5GL 0x82 +#define EEPROM_HT20_TX_PWR_INX_DIFF_5GL 0x85 +#define EEPROM_OFDM_TX_PWR_INX_DIFF_5GL 0x88 +#define EEPROM_HT40_MAX_PWR_OFFSET_5GL 0x8B +#define EEPROM_HT20_MAX_PWR_OFFSET_5GL 0x8E + +/* 5GM channel 100-140 */ +#define EEPROM_HT40_1S_TX_PWR_INX_5GM 0x91 +#define EEPROM_HT40_2S_TX_PWR_INX_DIFF_5GM 0x97 +#define EEPROM_HT20_TX_PWR_INX_DIFF_5GM 0x9A +#define EEPROM_OFDM_TX_PWR_INX_DIFF_5GM 0x9D +#define EEPROM_HT40_MAX_PWR_OFFSET_5GM 0xA0 +#define EEPROM_HT20_MAX_PWR_OFFSET_5GM 0xA3 + +/* 5GH channel 149-165 */ +#define EEPROM_HT40_1S_TX_PWR_INX_5GH 0xA6 +#define EEPROM_HT40_2S_TX_PWR_INX_DIFF_5GH 0xAC +#define EEPROM_HT20_TX_PWR_INX_DIFF_5GH 0xAF +#define EEPROM_OFDM_TX_PWR_INX_DIFF_5GH 0xB2 +#define EEPROM_HT40_MAX_PWR_OFFSET_5GH 0xB5 +#define EEPROM_HT20_MAX_PWR_OFFSET_5GH 0xB8 + +/* Map of supported channels. */ +#define EEPROM_CHANNEL_PLAN 0xBB +#define EEPROM_IQK_DELTA 0xBC +#define EEPROM_LCK_DELTA 0xBC +#define EEPROM_XTAL_K 0xBD /* [7:5] */ +#define EEPROM_TSSI_A_5G 0xBE +#define EEPROM_TSSI_B_5G 0xBF +#define EEPROM_TSSI_AB_5G 0xC0 +#define EEPROM_THERMAL_METER 0xC3 /* [4:0] */ +#define EEPROM_RF_OPT1 0xC4 +#define EEPROM_RF_OPT2 0xC5 +#define EEPROM_RF_OPT3 0xC6 +#define EEPROM_RF_OPT4 0xC7 +#define EEPROM_RF_OPT5 0xC8 +#define EEPROM_RF_OPT6 0xC9 +#define EEPROM_VERSION 0xCA +#define EEPROM_CUSTOMER_ID 0xCB +#define EEPROM_RF_OPT7 0xCC + +#define EEPROM_DEF_PART_NO 0x3FD /* Byte */ +#define EEPROME_CHIP_VERSION_L 0x3FF +#define EEPROME_CHIP_VERSION_H 0x3FE + +/* + * Current IOREG MAP + * 0x0000h ~ 0x00FFh System Configuration (256 Bytes) + * 0x0100h ~ 0x01FFh MACTOP General Configuration (256 Bytes) + * 0x0200h ~ 0x027Fh TXDMA Configuration (128 Bytes) + * 0x0280h ~ 0x02FFh RXDMA Configuration (128 Bytes) + * 0x0300h ~ 0x03FFh PCIE EMAC Reserved Region (256 Bytes) + * 0x0400h ~ 0x04FFh Protocol Configuration (256 Bytes) + * 0x0500h ~ 0x05FFh EDCA Configuration (256 Bytes) + * 0x0600h ~ 0x07FFh WMAC Configuration (512 Bytes) + * 0x2000h ~ 0x3FFFh 8051 FW Download Region (8196 Bytes) + */ + +/* ----------------------------------------------------- */ +/* 8192C (RCR) (Offset 0x608, 32 bits) */ +/* ----------------------------------------------------- */ +#define RCR_APPFCS BIT(31) +#define RCR_APP_MIC BIT(30) +#define RCR_APP_ICV BIT(29) +#define RCR_APP_PHYST_RXFF BIT(28) +#define RCR_APP_BA_SSN BIT(27) +#define RCR_ENMBID BIT(24) +#define RCR_LSIGEN BIT(23) +#define RCR_MFBEN BIT(22) +#define RCR_HTC_LOC_CTRL BIT(14) +#define RCR_AMF BIT(13) +#define RCR_ACF BIT(12) +#define RCR_ADF BIT(11) +#define RCR_AICV BIT(9) +#define RCR_ACRC32 BIT(8) +#define RCR_CBSSID_BCN BIT(7) +#define RCR_CBSSID_DATA BIT(6) +#define RCR_APWRMGT BIT(5) +#define RCR_ADD3 BIT(4) +#define RCR_AB BIT(3) +#define RCR_AM BIT(2) +#define RCR_APM BIT(1) +#define RCR_AAP BIT(0) +#define RCR_MXDMA_OFFSET 8 +#define RCR_FIFO_OFFSET 13 + +/* ----------------------------------------------------- */ +/* 8192C Regsiter Bit and Content definition */ +/* ----------------------------------------------------- */ +/* ----------------------------------------------------- */ +/* 0x0000h ~ 0x00FFh System Configuration */ +/* ----------------------------------------------------- */ + +/* SPS0_CTRL */ +#define SW18_FPWM BIT(3) + + +/* SYS_ISO_CTRL */ +#define ISO_MD2PP BIT(0) +#define ISO_UA2USB BIT(1) +#define ISO_UD2CORE BIT(2) +#define ISO_PA2PCIE BIT(3) +#define ISO_PD2CORE BIT(4) +#define ISO_IP2MAC BIT(5) +#define ISO_DIOP BIT(6) +#define ISO_DIOE BIT(7) +#define ISO_EB2CORE BIT(8) +#define ISO_DIOR BIT(9) + +#define PWC_EV25V BIT(14) +#define PWC_EV12V BIT(15) + + +/* SYS_FUNC_EN */ +#define FEN_BBRSTB BIT(0) +#define FEN_BB_GLB_RSTn BIT(1) +#define FEN_USBA BIT(2) +#define FEN_UPLL BIT(3) +#define FEN_USBD BIT(4) +#define FEN_DIO_PCIE BIT(5) +#define FEN_PCIEA BIT(6) +#define FEN_PPLL BIT(7) +#define FEN_PCIED BIT(8) +#define FEN_DIOE BIT(9) +#define FEN_CPUEN BIT(10) +#define FEN_DCORE BIT(11) +#define FEN_ELDR BIT(12) +#define FEN_DIO_RF BIT(13) +#define FEN_HWPDN BIT(14) +#define FEN_MREGEN BIT(15) + +/* APS_FSMCO */ +#define PFM_LDALL BIT(0) +#define PFM_ALDN BIT(1) +#define PFM_LDKP BIT(2) +#define PFM_WOWL BIT(3) +#define EnPDN BIT(4) +#define PDN_PL BIT(5) +#define APFM_ONMAC BIT(8) +#define APFM_OFF BIT(9) +#define APFM_RSM BIT(10) +#define AFSM_HSUS BIT(11) +#define AFSM_PCIE BIT(12) +#define APDM_MAC BIT(13) +#define APDM_HOST BIT(14) +#define APDM_HPDN BIT(15) +#define RDY_MACON BIT(16) +#define SUS_HOST BIT(17) +#define ROP_ALD BIT(20) +#define ROP_PWR BIT(21) +#define ROP_SPS BIT(22) +#define SOP_MRST BIT(25) +#define SOP_FUSE BIT(26) +#define SOP_ABG BIT(27) +#define SOP_AMB BIT(28) +#define SOP_RCK BIT(29) +#define SOP_A8M BIT(30) +#define XOP_BTCK BIT(31) + +/* SYS_CLKR */ +#define ANAD16V_EN BIT(0) +#define ANA8M BIT(1) +#define MACSLP BIT(4) +#define LOADER_CLK_EN BIT(5) +#define _80M_SSC_DIS BIT(7) +#define _80M_SSC_EN_HO BIT(8) +#define PHY_SSC_RSTB BIT(9) +#define SEC_CLK_EN BIT(10) +#define MAC_CLK_EN BIT(11) +#define SYS_CLK_EN BIT(12) +#define RING_CLK_EN BIT(13) + + +/* 9346CR */ +#define BOOT_FROM_EEPROM BIT(4) +#define EEPROM_EN BIT(5) + +/* AFE_MISC */ +#define AFE_BGEN BIT(0) +#define AFE_MBEN BIT(1) +#define MAC_ID_EN BIT(7) + +/* RSV_CTRL */ +#define WLOCK_ALL BIT(0) +#define WLOCK_00 BIT(1) +#define WLOCK_04 BIT(2) +#define WLOCK_08 BIT(3) +#define WLOCK_40 BIT(4) +#define R_DIS_PRST_0 BIT(5) +#define R_DIS_PRST_1 BIT(6) +#define LOCK_ALL_EN BIT(7) + +/* RF_CTRL */ +#define RF_EN BIT(0) +#define RF_RSTB BIT(1) +#define RF_SDMRSTB BIT(2) + + + +/* LDOA15_CTRL */ +#define LDA15_EN BIT(0) +#define LDA15_STBY BIT(1) +#define LDA15_OBUF BIT(2) +#define LDA15_REG_VOS BIT(3) +#define _LDA15_VOADJ(x) (((x) & 0x7) << 4) + + + +/* LDOV12D_CTRL */ +#define LDV12_EN BIT(0) +#define LDV12_SDBY BIT(1) +#define LPLDO_HSM BIT(2) +#define LPLDO_LSM_DIS BIT(3) +#define _LDV12_VADJ(x) (((x) & 0xF) << 4) + + +/* AFE_XTAL_CTRL */ +#define XTAL_EN BIT(0) +#define XTAL_BSEL BIT(1) +#define _XTAL_BOSC(x) (((x) & 0x3) << 2) +#define _XTAL_CADJ(x) (((x) & 0xF) << 4) +#define XTAL_GATE_USB BIT(8) +#define _XTAL_USB_DRV(x) (((x) & 0x3) << 9) +#define XTAL_GATE_AFE BIT(11) +#define _XTAL_AFE_DRV(x) (((x) & 0x3) << 12) +#define XTAL_RF_GATE BIT(14) +#define _XTAL_RF_DRV(x) (((x) & 0x3) << 15) +#define XTAL_GATE_DIG BIT(17) +#define _XTAL_DIG_DRV(x) (((x) & 0x3) << 18) +#define XTAL_BT_GATE BIT(20) +#define _XTAL_BT_DRV(x) (((x) & 0x3) << 21) +#define _XTAL_GPIO(x) (((x) & 0x7) << 23) + + +#define CKDLY_AFE BIT(26) +#define CKDLY_USB BIT(27) +#define CKDLY_DIG BIT(28) +#define CKDLY_BT BIT(29) + + +/* AFE_PLL_CTRL */ +#define APLL_EN BIT(0) +#define APLL_320_EN BIT(1) +#define APLL_FREF_SEL BIT(2) +#define APLL_EDGE_SEL BIT(3) +#define APLL_WDOGB BIT(4) +#define APLL_LPFEN BIT(5) + +#define APLL_REF_CLK_13MHZ 0x1 +#define APLL_REF_CLK_19_2MHZ 0x2 +#define APLL_REF_CLK_20MHZ 0x3 +#define APLL_REF_CLK_25MHZ 0x4 +#define APLL_REF_CLK_26MHZ 0x5 +#define APLL_REF_CLK_38_4MHZ 0x6 +#define APLL_REF_CLK_40MHZ 0x7 + +#define APLL_320EN BIT(14) +#define APLL_80EN BIT(15) +#define APLL_1MEN BIT(24) + + +/* EFUSE_CTRL */ +#define ALD_EN BIT(18) +#define EF_PD BIT(19) +#define EF_FLAG BIT(31) + +/* EFUSE_TEST */ +#define EF_TRPT BIT(7) +#define LDOE25_EN BIT(31) + +/* MCUFWDL */ +#define MCUFWDL_EN BIT(0) +#define MCUFWDL_RDY BIT(1) +#define FWDL_ChkSum_rpt BIT(2) +#define MACINI_RDY BIT(3) +#define BBINI_RDY BIT(4) +#define RFINI_RDY BIT(5) +#define WINTINI_RDY BIT(6) +#define MAC1_WINTINI_RDY BIT(11) +#define CPRST BIT(23) + +/* REG_SYS_CFG */ +#define XCLK_VLD BIT(0) +#define ACLK_VLD BIT(1) +#define UCLK_VLD BIT(2) +#define PCLK_VLD BIT(3) +#define PCIRSTB BIT(4) +#define V15_VLD BIT(5) +#define TRP_B15V_EN BIT(7) +#define SIC_IDLE BIT(8) +#define BD_MAC2 BIT(9) +#define BD_MAC1 BIT(10) +#define IC_MACPHY_MODE BIT(11) +#define PAD_HWPD_IDN BIT(22) +#define TRP_VAUX_EN BIT(23) +#define TRP_BT_EN BIT(24) +#define BD_PKG_SEL BIT(25) +#define BD_HCI_SEL BIT(26) +#define TYPE_ID BIT(27) + +/* LLT_INIT */ +#define _LLT_NO_ACTIVE 0x0 +#define _LLT_WRITE_ACCESS 0x1 +#define _LLT_READ_ACCESS 0x2 + +#define _LLT_INIT_DATA(x) ((x) & 0xFF) +#define _LLT_INIT_ADDR(x) (((x) & 0xFF) << 8) +#define _LLT_OP(x) (((x) & 0x3) << 30) +#define _LLT_OP_VALUE(x) (((x) >> 30) & 0x3) + + +/* ----------------------------------------------------- */ +/* 0x0400h ~ 0x047Fh Protocol Configuration */ +/* ----------------------------------------------------- */ +#define RETRY_LIMIT_SHORT_SHIFT 8 +#define RETRY_LIMIT_LONG_SHIFT 0 + + +/* ----------------------------------------------------- */ +/* 0x0500h ~ 0x05FFh EDCA Configuration */ +/* ----------------------------------------------------- */ +/* EDCA setting */ +#define AC_PARAM_TXOP_LIMIT_OFFSET 16 +#define AC_PARAM_ECW_MAX_OFFSET 12 +#define AC_PARAM_ECW_MIN_OFFSET 8 +#define AC_PARAM_AIFS_OFFSET 0 + +/* ACMHWCTRL */ +#define ACMHW_HWEN BIT(0) +#define ACMHW_BEQEN BIT(1) +#define ACMHW_VIQEN BIT(2) +#define ACMHW_VOQEN BIT(3) + +/* ----------------------------------------------------- */ +/* 0x0600h ~ 0x07FFh WMAC Configuration */ +/* ----------------------------------------------------- */ + +/* TCR */ +#define TSFRST BIT(0) +#define DIS_GCLK BIT(1) +#define PAD_SEL BIT(2) +#define PWR_ST BIT(6) +#define PWRBIT_OW_EN BIT(7) +#define ACRC BIT(8) +#define CFENDFORM BIT(9) +#define ICV BIT(10) + +/* SECCFG */ +#define SCR_TXUSEDK BIT(0) +#define SCR_RXUSEDK BIT(1) +#define SCR_TXENCENABLE BIT(2) +#define SCR_RXENCENABLE BIT(3) +#define SCR_SKBYA2 BIT(4) +#define SCR_NOSKMC BIT(5) +#define SCR_TXBCUSEDK BIT(6) +#define SCR_RXBCUSEDK BIT(7) + +/* General definitions */ +#define LAST_ENTRY_OF_TX_PKT_BUFFER 255 +#define LAST_ENTRY_OF_TX_PKT_BUFFER_DUAL_MAC 127 + +#define POLLING_LLT_THRESHOLD 20 +#define POLLING_READY_TIMEOUT_COUNT 1000 + +/* Min Spacing related settings. */ +#define MAX_MSS_DENSITY_2T 0x13 +#define MAX_MSS_DENSITY_1T 0x0A + + +/* BB-PHY register PMAC 0x100 PHY 0x800 - 0xEFF */ +/* 1. PMAC duplicate register due to connection: */ +/* RF_Mode, TRxRN, NumOf L-STF */ +/* 2. 0x800/0x900/0xA00/0xC00/0xD00/0xE00 */ +/* 3. RF register 0x00-2E */ +/* 4. Bit Mask for BB/RF register */ +/* 5. Other defintion for BB/RF R/W */ + +/* 3. Page8(0x800) */ +#define RFPGA0_RFMOD 0x800 + +#define RFPGA0_TXINFO 0x804 +#define RFPGA0_PSDFUNCTION 0x808 + +#define RFPGA0_TXGAINSTAGE 0x80c + +#define RFPGA0_RFTIMING1 0x810 +#define RFPGA0_RFTIMING2 0x814 + +#define RFPGA0_XA_HSSIPARAMETER1 0x820 +#define RFPGA0_XA_HSSIPARAMETER2 0x824 +#define RFPGA0_XB_HSSIPARAMETER1 0x828 +#define RFPGA0_XB_HSSIPARAMETER2 0x82c + +#define RFPGA0_XA_LSSIPARAMETER 0x840 +#define RFPGA0_XB_LSSIPARAMETER 0x844 + +#define RFPGA0_RFWAkEUPPARAMETER 0x850 +#define RFPGA0_RFSLEEPUPPARAMETER 0x854 + +#define RFPGA0_XAB_SWITCHCONTROL 0x858 +#define RFPGA0_XCD_SWITCHCONTROL 0x85c + +#define RFPGA0_XA_RFINTERFACEOE 0x860 +#define RFPGA0_XB_RFINTERFACEOE 0x864 + +#define RFPGA0_XAB_RFINTERFACESW 0x870 +#define RFPGA0_XCD_RFINTERFACESW 0x874 + +#define RFPGA0_XAB_RFPARAMETER 0x878 +#define RFPGA0_XCD_RFPARAMETER 0x87c + +#define RFPGA0_ANALOGPARAMETER1 0x880 +#define RFPGA0_ANALOGPARAMETER2 0x884 +#define RFPGA0_ANALOGPARAMETER3 0x888 +#define RFPGA0_ADDALLOCKEN 0x888 +#define RFPGA0_ANALOGPARAMETER4 0x88c + +#define RFPGA0_XA_LSSIREADBACK 0x8a0 +#define RFPGA0_XB_LSSIREADBACK 0x8a4 +#define RFPGA0_XC_LSSIREADBACK 0x8a8 +#define RFPGA0_XD_LSSIREADBACK 0x8ac + +#define RFPGA0_PSDREPORT 0x8b4 +#define TRANSCEIVERA_HSPI_READBACK 0x8b8 +#define TRANSCEIVERB_HSPI_READBACK 0x8bc +#define RFPGA0_XAB_RFINTERFACERB 0x8e0 +#define RFPGA0_XCD_RFINTERFACERB 0x8e4 + +/* 4. Page9(0x900) */ +#define RFPGA1_RFMOD 0x900 + +#define RFPGA1_TXBLOCK 0x904 +#define RFPGA1_DEBUGSELECT 0x908 +#define RFPGA1_TXINFO 0x90c + +/* 5. PageA(0xA00) */ +#define RCCK0_SYSTEM 0xa00 + +#define RCCK0_AFESSTTING 0xa04 +#define RCCK0_CCA 0xa08 + +#define RCCK0_RXAGC1 0xa0c +#define RCCK0_RXAGC2 0xa10 + +#define RCCK0_RXHP 0xa14 + +#define RCCK0_DSPPARAMETER1 0xa18 +#define RCCK0_DSPPARAMETER2 0xa1c + +#define RCCK0_TXFILTER1 0xa20 +#define RCCK0_TXFILTER2 0xa24 +#define RCCK0_DEBUGPORT 0xa28 +#define RCCK0_FALSEALARMREPORT 0xa2c +#define RCCK0_TRSSIREPORT 0xa50 +#define RCCK0_RXREPORT 0xa54 +#define RCCK0_FACOUNTERLOWER 0xa5c +#define RCCK0_FACOUNTERUPPER 0xa58 + +/* 6. PageC(0xC00) */ +#define ROFDM0_LSTF 0xc00 + +#define ROFDM0_TRXPATHENABLE 0xc04 +#define ROFDM0_TRMUXPAR 0xc08 +#define ROFDM0_TRSWISOLATION 0xc0c + +#define ROFDM0_XARXAFE 0xc10 +#define ROFDM0_XARXIQIMBALANCE 0xc14 +#define ROFDM0_XBRXAFE 0xc18 +#define ROFDM0_XBRXIQIMBALANCE 0xc1c +#define ROFDM0_XCRXAFE 0xc20 +#define ROFDM0_XCRXIQIMBALANCE 0xc24 +#define ROFDM0_XDRXAFE 0xc28 +#define ROFDM0_XDRXIQIMBALANCE 0xc2c + +#define ROFDM0_RXDETECTOR1 0xc30 +#define ROFDM0_RXDETECTOR2 0xc34 +#define ROFDM0_RXDETECTOR3 0xc38 +#define ROFDM0_RXDETECTOR4 0xc3c + +#define ROFDM0_RXDSP 0xc40 +#define ROFDM0_CFOANDDAGC 0xc44 +#define ROFDM0_CCADROPTHRESHOLD 0xc48 +#define ROFDM0_ECCATHRESHOLD 0xc4c + +#define ROFDM0_XAAGCCORE1 0xc50 +#define ROFDM0_XAAGCCORE2 0xc54 +#define ROFDM0_XBAGCCORE1 0xc58 +#define ROFDM0_XBAGCCORE2 0xc5c +#define ROFDM0_XCAGCCORE1 0xc60 +#define ROFDM0_XCAGCCORE2 0xc64 +#define ROFDM0_XDAGCCORE1 0xc68 +#define ROFDM0_XDAGCCORE2 0xc6c + +#define ROFDM0_AGCPARAMETER1 0xc70 +#define ROFDM0_AGCPARAMETER2 0xc74 +#define ROFDM0_AGCRSSITABLE 0xc78 +#define ROFDM0_HTSTFAGC 0xc7c + +#define ROFDM0_XATxIQIMBALANCE 0xc80 +#define ROFDM0_XATxAFE 0xc84 +#define ROFDM0_XBTxIQIMBALANCE 0xc88 +#define ROFDM0_XBTxAFE 0xc8c +#define ROFDM0_XCTxIQIMBALANCE 0xc90 +#define ROFDM0_XCTxAFE 0xc94 +#define ROFDM0_XDTxIQIMBALANCE 0xc98 +#define ROFDM0_XDTxAFE 0xc9c + +#define ROFDM0_RXHPPARAMETER 0xce0 +#define ROFDM0_TXPSEUDONOISEWGT 0xce4 +#define ROFDM0_FRAMESYNC 0xcf0 +#define ROFDM0_DFSREPORT 0xcf4 +#define ROFDM0_TXCOEFF1 0xca4 +#define ROFDM0_TXCOEFF2 0xca8 +#define ROFDM0_TXCOEFF3 0xcac +#define ROFDM0_TXCOEFF4 0xcb0 +#define ROFDM0_TXCOEFF5 0xcb4 +#define ROFDM0_TXCOEFF6 0xcb8 + +/* 7. PageD(0xD00) */ +#define ROFDM1_LSTF 0xd00 +#define ROFDM1_TRXPATHENABLE 0xd04 + +#define ROFDM1_CFO 0xd08 +#define ROFDM1_CSI1 0xd10 +#define ROFDM1_SBD 0xd14 +#define ROFDM1_CSI2 0xd18 +#define ROFDM1_CFOTRACKING 0xd2c +#define ROFDM1_TRXMESAURE1 0xd34 +#define ROFDM1_INTFDET 0xd3c +#define ROFDM1_PSEUDONOISESTATEAB 0xd50 +#define ROFDM1_PSEUDONOISESTATECD 0xd54 +#define ROFDM1_RXPSEUDONOISEWGT 0xd58 + +#define ROFDM_PHYCOUNTER1 0xda0 +#define ROFDM_PHYCOUNTER2 0xda4 +#define ROFDM_PHYCOUNTER3 0xda8 + +#define ROFDM_SHORTCFOAB 0xdac +#define ROFDM_SHORTCFOCD 0xdb0 +#define ROFDM_LONGCFOAB 0xdb4 +#define ROFDM_LONGCFOCD 0xdb8 +#define ROFDM_TAILCFOAB 0xdbc +#define ROFDM_TAILCFOCD 0xdc0 +#define ROFDM_PWMEASURE1 0xdc4 +#define ROFDM_PWMEASURE2 0xdc8 +#define ROFDM_BWREPORT 0xdcc +#define ROFDM_AGCREPORT 0xdd0 +#define ROFDM_RXSNR 0xdd4 +#define ROFDM_RXEVMCSI 0xdd8 +#define ROFDM_SIGReport 0xddc + +/* 8. PageE(0xE00) */ +#define RTXAGC_A_RATE18_06 0xe00 +#define RTXAGC_A_RATE54_24 0xe04 +#define RTXAGC_A_CCK1_MCS32 0xe08 +#define RTXAGC_A_MCS03_MCS00 0xe10 +#define RTXAGC_A_MCS07_MCS04 0xe14 +#define RTXAGC_A_MCS11_MCS08 0xe18 +#define RTXAGC_A_MCS15_MCS12 0xe1c + +#define RTXAGC_B_RATE18_06 0x830 +#define RTXAGC_B_RATE54_24 0x834 +#define RTXAGC_B_CCK1_55_MCS32 0x838 +#define RTXAGC_B_MCS03_MCS00 0x83c +#define RTXAGC_B_MCS07_MCS04 0x848 +#define RTXAGC_B_MCS11_MCS08 0x84c +#define RTXAGC_B_MCS15_MCS12 0x868 +#define RTXAGC_B_CCK11_A_CCK2_11 0x86c + +/* RL6052 Register definition */ +#define RF_AC 0x00 + +#define RF_IQADJ_G1 0x01 +#define RF_IQADJ_G2 0x02 +#define RF_POW_TRSW 0x05 + +#define RF_GAIN_RX 0x06 +#define RF_GAIN_TX 0x07 + +#define RF_TXM_IDAC 0x08 +#define RF_BS_IQGEN 0x0F + +#define RF_MODE1 0x10 +#define RF_MODE2 0x11 + +#define RF_RX_AGC_HP 0x12 +#define RF_TX_AGC 0x13 +#define RF_BIAS 0x14 +#define RF_IPA 0x15 +#define RF_POW_ABILITY 0x17 +#define RF_MODE_AG 0x18 +#define rRfChannel 0x18 +#define RF_CHNLBW 0x18 +#define RF_TOP 0x19 + +#define RF_RX_G1 0x1A +#define RF_RX_G2 0x1B + +#define RF_RX_BB2 0x1C +#define RF_RX_BB1 0x1D + +#define RF_RCK1 0x1E +#define RF_RCK2 0x1F + +#define RF_TX_G1 0x20 +#define RF_TX_G2 0x21 +#define RF_TX_G3 0x22 + +#define RF_TX_BB1 0x23 + +#define RF_T_METER 0x42 + +#define RF_SYN_G1 0x25 +#define RF_SYN_G2 0x26 +#define RF_SYN_G3 0x27 +#define RF_SYN_G4 0x28 +#define RF_SYN_G5 0x29 +#define RF_SYN_G6 0x2A +#define RF_SYN_G7 0x2B +#define RF_SYN_G8 0x2C + +#define RF_RCK_OS 0x30 + +#define RF_TXPA_G1 0x31 +#define RF_TXPA_G2 0x32 +#define RF_TXPA_G3 0x33 + +/* Bit Mask */ + +/* 2. Page8(0x800) */ +#define BRFMOD 0x1 +#define BCCKTXSC 0x30 +#define BCCKEN 0x1000000 +#define BOFDMEN 0x2000000 + +#define B3WIREDATALENGTH 0x800 +#define B3WIREADDRESSLENGTH 0x400 + +#define BRFSI_RFENV 0x10 + +#define BLSSIREADADDRESS 0x7f800000 +#define BLSSIREADEDGE 0x80000000 +#define BLSSIREADBACKDATA 0xfffff +/* 4. PageA(0xA00) */ +#define BCCKSIDEBAND 0x10 + +/* Other Definition */ +#define BBYTE0 0x1 +#define BBYTE1 0x2 +#define BBYTE2 0x4 +#define BBYTE3 0x8 +#define BWORD0 0x3 +#define BWORD1 0xc +#define BDWORD 0xf + +#define BMASKBYTE0 0xff +#define BMASKBYTE1 0xff00 +#define BMASKBYTE2 0xff0000 +#define BMASKBYTE3 0xff000000 +#define BMASKHWORD 0xffff0000 +#define BMASKLWORD 0x0000ffff +#define BMASKDWORD 0xffffffff +#define BMASK12BITS 0xfff +#define BMASKH4BITS 0xf0000000 +#define BMASKOFDM_D 0xffc00000 +#define BMASKCCK 0x3f3f3f3f + +#define BRFREGOFFSETMASK 0xfffff + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/rf.c b/drivers/net/wireless/rtlwifi/rtl8192de/rf.c new file mode 100644 index 0000000..20144e0 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/rf.c @@ -0,0 +1,623 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "rf.h" +#include "dm.h" +#include "hw.h" + +void rtl92d_phy_rf6052_set_bandwidth(struct ieee80211_hw *hw, u8 bandwidth) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u8 rfpath; + + switch (bandwidth) { + case HT_CHANNEL_WIDTH_20: + for (rfpath = 0; rfpath < rtlphy->num_total_rfpath; rfpath++) { + rtlphy->rfreg_chnlval[rfpath] = ((rtlphy->rfreg_chnlval + [rfpath] & 0xfffff3ff) | 0x0400); + rtl_set_rfreg(hw, rfpath, RF_CHNLBW, BIT(10) | + BIT(11), 0x01); + + RT_TRACE(rtlpriv, COMP_RF, DBG_LOUD, + "20M RF 0x18 = 0x%x\n", + rtlphy->rfreg_chnlval[rfpath]); + } + + break; + case HT_CHANNEL_WIDTH_20_40: + for (rfpath = 0; rfpath < rtlphy->num_total_rfpath; rfpath++) { + rtlphy->rfreg_chnlval[rfpath] = + ((rtlphy->rfreg_chnlval[rfpath] & 0xfffff3ff)); + rtl_set_rfreg(hw, rfpath, RF_CHNLBW, BIT(10) | BIT(11), + 0x00); + RT_TRACE(rtlpriv, COMP_RF, DBG_LOUD, + "40M RF 0x18 = 0x%x\n", + rtlphy->rfreg_chnlval[rfpath]); + } + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", bandwidth); + break; + } +} + +void rtl92d_phy_rf6052_set_cck_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u32 tx_agc[2] = {0, 0}, tmpval; + bool turbo_scanoff = false; + u8 idx1, idx2; + u8 *ptr; + + if (rtlefuse->eeprom_regulatory != 0) + turbo_scanoff = true; + if (mac->act_scanning) { + tx_agc[RF90_PATH_A] = 0x3f3f3f3f; + tx_agc[RF90_PATH_B] = 0x3f3f3f3f; + if (turbo_scanoff) { + for (idx1 = RF90_PATH_A; idx1 <= RF90_PATH_B; idx1++) { + tx_agc[idx1] = ppowerlevel[idx1] | + (ppowerlevel[idx1] << 8) | + (ppowerlevel[idx1] << 16) | + (ppowerlevel[idx1] << 24); + } + } + } else { + for (idx1 = RF90_PATH_A; idx1 <= RF90_PATH_B; idx1++) { + tx_agc[idx1] = ppowerlevel[idx1] | + (ppowerlevel[idx1] << 8) | + (ppowerlevel[idx1] << 16) | + (ppowerlevel[idx1] << 24); + } + if (rtlefuse->eeprom_regulatory == 0) { + tmpval = (rtlphy->mcs_offset[0][6]) + + (rtlphy->mcs_offset[0][7] << 8); + tx_agc[RF90_PATH_A] += tmpval; + tmpval = (rtlphy->mcs_offset[0][14]) + + (rtlphy->mcs_offset[0][15] << 24); + tx_agc[RF90_PATH_B] += tmpval; + } + } + + for (idx1 = RF90_PATH_A; idx1 <= RF90_PATH_B; idx1++) { + ptr = (u8 *) (&(tx_agc[idx1])); + for (idx2 = 0; idx2 < 4; idx2++) { + if (*ptr > RF6052_MAX_TX_PWR) + *ptr = RF6052_MAX_TX_PWR; + ptr++; + } + } + + tmpval = tx_agc[RF90_PATH_A] & 0xff; + rtl_set_bbreg(hw, RTXAGC_A_CCK1_MCS32, BMASKBYTE1, tmpval); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 1M (rf-A) = 0x%x (reg 0x%x)\n", + tmpval, RTXAGC_A_CCK1_MCS32); + tmpval = tx_agc[RF90_PATH_A] >> 8; + rtl_set_bbreg(hw, RTXAGC_B_CCK11_A_CCK2_11, 0xffffff00, tmpval); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 2~11M (rf-A) = 0x%x (reg 0x%x)\n", + tmpval, RTXAGC_B_CCK11_A_CCK2_11); + tmpval = tx_agc[RF90_PATH_B] >> 24; + rtl_set_bbreg(hw, RTXAGC_B_CCK11_A_CCK2_11, BMASKBYTE0, tmpval); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 11M (rf-B) = 0x%x (reg 0x%x)\n", + tmpval, RTXAGC_B_CCK11_A_CCK2_11); + tmpval = tx_agc[RF90_PATH_B] & 0x00ffffff; + rtl_set_bbreg(hw, RTXAGC_B_CCK1_55_MCS32, 0xffffff00, tmpval); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 1~5.5M (rf-B) = 0x%x (reg 0x%x)\n", + tmpval, RTXAGC_B_CCK1_55_MCS32); +} + +static void _rtl92d_phy_get_power_base(struct ieee80211_hw *hw, + u8 *ppowerlevel, u8 channel, + u32 *ofdmbase, u32 *mcsbase) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u32 powerbase0, powerbase1; + u8 legacy_pwrdiff, ht20_pwrdiff; + u8 i, powerlevel[2]; + + for (i = 0; i < 2; i++) { + powerlevel[i] = ppowerlevel[i]; + legacy_pwrdiff = rtlefuse->txpwr_legacyhtdiff[i][channel - 1]; + powerbase0 = powerlevel[i] + legacy_pwrdiff; + powerbase0 = (powerbase0 << 24) | (powerbase0 << 16) | + (powerbase0 << 8) | powerbase0; + *(ofdmbase + i) = powerbase0; + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + " [OFDM power base index rf(%c) = 0x%x]\n", + i == 0 ? 'A' : 'B', *(ofdmbase + i)); + } + + for (i = 0; i < 2; i++) { + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20) { + ht20_pwrdiff = rtlefuse->txpwr_ht20diff[i][channel - 1]; + powerlevel[i] += ht20_pwrdiff; + } + powerbase1 = powerlevel[i]; + powerbase1 = (powerbase1 << 24) | (powerbase1 << 16) | + (powerbase1 << 8) | powerbase1; + *(mcsbase + i) = powerbase1; + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + " [MCS power base index rf(%c) = 0x%x]\n", + i == 0 ? 'A' : 'B', *(mcsbase + i)); + } +} + +static u8 _rtl92d_phy_get_chnlgroup_bypg(u8 chnlindex) +{ + u8 group; + u8 channel_info[59] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, + 60, 62, 64, 100, 102, 104, 106, 108, 110, 112, + 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, + 134, 136, 138, 140, 149, 151, 153, 155, 157, 159, + 161, 163, 165 + }; + + if (channel_info[chnlindex] <= 3) /* Chanel 1-3 */ + group = 0; + else if (channel_info[chnlindex] <= 9) /* Channel 4-9 */ + group = 1; + else if (channel_info[chnlindex] <= 14) /* Channel 10-14 */ + group = 2; + else if (channel_info[chnlindex] <= 64) + group = 6; + else if (channel_info[chnlindex] <= 140) + group = 7; + else + group = 8; + return group; +} + +static void _rtl92d_get_txpower_writeval_by_regulatory(struct ieee80211_hw *hw, + u8 channel, u8 index, + u32 *powerbase0, + u32 *powerbase1, + u32 *p_outwriteval) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 i, chnlgroup = 0, pwr_diff_limit[4]; + u32 writeval = 0, customer_limit, rf; + + for (rf = 0; rf < 2; rf++) { + switch (rtlefuse->eeprom_regulatory) { + case 0: + chnlgroup = 0; + writeval = rtlphy->mcs_offset + [chnlgroup][index + + (rf ? 8 : 0)] + ((index < 2) ? + powerbase0[rf] : + powerbase1[rf]); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "RTK better performance, writeval(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', writeval); + break; + case 1: + if (rtlphy->pwrgroup_cnt == 1) + chnlgroup = 0; + if (rtlphy->pwrgroup_cnt >= MAX_PG_GROUP) { + chnlgroup = _rtl92d_phy_get_chnlgroup_bypg( + channel - 1); + if (rtlphy->current_chan_bw == + HT_CHANNEL_WIDTH_20) + chnlgroup++; + else + chnlgroup += 4; + writeval = rtlphy->mcs_offset + [chnlgroup][index + + (rf ? 8 : 0)] + ((index < 2) ? + powerbase0[rf] : + powerbase1[rf]); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Realtek regulatory, 20MHz, writeval(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', writeval); + } + break; + case 2: + writeval = ((index < 2) ? powerbase0[rf] : + powerbase1[rf]); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Better regulatory, writeval(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', writeval); + break; + case 3: + chnlgroup = 0; + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20_40) { + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "customer's limit, 40MHz rf(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', + rtlefuse->pwrgroup_ht40[rf] + [channel - 1]); + } else { + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "customer's limit, 20MHz rf(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', + rtlefuse->pwrgroup_ht20[rf] + [channel - 1]); + } + for (i = 0; i < 4; i++) { + pwr_diff_limit[i] = (u8)((rtlphy->mcs_offset + [chnlgroup][index + (rf ? 8 : 0)] & + (0x7f << (i * 8))) >> (i * 8)); + if (rtlphy->current_chan_bw == + HT_CHANNEL_WIDTH_20_40) { + if (pwr_diff_limit[i] > + rtlefuse->pwrgroup_ht40[rf] + [channel - 1]) + pwr_diff_limit[i] = + rtlefuse->pwrgroup_ht40 + [rf][channel - 1]; + } else { + if (pwr_diff_limit[i] > + rtlefuse->pwrgroup_ht20[rf][ + channel - 1]) + pwr_diff_limit[i] = + rtlefuse->pwrgroup_ht20[rf] + [channel - 1]; + } + } + customer_limit = (pwr_diff_limit[3] << 24) | + (pwr_diff_limit[2] << 16) | + (pwr_diff_limit[1] << 8) | + (pwr_diff_limit[0]); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Customer's limit rf(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', customer_limit); + writeval = customer_limit + ((index < 2) ? + powerbase0[rf] : powerbase1[rf]); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Customer, writeval rf(%c)= 0x%x\n", + rf == 0 ? 'A' : 'B', writeval); + break; + default: + chnlgroup = 0; + writeval = rtlphy->mcs_offset[chnlgroup][index + + (rf ? 8 : 0)] + ((index < 2) ? + powerbase0[rf] : powerbase1[rf]); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "RTK better performance, writeval rf(%c) = 0x%x\n", + rf == 0 ? 'A' : 'B', writeval); + break; + } + *(p_outwriteval + rf) = writeval; + } +} + +static void _rtl92d_write_ofdm_power_reg(struct ieee80211_hw *hw, + u8 index, u32 *pvalue) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + static u16 regoffset_a[6] = { + RTXAGC_A_RATE18_06, RTXAGC_A_RATE54_24, + RTXAGC_A_MCS03_MCS00, RTXAGC_A_MCS07_MCS04, + RTXAGC_A_MCS11_MCS08, RTXAGC_A_MCS15_MCS12 + }; + static u16 regoffset_b[6] = { + RTXAGC_B_RATE18_06, RTXAGC_B_RATE54_24, + RTXAGC_B_MCS03_MCS00, RTXAGC_B_MCS07_MCS04, + RTXAGC_B_MCS11_MCS08, RTXAGC_B_MCS15_MCS12 + }; + u8 i, rf, pwr_val[4]; + u32 writeval; + u16 regoffset; + + for (rf = 0; rf < 2; rf++) { + writeval = pvalue[rf]; + for (i = 0; i < 4; i++) { + pwr_val[i] = (u8) ((writeval & (0x7f << + (i * 8))) >> (i * 8)); + if (pwr_val[i] > RF6052_MAX_TX_PWR) + pwr_val[i] = RF6052_MAX_TX_PWR; + } + writeval = (pwr_val[3] << 24) | (pwr_val[2] << 16) | + (pwr_val[1] << 8) | pwr_val[0]; + if (rf == 0) + regoffset = regoffset_a[index]; + else + regoffset = regoffset_b[index]; + rtl_set_bbreg(hw, regoffset, BMASKDWORD, writeval); + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Set 0x%x = %08x\n", regoffset, writeval); + if (((get_rf_type(rtlphy) == RF_2T2R) && + (regoffset == RTXAGC_A_MCS15_MCS12 || + regoffset == RTXAGC_B_MCS15_MCS12)) || + ((get_rf_type(rtlphy) != RF_2T2R) && + (regoffset == RTXAGC_A_MCS07_MCS04 || + regoffset == RTXAGC_B_MCS07_MCS04))) { + writeval = pwr_val[3]; + if (regoffset == RTXAGC_A_MCS15_MCS12 || + regoffset == RTXAGC_A_MCS07_MCS04) + regoffset = 0xc90; + if (regoffset == RTXAGC_B_MCS15_MCS12 || + regoffset == RTXAGC_B_MCS07_MCS04) + regoffset = 0xc98; + for (i = 0; i < 3; i++) { + if (i != 2) + writeval = (writeval > 8) ? + (writeval - 8) : 0; + else + writeval = (writeval > 6) ? + (writeval - 6) : 0; + rtl_write_byte(rtlpriv, (u32) (regoffset + i), + (u8) writeval); + } + } + } +} + +void rtl92d_phy_rf6052_set_ofdm_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel, u8 channel) +{ + u32 writeval[2], powerbase0[2], powerbase1[2]; + u8 index; + + _rtl92d_phy_get_power_base(hw, ppowerlevel, channel, + &powerbase0[0], &powerbase1[0]); + for (index = 0; index < 6; index++) { + _rtl92d_get_txpower_writeval_by_regulatory(hw, + channel, index, &powerbase0[0], + &powerbase1[0], &writeval[0]); + _rtl92d_write_ofdm_power_reg(hw, index, &writeval[0]); + } +} + +bool rtl92d_phy_enable_anotherphy(struct ieee80211_hw *hw, bool bmac0) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = &(rtlpriv->rtlhal); + u8 u1btmp; + u8 direct = bmac0 ? BIT(3) | BIT(2) : BIT(3); + u8 mac_reg = bmac0 ? REG_MAC1 : REG_MAC0; + u8 mac_on_bit = bmac0 ? MAC1_ON : MAC0_ON; + bool bresult = true; /* true: need to enable BB/RF power */ + + rtlhal->during_mac0init_radiob = false; + rtlhal->during_mac1init_radioa = false; + RT_TRACE(rtlpriv, COMP_RF, DBG_LOUD, "===>\n"); + /* MAC0 Need PHY1 load radio_b.txt . Driver use DBI to write. */ + u1btmp = rtl_read_byte(rtlpriv, mac_reg); + if (!(u1btmp & mac_on_bit)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "enable BB & RF\n"); + /* Enable BB and RF power */ + rtl92de_write_dword_dbi(hw, REG_SYS_ISO_CTRL, + rtl92de_read_dword_dbi(hw, REG_SYS_ISO_CTRL, direct) | + BIT(29) | BIT(16) | BIT(17), direct); + } else { + /* We think if MAC1 is ON,then radio_a.txt + * and radio_b.txt has been load. */ + bresult = false; + } + RT_TRACE(rtlpriv, COMP_RF, DBG_LOUD, "<===\n"); + return bresult; + +} + +void rtl92d_phy_powerdown_anotherphy(struct ieee80211_hw *hw, bool bmac0) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = &(rtlpriv->rtlhal); + u8 u1btmp; + u8 direct = bmac0 ? BIT(3) | BIT(2) : BIT(3); + u8 mac_reg = bmac0 ? REG_MAC1 : REG_MAC0; + u8 mac_on_bit = bmac0 ? MAC1_ON : MAC0_ON; + + rtlhal->during_mac0init_radiob = false; + rtlhal->during_mac1init_radioa = false; + RT_TRACE(rtlpriv, COMP_RF, DBG_LOUD, "====>\n"); + /* check MAC0 enable or not again now, if + * enabled, not power down radio A. */ + u1btmp = rtl_read_byte(rtlpriv, mac_reg); + if (!(u1btmp & mac_on_bit)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "power down\n"); + /* power down RF radio A according to YuNan's advice. */ + rtl92de_write_dword_dbi(hw, RFPGA0_XA_LSSIPARAMETER, + 0x00000000, direct); + } + RT_TRACE(rtlpriv, COMP_RF, DBG_LOUD, "<====\n"); +} + +bool rtl92d_phy_rf6052_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + bool rtstatus = true; + struct rtl_hal *rtlhal = &(rtlpriv->rtlhal); + u32 u4_regvalue = 0; + u8 rfpath; + struct bb_reg_def *pphyreg; + bool mac1_initradioa_first = false, mac0_initradiob_first = false; + bool need_pwrdown_radioa = false, need_pwrdown_radiob = false; + bool true_bpath = false; + + if (rtlphy->rf_type == RF_1T1R) + rtlphy->num_total_rfpath = 1; + else + rtlphy->num_total_rfpath = 2; + + /* Single phy mode: use radio_a radio_b config path_A path_B */ + /* seperately by MAC0, and MAC1 needn't configure RF; */ + /* Dual PHY mode:MAC0 use radio_a config 1st phy path_A, */ + /* MAC1 use radio_b config 2nd PHY path_A. */ + /* DMDP,MAC0 on G band,MAC1 on A band. */ + if (rtlhal->macphymode == DUALMAC_DUALPHY) { + if (rtlhal->current_bandtype == BAND_ON_2_4G && + rtlhal->interfaceindex == 0) { + /* MAC0 needs PHY1 load radio_b.txt. + * Driver use DBI to write. */ + if (rtl92d_phy_enable_anotherphy(hw, true)) { + rtlphy->num_total_rfpath = 2; + mac0_initradiob_first = true; + } else { + /* We think if MAC1 is ON,then radio_a.txt and + * radio_b.txt has been load. */ + return rtstatus; + } + } else if (rtlhal->current_bandtype == BAND_ON_5G && + rtlhal->interfaceindex == 1) { + /* MAC1 needs PHY0 load radio_a.txt. + * Driver use DBI to write. */ + if (rtl92d_phy_enable_anotherphy(hw, false)) { + rtlphy->num_total_rfpath = 2; + mac1_initradioa_first = true; + } else { + /* We think if MAC0 is ON,then radio_a.txt and + * radio_b.txt has been load. */ + return rtstatus; + } + } else if (rtlhal->interfaceindex == 1) { + /* MAC0 enabled, only init radia B. */ + true_bpath = true; + } + } + + for (rfpath = 0; rfpath < rtlphy->num_total_rfpath; rfpath++) { + /* Mac1 use PHY0 write */ + if (mac1_initradioa_first) { + if (rfpath == RF90_PATH_A) { + rtlhal->during_mac1init_radioa = true; + need_pwrdown_radioa = true; + } else if (rfpath == RF90_PATH_B) { + rtlhal->during_mac1init_radioa = false; + mac1_initradioa_first = false; + rfpath = RF90_PATH_A; + true_bpath = true; + rtlphy->num_total_rfpath = 1; + } + } else if (mac0_initradiob_first) { + /* Mac0 use PHY1 write */ + if (rfpath == RF90_PATH_A) + rtlhal->during_mac0init_radiob = false; + if (rfpath == RF90_PATH_B) { + rtlhal->during_mac0init_radiob = true; + mac0_initradiob_first = false; + need_pwrdown_radiob = true; + rfpath = RF90_PATH_A; + true_bpath = true; + rtlphy->num_total_rfpath = 1; + } + } + pphyreg = &rtlphy->phyreg_def[rfpath]; + switch (rfpath) { + case RF90_PATH_A: + case RF90_PATH_C: + u4_regvalue = rtl_get_bbreg(hw, pphyreg->rfintfs, + BRFSI_RFENV); + break; + case RF90_PATH_B: + case RF90_PATH_D: + u4_regvalue = rtl_get_bbreg(hw, pphyreg->rfintfs, + BRFSI_RFENV << 16); + break; + } + rtl_set_bbreg(hw, pphyreg->rfintfe, BRFSI_RFENV << 16, 0x1); + udelay(1); + rtl_set_bbreg(hw, pphyreg->rfintfo, BRFSI_RFENV, 0x1); + udelay(1); + /* Set bit number of Address and Data for RF register */ + /* Set 1 to 4 bits for 8255 */ + rtl_set_bbreg(hw, pphyreg->rfhssi_para2, + B3WIREADDRESSLENGTH, 0x0); + udelay(1); + /* Set 0 to 12 bits for 8255 */ + rtl_set_bbreg(hw, pphyreg->rfhssi_para2, B3WIREDATALENGTH, 0x0); + udelay(1); + switch (rfpath) { + case RF90_PATH_A: + if (true_bpath) + rtstatus = rtl92d_phy_config_rf_with_headerfile( + hw, radiob_txt, + (enum radio_path)rfpath); + else + rtstatus = rtl92d_phy_config_rf_with_headerfile( + hw, radioa_txt, + (enum radio_path)rfpath); + break; + case RF90_PATH_B: + rtstatus = + rtl92d_phy_config_rf_with_headerfile(hw, radiob_txt, + (enum radio_path) rfpath); + break; + case RF90_PATH_C: + break; + case RF90_PATH_D: + break; + } + switch (rfpath) { + case RF90_PATH_A: + case RF90_PATH_C: + rtl_set_bbreg(hw, pphyreg->rfintfs, BRFSI_RFENV, + u4_regvalue); + break; + case RF90_PATH_B: + case RF90_PATH_D: + rtl_set_bbreg(hw, pphyreg->rfintfs, BRFSI_RFENV << 16, + u4_regvalue); + break; + } + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Radio[%d] Fail!!", rfpath); + goto phy_rf_cfg_fail; + } + + } + + /* check MAC0 enable or not again, if enabled, + * not power down radio A. */ + /* check MAC1 enable or not again, if enabled, + * not power down radio B. */ + if (need_pwrdown_radioa) + rtl92d_phy_powerdown_anotherphy(hw, false); + else if (need_pwrdown_radiob) + rtl92d_phy_powerdown_anotherphy(hw, true); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "<---\n"); + return rtstatus; + +phy_rf_cfg_fail: + return rtstatus; +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/rf.h b/drivers/net/wireless/rtlwifi/rtl8192de/rf.h new file mode 100644 index 0000000..0fe1a48 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/rf.h @@ -0,0 +1,44 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92D_RF_H__ +#define __RTL92D_RF_H__ + +extern void rtl92d_phy_rf6052_set_bandwidth(struct ieee80211_hw *hw, + u8 bandwidth); +extern void rtl92d_phy_rf6052_set_cck_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel); +extern void rtl92d_phy_rf6052_set_ofdm_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel, u8 channel); +extern bool rtl92d_phy_rf6052_config(struct ieee80211_hw *hw); +extern bool rtl92d_phy_enable_anotherphy(struct ieee80211_hw *hw, bool bmac0); +extern void rtl92d_phy_powerdown_anotherphy(struct ieee80211_hw *hw, + bool bmac0); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/sw.c b/drivers/net/wireless/rtlwifi/rtl8192de/sw.c new file mode 100644 index 0000000..5e9d41d --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/sw.c @@ -0,0 +1,425 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../core.h" +#include "../pci.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "dm.h" +#include "hw.h" +#include "sw.h" +#include "trx.h" +#include "led.h" + +#include + +static void rtl92d_init_aspm_vars(struct ieee80211_hw *hw) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + /*close ASPM for AMD defaultly */ + rtlpci->const_amdpci_aspm = 0; + + /* + * ASPM PS mode. + * 0 - Disable ASPM, + * 1 - Enable ASPM without Clock Req, + * 2 - Enable ASPM with Clock Req, + * 3 - Alwyas Enable ASPM with Clock Req, + * 4 - Always Enable ASPM without Clock Req. + * set defult to RTL8192CE:3 RTL8192E:2 + * */ + rtlpci->const_pci_aspm = 3; + + /*Setting for PCI-E device */ + rtlpci->const_devicepci_aspm_setting = 0x03; + + /*Setting for PCI-E bridge */ + rtlpci->const_hostpci_aspm_setting = 0x02; + + /* + * In Hw/Sw Radio Off situation. + * 0 - Default, + * 1 - From ASPM setting without low Mac Pwr, + * 2 - From ASPM setting with low Mac Pwr, + * 3 - Bus D3 + * set default to RTL8192CE:0 RTL8192SE:2 + */ + rtlpci->const_hwsw_rfoff_d3 = 0; + + /* + * This setting works for those device with + * backdoor ASPM setting such as EPHY setting. + * 0 - Not support ASPM, + * 1 - Support ASPM, + * 2 - According to chipset. + */ + rtlpci->const_support_pciaspm = 1; +} + +static int rtl92d_init_sw_vars(struct ieee80211_hw *hw) +{ + int err; + u8 tid; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + rtlpriv->dm.dm_initialgain_enable = true; + rtlpriv->dm.dm_flag = 0; + rtlpriv->dm.disable_framebursting = false; + rtlpriv->dm.thermalvalue = 0; + rtlpriv->dm.useramask = true; + + /* dual mac */ + if (rtlpriv->rtlhal.current_bandtype == BAND_ON_5G) + rtlpriv->phy.current_channel = 36; + else + rtlpriv->phy.current_channel = 1; + + if (rtlpriv->rtlhal.macphymode != SINGLEMAC_SINGLEPHY) { + rtlpriv->rtlhal.disable_amsdu_8k = true; + /* No long RX - reduce fragmentation */ + rtlpci->rxbuffersize = 4096; + } + + rtlpci->transmit_config = CFENDFORM | BIT(12) | BIT(13); + + rtlpci->receive_config = ( + RCR_APPFCS + | RCR_AMF + | RCR_ADF + | RCR_APP_MIC + | RCR_APP_ICV + | RCR_AICV + | RCR_ACRC32 + | RCR_AB + | RCR_AM + | RCR_APM + | RCR_APP_PHYST_RXFF + | RCR_HTC_LOC_CTRL + ); + + rtlpci->irq_mask[0] = (u32) ( + IMR_ROK + | IMR_VODOK + | IMR_VIDOK + | IMR_BEDOK + | IMR_BKDOK + | IMR_MGNTDOK + | IMR_HIGHDOK + | IMR_BDOK + | IMR_RDU + | IMR_RXFOVW + ); + + rtlpci->irq_mask[1] = (u32) (IMR_CPWM | IMR_C2HCMD); + + /* for debug level */ + rtlpriv->dbg.global_debuglevel = rtlpriv->cfg->mod_params->debug; + /* for LPS & IPS */ + rtlpriv->psc.inactiveps = rtlpriv->cfg->mod_params->inactiveps; + rtlpriv->psc.swctrl_lps = rtlpriv->cfg->mod_params->swctrl_lps; + rtlpriv->psc.fwctrl_lps = rtlpriv->cfg->mod_params->fwctrl_lps; + if (!rtlpriv->psc.inactiveps) + pr_info("Power Save off (module option)\n"); + if (!rtlpriv->psc.fwctrl_lps) + pr_info("FW Power Save off (module option)\n"); + rtlpriv->psc.reg_fwctrl_lps = 3; + rtlpriv->psc.reg_max_lps_awakeintvl = 5; + /* for ASPM, you can close aspm through + * set const_support_pciaspm = 0 */ + rtl92d_init_aspm_vars(hw); + + if (rtlpriv->psc.reg_fwctrl_lps == 1) + rtlpriv->psc.fwctrl_psmode = FW_PS_MIN_MODE; + else if (rtlpriv->psc.reg_fwctrl_lps == 2) + rtlpriv->psc.fwctrl_psmode = FW_PS_MAX_MODE; + else if (rtlpriv->psc.reg_fwctrl_lps == 3) + rtlpriv->psc.fwctrl_psmode = FW_PS_DTIM_MODE; + + /* for early mode */ + rtlpriv->rtlhal.earlymode_enable = false; + for (tid = 0; tid < 8; tid++) + skb_queue_head_init(&rtlpriv->mac80211.skb_waitq[tid]); + + /* for firmware buf */ + rtlpriv->rtlhal.pfirmware = vzalloc(0x8000); + if (!rtlpriv->rtlhal.pfirmware) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Can't alloc buffer for fw\n"); + return 1; + } + + rtlpriv->max_fw_size = 0x8000; + pr_info("Driver for Realtek RTL8192DE WLAN interface\n"); + pr_info("Loading firmware file %s\n", rtlpriv->cfg->fw_name); + + /* request fw */ + err = request_firmware_nowait(THIS_MODULE, 1, rtlpriv->cfg->fw_name, + rtlpriv->io.dev, GFP_KERNEL, hw, + rtl_fw_cb); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Failed to request firmware!\n"); + return 1; + } + + return 0; +} + +static void rtl92d_deinit_sw_vars(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tid; + + if (rtlpriv->rtlhal.pfirmware) { + vfree(rtlpriv->rtlhal.pfirmware); + rtlpriv->rtlhal.pfirmware = NULL; + } + for (tid = 0; tid < 8; tid++) + skb_queue_purge(&rtlpriv->mac80211.skb_waitq[tid]); +} + +static struct rtl_hal_ops rtl8192de_hal_ops = { + .init_sw_vars = rtl92d_init_sw_vars, + .deinit_sw_vars = rtl92d_deinit_sw_vars, + .read_eeprom_info = rtl92de_read_eeprom_info, + .interrupt_recognized = rtl92de_interrupt_recognized, + .hw_init = rtl92de_hw_init, + .hw_disable = rtl92de_card_disable, + .hw_suspend = rtl92de_suspend, + .hw_resume = rtl92de_resume, + .enable_interrupt = rtl92de_enable_interrupt, + .disable_interrupt = rtl92de_disable_interrupt, + .set_network_type = rtl92de_set_network_type, + .set_chk_bssid = rtl92de_set_check_bssid, + .set_qos = rtl92de_set_qos, + .set_bcn_reg = rtl92de_set_beacon_related_registers, + .set_bcn_intv = rtl92de_set_beacon_interval, + .update_interrupt_mask = rtl92de_update_interrupt_mask, + .get_hw_reg = rtl92de_get_hw_reg, + .set_hw_reg = rtl92de_set_hw_reg, + .update_rate_tbl = rtl92de_update_hal_rate_tbl, + .fill_tx_desc = rtl92de_tx_fill_desc, + .fill_tx_cmddesc = rtl92de_tx_fill_cmddesc, + .query_rx_desc = rtl92de_rx_query_desc, + .set_channel_access = rtl92de_update_channel_access_setting, + .radio_onoff_checking = rtl92de_gpio_radio_on_off_checking, + .set_bw_mode = rtl92d_phy_set_bw_mode, + .switch_channel = rtl92d_phy_sw_chnl, + .dm_watchdog = rtl92d_dm_watchdog, + .scan_operation_backup = rtl92d_phy_scan_operation_backup, + .set_rf_power_state = rtl92d_phy_set_rf_power_state, + .led_control = rtl92de_led_control, + .set_desc = rtl92de_set_desc, + .get_desc = rtl92de_get_desc, + .tx_polling = rtl92de_tx_polling, + .enable_hw_sec = rtl92de_enable_hw_security_config, + .set_key = rtl92de_set_key, + .init_sw_leds = rtl92de_init_sw_leds, + .get_bbreg = rtl92d_phy_query_bb_reg, + .set_bbreg = rtl92d_phy_set_bb_reg, + .get_rfreg = rtl92d_phy_query_rf_reg, + .set_rfreg = rtl92d_phy_set_rf_reg, + .linked_set_reg = rtl92d_linked_set_reg, +}; + +static struct rtl_mod_params rtl92de_mod_params = { + .sw_crypto = false, + .inactiveps = true, + .swctrl_lps = true, + .fwctrl_lps = false, + .debug = DBG_EMERG, +}; + +static struct rtl_hal_cfg rtl92de_hal_cfg = { + .bar_id = 2, + .write_readback = true, + .name = "rtl8192de", + .fw_name = "rtlwifi/rtl8192defw.bin", + .ops = &rtl8192de_hal_ops, + .mod_params = &rtl92de_mod_params, + + .maps[SYS_ISO_CTRL] = REG_SYS_ISO_CTRL, + .maps[SYS_FUNC_EN] = REG_SYS_FUNC_EN, + .maps[SYS_CLK] = REG_SYS_CLKR, + .maps[MAC_RCR_AM] = RCR_AM, + .maps[MAC_RCR_AB] = RCR_AB, + .maps[MAC_RCR_ACRC32] = RCR_ACRC32, + .maps[MAC_RCR_ACF] = RCR_ACF, + .maps[MAC_RCR_AAP] = RCR_AAP, + + .maps[EFUSE_TEST] = REG_EFUSE_TEST, + .maps[EFUSE_CTRL] = REG_EFUSE_CTRL, + .maps[EFUSE_CLK] = 0, /* just for 92se */ + .maps[EFUSE_CLK_CTRL] = REG_EFUSE_CTRL, + .maps[EFUSE_PWC_EV12V] = PWC_EV12V, + .maps[EFUSE_FEN_ELDR] = FEN_ELDR, + .maps[EFUSE_LOADER_CLK_EN] = LOADER_CLK_EN, + .maps[EFUSE_ANA8M] = 0, /* just for 92se */ + .maps[EFUSE_HWSET_MAX_SIZE] = HWSET_MAX_SIZE, + .maps[EFUSE_MAX_SECTION_MAP] = EFUSE_MAX_SECTION, + .maps[EFUSE_REAL_CONTENT_SIZE] = EFUSE_REAL_CONTENT_LEN, + + .maps[RWCAM] = REG_CAMCMD, + .maps[WCAMI] = REG_CAMWRITE, + .maps[RCAMO] = REG_CAMREAD, + .maps[CAMDBG] = REG_CAMDBG, + .maps[SECR] = REG_SECCFG, + .maps[SEC_CAM_NONE] = CAM_NONE, + .maps[SEC_CAM_WEP40] = CAM_WEP40, + .maps[SEC_CAM_TKIP] = CAM_TKIP, + .maps[SEC_CAM_AES] = CAM_AES, + .maps[SEC_CAM_WEP104] = CAM_WEP104, + + .maps[RTL_IMR_BCNDMAINT6] = IMR_BCNDMAINT6, + .maps[RTL_IMR_BCNDMAINT5] = IMR_BCNDMAINT5, + .maps[RTL_IMR_BCNDMAINT4] = IMR_BCNDMAINT4, + .maps[RTL_IMR_BCNDMAINT3] = IMR_BCNDMAINT3, + .maps[RTL_IMR_BCNDMAINT2] = IMR_BCNDMAINT2, + .maps[RTL_IMR_BCNDMAINT1] = IMR_BCNDMAINT1, + .maps[RTL_IMR_BCNDOK8] = IMR_BCNDOK8, + .maps[RTL_IMR_BCNDOK7] = IMR_BCNDOK7, + .maps[RTL_IMR_BCNDOK6] = IMR_BCNDOK6, + .maps[RTL_IMR_BCNDOK5] = IMR_BCNDOK5, + .maps[RTL_IMR_BCNDOK4] = IMR_BCNDOK4, + .maps[RTL_IMR_BCNDOK3] = IMR_BCNDOK3, + .maps[RTL_IMR_BCNDOK2] = IMR_BCNDOK2, + .maps[RTL_IMR_BCNDOK1] = IMR_BCNDOK1, + .maps[RTL_IMR_TIMEOUT2] = IMR_TIMEOUT2, + .maps[RTL_IMR_TIMEOUT1] = IMR_TIMEOUT1, + + .maps[RTL_IMR_TXFOVW] = IMR_TXFOVW, + .maps[RTL_IMR_PSTIMEOUT] = IMR_PSTIMEOUT, + .maps[RTL_IMR_BCNINT] = IMR_BCNINT, + .maps[RTL_IMR_RXFOVW] = IMR_RXFOVW, + .maps[RTL_IMR_RDU] = IMR_RDU, + .maps[RTL_IMR_ATIMEND] = IMR_ATIMEND, + .maps[RTL_IMR_BDOK] = IMR_BDOK, + .maps[RTL_IMR_MGNTDOK] = IMR_MGNTDOK, + .maps[RTL_IMR_TBDER] = IMR_TBDER, + .maps[RTL_IMR_HIGHDOK] = IMR_HIGHDOK, + .maps[RTL_IMR_TBDOK] = IMR_TBDOK, + .maps[RTL_IMR_BKDOK] = IMR_BKDOK, + .maps[RTL_IMR_BEDOK] = IMR_BEDOK, + .maps[RTL_IMR_VIDOK] = IMR_VIDOK, + .maps[RTL_IMR_VODOK] = IMR_VODOK, + .maps[RTL_IMR_ROK] = IMR_ROK, + .maps[RTL_IBSS_INT_MASKS] = (IMR_BCNINT | IMR_TBDOK | IMR_TBDER), + + .maps[RTL_RC_CCK_RATE1M] = DESC92_RATE1M, + .maps[RTL_RC_CCK_RATE2M] = DESC92_RATE2M, + .maps[RTL_RC_CCK_RATE5_5M] = DESC92_RATE5_5M, + .maps[RTL_RC_CCK_RATE11M] = DESC92_RATE11M, + .maps[RTL_RC_OFDM_RATE6M] = DESC92_RATE6M, + .maps[RTL_RC_OFDM_RATE9M] = DESC92_RATE9M, + .maps[RTL_RC_OFDM_RATE12M] = DESC92_RATE12M, + .maps[RTL_RC_OFDM_RATE18M] = DESC92_RATE18M, + .maps[RTL_RC_OFDM_RATE24M] = DESC92_RATE24M, + .maps[RTL_RC_OFDM_RATE36M] = DESC92_RATE36M, + .maps[RTL_RC_OFDM_RATE48M] = DESC92_RATE48M, + .maps[RTL_RC_OFDM_RATE54M] = DESC92_RATE54M, + + .maps[RTL_RC_HT_RATEMCS7] = DESC92_RATEMCS7, + .maps[RTL_RC_HT_RATEMCS15] = DESC92_RATEMCS15, +}; + +static struct pci_device_id rtl92de_pci_ids[] = { + {RTL_PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8193, rtl92de_hal_cfg)}, + {RTL_PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x002B, rtl92de_hal_cfg)}, + {}, +}; + +MODULE_DEVICE_TABLE(pci, rtl92de_pci_ids); + +MODULE_AUTHOR("lizhaoming "); +MODULE_AUTHOR("Realtek WlanFAE "); +MODULE_AUTHOR("Larry Finger "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Realtek 8192DE 802.11n Dual Mac PCI wireless"); +MODULE_FIRMWARE("rtlwifi/rtl8192defw.bin"); + +module_param_named(swenc, rtl92de_mod_params.sw_crypto, bool, 0444); +module_param_named(debug, rtl92de_mod_params.debug, int, 0444); +module_param_named(ips, rtl92de_mod_params.inactiveps, bool, 0444); +module_param_named(swlps, rtl92de_mod_params.swctrl_lps, bool, 0444); +module_param_named(fwlps, rtl92de_mod_params.fwctrl_lps, bool, 0444); +MODULE_PARM_DESC(swenc, "Set to 1 for software crypto (default 0)\n"); +MODULE_PARM_DESC(ips, "Set to 0 to not use link power save (default 1)\n"); +MODULE_PARM_DESC(swlps, "Set to 1 to use SW control power save (default 0)\n"); +MODULE_PARM_DESC(fwlps, "Set to 1 to use FW control power save (default 1)\n"); +MODULE_PARM_DESC(debug, "Set debug level (0-5) (default 0)"); + +compat_pci_suspend(rtl_pci_suspend); +compat_pci_resume(rtl_pci_resume); + +static SIMPLE_DEV_PM_OPS(rtlwifi_pm_ops, rtl_pci_suspend, rtl_pci_resume); + +static struct pci_driver rtl92de_driver = { + .name = KBUILD_MODNAME, + .id_table = rtl92de_pci_ids, + .probe = rtl_pci_probe, + .remove = rtl_pci_disconnect, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) + .driver.pm = &rtlwifi_pm_ops, +#elif defined(CONFIG_PM_SLEEP) + .suspend = rtl_pci_suspend_compat, + .resume = rtl_pci_resume_compat, +#endif +}; + +/* add global spin lock to solve the problem that + * Dul mac register operation on the same time */ +spinlock_t globalmutex_power; +spinlock_t globalmutex_for_fwdownload; +spinlock_t globalmutex_for_power_and_efuse; + +static int __init rtl92de_module_init(void) +{ + int ret = 0; + + spin_lock_init(&globalmutex_power); + spin_lock_init(&globalmutex_for_fwdownload); + spin_lock_init(&globalmutex_for_power_and_efuse); + + ret = pci_register_driver(&rtl92de_driver); + if (ret) + RT_ASSERT(false, "No device found\n"); + return ret; +} + +static void __exit rtl92de_module_exit(void) +{ + pci_unregister_driver(&rtl92de_driver); +} + +module_init(rtl92de_module_init); +module_exit(rtl92de_module_exit); diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/sw.h b/drivers/net/wireless/rtlwifi/rtl8192de/sw.h new file mode 100644 index 0000000..0e6035b --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/sw.h @@ -0,0 +1,37 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92DE_SW_H__ +#define __RTL92DE_SW_H__ + +extern spinlock_t globalmutex_power; +extern spinlock_t globalmutex_for_fwdownload; +extern spinlock_t globalmutex_for_power_and_efuse; + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/table.c b/drivers/net/wireless/rtlwifi/rtl8192de/table.c new file mode 100644 index 0000000..8ea6f52 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/table.c @@ -0,0 +1,1690 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + * Created on 2010/12/23, 6:38 + *****************************************************************************/ + +#include + +#include "table.h" + +u32 rtl8192de_phy_reg_2tarray[PHY_REG_2T_ARRAYLENGTH] = { + 0x024, 0x0011800d, + 0x028, 0x00ffdb83, + 0x014, 0x088ba955, + 0x010, 0x49022b03, + 0x800, 0x80040002, + 0x804, 0x00000003, + 0x808, 0x0000fc00, + 0x80c, 0x0000000a, + 0x810, 0x80706388, + 0x814, 0x020c3d10, + 0x818, 0x02200385, + 0x81c, 0x00000000, + 0x820, 0x01000100, + 0x824, 0x00390004, + 0x828, 0x01000100, + 0x82c, 0x00390004, + 0x830, 0x27272727, + 0x834, 0x27272727, + 0x838, 0x27272727, + 0x83c, 0x27272727, + 0x840, 0x00010000, + 0x844, 0x00010000, + 0x848, 0x27272727, + 0x84c, 0x27272727, + 0x850, 0x00000000, + 0x854, 0x00000000, + 0x858, 0x569a569a, + 0x85c, 0x0c1b25a4, + 0x860, 0x66e60230, + 0x864, 0x061f0130, + 0x868, 0x27272727, + 0x86c, 0x272b2b2b, + 0x870, 0x07000700, + 0x874, 0x22188000, + 0x878, 0x08080808, + 0x87c, 0x00007ff8, + 0x880, 0xc0083070, + 0x884, 0x00000cd5, + 0x888, 0x00000000, + 0x88c, 0xcc0000c0, + 0x890, 0x00000800, + 0x894, 0xfffffffe, + 0x898, 0x40302010, + 0x89c, 0x00706050, + 0x900, 0x00000000, + 0x904, 0x00000023, + 0x908, 0x00000000, + 0x90c, 0x81121313, + 0xa00, 0x00d047c8, + 0xa04, 0x80ff000c, + 0xa08, 0x8c838300, + 0xa0c, 0x2e68120f, + 0xa10, 0x9500bb78, + 0xa14, 0x11144028, + 0xa18, 0x00881117, + 0xa1c, 0x89140f00, + 0xa20, 0x1a1b0000, + 0xa24, 0x090e1317, + 0xa28, 0x00000204, + 0xa2c, 0x00d30000, + 0xa70, 0x101fbf00, + 0xa74, 0x00000007, + 0xc00, 0x40071d40, + 0xc04, 0x03a05633, + 0xc08, 0x001000e4, + 0xc0c, 0x6c6c6c6c, + 0xc10, 0x08800000, + 0xc14, 0x40000100, + 0xc18, 0x08800000, + 0xc1c, 0x40000100, + 0xc20, 0x00000000, + 0xc24, 0x00000000, + 0xc28, 0x00000000, + 0xc2c, 0x00000000, + 0xc30, 0x69e9ac44, + 0xc34, 0x469652cf, + 0xc38, 0x49795994, + 0xc3c, 0x0a979718, + 0xc40, 0x1f7c403f, + 0xc44, 0x000100b7, + 0xc48, 0xec020107, + 0xc4c, 0x007f037f, + 0xc50, 0x69543420, + 0xc54, 0x43bc009e, + 0xc58, 0x69543420, + 0xc5c, 0x433c00a8, + 0xc60, 0x00000000, + 0xc64, 0x5116848b, + 0xc68, 0x47c00bff, + 0xc6c, 0x00000036, + 0xc70, 0x2c7f000d, + 0xc74, 0x058610db, + 0xc78, 0x0000001f, + 0xc7c, 0x40b95612, + 0xc80, 0x40000100, + 0xc84, 0x20f60000, + 0xc88, 0x40000100, + 0xc8c, 0x20e00000, + 0xc90, 0x00121820, + 0xc94, 0x00000007, + 0xc98, 0x00121820, + 0xc9c, 0x00007f7f, + 0xca0, 0x00000000, + 0xca4, 0x00000080, + 0xca8, 0x00000000, + 0xcac, 0x00000000, + 0xcb0, 0x00000000, + 0xcb4, 0x00000000, + 0xcb8, 0x00000000, + 0xcbc, 0x28000000, + 0xcc0, 0x00000000, + 0xcc4, 0x00000000, + 0xcc8, 0x00000000, + 0xccc, 0x00000000, + 0xcd0, 0x00000000, + 0xcd4, 0x00000000, + 0xcd8, 0x64b11e20, + 0xcdc, 0xe8767533, + 0xce0, 0x00222222, + 0xce4, 0x00000000, + 0xce8, 0x37644302, + 0xcec, 0x2f97d40c, + 0xd00, 0x00080740, + 0xd04, 0x00020403, + 0xd08, 0x0000907f, + 0xd0c, 0x20010201, + 0xd10, 0xa0633333, + 0xd14, 0x3333bc43, + 0xd18, 0x7a8f5b6b, + 0xd2c, 0xcc979975, + 0xd30, 0x00000000, + 0xd34, 0x80608404, + 0xd38, 0x00000000, + 0xd3c, 0x00027293, + 0xd40, 0x00000000, + 0xd44, 0x00000000, + 0xd48, 0x00000000, + 0xd4c, 0x00000000, + 0xd50, 0x6437140a, + 0xd54, 0x00000000, + 0xd58, 0x00000000, + 0xd5c, 0x30032064, + 0xd60, 0x4653de68, + 0xd64, 0x04518a3c, + 0xd68, 0x00002101, + 0xd6c, 0x2a201c16, + 0xd70, 0x1812362e, + 0xd74, 0x322c2220, + 0xd78, 0x000e3c24, + 0xe00, 0x2a2a2a2a, + 0xe04, 0x2a2a2a2a, + 0xe08, 0x03902a2a, + 0xe10, 0x2a2a2a2a, + 0xe14, 0x2a2a2a2a, + 0xe18, 0x2a2a2a2a, + 0xe1c, 0x2a2a2a2a, + 0xe28, 0x00000000, + 0xe30, 0x1000dc1f, + 0xe34, 0x10008c1f, + 0xe38, 0x02140102, + 0xe3c, 0x681604c2, + 0xe40, 0x01007c00, + 0xe44, 0x01004800, + 0xe48, 0xfb000000, + 0xe4c, 0x000028d1, + 0xe50, 0x1000dc1f, + 0xe54, 0x10008c1f, + 0xe58, 0x02140102, + 0xe5c, 0x28160d05, + 0xe60, 0x00000010, + 0xe68, 0x001b25a4, + 0xe6c, 0x63db25a4, + 0xe70, 0x63db25a4, + 0xe74, 0x0c126da4, + 0xe78, 0x0c126da4, + 0xe7c, 0x0c126da4, + 0xe80, 0x0c126da4, + 0xe84, 0x63db25a4, + 0xe88, 0x0c126da4, + 0xe8c, 0x63db25a4, + 0xed0, 0x63db25a4, + 0xed4, 0x63db25a4, + 0xed8, 0x63db25a4, + 0xedc, 0x001b25a4, + 0xee0, 0x001b25a4, + 0xeec, 0x6fdb25a4, + 0xf14, 0x00000003, + 0xf1c, 0x00000064, + 0xf4c, 0x00000004, + 0xf00, 0x00000300, +}; + +u32 rtl8192de_phy_reg_array_pg[PHY_REG_ARRAY_PG_LENGTH] = { + 0xe00, 0xffffffff, 0x07090c0c, + 0xe04, 0xffffffff, 0x01020405, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x0b0c0c0e, + 0xe14, 0xffffffff, 0x01030506, + 0xe18, 0xffffffff, 0x0b0c0d0e, + 0xe1c, 0xffffffff, 0x01030509, + 0x830, 0xffffffff, 0x07090c0c, + 0x834, 0xffffffff, 0x01020405, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x0b0c0c0e, + 0x848, 0xffffffff, 0x01030506, + 0x84c, 0xffffffff, 0x0b0c0d0e, + 0x868, 0xffffffff, 0x01030509, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x04040404, + 0xe04, 0xffffffff, 0x00020204, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x06060606, + 0xe14, 0xffffffff, 0x00020406, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x04040404, + 0x834, 0xffffffff, 0x00020204, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x06060606, + 0x848, 0xffffffff, 0x00020406, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x04040404, + 0xe04, 0xffffffff, 0x00020204, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x04040404, + 0x834, 0xffffffff, 0x00020204, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x04040404, + 0xe04, 0xffffffff, 0x00020204, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x08080808, + 0xe14, 0xffffffff, 0x00040408, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x04040404, + 0x834, 0xffffffff, 0x00020204, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x08080808, + 0x848, 0xffffffff, 0x00040408, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x04040404, + 0xe04, 0xffffffff, 0x00020204, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x08080808, + 0xe14, 0xffffffff, 0x00040408, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x04040404, + 0x834, 0xffffffff, 0x00020204, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x08080808, + 0x848, 0xffffffff, 0x00040408, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x04040404, + 0xe04, 0xffffffff, 0x00020204, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x08080808, + 0xe14, 0xffffffff, 0x00040408, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x04040404, + 0x834, 0xffffffff, 0x00020204, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x08080808, + 0x848, 0xffffffff, 0x00040408, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x04040404, + 0xe04, 0xffffffff, 0x00020204, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x08080808, + 0xe14, 0xffffffff, 0x00040408, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x04040404, + 0x834, 0xffffffff, 0x00020204, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x08080808, + 0x848, 0xffffffff, 0x00040408, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x04040404, + 0xe04, 0xffffffff, 0x00020204, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x08080808, + 0xe14, 0xffffffff, 0x00040408, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x04040404, + 0x834, 0xffffffff, 0x00020204, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x08080808, + 0x848, 0xffffffff, 0x00040408, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x04040404, + 0xe04, 0xffffffff, 0x00020204, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x08080808, + 0xe14, 0xffffffff, 0x00040408, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x04040404, + 0x834, 0xffffffff, 0x00020204, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x08080808, + 0x848, 0xffffffff, 0x00040408, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, +}; + +u32 rtl8192de_radioa_2tarray[RADIOA_2T_ARRAYLENGTH] = { + 0x000, 0x00030000, + 0x001, 0x00030000, + 0x002, 0x00000000, + 0x003, 0x00018c63, + 0x004, 0x00018c63, + 0x008, 0x00084000, + 0x00b, 0x0001c000, + 0x00e, 0x00018c67, + 0x00f, 0x00000851, + 0x014, 0x00021440, + 0x018, 0x00017524, + 0x019, 0x00000000, + 0x01d, 0x000a1290, + 0x023, 0x00001558, + 0x01a, 0x00030a99, + 0x01b, 0x00040b00, + 0x01c, 0x000fc339, + 0x03a, 0x000a57eb, + 0x03b, 0x00020000, + 0x03c, 0x000ff454, + 0x020, 0x0000aa52, + 0x021, 0x00054000, + 0x040, 0x0000aa52, + 0x041, 0x00014000, + 0x025, 0x000803be, + 0x026, 0x000fc638, + 0x027, 0x00077c18, + 0x028, 0x000de471, + 0x029, 0x000d7110, + 0x02a, 0x0008cb04, + 0x02b, 0x0004128b, + 0x02c, 0x00001840, + 0x043, 0x0002444f, + 0x044, 0x0001adb0, + 0x045, 0x00056467, + 0x046, 0x0008992c, + 0x047, 0x0000452c, + 0x048, 0x000f9c43, + 0x049, 0x00002e0c, + 0x04a, 0x000546eb, + 0x04b, 0x0008966c, + 0x04c, 0x0000dde9, + 0x018, 0x00007401, + 0x000, 0x00070000, + 0x012, 0x000dc000, + 0x012, 0x00090000, + 0x012, 0x00051000, + 0x012, 0x00012000, + 0x013, 0x000287b7, + 0x013, 0x000247ab, + 0x013, 0x0002079f, + 0x013, 0x0001c793, + 0x013, 0x0001839b, + 0x013, 0x00014392, + 0x013, 0x0001019a, + 0x013, 0x0000c191, + 0x013, 0x00008194, + 0x013, 0x000040a0, + 0x013, 0x00000018, + 0x015, 0x0000f424, + 0x015, 0x0004f424, + 0x015, 0x0008f424, + 0x016, 0x000e1330, + 0x016, 0x000a1330, + 0x016, 0x00061330, + 0x016, 0x00021330, + 0x018, 0x00017524, + 0x000, 0x00070000, + 0x012, 0x000cf000, + 0x012, 0x000bc000, + 0x012, 0x00078000, + 0x012, 0x00000000, + 0x013, 0x000287bc, + 0x013, 0x000247b0, + 0x013, 0x000203b4, + 0x013, 0x0001c3a8, + 0x013, 0x000181b4, + 0x013, 0x000141a8, + 0x013, 0x000100b0, + 0x013, 0x0000c0a4, + 0x013, 0x0000b02c, + 0x013, 0x00004020, + 0x013, 0x00000014, + 0x015, 0x0000f4c3, + 0x015, 0x0004f4c3, + 0x015, 0x0008f4c3, + 0x016, 0x000e085f, + 0x016, 0x000a085f, + 0x016, 0x0006085f, + 0x016, 0x0002085f, + 0x018, 0x00037524, + 0x000, 0x00070000, + 0x012, 0x000cf000, + 0x012, 0x000bc000, + 0x012, 0x00078000, + 0x012, 0x00000000, + 0x013, 0x000287bc, + 0x013, 0x000247b0, + 0x013, 0x000203b4, + 0x013, 0x0001c3a8, + 0x013, 0x000181b4, + 0x013, 0x000141a8, + 0x013, 0x000100b0, + 0x013, 0x0000c0a4, + 0x013, 0x0000b02c, + 0x013, 0x00004020, + 0x013, 0x00000014, + 0x015, 0x0000f4c3, + 0x015, 0x0004f4c3, + 0x015, 0x0008f4c3, + 0x016, 0x000e085f, + 0x016, 0x000a085f, + 0x016, 0x0006085f, + 0x016, 0x0002085f, + 0x018, 0x00057568, + 0x000, 0x00070000, + 0x012, 0x000cf000, + 0x012, 0x000bc000, + 0x012, 0x00078000, + 0x012, 0x00000000, + 0x013, 0x000287bc, + 0x013, 0x000247b0, + 0x013, 0x000203b4, + 0x013, 0x0001c3a8, + 0x013, 0x000181b4, + 0x013, 0x000141a8, + 0x013, 0x000100b0, + 0x013, 0x0000c0a4, + 0x013, 0x0000b02c, + 0x013, 0x00004020, + 0x013, 0x00000014, + 0x015, 0x0000f4c3, + 0x015, 0x0004f4c3, + 0x015, 0x0008f4c3, + 0x016, 0x000e085f, + 0x016, 0x000a085f, + 0x016, 0x0006085f, + 0x016, 0x0002085f, + 0x030, 0x0004470f, + 0x031, 0x00044ff0, + 0x032, 0x00000070, + 0x033, 0x000dd480, + 0x034, 0x000ffac0, + 0x035, 0x000b80c0, + 0x036, 0x00077000, + 0x037, 0x00064ff2, + 0x038, 0x000e7661, + 0x039, 0x00000e90, + 0x000, 0x00030000, + 0x018, 0x0000f401, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x01e, 0x00088009, + 0x01f, 0x00080003, + 0x0fe, 0x00000000, + 0x01e, 0x00088001, + 0x01f, 0x00080000, + 0x0fe, 0x00000000, + 0x018, 0x00097524, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x02b, 0x00041289, + 0x0fe, 0x00000000, + 0x02d, 0x0006aaaa, + 0x02e, 0x000b4d01, + 0x02d, 0x00080000, + 0x02e, 0x00004d02, + 0x02d, 0x00095555, + 0x02e, 0x00054d03, + 0x02d, 0x000aaaaa, + 0x02e, 0x000b4d04, + 0x02d, 0x000c0000, + 0x02e, 0x00004d05, + 0x02d, 0x000d5555, + 0x02e, 0x00054d06, + 0x02d, 0x000eaaaa, + 0x02e, 0x000b4d07, + 0x02d, 0x00000000, + 0x02e, 0x00005108, + 0x02d, 0x00015555, + 0x02e, 0x00055109, + 0x02d, 0x0002aaaa, + 0x02e, 0x000b510a, + 0x02d, 0x00040000, + 0x02e, 0x0000510b, + 0x02d, 0x00055555, + 0x02e, 0x0005510c, +}; + +u32 rtl8192de_radiob_2tarray[RADIOB_2T_ARRAYLENGTH] = { + 0x000, 0x00030000, + 0x001, 0x00030000, + 0x002, 0x00000000, + 0x003, 0x00018c63, + 0x004, 0x00018c63, + 0x008, 0x00084000, + 0x00b, 0x0001c000, + 0x00e, 0x00018c67, + 0x00f, 0x00000851, + 0x014, 0x00021440, + 0x018, 0x00007401, + 0x019, 0x00000060, + 0x01d, 0x000a1290, + 0x023, 0x00001558, + 0x01a, 0x00030a99, + 0x01b, 0x00040b00, + 0x01c, 0x000fc339, + 0x03a, 0x000a57eb, + 0x03b, 0x00020000, + 0x03c, 0x000ff454, + 0x020, 0x0000aa52, + 0x021, 0x00054000, + 0x040, 0x0000aa52, + 0x041, 0x00014000, + 0x025, 0x000803be, + 0x026, 0x000fc638, + 0x027, 0x00077c18, + 0x028, 0x000d1c31, + 0x029, 0x000d7110, + 0x02a, 0x000aeb04, + 0x02b, 0x0004128b, + 0x02c, 0x00001840, + 0x043, 0x0002444f, + 0x044, 0x0001adb0, + 0x045, 0x00056467, + 0x046, 0x0008992c, + 0x047, 0x0000452c, + 0x048, 0x000f9c43, + 0x049, 0x00002e0c, + 0x04a, 0x000546eb, + 0x04b, 0x0008966c, + 0x04c, 0x0000dde9, + 0x018, 0x00007401, + 0x000, 0x00070000, + 0x012, 0x000dc000, + 0x012, 0x00090000, + 0x012, 0x00051000, + 0x012, 0x00012000, + 0x013, 0x000287b7, + 0x013, 0x000247ab, + 0x013, 0x0002079f, + 0x013, 0x0001c793, + 0x013, 0x0001839b, + 0x013, 0x00014392, + 0x013, 0x0001019a, + 0x013, 0x0000c191, + 0x013, 0x00008194, + 0x013, 0x000040a0, + 0x013, 0x00000018, + 0x015, 0x0000f424, + 0x015, 0x0004f424, + 0x015, 0x0008f424, + 0x016, 0x000e1330, + 0x016, 0x000a1330, + 0x016, 0x00061330, + 0x016, 0x00021330, + 0x018, 0x00017524, + 0x000, 0x00070000, + 0x012, 0x000cf000, + 0x012, 0x000bc000, + 0x012, 0x00078000, + 0x012, 0x00000000, + 0x013, 0x000287bc, + 0x013, 0x000247b0, + 0x013, 0x000203b4, + 0x013, 0x0001c3a8, + 0x013, 0x000181b4, + 0x013, 0x000141a8, + 0x013, 0x000100b0, + 0x013, 0x0000c0a4, + 0x013, 0x0000b02c, + 0x013, 0x00004020, + 0x013, 0x00000014, + 0x015, 0x0000f4c3, + 0x015, 0x0004f4c3, + 0x015, 0x0008f4c3, + 0x016, 0x000e085f, + 0x016, 0x000a085f, + 0x016, 0x0006085f, + 0x016, 0x0002085f, + 0x018, 0x00037524, + 0x000, 0x00070000, + 0x012, 0x000cf000, + 0x012, 0x000bc000, + 0x012, 0x00078000, + 0x012, 0x00000000, + 0x013, 0x000287bc, + 0x013, 0x000247b0, + 0x013, 0x000203b4, + 0x013, 0x0001c3a8, + 0x013, 0x000181b4, + 0x013, 0x000141a8, + 0x013, 0x000100b0, + 0x013, 0x0000c0a4, + 0x013, 0x0000b02c, + 0x013, 0x00004020, + 0x013, 0x00000014, + 0x015, 0x0000f4c3, + 0x015, 0x0004f4c3, + 0x015, 0x0008f4c3, + 0x016, 0x000e085f, + 0x016, 0x000a085f, + 0x016, 0x0006085f, + 0x016, 0x0002085f, + 0x018, 0x00057524, + 0x000, 0x00070000, + 0x012, 0x000cf000, + 0x012, 0x000bc000, + 0x012, 0x00078000, + 0x012, 0x00000000, + 0x013, 0x000287bc, + 0x013, 0x000247b0, + 0x013, 0x000203b4, + 0x013, 0x0001c3a8, + 0x013, 0x000181b4, + 0x013, 0x000141a8, + 0x013, 0x000100b0, + 0x013, 0x0000c0a4, + 0x013, 0x0000b02c, + 0x013, 0x00004020, + 0x013, 0x00000014, + 0x015, 0x0000f4c3, + 0x015, 0x0004f4c3, + 0x015, 0x0008f4c3, + 0x016, 0x000e085f, + 0x016, 0x000a085f, + 0x016, 0x0006085f, + 0x016, 0x0002085f, + 0x030, 0x0004470f, + 0x031, 0x00044ff0, + 0x032, 0x00000070, + 0x033, 0x000dd480, + 0x034, 0x000ffac0, + 0x035, 0x000b80c0, + 0x036, 0x00077000, + 0x037, 0x00064ff2, + 0x038, 0x000e7661, + 0x039, 0x00000e90, + 0x000, 0x00030000, + 0x018, 0x0000f401, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x01e, 0x00088009, + 0x01f, 0x00080003, + 0x0fe, 0x00000000, + 0x01e, 0x00088001, + 0x01f, 0x00080000, + 0x0fe, 0x00000000, + 0x018, 0x00087401, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x02b, 0x00041289, + 0x0fe, 0x00000000, + 0x02d, 0x00066666, + 0x02e, 0x00064001, + 0x02d, 0x00091111, + 0x02e, 0x00014002, + 0x02d, 0x000bbbbb, + 0x02e, 0x000b4003, + 0x02d, 0x000e6666, + 0x02e, 0x00064004, + 0x02d, 0x00088888, + 0x02e, 0x00084005, + 0x02d, 0x0009dddd, + 0x02e, 0x000d4006, + 0x02d, 0x000b3333, + 0x02e, 0x00034007, + 0x02d, 0x00048888, + 0x02e, 0x00084408, + 0x02d, 0x000bbbbb, + 0x02e, 0x000b4409, + 0x02d, 0x000e6666, + 0x02e, 0x0006440a, + 0x02d, 0x00011111, + 0x02e, 0x0001480b, + 0x02d, 0x0003bbbb, + 0x02e, 0x000b480c, + 0x02d, 0x00066666, + 0x02e, 0x0006480d, + 0x02d, 0x000ccccc, + 0x02e, 0x000c480e, +}; + +u32 rtl8192de_radioa_2t_int_paarray[RADIOA_2T_INT_PA_ARRAYLENGTH] = { + 0x000, 0x00030000, + 0x001, 0x00030000, + 0x002, 0x00000000, + 0x003, 0x00018c63, + 0x004, 0x00018c63, + 0x008, 0x00084000, + 0x00b, 0x0001c000, + 0x00e, 0x00018c67, + 0x00f, 0x00000851, + 0x014, 0x00021440, + 0x018, 0x00017524, + 0x019, 0x00000000, + 0x01d, 0x000a1290, + 0x023, 0x00001558, + 0x01a, 0x00030a99, + 0x01b, 0x00040b00, + 0x01c, 0x000fc339, + 0x03a, 0x000a57eb, + 0x03b, 0x00020000, + 0x03c, 0x000ff454, + 0x020, 0x0000aa52, + 0x021, 0x00054000, + 0x040, 0x0000aa52, + 0x041, 0x00014000, + 0x025, 0x000803be, + 0x026, 0x000fc638, + 0x027, 0x00077c18, + 0x028, 0x000de471, + 0x029, 0x000d7110, + 0x02a, 0x0008eb04, + 0x02b, 0x0004128b, + 0x02c, 0x00001840, + 0x043, 0x0002444f, + 0x044, 0x0001adb0, + 0x045, 0x00056467, + 0x046, 0x0008992c, + 0x047, 0x0000452c, + 0x048, 0x000c0443, + 0x049, 0x00000730, + 0x04a, 0x00050f0f, + 0x04b, 0x000896ee, + 0x04c, 0x0000ddee, + 0x018, 0x00007401, + 0x000, 0x00070000, + 0x012, 0x000dc000, + 0x012, 0x00090000, + 0x012, 0x00051000, + 0x012, 0x00012000, + 0x013, 0x000287b7, + 0x013, 0x000247ab, + 0x013, 0x0002079f, + 0x013, 0x0001c793, + 0x013, 0x0001839b, + 0x013, 0x00014392, + 0x013, 0x0001019a, + 0x013, 0x0000c191, + 0x013, 0x00008194, + 0x013, 0x000040a0, + 0x013, 0x00000018, + 0x015, 0x0000f424, + 0x015, 0x0004f424, + 0x015, 0x0008f424, + 0x016, 0x000e1330, + 0x016, 0x000a1330, + 0x016, 0x00061330, + 0x016, 0x00021330, + 0x018, 0x00017524, + 0x000, 0x00070000, + 0x012, 0x000cf000, + 0x012, 0x000bc000, + 0x012, 0x00078000, + 0x012, 0x00000000, + 0x013, 0x000287bf, + 0x013, 0x000247b3, + 0x013, 0x000207a7, + 0x013, 0x0001c79b, + 0x013, 0x0001839f, + 0x013, 0x00014393, + 0x013, 0x00010399, + 0x013, 0x0000c38d, + 0x013, 0x00008199, + 0x013, 0x0000418d, + 0x013, 0x00000099, + 0x015, 0x0000f495, + 0x015, 0x0004f495, + 0x015, 0x0008f495, + 0x016, 0x000e1874, + 0x016, 0x000a1874, + 0x016, 0x00061874, + 0x016, 0x00021874, + 0x018, 0x00037564, + 0x000, 0x00070000, + 0x012, 0x000cf000, + 0x012, 0x000bc000, + 0x012, 0x00078000, + 0x012, 0x00000000, + 0x013, 0x000287bf, + 0x013, 0x000247b3, + 0x013, 0x000207a7, + 0x013, 0x0001c79b, + 0x013, 0x0001839f, + 0x013, 0x00014393, + 0x013, 0x00010399, + 0x013, 0x0000c38d, + 0x013, 0x00008199, + 0x013, 0x0000418d, + 0x013, 0x00000099, + 0x015, 0x0000f495, + 0x015, 0x0004f495, + 0x015, 0x0008f495, + 0x016, 0x000e1874, + 0x016, 0x000a1874, + 0x016, 0x00061874, + 0x016, 0x00021874, + 0x018, 0x00057595, + 0x000, 0x00070000, + 0x012, 0x000cf000, + 0x012, 0x000bc000, + 0x012, 0x00078000, + 0x012, 0x00000000, + 0x013, 0x000287bf, + 0x013, 0x000247b3, + 0x013, 0x000207a7, + 0x013, 0x0001c79b, + 0x013, 0x0001839f, + 0x013, 0x00014393, + 0x013, 0x00010399, + 0x013, 0x0000c38d, + 0x013, 0x00008199, + 0x013, 0x0000418d, + 0x013, 0x00000099, + 0x015, 0x0000f495, + 0x015, 0x0004f495, + 0x015, 0x0008f495, + 0x016, 0x000e1874, + 0x016, 0x000a1874, + 0x016, 0x00061874, + 0x016, 0x00021874, + 0x030, 0x0004470f, + 0x031, 0x00044ff0, + 0x032, 0x00000070, + 0x033, 0x000dd480, + 0x034, 0x000ffac0, + 0x035, 0x000b80c0, + 0x036, 0x00077000, + 0x037, 0x00064ff2, + 0x038, 0x000e7661, + 0x039, 0x00000e90, + 0x000, 0x00030000, + 0x018, 0x0000f401, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x01e, 0x00088009, + 0x01f, 0x00080003, + 0x0fe, 0x00000000, + 0x01e, 0x00088001, + 0x01f, 0x00080000, + 0x0fe, 0x00000000, + 0x018, 0x00097524, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x02b, 0x00041289, + 0x0fe, 0x00000000, + 0x02d, 0x0006aaaa, + 0x02e, 0x000b4d01, + 0x02d, 0x00080000, + 0x02e, 0x00004d02, + 0x02d, 0x00095555, + 0x02e, 0x00054d03, + 0x02d, 0x000aaaaa, + 0x02e, 0x000b4d04, + 0x02d, 0x000c0000, + 0x02e, 0x00004d05, + 0x02d, 0x000d5555, + 0x02e, 0x00054d06, + 0x02d, 0x000eaaaa, + 0x02e, 0x000b4d07, + 0x02d, 0x00000000, + 0x02e, 0x00005108, + 0x02d, 0x00015555, + 0x02e, 0x00055109, + 0x02d, 0x0002aaaa, + 0x02e, 0x000b510a, + 0x02d, 0x00040000, + 0x02e, 0x0000510b, + 0x02d, 0x00055555, + 0x02e, 0x0005510c, +}; + +u32 rtl8192de_radiob_2t_int_paarray[RADIOB_2T_INT_PA_ARRAYLENGTH] = { + 0x000, 0x00030000, + 0x001, 0x00030000, + 0x002, 0x00000000, + 0x003, 0x00018c63, + 0x004, 0x00018c63, + 0x008, 0x00084000, + 0x00b, 0x0001c000, + 0x00e, 0x00018c67, + 0x00f, 0x00000851, + 0x014, 0x00021440, + 0x018, 0x00007401, + 0x019, 0x00000060, + 0x01d, 0x000a1290, + 0x023, 0x00001558, + 0x01a, 0x00030a99, + 0x01b, 0x00040b00, + 0x01c, 0x000fc339, + 0x03a, 0x000a57eb, + 0x03b, 0x00020000, + 0x03c, 0x000ff454, + 0x020, 0x0000aa52, + 0x021, 0x00054000, + 0x040, 0x0000aa52, + 0x041, 0x00014000, + 0x025, 0x000803be, + 0x026, 0x000fc638, + 0x027, 0x00077c18, + 0x028, 0x000d1c31, + 0x029, 0x000d7110, + 0x02a, 0x000aeb04, + 0x02b, 0x0004128b, + 0x02c, 0x00001840, + 0x043, 0x0002444f, + 0x044, 0x0001adb0, + 0x045, 0x00056467, + 0x046, 0x0008992c, + 0x047, 0x0000452c, + 0x048, 0x000c0443, + 0x049, 0x00000730, + 0x04a, 0x00050f0f, + 0x04b, 0x000896ee, + 0x04c, 0x0000ddee, + 0x018, 0x00007401, + 0x000, 0x00070000, + 0x012, 0x000dc000, + 0x012, 0x00090000, + 0x012, 0x00051000, + 0x012, 0x00012000, + 0x013, 0x000287b7, + 0x013, 0x000247ab, + 0x013, 0x0002079f, + 0x013, 0x0001c793, + 0x013, 0x0001839b, + 0x013, 0x00014392, + 0x013, 0x0001019a, + 0x013, 0x0000c191, + 0x013, 0x00008194, + 0x013, 0x000040a0, + 0x013, 0x00000018, + 0x015, 0x0000f424, + 0x015, 0x0004f424, + 0x015, 0x0008f424, + 0x016, 0x000e1330, + 0x016, 0x000a1330, + 0x016, 0x00061330, + 0x016, 0x00021330, + 0x018, 0x00017524, + 0x000, 0x00070000, + 0x012, 0x000cf000, + 0x012, 0x000bc000, + 0x012, 0x00078000, + 0x012, 0x00000000, + 0x013, 0x000287bf, + 0x013, 0x000247b3, + 0x013, 0x000207a7, + 0x013, 0x0001c79b, + 0x013, 0x0001839f, + 0x013, 0x00014393, + 0x013, 0x00010399, + 0x013, 0x0000c38d, + 0x013, 0x00008199, + 0x013, 0x0000418d, + 0x013, 0x00000099, + 0x015, 0x0000f495, + 0x015, 0x0004f495, + 0x015, 0x0008f495, + 0x016, 0x000e1874, + 0x016, 0x000a1874, + 0x016, 0x00061874, + 0x016, 0x00021874, + 0x018, 0x00037564, + 0x000, 0x00070000, + 0x012, 0x000cf000, + 0x012, 0x000bc000, + 0x012, 0x00078000, + 0x012, 0x00000000, + 0x013, 0x000287bf, + 0x013, 0x000247b3, + 0x013, 0x000207a7, + 0x013, 0x0001c79b, + 0x013, 0x0001839f, + 0x013, 0x00014393, + 0x013, 0x00010399, + 0x013, 0x0000c38d, + 0x013, 0x00008199, + 0x013, 0x0000418d, + 0x013, 0x00000099, + 0x015, 0x0000f495, + 0x015, 0x0004f495, + 0x015, 0x0008f495, + 0x016, 0x000e1874, + 0x016, 0x000a1874, + 0x016, 0x00061874, + 0x016, 0x00021874, + 0x018, 0x00057595, + 0x000, 0x00070000, + 0x012, 0x000cf000, + 0x012, 0x000bc000, + 0x012, 0x00078000, + 0x012, 0x00000000, + 0x013, 0x000287bf, + 0x013, 0x000247b3, + 0x013, 0x000207a7, + 0x013, 0x0001c79b, + 0x013, 0x0001839f, + 0x013, 0x00014393, + 0x013, 0x00010399, + 0x013, 0x0000c38d, + 0x013, 0x00008199, + 0x013, 0x0000418d, + 0x013, 0x00000099, + 0x015, 0x0000f495, + 0x015, 0x0004f495, + 0x015, 0x0008f495, + 0x016, 0x000e1874, + 0x016, 0x000a1874, + 0x016, 0x00061874, + 0x016, 0x00021874, + 0x030, 0x0004470f, + 0x031, 0x00044ff0, + 0x032, 0x00000070, + 0x033, 0x000dd480, + 0x034, 0x000ffac0, + 0x035, 0x000b80c0, + 0x036, 0x00077000, + 0x037, 0x00064ff2, + 0x038, 0x000e7661, + 0x039, 0x00000e90, + 0x000, 0x00030000, + 0x018, 0x0000f401, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x01e, 0x00088009, + 0x01f, 0x00080003, + 0x0fe, 0x00000000, + 0x01e, 0x00088001, + 0x01f, 0x00080000, + 0x0fe, 0x00000000, + 0x018, 0x00087401, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x02b, 0x00041289, + 0x0fe, 0x00000000, + 0x02d, 0x00066666, + 0x02e, 0x00064001, + 0x02d, 0x00091111, + 0x02e, 0x00014002, + 0x02d, 0x000bbbbb, + 0x02e, 0x000b4003, + 0x02d, 0x000e6666, + 0x02e, 0x00064004, + 0x02d, 0x00088888, + 0x02e, 0x00084005, + 0x02d, 0x0009dddd, + 0x02e, 0x000d4006, + 0x02d, 0x000b3333, + 0x02e, 0x00034007, + 0x02d, 0x00048888, + 0x02e, 0x00084408, + 0x02d, 0x000bbbbb, + 0x02e, 0x000b4409, + 0x02d, 0x000e6666, + 0x02e, 0x0006440a, + 0x02d, 0x00011111, + 0x02e, 0x0001480b, + 0x02d, 0x0003bbbb, + 0x02e, 0x000b480c, + 0x02d, 0x00066666, + 0x02e, 0x0006480d, + 0x02d, 0x000ccccc, + 0x02e, 0x000c480e, +}; + +u32 rtl8192de_mac_2tarray[MAC_2T_ARRAYLENGTH] = { + 0x420, 0x00000080, + 0x423, 0x00000000, + 0x430, 0x00000000, + 0x431, 0x00000000, + 0x432, 0x00000000, + 0x433, 0x00000001, + 0x434, 0x00000004, + 0x435, 0x00000005, + 0x436, 0x00000006, + 0x437, 0x00000007, + 0x438, 0x00000000, + 0x439, 0x00000000, + 0x43a, 0x00000000, + 0x43b, 0x00000001, + 0x43c, 0x00000004, + 0x43d, 0x00000005, + 0x43e, 0x00000006, + 0x43f, 0x00000007, + 0x440, 0x00000050, + 0x441, 0x00000001, + 0x442, 0x00000000, + 0x444, 0x00000015, + 0x445, 0x000000f0, + 0x446, 0x0000000f, + 0x447, 0x00000000, + 0x462, 0x00000008, + 0x463, 0x00000003, + 0x4c8, 0x000000ff, + 0x4c9, 0x00000008, + 0x4cc, 0x000000ff, + 0x4cd, 0x000000ff, + 0x4ce, 0x00000001, + 0x500, 0x00000026, + 0x501, 0x000000a2, + 0x502, 0x0000002f, + 0x503, 0x00000000, + 0x504, 0x00000028, + 0x505, 0x000000a3, + 0x506, 0x0000005e, + 0x507, 0x00000000, + 0x508, 0x0000002b, + 0x509, 0x000000a4, + 0x50a, 0x0000005e, + 0x50b, 0x00000000, + 0x50c, 0x0000004f, + 0x50d, 0x000000a4, + 0x50e, 0x00000000, + 0x50f, 0x00000000, + 0x512, 0x0000001c, + 0x514, 0x0000000a, + 0x515, 0x00000010, + 0x516, 0x0000000a, + 0x517, 0x00000010, + 0x51a, 0x00000016, + 0x524, 0x0000000f, + 0x525, 0x0000004f, + 0x546, 0x00000040, + 0x547, 0x00000000, + 0x550, 0x00000010, + 0x551, 0x00000010, + 0x559, 0x00000002, + 0x55a, 0x00000002, + 0x55d, 0x000000ff, + 0x605, 0x00000030, + 0x608, 0x0000000e, + 0x609, 0x0000002a, + 0x652, 0x00000020, + 0x63c, 0x0000000a, + 0x63d, 0x0000000a, + 0x63e, 0x0000000e, + 0x63f, 0x0000000e, + 0x66e, 0x00000005, + 0x700, 0x00000021, + 0x701, 0x00000043, + 0x702, 0x00000065, + 0x703, 0x00000087, + 0x708, 0x00000021, + 0x709, 0x00000043, + 0x70a, 0x00000065, + 0x70b, 0x00000087, +}; + +u32 rtl8192de_agctab_array[AGCTAB_ARRAYLENGTH] = { + 0xc78, 0x7b000001, + 0xc78, 0x7b010001, + 0xc78, 0x7b020001, + 0xc78, 0x7b030001, + 0xc78, 0x7b040001, + 0xc78, 0x7b050001, + 0xc78, 0x7b060001, + 0xc78, 0x7a070001, + 0xc78, 0x79080001, + 0xc78, 0x78090001, + 0xc78, 0x770a0001, + 0xc78, 0x760b0001, + 0xc78, 0x750c0001, + 0xc78, 0x740d0001, + 0xc78, 0x730e0001, + 0xc78, 0x720f0001, + 0xc78, 0x71100001, + 0xc78, 0x70110001, + 0xc78, 0x6f120001, + 0xc78, 0x6e130001, + 0xc78, 0x6d140001, + 0xc78, 0x6c150001, + 0xc78, 0x6b160001, + 0xc78, 0x6a170001, + 0xc78, 0x69180001, + 0xc78, 0x68190001, + 0xc78, 0x671a0001, + 0xc78, 0x661b0001, + 0xc78, 0x651c0001, + 0xc78, 0x641d0001, + 0xc78, 0x631e0001, + 0xc78, 0x621f0001, + 0xc78, 0x61200001, + 0xc78, 0x60210001, + 0xc78, 0x49220001, + 0xc78, 0x48230001, + 0xc78, 0x47240001, + 0xc78, 0x46250001, + 0xc78, 0x45260001, + 0xc78, 0x44270001, + 0xc78, 0x43280001, + 0xc78, 0x42290001, + 0xc78, 0x412a0001, + 0xc78, 0x402b0001, + 0xc78, 0x262c0001, + 0xc78, 0x252d0001, + 0xc78, 0x242e0001, + 0xc78, 0x232f0001, + 0xc78, 0x22300001, + 0xc78, 0x21310001, + 0xc78, 0x20320001, + 0xc78, 0x06330001, + 0xc78, 0x05340001, + 0xc78, 0x04350001, + 0xc78, 0x03360001, + 0xc78, 0x02370001, + 0xc78, 0x01380001, + 0xc78, 0x00390001, + 0xc78, 0x003a0001, + 0xc78, 0x003b0001, + 0xc78, 0x003c0001, + 0xc78, 0x003d0001, + 0xc78, 0x003e0001, + 0xc78, 0x003f0001, + 0xc78, 0x7b400001, + 0xc78, 0x7b410001, + 0xc78, 0x7a420001, + 0xc78, 0x79430001, + 0xc78, 0x78440001, + 0xc78, 0x77450001, + 0xc78, 0x76460001, + 0xc78, 0x75470001, + 0xc78, 0x74480001, + 0xc78, 0x73490001, + 0xc78, 0x724a0001, + 0xc78, 0x714b0001, + 0xc78, 0x704c0001, + 0xc78, 0x6f4d0001, + 0xc78, 0x6e4e0001, + 0xc78, 0x6d4f0001, + 0xc78, 0x6c500001, + 0xc78, 0x6b510001, + 0xc78, 0x6a520001, + 0xc78, 0x69530001, + 0xc78, 0x68540001, + 0xc78, 0x67550001, + 0xc78, 0x66560001, + 0xc78, 0x65570001, + 0xc78, 0x64580001, + 0xc78, 0x63590001, + 0xc78, 0x625a0001, + 0xc78, 0x615b0001, + 0xc78, 0x605c0001, + 0xc78, 0x485d0001, + 0xc78, 0x475e0001, + 0xc78, 0x465f0001, + 0xc78, 0x45600001, + 0xc78, 0x44610001, + 0xc78, 0x43620001, + 0xc78, 0x42630001, + 0xc78, 0x41640001, + 0xc78, 0x40650001, + 0xc78, 0x27660001, + 0xc78, 0x26670001, + 0xc78, 0x25680001, + 0xc78, 0x24690001, + 0xc78, 0x236a0001, + 0xc78, 0x226b0001, + 0xc78, 0x216c0001, + 0xc78, 0x206d0001, + 0xc78, 0x206e0001, + 0xc78, 0x206f0001, + 0xc78, 0x20700001, + 0xc78, 0x20710001, + 0xc78, 0x20720001, + 0xc78, 0x20730001, + 0xc78, 0x20740001, + 0xc78, 0x20750001, + 0xc78, 0x20760001, + 0xc78, 0x20770001, + 0xc78, 0x20780001, + 0xc78, 0x20790001, + 0xc78, 0x207a0001, + 0xc78, 0x207b0001, + 0xc78, 0x207c0001, + 0xc78, 0x207d0001, + 0xc78, 0x207e0001, + 0xc78, 0x207f0001, + 0xc78, 0x38000002, + 0xc78, 0x38010002, + 0xc78, 0x38020002, + 0xc78, 0x38030002, + 0xc78, 0x38040002, + 0xc78, 0x38050002, + 0xc78, 0x38060002, + 0xc78, 0x38070002, + 0xc78, 0x38080002, + 0xc78, 0x3c090002, + 0xc78, 0x3e0a0002, + 0xc78, 0x400b0002, + 0xc78, 0x440c0002, + 0xc78, 0x480d0002, + 0xc78, 0x4c0e0002, + 0xc78, 0x500f0002, + 0xc78, 0x52100002, + 0xc78, 0x56110002, + 0xc78, 0x5a120002, + 0xc78, 0x5e130002, + 0xc78, 0x60140002, + 0xc78, 0x60150002, + 0xc78, 0x60160002, + 0xc78, 0x62170002, + 0xc78, 0x62180002, + 0xc78, 0x62190002, + 0xc78, 0x621a0002, + 0xc78, 0x621b0002, + 0xc78, 0x621c0002, + 0xc78, 0x621d0002, + 0xc78, 0x621e0002, + 0xc78, 0x621f0002, + 0xc78, 0x32000044, + 0xc78, 0x32010044, + 0xc78, 0x32020044, + 0xc78, 0x32030044, + 0xc78, 0x32040044, + 0xc78, 0x32050044, + 0xc78, 0x32060044, + 0xc78, 0x32070044, + 0xc78, 0x32080044, + 0xc78, 0x34090044, + 0xc78, 0x350a0044, + 0xc78, 0x360b0044, + 0xc78, 0x370c0044, + 0xc78, 0x380d0044, + 0xc78, 0x390e0044, + 0xc78, 0x3a0f0044, + 0xc78, 0x3e100044, + 0xc78, 0x42110044, + 0xc78, 0x44120044, + 0xc78, 0x46130044, + 0xc78, 0x4a140044, + 0xc78, 0x4e150044, + 0xc78, 0x50160044, + 0xc78, 0x55170044, + 0xc78, 0x5a180044, + 0xc78, 0x5e190044, + 0xc78, 0x641a0044, + 0xc78, 0x6e1b0044, + 0xc78, 0x6e1c0044, + 0xc78, 0x6e1d0044, + 0xc78, 0x6e1e0044, + 0xc78, 0x6e1f0044, + 0xc78, 0x6e1f0000, +}; + +u32 rtl8192de_agctab_5garray[AGCTAB_5G_ARRAYLENGTH] = { + 0xc78, 0x7b000001, + 0xc78, 0x7b010001, + 0xc78, 0x7a020001, + 0xc78, 0x79030001, + 0xc78, 0x78040001, + 0xc78, 0x77050001, + 0xc78, 0x76060001, + 0xc78, 0x75070001, + 0xc78, 0x74080001, + 0xc78, 0x73090001, + 0xc78, 0x720a0001, + 0xc78, 0x710b0001, + 0xc78, 0x700c0001, + 0xc78, 0x6f0d0001, + 0xc78, 0x6e0e0001, + 0xc78, 0x6d0f0001, + 0xc78, 0x6c100001, + 0xc78, 0x6b110001, + 0xc78, 0x6a120001, + 0xc78, 0x69130001, + 0xc78, 0x68140001, + 0xc78, 0x67150001, + 0xc78, 0x66160001, + 0xc78, 0x65170001, + 0xc78, 0x64180001, + 0xc78, 0x63190001, + 0xc78, 0x621a0001, + 0xc78, 0x611b0001, + 0xc78, 0x601c0001, + 0xc78, 0x481d0001, + 0xc78, 0x471e0001, + 0xc78, 0x461f0001, + 0xc78, 0x45200001, + 0xc78, 0x44210001, + 0xc78, 0x43220001, + 0xc78, 0x42230001, + 0xc78, 0x41240001, + 0xc78, 0x40250001, + 0xc78, 0x27260001, + 0xc78, 0x26270001, + 0xc78, 0x25280001, + 0xc78, 0x24290001, + 0xc78, 0x232a0001, + 0xc78, 0x222b0001, + 0xc78, 0x212c0001, + 0xc78, 0x202d0001, + 0xc78, 0x202e0001, + 0xc78, 0x202f0001, + 0xc78, 0x20300001, + 0xc78, 0x20310001, + 0xc78, 0x20320001, + 0xc78, 0x20330001, + 0xc78, 0x20340001, + 0xc78, 0x20350001, + 0xc78, 0x20360001, + 0xc78, 0x20370001, + 0xc78, 0x20380001, + 0xc78, 0x20390001, + 0xc78, 0x203a0001, + 0xc78, 0x203b0001, + 0xc78, 0x203c0001, + 0xc78, 0x203d0001, + 0xc78, 0x203e0001, + 0xc78, 0x203f0001, + 0xc78, 0x32000044, + 0xc78, 0x32010044, + 0xc78, 0x32020044, + 0xc78, 0x32030044, + 0xc78, 0x32040044, + 0xc78, 0x32050044, + 0xc78, 0x32060044, + 0xc78, 0x32070044, + 0xc78, 0x32080044, + 0xc78, 0x34090044, + 0xc78, 0x350a0044, + 0xc78, 0x360b0044, + 0xc78, 0x370c0044, + 0xc78, 0x380d0044, + 0xc78, 0x390e0044, + 0xc78, 0x3a0f0044, + 0xc78, 0x3e100044, + 0xc78, 0x42110044, + 0xc78, 0x44120044, + 0xc78, 0x46130044, + 0xc78, 0x4a140044, + 0xc78, 0x4e150044, + 0xc78, 0x50160044, + 0xc78, 0x55170044, + 0xc78, 0x5a180044, + 0xc78, 0x5e190044, + 0xc78, 0x641a0044, + 0xc78, 0x6e1b0044, + 0xc78, 0x6e1c0044, + 0xc78, 0x6e1d0044, + 0xc78, 0x6e1e0044, + 0xc78, 0x6e1f0044, + 0xc78, 0x6e1f0000, +}; + +u32 rtl8192de_agctab_2garray[AGCTAB_2G_ARRAYLENGTH] = { + 0xc78, 0x7b000001, + 0xc78, 0x7b010001, + 0xc78, 0x7b020001, + 0xc78, 0x7b030001, + 0xc78, 0x7b040001, + 0xc78, 0x7b050001, + 0xc78, 0x7b060001, + 0xc78, 0x7a070001, + 0xc78, 0x79080001, + 0xc78, 0x78090001, + 0xc78, 0x770a0001, + 0xc78, 0x760b0001, + 0xc78, 0x750c0001, + 0xc78, 0x740d0001, + 0xc78, 0x730e0001, + 0xc78, 0x720f0001, + 0xc78, 0x71100001, + 0xc78, 0x70110001, + 0xc78, 0x6f120001, + 0xc78, 0x6e130001, + 0xc78, 0x6d140001, + 0xc78, 0x6c150001, + 0xc78, 0x6b160001, + 0xc78, 0x6a170001, + 0xc78, 0x69180001, + 0xc78, 0x68190001, + 0xc78, 0x671a0001, + 0xc78, 0x661b0001, + 0xc78, 0x651c0001, + 0xc78, 0x641d0001, + 0xc78, 0x631e0001, + 0xc78, 0x621f0001, + 0xc78, 0x61200001, + 0xc78, 0x60210001, + 0xc78, 0x49220001, + 0xc78, 0x48230001, + 0xc78, 0x47240001, + 0xc78, 0x46250001, + 0xc78, 0x45260001, + 0xc78, 0x44270001, + 0xc78, 0x43280001, + 0xc78, 0x42290001, + 0xc78, 0x412a0001, + 0xc78, 0x402b0001, + 0xc78, 0x262c0001, + 0xc78, 0x252d0001, + 0xc78, 0x242e0001, + 0xc78, 0x232f0001, + 0xc78, 0x22300001, + 0xc78, 0x21310001, + 0xc78, 0x20320001, + 0xc78, 0x06330001, + 0xc78, 0x05340001, + 0xc78, 0x04350001, + 0xc78, 0x03360001, + 0xc78, 0x02370001, + 0xc78, 0x01380001, + 0xc78, 0x00390001, + 0xc78, 0x003a0001, + 0xc78, 0x003b0001, + 0xc78, 0x003c0001, + 0xc78, 0x003d0001, + 0xc78, 0x003e0001, + 0xc78, 0x003f0001, + 0xc78, 0x38000002, + 0xc78, 0x38010002, + 0xc78, 0x38020002, + 0xc78, 0x38030002, + 0xc78, 0x38040002, + 0xc78, 0x38050002, + 0xc78, 0x38060002, + 0xc78, 0x38070002, + 0xc78, 0x38080002, + 0xc78, 0x3c090002, + 0xc78, 0x3e0a0002, + 0xc78, 0x400b0002, + 0xc78, 0x440c0002, + 0xc78, 0x480d0002, + 0xc78, 0x4c0e0002, + 0xc78, 0x500f0002, + 0xc78, 0x52100002, + 0xc78, 0x56110002, + 0xc78, 0x5a120002, + 0xc78, 0x5e130002, + 0xc78, 0x60140002, + 0xc78, 0x60150002, + 0xc78, 0x60160002, + 0xc78, 0x62170002, + 0xc78, 0x62180002, + 0xc78, 0x62190002, + 0xc78, 0x621a0002, + 0xc78, 0x621b0002, + 0xc78, 0x621c0002, + 0xc78, 0x621d0002, + 0xc78, 0x621e0002, + 0xc78, 0x621f0002, + 0xc78, 0x6e1f0000, +}; diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/table.h b/drivers/net/wireless/rtlwifi/rtl8192de/table.h new file mode 100644 index 0000000..8b724a8 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/table.h @@ -0,0 +1,57 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + * Created on 2010/ 5/18, 1:41 + *****************************************************************************/ + +#ifndef __RTL92DE_TABLE__H_ +#define __RTL92DE_TABLE__H_ + +/*Created on 2011/ 1/14, 1:35*/ + +#define PHY_REG_2T_ARRAYLENGTH 380 +extern u32 rtl8192de_phy_reg_2tarray[PHY_REG_2T_ARRAYLENGTH]; +#define PHY_REG_ARRAY_PG_LENGTH 624 +extern u32 rtl8192de_phy_reg_array_pg[PHY_REG_ARRAY_PG_LENGTH]; +#define RADIOA_2T_ARRAYLENGTH 378 +extern u32 rtl8192de_radioa_2tarray[RADIOA_2T_ARRAYLENGTH]; +#define RADIOB_2T_ARRAYLENGTH 384 +extern u32 rtl8192de_radiob_2tarray[RADIOB_2T_ARRAYLENGTH]; +#define RADIOA_2T_INT_PA_ARRAYLENGTH 378 +extern u32 rtl8192de_radioa_2t_int_paarray[RADIOA_2T_INT_PA_ARRAYLENGTH]; +#define RADIOB_2T_INT_PA_ARRAYLENGTH 384 +extern u32 rtl8192de_radiob_2t_int_paarray[RADIOB_2T_INT_PA_ARRAYLENGTH]; +#define MAC_2T_ARRAYLENGTH 160 +extern u32 rtl8192de_mac_2tarray[MAC_2T_ARRAYLENGTH]; +#define AGCTAB_ARRAYLENGTH 386 +extern u32 rtl8192de_agctab_array[AGCTAB_ARRAYLENGTH]; +#define AGCTAB_5G_ARRAYLENGTH 194 +extern u32 rtl8192de_agctab_5garray[AGCTAB_5G_ARRAYLENGTH]; +#define AGCTAB_2G_ARRAYLENGTH 194 +extern u32 rtl8192de_agctab_2garray[AGCTAB_2G_ARRAYLENGTH]; + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/trx.c b/drivers/net/wireless/rtlwifi/rtl8192de/trx.c new file mode 100644 index 0000000..b8ec718 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/trx.c @@ -0,0 +1,872 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "../base.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "trx.h" +#include "led.h" + +static u8 _rtl92de_map_hwqueue_to_fwqueue(struct sk_buff *skb, u8 hw_queue) +{ + __le16 fc = rtl_get_fc(skb); + + if (unlikely(ieee80211_is_beacon(fc))) + return QSLT_BEACON; + if (ieee80211_is_mgmt(fc)) + return QSLT_MGNT; + + return skb->priority; +} + +static u8 _rtl92d_query_rxpwrpercentage(char antpower) +{ + if ((antpower <= -100) || (antpower >= 20)) + return 0; + else if (antpower >= 0) + return 100; + else + return 100 + antpower; +} + +static u8 _rtl92d_evm_db_to_percentage(char value) +{ + char ret_val = value; + + if (ret_val >= 0) + ret_val = 0; + if (ret_val <= -33) + ret_val = -33; + ret_val = 0 - ret_val; + ret_val *= 3; + if (ret_val == 99) + ret_val = 100; + return ret_val; +} + +static long _rtl92de_translate_todbm(struct ieee80211_hw *hw, + u8 signal_strength_index) +{ + long signal_power; + + signal_power = (long)((signal_strength_index + 1) >> 1); + signal_power -= 95; + return signal_power; +} + +static long _rtl92de_signal_scale_mapping(struct ieee80211_hw *hw, long currsig) +{ + long retsig; + + if (currsig >= 61 && currsig <= 100) + retsig = 90 + ((currsig - 60) / 4); + else if (currsig >= 41 && currsig <= 60) + retsig = 78 + ((currsig - 40) / 2); + else if (currsig >= 31 && currsig <= 40) + retsig = 66 + (currsig - 30); + else if (currsig >= 21 && currsig <= 30) + retsig = 54 + (currsig - 20); + else if (currsig >= 5 && currsig <= 20) + retsig = 42 + (((currsig - 5) * 2) / 3); + else if (currsig == 4) + retsig = 36; + else if (currsig == 3) + retsig = 27; + else if (currsig == 2) + retsig = 18; + else if (currsig == 1) + retsig = 9; + else + retsig = currsig; + return retsig; +} + +static void _rtl92de_query_rxphystatus(struct ieee80211_hw *hw, + struct rtl_stats *pstats, + struct rx_desc_92d *pdesc, + struct rx_fwinfo_92d *p_drvinfo, + bool packet_match_bssid, + bool packet_toself, + bool packet_beacon) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtlpriv); + struct phy_sts_cck_8192d *cck_buf; + s8 rx_pwr_all, rx_pwr[4]; + u8 rf_rx_num = 0, evm, pwdb_all; + u8 i, max_spatial_stream; + u32 rssi, total_rssi = 0; + bool is_cck_rate; + + is_cck_rate = RX_HAL_IS_CCK_RATE(pdesc); + pstats->packet_matchbssid = packet_match_bssid; + pstats->packet_toself = packet_toself; + pstats->packet_beacon = packet_beacon; + pstats->is_cck = is_cck_rate; + pstats->rx_mimo_sig_qual[0] = -1; + pstats->rx_mimo_sig_qual[1] = -1; + + if (is_cck_rate) { + u8 report, cck_highpwr; + cck_buf = (struct phy_sts_cck_8192d *)p_drvinfo; + if (ppsc->rfpwr_state == ERFON) + cck_highpwr = (u8) rtl_get_bbreg(hw, + RFPGA0_XA_HSSIPARAMETER2, + BIT(9)); + else + cck_highpwr = false; + if (!cck_highpwr) { + u8 cck_agc_rpt = cck_buf->cck_agc_rpt; + report = cck_buf->cck_agc_rpt & 0xc0; + report = report >> 6; + switch (report) { + case 0x3: + rx_pwr_all = -46 - (cck_agc_rpt & 0x3e); + break; + case 0x2: + rx_pwr_all = -26 - (cck_agc_rpt & 0x3e); + break; + case 0x1: + rx_pwr_all = -12 - (cck_agc_rpt & 0x3e); + break; + case 0x0: + rx_pwr_all = 16 - (cck_agc_rpt & 0x3e); + break; + } + } else { + u8 cck_agc_rpt = cck_buf->cck_agc_rpt; + report = p_drvinfo->cfosho[0] & 0x60; + report = report >> 5; + switch (report) { + case 0x3: + rx_pwr_all = -46 - ((cck_agc_rpt & 0x1f) << 1); + break; + case 0x2: + rx_pwr_all = -26 - ((cck_agc_rpt & 0x1f) << 1); + break; + case 0x1: + rx_pwr_all = -12 - ((cck_agc_rpt & 0x1f) << 1); + break; + case 0x0: + rx_pwr_all = 16 - ((cck_agc_rpt & 0x1f) << 1); + break; + } + } + pwdb_all = _rtl92d_query_rxpwrpercentage(rx_pwr_all); + /* CCK gain is smaller than OFDM/MCS gain, */ + /* so we add gain diff by experiences, the val is 6 */ + pwdb_all += 6; + if (pwdb_all > 100) + pwdb_all = 100; + /* modify the offset to make the same gain index with OFDM. */ + if (pwdb_all > 34 && pwdb_all <= 42) + pwdb_all -= 2; + else if (pwdb_all > 26 && pwdb_all <= 34) + pwdb_all -= 6; + else if (pwdb_all > 14 && pwdb_all <= 26) + pwdb_all -= 8; + else if (pwdb_all > 4 && pwdb_all <= 14) + pwdb_all -= 4; + pstats->rx_pwdb_all = pwdb_all; + pstats->recvsignalpower = rx_pwr_all; + if (packet_match_bssid) { + u8 sq; + if (pstats->rx_pwdb_all > 40) { + sq = 100; + } else { + sq = cck_buf->sq_rpt; + if (sq > 64) + sq = 0; + else if (sq < 20) + sq = 100; + else + sq = ((64 - sq) * 100) / 44; + } + pstats->signalquality = sq; + pstats->rx_mimo_sig_qual[0] = sq; + pstats->rx_mimo_sig_qual[1] = -1; + } + } else { + rtlpriv->dm.rfpath_rxenable[0] = true; + rtlpriv->dm.rfpath_rxenable[1] = true; + for (i = RF90_PATH_A; i < RF6052_MAX_PATH; i++) { + if (rtlpriv->dm.rfpath_rxenable[i]) + rf_rx_num++; + rx_pwr[i] = ((p_drvinfo->gain_trsw[i] & 0x3f) * 2) + - 110; + rssi = _rtl92d_query_rxpwrpercentage(rx_pwr[i]); + total_rssi += rssi; + rtlpriv->stats.rx_snr_db[i] = + (long)(p_drvinfo->rxsnr[i] / 2); + if (packet_match_bssid) + pstats->rx_mimo_signalstrength[i] = (u8) rssi; + } + rx_pwr_all = ((p_drvinfo->pwdb_all >> 1) & 0x7f) - 106; + pwdb_all = _rtl92d_query_rxpwrpercentage(rx_pwr_all); + pstats->rx_pwdb_all = pwdb_all; + pstats->rxpower = rx_pwr_all; + pstats->recvsignalpower = rx_pwr_all; + if (pdesc->rxht && pdesc->rxmcs >= DESC92_RATEMCS8 && + pdesc->rxmcs <= DESC92_RATEMCS15) + max_spatial_stream = 2; + else + max_spatial_stream = 1; + for (i = 0; i < max_spatial_stream; i++) { + evm = _rtl92d_evm_db_to_percentage(p_drvinfo->rxevm[i]); + if (packet_match_bssid) { + if (i == 0) + pstats->signalquality = + (u8)(evm & 0xff); + pstats->rx_mimo_sig_qual[i] = + (u8)(evm & 0xff); + } + } + } + if (is_cck_rate) + pstats->signalstrength = (u8)(_rtl92de_signal_scale_mapping(hw, + pwdb_all)); + else if (rf_rx_num != 0) + pstats->signalstrength = (u8)(_rtl92de_signal_scale_mapping(hw, + total_rssi /= rf_rx_num)); +} + +static void rtl92d_loop_over_paths(struct ieee80211_hw *hw, + struct rtl_stats *pstats) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u8 rfpath; + + for (rfpath = RF90_PATH_A; rfpath < rtlphy->num_total_rfpath; + rfpath++) { + if (rtlpriv->stats.rx_rssi_percentage[rfpath] == 0) { + rtlpriv->stats.rx_rssi_percentage[rfpath] = + pstats->rx_mimo_signalstrength[rfpath]; + + } + if (pstats->rx_mimo_signalstrength[rfpath] > + rtlpriv->stats.rx_rssi_percentage[rfpath]) { + rtlpriv->stats.rx_rssi_percentage[rfpath] = + ((rtlpriv->stats.rx_rssi_percentage[rfpath] * + (RX_SMOOTH_FACTOR - 1)) + + (pstats->rx_mimo_signalstrength[rfpath])) / + (RX_SMOOTH_FACTOR); + rtlpriv->stats.rx_rssi_percentage[rfpath] = + rtlpriv->stats.rx_rssi_percentage[rfpath] + 1; + } else { + rtlpriv->stats.rx_rssi_percentage[rfpath] = + ((rtlpriv->stats.rx_rssi_percentage[rfpath] * + (RX_SMOOTH_FACTOR - 1)) + + (pstats->rx_mimo_signalstrength[rfpath])) / + (RX_SMOOTH_FACTOR); + } + } +} + +static void _rtl92de_process_ui_rssi(struct ieee80211_hw *hw, + struct rtl_stats *pstats) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 last_rssi, tmpval; + + if (pstats->packet_toself || pstats->packet_beacon) { + rtlpriv->stats.rssi_calculate_cnt++; + if (rtlpriv->stats.ui_rssi.total_num++ >= + PHY_RSSI_SLID_WIN_MAX) { + rtlpriv->stats.ui_rssi.total_num = + PHY_RSSI_SLID_WIN_MAX; + last_rssi = rtlpriv->stats.ui_rssi.elements[ + rtlpriv->stats.ui_rssi.index]; + rtlpriv->stats.ui_rssi.total_val -= last_rssi; + } + rtlpriv->stats.ui_rssi.total_val += pstats->signalstrength; + rtlpriv->stats.ui_rssi.elements + [rtlpriv->stats.ui_rssi.index++] = + pstats->signalstrength; + if (rtlpriv->stats.ui_rssi.index >= PHY_RSSI_SLID_WIN_MAX) + rtlpriv->stats.ui_rssi.index = 0; + tmpval = rtlpriv->stats.ui_rssi.total_val / + rtlpriv->stats.ui_rssi.total_num; + rtlpriv->stats.signal_strength = _rtl92de_translate_todbm(hw, + (u8) tmpval); + pstats->rssi = rtlpriv->stats.signal_strength; + } + if (!pstats->is_cck && pstats->packet_toself) + rtl92d_loop_over_paths(hw, pstats); +} + +static void _rtl92de_update_rxsignalstatistics(struct ieee80211_hw *hw, + struct rtl_stats *pstats) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int weighting = 0; + + if (rtlpriv->stats.recv_signal_power == 0) + rtlpriv->stats.recv_signal_power = pstats->recvsignalpower; + if (pstats->recvsignalpower > rtlpriv->stats.recv_signal_power) + weighting = 5; + else if (pstats->recvsignalpower < rtlpriv->stats.recv_signal_power) + weighting = (-5); + rtlpriv->stats.recv_signal_power = (rtlpriv->stats.recv_signal_power * + 5 + pstats->recvsignalpower + weighting) / 6; +} + +static void _rtl92de_process_pwdb(struct ieee80211_hw *hw, + struct rtl_stats *pstats) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + long undec_sm_pwdb; + + if (mac->opmode == NL80211_IFTYPE_ADHOC || + mac->opmode == NL80211_IFTYPE_AP) + return; + else + undec_sm_pwdb = rtlpriv->dm.undec_sm_pwdb; + + if (pstats->packet_toself || pstats->packet_beacon) { + if (undec_sm_pwdb < 0) + undec_sm_pwdb = pstats->rx_pwdb_all; + if (pstats->rx_pwdb_all > (u32) undec_sm_pwdb) { + undec_sm_pwdb = (((undec_sm_pwdb) * + (RX_SMOOTH_FACTOR - 1)) + + (pstats->rx_pwdb_all)) / (RX_SMOOTH_FACTOR); + undec_sm_pwdb = undec_sm_pwdb + 1; + } else { + undec_sm_pwdb = (((undec_sm_pwdb) * + (RX_SMOOTH_FACTOR - 1)) + + (pstats->rx_pwdb_all)) / (RX_SMOOTH_FACTOR); + } + rtlpriv->dm.undec_sm_pwdb = undec_sm_pwdb; + _rtl92de_update_rxsignalstatistics(hw, pstats); + } +} + +static void rtl92d_loop_over_streams(struct ieee80211_hw *hw, + struct rtl_stats *pstats) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int stream; + + for (stream = 0; stream < 2; stream++) { + if (pstats->rx_mimo_sig_qual[stream] != -1) { + if (rtlpriv->stats.rx_evm_percentage[stream] == 0) { + rtlpriv->stats.rx_evm_percentage[stream] = + pstats->rx_mimo_sig_qual[stream]; + } + rtlpriv->stats.rx_evm_percentage[stream] = + ((rtlpriv->stats.rx_evm_percentage[stream] + * (RX_SMOOTH_FACTOR - 1)) + + (pstats->rx_mimo_sig_qual[stream] * 1)) / + (RX_SMOOTH_FACTOR); + } + } +} + +static void _rtl92de_process_ui_link_quality(struct ieee80211_hw *hw, + struct rtl_stats *pstats) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 last_evm, tmpval; + + if (pstats->signalquality == 0) + return; + if (pstats->packet_toself || pstats->packet_beacon) { + if (rtlpriv->stats.ui_link_quality.total_num++ >= + PHY_LINKQUALITY_SLID_WIN_MAX) { + rtlpriv->stats.ui_link_quality.total_num = + PHY_LINKQUALITY_SLID_WIN_MAX; + last_evm = rtlpriv->stats.ui_link_quality.elements[ + rtlpriv->stats.ui_link_quality.index]; + rtlpriv->stats.ui_link_quality.total_val -= last_evm; + } + rtlpriv->stats.ui_link_quality.total_val += + pstats->signalquality; + rtlpriv->stats.ui_link_quality.elements[ + rtlpriv->stats.ui_link_quality.index++] = + pstats->signalquality; + if (rtlpriv->stats.ui_link_quality.index >= + PHY_LINKQUALITY_SLID_WIN_MAX) + rtlpriv->stats.ui_link_quality.index = 0; + tmpval = rtlpriv->stats.ui_link_quality.total_val / + rtlpriv->stats.ui_link_quality.total_num; + rtlpriv->stats.signal_quality = tmpval; + rtlpriv->stats.last_sigstrength_inpercent = tmpval; + rtl92d_loop_over_streams(hw, pstats); + } +} + +static void _rtl92de_process_phyinfo(struct ieee80211_hw *hw, + u8 *buffer, + struct rtl_stats *pcurrent_stats) +{ + + if (!pcurrent_stats->packet_matchbssid && + !pcurrent_stats->packet_beacon) + return; + + _rtl92de_process_ui_rssi(hw, pcurrent_stats); + _rtl92de_process_pwdb(hw, pcurrent_stats); + _rtl92de_process_ui_link_quality(hw, pcurrent_stats); +} + +static void _rtl92de_translate_rx_signal_stuff(struct ieee80211_hw *hw, + struct sk_buff *skb, + struct rtl_stats *pstats, + struct rx_desc_92d *pdesc, + struct rx_fwinfo_92d *p_drvinfo) +{ + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct ieee80211_hdr *hdr; + u8 *tmp_buf; + u8 *praddr; + u16 type, cfc; + __le16 fc; + bool packet_matchbssid, packet_toself, packet_beacon = false; + + tmp_buf = skb->data + pstats->rx_drvinfo_size + pstats->rx_bufshift; + hdr = (struct ieee80211_hdr *)tmp_buf; + fc = hdr->frame_control; + cfc = le16_to_cpu(fc); + type = WLAN_FC_GET_TYPE(fc); + praddr = hdr->addr1; + packet_matchbssid = ((IEEE80211_FTYPE_CTL != type) && + ether_addr_equal(mac->bssid, + (cfc & IEEE80211_FCTL_TODS) ? hdr->addr1 : + (cfc & IEEE80211_FCTL_FROMDS) ? hdr->addr2 : + hdr->addr3) && + (!pstats->hwerror) && (!pstats->crc) && (!pstats->icv)); + packet_toself = packet_matchbssid && + ether_addr_equal(praddr, rtlefuse->dev_addr); + if (ieee80211_is_beacon(fc)) + packet_beacon = true; + _rtl92de_query_rxphystatus(hw, pstats, pdesc, p_drvinfo, + packet_matchbssid, packet_toself, + packet_beacon); + _rtl92de_process_phyinfo(hw, tmp_buf, pstats); +} + +bool rtl92de_rx_query_desc(struct ieee80211_hw *hw, struct rtl_stats *stats, + struct ieee80211_rx_status *rx_status, + u8 *p_desc, struct sk_buff *skb) +{ + struct rx_fwinfo_92d *p_drvinfo; + struct rx_desc_92d *pdesc = (struct rx_desc_92d *)p_desc; + u32 phystatus = GET_RX_DESC_PHYST(pdesc); + + stats->length = (u16) GET_RX_DESC_PKT_LEN(pdesc); + stats->rx_drvinfo_size = (u8) GET_RX_DESC_DRV_INFO_SIZE(pdesc) * + RX_DRV_INFO_SIZE_UNIT; + stats->rx_bufshift = (u8) (GET_RX_DESC_SHIFT(pdesc) & 0x03); + stats->icv = (u16) GET_RX_DESC_ICV(pdesc); + stats->crc = (u16) GET_RX_DESC_CRC32(pdesc); + stats->hwerror = (stats->crc | stats->icv); + stats->decrypted = !GET_RX_DESC_SWDEC(pdesc); + stats->rate = (u8) GET_RX_DESC_RXMCS(pdesc); + stats->shortpreamble = (u16) GET_RX_DESC_SPLCP(pdesc); + stats->isampdu = (bool) (GET_RX_DESC_PAGGR(pdesc) == 1); + stats->isfirst_ampdu = (bool) ((GET_RX_DESC_PAGGR(pdesc) == 1) + && (GET_RX_DESC_FAGGR(pdesc) == 1)); + stats->timestamp_low = GET_RX_DESC_TSFL(pdesc); + stats->rx_is40Mhzpacket = (bool) GET_RX_DESC_BW(pdesc); + rx_status->freq = hw->conf.chandef.chan->center_freq; + rx_status->band = hw->conf.chandef.chan->band; + if (GET_RX_DESC_CRC32(pdesc)) + rx_status->flag |= RX_FLAG_FAILED_FCS_CRC; + if (!GET_RX_DESC_SWDEC(pdesc)) + rx_status->flag |= RX_FLAG_DECRYPTED; + if (GET_RX_DESC_BW(pdesc)) + rx_status->flag |= RX_FLAG_40MHZ; + if (GET_RX_DESC_RXHT(pdesc)) + rx_status->flag |= RX_FLAG_HT; + rx_status->flag |= RX_FLAG_MACTIME_START; + if (stats->decrypted) + rx_status->flag |= RX_FLAG_DECRYPTED; + rx_status->rate_idx = rtlwifi_rate_mapping(hw, + (bool)GET_RX_DESC_RXHT(pdesc), + (u8)GET_RX_DESC_RXMCS(pdesc), + (bool)GET_RX_DESC_PAGGR(pdesc)); + rx_status->mactime = GET_RX_DESC_TSFL(pdesc); + if (phystatus) { + p_drvinfo = (struct rx_fwinfo_92d *)(skb->data + + stats->rx_bufshift); + _rtl92de_translate_rx_signal_stuff(hw, + skb, stats, pdesc, + p_drvinfo); + } + /*rx_status->qual = stats->signal; */ + rx_status->signal = stats->rssi + 10; + /*rx_status->noise = -stats->noise; */ + return true; +} + +static void _rtl92de_insert_emcontent(struct rtl_tcb_desc *ptcb_desc, + u8 *virtualaddress) +{ + memset(virtualaddress, 0, 8); + + SET_EARLYMODE_PKTNUM(virtualaddress, ptcb_desc->empkt_num); + SET_EARLYMODE_LEN0(virtualaddress, ptcb_desc->empkt_len[0]); + SET_EARLYMODE_LEN1(virtualaddress, ptcb_desc->empkt_len[1]); + SET_EARLYMODE_LEN2_1(virtualaddress, ptcb_desc->empkt_len[2] & 0xF); + SET_EARLYMODE_LEN2_2(virtualaddress, ptcb_desc->empkt_len[2] >> 4); + SET_EARLYMODE_LEN3(virtualaddress, ptcb_desc->empkt_len[3]); + SET_EARLYMODE_LEN4(virtualaddress, ptcb_desc->empkt_len[4]); +} + +void rtl92de_tx_fill_desc(struct ieee80211_hw *hw, + struct ieee80211_hdr *hdr, u8 *pdesc_tx, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff *skb, + u8 hw_queue, struct rtl_tcb_desc *ptcb_desc) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + u8 *pdesc = pdesc_tx; + u16 seq_number; + __le16 fc = hdr->frame_control; + unsigned int buf_len = 0; + unsigned int skb_len = skb->len; + u8 fw_qsel = _rtl92de_map_hwqueue_to_fwqueue(skb, hw_queue); + bool firstseg = ((hdr->seq_ctrl & + cpu_to_le16(IEEE80211_SCTL_FRAG)) == 0); + bool lastseg = ((hdr->frame_control & + cpu_to_le16(IEEE80211_FCTL_MOREFRAGS)) == 0); + dma_addr_t mapping; + u8 bw_40 = 0; + + if (mac->opmode == NL80211_IFTYPE_STATION) { + bw_40 = mac->bw_40; + } else if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_ADHOC) { + if (sta) + bw_40 = sta->bandwidth >= IEEE80211_STA_RX_BW_40; + } + seq_number = (le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_SEQ) >> 4; + rtl_get_tcb_desc(hw, info, sta, skb, ptcb_desc); + /* reserve 8 byte for AMPDU early mode */ + if (rtlhal->earlymode_enable) { + skb_push(skb, EM_HDR_LEN); + memset(skb->data, 0, EM_HDR_LEN); + } + buf_len = skb->len; + mapping = pci_map_single(rtlpci->pdev, skb->data, skb->len, + PCI_DMA_TODEVICE); + if (pci_dma_mapping_error(rtlpci->pdev, mapping)) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "DMA mapping error"); + return; + } + CLEAR_PCI_TX_DESC_CONTENT(pdesc, sizeof(struct tx_desc_92d)); + if (ieee80211_is_nullfunc(fc) || ieee80211_is_ctl(fc)) { + firstseg = true; + lastseg = true; + } + if (firstseg) { + if (rtlhal->earlymode_enable) { + SET_TX_DESC_PKT_OFFSET(pdesc, 1); + SET_TX_DESC_OFFSET(pdesc, USB_HWDESC_HEADER_LEN + + EM_HDR_LEN); + if (ptcb_desc->empkt_num) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_LOUD, + "Insert 8 byte.pTcb->EMPktNum:%d\n", + ptcb_desc->empkt_num); + _rtl92de_insert_emcontent(ptcb_desc, + (u8 *)(skb->data)); + } + } else { + SET_TX_DESC_OFFSET(pdesc, USB_HWDESC_HEADER_LEN); + } + /* 5G have no CCK rate */ + if (rtlhal->current_bandtype == BAND_ON_5G) + if (ptcb_desc->hw_rate < DESC92_RATE6M) + ptcb_desc->hw_rate = DESC92_RATE6M; + SET_TX_DESC_TX_RATE(pdesc, ptcb_desc->hw_rate); + if (ptcb_desc->use_shortgi || ptcb_desc->use_shortpreamble) + SET_TX_DESC_DATA_SHORTGI(pdesc, 1); + + if (rtlhal->macphymode == DUALMAC_DUALPHY && + ptcb_desc->hw_rate == DESC92_RATEMCS7) + SET_TX_DESC_DATA_SHORTGI(pdesc, 1); + + if (info->flags & IEEE80211_TX_CTL_AMPDU) { + SET_TX_DESC_AGG_ENABLE(pdesc, 1); + SET_TX_DESC_MAX_AGG_NUM(pdesc, 0x14); + } + SET_TX_DESC_SEQ(pdesc, seq_number); + SET_TX_DESC_RTS_ENABLE(pdesc, ((ptcb_desc->rts_enable && + !ptcb_desc->cts_enable) ? 1 : 0)); + SET_TX_DESC_HW_RTS_ENABLE(pdesc, ((ptcb_desc->rts_enable + || ptcb_desc->cts_enable) ? 1 : 0)); + SET_TX_DESC_CTS2SELF(pdesc, ((ptcb_desc->cts_enable) ? 1 : 0)); + SET_TX_DESC_RTS_STBC(pdesc, ((ptcb_desc->rts_stbc) ? 1 : 0)); + /* 5G have no CCK rate */ + if (rtlhal->current_bandtype == BAND_ON_5G) + if (ptcb_desc->rts_rate < DESC92_RATE6M) + ptcb_desc->rts_rate = DESC92_RATE6M; + SET_TX_DESC_RTS_RATE(pdesc, ptcb_desc->rts_rate); + SET_TX_DESC_RTS_BW(pdesc, 0); + SET_TX_DESC_RTS_SC(pdesc, ptcb_desc->rts_sc); + SET_TX_DESC_RTS_SHORT(pdesc, ((ptcb_desc->rts_rate <= + DESC92_RATE54M) ? + (ptcb_desc->rts_use_shortpreamble ? 1 : 0) : + (ptcb_desc->rts_use_shortgi ? 1 : 0))); + if (bw_40) { + if (ptcb_desc->packet_bw) { + SET_TX_DESC_DATA_BW(pdesc, 1); + SET_TX_DESC_TX_SUB_CARRIER(pdesc, 3); + } else { + SET_TX_DESC_DATA_BW(pdesc, 0); + SET_TX_DESC_TX_SUB_CARRIER(pdesc, + mac->cur_40_prime_sc); + } + } else { + SET_TX_DESC_DATA_BW(pdesc, 0); + SET_TX_DESC_TX_SUB_CARRIER(pdesc, 0); + } + SET_TX_DESC_LINIP(pdesc, 0); + SET_TX_DESC_PKT_SIZE(pdesc, (u16) skb_len); + if (sta) { + u8 ampdu_density = sta->ht_cap.ampdu_density; + SET_TX_DESC_AMPDU_DENSITY(pdesc, ampdu_density); + } + if (info->control.hw_key) { + struct ieee80211_key_conf *keyconf; + + keyconf = info->control.hw_key; + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + case WLAN_CIPHER_SUITE_TKIP: + SET_TX_DESC_SEC_TYPE(pdesc, 0x1); + break; + case WLAN_CIPHER_SUITE_CCMP: + SET_TX_DESC_SEC_TYPE(pdesc, 0x3); + break; + default: + SET_TX_DESC_SEC_TYPE(pdesc, 0x0); + break; + + } + } + SET_TX_DESC_PKT_ID(pdesc, 0); + SET_TX_DESC_QUEUE_SEL(pdesc, fw_qsel); + SET_TX_DESC_DATA_RATE_FB_LIMIT(pdesc, 0x1F); + SET_TX_DESC_RTS_RATE_FB_LIMIT(pdesc, 0xF); + SET_TX_DESC_DISABLE_FB(pdesc, ptcb_desc->disable_ratefallback ? + 1 : 0); + SET_TX_DESC_USE_RATE(pdesc, ptcb_desc->use_driver_rate ? 1 : 0); + + /* Set TxRate and RTSRate in TxDesc */ + /* This prevent Tx initial rate of new-coming packets */ + /* from being overwritten by retried packet rate.*/ + if (!ptcb_desc->use_driver_rate) { + SET_TX_DESC_RTS_RATE(pdesc, 0x08); + /* SET_TX_DESC_TX_RATE(pdesc, 0x0b); */ + } + if (ieee80211_is_data_qos(fc)) { + if (mac->rdg_en) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "Enable RDG function\n"); + SET_TX_DESC_RDG_ENABLE(pdesc, 1); + SET_TX_DESC_HTC(pdesc, 1); + } + } + } + + SET_TX_DESC_FIRST_SEG(pdesc, (firstseg ? 1 : 0)); + SET_TX_DESC_LAST_SEG(pdesc, (lastseg ? 1 : 0)); + SET_TX_DESC_TX_BUFFER_SIZE(pdesc, (u16) buf_len); + SET_TX_DESC_TX_BUFFER_ADDRESS(pdesc, mapping); + if (rtlpriv->dm.useramask) { + SET_TX_DESC_RATE_ID(pdesc, ptcb_desc->ratr_index); + SET_TX_DESC_MACID(pdesc, ptcb_desc->mac_id); + } else { + SET_TX_DESC_RATE_ID(pdesc, 0xC + ptcb_desc->ratr_index); + SET_TX_DESC_MACID(pdesc, ptcb_desc->ratr_index); + } + if (ieee80211_is_data_qos(fc)) + SET_TX_DESC_QOS(pdesc, 1); + + if ((!ieee80211_is_data_qos(fc)) && ppsc->fwctrl_lps) { + SET_TX_DESC_HWSEQ_EN(pdesc, 1); + SET_TX_DESC_PKT_ID(pdesc, 8); + } + SET_TX_DESC_MORE_FRAG(pdesc, (lastseg ? 0 : 1)); + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, "\n"); +} + +void rtl92de_tx_fill_cmddesc(struct ieee80211_hw *hw, + u8 *pdesc, bool firstseg, + bool lastseg, struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtlpriv); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + u8 fw_queue = QSLT_BEACON; + dma_addr_t mapping = pci_map_single(rtlpci->pdev, + skb->data, skb->len, PCI_DMA_TODEVICE); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(skb->data); + __le16 fc = hdr->frame_control; + + if (pci_dma_mapping_error(rtlpci->pdev, mapping)) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "DMA mapping error"); + return; + } + CLEAR_PCI_TX_DESC_CONTENT(pdesc, TX_DESC_SIZE); + if (firstseg) + SET_TX_DESC_OFFSET(pdesc, USB_HWDESC_HEADER_LEN); + /* 5G have no CCK rate + * Caution: The macros below are multi-line expansions. + * The braces are needed no matter what checkpatch says + */ + if (rtlhal->current_bandtype == BAND_ON_5G) { + SET_TX_DESC_TX_RATE(pdesc, DESC92_RATE6M); + } else { + SET_TX_DESC_TX_RATE(pdesc, DESC92_RATE1M); + } + SET_TX_DESC_SEQ(pdesc, 0); + SET_TX_DESC_LINIP(pdesc, 0); + SET_TX_DESC_QUEUE_SEL(pdesc, fw_queue); + SET_TX_DESC_FIRST_SEG(pdesc, 1); + SET_TX_DESC_LAST_SEG(pdesc, 1); + SET_TX_DESC_TX_BUFFER_SIZE(pdesc, (u16)skb->len); + SET_TX_DESC_TX_BUFFER_ADDRESS(pdesc, mapping); + SET_TX_DESC_RATE_ID(pdesc, 7); + SET_TX_DESC_MACID(pdesc, 0); + SET_TX_DESC_PKT_SIZE(pdesc, (u16) (skb->len)); + SET_TX_DESC_FIRST_SEG(pdesc, 1); + SET_TX_DESC_LAST_SEG(pdesc, 1); + SET_TX_DESC_OFFSET(pdesc, 0x20); + SET_TX_DESC_USE_RATE(pdesc, 1); + + if (!ieee80211_is_data_qos(fc) && ppsc->fwctrl_lps) { + SET_TX_DESC_HWSEQ_EN(pdesc, 1); + SET_TX_DESC_PKT_ID(pdesc, 8); + } + + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_LOUD, + "H2C Tx Cmd Content", pdesc, TX_DESC_SIZE); + wmb(); + SET_TX_DESC_OWN(pdesc, 1); +} + +void rtl92de_set_desc(u8 *pdesc, bool istx, u8 desc_name, u8 *val) +{ + if (istx) { + switch (desc_name) { + case HW_DESC_OWN: + wmb(); + SET_TX_DESC_OWN(pdesc, 1); + break; + case HW_DESC_TX_NEXTDESC_ADDR: + SET_TX_DESC_NEXT_DESC_ADDRESS(pdesc, *(u32 *) val); + break; + default: + RT_ASSERT(false, "ERR txdesc :%d not process\n", + desc_name); + break; + } + } else { + switch (desc_name) { + case HW_DESC_RXOWN: + wmb(); + SET_RX_DESC_OWN(pdesc, 1); + break; + case HW_DESC_RXBUFF_ADDR: + SET_RX_DESC_BUFF_ADDR(pdesc, *(u32 *) val); + break; + case HW_DESC_RXPKT_LEN: + SET_RX_DESC_PKT_LEN(pdesc, *(u32 *) val); + break; + case HW_DESC_RXERO: + SET_RX_DESC_EOR(pdesc, 1); + break; + default: + RT_ASSERT(false, "ERR rxdesc :%d not process\n", + desc_name); + break; + } + } +} + +u32 rtl92de_get_desc(u8 *p_desc, bool istx, u8 desc_name) +{ + u32 ret = 0; + + if (istx) { + switch (desc_name) { + case HW_DESC_OWN: + ret = GET_TX_DESC_OWN(p_desc); + break; + case HW_DESC_TXBUFF_ADDR: + ret = GET_TX_DESC_TX_BUFFER_ADDRESS(p_desc); + break; + default: + RT_ASSERT(false, "ERR txdesc :%d not process\n", + desc_name); + break; + } + } else { + struct rx_desc_92c *pdesc = (struct rx_desc_92c *)p_desc; + switch (desc_name) { + case HW_DESC_OWN: + ret = GET_RX_DESC_OWN(pdesc); + break; + case HW_DESC_RXPKT_LEN: + ret = GET_RX_DESC_PKT_LEN(pdesc); + break; + default: + RT_ASSERT(false, "ERR rxdesc :%d not process\n", + desc_name); + break; + } + } + return ret; +} + +void rtl92de_tx_polling(struct ieee80211_hw *hw, u8 hw_queue) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + if (hw_queue == BEACON_QUEUE) + rtl_write_word(rtlpriv, REG_PCIE_CTRL_REG, BIT(4)); + else + rtl_write_word(rtlpriv, REG_PCIE_CTRL_REG, + BIT(0) << (hw_queue)); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192de/trx.h b/drivers/net/wireless/rtlwifi/rtl8192de/trx.h new file mode 100644 index 0000000..c1b5dfb --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192de/trx.h @@ -0,0 +1,747 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92DE_TRX_H__ +#define __RTL92DE_TRX_H__ + +#define TX_DESC_SIZE 64 +#define TX_DESC_AGGR_SUBFRAME_SIZE 32 + +#define RX_DESC_SIZE 32 +#define RX_DRV_INFO_SIZE_UNIT 8 + +#define TX_DESC_NEXT_DESC_OFFSET 40 +#define USB_HWDESC_HEADER_LEN 32 +#define CRCLENGTH 4 + +/* Define a macro that takes a le32 word, converts it to host ordering, + * right shifts by a specified count, creates a mask of the specified + * bit count, and extracts that number of bits. + */ + +#define SHIFT_AND_MASK_LE(__pdesc, __shift, __mask) \ + ((le32_to_cpu(*(((__le32 *)(__pdesc)))) >> (__shift)) & \ + BIT_LEN_MASK_32(__mask)) + +/* Define a macro that clears a bit field in an le32 word and + * sets the specified value into that bit field. The resulting + * value remains in le32 ordering; however, it is properly converted + * to host ordering for the clear and set operations before conversion + * back to le32. + */ + +#define SET_BITS_OFFSET_LE(__pdesc, __shift, __len, __val) \ + (*(__le32 *)(__pdesc) = \ + (cpu_to_le32((le32_to_cpu(*((__le32 *)(__pdesc))) & \ + (~(BIT_OFFSET_LEN_MASK_32((__shift), __len)))) | \ + (((u32)(__val) & BIT_LEN_MASK_32(__len)) << (__shift))))); + +/* macros to read/write various fields in RX or TX descriptors */ + +#define SET_TX_DESC_PKT_SIZE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 0, 16, __val) +#define SET_TX_DESC_OFFSET(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 16, 8, __val) +#define SET_TX_DESC_BMC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 24, 1, __val) +#define SET_TX_DESC_HTC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 25, 1, __val) +#define SET_TX_DESC_LAST_SEG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 26, 1, __val) +#define SET_TX_DESC_FIRST_SEG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 27, 1, __val) +#define SET_TX_DESC_LINIP(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 28, 1, __val) +#define SET_TX_DESC_NO_ACM(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 29, 1, __val) +#define SET_TX_DESC_GF(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 30, 1, __val) +#define SET_TX_DESC_OWN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 31, 1, __val) + +#define GET_TX_DESC_PKT_SIZE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 0, 16) +#define GET_TX_DESC_OFFSET(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 16, 8) +#define GET_TX_DESC_BMC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 24, 1) +#define GET_TX_DESC_HTC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 25, 1) +#define GET_TX_DESC_LAST_SEG(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 26, 1) +#define GET_TX_DESC_FIRST_SEG(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 27, 1) +#define GET_TX_DESC_LINIP(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 28, 1) +#define GET_TX_DESC_NO_ACM(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 29, 1) +#define GET_TX_DESC_GF(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 30, 1) +#define GET_TX_DESC_OWN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 31, 1) + +#define SET_TX_DESC_MACID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 0, 5, __val) +#define SET_TX_DESC_AGG_ENABLE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 5, 1, __val) +#define SET_TX_DESC_BK(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 6, 1, __val) +#define SET_TX_DESC_RDG_ENABLE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 7, 1, __val) +#define SET_TX_DESC_QUEUE_SEL(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 8, 5, __val) +#define SET_TX_DESC_RDG_NAV_EXT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 13, 1, __val) +#define SET_TX_DESC_LSIG_TXOP_EN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 14, 1, __val) +#define SET_TX_DESC_PIFS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 15, 1, __val) +#define SET_TX_DESC_RATE_ID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 16, 4, __val) +#define SET_TX_DESC_NAV_USE_HDR(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 20, 1, __val) +#define SET_TX_DESC_EN_DESC_ID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 21, 1, __val) +#define SET_TX_DESC_SEC_TYPE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 22, 2, __val) +#define SET_TX_DESC_PKT_OFFSET(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+4, 26, 8, __val) + +#define GET_TX_DESC_MACID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 0, 5) +#define GET_TX_DESC_AGG_ENABLE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 5, 1) +#define GET_TX_DESC_AGG_BREAK(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 6, 1) +#define GET_TX_DESC_RDG_ENABLE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 7, 1) +#define GET_TX_DESC_QUEUE_SEL(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 8, 5) +#define GET_TX_DESC_RDG_NAV_EXT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 13, 1) +#define GET_TX_DESC_LSIG_TXOP_EN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 14, 1) +#define GET_TX_DESC_PIFS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 15, 1) +#define GET_TX_DESC_RATE_ID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 16, 4) +#define GET_TX_DESC_NAV_USE_HDR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 20, 1) +#define GET_TX_DESC_EN_DESC_ID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 21, 1) +#define GET_TX_DESC_SEC_TYPE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 22, 2) +#define GET_TX_DESC_PKT_OFFSET(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 24, 8) + +#define SET_TX_DESC_RTS_RC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 0, 6, __val) +#define SET_TX_DESC_DATA_RC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 6, 6, __val) +#define SET_TX_DESC_BAR_RTY_TH(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 14, 2, __val) +#define SET_TX_DESC_MORE_FRAG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 17, 1, __val) +#define SET_TX_DESC_RAW(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 18, 1, __val) +#define SET_TX_DESC_CCX(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 19, 1, __val) +#define SET_TX_DESC_AMPDU_DENSITY(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 20, 3, __val) +#define SET_TX_DESC_ANTSEL_A(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 24, 1, __val) +#define SET_TX_DESC_ANTSEL_B(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 25, 1, __val) +#define SET_TX_DESC_TX_ANT_CCK(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 26, 2, __val) +#define SET_TX_DESC_TX_ANTL(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 28, 2, __val) +#define SET_TX_DESC_TX_ANT_HT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+8, 30, 2, __val) + +#define GET_TX_DESC_RTS_RC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 0, 6) +#define GET_TX_DESC_DATA_RC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 6, 6) +#define GET_TX_DESC_BAR_RTY_TH(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 14, 2) +#define GET_TX_DESC_MORE_FRAG(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 17, 1) +#define GET_TX_DESC_RAW(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 18, 1) +#define GET_TX_DESC_CCX(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 19, 1) +#define GET_TX_DESC_AMPDU_DENSITY(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 20, 3) +#define GET_TX_DESC_ANTSEL_A(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 24, 1) +#define GET_TX_DESC_ANTSEL_B(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 25, 1) +#define GET_TX_DESC_TX_ANT_CCK(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 26, 2) +#define GET_TX_DESC_TX_ANTL(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 28, 2) +#define GET_TX_DESC_TX_ANT_HT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 30, 2) + +#define SET_TX_DESC_NEXT_HEAP_PAGE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+12, 0, 8, __val) +#define SET_TX_DESC_TAIL_PAGE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+12, 8, 8, __val) +#define SET_TX_DESC_SEQ(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+12, 16, 12, __val) +#define SET_TX_DESC_PKT_ID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+12, 28, 4, __val) + +#define GET_TX_DESC_NEXT_HEAP_PAGE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 0, 8) +#define GET_TX_DESC_TAIL_PAGE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 8, 8) +#define GET_TX_DESC_SEQ(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 16, 12) +#define GET_TX_DESC_PKT_ID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 28, 4) + +#define SET_TX_DESC_RTS_RATE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 0, 5, __val) +#define SET_TX_DESC_AP_DCFE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 5, 1, __val) +#define SET_TX_DESC_QOS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 6, 1, __val) +#define SET_TX_DESC_HWSEQ_EN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 7, 1, __val) +#define SET_TX_DESC_USE_RATE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 8, 1, __val) +#define SET_TX_DESC_DISABLE_RTS_FB(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 9, 1, __val) +#define SET_TX_DESC_DISABLE_FB(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 10, 1, __val) +#define SET_TX_DESC_CTS2SELF(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 11, 1, __val) +#define SET_TX_DESC_RTS_ENABLE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 12, 1, __val) +#define SET_TX_DESC_HW_RTS_ENABLE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 13, 1, __val) +#define SET_TX_DESC_PORT_ID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 14, 1, __val) +#define SET_TX_DESC_WAIT_DCTS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 18, 1, __val) +#define SET_TX_DESC_CTS2AP_EN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 19, 1, __val) +#define SET_TX_DESC_TX_SUB_CARRIER(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 20, 2, __val) +#define SET_TX_DESC_TX_STBC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 22, 2, __val) +#define SET_TX_DESC_DATA_SHORT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 24, 1, __val) +#define SET_TX_DESC_DATA_BW(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 25, 1, __val) +#define SET_TX_DESC_RTS_SHORT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 26, 1, __val) +#define SET_TX_DESC_RTS_BW(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 27, 1, __val) +#define SET_TX_DESC_RTS_SC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 28, 2, __val) +#define SET_TX_DESC_RTS_STBC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+16, 30, 2, __val) + +#define GET_TX_DESC_RTS_RATE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 0, 5) +#define GET_TX_DESC_AP_DCFE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 5, 1) +#define GET_TX_DESC_QOS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 6, 1) +#define GET_TX_DESC_HWSEQ_EN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 7, 1) +#define GET_TX_DESC_USE_RATE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 8, 1) +#define GET_TX_DESC_DISABLE_RTS_FB(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 9, 1) +#define GET_TX_DESC_DISABLE_FB(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 10, 1) +#define GET_TX_DESC_CTS2SELF(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 11, 1) +#define GET_TX_DESC_RTS_ENABLE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 12, 1) +#define GET_TX_DESC_HW_RTS_ENABLE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 13, 1) +#define GET_TX_DESC_PORT_ID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 14, 1) +#define GET_TX_DESC_WAIT_DCTS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 18, 1) +#define GET_TX_DESC_CTS2AP_EN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 19, 1) +#define GET_TX_DESC_TX_SUB_CARRIER(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 20, 2) +#define GET_TX_DESC_TX_STBC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 22, 2) +#define GET_TX_DESC_DATA_SHORT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 24, 1) +#define GET_TX_DESC_DATA_BW(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 25, 1) +#define GET_TX_DESC_RTS_SHORT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 26, 1) +#define GET_TX_DESC_RTS_BW(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 27, 1) +#define GET_TX_DESC_RTS_SC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 28, 2) +#define GET_TX_DESC_RTS_STBC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 30, 2) + +#define SET_TX_DESC_TX_RATE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 0, 6, __val) +#define SET_TX_DESC_DATA_SHORTGI(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 6, 1, __val) +#define SET_TX_DESC_CCX_TAG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 7, 1, __val) +#define SET_TX_DESC_DATA_RATE_FB_LIMIT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 8, 5, __val) +#define SET_TX_DESC_RTS_RATE_FB_LIMIT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 13, 4, __val) +#define SET_TX_DESC_RETRY_LIMIT_ENABLE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 17, 1, __val) +#define SET_TX_DESC_DATA_RETRY_LIMIT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 18, 6, __val) +#define SET_TX_DESC_USB_TXAGG_NUM(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+20, 24, 8, __val) + +#define GET_TX_DESC_TX_RATE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 0, 6) +#define GET_TX_DESC_DATA_SHORTGI(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 6, 1) +#define GET_TX_DESC_CCX_TAG(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 7, 1) +#define GET_TX_DESC_DATA_RATE_FB_LIMIT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 8, 5) +#define GET_TX_DESC_RTS_RATE_FB_LIMIT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 13, 4) +#define GET_TX_DESC_RETRY_LIMIT_ENABLE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 17, 1) +#define GET_TX_DESC_DATA_RETRY_LIMIT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 18, 6) +#define GET_TX_DESC_USB_TXAGG_NUM(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 24, 8) + +#define SET_TX_DESC_TXAGC_A(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 0, 5, __val) +#define SET_TX_DESC_TXAGC_B(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 5, 5, __val) +#define SET_TX_DESC_USE_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 10, 1, __val) +#define SET_TX_DESC_MAX_AGG_NUM(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 11, 5, __val) +#define SET_TX_DESC_MCSG1_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 16, 4, __val) +#define SET_TX_DESC_MCSG2_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 20, 4, __val) +#define SET_TX_DESC_MCSG3_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 24, 4, __val) +#define SET_TX_DESC_MCS7_SGI_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 28, 4, __val) + +#define GET_TX_DESC_TXAGC_A(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 0, 5) +#define GET_TX_DESC_TXAGC_B(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 5, 5) +#define GET_TX_DESC_USE_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 10, 1) +#define GET_TX_DESC_MAX_AGG_NUM(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 11, 5) +#define GET_TX_DESC_MCSG1_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 16, 4) +#define GET_TX_DESC_MCSG2_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 20, 4) +#define GET_TX_DESC_MCSG3_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 24, 4) +#define GET_TX_DESC_MCS7_SGI_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 28, 4) + +#define SET_TX_DESC_TX_BUFFER_SIZE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+28, 0, 16, __val) +#define SET_TX_DESC_MCSG4_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+28, 16, 4, __val) +#define SET_TX_DESC_MCSG5_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+28, 20, 4, __val) +#define SET_TX_DESC_MCSG6_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+28, 24, 4, __val) +#define SET_TX_DESC_MCS15_SGI_MAX_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+28, 28, 4, __val) + +#define GET_TX_DESC_TX_BUFFER_SIZE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+28, 0, 16) +#define GET_TX_DESC_MCSG4_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+28, 16, 4) +#define GET_TX_DESC_MCSG5_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+28, 20, 4) +#define GET_TX_DESC_MCSG6_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+28, 24, 4) +#define GET_TX_DESC_MCS15_SGI_MAX_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+28, 28, 4) + +#define SET_TX_DESC_TX_BUFFER_ADDRESS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+32, 0, 32, __val) +#define SET_TX_DESC_TX_BUFFER_ADDRESS64(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+36, 0, 32, __val) + +#define GET_TX_DESC_TX_BUFFER_ADDRESS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+32, 0, 32) +#define GET_TX_DESC_TX_BUFFER_ADDRESS64(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+36, 0, 32) + +#define SET_TX_DESC_NEXT_DESC_ADDRESS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+40, 0, 32, __val) +#define SET_TX_DESC_NEXT_DESC_ADDRESS64(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+44, 0, 32, __val) + +#define GET_TX_DESC_NEXT_DESC_ADDRESS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+40, 0, 32) +#define GET_TX_DESC_NEXT_DESC_ADDRESS64(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+44, 0, 32) + +#define GET_RX_DESC_PKT_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 0, 14) +#define GET_RX_DESC_CRC32(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 14, 1) +#define GET_RX_DESC_ICV(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 15, 1) +#define GET_RX_DESC_DRV_INFO_SIZE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 16, 4) +#define GET_RX_DESC_SECURITY(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 20, 3) +#define GET_RX_DESC_QOS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 23, 1) +#define GET_RX_DESC_SHIFT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 24, 2) +#define GET_RX_DESC_PHYST(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 26, 1) +#define GET_RX_DESC_SWDEC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 27, 1) +#define GET_RX_DESC_LS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 28, 1) +#define GET_RX_DESC_FS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 29, 1) +#define GET_RX_DESC_EOR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 30, 1) +#define GET_RX_DESC_OWN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 31, 1) + +#define SET_RX_DESC_PKT_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 0, 14, __val) +#define SET_RX_DESC_EOR(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 30, 1, __val) +#define SET_RX_DESC_OWN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 31, 1, __val) + +#define GET_RX_DESC_MACID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 0, 5) +#define GET_RX_DESC_TID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 5, 4) +#define GET_RX_DESC_HWRSVD(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 9, 5) +#define GET_RX_DESC_PAGGR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 14, 1) +#define GET_RX_DESC_FAGGR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 15, 1) +#define GET_RX_DESC_A1_FIT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 16, 4) +#define GET_RX_DESC_A2_FIT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 20, 4) +#define GET_RX_DESC_PAM(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 24, 1) +#define GET_RX_DESC_PWR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 25, 1) +#define GET_RX_DESC_MD(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 26, 1) +#define GET_RX_DESC_MF(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 27, 1) +#define GET_RX_DESC_TYPE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 28, 2) +#define GET_RX_DESC_MC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 30, 1) +#define GET_RX_DESC_BC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+4, 31, 1) +#define GET_RX_DESC_SEQ(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 0, 12) +#define GET_RX_DESC_FRAG(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 12, 4) +#define GET_RX_DESC_NEXT_PKT_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 16, 14) +#define GET_RX_DESC_NEXT_IND(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 30, 1) +#define GET_RX_DESC_RSVD(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+8, 31, 1) + +#define GET_RX_DESC_RXMCS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 0, 6) +#define GET_RX_DESC_RXHT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 6, 1) +#define GET_RX_DESC_SPLCP(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 8, 1) +#define GET_RX_DESC_BW(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 9, 1) +#define GET_RX_DESC_HTC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 10, 1) +#define GET_RX_DESC_HWPC_ERR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 14, 1) +#define GET_RX_DESC_HWPC_IND(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 15, 1) +#define GET_RX_DESC_IV0(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+12, 16, 16) + +#define GET_RX_DESC_IV1(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+16, 0, 32) +#define GET_RX_DESC_TSFL(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+20, 0, 32) + +#define GET_RX_DESC_BUFF_ADDR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+24, 0, 32) +#define GET_RX_DESC_BUFF_ADDR64(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc+28, 0, 32) + +#define SET_RX_DESC_BUFF_ADDR(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+24, 0, 32, __val) +#define SET_RX_DESC_BUFF_ADDR64(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc+28, 0, 32, __val) + +#define CLEAR_PCI_TX_DESC_CONTENT(__pdesc, _size) \ + memset((void *)__pdesc, 0, \ + min_t(size_t, _size, TX_DESC_NEXT_DESC_OFFSET)) + +/* For 92D early mode */ +#define SET_EARLYMODE_PKTNUM(__paddr, __value) \ + SET_BITS_OFFSET_LE(__paddr, 0, 3, __value) +#define SET_EARLYMODE_LEN0(__paddr, __value) \ + SET_BITS_OFFSET_LE(__paddr, 4, 12, __value) +#define SET_EARLYMODE_LEN1(__paddr, __value) \ + SET_BITS_OFFSET_LE(__paddr, 16, 12, __value) +#define SET_EARLYMODE_LEN2_1(__paddr, __value) \ + SET_BITS_OFFSET_LE(__paddr, 28, 4, __value) +#define SET_EARLYMODE_LEN2_2(__paddr, __value) \ + SET_BITS_OFFSET_LE(__paddr+4, 0, 8, __value) +#define SET_EARLYMODE_LEN3(__paddr, __value) \ + SET_BITS_OFFSET_LE(__paddr+4, 8, 12, __value) +#define SET_EARLYMODE_LEN4(__paddr, __value) \ + SET_BITS_OFFSET_LE(__paddr+4, 20, 12, __value) + +struct rx_fwinfo_92d { + u8 gain_trsw[4]; + u8 pwdb_all; + u8 cfosho[4]; + u8 cfotail[4]; + char rxevm[2]; + char rxsnr[4]; + u8 pdsnr[2]; + u8 csi_current[2]; + u8 csi_target[2]; + u8 sigevm; + u8 max_ex_pwr; + u8 ex_intf_flag:1; + u8 sgi_en:1; + u8 rxsc:2; + u8 reserve:4; +} __packed; + +struct tx_desc_92d { + u32 pktsize:16; + u32 offset:8; + u32 bmc:1; + u32 htc:1; + u32 lastseg:1; + u32 firstseg:1; + u32 linip:1; + u32 noacm:1; + u32 gf:1; + u32 own:1; + + u32 macid:5; + u32 agg_en:1; + u32 bk:1; + u32 rdg_en:1; + u32 queuesel:5; + u32 rd_nav_ext:1; + u32 lsig_txop_en:1; + u32 pifs:1; + u32 rateid:4; + u32 nav_usehdr:1; + u32 en_descid:1; + u32 sectype:2; + u32 pktoffset:8; + + u32 rts_rc:6; + u32 data_rc:6; + u32 rsvd0:2; + u32 bar_retryht:2; + u32 rsvd1:1; + u32 morefrag:1; + u32 raw:1; + u32 ccx:1; + u32 ampdudensity:3; + u32 rsvd2:1; + u32 ant_sela:1; + u32 ant_selb:1; + u32 txant_cck:2; + u32 txant_l:2; + u32 txant_ht:2; + + u32 nextheadpage:8; + u32 tailpage:8; + u32 seq:12; + u32 pktid:4; + + u32 rtsrate:5; + u32 apdcfe:1; + u32 qos:1; + u32 hwseq_enable:1; + u32 userrate:1; + u32 dis_rtsfb:1; + u32 dis_datafb:1; + u32 cts2self:1; + u32 rts_en:1; + u32 hwrts_en:1; + u32 portid:1; + u32 rsvd3:3; + u32 waitdcts:1; + u32 cts2ap_en:1; + u32 txsc:2; + u32 stbc:2; + u32 txshort:1; + u32 txbw:1; + u32 rtsshort:1; + u32 rtsbw:1; + u32 rtssc:2; + u32 rtsstbc:2; + + u32 txrate:6; + u32 shortgi:1; + u32 ccxt:1; + u32 txrate_fb_lmt:5; + u32 rtsrate_fb_lmt:4; + u32 retrylmt_en:1; + u32 txretrylmt:6; + u32 usb_txaggnum:8; + + u32 txagca:5; + u32 txagcb:5; + u32 usemaxlen:1; + u32 maxaggnum:5; + u32 mcsg1maxlen:4; + u32 mcsg2maxlen:4; + u32 mcsg3maxlen:4; + u32 mcs7sgimaxlen:4; + + u32 txbuffersize:16; + u32 mcsg4maxlen:4; + u32 mcsg5maxlen:4; + u32 mcsg6maxlen:4; + u32 mcsg15sgimaxlen:4; + + u32 txbuffaddr; + u32 txbufferaddr64; + u32 nextdescaddress; + u32 nextdescaddress64; + + u32 reserve_pass_pcie_mm_limit[4]; +} __packed; + +struct rx_desc_92d { + u32 length:14; + u32 crc32:1; + u32 icverror:1; + u32 drv_infosize:4; + u32 security:3; + u32 qos:1; + u32 shift:2; + u32 phystatus:1; + u32 swdec:1; + u32 lastseg:1; + u32 firstseg:1; + u32 eor:1; + u32 own:1; + + u32 macid:5; + u32 tid:4; + u32 hwrsvd:5; + u32 paggr:1; + u32 faggr:1; + u32 a1_fit:4; + u32 a2_fit:4; + u32 pam:1; + u32 pwr:1; + u32 moredata:1; + u32 morefrag:1; + u32 type:2; + u32 mc:1; + u32 bc:1; + + u32 seq:12; + u32 frag:4; + u32 nextpktlen:14; + u32 nextind:1; + u32 rsvd:1; + + u32 rxmcs:6; + u32 rxht:1; + u32 amsdu:1; + u32 splcp:1; + u32 bandwidth:1; + u32 htc:1; + u32 tcpchk_rpt:1; + u32 ipcchk_rpt:1; + u32 tcpchk_valid:1; + u32 hwpcerr:1; + u32 hwpcind:1; + u32 iv0:16; + + u32 iv1; + + u32 tsfl; + + u32 bufferaddress; + u32 bufferaddress64; + +} __packed; + +void rtl92de_tx_fill_desc(struct ieee80211_hw *hw, + struct ieee80211_hdr *hdr, + u8 *pdesc, struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff *skb, u8 hw_queue, + struct rtl_tcb_desc *ptcb_desc); +bool rtl92de_rx_query_desc(struct ieee80211_hw *hw, + struct rtl_stats *stats, + struct ieee80211_rx_status *rx_status, + u8 *pdesc, struct sk_buff *skb); +void rtl92de_set_desc(u8 *pdesc, bool istx, u8 desc_name, u8 *val); +u32 rtl92de_get_desc(u8 *pdesc, bool istx, u8 desc_name); +void rtl92de_tx_polling(struct ieee80211_hw *hw, u8 hw_queue); +void rtl92de_tx_fill_cmddesc(struct ieee80211_hw *hw, u8 *pdesc, + bool b_firstseg, bool b_lastseg, + struct sk_buff *skb); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/Makefile b/drivers/net/wireless/rtlwifi/rtl8192se/Makefile new file mode 100644 index 0000000..de7ba58 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/Makefile @@ -0,0 +1,15 @@ +rtl8192se-objs := \ + dm.o \ + fw.o \ + hw.o \ + led.o \ + phy.o \ + rf.o \ + sw.o \ + table.o \ + trx.o + +obj-$(CPTCFG_RTL8192SE) += rtl8192se.o + +ccflags-y += -D__CHECK_ENDIAN__ + diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/def.h b/drivers/net/wireless/rtlwifi/rtl8192se/def.h new file mode 100644 index 0000000..83c9867 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/def.h @@ -0,0 +1,554 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ +#ifndef __REALTEK_92S_DEF_H__ +#define __REALTEK_92S_DEF_H__ + +#define RX_MPDU_QUEUE 0 +#define RX_CMD_QUEUE 1 +#define RX_MAX_QUEUE 2 + +#define SHORT_SLOT_TIME 9 +#define NON_SHORT_SLOT_TIME 20 + +/* Queue Select Value in TxDesc */ +#define QSLT_BK 0x2 +#define QSLT_BE 0x0 +#define QSLT_VI 0x5 +#define QSLT_VO 0x6 +#define QSLT_BEACON 0x10 +#define QSLT_HIGH 0x11 +#define QSLT_MGNT 0x12 +#define QSLT_CMD 0x13 + +/* Tx Desc */ +#define TX_DESC_SIZE_RTL8192S (16 * 4) +#define TX_CMDDESC_SIZE_RTL8192S (16 * 4) + +/* Define a macro that takes a le32 word, converts it to host ordering, + * right shifts by a specified count, creates a mask of the specified + * bit count, and extracts that number of bits. + */ + +#define SHIFT_AND_MASK_LE(__pdesc, __shift, __mask) \ + ((le32_to_cpu(*(((__le32 *)(__pdesc)))) >> (__shift)) & \ + BIT_LEN_MASK_32(__mask)) + +/* Define a macro that clears a bit field in an le32 word and + * sets the specified value into that bit field. The resulting + * value remains in le32 ordering; however, it is properly converted + * to host ordering for the clear and set operations before conversion + * back to le32. + */ + +#define SET_BITS_OFFSET_LE(__pdesc, __shift, __len, __val) \ + (*(__le32 *)(__pdesc) = \ + (cpu_to_le32((le32_to_cpu(*((__le32 *)(__pdesc))) & \ + (~(BIT_OFFSET_LEN_MASK_32((__shift), __len)))) | \ + (((u32)(__val) & BIT_LEN_MASK_32(__len)) << (__shift))))); + +/* macros to read/write various fields in RX or TX descriptors */ + +/* Dword 0 */ +#define SET_TX_DESC_PKT_SIZE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 0, 16, __val) +#define SET_TX_DESC_OFFSET(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 16, 8, __val) +#define SET_TX_DESC_TYPE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 24, 2, __val) +#define SET_TX_DESC_LAST_SEG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 26, 1, __val) +#define SET_TX_DESC_FIRST_SEG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 27, 1, __val) +#define SET_TX_DESC_LINIP(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 28, 1, __val) +#define SET_TX_DESC_AMSDU(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 29, 1, __val) +#define SET_TX_DESC_GREEN_FIELD(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 30, 1, __val) +#define SET_TX_DESC_OWN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 31, 1, __val) + +#define GET_TX_DESC_OWN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 31, 1) + +/* Dword 1 */ +#define SET_TX_DESC_MACID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 0, 5, __val) +#define SET_TX_DESC_MORE_DATA(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 5, 1, __val) +#define SET_TX_DESC_MORE_FRAG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 6, 1, __val) +#define SET_TX_DESC_PIFS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 7, 1, __val) +#define SET_TX_DESC_QUEUE_SEL(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 8, 5, __val) +#define SET_TX_DESC_ACK_POLICY(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 13, 2, __val) +#define SET_TX_DESC_NO_ACM(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 15, 1, __val) +#define SET_TX_DESC_NON_QOS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 16, 1, __val) +#define SET_TX_DESC_KEY_ID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 17, 2, __val) +#define SET_TX_DESC_OUI(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 19, 1, __val) +#define SET_TX_DESC_PKT_TYPE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 20, 1, __val) +#define SET_TX_DESC_EN_DESC_ID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 21, 1, __val) +#define SET_TX_DESC_SEC_TYPE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 22, 2, __val) +#define SET_TX_DESC_WDS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 24, 1, __val) +#define SET_TX_DESC_HTC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 25, 1, __val) +#define SET_TX_DESC_PKT_OFFSET(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 26, 5, __val) +#define SET_TX_DESC_HWPC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 27, 1, __val) + +/* Dword 2 */ +#define SET_TX_DESC_DATA_RETRY_LIMIT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 8, 0, 6, __val) +#define SET_TX_DESC_RETRY_LIMIT_ENABLE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 8, 6, 1, __val) +#define SET_TX_DESC_TSFL(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 8, 7, 5, __val) +#define SET_TX_DESC_RTS_RETRY_COUNT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 8, 12, 6, __val) +#define SET_TX_DESC_DATA_RETRY_COUNT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 8, 18, 6, __val) +#define SET_TX_DESC_RSVD_MACID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(((__pdesc) + 8), 24, 5, __val) +#define SET_TX_DESC_AGG_ENABLE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 8, 29, 1, __val) +#define SET_TX_DESC_AGG_BREAK(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 8, 30, 1, __val) +#define SET_TX_DESC_OWN_MAC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 8, 31, 1, __val) + +/* Dword 3 */ +#define SET_TX_DESC_NEXT_HEAP_PAGE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 12, 0, 8, __val) +#define SET_TX_DESC_TAIL_PAGE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 12, 8, 8, __val) +#define SET_TX_DESC_SEQ(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 12, 16, 12, __val) +#define SET_TX_DESC_FRAG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 12, 28, 4, __val) + +/* Dword 4 */ +#define SET_TX_DESC_RTS_RATE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 0, 6, __val) +#define SET_TX_DESC_DISABLE_RTS_FB(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 6, 1, __val) +#define SET_TX_DESC_RTS_RATE_FB_LIMIT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 7, 4, __val) +#define SET_TX_DESC_CTS_ENABLE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 11, 1, __val) +#define SET_TX_DESC_RTS_ENABLE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 12, 1, __val) +#define SET_TX_DESC_RA_BRSR_ID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 13, 3, __val) +#define SET_TX_DESC_TXHT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 16, 1, __val) +#define SET_TX_DESC_TX_SHORT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 17, 1, __val) +#define SET_TX_DESC_TX_BANDWIDTH(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 18, 1, __val) +#define SET_TX_DESC_TX_SUB_CARRIER(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 19, 2, __val) +#define SET_TX_DESC_TX_STBC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 21, 2, __val) +#define SET_TX_DESC_TX_REVERSE_DIRECTION(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 23, 1, __val) +#define SET_TX_DESC_RTS_HT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 24, 1, __val) +#define SET_TX_DESC_RTS_SHORT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 25, 1, __val) +#define SET_TX_DESC_RTS_BANDWIDTH(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 26, 1, __val) +#define SET_TX_DESC_RTS_SUB_CARRIER(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 27, 2, __val) +#define SET_TX_DESC_RTS_STBC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 29, 2, __val) +#define SET_TX_DESC_USER_RATE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 31, 1, __val) + +/* Dword 5 */ +#define SET_TX_DESC_PACKET_ID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 20, 0, 9, __val) +#define SET_TX_DESC_TX_RATE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 20, 9, 6, __val) +#define SET_TX_DESC_DISABLE_FB(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 20, 15, 1, __val) +#define SET_TX_DESC_DATA_RATE_FB_LIMIT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 20, 16, 5, __val) +#define SET_TX_DESC_TX_AGC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 20, 21, 11, __val) + +/* Dword 6 */ +#define SET_TX_DESC_IP_CHECK_SUM(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 24, 0, 16, __val) +#define SET_TX_DESC_TCP_CHECK_SUM(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 24, 16, 16, __val) + +/* Dword 7 */ +#define SET_TX_DESC_TX_BUFFER_SIZE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 28, 0, 16, __val) +#define SET_TX_DESC_IP_HEADER_OFFSET(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 28, 16, 8, __val) +#define SET_TX_DESC_TCP_ENABLE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 28, 31, 1, __val) + +/* Dword 8 */ +#define SET_TX_DESC_TX_BUFFER_ADDRESS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 32, 0, 32, __val) +#define GET_TX_DESC_TX_BUFFER_ADDRESS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 32, 0, 32) + +/* Dword 9 */ +#define SET_TX_DESC_NEXT_DESC_ADDRESS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 36, 0, 32, __val) + +/* Because the PCI Tx descriptors are chaied at the + * initialization and all the NextDescAddresses in + * these descriptors cannot not be cleared (,or + * driver/HW cannot find the next descriptor), the + * offset 36 (NextDescAddresses) is reserved when + * the desc is cleared. */ +#define TX_DESC_NEXT_DESC_OFFSET 36 +#define CLEAR_PCI_TX_DESC_CONTENT(__pdesc, _size) \ + memset(__pdesc, 0, min_t(size_t, _size, TX_DESC_NEXT_DESC_OFFSET)) + +/* Rx Desc */ +#define RX_STATUS_DESC_SIZE 24 +#define RX_DRV_INFO_SIZE_UNIT 8 + +/* DWORD 0 */ +#define SET_RX_STATUS_DESC_PKT_LEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 0, 14, __val) +#define SET_RX_STATUS_DESC_CRC32(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 14, 1, __val) +#define SET_RX_STATUS_DESC_ICV(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 15, 1, __val) +#define SET_RX_STATUS_DESC_DRVINFO_SIZE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 16, 4, __val) +#define SET_RX_STATUS_DESC_SECURITY(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 20, 3, __val) +#define SET_RX_STATUS_DESC_QOS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 23, 1, __val) +#define SET_RX_STATUS_DESC_SHIFT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 24, 2, __val) +#define SET_RX_STATUS_DESC_PHY_STATUS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 26, 1, __val) +#define SET_RX_STATUS_DESC_SWDEC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 27, 1, __val) +#define SET_RX_STATUS_DESC_LAST_SEG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 28, 1, __val) +#define SET_RX_STATUS_DESC_FIRST_SEG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 29, 1, __val) +#define SET_RX_STATUS_DESC_EOR(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 30, 1, __val) +#define SET_RX_STATUS_DESC_OWN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc, 31, 1, __val) + +#define GET_RX_STATUS_DESC_PKT_LEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 0, 14) +#define GET_RX_STATUS_DESC_CRC32(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 14, 1) +#define GET_RX_STATUS_DESC_ICV(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 15, 1) +#define GET_RX_STATUS_DESC_DRVINFO_SIZE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 16, 4) +#define GET_RX_STATUS_DESC_SECURITY(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 20, 3) +#define GET_RX_STATUS_DESC_QOS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 23, 1) +#define GET_RX_STATUS_DESC_SHIFT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 24, 2) +#define GET_RX_STATUS_DESC_PHY_STATUS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 26, 1) +#define GET_RX_STATUS_DESC_SWDEC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 27, 1) +#define GET_RX_STATUS_DESC_LAST_SEG(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 28, 1) +#define GET_RX_STATUS_DESC_FIRST_SEG(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 29, 1) +#define GET_RX_STATUS_DESC_EOR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 30, 1) +#define GET_RX_STATUS_DESC_OWN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc, 31, 1) + +/* DWORD 1 */ +#define SET_RX_STATUS_DESC_MACID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 0, 5, __val) +#define SET_RX_STATUS_DESC_TID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 5, 4, __val) +#define SET_RX_STATUS_DESC_PAGGR(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 14, 1, __val) +#define SET_RX_STATUS_DESC_FAGGR(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 15, 1, __val) +#define SET_RX_STATUS_DESC_A1_FIT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 16, 4, __val) +#define SET_RX_STATUS_DESC_A2_FIT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 20, 4, __val) +#define SET_RX_STATUS_DESC_PAM(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 24, 1, __val) +#define SET_RX_STATUS_DESC_PWR(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 25, 1, __val) +#define SET_RX_STATUS_DESC_MOREDATA(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 26, 1, __val) +#define SET_RX_STATUS_DESC_MOREFRAG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 27, 1, __val) +#define SET_RX_STATUS_DESC_TYPE(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 28, 2, __val) +#define SET_RX_STATUS_DESC_MC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 30, 1, __val) +#define SET_RX_STATUS_DESC_BC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 4, 31, 1, __val) + +#define GET_RX_STATUS_DEC_MACID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 4, 0, 5) +#define GET_RX_STATUS_DESC_TID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 4, 5, 4) +#define GET_RX_STATUS_DESC_PAGGR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 4, 14, 1) +#define GET_RX_STATUS_DESC_FAGGR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 4, 15, 1) +#define GET_RX_STATUS_DESC_A1_FIT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 4, 16, 4) +#define GET_RX_STATUS_DESC_A2_FIT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 4, 20, 4) +#define GET_RX_STATUS_DESC_PAM(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 4, 24, 1) +#define GET_RX_STATUS_DESC_PWR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 4, 25, 1) +#define GET_RX_STATUS_DESC_MORE_DATA(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 4, 26, 1) +#define GET_RX_STATUS_DESC_MORE_FRAG(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 4, 27, 1) +#define GET_RX_STATUS_DESC_TYPE(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 4, 28, 2) +#define GET_RX_STATUS_DESC_MC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 4, 30, 1) +#define GET_RX_STATUS_DESC_BC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 4, 31, 1) + +/* DWORD 2 */ +#define SET_RX_STATUS_DESC_SEQ(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 8, 0, 12, __val) +#define SET_RX_STATUS_DESC_FRAG(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 8, 12, 4, __val) +#define SET_RX_STATUS_DESC_NEXT_PKTLEN(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 8, 16, 8, __val) +#define SET_RX_STATUS_DESC_NEXT_IND(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 8, 30, 1, __val) + +#define GET_RX_STATUS_DESC_SEQ(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 8, 0, 12) +#define GET_RX_STATUS_DESC_FRAG(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 8, 12, 4) +#define GET_RX_STATUS_DESC_NEXT_PKTLEN(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 8, 16, 8) +#define GET_RX_STATUS_DESC_NEXT_IND(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 8, 30, 1) + +/* DWORD 3 */ +#define SET_RX_STATUS_DESC_RX_MCS(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 12, 0, 6, __val) +#define SET_RX_STATUS_DESC_RX_HT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 12, 6, 1, __val) +#define SET_RX_STATUS_DESC_AMSDU(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 12, 7, 1, __val) +#define SET_RX_STATUS_DESC_SPLCP(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 12, 8, 1, __val) +#define SET_RX_STATUS_DESC_BW(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 12, 9, 1, __val) +#define SET_RX_STATUS_DESC_HTC(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 12, 10, 1, __val) +#define SET_RX_STATUS_DESC_TCP_CHK_RPT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 12, 11, 1, __val) +#define SET_RX_STATUS_DESC_IP_CHK_RPT(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 12, 12, 1, __val) +#define SET_RX_STATUS_DESC_TCP_CHK_VALID(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 12, 13, 1, __val) +#define SET_RX_STATUS_DESC_HWPC_ERR(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 12, 14, 1, __val) +#define SET_RX_STATUS_DESC_HWPC_IND(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 12, 15, 1, __val) +#define SET_RX_STATUS_DESC_IV0(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 12, 16, 16, __val) + +#define GET_RX_STATUS_DESC_RX_MCS(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 12, 0, 6) +#define GET_RX_STATUS_DESC_RX_HT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 12, 6, 1) +#define GET_RX_STATUS_DESC_AMSDU(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 12, 7, 1) +#define GET_RX_STATUS_DESC_SPLCP(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 12, 8, 1) +#define GET_RX_STATUS_DESC_BW(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 12, 9, 1) +#define GET_RX_STATUS_DESC_HTC(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 12, 10, 1) +#define GET_RX_STATUS_DESC_TCP_CHK_RPT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 12, 11, 1) +#define GET_RX_STATUS_DESC_IP_CHK_RPT(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 12, 12, 1) +#define GET_RX_STATUS_DESC_TCP_CHK_VALID(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 12, 13, 1) +#define GET_RX_STATUS_DESC_HWPC_ERR(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 12, 14, 1) +#define GET_RX_STATUS_DESC_HWPC_IND(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 12, 15, 1) +#define GET_RX_STATUS_DESC_IV0(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 12, 16, 16) + +/* DWORD 4 */ +#define SET_RX_STATUS_DESC_IV1(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 16, 0, 32, __val) +#define GET_RX_STATUS_DESC_IV1(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 16, 0, 32) + +/* DWORD 5 */ +#define SET_RX_STATUS_DESC_TSFL(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 20, 0, 32, __val) +#define GET_RX_STATUS_DESC_TSFL(__pdesc) \ + SHIFT_AND_MASK_LE(__pdesc + 20, 0, 32) + +/* DWORD 6 */ +#define SET_RX_STATUS__DESC_BUFF_ADDR(__pdesc, __val) \ + SET_BITS_OFFSET_LE(__pdesc + 24, 0, 32, __val) + +#define SE_RX_HAL_IS_CCK_RATE(_pdesc)\ + (GET_RX_STATUS_DESC_RX_MCS(_pdesc) == DESC92_RATE1M || \ + GET_RX_STATUS_DESC_RX_MCS(_pdesc) == DESC92_RATE2M || \ + GET_RX_STATUS_DESC_RX_MCS(_pdesc) == DESC92_RATE5_5M ||\ + GET_RX_STATUS_DESC_RX_MCS(_pdesc) == DESC92_RATE11M) + +enum rf_optype { + RF_OP_BY_SW_3WIRE = 0, + RF_OP_BY_FW, + RF_OP_MAX +}; + +enum ic_inferiority { + IC_INFERIORITY_A = 0, + IC_INFERIORITY_B = 1, +}; + +enum fwcmd_iotype { + /* For DIG DM */ + FW_CMD_DIG_ENABLE = 0, + FW_CMD_DIG_DISABLE = 1, + FW_CMD_DIG_HALT = 2, + FW_CMD_DIG_RESUME = 3, + /* For High Power DM */ + FW_CMD_HIGH_PWR_ENABLE = 4, + FW_CMD_HIGH_PWR_DISABLE = 5, + /* For Rate adaptive DM */ + FW_CMD_RA_RESET = 6, + FW_CMD_RA_ACTIVE = 7, + FW_CMD_RA_REFRESH_N = 8, + FW_CMD_RA_REFRESH_BG = 9, + FW_CMD_RA_INIT = 10, + /* For FW supported IQK */ + FW_CMD_IQK_INIT = 11, + /* Tx power tracking switch, + * MP driver only */ + FW_CMD_TXPWR_TRACK_ENABLE = 12, + /* Tx power tracking switch, + * MP driver only */ + FW_CMD_TXPWR_TRACK_DISABLE = 13, + /* Tx power tracking with thermal + * indication, for Normal driver */ + FW_CMD_TXPWR_TRACK_THERMAL = 14, + FW_CMD_PAUSE_DM_BY_SCAN = 15, + FW_CMD_RESUME_DM_BY_SCAN = 16, + FW_CMD_RA_REFRESH_N_COMB = 17, + FW_CMD_RA_REFRESH_BG_COMB = 18, + FW_CMD_ANTENNA_SW_ENABLE = 19, + FW_CMD_ANTENNA_SW_DISABLE = 20, + /* Tx Status report for CCX from FW */ + FW_CMD_TX_FEEDBACK_CCX_ENABLE = 21, + /* Indifate firmware that driver + * enters LPS, For PS-Poll issue */ + FW_CMD_LPS_ENTER = 22, + /* Indicate firmware that driver + * leave LPS*/ + FW_CMD_LPS_LEAVE = 23, + /* Set DIG mode to signal strength */ + FW_CMD_DIG_MODE_SS = 24, + /* Set DIG mode to false alarm. */ + FW_CMD_DIG_MODE_FA = 25, + FW_CMD_ADD_A2_ENTRY = 26, + FW_CMD_CTRL_DM_BY_DRIVER = 27, + FW_CMD_CTRL_DM_BY_DRIVER_NEW = 28, + FW_CMD_PAPE_CONTROL = 29, + FW_CMD_IQK_ENABLE = 30, +}; + +/* Driver info contain PHY status + * and other variabel size info + * PHY Status content as below + */ +struct rx_fwinfo { + /* DWORD 0 */ + u8 gain_trsw[4]; + /* DWORD 1 */ + u8 pwdb_all; + u8 cfosho[4]; + /* DWORD 2 */ + u8 cfotail[4]; + /* DWORD 3 */ + s8 rxevm[2]; + s8 rxsnr[4]; + /* DWORD 4 */ + u8 pdsnr[2]; + /* DWORD 5 */ + u8 csi_current[2]; + u8 csi_target[2]; + /* DWORD 6 */ + u8 sigevm; + u8 max_ex_pwr; + u8 ex_intf_flag:1; + u8 sgi_en:1; + u8 rxsc:2; + u8 reserve:4; +}; + +struct phy_sts_cck_8192s_t { + u8 adc_pwdb_x[4]; + u8 sq_rpt; + u8 cck_agc_rpt; +}; + +#endif + diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/dm.c b/drivers/net/wireless/rtlwifi/rtl8192se/dm.c new file mode 100644 index 0000000..b3a2d5e --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/dm.c @@ -0,0 +1,742 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../base.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "dm.h" +#include "fw.h" + +static const u32 edca_setting_dl[PEER_MAX] = { + 0xa44f, /* 0 UNKNOWN */ + 0x5ea44f, /* 1 REALTEK_90 */ + 0x5ea44f, /* 2 REALTEK_92SE */ + 0xa630, /* 3 BROAD */ + 0xa44f, /* 4 RAL */ + 0xa630, /* 5 ATH */ + 0xa630, /* 6 CISCO */ + 0xa42b, /* 7 MARV */ +}; + +static const u32 edca_setting_dl_gmode[PEER_MAX] = { + 0x4322, /* 0 UNKNOWN */ + 0xa44f, /* 1 REALTEK_90 */ + 0x5ea44f, /* 2 REALTEK_92SE */ + 0xa42b, /* 3 BROAD */ + 0x5e4322, /* 4 RAL */ + 0x4322, /* 5 ATH */ + 0xa430, /* 6 CISCO */ + 0x5ea44f, /* 7 MARV */ +}; + +static const u32 edca_setting_ul[PEER_MAX] = { + 0x5e4322, /* 0 UNKNOWN */ + 0xa44f, /* 1 REALTEK_90 */ + 0x5ea44f, /* 2 REALTEK_92SE */ + 0x5ea322, /* 3 BROAD */ + 0x5ea422, /* 4 RAL */ + 0x5ea322, /* 5 ATH */ + 0x3ea44f, /* 6 CISCO */ + 0x5ea44f, /* 7 MARV */ +}; + +static void _rtl92s_dm_check_edca_turbo(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + + static u64 last_txok_cnt; + static u64 last_rxok_cnt; + u64 cur_txok_cnt = 0; + u64 cur_rxok_cnt = 0; + + u32 edca_be_ul = edca_setting_ul[mac->vendor]; + u32 edca_be_dl = edca_setting_dl[mac->vendor]; + u32 edca_gmode = edca_setting_dl_gmode[mac->vendor]; + + if (mac->link_state != MAC80211_LINKED) { + rtlpriv->dm.current_turbo_edca = false; + goto dm_checkedcaturbo_exit; + } + + if ((!rtlpriv->dm.is_any_nonbepkts) && + (!rtlpriv->dm.disable_framebursting)) { + cur_txok_cnt = rtlpriv->stats.txbytesunicast - last_txok_cnt; + cur_rxok_cnt = rtlpriv->stats.rxbytesunicast - last_rxok_cnt; + + if (rtlpriv->phy.rf_type == RF_1T2R) { + if (cur_txok_cnt > 4 * cur_rxok_cnt) { + /* Uplink TP is present. */ + if (rtlpriv->dm.is_cur_rdlstate || + !rtlpriv->dm.current_turbo_edca) { + rtl_write_dword(rtlpriv, EDCAPARA_BE, + edca_be_ul); + rtlpriv->dm.is_cur_rdlstate = false; + } + } else {/* Balance TP is present. */ + if (!rtlpriv->dm.is_cur_rdlstate || + !rtlpriv->dm.current_turbo_edca) { + if (mac->mode == WIRELESS_MODE_G || + mac->mode == WIRELESS_MODE_B) + rtl_write_dword(rtlpriv, + EDCAPARA_BE, + edca_gmode); + else + rtl_write_dword(rtlpriv, + EDCAPARA_BE, + edca_be_dl); + rtlpriv->dm.is_cur_rdlstate = true; + } + } + rtlpriv->dm.current_turbo_edca = true; + } else { + if (cur_rxok_cnt > 4 * cur_txok_cnt) { + if (!rtlpriv->dm.is_cur_rdlstate || + !rtlpriv->dm.current_turbo_edca) { + if (mac->mode == WIRELESS_MODE_G || + mac->mode == WIRELESS_MODE_B) + rtl_write_dword(rtlpriv, + EDCAPARA_BE, + edca_gmode); + else + rtl_write_dword(rtlpriv, + EDCAPARA_BE, + edca_be_dl); + rtlpriv->dm.is_cur_rdlstate = true; + } + } else { + if (rtlpriv->dm.is_cur_rdlstate || + !rtlpriv->dm.current_turbo_edca) { + rtl_write_dword(rtlpriv, EDCAPARA_BE, + edca_be_ul); + rtlpriv->dm.is_cur_rdlstate = false; + } + } + rtlpriv->dm.current_turbo_edca = true; + } + } else { + if (rtlpriv->dm.current_turbo_edca) { + u8 tmp = AC0_BE; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_AC_PARAM, + &tmp); + rtlpriv->dm.current_turbo_edca = false; + } + } + +dm_checkedcaturbo_exit: + rtlpriv->dm.is_any_nonbepkts = false; + last_txok_cnt = rtlpriv->stats.txbytesunicast; + last_rxok_cnt = rtlpriv->stats.rxbytesunicast; +} + +static void _rtl92s_dm_txpowertracking_callback_thermalmeter( + struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 thermalvalue = 0; + u32 fw_cmd = 0; + + rtlpriv->dm.txpower_trackinginit = true; + + thermalvalue = (u8)rtl_get_rfreg(hw, RF90_PATH_A, RF_T_METER, 0x1f); + + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Readback Thermal Meter = 0x%x pre thermal meter 0x%x eeprom_thermal meter 0x%x\n", + thermalvalue, + rtlpriv->dm.thermalvalue, rtlefuse->eeprom_thermalmeter); + + if (thermalvalue) { + rtlpriv->dm.thermalvalue = thermalvalue; + if (hal_get_firmwareversion(rtlpriv) >= 0x35) { + rtl92s_phy_set_fw_cmd(hw, FW_CMD_TXPWR_TRACK_THERMAL); + } else { + fw_cmd = (FW_TXPWR_TRACK_THERMAL | + (rtlpriv->efuse.thermalmeter[0] << 8) | + (thermalvalue << 16)); + + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "Write to FW Thermal Val = 0x%x\n", fw_cmd); + + rtl_write_dword(rtlpriv, WFM5, fw_cmd); + rtl92s_phy_chk_fwcmd_iodone(hw); + } + } + + rtlpriv->dm.txpowercount = 0; +} + +static void _rtl92s_dm_check_txpowertracking_thermalmeter( + struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + static u8 tm_trigger; + u8 tx_power_checkcnt = 5; + + /* 2T2R TP issue */ + if (rtlphy->rf_type == RF_2T2R) + return; + + if (!rtlpriv->dm.txpower_tracking) + return; + + if (rtlpriv->dm.txpowercount <= tx_power_checkcnt) { + rtlpriv->dm.txpowercount++; + return; + } + + if (!tm_trigger) { + rtl_set_rfreg(hw, RF90_PATH_A, RF_T_METER, + RFREG_OFFSET_MASK, 0x60); + tm_trigger = 1; + } else { + _rtl92s_dm_txpowertracking_callback_thermalmeter(hw); + tm_trigger = 0; + } +} + +static void _rtl92s_dm_refresh_rateadaptive_mask(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rate_adaptive *ra = &(rtlpriv->ra); + struct ieee80211_sta *sta = NULL; + u32 low_rssi_thresh = 0; + u32 middle_rssi_thresh = 0; + u32 high_rssi_thresh = 0; + + if (is_hal_stop(rtlhal)) + return; + + if (!rtlpriv->dm.useramask) + return; + + if (hal_get_firmwareversion(rtlpriv) >= 61 && + !rtlpriv->dm.inform_fw_driverctrldm) { + rtl92s_phy_set_fw_cmd(hw, FW_CMD_CTRL_DM_BY_DRIVER); + rtlpriv->dm.inform_fw_driverctrldm = true; + } + + if ((mac->link_state == MAC80211_LINKED) && + (mac->opmode == NL80211_IFTYPE_STATION)) { + switch (ra->pre_ratr_state) { + case DM_RATR_STA_HIGH: + high_rssi_thresh = 40; + middle_rssi_thresh = 30; + low_rssi_thresh = 20; + break; + case DM_RATR_STA_MIDDLE: + high_rssi_thresh = 44; + middle_rssi_thresh = 30; + low_rssi_thresh = 20; + break; + case DM_RATR_STA_LOW: + high_rssi_thresh = 44; + middle_rssi_thresh = 34; + low_rssi_thresh = 20; + break; + case DM_RATR_STA_ULTRALOW: + high_rssi_thresh = 44; + middle_rssi_thresh = 34; + low_rssi_thresh = 24; + break; + default: + high_rssi_thresh = 44; + middle_rssi_thresh = 34; + low_rssi_thresh = 24; + break; + } + + if (rtlpriv->dm.undec_sm_pwdb > (long)high_rssi_thresh) { + ra->ratr_state = DM_RATR_STA_HIGH; + } else if (rtlpriv->dm.undec_sm_pwdb > + (long)middle_rssi_thresh) { + ra->ratr_state = DM_RATR_STA_LOW; + } else if (rtlpriv->dm.undec_sm_pwdb > + (long)low_rssi_thresh) { + ra->ratr_state = DM_RATR_STA_LOW; + } else { + ra->ratr_state = DM_RATR_STA_ULTRALOW; + } + + if (ra->pre_ratr_state != ra->ratr_state) { + RT_TRACE(rtlpriv, COMP_RATE, DBG_LOUD, + "RSSI = %ld RSSI_LEVEL = %d PreState = %d, CurState = %d\n", + rtlpriv->dm.undec_sm_pwdb, ra->ratr_state, + ra->pre_ratr_state, ra->ratr_state); + + rcu_read_lock(); + sta = rtl_find_sta(hw, mac->bssid); + if (sta) + rtlpriv->cfg->ops->update_rate_tbl(hw, sta, + ra->ratr_state); + rcu_read_unlock(); + + ra->pre_ratr_state = ra->ratr_state; + } + } +} + +static void _rtl92s_dm_switch_baseband_mrc(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + bool current_mrc; + bool enable_mrc = true; + long tmpentry_maxpwdb = 0; + u8 rssi_a = 0; + u8 rssi_b = 0; + + if (is_hal_stop(rtlhal)) + return; + + if ((rtlphy->rf_type == RF_1T1R) || (rtlphy->rf_type == RF_2T2R)) + return; + + rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_MRC, (u8 *)(¤t_mrc)); + + if (mac->link_state >= MAC80211_LINKED) { + if (rtlpriv->dm.undec_sm_pwdb > tmpentry_maxpwdb) { + rssi_a = rtlpriv->stats.rx_rssi_percentage[RF90_PATH_A]; + rssi_b = rtlpriv->stats.rx_rssi_percentage[RF90_PATH_B]; + } + } + + /* MRC settings would NOT affect TP on Wireless B mode. */ + if (mac->mode != WIRELESS_MODE_B) { + if ((rssi_a == 0) && (rssi_b == 0)) { + enable_mrc = true; + } else if (rssi_b > 30) { + /* Turn on B-Path */ + enable_mrc = true; + } else if (rssi_b < 5) { + /* Turn off B-path */ + enable_mrc = false; + /* Take care of RSSI differentiation. */ + } else if (rssi_a > 15 && (rssi_a >= rssi_b)) { + if ((rssi_a - rssi_b) > 15) + /* Turn off B-path */ + enable_mrc = false; + else if ((rssi_a - rssi_b) < 10) + /* Turn on B-Path */ + enable_mrc = true; + else + enable_mrc = current_mrc; + } else { + /* Turn on B-Path */ + enable_mrc = true; + } + } + + /* Update MRC settings if needed. */ + if (enable_mrc != current_mrc) + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_MRC, + (u8 *)&enable_mrc); + +} + +void rtl92s_dm_init_edca_turbo(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.current_turbo_edca = false; + rtlpriv->dm.is_any_nonbepkts = false; + rtlpriv->dm.is_cur_rdlstate = false; +} + +static void _rtl92s_dm_init_rate_adaptive_mask(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rate_adaptive *ra = &(rtlpriv->ra); + + ra->ratr_state = DM_RATR_STA_MAX; + ra->pre_ratr_state = DM_RATR_STA_MAX; + + if (rtlpriv->dm.dm_type == DM_TYPE_BYDRIVER && + hal_get_firmwareversion(rtlpriv) >= 60) + rtlpriv->dm.useramask = true; + else + rtlpriv->dm.useramask = false; + + rtlpriv->dm.useramask = false; + rtlpriv->dm.inform_fw_driverctrldm = false; +} + +static void _rtl92s_dm_init_txpowertracking_thermalmeter( + struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.txpower_tracking = true; + rtlpriv->dm.txpowercount = 0; + rtlpriv->dm.txpower_trackinginit = false; +} + +static void _rtl92s_dm_false_alarm_counter_statistics(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct false_alarm_statistics *falsealm_cnt = &(rtlpriv->falsealm_cnt); + u32 ret_value; + + ret_value = rtl_get_bbreg(hw, ROFDM_PHYCOUNTER1, MASKDWORD); + falsealm_cnt->cnt_parity_fail = ((ret_value & 0xffff0000) >> 16); + + ret_value = rtl_get_bbreg(hw, ROFDM_PHYCOUNTER2, MASKDWORD); + falsealm_cnt->cnt_rate_illegal = (ret_value & 0xffff); + falsealm_cnt->cnt_crc8_fail = ((ret_value & 0xffff0000) >> 16); + ret_value = rtl_get_bbreg(hw, ROFDM_PHYCOUNTER3, MASKDWORD); + falsealm_cnt->cnt_mcs_fail = (ret_value & 0xffff); + + falsealm_cnt->cnt_ofdm_fail = falsealm_cnt->cnt_parity_fail + + falsealm_cnt->cnt_rate_illegal + falsealm_cnt->cnt_crc8_fail + + falsealm_cnt->cnt_mcs_fail; + + /* read CCK false alarm */ + ret_value = rtl_get_bbreg(hw, 0xc64, MASKDWORD); + falsealm_cnt->cnt_cck_fail = (ret_value & 0xffff); + falsealm_cnt->cnt_all = falsealm_cnt->cnt_ofdm_fail + + falsealm_cnt->cnt_cck_fail; +} + +static void rtl92s_backoff_enable_flag(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *digtable = &rtlpriv->dm_digtable; + struct false_alarm_statistics *falsealm_cnt = &(rtlpriv->falsealm_cnt); + + if (falsealm_cnt->cnt_all > digtable->fa_highthresh) { + if ((digtable->back_val - 6) < + digtable->backoffval_range_min) + digtable->back_val = digtable->backoffval_range_min; + else + digtable->back_val -= 6; + } else if (falsealm_cnt->cnt_all < digtable->fa_lowthresh) { + if ((digtable->back_val + 6) > + digtable->backoffval_range_max) + digtable->back_val = + digtable->backoffval_range_max; + else + digtable->back_val += 6; + } +} + +static void _rtl92s_dm_initial_gain_sta_beforeconnect(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *digtable = &rtlpriv->dm_digtable; + struct false_alarm_statistics *falsealm_cnt = &(rtlpriv->falsealm_cnt); + static u8 initialized, force_write; + u8 initial_gain = 0; + + if ((digtable->pre_sta_cstate == digtable->cur_sta_cstate) || + (digtable->cur_sta_cstate == DIG_STA_BEFORE_CONNECT)) { + if (digtable->cur_sta_cstate == DIG_STA_BEFORE_CONNECT) { + if (rtlpriv->psc.rfpwr_state != ERFON) + return; + + if (digtable->backoff_enable_flag) + rtl92s_backoff_enable_flag(hw); + else + digtable->back_val = DM_DIG_BACKOFF; + + if ((digtable->rssi_val + 10 - digtable->back_val) > + digtable->rx_gain_max) + digtable->cur_igvalue = + digtable->rx_gain_max; + else if ((digtable->rssi_val + 10 - digtable->back_val) + < digtable->rx_gain_min) + digtable->cur_igvalue = + digtable->rx_gain_min; + else + digtable->cur_igvalue = digtable->rssi_val + 10 + - digtable->back_val; + + if (falsealm_cnt->cnt_all > 10000) + digtable->cur_igvalue = + (digtable->cur_igvalue > 0x33) ? + digtable->cur_igvalue : 0x33; + + if (falsealm_cnt->cnt_all > 16000) + digtable->cur_igvalue = + digtable->rx_gain_max; + /* connected -> connected or disconnected -> disconnected */ + } else { + /* Firmware control DIG, do nothing in driver dm */ + return; + } + /* disconnected -> connected or connected -> + * disconnected or beforeconnect->(dis)connected */ + } else { + /* Enable FW DIG */ + digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_MAX; + rtl92s_phy_set_fw_cmd(hw, FW_CMD_DIG_ENABLE); + + digtable->back_val = DM_DIG_BACKOFF; + digtable->cur_igvalue = rtlpriv->phy.default_initialgain[0]; + digtable->pre_igvalue = 0; + return; + } + + /* Forced writing to prevent from fw-dig overwriting. */ + if (digtable->pre_igvalue != rtl_get_bbreg(hw, ROFDM0_XAAGCCORE1, + MASKBYTE0)) + force_write = 1; + + if ((digtable->pre_igvalue != digtable->cur_igvalue) || + !initialized || force_write) { + /* Disable FW DIG */ + rtl92s_phy_set_fw_cmd(hw, FW_CMD_DIG_DISABLE); + + initial_gain = (u8)digtable->cur_igvalue; + + /* Set initial gain. */ + rtl_set_bbreg(hw, ROFDM0_XAAGCCORE1, MASKBYTE0, initial_gain); + rtl_set_bbreg(hw, ROFDM0_XBAGCCORE1, MASKBYTE0, initial_gain); + digtable->pre_igvalue = digtable->cur_igvalue; + initialized = 1; + force_write = 0; + } +} + +static void _rtl92s_dm_ctrl_initgain_bytwoport(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dig = &rtlpriv->dm_digtable; + + if (rtlpriv->mac80211.act_scanning) + return; + + /* Decide the current status and if modify initial gain or not */ + if (rtlpriv->mac80211.link_state >= MAC80211_LINKED || + rtlpriv->mac80211.opmode == NL80211_IFTYPE_ADHOC) + dig->cur_sta_cstate = DIG_STA_CONNECT; + else + dig->cur_sta_cstate = DIG_STA_DISCONNECT; + + dig->rssi_val = rtlpriv->dm.undec_sm_pwdb; + + /* Change dig mode to rssi */ + if (dig->cur_sta_cstate != DIG_STA_DISCONNECT) { + if (dig->dig_twoport_algorithm == + DIG_TWO_PORT_ALGO_FALSE_ALARM) { + dig->dig_twoport_algorithm = DIG_TWO_PORT_ALGO_RSSI; + rtl92s_phy_set_fw_cmd(hw, FW_CMD_DIG_MODE_SS); + } + } + + _rtl92s_dm_false_alarm_counter_statistics(hw); + _rtl92s_dm_initial_gain_sta_beforeconnect(hw); + + dig->pre_sta_cstate = dig->cur_sta_cstate; +} + +static void _rtl92s_dm_ctrl_initgain_byrssi(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct dig_t *digtable = &rtlpriv->dm_digtable; + + /* 2T2R TP issue */ + if (rtlphy->rf_type == RF_2T2R) + return; + + if (!rtlpriv->dm.dm_initialgain_enable) + return; + + if (digtable->dig_enable_flag == false) + return; + + _rtl92s_dm_ctrl_initgain_bytwoport(hw); +} + +static void _rtl92s_dm_dynamic_txpower(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + long undec_sm_pwdb; + long txpwr_threshold_lv1, txpwr_threshold_lv2; + + /* 2T2R TP issue */ + if (rtlphy->rf_type == RF_2T2R) + return; + + if (!rtlpriv->dm.dynamic_txpower_enable || + rtlpriv->dm.dm_flag & HAL_DM_HIPWR_DISABLE) { + rtlpriv->dm.dynamic_txhighpower_lvl = TX_HIGHPWR_LEVEL_NORMAL; + return; + } + + if ((mac->link_state < MAC80211_LINKED) && + (rtlpriv->dm.entry_min_undec_sm_pwdb == 0)) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_TRACE, + "Not connected to any\n"); + + rtlpriv->dm.dynamic_txhighpower_lvl = TX_HIGHPWR_LEVEL_NORMAL; + + rtlpriv->dm.last_dtp_lvl = TX_HIGHPWR_LEVEL_NORMAL; + return; + } + + if (mac->link_state >= MAC80211_LINKED) { + if (mac->opmode == NL80211_IFTYPE_ADHOC) { + undec_sm_pwdb = rtlpriv->dm.entry_min_undec_sm_pwdb; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "AP Client PWDB = 0x%lx\n", + undec_sm_pwdb); + } else { + undec_sm_pwdb = rtlpriv->dm.undec_sm_pwdb; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "STA Default Port PWDB = 0x%lx\n", + undec_sm_pwdb); + } + } else { + undec_sm_pwdb = rtlpriv->dm.entry_min_undec_sm_pwdb; + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "AP Ext Port PWDB = 0x%lx\n", + undec_sm_pwdb); + } + + txpwr_threshold_lv2 = TX_POWER_NEAR_FIELD_THRESH_LVL2; + txpwr_threshold_lv1 = TX_POWER_NEAR_FIELD_THRESH_LVL1; + + if (rtl_get_bbreg(hw, 0xc90, MASKBYTE0) == 1) + rtlpriv->dm.dynamic_txhighpower_lvl = TX_HIGHPWR_LEVEL_NORMAL; + else if (undec_sm_pwdb >= txpwr_threshold_lv2) + rtlpriv->dm.dynamic_txhighpower_lvl = TX_HIGHPWR_LEVEL_NORMAL2; + else if ((undec_sm_pwdb < (txpwr_threshold_lv2 - 3)) && + (undec_sm_pwdb >= txpwr_threshold_lv1)) + rtlpriv->dm.dynamic_txhighpower_lvl = TX_HIGHPWR_LEVEL_NORMAL1; + else if (undec_sm_pwdb < (txpwr_threshold_lv1 - 3)) + rtlpriv->dm.dynamic_txhighpower_lvl = TX_HIGHPWR_LEVEL_NORMAL; + + if ((rtlpriv->dm.dynamic_txhighpower_lvl != rtlpriv->dm.last_dtp_lvl)) + rtl92s_phy_set_txpower(hw, rtlphy->current_channel); + + rtlpriv->dm.last_dtp_lvl = rtlpriv->dm.dynamic_txhighpower_lvl; +} + +static void _rtl92s_dm_init_dig(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *digtable = &rtlpriv->dm_digtable; + + /* Disable DIG scheme now.*/ + digtable->dig_enable_flag = true; + digtable->backoff_enable_flag = true; + + if ((rtlpriv->dm.dm_type == DM_TYPE_BYDRIVER) && + (hal_get_firmwareversion(rtlpriv) >= 0x3c)) + digtable->dig_algorithm = DIG_ALGO_BY_TOW_PORT; + else + digtable->dig_algorithm = + DIG_ALGO_BEFORE_CONNECT_BY_RSSI_AND_ALARM; + + digtable->dig_twoport_algorithm = DIG_TWO_PORT_ALGO_RSSI; + digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_MAX; + /* off=by real rssi value, on=by digtable->rssi_val for new dig */ + digtable->dig_dbgmode = DM_DBG_OFF; + digtable->dig_slgorithm_switch = 0; + + /* 2007/10/04 MH Define init gain threshol. */ + digtable->dig_state = DM_STA_DIG_MAX; + digtable->dig_highpwrstate = DM_STA_DIG_MAX; + + digtable->cur_sta_cstate = DIG_STA_DISCONNECT; + digtable->pre_sta_cstate = DIG_STA_DISCONNECT; + digtable->cur_ap_cstate = DIG_AP_DISCONNECT; + digtable->pre_ap_cstate = DIG_AP_DISCONNECT; + + digtable->rssi_lowthresh = DM_DIG_THRESH_LOW; + digtable->rssi_highthresh = DM_DIG_THRESH_HIGH; + + digtable->fa_lowthresh = DM_FALSEALARM_THRESH_LOW; + digtable->fa_highthresh = DM_FALSEALARM_THRESH_HIGH; + + digtable->rssi_highpower_lowthresh = DM_DIG_HIGH_PWR_THRESH_LOW; + digtable->rssi_highpower_highthresh = DM_DIG_HIGH_PWR_THRESH_HIGH; + + /* for dig debug rssi value */ + digtable->rssi_val = 50; + digtable->back_val = DM_DIG_BACKOFF; + digtable->rx_gain_max = DM_DIG_MAX; + + digtable->rx_gain_min = DM_DIG_MIN; + + digtable->backoffval_range_max = DM_DIG_BACKOFF_MAX; + digtable->backoffval_range_min = DM_DIG_BACKOFF_MIN; +} + +static void _rtl92s_dm_init_dynamic_txpower(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if ((hal_get_firmwareversion(rtlpriv) >= 60) && + (rtlpriv->dm.dm_type == DM_TYPE_BYDRIVER)) + rtlpriv->dm.dynamic_txpower_enable = true; + else + rtlpriv->dm.dynamic_txpower_enable = false; + + rtlpriv->dm.last_dtp_lvl = TX_HIGHPWR_LEVEL_NORMAL; + rtlpriv->dm.dynamic_txhighpower_lvl = TX_HIGHPWR_LEVEL_NORMAL; +} + +void rtl92s_dm_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.dm_type = DM_TYPE_BYDRIVER; + rtlpriv->dm.undec_sm_pwdb = -1; + + _rtl92s_dm_init_dynamic_txpower(hw); + rtl92s_dm_init_edca_turbo(hw); + _rtl92s_dm_init_rate_adaptive_mask(hw); + _rtl92s_dm_init_txpowertracking_thermalmeter(hw); + _rtl92s_dm_init_dig(hw); + + rtl_write_dword(rtlpriv, WFM5, FW_CCA_CHK_ENABLE); +} + +void rtl92s_dm_watchdog(struct ieee80211_hw *hw) +{ + _rtl92s_dm_check_edca_turbo(hw); + _rtl92s_dm_check_txpowertracking_thermalmeter(hw); + _rtl92s_dm_ctrl_initgain_byrssi(hw); + _rtl92s_dm_dynamic_txpower(hw); + _rtl92s_dm_refresh_rateadaptive_mask(hw); + _rtl92s_dm_switch_baseband_mrc(hw); +} + diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/dm.h b/drivers/net/wireless/rtlwifi/rtl8192se/dm.h new file mode 100644 index 0000000..2e9052c --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/dm.h @@ -0,0 +1,120 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ +#ifndef __RTL_92S_DM_H__ +#define __RTL_92S_DM_H__ + +enum dm_dig_alg { + DIG_ALGO_BY_FALSE_ALARM = 0, + DIG_ALGO_BY_RSSI = 1, + DIG_ALGO_BEFORE_CONNECT_BY_RSSI_AND_ALARM = 2, + DIG_ALGO_BY_TOW_PORT = 3, + DIG_ALGO_MAX +}; + +enum dm_dig_two_port_alg { + DIG_TWO_PORT_ALGO_RSSI = 0, + DIG_TWO_PORT_ALGO_FALSE_ALARM = 1, +}; + +enum dm_dig_dbg { + DM_DBG_OFF = 0, + DM_DBG_ON = 1, + DM_DBG_MAX +}; + +enum dm_dig_sta { + DM_STA_DIG_OFF = 0, + DM_STA_DIG_ON, + DM_STA_DIG_MAX +}; + +enum dm_dig_connect { + DIG_STA_DISCONNECT = 0, + DIG_STA_CONNECT = 1, + DIG_STA_BEFORE_CONNECT = 2, + DIG_AP_DISCONNECT = 3, + DIG_AP_CONNECT = 4, + DIG_AP_ADD_STATION = 5, + DIG_CONNECT_MAX +}; + +enum dm_dig_ext_port_alg { + DIG_EXT_PORT_STAGE_0 = 0, + DIG_EXT_PORT_STAGE_1 = 1, + DIG_EXT_PORT_STAGE_2 = 2, + DIG_EXT_PORT_STAGE_3 = 3, + DIG_EXT_PORT_STAGE_MAX = 4, +}; + +enum dm_ratr_sta { + DM_RATR_STA_HIGH = 0, + DM_RATR_STA_MIDDLEHIGH = 1, + DM_RATR_STA_MIDDLE = 2, + DM_RATR_STA_MIDDLELOW = 3, + DM_RATR_STA_LOW = 4, + DM_RATR_STA_ULTRALOW = 5, + DM_RATR_STA_MAX +}; + +#define DM_TYPE_BYFW 0 +#define DM_TYPE_BYDRIVER 1 + +#define TX_HIGH_PWR_LEVEL_NORMAL 0 +#define TX_HIGH_PWR_LEVEL_LEVEL1 1 +#define TX_HIGH_PWR_LEVEL_LEVEL2 2 + +#define HAL_DM_DIG_DISABLE BIT(0) /* Disable Dig */ +#define HAL_DM_HIPWR_DISABLE BIT(1) /* Disable High Power */ + +#define TX_HIGHPWR_LEVEL_NORMAL 0 +#define TX_HIGHPWR_LEVEL_NORMAL1 1 +#define TX_HIGHPWR_LEVEL_NORMAL2 2 + +#define TX_POWER_NEAR_FIELD_THRESH_LVL2 74 +#define TX_POWER_NEAR_FIELD_THRESH_LVL1 67 + +#define DM_DIG_THRESH_HIGH 40 +#define DM_DIG_THRESH_LOW 35 +#define DM_FALSEALARM_THRESH_LOW 40 +#define DM_FALSEALARM_THRESH_HIGH 1000 +#define DM_DIG_HIGH_PWR_THRESH_HIGH 75 +#define DM_DIG_HIGH_PWR_THRESH_LOW 70 +#define DM_DIG_BACKOFF 12 +#define DM_DIG_MAX 0x3e +#define DM_DIG_MIN 0x1c +#define DM_DIG_MIN_Netcore 0x12 +#define DM_DIG_BACKOFF_MAX 12 +#define DM_DIG_BACKOFF_MIN -4 + +void rtl92s_dm_watchdog(struct ieee80211_hw *hw); +void rtl92s_dm_init(struct ieee80211_hw *hw); +void rtl92s_dm_init_edca_turbo(struct ieee80211_hw *hw); + +#endif + diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/fw.c b/drivers/net/wireless/rtlwifi/rtl8192se/fw.c new file mode 100644 index 0000000..380e7d4 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/fw.c @@ -0,0 +1,656 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "../base.h" +#include "reg.h" +#include "def.h" +#include "fw.h" + +static void _rtl92s_fw_set_rqpn(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_dword(rtlpriv, RQPN, 0xffffffff); + rtl_write_dword(rtlpriv, RQPN + 4, 0xffffffff); + rtl_write_byte(rtlpriv, RQPN + 8, 0xff); + rtl_write_byte(rtlpriv, RQPN + 0xB, 0x80); +} + +static bool _rtl92s_firmware_enable_cpu(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 ichecktime = 200; + u16 tmpu2b; + u8 tmpu1b, cpustatus = 0; + + _rtl92s_fw_set_rqpn(hw); + + /* Enable CPU. */ + tmpu1b = rtl_read_byte(rtlpriv, SYS_CLKR); + /* AFE source */ + rtl_write_byte(rtlpriv, SYS_CLKR, (tmpu1b | SYS_CPU_CLKSEL)); + + tmpu2b = rtl_read_word(rtlpriv, REG_SYS_FUNC_EN); + rtl_write_word(rtlpriv, REG_SYS_FUNC_EN, (tmpu2b | FEN_CPUEN)); + + /* Polling IMEM Ready after CPU has refilled. */ + do { + cpustatus = rtl_read_byte(rtlpriv, TCR); + if (cpustatus & IMEM_RDY) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "IMEM Ready after CPU has refilled\n"); + break; + } + + udelay(100); + } while (ichecktime--); + + if (!(cpustatus & IMEM_RDY)) + return false; + + return true; +} + +static enum fw_status _rtl92s_firmware_get_nextstatus( + enum fw_status fw_currentstatus) +{ + enum fw_status next_fwstatus = 0; + + switch (fw_currentstatus) { + case FW_STATUS_INIT: + next_fwstatus = FW_STATUS_LOAD_IMEM; + break; + case FW_STATUS_LOAD_IMEM: + next_fwstatus = FW_STATUS_LOAD_EMEM; + break; + case FW_STATUS_LOAD_EMEM: + next_fwstatus = FW_STATUS_LOAD_DMEM; + break; + case FW_STATUS_LOAD_DMEM: + next_fwstatus = FW_STATUS_READY; + break; + default: + break; + } + + return next_fwstatus; +} + +static u8 _rtl92s_firmware_header_map_rftype(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + switch (rtlphy->rf_type) { + case RF_1T1R: + return 0x11; + break; + case RF_1T2R: + return 0x12; + break; + case RF_2T2R: + return 0x22; + break; + default: + RT_TRACE(rtlpriv, COMP_INIT, DBG_EMERG, "Unknown RF type(%x)\n", + rtlphy->rf_type); + break; + } + return 0x22; +} + +static void _rtl92s_firmwareheader_priveupdate(struct ieee80211_hw *hw, + struct fw_priv *pfw_priv) +{ + /* Update RF types for RATR settings. */ + pfw_priv->rf_config = _rtl92s_firmware_header_map_rftype(hw); +} + + + +static bool _rtl92s_cmd_send_packet(struct ieee80211_hw *hw, + struct sk_buff *skb, u8 last) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl8192_tx_ring *ring; + struct rtl_tx_desc *pdesc; + unsigned long flags; + u8 idx = 0; + + ring = &rtlpci->tx_ring[TXCMD_QUEUE]; + + spin_lock_irqsave(&rtlpriv->locks.irq_th_lock, flags); + + idx = (ring->idx + skb_queue_len(&ring->queue)) % ring->entries; + pdesc = &ring->desc[idx]; + rtlpriv->cfg->ops->fill_tx_cmddesc(hw, (u8 *)pdesc, 1, 1, skb); + __skb_queue_tail(&ring->queue, skb); + + spin_unlock_irqrestore(&rtlpriv->locks.irq_th_lock, flags); + + return true; +} + +static bool _rtl92s_firmware_downloadcode(struct ieee80211_hw *hw, + u8 *code_virtual_address, u32 buffer_len) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct sk_buff *skb; + struct rtl_tcb_desc *tcb_desc; + unsigned char *seg_ptr; + u16 frag_threshold = MAX_FIRMWARE_CODE_SIZE; + u16 frag_length, frag_offset = 0; + u16 extra_descoffset = 0; + u8 last_inipkt = 0; + + _rtl92s_fw_set_rqpn(hw); + + if (buffer_len >= MAX_FIRMWARE_CODE_SIZE) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Size over FIRMWARE_CODE_SIZE!\n"); + + return false; + } + + extra_descoffset = 0; + + do { + if ((buffer_len - frag_offset) > frag_threshold) { + frag_length = frag_threshold + extra_descoffset; + } else { + frag_length = (u16)(buffer_len - frag_offset + + extra_descoffset); + last_inipkt = 1; + } + + /* Allocate skb buffer to contain firmware */ + /* info and tx descriptor info. */ + skb = dev_alloc_skb(frag_length); + if (!skb) + return false; + skb_reserve(skb, extra_descoffset); + seg_ptr = (u8 *)skb_put(skb, (u32)(frag_length - + extra_descoffset)); + memcpy(seg_ptr, code_virtual_address + frag_offset, + (u32)(frag_length - extra_descoffset)); + + tcb_desc = (struct rtl_tcb_desc *)(skb->cb); + tcb_desc->queue_index = TXCMD_QUEUE; + tcb_desc->cmd_or_init = DESC_PACKET_TYPE_INIT; + tcb_desc->last_inipkt = last_inipkt; + + _rtl92s_cmd_send_packet(hw, skb, last_inipkt); + + frag_offset += (frag_length - extra_descoffset); + + } while (frag_offset < buffer_len); + + rtl_write_byte(rtlpriv, TP_POLL, TPPOLL_CQ); + + return true ; +} + +static bool _rtl92s_firmware_checkready(struct ieee80211_hw *hw, + u8 loadfw_status) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rt_firmware *firmware = (struct rt_firmware *)rtlhal->pfirmware; + u32 tmpu4b; + u8 cpustatus = 0; + short pollingcnt = 1000; + bool rtstatus = true; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "LoadStaus(%d)\n", loadfw_status); + + firmware->fwstatus = (enum fw_status)loadfw_status; + + switch (loadfw_status) { + case FW_STATUS_LOAD_IMEM: + /* Polling IMEM code done. */ + do { + cpustatus = rtl_read_byte(rtlpriv, TCR); + if (cpustatus & IMEM_CODE_DONE) + break; + udelay(5); + } while (pollingcnt--); + + if (!(cpustatus & IMEM_CHK_RPT) || (pollingcnt <= 0)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "FW_STATUS_LOAD_IMEM FAIL CPU, Status=%x\n", + cpustatus); + goto status_check_fail; + } + break; + + case FW_STATUS_LOAD_EMEM: + /* Check Put Code OK and Turn On CPU */ + /* Polling EMEM code done. */ + do { + cpustatus = rtl_read_byte(rtlpriv, TCR); + if (cpustatus & EMEM_CODE_DONE) + break; + udelay(5); + } while (pollingcnt--); + + if (!(cpustatus & EMEM_CHK_RPT) || (pollingcnt <= 0)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "FW_STATUS_LOAD_EMEM FAIL CPU, Status=%x\n", + cpustatus); + goto status_check_fail; + } + + /* Turn On CPU */ + rtstatus = _rtl92s_firmware_enable_cpu(hw); + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Enable CPU fail!\n"); + goto status_check_fail; + } + break; + + case FW_STATUS_LOAD_DMEM: + /* Polling DMEM code done */ + do { + cpustatus = rtl_read_byte(rtlpriv, TCR); + if (cpustatus & DMEM_CODE_DONE) + break; + udelay(5); + } while (pollingcnt--); + + if (!(cpustatus & DMEM_CODE_DONE) || (pollingcnt <= 0)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Polling DMEM code done fail ! cpustatus(%#x)\n", + cpustatus); + goto status_check_fail; + } + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "DMEM code download success, cpustatus(%#x)\n", + cpustatus); + + /* Prevent Delay too much and being scheduled out */ + /* Polling Load Firmware ready */ + pollingcnt = 2000; + do { + cpustatus = rtl_read_byte(rtlpriv, TCR); + if (cpustatus & FWRDY) + break; + udelay(40); + } while (pollingcnt--); + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Polling Load Firmware ready, cpustatus(%x)\n", + cpustatus); + + if (((cpustatus & LOAD_FW_READY) != LOAD_FW_READY) || + (pollingcnt <= 0)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Polling Load Firmware ready fail ! cpustatus(%x)\n", + cpustatus); + goto status_check_fail; + } + + /* If right here, we can set TCR/RCR to desired value */ + /* and config MAC lookback mode to normal mode */ + tmpu4b = rtl_read_dword(rtlpriv, TCR); + rtl_write_dword(rtlpriv, TCR, (tmpu4b & (~TCR_ICV))); + + tmpu4b = rtl_read_dword(rtlpriv, RCR); + rtl_write_dword(rtlpriv, RCR, (tmpu4b | RCR_APPFCS | + RCR_APP_ICV | RCR_APP_MIC)); + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Current RCR settings(%#x)\n", tmpu4b); + + /* Set to normal mode. */ + rtl_write_byte(rtlpriv, LBKMD_SEL, LBK_NORMAL); + break; + + default: + RT_TRACE(rtlpriv, COMP_INIT, DBG_EMERG, + "Unknown status check!\n"); + rtstatus = false; + break; + } + +status_check_fail: + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "loadfw_status(%d), rtstatus(%x)\n", + loadfw_status, rtstatus); + return rtstatus; +} + +int rtl92s_download_fw(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rt_firmware *firmware = NULL; + struct fw_hdr *pfwheader; + struct fw_priv *pfw_priv = NULL; + u8 *puc_mappedfile = NULL; + u32 ul_filelength = 0; + u8 fwhdr_size = RT_8192S_FIRMWARE_HDR_SIZE; + u8 fwstatus = FW_STATUS_INIT; + bool rtstatus = true; + + if (rtlpriv->max_fw_size == 0 || !rtlhal->pfirmware) + return 1; + + firmware = (struct rt_firmware *)rtlhal->pfirmware; + firmware->fwstatus = FW_STATUS_INIT; + + puc_mappedfile = firmware->sz_fw_tmpbuffer; + + /* 1. Retrieve FW header. */ + firmware->pfwheader = (struct fw_hdr *) puc_mappedfile; + pfwheader = firmware->pfwheader; + firmware->firmwareversion = byte(pfwheader->version, 0); + firmware->pfwheader->fwpriv.hci_sel = 1;/* pcie */ + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "signature:%x, version:%x, size:%x, imemsize:%x, sram size:%x\n", + pfwheader->signature, + pfwheader->version, pfwheader->dmem_size, + pfwheader->img_imem_size, pfwheader->img_sram_size); + + /* 2. Retrieve IMEM image. */ + if ((pfwheader->img_imem_size == 0) || (pfwheader->img_imem_size > + sizeof(firmware->fw_imem))) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "memory for data image is less than IMEM required\n"); + goto fail; + } else { + puc_mappedfile += fwhdr_size; + + memcpy(firmware->fw_imem, puc_mappedfile, + pfwheader->img_imem_size); + firmware->fw_imem_len = pfwheader->img_imem_size; + } + + /* 3. Retriecve EMEM image. */ + if (pfwheader->img_sram_size > sizeof(firmware->fw_emem)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "memory for data image is less than EMEM required\n"); + goto fail; + } else { + puc_mappedfile += firmware->fw_imem_len; + + memcpy(firmware->fw_emem, puc_mappedfile, + pfwheader->img_sram_size); + firmware->fw_emem_len = pfwheader->img_sram_size; + } + + /* 4. download fw now */ + fwstatus = _rtl92s_firmware_get_nextstatus(firmware->fwstatus); + while (fwstatus != FW_STATUS_READY) { + /* Image buffer redirection. */ + switch (fwstatus) { + case FW_STATUS_LOAD_IMEM: + puc_mappedfile = firmware->fw_imem; + ul_filelength = firmware->fw_imem_len; + break; + case FW_STATUS_LOAD_EMEM: + puc_mappedfile = firmware->fw_emem; + ul_filelength = firmware->fw_emem_len; + break; + case FW_STATUS_LOAD_DMEM: + /* Partial update the content of header private. */ + pfwheader = firmware->pfwheader; + pfw_priv = &pfwheader->fwpriv; + _rtl92s_firmwareheader_priveupdate(hw, pfw_priv); + puc_mappedfile = (u8 *)(firmware->pfwheader) + + RT_8192S_FIRMWARE_HDR_EXCLUDE_PRI_SIZE; + ul_filelength = fwhdr_size - + RT_8192S_FIRMWARE_HDR_EXCLUDE_PRI_SIZE; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Unexpected Download step!!\n"); + goto fail; + break; + } + + /* <2> Download image file */ + rtstatus = _rtl92s_firmware_downloadcode(hw, puc_mappedfile, + ul_filelength); + + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "fail!\n"); + goto fail; + } + + /* <3> Check whether load FW process is ready */ + rtstatus = _rtl92s_firmware_checkready(hw, fwstatus); + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "fail!\n"); + goto fail; + } + + fwstatus = _rtl92s_firmware_get_nextstatus(firmware->fwstatus); + } + + return rtstatus; +fail: + return 0; +} + +static u32 _rtl92s_fill_h2c_cmd(struct sk_buff *skb, u32 h2cbufferlen, + u32 cmd_num, u32 *pelement_id, u32 *pcmd_len, + u8 **pcmb_buffer, u8 *cmd_start_seq) +{ + u32 totallen = 0, len = 0, tx_desclen = 0; + u32 pre_continueoffset = 0; + u8 *ph2c_buffer; + u8 i = 0; + + do { + /* 8 - Byte aligment */ + len = H2C_TX_CMD_HDR_LEN + N_BYTE_ALIGMENT(pcmd_len[i], 8); + + /* Buffer length is not enough */ + if (h2cbufferlen < totallen + len + tx_desclen) + break; + + /* Clear content */ + ph2c_buffer = (u8 *)skb_put(skb, (u32)len); + memset((ph2c_buffer + totallen + tx_desclen), 0, len); + + /* CMD len */ + SET_BITS_TO_LE_4BYTE((ph2c_buffer + totallen + tx_desclen), + 0, 16, pcmd_len[i]); + + /* CMD ID */ + SET_BITS_TO_LE_4BYTE((ph2c_buffer + totallen + tx_desclen), + 16, 8, pelement_id[i]); + + /* CMD Sequence */ + *cmd_start_seq = *cmd_start_seq % 0x80; + SET_BITS_TO_LE_4BYTE((ph2c_buffer + totallen + tx_desclen), + 24, 7, *cmd_start_seq); + ++*cmd_start_seq; + + /* Copy memory */ + memcpy((ph2c_buffer + totallen + tx_desclen + + H2C_TX_CMD_HDR_LEN), pcmb_buffer[i], pcmd_len[i]); + + /* CMD continue */ + /* set the continue in prevoius cmd. */ + if (i < cmd_num - 1) + SET_BITS_TO_LE_4BYTE((ph2c_buffer + pre_continueoffset), + 31, 1, 1); + + pre_continueoffset = totallen; + + totallen += len; + } while (++i < cmd_num); + + return totallen; +} + +static u32 _rtl92s_get_h2c_cmdlen(u32 h2cbufferlen, u32 cmd_num, u32 *pcmd_len) +{ + u32 totallen = 0, len = 0, tx_desclen = 0; + u8 i = 0; + + do { + /* 8 - Byte aligment */ + len = H2C_TX_CMD_HDR_LEN + N_BYTE_ALIGMENT(pcmd_len[i], 8); + + /* Buffer length is not enough */ + if (h2cbufferlen < totallen + len + tx_desclen) + break; + + totallen += len; + } while (++i < cmd_num); + + return totallen + tx_desclen; +} + +static bool _rtl92s_firmware_set_h2c_cmd(struct ieee80211_hw *hw, u8 h2c_cmd, + u8 *pcmd_buffer) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_tcb_desc *cb_desc; + struct sk_buff *skb; + u32 element_id = 0; + u32 cmd_len = 0; + u32 len; + + switch (h2c_cmd) { + case FW_H2C_SETPWRMODE: + element_id = H2C_SETPWRMODE_CMD ; + cmd_len = sizeof(struct h2c_set_pwrmode_parm); + break; + case FW_H2C_JOINBSSRPT: + element_id = H2C_JOINBSSRPT_CMD; + cmd_len = sizeof(struct h2c_joinbss_rpt_parm); + break; + case FW_H2C_WOWLAN_UPDATE_GTK: + element_id = H2C_WOWLAN_UPDATE_GTK_CMD; + cmd_len = sizeof(struct h2c_wpa_two_way_parm); + break; + case FW_H2C_WOWLAN_UPDATE_IV: + element_id = H2C_WOWLAN_UPDATE_IV_CMD; + cmd_len = sizeof(unsigned long long); + break; + case FW_H2C_WOWLAN_OFFLOAD: + element_id = H2C_WOWLAN_FW_OFFLOAD; + cmd_len = sizeof(u8); + break; + default: + break; + } + + len = _rtl92s_get_h2c_cmdlen(MAX_TRANSMIT_BUFFER_SIZE, 1, &cmd_len); + skb = dev_alloc_skb(len); + if (!skb) + return false; + cb_desc = (struct rtl_tcb_desc *)(skb->cb); + cb_desc->queue_index = TXCMD_QUEUE; + cb_desc->cmd_or_init = DESC_PACKET_TYPE_NORMAL; + cb_desc->last_inipkt = false; + + _rtl92s_fill_h2c_cmd(skb, MAX_TRANSMIT_BUFFER_SIZE, 1, &element_id, + &cmd_len, &pcmd_buffer, &rtlhal->h2c_txcmd_seq); + _rtl92s_cmd_send_packet(hw, skb, false); + rtlpriv->cfg->ops->tx_polling(hw, TXCMD_QUEUE); + + return true; +} + +void rtl92s_set_fw_pwrmode_cmd(struct ieee80211_hw *hw, u8 Mode) +{ + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct h2c_set_pwrmode_parm pwrmode; + u16 max_wakeup_period = 0; + + pwrmode.mode = Mode; + pwrmode.flag_low_traffic_en = 0; + pwrmode.flag_lpnav_en = 0; + pwrmode.flag_rf_low_snr_en = 0; + pwrmode.flag_dps_en = 0; + pwrmode.bcn_rx_en = 0; + pwrmode.bcn_to = 0; + SET_BITS_TO_LE_2BYTE((u8 *)(&pwrmode) + 8, 0, 16, + mac->vif->bss_conf.beacon_int); + pwrmode.app_itv = 0; + pwrmode.awake_bcn_itvl = ppsc->reg_max_lps_awakeintvl; + pwrmode.smart_ps = 1; + pwrmode.bcn_pass_period = 10; + + /* Set beacon pass count */ + if (pwrmode.mode == FW_PS_MIN_MODE) + max_wakeup_period = mac->vif->bss_conf.beacon_int; + else if (pwrmode.mode == FW_PS_MAX_MODE) + max_wakeup_period = mac->vif->bss_conf.beacon_int * + mac->vif->bss_conf.dtim_period; + + if (max_wakeup_period >= 500) + pwrmode.bcn_pass_cnt = 1; + else if ((max_wakeup_period >= 300) && (max_wakeup_period < 500)) + pwrmode.bcn_pass_cnt = 2; + else if ((max_wakeup_period >= 200) && (max_wakeup_period < 300)) + pwrmode.bcn_pass_cnt = 3; + else if ((max_wakeup_period >= 20) && (max_wakeup_period < 200)) + pwrmode.bcn_pass_cnt = 5; + else + pwrmode.bcn_pass_cnt = 1; + + _rtl92s_firmware_set_h2c_cmd(hw, FW_H2C_SETPWRMODE, (u8 *)&pwrmode); + +} + +void rtl92s_set_fw_joinbss_report_cmd(struct ieee80211_hw *hw, + u8 mstatus, u8 ps_qosinfo) +{ + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct h2c_joinbss_rpt_parm joinbss_rpt; + + joinbss_rpt.opmode = mstatus; + joinbss_rpt.ps_qos_info = ps_qosinfo; + joinbss_rpt.bssid[0] = mac->bssid[0]; + joinbss_rpt.bssid[1] = mac->bssid[1]; + joinbss_rpt.bssid[2] = mac->bssid[2]; + joinbss_rpt.bssid[3] = mac->bssid[3]; + joinbss_rpt.bssid[4] = mac->bssid[4]; + joinbss_rpt.bssid[5] = mac->bssid[5]; + SET_BITS_TO_LE_2BYTE((u8 *)(&joinbss_rpt) + 8, 0, 16, + mac->vif->bss_conf.beacon_int); + SET_BITS_TO_LE_2BYTE((u8 *)(&joinbss_rpt) + 10, 0, 16, mac->assoc_id); + + _rtl92s_firmware_set_h2c_cmd(hw, FW_H2C_JOINBSSRPT, (u8 *)&joinbss_rpt); +} + diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/fw.h b/drivers/net/wireless/rtlwifi/rtl8192se/fw.h new file mode 100644 index 0000000..d53f433 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/fw.h @@ -0,0 +1,376 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ +#ifndef __REALTEK_FIRMWARE92S_H__ +#define __REALTEK_FIRMWARE92S_H__ + +#define RTL8190_MAX_FIRMWARE_CODE_SIZE 64000 +#define RTL8190_MAX_RAW_FIRMWARE_CODE_SIZE 90000 +#define RTL8190_CPU_START_OFFSET 0x80 +/* Firmware Local buffer size. 64k */ +#define MAX_FIRMWARE_CODE_SIZE 0xFF00 + +#define RT_8192S_FIRMWARE_HDR_SIZE 80 +#define RT_8192S_FIRMWARE_HDR_EXCLUDE_PRI_SIZE 32 + +/* support till 64 bit bus width OS */ +#define MAX_DEV_ADDR_SIZE 8 +#define MAX_FIRMWARE_INFORMATION_SIZE 32 +#define MAX_802_11_HEADER_LENGTH (40 + \ + MAX_FIRMWARE_INFORMATION_SIZE) +#define ENCRYPTION_MAX_OVERHEAD 128 +#define MAX_FRAGMENT_COUNT 8 +#define MAX_TRANSMIT_BUFFER_SIZE (1600 + \ + (MAX_802_11_HEADER_LENGTH + \ + ENCRYPTION_MAX_OVERHEAD) *\ + MAX_FRAGMENT_COUNT) + +#define H2C_TX_CMD_HDR_LEN 8 + +/* The following DM control code are for Reg0x364, */ +#define FW_DIG_ENABLE_CTL BIT(0) +#define FW_HIGH_PWR_ENABLE_CTL BIT(1) +#define FW_SS_CTL BIT(2) +#define FW_RA_INIT_CTL BIT(3) +#define FW_RA_BG_CTL BIT(4) +#define FW_RA_N_CTL BIT(5) +#define FW_PWR_TRK_CTL BIT(6) +#define FW_IQK_CTL BIT(7) +#define FW_FA_CTL BIT(8) +#define FW_DRIVER_CTRL_DM_CTL BIT(9) +#define FW_PAPE_CTL_BY_SW_HW BIT(10) +#define FW_DISABLE_ALL_DM 0 +#define FW_PWR_TRK_PARAM_CLR 0x0000ffff +#define FW_RA_PARAM_CLR 0xffff0000 + +enum desc_packet_type { + DESC_PACKET_TYPE_INIT = 0, + DESC_PACKET_TYPE_NORMAL = 1, +}; + +/* 8-bytes alignment required */ +struct fw_priv { + /* --- long word 0 ---- */ + /* 0x12: CE product, 0x92: IT product */ + u8 signature_0; + /* 0x87: CE product, 0x81: IT product */ + u8 signature_1; + /* 0x81: PCI-AP, 01:PCIe, 02: 92S-U, + * 0x82: USB-AP, 0x12: 72S-U, 03:SDIO */ + u8 hci_sel; + /* the same value as reigster value */ + u8 chip_version; + /* customer ID low byte */ + u8 customer_id_0; + /* customer ID high byte */ + u8 customer_id_1; + /* 0x11: 1T1R, 0x12: 1T2R, + * 0x92: 1T2R turbo, 0x22: 2T2R */ + u8 rf_config; + /* 4: 4EP, 6: 6EP, 11: 11EP */ + u8 usb_ep_num; + + /* --- long word 1 ---- */ + /* regulatory class bit map 0 */ + u8 regulatory_class_0; + /* regulatory class bit map 1 */ + u8 regulatory_class_1; + /* regulatory class bit map 2 */ + u8 regulatory_class_2; + /* regulatory class bit map 3 */ + u8 regulatory_class_3; + /* 0:SWSI, 1:HWSI, 2:HWPI */ + u8 rfintfs; + u8 def_nettype; + u8 rsvd010; + u8 rsvd011; + + /* --- long word 2 ---- */ + /* 0x00: normal, 0x03: MACLBK, 0x01: PHYLBK */ + u8 lbk_mode; + /* 1: for MP use, 0: for normal + * driver (to be discussed) */ + u8 mp_mode; + u8 rsvd020; + u8 rsvd021; + u8 rsvd022; + u8 rsvd023; + u8 rsvd024; + u8 rsvd025; + + /* --- long word 3 ---- */ + /* QoS enable */ + u8 qos_en; + /* 40MHz BW enable */ + /* 4181 convert AMSDU to AMPDU, 0: disable */ + u8 bw_40mhz_en; + u8 amsdu2ampdu_en; + /* 11n AMPDU enable */ + u8 ampdu_en; + /* FW offloads, 0: driver handles */ + u8 rate_control_offload; + /* FW offloads, 0: driver handles */ + u8 aggregation_offload; + u8 rsvd030; + u8 rsvd031; + + /* --- long word 4 ---- */ + /* 1. FW offloads, 0: driver handles */ + u8 beacon_offload; + /* 2. FW offloads, 0: driver handles */ + u8 mlme_offload; + /* 3. FW offloads, 0: driver handles */ + u8 hwpc_offload; + /* 4. FW offloads, 0: driver handles */ + u8 tcp_checksum_offload; + /* 5. FW offloads, 0: driver handles */ + u8 tcp_offload; + /* 6. FW offloads, 0: driver handles */ + u8 ps_control_offload; + /* 7. FW offloads, 0: driver handles */ + u8 wwlan_offload; + u8 rsvd040; + + /* --- long word 5 ---- */ + /* tcp tx packet length low byte */ + u8 tcp_tx_frame_len_L; + /* tcp tx packet length high byte */ + u8 tcp_tx_frame_len_H; + /* tcp rx packet length low byte */ + u8 tcp_rx_frame_len_L; + /* tcp rx packet length high byte */ + u8 tcp_rx_frame_len_H; + u8 rsvd050; + u8 rsvd051; + u8 rsvd052; + u8 rsvd053; +}; + +/* 8-byte alinment required */ +struct fw_hdr { + + /* --- LONG WORD 0 ---- */ + u16 signature; + /* 0x8000 ~ 0x8FFF for FPGA version, + * 0x0000 ~ 0x7FFF for ASIC version, */ + u16 version; + /* define the size of boot loader */ + u32 dmem_size; + + + /* --- LONG WORD 1 ---- */ + /* define the size of FW in IMEM */ + u32 img_imem_size; + /* define the size of FW in SRAM */ + u32 img_sram_size; + + /* --- LONG WORD 2 ---- */ + /* define the size of DMEM variable */ + u32 fw_priv_size; + u32 rsvd0; + + /* --- LONG WORD 3 ---- */ + u32 rsvd1; + u32 rsvd2; + + struct fw_priv fwpriv; + +} ; + +enum fw_status { + FW_STATUS_INIT = 0, + FW_STATUS_LOAD_IMEM = 1, + FW_STATUS_LOAD_EMEM = 2, + FW_STATUS_LOAD_DMEM = 3, + FW_STATUS_READY = 4, +}; + +struct rt_firmware { + struct fw_hdr *pfwheader; + enum fw_status fwstatus; + u16 firmwareversion; + u8 fw_imem[RTL8190_MAX_FIRMWARE_CODE_SIZE]; + u8 fw_emem[RTL8190_MAX_FIRMWARE_CODE_SIZE]; + u32 fw_imem_len; + u32 fw_emem_len; + u8 sz_fw_tmpbuffer[RTL8190_MAX_RAW_FIRMWARE_CODE_SIZE]; + u32 sz_fw_tmpbufferlen; + u16 cmdpacket_fragthresold; +}; + +struct h2c_set_pwrmode_parm { + u8 mode; + u8 flag_low_traffic_en; + u8 flag_lpnav_en; + u8 flag_rf_low_snr_en; + /* 1: dps, 0: 32k */ + u8 flag_dps_en; + u8 bcn_rx_en; + u8 bcn_pass_cnt; + /* beacon TO (ms). ¡§=0¡¨ no limit. */ + u8 bcn_to; + u16 bcn_itv; + /* only for VOIP mode. */ + u8 app_itv; + u8 awake_bcn_itvl; + u8 smart_ps; + /* unit: 100 ms */ + u8 bcn_pass_period; +}; + +struct h2c_joinbss_rpt_parm { + u8 opmode; + u8 ps_qos_info; + u8 bssid[6]; + u16 bcnitv; + u16 aid; +} ; + +struct h2c_wpa_ptk { + /* EAPOL-Key Key Confirmation Key (KCK) */ + u8 kck[16]; + /* EAPOL-Key Key Encryption Key (KEK) */ + u8 kek[16]; + /* Temporal Key 1 (TK1) */ + u8 tk1[16]; + union { + /* Temporal Key 2 (TK2) */ + u8 tk2[16]; + struct { + u8 tx_mic_key[8]; + u8 rx_mic_key[8]; + } athu; + } u; +}; + +struct h2c_wpa_two_way_parm { + /* algorithm TKIP or AES */ + u8 pairwise_en_alg; + u8 group_en_alg; + struct h2c_wpa_ptk wpa_ptk_value; +} ; + +enum h2c_cmd { + FW_H2C_SETPWRMODE = 0, + FW_H2C_JOINBSSRPT = 1, + FW_H2C_WOWLAN_UPDATE_GTK = 2, + FW_H2C_WOWLAN_UPDATE_IV = 3, + FW_H2C_WOWLAN_OFFLOAD = 4, +}; + +enum fw_h2c_cmd { + H2C_READ_MACREG_CMD, /*0*/ + H2C_WRITE_MACREG_CMD, + H2C_READBB_CMD, + H2C_WRITEBB_CMD, + H2C_READRF_CMD, + H2C_WRITERF_CMD, /*5*/ + H2C_READ_EEPROM_CMD, + H2C_WRITE_EEPROM_CMD, + H2C_READ_EFUSE_CMD, + H2C_WRITE_EFUSE_CMD, + H2C_READ_CAM_CMD, /*10*/ + H2C_WRITE_CAM_CMD, + H2C_SETBCNITV_CMD, + H2C_SETMBIDCFG_CMD, + H2C_JOINBSS_CMD, + H2C_DISCONNECT_CMD, /*15*/ + H2C_CREATEBSS_CMD, + H2C_SETOPMode_CMD, + H2C_SITESURVEY_CMD, + H2C_SETAUTH_CMD, + H2C_SETKEY_CMD, /*20*/ + H2C_SETSTAKEY_CMD, + H2C_SETASSOCSTA_CMD, + H2C_DELASSOCSTA_CMD, + H2C_SETSTAPWRSTATE_CMD, + H2C_SETBASICRATE_CMD, /*25*/ + H2C_GETBASICRATE_CMD, + H2C_SETDATARATE_CMD, + H2C_GETDATARATE_CMD, + H2C_SETPHYINFO_CMD, + H2C_GETPHYINFO_CMD, /*30*/ + H2C_SETPHY_CMD, + H2C_GETPHY_CMD, + H2C_READRSSI_CMD, + H2C_READGAIN_CMD, + H2C_SETATIM_CMD, /*35*/ + H2C_SETPWRMODE_CMD, + H2C_JOINBSSRPT_CMD, + H2C_SETRATABLE_CMD, + H2C_GETRATABLE_CMD, + H2C_GETCCXREPORT_CMD, /*40*/ + H2C_GETDTMREPORT_CMD, + H2C_GETTXRATESTATICS_CMD, + H2C_SETUSBSUSPEND_CMD, + H2C_SETH2CLBK_CMD, + H2C_TMP1, /*45*/ + H2C_WOWLAN_UPDATE_GTK_CMD, + H2C_WOWLAN_FW_OFFLOAD, + H2C_TMP2, + H2C_TMP3, + H2C_WOWLAN_UPDATE_IV_CMD, /*50*/ + H2C_TMP4, + MAX_H2CCMD /*52*/ +}; + +/* The following macros are used for FW + * CMD map and parameter updated. */ +#define FW_CMD_IO_CLR(rtlpriv, _Bit) \ + do { \ + udelay(1000); \ + rtlpriv->rtlhal.fwcmd_iomap &= (~_Bit); \ + } while (0) + +#define FW_CMD_IO_UPDATE(rtlpriv, _val) \ + rtlpriv->rtlhal.fwcmd_iomap = _val; + +#define FW_CMD_IO_SET(rtlpriv, _val) \ + do { \ + rtl_write_word(rtlpriv, LBUS_MON_ADDR, (u16)_val); \ + FW_CMD_IO_UPDATE(rtlpriv, _val); \ + } while (0) + +#define FW_CMD_PARA_SET(rtlpriv, _val) \ + do { \ + rtl_write_dword(rtlpriv, LBUS_ADDR_MASK, _val); \ + rtlpriv->rtlhal.fwcmd_ioparam = _val; \ + } while (0) + +#define FW_CMD_IO_QUERY(rtlpriv) \ + (u16)(rtlpriv->rtlhal.fwcmd_iomap) +#define FW_CMD_IO_PARA_QUERY(rtlpriv) \ + ((u32)(rtlpriv->rtlhal.fwcmd_ioparam)) + +int rtl92s_download_fw(struct ieee80211_hw *hw); +void rtl92s_set_fw_pwrmode_cmd(struct ieee80211_hw *hw, u8 mode); +void rtl92s_set_fw_joinbss_report_cmd(struct ieee80211_hw *hw, + u8 mstatus, u8 ps_qosinfo); + +#endif + diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/hw.c b/drivers/net/wireless/rtlwifi/rtl8192se/hw.c new file mode 100644 index 0000000..4f46178 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/hw.c @@ -0,0 +1,2552 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../efuse.h" +#include "../base.h" +#include "../regd.h" +#include "../cam.h" +#include "../ps.h" +#include "../pci.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "dm.h" +#include "fw.h" +#include "led.h" +#include "hw.h" + +void rtl92se_get_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + switch (variable) { + case HW_VAR_RCR: { + *((u32 *) (val)) = rtlpci->receive_config; + break; + } + case HW_VAR_RF_STATE: { + *((enum rf_pwrstate *)(val)) = ppsc->rfpwr_state; + break; + } + case HW_VAR_FW_PSMODE_STATUS: { + *((bool *) (val)) = ppsc->fw_current_inpsmode; + break; + } + case HW_VAR_CORRECT_TSF: { + u64 tsf; + u32 *ptsf_low = (u32 *)&tsf; + u32 *ptsf_high = ((u32 *)&tsf) + 1; + + *ptsf_high = rtl_read_dword(rtlpriv, (TSFR + 4)); + *ptsf_low = rtl_read_dword(rtlpriv, TSFR); + + *((u64 *) (val)) = tsf; + + break; + } + case HW_VAR_MRC: { + *((bool *)(val)) = rtlpriv->dm.current_mrc_switch; + break; + } + default: { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + } +} + +void rtl92se_set_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + switch (variable) { + case HW_VAR_ETHER_ADDR:{ + rtl_write_dword(rtlpriv, IDR0, ((u32 *)(val))[0]); + rtl_write_word(rtlpriv, IDR4, ((u16 *)(val + 4))[0]); + break; + } + case HW_VAR_BASIC_RATE:{ + u16 rate_cfg = ((u16 *) val)[0]; + u8 rate_index = 0; + + if (rtlhal->version == VERSION_8192S_ACUT) + rate_cfg = rate_cfg & 0x150; + else + rate_cfg = rate_cfg & 0x15f; + + rate_cfg |= 0x01; + + rtl_write_byte(rtlpriv, RRSR, rate_cfg & 0xff); + rtl_write_byte(rtlpriv, RRSR + 1, + (rate_cfg >> 8) & 0xff); + + while (rate_cfg > 0x1) { + rate_cfg = (rate_cfg >> 1); + rate_index++; + } + rtl_write_byte(rtlpriv, INIRTSMCS_SEL, rate_index); + + break; + } + case HW_VAR_BSSID:{ + rtl_write_dword(rtlpriv, BSSIDR, ((u32 *)(val))[0]); + rtl_write_word(rtlpriv, BSSIDR + 4, + ((u16 *)(val + 4))[0]); + break; + } + case HW_VAR_SIFS:{ + rtl_write_byte(rtlpriv, SIFS_OFDM, val[0]); + rtl_write_byte(rtlpriv, SIFS_OFDM + 1, val[1]); + break; + } + case HW_VAR_SLOT_TIME:{ + u8 e_aci; + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "HW_VAR_SLOT_TIME %x\n", val[0]); + + rtl_write_byte(rtlpriv, SLOT_TIME, val[0]); + + for (e_aci = 0; e_aci < AC_MAX; e_aci++) { + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_AC_PARAM, + (&e_aci)); + } + break; + } + case HW_VAR_ACK_PREAMBLE:{ + u8 reg_tmp; + u8 short_preamble = (bool) (*val); + reg_tmp = (mac->cur_40_prime_sc) << 5; + if (short_preamble) + reg_tmp |= 0x80; + + rtl_write_byte(rtlpriv, RRSR + 2, reg_tmp); + break; + } + case HW_VAR_AMPDU_MIN_SPACE:{ + u8 min_spacing_to_set; + u8 sec_min_space; + + min_spacing_to_set = *val; + if (min_spacing_to_set <= 7) { + if (rtlpriv->sec.pairwise_enc_algorithm == + NO_ENCRYPTION) + sec_min_space = 0; + else + sec_min_space = 1; + + if (min_spacing_to_set < sec_min_space) + min_spacing_to_set = sec_min_space; + if (min_spacing_to_set > 5) + min_spacing_to_set = 5; + + mac->min_space_cfg = + ((mac->min_space_cfg & 0xf8) | + min_spacing_to_set); + + *val = min_spacing_to_set; + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_AMPDU_MIN_SPACE: %#x\n", + mac->min_space_cfg); + + rtl_write_byte(rtlpriv, AMPDU_MIN_SPACE, + mac->min_space_cfg); + } + break; + } + case HW_VAR_SHORTGI_DENSITY:{ + u8 density_to_set; + + density_to_set = *val; + mac->min_space_cfg = rtlpriv->rtlhal.minspace_cfg; + mac->min_space_cfg |= (density_to_set << 3); + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_SHORTGI_DENSITY: %#x\n", + mac->min_space_cfg); + + rtl_write_byte(rtlpriv, AMPDU_MIN_SPACE, + mac->min_space_cfg); + + break; + } + case HW_VAR_AMPDU_FACTOR:{ + u8 factor_toset; + u8 regtoset; + u8 factorlevel[18] = { + 2, 4, 4, 7, 7, 13, 13, + 13, 2, 7, 7, 13, 13, + 15, 15, 15, 15, 0}; + u8 index = 0; + + factor_toset = *val; + if (factor_toset <= 3) { + factor_toset = (1 << (factor_toset + 2)); + if (factor_toset > 0xf) + factor_toset = 0xf; + + for (index = 0; index < 17; index++) { + if (factorlevel[index] > factor_toset) + factorlevel[index] = + factor_toset; + } + + for (index = 0; index < 8; index++) { + regtoset = ((factorlevel[index * 2]) | + (factorlevel[index * + 2 + 1] << 4)); + rtl_write_byte(rtlpriv, + AGGLEN_LMT_L + index, + regtoset); + } + + regtoset = ((factorlevel[16]) | + (factorlevel[17] << 4)); + rtl_write_byte(rtlpriv, AGGLEN_LMT_H, regtoset); + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_AMPDU_FACTOR: %#x\n", + factor_toset); + } + break; + } + case HW_VAR_AC_PARAM:{ + u8 e_aci = *val; + rtl92s_dm_init_edca_turbo(hw); + + if (rtlpci->acm_method != eAcmWay2_SW) + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_ACM_CTRL, + &e_aci); + break; + } + case HW_VAR_ACM_CTRL:{ + u8 e_aci = *val; + union aci_aifsn *p_aci_aifsn = (union aci_aifsn *)(&( + mac->ac[0].aifs)); + u8 acm = p_aci_aifsn->f.acm; + u8 acm_ctrl = rtl_read_byte(rtlpriv, AcmHwCtrl); + + acm_ctrl = acm_ctrl | ((rtlpci->acm_method == 2) ? + 0x0 : 0x1); + + if (acm) { + switch (e_aci) { + case AC0_BE: + acm_ctrl |= AcmHw_BeqEn; + break; + case AC2_VI: + acm_ctrl |= AcmHw_ViqEn; + break; + case AC3_VO: + acm_ctrl |= AcmHw_VoqEn; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "HW_VAR_ACM_CTRL acm set failed: eACI is %d\n", + acm); + break; + } + } else { + switch (e_aci) { + case AC0_BE: + acm_ctrl &= (~AcmHw_BeqEn); + break; + case AC2_VI: + acm_ctrl &= (~AcmHw_ViqEn); + break; + case AC3_VO: + acm_ctrl &= (~AcmHw_BeqEn); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + } + + RT_TRACE(rtlpriv, COMP_QOS, DBG_TRACE, + "HW_VAR_ACM_CTRL Write 0x%X\n", acm_ctrl); + rtl_write_byte(rtlpriv, AcmHwCtrl, acm_ctrl); + break; + } + case HW_VAR_RCR:{ + rtl_write_dword(rtlpriv, RCR, ((u32 *) (val))[0]); + rtlpci->receive_config = ((u32 *) (val))[0]; + break; + } + case HW_VAR_RETRY_LIMIT:{ + u8 retry_limit = val[0]; + + rtl_write_word(rtlpriv, RETRY_LIMIT, + retry_limit << RETRY_LIMIT_SHORT_SHIFT | + retry_limit << RETRY_LIMIT_LONG_SHIFT); + break; + } + case HW_VAR_DUAL_TSF_RST: { + break; + } + case HW_VAR_EFUSE_BYTES: { + rtlefuse->efuse_usedbytes = *((u16 *) val); + break; + } + case HW_VAR_EFUSE_USAGE: { + rtlefuse->efuse_usedpercentage = *val; + break; + } + case HW_VAR_IO_CMD: { + break; + } + case HW_VAR_WPA_CONFIG: { + rtl_write_byte(rtlpriv, REG_SECR, *val); + break; + } + case HW_VAR_SET_RPWM:{ + break; + } + case HW_VAR_H2C_FW_PWRMODE:{ + break; + } + case HW_VAR_FW_PSMODE_STATUS: { + ppsc->fw_current_inpsmode = *((bool *) val); + break; + } + case HW_VAR_H2C_FW_JOINBSSRPT:{ + break; + } + case HW_VAR_AID:{ + break; + } + case HW_VAR_CORRECT_TSF:{ + break; + } + case HW_VAR_MRC: { + bool bmrc_toset = *((bool *)val); + u8 u1bdata = 0; + + if (bmrc_toset) { + rtl_set_bbreg(hw, ROFDM0_TRXPATHENABLE, + MASKBYTE0, 0x33); + u1bdata = (u8)rtl_get_bbreg(hw, + ROFDM1_TRXPATHENABLE, + MASKBYTE0); + rtl_set_bbreg(hw, ROFDM1_TRXPATHENABLE, + MASKBYTE0, + ((u1bdata & 0xf0) | 0x03)); + u1bdata = (u8)rtl_get_bbreg(hw, + ROFDM0_TRXPATHENABLE, + MASKBYTE1); + rtl_set_bbreg(hw, ROFDM0_TRXPATHENABLE, + MASKBYTE1, + (u1bdata | 0x04)); + + /* Update current settings. */ + rtlpriv->dm.current_mrc_switch = bmrc_toset; + } else { + rtl_set_bbreg(hw, ROFDM0_TRXPATHENABLE, + MASKBYTE0, 0x13); + u1bdata = (u8)rtl_get_bbreg(hw, + ROFDM1_TRXPATHENABLE, + MASKBYTE0); + rtl_set_bbreg(hw, ROFDM1_TRXPATHENABLE, + MASKBYTE0, + ((u1bdata & 0xf0) | 0x01)); + u1bdata = (u8)rtl_get_bbreg(hw, + ROFDM0_TRXPATHENABLE, + MASKBYTE1); + rtl_set_bbreg(hw, ROFDM0_TRXPATHENABLE, + MASKBYTE1, (u1bdata & 0xfb)); + + /* Update current settings. */ + rtlpriv->dm.current_mrc_switch = bmrc_toset; + } + + break; + } + case HW_VAR_FW_LPS_ACTION: { + bool enter_fwlps = *((bool *)val); + u8 rpwm_val, fw_pwrmode; + bool fw_current_inps; + + if (enter_fwlps) { + rpwm_val = 0x02; /* RF off */ + fw_current_inps = true; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_FW_PSMODE_STATUS, + (u8 *)(&fw_current_inps)); + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_H2C_FW_PWRMODE, + (u8 *)(&ppsc->fwctrl_psmode)); + + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_SET_RPWM, + (u8 *)(&rpwm_val)); + } else { + rpwm_val = 0x0C; /* RF on */ + fw_pwrmode = FW_PS_ACTIVE_MODE; + fw_current_inps = false; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SET_RPWM, + (u8 *)(&rpwm_val)); + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_H2C_FW_PWRMODE, + (u8 *)(&fw_pwrmode)); + + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_FW_PSMODE_STATUS, + (u8 *)(&fw_current_inps)); + } + break; } + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + +} + +void rtl92se_enable_hw_security_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 sec_reg_value = 0x0; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "PairwiseEncAlgorithm = %d GroupEncAlgorithm = %d\n", + rtlpriv->sec.pairwise_enc_algorithm, + rtlpriv->sec.group_enc_algorithm); + + if (rtlpriv->cfg->mod_params->sw_crypto || rtlpriv->sec.use_sw_sec) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "not open hw encryption\n"); + return; + } + + sec_reg_value = SCR_TXENCENABLE | SCR_RXENCENABLE; + + if (rtlpriv->sec.use_defaultkey) { + sec_reg_value |= SCR_TXUSEDK; + sec_reg_value |= SCR_RXUSEDK; + } + + RT_TRACE(rtlpriv, COMP_SEC, DBG_LOUD, "The SECR-value %x\n", + sec_reg_value); + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_WPA_CONFIG, &sec_reg_value); + +} + +static u8 _rtl92se_halset_sysclk(struct ieee80211_hw *hw, u8 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 waitcount = 100; + bool bresult = false; + u8 tmpvalue; + + rtl_write_byte(rtlpriv, SYS_CLKR + 1, data); + + /* Wait the MAC synchronized. */ + udelay(400); + + /* Check if it is set ready. */ + tmpvalue = rtl_read_byte(rtlpriv, SYS_CLKR + 1); + bresult = ((tmpvalue & BIT(7)) == (data & BIT(7))); + + if ((data & (BIT(6) | BIT(7))) == false) { + waitcount = 100; + tmpvalue = 0; + + while (1) { + waitcount--; + + tmpvalue = rtl_read_byte(rtlpriv, SYS_CLKR + 1); + if ((tmpvalue & BIT(6))) + break; + + pr_err("wait for BIT(6) return value %x\n", tmpvalue); + if (waitcount == 0) + break; + + udelay(10); + } + + if (waitcount == 0) + bresult = false; + else + bresult = true; + } + + return bresult; +} + +void rtl8192se_gpiobit3_cfg_inputmode(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 u1tmp; + + /* The following config GPIO function */ + rtl_write_byte(rtlpriv, MAC_PINMUX_CFG, (GPIOMUX_EN | GPIOSEL_GPIO)); + u1tmp = rtl_read_byte(rtlpriv, GPIO_IO_SEL); + + /* config GPIO3 to input */ + u1tmp &= HAL_8192S_HW_GPIO_OFF_MASK; + rtl_write_byte(rtlpriv, GPIO_IO_SEL, u1tmp); + +} + +static u8 _rtl92se_rf_onoff_detect(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 u1tmp; + u8 retval = ERFON; + + /* The following config GPIO function */ + rtl_write_byte(rtlpriv, MAC_PINMUX_CFG, (GPIOMUX_EN | GPIOSEL_GPIO)); + u1tmp = rtl_read_byte(rtlpriv, GPIO_IO_SEL); + + /* config GPIO3 to input */ + u1tmp &= HAL_8192S_HW_GPIO_OFF_MASK; + rtl_write_byte(rtlpriv, GPIO_IO_SEL, u1tmp); + + /* On some of the platform, driver cannot read correct + * value without delay between Write_GPIO_SEL and Read_GPIO_IN */ + mdelay(10); + + /* check GPIO3 */ + u1tmp = rtl_read_byte(rtlpriv, GPIO_IN_SE); + retval = (u1tmp & HAL_8192S_HW_GPIO_OFF_BIT) ? ERFON : ERFOFF; + + return retval; +} + +static void _rtl92se_macconfig_before_fwdownload(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + u8 i; + u8 tmpu1b; + u16 tmpu2b; + u8 pollingcnt = 20; + + if (rtlpci->first_init) { + /* Reset PCIE Digital */ + tmpu1b = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN + 1); + tmpu1b &= 0xFE; + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN + 1, tmpu1b); + udelay(1); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN + 1, tmpu1b | BIT(0)); + } + + /* Switch to SW IO control */ + tmpu1b = rtl_read_byte(rtlpriv, (SYS_CLKR + 1)); + if (tmpu1b & BIT(7)) { + tmpu1b &= ~(BIT(6) | BIT(7)); + + /* Set failed, return to prevent hang. */ + if (!_rtl92se_halset_sysclk(hw, tmpu1b)) + return; + } + + rtl_write_byte(rtlpriv, AFE_PLL_CTRL, 0x0); + udelay(50); + rtl_write_byte(rtlpriv, LDOA15_CTRL, 0x34); + udelay(50); + + /* Clear FW RPWM for FW control LPS.*/ + rtl_write_byte(rtlpriv, RPWM, 0x0); + + /* Reset MAC-IO and CPU and Core Digital BIT(10)/11/15 */ + tmpu1b = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN + 1); + tmpu1b &= 0x73; + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN + 1, tmpu1b); + /* wait for BIT 10/11/15 to pull high automatically!! */ + mdelay(1); + + rtl_write_byte(rtlpriv, CMDR, 0); + rtl_write_byte(rtlpriv, TCR, 0); + + /* Data sheet not define 0x562!!! Copy from WMAC!!!!! */ + tmpu1b = rtl_read_byte(rtlpriv, 0x562); + tmpu1b |= 0x08; + rtl_write_byte(rtlpriv, 0x562, tmpu1b); + tmpu1b &= ~(BIT(3)); + rtl_write_byte(rtlpriv, 0x562, tmpu1b); + + /* Enable AFE clock source */ + tmpu1b = rtl_read_byte(rtlpriv, AFE_XTAL_CTRL); + rtl_write_byte(rtlpriv, AFE_XTAL_CTRL, (tmpu1b | 0x01)); + /* Delay 1.5ms */ + mdelay(2); + tmpu1b = rtl_read_byte(rtlpriv, AFE_XTAL_CTRL + 1); + rtl_write_byte(rtlpriv, AFE_XTAL_CTRL + 1, (tmpu1b & 0xfb)); + + /* Enable AFE Macro Block's Bandgap */ + tmpu1b = rtl_read_byte(rtlpriv, AFE_MISC); + rtl_write_byte(rtlpriv, AFE_MISC, (tmpu1b | BIT(0))); + mdelay(1); + + /* Enable AFE Mbias */ + tmpu1b = rtl_read_byte(rtlpriv, AFE_MISC); + rtl_write_byte(rtlpriv, AFE_MISC, (tmpu1b | 0x02)); + mdelay(1); + + /* Enable LDOA15 block */ + tmpu1b = rtl_read_byte(rtlpriv, LDOA15_CTRL); + rtl_write_byte(rtlpriv, LDOA15_CTRL, (tmpu1b | BIT(0))); + + /* Set Digital Vdd to Retention isolation Path. */ + tmpu2b = rtl_read_word(rtlpriv, REG_SYS_ISO_CTRL); + rtl_write_word(rtlpriv, REG_SYS_ISO_CTRL, (tmpu2b | BIT(11))); + + /* For warm reboot NIC disappera bug. */ + tmpu2b = rtl_read_word(rtlpriv, REG_SYS_FUNC_EN); + rtl_write_word(rtlpriv, REG_SYS_FUNC_EN, (tmpu2b | BIT(13))); + + rtl_write_byte(rtlpriv, REG_SYS_ISO_CTRL + 1, 0x68); + + /* Enable AFE PLL Macro Block */ + /* We need to delay 100u before enabling PLL. */ + udelay(200); + tmpu1b = rtl_read_byte(rtlpriv, AFE_PLL_CTRL); + rtl_write_byte(rtlpriv, AFE_PLL_CTRL, (tmpu1b | BIT(0) | BIT(4))); + + /* for divider reset */ + udelay(100); + rtl_write_byte(rtlpriv, AFE_PLL_CTRL, (tmpu1b | BIT(0) | + BIT(4) | BIT(6))); + udelay(10); + rtl_write_byte(rtlpriv, AFE_PLL_CTRL, (tmpu1b | BIT(0) | BIT(4))); + udelay(10); + + /* Enable MAC 80MHZ clock */ + tmpu1b = rtl_read_byte(rtlpriv, AFE_PLL_CTRL + 1); + rtl_write_byte(rtlpriv, AFE_PLL_CTRL + 1, (tmpu1b | BIT(0))); + mdelay(1); + + /* Release isolation AFE PLL & MD */ + rtl_write_byte(rtlpriv, REG_SYS_ISO_CTRL, 0xA6); + + /* Enable MAC clock */ + tmpu2b = rtl_read_word(rtlpriv, SYS_CLKR); + rtl_write_word(rtlpriv, SYS_CLKR, (tmpu2b | BIT(12) | BIT(11))); + + /* Enable Core digital and enable IOREG R/W */ + tmpu2b = rtl_read_word(rtlpriv, REG_SYS_FUNC_EN); + rtl_write_word(rtlpriv, REG_SYS_FUNC_EN, (tmpu2b | BIT(11))); + + tmpu1b = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN + 1); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN + 1, tmpu1b & ~(BIT(7))); + + /* enable REG_EN */ + rtl_write_word(rtlpriv, REG_SYS_FUNC_EN, (tmpu2b | BIT(11) | BIT(15))); + + /* Switch the control path. */ + tmpu2b = rtl_read_word(rtlpriv, SYS_CLKR); + rtl_write_word(rtlpriv, SYS_CLKR, (tmpu2b & (~BIT(2)))); + + tmpu1b = rtl_read_byte(rtlpriv, (SYS_CLKR + 1)); + tmpu1b = ((tmpu1b | BIT(7)) & (~BIT(6))); + if (!_rtl92se_halset_sysclk(hw, tmpu1b)) + return; /* Set failed, return to prevent hang. */ + + rtl_write_word(rtlpriv, CMDR, 0x07FC); + + /* MH We must enable the section of code to prevent load IMEM fail. */ + /* Load MAC register from WMAc temporarily We simulate macreg. */ + /* txt HW will provide MAC txt later */ + rtl_write_byte(rtlpriv, 0x6, 0x30); + rtl_write_byte(rtlpriv, 0x49, 0xf0); + + rtl_write_byte(rtlpriv, 0x4b, 0x81); + + rtl_write_byte(rtlpriv, 0xb5, 0x21); + + rtl_write_byte(rtlpriv, 0xdc, 0xff); + rtl_write_byte(rtlpriv, 0xdd, 0xff); + rtl_write_byte(rtlpriv, 0xde, 0xff); + rtl_write_byte(rtlpriv, 0xdf, 0xff); + + rtl_write_byte(rtlpriv, 0x11a, 0x00); + rtl_write_byte(rtlpriv, 0x11b, 0x00); + + for (i = 0; i < 32; i++) + rtl_write_byte(rtlpriv, INIMCS_SEL + i, 0x1b); + + rtl_write_byte(rtlpriv, 0x236, 0xff); + + rtl_write_byte(rtlpriv, 0x503, 0x22); + + if (ppsc->support_aspm && !ppsc->support_backdoor) + rtl_write_byte(rtlpriv, 0x560, 0x40); + else + rtl_write_byte(rtlpriv, 0x560, 0x00); + + rtl_write_byte(rtlpriv, DBG_PORT, 0x91); + + /* Set RX Desc Address */ + rtl_write_dword(rtlpriv, RDQDA, rtlpci->rx_ring[RX_MPDU_QUEUE].dma); + rtl_write_dword(rtlpriv, RCDA, rtlpci->rx_ring[RX_CMD_QUEUE].dma); + + /* Set TX Desc Address */ + rtl_write_dword(rtlpriv, TBKDA, rtlpci->tx_ring[BK_QUEUE].dma); + rtl_write_dword(rtlpriv, TBEDA, rtlpci->tx_ring[BE_QUEUE].dma); + rtl_write_dword(rtlpriv, TVIDA, rtlpci->tx_ring[VI_QUEUE].dma); + rtl_write_dword(rtlpriv, TVODA, rtlpci->tx_ring[VO_QUEUE].dma); + rtl_write_dword(rtlpriv, TBDA, rtlpci->tx_ring[BEACON_QUEUE].dma); + rtl_write_dword(rtlpriv, TCDA, rtlpci->tx_ring[TXCMD_QUEUE].dma); + rtl_write_dword(rtlpriv, TMDA, rtlpci->tx_ring[MGNT_QUEUE].dma); + rtl_write_dword(rtlpriv, THPDA, rtlpci->tx_ring[HIGH_QUEUE].dma); + rtl_write_dword(rtlpriv, HDA, rtlpci->tx_ring[HCCA_QUEUE].dma); + + rtl_write_word(rtlpriv, CMDR, 0x37FC); + + /* To make sure that TxDMA can ready to download FW. */ + /* We should reset TxDMA if IMEM RPT was not ready. */ + do { + tmpu1b = rtl_read_byte(rtlpriv, TCR); + if ((tmpu1b & TXDMA_INIT_VALUE) == TXDMA_INIT_VALUE) + break; + + udelay(5); + } while (pollingcnt--); + + if (pollingcnt <= 0) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Polling TXDMA_INIT_VALUE timeout!! Current TCR(%#x)\n", + tmpu1b); + tmpu1b = rtl_read_byte(rtlpriv, CMDR); + rtl_write_byte(rtlpriv, CMDR, tmpu1b & (~TXDMA_EN)); + udelay(2); + /* Reset TxDMA */ + rtl_write_byte(rtlpriv, CMDR, tmpu1b | TXDMA_EN); + } + + /* After MACIO reset,we must refresh LED state. */ + if ((ppsc->rfoff_reason == RF_CHANGE_BY_IPS) || + (ppsc->rfoff_reason == 0)) { + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_led *pLed0 = &(pcipriv->ledctl.sw_led0); + enum rf_pwrstate rfpwr_state_toset; + rfpwr_state_toset = _rtl92se_rf_onoff_detect(hw); + + if (rfpwr_state_toset == ERFON) + rtl92se_sw_led_on(hw, pLed0); + } +} + +static void _rtl92se_macconfig_after_fwdownload(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u8 i; + u16 tmpu2b; + + /* 1. System Configure Register (Offset: 0x0000 - 0x003F) */ + + /* 2. Command Control Register (Offset: 0x0040 - 0x004F) */ + /* Turn on 0x40 Command register */ + rtl_write_word(rtlpriv, CMDR, (BBRSTN | BB_GLB_RSTN | + SCHEDULE_EN | MACRXEN | MACTXEN | DDMA_EN | FW2HW_EN | + RXDMA_EN | TXDMA_EN | HCI_RXDMA_EN | HCI_TXDMA_EN)); + + /* Set TCR TX DMA pre 2 FULL enable bit */ + rtl_write_dword(rtlpriv, TCR, rtl_read_dword(rtlpriv, TCR) | + TXDMAPRE2FULL); + + /* Set RCR */ + rtl_write_dword(rtlpriv, RCR, rtlpci->receive_config); + + /* 3. MACID Setting Register (Offset: 0x0050 - 0x007F) */ + + /* 4. Timing Control Register (Offset: 0x0080 - 0x009F) */ + /* Set CCK/OFDM SIFS */ + /* CCK SIFS shall always be 10us. */ + rtl_write_word(rtlpriv, SIFS_CCK, 0x0a0a); + rtl_write_word(rtlpriv, SIFS_OFDM, 0x1010); + + /* Set AckTimeout */ + rtl_write_byte(rtlpriv, ACK_TIMEOUT, 0x40); + + /* Beacon related */ + rtl_write_word(rtlpriv, BCN_INTERVAL, 100); + rtl_write_word(rtlpriv, ATIMWND, 2); + + /* 5. FIFO Control Register (Offset: 0x00A0 - 0x015F) */ + /* 5.1 Initialize Number of Reserved Pages in Firmware Queue */ + /* Firmware allocate now, associate with FW internal setting.!!! */ + + /* 5.2 Setting TX/RX page size 0/1/2/3/4=64/128/256/512/1024 */ + /* 5.3 Set driver info, we only accept PHY status now. */ + /* 5.4 Set RXDMA arbitration to control RXDMA/MAC/FW R/W for RXFIFO */ + rtl_write_byte(rtlpriv, RXDMA, rtl_read_byte(rtlpriv, RXDMA) | BIT(6)); + + /* 6. Adaptive Control Register (Offset: 0x0160 - 0x01CF) */ + /* Set RRSR to all legacy rate and HT rate + * CCK rate is supported by default. + * CCK rate will be filtered out only when associated + * AP does not support it. + * Only enable ACK rate to OFDM 24M + * Disable RRSR for CCK rate in A-Cut */ + + if (rtlhal->version == VERSION_8192S_ACUT) + rtl_write_byte(rtlpriv, RRSR, 0xf0); + else if (rtlhal->version == VERSION_8192S_BCUT) + rtl_write_byte(rtlpriv, RRSR, 0xff); + rtl_write_byte(rtlpriv, RRSR + 1, 0x01); + rtl_write_byte(rtlpriv, RRSR + 2, 0x00); + + /* A-Cut IC do not support CCK rate. We forbid ARFR to */ + /* fallback to CCK rate */ + for (i = 0; i < 8; i++) { + /*Disable RRSR for CCK rate in A-Cut */ + if (rtlhal->version == VERSION_8192S_ACUT) + rtl_write_dword(rtlpriv, ARFR0 + i * 4, 0x1f0ff0f0); + } + + /* Different rate use different AMPDU size */ + /* MCS32/ MCS15_SG use max AMPDU size 15*2=30K */ + rtl_write_byte(rtlpriv, AGGLEN_LMT_H, 0x0f); + /* MCS0/1/2/3 use max AMPDU size 4*2=8K */ + rtl_write_word(rtlpriv, AGGLEN_LMT_L, 0x7442); + /* MCS4/5 use max AMPDU size 8*2=16K 6/7 use 10*2=20K */ + rtl_write_word(rtlpriv, AGGLEN_LMT_L + 2, 0xddd7); + /* MCS8/9 use max AMPDU size 8*2=16K 10/11 use 10*2=20K */ + rtl_write_word(rtlpriv, AGGLEN_LMT_L + 4, 0xd772); + /* MCS12/13/14/15 use max AMPDU size 15*2=30K */ + rtl_write_word(rtlpriv, AGGLEN_LMT_L + 6, 0xfffd); + + /* Set Data / Response auto rate fallack retry count */ + rtl_write_dword(rtlpriv, DARFRC, 0x04010000); + rtl_write_dword(rtlpriv, DARFRC + 4, 0x09070605); + rtl_write_dword(rtlpriv, RARFRC, 0x04010000); + rtl_write_dword(rtlpriv, RARFRC + 4, 0x09070605); + + /* 7. EDCA Setting Register (Offset: 0x01D0 - 0x01FF) */ + /* Set all rate to support SG */ + rtl_write_word(rtlpriv, SG_RATE, 0xFFFF); + + /* 8. WMAC, BA, and CCX related Register (Offset: 0x0200 - 0x023F) */ + /* Set NAV protection length */ + rtl_write_word(rtlpriv, NAV_PROT_LEN, 0x0080); + /* CF-END Threshold */ + rtl_write_byte(rtlpriv, CFEND_TH, 0xFF); + /* Set AMPDU minimum space */ + rtl_write_byte(rtlpriv, AMPDU_MIN_SPACE, 0x07); + /* Set TXOP stall control for several queue/HI/BCN/MGT/ */ + rtl_write_byte(rtlpriv, TXOP_STALL_CTRL, 0x00); + + /* 9. Security Control Register (Offset: 0x0240 - 0x025F) */ + /* 10. Power Save Control Register (Offset: 0x0260 - 0x02DF) */ + /* 11. General Purpose Register (Offset: 0x02E0 - 0x02FF) */ + /* 12. Host Interrupt Status Register (Offset: 0x0300 - 0x030F) */ + /* 13. Test Mode and Debug Control Register (Offset: 0x0310 - 0x034F) */ + + /* 14. Set driver info, we only accept PHY status now. */ + rtl_write_byte(rtlpriv, RXDRVINFO_SZ, 4); + + /* 15. For EEPROM R/W Workaround */ + /* 16. For EFUSE to share REG_SYS_FUNC_EN with EEPROM!!! */ + tmpu2b = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, tmpu2b | BIT(13)); + tmpu2b = rtl_read_byte(rtlpriv, REG_SYS_ISO_CTRL); + rtl_write_byte(rtlpriv, REG_SYS_ISO_CTRL, tmpu2b & (~BIT(8))); + + /* 17. For EFUSE */ + /* We may R/W EFUSE in EEPROM mode */ + if (rtlefuse->epromtype == EEPROM_BOOT_EFUSE) { + u8 tempval; + + tempval = rtl_read_byte(rtlpriv, REG_SYS_ISO_CTRL + 1); + tempval &= 0xFE; + rtl_write_byte(rtlpriv, REG_SYS_ISO_CTRL + 1, tempval); + + /* Change Program timing */ + rtl_write_byte(rtlpriv, REG_EFUSE_CTRL + 3, 0x72); + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "EFUSE CONFIG OK\n"); + } + + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "OK\n"); + +} + +static void _rtl92se_hw_configure(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + u8 reg_bw_opmode = 0; + u32 reg_rrsr = 0; + u8 regtmp = 0; + + reg_bw_opmode = BW_OPMODE_20MHZ; + reg_rrsr = RATE_ALL_CCK | RATE_ALL_OFDM_AG; + + regtmp = rtl_read_byte(rtlpriv, INIRTSMCS_SEL); + reg_rrsr = ((reg_rrsr & 0x000fffff) << 8) | regtmp; + rtl_write_dword(rtlpriv, INIRTSMCS_SEL, reg_rrsr); + rtl_write_byte(rtlpriv, BW_OPMODE, reg_bw_opmode); + + /* Set Retry Limit here */ + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_RETRY_LIMIT, + (u8 *)(&rtlpci->shortretry_limit)); + + rtl_write_byte(rtlpriv, MLT, 0x8f); + + /* For Min Spacing configuration. */ + switch (rtlphy->rf_type) { + case RF_1T2R: + case RF_1T1R: + rtlhal->minspace_cfg = (MAX_MSS_DENSITY_1T << 3); + break; + case RF_2T2R: + case RF_2T2R_GREEN: + rtlhal->minspace_cfg = (MAX_MSS_DENSITY_2T << 3); + break; + } + rtl_write_byte(rtlpriv, AMPDU_MIN_SPACE, rtlhal->minspace_cfg); +} + +int rtl92se_hw_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 tmp_byte = 0; + + bool rtstatus = true; + u8 tmp_u1b; + int err = false; + u8 i; + int wdcapra_add[] = { + EDCAPARA_BE, EDCAPARA_BK, + EDCAPARA_VI, EDCAPARA_VO}; + u8 secr_value = 0x0; + + rtlpci->being_init_adapter = true; + + rtlpriv->intf_ops->disable_aspm(hw); + + /* 1. MAC Initialize */ + /* Before FW download, we have to set some MAC register */ + _rtl92se_macconfig_before_fwdownload(hw); + + rtlhal->version = (enum version_8192s)((rtl_read_dword(rtlpriv, + PMC_FSM) >> 16) & 0xF); + + rtl8192se_gpiobit3_cfg_inputmode(hw); + + /* 2. download firmware */ + rtstatus = rtl92s_download_fw(hw); + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Failed to download FW. Init HW without FW now... " + "Please copy FW into /lib/firmware/rtlwifi\n"); + return 1; + } + + /* After FW download, we have to reset MAC register */ + _rtl92se_macconfig_after_fwdownload(hw); + + /*Retrieve default FW Cmd IO map. */ + rtlhal->fwcmd_iomap = rtl_read_word(rtlpriv, LBUS_MON_ADDR); + rtlhal->fwcmd_ioparam = rtl_read_dword(rtlpriv, LBUS_ADDR_MASK); + + /* 3. Initialize MAC/PHY Config by MACPHY_reg.txt */ + if (!rtl92s_phy_mac_config(hw)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "MAC Config failed\n"); + return rtstatus; + } + + /* because last function modify RCR, so we update + * rcr var here, or TP will unstable for receive_config + * is wrong, RX RCR_ACRC32 will cause TP unstabel & Rx + * RCR_APP_ICV will cause mac80211 unassoc for cisco 1252 + */ + rtlpci->receive_config = rtl_read_dword(rtlpriv, RCR); + rtlpci->receive_config &= ~(RCR_ACRC32 | RCR_AICV); + rtl_write_dword(rtlpriv, RCR, rtlpci->receive_config); + + /* Make sure BB/RF write OK. We should prevent enter IPS. radio off. */ + /* We must set flag avoid BB/RF config period later!! */ + rtl_write_dword(rtlpriv, CMDR, 0x37FC); + + /* 4. Initialize BB After MAC Config PHY_reg.txt, AGC_Tab.txt */ + if (!rtl92s_phy_bb_config(hw)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_EMERG, "BB Config failed\n"); + return rtstatus; + } + + /* 5. Initiailze RF RAIO_A.txt RF RAIO_B.txt */ + /* Before initalizing RF. We can not use FW to do RF-R/W. */ + + rtlphy->rf_mode = RF_OP_BY_SW_3WIRE; + + /* Before RF-R/W we must execute the IO from Scott's suggestion. */ + rtl_write_byte(rtlpriv, AFE_XTAL_CTRL + 1, 0xDB); + if (rtlhal->version == VERSION_8192S_ACUT) + rtl_write_byte(rtlpriv, SPS1_CTRL + 3, 0x07); + else + rtl_write_byte(rtlpriv, RF_CTRL, 0x07); + + if (!rtl92s_phy_rf_config(hw)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "RF Config failed\n"); + return rtstatus; + } + + /* After read predefined TXT, we must set BB/MAC/RF + * register as our requirement */ + + rtlphy->rfreg_chnlval[0] = rtl92s_phy_query_rf_reg(hw, + (enum radio_path)0, + RF_CHNLBW, + RFREG_OFFSET_MASK); + rtlphy->rfreg_chnlval[1] = rtl92s_phy_query_rf_reg(hw, + (enum radio_path)1, + RF_CHNLBW, + RFREG_OFFSET_MASK); + + /*---- Set CCK and OFDM Block "ON"----*/ + rtl_set_bbreg(hw, RFPGA0_RFMOD, BCCKEN, 0x1); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BOFDMEN, 0x1); + + /*3 Set Hardware(Do nothing now) */ + _rtl92se_hw_configure(hw); + + /* Read EEPROM TX power index and PHY_REG_PG.txt to capture correct */ + /* TX power index for different rate set. */ + /* Get original hw reg values */ + rtl92s_phy_get_hw_reg_originalvalue(hw); + /* Write correct tx power index */ + rtl92s_phy_set_txpower(hw, rtlphy->current_channel); + + /* We must set MAC address after firmware download. */ + for (i = 0; i < 6; i++) + rtl_write_byte(rtlpriv, MACIDR0 + i, rtlefuse->dev_addr[i]); + + /* EEPROM R/W workaround */ + tmp_u1b = rtl_read_byte(rtlpriv, MAC_PINMUX_CFG); + rtl_write_byte(rtlpriv, MAC_PINMUX_CFG, tmp_u1b & (~BIT(3))); + + rtl_write_byte(rtlpriv, 0x4d, 0x0); + + if (hal_get_firmwareversion(rtlpriv) >= 0x49) { + tmp_byte = rtl_read_byte(rtlpriv, FW_RSVD_PG_CRTL) & (~BIT(4)); + tmp_byte = tmp_byte | BIT(5); + rtl_write_byte(rtlpriv, FW_RSVD_PG_CRTL, tmp_byte); + rtl_write_dword(rtlpriv, TXDESC_MSK, 0xFFFFCFFF); + } + + /* We enable high power and RA related mechanism after NIC + * initialized. */ + if (hal_get_firmwareversion(rtlpriv) >= 0x35) { + /* Fw v.53 and later. */ + rtl92s_phy_set_fw_cmd(hw, FW_CMD_RA_INIT); + } else if (hal_get_firmwareversion(rtlpriv) == 0x34) { + /* Fw v.52. */ + rtl_write_dword(rtlpriv, WFM5, FW_RA_INIT); + rtl92s_phy_chk_fwcmd_iodone(hw); + } else { + /* Compatible earlier FW version. */ + rtl_write_dword(rtlpriv, WFM5, FW_RA_RESET); + rtl92s_phy_chk_fwcmd_iodone(hw); + rtl_write_dword(rtlpriv, WFM5, FW_RA_ACTIVE); + rtl92s_phy_chk_fwcmd_iodone(hw); + rtl_write_dword(rtlpriv, WFM5, FW_RA_REFRESH); + rtl92s_phy_chk_fwcmd_iodone(hw); + } + + /* Add to prevent ASPM bug. */ + /* Always enable hst and NIC clock request. */ + rtl92s_phy_switch_ephy_parameter(hw); + + /* Security related + * 1. Clear all H/W keys. + * 2. Enable H/W encryption/decryption. */ + rtl_cam_reset_all_entry(hw); + secr_value |= SCR_TXENCENABLE; + secr_value |= SCR_RXENCENABLE; + secr_value |= SCR_NOSKMC; + rtl_write_byte(rtlpriv, REG_SECR, secr_value); + + for (i = 0; i < 4; i++) + rtl_write_dword(rtlpriv, wdcapra_add[i], 0x5e4322); + + if (rtlphy->rf_type == RF_1T2R) { + bool mrc2set = true; + /* Turn on B-Path */ + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_MRC, (u8 *)&mrc2set); + } + + rtlpriv->cfg->ops->led_control(hw, LED_CTL_POWER_ON); + rtl92s_dm_init(hw); + rtlpci->being_init_adapter = false; + + return err; +} + +void rtl92se_set_mac_addr(struct rtl_io *io, const u8 *addr) +{ + /* This is a stub. */ +} + +void rtl92se_set_check_bssid(struct ieee80211_hw *hw, bool check_bssid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u32 reg_rcr = rtlpci->receive_config; + + if (rtlpriv->psc.rfpwr_state != ERFON) + return; + + if (check_bssid) { + reg_rcr |= (RCR_CBSSID); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_RCR, (u8 *)(®_rcr)); + } else if (!check_bssid) { + reg_rcr &= (~RCR_CBSSID); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_RCR, (u8 *)(®_rcr)); + } + +} + +static int _rtl92se_set_media_status(struct ieee80211_hw *hw, + enum nl80211_iftype type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 bt_msr = rtl_read_byte(rtlpriv, MSR); + u32 temp; + bt_msr &= ~MSR_LINK_MASK; + + switch (type) { + case NL80211_IFTYPE_UNSPECIFIED: + bt_msr |= (MSR_LINK_NONE << MSR_LINK_SHIFT); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to NO LINK!\n"); + break; + case NL80211_IFTYPE_ADHOC: + bt_msr |= (MSR_LINK_ADHOC << MSR_LINK_SHIFT); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to Ad Hoc!\n"); + break; + case NL80211_IFTYPE_STATION: + bt_msr |= (MSR_LINK_MANAGED << MSR_LINK_SHIFT); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to STA!\n"); + break; + case NL80211_IFTYPE_AP: + bt_msr |= (MSR_LINK_MASTER << MSR_LINK_SHIFT); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to AP!\n"); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Network type %d not supported!\n", type); + return 1; + break; + + } + + rtl_write_byte(rtlpriv, (MSR), bt_msr); + + temp = rtl_read_dword(rtlpriv, TCR); + rtl_write_dword(rtlpriv, TCR, temp & (~BIT(8))); + rtl_write_dword(rtlpriv, TCR, temp | BIT(8)); + + + return 0; +} + +/* HW_VAR_MEDIA_STATUS & HW_VAR_CECHK_BSSID */ +int rtl92se_set_network_type(struct ieee80211_hw *hw, enum nl80211_iftype type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (_rtl92se_set_media_status(hw, type)) + return -EOPNOTSUPP; + + if (rtlpriv->mac80211.link_state == MAC80211_LINKED) { + if (type != NL80211_IFTYPE_AP) + rtl92se_set_check_bssid(hw, true); + } else { + rtl92se_set_check_bssid(hw, false); + } + + return 0; +} + +/* don't set REG_EDCA_BE_PARAM here because mac80211 will send pkt when scan */ +void rtl92se_set_qos(struct ieee80211_hw *hw, int aci) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + rtl92s_dm_init_edca_turbo(hw); + + switch (aci) { + case AC1_BK: + rtl_write_dword(rtlpriv, EDCAPARA_BK, 0xa44f); + break; + case AC0_BE: + /* rtl_write_dword(rtlpriv, EDCAPARA_BE, u4b_ac_param); */ + break; + case AC2_VI: + rtl_write_dword(rtlpriv, EDCAPARA_VI, 0x5e4322); + break; + case AC3_VO: + rtl_write_dword(rtlpriv, EDCAPARA_VO, 0x2f3222); + break; + default: + RT_ASSERT(false, "invalid aci: %d !\n", aci); + break; + } +} + +void rtl92se_enable_interrupt(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + rtl_write_dword(rtlpriv, INTA_MASK, rtlpci->irq_mask[0]); + /* Support Bit 32-37(Assign as Bit 0-5) interrupt setting now */ + rtl_write_dword(rtlpriv, INTA_MASK + 4, rtlpci->irq_mask[1] & 0x3F); +} + +void rtl92se_disable_interrupt(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv; + struct rtl_pci *rtlpci; + + rtlpriv = rtl_priv(hw); + /* if firmware not available, no interrupts */ + if (!rtlpriv || !rtlpriv->max_fw_size) + return; + rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + rtl_write_dword(rtlpriv, INTA_MASK, 0); + rtl_write_dword(rtlpriv, INTA_MASK + 4, 0); + + synchronize_irq(rtlpci->pdev->irq); +} + +static u8 _rtl92s_set_sysclk(struct ieee80211_hw *hw, u8 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 waitcnt = 100; + bool result = false; + u8 tmp; + + rtl_write_byte(rtlpriv, SYS_CLKR + 1, data); + + /* Wait the MAC synchronized. */ + udelay(400); + + /* Check if it is set ready. */ + tmp = rtl_read_byte(rtlpriv, SYS_CLKR + 1); + result = ((tmp & BIT(7)) == (data & BIT(7))); + + if ((data & (BIT(6) | BIT(7))) == false) { + waitcnt = 100; + tmp = 0; + + while (1) { + waitcnt--; + tmp = rtl_read_byte(rtlpriv, SYS_CLKR + 1); + + if ((tmp & BIT(6))) + break; + + pr_err("wait for BIT(6) return value %x\n", tmp); + + if (waitcnt == 0) + break; + udelay(10); + } + + if (waitcnt == 0) + result = false; + else + result = true; + } + + return result; +} + +static void _rtl92s_phy_set_rfhalt(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + u8 u1btmp; + + if (rtlhal->driver_going2unload) + rtl_write_byte(rtlpriv, 0x560, 0x0); + + /* Power save for BB/RF */ + u1btmp = rtl_read_byte(rtlpriv, LDOV12D_CTRL); + u1btmp |= BIT(0); + rtl_write_byte(rtlpriv, LDOV12D_CTRL, u1btmp); + rtl_write_byte(rtlpriv, SPS1_CTRL, 0x0); + rtl_write_byte(rtlpriv, TXPAUSE, 0xFF); + rtl_write_word(rtlpriv, CMDR, 0x57FC); + udelay(100); + rtl_write_word(rtlpriv, CMDR, 0x77FC); + rtl_write_byte(rtlpriv, PHY_CCA, 0x0); + udelay(10); + rtl_write_word(rtlpriv, CMDR, 0x37FC); + udelay(10); + rtl_write_word(rtlpriv, CMDR, 0x77FC); + udelay(10); + rtl_write_word(rtlpriv, CMDR, 0x57FC); + rtl_write_word(rtlpriv, CMDR, 0x0000); + + if (rtlhal->driver_going2unload) { + u1btmp = rtl_read_byte(rtlpriv, (REG_SYS_FUNC_EN + 1)); + u1btmp &= ~(BIT(0)); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN + 1, u1btmp); + } + + u1btmp = rtl_read_byte(rtlpriv, (SYS_CLKR + 1)); + + /* Add description. After switch control path. register + * after page1 will be invisible. We can not do any IO + * for register>0x40. After resume&MACIO reset, we need + * to remember previous reg content. */ + if (u1btmp & BIT(7)) { + u1btmp &= ~(BIT(6) | BIT(7)); + if (!_rtl92s_set_sysclk(hw, u1btmp)) { + pr_err("Switch ctrl path fail\n"); + return; + } + } + + /* Power save for MAC */ + if (ppsc->rfoff_reason == RF_CHANGE_BY_IPS && + !rtlhal->driver_going2unload) { + /* enable LED function */ + rtl_write_byte(rtlpriv, 0x03, 0xF9); + /* SW/HW radio off or halt adapter!! For example S3/S4 */ + } else { + /* LED function disable. Power range is about 8mA now. */ + /* if write 0xF1 disconnet_pci power + * ifconfig wlan0 down power are both high 35:70 */ + /* if write oxF9 disconnet_pci power + * ifconfig wlan0 down power are both low 12:45*/ + rtl_write_byte(rtlpriv, 0x03, 0xF9); + } + + rtl_write_byte(rtlpriv, SYS_CLKR + 1, 0x70); + rtl_write_byte(rtlpriv, AFE_PLL_CTRL + 1, 0x68); + rtl_write_byte(rtlpriv, AFE_PLL_CTRL, 0x00); + rtl_write_byte(rtlpriv, LDOA15_CTRL, 0x34); + rtl_write_byte(rtlpriv, AFE_XTAL_CTRL, 0x0E); + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + +} + +static void _rtl92se_gen_refreshledstate(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_led *pLed0 = &(pcipriv->ledctl.sw_led0); + + if (rtlpci->up_first_time == 1) + return; + + if (rtlpriv->psc.rfoff_reason == RF_CHANGE_BY_IPS) + rtl92se_sw_led_on(hw, pLed0); + else + rtl92se_sw_led_off(hw, pLed0); +} + + +static void _rtl92se_power_domain_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u16 tmpu2b; + u8 tmpu1b; + + rtlpriv->psc.pwrdomain_protect = true; + + tmpu1b = rtl_read_byte(rtlpriv, (SYS_CLKR + 1)); + if (tmpu1b & BIT(7)) { + tmpu1b &= ~(BIT(6) | BIT(7)); + if (!_rtl92s_set_sysclk(hw, tmpu1b)) { + rtlpriv->psc.pwrdomain_protect = false; + return; + } + } + + rtl_write_byte(rtlpriv, AFE_PLL_CTRL, 0x0); + rtl_write_byte(rtlpriv, LDOA15_CTRL, 0x34); + + /* Reset MAC-IO and CPU and Core Digital BIT10/11/15 */ + tmpu1b = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN + 1); + + /* If IPS we need to turn LED on. So we not + * not disable BIT 3/7 of reg3. */ + if (rtlpriv->psc.rfoff_reason & (RF_CHANGE_BY_IPS | RF_CHANGE_BY_HW)) + tmpu1b &= 0xFB; + else + tmpu1b &= 0x73; + + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN + 1, tmpu1b); + /* wait for BIT 10/11/15 to pull high automatically!! */ + mdelay(1); + + rtl_write_byte(rtlpriv, CMDR, 0); + rtl_write_byte(rtlpriv, TCR, 0); + + /* Data sheet not define 0x562!!! Copy from WMAC!!!!! */ + tmpu1b = rtl_read_byte(rtlpriv, 0x562); + tmpu1b |= 0x08; + rtl_write_byte(rtlpriv, 0x562, tmpu1b); + tmpu1b &= ~(BIT(3)); + rtl_write_byte(rtlpriv, 0x562, tmpu1b); + + /* Enable AFE clock source */ + tmpu1b = rtl_read_byte(rtlpriv, AFE_XTAL_CTRL); + rtl_write_byte(rtlpriv, AFE_XTAL_CTRL, (tmpu1b | 0x01)); + /* Delay 1.5ms */ + udelay(1500); + tmpu1b = rtl_read_byte(rtlpriv, AFE_XTAL_CTRL + 1); + rtl_write_byte(rtlpriv, AFE_XTAL_CTRL + 1, (tmpu1b & 0xfb)); + + /* Enable AFE Macro Block's Bandgap */ + tmpu1b = rtl_read_byte(rtlpriv, AFE_MISC); + rtl_write_byte(rtlpriv, AFE_MISC, (tmpu1b | BIT(0))); + mdelay(1); + + /* Enable AFE Mbias */ + tmpu1b = rtl_read_byte(rtlpriv, AFE_MISC); + rtl_write_byte(rtlpriv, AFE_MISC, (tmpu1b | 0x02)); + mdelay(1); + + /* Enable LDOA15 block */ + tmpu1b = rtl_read_byte(rtlpriv, LDOA15_CTRL); + rtl_write_byte(rtlpriv, LDOA15_CTRL, (tmpu1b | BIT(0))); + + /* Set Digital Vdd to Retention isolation Path. */ + tmpu2b = rtl_read_word(rtlpriv, REG_SYS_ISO_CTRL); + rtl_write_word(rtlpriv, REG_SYS_ISO_CTRL, (tmpu2b | BIT(11))); + + + /* For warm reboot NIC disappera bug. */ + tmpu2b = rtl_read_word(rtlpriv, REG_SYS_FUNC_EN); + rtl_write_word(rtlpriv, REG_SYS_FUNC_EN, (tmpu2b | BIT(13))); + + rtl_write_byte(rtlpriv, REG_SYS_ISO_CTRL + 1, 0x68); + + /* Enable AFE PLL Macro Block */ + tmpu1b = rtl_read_byte(rtlpriv, AFE_PLL_CTRL); + rtl_write_byte(rtlpriv, AFE_PLL_CTRL, (tmpu1b | BIT(0) | BIT(4))); + /* Enable MAC 80MHZ clock */ + tmpu1b = rtl_read_byte(rtlpriv, AFE_PLL_CTRL + 1); + rtl_write_byte(rtlpriv, AFE_PLL_CTRL + 1, (tmpu1b | BIT(0))); + mdelay(1); + + /* Release isolation AFE PLL & MD */ + rtl_write_byte(rtlpriv, REG_SYS_ISO_CTRL, 0xA6); + + /* Enable MAC clock */ + tmpu2b = rtl_read_word(rtlpriv, SYS_CLKR); + rtl_write_word(rtlpriv, SYS_CLKR, (tmpu2b | BIT(12) | BIT(11))); + + /* Enable Core digital and enable IOREG R/W */ + tmpu2b = rtl_read_word(rtlpriv, REG_SYS_FUNC_EN); + rtl_write_word(rtlpriv, REG_SYS_FUNC_EN, (tmpu2b | BIT(11))); + /* enable REG_EN */ + rtl_write_word(rtlpriv, REG_SYS_FUNC_EN, (tmpu2b | BIT(11) | BIT(15))); + + /* Switch the control path. */ + tmpu2b = rtl_read_word(rtlpriv, SYS_CLKR); + rtl_write_word(rtlpriv, SYS_CLKR, (tmpu2b & (~BIT(2)))); + + tmpu1b = rtl_read_byte(rtlpriv, (SYS_CLKR + 1)); + tmpu1b = ((tmpu1b | BIT(7)) & (~BIT(6))); + if (!_rtl92s_set_sysclk(hw, tmpu1b)) { + rtlpriv->psc.pwrdomain_protect = false; + return; + } + + rtl_write_word(rtlpriv, CMDR, 0x37FC); + + /* After MACIO reset,we must refresh LED state. */ + _rtl92se_gen_refreshledstate(hw); + + rtlpriv->psc.pwrdomain_protect = false; +} + +void rtl92se_card_disable(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + enum nl80211_iftype opmode; + u8 wait = 30; + + rtlpriv->intf_ops->enable_aspm(hw); + + if (rtlpci->driver_is_goingto_unload || + ppsc->rfoff_reason > RF_CHANGE_BY_PS) + rtlpriv->cfg->ops->led_control(hw, LED_CTL_POWER_OFF); + + /* we should chnge GPIO to input mode + * this will drop away current about 25mA*/ + rtl8192se_gpiobit3_cfg_inputmode(hw); + + /* this is very important for ips power save */ + while (wait-- >= 10 && rtlpriv->psc.pwrdomain_protect) { + if (rtlpriv->psc.pwrdomain_protect) + mdelay(20); + else + break; + } + + mac->link_state = MAC80211_NOLINK; + opmode = NL80211_IFTYPE_UNSPECIFIED; + _rtl92se_set_media_status(hw, opmode); + + _rtl92s_phy_set_rfhalt(hw); + udelay(100); +} + +void rtl92se_interrupt_recognized(struct ieee80211_hw *hw, u32 *p_inta, + u32 *p_intb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + *p_inta = rtl_read_dword(rtlpriv, ISR) & rtlpci->irq_mask[0]; + rtl_write_dword(rtlpriv, ISR, *p_inta); + + *p_intb = rtl_read_dword(rtlpriv, ISR + 4) & rtlpci->irq_mask[1]; + rtl_write_dword(rtlpriv, ISR + 4, *p_intb); +} + +void rtl92se_set_beacon_related_registers(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 bcntime_cfg = 0; + u16 bcn_cw = 6, bcn_ifs = 0xf; + u16 atim_window = 2; + + /* ATIM Window (in unit of TU). */ + rtl_write_word(rtlpriv, ATIMWND, atim_window); + + /* Beacon interval (in unit of TU). */ + rtl_write_word(rtlpriv, BCN_INTERVAL, mac->beacon_interval); + + /* DrvErlyInt (in unit of TU). (Time to send + * interrupt to notify driver to change + * beacon content) */ + rtl_write_word(rtlpriv, BCN_DRV_EARLY_INT, 10 << 4); + + /* BcnDMATIM(in unit of us). Indicates the + * time before TBTT to perform beacon queue DMA */ + rtl_write_word(rtlpriv, BCN_DMATIME, 256); + + /* Force beacon frame transmission even + * after receiving beacon frame from + * other ad hoc STA */ + rtl_write_byte(rtlpriv, BCN_ERR_THRESH, 100); + + /* Beacon Time Configuration */ + if (mac->opmode == NL80211_IFTYPE_ADHOC) + bcntime_cfg |= (bcn_cw << BCN_TCFG_CW_SHIFT); + + /* TODO: bcn_ifs may required to be changed on ASIC */ + bcntime_cfg |= bcn_ifs << BCN_TCFG_IFS; + + /*for beacon changed */ + rtl92s_phy_set_beacon_hwreg(hw, mac->beacon_interval); +} + +void rtl92se_set_beacon_interval(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 bcn_interval = mac->beacon_interval; + + /* Beacon interval (in unit of TU). */ + rtl_write_word(rtlpriv, BCN_INTERVAL, bcn_interval); + /* 2008.10.24 added by tynli for beacon changed. */ + rtl92s_phy_set_beacon_hwreg(hw, bcn_interval); +} + +void rtl92se_update_interrupt_mask(struct ieee80211_hw *hw, + u32 add_msr, u32 rm_msr) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + RT_TRACE(rtlpriv, COMP_INTR, DBG_LOUD, "add_msr:%x, rm_msr:%x\n", + add_msr, rm_msr); + + if (add_msr) + rtlpci->irq_mask[0] |= add_msr; + + if (rm_msr) + rtlpci->irq_mask[0] &= (~rm_msr); + + rtl92se_disable_interrupt(hw); + rtl92se_enable_interrupt(hw); +} + +static void _rtl8192se_get_IC_Inferiority(struct ieee80211_hw *hw) +{ + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 efuse_id; + + rtlhal->ic_class = IC_INFERIORITY_A; + + /* Only retrieving while using EFUSE. */ + if ((rtlefuse->epromtype == EEPROM_BOOT_EFUSE) && + !rtlefuse->autoload_failflag) { + efuse_id = efuse_read_1byte(hw, EFUSE_IC_ID_OFFSET); + + if (efuse_id == 0xfe) + rtlhal->ic_class = IC_INFERIORITY_B; + } +} + +static void _rtl92se_read_adapter_info(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u16 i, usvalue; + u16 eeprom_id; + u8 tempval; + u8 hwinfo[HWSET_MAX_SIZE_92S]; + u8 rf_path, index; + + if (rtlefuse->epromtype == EEPROM_93C46) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "RTL819X Not boot from eeprom, check it !!\n"); + } else if (rtlefuse->epromtype == EEPROM_BOOT_EFUSE) { + rtl_efuse_shadow_map_update(hw); + + memcpy((void *)hwinfo, (void *) + &rtlefuse->efuse_map[EFUSE_INIT_MAP][0], + HWSET_MAX_SIZE_92S); + } + + RT_PRINT_DATA(rtlpriv, COMP_INIT, DBG_DMESG, "MAP", + hwinfo, HWSET_MAX_SIZE_92S); + + eeprom_id = *((u16 *)&hwinfo[0]); + if (eeprom_id != RTL8190_EEPROM_ID) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "EEPROM ID(%#x) is invalid!!\n", eeprom_id); + rtlefuse->autoload_failflag = true; + } else { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Autoload OK\n"); + rtlefuse->autoload_failflag = false; + } + + if (rtlefuse->autoload_failflag) + return; + + _rtl8192se_get_IC_Inferiority(hw); + + /* Read IC Version && Channel Plan */ + /* VID, DID SE 0xA-D */ + rtlefuse->eeprom_vid = *(u16 *)&hwinfo[EEPROM_VID]; + rtlefuse->eeprom_did = *(u16 *)&hwinfo[EEPROM_DID]; + rtlefuse->eeprom_svid = *(u16 *)&hwinfo[EEPROM_SVID]; + rtlefuse->eeprom_smid = *(u16 *)&hwinfo[EEPROM_SMID]; + rtlefuse->eeprom_version = *(u16 *)&hwinfo[EEPROM_VERSION]; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROMId = 0x%4x\n", eeprom_id); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM VID = 0x%4x\n", rtlefuse->eeprom_vid); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM DID = 0x%4x\n", rtlefuse->eeprom_did); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM SVID = 0x%4x\n", rtlefuse->eeprom_svid); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM SMID = 0x%4x\n", rtlefuse->eeprom_smid); + + for (i = 0; i < 6; i += 2) { + usvalue = *(u16 *)&hwinfo[EEPROM_MAC_ADDR + i]; + *((u16 *) (&rtlefuse->dev_addr[i])) = usvalue; + } + + for (i = 0; i < 6; i++) + rtl_write_byte(rtlpriv, MACIDR0 + i, rtlefuse->dev_addr[i]); + + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "%pM\n", rtlefuse->dev_addr); + + /* Get Tx Power Level by Channel */ + /* Read Tx power of Channel 1 ~ 14 from EEPROM. */ + /* 92S suupport RF A & B */ + for (rf_path = 0; rf_path < 2; rf_path++) { + for (i = 0; i < 3; i++) { + /* Read CCK RF A & B Tx power */ + rtlefuse->eeprom_chnlarea_txpwr_cck[rf_path][i] = + hwinfo[EEPROM_TXPOWERBASE + rf_path * 3 + i]; + + /* Read OFDM RF A & B Tx power for 1T */ + rtlefuse->eeprom_chnlarea_txpwr_ht40_1s[rf_path][i] = + hwinfo[EEPROM_TXPOWERBASE + 6 + rf_path * 3 + i]; + + /* Read OFDM RF A & B Tx power for 2T */ + rtlefuse->eprom_chnl_txpwr_ht40_2sdf[rf_path][i] + = hwinfo[EEPROM_TXPOWERBASE + 12 + + rf_path * 3 + i]; + } + } + + for (rf_path = 0; rf_path < 2; rf_path++) + for (i = 0; i < 3; i++) + RTPRINT(rtlpriv, FINIT, INIT_EEPROM, + "RF(%d) EEPROM CCK Area(%d) = 0x%x\n", + rf_path, i, + rtlefuse->eeprom_chnlarea_txpwr_cck + [rf_path][i]); + for (rf_path = 0; rf_path < 2; rf_path++) + for (i = 0; i < 3; i++) + RTPRINT(rtlpriv, FINIT, INIT_EEPROM, + "RF(%d) EEPROM HT40 1S Area(%d) = 0x%x\n", + rf_path, i, + rtlefuse->eeprom_chnlarea_txpwr_ht40_1s + [rf_path][i]); + for (rf_path = 0; rf_path < 2; rf_path++) + for (i = 0; i < 3; i++) + RTPRINT(rtlpriv, FINIT, INIT_EEPROM, + "RF(%d) EEPROM HT40 2S Diff Area(%d) = 0x%x\n", + rf_path, i, + rtlefuse->eprom_chnl_txpwr_ht40_2sdf + [rf_path][i]); + + for (rf_path = 0; rf_path < 2; rf_path++) { + + /* Assign dedicated channel tx power */ + for (i = 0; i < 14; i++) { + /* channel 1~3 use the same Tx Power Level. */ + if (i < 3) + index = 0; + /* Channel 4-8 */ + else if (i < 8) + index = 1; + /* Channel 9-14 */ + else + index = 2; + + /* Record A & B CCK /OFDM - 1T/2T Channel area + * tx power */ + rtlefuse->txpwrlevel_cck[rf_path][i] = + rtlefuse->eeprom_chnlarea_txpwr_cck + [rf_path][index]; + rtlefuse->txpwrlevel_ht40_1s[rf_path][i] = + rtlefuse->eeprom_chnlarea_txpwr_ht40_1s + [rf_path][index]; + rtlefuse->txpwrlevel_ht40_2s[rf_path][i] = + rtlefuse->eprom_chnl_txpwr_ht40_2sdf + [rf_path][index]; + } + + for (i = 0; i < 14; i++) { + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF(%d)-Ch(%d) [CCK / HT40_1S / HT40_2S] = [0x%x / 0x%x / 0x%x]\n", + rf_path, i, + rtlefuse->txpwrlevel_cck[rf_path][i], + rtlefuse->txpwrlevel_ht40_1s[rf_path][i], + rtlefuse->txpwrlevel_ht40_2s[rf_path][i]); + } + } + + for (rf_path = 0; rf_path < 2; rf_path++) { + for (i = 0; i < 3; i++) { + /* Read Power diff limit. */ + rtlefuse->eeprom_pwrgroup[rf_path][i] = + hwinfo[EEPROM_TXPWRGROUP + rf_path * 3 + i]; + } + } + + for (rf_path = 0; rf_path < 2; rf_path++) { + /* Fill Pwr group */ + for (i = 0; i < 14; i++) { + /* Chanel 1-3 */ + if (i < 3) + index = 0; + /* Channel 4-8 */ + else if (i < 8) + index = 1; + /* Channel 9-13 */ + else + index = 2; + + rtlefuse->pwrgroup_ht20[rf_path][i] = + (rtlefuse->eeprom_pwrgroup[rf_path][index] & + 0xf); + rtlefuse->pwrgroup_ht40[rf_path][i] = + ((rtlefuse->eeprom_pwrgroup[rf_path][index] & + 0xf0) >> 4); + + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-%d pwrgroup_ht20[%d] = 0x%x\n", + rf_path, i, + rtlefuse->pwrgroup_ht20[rf_path][i]); + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-%d pwrgroup_ht40[%d] = 0x%x\n", + rf_path, i, + rtlefuse->pwrgroup_ht40[rf_path][i]); + } + } + + for (i = 0; i < 14; i++) { + /* Read tx power difference between HT OFDM 20/40 MHZ */ + /* channel 1-3 */ + if (i < 3) + index = 0; + /* Channel 4-8 */ + else if (i < 8) + index = 1; + /* Channel 9-14 */ + else + index = 2; + + tempval = hwinfo[EEPROM_TX_PWR_HT20_DIFF + index] & 0xff; + rtlefuse->txpwr_ht20diff[RF90_PATH_A][i] = (tempval & 0xF); + rtlefuse->txpwr_ht20diff[RF90_PATH_B][i] = + ((tempval >> 4) & 0xF); + + /* Read OFDM<->HT tx power diff */ + /* Channel 1-3 */ + if (i < 3) + index = 0; + /* Channel 4-8 */ + else if (i < 8) + index = 0x11; + /* Channel 9-14 */ + else + index = 1; + + tempval = hwinfo[EEPROM_TX_PWR_OFDM_DIFF + index] & 0xff; + rtlefuse->txpwr_legacyhtdiff[RF90_PATH_A][i] = + (tempval & 0xF); + rtlefuse->txpwr_legacyhtdiff[RF90_PATH_B][i] = + ((tempval >> 4) & 0xF); + + tempval = hwinfo[TX_PWR_SAFETY_CHK]; + rtlefuse->txpwr_safetyflag = (tempval & 0x01); + } + + rtlefuse->eeprom_regulatory = 0; + if (rtlefuse->eeprom_version >= 2) { + /* BIT(0)~2 */ + if (rtlefuse->eeprom_version >= 4) + rtlefuse->eeprom_regulatory = + (hwinfo[EEPROM_REGULATORY] & 0x7); + else /* BIT(0) */ + rtlefuse->eeprom_regulatory = + (hwinfo[EEPROM_REGULATORY] & 0x1); + } + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "eeprom_regulatory = 0x%x\n", rtlefuse->eeprom_regulatory); + + for (i = 0; i < 14; i++) + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-A Ht20 to HT40 Diff[%d] = 0x%x\n", + i, rtlefuse->txpwr_ht20diff[RF90_PATH_A][i]); + for (i = 0; i < 14; i++) + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-A Legacy to Ht40 Diff[%d] = 0x%x\n", + i, rtlefuse->txpwr_legacyhtdiff[RF90_PATH_A][i]); + for (i = 0; i < 14; i++) + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-B Ht20 to HT40 Diff[%d] = 0x%x\n", + i, rtlefuse->txpwr_ht20diff[RF90_PATH_B][i]); + for (i = 0; i < 14; i++) + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-B Legacy to HT40 Diff[%d] = 0x%x\n", + i, rtlefuse->txpwr_legacyhtdiff[RF90_PATH_B][i]); + + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "TxPwrSafetyFlag = %d\n", rtlefuse->txpwr_safetyflag); + + /* Read RF-indication and Tx Power gain + * index diff of legacy to HT OFDM rate. */ + tempval = hwinfo[EEPROM_RFIND_POWERDIFF] & 0xff; + rtlefuse->eeprom_txpowerdiff = tempval; + rtlefuse->legacy_httxpowerdiff = + rtlefuse->txpwr_legacyhtdiff[RF90_PATH_A][0]; + + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "TxPowerDiff = %#x\n", rtlefuse->eeprom_txpowerdiff); + + /* Get TSSI value for each path. */ + usvalue = *(u16 *)&hwinfo[EEPROM_TSSI_A]; + rtlefuse->eeprom_tssi[RF90_PATH_A] = (u8)((usvalue & 0xff00) >> 8); + usvalue = hwinfo[EEPROM_TSSI_B]; + rtlefuse->eeprom_tssi[RF90_PATH_B] = (u8)(usvalue & 0xff); + + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, "TSSI_A = 0x%x, TSSI_B = 0x%x\n", + rtlefuse->eeprom_tssi[RF90_PATH_A], + rtlefuse->eeprom_tssi[RF90_PATH_B]); + + /* Read antenna tx power offset of B/C/D to A from EEPROM */ + /* and read ThermalMeter from EEPROM */ + tempval = hwinfo[EEPROM_THERMALMETER]; + rtlefuse->eeprom_thermalmeter = tempval; + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "thermalmeter = 0x%x\n", rtlefuse->eeprom_thermalmeter); + + /* ThermalMeter, BIT(0)~3 for RFIC1, BIT(4)~7 for RFIC2 */ + rtlefuse->thermalmeter[0] = (rtlefuse->eeprom_thermalmeter & 0x1f); + rtlefuse->tssi_13dbm = rtlefuse->eeprom_thermalmeter * 100; + + /* Read CrystalCap from EEPROM */ + tempval = hwinfo[EEPROM_CRYSTALCAP] >> 4; + rtlefuse->eeprom_crystalcap = tempval; + /* CrystalCap, BIT(12)~15 */ + rtlefuse->crystalcap = rtlefuse->eeprom_crystalcap; + + /* Read IC Version && Channel Plan */ + /* Version ID, Channel plan */ + rtlefuse->eeprom_channelplan = hwinfo[EEPROM_CHANNELPLAN]; + rtlefuse->txpwr_fromeprom = true; + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "EEPROM ChannelPlan = 0x%4x\n", rtlefuse->eeprom_channelplan); + + /* Read Customer ID or Board Type!!! */ + tempval = hwinfo[EEPROM_BOARDTYPE]; + /* Change RF type definition */ + if (tempval == 0) + rtlphy->rf_type = RF_2T2R; + else if (tempval == 1) + rtlphy->rf_type = RF_1T2R; + else if (tempval == 2) + rtlphy->rf_type = RF_1T2R; + else if (tempval == 3) + rtlphy->rf_type = RF_1T1R; + + /* 1T2R but 1SS (1x1 receive combining) */ + rtlefuse->b1x1_recvcombine = false; + if (rtlphy->rf_type == RF_1T2R) { + tempval = rtl_read_byte(rtlpriv, 0x07); + if (!(tempval & BIT(0))) { + rtlefuse->b1x1_recvcombine = true; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "RF_TYPE=1T2R but only 1SS\n"); + } + } + rtlefuse->b1ss_support = rtlefuse->b1x1_recvcombine; + rtlefuse->eeprom_oemid = *&hwinfo[EEPROM_CUSTOMID]; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "EEPROM Customer ID: 0x%2x", + rtlefuse->eeprom_oemid); + + /* set channel paln to world wide 13 */ + rtlefuse->channel_plan = COUNTRY_CODE_WORLD_WIDE_13; +} + +void rtl92se_read_eeprom_info(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 tmp_u1b = 0; + + tmp_u1b = rtl_read_byte(rtlpriv, EPROM_CMD); + + if (tmp_u1b & BIT(4)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "Boot from EEPROM\n"); + rtlefuse->epromtype = EEPROM_93C46; + } else { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "Boot from EFUSE\n"); + rtlefuse->epromtype = EEPROM_BOOT_EFUSE; + } + + if (tmp_u1b & BIT(5)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Autoload OK\n"); + rtlefuse->autoload_failflag = false; + _rtl92se_read_adapter_info(hw); + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Autoload ERR!!\n"); + rtlefuse->autoload_failflag = true; + } +} + +static void rtl92se_update_hal_rate_table(struct ieee80211_hw *hw, + struct ieee80211_sta *sta) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u32 ratr_value; + u8 ratr_index = 0; + u8 nmode = mac->ht_enable; + u8 mimo_ps = IEEE80211_SMPS_OFF; + u16 shortgi_rate = 0; + u32 tmp_ratr_value = 0; + u8 curtxbw_40mhz = mac->bw_40; + u8 curshortgi_40mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_40) ? + 1 : 0; + u8 curshortgi_20mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_20) ? + 1 : 0; + enum wireless_mode wirelessmode = mac->mode; + + if (rtlhal->current_bandtype == BAND_ON_5G) + ratr_value = sta->supp_rates[1] << 4; + else + ratr_value = sta->supp_rates[0]; + if (mac->opmode == NL80211_IFTYPE_ADHOC) + ratr_value = 0xfff; + ratr_value |= (sta->ht_cap.mcs.rx_mask[1] << 20 | + sta->ht_cap.mcs.rx_mask[0] << 12); + switch (wirelessmode) { + case WIRELESS_MODE_B: + ratr_value &= 0x0000000D; + break; + case WIRELESS_MODE_G: + ratr_value &= 0x00000FF5; + break; + case WIRELESS_MODE_N_24G: + case WIRELESS_MODE_N_5G: + nmode = 1; + if (mimo_ps == IEEE80211_SMPS_STATIC) { + ratr_value &= 0x0007F005; + } else { + u32 ratr_mask; + + if (get_rf_type(rtlphy) == RF_1T2R || + get_rf_type(rtlphy) == RF_1T1R) { + if (curtxbw_40mhz) + ratr_mask = 0x000ff015; + else + ratr_mask = 0x000ff005; + } else { + if (curtxbw_40mhz) + ratr_mask = 0x0f0ff015; + else + ratr_mask = 0x0f0ff005; + } + + ratr_value &= ratr_mask; + } + break; + default: + if (rtlphy->rf_type == RF_1T2R) + ratr_value &= 0x000ff0ff; + else + ratr_value &= 0x0f0ff0ff; + + break; + } + + if (rtlpriv->rtlhal.version >= VERSION_8192S_BCUT) + ratr_value &= 0x0FFFFFFF; + else if (rtlpriv->rtlhal.version == VERSION_8192S_ACUT) + ratr_value &= 0x0FFFFFF0; + + if (nmode && ((curtxbw_40mhz && + curshortgi_40mhz) || (!curtxbw_40mhz && + curshortgi_20mhz))) { + + ratr_value |= 0x10000000; + tmp_ratr_value = (ratr_value >> 12); + + for (shortgi_rate = 15; shortgi_rate > 0; shortgi_rate--) { + if ((1 << shortgi_rate) & tmp_ratr_value) + break; + } + + shortgi_rate = (shortgi_rate << 12) | (shortgi_rate << 8) | + (shortgi_rate << 4) | (shortgi_rate); + + rtl_write_byte(rtlpriv, SG_RATE, shortgi_rate); + } + + rtl_write_dword(rtlpriv, ARFR0 + ratr_index * 4, ratr_value); + if (ratr_value & 0xfffff000) + rtl92s_phy_set_fw_cmd(hw, FW_CMD_RA_REFRESH_N); + else + rtl92s_phy_set_fw_cmd(hw, FW_CMD_RA_REFRESH_BG); + + RT_TRACE(rtlpriv, COMP_RATR, DBG_DMESG, "%x\n", + rtl_read_dword(rtlpriv, ARFR0)); +} + +static void rtl92se_update_hal_rate_mask(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + u8 rssi_level) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_sta_info *sta_entry = NULL; + u32 ratr_bitmap; + u8 ratr_index = 0; + u8 curtxbw_40mhz = (sta->bandwidth >= IEEE80211_STA_RX_BW_40) ? 1 : 0; + u8 curshortgi_40mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_40) ? + 1 : 0; + u8 curshortgi_20mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_20) ? + 1 : 0; + enum wireless_mode wirelessmode = 0; + bool shortgi = false; + u32 ratr_value = 0; + u8 shortgi_rate = 0; + u32 mask = 0; + u32 band = 0; + bool bmulticast = false; + u8 macid = 0; + u8 mimo_ps = IEEE80211_SMPS_OFF; + + sta_entry = (struct rtl_sta_info *) sta->drv_priv; + wirelessmode = sta_entry->wireless_mode; + if (mac->opmode == NL80211_IFTYPE_STATION) + curtxbw_40mhz = mac->bw_40; + else if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_ADHOC) + macid = sta->aid + 1; + + if (rtlhal->current_bandtype == BAND_ON_5G) + ratr_bitmap = sta->supp_rates[1] << 4; + else + ratr_bitmap = sta->supp_rates[0]; + if (mac->opmode == NL80211_IFTYPE_ADHOC) + ratr_bitmap = 0xfff; + ratr_bitmap |= (sta->ht_cap.mcs.rx_mask[1] << 20 | + sta->ht_cap.mcs.rx_mask[0] << 12); + switch (wirelessmode) { + case WIRELESS_MODE_B: + band |= WIRELESS_11B; + ratr_index = RATR_INX_WIRELESS_B; + if (ratr_bitmap & 0x0000000c) + ratr_bitmap &= 0x0000000d; + else + ratr_bitmap &= 0x0000000f; + break; + case WIRELESS_MODE_G: + band |= (WIRELESS_11G | WIRELESS_11B); + ratr_index = RATR_INX_WIRELESS_GB; + + if (rssi_level == 1) + ratr_bitmap &= 0x00000f00; + else if (rssi_level == 2) + ratr_bitmap &= 0x00000ff0; + else + ratr_bitmap &= 0x00000ff5; + break; + case WIRELESS_MODE_A: + band |= WIRELESS_11A; + ratr_index = RATR_INX_WIRELESS_A; + ratr_bitmap &= 0x00000ff0; + break; + case WIRELESS_MODE_N_24G: + case WIRELESS_MODE_N_5G: + band |= (WIRELESS_11N | WIRELESS_11G | WIRELESS_11B); + ratr_index = RATR_INX_WIRELESS_NGB; + + if (mimo_ps == IEEE80211_SMPS_STATIC) { + if (rssi_level == 1) + ratr_bitmap &= 0x00070000; + else if (rssi_level == 2) + ratr_bitmap &= 0x0007f000; + else + ratr_bitmap &= 0x0007f005; + } else { + if (rtlphy->rf_type == RF_1T2R || + rtlphy->rf_type == RF_1T1R) { + if (rssi_level == 1) { + ratr_bitmap &= 0x000f0000; + } else if (rssi_level == 3) { + ratr_bitmap &= 0x000fc000; + } else if (rssi_level == 5) { + ratr_bitmap &= 0x000ff000; + } else { + if (curtxbw_40mhz) + ratr_bitmap &= 0x000ff015; + else + ratr_bitmap &= 0x000ff005; + } + } else { + if (rssi_level == 1) { + ratr_bitmap &= 0x0f8f0000; + } else if (rssi_level == 3) { + ratr_bitmap &= 0x0f8fc000; + } else if (rssi_level == 5) { + ratr_bitmap &= 0x0f8ff000; + } else { + if (curtxbw_40mhz) + ratr_bitmap &= 0x0f8ff015; + else + ratr_bitmap &= 0x0f8ff005; + } + } + } + + if ((curtxbw_40mhz && curshortgi_40mhz) || + (!curtxbw_40mhz && curshortgi_20mhz)) { + if (macid == 0) + shortgi = true; + else if (macid == 1) + shortgi = false; + } + break; + default: + band |= (WIRELESS_11N | WIRELESS_11G | WIRELESS_11B); + ratr_index = RATR_INX_WIRELESS_NGB; + + if (rtlphy->rf_type == RF_1T2R) + ratr_bitmap &= 0x000ff0ff; + else + ratr_bitmap &= 0x0f8ff0ff; + break; + } + sta_entry->ratr_index = ratr_index; + + if (rtlpriv->rtlhal.version >= VERSION_8192S_BCUT) + ratr_bitmap &= 0x0FFFFFFF; + else if (rtlpriv->rtlhal.version == VERSION_8192S_ACUT) + ratr_bitmap &= 0x0FFFFFF0; + + if (shortgi) { + ratr_bitmap |= 0x10000000; + /* Get MAX MCS available. */ + ratr_value = (ratr_bitmap >> 12); + for (shortgi_rate = 15; shortgi_rate > 0; shortgi_rate--) { + if ((1 << shortgi_rate) & ratr_value) + break; + } + + shortgi_rate = (shortgi_rate << 12) | (shortgi_rate << 8) | + (shortgi_rate << 4) | (shortgi_rate); + rtl_write_byte(rtlpriv, SG_RATE, shortgi_rate); + } + + mask |= (bmulticast ? 1 : 0) << 9 | (macid & 0x1f) << 4 | (band & 0xf); + + RT_TRACE(rtlpriv, COMP_RATR, DBG_TRACE, "mask = %x, bitmap = %x\n", + mask, ratr_bitmap); + rtl_write_dword(rtlpriv, 0x2c4, ratr_bitmap); + rtl_write_dword(rtlpriv, WFM5, (FW_RA_UPDATE_MASK | (mask << 8))); + + if (macid != 0) + sta_entry->ratr_index = ratr_index; +} + +void rtl92se_update_hal_rate_tbl(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 rssi_level) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (rtlpriv->dm.useramask) + rtl92se_update_hal_rate_mask(hw, sta, rssi_level); + else + rtl92se_update_hal_rate_table(hw, sta); +} + +void rtl92se_update_channel_access_setting(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 sifs_timer; + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SLOT_TIME, + &mac->slot_time); + sifs_timer = 0x0e0e; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SIFS, (u8 *)&sifs_timer); + +} + +/* this ifunction is for RFKILL, it's different with windows, + * because UI will disable wireless when GPIO Radio Off. + * And here we not check or Disable/Enable ASPM like windows*/ +bool rtl92se_gpio_radio_on_off_checking(struct ieee80211_hw *hw, u8 *valid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + enum rf_pwrstate rfpwr_toset /*, cur_rfstate */; + unsigned long flag = 0; + bool actuallyset = false; + bool turnonbypowerdomain = false; + + /* just 8191se can check gpio before firstup, 92c/92d have fixed it */ + if ((rtlpci->up_first_time == 1) || (rtlpci->being_init_adapter)) + return false; + + if (ppsc->swrf_processing) + return false; + + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); + if (ppsc->rfchange_inprogress) { + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + return false; + } else { + ppsc->rfchange_inprogress = true; + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + } + + /* cur_rfstate = ppsc->rfpwr_state;*/ + + /* because after _rtl92s_phy_set_rfhalt, all power + * closed, so we must open some power for GPIO check, + * or we will always check GPIO RFOFF here, + * And we should close power after GPIO check */ + if (RT_IN_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC)) { + _rtl92se_power_domain_init(hw); + turnonbypowerdomain = true; + } + + rfpwr_toset = _rtl92se_rf_onoff_detect(hw); + + if ((ppsc->hwradiooff) && (rfpwr_toset == ERFON)) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "RFKILL-HW Radio ON, RF ON\n"); + + rfpwr_toset = ERFON; + ppsc->hwradiooff = false; + actuallyset = true; + } else if ((!ppsc->hwradiooff) && (rfpwr_toset == ERFOFF)) { + RT_TRACE(rtlpriv, COMP_RF, + DBG_DMESG, "RFKILL-HW Radio OFF, RF OFF\n"); + + rfpwr_toset = ERFOFF; + ppsc->hwradiooff = true; + actuallyset = true; + } + + if (actuallyset) { + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); + ppsc->rfchange_inprogress = false; + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + + /* this not include ifconfig wlan0 down case */ + /* } else if (rfpwr_toset == ERFOFF || cur_rfstate == ERFOFF) { */ + } else { + /* because power_domain_init may be happen when + * _rtl92s_phy_set_rfhalt, this will open some powers + * and cause current increasing about 40 mA for ips, + * rfoff and ifconfig down, so we set + * _rtl92s_phy_set_rfhalt again here */ + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_HALT_NIC && + turnonbypowerdomain) { + _rtl92s_phy_set_rfhalt(hw); + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + } + + spin_lock_irqsave(&rtlpriv->locks.rf_ps_lock, flag); + ppsc->rfchange_inprogress = false; + spin_unlock_irqrestore(&rtlpriv->locks.rf_ps_lock, flag); + } + + *valid = 1; + return !ppsc->hwradiooff; + +} + +/* Is_wepkey just used for WEP used as group & pairwise key + * if pairwise is AES ang group is WEP Is_wepkey == false.*/ +void rtl92se_set_key(struct ieee80211_hw *hw, u32 key_index, u8 *p_macaddr, + bool is_group, u8 enc_algo, bool is_wepkey, bool clear_all) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 *macaddr = p_macaddr; + + u32 entry_id = 0; + bool is_pairwise = false; + + static u8 cam_const_addr[4][6] = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x03} + }; + static u8 cam_const_broad[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + if (clear_all) { + u8 idx = 0; + u8 cam_offset = 0; + u8 clear_number = 5; + + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, "clear_all\n"); + + for (idx = 0; idx < clear_number; idx++) { + rtl_cam_mark_invalid(hw, cam_offset + idx); + rtl_cam_empty_entry(hw, cam_offset + idx); + + if (idx < 5) { + memset(rtlpriv->sec.key_buf[idx], 0, + MAX_KEY_LEN); + rtlpriv->sec.key_len[idx] = 0; + } + } + + } else { + switch (enc_algo) { + case WEP40_ENCRYPTION: + enc_algo = CAM_WEP40; + break; + case WEP104_ENCRYPTION: + enc_algo = CAM_WEP104; + break; + case TKIP_ENCRYPTION: + enc_algo = CAM_TKIP; + break; + case AESCCMP_ENCRYPTION: + enc_algo = CAM_AES; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + enc_algo = CAM_TKIP; + break; + } + + if (is_wepkey || rtlpriv->sec.use_defaultkey) { + macaddr = cam_const_addr[key_index]; + entry_id = key_index; + } else { + if (is_group) { + macaddr = cam_const_broad; + entry_id = key_index; + } else { + if (mac->opmode == NL80211_IFTYPE_AP) { + entry_id = rtl_cam_get_free_entry(hw, + p_macaddr); + if (entry_id >= TOTAL_CAM_ENTRY) { + RT_TRACE(rtlpriv, + COMP_SEC, DBG_EMERG, + "Can not find free hw security cam entry\n"); + return; + } + } else { + entry_id = CAM_PAIRWISE_KEY_POSITION; + } + + key_index = PAIRWISE_KEYIDX; + is_pairwise = true; + } + } + + if (rtlpriv->sec.key_len[key_index] == 0) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "delete one entry, entry_id is %d\n", + entry_id); + if (mac->opmode == NL80211_IFTYPE_AP) + rtl_cam_del_entry(hw, p_macaddr); + rtl_cam_delete_one_entry(hw, p_macaddr, entry_id); + } else { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "add one entry\n"); + if (is_pairwise) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "set Pairwise key\n"); + + rtl_cam_add_one_entry(hw, macaddr, key_index, + entry_id, enc_algo, + CAM_CONFIG_NO_USEDK, + rtlpriv->sec.key_buf[key_index]); + } else { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "set group key\n"); + + if (mac->opmode == NL80211_IFTYPE_ADHOC) { + rtl_cam_add_one_entry(hw, + rtlefuse->dev_addr, + PAIRWISE_KEYIDX, + CAM_PAIRWISE_KEY_POSITION, + enc_algo, CAM_CONFIG_NO_USEDK, + rtlpriv->sec.key_buf[entry_id]); + } + + rtl_cam_add_one_entry(hw, macaddr, key_index, + entry_id, enc_algo, + CAM_CONFIG_NO_USEDK, + rtlpriv->sec.key_buf[entry_id]); + } + + } + } +} + +void rtl92se_suspend(struct ieee80211_hw *hw) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + rtlpci->up_first_time = true; +} + +void rtl92se_resume(struct ieee80211_hw *hw) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u32 val; + + pci_read_config_dword(rtlpci->pdev, 0x40, &val); + if ((val & 0x0000ff00) != 0) + pci_write_config_dword(rtlpci->pdev, 0x40, + val & 0xffff00ff); +} + +/* Turn on AAP (RCR:bit 0) for promicuous mode. */ +void rtl92se_allow_all_destaddr(struct ieee80211_hw *hw, + bool allow_all_da, bool write_into_reg) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + if (allow_all_da) /* Set BIT0 */ + rtlpci->receive_config |= RCR_AAP; + else /* Clear BIT0 */ + rtlpci->receive_config &= ~RCR_AAP; + + if (write_into_reg) + rtl_write_dword(rtlpriv, RCR, rtlpci->receive_config); + + RT_TRACE(rtlpriv, COMP_TURBO | COMP_INIT, DBG_LOUD, + "receive_config=0x%08X, write_into_reg=%d\n", + rtlpci->receive_config, write_into_reg); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/hw.h b/drivers/net/wireless/rtlwifi/rtl8192se/hw.h new file mode 100644 index 0000000..da48aa8 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/hw.h @@ -0,0 +1,80 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ +#ifndef __REALTEK_PCI92SE_HW_H__ +#define __REALTEK_PCI92SE_HW_H__ + +#define MSR_LINK_MANAGED 2 +#define MSR_LINK_NONE 0 +#define MSR_LINK_SHIFT 0 +#define MSR_LINK_ADHOC 1 +#define MSR_LINK_MASTER 3 + +enum WIRELESS_NETWORK_TYPE { + WIRELESS_11B = 1, + WIRELESS_11G = 2, + WIRELESS_11A = 4, + WIRELESS_11N = 8 +}; + +void rtl92se_get_hw_reg(struct ieee80211_hw *hw, + u8 variable, u8 *val); +void rtl92se_read_eeprom_info(struct ieee80211_hw *hw); +void rtl92se_interrupt_recognized(struct ieee80211_hw *hw, + u32 *inta, u32 *intb); +int rtl92se_hw_init(struct ieee80211_hw *hw); +void rtl92se_card_disable(struct ieee80211_hw *hw); +void rtl92se_enable_interrupt(struct ieee80211_hw *hw); +void rtl92se_disable_interrupt(struct ieee80211_hw *hw); +int rtl92se_set_network_type(struct ieee80211_hw *hw, + enum nl80211_iftype type); +void rtl92se_set_check_bssid(struct ieee80211_hw *hw, bool check_bssid); +void rtl92se_set_mac_addr(struct rtl_io *io, const u8 *addr); +void rtl92se_set_qos(struct ieee80211_hw *hw, int aci); +void rtl92se_set_beacon_related_registers(struct ieee80211_hw *hw); +void rtl92se_set_beacon_interval(struct ieee80211_hw *hw); +void rtl92se_update_interrupt_mask(struct ieee80211_hw *hw, + u32 add_msr, u32 rm_msr); +void rtl92se_set_hw_reg(struct ieee80211_hw *hw, u8 variable, + u8 *val); +void rtl92se_update_hal_rate_tbl(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 rssi_level); +void rtl92se_update_channel_access_setting(struct ieee80211_hw *hw); +bool rtl92se_gpio_radio_on_off_checking(struct ieee80211_hw *hw, + u8 *valid); +void rtl8192se_gpiobit3_cfg_inputmode(struct ieee80211_hw *hw); +void rtl92se_enable_hw_security_config(struct ieee80211_hw *hw); +void rtl92se_set_key(struct ieee80211_hw *hw, + u32 key_index, u8 *macaddr, bool is_group, + u8 enc_algo, bool is_wepkey, bool clear_all); +void rtl92se_suspend(struct ieee80211_hw *hw); +void rtl92se_resume(struct ieee80211_hw *hw); +void rtl92se_allow_all_destaddr(struct ieee80211_hw *hw, + bool allow_all_da, bool write_into_reg); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/led.c b/drivers/net/wireless/rtlwifi/rtl8192se/led.c new file mode 100644 index 0000000..44949b5 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/led.c @@ -0,0 +1,151 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "reg.h" +#include "led.h" + +static void _rtl92se_init_led(struct ieee80211_hw *hw, + struct rtl_led *pled, enum rtl_led_pin ledpin) +{ + pled->hw = hw; + pled->ledpin = ledpin; + pled->ledon = false; +} + +void rtl92se_init_sw_leds(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + _rtl92se_init_led(hw, &(pcipriv->ledctl.sw_led0), LED_PIN_LED0); + _rtl92se_init_led(hw, &(pcipriv->ledctl.sw_led1), LED_PIN_LED1); +} + +void rtl92se_sw_led_on(struct ieee80211_hw *hw, struct rtl_led *pled) +{ + u8 ledcfg; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, "LedAddr:%X ledpin=%d\n", + LEDCFG, pled->ledpin); + + ledcfg = rtl_read_byte(rtlpriv, LEDCFG); + + switch (pled->ledpin) { + case LED_PIN_GPIO0: + break; + case LED_PIN_LED0: + rtl_write_byte(rtlpriv, LEDCFG, ledcfg & 0xf0); + break; + case LED_PIN_LED1: + rtl_write_byte(rtlpriv, LEDCFG, ledcfg & 0x0f); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + pled->ledon = true; +} + +void rtl92se_sw_led_off(struct ieee80211_hw *hw, struct rtl_led *pled) +{ + struct rtl_priv *rtlpriv; + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + u8 ledcfg; + + rtlpriv = rtl_priv(hw); + if (!rtlpriv || rtlpriv->max_fw_size) + return; + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, "LedAddr:%X ledpin=%d\n", + LEDCFG, pled->ledpin); + + ledcfg = rtl_read_byte(rtlpriv, LEDCFG); + + switch (pled->ledpin) { + case LED_PIN_GPIO0: + break; + case LED_PIN_LED0: + ledcfg &= 0xf0; + if (pcipriv->ledctl.led_opendrain) + rtl_write_byte(rtlpriv, LEDCFG, (ledcfg | BIT(1))); + else + rtl_write_byte(rtlpriv, LEDCFG, (ledcfg | BIT(3))); + break; + case LED_PIN_LED1: + ledcfg &= 0x0f; + rtl_write_byte(rtlpriv, LEDCFG, (ledcfg | BIT(3))); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + pled->ledon = false; +} + +static void _rtl92se_sw_led_control(struct ieee80211_hw *hw, + enum led_ctl_mode ledaction) +{ + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_led *pLed0 = &(pcipriv->ledctl.sw_led0); + switch (ledaction) { + case LED_CTL_POWER_ON: + case LED_CTL_LINK: + case LED_CTL_NO_LINK: + rtl92se_sw_led_on(hw, pLed0); + break; + case LED_CTL_POWER_OFF: + rtl92se_sw_led_off(hw, pLed0); + break; + default: + break; + } +} + +void rtl92se_led_control(struct ieee80211_hw *hw, enum led_ctl_mode ledaction) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + if ((ppsc->rfoff_reason > RF_CHANGE_BY_PS) && + (ledaction == LED_CTL_TX || + ledaction == LED_CTL_RX || + ledaction == LED_CTL_SITE_SURVEY || + ledaction == LED_CTL_LINK || + ledaction == LED_CTL_NO_LINK || + ledaction == LED_CTL_START_TO_LINK || + ledaction == LED_CTL_POWER_ON)) { + return; + } + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, "ledaction %d\n", ledaction); + + _rtl92se_sw_led_control(hw, ledaction); +} + diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/led.h b/drivers/net/wireless/rtlwifi/rtl8192se/led.h new file mode 100644 index 0000000..2182dbe --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/led.h @@ -0,0 +1,37 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ +#ifndef __REALTEK_PCI92SE_LED_H__ +#define __REALTEK_PCI92SE_LED_H__ + +void rtl92se_init_sw_leds(struct ieee80211_hw *hw); +void rtl92se_sw_led_on(struct ieee80211_hw *hw, struct rtl_led *pled); +void rtl92se_sw_led_off(struct ieee80211_hw *hw, struct rtl_led *pled); +void rtl92se_led_control(struct ieee80211_hw *hw, enum led_ctl_mode ledaction); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/phy.c b/drivers/net/wireless/rtlwifi/rtl8192se/phy.c new file mode 100644 index 0000000..9c092e6 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/phy.c @@ -0,0 +1,1727 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "../ps.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "rf.h" +#include "dm.h" +#include "fw.h" +#include "hw.h" +#include "table.h" + +static u32 _rtl92s_phy_calculate_bit_shift(u32 bitmask) +{ + u32 i; + + for (i = 0; i <= 31; i++) { + if (((bitmask >> i) & 0x1) == 1) + break; + } + + return i; +} + +u32 rtl92s_phy_query_bb_reg(struct ieee80211_hw *hw, u32 regaddr, u32 bitmask) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 returnvalue = 0, originalvalue, bitshift; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, "regaddr(%#x), bitmask(%#x)\n", + regaddr, bitmask); + + originalvalue = rtl_read_dword(rtlpriv, regaddr); + bitshift = _rtl92s_phy_calculate_bit_shift(bitmask); + returnvalue = (originalvalue & bitmask) >> bitshift; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, "BBR MASK=0x%x Addr[0x%x]=0x%x\n", + bitmask, regaddr, originalvalue); + + return returnvalue; + +} + +void rtl92s_phy_set_bb_reg(struct ieee80211_hw *hw, u32 regaddr, u32 bitmask, + u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 originalvalue, bitshift; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x)\n", + regaddr, bitmask, data); + + if (bitmask != MASKDWORD) { + originalvalue = rtl_read_dword(rtlpriv, regaddr); + bitshift = _rtl92s_phy_calculate_bit_shift(bitmask); + data = ((originalvalue & (~bitmask)) | (data << bitshift)); + } + + rtl_write_dword(rtlpriv, regaddr, data); + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x)\n", + regaddr, bitmask, data); + +} + +static u32 _rtl92s_phy_rf_serial_read(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset) +{ + + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct bb_reg_def *pphyreg = &rtlphy->phyreg_def[rfpath]; + u32 newoffset; + u32 tmplong, tmplong2; + u8 rfpi_enable = 0; + u32 retvalue = 0; + + offset &= 0x3f; + newoffset = offset; + + tmplong = rtl_get_bbreg(hw, RFPGA0_XA_HSSIPARAMETER2, MASKDWORD); + + if (rfpath == RF90_PATH_A) + tmplong2 = tmplong; + else + tmplong2 = rtl_get_bbreg(hw, pphyreg->rfhssi_para2, MASKDWORD); + + tmplong2 = (tmplong2 & (~BLSSI_READADDRESS)) | (newoffset << 23) | + BLSSI_READEDGE; + + rtl_set_bbreg(hw, RFPGA0_XA_HSSIPARAMETER2, MASKDWORD, + tmplong & (~BLSSI_READEDGE)); + + mdelay(1); + + rtl_set_bbreg(hw, pphyreg->rfhssi_para2, MASKDWORD, tmplong2); + mdelay(1); + + rtl_set_bbreg(hw, RFPGA0_XA_HSSIPARAMETER2, MASKDWORD, tmplong | + BLSSI_READEDGE); + mdelay(1); + + if (rfpath == RF90_PATH_A) + rfpi_enable = (u8)rtl_get_bbreg(hw, RFPGA0_XA_HSSIPARAMETER1, + BIT(8)); + else if (rfpath == RF90_PATH_B) + rfpi_enable = (u8)rtl_get_bbreg(hw, RFPGA0_XB_HSSIPARAMETER1, + BIT(8)); + + if (rfpi_enable) + retvalue = rtl_get_bbreg(hw, pphyreg->rf_rbpi, + BLSSI_READBACK_DATA); + else + retvalue = rtl_get_bbreg(hw, pphyreg->rf_rb, + BLSSI_READBACK_DATA); + + retvalue = rtl_get_bbreg(hw, pphyreg->rf_rb, + BLSSI_READBACK_DATA); + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, "RFR-%d Addr[0x%x]=0x%x\n", + rfpath, pphyreg->rf_rb, retvalue); + + return retvalue; + +} + +static void _rtl92s_phy_rf_serial_write(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset, + u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct bb_reg_def *pphyreg = &rtlphy->phyreg_def[rfpath]; + u32 data_and_addr = 0; + u32 newoffset; + + offset &= 0x3f; + newoffset = offset; + + data_and_addr = ((newoffset << 20) | (data & 0x000fffff)) & 0x0fffffff; + rtl_set_bbreg(hw, pphyreg->rf3wire_offset, MASKDWORD, data_and_addr); + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, "RFW-%d Addr[0x%x]=0x%x\n", + rfpath, pphyreg->rf3wire_offset, data_and_addr); +} + + +u32 rtl92s_phy_query_rf_reg(struct ieee80211_hw *hw, enum radio_path rfpath, + u32 regaddr, u32 bitmask) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 original_value, readback_value, bitshift; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), rfpath(%#x), bitmask(%#x)\n", + regaddr, rfpath, bitmask); + + spin_lock(&rtlpriv->locks.rf_lock); + + original_value = _rtl92s_phy_rf_serial_read(hw, rfpath, regaddr); + + bitshift = _rtl92s_phy_calculate_bit_shift(bitmask); + readback_value = (original_value & bitmask) >> bitshift; + + spin_unlock(&rtlpriv->locks.rf_lock); + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), rfpath(%#x), bitmask(%#x), original_value(%#x)\n", + regaddr, rfpath, bitmask, original_value); + + return readback_value; +} + +void rtl92s_phy_set_rf_reg(struct ieee80211_hw *hw, enum radio_path rfpath, + u32 regaddr, u32 bitmask, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 original_value, bitshift; + + if (!((rtlphy->rf_pathmap >> rfpath) & 0x1)) + return; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x), rfpath(%#x)\n", + regaddr, bitmask, data, rfpath); + + spin_lock(&rtlpriv->locks.rf_lock); + + if (bitmask != RFREG_OFFSET_MASK) { + original_value = _rtl92s_phy_rf_serial_read(hw, rfpath, + regaddr); + bitshift = _rtl92s_phy_calculate_bit_shift(bitmask); + data = ((original_value & (~bitmask)) | (data << bitshift)); + } + + _rtl92s_phy_rf_serial_write(hw, rfpath, regaddr, data); + + spin_unlock(&rtlpriv->locks.rf_lock); + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x), rfpath(%#x)\n", + regaddr, bitmask, data, rfpath); + +} + +void rtl92s_phy_scan_operation_backup(struct ieee80211_hw *hw, + u8 operation) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (!is_hal_stop(rtlhal)) { + switch (operation) { + case SCAN_OPT_BACKUP: + rtl92s_phy_set_fw_cmd(hw, FW_CMD_PAUSE_DM_BY_SCAN); + break; + case SCAN_OPT_RESTORE: + rtl92s_phy_set_fw_cmd(hw, FW_CMD_RESUME_DM_BY_SCAN); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Unknown operation\n"); + break; + } + } +} + +void rtl92s_phy_set_bw_mode(struct ieee80211_hw *hw, + enum nl80211_channel_type ch_type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u8 reg_bw_opmode; + + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, "Switch to %s bandwidth\n", + rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20 ? + "20MHz" : "40MHz"); + + if (rtlphy->set_bwmode_inprogress) + return; + if (is_hal_stop(rtlhal)) + return; + + rtlphy->set_bwmode_inprogress = true; + + reg_bw_opmode = rtl_read_byte(rtlpriv, BW_OPMODE); + /* dummy read */ + rtl_read_byte(rtlpriv, RRSR + 2); + + switch (rtlphy->current_chan_bw) { + case HT_CHANNEL_WIDTH_20: + reg_bw_opmode |= BW_OPMODE_20MHZ; + rtl_write_byte(rtlpriv, BW_OPMODE, reg_bw_opmode); + break; + case HT_CHANNEL_WIDTH_20_40: + reg_bw_opmode &= ~BW_OPMODE_20MHZ; + rtl_write_byte(rtlpriv, BW_OPMODE, reg_bw_opmode); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", rtlphy->current_chan_bw); + break; + } + + switch (rtlphy->current_chan_bw) { + case HT_CHANNEL_WIDTH_20: + rtl_set_bbreg(hw, RFPGA0_RFMOD, BRFMOD, 0x0); + rtl_set_bbreg(hw, RFPGA1_RFMOD, BRFMOD, 0x0); + + if (rtlhal->version >= VERSION_8192S_BCUT) + rtl_write_byte(rtlpriv, RFPGA0_ANALOGPARAMETER2, 0x58); + break; + case HT_CHANNEL_WIDTH_20_40: + rtl_set_bbreg(hw, RFPGA0_RFMOD, BRFMOD, 0x1); + rtl_set_bbreg(hw, RFPGA1_RFMOD, BRFMOD, 0x1); + + rtl_set_bbreg(hw, RCCK0_SYSTEM, BCCK_SIDEBAND, + (mac->cur_40_prime_sc >> 1)); + rtl_set_bbreg(hw, ROFDM1_LSTF, 0xC00, mac->cur_40_prime_sc); + + if (rtlhal->version >= VERSION_8192S_BCUT) + rtl_write_byte(rtlpriv, RFPGA0_ANALOGPARAMETER2, 0x18); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", rtlphy->current_chan_bw); + break; + } + + rtl92s_phy_rf6052_set_bandwidth(hw, rtlphy->current_chan_bw); + rtlphy->set_bwmode_inprogress = false; + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, "<==\n"); +} + +static bool _rtl92s_phy_set_sw_chnl_cmdarray(struct swchnlcmd *cmdtable, + u32 cmdtableidx, u32 cmdtablesz, enum swchnlcmd_id cmdid, + u32 para1, u32 para2, u32 msdelay) +{ + struct swchnlcmd *pcmd; + + if (cmdtable == NULL) { + RT_ASSERT(false, "cmdtable cannot be NULL\n"); + return false; + } + + if (cmdtableidx >= cmdtablesz) + return false; + + pcmd = cmdtable + cmdtableidx; + pcmd->cmdid = cmdid; + pcmd->para1 = para1; + pcmd->para2 = para2; + pcmd->msdelay = msdelay; + + return true; +} + +static bool _rtl92s_phy_sw_chnl_step_by_step(struct ieee80211_hw *hw, + u8 channel, u8 *stage, u8 *step, u32 *delay) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct swchnlcmd precommoncmd[MAX_PRECMD_CNT]; + u32 precommoncmdcnt; + struct swchnlcmd postcommoncmd[MAX_POSTCMD_CNT]; + u32 postcommoncmdcnt; + struct swchnlcmd rfdependcmd[MAX_RFDEPENDCMD_CNT]; + u32 rfdependcmdcnt; + struct swchnlcmd *currentcmd = NULL; + u8 rfpath; + u8 num_total_rfpath = rtlphy->num_total_rfpath; + + precommoncmdcnt = 0; + _rtl92s_phy_set_sw_chnl_cmdarray(precommoncmd, precommoncmdcnt++, + MAX_PRECMD_CNT, CMDID_SET_TXPOWEROWER_LEVEL, 0, 0, 0); + _rtl92s_phy_set_sw_chnl_cmdarray(precommoncmd, precommoncmdcnt++, + MAX_PRECMD_CNT, CMDID_END, 0, 0, 0); + + postcommoncmdcnt = 0; + + _rtl92s_phy_set_sw_chnl_cmdarray(postcommoncmd, postcommoncmdcnt++, + MAX_POSTCMD_CNT, CMDID_END, 0, 0, 0); + + rfdependcmdcnt = 0; + + RT_ASSERT((channel >= 1 && channel <= 14), + "invalid channel for Zebra: %d\n", channel); + + _rtl92s_phy_set_sw_chnl_cmdarray(rfdependcmd, rfdependcmdcnt++, + MAX_RFDEPENDCMD_CNT, CMDID_RF_WRITEREG, + RF_CHNLBW, channel, 10); + + _rtl92s_phy_set_sw_chnl_cmdarray(rfdependcmd, rfdependcmdcnt++, + MAX_RFDEPENDCMD_CNT, CMDID_END, 0, 0, 0); + + do { + switch (*stage) { + case 0: + currentcmd = &precommoncmd[*step]; + break; + case 1: + currentcmd = &rfdependcmd[*step]; + break; + case 2: + currentcmd = &postcommoncmd[*step]; + break; + } + + if (currentcmd->cmdid == CMDID_END) { + if ((*stage) == 2) { + return true; + } else { + (*stage)++; + (*step) = 0; + continue; + } + } + + switch (currentcmd->cmdid) { + case CMDID_SET_TXPOWEROWER_LEVEL: + rtl92s_phy_set_txpower(hw, channel); + break; + case CMDID_WRITEPORT_ULONG: + rtl_write_dword(rtlpriv, currentcmd->para1, + currentcmd->para2); + break; + case CMDID_WRITEPORT_USHORT: + rtl_write_word(rtlpriv, currentcmd->para1, + (u16)currentcmd->para2); + break; + case CMDID_WRITEPORT_UCHAR: + rtl_write_byte(rtlpriv, currentcmd->para1, + (u8)currentcmd->para2); + break; + case CMDID_RF_WRITEREG: + for (rfpath = 0; rfpath < num_total_rfpath; rfpath++) { + rtlphy->rfreg_chnlval[rfpath] = + ((rtlphy->rfreg_chnlval[rfpath] & + 0xfffffc00) | currentcmd->para2); + rtl_set_rfreg(hw, (enum radio_path)rfpath, + currentcmd->para1, + RFREG_OFFSET_MASK, + rtlphy->rfreg_chnlval[rfpath]); + } + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + + break; + } while (true); + + (*delay) = currentcmd->msdelay; + (*step)++; + return false; +} + +u8 rtl92s_phy_sw_chnl(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 delay; + bool ret; + + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, "switch to channel%d\n", + rtlphy->current_channel); + + if (rtlphy->sw_chnl_inprogress) + return 0; + + if (rtlphy->set_bwmode_inprogress) + return 0; + + if (is_hal_stop(rtlhal)) + return 0; + + rtlphy->sw_chnl_inprogress = true; + rtlphy->sw_chnl_stage = 0; + rtlphy->sw_chnl_step = 0; + + do { + if (!rtlphy->sw_chnl_inprogress) + break; + + ret = _rtl92s_phy_sw_chnl_step_by_step(hw, + rtlphy->current_channel, + &rtlphy->sw_chnl_stage, + &rtlphy->sw_chnl_step, &delay); + if (!ret) { + if (delay > 0) + mdelay(delay); + else + continue; + } else { + rtlphy->sw_chnl_inprogress = false; + } + break; + } while (true); + + rtlphy->sw_chnl_inprogress = false; + + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, "<==\n"); + + return 1; +} + +static void _rtl92se_phy_set_rf_sleep(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 u1btmp; + + u1btmp = rtl_read_byte(rtlpriv, LDOV12D_CTRL); + u1btmp |= BIT(0); + + rtl_write_byte(rtlpriv, LDOV12D_CTRL, u1btmp); + rtl_write_byte(rtlpriv, SPS1_CTRL, 0x0); + rtl_write_byte(rtlpriv, TXPAUSE, 0xFF); + rtl_write_word(rtlpriv, CMDR, 0x57FC); + udelay(100); + + rtl_write_word(rtlpriv, CMDR, 0x77FC); + rtl_write_byte(rtlpriv, PHY_CCA, 0x0); + udelay(10); + + rtl_write_word(rtlpriv, CMDR, 0x37FC); + udelay(10); + + rtl_write_word(rtlpriv, CMDR, 0x77FC); + udelay(10); + + rtl_write_word(rtlpriv, CMDR, 0x57FC); + + /* we should chnge GPIO to input mode + * this will drop away current about 25mA*/ + rtl8192se_gpiobit3_cfg_inputmode(hw); +} + +bool rtl92s_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + bool bresult = true; + u8 i, queue_id; + struct rtl8192_tx_ring *ring = NULL; + + if (rfpwr_state == ppsc->rfpwr_state) + return false; + + switch (rfpwr_state) { + case ERFON:{ + if ((ppsc->rfpwr_state == ERFOFF) && + RT_IN_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC)) { + + bool rtstatus; + u32 InitializeCount = 0; + do { + InitializeCount++; + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "IPS Set eRf nic enable\n"); + rtstatus = rtl_ps_enable_nic(hw); + } while (!rtstatus && (InitializeCount < 10)); + + RT_CLEAR_PS_LEVEL(ppsc, + RT_RF_OFF_LEVL_HALT_NIC); + } else { + RT_TRACE(rtlpriv, COMP_POWER, DBG_DMESG, + "awake, sleeped:%d ms state_inap:%x\n", + jiffies_to_msecs(jiffies - + ppsc-> + last_sleep_jiffies), + rtlpriv->psc.state_inap); + ppsc->last_awake_jiffies = jiffies; + rtl_write_word(rtlpriv, CMDR, 0x37FC); + rtl_write_byte(rtlpriv, TXPAUSE, 0x00); + rtl_write_byte(rtlpriv, PHY_CCA, 0x3); + } + + if (mac->link_state == MAC80211_LINKED) + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_LINK); + else + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_NO_LINK); + break; + } + case ERFOFF:{ + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_HALT_NIC) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "IPS Set eRf nic disable\n"); + rtl_ps_disable_nic(hw); + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + } else { + if (ppsc->rfoff_reason == RF_CHANGE_BY_IPS) + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_NO_LINK); + else + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_POWER_OFF); + } + break; + } + case ERFSLEEP: + if (ppsc->rfpwr_state == ERFOFF) + return false; + + for (queue_id = 0, i = 0; + queue_id < RTL_PCI_MAX_TX_QUEUE_COUNT;) { + ring = &pcipriv->dev.tx_ring[queue_id]; + if (skb_queue_len(&ring->queue) == 0 || + queue_id == BEACON_QUEUE) { + queue_id++; + continue; + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "eRf Off/Sleep: %d times TcbBusyQueue[%d] = %d before doze!\n", + i + 1, queue_id, + skb_queue_len(&ring->queue)); + + udelay(10); + i++; + } + + if (i >= MAX_DOZE_WAITING_TIMES_9x) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "ERFOFF: %d times TcbBusyQueue[%d] = %d !\n", + MAX_DOZE_WAITING_TIMES_9x, + queue_id, + skb_queue_len(&ring->queue)); + break; + } + } + + RT_TRACE(rtlpriv, COMP_POWER, DBG_DMESG, + "Set ERFSLEEP awaked:%d ms\n", + jiffies_to_msecs(jiffies - + ppsc->last_awake_jiffies)); + + RT_TRACE(rtlpriv, COMP_POWER, DBG_DMESG, + "sleep awaked:%d ms state_inap:%x\n", + jiffies_to_msecs(jiffies - + ppsc->last_awake_jiffies), + rtlpriv->psc.state_inap); + ppsc->last_sleep_jiffies = jiffies; + _rtl92se_phy_set_rf_sleep(hw); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + bresult = false; + break; + } + + if (bresult) + ppsc->rfpwr_state = rfpwr_state; + + return bresult; +} + +static bool _rtl92s_phy_config_rfpa_bias_current(struct ieee80211_hw *hw, + enum radio_path rfpath) +{ + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + bool rtstatus = true; + u32 tmpval = 0; + + /* If inferiority IC, we have to increase the PA bias current */ + if (rtlhal->ic_class != IC_INFERIORITY_A) { + tmpval = rtl92s_phy_query_rf_reg(hw, rfpath, RF_IPA, 0xf); + rtl92s_phy_set_rf_reg(hw, rfpath, RF_IPA, 0xf, tmpval + 1); + } + + return rtstatus; +} + +static void _rtl92s_store_pwrindex_diffrate_offset(struct ieee80211_hw *hw, + u32 reg_addr, u32 bitmask, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + int index; + + if (reg_addr == RTXAGC_RATE18_06) + index = 0; + else if (reg_addr == RTXAGC_RATE54_24) + index = 1; + else if (reg_addr == RTXAGC_CCK_MCS32) + index = 6; + else if (reg_addr == RTXAGC_MCS03_MCS00) + index = 2; + else if (reg_addr == RTXAGC_MCS07_MCS04) + index = 3; + else if (reg_addr == RTXAGC_MCS11_MCS08) + index = 4; + else if (reg_addr == RTXAGC_MCS15_MCS12) + index = 5; + else + return; + + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][index] = data; + if (index == 5) + rtlphy->pwrgroup_cnt++; +} + +static void _rtl92s_phy_init_register_definition(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + /*RF Interface Sowrtware Control */ + rtlphy->phyreg_def[RF90_PATH_A].rfintfs = RFPGA0_XAB_RFINTERFACESW; + rtlphy->phyreg_def[RF90_PATH_B].rfintfs = RFPGA0_XAB_RFINTERFACESW; + rtlphy->phyreg_def[RF90_PATH_C].rfintfs = RFPGA0_XCD_RFINTERFACESW; + rtlphy->phyreg_def[RF90_PATH_D].rfintfs = RFPGA0_XCD_RFINTERFACESW; + + /* RF Interface Readback Value */ + rtlphy->phyreg_def[RF90_PATH_A].rfintfi = RFPGA0_XAB_RFINTERFACERB; + rtlphy->phyreg_def[RF90_PATH_B].rfintfi = RFPGA0_XAB_RFINTERFACERB; + rtlphy->phyreg_def[RF90_PATH_C].rfintfi = RFPGA0_XCD_RFINTERFACERB; + rtlphy->phyreg_def[RF90_PATH_D].rfintfi = RFPGA0_XCD_RFINTERFACERB; + + /* RF Interface Output (and Enable) */ + rtlphy->phyreg_def[RF90_PATH_A].rfintfo = RFPGA0_XA_RFINTERFACEOE; + rtlphy->phyreg_def[RF90_PATH_B].rfintfo = RFPGA0_XB_RFINTERFACEOE; + rtlphy->phyreg_def[RF90_PATH_C].rfintfo = RFPGA0_XC_RFINTERFACEOE; + rtlphy->phyreg_def[RF90_PATH_D].rfintfo = RFPGA0_XD_RFINTERFACEOE; + + /* RF Interface (Output and) Enable */ + rtlphy->phyreg_def[RF90_PATH_A].rfintfe = RFPGA0_XA_RFINTERFACEOE; + rtlphy->phyreg_def[RF90_PATH_B].rfintfe = RFPGA0_XB_RFINTERFACEOE; + rtlphy->phyreg_def[RF90_PATH_C].rfintfe = RFPGA0_XC_RFINTERFACEOE; + rtlphy->phyreg_def[RF90_PATH_D].rfintfe = RFPGA0_XD_RFINTERFACEOE; + + /* Addr of LSSI. Wirte RF register by driver */ + rtlphy->phyreg_def[RF90_PATH_A].rf3wire_offset = + RFPGA0_XA_LSSIPARAMETER; + rtlphy->phyreg_def[RF90_PATH_B].rf3wire_offset = + RFPGA0_XB_LSSIPARAMETER; + rtlphy->phyreg_def[RF90_PATH_C].rf3wire_offset = + RFPGA0_XC_LSSIPARAMETER; + rtlphy->phyreg_def[RF90_PATH_D].rf3wire_offset = + RFPGA0_XD_LSSIPARAMETER; + + /* RF parameter */ + rtlphy->phyreg_def[RF90_PATH_A].rflssi_select = RFPGA0_XAB_RFPARAMETER; + rtlphy->phyreg_def[RF90_PATH_B].rflssi_select = RFPGA0_XAB_RFPARAMETER; + rtlphy->phyreg_def[RF90_PATH_C].rflssi_select = RFPGA0_XCD_RFPARAMETER; + rtlphy->phyreg_def[RF90_PATH_D].rflssi_select = RFPGA0_XCD_RFPARAMETER; + + /* Tx AGC Gain Stage (same for all path. Should we remove this?) */ + rtlphy->phyreg_def[RF90_PATH_A].rftxgain_stage = RFPGA0_TXGAINSTAGE; + rtlphy->phyreg_def[RF90_PATH_B].rftxgain_stage = RFPGA0_TXGAINSTAGE; + rtlphy->phyreg_def[RF90_PATH_C].rftxgain_stage = RFPGA0_TXGAINSTAGE; + rtlphy->phyreg_def[RF90_PATH_D].rftxgain_stage = RFPGA0_TXGAINSTAGE; + + /* Tranceiver A~D HSSI Parameter-1 */ + rtlphy->phyreg_def[RF90_PATH_A].rfhssi_para1 = RFPGA0_XA_HSSIPARAMETER1; + rtlphy->phyreg_def[RF90_PATH_B].rfhssi_para1 = RFPGA0_XB_HSSIPARAMETER1; + rtlphy->phyreg_def[RF90_PATH_C].rfhssi_para1 = RFPGA0_XC_HSSIPARAMETER1; + rtlphy->phyreg_def[RF90_PATH_D].rfhssi_para1 = RFPGA0_XD_HSSIPARAMETER1; + + /* Tranceiver A~D HSSI Parameter-2 */ + rtlphy->phyreg_def[RF90_PATH_A].rfhssi_para2 = RFPGA0_XA_HSSIPARAMETER2; + rtlphy->phyreg_def[RF90_PATH_B].rfhssi_para2 = RFPGA0_XB_HSSIPARAMETER2; + rtlphy->phyreg_def[RF90_PATH_C].rfhssi_para2 = RFPGA0_XC_HSSIPARAMETER2; + rtlphy->phyreg_def[RF90_PATH_D].rfhssi_para2 = RFPGA0_XD_HSSIPARAMETER2; + + /* RF switch Control */ + rtlphy->phyreg_def[RF90_PATH_A].rfsw_ctrl = RFPGA0_XAB_SWITCHCONTROL; + rtlphy->phyreg_def[RF90_PATH_B].rfsw_ctrl = RFPGA0_XAB_SWITCHCONTROL; + rtlphy->phyreg_def[RF90_PATH_C].rfsw_ctrl = RFPGA0_XCD_SWITCHCONTROL; + rtlphy->phyreg_def[RF90_PATH_D].rfsw_ctrl = RFPGA0_XCD_SWITCHCONTROL; + + /* AGC control 1 */ + rtlphy->phyreg_def[RF90_PATH_A].rfagc_control1 = ROFDM0_XAAGCCORE1; + rtlphy->phyreg_def[RF90_PATH_B].rfagc_control1 = ROFDM0_XBAGCCORE1; + rtlphy->phyreg_def[RF90_PATH_C].rfagc_control1 = ROFDM0_XCAGCCORE1; + rtlphy->phyreg_def[RF90_PATH_D].rfagc_control1 = ROFDM0_XDAGCCORE1; + + /* AGC control 2 */ + rtlphy->phyreg_def[RF90_PATH_A].rfagc_control2 = ROFDM0_XAAGCCORE2; + rtlphy->phyreg_def[RF90_PATH_B].rfagc_control2 = ROFDM0_XBAGCCORE2; + rtlphy->phyreg_def[RF90_PATH_C].rfagc_control2 = ROFDM0_XCAGCCORE2; + rtlphy->phyreg_def[RF90_PATH_D].rfagc_control2 = ROFDM0_XDAGCCORE2; + + /* RX AFE control 1 */ + rtlphy->phyreg_def[RF90_PATH_A].rfrxiq_imbal = ROFDM0_XARXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_B].rfrxiq_imbal = ROFDM0_XBRXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_C].rfrxiq_imbal = ROFDM0_XCRXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_D].rfrxiq_imbal = ROFDM0_XDRXIQIMBALANCE; + + /* RX AFE control 1 */ + rtlphy->phyreg_def[RF90_PATH_A].rfrx_afe = ROFDM0_XARXAFE; + rtlphy->phyreg_def[RF90_PATH_B].rfrx_afe = ROFDM0_XBRXAFE; + rtlphy->phyreg_def[RF90_PATH_C].rfrx_afe = ROFDM0_XCRXAFE; + rtlphy->phyreg_def[RF90_PATH_D].rfrx_afe = ROFDM0_XDRXAFE; + + /* Tx AFE control 1 */ + rtlphy->phyreg_def[RF90_PATH_A].rftxiq_imbal = ROFDM0_XATXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_B].rftxiq_imbal = ROFDM0_XBTXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_C].rftxiq_imbal = ROFDM0_XCTXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_D].rftxiq_imbal = ROFDM0_XDTXIQIMBALANCE; + + /* Tx AFE control 2 */ + rtlphy->phyreg_def[RF90_PATH_A].rftx_afe = ROFDM0_XATXAFE; + rtlphy->phyreg_def[RF90_PATH_B].rftx_afe = ROFDM0_XBTXAFE; + rtlphy->phyreg_def[RF90_PATH_C].rftx_afe = ROFDM0_XCTXAFE; + rtlphy->phyreg_def[RF90_PATH_D].rftx_afe = ROFDM0_XDTXAFE; + + /* Tranceiver LSSI Readback */ + rtlphy->phyreg_def[RF90_PATH_A].rf_rb = RFPGA0_XA_LSSIREADBACK; + rtlphy->phyreg_def[RF90_PATH_B].rf_rb = RFPGA0_XB_LSSIREADBACK; + rtlphy->phyreg_def[RF90_PATH_C].rf_rb = RFPGA0_XC_LSSIREADBACK; + rtlphy->phyreg_def[RF90_PATH_D].rf_rb = RFPGA0_XD_LSSIREADBACK; + + /* Tranceiver LSSI Readback PI mode */ + rtlphy->phyreg_def[RF90_PATH_A].rf_rbpi = TRANSCEIVERA_HSPI_READBACK; + rtlphy->phyreg_def[RF90_PATH_B].rf_rbpi = TRANSCEIVERB_HSPI_READBACK; +} + + +static bool _rtl92s_phy_config_bb(struct ieee80211_hw *hw, u8 configtype) +{ + int i; + u32 *phy_reg_table; + u32 *agc_table; + u16 phy_reg_len, agc_len; + + agc_len = AGCTAB_ARRAYLENGTH; + agc_table = rtl8192seagctab_array; + /* Default RF_type: 2T2R */ + phy_reg_len = PHY_REG_2T2RARRAYLENGTH; + phy_reg_table = rtl8192sephy_reg_2t2rarray; + + if (configtype == BASEBAND_CONFIG_PHY_REG) { + for (i = 0; i < phy_reg_len; i = i + 2) { + if (phy_reg_table[i] == 0xfe) + mdelay(50); + else if (phy_reg_table[i] == 0xfd) + mdelay(5); + else if (phy_reg_table[i] == 0xfc) + mdelay(1); + else if (phy_reg_table[i] == 0xfb) + udelay(50); + else if (phy_reg_table[i] == 0xfa) + udelay(5); + else if (phy_reg_table[i] == 0xf9) + udelay(1); + + /* Add delay for ECS T20 & LG malow platform, */ + udelay(1); + + rtl92s_phy_set_bb_reg(hw, phy_reg_table[i], MASKDWORD, + phy_reg_table[i + 1]); + } + } else if (configtype == BASEBAND_CONFIG_AGC_TAB) { + for (i = 0; i < agc_len; i = i + 2) { + rtl92s_phy_set_bb_reg(hw, agc_table[i], MASKDWORD, + agc_table[i + 1]); + + /* Add delay for ECS T20 & LG malow platform */ + udelay(1); + } + } + + return true; +} + +static bool _rtl92s_phy_set_bb_to_diff_rf(struct ieee80211_hw *hw, + u8 configtype) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 *phy_regarray2xtxr_table; + u16 phy_regarray2xtxr_len; + int i; + + if (rtlphy->rf_type == RF_1T1R) { + phy_regarray2xtxr_table = rtl8192sephy_changeto_1t1rarray; + phy_regarray2xtxr_len = PHY_CHANGETO_1T1RARRAYLENGTH; + } else if (rtlphy->rf_type == RF_1T2R) { + phy_regarray2xtxr_table = rtl8192sephy_changeto_1t2rarray; + phy_regarray2xtxr_len = PHY_CHANGETO_1T2RARRAYLENGTH; + } else { + return false; + } + + if (configtype == BASEBAND_CONFIG_PHY_REG) { + for (i = 0; i < phy_regarray2xtxr_len; i = i + 3) { + if (phy_regarray2xtxr_table[i] == 0xfe) + mdelay(50); + else if (phy_regarray2xtxr_table[i] == 0xfd) + mdelay(5); + else if (phy_regarray2xtxr_table[i] == 0xfc) + mdelay(1); + else if (phy_regarray2xtxr_table[i] == 0xfb) + udelay(50); + else if (phy_regarray2xtxr_table[i] == 0xfa) + udelay(5); + else if (phy_regarray2xtxr_table[i] == 0xf9) + udelay(1); + + rtl92s_phy_set_bb_reg(hw, phy_regarray2xtxr_table[i], + phy_regarray2xtxr_table[i + 1], + phy_regarray2xtxr_table[i + 2]); + } + } + + return true; +} + +static bool _rtl92s_phy_config_bb_with_pg(struct ieee80211_hw *hw, + u8 configtype) +{ + int i; + u32 *phy_table_pg; + u16 phy_pg_len; + + phy_pg_len = PHY_REG_ARRAY_PGLENGTH; + phy_table_pg = rtl8192sephy_reg_array_pg; + + if (configtype == BASEBAND_CONFIG_PHY_REG) { + for (i = 0; i < phy_pg_len; i = i + 3) { + if (phy_table_pg[i] == 0xfe) + mdelay(50); + else if (phy_table_pg[i] == 0xfd) + mdelay(5); + else if (phy_table_pg[i] == 0xfc) + mdelay(1); + else if (phy_table_pg[i] == 0xfb) + udelay(50); + else if (phy_table_pg[i] == 0xfa) + udelay(5); + else if (phy_table_pg[i] == 0xf9) + udelay(1); + + _rtl92s_store_pwrindex_diffrate_offset(hw, + phy_table_pg[i], + phy_table_pg[i + 1], + phy_table_pg[i + 2]); + rtl92s_phy_set_bb_reg(hw, phy_table_pg[i], + phy_table_pg[i + 1], + phy_table_pg[i + 2]); + } + } + + return true; +} + +static bool _rtl92s_phy_bb_config_parafile(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + bool rtstatus = true; + + /* 1. Read PHY_REG.TXT BB INIT!! */ + /* We will separate as 1T1R/1T2R/1T2R_GREEN/2T2R */ + if (rtlphy->rf_type == RF_1T2R || rtlphy->rf_type == RF_2T2R || + rtlphy->rf_type == RF_1T1R || rtlphy->rf_type == RF_2T2R_GREEN) { + rtstatus = _rtl92s_phy_config_bb(hw, BASEBAND_CONFIG_PHY_REG); + + if (rtlphy->rf_type != RF_2T2R && + rtlphy->rf_type != RF_2T2R_GREEN) + /* so we should reconfig BB reg with the right + * PHY parameters. */ + rtstatus = _rtl92s_phy_set_bb_to_diff_rf(hw, + BASEBAND_CONFIG_PHY_REG); + } else { + rtstatus = false; + } + + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_EMERG, + "Write BB Reg Fail!!\n"); + goto phy_BB8190_Config_ParaFile_Fail; + } + + /* 2. If EEPROM or EFUSE autoload OK, We must config by + * PHY_REG_PG.txt */ + if (rtlefuse->autoload_failflag == false) { + rtlphy->pwrgroup_cnt = 0; + + rtstatus = _rtl92s_phy_config_bb_with_pg(hw, + BASEBAND_CONFIG_PHY_REG); + } + if (!rtstatus) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_EMERG, + "_rtl92s_phy_bb_config_parafile(): BB_PG Reg Fail!!\n"); + goto phy_BB8190_Config_ParaFile_Fail; + } + + /* 3. BB AGC table Initialization */ + rtstatus = _rtl92s_phy_config_bb(hw, BASEBAND_CONFIG_AGC_TAB); + + if (!rtstatus) { + pr_err("%s(): AGC Table Fail\n", __func__); + goto phy_BB8190_Config_ParaFile_Fail; + } + + /* Check if the CCK HighPower is turned ON. */ + /* This is used to calculate PWDB. */ + rtlphy->cck_high_power = (bool)(rtl92s_phy_query_bb_reg(hw, + RFPGA0_XA_HSSIPARAMETER2, 0x200)); + +phy_BB8190_Config_ParaFile_Fail: + return rtstatus; +} + +u8 rtl92s_phy_config_rf(struct ieee80211_hw *hw, enum radio_path rfpath) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + int i; + bool rtstatus = true; + u32 *radio_a_table; + u32 *radio_b_table; + u16 radio_a_tblen, radio_b_tblen; + + radio_a_tblen = RADIOA_1T_ARRAYLENGTH; + radio_a_table = rtl8192seradioa_1t_array; + + /* Using Green mode array table for RF_2T2R_GREEN */ + if (rtlphy->rf_type == RF_2T2R_GREEN) { + radio_b_table = rtl8192seradiob_gm_array; + radio_b_tblen = RADIOB_GM_ARRAYLENGTH; + } else { + radio_b_table = rtl8192seradiob_array; + radio_b_tblen = RADIOB_ARRAYLENGTH; + } + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Radio No %x\n", rfpath); + rtstatus = true; + + switch (rfpath) { + case RF90_PATH_A: + for (i = 0; i < radio_a_tblen; i = i + 2) { + if (radio_a_table[i] == 0xfe) + /* Delay specific ms. Only RF configuration + * requires delay. */ + mdelay(50); + else if (radio_a_table[i] == 0xfd) + mdelay(5); + else if (radio_a_table[i] == 0xfc) + mdelay(1); + else if (radio_a_table[i] == 0xfb) + udelay(50); + else if (radio_a_table[i] == 0xfa) + udelay(5); + else if (radio_a_table[i] == 0xf9) + udelay(1); + else + rtl92s_phy_set_rf_reg(hw, rfpath, + radio_a_table[i], + MASK20BITS, + radio_a_table[i + 1]); + + /* Add delay for ECS T20 & LG malow platform */ + udelay(1); + } + + /* PA Bias current for inferiority IC */ + _rtl92s_phy_config_rfpa_bias_current(hw, rfpath); + break; + case RF90_PATH_B: + for (i = 0; i < radio_b_tblen; i = i + 2) { + if (radio_b_table[i] == 0xfe) + /* Delay specific ms. Only RF configuration + * requires delay.*/ + mdelay(50); + else if (radio_b_table[i] == 0xfd) + mdelay(5); + else if (radio_b_table[i] == 0xfc) + mdelay(1); + else if (radio_b_table[i] == 0xfb) + udelay(50); + else if (radio_b_table[i] == 0xfa) + udelay(5); + else if (radio_b_table[i] == 0xf9) + udelay(1); + else + rtl92s_phy_set_rf_reg(hw, rfpath, + radio_b_table[i], + MASK20BITS, + radio_b_table[i + 1]); + + /* Add delay for ECS T20 & LG malow platform */ + udelay(1); + } + break; + case RF90_PATH_C: + ; + break; + case RF90_PATH_D: + ; + break; + default: + break; + } + + return rtstatus; +} + + +bool rtl92s_phy_mac_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + u32 arraylength; + u32 *ptraArray; + + arraylength = MAC_2T_ARRAYLENGTH; + ptraArray = rtl8192semac_2t_array; + + for (i = 0; i < arraylength; i = i + 2) + rtl_write_byte(rtlpriv, ptraArray[i], (u8)ptraArray[i + 1]); + + return true; +} + + +bool rtl92s_phy_bb_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + bool rtstatus = true; + u8 pathmap, index, rf_num = 0; + u8 path1, path2; + + _rtl92s_phy_init_register_definition(hw); + + /* Config BB and AGC */ + rtstatus = _rtl92s_phy_bb_config_parafile(hw); + + + /* Check BB/RF confiuration setting. */ + /* We only need to configure RF which is turned on. */ + path1 = (u8)(rtl92s_phy_query_bb_reg(hw, RFPGA0_TXINFO, 0xf)); + mdelay(10); + path2 = (u8)(rtl92s_phy_query_bb_reg(hw, ROFDM0_TRXPATHENABLE, 0xf)); + pathmap = path1 | path2; + + rtlphy->rf_pathmap = pathmap; + for (index = 0; index < 4; index++) { + if ((pathmap >> index) & 0x1) + rf_num++; + } + + if ((rtlphy->rf_type == RF_1T1R && rf_num != 1) || + (rtlphy->rf_type == RF_1T2R && rf_num != 2) || + (rtlphy->rf_type == RF_2T2R && rf_num != 2) || + (rtlphy->rf_type == RF_2T2R_GREEN && rf_num != 2)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_EMERG, + "RF_Type(%x) does not match RF_Num(%x)!!\n", + rtlphy->rf_type, rf_num); + RT_TRACE(rtlpriv, COMP_INIT, DBG_EMERG, + "path1 0x%x, path2 0x%x, pathmap 0x%x\n", + path1, path2, pathmap); + } + + return rtstatus; +} + +bool rtl92s_phy_rf_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + /* Initialize general global value */ + if (rtlphy->rf_type == RF_1T1R) + rtlphy->num_total_rfpath = 1; + else + rtlphy->num_total_rfpath = 2; + + /* Config BB and RF */ + return rtl92s_phy_rf6052_config(hw); +} + +void rtl92s_phy_get_hw_reg_originalvalue(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + /* read rx initial gain */ + rtlphy->default_initialgain[0] = rtl_get_bbreg(hw, + ROFDM0_XAAGCCORE1, MASKBYTE0); + rtlphy->default_initialgain[1] = rtl_get_bbreg(hw, + ROFDM0_XBAGCCORE1, MASKBYTE0); + rtlphy->default_initialgain[2] = rtl_get_bbreg(hw, + ROFDM0_XCAGCCORE1, MASKBYTE0); + rtlphy->default_initialgain[3] = rtl_get_bbreg(hw, + ROFDM0_XDAGCCORE1, MASKBYTE0); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Default initial gain (c50=0x%x, c58=0x%x, c60=0x%x, c68=0x%x)\n", + rtlphy->default_initialgain[0], + rtlphy->default_initialgain[1], + rtlphy->default_initialgain[2], + rtlphy->default_initialgain[3]); + + /* read framesync */ + rtlphy->framesync = rtl_get_bbreg(hw, ROFDM0_RXDETECTOR3, MASKBYTE0); + rtlphy->framesync_c34 = rtl_get_bbreg(hw, ROFDM0_RXDETECTOR2, + MASKDWORD); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "Default framesync (0x%x) = 0x%x\n", + ROFDM0_RXDETECTOR3, rtlphy->framesync); + +} + +static void _rtl92s_phy_get_txpower_index(struct ieee80211_hw *hw, u8 channel, + u8 *cckpowerlevel, u8 *ofdmpowerLevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 index = (channel - 1); + + /* 1. CCK */ + /* RF-A */ + cckpowerlevel[0] = rtlefuse->txpwrlevel_cck[0][index]; + /* RF-B */ + cckpowerlevel[1] = rtlefuse->txpwrlevel_cck[1][index]; + + /* 2. OFDM for 1T or 2T */ + if (rtlphy->rf_type == RF_1T2R || rtlphy->rf_type == RF_1T1R) { + /* Read HT 40 OFDM TX power */ + ofdmpowerLevel[0] = rtlefuse->txpwrlevel_ht40_1s[0][index]; + ofdmpowerLevel[1] = rtlefuse->txpwrlevel_ht40_1s[1][index]; + } else if (rtlphy->rf_type == RF_2T2R) { + /* Read HT 40 OFDM TX power */ + ofdmpowerLevel[0] = rtlefuse->txpwrlevel_ht40_2s[0][index]; + ofdmpowerLevel[1] = rtlefuse->txpwrlevel_ht40_2s[1][index]; + } else { + ofdmpowerLevel[0] = 0; + ofdmpowerLevel[1] = 0; + } +} + +static void _rtl92s_phy_ccxpower_indexcheck(struct ieee80211_hw *hw, + u8 channel, u8 *cckpowerlevel, u8 *ofdmpowerlevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + rtlphy->cur_cck_txpwridx = cckpowerlevel[0]; + rtlphy->cur_ofdm24g_txpwridx = ofdmpowerlevel[0]; +} + +void rtl92s_phy_set_txpower(struct ieee80211_hw *hw, u8 channel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + /* [0]:RF-A, [1]:RF-B */ + u8 cckpowerlevel[2], ofdmpowerLevel[2]; + + if (!rtlefuse->txpwr_fromeprom) + return; + + /* Mainly we use RF-A Tx Power to write the Tx Power registers, + * but the RF-B Tx Power must be calculated by the antenna diff. + * So we have to rewrite Antenna gain offset register here. + * Please refer to BB register 0x80c + * 1. For CCK. + * 2. For OFDM 1T or 2T */ + _rtl92s_phy_get_txpower_index(hw, channel, &cckpowerlevel[0], + &ofdmpowerLevel[0]); + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "Channel-%d, cckPowerLevel (A / B) = 0x%x / 0x%x, ofdmPowerLevel (A / B) = 0x%x / 0x%x\n", + channel, cckpowerlevel[0], cckpowerlevel[1], + ofdmpowerLevel[0], ofdmpowerLevel[1]); + + _rtl92s_phy_ccxpower_indexcheck(hw, channel, &cckpowerlevel[0], + &ofdmpowerLevel[0]); + + rtl92s_phy_rf6052_set_ccktxpower(hw, cckpowerlevel[0]); + rtl92s_phy_rf6052_set_ofdmtxpower(hw, &ofdmpowerLevel[0], channel); + +} + +void rtl92s_phy_chk_fwcmd_iodone(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u16 pollingcnt = 10000; + u32 tmpvalue; + + /* Make sure that CMD IO has be accepted by FW. */ + do { + udelay(10); + + tmpvalue = rtl_read_dword(rtlpriv, WFM5); + if (tmpvalue == 0) + break; + } while (--pollingcnt); + + if (pollingcnt == 0) + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Set FW Cmd fail!!\n"); +} + + +static void _rtl92s_phy_set_fwcmd_io(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 input, current_aid = 0; + + if (is_hal_stop(rtlhal)) + return; + + if (hal_get_firmwareversion(rtlpriv) < 0x34) + goto skip; + /* We re-map RA related CMD IO to combinational ones */ + /* if FW version is v.52 or later. */ + switch (rtlhal->current_fwcmd_io) { + case FW_CMD_RA_REFRESH_N: + rtlhal->current_fwcmd_io = FW_CMD_RA_REFRESH_N_COMB; + break; + case FW_CMD_RA_REFRESH_BG: + rtlhal->current_fwcmd_io = FW_CMD_RA_REFRESH_BG_COMB; + break; + default: + break; + } + +skip: + switch (rtlhal->current_fwcmd_io) { + case FW_CMD_RA_RESET: + RT_TRACE(rtlpriv, COMP_CMD, DBG_DMESG, "FW_CMD_RA_RESET\n"); + rtl_write_dword(rtlpriv, WFM5, FW_RA_RESET); + rtl92s_phy_chk_fwcmd_iodone(hw); + break; + case FW_CMD_RA_ACTIVE: + RT_TRACE(rtlpriv, COMP_CMD, DBG_DMESG, "FW_CMD_RA_ACTIVE\n"); + rtl_write_dword(rtlpriv, WFM5, FW_RA_ACTIVE); + rtl92s_phy_chk_fwcmd_iodone(hw); + break; + case FW_CMD_RA_REFRESH_N: + RT_TRACE(rtlpriv, COMP_CMD, DBG_DMESG, "FW_CMD_RA_REFRESH_N\n"); + input = FW_RA_REFRESH; + rtl_write_dword(rtlpriv, WFM5, input); + rtl92s_phy_chk_fwcmd_iodone(hw); + rtl_write_dword(rtlpriv, WFM5, FW_RA_ENABLE_RSSI_MASK); + rtl92s_phy_chk_fwcmd_iodone(hw); + break; + case FW_CMD_RA_REFRESH_BG: + RT_TRACE(rtlpriv, COMP_CMD, DBG_DMESG, + "FW_CMD_RA_REFRESH_BG\n"); + rtl_write_dword(rtlpriv, WFM5, FW_RA_REFRESH); + rtl92s_phy_chk_fwcmd_iodone(hw); + rtl_write_dword(rtlpriv, WFM5, FW_RA_DISABLE_RSSI_MASK); + rtl92s_phy_chk_fwcmd_iodone(hw); + break; + case FW_CMD_RA_REFRESH_N_COMB: + RT_TRACE(rtlpriv, COMP_CMD, DBG_DMESG, + "FW_CMD_RA_REFRESH_N_COMB\n"); + input = FW_RA_IOT_N_COMB; + rtl_write_dword(rtlpriv, WFM5, input); + rtl92s_phy_chk_fwcmd_iodone(hw); + break; + case FW_CMD_RA_REFRESH_BG_COMB: + RT_TRACE(rtlpriv, COMP_CMD, DBG_DMESG, + "FW_CMD_RA_REFRESH_BG_COMB\n"); + input = FW_RA_IOT_BG_COMB; + rtl_write_dword(rtlpriv, WFM5, input); + rtl92s_phy_chk_fwcmd_iodone(hw); + break; + case FW_CMD_IQK_ENABLE: + RT_TRACE(rtlpriv, COMP_CMD, DBG_DMESG, "FW_CMD_IQK_ENABLE\n"); + rtl_write_dword(rtlpriv, WFM5, FW_IQK_ENABLE); + rtl92s_phy_chk_fwcmd_iodone(hw); + break; + case FW_CMD_PAUSE_DM_BY_SCAN: + /* Lower initial gain */ + rtl_set_bbreg(hw, ROFDM0_XAAGCCORE1, MASKBYTE0, 0x17); + rtl_set_bbreg(hw, ROFDM0_XBAGCCORE1, MASKBYTE0, 0x17); + /* CCA threshold */ + rtl_set_bbreg(hw, RCCK0_CCA, MASKBYTE2, 0x40); + break; + case FW_CMD_RESUME_DM_BY_SCAN: + /* CCA threshold */ + rtl_set_bbreg(hw, RCCK0_CCA, MASKBYTE2, 0xcd); + rtl92s_phy_set_txpower(hw, rtlphy->current_channel); + break; + case FW_CMD_HIGH_PWR_DISABLE: + if (rtlpriv->dm.dm_flag & HAL_DM_HIPWR_DISABLE) + break; + + /* Lower initial gain */ + rtl_set_bbreg(hw, ROFDM0_XAAGCCORE1, MASKBYTE0, 0x17); + rtl_set_bbreg(hw, ROFDM0_XBAGCCORE1, MASKBYTE0, 0x17); + /* CCA threshold */ + rtl_set_bbreg(hw, RCCK0_CCA, MASKBYTE2, 0x40); + break; + case FW_CMD_HIGH_PWR_ENABLE: + if ((rtlpriv->dm.dm_flag & HAL_DM_HIPWR_DISABLE) || + rtlpriv->dm.dynamic_txpower_enable) + break; + + /* CCA threshold */ + rtl_set_bbreg(hw, RCCK0_CCA, MASKBYTE2, 0xcd); + break; + case FW_CMD_LPS_ENTER: + RT_TRACE(rtlpriv, COMP_CMD, DBG_DMESG, "FW_CMD_LPS_ENTER\n"); + current_aid = rtlpriv->mac80211.assoc_id; + rtl_write_dword(rtlpriv, WFM5, (FW_LPS_ENTER | + ((current_aid | 0xc000) << 8))); + rtl92s_phy_chk_fwcmd_iodone(hw); + /* FW set TXOP disable here, so disable EDCA + * turbo mode until driver leave LPS */ + break; + case FW_CMD_LPS_LEAVE: + RT_TRACE(rtlpriv, COMP_CMD, DBG_DMESG, "FW_CMD_LPS_LEAVE\n"); + rtl_write_dword(rtlpriv, WFM5, FW_LPS_LEAVE); + rtl92s_phy_chk_fwcmd_iodone(hw); + break; + case FW_CMD_ADD_A2_ENTRY: + RT_TRACE(rtlpriv, COMP_CMD, DBG_DMESG, "FW_CMD_ADD_A2_ENTRY\n"); + rtl_write_dword(rtlpriv, WFM5, FW_ADD_A2_ENTRY); + rtl92s_phy_chk_fwcmd_iodone(hw); + break; + case FW_CMD_CTRL_DM_BY_DRIVER: + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "FW_CMD_CTRL_DM_BY_DRIVER\n"); + rtl_write_dword(rtlpriv, WFM5, FW_CTRL_DM_BY_DRIVER); + rtl92s_phy_chk_fwcmd_iodone(hw); + break; + + default: + break; + } + + rtl92s_phy_chk_fwcmd_iodone(hw); + + /* Clear FW CMD operation flag. */ + rtlhal->set_fwcmd_inprogress = false; +} + +bool rtl92s_phy_set_fw_cmd(struct ieee80211_hw *hw, enum fwcmd_iotype fw_cmdio) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *digtable = &rtlpriv->dm_digtable; + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u32 fw_param = FW_CMD_IO_PARA_QUERY(rtlpriv); + u16 fw_cmdmap = FW_CMD_IO_QUERY(rtlpriv); + bool postprocessing = false; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Set FW Cmd(%#x), set_fwcmd_inprogress(%d)\n", + fw_cmdio, rtlhal->set_fwcmd_inprogress); + + do { + /* We re-map to combined FW CMD ones if firmware version */ + /* is v.53 or later. */ + if (hal_get_firmwareversion(rtlpriv) >= 0x35) { + switch (fw_cmdio) { + case FW_CMD_RA_REFRESH_N: + fw_cmdio = FW_CMD_RA_REFRESH_N_COMB; + break; + case FW_CMD_RA_REFRESH_BG: + fw_cmdio = FW_CMD_RA_REFRESH_BG_COMB; + break; + default: + break; + } + } else { + if ((fw_cmdio == FW_CMD_IQK_ENABLE) || + (fw_cmdio == FW_CMD_RA_REFRESH_N) || + (fw_cmdio == FW_CMD_RA_REFRESH_BG)) { + postprocessing = true; + break; + } + } + + /* If firmware version is v.62 or later, + * use FW_CMD_IO_SET for FW_CMD_CTRL_DM_BY_DRIVER */ + if (hal_get_firmwareversion(rtlpriv) >= 0x3E) { + if (fw_cmdio == FW_CMD_CTRL_DM_BY_DRIVER) + fw_cmdio = FW_CMD_CTRL_DM_BY_DRIVER_NEW; + } + + + /* We shall revise all FW Cmd IO into Reg0x364 + * DM map table in the future. */ + switch (fw_cmdio) { + case FW_CMD_RA_INIT: + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "RA init!!\n"); + fw_cmdmap |= FW_RA_INIT_CTL; + FW_CMD_IO_SET(rtlpriv, fw_cmdmap); + /* Clear control flag to sync with FW. */ + FW_CMD_IO_CLR(rtlpriv, FW_RA_INIT_CTL); + break; + case FW_CMD_DIG_DISABLE: + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Set DIG disable!!\n"); + fw_cmdmap &= ~FW_DIG_ENABLE_CTL; + FW_CMD_IO_SET(rtlpriv, fw_cmdmap); + break; + case FW_CMD_DIG_ENABLE: + case FW_CMD_DIG_RESUME: + if (!(rtlpriv->dm.dm_flag & HAL_DM_DIG_DISABLE)) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Set DIG enable or resume!!\n"); + fw_cmdmap |= (FW_DIG_ENABLE_CTL | FW_SS_CTL); + FW_CMD_IO_SET(rtlpriv, fw_cmdmap); + } + break; + case FW_CMD_DIG_HALT: + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Set DIG halt!!\n"); + fw_cmdmap &= ~(FW_DIG_ENABLE_CTL | FW_SS_CTL); + FW_CMD_IO_SET(rtlpriv, fw_cmdmap); + break; + case FW_CMD_TXPWR_TRACK_THERMAL: { + u8 thermalval = 0; + fw_cmdmap |= FW_PWR_TRK_CTL; + + /* Clear FW parameter in terms of thermal parts. */ + fw_param &= FW_PWR_TRK_PARAM_CLR; + + thermalval = rtlpriv->dm.thermalvalue; + fw_param |= ((thermalval << 24) | + (rtlefuse->thermalmeter[0] << 16)); + + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Set TxPwr tracking!! FwCmdMap(%#x), FwParam(%#x)\n", + fw_cmdmap, fw_param); + + FW_CMD_PARA_SET(rtlpriv, fw_param); + FW_CMD_IO_SET(rtlpriv, fw_cmdmap); + + /* Clear control flag to sync with FW. */ + FW_CMD_IO_CLR(rtlpriv, FW_PWR_TRK_CTL); + } + break; + /* The following FW CMDs are only compatible to + * v.53 or later. */ + case FW_CMD_RA_REFRESH_N_COMB: + fw_cmdmap |= FW_RA_N_CTL; + + /* Clear RA BG mode control. */ + fw_cmdmap &= ~(FW_RA_BG_CTL | FW_RA_INIT_CTL); + + /* Clear FW parameter in terms of RA parts. */ + fw_param &= FW_RA_PARAM_CLR; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "[FW CMD] [New Version] Set RA/IOT Comb in n mode!! FwCmdMap(%#x), FwParam(%#x)\n", + fw_cmdmap, fw_param); + + FW_CMD_PARA_SET(rtlpriv, fw_param); + FW_CMD_IO_SET(rtlpriv, fw_cmdmap); + + /* Clear control flag to sync with FW. */ + FW_CMD_IO_CLR(rtlpriv, FW_RA_N_CTL); + break; + case FW_CMD_RA_REFRESH_BG_COMB: + fw_cmdmap |= FW_RA_BG_CTL; + + /* Clear RA n-mode control. */ + fw_cmdmap &= ~(FW_RA_N_CTL | FW_RA_INIT_CTL); + /* Clear FW parameter in terms of RA parts. */ + fw_param &= FW_RA_PARAM_CLR; + + FW_CMD_PARA_SET(rtlpriv, fw_param); + FW_CMD_IO_SET(rtlpriv, fw_cmdmap); + + /* Clear control flag to sync with FW. */ + FW_CMD_IO_CLR(rtlpriv, FW_RA_BG_CTL); + break; + case FW_CMD_IQK_ENABLE: + fw_cmdmap |= FW_IQK_CTL; + FW_CMD_IO_SET(rtlpriv, fw_cmdmap); + /* Clear control flag to sync with FW. */ + FW_CMD_IO_CLR(rtlpriv, FW_IQK_CTL); + break; + /* The following FW CMD is compatible to v.62 or later. */ + case FW_CMD_CTRL_DM_BY_DRIVER_NEW: + fw_cmdmap |= FW_DRIVER_CTRL_DM_CTL; + FW_CMD_IO_SET(rtlpriv, fw_cmdmap); + break; + /* The followed FW Cmds needs post-processing later. */ + case FW_CMD_RESUME_DM_BY_SCAN: + fw_cmdmap |= (FW_DIG_ENABLE_CTL | + FW_HIGH_PWR_ENABLE_CTL | + FW_SS_CTL); + + if (rtlpriv->dm.dm_flag & HAL_DM_DIG_DISABLE || + !digtable->dig_enable_flag) + fw_cmdmap &= ~FW_DIG_ENABLE_CTL; + + if ((rtlpriv->dm.dm_flag & HAL_DM_HIPWR_DISABLE) || + rtlpriv->dm.dynamic_txpower_enable) + fw_cmdmap &= ~FW_HIGH_PWR_ENABLE_CTL; + + if ((digtable->dig_ext_port_stage == + DIG_EXT_PORT_STAGE_0) || + (digtable->dig_ext_port_stage == + DIG_EXT_PORT_STAGE_1)) + fw_cmdmap &= ~FW_DIG_ENABLE_CTL; + + FW_CMD_IO_SET(rtlpriv, fw_cmdmap); + postprocessing = true; + break; + case FW_CMD_PAUSE_DM_BY_SCAN: + fw_cmdmap &= ~(FW_DIG_ENABLE_CTL | + FW_HIGH_PWR_ENABLE_CTL | + FW_SS_CTL); + FW_CMD_IO_SET(rtlpriv, fw_cmdmap); + postprocessing = true; + break; + case FW_CMD_HIGH_PWR_DISABLE: + fw_cmdmap &= ~FW_HIGH_PWR_ENABLE_CTL; + FW_CMD_IO_SET(rtlpriv, fw_cmdmap); + postprocessing = true; + break; + case FW_CMD_HIGH_PWR_ENABLE: + if (!(rtlpriv->dm.dm_flag & HAL_DM_HIPWR_DISABLE) && + !rtlpriv->dm.dynamic_txpower_enable) { + fw_cmdmap |= (FW_HIGH_PWR_ENABLE_CTL | + FW_SS_CTL); + FW_CMD_IO_SET(rtlpriv, fw_cmdmap); + postprocessing = true; + } + break; + case FW_CMD_DIG_MODE_FA: + fw_cmdmap |= FW_FA_CTL; + FW_CMD_IO_SET(rtlpriv, fw_cmdmap); + break; + case FW_CMD_DIG_MODE_SS: + fw_cmdmap &= ~FW_FA_CTL; + FW_CMD_IO_SET(rtlpriv, fw_cmdmap); + break; + case FW_CMD_PAPE_CONTROL: + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "[FW CMD] Set PAPE Control\n"); + fw_cmdmap &= ~FW_PAPE_CTL_BY_SW_HW; + + FW_CMD_IO_SET(rtlpriv, fw_cmdmap); + break; + default: + /* Pass to original FW CMD processing callback + * routine. */ + postprocessing = true; + break; + } + } while (false); + + /* We shall post processing these FW CMD if + * variable postprocessing is set. + */ + if (postprocessing && !rtlhal->set_fwcmd_inprogress) { + rtlhal->set_fwcmd_inprogress = true; + /* Update current FW Cmd for callback use. */ + rtlhal->current_fwcmd_io = fw_cmdio; + } else { + return false; + } + + _rtl92s_phy_set_fwcmd_io(hw); + return true; +} + +static void _rtl92s_phy_check_ephy_switchready(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 delay = 100; + u8 regu1; + + regu1 = rtl_read_byte(rtlpriv, 0x554); + while ((regu1 & BIT(5)) && (delay > 0)) { + regu1 = rtl_read_byte(rtlpriv, 0x554); + delay--; + /* We delay only 50us to prevent + * being scheduled out. */ + udelay(50); + } +} + +void rtl92s_phy_switch_ephy_parameter(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + /* The way to be capable to switch clock request + * when the PG setting does not support clock request. + * This is the backdoor solution to switch clock + * request before ASPM or D3. */ + rtl_write_dword(rtlpriv, 0x540, 0x73c11); + rtl_write_dword(rtlpriv, 0x548, 0x2407c); + + /* Switch EPHY parameter!!!! */ + rtl_write_word(rtlpriv, 0x550, 0x1000); + rtl_write_byte(rtlpriv, 0x554, 0x20); + _rtl92s_phy_check_ephy_switchready(hw); + + rtl_write_word(rtlpriv, 0x550, 0xa0eb); + rtl_write_byte(rtlpriv, 0x554, 0x3e); + _rtl92s_phy_check_ephy_switchready(hw); + + rtl_write_word(rtlpriv, 0x550, 0xff80); + rtl_write_byte(rtlpriv, 0x554, 0x39); + _rtl92s_phy_check_ephy_switchready(hw); + + /* Delay L1 enter time */ + if (ppsc->support_aspm && !ppsc->support_backdoor) + rtl_write_byte(rtlpriv, 0x560, 0x40); + else + rtl_write_byte(rtlpriv, 0x560, 0x00); + +} + +void rtl92s_phy_set_beacon_hwreg(struct ieee80211_hw *hw, u16 beaconinterval) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 new_bcn_num = 0; + + if (hal_get_firmwareversion(rtlpriv) >= 0x33) { + /* Fw v.51 and later. */ + rtl_write_dword(rtlpriv, WFM5, 0xF1000000 | + (beaconinterval << 8)); + } else { + new_bcn_num = beaconinterval * 32 - 64; + rtl_write_dword(rtlpriv, WFM3 + 4, new_bcn_num); + rtl_write_dword(rtlpriv, WFM3, 0xB026007C); + } +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/phy.h b/drivers/net/wireless/rtlwifi/rtl8192se/phy.h new file mode 100644 index 0000000..8acf476 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/phy.h @@ -0,0 +1,102 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ +#ifndef __RTL92S_PHY_H__ +#define __RTL92S_PHY_H__ + +#define MAX_TXPWR_IDX_NMODE_92S 63 +#define MAX_DOZE_WAITING_TIMES_9x 64 + +/* Channel switch:The size of + * command tables for switch channel */ +#define MAX_PRECMD_CNT 16 +#define MAX_RFDEPENDCMD_CNT 16 +#define MAX_POSTCMD_CNT 16 + +#define RF90_PATH_MAX 4 +#define RF6052_MAX_PATH 2 + +enum version_8192s { + VERSION_8192S_ACUT, + VERSION_8192S_BCUT, + VERSION_8192S_CCUT +}; + +enum swchnlcmd_id { + CMDID_END, + CMDID_SET_TXPOWEROWER_LEVEL, + CMDID_BBREGWRITE10, + CMDID_WRITEPORT_ULONG, + CMDID_WRITEPORT_USHORT, + CMDID_WRITEPORT_UCHAR, + CMDID_RF_WRITEREG, +}; + +struct swchnlcmd { + enum swchnlcmd_id cmdid; + u32 para1; + u32 para2; + u32 msdelay; +}; + +enum baseband_config_type { + /* Radio Path A */ + BASEBAND_CONFIG_PHY_REG = 0, + /* Radio Path B */ + BASEBAND_CONFIG_AGC_TAB = 1, +}; + +#define hal_get_firmwareversion(rtlpriv) \ + (((struct rt_firmware *)(rtlpriv->rtlhal.pfirmware))->firmwareversion) + +u32 rtl92s_phy_query_bb_reg(struct ieee80211_hw *hw, u32 regaddr, u32 bitmask); +void rtl92s_phy_set_bb_reg(struct ieee80211_hw *hw, u32 regaddr, u32 bitmask, + u32 data); +void rtl92s_phy_scan_operation_backup(struct ieee80211_hw *hw, u8 operation); +u32 rtl92s_phy_query_rf_reg(struct ieee80211_hw *hw, enum radio_path rfpath, + u32 regaddr, u32 bitmask); +void rtl92s_phy_set_rf_reg(struct ieee80211_hw *hw, enum radio_path rfpath, + u32 regaddr, u32 bitmask, u32 data); +void rtl92s_phy_set_bw_mode(struct ieee80211_hw *hw, + enum nl80211_channel_type ch_type); +u8 rtl92s_phy_sw_chnl(struct ieee80211_hw *hw); +bool rtl92s_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpower_state); +bool rtl92s_phy_mac_config(struct ieee80211_hw *hw); +void rtl92s_phy_switch_ephy_parameter(struct ieee80211_hw *hw); +bool rtl92s_phy_bb_config(struct ieee80211_hw *hw); +bool rtl92s_phy_rf_config(struct ieee80211_hw *hw); +void rtl92s_phy_get_hw_reg_originalvalue(struct ieee80211_hw *hw); +void rtl92s_phy_set_txpower(struct ieee80211_hw *hw, u8 channel); +bool rtl92s_phy_set_fw_cmd(struct ieee80211_hw *hw, enum fwcmd_iotype fwcmd_io); +void rtl92s_phy_chk_fwcmd_iodone(struct ieee80211_hw *hw); +void rtl92s_phy_set_beacon_hwreg(struct ieee80211_hw *hw, u16 beaconinterval); +u8 rtl92s_phy_config_rf(struct ieee80211_hw *hw, enum radio_path rfpath) ; + +#endif + diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/reg.h b/drivers/net/wireless/rtlwifi/rtl8192se/reg.h new file mode 100644 index 0000000..84d1181 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/reg.h @@ -0,0 +1,1185 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ +#ifndef __REALTEK_92S_REG_H__ +#define __REALTEK_92S_REG_H__ + +/* 1. System Configuration Registers */ +#define REG_SYS_ISO_CTRL 0x0000 +#define REG_SYS_FUNC_EN 0x0002 +#define PMC_FSM 0x0004 +#define SYS_CLKR 0x0008 +#define EPROM_CMD 0x000A +#define EE_VPD 0x000C +#define AFE_MISC 0x0010 +#define SPS0_CTRL 0x0011 +#define SPS1_CTRL 0x0018 +#define RF_CTRL 0x001F +#define LDOA15_CTRL 0x0020 +#define LDOV12D_CTRL 0x0021 +#define LDOHCI12_CTRL 0x0022 +#define LDO_USB_SDIO 0x0023 +#define LPLDO_CTRL 0x0024 +#define AFE_XTAL_CTRL 0x0026 +#define AFE_PLL_CTRL 0x0028 +#define REG_EFUSE_CTRL 0x0030 +#define REG_EFUSE_TEST 0x0034 +#define PWR_DATA 0x0038 +#define DBG_PORT 0x003A +#define DPS_TIMER 0x003C +#define RCLK_MON 0x003E + +/* 2. Command Control Registers */ +#define CMDR 0x0040 +#define TXPAUSE 0x0042 +#define LBKMD_SEL 0x0043 +#define TCR 0x0044 +#define RCR 0x0048 +#define MSR 0x004C +#define SYSF_CFG 0x004D +#define RX_PKY_LIMIT 0x004E +#define MBIDCTRL 0x004F + +/* 3. MACID Setting Registers */ +#define MACIDR 0x0050 +#define MACIDR0 0x0050 +#define MACIDR4 0x0054 +#define BSSIDR 0x0058 +#define HWVID 0x005E +#define MAR 0x0060 +#define MBIDCAMCONTENT 0x0068 +#define MBIDCAMCFG 0x0070 +#define BUILDTIME 0x0074 +#define BUILDUSER 0x0078 + +#define IDR0 MACIDR0 +#define IDR4 MACIDR4 + +/* 4. Timing Control Registers */ +#define TSFR 0x0080 +#define SLOT_TIME 0x0089 +#define USTIME 0x008A +#define SIFS_CCK 0x008C +#define SIFS_OFDM 0x008E +#define PIFS_TIME 0x0090 +#define ACK_TIMEOUT 0x0091 +#define EIFSTR 0x0092 +#define BCN_INTERVAL 0x0094 +#define ATIMWND 0x0096 +#define BCN_DRV_EARLY_INT 0x0098 +#define BCN_DMATIME 0x009A +#define BCN_ERR_THRESH 0x009C +#define MLT 0x009D +#define RSVD_MAC_TUNE_US 0x009E + +/* 5. FIFO Control Registers */ +#define RQPN 0x00A0 +#define RQPN1 0x00A0 +#define RQPN2 0x00A1 +#define RQPN3 0x00A2 +#define RQPN4 0x00A3 +#define RQPN5 0x00A4 +#define RQPN6 0x00A5 +#define RQPN7 0x00A6 +#define RQPN8 0x00A7 +#define RQPN9 0x00A8 +#define RQPN10 0x00A9 +#define LD_RQPN 0x00AB +#define RXFF_BNDY 0x00AC +#define RXRPT_BNDY 0x00B0 +#define TXPKTBUF_PGBNDY 0x00B4 +#define PBP 0x00B5 +#define RXDRVINFO_SZ 0x00B6 +#define TXFF_STATUS 0x00B7 +#define RXFF_STATUS 0x00B8 +#define TXFF_EMPTY_TH 0x00B9 +#define SDIO_RX_BLKSZ 0x00BC +#define RXDMA 0x00BD +#define RXPKT_NUM 0x00BE +#define C2HCMD_UDT_SIZE 0x00C0 +#define C2HCMD_UDT_ADDR 0x00C2 +#define FIFOPAGE1 0x00C4 +#define FIFOPAGE2 0x00C8 +#define FIFOPAGE3 0x00CC +#define FIFOPAGE4 0x00D0 +#define FIFOPAGE5 0x00D4 +#define FW_RSVD_PG_CRTL 0x00D8 +#define RXDMA_AGG_PG_TH 0x00D9 +#define TXDESC_MSK 0x00DC +#define TXRPTFF_RDPTR 0x00E0 +#define TXRPTFF_WTPTR 0x00E4 +#define C2HFF_RDPTR 0x00E8 +#define C2HFF_WTPTR 0x00EC +#define RXFF0_RDPTR 0x00F0 +#define RXFF0_WTPTR 0x00F4 +#define RXFF1_RDPTR 0x00F8 +#define RXFF1_WTPTR 0x00FC +#define RXRPT0_RDPTR 0x0100 +#define RXRPT0_WTPTR 0x0104 +#define RXRPT1_RDPTR 0x0108 +#define RXRPT1_WTPTR 0x010C +#define RX0_UDT_SIZE 0x0110 +#define RX1PKTNUM 0x0114 +#define RXFILTERMAP 0x0116 +#define RXFILTERMAP_GP1 0x0118 +#define RXFILTERMAP_GP2 0x011A +#define RXFILTERMAP_GP3 0x011C +#define BCNQ_CTRL 0x0120 +#define MGTQ_CTRL 0x0124 +#define HIQ_CTRL 0x0128 +#define VOTID7_CTRL 0x012c +#define VOTID6_CTRL 0x0130 +#define VITID5_CTRL 0x0134 +#define VITID4_CTRL 0x0138 +#define BETID3_CTRL 0x013c +#define BETID0_CTRL 0x0140 +#define BKTID2_CTRL 0x0144 +#define BKTID1_CTRL 0x0148 +#define CMDQ_CTRL 0x014c +#define TXPKT_NUM_CTRL 0x0150 +#define TXQ_PGADD 0x0152 +#define TXFF_PG_NUM 0x0154 +#define TRXDMA_STATUS 0x0156 + +/* 6. Adaptive Control Registers */ +#define INIMCS_SEL 0x0160 +#define TX_RATE_REG INIMCS_SEL +#define INIRTSMCS_SEL 0x0180 +#define RRSR 0x0181 +#define ARFR0 0x0184 +#define ARFR1 0x0188 +#define ARFR2 0x018C +#define ARFR3 0x0190 +#define ARFR4 0x0194 +#define ARFR5 0x0198 +#define ARFR6 0x019C +#define ARFR7 0x01A0 +#define AGGLEN_LMT_H 0x01A7 +#define AGGLEN_LMT_L 0x01A8 +#define DARFRC 0x01B0 +#define RARFRC 0x01B8 +#define MCS_TXAGC 0x01C0 +#define CCK_TXAGC 0x01C8 + +/* 7. EDCA Setting Registers */ +#define EDCAPARA_VO 0x01D0 +#define EDCAPARA_VI 0x01D4 +#define EDCAPARA_BE 0x01D8 +#define EDCAPARA_BK 0x01DC +#define BCNTCFG 0x01E0 +#define CWRR 0x01E2 +#define ACMAVG 0x01E4 +#define AcmHwCtrl 0x01E7 +#define VO_ADMTM 0x01E8 +#define VI_ADMTM 0x01EC +#define BE_ADMTM 0x01F0 +#define RETRY_LIMIT 0x01F4 +#define SG_RATE 0x01F6 + +/* 8. WMAC, BA and CCX related Register. */ +#define NAV_CTRL 0x0200 +#define BW_OPMODE 0x0203 +#define BACAMCMD 0x0204 +#define BACAMCONTENT 0x0208 + +/* the 0x2xx register WMAC definition */ +#define LBDLY 0x0210 +#define FWDLY 0x0211 +#define HWPC_RX_CTRL 0x0218 +#define MQIR 0x0220 +#define MAIR 0x0222 +#define MSIR 0x0224 +#define CLM_RESULT 0x0227 +#define NHM_RPI_CNT 0x0228 +#define RXERR_RPT 0x0230 +#define NAV_PROT_LEN 0x0234 +#define CFEND_TH 0x0236 +#define AMPDU_MIN_SPACE 0x0237 +#define TXOP_STALL_CTRL 0x0238 + +/* 9. Security Control Registers */ +#define REG_RWCAM 0x0240 +#define REG_WCAMI 0x0244 +#define REG_RCAMO 0x0248 +#define REG_CAMDBG 0x024C +#define REG_SECR 0x0250 + +/* 10. Power Save Control Registers */ +#define WOW_CTRL 0x0260 +#define PSSTATUS 0x0261 +#define PSSWITCH 0x0262 +#define MIMOPS_WAIT_PERIOD 0x0263 +#define LPNAV_CTRL 0x0264 +#define WFM0 0x0270 +#define WFM1 0x0280 +#define WFM2 0x0290 +#define WFM3 0x02A0 +#define WFM4 0x02B0 +#define WFM5 0x02C0 +#define WFCRC 0x02D0 +#define FW_RPT_REG 0x02c4 + +/* 11. General Purpose Registers */ +#define PSTIME 0x02E0 +#define TIMER0 0x02E4 +#define TIMER1 0x02E8 +#define GPIO_IN_SE 0x02EC +#define GPIO_IO_SEL 0x02EE +#define MAC_PINMUX_CFG 0x02F1 +#define LEDCFG 0x02F2 +#define PHY_REG 0x02F3 +#define PHY_REG_DATA 0x02F4 +#define REG_EFUSE_CLK 0x02F8 + +/* 12. Host Interrupt Status Registers */ +#define INTA_MASK 0x0300 +#define ISR 0x0308 + +/* 13. Test Mode and Debug Control Registers */ +#define DBG_PORT_SWITCH 0x003A +#define BIST 0x0310 +#define DBS 0x0314 +#define CPUINST 0x0318 +#define CPUCAUSE 0x031C +#define LBUS_ERR_ADDR 0x0320 +#define LBUS_ERR_CMD 0x0324 +#define LBUS_ERR_DATA_L 0x0328 +#define LBUS_ERR_DATA_H 0x032C +#define LX_EXCEPTION_ADDR 0x0330 +#define WDG_CTRL 0x0334 +#define INTMTU 0x0338 +#define INTM 0x033A +#define FDLOCKTURN0 0x033C +#define FDLOCKTURN1 0x033D +#define TRXPKTBUF_DBG_DATA 0x0340 +#define TRXPKTBUF_DBG_CTRL 0x0348 +#define DPLL 0x034A +#define CBUS_ERR_ADDR 0x0350 +#define CBUS_ERR_CMD 0x0354 +#define CBUS_ERR_DATA_L 0x0358 +#define CBUS_ERR_DATA_H 0x035C +#define USB_SIE_INTF_ADDR 0x0360 +#define USB_SIE_INTF_WD 0x0361 +#define USB_SIE_INTF_RD 0x0362 +#define USB_SIE_INTF_CTRL 0x0363 +#define LBUS_MON_ADDR 0x0364 +#define LBUS_ADDR_MASK 0x0368 + +/* Boundary is 0x37F */ + +/* 14. PCIE config register */ +#define TP_POLL 0x0500 +#define PM_CTRL 0x0502 +#define PCIF 0x0503 + +#define THPDA 0x0514 +#define TMDA 0x0518 +#define TCDA 0x051C +#define HDA 0x0520 +#define TVODA 0x0524 +#define TVIDA 0x0528 +#define TBEDA 0x052C +#define TBKDA 0x0530 +#define TBDA 0x0534 +#define RCDA 0x0538 +#define RDQDA 0x053C +#define DBI_WDATA 0x0540 +#define DBI_RDATA 0x0544 +#define DBI_CTRL 0x0548 +#define MDIO_DATA 0x0550 +#define MDIO_CTRL 0x0554 +#define PCI_RPWM 0x0561 +#define PCI_CPWM 0x0563 + +/* Config register (Offset 0x800-) */ +#define PHY_CCA 0x803 + +/* Min Spacing related settings. */ +#define MAX_MSS_DENSITY_2T 0x13 +#define MAX_MSS_DENSITY_1T 0x0A + +/* Rx DMA Control related settings */ +#define RXDMA_AGG_EN BIT(7) + +#define RPWM PCI_RPWM + +/* Regsiter Bit and Content definition */ + +#define ISO_MD2PP BIT(0) +#define ISO_PA2PCIE BIT(3) +#define ISO_PLL2MD BIT(4) +#define ISO_PWC_DV2RP BIT(11) +#define ISO_PWC_RV2RP BIT(12) + + +#define FEN_MREGEN BIT(15) +#define FEN_DCORE BIT(11) +#define FEN_CPUEN BIT(10) + +#define PAD_HWPD_IDN BIT(22) + +#define SYS_CLKSEL_80M BIT(0) +#define SYS_PS_CLKSEL BIT(1) +#define SYS_CPU_CLKSEL BIT(2) +#define SYS_MAC_CLK_EN BIT(11) +#define SYS_SWHW_SEL BIT(14) +#define SYS_FWHW_SEL BIT(15) + +#define CmdEEPROM_En BIT(5) +#define CmdEERPOMSEL BIT(4) +#define Cmd9346CR_9356SEL BIT(4) + +#define AFE_MBEN BIT(1) +#define AFE_BGEN BIT(0) + +#define SPS1_SWEN BIT(1) +#define SPS1_LDEN BIT(0) + +#define RF_EN BIT(0) +#define RF_RSTB BIT(1) +#define RF_SDMRSTB BIT(2) + +#define LDA15_EN BIT(0) + +#define LDV12_EN BIT(0) +#define LDV12_SDBY BIT(1) + +#define XTAL_GATE_AFE BIT(10) + +#define APLL_EN BIT(0) + +#define AFR_CardBEn BIT(0) +#define AFR_CLKRUN_SEL BIT(1) +#define AFR_FuncRegEn BIT(2) + +#define APSDOFF_STATUS BIT(15) +#define APSDOFF BIT(14) +#define BBRSTN BIT(13) +#define BB_GLB_RSTN BIT(12) +#define SCHEDULE_EN BIT(10) +#define MACRXEN BIT(9) +#define MACTXEN BIT(8) +#define DDMA_EN BIT(7) +#define FW2HW_EN BIT(6) +#define RXDMA_EN BIT(5) +#define TXDMA_EN BIT(4) +#define HCI_RXDMA_EN BIT(3) +#define HCI_TXDMA_EN BIT(2) + +#define StopHCCA BIT(6) +#define StopHigh BIT(5) +#define StopMgt BIT(4) +#define StopVO BIT(3) +#define StopVI BIT(2) +#define StopBE BIT(1) +#define StopBK BIT(0) + +#define LBK_NORMAL 0x00 +#define LBK_MAC_LB (BIT(0) | BIT(1) | BIT(3)) +#define LBK_MAC_DLB (BIT(0) | BIT(1)) +#define LBK_DMA_LB (BIT(0) | BIT(1) | BIT(2)) + +#define TCP_OFDL_EN BIT(25) +#define HWPC_TX_EN BIT(24) +#define TXDMAPRE2FULL BIT(23) +#define DISCW BIT(20) +#define TCRICV BIT(19) +#define CfendForm BIT(17) +#define TCRCRC BIT(16) +#define FAKE_IMEM_EN BIT(15) +#define TSFRST BIT(9) +#define TSFEN BIT(8) +#define FWALLRDY (BIT(0) | BIT(1) | BIT(2) | \ + BIT(3) | BIT(4) | BIT(5) | \ + BIT(6) | BIT(7)) +#define FWRDY BIT(7) +#define BASECHG BIT(6) +#define IMEM BIT(5) +#define DMEM_CODE_DONE BIT(4) +#define EXT_IMEM_CHK_RPT BIT(3) +#define EXT_IMEM_CODE_DONE BIT(2) +#define IMEM_CHK_RPT BIT(1) +#define IMEM_CODE_DONE BIT(0) +#define IMEM_CODE_DONE BIT(0) +#define IMEM_CHK_RPT BIT(1) +#define EMEM_CODE_DONE BIT(2) +#define EMEM_CHK_RPT BIT(3) +#define DMEM_CODE_DONE BIT(4) +#define IMEM_RDY BIT(5) +#define BASECHG BIT(6) +#define FWRDY BIT(7) +#define LOAD_FW_READY (IMEM_CODE_DONE | \ + IMEM_CHK_RPT | \ + EMEM_CODE_DONE | \ + EMEM_CHK_RPT | \ + DMEM_CODE_DONE | \ + IMEM_RDY | \ + BASECHG | \ + FWRDY) +#define TCR_TSFEN BIT(8) +#define TCR_TSFRST BIT(9) +#define TCR_FAKE_IMEM_EN BIT(15) +#define TCR_CRC BIT(16) +#define TCR_ICV BIT(19) +#define TCR_DISCW BIT(20) +#define TCR_HWPC_TX_EN BIT(24) +#define TCR_TCP_OFDL_EN BIT(25) +#define TXDMA_INIT_VALUE (IMEM_CHK_RPT | \ + EXT_IMEM_CHK_RPT) + +#define RCR_APPFCS BIT(31) +#define RCR_DIS_ENC_2BYTE BIT(30) +#define RCR_DIS_AES_2BYTE BIT(29) +#define RCR_HTC_LOC_CTRL BIT(28) +#define RCR_ENMBID BIT(27) +#define RCR_RX_TCPOFDL_EN BIT(26) +#define RCR_APP_PHYST_RXFF BIT(25) +#define RCR_APP_PHYST_STAFF BIT(24) +#define RCR_CBSSID BIT(23) +#define RCR_APWRMGT BIT(22) +#define RCR_ADD3 BIT(21) +#define RCR_AMF BIT(20) +#define RCR_ACF BIT(19) +#define RCR_ADF BIT(18) +#define RCR_APP_MIC BIT(17) +#define RCR_APP_ICV BIT(16) +#define RCR_RXFTH BIT(13) +#define RCR_AICV BIT(12) +#define RCR_RXDESC_LK_EN BIT(11) +#define RCR_APP_BA_SSN BIT(6) +#define RCR_ACRC32 BIT(5) +#define RCR_RXSHFT_EN BIT(4) +#define RCR_AB BIT(3) +#define RCR_AM BIT(2) +#define RCR_APM BIT(1) +#define RCR_AAP BIT(0) +#define RCR_MXDMA_OFFSET 8 +#define RCR_FIFO_OFFSET 13 + + +#define MSR_LINK_MASK ((1 << 0) | (1 << 1)) +#define MSR_LINK_MANAGED 2 +#define MSR_LINK_NONE 0 +#define MSR_LINK_SHIFT 0 +#define MSR_LINK_ADHOC 1 +#define MSR_LINK_MASTER 3 +#define MSR_NOLINK 0x00 +#define MSR_ADHOC 0x01 +#define MSR_INFRA 0x02 +#define MSR_AP 0x03 + +#define ENUART BIT(7) +#define ENJTAG BIT(3) +#define BTMODE (BIT(2) | BIT(1)) +#define ENBT BIT(0) + +#define ENMBID BIT(7) +#define BCNUM (BIT(6) | BIT(5) | BIT(4)) + +#define USTIME_EDCA 0xFF00 +#define USTIME_TSF 0x00FF + +#define SIFS_TRX 0xFF00 +#define SIFS_CTX 0x00FF + +#define ENSWBCN BIT(15) +#define DRVERLY_TU 0x0FF0 +#define DRVERLY_US 0x000F +#define BCN_TCFG_CW_SHIFT 8 +#define BCN_TCFG_IFS 0 + +#define RRSR_RSC_OFFSET 21 +#define RRSR_SHORT_OFFSET 23 +#define RRSR_RSC_BW_40M 0x600000 +#define RRSR_RSC_UPSUBCHNL 0x400000 +#define RRSR_RSC_LOWSUBCHNL 0x200000 +#define RRSR_SHORT 0x800000 +#define RRSR_1M BIT(0) +#define RRSR_2M BIT(1) +#define RRSR_5_5M BIT(2) +#define RRSR_11M BIT(3) +#define RRSR_6M BIT(4) +#define RRSR_9M BIT(5) +#define RRSR_12M BIT(6) +#define RRSR_18M BIT(7) +#define RRSR_24M BIT(8) +#define RRSR_36M BIT(9) +#define RRSR_48M BIT(10) +#define RRSR_54M BIT(11) +#define RRSR_MCS0 BIT(12) +#define RRSR_MCS1 BIT(13) +#define RRSR_MCS2 BIT(14) +#define RRSR_MCS3 BIT(15) +#define RRSR_MCS4 BIT(16) +#define RRSR_MCS5 BIT(17) +#define RRSR_MCS6 BIT(18) +#define RRSR_MCS7 BIT(19) +#define BRSR_AckShortPmb BIT(23) + +#define RATR_1M 0x00000001 +#define RATR_2M 0x00000002 +#define RATR_55M 0x00000004 +#define RATR_11M 0x00000008 +#define RATR_6M 0x00000010 +#define RATR_9M 0x00000020 +#define RATR_12M 0x00000040 +#define RATR_18M 0x00000080 +#define RATR_24M 0x00000100 +#define RATR_36M 0x00000200 +#define RATR_48M 0x00000400 +#define RATR_54M 0x00000800 +#define RATR_MCS0 0x00001000 +#define RATR_MCS1 0x00002000 +#define RATR_MCS2 0x00004000 +#define RATR_MCS3 0x00008000 +#define RATR_MCS4 0x00010000 +#define RATR_MCS5 0x00020000 +#define RATR_MCS6 0x00040000 +#define RATR_MCS7 0x00080000 +#define RATR_MCS8 0x00100000 +#define RATR_MCS9 0x00200000 +#define RATR_MCS10 0x00400000 +#define RATR_MCS11 0x00800000 +#define RATR_MCS12 0x01000000 +#define RATR_MCS13 0x02000000 +#define RATR_MCS14 0x04000000 +#define RATR_MCS15 0x08000000 + +#define RATE_ALL_CCK (RATR_1M | RATR_2M | \ + RATR_55M | RATR_11M) +#define RATE_ALL_OFDM_AG (RATR_6M | RATR_9M | \ + RATR_12M | RATR_18M | \ + RATR_24M | RATR_36M | \ + RATR_48M | RATR_54M) +#define RATE_ALL_OFDM_1SS (RATR_MCS0 | RATR_MCS1 | \ + RATR_MCS2 | RATR_MCS3 | \ + RATR_MCS4 | RATR_MCS5 | \ + RATR_MCS6 | RATR_MCS7) +#define RATE_ALL_OFDM_2SS (RATR_MCS8 | RATR_MCS9 | \ + RATR_MCS10 | RATR_MCS11 | \ + RATR_MCS12 | RATR_MCS13 | \ + RATR_MCS14 | RATR_MCS15) + +#define AC_PARAM_TXOP_LIMIT_OFFSET 16 +#define AC_PARAM_ECW_MAX_OFFSET 12 +#define AC_PARAM_ECW_MIN_OFFSET 8 +#define AC_PARAM_AIFS_OFFSET 0 + +#define AcmHw_HwEn BIT(0) +#define AcmHw_BeqEn BIT(1) +#define AcmHw_ViqEn BIT(2) +#define AcmHw_VoqEn BIT(3) +#define AcmHw_BeqStatus BIT(4) +#define AcmHw_ViqStatus BIT(5) +#define AcmHw_VoqStatus BIT(6) + +#define RETRY_LIMIT_SHORT_SHIFT 8 +#define RETRY_LIMIT_LONG_SHIFT 0 + +#define NAV_UPPER_EN BIT(16) +#define NAV_UPPER 0xFF00 +#define NAV_RTSRST 0xFF + +#define BW_OPMODE_20MHZ BIT(2) +#define BW_OPMODE_5G BIT(1) +#define BW_OPMODE_11J BIT(0) + +#define RXERR_RPT_RST BIT(27) +#define RXERR_OFDM_PPDU 0 +#define RXERR_OFDM_FALSE_ALARM 1 +#define RXERR_OFDM_MPDU_OK 2 +#define RXERR_OFDM_MPDU_FAIL 3 +#define RXERR_CCK_PPDU 4 +#define RXERR_CCK_FALSE_ALARM 5 +#define RXERR_CCK_MPDU_OK 6 +#define RXERR_CCK_MPDU_FAIL 7 +#define RXERR_HT_PPDU 8 +#define RXERR_HT_FALSE_ALARM 9 +#define RXERR_HT_MPDU_TOTAL 10 +#define RXERR_HT_MPDU_OK 11 +#define RXERR_HT_MPDU_FAIL 12 +#define RXERR_RX_FULL_DROP 15 + +#define SCR_TXUSEDK BIT(0) +#define SCR_RXUSEDK BIT(1) +#define SCR_TXENCENABLE BIT(2) +#define SCR_RXENCENABLE BIT(3) +#define SCR_SKBYA2 BIT(4) +#define SCR_NOSKMC BIT(5) + +#define CAM_VALID BIT(15) +#define CAM_NOTVALID 0x0000 +#define CAM_USEDK BIT(5) + +#define CAM_NONE 0x0 +#define CAM_WEP40 0x01 +#define CAM_TKIP 0x02 +#define CAM_AES 0x04 +#define CAM_WEP104 0x05 + +#define TOTAL_CAM_ENTRY 32 +#define HALF_CAM_ENTRY 16 + +#define CAM_WRITE BIT(16) +#define CAM_READ 0x00000000 +#define CAM_POLLINIG BIT(31) + +#define WOW_PMEN BIT(0) +#define WOW_WOMEN BIT(1) +#define WOW_MAGIC BIT(2) +#define WOW_UWF BIT(3) + +#define GPIOMUX_EN BIT(3) +#define GPIOSEL_GPIO 0 +#define GPIOSEL_PHYDBG 1 +#define GPIOSEL_BT 2 +#define GPIOSEL_WLANDBG 3 +#define GPIOSEL_GPIO_MASK (~(BIT(0)|BIT(1))) + +#define HST_RDBUSY BIT(0) +#define CPU_WTBUSY BIT(1) + +#define IMR8190_DISABLED 0x0 +#define IMR_CPUERR BIT(5) +#define IMR_ATIMEND BIT(4) +#define IMR_TBDOK BIT(3) +#define IMR_TBDER BIT(2) +#define IMR_BCNDMAINT8 BIT(1) +#define IMR_BCNDMAINT7 BIT(0) +#define IMR_BCNDMAINT6 BIT(31) +#define IMR_BCNDMAINT5 BIT(30) +#define IMR_BCNDMAINT4 BIT(29) +#define IMR_BCNDMAINT3 BIT(28) +#define IMR_BCNDMAINT2 BIT(27) +#define IMR_BCNDMAINT1 BIT(26) +#define IMR_BCNDOK8 BIT(25) +#define IMR_BCNDOK7 BIT(24) +#define IMR_BCNDOK6 BIT(23) +#define IMR_BCNDOK5 BIT(22) +#define IMR_BCNDOK4 BIT(21) +#define IMR_BCNDOK3 BIT(20) +#define IMR_BCNDOK2 BIT(19) +#define IMR_BCNDOK1 BIT(18) +#define IMR_TIMEOUT2 BIT(17) +#define IMR_TIMEOUT1 BIT(16) +#define IMR_TXFOVW BIT(15) +#define IMR_PSTIMEOUT BIT(14) +#define IMR_BCNINT BIT(13) +#define IMR_RXFOVW BIT(12) +#define IMR_RDU BIT(11) +#define IMR_RXCMDOK BIT(10) +#define IMR_BDOK BIT(9) +#define IMR_HIGHDOK BIT(8) +#define IMR_COMDOK BIT(7) +#define IMR_MGNTDOK BIT(6) +#define IMR_HCCADOK BIT(5) +#define IMR_BKDOK BIT(4) +#define IMR_BEDOK BIT(3) +#define IMR_VIDOK BIT(2) +#define IMR_VODOK BIT(1) +#define IMR_ROK BIT(0) + +#define TPPOLL_BKQ BIT(0) +#define TPPOLL_BEQ BIT(1) +#define TPPOLL_VIQ BIT(2) +#define TPPOLL_VOQ BIT(3) +#define TPPOLL_BQ BIT(4) +#define TPPOLL_CQ BIT(5) +#define TPPOLL_MQ BIT(6) +#define TPPOLL_HQ BIT(7) +#define TPPOLL_HCCAQ BIT(8) +#define TPPOLL_STOPBK BIT(9) +#define TPPOLL_STOPBE BIT(10) +#define TPPOLL_STOPVI BIT(11) +#define TPPOLL_STOPVO BIT(12) +#define TPPOLL_STOPMGT BIT(13) +#define TPPOLL_STOPHIGH BIT(14) +#define TPPOLL_STOPHCCA BIT(15) +#define TPPOLL_SHIFT 8 + +#define CCX_CMD_CLM_ENABLE BIT(0) +#define CCX_CMD_NHM_ENABLE BIT(1) +#define CCX_CMD_FUNCTION_ENABLE BIT(8) +#define CCX_CMD_IGNORE_CCA BIT(9) +#define CCX_CMD_IGNORE_TXON BIT(10) +#define CCX_CLM_RESULT_READY BIT(16) +#define CCX_NHM_RESULT_READY BIT(16) +#define CCX_CMD_RESET 0x0 + + +#define HWSET_MAX_SIZE_92S 128 +#define EFUSE_MAX_SECTION 16 +#define EFUSE_REAL_CONTENT_LEN 512 +#define EFUSE_OOB_PROTECT_BYTES 15 + +#define RTL8190_EEPROM_ID 0x8129 +#define EEPROM_HPON 0x02 +#define EEPROM_CLK 0x06 +#define EEPROM_TESTR 0x08 + +#define EEPROM_VID 0x0A +#define EEPROM_DID 0x0C +#define EEPROM_SVID 0x0E +#define EEPROM_SMID 0x10 + +#define EEPROM_MAC_ADDR 0x12 +#define EEPROM_NODE_ADDRESS_BYTE_0 0x12 + +#define EEPROM_PWDIFF 0x54 + +#define EEPROM_TXPOWERBASE 0x50 +#define EEPROM_TX_PWR_INDEX_RANGE 28 + +#define EEPROM_TX_PWR_HT20_DIFF 0x62 +#define DEFAULT_HT20_TXPWR_DIFF 2 +#define EEPROM_TX_PWR_OFDM_DIFF 0x65 + +#define EEPROM_TXPWRGROUP 0x67 +#define EEPROM_REGULATORY 0x6D + +#define TX_PWR_SAFETY_CHK 0x6D +#define EEPROM_TXPWINDEX_CCK_24G 0x5D +#define EEPROM_TXPWINDEX_OFDM_24G 0x6B +#define EEPROM_HT2T_CH1_A 0x6c +#define EEPROM_HT2T_CH7_A 0x6d +#define EEPROM_HT2T_CH13_A 0x6e +#define EEPROM_HT2T_CH1_B 0x6f +#define EEPROM_HT2T_CH7_B 0x70 +#define EEPROM_HT2T_CH13_B 0x71 + +#define EEPROM_TSSI_A 0x74 +#define EEPROM_TSSI_B 0x75 + +#define EEPROM_RFIND_POWERDIFF 0x76 +#define EEPROM_DEFAULT_LEGACYHTTXPOWERDIFF 0x3 + +#define EEPROM_THERMALMETER 0x77 +#define EEPROM_BLUETOOTH_COEXIST 0x78 +#define EEPROM_BLUETOOTH_TYPE 0x4f + +#define EEPROM_OPTIONAL 0x78 +#define EEPROM_WOWLAN 0x78 + +#define EEPROM_CRYSTALCAP 0x79 +#define EEPROM_CHANNELPLAN 0x7B +#define EEPROM_VERSION 0x7C +#define EEPROM_CUSTOMID 0x7A +#define EEPROM_BOARDTYPE 0x7E + +#define EEPROM_CHANNEL_PLAN_FCC 0x0 +#define EEPROM_CHANNEL_PLAN_IC 0x1 +#define EEPROM_CHANNEL_PLAN_ETSI 0x2 +#define EEPROM_CHANNEL_PLAN_SPAIN 0x3 +#define EEPROM_CHANNEL_PLAN_FRANCE 0x4 +#define EEPROM_CHANNEL_PLAN_MKK 0x5 +#define EEPROM_CHANNEL_PLAN_MKK1 0x6 +#define EEPROM_CHANNEL_PLAN_ISRAEL 0x7 +#define EEPROM_CHANNEL_PLAN_TELEC 0x8 +#define EEPROM_CHANNEL_PLAN_GLOBAL_DOMAIN 0x9 +#define EEPROM_CHANNEL_PLAN_WORLD_WIDE_13 0xA +#define EEPROM_CHANNEL_PLAN_NCC 0xB +#define EEPROM_CHANNEL_PLAN_BY_HW_MASK 0x80 + +#define FW_DIG_DISABLE 0xfd00cc00 +#define FW_DIG_ENABLE 0xfd000000 +#define FW_DIG_HALT 0xfd000001 +#define FW_DIG_RESUME 0xfd000002 +#define FW_HIGH_PWR_DISABLE 0xfd000008 +#define FW_HIGH_PWR_ENABLE 0xfd000009 +#define FW_ADD_A2_ENTRY 0xfd000016 +#define FW_TXPWR_TRACK_ENABLE 0xfd000017 +#define FW_TXPWR_TRACK_DISABLE 0xfd000018 +#define FW_TXPWR_TRACK_THERMAL 0xfd000019 +#define FW_TXANT_SWITCH_ENABLE 0xfd000023 +#define FW_TXANT_SWITCH_DISABLE 0xfd000024 +#define FW_RA_INIT 0xfd000026 +#define FW_CTRL_DM_BY_DRIVER 0Xfd00002a +#define FW_RA_IOT_BG_COMB 0xfd000030 +#define FW_RA_IOT_N_COMB 0xfd000031 +#define FW_RA_REFRESH 0xfd0000a0 +#define FW_RA_UPDATE_MASK 0xfd0000a2 +#define FW_RA_DISABLE 0xfd0000a4 +#define FW_RA_ACTIVE 0xfd0000a6 +#define FW_RA_DISABLE_RSSI_MASK 0xfd0000ac +#define FW_RA_ENABLE_RSSI_MASK 0xfd0000ad +#define FW_RA_RESET 0xfd0000af +#define FW_DM_DISABLE 0xfd00aa00 +#define FW_IQK_ENABLE 0xf0000020 +#define FW_IQK_SUCCESS 0x0000dddd +#define FW_IQK_FAIL 0x0000ffff +#define FW_OP_FAILURE 0xffffffff +#define FW_TX_FEEDBACK_NONE 0xfb000000 +#define FW_TX_FEEDBACK_DTM_ENABLE (FW_TX_FEEDBACK_NONE | 0x1) +#define FW_TX_FEEDBACK_CCX_ENABL (FW_TX_FEEDBACK_NONE | 0x2) +#define FW_BB_RESET_ENABLE 0xff00000d +#define FW_BB_RESET_DISABLE 0xff00000e +#define FW_CCA_CHK_ENABLE 0xff000011 +#define FW_CCK_RESET_CNT 0xff000013 +#define FW_LPS_ENTER 0xfe000010 +#define FW_LPS_LEAVE 0xfe000011 +#define FW_INDIRECT_READ 0xf2000000 +#define FW_INDIRECT_WRITE 0xf2000001 +#define FW_CHAN_SET 0xf3000001 + +#define RFPC 0x5F +#define RCR_9356SEL BIT(6) +#define TCR_LRL_OFFSET 0 +#define TCR_SRL_OFFSET 8 +#define TCR_MXDMA_OFFSET 21 +#define TCR_SAT BIT(24) +#define RCR_MXDMA_OFFSET 8 +#define RCR_FIFO_OFFSET 13 +#define RCR_OnlyErlPkt BIT(31) +#define CWR 0xDC +#define RETRYCTR 0xDE + +#define CPU_GEN_SYSTEM_RESET 0x00000001 + +#define CCX_COMMAND_REG 0x890 +#define CLM_PERIOD_REG 0x894 +#define NHM_PERIOD_REG 0x896 + +#define NHM_THRESHOLD0 0x898 +#define NHM_THRESHOLD1 0x899 +#define NHM_THRESHOLD2 0x89A +#define NHM_THRESHOLD3 0x89B +#define NHM_THRESHOLD4 0x89C +#define NHM_THRESHOLD5 0x89D +#define NHM_THRESHOLD6 0x89E +#define CLM_RESULT_REG 0x8D0 +#define NHM_RESULT_REG 0x8D4 +#define NHM_RPI_COUNTER0 0x8D8 +#define NHM_RPI_COUNTER1 0x8D9 +#define NHM_RPI_COUNTER2 0x8DA +#define NHM_RPI_COUNTER3 0x8DB +#define NHM_RPI_COUNTER4 0x8DC +#define NHM_RPI_COUNTER5 0x8DD +#define NHM_RPI_COUNTER6 0x8DE +#define NHM_RPI_COUNTER7 0x8DF + +#define HAL_8192S_HW_GPIO_OFF_BIT BIT(3) +#define HAL_8192S_HW_GPIO_OFF_MASK 0xF7 +#define HAL_8192S_HW_GPIO_WPS_BIT BIT(4) + +#define RPMAC_RESET 0x100 +#define RPMAC_TXSTART 0x104 +#define RPMAC_TXLEGACYSIG 0x108 +#define RPMAC_TXHTSIG1 0x10c +#define RPMAC_TXHTSIG2 0x110 +#define RPMAC_PHYDEBUG 0x114 +#define RPMAC_TXPACKETNNM 0x118 +#define RPMAC_TXIDLE 0x11c +#define RPMAC_TXMACHEADER0 0x120 +#define RPMAC_TXMACHEADER1 0x124 +#define RPMAC_TXMACHEADER2 0x128 +#define RPMAC_TXMACHEADER3 0x12c +#define RPMAC_TXMACHEADER4 0x130 +#define RPMAC_TXMACHEADER5 0x134 +#define RPMAC_TXDATATYPE 0x138 +#define RPMAC_TXRANDOMSEED 0x13c +#define RPMAC_CCKPLCPPREAMBLE 0x140 +#define RPMAC_CCKPLCPHEADER 0x144 +#define RPMAC_CCKCRC16 0x148 +#define RPMAC_OFDMRXCRC32OK 0x170 +#define RPMAC_OFDMRXCRC32ER 0x174 +#define RPMAC_OFDMRXPARITYER 0x178 +#define RPMAC_OFDMRXCRC8ER 0x17c +#define RPMAC_CCKCRXRC16ER 0x180 +#define RPMAC_CCKCRXRC32ER 0x184 +#define RPMAC_CCKCRXRC32OK 0x188 +#define RPMAC_TXSTATUS 0x18c + +#define RF_BB_CMD_ADDR 0x02c0 +#define RF_BB_CMD_DATA 0x02c4 + +#define RFPGA0_RFMOD 0x800 + +#define RFPGA0_TXINFO 0x804 +#define RFPGA0_PSDFUNCTION 0x808 + +#define RFPGA0_TXGAINSTAGE 0x80c + +#define RFPGA0_RFTIMING1 0x810 +#define RFPGA0_RFTIMING2 0x814 +#define RFPGA0_XA_HSSIPARAMETER1 0x820 +#define RFPGA0_XA_HSSIPARAMETER2 0x824 +#define RFPGA0_XB_HSSIPARAMETER1 0x828 +#define RFPGA0_XB_HSSIPARAMETER2 0x82c +#define RFPGA0_XC_HSSIPARAMETER1 0x830 +#define RFPGA0_XC_HSSIPARAMETER2 0x834 +#define RFPGA0_XD_HSSIPARAMETER1 0x838 +#define RFPGA0_XD_HSSIPARAMETER2 0x83c +#define RFPGA0_XA_LSSIPARAMETER 0x840 +#define RFPGA0_XB_LSSIPARAMETER 0x844 +#define RFPGA0_XC_LSSIPARAMETER 0x848 +#define RFPGA0_XD_LSSIPARAMETER 0x84c + +#define RFPGA0_RFWAKEUP_PARAMETER 0x850 +#define RFPGA0_RFSLEEPUP_PARAMETER 0x854 + +#define RFPGA0_XAB_SWITCHCONTROL 0x858 +#define RFPGA0_XCD_SWITCHCONTROL 0x85c + +#define RFPGA0_XA_RFINTERFACEOE 0x860 +#define RFPGA0_XB_RFINTERFACEOE 0x864 +#define RFPGA0_XC_RFINTERFACEOE 0x868 +#define RFPGA0_XD_RFINTERFACEOE 0x86c + +#define RFPGA0_XAB_RFINTERFACESW 0x870 +#define RFPGA0_XCD_RFINTERFACESW 0x874 + +#define RFPGA0_XAB_RFPARAMETER 0x878 +#define RFPGA0_XCD_RFPARAMETER 0x87c + +#define RFPGA0_ANALOGPARAMETER1 0x880 +#define RFPGA0_ANALOGPARAMETER2 0x884 +#define RFPGA0_ANALOGPARAMETER3 0x888 +#define RFPGA0_ANALOGPARAMETER4 0x88c + +#define RFPGA0_XA_LSSIREADBACK 0x8a0 +#define RFPGA0_XB_LSSIREADBACK 0x8a4 +#define RFPGA0_XC_LSSIREADBACK 0x8a8 +#define RFPGA0_XD_LSSIREADBACK 0x8ac + +#define RFPGA0_PSDREPORT 0x8b4 +#define TRANSCEIVERA_HSPI_READBACK 0x8b8 +#define TRANSCEIVERB_HSPI_READBACK 0x8bc +#define RFPGA0_XAB_RFINTERFACERB 0x8e0 +#define RFPGA0_XCD_RFINTERFACERB 0x8e4 +#define RFPGA1_RFMOD 0x900 + +#define RFPGA1_TXBLOCK 0x904 +#define RFPGA1_DEBUGSELECT 0x908 +#define RFPGA1_TXINFO 0x90c + +#define RCCK0_SYSTEM 0xa00 + +#define RCCK0_AFESETTING 0xa04 +#define RCCK0_CCA 0xa08 + +#define RCCK0_RXAGC1 0xa0c +#define RCCK0_RXAGC2 0xa10 + +#define RCCK0_RXHP 0xa14 + +#define RCCK0_DSPPARAMETER1 0xa18 +#define RCCK0_DSPPARAMETER2 0xa1c + +#define RCCK0_TXFILTER1 0xa20 +#define RCCK0_TXFILTER2 0xa24 +#define RCCK0_DEBUGPORT 0xa28 +#define RCCK0_FALSEALARMREPORT 0xa2c +#define RCCK0_TRSSIREPORT 0xa50 +#define RCCK0_RXREPORT 0xa54 +#define RCCK0_FACOUNTERLOWER 0xa5c +#define RCCK0_FACOUNTERUPPER 0xa58 + +#define ROFDM0_LSTF 0xc00 + +#define ROFDM0_TRXPATHENABLE 0xc04 +#define ROFDM0_TRMUXPAR 0xc08 +#define ROFDM0_TRSWISOLATION 0xc0c + +#define ROFDM0_XARXAFE 0xc10 +#define ROFDM0_XARXIQIMBALANCE 0xc14 +#define ROFDM0_XBRXAFE 0xc18 +#define ROFDM0_XBRXIQIMBALANCE 0xc1c +#define ROFDM0_XCRXAFE 0xc20 +#define ROFDM0_XCRXIQIMBALANCE 0xc24 +#define ROFDM0_XDRXAFE 0xc28 +#define ROFDM0_XDRXIQIMBALANCE 0xc2c + +#define ROFDM0_RXDETECTOR1 0xc30 +#define ROFDM0_RXDETECTOR2 0xc34 +#define ROFDM0_RXDETECTOR3 0xc38 +#define ROFDM0_RXDETECTOR4 0xc3c + +#define ROFDM0_RXDSP 0xc40 +#define ROFDM0_CFO_AND_DAGC 0xc44 +#define ROFDM0_CCADROP_THRESHOLD 0xc48 +#define ROFDM0_ECCA_THRESHOLD 0xc4c + +#define ROFDM0_XAAGCCORE1 0xc50 +#define ROFDM0_XAAGCCORE2 0xc54 +#define ROFDM0_XBAGCCORE1 0xc58 +#define ROFDM0_XBAGCCORE2 0xc5c +#define ROFDM0_XCAGCCORE1 0xc60 +#define ROFDM0_XCAGCCORE2 0xc64 +#define ROFDM0_XDAGCCORE1 0xc68 +#define ROFDM0_XDAGCCORE2 0xc6c + +#define ROFDM0_AGCPARAMETER1 0xc70 +#define ROFDM0_AGCPARAMETER2 0xc74 +#define ROFDM0_AGCRSSITABLE 0xc78 +#define ROFDM0_HTSTFAGC 0xc7c + +#define ROFDM0_XATXIQIMBALANCE 0xc80 +#define ROFDM0_XATXAFE 0xc84 +#define ROFDM0_XBTXIQIMBALANCE 0xc88 +#define ROFDM0_XBTXAFE 0xc8c +#define ROFDM0_XCTXIQIMBALANCE 0xc90 +#define ROFDM0_XCTXAFE 0xc94 +#define ROFDM0_XDTXIQIMBALANCE 0xc98 +#define ROFDM0_XDTXAFE 0xc9c + +#define ROFDM0_RXHP_PARAMETER 0xce0 +#define ROFDM0_TXPSEUDO_NOISE_WGT 0xce4 +#define ROFDM0_FRAME_SYNC 0xcf0 +#define ROFDM0_DFSREPORT 0xcf4 +#define ROFDM0_TXCOEFF1 0xca4 +#define ROFDM0_TXCOEFF2 0xca8 +#define ROFDM0_TXCOEFF3 0xcac +#define ROFDM0_TXCOEFF4 0xcb0 +#define ROFDM0_TXCOEFF5 0xcb4 +#define ROFDM0_TXCOEFF6 0xcb8 + + +#define ROFDM1_LSTF 0xd00 +#define ROFDM1_TRXPATHENABLE 0xd04 + +#define ROFDM1_CFO 0xd08 +#define ROFDM1_CSI1 0xd10 +#define ROFDM1_SBD 0xd14 +#define ROFDM1_CSI2 0xd18 +#define ROFDM1_CFOTRACKING 0xd2c +#define ROFDM1_TRXMESAURE1 0xd34 +#define ROFDM1_INTF_DET 0xd3c +#define ROFDM1_PSEUDO_NOISESTATEAB 0xd50 +#define ROFDM1_PSEUDO_NOISESTATECD 0xd54 +#define ROFDM1_RX_PSEUDO_NOISE_WGT 0xd58 + +#define ROFDM_PHYCOUNTER1 0xda0 +#define ROFDM_PHYCOUNTER2 0xda4 +#define ROFDM_PHYCOUNTER3 0xda8 + +#define ROFDM_SHORT_CFOAB 0xdac +#define ROFDM_SHORT_CFOCD 0xdb0 +#define ROFDM_LONG_CFOAB 0xdb4 +#define ROFDM_LONG_CFOCD 0xdb8 +#define ROFDM_TAIL_CFOAB 0xdbc +#define ROFDM_TAIL_CFOCD 0xdc0 +#define ROFDM_PW_MEASURE1 0xdc4 +#define ROFDM_PW_MEASURE2 0xdc8 +#define ROFDM_BW_REPORT 0xdcc +#define ROFDM_AGC_REPORT 0xdd0 +#define ROFDM_RXSNR 0xdd4 +#define ROFDM_RXEVMCSI 0xdd8 +#define ROFDM_SIG_REPORT 0xddc + + +#define RTXAGC_RATE18_06 0xe00 +#define RTXAGC_RATE54_24 0xe04 +#define RTXAGC_CCK_MCS32 0xe08 +#define RTXAGC_MCS03_MCS00 0xe10 +#define RTXAGC_MCS07_MCS04 0xe14 +#define RTXAGC_MCS11_MCS08 0xe18 +#define RTXAGC_MCS15_MCS12 0xe1c + + +#define RF_AC 0x00 +#define RF_IQADJ_G1 0x01 +#define RF_IQADJ_G2 0x02 +#define RF_POW_TRSW 0x05 +#define RF_GAIN_RX 0x06 +#define RF_GAIN_TX 0x07 +#define RF_TXM_IDAC 0x08 +#define RF_BS_IQGEN 0x0F + +#define RF_MODE1 0x10 +#define RF_MODE2 0x11 +#define RF_RX_AGC_HP 0x12 +#define RF_TX_AGC 0x13 +#define RF_BIAS 0x14 +#define RF_IPA 0x15 +#define RF_POW_ABILITY 0x17 +#define RF_MODE_AG 0x18 +#define RF_CHANNEL 0x18 +#define RF_CHNLBW 0x18 +#define RF_TOP 0x19 +#define RF_RX_G1 0x1A +#define RF_RX_G2 0x1B +#define RF_RX_BB2 0x1C +#define RF_RX_BB1 0x1D +#define RF_RCK1 0x1E +#define RF_RCK2 0x1F + +#define RF_TX_G1 0x20 +#define RF_TX_G2 0x21 +#define RF_TX_G3 0x22 +#define RF_TX_BB1 0x23 +#define RF_T_METER 0x24 +#define RF_SYN_G1 0x25 +#define RF_SYN_G2 0x26 +#define RF_SYN_G3 0x27 +#define RF_SYN_G4 0x28 +#define RF_SYN_G5 0x29 +#define RF_SYN_G6 0x2A +#define RF_SYN_G7 0x2B +#define RF_SYN_G8 0x2C + +#define RF_RCK_OS 0x30 +#define RF_TXPA_G1 0x31 +#define RF_TXPA_G2 0x32 +#define RF_TXPA_G3 0x33 + +#define BRFMOD 0x1 +#define BCCKEN 0x1000000 +#define BOFDMEN 0x2000000 + +#define BXBTXAGC 0xf00 +#define BXCTXAGC 0xf000 +#define BXDTXAGC 0xf0000 + +#define B3WIRE_DATALENGTH 0x800 +#define B3WIRE_ADDRESSLENGTH 0x400 + +#define BRFSI_RFENV 0x10 + +#define BLSSI_READADDRESS 0x7f800000 +#define BLSSI_READEDGE 0x80000000 +#define BLSSI_READBACK_DATA 0xfffff + +#define BADCLKPHASE 0x4000000 + +#define BCCK_SIDEBAND 0x10 + +#define BTX_AGCRATECCK 0x7f00 + +#define MASKBYTE0 0xff +#define MASKBYTE1 0xff00 +#define MASKBYTE2 0xff0000 +#define MASKBYTE3 0xff000000 +#define MASKHWORD 0xffff0000 +#define MASKLWORD 0x0000ffff +#define MASKDWORD 0xffffffff + +#define MAKS12BITS 0xfffff +#define MASK20BITS 0xfffff +#define RFREG_OFFSET_MASK 0xfffff + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/rf.c b/drivers/net/wireless/rtlwifi/rtl8192se/rf.c new file mode 100644 index 0000000..5061f1d --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/rf.c @@ -0,0 +1,535 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "rf.h" +#include "dm.h" + + +static void _rtl92s_get_powerbase(struct ieee80211_hw *hw, u8 *p_pwrlevel, + u8 chnl, u32 *ofdmbase, u32 *mcsbase, + u8 *p_final_pwridx) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u32 pwrbase0, pwrbase1; + u8 legacy_pwrdiff = 0, ht20_pwrdiff = 0; + u8 i, pwrlevel[4]; + + for (i = 0; i < 2; i++) + pwrlevel[i] = p_pwrlevel[i]; + + /* We only care about the path A for legacy. */ + if (rtlefuse->eeprom_version < 2) { + pwrbase0 = pwrlevel[0] + (rtlefuse->legacy_httxpowerdiff & 0xf); + } else if (rtlefuse->eeprom_version >= 2) { + legacy_pwrdiff = rtlefuse->txpwr_legacyhtdiff + [RF90_PATH_A][chnl - 1]; + + /* For legacy OFDM, tx pwr always > HT OFDM pwr. + * We do not care Path B + * legacy OFDM pwr diff. NO BB register + * to notify HW. */ + pwrbase0 = pwrlevel[0] + legacy_pwrdiff; + } + + pwrbase0 = (pwrbase0 << 24) | (pwrbase0 << 16) | (pwrbase0 << 8) | + pwrbase0; + *ofdmbase = pwrbase0; + + /* MCS rates */ + if (rtlefuse->eeprom_version >= 2) { + /* Check HT20 to HT40 diff */ + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20) { + for (i = 0; i < 2; i++) { + /* rf-A, rf-B */ + /* HT 20<->40 pwr diff */ + ht20_pwrdiff = rtlefuse->txpwr_ht20diff + [i][chnl - 1]; + + if (ht20_pwrdiff < 8) /* 0~+7 */ + pwrlevel[i] += ht20_pwrdiff; + else /* index8-15=-8~-1 */ + pwrlevel[i] -= (16 - ht20_pwrdiff); + } + } + } + + /* use index of rf-A */ + pwrbase1 = pwrlevel[0]; + pwrbase1 = (pwrbase1 << 24) | (pwrbase1 << 16) | (pwrbase1 << 8) | + pwrbase1; + *mcsbase = pwrbase1; + + /* The following is for Antenna + * diff from Ant-B to Ant-A */ + p_final_pwridx[0] = pwrlevel[0]; + p_final_pwridx[1] = pwrlevel[1]; + + switch (rtlefuse->eeprom_regulatory) { + case 3: + /* The following is for calculation + * of the power diff for Ant-B to Ant-A. */ + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20_40) { + p_final_pwridx[0] += rtlefuse->pwrgroup_ht40 + [RF90_PATH_A][ + chnl - 1]; + p_final_pwridx[1] += rtlefuse->pwrgroup_ht40 + [RF90_PATH_B][ + chnl - 1]; + } else { + p_final_pwridx[0] += rtlefuse->pwrgroup_ht20 + [RF90_PATH_A][ + chnl - 1]; + p_final_pwridx[1] += rtlefuse->pwrgroup_ht20 + [RF90_PATH_B][ + chnl - 1]; + } + break; + default: + break; + } + + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20_40) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "40MHz finalpwr_idx (A / B) = 0x%x / 0x%x\n", + p_final_pwridx[0], p_final_pwridx[1]); + } else { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "20MHz finalpwr_idx (A / B) = 0x%x / 0x%x\n", + p_final_pwridx[0], p_final_pwridx[1]); + } +} + +static void _rtl92s_set_antennadiff(struct ieee80211_hw *hw, + u8 *p_final_pwridx) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + char ant_pwr_diff = 0; + u32 u4reg_val = 0; + + if (rtlphy->rf_type == RF_2T2R) { + ant_pwr_diff = p_final_pwridx[1] - p_final_pwridx[0]; + + /* range is from 7~-8, + * index = 0x0~0xf */ + if (ant_pwr_diff > 7) + ant_pwr_diff = 7; + if (ant_pwr_diff < -8) + ant_pwr_diff = -8; + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "Antenna Diff from RF-B to RF-A = %d (0x%x)\n", + ant_pwr_diff, ant_pwr_diff & 0xf); + + ant_pwr_diff &= 0xf; + } + + /* Antenna TX power difference */ + rtlefuse->antenna_txpwdiff[2] = 0;/* RF-D, don't care */ + rtlefuse->antenna_txpwdiff[1] = 0;/* RF-C, don't care */ + rtlefuse->antenna_txpwdiff[0] = (u8)(ant_pwr_diff); /* RF-B */ + + u4reg_val = rtlefuse->antenna_txpwdiff[2] << 8 | + rtlefuse->antenna_txpwdiff[1] << 4 | + rtlefuse->antenna_txpwdiff[0]; + + rtl_set_bbreg(hw, RFPGA0_TXGAINSTAGE, (BXBTXAGC | BXCTXAGC | BXDTXAGC), + u4reg_val); + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, "Write BCD-Diff(0x%x) = 0x%x\n", + RFPGA0_TXGAINSTAGE, u4reg_val); +} + +static void _rtl92s_get_txpower_writeval_byregulatory(struct ieee80211_hw *hw, + u8 chnl, u8 index, + u32 pwrbase0, + u32 pwrbase1, + u32 *p_outwrite_val) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 i, chnlgroup, pwrdiff_limit[4]; + u32 writeval, customer_limit; + + /* Index 0 & 1= legacy OFDM, 2-5=HT_MCS rate */ + switch (rtlefuse->eeprom_regulatory) { + case 0: + /* Realtek better performance increase power diff + * defined by Realtek for large power */ + chnlgroup = 0; + + writeval = rtlphy->mcs_offset[chnlgroup][index] + + ((index < 2) ? pwrbase0 : pwrbase1); + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "RTK better performance, writeval = 0x%x\n", writeval); + break; + case 1: + /* Realtek regulatory increase power diff defined + * by Realtek for regulatory */ + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20_40) { + writeval = ((index < 2) ? pwrbase0 : pwrbase1); + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "Realtek regulatory, 40MHz, writeval = 0x%x\n", + writeval); + } else { + if (rtlphy->pwrgroup_cnt == 1) + chnlgroup = 0; + + if (rtlphy->pwrgroup_cnt >= 3) { + if (chnl <= 3) + chnlgroup = 0; + else if (chnl >= 4 && chnl <= 8) + chnlgroup = 1; + else if (chnl > 8) + chnlgroup = 2; + if (rtlphy->pwrgroup_cnt == 4) + chnlgroup++; + } + + writeval = rtlphy->mcs_offset[chnlgroup][index] + + ((index < 2) ? + pwrbase0 : pwrbase1); + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "Realtek regulatory, 20MHz, writeval = 0x%x\n", + writeval); + } + break; + case 2: + /* Better regulatory don't increase any power diff */ + writeval = ((index < 2) ? pwrbase0 : pwrbase1); + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "Better regulatory, writeval = 0x%x\n", writeval); + break; + case 3: + /* Customer defined power diff. increase power diff + defined by customer. */ + chnlgroup = 0; + + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20_40) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "customer's limit, 40MHz = 0x%x\n", + rtlefuse->pwrgroup_ht40 + [RF90_PATH_A][chnl - 1]); + } else { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "customer's limit, 20MHz = 0x%x\n", + rtlefuse->pwrgroup_ht20 + [RF90_PATH_A][chnl - 1]); + } + + for (i = 0; i < 4; i++) { + pwrdiff_limit[i] = (u8)((rtlphy->mcs_offset + [chnlgroup][index] & (0x7f << (i * 8))) + >> (i * 8)); + + if (rtlphy->current_chan_bw == + HT_CHANNEL_WIDTH_20_40) { + if (pwrdiff_limit[i] > + rtlefuse->pwrgroup_ht40 + [RF90_PATH_A][chnl - 1]) { + pwrdiff_limit[i] = + rtlefuse->pwrgroup_ht20 + [RF90_PATH_A][chnl - 1]; + } + } else { + if (pwrdiff_limit[i] > + rtlefuse->pwrgroup_ht20 + [RF90_PATH_A][chnl - 1]) { + pwrdiff_limit[i] = + rtlefuse->pwrgroup_ht20 + [RF90_PATH_A][chnl - 1]; + } + } + } + + customer_limit = (pwrdiff_limit[3] << 24) | + (pwrdiff_limit[2] << 16) | + (pwrdiff_limit[1] << 8) | + (pwrdiff_limit[0]); + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "Customer's limit = 0x%x\n", customer_limit); + + writeval = customer_limit + ((index < 2) ? + pwrbase0 : pwrbase1); + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "Customer, writeval = 0x%x\n", writeval); + break; + default: + chnlgroup = 0; + writeval = rtlphy->mcs_offset[chnlgroup][index] + + ((index < 2) ? pwrbase0 : pwrbase1); + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "RTK better performance, writeval = 0x%x\n", writeval); + break; + } + + if (rtlpriv->dm.dynamic_txhighpower_lvl == TX_HIGH_PWR_LEVEL_LEVEL1) + writeval = 0x10101010; + else if (rtlpriv->dm.dynamic_txhighpower_lvl == + TX_HIGH_PWR_LEVEL_LEVEL2) + writeval = 0x0; + + *p_outwrite_val = writeval; + +} + +static void _rtl92s_write_ofdm_powerreg(struct ieee80211_hw *hw, + u8 index, u32 val) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u16 regoffset[6] = {0xe00, 0xe04, 0xe10, 0xe14, 0xe18, 0xe1c}; + u8 i, rfa_pwr[4]; + u8 rfa_lower_bound = 0, rfa_upper_bound = 0, rf_pwr_diff = 0; + u32 writeval = val; + + /* If path A and Path B coexist, we must limit Path A tx power. + * Protect Path B pwr over or under flow. We need to calculate + * upper and lower bound of path A tx power. */ + if (rtlphy->rf_type == RF_2T2R) { + rf_pwr_diff = rtlefuse->antenna_txpwdiff[0]; + + /* Diff=-8~-1 */ + if (rf_pwr_diff >= 8) { + /* Prevent underflow!! */ + rfa_lower_bound = 0x10 - rf_pwr_diff; + /* if (rf_pwr_diff >= 0) Diff = 0-7 */ + } else { + rfa_upper_bound = RF6052_MAX_TX_PWR - rf_pwr_diff; + } + } + + for (i = 0; i < 4; i++) { + rfa_pwr[i] = (u8)((writeval & (0x7f << (i * 8))) >> (i * 8)); + if (rfa_pwr[i] > RF6052_MAX_TX_PWR) + rfa_pwr[i] = RF6052_MAX_TX_PWR; + + /* If path A and Path B coexist, we must limit Path A tx power. + * Protect Path B pwr over or under flow. We need to calculate + * upper and lower bound of path A tx power. */ + if (rtlphy->rf_type == RF_2T2R) { + /* Diff=-8~-1 */ + if (rf_pwr_diff >= 8) { + /* Prevent underflow!! */ + if (rfa_pwr[i] < rfa_lower_bound) + rfa_pwr[i] = rfa_lower_bound; + /* Diff = 0-7 */ + } else if (rf_pwr_diff >= 1) { + /* Prevent overflow */ + if (rfa_pwr[i] > rfa_upper_bound) + rfa_pwr[i] = rfa_upper_bound; + } + } + + } + + writeval = (rfa_pwr[3] << 24) | (rfa_pwr[2] << 16) | (rfa_pwr[1] << 8) | + rfa_pwr[0]; + + rtl_set_bbreg(hw, regoffset[index], 0x7f7f7f7f, writeval); +} + +void rtl92s_phy_rf6052_set_ofdmtxpower(struct ieee80211_hw *hw, + u8 *p_pwrlevel, u8 chnl) +{ + u32 writeval, pwrbase0, pwrbase1; + u8 index = 0; + u8 finalpwr_idx[4]; + + _rtl92s_get_powerbase(hw, p_pwrlevel, chnl, &pwrbase0, &pwrbase1, + &finalpwr_idx[0]); + _rtl92s_set_antennadiff(hw, &finalpwr_idx[0]); + + for (index = 0; index < 6; index++) { + _rtl92s_get_txpower_writeval_byregulatory(hw, chnl, index, + pwrbase0, pwrbase1, &writeval); + + _rtl92s_write_ofdm_powerreg(hw, index, writeval); + } +} + +void rtl92s_phy_rf6052_set_ccktxpower(struct ieee80211_hw *hw, u8 pwrlevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u32 txagc = 0; + bool dont_inc_cck_or_turboscanoff = false; + + if (((rtlefuse->eeprom_version >= 2) && + (rtlefuse->txpwr_safetyflag == 1)) || + ((rtlefuse->eeprom_version >= 2) && + (rtlefuse->eeprom_regulatory != 0))) + dont_inc_cck_or_turboscanoff = true; + + if (mac->act_scanning) { + txagc = 0x3f; + if (dont_inc_cck_or_turboscanoff) + txagc = pwrlevel; + } else { + txagc = pwrlevel; + + if (rtlpriv->dm.dynamic_txhighpower_lvl == + TX_HIGH_PWR_LEVEL_LEVEL1) + txagc = 0x10; + else if (rtlpriv->dm.dynamic_txhighpower_lvl == + TX_HIGH_PWR_LEVEL_LEVEL2) + txagc = 0x0; + } + + if (txagc > RF6052_MAX_TX_PWR) + txagc = RF6052_MAX_TX_PWR; + + rtl_set_bbreg(hw, RTXAGC_CCK_MCS32, BTX_AGCRATECCK, txagc); + +} + +bool rtl92s_phy_rf6052_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 u4reg_val = 0; + u8 rfpath; + bool rtstatus = true; + struct bb_reg_def *pphyreg; + + /* Initialize RF */ + for (rfpath = 0; rfpath < rtlphy->num_total_rfpath; rfpath++) { + + pphyreg = &rtlphy->phyreg_def[rfpath]; + + /* Store original RFENV control type */ + switch (rfpath) { + case RF90_PATH_A: + case RF90_PATH_C: + u4reg_val = rtl92s_phy_query_bb_reg(hw, + pphyreg->rfintfs, + BRFSI_RFENV); + break; + case RF90_PATH_B: + case RF90_PATH_D: + u4reg_val = rtl92s_phy_query_bb_reg(hw, + pphyreg->rfintfs, + BRFSI_RFENV << 16); + break; + } + + /* Set RF_ENV enable */ + rtl92s_phy_set_bb_reg(hw, pphyreg->rfintfe, + BRFSI_RFENV << 16, 0x1); + + /* Set RF_ENV output high */ + rtl92s_phy_set_bb_reg(hw, pphyreg->rfintfo, BRFSI_RFENV, 0x1); + + /* Set bit number of Address and Data for RF register */ + rtl92s_phy_set_bb_reg(hw, pphyreg->rfhssi_para2, + B3WIRE_ADDRESSLENGTH, 0x0); + rtl92s_phy_set_bb_reg(hw, pphyreg->rfhssi_para2, + B3WIRE_DATALENGTH, 0x0); + + /* Initialize RF fom connfiguration file */ + switch (rfpath) { + case RF90_PATH_A: + rtstatus = rtl92s_phy_config_rf(hw, + (enum radio_path)rfpath); + break; + case RF90_PATH_B: + rtstatus = rtl92s_phy_config_rf(hw, + (enum radio_path)rfpath); + break; + case RF90_PATH_C: + break; + case RF90_PATH_D: + break; + } + + /* Restore RFENV control type */ + switch (rfpath) { + case RF90_PATH_A: + case RF90_PATH_C: + rtl92s_phy_set_bb_reg(hw, pphyreg->rfintfs, BRFSI_RFENV, + u4reg_val); + break; + case RF90_PATH_B: + case RF90_PATH_D: + rtl92s_phy_set_bb_reg(hw, pphyreg->rfintfs, + BRFSI_RFENV << 16, + u4reg_val); + break; + } + + if (!rtstatus) { + pr_err("Radio[%d] Fail!!\n", rfpath); + goto fail; + } + + } + + return rtstatus; + +fail: + return rtstatus; +} + +void rtl92s_phy_rf6052_set_bandwidth(struct ieee80211_hw *hw, u8 bandwidth) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + switch (bandwidth) { + case HT_CHANNEL_WIDTH_20: + rtlphy->rfreg_chnlval[0] = ((rtlphy->rfreg_chnlval[0] & + 0xfffff3ff) | 0x0400); + rtl_set_rfreg(hw, RF90_PATH_A, RF_CHNLBW, RFREG_OFFSET_MASK, + rtlphy->rfreg_chnlval[0]); + break; + case HT_CHANNEL_WIDTH_20_40: + rtlphy->rfreg_chnlval[0] = ((rtlphy->rfreg_chnlval[0] & + 0xfffff3ff)); + rtl_set_rfreg(hw, RF90_PATH_A, RF_CHNLBW, RFREG_OFFSET_MASK, + rtlphy->rfreg_chnlval[0]); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", bandwidth); + break; + } +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/rf.h b/drivers/net/wireless/rtlwifi/rtl8192se/rf.h new file mode 100644 index 0000000..8a29eb9 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/rf.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ +#ifndef __INC_RTL92S_RF_H +#define __INC_RTL92S_RF_H + +#define RF6052_MAX_TX_PWR 0x3F + +void rtl92s_phy_rf6052_set_bandwidth(struct ieee80211_hw *hw, + u8 bandwidth); +bool rtl92s_phy_rf6052_config(struct ieee80211_hw *hw) ; +void rtl92s_phy_rf6052_set_ccktxpower(struct ieee80211_hw *hw, + u8 powerlevel); +void rtl92s_phy_rf6052_set_ofdmtxpower(struct ieee80211_hw *hw, + u8 *p_pwrlevel, u8 chnl); + +#endif + diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/sw.c b/drivers/net/wireless/rtlwifi/rtl8192se/sw.c new file mode 100644 index 0000000..240ae28 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/sw.c @@ -0,0 +1,451 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../core.h" +#include "../base.h" +#include "../pci.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "dm.h" +#include "fw.h" +#include "hw.h" +#include "sw.h" +#include "trx.h" +#include "led.h" + +#include + +static void rtl92s_init_aspm_vars(struct ieee80211_hw *hw) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + /*close ASPM for AMD defaultly */ + rtlpci->const_amdpci_aspm = 0; + + /* ASPM PS mode. + * 0 - Disable ASPM, + * 1 - Enable ASPM without Clock Req, + * 2 - Enable ASPM with Clock Req, + * 3 - Alwyas Enable ASPM with Clock Req, + * 4 - Always Enable ASPM without Clock Req. + * set defult to RTL8192CE:3 RTL8192E:2 + * */ + rtlpci->const_pci_aspm = 2; + + /*Setting for PCI-E device */ + rtlpci->const_devicepci_aspm_setting = 0x03; + + /*Setting for PCI-E bridge */ + rtlpci->const_hostpci_aspm_setting = 0x02; + + /* In Hw/Sw Radio Off situation. + * 0 - Default, + * 1 - From ASPM setting without low Mac Pwr, + * 2 - From ASPM setting with low Mac Pwr, + * 3 - Bus D3 + * set default to RTL8192CE:0 RTL8192SE:2 + */ + rtlpci->const_hwsw_rfoff_d3 = 2; + + /* This setting works for those device with + * backdoor ASPM setting such as EPHY setting. + * 0 - Not support ASPM, + * 1 - Support ASPM, + * 2 - According to chipset. + */ + rtlpci->const_support_pciaspm = 2; +} + +static void rtl92se_fw_cb(const struct firmware *firmware, void *context) +{ + struct ieee80211_hw *hw = context; + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(pcipriv); + struct rt_firmware *pfirmware = NULL; + int err; + + RT_TRACE(rtlpriv, COMP_ERR, DBG_LOUD, + "Firmware callback routine entered!\n"); + complete(&rtlpriv->firmware_loading_complete); + if (!firmware) { + pr_err("Firmware %s not available\n", rtlpriv->cfg->fw_name); + rtlpriv->max_fw_size = 0; + return; + } + if (firmware->size > rtlpriv->max_fw_size) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Firmware is too big!\n"); + rtlpriv->max_fw_size = 0; + release_firmware(firmware); + return; + } + pfirmware = (struct rt_firmware *)rtlpriv->rtlhal.pfirmware; + memcpy(pfirmware->sz_fw_tmpbuffer, firmware->data, firmware->size); + pfirmware->sz_fw_tmpbufferlen = firmware->size; + release_firmware(firmware); + + err = ieee80211_register_hw(hw); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Can't register mac80211 hw\n"); + return; + } else { + rtlpriv->mac80211.mac80211_registered = 1; + } + rtlpci->irq_alloc = 1; + set_bit(RTL_STATUS_INTERFACE_START, &rtlpriv->status); + + /*init rfkill */ + rtl_init_rfkill(hw); +} + +static int rtl92s_init_sw_vars(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + int err = 0; + u16 earlyrxthreshold = 7; + + rtlpriv->dm.dm_initialgain_enable = true; + rtlpriv->dm.dm_flag = 0; + rtlpriv->dm.disable_framebursting = false; + rtlpriv->dm.thermalvalue = 0; + rtlpriv->dm.useramask = true; + + /* compatible 5G band 91se just 2.4G band & smsp */ + rtlpriv->rtlhal.current_bandtype = BAND_ON_2_4G; + rtlpriv->rtlhal.bandset = BAND_ON_2_4G; + rtlpriv->rtlhal.macphymode = SINGLEMAC_SINGLEPHY; + + rtlpci->transmit_config = 0; + + rtlpci->receive_config = + RCR_APPFCS | + RCR_APWRMGT | + /*RCR_ADD3 |*/ + RCR_AMF | + RCR_ADF | + RCR_APP_MIC | + RCR_APP_ICV | + RCR_AICV | + /* Accept ICV error, CRC32 Error */ + RCR_ACRC32 | + RCR_AB | + /* Accept Broadcast, Multicast */ + RCR_AM | + /* Accept Physical match */ + RCR_APM | + /* Accept Destination Address packets */ + /*RCR_AAP |*/ + RCR_APP_PHYST_STAFF | + /* Accept PHY status */ + RCR_APP_PHYST_RXFF | + (earlyrxthreshold << RCR_FIFO_OFFSET); + + rtlpci->irq_mask[0] = (u32) + (IMR_ROK | + IMR_VODOK | + IMR_VIDOK | + IMR_BEDOK | + IMR_BKDOK | + IMR_HCCADOK | + IMR_MGNTDOK | + IMR_COMDOK | + IMR_HIGHDOK | + IMR_BDOK | + IMR_RXCMDOK | + /*IMR_TIMEOUT0 |*/ + IMR_RDU | + IMR_RXFOVW | + IMR_BCNINT + /*| IMR_TXFOVW*/ + /*| IMR_TBDOK | + IMR_TBDER*/); + + rtlpci->irq_mask[1] = (u32) 0; + + rtlpci->shortretry_limit = 0x30; + rtlpci->longretry_limit = 0x30; + + rtlpci->first_init = true; + + /* for debug level */ + rtlpriv->dbg.global_debuglevel = rtlpriv->cfg->mod_params->debug; + /* for LPS & IPS */ + rtlpriv->psc.inactiveps = rtlpriv->cfg->mod_params->inactiveps; + rtlpriv->psc.swctrl_lps = rtlpriv->cfg->mod_params->swctrl_lps; + rtlpriv->psc.fwctrl_lps = rtlpriv->cfg->mod_params->fwctrl_lps; + if (!rtlpriv->psc.inactiveps) + pr_info("Power Save off (module option)\n"); + if (!rtlpriv->psc.fwctrl_lps) + pr_info("FW Power Save off (module option)\n"); + rtlpriv->psc.reg_fwctrl_lps = 3; + rtlpriv->psc.reg_max_lps_awakeintvl = 5; + /* for ASPM, you can close aspm through + * set const_support_pciaspm = 0 */ + rtl92s_init_aspm_vars(hw); + + if (rtlpriv->psc.reg_fwctrl_lps == 1) + rtlpriv->psc.fwctrl_psmode = FW_PS_MIN_MODE; + else if (rtlpriv->psc.reg_fwctrl_lps == 2) + rtlpriv->psc.fwctrl_psmode = FW_PS_MAX_MODE; + else if (rtlpriv->psc.reg_fwctrl_lps == 3) + rtlpriv->psc.fwctrl_psmode = FW_PS_DTIM_MODE; + + /* for firmware buf */ + rtlpriv->rtlhal.pfirmware = vzalloc(sizeof(struct rt_firmware)); + if (!rtlpriv->rtlhal.pfirmware) + return 1; + + rtlpriv->max_fw_size = RTL8190_MAX_RAW_FIRMWARE_CODE_SIZE; + + pr_info("Driver for Realtek RTL8192SE/RTL8191SE\n" + "Loading firmware %s\n", rtlpriv->cfg->fw_name); + /* request fw */ + err = request_firmware_nowait(THIS_MODULE, 1, rtlpriv->cfg->fw_name, + rtlpriv->io.dev, GFP_KERNEL, hw, + rtl92se_fw_cb); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Failed to request firmware!\n"); + return 1; + } + + return err; +} + +static void rtl92s_deinit_sw_vars(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (rtlpriv->rtlhal.pfirmware) { + vfree(rtlpriv->rtlhal.pfirmware); + rtlpriv->rtlhal.pfirmware = NULL; + } +} + +static struct rtl_hal_ops rtl8192se_hal_ops = { + .init_sw_vars = rtl92s_init_sw_vars, + .deinit_sw_vars = rtl92s_deinit_sw_vars, + .read_eeprom_info = rtl92se_read_eeprom_info, + .interrupt_recognized = rtl92se_interrupt_recognized, + .hw_init = rtl92se_hw_init, + .hw_disable = rtl92se_card_disable, + .hw_suspend = rtl92se_suspend, + .hw_resume = rtl92se_resume, + .enable_interrupt = rtl92se_enable_interrupt, + .disable_interrupt = rtl92se_disable_interrupt, + .set_network_type = rtl92se_set_network_type, + .set_chk_bssid = rtl92se_set_check_bssid, + .set_qos = rtl92se_set_qos, + .set_bcn_reg = rtl92se_set_beacon_related_registers, + .set_bcn_intv = rtl92se_set_beacon_interval, + .update_interrupt_mask = rtl92se_update_interrupt_mask, + .get_hw_reg = rtl92se_get_hw_reg, + .set_hw_reg = rtl92se_set_hw_reg, + .update_rate_tbl = rtl92se_update_hal_rate_tbl, + .fill_tx_desc = rtl92se_tx_fill_desc, + .fill_tx_cmddesc = rtl92se_tx_fill_cmddesc, + .query_rx_desc = rtl92se_rx_query_desc, + .set_channel_access = rtl92se_update_channel_access_setting, + .radio_onoff_checking = rtl92se_gpio_radio_on_off_checking, + .set_bw_mode = rtl92s_phy_set_bw_mode, + .switch_channel = rtl92s_phy_sw_chnl, + .dm_watchdog = rtl92s_dm_watchdog, + .scan_operation_backup = rtl92s_phy_scan_operation_backup, + .set_rf_power_state = rtl92s_phy_set_rf_power_state, + .led_control = rtl92se_led_control, + .set_desc = rtl92se_set_desc, + .get_desc = rtl92se_get_desc, + .tx_polling = rtl92se_tx_polling, + .enable_hw_sec = rtl92se_enable_hw_security_config, + .set_key = rtl92se_set_key, + .init_sw_leds = rtl92se_init_sw_leds, + .allow_all_destaddr = rtl92se_allow_all_destaddr, + .get_bbreg = rtl92s_phy_query_bb_reg, + .set_bbreg = rtl92s_phy_set_bb_reg, + .get_rfreg = rtl92s_phy_query_rf_reg, + .set_rfreg = rtl92s_phy_set_rf_reg, +}; + +static struct rtl_mod_params rtl92se_mod_params = { + .sw_crypto = false, + .inactiveps = true, + .swctrl_lps = true, + .fwctrl_lps = false, + .debug = DBG_EMERG, +}; + +/* Because memory R/W bursting will cause system hang/crash + * for 92se, so we don't read back after every write action */ +static struct rtl_hal_cfg rtl92se_hal_cfg = { + .bar_id = 1, + .write_readback = false, + .name = "rtl92s_pci", + .fw_name = "rtlwifi/rtl8192sefw.bin", + .ops = &rtl8192se_hal_ops, + .mod_params = &rtl92se_mod_params, + + .maps[SYS_ISO_CTRL] = REG_SYS_ISO_CTRL, + .maps[SYS_FUNC_EN] = REG_SYS_FUNC_EN, + .maps[SYS_CLK] = SYS_CLKR, + .maps[MAC_RCR_AM] = RCR_AM, + .maps[MAC_RCR_AB] = RCR_AB, + .maps[MAC_RCR_ACRC32] = RCR_ACRC32, + .maps[MAC_RCR_ACF] = RCR_ACF, + .maps[MAC_RCR_AAP] = RCR_AAP, + + .maps[EFUSE_TEST] = REG_EFUSE_TEST, + .maps[EFUSE_CTRL] = REG_EFUSE_CTRL, + .maps[EFUSE_CLK] = REG_EFUSE_CLK, + .maps[EFUSE_CLK_CTRL] = REG_EFUSE_CTRL, + .maps[EFUSE_PWC_EV12V] = 0, /* nouse for 8192se */ + .maps[EFUSE_FEN_ELDR] = 0, /* nouse for 8192se */ + .maps[EFUSE_LOADER_CLK_EN] = 0,/* nouse for 8192se */ + .maps[EFUSE_ANA8M] = EFUSE_ANA8M, + .maps[EFUSE_HWSET_MAX_SIZE] = HWSET_MAX_SIZE_92S, + .maps[EFUSE_MAX_SECTION_MAP] = EFUSE_MAX_SECTION, + .maps[EFUSE_REAL_CONTENT_SIZE] = EFUSE_REAL_CONTENT_LEN, + .maps[EFUSE_OOB_PROTECT_BYTES_LEN] = EFUSE_OOB_PROTECT_BYTES, + + .maps[RWCAM] = REG_RWCAM, + .maps[WCAMI] = REG_WCAMI, + .maps[RCAMO] = REG_RCAMO, + .maps[CAMDBG] = REG_CAMDBG, + .maps[SECR] = REG_SECR, + .maps[SEC_CAM_NONE] = CAM_NONE, + .maps[SEC_CAM_WEP40] = CAM_WEP40, + .maps[SEC_CAM_TKIP] = CAM_TKIP, + .maps[SEC_CAM_AES] = CAM_AES, + .maps[SEC_CAM_WEP104] = CAM_WEP104, + + .maps[RTL_IMR_BCNDMAINT6] = IMR_BCNDMAINT6, + .maps[RTL_IMR_BCNDMAINT5] = IMR_BCNDMAINT5, + .maps[RTL_IMR_BCNDMAINT4] = IMR_BCNDMAINT4, + .maps[RTL_IMR_BCNDMAINT3] = IMR_BCNDMAINT3, + .maps[RTL_IMR_BCNDMAINT2] = IMR_BCNDMAINT2, + .maps[RTL_IMR_BCNDMAINT1] = IMR_BCNDMAINT1, + .maps[RTL_IMR_BCNDOK8] = IMR_BCNDOK8, + .maps[RTL_IMR_BCNDOK7] = IMR_BCNDOK7, + .maps[RTL_IMR_BCNDOK6] = IMR_BCNDOK6, + .maps[RTL_IMR_BCNDOK5] = IMR_BCNDOK5, + .maps[RTL_IMR_BCNDOK4] = IMR_BCNDOK4, + .maps[RTL_IMR_BCNDOK3] = IMR_BCNDOK3, + .maps[RTL_IMR_BCNDOK2] = IMR_BCNDOK2, + .maps[RTL_IMR_BCNDOK1] = IMR_BCNDOK1, + .maps[RTL_IMR_TIMEOUT2] = IMR_TIMEOUT2, + .maps[RTL_IMR_TIMEOUT1] = IMR_TIMEOUT1, + + .maps[RTL_IMR_TXFOVW] = IMR_TXFOVW, + .maps[RTL_IMR_PSTIMEOUT] = IMR_PSTIMEOUT, + .maps[RTL_IMR_BCNINT] = IMR_BCNINT, + .maps[RTL_IMR_RXFOVW] = IMR_RXFOVW, + .maps[RTL_IMR_RDU] = IMR_RDU, + .maps[RTL_IMR_ATIMEND] = IMR_ATIMEND, + .maps[RTL_IMR_BDOK] = IMR_BDOK, + .maps[RTL_IMR_MGNTDOK] = IMR_MGNTDOK, + .maps[RTL_IMR_TBDER] = IMR_TBDER, + .maps[RTL_IMR_HIGHDOK] = IMR_HIGHDOK, + .maps[RTL_IMR_COMDOK] = IMR_COMDOK, + .maps[RTL_IMR_TBDOK] = IMR_TBDOK, + .maps[RTL_IMR_BKDOK] = IMR_BKDOK, + .maps[RTL_IMR_BEDOK] = IMR_BEDOK, + .maps[RTL_IMR_VIDOK] = IMR_VIDOK, + .maps[RTL_IMR_VODOK] = IMR_VODOK, + .maps[RTL_IMR_ROK] = IMR_ROK, + .maps[RTL_IBSS_INT_MASKS] = (IMR_BCNINT | IMR_TBDOK | IMR_TBDER), + + .maps[RTL_RC_CCK_RATE1M] = DESC92_RATE1M, + .maps[RTL_RC_CCK_RATE2M] = DESC92_RATE2M, + .maps[RTL_RC_CCK_RATE5_5M] = DESC92_RATE5_5M, + .maps[RTL_RC_CCK_RATE11M] = DESC92_RATE11M, + .maps[RTL_RC_OFDM_RATE6M] = DESC92_RATE6M, + .maps[RTL_RC_OFDM_RATE9M] = DESC92_RATE9M, + .maps[RTL_RC_OFDM_RATE12M] = DESC92_RATE12M, + .maps[RTL_RC_OFDM_RATE18M] = DESC92_RATE18M, + .maps[RTL_RC_OFDM_RATE24M] = DESC92_RATE24M, + .maps[RTL_RC_OFDM_RATE36M] = DESC92_RATE36M, + .maps[RTL_RC_OFDM_RATE48M] = DESC92_RATE48M, + .maps[RTL_RC_OFDM_RATE54M] = DESC92_RATE54M, + + .maps[RTL_RC_HT_RATEMCS7] = DESC92_RATEMCS7, + .maps[RTL_RC_HT_RATEMCS15] = DESC92_RATEMCS15, +}; + +static struct pci_device_id rtl92se_pci_ids[] = { + {RTL_PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8192, rtl92se_hal_cfg)}, + {RTL_PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8171, rtl92se_hal_cfg)}, + {RTL_PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8172, rtl92se_hal_cfg)}, + {RTL_PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8173, rtl92se_hal_cfg)}, + {RTL_PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8174, rtl92se_hal_cfg)}, + {}, +}; + +MODULE_DEVICE_TABLE(pci, rtl92se_pci_ids); + +MODULE_AUTHOR("lizhaoming "); +MODULE_AUTHOR("Realtek WlanFAE "); +MODULE_AUTHOR("Larry Finger "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Realtek 8192S/8191S 802.11n PCI wireless"); +MODULE_FIRMWARE("rtlwifi/rtl8192sefw.bin"); + +module_param_named(swenc, rtl92se_mod_params.sw_crypto, bool, 0444); +module_param_named(debug, rtl92se_mod_params.debug, int, 0444); +module_param_named(ips, rtl92se_mod_params.inactiveps, bool, 0444); +module_param_named(swlps, rtl92se_mod_params.swctrl_lps, bool, 0444); +module_param_named(fwlps, rtl92se_mod_params.fwctrl_lps, bool, 0444); +MODULE_PARM_DESC(swenc, "Set to 1 for software crypto (default 0)\n"); +MODULE_PARM_DESC(ips, "Set to 0 to not use link power save (default 1)\n"); +MODULE_PARM_DESC(swlps, "Set to 1 to use SW control power save (default 0)\n"); +MODULE_PARM_DESC(fwlps, "Set to 1 to use FW control power save (default 1)\n"); +MODULE_PARM_DESC(debug, "Set debug level (0-5) (default 0)"); + +compat_pci_suspend(rtl_pci_suspend); +compat_pci_resume(rtl_pci_resume); + +static SIMPLE_DEV_PM_OPS(rtlwifi_pm_ops, rtl_pci_suspend, rtl_pci_resume); + +static struct pci_driver rtl92se_driver = { + .name = KBUILD_MODNAME, + .id_table = rtl92se_pci_ids, + .probe = rtl_pci_probe, + .remove = rtl_pci_disconnect, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) + .driver.pm = &rtlwifi_pm_ops, +#elif defined(CONFIG_PM_SLEEP) + .suspend = rtl_pci_suspend_compat, + .resume = rtl_pci_resume_compat, +#endif +}; + +module_pci_driver(rtl92se_driver); diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/sw.h b/drivers/net/wireless/rtlwifi/rtl8192se/sw.h new file mode 100644 index 0000000..2eb8886 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/sw.h @@ -0,0 +1,36 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + *****************************************************************************/ +#ifndef __REALTEK_PCI92SE_SW_H__ +#define __REALTEK_PCI92SE_SW_H__ + +#define EFUSE_MAX_SECTION 16 + +int rtl92se_init_sw(struct ieee80211_hw *hw); +void rtl92se_deinit_sw(struct ieee80211_hw *hw); +void rtl92se_init_var_map(struct ieee80211_hw *hw); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/table.c b/drivers/net/wireless/rtlwifi/rtl8192se/table.c new file mode 100644 index 0000000..f1a73f7 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/table.c @@ -0,0 +1,634 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + * Created on 2010/ 5/18, 1:41 + *****************************************************************************/ + +#include "table.h" + +u32 rtl8192sephy_reg_2t2rarray[PHY_REG_2T2RARRAYLENGTH] = { + 0x01c, 0x07000000, + 0x800, 0x00040000, + 0x804, 0x00008003, + 0x808, 0x0000fc00, + 0x80c, 0x0000000a, + 0x810, 0x10005088, + 0x814, 0x020c3d10, + 0x818, 0x00200185, + 0x81c, 0x00000000, + 0x820, 0x01000000, + 0x824, 0x00390004, + 0x828, 0x01000000, + 0x82c, 0x00390004, + 0x830, 0x00000004, + 0x834, 0x00690200, + 0x838, 0x00000004, + 0x83c, 0x00690200, + 0x840, 0x00010000, + 0x844, 0x00010000, + 0x848, 0x00000000, + 0x84c, 0x00000000, + 0x850, 0x00000000, + 0x854, 0x00000000, + 0x858, 0x48484848, + 0x85c, 0x65a965a9, + 0x860, 0x0f7f0130, + 0x864, 0x0f7f0130, + 0x868, 0x0f7f0130, + 0x86c, 0x0f7f0130, + 0x870, 0x03000700, + 0x874, 0x03000300, + 0x878, 0x00020002, + 0x87c, 0x004f0201, + 0x880, 0xa8300ac1, + 0x884, 0x00000058, + 0x888, 0x00000008, + 0x88c, 0x00000004, + 0x890, 0x00000000, + 0x894, 0xfffffffe, + 0x898, 0x40302010, + 0x89c, 0x00706050, + 0x8b0, 0x00000000, + 0x8e0, 0x00000000, + 0x8e4, 0x00000000, + 0xe00, 0x30333333, + 0xe04, 0x2a2d2e2f, + 0xe08, 0x00003232, + 0xe10, 0x30333333, + 0xe14, 0x2a2d2e2f, + 0xe18, 0x30333333, + 0xe1c, 0x2a2d2e2f, + 0xe30, 0x01007c00, + 0xe34, 0x01004800, + 0xe38, 0x1000dc1f, + 0xe3c, 0x10008c1f, + 0xe40, 0x021400a0, + 0xe44, 0x281600a0, + 0xe48, 0xf8000001, + 0xe4c, 0x00002910, + 0xe50, 0x01007c00, + 0xe54, 0x01004800, + 0xe58, 0x1000dc1f, + 0xe5c, 0x10008c1f, + 0xe60, 0x021400a0, + 0xe64, 0x281600a0, + 0xe6c, 0x00002910, + 0xe70, 0x31ed92fb, + 0xe74, 0x361536fb, + 0xe78, 0x361536fb, + 0xe7c, 0x361536fb, + 0xe80, 0x361536fb, + 0xe84, 0x000d92fb, + 0xe88, 0x000d92fb, + 0xe8c, 0x31ed92fb, + 0xed0, 0x31ed92fb, + 0xed4, 0x31ed92fb, + 0xed8, 0x000d92fb, + 0xedc, 0x000d92fb, + 0xee0, 0x000d92fb, + 0xee4, 0x015e5448, + 0xee8, 0x21555448, + 0x900, 0x00000000, + 0x904, 0x00000023, + 0x908, 0x00000000, + 0x90c, 0x01121313, + 0xa00, 0x00d047c8, + 0xa04, 0x80ff0008, + 0xa08, 0x8ccd8300, + 0xa0c, 0x2e62120f, + 0xa10, 0x9500bb78, + 0xa14, 0x11144028, + 0xa18, 0x00881117, + 0xa1c, 0x89140f00, + 0xa20, 0x1a1b0000, + 0xa24, 0x090e1317, + 0xa28, 0x00000204, + 0xa2c, 0x10d30000, + 0xc00, 0x40071d40, + 0xc04, 0x00a05633, + 0xc08, 0x000000e4, + 0xc0c, 0x6c6c6c6c, + 0xc10, 0x08800000, + 0xc14, 0x40000100, + 0xc18, 0x08000000, + 0xc1c, 0x40000100, + 0xc20, 0x08000000, + 0xc24, 0x40000100, + 0xc28, 0x08000000, + 0xc2c, 0x40000100, + 0xc30, 0x6de9ac44, + 0xc34, 0x469652cf, + 0xc38, 0x49795994, + 0xc3c, 0x0a979764, + 0xc40, 0x1f7c403f, + 0xc44, 0x000100b7, + 0xc48, 0xec020000, + 0xc4c, 0x007f037f, + 0xc50, 0x69543420, + 0xc54, 0x433c0094, + 0xc58, 0x69543420, + 0xc5c, 0x433c0094, + 0xc60, 0x69543420, + 0xc64, 0x433c0094, + 0xc68, 0x69543420, + 0xc6c, 0x433c0094, + 0xc70, 0x2c7f000d, + 0xc74, 0x0186155b, + 0xc78, 0x0000001f, + 0xc7c, 0x00b91612, + 0xc80, 0x40000100, + 0xc84, 0x20f60000, + 0xc88, 0x20000080, + 0xc8c, 0x20200000, + 0xc90, 0x40000100, + 0xc94, 0x00000000, + 0xc98, 0x40000100, + 0xc9c, 0x00000000, + 0xca0, 0x00492492, + 0xca4, 0x00000000, + 0xca8, 0x00000000, + 0xcac, 0x00000000, + 0xcb0, 0x00000000, + 0xcb4, 0x00000000, + 0xcb8, 0x00000000, + 0xcbc, 0x28000000, + 0xcc0, 0x00000000, + 0xcc4, 0x00000000, + 0xcc8, 0x00000000, + 0xccc, 0x00000000, + 0xcd0, 0x00000000, + 0xcd4, 0x00000000, + 0xcd8, 0x64b22427, + 0xcdc, 0x00766932, + 0xce0, 0x00222222, + 0xce4, 0x00000000, + 0xce8, 0x37644302, + 0xcec, 0x2f97d40c, + 0xd00, 0x00000750, + 0xd04, 0x00000403, + 0xd08, 0x0000907f, + 0xd0c, 0x00000001, + 0xd10, 0xa0633333, + 0xd14, 0x33333c63, + 0xd18, 0x6a8f5b6b, + 0xd1c, 0x00000000, + 0xd20, 0x00000000, + 0xd24, 0x00000000, + 0xd28, 0x00000000, + 0xd2c, 0xcc979975, + 0xd30, 0x00000000, + 0xd34, 0x00000000, + 0xd38, 0x00000000, + 0xd3c, 0x00027293, + 0xd40, 0x00000000, + 0xd44, 0x00000000, + 0xd48, 0x00000000, + 0xd50, 0x6437140a, + 0xd54, 0x024dbd02, + 0xd58, 0x00000000, + 0xd5c, 0x30032064, + 0xd60, 0x4653de68, + 0xd64, 0x00518a3c, + 0xd68, 0x00002101, + 0xf14, 0x00000003, + 0xf4c, 0x00000000, + 0xf00, 0x00000300, +}; + +u32 rtl8192sephy_changeto_1t1rarray[PHY_CHANGETO_1T1RARRAYLENGTH] = { + 0x844, 0xffffffff, 0x00010000, + 0x804, 0x0000000f, 0x00000001, + 0x824, 0x00f0000f, 0x00300004, + 0x82c, 0x00f0000f, 0x00100002, + 0x870, 0x04000000, 0x00000001, + 0x864, 0x00000400, 0x00000000, + 0x878, 0x000f000f, 0x00000002, + 0xe74, 0x0f000000, 0x00000002, + 0xe78, 0x0f000000, 0x00000002, + 0xe7c, 0x0f000000, 0x00000002, + 0xe80, 0x0f000000, 0x00000002, + 0x90c, 0x000000ff, 0x00000011, + 0xc04, 0x000000ff, 0x00000011, + 0xd04, 0x0000000f, 0x00000001, + 0x1f4, 0xffff0000, 0x00007777, + 0x234, 0xf8000000, 0x0000000a, +}; + +u32 rtl8192sephy_changeto_1t2rarray[PHY_CHANGETO_1T2RARRAYLENGTH] = { + 0x804, 0x0000000f, 0x00000003, + 0x824, 0x00f0000f, 0x00300004, + 0x82c, 0x00f0000f, 0x00300002, + 0x870, 0x04000000, 0x00000001, + 0x864, 0x00000400, 0x00000000, + 0x878, 0x000f000f, 0x00000002, + 0xe74, 0x0f000000, 0x00000002, + 0xe78, 0x0f000000, 0x00000002, + 0xe7c, 0x0f000000, 0x00000002, + 0xe80, 0x0f000000, 0x00000002, + 0x90c, 0x000000ff, 0x00000011, + 0xc04, 0x000000ff, 0x00000033, + 0xd04, 0x0000000f, 0x00000003, + 0x1f4, 0xffff0000, 0x00007777, + 0x234, 0xf8000000, 0x0000000a, +}; + +u32 rtl8192sephy_reg_array_pg[PHY_REG_ARRAY_PGLENGTH] = { + 0xe00, 0xffffffff, 0x06090909, + 0xe04, 0xffffffff, 0x00030406, + 0xe08, 0x0000ff00, 0x00000000, + 0xe10, 0xffffffff, 0x0a0c0d0e, + 0xe14, 0xffffffff, 0x04070809, + 0xe18, 0xffffffff, 0x0a0c0d0e, + 0xe1c, 0xffffffff, 0x04070809, + 0xe00, 0xffffffff, 0x04040404, + 0xe04, 0xffffffff, 0x00020204, + 0xe08, 0x0000ff00, 0x00000000, + 0xe10, 0xffffffff, 0x02040404, + 0xe14, 0xffffffff, 0x00000002, + 0xe18, 0xffffffff, 0x02040404, + 0xe1c, 0xffffffff, 0x00000002, + 0xe00, 0xffffffff, 0x04040404, + 0xe04, 0xffffffff, 0x00020204, + 0xe08, 0x0000ff00, 0x00000000, + 0xe10, 0xffffffff, 0x02040404, + 0xe14, 0xffffffff, 0x00000002, + 0xe18, 0xffffffff, 0x02040404, + 0xe1c, 0xffffffff, 0x00000002, + 0xe00, 0xffffffff, 0x02020202, + 0xe04, 0xffffffff, 0x00020202, + 0xe08, 0x0000ff00, 0x00000000, + 0xe10, 0xffffffff, 0x02020202, + 0xe14, 0xffffffff, 0x00000002, + 0xe18, 0xffffffff, 0x02020202, + 0xe1c, 0xffffffff, 0x00000002, +}; + +u32 rtl8192seradioa_1t_array[RADIOA_1T_ARRAYLENGTH] = { + 0x000, 0x00030159, + 0x001, 0x00030250, + 0x002, 0x00010000, + 0x010, 0x0008000f, + 0x011, 0x000231fc, + 0x010, 0x000c000f, + 0x011, 0x0003f9f8, + 0x010, 0x0002000f, + 0x011, 0x00020101, + 0x014, 0x0001093e, + 0x014, 0x0009093e, + 0x015, 0x0000f8f4, + 0x017, 0x000f6500, + 0x01a, 0x00013056, + 0x01b, 0x00060000, + 0x01c, 0x00000300, + 0x01e, 0x00031059, + 0x021, 0x00054000, + 0x022, 0x0000083c, + 0x023, 0x00001558, + 0x024, 0x00000060, + 0x025, 0x00022583, + 0x026, 0x0000f200, + 0x027, 0x000eacf1, + 0x028, 0x0009bd54, + 0x029, 0x00004582, + 0x02a, 0x00000001, + 0x02b, 0x00021334, + 0x02a, 0x00000000, + 0x02b, 0x0000000a, + 0x02a, 0x00000001, + 0x02b, 0x00000808, + 0x02b, 0x00053333, + 0x02c, 0x0000000c, + 0x02a, 0x00000002, + 0x02b, 0x00000808, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000003, + 0x02b, 0x00000808, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x00000004, + 0x02b, 0x00000808, + 0x02b, 0x0006b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000005, + 0x02b, 0x00000709, + 0x02b, 0x00053333, + 0x02c, 0x0000000d, + 0x02a, 0x00000006, + 0x02b, 0x00000709, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000007, + 0x02b, 0x00000709, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x00000008, + 0x02b, 0x00000709, + 0x02b, 0x0006b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000009, + 0x02b, 0x0000060a, + 0x02b, 0x00053333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000a, + 0x02b, 0x0000060a, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000b, + 0x02b, 0x0000060a, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000c, + 0x02b, 0x0000060a, + 0x02b, 0x0006b333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000d, + 0x02b, 0x0000050b, + 0x02b, 0x00053333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000e, + 0x02b, 0x0000050b, + 0x02b, 0x00066623, + 0x02c, 0x0000001a, + 0x02a, 0x000e4000, + 0x030, 0x00020000, + 0x031, 0x000b9631, + 0x032, 0x0000130d, + 0x033, 0x00000187, + 0x013, 0x00019e6c, + 0x013, 0x00015e94, + 0x000, 0x00010159, + 0x018, 0x0000f401, + 0x0fe, 0x00000000, + 0x01e, 0x0003105b, + 0x0fe, 0x00000000, + 0x000, 0x00030159, + 0x010, 0x0004000f, + 0x011, 0x000203f9, +}; + +u32 rtl8192seradiob_array[RADIOB_ARRAYLENGTH] = { + 0x000, 0x00030159, + 0x001, 0x00001041, + 0x002, 0x00011000, + 0x005, 0x00080fc0, + 0x007, 0x000fc803, + 0x013, 0x00017cb0, + 0x013, 0x00011cc0, + 0x013, 0x0000dc60, + 0x013, 0x00008c60, + 0x013, 0x00004450, + 0x013, 0x00000020, +}; + +u32 rtl8192seradiob_gm_array[RADIOB_GM_ARRAYLENGTH] = { + 0x000, 0x00030159, + 0x001, 0x00001041, + 0x002, 0x00011000, + 0x005, 0x00080fc0, + 0x007, 0x000fc803, +}; + +u32 rtl8192semac_2t_array[MAC_2T_ARRAYLENGTH] = { + 0x020, 0x00000035, + 0x048, 0x0000000e, + 0x049, 0x000000f0, + 0x04a, 0x00000077, + 0x04b, 0x00000083, + 0x0b5, 0x00000021, + 0x0dc, 0x000000ff, + 0x0dd, 0x000000ff, + 0x0de, 0x000000ff, + 0x0df, 0x000000ff, + 0x116, 0x00000000, + 0x117, 0x00000000, + 0x118, 0x00000000, + 0x119, 0x00000000, + 0x11a, 0x00000000, + 0x11b, 0x00000000, + 0x11c, 0x00000000, + 0x11d, 0x00000000, + 0x160, 0x0000000b, + 0x161, 0x0000000b, + 0x162, 0x0000000b, + 0x163, 0x0000000b, + 0x164, 0x0000000b, + 0x165, 0x0000000b, + 0x166, 0x0000000b, + 0x167, 0x0000000b, + 0x168, 0x0000000b, + 0x169, 0x0000000b, + 0x16a, 0x0000000b, + 0x16b, 0x0000000b, + 0x16c, 0x0000000b, + 0x16d, 0x0000000b, + 0x16e, 0x0000000b, + 0x16f, 0x0000000b, + 0x170, 0x0000000b, + 0x171, 0x0000000b, + 0x172, 0x0000000b, + 0x173, 0x0000000b, + 0x174, 0x0000000b, + 0x175, 0x0000000b, + 0x176, 0x0000000b, + 0x177, 0x0000000b, + 0x178, 0x0000000b, + 0x179, 0x0000000b, + 0x17a, 0x0000000b, + 0x17b, 0x0000000b, + 0x17c, 0x0000000b, + 0x17d, 0x0000000b, + 0x17e, 0x0000000b, + 0x17f, 0x0000000b, + 0x236, 0x0000000c, + 0x503, 0x00000022, + 0x560, 0x00000000, +}; + +u32 rtl8192seagctab_array[AGCTAB_ARRAYLENGTH] = { + 0xc78, 0x7f000001, + 0xc78, 0x7f010001, + 0xc78, 0x7e020001, + 0xc78, 0x7d030001, + 0xc78, 0x7c040001, + 0xc78, 0x7b050001, + 0xc78, 0x7a060001, + 0xc78, 0x79070001, + 0xc78, 0x78080001, + 0xc78, 0x77090001, + 0xc78, 0x760a0001, + 0xc78, 0x750b0001, + 0xc78, 0x740c0001, + 0xc78, 0x730d0001, + 0xc78, 0x720e0001, + 0xc78, 0x710f0001, + 0xc78, 0x70100001, + 0xc78, 0x6f110001, + 0xc78, 0x6f120001, + 0xc78, 0x6e130001, + 0xc78, 0x6d140001, + 0xc78, 0x6d150001, + 0xc78, 0x6c160001, + 0xc78, 0x6b170001, + 0xc78, 0x6a180001, + 0xc78, 0x6a190001, + 0xc78, 0x691a0001, + 0xc78, 0x681b0001, + 0xc78, 0x671c0001, + 0xc78, 0x661d0001, + 0xc78, 0x651e0001, + 0xc78, 0x641f0001, + 0xc78, 0x63200001, + 0xc78, 0x4c210001, + 0xc78, 0x4b220001, + 0xc78, 0x4a230001, + 0xc78, 0x49240001, + 0xc78, 0x48250001, + 0xc78, 0x47260001, + 0xc78, 0x46270001, + 0xc78, 0x45280001, + 0xc78, 0x44290001, + 0xc78, 0x2c2a0001, + 0xc78, 0x2b2b0001, + 0xc78, 0x2a2c0001, + 0xc78, 0x292d0001, + 0xc78, 0x282e0001, + 0xc78, 0x272f0001, + 0xc78, 0x26300001, + 0xc78, 0x25310001, + 0xc78, 0x24320001, + 0xc78, 0x23330001, + 0xc78, 0x22340001, + 0xc78, 0x09350001, + 0xc78, 0x08360001, + 0xc78, 0x07370001, + 0xc78, 0x06380001, + 0xc78, 0x05390001, + 0xc78, 0x043a0001, + 0xc78, 0x033b0001, + 0xc78, 0x023c0001, + 0xc78, 0x013d0001, + 0xc78, 0x003e0001, + 0xc78, 0x003f0001, + 0xc78, 0x7f400001, + 0xc78, 0x7f410001, + 0xc78, 0x7e420001, + 0xc78, 0x7d430001, + 0xc78, 0x7c440001, + 0xc78, 0x7b450001, + 0xc78, 0x7a460001, + 0xc78, 0x79470001, + 0xc78, 0x78480001, + 0xc78, 0x77490001, + 0xc78, 0x764a0001, + 0xc78, 0x754b0001, + 0xc78, 0x744c0001, + 0xc78, 0x734d0001, + 0xc78, 0x724e0001, + 0xc78, 0x714f0001, + 0xc78, 0x70500001, + 0xc78, 0x6f510001, + 0xc78, 0x6f520001, + 0xc78, 0x6e530001, + 0xc78, 0x6d540001, + 0xc78, 0x6d550001, + 0xc78, 0x6c560001, + 0xc78, 0x6b570001, + 0xc78, 0x6a580001, + 0xc78, 0x6a590001, + 0xc78, 0x695a0001, + 0xc78, 0x685b0001, + 0xc78, 0x675c0001, + 0xc78, 0x665d0001, + 0xc78, 0x655e0001, + 0xc78, 0x645f0001, + 0xc78, 0x63600001, + 0xc78, 0x4c610001, + 0xc78, 0x4b620001, + 0xc78, 0x4a630001, + 0xc78, 0x49640001, + 0xc78, 0x48650001, + 0xc78, 0x47660001, + 0xc78, 0x46670001, + 0xc78, 0x45680001, + 0xc78, 0x44690001, + 0xc78, 0x2c6a0001, + 0xc78, 0x2b6b0001, + 0xc78, 0x2a6c0001, + 0xc78, 0x296d0001, + 0xc78, 0x286e0001, + 0xc78, 0x276f0001, + 0xc78, 0x26700001, + 0xc78, 0x25710001, + 0xc78, 0x24720001, + 0xc78, 0x23730001, + 0xc78, 0x22740001, + 0xc78, 0x09750001, + 0xc78, 0x08760001, + 0xc78, 0x07770001, + 0xc78, 0x06780001, + 0xc78, 0x05790001, + 0xc78, 0x047a0001, + 0xc78, 0x037b0001, + 0xc78, 0x027c0001, + 0xc78, 0x017d0001, + 0xc78, 0x007e0001, + 0xc78, 0x007f0001, + 0xc78, 0x3000001e, + 0xc78, 0x3001001e, + 0xc78, 0x3002001e, + 0xc78, 0x3003001e, + 0xc78, 0x3004001e, + 0xc78, 0x3405001e, + 0xc78, 0x3806001e, + 0xc78, 0x3e07001e, + 0xc78, 0x3e08001e, + 0xc78, 0x4409001e, + 0xc78, 0x460a001e, + 0xc78, 0x480b001e, + 0xc78, 0x480c001e, + 0xc78, 0x4e0d001e, + 0xc78, 0x560e001e, + 0xc78, 0x5a0f001e, + 0xc78, 0x5e10001e, + 0xc78, 0x6211001e, + 0xc78, 0x6c12001e, + 0xc78, 0x7213001e, + 0xc78, 0x7214001e, + 0xc78, 0x7215001e, + 0xc78, 0x7216001e, + 0xc78, 0x7217001e, + 0xc78, 0x7218001e, + 0xc78, 0x7219001e, + 0xc78, 0x721a001e, + 0xc78, 0x721b001e, + 0xc78, 0x721c001e, + 0xc78, 0x721d001e, + 0xc78, 0x721e001e, + 0xc78, 0x721f001e, +}; + diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/table.h b/drivers/net/wireless/rtlwifi/rtl8192se/table.h new file mode 100644 index 0000000..2feb73b --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/table.h @@ -0,0 +1,49 @@ +/****************************************************************************** + * Copyright(c) 2008 - 2012 Realtek Corporation. All rights reserved. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * + * Larry Finger + * + ******************************************************************************/ +#ifndef __INC_HAL8192SE_FW_IMG_H +#define __INC_HAL8192SE_FW_IMG_H + +#include + +/*Created on 2010/ 4/12, 5:56*/ + +#define PHY_REG_2T2RARRAYLENGTH 372 +extern u32 rtl8192sephy_reg_2t2rarray[PHY_REG_2T2RARRAYLENGTH]; +#define PHY_CHANGETO_1T1RARRAYLENGTH 48 +extern u32 rtl8192sephy_changeto_1t1rarray[PHY_CHANGETO_1T1RARRAYLENGTH]; +#define PHY_CHANGETO_1T2RARRAYLENGTH 45 +extern u32 rtl8192sephy_changeto_1t2rarray[PHY_CHANGETO_1T2RARRAYLENGTH]; +#define PHY_REG_ARRAY_PGLENGTH 84 +extern u32 rtl8192sephy_reg_array_pg[PHY_REG_ARRAY_PGLENGTH]; +#define RADIOA_1T_ARRAYLENGTH 202 +extern u32 rtl8192seradioa_1t_array[RADIOA_1T_ARRAYLENGTH]; +#define RADIOB_ARRAYLENGTH 22 +extern u32 rtl8192seradiob_array[RADIOB_ARRAYLENGTH]; +#define RADIOB_GM_ARRAYLENGTH 10 +extern u32 rtl8192seradiob_gm_array[RADIOB_GM_ARRAYLENGTH]; +#define MAC_2T_ARRAYLENGTH 106 +extern u32 rtl8192semac_2t_array[MAC_2T_ARRAYLENGTH]; +#define AGCTAB_ARRAYLENGTH 320 +extern u32 rtl8192seagctab_array[AGCTAB_ARRAYLENGTH]; + +#endif + diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/trx.c b/drivers/net/wireless/rtlwifi/rtl8192se/trx.c new file mode 100644 index 0000000..c709511 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/trx.c @@ -0,0 +1,654 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "../base.h" +#include "../stats.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "fw.h" +#include "trx.h" +#include "led.h" + +static u8 _rtl92se_map_hwqueue_to_fwqueue(struct sk_buff *skb, u8 skb_queue) +{ + __le16 fc = rtl_get_fc(skb); + + if (unlikely(ieee80211_is_beacon(fc))) + return QSLT_BEACON; + if (ieee80211_is_mgmt(fc) || ieee80211_is_ctl(fc)) + return QSLT_MGNT; + if (ieee80211_is_nullfunc(fc)) + return QSLT_HIGH; + + return skb->priority; +} + +static void _rtl92se_query_rxphystatus(struct ieee80211_hw *hw, + struct rtl_stats *pstats, u8 *pdesc, + struct rx_fwinfo *p_drvinfo, + bool packet_match_bssid, + bool packet_toself, + bool packet_beacon) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct phy_sts_cck_8192s_t *cck_buf; + struct rtl_ps_ctl *ppsc = rtl_psc(rtlpriv); + s8 rx_pwr_all = 0, rx_pwr[4]; + u8 rf_rx_num = 0, evm, pwdb_all; + u8 i, max_spatial_stream; + u32 rssi, total_rssi = 0; + bool is_cck = pstats->is_cck; + + pstats->packet_matchbssid = packet_match_bssid; + pstats->packet_toself = packet_toself; + pstats->packet_beacon = packet_beacon; + pstats->rx_mimo_sig_qual[0] = -1; + pstats->rx_mimo_sig_qual[1] = -1; + + if (is_cck) { + u8 report, cck_highpwr; + cck_buf = (struct phy_sts_cck_8192s_t *)p_drvinfo; + + if (ppsc->rfpwr_state == ERFON) + cck_highpwr = (u8) rtl_get_bbreg(hw, + RFPGA0_XA_HSSIPARAMETER2, + 0x200); + else + cck_highpwr = false; + + if (!cck_highpwr) { + u8 cck_agc_rpt = cck_buf->cck_agc_rpt; + report = cck_buf->cck_agc_rpt & 0xc0; + report = report >> 6; + switch (report) { + case 0x3: + rx_pwr_all = -40 - (cck_agc_rpt & 0x3e); + break; + case 0x2: + rx_pwr_all = -20 - (cck_agc_rpt & 0x3e); + break; + case 0x1: + rx_pwr_all = -2 - (cck_agc_rpt & 0x3e); + break; + case 0x0: + rx_pwr_all = 14 - (cck_agc_rpt & 0x3e); + break; + } + } else { + u8 cck_agc_rpt = cck_buf->cck_agc_rpt; + report = p_drvinfo->cfosho[0] & 0x60; + report = report >> 5; + switch (report) { + case 0x3: + rx_pwr_all = -40 - ((cck_agc_rpt & 0x1f) << 1); + break; + case 0x2: + rx_pwr_all = -20 - ((cck_agc_rpt & 0x1f) << 1); + break; + case 0x1: + rx_pwr_all = -2 - ((cck_agc_rpt & 0x1f) << 1); + break; + case 0x0: + rx_pwr_all = 14 - ((cck_agc_rpt & 0x1f) << 1); + break; + } + } + + pwdb_all = rtl_query_rxpwrpercentage(rx_pwr_all); + + /* CCK gain is smaller than OFDM/MCS gain, */ + /* so we add gain diff by experiences, the val is 6 */ + pwdb_all += 6; + if (pwdb_all > 100) + pwdb_all = 100; + /* modify the offset to make the same gain index with OFDM. */ + if (pwdb_all > 34 && pwdb_all <= 42) + pwdb_all -= 2; + else if (pwdb_all > 26 && pwdb_all <= 34) + pwdb_all -= 6; + else if (pwdb_all > 14 && pwdb_all <= 26) + pwdb_all -= 8; + else if (pwdb_all > 4 && pwdb_all <= 14) + pwdb_all -= 4; + + pstats->rx_pwdb_all = pwdb_all; + pstats->recvsignalpower = rx_pwr_all; + + if (packet_match_bssid) { + u8 sq; + if (pstats->rx_pwdb_all > 40) { + sq = 100; + } else { + sq = cck_buf->sq_rpt; + if (sq > 64) + sq = 0; + else if (sq < 20) + sq = 100; + else + sq = ((64 - sq) * 100) / 44; + } + + pstats->signalquality = sq; + pstats->rx_mimo_sig_qual[0] = sq; + pstats->rx_mimo_sig_qual[1] = -1; + } + } else { + rtlpriv->dm.rfpath_rxenable[0] = + rtlpriv->dm.rfpath_rxenable[1] = true; + for (i = RF90_PATH_A; i < RF6052_MAX_PATH; i++) { + if (rtlpriv->dm.rfpath_rxenable[i]) + rf_rx_num++; + + rx_pwr[i] = ((p_drvinfo->gain_trsw[i] & + 0x3f) * 2) - 110; + rssi = rtl_query_rxpwrpercentage(rx_pwr[i]); + total_rssi += rssi; + rtlpriv->stats.rx_snr_db[i] = + (long)(p_drvinfo->rxsnr[i] / 2); + + if (packet_match_bssid) + pstats->rx_mimo_signalstrength[i] = (u8) rssi; + } + + rx_pwr_all = ((p_drvinfo->pwdb_all >> 1) & 0x7f) - 110; + pwdb_all = rtl_query_rxpwrpercentage(rx_pwr_all); + pstats->rx_pwdb_all = pwdb_all; + pstats->rxpower = rx_pwr_all; + pstats->recvsignalpower = rx_pwr_all; + + if (pstats->is_ht && pstats->rate >= DESC92_RATEMCS8 && + pstats->rate <= DESC92_RATEMCS15) + max_spatial_stream = 2; + else + max_spatial_stream = 1; + + for (i = 0; i < max_spatial_stream; i++) { + evm = rtl_evm_db_to_percentage(p_drvinfo->rxevm[i]); + + if (packet_match_bssid) { + if (i == 0) + pstats->signalquality = (u8)(evm & + 0xff); + pstats->rx_mimo_sig_qual[i] = (u8) (evm & 0xff); + } + } + } + + if (is_cck) + pstats->signalstrength = (u8)(rtl_signal_scale_mapping(hw, + pwdb_all)); + else if (rf_rx_num != 0) + pstats->signalstrength = (u8) (rtl_signal_scale_mapping(hw, + total_rssi /= rf_rx_num)); +} + +static void _rtl92se_translate_rx_signal_stuff(struct ieee80211_hw *hw, + struct sk_buff *skb, struct rtl_stats *pstats, + u8 *pdesc, struct rx_fwinfo *p_drvinfo) +{ + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + + struct ieee80211_hdr *hdr; + u8 *tmp_buf; + u8 *praddr; + __le16 fc; + u16 type, cfc; + bool packet_matchbssid, packet_toself, packet_beacon = false; + + tmp_buf = skb->data + pstats->rx_drvinfo_size + pstats->rx_bufshift; + + hdr = (struct ieee80211_hdr *)tmp_buf; + fc = hdr->frame_control; + cfc = le16_to_cpu(fc); + type = WLAN_FC_GET_TYPE(fc); + praddr = hdr->addr1; + + packet_matchbssid = ((IEEE80211_FTYPE_CTL != type) && + ether_addr_equal(mac->bssid, + (cfc & IEEE80211_FCTL_TODS) ? hdr->addr1 : + (cfc & IEEE80211_FCTL_FROMDS) ? hdr->addr2 : + hdr->addr3) && + (!pstats->hwerror) && (!pstats->crc) && (!pstats->icv)); + + packet_toself = packet_matchbssid && + ether_addr_equal(praddr, rtlefuse->dev_addr); + + if (ieee80211_is_beacon(fc)) + packet_beacon = true; + + _rtl92se_query_rxphystatus(hw, pstats, pdesc, p_drvinfo, + packet_matchbssid, packet_toself, packet_beacon); + rtl_process_phyinfo(hw, tmp_buf, pstats); +} + +bool rtl92se_rx_query_desc(struct ieee80211_hw *hw, struct rtl_stats *stats, + struct ieee80211_rx_status *rx_status, u8 *pdesc, + struct sk_buff *skb) +{ + struct rx_fwinfo *p_drvinfo; + u32 phystatus = (u32)GET_RX_STATUS_DESC_PHY_STATUS(pdesc); + struct ieee80211_hdr *hdr; + bool first_ampdu = false; + + stats->length = (u16)GET_RX_STATUS_DESC_PKT_LEN(pdesc); + stats->rx_drvinfo_size = (u8)GET_RX_STATUS_DESC_DRVINFO_SIZE(pdesc) * 8; + stats->rx_bufshift = (u8)(GET_RX_STATUS_DESC_SHIFT(pdesc) & 0x03); + stats->icv = (u16)GET_RX_STATUS_DESC_ICV(pdesc); + stats->crc = (u16)GET_RX_STATUS_DESC_CRC32(pdesc); + stats->hwerror = (u16)(stats->crc | stats->icv); + stats->decrypted = !GET_RX_STATUS_DESC_SWDEC(pdesc); + + stats->rate = (u8)GET_RX_STATUS_DESC_RX_MCS(pdesc); + stats->shortpreamble = (u16)GET_RX_STATUS_DESC_SPLCP(pdesc); + stats->isampdu = (bool)(GET_RX_STATUS_DESC_PAGGR(pdesc) == 1); + stats->isfirst_ampdu = (bool) ((GET_RX_STATUS_DESC_PAGGR(pdesc) == 1) + && (GET_RX_STATUS_DESC_FAGGR(pdesc) == 1)); + stats->timestamp_low = GET_RX_STATUS_DESC_TSFL(pdesc); + stats->rx_is40Mhzpacket = (bool)GET_RX_STATUS_DESC_BW(pdesc); + stats->is_ht = (bool)GET_RX_STATUS_DESC_RX_HT(pdesc); + stats->is_cck = SE_RX_HAL_IS_CCK_RATE(pdesc); + + if (stats->hwerror) + return false; + + rx_status->freq = hw->conf.chandef.chan->center_freq; + rx_status->band = hw->conf.chandef.chan->band; + + if (stats->crc) + rx_status->flag |= RX_FLAG_FAILED_FCS_CRC; + + if (stats->rx_is40Mhzpacket) + rx_status->flag |= RX_FLAG_40MHZ; + + if (stats->is_ht) + rx_status->flag |= RX_FLAG_HT; + + rx_status->flag |= RX_FLAG_MACTIME_START; + + /* hw will set stats->decrypted true, if it finds the + * frame is open data frame or mgmt frame, + * hw will not decrypt robust managment frame + * for IEEE80211w but still set stats->decrypted + * true, so here we should set it back to undecrypted + * for IEEE80211w frame, and mac80211 sw will help + * to decrypt it */ + if (stats->decrypted) { + hdr = (struct ieee80211_hdr *)(skb->data + + stats->rx_drvinfo_size + stats->rx_bufshift); + + if (!hdr) { + /* during testing, hdr was NULL here */ + return false; + } + if ((ieee80211_is_robust_mgmt_frame(hdr)) && + (ieee80211_has_protected(hdr->frame_control))) + rx_status->flag &= ~RX_FLAG_DECRYPTED; + else + rx_status->flag |= RX_FLAG_DECRYPTED; + } + + rx_status->rate_idx = rtlwifi_rate_mapping(hw, + stats->is_ht, stats->rate, first_ampdu); + + rx_status->mactime = stats->timestamp_low; + if (phystatus) { + p_drvinfo = (struct rx_fwinfo *)(skb->data + + stats->rx_bufshift); + _rtl92se_translate_rx_signal_stuff(hw, skb, stats, pdesc, + p_drvinfo); + } + + /*rx_status->qual = stats->signal; */ + rx_status->signal = stats->rssi + 10; + /*rx_status->noise = -stats->noise; */ + + return true; +} + +void rtl92se_tx_fill_desc(struct ieee80211_hw *hw, + struct ieee80211_hdr *hdr, u8 *pdesc_tx, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff *skb, + u8 hw_queue, struct rtl_tcb_desc *ptcb_desc) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 *pdesc = pdesc_tx; + u16 seq_number; + __le16 fc = hdr->frame_control; + u8 reserved_macid = 0; + u8 fw_qsel = _rtl92se_map_hwqueue_to_fwqueue(skb, hw_queue); + bool firstseg = (!(hdr->seq_ctrl & cpu_to_le16(IEEE80211_SCTL_FRAG))); + bool lastseg = (!(hdr->frame_control & + cpu_to_le16(IEEE80211_FCTL_MOREFRAGS))); + dma_addr_t mapping = pci_map_single(rtlpci->pdev, skb->data, skb->len, + PCI_DMA_TODEVICE); + u8 bw_40 = 0; + + if (pci_dma_mapping_error(rtlpci->pdev, mapping)) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "DMA mapping error"); + return; + } + if (mac->opmode == NL80211_IFTYPE_STATION) { + bw_40 = mac->bw_40; + } else if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_ADHOC) { + if (sta) + bw_40 = sta->bandwidth >= IEEE80211_STA_RX_BW_40; + } + + seq_number = (le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_SEQ) >> 4; + + rtl_get_tcb_desc(hw, info, sta, skb, ptcb_desc); + + CLEAR_PCI_TX_DESC_CONTENT(pdesc, TX_DESC_SIZE_RTL8192S); + + if (ieee80211_is_nullfunc(fc) || ieee80211_is_ctl(fc)) { + firstseg = true; + lastseg = true; + } + + if (firstseg) { + if (rtlpriv->dm.useramask) { + /* set txdesc macId */ + if (ptcb_desc->mac_id < 32) { + SET_TX_DESC_MACID(pdesc, ptcb_desc->mac_id); + reserved_macid |= ptcb_desc->mac_id; + } + } + SET_TX_DESC_RSVD_MACID(pdesc, reserved_macid); + + SET_TX_DESC_TXHT(pdesc, ((ptcb_desc->hw_rate >= + DESC92_RATEMCS0) ? 1 : 0)); + + if (rtlhal->version == VERSION_8192S_ACUT) { + if (ptcb_desc->hw_rate == DESC92_RATE1M || + ptcb_desc->hw_rate == DESC92_RATE2M || + ptcb_desc->hw_rate == DESC92_RATE5_5M || + ptcb_desc->hw_rate == DESC92_RATE11M) { + ptcb_desc->hw_rate = DESC92_RATE12M; + } + } + + SET_TX_DESC_TX_RATE(pdesc, ptcb_desc->hw_rate); + + if (ptcb_desc->use_shortgi || ptcb_desc->use_shortpreamble) + SET_TX_DESC_TX_SHORT(pdesc, 0); + + /* Aggregation related */ + if (info->flags & IEEE80211_TX_CTL_AMPDU) + SET_TX_DESC_AGG_ENABLE(pdesc, 1); + + /* For AMPDU, we must insert SSN into TX_DESC */ + SET_TX_DESC_SEQ(pdesc, seq_number); + + /* Protection mode related */ + /* For 92S, if RTS/CTS are set, HW will execute RTS. */ + /* We choose only one protection mode to execute */ + SET_TX_DESC_RTS_ENABLE(pdesc, ((ptcb_desc->rts_enable && + !ptcb_desc->cts_enable) ? 1 : 0)); + SET_TX_DESC_CTS_ENABLE(pdesc, ((ptcb_desc->cts_enable) ? + 1 : 0)); + SET_TX_DESC_RTS_STBC(pdesc, ((ptcb_desc->rts_stbc) ? 1 : 0)); + + SET_TX_DESC_RTS_RATE(pdesc, ptcb_desc->rts_rate); + SET_TX_DESC_RTS_BANDWIDTH(pdesc, 0); + SET_TX_DESC_RTS_SUB_CARRIER(pdesc, ptcb_desc->rts_sc); + SET_TX_DESC_RTS_SHORT(pdesc, ((ptcb_desc->rts_rate <= + DESC92_RATE54M) ? + (ptcb_desc->rts_use_shortpreamble ? 1 : 0) + : (ptcb_desc->rts_use_shortgi ? 1 : 0))); + + + /* Set Bandwidth and sub-channel settings. */ + if (bw_40) { + if (ptcb_desc->packet_bw) { + SET_TX_DESC_TX_BANDWIDTH(pdesc, 1); + /* use duplicated mode */ + SET_TX_DESC_TX_SUB_CARRIER(pdesc, 0); + } else { + SET_TX_DESC_TX_BANDWIDTH(pdesc, 0); + SET_TX_DESC_TX_SUB_CARRIER(pdesc, + mac->cur_40_prime_sc); + } + } else { + SET_TX_DESC_TX_BANDWIDTH(pdesc, 0); + SET_TX_DESC_TX_SUB_CARRIER(pdesc, 0); + } + + /* 3 Fill necessary field in First Descriptor */ + /*DWORD 0*/ + SET_TX_DESC_LINIP(pdesc, 0); + SET_TX_DESC_OFFSET(pdesc, 32); + SET_TX_DESC_PKT_SIZE(pdesc, (u16) skb->len); + + /*DWORD 1*/ + SET_TX_DESC_RA_BRSR_ID(pdesc, ptcb_desc->ratr_index); + + /* Fill security related */ + if (info->control.hw_key) { + struct ieee80211_key_conf *keyconf; + + keyconf = info->control.hw_key; + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + SET_TX_DESC_SEC_TYPE(pdesc, 0x1); + break; + case WLAN_CIPHER_SUITE_TKIP: + SET_TX_DESC_SEC_TYPE(pdesc, 0x2); + break; + case WLAN_CIPHER_SUITE_CCMP: + SET_TX_DESC_SEC_TYPE(pdesc, 0x3); + break; + default: + SET_TX_DESC_SEC_TYPE(pdesc, 0x0); + break; + + } + } + + /* Set Packet ID */ + SET_TX_DESC_PACKET_ID(pdesc, 0); + + /* We will assign magement queue to BK. */ + SET_TX_DESC_QUEUE_SEL(pdesc, fw_qsel); + + /* Alwasy enable all rate fallback range */ + SET_TX_DESC_DATA_RATE_FB_LIMIT(pdesc, 0x1F); + + /* Fix: I don't kown why hw use 6.5M to tx when set it */ + SET_TX_DESC_USER_RATE(pdesc, + ptcb_desc->use_driver_rate ? 1 : 0); + + /* Set NON_QOS bit. */ + if (!ieee80211_is_data_qos(fc)) + SET_TX_DESC_NON_QOS(pdesc, 1); + + } + + /* Fill fields that are required to be initialized + * in all of the descriptors */ + /*DWORD 0 */ + SET_TX_DESC_FIRST_SEG(pdesc, (firstseg ? 1 : 0)); + SET_TX_DESC_LAST_SEG(pdesc, (lastseg ? 1 : 0)); + + /* DWORD 7 */ + SET_TX_DESC_TX_BUFFER_SIZE(pdesc, (u16) skb->len); + + /* DOWRD 8 */ + SET_TX_DESC_TX_BUFFER_ADDRESS(pdesc, mapping); + + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, "\n"); +} + +void rtl92se_tx_fill_cmddesc(struct ieee80211_hw *hw, u8 *pdesc, + bool firstseg, bool lastseg, struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_tcb_desc *tcb_desc = (struct rtl_tcb_desc *)(skb->cb); + + dma_addr_t mapping = pci_map_single(rtlpci->pdev, skb->data, skb->len, + PCI_DMA_TODEVICE); + + if (pci_dma_mapping_error(rtlpci->pdev, mapping)) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "DMA mapping error"); + return; + } + /* Clear all status */ + CLEAR_PCI_TX_DESC_CONTENT(pdesc, TX_CMDDESC_SIZE_RTL8192S); + + /* This bit indicate this packet is used for FW download. */ + if (tcb_desc->cmd_or_init == DESC_PACKET_TYPE_INIT) { + /* For firmware downlaod we only need to set LINIP */ + SET_TX_DESC_LINIP(pdesc, tcb_desc->last_inipkt); + + /* 92SE must set as 1 for firmware download HW DMA error */ + SET_TX_DESC_FIRST_SEG(pdesc, 1); + SET_TX_DESC_LAST_SEG(pdesc, 1); + + /* 92SE need not to set TX packet size when firmware download */ + SET_TX_DESC_PKT_SIZE(pdesc, (u16)(skb->len)); + SET_TX_DESC_TX_BUFFER_SIZE(pdesc, (u16)(skb->len)); + SET_TX_DESC_TX_BUFFER_ADDRESS(pdesc, mapping); + + wmb(); + SET_TX_DESC_OWN(pdesc, 1); + } else { /* H2C Command Desc format (Host TXCMD) */ + /* 92SE must set as 1 for firmware download HW DMA error */ + SET_TX_DESC_FIRST_SEG(pdesc, 1); + SET_TX_DESC_LAST_SEG(pdesc, 1); + + SET_TX_DESC_OFFSET(pdesc, 0x20); + + /* Buffer size + command header */ + SET_TX_DESC_PKT_SIZE(pdesc, (u16)(skb->len)); + /* Fixed queue of H2C command */ + SET_TX_DESC_QUEUE_SEL(pdesc, 0x13); + + SET_BITS_TO_LE_4BYTE(skb->data, 24, 7, rtlhal->h2c_txcmd_seq); + + SET_TX_DESC_TX_BUFFER_SIZE(pdesc, (u16)(skb->len)); + SET_TX_DESC_TX_BUFFER_ADDRESS(pdesc, mapping); + + wmb(); + SET_TX_DESC_OWN(pdesc, 1); + + } +} + +void rtl92se_set_desc(u8 *pdesc, bool istx, u8 desc_name, u8 *val) +{ + if (istx) { + switch (desc_name) { + case HW_DESC_OWN: + wmb(); + SET_TX_DESC_OWN(pdesc, 1); + break; + case HW_DESC_TX_NEXTDESC_ADDR: + SET_TX_DESC_NEXT_DESC_ADDRESS(pdesc, *(u32 *) val); + break; + default: + RT_ASSERT(false, "ERR txdesc :%d not process\n", + desc_name); + break; + } + } else { + switch (desc_name) { + case HW_DESC_RXOWN: + wmb(); + SET_RX_STATUS_DESC_OWN(pdesc, 1); + break; + case HW_DESC_RXBUFF_ADDR: + SET_RX_STATUS__DESC_BUFF_ADDR(pdesc, *(u32 *) val); + break; + case HW_DESC_RXPKT_LEN: + SET_RX_STATUS_DESC_PKT_LEN(pdesc, *(u32 *) val); + break; + case HW_DESC_RXERO: + SET_RX_STATUS_DESC_EOR(pdesc, 1); + break; + default: + RT_ASSERT(false, "ERR rxdesc :%d not process\n", + desc_name); + break; + } + } +} + +u32 rtl92se_get_desc(u8 *desc, bool istx, u8 desc_name) +{ + u32 ret = 0; + + if (istx) { + switch (desc_name) { + case HW_DESC_OWN: + ret = GET_TX_DESC_OWN(desc); + break; + case HW_DESC_TXBUFF_ADDR: + ret = GET_TX_DESC_TX_BUFFER_ADDRESS(desc); + break; + default: + RT_ASSERT(false, "ERR txdesc :%d not process\n", + desc_name); + break; + } + } else { + switch (desc_name) { + case HW_DESC_OWN: + ret = GET_RX_STATUS_DESC_OWN(desc); + break; + case HW_DESC_RXPKT_LEN: + ret = GET_RX_STATUS_DESC_PKT_LEN(desc); + break; + default: + RT_ASSERT(false, "ERR rxdesc :%d not process\n", + desc_name); + break; + } + } + return ret; +} + +void rtl92se_tx_polling(struct ieee80211_hw *hw, u8 hw_queue) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + rtl_write_word(rtlpriv, TP_POLL, BIT(0) << (hw_queue)); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8192se/trx.h b/drivers/net/wireless/rtlwifi/rtl8192se/trx.h new file mode 100644 index 0000000..64dd66f --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8192se/trx.h @@ -0,0 +1,46 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ +#ifndef __REALTEK_PCI92SE_TRX_H__ +#define __REALTEK_PCI92SE_TRX_H__ + +void rtl92se_tx_fill_desc(struct ieee80211_hw *hw, struct ieee80211_hdr *hdr, + u8 *pdesc, struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff *skb, u8 hw_queue, + struct rtl_tcb_desc *ptcb_desc); +void rtl92se_tx_fill_cmddesc(struct ieee80211_hw *hw, u8 *pdesc, bool firstseg, + bool lastseg, struct sk_buff *skb); +bool rtl92se_rx_query_desc(struct ieee80211_hw *hw, struct rtl_stats *stats, + struct ieee80211_rx_status *rx_status, u8 *pdesc, + struct sk_buff *skb); +void rtl92se_set_desc(u8 *pdesc, bool istx, u8 desc_name, u8 *val); +u32 rtl92se_get_desc(u8 *pdesc, bool istx, u8 desc_name); +void rtl92se_tx_polling(struct ieee80211_hw *hw, u8 hw_queue); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/Makefile b/drivers/net/wireless/rtlwifi/rtl8723ae/Makefile new file mode 100644 index 0000000..3f022a9 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/Makefile @@ -0,0 +1,22 @@ +obj-m := rtl8723ae.o + + +rtl8723ae-objs := \ + dm.o \ + fw.o \ + hal_btc.o \ + hal_bt_coexist.o\ + hw.o \ + led.o \ + phy.o \ + pwrseq.o \ + pwrseqcmd.o \ + rf.o \ + sw.o \ + table.o \ + trx.o \ + + +obj-$(CPTCFG_RTL8723AE) += rtl8723ae.o + +ccflags-y += -D__CHECK_ENDIAN__ diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/btc.h b/drivers/net/wireless/rtlwifi/rtl8723ae/btc.h new file mode 100644 index 0000000..417afee --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/btc.h @@ -0,0 +1,41 @@ +/****************************************************************************** + ** + ** Copyright(c) 2009-2012 Realtek Corporation. + ** + ** This program is free software; you can redistribute it and/or modify it + ** under the terms of version 2 of the GNU General Public License as + ** published by the Free Software Foundation. + ** + ** This program is distributed in the hope that it will be useful, but WITHOUT + ** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + ** FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + ** more details. + ** + ** You should have received a copy of the GNU General Public License along with + ** this program; if not, write to the Free Software Foundation, Inc., + ** 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + ** + ** The full GNU General Public License is included in this distribution in the + ** file called LICENSE. + ** + ** Contact Information: + ** wlanfae + ** Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + ** Hsinchu 300, Taiwan. + ** Larry Finger + ** + ***************************************************************************** + */ + +#ifndef __RTL8723E_BTC_H__ +#define __RTL8723E_BTC_H__ + +#include "../wifi.h" +#include "hal_bt_coexist.h" + +struct bt_coexist_c2h_info { + u8 no_parse_c2h; + u8 has_c2h; +}; + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/def.h b/drivers/net/wireless/rtlwifi/rtl8723ae/def.h new file mode 100644 index 0000000..8c11035 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/def.h @@ -0,0 +1,163 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + **************************************************************************** + */ + +#ifndef __RTL8723E_DEF_H__ +#define __RTL8723E_DEF_H__ + +#define HAL_PRIME_CHNL_OFFSET_LOWER 1 + +#define RX_MPDU_QUEUE 0 + +#define CHIP_8723 BIT(0) +#define NORMAL_CHIP BIT(3) +#define RF_TYPE_1T2R BIT(4) +#define RF_TYPE_2T2R BIT(5) +#define CHIP_VENDOR_UMC BIT(7) +#define B_CUT_VERSION BIT(12) +#define C_CUT_VERSION BIT(13) +#define D_CUT_VERSION ((BIT(12)|BIT(13))) +#define E_CUT_VERSION BIT(14) +#define RF_RL_ID (BIT(31)|BIT(30)|BIT(29)|BIT(28)) + +enum version_8723e { + VERSION_TEST_UMC_CHIP_8723 = 0x0081, + VERSION_NORMAL_UMC_CHIP_8723_1T1R_A_CUT = 0x0089, + VERSION_NORMAL_UMC_CHIP_8723_1T1R_B_CUT = 0x1089, +}; + +/* MASK */ +#define IC_TYPE_MASK (BIT(0)|BIT(1)|BIT(2)) +#define CHIP_TYPE_MASK BIT(3) +#define RF_TYPE_MASK (BIT(4)|BIT(5)|BIT(6)) +#define MANUFACTUER_MASK BIT(7) +#define ROM_VERSION_MASK (BIT(11)|BIT(10)|BIT(9)|BIT(8)) +#define CUT_VERSION_MASK (BIT(15)|BIT(14)|BIT(13)|BIT(12)) + +/* Get element */ +#define GET_CVID_IC_TYPE(version) ((version) & IC_TYPE_MASK) +#define GET_CVID_MANUFACTUER(version) ((version) & MANUFACTUER_MASK) +#define GET_CVID_CUT_VERSION(version) ((version) & CUT_VERSION_MASK) + +#define IS_81XXC(version) ((GET_CVID_IC_TYPE(version) == 0) ?\ + true : false) +#define IS_8723_SERIES(version) \ + ((GET_CVID_IC_TYPE(version) == CHIP_8723) ? true : false) +#define IS_CHIP_VENDOR_UMC(version) \ + ((GET_CVID_MANUFACTUER(version)) ? true : false) + +#define IS_VENDOR_UMC_A_CUT(version) ((IS_CHIP_VENDOR_UMC(version)) ? \ + ((GET_CVID_CUT_VERSION(version)) ? false : true) : false) +#define IS_VENDOR_8723_A_CUT(version) ((IS_8723_SERIES(version)) ? \ + ((GET_CVID_CUT_VERSION(version)) ? false : true) : false) +#define IS_81xxC_VENDOR_UMC_B_CUT(version) ((IS_CHIP_VENDOR_UMC(version)) \ + ? ((GET_CVID_CUT_VERSION(version) == B_CUT_VERSION) ? \ + true : false) : false) + +enum rf_optype { + RF_OP_BY_SW_3WIRE = 0, + RF_OP_BY_FW, + RF_OP_MAX +}; + +enum rf_power_state { + RF_ON, + RF_OFF, + RF_SLEEP, + RF_SHUT_DOWN, +}; + +enum power_save_mode { + POWER_SAVE_MODE_ACTIVE, + POWER_SAVE_MODE_SAVE, +}; + +enum power_polocy_config { + POWERCFG_MAX_POWER_SAVINGS, + POWERCFG_GLOBAL_POWER_SAVINGS, + POWERCFG_LOCAL_POWER_SAVINGS, + POWERCFG_LENOVO, +}; + +enum interface_select_pci { + INTF_SEL1_MINICARD = 0, + INTF_SEL0_PCIE = 1, + INTF_SEL2_RSV = 2, + INTF_SEL3_RSV = 3, +}; + +enum hal_fw_c2h_cmd_id { + HAL_FW_C2H_CMD_Read_MACREG = 0, + HAL_FW_C2H_CMD_Read_BBREG = 1, + HAL_FW_C2H_CMD_Read_RFREG = 2, + HAL_FW_C2H_CMD_Read_EEPROM = 3, + HAL_FW_C2H_CMD_Read_EFUSE = 4, + HAL_FW_C2H_CMD_Read_CAM = 5, + HAL_FW_C2H_CMD_Get_BasicRate = 6, + HAL_FW_C2H_CMD_Get_DataRate = 7, + HAL_FW_C2H_CMD_Survey = 8, + HAL_FW_C2H_CMD_SurveyDone = 9, + HAL_FW_C2H_CMD_JoinBss = 10, + HAL_FW_C2H_CMD_AddSTA = 11, + HAL_FW_C2H_CMD_DelSTA = 12, + HAL_FW_C2H_CMD_AtimDone = 13, + HAL_FW_C2H_CMD_TX_Report = 14, + HAL_FW_C2H_CMD_CCX_Report = 15, + HAL_FW_C2H_CMD_DTM_Report = 16, + HAL_FW_C2H_CMD_TX_Rate_Statistics = 17, + HAL_FW_C2H_CMD_C2HLBK = 18, + HAL_FW_C2H_CMD_C2HDBG = 19, + HAL_FW_C2H_CMD_C2HFEEDBACK = 20, + HAL_FW_C2H_CMD_MAX +}; + +enum rtl_desc_qsel { + QSLT_BK = 0x2, + QSLT_BE = 0x0, + QSLT_VI = 0x5, + QSLT_VO = 0x7, + QSLT_BEACON = 0x10, + QSLT_HIGH = 0x11, + QSLT_MGNT = 0x12, + QSLT_CMD = 0x13, +}; + +struct phy_sts_cck_8723e_t { + u8 adc_pwdb_X[4]; + u8 sq_rpt; + u8 cck_agc_rpt; +}; + +struct h2c_cmd_8723e { + u8 element_id; + u32 cmd_len; + u8 *p_cmdbuffer; +}; + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/dm.c b/drivers/net/wireless/rtlwifi/rtl8723ae/dm.c new file mode 100644 index 0000000..a36eee2 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/dm.c @@ -0,0 +1,994 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + **************************************************************************** + */ + +#include "../wifi.h" +#include "../base.h" +#include "../pci.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "dm.h" +#include "fw.h" +#include "hal_btc.h" + +static const u32 ofdmswing_table[OFDM_TABLE_SIZE] = { + 0x7f8001fe, + 0x788001e2, + 0x71c001c7, + 0x6b8001ae, + 0x65400195, + 0x5fc0017f, + 0x5a400169, + 0x55400155, + 0x50800142, + 0x4c000130, + 0x47c0011f, + 0x43c0010f, + 0x40000100, + 0x3c8000f2, + 0x390000e4, + 0x35c000d7, + 0x32c000cb, + 0x300000c0, + 0x2d4000b5, + 0x2ac000ab, + 0x288000a2, + 0x26000098, + 0x24000090, + 0x22000088, + 0x20000080, + 0x1e400079, + 0x1c800072, + 0x1b00006c, + 0x19800066, + 0x18000060, + 0x16c0005b, + 0x15800056, + 0x14400051, + 0x1300004c, + 0x12000048, + 0x11000044, + 0x10000040, +}; + +static const u8 cckswing_table_ch1ch13[CCK_TABLE_SIZE][8] = { + {0x36, 0x35, 0x2e, 0x25, 0x1c, 0x12, 0x09, 0x04}, + {0x33, 0x32, 0x2b, 0x23, 0x1a, 0x11, 0x08, 0x04}, + {0x30, 0x2f, 0x29, 0x21, 0x19, 0x10, 0x08, 0x03}, + {0x2d, 0x2d, 0x27, 0x1f, 0x18, 0x0f, 0x08, 0x03}, + {0x2b, 0x2a, 0x25, 0x1e, 0x16, 0x0e, 0x07, 0x03}, + {0x28, 0x28, 0x22, 0x1c, 0x15, 0x0d, 0x07, 0x03}, + {0x26, 0x25, 0x21, 0x1b, 0x14, 0x0d, 0x06, 0x03}, + {0x24, 0x23, 0x1f, 0x19, 0x13, 0x0c, 0x06, 0x03}, + {0x22, 0x21, 0x1d, 0x18, 0x11, 0x0b, 0x06, 0x02}, + {0x20, 0x20, 0x1b, 0x16, 0x11, 0x08, 0x05, 0x02}, + {0x1f, 0x1e, 0x1a, 0x15, 0x10, 0x0a, 0x05, 0x02}, + {0x1d, 0x1c, 0x18, 0x14, 0x0f, 0x0a, 0x05, 0x02}, + {0x1b, 0x1a, 0x17, 0x13, 0x0e, 0x09, 0x04, 0x02}, + {0x1a, 0x19, 0x16, 0x12, 0x0d, 0x09, 0x04, 0x02}, + {0x18, 0x17, 0x15, 0x11, 0x0c, 0x08, 0x04, 0x02}, + {0x17, 0x16, 0x13, 0x10, 0x0c, 0x08, 0x04, 0x02}, + {0x16, 0x15, 0x12, 0x0f, 0x0b, 0x07, 0x04, 0x01}, + {0x14, 0x14, 0x11, 0x0e, 0x0b, 0x07, 0x03, 0x02}, + {0x13, 0x13, 0x10, 0x0d, 0x0a, 0x06, 0x03, 0x01}, + {0x12, 0x12, 0x0f, 0x0c, 0x09, 0x06, 0x03, 0x01}, + {0x11, 0x11, 0x0f, 0x0c, 0x09, 0x06, 0x03, 0x01}, + {0x10, 0x10, 0x0e, 0x0b, 0x08, 0x05, 0x03, 0x01}, + {0x0f, 0x0f, 0x0d, 0x0b, 0x08, 0x05, 0x03, 0x01}, + {0x0e, 0x0e, 0x0c, 0x0a, 0x08, 0x05, 0x02, 0x01}, + {0x0d, 0x0d, 0x0c, 0x0a, 0x07, 0x05, 0x02, 0x01}, + {0x0d, 0x0c, 0x0b, 0x09, 0x07, 0x04, 0x02, 0x01}, + {0x0c, 0x0c, 0x0a, 0x09, 0x06, 0x04, 0x02, 0x01}, + {0x0b, 0x0b, 0x0a, 0x08, 0x06, 0x04, 0x02, 0x01}, + {0x0b, 0x0a, 0x09, 0x08, 0x06, 0x04, 0x02, 0x01}, + {0x0a, 0x0a, 0x09, 0x07, 0x05, 0x03, 0x02, 0x01}, + {0x0a, 0x09, 0x08, 0x07, 0x05, 0x03, 0x02, 0x01}, + {0x09, 0x09, 0x08, 0x06, 0x05, 0x03, 0x01, 0x01}, + {0x09, 0x08, 0x07, 0x06, 0x04, 0x03, 0x01, 0x01} +}; + +static const u8 cckswing_table_ch14[CCK_TABLE_SIZE][8] = { + {0x36, 0x35, 0x2e, 0x1b, 0x00, 0x00, 0x00, 0x00}, + {0x33, 0x32, 0x2b, 0x19, 0x00, 0x00, 0x00, 0x00}, + {0x30, 0x2f, 0x29, 0x18, 0x00, 0x00, 0x00, 0x00}, + {0x2d, 0x2d, 0x17, 0x17, 0x00, 0x00, 0x00, 0x00}, + {0x2b, 0x2a, 0x25, 0x15, 0x00, 0x00, 0x00, 0x00}, + {0x28, 0x28, 0x24, 0x14, 0x00, 0x00, 0x00, 0x00}, + {0x26, 0x25, 0x21, 0x13, 0x00, 0x00, 0x00, 0x00}, + {0x24, 0x23, 0x1f, 0x12, 0x00, 0x00, 0x00, 0x00}, + {0x22, 0x21, 0x1d, 0x11, 0x00, 0x00, 0x00, 0x00}, + {0x20, 0x20, 0x1b, 0x10, 0x00, 0x00, 0x00, 0x00}, + {0x1f, 0x1e, 0x1a, 0x0f, 0x00, 0x00, 0x00, 0x00}, + {0x1d, 0x1c, 0x18, 0x0e, 0x00, 0x00, 0x00, 0x00}, + {0x1b, 0x1a, 0x17, 0x0e, 0x00, 0x00, 0x00, 0x00}, + {0x1a, 0x19, 0x16, 0x0d, 0x00, 0x00, 0x00, 0x00}, + {0x18, 0x17, 0x15, 0x0c, 0x00, 0x00, 0x00, 0x00}, + {0x17, 0x16, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00}, + {0x16, 0x15, 0x12, 0x0b, 0x00, 0x00, 0x00, 0x00}, + {0x14, 0x14, 0x11, 0x0a, 0x00, 0x00, 0x00, 0x00}, + {0x13, 0x13, 0x10, 0x0a, 0x00, 0x00, 0x00, 0x00}, + {0x12, 0x12, 0x0f, 0x09, 0x00, 0x00, 0x00, 0x00}, + {0x11, 0x11, 0x0f, 0x09, 0x00, 0x00, 0x00, 0x00}, + {0x10, 0x10, 0x0e, 0x08, 0x00, 0x00, 0x00, 0x00}, + {0x0f, 0x0f, 0x0d, 0x08, 0x00, 0x00, 0x00, 0x00}, + {0x0e, 0x0e, 0x0c, 0x07, 0x00, 0x00, 0x00, 0x00}, + {0x0d, 0x0d, 0x0c, 0x07, 0x00, 0x00, 0x00, 0x00}, + {0x0d, 0x0c, 0x0b, 0x06, 0x00, 0x00, 0x00, 0x00}, + {0x0c, 0x0c, 0x0a, 0x06, 0x00, 0x00, 0x00, 0x00}, + {0x0b, 0x0b, 0x0a, 0x06, 0x00, 0x00, 0x00, 0x00}, + {0x0b, 0x0a, 0x09, 0x05, 0x00, 0x00, 0x00, 0x00}, + {0x0a, 0x0a, 0x09, 0x05, 0x00, 0x00, 0x00, 0x00}, + {0x0a, 0x09, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00}, + {0x09, 0x09, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00}, + {0x09, 0x08, 0x07, 0x04, 0x00, 0x00, 0x00, 0x00} +}; + +static void rtl8723ae_dm_diginit(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + + dm_digtable->dig_enable_flag = true; + dm_digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_MAX; + dm_digtable->cur_igvalue = 0x20; + dm_digtable->pre_igvalue = 0x0; + dm_digtable->cursta_cstate = DIG_STA_DISCONNECT; + dm_digtable->presta_cstate = DIG_STA_DISCONNECT; + dm_digtable->curmultista_cstate = DIG_MULTISTA_DISCONNECT; + dm_digtable->rssi_lowthresh = DM_DIG_THRESH_LOW; + dm_digtable->rssi_highthresh = DM_DIG_THRESH_HIGH; + dm_digtable->fa_lowthresh = DM_FALSEALARM_THRESH_LOW; + dm_digtable->fa_highthresh = DM_FALSEALARM_THRESH_HIGH; + dm_digtable->rx_gain_max = DM_DIG_MAX; + dm_digtable->rx_gain_min = DM_DIG_MIN; + dm_digtable->back_val = DM_DIG_BACKOFF_DEFAULT; + dm_digtable->back_range_max = DM_DIG_BACKOFF_MAX; + dm_digtable->back_range_min = DM_DIG_BACKOFF_MIN; + dm_digtable->pre_cck_pd_state = CCK_PD_STAGE_MAX; + dm_digtable->cur_cck_pd_state = CCK_PD_STAGE_MAX; +} + +static u8 rtl_init_gain_min_pwdb(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + long rssi_val_min = 0; + + if ((dm_digtable->curmultista_cstate == DIG_MULTISTA_CONNECT) && + (dm_digtable->cursta_cstate == DIG_STA_CONNECT)) { + if (rtlpriv->dm.entry_min_undec_sm_pwdb != 0) + rssi_val_min = + (rtlpriv->dm.entry_min_undec_sm_pwdb > + rtlpriv->dm.undec_sm_pwdb) ? + rtlpriv->dm.undec_sm_pwdb : + rtlpriv->dm.entry_min_undec_sm_pwdb; + else + rssi_val_min = rtlpriv->dm.undec_sm_pwdb; + } else if (dm_digtable->cursta_cstate == DIG_STA_CONNECT || + dm_digtable->cursta_cstate == DIG_STA_BEFORE_CONNECT) { + rssi_val_min = rtlpriv->dm.undec_sm_pwdb; + } else if (dm_digtable->curmultista_cstate == DIG_MULTISTA_CONNECT) { + rssi_val_min = rtlpriv->dm.entry_min_undec_sm_pwdb; + } + + return (u8) rssi_val_min; +} + +static void rtl8723ae_dm_false_alarm_counter_statistics(struct ieee80211_hw *hw) +{ + u32 ret_value; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct false_alarm_statistics *falsealm_cnt = &(rtlpriv->falsealm_cnt); + + ret_value = rtl_get_bbreg(hw, ROFDM_PHYCOUNTER1, MASKDWORD); + falsealm_cnt->cnt_parity_fail = ((ret_value & 0xffff0000) >> 16); + + ret_value = rtl_get_bbreg(hw, ROFDM_PHYCOUNTER2, MASKDWORD); + falsealm_cnt->cnt_rate_illegal = (ret_value & 0xffff); + falsealm_cnt->cnt_crc8_fail = ((ret_value & 0xffff0000) >> 16); + + ret_value = rtl_get_bbreg(hw, ROFDM_PHYCOUNTER3, MASKDWORD); + falsealm_cnt->cnt_mcs_fail = (ret_value & 0xffff); + falsealm_cnt->cnt_ofdm_fail = falsealm_cnt->cnt_parity_fail + + falsealm_cnt->cnt_rate_illegal + + falsealm_cnt->cnt_crc8_fail + falsealm_cnt->cnt_mcs_fail; + + rtl_set_bbreg(hw, RCCK0_FALSEALARMREPORT, BIT(14), 1); + ret_value = rtl_get_bbreg(hw, RCCK0_FACOUNTERLOWER, MASKBYTE0); + falsealm_cnt->cnt_cck_fail = ret_value; + + ret_value = rtl_get_bbreg(hw, RCCK0_FACOUNTERUPPER, MASKBYTE3); + falsealm_cnt->cnt_cck_fail += (ret_value & 0xff) << 8; + falsealm_cnt->cnt_all = (falsealm_cnt->cnt_parity_fail + + falsealm_cnt->cnt_rate_illegal + + falsealm_cnt->cnt_crc8_fail + + falsealm_cnt->cnt_mcs_fail + + falsealm_cnt->cnt_cck_fail); + + rtl_set_bbreg(hw, ROFDM1_LSTF, 0x08000000, 1); + rtl_set_bbreg(hw, ROFDM1_LSTF, 0x08000000, 0); + rtl_set_bbreg(hw, RCCK0_FALSEALARMREPORT, 0x0000c000, 0); + rtl_set_bbreg(hw, RCCK0_FALSEALARMREPORT, 0x0000c000, 2); + + RT_TRACE(rtlpriv, COMP_DIG, DBG_TRACE, + "cnt_parity_fail = %d, cnt_rate_illegal = %d, " + "cnt_crc8_fail = %d, cnt_mcs_fail = %d\n", + falsealm_cnt->cnt_parity_fail, + falsealm_cnt->cnt_rate_illegal, + falsealm_cnt->cnt_crc8_fail, falsealm_cnt->cnt_mcs_fail); + + RT_TRACE(rtlpriv, COMP_DIG, DBG_TRACE, + "cnt_ofdm_fail = %x, cnt_cck_fail = %x, cnt_all = %x\n", + falsealm_cnt->cnt_ofdm_fail, + falsealm_cnt->cnt_cck_fail, falsealm_cnt->cnt_all); +} + +static void rtl92c_dm_ctrl_initgain_by_fa(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + u8 value_igi = dm_digtable->cur_igvalue; + + if (rtlpriv->falsealm_cnt.cnt_all < DM_DIG_FA_TH0) + value_igi--; + else if (rtlpriv->falsealm_cnt.cnt_all < DM_DIG_FA_TH1) + value_igi += 0; + else if (rtlpriv->falsealm_cnt.cnt_all < DM_DIG_FA_TH2) + value_igi++; + else + value_igi += 2; + + value_igi = clamp(value_igi, (u8)DM_DIG_FA_LOWER, (u8)DM_DIG_FA_UPPER); + if (rtlpriv->falsealm_cnt.cnt_all > 10000) + value_igi = 0x32; + + dm_digtable->cur_igvalue = value_igi; + rtl8723ae_dm_write_dig(hw); +} + +static void rtl92c_dm_ctrl_initgain_by_rssi(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dgtbl = &rtlpriv->dm_digtable; + + if (rtlpriv->falsealm_cnt.cnt_all > dgtbl->fa_highthresh) { + if ((dgtbl->back_val - 2) < dgtbl->back_range_min) + dgtbl->back_val = dgtbl->back_range_min; + else + dgtbl->back_val -= 2; + } else if (rtlpriv->falsealm_cnt.cnt_all < dgtbl->fa_lowthresh) { + if ((dgtbl->back_val + 2) > dgtbl->back_range_max) + dgtbl->back_val = dgtbl->back_range_max; + else + dgtbl->back_val += 2; + } + + if ((dgtbl->rssi_val_min + 10 - dgtbl->back_val) > + dgtbl->rx_gain_max) + dgtbl->cur_igvalue = dgtbl->rx_gain_max; + else if ((dgtbl->rssi_val_min + 10 - + dgtbl->back_val) < dgtbl->rx_gain_min) + dgtbl->cur_igvalue = dgtbl->rx_gain_min; + else + dgtbl->cur_igvalue = dgtbl->rssi_val_min + 10 - dgtbl->back_val; + + RT_TRACE(rtlpriv, COMP_DIG, DBG_TRACE, + "rssi_val_min = %x back_val %x\n", + dgtbl->rssi_val_min, dgtbl->back_val); + + rtl8723ae_dm_write_dig(hw); +} + +static void rtl8723ae_dm_initial_gain_multi_sta(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + long rssi_strength = rtlpriv->dm.entry_min_undec_sm_pwdb; + bool multi_sta = false; + + if (mac->opmode == NL80211_IFTYPE_ADHOC) + multi_sta = true; + + if ((!multi_sta) || + (dm_digtable->cursta_cstate != DIG_STA_DISCONNECT)) { + rtlpriv->initialized = false; + dm_digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_MAX; + return; + } else if (!rtlpriv->initialized) { + rtlpriv->initialized = true; + dm_digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_0; + dm_digtable->cur_igvalue = 0x20; + rtl8723ae_dm_write_dig(hw); + } + + if (dm_digtable->curmultista_cstate == DIG_MULTISTA_CONNECT) { + if ((rssi_strength < dm_digtable->rssi_lowthresh) && + (dm_digtable->dig_ext_port_stage != DIG_EXT_PORT_STAGE_1)) { + + if (dm_digtable->dig_ext_port_stage == + DIG_EXT_PORT_STAGE_2) { + dm_digtable->cur_igvalue = 0x20; + rtl8723ae_dm_write_dig(hw); + } + + dm_digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_1; + } else if (rssi_strength > dm_digtable->rssi_highthresh) { + dm_digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_2; + rtl92c_dm_ctrl_initgain_by_fa(hw); + } + } else if (dm_digtable->dig_ext_port_stage != DIG_EXT_PORT_STAGE_0) { + dm_digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_0; + dm_digtable->cur_igvalue = 0x20; + rtl8723ae_dm_write_dig(hw); + } + + RT_TRACE(rtlpriv, COMP_DIG, DBG_TRACE, + "curmultista_cstate = %x dig_ext_port_stage %x\n", + dm_digtable->curmultista_cstate, + dm_digtable->dig_ext_port_stage); +} + +static void rtl8723ae_dm_initial_gain_sta(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + + RT_TRACE(rtlpriv, COMP_DIG, DBG_TRACE, + "presta_cstate = %x, cursta_cstate = %x\n", + dm_digtable->presta_cstate, + dm_digtable->cursta_cstate); + + if (dm_digtable->presta_cstate == dm_digtable->cursta_cstate || + dm_digtable->cursta_cstate == DIG_STA_BEFORE_CONNECT || + dm_digtable->cursta_cstate == DIG_STA_CONNECT) { + + if (dm_digtable->cursta_cstate != DIG_STA_DISCONNECT) { + dm_digtable->rssi_val_min = rtl_init_gain_min_pwdb(hw); + rtl92c_dm_ctrl_initgain_by_rssi(hw); + } + } else { + dm_digtable->rssi_val_min = 0; + dm_digtable->dig_ext_port_stage = DIG_EXT_PORT_STAGE_MAX; + dm_digtable->back_val = DM_DIG_BACKOFF_DEFAULT; + dm_digtable->cur_igvalue = 0x20; + dm_digtable->pre_igvalue = 0; + rtl8723ae_dm_write_dig(hw); + } +} +static void rtl8723ae_dm_cck_packet_detection_thresh(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + + if (dm_digtable->cursta_cstate == DIG_STA_CONNECT) { + dm_digtable->rssi_val_min = rtl_init_gain_min_pwdb(hw); + + if (dm_digtable->pre_cck_pd_state == CCK_PD_STAGE_LowRssi) { + if (dm_digtable->rssi_val_min <= 25) + dm_digtable->cur_cck_pd_state = + CCK_PD_STAGE_LowRssi; + else + dm_digtable->cur_cck_pd_state = + CCK_PD_STAGE_HighRssi; + } else { + if (dm_digtable->rssi_val_min <= 20) + dm_digtable->cur_cck_pd_state = + CCK_PD_STAGE_LowRssi; + else + dm_digtable->cur_cck_pd_state = + CCK_PD_STAGE_HighRssi; + } + } else { + dm_digtable->cur_cck_pd_state = CCK_PD_STAGE_MAX; + } + + if (dm_digtable->pre_cck_pd_state != dm_digtable->cur_cck_pd_state) { + if (dm_digtable->cur_cck_pd_state == CCK_PD_STAGE_LowRssi) { + if (rtlpriv->falsealm_cnt.cnt_cck_fail > 800) + dm_digtable->cur_cck_fa_state = + CCK_FA_STAGE_High; + else + dm_digtable->cur_cck_fa_state = + CCK_FA_STAGE_Low; + + if (dm_digtable->pre_cck_fa_state != + dm_digtable->cur_cck_fa_state) { + if (dm_digtable->cur_cck_fa_state == + CCK_FA_STAGE_Low) + rtl_set_bbreg(hw, RCCK0_CCA, MASKBYTE2, + 0x83); + else + rtl_set_bbreg(hw, RCCK0_CCA, MASKBYTE2, + 0xcd); + + dm_digtable->pre_cck_fa_state = + dm_digtable->cur_cck_fa_state; + } + + rtl_set_bbreg(hw, RCCK0_SYSTEM, MASKBYTE1, 0x40); + + } else { + rtl_set_bbreg(hw, RCCK0_CCA, MASKBYTE2, 0xcd); + rtl_set_bbreg(hw, RCCK0_SYSTEM, MASKBYTE1, 0x47); + + } + dm_digtable->pre_cck_pd_state = dm_digtable->cur_cck_pd_state; + } + + RT_TRACE(rtlpriv, COMP_DIG, DBG_TRACE, + "CCKPDStage=%x\n", dm_digtable->cur_cck_pd_state); + +} + +static void rtl8723ae_dm_ctrl_initgain_by_twoport(struct ieee80211_hw *hw) +{ + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + + if (mac->act_scanning == true) + return; + + if (mac->link_state >= MAC80211_LINKED) + dm_digtable->cursta_cstate = DIG_STA_CONNECT; + else + dm_digtable->cursta_cstate = DIG_STA_DISCONNECT; + + rtl8723ae_dm_initial_gain_sta(hw); + rtl8723ae_dm_initial_gain_multi_sta(hw); + rtl8723ae_dm_cck_packet_detection_thresh(hw); + + dm_digtable->presta_cstate = dm_digtable->cursta_cstate; + +} + +static void rtl8723ae_dm_dig(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + + if (rtlpriv->dm.dm_initialgain_enable == false) + return; + if (dm_digtable->dig_enable_flag == false) + return; + + rtl8723ae_dm_ctrl_initgain_by_twoport(hw); +} + +static void rtl8723ae_dm_init_dynamic_txpower(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.dynamic_txpower_enable = false; + + rtlpriv->dm.last_dtp_lvl = TXHIGHPWRLEVEL_NORMAL; + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; +} + +static void rtl8723ae_dm_dynamic_txpower(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + long undec_sm_pwdb; + + if (!rtlpriv->dm.dynamic_txpower_enable) + return; + + if (rtlpriv->dm.dm_flag & HAL_DM_HIPWR_DISABLE) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + return; + } + + if ((mac->link_state < MAC80211_LINKED) && + (rtlpriv->dm.entry_min_undec_sm_pwdb == 0)) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_TRACE, + "Not connected\n"); + + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + + rtlpriv->dm.last_dtp_lvl = TXHIGHPWRLEVEL_NORMAL; + return; + } + + if (mac->link_state >= MAC80211_LINKED) { + if (mac->opmode == NL80211_IFTYPE_ADHOC) { + undec_sm_pwdb = rtlpriv->dm.entry_min_undec_sm_pwdb; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "AP Client PWDB = 0x%lx\n", + undec_sm_pwdb); + } else { + undec_sm_pwdb = rtlpriv->dm.undec_sm_pwdb; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "STA Default Port PWDB = 0x%lx\n", + undec_sm_pwdb); + } + } else { + undec_sm_pwdb = rtlpriv->dm.entry_min_undec_sm_pwdb; + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "AP Ext Port PWDB = 0x%lx\n", + undec_sm_pwdb); + } + + if (undec_sm_pwdb >= TX_POWER_NEAR_FIELD_THRESH_LVL2) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_LEVEL1; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_LEVEL1 (TxPwr=0x0)\n"); + } else if ((undec_sm_pwdb < (TX_POWER_NEAR_FIELD_THRESH_LVL2 - 3)) && + (undec_sm_pwdb >= TX_POWER_NEAR_FIELD_THRESH_LVL1)) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_LEVEL1; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_LEVEL1 (TxPwr=0x10)\n"); + } else if (undec_sm_pwdb < (TX_POWER_NEAR_FIELD_THRESH_LVL1 - 5)) { + rtlpriv->dm.dynamic_txhighpower_lvl = TXHIGHPWRLEVEL_NORMAL; + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "TXHIGHPWRLEVEL_NORMAL\n"); + } + + if ((rtlpriv->dm.dynamic_txhighpower_lvl != rtlpriv->dm.last_dtp_lvl)) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "PHY_SetTxPowerLevel8192S() Channel = %d\n", + rtlphy->current_channel); + rtl8723ae_phy_set_txpower_level(hw, rtlphy->current_channel); + } + + rtlpriv->dm.last_dtp_lvl = rtlpriv->dm.dynamic_txhighpower_lvl; +} + +void rtl8723ae_dm_write_dig(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + + RT_TRACE(rtlpriv, COMP_DIG, DBG_LOUD, + "cur_igvalue = 0x%x, " + "pre_igvalue = 0x%x, back_val = %d\n", + dm_digtable->cur_igvalue, dm_digtable->pre_igvalue, + dm_digtable->back_val); + + if (dm_digtable->pre_igvalue != dm_digtable->cur_igvalue) { + rtl_set_bbreg(hw, ROFDM0_XAAGCCORE1, 0x7f, + dm_digtable->cur_igvalue); + rtl_set_bbreg(hw, ROFDM0_XBAGCCORE1, 0x7f, + dm_digtable->cur_igvalue); + + dm_digtable->pre_igvalue = dm_digtable->cur_igvalue; + } +} + +static void rtl8723ae_dm_pwdmonitor(struct ieee80211_hw *hw) +{ +} + +void rtl8723ae_dm_init_edca_turbo(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.current_turbo_edca = false; + rtlpriv->dm.is_any_nonbepkts = false; + rtlpriv->dm.is_cur_rdlstate = false; +} + +static void rtl8723ae_dm_check_edca_turbo(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + + u64 cur_txok_cnt = 0; + u64 cur_rxok_cnt = 0; + u32 edca_be_ul = 0x5ea42b; + u32 edca_be_dl = 0x5ea42b; + bool bt_change_edca = false; + + if ((mac->last_bt_edca_ul != rtlpcipriv->bt_coexist.bt_edca_ul) || + (mac->last_bt_edca_dl != rtlpcipriv->bt_coexist.bt_edca_dl)) { + rtlpriv->dm.current_turbo_edca = false; + mac->last_bt_edca_ul = rtlpcipriv->bt_coexist.bt_edca_ul; + mac->last_bt_edca_dl = rtlpcipriv->bt_coexist.bt_edca_dl; + } + + if (rtlpcipriv->bt_coexist.bt_edca_ul != 0) { + edca_be_ul = rtlpcipriv->bt_coexist.bt_edca_ul; + bt_change_edca = true; + } + + if (rtlpcipriv->bt_coexist.bt_edca_dl != 0) { + edca_be_ul = rtlpcipriv->bt_coexist.bt_edca_dl; + bt_change_edca = true; + } + + if (mac->link_state != MAC80211_LINKED) { + rtlpriv->dm.current_turbo_edca = false; + return; + } + + if ((!mac->ht_enable) && (!rtlpcipriv->bt_coexist.bt_coexistence)) { + if (!(edca_be_ul & 0xffff0000)) + edca_be_ul |= 0x005e0000; + + if (!(edca_be_dl & 0xffff0000)) + edca_be_dl |= 0x005e0000; + } + + if ((bt_change_edca) || ((!rtlpriv->dm.is_any_nonbepkts) && + (!rtlpriv->dm.disable_framebursting))) { + + cur_txok_cnt = rtlpriv->stats.txbytesunicast - + mac->last_txok_cnt; + cur_rxok_cnt = rtlpriv->stats.rxbytesunicast - + mac->last_rxok_cnt; + + if (cur_rxok_cnt > 4 * cur_txok_cnt) { + if (!rtlpriv->dm.is_cur_rdlstate || + !rtlpriv->dm.current_turbo_edca) { + rtl_write_dword(rtlpriv, + REG_EDCA_BE_PARAM, + edca_be_dl); + rtlpriv->dm.is_cur_rdlstate = true; + } + } else { + if (rtlpriv->dm.is_cur_rdlstate || + !rtlpriv->dm.current_turbo_edca) { + rtl_write_dword(rtlpriv, + REG_EDCA_BE_PARAM, + edca_be_ul); + rtlpriv->dm.is_cur_rdlstate = false; + } + } + rtlpriv->dm.current_turbo_edca = true; + } else { + if (rtlpriv->dm.current_turbo_edca) { + u8 tmp = AC0_BE; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_AC_PARAM, + (u8 *) (&tmp)); + rtlpriv->dm.current_turbo_edca = false; + } + } + + rtlpriv->dm.is_any_nonbepkts = false; + mac->last_txok_cnt = rtlpriv->stats.txbytesunicast; + mac->last_rxok_cnt = rtlpriv->stats.rxbytesunicast; +} + +static void rtl8723ae_dm_initialize_txpower_tracking(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.txpower_tracking = true; + rtlpriv->dm.txpower_trackinginit = false; + + RT_TRACE(rtlpriv, COMP_POWER_TRACKING, DBG_LOUD, + "pMgntInfo->txpower_tracking = %d\n", + rtlpriv->dm.txpower_tracking); +} + +void rtl8723ae_dm_init_rate_adaptive_mask(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rate_adaptive *p_ra = &(rtlpriv->ra); + + p_ra->ratr_state = DM_RATR_STA_INIT; + p_ra->pre_ratr_state = DM_RATR_STA_INIT; + + if (rtlpriv->dm.dm_type == DM_TYPE_BYDRIVER) + rtlpriv->dm.useramask = true; + else + rtlpriv->dm.useramask = false; +} + +static void rtl8723ae_dm_refresh_rate_adaptive_mask(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rate_adaptive *p_ra = &(rtlpriv->ra); + u32 low_rssithresh_for_ra, high_rssithresh_for_ra; + struct ieee80211_sta *sta = NULL; + + if (is_hal_stop(rtlhal)) { + RT_TRACE(rtlpriv, COMP_RATE, DBG_LOUD, + " driver is going to unload\n"); + return; + } + + if (!rtlpriv->dm.useramask) { + RT_TRACE(rtlpriv, COMP_RATE, DBG_LOUD, + " driver does not control rate adaptive mask\n"); + return; + } + + if (mac->link_state == MAC80211_LINKED && + mac->opmode == NL80211_IFTYPE_STATION) { + switch (p_ra->pre_ratr_state) { + case DM_RATR_STA_HIGH: + high_rssithresh_for_ra = 50; + low_rssithresh_for_ra = 20; + break; + case DM_RATR_STA_MIDDLE: + high_rssithresh_for_ra = 55; + low_rssithresh_for_ra = 20; + break; + case DM_RATR_STA_LOW: + high_rssithresh_for_ra = 50; + low_rssithresh_for_ra = 25; + break; + default: + high_rssithresh_for_ra = 50; + low_rssithresh_for_ra = 20; + break; + } + + if (rtlpriv->dm.undec_sm_pwdb > high_rssithresh_for_ra) + p_ra->ratr_state = DM_RATR_STA_HIGH; + else if (rtlpriv->dm.undec_sm_pwdb > low_rssithresh_for_ra) + p_ra->ratr_state = DM_RATR_STA_MIDDLE; + else + p_ra->ratr_state = DM_RATR_STA_LOW; + + if (p_ra->pre_ratr_state != p_ra->ratr_state) { + RT_TRACE(rtlpriv, COMP_RATE, DBG_LOUD, + "RSSI = %ld\n", + rtlpriv->dm.undec_sm_pwdb); + RT_TRACE(rtlpriv, COMP_RATE, DBG_LOUD, + "RSSI_LEVEL = %d\n", p_ra->ratr_state); + RT_TRACE(rtlpriv, COMP_RATE, DBG_LOUD, + "PreState = %d, CurState = %d\n", + p_ra->pre_ratr_state, p_ra->ratr_state); + + rcu_read_lock(); + sta = rtl_find_sta(hw, mac->bssid); + if (sta) + rtlpriv->cfg->ops->update_rate_tbl(hw, sta, + p_ra->ratr_state); + rcu_read_unlock(); + + p_ra->pre_ratr_state = p_ra->ratr_state; + } + } +} + +static void rtl8723ae_dm_init_dynamic_bpowersaving(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm_pstable.pre_ccastate = CCA_MAX; + rtlpriv->dm_pstable.cur_ccasate = CCA_MAX; + rtlpriv->dm_pstable.pre_rfstate = RF_MAX; + rtlpriv->dm_pstable.cur_rfstate = RF_MAX; + rtlpriv->dm_pstable.rssi_val_min = 0; +} + +void rtl8723ae_dm_rf_saving(struct ieee80211_hw *hw, u8 force_in_normal) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct ps_t *dm_pstable = &rtlpriv->dm_pstable; + + if (!rtlpriv->reg_init) { + rtlpriv->reg_874 = (rtl_get_bbreg(hw, RFPGA0_XCD_RFINTERFACESW, + MASKDWORD) & 0x1CC000) >> 14; + + rtlpriv->reg_c70 = (rtl_get_bbreg(hw, ROFDM0_AGCPARAMETER1, + MASKDWORD) & BIT(3)) >> 3; + + rtlpriv->reg_85c = (rtl_get_bbreg(hw, RFPGA0_XCD_SWITCHCONTROL, + MASKDWORD) & 0xFF000000) >> 24; + + rtlpriv->reg_a74 = (rtl_get_bbreg(hw, 0xa74, MASKDWORD) & + 0xF000) >> 12; + + rtlpriv->reg_init = true; + } + + if (!force_in_normal) { + if (dm_pstable->rssi_val_min != 0) { + if (dm_pstable->pre_rfstate == RF_NORMAL) { + if (dm_pstable->rssi_val_min >= 30) + dm_pstable->cur_rfstate = RF_SAVE; + else + dm_pstable->cur_rfstate = RF_NORMAL; + } else { + if (dm_pstable->rssi_val_min <= 25) + dm_pstable->cur_rfstate = RF_NORMAL; + else + dm_pstable->cur_rfstate = RF_SAVE; + } + } else { + dm_pstable->cur_rfstate = RF_MAX; + } + } else { + dm_pstable->cur_rfstate = RF_NORMAL; + } + + if (dm_pstable->pre_rfstate != dm_pstable->cur_rfstate) { + if (dm_pstable->cur_rfstate == RF_SAVE) { + + rtl_set_bbreg(hw, RFPGA0_XCD_RFINTERFACESW, + BIT(5), 0x1); + rtl_set_bbreg(hw, RFPGA0_XCD_RFINTERFACESW, + 0x1C0000, 0x2); + rtl_set_bbreg(hw, ROFDM0_AGCPARAMETER1, BIT(3), 0); + rtl_set_bbreg(hw, RFPGA0_XCD_SWITCHCONTROL, + 0xFF000000, 0x63); + rtl_set_bbreg(hw, RFPGA0_XCD_RFINTERFACESW, + 0xC000, 0x2); + rtl_set_bbreg(hw, 0xa74, 0xF000, 0x3); + rtl_set_bbreg(hw, 0x818, BIT(28), 0x0); + rtl_set_bbreg(hw, 0x818, BIT(28), 0x1); + } else { + rtl_set_bbreg(hw, RFPGA0_XCD_RFINTERFACESW, + 0x1CC000, rtlpriv->reg_874); + rtl_set_bbreg(hw, ROFDM0_AGCPARAMETER1, BIT(3), + rtlpriv->reg_c70); + rtl_set_bbreg(hw, RFPGA0_XCD_SWITCHCONTROL, 0xFF000000, + rtlpriv->reg_85c); + rtl_set_bbreg(hw, 0xa74, 0xF000, rtlpriv->reg_a74); + rtl_set_bbreg(hw, 0x818, BIT(28), 0x0); + rtl_set_bbreg(hw, RFPGA0_XCD_RFINTERFACESW, + BIT(5), 0x0); + } + + dm_pstable->pre_rfstate = dm_pstable->cur_rfstate; + } +} + +static void rtl8723ae_dm_dynamic_bpowersaving(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct ps_t *dm_pstable = &rtlpriv->dm_pstable; + + if (((mac->link_state == MAC80211_NOLINK)) && + (rtlpriv->dm.entry_min_undec_sm_pwdb == 0)) { + dm_pstable->rssi_val_min = 0; + RT_TRACE(rtlpriv, DBG_LOUD, DBG_LOUD, + "Not connected to any\n"); + } + + if (mac->link_state == MAC80211_LINKED) { + if (mac->opmode == NL80211_IFTYPE_ADHOC) { + dm_pstable->rssi_val_min = + rtlpriv->dm.entry_min_undec_sm_pwdb; + RT_TRACE(rtlpriv, DBG_LOUD, DBG_LOUD, + "AP Client PWDB = 0x%lx\n", + dm_pstable->rssi_val_min); + } else { + dm_pstable->rssi_val_min = rtlpriv->dm.undec_sm_pwdb; + RT_TRACE(rtlpriv, DBG_LOUD, DBG_LOUD, + "STA Default Port PWDB = 0x%lx\n", + dm_pstable->rssi_val_min); + } + } else { + dm_pstable->rssi_val_min = rtlpriv->dm.entry_min_undec_sm_pwdb; + + RT_TRACE(rtlpriv, DBG_LOUD, DBG_LOUD, + "AP Ext Port PWDB = 0x%lx\n", + dm_pstable->rssi_val_min); + } + + rtl8723ae_dm_rf_saving(hw, false); +} + +void rtl8723ae_dm_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->dm.dm_type = DM_TYPE_BYDRIVER; + rtl8723ae_dm_diginit(hw); + rtl8723ae_dm_init_dynamic_txpower(hw); + rtl8723ae_dm_init_edca_turbo(hw); + rtl8723ae_dm_init_rate_adaptive_mask(hw); + rtl8723ae_dm_initialize_txpower_tracking(hw); + rtl8723ae_dm_init_dynamic_bpowersaving(hw); +} + +void rtl8723ae_dm_watchdog(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + bool fw_current_inpsmode = false; + bool fw_ps_awake = true; + rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_FW_PSMODE_STATUS, + (u8 *) (&fw_current_inpsmode)); + rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_FWLPS_RF_ON, + (u8 *) (&fw_ps_awake)); + + if (ppsc->p2p_ps_info.p2p_ps_mode) + fw_ps_awake = false; + + if ((ppsc->rfpwr_state == ERFON) && + ((!fw_current_inpsmode) && fw_ps_awake) && + (!ppsc->rfchange_inprogress)) { + rtl8723ae_dm_pwdmonitor(hw); + rtl8723ae_dm_dig(hw); + rtl8723ae_dm_false_alarm_counter_statistics(hw); + rtl8723ae_dm_dynamic_bpowersaving(hw); + rtl8723ae_dm_dynamic_txpower(hw); + rtl8723ae_dm_refresh_rate_adaptive_mask(hw); + rtl8723ae_dm_bt_coexist(hw); + rtl8723ae_dm_check_edca_turbo(hw); + } + if (rtlpcipriv->bt_coexist.init_set) + rtl_write_byte(rtlpriv, 0x76e, 0xc); +} + +static void rtl8723ae_dm_init_bt_coexist(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + + rtlpcipriv->bt_coexist.bt_rfreg_origin_1e + = rtl_get_rfreg(hw, (enum radio_path)0, RF_RCK1, 0xfffff); + rtlpcipriv->bt_coexist.bt_rfreg_origin_1f + = rtl_get_rfreg(hw, (enum radio_path)0, RF_RCK2, 0xf0); + + rtlpcipriv->bt_coexist.cstate = 0; + rtlpcipriv->bt_coexist.previous_state = 0; + rtlpcipriv->bt_coexist.cstate_h = 0; + rtlpcipriv->bt_coexist.previous_state_h = 0; + rtlpcipriv->bt_coexist.lps_counter = 0; + + /* Enable counter statistics */ + rtl_write_byte(rtlpriv, 0x76e, 0x4); + rtl_write_byte(rtlpriv, 0x778, 0x3); + rtl_write_byte(rtlpriv, 0x40, 0x20); + + rtlpcipriv->bt_coexist.init_set = true; +} + +void rtl8723ae_dm_bt_coexist(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + u8 tmp_byte = 0; + if (!rtlpcipriv->bt_coexist.bt_coexistence) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_LOUD, + "[DM]{BT], BT not exist!!\n"); + return; + } + + if (!rtlpcipriv->bt_coexist.init_set) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_LOUD, + "[DM][BT], rtl8723ae_dm_bt_coexist()\n"); + + rtl8723ae_dm_init_bt_coexist(hw); + } + + tmp_byte = rtl_read_byte(rtlpriv, 0x40); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_LOUD, + "[DM][BT], 0x40 is 0x%x", tmp_byte); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[DM][BT], bt_dm_coexist start"); + rtl8723ae_dm_bt_coexist_8723(hw); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/dm.h b/drivers/net/wireless/rtlwifi/rtl8723ae/dm.h new file mode 100644 index 0000000..a372b02 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/dm.h @@ -0,0 +1,155 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + **************************************************************************** + */ + +#ifndef __RTL8723E_DM_H__ +#define __RTL8723E_DM_H__ + +#define HAL_DM_HIPWR_DISABLE BIT(1) + +#define OFDM_TABLE_SIZE 37 +#define CCK_TABLE_SIZE 33 + +#define DM_DIG_THRESH_HIGH 40 +#define DM_DIG_THRESH_LOW 35 + +#define DM_FALSEALARM_THRESH_LOW 400 +#define DM_FALSEALARM_THRESH_HIGH 1000 + +#define DM_DIG_MAX 0x3e +#define DM_DIG_MIN 0x1e + +#define DM_DIG_FA_UPPER 0x32 +#define DM_DIG_FA_LOWER 0x20 +#define DM_DIG_FA_TH0 0x20 +#define DM_DIG_FA_TH1 0x100 +#define DM_DIG_FA_TH2 0x200 + +#define DM_DIG_BACKOFF_MAX 12 +#define DM_DIG_BACKOFF_MIN -4 +#define DM_DIG_BACKOFF_DEFAULT 10 + +#define RXPATHSELECTION_SS_TH_LOW 30 +#define RXPATHSELECTION_DIFF_TH 18 + +#define DM_RATR_STA_INIT 0 +#define DM_RATR_STA_HIGH 1 +#define DM_RATR_STA_MIDDLE 2 +#define DM_RATR_STA_LOW 3 + +#define TXHIGHPWRLEVEL_NORMAL 0 +#define TXHIGHPWRLEVEL_LEVEL1 1 +#define TXHIGHPWRLEVEL_LEVEL2 2 +#define TXHIGHPWRLEVEL_BT1 3 +#define TXHIGHPWRLEVEL_BT2 4 + +#define DM_TYPE_BYDRIVER 1 + +#define TX_POWER_NEAR_FIELD_THRESH_LVL2 74 +#define TX_POWER_NEAR_FIELD_THRESH_LVL1 67 + +struct swat_t { + u8 failure_cnt; + u8 try_flag; + u8 stop_trying; + long pre_rssi; + long trying_threshold; + u8 cur_antenna; + u8 pre_antenna; +}; + +enum tag_dynamic_init_gain_operation_type_definition { + DIG_TYPE_THRESH_HIGH = 0, + DIG_TYPE_THRESH_LOW = 1, + DIG_TYPE_BACKOFF = 2, + DIG_TYPE_RX_GAIN_MIN = 3, + DIG_TYPE_RX_GAIN_MAX = 4, + DIG_TYPE_ENABLE = 5, + DIG_TYPE_DISABLE = 6, + DIG_OP_TYPE_MAX +}; + +enum tag_cck_packet_detection_threshold_type_definition { + CCK_PD_STAGE_LowRssi = 0, + CCK_PD_STAGE_HighRssi = 1, + CCK_FA_STAGE_Low = 2, + CCK_FA_STAGE_High = 3, + CCK_PD_STAGE_MAX = 4, +}; + +enum dm_1r_cca_e { + CCA_1R = 0, + CCA_2R = 1, + CCA_MAX = 2, +}; + +enum dm_rf_e { + RF_SAVE = 0, + RF_NORMAL = 1, + RF_MAX = 2, +}; + +enum dm_sw_ant_switch_e { + ANS_ANTENNA_B = 1, + ANS_ANTENNA_A = 2, + ANS_ANTENNA_MAX = 3, +}; + +enum dm_dig_ext_port_alg_e { + DIG_EXT_PORT_STAGE_0 = 0, + DIG_EXT_PORT_STAGE_1 = 1, + DIG_EXT_PORT_STAGE_2 = 2, + DIG_EXT_PORT_STAGE_3 = 3, + DIG_EXT_PORT_STAGE_MAX = 4, +}; + +enum dm_dig_connect_e { + DIG_STA_DISCONNECT = 0, + DIG_STA_CONNECT = 1, + DIG_STA_BEFORE_CONNECT = 2, + DIG_MULTISTA_DISCONNECT = 3, + DIG_MULTISTA_CONNECT = 4, + DIG_CONNECT_MAX +}; + +#define GET_UNDECORATED_AVERAGE_RSSI(_priv) \ + ((((struct rtl_priv *)(_priv))->mac80211.opmode == \ + NL80211_IFTYPE_ADHOC) ? \ + (((struct rtl_priv *)(_priv))->dm.entry_min_undec_sm_pwdb) \ + : (((struct rtl_priv *)(_priv))->dm.undec_sm_pwdb)) + +void rtl8723ae_dm_init(struct ieee80211_hw *hw); +void rtl8723ae_dm_watchdog(struct ieee80211_hw *hw); +void rtl8723ae_dm_write_dig(struct ieee80211_hw *hw); +void rtl8723ae_dm_init_edca_turbo(struct ieee80211_hw *hw); +void rtl8723ae_dm_init_rate_adaptive_mask(struct ieee80211_hw *hw); +void rtl8723ae_dm_rf_saving(struct ieee80211_hw *hw, u8 bforce_in_normal); +void rtl8723ae_dm_bt_coexist(struct ieee80211_hw *hw); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/fw.c b/drivers/net/wireless/rtlwifi/rtl8723ae/fw.c new file mode 100644 index 0000000..dedfa1e --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/fw.c @@ -0,0 +1,838 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + **************************************************************************** + */ + +#include "../wifi.h" +#include "../pci.h" +#include "../base.h" +#include "reg.h" +#include "def.h" +#include "fw.h" + +static void _rtl8723ae_enable_fw_download(struct ieee80211_hw *hw, bool enable) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tmp; + if (enable) { + tmp = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN + 1); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN + 1, tmp | 0x04); + + tmp = rtl_read_byte(rtlpriv, REG_MCUFWDL); + rtl_write_byte(rtlpriv, REG_MCUFWDL, tmp | 0x01); + + tmp = rtl_read_byte(rtlpriv, REG_MCUFWDL + 2); + rtl_write_byte(rtlpriv, REG_MCUFWDL + 2, tmp & 0xf7); + } else { + tmp = rtl_read_byte(rtlpriv, REG_MCUFWDL); + rtl_write_byte(rtlpriv, REG_MCUFWDL, tmp & 0xfe); + + rtl_write_byte(rtlpriv, REG_MCUFWDL + 1, 0x00); + } +} + +static void _rtl8723ae_fw_block_write(struct ieee80211_hw *hw, + const u8 *buffer, u32 size) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 blockSize = sizeof(u32); + u8 *bufferPtr = (u8 *) buffer; + u32 *pu4BytePtr = (u32 *) buffer; + u32 i, offset, blockCount, remainSize; + + blockCount = size / blockSize; + remainSize = size % blockSize; + + for (i = 0; i < blockCount; i++) { + offset = i * blockSize; + rtl_write_dword(rtlpriv, (FW_8192C_START_ADDRESS + offset), + *(pu4BytePtr + i)); + } + + if (remainSize) { + offset = blockCount * blockSize; + bufferPtr += offset; + for (i = 0; i < remainSize; i++) { + rtl_write_byte(rtlpriv, (FW_8192C_START_ADDRESS + + offset + i), *(bufferPtr + i)); + } + } +} + +static void _rtl8723ae_fw_page_write(struct ieee80211_hw *hw, + u32 page, const u8 *buffer, u32 size) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 value8; + u8 u8page = (u8) (page & 0x07); + + value8 = (rtl_read_byte(rtlpriv, REG_MCUFWDL + 2) & 0xF8) | u8page; + + rtl_write_byte(rtlpriv, (REG_MCUFWDL + 2), value8); + _rtl8723ae_fw_block_write(hw, buffer, size); +} + +static void _rtl8723ae_write_fw(struct ieee80211_hw *hw, + enum version_8723e version, u8 *buffer, + u32 size) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 *bufferPtr = (u8 *) buffer; + u32 page_nums, remain_size; + u32 page, offset; + + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, "FW size is %d bytes,\n", size); + + page_nums = size / FW_8192C_PAGE_SIZE; + remain_size = size % FW_8192C_PAGE_SIZE; + + if (page_nums > 6) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Page numbers should not be greater then 6\n"); + } + + for (page = 0; page < page_nums; page++) { + offset = page * FW_8192C_PAGE_SIZE; + _rtl8723ae_fw_page_write(hw, page, (bufferPtr + offset), + FW_8192C_PAGE_SIZE); + } + + if (remain_size) { + offset = page_nums * FW_8192C_PAGE_SIZE; + page = page_nums; + _rtl8723ae_fw_page_write(hw, page, (bufferPtr + offset), + remain_size); + } + + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, "FW write done.\n"); +} + +static int _rtl8723ae_fw_free_to_go(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int err = -EIO; + u32 counter = 0; + u32 value32; + + do { + value32 = rtl_read_dword(rtlpriv, REG_MCUFWDL); + } while ((counter++ < FW_8192C_POLLING_TIMEOUT_COUNT) && + (!(value32 & FWDL_ChkSum_rpt))); + + if (counter >= FW_8192C_POLLING_TIMEOUT_COUNT) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "chksum report faill ! REG_MCUFWDL:0x%08x .\n", + value32); + goto exit; + } + + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, + "Checksum report OK ! REG_MCUFWDL:0x%08x .\n", value32); + + value32 = rtl_read_dword(rtlpriv, REG_MCUFWDL); + value32 |= MCUFWDL_RDY; + value32 &= ~WINTINI_RDY; + rtl_write_dword(rtlpriv, REG_MCUFWDL, value32); + + counter = 0; + + do { + value32 = rtl_read_dword(rtlpriv, REG_MCUFWDL); + if (value32 & WINTINI_RDY) { + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, + "Polling FW ready success!! REG_MCUFWDL:0x%08x .\n", + value32); + err = 0; + goto exit; + } + + mdelay(FW_8192C_POLLING_DELAY); + + } while (counter++ < FW_8192C_POLLING_TIMEOUT_COUNT); + + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Polling FW ready fail!! REG_MCUFWDL:0x%08x .\n", value32); + +exit: + return err; +} + +int rtl8723ae_download_fw(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl8723ae_firmware_header *pfwheader; + u8 *pfwdata; + u32 fwsize; + int err; + enum version_8723e version = rtlhal->version; + + if (!rtlhal->pfirmware) + return 1; + + pfwheader = (struct rtl8723ae_firmware_header *)rtlhal->pfirmware; + pfwdata = (u8 *) rtlhal->pfirmware; + fwsize = rtlhal->fwsize; + + if (IS_FW_HEADER_EXIST(pfwheader)) { + RT_TRACE(rtlpriv, COMP_FW, DBG_DMESG, + "Firmware Version(%d), Signature(%#x),Size(%d)\n", + pfwheader->version, pfwheader->signature, + (int)sizeof(struct rtl8723ae_firmware_header)); + + pfwdata = pfwdata + sizeof(struct rtl8723ae_firmware_header); + fwsize = fwsize - sizeof(struct rtl8723ae_firmware_header); + } + + if (rtl_read_byte(rtlpriv, REG_MCUFWDL)&BIT(7)) { + rtl8723ae_firmware_selfreset(hw); + rtl_write_byte(rtlpriv, REG_MCUFWDL, 0x00); + } + _rtl8723ae_enable_fw_download(hw, true); + _rtl8723ae_write_fw(hw, version, pfwdata, fwsize); + _rtl8723ae_enable_fw_download(hw, false); + + err = _rtl8723ae_fw_free_to_go(hw); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Firmware is not ready to run!\n"); + } else { + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, + "Firmware is ready to run!\n"); + } + return 0; +} + +static bool rtl8723ae_check_fw_read_last_h2c(struct ieee80211_hw *hw, u8 boxnum) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 val_hmetfr, val_mcutst_1; + bool result = false; + + val_hmetfr = rtl_read_byte(rtlpriv, REG_HMETFR); + val_mcutst_1 = rtl_read_byte(rtlpriv, (REG_MCUTST_1 + boxnum)); + + if (((val_hmetfr >> boxnum) & BIT(0)) == 0 && val_mcutst_1 == 0) + result = true; + return result; +} + +static void _rtl8723ae_fill_h2c_command(struct ieee80211_hw *hw, + u8 element_id, u32 cmd_len, + u8 *p_cmdbuffer) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 boxnum; + u16 box_reg = 0, box_extreg = 0; + u8 u1tmp; + bool isfw_rd = false; + bool bwrite_success = false; + u8 wait_h2c_limmit = 100; + u8 wait_writeh2c_limmit = 100; + u8 boxcontent[4], boxextcontent[2]; + u32 h2c_waitcounter = 0; + unsigned long flag; + u8 idx; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "come in\n"); + + while (true) { + spin_lock_irqsave(&rtlpriv->locks.h2c_lock, flag); + if (rtlhal->h2c_setinprogress) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "H2C set in progress! Wait to set..element_id(%d).\n", + element_id); + + while (rtlhal->h2c_setinprogress) { + spin_unlock_irqrestore(&rtlpriv->locks.h2c_lock, + flag); + h2c_waitcounter++; + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Wait 100 us (%d times)...\n", + h2c_waitcounter); + udelay(100); + + if (h2c_waitcounter > 1000) + return; + spin_lock_irqsave(&rtlpriv->locks.h2c_lock, + flag); + } + spin_unlock_irqrestore(&rtlpriv->locks.h2c_lock, flag); + } else { + rtlhal->h2c_setinprogress = true; + spin_unlock_irqrestore(&rtlpriv->locks.h2c_lock, flag); + break; + } + } + + while (!bwrite_success) { + wait_writeh2c_limmit--; + if (wait_writeh2c_limmit == 0) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Write H2C fail because no trigger " + "for FW INT!\n"); + break; + } + + boxnum = rtlhal->last_hmeboxnum; + switch (boxnum) { + case 0: + box_reg = REG_HMEBOX_0; + box_extreg = REG_HMEBOX_EXT_0; + break; + case 1: + box_reg = REG_HMEBOX_1; + box_extreg = REG_HMEBOX_EXT_1; + break; + case 2: + box_reg = REG_HMEBOX_2; + box_extreg = REG_HMEBOX_EXT_2; + break; + case 3: + box_reg = REG_HMEBOX_3; + box_extreg = REG_HMEBOX_EXT_3; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + + isfw_rd = rtl8723ae_check_fw_read_last_h2c(hw, boxnum); + while (!isfw_rd) { + + wait_h2c_limmit--; + if (wait_h2c_limmit == 0) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Wating too long for FW read clear HMEBox(%d)!\n", + boxnum); + break; + } + + udelay(10); + + isfw_rd = rtl8723ae_check_fw_read_last_h2c(hw, boxnum); + u1tmp = rtl_read_byte(rtlpriv, 0x1BF); + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Wating for FW read clear HMEBox(%d)!!! " + "0x1BF = %2x\n", boxnum, u1tmp); + } + + if (!isfw_rd) { + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Write H2C register BOX[%d] fail!!!!! " + "Fw do not read.\n", boxnum); + break; + } + + memset(boxcontent, 0, sizeof(boxcontent)); + memset(boxextcontent, 0, sizeof(boxextcontent)); + boxcontent[0] = element_id; + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "Write element_id box_reg(%4x) = %2x\n", + box_reg, element_id); + + switch (cmd_len) { + case 1: + boxcontent[0] &= ~(BIT(7)); + memcpy((u8 *) (boxcontent) + 1, + p_cmdbuffer, 1); + + for (idx = 0; idx < 4; idx++) { + rtl_write_byte(rtlpriv, box_reg + idx, + boxcontent[idx]); + } + break; + case 2: + boxcontent[0] &= ~(BIT(7)); + memcpy((u8 *) (boxcontent) + 1, + p_cmdbuffer, 2); + + for (idx = 0; idx < 4; idx++) { + rtl_write_byte(rtlpriv, box_reg + idx, + boxcontent[idx]); + } + break; + case 3: + boxcontent[0] &= ~(BIT(7)); + memcpy((u8 *) (boxcontent) + 1, + p_cmdbuffer, 3); + + for (idx = 0; idx < 4; idx++) { + rtl_write_byte(rtlpriv, box_reg + idx, + boxcontent[idx]); + } + break; + case 4: + boxcontent[0] |= (BIT(7)); + memcpy((u8 *) (boxextcontent), + p_cmdbuffer, 2); + memcpy((u8 *) (boxcontent) + 1, + p_cmdbuffer + 2, 2); + + for (idx = 0; idx < 2; idx++) { + rtl_write_byte(rtlpriv, box_extreg + idx, + boxextcontent[idx]); + } + + for (idx = 0; idx < 4; idx++) { + rtl_write_byte(rtlpriv, box_reg + idx, + boxcontent[idx]); + } + break; + case 5: + boxcontent[0] |= (BIT(7)); + memcpy((u8 *) (boxextcontent), + p_cmdbuffer, 2); + memcpy((u8 *) (boxcontent) + 1, + p_cmdbuffer + 2, 3); + + for (idx = 0; idx < 2; idx++) { + rtl_write_byte(rtlpriv, box_extreg + idx, + boxextcontent[idx]); + } + + for (idx = 0; idx < 4; idx++) { + rtl_write_byte(rtlpriv, box_reg + idx, + boxcontent[idx]); + } + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not process\n"); + break; + } + + bwrite_success = true; + + rtlhal->last_hmeboxnum = boxnum + 1; + if (rtlhal->last_hmeboxnum == 4) + rtlhal->last_hmeboxnum = 0; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, + "pHalData->last_hmeboxnum = %d\n", + rtlhal->last_hmeboxnum); + } + + spin_lock_irqsave(&rtlpriv->locks.h2c_lock, flag); + rtlhal->h2c_setinprogress = false; + spin_unlock_irqrestore(&rtlpriv->locks.h2c_lock, flag); + + RT_TRACE(rtlpriv, COMP_CMD, DBG_LOUD, "go out\n"); +} + +void rtl8723ae_fill_h2c_cmd(struct ieee80211_hw *hw, + u8 element_id, u32 cmd_len, u8 *p_cmdbuffer) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + + if (rtlhal->fw_ready == false) { + RT_ASSERT(false, + "return H2C cmd because of Fw download fail!!!\n"); + return; + } + + _rtl8723ae_fill_h2c_command(hw, element_id, cmd_len, p_cmdbuffer); + return; +} + +void rtl8723ae_firmware_selfreset(struct ieee80211_hw *hw) +{ + u8 u1tmp; + u8 delay = 100; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_byte(rtlpriv, REG_HMETFR + 3, 0x20); + u1tmp = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN + 1); + + while (u1tmp & BIT(2)) { + delay--; + if (delay == 0) + break; + udelay(50); + u1tmp = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN + 1); + } + if (delay == 0) { + u1tmp = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN + 1); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN + 1, u1tmp&(~BIT(2))); + } +} + +void rtl8723ae_set_fw_pwrmode_cmd(struct ieee80211_hw *hw, u8 mode) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 u1_h2c_set_pwrmode[3] = { 0 }; + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, "FW LPS mode = %d\n", mode); + + SET_H2CCMD_PWRMODE_PARM_MODE(u1_h2c_set_pwrmode, mode); + SET_H2CCMD_PWRMODE_PARM_SMART_PS(u1_h2c_set_pwrmode, + (rtlpriv->mac80211.p2p) ? + ppsc->smart_ps : 1); + SET_H2CCMD_PWRMODE_PARM_BCN_PASS_TIME(u1_h2c_set_pwrmode, + ppsc->reg_max_lps_awakeintvl); + + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_DMESG, + "rtl8723ae_set_fw_rsvdpagepkt(): u1_h2c_set_pwrmode\n", + u1_h2c_set_pwrmode, 3); + rtl8723ae_fill_h2c_cmd(hw, H2C_SETPWRMODE, 3, u1_h2c_set_pwrmode); + +} + +static bool _rtl8723ae_cmd_send_packet(struct ieee80211_hw *hw, + struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl8192_tx_ring *ring; + struct rtl_tx_desc *pdesc; + unsigned long flags; + struct sk_buff *pskb = NULL; + + ring = &rtlpci->tx_ring[BEACON_QUEUE]; + + pskb = __skb_dequeue(&ring->queue); + if (pskb) + kfree_skb(pskb); + + spin_lock_irqsave(&rtlpriv->locks.irq_th_lock, flags); + + pdesc = &ring->desc[0]; + + rtlpriv->cfg->ops->fill_tx_cmddesc(hw, (u8 *) pdesc, 1, 1, skb); + + __skb_queue_tail(&ring->queue, skb); + + spin_unlock_irqrestore(&rtlpriv->locks.irq_th_lock, flags); + + rtlpriv->cfg->ops->tx_polling(hw, BEACON_QUEUE); + + return true; +} + +static u8 reserved_page_packet[TOTAL_RESERVED_PKT_LEN] = { + /* page 0 beacon */ + 0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0xE0, 0x4C, 0x76, 0x00, 0x42, + 0x00, 0x40, 0x10, 0x10, 0x00, 0x03, 0x50, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x64, 0x00, 0x00, 0x04, 0x00, 0x0C, 0x6C, 0x69, + 0x6E, 0x6B, 0x73, 0x79, 0x73, 0x5F, 0x77, 0x6C, + 0x61, 0x6E, 0x01, 0x04, 0x82, 0x84, 0x8B, 0x96, + 0x03, 0x01, 0x01, 0x06, 0x02, 0x00, 0x00, 0x2A, + 0x01, 0x00, 0x32, 0x08, 0x24, 0x30, 0x48, 0x6C, + 0x0C, 0x12, 0x18, 0x60, 0x2D, 0x1A, 0x6C, 0x18, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3D, 0x00, 0xDD, 0x06, 0x00, 0xE0, 0x4C, 0x02, + 0x01, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 1 beacon */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x20, 0x8C, 0x00, 0x12, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 2 ps-poll */ + 0xA4, 0x10, 0x01, 0xC0, 0x00, 0x40, 0x10, 0x10, + 0x00, 0x03, 0x00, 0xE0, 0x4C, 0x76, 0x00, 0x42, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x20, 0x8C, 0x00, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 3 null */ + 0x48, 0x01, 0x00, 0x00, 0x00, 0x40, 0x10, 0x10, + 0x00, 0x03, 0x00, 0xE0, 0x4C, 0x76, 0x00, 0x42, + 0x00, 0x40, 0x10, 0x10, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x72, 0x00, 0x20, 0x8C, 0x00, 0x12, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 4 probe_resp */ + 0x50, 0x00, 0x00, 0x00, 0x00, 0x40, 0x10, 0x10, + 0x00, 0x03, 0x00, 0xE0, 0x4C, 0x76, 0x00, 0x42, + 0x00, 0x40, 0x10, 0x10, 0x00, 0x03, 0x00, 0x00, + 0x9E, 0x46, 0x15, 0x32, 0x27, 0xF2, 0x2D, 0x00, + 0x64, 0x00, 0x00, 0x04, 0x00, 0x0C, 0x6C, 0x69, + 0x6E, 0x6B, 0x73, 0x79, 0x73, 0x5F, 0x77, 0x6C, + 0x61, 0x6E, 0x01, 0x04, 0x82, 0x84, 0x8B, 0x96, + 0x03, 0x01, 0x01, 0x06, 0x02, 0x00, 0x00, 0x2A, + 0x01, 0x00, 0x32, 0x08, 0x24, 0x30, 0x48, 0x6C, + 0x0C, 0x12, 0x18, 0x60, 0x2D, 0x1A, 0x6C, 0x18, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3D, 0x00, 0xDD, 0x06, 0x00, 0xE0, 0x4C, 0x02, + 0x01, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + /* page 5 probe_resp */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +void rtl8723ae_set_fw_rsvdpagepkt(struct ieee80211_hw *hw, bool dl_finished) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct sk_buff *skb = NULL; + + u32 totalpacketlen; + bool rtstatus; + u8 u1RsvdPageLoc[3] = { 0 }; + bool dlok = false; + + u8 *beacon; + u8 *p_pspoll; + u8 *nullfunc; + u8 *p_probersp; + /*--------------------------------------------------------- + (1) beacon + --------------------------------------------------------- + */ + beacon = &reserved_page_packet[BEACON_PG * 128]; + SET_80211_HDR_ADDRESS2(beacon, mac->mac_addr); + SET_80211_HDR_ADDRESS3(beacon, mac->bssid); + + /*------------------------------------------------------- + (2) ps-poll + -------------------------------------------------------- + */ + p_pspoll = &reserved_page_packet[PSPOLL_PG * 128]; + SET_80211_PS_POLL_AID(p_pspoll, (mac->assoc_id | 0xc000)); + SET_80211_PS_POLL_BSSID(p_pspoll, mac->bssid); + SET_80211_PS_POLL_TA(p_pspoll, mac->mac_addr); + + SET_H2CCMD_RSVDPAGE_LOC_PSPOLL(u1RsvdPageLoc, PSPOLL_PG); + + /*-------------------------------------------------------- + (3) null data + ---------------------------------------------------------i + */ + nullfunc = &reserved_page_packet[NULL_PG * 128]; + SET_80211_HDR_ADDRESS1(nullfunc, mac->bssid); + SET_80211_HDR_ADDRESS2(nullfunc, mac->mac_addr); + SET_80211_HDR_ADDRESS3(nullfunc, mac->bssid); + + SET_H2CCMD_RSVDPAGE_LOC_NULL_DATA(u1RsvdPageLoc, NULL_PG); + + /*--------------------------------------------------------- + (4) probe response + ---------------------------------------------------------- + */ + p_probersp = &reserved_page_packet[PROBERSP_PG * 128]; + SET_80211_HDR_ADDRESS1(p_probersp, mac->bssid); + SET_80211_HDR_ADDRESS2(p_probersp, mac->mac_addr); + SET_80211_HDR_ADDRESS3(p_probersp, mac->bssid); + + SET_H2CCMD_RSVDPAGE_LOC_PROBE_RSP(u1RsvdPageLoc, PROBERSP_PG); + + totalpacketlen = TOTAL_RESERVED_PKT_LEN; + + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_LOUD, + "rtl8723ae_set_fw_rsvdpagepkt(): HW_VAR_SET_TX_CMD: ALL\n", + &reserved_page_packet[0], totalpacketlen); + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_DMESG, + "rtl8723ae_set_fw_rsvdpagepkt(): HW_VAR_SET_TX_CMD: ALL\n", + u1RsvdPageLoc, 3); + + skb = dev_alloc_skb(totalpacketlen); + memcpy((u8 *) skb_put(skb, totalpacketlen), + &reserved_page_packet, totalpacketlen); + + rtstatus = _rtl8723ae_cmd_send_packet(hw, skb); + + if (rtstatus) + dlok = true; + + if (dlok) { + RT_TRACE(rtlpriv, COMP_POWER, DBG_LOUD, + "Set RSVD page location to Fw.\n"); + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_DMESG, + "H2C_RSVDPAGE:\n", + u1RsvdPageLoc, 3); + rtl8723ae_fill_h2c_cmd(hw, H2C_RSVDPAGE, + sizeof(u1RsvdPageLoc), u1RsvdPageLoc); + } else + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Set RSVD page location to Fw FAIL!!!!!!.\n"); +} + +void rtl8723ae_set_fw_joinbss_report_cmd(struct ieee80211_hw *hw, u8 mstatus) +{ + u8 u1_joinbssrpt_parm[1] = { 0 }; + + SET_H2CCMD_JOINBSSRPT_PARM_OPMODE(u1_joinbssrpt_parm, mstatus); + + rtl8723ae_fill_h2c_cmd(hw, H2C_JOINBSSRPT, 1, u1_joinbssrpt_parm); +} + +static void rtl8723e_set_p2p_ctw_period_cmd(struct ieee80211_hw *hw, + u8 ctwindow) +{ + u8 u1_ctwindow_period[1] = {ctwindow}; + + rtl8723ae_fill_h2c_cmd(hw, H2C_P2P_PS_CTW_CMD, 1, u1_ctwindow_period); +} + +void rtl8723ae_set_p2p_ps_offload_cmd(struct ieee80211_hw *hw, u8 p2p_ps_state) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *rtlps = rtl_psc(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_p2p_ps_info *p2pinfo = &(rtlps->p2p_ps_info); + struct p2p_ps_offload_t *p2p_ps_offload = &rtlhal->p2p_ps_offload; + u8 i; + u16 ctwindow; + u32 start_time, tsf_low; + + switch (p2p_ps_state) { + case P2P_PS_DISABLE: + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, "P2P_PS_DISABLE\n"); + memset(p2p_ps_offload, 0, sizeof(struct p2p_ps_offload_t)); + break; + case P2P_PS_ENABLE: + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, "P2P_PS_ENABLE\n"); + /* update CTWindow value. */ + if (p2pinfo->ctwindow > 0) { + p2p_ps_offload->ctwindow_en = 1; + ctwindow = p2pinfo->ctwindow; + rtl8723e_set_p2p_ctw_period_cmd(hw, ctwindow); + } + + /* hw only support 2 set of NoA */ + for (i = 0; i < p2pinfo->noa_num; i++) { + /* To control the register setting for which NOA*/ + rtl_write_byte(rtlpriv, 0x5cf, (i << 4)); + if (i == 0) + p2p_ps_offload->noa0_en = 1; + else + p2p_ps_offload->noa1_en = 1; + + /* config P2P NoA Descriptor Register */ + rtl_write_dword(rtlpriv, 0x5E0, + p2pinfo->noa_duration[i]); + rtl_write_dword(rtlpriv, 0x5E4, + p2pinfo->noa_interval[i]); + + /*Get Current TSF value */ + tsf_low = rtl_read_dword(rtlpriv, REG_TSFTR); + + start_time = p2pinfo->noa_start_time[i]; + if (p2pinfo->noa_count_type[i] != 1) { + while (start_time <= (tsf_low+(50*1024))) { + start_time += p2pinfo->noa_interval[i]; + if (p2pinfo->noa_count_type[i] != 255) + p2pinfo->noa_count_type[i]--; + } + } + rtl_write_dword(rtlpriv, 0x5E8, start_time); + rtl_write_dword(rtlpriv, 0x5EC, + p2pinfo->noa_count_type[i]); + } + if ((p2pinfo->opp_ps == 1) || (p2pinfo->noa_num > 0)) { + /* rst p2p circuit */ + rtl_write_byte(rtlpriv, REG_DUAL_TSF_RST, BIT(4)); + + p2p_ps_offload->offload_en = 1; + + if (P2P_ROLE_GO == rtlpriv->mac80211.p2p) { + p2p_ps_offload->role = 1; + p2p_ps_offload->allstasleep = 0; + } else { + p2p_ps_offload->role = 0; + } + p2p_ps_offload->discovery = 0; + } + break; + case P2P_PS_SCAN: + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, "P2P_PS_SCAN\n"); + p2p_ps_offload->discovery = 1; + break; + case P2P_PS_SCAN_DONE: + RT_TRACE(rtlpriv, COMP_FW, DBG_LOUD, "P2P_PS_SCAN_DONE\n"); + p2p_ps_offload->discovery = 0; + p2pinfo->p2p_ps_state = P2P_PS_ENABLE; + break; + default: + break; + } + rtl8723ae_fill_h2c_cmd(hw, H2C_P2P_PS_OFFLOAD, 1, (u8 *)p2p_ps_offload); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/fw.h b/drivers/net/wireless/rtlwifi/rtl8723ae/fw.h new file mode 100644 index 0000000..ed3b795 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/fw.h @@ -0,0 +1,104 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * Larry Finger + * + **************************************************************************** + */ + +#ifndef __RTL92C__FW__H__ +#define __RTL92C__FW__H__ + +#define FW_8192C_START_ADDRESS 0x1000 +#define FW_8192C_END_ADDRESS 0x3FFF +#define FW_8192C_PAGE_SIZE 4096 +#define FW_8192C_POLLING_DELAY 5 +#define FW_8192C_POLLING_TIMEOUT_COUNT 1000 + +#define BEACON_PG 0 +#define PSPOLL_PG 2 +#define NULL_PG 3 +#define PROBERSP_PG 4 /* ->5 */ + +#define TOTAL_RESERVED_PKT_LEN 768 + +#define IS_FW_HEADER_EXIST(_pfwhdr) \ + ((_pfwhdr->signature&0xFF00) == 0x2300) + +struct rtl8723ae_firmware_header { + u16 signature; + u8 category; + u8 function; + u16 version; + u8 subversion; + u8 rsvd1; + u8 month; + u8 date; + u8 hour; + u8 minute; + u16 ramcodeSize; + u16 rsvd2; + u32 svnindex; + u32 rsvd3; + u32 rsvd4; + u32 rsvd5; +}; + +enum rtl8192c_h2c_cmd { + H2C_AP_OFFLOAD = 0, + H2C_SETPWRMODE = 1, + H2C_JOINBSSRPT = 2, + H2C_RSVDPAGE = 3, + H2C_RSSI_REPORT = 4, + H2C_P2P_PS_CTW_CMD = 5, + H2C_P2P_PS_OFFLOAD = 6, + H2C_RA_MASK = 7, + MAX_H2CCMD +}; + +#define SET_H2CCMD_PWRMODE_PARM_MODE(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE(__ph2ccmd, 0, 8, __val) +#define SET_H2CCMD_PWRMODE_PARM_SMART_PS(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE((__ph2ccmd)+1, 0, 8, __val) +#define SET_H2CCMD_PWRMODE_PARM_BCN_PASS_TIME(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE((__ph2ccmd)+2, 0, 8, __val) +#define SET_H2CCMD_JOINBSSRPT_PARM_OPMODE(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE(__ph2ccmd, 0, 8, __val) +#define SET_H2CCMD_RSVDPAGE_LOC_PROBE_RSP(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE(__ph2ccmd, 0, 8, __val) +#define SET_H2CCMD_RSVDPAGE_LOC_PSPOLL(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE((__ph2ccmd)+1, 0, 8, __val) +#define SET_H2CCMD_RSVDPAGE_LOC_NULL_DATA(__ph2ccmd, __val) \ + SET_BITS_TO_LE_1BYTE((__ph2ccmd)+2, 0, 8, __val) + +int rtl8723ae_download_fw(struct ieee80211_hw *hw); +void rtl8723ae_fill_h2c_cmd(struct ieee80211_hw *hw, u8 element_id, + u32 cmd_len, u8 *p_cmdbuffer); +void rtl8723ae_firmware_selfreset(struct ieee80211_hw *hw); +void rtl8723ae_set_fw_pwrmode_cmd(struct ieee80211_hw *hw, u8 mode); +void rtl8723ae_set_fw_rsvdpagepkt(struct ieee80211_hw *hw, bool b_dl_finished); +void rtl8723ae_set_fw_joinbss_report_cmd(struct ieee80211_hw *hw, u8 mstatus); +void rtl8723ae_set_p2p_ps_offload_cmd(struct ieee80211_hw *hw, u8 p2p_ps_state); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/hal_bt_coexist.c b/drivers/net/wireless/rtlwifi/rtl8723ae/hal_bt_coexist.c new file mode 100644 index 0000000..3d092e4 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/hal_bt_coexist.c @@ -0,0 +1,542 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "hal_bt_coexist.h" +#include "../pci.h" +#include "dm.h" +#include "fw.h" +#include "phy.h" +#include "reg.h" +#include "hal_btc.h" + +void rtl8723ae_dm_bt_reject_ap_aggregated_packet(struct ieee80211_hw *hw, + bool reject) +{ +} + +void _rtl8723_dm_bt_check_wifi_state(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + if (rtlpriv->link_info.busytraffic) { + rtlpcipriv->bt_coexist.cstate &= ~BT_COEX_STATE_WIFI_IDLE; + + if (rtlpriv->link_info.tx_busy_traffic) + rtlpcipriv->bt_coexist.cstate |= + BT_COEX_STATE_WIFI_UPLINK; + else + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_UPLINK; + + if (rtlpriv->link_info.rx_busy_traffic) + rtlpcipriv->bt_coexist.cstate |= + BT_COEX_STATE_WIFI_DOWNLINK; + else + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_DOWNLINK; + } else { + rtlpcipriv->bt_coexist.cstate |= BT_COEX_STATE_WIFI_IDLE; + rtlpcipriv->bt_coexist.cstate &= ~BT_COEX_STATE_WIFI_UPLINK; + rtlpcipriv->bt_coexist.cstate &= ~BT_COEX_STATE_WIFI_DOWNLINK; + } + + if (rtlpriv->mac80211.mode == WIRELESS_MODE_G || + rtlpriv->mac80211.mode == WIRELESS_MODE_B) { + rtlpcipriv->bt_coexist.cstate |= BT_COEX_STATE_WIFI_LEGACY; + rtlpcipriv->bt_coexist.cstate &= ~BT_COEX_STATE_WIFI_HT20; + rtlpcipriv->bt_coexist.cstate &= ~BT_COEX_STATE_WIFI_HT40; + } else { + rtlpcipriv->bt_coexist.cstate &= ~BT_COEX_STATE_WIFI_LEGACY; + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20_40) { + rtlpcipriv->bt_coexist.cstate |= + BT_COEX_STATE_WIFI_HT40; + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_HT20; + } else { + rtlpcipriv->bt_coexist.cstate |= + BT_COEX_STATE_WIFI_HT20; + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_HT40; + } + } + + if (rtlpriv->bt_operation_on) + rtlpcipriv->bt_coexist.cstate |= BT_COEX_STATE_BT30; + else + rtlpcipriv->bt_coexist.cstate &= ~BT_COEX_STATE_BT30; +} + +u8 rtl8723ae_dm_bt_check_coex_rssi_state1(struct ieee80211_hw *hw, + u8 level_num, u8 rssi_thresh, + u8 rssi_thresh1) + +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + long smooth; + u8 bt_rssi_state = 0; + + smooth = rtl8723ae_dm_bt_get_rx_ss(hw); + + if (level_num == 2) { + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_RSSI_1_MEDIUM; + + if ((rtlpcipriv->bt_coexist.bt_pre_rssi_state == + BT_RSSI_STATE_LOW) || + (rtlpcipriv->bt_coexist.bt_pre_rssi_state == + BT_RSSI_STATE_STAY_LOW)) { + if (smooth >= (rssi_thresh + + BT_FW_COEX_THRESH_TOL)) { + bt_rssi_state = BT_RSSI_STATE_HIGH; + rtlpcipriv->bt_coexist.cstate |= + BT_COEX_STATE_WIFI_RSSI_1_HIGH; + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_RSSI_1_LOW; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI_1 state switch to High\n"); + } else { + bt_rssi_state = BT_RSSI_STATE_STAY_LOW; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI_1 state stay at Low\n"); + } + } else { + if (smooth < rssi_thresh) { + bt_rssi_state = BT_RSSI_STATE_LOW; + rtlpcipriv->bt_coexist.cstate |= + BT_COEX_STATE_WIFI_RSSI_1_LOW; + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_RSSI_1_HIGH; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI_1 state switch to Low\n"); + } else { + bt_rssi_state = BT_RSSI_STATE_STAY_HIGH; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI_1 state stay at High\n"); + } + } + } else if (level_num == 3) { + if (rssi_thresh > rssi_thresh1) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI_1 thresh error!!\n"); + return rtlpcipriv->bt_coexist.bt_pre_rssi_state; + } + + if ((rtlpcipriv->bt_coexist.bt_pre_rssi_state == + BT_RSSI_STATE_LOW) || + (rtlpcipriv->bt_coexist.bt_pre_rssi_state == + BT_RSSI_STATE_STAY_LOW)) { + if (smooth >= + (rssi_thresh+BT_FW_COEX_THRESH_TOL)) { + bt_rssi_state = BT_RSSI_STATE_MEDIUM; + rtlpcipriv->bt_coexist.cstate |= + BT_COEX_STATE_WIFI_RSSI_1_MEDIUM; + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_RSSI_1_LOW; + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_RSSI_1_HIGH; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI_1 state switch to Medium\n"); + } else { + bt_rssi_state = BT_RSSI_STATE_STAY_LOW; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI_1 state stay at Low\n"); + } + } else if ((rtlpcipriv->bt_coexist.bt_pre_rssi_state == + BT_RSSI_STATE_MEDIUM) || + (rtlpcipriv->bt_coexist.bt_pre_rssi_state == + BT_RSSI_STATE_STAY_MEDIUM)) { + if (smooth >= (rssi_thresh1 + + BT_FW_COEX_THRESH_TOL)) { + bt_rssi_state = BT_RSSI_STATE_HIGH; + rtlpcipriv->bt_coexist.cstate |= + BT_COEX_STATE_WIFI_RSSI_1_HIGH; + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_RSSI_1_LOW; + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_RSSI_1_MEDIUM; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI_1 state switch to High\n"); + } else if (smooth < rssi_thresh) { + bt_rssi_state = BT_RSSI_STATE_LOW; + rtlpcipriv->bt_coexist.cstate |= + BT_COEX_STATE_WIFI_RSSI_1_LOW; + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_RSSI_1_HIGH; + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_RSSI_1_MEDIUM; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI_1 state switch to Low\n"); + } else { + bt_rssi_state = BT_RSSI_STATE_STAY_MEDIUM; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI_1 state stay at Medium\n"); + } + } else { + if (smooth < rssi_thresh1) { + bt_rssi_state = BT_RSSI_STATE_MEDIUM; + rtlpcipriv->bt_coexist.cstate |= + BT_COEX_STATE_WIFI_RSSI_1_MEDIUM; + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_RSSI_1_HIGH; + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_RSSI_1_LOW; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI_1 state switch to Medium\n"); + } else { + bt_rssi_state = BT_RSSI_STATE_STAY_HIGH; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI_1 state stay at High\n"); + } + } + } + + rtlpcipriv->bt_coexist.bt_pre_rssi_state1 = bt_rssi_state; + + return bt_rssi_state; +} + +u8 rtl8723ae_dm_bt_check_coex_rssi_state(struct ieee80211_hw *hw, + u8 level_num, u8 rssi_thresh, + u8 rssi_thresh1) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + long smooth; + u8 bt_rssi_state = 0; + + smooth = rtl8723ae_dm_bt_get_rx_ss(hw); + + if (level_num == 2) { + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_RSSI_MEDIUM; + + if ((rtlpcipriv->bt_coexist.bt_pre_rssi_state == + BT_RSSI_STATE_LOW) || + (rtlpcipriv->bt_coexist.bt_pre_rssi_state == + BT_RSSI_STATE_STAY_LOW)){ + if (smooth >= + (rssi_thresh + BT_FW_COEX_THRESH_TOL)) { + bt_rssi_state = BT_RSSI_STATE_HIGH; + rtlpcipriv->bt_coexist.cstate |= + BT_COEX_STATE_WIFI_RSSI_HIGH; + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_RSSI_LOW; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI state switch to High\n"); + } else { + bt_rssi_state = BT_RSSI_STATE_STAY_LOW; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI state stay at Low\n"); + } + } else { + if (smooth < rssi_thresh) { + bt_rssi_state = BT_RSSI_STATE_LOW; + rtlpcipriv->bt_coexist.cstate |= + BT_COEX_STATE_WIFI_RSSI_LOW; + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_WIFI_RSSI_HIGH; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI state switch to Low\n"); + } else { + bt_rssi_state = BT_RSSI_STATE_STAY_HIGH; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI state stay at High\n"); + } + } + } else if (level_num == 3) { + if (rssi_thresh > rssi_thresh1) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI thresh error!!\n"); + return rtlpcipriv->bt_coexist.bt_pre_rssi_state; + } + if ((rtlpcipriv->bt_coexist.bt_pre_rssi_state == + BT_RSSI_STATE_LOW) || + (rtlpcipriv->bt_coexist.bt_pre_rssi_state == + BT_RSSI_STATE_STAY_LOW)) { + if (smooth >= + (rssi_thresh + BT_FW_COEX_THRESH_TOL)) { + bt_rssi_state = BT_RSSI_STATE_MEDIUM; + rtlpcipriv->bt_coexist.cstate + |= BT_COEX_STATE_WIFI_RSSI_MEDIUM; + rtlpcipriv->bt_coexist.cstate + &= ~BT_COEX_STATE_WIFI_RSSI_LOW; + rtlpcipriv->bt_coexist.cstate + &= ~BT_COEX_STATE_WIFI_RSSI_HIGH; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI state switch to Medium\n"); + } else { + bt_rssi_state = BT_RSSI_STATE_STAY_LOW; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI state stay at Low\n"); + } + } else if ((rtlpcipriv->bt_coexist.bt_pre_rssi_state == + BT_RSSI_STATE_MEDIUM) || + (rtlpcipriv->bt_coexist.bt_pre_rssi_state == + BT_RSSI_STATE_STAY_MEDIUM)) { + if (smooth >= + (rssi_thresh1 + BT_FW_COEX_THRESH_TOL)) { + bt_rssi_state = BT_RSSI_STATE_HIGH; + rtlpcipriv->bt_coexist.cstate + |= BT_COEX_STATE_WIFI_RSSI_HIGH; + rtlpcipriv->bt_coexist.cstate + &= ~BT_COEX_STATE_WIFI_RSSI_LOW; + rtlpcipriv->bt_coexist.cstate + &= ~BT_COEX_STATE_WIFI_RSSI_MEDIUM; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI state switch to High\n"); + } else if (smooth < rssi_thresh) { + bt_rssi_state = BT_RSSI_STATE_LOW; + rtlpcipriv->bt_coexist.cstate + |= BT_COEX_STATE_WIFI_RSSI_LOW; + rtlpcipriv->bt_coexist.cstate + &= ~BT_COEX_STATE_WIFI_RSSI_HIGH; + rtlpcipriv->bt_coexist.cstate + &= ~BT_COEX_STATE_WIFI_RSSI_MEDIUM; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI state switch to Low\n"); + } else { + bt_rssi_state = BT_RSSI_STATE_STAY_MEDIUM; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI state stay at Medium\n"); + } + } else { + if (smooth < rssi_thresh1) { + bt_rssi_state = BT_RSSI_STATE_MEDIUM; + rtlpcipriv->bt_coexist.cstate + |= BT_COEX_STATE_WIFI_RSSI_MEDIUM; + rtlpcipriv->bt_coexist.cstate + &= ~BT_COEX_STATE_WIFI_RSSI_HIGH; + rtlpcipriv->bt_coexist.cstate + &= ~BT_COEX_STATE_WIFI_RSSI_LOW; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI state switch to Medium\n"); + } else { + bt_rssi_state = BT_RSSI_STATE_STAY_HIGH; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], RSSI state stay at High\n"); + } + } + } + + rtlpcipriv->bt_coexist.bt_pre_rssi_state = bt_rssi_state; + return bt_rssi_state; +} + +long rtl8723ae_dm_bt_get_rx_ss(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + long smooth = 0; + + if (rtlpriv->mac80211.link_state >= MAC80211_LINKED) + smooth = GET_UNDECORATED_AVERAGE_RSSI(rtlpriv); + else + smooth = rtlpriv->dm.entry_min_undec_sm_pwdb; + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "rtl8723ae_dm_bt_get_rx_ss() = %ld\n", smooth); + + return smooth; +} + +void rtl8723ae_dm_bt_balance(struct ieee80211_hw *hw, + bool balance_on, u8 ms0, u8 ms1) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 h2c_parameter[3] = {0}; + + if (balance_on) { + h2c_parameter[2] = 1; + h2c_parameter[1] = ms1; + h2c_parameter[0] = ms0; + rtlpcipriv->bt_coexist.fw_coexist_all_off = false; + } else { + h2c_parameter[2] = 0; + h2c_parameter[1] = 0; + h2c_parameter[0] = 0; + } + rtlpcipriv->bt_coexist.balance_on = balance_on; + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[DM][BT], Balance=[%s:%dms:%dms], write 0xc=0x%x\n", + balance_on ? "ON" : "OFF", ms0, ms1, + h2c_parameter[0]<<16 | h2c_parameter[1]<<8 | h2c_parameter[2]); + + rtl8723ae_fill_h2c_cmd(hw, 0xc, 3, h2c_parameter); +} + + +void rtl8723ae_dm_bt_agc_table(struct ieee80211_hw *hw, u8 type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + + if (type == BT_AGCTABLE_OFF) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BT]AGCTable Off!\n"); + rtl_write_dword(rtlpriv, 0xc78, 0x641c0001); + rtl_write_dword(rtlpriv, 0xc78, 0x631d0001); + rtl_write_dword(rtlpriv, 0xc78, 0x621e0001); + rtl_write_dword(rtlpriv, 0xc78, 0x611f0001); + rtl_write_dword(rtlpriv, 0xc78, 0x60200001); + + rtl8723ae_phy_set_rf_reg(hw, RF90_PATH_A, + RF_RX_AGC_HP, 0xfffff, 0x32000); + rtl8723ae_phy_set_rf_reg(hw, RF90_PATH_A, + RF_RX_AGC_HP, 0xfffff, 0x71000); + rtl8723ae_phy_set_rf_reg(hw, RF90_PATH_A, + RF_RX_AGC_HP, 0xfffff, 0xb0000); + rtl8723ae_phy_set_rf_reg(hw, RF90_PATH_A, + RF_RX_AGC_HP, 0xfffff, 0xfc000); + rtl8723ae_phy_set_rf_reg(hw, RF90_PATH_A, + RF_RX_G1, 0xfffff, 0x30355); + } else if (type == BT_AGCTABLE_ON) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BT]AGCTable On!\n"); + rtl_write_dword(rtlpriv, 0xc78, 0x4e1c0001); + rtl_write_dword(rtlpriv, 0xc78, 0x4d1d0001); + rtl_write_dword(rtlpriv, 0xc78, 0x4c1e0001); + rtl_write_dword(rtlpriv, 0xc78, 0x4b1f0001); + rtl_write_dword(rtlpriv, 0xc78, 0x4a200001); + + rtl8723ae_phy_set_rf_reg(hw, RF90_PATH_A, + RF_RX_AGC_HP, 0xfffff, 0xdc000); + rtl8723ae_phy_set_rf_reg(hw, RF90_PATH_A, + RF_RX_AGC_HP, 0xfffff, 0x90000); + rtl8723ae_phy_set_rf_reg(hw, RF90_PATH_A, + RF_RX_AGC_HP, 0xfffff, 0x51000); + rtl8723ae_phy_set_rf_reg(hw, RF90_PATH_A, + RF_RX_AGC_HP, 0xfffff, 0x12000); + rtl8723ae_phy_set_rf_reg(hw, RF90_PATH_A, + RF_RX_G1, 0xfffff, 0x00355); + + rtlpcipriv->bt_coexist.sw_coexist_all_off = false; + } +} + +void rtl8723ae_dm_bt_bback_off_level(struct ieee80211_hw *hw, u8 type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + + if (type == BT_BB_BACKOFF_OFF) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BT]BBBackOffLevel Off!\n"); + rtl_write_dword(rtlpriv, 0xc04, 0x3a05611); + } else if (type == BT_BB_BACKOFF_ON) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BT]BBBackOffLevel On!\n"); + rtl_write_dword(rtlpriv, 0xc04, 0x3a07611); + rtlpcipriv->bt_coexist.sw_coexist_all_off = false; + } +} + +void rtl8723ae_dm_bt_fw_coex_all_off(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "rtl8723ae_dm_bt_fw_coex_all_off()\n"); + + if (rtlpcipriv->bt_coexist.fw_coexist_all_off) + return; + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "rtl8723ae_dm_bt_fw_coex_all_off(), real Do\n"); + rtl8723ae_dm_bt_fw_coex_all_off_8723a(hw); + rtlpcipriv->bt_coexist.fw_coexist_all_off = true; +} + +void rtl8723ae_dm_bt_sw_coex_all_off(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "rtl8723ae_dm_bt_sw_coex_all_off()\n"); + + if (rtlpcipriv->bt_coexist.sw_coexist_all_off) + return; + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "rtl8723ae_dm_bt_sw_coex_all_off(), real Do\n"); + rtl8723ae_dm_bt_sw_coex_all_off_8723a(hw); + rtlpcipriv->bt_coexist.sw_coexist_all_off = true; +} + +void rtl8723ae_dm_bt_hw_coex_all_off(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "rtl8723ae_dm_bt_hw_coex_all_off()\n"); + + if (rtlpcipriv->bt_coexist.hw_coexist_all_off) + return; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "rtl8723ae_dm_bt_hw_coex_all_off(), real Do\n"); + + rtl8723ae_dm_bt_hw_coex_all_off_8723a(hw); + + rtlpcipriv->bt_coexist.hw_coexist_all_off = true; +} + +void rtl8723ae_btdm_coex_all_off(struct ieee80211_hw *hw) +{ + rtl8723ae_dm_bt_fw_coex_all_off(hw); + rtl8723ae_dm_bt_sw_coex_all_off(hw); + rtl8723ae_dm_bt_hw_coex_all_off(hw); +} + +bool rtl8723ae_dm_bt_is_coexist_state_changed(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + + if ((rtlpcipriv->bt_coexist.previous_state == + rtlpcipriv->bt_coexist.cstate) && + (rtlpcipriv->bt_coexist.previous_state_h == + rtlpcipriv->bt_coexist.cstate_h)) + return false; + else + return true; +} + +bool rtl8723ae_dm_bt_is_wifi_up_link(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (rtlpriv->link_info.tx_busy_traffic) + return true; + else + return false; +} diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/hal_bt_coexist.h b/drivers/net/wireless/rtlwifi/rtl8723ae/hal_bt_coexist.h new file mode 100644 index 0000000..76f4d12 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/hal_bt_coexist.h @@ -0,0 +1,160 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL8723E_HAL_BT_COEXIST_H__ +#define __RTL8723E_HAL_BT_COEXIST_H__ + +#include "../wifi.h" + +/* The reg define is for 8723 */ +#define REG_HIGH_PRIORITY_TXRX 0x770 +#define REG_LOW_PRIORITY_TXRX 0x774 + +#define BT_FW_COEX_THRESH_TOL 6 +#define BT_FW_COEX_THRESH_20 20 +#define BT_FW_COEX_THRESH_23 23 +#define BT_FW_COEX_THRESH_25 25 +#define BT_FW_COEX_THRESH_30 30 +#define BT_FW_COEX_THRESH_35 35 +#define BT_FW_COEX_THRESH_40 40 +#define BT_FW_COEX_THRESH_45 45 +#define BT_FW_COEX_THRESH_47 47 +#define BT_FW_COEX_THRESH_50 50 +#define BT_FW_COEX_THRESH_55 55 + +#define BT_COEX_STATE_BT30 BIT(0) +#define BT_COEX_STATE_WIFI_HT20 BIT(1) +#define BT_COEX_STATE_WIFI_HT40 BIT(2) +#define BT_COEX_STATE_WIFI_LEGACY BIT(3) + +#define BT_COEX_STATE_WIFI_RSSI_LOW BIT(4) +#define BT_COEX_STATE_WIFI_RSSI_MEDIUM BIT(5) +#define BT_COEX_STATE_WIFI_RSSI_HIGH BIT(6) +#define BT_COEX_STATE_DEC_BT_POWER BIT(7) + +#define BT_COEX_STATE_WIFI_IDLE BIT(8) +#define BT_COEX_STATE_WIFI_UPLINK BIT(9) +#define BT_COEX_STATE_WIFI_DOWNLINK BIT(10) + +#define BT_COEX_STATE_BT_INQ_PAGE BIT(11) +#define BT_COEX_STATE_BT_IDLE BIT(12) +#define BT_COEX_STATE_BT_UPLINK BIT(13) +#define BT_COEX_STATE_BT_DOWNLINK BIT(14) + +#define BT_COEX_STATE_HOLD_FOR_BT_OPERATION BIT(15) +#define BT_COEX_STATE_BT_RSSI_LOW BIT(19) + +#define BT_COEX_STATE_PROFILE_HID BIT(20) +#define BT_COEX_STATE_PROFILE_A2DP BIT(21) +#define BT_COEX_STATE_PROFILE_PAN BIT(22) +#define BT_COEX_STATE_PROFILE_SCO BIT(23) + +#define BT_COEX_STATE_WIFI_RSSI_1_LOW BIT(24) +#define BT_COEX_STATE_WIFI_RSSI_1_MEDIUM BIT(25) +#define BT_COEX_STATE_WIFI_RSSI_1_HIGH BIT(26) + +#define BT_COEX_STATE_BTINFO_COMMON BIT(30) +#define BT_COEX_STATE_BTINFO_B_HID_SCOESCO BIT(31) +#define BT_COEX_STATE_BTINFO_B_FTP_A2DP BIT(29) + +#define BT_COEX_STATE_BT_CNT_LEVEL_0 BIT(0) +#define BT_COEX_STATE_BT_CNT_LEVEL_1 BIT(1) +#define BT_COEX_STATE_BT_CNT_LEVEL_2 BIT(2) +#define BT_COEX_STATE_BT_CNT_LEVEL_3 BIT(3) + +#define BT_RSSI_STATE_HIGH 0 +#define BT_RSSI_STATE_MEDIUM 1 +#define BT_RSSI_STATE_LOW 2 +#define BT_RSSI_STATE_STAY_HIGH 3 +#define BT_RSSI_STATE_STAY_MEDIUM 4 +#define BT_RSSI_STATE_STAY_LOW 5 + +#define BT_AGCTABLE_OFF 0 +#define BT_AGCTABLE_ON 1 +#define BT_BB_BACKOFF_OFF 0 +#define BT_BB_BACKOFF_ON 1 +#define BT_FW_NAV_OFF 0 +#define BT_FW_NAV_ON 1 + +#define BT_COEX_MECH_NONE 0 +#define BT_COEX_MECH_SCO 1 +#define BT_COEX_MECH_HID 2 +#define BT_COEX_MECH_A2DP 3 +#define BT_COEX_MECH_PAN 4 +#define BT_COEX_MECH_HID_A2DP 5 +#define BT_COEX_MECH_HID_PAN 6 +#define BT_COEX_MECH_PAN_A2DP 7 +#define BT_COEX_MECH_HID_SCO_ESCO 8 +#define BT_COEX_MECH_FTP_A2DP 9 +#define BT_COEX_MECH_COMMON 10 +#define BT_COEX_MECH_MAX 11 + +#define BT_DBG_PROFILE_NONE 0 +#define BT_DBG_PROFILE_SCO 1 +#define BT_DBG_PROFILE_HID 2 +#define BT_DBG_PROFILE_A2DP 3 +#define BT_DBG_PROFILE_PAN 4 +#define BT_DBG_PROFILE_HID_A2DP 5 +#define BT_DBG_PROFILE_HID_PAN 6 +#define BT_DBG_PROFILE_PAN_A2DP 7 +#define BT_DBG_PROFILE_MAX 9 + +#define BTINFO_B_FTP BIT(7) +#define BTINFO_B_A2DP BIT(6) +#define BTINFO_B_HID BIT(5) +#define BTINFO_B_SCO_BUSY BIT(4) +#define BTINFO_B_ACL_BUSY BIT(3) +#define BTINFO_B_INQ_PAGE BIT(2) +#define BTINFO_B_SCO_ESCO BIT(1) +#define BTINFO_B_CONNECTION BIT(0) + + +void rtl8723ae_btdm_coex_all_off(struct ieee80211_hw *hw); +void rtl8723ae_dm_bt_fw_coex_all_off(struct ieee80211_hw *hw); + +void rtl8723ae_dm_bt_sw_coex_all_off(struct ieee80211_hw *hw); +void rtl8723ae_dm_bt_hw_coex_all_off(struct ieee80211_hw *hw); +long rtl8723ae_dm_bt_get_rx_ss(struct ieee80211_hw *hw); +void rtl8723ae_dm_bt_balance(struct ieee80211_hw *hw, + bool balance_on, u8 ms0, u8 ms1); +void rtl8723ae_dm_bt_agc_table(struct ieee80211_hw *hw, u8 type); +void rtl8723ae_dm_bt_bback_off_level(struct ieee80211_hw *hw, u8 type); +u8 rtl8723ae_dm_bt_check_coex_rssi_state(struct ieee80211_hw *hw, + u8 level_num, u8 rssi_thresh, + u8 rssi_thresh1); +u8 rtl8723ae_dm_bt_check_coex_rssi_state1(struct ieee80211_hw *hw, + u8 level_num, u8 rssi_thresh, + u8 rssi_thresh1); +void _rtl8723_dm_bt_check_wifi_state(struct ieee80211_hw *hw); +void rtl8723ae_dm_bt_reject_ap_aggregated_packet(struct ieee80211_hw *hw, + bool reject); + +bool rtl8723ae_dm_bt_is_coexist_state_changed(struct ieee80211_hw *hw); +bool rtl8723ae_dm_bt_is_wifi_up_link(struct ieee80211_hw *hw); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/hal_btc.c b/drivers/net/wireless/rtlwifi/rtl8723ae/hal_btc.c new file mode 100644 index 0000000..68c2834 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/hal_btc.c @@ -0,0 +1,1784 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + **************************************************************************** + */ +#include "hal_btc.h" +#include "../pci.h" +#include "phy.h" +#include "fw.h" +#include "reg.h" +#include "def.h" + +void rtl8723ae_bt_coex_off_before_lps(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + if (!rtlpcipriv->bt_coexist.bt_coexistence) + return; + + if (ppsc->inactiveps) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BT][DM], Before enter IPS, turn off all Coexist DM\n"); + rtlpcipriv->bt_coexist.cstate = 0; + rtlpcipriv->bt_coexist.previous_state = 0; + rtlpcipriv->bt_coexist.cstate_h = 0; + rtlpcipriv->bt_coexist.previous_state_h = 0; + rtl8723ae_btdm_coex_all_off(hw); + } +} + +static enum _RT_MEDIA_STATUS mgnt_link_status_query(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + enum _RT_MEDIA_STATUS m_status = RT_MEDIA_DISCONNECT; + + u8 bibss = (mac->opmode == NL80211_IFTYPE_ADHOC) ? 1 : 0; + + if (bibss || rtlpriv->mac80211.link_state >= MAC80211_LINKED) + m_status = RT_MEDIA_CONNECT; + + return m_status; +} + +void rtl_8723e_bt_wifi_media_status_notify(struct ieee80211_hw *hw, + bool mstatus) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u8 h2c_parameter[3] = {0}; + u8 chnl; + + if (!rtlpcipriv->bt_coexist.bt_coexistence) + return; + + if (RT_MEDIA_CONNECT == mstatus) + h2c_parameter[0] = 0x1; /* 0: disconnected, 1:connected */ + else + h2c_parameter[0] = 0x0; + + if (mgnt_link_status_query(hw)) { + chnl = rtlphy->current_channel; + h2c_parameter[1] = chnl; + } + + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20_40) + h2c_parameter[2] = 0x30; + else + h2c_parameter[2] = 0x20; + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], FW write 0x19 = 0x%x\n", + h2c_parameter[0]<<16|h2c_parameter[1]<<8|h2c_parameter[2]); + + rtl8723ae_fill_h2c_cmd(hw, 0x19, 3, h2c_parameter); + +} + +static bool rtl8723ae_dm_bt_is_wifi_busy(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + if (rtlpriv->link_info.busytraffic || + rtlpriv->link_info.rx_busy_traffic || + rtlpriv->link_info.tx_busy_traffic) + return true; + else + return false; +} + +static void rtl8723ae_dm_bt_set_fw_3a(struct ieee80211_hw *hw, + u8 byte1, u8 byte2, u8 byte3, + u8 byte4, u8 byte5) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 h2c_parameter[5] = {0}; + + h2c_parameter[0] = byte1; + h2c_parameter[1] = byte2; + h2c_parameter[2] = byte3; + h2c_parameter[3] = byte4; + h2c_parameter[4] = byte5; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], FW write 0x3a(4bytes) = 0x%x%8x\n", + h2c_parameter[0], h2c_parameter[1]<<24 | h2c_parameter[2]<<16 | + h2c_parameter[3]<<8 | h2c_parameter[4]); + rtl8723ae_fill_h2c_cmd(hw, 0x3a, 5, h2c_parameter); +} + +static bool rtl8723ae_dm_bt_need_to_dec_bt_pwr(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (mgnt_link_status_query(hw) == RT_MEDIA_CONNECT) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Need to decrease bt power\n"); + rtlpcipriv->bt_coexist.cstate |= BT_COEX_STATE_DEC_BT_POWER; + return true; + } + + rtlpcipriv->bt_coexist.cstate &= ~BT_COEX_STATE_DEC_BT_POWER; + return false; +} + +static bool rtl8723ae_dm_bt_is_same_coexist_state(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + + if ((rtlpcipriv->bt_coexist.previous_state == + rtlpcipriv->bt_coexist.cstate) && + (rtlpcipriv->bt_coexist.previous_state_h == + rtlpcipriv->bt_coexist.cstate_h)) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[DM][BT], Coexist state do not chang!!\n"); + return true; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[DM][BT], Coexist state changed!!\n"); + return false; + } +} + +static void rtl8723ae_dm_bt_set_coex_table(struct ieee80211_hw *hw, + u32 val_0x6c0, u32 val_0x6c8, + u32 val_0x6cc) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "set coex table, set 0x6c0 = 0x%x\n", val_0x6c0); + rtl_write_dword(rtlpriv, 0x6c0, val_0x6c0); + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "set coex table, set 0x6c8 = 0x%x\n", val_0x6c8); + rtl_write_dword(rtlpriv, 0x6c8, val_0x6c8); + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "set coex table, set 0x6cc = 0x%x\n", val_0x6cc); + rtl_write_byte(rtlpriv, 0x6cc, val_0x6cc); +} + +static void rtl8723ae_dm_bt_set_hw_pta_mode(struct ieee80211_hw *hw, bool mode) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (BT_PTA_MODE_ON == mode) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, "PTA mode on, "); + /* Enable GPIO 0/1/2/3/8 pins for bt */ + rtl_write_byte(rtlpriv, 0x40, 0x20); + rtlpcipriv->bt_coexist.hw_coexist_all_off = false; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, "PTA mode off\n"); + rtl_write_byte(rtlpriv, 0x40, 0x0); + } +} + +static void rtl8723ae_dm_bt_set_sw_rf_rx_lpf_corner(struct ieee80211_hw *hw, + u8 type) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (BT_RF_RX_LPF_CORNER_SHRINK == type) { + /* Shrink RF Rx LPF corner, 0x1e[7:4]=1111 ==> [11:4] by Jenyu*/ + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "Shrink RF Rx LPF corner!!\n"); + rtl8723ae_phy_set_rf_reg(hw, RF90_PATH_A, 0x1e, 0xfffff, + 0xf0ff7); + rtlpcipriv->bt_coexist.sw_coexist_all_off = false; + } else if (BT_RF_RX_LPF_CORNER_RESUME == type) { + /*Resume RF Rx LPF corner*/ + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "Resume RF Rx LPF corner!!\n"); + rtl8723ae_phy_set_rf_reg(hw, RF90_PATH_A, 0x1e, 0xfffff, + rtlpcipriv->bt_coexist.bt_rfreg_origin_1e); + } +} + +static void rtl8723ae_bt_set_penalty_tx_rate_adap(struct ieee80211_hw *hw, + u8 ra_type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + u8 tmu1; + + tmu1 = rtl_read_byte(rtlpriv, 0x4fd); + tmu1 |= BIT(0); + if (BT_TX_RATE_ADAPTIVE_LOW_PENALTY == ra_type) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "Tx rate adaptive, set low penalty!!\n"); + tmu1 &= ~BIT(2); + rtlpcipriv->bt_coexist.sw_coexist_all_off = false; + } else if (BT_TX_RATE_ADAPTIVE_NORMAL == ra_type) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "Tx rate adaptive, set normal!!\n"); + tmu1 |= BIT(2); + } + rtl_write_byte(rtlpriv, 0x4fd, tmu1); +} + +static void rtl8723ae_dm_bt_btdm_structure_reload(struct ieee80211_hw *hw, + struct btdm_8723 *btdm) +{ + btdm->all_off = false; + btdm->agc_table_en = false; + btdm->adc_back_off_on = false; + btdm->b2_ant_hid_en = false; + btdm->low_penalty_rate_adaptive = false; + btdm->rf_rx_lpf_shrink = false; + btdm->reject_aggre_pkt = false; + + btdm->tdma_on = false; + btdm->tdma_ant = TDMA_2ANT; + btdm->tdma_nav = TDMA_NAV_OFF; + btdm->tdma_dac_swing = TDMA_DAC_SWING_OFF; + btdm->fw_dac_swing_lvl = 0x20; + + btdm->tra_tdma_on = false; + btdm->tra_tdma_ant = TDMA_2ANT; + btdm->tra_tdma_nav = TDMA_NAV_OFF; + btdm->ignore_wlan_act = false; + + btdm->ps_tdma_on = false; + btdm->ps_tdma_byte[0] = 0x0; + btdm->ps_tdma_byte[1] = 0x0; + btdm->ps_tdma_byte[2] = 0x0; + btdm->ps_tdma_byte[3] = 0x8; + btdm->ps_tdma_byte[4] = 0x0; + + btdm->pta_on = true; + btdm->val_0x6c0 = 0x5a5aaaaa; + btdm->val_0x6c8 = 0xcc; + btdm->val_0x6cc = 0x3; + + btdm->sw_dac_swing_on = false; + btdm->sw_dac_swing_lvl = 0xc0; + btdm->wlan_act_hi = 0x20; + btdm->wlan_act_lo = 0x10; + btdm->bt_retry_index = 2; + + btdm->dec_bt_pwr = false; +} + +static void dm_bt_btdm_structure_reload_all_off(struct ieee80211_hw *hw, + struct btdm_8723 *btdm) +{ + rtl8723ae_dm_bt_btdm_structure_reload(hw, btdm); + btdm->all_off = true; + btdm->pta_on = false; + btdm->wlan_act_hi = 0x10; +} + +static bool rtl8723ae_dm_bt_is_2_ant_common_action(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct btdm_8723 btdm8723; + bool common = false; + + rtl8723ae_dm_bt_btdm_structure_reload(hw, &btdm8723); + + if (!rtl8723ae_dm_bt_is_wifi_busy(hw) + && !rtlpcipriv->bt_coexist.bt_busy) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Wifi idle + Bt idle, bt coex mechanism always off!!\n"); + dm_bt_btdm_structure_reload_all_off(hw, &btdm8723); + common = true; + } else if (rtl8723ae_dm_bt_is_wifi_busy(hw) + && !rtlpcipriv->bt_coexist.bt_busy) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Wifi non-idle + Bt disabled/idle!!\n"); + btdm8723.low_penalty_rate_adaptive = true; + btdm8723.rf_rx_lpf_shrink = false; + btdm8723.reject_aggre_pkt = false; + + /* sw mechanism */ + btdm8723.agc_table_en = false; + btdm8723.adc_back_off_on = false; + btdm8723.sw_dac_swing_on = false; + + btdm8723.pta_on = true; + btdm8723.val_0x6c0 = 0x5a5aaaaa; + btdm8723.val_0x6c8 = 0xcccc; + btdm8723.val_0x6cc = 0x3; + + btdm8723.tdma_on = false; + btdm8723.tdma_dac_swing = TDMA_DAC_SWING_OFF; + btdm8723.b2_ant_hid_en = false; + + common = true; + } else if (rtlpcipriv->bt_coexist.bt_busy) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Bt non-idle!\n"); + if (mgnt_link_status_query(hw) == RT_MEDIA_CONNECT) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Wifi connection exist\n"); + common = false; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "No Wifi connection!\n"); + btdm8723.rf_rx_lpf_shrink = true; + btdm8723.low_penalty_rate_adaptive = false; + btdm8723.reject_aggre_pkt = false; + + /* sw mechanism */ + btdm8723.agc_table_en = false; + btdm8723.adc_back_off_on = false; + btdm8723.sw_dac_swing_on = false; + + btdm8723.pta_on = true; + btdm8723.val_0x6c0 = 0x55555555; + btdm8723.val_0x6c8 = 0x0000ffff; + btdm8723.val_0x6cc = 0x3; + + btdm8723.tdma_on = false; + btdm8723.tdma_dac_swing = TDMA_DAC_SWING_OFF; + btdm8723.b2_ant_hid_en = false; + + common = true; + } + } + + if (rtl8723ae_dm_bt_need_to_dec_bt_pwr(hw)) + btdm8723.dec_bt_pwr = true; + + if (common) + rtlpcipriv->bt_coexist.cstate |= BT_COEX_STATE_BTINFO_COMMON; + + if (common && rtl8723ae_dm_bt_is_coexist_state_changed(hw)) + rtl8723ae_dm_bt_set_bt_dm(hw, &btdm8723); + + return common; +} + +static void rtl8723ae_dm_bt_set_sw_full_time_dac_swing(struct ieee80211_hw *hw, + bool sw_dac_swing_on, + u32 sw_dac_swing_lvl) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (sw_dac_swing_on) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], SwDacSwing = 0x%x\n", sw_dac_swing_lvl); + rtl8723ae_phy_set_bb_reg(hw, 0x880, 0xff000000, + sw_dac_swing_lvl); + rtlpcipriv->bt_coexist.sw_coexist_all_off = false; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], SwDacSwing Off!\n"); + rtl8723ae_phy_set_bb_reg(hw, 0x880, 0xff000000, 0xc0); + } +} + +static void rtl8723ae_dm_bt_set_fw_dec_bt_pwr(struct ieee80211_hw *hw, + bool dec_bt_pwr) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 h2c_parameter[1] = {0}; + + h2c_parameter[0] = 0; + + if (dec_bt_pwr) { + h2c_parameter[0] |= BIT(1); + rtlpcipriv->bt_coexist.fw_coexist_all_off = false; + } + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], decrease Bt Power : %s, write 0x21 = 0x%x\n", + (dec_bt_pwr ? "Yes!!" : "No!!"), h2c_parameter[0]); + + rtl8723ae_fill_h2c_cmd(hw, 0x21, 1, h2c_parameter); +} + +static void rtl8723ae_dm_bt_set_fw_2_ant_hid(struct ieee80211_hw *hw, + bool enable, bool dac_swing_on) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 h2c_parameter[1] = {0}; + + if (enable) { + h2c_parameter[0] |= BIT(0); + rtlpcipriv->bt_coexist.fw_coexist_all_off = false; + } + if (dac_swing_on) + h2c_parameter[0] |= BIT(1); /* Dac Swing default enable */ + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], turn 2-Ant+HID mode %s, DACSwing:%s, write 0x15 = 0x%x\n", + (enable ? "ON!!" : "OFF!!"), (dac_swing_on ? "ON" : "OFF"), + h2c_parameter[0]); + + rtl8723ae_fill_h2c_cmd(hw, 0x15, 1, h2c_parameter); +} + +static void rtl8723ae_dm_bt_set_fw_tdma_ctrl(struct ieee80211_hw *hw, + bool enable, u8 ant_num, u8 nav_en, + u8 dac_swing_en) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + u8 h2c_parameter[1] = {0}; + u8 h2c_parameter1[1] = {0}; + + h2c_parameter[0] = 0; + h2c_parameter1[0] = 0; + + if (enable) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], set BT PTA update manager to trigger update!!\n"); + h2c_parameter1[0] |= BIT(0); + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], turn TDMA mode ON!!\n"); + h2c_parameter[0] |= BIT(0); /* function enable */ + if (TDMA_1ANT == ant_num) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], TDMA_1ANT\n"); + h2c_parameter[0] |= BIT(1); + } else if (TDMA_2ANT == ant_num) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], TDMA_2ANT\n"); + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], Unknown Ant\n"); + } + + if (TDMA_NAV_OFF == nav_en) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], TDMA_NAV_OFF\n"); + } else if (TDMA_NAV_ON == nav_en) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], TDMA_NAV_ON\n"); + h2c_parameter[0] |= BIT(2); + } + + if (TDMA_DAC_SWING_OFF == dac_swing_en) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], TDMA_DAC_SWING_OFF\n"); + } else if (TDMA_DAC_SWING_ON == dac_swing_en) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], TDMA_DAC_SWING_ON\n"); + h2c_parameter[0] |= BIT(4); + } + rtlpcipriv->bt_coexist.fw_coexist_all_off = false; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], set BT PTA update manager to no update!!\n"); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], turn TDMA mode OFF!!\n"); + } + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], FW2AntTDMA, write 0x26 = 0x%x\n", + h2c_parameter1[0]); + rtl8723ae_fill_h2c_cmd(hw, 0x26, 1, h2c_parameter1); + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], FW2AntTDMA, write 0x14 = 0x%x\n", h2c_parameter[0]); + rtl8723ae_fill_h2c_cmd(hw, 0x14, 1, h2c_parameter); +} + +static void rtl8723ae_dm_bt_set_fw_ignore_wlan_act(struct ieee80211_hw *hw, + bool enable) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + u8 h2c_parameter[1] = {0}; + + if (enable) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], BT Ignore Wlan_Act !!\n"); + h2c_parameter[0] |= BIT(0); /* function enable */ + rtlpcipriv->bt_coexist.fw_coexist_all_off = false; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], BT don't ignore Wlan_Act !!\n"); + } + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], set FW for BT Ignore Wlan_Act, write 0x25 = 0x%x\n", + h2c_parameter[0]); + + rtl8723ae_fill_h2c_cmd(hw, 0x25, 1, h2c_parameter); +} + +static void rtl8723ae_dm_bt_set_fw_tra_tdma_ctrl(struct ieee80211_hw *hw, + bool enable, u8 ant_num, + u8 nav_en) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 h2c_parameter[2] = {0}; + + /* Only 8723 B cut should do this */ + if (IS_VENDOR_8723_A_CUT(rtlhal->version)) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], not 8723B cut, don't set Traditional TDMA!!\n"); + return; + } + + if (enable) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], turn TTDMA mode ON!!\n"); + h2c_parameter[0] |= BIT(0); /* function enable */ + if (TDMA_1ANT == ant_num) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], TTDMA_1ANT\n"); + h2c_parameter[0] |= BIT(1); + } else if (TDMA_2ANT == ant_num) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], TTDMA_2ANT\n"); + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], Unknown Ant\n"); + } + + if (TDMA_NAV_OFF == nav_en) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], TTDMA_NAV_OFF\n"); + } else if (TDMA_NAV_ON == nav_en) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], TTDMA_NAV_ON\n"); + h2c_parameter[1] |= BIT(0); + } + + rtlpcipriv->bt_coexist.fw_coexist_all_off = false; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], turn TTDMA mode OFF!!\n"); + } + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], FW Traditional TDMA, write 0x33 = 0x%x\n", + h2c_parameter[0] << 8 | h2c_parameter[1]); + + rtl8723ae_fill_h2c_cmd(hw, 0x33, 2, h2c_parameter); +} + +static void rtl8723ae_dm_bt_set_fw_dac_swing_level(struct ieee80211_hw *hw, + u8 dac_swing_lvl) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 h2c_parameter[1] = {0}; + + h2c_parameter[0] = dac_swing_lvl; + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], Set Dac Swing Level = 0x%x\n", dac_swing_lvl); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], write 0x29 = 0x%x\n", h2c_parameter[0]); + + rtl8723ae_fill_h2c_cmd(hw, 0x29, 1, h2c_parameter); +} + +static void rtl8723ae_dm_bt_set_fw_bt_hid_info(struct ieee80211_hw *hw, + bool enable) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 h2c_parameter[1] = {0}; + + h2c_parameter[0] = 0; + + if (enable) { + h2c_parameter[0] |= BIT(0); + rtlpcipriv->bt_coexist.fw_coexist_all_off = false; + } + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], Set BT HID information = 0x%x\n", enable); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], write 0x24 = 0x%x\n", h2c_parameter[0]); + + rtl8723ae_fill_h2c_cmd(hw, 0x24, 1, h2c_parameter); +} + +static void rtl8723ae_dm_bt_set_fw_bt_retry_index(struct ieee80211_hw *hw, + u8 retry_index) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 h2c_parameter[1] = {0}; + + h2c_parameter[0] = retry_index; + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], Set BT Retry Index=%d\n", retry_index); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], write 0x23 = 0x%x\n", h2c_parameter[0]); + + rtl8723ae_fill_h2c_cmd(hw, 0x23, 1, h2c_parameter); +} + +static void rtl8723ae_dm_bt_set_fw_wlan_act(struct ieee80211_hw *hw, + u8 wlan_act_hi, u8 wlan_act_lo) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 h2c_parameter_hi[1] = {0}; + u8 h2c_parameter_lo[1] = {0}; + + h2c_parameter_hi[0] = wlan_act_hi; + h2c_parameter_lo[0] = wlan_act_lo; + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], Set WLAN_ACT Hi:Lo = 0x%x/0x%x\n", wlan_act_hi, + wlan_act_lo); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], write 0x22 = 0x%x\n", h2c_parameter_hi[0]); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], write 0x11 = 0x%x\n", h2c_parameter_lo[0]); + + /* WLAN_ACT = High duration, unit:ms */ + rtl8723ae_fill_h2c_cmd(hw, 0x22, 1, h2c_parameter_hi); + /* WLAN_ACT = Low duration, unit:3*625us */ + rtl8723ae_fill_h2c_cmd(hw, 0x11, 1, h2c_parameter_lo); +} + +void rtl8723ae_dm_bt_set_bt_dm(struct ieee80211_hw *hw, struct btdm_8723 *btdm) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + struct btdm_8723 *btdm_8723 = &rtlhal->hal_coex_8723.btdm; + u8 i; + bool fw_current_inpsmode = false; + bool fw_ps_awake = true; + + rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_FW_PSMODE_STATUS, + (u8 *)(&fw_current_inpsmode)); + rtlpriv->cfg->ops->get_hw_reg(hw, HW_VAR_FWLPS_RF_ON, + (u8 *)(&fw_ps_awake)); + + /* check new setting is different than the old one, + * if all the same, don't do the setting again. + */ + if (memcmp(btdm_8723, btdm, sizeof(struct btdm_8723)) == 0) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], the same coexist setting, return!!\n"); + return; + } else { /* save the new coexist setting */ + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], UPDATE TO NEW COEX SETTING!!\n"); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new bAllOff = 0x%x/ 0x%x\n", + btdm_8723->all_off, btdm->all_off); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new agc_table_en = 0x%x/ 0x%x\n", + btdm_8723->agc_table_en, btdm->agc_table_en); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new adc_back_off_on = 0x%x/ 0x%x\n", + btdm_8723->adc_back_off_on, btdm->adc_back_off_on); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new b2_ant_hid_en = 0x%x/ 0x%x\n", + btdm_8723->b2_ant_hid_en, btdm->b2_ant_hid_en); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new bLowPenaltyRateAdaptive = 0x%x/ 0x%x\n", + btdm_8723->low_penalty_rate_adaptive, + btdm->low_penalty_rate_adaptive); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new bRfRxLpfShrink = 0x%x/ 0x%x\n", + btdm_8723->rf_rx_lpf_shrink, btdm->rf_rx_lpf_shrink); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new bRejectAggrePkt = 0x%x/ 0x%x\n", + btdm_8723->reject_aggre_pkt, btdm->reject_aggre_pkt); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new tdma_on = 0x%x/ 0x%x\n", + btdm_8723->tdma_on, btdm->tdma_on); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new tdmaAnt = 0x%x/ 0x%x\n", + btdm_8723->tdma_ant, btdm->tdma_ant); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new tdmaNav = 0x%x/ 0x%x\n", + btdm_8723->tdma_nav, btdm->tdma_nav); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new tdma_dac_swing = 0x%x/ 0x%x\n", + btdm_8723->tdma_dac_swing, btdm->tdma_dac_swing); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new fwDacSwingLvl = 0x%x/ 0x%x\n", + btdm_8723->fw_dac_swing_lvl, btdm->fw_dac_swing_lvl); + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new bTraTdmaOn = 0x%x/ 0x%x\n", + btdm_8723->tra_tdma_on, btdm->tra_tdma_on); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new traTdmaAnt = 0x%x/ 0x%x\n", + btdm_8723->tra_tdma_ant, btdm->tra_tdma_ant); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new traTdmaNav = 0x%x/ 0x%x\n", + btdm_8723->tra_tdma_nav, btdm->tra_tdma_nav); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new bPsTdmaOn = 0x%x/ 0x%x\n", + btdm_8723->ps_tdma_on, btdm->ps_tdma_on); + for (i = 0; i < 5; i++) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new psTdmaByte[i] = 0x%x/ 0x%x\n", + btdm_8723->ps_tdma_byte[i], + btdm->ps_tdma_byte[i]); + } + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new bIgnoreWlanAct = 0x%x/ 0x%x\n", + btdm_8723->ignore_wlan_act, btdm->ignore_wlan_act); + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new bPtaOn = 0x%x/ 0x%x\n", + btdm_8723->pta_on, btdm->pta_on); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new val_0x6c0 = 0x%x/ 0x%x\n", + btdm_8723->val_0x6c0, btdm->val_0x6c0); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new val_0x6c8 = 0x%x/ 0x%x\n", + btdm_8723->val_0x6c8, btdm->val_0x6c8); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new val_0x6cc = 0x%x/ 0x%x\n", + btdm_8723->val_0x6cc, btdm->val_0x6cc); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new sw_dac_swing_on = 0x%x/ 0x%x\n", + btdm_8723->sw_dac_swing_on, btdm->sw_dac_swing_on); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new sw_dac_swing_lvl = 0x%x/ 0x%x\n", + btdm_8723->sw_dac_swing_lvl, + btdm->sw_dac_swing_lvl); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new wlanActHi = 0x%x/ 0x%x\n", + btdm_8723->wlan_act_hi, btdm->wlan_act_hi); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new wlanActLo = 0x%x/ 0x%x\n", + btdm_8723->wlan_act_lo, btdm->wlan_act_lo); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], original/new btRetryIndex = 0x%x/ 0x%x\n", + btdm_8723->bt_retry_index, btdm->bt_retry_index); + + memcpy(btdm_8723, btdm, sizeof(struct btdm_8723)); + } + /* + * Here we only consider when Bt Operation + * inquiry/paging/pairing is ON + * we only need to turn off TDMA + */ + + if (rtlpcipriv->bt_coexist.hold_for_bt_operation) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], set to ignore wlanAct for BT OP!!\n"); + rtl8723ae_dm_bt_set_fw_ignore_wlan_act(hw, true); + return; + } + + if (btdm->all_off) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], disable all coexist mechanism !!\n"); + rtl8723ae_btdm_coex_all_off(hw); + return; + } + + rtl8723ae_dm_bt_reject_ap_aggregated_packet(hw, btdm->reject_aggre_pkt); + + if (btdm->low_penalty_rate_adaptive) + rtl8723ae_bt_set_penalty_tx_rate_adap(hw, + BT_TX_RATE_ADAPTIVE_LOW_PENALTY); + else + rtl8723ae_bt_set_penalty_tx_rate_adap(hw, + BT_TX_RATE_ADAPTIVE_NORMAL); + + if (btdm->rf_rx_lpf_shrink) + rtl8723ae_dm_bt_set_sw_rf_rx_lpf_corner(hw, + BT_RF_RX_LPF_CORNER_SHRINK); + else + rtl8723ae_dm_bt_set_sw_rf_rx_lpf_corner(hw, + BT_RF_RX_LPF_CORNER_RESUME); + + if (btdm->agc_table_en) + rtl8723ae_dm_bt_agc_table(hw, BT_AGCTABLE_ON); + else + rtl8723ae_dm_bt_agc_table(hw, BT_AGCTABLE_OFF); + + if (btdm->adc_back_off_on) + rtl8723ae_dm_bt_bback_off_level(hw, BT_BB_BACKOFF_ON); + else + rtl8723ae_dm_bt_bback_off_level(hw, BT_BB_BACKOFF_OFF); + + rtl8723ae_dm_bt_set_fw_bt_retry_index(hw, btdm->bt_retry_index); + + rtl8723ae_dm_bt_set_fw_dac_swing_level(hw, btdm->fw_dac_swing_lvl); + rtl8723ae_dm_bt_set_fw_wlan_act(hw, btdm->wlan_act_hi, + btdm->wlan_act_lo); + + rtl8723ae_dm_bt_set_coex_table(hw, btdm->val_0x6c0, + btdm->val_0x6c8, btdm->val_0x6cc); + rtl8723ae_dm_bt_set_hw_pta_mode(hw, btdm->pta_on); + + /* Note: There is a constraint between TDMA and 2AntHID + * Only one of 2AntHid and tdma can be turned on + * We should turn off those mechanisms first + * and then turn on them on. + */ + if (btdm->b2_ant_hid_en) { + /* turn off tdma */ + rtl8723ae_dm_bt_set_fw_tra_tdma_ctrl(hw, btdm->tra_tdma_on, + btdm->tra_tdma_ant, + btdm->tra_tdma_nav); + rtl8723ae_dm_bt_set_fw_tdma_ctrl(hw, false, btdm->tdma_ant, + btdm->tdma_nav, + btdm->tdma_dac_swing); + + /* turn off Pstdma */ + rtl8723ae_dm_bt_set_fw_ignore_wlan_act(hw, + btdm->ignore_wlan_act); + /* Antenna control by PTA, 0x870 = 0x300. */ + rtl8723ae_dm_bt_set_fw_3a(hw, 0x0, 0x0, 0x0, 0x8, 0x0); + + /* turn on 2AntHid */ + rtl8723ae_dm_bt_set_fw_bt_hid_info(hw, true); + rtl8723ae_dm_bt_set_fw_2_ant_hid(hw, true, true); + } else if (btdm->tdma_on) { + /* turn off 2AntHid */ + rtl8723ae_dm_bt_set_fw_bt_hid_info(hw, false); + rtl8723ae_dm_bt_set_fw_2_ant_hid(hw, false, false); + + /* turn off pstdma */ + rtl8723ae_dm_bt_set_fw_ignore_wlan_act(hw, + btdm->ignore_wlan_act); + /* Antenna control by PTA, 0x870 = 0x300. */ + rtl8723ae_dm_bt_set_fw_3a(hw, 0x0, 0x0, 0x0, 0x8, 0x0); + + /* turn on tdma */ + rtl8723ae_dm_bt_set_fw_tra_tdma_ctrl(hw, btdm->tra_tdma_on, + btdm->tra_tdma_ant, btdm->tra_tdma_nav); + rtl8723ae_dm_bt_set_fw_tdma_ctrl(hw, true, btdm->tdma_ant, + btdm->tdma_nav, btdm->tdma_dac_swing); + } else if (btdm->ps_tdma_on) { + /* turn off 2AntHid */ + rtl8723ae_dm_bt_set_fw_bt_hid_info(hw, false); + rtl8723ae_dm_bt_set_fw_2_ant_hid(hw, false, false); + + /* turn off tdma */ + rtl8723ae_dm_bt_set_fw_tra_tdma_ctrl(hw, btdm->tra_tdma_on, + btdm->tra_tdma_ant, btdm->tra_tdma_nav); + rtl8723ae_dm_bt_set_fw_tdma_ctrl(hw, false, btdm->tdma_ant, + btdm->tdma_nav, btdm->tdma_dac_swing); + + /* turn on pstdma */ + rtl8723ae_dm_bt_set_fw_ignore_wlan_act(hw, + btdm->ignore_wlan_act); + rtl8723ae_dm_bt_set_fw_3a(hw, + btdm->ps_tdma_byte[0], + btdm->ps_tdma_byte[1], + btdm->ps_tdma_byte[2], + btdm->ps_tdma_byte[3], + btdm->ps_tdma_byte[4]); + } else { + /* turn off 2AntHid */ + rtl8723ae_dm_bt_set_fw_bt_hid_info(hw, false); + rtl8723ae_dm_bt_set_fw_2_ant_hid(hw, false, false); + + /* turn off tdma */ + rtl8723ae_dm_bt_set_fw_tra_tdma_ctrl(hw, btdm->tra_tdma_on, + btdm->tra_tdma_ant, btdm->tra_tdma_nav); + rtl8723ae_dm_bt_set_fw_tdma_ctrl(hw, false, btdm->tdma_ant, + btdm->tdma_nav, btdm->tdma_dac_swing); + + /* turn off pstdma */ + rtl8723ae_dm_bt_set_fw_ignore_wlan_act(hw, + btdm->ignore_wlan_act); + /* Antenna control by PTA, 0x870 = 0x300. */ + rtl8723ae_dm_bt_set_fw_3a(hw, 0x0, 0x0, 0x0, 0x8, 0x0); + } + + /* Note: + * We should add delay for making sure sw DacSwing can be set + * sucessfully. Because of that rtl8723ae_dm_bt_set_fw_2_ant_hid() + * and rtl8723ae_dm_bt_set_fw_tdma_ctrl() + * will overwrite the reg 0x880. + */ + mdelay(30); + rtl8723ae_dm_bt_set_sw_full_time_dac_swing(hw, + btdm->sw_dac_swing_on, btdm->sw_dac_swing_lvl); + rtl8723ae_dm_bt_set_fw_dec_bt_pwr(hw, btdm->dec_bt_pwr); +} + +/*============================================================ + * extern function start with BTDM_ + *============================================================ + */ +static u32 rtl8723ae_dm_bt_tx_rx_couter_h(struct ieee80211_hw *hw) +{ + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u32 counters = 0; + + counters = rtlhal->hal_coex_8723.high_priority_tx + + rtlhal->hal_coex_8723.high_priority_rx; + return counters; +} + +static u32 rtl8723ae_dm_bt_tx_rx_couter_l(struct ieee80211_hw *hw) +{ + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + return rtlhal->hal_coex_8723.low_priority_tx + + rtlhal->hal_coex_8723.low_priority_rx; +} + +static u8 rtl8723ae_dm_bt_bt_tx_rx_counter_level(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + u32 bt_tx_rx_cnt = 0; + u8 bt_tx_rx_cnt_lvl = 0; + + bt_tx_rx_cnt = rtl8723ae_dm_bt_tx_rx_couter_h(hw) + + rtl8723ae_dm_bt_tx_rx_couter_l(hw); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters = %d\n", bt_tx_rx_cnt); + + rtlpcipriv->bt_coexist.cstate_h &= + ~(BT_COEX_STATE_BT_CNT_LEVEL_0 | BT_COEX_STATE_BT_CNT_LEVEL_1 | + BT_COEX_STATE_BT_CNT_LEVEL_2); + + if (bt_tx_rx_cnt >= BT_TXRX_CNT_THRES_3) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters at level 3\n"); + bt_tx_rx_cnt_lvl = BT_TXRX_CNT_LEVEL_3; + rtlpcipriv->bt_coexist.cstate_h |= BT_COEX_STATE_BT_CNT_LEVEL_3; + } else if (bt_tx_rx_cnt >= BT_TXRX_CNT_THRES_2) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters at level 2\n"); + bt_tx_rx_cnt_lvl = BT_TXRX_CNT_LEVEL_2; + rtlpcipriv->bt_coexist.cstate_h |= BT_COEX_STATE_BT_CNT_LEVEL_2; + } else if (bt_tx_rx_cnt >= BT_TXRX_CNT_THRES_1) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters at level 1\n"); + bt_tx_rx_cnt_lvl = BT_TXRX_CNT_LEVEL_1; + rtlpcipriv->bt_coexist.cstate_h |= BT_COEX_STATE_BT_CNT_LEVEL_1; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters at level 0\n"); + bt_tx_rx_cnt_lvl = BT_TXRX_CNT_LEVEL_0; + rtlpcipriv->bt_coexist.cstate_h |= BT_COEX_STATE_BT_CNT_LEVEL_0; + } + return bt_tx_rx_cnt_lvl; +} + +static void rtl8723ae_dm_bt_2_ant_hid_sco_esco(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct btdm_8723 btdm8723; + u8 bt_rssi_state, bt_rssi_state1; + u8 bt_tx_rx_cnt_lvl; + + rtl8723ae_dm_bt_btdm_structure_reload(hw, &btdm8723); + + btdm8723.rf_rx_lpf_shrink = true; + btdm8723.low_penalty_rate_adaptive = true; + btdm8723.reject_aggre_pkt = false; + + bt_tx_rx_cnt_lvl = rtl8723ae_dm_bt_bt_tx_rx_counter_level(hw); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters = %d\n", bt_tx_rx_cnt_lvl); + + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20_40) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, "HT40\n"); + /* coex table */ + btdm8723.val_0x6c0 = 0x55555555; + btdm8723.val_0x6c8 = 0xffff; + btdm8723.val_0x6cc = 0x3; + + /* sw mechanism */ + btdm8723.agc_table_en = false; + btdm8723.adc_back_off_on = false; + btdm8723.sw_dac_swing_on = false; + + /* fw mechanism */ + btdm8723.ps_tdma_on = true; + if (bt_tx_rx_cnt_lvl == BT_TXRX_CNT_LEVEL_2) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters >= 1400\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0x5; + btdm8723.ps_tdma_byte[2] = 0x5; + btdm8723.ps_tdma_byte[3] = 0x2; + btdm8723.ps_tdma_byte[4] = 0x80; + } else if (bt_tx_rx_cnt_lvl == BT_TXRX_CNT_LEVEL_1) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters >= 1200 && < 1400\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0xa; + btdm8723.ps_tdma_byte[2] = 0xa; + btdm8723.ps_tdma_byte[3] = 0x2; + btdm8723.ps_tdma_byte[4] = 0x80; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters < 1200\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0xf; + btdm8723.ps_tdma_byte[2] = 0xf; + btdm8723.ps_tdma_byte[3] = 0x2; + btdm8723.ps_tdma_byte[4] = 0x80; + } + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "HT20 or Legacy\n"); + bt_rssi_state = rtl8723ae_dm_bt_check_coex_rssi_state(hw, 2, + 47, 0); + bt_rssi_state1 = rtl8723ae_dm_bt_check_coex_rssi_state1(hw, 2, + 27, 0); + + /* coex table */ + btdm8723.val_0x6c0 = 0x55555555; + btdm8723.val_0x6c8 = 0xffff; + btdm8723.val_0x6cc = 0x3; + + /* sw mechanism */ + if ((bt_rssi_state == BT_RSSI_STATE_HIGH) || + (bt_rssi_state == BT_RSSI_STATE_STAY_HIGH)) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Wifi rssi high\n"); + btdm8723.agc_table_en = true; + btdm8723.adc_back_off_on = true; + btdm8723.sw_dac_swing_on = false; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Wifi rssi low\n"); + btdm8723.agc_table_en = false; + btdm8723.adc_back_off_on = false; + btdm8723.sw_dac_swing_on = false; + } + + /* fw mechanism */ + btdm8723.ps_tdma_on = true; + if ((bt_rssi_state1 == BT_RSSI_STATE_HIGH) || + (bt_rssi_state1 == BT_RSSI_STATE_STAY_HIGH)) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Wifi rssi-1 high\n"); + /* only rssi high we need to do this, + * when rssi low, the value will modified by fw + */ + rtl_write_byte(rtlpriv, 0x883, 0x40); + if (bt_tx_rx_cnt_lvl == BT_TXRX_CNT_LEVEL_2) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters >= 1400\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0x5; + btdm8723.ps_tdma_byte[2] = 0x5; + btdm8723.ps_tdma_byte[3] = 0x83; + btdm8723.ps_tdma_byte[4] = 0x80; + } else if (bt_tx_rx_cnt_lvl == BT_TXRX_CNT_LEVEL_1) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters >= 1200 && < 1400\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0xa; + btdm8723.ps_tdma_byte[2] = 0xa; + btdm8723.ps_tdma_byte[3] = 0x83; + btdm8723.ps_tdma_byte[4] = 0x80; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters < 1200\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0xf; + btdm8723.ps_tdma_byte[2] = 0xf; + btdm8723.ps_tdma_byte[3] = 0x83; + btdm8723.ps_tdma_byte[4] = 0x80; + } + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Wifi rssi-1 low\n"); + if (bt_tx_rx_cnt_lvl == BT_TXRX_CNT_LEVEL_2) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters >= 1400\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0x5; + btdm8723.ps_tdma_byte[2] = 0x5; + btdm8723.ps_tdma_byte[3] = 0x2; + btdm8723.ps_tdma_byte[4] = 0x80; + } else if (bt_tx_rx_cnt_lvl == BT_TXRX_CNT_LEVEL_1) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters >= 1200 && < 1400\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0xa; + btdm8723.ps_tdma_byte[2] = 0xa; + btdm8723.ps_tdma_byte[3] = 0x2; + btdm8723.ps_tdma_byte[4] = 0x80; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters < 1200\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0xf; + btdm8723.ps_tdma_byte[2] = 0xf; + btdm8723.ps_tdma_byte[3] = 0x2; + btdm8723.ps_tdma_byte[4] = 0x80; + } + } + } + + if (rtl8723ae_dm_bt_need_to_dec_bt_pwr(hw)) + btdm8723.dec_bt_pwr = true; + + /* Always ignore WlanAct if bHid|bSCOBusy|bSCOeSCO */ + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT btInqPageStartTime = 0x%x, btTxRxCntLvl = %d\n", + rtlhal->hal_coex_8723.bt_inq_page_start_time, + bt_tx_rx_cnt_lvl); + if ((rtlhal->hal_coex_8723.bt_inq_page_start_time) || + (BT_TXRX_CNT_LEVEL_3 == bt_tx_rx_cnt_lvl)) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], Set BT inquiry / page scan 0x3a setting\n"); + btdm8723.ps_tdma_on = true; + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0x5; + btdm8723.ps_tdma_byte[2] = 0x5; + btdm8723.ps_tdma_byte[3] = 0x2; + btdm8723.ps_tdma_byte[4] = 0x80; + } + + if (rtl8723ae_dm_bt_is_coexist_state_changed(hw)) + rtl8723ae_dm_bt_set_bt_dm(hw, &btdm8723); +} + +static void rtl8723ae_dm_bt_2_ant_fta2dp(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct btdm_8723 btdm8723; + u8 bt_rssi_state, bt_rssi_state1; + u32 bt_tx_rx_cnt_lvl; + + rtl8723ae_dm_bt_btdm_structure_reload(hw, &btdm8723); + btdm8723.rf_rx_lpf_shrink = true; + btdm8723.low_penalty_rate_adaptive = true; + btdm8723.reject_aggre_pkt = false; + + bt_tx_rx_cnt_lvl = rtl8723ae_dm_bt_bt_tx_rx_counter_level(hw); + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters = %d\n", bt_tx_rx_cnt_lvl); + + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20_40) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, "HT40\n"); + bt_rssi_state = rtl8723ae_dm_bt_check_coex_rssi_state(hw, 2, + 37, 0); + + /* coex table */ + btdm8723.val_0x6c0 = 0x55555555; + btdm8723.val_0x6c8 = 0xffff; + btdm8723.val_0x6cc = 0x3; + + /* sw mechanism */ + btdm8723.agc_table_en = false; + btdm8723.adc_back_off_on = true; + btdm8723.sw_dac_swing_on = false; + + /* fw mechanism */ + btdm8723.ps_tdma_on = true; + if ((bt_rssi_state == BT_RSSI_STATE_HIGH) || + (bt_rssi_state == BT_RSSI_STATE_STAY_HIGH)) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Wifi rssi high\n"); + if (bt_tx_rx_cnt_lvl == BT_TXRX_CNT_LEVEL_2) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters >= 1400\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0x5; + btdm8723.ps_tdma_byte[2] = 0x5; + btdm8723.ps_tdma_byte[3] = 0x81; + btdm8723.ps_tdma_byte[4] = 0x80; + } else if (bt_tx_rx_cnt_lvl == BT_TXRX_CNT_LEVEL_1) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters >= 1200 && < 1400\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0xa; + btdm8723.ps_tdma_byte[2] = 0xa; + btdm8723.ps_tdma_byte[3] = 0x81; + btdm8723.ps_tdma_byte[4] = 0x80; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters < 1200\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0xf; + btdm8723.ps_tdma_byte[2] = 0xf; + btdm8723.ps_tdma_byte[3] = 0x81; + btdm8723.ps_tdma_byte[4] = 0x80; + } + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Wifi rssi low\n"); + if (bt_tx_rx_cnt_lvl == BT_TXRX_CNT_LEVEL_2) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters >= 1400\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0x5; + btdm8723.ps_tdma_byte[2] = 0x5; + btdm8723.ps_tdma_byte[3] = 0x0; + btdm8723.ps_tdma_byte[4] = 0x80; + } else if (bt_tx_rx_cnt_lvl == BT_TXRX_CNT_LEVEL_1) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters >= 1200 && < 1400\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0xa; + btdm8723.ps_tdma_byte[2] = 0xa; + btdm8723.ps_tdma_byte[3] = 0x0; + btdm8723.ps_tdma_byte[4] = 0x80; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters < 1200\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0xf; + btdm8723.ps_tdma_byte[2] = 0xf; + btdm8723.ps_tdma_byte[3] = 0x0; + btdm8723.ps_tdma_byte[4] = 0x80; + } + } + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "HT20 or Legacy\n"); + bt_rssi_state = rtl8723ae_dm_bt_check_coex_rssi_state(hw, 2, + 47, 0); + bt_rssi_state1 = rtl8723ae_dm_bt_check_coex_rssi_state1(hw, 2, + 27, 0); + + /* coex table */ + btdm8723.val_0x6c0 = 0x55555555; + btdm8723.val_0x6c8 = 0xffff; + btdm8723.val_0x6cc = 0x3; + + /* sw mechanism */ + if ((bt_rssi_state == BT_RSSI_STATE_HIGH) || + (bt_rssi_state == BT_RSSI_STATE_STAY_HIGH)) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Wifi rssi high\n"); + btdm8723.agc_table_en = true; + btdm8723.adc_back_off_on = true; + btdm8723.sw_dac_swing_on = false; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Wifi rssi low\n"); + btdm8723.agc_table_en = false; + btdm8723.adc_back_off_on = false; + btdm8723.sw_dac_swing_on = false; + } + + /* fw mechanism */ + btdm8723.ps_tdma_on = true; + if ((bt_rssi_state1 == BT_RSSI_STATE_HIGH) || + (bt_rssi_state1 == BT_RSSI_STATE_STAY_HIGH)) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Wifi rssi-1 high\n"); + /* only rssi high we need to do this, + * when rssi low, the value will modified by fw + */ + rtl_write_byte(rtlpriv, 0x883, 0x40); + if (bt_tx_rx_cnt_lvl == BT_TXRX_CNT_LEVEL_2) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters >= 1400\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0x5; + btdm8723.ps_tdma_byte[2] = 0x5; + btdm8723.ps_tdma_byte[3] = 0x81; + btdm8723.ps_tdma_byte[4] = 0x80; + } else if (bt_tx_rx_cnt_lvl == BT_TXRX_CNT_LEVEL_1) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters >= 1200 && < 1400\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0xa; + btdm8723.ps_tdma_byte[2] = 0xa; + btdm8723.ps_tdma_byte[3] = 0x81; + btdm8723.ps_tdma_byte[4] = 0x80; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters < 1200\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0xf; + btdm8723.ps_tdma_byte[2] = 0xf; + btdm8723.ps_tdma_byte[3] = 0x81; + btdm8723.ps_tdma_byte[4] = 0x80; + } + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Wifi rssi-1 low\n"); + if (bt_tx_rx_cnt_lvl == BT_TXRX_CNT_LEVEL_2) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters >= 1400\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0x5; + btdm8723.ps_tdma_byte[2] = 0x5; + btdm8723.ps_tdma_byte[3] = 0x0; + btdm8723.ps_tdma_byte[4] = 0x80; + } else if (bt_tx_rx_cnt_lvl == BT_TXRX_CNT_LEVEL_1) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters >= 1200 && < 1400\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0xa; + btdm8723.ps_tdma_byte[2] = 0xa; + btdm8723.ps_tdma_byte[3] = 0x0; + btdm8723.ps_tdma_byte[4] = 0x80; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT TxRx Counters < 1200\n"); + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0xf; + btdm8723.ps_tdma_byte[2] = 0xf; + btdm8723.ps_tdma_byte[3] = 0x0; + btdm8723.ps_tdma_byte[4] = 0x80; + } + } + } + + if (rtl8723ae_dm_bt_need_to_dec_bt_pwr(hw)) + btdm8723.dec_bt_pwr = true; + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT btInqPageStartTime = 0x%x, btTxRxCntLvl = %d\n", + rtlhal->hal_coex_8723.bt_inq_page_start_time, + bt_tx_rx_cnt_lvl); + + if ((rtlhal->hal_coex_8723.bt_inq_page_start_time) || + (BT_TXRX_CNT_LEVEL_3 == bt_tx_rx_cnt_lvl)) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], Set BT inquiry / page scan 0x3a setting\n"); + btdm8723.ps_tdma_on = true; + btdm8723.ps_tdma_byte[0] = 0xa3; + btdm8723.ps_tdma_byte[1] = 0x5; + btdm8723.ps_tdma_byte[2] = 0x5; + btdm8723.ps_tdma_byte[3] = 0x83; + btdm8723.ps_tdma_byte[4] = 0x80; + } + + if (rtl8723ae_dm_bt_is_coexist_state_changed(hw)) + rtl8723ae_dm_bt_set_bt_dm(hw, &btdm8723); +} + +static void rtl8723ae_dm_bt_inq_page_monitor(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + u32 cur_time = jiffies; + + if (rtlhal->hal_coex_8723.c2h_bt_inquiry_page) { + /* bt inquiry or page is started. */ + if (rtlhal->hal_coex_8723.bt_inq_page_start_time == 0) { + rtlpcipriv->bt_coexist.cstate |= + BT_COEX_STATE_BT_INQ_PAGE; + rtlhal->hal_coex_8723.bt_inq_page_start_time = cur_time; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT Inquiry/page is started at time : 0x%x\n", + rtlhal->hal_coex_8723.bt_inq_page_start_time); + } + } + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT Inquiry/page started time : 0x%x, cur_time : 0x%x\n", + rtlhal->hal_coex_8723.bt_inq_page_start_time, cur_time); + + if (rtlhal->hal_coex_8723.bt_inq_page_start_time) { + if ((((long)cur_time - + (long)rtlhal->hal_coex_8723.bt_inq_page_start_time) / HZ) >= + 10) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BT Inquiry/page >= 10sec!!!"); + rtlhal->hal_coex_8723.bt_inq_page_start_time = 0; + rtlpcipriv->bt_coexist.cstate &= + ~BT_COEX_STATE_BT_INQ_PAGE; + } + } +} + +static void rtl8723ae_dm_bt_reset_action_profile_state(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + + rtlpcipriv->bt_coexist.cstate &= + ~(BT_COEX_STATE_PROFILE_HID | BT_COEX_STATE_PROFILE_A2DP | + BT_COEX_STATE_PROFILE_PAN | BT_COEX_STATE_PROFILE_SCO); + + rtlpcipriv->bt_coexist.cstate &= + ~(BT_COEX_STATE_BTINFO_COMMON | + BT_COEX_STATE_BTINFO_B_HID_SCOESCO | + BT_COEX_STATE_BTINFO_B_FTP_A2DP); +} + +static void _rtl8723ae_dm_bt_coexist_2_ant(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + u8 bt_info_original; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex] Get bt info by fw!!\n"); + + _rtl8723_dm_bt_check_wifi_state(hw); + + if (rtlhal->hal_coex_8723.c2h_bt_info_req_sent) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex] c2h for btInfo not rcvd yet!!\n"); + } + + bt_info_original = rtlhal->hal_coex_8723.c2h_bt_info_original; + + /* when bt inquiry or page scan, we have to set h2c 0x25 + * ignore wlanact for continuous 4x2secs + */ + rtl8723ae_dm_bt_inq_page_monitor(hw); + rtl8723ae_dm_bt_reset_action_profile_state(hw); + + if (rtl8723ae_dm_bt_is_2_ant_common_action(hw)) { + rtlpcipriv->bt_coexist.bt_profile_case = BT_COEX_MECH_COMMON; + rtlpcipriv->bt_coexist.bt_profile_action = BT_COEX_MECH_COMMON; + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Action 2-Ant common.\n"); + } else { + if ((bt_info_original & BTINFO_B_HID) || + (bt_info_original & BTINFO_B_SCO_BUSY) || + (bt_info_original & BTINFO_B_SCO_ESCO)) { + rtlpcipriv->bt_coexist.cstate |= + BT_COEX_STATE_BTINFO_B_HID_SCOESCO; + rtlpcipriv->bt_coexist.bt_profile_case = + BT_COEX_MECH_HID_SCO_ESCO; + rtlpcipriv->bt_coexist.bt_profile_action = + BT_COEX_MECH_HID_SCO_ESCO; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BTInfo: bHid|bSCOBusy|bSCOeSCO\n"); + rtl8723ae_dm_bt_2_ant_hid_sco_esco(hw); + } else if ((bt_info_original & BTINFO_B_FTP) || + (bt_info_original & BTINFO_B_A2DP)) { + rtlpcipriv->bt_coexist.cstate |= + BT_COEX_STATE_BTINFO_B_FTP_A2DP; + rtlpcipriv->bt_coexist.bt_profile_case = + BT_COEX_MECH_FTP_A2DP; + rtlpcipriv->bt_coexist.bt_profile_action = + BT_COEX_MECH_FTP_A2DP; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "BTInfo: bFTP|bA2DP\n"); + rtl8723ae_dm_bt_2_ant_fta2dp(hw); + } else { + rtlpcipriv->bt_coexist.cstate |= + BT_COEX_STATE_BTINFO_B_HID_SCOESCO; + rtlpcipriv->bt_coexist.bt_profile_case = + BT_COEX_MECH_NONE; + rtlpcipriv->bt_coexist.bt_profile_action = + BT_COEX_MECH_NONE; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], BTInfo: undefined case!!!!\n"); + rtl8723ae_dm_bt_2_ant_hid_sco_esco(hw); + } + } +} + +static void _rtl8723ae_dm_bt_coexist_1_ant(struct ieee80211_hw *hw) +{ +} + +void rtl8723ae_dm_bt_hw_coex_all_off_8723a(struct ieee80211_hw *hw) +{ + rtl8723ae_dm_bt_set_coex_table(hw, 0x5a5aaaaa, 0xcc, 0x3); + rtl8723ae_dm_bt_set_hw_pta_mode(hw, true); +} + +void rtl8723ae_dm_bt_fw_coex_all_off_8723a(struct ieee80211_hw *hw) +{ + rtl8723ae_dm_bt_set_fw_ignore_wlan_act(hw, false); + rtl8723ae_dm_bt_set_fw_3a(hw, 0x0, 0x0, 0x0, 0x8, 0x0); + rtl8723ae_dm_bt_set_fw_2_ant_hid(hw, false, false); + rtl8723ae_dm_bt_set_fw_tra_tdma_ctrl(hw, false, + TDMA_2ANT, TDMA_NAV_OFF); + rtl8723ae_dm_bt_set_fw_tdma_ctrl(hw, false, TDMA_2ANT, + TDMA_NAV_OFF, TDMA_DAC_SWING_OFF); + rtl8723ae_dm_bt_set_fw_dac_swing_level(hw, 0); + rtl8723ae_dm_bt_set_fw_bt_hid_info(hw, false); + rtl8723ae_dm_bt_set_fw_bt_retry_index(hw, 2); + rtl8723ae_dm_bt_set_fw_wlan_act(hw, 0x10, 0x10); + rtl8723ae_dm_bt_set_fw_dec_bt_pwr(hw, false); +} + +void rtl8723ae_dm_bt_sw_coex_all_off_8723a(struct ieee80211_hw *hw) +{ + rtl8723ae_dm_bt_agc_table(hw, BT_AGCTABLE_OFF); + rtl8723ae_dm_bt_bback_off_level(hw, BT_BB_BACKOFF_OFF); + rtl8723ae_dm_bt_reject_ap_aggregated_packet(hw, false); + + rtl8723ae_bt_set_penalty_tx_rate_adap(hw, BT_TX_RATE_ADAPTIVE_NORMAL); + rtl8723ae_dm_bt_set_sw_rf_rx_lpf_corner(hw, BT_RF_RX_LPF_CORNER_RESUME); + rtl8723ae_dm_bt_set_sw_full_time_dac_swing(hw, false, 0xc0); +} + +static void rtl8723ae_dm_bt_query_bt_information(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + u8 h2c_parameter[1] = {0}; + + rtlhal->hal_coex_8723.c2h_bt_info_req_sent = true; + + h2c_parameter[0] |= BIT(0); + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "Query Bt information, write 0x38 = 0x%x\n", + h2c_parameter[0]); + + rtl8723ae_fill_h2c_cmd(hw, 0x38, 1, h2c_parameter); +} + +static void rtl8723ae_dm_bt_bt_hw_counters_monitor(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + u32 reg_htx_rx, reg_ltx_rx, u32_tmp; + u32 reg_htx, reg_hrx, reg_ltx, reg_lrx; + + reg_htx_rx = REG_HIGH_PRIORITY_TXRX; + reg_ltx_rx = REG_LOW_PRIORITY_TXRX; + + u32_tmp = rtl_read_dword(rtlpriv, reg_htx_rx); + reg_htx = u32_tmp & MASKLWORD; + reg_hrx = (u32_tmp & MASKHWORD)>>16; + + u32_tmp = rtl_read_dword(rtlpriv, reg_ltx_rx); + reg_ltx = u32_tmp & MASKLWORD; + reg_lrx = (u32_tmp & MASKHWORD)>>16; + + if (rtlpcipriv->bt_coexist.lps_counter > 1) { + reg_htx %= rtlpcipriv->bt_coexist.lps_counter; + reg_hrx %= rtlpcipriv->bt_coexist.lps_counter; + reg_ltx %= rtlpcipriv->bt_coexist.lps_counter; + reg_lrx %= rtlpcipriv->bt_coexist.lps_counter; + } + + rtlhal->hal_coex_8723.high_priority_tx = reg_htx; + rtlhal->hal_coex_8723.high_priority_rx = reg_hrx; + rtlhal->hal_coex_8723.low_priority_tx = reg_ltx; + rtlhal->hal_coex_8723.low_priority_rx = reg_lrx; + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "High Priority Tx/Rx (reg 0x%x)=%x(%d)/%x(%d)\n", + reg_htx_rx, reg_htx, reg_htx, reg_hrx, reg_hrx); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "Low Priority Tx/Rx (reg 0x%x)=%x(%d)/%x(%d)\n", + reg_ltx_rx, reg_ltx, reg_ltx, reg_lrx, reg_lrx); + rtlpcipriv->bt_coexist.lps_counter = 0; +} + +static void rtl8723ae_dm_bt_bt_enable_disable_check(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + bool bt_alife = true; + + if (rtlhal->hal_coex_8723.high_priority_tx == 0 && + rtlhal->hal_coex_8723.high_priority_rx == 0 && + rtlhal->hal_coex_8723.low_priority_tx == 0 && + rtlhal->hal_coex_8723.low_priority_rx == 0) + bt_alife = false; + if (rtlhal->hal_coex_8723.high_priority_tx == 0xeaea && + rtlhal->hal_coex_8723.high_priority_rx == 0xeaea && + rtlhal->hal_coex_8723.low_priority_tx == 0xeaea && + rtlhal->hal_coex_8723.low_priority_rx == 0xeaea) + bt_alife = false; + if (rtlhal->hal_coex_8723.high_priority_tx == 0xffff && + rtlhal->hal_coex_8723.high_priority_rx == 0xffff && + rtlhal->hal_coex_8723.low_priority_tx == 0xffff && + rtlhal->hal_coex_8723.low_priority_rx == 0xffff) + bt_alife = false; + if (bt_alife) { + rtlpcipriv->bt_coexist.bt_active_zero_cnt = 0; + rtlpcipriv->bt_coexist.cur_bt_disabled = false; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "8723A BT is enabled !!\n"); + } else { + rtlpcipriv->bt_coexist.bt_active_zero_cnt++; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "8723A bt all counters = 0, %d times!!\n", + rtlpcipriv->bt_coexist.bt_active_zero_cnt); + if (rtlpcipriv->bt_coexist.bt_active_zero_cnt >= 2) { + rtlpcipriv->bt_coexist.cur_bt_disabled = true; + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "8723A BT is disabled !!\n"); + } + } + if (rtlpcipriv->bt_coexist.pre_bt_disabled != + rtlpcipriv->bt_coexist.cur_bt_disabled) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "8723A BT is from %s to %s!!\n", + (rtlpcipriv->bt_coexist.pre_bt_disabled ? + "disabled" : "enabled"), + (rtlpcipriv->bt_coexist.cur_bt_disabled ? + "disabled" : "enabled")); + rtlpcipriv->bt_coexist.pre_bt_disabled + = rtlpcipriv->bt_coexist.cur_bt_disabled; + } +} + + +void rtl8723ae_dm_bt_coexist_8723(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + + rtl8723ae_dm_bt_query_bt_information(hw); + rtl8723ae_dm_bt_bt_hw_counters_monitor(hw); + rtl8723ae_dm_bt_bt_enable_disable_check(hw); + + if (rtlpcipriv->bt_coexist.bt_ant_num == ANT_X2) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], 2 Ant mechanism\n"); + _rtl8723ae_dm_bt_coexist_2_ant(hw); + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "[BTCoex], 1 Ant mechanism\n"); + _rtl8723ae_dm_bt_coexist_1_ant(hw); + } + + if (!rtl8723ae_dm_bt_is_same_coexist_state(hw)) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTCoex], Coexist State[bitMap] change from 0x%x%8x to 0x%x%8x\n", + rtlpcipriv->bt_coexist.previous_state_h, + rtlpcipriv->bt_coexist.previous_state, + rtlpcipriv->bt_coexist.cstate_h, + rtlpcipriv->bt_coexist.cstate); + rtlpcipriv->bt_coexist.previous_state + = rtlpcipriv->bt_coexist.cstate; + rtlpcipriv->bt_coexist.previous_state_h + = rtlpcipriv->bt_coexist.cstate_h; + } +} + +static void rtl8723ae_dm_bt_parse_bt_info(struct ieee80211_hw *hw, + u8 *tmbuf, u8 len) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtlpriv); + struct rtl_pci_priv *rtlpcipriv = rtl_pcipriv(hw); + u8 bt_info; + u8 i; + + rtlhal->hal_coex_8723.c2h_bt_info_req_sent = false; + rtlhal->hal_coex_8723.bt_retry_cnt = 0; + for (i = 0; i < len; i++) { + if (i == 0) + rtlhal->hal_coex_8723.c2h_bt_info_original = tmbuf[i]; + else if (i == 1) + rtlhal->hal_coex_8723.bt_retry_cnt = tmbuf[i]; + if (i == len-1) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "0x%2x]", tmbuf[i]); + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "0x%2x, ", tmbuf[i]); + } + } + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "BT info bt_info (Data)= 0x%x\n", + rtlhal->hal_coex_8723.c2h_bt_info_original); + bt_info = rtlhal->hal_coex_8723.c2h_bt_info_original; + + if (bt_info & BIT(2)) + rtlhal->hal_coex_8723.c2h_bt_inquiry_page = true; + else + rtlhal->hal_coex_8723.c2h_bt_inquiry_page = false; + + if (bt_info & BTINFO_B_CONNECTION) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTC2H], BTInfo: bConnect=true\n"); + rtlpcipriv->bt_coexist.bt_busy = true; + rtlpcipriv->bt_coexist.cstate &= ~BT_COEX_STATE_BT_IDLE; + } else { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_DMESG, + "[BTC2H], BTInfo: bConnect=false\n"); + rtlpcipriv->bt_coexist.bt_busy = false; + rtlpcipriv->bt_coexist.cstate |= BT_COEX_STATE_BT_IDLE; + } +} +void rtl_8723e_c2h_command_handle(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct c2h_evt_hdr c2h_event; + u8 *ptmbuf; + u8 index; + u8 u1tmp; + + memset(&c2h_event, 0, sizeof(c2h_event)); + u1tmp = rtl_read_byte(rtlpriv, REG_C2HEVT_MSG_NORMAL); + RT_TRACE(rtlpriv, COMP_FW, DBG_DMESG, + "&&&&&&: REG_C2HEVT_MSG_NORMAL is 0x%x\n", u1tmp); + c2h_event.cmd_id = u1tmp & 0xF; + c2h_event.cmd_len = (u1tmp & 0xF0) >> 4; + c2h_event.cmd_seq = rtl_read_byte(rtlpriv, REG_C2HEVT_MSG_NORMAL + 1); + RT_TRACE(rtlpriv, COMP_FW, DBG_DMESG, + "cmd_id: %d, cmd_len: %d, cmd_seq: %d\n", + c2h_event.cmd_id , c2h_event.cmd_len, c2h_event.cmd_seq); + u1tmp = rtl_read_byte(rtlpriv, 0x01AF); + if (u1tmp == C2H_EVT_HOST_CLOSE) { + return; + } else if (u1tmp != C2H_EVT_FW_CLOSE) { + rtl_write_byte(rtlpriv, 0x1AF, 0x00); + return; + } + ptmbuf = kmalloc(c2h_event.cmd_len, GFP_KERNEL); + if (ptmbuf == NULL) { + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, + "malloc cmd buf failed\n"); + return; + } + + /* Read the content */ + for (index = 0; index < c2h_event.cmd_len; index++) + ptmbuf[index] = rtl_read_byte(rtlpriv, REG_C2HEVT_MSG_NORMAL + + 2 + index); + + switch (c2h_event.cmd_id) { + case C2H_BT_RSSI: + break; + + case C2H_BT_OP_MODE: + break; + + case BT_INFO: + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, + "BT info Byte[0] (ID) is 0x%x\n", c2h_event.cmd_id); + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, + "BT info Byte[1] (Seq) is 0x%x\n", c2h_event.cmd_seq); + RT_TRACE(rtlpriv, COMP_FW, DBG_TRACE, + "BT info Byte[2] (Data)= 0x%x\n", ptmbuf[0]); + + rtl8723ae_dm_bt_parse_bt_info(hw, ptmbuf, c2h_event.cmd_len); + break; + default: + break; + } + kfree(ptmbuf); + + rtl_write_byte(rtlpriv, 0x01AF, C2H_EVT_HOST_CLOSE); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/hal_btc.h b/drivers/net/wireless/rtlwifi/rtl8723ae/hal_btc.h new file mode 100644 index 0000000..4325ecd --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/hal_btc.h @@ -0,0 +1,151 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * Larry Finger + * + **************************************************************************** + */ + +#ifndef __RTL8723E_HAL_BTC_H__ +#define __RTL8723E_HAL_BTC_H__ + +#include "../wifi.h" +#include "btc.h" +#include "hal_bt_coexist.h" + +#define BT_TXRX_CNT_THRES_1 1200 +#define BT_TXRX_CNT_THRES_2 1400 +#define BT_TXRX_CNT_THRES_3 3000 +#define BT_TXRX_CNT_LEVEL_0 0 /* < 1200 */ +#define BT_TXRX_CNT_LEVEL_1 1 /* >= 1200 && < 1400 */ +#define BT_TXRX_CNT_LEVEL_2 2 /* >= 1400 */ +#define BT_TXRX_CNT_LEVEL_3 3 + +/* TDMA mode definition */ +#define TDMA_2ANT 0 +#define TDMA_1ANT 1 +#define TDMA_NAV_OFF 0 +#define TDMA_NAV_ON 1 +#define TDMA_DAC_SWING_OFF 0 +#define TDMA_DAC_SWING_ON 1 + +/* PTA mode related definition */ +#define BT_PTA_MODE_OFF 0 +#define BT_PTA_MODE_ON 1 + +/* Penalty Tx Rate Adaptive */ +#define BT_TX_RATE_ADAPTIVE_NORMAL 0 +#define BT_TX_RATE_ADAPTIVE_LOW_PENALTY 1 + +/* RF Corner */ +#define BT_RF_RX_LPF_CORNER_RESUME 0 +#define BT_RF_RX_LPF_CORNER_SHRINK 1 + +#define C2H_EVT_HOST_CLOSE 0x00 +#define C2H_EVT_FW_CLOSE 0xFF + +enum bt_traffic_mode { + BT_MOTOR_EXT_BE = 0x00, + BT_MOTOR_EXT_GUL = 0x01, + BT_MOTOR_EXT_GUB = 0x02, + BT_MOTOR_EXT_GULB = 0x03 +}; + +enum bt_traffic_mode_profile { + BT_PROFILE_NONE, + BT_PROFILE_A2DP, + BT_PROFILE_PAN, + BT_PROFILE_HID, + BT_PROFILE_SCO +}; + +enum hci_ext_bt_operation { + HCI_BT_OP_NONE = 0x0, + HCI_BT_OP_INQUIRE_START = 0x1, + HCI_BT_OP_INQUIRE_FINISH = 0x2, + HCI_BT_OP_PAGING_START = 0x3, + HCI_BT_OP_PAGING_SUCCESS = 0x4, + HCI_BT_OP_PAGING_UNSUCCESS = 0x5, + HCI_BT_OP_PAIRING_START = 0x6, + HCI_BT_OP_PAIRING_FINISH = 0x7, + HCI_BT_OP_BT_DEV_ENABLE = 0x8, + HCI_BT_OP_BT_DEV_DISABLE = 0x9, + HCI_BT_OP_MAX, +}; + +enum bt_spec { + BT_SPEC_1_0_b = 0x00, + BT_SPEC_1_1 = 0x01, + BT_SPEC_1_2 = 0x02, + BT_SPEC_2_0_EDR = 0x03, + BT_SPEC_2_1_EDR = 0x04, + BT_SPEC_3_0_HS = 0x05, + BT_SPEC_4_0 = 0x06 +}; + +struct c2h_evt_hdr { + u8 cmd_id; + u8 cmd_len; + u8 cmd_seq; +}; + +enum bt_state { + BT_INFO_STATE_DISABLED = 0, + BT_INFO_STATE_NO_CONNECTION = 1, + BT_INFO_STATE_CONNECT_IDLE = 2, + BT_INFO_STATE_INQ_OR_PAG = 3, + BT_INFO_STATE_ACL_ONLY_BUSY = 4, + BT_INFO_STATE_SCO_ONLY_BUSY = 5, + BT_INFO_STATE_ACL_SCO_BUSY = 6, + BT_INFO_STATE_HID_BUSY = 7, + BT_INFO_STATE_HID_SCO_BUSY = 8, + BT_INFO_STATE_MAX = 7 +}; + +enum rtl8723ae_c2h_evt { + C2H_DBG = 0, + C2H_TSF = 1, + C2H_AP_RPT_RSP = 2, + C2H_CCX_TX_RPT = 3, /* The FW notify the report of the specific */ + /* tx packet. */ + C2H_BT_RSSI = 4, + C2H_BT_OP_MODE = 5, + C2H_HW_INFO_EXCH = 10, + C2H_C2H_H2C_TEST = 11, + BT_INFO = 12, + MAX_C2HEVENT +}; + +void rtl8723ae_dm_bt_fw_coex_all_off_8723a(struct ieee80211_hw *hw); +void rtl8723ae_dm_bt_sw_coex_all_off_8723a(struct ieee80211_hw *hw); +void rtl8723ae_dm_bt_hw_coex_all_off_8723a(struct ieee80211_hw *hw); +void rtl8723ae_dm_bt_coexist_8723(struct ieee80211_hw *hw); +void rtl8723ae_dm_bt_set_bt_dm(struct ieee80211_hw *hw, + struct btdm_8723 *p_btdm); +void rtl_8723e_c2h_command_handle(struct ieee80211_hw *hw); +void rtl_8723e_bt_wifi_media_status_notify(struct ieee80211_hw *hw, + bool mstatus); +void rtl8723ae_bt_coex_off_before_lps(struct ieee80211_hw *hw); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/hw.c b/drivers/net/wireless/rtlwifi/rtl8723ae/hw.c new file mode 100644 index 0000000..c333dfd --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/hw.c @@ -0,0 +1,2399 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../efuse.h" +#include "../base.h" +#include "../regd.h" +#include "../cam.h" +#include "../ps.h" +#include "../pci.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "dm.h" +#include "fw.h" +#include "led.h" +#include "hw.h" +#include "pwrseqcmd.h" +#include "pwrseq.h" +#include "btc.h" + +static void _rtl8723ae_set_bcn_ctrl_reg(struct ieee80211_hw *hw, + u8 set_bits, u8 clear_bits) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpci->reg_bcn_ctrl_val |= set_bits; + rtlpci->reg_bcn_ctrl_val &= ~clear_bits; + + rtl_write_byte(rtlpriv, REG_BCN_CTRL, (u8) rtlpci->reg_bcn_ctrl_val); +} + +static void _rtl8723ae_stop_tx_beacon(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tmp1byte; + + tmp1byte = rtl_read_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2); + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, tmp1byte & (~BIT(6))); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 1, 0x64); + tmp1byte = rtl_read_byte(rtlpriv, REG_TBTT_PROHIBIT + 2); + tmp1byte &= ~(BIT(0)); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 2, tmp1byte); +} + +static void _rtl8723ae_resume_tx_beacon(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tmp1byte; + + tmp1byte = rtl_read_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2); + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, tmp1byte | BIT(6)); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 1, 0xff); + tmp1byte = rtl_read_byte(rtlpriv, REG_TBTT_PROHIBIT + 2); + tmp1byte |= BIT(1); + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 2, tmp1byte); +} + +static void _rtl8723ae_enable_bcn_sufunc(struct ieee80211_hw *hw) +{ + _rtl8723ae_set_bcn_ctrl_reg(hw, 0, BIT(1)); +} + +static void _rtl8723ae_disable_bcn_sufunc(struct ieee80211_hw *hw) +{ + _rtl8723ae_set_bcn_ctrl_reg(hw, BIT(1), 0); +} + +void rtl8723ae_get_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + switch (variable) { + case HW_VAR_RCR: + *((u32 *) (val)) = rtlpci->receive_config; + break; + case HW_VAR_RF_STATE: + *((enum rf_pwrstate *)(val)) = ppsc->rfpwr_state; + break; + case HW_VAR_FWLPS_RF_ON:{ + enum rf_pwrstate rfState; + u32 val_rcr; + + rtlpriv->cfg->ops->get_hw_reg(hw, + HW_VAR_RF_STATE, + (u8 *) (&rfState)); + if (rfState == ERFOFF) { + *((bool *) (val)) = true; + } else { + val_rcr = rtl_read_dword(rtlpriv, REG_RCR); + val_rcr &= 0x00070000; + if (val_rcr) + *((bool *) (val)) = false; + else + *((bool *) (val)) = true; + } + break; } + case HW_VAR_FW_PSMODE_STATUS: + *((bool *) (val)) = ppsc->fw_current_inpsmode; + break; + case HW_VAR_CORRECT_TSF:{ + u64 tsf; + u32 *ptsf_low = (u32 *)&tsf; + u32 *ptsf_high = ((u32 *)&tsf) + 1; + + *ptsf_high = rtl_read_dword(rtlpriv, (REG_TSFTR + 4)); + *ptsf_low = rtl_read_dword(rtlpriv, REG_TSFTR); + + *((u64 *) (val)) = tsf; + + break; } + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not process\n"); + break; + } +} + +void rtl8723ae_set_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + u8 idx; + + switch (variable) { + case HW_VAR_ETHER_ADDR: + for (idx = 0; idx < ETH_ALEN; idx++) { + rtl_write_byte(rtlpriv, (REG_MACID + idx), + val[idx]); + } + break; + case HW_VAR_BASIC_RATE:{ + u16 rate_cfg = ((u16 *) val)[0]; + u8 rate_index = 0; + rate_cfg = rate_cfg & 0x15f; + rate_cfg |= 0x01; + rtl_write_byte(rtlpriv, REG_RRSR, rate_cfg & 0xff); + rtl_write_byte(rtlpriv, REG_RRSR + 1, + (rate_cfg >> 8) & 0xff); + while (rate_cfg > 0x1) { + rate_cfg = (rate_cfg >> 1); + rate_index++; + } + rtl_write_byte(rtlpriv, REG_INIRTS_RATE_SEL, + rate_index); + break; } + case HW_VAR_BSSID: + for (idx = 0; idx < ETH_ALEN; idx++) { + rtl_write_byte(rtlpriv, (REG_BSSID + idx), + val[idx]); + } + break; + case HW_VAR_SIFS: + rtl_write_byte(rtlpriv, REG_SIFS_CTX + 1, val[0]); + rtl_write_byte(rtlpriv, REG_SIFS_TRX + 1, val[1]); + + rtl_write_byte(rtlpriv, REG_SPEC_SIFS + 1, val[0]); + rtl_write_byte(rtlpriv, REG_MAC_SPEC_SIFS + 1, val[0]); + + if (!mac->ht_enable) + rtl_write_word(rtlpriv, REG_RESP_SIFS_OFDM, + 0x0e0e); + else + rtl_write_word(rtlpriv, REG_RESP_SIFS_OFDM, + *((u16 *) val)); + break; + case HW_VAR_SLOT_TIME:{ + u8 e_aci; + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "HW_VAR_SLOT_TIME %x\n", val[0]); + + rtl_write_byte(rtlpriv, REG_SLOT, val[0]); + + for (e_aci = 0; e_aci < AC_MAX; e_aci++) { + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_AC_PARAM, + (u8 *) (&e_aci)); + } + break; } + case HW_VAR_ACK_PREAMBLE:{ + u8 reg_tmp; + u8 short_preamble = (bool) (*(u8 *) val); + reg_tmp = (mac->cur_40_prime_sc) << 5; + if (short_preamble) + reg_tmp |= 0x80; + + rtl_write_byte(rtlpriv, REG_RRSR + 2, reg_tmp); + break; } + case HW_VAR_AMPDU_MIN_SPACE:{ + u8 min_spacing_to_set; + u8 sec_min_space; + + min_spacing_to_set = *((u8 *) val); + if (min_spacing_to_set <= 7) { + sec_min_space = 0; + + if (min_spacing_to_set < sec_min_space) + min_spacing_to_set = sec_min_space; + + mac->min_space_cfg = ((mac->min_space_cfg & + 0xf8) | + min_spacing_to_set); + + *val = min_spacing_to_set; + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_AMPDU_MIN_SPACE: %#x\n", + mac->min_space_cfg); + + rtl_write_byte(rtlpriv, REG_AMPDU_MIN_SPACE, + mac->min_space_cfg); + } + break; } + case HW_VAR_SHORTGI_DENSITY:{ + u8 density_to_set; + + density_to_set = *((u8 *) val); + mac->min_space_cfg |= (density_to_set << 3); + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_SHORTGI_DENSITY: %#x\n", + mac->min_space_cfg); + + rtl_write_byte(rtlpriv, REG_AMPDU_MIN_SPACE, + mac->min_space_cfg); + + break; } + case HW_VAR_AMPDU_FACTOR:{ + u8 regtoset_normal[4] = {0x41, 0xa8, 0x72, 0xb9}; + u8 regtoset_bt[4] = {0x31, 0x74, 0x42, 0x97}; + u8 factor_toset; + u8 *p_regtoset = NULL; + u8 index; + + if ((pcipriv->bt_coexist.bt_coexistence) && + (pcipriv->bt_coexist.bt_coexist_type == BT_CSR_BC4)) + p_regtoset = regtoset_bt; + else + p_regtoset = regtoset_normal; + + factor_toset = *((u8 *) val); + if (factor_toset <= 3) { + factor_toset = (1 << (factor_toset + 2)); + if (factor_toset > 0xf) + factor_toset = 0xf; + + for (index = 0; index < 4; index++) { + if ((p_regtoset[index] & 0xf0) > + (factor_toset << 4)) + p_regtoset[index] = + (p_regtoset[index] & 0x0f) | + (factor_toset << 4); + + if ((p_regtoset[index] & 0x0f) > + factor_toset) + p_regtoset[index] = + (p_regtoset[index] & 0xf0) | + (factor_toset); + + rtl_write_byte(rtlpriv, + (REG_AGGLEN_LMT + index), + p_regtoset[index]); + + } + + RT_TRACE(rtlpriv, COMP_MLME, DBG_LOUD, + "Set HW_VAR_AMPDU_FACTOR: %#x\n", + factor_toset); + } + break; } + case HW_VAR_AC_PARAM:{ + u8 e_aci = *((u8 *) val); + rtl8723ae_dm_init_edca_turbo(hw); + + if (rtlpci->acm_method != eAcmWay2_SW) + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_ACM_CTRL, + (u8 *) (&e_aci)); + break; } + case HW_VAR_ACM_CTRL:{ + u8 e_aci = *((u8 *) val); + union aci_aifsn *p_aci_aifsn = + (union aci_aifsn *)(&(mac->ac[0].aifs)); + u8 acm = p_aci_aifsn->f.acm; + u8 acm_ctrl = rtl_read_byte(rtlpriv, REG_ACMHWCTRL); + + acm_ctrl |= ((rtlpci->acm_method == 2) ? 0x0 : 0x1); + + if (acm) { + switch (e_aci) { + case AC0_BE: + acm_ctrl |= AcmHw_BeqEn; + break; + case AC2_VI: + acm_ctrl |= AcmHw_ViqEn; + break; + case AC3_VO: + acm_ctrl |= AcmHw_VoqEn; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "HW_VAR_ACM_CTRL acm set failed: eACI is %d\n", + acm); + break; + } + } else { + switch (e_aci) { + case AC0_BE: + acm_ctrl &= (~AcmHw_BeqEn); + break; + case AC2_VI: + acm_ctrl &= (~AcmHw_ViqEn); + break; + case AC3_VO: + acm_ctrl &= (~AcmHw_BeqEn); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + } + + RT_TRACE(rtlpriv, COMP_QOS, DBG_TRACE, + "SetHwReg8190pci(): [HW_VAR_ACM_CTRL] Write 0x%X\n", + acm_ctrl); + rtl_write_byte(rtlpriv, REG_ACMHWCTRL, acm_ctrl); + break; } + case HW_VAR_RCR: + rtl_write_dword(rtlpriv, REG_RCR, ((u32 *) (val))[0]); + rtlpci->receive_config = ((u32 *) (val))[0]; + break; + case HW_VAR_RETRY_LIMIT:{ + u8 retry_limit = ((u8 *) (val))[0]; + + rtl_write_word(rtlpriv, REG_RL, + retry_limit << RETRY_LIMIT_SHORT_SHIFT | + retry_limit << RETRY_LIMIT_LONG_SHIFT); + break; } + case HW_VAR_DUAL_TSF_RST: + rtl_write_byte(rtlpriv, REG_DUAL_TSF_RST, (BIT(0) | BIT(1))); + break; + case HW_VAR_EFUSE_BYTES: + rtlefuse->efuse_usedbytes = *((u16 *) val); + break; + case HW_VAR_EFUSE_USAGE: + rtlefuse->efuse_usedpercentage = *((u8 *) val); + break; + case HW_VAR_IO_CMD: + rtl8723ae_phy_set_io_cmd(hw, (*(enum io_type *)val)); + break; + case HW_VAR_WPA_CONFIG: + rtl_write_byte(rtlpriv, REG_SECCFG, *((u8 *) val)); + break; + case HW_VAR_SET_RPWM:{ + u8 rpwm_val; + + rpwm_val = rtl_read_byte(rtlpriv, REG_PCIE_HRPWM); + udelay(1); + + if (rpwm_val & BIT(7)) { + rtl_write_byte(rtlpriv, REG_PCIE_HRPWM, + (*(u8 *) val)); + } else { + rtl_write_byte(rtlpriv, REG_PCIE_HRPWM, + ((*(u8 *) val) | BIT(7))); + } + + break; } + case HW_VAR_H2C_FW_PWRMODE:{ + u8 psmode = (*(u8 *) val); + + if (psmode != FW_PS_ACTIVE_MODE) + rtl8723ae_dm_rf_saving(hw, true); + + rtl8723ae_set_fw_pwrmode_cmd(hw, (*(u8 *) val)); + break; } + case HW_VAR_FW_PSMODE_STATUS: + ppsc->fw_current_inpsmode = *((bool *) val); + break; + case HW_VAR_H2C_FW_JOINBSSRPT:{ + u8 mstatus = (*(u8 *) val); + u8 tmp_regcr, tmp_reg422; + bool recover = false; + + if (mstatus == RT_MEDIA_CONNECT) { + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_AID, NULL); + + tmp_regcr = rtl_read_byte(rtlpriv, REG_CR + 1); + rtl_write_byte(rtlpriv, REG_CR + 1, + (tmp_regcr | BIT(0))); + + _rtl8723ae_set_bcn_ctrl_reg(hw, 0, BIT(3)); + _rtl8723ae_set_bcn_ctrl_reg(hw, BIT(4), 0); + + tmp_reg422 = rtl_read_byte(rtlpriv, + REG_FWHW_TXQ_CTRL + 2); + if (tmp_reg422 & BIT(6)) + recover = true; + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, + tmp_reg422 & (~BIT(6))); + + rtl8723ae_set_fw_rsvdpagepkt(hw, 0); + + _rtl8723ae_set_bcn_ctrl_reg(hw, BIT(3), 0); + _rtl8723ae_set_bcn_ctrl_reg(hw, 0, BIT(4)); + + if (recover) + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 2, + tmp_reg422); + + rtl_write_byte(rtlpriv, REG_CR + 1, + (tmp_regcr & ~(BIT(0)))); + } + rtl8723ae_set_fw_joinbss_report_cmd(hw, (*(u8 *) val)); + + break; } + case HW_VAR_H2C_FW_P2P_PS_OFFLOAD: + rtl8723ae_set_p2p_ps_offload_cmd(hw, (*(u8 *)val)); + break; + case HW_VAR_AID:{ + u16 u2btmp; + u2btmp = rtl_read_word(rtlpriv, REG_BCN_PSR_RPT); + u2btmp &= 0xC000; + rtl_write_word(rtlpriv, REG_BCN_PSR_RPT, (u2btmp | + mac->assoc_id)); + break; } + case HW_VAR_CORRECT_TSF:{ + u8 btype_ibss = ((u8 *) (val))[0]; + + if (btype_ibss == true) + _rtl8723ae_stop_tx_beacon(hw); + + _rtl8723ae_set_bcn_ctrl_reg(hw, 0, BIT(3)); + + rtl_write_dword(rtlpriv, REG_TSFTR, + (u32) (mac->tsf & 0xffffffff)); + rtl_write_dword(rtlpriv, REG_TSFTR + 4, + (u32) ((mac->tsf >> 32) & 0xffffffff)); + + _rtl8723ae_set_bcn_ctrl_reg(hw, BIT(3), 0); + + if (btype_ibss == true) + _rtl8723ae_resume_tx_beacon(hw); + break; } + case HW_VAR_FW_LPS_ACTION: { + bool enter_fwlps = *((bool *)val); + u8 rpwm_val, fw_pwrmode; + bool fw_current_inps; + + if (enter_fwlps) { + rpwm_val = 0x02; /* RF off */ + fw_current_inps = true; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_FW_PSMODE_STATUS, + (u8 *)(&fw_current_inps)); + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_H2C_FW_PWRMODE, + (u8 *)(&ppsc->fwctrl_psmode)); + + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_SET_RPWM, + (u8 *)(&rpwm_val)); + } else { + rpwm_val = 0x0C; /* RF on */ + fw_pwrmode = FW_PS_ACTIVE_MODE; + fw_current_inps = false; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SET_RPWM, + (u8 *)(&rpwm_val)); + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_H2C_FW_PWRMODE, + (u8 *)(&fw_pwrmode)); + + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_FW_PSMODE_STATUS, + (u8 *)(&fw_current_inps)); + } + break; } + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } +} + +static bool _rtl8723ae_llt_write(struct ieee80211_hw *hw, u32 address, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + bool status = true; + long count = 0; + u32 value = _LLT_INIT_ADDR(address) | + _LLT_INIT_DATA(data) | _LLT_OP(_LLT_WRITE_ACCESS); + + rtl_write_dword(rtlpriv, REG_LLT_INIT, value); + + do { + value = rtl_read_dword(rtlpriv, REG_LLT_INIT); + if (_LLT_NO_ACTIVE == _LLT_OP_VALUE(value)) + break; + + if (count > POLLING_LLT_THRESHOLD) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Failed to polling write LLT done at address %d!\n", + address); + status = false; + break; + } + } while (++count); + + return status; +} + +static bool _rtl8723ae_llt_table_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + unsigned short i; + u8 txpktbuf_bndy; + u8 maxPage; + bool status; + u8 ubyte; + + maxPage = 255; + txpktbuf_bndy = 246; + + rtl_write_byte(rtlpriv, REG_CR, 0x8B); + + rtl_write_word(rtlpriv, REG_RQPN_NPQ, 0x0000); + + rtl_write_dword(rtlpriv, REG_RQPN, 0x80ac1c29); + rtl_write_byte(rtlpriv, REG_RQPN_NPQ, 0x03); + + rtl_write_dword(rtlpriv, REG_TRXFF_BNDY, (0x27FF0000 | txpktbuf_bndy)); + rtl_write_byte(rtlpriv, REG_TDECTRL + 1, txpktbuf_bndy); + + rtl_write_byte(rtlpriv, REG_TXPKTBUF_BCNQ_BDNY, txpktbuf_bndy); + rtl_write_byte(rtlpriv, REG_TXPKTBUF_MGQ_BDNY, txpktbuf_bndy); + + rtl_write_byte(rtlpriv, 0x45D, txpktbuf_bndy); + rtl_write_byte(rtlpriv, REG_PBP, 0x11); + rtl_write_byte(rtlpriv, REG_RX_DRVINFO_SZ, 0x4); + + for (i = 0; i < (txpktbuf_bndy - 1); i++) { + status = _rtl8723ae_llt_write(hw, i, i + 1); + if (true != status) + return status; + } + + status = _rtl8723ae_llt_write(hw, (txpktbuf_bndy - 1), 0xFF); + if (true != status) + return status; + + for (i = txpktbuf_bndy; i < maxPage; i++) { + status = _rtl8723ae_llt_write(hw, i, (i + 1)); + if (true != status) + return status; + } + + status = _rtl8723ae_llt_write(hw, maxPage, txpktbuf_bndy); + if (true != status) + return status; + + rtl_write_byte(rtlpriv, REG_CR, 0xff); + ubyte = rtl_read_byte(rtlpriv, REG_RQPN + 3); + rtl_write_byte(rtlpriv, REG_RQPN + 3, ubyte | BIT(7)); + + return true; +} + +static void _rtl8723ae_gen_refresh_led_state(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_led *pLed0 = &(pcipriv->ledctl.sw_led0); + + if (rtlpriv->rtlhal.up_first_time) + return; + + if (ppsc->rfoff_reason == RF_CHANGE_BY_IPS) + rtl8723ae_sw_led_on(hw, pLed0); + else if (ppsc->rfoff_reason == RF_CHANGE_BY_INIT) + rtl8723ae_sw_led_on(hw, pLed0); + else + rtl8723ae_sw_led_off(hw, pLed0); +} + +static bool _rtl8712e_init_mac(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + unsigned char bytetmp; + unsigned short wordtmp; + u16 retry = 0; + u16 tmpu2b; + bool mac_func_enable; + + rtl_write_byte(rtlpriv, REG_RSV_CTRL, 0x00); + bytetmp = rtl_read_byte(rtlpriv, REG_CR); + if (bytetmp == 0xFF) + mac_func_enable = true; + else + mac_func_enable = false; + + + /* HW Power on sequence */ + if (!rtl_hal_pwrseqcmdparsing(rtlpriv, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, + PWR_INTF_PCI_MSK, Rtl8723_NIC_ENABLE_FLOW)) + return false; + + bytetmp = rtl_read_byte(rtlpriv, REG_PCIE_CTRL_REG+2); + rtl_write_byte(rtlpriv, REG_PCIE_CTRL_REG+2, bytetmp | BIT(4)); + + /* eMAC time out function enable, 0x369[7]=1 */ + bytetmp = rtl_read_byte(rtlpriv, 0x369); + rtl_write_byte(rtlpriv, 0x369, bytetmp | BIT(7)); + + /* ePHY reg 0x1e bit[4]=1 using MDIO interface, + * we should do this before Enabling ASPM backdoor. + */ + do { + rtl_write_word(rtlpriv, 0x358, 0x5e); + udelay(100); + rtl_write_word(rtlpriv, 0x356, 0xc280); + rtl_write_word(rtlpriv, 0x354, 0xc290); + rtl_write_word(rtlpriv, 0x358, 0x3e); + udelay(100); + rtl_write_word(rtlpriv, 0x358, 0x5e); + udelay(100); + tmpu2b = rtl_read_word(rtlpriv, 0x356); + retry++; + } while (tmpu2b != 0xc290 && retry < 100); + + if (retry >= 100) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "InitMAC(): ePHY configure fail!!!\n"); + return false; + } + + rtl_write_word(rtlpriv, REG_CR, 0x2ff); + rtl_write_word(rtlpriv, REG_CR + 1, 0x06); + + if (!mac_func_enable) { + if (_rtl8723ae_llt_table_init(hw) == false) + return false; + } + + rtl_write_dword(rtlpriv, REG_HISR, 0xffffffff); + rtl_write_byte(rtlpriv, REG_HISRE, 0xff); + + rtl_write_word(rtlpriv, REG_TRXFF_BNDY + 2, 0x27ff); + + wordtmp = rtl_read_word(rtlpriv, REG_TRXDMA_CTRL) & 0xf; + wordtmp |= 0xF771; + rtl_write_word(rtlpriv, REG_TRXDMA_CTRL, wordtmp); + + rtl_write_byte(rtlpriv, REG_FWHW_TXQ_CTRL + 1, 0x1F); + rtl_write_dword(rtlpriv, REG_RCR, rtlpci->receive_config); + rtl_write_word(rtlpriv, REG_RXFLTMAP2, 0xFFFF); + rtl_write_dword(rtlpriv, REG_TCR, rtlpci->transmit_config); + + rtl_write_byte(rtlpriv, 0x4d0, 0x0); + + rtl_write_dword(rtlpriv, REG_BCNQ_DESA, + ((u64) rtlpci->tx_ring[BEACON_QUEUE].dma) & + DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_MGQ_DESA, + (u64) rtlpci->tx_ring[MGNT_QUEUE].dma & + DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_VOQ_DESA, + (u64) rtlpci->tx_ring[VO_QUEUE].dma & DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_VIQ_DESA, + (u64) rtlpci->tx_ring[VI_QUEUE].dma & DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_BEQ_DESA, + (u64) rtlpci->tx_ring[BE_QUEUE].dma & DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_BKQ_DESA, + (u64) rtlpci->tx_ring[BK_QUEUE].dma & DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_HQ_DESA, + (u64) rtlpci->tx_ring[HIGH_QUEUE].dma & + DMA_BIT_MASK(32)); + rtl_write_dword(rtlpriv, REG_RX_DESA, + (u64) rtlpci->rx_ring[RX_MPDU_QUEUE].dma & + DMA_BIT_MASK(32)); + + rtl_write_byte(rtlpriv, REG_PCIE_CTRL_REG + 3, 0x74); + + rtl_write_dword(rtlpriv, REG_INT_MIG, 0); + + bytetmp = rtl_read_byte(rtlpriv, REG_APSD_CTRL); + rtl_write_byte(rtlpriv, REG_APSD_CTRL, bytetmp & ~BIT(6)); + do { + retry++; + bytetmp = rtl_read_byte(rtlpriv, REG_APSD_CTRL); + } while ((retry < 200) && (bytetmp & BIT(7))); + + _rtl8723ae_gen_refresh_led_state(hw); + + rtl_write_dword(rtlpriv, REG_MCUTST_1, 0x0); + + return true; +} + +static void _rtl8723ae_hw_configure(struct ieee80211_hw *hw) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + u8 reg_bw_opmode; + u32 reg_prsr; + + reg_bw_opmode = BW_OPMODE_20MHZ; + reg_prsr = RATE_ALL_CCK | RATE_ALL_OFDM_AG; + + rtl_write_byte(rtlpriv, REG_INIRTS_RATE_SEL, 0x8); + + rtl_write_byte(rtlpriv, REG_BWOPMODE, reg_bw_opmode); + + rtl_write_dword(rtlpriv, REG_RRSR, reg_prsr); + + rtl_write_byte(rtlpriv, REG_SLOT, 0x09); + + rtl_write_byte(rtlpriv, REG_AMPDU_MIN_SPACE, 0x0); + + rtl_write_word(rtlpriv, REG_FWHW_TXQ_CTRL, 0x1F80); + + rtl_write_word(rtlpriv, REG_RL, 0x0707); + + rtl_write_dword(rtlpriv, REG_BAR_MODE_CTRL, 0x02012802); + + rtl_write_byte(rtlpriv, REG_HWSEQ_CTRL, 0xFF); + + rtl_write_dword(rtlpriv, REG_DARFRC, 0x01000000); + rtl_write_dword(rtlpriv, REG_DARFRC + 4, 0x07060504); + rtl_write_dword(rtlpriv, REG_RARFRC, 0x01000000); + rtl_write_dword(rtlpriv, REG_RARFRC + 4, 0x07060504); + + if ((pcipriv->bt_coexist.bt_coexistence) && + (pcipriv->bt_coexist.bt_coexist_type == BT_CSR_BC4)) + rtl_write_dword(rtlpriv, REG_AGGLEN_LMT, 0x97427431); + else + rtl_write_dword(rtlpriv, REG_AGGLEN_LMT, 0xb972a841); + + rtl_write_byte(rtlpriv, REG_ATIMWND, 0x2); + + rtl_write_byte(rtlpriv, REG_BCN_MAX_ERR, 0xff); + + rtlpci->reg_bcn_ctrl_val = 0x1f; + rtl_write_byte(rtlpriv, REG_BCN_CTRL, rtlpci->reg_bcn_ctrl_val); + + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 1, 0xff); + + rtl_write_byte(rtlpriv, REG_TBTT_PROHIBIT + 1, 0xff); + + rtl_write_byte(rtlpriv, REG_PIFS, 0x1C); + rtl_write_byte(rtlpriv, REG_AGGR_BREAK_TIME, 0x16); + + if ((pcipriv->bt_coexist.bt_coexistence) && + (pcipriv->bt_coexist.bt_coexist_type == BT_CSR_BC4)) { + rtl_write_word(rtlpriv, REG_NAV_PROT_LEN, 0x0020); + rtl_write_word(rtlpriv, REG_PROT_MODE_CTRL, 0x0402); + } else { + rtl_write_word(rtlpriv, REG_NAV_PROT_LEN, 0x0020); + rtl_write_word(rtlpriv, REG_NAV_PROT_LEN, 0x0020); + } + + if ((pcipriv->bt_coexist.bt_coexistence) && + (pcipriv->bt_coexist.bt_coexist_type == BT_CSR_BC4)) + rtl_write_dword(rtlpriv, REG_FAST_EDCA_CTRL, 0x03086666); + else + rtl_write_dword(rtlpriv, REG_FAST_EDCA_CTRL, 0x086666); + + rtl_write_byte(rtlpriv, REG_ACKTO, 0x40); + + rtl_write_word(rtlpriv, REG_SPEC_SIFS, 0x1010); + rtl_write_word(rtlpriv, REG_MAC_SPEC_SIFS, 0x1010); + + rtl_write_word(rtlpriv, REG_SIFS_CTX, 0x1010); + + rtl_write_word(rtlpriv, REG_SIFS_TRX, 0x1010); + + rtl_write_dword(rtlpriv, REG_MAR, 0xffffffff); + rtl_write_dword(rtlpriv, REG_MAR + 4, 0xffffffff); + + rtl_write_dword(rtlpriv, 0x394, 0x1); +} + +static void _rtl8723ae_enable_aspm_back_door(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + rtl_write_byte(rtlpriv, 0x34b, 0x93); + rtl_write_word(rtlpriv, 0x350, 0x870c); + rtl_write_byte(rtlpriv, 0x352, 0x1); + + if (ppsc->support_backdoor) + rtl_write_byte(rtlpriv, 0x349, 0x1b); + else + rtl_write_byte(rtlpriv, 0x349, 0x03); + + rtl_write_word(rtlpriv, 0x350, 0x2718); + rtl_write_byte(rtlpriv, 0x352, 0x1); +} + +void rtl8723ae_enable_hw_security_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 sec_reg_value; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "PairwiseEncAlgorithm = %d GroupEncAlgorithm = %d\n", + rtlpriv->sec.pairwise_enc_algorithm, + rtlpriv->sec.group_enc_algorithm); + + if (rtlpriv->cfg->mod_params->sw_crypto || rtlpriv->sec.use_sw_sec) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "not open hw encryption\n"); + return; + } + + sec_reg_value = SCR_TxEncEnable | SCR_RxDecEnable; + + if (rtlpriv->sec.use_defaultkey) { + sec_reg_value |= SCR_TxUseDK; + sec_reg_value |= SCR_RxUseDK; + } + + sec_reg_value |= (SCR_RXBCUSEDK | SCR_TXBCUSEDK); + + rtl_write_byte(rtlpriv, REG_CR + 1, 0x02); + + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "The SECR-value %x\n", sec_reg_value); + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_WPA_CONFIG, &sec_reg_value); + +} + +int rtl8723ae_hw_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + bool rtstatus = true; + int err; + u8 tmp_u1b; + + rtlpriv->rtlhal.being_init_adapter = true; + rtlpriv->intf_ops->disable_aspm(hw); + rtstatus = _rtl8712e_init_mac(hw); + if (rtstatus != true) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Init MAC failed\n"); + err = 1; + return err; + } + + err = rtl8723ae_download_fw(hw); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Failed to download FW. Init HW without FW now..\n"); + err = 1; + rtlhal->fw_ready = false; + return err; + } else { + rtlhal->fw_ready = true; + } + + rtlhal->last_hmeboxnum = 0; + rtl8723ae_phy_mac_config(hw); + /* because the last function modifies RCR, we update + * rcr var here, or TP will be unstable as ther receive_config + * is wrong, RX RCR_ACRC32 will cause TP unstable & Rx + * RCR_APP_ICV will cause mac80211 unassoc for cisco 1252 + */ + rtlpci->receive_config = rtl_read_dword(rtlpriv, REG_RCR); + rtlpci->receive_config &= ~(RCR_ACRC32 | RCR_AICV); + rtl_write_dword(rtlpriv, REG_RCR, rtlpci->receive_config); + + rtl8723ae_phy_bb_config(hw); + rtlphy->rf_mode = RF_OP_BY_SW_3WIRE; + rtl8723ae_phy_rf_config(hw); + if (IS_VENDOR_UMC_A_CUT(rtlhal->version)) { + rtl_set_rfreg(hw, RF90_PATH_A, RF_RX_G1, MASKDWORD, 0x30255); + rtl_set_rfreg(hw, RF90_PATH_A, RF_RX_G2, MASKDWORD, 0x50a00); + } else if (IS_81xxC_VENDOR_UMC_B_CUT(rtlhal->version)) { + rtl_set_rfreg(hw, RF90_PATH_A, 0x0C, MASKDWORD, 0x894AE); + rtl_set_rfreg(hw, RF90_PATH_A, 0x0A, MASKDWORD, 0x1AF31); + rtl_set_rfreg(hw, RF90_PATH_A, RF_IPA, MASKDWORD, 0x8F425); + rtl_set_rfreg(hw, RF90_PATH_A, RF_SYN_G2, MASKDWORD, 0x4F200); + rtl_set_rfreg(hw, RF90_PATH_A, RF_RCK1, MASKDWORD, 0x44053); + rtl_set_rfreg(hw, RF90_PATH_A, RF_RCK2, MASKDWORD, 0x80201); + } + rtlphy->rfreg_chnlval[0] = rtl_get_rfreg(hw, (enum radio_path)0, + RF_CHNLBW, RFREG_OFFSET_MASK); + rtlphy->rfreg_chnlval[1] = rtl_get_rfreg(hw, (enum radio_path)1, + RF_CHNLBW, RFREG_OFFSET_MASK); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BCCKEN, 0x1); + rtl_set_bbreg(hw, RFPGA0_RFMOD, BOFDMEN, 0x1); + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER2, BIT(10), 1); + _rtl8723ae_hw_configure(hw); + rtl_cam_reset_all_entry(hw); + rtl8723ae_enable_hw_security_config(hw); + + ppsc->rfpwr_state = ERFON; + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_ETHER_ADDR, mac->mac_addr); + _rtl8723ae_enable_aspm_back_door(hw); + rtlpriv->intf_ops->enable_aspm(hw); + + rtl8723ae_bt_hw_init(hw); + + if (ppsc->rfpwr_state == ERFON) { + rtl8723ae_phy_set_rfpath_switch(hw, 1); + if (rtlphy->iqk_initialized) { + rtl8723ae_phy_iq_calibrate(hw, true); + } else { + rtl8723ae_phy_iq_calibrate(hw, false); + rtlphy->iqk_initialized = true; + } + + rtl8723ae_phy_lc_calibrate(hw); + } + + tmp_u1b = efuse_read_1byte(hw, 0x1FA); + if (!(tmp_u1b & BIT(0))) { + rtl_set_rfreg(hw, RF90_PATH_A, 0x15, 0x0F, 0x05); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "PA BIAS path A\n"); + } + + if (!(tmp_u1b & BIT(4))) { + tmp_u1b = rtl_read_byte(rtlpriv, 0x16) & 0x0F; + rtl_write_byte(rtlpriv, 0x16, tmp_u1b | 0x80); + udelay(10); + rtl_write_byte(rtlpriv, 0x16, tmp_u1b | 0x90); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "under 1.5V\n"); + } + rtl8723ae_dm_init(hw); + rtlpriv->rtlhal.being_init_adapter = false; + return err; +} + +static enum version_8723e _rtl8723ae_read_chip_version(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + enum version_8723e version = 0x0000; + u32 value32; + + value32 = rtl_read_dword(rtlpriv, REG_SYS_CFG); + if (value32 & TRP_VAUX_EN) { + version = (enum version_8723e)(version | + ((value32 & VENDOR_ID) ? CHIP_VENDOR_UMC : 0)); + /* RTL8723 with BT function. */ + version = (enum version_8723e)(version | + ((value32 & BT_FUNC) ? CHIP_8723 : 0)); + + } else { + /* Normal mass production chip. */ + version = (enum version_8723e) NORMAL_CHIP; + version = (enum version_8723e)(version | + ((value32 & VENDOR_ID) ? CHIP_VENDOR_UMC : 0)); + /* RTL8723 with BT function. */ + version = (enum version_8723e)(version | + ((value32 & BT_FUNC) ? CHIP_8723 : 0)); + if (IS_CHIP_VENDOR_UMC(version)) + version = (enum version_8723e)(version | + ((value32 & CHIP_VER_RTL_MASK)));/* IC version (CUT) */ + if (IS_8723_SERIES(version)) { + value32 = rtl_read_dword(rtlpriv, REG_GPIO_OUTSTS); + /* ROM code version */ + version = (enum version_8723e)(version | + ((value32 & RF_RL_ID)>>20)); + } + } + + if (IS_8723_SERIES(version)) { + value32 = rtl_read_dword(rtlpriv, REG_MULTI_FUNC_CTRL); + rtlphy->polarity_ctl = ((value32 & WL_HWPDN_SL) ? + RT_POLARITY_HIGH_ACT : + RT_POLARITY_LOW_ACT); + } + switch (version) { + case VERSION_TEST_UMC_CHIP_8723: + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Chip Version ID: VERSION_TEST_UMC_CHIP_8723.\n"); + break; + case VERSION_NORMAL_UMC_CHIP_8723_1T1R_A_CUT: + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Chip Version ID: VERSION_NORMAL_UMC_CHIP_8723_1T1R_A_CUT.\n"); + break; + case VERSION_NORMAL_UMC_CHIP_8723_1T1R_B_CUT: + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Chip Version ID: VERSION_NORMAL_UMC_CHIP_8723_1T1R_B_CUT.\n"); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Chip Version ID: Unknown. Bug?\n"); + break; + } + + if (IS_8723_SERIES(version)) + rtlphy->rf_type = RF_1T1R; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Chip RF Type: %s\n", + (rtlphy->rf_type == RF_2T2R) ? "RF_2T2R" : "RF_1T1R"); + + return version; +} + +static int _rtl8723ae_set_media_status(struct ieee80211_hw *hw, + enum nl80211_iftype type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 bt_msr = rtl_read_byte(rtlpriv, MSR) & 0xfc; + enum led_ctl_mode ledaction = LED_CTL_NO_LINK; + + rtl_write_dword(rtlpriv, REG_BCN_CTRL, 0); + RT_TRACE(rtlpriv, COMP_BEACON, DBG_LOUD, + "clear 0x550 when set HW_VAR_MEDIA_STATUS\n"); + + if (type == NL80211_IFTYPE_UNSPECIFIED || + type == NL80211_IFTYPE_STATION) { + _rtl8723ae_stop_tx_beacon(hw); + _rtl8723ae_enable_bcn_sufunc(hw); + } else if (type == NL80211_IFTYPE_ADHOC || + type == NL80211_IFTYPE_AP) { + _rtl8723ae_resume_tx_beacon(hw); + _rtl8723ae_disable_bcn_sufunc(hw); + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "Set HW_VAR_MEDIA_STATUS: No such media status(%x).\n", + type); + } + + switch (type) { + case NL80211_IFTYPE_UNSPECIFIED: + bt_msr |= MSR_NOLINK; + ledaction = LED_CTL_LINK; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to NO LINK!\n"); + break; + case NL80211_IFTYPE_ADHOC: + bt_msr |= MSR_ADHOC; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to Ad Hoc!\n"); + break; + case NL80211_IFTYPE_STATION: + bt_msr |= MSR_INFRA; + ledaction = LED_CTL_LINK; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to STA!\n"); + break; + case NL80211_IFTYPE_AP: + bt_msr |= MSR_AP; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Set Network type to AP!\n"); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Network type %d not supported!\n", + type); + return 1; + break; + + } + + rtl_write_byte(rtlpriv, (MSR), bt_msr); + rtlpriv->cfg->ops->led_control(hw, ledaction); + if ((bt_msr & 0x03) == MSR_AP) + rtl_write_byte(rtlpriv, REG_BCNTCFG + 1, 0x00); + else + rtl_write_byte(rtlpriv, REG_BCNTCFG + 1, 0x66); + return 0; +} + +void rtl8723ae_set_check_bssid(struct ieee80211_hw *hw, bool check_bssid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + u32 reg_rcr = rtlpci->receive_config; + + if (rtlpriv->psc.rfpwr_state != ERFON) + return; + + if (check_bssid == true) { + reg_rcr |= (RCR_CBSSID_DATA | RCR_CBSSID_BCN); + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_RCR, + (u8 *)(®_rcr)); + _rtl8723ae_set_bcn_ctrl_reg(hw, 0, BIT(4)); + } else if (check_bssid == false) { + reg_rcr &= (~(RCR_CBSSID_DATA | RCR_CBSSID_BCN)); + _rtl8723ae_set_bcn_ctrl_reg(hw, BIT(4), 0); + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_RCR, (u8 *) (®_rcr)); + } +} + +int rtl8723ae_set_network_type(struct ieee80211_hw *hw, + enum nl80211_iftype type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (_rtl8723ae_set_media_status(hw, type)) + return -EOPNOTSUPP; + + if (rtlpriv->mac80211.link_state == MAC80211_LINKED) { + if (type != NL80211_IFTYPE_AP) + rtl8723ae_set_check_bssid(hw, true); + } else { + rtl8723ae_set_check_bssid(hw, false); + } + return 0; +} + +/* don't set REG_EDCA_BE_PARAM here because mac80211 will send pkt when scan */ +void rtl8723ae_set_qos(struct ieee80211_hw *hw, int aci) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl8723ae_dm_init_edca_turbo(hw); + switch (aci) { + case AC1_BK: + rtl_write_dword(rtlpriv, REG_EDCA_BK_PARAM, 0xa44f); + break; + case AC0_BE: + /* rtl_write_dword(rtlpriv, REG_EDCA_BE_PARAM, u4ac_param); */ + break; + case AC2_VI: + rtl_write_dword(rtlpriv, REG_EDCA_VI_PARAM, 0x5e4322); + break; + case AC3_VO: + rtl_write_dword(rtlpriv, REG_EDCA_VO_PARAM, 0x2f3222); + break; + default: + RT_ASSERT(false, "invalid aci: %d !\n", aci); + break; + } +} + +void rtl8723ae_enable_interrupt(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + rtl_write_dword(rtlpriv, 0x3a8, rtlpci->irq_mask[0] & 0xFFFFFFFF); + rtl_write_dword(rtlpriv, 0x3ac, rtlpci->irq_mask[1] & 0xFFFFFFFF); + rtlpci->irq_enabled = true; +} + +void rtl8723ae_disable_interrupt(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + rtl_write_dword(rtlpriv, 0x3a8, IMR8190_DISABLED); + rtl_write_dword(rtlpriv, 0x3ac, IMR8190_DISABLED); + rtlpci->irq_enabled = false; + synchronize_irq(rtlpci->pdev->irq); +} + +static void _rtl8723ae_poweroff_adapter(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 u1tmp; + + /* Combo (PCIe + USB) Card and PCIe-MF Card */ + /* 1. Run LPS WL RFOFF flow */ + rtl_hal_pwrseqcmdparsing(rtlpriv, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, + PWR_INTF_PCI_MSK, Rtl8723_NIC_LPS_ENTER_FLOW); + + /* 2. 0x1F[7:0] = 0 */ + /* turn off RF */ + rtl_write_byte(rtlpriv, REG_RF_CTRL, 0x00); + if ((rtl_read_byte(rtlpriv, REG_MCUFWDL) & BIT(7)) && rtlhal->fw_ready) + rtl8723ae_firmware_selfreset(hw); + + /* Reset MCU. Suggested by Filen. */ + u1tmp = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN+1); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN+1, (u1tmp & (~BIT(2)))); + + /* g. MCUFWDL 0x80[1:0]=0 */ + /* reset MCU ready status */ + rtl_write_byte(rtlpriv, REG_MCUFWDL, 0x00); + + /* HW card disable configuration. */ + rtl_hal_pwrseqcmdparsing(rtlpriv, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, + PWR_INTF_PCI_MSK, Rtl8723_NIC_DISABLE_FLOW); + + /* Reset MCU IO Wrapper */ + u1tmp = rtl_read_byte(rtlpriv, REG_RSV_CTRL + 1); + rtl_write_byte(rtlpriv, REG_RSV_CTRL + 1, (u1tmp & (~BIT(0)))); + u1tmp = rtl_read_byte(rtlpriv, REG_RSV_CTRL + 1); + rtl_write_byte(rtlpriv, REG_RSV_CTRL + 1, u1tmp | BIT(0)); + + /* 7. RSV_CTRL 0x1C[7:0] = 0x0E */ + /* lock ISO/CLK/Power control register */ + rtl_write_byte(rtlpriv, REG_RSV_CTRL, 0x0e); +} + +void rtl8723ae_card_disable(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + enum nl80211_iftype opmode; + + mac->link_state = MAC80211_NOLINK; + opmode = NL80211_IFTYPE_UNSPECIFIED; + _rtl8723ae_set_media_status(hw, opmode); + if (rtlpci->driver_is_goingto_unload || + ppsc->rfoff_reason > RF_CHANGE_BY_PS) + rtlpriv->cfg->ops->led_control(hw, LED_CTL_POWER_OFF); + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + _rtl8723ae_poweroff_adapter(hw); + + /* after power off we should do iqk again */ + rtlpriv->phy.iqk_initialized = false; +} + +void rtl8723ae_interrupt_recognized(struct ieee80211_hw *hw, + u32 *p_inta, u32 *p_intb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + *p_inta = rtl_read_dword(rtlpriv, 0x3a0) & rtlpci->irq_mask[0]; + rtl_write_dword(rtlpriv, 0x3a0, *p_inta); +} + +void rtl8723ae_set_beacon_related_registers(struct ieee80211_hw *hw) +{ + + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 bcn_interval, atim_window; + + bcn_interval = mac->beacon_interval; + atim_window = 2; /*FIX MERGE */ + rtl8723ae_disable_interrupt(hw); + rtl_write_word(rtlpriv, REG_ATIMWND, atim_window); + rtl_write_word(rtlpriv, REG_BCN_INTERVAL, bcn_interval); + rtl_write_word(rtlpriv, REG_BCNTCFG, 0x660f); + rtl_write_byte(rtlpriv, REG_RXTSF_OFFSET_CCK, 0x18); + rtl_write_byte(rtlpriv, REG_RXTSF_OFFSET_OFDM, 0x18); + rtl_write_byte(rtlpriv, 0x606, 0x30); + rtl8723ae_enable_interrupt(hw); +} + +void rtl8723ae_set_beacon_interval(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 bcn_interval = mac->beacon_interval; + + RT_TRACE(rtlpriv, COMP_BEACON, DBG_DMESG, + "beacon_interval:%d\n", bcn_interval); + rtl8723ae_disable_interrupt(hw); + rtl_write_word(rtlpriv, REG_BCN_INTERVAL, bcn_interval); + rtl8723ae_enable_interrupt(hw); +} + +void rtl8723ae_update_interrupt_mask(struct ieee80211_hw *hw, + u32 add_msr, u32 rm_msr) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + RT_TRACE(rtlpriv, COMP_INTR, DBG_LOUD, + "add_msr:%x, rm_msr:%x\n", add_msr, rm_msr); + + if (add_msr) + rtlpci->irq_mask[0] |= add_msr; + if (rm_msr) + rtlpci->irq_mask[0] &= (~rm_msr); + rtl8723ae_disable_interrupt(hw); + rtl8723ae_enable_interrupt(hw); +} + +static u8 _rtl8723ae_get_chnl_group(u8 chnl) +{ + u8 group; + + if (chnl < 3) + group = 0; + else if (chnl < 9) + group = 1; + else + group = 2; + return group; +} + +static void _rtl8723ae_read_txpower_info_from_hwpg(struct ieee80211_hw *hw, + bool autoload_fail, + u8 *hwinfo) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 rf_path, index, tempval; + u16 i; + + for (rf_path = 0; rf_path < 1; rf_path++) { + for (i = 0; i < 3; i++) { + if (!autoload_fail) { + rtlefuse->eeprom_chnlarea_txpwr_cck + [rf_path][i] = + hwinfo[EEPROM_TXPOWERCCK + rf_path * 3 + i]; + rtlefuse->eeprom_chnlarea_txpwr_ht40_1s + [rf_path][i] = + hwinfo[EEPROM_TXPOWERHT40_1S + rf_path * + 3 + i]; + } else { + rtlefuse->eeprom_chnlarea_txpwr_cck + [rf_path][i] = + EEPROM_DEFAULT_TXPOWERLEVEL; + rtlefuse->eeprom_chnlarea_txpwr_ht40_1s + [rf_path][i] = + EEPROM_DEFAULT_TXPOWERLEVEL; + } + } + } + + for (i = 0; i < 3; i++) { + if (!autoload_fail) + tempval = hwinfo[EEPROM_TXPOWERHT40_2SDIFF + i]; + else + tempval = EEPROM_DEFAULT_HT40_2SDIFF; + rtlefuse->eprom_chnl_txpwr_ht40_2sdf[RF90_PATH_A][i] = + (tempval & 0xf); + rtlefuse->eprom_chnl_txpwr_ht40_2sdf[RF90_PATH_B][i] = + ((tempval & 0xf0) >> 4); + } + + for (rf_path = 0; rf_path < 2; rf_path++) + for (i = 0; i < 3; i++) + RTPRINT(rtlpriv, FINIT, INIT_EEPROM, + "RF(%d) EEPROM CCK Area(%d) = 0x%x\n", rf_path, + i, rtlefuse->eeprom_chnlarea_txpwr_cck + [rf_path][i]); + for (rf_path = 0; rf_path < 2; rf_path++) + for (i = 0; i < 3; i++) + RTPRINT(rtlpriv, FINIT, INIT_EEPROM, + "RF(%d) EEPROM HT40 1S Area(%d) = 0x%x\n", + rf_path, i, + rtlefuse->eeprom_chnlarea_txpwr_ht40_1s + [rf_path][i]); + for (rf_path = 0; rf_path < 2; rf_path++) + for (i = 0; i < 3; i++) + RTPRINT(rtlpriv, FINIT, INIT_EEPROM, + "RF(%d) EEPROM HT40 2S Diff Area(%d) = 0x%x\n", + rf_path, i, + rtlefuse->eprom_chnl_txpwr_ht40_2sdf + [rf_path][i]); + + for (rf_path = 0; rf_path < 2; rf_path++) { + for (i = 0; i < 14; i++) { + index = _rtl8723ae_get_chnl_group((u8) i); + + rtlefuse->txpwrlevel_cck[rf_path][i] = + rtlefuse->eeprom_chnlarea_txpwr_cck + [rf_path][index]; + rtlefuse->txpwrlevel_ht40_1s[rf_path][i] = + rtlefuse->eeprom_chnlarea_txpwr_ht40_1s + [rf_path][index]; + + if ((rtlefuse->eeprom_chnlarea_txpwr_ht40_1s + [rf_path][index] - + rtlefuse->eprom_chnl_txpwr_ht40_2sdf[rf_path] + [index]) > 0) { + rtlefuse->txpwrlevel_ht40_2s[rf_path][i] = + rtlefuse->eeprom_chnlarea_txpwr_ht40_1s + [rf_path][index] - + rtlefuse->eprom_chnl_txpwr_ht40_2sdf + [rf_path][index]; + } else { + rtlefuse->txpwrlevel_ht40_2s[rf_path][i] = 0; + } + } + + for (i = 0; i < 14; i++) { + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF(%d)-Ch(%d) [CCK / HT40_1S / HT40_2S] = " + "[0x%x / 0x%x / 0x%x]\n", rf_path, i, + rtlefuse->txpwrlevel_cck[rf_path][i], + rtlefuse->txpwrlevel_ht40_1s[rf_path][i], + rtlefuse->txpwrlevel_ht40_2s[rf_path][i]); + } + } + + for (i = 0; i < 3; i++) { + if (!autoload_fail) { + rtlefuse->eeprom_pwrlimit_ht40[i] = + hwinfo[EEPROM_TXPWR_GROUP + i]; + rtlefuse->eeprom_pwrlimit_ht20[i] = + hwinfo[EEPROM_TXPWR_GROUP + 3 + i]; + } else { + rtlefuse->eeprom_pwrlimit_ht40[i] = 0; + rtlefuse->eeprom_pwrlimit_ht20[i] = 0; + } + } + + for (rf_path = 0; rf_path < 2; rf_path++) { + for (i = 0; i < 14; i++) { + index = _rtl8723ae_get_chnl_group((u8) i); + + if (rf_path == RF90_PATH_A) { + rtlefuse->pwrgroup_ht20[rf_path][i] = + (rtlefuse->eeprom_pwrlimit_ht20[index] & + 0xf); + rtlefuse->pwrgroup_ht40[rf_path][i] = + (rtlefuse->eeprom_pwrlimit_ht40[index] & + 0xf); + } else if (rf_path == RF90_PATH_B) { + rtlefuse->pwrgroup_ht20[rf_path][i] = + ((rtlefuse->eeprom_pwrlimit_ht20[index] & + 0xf0) >> 4); + rtlefuse->pwrgroup_ht40[rf_path][i] = + ((rtlefuse->eeprom_pwrlimit_ht40[index] & + 0xf0) >> 4); + } + + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-%d pwrgroup_ht20[%d] = 0x%x\n", rf_path, i, + rtlefuse->pwrgroup_ht20[rf_path][i]); + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-%d pwrgroup_ht40[%d] = 0x%x\n", rf_path, i, + rtlefuse->pwrgroup_ht40[rf_path][i]); + } + } + + for (i = 0; i < 14; i++) { + index = _rtl8723ae_get_chnl_group((u8) i); + + if (!autoload_fail) + tempval = hwinfo[EEPROM_TXPOWERHT20DIFF + index]; + else + tempval = EEPROM_DEFAULT_HT20_DIFF; + + rtlefuse->txpwr_ht20diff[RF90_PATH_A][i] = (tempval & 0xF); + rtlefuse->txpwr_ht20diff[RF90_PATH_B][i] = + ((tempval >> 4) & 0xF); + + if (rtlefuse->txpwr_ht20diff[RF90_PATH_A][i] & BIT(3)) + rtlefuse->txpwr_ht20diff[RF90_PATH_A][i] |= 0xF0; + + if (rtlefuse->txpwr_ht20diff[RF90_PATH_B][i] & BIT(3)) + rtlefuse->txpwr_ht20diff[RF90_PATH_B][i] |= 0xF0; + + index = _rtl8723ae_get_chnl_group((u8) i); + + if (!autoload_fail) + tempval = hwinfo[EEPROM_TXPOWER_OFDMDIFF + index]; + else + tempval = EEPROM_DEFAULT_LEGACYHTTXPOWERDIFF; + + rtlefuse->txpwr_legacyhtdiff[RF90_PATH_A][i] = (tempval & 0xF); + rtlefuse->txpwr_legacyhtdiff[RF90_PATH_B][i] = + ((tempval >> 4) & 0xF); + } + + rtlefuse->legacy_ht_txpowerdiff = + rtlefuse->txpwr_legacyhtdiff[RF90_PATH_A][7]; + + for (i = 0; i < 14; i++) + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-A Ht20 to HT40 Diff[%d] = 0x%x\n", i, + rtlefuse->txpwr_ht20diff[RF90_PATH_A][i]); + for (i = 0; i < 14; i++) + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-A Legacy to Ht40 Diff[%d] = 0x%x\n", i, + rtlefuse->txpwr_legacyhtdiff[RF90_PATH_A][i]); + for (i = 0; i < 14; i++) + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-B Ht20 to HT40 Diff[%d] = 0x%x\n", i, + rtlefuse->txpwr_ht20diff[RF90_PATH_B][i]); + for (i = 0; i < 14; i++) + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "RF-B Legacy to HT40 Diff[%d] = 0x%x\n", i, + rtlefuse->txpwr_legacyhtdiff[RF90_PATH_B][i]); + + if (!autoload_fail) + rtlefuse->eeprom_regulatory = (hwinfo[RF_OPTION1] & 0x7); + else + rtlefuse->eeprom_regulatory = 0; + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "eeprom_regulatory = 0x%x\n", rtlefuse->eeprom_regulatory); + + if (!autoload_fail) + rtlefuse->eeprom_tssi[RF90_PATH_A] = hwinfo[EEPROM_TSSI_A]; + else + rtlefuse->eeprom_tssi[RF90_PATH_A] = EEPROM_DEFAULT_TSSI; + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "TSSI_A = 0x%x, TSSI_B = 0x%x\n", + rtlefuse->eeprom_tssi[RF90_PATH_A], + rtlefuse->eeprom_tssi[RF90_PATH_B]); + + if (!autoload_fail) + tempval = hwinfo[EEPROM_THERMAL_METER]; + else + tempval = EEPROM_DEFAULT_THERMALMETER; + rtlefuse->eeprom_thermalmeter = (tempval & 0x1f); + + if (rtlefuse->eeprom_thermalmeter == 0x1f || autoload_fail) + rtlefuse->apk_thermalmeterignore = true; + + rtlefuse->thermalmeter[0] = rtlefuse->eeprom_thermalmeter; + RTPRINT(rtlpriv, FINIT, INIT_TXPOWER, + "thermalmeter = 0x%x\n", rtlefuse->eeprom_thermalmeter); +} + +static void _rtl8723ae_read_adapter_info(struct ieee80211_hw *hw, + bool pseudo_test) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u16 i, usvalue; + u8 hwinfo[HWSET_MAX_SIZE]; + u16 eeprom_id; + + if (pseudo_test) { + /* need add */ + return; + } + if (rtlefuse->epromtype == EEPROM_BOOT_EFUSE) { + rtl_efuse_shadow_map_update(hw); + + memcpy(hwinfo, &rtlefuse->efuse_map[EFUSE_INIT_MAP][0], + HWSET_MAX_SIZE); + } else if (rtlefuse->epromtype == EEPROM_93C46) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "RTL819X Not boot from eeprom, check it !!"); + } + + RT_PRINT_DATA(rtlpriv, COMP_INIT, DBG_DMESG, ("MAP\n"), + hwinfo, HWSET_MAX_SIZE); + + eeprom_id = *((u16 *)&hwinfo[0]); + if (eeprom_id != RTL8190_EEPROM_ID) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "EEPROM ID(%#x) is invalid!!\n", eeprom_id); + rtlefuse->autoload_failflag = true; + } else { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Autoload OK\n"); + rtlefuse->autoload_failflag = false; + } + + if (rtlefuse->autoload_failflag == true) + return; + + rtlefuse->eeprom_vid = *(u16 *) &hwinfo[EEPROM_VID]; + rtlefuse->eeprom_did = *(u16 *) &hwinfo[EEPROM_DID]; + rtlefuse->eeprom_svid = *(u16 *) &hwinfo[EEPROM_SVID]; + rtlefuse->eeprom_smid = *(u16 *) &hwinfo[EEPROM_SMID]; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROMId = 0x%4x\n", eeprom_id); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM VID = 0x%4x\n", rtlefuse->eeprom_vid); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM DID = 0x%4x\n", rtlefuse->eeprom_did); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM SVID = 0x%4x\n", rtlefuse->eeprom_svid); + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM SMID = 0x%4x\n", rtlefuse->eeprom_smid); + + for (i = 0; i < 6; i += 2) { + usvalue = *(u16 *)&hwinfo[EEPROM_MAC_ADDR + i]; + *((u16 *) (&rtlefuse->dev_addr[i])) = usvalue; + } + + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "dev_addr: %pM\n", rtlefuse->dev_addr); + + _rtl8723ae_read_txpower_info_from_hwpg(hw, + rtlefuse->autoload_failflag, hwinfo); + + rtl8723ae_read_bt_coexist_info_from_hwpg(hw, + rtlefuse->autoload_failflag, hwinfo); + + rtlefuse->eeprom_channelplan = *(u8 *)&hwinfo[EEPROM_CHANNELPLAN]; + rtlefuse->eeprom_version = *(u16 *)&hwinfo[EEPROM_VERSION]; + rtlefuse->txpwr_fromeprom = true; + rtlefuse->eeprom_oemid = *(u8 *)&hwinfo[EEPROM_CUSTOMER_ID]; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, + "EEPROM Customer ID: 0x%2x\n", rtlefuse->eeprom_oemid); + + /* set channel paln to world wide 13 */ + rtlefuse->channel_plan = COUNTRY_CODE_WORLD_WIDE_13; + + if (rtlhal->oem_id == RT_CID_DEFAULT) { + switch (rtlefuse->eeprom_oemid) { + case EEPROM_CID_DEFAULT: + if (rtlefuse->eeprom_did == 0x8176) { + if (CHK_SVID_SMID(0x10EC, 0x6151) || + CHK_SVID_SMID(0x10EC, 0x6152) || + CHK_SVID_SMID(0x10EC, 0x6154) || + CHK_SVID_SMID(0x10EC, 0x6155) || + CHK_SVID_SMID(0x10EC, 0x6177) || + CHK_SVID_SMID(0x10EC, 0x6178) || + CHK_SVID_SMID(0x10EC, 0x6179) || + CHK_SVID_SMID(0x10EC, 0x6180) || + CHK_SVID_SMID(0x10EC, 0x8151) || + CHK_SVID_SMID(0x10EC, 0x8152) || + CHK_SVID_SMID(0x10EC, 0x8154) || + CHK_SVID_SMID(0x10EC, 0x8155) || + CHK_SVID_SMID(0x10EC, 0x8181) || + CHK_SVID_SMID(0x10EC, 0x8182) || + CHK_SVID_SMID(0x10EC, 0x8184) || + CHK_SVID_SMID(0x10EC, 0x8185) || + CHK_SVID_SMID(0x10EC, 0x9151) || + CHK_SVID_SMID(0x10EC, 0x9152) || + CHK_SVID_SMID(0x10EC, 0x9154) || + CHK_SVID_SMID(0x10EC, 0x9155) || + CHK_SVID_SMID(0x10EC, 0x9181) || + CHK_SVID_SMID(0x10EC, 0x9182) || + CHK_SVID_SMID(0x10EC, 0x9184) || + CHK_SVID_SMID(0x10EC, 0x9185)) + rtlhal->oem_id = RT_CID_TOSHIBA; + else if (rtlefuse->eeprom_svid == 0x1025) + rtlhal->oem_id = RT_CID_819x_Acer; + else if (CHK_SVID_SMID(0x10EC, 0x6191) || + CHK_SVID_SMID(0x10EC, 0x6192) || + CHK_SVID_SMID(0x10EC, 0x6193) || + CHK_SVID_SMID(0x10EC, 0x7191) || + CHK_SVID_SMID(0x10EC, 0x7192) || + CHK_SVID_SMID(0x10EC, 0x7193) || + CHK_SVID_SMID(0x10EC, 0x8191) || + CHK_SVID_SMID(0x10EC, 0x8192) || + CHK_SVID_SMID(0x10EC, 0x8193)) + rtlhal->oem_id = RT_CID_819x_SAMSUNG; + else if (CHK_SVID_SMID(0x10EC, 0x8195) || + CHK_SVID_SMID(0x10EC, 0x9195) || + CHK_SVID_SMID(0x10EC, 0x7194) || + CHK_SVID_SMID(0x10EC, 0x8200) || + CHK_SVID_SMID(0x10EC, 0x8201) || + CHK_SVID_SMID(0x10EC, 0x8202) || + CHK_SVID_SMID(0x10EC, 0x9200)) + rtlhal->oem_id = RT_CID_819x_Lenovo; + else if (CHK_SVID_SMID(0x10EC, 0x8197) || + CHK_SVID_SMID(0x10EC, 0x9196)) + rtlhal->oem_id = RT_CID_819x_CLEVO; + else if (CHK_SVID_SMID(0x1028, 0x8194) || + CHK_SVID_SMID(0x1028, 0x8198) || + CHK_SVID_SMID(0x1028, 0x9197) || + CHK_SVID_SMID(0x1028, 0x9198)) + rtlhal->oem_id = RT_CID_819x_DELL; + else if (CHK_SVID_SMID(0x103C, 0x1629)) + rtlhal->oem_id = RT_CID_819x_HP; + else if (CHK_SVID_SMID(0x1A32, 0x2315)) + rtlhal->oem_id = RT_CID_819x_QMI; + else if (CHK_SVID_SMID(0x10EC, 0x8203)) + rtlhal->oem_id = RT_CID_819x_PRONETS; + else if (CHK_SVID_SMID(0x1043, 0x84B5)) + rtlhal->oem_id = + RT_CID_819x_Edimax_ASUS; + else + rtlhal->oem_id = RT_CID_DEFAULT; + } else if (rtlefuse->eeprom_did == 0x8178) { + if (CHK_SVID_SMID(0x10EC, 0x6181) || + CHK_SVID_SMID(0x10EC, 0x6182) || + CHK_SVID_SMID(0x10EC, 0x6184) || + CHK_SVID_SMID(0x10EC, 0x6185) || + CHK_SVID_SMID(0x10EC, 0x7181) || + CHK_SVID_SMID(0x10EC, 0x7182) || + CHK_SVID_SMID(0x10EC, 0x7184) || + CHK_SVID_SMID(0x10EC, 0x7185) || + CHK_SVID_SMID(0x10EC, 0x8181) || + CHK_SVID_SMID(0x10EC, 0x8182) || + CHK_SVID_SMID(0x10EC, 0x8184) || + CHK_SVID_SMID(0x10EC, 0x8185) || + CHK_SVID_SMID(0x10EC, 0x9181) || + CHK_SVID_SMID(0x10EC, 0x9182) || + CHK_SVID_SMID(0x10EC, 0x9184) || + CHK_SVID_SMID(0x10EC, 0x9185)) + rtlhal->oem_id = RT_CID_TOSHIBA; + else if (rtlefuse->eeprom_svid == 0x1025) + rtlhal->oem_id = RT_CID_819x_Acer; + else if (CHK_SVID_SMID(0x10EC, 0x8186)) + rtlhal->oem_id = RT_CID_819x_PRONETS; + else if (CHK_SVID_SMID(0x1043, 0x8486)) + rtlhal->oem_id = + RT_CID_819x_Edimax_ASUS; + else + rtlhal->oem_id = RT_CID_DEFAULT; + } else { + rtlhal->oem_id = RT_CID_DEFAULT; + } + break; + case EEPROM_CID_TOSHIBA: + rtlhal->oem_id = RT_CID_TOSHIBA; + break; + case EEPROM_CID_CCX: + rtlhal->oem_id = RT_CID_CCX; + break; + case EEPROM_CID_QMI: + rtlhal->oem_id = RT_CID_819x_QMI; + break; + case EEPROM_CID_WHQL: + break; + default: + rtlhal->oem_id = RT_CID_DEFAULT; + break; + + } + } +} + +static void _rtl8723ae_hal_customized_behavior(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + pcipriv->ledctl.led_opendrain = true; + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "RT Customized ID: 0x%02X\n", rtlhal->oem_id); +} + +void rtl8723ae_read_eeprom_info(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 tmp_u1b; + u32 value32; + + value32 = rtl_read_dword(rtlpriv, rtlpriv->cfg->maps[EFUSE_TEST]); + value32 = (value32 & ~EFUSE_SEL_MASK) | EFUSE_SEL(EFUSE_WIFI_SEL_0); + rtl_write_dword(rtlpriv, rtlpriv->cfg->maps[EFUSE_TEST], value32); + + rtlhal->version = _rtl8723ae_read_chip_version(hw); + + if (get_rf_type(rtlphy) == RF_1T1R) + rtlpriv->dm.rfpath_rxenable[0] = true; + else + rtlpriv->dm.rfpath_rxenable[0] = + rtlpriv->dm.rfpath_rxenable[1] = true; + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "VersionID = 0x%4x\n", + rtlhal->version); + + tmp_u1b = rtl_read_byte(rtlpriv, REG_9346CR); + if (tmp_u1b & BIT(4)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "Boot from EEPROM\n"); + rtlefuse->epromtype = EEPROM_93C46; + } else { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "Boot from EFUSE\n"); + rtlefuse->epromtype = EEPROM_BOOT_EFUSE; + } + if (tmp_u1b & BIT(5)) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_LOUD, "Autoload OK\n"); + rtlefuse->autoload_failflag = false; + _rtl8723ae_read_adapter_info(hw, false); + } else { + rtlefuse->autoload_failflag = true; + _rtl8723ae_read_adapter_info(hw, false); + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Autoload ERR!!\n"); + } + _rtl8723ae_hal_customized_behavior(hw); +} + +static void rtl8723ae_update_hal_rate_table(struct ieee80211_hw *hw, + struct ieee80211_sta *sta) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u32 ratr_value; + u8 ratr_index = 0; + u8 nmode = mac->ht_enable; + u8 mimo_ps = IEEE80211_SMPS_OFF; + u8 curtxbw_40mhz = mac->bw_40; + u8 curshortgi_40mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_40) ? + 1 : 0; + u8 curshortgi_20mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_20) ? + 1 : 0; + enum wireless_mode wirelessmode = mac->mode; + + if (rtlhal->current_bandtype == BAND_ON_5G) + ratr_value = sta->supp_rates[1] << 4; + else + ratr_value = sta->supp_rates[0]; + if (mac->opmode == NL80211_IFTYPE_ADHOC) + ratr_value = 0xfff; + ratr_value |= (sta->ht_cap.mcs.rx_mask[1] << 20 | + sta->ht_cap.mcs.rx_mask[0] << 12); + switch (wirelessmode) { + case WIRELESS_MODE_B: + if (ratr_value & 0x0000000c) + ratr_value &= 0x0000000d; + else + ratr_value &= 0x0000000f; + break; + case WIRELESS_MODE_G: + ratr_value &= 0x00000FF5; + break; + case WIRELESS_MODE_N_24G: + case WIRELESS_MODE_N_5G: + nmode = 1; + if (mimo_ps == IEEE80211_SMPS_STATIC) { + ratr_value &= 0x0007F005; + } else { + u32 ratr_mask; + + if (get_rf_type(rtlphy) == RF_1T2R || + get_rf_type(rtlphy) == RF_1T1R) + ratr_mask = 0x000ff005; + else + ratr_mask = 0x0f0ff005; + + ratr_value &= ratr_mask; + } + break; + default: + if (rtlphy->rf_type == RF_1T2R) + ratr_value &= 0x000ff0ff; + else + ratr_value &= 0x0f0ff0ff; + + break; + } + + if ((pcipriv->bt_coexist.bt_coexistence) && + (pcipriv->bt_coexist.bt_coexist_type == BT_CSR_BC4) && + (pcipriv->bt_coexist.bt_cur_state) && + (pcipriv->bt_coexist.bt_ant_isolation) && + ((pcipriv->bt_coexist.bt_service == BT_SCO) || + (pcipriv->bt_coexist.bt_service == BT_BUSY))) + ratr_value &= 0x0fffcfc0; + else + ratr_value &= 0x0FFFFFFF; + + if (nmode && ((curtxbw_40mhz && curshortgi_40mhz) || + (!curtxbw_40mhz && curshortgi_20mhz))) + ratr_value |= 0x10000000; + + rtl_write_dword(rtlpriv, REG_ARFR0 + ratr_index * 4, ratr_value); + + RT_TRACE(rtlpriv, COMP_RATR, DBG_DMESG, + "%x\n", rtl_read_dword(rtlpriv, REG_ARFR0)); +} + +static void rtl8723ae_update_hal_rate_mask(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 rssi_level) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_sta_info *sta_entry = NULL; + u32 ratr_bitmap; + u8 ratr_index; + u8 curtxbw_40mhz = (sta->bandwidth >= IEEE80211_STA_RX_BW_40) ? 1 : 0; + u8 curshortgi_40mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_40) ? + 1 : 0; + u8 curshortgi_20mhz = (sta->ht_cap.cap & IEEE80211_HT_CAP_SGI_20) ? + 1 : 0; + enum wireless_mode wirelessmode = 0; + bool shortgi = false; + u8 rate_mask[5]; + u8 macid = 0; + u8 mimo_ps = IEEE80211_SMPS_OFF; + + sta_entry = (struct rtl_sta_info *) sta->drv_priv; + wirelessmode = sta_entry->wireless_mode; + if (mac->opmode == NL80211_IFTYPE_STATION) + curtxbw_40mhz = mac->bw_40; + else if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_ADHOC) + macid = sta->aid + 1; + + if (rtlhal->current_bandtype == BAND_ON_5G) + ratr_bitmap = sta->supp_rates[1] << 4; + else + ratr_bitmap = sta->supp_rates[0]; + if (mac->opmode == NL80211_IFTYPE_ADHOC) + ratr_bitmap = 0xfff; + ratr_bitmap |= (sta->ht_cap.mcs.rx_mask[1] << 20 | + sta->ht_cap.mcs.rx_mask[0] << 12); + switch (wirelessmode) { + case WIRELESS_MODE_B: + ratr_index = RATR_INX_WIRELESS_B; + if (ratr_bitmap & 0x0000000c) + ratr_bitmap &= 0x0000000d; + else + ratr_bitmap &= 0x0000000f; + break; + case WIRELESS_MODE_G: + ratr_index = RATR_INX_WIRELESS_GB; + + if (rssi_level == 1) + ratr_bitmap &= 0x00000f00; + else if (rssi_level == 2) + ratr_bitmap &= 0x00000ff0; + else + ratr_bitmap &= 0x00000ff5; + break; + case WIRELESS_MODE_A: + ratr_index = RATR_INX_WIRELESS_A; + ratr_bitmap &= 0x00000ff0; + break; + case WIRELESS_MODE_N_24G: + case WIRELESS_MODE_N_5G: + ratr_index = RATR_INX_WIRELESS_NGB; + + if (mimo_ps == IEEE80211_SMPS_STATIC) { + if (rssi_level == 1) + ratr_bitmap &= 0x00070000; + else if (rssi_level == 2) + ratr_bitmap &= 0x0007f000; + else + ratr_bitmap &= 0x0007f005; + } else { + if (rtlphy->rf_type == RF_1T2R || + rtlphy->rf_type == RF_1T1R) { + if (curtxbw_40mhz) { + if (rssi_level == 1) + ratr_bitmap &= 0x000f0000; + else if (rssi_level == 2) + ratr_bitmap &= 0x000ff000; + else + ratr_bitmap &= 0x000ff015; + } else { + if (rssi_level == 1) + ratr_bitmap &= 0x000f0000; + else if (rssi_level == 2) + ratr_bitmap &= 0x000ff000; + else + ratr_bitmap &= 0x000ff005; + } + } else { + if (curtxbw_40mhz) { + if (rssi_level == 1) + ratr_bitmap &= 0x0f0f0000; + else if (rssi_level == 2) + ratr_bitmap &= 0x0f0ff000; + else + ratr_bitmap &= 0x0f0ff015; + } else { + if (rssi_level == 1) + ratr_bitmap &= 0x0f0f0000; + else if (rssi_level == 2) + ratr_bitmap &= 0x0f0ff000; + else + ratr_bitmap &= 0x0f0ff005; + } + } + } + + if ((curtxbw_40mhz && curshortgi_40mhz) || + (!curtxbw_40mhz && curshortgi_20mhz)) { + if (macid == 0) + shortgi = true; + else if (macid == 1) + shortgi = false; + } + break; + default: + ratr_index = RATR_INX_WIRELESS_NGB; + + if (rtlphy->rf_type == RF_1T2R) + ratr_bitmap &= 0x000ff0ff; + else + ratr_bitmap &= 0x0f0ff0ff; + break; + } + sta_entry->ratr_index = ratr_index; + + RT_TRACE(rtlpriv, COMP_RATR, DBG_DMESG, + "ratr_bitmap :%x\n", ratr_bitmap); + /* convert ratr_bitmap to le byte array */ + rate_mask[0] = ratr_bitmap; + rate_mask[1] = (ratr_bitmap >>= 8); + rate_mask[2] = (ratr_bitmap >>= 8); + rate_mask[3] = ((ratr_bitmap >> 8) & 0x0f) | (ratr_index << 4); + rate_mask[4] = macid | (shortgi ? 0x20 : 0x00) | 0x80; + RT_TRACE(rtlpriv, COMP_RATR, DBG_DMESG, + "Rate_index:%x, ratr_bitmap: %*phC\n", + ratr_index, 5, rate_mask); + rtl8723ae_fill_h2c_cmd(hw, H2C_RA_MASK, 5, rate_mask); +} + +void rtl8723ae_update_hal_rate_tbl(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 rssi_level) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (rtlpriv->dm.useramask) + rtl8723ae_update_hal_rate_mask(hw, sta, rssi_level); + else + rtl8723ae_update_hal_rate_table(hw, sta); +} + +void rtl8723ae_update_channel_access_setting(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u16 sifs_timer; + + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SLOT_TIME, + (u8 *)&mac->slot_time); + if (!mac->ht_enable) + sifs_timer = 0x0a0a; + else + sifs_timer = 0x1010; + rtlpriv->cfg->ops->set_hw_reg(hw, HW_VAR_SIFS, (u8 *)&sifs_timer); +} + +bool rtl8723ae_gpio_radio_on_off_checking(struct ieee80211_hw *hw, u8 *valid) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + enum rf_pwrstate e_rfpowerstate_toset; + u8 u1tmp; + bool actuallyset = false; + + if (rtlpriv->rtlhal.being_init_adapter) + return false; + + if (ppsc->swrf_processing) + return false; + + spin_lock(&rtlpriv->locks.rf_ps_lock); + if (ppsc->rfchange_inprogress) { + spin_unlock(&rtlpriv->locks.rf_ps_lock); + return false; + } else { + ppsc->rfchange_inprogress = true; + spin_unlock(&rtlpriv->locks.rf_ps_lock); + } + + rtl_write_byte(rtlpriv, REG_GPIO_IO_SEL_2, + rtl_read_byte(rtlpriv, REG_GPIO_IO_SEL_2)&~(BIT(1))); + + u1tmp = rtl_read_byte(rtlpriv, REG_GPIO_PIN_CTRL_2); + + if (rtlphy->polarity_ctl) + e_rfpowerstate_toset = (u1tmp & BIT(1)) ? ERFOFF : ERFON; + else + e_rfpowerstate_toset = (u1tmp & BIT(1)) ? ERFON : ERFOFF; + + if ((ppsc->hwradiooff == true) && (e_rfpowerstate_toset == ERFON)) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "GPIOChangeRF - HW Radio ON, RF ON\n"); + + e_rfpowerstate_toset = ERFON; + ppsc->hwradiooff = false; + actuallyset = true; + } else if ((ppsc->hwradiooff == false) + && (e_rfpowerstate_toset == ERFOFF)) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "GPIOChangeRF - HW Radio OFF, RF OFF\n"); + + e_rfpowerstate_toset = ERFOFF; + ppsc->hwradiooff = true; + actuallyset = true; + } + + if (actuallyset) { + spin_lock(&rtlpriv->locks.rf_ps_lock); + ppsc->rfchange_inprogress = false; + spin_unlock(&rtlpriv->locks.rf_ps_lock); + } else { + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_HALT_NIC) + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + + spin_lock(&rtlpriv->locks.rf_ps_lock); + ppsc->rfchange_inprogress = false; + spin_unlock(&rtlpriv->locks.rf_ps_lock); + } + + *valid = 1; + return !ppsc->hwradiooff; +} + +void rtl8723ae_set_key(struct ieee80211_hw *hw, u32 key_index, + u8 *p_macaddr, bool is_group, u8 enc_algo, + bool is_wepkey, bool clear_all) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 *macaddr = p_macaddr; + u32 entry_id = 0; + bool is_pairwise = false; + static u8 cam_const_addr[4][6] = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x03} + }; + static u8 cam_const_broad[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + if (clear_all) { + u8 idx = 0; + u8 cam_offset = 0; + u8 clear_number = 5; + + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, "clear_all\n"); + + for (idx = 0; idx < clear_number; idx++) { + rtl_cam_mark_invalid(hw, cam_offset + idx); + rtl_cam_empty_entry(hw, cam_offset + idx); + + if (idx < 5) { + memset(rtlpriv->sec.key_buf[idx], 0, + MAX_KEY_LEN); + rtlpriv->sec.key_len[idx] = 0; + } + } + } else { + switch (enc_algo) { + case WEP40_ENCRYPTION: + enc_algo = CAM_WEP40; + break; + case WEP104_ENCRYPTION: + enc_algo = CAM_WEP104; + break; + case TKIP_ENCRYPTION: + enc_algo = CAM_TKIP; + break; + case AESCCMP_ENCRYPTION: + enc_algo = CAM_AES; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + enc_algo = CAM_TKIP; + break; + } + + if (is_wepkey || rtlpriv->sec.use_defaultkey) { + macaddr = cam_const_addr[key_index]; + entry_id = key_index; + } else { + if (is_group) { + macaddr = cam_const_broad; + entry_id = key_index; + } else { + if (mac->opmode == NL80211_IFTYPE_AP) { + entry_id = rtl_cam_get_free_entry(hw, + macaddr); + if (entry_id >= TOTAL_CAM_ENTRY) { + RT_TRACE(rtlpriv, COMP_SEC, + DBG_EMERG, + "Can not find free hw security cam entry\n"); + return; + } + } else { + entry_id = CAM_PAIRWISE_KEY_POSITION; + } + + key_index = PAIRWISE_KEYIDX; + is_pairwise = true; + } + } + + if (rtlpriv->sec.key_len[key_index] == 0) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "delete one entry, entry_id is %d\n", + entry_id); + if (mac->opmode == NL80211_IFTYPE_AP) + rtl_cam_del_entry(hw, p_macaddr); + rtl_cam_delete_one_entry(hw, p_macaddr, entry_id); + } else { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "add one entry\n"); + if (is_pairwise) { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "set Pairwiase key\n"); + + rtl_cam_add_one_entry(hw, macaddr, key_index, + entry_id, enc_algo, + CAM_CONFIG_NO_USEDK, + rtlpriv->sec.key_buf[key_index]); + } else { + RT_TRACE(rtlpriv, COMP_SEC, DBG_DMESG, + "set group key\n"); + + if (mac->opmode == NL80211_IFTYPE_ADHOC) { + rtl_cam_add_one_entry(hw, + rtlefuse->dev_addr, + PAIRWISE_KEYIDX, + CAM_PAIRWISE_KEY_POSITION, + enc_algo, + CAM_CONFIG_NO_USEDK, + rtlpriv->sec.key_buf + [entry_id]); + } + + rtl_cam_add_one_entry(hw, macaddr, key_index, + entry_id, enc_algo, + CAM_CONFIG_NO_USEDK, + rtlpriv->sec.key_buf[entry_id]); + } + + } + } +} + +static void rtl8723ae_bt_var_init(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + + pcipriv->bt_coexist.bt_coexistence = + pcipriv->bt_coexist.eeprom_bt_coexist; + pcipriv->bt_coexist.bt_ant_num = + pcipriv->bt_coexist.eeprom_bt_ant_num; + pcipriv->bt_coexist.bt_coexist_type = + pcipriv->bt_coexist.eeprom_bt_type; + + pcipriv->bt_coexist.bt_ant_isolation = + pcipriv->bt_coexist.eeprom_bt_ant_isol; + + pcipriv->bt_coexist.bt_radio_shared_type = + pcipriv->bt_coexist.eeprom_bt_radio_shared; + + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "BT Coexistance = 0x%x\n", + pcipriv->bt_coexist.bt_coexistence); + + if (pcipriv->bt_coexist.bt_coexistence) { + pcipriv->bt_coexist.bt_busy_traffic = false; + pcipriv->bt_coexist.bt_traffic_mode_set = false; + pcipriv->bt_coexist.bt_non_traffic_mode_set = false; + + pcipriv->bt_coexist.cstate = 0; + pcipriv->bt_coexist.previous_state = 0; + + if (pcipriv->bt_coexist.bt_ant_num == ANT_X2) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "BlueTooth BT_Ant_Num = Antx2\n"); + } else if (pcipriv->bt_coexist.bt_ant_num == ANT_X1) { + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "BlueTooth BT_Ant_Num = Antx1\n"); + } + + switch (pcipriv->bt_coexist.bt_coexist_type) { + case BT_2WIRE: + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "BlueTooth BT_CoexistType = BT_2Wire\n"); + break; + case BT_ISSC_3WIRE: + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "BlueTooth BT_CoexistType = BT_ISSC_3Wire\n"); + break; + case BT_ACCEL: + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "BlueTooth BT_CoexistType = BT_ACCEL\n"); + break; + case BT_CSR_BC4: + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "BlueTooth BT_CoexistType = BT_CSR_BC4\n"); + break; + case BT_CSR_BC8: + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "BlueTooth BT_CoexistType = BT_CSR_BC8\n"); + break; + case BT_RTL8756: + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "BlueTooth BT_CoexistType = BT_RTL8756\n"); + break; + default: + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "BlueTooth BT_CoexistType = Unknown\n"); + break; + } + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "BlueTooth BT_Ant_isolation = %d\n", + pcipriv->bt_coexist.bt_ant_isolation); + RT_TRACE(rtlpriv, COMP_BT_COEXIST, DBG_TRACE, + "BT_RadioSharedType = 0x%x\n", + pcipriv->bt_coexist.bt_radio_shared_type); + pcipriv->bt_coexist.bt_active_zero_cnt = 0; + pcipriv->bt_coexist.cur_bt_disabled = false; + pcipriv->bt_coexist.pre_bt_disabled = false; + } +} + +void rtl8723ae_read_bt_coexist_info_from_hwpg(struct ieee80211_hw *hw, + bool auto_load_fail, u8 *hwinfo) +{ + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 value; + u32 tmpu_32; + + if (!auto_load_fail) { + tmpu_32 = rtl_read_dword(rtlpriv, REG_MULTI_FUNC_CTRL); + if (tmpu_32 & BIT(18)) + pcipriv->bt_coexist.eeprom_bt_coexist = 1; + else + pcipriv->bt_coexist.eeprom_bt_coexist = 0; + value = hwinfo[RF_OPTION4]; + pcipriv->bt_coexist.eeprom_bt_type = BT_RTL8723A; + pcipriv->bt_coexist.eeprom_bt_ant_num = (value & 0x1); + pcipriv->bt_coexist.eeprom_bt_ant_isol = ((value & 0x10) >> 4); + pcipriv->bt_coexist.eeprom_bt_radio_shared = + ((value & 0x20) >> 5); + } else { + pcipriv->bt_coexist.eeprom_bt_coexist = 0; + pcipriv->bt_coexist.eeprom_bt_type = BT_RTL8723A; + pcipriv->bt_coexist.eeprom_bt_ant_num = ANT_X2; + pcipriv->bt_coexist.eeprom_bt_ant_isol = 0; + pcipriv->bt_coexist.eeprom_bt_radio_shared = BT_RADIO_SHARED; + } + + rtl8723ae_bt_var_init(hw); +} + +void rtl8723ae_bt_reg_init(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + + /* 0:Low, 1:High, 2:From Efuse. */ + pcipriv->bt_coexist.reg_bt_iso = 2; + /* 0:Idle, 1:None-SCO, 2:SCO, 3:From Counter. */ + pcipriv->bt_coexist.reg_bt_sco = 3; + /* 0:Disable BT control A-MPDU, 1:Enable BT control A-MPDU. */ + pcipriv->bt_coexist.reg_bt_sco = 0; +} + + +void rtl8723ae_bt_hw_init(struct ieee80211_hw *hw) +{ +} + +void rtl8723ae_suspend(struct ieee80211_hw *hw) +{ +} + +void rtl8723ae_resume(struct ieee80211_hw *hw) +{ +} + +/* Turn on AAP (RCR:bit 0) for promicuous mode. */ +void rtl8723ae_allow_all_destaddr(struct ieee80211_hw *hw, + bool allow_all_da, bool write_into_reg) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + if (allow_all_da) /* Set BIT0 */ + rtlpci->receive_config |= RCR_AAP; + else /* Clear BIT0 */ + rtlpci->receive_config &= ~RCR_AAP; + + if (write_into_reg) + rtl_write_dword(rtlpriv, REG_RCR, rtlpci->receive_config); + + + RT_TRACE(rtlpriv, COMP_TURBO | COMP_INIT, DBG_LOUD, + "receive_config=0x%08X, write_into_reg=%d\n", + rtlpci->receive_config, write_into_reg); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/hw.h b/drivers/net/wireless/rtlwifi/rtl8723ae/hw.h new file mode 100644 index 0000000..6fa24f7 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/hw.h @@ -0,0 +1,73 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL8723E_HW_H__ +#define __RTL8723E_HW_H__ + +#define CHK_SVID_SMID(_val1, _val2) \ + ((rtlefuse->eeprom_svid == (_val1)) && \ + (rtlefuse->eeprom_smid == (_val2))) + +void rtl8723ae_get_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val); +void rtl8723ae_read_eeprom_info(struct ieee80211_hw *hw); + +void rtl8723ae_interrupt_recognized(struct ieee80211_hw *hw, + u32 *p_inta, u32 *p_intb); +int rtl8723ae_hw_init(struct ieee80211_hw *hw); +void rtl8723ae_card_disable(struct ieee80211_hw *hw); +void rtl8723ae_enable_interrupt(struct ieee80211_hw *hw); +void rtl8723ae_disable_interrupt(struct ieee80211_hw *hw); +int rtl8723ae_set_network_type(struct ieee80211_hw *hw, + enum nl80211_iftype type); +void rtl8723ae_set_check_bssid(struct ieee80211_hw *hw, bool check_bssid); +void rtl8723ae_set_qos(struct ieee80211_hw *hw, int aci); +void rtl8723ae_set_beacon_related_registers(struct ieee80211_hw *hw); +void rtl8723ae_set_beacon_interval(struct ieee80211_hw *hw); +void rtl8723ae_update_interrupt_mask(struct ieee80211_hw *hw, + u32 add_msr, u32 rm_msr); +void rtl8723ae_set_hw_reg(struct ieee80211_hw *hw, u8 variable, u8 *val); +void rtl8723ae_update_hal_rate_tbl(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 rssi_level); +void rtl8723ae_update_channel_access_setting(struct ieee80211_hw *hw); +bool rtl8723ae_gpio_radio_on_off_checking(struct ieee80211_hw *hw, u8 *valid); +void rtl8723ae_enable_hw_security_config(struct ieee80211_hw *hw); +void rtl8723ae_set_key(struct ieee80211_hw *hw, u32 key_index, + u8 *p_macaddr, bool is_group, u8 enc_algo, + bool is_wepkey, bool clear_all); + +void rtl8723ae_read_bt_coexist_info_from_hwpg(struct ieee80211_hw *hw, + bool autoload_fail, u8 *hwinfo); +void rtl8723ae_bt_reg_init(struct ieee80211_hw *hw); +void rtl8723ae_bt_hw_init(struct ieee80211_hw *hw); +void rtl8723ae_suspend(struct ieee80211_hw *hw); +void rtl8723ae_resume(struct ieee80211_hw *hw); +void rtl8723ae_allow_all_destaddr(struct ieee80211_hw *hw, + bool allow_all_da, bool write_into_reg); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/led.c b/drivers/net/wireless/rtlwifi/rtl8723ae/led.c new file mode 100644 index 0000000..061526f --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/led.c @@ -0,0 +1,157 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "reg.h" +#include "led.h" + +static void _rtl8723ae_init_led(struct ieee80211_hw *hw, + struct rtl_led *pled, enum rtl_led_pin ledpin) +{ + pled->hw = hw; + pled->ledpin = ledpin; + pled->ledon = false; +} + +void rtl8723ae_sw_led_on(struct ieee80211_hw *hw, struct rtl_led *pled) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 ledcfg; + + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, + "LedAddr:%X ledpin=%d\n", REG_LEDCFG2, pled->ledpin); + + ledcfg = rtl_read_byte(rtlpriv, REG_LEDCFG2); + + switch (pled->ledpin) { + case LED_PIN_GPIO0: + break; + case LED_PIN_LED0: + ledcfg &= ~BIT(6); + rtl_write_byte(rtlpriv, + REG_LEDCFG2, (ledcfg & 0xf0) | BIT(5)); + break; + case LED_PIN_LED1: + rtl_write_byte(rtlpriv, REG_LEDCFG2, (ledcfg & 0x0f) | BIT(5)); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + pled->ledon = true; +} + +void rtl8723ae_sw_led_off(struct ieee80211_hw *hw, struct rtl_led *pled) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + u8 ledcfg; + + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, + "LedAddr:%X ledpin=%d\n", REG_LEDCFG2, pled->ledpin); + + ledcfg = rtl_read_byte(rtlpriv, REG_LEDCFG2); + + switch (pled->ledpin) { + case LED_PIN_GPIO0: + break; + case LED_PIN_LED0: + ledcfg &= 0xf0; + if (pcipriv->ledctl.led_opendrain) { + ledcfg &= 0x90; + rtl_write_byte(rtlpriv, REG_LEDCFG2, (ledcfg|BIT(3))); + ledcfg = rtl_read_byte(rtlpriv, REG_MAC_PINMUX_CFG); + ledcfg &= 0xFE; + rtl_write_byte(rtlpriv, REG_MAC_PINMUX_CFG, ledcfg); + } else { + ledcfg &= ~BIT(6); + rtl_write_byte(rtlpriv, REG_LEDCFG2, + (ledcfg | BIT(3) | BIT(5))); + } + break; + case LED_PIN_LED1: + ledcfg = rtl_read_byte(rtlpriv, REG_LEDCFG1) & 0x10; + rtl_write_byte(rtlpriv, REG_LEDCFG1, (ledcfg | BIT(3))); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + break; + } + pled->ledon = false; +} + +void rtl8723ae_init_sw_leds(struct ieee80211_hw *hw) +{ + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + + _rtl8723ae_init_led(hw, &(pcipriv->ledctl.sw_led0), LED_PIN_LED0); + _rtl8723ae_init_led(hw, &(pcipriv->ledctl.sw_led1), LED_PIN_LED1); +} + +static void _rtl8723ae_sw_led_control(struct ieee80211_hw *hw, + enum led_ctl_mode ledaction) +{ + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_led *pLed0 = &(pcipriv->ledctl.sw_led0); + + switch (ledaction) { + case LED_CTL_POWER_ON: + case LED_CTL_LINK: + case LED_CTL_NO_LINK: + rtl8723ae_sw_led_on(hw, pLed0); + break; + case LED_CTL_POWER_OFF: + rtl8723ae_sw_led_off(hw, pLed0); + break; + default: + break; + } +} + +void rtl8723ae_led_control(struct ieee80211_hw *hw, enum led_ctl_mode ledaction) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + + if ((ppsc->rfoff_reason > RF_CHANGE_BY_PS) && + (ledaction == LED_CTL_TX || + ledaction == LED_CTL_RX || + ledaction == LED_CTL_SITE_SURVEY || + ledaction == LED_CTL_LINK || + ledaction == LED_CTL_NO_LINK || + ledaction == LED_CTL_START_TO_LINK || + ledaction == LED_CTL_POWER_ON)) { + return; + } + RT_TRACE(rtlpriv, COMP_LED, DBG_LOUD, "ledaction %d,\n", ledaction); + _rtl8723ae_sw_led_control(hw, ledaction); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/led.h b/drivers/net/wireless/rtlwifi/rtl8723ae/led.h new file mode 100644 index 0000000..2cb88e7 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/led.h @@ -0,0 +1,39 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92CE_LED_H__ +#define __RTL92CE_LED_H__ + +void rtl8723ae_init_sw_leds(struct ieee80211_hw *hw); +void rtl8723ae_sw_led_on(struct ieee80211_hw *hw, struct rtl_led *pled); +void rtl8723ae_sw_led_off(struct ieee80211_hw *hw, struct rtl_led *pled); +void rtl8723ae_led_control(struct ieee80211_hw *hw, + enum led_ctl_mode ledaction); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/phy.c b/drivers/net/wireless/rtlwifi/rtl8723ae/phy.c new file mode 100644 index 0000000..eafbb18 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/phy.c @@ -0,0 +1,2028 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "../ps.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "rf.h" +#include "dm.h" +#include "table.h" + +/* static forward definitions */ +static u32 _phy_fw_rf_serial_read(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset); +static void _phy_fw_rf_serial_write(struct ieee80211_hw *hw, + enum radio_path rfpath, + u32 offset, u32 data); +static u32 _phy_rf_serial_read(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset); +static void _phy_rf_serial_write(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset, u32 data); +static u32 _phy_calculate_bit_shift(u32 bitmask); +static bool _phy_bb8192c_config_parafile(struct ieee80211_hw *hw); +static bool _phy_cfg_mac_w_header(struct ieee80211_hw *hw); +static bool _phy_cfg_bb_w_header(struct ieee80211_hw *hw, u8 configtype); +static bool _phy_cfg_bb_w_pgheader(struct ieee80211_hw *hw, u8 configtype); +static void _phy_init_bb_rf_reg_def(struct ieee80211_hw *hw); +static bool _phy_set_sw_chnl_cmdarray(struct swchnlcmd *cmdtable, + u32 cmdtableidx, u32 cmdtablesz, + enum swchnlcmd_id cmdid, + u32 para1, u32 para2, + u32 msdelay); +static bool _phy_sw_chnl_step_by_step(struct ieee80211_hw *hw, u8 channel, + u8 *stage, u8 *step, u32 *delay); +static u8 _phy_dbm_to_txpwr_Idx(struct ieee80211_hw *hw, + enum wireless_mode wirelessmode, + long power_indbm); +static long _phy_txpwr_idx_to_dbm(struct ieee80211_hw *hw, + enum wireless_mode wirelessmode, u8 txpwridx); +static void rtl8723ae_phy_set_io(struct ieee80211_hw *hw); + +u32 rtl8723ae_phy_query_bb_reg(struct ieee80211_hw *hw, u32 regaddr, + u32 bitmask) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 returnvalue, originalvalue, bitshift; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x)\n", regaddr, bitmask); + originalvalue = rtl_read_dword(rtlpriv, regaddr); + bitshift = _phy_calculate_bit_shift(bitmask); + returnvalue = (originalvalue & bitmask) >> bitshift; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "BBR MASK=0x%x Addr[0x%x]=0x%x\n", bitmask, regaddr, + originalvalue); + + return returnvalue; +} + +void rtl8723ae_phy_set_bb_reg(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 originalvalue, bitshift; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x)\n", regaddr, + bitmask, data); + + if (bitmask != MASKDWORD) { + originalvalue = rtl_read_dword(rtlpriv, regaddr); + bitshift = _phy_calculate_bit_shift(bitmask); + data = ((originalvalue & (~bitmask)) | (data << bitshift)); + } + + rtl_write_dword(rtlpriv, regaddr, data); + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x)\n", + regaddr, bitmask, data); +} + +u32 rtl8723ae_phy_query_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, u32 bitmask) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 original_value, readback_value, bitshift; + struct rtl_phy *rtlphy = &(rtlpriv->phy); + unsigned long flags; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), rfpath(%#x), bitmask(%#x)\n", + regaddr, rfpath, bitmask); + + spin_lock_irqsave(&rtlpriv->locks.rf_lock, flags); + + if (rtlphy->rf_mode != RF_OP_BY_FW) + original_value = _phy_rf_serial_read(hw, rfpath, regaddr); + else + original_value = _phy_fw_rf_serial_read(hw, rfpath, regaddr); + + bitshift = _phy_calculate_bit_shift(bitmask); + readback_value = (original_value & bitmask) >> bitshift; + + spin_unlock_irqrestore(&rtlpriv->locks.rf_lock, flags); + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), rfpath(%#x), bitmask(%#x), original_value(%#x)\n", + regaddr, rfpath, bitmask, original_value); + + return readback_value; +} + +void rtl8723ae_phy_set_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, + u32 regaddr, u32 bitmask, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 original_value, bitshift; + unsigned long flags; + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x), rfpath(%#x)\n", + regaddr, bitmask, data, rfpath); + + spin_lock_irqsave(&rtlpriv->locks.rf_lock, flags); + + if (rtlphy->rf_mode != RF_OP_BY_FW) { + if (bitmask != RFREG_OFFSET_MASK) { + original_value = _phy_rf_serial_read(hw, rfpath, + regaddr); + bitshift = _phy_calculate_bit_shift(bitmask); + data = ((original_value & (~bitmask)) | + (data << bitshift)); + } + + _phy_rf_serial_write(hw, rfpath, regaddr, data); + } else { + if (bitmask != RFREG_OFFSET_MASK) { + original_value = _phy_fw_rf_serial_read(hw, rfpath, + regaddr); + bitshift = _phy_calculate_bit_shift(bitmask); + data = ((original_value & (~bitmask)) | + (data << bitshift)); + } + _phy_fw_rf_serial_write(hw, rfpath, regaddr, data); + } + + spin_unlock_irqrestore(&rtlpriv->locks.rf_lock, flags); + + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, + "regaddr(%#x), bitmask(%#x), data(%#x), rfpath(%#x)\n", + regaddr, bitmask, data, rfpath); +} + +static u32 _phy_fw_rf_serial_read(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset) +{ + RT_ASSERT(false, "deprecated!\n"); + return 0; +} + +static void _phy_fw_rf_serial_write(struct ieee80211_hw *hw, + enum radio_path rfpath, + u32 offset, u32 data) +{ + RT_ASSERT(false, "deprecated!\n"); +} + +static u32 _phy_rf_serial_read(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct bb_reg_def *pphyreg = &rtlphy->phyreg_def[rfpath]; + u32 newoffset; + u32 tmplong, tmplong2; + u8 rfpi_enable = 0; + u32 retvalue; + + offset &= 0x3f; + newoffset = offset; + if (RT_CANNOT_IO(hw)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "return all one\n"); + return 0xFFFFFFFF; + } + tmplong = rtl_get_bbreg(hw, RFPGA0_XA_HSSIPARAMETER2, MASKDWORD); + if (rfpath == RF90_PATH_A) + tmplong2 = tmplong; + else + tmplong2 = rtl_get_bbreg(hw, pphyreg->rfhssi_para2, MASKDWORD); + tmplong2 = (tmplong2 & (~BLSSIREADADDRESS)) | + (newoffset << 23) | BLSSIREADEDGE; + rtl_set_bbreg(hw, RFPGA0_XA_HSSIPARAMETER2, MASKDWORD, + tmplong & (~BLSSIREADEDGE)); + mdelay(1); + rtl_set_bbreg(hw, pphyreg->rfhssi_para2, MASKDWORD, tmplong2); + mdelay(1); + rtl_set_bbreg(hw, RFPGA0_XA_HSSIPARAMETER2, MASKDWORD, + tmplong | BLSSIREADEDGE); + mdelay(1); + if (rfpath == RF90_PATH_A) + rfpi_enable = (u8) rtl_get_bbreg(hw, RFPGA0_XA_HSSIPARAMETER1, + BIT(8)); + else if (rfpath == RF90_PATH_B) + rfpi_enable = (u8) rtl_get_bbreg(hw, RFPGA0_XB_HSSIPARAMETER1, + BIT(8)); + if (rfpi_enable) + retvalue = rtl_get_bbreg(hw, pphyreg->rf_rbpi, + BLSSIREADBACKDATA); + else + retvalue = rtl_get_bbreg(hw, pphyreg->rf_rb, + BLSSIREADBACKDATA); + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, "RFR-%d Addr[0x%x]=0x%x\n", + rfpath, pphyreg->rf_rb, retvalue); + return retvalue; +} + +static void _phy_rf_serial_write(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 offset, u32 data) +{ + u32 data_and_addr; + u32 newoffset; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct bb_reg_def *pphyreg = &rtlphy->phyreg_def[rfpath]; + + if (RT_CANNOT_IO(hw)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "stop\n"); + return; + } + offset &= 0x3f; + newoffset = offset; + data_and_addr = ((newoffset << 20) | (data & 0x000fffff)) & 0x0fffffff; + rtl_set_bbreg(hw, pphyreg->rf3wire_offset, MASKDWORD, data_and_addr); + RT_TRACE(rtlpriv, COMP_RF, DBG_TRACE, "RFW-%d Addr[0x%x]=0x%x\n", + rfpath, pphyreg->rf3wire_offset, data_and_addr); +} + +static u32 _phy_calculate_bit_shift(u32 bitmask) +{ + u32 i; + + for (i = 0; i <= 31; i++) { + if (((bitmask >> i) & 0x1) == 1) + break; + } + return i; +} + +static void _rtl8723ae_phy_bb_config_1t(struct ieee80211_hw *hw) +{ + rtl_set_bbreg(hw, RFPGA0_TXINFO, 0x3, 0x2); + rtl_set_bbreg(hw, RFPGA1_TXINFO, 0x300033, 0x200022); + rtl_set_bbreg(hw, RCCK0_AFESETTING, MASKBYTE3, 0x45); + rtl_set_bbreg(hw, ROFDM0_TRXPATHENABLE, MASKBYTE0, 0x23); + rtl_set_bbreg(hw, ROFDM0_AGCPARAMETER1, 0x30, 0x1); + rtl_set_bbreg(hw, 0xe74, 0x0c000000, 0x2); + rtl_set_bbreg(hw, 0xe78, 0x0c000000, 0x2); + rtl_set_bbreg(hw, 0xe7c, 0x0c000000, 0x2); + rtl_set_bbreg(hw, 0xe80, 0x0c000000, 0x2); + rtl_set_bbreg(hw, 0xe88, 0x0c000000, 0x2); +} + +bool rtl8723ae_phy_mac_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + bool rtstatus = _phy_cfg_mac_w_header(hw); + rtl_write_byte(rtlpriv, 0x04CA, 0x0A); + return rtstatus; +} + +bool rtl8723ae_phy_bb_config(struct ieee80211_hw *hw) +{ + bool rtstatus = true; + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tmpu1b; + u8 reg_hwparafile = 1; + + _phy_init_bb_rf_reg_def(hw); + + /* 1. 0x28[1] = 1 */ + tmpu1b = rtl_read_byte(rtlpriv, REG_AFE_PLL_CTRL); + udelay(2); + rtl_write_byte(rtlpriv, REG_AFE_PLL_CTRL, (tmpu1b|BIT(1))); + udelay(2); + /* 2. 0x29[7:0] = 0xFF */ + rtl_write_byte(rtlpriv, REG_AFE_PLL_CTRL+1, 0xff); + udelay(2); + + /* 3. 0x02[1:0] = 2b'11 */ + tmpu1b = rtl_read_byte(rtlpriv, REG_SYS_FUNC_EN); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, (tmpu1b | + FEN_BB_GLB_RSTn | FEN_BBRSTB)); + + /* 4. 0x25[6] = 0 */ + tmpu1b = rtl_read_byte(rtlpriv, REG_AFE_XTAL_CTRL+1); + rtl_write_byte(rtlpriv, REG_AFE_XTAL_CTRL+1, (tmpu1b&(~BIT(6)))); + + /* 5. 0x24[20] = 0 Advised by SD3 Alex Wang. 2011.02.09. */ + tmpu1b = rtl_read_byte(rtlpriv, REG_AFE_XTAL_CTRL+2); + rtl_write_byte(rtlpriv, REG_AFE_XTAL_CTRL+2, (tmpu1b&(~BIT(4)))); + + /* 6. 0x1f[7:0] = 0x07 */ + rtl_write_byte(rtlpriv, REG_RF_CTRL, 0x07); + + if (reg_hwparafile == 1) + rtstatus = _phy_bb8192c_config_parafile(hw); + return rtstatus; +} + +bool rtl8723ae_phy_rf_config(struct ieee80211_hw *hw) +{ + return rtl8723ae_phy_rf6052_config(hw); +} + +static bool _phy_bb8192c_config_parafile(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + bool rtstatus; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "==>\n"); + rtstatus = _phy_cfg_bb_w_header(hw, BASEBAND_CONFIG_PHY_REG); + if (rtstatus != true) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Write BB Reg Fail!!"); + return false; + } + + if (rtlphy->rf_type == RF_1T2R) { + _rtl8723ae_phy_bb_config_1t(hw); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "Config to 1T!!\n"); + } + if (rtlefuse->autoload_failflag == false) { + rtlphy->pwrgroup_cnt = 0; + rtstatus = _phy_cfg_bb_w_pgheader(hw, BASEBAND_CONFIG_PHY_REG); + } + if (rtstatus != true) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "BB_PG Reg Fail!!"); + return false; + } + rtstatus = _phy_cfg_bb_w_header(hw, BASEBAND_CONFIG_AGC_TAB); + if (rtstatus != true) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "AGC Table Fail\n"); + return false; + } + rtlphy->cck_high_power = (bool) (rtl_get_bbreg(hw, + RFPGA0_XA_HSSIPARAMETER2, 0x200)); + return true; +} + +static bool _phy_cfg_mac_w_header(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + u32 arraylength; + u32 *ptrarray; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "Read Rtl723MACPHY_Array\n"); + arraylength = RTL8723E_MACARRAYLENGTH; + ptrarray = RTL8723EMAC_ARRAY; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Img:RTL8192CEMAC_2T_ARRAY\n"); + for (i = 0; i < arraylength; i = i + 2) + rtl_write_byte(rtlpriv, ptrarray[i], (u8) ptrarray[i + 1]); + return true; +} + +static bool _phy_cfg_bb_w_header(struct ieee80211_hw *hw, u8 configtype) +{ + int i; + u32 *phy_regarray_table; + u32 *agctab_array_table; + u16 phy_reg_arraylen, agctab_arraylen; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + agctab_arraylen = RTL8723E_AGCTAB_1TARRAYLENGTH; + agctab_array_table = RTL8723EAGCTAB_1TARRAY; + phy_reg_arraylen = RTL8723E_PHY_REG_1TARRAY_LENGTH; + phy_regarray_table = RTL8723EPHY_REG_1TARRAY; + if (configtype == BASEBAND_CONFIG_PHY_REG) { + for (i = 0; i < phy_reg_arraylen; i = i + 2) { + if (phy_regarray_table[i] == 0xfe) + mdelay(50); + else if (phy_regarray_table[i] == 0xfd) + mdelay(5); + else if (phy_regarray_table[i] == 0xfc) + mdelay(1); + else if (phy_regarray_table[i] == 0xfb) + udelay(50); + else if (phy_regarray_table[i] == 0xfa) + udelay(5); + else if (phy_regarray_table[i] == 0xf9) + udelay(1); + rtl_set_bbreg(hw, phy_regarray_table[i], MASKDWORD, + phy_regarray_table[i + 1]); + udelay(1); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "The phy_regarray_table[0] is %x" + " Rtl819XPHY_REGArray[1] is %x\n", + phy_regarray_table[i], + phy_regarray_table[i + 1]); + } + } else if (configtype == BASEBAND_CONFIG_AGC_TAB) { + for (i = 0; i < agctab_arraylen; i = i + 2) { + rtl_set_bbreg(hw, agctab_array_table[i], MASKDWORD, + agctab_array_table[i + 1]); + udelay(1); + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "The agctab_array_table[0] is " + "%x Rtl819XPHY_REGArray[1] is %x\n", + agctab_array_table[i], + agctab_array_table[i + 1]); + } + } + return true; +} + +static void _st_pwrIdx_dfrate_off(struct ieee80211_hw *hw, u32 regaddr, + u32 bitmask, u32 data) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + switch (regaddr) { + case RTXAGC_A_RATE18_06: + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][0] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][0] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][0]); + break; + case RTXAGC_A_RATE54_24: + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][1] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][1] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][1]); + break; + case RTXAGC_A_CCK1_MCS32: + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][6] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][6] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][6]); + break; + case RTXAGC_B_CCK11_A_CCK2_11: + if (bitmask == 0xffffff00) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][7] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][7] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][7]); + } + if (bitmask == 0x000000ff) { + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][15] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][15] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][15]); + } + break; + case RTXAGC_A_MCS03_MCS00: + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][2] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][2] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][2]); + break; + case RTXAGC_A_MCS07_MCS04: + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][3] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][3] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][3]); + break; + case RTXAGC_A_MCS11_MCS08: + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][4] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][4] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][4]); + break; + case RTXAGC_A_MCS15_MCS12: + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][5] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][5] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][5]); + break; + case RTXAGC_B_RATE18_06: + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][8] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][8] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][8]); + break; + case RTXAGC_B_RATE54_24: + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][9] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][9] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][9]); + break; + case RTXAGC_B_CCK1_55_MCS32: + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][14] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][14] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][14]); + break; + case RTXAGC_B_MCS03_MCS00: + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][10] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][10] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][10]); + break; + case RTXAGC_B_MCS07_MCS04: + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][11] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][11] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][11]); + break; + case RTXAGC_B_MCS11_MCS08: + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][12] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][12] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][12]); + break; + case RTXAGC_B_MCS15_MCS12: + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][13] = data; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "MCSTxPowerLevelOriginalOffset[%d][13] = 0x%x\n", + rtlphy->pwrgroup_cnt, + rtlphy->mcs_offset[rtlphy->pwrgroup_cnt][13]); + rtlphy->pwrgroup_cnt++; + break; + } +} + +static bool _phy_cfg_bb_w_pgheader(struct ieee80211_hw *hw, u8 configtype) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int i; + u32 *phy_regarray_table_pg; + u16 phy_regarray_pg_len; + + phy_regarray_pg_len = RTL8723E_PHY_REG_ARRAY_PGLENGTH; + phy_regarray_table_pg = RTL8723EPHY_REG_ARRAY_PG; + + if (configtype == BASEBAND_CONFIG_PHY_REG) { + for (i = 0; i < phy_regarray_pg_len; i = i + 3) { + if (phy_regarray_table_pg[i] == 0xfe) + mdelay(50); + else if (phy_regarray_table_pg[i] == 0xfd) + mdelay(5); + else if (phy_regarray_table_pg[i] == 0xfc) + mdelay(1); + else if (phy_regarray_table_pg[i] == 0xfb) + udelay(50); + else if (phy_regarray_table_pg[i] == 0xfa) + udelay(5); + else if (phy_regarray_table_pg[i] == 0xf9) + udelay(1); + + _st_pwrIdx_dfrate_off(hw, phy_regarray_table_pg[i], + phy_regarray_table_pg[i + 1], + phy_regarray_table_pg[i + 2]); + } + } else { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "configtype != BaseBand_Config_PHY_REG\n"); + } + return true; +} + +bool rtl8723ae_phy_config_rf_with_headerfile(struct ieee80211_hw *hw, + enum radio_path rfpath) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int i; + u32 *radioa_array_table; + u16 radioa_arraylen; + + radioa_arraylen = Rtl8723ERADIOA_1TARRAYLENGTH; + radioa_array_table = RTL8723E_RADIOA_1TARRAY; + + switch (rfpath) { + case RF90_PATH_A: + for (i = 0; i < radioa_arraylen; i = i + 2) { + if (radioa_array_table[i] == 0xfe) + mdelay(50); + else if (radioa_array_table[i] == 0xfd) + mdelay(5); + else if (radioa_array_table[i] == 0xfc) + mdelay(1); + else if (radioa_array_table[i] == 0xfb) + udelay(50); + else if (radioa_array_table[i] == 0xfa) + udelay(5); + else if (radioa_array_table[i] == 0xf9) + udelay(1); + else { + rtl_set_rfreg(hw, rfpath, radioa_array_table[i], + RFREG_OFFSET_MASK, + radioa_array_table[i + 1]); + udelay(1); + } + } + break; + case RF90_PATH_B: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not process\n"); + break; + case RF90_PATH_C: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not process\n"); + break; + case RF90_PATH_D: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not process\n"); + break; + } + return true; +} + +void rtl8723ae_phy_get_hw_reg_originalvalue(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + rtlphy->default_initialgain[0] = + (u8) rtl_get_bbreg(hw, ROFDM0_XAAGCCORE1, MASKBYTE0); + rtlphy->default_initialgain[1] = + (u8) rtl_get_bbreg(hw, ROFDM0_XBAGCCORE1, MASKBYTE0); + rtlphy->default_initialgain[2] = + (u8) rtl_get_bbreg(hw, ROFDM0_XCAGCCORE1, MASKBYTE0); + rtlphy->default_initialgain[3] = + (u8) rtl_get_bbreg(hw, ROFDM0_XDAGCCORE1, MASKBYTE0); + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Default initial gain (c50=0x%x, c58=0x%x, c60=0x%x, c68=0x%x\n", + rtlphy->default_initialgain[0], + rtlphy->default_initialgain[1], + rtlphy->default_initialgain[2], + rtlphy->default_initialgain[3]); + + rtlphy->framesync = (u8) rtl_get_bbreg(hw, + ROFDM0_RXDETECTOR3, MASKBYTE0); + rtlphy->framesync_c34 = rtl_get_bbreg(hw, + ROFDM0_RXDETECTOR2, MASKDWORD); + + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Default framesync (0x%x) = 0x%x\n", + ROFDM0_RXDETECTOR3, rtlphy->framesync); +} + +static void _phy_init_bb_rf_reg_def(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + rtlphy->phyreg_def[RF90_PATH_A].rfintfs = RFPGA0_XAB_RFINTERFACESW; + rtlphy->phyreg_def[RF90_PATH_B].rfintfs = RFPGA0_XAB_RFINTERFACESW; + rtlphy->phyreg_def[RF90_PATH_C].rfintfs = RFPGA0_XCD_RFINTERFACESW; + rtlphy->phyreg_def[RF90_PATH_D].rfintfs = RFPGA0_XCD_RFINTERFACESW; + + rtlphy->phyreg_def[RF90_PATH_A].rfintfi = RFPGA0_XAB_RFINTERFACERB; + rtlphy->phyreg_def[RF90_PATH_B].rfintfi = RFPGA0_XAB_RFINTERFACERB; + rtlphy->phyreg_def[RF90_PATH_C].rfintfi = RFPGA0_XCD_RFINTERFACERB; + rtlphy->phyreg_def[RF90_PATH_D].rfintfi = RFPGA0_XCD_RFINTERFACERB; + + rtlphy->phyreg_def[RF90_PATH_A].rfintfo = RFPGA0_XA_RFINTERFACEOE; + rtlphy->phyreg_def[RF90_PATH_B].rfintfo = RFPGA0_XB_RFINTERFACEOE; + + rtlphy->phyreg_def[RF90_PATH_A].rfintfe = RFPGA0_XA_RFINTERFACEOE; + rtlphy->phyreg_def[RF90_PATH_B].rfintfe = RFPGA0_XB_RFINTERFACEOE; + + rtlphy->phyreg_def[RF90_PATH_A].rf3wire_offset = + RFPGA0_XA_LSSIPARAMETER; + rtlphy->phyreg_def[RF90_PATH_B].rf3wire_offset = + RFPGA0_XB_LSSIPARAMETER; + + rtlphy->phyreg_def[RF90_PATH_A].rflssi_select = rFPGA0_XAB_RFPARAMETER; + rtlphy->phyreg_def[RF90_PATH_B].rflssi_select = rFPGA0_XAB_RFPARAMETER; + rtlphy->phyreg_def[RF90_PATH_C].rflssi_select = rFPGA0_XCD_RFPARAMETER; + rtlphy->phyreg_def[RF90_PATH_D].rflssi_select = rFPGA0_XCD_RFPARAMETER; + + rtlphy->phyreg_def[RF90_PATH_A].rftxgain_stage = RFPGA0_TXGAINSTAGE; + rtlphy->phyreg_def[RF90_PATH_B].rftxgain_stage = RFPGA0_TXGAINSTAGE; + rtlphy->phyreg_def[RF90_PATH_C].rftxgain_stage = RFPGA0_TXGAINSTAGE; + rtlphy->phyreg_def[RF90_PATH_D].rftxgain_stage = RFPGA0_TXGAINSTAGE; + + rtlphy->phyreg_def[RF90_PATH_A].rfhssi_para1 = RFPGA0_XA_HSSIPARAMETER1; + rtlphy->phyreg_def[RF90_PATH_B].rfhssi_para1 = RFPGA0_XB_HSSIPARAMETER1; + + rtlphy->phyreg_def[RF90_PATH_A].rfhssi_para2 = RFPGA0_XA_HSSIPARAMETER2; + rtlphy->phyreg_def[RF90_PATH_B].rfhssi_para2 = RFPGA0_XB_HSSIPARAMETER2; + + rtlphy->phyreg_def[RF90_PATH_A].rfsw_ctrl = RFPGA0_XAB_SWITCHCONTROL; + rtlphy->phyreg_def[RF90_PATH_B].rfsw_ctrl = RFPGA0_XAB_SWITCHCONTROL; + rtlphy->phyreg_def[RF90_PATH_C].rfsw_ctrl = RFPGA0_XCD_SWITCHCONTROL; + rtlphy->phyreg_def[RF90_PATH_D].rfsw_ctrl = RFPGA0_XCD_SWITCHCONTROL; + + rtlphy->phyreg_def[RF90_PATH_A].rfagc_control1 = ROFDM0_XAAGCCORE1; + rtlphy->phyreg_def[RF90_PATH_B].rfagc_control1 = ROFDM0_XBAGCCORE1; + rtlphy->phyreg_def[RF90_PATH_C].rfagc_control1 = ROFDM0_XCAGCCORE1; + rtlphy->phyreg_def[RF90_PATH_D].rfagc_control1 = ROFDM0_XDAGCCORE1; + + rtlphy->phyreg_def[RF90_PATH_A].rfagc_control2 = ROFDM0_XAAGCCORE2; + rtlphy->phyreg_def[RF90_PATH_B].rfagc_control2 = ROFDM0_XBAGCCORE2; + rtlphy->phyreg_def[RF90_PATH_C].rfagc_control2 = ROFDM0_XCAGCCORE2; + rtlphy->phyreg_def[RF90_PATH_D].rfagc_control2 = ROFDM0_XDAGCCORE2; + + rtlphy->phyreg_def[RF90_PATH_A].rfrxiq_imbal = ROFDM0_XARXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_B].rfrxiq_imbal = ROFDM0_XBRXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_C].rfrxiq_imbal = ROFDM0_XCRXIQIMBANLANCE; + rtlphy->phyreg_def[RF90_PATH_D].rfrxiq_imbal = ROFDM0_XDRXIQIMBALANCE; + + rtlphy->phyreg_def[RF90_PATH_A].rfrx_afe = ROFDM0_XARXAFE; + rtlphy->phyreg_def[RF90_PATH_B].rfrx_afe = ROFDM0_XBRXAFE; + rtlphy->phyreg_def[RF90_PATH_C].rfrx_afe = ROFDM0_XCRXAFE; + rtlphy->phyreg_def[RF90_PATH_D].rfrx_afe = ROFDM0_XDRXAFE; + + rtlphy->phyreg_def[RF90_PATH_A].rftxiq_imbal = ROFDM0_XATXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_B].rftxiq_imbal = ROFDM0_XBTXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_C].rftxiq_imbal = ROFDM0_XCTXIQIMBALANCE; + rtlphy->phyreg_def[RF90_PATH_D].rftxiq_imbal = ROFDM0_XDTXIQIMBALANCE; + + rtlphy->phyreg_def[RF90_PATH_A].rftx_afe = ROFDM0_XATXAFE; + rtlphy->phyreg_def[RF90_PATH_B].rftx_afe = ROFDM0_XBTXAFE; + rtlphy->phyreg_def[RF90_PATH_C].rftx_afe = ROFDM0_XCTXAFE; + rtlphy->phyreg_def[RF90_PATH_D].rftx_afe = ROFDM0_XDTXAFE; + + rtlphy->phyreg_def[RF90_PATH_A].rf_rb = RFPGA0_XA_LSSIREADBACK; + rtlphy->phyreg_def[RF90_PATH_B].rf_rb = RFPGA0_XB_LSSIREADBACK; + rtlphy->phyreg_def[RF90_PATH_C].rf_rb = RFPGA0_XC_LSSIREADBACK; + rtlphy->phyreg_def[RF90_PATH_D].rf_rb = RFPGA0_XD_LSSIREADBACK; + + rtlphy->phyreg_def[RF90_PATH_A].rf_rbpi = TRANSCEIVEA_HSPI_READBACK; + rtlphy->phyreg_def[RF90_PATH_B].rf_rbpi = TRANSCEIVEB_HSPI_READBACK; +} + +void rtl8723ae_phy_get_txpower_level(struct ieee80211_hw *hw, long *powerlevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 txpwr_level; + long txpwr_dbm; + + txpwr_level = rtlphy->cur_cck_txpwridx; + txpwr_dbm = _phy_txpwr_idx_to_dbm(hw, WIRELESS_MODE_B, txpwr_level); + txpwr_level = rtlphy->cur_ofdm24g_txpwridx + + rtlefuse->legacy_ht_txpowerdiff; + if (_phy_txpwr_idx_to_dbm(hw, WIRELESS_MODE_G, txpwr_level) > txpwr_dbm) + txpwr_dbm = _phy_txpwr_idx_to_dbm(hw, WIRELESS_MODE_G, + txpwr_level); + txpwr_level = rtlphy->cur_ofdm24g_txpwridx; + if (_phy_txpwr_idx_to_dbm(hw, WIRELESS_MODE_N_24G, txpwr_level) > + txpwr_dbm) + txpwr_dbm = _phy_txpwr_idx_to_dbm(hw, WIRELESS_MODE_N_24G, + txpwr_level); + *powerlevel = txpwr_dbm; +} + +static void _rtl8723ae_get_txpower_index(struct ieee80211_hw *hw, u8 channel, + u8 *cckpowerlevel, u8 *ofdmpowerlevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 index = (channel - 1); + + cckpowerlevel[RF90_PATH_A] = + rtlefuse->txpwrlevel_cck[RF90_PATH_A][index]; + cckpowerlevel[RF90_PATH_B] = + rtlefuse->txpwrlevel_cck[RF90_PATH_B][index]; + if (get_rf_type(rtlphy) == RF_1T2R || get_rf_type(rtlphy) == RF_1T1R) { + ofdmpowerlevel[RF90_PATH_A] = + rtlefuse->txpwrlevel_ht40_1s[RF90_PATH_A][index]; + ofdmpowerlevel[RF90_PATH_B] = + rtlefuse->txpwrlevel_ht40_1s[RF90_PATH_B][index]; + } else if (get_rf_type(rtlphy) == RF_2T2R) { + ofdmpowerlevel[RF90_PATH_A] = + rtlefuse->txpwrlevel_ht40_2s[RF90_PATH_A][index]; + ofdmpowerlevel[RF90_PATH_B] = + rtlefuse->txpwrlevel_ht40_2s[RF90_PATH_B][index]; + } +} + +static void _rtl8723ae_ccxpower_index_check(struct ieee80211_hw *hw, + u8 channel, u8 *cckpowerlevel, + u8 *ofdmpowerlevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + rtlphy->cur_cck_txpwridx = cckpowerlevel[0]; + rtlphy->cur_ofdm24g_txpwridx = ofdmpowerlevel[0]; +} + +void rtl8723ae_phy_set_txpower_level(struct ieee80211_hw *hw, u8 channel) +{ + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 cckpowerlevel[2], ofdmpowerlevel[2]; + + if (rtlefuse->txpwr_fromeprom == false) + return; + _rtl8723ae_get_txpower_index(hw, channel, &cckpowerlevel[0], + &ofdmpowerlevel[0]); + _rtl8723ae_ccxpower_index_check(hw, channel, &cckpowerlevel[0], + &ofdmpowerlevel[0]); + rtl8723ae_phy_rf6052_set_cck_txpower(hw, &cckpowerlevel[0]); + rtl8723ae_phy_rf6052_set_ofdm_txpower(hw, &ofdmpowerlevel[0], channel); +} + +bool rtl8723ae_phy_update_txpower_dbm(struct ieee80211_hw *hw, long power_indbm) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 idx; + u8 rf_path; + u8 ccktxpwridx = _phy_dbm_to_txpwr_Idx(hw, WIRELESS_MODE_B, + power_indbm); + u8 ofdmtxpwridx = _phy_dbm_to_txpwr_Idx(hw, WIRELESS_MODE_N_24G, + power_indbm); + if (ofdmtxpwridx - rtlefuse->legacy_ht_txpowerdiff > 0) + ofdmtxpwridx -= rtlefuse->legacy_ht_txpowerdiff; + else + ofdmtxpwridx = 0; + RT_TRACE(rtlpriv, COMP_TXAGC, DBG_TRACE, + "%lx dBm, ccktxpwridx = %d, ofdmtxpwridx = %d\n", + power_indbm, ccktxpwridx, ofdmtxpwridx); + for (idx = 0; idx < 14; idx++) { + for (rf_path = 0; rf_path < 2; rf_path++) { + rtlefuse->txpwrlevel_cck[rf_path][idx] = ccktxpwridx; + rtlefuse->txpwrlevel_ht40_1s[rf_path][idx] = + ofdmtxpwridx; + rtlefuse->txpwrlevel_ht40_2s[rf_path][idx] = + ofdmtxpwridx; + } + } + rtl8723ae_phy_set_txpower_level(hw, rtlphy->current_channel); + return true; +} + +static u8 _phy_dbm_to_txpwr_Idx(struct ieee80211_hw *hw, + enum wireless_mode wirelessmode, + long power_indbm) +{ + u8 txpwridx; + long offset; + + switch (wirelessmode) { + case WIRELESS_MODE_B: + offset = -7; + break; + case WIRELESS_MODE_G: + case WIRELESS_MODE_N_24G: + offset = -8; + break; + default: + offset = -8; + break; + } + + if ((power_indbm - offset) > 0) + txpwridx = (u8) ((power_indbm - offset) * 2); + else + txpwridx = 0; + + if (txpwridx > MAX_TXPWR_IDX_NMODE_92S) + txpwridx = MAX_TXPWR_IDX_NMODE_92S; + + return txpwridx; +} + +static long _phy_txpwr_idx_to_dbm(struct ieee80211_hw *hw, + enum wireless_mode wirelessmode, u8 txpwridx) +{ + long offset; + long pwrout_dbm; + + switch (wirelessmode) { + case WIRELESS_MODE_B: + offset = -7; + break; + case WIRELESS_MODE_G: + case WIRELESS_MODE_N_24G: + offset = -8; + break; + default: + offset = -8; + break; + } + pwrout_dbm = txpwridx / 2 + offset; + return pwrout_dbm; +} + +void rtl8723ae_phy_scan_operation_backup(struct ieee80211_hw *hw, u8 operation) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + enum io_type iotype; + + if (!is_hal_stop(rtlhal)) { + switch (operation) { + case SCAN_OPT_BACKUP: + iotype = IO_CMD_PAUSE_DM_BY_SCAN; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_IO_CMD, + (u8 *)&iotype); + + break; + case SCAN_OPT_RESTORE: + iotype = IO_CMD_RESUME_DM_BY_SCAN; + rtlpriv->cfg->ops->set_hw_reg(hw, + HW_VAR_IO_CMD, + (u8 *)&iotype); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Unknown Scan Backup operation.\n"); + break; + } + } +} + +void rtl8723ae_phy_set_bw_mode_callback(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + u8 reg_bw_opmode; + u8 reg_prsr_rsc; + + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, + "Switch to %s bandwidth\n", + rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20 ? + "20MHz" : "40MHz"); + + if (is_hal_stop(rtlhal)) { + rtlphy->set_bwmode_inprogress = false; + return; + } + + reg_bw_opmode = rtl_read_byte(rtlpriv, REG_BWOPMODE); + reg_prsr_rsc = rtl_read_byte(rtlpriv, REG_RRSR + 2); + + switch (rtlphy->current_chan_bw) { + case HT_CHANNEL_WIDTH_20: + reg_bw_opmode |= BW_OPMODE_20MHZ; + rtl_write_byte(rtlpriv, REG_BWOPMODE, reg_bw_opmode); + break; + case HT_CHANNEL_WIDTH_20_40: + reg_bw_opmode &= ~BW_OPMODE_20MHZ; + rtl_write_byte(rtlpriv, REG_BWOPMODE, reg_bw_opmode); + reg_prsr_rsc = + (reg_prsr_rsc & 0x90) | (mac->cur_40_prime_sc << 5); + rtl_write_byte(rtlpriv, REG_RRSR + 2, reg_prsr_rsc); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", rtlphy->current_chan_bw); + break; + } + + switch (rtlphy->current_chan_bw) { + case HT_CHANNEL_WIDTH_20: + rtl_set_bbreg(hw, RFPGA0_RFMOD, BRFMOD, 0x0); + rtl_set_bbreg(hw, RFPGA1_RFMOD, BRFMOD, 0x0); + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER2, BIT(10), 1); + break; + case HT_CHANNEL_WIDTH_20_40: + rtl_set_bbreg(hw, RFPGA0_RFMOD, BRFMOD, 0x1); + rtl_set_bbreg(hw, RFPGA1_RFMOD, BRFMOD, 0x1); + + rtl_set_bbreg(hw, RCCK0_SYSTEM, BCCK_SIDEBAND, + (mac->cur_40_prime_sc >> 1)); + rtl_set_bbreg(hw, ROFDM1_LSTF, 0xC00, mac->cur_40_prime_sc); + rtl_set_bbreg(hw, RFPGA0_ANALOGPARAMETER2, BIT(10), 0); + + rtl_set_bbreg(hw, 0x818, (BIT(26) | BIT(27)), + (mac->cur_40_prime_sc == + HAL_PRIME_CHNL_OFFSET_LOWER) ? 2 : 1); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", rtlphy->current_chan_bw); + break; + } + rtl8723ae_phy_rf6052_set_bandwidth(hw, rtlphy->current_chan_bw); + rtlphy->set_bwmode_inprogress = false; + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, "<==\n"); +} + +void rtl8723ae_phy_set_bw_mode(struct ieee80211_hw *hw, + enum nl80211_channel_type ch_type) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + u8 tmp_bw = rtlphy->current_chan_bw; + + if (rtlphy->set_bwmode_inprogress) + return; + rtlphy->set_bwmode_inprogress = true; + if ((!is_hal_stop(rtlhal)) && !(RT_CANNOT_IO(hw))) { + rtl8723ae_phy_set_bw_mode_callback(hw); + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "FALSE driver sleep or unload\n"); + rtlphy->set_bwmode_inprogress = false; + rtlphy->current_chan_bw = tmp_bw; + } +} + +void rtl8723ae_phy_sw_chnl_callback(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 delay; + + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, + "switch to channel%d\n", rtlphy->current_channel); + if (is_hal_stop(rtlhal)) + return; + do { + if (!rtlphy->sw_chnl_inprogress) + break; + if (!_phy_sw_chnl_step_by_step + (hw, rtlphy->current_channel, &rtlphy->sw_chnl_stage, + &rtlphy->sw_chnl_step, &delay)) { + if (delay > 0) + mdelay(delay); + else + continue; + } else { + rtlphy->sw_chnl_inprogress = false; + } + break; + } while (true); + RT_TRACE(rtlpriv, COMP_SCAN, DBG_TRACE, "<==\n"); +} + +u8 rtl8723ae_phy_sw_chnl(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (rtlphy->sw_chnl_inprogress) + return 0; + if (rtlphy->set_bwmode_inprogress) + return 0; + RT_ASSERT((rtlphy->current_channel <= 14), + "WIRELESS_MODE_G but channel>14"); + rtlphy->sw_chnl_inprogress = true; + rtlphy->sw_chnl_stage = 0; + rtlphy->sw_chnl_step = 0; + if (!(is_hal_stop(rtlhal)) && !(RT_CANNOT_IO(hw))) { + rtl8723ae_phy_sw_chnl_callback(hw); + RT_TRACE(rtlpriv, COMP_CHAN, DBG_LOUD, + "sw_chnl_inprogress false schedule workitem\n"); + rtlphy->sw_chnl_inprogress = false; + } else { + RT_TRACE(rtlpriv, COMP_CHAN, DBG_LOUD, + "sw_chnl_inprogress false driver sleep or unload\n"); + rtlphy->sw_chnl_inprogress = false; + } + return 1; +} + +static void _rtl8723ae_phy_sw_rf_seting(struct ieee80211_hw *hw, u8 channel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (IS_81xxC_VENDOR_UMC_B_CUT(rtlhal->version)) { + if (channel == 6 && rtlphy->current_chan_bw == + HT_CHANNEL_WIDTH_20) + rtl_set_rfreg(hw, RF90_PATH_A, RF_RX_G1, MASKDWORD, + 0x00255); + else{ + u32 backupRF0x1A = (u32)rtl_get_rfreg(hw, RF90_PATH_A, + RF_RX_G1, RFREG_OFFSET_MASK); + rtl_set_rfreg(hw, RF90_PATH_A, RF_RX_G1, MASKDWORD, + backupRF0x1A); + } + } +} + +static bool _phy_sw_chnl_step_by_step(struct ieee80211_hw *hw, u8 channel, + u8 *stage, u8 *step, u32 *delay) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct swchnlcmd precommoncmd[MAX_PRECMD_CNT]; + u32 precommoncmdcnt; + struct swchnlcmd postcommoncmd[MAX_POSTCMD_CNT]; + u32 postcommoncmdcnt; + struct swchnlcmd rfdependcmd[MAX_RFDEPENDCMD_CNT]; + u32 rfdependcmdcnt; + struct swchnlcmd *currentcmd = NULL; + u8 rfpath; + u8 num_total_rfpath = rtlphy->num_total_rfpath; + + precommoncmdcnt = 0; + _phy_set_sw_chnl_cmdarray(precommoncmd, precommoncmdcnt++, + MAX_PRECMD_CNT, CMDID_SET_TXPOWEROWER_LEVEL, + 0, 0, 0); + _phy_set_sw_chnl_cmdarray(precommoncmd, precommoncmdcnt++, + MAX_PRECMD_CNT, CMDID_END, 0, 0, 0); + postcommoncmdcnt = 0; + + _phy_set_sw_chnl_cmdarray(postcommoncmd, postcommoncmdcnt++, + MAX_POSTCMD_CNT, CMDID_END, 0, 0, 0); + rfdependcmdcnt = 0; + + RT_ASSERT((channel >= 1 && channel <= 14), + "illegal channel for Zebra: %d\n", channel); + + _phy_set_sw_chnl_cmdarray(rfdependcmd, rfdependcmdcnt++, + MAX_RFDEPENDCMD_CNT, CMDID_RF_WRITEREG, + RF_CHNLBW, channel, 10); + + _phy_set_sw_chnl_cmdarray(rfdependcmd, rfdependcmdcnt++, + MAX_RFDEPENDCMD_CNT, CMDID_END, 0, 0, 0); + + do { + switch (*stage) { + case 0: + currentcmd = &precommoncmd[*step]; + break; + case 1: + currentcmd = &rfdependcmd[*step]; + break; + case 2: + currentcmd = &postcommoncmd[*step]; + break; + } + + if (currentcmd->cmdid == CMDID_END) { + if ((*stage) == 2) { + return true; + } else { + (*stage)++; + (*step) = 0; + continue; + } + } + + switch (currentcmd->cmdid) { + case CMDID_SET_TXPOWEROWER_LEVEL: + rtl8723ae_phy_set_txpower_level(hw, channel); + break; + case CMDID_WRITEPORT_ULONG: + rtl_write_dword(rtlpriv, currentcmd->para1, + currentcmd->para2); + break; + case CMDID_WRITEPORT_USHORT: + rtl_write_word(rtlpriv, currentcmd->para1, + (u16) currentcmd->para2); + break; + case CMDID_WRITEPORT_UCHAR: + rtl_write_byte(rtlpriv, currentcmd->para1, + (u8) currentcmd->para2); + break; + case CMDID_RF_WRITEREG: + for (rfpath = 0; rfpath < num_total_rfpath; rfpath++) { + rtlphy->rfreg_chnlval[rfpath] = + ((rtlphy->rfreg_chnlval[rfpath] & + 0xfffffc00) | currentcmd->para2); + + rtl_set_rfreg(hw, (enum radio_path)rfpath, + currentcmd->para1, + RFREG_OFFSET_MASK, + rtlphy->rfreg_chnlval[rfpath]); + } + _rtl8723ae_phy_sw_rf_seting(hw, channel); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not process\n"); + break; + } + + break; + } while (true); + + (*delay) = currentcmd->msdelay; + (*step)++; + return false; +} + +static bool _phy_set_sw_chnl_cmdarray(struct swchnlcmd *cmdtable, + u32 cmdtableidx, u32 cmdtablesz, + enum swchnlcmd_id cmdid, u32 para1, + u32 para2, u32 msdelay) +{ + struct swchnlcmd *pcmd; + + if (cmdtable == NULL) { + RT_ASSERT(false, "cmdtable cannot be NULL.\n"); + return false; + } + + if (cmdtableidx >= cmdtablesz) + return false; + + pcmd = cmdtable + cmdtableidx; + pcmd->cmdid = cmdid; + pcmd->para1 = para1; + pcmd->para2 = para2; + pcmd->msdelay = msdelay; + return true; +} + +static u8 _rtl8723ae_phy_path_a_iqk(struct ieee80211_hw *hw, bool config_pathb) +{ + u32 reg_eac, reg_e94, reg_e9c, reg_ea4; + u8 result = 0x00; + + rtl_set_bbreg(hw, 0xe30, MASKDWORD, 0x10008c1f); + rtl_set_bbreg(hw, 0xe34, MASKDWORD, 0x10008c1f); + rtl_set_bbreg(hw, 0xe38, MASKDWORD, 0x82140102); + rtl_set_bbreg(hw, 0xe3c, MASKDWORD, + config_pathb ? 0x28160202 : 0x28160502); + + if (config_pathb) { + rtl_set_bbreg(hw, 0xe50, MASKDWORD, 0x10008c22); + rtl_set_bbreg(hw, 0xe54, MASKDWORD, 0x10008c22); + rtl_set_bbreg(hw, 0xe58, MASKDWORD, 0x82140102); + rtl_set_bbreg(hw, 0xe5c, MASKDWORD, 0x28160202); + } + + rtl_set_bbreg(hw, 0xe4c, MASKDWORD, 0x001028d1); + rtl_set_bbreg(hw, 0xe48, MASKDWORD, 0xf9000000); + rtl_set_bbreg(hw, 0xe48, MASKDWORD, 0xf8000000); + + mdelay(IQK_DELAY_TIME); + + reg_eac = rtl_get_bbreg(hw, 0xeac, MASKDWORD); + reg_e94 = rtl_get_bbreg(hw, 0xe94, MASKDWORD); + reg_e9c = rtl_get_bbreg(hw, 0xe9c, MASKDWORD); + reg_ea4 = rtl_get_bbreg(hw, 0xea4, MASKDWORD); + + if (!(reg_eac & BIT(28)) && + (((reg_e94 & 0x03FF0000) >> 16) != 0x142) && + (((reg_e9c & 0x03FF0000) >> 16) != 0x42)) + result |= 0x01; + else + return result; + + if (!(reg_eac & BIT(27)) && + (((reg_ea4 & 0x03FF0000) >> 16) != 0x132) && + (((reg_eac & 0x03FF0000) >> 16) != 0x36)) + result |= 0x02; + return result; +} + +static u8 _rtl8723ae_phy_path_b_iqk(struct ieee80211_hw *hw) +{ + u32 reg_eac, reg_eb4, reg_ebc, reg_ec4, reg_ecc; + u8 result = 0x00; + + rtl_set_bbreg(hw, 0xe60, MASKDWORD, 0x00000002); + rtl_set_bbreg(hw, 0xe60, MASKDWORD, 0x00000000); + mdelay(IQK_DELAY_TIME); + reg_eac = rtl_get_bbreg(hw, 0xeac, MASKDWORD); + reg_eb4 = rtl_get_bbreg(hw, 0xeb4, MASKDWORD); + reg_ebc = rtl_get_bbreg(hw, 0xebc, MASKDWORD); + reg_ec4 = rtl_get_bbreg(hw, 0xec4, MASKDWORD); + reg_ecc = rtl_get_bbreg(hw, 0xecc, MASKDWORD); + + if (!(reg_eac & BIT(31)) && + (((reg_eb4 & 0x03FF0000) >> 16) != 0x142) && + (((reg_ebc & 0x03FF0000) >> 16) != 0x42)) + result |= 0x01; + else + return result; + if (!(reg_eac & BIT(30)) && + (((reg_ec4 & 0x03FF0000) >> 16) != 0x132) && + (((reg_ecc & 0x03FF0000) >> 16) != 0x36)) + result |= 0x02; + return result; +} + +static void phy_path_a_fill_iqk_matrix(struct ieee80211_hw *hw, bool iqk_ok, + long result[][8], u8 final_candidate, + bool btxonly) +{ + u32 oldval_0, x, tx0_a, reg; + long y, tx0_c; + + if (final_candidate == 0xFF) { + return; + } else if (iqk_ok) { + oldval_0 = (rtl_get_bbreg(hw, ROFDM0_XATXIQIMBALANCE, + MASKDWORD) >> 22) & 0x3FF; + x = result[final_candidate][0]; + if ((x & 0x00000200) != 0) + x = x | 0xFFFFFC00; + tx0_a = (x * oldval_0) >> 8; + rtl_set_bbreg(hw, ROFDM0_XATXIQIMBALANCE, 0x3FF, tx0_a); + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, BIT(31), + ((x * oldval_0 >> 7) & 0x1)); + y = result[final_candidate][1]; + if ((y & 0x00000200) != 0) + y = y | 0xFFFFFC00; + tx0_c = (y * oldval_0) >> 8; + rtl_set_bbreg(hw, ROFDM0_XCTXAFE, 0xF0000000, + ((tx0_c & 0x3C0) >> 6)); + rtl_set_bbreg(hw, ROFDM0_XATXIQIMBALANCE, 0x003F0000, + (tx0_c & 0x3F)); + rtl_set_bbreg(hw, ROFDM0_ECCATHRESHOLD, BIT(29), + ((y * oldval_0 >> 7) & 0x1)); + if (btxonly) + return; + reg = result[final_candidate][2]; + rtl_set_bbreg(hw, ROFDM0_XARXIQIMBALANCE, 0x3FF, reg); + reg = result[final_candidate][3] & 0x3F; + rtl_set_bbreg(hw, ROFDM0_XARXIQIMBALANCE, 0xFC00, reg); + reg = (result[final_candidate][3] >> 6) & 0xF; + rtl_set_bbreg(hw, 0xca0, 0xF0000000, reg); + } +} + +static void phy_save_adda_regs(struct ieee80211_hw *hw, + u32 *addareg, u32 *addabackup, + u32 registernum) +{ + u32 i; + + for (i = 0; i < registernum; i++) + addabackup[i] = rtl_get_bbreg(hw, addareg[i], MASKDWORD); +} + +static void phy_save_mac_regs(struct ieee80211_hw *hw, u32 *macreg, + u32 *macbackup) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + + for (i = 0; i < (IQK_MAC_REG_NUM - 1); i++) + macbackup[i] = rtl_read_byte(rtlpriv, macreg[i]); + macbackup[i] = rtl_read_dword(rtlpriv, macreg[i]); +} + +static void phy_reload_adda_regs(struct ieee80211_hw *hw, u32 *addareg, + u32 *addabackup, u32 regiesternum) +{ + u32 i; + + for (i = 0; i < regiesternum; i++) + rtl_set_bbreg(hw, addareg[i], MASKDWORD, addabackup[i]); +} + +static void phy_reload_mac_regs(struct ieee80211_hw *hw, u32 *macreg, + u32 *macbackup) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i; + + for (i = 0; i < (IQK_MAC_REG_NUM - 1); i++) + rtl_write_byte(rtlpriv, macreg[i], (u8) macbackup[i]); + rtl_write_dword(rtlpriv, macreg[i], macbackup[i]); +} + +static void _rtl8723ae_phy_path_adda_on(struct ieee80211_hw *hw, + u32 *addareg, bool is_patha_on, + bool is2t) +{ + u32 pathOn; + u32 i; + + pathOn = is_patha_on ? 0x04db25a4 : 0x0b1b25a4; + if (false == is2t) { + pathOn = 0x0bdb25a0; + rtl_set_bbreg(hw, addareg[0], MASKDWORD, 0x0b1b25a0); + } else { + rtl_set_bbreg(hw, addareg[0], MASKDWORD, pathOn); + } + + for (i = 1; i < IQK_ADDA_REG_NUM; i++) + rtl_set_bbreg(hw, addareg[i], MASKDWORD, pathOn); +} + +static void _rtl8723ae_phy_mac_setting_calibration(struct ieee80211_hw *hw, + u32 *macreg, u32 *macbackup) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 i = 0; + + rtl_write_byte(rtlpriv, macreg[i], 0x3F); + + for (i = 1; i < (IQK_MAC_REG_NUM - 1); i++) + rtl_write_byte(rtlpriv, macreg[i], + (u8) (macbackup[i] & (~BIT(3)))); + rtl_write_byte(rtlpriv, macreg[i], (u8) (macbackup[i] & (~BIT(5)))); +} + +static void _rtl8723ae_phy_path_a_standby(struct ieee80211_hw *hw) +{ + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0x0); + rtl_set_bbreg(hw, 0x840, MASKDWORD, 0x00010000); + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0x80800000); +} + +static void _rtl8723ae_phy_pi_mode_switch(struct ieee80211_hw *hw, bool pi_mode) +{ + u32 mode; + + mode = pi_mode ? 0x01000100 : 0x01000000; + rtl_set_bbreg(hw, 0x820, MASKDWORD, mode); + rtl_set_bbreg(hw, 0x828, MASKDWORD, mode); +} + +static bool phy_simularity_comp(struct ieee80211_hw *hw, long result[][8], + u8 c1, u8 c2) +{ + u32 i, j, diff, simularity_bitmap, bound; + + u8 final_candidate[2] = { 0xFF, 0xFF }; + bool bresult = true; + + bound = 4; + + simularity_bitmap = 0; + + for (i = 0; i < bound; i++) { + diff = (result[c1][i] > result[c2][i]) ? + (result[c1][i] - result[c2][i]) : + (result[c2][i] - result[c1][i]); + + if (diff > MAX_TOLERANCE) { + if ((i == 2 || i == 6) && !simularity_bitmap) { + if (result[c1][i] + result[c1][i + 1] == 0) + final_candidate[(i / 4)] = c2; + else if (result[c2][i] + result[c2][i + 1] == 0) + final_candidate[(i / 4)] = c1; + else + simularity_bitmap = simularity_bitmap | + (1 << i); + } else + simularity_bitmap = + simularity_bitmap | (1 << i); + } + } + + if (simularity_bitmap == 0) { + for (i = 0; i < (bound / 4); i++) { + if (final_candidate[i] != 0xFF) { + for (j = i * 4; j < (i + 1) * 4 - 2; j++) + result[3][j] = + result[final_candidate[i]][j]; + bresult = false; + } + } + return bresult; + } else if (!(simularity_bitmap & 0x0F)) { + for (i = 0; i < 4; i++) + result[3][i] = result[c1][i]; + return false; + } else { + return false; + } + +} + +static void _rtl8723ae_phy_iq_calibrate(struct ieee80211_hw *hw, + long result[][8], u8 t, bool is2t) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 i; + u8 patha_ok, pathb_ok; + u32 adda_reg[IQK_ADDA_REG_NUM] = { + 0x85c, 0xe6c, 0xe70, 0xe74, + 0xe78, 0xe7c, 0xe80, 0xe84, + 0xe88, 0xe8c, 0xed0, 0xed4, + 0xed8, 0xedc, 0xee0, 0xeec + }; + u32 iqk_mac_reg[IQK_MAC_REG_NUM] = { + 0x522, 0x550, 0x551, 0x040 + }; + const u32 retrycount = 2; + + if (t == 0) { + phy_save_adda_regs(hw, adda_reg, rtlphy->adda_backup, 16); + phy_save_mac_regs(hw, iqk_mac_reg, rtlphy->iqk_mac_backup); + } + _rtl8723ae_phy_path_adda_on(hw, adda_reg, true, is2t); + if (t == 0) { + rtlphy->rfpi_enable = (u8) rtl_get_bbreg(hw, + RFPGA0_XA_HSSIPARAMETER1, + BIT(8)); + } + + if (!rtlphy->rfpi_enable) + _rtl8723ae_phy_pi_mode_switch(hw, true); + if (t == 0) { + rtlphy->reg_c04 = rtl_get_bbreg(hw, 0xc04, MASKDWORD); + rtlphy->reg_c08 = rtl_get_bbreg(hw, 0xc08, MASKDWORD); + rtlphy->reg_874 = rtl_get_bbreg(hw, 0x874, MASKDWORD); + } + rtl_set_bbreg(hw, 0xc04, MASKDWORD, 0x03a05600); + rtl_set_bbreg(hw, 0xc08, MASKDWORD, 0x000800e4); + rtl_set_bbreg(hw, 0x874, MASKDWORD, 0x22204000); + if (is2t) { + rtl_set_bbreg(hw, 0x840, MASKDWORD, 0x00010000); + rtl_set_bbreg(hw, 0x844, MASKDWORD, 0x00010000); + } + _rtl8723ae_phy_mac_setting_calibration(hw, iqk_mac_reg, + rtlphy->iqk_mac_backup); + rtl_set_bbreg(hw, 0xb68, MASKDWORD, 0x00080000); + if (is2t) + rtl_set_bbreg(hw, 0xb6c, MASKDWORD, 0x00080000); + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0x80800000); + rtl_set_bbreg(hw, 0xe40, MASKDWORD, 0x01007c00); + rtl_set_bbreg(hw, 0xe44, MASKDWORD, 0x01004800); + for (i = 0; i < retrycount; i++) { + patha_ok = _rtl8723ae_phy_path_a_iqk(hw, is2t); + if (patha_ok == 0x03) { + result[t][0] = (rtl_get_bbreg(hw, 0xe94, MASKDWORD) & + 0x3FF0000) >> 16; + result[t][1] = (rtl_get_bbreg(hw, 0xe9c, MASKDWORD) & + 0x3FF0000) >> 16; + result[t][2] = (rtl_get_bbreg(hw, 0xea4, MASKDWORD) & + 0x3FF0000) >> 16; + result[t][3] = (rtl_get_bbreg(hw, 0xeac, MASKDWORD) & + 0x3FF0000) >> 16; + break; + } else if (i == (retrycount - 1) && patha_ok == 0x01) + + result[t][0] = (rtl_get_bbreg(hw, 0xe94, + MASKDWORD) & 0x3FF0000) >> 16; + result[t][1] = + (rtl_get_bbreg(hw, 0xe9c, MASKDWORD) & 0x3FF0000) >> 16; + + } + + if (is2t) { + _rtl8723ae_phy_path_a_standby(hw); + _rtl8723ae_phy_path_adda_on(hw, adda_reg, false, is2t); + for (i = 0; i < retrycount; i++) { + pathb_ok = _rtl8723ae_phy_path_b_iqk(hw); + if (pathb_ok == 0x03) { + result[t][4] = + (rtl_get_bbreg(hw, 0xeb4, MASKDWORD) & + 0x3FF0000) >> 16; + result[t][5] = + (rtl_get_bbreg(hw, 0xebc, MASKDWORD) & + 0x3FF0000) >> 16; + result[t][6] = + (rtl_get_bbreg(hw, 0xec4, MASKDWORD) & + 0x3FF0000) >> 16; + result[t][7] = + (rtl_get_bbreg(hw, 0xecc, MASKDWORD) & + 0x3FF0000) >> 16; + break; + } else if (i == (retrycount - 1) && pathb_ok == 0x01) { + result[t][4] = + (rtl_get_bbreg(hw, 0xeb4, MASKDWORD) & + 0x3FF0000) >> 16; + } + result[t][5] = (rtl_get_bbreg(hw, 0xebc, MASKDWORD) & + 0x3FF0000) >> 16; + } + } + rtl_set_bbreg(hw, 0xc04, MASKDWORD, rtlphy->reg_c04); + rtl_set_bbreg(hw, 0x874, MASKDWORD, rtlphy->reg_874); + rtl_set_bbreg(hw, 0xc08, MASKDWORD, rtlphy->reg_c08); + rtl_set_bbreg(hw, 0xe28, MASKDWORD, 0); + rtl_set_bbreg(hw, 0x840, MASKDWORD, 0x00032ed3); + if (is2t) + rtl_set_bbreg(hw, 0x844, MASKDWORD, 0x00032ed3); + if (t != 0) { + if (!rtlphy->rfpi_enable) + _rtl8723ae_phy_pi_mode_switch(hw, false); + phy_reload_adda_regs(hw, adda_reg, rtlphy->adda_backup, 16); + phy_reload_mac_regs(hw, iqk_mac_reg, rtlphy->iqk_mac_backup); + } +} + +static void _rtl8723ae_phy_lc_calibrate(struct ieee80211_hw *hw, bool is2t) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 tmpreg; + u32 rf_a_mode = 0, rf_b_mode = 0, lc_cal; + + tmpreg = rtl_read_byte(rtlpriv, 0xd03); + + if ((tmpreg & 0x70) != 0) + rtl_write_byte(rtlpriv, 0xd03, tmpreg & 0x8F); + else + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0xFF); + + if ((tmpreg & 0x70) != 0) { + rf_a_mode = rtl_get_rfreg(hw, RF90_PATH_A, 0x00, MASK12BITS); + + if (is2t) + rf_b_mode = rtl_get_rfreg(hw, RF90_PATH_B, 0x00, + MASK12BITS); + + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, MASK12BITS, + (rf_a_mode & 0x8FFFF) | 0x10000); + + if (is2t) + rtl_set_rfreg(hw, RF90_PATH_B, 0x00, MASK12BITS, + (rf_b_mode & 0x8FFFF) | 0x10000); + } + lc_cal = rtl_get_rfreg(hw, RF90_PATH_A, 0x18, MASK12BITS); + + rtl_set_rfreg(hw, RF90_PATH_A, 0x18, MASK12BITS, lc_cal | 0x08000); + + mdelay(100); + + if ((tmpreg & 0x70) != 0) { + rtl_write_byte(rtlpriv, 0xd03, tmpreg); + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, MASK12BITS, rf_a_mode); + + if (is2t) + rtl_set_rfreg(hw, RF90_PATH_B, 0x00, MASK12BITS, + rf_b_mode); + } else { + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0x00); + } +} + +static void _rtl8723ae_phy_set_rfpath_switch(struct ieee80211_hw *hw, + bool bmain, bool is2t) +{ + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + + if (is_hal_stop(rtlhal)) { + rtl_set_bbreg(hw, REG_LEDCFG0, BIT(23), 0x01); + rtl_set_bbreg(hw, rFPGA0_XAB_RFPARAMETER, BIT(13), 0x01); + } + if (is2t) { + if (bmain) + rtl_set_bbreg(hw, RFPGA0_XB_RFINTERFACEOE, + BIT(5) | BIT(6), 0x1); + else + rtl_set_bbreg(hw, RFPGA0_XB_RFINTERFACEOE, + BIT(5) | BIT(6), 0x2); + } else { + if (bmain) + rtl_set_bbreg(hw, RFPGA0_XA_RFINTERFACEOE, 0x300, 0x2); + else + rtl_set_bbreg(hw, RFPGA0_XA_RFINTERFACEOE, 0x300, 0x1); + + } +} + +#undef IQK_ADDA_REG_NUM +#undef IQK_DELAY_TIME + +void rtl8723ae_phy_iq_calibrate(struct ieee80211_hw *hw, bool recovery) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + long result[4][8]; + u8 i, final_candidate; + bool patha_ok, pathb_ok; + long reg_e94, reg_e9c, reg_ea4, reg_eb4, reg_ebc, reg_tmp = 0; + bool is12simular, is13simular, is23simular; + bool start_conttx = false, singletone = false; + u32 iqk_bb_reg[10] = { + ROFDM0_XARXIQIMBALANCE, + ROFDM0_XBRXIQIMBALANCE, + ROFDM0_ECCATHRESHOLD, + ROFDM0_AGCRSSITABLE, + ROFDM0_XATXIQIMBALANCE, + ROFDM0_XBTXIQIMBALANCE, + ROFDM0_XCTXIQIMBALANCE, + ROFDM0_XCTXAFE, + ROFDM0_XDTXAFE, + ROFDM0_RXIQEXTANTA + }; + + if (recovery) { + phy_reload_adda_regs(hw, iqk_bb_reg, rtlphy->iqk_bb_backup, 10); + return; + } + if (start_conttx || singletone) + return; + for (i = 0; i < 8; i++) { + result[0][i] = 0; + result[1][i] = 0; + result[2][i] = 0; + result[3][i] = 0; + } + final_candidate = 0xff; + patha_ok = false; + pathb_ok = false; + is12simular = false; + is23simular = false; + is13simular = false; + for (i = 0; i < 3; i++) { + _rtl8723ae_phy_iq_calibrate(hw, result, i, false); + if (i == 1) { + is12simular = phy_simularity_comp(hw, result, 0, 1); + if (is12simular) { + final_candidate = 0; + break; + } + } + if (i == 2) { + is13simular = phy_simularity_comp(hw, result, 0, 2); + if (is13simular) { + final_candidate = 0; + break; + } + is23simular = phy_simularity_comp(hw, result, 1, 2); + if (is23simular) { + final_candidate = 1; + } else { + for (i = 0; i < 8; i++) + reg_tmp += result[3][i]; + + if (reg_tmp != 0) + final_candidate = 3; + else + final_candidate = 0xFF; + } + } + } + for (i = 0; i < 4; i++) { + reg_e94 = result[i][0]; + reg_e9c = result[i][1]; + reg_ea4 = result[i][2]; + reg_eb4 = result[i][4]; + reg_ebc = result[i][5]; + } + if (final_candidate != 0xff) { + rtlphy->reg_e94 = reg_e94 = result[final_candidate][0]; + rtlphy->reg_e9c = reg_e9c = result[final_candidate][1]; + reg_ea4 = result[final_candidate][2]; + rtlphy->reg_eb4 = reg_eb4 = result[final_candidate][4]; + rtlphy->reg_ebc = reg_ebc = result[final_candidate][5]; + patha_ok = pathb_ok = true; + } else { + rtlphy->reg_e94 = rtlphy->reg_eb4 = 0x100; + rtlphy->reg_e9c = rtlphy->reg_ebc = 0x0; + } + if (reg_e94 != 0) /*&&(reg_ea4 != 0) */ + phy_path_a_fill_iqk_matrix(hw, patha_ok, result, + final_candidate, (reg_ea4 == 0)); + phy_save_adda_regs(hw, iqk_bb_reg, rtlphy->iqk_bb_backup, 10); +} + +void rtl8723ae_phy_lc_calibrate(struct ieee80211_hw *hw) +{ + bool start_conttx = false, singletone = false; + + if (start_conttx || singletone) + return; + _rtl8723ae_phy_lc_calibrate(hw, false); +} + +void rtl8723ae_phy_set_rfpath_switch(struct ieee80211_hw *hw, bool bmain) +{ + _rtl8723ae_phy_set_rfpath_switch(hw, bmain, false); +} + +bool rtl8723ae_phy_set_io_cmd(struct ieee80211_hw *hw, enum io_type iotype) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + bool postprocessing = false; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "-->IO Cmd(%#x), set_io_inprogress(%d)\n", + iotype, rtlphy->set_io_inprogress); + do { + switch (iotype) { + case IO_CMD_RESUME_DM_BY_SCAN: + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "[IO CMD] Resume DM after scan.\n"); + postprocessing = true; + break; + case IO_CMD_PAUSE_DM_BY_SCAN: + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "[IO CMD] Pause DM before scan.\n"); + postprocessing = true; + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not process\n"); + break; + } + } while (false); + if (postprocessing && !rtlphy->set_io_inprogress) { + rtlphy->set_io_inprogress = true; + rtlphy->current_io_type = iotype; + } else { + return false; + } + rtl8723ae_phy_set_io(hw); + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, "<--IO Type(%#x)\n", iotype); + return true; +} + +static void rtl8723ae_phy_set_io(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct dig_t *dm_digtable = &rtlpriv->dm_digtable; + + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "--->Cmd(%#x), set_io_inprogress(%d)\n", + rtlphy->current_io_type, rtlphy->set_io_inprogress); + switch (rtlphy->current_io_type) { + case IO_CMD_RESUME_DM_BY_SCAN: + dm_digtable->cur_igvalue = rtlphy->initgain_backup.xaagccore1; + rtl8723ae_dm_write_dig(hw); + rtl8723ae_phy_set_txpower_level(hw, rtlphy->current_channel); + break; + case IO_CMD_PAUSE_DM_BY_SCAN: + rtlphy->initgain_backup.xaagccore1 = dm_digtable->cur_igvalue; + dm_digtable->cur_igvalue = 0x17; + rtl8723ae_dm_write_dig(hw); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not process\n"); + break; + } + rtlphy->set_io_inprogress = false; + RT_TRACE(rtlpriv, COMP_CMD, DBG_TRACE, + "<---(%#x)\n", rtlphy->current_io_type); +} + +static void rtl8723ae_phy_set_rf_on(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtl_write_byte(rtlpriv, REG_SPS0_CTRL, 0x2b); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE3); + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x00); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE2); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE3); + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0x00); +} + +static void _rtl8723ae_phy_set_rf_sleep(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 u4b_tmp; + u8 delay = 5; + + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0xFF); + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, RFREG_OFFSET_MASK, 0x00); + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x40); + u4b_tmp = rtl_get_rfreg(hw, RF90_PATH_A, 0, RFREG_OFFSET_MASK); + while (u4b_tmp != 0 && delay > 0) { + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x0); + rtl_set_rfreg(hw, RF90_PATH_A, 0x00, RFREG_OFFSET_MASK, 0x00); + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x40); + u4b_tmp = rtl_get_rfreg(hw, RF90_PATH_A, 0, RFREG_OFFSET_MASK); + delay--; + } + if (delay == 0) { + rtl_write_byte(rtlpriv, REG_APSD_CTRL, 0x00); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE2); + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE3); + rtl_write_byte(rtlpriv, REG_TXPAUSE, 0x00); + RT_TRACE(rtlpriv, COMP_POWER, DBG_TRACE, + "Switch RF timeout !!!.\n"); + return; + } + rtl_write_byte(rtlpriv, REG_SYS_FUNC_EN, 0xE2); + rtl_write_byte(rtlpriv, REG_SPS0_CTRL, 0x22); +} + +static bool _rtl8723ae_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci_priv *pcipriv = rtl_pcipriv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl8192_tx_ring *ring = NULL; + bool bresult = true; + u8 i, queue_id; + + switch (rfpwr_state) { + case ERFON: + if ((ppsc->rfpwr_state == ERFOFF) && + RT_IN_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC)) { + bool rtstatus; + u32 InitializeCount = 0; + do { + InitializeCount++; + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "IPS Set eRf nic enable\n"); + rtstatus = rtl_ps_enable_nic(hw); + } while ((rtstatus != true) && (InitializeCount < 10)); + RT_CLEAR_PS_LEVEL(ppsc, + RT_RF_OFF_LEVL_HALT_NIC); + } else { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "Set ERFON sleeped:%d ms\n", + jiffies_to_msecs(jiffies - + ppsc->last_sleep_jiffies)); + ppsc->last_awake_jiffies = jiffies; + rtl8723ae_phy_set_rf_on(hw); + } + if (mac->link_state == MAC80211_LINKED) { + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_LINK); + } else { + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_NO_LINK); + } + break; + case ERFOFF: + if (ppsc->reg_rfps_level & RT_RF_OFF_LEVL_HALT_NIC) { + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "IPS Set eRf nic disable\n"); + rtl_ps_disable_nic(hw); + RT_SET_PS_LEVEL(ppsc, RT_RF_OFF_LEVL_HALT_NIC); + } else { + if (ppsc->rfoff_reason == RF_CHANGE_BY_IPS) { + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_NO_LINK); + } else { + rtlpriv->cfg->ops->led_control(hw, + LED_CTL_POWER_OFF); + } + } + break; + case ERFSLEEP: + if (ppsc->rfpwr_state == ERFOFF) + break; + for (queue_id = 0, i = 0; + queue_id < RTL_PCI_MAX_TX_QUEUE_COUNT;) { + ring = &pcipriv->dev.tx_ring[queue_id]; + if (skb_queue_len(&ring->queue) == 0) { + queue_id++; + continue; + } else { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "eRf Off/Sleep: %d times TcbBusyQueue[%d] =%d before doze!\n", + (i + 1), queue_id, + skb_queue_len(&ring->queue)); + + udelay(10); + i++; + } + if (i >= MAX_DOZE_WAITING_TIMES_9x) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_WARNING, + "\n ERFSLEEP: %d times TcbBusyQueue[%d] = %d !\n", + MAX_DOZE_WAITING_TIMES_9x, + queue_id, + skb_queue_len(&ring->queue)); + break; + } + } + RT_TRACE(rtlpriv, COMP_RF, DBG_DMESG, + "Set ERFSLEEP awaked:%d ms\n", + jiffies_to_msecs(jiffies - ppsc->last_awake_jiffies)); + ppsc->last_sleep_jiffies = jiffies; + _rtl8723ae_phy_set_rf_sleep(hw); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "switch case not processed\n"); + bresult = false; + break; + } + if (bresult) + ppsc->rfpwr_state = rfpwr_state; + return bresult; +} + +bool rtl8723ae_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state) +{ + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + bool bresult = false; + + if (rfpwr_state == ppsc->rfpwr_state) + return bresult; + bresult = _rtl8723ae_phy_set_rf_power_state(hw, rfpwr_state); + return bresult; +} diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/phy.h b/drivers/net/wireless/rtlwifi/rtl8723ae/phy.h new file mode 100644 index 0000000..e7a59eb --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/phy.h @@ -0,0 +1,224 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL92C_PHY_H__ +#define __RTL92C_PHY_H__ + +#define MAX_PRECMD_CNT 16 +#define MAX_RFDEPENDCMD_CNT 16 +#define MAX_POSTCMD_CNT 16 + +#define MAX_DOZE_WAITING_TIMES_9x 64 + +#define RT_CANNOT_IO(hw) false +#define HIGHPOWER_RADIOA_ARRAYLEN 22 + +#define MAX_TOLERANCE 5 +#define IQK_DELAY_TIME 1 + +#define APK_BB_REG_NUM 5 +#define APK_AFE_REG_NUM 16 +#define APK_CURVE_REG_NUM 4 +#define PATH_NUM 2 + +#define LOOP_LIMIT 5 +#define MAX_STALL_TIME 50 +#define AntennaDiversityValue 0x80 +#define MAX_TXPWR_IDX_NMODE_92S 63 +#define Reset_Cnt_Limit 3 + +#define IQK_MAC_REG_NUM 4 + +#define RF6052_MAX_PATH 2 + +#define CT_OFFSET_MAC_ADDR 0X16 + +#define CT_OFFSET_CCK_TX_PWR_IDX 0x5A +#define CT_OFFSET_HT401S_TX_PWR_IDX 0x60 +#define CT_OFFSET_HT402S_TX_PWR_IDX_DIFF 0x66 +#define CT_OFFSET_HT20_TX_PWR_IDX_DIFF 0x69 +#define CT_OFFSET_OFDM_TX_PWR_IDX_DIFF 0x6C + +#define CT_OFFSET_HT40_MAX_PWR_OFFSET 0x6F +#define CT_OFFSET_HT20_MAX_PWR_OFFSET 0x72 + +#define CT_OFFSET_CHANNEL_PLAH 0x75 +#define CT_OFFSET_THERMAL_METER 0x78 +#define CT_OFFSET_RF_OPTION 0x79 +#define CT_OFFSET_VERSION 0x7E +#define CT_OFFSET_CUSTOMER_ID 0x7F + +#define RTL92C_MAX_PATH_NUM 2 + +enum swchnlcmd_id { + CMDID_END, + CMDID_SET_TXPOWEROWER_LEVEL, + CMDID_BBREGWRITE10, + CMDID_WRITEPORT_ULONG, + CMDID_WRITEPORT_USHORT, + CMDID_WRITEPORT_UCHAR, + CMDID_RF_WRITEREG, +}; + +struct swchnlcmd { + enum swchnlcmd_id cmdid; + u32 para1; + u32 para2; + u32 msdelay; +}; + +enum hw90_block_e { + HW90_BLOCK_MAC = 0, + HW90_BLOCK_PHY0 = 1, + HW90_BLOCK_PHY1 = 2, + HW90_BLOCK_RF = 3, + HW90_BLOCK_MAXIMUM = 4, +}; + +enum baseband_config_type { + BASEBAND_CONFIG_PHY_REG = 0, + BASEBAND_CONFIG_AGC_TAB = 1, +}; + +enum ra_offset_area { + RA_OFFSET_LEGACY_OFDM1, + RA_OFFSET_LEGACY_OFDM2, + RA_OFFSET_HT_OFDM1, + RA_OFFSET_HT_OFDM2, + RA_OFFSET_HT_OFDM3, + RA_OFFSET_HT_OFDM4, + RA_OFFSET_HT_CCK, +}; + +enum antenna_path { + ANTENNA_NONE, + ANTENNA_D, + ANTENNA_C, + ANTENNA_CD, + ANTENNA_B, + ANTENNA_BD, + ANTENNA_BC, + ANTENNA_BCD, + ANTENNA_A, + ANTENNA_AD, + ANTENNA_AC, + ANTENNA_ACD, + ANTENNA_AB, + ANTENNA_ABD, + ANTENNA_ABC, + ANTENNA_ABCD +}; + +struct r_antenna_select_ofdm { + u32 r_tx_antenna:4; + u32 r_ant_l:4; + u32 r_ant_non_ht:4; + u32 r_ant_ht1:4; + u32 r_ant_ht2:4; + u32 r_ant_ht_s1:4; + u32 r_ant_non_ht_s1:4; + u32 ofdm_txsc:2; + u32 reserved:2; +}; + +struct r_antenna_select_cck { + u8 r_cckrx_enable_2:2; + u8 r_cckrx_enable:2; + u8 r_ccktx_enable:4; +}; + +struct efuse_contents { + u8 mac_addr[ETH_ALEN]; + u8 cck_tx_power_idx[6]; + u8 ht40_1s_tx_power_idx[6]; + u8 ht40_2s_tx_power_idx_diff[3]; + u8 ht20_tx_power_idx_diff[3]; + u8 ofdm_tx_power_idx_diff[3]; + u8 ht40_max_power_offset[3]; + u8 ht20_max_power_offset[3]; + u8 channel_plan; + u8 thermal_meter; + u8 rf_option[5]; + u8 version; + u8 oem_id; + u8 regulatory; +}; + +struct tx_power_struct { + u8 cck[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 ht40_1s[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 ht40_2s[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 ht20_diff[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 legacy_ht_diff[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 legacy_ht_txpowerdiff; + u8 groupht20[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 groupht40[RTL92C_MAX_PATH_NUM][CHANNEL_MAX_NUMBER]; + u8 pwrgroup_cnt; + u32 mcs_original_offset[4][16]; +}; + +extern u32 rtl8723ae_phy_query_bb_reg(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask); +extern void rtl8723ae_phy_set_bb_reg(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask, u32 data); +extern u32 rtl8723ae_phy_query_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, + u32 bitmask); +extern void rtl8723ae_phy_set_rf_reg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, + u32 bitmask, u32 data); +extern bool rtl8723ae_phy_mac_config(struct ieee80211_hw *hw); +extern bool rtl8723ae_phy_bb_config(struct ieee80211_hw *hw); +extern bool rtl8723ae_phy_rf_config(struct ieee80211_hw *hw); +extern bool rtl92c_phy_config_rf_with_feaderfile(struct ieee80211_hw *hw, + enum radio_path rfpath); +extern void rtl8723ae_phy_get_hw_reg_originalvalue(struct ieee80211_hw *hw); +extern void rtl8723ae_phy_get_txpower_level(struct ieee80211_hw *hw, + long *powerlevel); +extern void rtl8723ae_phy_set_txpower_level(struct ieee80211_hw *hw, + u8 channel); +extern bool rtl8723ae_phy_update_txpower_dbm(struct ieee80211_hw *hw, + long power_indbm); +extern void rtl8723ae_phy_scan_operation_backup(struct ieee80211_hw *hw, + u8 operation); +extern void rtl8723ae_phy_set_bw_mode_callback(struct ieee80211_hw *hw); +extern void rtl8723ae_phy_set_bw_mode(struct ieee80211_hw *hw, + enum nl80211_channel_type ch_type); +extern void rtl8723ae_phy_sw_chnl_callback(struct ieee80211_hw *hw); +extern u8 rtl8723ae_phy_sw_chnl(struct ieee80211_hw *hw); +extern void rtl8723ae_phy_iq_calibrate(struct ieee80211_hw *hw, bool recovery); +void rtl8723ae_phy_lc_calibrate(struct ieee80211_hw *hw); +void rtl8723ae_phy_set_rfpath_switch(struct ieee80211_hw *hw, bool bmain); +bool rtl8723ae_phy_config_rf_with_headerfile(struct ieee80211_hw *hw, + enum radio_path rfpath); +bool rtl8723ae_phy_set_io_cmd(struct ieee80211_hw *hw, enum io_type iotype); +extern bool rtl8723ae_phy_set_rf_power_state(struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/pwrseq.c b/drivers/net/wireless/rtlwifi/rtl8723ae/pwrseq.c new file mode 100644 index 0000000..df6ca9a --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/pwrseq.c @@ -0,0 +1,109 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "pwrseqcmd.h" +#include "pwrseq.h" + +/* drivers should parse arrays below and do the corresponding actions */ + +/*3 Power on Array*/ +struct wlan_pwr_cfg rtl8723A_power_on_flow[RTL8723A_TRANS_CARDEMU_TO_ACT_STPS + + RTL8723A_TRANS_END_STPS] = { + RTL8723A_TRANS_CARDEMU_TO_ACT, + RTL8723A_TRANS_END +}; + +/*3Radio off GPIO Array */ +struct wlan_pwr_cfg rtl8723A_radio_off_flow[RTL8723A_TRANS_ACT_TO_CARDEMU_STPS + + RTL8723A_TRANS_END_STPS] = { + RTL8723A_TRANS_ACT_TO_CARDEMU, + RTL8723A_TRANS_END +}; + +/*3Card Disable Array*/ +struct wlan_pwr_cfg +rtl8723A_card_disable_flow[RTL8723A_TRANS_ACT_TO_CARDEMU_STPS + + RTL8723A_TRANS_CARDEMU_TO_PDN_STPS + + RTL8723A_TRANS_END_STPS] = { + RTL8723A_TRANS_ACT_TO_CARDEMU, + RTL8723A_TRANS_CARDEMU_TO_CARDDIS, + RTL8723A_TRANS_END +}; + +/*3 Card Enable Array*/ +struct wlan_pwr_cfg rtl8723A_card_enable_flow[RTL8723A_TRANS_ACT_TO_CARDEMU_STPS + + RTL8723A_TRANS_CARDEMU_TO_PDN_STPS + + RTL8723A_TRANS_END_STPS] = { + RTL8723A_TRANS_CARDDIS_TO_CARDEMU, + RTL8723A_TRANS_CARDEMU_TO_ACT, + RTL8723A_TRANS_END +}; + +/*3Suspend Array*/ +struct wlan_pwr_cfg rtl8723A_suspend_flow[RTL8723A_TRANS_ACT_TO_CARDEMU_STPS + + RTL8723A_TRANS_CARDEMU_TO_SUS_STPS + + RTL8723A_TRANS_END_STPS] = { + RTL8723A_TRANS_ACT_TO_CARDEMU, + RTL8723A_TRANS_CARDEMU_TO_SUS, + RTL8723A_TRANS_END +}; + +/*3 Resume Array*/ +struct wlan_pwr_cfg rtl8723A_resume_flow[RTL8723A_TRANS_ACT_TO_CARDEMU_STPS + + RTL8723A_TRANS_CARDEMU_TO_SUS_STPS + + RTL8723A_TRANS_END_STPS] = { + RTL8723A_TRANS_SUS_TO_CARDEMU, + RTL8723A_TRANS_CARDEMU_TO_ACT, + RTL8723A_TRANS_END +}; + +/*3HWPDN Array*/ +struct wlan_pwr_cfg rtl8723A_hwpdn_flow[RTL8723A_TRANS_ACT_TO_CARDEMU_STPS + + RTL8723A_TRANS_CARDEMU_TO_PDN_STPS + + RTL8723A_TRANS_END_STPS] = { + RTL8723A_TRANS_ACT_TO_CARDEMU, + RTL8723A_TRANS_CARDEMU_TO_PDN, + RTL8723A_TRANS_END +}; + +/*3 Enter LPS */ +struct wlan_pwr_cfg rtl8723A_enter_lps_flow[RTL8723A_TRANS_ACT_TO_LPS_STPS + + RTL8723A_TRANS_END_STPS] = { + /*FW behavior*/ + RTL8723A_TRANS_ACT_TO_LPS, + RTL8723A_TRANS_END +}; + +/*3 Leave LPS */ +struct wlan_pwr_cfg rtl8723A_leave_lps_flow[RTL8723A_TRANS_LPS_TO_ACT_STPS + + RTL8723A_TRANS_END_STPS] = { + /*FW behavior*/ + RTL8723A_TRANS_LPS_TO_ACT, + RTL8723A_TRANS_END +}; diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/pwrseq.h b/drivers/net/wireless/rtlwifi/rtl8723ae/pwrseq.h new file mode 100644 index 0000000..7a46f9f --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/pwrseq.h @@ -0,0 +1,322 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL8723E_PWRSEQ_H__ +#define __RTL8723E_PWRSEQ_H__ + +#include "pwrseqcmd.h" +/* + Check document WM-20110607-Paul-RTL8723A_Power_Architecture-R02.vsd + There are 6 HW Power States: + 0: POFF--Power Off + 1: PDN--Power Down + 2: CARDEMU--Card Emulation + 3: ACT--Active Mode + 4: LPS--Low Power State + 5: SUS--Suspend + + The transision from different states are defined below + TRANS_CARDEMU_TO_ACT + TRANS_ACT_TO_CARDEMU + TRANS_CARDEMU_TO_SUS + TRANS_SUS_TO_CARDEMU + TRANS_CARDEMU_TO_PDN + TRANS_ACT_TO_LPS + TRANS_LPS_TO_ACT + + TRANS_END +*/ + +#define RTL8723A_TRANS_CARDEMU_TO_ACT_STPS 10 +#define RTL8723A_TRANS_ACT_TO_CARDEMU_STPS 10 +#define RTL8723A_TRANS_CARDEMU_TO_SUS_STPS 10 +#define RTL8723A_TRANS_SUS_TO_CARDEMU_STPS 10 +#define RTL8723A_TRANS_CARDEMU_TO_PDN_STPS 10 +#define RTL8723A_TRANS_PDN_TO_CARDEMU_STPS 10 +#define RTL8723A_TRANS_ACT_TO_LPS_STPS 15 +#define RTL8723A_TRANS_LPS_TO_ACT_STPS 15 +#define RTL8723A_TRANS_END_STPS 1 + + +#define RTL8723A_TRANS_CARDEMU_TO_ACT \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, \ + * comments here*/ \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(2), 0}, \ + /* disable SW LPS 0x04[10]=0*/ \ + {0x0006, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_POLLING, BIT(1), BIT(1)}, \ + /* wait till 0x04[17] = 1 power ready*/ \ + {0x0006, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(0), BIT(0)}, \ + /* release WLON reset 0x04[16]=1*/ \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(7), 0}, \ + /* disable HWPDN 0x04[15]=0*/ \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, (BIT(4)|BIT(3)), 0}, \ + /* disable WL suspend*/ \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(0), BIT(0)}, \ + /* polling until return 0*/ \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_POLLING, BIT(0), 0} + +#define RTL8723A_TRANS_ACT_TO_CARDEMU \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, \ + * comments here*/ \ + {0x001F, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0}, \ + /*0x1F[7:0] = 0 turn off RF*/ \ + {0x004E, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(7), 0}, \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(1), BIT(1)}, \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_POLLING, BIT(1), 0} + +#define RTL8723A_TRANS_CARDEMU_TO_SUS \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, \ + * comments here*/ \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_PCI_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(4)|BIT(3), \ + (BIT(4)|BIT(3))}, \ + /*0x04[12:11] = 2b'11 enable WL suspend for PCIe*/ \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_USB_MSK | \ + PWR_INTF_SDIO_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(3)|BIT(4), BIT(3)},\ + /*0x04[12:11] = 2b'01 enable WL suspend*/ \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_PCI_MSK, \ + PWR_BASEADDR_MAC, \ + PWR_CMD_WRITE, BIT(3)|BIT(4), BIT(3)|BIT(4)}, \ + /*0x04[12:11] = 2b'11 enable WL suspend for PCIe*/ \ + {0x0086, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + PWR_BASEADDR_SDIO, \ + PWR_CMD_WRITE, BIT(0), BIT(0)}, \ + /*Set SDIO suspend local register*/ \ + {0x0086, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + PWR_BASEADDR_SDIO, \ + PWR_CMD_POLLING, BIT(1), 0} \ + /*wait power state to suspend*/ + +#define RTL8723A_TRANS_SUS_TO_CARDEMU \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, */\ + {0x0086, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + PWR_BASEADDR_SDIO, PWR_CMD_WRITE, BIT(0), 0}, \ + /*Set SDIO suspend local register*/ \ + {0x0086, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + PWR_BASEADDR_SDIO, PWR_CMD_POLLING, BIT(1), BIT(1)}, \ + /*wait power state to suspend*/ \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(3)|BIT(4), 0} \ + /*0x04[12:11] = 2b'01enable WL suspend*/ + +#define RTL8723A_TRANS_CARDEMU_TO_CARDDIS \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, */\ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, \ + PWR_INTF_USB_MSK|PWR_INTF_SDIO_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(3)|BIT(4), BIT(3)},\ + /*0x04[12:11] = 2b'01 enable WL suspend*/ \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_PCI_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(2), BIT(2)}, \ + /*0x04[10] = 1, enable SW LPS*/ \ + {0x0086, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + PWR_BASEADDR_SDIO, PWR_CMD_WRITE, BIT(0), BIT(0)}, \ + /*Set SDIO suspend local register*/ \ + {0x0086, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + PWR_BASEADDR_SDIO, PWR_CMD_POLLING, BIT(1), 0} \ + /*wait power state to suspend*/ + +#define RTL8723A_TRANS_CARDDIS_TO_CARDEMU \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, */\ + {0x0086, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + PWR_BASEADDR_SDIO, PWR_CMD_WRITE, BIT(0), 0}, \ + /*Set SDIO suspend local register*/ \ + {0x0086, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + PWR_BASEADDR_SDIO, PWR_CMD_POLLING, BIT(1), BIT(1)}, \ + /*wait power state to suspend*/ \ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(3)|BIT(4), 0}, \ + /*0x04[12:11] = 2b'00enable WL suspend*/ \ + {0x0301, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0} \ + /*PCIe DMA start*/ + +#define RTL8723A_TRANS_CARDEMU_TO_PDN \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, */\ + {0x0006, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(0), 0}, \ + /* 0x04[16] = 0*/\ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(7), BIT(7)} \ + /* 0x04[15] = 1*/ + +#define RTL8723A_TRANS_PDN_TO_CARDEMU \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, */\ + {0x0005, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(7), 0} \ + /* 0x04[15] = 0*/ + +#define RTL8723A_TRANS_ACT_TO_LPS \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, */\ + {0x0301, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0xFF}, \ + /*PCIe DMA stop*/ \ + {0x0522, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0x7F}, \ + /*Tx Pause*/ \ + {0x05F8, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_POLLING, 0xFF, 0}, \ + /*Should be zero if no packet is transmitting*/ \ + {0x05F9, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_POLLING, 0xFF, 0}, \ + /*Should be zero if no packet is transmitting*/ \ + {0x05FA, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_POLLING, 0xFF, 0}, \ + /*Should be zero if no packet is transmitting*/ \ + {0x05FB, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_POLLING, 0xFF, 0}, \ + /*Should be zero if no packet is transmitting*/ \ + {0x0002, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(0), 0}, \ + /*CCK and OFDM are disabled,and clock are gated*/ \ + {0x0002, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_DELAY, 0, PWRSEQ_DELAY_US}, \ + /*Delay 1us*/ \ + {0x0002, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(1), 0}, \ + /*Whole BB is reset*/ \ + {0x0100, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0x3F}, \ + /*Reset MAC TRX*/ \ + {0x0101, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(1), 0}, \ + /*check if removed later*/ \ + {0x0553, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(5), BIT(5)} \ + /*Respond TxOK to scheduler*/ + +#define RTL8723A_TRANS_LPS_TO_ACT \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, */\ + {0x0080, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_SDIO_MSK, \ + PWR_BASEADDR_SDIO, PWR_CMD_WRITE, 0xFF, 0x84}, \ + /*SDIO RPWM*/ \ + {0xFE58, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_USB_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0x84}, \ + /*USB RPWM*/ \ + {0x0361, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_PCI_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0x84}, \ + /*PCIe RPWM*/ \ + {0x0002, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_DELAY, 0, PWRSEQ_DELAY_MS}, \ + /*Delay*/ \ + {0x0008, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(4), 0}, \ + /* 0x08[4] = 0 switch TSF to 40M*/ \ + {0x0109, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_POLLING, BIT(7), 0}, \ + /*Polling 0x109[7]=0 TSF in 40M*/ \ + {0x0029, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(6)|BIT(7), 0}, \ + /*. 0x29[7:6] = 2b'00 enable BB clock*/ \ + {0x0101, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(1), BIT(1)}, \ + /*. 0x101[1] = 1*/ \ + {0x0100, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0xFF}, \ + /* 0x100[7:0] = 0xFF enable WMAC TRX*/ \ + {0x0002, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, BIT(1)|BIT(0), \ + BIT(1)|BIT(0)}, \ + /* 0x02[1:0] = 2b'11 enable BB macro*/ \ + {0x0522, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + PWR_BASEADDR_MAC, PWR_CMD_WRITE, 0xFF, 0} \ + /*. 0x522 = 0*/ + +#define RTL8723A_TRANS_END \ + /* format */ \ + /* { offset, cut_msk, fab_msk|interface_msk, base|cmd, msk, value }, */\ + {0xFFFF, PWR_CUT_ALL_MSK, PWR_FAB_ALL_MSK, PWR_INTF_ALL_MSK, \ + 0, PWR_CMD_END, 0, 0} + +extern struct +wlan_pwr_cfg rtl8723A_power_on_flow[RTL8723A_TRANS_CARDEMU_TO_ACT_STPS + + RTL8723A_TRANS_END_STPS]; +extern struct +wlan_pwr_cfg rtl8723A_radio_off_flow[RTL8723A_TRANS_ACT_TO_CARDEMU_STPS + + RTL8723A_TRANS_END_STPS]; +extern struct +wlan_pwr_cfg rtl8723A_card_disable_flow[RTL8723A_TRANS_ACT_TO_CARDEMU_STPS + + RTL8723A_TRANS_CARDEMU_TO_PDN_STPS + + RTL8723A_TRANS_END_STPS]; +extern struct +wlan_pwr_cfg rtl8723A_card_enable_flow[RTL8723A_TRANS_ACT_TO_CARDEMU_STPS + + RTL8723A_TRANS_CARDEMU_TO_PDN_STPS + + RTL8723A_TRANS_END_STPS]; +extern struct +wlan_pwr_cfg rtl8723A_suspend_flow[RTL8723A_TRANS_ACT_TO_CARDEMU_STPS + + RTL8723A_TRANS_CARDEMU_TO_SUS_STPS + + RTL8723A_TRANS_END_STPS]; +extern struct +wlan_pwr_cfg rtl8723A_resume_flow[RTL8723A_TRANS_ACT_TO_CARDEMU_STPS + + RTL8723A_TRANS_CARDEMU_TO_SUS_STPS + + RTL8723A_TRANS_END_STPS]; +extern struct +wlan_pwr_cfg rtl8723A_hwpdn_flow[RTL8723A_TRANS_ACT_TO_CARDEMU_STPS + + RTL8723A_TRANS_CARDEMU_TO_PDN_STPS + + RTL8723A_TRANS_END_STPS]; +extern struct +wlan_pwr_cfg rtl8723A_enter_lps_flow[RTL8723A_TRANS_ACT_TO_LPS_STPS + + RTL8723A_TRANS_END_STPS]; +extern struct +wlan_pwr_cfg rtl8723A_leave_lps_flow[RTL8723A_TRANS_LPS_TO_ACT_STPS + + RTL8723A_TRANS_END_STPS]; + +/* RTL8723 Power Configuration CMDs for PCIe interface */ +#define Rtl8723_NIC_PWR_ON_FLOW rtl8723A_power_on_flow +#define Rtl8723_NIC_RF_OFF_FLOW rtl8723A_radio_off_flow +#define Rtl8723_NIC_DISABLE_FLOW rtl8723A_card_disable_flow +#define Rtl8723_NIC_ENABLE_FLOW rtl8723A_card_enable_flow +#define Rtl8723_NIC_SUSPEND_FLOW rtl8723A_suspend_flow +#define Rtl8723_NIC_RESUME_FLOW rtl8723A_resume_flow +#define Rtl8723_NIC_PDN_FLOW rtl8723A_hwpdn_flow +#define Rtl8723_NIC_LPS_ENTER_FLOW rtl8723A_enter_lps_flow +#define Rtl8723_NIC_LPS_LEAVE_FLOW rtl8723A_leave_lps_flow + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/pwrseqcmd.c b/drivers/net/wireless/rtlwifi/rtl8723ae/pwrseqcmd.c new file mode 100644 index 0000000..2044b59 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/pwrseqcmd.c @@ -0,0 +1,129 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "pwrseq.h" + +/* Description: + * This routine deals with the Power Configuration CMD + * parsing for RTL8723/RTL8188E Series IC. + * Assumption: + * We should follow specific format that was released from HW SD. + */ +bool rtl_hal_pwrseqcmdparsing(struct rtl_priv *rtlpriv, u8 cut_version, + u8 faversion, u8 interface_type, + struct wlan_pwr_cfg pwrcfgcmd[]) +{ + struct wlan_pwr_cfg cfg_cmd = {0}; + bool polling_bit = false; + u32 ary_idx = 0; + u8 value = 0; + u32 offset = 0; + u32 polling_count = 0; + u32 max_polling_cnt = 5000; + + do { + cfg_cmd = pwrcfgcmd[ary_idx]; + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "rtl_hal_pwrseqcmdparsing(): offset(%#x),cut_msk(%#x), famsk(%#x)," + "interface_msk(%#x), base(%#x), cmd(%#x), msk(%#x), value(%#x)\n", + GET_PWR_CFG_OFFSET(cfg_cmd), + GET_PWR_CFG_CUT_MASK(cfg_cmd), + GET_PWR_CFG_FAB_MASK(cfg_cmd), + GET_PWR_CFG_INTF_MASK(cfg_cmd), + GET_PWR_CFG_BASE(cfg_cmd), GET_PWR_CFG_CMD(cfg_cmd), + GET_PWR_CFG_MASK(cfg_cmd), GET_PWR_CFG_VALUE(cfg_cmd)); + + if ((GET_PWR_CFG_FAB_MASK(cfg_cmd)&faversion) && + (GET_PWR_CFG_CUT_MASK(cfg_cmd)&cut_version) && + (GET_PWR_CFG_INTF_MASK(cfg_cmd)&interface_type)) { + switch (GET_PWR_CFG_CMD(cfg_cmd)) { + case PWR_CMD_READ: + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "rtl_hal_pwrseqcmdparsing(): PWR_CMD_READ\n"); + break; + case PWR_CMD_WRITE: + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "rtl_hal_pwrseqcmdparsing(): PWR_CMD_WRITE\n"); + offset = GET_PWR_CFG_OFFSET(cfg_cmd); + + /*Read the value from system register*/ + value = rtl_read_byte(rtlpriv, offset); + value &= (~(GET_PWR_CFG_MASK(cfg_cmd))); + value |= (GET_PWR_CFG_VALUE(cfg_cmd) & + GET_PWR_CFG_MASK(cfg_cmd)); + + /*Write the value back to sytem register*/ + rtl_write_byte(rtlpriv, offset, value); + break; + case PWR_CMD_POLLING: + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "rtl_hal_pwrseqcmdparsing(): PWR_CMD_POLLING\n"); + polling_bit = false; + offset = GET_PWR_CFG_OFFSET(cfg_cmd); + + do { + value = rtl_read_byte(rtlpriv, offset); + + value &= GET_PWR_CFG_MASK(cfg_cmd); + if (value == + (GET_PWR_CFG_VALUE(cfg_cmd) + & GET_PWR_CFG_MASK(cfg_cmd))) + polling_bit = true; + else + udelay(10); + + if (polling_count++ > max_polling_cnt) + return false; + } while (!polling_bit); + break; + case PWR_CMD_DELAY: + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "rtl_hal_pwrseqcmdparsing(): PWR_CMD_DELAY\n"); + if (GET_PWR_CFG_VALUE(cfg_cmd) == + PWRSEQ_DELAY_US) + udelay(GET_PWR_CFG_OFFSET(cfg_cmd)); + else + mdelay(GET_PWR_CFG_OFFSET(cfg_cmd)); + break; + case PWR_CMD_END: + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "rtl_hal_pwrseqcmdparsing(): PWR_CMD_END\n"); + return true; + default: + RT_ASSERT(false, + "rtl_hal_pwrseqcmdparsing(): Unknown CMD!!\n"); + break; + } + + } + ary_idx++; + } while (1); + + return true; +} diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/pwrseqcmd.h b/drivers/net/wireless/rtlwifi/rtl8723ae/pwrseqcmd.h new file mode 100644 index 0000000..6e0f3ea --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/pwrseqcmd.h @@ -0,0 +1,98 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL8723E_PWRSEQCMD_H__ +#define __RTL8723E_PWRSEQCMD_H__ + +#include "../wifi.h" +/*--------------------------------------------- + * 3 The value of cmd: 4 bits + *--------------------------------------------- + */ +#define PWR_CMD_READ 0x00 +#define PWR_CMD_WRITE 0x01 +#define PWR_CMD_POLLING 0x02 +#define PWR_CMD_DELAY 0x03 +#define PWR_CMD_END 0x04 + +/* define the base address of each block */ +#define PWR_BASEADDR_MAC 0x00 +#define PWR_BASEADDR_USB 0x01 +#define PWR_BASEADDR_PCIE 0x02 +#define PWR_BASEADDR_SDIO 0x03 + +#define PWR_INTF_SDIO_MSK BIT(0) +#define PWR_INTF_USB_MSK BIT(1) +#define PWR_INTF_PCI_MSK BIT(2) +#define PWR_INTF_ALL_MSK (BIT(0)|BIT(1)|BIT(2)|BIT(3)) + +#define PWR_FAB_TSMC_MSK BIT(0) +#define PWR_FAB_UMC_MSK BIT(1) +#define PWR_FAB_ALL_MSK (BIT(0)|BIT(1)|BIT(2)|BIT(3)) + +#define PWR_CUT_TESTCHIP_MSK BIT(0) +#define PWR_CUT_A_MSK BIT(1) +#define PWR_CUT_B_MSK BIT(2) +#define PWR_CUT_C_MSK BIT(3) +#define PWR_CUT_D_MSK BIT(4) +#define PWR_CUT_E_MSK BIT(5) +#define PWR_CUT_F_MSK BIT(6) +#define PWR_CUT_G_MSK BIT(7) +#define PWR_CUT_ALL_MSK 0xFF + +enum pwrseq_delay_unit { + PWRSEQ_DELAY_US, + PWRSEQ_DELAY_MS, +}; + +struct wlan_pwr_cfg { + u16 offset; + u8 cut_msk; + u8 fab_msk:4; + u8 interface_msk:4; + u8 base:4; + u8 cmd:4; + u8 msk; + u8 value; +}; + +#define GET_PWR_CFG_OFFSET(__PWR_CMD) (__PWR_CMD.offset) +#define GET_PWR_CFG_CUT_MASK(__PWR_CMD) (__PWR_CMD.cut_msk) +#define GET_PWR_CFG_FAB_MASK(__PWR_CMD) (__PWR_CMD.fab_msk) +#define GET_PWR_CFG_INTF_MASK(__PWR_CMD) (__PWR_CMD.interface_msk) +#define GET_PWR_CFG_BASE(__PWR_CMD) (__PWR_CMD.base) +#define GET_PWR_CFG_CMD(__PWR_CMD) (__PWR_CMD.cmd) +#define GET_PWR_CFG_MASK(__PWR_CMD) (__PWR_CMD.msk) +#define GET_PWR_CFG_VALUE(__PWR_CMD) (__PWR_CMD.value) + +bool rtl_hal_pwrseqcmdparsing(struct rtl_priv *rtlpriv, u8 cut_version, + u8 fab_version, u8 interface_type, + struct wlan_pwr_cfg pwrcfgcmd[]); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/reg.h b/drivers/net/wireless/rtlwifi/rtl8723ae/reg.h new file mode 100644 index 0000000..199da36 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/reg.h @@ -0,0 +1,2097 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL8723E_REG_H__ +#define __RTL8723E_REG_H__ + +#define REG_SYS_ISO_CTRL 0x0000 +#define REG_SYS_FUNC_EN 0x0002 +#define REG_APS_FSMCO 0x0004 +#define REG_SYS_CLKR 0x0008 +#define REG_9346CR 0x000A +#define REG_EE_VPD 0x000C +#define REG_AFE_MISC 0x0010 +#define REG_SPS0_CTRL 0x0011 +#define REG_SPS_OCP_CFG 0x0018 +#define REG_RSV_CTRL 0x001C +#define REG_RF_CTRL 0x001F +#define REG_LDOA15_CTRL 0x0020 +#define REG_LDOV12D_CTRL 0x0021 +#define REG_LDOHCI12_CTRL 0x0022 +#define REG_LPLDO_CTRL 0x0023 +#define REG_AFE_XTAL_CTRL 0x0024 +#define REG_AFE_PLL_CTRL 0x0028 +#define REG_EFUSE_CTRL 0x0030 +#define REG_EFUSE_TEST 0x0034 +#define REG_PWR_DATA 0x0038 +#define REG_CAL_TIMER 0x003C +#define REG_ACLK_MON 0x003E +#define REG_GPIO_MUXCFG 0x0040 +#define REG_GPIO_IO_SEL 0x0042 +#define REG_MAC_PINMUX_CFG 0x0043 +#define REG_GPIO_PIN_CTRL 0x0044 +#define REG_GPIO_INTM 0x0048 +#define REG_LEDCFG0 0x004C +#define REG_LEDCFG1 0x004D +#define REG_LEDCFG2 0x004E +#define REG_LEDCFG3 0x004F +#define REG_FSIMR 0x0050 +#define REG_FSISR 0x0054 +#define REG_GPIO_PIN_CTRL_2 0x0060 +#define REG_GPIO_IO_SEL_2 0x0062 +#define REG_MULTI_FUNC_CTRL 0x0068 + +#define REG_MCUFWDL 0x0080 + +#define REG_HMEBOX_EXT_0 0x0088 +#define REG_HMEBOX_EXT_1 0x008A +#define REG_HMEBOX_EXT_2 0x008C +#define REG_HMEBOX_EXT_3 0x008E + +#define REG_BIST_SCAN 0x00D0 +#define REG_BIST_RPT 0x00D4 +#define REG_BIST_ROM_RPT 0x00D8 +#define REG_USB_SIE_INTF 0x00E0 +#define REG_PCIE_MIO_INTF 0x00E4 +#define REG_PCIE_MIO_INTD 0x00E8 +#define REG_SYS_CFG 0x00F0 +#define REG_GPIO_OUTSTS 0x00F4 + +#define REG_CR 0x0100 +#define REG_PBP 0x0104 +#define REG_TRXDMA_CTRL 0x010C +#define REG_TRXFF_BNDY 0x0114 +#define REG_TRXFF_STATUS 0x0118 +#define REG_RXFF_PTR 0x011C +#define REG_HIMR 0x0120 +#define REG_HISR 0x0124 +#define REG_HIMRE 0x0128 +#define REG_HISRE 0x012C +#define REG_CPWM 0x012F +#define REG_FWIMR 0x0130 +#define REG_FWISR 0x0134 +#define REG_PKTBUF_DBG_CTRL 0x0140 +#define REG_PKTBUF_DBG_DATA_L 0x0144 +#define REG_PKTBUF_DBG_DATA_H 0x0148 + +#define REG_TC0_CTRL 0x0150 +#define REG_TC1_CTRL 0x0154 +#define REG_TC2_CTRL 0x0158 +#define REG_TC3_CTRL 0x015C +#define REG_TC4_CTRL 0x0160 +#define REG_TCUNIT_BASE 0x0164 +#define REG_MBIST_START 0x0174 +#define REG_MBIST_DONE 0x0178 +#define REG_MBIST_FAIL 0x017C +#define REG_C2HEVT_MSG_NORMAL 0x01A0 +#define REG_C2HEVT_MSG_TEST 0x01B8 +#define REG_MCUTST_1 0x01c0 +#define REG_FMETHR 0x01C8 +#define REG_HMETFR 0x01CC +#define REG_HMEBOX_0 0x01D0 +#define REG_HMEBOX_1 0x01D4 +#define REG_HMEBOX_2 0x01D8 +#define REG_HMEBOX_3 0x01DC + +#define REG_LLT_INIT 0x01E0 +#define REG_BB_ACCEESS_CTRL 0x01E8 +#define REG_BB_ACCESS_DATA 0x01EC + +#define REG_RQPN 0x0200 +#define REG_FIFOPAGE 0x0204 +#define REG_TDECTRL 0x0208 +#define REG_TXDMA_OFFSET_CHK 0x020C +#define REG_TXDMA_STATUS 0x0210 +#define REG_RQPN_NPQ 0x0214 + +#define REG_RXDMA_AGG_PG_TH 0x0280 +#define REG_RXPKT_NUM 0x0284 +#define REG_RXDMA_STATUS 0x0288 + +#define REG_PCIE_CTRL_REG 0x0300 +#define REG_INT_MIG 0x0304 +#define REG_BCNQ_DESA 0x0308 +#define REG_HQ_DESA 0x0310 +#define REG_MGQ_DESA 0x0318 +#define REG_VOQ_DESA 0x0320 +#define REG_VIQ_DESA 0x0328 +#define REG_BEQ_DESA 0x0330 +#define REG_BKQ_DESA 0x0338 +#define REG_RX_DESA 0x0340 +#define REG_DBI 0x0348 +#define REG_MDIO 0x0354 +#define REG_DBG_SEL 0x0360 +#define REG_PCIE_HRPWM 0x0361 +#define REG_PCIE_HCPWM 0x0363 +#define REG_UART_CTRL 0x0364 +#define REG_UART_TX_DESA 0x0370 +#define REG_UART_RX_DESA 0x0378 + +#define REG_HDAQ_DESA_NODEF 0x0000 +#define REG_CMDQ_DESA_NODEF 0x0000 + +#define REG_VOQ_INFORMATION 0x0400 +#define REG_VIQ_INFORMATION 0x0404 +#define REG_BEQ_INFORMATION 0x0408 +#define REG_BKQ_INFORMATION 0x040C +#define REG_MGQ_INFORMATION 0x0410 +#define REG_HGQ_INFORMATION 0x0414 +#define REG_BCNQ_INFORMATION 0x0418 + +#define REG_CPU_MGQ_INFORMATION 0x041C +#define REG_FWHW_TXQ_CTRL 0x0420 +#define REG_HWSEQ_CTRL 0x0423 +#define REG_TXPKTBUF_BCNQ_BDNY 0x0424 +#define REG_TXPKTBUF_MGQ_BDNY 0x0425 +#define REG_MULTI_BCNQ_EN 0x0426 +#define REG_MULTI_BCNQ_OFFSET 0x0427 +#define REG_SPEC_SIFS 0x0428 +#define REG_RL 0x042A +#define REG_DARFRC 0x0430 +#define REG_RARFRC 0x0438 +#define REG_RRSR 0x0440 +#define REG_ARFR0 0x0444 +#define REG_ARFR1 0x0448 +#define REG_ARFR2 0x044C +#define REG_ARFR3 0x0450 +#define REG_AGGLEN_LMT 0x0458 +#define REG_AMPDU_MIN_SPACE 0x045C +#define REG_TXPKTBUF_WMAC_LBK_BF_HD 0x045D +#define REG_FAST_EDCA_CTRL 0x0460 +#define REG_RD_RESP_PKT_TH 0x0463 +#define REG_INIRTS_RATE_SEL 0x0480 +#define REG_INIDATA_RATE_SEL 0x0484 +#define REG_POWER_STATUS 0x04A4 +#define REG_POWER_STAGE1 0x04B4 +#define REG_POWER_STAGE2 0x04B8 +#define REG_PKT_LIFE_TIME 0x04C0 +#define REG_STBC_SETTING 0x04C4 +#define REG_PROT_MODE_CTRL 0x04C8 +#define REG_BAR_MODE_CTRL 0x04CC +#define REG_RA_TRY_RATE_AGG_LMT 0x04CF +#define REG_NQOS_SEQ 0x04DC +#define REG_QOS_SEQ 0x04DE +#define REG_NEED_CPU_HANDLE 0x04E0 +#define REG_PKT_LOSE_RPT 0x04E1 +#define REG_PTCL_ERR_STATUS 0x04E2 +#define REG_DUMMY 0x04FC + +#define REG_EDCA_VO_PARAM 0x0500 +#define REG_EDCA_VI_PARAM 0x0504 +#define REG_EDCA_BE_PARAM 0x0508 +#define REG_EDCA_BK_PARAM 0x050C +#define REG_BCNTCFG 0x0510 +#define REG_PIFS 0x0512 +#define REG_RDG_PIFS 0x0513 +#define REG_SIFS_CTX 0x0514 +#define REG_SIFS_TRX 0x0516 +#define REG_AGGR_BREAK_TIME 0x051A +#define REG_SLOT 0x051B +#define REG_TX_PTCL_CTRL 0x0520 +#define REG_TXPAUSE 0x0522 +#define REG_DIS_TXREQ_CLR 0x0523 +#define REG_RD_CTRL 0x0524 +#define REG_TBTT_PROHIBIT 0x0540 +#define REG_RD_NAV_NXT 0x0544 +#define REG_NAV_PROT_LEN 0x0546 +#define REG_BCN_CTRL 0x0550 +#define REG_USTIME_TSF 0x0551 +#define REG_MBID_NUM 0x0552 +#define REG_DUAL_TSF_RST 0x0553 +#define REG_BCN_INTERVAL 0x0554 +#define REG_MBSSID_BCN_SPACE 0x0554 +#define REG_DRVERLYINT 0x0558 +#define REG_BCNDMATIM 0x0559 +#define REG_ATIMWND 0x055A +#define REG_BCN_MAX_ERR 0x055D +#define REG_RXTSF_OFFSET_CCK 0x055E +#define REG_RXTSF_OFFSET_OFDM 0x055F +#define REG_TSFTR 0x0560 +#define REG_INIT_TSFTR 0x0564 +#define REG_PSTIMER 0x0580 +#define REG_TIMER0 0x0584 +#define REG_TIMER1 0x0588 +#define REG_ACMHWCTRL 0x05C0 +#define REG_ACMRSTCTRL 0x05C1 +#define REG_ACMAVG 0x05C2 +#define REG_VO_ADMTIME 0x05C4 +#define REG_VI_ADMTIME 0x05C6 +#define REG_BE_ADMTIME 0x05C8 +#define REG_EDCA_RANDOM_GEN 0x05CC +#define REG_SCH_TXCMD 0x05D0 + +#define REG_APSD_CTRL 0x0600 +#define REG_BWOPMODE 0x0603 +#define REG_TCR 0x0604 +#define REG_RCR 0x0608 +#define REG_RX_PKT_LIMIT 0x060C +#define REG_RX_DLK_TIME 0x060D +#define REG_RX_DRVINFO_SZ 0x060F + +#define REG_MACID 0x0610 +#define REG_BSSID 0x0618 +#define REG_MAR 0x0620 +#define REG_MBIDCAMCFG 0x0628 + +#define REG_USTIME_EDCA 0x0638 +#define REG_MAC_SPEC_SIFS 0x063A +#define REG_RESP_SIFS_CCK 0x063C +#define REG_RESP_SIFS_OFDM 0x063E +#define REG_ACKTO 0x0640 +#define REG_CTS2TO 0x0641 +#define REG_EIFS 0x0642 + +#define REG_NAV_CTRL 0x0650 +#define REG_BACAMCMD 0x0654 +#define REG_BACAMCONTENT 0x0658 +#define REG_LBDLY 0x0660 +#define REG_FWDLY 0x0661 +#define REG_RXERR_RPT 0x0664 +#define REG_WMAC_TRXPTCL_CTL 0x0668 + +#define REG_CAMCMD 0x0670 +#define REG_CAMWRITE 0x0674 +#define REG_CAMREAD 0x0678 +#define REG_CAMDBG 0x067C +#define REG_SECCFG 0x0680 + +#define REG_WOW_CTRL 0x0690 +#define REG_PSSTATUS 0x0691 +#define REG_PS_RX_INFO 0x0692 +#define REG_LPNAV_CTRL 0x0694 +#define REG_WKFMCAM_CMD 0x0698 +#define REG_WKFMCAM_RWD 0x069C +#define REG_RXFLTMAP0 0x06A0 +#define REG_RXFLTMAP1 0x06A2 +#define REG_RXFLTMAP2 0x06A4 +#define REG_BCN_PSR_RPT 0x06A8 +#define REG_CALB32K_CTRL 0x06AC +#define REG_PKT_MON_CTRL 0x06B4 +#define REG_BT_COEX_TABLE 0x06C0 +#define REG_WMAC_RESP_TXINFO 0x06D8 + +#define REG_USB_INFO 0xFE17 +#define REG_USB_SPECIAL_OPTION 0xFE55 +#define REG_USB_DMA_AGG_TO 0xFE5B +#define REG_USB_AGG_TO 0xFE5C +#define REG_USB_AGG_TH 0xFE5D + +#define REG_TEST_USB_TXQS 0xFE48 +#define REG_TEST_SIE_VID 0xFE60 +#define REG_TEST_SIE_PID 0xFE62 +#define REG_TEST_SIE_OPTIONAL 0xFE64 +#define REG_TEST_SIE_CHIRP_K 0xFE65 +#define REG_TEST_SIE_PHY 0xFE66 +#define REG_TEST_SIE_MAC_ADDR 0xFE70 +#define REG_TEST_SIE_STRING 0xFE80 + +#define REG_NORMAL_SIE_VID 0xFE60 +#define REG_NORMAL_SIE_PID 0xFE62 +#define REG_NORMAL_SIE_OPTIONAL 0xFE64 +#define REG_NORMAL_SIE_EP 0xFE65 +#define REG_NORMAL_SIE_PHY 0xFE68 +#define REG_NORMAL_SIE_MAC_ADDR 0xFE70 +#define REG_NORMAL_SIE_STRING 0xFE80 + +#define CR9346 REG_9346CR +#define MSR (REG_CR + 2) +#define ISR REG_HISR +#define TSFR REG_TSFTR + +#define MACIDR0 REG_MACID +#define MACIDR4 (REG_MACID + 4) + +#define PBP REG_PBP + +#define IDR0 MACIDR0 +#define IDR4 MACIDR4 + +#define UNUSED_REGISTER 0x1BF +#define DCAM UNUSED_REGISTER +#define PSR UNUSED_REGISTER +#define BBADDR UNUSED_REGISTER +#define PHYDATAR UNUSED_REGISTER + +#define INVALID_BBRF_VALUE 0x12345678 + +#define MAX_MSS_DENSITY_2T 0x13 +#define MAX_MSS_DENSITY_1T 0x0A + +#define CMDEEPROM_EN BIT(5) +#define CMDEEPROM_SEL BIT(4) +#define CMD9346CR_9356SEL BIT(4) +#define AUTOLOAD_EEPROM (CMDEEPROM_EN|CMDEEPROM_SEL) +#define AUTOLOAD_EFUSE CMDEEPROM_EN + +#define GPIOSEL_GPIO 0 +#define GPIOSEL_ENBT BIT(5) + +#define GPIO_IN REG_GPIO_PIN_CTRL +#define GPIO_OUT (REG_GPIO_PIN_CTRL+1) +#define GPIO_IO_SEL (REG_GPIO_PIN_CTRL+2) +#define GPIO_MOD (REG_GPIO_PIN_CTRL+3) + +#define MSR_NOLINK 0x00 +#define MSR_ADHOC 0x01 +#define MSR_INFRA 0x02 +#define MSR_AP 0x03 + +#define RRSR_RSC_OFFSET 21 +#define RRSR_SHORT_OFFSET 23 +#define RRSR_RSC_BW_40M 0x600000 +#define RRSR_RSC_UPSUBCHNL 0x400000 +#define RRSR_RSC_LOWSUBCHNL 0x200000 +#define RRSR_SHORT 0x800000 +#define RRSR_1M BIT(0) +#define RRSR_2M BIT(1) +#define RRSR_5_5M BIT(2) +#define RRSR_11M BIT(3) +#define RRSR_6M BIT(4) +#define RRSR_9M BIT(5) +#define RRSR_12M BIT(6) +#define RRSR_18M BIT(7) +#define RRSR_24M BIT(8) +#define RRSR_36M BIT(9) +#define RRSR_48M BIT(10) +#define RRSR_54M BIT(11) +#define RRSR_MCS0 BIT(12) +#define RRSR_MCS1 BIT(13) +#define RRSR_MCS2 BIT(14) +#define RRSR_MCS3 BIT(15) +#define RRSR_MCS4 BIT(16) +#define RRSR_MCS5 BIT(17) +#define RRSR_MCS6 BIT(18) +#define RRSR_MCS7 BIT(19) +#define BRSR_ACKSHORTPMB BIT(23) + +#define RATR_1M 0x00000001 +#define RATR_2M 0x00000002 +#define RATR_55M 0x00000004 +#define RATR_11M 0x00000008 +#define RATR_6M 0x00000010 +#define RATR_9M 0x00000020 +#define RATR_12M 0x00000040 +#define RATR_18M 0x00000080 +#define RATR_24M 0x00000100 +#define RATR_36M 0x00000200 +#define RATR_48M 0x00000400 +#define RATR_54M 0x00000800 +#define RATR_MCS0 0x00001000 +#define RATR_MCS1 0x00002000 +#define RATR_MCS2 0x00004000 +#define RATR_MCS3 0x00008000 +#define RATR_MCS4 0x00010000 +#define RATR_MCS5 0x00020000 +#define RATR_MCS6 0x00040000 +#define RATR_MCS7 0x00080000 +#define RATR_MCS8 0x00100000 +#define RATR_MCS9 0x00200000 +#define RATR_MCS10 0x00400000 +#define RATR_MCS11 0x00800000 +#define RATR_MCS12 0x01000000 +#define RATR_MCS13 0x02000000 +#define RATR_MCS14 0x04000000 +#define RATR_MCS15 0x08000000 + +#define RATE_ALL_CCK (RATR_1M | RATR_2M | RATR_55M | RATR_11M) +#define RATE_ALL_OFDM_AG (RATR_6M | RATR_9M | RATR_12M | RATR_18M |\ + RATR_24M | RATR_36M | RATR_48M | RATR_54M) +#define RATE_ALL_OFDM_1SS (RATR_MCS0 | RATR_MCS1 | RATR_MCS2 |\ + RATR_MCS3 | RATR_MCS4 | RATR_MCS5 |\ + RATR_MCS6 | RATR_MCS7) +#define RATE_ALL_OFDM_2SS (RATR_MCS8 | RATR_MCS9 | RATR_MCS10 |\ + RATR_MCS11 | RATR_MCS12 | RATR_MCS13 |\ + RATR_MCS14 | RATR_MCS15) + +#define BW_OPMODE_20MHZ BIT(2) +#define BW_OPMODE_5G BIT(1) +#define BW_OPMODE_11J BIT(0) + +#define CAM_VALID BIT(15) +#define CAM_NOTVALID 0x0000 +#define CAM_USEDK BIT(5) + +#define CAM_NONE 0x0 +#define CAM_WEP40 0x01 +#define CAM_TKIP 0x02 +#define CAM_AES 0x04 +#define CAM_WEP104 0x05 + +#define TOTAL_CAM_ENTRY 32 +#define HALF_CAM_ENTRY 16 + +#define CAM_WRITE BIT(16) +#define CAM_READ 0x00000000 +#define CAM_POLLINIG BIT(31) + +#define SCR_USEDK 0x01 +#define SCR_TXSEC_ENABLE 0x02 +#define SCR_RXSEC_ENABLE 0x04 + +#define WOW_PMEN BIT(0) +#define WOW_WOMEN BIT(1) +#define WOW_MAGIC BIT(2) +#define WOW_UWF BIT(3) + +#define IMR8190_DISABLED 0x0 +#define IMR_BCNDMAINT6 BIT(31) +#define IMR_BCNDMAINT5 BIT(30) +#define IMR_BCNDMAINT4 BIT(29) +#define IMR_BCNDMAINT3 BIT(28) +#define IMR_BCNDMAINT2 BIT(27) +#define IMR_BCNDMAINT1 BIT(26) +#define IMR_BCNDOK8 BIT(25) +#define IMR_BCNDOK7 BIT(24) +#define IMR_BCNDOK6 BIT(23) +#define IMR_BCNDOK5 BIT(22) +#define IMR_BCNDOK4 BIT(21) +#define IMR_BCNDOK3 BIT(20) +#define IMR_BCNDOK2 BIT(19) +#define IMR_BCNDOK1 BIT(18) +#define IMR_TIMEOUT2 BIT(17) +#define IMR_TIMEOUT1 BIT(16) +#define IMR_TXFOVW BIT(15) +#define IMR_PSTIMEOUT BIT(14) +#define IMR_BCNINT BIT(13) +#define IMR_RXFOVW BIT(12) +#define IMR_RDU BIT(11) +#define IMR_ATIMEND BIT(10) +#define IMR_BDOK BIT(9) +#define IMR_HIGHDOK BIT(8) +#define IMR_TBDOK BIT(7) +#define IMR_MGNTDOK BIT(6) +#define IMR_TBDER BIT(5) +#define IMR_BKDOK BIT(4) +#define IMR_BEDOK BIT(3) +#define IMR_VIDOK BIT(2) +#define IMR_VODOK BIT(1) +#define IMR_ROK BIT(0) + +#define IMR_TXERR BIT(11) +#define IMR_RXERR BIT(10) +#define IMR_CPWM BIT(8) +#define IMR_OCPINT BIT(1) +#define IMR_WLANOFF BIT(0) + +/* 8723E series PCIE Host IMR/ISR bit */ +/* IMR DW0 Bit 0-31 */ +#define PHIMR_TIMEOUT2 BIT(31) +#define PHIMR_TIMEOUT1 BIT(30) +#define PHIMR_PSTIMEOUT BIT(29) +#define PHIMR_GTINT4 BIT(28) +#define PHIMR_GTINT3 BIT(27) +#define PHIMR_TXBCNERR BIT(26) +#define PHIMR_TXBCNOK BIT(25) +#define PHIMR_TSF_BIT32_TOGGLE BIT(24) +#define PHIMR_BCNDMAINT3 BIT(23) +#define PHIMR_BCNDMAINT2 BIT(22) +#define PHIMR_BCNDMAINT1 BIT(21) +#define PHIMR_BCNDMAINT0 BIT(20) +#define PHIMR_BCNDOK3 BIT(19) +#define PHIMR_BCNDOK2 BIT(18) +#define PHIMR_BCNDOK1 BIT(17) +#define PHIMR_BCNDOK0 BIT(16) +#define PHIMR_HSISR_IND_ON BIT(15) +#define PHIMR_BCNDMAINT_E BIT(14) +#define PHIMR_ATIMEND_E BIT(13) +#define PHIMR_ATIM_CTW_END BIT(12) +#define PHIMR_HISRE_IND BIT(11) +#define PHIMR_C2HCMD BIT(10) +#define PHIMR_CPWM2 BIT(9) +#define PHIMR_CPWM BIT(8) +#define PHIMR_HIGHDOK BIT(7) +#define PHIMR_MGNTDOK BIT(6) +#define PHIMR_BKDOK BIT(5) +#define PHIMR_BEDOK BIT(4) +#define PHIMR_VIDOK BIT(3) +#define PHIMR_VODOK BIT(2) +#define PHIMR_RDU BIT(1) +#define PHIMR_ROK BIT(0) + +/* PCIE Host Interrupt Status Extension bit */ +#define PHIMR_BCNDMAINT7 BIT(23) +#define PHIMR_BCNDMAINT6 BIT(22) +#define PHIMR_BCNDMAINT5 BIT(21) +#define PHIMR_BCNDMAINT4 BIT(20) +#define PHIMR_BCNDOK7 BIT(19) +#define PHIMR_BCNDOK6 BIT(18) +#define PHIMR_BCNDOK5 BIT(17) +#define PHIMR_BCNDOK4 BIT(16) +/* bit12-15: RSVD */ +#define PHIMR_TXERR BIT(11) +#define PHIMR_RXERR BIT(10) +#define PHIMR_TXFOVW BIT(9) +#define PHIMR_RXFOVW BIT(8) +/* bit2-7: RSV */ +#define PHIMR_OCPINT BIT(1) + +#define HWSET_MAX_SIZE 256 +#define EFUSE_MAX_SECTION 32 +#define EFUSE_REAL_CONTENT_LEN 512 +#define EFUSE_OOB_PROTECT_BYTES 15 + +#define EEPROM_DEFAULT_TSSI 0x0 +#define EEPROM_DEFAULT_TXPOWERDIFF 0x0 +#define EEPROM_DEFAULT_CRYSTALCAP 0x5 +#define EEPROM_DEFAULT_BOARDTYPE 0x02 +#define EEPROM_DEFAULT_TXPOWER 0x1010 +#define EEPROM_DEFAULT_HT2T_TXPWR 0x10 + +#define EEPROM_DEFAULT_LEGACYHTTXPOWERDIFF 0x3 +#define EEPROM_DEFAULT_THERMALMETER 0x12 +#define EEPROM_DEFAULT_ANTTXPOWERDIFF 0x0 +#define EEPROM_DEFAULT_TXPWDIFF_CRYSTALCAP 0x5 +#define EEPROM_DEFAULT_TXPOWERLEVEL 0x22 +#define EEPROM_DEFAULT_HT40_2SDIFF 0x0 +#define EEPROM_DEFAULT_HT20_DIFF 2 +#define EEPROM_DEFAULT_LEGACYHTTXPOWERDIFF 0x3 +#define EEPROM_DEFAULT_HT40_PWRMAXOFFSET 0 +#define EEPROM_DEFAULT_HT20_PWRMAXOFFSET 0 + + +#define EEPROM_DEFAULT_PID 0x1234 +#define EEPROM_DEFAULT_VID 0x5678 +#define EEPROM_DEFAULT_CUSTOMERID 0xAB +#define EEPROM_DEFAULT_SUBCUSTOMERID 0xCD +#define EEPROM_DEFAULT_VERSION 0 + +#define EEPROM_CHANNEL_PLAN_FCC 0x0 +#define EEPROM_CHANNEL_PLAN_IC 0x1 +#define EEPROM_CHANNEL_PLAN_ETSI 0x2 +#define EEPROM_CHANNEL_PLAN_SPAIN 0x3 +#define EEPROM_CHANNEL_PLAN_FRANCE 0x4 +#define EEPROM_CHANNEL_PLAN_MKK 0x5 +#define EEPROM_CHANNEL_PLAN_MKK1 0x6 +#define EEPROM_CHANNEL_PLAN_ISRAEL 0x7 +#define EEPROM_CHANNEL_PLAN_TELEC 0x8 +#define EEPROM_CHANNEL_PLAN_GLOBAL_DOMAIN 0x9 +#define EEPROM_CHANNEL_PLAN_WORLD_WIDE_13 0xA +#define EEPROM_CHANNEL_PLAN_NCC 0xB +#define EEPROM_CHANNEL_PLAN_BY_HW_MASK 0x80 + +#define EEPROM_CID_DEFAULT 0x0 +#define EEPROM_CID_TOSHIBA 0x4 +#define EEPROM_CID_CCX 0x10 +#define EEPROM_CID_QMI 0x0D +#define EEPROM_CID_WHQL 0xFE + +#define RTL8192_EEPROM_ID 0x8129 + +#define RTL8190_EEPROM_ID 0x8129 +#define EEPROM_HPON 0x02 +#define EEPROM_CLK 0x06 +#define EEPROM_TESTR 0x08 + +#define EEPROM_VID 0x49 +#define EEPROM_DID 0x4B +#define EEPROM_SVID 0x4D +#define EEPROM_SMID 0x4F + +#define EEPROM_MAC_ADDR 0x67 + +#define EEPROM_CCK_TX_PWR_INX 0x5A +#define EEPROM_HT40_1S_TX_PWR_INX 0x60 +#define EEPROM_HT40_2S_TX_PWR_INX_DIFF 0x66 +#define EEPROM_HT20_TX_PWR_INX_DIFF 0x69 +#define EEPROM_OFDM_TX_PWR_INX_DIFF 0x6C +#define EEPROM_HT40_MAX_PWR_OFFSET 0x25 +#define EEPROM_HT20_MAX_PWR_OFFSET 0x22 + +#define EEPROM_THERMAL_METER 0x2a +#define EEPROM_XTAL_K 0x78 +#define EEPROM_RF_OPT1 0x79 +#define EEPROM_RF_OPT2 0x7A +#define EEPROM_RF_OPT3 0x7B +#define EEPROM_RF_OPT4 0x7C +#define EEPROM_CHANNEL_PLAN 0x28 +#define EEPROM_VERSION 0x30 +#define EEPROM_CUSTOMER_ID 0x31 + +#define EEPROM_PWRDIFF 0x54 + +#define EEPROM_TXPOWERCCK 0x10 +#define EEPROM_TXPOWERHT40_1S 0x16 +#define EEPROM_TXPOWERHT40_2SDIFF 0x66 +#define EEPROM_TXPOWERHT20DIFF 0x1C +#define EEPROM_TXPOWER_OFDMDIFF 0x1F + +#define EEPROM_TXPWR_GROUP 0x22 + +#define EEPROM_TSSI_A 0x29 +#define EEPROM_TSSI_B 0x77 + +#define EEPROM_CHANNELPLAN 0x28 + +#define RF_OPTION1 0x2B +#define RF_OPTION2 0x2C +#define RF_OPTION3 0x2D +#define RF_OPTION4 0x2E + +#define STOPBECON BIT(6) +#define STOPHIGHT BIT(5) +#define STOPMGT BIT(4) +#define STOPVO BIT(3) +#define STOPVI BIT(2) +#define STOPBE BIT(1) +#define STOPBK BIT(0) + +#define RCR_APPFCS BIT(31) +#define RCR_APP_MIC BIT(30) +#define RCR_APP_ICV BIT(29) +#define RCR_APP_PHYST_RXFF BIT(28) +#define RCR_APP_BA_SSN BIT(27) +#define RCR_ENMBID BIT(24) +#define RCR_LSIGEN BIT(23) +#define RCR_MFBEN BIT(22) +#define RCR_HTC_LOC_CTRL BIT(14) +#define RCR_AMF BIT(13) +#define RCR_ACF BIT(12) +#define RCR_ADF BIT(11) +#define RCR_AICV BIT(9) +#define RCR_ACRC32 BIT(8) +#define RCR_CBSSID_BCN BIT(7) +#define RCR_CBSSID_DATA BIT(6) +#define RCR_CBSSID RCR_CBSSID_DATA +#define RCR_APWRMGT BIT(5) +#define RCR_ADD3 BIT(4) +#define RCR_AB BIT(3) +#define RCR_AM BIT(2) +#define RCR_APM BIT(1) +#define RCR_AAP BIT(0) +#define RCR_MXDMA_OFFSET 8 +#define RCR_FIFO_OFFSET 13 + +#define RSV_CTRL 0x001C +#define RD_CTRL 0x0524 + +#define REG_USB_INFO 0xFE17 +#define REG_USB_SPECIAL_OPTION 0xFE55 +#define REG_USB_DMA_AGG_TO 0xFE5B +#define REG_USB_AGG_TO 0xFE5C +#define REG_USB_AGG_TH 0xFE5D + +#define REG_USB_VID 0xFE60 +#define REG_USB_PID 0xFE62 +#define REG_USB_OPTIONAL 0xFE64 +#define REG_USB_CHIRP_K 0xFE65 +#define REG_USB_PHY 0xFE66 +#define REG_USB_MAC_ADDR 0xFE70 +#define REG_USB_HRPWM 0xFE58 +#define REG_USB_HCPWM 0xFE57 + +#define SW18_FPWM BIT(3) + +#define ISO_MD2PP BIT(0) +#define ISO_UA2USB BIT(1) +#define ISO_UD2CORE BIT(2) +#define ISO_PA2PCIE BIT(3) +#define ISO_PD2CORE BIT(4) +#define ISO_IP2MAC BIT(5) +#define ISO_DIOP BIT(6) +#define ISO_DIOE BIT(7) +#define ISO_EB2CORE BIT(8) +#define ISO_DIOR BIT(9) + +#define PWC_EV25V BIT(14) +#define PWC_EV12V BIT(15) + +#define FEN_BBRSTB BIT(0) +#define FEN_BB_GLB_RSTn BIT(1) +#define FEN_USBA BIT(2) +#define FEN_UPLL BIT(3) +#define FEN_USBD BIT(4) +#define FEN_DIO_PCIE BIT(5) +#define FEN_PCIEA BIT(6) +#define FEN_PPLL BIT(7) +#define FEN_PCIED BIT(8) +#define FEN_DIOE BIT(9) +#define FEN_CPUEN BIT(10) +#define FEN_DCORE BIT(11) +#define FEN_ELDR BIT(12) +#define FEN_DIO_RF BIT(13) +#define FEN_HWPDN BIT(14) +#define FEN_MREGEN BIT(15) + +#define PFM_LDALL BIT(0) +#define PFM_ALDN BIT(1) +#define PFM_LDKP BIT(2) +#define PFM_WOWL BIT(3) +#define EnPDN BIT(4) +#define PDN_PL BIT(5) +#define APFM_ONMAC BIT(8) +#define APFM_OFF BIT(9) +#define APFM_RSM BIT(10) +#define AFSM_HSUS BIT(11) +#define AFSM_PCIE BIT(12) +#define APDM_MAC BIT(13) +#define APDM_HOST BIT(14) +#define APDM_HPDN BIT(15) +#define RDY_MACON BIT(16) +#define SUS_HOST BIT(17) +#define ROP_ALD BIT(20) +#define ROP_PWR BIT(21) +#define ROP_SPS BIT(22) +#define SOP_MRST BIT(25) +#define SOP_FUSE BIT(26) +#define SOP_ABG BIT(27) +#define SOP_AMB BIT(28) +#define SOP_RCK BIT(29) +#define SOP_A8M BIT(30) +#define XOP_BTCK BIT(31) + +#define ANAD16V_EN BIT(0) +#define ANA8M BIT(1) +#define MACSLP BIT(4) +#define LOADER_CLK_EN BIT(5) +#define _80M_SSC_DIS BIT(7) +#define _80M_SSC_EN_HO BIT(8) +#define PHY_SSC_RSTB BIT(9) +#define SEC_CLK_EN BIT(10) +#define MAC_CLK_EN BIT(11) +#define SYS_CLK_EN BIT(12) +#define RING_CLK_EN BIT(13) + +#define BOOT_FROM_EEPROM BIT(4) +#define EEPROM_EN BIT(5) + +#define AFE_BGEN BIT(0) +#define AFE_MBEN BIT(1) +#define MAC_ID_EN BIT(7) + +#define WLOCK_ALL BIT(0) +#define WLOCK_00 BIT(1) +#define WLOCK_04 BIT(2) +#define WLOCK_08 BIT(3) +#define WLOCK_40 BIT(4) +#define R_DIS_PRST_0 BIT(5) +#define R_DIS_PRST_1 BIT(6) +#define LOCK_ALL_EN BIT(7) + +#define RF_EN BIT(0) +#define RF_RSTB BIT(1) +#define RF_SDMRSTB BIT(2) + +#define LDA15_EN BIT(0) +#define LDA15_STBY BIT(1) +#define LDA15_OBUF BIT(2) +#define LDA15_REG_VOS BIT(3) +#define _LDA15_VOADJ(x) (((x) & 0x7) << 4) + +#define LDV12_EN BIT(0) +#define LDV12_SDBY BIT(1) +#define LPLDO_HSM BIT(2) +#define LPLDO_LSM_DIS BIT(3) +#define _LDV12_VADJ(x) (((x) & 0xF) << 4) + +#define XTAL_EN BIT(0) +#define XTAL_BSEL BIT(1) +#define _XTAL_BOSC(x) (((x) & 0x3) << 2) +#define _XTAL_CADJ(x) (((x) & 0xF) << 4) +#define XTAL_GATE_USB BIT(8) +#define _XTAL_USB_DRV(x) (((x) & 0x3) << 9) +#define XTAL_GATE_AFE BIT(11) +#define _XTAL_AFE_DRV(x) (((x) & 0x3) << 12) +#define XTAL_RF_GATE BIT(14) +#define _XTAL_RF_DRV(x) (((x) & 0x3) << 15) +#define XTAL_GATE_DIG BIT(17) +#define _XTAL_DIG_DRV(x) (((x) & 0x3) << 18) +#define XTAL_BT_GATE BIT(20) +#define _XTAL_BT_DRV(x) (((x) & 0x3) << 21) +#define _XTAL_GPIO(x) (((x) & 0x7) << 23) + +#define CKDLY_AFE BIT(26) +#define CKDLY_USB BIT(27) +#define CKDLY_DIG BIT(28) +#define CKDLY_BT BIT(29) + +#define APLL_EN BIT(0) +#define APLL_320_EN BIT(1) +#define APLL_FREF_SEL BIT(2) +#define APLL_EDGE_SEL BIT(3) +#define APLL_WDOGB BIT(4) +#define APLL_LPFEN BIT(5) + +#define APLL_REF_CLK_13MHZ 0x1 +#define APLL_REF_CLK_19_2MHZ 0x2 +#define APLL_REF_CLK_20MHZ 0x3 +#define APLL_REF_CLK_25MHZ 0x4 +#define APLL_REF_CLK_26MHZ 0x5 +#define APLL_REF_CLK_38_4MHZ 0x6 +#define APLL_REF_CLK_40MHZ 0x7 + +#define APLL_320EN BIT(14) +#define APLL_80EN BIT(15) +#define APLL_1MEN BIT(24) + +#define ALD_EN BIT(18) +#define EF_PD BIT(19) +#define EF_FLAG BIT(31) + +#define EF_TRPT BIT(7) +#define LDOE25_EN BIT(31) + +#define RSM_EN BIT(0) +#define Timer_EN BIT(4) + +#define TRSW0EN BIT(2) +#define TRSW1EN BIT(3) +#define EROM_EN BIT(4) +#define EnBT BIT(5) +#define EnUart BIT(8) +#define Uart_910 BIT(9) +#define EnPMAC BIT(10) +#define SIC_SWRST BIT(11) +#define EnSIC BIT(12) +#define SIC_23 BIT(13) +#define EnHDP BIT(14) +#define SIC_LBK BIT(15) + +#define LED0PL BIT(4) +#define LED1PL BIT(12) +#define LED0DIS BIT(7) + +#define MCUFWDL_EN BIT(0) +#define MCUFWDL_RDY BIT(1) +#define FWDL_ChkSum_rpt BIT(2) +#define MACINI_RDY BIT(3) +#define BBINI_RDY BIT(4) +#define RFINI_RDY BIT(5) +#define WINTINI_RDY BIT(6) +#define CPRST BIT(23) + +#define XCLK_VLD BIT(0) +#define ACLK_VLD BIT(1) +#define UCLK_VLD BIT(2) +#define PCLK_VLD BIT(3) +#define PCIRSTB BIT(4) +#define V15_VLD BIT(5) +#define TRP_B15V_EN BIT(7) +#define SIC_IDLE BIT(8) +#define BD_MAC2 BIT(9) +#define BD_MAC1 BIT(10) +#define IC_MACPHY_MODE BIT(11) +#define BT_FUNC BIT(16) +#define VENDOR_ID BIT(19) +#define PAD_HWPD_IDN BIT(22) +#define TRP_VAUX_EN BIT(23) +#define TRP_BT_EN BIT(24) +#define BD_PKG_SEL BIT(25) +#define BD_HCI_SEL BIT(26) +#define TYPE_ID BIT(27) + +#define CHIP_VER_RTL_MASK 0xF000 +#define CHIP_VER_RTL_SHIFT 12 + +#define REG_LBMODE (REG_CR + 3) + +#define HCI_TXDMA_EN BIT(0) +#define HCI_RXDMA_EN BIT(1) +#define TXDMA_EN BIT(2) +#define RXDMA_EN BIT(3) +#define PROTOCOL_EN BIT(4) +#define SCHEDULE_EN BIT(5) +#define MACTXEN BIT(6) +#define MACRXEN BIT(7) +#define ENSWBCN BIT(8) +#define ENSEC BIT(9) + +#define _NETTYPE(x) (((x) & 0x3) << 16) +#define MASK_NETTYPE 0x30000 +#define NT_NO_LINK 0x0 +#define NT_LINK_AD_HOC 0x1 +#define NT_LINK_AP 0x2 +#define NT_AS_AP 0x3 + +#define _LBMODE(x) (((x) & 0xF) << 24) +#define MASK_LBMODE 0xF000000 +#define LOOPBACK_NORMAL 0x0 +#define LOOPBACK_IMMEDIATELY 0xB +#define LOOPBACK_MAC_DELAY 0x3 +#define LOOPBACK_PHY 0x1 +#define LOOPBACK_DMA 0x7 + +#define GET_RX_PAGE_SIZE(value) ((value) & 0xF) +#define GET_TX_PAGE_SIZE(value) (((value) & 0xF0) >> 4) +#define _PSRX_MASK 0xF +#define _PSTX_MASK 0xF0 +#define _PSRX(x) (x) +#define _PSTX(x) ((x) << 4) + +#define PBP_64 0x0 +#define PBP_128 0x1 +#define PBP_256 0x2 +#define PBP_512 0x3 +#define PBP_1024 0x4 + +#define RXDMA_ARBBW_EN BIT(0) +#define RXSHFT_EN BIT(1) +#define RXDMA_AGG_EN BIT(2) +#define QS_VO_QUEUE BIT(8) +#define QS_VI_QUEUE BIT(9) +#define QS_BE_QUEUE BIT(10) +#define QS_BK_QUEUE BIT(11) +#define QS_MANAGER_QUEUE BIT(12) +#define QS_HIGH_QUEUE BIT(13) + +#define HQSEL_VOQ BIT(0) +#define HQSEL_VIQ BIT(1) +#define HQSEL_BEQ BIT(2) +#define HQSEL_BKQ BIT(3) +#define HQSEL_MGTQ BIT(4) +#define HQSEL_HIQ BIT(5) + +#define _TXDMA_HIQ_MAP(x) (((x)&0x3) << 14) +#define _TXDMA_MGQ_MAP(x) (((x)&0x3) << 12) +#define _TXDMA_BKQ_MAP(x) (((x)&0x3) << 10) +#define _TXDMA_BEQ_MAP(x) (((x)&0x3) << 8) +#define _TXDMA_VIQ_MAP(x) (((x)&0x3) << 6) +#define _TXDMA_VOQ_MAP(x) (((x)&0x3) << 4) + +#define QUEUE_LOW 1 +#define QUEUE_NORMAL 2 +#define QUEUE_HIGH 3 + +#define _LLT_NO_ACTIVE 0x0 +#define _LLT_WRITE_ACCESS 0x1 +#define _LLT_READ_ACCESS 0x2 + +#define _LLT_INIT_DATA(x) ((x) & 0xFF) +#define _LLT_INIT_ADDR(x) (((x) & 0xFF) << 8) +#define _LLT_OP(x) (((x) & 0x3) << 30) +#define _LLT_OP_VALUE(x) (((x) >> 30) & 0x3) + +#define BB_WRITE_READ_MASK (BIT(31) | BIT(30)) +#define BB_WRITE_EN BIT(30) +#define BB_READ_EN BIT(31) + +#define _HPQ(x) ((x) & 0xFF) +#define _LPQ(x) (((x) & 0xFF) << 8) +#define _PUBQ(x) (((x) & 0xFF) << 16) +#define _NPQ(x) ((x) & 0xFF) + +#define HPQ_PUBLIC_DIS BIT(24) +#define LPQ_PUBLIC_DIS BIT(25) +#define LD_RQPN BIT(31) + +#define BCN_VALID BIT(16) +#define BCN_HEAD(x) (((x) & 0xFF) << 8) +#define BCN_HEAD_MASK 0xFF00 + +#define BLK_DESC_NUM_SHIFT 4 +#define BLK_DESC_NUM_MASK 0xF + +#define DROP_DATA_EN BIT(9) + +#define EN_AMPDU_RTY_NEW BIT(7) + +#define _INIRTSMCS_SEL(x) ((x) & 0x3F) + +#define _SPEC_SIFS_CCK(x) ((x) & 0xFF) +#define _SPEC_SIFS_OFDM(x) (((x) & 0xFF) << 8) + +#define RATE_REG_BITMAP_ALL 0xFFFFF + +#define _RRSC_BITMAP(x) ((x) & 0xFFFFF) + +#define _RRSR_RSC(x) (((x) & 0x3) << 21) +#define RRSR_RSC_RESERVED 0x0 +#define RRSR_RSC_UPPER_SUBCHANNEL 0x1 +#define RRSR_RSC_LOWER_SUBCHANNEL 0x2 +#define RRSR_RSC_DUPLICATE_MODE 0x3 + +#define USE_SHORT_G1 BIT(20) + +#define _AGGLMT_MCS0(x) ((x) & 0xF) +#define _AGGLMT_MCS1(x) (((x) & 0xF) << 4) +#define _AGGLMT_MCS2(x) (((x) & 0xF) << 8) +#define _AGGLMT_MCS3(x) (((x) & 0xF) << 12) +#define _AGGLMT_MCS4(x) (((x) & 0xF) << 16) +#define _AGGLMT_MCS5(x) (((x) & 0xF) << 20) +#define _AGGLMT_MCS6(x) (((x) & 0xF) << 24) +#define _AGGLMT_MCS7(x) (((x) & 0xF) << 28) + +#define RETRY_LIMIT_SHORT_SHIFT 8 +#define RETRY_LIMIT_LONG_SHIFT 0 + +#define _DARF_RC1(x) ((x) & 0x1F) +#define _DARF_RC2(x) (((x) & 0x1F) << 8) +#define _DARF_RC3(x) (((x) & 0x1F) << 16) +#define _DARF_RC4(x) (((x) & 0x1F) << 24) +#define _DARF_RC5(x) ((x) & 0x1F) +#define _DARF_RC6(x) (((x) & 0x1F) << 8) +#define _DARF_RC7(x) (((x) & 0x1F) << 16) +#define _DARF_RC8(x) (((x) & 0x1F) << 24) + +#define _RARF_RC1(x) ((x) & 0x1F) +#define _RARF_RC2(x) (((x) & 0x1F) << 8) +#define _RARF_RC3(x) (((x) & 0x1F) << 16) +#define _RARF_RC4(x) (((x) & 0x1F) << 24) +#define _RARF_RC5(x) ((x) & 0x1F) +#define _RARF_RC6(x) (((x) & 0x1F) << 8) +#define _RARF_RC7(x) (((x) & 0x1F) << 16) +#define _RARF_RC8(x) (((x) & 0x1F) << 24) + +#define AC_PARAM_TXOP_LIMIT_OFFSET 16 +#define AC_PARAM_ECW_MAX_OFFSET 12 +#define AC_PARAM_ECW_MIN_OFFSET 8 +#define AC_PARAM_AIFS_OFFSET 0 + +#define _AIFS(x) (x) +#define _ECW_MAX_MIN(x) ((x) << 8) +#define _TXOP_LIMIT(x) ((x) << 16) + +#define _BCNIFS(x) ((x) & 0xFF) +#define _BCNECW(x) ((((x) & 0xF)) << 8) + +#define _LRL(x) ((x) & 0x3F) +#define _SRL(x) (((x) & 0x3F) << 8) + +#define _SIFS_CCK_CTX(x) ((x) & 0xFF) +#define _SIFS_CCK_TRX(x) (((x) & 0xFF) << 8); + +#define _SIFS_OFDM_CTX(x) ((x) & 0xFF) +#define _SIFS_OFDM_TRX(x) (((x) & 0xFF) << 8); + +#define _TBTT_PROHIBIT_HOLD(x) (((x) & 0xFF) << 8) + +#define DIS_EDCA_CNT_DWN BIT(11) + +#define EN_MBSSID BIT(1) +#define EN_TXBCN_RPT BIT(2) +#define EN_BCN_FUNCTION BIT(3) + +#define TSFTR_RST BIT(0) +#define TSFTR1_RST BIT(1) + +#define STOP_BCNQ BIT(6) + +#define DIS_TSF_UDT0_NORMAL_CHIP BIT(4) +#define DIS_TSF_UDT0_TEST_CHIP BIT(5) + +#define AcmHw_HwEn BIT(0) +#define AcmHw_BeqEn BIT(1) +#define AcmHw_ViqEn BIT(2) +#define AcmHw_VoqEn BIT(3) +#define AcmHw_BeqStatus BIT(4) +#define AcmHw_ViqStatus BIT(5) +#define AcmHw_VoqStatus BIT(6) + +#define APSDOFF BIT(6) +#define APSDOFF_STATUS BIT(7) + +#define BW_20MHZ BIT(2) + +#define RATE_BITMAP_ALL 0xFFFFF + +#define RATE_RRSR_CCK_ONLY_1M 0xFFFF1 + +#define TSFRST BIT(0) +#define DIS_GCLK BIT(1) +#define PAD_SEL BIT(2) +#define PWR_ST BIT(6) +#define PWRBIT_OW_EN BIT(7) +#define ACRC BIT(8) +#define CFENDFORM BIT(9) +#define ICV BIT(10) + +#define AAP BIT(0) +#define APM BIT(1) +#define AM BIT(2) +#define AB BIT(3) +#define ADD3 BIT(4) +#define APWRMGT BIT(5) +#define CBSSID BIT(6) +#define CBSSID_DATA BIT(6) +#define CBSSID_BCN BIT(7) +#define ACRC32 BIT(8) +#define AICV BIT(9) +#define ADF BIT(11) +#define ACF BIT(12) +#define AMF BIT(13) +#define HTC_LOC_CTRL BIT(14) +#define UC_DATA_EN BIT(16) +#define BM_DATA_EN BIT(17) +#define MFBEN BIT(22) +#define LSIGEN BIT(23) +#define EnMBID BIT(24) +#define APP_BASSN BIT(27) +#define APP_PHYSTS BIT(28) +#define APP_ICV BIT(29) +#define APP_MIC BIT(30) +#define APP_FCS BIT(31) + +#define _MIN_SPACE(x) ((x) & 0x7) +#define _SHORT_GI_PADDING(x) (((x) & 0x1F) << 3) + +#define RXERR_TYPE_OFDM_PPDU 0 +#define RXERR_TYPE_OFDM_FALSE_ALARM 1 +#define RXERR_TYPE_OFDM_MPDU_OK 2 +#define RXERR_TYPE_OFDM_MPDU_FAIL 3 +#define RXERR_TYPE_CCK_PPDU 4 +#define RXERR_TYPE_CCK_FALSE_ALARM 5 +#define RXERR_TYPE_CCK_MPDU_OK 6 +#define RXERR_TYPE_CCK_MPDU_FAIL 7 +#define RXERR_TYPE_HT_PPDU 8 +#define RXERR_TYPE_HT_FALSE_ALARM 9 +#define RXERR_TYPE_HT_MPDU_TOTAL 10 +#define RXERR_TYPE_HT_MPDU_OK 11 +#define RXERR_TYPE_HT_MPDU_FAIL 12 +#define RXERR_TYPE_RX_FULL_DROP 15 + +#define RXERR_COUNTER_MASK 0xFFFFF +#define RXERR_RPT_RST BIT(27) +#define _RXERR_RPT_SEL(type) ((type) << 28) + +#define SCR_TxUseDK BIT(0) +#define SCR_RxUseDK BIT(1) +#define SCR_TxEncEnable BIT(2) +#define SCR_RxDecEnable BIT(3) +#define SCR_SKByA2 BIT(4) +#define SCR_NoSKMC BIT(5) +#define SCR_TXBCUSEDK BIT(6) +#define SCR_RXBCUSEDK BIT(7) + +#define USB_IS_HIGH_SPEED 0 +#define USB_IS_FULL_SPEED 1 +#define USB_SPEED_MASK BIT(5) + +#define USB_NORMAL_SIE_EP_MASK 0xF +#define USB_NORMAL_SIE_EP_SHIFT 4 + +#define USB_TEST_EP_MASK 0x30 +#define USB_TEST_EP_SHIFT 4 + +#define USB_AGG_EN BIT(3) + +#define MAC_ADDR_LEN 6 +#define LAST_ENTRY_OF_TX_PKT_BUFFER 255 + +#define POLLING_LLT_THRESHOLD 20 +#define POLLING_READY_TIMEOUT_COUNT 1000 + +#define MAX_MSS_DENSITY_2T 0x13 +#define MAX_MSS_DENSITY_1T 0x0A + +#define EPROM_CMD_OPERATING_MODE_MASK ((1<<7)|(1<<6)) +#define EPROM_CMD_CONFIG 0x3 +#define EPROM_CMD_LOAD 1 + +#define HWSET_MAX_SIZE_92S HWSET_MAX_SIZE + +#define HAL_8192C_HW_GPIO_WPS_BIT BIT(2) + +#define RPMAC_RESET 0x100 +#define RPMAC_TXSTART 0x104 +#define RPMAC_TXLEGACYSIG 0x108 +#define RPMAC_TXHTSIG1 0x10c +#define RPMAC_TXHTSIG2 0x110 +#define RPMAC_PHYDEBUG 0x114 +#define RPMAC_TXPACKETNUM 0x118 +#define RPMAC_TXIDLE 0x11c +#define RPMAC_TXMACHEADER0 0x120 +#define RPMAC_TXMACHEADER1 0x124 +#define RPMAC_TXMACHEADER2 0x128 +#define RPMAC_TXMACHEADER3 0x12c +#define RPMAC_TXMACHEADER4 0x130 +#define RPMAC_TXMACHEADER5 0x134 +#define RPMAC_TXDADATYPE 0x138 +#define RPMAC_TXRANDOMSEED 0x13c +#define RPMAC_CCKPLCPPREAMBLE 0x140 +#define RPMAC_CCKPLCPHEADER 0x144 +#define RPMAC_CCKCRC16 0x148 +#define RPMAC_OFDMRXCRC32OK 0x170 +#define RPMAC_OFDMRXCRC32Er 0x174 +#define RPMAC_OFDMRXPARITYER 0x178 +#define RPMAC_OFDMRXCRC8ER 0x17c +#define RPMAC_CCKCRXRC16ER 0x180 +#define RPMAC_CCKCRXRC32ER 0x184 +#define RPMAC_CCKCRXRC32OK 0x188 +#define RPMAC_TXSTATUS 0x18c + +#define RFPGA0_RFMOD 0x800 + +#define RFPGA0_TXINFO 0x804 +#define RFPGA0_PSDFUNCTION 0x808 + +#define RFPGA0_TXGAINSTAGE 0x80c + +#define RFPGA0_RFTIMING1 0x810 +#define RFPGA0_RFTIMING2 0x814 + +#define RFPGA0_XA_HSSIPARAMETER1 0x820 +#define RFPGA0_XA_HSSIPARAMETER2 0x824 +#define RFPGA0_XB_HSSIPARAMETER1 0x828 +#define RFPGA0_XB_HSSIPARAMETER2 0x82c + +#define RFPGA0_XA_LSSIPARAMETER 0x840 +#define RFPGA0_XB_LSSIPARAMETER 0x844 + +#define RFPGA0_RFWAKEUPPARAMETER 0x850 +#define RFPGA0_RFSLEEPUPPARAMETER 0x854 + +#define RFPGA0_XAB_SWITCHCONTROL 0x858 +#define RFPGA0_XCD_SWITCHCONTROL 0x85c + +#define RFPGA0_XA_RFINTERFACEOE 0x860 +#define RFPGA0_XB_RFINTERFACEOE 0x864 + +#define RFPGA0_XAB_RFINTERFACESW 0x870 +#define RFPGA0_XCD_RFINTERFACESW 0x874 + +#define rFPGA0_XAB_RFPARAMETER 0x878 +#define rFPGA0_XCD_RFPARAMETER 0x87c + +#define RFPGA0_ANALOGPARAMETER1 0x880 +#define RFPGA0_ANALOGPARAMETER2 0x884 +#define RFPGA0_ANALOGPARAMETER3 0x888 +#define RFPGA0_ANALOGPARAMETER4 0x88c + +#define RFPGA0_XA_LSSIREADBACK 0x8a0 +#define RFPGA0_XB_LSSIREADBACK 0x8a4 +#define RFPGA0_XC_LSSIREADBACK 0x8a8 +#define RFPGA0_XD_LSSIREADBACK 0x8ac + +#define RFPGA0_PSDREPORT 0x8b4 +#define TRANSCEIVEA_HSPI_READBACK 0x8b8 +#define TRANSCEIVEB_HSPI_READBACK 0x8bc +#define RFPGA0_XAB_RFINTERFACERB 0x8e0 +#define RFPGA0_XCD_RFINTERFACERB 0x8e4 + +#define RFPGA1_RFMOD 0x900 + +#define RFPGA1_TXBLOCK 0x904 +#define RFPGA1_DEBUGSELECT 0x908 +#define RFPGA1_TXINFO 0x90c + +#define RCCK0_SYSTEM 0xa00 + +#define RCCK0_AFESETTING 0xa04 +#define RCCK0_CCA 0xa08 + +#define RCCK0_RXAGC1 0xa0c +#define RCCK0_RXAGC2 0xa10 + +#define RCCK0_RXHP 0xa14 + +#define RCCK0_DSPPARAMETER1 0xa18 +#define RCCK0_DSPPARAMETER2 0xa1c + +#define RCCK0_TXFILTER1 0xa20 +#define RCCK0_TXFILTER2 0xa24 +#define RCCK0_DEBUGPORT 0xa28 +#define RCCK0_FALSEALARMREPORT 0xa2c +#define RCCK0_TRSSIREPORT 0xa50 +#define RCCK0_RXREPORT 0xa54 +#define RCCK0_FACOUNTERLOWER 0xa5c +#define RCCK0_FACOUNTERUPPER 0xa58 + +#define ROFDM0_LSTF 0xc00 + +#define ROFDM0_TRXPATHENABLE 0xc04 +#define ROFDM0_TRMUXPAR 0xc08 +#define ROFDM0_TRSWISOLATION 0xc0c + +#define ROFDM0_XARXAFE 0xc10 +#define ROFDM0_XARXIQIMBALANCE 0xc14 +#define ROFDM0_XBRXAFE 0xc18 +#define ROFDM0_XBRXIQIMBALANCE 0xc1c +#define ROFDM0_XCRXAFE 0xc20 +#define ROFDM0_XCRXIQIMBANLANCE 0xc24 +#define ROFDM0_XDRXAFE 0xc28 +#define ROFDM0_XDRXIQIMBALANCE 0xc2c + +#define ROFDM0_RXDETECTOR1 0xc30 +#define ROFDM0_RXDETECTOR2 0xc34 +#define ROFDM0_RXDETECTOR3 0xc38 +#define ROFDM0_RXDETECTOR4 0xc3c + +#define ROFDM0_RXDSP 0xc40 +#define ROFDM0_CFOANDDAGC 0xc44 +#define ROFDM0_CCADROPTHRESHOLD 0xc48 +#define ROFDM0_ECCATHRESHOLD 0xc4c + +#define ROFDM0_XAAGCCORE1 0xc50 +#define ROFDM0_XAAGCCORE2 0xc54 +#define ROFDM0_XBAGCCORE1 0xc58 +#define ROFDM0_XBAGCCORE2 0xc5c +#define ROFDM0_XCAGCCORE1 0xc60 +#define ROFDM0_XCAGCCORE2 0xc64 +#define ROFDM0_XDAGCCORE1 0xc68 +#define ROFDM0_XDAGCCORE2 0xc6c + +#define ROFDM0_AGCPARAMETER1 0xc70 +#define ROFDM0_AGCPARAMETER2 0xc74 +#define ROFDM0_AGCRSSITABLE 0xc78 +#define ROFDM0_HTSTFAGC 0xc7c + +#define ROFDM0_XATXIQIMBALANCE 0xc80 +#define ROFDM0_XATXAFE 0xc84 +#define ROFDM0_XBTXIQIMBALANCE 0xc88 +#define ROFDM0_XBTXAFE 0xc8c +#define ROFDM0_XCTXIQIMBALANCE 0xc90 +#define ROFDM0_XCTXAFE 0xc94 +#define ROFDM0_XDTXIQIMBALANCE 0xc98 +#define ROFDM0_XDTXAFE 0xc9c + +#define ROFDM0_RXIQEXTANTA 0xca0 + +#define ROFDM0_RXHPPARAMETER 0xce0 +#define ROFDM0_TXPSEUDONOISEWGT 0xce4 +#define ROFDM0_FRAMESYNC 0xcf0 +#define ROFDM0_DFSREPORT 0xcf4 +#define ROFDM0_TXCOEFF1 0xca4 +#define ROFDM0_TXCOEFF2 0xca8 +#define ROFDM0_TXCOEFF3 0xcac +#define ROFDM0_TXCOEFF4 0xcb0 +#define ROFDM0_TXCOEFF5 0xcb4 +#define ROFDM0_TXCOEFF6 0xcb8 + +#define ROFDM1_LSTF 0xd00 +#define ROFDM1_TRXPATHENABLE 0xd04 + +#define ROFDM1_CF0 0xd08 +#define ROFDM1_CSI1 0xd10 +#define ROFDM1_SBD 0xd14 +#define ROFDM1_CSI2 0xd18 +#define ROFDM1_CFOTRACKING 0xd2c +#define ROFDM1_TRXMESAURE1 0xd34 +#define ROFDM1_INTFDET 0xd3c +#define ROFDM1_PSEUDONOISESTATEAB 0xd50 +#define ROFDM1_PSEUDONOISESTATECD 0xd54 +#define ROFDM1_RXPSEUDONOISEWGT 0xd58 + +#define ROFDM_PHYCOUNTER1 0xda0 +#define ROFDM_PHYCOUNTER2 0xda4 +#define ROFDM_PHYCOUNTER3 0xda8 + +#define ROFDM_SHORTCFOAB 0xdac +#define ROFDM_SHORTCFOCD 0xdb0 +#define ROFDM_LONGCFOAB 0xdb4 +#define ROFDM_LONGCFOCD 0xdb8 +#define ROFDM_TAILCF0AB 0xdbc +#define ROFDM_TAILCF0CD 0xdc0 +#define ROFDM_PWMEASURE1 0xdc4 +#define ROFDM_PWMEASURE2 0xdc8 +#define ROFDM_BWREPORT 0xdcc +#define ROFDM_AGCREPORT 0xdd0 +#define ROFDM_RXSNR 0xdd4 +#define ROFDM_RXEVMCSI 0xdd8 +#define ROFDM_SIGREPORT 0xddc + +#define RTXAGC_A_RATE18_06 0xe00 +#define RTXAGC_A_RATE54_24 0xe04 +#define RTXAGC_A_CCK1_MCS32 0xe08 +#define RTXAGC_A_MCS03_MCS00 0xe10 +#define RTXAGC_A_MCS07_MCS04 0xe14 +#define RTXAGC_A_MCS11_MCS08 0xe18 +#define RTXAGC_A_MCS15_MCS12 0xe1c + +#define RTXAGC_B_RATE18_06 0x830 +#define RTXAGC_B_RATE54_24 0x834 +#define RTXAGC_B_CCK1_55_MCS32 0x838 +#define RTXAGC_B_MCS03_MCS00 0x83c +#define RTXAGC_B_MCS07_MCS04 0x848 +#define RTXAGC_B_MCS11_MCS08 0x84c +#define RTXAGC_B_MCS15_MCS12 0x868 +#define RTXAGC_B_CCK11_A_CCK2_11 0x86c + +#define RZEBRA1_HSSIENABLE 0x0 +#define RZEBRA1_TRXENABLE1 0x1 +#define RZEBRA1_TRXENABLE2 0x2 +#define RZEBRA1_AGC 0x4 +#define RZEBRA1_CHARGEPUMP 0x5 +#define RZEBRA1_CHANNEL 0x7 + +#define RZEBRA1_TXGAIN 0x8 +#define RZEBRA1_TXLPF 0x9 +#define RZEBRA1_RXLPF 0xb +#define RZEBRA1_RXHPFCORNER 0xc + +#define RGLOBALCTRL 0 +#define RRTL8256_TXLPF 19 +#define RRTL8256_RXLPF 11 +#define RRTL8258_TXLPF 0x11 +#define RRTL8258_RXLPF 0x13 +#define RRTL8258_RSSILPF 0xa + +#define RF_AC 0x00 + +#define RF_IQADJ_G1 0x01 +#define RF_IQADJ_G2 0x02 +#define RF_POW_TRSW 0x05 + +#define RF_GAIN_RX 0x06 +#define RF_GAIN_TX 0x07 + +#define RF_TXM_IDAC 0x08 +#define RF_BS_IQGEN 0x0F + +#define RF_MODE1 0x10 +#define RF_MODE2 0x11 + +#define RF_RX_AGC_HP 0x12 +#define RF_TX_AGC 0x13 +#define RF_BIAS 0x14 +#define RF_IPA 0x15 +#define RF_POW_ABILITY 0x17 +#define RF_MODE_AG 0x18 +#define RRFCHANNEL 0x18 +#define RF_CHNLBW 0x18 +#define RF_TOP 0x19 + +#define RF_RX_G1 0x1A +#define RF_RX_G2 0x1B + +#define RF_RX_BB2 0x1C +#define RF_RX_BB1 0x1D + +#define RF_RCK1 0x1E +#define RF_RCK2 0x1F + +#define RF_TX_G1 0x20 +#define RF_TX_G2 0x21 +#define RF_TX_G3 0x22 + +#define RF_TX_BB1 0x23 +#define RF_T_METER 0x24 + +#define RF_SYN_G1 0x25 +#define RF_SYN_G2 0x26 +#define RF_SYN_G3 0x27 +#define RF_SYN_G4 0x28 +#define RF_SYN_G5 0x29 +#define RF_SYN_G6 0x2A +#define RF_SYN_G7 0x2B +#define RF_SYN_G8 0x2C + +#define RF_RCK_OS 0x30 +#define RF_TXPA_G1 0x31 +#define RF_TXPA_G2 0x32 +#define RF_TXPA_G3 0x33 + +#define BBBRESETB 0x100 +#define BGLOBALRESETB 0x200 +#define BOFDMTXSTART 0x4 +#define BCCKTXSTART 0x8 +#define BCRC32DEBUG 0x100 +#define BPMACLOOPBACK 0x10 +#define BTXLSIG 0xffffff +#define BOFDMTXRATE 0xf +#define BOFDMTXRESERVED 0x10 +#define BOFDMTXLENGTH 0x1ffe0 +#define BOFDMTXPARITY 0x20000 +#define BTXHTSIG1 0xffffff +#define BTXHTMCSRATE 0x7f +#define BTXHTBW 0x80 +#define BTXHTLENGTH 0xffff00 +#define BTXHTSIG2 0xffffff +#define BTXHTSMOOTHING 0x1 +#define BTXHTSOUNDING 0x2 +#define BTXHTRESERVED 0x4 +#define BTXHTAGGREATION 0x8 +#define BTXHTSTBC 0x30 +#define BTXHTADVANCECODING 0x40 +#define BTXHTSHORTGI 0x80 +#define BTXHTNUMBERHT_LTF 0x300 +#define BTXHTCRC8 0x3fc00 +#define BCOUNTERRESET 0x10000 +#define BNUMOFOFDMTX 0xffff +#define BNUMOFCCKTX 0xffff0000 +#define BTXIDLEINTERVAL 0xffff +#define BOFDMSERVICE 0xffff0000 +#define BTXMACHEADER 0xffffffff +#define BTXDATAINIT 0xff +#define BTXHTMODE 0x100 +#define BTXDATATYPE 0x30000 +#define BTXRANDOMSEED 0xffffffff +#define BCCKTXPREAMBLE 0x1 +#define BCCKTXSFD 0xffff0000 +#define BCCKTXSIG 0xff +#define BCCKTXSERVICE 0xff00 +#define BCCKLENGTHEXT 0x8000 +#define BCCKTXLENGHT 0xffff0000 +#define BCCKTXCRC16 0xffff +#define BCCKTXSTATUS 0x1 +#define BOFDMTXSTATUS 0x2 +#define IS_BB_REG_OFFSET_92S(_Offset) \ + ((_Offset >= 0x800) && (_Offset <= 0xfff)) + +#define BRFMOD 0x1 +#define BJAPANMODE 0x2 +#define BCCKTXSC 0x30 +#define BCCKEN 0x1000000 +#define BOFDMEN 0x2000000 + +#define BOFDMRXADCPHASE 0x10000 +#define BOFDMTXDACPHASE 0x40000 +#define BXATXAGC 0x3f + +#define BXBTXAGC 0xf00 +#define BXCTXAGC 0xf000 +#define BXDTXAGC 0xf0000 + +#define BPASTART 0xf0000000 +#define BTRSTART 0x00f00000 +#define BRFSTART 0x0000f000 +#define BBBSTART 0x000000f0 +#define BBBCCKSTART 0x0000000f +#define BPAEND 0xf +#define BTREND 0x0f000000 +#define BRFEND 0x000f0000 +#define BCCAMASK 0x000000f0 +#define BR2RCCAMASK 0x00000f00 +#define BHSSI_R2TDELAY 0xf8000000 +#define BHSSI_T2RDELAY 0xf80000 +#define BCONTXHSSI 0x400 +#define BIGFROMCCK 0x200 +#define BAGCADDRESS 0x3f +#define BRXHPTX 0x7000 +#define BRXHP2RX 0x38000 +#define BRXHPCCKINI 0xc0000 +#define BAGCTXCODE 0xc00000 +#define BAGCRXCODE 0x300000 + +#define B3WIREDATALENGTH 0x800 +#define B3WIREADDREAALENGTH 0x400 + +#define B3WIRERFPOWERDOWN 0x1 +#define B5GPAPEPOLARITY 0x40000000 +#define B2GPAPEPOLARITY 0x80000000 +#define BRFSW_TXDEFAULTANT 0x3 +#define BRFSW_TXOPTIONANT 0x30 +#define BRFSW_RXDEFAULTANT 0x300 +#define BRFSW_RXOPTIONANT 0x3000 +#define BRFSI_3WIREDATA 0x1 +#define BRFSI_3WIRECLOCK 0x2 +#define BRFSI_3WIRELOAD 0x4 +#define BRFSI_3WIRERW 0x8 +#define BRFSI_3WIRE 0xf + +#define BRFSI_RFENV 0x10 + +#define BRFSI_TRSW 0x20 +#define BRFSI_TRSWB 0x40 +#define BRFSI_ANTSW 0x100 +#define BRFSI_ANTSWB 0x200 +#define BRFSI_PAPE 0x400 +#define BRFSI_PAPE5G 0x800 +#define BBANDSELECT 0x1 +#define BHTSIG2_GI 0x80 +#define BHTSIG2_SMOOTHING 0x01 +#define BHTSIG2_SOUNDING 0x02 +#define BHTSIG2_AGGREATON 0x08 +#define BHTSIG2_STBC 0x30 +#define BHTSIG2_ADVCODING 0x40 +#define BHTSIG2_NUMOFHTLTF 0x300 +#define BHTSIG2_CRC8 0x3fc +#define BHTSIG1_MCS 0x7f +#define BHTSIG1_BANDWIDTH 0x80 +#define BHTSIG1_HTLENGTH 0xffff +#define BLSIG_RATE 0xf +#define BLSIG_RESERVED 0x10 +#define BLSIG_LENGTH 0x1fffe +#define BLSIG_PARITY 0x20 +#define BCCKRXPHASE 0x4 + +#define BLSSIREADADDRESS 0x7f800000 +#define BLSSIREADEDGE 0x80000000 + +#define BLSSIREADBACKDATA 0xfffff + +#define BLSSIREADOKFLAG 0x1000 +#define BCCKSAMPLERATE 0x8 +#define BREGULATOR0STANDBY 0x1 +#define BREGULATORPLLSTANDBY 0x2 +#define BREGULATOR1STANDBY 0x4 +#define BPLLPOWERUP 0x8 +#define BDPLLPOWERUP 0x10 +#define BDA10POWERUP 0x20 +#define BAD7POWERUP 0x200 +#define BDA6POWERUP 0x2000 +#define BXTALPOWERUP 0x4000 +#define B40MDCLKPOWERUP 0x8000 +#define BDA6DEBUGMODE 0x20000 +#define BDA6SWING 0x380000 + +#define BADCLKPHASE 0x4000000 +#define B80MCLKDELAY 0x18000000 +#define BAFEWATCHDOGENABLE 0x20000000 + +#define BXTALCAP01 0xc0000000 +#define BXTALCAP23 0x3 +#define BXTALCAP92X 0x0f000000 +#define BXTALCAP 0x0f000000 + +#define BINTDIFCLKENABLE 0x400 +#define BEXTSIGCLKENABLE 0x800 +#define BBANDGAP_MBIAS_POWERUP 0x10000 +#define BAD11SH_GAIN 0xc0000 +#define BAD11NPUT_RANGE 0x700000 +#define BAD110P_CURRENT 0x3800000 +#define BLPATH_LOOPBACK 0x4000000 +#define BQPATH_LOOPBACK 0x8000000 +#define BAFE_LOOPBACK 0x10000000 +#define BDA10_SWING 0x7e0 +#define BDA10_REVERSE 0x800 +#define BDA_CLK_SOURCE 0x1000 +#define BDA7INPUT_RANGE 0x6000 +#define BDA7_GAIN 0x38000 +#define BDA7OUTPUT_CM_MODE 0x40000 +#define BDA7INPUT_CM_MODE 0x380000 +#define BDA7CURRENT 0xc00000 +#define BREGULATOR_ADJUST 0x7000000 +#define BAD11POWERUP_ATTX 0x1 +#define BDA10PS_ATTX 0x10 +#define BAD11POWERUP_ATRX 0x100 +#define BDA10PS_ATRX 0x1000 +#define BCCKRX_AGC_FORMAT 0x200 +#define BPSDFFT_SAMPLE_POINT 0xc000 +#define BPSD_AVERAGE_NUM 0x3000 +#define BIQPATH_CONTROL 0xc00 +#define BPSD_FREQ 0x3ff +#define BPSD_ANTENNA_PATH 0x30 +#define BPSD_IQ_SWITCH 0x40 +#define BPSD_RX_TRIGGER 0x400000 +#define BPSD_TX_TRIGGER 0x80000000 +#define BPSD_SINE_TONE_SCALE 0x7f000000 +#define BPSD_REPORT 0xffff + +#define BOFDM_TXSC 0x30000000 +#define BCCK_TXON 0x1 +#define BOFDM_TXON 0x2 +#define BDEBUG_PAGE 0xfff +#define BDEBUG_ITEM 0xff +#define BANTL 0x10 +#define BANT_NONHT 0x100 +#define BANT_HT1 0x1000 +#define BANT_HT2 0x10000 +#define BANT_HT1S1 0x100000 +#define BANT_NONHTS1 0x1000000 + +#define BCCK_BBMODE 0x3 +#define BCCK_TXPOWERSAVING 0x80 +#define BCCK_RXPOWERSAVING 0x40 + +#define BCCK_SIDEBAND 0x10 + +#define BCCK_SCRAMBLE 0x8 +#define BCCK_ANTDIVERSITY 0x8000 +#define BCCK_CARRIER_RECOVERY 0x4000 +#define BCCK_TXRATE 0x3000 +#define BCCK_DCCANCEL 0x0800 +#define BCCK_ISICANCEL 0x0400 +#define BCCK_MATCH_FILTER 0x0200 +#define BCCK_EQUALIZER 0x0100 +#define BCCK_PREAMBLE_DETECT 0x800000 +#define BCCK_FAST_FALSECCAi 0x400000 +#define BCCK_CH_ESTSTARTi 0x300000 +#define BCCK_CCA_COUNTi 0x080000 +#define BCCK_CS_LIM 0x070000 +#define BCCK_BIST_MODEi 0x80000000 +#define BCCK_CCAMASK 0x40000000 +#define BCCK_TX_DAC_PHASE 0x4 +#define BCCK_RX_ADC_PHASE 0x20000000 +#define BCCKR_CP_MODE 0x0100 +#define BCCK_TXDC_OFFSET 0xf0 +#define BCCK_RXDC_OFFSET 0xf +#define BCCK_CCA_MODE 0xc000 +#define BCCK_FALSECS_LIM 0x3f00 +#define BCCK_CS_RATIO 0xc00000 +#define BCCK_CORGBIT_SEL 0x300000 +#define BCCK_PD_LIM 0x0f0000 +#define BCCK_NEWCCA 0x80000000 +#define BCCK_RXHP_OF_IG 0x8000 +#define BCCK_RXIG 0x7f00 +#define BCCK_LNA_POLARITY 0x800000 +#define BCCK_RX1ST_BAIN 0x7f0000 +#define BCCK_RF_EXTEND 0x20000000 +#define BCCK_RXAGC_SATLEVEL 0x1f000000 +#define BCCK_RXAGC_SATCOUNT 0xe0 +#define bCCKRxRFSettle 0x1f +#define BCCK_FIXED_RXAGC 0x8000 +#define BCCK_ANTENNA_POLARITY 0x2000 +#define BCCK_TXFILTER_TYPE 0x0c00 +#define BCCK_RXAGC_REPORTTYPE 0x0300 +#define BCCK_RXDAGC_EN 0x80000000 +#define BCCK_RXDAGC_PERIOD 0x20000000 +#define BCCK_RXDAGC_SATLEVEL 0x1f000000 +#define BCCK_TIMING_RECOVERY 0x800000 +#define BCCK_TXC0 0x3f0000 +#define BCCK_TXC1 0x3f000000 +#define BCCK_TXC2 0x3f +#define BCCK_TXC3 0x3f00 +#define BCCK_TXC4 0x3f0000 +#define BCCK_TXC5 0x3f000000 +#define BCCK_TXC6 0x3f +#define BCCK_TXC7 0x3f00 +#define BCCK_DEBUGPORT 0xff0000 +#define BCCK_DAC_DEBUG 0x0f000000 +#define BCCK_FALSEALARM_ENABLE 0x8000 +#define BCCK_FALSEALARM_READ 0x4000 +#define BCCK_TRSSI 0x7f +#define BCCK_RXAGC_REPORT 0xfe +#define BCCK_RXREPORT_ANTSEL 0x80000000 +#define BCCK_RXREPORT_MFOFF 0x40000000 +#define BCCK_RXREPORT_SQLOSS 0x20000000 +#define BCCK_RXREPORT_PKTLOSS 0x10000000 +#define BCCK_RXREPORT_LOCKEDBIT 0x08000000 +#define BCCK_RXREPORT_RATEERROR 0x04000000 +#define BCCK_RXREPORT_RXRATE 0x03000000 +#define BCCK_RXFA_COUNTER_LOWER 0xff +#define BCCK_RXFA_COUNTER_UPPER 0xff000000 +#define BCCK_RXHPAGC_START 0xe000 +#define BCCK_RXHPAGC_FINAL 0x1c00 +#define BCCK_RXFALSEALARM_ENABLE 0x8000 +#define BCCK_FACOUNTER_FREEZE 0x4000 +#define BCCK_TXPATH_SEL 0x10000000 +#define BCCK_DEFAULT_RXPATH 0xc000000 +#define BCCK_OPTION_RXPATH 0x3000000 + +#define BNUM_OFSTF 0x3 +#define BSHIFT_L 0xc0 +#define BGI_TH 0xc +#define BRXPATH_A 0x1 +#define BRXPATH_B 0x2 +#define BRXPATH_C 0x4 +#define BRXPATH_D 0x8 +#define BTXPATH_A 0x1 +#define BTXPATH_B 0x2 +#define BTXPATH_C 0x4 +#define BTXPATH_D 0x8 +#define BTRSSI_FREQ 0x200 +#define BADC_BACKOFF 0x3000 +#define BDFIR_BACKOFF 0xc000 +#define BTRSSI_LATCH_PHASE 0x10000 +#define BRX_LDC_OFFSET 0xff +#define BRX_QDC_OFFSET 0xff00 +#define BRX_DFIR_MODE 0x1800000 +#define BRX_DCNF_TYPE 0xe000000 +#define BRXIQIMB_A 0x3ff +#define BRXIQIMB_B 0xfc00 +#define BRXIQIMB_C 0x3f0000 +#define BRXIQIMB_D 0xffc00000 +#define BDC_DC_NOTCH 0x60000 +#define BRXNB_NOTCH 0x1f000000 +#define BPD_TH 0xf +#define BPD_TH_OPT2 0xc000 +#define BPWED_TH 0x700 +#define BIFMF_WIN_L 0x800 +#define BPD_OPTION 0x1000 +#define BMF_WIN_L 0xe000 +#define BBW_SEARCH_L 0x30000 +#define BWIN_ENH_L 0xc0000 +#define BBW_TH 0x700000 +#define BED_TH2 0x3800000 +#define BBW_OPTION 0x4000000 +#define BRADIO_TH 0x18000000 +#define BWINDOW_L 0xe0000000 +#define BSBD_OPTION 0x1 +#define BFRAME_TH 0x1c +#define BFS_OPTION 0x60 +#define BDC_SLOPE_CHECK 0x80 +#define BFGUARD_COUNTER_DC_L 0xe00 +#define BFRAME_WEIGHT_SHORT 0x7000 +#define BSUB_TUNE 0xe00000 +#define BFRAME_DC_LENGTH 0xe000000 +#define BSBD_START_OFFSET 0x30000000 +#define BFRAME_TH_2 0x7 +#define BFRAME_GI2_TH 0x38 +#define BGI2_SYNC_EN 0x40 +#define BSARCH_SHORT_EARLY 0x300 +#define BSARCH_SHORT_LATE 0xc00 +#define BSARCH_GI2_LATE 0x70000 +#define BCFOANTSUM 0x1 +#define BCFOACC 0x2 +#define BCFOSTARTOFFSET 0xc +#define BCFOLOOPBACK 0x70 +#define BCFOSUMWEIGHT 0x80 +#define BDAGCENABLE 0x10000 +#define BTXIQIMB_A 0x3ff +#define BTXIQIMB_b 0xfc00 +#define BTXIQIMB_C 0x3f0000 +#define BTXIQIMB_D 0xffc00000 +#define BTXIDCOFFSET 0xff +#define BTXIQDCOFFSET 0xff00 +#define BTXDFIRMODE 0x10000 +#define BTXPESUDO_NOISEON 0x4000000 +#define BTXPESUDO_NOISE_A 0xff +#define BTXPESUDO_NOISE_B 0xff00 +#define BTXPESUDO_NOISE_C 0xff0000 +#define BTXPESUDO_NOISE_D 0xff000000 +#define BCCA_DROPOPTION 0x20000 +#define BCCA_DROPTHRES 0xfff00000 +#define BEDCCA_H 0xf +#define BEDCCA_L 0xf0 +#define BLAMBDA_ED 0x300 +#define BRX_INITIALGAIN 0x7f +#define BRX_ANTDIV_EN 0x80 +#define BRX_AGC_ADDRESS_FOR_LNA 0x7f00 +#define BRX_HIGHPOWER_FLOW 0x8000 +#define BRX_AGC_FREEZE_THRES 0xc0000 +#define BRX_FREEZESTEP_AGC1 0x300000 +#define BRX_FREEZESTEP_AGC2 0xc00000 +#define BRX_FREEZESTEP_AGC3 0x3000000 +#define BRX_FREEZESTEP_AGC0 0xc000000 +#define BRXRSSI_CMP_EN 0x10000000 +#define BRXQUICK_AGCEN 0x20000000 +#define BRXAGC_FREEZE_THRES_MODE 0x40000000 +#define BRX_OVERFLOW_CHECKTYPE 0x80000000 +#define BRX_AGCSHIFT 0x7f +#define BTRSW_TRI_ONLY 0x80 +#define BPOWER_THRES 0x300 +#define BRXAGC_EN 0x1 +#define BRXAGC_TOGETHER_EN 0x2 +#define BRXAGC_MIN 0x4 +#define BRXHP_INI 0x7 +#define BRXHP_TRLNA 0x70 +#define BRXHP_RSSI 0x700 +#define BRXHP_BBP1 0x7000 +#define BRXHP_BBP2 0x70000 +#define BRXHP_BBP3 0x700000 +#define BRSSI_H 0x7f0000 +#define BRSSI_GEN 0x7f000000 +#define BRXSETTLE_TRSW 0x7 +#define BRXSETTLE_LNA 0x38 +#define BRXSETTLE_RSSI 0x1c0 +#define BRXSETTLE_BBP 0xe00 +#define BRXSETTLE_RXHP 0x7000 +#define BRXSETTLE_ANTSW_RSSI 0x38000 +#define BRXSETTLE_ANTSW 0xc0000 +#define BRXPROCESS_TIME_DAGC 0x300000 +#define BRXSETTLE_HSSI 0x400000 +#define BRXPROCESS_TIME_BBPPW 0x800000 +#define BRXANTENNA_POWER_SHIFT 0x3000000 +#define BRSSI_TABLE_SELECT 0xc000000 +#define BRXHP_FINAL 0x7000000 +#define BRXHPSETTLE_BBP 0x7 +#define BRXHTSETTLE_HSSI 0x8 +#define BRXHTSETTLE_RXHP 0x70 +#define BRXHTSETTLE_BBPPW 0x80 +#define BRXHTSETTLE_IDLE 0x300 +#define BRXHTSETTLE_RESERVED 0x1c00 +#define BRXHT_RXHP_EN 0x8000 +#define BRXAGC_FREEZE_THRES 0x30000 +#define BRXAGC_TOGETHEREN 0x40000 +#define BRXHTAGC_MIN 0x80000 +#define BRXHTAGC_EN 0x100000 +#define BRXHTDAGC_EN 0x200000 +#define BRXHT_RXHP_BBP 0x1c00000 +#define BRXHT_RXHP_FINAL 0xe0000000 +#define BRXPW_RADIO_TH 0x3 +#define BRXPW_RADIO_EN 0x4 +#define BRXMF_HOLD 0x3800 +#define BRXPD_DELAY_TH1 0x38 +#define BRXPD_DELAY_TH2 0x1c0 +#define BRXPD_DC_COUNT_MAX 0x600 +#define BRXPD_DELAY_TH 0x8000 +#define BRXPROCESS_DELAY 0xf0000 +#define BRXSEARCHRANGE_GI2_EARLY 0x700000 +#define BRXFRAME_FUARD_COUNTER_L 0x3800000 +#define BRXSGI_GUARD_L 0xc000000 +#define BRXSGI_SEARCH_L 0x30000000 +#define BRXSGI_TH 0xc0000000 +#define BDFSCNT0 0xff +#define BDFSCNT1 0xff00 +#define BDFSFLAG 0xf0000 +#define BMF_WEIGHT_SUM 0x300000 +#define BMINIDX_TH 0x7f000000 +#define BDAFORMAT 0x40000 +#define BTXCH_EMU_ENABLE 0x01000000 +#define BTRSW_ISOLATION_A 0x7f +#define BTRSW_ISOLATION_B 0x7f00 +#define BTRSW_ISOLATION_C 0x7f0000 +#define BTRSW_ISOLATION_D 0x7f000000 +#define BEXT_LNA_GAIN 0x7c00 + +#define BSTBC_EN 0x4 +#define BANTENNA_MAPPING 0x10 +#define BNSS 0x20 +#define BCFO_ANTSUM_ID 0x200 +#define BPHY_COUNTER_RESET 0x8000000 +#define BCFO_REPORT_GET 0x4000000 +#define BOFDM_CONTINUE_TX 0x10000000 +#define BOFDM_SINGLE_CARRIER 0x20000000 +#define BOFDM_SINGLE_TONE 0x40000000 +#define BHT_DETECT 0x100 +#define BCFOEN 0x10000 +#define BCFOVALUE 0xfff00000 +#define BSIGTONE_RE 0x3f +#define BSIGTONE_IM 0x7f00 +#define BCOUNTER_CCA 0xffff +#define BCOUNTER_PARITYFAIL 0xffff0000 +#define BCOUNTER_RATEILLEGAL 0xffff +#define BCOUNTER_CRC8FAIL 0xffff0000 +#define BCOUNTER_MCSNOSUPPORT 0xffff +#define BCOUNTER_FASTSYNC 0xffff +#define BSHORTCFO 0xfff +#define BSHORTCFOT_LENGTH 12 +#define BSHORTCFOF_LENGTH 11 +#define BLONGCFO 0x7ff +#define BLONGCFOT_LENGTH 11 +#define BLONGCFOF_LENGTH 11 +#define BTAILCFO 0x1fff +#define BTAILCFOT_LENGTH 13 +#define BTAILCFOF_LENGTH 12 +#define BNOISE_EN_PWDB 0xffff +#define BCC_POWER_DB 0xffff0000 +#define BMOISE_PWDB 0xffff +#define BPOWERMEAST_LENGTH 10 +#define BPOWERMEASF_LENGTH 3 +#define BRX_HT_BW 0x1 +#define BRXSC 0x6 +#define BRX_HT 0x8 +#define BNB_INTF_DET_ON 0x1 +#define BINTF_WIN_LEN_CFG 0x30 +#define BNB_INTF_TH_CFG 0x1c0 +#define BRFGAIN 0x3f +#define BTABLESEL 0x40 +#define BTRSW 0x80 +#define BRXSNR_A 0xff +#define BRXSNR_B 0xff00 +#define BRXSNR_C 0xff0000 +#define BRXSNR_D 0xff000000 +#define BSNR_EVMT_LENGTH 8 +#define BSNR_EVMF_LENGTH 1 +#define BCSI1ST 0xff +#define BCSI2ND 0xff00 +#define BRXEVM1ST 0xff0000 +#define BRXEVM2ND 0xff000000 +#define BSIGEVM 0xff +#define BPWDB 0xff00 +#define BSGIEN 0x10000 + +#define BSFACTOR_QMA1 0xf +#define BSFACTOR_QMA2 0xf0 +#define BSFACTOR_QMA3 0xf00 +#define BSFACTOR_QMA4 0xf000 +#define BSFACTOR_QMA5 0xf0000 +#define BSFACTOR_QMA6 0xf0000 +#define BSFACTOR_QMA7 0xf00000 +#define BSFACTOR_QMA8 0xf000000 +#define BSFACTOR_QMA9 0xf0000000 +#define BCSI_SCHEME 0x100000 + +#define BNOISE_LVL_TOP_SET 0x3 +#define BCHSMOOTH 0x4 +#define BCHSMOOTH_CFG1 0x38 +#define BCHSMOOTH_CFG2 0x1c0 +#define BCHSMOOTH_CFG3 0xe00 +#define BCHSMOOTH_CFG4 0x7000 +#define BMRCMODE 0x800000 +#define BTHEVMCFG 0x7000000 + +#define BLOOP_FIT_TYPE 0x1 +#define BUPD_CFO 0x40 +#define BUPD_CFO_OFFDATA 0x80 +#define BADV_UPD_CFO 0x100 +#define BADV_TIME_CTRL 0x800 +#define BUPD_CLKO 0x1000 +#define BFC 0x6000 +#define BTRACKING_MODE 0x8000 +#define BPHCMP_ENABLE 0x10000 +#define BUPD_CLKO_LTF 0x20000 +#define BCOM_CH_CFO 0x40000 +#define BCSI_ESTI_MODE 0x80000 +#define BADV_UPD_EQZ 0x100000 +#define BUCHCFG 0x7000000 +#define BUPDEQZ 0x8000000 + +#define BRX_PESUDO_NOISE_ON 0x20000000 +#define BRX_PESUDO_NOISE_A 0xff +#define BRX_PESUDO_NOISE_B 0xff00 +#define BRX_PESUDO_NOISE_C 0xff0000 +#define BRX_PESUDO_NOISE_D 0xff000000 +#define BRX_PESUDO_NOISESTATE_A 0xffff +#define BRX_PESUDO_NOISESTATE_B 0xffff0000 +#define BRX_PESUDO_NOISESTATE_C 0xffff +#define BRX_PESUDO_NOISESTATE_D 0xffff0000 + +#define BZEBRA1_HSSIENABLE 0x8 +#define BZEBRA1_TRXCONTROL 0xc00 +#define BZEBRA1_TRXGAINSETTING 0x07f +#define BZEBRA1_RXCOUNTER 0xc00 +#define BZEBRA1_TXCHANGEPUMP 0x38 +#define BZEBRA1_RXCHANGEPUMP 0x7 +#define BZEBRA1_CHANNEL_NUM 0xf80 +#define BZEBRA1_TXLPFBW 0x400 +#define BZEBRA1_RXLPFBW 0x600 + +#define BRTL8256REG_MODE_CTRL1 0x100 +#define BRTL8256REG_MODE_CTRL0 0x40 +#define BRTL8256REG_TXLPFBW 0x18 +#define BRTL8256REG_RXLPFBW 0x600 + +#define BRTL8258_TXLPFBW 0xc +#define BRTL8258_RXLPFBW 0xc00 +#define BRTL8258_RSSILPFBW 0xc0 + +#define BBYTE0 0x1 +#define BBYTE1 0x2 +#define BBYTE2 0x4 +#define BBYTE3 0x8 +#define BWORD0 0x3 +#define BWORD1 0xc +#define BWORD 0xf + +#define MASKBYTE0 0xff +#define MASKBYTE1 0xff00 +#define MASKBYTE2 0xff0000 +#define MASKBYTE3 0xff000000 +#define MASKHWORD 0xffff0000 +#define MASKLWORD 0x0000ffff +#define MASKDWORD 0xffffffff +#define MASK12BITS 0xfff +#define MASKH4BITS 0xf0000000 +#define MASKOFDM_D 0xffc00000 +#define MASKCCK 0x3f3f3f3f + +#define MASK4BITS 0x0f +#define MASK20BITS 0xfffff +#define RFREG_OFFSET_MASK 0xfffff + +#define BENABLE 0x1 +#define BDISABLE 0x0 + +#define LEFT_ANTENNA 0x0 +#define RIGHT_ANTENNA 0x1 + +#define TCHECK_TXSTATUS 500 +#define TUPDATE_RXCOUNTER 100 + +/* 2 EFUSE_TEST (For RTL8723 partially) */ +#define EFUSE_SEL(x) (((x) & 0x3) << 8) +#define EFUSE_SEL_MASK 0x300 +#define EFUSE_WIFI_SEL_0 0x0 + +/* Enable GPIO[9] as WiFi HW PDn source*/ +#define WL_HWPDN_EN BIT(0) +/* WiFi HW PDn polarity control*/ +#define WL_HWPDN_SL BIT(1) + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/rf.c b/drivers/net/wireless/rtlwifi/rtl8723ae/rf.c new file mode 100644 index 0000000..50dd2fb --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/rf.c @@ -0,0 +1,505 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "rf.h" +#include "dm.h" + +void rtl8723ae_phy_rf6052_set_bandwidth(struct ieee80211_hw *hw, u8 bandwidth) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + switch (bandwidth) { + case HT_CHANNEL_WIDTH_20: + rtlphy->rfreg_chnlval[0] = ((rtlphy->rfreg_chnlval[0] & + 0xfffff3ff) | 0x0400); + rtl_set_rfreg(hw, RF90_PATH_A, RF_CHNLBW, RFREG_OFFSET_MASK, + rtlphy->rfreg_chnlval[0]); + break; + case HT_CHANNEL_WIDTH_20_40: + rtlphy->rfreg_chnlval[0] = ((rtlphy->rfreg_chnlval[0] & + 0xfffff3ff)); + rtl_set_rfreg(hw, RF90_PATH_A, RF_CHNLBW, RFREG_OFFSET_MASK, + rtlphy->rfreg_chnlval[0]); + break; + default: + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "unknown bandwidth: %#X\n", bandwidth); + break; + } +} + +void rtl8723ae_phy_rf6052_set_cck_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u32 tx_agc[2] = {0, 0}, tmpval; + bool turbo_scanoff = false; + u8 idx1, idx2; + u8 *ptr; + + if (rtlefuse->eeprom_regulatory != 0) + turbo_scanoff = true; + + if (mac->act_scanning == true) { + tx_agc[RF90_PATH_A] = 0x3f3f3f3f; + tx_agc[RF90_PATH_B] = 0x3f3f3f3f; + + if (turbo_scanoff) { + for (idx1 = RF90_PATH_A; idx1 <= RF90_PATH_B; idx1++) { + tx_agc[idx1] = ppowerlevel[idx1] | + (ppowerlevel[idx1] << 8) | + (ppowerlevel[idx1] << 16) | + (ppowerlevel[idx1] << 24); + } + } + } else { + for (idx1 = RF90_PATH_A; idx1 <= RF90_PATH_B; idx1++) { + tx_agc[idx1] = ppowerlevel[idx1] | + (ppowerlevel[idx1] << 8) | + (ppowerlevel[idx1] << 16) | + (ppowerlevel[idx1] << 24); + } + + if (rtlefuse->eeprom_regulatory == 0) { + tmpval = (rtlphy->mcs_offset[0][6]) + + (rtlphy->mcs_offset[0][7] << 8); + tx_agc[RF90_PATH_A] += tmpval; + + tmpval = (rtlphy->mcs_offset[0][14]) + + (rtlphy->mcs_offset[0][15] << 24); + tx_agc[RF90_PATH_B] += tmpval; + } + } + + for (idx1 = RF90_PATH_A; idx1 <= RF90_PATH_B; idx1++) { + ptr = (u8 *) (&(tx_agc[idx1])); + for (idx2 = 0; idx2 < 4; idx2++) { + if (*ptr > RF6052_MAX_TX_PWR) + *ptr = RF6052_MAX_TX_PWR; + ptr++; + } + } + + tmpval = tx_agc[RF90_PATH_A] & 0xff; + rtl_set_bbreg(hw, RTXAGC_A_CCK1_MCS32, MASKBYTE1, tmpval); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 1M (rf-A) = 0x%x (reg 0x%x)\n", tmpval, + RTXAGC_A_CCK1_MCS32); + + tmpval = tx_agc[RF90_PATH_A] >> 8; + + tmpval = tmpval & 0xff00ffff; + + rtl_set_bbreg(hw, RTXAGC_B_CCK11_A_CCK2_11, 0xffffff00, tmpval); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 2~11M (rf-A) = 0x%x (reg 0x%x)\n", tmpval, + RTXAGC_B_CCK11_A_CCK2_11); + + tmpval = tx_agc[RF90_PATH_B] >> 24; + rtl_set_bbreg(hw, RTXAGC_B_CCK11_A_CCK2_11, MASKBYTE0, tmpval); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 11M (rf-B) = 0x%x (reg 0x%x)\n", tmpval, + RTXAGC_B_CCK11_A_CCK2_11); + + tmpval = tx_agc[RF90_PATH_B] & 0x00ffffff; + rtl_set_bbreg(hw, RTXAGC_B_CCK1_55_MCS32, 0xffffff00, tmpval); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "CCK PWR 1~5.5M (rf-B) = 0x%x (reg 0x%x)\n", tmpval, + RTXAGC_B_CCK1_55_MCS32); +} + +static void rtl8723ae_phy_get_power_base(struct ieee80211_hw *hw, + u8 *ppowerlevel, u8 channel, + u32 *ofdmbase, u32 *mcsbase) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u32 powerBase0, powerBase1; + u8 legacy_pwrdiff, ht20_pwrdiff; + u8 i, powerlevel[2]; + + for (i = 0; i < 2; i++) { + powerlevel[i] = ppowerlevel[i]; + legacy_pwrdiff = rtlefuse->txpwr_legacyhtdiff[i][channel - 1]; + powerBase0 = powerlevel[i] + legacy_pwrdiff; + + powerBase0 = (powerBase0 << 24) | (powerBase0 << 16) | + (powerBase0 << 8) | powerBase0; + *(ofdmbase + i) = powerBase0; + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + " [OFDM power base index rf(%c) = 0x%x]\n", + ((i == 0) ? 'A' : 'B'), *(ofdmbase + i)); + } + + for (i = 0; i < 2; i++) { + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20) { + ht20_pwrdiff = rtlefuse->txpwr_ht20diff[i][channel - 1]; + powerlevel[i] += ht20_pwrdiff; + } + powerBase1 = powerlevel[i]; + powerBase1 = (powerBase1 << 24) | + (powerBase1 << 16) | (powerBase1 << 8) | powerBase1; + + *(mcsbase + i) = powerBase1; + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + " [MCS power base index rf(%c) = 0x%x]\n", + ((i == 0) ? 'A' : 'B'), *(mcsbase + i)); + } +} + +static void rtl8723ae_get_txpwr_val_by_reg(struct ieee80211_hw *hw, + u8 channel, u8 index, + u32 *powerBase0, + u32 *powerBase1, + u32 *p_outwriteval) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + u8 i, chnlgroup = 0, pwr_diff_limit[4]; + u32 writeVal, customer_limit, rf; + + for (rf = 0; rf < 2; rf++) { + switch (rtlefuse->eeprom_regulatory) { + case 0: + chnlgroup = 0; + + writeVal = rtlphy->mcs_offset[chnlgroup] + [index + (rf ? 8 : 0)] + + ((index < 2) ? powerBase0[rf] : + powerBase1[rf]); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "RTK better performance, " + "writeVal(%c) = 0x%x\n", + ((rf == 0) ? 'A' : 'B'), writeVal); + break; + case 1: + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20_40) { + writeVal = ((index < 2) ? powerBase0[rf] : + powerBase1[rf]); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Realtek regulatory, 40MHz, " + "writeVal(%c) = 0x%x\n", + ((rf == 0) ? 'A' : 'B'), writeVal); + } else { + if (rtlphy->pwrgroup_cnt == 1) + chnlgroup = 0; + if (rtlphy->pwrgroup_cnt >= 3) { + if (channel <= 3) + chnlgroup = 0; + else if (channel >= 4 && channel <= 9) + chnlgroup = 1; + else if (channel > 9) + chnlgroup = 2; + if (rtlphy->current_chan_bw == + HT_CHANNEL_WIDTH_20) + chnlgroup++; + else + chnlgroup += 4; + } + + writeVal = rtlphy->mcs_offset[chnlgroup] + [index + (rf ? 8 : 0)] + ((index < 2) ? + powerBase0[rf] : + powerBase1[rf]); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Realtek regulatory, 20MHz, writeVal(%c) = 0x%x\n", + ((rf == 0) ? 'A' : 'B'), writeVal); + } + break; + case 2: + writeVal = + ((index < 2) ? powerBase0[rf] : powerBase1[rf]); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Better regulatory, writeVal(%c) = 0x%x\n", + ((rf == 0) ? 'A' : 'B'), writeVal); + break; + case 3: + chnlgroup = 0; + + if (rtlphy->current_chan_bw == HT_CHANNEL_WIDTH_20_40) { + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "customer's limit, 40MHz rf(%c) = 0x%x\n", + ((rf == 0) ? 'A' : 'B'), + rtlefuse->pwrgroup_ht40[rf][channel-1]); + } else { + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "customer's limit, 20MHz rf(%c) = 0x%x\n", + ((rf == 0) ? 'A' : 'B'), + rtlefuse->pwrgroup_ht20[rf][channel-1]); + } + for (i = 0; i < 4; i++) { + pwr_diff_limit[i] = + (u8) ((rtlphy->mcs_offset + [chnlgroup][index + (rf ? 8 : 0)] & + (0x7f << (i * 8))) >> (i * 8)); + + if (rtlphy->current_chan_bw == + HT_CHANNEL_WIDTH_20_40) { + if (pwr_diff_limit[i] > + rtlefuse-> + pwrgroup_ht40[rf][channel - 1]) + pwr_diff_limit[i] = + rtlefuse->pwrgroup_ht40[rf] + [channel - 1]; + } else { + if (pwr_diff_limit[i] > + rtlefuse-> + pwrgroup_ht20[rf][channel - 1]) + pwr_diff_limit[i] = + rtlefuse->pwrgroup_ht20[rf] + [channel - 1]; + } + } + + customer_limit = (pwr_diff_limit[3] << 24) | + (pwr_diff_limit[2] << 16) | + (pwr_diff_limit[1] << 8) | (pwr_diff_limit[0]); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Customer's limit rf(%c) = 0x%x\n", + ((rf == 0) ? 'A' : 'B'), customer_limit); + + writeVal = customer_limit + + ((index < 2) ? powerBase0[rf] : powerBase1[rf]); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Customer, writeVal rf(%c)= 0x%x\n", + ((rf == 0) ? 'A' : 'B'), writeVal); + break; + default: + chnlgroup = 0; + writeVal = rtlphy->mcs_offset[chnlgroup][index + + (rf ? 8 : 0)] + ((index < 2) ? powerBase0[rf] : + powerBase1[rf]); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "RTK better performance, writeVal rf(%c) = 0x%x\n", + ((rf == 0) ? 'A' : 'B'), writeVal); + break; + } + + if (rtlpriv->dm.dynamic_txhighpower_lvl == TXHIGHPWRLEVEL_BT1) + writeVal = writeVal - 0x06060606; + else if (rtlpriv->dm.dynamic_txhighpower_lvl == + TXHIGHPWRLEVEL_BT2) + writeVal = writeVal - 0x0c0c0c0c; + *(p_outwriteval + rf) = writeVal; + } +} + +static void _rtl8723ae_write_ofdm_power_reg(struct ieee80211_hw *hw, + u8 index, u32 *pValue) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + u16 regoffset_a[6] = { + RTXAGC_A_RATE18_06, RTXAGC_A_RATE54_24, + RTXAGC_A_MCS03_MCS00, RTXAGC_A_MCS07_MCS04, + RTXAGC_A_MCS11_MCS08, RTXAGC_A_MCS15_MCS12 + }; + u16 regoffset_b[6] = { + RTXAGC_B_RATE18_06, RTXAGC_B_RATE54_24, + RTXAGC_B_MCS03_MCS00, RTXAGC_B_MCS07_MCS04, + RTXAGC_B_MCS11_MCS08, RTXAGC_B_MCS15_MCS12 + }; + u8 i, rf, pwr_val[4]; + u32 writeVal; + u16 regoffset; + + for (rf = 0; rf < 2; rf++) { + writeVal = pValue[rf]; + for (i = 0; i < 4; i++) { + pwr_val[i] = (u8) ((writeVal & (0x7f << + (i * 8))) >> (i * 8)); + + if (pwr_val[i] > RF6052_MAX_TX_PWR) + pwr_val[i] = RF6052_MAX_TX_PWR; + } + writeVal = (pwr_val[3] << 24) | (pwr_val[2] << 16) | + (pwr_val[1] << 8) | pwr_val[0]; + + if (rf == 0) + regoffset = regoffset_a[index]; + else + regoffset = regoffset_b[index]; + rtl_set_bbreg(hw, regoffset, MASKDWORD, writeVal); + + RTPRINT(rtlpriv, FPHY, PHY_TXPWR, + "Set 0x%x = %08x\n", regoffset, writeVal); + + if (((get_rf_type(rtlphy) == RF_2T2R) && + (regoffset == RTXAGC_A_MCS15_MCS12 || + regoffset == RTXAGC_B_MCS15_MCS12)) || + ((get_rf_type(rtlphy) != RF_2T2R) && + (regoffset == RTXAGC_A_MCS07_MCS04 || + regoffset == RTXAGC_B_MCS07_MCS04))) { + + writeVal = pwr_val[3]; + if (regoffset == RTXAGC_A_MCS15_MCS12 || + regoffset == RTXAGC_A_MCS07_MCS04) + regoffset = 0xc90; + if (regoffset == RTXAGC_B_MCS15_MCS12 || + regoffset == RTXAGC_B_MCS07_MCS04) + regoffset = 0xc98; + + for (i = 0; i < 3; i++) { + writeVal = (writeVal > 6) ? (writeVal - 6) : 0; + rtl_write_byte(rtlpriv, (u32) (regoffset + i), + (u8) writeVal); + } + } + } +} + +void rtl8723ae_phy_rf6052_set_ofdm_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel, u8 channel) +{ + u32 writeVal[2], powerBase0[2], powerBase1[2]; + u8 index; + + rtl8723ae_phy_get_power_base(hw, ppowerlevel, + channel, &powerBase0[0], &powerBase1[0]); + + for (index = 0; index < 6; index++) { + rtl8723ae_get_txpwr_val_by_reg(hw, channel, index, + &powerBase0[0], + &powerBase1[0], + &writeVal[0]); + + _rtl8723ae_write_ofdm_power_reg(hw, index, &writeVal[0]); + } +} + +static bool _rtl8723ae_phy_rf6052_config_parafile(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u32 u4_regvalue = 0; + u8 rfpath; + bool rtstatus = true; + struct bb_reg_def *pphyreg; + + for (rfpath = 0; rfpath < rtlphy->num_total_rfpath; rfpath++) { + + pphyreg = &rtlphy->phyreg_def[rfpath]; + + switch (rfpath) { + case RF90_PATH_A: + case RF90_PATH_C: + u4_regvalue = rtl_get_bbreg(hw, pphyreg->rfintfs, + BRFSI_RFENV); + break; + case RF90_PATH_B: + case RF90_PATH_D: + u4_regvalue = rtl_get_bbreg(hw, pphyreg->rfintfs, + BRFSI_RFENV << 16); + break; + } + + rtl_set_bbreg(hw, pphyreg->rfintfe, BRFSI_RFENV << 16, 0x1); + udelay(1); + + rtl_set_bbreg(hw, pphyreg->rfintfo, BRFSI_RFENV, 0x1); + udelay(1); + + rtl_set_bbreg(hw, pphyreg->rfhssi_para2, + B3WIREADDREAALENGTH, 0x0); + udelay(1); + + rtl_set_bbreg(hw, pphyreg->rfhssi_para2, B3WIREDATALENGTH, 0x0); + udelay(1); + + switch (rfpath) { + case RF90_PATH_A: + rtstatus = rtl8723ae_phy_config_rf_with_headerfile(hw, + (enum radio_path)rfpath); + break; + case RF90_PATH_B: + rtstatus = rtl8723ae_phy_config_rf_with_headerfile(hw, + (enum radio_path)rfpath); + break; + case RF90_PATH_C: + break; + case RF90_PATH_D: + break; + } + switch (rfpath) { + case RF90_PATH_A: + case RF90_PATH_C: + rtl_set_bbreg(hw, pphyreg->rfintfs, + BRFSI_RFENV, u4_regvalue); + break; + case RF90_PATH_B: + case RF90_PATH_D: + rtl_set_bbreg(hw, pphyreg->rfintfs, + BRFSI_RFENV << 16, u4_regvalue); + break; + } + if (rtstatus != true) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, + "Radio[%d] Fail!!", rfpath); + return false; + } + } + RT_TRACE(rtlpriv, COMP_INIT, DBG_TRACE, "<---\n"); + return rtstatus; +} + +bool rtl8723ae_phy_rf6052_config(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + + if (rtlphy->rf_type == RF_1T1R) + rtlphy->num_total_rfpath = 1; + else + rtlphy->num_total_rfpath = 2; + + return _rtl8723ae_phy_rf6052_config_parafile(hw); +} diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/rf.h b/drivers/net/wireless/rtlwifi/rtl8723ae/rf.h new file mode 100644 index 0000000..d0f9dd7 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/rf.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL8723E_RF_H__ +#define __RTL8723E_RF_H__ + +#define RF6052_MAX_TX_PWR 0x3F + +extern void rtl8723ae_phy_rf6052_set_bandwidth(struct ieee80211_hw *hw, + u8 bandwidth); +extern void rtl8723ae_phy_rf6052_set_cck_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel); +extern void rtl8723ae_phy_rf6052_set_ofdm_txpower(struct ieee80211_hw *hw, + u8 *ppowerlevel, u8 channel); +extern bool rtl8723ae_phy_rf6052_config(struct ieee80211_hw *hw); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/sw.c b/drivers/net/wireless/rtlwifi/rtl8723ae/sw.c new file mode 100644 index 0000000..243c48c --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/sw.c @@ -0,0 +1,388 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include +#include + +#include "../core.h" +#include "../pci.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "dm.h" +#include "hw.h" +#include "sw.h" +#include "trx.h" +#include "led.h" +#include "table.h" +#include "hal_btc.h" + +static void rtl8723ae_init_aspm_vars(struct ieee80211_hw *hw) +{ + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + + /*close ASPM for AMD defaultly */ + rtlpci->const_amdpci_aspm = 0; + + /* ASPM PS mode. + * 0 - Disable ASPM, + * 1 - Enable ASPM without Clock Req, + * 2 - Enable ASPM with Clock Req, + * 3 - Alwyas Enable ASPM with Clock Req, + * 4 - Always Enable ASPM without Clock Req. + * set defult to RTL8192CE:3 RTL8192E:2 + */ + rtlpci->const_pci_aspm = 3; + + /*Setting for PCI-E device */ + rtlpci->const_devicepci_aspm_setting = 0x03; + + /*Setting for PCI-E bridge */ + rtlpci->const_hostpci_aspm_setting = 0x02; + + /* In Hw/Sw Radio Off situation. + * 0 - Default, + * 1 - From ASPM setting without low Mac Pwr, + * 2 - From ASPM setting with low Mac Pwr, + * 3 - Bus D3 + * set default to RTL8192CE:0 RTL8192SE:2 + */ + rtlpci->const_hwsw_rfoff_d3 = 0; + + /* This setting works for those device with + * backdoor ASPM setting such as EPHY setting. + * 0 - Not support ASPM, + * 1 - Support ASPM, + * 2 - According to chipset. + */ + rtlpci->const_support_pciaspm = 1; +} + +int rtl8723ae_init_sw_vars(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + int err; + + rtl8723ae_bt_reg_init(hw); + rtlpriv->dm.dm_initialgain_enable = 1; + rtlpriv->dm.dm_flag = 0; + rtlpriv->dm.disable_framebursting = 0; + rtlpriv->dm.thermalvalue = 0; + rtlpci->transmit_config = CFENDFORM | BIT(12) | BIT(13); + + /* compatible 5G band 88ce just 2.4G band & smsp */ + rtlpriv->rtlhal.current_bandtype = BAND_ON_2_4G; + rtlpriv->rtlhal.bandset = BAND_ON_2_4G; + rtlpriv->rtlhal.macphymode = SINGLEMAC_SINGLEPHY; + + rtlpci->receive_config = (RCR_APPFCS | + RCR_APP_MIC | + RCR_APP_ICV | + RCR_APP_PHYST_RXFF | + RCR_HTC_LOC_CTRL | + RCR_AMF | + RCR_ACF | + RCR_ADF | + RCR_AICV | + RCR_AB | + RCR_AM | + RCR_APM | + 0); + + rtlpci->irq_mask[0] = + (u32) (PHIMR_ROK | + PHIMR_RDU | + PHIMR_VODOK | + PHIMR_VIDOK | + PHIMR_BEDOK | + PHIMR_BKDOK | + PHIMR_MGNTDOK | + PHIMR_HIGHDOK | + PHIMR_C2HCMD | + PHIMR_HISRE_IND | + PHIMR_TSF_BIT32_TOGGLE | + PHIMR_TXBCNOK | + PHIMR_PSTIMEOUT | + 0); + + rtlpci->irq_mask[1] = (u32)(PHIMR_RXFOVW | 0); + + /* for debug level */ + rtlpriv->dbg.global_debuglevel = rtlpriv->cfg->mod_params->debug; + /* for LPS & IPS */ + rtlpriv->psc.inactiveps = rtlpriv->cfg->mod_params->inactiveps; + rtlpriv->psc.swctrl_lps = rtlpriv->cfg->mod_params->swctrl_lps; + rtlpriv->psc.fwctrl_lps = rtlpriv->cfg->mod_params->fwctrl_lps; + rtlpriv->psc.reg_fwctrl_lps = 3; + rtlpriv->psc.reg_max_lps_awakeintvl = 5; + /* for ASPM, you can close aspm through + * set const_support_pciaspm = 0 + */ + rtl8723ae_init_aspm_vars(hw); + + if (rtlpriv->psc.reg_fwctrl_lps == 1) + rtlpriv->psc.fwctrl_psmode = FW_PS_MIN_MODE; + else if (rtlpriv->psc.reg_fwctrl_lps == 2) + rtlpriv->psc.fwctrl_psmode = FW_PS_MAX_MODE; + else if (rtlpriv->psc.reg_fwctrl_lps == 3) + rtlpriv->psc.fwctrl_psmode = FW_PS_DTIM_MODE; + + /* for firmware buf */ + rtlpriv->rtlhal.pfirmware = vmalloc(0x6000); + if (!rtlpriv->rtlhal.pfirmware) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Can't alloc buffer for fw.\n"); + return 1; + } + + if (IS_VENDOR_8723_A_CUT(rtlhal->version)) + rtlpriv->cfg->fw_name = "rtlwifi/rtl8723fw.bin"; + else if (IS_81xxC_VENDOR_UMC_B_CUT(rtlhal->version)) + rtlpriv->cfg->fw_name = "rtlwifi/rtl8723fw_B.bin"; + + rtlpriv->max_fw_size = 0x6000; + pr_info("Using firmware %s\n", rtlpriv->cfg->fw_name); + err = request_firmware_nowait(THIS_MODULE, 1, rtlpriv->cfg->fw_name, + rtlpriv->io.dev, GFP_KERNEL, hw, + rtl_fw_cb); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Failed to request firmware!\n"); + return 1; + } + return 0; +} + +void rtl8723ae_deinit_sw_vars(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + if (rtlpriv->rtlhal.pfirmware) { + vfree(rtlpriv->rtlhal.pfirmware); + rtlpriv->rtlhal.pfirmware = NULL; + } +} + +static struct rtl_hal_ops rtl8723ae_hal_ops = { + .init_sw_vars = rtl8723ae_init_sw_vars, + .deinit_sw_vars = rtl8723ae_deinit_sw_vars, + .read_eeprom_info = rtl8723ae_read_eeprom_info, + .interrupt_recognized = rtl8723ae_interrupt_recognized, + .hw_init = rtl8723ae_hw_init, + .hw_disable = rtl8723ae_card_disable, + .hw_suspend = rtl8723ae_suspend, + .hw_resume = rtl8723ae_resume, + .enable_interrupt = rtl8723ae_enable_interrupt, + .disable_interrupt = rtl8723ae_disable_interrupt, + .set_network_type = rtl8723ae_set_network_type, + .set_chk_bssid = rtl8723ae_set_check_bssid, + .set_qos = rtl8723ae_set_qos, + .set_bcn_reg = rtl8723ae_set_beacon_related_registers, + .set_bcn_intv = rtl8723ae_set_beacon_interval, + .update_interrupt_mask = rtl8723ae_update_interrupt_mask, + .get_hw_reg = rtl8723ae_get_hw_reg, + .set_hw_reg = rtl8723ae_set_hw_reg, + .update_rate_tbl = rtl8723ae_update_hal_rate_tbl, + .fill_tx_desc = rtl8723ae_tx_fill_desc, + .fill_tx_cmddesc = rtl8723ae_tx_fill_cmddesc, + .query_rx_desc = rtl8723ae_rx_query_desc, + .set_channel_access = rtl8723ae_update_channel_access_setting, + .radio_onoff_checking = rtl8723ae_gpio_radio_on_off_checking, + .set_bw_mode = rtl8723ae_phy_set_bw_mode, + .switch_channel = rtl8723ae_phy_sw_chnl, + .dm_watchdog = rtl8723ae_dm_watchdog, + .scan_operation_backup = rtl8723ae_phy_scan_operation_backup, + .set_rf_power_state = rtl8723ae_phy_set_rf_power_state, + .led_control = rtl8723ae_led_control, + .set_desc = rtl8723ae_set_desc, + .get_desc = rtl8723ae_get_desc, + .tx_polling = rtl8723ae_tx_polling, + .enable_hw_sec = rtl8723ae_enable_hw_security_config, + .set_key = rtl8723ae_set_key, + .init_sw_leds = rtl8723ae_init_sw_leds, + .allow_all_destaddr = rtl8723ae_allow_all_destaddr, + .get_bbreg = rtl8723ae_phy_query_bb_reg, + .set_bbreg = rtl8723ae_phy_set_bb_reg, + .get_rfreg = rtl8723ae_phy_query_rf_reg, + .set_rfreg = rtl8723ae_phy_set_rf_reg, + .c2h_command_handle = rtl_8723e_c2h_command_handle, + .bt_wifi_media_status_notify = rtl_8723e_bt_wifi_media_status_notify, + .bt_coex_off_before_lps = rtl8723ae_bt_coex_off_before_lps, +}; + +static struct rtl_mod_params rtl8723ae_mod_params = { + .sw_crypto = false, + .inactiveps = true, + .swctrl_lps = false, + .fwctrl_lps = true, + .debug = DBG_EMERG, +}; + +static struct rtl_hal_cfg rtl8723ae_hal_cfg = { + .bar_id = 2, + .write_readback = true, + .name = "rtl8723ae_pci", + .fw_name = "rtlwifi/rtl8723fw.bin", + .ops = &rtl8723ae_hal_ops, + .mod_params = &rtl8723ae_mod_params, + .maps[SYS_ISO_CTRL] = REG_SYS_ISO_CTRL, + .maps[SYS_FUNC_EN] = REG_SYS_FUNC_EN, + .maps[SYS_CLK] = REG_SYS_CLKR, + .maps[MAC_RCR_AM] = AM, + .maps[MAC_RCR_AB] = AB, + .maps[MAC_RCR_ACRC32] = ACRC32, + .maps[MAC_RCR_ACF] = ACF, + .maps[MAC_RCR_AAP] = AAP, + .maps[EFUSE_TEST] = REG_EFUSE_TEST, + .maps[EFUSE_CTRL] = REG_EFUSE_CTRL, + .maps[EFUSE_CLK] = 0, + .maps[EFUSE_CLK_CTRL] = REG_EFUSE_CTRL, + .maps[EFUSE_PWC_EV12V] = PWC_EV12V, + .maps[EFUSE_FEN_ELDR] = FEN_ELDR, + .maps[EFUSE_LOADER_CLK_EN] = LOADER_CLK_EN, + .maps[EFUSE_ANA8M] = ANA8M, + .maps[EFUSE_HWSET_MAX_SIZE] = HWSET_MAX_SIZE, + .maps[EFUSE_MAX_SECTION_MAP] = EFUSE_MAX_SECTION, + .maps[EFUSE_REAL_CONTENT_SIZE] = EFUSE_REAL_CONTENT_LEN, + .maps[EFUSE_OOB_PROTECT_BYTES_LEN] = EFUSE_OOB_PROTECT_BYTES, + + .maps[RWCAM] = REG_CAMCMD, + .maps[WCAMI] = REG_CAMWRITE, + .maps[RCAMO] = REG_CAMREAD, + .maps[CAMDBG] = REG_CAMDBG, + .maps[SECR] = REG_SECCFG, + .maps[SEC_CAM_NONE] = CAM_NONE, + .maps[SEC_CAM_WEP40] = CAM_WEP40, + .maps[SEC_CAM_TKIP] = CAM_TKIP, + .maps[SEC_CAM_AES] = CAM_AES, + .maps[SEC_CAM_WEP104] = CAM_WEP104, + + .maps[RTL_IMR_BCNDMAINT6] = IMR_BCNDMAINT6, + .maps[RTL_IMR_BCNDMAINT5] = IMR_BCNDMAINT5, + .maps[RTL_IMR_BCNDMAINT4] = IMR_BCNDMAINT4, + .maps[RTL_IMR_BCNDMAINT3] = IMR_BCNDMAINT3, + .maps[RTL_IMR_BCNDMAINT2] = IMR_BCNDMAINT2, + .maps[RTL_IMR_BCNDMAINT1] = IMR_BCNDMAINT1, + .maps[RTL_IMR_BCNDOK8] = IMR_BCNDOK8, + .maps[RTL_IMR_BCNDOK7] = IMR_BCNDOK7, + .maps[RTL_IMR_BCNDOK6] = IMR_BCNDOK6, + .maps[RTL_IMR_BCNDOK5] = IMR_BCNDOK5, + .maps[RTL_IMR_BCNDOK4] = IMR_BCNDOK4, + .maps[RTL_IMR_BCNDOK3] = IMR_BCNDOK3, + .maps[RTL_IMR_BCNDOK2] = IMR_BCNDOK2, + .maps[RTL_IMR_BCNDOK1] = IMR_BCNDOK1, + .maps[RTL_IMR_TIMEOUT2] = IMR_TIMEOUT2, + .maps[RTL_IMR_TIMEOUT1] = IMR_TIMEOUT1, + + .maps[RTL_IMR_TXFOVW] = PHIMR_TXFOVW, + .maps[RTL_IMR_PSTIMEOUT] = PHIMR_PSTIMEOUT, + .maps[RTL_IMR_BCNINT] = PHIMR_BCNDMAINT0, + .maps[RTL_IMR_RXFOVW] = PHIMR_RXFOVW, + .maps[RTL_IMR_RDU] = PHIMR_RDU, + .maps[RTL_IMR_ATIMEND] = PHIMR_ATIMEND_E, + .maps[RTL_IMR_BDOK] = PHIMR_BCNDOK0, + .maps[RTL_IMR_MGNTDOK] = PHIMR_MGNTDOK, + .maps[RTL_IMR_TBDER] = PHIMR_TXBCNERR, + .maps[RTL_IMR_HIGHDOK] = PHIMR_HIGHDOK, + .maps[RTL_IMR_TBDOK] = PHIMR_TXBCNOK, + .maps[RTL_IMR_BKDOK] = PHIMR_BKDOK, + .maps[RTL_IMR_BEDOK] = PHIMR_BEDOK, + .maps[RTL_IMR_VIDOK] = PHIMR_VIDOK, + .maps[RTL_IMR_VODOK] = PHIMR_VODOK, + .maps[RTL_IMR_ROK] = PHIMR_ROK, + .maps[RTL_IBSS_INT_MASKS] = (PHIMR_BCNDMAINT0 | + PHIMR_TXBCNOK | PHIMR_TXBCNERR), + .maps[RTL_IMR_C2HCMD] = PHIMR_C2HCMD, + + + .maps[RTL_RC_CCK_RATE1M] = DESC92_RATE1M, + .maps[RTL_RC_CCK_RATE2M] = DESC92_RATE2M, + .maps[RTL_RC_CCK_RATE5_5M] = DESC92_RATE5_5M, + .maps[RTL_RC_CCK_RATE11M] = DESC92_RATE11M, + .maps[RTL_RC_OFDM_RATE6M] = DESC92_RATE6M, + .maps[RTL_RC_OFDM_RATE9M] = DESC92_RATE9M, + .maps[RTL_RC_OFDM_RATE12M] = DESC92_RATE12M, + .maps[RTL_RC_OFDM_RATE18M] = DESC92_RATE18M, + .maps[RTL_RC_OFDM_RATE24M] = DESC92_RATE24M, + .maps[RTL_RC_OFDM_RATE36M] = DESC92_RATE36M, + .maps[RTL_RC_OFDM_RATE48M] = DESC92_RATE48M, + .maps[RTL_RC_OFDM_RATE54M] = DESC92_RATE54M, + + .maps[RTL_RC_HT_RATEMCS7] = DESC92_RATEMCS7, + .maps[RTL_RC_HT_RATEMCS15] = DESC92_RATEMCS15, +}; + +static struct pci_device_id rtl8723ae_pci_ids[] = { + {RTL_PCI_DEVICE(PCI_VENDOR_ID_REALTEK, 0x8723, rtl8723ae_hal_cfg)}, + {}, +}; + +MODULE_DEVICE_TABLE(pci, rtl8723ae_pci_ids); + +MODULE_AUTHOR("lizhaoming "); +MODULE_AUTHOR("Realtek WlanFAE "); +MODULE_AUTHOR("Larry Finger "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Realtek 8723E 802.11n PCI wireless"); +MODULE_FIRMWARE("rtlwifi/rtl8723fw.bin"); +MODULE_FIRMWARE("rtlwifi/rtl8723fw_B.bin"); + +module_param_named(swenc, rtl8723ae_mod_params.sw_crypto, bool, 0444); +module_param_named(debug, rtl8723ae_mod_params.debug, int, 0444); +module_param_named(ips, rtl8723ae_mod_params.inactiveps, bool, 0444); +module_param_named(swlps, rtl8723ae_mod_params.swctrl_lps, bool, 0444); +module_param_named(fwlps, rtl8723ae_mod_params.fwctrl_lps, bool, 0444); +MODULE_PARM_DESC(swenc, "Set to 1 for software crypto (default 0)\n"); +MODULE_PARM_DESC(ips, "Set to 0 to not use link power save (default 1)\n"); +MODULE_PARM_DESC(swlps, "Set to 1 to use SW control power save (default 0)\n"); +MODULE_PARM_DESC(fwlps, "Set to 1 to use FW control power save (default 1)\n"); +MODULE_PARM_DESC(debug, "Set debug level (0-5) (default 0)"); + +compat_pci_suspend(rtl_pci_suspend); +compat_pci_resume(rtl_pci_resume); + +static SIMPLE_DEV_PM_OPS(rtlwifi_pm_ops, rtl_pci_suspend, rtl_pci_resume); + +static struct pci_driver rtl8723ae_driver = { + .name = KBUILD_MODNAME, + .id_table = rtl8723ae_pci_ids, + .probe = rtl_pci_probe, + .remove = rtl_pci_disconnect, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)) + .driver.pm = &rtlwifi_pm_ops, +#elif defined(CONFIG_PM_SLEEP) + .suspend = rtl_pci_suspend_compat, + .resume = rtl_pci_resume_compat, +#endif +}; + +module_pci_driver(rtl8723ae_driver); diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/sw.h b/drivers/net/wireless/rtlwifi/rtl8723ae/sw.h new file mode 100644 index 0000000..fc4fde5 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/sw.h @@ -0,0 +1,37 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL8723E_SW_H__ +#define __RTL8723E_SW_H__ + +int rtl8723ae_init_sw_vars(struct ieee80211_hw *hw); +void rtl8723ae_deinit_sw_vars(struct ieee80211_hw *hw); +void rtl8723ae_init_var_map(struct ieee80211_hw *hw); + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/table.c b/drivers/net/wireless/rtlwifi/rtl8723ae/table.c new file mode 100644 index 0000000..9b0b50c --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/table.c @@ -0,0 +1,738 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Created on 2010/ 5/18, 1:41 + * + * Larry Finger + * + *****************************************************************************/ + +#include "table.h" + +u32 RTL8723EPHY_REG_1TARRAY[RTL8723E_PHY_REG_1TARRAY_LENGTH] = { + 0x800, 0x80040000, + 0x804, 0x00000003, + 0x808, 0x0000fc00, + 0x80c, 0x0000000a, + 0x810, 0x10005388, + 0x814, 0x020c3d10, + 0x818, 0x02200385, + 0x81c, 0x00000000, + 0x820, 0x01000100, + 0x824, 0x00390004, + 0x828, 0x00000000, + 0x82c, 0x00000000, + 0x830, 0x00000000, + 0x834, 0x00000000, + 0x838, 0x00000000, + 0x83c, 0x00000000, + 0x840, 0x00010000, + 0x844, 0x00000000, + 0x848, 0x00000000, + 0x84c, 0x00000000, + 0x850, 0x00000000, + 0x854, 0x00000000, + 0x858, 0x569a569a, + 0x85c, 0x001b25a4, + 0x860, 0x66f60110, + 0x864, 0x061f0130, + 0x868, 0x00000000, + 0x86c, 0x32323200, + 0x870, 0x07000760, + 0x874, 0x22004000, + 0x878, 0x00000808, + 0x87c, 0x00000000, + 0x880, 0xc0083070, + 0x884, 0x000004d5, + 0x888, 0x00000000, + 0x88c, 0xccc000c0, + 0x890, 0x00000800, + 0x894, 0xfffffffe, + 0x898, 0x40302010, + 0x89c, 0x00706050, + 0x900, 0x00000000, + 0x904, 0x00000023, + 0x908, 0x00000000, + 0x90c, 0x81121111, + 0xa00, 0x00d047c8, + 0xa04, 0x80ff000c, + 0xa08, 0x8c838300, + 0xa0c, 0x2e68120f, + 0xa10, 0x9500bb78, + 0xa14, 0x11144028, + 0xa18, 0x00881117, + 0xa1c, 0x89140f00, + 0xa20, 0x1a1b0000, + 0xa24, 0x090e1317, + 0xa28, 0x00000204, + 0xa2c, 0x00d30000, + 0xa70, 0x101fbf00, + 0xa74, 0x00000007, + 0xa78, 0x00000900, + 0xc00, 0x48071d40, + 0xc04, 0x03a05611, + 0xc08, 0x000000e4, + 0xc0c, 0x6c6c6c6c, + 0xc10, 0x08800000, + 0xc14, 0x40000100, + 0xc18, 0x08800000, + 0xc1c, 0x40000100, + 0xc20, 0x00000000, + 0xc24, 0x00000000, + 0xc28, 0x00000000, + 0xc2c, 0x00000000, + 0xc30, 0x69e9ac44, + 0xc34, 0x469652cf, + 0xc38, 0x49795994, + 0xc3c, 0x0a97971c, + 0xc40, 0x1f7c403f, + 0xc44, 0x000100b7, + 0xc48, 0xec020107, + 0xc4c, 0x007f037f, + 0xc50, 0x69543420, + 0xc54, 0x43bc0094, + 0xc58, 0x69543420, + 0xc5c, 0x433c0094, + 0xc60, 0x00000000, + 0xc64, 0x7116848b, + 0xc68, 0x47c00bff, + 0xc6c, 0x00000036, + 0xc70, 0x2c7f000d, + 0xc74, 0x018610db, + 0xc78, 0x0000001f, + 0xc7c, 0x00b91612, + 0xc80, 0x40000100, + 0xc84, 0x20f60000, + 0xc88, 0x40000100, + 0xc8c, 0x20200000, + 0xc90, 0x00121820, + 0xc94, 0x00000000, + 0xc98, 0x00121820, + 0xc9c, 0x00007f7f, + 0xca0, 0x00000000, + 0xca4, 0x00000080, + 0xca8, 0x00000000, + 0xcac, 0x00000000, + 0xcb0, 0x00000000, + 0xcb4, 0x00000000, + 0xcb8, 0x00000000, + 0xcbc, 0x28000000, + 0xcc0, 0x00000000, + 0xcc4, 0x00000000, + 0xcc8, 0x00000000, + 0xccc, 0x00000000, + 0xcd0, 0x00000000, + 0xcd4, 0x00000000, + 0xcd8, 0x64b22427, + 0xcdc, 0x00766932, + 0xce0, 0x00222222, + 0xce4, 0x00000000, + 0xce8, 0x37644302, + 0xcec, 0x2f97d40c, + 0xd00, 0x00080740, + 0xd04, 0x00020401, + 0xd08, 0x0000907f, + 0xd0c, 0x20010201, + 0xd10, 0xa0633333, + 0xd14, 0x3333bc43, + 0xd18, 0x7a8f5b6b, + 0xd2c, 0xcc979975, + 0xd30, 0x00000000, + 0xd34, 0x80608000, + 0xd38, 0x00000000, + 0xd3c, 0x00027293, + 0xd40, 0x00000000, + 0xd44, 0x00000000, + 0xd48, 0x00000000, + 0xd4c, 0x00000000, + 0xd50, 0x6437140a, + 0xd54, 0x00000000, + 0xd58, 0x00000000, + 0xd5c, 0x30032064, + 0xd60, 0x4653de68, + 0xd64, 0x04518a3c, + 0xd68, 0x00002101, + 0xd6c, 0x2a201c16, + 0xd70, 0x1812362e, + 0xd74, 0x322c2220, + 0xd78, 0x000e3c24, + 0xe00, 0x2a2a2a2a, + 0xe04, 0x2a2a2a2a, + 0xe08, 0x03902a2a, + 0xe10, 0x2a2a2a2a, + 0xe14, 0x2a2a2a2a, + 0xe18, 0x2a2a2a2a, + 0xe1c, 0x2a2a2a2a, + 0xe28, 0x00000000, + 0xe30, 0x1000dc1f, + 0xe34, 0x10008c1f, + 0xe38, 0x02140102, + 0xe3c, 0x681604c2, + 0xe40, 0x01007c00, + 0xe44, 0x01004800, + 0xe48, 0xfb000000, + 0xe4c, 0x000028d1, + 0xe50, 0x1000dc1f, + 0xe54, 0x10008c1f, + 0xe58, 0x02140102, + 0xe5c, 0x28160d05, + 0xe60, 0x00000008, + 0xe68, 0x001b25a4, + 0xe6c, 0x631b25a0, + 0xe70, 0x631b25a0, + 0xe74, 0x081b25a0, + 0xe78, 0x081b25a0, + 0xe7c, 0x081b25a0, + 0xe80, 0x081b25a0, + 0xe84, 0x631b25a0, + 0xe88, 0x081b25a0, + 0xe8c, 0x631b25a0, + 0xed0, 0x631b25a0, + 0xed4, 0x631b25a0, + 0xed8, 0x631b25a0, + 0xedc, 0x001b25a0, + 0xee0, 0x001b25a0, + 0xeec, 0x6b1b25a0, + 0xf14, 0x00000003, + 0xf4c, 0x00000000, + 0xf00, 0x00000300, +}; + +u32 RTL8723EPHY_REG_ARRAY_PG[RTL8723E_PHY_REG_ARRAY_PGLENGTH] = { + 0xe00, 0xffffffff, 0x0a0c0c0c, + 0xe04, 0xffffffff, 0x02040608, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x0a0c0d0e, + 0xe14, 0xffffffff, 0x02040608, + 0xe18, 0xffffffff, 0x0a0c0d0e, + 0xe1c, 0xffffffff, 0x02040608, + 0x830, 0xffffffff, 0x0a0c0c0c, + 0x834, 0xffffffff, 0x02040608, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x0a0c0d0e, + 0x848, 0xffffffff, 0x02040608, + 0x84c, 0xffffffff, 0x0a0c0d0e, + 0x868, 0xffffffff, 0x02040608, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x04040404, + 0xe04, 0xffffffff, 0x00020204, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x06060606, + 0xe14, 0xffffffff, 0x00020406, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x04040404, + 0x834, 0xffffffff, 0x00020204, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x06060606, + 0x848, 0xffffffff, 0x00020406, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x04040404, + 0xe04, 0xffffffff, 0x00020204, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x04040404, + 0x834, 0xffffffff, 0x00020204, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, + 0xe00, 0xffffffff, 0x00000000, + 0xe04, 0xffffffff, 0x00000000, + 0xe08, 0x0000ff00, 0x00000000, + 0x86c, 0xffffff00, 0x00000000, + 0xe10, 0xffffffff, 0x00000000, + 0xe14, 0xffffffff, 0x00000000, + 0xe18, 0xffffffff, 0x00000000, + 0xe1c, 0xffffffff, 0x00000000, + 0x830, 0xffffffff, 0x00000000, + 0x834, 0xffffffff, 0x00000000, + 0x838, 0xffffff00, 0x00000000, + 0x86c, 0x000000ff, 0x00000000, + 0x83c, 0xffffffff, 0x00000000, + 0x848, 0xffffffff, 0x00000000, + 0x84c, 0xffffffff, 0x00000000, + 0x868, 0xffffffff, 0x00000000, +}; + +u32 RTL8723E_RADIOA_1TARRAY[Rtl8723ERADIOA_1TARRAYLENGTH] = { + 0x000, 0x00030159, + 0x001, 0x00031284, + 0x002, 0x00098000, + 0x003, 0x00018c63, + 0x004, 0x000210e7, + 0x009, 0x0002044f, + 0x00a, 0x0001a3f1, + 0x00b, 0x00014787, + 0x00c, 0x000896fe, + 0x00d, 0x0000e02c, + 0x00e, 0x00039ce7, + 0x00f, 0x00000451, + 0x019, 0x00000000, + 0x01a, 0x00030355, + 0x01b, 0x00060a00, + 0x01c, 0x000fc378, + 0x01d, 0x000a1250, + 0x01e, 0x0004445f, + 0x01f, 0x00080001, + 0x020, 0x0000b614, + 0x021, 0x0006c000, + 0x022, 0x00000000, + 0x023, 0x00001558, + 0x024, 0x00000060, + 0x025, 0x00000483, + 0x026, 0x0004f000, + 0x027, 0x000ec7d9, + 0x028, 0x00057730, + 0x029, 0x00004783, + 0x02a, 0x00000001, + 0x02b, 0x00021334, + 0x02a, 0x00000000, + 0x02b, 0x00000054, + 0x02a, 0x00000001, + 0x02b, 0x00000808, + 0x02b, 0x00053333, + 0x02c, 0x0000000c, + 0x02a, 0x00000002, + 0x02b, 0x00000808, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000003, + 0x02b, 0x00000808, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x00000004, + 0x02b, 0x00000808, + 0x02b, 0x0006b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000005, + 0x02b, 0x00000808, + 0x02b, 0x00073333, + 0x02c, 0x0000000d, + 0x02a, 0x00000006, + 0x02b, 0x00000709, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000007, + 0x02b, 0x00000709, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x00000008, + 0x02b, 0x0000060a, + 0x02b, 0x0004b333, + 0x02c, 0x0000000d, + 0x02a, 0x00000009, + 0x02b, 0x0000060a, + 0x02b, 0x00053333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000a, + 0x02b, 0x0000060a, + 0x02b, 0x0005b333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000b, + 0x02b, 0x0000060a, + 0x02b, 0x00063333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000c, + 0x02b, 0x0000060a, + 0x02b, 0x0006b333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000d, + 0x02b, 0x0000060a, + 0x02b, 0x00073333, + 0x02c, 0x0000000d, + 0x02a, 0x0000000e, + 0x02b, 0x0000050b, + 0x02b, 0x00066666, + 0x02c, 0x0000001a, + 0x02a, 0x000e0000, + 0x010, 0x0004000f, + 0x011, 0x000e31fc, + 0x010, 0x0006000f, + 0x011, 0x000ff9f8, + 0x010, 0x0002000f, + 0x011, 0x000203f9, + 0x010, 0x0003000f, + 0x011, 0x000ff500, + 0x010, 0x00000000, + 0x011, 0x00000000, + 0x010, 0x0008000f, + 0x011, 0x0003f100, + 0x010, 0x0009000f, + 0x011, 0x00023100, + 0x012, 0x00032000, + 0x012, 0x00071000, + 0x012, 0x000b0000, + 0x012, 0x000fc000, + 0x013, 0x000287b3, + 0x013, 0x000244b7, + 0x013, 0x000204ab, + 0x013, 0x0001c49f, + 0x013, 0x00018493, + 0x013, 0x0001429b, + 0x013, 0x00010299, + 0x013, 0x0000c29c, + 0x013, 0x000081a0, + 0x013, 0x000040ac, + 0x013, 0x00000020, + 0x014, 0x0001944c, + 0x014, 0x00059444, + 0x014, 0x0009944c, + 0x014, 0x000d9444, + 0x015, 0x0000f424, + 0x015, 0x0004f407, + 0x015, 0x0008f424, + 0x015, 0x000cf424, + 0x016, 0x00000339, + 0x016, 0x00040339, + 0x016, 0x00080339, + 0x016, 0x000c0336, + 0x000, 0x00010159, + 0x018, 0x0000f401, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x01f, 0x00080003, + 0x0fe, 0x00000000, + 0x0fe, 0x00000000, + 0x01e, 0x00044457, + 0x01f, 0x00080000, + 0x000, 0x00030159, +}; + + +u32 RTL8723E_RADIOB_1TARRAY[RTL8723E_RADIOB_1TARRAYLENGTH] = { + 0x0, +}; + + +u32 RTL8723EMAC_ARRAY[RTL8723E_MACARRAYLENGTH] = { + 0x420, 0x00000080, + 0x423, 0x00000000, + 0x430, 0x00000000, + 0x431, 0x00000000, + 0x432, 0x00000000, + 0x433, 0x00000001, + 0x434, 0x00000004, + 0x435, 0x00000005, + 0x436, 0x00000006, + 0x437, 0x00000007, + 0x438, 0x00000000, + 0x439, 0x00000000, + 0x43a, 0x00000000, + 0x43b, 0x00000001, + 0x43c, 0x00000004, + 0x43d, 0x00000005, + 0x43e, 0x00000006, + 0x43f, 0x00000007, + 0x440, 0x0000005d, + 0x441, 0x00000001, + 0x442, 0x00000000, + 0x444, 0x00000015, + 0x445, 0x000000f0, + 0x446, 0x0000000f, + 0x447, 0x00000000, + 0x458, 0x00000041, + 0x459, 0x000000a8, + 0x45a, 0x00000072, + 0x45b, 0x000000b9, + 0x460, 0x00000066, + 0x461, 0x00000066, + 0x462, 0x00000008, + 0x463, 0x00000003, + 0x4c8, 0x000000ff, + 0x4c9, 0x00000008, + 0x4cc, 0x000000ff, + 0x4cd, 0x000000ff, + 0x4ce, 0x00000001, + 0x500, 0x00000026, + 0x501, 0x000000a2, + 0x502, 0x0000002f, + 0x503, 0x00000000, + 0x504, 0x00000028, + 0x505, 0x000000a3, + 0x506, 0x0000005e, + 0x507, 0x00000000, + 0x508, 0x0000002b, + 0x509, 0x000000a4, + 0x50a, 0x0000005e, + 0x50b, 0x00000000, + 0x50c, 0x0000004f, + 0x50d, 0x000000a4, + 0x50e, 0x00000000, + 0x50f, 0x00000000, + 0x512, 0x0000001c, + 0x514, 0x0000000a, + 0x515, 0x00000010, + 0x516, 0x0000000a, + 0x517, 0x00000010, + 0x51a, 0x00000016, + 0x524, 0x0000000f, + 0x525, 0x0000004f, + 0x546, 0x00000040, + 0x547, 0x00000000, + 0x550, 0x00000010, + 0x551, 0x00000010, + 0x559, 0x00000002, + 0x55a, 0x00000002, + 0x55d, 0x000000ff, + 0x605, 0x00000030, + 0x608, 0x0000000e, + 0x609, 0x0000002a, + 0x652, 0x00000020, + 0x63c, 0x0000000a, + 0x63d, 0x0000000e, + 0x63e, 0x0000000a, + 0x63f, 0x0000000e, + 0x66e, 0x00000005, + 0x700, 0x00000021, + 0x701, 0x00000043, + 0x702, 0x00000065, + 0x703, 0x00000087, + 0x708, 0x00000021, + 0x709, 0x00000043, + 0x70a, 0x00000065, + 0x70b, 0x00000087, +}; + +u32 RTL8723EAGCTAB_1TARRAY[RTL8723E_AGCTAB_1TARRAYLENGTH] = { + 0xc78, 0x7b000001, + 0xc78, 0x7b010001, + 0xc78, 0x7b020001, + 0xc78, 0x7b030001, + 0xc78, 0x7b040001, + 0xc78, 0x7b050001, + 0xc78, 0x7a060001, + 0xc78, 0x79070001, + 0xc78, 0x78080001, + 0xc78, 0x77090001, + 0xc78, 0x760a0001, + 0xc78, 0x750b0001, + 0xc78, 0x740c0001, + 0xc78, 0x730d0001, + 0xc78, 0x720e0001, + 0xc78, 0x710f0001, + 0xc78, 0x70100001, + 0xc78, 0x6f110001, + 0xc78, 0x6e120001, + 0xc78, 0x6d130001, + 0xc78, 0x6c140001, + 0xc78, 0x6b150001, + 0xc78, 0x6a160001, + 0xc78, 0x69170001, + 0xc78, 0x68180001, + 0xc78, 0x67190001, + 0xc78, 0x661a0001, + 0xc78, 0x651b0001, + 0xc78, 0x641c0001, + 0xc78, 0x631d0001, + 0xc78, 0x621e0001, + 0xc78, 0x611f0001, + 0xc78, 0x60200001, + 0xc78, 0x49210001, + 0xc78, 0x48220001, + 0xc78, 0x47230001, + 0xc78, 0x46240001, + 0xc78, 0x45250001, + 0xc78, 0x44260001, + 0xc78, 0x43270001, + 0xc78, 0x42280001, + 0xc78, 0x41290001, + 0xc78, 0x402a0001, + 0xc78, 0x262b0001, + 0xc78, 0x252c0001, + 0xc78, 0x242d0001, + 0xc78, 0x232e0001, + 0xc78, 0x222f0001, + 0xc78, 0x21300001, + 0xc78, 0x20310001, + 0xc78, 0x06320001, + 0xc78, 0x05330001, + 0xc78, 0x04340001, + 0xc78, 0x03350001, + 0xc78, 0x02360001, + 0xc78, 0x01370001, + 0xc78, 0x00380001, + 0xc78, 0x00390001, + 0xc78, 0x003a0001, + 0xc78, 0x003b0001, + 0xc78, 0x003c0001, + 0xc78, 0x003d0001, + 0xc78, 0x003e0001, + 0xc78, 0x003f0001, + 0xc78, 0x7b400001, + 0xc78, 0x7b410001, + 0xc78, 0x7b420001, + 0xc78, 0x7b430001, + 0xc78, 0x7b440001, + 0xc78, 0x7b450001, + 0xc78, 0x7a460001, + 0xc78, 0x79470001, + 0xc78, 0x78480001, + 0xc78, 0x77490001, + 0xc78, 0x764a0001, + 0xc78, 0x754b0001, + 0xc78, 0x744c0001, + 0xc78, 0x734d0001, + 0xc78, 0x724e0001, + 0xc78, 0x714f0001, + 0xc78, 0x70500001, + 0xc78, 0x6f510001, + 0xc78, 0x6e520001, + 0xc78, 0x6d530001, + 0xc78, 0x6c540001, + 0xc78, 0x6b550001, + 0xc78, 0x6a560001, + 0xc78, 0x69570001, + 0xc78, 0x68580001, + 0xc78, 0x67590001, + 0xc78, 0x665a0001, + 0xc78, 0x655b0001, + 0xc78, 0x645c0001, + 0xc78, 0x635d0001, + 0xc78, 0x625e0001, + 0xc78, 0x615f0001, + 0xc78, 0x60600001, + 0xc78, 0x49610001, + 0xc78, 0x48620001, + 0xc78, 0x47630001, + 0xc78, 0x46640001, + 0xc78, 0x45650001, + 0xc78, 0x44660001, + 0xc78, 0x43670001, + 0xc78, 0x42680001, + 0xc78, 0x41690001, + 0xc78, 0x406a0001, + 0xc78, 0x266b0001, + 0xc78, 0x256c0001, + 0xc78, 0x246d0001, + 0xc78, 0x236e0001, + 0xc78, 0x226f0001, + 0xc78, 0x21700001, + 0xc78, 0x20710001, + 0xc78, 0x06720001, + 0xc78, 0x05730001, + 0xc78, 0x04740001, + 0xc78, 0x03750001, + 0xc78, 0x02760001, + 0xc78, 0x01770001, + 0xc78, 0x00780001, + 0xc78, 0x00790001, + 0xc78, 0x007a0001, + 0xc78, 0x007b0001, + 0xc78, 0x007c0001, + 0xc78, 0x007d0001, + 0xc78, 0x007e0001, + 0xc78, 0x007f0001, + 0xc78, 0x3800001e, + 0xc78, 0x3801001e, + 0xc78, 0x3802001e, + 0xc78, 0x3803001e, + 0xc78, 0x3804001e, + 0xc78, 0x3805001e, + 0xc78, 0x3806001e, + 0xc78, 0x3807001e, + 0xc78, 0x3808001e, + 0xc78, 0x3c09001e, + 0xc78, 0x3e0a001e, + 0xc78, 0x400b001e, + 0xc78, 0x440c001e, + 0xc78, 0x480d001e, + 0xc78, 0x4c0e001e, + 0xc78, 0x500f001e, + 0xc78, 0x5210001e, + 0xc78, 0x5611001e, + 0xc78, 0x5a12001e, + 0xc78, 0x5e13001e, + 0xc78, 0x6014001e, + 0xc78, 0x6015001e, + 0xc78, 0x6016001e, + 0xc78, 0x6217001e, + 0xc78, 0x6218001e, + 0xc78, 0x6219001e, + 0xc78, 0x621a001e, + 0xc78, 0x621b001e, + 0xc78, 0x621c001e, + 0xc78, 0x621d001e, + 0xc78, 0x621e001e, + 0xc78, 0x621f001e, +}; diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/table.h b/drivers/net/wireless/rtlwifi/rtl8723ae/table.h new file mode 100644 index 0000000..f5ce713 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/table.h @@ -0,0 +1,50 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Created on 2010/ 5/18, 1:41 + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL8723E_TABLE__H_ +#define __RTL8723E_TABLE__H_ + +#include + +#define RTL8723E_PHY_REG_1TARRAY_LENGTH 372 +extern u32 RTL8723EPHY_REG_1TARRAY[RTL8723E_PHY_REG_1TARRAY_LENGTH]; +#define RTL8723E_PHY_REG_ARRAY_PGLENGTH 336 +extern u32 RTL8723EPHY_REG_ARRAY_PG[RTL8723E_PHY_REG_ARRAY_PGLENGTH]; +#define Rtl8723ERADIOA_1TARRAYLENGTH 282 +extern u32 RTL8723E_RADIOA_1TARRAY[Rtl8723ERADIOA_1TARRAYLENGTH]; +#define RTL8723E_RADIOB_1TARRAYLENGTH 1 +extern u32 RTL8723E_RADIOB_1TARRAY[RTL8723E_RADIOB_1TARRAYLENGTH]; +#define RTL8723E_MACARRAYLENGTH 172 +extern u32 RTL8723EMAC_ARRAY[RTL8723E_MACARRAYLENGTH]; +#define RTL8723E_AGCTAB_1TARRAYLENGTH 320 +extern u32 RTL8723EAGCTAB_1TARRAY[RTL8723E_AGCTAB_1TARRAYLENGTH]; + +#endif diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/trx.c b/drivers/net/wireless/rtlwifi/rtl8723ae/trx.c new file mode 100644 index 0000000..c72758d --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/trx.c @@ -0,0 +1,681 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#include "../wifi.h" +#include "../pci.h" +#include "../base.h" +#include "../stats.h" +#include "reg.h" +#include "def.h" +#include "phy.h" +#include "trx.h" +#include "led.h" + +static u8 _rtl8723ae_map_hwqueue_to_fwqueue(struct sk_buff *skb, u8 hw_queue) +{ + __le16 fc = rtl_get_fc(skb); + + if (unlikely(ieee80211_is_beacon(fc))) + return QSLT_BEACON; + if (ieee80211_is_mgmt(fc) || ieee80211_is_ctl(fc)) + return QSLT_MGNT; + + return skb->priority; +} + +static void _rtl8723ae_query_rxphystatus(struct ieee80211_hw *hw, + struct rtl_stats *pstatus, u8 *pdesc, + struct rx_fwinfo_8723e *p_drvinfo, + bool bpacket_match_bssid, + bool bpacket_toself, bool packet_beacon) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_ps_ctl *ppsc = rtl_psc(rtlpriv); + struct phy_sts_cck_8723e_t *cck_buf; + s8 rx_pwr_all, rx_pwr[4]; + u8 rf_rx_num = 0, evm, pwdb_all; + u8 i, max_spatial_stream; + u32 rssi, total_rssi = 0; + bool is_cck = pstatus->is_cck; + + /* Record it for next packet processing */ + pstatus->packet_matchbssid = bpacket_match_bssid; + pstatus->packet_toself = bpacket_toself; + pstatus->packet_beacon = packet_beacon; + pstatus->rx_mimo_sig_qual[0] = -1; + pstatus->rx_mimo_sig_qual[1] = -1; + + if (is_cck) { + u8 report, cck_highpwr; + + /* CCK Driver info Structure is not the same as OFDM packet. */ + cck_buf = (struct phy_sts_cck_8723e_t *)p_drvinfo; + + /* (1)Hardware does not provide RSSI for CCK + * (2)PWDB, Average PWDB cacluated by + * hardware (for rate adaptive) + */ + if (ppsc->rfpwr_state == ERFON) + cck_highpwr = (u8) rtl_get_bbreg(hw, + RFPGA0_XA_HSSIPARAMETER2, + BIT(9)); + else + cck_highpwr = false; + + if (!cck_highpwr) { + u8 cck_agc_rpt = cck_buf->cck_agc_rpt; + report = cck_buf->cck_agc_rpt & 0xc0; + report = report >> 6; + switch (report) { + case 0x3: + rx_pwr_all = -46 - (cck_agc_rpt & 0x3e); + break; + case 0x2: + rx_pwr_all = -26 - (cck_agc_rpt & 0x3e); + break; + case 0x1: + rx_pwr_all = -12 - (cck_agc_rpt & 0x3e); + break; + case 0x0: + rx_pwr_all = 16 - (cck_agc_rpt & 0x3e); + break; + } + } else { + u8 cck_agc_rpt = cck_buf->cck_agc_rpt; + report = p_drvinfo->cfosho[0] & 0x60; + report = report >> 5; + switch (report) { + case 0x3: + rx_pwr_all = -46 - ((cck_agc_rpt & 0x1f) << 1); + break; + case 0x2: + rx_pwr_all = -26 - ((cck_agc_rpt & 0x1f) << 1); + break; + case 0x1: + rx_pwr_all = -12 - ((cck_agc_rpt & 0x1f) << 1); + break; + case 0x0: + rx_pwr_all = 16 - ((cck_agc_rpt & 0x1f) << 1); + break; + } + } + + pwdb_all = rtl_query_rxpwrpercentage(rx_pwr_all); + /* CCK gain is smaller than OFDM/MCS gain, + * so we add gain diff. From experience, the val is 6 + */ + pwdb_all += 6; + if (pwdb_all > 100) + pwdb_all = 100; + /* modify the offset to make the same + * gain index with OFDM. + */ + if (pwdb_all > 34 && pwdb_all <= 42) + pwdb_all -= 2; + else if (pwdb_all > 26 && pwdb_all <= 34) + pwdb_all -= 6; + else if (pwdb_all > 14 && pwdb_all <= 26) + pwdb_all -= 8; + else if (pwdb_all > 4 && pwdb_all <= 14) + pwdb_all -= 4; + + pstatus->rx_pwdb_all = pwdb_all; + pstatus->recvsignalpower = rx_pwr_all; + + /* (3) Get Signal Quality (EVM) */ + if (bpacket_match_bssid) { + u8 sq; + + if (pstatus->rx_pwdb_all > 40) { + sq = 100; + } else { + sq = cck_buf->sq_rpt; + if (sq > 64) + sq = 0; + else if (sq < 20) + sq = 100; + else + sq = ((64 - sq) * 100) / 44; + } + + pstatus->signalquality = sq; + pstatus->rx_mimo_sig_qual[0] = sq; + pstatus->rx_mimo_sig_qual[1] = -1; + } + } else { + rtlpriv->dm.rfpath_rxenable[0] = + rtlpriv->dm.rfpath_rxenable[1] = true; + + /* (1)Get RSSI for HT rate */ + for (i = RF90_PATH_A; i < RF6052_MAX_PATH; i++) { + + /* we will judge RF RX path now. */ + if (rtlpriv->dm.rfpath_rxenable[i]) + rf_rx_num++; + + rx_pwr[i] = ((p_drvinfo->gain_trsw[i] & 0x3f)*2) - 110; + + /* Translate DBM to percentage. */ + rssi = rtl_query_rxpwrpercentage(rx_pwr[i]); + total_rssi += rssi; + + /* Get Rx snr value in DB */ + rtlpriv->stats.rx_snr_db[i] = (p_drvinfo->rxsnr[i] / 2); + + /* Record Signal Strength for next packet */ + if (bpacket_match_bssid) + pstatus->rx_mimo_signalstrength[i] = (u8) rssi; + } + + /* (2)PWDB, Average PWDB cacluated by + * hardware (for rate adaptive) + */ + rx_pwr_all = ((p_drvinfo->pwdb_all >> 1) & 0x7f) - 110; + + pwdb_all = rtl_query_rxpwrpercentage(rx_pwr_all); + pstatus->rx_pwdb_all = pwdb_all; + pstatus->rxpower = rx_pwr_all; + pstatus->recvsignalpower = rx_pwr_all; + + /* (3)EVM of HT rate */ + if (pstatus->is_ht && pstatus->rate >= DESC92_RATEMCS8 && + pstatus->rate <= DESC92_RATEMCS15) + max_spatial_stream = 2; + else + max_spatial_stream = 1; + + for (i = 0; i < max_spatial_stream; i++) { + evm = rtl_evm_db_to_percentage(p_drvinfo->rxevm[i]); + + if (bpacket_match_bssid) { + /* Fill value in RFD, Get the first + * spatial stream only + */ + if (i == 0) + pstatus->signalquality = (evm & 0xff); + pstatus->rx_mimo_sig_qual[i] = (evm & 0xff); + } + } + } + + /* UI BSS List signal strength(in percentage), + * make it good looking, from 0~100. + */ + if (is_cck) + pstatus->signalstrength = (u8)(rtl_signal_scale_mapping(hw, + pwdb_all)); + else if (rf_rx_num != 0) + pstatus->signalstrength = (u8)(rtl_signal_scale_mapping(hw, + total_rssi /= rf_rx_num)); +} + +static void _rtl8723ae_translate_rx_signal_stuff(struct ieee80211_hw *hw, + struct sk_buff *skb, struct rtl_stats *pstatus, + u8 *pdesc, struct rx_fwinfo_8723e *p_drvinfo) +{ + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_efuse *rtlefuse = rtl_efuse(rtl_priv(hw)); + struct ieee80211_hdr *hdr; + u8 *tmp_buf; + u8 *praddr; + __le16 fc; + u16 type; + bool packet_matchbssid, packet_toself, packet_beacon = false; + + tmp_buf = skb->data + pstatus->rx_drvinfo_size + pstatus->rx_bufshift; + + hdr = (struct ieee80211_hdr *)tmp_buf; + fc = hdr->frame_control; + type = WLAN_FC_GET_TYPE(fc); + praddr = hdr->addr1; + + packet_matchbssid = ((IEEE80211_FTYPE_CTL != type) && + (!compare_ether_addr(mac->bssid, + (le16_to_cpu(fc) & IEEE80211_FCTL_TODS) ? + hdr->addr1 : (le16_to_cpu(fc) & + IEEE80211_FCTL_FROMDS) ? + hdr->addr2 : hdr->addr3)) && (!pstatus->hwerror) && + (!pstatus->crc) && (!pstatus->icv)); + + packet_toself = packet_matchbssid && + (!compare_ether_addr(praddr, rtlefuse->dev_addr)); + + if (ieee80211_is_beacon(fc)) + packet_beacon = true; + + _rtl8723ae_query_rxphystatus(hw, pstatus, pdesc, p_drvinfo, + packet_matchbssid, packet_toself, + packet_beacon); + + rtl_process_phyinfo(hw, tmp_buf, pstatus); +} + +bool rtl8723ae_rx_query_desc(struct ieee80211_hw *hw, + struct rtl_stats *status, + struct ieee80211_rx_status *rx_status, + u8 *pdesc, struct sk_buff *skb) +{ + struct rx_fwinfo_8723e *p_drvinfo; + struct ieee80211_hdr *hdr; + u32 phystatus = GET_RX_DESC_PHYST(pdesc); + + status->length = (u16) GET_RX_DESC_PKT_LEN(pdesc); + status->rx_drvinfo_size = (u8) GET_RX_DESC_DRV_INFO_SIZE(pdesc) * + RX_DRV_INFO_SIZE_UNIT; + status->rx_bufshift = (u8) (GET_RX_DESC_SHIFT(pdesc) & 0x03); + status->icv = (u16) GET_RX_DESC_ICV(pdesc); + status->crc = (u16) GET_RX_DESC_CRC32(pdesc); + status->hwerror = (status->crc | status->icv); + status->decrypted = !GET_RX_DESC_SWDEC(pdesc); + status->rate = (u8) GET_RX_DESC_RXMCS(pdesc); + status->shortpreamble = (u16) GET_RX_DESC_SPLCP(pdesc); + status->isampdu = (bool) (GET_RX_DESC_PAGGR(pdesc) == 1); + status->isfirst_ampdu = (bool) ((GET_RX_DESC_PAGGR(pdesc) == 1) + && (GET_RX_DESC_FAGGR(pdesc) == 1)); + status->timestamp_low = GET_RX_DESC_TSFL(pdesc); + status->rx_is40Mhzpacket = (bool) GET_RX_DESC_BW(pdesc); + status->is_ht = (bool)GET_RX_DESC_RXHT(pdesc); + + status->is_cck = RTL8723E_RX_HAL_IS_CCK_RATE(status->rate); + + rx_status->freq = hw->conf.chandef.chan->center_freq; + rx_status->band = hw->conf.chandef.chan->band; + + if (status->crc) + rx_status->flag |= RX_FLAG_FAILED_FCS_CRC; + + if (status->rx_is40Mhzpacket) + rx_status->flag |= RX_FLAG_40MHZ; + + if (status->is_ht) + rx_status->flag |= RX_FLAG_HT; + + rx_status->flag |= RX_FLAG_MACTIME_START; + + /* hw will set status->decrypted true, if it finds the + * frame is open data frame or mgmt frame. + * Thus hw will not decrypt a robust managment frame + * for IEEE80211w but still set status->decrypted + * true, so here we should set it back to undecrypted + * for IEEE80211w frame, and mac80211 sw will help + * to decrypt it + */ + if (status->decrypted) { + hdr = (struct ieee80211_hdr *)(skb->data + + status->rx_drvinfo_size + status->rx_bufshift); + + if (!hdr) { + /* during testing, hdr could be NULL here */ + return false; + } + if ((ieee80211_is_robust_mgmt_frame(hdr)) && + (ieee80211_has_protected(hdr->frame_control))) + rx_status->flag &= ~RX_FLAG_DECRYPTED; + else + rx_status->flag |= RX_FLAG_DECRYPTED; + } + + /* rate_idx: index of data rate into band's + * supported rates or MCS index if HT rates + * are use (RX_FLAG_HT) + */ + rx_status->rate_idx = rtlwifi_rate_mapping(hw, status->is_ht, + status->rate, false); + + rx_status->mactime = status->timestamp_low; + if (phystatus == true) { + p_drvinfo = (struct rx_fwinfo_8723e *)(skb->data + + status->rx_bufshift); + + _rtl8723ae_translate_rx_signal_stuff(hw, + skb, status, pdesc, p_drvinfo); + } + + /*rx_status->qual = status->signal; */ + rx_status->signal = status->recvsignalpower + 10; + /*rx_status->noise = -status->noise; */ + + return true; +} + +void rtl8723ae_tx_fill_desc(struct ieee80211_hw *hw, + struct ieee80211_hdr *hdr, u8 *pdesc_tx, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff *skb, u8 hw_queue, + struct rtl_tcb_desc *ptcdesc) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + bool defaultadapter = true; + u8 *pdesc = (u8 *) pdesc_tx; + u16 seq_number; + __le16 fc = hdr->frame_control; + u8 fw_qsel = _rtl8723ae_map_hwqueue_to_fwqueue(skb, hw_queue); + bool firstseg = ((hdr->seq_ctrl & + cpu_to_le16(IEEE80211_SCTL_FRAG)) == 0); + bool lastseg = ((hdr->frame_control & + cpu_to_le16(IEEE80211_FCTL_MOREFRAGS)) == 0); + dma_addr_t mapping = pci_map_single(rtlpci->pdev, + skb->data, skb->len, + PCI_DMA_TODEVICE); + u8 bw_40 = 0; + + if (pci_dma_mapping_error(rtlpci->pdev, mapping)) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "DMA mapping error"); + return; + } + if (mac->opmode == NL80211_IFTYPE_STATION) { + bw_40 = mac->bw_40; + } else if (mac->opmode == NL80211_IFTYPE_AP || + mac->opmode == NL80211_IFTYPE_ADHOC) { + if (sta) + bw_40 = sta->bandwidth >= IEEE80211_STA_RX_BW_40; + } + + seq_number = (le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_SEQ) >> 4; + + rtl_get_tcb_desc(hw, info, sta, skb, ptcdesc); + + CLEAR_PCI_TX_DESC_CONTENT(pdesc, sizeof(struct tx_desc_8723e)); + + if (ieee80211_is_nullfunc(fc) || ieee80211_is_ctl(fc)) { + firstseg = true; + lastseg = true; + } + + if (firstseg) { + SET_TX_DESC_OFFSET(pdesc, USB_HWDESC_HEADER_LEN); + + SET_TX_DESC_TX_RATE(pdesc, ptcdesc->hw_rate); + + if (ptcdesc->use_shortgi || ptcdesc->use_shortpreamble) + SET_TX_DESC_DATA_SHORTGI(pdesc, 1); + + if (info->flags & IEEE80211_TX_CTL_AMPDU) { + SET_TX_DESC_AGG_BREAK(pdesc, 1); + SET_TX_DESC_MAX_AGG_NUM(pdesc, 0x14); + } + SET_TX_DESC_SEQ(pdesc, seq_number); + + SET_TX_DESC_RTS_ENABLE(pdesc, ((ptcdesc->rts_enable && + !ptcdesc-> + cts_enable) ? 1 : 0)); + SET_TX_DESC_HW_RTS_ENABLE(pdesc, + ((ptcdesc->rts_enable + || ptcdesc->cts_enable) ? 1 : 0)); + SET_TX_DESC_CTS2SELF(pdesc, ((ptcdesc->cts_enable) ? 1 : 0)); + SET_TX_DESC_RTS_STBC(pdesc, ((ptcdesc->rts_stbc) ? 1 : 0)); + + SET_TX_DESC_RTS_RATE(pdesc, ptcdesc->rts_rate); + SET_TX_DESC_RTS_BW(pdesc, 0); + SET_TX_DESC_RTS_SC(pdesc, ptcdesc->rts_sc); + SET_TX_DESC_RTS_SHORT(pdesc, + ((ptcdesc->rts_rate <= DESC92_RATE54M) ? + (ptcdesc->rts_use_shortpreamble ? 1 : 0) + : (ptcdesc->rts_use_shortgi ? 1 : 0))); + + if (bw_40) { + if (ptcdesc->packet_bw) { + SET_TX_DESC_DATA_BW(pdesc, 1); + SET_TX_DESC_TX_SUB_CARRIER(pdesc, 3); + } else { + SET_TX_DESC_DATA_BW(pdesc, 0); + SET_TX_DESC_TX_SUB_CARRIER(pdesc, + mac->cur_40_prime_sc); + } + } else { + SET_TX_DESC_DATA_BW(pdesc, 0); + SET_TX_DESC_TX_SUB_CARRIER(pdesc, 0); + } + + SET_TX_DESC_LINIP(pdesc, 0); + SET_TX_DESC_PKT_SIZE(pdesc, (u16) skb->len); + + if (sta) { + u8 ampdu_density = sta->ht_cap.ampdu_density; + SET_TX_DESC_AMPDU_DENSITY(pdesc, ampdu_density); + } + + if (info->control.hw_key) { + struct ieee80211_key_conf *keyconf = + info->control.hw_key; + + switch (keyconf->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + case WLAN_CIPHER_SUITE_TKIP: + SET_TX_DESC_SEC_TYPE(pdesc, 0x1); + break; + case WLAN_CIPHER_SUITE_CCMP: + SET_TX_DESC_SEC_TYPE(pdesc, 0x3); + break; + default: + SET_TX_DESC_SEC_TYPE(pdesc, 0x0); + break; + } + } + + SET_TX_DESC_PKT_ID(pdesc, 0); + SET_TX_DESC_QUEUE_SEL(pdesc, fw_qsel); + + SET_TX_DESC_DATA_RATE_FB_LIMIT(pdesc, 0x1F); + SET_TX_DESC_RTS_RATE_FB_LIMIT(pdesc, 0xF); + SET_TX_DESC_DISABLE_FB(pdesc, 0); + SET_TX_DESC_USE_RATE(pdesc, ptcdesc->use_driver_rate ? 1 : 0); + + if (ieee80211_is_data_qos(fc)) { + if (mac->rdg_en) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "Enable RDG function.\n"); + SET_TX_DESC_RDG_ENABLE(pdesc, 1); + SET_TX_DESC_HTC(pdesc, 1); + } + } + } + + SET_TX_DESC_FIRST_SEG(pdesc, (firstseg ? 1 : 0)); + SET_TX_DESC_LAST_SEG(pdesc, (lastseg ? 1 : 0)); + + SET_TX_DESC_TX_BUFFER_SIZE(pdesc, (u16) skb->len); + + SET_TX_DESC_TX_BUFFER_ADDRESS(pdesc, mapping); + + if (rtlpriv->dm.useramask) { + SET_TX_DESC_RATE_ID(pdesc, ptcdesc->ratr_index); + SET_TX_DESC_MACID(pdesc, ptcdesc->mac_id); + } else { + SET_TX_DESC_RATE_ID(pdesc, 0xC + ptcdesc->ratr_index); + SET_TX_DESC_MACID(pdesc, ptcdesc->ratr_index); + } + + if ((!ieee80211_is_data_qos(fc)) && ppsc->fwctrl_lps) { + SET_TX_DESC_HWSEQ_EN_8723(pdesc, 1); + + if (!defaultadapter) + SET_TX_DESC_HWSEQ_SEL_8723(pdesc, 1); + } + + SET_TX_DESC_MORE_FRAG(pdesc, (lastseg ? 0 : 1)); + + if (is_multicast_ether_addr(ieee80211_get_DA(hdr)) || + is_broadcast_ether_addr(ieee80211_get_DA(hdr))) { + SET_TX_DESC_BMC(pdesc, 1); + } + + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, "\n"); +} + +void rtl8723ae_tx_fill_cmddesc(struct ieee80211_hw *hw, + u8 *pdesc, bool firstseg, + bool lastseg, struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_pci *rtlpci = rtl_pcidev(rtl_pcipriv(hw)); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(skb->data); + u8 fw_queue = QSLT_BEACON; + dma_addr_t mapping = pci_map_single(rtlpci->pdev, + skb->data, skb->len, + PCI_DMA_TODEVICE); + __le16 fc = hdr->frame_control; + + if (pci_dma_mapping_error(rtlpci->pdev, mapping)) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_TRACE, + "DMA mapping error"); + return; + } + CLEAR_PCI_TX_DESC_CONTENT(pdesc, TX_DESC_SIZE); + + if (firstseg) + SET_TX_DESC_OFFSET(pdesc, USB_HWDESC_HEADER_LEN); + + SET_TX_DESC_TX_RATE(pdesc, DESC92_RATE1M); + + SET_TX_DESC_SEQ(pdesc, 0); + + SET_TX_DESC_LINIP(pdesc, 0); + + SET_TX_DESC_QUEUE_SEL(pdesc, fw_queue); + + SET_TX_DESC_FIRST_SEG(pdesc, 1); + SET_TX_DESC_LAST_SEG(pdesc, 1); + + SET_TX_DESC_TX_BUFFER_SIZE(pdesc, (u16) (skb->len)); + + SET_TX_DESC_TX_BUFFER_ADDRESS(pdesc, mapping); + + SET_TX_DESC_RATE_ID(pdesc, 7); + SET_TX_DESC_MACID(pdesc, 0); + + SET_TX_DESC_OWN(pdesc, 1); + + SET_TX_DESC_PKT_SIZE((u8 *) pdesc, (u16) (skb->len)); + + SET_TX_DESC_FIRST_SEG(pdesc, 1); + SET_TX_DESC_LAST_SEG(pdesc, 1); + + SET_TX_DESC_OFFSET(pdesc, 0x20); + + SET_TX_DESC_USE_RATE(pdesc, 1); + + if (!ieee80211_is_data_qos(fc)) { + SET_TX_DESC_HWSEQ_EN_8723(pdesc, 1); + /* SET_TX_DESC_HWSEQ_EN(pdesc, 1); */ + /* SET_TX_DESC_PKT_ID(pdesc, 8); */ + } + + RT_PRINT_DATA(rtlpriv, COMP_CMD, DBG_LOUD, + "H2C Tx Cmd Content\n", + pdesc, TX_DESC_SIZE); +} + +void rtl8723ae_set_desc(u8 *pdesc, bool istx, u8 desc_name, u8 *val) +{ + if (istx == true) { + switch (desc_name) { + case HW_DESC_OWN: + SET_TX_DESC_OWN(pdesc, 1); + break; + case HW_DESC_TX_NEXTDESC_ADDR: + SET_TX_DESC_NEXT_DESC_ADDRESS(pdesc, *(u32 *) val); + break; + default: + RT_ASSERT(false, "ERR txdesc :%d not process\n", + desc_name); + break; + } + } else { + switch (desc_name) { + case HW_DESC_RXOWN: + SET_RX_DESC_OWN(pdesc, 1); + break; + case HW_DESC_RXBUFF_ADDR: + SET_RX_DESC_BUFF_ADDR(pdesc, *(u32 *) val); + break; + case HW_DESC_RXPKT_LEN: + SET_RX_DESC_PKT_LEN(pdesc, *(u32 *) val); + break; + case HW_DESC_RXERO: + SET_RX_DESC_EOR(pdesc, 1); + break; + default: + RT_ASSERT(false, "ERR rxdesc :%d not process\n", + desc_name); + break; + } + } +} + +u32 rtl8723ae_get_desc(u8 *pdesc, bool istx, u8 desc_name) +{ + u32 ret = 0; + + if (istx == true) { + switch (desc_name) { + case HW_DESC_OWN: + ret = GET_TX_DESC_OWN(pdesc); + break; + case HW_DESC_TXBUFF_ADDR: + ret = GET_TX_DESC_TX_BUFFER_ADDRESS(pdesc); + break; + default: + RT_ASSERT(false, "ERR txdesc :%d not process\n", + desc_name); + break; + } + } else { + switch (desc_name) { + case HW_DESC_OWN: + ret = GET_RX_DESC_OWN(pdesc); + break; + case HW_DESC_RXPKT_LEN: + ret = GET_RX_DESC_PKT_LEN(pdesc); + break; + default: + RT_ASSERT(false, "ERR rxdesc :%d not process\n", + desc_name); + break; + } + } + return ret; +} + +void rtl8723ae_tx_polling(struct ieee80211_hw *hw, u8 hw_queue) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + if (hw_queue == BEACON_QUEUE) { + rtl_write_word(rtlpriv, REG_PCIE_CTRL_REG, BIT(4)); + } else { + rtl_write_word(rtlpriv, REG_PCIE_CTRL_REG, + BIT(0) << (hw_queue)); + } +} diff --git a/drivers/net/wireless/rtlwifi/rtl8723ae/trx.h b/drivers/net/wireless/rtlwifi/rtl8723ae/trx.h new file mode 100644 index 0000000..ad05b54 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/rtl8723ae/trx.h @@ -0,0 +1,725 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL8723E_TRX_H__ +#define __RTL8723E_TRX_H__ + +#define TX_DESC_SIZE 64 +#define TX_DESC_AGGR_SUBFRAME_SIZE 32 + +#define RX_DESC_SIZE 32 +#define RX_DRV_INFO_SIZE_UNIT 8 + +#define TX_DESC_NEXT_DESC_OFFSET 40 +#define USB_HWDESC_HEADER_LEN 32 +#define CRCLENGTH 4 + +#define SET_TX_DESC_PKT_SIZE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 0, 16, __val) +#define SET_TX_DESC_OFFSET(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 16, 8, __val) +#define SET_TX_DESC_BMC(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 24, 1, __val) +#define SET_TX_DESC_HTC(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 25, 1, __val) +#define SET_TX_DESC_LAST_SEG(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 26, 1, __val) +#define SET_TX_DESC_FIRST_SEG(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 27, 1, __val) +#define SET_TX_DESC_LINIP(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 28, 1, __val) +#define SET_TX_DESC_NO_ACM(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 29, 1, __val) +#define SET_TX_DESC_GF(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 30, 1, __val) +#define SET_TX_DESC_OWN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 31, 1, __val) + +#define GET_TX_DESC_PKT_SIZE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 0, 16) +#define GET_TX_DESC_OFFSET(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 16, 8) +#define GET_TX_DESC_BMC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 24, 1) +#define GET_TX_DESC_HTC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 25, 1) +#define GET_TX_DESC_LAST_SEG(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 26, 1) +#define GET_TX_DESC_FIRST_SEG(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 27, 1) +#define GET_TX_DESC_LINIP(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 28, 1) +#define GET_TX_DESC_NO_ACM(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 29, 1) +#define GET_TX_DESC_GF(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 30, 1) +#define GET_TX_DESC_OWN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 31, 1) + +#define SET_TX_DESC_MACID(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 0, 5, __val) +#define SET_TX_DESC_AGG_BREAK(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 5, 1, __val) +#define SET_TX_DESC_BK(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 6, 1, __val) +#define SET_TX_DESC_RDG_ENABLE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 7, 1, __val) +#define SET_TX_DESC_QUEUE_SEL(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 8, 5, __val) +#define SET_TX_DESC_RDG_NAV_EXT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 13, 1, __val) +#define SET_TX_DESC_LSIG_TXOP_EN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 14, 1, __val) +#define SET_TX_DESC_PIFS(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 15, 1, __val) +#define SET_TX_DESC_RATE_ID(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 16, 4, __val) +#define SET_TX_DESC_NAV_USE_HDR(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 20, 1, __val) +#define SET_TX_DESC_EN_DESC_ID(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 21, 1, __val) +#define SET_TX_DESC_SEC_TYPE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 22, 2, __val) +#define SET_TX_DESC_PKT_OFFSET(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+4, 24, 8, __val) + +#define GET_TX_DESC_MACID(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 0, 5) +#define GET_TX_DESC_AGG_ENABLE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 5, 1) +#define GET_TX_DESC_AGG_BREAK(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 6, 1) +#define GET_TX_DESC_RDG_ENABLE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 7, 1) +#define GET_TX_DESC_QUEUE_SEL(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 8, 5) +#define GET_TX_DESC_RDG_NAV_EXT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 13, 1) +#define GET_TX_DESC_LSIG_TXOP_EN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 14, 1) +#define GET_TX_DESC_PIFS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 15, 1) +#define GET_TX_DESC_RATE_ID(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 16, 4) +#define GET_TX_DESC_NAV_USE_HDR(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 20, 1) +#define GET_TX_DESC_EN_DESC_ID(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 21, 1) +#define GET_TX_DESC_SEC_TYPE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 22, 2) +#define GET_TX_DESC_PKT_OFFSET(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 24, 8) + +#define SET_TX_DESC_RTS_RC(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 0, 6, __val) +#define SET_TX_DESC_DATA_RC(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 6, 6, __val) +#define SET_TX_DESC_BAR_RTY_TH(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 14, 2, __val) +#define SET_TX_DESC_MORE_FRAG(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 17, 1, __val) +#define SET_TX_DESC_RAW(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 18, 1, __val) +#define SET_TX_DESC_CCX(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 19, 1, __val) +#define SET_TX_DESC_AMPDU_DENSITY(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 20, 3, __val) +#define SET_TX_DESC_ANTSEL_A(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 24, 1, __val) +#define SET_TX_DESC_ANTSEL_B(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 25, 1, __val) +#define SET_TX_DESC_TX_ANT_CCK(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 26, 2, __val) +#define SET_TX_DESC_TX_ANTL(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 28, 2, __val) +#define SET_TX_DESC_TX_ANT_HT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+8, 30, 2, __val) + +#define GET_TX_DESC_RTS_RC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 0, 6) +#define GET_TX_DESC_DATA_RC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 6, 6) +#define GET_TX_DESC_BAR_RTY_TH(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 14, 2) +#define GET_TX_DESC_MORE_FRAG(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 17, 1) +#define GET_TX_DESC_RAW(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 18, 1) +#define GET_TX_DESC_CCX(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 19, 1) +#define GET_TX_DESC_AMPDU_DENSITY(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 20, 3) +#define GET_TX_DESC_ANTSEL_A(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 24, 1) +#define GET_TX_DESC_ANTSEL_B(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 25, 1) +#define GET_TX_DESC_TX_ANT_CCK(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 26, 2) +#define GET_TX_DESC_TX_ANTL(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 28, 2) +#define GET_TX_DESC_TX_ANT_HT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 30, 2) + +#define SET_TX_DESC_NEXT_HEAP_PAGE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+12, 0, 8, __val) +#define SET_TX_DESC_TAIL_PAGE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+12, 8, 8, __val) +#define SET_TX_DESC_SEQ(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+12, 16, 12, __val) +#define SET_TX_DESC_PKT_ID(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+12, 28, 4, __val) + +#define GET_TX_DESC_NEXT_HEAP_PAGE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 0, 8) +#define GET_TX_DESC_TAIL_PAGE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 8, 8) +#define GET_TX_DESC_SEQ(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 16, 12) +#define GET_TX_DESC_PKT_ID(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 28, 4) + +/* For RTL8723 */ +#define SET_TX_DESC_TRIGGER_INT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+12, 30, 1, __val) +#define SET_TX_DESC_HWSEQ_EN_8723(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+12, 31, 1, __val) +#define SET_TX_DESC_HWSEQ_SEL_8723(__pTxDesc, __Value) \ + SET_BITS_TO_LE_4BYTE(__pTxDesc+16, 6, 2, __Value) + +#define SET_TX_DESC_RTS_RATE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 0, 5, __val) +#define SET_TX_DESC_AP_DCFE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 5, 1, __val) +#define SET_TX_DESC_QOS(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 6, 1, __val) +#define SET_TX_DESC_HWSEQ_EN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 7, 1, __val) +#define SET_TX_DESC_USE_RATE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 8, 1, __val) +#define SET_TX_DESC_DISABLE_RTS_FB(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 9, 1, __val) +#define SET_TX_DESC_DISABLE_FB(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 10, 1, __val) +#define SET_TX_DESC_CTS2SELF(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 11, 1, __val) +#define SET_TX_DESC_RTS_ENABLE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 12, 1, __val) +#define SET_TX_DESC_HW_RTS_ENABLE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 13, 1, __val) +#define SET_TX_DESC_PORT_ID(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 14, 1, __val) +#define SET_TX_DESC_WAIT_DCTS(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 18, 1, __val) +#define SET_TX_DESC_CTS2AP_EN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 19, 1, __val) +#define SET_TX_DESC_TX_SUB_CARRIER(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 20, 2, __val) +#define SET_TX_DESC_TX_STBC(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 22, 2, __val) +#define SET_TX_DESC_DATA_SHORT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 24, 1, __val) +#define SET_TX_DESC_DATA_BW(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 25, 1, __val) +#define SET_TX_DESC_RTS_SHORT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 26, 1, __val) +#define SET_TX_DESC_RTS_BW(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 27, 1, __val) +#define SET_TX_DESC_RTS_SC(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 28, 2, __val) +#define SET_TX_DESC_RTS_STBC(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+16, 30, 2, __val) + +#define GET_TX_DESC_RTS_RATE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 0, 5) +#define GET_TX_DESC_AP_DCFE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 5, 1) +#define GET_TX_DESC_QOS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 6, 1) +#define GET_TX_DESC_HWSEQ_EN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 7, 1) +#define GET_TX_DESC_USE_RATE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 8, 1) +#define GET_TX_DESC_DISABLE_RTS_FB(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 9, 1) +#define GET_TX_DESC_DISABLE_FB(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 10, 1) +#define GET_TX_DESC_CTS2SELF(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 11, 1) +#define GET_TX_DESC_RTS_ENABLE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 12, 1) +#define GET_TX_DESC_HW_RTS_ENABLE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 13, 1) +#define GET_TX_DESC_PORT_ID(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 14, 1) +#define GET_TX_DESC_WAIT_DCTS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 18, 1) +#define GET_TX_DESC_CTS2AP_EN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 19, 1) +#define GET_TX_DESC_TX_SUB_CARRIER(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 20, 2) +#define GET_TX_DESC_TX_STBC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 22, 2) +#define GET_TX_DESC_DATA_SHORT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 24, 1) +#define GET_TX_DESC_DATA_BW(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 25, 1) +#define GET_TX_DESC_RTS_SHORT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 26, 1) +#define GET_TX_DESC_RTS_BW(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 27, 1) +#define GET_TX_DESC_RTS_SC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 28, 2) +#define GET_TX_DESC_RTS_STBC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 30, 2) + +#define SET_TX_DESC_TX_RATE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+20, 0, 6, __val) +#define SET_TX_DESC_DATA_SHORTGI(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+20, 6, 1, __val) +#define SET_TX_DESC_CCX_TAG(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+20, 7, 1, __val) +#define SET_TX_DESC_DATA_RATE_FB_LIMIT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+20, 8, 5, __val) +#define SET_TX_DESC_RTS_RATE_FB_LIMIT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+20, 13, 4, __val) +#define SET_TX_DESC_RETRY_LIMIT_ENABLE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+20, 17, 1, __val) +#define SET_TX_DESC_DATA_RETRY_LIMIT(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+20, 18, 6, __val) +#define SET_TX_DESC_USB_TXAGG_NUM(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+20, 24, 8, __val) + +#define GET_TX_DESC_TX_RATE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 0, 6) +#define GET_TX_DESC_DATA_SHORTGI(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 6, 1) +#define GET_TX_DESC_CCX_TAG(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 7, 1) +#define GET_TX_DESC_DATA_RATE_FB_LIMIT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 8, 5) +#define GET_TX_DESC_RTS_RATE_FB_LIMIT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 13, 4) +#define GET_TX_DESC_RETRY_LIMIT_ENABLE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 17, 1) +#define GET_TX_DESC_DATA_RETRY_LIMIT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 18, 6) +#define GET_TX_DESC_USB_TXAGG_NUM(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 24, 8) + +#define SET_TX_DESC_TXAGC_A(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 0, 5, __val) +#define SET_TX_DESC_TXAGC_B(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 5, 5, __val) +#define SET_TX_DESC_USE_MAX_LEN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 10, 1, __val) +#define SET_TX_DESC_MAX_AGG_NUM(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 11, 5, __val) +#define SET_TX_DESC_MCSG1_MAX_LEN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 16, 4, __val) +#define SET_TX_DESC_MCSG2_MAX_LEN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 20, 4, __val) +#define SET_TX_DESC_MCSG3_MAX_LEN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 24, 4, __val) +#define SET_TX_DESC_MCS7_SGI_MAX_LEN(__pdesc, __val)\ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 28, 4, __val) + +#define GET_TX_DESC_TXAGC_A(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 0, 5) +#define GET_TX_DESC_TXAGC_B(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 5, 5) +#define GET_TX_DESC_USE_MAX_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 10, 1) +#define GET_TX_DESC_MAX_AGG_NUM(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 11, 5) +#define GET_TX_DESC_MCSG1_MAX_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 16, 4) +#define GET_TX_DESC_MCSG2_MAX_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 20, 4) +#define GET_TX_DESC_MCSG3_MAX_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 24, 4) +#define GET_TX_DESC_MCS7_SGI_MAX_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 28, 4) + +#define SET_TX_DESC_TX_BUFFER_SIZE(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+28, 0, 16, __val) +#define SET_TX_DESC_MCSG4_MAX_LEN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+28, 16, 4, __val) +#define SET_TX_DESC_MCSG5_MAX_LEN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+28, 20, 4, __val) +#define SET_TX_DESC_MCSG6_MAX_LEN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+28, 24, 4, __val) +#define SET_TX_DESC_MCS15_SGI_MAX_LEN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+28, 28, 4, __val) + +#define GET_TX_DESC_TX_BUFFER_SIZE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+28, 0, 16) +#define GET_TX_DESC_MCSG4_MAX_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+28, 16, 4) +#define GET_TX_DESC_MCSG5_MAX_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+28, 20, 4) +#define GET_TX_DESC_MCSG6_MAX_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+28, 24, 4) +#define GET_TX_DESC_MCS15_SGI_MAX_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+28, 28, 4) + +#define SET_TX_DESC_TX_BUFFER_ADDRESS(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+32, 0, 32, __val) +#define SET_TX_DESC_TX_BUFFER_ADDRESS64(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+36, 0, 32, __val) + +#define GET_TX_DESC_TX_BUFFER_ADDRESS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+32, 0, 32) +#define GET_TX_DESC_TX_BUFFER_ADDRESS64(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+36, 0, 32) + +#define SET_TX_DESC_NEXT_DESC_ADDRESS(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+40, 0, 32, __val) +#define SET_TX_DESC_NEXT_DESC_ADDRESS64(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+44, 0, 32, __val) + +#define GET_TX_DESC_NEXT_DESC_ADDRESS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+40, 0, 32) +#define GET_TX_DESC_NEXT_DESC_ADDRESS64(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+44, 0, 32) + +#define GET_RX_DESC_PKT_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 0, 14) +#define GET_RX_DESC_CRC32(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 14, 1) +#define GET_RX_DESC_ICV(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 15, 1) +#define GET_RX_DESC_DRV_INFO_SIZE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 16, 4) +#define GET_RX_DESC_SECURITY(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 20, 3) +#define GET_RX_DESC_QOS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 23, 1) +#define GET_RX_DESC_SHIFT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 24, 2) +#define GET_RX_DESC_PHYST(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 26, 1) +#define GET_RX_DESC_SWDEC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 27, 1) +#define GET_RX_DESC_LS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 28, 1) +#define GET_RX_DESC_FS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 29, 1) +#define GET_RX_DESC_EOR(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 30, 1) +#define GET_RX_DESC_OWN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc, 31, 1) + +#define SET_RX_DESC_PKT_LEN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 0, 14, __val) +#define SET_RX_DESC_EOR(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 30, 1, __val) +#define SET_RX_DESC_OWN(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc, 31, 1, __val) + +#define GET_RX_DESC_MACID(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 0, 5) +#define GET_RX_DESC_TID(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 5, 4) +#define GET_RX_DESC_HWRSVD(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 9, 5) +#define GET_RX_DESC_PAGGR(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 14, 1) +#define GET_RX_DESC_FAGGR(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 15, 1) +#define GET_RX_DESC_A1_FIT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 16, 4) +#define GET_RX_DESC_A2_FIT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 20, 4) +#define GET_RX_DESC_PAM(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 24, 1) +#define GET_RX_DESC_PWR(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 25, 1) +#define GET_RX_DESC_MD(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 26, 1) +#define GET_RX_DESC_MF(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 27, 1) +#define GET_RX_DESC_TYPE(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 28, 2) +#define GET_RX_DESC_MC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 30, 1) +#define GET_RX_DESC_BC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+4, 31, 1) +#define GET_RX_DESC_SEQ(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 0, 12) +#define GET_RX_DESC_FRAG(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 12, 4) +#define GET_RX_DESC_NEXT_PKT_LEN(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 16, 14) +#define GET_RX_DESC_NEXT_IND(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 30, 1) +#define GET_RX_DESC_RSVD(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+8, 31, 1) + +#define GET_RX_DESC_RXMCS(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 0, 6) +#define GET_RX_DESC_RXHT(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 6, 1) +#define GET_RX_DESC_SPLCP(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 8, 1) +#define GET_RX_DESC_BW(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 9, 1) +#define GET_RX_DESC_HTC(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 10, 1) +#define GET_RX_DESC_HWPC_ERR(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 14, 1) +#define GET_RX_DESC_HWPC_IND(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 15, 1) +#define GET_RX_DESC_IV0(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+12, 16, 16) + +#define GET_RX_DESC_IV1(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+16, 0, 32) +#define GET_RX_DESC_TSFL(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+20, 0, 32) + +#define GET_RX_DESC_BUFF_ADDR(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+24, 0, 32) +#define GET_RX_DESC_BUFF_ADDR64(__pdesc) \ + LE_BITS_TO_4BYTE(__pdesc+28, 0, 32) + +#define SET_RX_DESC_BUFF_ADDR(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+24, 0, 32, __val) +#define SET_RX_DESC_BUFF_ADDR64(__pdesc, __val) \ + SET_BITS_TO_LE_4BYTE(__pdesc+28, 0, 32, __val) + +#define CLEAR_PCI_TX_DESC_CONTENT(__pdesc, _size) \ +do { \ + if (_size > TX_DESC_NEXT_DESC_OFFSET) \ + memset(__pdesc, 0, TX_DESC_NEXT_DESC_OFFSET); \ + else \ + memset(__pdesc, 0, _size); \ +} while (0) + +#define RTL8723E_RX_HAL_IS_CCK_RATE(rxmcs) \ + ((rxmcs) == DESC92_RATE1M || \ + (rxmcs) == DESC92_RATE2M || \ + (rxmcs) == DESC92_RATE5_5M || \ + (rxmcs) == DESC92_RATE11M) + +struct rx_fwinfo_8723e { + u8 gain_trsw[4]; + u8 pwdb_all; + u8 cfosho[4]; + u8 cfotail[4]; + char rxevm[2]; + char rxsnr[4]; + u8 pdsnr[2]; + u8 csi_current[2]; + u8 csi_target[2]; + u8 sigevm; + u8 max_ex_pwr; + u8 ex_intf_flag:1; + u8 sgi_en:1; + u8 rxsc:2; + u8 reserve:4; +} __packed; + +struct tx_desc_8723e { + u32 pktsize:16; + u32 offset:8; + u32 bmc:1; + u32 htc:1; + u32 lastseg:1; + u32 firstseg:1; + u32 linip:1; + u32 noacm:1; + u32 gf:1; + u32 own:1; + + u32 macid:5; + u32 agg_en:1; + u32 bk:1; + u32 rdg_en:1; + u32 queuesel:5; + u32 rd_nav_ext:1; + u32 lsig_txop_en:1; + u32 pifs:1; + u32 rateid:4; + u32 nav_usehdr:1; + u32 en_descid:1; + u32 sectype:2; + u32 pktoffset:8; + + u32 rts_rc:6; + u32 data_rc:6; + u32 rsvd0:2; + u32 bar_retryht:2; + u32 rsvd1:1; + u32 morefrag:1; + u32 raw:1; + u32 ccx:1; + u32 ampdudensity:3; + u32 rsvd2:1; + u32 ant_sela:1; + u32 ant_selb:1; + u32 txant_cck:2; + u32 txant_l:2; + u32 txant_ht:2; + + u32 nextheadpage:8; + u32 tailpage:8; + u32 seq:12; + u32 pktid:4; + + u32 rtsrate:5; + u32 apdcfe:1; + u32 qos:1; + u32 hwseq_enable:1; + u32 userrate:1; + u32 dis_rtsfb:1; + u32 dis_datafb:1; + u32 cts2self:1; + u32 rts_en:1; + u32 hwrts_en:1; + u32 portid:1; + u32 rsvd3:3; + u32 waitdcts:1; + u32 cts2ap_en:1; + u32 txsc:2; + u32 stbc:2; + u32 txshort:1; + u32 txbw:1; + u32 rtsshort:1; + u32 rtsbw:1; + u32 rtssc:2; + u32 rtsstbc:2; + + u32 txrate:6; + u32 shortgi:1; + u32 ccxt:1; + u32 txrate_fb_lmt:5; + u32 rtsrate_fb_lmt:4; + u32 retrylmt_en:1; + u32 txretrylmt:6; + u32 usb_txaggnum:8; + + u32 txagca:5; + u32 txagcb:5; + u32 usemaxlen:1; + u32 maxaggnum:5; + u32 mcsg1maxlen:4; + u32 mcsg2maxlen:4; + u32 mcsg3maxlen:4; + u32 mcs7sgimaxlen:4; + + u32 txbuffersize:16; + u32 mcsg4maxlen:4; + u32 mcsg5maxlen:4; + u32 mcsg6maxlen:4; + u32 mcsg15sgimaxlen:4; + + u32 txbuffaddr; + u32 txbufferaddr64; + u32 nextdescaddress; + u32 nextdescaddress64; + + u32 reserve_pass_pcie_mm_limit[4]; +} __packed; + +struct rx_desc_8723e { + u32 length:14; + u32 crc32:1; + u32 icverror:1; + u32 drv_infosize:4; + u32 security:3; + u32 qos:1; + u32 shift:2; + u32 phystatus:1; + u32 swdec:1; + u32 lastseg:1; + u32 firstseg:1; + u32 eor:1; + u32 own:1; + + u32 macid:5; + u32 tid:4; + u32 hwrsvd:5; + u32 paggr:1; + u32 faggr:1; + u32 a1_fit:4; + u32 a2_fit:4; + u32 pam:1; + u32 pwr:1; + u32 moredata:1; + u32 morefrag:1; + u32 type:2; + u32 mc:1; + u32 bc:1; + + u32 seq:12; + u32 frag:4; + u32 nextpktlen:14; + u32 nextind:1; + u32 rsvd:1; + + u32 rxmcs:6; + u32 rxht:1; + u32 amsdu:1; + u32 splcp:1; + u32 bandwidth:1; + u32 htc:1; + u32 tcpchk_rpt:1; + u32 ipcchk_rpt:1; + u32 tcpchk_valid:1; + u32 hwpcerr:1; + u32 hwpcind:1; + u32 iv0:16; + + u32 iv1; + + u32 tsfl; + + u32 bufferaddress; + u32 bufferaddress64; + +} __packed; + +void rtl8723ae_tx_fill_desc(struct ieee80211_hw *hw, + struct ieee80211_hdr *hdr, u8 *pdesc_tx, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff *skb, u8 hw_queue, + struct rtl_tcb_desc *ptcb_desc); +bool rtl8723ae_rx_query_desc(struct ieee80211_hw *hw, + struct rtl_stats *status, + struct ieee80211_rx_status *rx_status, + u8 *pdesc, struct sk_buff *skb); +void rtl8723ae_set_desc(u8 *pdesc, bool istx, u8 desc_name, u8 *val); +u32 rtl8723ae_get_desc(u8 *pdesc, bool istx, u8 desc_name); +void rtl8723ae_tx_polling(struct ieee80211_hw *hw, u8 hw_queue); +void rtl8723ae_tx_fill_cmddesc(struct ieee80211_hw *hw, u8 *pdesc, + bool b_firstseg, bool b_lastseg, + struct sk_buff *skb); + +#endif diff --git a/drivers/net/wireless/rtlwifi/stats.c b/drivers/net/wireless/rtlwifi/stats.c new file mode 100644 index 0000000..8ed3174 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/stats.c @@ -0,0 +1,268 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ +#include "wifi.h" +#include "stats.h" +#include + +u8 rtl_query_rxpwrpercentage(char antpower) +{ + if ((antpower <= -100) || (antpower >= 20)) + return 0; + else if (antpower >= 0) + return 100; + else + return 100 + antpower; +} +EXPORT_SYMBOL(rtl_query_rxpwrpercentage); + +u8 rtl_evm_db_to_percentage(char value) +{ + char ret_val; + ret_val = value; + + if (ret_val >= 0) + ret_val = 0; + if (ret_val <= -33) + ret_val = -33; + ret_val = 0 - ret_val; + ret_val *= 3; + if (ret_val == 99) + ret_val = 100; + + return ret_val; +} +EXPORT_SYMBOL(rtl_evm_db_to_percentage); + +static long rtl_translate_todbm(struct ieee80211_hw *hw, + u8 signal_strength_index) +{ + long signal_power; + + signal_power = (long)((signal_strength_index + 1) >> 1); + signal_power -= 95; + return signal_power; +} + +long rtl_signal_scale_mapping(struct ieee80211_hw *hw, long currsig) +{ + long retsig; + + if (currsig >= 61 && currsig <= 100) + retsig = 90 + ((currsig - 60) / 4); + else if (currsig >= 41 && currsig <= 60) + retsig = 78 + ((currsig - 40) / 2); + else if (currsig >= 31 && currsig <= 40) + retsig = 66 + (currsig - 30); + else if (currsig >= 21 && currsig <= 30) + retsig = 54 + (currsig - 20); + else if (currsig >= 5 && currsig <= 20) + retsig = 42 + (((currsig - 5) * 2) / 3); + else if (currsig == 4) + retsig = 36; + else if (currsig == 3) + retsig = 27; + else if (currsig == 2) + retsig = 18; + else if (currsig == 1) + retsig = 9; + else + retsig = currsig; + + return retsig; +} +EXPORT_SYMBOL(rtl_signal_scale_mapping); + +static void rtl_process_ui_rssi(struct ieee80211_hw *hw, + struct rtl_stats *pstatus) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_phy *rtlphy = &(rtlpriv->phy); + u8 rfpath; + u32 last_rssi, tmpval; + + rtlpriv->stats.rssi_calculate_cnt++; + + if (rtlpriv->stats.ui_rssi.total_num++ >= PHY_RSSI_SLID_WIN_MAX) { + rtlpriv->stats.ui_rssi.total_num = PHY_RSSI_SLID_WIN_MAX; + last_rssi = rtlpriv->stats.ui_rssi.elements[ + rtlpriv->stats.ui_rssi.index]; + rtlpriv->stats.ui_rssi.total_val -= last_rssi; + } + rtlpriv->stats.ui_rssi.total_val += pstatus->signalstrength; + rtlpriv->stats.ui_rssi.elements[rtlpriv->stats.ui_rssi.index++] = + pstatus->signalstrength; + if (rtlpriv->stats.ui_rssi.index >= PHY_RSSI_SLID_WIN_MAX) + rtlpriv->stats.ui_rssi.index = 0; + tmpval = rtlpriv->stats.ui_rssi.total_val / + rtlpriv->stats.ui_rssi.total_num; + rtlpriv->stats.signal_strength = rtl_translate_todbm(hw, + (u8) tmpval); + pstatus->rssi = rtlpriv->stats.signal_strength; + + if (pstatus->is_cck) + return; + + for (rfpath = RF90_PATH_A; rfpath < rtlphy->num_total_rfpath; + rfpath++) { + if (rtlpriv->stats.rx_rssi_percentage[rfpath] == 0) { + rtlpriv->stats.rx_rssi_percentage[rfpath] = + pstatus->rx_mimo_signalstrength[rfpath]; + + } + if (pstatus->rx_mimo_signalstrength[rfpath] > + rtlpriv->stats.rx_rssi_percentage[rfpath]) { + rtlpriv->stats.rx_rssi_percentage[rfpath] = + ((rtlpriv->stats.rx_rssi_percentage[rfpath] * + (RX_SMOOTH_FACTOR - 1)) + + (pstatus->rx_mimo_signalstrength[rfpath])) / + (RX_SMOOTH_FACTOR); + rtlpriv->stats.rx_rssi_percentage[rfpath] = + rtlpriv->stats.rx_rssi_percentage[rfpath] + 1; + } else { + rtlpriv->stats.rx_rssi_percentage[rfpath] = + ((rtlpriv->stats.rx_rssi_percentage[rfpath] * + (RX_SMOOTH_FACTOR - 1)) + + (pstatus->rx_mimo_signalstrength[rfpath])) / + (RX_SMOOTH_FACTOR); + } + } +} + +static void rtl_update_rxsignalstatistics(struct ieee80211_hw *hw, + struct rtl_stats *pstatus) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + int weighting = 0; + + if (rtlpriv->stats.recv_signal_power == 0) + rtlpriv->stats.recv_signal_power = pstatus->recvsignalpower; + if (pstatus->recvsignalpower > rtlpriv->stats.recv_signal_power) + weighting = 5; + else if (pstatus->recvsignalpower < rtlpriv->stats.recv_signal_power) + weighting = (-5); + rtlpriv->stats.recv_signal_power = (rtlpriv->stats.recv_signal_power * + 5 + pstatus->recvsignalpower + weighting) / 6; +} + +static void rtl_process_pwdb(struct ieee80211_hw *hw, struct rtl_stats *pstatus) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_sta_info *drv_priv = NULL; + struct ieee80211_sta *sta = NULL; + long undec_sm_pwdb; + + rcu_read_lock(); + if (rtlpriv->mac80211.opmode != NL80211_IFTYPE_STATION) + sta = rtl_find_sta(hw, pstatus->psaddr); + + /* adhoc or ap mode */ + if (sta) { + drv_priv = (struct rtl_sta_info *) sta->drv_priv; + undec_sm_pwdb = drv_priv->rssi_stat.undec_sm_pwdb; + } else { + undec_sm_pwdb = rtlpriv->dm.undec_sm_pwdb; + } + + if (undec_sm_pwdb < 0) + undec_sm_pwdb = pstatus->rx_pwdb_all; + if (pstatus->rx_pwdb_all > (u32) undec_sm_pwdb) { + undec_sm_pwdb = (((undec_sm_pwdb) * + (RX_SMOOTH_FACTOR - 1)) + + (pstatus->rx_pwdb_all)) / (RX_SMOOTH_FACTOR); + undec_sm_pwdb = undec_sm_pwdb + 1; + } else { + undec_sm_pwdb = (((undec_sm_pwdb) * (RX_SMOOTH_FACTOR - 1)) + + (pstatus->rx_pwdb_all)) / (RX_SMOOTH_FACTOR); + } + + if (sta) { + drv_priv->rssi_stat.undec_sm_pwdb = undec_sm_pwdb; + } else { + rtlpriv->dm.undec_sm_pwdb = undec_sm_pwdb; + } + rcu_read_unlock(); + + rtl_update_rxsignalstatistics(hw, pstatus); +} + +static void rtl_process_ui_link_quality(struct ieee80211_hw *hw, + struct rtl_stats *pstatus) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u32 last_evm, n_stream, tmpval; + + if (pstatus->signalquality == 0) + return; + + if (rtlpriv->stats.ui_link_quality.total_num++ >= + PHY_LINKQUALITY_SLID_WIN_MAX) { + rtlpriv->stats.ui_link_quality.total_num = + PHY_LINKQUALITY_SLID_WIN_MAX; + last_evm = rtlpriv->stats.ui_link_quality.elements[ + rtlpriv->stats.ui_link_quality.index]; + rtlpriv->stats.ui_link_quality.total_val -= last_evm; + } + rtlpriv->stats.ui_link_quality.total_val += pstatus->signalquality; + rtlpriv->stats.ui_link_quality.elements[ + rtlpriv->stats.ui_link_quality.index++] = + pstatus->signalquality; + if (rtlpriv->stats.ui_link_quality.index >= + PHY_LINKQUALITY_SLID_WIN_MAX) + rtlpriv->stats.ui_link_quality.index = 0; + tmpval = rtlpriv->stats.ui_link_quality.total_val / + rtlpriv->stats.ui_link_quality.total_num; + rtlpriv->stats.signal_quality = tmpval; + rtlpriv->stats.last_sigstrength_inpercent = tmpval; + for (n_stream = 0; n_stream < 2; n_stream++) { + if (pstatus->rx_mimo_sig_qual[n_stream] != -1) { + if (rtlpriv->stats.rx_evm_percentage[n_stream] == 0) { + rtlpriv->stats.rx_evm_percentage[n_stream] = + pstatus->rx_mimo_sig_qual[n_stream]; + } + rtlpriv->stats.rx_evm_percentage[n_stream] = + ((rtlpriv->stats.rx_evm_percentage[n_stream] + * (RX_SMOOTH_FACTOR - 1)) + + (pstatus->rx_mimo_sig_qual[n_stream] * 1)) / + (RX_SMOOTH_FACTOR); + } + } +} + +void rtl_process_phyinfo(struct ieee80211_hw *hw, u8 *buffer, + struct rtl_stats *pstatus) +{ + + if (!pstatus->packet_matchbssid) + return; + + rtl_process_ui_rssi(hw, pstatus); + rtl_process_pwdb(hw, pstatus); + rtl_process_ui_link_quality(hw, pstatus); +} +EXPORT_SYMBOL(rtl_process_phyinfo); diff --git a/drivers/net/wireless/rtlwifi/stats.h b/drivers/net/wireless/rtlwifi/stats.h new file mode 100644 index 0000000..0dbdc52 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/stats.h @@ -0,0 +1,46 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL_STATS_H__ +#define __RTL_STATS_H__ + +#define PHY_RSSI_SLID_WIN_MAX 100 +#define PHY_LINKQUALITY_SLID_WIN_MAX 20 +#define PHY_BEACON_RSSI_SLID_WIN_MAX 10 + +/* Rx smooth factor */ +#define RX_SMOOTH_FACTOR 20 + +u8 rtl_query_rxpwrpercentage(char antpower); +u8 rtl_evm_db_to_percentage(char value); +long rtl_signal_scale_mapping(struct ieee80211_hw *hw, long currsig); +void rtl_process_phyinfo(struct ieee80211_hw *hw, u8 *buffer, + struct rtl_stats *pstatus); + +#endif diff --git a/drivers/net/wireless/rtlwifi/usb.c b/drivers/net/wireless/rtlwifi/usb.c new file mode 100644 index 0000000..1feebdc --- /dev/null +++ b/drivers/net/wireless/rtlwifi/usb.c @@ -0,0 +1,1167 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + *****************************************************************************/ + +#include "wifi.h" +#include "core.h" +#include "usb.h" +#include "base.h" +#include "ps.h" +#include "rtl8192c/fw_common.h" +#include + +#define REALTEK_USB_VENQT_READ 0xC0 +#define REALTEK_USB_VENQT_WRITE 0x40 +#define REALTEK_USB_VENQT_CMD_REQ 0x05 +#define REALTEK_USB_VENQT_CMD_IDX 0x00 + +#define MAX_USBCTRL_VENDORREQ_TIMES 10 + +static void usbctrl_async_callback(struct urb *urb) +{ + if (urb) { + /* free dr */ + kfree(urb->setup_packet); + /* free databuf */ + kfree(urb->transfer_buffer); + } +} + +static int _usbctrl_vendorreq_async_write(struct usb_device *udev, u8 request, + u16 value, u16 index, void *pdata, + u16 len) +{ + int rc; + unsigned int pipe; + u8 reqtype; + struct usb_ctrlrequest *dr; + struct urb *urb; + const u16 databuf_maxlen = REALTEK_USB_VENQT_MAX_BUF_SIZE; + u8 *databuf; + + if (WARN_ON_ONCE(len > databuf_maxlen)) + len = databuf_maxlen; + + pipe = usb_sndctrlpipe(udev, 0); /* write_out */ + reqtype = REALTEK_USB_VENQT_WRITE; + + dr = kmalloc(sizeof(*dr), GFP_ATOMIC); + if (!dr) + return -ENOMEM; + + databuf = kmalloc(databuf_maxlen, GFP_ATOMIC); + if (!databuf) { + kfree(dr); + return -ENOMEM; + } + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + kfree(databuf); + kfree(dr); + return -ENOMEM; + } + + dr->bRequestType = reqtype; + dr->bRequest = request; + dr->wValue = cpu_to_le16(value); + dr->wIndex = cpu_to_le16(index); + dr->wLength = cpu_to_le16(len); + /* data are already in little-endian order */ + memcpy(databuf, pdata, len); + usb_fill_control_urb(urb, udev, pipe, + (unsigned char *)dr, databuf, len, + usbctrl_async_callback, NULL); + rc = usb_submit_urb(urb, GFP_ATOMIC); + if (rc < 0) { + kfree(databuf); + kfree(dr); + } + usb_free_urb(urb); + return rc; +} + +static int _usbctrl_vendorreq_sync_read(struct usb_device *udev, u8 request, + u16 value, u16 index, void *pdata, + u16 len) +{ + unsigned int pipe; + int status; + u8 reqtype; + int vendorreq_times = 0; + static int count; + + pipe = usb_rcvctrlpipe(udev, 0); /* read_in */ + reqtype = REALTEK_USB_VENQT_READ; + + do { + status = usb_control_msg(udev, pipe, request, reqtype, value, + index, pdata, len, 0); /*max. timeout*/ + if (status < 0) { + /* firmware download is checksumed, don't retry */ + if ((value >= FW_8192C_START_ADDRESS && + value <= FW_8192C_END_ADDRESS)) + break; + } else { + break; + } + } while (++vendorreq_times < MAX_USBCTRL_VENDORREQ_TIMES); + + if (status < 0 && count++ < 4) + pr_err("reg 0x%x, usbctrl_vendorreq TimeOut! status:0x%x value=0x%x\n", + value, status, *(u32 *)pdata); + return status; +} + +static u32 _usb_read_sync(struct rtl_priv *rtlpriv, u32 addr, u16 len) +{ + struct device *dev = rtlpriv->io.dev; + struct usb_device *udev = to_usb_device(dev); + u8 request; + u16 wvalue; + u16 index; + __le32 *data; + unsigned long flags; + + spin_lock_irqsave(&rtlpriv->locks.usb_lock, flags); + if (++rtlpriv->usb_data_index >= RTL_USB_MAX_RX_COUNT) + rtlpriv->usb_data_index = 0; + data = &rtlpriv->usb_data[rtlpriv->usb_data_index]; + spin_unlock_irqrestore(&rtlpriv->locks.usb_lock, flags); + request = REALTEK_USB_VENQT_CMD_REQ; + index = REALTEK_USB_VENQT_CMD_IDX; /* n/a */ + + wvalue = (u16)addr; + _usbctrl_vendorreq_sync_read(udev, request, wvalue, index, data, len); + return le32_to_cpu(*data); +} + +static u8 _usb_read8_sync(struct rtl_priv *rtlpriv, u32 addr) +{ + return (u8)_usb_read_sync(rtlpriv, addr, 1); +} + +static u16 _usb_read16_sync(struct rtl_priv *rtlpriv, u32 addr) +{ + return (u16)_usb_read_sync(rtlpriv, addr, 2); +} + +static u32 _usb_read32_sync(struct rtl_priv *rtlpriv, u32 addr) +{ + return _usb_read_sync(rtlpriv, addr, 4); +} + +static void _usb_write_async(struct usb_device *udev, u32 addr, u32 val, + u16 len) +{ + u8 request; + u16 wvalue; + u16 index; + __le32 data; + + request = REALTEK_USB_VENQT_CMD_REQ; + index = REALTEK_USB_VENQT_CMD_IDX; /* n/a */ + wvalue = (u16)(addr&0x0000ffff); + data = cpu_to_le32(val); + _usbctrl_vendorreq_async_write(udev, request, wvalue, index, &data, + len); +} + +static void _usb_write8_async(struct rtl_priv *rtlpriv, u32 addr, u8 val) +{ + struct device *dev = rtlpriv->io.dev; + + _usb_write_async(to_usb_device(dev), addr, val, 1); +} + +static void _usb_write16_async(struct rtl_priv *rtlpriv, u32 addr, u16 val) +{ + struct device *dev = rtlpriv->io.dev; + + _usb_write_async(to_usb_device(dev), addr, val, 2); +} + +static void _usb_write32_async(struct rtl_priv *rtlpriv, u32 addr, u32 val) +{ + struct device *dev = rtlpriv->io.dev; + + _usb_write_async(to_usb_device(dev), addr, val, 4); +} + +static void _usb_writeN_sync(struct rtl_priv *rtlpriv, u32 addr, void *data, + u16 len) +{ + struct device *dev = rtlpriv->io.dev; + struct usb_device *udev = to_usb_device(dev); + u8 request = REALTEK_USB_VENQT_CMD_REQ; + u8 reqtype = REALTEK_USB_VENQT_WRITE; + u16 wvalue; + u16 index = REALTEK_USB_VENQT_CMD_IDX; + int pipe = usb_sndctrlpipe(udev, 0); /* write_out */ + u8 *buffer; + + wvalue = (u16)(addr & 0x0000ffff); + buffer = kmemdup(data, len, GFP_ATOMIC); + if (!buffer) + return; + usb_control_msg(udev, pipe, request, reqtype, wvalue, + index, buffer, len, 50); + + kfree(buffer); +} + +static void _rtl_usb_io_handler_init(struct device *dev, + struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->io.dev = dev; + mutex_init(&rtlpriv->io.bb_mutex); + rtlpriv->io.write8_async = _usb_write8_async; + rtlpriv->io.write16_async = _usb_write16_async; + rtlpriv->io.write32_async = _usb_write32_async; + rtlpriv->io.read8_sync = _usb_read8_sync; + rtlpriv->io.read16_sync = _usb_read16_sync; + rtlpriv->io.read32_sync = _usb_read32_sync; + rtlpriv->io.writeN_sync = _usb_writeN_sync; +} + +static void _rtl_usb_io_handler_release(struct ieee80211_hw *hw) +{ + struct rtl_priv __maybe_unused *rtlpriv = rtl_priv(hw); + + mutex_destroy(&rtlpriv->io.bb_mutex); +} + +/** + * + * Default aggregation handler. Do nothing and just return the oldest skb. + */ +static struct sk_buff *_none_usb_tx_aggregate_hdl(struct ieee80211_hw *hw, + struct sk_buff_head *list) +{ + return skb_dequeue(list); +} + +#define IS_HIGH_SPEED_USB(udev) \ + ((USB_SPEED_HIGH == (udev)->speed) ? true : false) + +static int _rtl_usb_init_tx(struct ieee80211_hw *hw) +{ + u32 i; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + + rtlusb->max_bulk_out_size = IS_HIGH_SPEED_USB(rtlusb->udev) + ? USB_HIGH_SPEED_BULK_SIZE + : USB_FULL_SPEED_BULK_SIZE; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, "USB Max Bulk-out Size=%d\n", + rtlusb->max_bulk_out_size); + + for (i = 0; i < __RTL_TXQ_NUM; i++) { + u32 ep_num = rtlusb->ep_map.ep_mapping[i]; + if (!ep_num) { + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "Invalid endpoint map setting!\n"); + return -EINVAL; + } + } + + rtlusb->usb_tx_post_hdl = + rtlpriv->cfg->usb_interface_cfg->usb_tx_post_hdl; + rtlusb->usb_tx_cleanup = + rtlpriv->cfg->usb_interface_cfg->usb_tx_cleanup; + rtlusb->usb_tx_aggregate_hdl = + (rtlpriv->cfg->usb_interface_cfg->usb_tx_aggregate_hdl) + ? rtlpriv->cfg->usb_interface_cfg->usb_tx_aggregate_hdl + : &_none_usb_tx_aggregate_hdl; + + init_usb_anchor(&rtlusb->tx_submitted); + for (i = 0; i < RTL_USB_MAX_EP_NUM; i++) { + skb_queue_head_init(&rtlusb->tx_skb_queue[i]); + init_usb_anchor(&rtlusb->tx_pending[i]); + } + return 0; +} + +static void _rtl_rx_work(unsigned long param); + +static int _rtl_usb_init_rx(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_usb_priv *usb_priv = rtl_usbpriv(hw); + struct rtl_usb *rtlusb = rtl_usbdev(usb_priv); + + rtlusb->rx_max_size = rtlpriv->cfg->usb_interface_cfg->rx_max_size; + rtlusb->rx_urb_num = rtlpriv->cfg->usb_interface_cfg->rx_urb_num; + rtlusb->in_ep = rtlpriv->cfg->usb_interface_cfg->in_ep_num; + rtlusb->usb_rx_hdl = rtlpriv->cfg->usb_interface_cfg->usb_rx_hdl; + rtlusb->usb_rx_segregate_hdl = + rtlpriv->cfg->usb_interface_cfg->usb_rx_segregate_hdl; + + pr_info("rx_max_size %d, rx_urb_num %d, in_ep %d\n", + rtlusb->rx_max_size, rtlusb->rx_urb_num, rtlusb->in_ep); + init_usb_anchor(&rtlusb->rx_submitted); + init_usb_anchor(&rtlusb->rx_cleanup_urbs); + + skb_queue_head_init(&rtlusb->rx_queue); + rtlusb->rx_work_tasklet.func = _rtl_rx_work; + rtlusb->rx_work_tasklet.data = (unsigned long)rtlusb; + + return 0; +} + +static int _rtl_usb_init(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_usb_priv *usb_priv = rtl_usbpriv(hw); + struct rtl_usb *rtlusb = rtl_usbdev(usb_priv); + int err; + u8 epidx; + struct usb_interface *usb_intf = rtlusb->intf; + u8 epnums = usb_intf->cur_altsetting->desc.bNumEndpoints; + + rtlusb->out_ep_nums = rtlusb->in_ep_nums = 0; + for (epidx = 0; epidx < epnums; epidx++) { + struct usb_endpoint_descriptor *pep_desc; + pep_desc = &usb_intf->cur_altsetting->endpoint[epidx].desc; + + if (usb_endpoint_dir_in(pep_desc)) + rtlusb->in_ep_nums++; + else if (usb_endpoint_dir_out(pep_desc)) + rtlusb->out_ep_nums++; + + RT_TRACE(rtlpriv, COMP_INIT, DBG_DMESG, + "USB EP(0x%02x), MaxPacketSize=%d, Interval=%d\n", + pep_desc->bEndpointAddress, pep_desc->wMaxPacketSize, + pep_desc->bInterval); + } + if (rtlusb->in_ep_nums < rtlpriv->cfg->usb_interface_cfg->in_ep_num) { + pr_err("Too few input end points found\n"); + return -EINVAL; + } + if (rtlusb->out_ep_nums == 0) { + pr_err("No output end points found\n"); + return -EINVAL; + } + /* usb endpoint mapping */ + err = rtlpriv->cfg->usb_interface_cfg->usb_endpoint_mapping(hw); + rtlusb->usb_mq_to_hwq = rtlpriv->cfg->usb_interface_cfg->usb_mq_to_hwq; + _rtl_usb_init_tx(hw); + _rtl_usb_init_rx(hw); + return err; +} + +static void rtl_usb_init_sw(struct ieee80211_hw *hw) +{ + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_ps_ctl *ppsc = rtl_psc(rtl_priv(hw)); + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + + rtlhal->hw = hw; + ppsc->inactiveps = false; + ppsc->leisure_ps = false; + ppsc->fwctrl_lps = false; + ppsc->reg_fwctrl_lps = 3; + ppsc->reg_max_lps_awakeintvl = 5; + ppsc->fwctrl_psmode = FW_PS_DTIM_MODE; + + /* IBSS */ + mac->beacon_interval = 100; + + /* AMPDU */ + mac->min_space_cfg = 0; + mac->max_mss_density = 0; + + /* set sane AMPDU defaults */ + mac->current_ampdu_density = 7; + mac->current_ampdu_factor = 3; + + /* QOS */ + rtlusb->acm_method = eAcmWay2_SW; + + /* IRQ */ + /* HIMR - turn all on */ + rtlusb->irq_mask[0] = 0xFFFFFFFF; + /* HIMR_EX - turn all on */ + rtlusb->irq_mask[1] = 0xFFFFFFFF; + rtlusb->disableHWSM = true; +} + +static void _rtl_rx_completed(struct urb *urb); + +static int _rtl_prep_rx_urb(struct ieee80211_hw *hw, struct rtl_usb *rtlusb, + struct urb *urb, gfp_t gfp_mask) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + void *buf; + + buf = usb_alloc_coherent(rtlusb->udev, rtlusb->rx_max_size, gfp_mask, + &urb->transfer_dma); + if (!buf) { + RT_TRACE(rtlpriv, COMP_USB, DBG_EMERG, + "Failed to usb_alloc_coherent!!\n"); + return -ENOMEM; + } + + usb_fill_bulk_urb(urb, rtlusb->udev, + usb_rcvbulkpipe(rtlusb->udev, rtlusb->in_ep), + buf, rtlusb->rx_max_size, _rtl_rx_completed, rtlusb); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + return 0; +} + +static void _rtl_usb_rx_process_agg(struct ieee80211_hw *hw, + struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 *rxdesc = skb->data; + struct ieee80211_hdr *hdr; + bool unicast = false; + __le16 fc; + struct ieee80211_rx_status rx_status = {0}; + struct rtl_stats stats = { + .signal = 0, + .noise = -98, + .rate = 0, + }; + + skb_pull(skb, RTL_RX_DESC_SIZE); + rtlpriv->cfg->ops->query_rx_desc(hw, &stats, &rx_status, rxdesc, skb); + skb_pull(skb, (stats.rx_drvinfo_size + stats.rx_bufshift)); + hdr = (struct ieee80211_hdr *)(skb->data); + fc = hdr->frame_control; + if (!stats.crc) { + memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); + + if (is_broadcast_ether_addr(hdr->addr1)) { + /*TODO*/; + } else if (is_multicast_ether_addr(hdr->addr1)) { + /*TODO*/ + } else { + unicast = true; + rtlpriv->stats.rxbytesunicast += skb->len; + } + + rtl_is_special_data(hw, skb, false); + + if (ieee80211_is_data(fc)) { + rtlpriv->cfg->ops->led_control(hw, LED_CTL_RX); + + if (unicast) + rtlpriv->link_info.num_rx_inperiod++; + } + } +} + +static void _rtl_usb_rx_process_noagg(struct ieee80211_hw *hw, + struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + u8 *rxdesc = skb->data; + struct ieee80211_hdr *hdr; + bool unicast = false; + __le16 fc; + struct ieee80211_rx_status rx_status = {0}; + struct rtl_stats stats = { + .signal = 0, + .noise = -98, + .rate = 0, + }; + + skb_pull(skb, RTL_RX_DESC_SIZE); + rtlpriv->cfg->ops->query_rx_desc(hw, &stats, &rx_status, rxdesc, skb); + skb_pull(skb, (stats.rx_drvinfo_size + stats.rx_bufshift)); + hdr = (struct ieee80211_hdr *)(skb->data); + fc = hdr->frame_control; + if (!stats.crc) { + memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); + + if (is_broadcast_ether_addr(hdr->addr1)) { + /*TODO*/; + } else if (is_multicast_ether_addr(hdr->addr1)) { + /*TODO*/ + } else { + unicast = true; + rtlpriv->stats.rxbytesunicast += skb->len; + } + + rtl_is_special_data(hw, skb, false); + + if (ieee80211_is_data(fc)) { + rtlpriv->cfg->ops->led_control(hw, LED_CTL_RX); + + if (unicast) + rtlpriv->link_info.num_rx_inperiod++; + } + + /* static bcn for roaming */ + rtl_beacon_statistic(hw, skb); + + if (likely(rtl_action_proc(hw, skb, false))) + ieee80211_rx(hw, skb); + else + dev_kfree_skb_any(skb); + } +} + +static void _rtl_rx_pre_process(struct ieee80211_hw *hw, struct sk_buff *skb) +{ + struct sk_buff *_skb; + struct sk_buff_head rx_queue; + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + + skb_queue_head_init(&rx_queue); + if (rtlusb->usb_rx_segregate_hdl) + rtlusb->usb_rx_segregate_hdl(hw, skb, &rx_queue); + WARN_ON(skb_queue_empty(&rx_queue)); + while (!skb_queue_empty(&rx_queue)) { + _skb = skb_dequeue(&rx_queue); + _rtl_usb_rx_process_agg(hw, _skb); + ieee80211_rx(hw, _skb); + } +} + +#define __RX_SKB_MAX_QUEUED 32 + +static void _rtl_rx_work(unsigned long param) +{ + struct rtl_usb *rtlusb = (struct rtl_usb *)param; + struct ieee80211_hw *hw = usb_get_intfdata(rtlusb->intf); + struct sk_buff *skb; + + while ((skb = skb_dequeue(&rtlusb->rx_queue))) { + if (unlikely(IS_USB_STOP(rtlusb))) { + dev_kfree_skb_any(skb); + continue; + } + + if (likely(!rtlusb->usb_rx_segregate_hdl)) { + _rtl_usb_rx_process_noagg(hw, skb); + } else { + /* TO DO */ + _rtl_rx_pre_process(hw, skb); + pr_err("rx agg not supported\n"); + } + } +} + +static unsigned int _rtl_rx_get_padding(struct ieee80211_hdr *hdr, + unsigned int len) +{ + unsigned int padding = 0; + + /* make function no-op when possible */ + if (NET_IP_ALIGN == 0 || len < sizeof(*hdr)) + return 0; + + /* alignment calculation as in lbtf_rx() / carl9170_rx_copy_data() */ + /* TODO: deduplicate common code, define helper function instead? */ + + if (ieee80211_is_data_qos(hdr->frame_control)) { + u8 *qc = ieee80211_get_qos_ctl(hdr); + + padding ^= NET_IP_ALIGN; + + /* Input might be invalid, avoid accessing memory outside + * the buffer. + */ + if ((unsigned long)qc - (unsigned long)hdr < len && + *qc & IEEE80211_QOS_CTL_A_MSDU_PRESENT) + padding ^= NET_IP_ALIGN; + } + + if (ieee80211_has_a4(hdr->frame_control)) + padding ^= NET_IP_ALIGN; + + return padding; +} + +#define __RADIO_TAP_SIZE_RSV 32 + +static void _rtl_rx_completed(struct urb *_urb) +{ + struct rtl_usb *rtlusb = (struct rtl_usb *)_urb->context; + struct ieee80211_hw *hw = usb_get_intfdata(rtlusb->intf); + struct rtl_priv *rtlpriv = rtl_priv(hw); + int err = 0; + + if (unlikely(IS_USB_STOP(rtlusb))) + goto free; + + if (likely(0 == _urb->status)) { + unsigned int padding; + struct sk_buff *skb; + unsigned int qlen; + unsigned int size = _urb->actual_length; + struct ieee80211_hdr *hdr; + + if (size < RTL_RX_DESC_SIZE + sizeof(struct ieee80211_hdr)) { + RT_TRACE(rtlpriv, COMP_USB, DBG_EMERG, + "Too short packet from bulk IN! (len: %d)\n", + size); + goto resubmit; + } + + qlen = skb_queue_len(&rtlusb->rx_queue); + if (qlen >= __RX_SKB_MAX_QUEUED) { + RT_TRACE(rtlpriv, COMP_USB, DBG_EMERG, + "Pending RX skbuff queue full! (qlen: %d)\n", + qlen); + goto resubmit; + } + + hdr = (void *)(_urb->transfer_buffer + RTL_RX_DESC_SIZE); + padding = _rtl_rx_get_padding(hdr, size - RTL_RX_DESC_SIZE); + + skb = dev_alloc_skb(size + __RADIO_TAP_SIZE_RSV + padding); + if (!skb) { + RT_TRACE(rtlpriv, COMP_USB, DBG_EMERG, + "Can't allocate skb for bulk IN!\n"); + goto resubmit; + } + + _rtl_install_trx_info(rtlusb, skb, rtlusb->in_ep); + + /* Make sure the payload data is 4 byte aligned. */ + skb_reserve(skb, padding); + + /* reserve some space for mac80211's radiotap */ + skb_reserve(skb, __RADIO_TAP_SIZE_RSV); + + memcpy(skb_put(skb, size), _urb->transfer_buffer, size); + + skb_queue_tail(&rtlusb->rx_queue, skb); + tasklet_schedule(&rtlusb->rx_work_tasklet); + + goto resubmit; + } + + switch (_urb->status) { + /* disconnect */ + case -ENOENT: + case -ECONNRESET: + case -ENODEV: + case -ESHUTDOWN: + goto free; + default: + break; + } + +resubmit: + usb_anchor_urb(_urb, &rtlusb->rx_submitted); + err = usb_submit_urb(_urb, GFP_ATOMIC); + if (unlikely(err)) { + usb_unanchor_urb(_urb); + goto free; + } + return; + +free: + /* On some architectures, usb_free_coherent must not be called from + * hardirq context. Queue urb to cleanup list. + */ + usb_anchor_urb(_urb, &rtlusb->rx_cleanup_urbs); +} + +#undef __RADIO_TAP_SIZE_RSV + +static void _rtl_usb_cleanup_rx(struct ieee80211_hw *hw) +{ + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + struct urb *urb; + + usb_kill_anchored_urbs(&rtlusb->rx_submitted); + + tasklet_kill(&rtlusb->rx_work_tasklet); + skb_queue_purge(&rtlusb->rx_queue); + + while ((urb = usb_get_from_anchor(&rtlusb->rx_cleanup_urbs))) { + usb_free_coherent(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); + usb_free_urb(urb); + } +} + +static int _rtl_usb_receive(struct ieee80211_hw *hw) +{ + struct urb *urb; + int err; + int i; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + + WARN_ON(0 == rtlusb->rx_urb_num); + /* 1600 == 1514 + max WLAN header + rtk info */ + WARN_ON(rtlusb->rx_max_size < 1600); + + for (i = 0; i < rtlusb->rx_urb_num; i++) { + err = -ENOMEM; + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + RT_TRACE(rtlpriv, COMP_USB, DBG_EMERG, + "Failed to alloc URB!!\n"); + goto err_out; + } + + err = _rtl_prep_rx_urb(hw, rtlusb, urb, GFP_KERNEL); + if (err < 0) { + RT_TRACE(rtlpriv, COMP_USB, DBG_EMERG, + "Failed to prep_rx_urb!!\n"); + usb_free_urb(urb); + goto err_out; + } + + usb_anchor_urb(urb, &rtlusb->rx_submitted); + err = usb_submit_urb(urb, GFP_KERNEL); + if (err) + goto err_out; + usb_free_urb(urb); + } + return 0; + +err_out: + usb_kill_anchored_urbs(&rtlusb->rx_submitted); + _rtl_usb_cleanup_rx(hw); + return err; +} + +static int rtl_usb_start(struct ieee80211_hw *hw) +{ + int err; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + + err = rtlpriv->cfg->ops->hw_init(hw); + if (!err) { + rtl_init_rx_config(hw); + + /* Enable software */ + SET_USB_START(rtlusb); + /* should after adapter start and interrupt enable. */ + set_hal_start(rtlhal); + + /* Start bulk IN */ + err = _rtl_usb_receive(hw); + } + + return err; +} +/** + * + * + */ + +/*======================= tx =========================================*/ +static void rtl_usb_cleanup(struct ieee80211_hw *hw) +{ + u32 i; + struct sk_buff *_skb; + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + struct ieee80211_tx_info *txinfo; + + SET_USB_STOP(rtlusb); + + /* clean up rx stuff. */ + _rtl_usb_cleanup_rx(hw); + + /* clean up tx stuff */ + for (i = 0; i < RTL_USB_MAX_EP_NUM; i++) { + while ((_skb = skb_dequeue(&rtlusb->tx_skb_queue[i]))) { + rtlusb->usb_tx_cleanup(hw, _skb); + txinfo = IEEE80211_SKB_CB(_skb); + ieee80211_tx_info_clear_status(txinfo); + txinfo->flags |= IEEE80211_TX_STAT_ACK; + ieee80211_tx_status_irqsafe(hw, _skb); + } + usb_kill_anchored_urbs(&rtlusb->tx_pending[i]); + } + usb_kill_anchored_urbs(&rtlusb->tx_submitted); +} + +/** + * + * We may add some struct into struct rtl_usb later. Do deinit here. + * + */ +static void rtl_usb_deinit(struct ieee80211_hw *hw) +{ + rtl_usb_cleanup(hw); +} + +static void rtl_usb_stop(struct ieee80211_hw *hw) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + + /* should after adapter start and interrupt enable. */ + set_hal_stop(rtlhal); + cancel_work_sync(&rtlpriv->works.fill_h2c_cmd); + /* Enable software */ + SET_USB_STOP(rtlusb); + rtl_usb_deinit(hw); + rtlpriv->cfg->ops->hw_disable(hw); +} + +static void _rtl_submit_tx_urb(struct ieee80211_hw *hw, struct urb *_urb) +{ + int err; + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + + usb_anchor_urb(_urb, &rtlusb->tx_submitted); + err = usb_submit_urb(_urb, GFP_ATOMIC); + if (err < 0) { + struct sk_buff *skb; + + RT_TRACE(rtlpriv, COMP_USB, DBG_EMERG, + "Failed to submit urb\n"); + usb_unanchor_urb(_urb); + skb = (struct sk_buff *)_urb->context; + kfree_skb(skb); + } + usb_free_urb(_urb); +} + +static int _usb_tx_post(struct ieee80211_hw *hw, struct urb *urb, + struct sk_buff *skb) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + struct ieee80211_tx_info *txinfo; + + rtlusb->usb_tx_post_hdl(hw, urb, skb); + skb_pull(skb, RTL_TX_HEADER_SIZE); + txinfo = IEEE80211_SKB_CB(skb); + ieee80211_tx_info_clear_status(txinfo); + txinfo->flags |= IEEE80211_TX_STAT_ACK; + + if (urb->status) { + RT_TRACE(rtlpriv, COMP_USB, DBG_EMERG, + "Urb has error status 0x%X\n", urb->status); + goto out; + } + /* TODO: statistics */ +out: + ieee80211_tx_status_irqsafe(hw, skb); + return urb->status; +} + +static void _rtl_tx_complete(struct urb *urb) +{ + struct sk_buff *skb = (struct sk_buff *)urb->context; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct rtl_usb *rtlusb = (struct rtl_usb *)info->rate_driver_data[0]; + struct ieee80211_hw *hw = usb_get_intfdata(rtlusb->intf); + int err; + + if (unlikely(IS_USB_STOP(rtlusb))) + return; + err = _usb_tx_post(hw, urb, skb); + if (err) { + /* Ignore error and keep issuiing other urbs */ + return; + } +} + +static struct urb *_rtl_usb_tx_urb_setup(struct ieee80211_hw *hw, + struct sk_buff *skb, u32 ep_num) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + struct urb *_urb; + + WARN_ON(NULL == skb); + _urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!_urb) { + RT_TRACE(rtlpriv, COMP_USB, DBG_EMERG, + "Can't allocate URB for bulk out!\n"); + kfree_skb(skb); + return NULL; + } + _rtl_install_trx_info(rtlusb, skb, ep_num); + usb_fill_bulk_urb(_urb, rtlusb->udev, usb_sndbulkpipe(rtlusb->udev, + ep_num), skb->data, skb->len, _rtl_tx_complete, skb); + _urb->transfer_flags |= URB_ZERO_PACKET; + return _urb; +} + +static void _rtl_usb_transmit(struct ieee80211_hw *hw, struct sk_buff *skb, + enum rtl_txq qnum) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + u32 ep_num; + struct urb *_urb = NULL; + struct sk_buff *_skb = NULL; + + WARN_ON(NULL == rtlusb->usb_tx_aggregate_hdl); + if (unlikely(IS_USB_STOP(rtlusb))) { + RT_TRACE(rtlpriv, COMP_USB, DBG_EMERG, + "USB device is stopping...\n"); + kfree_skb(skb); + return; + } + ep_num = rtlusb->ep_map.ep_mapping[qnum]; + _skb = skb; + _urb = _rtl_usb_tx_urb_setup(hw, _skb, ep_num); + if (unlikely(!_urb)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Can't allocate urb. Drop skb!\n"); + kfree_skb(skb); + return; + } + _rtl_submit_tx_urb(hw, _urb); +} + +static void _rtl_usb_tx_preprocess(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + struct sk_buff *skb, + u16 hw_queue) +{ + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct rtl_tx_desc *pdesc = NULL; + struct rtl_tcb_desc tcb_desc; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(skb->data); + __le16 fc = hdr->frame_control; + u8 *pda_addr = hdr->addr1; + /* ssn */ + u8 *qc = NULL; + u8 tid = 0; + u16 seq_number = 0; + + memset(&tcb_desc, 0, sizeof(struct rtl_tcb_desc)); + if (ieee80211_is_auth(fc)) { + RT_TRACE(rtlpriv, COMP_SEND, DBG_DMESG, "MAC80211_LINKING\n"); + rtl_ips_nic_on(hw); + } + + if (rtlpriv->psc.sw_ps_enabled) { + if (ieee80211_is_data(fc) && !ieee80211_is_nullfunc(fc) && + !ieee80211_has_pm(fc)) + hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PM); + } + + rtl_action_proc(hw, skb, true); + if (is_multicast_ether_addr(pda_addr)) + rtlpriv->stats.txbytesmulticast += skb->len; + else if (is_broadcast_ether_addr(pda_addr)) + rtlpriv->stats.txbytesbroadcast += skb->len; + else + rtlpriv->stats.txbytesunicast += skb->len; + if (ieee80211_is_data_qos(fc)) { + qc = ieee80211_get_qos_ctl(hdr); + tid = qc[0] & IEEE80211_QOS_CTL_TID_MASK; + seq_number = (le16_to_cpu(hdr->seq_ctrl) & + IEEE80211_SCTL_SEQ) >> 4; + seq_number += 1; + seq_number <<= 4; + } + rtlpriv->cfg->ops->fill_tx_desc(hw, hdr, (u8 *)pdesc, info, sta, skb, + hw_queue, &tcb_desc); + if (!ieee80211_has_morefrags(hdr->frame_control)) { + if (qc) + mac->tids[tid].seq_number = seq_number; + } + if (ieee80211_is_data(fc)) + rtlpriv->cfg->ops->led_control(hw, LED_CTL_TX); +} + +static int rtl_usb_tx(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + struct sk_buff *skb, + struct rtl_tcb_desc *dummy) +{ + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + struct rtl_hal *rtlhal = rtl_hal(rtl_priv(hw)); + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(skb->data); + __le16 fc = hdr->frame_control; + u16 hw_queue; + + if (unlikely(is_hal_stop(rtlhal))) + goto err_free; + hw_queue = rtlusb->usb_mq_to_hwq(fc, skb_get_queue_mapping(skb)); + _rtl_usb_tx_preprocess(hw, sta, skb, hw_queue); + _rtl_usb_transmit(hw, skb, hw_queue); + return NETDEV_TX_OK; + +err_free: + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; +} + +static bool rtl_usb_tx_chk_waitq_insert(struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + struct sk_buff *skb) +{ + return false; +} + +static void rtl_fill_h2c_cmd_work_callback(struct work_struct *work) +{ + struct rtl_works *rtlworks = + container_of(work, struct rtl_works, fill_h2c_cmd); + struct ieee80211_hw *hw = rtlworks->hw; + struct rtl_priv *rtlpriv = rtl_priv(hw); + + rtlpriv->cfg->ops->fill_h2c_cmd(hw, H2C_RA_MASK, 5, rtlpriv->rate_mask); +} + +static struct rtl_intf_ops rtl_usb_ops = { + .adapter_start = rtl_usb_start, + .adapter_stop = rtl_usb_stop, + .adapter_tx = rtl_usb_tx, + .waitq_insert = rtl_usb_tx_chk_waitq_insert, +}; + +int rtl_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id, + struct rtl_hal_cfg *rtl_hal_cfg) +{ + int err; + struct ieee80211_hw *hw = NULL; + struct rtl_priv *rtlpriv = NULL; + struct usb_device *udev; + struct rtl_usb_priv *usb_priv; + + hw = ieee80211_alloc_hw(sizeof(struct rtl_priv) + + sizeof(struct rtl_usb_priv), &rtl_ops); + if (!hw) { + RT_ASSERT(false, "ieee80211 alloc failed\n"); + return -ENOMEM; + } + rtlpriv = hw->priv; + rtlpriv->usb_data = kzalloc(RTL_USB_MAX_RX_COUNT * sizeof(u32), + GFP_KERNEL); + if (!rtlpriv->usb_data) + return -ENOMEM; + + /* this spin lock must be initialized early */ + spin_lock_init(&rtlpriv->locks.usb_lock); + INIT_WORK(&rtlpriv->works.fill_h2c_cmd, + rtl_fill_h2c_cmd_work_callback); + INIT_WORK(&rtlpriv->works.lps_change_work, + rtl_lps_change_work_callback); + + rtlpriv->usb_data_index = 0; + init_completion(&rtlpriv->firmware_loading_complete); + SET_IEEE80211_DEV(hw, &intf->dev); + udev = interface_to_usbdev(intf); + usb_get_dev(udev); + usb_priv = rtl_usbpriv(hw); + memset(usb_priv, 0, sizeof(*usb_priv)); + usb_priv->dev.intf = intf; + usb_priv->dev.udev = udev; + usb_set_intfdata(intf, hw); + /* init cfg & intf_ops */ + rtlpriv->rtlhal.interface = INTF_USB; + rtlpriv->cfg = rtl_hal_cfg; + rtlpriv->intf_ops = &rtl_usb_ops; + rtl_dbgp_flag_init(hw); + /* Init IO handler */ + _rtl_usb_io_handler_init(&udev->dev, hw); + rtlpriv->cfg->ops->read_chip_version(hw); + /*like read eeprom and so on */ + rtlpriv->cfg->ops->read_eeprom_info(hw); + err = _rtl_usb_init(hw); + if (err) + goto error_out; + rtl_usb_init_sw(hw); + /* Init mac80211 sw */ + err = rtl_init_core(hw); + if (err) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, + "Can't allocate sw for mac80211\n"); + goto error_out; + } + if (rtlpriv->cfg->ops->init_sw_vars(hw)) { + RT_TRACE(rtlpriv, COMP_ERR, DBG_EMERG, "Can't init_sw_vars\n"); + goto error_out; + } + rtlpriv->cfg->ops->init_sw_leds(hw); + + return 0; +error_out: + rtl_deinit_core(hw); + _rtl_usb_io_handler_release(hw); + usb_put_dev(udev); + complete(&rtlpriv->firmware_loading_complete); + return -ENODEV; +} +EXPORT_SYMBOL(rtl_usb_probe); + +void rtl_usb_disconnect(struct usb_interface *intf) +{ + struct ieee80211_hw *hw = usb_get_intfdata(intf); + struct rtl_priv *rtlpriv = rtl_priv(hw); + struct rtl_mac *rtlmac = rtl_mac(rtl_priv(hw)); + struct rtl_usb *rtlusb = rtl_usbdev(rtl_usbpriv(hw)); + + if (unlikely(!rtlpriv)) + return; + + /* just in case driver is removed before firmware callback */ + wait_for_completion(&rtlpriv->firmware_loading_complete); + /*ieee80211_unregister_hw will call ops_stop */ + if (rtlmac->mac80211_registered == 1) { + ieee80211_unregister_hw(hw); + rtlmac->mac80211_registered = 0; + } else { + rtl_deinit_deferred_work(hw); + rtlpriv->intf_ops->adapter_stop(hw); + } + /*deinit rfkill */ + /* rtl_deinit_rfkill(hw); */ + rtl_usb_deinit(hw); + rtl_deinit_core(hw); + kfree(rtlpriv->usb_data); + rtlpriv->cfg->ops->deinit_sw_leds(hw); + rtlpriv->cfg->ops->deinit_sw_vars(hw); + _rtl_usb_io_handler_release(hw); + usb_put_dev(rtlusb->udev); + usb_set_intfdata(intf, NULL); + ieee80211_free_hw(hw); +} +EXPORT_SYMBOL(rtl_usb_disconnect); + +int rtl_usb_suspend(struct usb_interface *pusb_intf, pm_message_t message) +{ + return 0; +} +EXPORT_SYMBOL(rtl_usb_suspend); + +int rtl_usb_resume(struct usb_interface *pusb_intf) +{ + return 0; +} +EXPORT_SYMBOL(rtl_usb_resume); diff --git a/drivers/net/wireless/rtlwifi/usb.h b/drivers/net/wireless/rtlwifi/usb.h new file mode 100644 index 0000000..685273c --- /dev/null +++ b/drivers/net/wireless/rtlwifi/usb.h @@ -0,0 +1,169 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + *****************************************************************************/ + +#ifndef __RTL_USB_H__ +#define __RTL_USB_H__ + +#include + +#define RTL_RX_DESC_SIZE 24 + +#define RTL_USB_DEVICE(vend, prod, cfg) \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, \ + .idVendor = (vend), \ + .idProduct = (prod), \ + .driver_info = (kernel_ulong_t)&(cfg) + +#define USB_HIGH_SPEED_BULK_SIZE 512 +#define USB_FULL_SPEED_BULK_SIZE 64 + + +#define RTL_USB_MAX_TXQ_NUM 4 /* max tx queue */ +#define RTL_USB_MAX_EP_NUM 6 /* max ep number */ +#define RTL_USB_MAX_TX_URBS_NUM 8 + +enum rtl_txq { + /* These definitions shall be consistent with value + * returned by skb_get_queue_mapping + *------------------------------------*/ + RTL_TXQ_BK, + RTL_TXQ_BE, + RTL_TXQ_VI, + RTL_TXQ_VO, + /*------------------------------------*/ + RTL_TXQ_BCN, + RTL_TXQ_MGT, + RTL_TXQ_HI, + + /* Must be last */ + __RTL_TXQ_NUM, +}; + +struct rtl_ep_map { + u32 ep_mapping[__RTL_TXQ_NUM]; +}; + +struct _trx_info { + struct rtl_usb *rtlusb; + u32 ep_num; +}; + +static inline void _rtl_install_trx_info(struct rtl_usb *rtlusb, + struct sk_buff *skb, + u32 ep_num) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + info->rate_driver_data[0] = rtlusb; + info->rate_driver_data[1] = (void *)(__kernel_size_t)ep_num; +} + + +/* Add suspend/resume later */ +enum rtl_usb_state { + USB_STATE_STOP = 0, + USB_STATE_START = 1, +}; + +#define IS_USB_STOP(rtlusb_ptr) (USB_STATE_STOP == (rtlusb_ptr)->state) +#define IS_USB_START(rtlusb_ptr) (USB_STATE_START == (rtlusb_ptr)->state) +#define SET_USB_STOP(rtlusb_ptr) \ + do { \ + (rtlusb_ptr)->state = USB_STATE_STOP; \ + } while (0) + +#define SET_USB_START(rtlusb_ptr) \ + do { \ + (rtlusb_ptr)->state = USB_STATE_START; \ + } while (0) + +struct rtl_usb { + struct usb_device *udev; + struct usb_interface *intf; + enum rtl_usb_state state; + + /* Bcn control register setting */ + u32 reg_bcn_ctrl_val; + /* for 88/92cu card disable */ + u8 disableHWSM; + /*QOS & EDCA */ + enum acm_method acm_method; + /* irq . HIMR,HIMR_EX */ + u32 irq_mask[2]; + bool irq_enabled; + + u16 (*usb_mq_to_hwq)(__le16 fc, u16 mac80211_queue_index); + + /* Tx */ + u8 out_ep_nums ; + u8 out_queue_sel; + struct rtl_ep_map ep_map; + + u32 max_bulk_out_size; + u32 tx_submitted_urbs; + struct sk_buff_head tx_skb_queue[RTL_USB_MAX_EP_NUM]; + + struct usb_anchor tx_pending[RTL_USB_MAX_EP_NUM]; + struct usb_anchor tx_submitted; + + struct sk_buff *(*usb_tx_aggregate_hdl)(struct ieee80211_hw *, + struct sk_buff_head *); + int (*usb_tx_post_hdl)(struct ieee80211_hw *, + struct urb *, struct sk_buff *); + void (*usb_tx_cleanup)(struct ieee80211_hw *, struct sk_buff *); + + /* Rx */ + u8 in_ep_nums; + u32 in_ep; /* Bulk IN endpoint number */ + u32 rx_max_size; /* Bulk IN max buffer size */ + u32 rx_urb_num; /* How many Bulk INs are submitted to host. */ + struct usb_anchor rx_submitted; + struct usb_anchor rx_cleanup_urbs; + struct tasklet_struct rx_work_tasklet; + struct sk_buff_head rx_queue; + void (*usb_rx_segregate_hdl)(struct ieee80211_hw *, struct sk_buff *, + struct sk_buff_head *); + void (*usb_rx_hdl)(struct ieee80211_hw *, struct sk_buff *); +}; + +struct rtl_usb_priv { + struct rtl_usb dev; + struct rtl_led_ctl ledctl; +}; + +#define rtl_usbpriv(hw) (((struct rtl_usb_priv *)(rtl_priv(hw))->priv)) +#define rtl_usbdev(usbpriv) (&((usbpriv)->dev)) + + + +int rtl_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id, + struct rtl_hal_cfg *rtl92cu_hal_cfg); +void rtl_usb_disconnect(struct usb_interface *intf); +int rtl_usb_suspend(struct usb_interface *pusb_intf, pm_message_t message); +int rtl_usb_resume(struct usb_interface *pusb_intf); + +#endif diff --git a/drivers/net/wireless/rtlwifi/wifi.h b/drivers/net/wireless/rtlwifi/wifi.h new file mode 100644 index 0000000..7032587 --- /dev/null +++ b/drivers/net/wireless/rtlwifi/wifi.h @@ -0,0 +1,2479 @@ +/****************************************************************************** + * + * Copyright(c) 2009-2012 Realtek Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * The full GNU General Public License is included in this distribution in the + * file called LICENSE. + * + * Contact Information: + * wlanfae + * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, + * Hsinchu 300, Taiwan. + * + * Larry Finger + * + *****************************************************************************/ + +#ifndef __RTL_WIFI_H__ +#define __RTL_WIFI_H__ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include "debug.h" + +#define RF_CHANGE_BY_INIT 0 +#define RF_CHANGE_BY_IPS BIT(28) +#define RF_CHANGE_BY_PS BIT(29) +#define RF_CHANGE_BY_HW BIT(30) +#define RF_CHANGE_BY_SW BIT(31) + +#define IQK_ADDA_REG_NUM 16 +#define IQK_MAC_REG_NUM 4 + +#define MAX_KEY_LEN 61 +#define KEY_BUF_SIZE 5 + +/* QoS related. */ +/*aci: 0x00 Best Effort*/ +/*aci: 0x01 Background*/ +/*aci: 0x10 Video*/ +/*aci: 0x11 Voice*/ +/*Max: define total number.*/ +#define AC0_BE 0 +#define AC1_BK 1 +#define AC2_VI 2 +#define AC3_VO 3 +#define AC_MAX 4 +#define QOS_QUEUE_NUM 4 +#define RTL_MAC80211_NUM_QUEUE 5 +#define REALTEK_USB_VENQT_MAX_BUF_SIZE 254 +#define RTL_USB_MAX_RX_COUNT 100 +#define QBSS_LOAD_SIZE 5 +#define MAX_WMMELE_LENGTH 64 + +#define TOTAL_CAM_ENTRY 32 + +/*slot time for 11g. */ +#define RTL_SLOT_TIME_9 9 +#define RTL_SLOT_TIME_20 20 + +/*related with tcp/ip. */ +/*if_ehther.h*/ +#define ETH_P_PAE 0x888E /*Port Access Entity (IEEE 802.1X) */ +#define ETH_P_IP 0x0800 /*Internet Protocol packet */ +#define ETH_P_ARP 0x0806 /*Address Resolution packet */ +#define SNAP_SIZE 6 +#define PROTOC_TYPE_SIZE 2 + +/*related with 802.11 frame*/ +#define MAC80211_3ADDR_LEN 24 +#define MAC80211_4ADDR_LEN 30 + +#define CHANNEL_MAX_NUMBER (14 + 24 + 21) /* 14 is the max channel no */ +#define CHANNEL_GROUP_MAX (3 + 9) /* ch1~3, 4~9, 10~14 = three groups */ +#define MAX_PG_GROUP 13 +#define CHANNEL_GROUP_MAX_2G 3 +#define CHANNEL_GROUP_IDX_5GL 3 +#define CHANNEL_GROUP_IDX_5GM 6 +#define CHANNEL_GROUP_IDX_5GH 9 +#define CHANNEL_GROUP_MAX_5G 9 +#define CHANNEL_MAX_NUMBER_2G 14 +#define AVG_THERMAL_NUM 8 +#define AVG_THERMAL_NUM_88E 4 +#define MAX_TID_COUNT 9 + +/* for early mode */ +#define FCS_LEN 4 +#define EM_HDR_LEN 8 + +#define MAX_TX_COUNT 4 +#define MAX_RF_PATH 4 +#define MAX_CHNL_GROUP_24G 6 +#define MAX_CHNL_GROUP_5G 14 + +struct txpower_info_2g { + u8 index_cck_base[MAX_RF_PATH][MAX_CHNL_GROUP_24G]; + u8 index_bw40_base[MAX_RF_PATH][MAX_CHNL_GROUP_24G]; + /*If only one tx, only BW20 and OFDM are used.*/ + u8 cck_diff[MAX_RF_PATH][MAX_TX_COUNT]; + u8 ofdm_diff[MAX_RF_PATH][MAX_TX_COUNT]; + u8 bw20_diff[MAX_RF_PATH][MAX_TX_COUNT]; + u8 bw40_diff[MAX_RF_PATH][MAX_TX_COUNT]; +}; + +struct txpower_info_5g { + u8 index_bw40_base[MAX_RF_PATH][MAX_CHNL_GROUP_5G]; + /*If only one tx, only BW20, OFDM, BW80 and BW160 are used.*/ + u8 ofdm_diff[MAX_RF_PATH][MAX_TX_COUNT]; + u8 bw20_diff[MAX_RF_PATH][MAX_TX_COUNT]; + u8 bw40_diff[MAX_RF_PATH][MAX_TX_COUNT]; +}; + +enum intf_type { + INTF_PCI = 0, + INTF_USB = 1, +}; + +enum radio_path { + RF90_PATH_A = 0, + RF90_PATH_B = 1, + RF90_PATH_C = 2, + RF90_PATH_D = 3, +}; + +enum rt_eeprom_type { + EEPROM_93C46, + EEPROM_93C56, + EEPROM_BOOT_EFUSE, +}; + +enum ttl_status { + RTL_STATUS_INTERFACE_START = 0, +}; + +enum hardware_type { + HARDWARE_TYPE_RTL8192E, + HARDWARE_TYPE_RTL8192U, + HARDWARE_TYPE_RTL8192SE, + HARDWARE_TYPE_RTL8192SU, + HARDWARE_TYPE_RTL8192CE, + HARDWARE_TYPE_RTL8192CU, + HARDWARE_TYPE_RTL8192DE, + HARDWARE_TYPE_RTL8192DU, + HARDWARE_TYPE_RTL8723AE, + HARDWARE_TYPE_RTL8723U, + HARDWARE_TYPE_RTL8188EE, + + /* keep it last */ + HARDWARE_TYPE_NUM +}; + +#define IS_HARDWARE_TYPE_8192SU(rtlhal) \ + (rtlhal->hw_type == HARDWARE_TYPE_RTL8192SU) +#define IS_HARDWARE_TYPE_8192SE(rtlhal) \ + (rtlhal->hw_type == HARDWARE_TYPE_RTL8192SE) +#define IS_HARDWARE_TYPE_8192CE(rtlhal) \ + (rtlhal->hw_type == HARDWARE_TYPE_RTL8192CE) +#define IS_HARDWARE_TYPE_8192CU(rtlhal) \ + (rtlhal->hw_type == HARDWARE_TYPE_RTL8192CU) +#define IS_HARDWARE_TYPE_8192DE(rtlhal) \ + (rtlhal->hw_type == HARDWARE_TYPE_RTL8192DE) +#define IS_HARDWARE_TYPE_8192DU(rtlhal) \ + (rtlhal->hw_type == HARDWARE_TYPE_RTL8192DU) +#define IS_HARDWARE_TYPE_8723E(rtlhal) \ + (rtlhal->hw_type == HARDWARE_TYPE_RTL8723E) +#define IS_HARDWARE_TYPE_8723U(rtlhal) \ + (rtlhal->hw_type == HARDWARE_TYPE_RTL8723U) +#define IS_HARDWARE_TYPE_8192S(rtlhal) \ +(IS_HARDWARE_TYPE_8192SE(rtlhal) || IS_HARDWARE_TYPE_8192SU(rtlhal)) +#define IS_HARDWARE_TYPE_8192C(rtlhal) \ +(IS_HARDWARE_TYPE_8192CE(rtlhal) || IS_HARDWARE_TYPE_8192CU(rtlhal)) +#define IS_HARDWARE_TYPE_8192D(rtlhal) \ +(IS_HARDWARE_TYPE_8192DE(rtlhal) || IS_HARDWARE_TYPE_8192DU(rtlhal)) +#define IS_HARDWARE_TYPE_8723(rtlhal) \ +(IS_HARDWARE_TYPE_8723E(rtlhal) || IS_HARDWARE_TYPE_8723U(rtlhal)) +#define IS_HARDWARE_TYPE_8723U(rtlhal) \ + (rtlhal->hw_type == HARDWARE_TYPE_RTL8723U) + +#define RX_HAL_IS_CCK_RATE(_pdesc)\ + (_pdesc->rxmcs == DESC92_RATE1M || \ + _pdesc->rxmcs == DESC92_RATE2M || \ + _pdesc->rxmcs == DESC92_RATE5_5M || \ + _pdesc->rxmcs == DESC92_RATE11M) + +enum scan_operation_backup_opt { + SCAN_OPT_BACKUP = 0, + SCAN_OPT_RESTORE, + SCAN_OPT_MAX +}; + +/*RF state.*/ +enum rf_pwrstate { + ERFON, + ERFSLEEP, + ERFOFF +}; + +struct bb_reg_def { + u32 rfintfs; + u32 rfintfi; + u32 rfintfo; + u32 rfintfe; + u32 rf3wire_offset; + u32 rflssi_select; + u32 rftxgain_stage; + u32 rfhssi_para1; + u32 rfhssi_para2; + u32 rfsw_ctrl; + u32 rfagc_control1; + u32 rfagc_control2; + u32 rfrxiq_imbal; + u32 rfrx_afe; + u32 rftxiq_imbal; + u32 rftx_afe; + u32 rf_rb; /* rflssi_readback */ + u32 rf_rbpi; /* rflssi_readbackpi */ +}; + +enum io_type { + IO_CMD_PAUSE_DM_BY_SCAN = 0, + IO_CMD_RESUME_DM_BY_SCAN = 1, +}; + +enum hw_variables { + HW_VAR_ETHER_ADDR, + HW_VAR_MULTICAST_REG, + HW_VAR_BASIC_RATE, + HW_VAR_BSSID, + HW_VAR_MEDIA_STATUS, + HW_VAR_SECURITY_CONF, + HW_VAR_BEACON_INTERVAL, + HW_VAR_ATIM_WINDOW, + HW_VAR_LISTEN_INTERVAL, + HW_VAR_CS_COUNTER, + HW_VAR_DEFAULTKEY0, + HW_VAR_DEFAULTKEY1, + HW_VAR_DEFAULTKEY2, + HW_VAR_DEFAULTKEY3, + HW_VAR_SIFS, + HW_VAR_DIFS, + HW_VAR_EIFS, + HW_VAR_SLOT_TIME, + HW_VAR_ACK_PREAMBLE, + HW_VAR_CW_CONFIG, + HW_VAR_CW_VALUES, + HW_VAR_RATE_FALLBACK_CONTROL, + HW_VAR_CONTENTION_WINDOW, + HW_VAR_RETRY_COUNT, + HW_VAR_TR_SWITCH, + HW_VAR_COMMAND, + HW_VAR_WPA_CONFIG, + HW_VAR_AMPDU_MIN_SPACE, + HW_VAR_SHORTGI_DENSITY, + HW_VAR_AMPDU_FACTOR, + HW_VAR_MCS_RATE_AVAILABLE, + HW_VAR_AC_PARAM, + HW_VAR_ACM_CTRL, + HW_VAR_DIS_Req_Qsize, + HW_VAR_CCX_CHNL_LOAD, + HW_VAR_CCX_NOISE_HISTOGRAM, + HW_VAR_CCX_CLM_NHM, + HW_VAR_TxOPLimit, + HW_VAR_TURBO_MODE, + HW_VAR_RF_STATE, + HW_VAR_RF_OFF_BY_HW, + HW_VAR_BUS_SPEED, + HW_VAR_SET_DEV_POWER, + + HW_VAR_RCR, + HW_VAR_RATR_0, + HW_VAR_RRSR, + HW_VAR_CPU_RST, + HW_VAR_CHECK_BSSID, + HW_VAR_LBK_MODE, + HW_VAR_AES_11N_FIX, + HW_VAR_USB_RX_AGGR, + HW_VAR_USER_CONTROL_TURBO_MODE, + HW_VAR_RETRY_LIMIT, + HW_VAR_INIT_TX_RATE, + HW_VAR_TX_RATE_REG, + HW_VAR_EFUSE_USAGE, + HW_VAR_EFUSE_BYTES, + HW_VAR_AUTOLOAD_STATUS, + HW_VAR_RF_2R_DISABLE, + HW_VAR_SET_RPWM, + HW_VAR_H2C_FW_PWRMODE, + HW_VAR_H2C_FW_JOINBSSRPT, + HW_VAR_H2C_FW_P2P_PS_OFFLOAD, + HW_VAR_FW_PSMODE_STATUS, + HW_VAR_RESUME_CLK_ON, + HW_VAR_FW_LPS_ACTION, + HW_VAR_1X1_RECV_COMBINE, + HW_VAR_STOP_SEND_BEACON, + HW_VAR_TSF_TIMER, + HW_VAR_IO_CMD, + + HW_VAR_RF_RECOVERY, + HW_VAR_H2C_FW_UPDATE_GTK, + HW_VAR_WF_MASK, + HW_VAR_WF_CRC, + HW_VAR_WF_IS_MAC_ADDR, + HW_VAR_H2C_FW_OFFLOAD, + HW_VAR_RESET_WFCRC, + + HW_VAR_HANDLE_FW_C2H, + HW_VAR_DL_FW_RSVD_PAGE, + HW_VAR_AID, + HW_VAR_HW_SEQ_ENABLE, + HW_VAR_CORRECT_TSF, + HW_VAR_BCN_VALID, + HW_VAR_FWLPS_RF_ON, + HW_VAR_DUAL_TSF_RST, + HW_VAR_SWITCH_EPHY_WoWLAN, + HW_VAR_INT_MIGRATION, + HW_VAR_INT_AC, + HW_VAR_RF_TIMING, + + HAL_DEF_WOWLAN, + HW_VAR_MRC, + + HW_VAR_MGT_FILTER, + HW_VAR_CTRL_FILTER, + HW_VAR_DATA_FILTER, +}; + +enum _RT_MEDIA_STATUS { + RT_MEDIA_DISCONNECT = 0, + RT_MEDIA_CONNECT = 1 +}; + +enum rt_oem_id { + RT_CID_DEFAULT = 0, + RT_CID_8187_ALPHA0 = 1, + RT_CID_8187_SERCOMM_PS = 2, + RT_CID_8187_HW_LED = 3, + RT_CID_8187_NETGEAR = 4, + RT_CID_WHQL = 5, + RT_CID_819x_CAMEO = 6, + RT_CID_819x_RUNTOP = 7, + RT_CID_819x_Senao = 8, + RT_CID_TOSHIBA = 9, + RT_CID_819x_Netcore = 10, + RT_CID_Nettronix = 11, + RT_CID_DLINK = 12, + RT_CID_PRONET = 13, + RT_CID_COREGA = 14, + RT_CID_819x_ALPHA = 15, + RT_CID_819x_Sitecom = 16, + RT_CID_CCX = 17, + RT_CID_819x_Lenovo = 18, + RT_CID_819x_QMI = 19, + RT_CID_819x_Edimax_Belkin = 20, + RT_CID_819x_Sercomm_Belkin = 21, + RT_CID_819x_CAMEO1 = 22, + RT_CID_819x_MSI = 23, + RT_CID_819x_Acer = 24, + RT_CID_819x_HP = 27, + RT_CID_819x_CLEVO = 28, + RT_CID_819x_Arcadyan_Belkin = 29, + RT_CID_819x_SAMSUNG = 30, + RT_CID_819x_WNC_COREGA = 31, + RT_CID_819x_Foxcoon = 32, + RT_CID_819x_DELL = 33, + RT_CID_819x_PRONETS = 34, + RT_CID_819x_Edimax_ASUS = 35, + RT_CID_NETGEAR = 36, + RT_CID_PLANEX = 37, + RT_CID_CC_C = 38, +}; + +enum hw_descs { + HW_DESC_OWN, + HW_DESC_RXOWN, + HW_DESC_TX_NEXTDESC_ADDR, + HW_DESC_TXBUFF_ADDR, + HW_DESC_RXBUFF_ADDR, + HW_DESC_RXPKT_LEN, + HW_DESC_RXERO, +}; + +enum prime_sc { + PRIME_CHNL_OFFSET_DONT_CARE = 0, + PRIME_CHNL_OFFSET_LOWER = 1, + PRIME_CHNL_OFFSET_UPPER = 2, +}; + +enum rf_type { + RF_1T1R = 0, + RF_1T2R = 1, + RF_2T2R = 2, + RF_2T2R_GREEN = 3, +}; + +enum ht_channel_width { + HT_CHANNEL_WIDTH_20 = 0, + HT_CHANNEL_WIDTH_20_40 = 1, +}; + +/* Ref: 802.11i sepc D10.0 7.3.2.25.1 +Cipher Suites Encryption Algorithms */ +enum rt_enc_alg { + NO_ENCRYPTION = 0, + WEP40_ENCRYPTION = 1, + TKIP_ENCRYPTION = 2, + RSERVED_ENCRYPTION = 3, + AESCCMP_ENCRYPTION = 4, + WEP104_ENCRYPTION = 5, + AESCMAC_ENCRYPTION = 6, /*IEEE802.11w */ +}; + +enum rtl_hal_state { + _HAL_STATE_STOP = 0, + _HAL_STATE_START = 1, +}; + +enum rtl_desc92_rate { + DESC92_RATE1M = 0x00, + DESC92_RATE2M = 0x01, + DESC92_RATE5_5M = 0x02, + DESC92_RATE11M = 0x03, + + DESC92_RATE6M = 0x04, + DESC92_RATE9M = 0x05, + DESC92_RATE12M = 0x06, + DESC92_RATE18M = 0x07, + DESC92_RATE24M = 0x08, + DESC92_RATE36M = 0x09, + DESC92_RATE48M = 0x0a, + DESC92_RATE54M = 0x0b, + + DESC92_RATEMCS0 = 0x0c, + DESC92_RATEMCS1 = 0x0d, + DESC92_RATEMCS2 = 0x0e, + DESC92_RATEMCS3 = 0x0f, + DESC92_RATEMCS4 = 0x10, + DESC92_RATEMCS5 = 0x11, + DESC92_RATEMCS6 = 0x12, + DESC92_RATEMCS7 = 0x13, + DESC92_RATEMCS8 = 0x14, + DESC92_RATEMCS9 = 0x15, + DESC92_RATEMCS10 = 0x16, + DESC92_RATEMCS11 = 0x17, + DESC92_RATEMCS12 = 0x18, + DESC92_RATEMCS13 = 0x19, + DESC92_RATEMCS14 = 0x1a, + DESC92_RATEMCS15 = 0x1b, + DESC92_RATEMCS15_SG = 0x1c, + DESC92_RATEMCS32 = 0x20, +}; + +enum rtl_var_map { + /*reg map */ + SYS_ISO_CTRL = 0, + SYS_FUNC_EN, + SYS_CLK, + MAC_RCR_AM, + MAC_RCR_AB, + MAC_RCR_ACRC32, + MAC_RCR_ACF, + MAC_RCR_AAP, + + /*efuse map */ + EFUSE_TEST, + EFUSE_CTRL, + EFUSE_CLK, + EFUSE_CLK_CTRL, + EFUSE_PWC_EV12V, + EFUSE_FEN_ELDR, + EFUSE_LOADER_CLK_EN, + EFUSE_ANA8M, + EFUSE_HWSET_MAX_SIZE, + EFUSE_MAX_SECTION_MAP, + EFUSE_REAL_CONTENT_SIZE, + EFUSE_OOB_PROTECT_BYTES_LEN, + EFUSE_ACCESS, + + /*CAM map */ + RWCAM, + WCAMI, + RCAMO, + CAMDBG, + SECR, + SEC_CAM_NONE, + SEC_CAM_WEP40, + SEC_CAM_TKIP, + SEC_CAM_AES, + SEC_CAM_WEP104, + + /*IMR map */ + RTL_IMR_BCNDMAINT6, /*Beacon DMA Interrupt 6 */ + RTL_IMR_BCNDMAINT5, /*Beacon DMA Interrupt 5 */ + RTL_IMR_BCNDMAINT4, /*Beacon DMA Interrupt 4 */ + RTL_IMR_BCNDMAINT3, /*Beacon DMA Interrupt 3 */ + RTL_IMR_BCNDMAINT2, /*Beacon DMA Interrupt 2 */ + RTL_IMR_BCNDMAINT1, /*Beacon DMA Interrupt 1 */ + RTL_IMR_BCNDOK8, /*Beacon Queue DMA OK Interrup 8 */ + RTL_IMR_BCNDOK7, /*Beacon Queue DMA OK Interrup 7 */ + RTL_IMR_BCNDOK6, /*Beacon Queue DMA OK Interrup 6 */ + RTL_IMR_BCNDOK5, /*Beacon Queue DMA OK Interrup 5 */ + RTL_IMR_BCNDOK4, /*Beacon Queue DMA OK Interrup 4 */ + RTL_IMR_BCNDOK3, /*Beacon Queue DMA OK Interrup 3 */ + RTL_IMR_BCNDOK2, /*Beacon Queue DMA OK Interrup 2 */ + RTL_IMR_BCNDOK1, /*Beacon Queue DMA OK Interrup 1 */ + RTL_IMR_TIMEOUT2, /*Timeout interrupt 2 */ + RTL_IMR_TIMEOUT1, /*Timeout interrupt 1 */ + RTL_IMR_TXFOVW, /*Transmit FIFO Overflow */ + RTL_IMR_PSTIMEOUT, /*Power save time out interrupt */ + RTL_IMR_BCNINT, /*Beacon DMA Interrupt 0 */ + RTL_IMR_RXFOVW, /*Receive FIFO Overflow */ + RTL_IMR_RDU, /*Receive Descriptor Unavailable */ + RTL_IMR_ATIMEND, /*For 92C,ATIM Window End Interrupt */ + RTL_IMR_BDOK, /*Beacon Queue DMA OK Interrup */ + RTL_IMR_HIGHDOK, /*High Queue DMA OK Interrupt */ + RTL_IMR_COMDOK, /*Command Queue DMA OK Interrupt*/ + RTL_IMR_TBDOK, /*Transmit Beacon OK interrup */ + RTL_IMR_MGNTDOK, /*Management Queue DMA OK Interrupt */ + RTL_IMR_TBDER, /*For 92C,Transmit Beacon Error Interrupt */ + RTL_IMR_BKDOK, /*AC_BK DMA OK Interrupt */ + RTL_IMR_BEDOK, /*AC_BE DMA OK Interrupt */ + RTL_IMR_VIDOK, /*AC_VI DMA OK Interrupt */ + RTL_IMR_VODOK, /*AC_VO DMA Interrupt */ + RTL_IMR_ROK, /*Receive DMA OK Interrupt */ + RTL_IBSS_INT_MASKS, /*(RTL_IMR_BCNINT | RTL_IMR_TBDOK | + * RTL_IMR_TBDER) */ + RTL_IMR_C2HCMD, /*fw interrupt*/ + + /*CCK Rates, TxHT = 0 */ + RTL_RC_CCK_RATE1M, + RTL_RC_CCK_RATE2M, + RTL_RC_CCK_RATE5_5M, + RTL_RC_CCK_RATE11M, + + /*OFDM Rates, TxHT = 0 */ + RTL_RC_OFDM_RATE6M, + RTL_RC_OFDM_RATE9M, + RTL_RC_OFDM_RATE12M, + RTL_RC_OFDM_RATE18M, + RTL_RC_OFDM_RATE24M, + RTL_RC_OFDM_RATE36M, + RTL_RC_OFDM_RATE48M, + RTL_RC_OFDM_RATE54M, + + RTL_RC_HT_RATEMCS7, + RTL_RC_HT_RATEMCS15, + + /*keep it last */ + RTL_VAR_MAP_MAX, +}; + +/*Firmware PS mode for control LPS.*/ +enum _fw_ps_mode { + FW_PS_ACTIVE_MODE = 0, + FW_PS_MIN_MODE = 1, + FW_PS_MAX_MODE = 2, + FW_PS_DTIM_MODE = 3, + FW_PS_VOIP_MODE = 4, + FW_PS_UAPSD_WMM_MODE = 5, + FW_PS_UAPSD_MODE = 6, + FW_PS_IBSS_MODE = 7, + FW_PS_WWLAN_MODE = 8, + FW_PS_PM_Radio_Off = 9, + FW_PS_PM_Card_Disable = 10, +}; + +enum rt_psmode { + EACTIVE, /*Active/Continuous access. */ + EMAXPS, /*Max power save mode. */ + EFASTPS, /*Fast power save mode. */ + EAUTOPS, /*Auto power save mode. */ +}; + +/*LED related.*/ +enum led_ctl_mode { + LED_CTL_POWER_ON = 1, + LED_CTL_LINK = 2, + LED_CTL_NO_LINK = 3, + LED_CTL_TX = 4, + LED_CTL_RX = 5, + LED_CTL_SITE_SURVEY = 6, + LED_CTL_POWER_OFF = 7, + LED_CTL_START_TO_LINK = 8, + LED_CTL_START_WPS = 9, + LED_CTL_STOP_WPS = 10, +}; + +enum rtl_led_pin { + LED_PIN_GPIO0, + LED_PIN_LED0, + LED_PIN_LED1, + LED_PIN_LED2 +}; + +/*QoS related.*/ +/*acm implementation method.*/ +enum acm_method { + eAcmWay0_SwAndHw = 0, + eAcmWay1_HW = 1, + eAcmWay2_SW = 2, +}; + +enum macphy_mode { + SINGLEMAC_SINGLEPHY = 0, + DUALMAC_DUALPHY, + DUALMAC_SINGLEPHY, +}; + +enum band_type { + BAND_ON_2_4G = 0, + BAND_ON_5G, + BAND_ON_BOTH, + BANDMAX +}; + +/*aci/aifsn Field. +Ref: WMM spec 2.2.2: WME Parameter Element, p.12.*/ +union aci_aifsn { + u8 char_data; + + struct { + u8 aifsn:4; + u8 acm:1; + u8 aci:2; + u8 reserved:1; + } f; /* Field */ +}; + +/*mlme related.*/ +enum wireless_mode { + WIRELESS_MODE_UNKNOWN = 0x00, + WIRELESS_MODE_A = 0x01, + WIRELESS_MODE_B = 0x02, + WIRELESS_MODE_G = 0x04, + WIRELESS_MODE_AUTO = 0x08, + WIRELESS_MODE_N_24G = 0x10, + WIRELESS_MODE_N_5G = 0x20 +}; + +#define IS_WIRELESS_MODE_A(wirelessmode) \ + (wirelessmode == WIRELESS_MODE_A) +#define IS_WIRELESS_MODE_B(wirelessmode) \ + (wirelessmode == WIRELESS_MODE_B) +#define IS_WIRELESS_MODE_G(wirelessmode) \ + (wirelessmode == WIRELESS_MODE_G) +#define IS_WIRELESS_MODE_N_24G(wirelessmode) \ + (wirelessmode == WIRELESS_MODE_N_24G) +#define IS_WIRELESS_MODE_N_5G(wirelessmode) \ + (wirelessmode == WIRELESS_MODE_N_5G) + +enum ratr_table_mode { + RATR_INX_WIRELESS_NGB = 0, + RATR_INX_WIRELESS_NG = 1, + RATR_INX_WIRELESS_NB = 2, + RATR_INX_WIRELESS_N = 3, + RATR_INX_WIRELESS_GB = 4, + RATR_INX_WIRELESS_G = 5, + RATR_INX_WIRELESS_B = 6, + RATR_INX_WIRELESS_MC = 7, + RATR_INX_WIRELESS_A = 8, +}; + +enum rtl_link_state { + MAC80211_NOLINK = 0, + MAC80211_LINKING = 1, + MAC80211_LINKED = 2, + MAC80211_LINKED_SCANNING = 3, +}; + +enum act_category { + ACT_CAT_QOS = 1, + ACT_CAT_DLS = 2, + ACT_CAT_BA = 3, + ACT_CAT_HT = 7, + ACT_CAT_WMM = 17, +}; + +enum ba_action { + ACT_ADDBAREQ = 0, + ACT_ADDBARSP = 1, + ACT_DELBA = 2, +}; + +enum rt_polarity_ctl { + RT_POLARITY_LOW_ACT = 0, + RT_POLARITY_HIGH_ACT = 1, +}; + +struct octet_string { + u8 *octet; + u16 length; +}; + +struct rtl_hdr_3addr { + __le16 frame_ctl; + __le16 duration_id; + u8 addr1[ETH_ALEN]; + u8 addr2[ETH_ALEN]; + u8 addr3[ETH_ALEN]; + __le16 seq_ctl; + u8 payload[0]; +} __packed; + +struct rtl_info_element { + u8 id; + u8 len; + u8 data[0]; +} __packed; + +struct rtl_probe_rsp { + struct rtl_hdr_3addr header; + u32 time_stamp[2]; + __le16 beacon_interval; + __le16 capability; + /*SSID, supported rates, FH params, DS params, + CF params, IBSS params, TIM (if beacon), RSN */ + struct rtl_info_element info_element[0]; +} __packed; + +/*LED related.*/ +/*ledpin Identify how to implement this SW led.*/ +struct rtl_led { + void *hw; + enum rtl_led_pin ledpin; + bool ledon; +}; + +struct rtl_led_ctl { + bool led_opendrain; + struct rtl_led sw_led0; + struct rtl_led sw_led1; +}; + +struct rtl_qos_parameters { + __le16 cw_min; + __le16 cw_max; + u8 aifs; + u8 flag; + __le16 tx_op; +} __packed; + +struct rt_smooth_data { + u32 elements[100]; /*array to store values */ + u32 index; /*index to current array to store */ + u32 total_num; /*num of valid elements */ + u32 total_val; /*sum of valid elements */ +}; + +struct false_alarm_statistics { + u32 cnt_parity_fail; + u32 cnt_rate_illegal; + u32 cnt_crc8_fail; + u32 cnt_mcs_fail; + u32 cnt_fast_fsync_fail; + u32 cnt_sb_search_fail; + u32 cnt_ofdm_fail; + u32 cnt_cck_fail; + u32 cnt_all; + u32 cnt_ofdm_cca; + u32 cnt_cck_cca; + u32 cnt_cca_all; + u32 cnt_bw_usc; + u32 cnt_bw_lsc; +}; + +struct init_gain { + u8 xaagccore1; + u8 xbagccore1; + u8 xcagccore1; + u8 xdagccore1; + u8 cca; + +}; + +struct wireless_stats { + unsigned long txbytesunicast; + unsigned long txbytesmulticast; + unsigned long txbytesbroadcast; + unsigned long rxbytesunicast; + + long rx_snr_db[4]; + /*Correct smoothed ss in Dbm, only used + in driver to report real power now. */ + long recv_signal_power; + long signal_quality; + long last_sigstrength_inpercent; + + u32 rssi_calculate_cnt; + + /*Transformed, in dbm. Beautified signal + strength for UI, not correct. */ + long signal_strength; + + u8 rx_rssi_percentage[4]; + u8 rx_evm_percentage[2]; + + struct rt_smooth_data ui_rssi; + struct rt_smooth_data ui_link_quality; +}; + +struct rate_adaptive { + u8 rate_adaptive_disabled; + u8 ratr_state; + u16 reserve; + + u32 high_rssi_thresh_for_ra; + u32 high2low_rssi_thresh_for_ra; + u8 low2high_rssi_thresh_for_ra40m; + u32 low_rssi_thresh_for_ra40M; + u8 low2high_rssi_thresh_for_ra20m; + u32 low_rssi_thresh_for_ra20M; + u32 upper_rssi_threshold_ratr; + u32 middleupper_rssi_threshold_ratr; + u32 middle_rssi_threshold_ratr; + u32 middlelow_rssi_threshold_ratr; + u32 low_rssi_threshold_ratr; + u32 ultralow_rssi_threshold_ratr; + u32 low_rssi_threshold_ratr_40m; + u32 low_rssi_threshold_ratr_20m; + u8 ping_rssi_enable; + u32 ping_rssi_ratr; + u32 ping_rssi_thresh_for_ra; + u32 last_ratr; + u8 pre_ratr_state; +}; + +struct regd_pair_mapping { + u16 reg_dmnenum; + u16 reg_5ghz_ctl; + u16 reg_2ghz_ctl; +}; + +struct rtl_regulatory { + char alpha2[2]; + u16 country_code; + u16 max_power_level; + u32 tp_scale; + u16 current_rd; + u16 current_rd_ext; + int16_t power_limit; + struct regd_pair_mapping *regpair; +}; + +struct rtl_rfkill { + bool rfkill_state; /*0 is off, 1 is on */ +}; + +/*for P2P PS**/ +#define P2P_MAX_NOA_NUM 2 + +enum p2p_role { + P2P_ROLE_DISABLE = 0, + P2P_ROLE_DEVICE = 1, + P2P_ROLE_CLIENT = 2, + P2P_ROLE_GO = 3 +}; + +enum p2p_ps_state { + P2P_PS_DISABLE = 0, + P2P_PS_ENABLE = 1, + P2P_PS_SCAN = 2, + P2P_PS_SCAN_DONE = 3, + P2P_PS_ALLSTASLEEP = 4, /* for P2P GO */ +}; + +enum p2p_ps_mode { + P2P_PS_NONE = 0, + P2P_PS_CTWINDOW = 1, + P2P_PS_NOA = 2, + P2P_PS_MIX = 3, /* CTWindow and NoA */ +}; + +struct rtl_p2p_ps_info { + enum p2p_ps_mode p2p_ps_mode; /* indicate p2p ps mode */ + enum p2p_ps_state p2p_ps_state; /* indicate p2p ps state */ + u8 noa_index; /* Identifies instance of Notice of Absence timing. */ + /* Client traffic window. A period of time in TU after TBTT. */ + u8 ctwindow; + u8 opp_ps; /* opportunistic power save. */ + u8 noa_num; /* number of NoA descriptor in P2P IE. */ + /* Count for owner, Type of client. */ + u8 noa_count_type[P2P_MAX_NOA_NUM]; + /* Max duration for owner, preferred or min acceptable duration + * for client. + */ + u32 noa_duration[P2P_MAX_NOA_NUM]; + /* Length of interval for owner, preferred or max acceptable intervali + * of client. + */ + u32 noa_interval[P2P_MAX_NOA_NUM]; + /* schedule in terms of the lower 4 bytes of the TSF timer. */ + u32 noa_start_time[P2P_MAX_NOA_NUM]; +}; + +struct p2p_ps_offload_t { + u8 offload_en:1; + u8 role:1; /* 1: Owner, 0: Client */ + u8 ctwindow_en:1; + u8 noa0_en:1; + u8 noa1_en:1; + u8 allstasleep:1; + u8 discovery:1; + u8 reserved:1; +}; + +#define IQK_MATRIX_REG_NUM 8 +#define IQK_MATRIX_SETTINGS_NUM (1 + 24 + 21) + +struct iqk_matrix_regs { + bool iqk_done; + long value[1][IQK_MATRIX_REG_NUM]; +}; + +struct phy_parameters { + u16 length; + u32 *pdata; +}; + +enum hw_param_tab_index { + PHY_REG_2T, + PHY_REG_1T, + PHY_REG_PG, + RADIOA_2T, + RADIOB_2T, + RADIOA_1T, + RADIOB_1T, + MAC_REG, + AGCTAB_2T, + AGCTAB_1T, + MAX_TAB +}; + +struct rtl_phy { + struct bb_reg_def phyreg_def[4]; /*Radio A/B/C/D */ + struct init_gain initgain_backup; + enum io_type current_io_type; + + u8 rf_mode; + u8 rf_type; + u8 current_chan_bw; + u8 set_bwmode_inprogress; + u8 sw_chnl_inprogress; + u8 sw_chnl_stage; + u8 sw_chnl_step; + u8 current_channel; + u8 h2c_box_num; + u8 set_io_inprogress; + u8 lck_inprogress; + + /* record for power tracking */ + s32 reg_e94; + s32 reg_e9c; + s32 reg_ea4; + s32 reg_eac; + s32 reg_eb4; + s32 reg_ebc; + s32 reg_ec4; + s32 reg_ecc; + u8 rfpienable; + u8 reserve_0; + u16 reserve_1; + u32 reg_c04, reg_c08, reg_874; + u32 adda_backup[16]; + u32 iqk_mac_backup[IQK_MAC_REG_NUM]; + u32 iqk_bb_backup[10]; + bool iqk_initialized; + + /* Dual mac */ + bool need_iqk; + struct iqk_matrix_regs iqk_matrix[IQK_MATRIX_SETTINGS_NUM]; + + bool rfpi_enable; + + u8 pwrgroup_cnt; + u8 cck_high_power; + /* MAX_PG_GROUP groups of pwr diff by rates */ + u32 mcs_offset[MAX_PG_GROUP][16]; + u8 default_initialgain[4]; + + /* the current Tx power level */ + u8 cur_cck_txpwridx; + u8 cur_ofdm24g_txpwridx; + u8 cur_bw20_txpwridx; + u8 cur_bw40_txpwridx; + + u32 rfreg_chnlval[2]; + bool apk_done; + u32 reg_rf3c[2]; /* pathA / pathB */ + + /* bfsync */ + u8 framesync; + u32 framesync_c34; + + u8 num_total_rfpath; + struct phy_parameters hwparam_tables[MAX_TAB]; + u16 rf_pathmap; + + enum rt_polarity_ctl polarity_ctl; +}; + +#define MAX_TID_COUNT 9 +#define RTL_AGG_STOP 0 +#define RTL_AGG_PROGRESS 1 +#define RTL_AGG_START 2 +#define RTL_AGG_OPERATIONAL 3 +#define RTL_AGG_OFF 0 +#define RTL_AGG_ON 1 +#define RTL_RX_AGG_START 1 +#define RTL_RX_AGG_STOP 0 +#define RTL_AGG_EMPTYING_HW_QUEUE_ADDBA 2 +#define RTL_AGG_EMPTYING_HW_QUEUE_DELBA 3 + +struct rtl_ht_agg { + u16 txq_id; + u16 wait_for_ba; + u16 start_idx; + u64 bitmap; + u32 rate_n_flags; + u8 agg_state; + u8 rx_agg_state; +}; + +struct rssi_sta { + long undec_sm_pwdb; +}; + +struct rtl_tid_data { + u16 seq_number; + struct rtl_ht_agg agg; +}; + +struct rtl_sta_info { + struct list_head list; + u8 ratr_index; + u8 wireless_mode; + u8 mimo_ps; + u8 mac_addr[ETH_ALEN]; + struct rtl_tid_data tids[MAX_TID_COUNT]; + + /* just used for ap adhoc or mesh*/ + struct rssi_sta rssi_stat; +} __packed; + +struct rtl_priv; +struct rtl_io { + struct device *dev; + struct mutex bb_mutex; + + /*PCI MEM map */ + unsigned long pci_mem_end; /*shared mem end */ + unsigned long pci_mem_start; /*shared mem start */ + + /*PCI IO map */ + unsigned long pci_base_addr; /*device I/O address */ + + void (*write8_async) (struct rtl_priv *rtlpriv, u32 addr, u8 val); + void (*write16_async) (struct rtl_priv *rtlpriv, u32 addr, u16 val); + void (*write32_async) (struct rtl_priv *rtlpriv, u32 addr, u32 val); + void (*writeN_sync) (struct rtl_priv *rtlpriv, u32 addr, void *buf, + u16 len); + + u8(*read8_sync) (struct rtl_priv *rtlpriv, u32 addr); + u16(*read16_sync) (struct rtl_priv *rtlpriv, u32 addr); + u32(*read32_sync) (struct rtl_priv *rtlpriv, u32 addr); + +}; + +struct rtl_mac { + u8 mac_addr[ETH_ALEN]; + u8 mac80211_registered; + u8 beacon_enabled; + + u32 tx_ss_num; + u32 rx_ss_num; + + struct ieee80211_supported_band bands[IEEE80211_NUM_BANDS]; + struct ieee80211_hw *hw; + struct ieee80211_vif *vif; + enum nl80211_iftype opmode; + + /*Probe Beacon management */ + struct rtl_tid_data tids[MAX_TID_COUNT]; + enum rtl_link_state link_state; + + int n_channels; + int n_bitrates; + + bool offchan_delay; + u8 p2p; /*using p2p role*/ + bool p2p_in_use; + + /*filters */ + u32 rx_conf; + u16 rx_mgt_filter; + u16 rx_ctrl_filter; + u16 rx_data_filter; + + bool act_scanning; + u8 cnt_after_linked; + bool skip_scan; + + /* early mode */ + /* skb wait queue */ + struct sk_buff_head skb_waitq[MAX_TID_COUNT]; + + /*RDG*/ + bool rdg_en; + + /*AP*/ + u8 bssid[6]; + u32 vendor; + u8 mcs[16]; /* 16 bytes mcs for HT rates. */ + u32 basic_rates; /* b/g rates */ + u8 ht_enable; + u8 sgi_40; + u8 sgi_20; + u8 bw_40; + u8 mode; /* wireless mode */ + u8 slot_time; + u8 short_preamble; + u8 use_cts_protect; + u8 cur_40_prime_sc; + u8 cur_40_prime_sc_bk; + u64 tsf; + u8 retry_short; + u8 retry_long; + u16 assoc_id; + bool hiddenssid; + + /*IBSS*/ + int beacon_interval; + + /*AMPDU*/ + u8 min_space_cfg; /*For Min spacing configurations */ + u8 max_mss_density; + u8 current_ampdu_factor; + u8 current_ampdu_density; + + /*QOS & EDCA */ + struct ieee80211_tx_queue_params edca_param[RTL_MAC80211_NUM_QUEUE]; + struct rtl_qos_parameters ac[AC_MAX]; + + /* counters */ + u64 last_txok_cnt; + u64 last_rxok_cnt; + u32 last_bt_edca_ul; + u32 last_bt_edca_dl; +}; + +struct btdm_8723 { + bool all_off; + bool agc_table_en; + bool adc_back_off_on; + bool b2_ant_hid_en; + bool low_penalty_rate_adaptive; + bool rf_rx_lpf_shrink; + bool reject_aggre_pkt; + bool tra_tdma_on; + u8 tra_tdma_nav; + u8 tra_tdma_ant; + bool tdma_on; + u8 tdma_ant; + u8 tdma_nav; + u8 tdma_dac_swing; + u8 fw_dac_swing_lvl; + bool ps_tdma_on; + u8 ps_tdma_byte[5]; + bool pta_on; + u32 val_0x6c0; + u32 val_0x6c8; + u32 val_0x6cc; + bool sw_dac_swing_on; + u32 sw_dac_swing_lvl; + u32 wlan_act_hi; + u32 wlan_act_lo; + u32 bt_retry_index; + bool dec_bt_pwr; + bool ignore_wlan_act; +}; + +struct bt_coexist_8723 { + u32 high_priority_tx; + u32 high_priority_rx; + u32 low_priority_tx; + u32 low_priority_rx; + u8 c2h_bt_info; + bool c2h_bt_info_req_sent; + bool c2h_bt_inquiry_page; + u32 bt_inq_page_start_time; + u8 bt_retry_cnt; + u8 c2h_bt_info_original; + u8 bt_inquiry_page_cnt; + struct btdm_8723 btdm; +}; + +struct rtl_hal { + struct ieee80211_hw *hw; + bool driver_is_goingto_unload; + bool up_first_time; + bool first_init; + bool being_init_adapter; + bool bbrf_ready; + bool mac_func_enable; + struct bt_coexist_8723 hal_coex_8723; + + enum intf_type interface; + u16 hw_type; /*92c or 92d or 92s and so on */ + u8 ic_class; + u8 oem_id; + u32 version; /*version of chip */ + u8 state; /*stop 0, start 1 */ + u8 board_type; + + /*firmware */ + u32 fwsize; + u8 *pfirmware; + u16 fw_version; + u16 fw_subversion; + bool h2c_setinprogress; + u8 last_hmeboxnum; + bool fw_ready; + /*Reserve page start offset except beacon in TxQ. */ + u8 fw_rsvdpage_startoffset; + u8 h2c_txcmd_seq; + + /* FW Cmd IO related */ + u16 fwcmd_iomap; + u32 fwcmd_ioparam; + bool set_fwcmd_inprogress; + u8 current_fwcmd_io; + + struct p2p_ps_offload_t p2p_ps_offload; + bool fw_clk_change_in_progress; + bool allow_sw_to_change_hwclc; + u8 fw_ps_state; + /**/ + bool driver_going2unload; + + /*AMPDU init min space*/ + u8 minspace_cfg; /*For Min spacing configurations */ + + /* Dual mac */ + enum macphy_mode macphymode; + enum band_type current_bandtype; /* 0:2.4G, 1:5G */ + enum band_type current_bandtypebackup; + enum band_type bandset; + /* dual MAC 0--Mac0 1--Mac1 */ + u32 interfaceindex; + /* just for DualMac S3S4 */ + u8 macphyctl_reg; + bool earlymode_enable; + u8 max_earlymode_num; + /* Dual mac*/ + bool during_mac0init_radiob; + bool during_mac1init_radioa; + bool reloadtxpowerindex; + /* True if IMR or IQK have done + for 2.4G in scan progress */ + bool load_imrandiqk_setting_for2g; + + bool disable_amsdu_8k; + bool master_of_dmsp; + bool slave_of_dmsp; +}; + +struct rtl_security { + /*default 0 */ + bool use_sw_sec; + + bool being_setkey; + bool use_defaultkey; + /*Encryption Algorithm for Unicast Packet */ + enum rt_enc_alg pairwise_enc_algorithm; + /*Encryption Algorithm for Brocast/Multicast */ + enum rt_enc_alg group_enc_algorithm; + /*Cam Entry Bitmap */ + u32 hwsec_cam_bitmap; + u8 hwsec_cam_sta_addr[TOTAL_CAM_ENTRY][ETH_ALEN]; + /*local Key buffer, indx 0 is for + pairwise key 1-4 is for agoup key. */ + u8 key_buf[KEY_BUF_SIZE][MAX_KEY_LEN]; + u8 key_len[KEY_BUF_SIZE]; + + /*The pointer of Pairwise Key, + it always points to KeyBuf[4] */ + u8 *pairwise_key; +}; + +#define ASSOCIATE_ENTRY_NUM 33 + +struct fast_ant_training { + u8 bssid[6]; + u8 antsel_rx_keep_0; + u8 antsel_rx_keep_1; + u8 antsel_rx_keep_2; + u32 ant_sum[7]; + u32 ant_cnt[7]; + u32 ant_ave[7]; + u8 fat_state; + u32 train_idx; + u8 antsel_a[ASSOCIATE_ENTRY_NUM]; + u8 antsel_b[ASSOCIATE_ENTRY_NUM]; + u8 antsel_c[ASSOCIATE_ENTRY_NUM]; + u32 main_ant_sum[ASSOCIATE_ENTRY_NUM]; + u32 aux_ant_sum[ASSOCIATE_ENTRY_NUM]; + u32 main_ant_cnt[ASSOCIATE_ENTRY_NUM]; + u32 aux_ant_cnt[ASSOCIATE_ENTRY_NUM]; + u8 rx_idle_ant; + bool becomelinked; +}; + +struct rtl_dm { + /*PHY status for Dynamic Management */ + long entry_min_undec_sm_pwdb; + long undec_sm_pwdb; /*out dm */ + long entry_max_undec_sm_pwdb; + bool dm_initialgain_enable; + bool dynamic_txpower_enable; + bool current_turbo_edca; + bool is_any_nonbepkts; /*out dm */ + bool is_cur_rdlstate; + bool txpower_trackinginit; + bool disable_framebursting; + bool cck_inch14; + bool txpower_tracking; + bool useramask; + bool rfpath_rxenable[4]; + bool inform_fw_driverctrldm; + bool current_mrc_switch; + u8 txpowercount; + + u8 thermalvalue_rxgain; + u8 thermalvalue_iqk; + u8 thermalvalue_lck; + u8 thermalvalue; + u8 last_dtp_lvl; + u8 thermalvalue_avg[AVG_THERMAL_NUM]; + u8 thermalvalue_avg_index; + bool done_txpower; + u8 dynamic_txhighpower_lvl; /*Tx high power level */ + u8 dm_flag; /*Indicate each dynamic mechanism's status. */ + u8 dm_type; + u8 txpower_track_control; + bool interrupt_migration; + bool disable_tx_int; + char ofdm_index[2]; + char cck_index; + char delta_power_index; + char delta_power_index_last; + char power_index_offset; + + /*88e tx power tracking*/ + u8 swing_idx_ofdm[2]; + u8 swing_idx_ofdm_cur; + u8 swing_idx_ofdm_base; + bool swing_flag_ofdm; + u8 swing_idx_cck; + u8 swing_idx_cck_cur; + u8 swing_idx_cck_base; + bool swing_flag_cck; + + /* DMSP */ + bool supp_phymode_switch; + + struct fast_ant_training fat_table; +}; + +#define EFUSE_MAX_LOGICAL_SIZE 256 + +struct rtl_efuse { + bool autoLoad_ok; + bool bootfromefuse; + u16 max_physical_size; + + u8 efuse_map[2][EFUSE_MAX_LOGICAL_SIZE]; + u16 efuse_usedbytes; + u8 efuse_usedpercentage; +#ifdef EFUSE_REPG_WORKAROUND + bool efuse_re_pg_sec1flag; + u8 efuse_re_pg_data[8]; +#endif + + u8 autoload_failflag; + u8 autoload_status; + + short epromtype; + u16 eeprom_vid; + u16 eeprom_did; + u16 eeprom_svid; + u16 eeprom_smid; + u8 eeprom_oemid; + u16 eeprom_channelplan; + u8 eeprom_version; + u8 board_type; + u8 external_pa; + + u8 dev_addr[6]; + u8 wowlan_enable; + u8 antenna_div_cfg; + u8 antenna_div_type; + + bool txpwr_fromeprom; + u8 eeprom_crystalcap; + u8 eeprom_tssi[2]; + u8 eeprom_tssi_5g[3][2]; /* for 5GL/5GM/5GH band. */ + u8 eeprom_pwrlimit_ht20[CHANNEL_GROUP_MAX]; + u8 eeprom_pwrlimit_ht40[CHANNEL_GROUP_MAX]; + u8 eeprom_chnlarea_txpwr_cck[2][CHANNEL_GROUP_MAX_2G]; + u8 eeprom_chnlarea_txpwr_ht40_1s[2][CHANNEL_GROUP_MAX]; + u8 eprom_chnl_txpwr_ht40_2sdf[2][CHANNEL_GROUP_MAX]; + u8 txpwrlevel_cck[2][CHANNEL_MAX_NUMBER_2G]; + u8 txpwrlevel_ht40_1s[2][CHANNEL_MAX_NUMBER]; /*For HT 40MHZ pwr */ + u8 txpwrlevel_ht40_2s[2][CHANNEL_MAX_NUMBER]; /*For HT 40MHZ pwr */ + + u8 internal_pa_5g[2]; /* pathA / pathB */ + u8 eeprom_c9; + u8 eeprom_cc; + + /*For power group */ + u8 eeprom_pwrgroup[2][3]; + u8 pwrgroup_ht20[2][CHANNEL_MAX_NUMBER]; + u8 pwrgroup_ht40[2][CHANNEL_MAX_NUMBER]; + + char txpwr_ht20diff[2][CHANNEL_MAX_NUMBER]; /*HT 20<->40 Pwr diff */ + /*For HT<->legacy pwr diff*/ + u8 txpwr_legacyhtdiff[2][CHANNEL_MAX_NUMBER]; + u8 txpwr_safetyflag; /* Band edge enable flag */ + u16 eeprom_txpowerdiff; + u8 legacy_httxpowerdiff; /* Legacy to HT rate power diff */ + u8 antenna_txpwdiff[3]; + + u8 eeprom_regulatory; + u8 eeprom_thermalmeter; + u8 thermalmeter[2]; /*ThermalMeter, index 0 for RFIC0, 1 for RFIC1 */ + u16 tssi_13dbm; + u8 crystalcap; /* CrystalCap. */ + u8 delta_iqk; + u8 delta_lck; + + u8 legacy_ht_txpowerdiff; /*Legacy to HT rate power diff */ + bool apk_thermalmeterignore; + + bool b1x1_recvcombine; + bool b1ss_support; + + /*channel plan */ + u8 channel_plan; +}; + +struct rtl_ps_ctl { + bool pwrdomain_protect; + bool in_powersavemode; + bool rfchange_inprogress; + bool swrf_processing; + bool hwradiooff; + /* + * just for PCIE ASPM + * If it supports ASPM, Offset[560h] = 0x40, + * otherwise Offset[560h] = 0x00. + * */ + bool support_aspm; + bool support_backdoor; + + /*for LPS */ + enum rt_psmode dot11_psmode; /*Power save mode configured. */ + bool swctrl_lps; + bool leisure_ps; + bool fwctrl_lps; + u8 fwctrl_psmode; + /*For Fw control LPS mode */ + u8 reg_fwctrl_lps; + /*Record Fw PS mode status. */ + bool fw_current_inpsmode; + u8 reg_max_lps_awakeintvl; + bool report_linked; + bool low_power_enable;/*for 32k*/ + + /*for IPS */ + bool inactiveps; + + u32 rfoff_reason; + + /*RF OFF Level */ + u32 cur_ps_level; + u32 reg_rfps_level; + + /*just for PCIE ASPM */ + u8 const_amdpci_aspm; + bool pwrdown_mode; + + enum rf_pwrstate inactive_pwrstate; + enum rf_pwrstate rfpwr_state; /*cur power state */ + + /* for SW LPS*/ + bool sw_ps_enabled; + bool state; + bool state_inap; + bool multi_buffered; + u16 nullfunc_seq; + unsigned int dtim_counter; + unsigned int sleep_ms; + unsigned long last_sleep_jiffies; + unsigned long last_awake_jiffies; + unsigned long last_delaylps_stamp_jiffies; + unsigned long last_dtim; + unsigned long last_beacon; + unsigned long last_action; + unsigned long last_slept; + + /*For P2P PS */ + struct rtl_p2p_ps_info p2p_ps_info; + u8 pwr_mode; + u8 smart_ps; +}; + +struct rtl_stats { + u8 psaddr[ETH_ALEN]; + u32 mac_time[2]; + s8 rssi; + u8 signal; + u8 noise; + u8 rate; /* hw desc rate */ + u8 received_channel; + u8 control; + u8 mask; + u8 freq; + u16 len; + u64 tsf; + u32 beacon_time; + u8 nic_type; + u16 length; + u8 signalquality; /*in 0-100 index. */ + /* + * Real power in dBm for this packet, + * no beautification and aggregation. + * */ + s32 recvsignalpower; + s8 rxpower; /*in dBm Translate from PWdB */ + u8 signalstrength; /*in 0-100 index. */ + u16 hwerror:1; + u16 crc:1; + u16 icv:1; + u16 shortpreamble:1; + u16 antenna:1; + u16 decrypted:1; + u16 wakeup:1; + u32 timestamp_low; + u32 timestamp_high; + + u8 rx_drvinfo_size; + u8 rx_bufshift; + bool isampdu; + bool isfirst_ampdu; + bool rx_is40Mhzpacket; + u32 rx_pwdb_all; + u8 rx_mimo_signalstrength[4]; /*in 0~100 index */ + s8 rx_mimo_sig_qual[2]; + bool packet_matchbssid; + bool is_cck; + bool is_ht; + bool packet_toself; + bool packet_beacon; /*for rssi */ + char cck_adc_pwdb[4]; /*for rx path selection */ + + u8 packet_report_type; + + u32 macid; + u8 wake_match; + u32 bt_rx_rssi_percentage; + u32 macid_valid_entry[2]; +}; + + +struct rt_link_detect { + /* count for roaming */ + u32 bcn_rx_inperiod; + u32 roam_times; + + u32 num_tx_in4period[4]; + u32 num_rx_in4period[4]; + + u32 num_tx_inperiod; + u32 num_rx_inperiod; + + bool busytraffic; + bool tx_busy_traffic; + bool rx_busy_traffic; + bool higher_busytraffic; + bool higher_busyrxtraffic; + + u32 tidtx_in4period[MAX_TID_COUNT][4]; + u32 tidtx_inperiod[MAX_TID_COUNT]; + bool higher_busytxtraffic[MAX_TID_COUNT]; +}; + +struct rtl_tcb_desc { + u8 packet_bw:1; + u8 multicast:1; + u8 broadcast:1; + + u8 rts_stbc:1; + u8 rts_enable:1; + u8 cts_enable:1; + u8 rts_use_shortpreamble:1; + u8 rts_use_shortgi:1; + u8 rts_sc:1; + u8 rts_bw:1; + u8 rts_rate; + + u8 use_shortgi:1; + u8 use_shortpreamble:1; + u8 use_driver_rate:1; + u8 disable_ratefallback:1; + + u8 ratr_index; + u8 mac_id; + u8 hw_rate; + + u8 last_inipkt:1; + u8 cmd_or_init:1; + u8 queue_index; + + /* early mode */ + u8 empkt_num; + /* The max value by HW */ + u32 empkt_len[10]; + bool btx_enable_sw_calc_duration; +}; + +struct rtl_hal_ops { + int (*init_sw_vars) (struct ieee80211_hw *hw); + void (*deinit_sw_vars) (struct ieee80211_hw *hw); + void (*read_chip_version)(struct ieee80211_hw *hw); + void (*read_eeprom_info) (struct ieee80211_hw *hw); + void (*interrupt_recognized) (struct ieee80211_hw *hw, + u32 *p_inta, u32 *p_intb); + int (*hw_init) (struct ieee80211_hw *hw); + void (*hw_disable) (struct ieee80211_hw *hw); + void (*hw_suspend) (struct ieee80211_hw *hw); + void (*hw_resume) (struct ieee80211_hw *hw); + void (*enable_interrupt) (struct ieee80211_hw *hw); + void (*disable_interrupt) (struct ieee80211_hw *hw); + int (*set_network_type) (struct ieee80211_hw *hw, + enum nl80211_iftype type); + void (*set_chk_bssid)(struct ieee80211_hw *hw, + bool check_bssid); + void (*set_bw_mode) (struct ieee80211_hw *hw, + enum nl80211_channel_type ch_type); + u8(*switch_channel) (struct ieee80211_hw *hw); + void (*set_qos) (struct ieee80211_hw *hw, int aci); + void (*set_bcn_reg) (struct ieee80211_hw *hw); + void (*set_bcn_intv) (struct ieee80211_hw *hw); + void (*update_interrupt_mask) (struct ieee80211_hw *hw, + u32 add_msr, u32 rm_msr); + void (*get_hw_reg) (struct ieee80211_hw *hw, u8 variable, u8 *val); + void (*set_hw_reg) (struct ieee80211_hw *hw, u8 variable, u8 *val); + void (*update_rate_tbl) (struct ieee80211_hw *hw, + struct ieee80211_sta *sta, u8 rssi_level); + void (*update_rate_mask) (struct ieee80211_hw *hw, u8 rssi_level); + void (*fill_tx_desc) (struct ieee80211_hw *hw, + struct ieee80211_hdr *hdr, u8 *pdesc_tx, + struct ieee80211_tx_info *info, + struct ieee80211_sta *sta, + struct sk_buff *skb, u8 hw_queue, + struct rtl_tcb_desc *ptcb_desc); + void (*fill_fake_txdesc) (struct ieee80211_hw *hw, u8 *pDesc, + u32 buffer_len, bool bIsPsPoll); + void (*fill_tx_cmddesc) (struct ieee80211_hw *hw, u8 *pdesc, + bool firstseg, bool lastseg, + struct sk_buff *skb); + bool (*cmd_send_packet)(struct ieee80211_hw *hw, struct sk_buff *skb); + bool (*query_rx_desc) (struct ieee80211_hw *hw, + struct rtl_stats *stats, + struct ieee80211_rx_status *rx_status, + u8 *pdesc, struct sk_buff *skb); + void (*set_channel_access) (struct ieee80211_hw *hw); + bool (*radio_onoff_checking) (struct ieee80211_hw *hw, u8 *valid); + void (*dm_watchdog) (struct ieee80211_hw *hw); + void (*scan_operation_backup) (struct ieee80211_hw *hw, u8 operation); + bool (*set_rf_power_state) (struct ieee80211_hw *hw, + enum rf_pwrstate rfpwr_state); + void (*led_control) (struct ieee80211_hw *hw, + enum led_ctl_mode ledaction); + void (*set_desc) (u8 *pdesc, bool istx, u8 desc_name, u8 *val); + u32 (*get_desc) (u8 *pdesc, bool istx, u8 desc_name); + void (*tx_polling) (struct ieee80211_hw *hw, u8 hw_queue); + void (*enable_hw_sec) (struct ieee80211_hw *hw); + void (*set_key) (struct ieee80211_hw *hw, u32 key_index, + u8 *macaddr, bool is_group, u8 enc_algo, + bool is_wepkey, bool clear_all); + void (*init_sw_leds) (struct ieee80211_hw *hw); + void (*deinit_sw_leds) (struct ieee80211_hw *hw); + u32 (*get_bbreg) (struct ieee80211_hw *hw, u32 regaddr, u32 bitmask); + void (*set_bbreg) (struct ieee80211_hw *hw, u32 regaddr, u32 bitmask, + u32 data); + u32 (*get_rfreg) (struct ieee80211_hw *hw, enum radio_path rfpath, + u32 regaddr, u32 bitmask); + void (*set_rfreg) (struct ieee80211_hw *hw, enum radio_path rfpath, + u32 regaddr, u32 bitmask, u32 data); + void (*allow_all_destaddr)(struct ieee80211_hw *hw, + bool allow_all_da, bool write_into_reg); + void (*linked_set_reg) (struct ieee80211_hw *hw); + void (*chk_switch_dmdp) (struct ieee80211_hw *hw); + void (*dualmac_easy_concurrent) (struct ieee80211_hw *hw); + void (*dualmac_switch_to_dmdp) (struct ieee80211_hw *hw); + bool (*phy_rf6052_config) (struct ieee80211_hw *hw); + void (*phy_rf6052_set_cck_txpower) (struct ieee80211_hw *hw, + u8 *powerlevel); + void (*phy_rf6052_set_ofdm_txpower) (struct ieee80211_hw *hw, + u8 *ppowerlevel, u8 channel); + bool (*config_bb_with_headerfile) (struct ieee80211_hw *hw, + u8 configtype); + bool (*config_bb_with_pgheaderfile) (struct ieee80211_hw *hw, + u8 configtype); + void (*phy_lc_calibrate) (struct ieee80211_hw *hw, bool is2t); + void (*phy_set_bw_mode_callback) (struct ieee80211_hw *hw); + void (*dm_dynamic_txpower) (struct ieee80211_hw *hw); + void (*c2h_command_handle) (struct ieee80211_hw *hw); + void (*bt_wifi_media_status_notify) (struct ieee80211_hw *hw, + bool mstate); + void (*bt_coex_off_before_lps) (struct ieee80211_hw *hw); + void (*fill_h2c_cmd) (struct ieee80211_hw *hw, u8 element_id, + u32 cmd_len, u8 *p_cmdbuffer); +}; + +struct rtl_intf_ops { + /*com */ + void (*read_efuse_byte)(struct ieee80211_hw *hw, u16 _offset, u8 *pbuf); + int (*adapter_start) (struct ieee80211_hw *hw); + void (*adapter_stop) (struct ieee80211_hw *hw); + bool (*check_buddy_priv)(struct ieee80211_hw *hw, + struct rtl_priv **buddy_priv); + + int (*adapter_tx) (struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + struct sk_buff *skb, + struct rtl_tcb_desc *ptcb_desc); + void (*flush)(struct ieee80211_hw *hw, bool drop); + int (*reset_trx_ring) (struct ieee80211_hw *hw); + bool (*waitq_insert) (struct ieee80211_hw *hw, + struct ieee80211_sta *sta, + struct sk_buff *skb); + + /*pci */ + void (*disable_aspm) (struct ieee80211_hw *hw); + void (*enable_aspm) (struct ieee80211_hw *hw); + + /*usb */ +}; + +struct rtl_mod_params { + /* default: 0 = using hardware encryption */ + bool sw_crypto; + + /* default: 0 = DBG_EMERG (0)*/ + int debug; + + /* default: 1 = using no linked power save */ + bool inactiveps; + + /* default: 1 = using linked sw power save */ + bool swctrl_lps; + + /* default: 1 = using linked fw power save */ + bool fwctrl_lps; +}; + +struct rtl_hal_usbint_cfg { + /* data - rx */ + u32 in_ep_num; + u32 rx_urb_num; + u32 rx_max_size; + + /* op - rx */ + void (*usb_rx_hdl)(struct ieee80211_hw *, struct sk_buff *); + void (*usb_rx_segregate_hdl)(struct ieee80211_hw *, struct sk_buff *, + struct sk_buff_head *); + + /* tx */ + void (*usb_tx_cleanup)(struct ieee80211_hw *, struct sk_buff *); + int (*usb_tx_post_hdl)(struct ieee80211_hw *, struct urb *, + struct sk_buff *); + struct sk_buff *(*usb_tx_aggregate_hdl)(struct ieee80211_hw *, + struct sk_buff_head *); + + /* endpoint mapping */ + int (*usb_endpoint_mapping)(struct ieee80211_hw *hw); + u16 (*usb_mq_to_hwq)(__le16 fc, u16 mac80211_queue_index); +}; + +struct rtl_hal_cfg { + u8 bar_id; + bool write_readback; + char *name; + char *fw_name; + struct rtl_hal_ops *ops; + struct rtl_mod_params *mod_params; + struct rtl_hal_usbint_cfg *usb_interface_cfg; + + /*this map used for some registers or vars + defined int HAL but used in MAIN */ + u32 maps[RTL_VAR_MAP_MAX]; + +}; + +struct rtl_locks { + /* mutex */ + struct mutex conf_mutex; + struct mutex ps_mutex; + + /*spin lock */ + spinlock_t ips_lock; + spinlock_t irq_th_lock; + spinlock_t irq_pci_lock; + spinlock_t tx_lock; + spinlock_t h2c_lock; + spinlock_t rf_ps_lock; + spinlock_t rf_lock; + spinlock_t lps_lock; + spinlock_t waitq_lock; + spinlock_t entry_list_lock; + spinlock_t usb_lock; + + /*FW clock change */ + spinlock_t fw_ps_lock; + + /*Dual mac*/ + spinlock_t cck_and_rw_pagea_lock; + + /*Easy concurrent*/ + spinlock_t check_sendpkt_lock; +}; + +struct rtl_works { + struct ieee80211_hw *hw; + + /*timer */ + struct timer_list watchdog_timer; + struct timer_list dualmac_easyconcurrent_retrytimer; + struct timer_list fw_clockoff_timer; + struct timer_list fast_antenna_training_timer; + /*task */ + struct tasklet_struct irq_tasklet; + struct tasklet_struct irq_prepare_bcn_tasklet; + + /*work queue */ + struct workqueue_struct *rtl_wq; + struct delayed_work watchdog_wq; + struct delayed_work ips_nic_off_wq; + + /* For SW LPS */ + struct delayed_work ps_work; + struct delayed_work ps_rfon_wq; + struct delayed_work fwevt_wq; + + struct work_struct lps_change_work; + struct work_struct fill_h2c_cmd; +}; + +struct rtl_debug { + u32 dbgp_type[DBGP_TYPE_MAX]; + int global_debuglevel; + u64 global_debugcomponents; + + /* add for proc debug */ + struct proc_dir_entry *proc_dir; + char proc_name[20]; +}; + +#define MIMO_PS_STATIC 0 +#define MIMO_PS_DYNAMIC 1 +#define MIMO_PS_NOLIMIT 3 + +struct rtl_dualmac_easy_concurrent_ctl { + enum band_type currentbandtype_backfordmdp; + bool close_bbandrf_for_dmsp; + bool change_to_dmdp; + bool change_to_dmsp; + bool switch_in_process; +}; + +struct rtl_dmsp_ctl { + bool activescan_for_slaveofdmsp; + bool scan_for_anothermac_fordmsp; + bool scan_for_itself_fordmsp; + bool writedig_for_anothermacofdmsp; + u32 curdigvalue_for_anothermacofdmsp; + bool changecckpdstate_for_anothermacofdmsp; + u8 curcckpdstate_for_anothermacofdmsp; + bool changetxhighpowerlvl_for_anothermacofdmsp; + u8 curtxhighlvl_for_anothermacofdmsp; + long rssivalmin_for_anothermacofdmsp; +}; + +struct ps_t { + u8 pre_ccastate; + u8 cur_ccasate; + u8 pre_rfstate; + u8 cur_rfstate; + long rssi_val_min; +}; + +struct dig_t { + u32 rssi_lowthresh; + u32 rssi_highthresh; + u32 fa_lowthresh; + u32 fa_highthresh; + long last_min_undec_pwdb_for_dm; + long rssi_highpower_lowthresh; + long rssi_highpower_highthresh; + u32 recover_cnt; + u32 pre_igvalue; + u32 cur_igvalue; + long rssi_val; + u8 dig_enable_flag; + u8 dig_ext_port_stage; + u8 dig_algorithm; + u8 dig_twoport_algorithm; + u8 dig_dbgmode; + u8 dig_slgorithm_switch; + u8 cursta_cstate; + u8 presta_cstate; + u8 curmultista_cstate; + char back_val; + char back_range_max; + char back_range_min; + u8 rx_gain_max; + u8 rx_gain_min; + u8 min_undec_pwdb_for_dm; + u8 rssi_val_min; + u8 pre_cck_cca_thres; + u8 cur_cck_cca_thres; + u8 pre_cck_pd_state; + u8 cur_cck_pd_state; + u8 pre_cck_fa_state; + u8 cur_cck_fa_state; + u8 pre_ccastate; + u8 cur_ccasate; + u8 large_fa_hit; + u8 forbidden_igi; + u8 dig_state; + u8 dig_highpwrstate; + u8 cur_sta_cstate; + u8 pre_sta_cstate; + u8 cur_ap_cstate; + u8 pre_ap_cstate; + u8 cur_pd_thstate; + u8 pre_pd_thstate; + u8 cur_cs_ratiostate; + u8 pre_cs_ratiostate; + u8 backoff_enable_flag; + char backoffval_range_max; + char backoffval_range_min; + u8 dig_min_0; + u8 dig_min_1; + bool media_connect_0; + bool media_connect_1; + + u32 antdiv_rssi_max; + u32 rssi_max; +}; + +struct rtl_global_var { + /* from this list we can get + * other adapter's rtl_priv */ + struct list_head glb_priv_list; + spinlock_t glb_list_lock; +}; + +struct rtl_priv { + struct ieee80211_hw *hw; + struct completion firmware_loading_complete; + struct list_head list; + struct rtl_priv *buddy_priv; + struct rtl_global_var *glb_var; + struct rtl_dualmac_easy_concurrent_ctl easy_concurrent_ctl; + struct rtl_dmsp_ctl dmsp_ctl; + struct rtl_locks locks; + struct rtl_works works; + struct rtl_mac mac80211; + struct rtl_hal rtlhal; + struct rtl_regulatory regd; + struct rtl_rfkill rfkill; + struct rtl_io io; + struct rtl_phy phy; + struct rtl_dm dm; + struct rtl_security sec; + struct rtl_efuse efuse; + + struct rtl_ps_ctl psc; + struct rate_adaptive ra; + struct wireless_stats stats; + struct rt_link_detect link_info; + struct false_alarm_statistics falsealm_cnt; + + struct rtl_rate_priv *rate_priv; + + /* sta entry list for ap adhoc or mesh */ + struct list_head entry_list; + + struct rtl_debug dbg; + int max_fw_size; + + /* + *hal_cfg : for diff cards + *intf_ops : for diff interrface usb/pcie + */ + struct rtl_hal_cfg *cfg; + struct rtl_intf_ops *intf_ops; + + /*this var will be set by set_bit, + and was used to indicate status of + interface or hardware */ + unsigned long status; + + /* tables for dm */ + struct dig_t dm_digtable; + struct ps_t dm_pstable; + + /* section shared by individual drivers */ + union { + struct { /* data buffer pointer for USB reads */ + __le32 *usb_data; + int usb_data_index; + bool initialized; + }; + struct { /* section for 8723ae */ + bool reg_init; /* true if regs saved */ + u32 reg_874; + u32 reg_c70; + u32 reg_85c; + u32 reg_a74; + bool bt_operation_on; + }; + }; + bool enter_ps; /* true when entering PS */ + u8 rate_mask[5]; + + /*This must be the last item so + that it points to the data allocated + beyond this structure like: + rtl_pci_priv or rtl_usb_priv */ + u8 priv[0] __aligned(sizeof(void *)); +}; + +#define rtl_priv(hw) (((struct rtl_priv *)(hw)->priv)) +#define rtl_mac(rtlpriv) (&((rtlpriv)->mac80211)) +#define rtl_hal(rtlpriv) (&((rtlpriv)->rtlhal)) +#define rtl_efuse(rtlpriv) (&((rtlpriv)->efuse)) +#define rtl_psc(rtlpriv) (&((rtlpriv)->psc)) + + +/*************************************** + Bluetooth Co-existence Related +****************************************/ + +enum bt_ant_num { + ANT_X2 = 0, + ANT_X1 = 1, +}; + +enum bt_co_type { + BT_2WIRE = 0, + BT_ISSC_3WIRE = 1, + BT_ACCEL = 2, + BT_CSR_BC4 = 3, + BT_CSR_BC8 = 4, + BT_RTL8756 = 5, + BT_RTL8723A = 6, +}; + +enum bt_cur_state { + BT_OFF = 0, + BT_ON = 1, +}; + +enum bt_service_type { + BT_SCO = 0, + BT_A2DP = 1, + BT_HID = 2, + BT_HID_IDLE = 3, + BT_SCAN = 4, + BT_IDLE = 5, + BT_OTHER_ACTION = 6, + BT_BUSY = 7, + BT_OTHERBUSY = 8, + BT_PAN = 9, +}; + +enum bt_radio_shared { + BT_RADIO_SHARED = 0, + BT_RADIO_INDIVIDUAL = 1, +}; + +struct bt_coexist_info { + + /* EEPROM BT info. */ + u8 eeprom_bt_coexist; + u8 eeprom_bt_type; + u8 eeprom_bt_ant_num; + u8 eeprom_bt_ant_isol; + u8 eeprom_bt_radio_shared; + + u8 bt_coexistence; + u8 bt_ant_num; + u8 bt_coexist_type; + u8 bt_state; + u8 bt_cur_state; /* 0:on, 1:off */ + u8 bt_ant_isolation; /* 0:good, 1:bad */ + u8 bt_pape_ctrl; /* 0:SW, 1:SW/HW dynamic */ + u8 bt_service; + u8 bt_radio_shared_type; + u8 bt_rfreg_origin_1e; + u8 bt_rfreg_origin_1f; + u8 bt_rssi_state; + u32 ratio_tx; + u32 ratio_pri; + u32 bt_edca_ul; + u32 bt_edca_dl; + + bool init_set; + bool bt_busy_traffic; + bool bt_traffic_mode_set; + bool bt_non_traffic_mode_set; + + bool fw_coexist_all_off; + bool sw_coexist_all_off; + bool hw_coexist_all_off; + u32 cstate; + u32 previous_state; + u32 cstate_h; + u32 previous_state_h; + + u8 bt_pre_rssi_state; + u8 bt_pre_rssi_state1; + + u8 reg_bt_iso; + u8 reg_bt_sco; + bool balance_on; + u8 bt_active_zero_cnt; + bool cur_bt_disabled; + bool pre_bt_disabled; + + u8 bt_profile_case; + u8 bt_profile_action; + bool bt_busy; + bool hold_for_bt_operation; + u8 lps_counter; +}; + + +/**************************************** + mem access macro define start + Call endian free function when + 1. Read/write packet content. + 2. Before write integer to IO. + 3. After read integer from IO. +****************************************/ +/* Convert little data endian to host ordering */ +#define EF1BYTE(_val) \ + ((u8)(_val)) +#define EF2BYTE(_val) \ + (le16_to_cpu(_val)) +#define EF4BYTE(_val) \ + (le32_to_cpu(_val)) + +/* Read data from memory */ +#define READEF1BYTE(_ptr) \ + EF1BYTE(*((u8 *)(_ptr))) +/* Read le16 data from memory and convert to host ordering */ +#define READEF2BYTE(_ptr) \ + EF2BYTE(*(_ptr)) +#define READEF4BYTE(_ptr) \ + EF4BYTE(*(_ptr)) + +/* Write data to memory */ +#define WRITEEF1BYTE(_ptr, _val) \ + (*((u8 *)(_ptr))) = EF1BYTE(_val) +/* Write le16 data to memory in host ordering */ +#define WRITEEF2BYTE(_ptr, _val) \ + (*((u16 *)(_ptr))) = EF2BYTE(_val) +#define WRITEEF4BYTE(_ptr, _val) \ + (*((u32 *)(_ptr))) = EF2BYTE(_val) + +/* Create a bit mask + * Examples: + * BIT_LEN_MASK_32(0) => 0x00000000 + * BIT_LEN_MASK_32(1) => 0x00000001 + * BIT_LEN_MASK_32(2) => 0x00000003 + * BIT_LEN_MASK_32(32) => 0xFFFFFFFF + */ +#define BIT_LEN_MASK_32(__bitlen) \ + (0xFFFFFFFF >> (32 - (__bitlen))) +#define BIT_LEN_MASK_16(__bitlen) \ + (0xFFFF >> (16 - (__bitlen))) +#define BIT_LEN_MASK_8(__bitlen) \ + (0xFF >> (8 - (__bitlen))) + +/* Create an offset bit mask + * Examples: + * BIT_OFFSET_LEN_MASK_32(0, 2) => 0x00000003 + * BIT_OFFSET_LEN_MASK_32(16, 2) => 0x00030000 + */ +#define BIT_OFFSET_LEN_MASK_32(__bitoffset, __bitlen) \ + (BIT_LEN_MASK_32(__bitlen) << (__bitoffset)) +#define BIT_OFFSET_LEN_MASK_16(__bitoffset, __bitlen) \ + (BIT_LEN_MASK_16(__bitlen) << (__bitoffset)) +#define BIT_OFFSET_LEN_MASK_8(__bitoffset, __bitlen) \ + (BIT_LEN_MASK_8(__bitlen) << (__bitoffset)) + +/*Description: + * Return 4-byte value in host byte ordering from + * 4-byte pointer in little-endian system. + */ +#define LE_P4BYTE_TO_HOST_4BYTE(__pstart) \ + (EF4BYTE(*((__le32 *)(__pstart)))) +#define LE_P2BYTE_TO_HOST_2BYTE(__pstart) \ + (EF2BYTE(*((__le16 *)(__pstart)))) +#define LE_P1BYTE_TO_HOST_1BYTE(__pstart) \ + (EF1BYTE(*((u8 *)(__pstart)))) + +/*Description: +Translate subfield (continuous bits in little-endian) of 4-byte +value to host byte ordering.*/ +#define LE_BITS_TO_4BYTE(__pstart, __bitoffset, __bitlen) \ + ( \ + (LE_P4BYTE_TO_HOST_4BYTE(__pstart) >> (__bitoffset)) & \ + BIT_LEN_MASK_32(__bitlen) \ + ) +#define LE_BITS_TO_2BYTE(__pstart, __bitoffset, __bitlen) \ + ( \ + (LE_P2BYTE_TO_HOST_2BYTE(__pstart) >> (__bitoffset)) & \ + BIT_LEN_MASK_16(__bitlen) \ + ) +#define LE_BITS_TO_1BYTE(__pstart, __bitoffset, __bitlen) \ + ( \ + (LE_P1BYTE_TO_HOST_1BYTE(__pstart) >> (__bitoffset)) & \ + BIT_LEN_MASK_8(__bitlen) \ + ) + +/* Description: + * Mask subfield (continuous bits in little-endian) of 4-byte value + * and return the result in 4-byte value in host byte ordering. + */ +#define LE_BITS_CLEARED_TO_4BYTE(__pstart, __bitoffset, __bitlen) \ + ( \ + LE_P4BYTE_TO_HOST_4BYTE(__pstart) & \ + (~BIT_OFFSET_LEN_MASK_32(__bitoffset, __bitlen)) \ + ) +#define LE_BITS_CLEARED_TO_2BYTE(__pstart, __bitoffset, __bitlen) \ + ( \ + LE_P2BYTE_TO_HOST_2BYTE(__pstart) & \ + (~BIT_OFFSET_LEN_MASK_16(__bitoffset, __bitlen)) \ + ) +#define LE_BITS_CLEARED_TO_1BYTE(__pstart, __bitoffset, __bitlen) \ + ( \ + LE_P1BYTE_TO_HOST_1BYTE(__pstart) & \ + (~BIT_OFFSET_LEN_MASK_8(__bitoffset, __bitlen)) \ + ) + +/* Description: + * Set subfield of little-endian 4-byte value to specified value. + */ +#define SET_BITS_TO_LE_4BYTE(__pstart, __bitoffset, __bitlen, __val) \ + *((u32 *)(__pstart)) = \ + ( \ + LE_BITS_CLEARED_TO_4BYTE(__pstart, __bitoffset, __bitlen) | \ + ((((u32)__val) & BIT_LEN_MASK_32(__bitlen)) << (__bitoffset)) \ + ); +#define SET_BITS_TO_LE_2BYTE(__pstart, __bitoffset, __bitlen, __val) \ + *((u16 *)(__pstart)) = \ + ( \ + LE_BITS_CLEARED_TO_2BYTE(__pstart, __bitoffset, __bitlen) | \ + ((((u16)__val) & BIT_LEN_MASK_16(__bitlen)) << (__bitoffset)) \ + ); +#define SET_BITS_TO_LE_1BYTE(__pstart, __bitoffset, __bitlen, __val) \ + *((u8 *)(__pstart)) = EF1BYTE \ + ( \ + LE_BITS_CLEARED_TO_1BYTE(__pstart, __bitoffset, __bitlen) | \ + ((((u8)__val) & BIT_LEN_MASK_8(__bitlen)) << (__bitoffset)) \ + ); + +#define N_BYTE_ALIGMENT(__value, __aligment) ((__aligment == 1) ? \ + (__value) : (((__value + __aligment - 1) / __aligment) * __aligment)) + +/**************************************** + mem access macro define end +****************************************/ + +#define byte(x, n) ((x >> (8 * n)) & 0xff) + +#define packet_get_type(_packet) (EF1BYTE((_packet).octet[0]) & 0xFC) +#define RTL_WATCH_DOG_TIME 2000 +#define MSECS(t) msecs_to_jiffies(t) +#define WLAN_FC_GET_VERS(fc) (le16_to_cpu(fc) & IEEE80211_FCTL_VERS) +#define WLAN_FC_GET_TYPE(fc) (le16_to_cpu(fc) & IEEE80211_FCTL_FTYPE) +#define WLAN_FC_GET_STYPE(fc) (le16_to_cpu(fc) & IEEE80211_FCTL_STYPE) +#define WLAN_FC_MORE_DATA(fc) (le16_to_cpu(fc) & IEEE80211_FCTL_MOREDATA) +#define rtl_dm(rtlpriv) (&((rtlpriv)->dm)) + +#define RT_RF_OFF_LEVL_ASPM BIT(0) /*PCI ASPM */ +#define RT_RF_OFF_LEVL_CLK_REQ BIT(1) /*PCI clock request */ +#define RT_RF_OFF_LEVL_PCI_D3 BIT(2) /*PCI D3 mode */ +/*NIC halt, re-initialize hw parameters*/ +#define RT_RF_OFF_LEVL_HALT_NIC BIT(3) +#define RT_RF_OFF_LEVL_FREE_FW BIT(4) /*FW free, re-download the FW */ +#define RT_RF_OFF_LEVL_FW_32K BIT(5) /*FW in 32k */ +/*Always enable ASPM and Clock Req in initialization.*/ +#define RT_RF_PS_LEVEL_ALWAYS_ASPM BIT(6) +/* no matter RFOFF or SLEEP we set PS_ASPM_LEVL*/ +#define RT_PS_LEVEL_ASPM BIT(7) +/*When LPS is on, disable 2R if no packet is received or transmittd.*/ +#define RT_RF_LPS_DISALBE_2R BIT(30) +#define RT_RF_LPS_LEVEL_ASPM BIT(31) /*LPS with ASPM */ +#define RT_IN_PS_LEVEL(ppsc, _ps_flg) \ + ((ppsc->cur_ps_level & _ps_flg) ? true : false) +#define RT_CLEAR_PS_LEVEL(ppsc, _ps_flg) \ + (ppsc->cur_ps_level &= (~(_ps_flg))) +#define RT_SET_PS_LEVEL(ppsc, _ps_flg) \ + (ppsc->cur_ps_level |= _ps_flg) + +#define container_of_dwork_rtl(x, y, z) \ + container_of(container_of(x, struct delayed_work, work), y, z) + +#define FILL_OCTET_STRING(_os, _octet, _len) \ + (_os).octet = (u8 *)(_octet); \ + (_os).length = (_len); + +#define CP_MACADDR(des, src) \ + ((des)[0] = (src)[0], (des)[1] = (src)[1],\ + (des)[2] = (src)[2], (des)[3] = (src)[3],\ + (des)[4] = (src)[4], (des)[5] = (src)[5]) + +static inline u8 rtl_read_byte(struct rtl_priv *rtlpriv, u32 addr) +{ + return rtlpriv->io.read8_sync(rtlpriv, addr); +} + +static inline u16 rtl_read_word(struct rtl_priv *rtlpriv, u32 addr) +{ + return rtlpriv->io.read16_sync(rtlpriv, addr); +} + +static inline u32 rtl_read_dword(struct rtl_priv *rtlpriv, u32 addr) +{ + return rtlpriv->io.read32_sync(rtlpriv, addr); +} + +static inline void rtl_write_byte(struct rtl_priv *rtlpriv, u32 addr, u8 val8) +{ + rtlpriv->io.write8_async(rtlpriv, addr, val8); + + if (rtlpriv->cfg->write_readback) + rtlpriv->io.read8_sync(rtlpriv, addr); +} + +static inline void rtl_write_word(struct rtl_priv *rtlpriv, u32 addr, u16 val16) +{ + rtlpriv->io.write16_async(rtlpriv, addr, val16); + + if (rtlpriv->cfg->write_readback) + rtlpriv->io.read16_sync(rtlpriv, addr); +} + +static inline void rtl_write_dword(struct rtl_priv *rtlpriv, + u32 addr, u32 val32) +{ + rtlpriv->io.write32_async(rtlpriv, addr, val32); + + if (rtlpriv->cfg->write_readback) + rtlpriv->io.read32_sync(rtlpriv, addr); +} + +static inline u32 rtl_get_bbreg(struct ieee80211_hw *hw, + u32 regaddr, u32 bitmask) +{ + struct rtl_priv *rtlpriv = hw->priv; + + return rtlpriv->cfg->ops->get_bbreg(hw, regaddr, bitmask); +} + +static inline void rtl_set_bbreg(struct ieee80211_hw *hw, u32 regaddr, + u32 bitmask, u32 data) +{ + struct rtl_priv *rtlpriv = hw->priv; + + rtlpriv->cfg->ops->set_bbreg(hw, regaddr, bitmask, data); +} + +static inline u32 rtl_get_rfreg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, + u32 bitmask) +{ + struct rtl_priv *rtlpriv = hw->priv; + + return rtlpriv->cfg->ops->get_rfreg(hw, rfpath, regaddr, bitmask); +} + +static inline void rtl_set_rfreg(struct ieee80211_hw *hw, + enum radio_path rfpath, u32 regaddr, + u32 bitmask, u32 data) +{ + struct rtl_priv *rtlpriv = hw->priv; + + rtlpriv->cfg->ops->set_rfreg(hw, rfpath, regaddr, bitmask, data); +} + +static inline bool is_hal_stop(struct rtl_hal *rtlhal) +{ + return (_HAL_STATE_STOP == rtlhal->state); +} + +static inline void set_hal_start(struct rtl_hal *rtlhal) +{ + rtlhal->state = _HAL_STATE_START; +} + +static inline void set_hal_stop(struct rtl_hal *rtlhal) +{ + rtlhal->state = _HAL_STATE_STOP; +} + +static inline u8 get_rf_type(struct rtl_phy *rtlphy) +{ + return rtlphy->rf_type; +} + +static inline struct ieee80211_hdr *rtl_get_hdr(struct sk_buff *skb) +{ + return (struct ieee80211_hdr *)(skb->data); +} + +static inline __le16 rtl_get_fc(struct sk_buff *skb) +{ + return rtl_get_hdr(skb)->frame_control; +} + +static inline u16 rtl_get_tid_h(struct ieee80211_hdr *hdr) +{ + return (ieee80211_get_qos_ctl(hdr))[0] & IEEE80211_QOS_CTL_TID_MASK; +} + +static inline u16 rtl_get_tid(struct sk_buff *skb) +{ + return rtl_get_tid_h(rtl_get_hdr(skb)); +} + +static inline struct ieee80211_sta *get_sta(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *bssid) +{ + return ieee80211_find_sta(vif, bssid); +} + +static inline struct ieee80211_sta *rtl_find_sta(struct ieee80211_hw *hw, + u8 *mac_addr) +{ + struct rtl_mac *mac = rtl_mac(rtl_priv(hw)); + return ieee80211_find_sta(mac->vif, mac_addr); +} + +#endif diff --git a/drivers/net/wireless/ti/Kconfig b/drivers/net/wireless/ti/Kconfig new file mode 100644 index 0000000..5eee4b9 --- /dev/null +++ b/drivers/net/wireless/ti/Kconfig @@ -0,0 +1,25 @@ +menuconfig WL_TI + depends on !BACKPORT_KERNEL_2_6_30 + bool "TI Wireless LAN support" + ---help--- + This section contains support for all the wireless drivers + for Texas Instruments WLAN chips, such as wl1251 and the wl12xx + family. + +if WL_TI +source "drivers/net/wireless/ti/wl1251/Kconfig" +source "drivers/net/wireless/ti/wl12xx/Kconfig" +source "drivers/net/wireless/ti/wl18xx/Kconfig" + +# keep last for automatic dependencies +source "drivers/net/wireless/ti/wlcore/Kconfig" + +config WILINK_PLATFORM_DATA + bool "TI WiLink platform data" + depends on WLCORE_SDIO || WL1251_SDIO + default y + ---help--- + Small platform data bit needed to pass data to the sdio modules. + + +endif # WL_TI diff --git a/drivers/net/wireless/ti/Makefile b/drivers/net/wireless/ti/Makefile new file mode 100644 index 0000000..d758511 --- /dev/null +++ b/drivers/net/wireless/ti/Makefile @@ -0,0 +1,7 @@ +obj-$(CPTCFG_WLCORE) += wlcore/ +obj-$(CPTCFG_WL12XX) += wl12xx/ +obj-$(CPTCFG_WL1251) += wl1251/ +obj-$(CPTCFG_WL18XX) += wl18xx/ + +# small builtin driver bit +obj-$(CPTCFG_WILINK_PLATFORM_DATA) += wilink_platform_data.o diff --git a/drivers/net/wireless/ti/wilink_platform_data.c b/drivers/net/wireless/ti/wilink_platform_data.c new file mode 100644 index 0000000..998e958 --- /dev/null +++ b/drivers/net/wireless/ti/wilink_platform_data.c @@ -0,0 +1,49 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 2010-2011 Texas Instruments, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + +static struct wl12xx_platform_data *platform_data; + +int __init wl12xx_set_platform_data(const struct wl12xx_platform_data *data) +{ + if (platform_data) + return -EBUSY; + if (!data) + return -EINVAL; + + platform_data = kmemdup(data, sizeof(*data), GFP_KERNEL); + if (!platform_data) + return -ENOMEM; + + return 0; +} + +struct wl12xx_platform_data *wl12xx_get_platform_data(void) +{ + if (!platform_data) + return ERR_PTR(-ENODEV); + + return platform_data; +} +EXPORT_SYMBOL(wl12xx_get_platform_data); diff --git a/drivers/net/wireless/ti/wl1251/Kconfig b/drivers/net/wireless/ti/wl1251/Kconfig new file mode 100644 index 0000000..403b098 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/Kconfig @@ -0,0 +1,39 @@ +menuconfig WL1251 + depends on !BACKPORT_KERNEL_3_0 + tristate "TI wl1251 driver support" + depends on m + depends on MAC80211 && GENERIC_HARDIRQS + select BACKPORT_FW_LOADER + depends on CRC7 + ---help--- + This will enable TI wl1251 driver support. The drivers make + use of the mac80211 stack. + + If you choose to build a module, it'll be called wl1251. Say + N if unsure. + +config WL1251_SPI + depends on !BACKPORT_KERNEL_2_6_37 + tristate "TI wl1251 SPI support" + depends on m + depends on WL1251 && SPI_MASTER + ---help--- + This module adds support for the SPI interface of adapters using + TI wl1251 chipset. Select this if your platform is using + the SPI bus. + + If you choose to build a module, it'll be called wl1251_spi. + Say N if unsure. + +config WL1251_SDIO + depends on !BACKPORT_KERNEL_2_6_37 + tristate "TI wl1251 SDIO support" + depends on m + depends on WL1251 && MMC + ---help--- + This module adds support for the SDIO interface of adapters using + TI wl1251 chipset. Select this if your platform is using + the SDIO bus. + + If you choose to build a module, it'll be called + wl1251_sdio. Say N if unsure. diff --git a/drivers/net/wireless/ti/wl1251/Makefile b/drivers/net/wireless/ti/wl1251/Makefile new file mode 100644 index 0000000..3ff51d8 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/Makefile @@ -0,0 +1,10 @@ +wl1251-objs = main.o event.o tx.o rx.o ps.o cmd.o \ + acx.o boot.o init.o debugfs.o io.o +wl1251_spi-objs += spi.o +wl1251_sdio-objs += sdio.o + +obj-$(CPTCFG_WL1251) += wl1251.o +obj-$(CPTCFG_WL1251_SPI) += wl1251_spi.o +obj-$(CPTCFG_WL1251_SDIO) += wl1251_sdio.o + +ccflags-y += -D__CHECK_ENDIAN__ diff --git a/drivers/net/wireless/ti/wl1251/acx.c b/drivers/net/wireless/ti/wl1251/acx.c new file mode 100644 index 0000000..db6430c --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/acx.c @@ -0,0 +1,1097 @@ +#include "acx.h" + +#include +#include +#include + +#include "wl1251.h" +#include "reg.h" +#include "cmd.h" +#include "ps.h" + +int wl1251_acx_frame_rates(struct wl1251 *wl, u8 ctrl_rate, u8 ctrl_mod, + u8 mgt_rate, u8 mgt_mod) +{ + struct acx_fw_gen_frame_rates *rates; + int ret; + + wl1251_debug(DEBUG_ACX, "acx frame rates"); + + rates = kzalloc(sizeof(*rates), GFP_KERNEL); + if (!rates) { + ret = -ENOMEM; + goto out; + } + + rates->tx_ctrl_frame_rate = ctrl_rate; + rates->tx_ctrl_frame_mod = ctrl_mod; + rates->tx_mgt_frame_rate = mgt_rate; + rates->tx_mgt_frame_mod = mgt_mod; + + ret = wl1251_cmd_configure(wl, ACX_FW_GEN_FRAME_RATES, + rates, sizeof(*rates)); + if (ret < 0) { + wl1251_error("Failed to set FW rates and modulation"); + goto out; + } + +out: + kfree(rates); + return ret; +} + + +int wl1251_acx_station_id(struct wl1251 *wl) +{ + struct acx_dot11_station_id *mac; + int ret, i; + + wl1251_debug(DEBUG_ACX, "acx dot11_station_id"); + + mac = kzalloc(sizeof(*mac), GFP_KERNEL); + if (!mac) { + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < ETH_ALEN; i++) + mac->mac[i] = wl->mac_addr[ETH_ALEN - 1 - i]; + + ret = wl1251_cmd_configure(wl, DOT11_STATION_ID, mac, sizeof(*mac)); + if (ret < 0) + goto out; + +out: + kfree(mac); + return ret; +} + +int wl1251_acx_default_key(struct wl1251 *wl, u8 key_id) +{ + struct acx_dot11_default_key *default_key; + int ret; + + wl1251_debug(DEBUG_ACX, "acx dot11_default_key (%d)", key_id); + + default_key = kzalloc(sizeof(*default_key), GFP_KERNEL); + if (!default_key) { + ret = -ENOMEM; + goto out; + } + + default_key->id = key_id; + + ret = wl1251_cmd_configure(wl, DOT11_DEFAULT_KEY, + default_key, sizeof(*default_key)); + if (ret < 0) { + wl1251_error("Couldn't set default key"); + goto out; + } + + wl->default_key = key_id; + +out: + kfree(default_key); + return ret; +} + +int wl1251_acx_wake_up_conditions(struct wl1251 *wl, u8 wake_up_event, + u8 listen_interval) +{ + struct acx_wake_up_condition *wake_up; + int ret; + + wl1251_debug(DEBUG_ACX, "acx wake up conditions"); + + wake_up = kzalloc(sizeof(*wake_up), GFP_KERNEL); + if (!wake_up) { + ret = -ENOMEM; + goto out; + } + + wake_up->wake_up_event = wake_up_event; + wake_up->listen_interval = listen_interval; + + ret = wl1251_cmd_configure(wl, ACX_WAKE_UP_CONDITIONS, + wake_up, sizeof(*wake_up)); + if (ret < 0) { + wl1251_warning("could not set wake up conditions: %d", ret); + goto out; + } + +out: + kfree(wake_up); + return ret; +} + +int wl1251_acx_sleep_auth(struct wl1251 *wl, u8 sleep_auth) +{ + struct acx_sleep_auth *auth; + int ret; + + wl1251_debug(DEBUG_ACX, "acx sleep auth"); + + auth = kzalloc(sizeof(*auth), GFP_KERNEL); + if (!auth) { + ret = -ENOMEM; + goto out; + } + + auth->sleep_auth = sleep_auth; + + ret = wl1251_cmd_configure(wl, ACX_SLEEP_AUTH, auth, sizeof(*auth)); + +out: + kfree(auth); + return ret; +} + +int wl1251_acx_fw_version(struct wl1251 *wl, char *buf, size_t len) +{ + struct acx_revision *rev; + int ret; + + wl1251_debug(DEBUG_ACX, "acx fw rev"); + + rev = kzalloc(sizeof(*rev), GFP_KERNEL); + if (!rev) { + ret = -ENOMEM; + goto out; + } + + ret = wl1251_cmd_interrogate(wl, ACX_FW_REV, rev, sizeof(*rev)); + if (ret < 0) { + wl1251_warning("ACX_FW_REV interrogate failed"); + goto out; + } + + /* be careful with the buffer sizes */ + strncpy(buf, rev->fw_version, min(len, sizeof(rev->fw_version))); + + /* + * if the firmware version string is exactly + * sizeof(rev->fw_version) long or fw_len is less than + * sizeof(rev->fw_version) it won't be null terminated + */ + buf[min(len, sizeof(rev->fw_version)) - 1] = '\0'; + +out: + kfree(rev); + return ret; +} + +int wl1251_acx_tx_power(struct wl1251 *wl, int power) +{ + struct acx_current_tx_power *acx; + int ret; + + wl1251_debug(DEBUG_ACX, "acx dot11_cur_tx_pwr"); + + if (power < 0 || power > 25) + return -EINVAL; + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->current_tx_power = power * 10; + + ret = wl1251_cmd_configure(wl, DOT11_CUR_TX_PWR, acx, sizeof(*acx)); + if (ret < 0) { + wl1251_warning("configure of tx power failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1251_acx_feature_cfg(struct wl1251 *wl) +{ + struct acx_feature_config *feature; + int ret; + + wl1251_debug(DEBUG_ACX, "acx feature cfg"); + + feature = kzalloc(sizeof(*feature), GFP_KERNEL); + if (!feature) { + ret = -ENOMEM; + goto out; + } + + /* DF_ENCRYPTION_DISABLE and DF_SNIFF_MODE_ENABLE are disabled */ + feature->data_flow_options = 0; + feature->options = 0; + + ret = wl1251_cmd_configure(wl, ACX_FEATURE_CFG, + feature, sizeof(*feature)); + if (ret < 0) { + wl1251_error("Couldn't set HW encryption"); + goto out; + } + +out: + kfree(feature); + return ret; +} + +int wl1251_acx_mem_map(struct wl1251 *wl, struct acx_header *mem_map, + size_t len) +{ + int ret; + + wl1251_debug(DEBUG_ACX, "acx mem map"); + + ret = wl1251_cmd_interrogate(wl, ACX_MEM_MAP, mem_map, len); + if (ret < 0) + return ret; + + return 0; +} + +int wl1251_acx_data_path_params(struct wl1251 *wl, + struct acx_data_path_params_resp *resp) +{ + struct acx_data_path_params *params; + int ret; + + wl1251_debug(DEBUG_ACX, "acx data path params"); + + params = kzalloc(sizeof(*params), GFP_KERNEL); + if (!params) { + ret = -ENOMEM; + goto out; + } + + params->rx_packet_ring_chunk_size = DP_RX_PACKET_RING_CHUNK_SIZE; + params->tx_packet_ring_chunk_size = DP_TX_PACKET_RING_CHUNK_SIZE; + + params->rx_packet_ring_chunk_num = DP_RX_PACKET_RING_CHUNK_NUM; + params->tx_packet_ring_chunk_num = DP_TX_PACKET_RING_CHUNK_NUM; + + params->tx_complete_threshold = 1; + + params->tx_complete_ring_depth = FW_TX_CMPLT_BLOCK_SIZE; + + params->tx_complete_timeout = DP_TX_COMPLETE_TIME_OUT; + + ret = wl1251_cmd_configure(wl, ACX_DATA_PATH_PARAMS, + params, sizeof(*params)); + if (ret < 0) + goto out; + + /* FIXME: shouldn't this be ACX_DATA_PATH_RESP_PARAMS? */ + ret = wl1251_cmd_interrogate(wl, ACX_DATA_PATH_PARAMS, + resp, sizeof(*resp)); + + if (ret < 0) { + wl1251_warning("failed to read data path parameters: %d", ret); + goto out; + } else if (resp->header.cmd.status != CMD_STATUS_SUCCESS) { + wl1251_warning("data path parameter acx status failed"); + ret = -EIO; + goto out; + } + +out: + kfree(params); + return ret; +} + +int wl1251_acx_rx_msdu_life_time(struct wl1251 *wl, u32 life_time) +{ + struct acx_rx_msdu_lifetime *acx; + int ret; + + wl1251_debug(DEBUG_ACX, "acx rx msdu life time"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->lifetime = life_time; + ret = wl1251_cmd_configure(wl, DOT11_RX_MSDU_LIFE_TIME, + acx, sizeof(*acx)); + if (ret < 0) { + wl1251_warning("failed to set rx msdu life time: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1251_acx_rx_config(struct wl1251 *wl, u32 config, u32 filter) +{ + struct acx_rx_config *rx_config; + int ret; + + wl1251_debug(DEBUG_ACX, "acx rx config"); + + rx_config = kzalloc(sizeof(*rx_config), GFP_KERNEL); + if (!rx_config) { + ret = -ENOMEM; + goto out; + } + + rx_config->config_options = config; + rx_config->filter_options = filter; + + ret = wl1251_cmd_configure(wl, ACX_RX_CFG, + rx_config, sizeof(*rx_config)); + if (ret < 0) { + wl1251_warning("failed to set rx config: %d", ret); + goto out; + } + +out: + kfree(rx_config); + return ret; +} + +int wl1251_acx_pd_threshold(struct wl1251 *wl) +{ + struct acx_packet_detection *pd; + int ret; + + wl1251_debug(DEBUG_ACX, "acx data pd threshold"); + + pd = kzalloc(sizeof(*pd), GFP_KERNEL); + if (!pd) { + ret = -ENOMEM; + goto out; + } + + /* FIXME: threshold value not set */ + + ret = wl1251_cmd_configure(wl, ACX_PD_THRESHOLD, pd, sizeof(*pd)); + if (ret < 0) { + wl1251_warning("failed to set pd threshold: %d", ret); + goto out; + } + +out: + kfree(pd); + return ret; +} + +int wl1251_acx_slot(struct wl1251 *wl, enum acx_slot_type slot_time) +{ + struct acx_slot *slot; + int ret; + + wl1251_debug(DEBUG_ACX, "acx slot"); + + slot = kzalloc(sizeof(*slot), GFP_KERNEL); + if (!slot) { + ret = -ENOMEM; + goto out; + } + + slot->wone_index = STATION_WONE_INDEX; + slot->slot_time = slot_time; + + ret = wl1251_cmd_configure(wl, ACX_SLOT, slot, sizeof(*slot)); + if (ret < 0) { + wl1251_warning("failed to set slot time: %d", ret); + goto out; + } + +out: + kfree(slot); + return ret; +} + +int wl1251_acx_group_address_tbl(struct wl1251 *wl) +{ + struct acx_dot11_grp_addr_tbl *acx; + int ret; + + wl1251_debug(DEBUG_ACX, "acx group address tbl"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + /* MAC filtering */ + acx->enabled = 0; + acx->num_groups = 0; + memset(acx->mac_table, 0, ADDRESS_GROUP_MAX_LEN); + + ret = wl1251_cmd_configure(wl, DOT11_GROUP_ADDRESS_TBL, + acx, sizeof(*acx)); + if (ret < 0) { + wl1251_warning("failed to set group addr table: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1251_acx_service_period_timeout(struct wl1251 *wl) +{ + struct acx_rx_timeout *rx_timeout; + int ret; + + rx_timeout = kzalloc(sizeof(*rx_timeout), GFP_KERNEL); + if (!rx_timeout) { + ret = -ENOMEM; + goto out; + } + + wl1251_debug(DEBUG_ACX, "acx service period timeout"); + + rx_timeout->ps_poll_timeout = RX_TIMEOUT_PS_POLL_DEF; + rx_timeout->upsd_timeout = RX_TIMEOUT_UPSD_DEF; + + ret = wl1251_cmd_configure(wl, ACX_SERVICE_PERIOD_TIMEOUT, + rx_timeout, sizeof(*rx_timeout)); + if (ret < 0) { + wl1251_warning("failed to set service period timeout: %d", + ret); + goto out; + } + +out: + kfree(rx_timeout); + return ret; +} + +int wl1251_acx_rts_threshold(struct wl1251 *wl, u16 rts_threshold) +{ + struct acx_rts_threshold *rts; + int ret; + + wl1251_debug(DEBUG_ACX, "acx rts threshold"); + + rts = kzalloc(sizeof(*rts), GFP_KERNEL); + if (!rts) { + ret = -ENOMEM; + goto out; + } + + rts->threshold = rts_threshold; + + ret = wl1251_cmd_configure(wl, DOT11_RTS_THRESHOLD, rts, sizeof(*rts)); + if (ret < 0) { + wl1251_warning("failed to set rts threshold: %d", ret); + goto out; + } + +out: + kfree(rts); + return ret; +} + +int wl1251_acx_beacon_filter_opt(struct wl1251 *wl, bool enable_filter) +{ + struct acx_beacon_filter_option *beacon_filter; + int ret; + + wl1251_debug(DEBUG_ACX, "acx beacon filter opt"); + + beacon_filter = kzalloc(sizeof(*beacon_filter), GFP_KERNEL); + if (!beacon_filter) { + ret = -ENOMEM; + goto out; + } + + beacon_filter->enable = enable_filter; + beacon_filter->max_num_beacons = 0; + + ret = wl1251_cmd_configure(wl, ACX_BEACON_FILTER_OPT, + beacon_filter, sizeof(*beacon_filter)); + if (ret < 0) { + wl1251_warning("failed to set beacon filter opt: %d", ret); + goto out; + } + +out: + kfree(beacon_filter); + return ret; +} + +int wl1251_acx_beacon_filter_table(struct wl1251 *wl) +{ + struct acx_beacon_filter_ie_table *ie_table; + int idx = 0; + int ret; + + wl1251_debug(DEBUG_ACX, "acx beacon filter table"); + + ie_table = kzalloc(sizeof(*ie_table), GFP_KERNEL); + if (!ie_table) { + ret = -ENOMEM; + goto out; + } + + /* configure default beacon pass-through rules */ + ie_table->num_ie = 1; + ie_table->table[idx++] = BEACON_FILTER_IE_ID_CHANNEL_SWITCH_ANN; + ie_table->table[idx++] = BEACON_RULE_PASS_ON_APPEARANCE; + + ret = wl1251_cmd_configure(wl, ACX_BEACON_FILTER_TABLE, + ie_table, sizeof(*ie_table)); + if (ret < 0) { + wl1251_warning("failed to set beacon filter table: %d", ret); + goto out; + } + +out: + kfree(ie_table); + return ret; +} + +int wl1251_acx_conn_monit_params(struct wl1251 *wl) +{ + struct acx_conn_monit_params *acx; + int ret; + + wl1251_debug(DEBUG_ACX, "acx connection monitor parameters"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->synch_fail_thold = SYNCH_FAIL_DEFAULT_THRESHOLD; + acx->bss_lose_timeout = NO_BEACON_DEFAULT_TIMEOUT; + + ret = wl1251_cmd_configure(wl, ACX_CONN_MONIT_PARAMS, + acx, sizeof(*acx)); + if (ret < 0) { + wl1251_warning("failed to set connection monitor " + "parameters: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1251_acx_sg_enable(struct wl1251 *wl) +{ + struct acx_bt_wlan_coex *pta; + int ret; + + wl1251_debug(DEBUG_ACX, "acx sg enable"); + + pta = kzalloc(sizeof(*pta), GFP_KERNEL); + if (!pta) { + ret = -ENOMEM; + goto out; + } + + pta->enable = SG_ENABLE; + + ret = wl1251_cmd_configure(wl, ACX_SG_ENABLE, pta, sizeof(*pta)); + if (ret < 0) { + wl1251_warning("failed to set softgemini enable: %d", ret); + goto out; + } + +out: + kfree(pta); + return ret; +} + +int wl1251_acx_sg_cfg(struct wl1251 *wl) +{ + struct acx_bt_wlan_coex_param *param; + int ret; + + wl1251_debug(DEBUG_ACX, "acx sg cfg"); + + param = kzalloc(sizeof(*param), GFP_KERNEL); + if (!param) { + ret = -ENOMEM; + goto out; + } + + /* BT-WLAN coext parameters */ + param->min_rate = RATE_INDEX_24MBPS; + param->bt_hp_max_time = PTA_BT_HP_MAXTIME_DEF; + param->wlan_hp_max_time = PTA_WLAN_HP_MAX_TIME_DEF; + param->sense_disable_timer = PTA_SENSE_DISABLE_TIMER_DEF; + param->rx_time_bt_hp = PTA_PROTECTIVE_RX_TIME_DEF; + param->tx_time_bt_hp = PTA_PROTECTIVE_TX_TIME_DEF; + param->rx_time_bt_hp_fast = PTA_PROTECTIVE_RX_TIME_FAST_DEF; + param->tx_time_bt_hp_fast = PTA_PROTECTIVE_TX_TIME_FAST_DEF; + param->wlan_cycle_fast = PTA_CYCLE_TIME_FAST_DEF; + param->bt_anti_starvation_period = PTA_ANTI_STARVE_PERIOD_DEF; + param->next_bt_lp_packet = PTA_TIMEOUT_NEXT_BT_LP_PACKET_DEF; + param->wake_up_beacon = PTA_TIME_BEFORE_BEACON_DEF; + param->hp_dm_max_guard_time = PTA_HPDM_MAX_TIME_DEF; + param->next_wlan_packet = PTA_TIME_OUT_NEXT_WLAN_DEF; + param->antenna_type = PTA_ANTENNA_TYPE_DEF; + param->signal_type = PTA_SIGNALING_TYPE_DEF; + param->afh_leverage_on = PTA_AFH_LEVERAGE_ON_DEF; + param->quiet_cycle_num = PTA_NUMBER_QUIET_CYCLE_DEF; + param->max_cts = PTA_MAX_NUM_CTS_DEF; + param->wlan_packets_num = PTA_NUMBER_OF_WLAN_PACKETS_DEF; + param->bt_packets_num = PTA_NUMBER_OF_BT_PACKETS_DEF; + param->missed_rx_avalanche = PTA_RX_FOR_AVALANCHE_DEF; + param->wlan_elp_hp = PTA_ELP_HP_DEF; + param->bt_anti_starvation_cycles = PTA_ANTI_STARVE_NUM_CYCLE_DEF; + param->ack_mode_dual_ant = PTA_ACK_MODE_DEF; + param->pa_sd_enable = PTA_ALLOW_PA_SD_DEF; + param->pta_auto_mode_enable = PTA_AUTO_MODE_NO_CTS_DEF; + param->bt_hp_respected_num = PTA_BT_HP_RESPECTED_DEF; + + ret = wl1251_cmd_configure(wl, ACX_SG_CFG, param, sizeof(*param)); + if (ret < 0) { + wl1251_warning("failed to set sg config: %d", ret); + goto out; + } + +out: + kfree(param); + return ret; +} + +int wl1251_acx_cca_threshold(struct wl1251 *wl) +{ + struct acx_energy_detection *detection; + int ret; + + wl1251_debug(DEBUG_ACX, "acx cca threshold"); + + detection = kzalloc(sizeof(*detection), GFP_KERNEL); + if (!detection) { + ret = -ENOMEM; + goto out; + } + + detection->rx_cca_threshold = CCA_THRSH_DISABLE_ENERGY_D; + detection->tx_energy_detection = 0; + + ret = wl1251_cmd_configure(wl, ACX_CCA_THRESHOLD, + detection, sizeof(*detection)); + if (ret < 0) + wl1251_warning("failed to set cca threshold: %d", ret); + +out: + kfree(detection); + return ret; +} + +int wl1251_acx_bcn_dtim_options(struct wl1251 *wl) +{ + struct acx_beacon_broadcast *bb; + int ret; + + wl1251_debug(DEBUG_ACX, "acx bcn dtim options"); + + bb = kzalloc(sizeof(*bb), GFP_KERNEL); + if (!bb) { + ret = -ENOMEM; + goto out; + } + + bb->beacon_rx_timeout = BCN_RX_TIMEOUT_DEF_VALUE; + bb->broadcast_timeout = BROADCAST_RX_TIMEOUT_DEF_VALUE; + bb->rx_broadcast_in_ps = RX_BROADCAST_IN_PS_DEF_VALUE; + bb->ps_poll_threshold = CONSECUTIVE_PS_POLL_FAILURE_DEF; + + ret = wl1251_cmd_configure(wl, ACX_BCN_DTIM_OPTIONS, bb, sizeof(*bb)); + if (ret < 0) { + wl1251_warning("failed to set rx config: %d", ret); + goto out; + } + +out: + kfree(bb); + return ret; +} + +int wl1251_acx_aid(struct wl1251 *wl, u16 aid) +{ + struct acx_aid *acx_aid; + int ret; + + wl1251_debug(DEBUG_ACX, "acx aid"); + + acx_aid = kzalloc(sizeof(*acx_aid), GFP_KERNEL); + if (!acx_aid) { + ret = -ENOMEM; + goto out; + } + + acx_aid->aid = aid; + + ret = wl1251_cmd_configure(wl, ACX_AID, acx_aid, sizeof(*acx_aid)); + if (ret < 0) { + wl1251_warning("failed to set aid: %d", ret); + goto out; + } + +out: + kfree(acx_aid); + return ret; +} + +int wl1251_acx_event_mbox_mask(struct wl1251 *wl, u32 event_mask) +{ + struct acx_event_mask *mask; + int ret; + + wl1251_debug(DEBUG_ACX, "acx event mbox mask"); + + mask = kzalloc(sizeof(*mask), GFP_KERNEL); + if (!mask) { + ret = -ENOMEM; + goto out; + } + + /* high event mask is unused */ + mask->high_event_mask = 0xffffffff; + + mask->event_mask = event_mask; + + ret = wl1251_cmd_configure(wl, ACX_EVENT_MBOX_MASK, + mask, sizeof(*mask)); + if (ret < 0) { + wl1251_warning("failed to set acx_event_mbox_mask: %d", ret); + goto out; + } + +out: + kfree(mask); + return ret; +} + +int wl1251_acx_low_rssi(struct wl1251 *wl, s8 threshold, u8 weight, + u8 depth, enum wl1251_acx_low_rssi_type type) +{ + struct acx_low_rssi *rssi; + int ret; + + wl1251_debug(DEBUG_ACX, "acx low rssi"); + + rssi = kzalloc(sizeof(*rssi), GFP_KERNEL); + if (!rssi) + return -ENOMEM; + + rssi->threshold = threshold; + rssi->weight = weight; + rssi->depth = depth; + rssi->type = type; + + ret = wl1251_cmd_configure(wl, ACX_LOW_RSSI, rssi, sizeof(*rssi)); + if (ret < 0) + wl1251_warning("failed to set low rssi threshold: %d", ret); + + kfree(rssi); + return ret; +} + +int wl1251_acx_set_preamble(struct wl1251 *wl, enum acx_preamble_type preamble) +{ + struct acx_preamble *acx; + int ret; + + wl1251_debug(DEBUG_ACX, "acx_set_preamble"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->preamble = preamble; + + ret = wl1251_cmd_configure(wl, ACX_PREAMBLE_TYPE, acx, sizeof(*acx)); + if (ret < 0) { + wl1251_warning("Setting of preamble failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1251_acx_cts_protect(struct wl1251 *wl, + enum acx_ctsprotect_type ctsprotect) +{ + struct acx_ctsprotect *acx; + int ret; + + wl1251_debug(DEBUG_ACX, "acx_set_ctsprotect"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->ctsprotect = ctsprotect; + + ret = wl1251_cmd_configure(wl, ACX_CTS_PROTECTION, acx, sizeof(*acx)); + if (ret < 0) { + wl1251_warning("Setting of ctsprotect failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1251_acx_tsf_info(struct wl1251 *wl, u64 *mactime) +{ + struct acx_tsf_info *tsf_info; + int ret; + + tsf_info = kzalloc(sizeof(*tsf_info), GFP_KERNEL); + if (!tsf_info) { + ret = -ENOMEM; + goto out; + } + + ret = wl1251_cmd_interrogate(wl, ACX_TSF_INFO, + tsf_info, sizeof(*tsf_info)); + if (ret < 0) { + wl1251_warning("ACX_FW_REV interrogate failed"); + goto out; + } + + *mactime = tsf_info->current_tsf_lsb | + ((u64)tsf_info->current_tsf_msb << 32); + +out: + kfree(tsf_info); + return ret; +} + +int wl1251_acx_statistics(struct wl1251 *wl, struct acx_statistics *stats) +{ + int ret; + + wl1251_debug(DEBUG_ACX, "acx statistics"); + + ret = wl1251_cmd_interrogate(wl, ACX_STATISTICS, stats, + sizeof(*stats)); + if (ret < 0) { + wl1251_warning("acx statistics failed: %d", ret); + return -ENOMEM; + } + + return 0; +} + +int wl1251_acx_rate_policies(struct wl1251 *wl) +{ + struct acx_rate_policy *acx; + int ret = 0; + + wl1251_debug(DEBUG_ACX, "acx rate policies"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + + if (!acx) { + ret = -ENOMEM; + goto out; + } + + /* configure one default (one-size-fits-all) rate class */ + acx->rate_class_cnt = 1; + acx->rate_class[0].enabled_rates = ACX_RATE_MASK_UNSPECIFIED; + acx->rate_class[0].short_retry_limit = ACX_RATE_RETRY_LIMIT; + acx->rate_class[0].long_retry_limit = ACX_RATE_RETRY_LIMIT; + acx->rate_class[0].aflags = 0; + + ret = wl1251_cmd_configure(wl, ACX_RATE_POLICY, acx, sizeof(*acx)); + if (ret < 0) { + wl1251_warning("Setting of rate policies failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1251_acx_mem_cfg(struct wl1251 *wl) +{ + struct wl1251_acx_config_memory *mem_conf; + int ret, i; + + wl1251_debug(DEBUG_ACX, "acx mem cfg"); + + mem_conf = kzalloc(sizeof(*mem_conf), GFP_KERNEL); + if (!mem_conf) { + ret = -ENOMEM; + goto out; + } + + /* memory config */ + mem_conf->mem_config.num_stations = cpu_to_le16(DEFAULT_NUM_STATIONS); + mem_conf->mem_config.rx_mem_block_num = 35; + mem_conf->mem_config.tx_min_mem_block_num = 64; + mem_conf->mem_config.num_tx_queues = MAX_TX_QUEUES; + mem_conf->mem_config.host_if_options = HOSTIF_PKT_RING; + mem_conf->mem_config.num_ssid_profiles = 1; + mem_conf->mem_config.debug_buffer_size = + cpu_to_le16(TRACE_BUFFER_MAX_SIZE); + + /* RX queue config */ + mem_conf->rx_queue_config.dma_address = 0; + mem_conf->rx_queue_config.num_descs = ACX_RX_DESC_DEF; + mem_conf->rx_queue_config.priority = DEFAULT_RXQ_PRIORITY; + mem_conf->rx_queue_config.type = DEFAULT_RXQ_TYPE; + + /* TX queue config */ + for (i = 0; i < MAX_TX_QUEUES; i++) { + mem_conf->tx_queue_config[i].num_descs = ACX_TX_DESC_DEF; + mem_conf->tx_queue_config[i].attributes = i; + } + + ret = wl1251_cmd_configure(wl, ACX_MEM_CFG, mem_conf, + sizeof(*mem_conf)); + if (ret < 0) { + wl1251_warning("wl1251 mem config failed: %d", ret); + goto out; + } + +out: + kfree(mem_conf); + return ret; +} + +int wl1251_acx_wr_tbtt_and_dtim(struct wl1251 *wl, u16 tbtt, u8 dtim) +{ + struct wl1251_acx_wr_tbtt_and_dtim *acx; + int ret; + + wl1251_debug(DEBUG_ACX, "acx tbtt and dtim"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->tbtt = tbtt; + acx->dtim = dtim; + + ret = wl1251_cmd_configure(wl, ACX_WR_TBTT_AND_DTIM, + acx, sizeof(*acx)); + if (ret < 0) { + wl1251_warning("failed to set tbtt and dtim: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1251_acx_bet_enable(struct wl1251 *wl, enum wl1251_acx_bet_mode mode, + u8 max_consecutive) +{ + struct wl1251_acx_bet_enable *acx; + int ret; + + wl1251_debug(DEBUG_ACX, "acx bet enable"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->enable = mode; + acx->max_consecutive = max_consecutive; + + ret = wl1251_cmd_configure(wl, ACX_BET_ENABLE, acx, sizeof(*acx)); + if (ret < 0) { + wl1251_warning("wl1251 acx bet enable failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1251_acx_ac_cfg(struct wl1251 *wl, u8 ac, u8 cw_min, u16 cw_max, + u8 aifs, u16 txop) +{ + struct wl1251_acx_ac_cfg *acx; + int ret = 0; + + wl1251_debug(DEBUG_ACX, "acx ac cfg %d cw_ming %d cw_max %d " + "aifs %d txop %d", ac, cw_min, cw_max, aifs, txop); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->ac = ac; + acx->cw_min = cw_min; + acx->cw_max = cw_max; + acx->aifsn = aifs; + acx->txop_limit = txop; + + ret = wl1251_cmd_configure(wl, ACX_AC_CFG, acx, sizeof(*acx)); + if (ret < 0) { + wl1251_warning("acx ac cfg failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1251_acx_tid_cfg(struct wl1251 *wl, u8 queue, + enum wl1251_acx_channel_type type, + u8 tsid, enum wl1251_acx_ps_scheme ps_scheme, + enum wl1251_acx_ack_policy ack_policy) +{ + struct wl1251_acx_tid_cfg *acx; + int ret = 0; + + wl1251_debug(DEBUG_ACX, "acx tid cfg %d type %d tsid %d " + "ps_scheme %d ack_policy %d", queue, type, tsid, + ps_scheme, ack_policy); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->queue = queue; + acx->type = type; + acx->tsid = tsid; + acx->ps_scheme = ps_scheme; + acx->ack_policy = ack_policy; + + ret = wl1251_cmd_configure(wl, ACX_TID_CFG, acx, sizeof(*acx)); + if (ret < 0) { + wl1251_warning("acx tid cfg failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} diff --git a/drivers/net/wireless/ti/wl1251/acx.h b/drivers/net/wireless/ti/wl1251/acx.h new file mode 100644 index 0000000..c2ba100 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/acx.h @@ -0,0 +1,1483 @@ +/* + * This file is part of wl1251 + * + * Copyright (c) 1998-2007 Texas Instruments Incorporated + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL1251_ACX_H__ +#define __WL1251_ACX_H__ + +#include "wl1251.h" +#include "cmd.h" + +/* Target's information element */ +struct acx_header { + struct wl1251_cmd_header cmd; + + /* acx (or information element) header */ + u16 id; + + /* payload length (not including headers */ + u16 len; +} __packed; + +struct acx_error_counter { + struct acx_header header; + + /* The number of PLCP errors since the last time this */ + /* information element was interrogated. This field is */ + /* automatically cleared when it is interrogated.*/ + u32 PLCP_error; + + /* The number of FCS errors since the last time this */ + /* information element was interrogated. This field is */ + /* automatically cleared when it is interrogated.*/ + u32 FCS_error; + + /* The number of MPDUs without PLCP header errors received*/ + /* since the last time this information element was interrogated. */ + /* This field is automatically cleared when it is interrogated.*/ + u32 valid_frame; + + /* the number of missed sequence numbers in the squentially */ + /* values of frames seq numbers */ + u32 seq_num_miss; +} __packed; + +struct acx_revision { + struct acx_header header; + + /* + * The WiLink firmware version, an ASCII string x.x.x.x, + * that uniquely identifies the current firmware. + * The left most digit is incremented each time a + * significant change is made to the firmware, such as + * code redesign or new platform support. + * The second digit is incremented when major enhancements + * are added or major fixes are made. + * The third digit is incremented for each GA release. + * The fourth digit is incremented for each build. + * The first two digits identify a firmware release version, + * in other words, a unique set of features. + * The first three digits identify a GA release. + */ + char fw_version[20]; + + /* + * This 4 byte field specifies the WiLink hardware version. + * bits 0 - 15: Reserved. + * bits 16 - 23: Version ID - The WiLink version ID + * (1 = first spin, 2 = second spin, and so on). + * bits 24 - 31: Chip ID - The WiLink chip ID. + */ + u32 hw_version; +} __packed; + +enum wl1251_psm_mode { + /* Active mode */ + WL1251_PSM_CAM = 0, + + /* Power save mode */ + WL1251_PSM_PS = 1, + + /* Extreme low power */ + WL1251_PSM_ELP = 2, +}; + +struct acx_sleep_auth { + struct acx_header header; + + /* The sleep level authorization of the device. */ + /* 0 - Always active*/ + /* 1 - Power down mode: light / fast sleep*/ + /* 2 - ELP mode: Deep / Max sleep*/ + u8 sleep_auth; + u8 padding[3]; +} __packed; + +enum { + HOSTIF_PCI_MASTER_HOST_INDIRECT, + HOSTIF_PCI_MASTER_HOST_DIRECT, + HOSTIF_SLAVE, + HOSTIF_PKT_RING, + HOSTIF_DONTCARE = 0xFF +}; + +#define DEFAULT_UCAST_PRIORITY 0 +#define DEFAULT_RX_Q_PRIORITY 0 +#define DEFAULT_NUM_STATIONS 1 +#define DEFAULT_RXQ_PRIORITY 0 /* low 0 .. 15 high */ +#define DEFAULT_RXQ_TYPE 0x07 /* All frames, Data/Ctrl/Mgmt */ +#define TRACE_BUFFER_MAX_SIZE 256 + +#define DP_RX_PACKET_RING_CHUNK_SIZE 1600 +#define DP_TX_PACKET_RING_CHUNK_SIZE 1600 +#define DP_RX_PACKET_RING_CHUNK_NUM 2 +#define DP_TX_PACKET_RING_CHUNK_NUM 2 +#define DP_TX_COMPLETE_TIME_OUT 20 +#define FW_TX_CMPLT_BLOCK_SIZE 16 + +struct acx_data_path_params { + struct acx_header header; + + u16 rx_packet_ring_chunk_size; + u16 tx_packet_ring_chunk_size; + + u8 rx_packet_ring_chunk_num; + u8 tx_packet_ring_chunk_num; + + /* + * Maximum number of packets that can be gathered + * in the TX complete ring before an interrupt + * is generated. + */ + u8 tx_complete_threshold; + + /* Number of pending TX complete entries in cyclic ring.*/ + u8 tx_complete_ring_depth; + + /* + * Max num microseconds since a packet enters the TX + * complete ring until an interrupt is generated. + */ + u32 tx_complete_timeout; +} __packed; + + +struct acx_data_path_params_resp { + struct acx_header header; + + u16 rx_packet_ring_chunk_size; + u16 tx_packet_ring_chunk_size; + + u8 rx_packet_ring_chunk_num; + u8 tx_packet_ring_chunk_num; + + u8 pad[2]; + + u32 rx_packet_ring_addr; + u32 tx_packet_ring_addr; + + u32 rx_control_addr; + u32 tx_control_addr; + + u32 tx_complete_addr; +} __packed; + +#define TX_MSDU_LIFETIME_MIN 0 +#define TX_MSDU_LIFETIME_MAX 3000 +#define TX_MSDU_LIFETIME_DEF 512 +#define RX_MSDU_LIFETIME_MIN 0 +#define RX_MSDU_LIFETIME_MAX 0xFFFFFFFF +#define RX_MSDU_LIFETIME_DEF 512000 + +struct acx_rx_msdu_lifetime { + struct acx_header header; + + /* + * The maximum amount of time, in TU, before the + * firmware discards the MSDU. + */ + u32 lifetime; +} __packed; + +/* + * RX Config Options Table + * Bit Definition + * === ========== + * 31:14 Reserved + * 13 Copy RX Status - when set, write three receive status words + * to top of rx'd MPDUs. + * When cleared, do not write three status words (added rev 1.5) + * 12 Reserved + * 11 RX Complete upon FCS error - when set, give rx complete + * interrupt for FCS errors, after the rx filtering, e.g. unicast + * frames not to us with FCS error will not generate an interrupt. + * 10 SSID Filter Enable - When set, the WiLink discards all beacon, + * probe request, and probe response frames with an SSID that does + * not match the SSID specified by the host in the START/JOIN + * command. + * When clear, the WiLink receives frames with any SSID. + * 9 Broadcast Filter Enable - When set, the WiLink discards all + * broadcast frames. When clear, the WiLink receives all received + * broadcast frames. + * 8:6 Reserved + * 5 BSSID Filter Enable - When set, the WiLink discards any frames + * with a BSSID that does not match the BSSID specified by the + * host. + * When clear, the WiLink receives frames from any BSSID. + * 4 MAC Addr Filter - When set, the WiLink discards any frames + * with a destination address that does not match the MAC address + * of the adaptor. + * When clear, the WiLink receives frames destined to any MAC + * address. + * 3 Promiscuous - When set, the WiLink receives all valid frames + * (i.e., all frames that pass the FCS check). + * When clear, only frames that pass the other filters specified + * are received. + * 2 FCS - When set, the WiLink includes the FCS with the received + * frame. + * When cleared, the FCS is discarded. + * 1 PLCP header - When set, write all data from baseband to frame + * buffer including PHY header. + * 0 Reserved - Always equal to 0. + * + * RX Filter Options Table + * Bit Definition + * === ========== + * 31:12 Reserved - Always equal to 0. + * 11 Association - When set, the WiLink receives all association + * related frames (association request/response, reassocation + * request/response, and disassociation). When clear, these frames + * are discarded. + * 10 Auth/De auth - When set, the WiLink receives all authentication + * and de-authentication frames. When clear, these frames are + * discarded. + * 9 Beacon - When set, the WiLink receives all beacon frames. + * When clear, these frames are discarded. + * 8 Contention Free - When set, the WiLink receives all contention + * free frames. + * When clear, these frames are discarded. + * 7 Control - When set, the WiLink receives all control frames. + * When clear, these frames are discarded. + * 6 Data - When set, the WiLink receives all data frames. + * When clear, these frames are discarded. + * 5 FCS Error - When set, the WiLink receives frames that have FCS + * errors. + * When clear, these frames are discarded. + * 4 Management - When set, the WiLink receives all management + * frames. + * When clear, these frames are discarded. + * 3 Probe Request - When set, the WiLink receives all probe request + * frames. + * When clear, these frames are discarded. + * 2 Probe Response - When set, the WiLink receives all probe + * response frames. + * When clear, these frames are discarded. + * 1 RTS/CTS/ACK - When set, the WiLink receives all RTS, CTS and ACK + * frames. + * When clear, these frames are discarded. + * 0 Rsvd Type/Sub Type - When set, the WiLink receives all frames + * that have reserved frame types and sub types as defined by the + * 802.11 specification. + * When clear, these frames are discarded. + */ +struct acx_rx_config { + struct acx_header header; + + u32 config_options; + u32 filter_options; +} __packed; + +enum { + QOS_AC_BE = 0, + QOS_AC_BK, + QOS_AC_VI, + QOS_AC_VO, + QOS_HIGHEST_AC_INDEX = QOS_AC_VO, +}; + +#define MAX_NUM_OF_AC (QOS_HIGHEST_AC_INDEX+1) +#define FIRST_AC_INDEX QOS_AC_BE +#define MAX_NUM_OF_802_1d_TAGS 8 +#define AC_PARAMS_MAX_TSID 15 +#define MAX_APSD_CONF 0xffff + +#define QOS_TX_HIGH_MIN (0) +#define QOS_TX_HIGH_MAX (100) + +#define QOS_TX_HIGH_BK_DEF (25) +#define QOS_TX_HIGH_BE_DEF (35) +#define QOS_TX_HIGH_VI_DEF (35) +#define QOS_TX_HIGH_VO_DEF (35) + +#define QOS_TX_LOW_BK_DEF (15) +#define QOS_TX_LOW_BE_DEF (25) +#define QOS_TX_LOW_VI_DEF (25) +#define QOS_TX_LOW_VO_DEF (25) + +struct acx_tx_queue_qos_config { + struct acx_header header; + + u8 qid; + u8 pad[3]; + + /* Max number of blocks allowd in the queue */ + u16 high_threshold; + + /* Lowest memory blocks guaranteed for this queue */ + u16 low_threshold; +} __packed; + +struct acx_packet_detection { + struct acx_header header; + + u32 threshold; +} __packed; + + +enum acx_slot_type { + SLOT_TIME_LONG = 0, + SLOT_TIME_SHORT = 1, + DEFAULT_SLOT_TIME = SLOT_TIME_SHORT, + MAX_SLOT_TIMES = 0xFF +}; + +#define STATION_WONE_INDEX 0 + +struct acx_slot { + struct acx_header header; + + u8 wone_index; /* Reserved */ + u8 slot_time; + u8 reserved[6]; +} __packed; + + +#define ADDRESS_GROUP_MAX (8) +#define ADDRESS_GROUP_MAX_LEN (ETH_ALEN * ADDRESS_GROUP_MAX) + +struct acx_dot11_grp_addr_tbl { + struct acx_header header; + + u8 enabled; + u8 num_groups; + u8 pad[2]; + u8 mac_table[ADDRESS_GROUP_MAX_LEN]; +} __packed; + + +#define RX_TIMEOUT_PS_POLL_MIN 0 +#define RX_TIMEOUT_PS_POLL_MAX (200000) +#define RX_TIMEOUT_PS_POLL_DEF (15) +#define RX_TIMEOUT_UPSD_MIN 0 +#define RX_TIMEOUT_UPSD_MAX (200000) +#define RX_TIMEOUT_UPSD_DEF (15) + +struct acx_rx_timeout { + struct acx_header header; + + /* + * The longest time the STA will wait to receive + * traffic from the AP after a PS-poll has been + * transmitted. + */ + u16 ps_poll_timeout; + + /* + * The longest time the STA will wait to receive + * traffic from the AP after a frame has been sent + * from an UPSD enabled queue. + */ + u16 upsd_timeout; +} __packed; + +#define RTS_THRESHOLD_MIN 0 +#define RTS_THRESHOLD_MAX 4096 +#define RTS_THRESHOLD_DEF 2347 + +struct acx_rts_threshold { + struct acx_header header; + + u16 threshold; + u8 pad[2]; +} __packed; + +enum wl1251_acx_low_rssi_type { + /* + * The event is a "Level" indication which keeps triggering + * as long as the average RSSI is below the threshold. + */ + WL1251_ACX_LOW_RSSI_TYPE_LEVEL = 0, + + /* + * The event is an "Edge" indication which triggers + * only when the RSSI threshold is crossed from above. + */ + WL1251_ACX_LOW_RSSI_TYPE_EDGE = 1, +}; + +struct acx_low_rssi { + struct acx_header header; + + /* + * The threshold (in dBm) below (or above after low rssi + * indication) which the firmware generates an interrupt to the + * host. This parameter is signed. + */ + s8 threshold; + + /* + * The weight of the current RSSI sample, before adding the new + * sample, that is used to calculate the average RSSI. + */ + u8 weight; + + /* + * The number of Beacons/Probe response frames that will be + * received before issuing the Low or Regained RSSI event. + */ + u8 depth; + + /* + * Configures how the Low RSSI Event is triggered. Refer to + * enum wl1251_acx_low_rssi_type for more. + */ + u8 type; +} __packed; + +struct acx_beacon_filter_option { + struct acx_header header; + + u8 enable; + + /* + * The number of beacons without the unicast TIM + * bit set that the firmware buffers before + * signaling the host about ready frames. + * When set to 0 and the filter is enabled, beacons + * without the unicast TIM bit set are dropped. + */ + u8 max_num_beacons; + u8 pad[2]; +} __packed; + +/* + * ACXBeaconFilterEntry (not 221) + * Byte Offset Size (Bytes) Definition + * =========== ============ ========== + * 0 1 IE identifier + * 1 1 Treatment bit mask + * + * ACXBeaconFilterEntry (221) + * Byte Offset Size (Bytes) Definition + * =========== ============ ========== + * 0 1 IE identifier + * 1 1 Treatment bit mask + * 2 3 OUI + * 5 1 Type + * 6 2 Version + * + * + * Treatment bit mask - The information element handling: + * bit 0 - The information element is compared and transferred + * in case of change. + * bit 1 - The information element is transferred to the host + * with each appearance or disappearance. + * Note that both bits can be set at the same time. + */ +#define BEACON_FILTER_TABLE_MAX_IE_NUM (32) +#define BEACON_FILTER_TABLE_MAX_VENDOR_SPECIFIC_IE_NUM (6) +#define BEACON_FILTER_TABLE_IE_ENTRY_SIZE (2) +#define BEACON_FILTER_TABLE_EXTRA_VENDOR_SPECIFIC_IE_SIZE (6) +#define BEACON_FILTER_TABLE_MAX_SIZE ((BEACON_FILTER_TABLE_MAX_IE_NUM * \ + BEACON_FILTER_TABLE_IE_ENTRY_SIZE) + \ + (BEACON_FILTER_TABLE_MAX_VENDOR_SPECIFIC_IE_NUM * \ + BEACON_FILTER_TABLE_EXTRA_VENDOR_SPECIFIC_IE_SIZE)) + +#define BEACON_RULE_PASS_ON_CHANGE BIT(0) +#define BEACON_RULE_PASS_ON_APPEARANCE BIT(1) + +#define BEACON_FILTER_IE_ID_CHANNEL_SWITCH_ANN (37) + +struct acx_beacon_filter_ie_table { + struct acx_header header; + + u8 num_ie; + u8 pad[3]; + u8 table[BEACON_FILTER_TABLE_MAX_SIZE]; +} __packed; + +#define SYNCH_FAIL_DEFAULT_THRESHOLD 10 /* number of beacons */ +#define NO_BEACON_DEFAULT_TIMEOUT (500) /* in microseconds */ + +struct acx_conn_monit_params { + struct acx_header header; + + u32 synch_fail_thold; /* number of beacons missed */ + u32 bss_lose_timeout; /* number of TU's from synch fail */ +} __packed; + +enum { + SG_ENABLE = 0, + SG_DISABLE, + SG_SENSE_NO_ACTIVITY, + SG_SENSE_ACTIVE +}; + +struct acx_bt_wlan_coex { + struct acx_header header; + + /* + * 0 -> PTA enabled + * 1 -> PTA disabled + * 2 -> sense no active mode, i.e. + * an interrupt is sent upon + * BT activity. + * 3 -> PTA is switched on in response + * to the interrupt sending. + */ + u8 enable; + u8 pad[3]; +} __packed; + +#define PTA_ANTENNA_TYPE_DEF (0) +#define PTA_BT_HP_MAXTIME_DEF (2000) +#define PTA_WLAN_HP_MAX_TIME_DEF (5000) +#define PTA_SENSE_DISABLE_TIMER_DEF (1350) +#define PTA_PROTECTIVE_RX_TIME_DEF (1500) +#define PTA_PROTECTIVE_TX_TIME_DEF (1500) +#define PTA_TIMEOUT_NEXT_BT_LP_PACKET_DEF (3000) +#define PTA_SIGNALING_TYPE_DEF (1) +#define PTA_AFH_LEVERAGE_ON_DEF (0) +#define PTA_NUMBER_QUIET_CYCLE_DEF (0) +#define PTA_MAX_NUM_CTS_DEF (3) +#define PTA_NUMBER_OF_WLAN_PACKETS_DEF (2) +#define PTA_NUMBER_OF_BT_PACKETS_DEF (2) +#define PTA_PROTECTIVE_RX_TIME_FAST_DEF (1500) +#define PTA_PROTECTIVE_TX_TIME_FAST_DEF (3000) +#define PTA_CYCLE_TIME_FAST_DEF (8700) +#define PTA_RX_FOR_AVALANCHE_DEF (5) +#define PTA_ELP_HP_DEF (0) +#define PTA_ANTI_STARVE_PERIOD_DEF (500) +#define PTA_ANTI_STARVE_NUM_CYCLE_DEF (4) +#define PTA_ALLOW_PA_SD_DEF (1) +#define PTA_TIME_BEFORE_BEACON_DEF (6300) +#define PTA_HPDM_MAX_TIME_DEF (1600) +#define PTA_TIME_OUT_NEXT_WLAN_DEF (2550) +#define PTA_AUTO_MODE_NO_CTS_DEF (0) +#define PTA_BT_HP_RESPECTED_DEF (3) +#define PTA_WLAN_RX_MIN_RATE_DEF (24) +#define PTA_ACK_MODE_DEF (1) + +struct acx_bt_wlan_coex_param { + struct acx_header header; + + /* + * The minimum rate of a received WLAN packet in the STA, + * during protective mode, of which a new BT-HP request + * during this Rx will always be respected and gain the antenna. + */ + u32 min_rate; + + /* Max time the BT HP will be respected. */ + u16 bt_hp_max_time; + + /* Max time the WLAN HP will be respected. */ + u16 wlan_hp_max_time; + + /* + * The time between the last BT activity + * and the moment when the sense mode returns + * to SENSE_INACTIVE. + */ + u16 sense_disable_timer; + + /* Time before the next BT HP instance */ + u16 rx_time_bt_hp; + u16 tx_time_bt_hp; + + /* range: 10-20000 default: 1500 */ + u16 rx_time_bt_hp_fast; + u16 tx_time_bt_hp_fast; + + /* range: 2000-65535 default: 8700 */ + u16 wlan_cycle_fast; + + /* range: 0 - 15000 (Msec) default: 1000 */ + u16 bt_anti_starvation_period; + + /* range 400-10000(Usec) default: 3000 */ + u16 next_bt_lp_packet; + + /* Deafult: worst case for BT DH5 traffic */ + u16 wake_up_beacon; + + /* range: 0-50000(Usec) default: 1050 */ + u16 hp_dm_max_guard_time; + + /* + * This is to prevent both BT & WLAN antenna + * starvation. + * Range: 100-50000(Usec) default:2550 + */ + u16 next_wlan_packet; + + /* 0 -> shared antenna */ + u8 antenna_type; + + /* + * 0 -> TI legacy + * 1 -> Palau + */ + u8 signal_type; + + /* + * BT AFH status + * 0 -> no AFH + * 1 -> from dedicated GPIO + * 2 -> AFH on (from host) + */ + u8 afh_leverage_on; + + /* + * The number of cycles during which no + * TX will be sent after 1 cycle of RX + * transaction in protective mode + */ + u8 quiet_cycle_num; + + /* + * The maximum number of CTSs that will + * be sent for receiving RX packet in + * protective mode + */ + u8 max_cts; + + /* + * The number of WLAN packets + * transferred in common mode before + * switching to BT. + */ + u8 wlan_packets_num; + + /* + * The number of BT packets + * transferred in common mode before + * switching to WLAN. + */ + u8 bt_packets_num; + + /* range: 1-255 default: 5 */ + u8 missed_rx_avalanche; + + /* range: 0-1 default: 1 */ + u8 wlan_elp_hp; + + /* range: 0 - 15 default: 4 */ + u8 bt_anti_starvation_cycles; + + u8 ack_mode_dual_ant; + + /* + * Allow PA_SD assertion/de-assertion + * during enabled BT activity. + */ + u8 pa_sd_enable; + + /* + * Enable/Disable PTA in auto mode: + * Support Both Active & P.S modes + */ + u8 pta_auto_mode_enable; + + /* range: 0 - 20 default: 1 */ + u8 bt_hp_respected_num; +} __packed; + +#define CCA_THRSH_ENABLE_ENERGY_D 0x140A +#define CCA_THRSH_DISABLE_ENERGY_D 0xFFEF + +struct acx_energy_detection { + struct acx_header header; + + /* The RX Clear Channel Assessment threshold in the PHY */ + u16 rx_cca_threshold; + u8 tx_energy_detection; + u8 pad; +} __packed; + +#define BCN_RX_TIMEOUT_DEF_VALUE 10000 +#define BROADCAST_RX_TIMEOUT_DEF_VALUE 20000 +#define RX_BROADCAST_IN_PS_DEF_VALUE 1 +#define CONSECUTIVE_PS_POLL_FAILURE_DEF 4 + +struct acx_beacon_broadcast { + struct acx_header header; + + u16 beacon_rx_timeout; + u16 broadcast_timeout; + + /* Enables receiving of broadcast packets in PS mode */ + u8 rx_broadcast_in_ps; + + /* Consecutive PS Poll failures before updating the host */ + u8 ps_poll_threshold; + u8 pad[2]; +} __packed; + +struct acx_event_mask { + struct acx_header header; + + u32 event_mask; + u32 high_event_mask; /* Unused */ +} __packed; + +#define CFG_RX_FCS BIT(2) +#define CFG_RX_ALL_GOOD BIT(3) +#define CFG_UNI_FILTER_EN BIT(4) +#define CFG_BSSID_FILTER_EN BIT(5) +#define CFG_MC_FILTER_EN BIT(6) +#define CFG_MC_ADDR0_EN BIT(7) +#define CFG_MC_ADDR1_EN BIT(8) +#define CFG_BC_REJECT_EN BIT(9) +#define CFG_SSID_FILTER_EN BIT(10) +#define CFG_RX_INT_FCS_ERROR BIT(11) +#define CFG_RX_INT_ENCRYPTED BIT(12) +#define CFG_RX_WR_RX_STATUS BIT(13) +#define CFG_RX_FILTER_NULTI BIT(14) +#define CFG_RX_RESERVE BIT(15) +#define CFG_RX_TIMESTAMP_TSF BIT(16) + +#define CFG_RX_RSV_EN BIT(0) +#define CFG_RX_RCTS_ACK BIT(1) +#define CFG_RX_PRSP_EN BIT(2) +#define CFG_RX_PREQ_EN BIT(3) +#define CFG_RX_MGMT_EN BIT(4) +#define CFG_RX_FCS_ERROR BIT(5) +#define CFG_RX_DATA_EN BIT(6) +#define CFG_RX_CTL_EN BIT(7) +#define CFG_RX_CF_EN BIT(8) +#define CFG_RX_BCN_EN BIT(9) +#define CFG_RX_AUTH_EN BIT(10) +#define CFG_RX_ASSOC_EN BIT(11) + +#define SCAN_PASSIVE BIT(0) +#define SCAN_5GHZ_BAND BIT(1) +#define SCAN_TRIGGERED BIT(2) +#define SCAN_PRIORITY_HIGH BIT(3) + +struct acx_fw_gen_frame_rates { + struct acx_header header; + + u8 tx_ctrl_frame_rate; /* RATE_* */ + u8 tx_ctrl_frame_mod; /* CCK_* or PBCC_* */ + u8 tx_mgt_frame_rate; + u8 tx_mgt_frame_mod; +} __packed; + +/* STA MAC */ +struct acx_dot11_station_id { + struct acx_header header; + + u8 mac[ETH_ALEN]; + u8 pad[2]; +} __packed; + +struct acx_feature_config { + struct acx_header header; + + u32 options; + u32 data_flow_options; +} __packed; + +struct acx_current_tx_power { + struct acx_header header; + + u8 current_tx_power; + u8 padding[3]; +} __packed; + +struct acx_dot11_default_key { + struct acx_header header; + + u8 id; + u8 pad[3]; +} __packed; + +struct acx_tsf_info { + struct acx_header header; + + u32 current_tsf_msb; + u32 current_tsf_lsb; + u32 last_TBTT_msb; + u32 last_TBTT_lsb; + u8 last_dtim_count; + u8 pad[3]; +} __packed; + +enum acx_wake_up_event { + WAKE_UP_EVENT_BEACON_BITMAP = 0x01, /* Wake on every Beacon*/ + WAKE_UP_EVENT_DTIM_BITMAP = 0x02, /* Wake on every DTIM*/ + WAKE_UP_EVENT_N_DTIM_BITMAP = 0x04, /* Wake on every Nth DTIM */ + WAKE_UP_EVENT_N_BEACONS_BITMAP = 0x08, /* Wake on every Nth Beacon */ + WAKE_UP_EVENT_BITS_MASK = 0x0F +}; + +struct acx_wake_up_condition { + struct acx_header header; + + u8 wake_up_event; /* Only one bit can be set */ + u8 listen_interval; + u8 pad[2]; +} __packed; + +struct acx_aid { + struct acx_header header; + + /* + * To be set when associated with an AP. + */ + u16 aid; + u8 pad[2]; +} __packed; + +enum acx_preamble_type { + ACX_PREAMBLE_LONG = 0, + ACX_PREAMBLE_SHORT = 1 +}; + +struct acx_preamble { + struct acx_header header; + + /* + * When set, the WiLink transmits the frames with a short preamble and + * when cleared, the WiLink transmits the frames with a long preamble. + */ + u8 preamble; + u8 padding[3]; +} __packed; + +enum acx_ctsprotect_type { + CTSPROTECT_DISABLE = 0, + CTSPROTECT_ENABLE = 1 +}; + +struct acx_ctsprotect { + struct acx_header header; + u8 ctsprotect; + u8 padding[3]; +} __packed; + +struct acx_tx_statistics { + u32 internal_desc_overflow; +} __packed; + +struct acx_rx_statistics { + u32 out_of_mem; + u32 hdr_overflow; + u32 hw_stuck; + u32 dropped; + u32 fcs_err; + u32 xfr_hint_trig; + u32 path_reset; + u32 reset_counter; +} __packed; + +struct acx_dma_statistics { + u32 rx_requested; + u32 rx_errors; + u32 tx_requested; + u32 tx_errors; +} __packed; + +struct acx_isr_statistics { + /* host command complete */ + u32 cmd_cmplt; + + /* fiqisr() */ + u32 fiqs; + + /* (INT_STS_ND & INT_TRIG_RX_HEADER) */ + u32 rx_headers; + + /* (INT_STS_ND & INT_TRIG_RX_CMPLT) */ + u32 rx_completes; + + /* (INT_STS_ND & INT_TRIG_NO_RX_BUF) */ + u32 rx_mem_overflow; + + /* (INT_STS_ND & INT_TRIG_S_RX_RDY) */ + u32 rx_rdys; + + /* irqisr() */ + u32 irqs; + + /* (INT_STS_ND & INT_TRIG_TX_PROC) */ + u32 tx_procs; + + /* (INT_STS_ND & INT_TRIG_DECRYPT_DONE) */ + u32 decrypt_done; + + /* (INT_STS_ND & INT_TRIG_DMA0) */ + u32 dma0_done; + + /* (INT_STS_ND & INT_TRIG_DMA1) */ + u32 dma1_done; + + /* (INT_STS_ND & INT_TRIG_TX_EXC_CMPLT) */ + u32 tx_exch_complete; + + /* (INT_STS_ND & INT_TRIG_COMMAND) */ + u32 commands; + + /* (INT_STS_ND & INT_TRIG_RX_PROC) */ + u32 rx_procs; + + /* (INT_STS_ND & INT_TRIG_PM_802) */ + u32 hw_pm_mode_changes; + + /* (INT_STS_ND & INT_TRIG_ACKNOWLEDGE) */ + u32 host_acknowledges; + + /* (INT_STS_ND & INT_TRIG_PM_PCI) */ + u32 pci_pm; + + /* (INT_STS_ND & INT_TRIG_ACM_WAKEUP) */ + u32 wakeups; + + /* (INT_STS_ND & INT_TRIG_LOW_RSSI) */ + u32 low_rssi; +} __packed; + +struct acx_wep_statistics { + /* WEP address keys configured */ + u32 addr_key_count; + + /* default keys configured */ + u32 default_key_count; + + u32 reserved; + + /* number of times that WEP key not found on lookup */ + u32 key_not_found; + + /* number of times that WEP key decryption failed */ + u32 decrypt_fail; + + /* WEP packets decrypted */ + u32 packets; + + /* WEP decrypt interrupts */ + u32 interrupt; +} __packed; + +#define ACX_MISSED_BEACONS_SPREAD 10 + +struct acx_pwr_statistics { + /* the amount of enters into power save mode (both PD & ELP) */ + u32 ps_enter; + + /* the amount of enters into ELP mode */ + u32 elp_enter; + + /* the amount of missing beacon interrupts to the host */ + u32 missing_bcns; + + /* the amount of wake on host-access times */ + u32 wake_on_host; + + /* the amount of wake on timer-expire */ + u32 wake_on_timer_exp; + + /* the number of packets that were transmitted with PS bit set */ + u32 tx_with_ps; + + /* the number of packets that were transmitted with PS bit clear */ + u32 tx_without_ps; + + /* the number of received beacons */ + u32 rcvd_beacons; + + /* the number of entering into PowerOn (power save off) */ + u32 power_save_off; + + /* the number of entries into power save mode */ + u16 enable_ps; + + /* + * the number of exits from power save, not including failed PS + * transitions + */ + u16 disable_ps; + + /* + * the number of times the TSF counter was adjusted because + * of drift + */ + u32 fix_tsf_ps; + + /* Gives statistics about the spread continuous missed beacons. + * The 16 LSB are dedicated for the PS mode. + * The 16 MSB are dedicated for the PS mode. + * cont_miss_bcns_spread[0] - single missed beacon. + * cont_miss_bcns_spread[1] - two continuous missed beacons. + * cont_miss_bcns_spread[2] - three continuous missed beacons. + * ... + * cont_miss_bcns_spread[9] - ten and more continuous missed beacons. + */ + u32 cont_miss_bcns_spread[ACX_MISSED_BEACONS_SPREAD]; + + /* the number of beacons in awake mode */ + u32 rcvd_awake_beacons; +} __packed; + +struct acx_mic_statistics { + u32 rx_pkts; + u32 calc_failure; +} __packed; + +struct acx_aes_statistics { + u32 encrypt_fail; + u32 decrypt_fail; + u32 encrypt_packets; + u32 decrypt_packets; + u32 encrypt_interrupt; + u32 decrypt_interrupt; +} __packed; + +struct acx_event_statistics { + u32 heart_beat; + u32 calibration; + u32 rx_mismatch; + u32 rx_mem_empty; + u32 rx_pool; + u32 oom_late; + u32 phy_transmit_error; + u32 tx_stuck; +} __packed; + +struct acx_ps_statistics { + u32 pspoll_timeouts; + u32 upsd_timeouts; + u32 upsd_max_sptime; + u32 upsd_max_apturn; + u32 pspoll_max_apturn; + u32 pspoll_utilization; + u32 upsd_utilization; +} __packed; + +struct acx_rxpipe_statistics { + u32 rx_prep_beacon_drop; + u32 descr_host_int_trig_rx_data; + u32 beacon_buffer_thres_host_int_trig_rx_data; + u32 missed_beacon_host_int_trig_rx_data; + u32 tx_xfr_host_int_trig_rx_data; +} __packed; + +struct acx_statistics { + struct acx_header header; + + struct acx_tx_statistics tx; + struct acx_rx_statistics rx; + struct acx_dma_statistics dma; + struct acx_isr_statistics isr; + struct acx_wep_statistics wep; + struct acx_pwr_statistics pwr; + struct acx_aes_statistics aes; + struct acx_mic_statistics mic; + struct acx_event_statistics event; + struct acx_ps_statistics ps; + struct acx_rxpipe_statistics rxpipe; +} __packed; + +#define ACX_MAX_RATE_CLASSES 8 +#define ACX_RATE_MASK_UNSPECIFIED 0 +#define ACX_RATE_RETRY_LIMIT 10 + +struct acx_rate_class { + u32 enabled_rates; + u8 short_retry_limit; + u8 long_retry_limit; + u8 aflags; + u8 reserved; +} __packed; + +struct acx_rate_policy { + struct acx_header header; + + u32 rate_class_cnt; + struct acx_rate_class rate_class[ACX_MAX_RATE_CLASSES]; +} __packed; + +struct wl1251_acx_memory { + __le16 num_stations; /* number of STAs to be supported. */ + u16 reserved_1; + + /* + * Nmber of memory buffers for the RX mem pool. + * The actual number may be less if there are + * not enough blocks left for the minimum num + * of TX ones. + */ + u8 rx_mem_block_num; + u8 reserved_2; + u8 num_tx_queues; /* From 1 to 16 */ + u8 host_if_options; /* HOST_IF* */ + u8 tx_min_mem_block_num; + u8 num_ssid_profiles; + __le16 debug_buffer_size; +} __packed; + + +#define ACX_RX_DESC_MIN 1 +#define ACX_RX_DESC_MAX 127 +#define ACX_RX_DESC_DEF 32 +struct wl1251_acx_rx_queue_config { + u8 num_descs; + u8 pad; + u8 type; + u8 priority; + __le32 dma_address; +} __packed; + +#define ACX_TX_DESC_MIN 1 +#define ACX_TX_DESC_MAX 127 +#define ACX_TX_DESC_DEF 16 +struct wl1251_acx_tx_queue_config { + u8 num_descs; + u8 pad[2]; + u8 attributes; +} __packed; + +#define MAX_TX_QUEUE_CONFIGS 5 +#define MAX_TX_QUEUES 4 +struct wl1251_acx_config_memory { + struct acx_header header; + + struct wl1251_acx_memory mem_config; + struct wl1251_acx_rx_queue_config rx_queue_config; + struct wl1251_acx_tx_queue_config tx_queue_config[MAX_TX_QUEUE_CONFIGS]; +} __packed; + +struct wl1251_acx_mem_map { + struct acx_header header; + + void *code_start; + void *code_end; + + void *wep_defkey_start; + void *wep_defkey_end; + + void *sta_table_start; + void *sta_table_end; + + void *packet_template_start; + void *packet_template_end; + + void *queue_memory_start; + void *queue_memory_end; + + void *packet_memory_pool_start; + void *packet_memory_pool_end; + + void *debug_buffer1_start; + void *debug_buffer1_end; + + void *debug_buffer2_start; + void *debug_buffer2_end; + + /* Number of blocks FW allocated for TX packets */ + u32 num_tx_mem_blocks; + + /* Number of blocks FW allocated for RX packets */ + u32 num_rx_mem_blocks; +} __packed; + + +struct wl1251_acx_wr_tbtt_and_dtim { + + struct acx_header header; + + /* Time in TUs between two consecutive beacons */ + u16 tbtt; + + /* + * DTIM period + * For BSS: Number of TBTTs in a DTIM period (range: 1-10) + * For IBSS: value shall be set to 1 + */ + u8 dtim; + u8 padding; +} __packed; + +enum wl1251_acx_bet_mode { + WL1251_ACX_BET_DISABLE = 0, + WL1251_ACX_BET_ENABLE = 1, +}; + +struct wl1251_acx_bet_enable { + struct acx_header header; + + /* + * Specifies if beacon early termination procedure is enabled or + * disabled, see enum wl1251_acx_bet_mode. + */ + u8 enable; + + /* + * Specifies the maximum number of consecutive beacons that may be + * early terminated. After this number is reached at least one full + * beacon must be correctly received in FW before beacon ET + * resumes. Range 0 - 255. + */ + u8 max_consecutive; + + u8 padding[2]; +} __packed; + +struct wl1251_acx_ac_cfg { + struct acx_header header; + + /* + * Access Category - The TX queue's access category + * (refer to AccessCategory_enum) + */ + u8 ac; + + /* + * The contention window minimum size (in slots) for + * the access class. + */ + u8 cw_min; + + /* + * The contention window maximum size (in slots) for + * the access class. + */ + u16 cw_max; + + /* The AIF value (in slots) for the access class. */ + u8 aifsn; + + u8 reserved; + + /* The TX Op Limit (in microseconds) for the access class. */ + u16 txop_limit; +} __packed; + + +enum wl1251_acx_channel_type { + CHANNEL_TYPE_DCF = 0, + CHANNEL_TYPE_EDCF = 1, + CHANNEL_TYPE_HCCA = 2, +}; + +enum wl1251_acx_ps_scheme { + /* regular ps: simple sending of packets */ + WL1251_ACX_PS_SCHEME_LEGACY = 0, + + /* sending a packet triggers a unscheduled apsd downstream */ + WL1251_ACX_PS_SCHEME_UPSD_TRIGGER = 1, + + /* a pspoll packet will be sent before every data packet */ + WL1251_ACX_PS_SCHEME_LEGACY_PSPOLL = 2, + + /* scheduled apsd mode */ + WL1251_ACX_PS_SCHEME_SAPSD = 3, +}; + +enum wl1251_acx_ack_policy { + WL1251_ACX_ACK_POLICY_LEGACY = 0, + WL1251_ACX_ACK_POLICY_NO_ACK = 1, + WL1251_ACX_ACK_POLICY_BLOCK = 2, +}; + +struct wl1251_acx_tid_cfg { + struct acx_header header; + + /* tx queue id number (0-7) */ + u8 queue; + + /* channel access type for the queue, enum wl1251_acx_channel_type */ + u8 type; + + /* EDCA: ac index (0-3), HCCA: traffic stream id (8-15) */ + u8 tsid; + + /* ps scheme of the specified queue, enum wl1251_acx_ps_scheme */ + u8 ps_scheme; + + /* the tx queue ack policy, enum wl1251_acx_ack_policy */ + u8 ack_policy; + + u8 padding[3]; + + /* not supported */ + u32 apsdconf[2]; +} __packed; + +/************************************************************************* + + Host Interrupt Register (WiLink -> Host) + +**************************************************************************/ + +/* RX packet is ready in Xfer buffer #0 */ +#define WL1251_ACX_INTR_RX0_DATA BIT(0) + +/* TX result(s) are in the TX complete buffer */ +#define WL1251_ACX_INTR_TX_RESULT BIT(1) + +/* OBSOLETE */ +#define WL1251_ACX_INTR_TX_XFR BIT(2) + +/* RX packet is ready in Xfer buffer #1 */ +#define WL1251_ACX_INTR_RX1_DATA BIT(3) + +/* Event was entered to Event MBOX #A */ +#define WL1251_ACX_INTR_EVENT_A BIT(4) + +/* Event was entered to Event MBOX #B */ +#define WL1251_ACX_INTR_EVENT_B BIT(5) + +/* OBSOLETE */ +#define WL1251_ACX_INTR_WAKE_ON_HOST BIT(6) + +/* Trace message on MBOX #A */ +#define WL1251_ACX_INTR_TRACE_A BIT(7) + +/* Trace message on MBOX #B */ +#define WL1251_ACX_INTR_TRACE_B BIT(8) + +/* Command processing completion */ +#define WL1251_ACX_INTR_CMD_COMPLETE BIT(9) + +/* Init sequence is done */ +#define WL1251_ACX_INTR_INIT_COMPLETE BIT(14) + +#define WL1251_ACX_INTR_ALL 0xFFFFFFFF + +enum { + ACX_WAKE_UP_CONDITIONS = 0x0002, + ACX_MEM_CFG = 0x0003, + ACX_SLOT = 0x0004, + ACX_QUEUE_HEAD = 0x0005, /* for MASTER mode only */ + ACX_AC_CFG = 0x0007, + ACX_MEM_MAP = 0x0008, + ACX_AID = 0x000A, + ACX_RADIO_PARAM = 0x000B, /* Not used */ + ACX_CFG = 0x000C, /* Not used */ + ACX_FW_REV = 0x000D, + ACX_MEDIUM_USAGE = 0x000F, + ACX_RX_CFG = 0x0010, + ACX_TX_QUEUE_CFG = 0x0011, /* FIXME: only used by wl1251 */ + ACX_BSS_IN_PS = 0x0012, /* for AP only */ + ACX_STATISTICS = 0x0013, /* Debug API */ + ACX_FEATURE_CFG = 0x0015, + ACX_MISC_CFG = 0x0017, /* Not used */ + ACX_TID_CFG = 0x001A, + ACX_BEACON_FILTER_OPT = 0x001F, + ACX_LOW_RSSI = 0x0020, + ACX_NOISE_HIST = 0x0021, + ACX_HDK_VERSION = 0x0022, /* ??? */ + ACX_PD_THRESHOLD = 0x0023, + ACX_DATA_PATH_PARAMS = 0x0024, /* WO */ + ACX_DATA_PATH_RESP_PARAMS = 0x0024, /* RO */ + ACX_CCA_THRESHOLD = 0x0025, + ACX_EVENT_MBOX_MASK = 0x0026, +#ifdef FW_RUNNING_AS_AP + ACX_DTIM_PERIOD = 0x0027, /* for AP only */ +#else + ACX_WR_TBTT_AND_DTIM = 0x0027, /* STA only */ +#endif + ACX_ACI_OPTION_CFG = 0x0029, /* OBSOLETE (for 1251)*/ + ACX_GPIO_CFG = 0x002A, /* Not used */ + ACX_GPIO_SET = 0x002B, /* Not used */ + ACX_PM_CFG = 0x002C, /* To Be Documented */ + ACX_CONN_MONIT_PARAMS = 0x002D, + ACX_AVERAGE_RSSI = 0x002E, /* Not used */ + ACX_CONS_TX_FAILURE = 0x002F, + ACX_BCN_DTIM_OPTIONS = 0x0031, + ACX_SG_ENABLE = 0x0032, + ACX_SG_CFG = 0x0033, + ACX_ANTENNA_DIVERSITY_CFG = 0x0035, /* To Be Documented */ + ACX_LOW_SNR = 0x0037, /* To Be Documented */ + ACX_BEACON_FILTER_TABLE = 0x0038, + ACX_ARP_IP_FILTER = 0x0039, + ACX_ROAMING_STATISTICS_TBL = 0x003B, + ACX_RATE_POLICY = 0x003D, + ACX_CTS_PROTECTION = 0x003E, + ACX_SLEEP_AUTH = 0x003F, + ACX_PREAMBLE_TYPE = 0x0040, + ACX_ERROR_CNT = 0x0041, + ACX_FW_GEN_FRAME_RATES = 0x0042, + ACX_IBSS_FILTER = 0x0044, + ACX_SERVICE_PERIOD_TIMEOUT = 0x0045, + ACX_TSF_INFO = 0x0046, + ACX_CONFIG_PS_WMM = 0x0049, + ACX_ENABLE_RX_DATA_FILTER = 0x004A, + ACX_SET_RX_DATA_FILTER = 0x004B, + ACX_GET_DATA_FILTER_STATISTICS = 0x004C, + ACX_POWER_LEVEL_TABLE = 0x004D, + ACX_BET_ENABLE = 0x0050, + DOT11_STATION_ID = 0x1001, + DOT11_RX_MSDU_LIFE_TIME = 0x1004, + DOT11_CUR_TX_PWR = 0x100D, + DOT11_DEFAULT_KEY = 0x1010, + DOT11_RX_DOT11_MODE = 0x1012, + DOT11_RTS_THRESHOLD = 0x1013, + DOT11_GROUP_ADDRESS_TBL = 0x1014, + + MAX_DOT11_IE = DOT11_GROUP_ADDRESS_TBL, + + MAX_IE = 0xFFFF +}; + + +int wl1251_acx_frame_rates(struct wl1251 *wl, u8 ctrl_rate, u8 ctrl_mod, + u8 mgt_rate, u8 mgt_mod); +int wl1251_acx_station_id(struct wl1251 *wl); +int wl1251_acx_default_key(struct wl1251 *wl, u8 key_id); +int wl1251_acx_wake_up_conditions(struct wl1251 *wl, u8 wake_up_event, + u8 listen_interval); +int wl1251_acx_sleep_auth(struct wl1251 *wl, u8 sleep_auth); +int wl1251_acx_fw_version(struct wl1251 *wl, char *buf, size_t len); +int wl1251_acx_tx_power(struct wl1251 *wl, int power); +int wl1251_acx_feature_cfg(struct wl1251 *wl); +int wl1251_acx_mem_map(struct wl1251 *wl, + struct acx_header *mem_map, size_t len); +int wl1251_acx_data_path_params(struct wl1251 *wl, + struct acx_data_path_params_resp *data_path); +int wl1251_acx_rx_msdu_life_time(struct wl1251 *wl, u32 life_time); +int wl1251_acx_rx_config(struct wl1251 *wl, u32 config, u32 filter); +int wl1251_acx_pd_threshold(struct wl1251 *wl); +int wl1251_acx_slot(struct wl1251 *wl, enum acx_slot_type slot_time); +int wl1251_acx_group_address_tbl(struct wl1251 *wl); +int wl1251_acx_service_period_timeout(struct wl1251 *wl); +int wl1251_acx_rts_threshold(struct wl1251 *wl, u16 rts_threshold); +int wl1251_acx_beacon_filter_opt(struct wl1251 *wl, bool enable_filter); +int wl1251_acx_beacon_filter_table(struct wl1251 *wl); +int wl1251_acx_conn_monit_params(struct wl1251 *wl); +int wl1251_acx_sg_enable(struct wl1251 *wl); +int wl1251_acx_sg_cfg(struct wl1251 *wl); +int wl1251_acx_cca_threshold(struct wl1251 *wl); +int wl1251_acx_bcn_dtim_options(struct wl1251 *wl); +int wl1251_acx_aid(struct wl1251 *wl, u16 aid); +int wl1251_acx_event_mbox_mask(struct wl1251 *wl, u32 event_mask); +int wl1251_acx_low_rssi(struct wl1251 *wl, s8 threshold, u8 weight, + u8 depth, enum wl1251_acx_low_rssi_type type); +int wl1251_acx_set_preamble(struct wl1251 *wl, enum acx_preamble_type preamble); +int wl1251_acx_cts_protect(struct wl1251 *wl, + enum acx_ctsprotect_type ctsprotect); +int wl1251_acx_statistics(struct wl1251 *wl, struct acx_statistics *stats); +int wl1251_acx_tsf_info(struct wl1251 *wl, u64 *mactime); +int wl1251_acx_rate_policies(struct wl1251 *wl); +int wl1251_acx_mem_cfg(struct wl1251 *wl); +int wl1251_acx_wr_tbtt_and_dtim(struct wl1251 *wl, u16 tbtt, u8 dtim); +int wl1251_acx_bet_enable(struct wl1251 *wl, enum wl1251_acx_bet_mode mode, + u8 max_consecutive); +int wl1251_acx_ac_cfg(struct wl1251 *wl, u8 ac, u8 cw_min, u16 cw_max, + u8 aifs, u16 txop); +int wl1251_acx_tid_cfg(struct wl1251 *wl, u8 queue, + enum wl1251_acx_channel_type type, + u8 tsid, enum wl1251_acx_ps_scheme ps_scheme, + enum wl1251_acx_ack_policy ack_policy); + +#endif /* __WL1251_ACX_H__ */ diff --git a/drivers/net/wireless/ti/wl1251/boot.c b/drivers/net/wireless/ti/wl1251/boot.c new file mode 100644 index 0000000..a2e5241 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/boot.c @@ -0,0 +1,554 @@ +/* + * This file is part of wl1251 + * + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include + +#include "reg.h" +#include "boot.h" +#include "io.h" +#include "spi.h" +#include "event.h" +#include "acx.h" + +void wl1251_boot_target_enable_interrupts(struct wl1251 *wl) +{ + wl1251_reg_write32(wl, ACX_REG_INTERRUPT_MASK, ~(wl->intr_mask)); + wl1251_reg_write32(wl, HI_CFG, HI_CFG_DEF_VAL); +} + +int wl1251_boot_soft_reset(struct wl1251 *wl) +{ + unsigned long timeout; + u32 boot_data; + + /* perform soft reset */ + wl1251_reg_write32(wl, ACX_REG_SLV_SOFT_RESET, ACX_SLV_SOFT_RESET_BIT); + + /* SOFT_RESET is self clearing */ + timeout = jiffies + usecs_to_jiffies(SOFT_RESET_MAX_TIME); + while (1) { + boot_data = wl1251_reg_read32(wl, ACX_REG_SLV_SOFT_RESET); + wl1251_debug(DEBUG_BOOT, "soft reset bootdata 0x%x", boot_data); + if ((boot_data & ACX_SLV_SOFT_RESET_BIT) == 0) + break; + + if (time_after(jiffies, timeout)) { + /* 1.2 check pWhalBus->uSelfClearTime if the + * timeout was reached */ + wl1251_error("soft reset timeout"); + return -1; + } + + udelay(SOFT_RESET_STALL_TIME); + } + + /* disable Rx/Tx */ + wl1251_reg_write32(wl, ENABLE, 0x0); + + /* disable auto calibration on start*/ + wl1251_reg_write32(wl, SPARE_A2, 0xffff); + + return 0; +} + +int wl1251_boot_init_seq(struct wl1251 *wl) +{ + u32 scr_pad6, init_data, tmp, elp_cmd, ref_freq; + + /* + * col #1: INTEGER_DIVIDER + * col #2: FRACTIONAL_DIVIDER + * col #3: ATTN_BB + * col #4: ALPHA_BB + * col #5: STOP_TIME_BB + * col #6: BB_PLL_LOOP_FILTER + */ + static const u32 LUT[REF_FREQ_NUM][LUT_PARAM_NUM] = { + + { 83, 87381, 0xB, 5, 0xF00, 3}, /* REF_FREQ_19_2*/ + { 61, 141154, 0xB, 5, 0x1450, 2}, /* REF_FREQ_26_0*/ + { 41, 174763, 0xC, 6, 0x2D00, 1}, /* REF_FREQ_38_4*/ + { 40, 0, 0xC, 6, 0x2EE0, 1}, /* REF_FREQ_40_0*/ + { 47, 162280, 0xC, 6, 0x2760, 1} /* REF_FREQ_33_6 */ + }; + + /* read NVS params */ + scr_pad6 = wl1251_reg_read32(wl, SCR_PAD6); + wl1251_debug(DEBUG_BOOT, "scr_pad6 0x%x", scr_pad6); + + /* read ELP_CMD */ + elp_cmd = wl1251_reg_read32(wl, ELP_CMD); + wl1251_debug(DEBUG_BOOT, "elp_cmd 0x%x", elp_cmd); + + /* set the BB calibration time to be 300 usec (PLL_CAL_TIME) */ + ref_freq = scr_pad6 & 0x000000FF; + wl1251_debug(DEBUG_BOOT, "ref_freq 0x%x", ref_freq); + + wl1251_reg_write32(wl, PLL_CAL_TIME, 0x9); + + /* + * PG 1.2: set the clock buffer time to be 210 usec (CLK_BUF_TIME) + */ + wl1251_reg_write32(wl, CLK_BUF_TIME, 0x6); + + /* + * set the clock detect feature to work in the restart wu procedure + * (ELP_CFG_MODE[14]) and Select the clock source type + * (ELP_CFG_MODE[13:12]) + */ + tmp = ((scr_pad6 & 0x0000FF00) << 4) | 0x00004000; + wl1251_reg_write32(wl, ELP_CFG_MODE, tmp); + + /* PG 1.2: enable the BB PLL fix. Enable the PLL_LIMP_CLK_EN_CMD */ + elp_cmd |= 0x00000040; + wl1251_reg_write32(wl, ELP_CMD, elp_cmd); + + /* PG 1.2: Set the BB PLL stable time to be 1000usec + * (PLL_STABLE_TIME) */ + wl1251_reg_write32(wl, CFG_PLL_SYNC_CNT, 0x20); + + /* PG 1.2: read clock request time */ + init_data = wl1251_reg_read32(wl, CLK_REQ_TIME); + + /* + * PG 1.2: set the clock request time to be ref_clk_settling_time - + * 1ms = 4ms + */ + if (init_data > 0x21) + tmp = init_data - 0x21; + else + tmp = 0; + wl1251_reg_write32(wl, CLK_REQ_TIME, tmp); + + /* set BB PLL configurations in RF AFE */ + wl1251_reg_write32(wl, 0x003058cc, 0x4B5); + + /* set RF_AFE_REG_5 */ + wl1251_reg_write32(wl, 0x003058d4, 0x50); + + /* set RF_AFE_CTRL_REG_2 */ + wl1251_reg_write32(wl, 0x00305948, 0x11c001); + + /* + * change RF PLL and BB PLL divider for VCO clock and adjust VCO + * bais current(RF_AFE_REG_13) + */ + wl1251_reg_write32(wl, 0x003058f4, 0x1e); + + /* set BB PLL configurations */ + tmp = LUT[ref_freq][LUT_PARAM_INTEGER_DIVIDER] | 0x00017000; + wl1251_reg_write32(wl, 0x00305840, tmp); + + /* set fractional divider according to Appendix C-BB PLL + * Calculations + */ + tmp = LUT[ref_freq][LUT_PARAM_FRACTIONAL_DIVIDER]; + wl1251_reg_write32(wl, 0x00305844, tmp); + + /* set the initial data for the sigma delta */ + wl1251_reg_write32(wl, 0x00305848, 0x3039); + + /* + * set the accumulator attenuation value, calibration loop1 + * (alpha), calibration loop2 (beta), calibration loop3 (gamma) and + * the VCO gain + */ + tmp = (LUT[ref_freq][LUT_PARAM_ATTN_BB] << 16) | + (LUT[ref_freq][LUT_PARAM_ALPHA_BB] << 12) | 0x1; + wl1251_reg_write32(wl, 0x00305854, tmp); + + /* + * set the calibration stop time after holdoff time expires and set + * settling time HOLD_OFF_TIME_BB + */ + tmp = LUT[ref_freq][LUT_PARAM_STOP_TIME_BB] | 0x000A0000; + wl1251_reg_write32(wl, 0x00305858, tmp); + + /* + * set BB PLL Loop filter capacitor3- BB_C3[2:0] and set BB PLL + * constant leakage current to linearize PFD to 0uA - + * BB_ILOOPF[7:3] + */ + tmp = LUT[ref_freq][LUT_PARAM_BB_PLL_LOOP_FILTER] | 0x00000030; + wl1251_reg_write32(wl, 0x003058f8, tmp); + + /* + * set regulator output voltage for n divider to + * 1.35-BB_REFDIV[1:0], set charge pump current- BB_CPGAIN[4:2], + * set BB PLL Loop filter capacitor2- BB_C2[7:5], set gain of BB + * PLL auto-call to normal mode- BB_CALGAIN_3DB[8] + */ + wl1251_reg_write32(wl, 0x003058f0, 0x29); + + /* enable restart wakeup sequence (ELP_CMD[0]) */ + wl1251_reg_write32(wl, ELP_CMD, elp_cmd | 0x1); + + /* restart sequence completed */ + udelay(2000); + + return 0; +} + +static void wl1251_boot_set_ecpu_ctrl(struct wl1251 *wl, u32 flag) +{ + u32 cpu_ctrl; + + /* 10.5.0 run the firmware (I) */ + cpu_ctrl = wl1251_reg_read32(wl, ACX_REG_ECPU_CONTROL); + + /* 10.5.1 run the firmware (II) */ + cpu_ctrl &= ~flag; + wl1251_reg_write32(wl, ACX_REG_ECPU_CONTROL, cpu_ctrl); +} + +int wl1251_boot_run_firmware(struct wl1251 *wl) +{ + int loop, ret; + u32 chip_id, acx_intr; + + wl1251_boot_set_ecpu_ctrl(wl, ECPU_CONTROL_HALT); + + chip_id = wl1251_reg_read32(wl, CHIP_ID_B); + + wl1251_debug(DEBUG_BOOT, "chip id after firmware boot: 0x%x", chip_id); + + if (chip_id != wl->chip_id) { + wl1251_error("chip id doesn't match after firmware boot"); + return -EIO; + } + + /* wait for init to complete */ + loop = 0; + while (loop++ < INIT_LOOP) { + udelay(INIT_LOOP_DELAY); + acx_intr = wl1251_reg_read32(wl, ACX_REG_INTERRUPT_NO_CLEAR); + + if (acx_intr == 0xffffffff) { + wl1251_error("error reading hardware complete " + "init indication"); + return -EIO; + } + /* check that ACX_INTR_INIT_COMPLETE is enabled */ + else if (acx_intr & WL1251_ACX_INTR_INIT_COMPLETE) { + wl1251_reg_write32(wl, ACX_REG_INTERRUPT_ACK, + WL1251_ACX_INTR_INIT_COMPLETE); + break; + } + } + + if (loop > INIT_LOOP) { + wl1251_error("timeout waiting for the hardware to " + "complete initialization"); + return -EIO; + } + + /* get hardware config command mail box */ + wl->cmd_box_addr = wl1251_reg_read32(wl, REG_COMMAND_MAILBOX_PTR); + + /* get hardware config event mail box */ + wl->event_box_addr = wl1251_reg_read32(wl, REG_EVENT_MAILBOX_PTR); + + /* set the working partition to its "running" mode offset */ + wl1251_set_partition(wl, WL1251_PART_WORK_MEM_START, + WL1251_PART_WORK_MEM_SIZE, + WL1251_PART_WORK_REG_START, + WL1251_PART_WORK_REG_SIZE); + + wl1251_debug(DEBUG_MAILBOX, "cmd_box_addr 0x%x event_box_addr 0x%x", + wl->cmd_box_addr, wl->event_box_addr); + + wl1251_acx_fw_version(wl, wl->fw_ver, sizeof(wl->fw_ver)); + + /* + * in case of full asynchronous mode the firmware event must be + * ready to receive event from the command mailbox + */ + + /* enable gpio interrupts */ + wl1251_enable_interrupts(wl); + + /* Enable target's interrupts */ + wl->intr_mask = WL1251_ACX_INTR_RX0_DATA | + WL1251_ACX_INTR_RX1_DATA | + WL1251_ACX_INTR_TX_RESULT | + WL1251_ACX_INTR_EVENT_A | + WL1251_ACX_INTR_EVENT_B | + WL1251_ACX_INTR_INIT_COMPLETE; + wl1251_boot_target_enable_interrupts(wl); + + wl->event_mask = SCAN_COMPLETE_EVENT_ID | BSS_LOSE_EVENT_ID | + SYNCHRONIZATION_TIMEOUT_EVENT_ID | + ROAMING_TRIGGER_LOW_RSSI_EVENT_ID | + ROAMING_TRIGGER_REGAINED_RSSI_EVENT_ID | + REGAINED_BSS_EVENT_ID | BT_PTA_SENSE_EVENT_ID | + BT_PTA_PREDICTION_EVENT_ID | JOIN_EVENT_COMPLETE_ID; + + ret = wl1251_event_unmask(wl); + if (ret < 0) { + wl1251_error("EVENT mask setting failed"); + return ret; + } + + wl1251_event_mbox_config(wl); + + /* firmware startup completed */ + return 0; +} + +static int wl1251_boot_upload_firmware(struct wl1251 *wl) +{ + int addr, chunk_num, partition_limit; + size_t fw_data_len, len; + u8 *p, *buf; + + /* whal_FwCtrl_LoadFwImageSm() */ + + wl1251_debug(DEBUG_BOOT, "chip id before fw upload: 0x%x", + wl1251_reg_read32(wl, CHIP_ID_B)); + + /* 10.0 check firmware length and set partition */ + fw_data_len = (wl->fw[4] << 24) | (wl->fw[5] << 16) | + (wl->fw[6] << 8) | (wl->fw[7]); + + wl1251_debug(DEBUG_BOOT, "fw_data_len %zu chunk_size %d", fw_data_len, + CHUNK_SIZE); + + if ((fw_data_len % 4) != 0) { + wl1251_error("firmware length not multiple of four"); + return -EIO; + } + + buf = kmalloc(CHUNK_SIZE, GFP_KERNEL); + if (!buf) { + wl1251_error("allocation for firmware upload chunk failed"); + return -ENOMEM; + } + + wl1251_set_partition(wl, WL1251_PART_DOWN_MEM_START, + WL1251_PART_DOWN_MEM_SIZE, + WL1251_PART_DOWN_REG_START, + WL1251_PART_DOWN_REG_SIZE); + + /* 10.1 set partition limit and chunk num */ + chunk_num = 0; + partition_limit = WL1251_PART_DOWN_MEM_SIZE; + + while (chunk_num < fw_data_len / CHUNK_SIZE) { + /* 10.2 update partition, if needed */ + addr = WL1251_PART_DOWN_MEM_START + + (chunk_num + 2) * CHUNK_SIZE; + if (addr > partition_limit) { + addr = WL1251_PART_DOWN_MEM_START + + chunk_num * CHUNK_SIZE; + partition_limit = chunk_num * CHUNK_SIZE + + WL1251_PART_DOWN_MEM_SIZE; + wl1251_set_partition(wl, + addr, + WL1251_PART_DOWN_MEM_SIZE, + WL1251_PART_DOWN_REG_START, + WL1251_PART_DOWN_REG_SIZE); + } + + /* 10.3 upload the chunk */ + addr = WL1251_PART_DOWN_MEM_START + chunk_num * CHUNK_SIZE; + p = wl->fw + FW_HDR_SIZE + chunk_num * CHUNK_SIZE; + wl1251_debug(DEBUG_BOOT, "uploading fw chunk 0x%p to 0x%x", + p, addr); + + /* need to copy the chunk for dma */ + len = CHUNK_SIZE; + memcpy(buf, p, len); + wl1251_mem_write(wl, addr, buf, len); + + chunk_num++; + } + + /* 10.4 upload the last chunk */ + addr = WL1251_PART_DOWN_MEM_START + chunk_num * CHUNK_SIZE; + p = wl->fw + FW_HDR_SIZE + chunk_num * CHUNK_SIZE; + + /* need to copy the chunk for dma */ + len = fw_data_len % CHUNK_SIZE; + memcpy(buf, p, len); + + wl1251_debug(DEBUG_BOOT, "uploading fw last chunk (%zu B) 0x%p to 0x%x", + len, p, addr); + wl1251_mem_write(wl, addr, buf, len); + + kfree(buf); + + return 0; +} + +static int wl1251_boot_upload_nvs(struct wl1251 *wl) +{ + size_t nvs_len, nvs_bytes_written, burst_len; + int nvs_start, i; + u32 dest_addr, val; + u8 *nvs_ptr, *nvs; + + nvs = wl->nvs; + if (nvs == NULL) + return -ENODEV; + + nvs_ptr = nvs; + + nvs_len = wl->nvs_len; + nvs_start = wl->fw_len; + + /* + * Layout before the actual NVS tables: + * 1 byte : burst length. + * 2 bytes: destination address. + * n bytes: data to burst copy. + * + * This is ended by a 0 length, then the NVS tables. + */ + + while (nvs_ptr[0]) { + burst_len = nvs_ptr[0]; + dest_addr = (nvs_ptr[1] & 0xfe) | ((u32)(nvs_ptr[2] << 8)); + + /* We move our pointer to the data */ + nvs_ptr += 3; + + for (i = 0; i < burst_len; i++) { + val = (nvs_ptr[0] | (nvs_ptr[1] << 8) + | (nvs_ptr[2] << 16) | (nvs_ptr[3] << 24)); + + wl1251_debug(DEBUG_BOOT, + "nvs burst write 0x%x: 0x%x", + dest_addr, val); + wl1251_mem_write32(wl, dest_addr, val); + + nvs_ptr += 4; + dest_addr += 4; + } + } + + /* + * We've reached the first zero length, the first NVS table + * is 7 bytes further. + */ + nvs_ptr += 7; + nvs_len -= nvs_ptr - nvs; + nvs_len = ALIGN(nvs_len, 4); + + /* Now we must set the partition correctly */ + wl1251_set_partition(wl, nvs_start, + WL1251_PART_DOWN_MEM_SIZE, + WL1251_PART_DOWN_REG_START, + WL1251_PART_DOWN_REG_SIZE); + + /* And finally we upload the NVS tables */ + nvs_bytes_written = 0; + while (nvs_bytes_written < nvs_len) { + val = (nvs_ptr[0] | (nvs_ptr[1] << 8) + | (nvs_ptr[2] << 16) | (nvs_ptr[3] << 24)); + + wl1251_debug(DEBUG_BOOT, + "nvs write table 0x%x: 0x%x", + nvs_start, val); + wl1251_mem_write32(wl, nvs_start, val); + + nvs_ptr += 4; + nvs_bytes_written += 4; + nvs_start += 4; + } + + return 0; +} + +int wl1251_boot(struct wl1251 *wl) +{ + int ret = 0, minor_minor_e2_ver; + u32 tmp, boot_data; + + /* halt embedded ARM CPU while loading firmware */ + wl1251_reg_write32(wl, ACX_REG_ECPU_CONTROL, ECPU_CONTROL_HALT); + + ret = wl1251_boot_soft_reset(wl); + if (ret < 0) + goto out; + + /* 2. start processing NVS file */ + if (wl->use_eeprom) { + wl1251_reg_write32(wl, ACX_REG_EE_START, START_EEPROM_MGR); + /* Wait for EEPROM NVS burst read to complete */ + msleep(40); + wl1251_reg_write32(wl, ACX_EEPROMLESS_IND_REG, USE_EEPROM); + } else { + ret = wl1251_boot_upload_nvs(wl); + if (ret < 0) + goto out; + + /* write firmware's last address (ie. it's length) to + * ACX_EEPROMLESS_IND_REG */ + wl1251_reg_write32(wl, ACX_EEPROMLESS_IND_REG, wl->fw_len); + } + + /* 6. read the EEPROM parameters */ + tmp = wl1251_reg_read32(wl, SCR_PAD2); + + /* 7. read bootdata */ + wl->boot_attr.radio_type = (tmp & 0x0000FF00) >> 8; + wl->boot_attr.major = (tmp & 0x00FF0000) >> 16; + tmp = wl1251_reg_read32(wl, SCR_PAD3); + + /* 8. check bootdata and call restart sequence */ + wl->boot_attr.minor = (tmp & 0x00FF0000) >> 16; + minor_minor_e2_ver = (tmp & 0xFF000000) >> 24; + + wl1251_debug(DEBUG_BOOT, "radioType 0x%x majorE2Ver 0x%x " + "minorE2Ver 0x%x minor_minor_e2_ver 0x%x", + wl->boot_attr.radio_type, wl->boot_attr.major, + wl->boot_attr.minor, minor_minor_e2_ver); + + ret = wl1251_boot_init_seq(wl); + if (ret < 0) + goto out; + + /* 9. NVS processing done */ + boot_data = wl1251_reg_read32(wl, ACX_REG_ECPU_CONTROL); + + wl1251_debug(DEBUG_BOOT, "halt boot_data 0x%x", boot_data); + + /* 10. check that ECPU_CONTROL_HALT bits are set in + * pWhalBus->uBootData and start uploading firmware + */ + if ((boot_data & ECPU_CONTROL_HALT) == 0) { + wl1251_error("boot failed, ECPU_CONTROL_HALT not set"); + ret = -EIO; + goto out; + } + + ret = wl1251_boot_upload_firmware(wl); + if (ret < 0) + goto out; + + /* 10.5 start firmware */ + ret = wl1251_boot_run_firmware(wl); + if (ret < 0) + goto out; + +out: + return ret; +} diff --git a/drivers/net/wireless/ti/wl1251/boot.h b/drivers/net/wireless/ti/wl1251/boot.h new file mode 100644 index 0000000..7661bc5 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/boot.h @@ -0,0 +1,39 @@ +/* + * This file is part of wl1251 + * + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __BOOT_H__ +#define __BOOT_H__ + +#include "wl1251.h" + +int wl1251_boot_soft_reset(struct wl1251 *wl); +int wl1251_boot_init_seq(struct wl1251 *wl); +int wl1251_boot_run_firmware(struct wl1251 *wl); +void wl1251_boot_target_enable_interrupts(struct wl1251 *wl); +int wl1251_boot(struct wl1251 *wl); + +/* number of times we try to read the INIT interrupt */ +#define INIT_LOOP 20000 + +/* delay between retries */ +#define INIT_LOOP_DELAY 50 + +#endif diff --git a/drivers/net/wireless/ti/wl1251/cmd.c b/drivers/net/wireless/ti/wl1251/cmd.c new file mode 100644 index 0000000..6822b84 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/cmd.c @@ -0,0 +1,487 @@ +#include "cmd.h" + +#include +#include +#include + +#include "wl1251.h" +#include "reg.h" +#include "io.h" +#include "ps.h" +#include "acx.h" + +/** + * send command to firmware + * + * @wl: wl struct + * @id: command id + * @buf: buffer containing the command, must work with dma + * @len: length of the buffer + */ +int wl1251_cmd_send(struct wl1251 *wl, u16 id, void *buf, size_t len) +{ + struct wl1251_cmd_header *cmd; + unsigned long timeout; + u32 intr; + int ret = 0; + + cmd = buf; + cmd->id = id; + cmd->status = 0; + + WARN_ON(len % 4 != 0); + + wl1251_mem_write(wl, wl->cmd_box_addr, buf, len); + + wl1251_reg_write32(wl, ACX_REG_INTERRUPT_TRIG, INTR_TRIG_CMD); + + timeout = jiffies + msecs_to_jiffies(WL1251_COMMAND_TIMEOUT); + + intr = wl1251_reg_read32(wl, ACX_REG_INTERRUPT_NO_CLEAR); + while (!(intr & WL1251_ACX_INTR_CMD_COMPLETE)) { + if (time_after(jiffies, timeout)) { + wl1251_error("command complete timeout"); + ret = -ETIMEDOUT; + goto out; + } + + msleep(1); + + intr = wl1251_reg_read32(wl, ACX_REG_INTERRUPT_NO_CLEAR); + } + + wl1251_reg_write32(wl, ACX_REG_INTERRUPT_ACK, + WL1251_ACX_INTR_CMD_COMPLETE); + +out: + return ret; +} + +/** + * send test command to firmware + * + * @wl: wl struct + * @buf: buffer containing the command, with all headers, must work with dma + * @len: length of the buffer + * @answer: is answer needed + */ +int wl1251_cmd_test(struct wl1251 *wl, void *buf, size_t buf_len, u8 answer) +{ + int ret; + + wl1251_debug(DEBUG_CMD, "cmd test"); + + ret = wl1251_cmd_send(wl, CMD_TEST, buf, buf_len); + + if (ret < 0) { + wl1251_warning("TEST command failed"); + return ret; + } + + if (answer) { + struct wl1251_command *cmd_answer; + + /* + * The test command got in, we can read the answer. + * The answer would be a wl1251_command, where the + * parameter array contains the actual answer. + */ + wl1251_mem_read(wl, wl->cmd_box_addr, buf, buf_len); + + cmd_answer = buf; + + if (cmd_answer->header.status != CMD_STATUS_SUCCESS) + wl1251_error("TEST command answer error: %d", + cmd_answer->header.status); + } + + return 0; +} + +/** + * read acx from firmware + * + * @wl: wl struct + * @id: acx id + * @buf: buffer for the response, including all headers, must work with dma + * @len: length of buf + */ +int wl1251_cmd_interrogate(struct wl1251 *wl, u16 id, void *buf, size_t len) +{ + struct acx_header *acx = buf; + int ret; + + wl1251_debug(DEBUG_CMD, "cmd interrogate"); + + acx->id = id; + + /* payload length, does not include any headers */ + acx->len = len - sizeof(*acx); + + ret = wl1251_cmd_send(wl, CMD_INTERROGATE, acx, sizeof(*acx)); + if (ret < 0) { + wl1251_error("INTERROGATE command failed"); + goto out; + } + + /* the interrogate command got in, we can read the answer */ + wl1251_mem_read(wl, wl->cmd_box_addr, buf, len); + + acx = buf; + if (acx->cmd.status != CMD_STATUS_SUCCESS) + wl1251_error("INTERROGATE command error: %d", + acx->cmd.status); + +out: + return ret; +} + +/** + * write acx value to firmware + * + * @wl: wl struct + * @id: acx id + * @buf: buffer containing acx, including all headers, must work with dma + * @len: length of buf + */ +int wl1251_cmd_configure(struct wl1251 *wl, u16 id, void *buf, size_t len) +{ + struct acx_header *acx = buf; + int ret; + + wl1251_debug(DEBUG_CMD, "cmd configure"); + + acx->id = id; + + /* payload length, does not include any headers */ + acx->len = len - sizeof(*acx); + + ret = wl1251_cmd_send(wl, CMD_CONFIGURE, acx, len); + if (ret < 0) { + wl1251_warning("CONFIGURE command NOK"); + return ret; + } + + return 0; +} + +int wl1251_cmd_vbm(struct wl1251 *wl, u8 identity, + void *bitmap, u16 bitmap_len, u8 bitmap_control) +{ + struct wl1251_cmd_vbm_update *vbm; + int ret; + + wl1251_debug(DEBUG_CMD, "cmd vbm"); + + vbm = kzalloc(sizeof(*vbm), GFP_KERNEL); + if (!vbm) { + ret = -ENOMEM; + goto out; + } + + /* Count and period will be filled by the target */ + vbm->tim.bitmap_ctrl = bitmap_control; + if (bitmap_len > PARTIAL_VBM_MAX) { + wl1251_warning("cmd vbm len is %d B, truncating to %d", + bitmap_len, PARTIAL_VBM_MAX); + bitmap_len = PARTIAL_VBM_MAX; + } + memcpy(vbm->tim.pvb_field, bitmap, bitmap_len); + vbm->tim.identity = identity; + vbm->tim.length = bitmap_len + 3; + + vbm->len = cpu_to_le16(bitmap_len + 5); + + ret = wl1251_cmd_send(wl, CMD_VBM, vbm, sizeof(*vbm)); + if (ret < 0) { + wl1251_error("VBM command failed"); + goto out; + } + +out: + kfree(vbm); + return ret; +} + +int wl1251_cmd_data_path(struct wl1251 *wl, u8 channel, bool enable) +{ + struct cmd_enabledisable_path *cmd; + int ret; + u16 cmd_rx, cmd_tx; + + wl1251_debug(DEBUG_CMD, "cmd data path"); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->channel = channel; + + if (enable) { + cmd_rx = CMD_ENABLE_RX; + cmd_tx = CMD_ENABLE_TX; + } else { + cmd_rx = CMD_DISABLE_RX; + cmd_tx = CMD_DISABLE_TX; + } + + ret = wl1251_cmd_send(wl, cmd_rx, cmd, sizeof(*cmd)); + if (ret < 0) { + wl1251_error("rx %s cmd for channel %d failed", + enable ? "start" : "stop", channel); + goto out; + } + + wl1251_debug(DEBUG_BOOT, "rx %s cmd channel %d", + enable ? "start" : "stop", channel); + + ret = wl1251_cmd_send(wl, cmd_tx, cmd, sizeof(*cmd)); + if (ret < 0) { + wl1251_error("tx %s cmd for channel %d failed", + enable ? "start" : "stop", channel); + goto out; + } + + wl1251_debug(DEBUG_BOOT, "tx %s cmd channel %d", + enable ? "start" : "stop", channel); + +out: + kfree(cmd); + return ret; +} + +int wl1251_cmd_join(struct wl1251 *wl, u8 bss_type, u8 channel, + u16 beacon_interval, u8 dtim_interval) +{ + struct cmd_join *join; + int ret, i; + u8 *bssid; + + join = kzalloc(sizeof(*join), GFP_KERNEL); + if (!join) { + ret = -ENOMEM; + goto out; + } + + wl1251_debug(DEBUG_CMD, "cmd join%s ch %d %d/%d", + bss_type == BSS_TYPE_IBSS ? " ibss" : "", + channel, beacon_interval, dtim_interval); + + /* Reverse order BSSID */ + bssid = (u8 *) &join->bssid_lsb; + for (i = 0; i < ETH_ALEN; i++) + bssid[i] = wl->bssid[ETH_ALEN - i - 1]; + + join->rx_config_options = wl->rx_config; + join->rx_filter_options = wl->rx_filter; + + join->basic_rate_set = RATE_MASK_1MBPS | RATE_MASK_2MBPS | + RATE_MASK_5_5MBPS | RATE_MASK_11MBPS; + + join->beacon_interval = beacon_interval; + join->dtim_interval = dtim_interval; + join->bss_type = bss_type; + join->channel = channel; + join->ctrl = JOIN_CMD_CTRL_TX_FLUSH; + + ret = wl1251_cmd_send(wl, CMD_START_JOIN, join, sizeof(*join)); + if (ret < 0) { + wl1251_error("failed to initiate cmd join"); + goto out; + } + +out: + kfree(join); + return ret; +} + +int wl1251_cmd_ps_mode(struct wl1251 *wl, u8 ps_mode) +{ + struct wl1251_cmd_ps_params *ps_params = NULL; + int ret = 0; + + wl1251_debug(DEBUG_CMD, "cmd set ps mode"); + + ps_params = kzalloc(sizeof(*ps_params), GFP_KERNEL); + if (!ps_params) { + ret = -ENOMEM; + goto out; + } + + ps_params->ps_mode = ps_mode; + ps_params->send_null_data = 1; + ps_params->retries = 5; + ps_params->hang_over_period = 128; + ps_params->null_data_rate = 1; /* 1 Mbps */ + + ret = wl1251_cmd_send(wl, CMD_SET_PS_MODE, ps_params, + sizeof(*ps_params)); + if (ret < 0) { + wl1251_error("cmd set_ps_mode failed"); + goto out; + } + +out: + kfree(ps_params); + return ret; +} + +int wl1251_cmd_read_memory(struct wl1251 *wl, u32 addr, void *answer, + size_t len) +{ + struct cmd_read_write_memory *cmd; + int ret = 0; + + wl1251_debug(DEBUG_CMD, "cmd read memory"); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + WARN_ON(len > MAX_READ_SIZE); + len = min_t(size_t, len, MAX_READ_SIZE); + + cmd->addr = addr; + cmd->size = len; + + ret = wl1251_cmd_send(wl, CMD_READ_MEMORY, cmd, sizeof(*cmd)); + if (ret < 0) { + wl1251_error("read memory command failed: %d", ret); + goto out; + } + + /* the read command got in, we can now read the answer */ + wl1251_mem_read(wl, wl->cmd_box_addr, cmd, sizeof(*cmd)); + + if (cmd->header.status != CMD_STATUS_SUCCESS) + wl1251_error("error in read command result: %d", + cmd->header.status); + + memcpy(answer, cmd->value, len); + +out: + kfree(cmd); + return ret; +} + +int wl1251_cmd_template_set(struct wl1251 *wl, u16 cmd_id, + void *buf, size_t buf_len) +{ + struct wl1251_cmd_packet_template *cmd; + size_t cmd_len; + int ret = 0; + + wl1251_debug(DEBUG_CMD, "cmd template %d", cmd_id); + + WARN_ON(buf_len > WL1251_MAX_TEMPLATE_SIZE); + buf_len = min_t(size_t, buf_len, WL1251_MAX_TEMPLATE_SIZE); + cmd_len = ALIGN(sizeof(*cmd) + buf_len, 4); + + cmd = kzalloc(cmd_len, GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->size = cpu_to_le16(buf_len); + + if (buf) + memcpy(cmd->data, buf, buf_len); + + ret = wl1251_cmd_send(wl, cmd_id, cmd, cmd_len); + if (ret < 0) { + wl1251_warning("cmd set_template failed: %d", ret); + goto out; + } + +out: + kfree(cmd); + return ret; +} + +int wl1251_cmd_scan(struct wl1251 *wl, u8 *ssid, size_t ssid_len, + struct ieee80211_channel *channels[], + unsigned int n_channels, unsigned int n_probes) +{ + struct wl1251_cmd_scan *cmd; + int i, ret = 0; + + wl1251_debug(DEBUG_CMD, "cmd scan"); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + cmd->params.rx_config_options = cpu_to_le32(CFG_RX_ALL_GOOD); + cmd->params.rx_filter_options = cpu_to_le32(CFG_RX_PRSP_EN | + CFG_RX_MGMT_EN | + CFG_RX_BCN_EN); + cmd->params.scan_options = 0; + cmd->params.num_channels = n_channels; + cmd->params.num_probe_requests = n_probes; + cmd->params.tx_rate = cpu_to_le16(1 << 1); /* 2 Mbps */ + cmd->params.tid_trigger = 0; + + for (i = 0; i < n_channels; i++) { + cmd->channels[i].min_duration = + cpu_to_le32(WL1251_SCAN_MIN_DURATION); + cmd->channels[i].max_duration = + cpu_to_le32(WL1251_SCAN_MAX_DURATION); + memset(&cmd->channels[i].bssid_lsb, 0xff, 4); + memset(&cmd->channels[i].bssid_msb, 0xff, 2); + cmd->channels[i].early_termination = 0; + cmd->channels[i].tx_power_att = 0; + cmd->channels[i].channel = channels[i]->hw_value; + } + + cmd->params.ssid_len = ssid_len; + if (ssid) + memcpy(cmd->params.ssid, ssid, ssid_len); + + ret = wl1251_cmd_send(wl, CMD_SCAN, cmd, sizeof(*cmd)); + if (ret < 0) { + wl1251_error("cmd scan failed: %d", ret); + goto out; + } + + wl1251_mem_read(wl, wl->cmd_box_addr, cmd, sizeof(*cmd)); + + if (cmd->header.status != CMD_STATUS_SUCCESS) { + wl1251_error("cmd scan status wasn't success: %d", + cmd->header.status); + ret = -EIO; + goto out; + } + +out: + kfree(cmd); + return ret; +} + +int wl1251_cmd_trigger_scan_to(struct wl1251 *wl, u32 timeout) +{ + struct wl1251_cmd_trigger_scan_to *cmd; + int ret; + + wl1251_debug(DEBUG_CMD, "cmd trigger scan to"); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + cmd->timeout = timeout; + + ret = wl1251_cmd_send(wl, CMD_TRIGGER_SCAN_TO, cmd, sizeof(*cmd)); + if (ret < 0) { + wl1251_error("cmd trigger scan to failed: %d", ret); + goto out; + } + +out: + kfree(cmd); + return ret; +} diff --git a/drivers/net/wireless/ti/wl1251/cmd.h b/drivers/net/wireless/ti/wl1251/cmd.h new file mode 100644 index 0000000..ee4f2b3 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/cmd.h @@ -0,0 +1,415 @@ +/* + * This file is part of wl1251 + * + * Copyright (c) 1998-2007 Texas Instruments Incorporated + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL1251_CMD_H__ +#define __WL1251_CMD_H__ + +#include "wl1251.h" + +#include + +struct acx_header; + +int wl1251_cmd_send(struct wl1251 *wl, u16 type, void *buf, size_t buf_len); +int wl1251_cmd_test(struct wl1251 *wl, void *buf, size_t buf_len, u8 answer); +int wl1251_cmd_interrogate(struct wl1251 *wl, u16 id, void *buf, size_t len); +int wl1251_cmd_configure(struct wl1251 *wl, u16 id, void *buf, size_t len); +int wl1251_cmd_vbm(struct wl1251 *wl, u8 identity, + void *bitmap, u16 bitmap_len, u8 bitmap_control); +int wl1251_cmd_data_path(struct wl1251 *wl, u8 channel, bool enable); +int wl1251_cmd_join(struct wl1251 *wl, u8 bss_type, u8 channel, + u16 beacon_interval, u8 dtim_interval); +int wl1251_cmd_ps_mode(struct wl1251 *wl, u8 ps_mode); +int wl1251_cmd_read_memory(struct wl1251 *wl, u32 addr, void *answer, + size_t len); +int wl1251_cmd_template_set(struct wl1251 *wl, u16 cmd_id, + void *buf, size_t buf_len); +int wl1251_cmd_scan(struct wl1251 *wl, u8 *ssid, size_t ssid_len, + struct ieee80211_channel *channels[], + unsigned int n_channels, unsigned int n_probes); +int wl1251_cmd_trigger_scan_to(struct wl1251 *wl, u32 timeout); + +/* unit ms */ +#define WL1251_COMMAND_TIMEOUT 2000 + +enum wl1251_commands { + CMD_RESET = 0, + CMD_INTERROGATE = 1, /*use this to read information elements*/ + CMD_CONFIGURE = 2, /*use this to write information elements*/ + CMD_ENABLE_RX = 3, + CMD_ENABLE_TX = 4, + CMD_DISABLE_RX = 5, + CMD_DISABLE_TX = 6, + CMD_SCAN = 8, + CMD_STOP_SCAN = 9, + CMD_VBM = 10, + CMD_START_JOIN = 11, + CMD_SET_KEYS = 12, + CMD_READ_MEMORY = 13, + CMD_WRITE_MEMORY = 14, + CMD_BEACON = 19, + CMD_PROBE_RESP = 20, + CMD_NULL_DATA = 21, + CMD_PROBE_REQ = 22, + CMD_TEST = 23, + CMD_RADIO_CALIBRATE = 25, /* OBSOLETE */ + CMD_ENABLE_RX_PATH = 27, /* OBSOLETE */ + CMD_NOISE_HIST = 28, + CMD_RX_RESET = 29, + CMD_PS_POLL = 30, + CMD_QOS_NULL_DATA = 31, + CMD_LNA_CONTROL = 32, + CMD_SET_BCN_MODE = 33, + CMD_MEASUREMENT = 34, + CMD_STOP_MEASUREMENT = 35, + CMD_DISCONNECT = 36, + CMD_SET_PS_MODE = 37, + CMD_CHANNEL_SWITCH = 38, + CMD_STOP_CHANNEL_SWICTH = 39, + CMD_AP_DISCOVERY = 40, + CMD_STOP_AP_DISCOVERY = 41, + CMD_SPS_SCAN = 42, + CMD_STOP_SPS_SCAN = 43, + CMD_HEALTH_CHECK = 45, + CMD_DEBUG = 46, + CMD_TRIGGER_SCAN_TO = 47, + + NUM_COMMANDS, + MAX_COMMAND_ID = 0xFFFF, +}; + +#define MAX_CMD_PARAMS 572 + +struct wl1251_cmd_header { + u16 id; + u16 status; + /* payload */ + u8 data[0]; +} __packed; + +struct wl1251_command { + struct wl1251_cmd_header header; + u8 parameters[MAX_CMD_PARAMS]; +} __packed; + +enum { + CMD_MAILBOX_IDLE = 0, + CMD_STATUS_SUCCESS = 1, + CMD_STATUS_UNKNOWN_CMD = 2, + CMD_STATUS_UNKNOWN_IE = 3, + CMD_STATUS_REJECT_MEAS_SG_ACTIVE = 11, + CMD_STATUS_RX_BUSY = 13, + CMD_STATUS_INVALID_PARAM = 14, + CMD_STATUS_TEMPLATE_TOO_LARGE = 15, + CMD_STATUS_OUT_OF_MEMORY = 16, + CMD_STATUS_STA_TABLE_FULL = 17, + CMD_STATUS_RADIO_ERROR = 18, + CMD_STATUS_WRONG_NESTING = 19, + CMD_STATUS_TIMEOUT = 21, /* Driver internal use.*/ + CMD_STATUS_FW_RESET = 22, /* Driver internal use.*/ + MAX_COMMAND_STATUS = 0xff +}; + + +/* + * CMD_READ_MEMORY + * + * The host issues this command to read the WiLink device memory/registers. + * + * Note: The Base Band address has special handling (16 bits registers and + * addresses). For more information, see the hardware specification. + */ +/* + * CMD_WRITE_MEMORY + * + * The host issues this command to write the WiLink device memory/registers. + * + * The Base Band address has special handling (16 bits registers and + * addresses). For more information, see the hardware specification. + */ +#define MAX_READ_SIZE 256 + +struct cmd_read_write_memory { + struct wl1251_cmd_header header; + + /* The address of the memory to read from or write to.*/ + u32 addr; + + /* The amount of data in bytes to read from or write to the WiLink + * device.*/ + u32 size; + + /* The actual value read from or written to the Wilink. The source + of this field is the Host in WRITE command or the Wilink in READ + command. */ + u8 value[MAX_READ_SIZE]; +} __packed; + +#define CMDMBOX_HEADER_LEN 4 +#define CMDMBOX_INFO_ELEM_HEADER_LEN 4 + +#define WL1251_SCAN_MIN_DURATION 30000 +#define WL1251_SCAN_MAX_DURATION 60000 + +#define WL1251_SCAN_NUM_PROBES 3 + +struct wl1251_scan_parameters { + __le32 rx_config_options; + __le32 rx_filter_options; + + /* + * Scan options: + * bit 0: When this bit is set, passive scan. + * bit 1: Band, when this bit is set we scan + * in the 5Ghz band. + * bit 2: voice mode, 0 for normal scan. + * bit 3: scan priority, 1 for high priority. + */ + __le16 scan_options; + + /* Number of channels to scan */ + u8 num_channels; + + /* Number opf probe requests to send, per channel */ + u8 num_probe_requests; + + /* Rate and modulation for probe requests */ + __le16 tx_rate; + + u8 tid_trigger; + u8 ssid_len; + u8 ssid[32]; + +} __packed; + +struct wl1251_scan_ch_parameters { + __le32 min_duration; /* in TU */ + __le32 max_duration; /* in TU */ + u32 bssid_lsb; + u16 bssid_msb; + + /* + * bits 0-3: Early termination count. + * bits 4-5: Early termination condition. + */ + u8 early_termination; + + u8 tx_power_att; + u8 channel; + u8 pad[3]; +} __packed; + +/* SCAN parameters */ +#define SCAN_MAX_NUM_OF_CHANNELS 16 + +struct wl1251_cmd_scan { + struct wl1251_cmd_header header; + + struct wl1251_scan_parameters params; + struct wl1251_scan_ch_parameters channels[SCAN_MAX_NUM_OF_CHANNELS]; +} __packed; + +enum { + BSS_TYPE_IBSS = 0, + BSS_TYPE_STA_BSS = 2, + BSS_TYPE_AP_BSS = 3, + MAX_BSS_TYPE = 0xFF +}; + +#define JOIN_CMD_CTRL_TX_FLUSH 0x80 /* Firmware flushes all Tx */ +#define JOIN_CMD_CTRL_EARLY_WAKEUP_ENABLE 0x01 /* Early wakeup time */ + + +struct cmd_join { + struct wl1251_cmd_header header; + + u32 bssid_lsb; + u16 bssid_msb; + u16 beacon_interval; /* in TBTTs */ + u32 rx_config_options; + u32 rx_filter_options; + + /* + * The target uses this field to determine the rate at + * which to transmit control frame responses (such as + * ACK or CTS frames). + */ + u16 basic_rate_set; + u8 dtim_interval; + u8 tx_ctrl_frame_rate; /* OBSOLETE */ + u8 tx_ctrl_frame_mod; /* OBSOLETE */ + /* + * bits 0-2: This bitwise field specifies the type + * of BSS to start or join (BSS_TYPE_*). + * bit 4: Band - The radio band in which to join + * or start. + * 0 - 2.4GHz band + * 1 - 5GHz band + * bits 3, 5-7: Reserved + */ + u8 bss_type; + u8 channel; + u8 ssid_len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 ctrl; /* JOIN_CMD_CTRL_* */ + u8 tx_mgt_frame_rate; /* OBSOLETE */ + u8 tx_mgt_frame_mod; /* OBSOLETE */ + u8 reserved; +} __packed; + +struct cmd_enabledisable_path { + struct wl1251_cmd_header header; + + u8 channel; + u8 padding[3]; +} __packed; + +#define WL1251_MAX_TEMPLATE_SIZE 300 + +struct wl1251_cmd_packet_template { + struct wl1251_cmd_header header; + + __le16 size; + u8 data[0]; +} __packed; + +#define TIM_ELE_ID 5 +#define PARTIAL_VBM_MAX 251 + +struct wl1251_tim { + u8 identity; + u8 length; + u8 dtim_count; + u8 dtim_period; + u8 bitmap_ctrl; + u8 pvb_field[PARTIAL_VBM_MAX]; /* Partial Virtual Bitmap */ +} __packed; + +/* Virtual Bit Map update */ +struct wl1251_cmd_vbm_update { + struct wl1251_cmd_header header; + __le16 len; + u8 padding[2]; + struct wl1251_tim tim; +} __packed; + +enum wl1251_cmd_ps_mode { + CHIP_ACTIVE_MODE, + CHIP_POWER_SAVE_MODE +}; + +struct wl1251_cmd_ps_params { + struct wl1251_cmd_header header; + + u8 ps_mode; /* STATION_* */ + u8 send_null_data; /* Do we have to send NULL data packet ? */ + u8 retries; /* Number of retires for the initial NULL data packet */ + + /* + * TUs during which the target stays awake after switching + * to power save mode. + */ + u8 hang_over_period; + u16 null_data_rate; + u8 pad[2]; +} __packed; + +struct wl1251_cmd_trigger_scan_to { + struct wl1251_cmd_header header; + + u32 timeout; +} __packed; + +/* HW encryption keys */ +#define NUM_ACCESS_CATEGORIES_COPY 4 +#define MAX_KEY_SIZE 32 + +/* When set, disable HW encryption */ +#define DF_ENCRYPTION_DISABLE 0x01 +/* When set, disable HW decryption */ +#define DF_SNIFF_MODE_ENABLE 0x80 + +enum wl1251_cmd_key_action { + KEY_ADD_OR_REPLACE = 1, + KEY_REMOVE = 2, + KEY_SET_ID = 3, + MAX_KEY_ACTION = 0xffff, +}; + +enum wl1251_cmd_key_type { + KEY_WEP_DEFAULT = 0, + KEY_WEP_ADDR = 1, + KEY_AES_GROUP = 4, + KEY_AES_PAIRWISE = 5, + KEY_WEP_GROUP = 6, + KEY_TKIP_MIC_GROUP = 10, + KEY_TKIP_MIC_PAIRWISE = 11, +}; + +/* + * + * key_type_e key size key format + * ---------- --------- ---------- + * 0x00 5, 13, 29 Key data + * 0x01 5, 13, 29 Key data + * 0x04 16 16 bytes of key data + * 0x05 16 16 bytes of key data + * 0x0a 32 16 bytes of TKIP key data + * 8 bytes of RX MIC key data + * 8 bytes of TX MIC key data + * 0x0b 32 16 bytes of TKIP key data + * 8 bytes of RX MIC key data + * 8 bytes of TX MIC key data + * + */ + +struct wl1251_cmd_set_keys { + struct wl1251_cmd_header header; + + /* Ignored for default WEP key */ + u8 addr[ETH_ALEN]; + + /* key_action_e */ + u16 key_action; + + u16 reserved_1; + + /* key size in bytes */ + u8 key_size; + + /* key_type_e */ + u8 key_type; + u8 ssid_profile; + + /* + * TKIP, AES: frame's key id field. + * For WEP default key: key id; + */ + u8 id; + u8 reserved_2[6]; + u8 key[MAX_KEY_SIZE]; + u16 ac_seq_num16[NUM_ACCESS_CATEGORIES_COPY]; + u32 ac_seq_num32[NUM_ACCESS_CATEGORIES_COPY]; +} __packed; + + +#endif /* __WL1251_CMD_H__ */ diff --git a/drivers/net/wireless/ti/wl1251/debugfs.c b/drivers/net/wireless/ti/wl1251/debugfs.c new file mode 100644 index 0000000..448da1f --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/debugfs.c @@ -0,0 +1,539 @@ +/* + * This file is part of wl1251 + * + * Copyright (C) 2009 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "debugfs.h" + +#include +#include + +#include "wl1251.h" +#include "acx.h" +#include "ps.h" + +/* ms */ +#define WL1251_DEBUGFS_STATS_LIFETIME 1000 + +/* debugfs macros idea from mac80211 */ + +#define DEBUGFS_READONLY_FILE(name, buflen, fmt, value...) \ +static ssize_t name## _read(struct file *file, char __user *userbuf, \ + size_t count, loff_t *ppos) \ +{ \ + struct wl1251 *wl = file->private_data; \ + char buf[buflen]; \ + int res; \ + \ + res = scnprintf(buf, buflen, fmt "\n", ##value); \ + return simple_read_from_buffer(userbuf, count, ppos, buf, res); \ +} \ + \ +static const struct file_operations name## _ops = { \ + .read = name## _read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_ADD(name, parent) \ + wl->debugfs.name = debugfs_create_file(#name, 0400, parent, \ + wl, &name## _ops); \ + if (IS_ERR(wl->debugfs.name)) { \ + ret = PTR_ERR(wl->debugfs.name); \ + wl->debugfs.name = NULL; \ + goto out; \ + } + +#define DEBUGFS_DEL(name) \ + do { \ + debugfs_remove(wl->debugfs.name); \ + wl->debugfs.name = NULL; \ + } while (0) + +#define DEBUGFS_FWSTATS_FILE(sub, name, buflen, fmt) \ +static ssize_t sub## _ ##name## _read(struct file *file, \ + char __user *userbuf, \ + size_t count, loff_t *ppos) \ +{ \ + struct wl1251 *wl = file->private_data; \ + char buf[buflen]; \ + int res; \ + \ + wl1251_debugfs_update_stats(wl); \ + \ + res = scnprintf(buf, buflen, fmt "\n", \ + wl->stats.fw_stats->sub.name); \ + return simple_read_from_buffer(userbuf, count, ppos, buf, res); \ +} \ + \ +static const struct file_operations sub## _ ##name## _ops = { \ + .read = sub## _ ##name## _read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_FWSTATS_ADD(sub, name) \ + DEBUGFS_ADD(sub## _ ##name, wl->debugfs.fw_statistics) + +#define DEBUGFS_FWSTATS_DEL(sub, name) \ + DEBUGFS_DEL(sub## _ ##name) + +static void wl1251_debugfs_update_stats(struct wl1251 *wl) +{ + int ret; + + mutex_lock(&wl->mutex); + + ret = wl1251_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + if (wl->state == WL1251_STATE_ON && + time_after(jiffies, wl->stats.fw_stats_update + + msecs_to_jiffies(WL1251_DEBUGFS_STATS_LIFETIME))) { + wl1251_acx_statistics(wl, wl->stats.fw_stats); + wl->stats.fw_stats_update = jiffies; + } + + wl1251_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); +} + +DEBUGFS_FWSTATS_FILE(tx, internal_desc_overflow, 20, "%u"); + +DEBUGFS_FWSTATS_FILE(rx, out_of_mem, 20, "%u"); +DEBUGFS_FWSTATS_FILE(rx, hdr_overflow, 20, "%u"); +DEBUGFS_FWSTATS_FILE(rx, hw_stuck, 20, "%u"); +DEBUGFS_FWSTATS_FILE(rx, dropped, 20, "%u"); +DEBUGFS_FWSTATS_FILE(rx, fcs_err, 20, "%u"); +DEBUGFS_FWSTATS_FILE(rx, xfr_hint_trig, 20, "%u"); +DEBUGFS_FWSTATS_FILE(rx, path_reset, 20, "%u"); +DEBUGFS_FWSTATS_FILE(rx, reset_counter, 20, "%u"); + +DEBUGFS_FWSTATS_FILE(dma, rx_requested, 20, "%u"); +DEBUGFS_FWSTATS_FILE(dma, rx_errors, 20, "%u"); +DEBUGFS_FWSTATS_FILE(dma, tx_requested, 20, "%u"); +DEBUGFS_FWSTATS_FILE(dma, tx_errors, 20, "%u"); + +DEBUGFS_FWSTATS_FILE(isr, cmd_cmplt, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, fiqs, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, rx_headers, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, rx_mem_overflow, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, rx_rdys, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, irqs, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, tx_procs, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, decrypt_done, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, dma0_done, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, dma1_done, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, tx_exch_complete, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, commands, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, rx_procs, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, hw_pm_mode_changes, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, host_acknowledges, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, pci_pm, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, wakeups, 20, "%u"); +DEBUGFS_FWSTATS_FILE(isr, low_rssi, 20, "%u"); + +DEBUGFS_FWSTATS_FILE(wep, addr_key_count, 20, "%u"); +DEBUGFS_FWSTATS_FILE(wep, default_key_count, 20, "%u"); +/* skipping wep.reserved */ +DEBUGFS_FWSTATS_FILE(wep, key_not_found, 20, "%u"); +DEBUGFS_FWSTATS_FILE(wep, decrypt_fail, 20, "%u"); +DEBUGFS_FWSTATS_FILE(wep, packets, 20, "%u"); +DEBUGFS_FWSTATS_FILE(wep, interrupt, 20, "%u"); + +DEBUGFS_FWSTATS_FILE(pwr, ps_enter, 20, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, elp_enter, 20, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, missing_bcns, 20, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, wake_on_host, 20, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, wake_on_timer_exp, 20, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, tx_with_ps, 20, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, tx_without_ps, 20, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, rcvd_beacons, 20, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, power_save_off, 20, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, enable_ps, 20, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, disable_ps, 20, "%u"); +DEBUGFS_FWSTATS_FILE(pwr, fix_tsf_ps, 20, "%u"); +/* skipping cont_miss_bcns_spread for now */ +DEBUGFS_FWSTATS_FILE(pwr, rcvd_awake_beacons, 20, "%u"); + +DEBUGFS_FWSTATS_FILE(mic, rx_pkts, 20, "%u"); +DEBUGFS_FWSTATS_FILE(mic, calc_failure, 20, "%u"); + +DEBUGFS_FWSTATS_FILE(aes, encrypt_fail, 20, "%u"); +DEBUGFS_FWSTATS_FILE(aes, decrypt_fail, 20, "%u"); +DEBUGFS_FWSTATS_FILE(aes, encrypt_packets, 20, "%u"); +DEBUGFS_FWSTATS_FILE(aes, decrypt_packets, 20, "%u"); +DEBUGFS_FWSTATS_FILE(aes, encrypt_interrupt, 20, "%u"); +DEBUGFS_FWSTATS_FILE(aes, decrypt_interrupt, 20, "%u"); + +DEBUGFS_FWSTATS_FILE(event, heart_beat, 20, "%u"); +DEBUGFS_FWSTATS_FILE(event, calibration, 20, "%u"); +DEBUGFS_FWSTATS_FILE(event, rx_mismatch, 20, "%u"); +DEBUGFS_FWSTATS_FILE(event, rx_mem_empty, 20, "%u"); +DEBUGFS_FWSTATS_FILE(event, rx_pool, 20, "%u"); +DEBUGFS_FWSTATS_FILE(event, oom_late, 20, "%u"); +DEBUGFS_FWSTATS_FILE(event, phy_transmit_error, 20, "%u"); +DEBUGFS_FWSTATS_FILE(event, tx_stuck, 20, "%u"); + +DEBUGFS_FWSTATS_FILE(ps, pspoll_timeouts, 20, "%u"); +DEBUGFS_FWSTATS_FILE(ps, upsd_timeouts, 20, "%u"); +DEBUGFS_FWSTATS_FILE(ps, upsd_max_sptime, 20, "%u"); +DEBUGFS_FWSTATS_FILE(ps, upsd_max_apturn, 20, "%u"); +DEBUGFS_FWSTATS_FILE(ps, pspoll_max_apturn, 20, "%u"); +DEBUGFS_FWSTATS_FILE(ps, pspoll_utilization, 20, "%u"); +DEBUGFS_FWSTATS_FILE(ps, upsd_utilization, 20, "%u"); + +DEBUGFS_FWSTATS_FILE(rxpipe, rx_prep_beacon_drop, 20, "%u"); +DEBUGFS_FWSTATS_FILE(rxpipe, descr_host_int_trig_rx_data, 20, "%u"); +DEBUGFS_FWSTATS_FILE(rxpipe, beacon_buffer_thres_host_int_trig_rx_data, + 20, "%u"); +DEBUGFS_FWSTATS_FILE(rxpipe, missed_beacon_host_int_trig_rx_data, 20, "%u"); +DEBUGFS_FWSTATS_FILE(rxpipe, tx_xfr_host_int_trig_rx_data, 20, "%u"); + +DEBUGFS_READONLY_FILE(retry_count, 20, "%u", wl->stats.retry_count); +DEBUGFS_READONLY_FILE(excessive_retries, 20, "%u", + wl->stats.excessive_retries); + +static ssize_t tx_queue_len_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct wl1251 *wl = file->private_data; + u32 queue_len; + char buf[20]; + int res; + + queue_len = skb_queue_len(&wl->tx_queue); + + res = scnprintf(buf, sizeof(buf), "%u\n", queue_len); + return simple_read_from_buffer(userbuf, count, ppos, buf, res); +} + +static const struct file_operations tx_queue_len_ops = { + .read = tx_queue_len_read, + .open = simple_open, + .llseek = generic_file_llseek, +}; + +static ssize_t tx_queue_status_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct wl1251 *wl = file->private_data; + char buf[3], status; + int len; + + if (wl->tx_queue_stopped) + status = 's'; + else + status = 'r'; + + len = scnprintf(buf, sizeof(buf), "%c\n", status); + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations tx_queue_status_ops = { + .read = tx_queue_status_read, + .open = simple_open, + .llseek = generic_file_llseek, +}; + +static void wl1251_debugfs_delete_files(struct wl1251 *wl) +{ + DEBUGFS_FWSTATS_DEL(tx, internal_desc_overflow); + + DEBUGFS_FWSTATS_DEL(rx, out_of_mem); + DEBUGFS_FWSTATS_DEL(rx, hdr_overflow); + DEBUGFS_FWSTATS_DEL(rx, hw_stuck); + DEBUGFS_FWSTATS_DEL(rx, dropped); + DEBUGFS_FWSTATS_DEL(rx, fcs_err); + DEBUGFS_FWSTATS_DEL(rx, xfr_hint_trig); + DEBUGFS_FWSTATS_DEL(rx, path_reset); + DEBUGFS_FWSTATS_DEL(rx, reset_counter); + + DEBUGFS_FWSTATS_DEL(dma, rx_requested); + DEBUGFS_FWSTATS_DEL(dma, rx_errors); + DEBUGFS_FWSTATS_DEL(dma, tx_requested); + DEBUGFS_FWSTATS_DEL(dma, tx_errors); + + DEBUGFS_FWSTATS_DEL(isr, cmd_cmplt); + DEBUGFS_FWSTATS_DEL(isr, fiqs); + DEBUGFS_FWSTATS_DEL(isr, rx_headers); + DEBUGFS_FWSTATS_DEL(isr, rx_mem_overflow); + DEBUGFS_FWSTATS_DEL(isr, rx_rdys); + DEBUGFS_FWSTATS_DEL(isr, irqs); + DEBUGFS_FWSTATS_DEL(isr, tx_procs); + DEBUGFS_FWSTATS_DEL(isr, decrypt_done); + DEBUGFS_FWSTATS_DEL(isr, dma0_done); + DEBUGFS_FWSTATS_DEL(isr, dma1_done); + DEBUGFS_FWSTATS_DEL(isr, tx_exch_complete); + DEBUGFS_FWSTATS_DEL(isr, commands); + DEBUGFS_FWSTATS_DEL(isr, rx_procs); + DEBUGFS_FWSTATS_DEL(isr, hw_pm_mode_changes); + DEBUGFS_FWSTATS_DEL(isr, host_acknowledges); + DEBUGFS_FWSTATS_DEL(isr, pci_pm); + DEBUGFS_FWSTATS_DEL(isr, wakeups); + DEBUGFS_FWSTATS_DEL(isr, low_rssi); + + DEBUGFS_FWSTATS_DEL(wep, addr_key_count); + DEBUGFS_FWSTATS_DEL(wep, default_key_count); + /* skipping wep.reserved */ + DEBUGFS_FWSTATS_DEL(wep, key_not_found); + DEBUGFS_FWSTATS_DEL(wep, decrypt_fail); + DEBUGFS_FWSTATS_DEL(wep, packets); + DEBUGFS_FWSTATS_DEL(wep, interrupt); + + DEBUGFS_FWSTATS_DEL(pwr, ps_enter); + DEBUGFS_FWSTATS_DEL(pwr, elp_enter); + DEBUGFS_FWSTATS_DEL(pwr, missing_bcns); + DEBUGFS_FWSTATS_DEL(pwr, wake_on_host); + DEBUGFS_FWSTATS_DEL(pwr, wake_on_timer_exp); + DEBUGFS_FWSTATS_DEL(pwr, tx_with_ps); + DEBUGFS_FWSTATS_DEL(pwr, tx_without_ps); + DEBUGFS_FWSTATS_DEL(pwr, rcvd_beacons); + DEBUGFS_FWSTATS_DEL(pwr, power_save_off); + DEBUGFS_FWSTATS_DEL(pwr, enable_ps); + DEBUGFS_FWSTATS_DEL(pwr, disable_ps); + DEBUGFS_FWSTATS_DEL(pwr, fix_tsf_ps); + /* skipping cont_miss_bcns_spread for now */ + DEBUGFS_FWSTATS_DEL(pwr, rcvd_awake_beacons); + + DEBUGFS_FWSTATS_DEL(mic, rx_pkts); + DEBUGFS_FWSTATS_DEL(mic, calc_failure); + + DEBUGFS_FWSTATS_DEL(aes, encrypt_fail); + DEBUGFS_FWSTATS_DEL(aes, decrypt_fail); + DEBUGFS_FWSTATS_DEL(aes, encrypt_packets); + DEBUGFS_FWSTATS_DEL(aes, decrypt_packets); + DEBUGFS_FWSTATS_DEL(aes, encrypt_interrupt); + DEBUGFS_FWSTATS_DEL(aes, decrypt_interrupt); + + DEBUGFS_FWSTATS_DEL(event, heart_beat); + DEBUGFS_FWSTATS_DEL(event, calibration); + DEBUGFS_FWSTATS_DEL(event, rx_mismatch); + DEBUGFS_FWSTATS_DEL(event, rx_mem_empty); + DEBUGFS_FWSTATS_DEL(event, rx_pool); + DEBUGFS_FWSTATS_DEL(event, oom_late); + DEBUGFS_FWSTATS_DEL(event, phy_transmit_error); + DEBUGFS_FWSTATS_DEL(event, tx_stuck); + + DEBUGFS_FWSTATS_DEL(ps, pspoll_timeouts); + DEBUGFS_FWSTATS_DEL(ps, upsd_timeouts); + DEBUGFS_FWSTATS_DEL(ps, upsd_max_sptime); + DEBUGFS_FWSTATS_DEL(ps, upsd_max_apturn); + DEBUGFS_FWSTATS_DEL(ps, pspoll_max_apturn); + DEBUGFS_FWSTATS_DEL(ps, pspoll_utilization); + DEBUGFS_FWSTATS_DEL(ps, upsd_utilization); + + DEBUGFS_FWSTATS_DEL(rxpipe, rx_prep_beacon_drop); + DEBUGFS_FWSTATS_DEL(rxpipe, descr_host_int_trig_rx_data); + DEBUGFS_FWSTATS_DEL(rxpipe, beacon_buffer_thres_host_int_trig_rx_data); + DEBUGFS_FWSTATS_DEL(rxpipe, missed_beacon_host_int_trig_rx_data); + DEBUGFS_FWSTATS_DEL(rxpipe, tx_xfr_host_int_trig_rx_data); + + DEBUGFS_DEL(tx_queue_len); + DEBUGFS_DEL(tx_queue_status); + DEBUGFS_DEL(retry_count); + DEBUGFS_DEL(excessive_retries); +} + +static int wl1251_debugfs_add_files(struct wl1251 *wl) +{ + int ret = 0; + + DEBUGFS_FWSTATS_ADD(tx, internal_desc_overflow); + + DEBUGFS_FWSTATS_ADD(rx, out_of_mem); + DEBUGFS_FWSTATS_ADD(rx, hdr_overflow); + DEBUGFS_FWSTATS_ADD(rx, hw_stuck); + DEBUGFS_FWSTATS_ADD(rx, dropped); + DEBUGFS_FWSTATS_ADD(rx, fcs_err); + DEBUGFS_FWSTATS_ADD(rx, xfr_hint_trig); + DEBUGFS_FWSTATS_ADD(rx, path_reset); + DEBUGFS_FWSTATS_ADD(rx, reset_counter); + + DEBUGFS_FWSTATS_ADD(dma, rx_requested); + DEBUGFS_FWSTATS_ADD(dma, rx_errors); + DEBUGFS_FWSTATS_ADD(dma, tx_requested); + DEBUGFS_FWSTATS_ADD(dma, tx_errors); + + DEBUGFS_FWSTATS_ADD(isr, cmd_cmplt); + DEBUGFS_FWSTATS_ADD(isr, fiqs); + DEBUGFS_FWSTATS_ADD(isr, rx_headers); + DEBUGFS_FWSTATS_ADD(isr, rx_mem_overflow); + DEBUGFS_FWSTATS_ADD(isr, rx_rdys); + DEBUGFS_FWSTATS_ADD(isr, irqs); + DEBUGFS_FWSTATS_ADD(isr, tx_procs); + DEBUGFS_FWSTATS_ADD(isr, decrypt_done); + DEBUGFS_FWSTATS_ADD(isr, dma0_done); + DEBUGFS_FWSTATS_ADD(isr, dma1_done); + DEBUGFS_FWSTATS_ADD(isr, tx_exch_complete); + DEBUGFS_FWSTATS_ADD(isr, commands); + DEBUGFS_FWSTATS_ADD(isr, rx_procs); + DEBUGFS_FWSTATS_ADD(isr, hw_pm_mode_changes); + DEBUGFS_FWSTATS_ADD(isr, host_acknowledges); + DEBUGFS_FWSTATS_ADD(isr, pci_pm); + DEBUGFS_FWSTATS_ADD(isr, wakeups); + DEBUGFS_FWSTATS_ADD(isr, low_rssi); + + DEBUGFS_FWSTATS_ADD(wep, addr_key_count); + DEBUGFS_FWSTATS_ADD(wep, default_key_count); + /* skipping wep.reserved */ + DEBUGFS_FWSTATS_ADD(wep, key_not_found); + DEBUGFS_FWSTATS_ADD(wep, decrypt_fail); + DEBUGFS_FWSTATS_ADD(wep, packets); + DEBUGFS_FWSTATS_ADD(wep, interrupt); + + DEBUGFS_FWSTATS_ADD(pwr, ps_enter); + DEBUGFS_FWSTATS_ADD(pwr, elp_enter); + DEBUGFS_FWSTATS_ADD(pwr, missing_bcns); + DEBUGFS_FWSTATS_ADD(pwr, wake_on_host); + DEBUGFS_FWSTATS_ADD(pwr, wake_on_timer_exp); + DEBUGFS_FWSTATS_ADD(pwr, tx_with_ps); + DEBUGFS_FWSTATS_ADD(pwr, tx_without_ps); + DEBUGFS_FWSTATS_ADD(pwr, rcvd_beacons); + DEBUGFS_FWSTATS_ADD(pwr, power_save_off); + DEBUGFS_FWSTATS_ADD(pwr, enable_ps); + DEBUGFS_FWSTATS_ADD(pwr, disable_ps); + DEBUGFS_FWSTATS_ADD(pwr, fix_tsf_ps); + /* skipping cont_miss_bcns_spread for now */ + DEBUGFS_FWSTATS_ADD(pwr, rcvd_awake_beacons); + + DEBUGFS_FWSTATS_ADD(mic, rx_pkts); + DEBUGFS_FWSTATS_ADD(mic, calc_failure); + + DEBUGFS_FWSTATS_ADD(aes, encrypt_fail); + DEBUGFS_FWSTATS_ADD(aes, decrypt_fail); + DEBUGFS_FWSTATS_ADD(aes, encrypt_packets); + DEBUGFS_FWSTATS_ADD(aes, decrypt_packets); + DEBUGFS_FWSTATS_ADD(aes, encrypt_interrupt); + DEBUGFS_FWSTATS_ADD(aes, decrypt_interrupt); + + DEBUGFS_FWSTATS_ADD(event, heart_beat); + DEBUGFS_FWSTATS_ADD(event, calibration); + DEBUGFS_FWSTATS_ADD(event, rx_mismatch); + DEBUGFS_FWSTATS_ADD(event, rx_mem_empty); + DEBUGFS_FWSTATS_ADD(event, rx_pool); + DEBUGFS_FWSTATS_ADD(event, oom_late); + DEBUGFS_FWSTATS_ADD(event, phy_transmit_error); + DEBUGFS_FWSTATS_ADD(event, tx_stuck); + + DEBUGFS_FWSTATS_ADD(ps, pspoll_timeouts); + DEBUGFS_FWSTATS_ADD(ps, upsd_timeouts); + DEBUGFS_FWSTATS_ADD(ps, upsd_max_sptime); + DEBUGFS_FWSTATS_ADD(ps, upsd_max_apturn); + DEBUGFS_FWSTATS_ADD(ps, pspoll_max_apturn); + DEBUGFS_FWSTATS_ADD(ps, pspoll_utilization); + DEBUGFS_FWSTATS_ADD(ps, upsd_utilization); + + DEBUGFS_FWSTATS_ADD(rxpipe, rx_prep_beacon_drop); + DEBUGFS_FWSTATS_ADD(rxpipe, descr_host_int_trig_rx_data); + DEBUGFS_FWSTATS_ADD(rxpipe, beacon_buffer_thres_host_int_trig_rx_data); + DEBUGFS_FWSTATS_ADD(rxpipe, missed_beacon_host_int_trig_rx_data); + DEBUGFS_FWSTATS_ADD(rxpipe, tx_xfr_host_int_trig_rx_data); + + DEBUGFS_ADD(tx_queue_len, wl->debugfs.rootdir); + DEBUGFS_ADD(tx_queue_status, wl->debugfs.rootdir); + DEBUGFS_ADD(retry_count, wl->debugfs.rootdir); + DEBUGFS_ADD(excessive_retries, wl->debugfs.rootdir); + +out: + if (ret < 0) + wl1251_debugfs_delete_files(wl); + + return ret; +} + +void wl1251_debugfs_reset(struct wl1251 *wl) +{ + if (wl->stats.fw_stats != NULL) + memset(wl->stats.fw_stats, 0, sizeof(*wl->stats.fw_stats)); + wl->stats.retry_count = 0; + wl->stats.excessive_retries = 0; +} + +int wl1251_debugfs_init(struct wl1251 *wl) +{ + int ret; + + wl->debugfs.rootdir = debugfs_create_dir(KBUILD_MODNAME, NULL); + + if (IS_ERR(wl->debugfs.rootdir)) { + ret = PTR_ERR(wl->debugfs.rootdir); + wl->debugfs.rootdir = NULL; + goto err; + } + + wl->debugfs.fw_statistics = debugfs_create_dir("fw-statistics", + wl->debugfs.rootdir); + + if (IS_ERR(wl->debugfs.fw_statistics)) { + ret = PTR_ERR(wl->debugfs.fw_statistics); + wl->debugfs.fw_statistics = NULL; + goto err_root; + } + + wl->stats.fw_stats = kzalloc(sizeof(*wl->stats.fw_stats), + GFP_KERNEL); + + if (!wl->stats.fw_stats) { + ret = -ENOMEM; + goto err_fw; + } + + wl->stats.fw_stats_update = jiffies; + + ret = wl1251_debugfs_add_files(wl); + + if (ret < 0) + goto err_file; + + return 0; + +err_file: + kfree(wl->stats.fw_stats); + wl->stats.fw_stats = NULL; + +err_fw: + debugfs_remove(wl->debugfs.fw_statistics); + wl->debugfs.fw_statistics = NULL; + +err_root: + debugfs_remove(wl->debugfs.rootdir); + wl->debugfs.rootdir = NULL; + +err: + return ret; +} + +void wl1251_debugfs_exit(struct wl1251 *wl) +{ + wl1251_debugfs_delete_files(wl); + + kfree(wl->stats.fw_stats); + wl->stats.fw_stats = NULL; + + debugfs_remove(wl->debugfs.fw_statistics); + wl->debugfs.fw_statistics = NULL; + + debugfs_remove(wl->debugfs.rootdir); + wl->debugfs.rootdir = NULL; + +} diff --git a/drivers/net/wireless/ti/wl1251/debugfs.h b/drivers/net/wireless/ti/wl1251/debugfs.h new file mode 100644 index 0000000..b3417c0 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/debugfs.h @@ -0,0 +1,31 @@ +/* + * This file is part of wl1251 + * + * Copyright (C) 2009 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef WL1251_DEBUGFS_H +#define WL1251_DEBUGFS_H + +#include "wl1251.h" + +int wl1251_debugfs_init(struct wl1251 *wl); +void wl1251_debugfs_exit(struct wl1251 *wl); +void wl1251_debugfs_reset(struct wl1251 *wl); + +#endif /* WL1251_DEBUGFS_H */ diff --git a/drivers/net/wireless/ti/wl1251/event.c b/drivers/net/wireless/ti/wl1251/event.c new file mode 100644 index 0000000..74ae8e1 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/event.c @@ -0,0 +1,191 @@ +/* + * This file is part of wl1251 + * + * Copyright (c) 1998-2007 Texas Instruments Incorporated + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "wl1251.h" +#include "reg.h" +#include "io.h" +#include "event.h" +#include "ps.h" + +static int wl1251_event_scan_complete(struct wl1251 *wl, + struct event_mailbox *mbox) +{ + int ret = 0; + + wl1251_debug(DEBUG_EVENT, "status: 0x%x, channels: %d", + mbox->scheduled_scan_status, + mbox->scheduled_scan_channels); + + if (wl->scanning) { + ieee80211_scan_completed(wl->hw, false); + wl1251_debug(DEBUG_MAC80211, "mac80211 hw scan completed"); + wl->scanning = false; + if (wl->hw->conf.flags & IEEE80211_CONF_IDLE) + ret = wl1251_ps_set_mode(wl, STATION_IDLE); + } + + return ret; +} + +static void wl1251_event_mbox_dump(struct event_mailbox *mbox) +{ + wl1251_debug(DEBUG_EVENT, "MBOX DUMP:"); + wl1251_debug(DEBUG_EVENT, "\tvector: 0x%x", mbox->events_vector); + wl1251_debug(DEBUG_EVENT, "\tmask: 0x%x", mbox->events_mask); +} + +static int wl1251_event_process(struct wl1251 *wl, struct event_mailbox *mbox) +{ + int ret; + u32 vector; + + wl1251_event_mbox_dump(mbox); + + vector = mbox->events_vector & ~(mbox->events_mask); + wl1251_debug(DEBUG_EVENT, "vector: 0x%x", vector); + + if (vector & SCAN_COMPLETE_EVENT_ID) { + ret = wl1251_event_scan_complete(wl, mbox); + if (ret < 0) + return ret; + } + + if (vector & BSS_LOSE_EVENT_ID) { + wl1251_debug(DEBUG_EVENT, "BSS_LOSE_EVENT"); + + if (wl->psm_requested && + wl->station_mode != STATION_ACTIVE_MODE) { + ret = wl1251_ps_set_mode(wl, STATION_ACTIVE_MODE); + if (ret < 0) + return ret; + } + } + + if (vector & SYNCHRONIZATION_TIMEOUT_EVENT_ID) { + wl1251_debug(DEBUG_EVENT, "SYNCHRONIZATION_TIMEOUT_EVENT"); + + /* indicate to the stack, that beacons have been lost */ + ieee80211_beacon_loss(wl->vif); + } + + if (vector & REGAINED_BSS_EVENT_ID) { + if (wl->psm_requested) { + ret = wl1251_ps_set_mode(wl, STATION_POWER_SAVE_MODE); + if (ret < 0) + return ret; + } + } + + if (wl->vif && wl->rssi_thold) { + if (vector & ROAMING_TRIGGER_LOW_RSSI_EVENT_ID) { + wl1251_debug(DEBUG_EVENT, + "ROAMING_TRIGGER_LOW_RSSI_EVENT"); + ieee80211_cqm_rssi_notify(wl->vif, + NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW, + GFP_KERNEL); + } + + if (vector & ROAMING_TRIGGER_REGAINED_RSSI_EVENT_ID) { + wl1251_debug(DEBUG_EVENT, + "ROAMING_TRIGGER_REGAINED_RSSI_EVENT"); + ieee80211_cqm_rssi_notify(wl->vif, + NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH, + GFP_KERNEL); + } + } + + return 0; +} + +/* + * Poll the mailbox event field until any of the bits in the mask is set or a + * timeout occurs (WL1251_EVENT_TIMEOUT in msecs) + */ +int wl1251_event_wait(struct wl1251 *wl, u32 mask, int timeout_ms) +{ + u32 events_vector, event; + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(timeout_ms); + + do { + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + + msleep(1); + + /* read from both event fields */ + wl1251_mem_read(wl, wl->mbox_ptr[0], &events_vector, + sizeof(events_vector)); + event = events_vector & mask; + wl1251_mem_read(wl, wl->mbox_ptr[1], &events_vector, + sizeof(events_vector)); + event |= events_vector & mask; + } while (!event); + + return 0; +} + +int wl1251_event_unmask(struct wl1251 *wl) +{ + int ret; + + ret = wl1251_acx_event_mbox_mask(wl, ~(wl->event_mask)); + if (ret < 0) + return ret; + + return 0; +} + +void wl1251_event_mbox_config(struct wl1251 *wl) +{ + wl->mbox_ptr[0] = wl1251_reg_read32(wl, REG_EVENT_MAILBOX_PTR); + wl->mbox_ptr[1] = wl->mbox_ptr[0] + sizeof(struct event_mailbox); + + wl1251_debug(DEBUG_EVENT, "MBOX ptrs: 0x%x 0x%x", + wl->mbox_ptr[0], wl->mbox_ptr[1]); +} + +int wl1251_event_handle(struct wl1251 *wl, u8 mbox_num) +{ + struct event_mailbox mbox; + int ret; + + wl1251_debug(DEBUG_EVENT, "EVENT on mbox %d", mbox_num); + + if (mbox_num > 1) + return -EINVAL; + + /* first we read the mbox descriptor */ + wl1251_mem_read(wl, wl->mbox_ptr[mbox_num], &mbox, + sizeof(struct event_mailbox)); + + /* process the descriptor */ + ret = wl1251_event_process(wl, &mbox); + if (ret < 0) + return ret; + + /* then we let the firmware know it can go on...*/ + wl1251_reg_write32(wl, ACX_REG_INTERRUPT_TRIG, INTR_TRIG_EVENT_ACK); + + return 0; +} diff --git a/drivers/net/wireless/ti/wl1251/event.h b/drivers/net/wireless/ti/wl1251/event.h new file mode 100644 index 0000000..30eb5d1 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/event.h @@ -0,0 +1,120 @@ +/* + * This file is part of wl1251 + * + * Copyright (c) 1998-2007 Texas Instruments Incorporated + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL1251_EVENT_H__ +#define __WL1251_EVENT_H__ + +/* + * Mbox events + * + * The event mechanism is based on a pair of event buffers (buffers A and + * B) at fixed locations in the target's memory. The host processes one + * buffer while the other buffer continues to collect events. If the host + * is not processing events, an interrupt is issued to signal that a buffer + * is ready. Once the host is done with processing events from one buffer, + * it signals the target (with an ACK interrupt) that the event buffer is + * free. + */ + +enum { + RESERVED1_EVENT_ID = BIT(0), + RESERVED2_EVENT_ID = BIT(1), + MEASUREMENT_START_EVENT_ID = BIT(2), + SCAN_COMPLETE_EVENT_ID = BIT(3), + CALIBRATION_COMPLETE_EVENT_ID = BIT(4), + ROAMING_TRIGGER_LOW_RSSI_EVENT_ID = BIT(5), + PS_REPORT_EVENT_ID = BIT(6), + SYNCHRONIZATION_TIMEOUT_EVENT_ID = BIT(7), + HEALTH_REPORT_EVENT_ID = BIT(8), + ACI_DETECTION_EVENT_ID = BIT(9), + DEBUG_REPORT_EVENT_ID = BIT(10), + MAC_STATUS_EVENT_ID = BIT(11), + DISCONNECT_EVENT_COMPLETE_ID = BIT(12), + JOIN_EVENT_COMPLETE_ID = BIT(13), + CHANNEL_SWITCH_COMPLETE_EVENT_ID = BIT(14), + BSS_LOSE_EVENT_ID = BIT(15), + ROAMING_TRIGGER_MAX_TX_RETRY_EVENT_ID = BIT(16), + MEASUREMENT_COMPLETE_EVENT_ID = BIT(17), + AP_DISCOVERY_COMPLETE_EVENT_ID = BIT(18), + SCHEDULED_SCAN_COMPLETE_EVENT_ID = BIT(19), + PSPOLL_DELIVERY_FAILURE_EVENT_ID = BIT(20), + RESET_BSS_EVENT_ID = BIT(21), + REGAINED_BSS_EVENT_ID = BIT(22), + ROAMING_TRIGGER_REGAINED_RSSI_EVENT_ID = BIT(23), + ROAMING_TRIGGER_LOW_SNR_EVENT_ID = BIT(24), + ROAMING_TRIGGER_REGAINED_SNR_EVENT_ID = BIT(25), + + DBG_EVENT_ID = BIT(26), + BT_PTA_SENSE_EVENT_ID = BIT(27), + BT_PTA_PREDICTION_EVENT_ID = BIT(28), + BT_PTA_AVALANCHE_EVENT_ID = BIT(29), + + PLT_RX_CALIBRATION_COMPLETE_EVENT_ID = BIT(30), + + EVENT_MBOX_ALL_EVENT_ID = 0x7fffffff, +}; + +struct event_debug_report { + u8 debug_event_id; + u8 num_params; + u16 pad; + u32 report_1; + u32 report_2; + u32 report_3; +} __packed; + +struct event_mailbox { + u32 events_vector; + u32 events_mask; + u32 reserved_1; + u32 reserved_2; + + char average_rssi_level; + u8 ps_status; + u8 channel_switch_status; + u8 scheduled_scan_status; + + /* Channels scanned by the scheduled scan */ + u16 scheduled_scan_channels; + + /* If bit 0 is set -> target's fatal error */ + u16 health_report; + u16 bad_fft_counter; + u8 bt_pta_sense_info; + u8 bt_pta_protective_info; + u32 reserved; + u32 debug_report[2]; + + /* Number of FCS errors since last event */ + u32 fcs_err_counter; + + struct event_debug_report report; + u8 average_snr_level; + u8 padding[19]; +} __packed; + +int wl1251_event_unmask(struct wl1251 *wl); +void wl1251_event_mbox_config(struct wl1251 *wl); +int wl1251_event_handle(struct wl1251 *wl, u8 mbox); +int wl1251_event_wait(struct wl1251 *wl, u32 mask, int timeout_ms); + +#endif diff --git a/drivers/net/wireless/ti/wl1251/init.c b/drivers/net/wireless/ti/wl1251/init.c new file mode 100644 index 0000000..89b43d3 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/init.c @@ -0,0 +1,423 @@ +/* + * This file is part of wl1251 + * + * Copyright (C) 2009 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + +#include "init.h" +#include "wl12xx_80211.h" +#include "acx.h" +#include "cmd.h" +#include "reg.h" + +int wl1251_hw_init_hwenc_config(struct wl1251 *wl) +{ + int ret; + + ret = wl1251_acx_feature_cfg(wl); + if (ret < 0) { + wl1251_warning("couldn't set feature config"); + return ret; + } + + ret = wl1251_acx_default_key(wl, wl->default_key); + if (ret < 0) { + wl1251_warning("couldn't set default key"); + return ret; + } + + return 0; +} + +int wl1251_hw_init_templates_config(struct wl1251 *wl) +{ + int ret; + u8 partial_vbm[PARTIAL_VBM_MAX]; + + /* send empty templates for fw memory reservation */ + ret = wl1251_cmd_template_set(wl, CMD_PROBE_REQ, NULL, + sizeof(struct wl12xx_probe_req_template)); + if (ret < 0) + return ret; + + ret = wl1251_cmd_template_set(wl, CMD_NULL_DATA, NULL, + sizeof(struct wl12xx_null_data_template)); + if (ret < 0) + return ret; + + ret = wl1251_cmd_template_set(wl, CMD_PS_POLL, NULL, + sizeof(struct wl12xx_ps_poll_template)); + if (ret < 0) + return ret; + + ret = wl1251_cmd_template_set(wl, CMD_QOS_NULL_DATA, NULL, + sizeof + (struct wl12xx_qos_null_data_template)); + if (ret < 0) + return ret; + + ret = wl1251_cmd_template_set(wl, CMD_PROBE_RESP, NULL, + sizeof + (struct wl12xx_probe_resp_template)); + if (ret < 0) + return ret; + + ret = wl1251_cmd_template_set(wl, CMD_BEACON, NULL, + sizeof + (struct wl12xx_beacon_template)); + if (ret < 0) + return ret; + + /* tim templates, first reserve space then allocate an empty one */ + memset(partial_vbm, 0, PARTIAL_VBM_MAX); + ret = wl1251_cmd_vbm(wl, TIM_ELE_ID, partial_vbm, PARTIAL_VBM_MAX, 0); + if (ret < 0) + return ret; + + ret = wl1251_cmd_vbm(wl, TIM_ELE_ID, partial_vbm, 1, 0); + if (ret < 0) + return ret; + + return 0; +} + +int wl1251_hw_init_rx_config(struct wl1251 *wl, u32 config, u32 filter) +{ + int ret; + + ret = wl1251_acx_rx_msdu_life_time(wl, RX_MSDU_LIFETIME_DEF); + if (ret < 0) + return ret; + + ret = wl1251_acx_rx_config(wl, config, filter); + if (ret < 0) + return ret; + + return 0; +} + +int wl1251_hw_init_phy_config(struct wl1251 *wl) +{ + int ret; + + ret = wl1251_acx_pd_threshold(wl); + if (ret < 0) + return ret; + + ret = wl1251_acx_slot(wl, DEFAULT_SLOT_TIME); + if (ret < 0) + return ret; + + ret = wl1251_acx_group_address_tbl(wl); + if (ret < 0) + return ret; + + ret = wl1251_acx_service_period_timeout(wl); + if (ret < 0) + return ret; + + ret = wl1251_acx_rts_threshold(wl, RTS_THRESHOLD_DEF); + if (ret < 0) + return ret; + + return 0; +} + +int wl1251_hw_init_beacon_filter(struct wl1251 *wl) +{ + int ret; + + /* disable beacon filtering at this stage */ + ret = wl1251_acx_beacon_filter_opt(wl, false); + if (ret < 0) + return ret; + + ret = wl1251_acx_beacon_filter_table(wl); + if (ret < 0) + return ret; + + return 0; +} + +int wl1251_hw_init_pta(struct wl1251 *wl) +{ + int ret; + + ret = wl1251_acx_sg_enable(wl); + if (ret < 0) + return ret; + + ret = wl1251_acx_sg_cfg(wl); + if (ret < 0) + return ret; + + return 0; +} + +int wl1251_hw_init_energy_detection(struct wl1251 *wl) +{ + int ret; + + ret = wl1251_acx_cca_threshold(wl); + if (ret < 0) + return ret; + + return 0; +} + +int wl1251_hw_init_beacon_broadcast(struct wl1251 *wl) +{ + int ret; + + ret = wl1251_acx_bcn_dtim_options(wl); + if (ret < 0) + return ret; + + return 0; +} + +int wl1251_hw_init_power_auth(struct wl1251 *wl) +{ + return wl1251_acx_sleep_auth(wl, WL1251_PSM_CAM); +} + +int wl1251_hw_init_mem_config(struct wl1251 *wl) +{ + int ret; + + ret = wl1251_acx_mem_cfg(wl); + if (ret < 0) + return ret; + + wl->target_mem_map = kzalloc(sizeof(struct wl1251_acx_mem_map), + GFP_KERNEL); + if (!wl->target_mem_map) { + wl1251_error("couldn't allocate target memory map"); + return -ENOMEM; + } + + /* we now ask for the firmware built memory map */ + ret = wl1251_acx_mem_map(wl, wl->target_mem_map, + sizeof(struct wl1251_acx_mem_map)); + if (ret < 0) { + wl1251_error("couldn't retrieve firmware memory map"); + kfree(wl->target_mem_map); + wl->target_mem_map = NULL; + return ret; + } + + return 0; +} + +static int wl1251_hw_init_txq_fill(u8 qid, + struct acx_tx_queue_qos_config *config, + u32 num_blocks) +{ + config->qid = qid; + + switch (qid) { + case QOS_AC_BE: + config->high_threshold = + (QOS_TX_HIGH_BE_DEF * num_blocks) / 100; + config->low_threshold = + (QOS_TX_LOW_BE_DEF * num_blocks) / 100; + break; + case QOS_AC_BK: + config->high_threshold = + (QOS_TX_HIGH_BK_DEF * num_blocks) / 100; + config->low_threshold = + (QOS_TX_LOW_BK_DEF * num_blocks) / 100; + break; + case QOS_AC_VI: + config->high_threshold = + (QOS_TX_HIGH_VI_DEF * num_blocks) / 100; + config->low_threshold = + (QOS_TX_LOW_VI_DEF * num_blocks) / 100; + break; + case QOS_AC_VO: + config->high_threshold = + (QOS_TX_HIGH_VO_DEF * num_blocks) / 100; + config->low_threshold = + (QOS_TX_LOW_VO_DEF * num_blocks) / 100; + break; + default: + wl1251_error("Invalid TX queue id: %d", qid); + return -EINVAL; + } + + return 0; +} + +static int wl1251_hw_init_tx_queue_config(struct wl1251 *wl) +{ + struct acx_tx_queue_qos_config *config; + struct wl1251_acx_mem_map *wl_mem_map = wl->target_mem_map; + int ret, i; + + wl1251_debug(DEBUG_ACX, "acx tx queue config"); + + config = kzalloc(sizeof(*config), GFP_KERNEL); + if (!config) { + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < MAX_NUM_OF_AC; i++) { + ret = wl1251_hw_init_txq_fill(i, config, + wl_mem_map->num_tx_mem_blocks); + if (ret < 0) + goto out; + + ret = wl1251_cmd_configure(wl, ACX_TX_QUEUE_CFG, + config, sizeof(*config)); + if (ret < 0) + goto out; + } + + wl1251_acx_ac_cfg(wl, AC_BE, CWMIN_BE, CWMAX_BE, AIFS_DIFS, TXOP_BE); + wl1251_acx_ac_cfg(wl, AC_BK, CWMIN_BK, CWMAX_BK, AIFS_DIFS, TXOP_BK); + wl1251_acx_ac_cfg(wl, AC_VI, CWMIN_VI, CWMAX_VI, AIFS_DIFS, TXOP_VI); + wl1251_acx_ac_cfg(wl, AC_VO, CWMIN_VO, CWMAX_VO, AIFS_DIFS, TXOP_VO); + +out: + kfree(config); + return ret; +} + +static int wl1251_hw_init_data_path_config(struct wl1251 *wl) +{ + int ret; + + /* asking for the data path parameters */ + wl->data_path = kzalloc(sizeof(struct acx_data_path_params_resp), + GFP_KERNEL); + if (!wl->data_path) { + wl1251_error("Couldnt allocate data path parameters"); + return -ENOMEM; + } + + ret = wl1251_acx_data_path_params(wl, wl->data_path); + if (ret < 0) { + kfree(wl->data_path); + wl->data_path = NULL; + return ret; + } + + return 0; +} + + +int wl1251_hw_init(struct wl1251 *wl) +{ + struct wl1251_acx_mem_map *wl_mem_map; + int ret; + + ret = wl1251_hw_init_hwenc_config(wl); + if (ret < 0) + return ret; + + /* Template settings */ + ret = wl1251_hw_init_templates_config(wl); + if (ret < 0) + return ret; + + /* Default memory configuration */ + ret = wl1251_hw_init_mem_config(wl); + if (ret < 0) + return ret; + + /* Default data path configuration */ + ret = wl1251_hw_init_data_path_config(wl); + if (ret < 0) + goto out_free_memmap; + + /* RX config */ + ret = wl1251_hw_init_rx_config(wl, + RX_CFG_PROMISCUOUS | RX_CFG_TSF, + RX_FILTER_OPTION_DEF); + /* RX_CONFIG_OPTION_ANY_DST_ANY_BSS, + RX_FILTER_OPTION_FILTER_ALL); */ + if (ret < 0) + goto out_free_data_path; + + /* TX queues config */ + ret = wl1251_hw_init_tx_queue_config(wl); + if (ret < 0) + goto out_free_data_path; + + /* PHY layer config */ + ret = wl1251_hw_init_phy_config(wl); + if (ret < 0) + goto out_free_data_path; + + /* Initialize connection monitoring thresholds */ + ret = wl1251_acx_conn_monit_params(wl); + if (ret < 0) + goto out_free_data_path; + + /* Beacon filtering */ + ret = wl1251_hw_init_beacon_filter(wl); + if (ret < 0) + goto out_free_data_path; + + /* Bluetooth WLAN coexistence */ + ret = wl1251_hw_init_pta(wl); + if (ret < 0) + goto out_free_data_path; + + /* Energy detection */ + ret = wl1251_hw_init_energy_detection(wl); + if (ret < 0) + goto out_free_data_path; + + /* Beacons and boradcast settings */ + ret = wl1251_hw_init_beacon_broadcast(wl); + if (ret < 0) + goto out_free_data_path; + + /* Enable data path */ + ret = wl1251_cmd_data_path(wl, wl->channel, 1); + if (ret < 0) + goto out_free_data_path; + + /* Default power state */ + ret = wl1251_hw_init_power_auth(wl); + if (ret < 0) + goto out_free_data_path; + + wl_mem_map = wl->target_mem_map; + wl1251_info("%d tx blocks at 0x%x, %d rx blocks at 0x%x", + wl_mem_map->num_tx_mem_blocks, + wl->data_path->tx_control_addr, + wl_mem_map->num_rx_mem_blocks, + wl->data_path->rx_control_addr); + + return 0; + + out_free_data_path: + kfree(wl->data_path); + + out_free_memmap: + kfree(wl->target_mem_map); + + return ret; +} diff --git a/drivers/net/wireless/ti/wl1251/init.h b/drivers/net/wireless/ti/wl1251/init.h new file mode 100644 index 0000000..543f175 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/init.h @@ -0,0 +1,86 @@ +/* + * This file is part of wl1251 + * + * Copyright (C) 2009 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL1251_INIT_H__ +#define __WL1251_INIT_H__ + +#include "wl1251.h" + +enum { + /* best effort/legacy */ + AC_BE = 0, + + /* background */ + AC_BK = 1, + + /* video */ + AC_VI = 2, + + /* voice */ + AC_VO = 3, + + /* broadcast dummy access category */ + AC_BCAST = 4, + + NUM_ACCESS_CATEGORIES = 4 +}; + +/* following are defult values for the IE fields*/ +#define CWMIN_BK 15 +#define CWMIN_BE 15 +#define CWMIN_VI 7 +#define CWMIN_VO 3 +#define CWMAX_BK 1023 +#define CWMAX_BE 63 +#define CWMAX_VI 15 +#define CWMAX_VO 7 + +/* slot number setting to start transmission at PIFS interval */ +#define AIFS_PIFS 1 + +/* + * slot number setting to start transmission at DIFS interval - normal DCF + * access + */ +#define AIFS_DIFS 2 + +#define AIFSN_BK 7 +#define AIFSN_BE 3 +#define AIFSN_VI AIFS_PIFS +#define AIFSN_VO AIFS_PIFS +#define TXOP_BK 0 +#define TXOP_BE 0 +#define TXOP_VI 3008 +#define TXOP_VO 1504 + +int wl1251_hw_init_hwenc_config(struct wl1251 *wl); +int wl1251_hw_init_templates_config(struct wl1251 *wl); +int wl1251_hw_init_rx_config(struct wl1251 *wl, u32 config, u32 filter); +int wl1251_hw_init_phy_config(struct wl1251 *wl); +int wl1251_hw_init_beacon_filter(struct wl1251 *wl); +int wl1251_hw_init_pta(struct wl1251 *wl); +int wl1251_hw_init_energy_detection(struct wl1251 *wl); +int wl1251_hw_init_beacon_broadcast(struct wl1251 *wl); +int wl1251_hw_init_power_auth(struct wl1251 *wl); +int wl1251_hw_init_mem_config(struct wl1251 *wl); +int wl1251_hw_init(struct wl1251 *wl); + +#endif diff --git a/drivers/net/wireless/ti/wl1251/io.c b/drivers/net/wireless/ti/wl1251/io.c new file mode 100644 index 0000000..cdcadbf --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/io.c @@ -0,0 +1,194 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "wl1251.h" +#include "reg.h" +#include "io.h" + +/* FIXME: this is static data nowadays and the table can be removed */ +static enum wl12xx_acx_int_reg wl1251_io_reg_table[ACX_REG_TABLE_LEN] = { + [ACX_REG_INTERRUPT_TRIG] = (REGISTERS_BASE + 0x0474), + [ACX_REG_INTERRUPT_TRIG_H] = (REGISTERS_BASE + 0x0478), + [ACX_REG_INTERRUPT_MASK] = (REGISTERS_BASE + 0x0494), + [ACX_REG_HINT_MASK_SET] = (REGISTERS_BASE + 0x0498), + [ACX_REG_HINT_MASK_CLR] = (REGISTERS_BASE + 0x049C), + [ACX_REG_INTERRUPT_NO_CLEAR] = (REGISTERS_BASE + 0x04B0), + [ACX_REG_INTERRUPT_CLEAR] = (REGISTERS_BASE + 0x04A4), + [ACX_REG_INTERRUPT_ACK] = (REGISTERS_BASE + 0x04A8), + [ACX_REG_SLV_SOFT_RESET] = (REGISTERS_BASE + 0x0000), + [ACX_REG_EE_START] = (REGISTERS_BASE + 0x080C), + [ACX_REG_ECPU_CONTROL] = (REGISTERS_BASE + 0x0804) +}; + +static int wl1251_translate_reg_addr(struct wl1251 *wl, int addr) +{ + /* If the address is lower than REGISTERS_BASE, it means that this is + * a chip-specific register address, so look it up in the registers + * table */ + if (addr < REGISTERS_BASE) { + /* Make sure we don't go over the table */ + if (addr >= ACX_REG_TABLE_LEN) { + wl1251_error("address out of range (%d)", addr); + return -EINVAL; + } + addr = wl1251_io_reg_table[addr]; + } + + return addr - wl->physical_reg_addr + wl->virtual_reg_addr; +} + +static int wl1251_translate_mem_addr(struct wl1251 *wl, int addr) +{ + return addr - wl->physical_mem_addr + wl->virtual_mem_addr; +} + +void wl1251_mem_read(struct wl1251 *wl, int addr, void *buf, size_t len) +{ + int physical; + + physical = wl1251_translate_mem_addr(wl, addr); + + wl->if_ops->read(wl, physical, buf, len); +} + +void wl1251_mem_write(struct wl1251 *wl, int addr, void *buf, size_t len) +{ + int physical; + + physical = wl1251_translate_mem_addr(wl, addr); + + wl->if_ops->write(wl, physical, buf, len); +} + +u32 wl1251_mem_read32(struct wl1251 *wl, int addr) +{ + return wl1251_read32(wl, wl1251_translate_mem_addr(wl, addr)); +} + +void wl1251_mem_write32(struct wl1251 *wl, int addr, u32 val) +{ + wl1251_write32(wl, wl1251_translate_mem_addr(wl, addr), val); +} + +u32 wl1251_reg_read32(struct wl1251 *wl, int addr) +{ + return wl1251_read32(wl, wl1251_translate_reg_addr(wl, addr)); +} + +void wl1251_reg_write32(struct wl1251 *wl, int addr, u32 val) +{ + wl1251_write32(wl, wl1251_translate_reg_addr(wl, addr), val); +} + +/* Set the partitions to access the chip addresses. + * + * There are two VIRTUAL partitions (the memory partition and the + * registers partition), which are mapped to two different areas of the + * PHYSICAL (hardware) memory. This function also makes other checks to + * ensure that the partitions are not overlapping. In the diagram below, the + * memory partition comes before the register partition, but the opposite is + * also supported. + * + * PHYSICAL address + * space + * + * | | + * ...+----+--> mem_start + * VIRTUAL address ... | | + * space ... | | [PART_0] + * ... | | + * 0x00000000 <--+----+... ...+----+--> mem_start + mem_size + * | | ... | | + * |MEM | ... | | + * | | ... | | + * part_size <--+----+... | | {unused area) + * | | ... | | + * |REG | ... | | + * part_size | | ... | | + * + <--+----+... ...+----+--> reg_start + * reg_size ... | | + * ... | | [PART_1] + * ... | | + * ...+----+--> reg_start + reg_size + * | | + * + */ +void wl1251_set_partition(struct wl1251 *wl, + u32 mem_start, u32 mem_size, + u32 reg_start, u32 reg_size) +{ + struct wl1251_partition partition[2]; + + wl1251_debug(DEBUG_SPI, "mem_start %08X mem_size %08X", + mem_start, mem_size); + wl1251_debug(DEBUG_SPI, "reg_start %08X reg_size %08X", + reg_start, reg_size); + + /* Make sure that the two partitions together don't exceed the + * address range */ + if ((mem_size + reg_size) > HW_ACCESS_MEMORY_MAX_RANGE) { + wl1251_debug(DEBUG_SPI, "Total size exceeds maximum virtual" + " address range. Truncating partition[0]."); + mem_size = HW_ACCESS_MEMORY_MAX_RANGE - reg_size; + wl1251_debug(DEBUG_SPI, "mem_start %08X mem_size %08X", + mem_start, mem_size); + wl1251_debug(DEBUG_SPI, "reg_start %08X reg_size %08X", + reg_start, reg_size); + } + + if ((mem_start < reg_start) && + ((mem_start + mem_size) > reg_start)) { + /* Guarantee that the memory partition doesn't overlap the + * registers partition */ + wl1251_debug(DEBUG_SPI, "End of partition[0] is " + "overlapping partition[1]. Adjusted."); + mem_size = reg_start - mem_start; + wl1251_debug(DEBUG_SPI, "mem_start %08X mem_size %08X", + mem_start, mem_size); + wl1251_debug(DEBUG_SPI, "reg_start %08X reg_size %08X", + reg_start, reg_size); + } else if ((reg_start < mem_start) && + ((reg_start + reg_size) > mem_start)) { + /* Guarantee that the register partition doesn't overlap the + * memory partition */ + wl1251_debug(DEBUG_SPI, "End of partition[1] is" + " overlapping partition[0]. Adjusted."); + reg_size = mem_start - reg_start; + wl1251_debug(DEBUG_SPI, "mem_start %08X mem_size %08X", + mem_start, mem_size); + wl1251_debug(DEBUG_SPI, "reg_start %08X reg_size %08X", + reg_start, reg_size); + } + + partition[0].start = mem_start; + partition[0].size = mem_size; + partition[1].start = reg_start; + partition[1].size = reg_size; + + wl->physical_mem_addr = mem_start; + wl->physical_reg_addr = reg_start; + + wl->virtual_mem_addr = 0; + wl->virtual_reg_addr = mem_size; + + wl->if_ops->write(wl, HW_ACCESS_PART0_SIZE_ADDR, partition, + sizeof(partition)); +} diff --git a/drivers/net/wireless/ti/wl1251/io.h b/drivers/net/wireless/ti/wl1251/io.h new file mode 100644 index 0000000..d382877 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/io.h @@ -0,0 +1,83 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#ifndef __WL1251_IO_H__ +#define __WL1251_IO_H__ + +#include "wl1251.h" + +#define HW_ACCESS_MEMORY_MAX_RANGE 0x1FFC0 + +#define HW_ACCESS_PART0_SIZE_ADDR 0x1FFC0 +#define HW_ACCESS_PART0_START_ADDR 0x1FFC4 +#define HW_ACCESS_PART1_SIZE_ADDR 0x1FFC8 +#define HW_ACCESS_PART1_START_ADDR 0x1FFCC + +#define HW_ACCESS_REGISTER_SIZE 4 + +#define HW_ACCESS_PRAM_MAX_RANGE 0x3c000 + +static inline u32 wl1251_read32(struct wl1251 *wl, int addr) +{ + wl->if_ops->read(wl, addr, &wl->buffer_32, sizeof(wl->buffer_32)); + + return le32_to_cpu(wl->buffer_32); +} + +static inline void wl1251_write32(struct wl1251 *wl, int addr, u32 val) +{ + wl->buffer_32 = cpu_to_le32(val); + wl->if_ops->write(wl, addr, &wl->buffer_32, sizeof(wl->buffer_32)); +} + +static inline u32 wl1251_read_elp(struct wl1251 *wl, int addr) +{ + u32 response; + + if (wl->if_ops->read_elp) + wl->if_ops->read_elp(wl, addr, &response); + else + wl->if_ops->read(wl, addr, &response, sizeof(u32)); + + return response; +} + +static inline void wl1251_write_elp(struct wl1251 *wl, int addr, u32 val) +{ + if (wl->if_ops->write_elp) + wl->if_ops->write_elp(wl, addr, val); + else + wl->if_ops->write(wl, addr, &val, sizeof(u32)); +} + +/* Memory target IO, address is translated to partition 0 */ +void wl1251_mem_read(struct wl1251 *wl, int addr, void *buf, size_t len); +void wl1251_mem_write(struct wl1251 *wl, int addr, void *buf, size_t len); +u32 wl1251_mem_read32(struct wl1251 *wl, int addr); +void wl1251_mem_write32(struct wl1251 *wl, int addr, u32 val); +/* Registers IO */ +u32 wl1251_reg_read32(struct wl1251 *wl, int addr); +void wl1251_reg_write32(struct wl1251 *wl, int addr, u32 val); + +void wl1251_set_partition(struct wl1251 *wl, + u32 part_start, u32 part_size, + u32 reg_start, u32 reg_size); + +#endif diff --git a/drivers/net/wireless/ti/wl1251/main.c b/drivers/net/wireless/ti/wl1251/main.c new file mode 100644 index 0000000..0cd952f --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/main.c @@ -0,0 +1,1483 @@ +/* + * This file is part of wl1251 + * + * Copyright (C) 2008-2009 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#if (LINUX_VERSION_CODE == KERNEL_VERSION(2,6,28)) +#include +#endif +#include +#include +#include +#include + +#include "wl1251.h" +#include "wl12xx_80211.h" +#include "reg.h" +#include "io.h" +#include "cmd.h" +#include "event.h" +#include "tx.h" +#include "rx.h" +#include "ps.h" +#include "init.h" +#include "debugfs.h" +#include "boot.h" + +void wl1251_enable_interrupts(struct wl1251 *wl) +{ + wl->if_ops->enable_irq(wl); +} + +void wl1251_disable_interrupts(struct wl1251 *wl) +{ + wl->if_ops->disable_irq(wl); +} + +static int wl1251_power_off(struct wl1251 *wl) +{ + return wl->if_ops->power(wl, false); +} + +static int wl1251_power_on(struct wl1251 *wl) +{ + return wl->if_ops->power(wl, true); +} + +static int wl1251_fetch_firmware(struct wl1251 *wl) +{ + const struct firmware *fw; + struct device *dev = wiphy_dev(wl->hw->wiphy); + int ret; + + ret = request_firmware(&fw, WL1251_FW_NAME, dev); + + if (ret < 0) { + wl1251_error("could not get firmware: %d", ret); + return ret; + } + + if (fw->size % 4) { + wl1251_error("firmware size is not multiple of 32 bits: %zu", + fw->size); + ret = -EILSEQ; + goto out; + } + + wl->fw_len = fw->size; + wl->fw = vmalloc(wl->fw_len); + + if (!wl->fw) { + wl1251_error("could not allocate memory for the firmware"); + ret = -ENOMEM; + goto out; + } + + memcpy(wl->fw, fw->data, wl->fw_len); + + ret = 0; + +out: + release_firmware(fw); + + return ret; +} + +static int wl1251_fetch_nvs(struct wl1251 *wl) +{ + const struct firmware *fw; + struct device *dev = wiphy_dev(wl->hw->wiphy); + int ret; + + ret = request_firmware(&fw, WL1251_NVS_NAME, dev); + + if (ret < 0) { + wl1251_error("could not get nvs file: %d", ret); + return ret; + } + + if (fw->size % 4) { + wl1251_error("nvs size is not multiple of 32 bits: %zu", + fw->size); + ret = -EILSEQ; + goto out; + } + + wl->nvs_len = fw->size; + wl->nvs = kmemdup(fw->data, wl->nvs_len, GFP_KERNEL); + + if (!wl->nvs) { + wl1251_error("could not allocate memory for the nvs file"); + ret = -ENOMEM; + goto out; + } + + ret = 0; + +out: + release_firmware(fw); + + return ret; +} + +static void wl1251_fw_wakeup(struct wl1251 *wl) +{ + u32 elp_reg; + + elp_reg = ELPCTRL_WAKE_UP; + wl1251_write_elp(wl, HW_ACCESS_ELP_CTRL_REG_ADDR, elp_reg); + elp_reg = wl1251_read_elp(wl, HW_ACCESS_ELP_CTRL_REG_ADDR); + + if (!(elp_reg & ELPCTRL_WLAN_READY)) + wl1251_warning("WLAN not ready"); +} + +static int wl1251_chip_wakeup(struct wl1251 *wl) +{ + int ret; + + ret = wl1251_power_on(wl); + if (ret < 0) + return ret; + + msleep(WL1251_POWER_ON_SLEEP); + wl->if_ops->reset(wl); + + /* We don't need a real memory partition here, because we only want + * to use the registers at this point. */ + wl1251_set_partition(wl, + 0x00000000, + 0x00000000, + REGISTERS_BASE, + REGISTERS_DOWN_SIZE); + + /* ELP module wake up */ + wl1251_fw_wakeup(wl); + + /* whal_FwCtrl_BootSm() */ + + /* 0. read chip id from CHIP_ID */ + wl->chip_id = wl1251_reg_read32(wl, CHIP_ID_B); + + /* 1. check if chip id is valid */ + + switch (wl->chip_id) { + case CHIP_ID_1251_PG12: + wl1251_debug(DEBUG_BOOT, "chip id 0x%x (1251 PG12)", + wl->chip_id); + break; + case CHIP_ID_1251_PG11: + wl1251_debug(DEBUG_BOOT, "chip id 0x%x (1251 PG11)", + wl->chip_id); + break; + case CHIP_ID_1251_PG10: + default: + wl1251_error("unsupported chip id: 0x%x", wl->chip_id); + ret = -ENODEV; + goto out; + } + + if (wl->fw == NULL) { + ret = wl1251_fetch_firmware(wl); + if (ret < 0) + goto out; + } + + if (wl->nvs == NULL && !wl->use_eeprom) { + /* No NVS from netlink, try to get it from the filesystem */ + ret = wl1251_fetch_nvs(wl); + if (ret < 0) + goto out; + } + +out: + return ret; +} + +#define WL1251_IRQ_LOOP_COUNT 10 +static void wl1251_irq_work(struct work_struct *work) +{ + u32 intr, ctr = WL1251_IRQ_LOOP_COUNT; + struct wl1251 *wl = + container_of(work, struct wl1251, irq_work); + int ret; + + mutex_lock(&wl->mutex); + + wl1251_debug(DEBUG_IRQ, "IRQ work"); + + if (wl->state == WL1251_STATE_OFF) + goto out; + + ret = wl1251_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl1251_reg_write32(wl, ACX_REG_INTERRUPT_MASK, WL1251_ACX_INTR_ALL); + + intr = wl1251_reg_read32(wl, ACX_REG_INTERRUPT_CLEAR); + wl1251_debug(DEBUG_IRQ, "intr: 0x%x", intr); + + do { + if (wl->data_path) { + wl->rx_counter = wl1251_mem_read32( + wl, wl->data_path->rx_control_addr); + + /* We handle a frmware bug here */ + switch ((wl->rx_counter - wl->rx_handled) & 0xf) { + case 0: + wl1251_debug(DEBUG_IRQ, + "RX: FW and host in sync"); + intr &= ~WL1251_ACX_INTR_RX0_DATA; + intr &= ~WL1251_ACX_INTR_RX1_DATA; + break; + case 1: + wl1251_debug(DEBUG_IRQ, "RX: FW +1"); + intr |= WL1251_ACX_INTR_RX0_DATA; + intr &= ~WL1251_ACX_INTR_RX1_DATA; + break; + case 2: + wl1251_debug(DEBUG_IRQ, "RX: FW +2"); + intr |= WL1251_ACX_INTR_RX0_DATA; + intr |= WL1251_ACX_INTR_RX1_DATA; + break; + default: + wl1251_warning( + "RX: FW and host out of sync: %d", + wl->rx_counter - wl->rx_handled); + break; + } + + wl->rx_handled = wl->rx_counter; + + wl1251_debug(DEBUG_IRQ, "RX counter: %d", + wl->rx_counter); + } + + intr &= wl->intr_mask; + + if (intr == 0) { + wl1251_debug(DEBUG_IRQ, "INTR is 0"); + goto out_sleep; + } + + if (intr & WL1251_ACX_INTR_RX0_DATA) { + wl1251_debug(DEBUG_IRQ, "WL1251_ACX_INTR_RX0_DATA"); + wl1251_rx(wl); + } + + if (intr & WL1251_ACX_INTR_RX1_DATA) { + wl1251_debug(DEBUG_IRQ, "WL1251_ACX_INTR_RX1_DATA"); + wl1251_rx(wl); + } + + if (intr & WL1251_ACX_INTR_TX_RESULT) { + wl1251_debug(DEBUG_IRQ, "WL1251_ACX_INTR_TX_RESULT"); + wl1251_tx_complete(wl); + } + + if (intr & WL1251_ACX_INTR_EVENT_A) { + wl1251_debug(DEBUG_IRQ, "WL1251_ACX_INTR_EVENT_A"); + wl1251_event_handle(wl, 0); + } + + if (intr & WL1251_ACX_INTR_EVENT_B) { + wl1251_debug(DEBUG_IRQ, "WL1251_ACX_INTR_EVENT_B"); + wl1251_event_handle(wl, 1); + } + + if (intr & WL1251_ACX_INTR_INIT_COMPLETE) + wl1251_debug(DEBUG_IRQ, + "WL1251_ACX_INTR_INIT_COMPLETE"); + + if (--ctr == 0) + break; + + intr = wl1251_reg_read32(wl, ACX_REG_INTERRUPT_CLEAR); + } while (intr); + +out_sleep: + wl1251_reg_write32(wl, ACX_REG_INTERRUPT_MASK, ~(wl->intr_mask)); + wl1251_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); +} + +static int wl1251_join(struct wl1251 *wl, u8 bss_type, u8 channel, + u16 beacon_interval, u8 dtim_period) +{ + int ret; + + ret = wl1251_acx_frame_rates(wl, DEFAULT_HW_GEN_TX_RATE, + DEFAULT_HW_GEN_MODULATION_TYPE, + wl->tx_mgmt_frm_rate, + wl->tx_mgmt_frm_mod); + if (ret < 0) + goto out; + + /* + * Join command applies filters, and if we are not associated, + * BSSID filter must be disabled for association to work. + */ + if (is_zero_ether_addr(wl->bssid)) + wl->rx_config &= ~CFG_BSSID_FILTER_EN; + + ret = wl1251_cmd_join(wl, bss_type, channel, beacon_interval, + dtim_period); + if (ret < 0) + goto out; + + ret = wl1251_event_wait(wl, JOIN_EVENT_COMPLETE_ID, 100); + if (ret < 0) + wl1251_warning("join timeout"); + +out: + return ret; +} + +static void wl1251_op_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct wl1251 *wl = hw->priv; + unsigned long flags; + + skb_queue_tail(&wl->tx_queue, skb); + + /* + * The chip specific setup must run before the first TX packet - + * before that, the tx_work will not be initialized! + */ + + ieee80211_queue_work(wl->hw, &wl->tx_work); + + /* + * The workqueue is slow to process the tx_queue and we need stop + * the queue here, otherwise the queue will get too long. + */ + if (skb_queue_len(&wl->tx_queue) >= WL1251_TX_QUEUE_HIGH_WATERMARK) { + wl1251_debug(DEBUG_TX, "op_tx: tx_queue full, stop queues"); + + spin_lock_irqsave(&wl->wl_lock, flags); + ieee80211_stop_queues(wl->hw); + wl->tx_queue_stopped = true; + spin_unlock_irqrestore(&wl->wl_lock, flags); + } +} + +static int wl1251_op_start(struct ieee80211_hw *hw) +{ + struct wl1251 *wl = hw->priv; + struct wiphy *wiphy = hw->wiphy; + int ret = 0; + + wl1251_debug(DEBUG_MAC80211, "mac80211 start"); + + mutex_lock(&wl->mutex); + + if (wl->state != WL1251_STATE_OFF) { + wl1251_error("cannot start because not in off state: %d", + wl->state); + ret = -EBUSY; + goto out; + } + + ret = wl1251_chip_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl1251_boot(wl); + if (ret < 0) + goto out; + + ret = wl1251_hw_init(wl); + if (ret < 0) + goto out; + + ret = wl1251_acx_station_id(wl); + if (ret < 0) + goto out; + + wl->state = WL1251_STATE_ON; + + wl1251_info("firmware booted (%s)", wl->fw_ver); + + /* update hw/fw version info in wiphy struct */ + wiphy->hw_version = wl->chip_id; + strncpy(wiphy->fw_version, wl->fw_ver, sizeof(wiphy->fw_version)); + +out: + if (ret < 0) + wl1251_power_off(wl); + + mutex_unlock(&wl->mutex); + + return ret; +} + +static void wl1251_op_stop(struct ieee80211_hw *hw) +{ + struct wl1251 *wl = hw->priv; + + wl1251_info("down"); + + wl1251_debug(DEBUG_MAC80211, "mac80211 stop"); + + mutex_lock(&wl->mutex); + + WARN_ON(wl->state != WL1251_STATE_ON); + + if (wl->scanning) { + ieee80211_scan_completed(wl->hw, true); + wl->scanning = false; + } + + wl->state = WL1251_STATE_OFF; + + wl1251_disable_interrupts(wl); + + mutex_unlock(&wl->mutex); + + cancel_work_sync(&wl->irq_work); + cancel_work_sync(&wl->tx_work); + cancel_delayed_work_sync(&wl->elp_work); + + mutex_lock(&wl->mutex); + + /* let's notify MAC80211 about the remaining pending TX frames */ + wl1251_tx_flush(wl); + wl1251_power_off(wl); + + memset(wl->bssid, 0, ETH_ALEN); + wl->listen_int = 1; + wl->bss_type = MAX_BSS_TYPE; + + wl->data_in_count = 0; + wl->rx_counter = 0; + wl->rx_handled = 0; + wl->rx_current_buffer = 0; + wl->rx_last_id = 0; + wl->next_tx_complete = 0; + wl->elp = false; + wl->station_mode = STATION_ACTIVE_MODE; + wl->tx_queue_stopped = false; + wl->power_level = WL1251_DEFAULT_POWER_LEVEL; + wl->rssi_thold = 0; + wl->channel = WL1251_DEFAULT_CHANNEL; + + wl1251_debugfs_reset(wl); + + mutex_unlock(&wl->mutex); +} + +static int wl1251_op_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct wl1251 *wl = hw->priv; + int ret = 0; + + vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER | + IEEE80211_VIF_SUPPORTS_CQM_RSSI; + + wl1251_debug(DEBUG_MAC80211, "mac80211 add interface type %d mac %pM", + vif->type, vif->addr); + + mutex_lock(&wl->mutex); + if (wl->vif) { + ret = -EBUSY; + goto out; + } + + wl->vif = vif; + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + wl->bss_type = BSS_TYPE_STA_BSS; + break; + case NL80211_IFTYPE_ADHOC: + wl->bss_type = BSS_TYPE_IBSS; + break; + default: + ret = -EOPNOTSUPP; + goto out; + } + + if (memcmp(wl->mac_addr, vif->addr, ETH_ALEN)) { + memcpy(wl->mac_addr, vif->addr, ETH_ALEN); + SET_IEEE80211_PERM_ADDR(wl->hw, wl->mac_addr); + ret = wl1251_acx_station_id(wl); + if (ret < 0) + goto out; + } + +out: + mutex_unlock(&wl->mutex); + return ret; +} + +static void wl1251_op_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct wl1251 *wl = hw->priv; + + mutex_lock(&wl->mutex); + wl1251_debug(DEBUG_MAC80211, "mac80211 remove interface"); + wl->vif = NULL; + mutex_unlock(&wl->mutex); +} + +static int wl1251_build_qos_null_data(struct wl1251 *wl) +{ + struct ieee80211_qos_hdr template; + + memset(&template, 0, sizeof(template)); + + memcpy(template.addr1, wl->bssid, ETH_ALEN); + memcpy(template.addr2, wl->mac_addr, ETH_ALEN); + memcpy(template.addr3, wl->bssid, ETH_ALEN); + + template.frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | + IEEE80211_STYPE_QOS_NULLFUNC | + IEEE80211_FCTL_TODS); + + /* FIXME: not sure what priority to use here */ + template.qos_ctrl = cpu_to_le16(0); + + return wl1251_cmd_template_set(wl, CMD_QOS_NULL_DATA, &template, + sizeof(template)); +} + +static int wl1251_op_config(struct ieee80211_hw *hw, u32 changed) +{ + struct wl1251 *wl = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + int channel, ret = 0; + + channel = ieee80211_frequency_to_channel( + conf->chandef.chan->center_freq); + + wl1251_debug(DEBUG_MAC80211, "mac80211 config ch %d psm %s power %d", + channel, + conf->flags & IEEE80211_CONF_PS ? "on" : "off", + conf->power_level); + + mutex_lock(&wl->mutex); + + ret = wl1251_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + if (channel != wl->channel) { + wl->channel = channel; + + ret = wl1251_join(wl, wl->bss_type, wl->channel, + wl->beacon_int, wl->dtim_period); + if (ret < 0) + goto out_sleep; + } + + if (conf->flags & IEEE80211_CONF_PS && !wl->psm_requested) { + wl1251_debug(DEBUG_PSM, "psm enabled"); + + wl->psm_requested = true; + + wl->dtim_period = conf->ps_dtim_period; + + ret = wl1251_acx_wr_tbtt_and_dtim(wl, wl->beacon_int, + wl->dtim_period); + + /* + * mac80211 enables PSM only if we're already associated. + */ + ret = wl1251_ps_set_mode(wl, STATION_POWER_SAVE_MODE); + if (ret < 0) + goto out_sleep; + } else if (!(conf->flags & IEEE80211_CONF_PS) && + wl->psm_requested) { + wl1251_debug(DEBUG_PSM, "psm disabled"); + + wl->psm_requested = false; + + if (wl->station_mode != STATION_ACTIVE_MODE) { + ret = wl1251_ps_set_mode(wl, STATION_ACTIVE_MODE); + if (ret < 0) + goto out_sleep; + } + } + + if (changed & IEEE80211_CONF_CHANGE_IDLE && !wl->scanning) { + if (conf->flags & IEEE80211_CONF_IDLE) { + ret = wl1251_ps_set_mode(wl, STATION_IDLE); + if (ret < 0) + goto out_sleep; + } else { + ret = wl1251_ps_set_mode(wl, STATION_ACTIVE_MODE); + if (ret < 0) + goto out_sleep; + ret = wl1251_join(wl, wl->bss_type, wl->channel, + wl->beacon_int, wl->dtim_period); + if (ret < 0) + goto out_sleep; + } + } + + if (conf->power_level != wl->power_level) { + ret = wl1251_acx_tx_power(wl, conf->power_level); + if (ret < 0) + goto out_sleep; + + wl->power_level = conf->power_level; + } + +out_sleep: + wl1251_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +#define WL1251_SUPPORTED_FILTERS (FIF_PROMISC_IN_BSS | \ + FIF_ALLMULTI | \ + FIF_FCSFAIL | \ + FIF_BCN_PRBRESP_PROMISC | \ + FIF_CONTROL | \ + FIF_OTHER_BSS | \ + FIF_PROBE_REQ) + +static void wl1251_op_configure_filter(struct ieee80211_hw *hw, + unsigned int changed, + unsigned int *total,u64 multicast) +{ + struct wl1251 *wl = hw->priv; + int ret; + + wl1251_debug(DEBUG_MAC80211, "mac80211 configure filter"); + + *total &= WL1251_SUPPORTED_FILTERS; + changed &= WL1251_SUPPORTED_FILTERS; + + if (changed == 0) + /* no filters which we support changed */ + return; + + mutex_lock(&wl->mutex); + + wl->rx_config = WL1251_DEFAULT_RX_CONFIG; + wl->rx_filter = WL1251_DEFAULT_RX_FILTER; + + if (*total & FIF_PROMISC_IN_BSS) { + wl->rx_config |= CFG_BSSID_FILTER_EN; + wl->rx_config |= CFG_RX_ALL_GOOD; + } + if (*total & FIF_ALLMULTI) + /* + * CFG_MC_FILTER_EN in rx_config needs to be 0 to receive + * all multicast frames + */ + wl->rx_config &= ~CFG_MC_FILTER_EN; + if (*total & FIF_FCSFAIL) + wl->rx_filter |= CFG_RX_FCS_ERROR; + if (*total & FIF_BCN_PRBRESP_PROMISC) { + wl->rx_config &= ~CFG_BSSID_FILTER_EN; + wl->rx_config &= ~CFG_SSID_FILTER_EN; + } + if (*total & FIF_CONTROL) + wl->rx_filter |= CFG_RX_CTL_EN; + if (*total & FIF_OTHER_BSS || is_zero_ether_addr(wl->bssid)) + wl->rx_config &= ~CFG_BSSID_FILTER_EN; + if (*total & FIF_PROBE_REQ) + wl->rx_filter |= CFG_RX_PREQ_EN; + + if (wl->state == WL1251_STATE_OFF) + goto out; + + ret = wl1251_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + /* send filters to firmware */ + wl1251_acx_rx_config(wl, wl->rx_config, wl->rx_filter); + + wl1251_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); +} + +/* HW encryption */ +static int wl1251_set_key_type(struct wl1251 *wl, + struct wl1251_cmd_set_keys *key, + enum set_key_cmd cmd, + struct ieee80211_key_conf *mac80211_key, + const u8 *addr) +{ + switch (mac80211_key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + if (is_broadcast_ether_addr(addr)) + key->key_type = KEY_WEP_DEFAULT; + else + key->key_type = KEY_WEP_ADDR; + + mac80211_key->hw_key_idx = mac80211_key->keyidx; + break; + case WLAN_CIPHER_SUITE_TKIP: + if (is_broadcast_ether_addr(addr)) + key->key_type = KEY_TKIP_MIC_GROUP; + else + key->key_type = KEY_TKIP_MIC_PAIRWISE; + + mac80211_key->hw_key_idx = mac80211_key->keyidx; + break; + case WLAN_CIPHER_SUITE_CCMP: + if (is_broadcast_ether_addr(addr)) + key->key_type = KEY_AES_GROUP; + else + key->key_type = KEY_AES_PAIRWISE; + mac80211_key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; + break; + default: + wl1251_error("Unknown key cipher 0x%x", mac80211_key->cipher); + return -EOPNOTSUPP; + } + + return 0; +} + +static int wl1251_op_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct wl1251 *wl = hw->priv; + struct wl1251_cmd_set_keys *wl_cmd; + const u8 *addr; + int ret; + + static const u8 bcast_addr[ETH_ALEN] = + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + wl1251_debug(DEBUG_MAC80211, "mac80211 set key"); + + wl_cmd = kzalloc(sizeof(*wl_cmd), GFP_KERNEL); + if (!wl_cmd) { + ret = -ENOMEM; + goto out; + } + + addr = sta ? sta->addr : bcast_addr; + + wl1251_debug(DEBUG_CRYPT, "CMD: 0x%x", cmd); + wl1251_dump(DEBUG_CRYPT, "ADDR: ", addr, ETH_ALEN); + wl1251_debug(DEBUG_CRYPT, "Key: algo:0x%x, id:%d, len:%d flags 0x%x", + key->cipher, key->keyidx, key->keylen, key->flags); + wl1251_dump(DEBUG_CRYPT, "KEY: ", key->key, key->keylen); + + if (is_zero_ether_addr(addr)) { + /* We dont support TX only encryption */ + ret = -EOPNOTSUPP; + goto out; + } + + mutex_lock(&wl->mutex); + + ret = wl1251_ps_elp_wakeup(wl); + if (ret < 0) + goto out_unlock; + + switch (cmd) { + case SET_KEY: + wl_cmd->key_action = KEY_ADD_OR_REPLACE; + break; + case DISABLE_KEY: + wl_cmd->key_action = KEY_REMOVE; + break; + default: + wl1251_error("Unsupported key cmd 0x%x", cmd); + break; + } + + ret = wl1251_set_key_type(wl, wl_cmd, cmd, key, addr); + if (ret < 0) { + wl1251_error("Set KEY type failed"); + goto out_sleep; + } + + if (wl_cmd->key_type != KEY_WEP_DEFAULT) + memcpy(wl_cmd->addr, addr, ETH_ALEN); + + if ((wl_cmd->key_type == KEY_TKIP_MIC_GROUP) || + (wl_cmd->key_type == KEY_TKIP_MIC_PAIRWISE)) { + /* + * We get the key in the following form: + * TKIP (16 bytes) - TX MIC (8 bytes) - RX MIC (8 bytes) + * but the target is expecting: + * TKIP - RX MIC - TX MIC + */ + memcpy(wl_cmd->key, key->key, 16); + memcpy(wl_cmd->key + 16, key->key + 24, 8); + memcpy(wl_cmd->key + 24, key->key + 16, 8); + + } else { + memcpy(wl_cmd->key, key->key, key->keylen); + } + wl_cmd->key_size = key->keylen; + + wl_cmd->id = key->keyidx; + wl_cmd->ssid_profile = 0; + + wl1251_dump(DEBUG_CRYPT, "TARGET KEY: ", wl_cmd, sizeof(*wl_cmd)); + + ret = wl1251_cmd_send(wl, CMD_SET_KEYS, wl_cmd, sizeof(*wl_cmd)); + if (ret < 0) { + wl1251_warning("could not set keys"); + goto out_sleep; + } + +out_sleep: + wl1251_ps_elp_sleep(wl); + +out_unlock: + mutex_unlock(&wl->mutex); + +out: + kfree(wl_cmd); + + return ret; +} + +static int wl1251_op_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_scan_request *req) +{ + struct wl1251 *wl = hw->priv; + struct sk_buff *skb; + size_t ssid_len = 0; + u8 *ssid = NULL; + int ret; + + wl1251_debug(DEBUG_MAC80211, "mac80211 hw scan"); + + if (req->n_ssids) { + ssid = req->ssids[0].ssid; + ssid_len = req->ssids[0].ssid_len; + } + + mutex_lock(&wl->mutex); + + if (wl->scanning) { + wl1251_debug(DEBUG_SCAN, "scan already in progress"); + ret = -EINVAL; + goto out; + } + + ret = wl1251_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + if (hw->conf.flags & IEEE80211_CONF_IDLE) { + ret = wl1251_ps_set_mode(wl, STATION_ACTIVE_MODE); + if (ret < 0) + goto out_sleep; + ret = wl1251_join(wl, wl->bss_type, wl->channel, + wl->beacon_int, wl->dtim_period); + if (ret < 0) + goto out_sleep; + } + + skb = ieee80211_probereq_get(wl->hw, wl->vif, ssid, ssid_len, + req->ie_len); + if (!skb) { + ret = -ENOMEM; + goto out_idle; + } + if (req->ie_len) + memcpy(skb_put(skb, req->ie_len), req->ie, req->ie_len); + + ret = wl1251_cmd_template_set(wl, CMD_PROBE_REQ, skb->data, + skb->len); + dev_kfree_skb(skb); + if (ret < 0) + goto out_idle; + + ret = wl1251_cmd_trigger_scan_to(wl, 0); + if (ret < 0) + goto out_idle; + + wl->scanning = true; + + ret = wl1251_cmd_scan(wl, ssid, ssid_len, req->channels, + req->n_channels, WL1251_SCAN_NUM_PROBES); + if (ret < 0) { + wl->scanning = false; + goto out_idle; + } + goto out_sleep; + +out_idle: + if (hw->conf.flags & IEEE80211_CONF_IDLE) + ret = wl1251_ps_set_mode(wl, STATION_IDLE); +out_sleep: + wl1251_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static int wl1251_op_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +{ + struct wl1251 *wl = hw->priv; + int ret; + + mutex_lock(&wl->mutex); + + ret = wl1251_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl1251_acx_rts_threshold(wl, (u16) value); + if (ret < 0) + wl1251_warning("wl1251_op_set_rts_threshold failed: %d", ret); + + wl1251_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static void wl1251_op_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changed) +{ + struct wl1251 *wl = hw->priv; + struct sk_buff *beacon, *skb; + int ret; + + wl1251_debug(DEBUG_MAC80211, "mac80211 bss info changed"); + + mutex_lock(&wl->mutex); + + ret = wl1251_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + if (changed & BSS_CHANGED_CQM) { + ret = wl1251_acx_low_rssi(wl, bss_conf->cqm_rssi_thold, + WL1251_DEFAULT_LOW_RSSI_WEIGHT, + WL1251_DEFAULT_LOW_RSSI_DEPTH, + WL1251_ACX_LOW_RSSI_TYPE_EDGE); + if (ret < 0) + goto out; + wl->rssi_thold = bss_conf->cqm_rssi_thold; + } + + if (changed & BSS_CHANGED_BSSID) { + memcpy(wl->bssid, bss_conf->bssid, ETH_ALEN); + + skb = ieee80211_nullfunc_get(wl->hw, wl->vif); + if (!skb) + goto out_sleep; + + ret = wl1251_cmd_template_set(wl, CMD_NULL_DATA, + skb->data, skb->len); + dev_kfree_skb(skb); + if (ret < 0) + goto out_sleep; + + ret = wl1251_build_qos_null_data(wl); + if (ret < 0) + goto out; + + if (wl->bss_type != BSS_TYPE_IBSS) { + ret = wl1251_join(wl, wl->bss_type, wl->channel, + wl->beacon_int, wl->dtim_period); + if (ret < 0) + goto out_sleep; + } + } + + if (changed & BSS_CHANGED_ASSOC) { + if (bss_conf->assoc) { + wl->beacon_int = bss_conf->beacon_int; + + skb = ieee80211_pspoll_get(wl->hw, wl->vif); + if (!skb) + goto out_sleep; + + ret = wl1251_cmd_template_set(wl, CMD_PS_POLL, + skb->data, + skb->len); + dev_kfree_skb(skb); + if (ret < 0) + goto out_sleep; + + ret = wl1251_acx_aid(wl, bss_conf->aid); + if (ret < 0) + goto out_sleep; + } else { + /* use defaults when not associated */ + wl->beacon_int = WL1251_DEFAULT_BEACON_INT; + wl->dtim_period = WL1251_DEFAULT_DTIM_PERIOD; + } + } + if (changed & BSS_CHANGED_ERP_SLOT) { + if (bss_conf->use_short_slot) + ret = wl1251_acx_slot(wl, SLOT_TIME_SHORT); + else + ret = wl1251_acx_slot(wl, SLOT_TIME_LONG); + if (ret < 0) { + wl1251_warning("Set slot time failed %d", ret); + goto out_sleep; + } + } + + if (changed & BSS_CHANGED_ERP_PREAMBLE) { + if (bss_conf->use_short_preamble) + wl1251_acx_set_preamble(wl, ACX_PREAMBLE_SHORT); + else + wl1251_acx_set_preamble(wl, ACX_PREAMBLE_LONG); + } + + if (changed & BSS_CHANGED_ERP_CTS_PROT) { + if (bss_conf->use_cts_prot) + ret = wl1251_acx_cts_protect(wl, CTSPROTECT_ENABLE); + else + ret = wl1251_acx_cts_protect(wl, CTSPROTECT_DISABLE); + if (ret < 0) { + wl1251_warning("Set ctsprotect failed %d", ret); + goto out_sleep; + } + } + + if (changed & BSS_CHANGED_BEACON) { + beacon = ieee80211_beacon_get(hw, vif); + if (!beacon) + goto out_sleep; + + ret = wl1251_cmd_template_set(wl, CMD_BEACON, beacon->data, + beacon->len); + + if (ret < 0) { + dev_kfree_skb(beacon); + goto out_sleep; + } + + ret = wl1251_cmd_template_set(wl, CMD_PROBE_RESP, beacon->data, + beacon->len); + + dev_kfree_skb(beacon); + + if (ret < 0) + goto out_sleep; + + ret = wl1251_join(wl, wl->bss_type, wl->beacon_int, + wl->channel, wl->dtim_period); + + if (ret < 0) + goto out_sleep; + } + +out_sleep: + wl1251_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); +} + + +/* can't be const, mac80211 writes to this */ +static struct ieee80211_rate wl1251_rates[] = { + { .bitrate = 10, + .hw_value = 0x1, + .hw_value_short = 0x1, }, + { .bitrate = 20, + .hw_value = 0x2, + .hw_value_short = 0x2, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 55, + .hw_value = 0x4, + .hw_value_short = 0x4, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 110, + .hw_value = 0x20, + .hw_value_short = 0x20, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 60, + .hw_value = 0x8, + .hw_value_short = 0x8, }, + { .bitrate = 90, + .hw_value = 0x10, + .hw_value_short = 0x10, }, + { .bitrate = 120, + .hw_value = 0x40, + .hw_value_short = 0x40, }, + { .bitrate = 180, + .hw_value = 0x80, + .hw_value_short = 0x80, }, + { .bitrate = 240, + .hw_value = 0x200, + .hw_value_short = 0x200, }, + { .bitrate = 360, + .hw_value = 0x400, + .hw_value_short = 0x400, }, + { .bitrate = 480, + .hw_value = 0x800, + .hw_value_short = 0x800, }, + { .bitrate = 540, + .hw_value = 0x1000, + .hw_value_short = 0x1000, }, +}; + +/* can't be const, mac80211 writes to this */ +static struct ieee80211_channel wl1251_channels[] = { + { .hw_value = 1, .center_freq = 2412}, + { .hw_value = 2, .center_freq = 2417}, + { .hw_value = 3, .center_freq = 2422}, + { .hw_value = 4, .center_freq = 2427}, + { .hw_value = 5, .center_freq = 2432}, + { .hw_value = 6, .center_freq = 2437}, + { .hw_value = 7, .center_freq = 2442}, + { .hw_value = 8, .center_freq = 2447}, + { .hw_value = 9, .center_freq = 2452}, + { .hw_value = 10, .center_freq = 2457}, + { .hw_value = 11, .center_freq = 2462}, + { .hw_value = 12, .center_freq = 2467}, + { .hw_value = 13, .center_freq = 2472}, +}; + +static int wl1251_op_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + enum wl1251_acx_ps_scheme ps_scheme; + struct wl1251 *wl = hw->priv; + int ret; + + mutex_lock(&wl->mutex); + + wl1251_debug(DEBUG_MAC80211, "mac80211 conf tx %d", queue); + + ret = wl1251_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + /* mac80211 uses units of 32 usec */ + ret = wl1251_acx_ac_cfg(wl, wl1251_tx_get_queue(queue), + params->cw_min, params->cw_max, + params->aifs, params->txop * 32); + if (ret < 0) + goto out_sleep; + + if (params->uapsd) + ps_scheme = WL1251_ACX_PS_SCHEME_UPSD_TRIGGER; + else + ps_scheme = WL1251_ACX_PS_SCHEME_LEGACY; + + ret = wl1251_acx_tid_cfg(wl, wl1251_tx_get_queue(queue), + CHANNEL_TYPE_EDCF, + wl1251_tx_get_queue(queue), ps_scheme, + WL1251_ACX_ACK_POLICY_LEGACY); + if (ret < 0) + goto out_sleep; + +out_sleep: + wl1251_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static int wl1251_op_get_survey(struct ieee80211_hw *hw, int idx, + struct survey_info *survey) +{ + struct wl1251 *wl = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + + if (idx != 0) + return -ENOENT; + + survey->channel = conf->chandef.chan; + survey->filled = SURVEY_INFO_NOISE_DBM; + survey->noise = wl->noise; + + return 0; +} + +/* can't be const, mac80211 writes to this */ +static struct ieee80211_supported_band wl1251_band_2ghz = { + .channels = wl1251_channels, + .n_channels = ARRAY_SIZE(wl1251_channels), + .bitrates = wl1251_rates, + .n_bitrates = ARRAY_SIZE(wl1251_rates), +}; + +static const struct ieee80211_ops wl1251_ops = { + .start = wl1251_op_start, + .stop = wl1251_op_stop, + .add_interface = wl1251_op_add_interface, + .remove_interface = wl1251_op_remove_interface, + .config = wl1251_op_config, + .configure_filter = wl1251_op_configure_filter, + .tx = wl1251_op_tx, + .set_key = wl1251_op_set_key, + .hw_scan = wl1251_op_hw_scan, + .bss_info_changed = wl1251_op_bss_info_changed, + .set_rts_threshold = wl1251_op_set_rts_threshold, + .conf_tx = wl1251_op_conf_tx, + .get_survey = wl1251_op_get_survey, +}; + +static int wl1251_read_eeprom_byte(struct wl1251 *wl, off_t offset, u8 *data) +{ + unsigned long timeout; + + wl1251_reg_write32(wl, EE_ADDR, offset); + wl1251_reg_write32(wl, EE_CTL, EE_CTL_READ); + + /* EE_CTL_READ clears when data is ready */ + timeout = jiffies + msecs_to_jiffies(100); + while (1) { + if (!(wl1251_reg_read32(wl, EE_CTL) & EE_CTL_READ)) + break; + + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + + msleep(1); + } + + *data = wl1251_reg_read32(wl, EE_DATA); + return 0; +} + +static int wl1251_read_eeprom(struct wl1251 *wl, off_t offset, + u8 *data, size_t len) +{ + size_t i; + int ret; + + wl1251_reg_write32(wl, EE_START, 0); + + for (i = 0; i < len; i++) { + ret = wl1251_read_eeprom_byte(wl, offset + i, &data[i]); + if (ret < 0) + return ret; + } + + return 0; +} + +static int wl1251_read_eeprom_mac(struct wl1251 *wl) +{ + u8 mac[ETH_ALEN]; + int i, ret; + + wl1251_set_partition(wl, 0, 0, REGISTERS_BASE, REGISTERS_DOWN_SIZE); + + ret = wl1251_read_eeprom(wl, 0x1c, mac, sizeof(mac)); + if (ret < 0) { + wl1251_warning("failed to read MAC address from EEPROM"); + return ret; + } + + /* MAC is stored in reverse order */ + for (i = 0; i < ETH_ALEN; i++) + wl->mac_addr[i] = mac[ETH_ALEN - i - 1]; + + return 0; +} + +static int wl1251_register_hw(struct wl1251 *wl) +{ + int ret; + + if (wl->mac80211_registered) + return 0; + + SET_IEEE80211_PERM_ADDR(wl->hw, wl->mac_addr); + + ret = ieee80211_register_hw(wl->hw); + if (ret < 0) { + wl1251_error("unable to register mac80211 hw: %d", ret); + return ret; + } + + wl->mac80211_registered = true; + + wl1251_notice("loaded"); + + return 0; +} + +int wl1251_init_ieee80211(struct wl1251 *wl) +{ + int ret; + + /* The tx descriptor buffer and the TKIP space */ + wl->hw->extra_tx_headroom = sizeof(struct tx_double_buffer_desc) + + WL1251_TKIP_IV_SPACE; + + /* unit us */ + /* FIXME: find a proper value */ + wl->hw->channel_change_time = 10000; + + wl->hw->flags = IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_SUPPORTS_UAPSD; + + wl->hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC); + wl->hw->wiphy->max_scan_ssids = 1; + wl->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &wl1251_band_2ghz; + + wl->hw->queues = 4; + + if (wl->use_eeprom) + wl1251_read_eeprom_mac(wl); + + ret = wl1251_register_hw(wl); + if (ret) + goto out; + + wl1251_debugfs_init(wl); + wl1251_notice("initialized"); + + ret = 0; + +out: + return ret; +} +EXPORT_SYMBOL_GPL(wl1251_init_ieee80211); + +struct ieee80211_hw *wl1251_alloc_hw(void) +{ + struct ieee80211_hw *hw; + struct wl1251 *wl; + int i; + static const u8 nokia_oui[3] = {0x00, 0x1f, 0xdf}; + + hw = ieee80211_alloc_hw(sizeof(*wl), &wl1251_ops); + if (!hw) { + wl1251_error("could not alloc ieee80211_hw"); + return ERR_PTR(-ENOMEM); + } + + wl = hw->priv; + memset(wl, 0, sizeof(*wl)); + + wl->hw = hw; + + wl->data_in_count = 0; + + skb_queue_head_init(&wl->tx_queue); + + INIT_DELAYED_WORK(&wl->elp_work, wl1251_elp_work); + wl->channel = WL1251_DEFAULT_CHANNEL; + wl->scanning = false; + wl->default_key = 0; + wl->listen_int = 1; + wl->rx_counter = 0; + wl->rx_handled = 0; + wl->rx_current_buffer = 0; + wl->rx_last_id = 0; + wl->rx_config = WL1251_DEFAULT_RX_CONFIG; + wl->rx_filter = WL1251_DEFAULT_RX_FILTER; + wl->elp = false; + wl->station_mode = STATION_ACTIVE_MODE; + wl->psm_requested = false; + wl->tx_queue_stopped = false; + wl->power_level = WL1251_DEFAULT_POWER_LEVEL; + wl->rssi_thold = 0; + wl->beacon_int = WL1251_DEFAULT_BEACON_INT; + wl->dtim_period = WL1251_DEFAULT_DTIM_PERIOD; + wl->vif = NULL; + + for (i = 0; i < FW_TX_CMPLT_BLOCK_SIZE; i++) + wl->tx_frames[i] = NULL; + + wl->next_tx_complete = 0; + + INIT_WORK(&wl->irq_work, wl1251_irq_work); + INIT_WORK(&wl->tx_work, wl1251_tx_work); + + /* + * In case our MAC address is not correctly set, + * we use a random but Nokia MAC. + */ + memcpy(wl->mac_addr, nokia_oui, 3); + get_random_bytes(wl->mac_addr + 3, 3); + + wl->state = WL1251_STATE_OFF; + mutex_init(&wl->mutex); + + wl->tx_mgmt_frm_rate = DEFAULT_HW_GEN_TX_RATE; + wl->tx_mgmt_frm_mod = DEFAULT_HW_GEN_MODULATION_TYPE; + + wl->rx_descriptor = kmalloc(sizeof(*wl->rx_descriptor), GFP_KERNEL); + if (!wl->rx_descriptor) { + wl1251_error("could not allocate memory for rx descriptor"); + ieee80211_free_hw(hw); + return ERR_PTR(-ENOMEM); + } + + return hw; +} +EXPORT_SYMBOL_GPL(wl1251_alloc_hw); + +int wl1251_free_hw(struct wl1251 *wl) +{ + ieee80211_unregister_hw(wl->hw); + + wl1251_debugfs_exit(wl); + + kfree(wl->target_mem_map); + kfree(wl->data_path); + vfree(wl->fw); + wl->fw = NULL; + kfree(wl->nvs); + wl->nvs = NULL; + + kfree(wl->rx_descriptor); + wl->rx_descriptor = NULL; + + ieee80211_free_hw(wl->hw); + + return 0; +} +EXPORT_SYMBOL_GPL(wl1251_free_hw); + +MODULE_DESCRIPTION("TI wl1251 Wireles LAN Driver Core"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kalle Valo "); +MODULE_FIRMWARE(WL1251_FW_NAME); diff --git a/drivers/net/wireless/ti/wl1251/ps.c b/drivers/net/wireless/ti/wl1251/ps.c new file mode 100644 index 0000000..b9e27b9 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/ps.c @@ -0,0 +1,184 @@ +/* + * This file is part of wl1251 + * + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "reg.h" +#include "ps.h" +#include "cmd.h" +#include "io.h" + +/* in ms */ +#define WL1251_WAKEUP_TIMEOUT 100 + +void wl1251_elp_work(struct work_struct *work) +{ + struct delayed_work *dwork; + struct wl1251 *wl; + + dwork = container_of(work, struct delayed_work, work); + wl = container_of(dwork, struct wl1251, elp_work); + + wl1251_debug(DEBUG_PSM, "elp work"); + + mutex_lock(&wl->mutex); + + if (wl->elp || wl->station_mode == STATION_ACTIVE_MODE) + goto out; + + wl1251_debug(DEBUG_PSM, "chip to elp"); + wl1251_write_elp(wl, HW_ACCESS_ELP_CTRL_REG_ADDR, ELPCTRL_SLEEP); + wl->elp = true; + +out: + mutex_unlock(&wl->mutex); +} + +#define ELP_ENTRY_DELAY 5 + +/* Routines to toggle sleep mode while in ELP */ +void wl1251_ps_elp_sleep(struct wl1251 *wl) +{ + unsigned long delay; + + if (wl->station_mode != STATION_ACTIVE_MODE) { + delay = msecs_to_jiffies(ELP_ENTRY_DELAY); + ieee80211_queue_delayed_work(wl->hw, &wl->elp_work, delay); + } +} + +int wl1251_ps_elp_wakeup(struct wl1251 *wl) +{ + unsigned long timeout, start; + u32 elp_reg; + + cancel_delayed_work(&wl->elp_work); + + if (!wl->elp) + return 0; + + wl1251_debug(DEBUG_PSM, "waking up chip from elp"); + + start = jiffies; + timeout = jiffies + msecs_to_jiffies(WL1251_WAKEUP_TIMEOUT); + + wl1251_write_elp(wl, HW_ACCESS_ELP_CTRL_REG_ADDR, ELPCTRL_WAKE_UP); + + elp_reg = wl1251_read_elp(wl, HW_ACCESS_ELP_CTRL_REG_ADDR); + + /* + * FIXME: we should wait for irq from chip but, as a temporary + * solution to simplify locking, let's poll instead + */ + while (!(elp_reg & ELPCTRL_WLAN_READY)) { + if (time_after(jiffies, timeout)) { + wl1251_error("elp wakeup timeout"); + return -ETIMEDOUT; + } + msleep(1); + elp_reg = wl1251_read_elp(wl, HW_ACCESS_ELP_CTRL_REG_ADDR); + } + + wl1251_debug(DEBUG_PSM, "wakeup time: %u ms", + jiffies_to_msecs(jiffies - start)); + + wl->elp = false; + + return 0; +} + +int wl1251_ps_set_mode(struct wl1251 *wl, enum wl1251_station_mode mode) +{ + int ret; + + switch (mode) { + case STATION_POWER_SAVE_MODE: + wl1251_debug(DEBUG_PSM, "entering psm"); + + /* enable beacon filtering */ + ret = wl1251_acx_beacon_filter_opt(wl, true); + if (ret < 0) + return ret; + + ret = wl1251_acx_wake_up_conditions(wl, + WAKE_UP_EVENT_DTIM_BITMAP, + wl->listen_int); + if (ret < 0) + return ret; + + ret = wl1251_acx_bet_enable(wl, WL1251_ACX_BET_ENABLE, + WL1251_DEFAULT_BET_CONSECUTIVE); + if (ret < 0) + return ret; + + ret = wl1251_cmd_ps_mode(wl, CHIP_POWER_SAVE_MODE); + if (ret < 0) + return ret; + + ret = wl1251_acx_sleep_auth(wl, WL1251_PSM_ELP); + if (ret < 0) + return ret; + break; + case STATION_IDLE: + wl1251_debug(DEBUG_PSM, "entering idle"); + + ret = wl1251_acx_sleep_auth(wl, WL1251_PSM_ELP); + if (ret < 0) + return ret; + + ret = wl1251_cmd_template_set(wl, CMD_DISCONNECT, NULL, 0); + if (ret < 0) + return ret; + break; + case STATION_ACTIVE_MODE: + default: + wl1251_debug(DEBUG_PSM, "leaving psm"); + + ret = wl1251_acx_sleep_auth(wl, WL1251_PSM_CAM); + if (ret < 0) + return ret; + + /* disable BET */ + ret = wl1251_acx_bet_enable(wl, WL1251_ACX_BET_DISABLE, + WL1251_DEFAULT_BET_CONSECUTIVE); + if (ret < 0) + return ret; + + /* disable beacon filtering */ + ret = wl1251_acx_beacon_filter_opt(wl, false); + if (ret < 0) + return ret; + + ret = wl1251_acx_wake_up_conditions(wl, + WAKE_UP_EVENT_DTIM_BITMAP, + wl->listen_int); + if (ret < 0) + return ret; + + ret = wl1251_cmd_ps_mode(wl, CHIP_ACTIVE_MODE); + if (ret < 0) + return ret; + + break; + } + wl->station_mode = mode; + + return ret; +} + diff --git a/drivers/net/wireless/ti/wl1251/ps.h b/drivers/net/wireless/ti/wl1251/ps.h new file mode 100644 index 0000000..75efad2 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/ps.h @@ -0,0 +1,35 @@ +/* + * This file is part of wl1251 + * + * Copyright (c) 1998-2007 Texas Instruments Incorporated + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL1251_PS_H__ +#define __WL1251_PS_H__ + +#include "wl1251.h" +#include "acx.h" + +int wl1251_ps_set_mode(struct wl1251 *wl, enum wl1251_station_mode mode); +void wl1251_ps_elp_sleep(struct wl1251 *wl); +int wl1251_ps_elp_wakeup(struct wl1251 *wl); +void wl1251_elp_work(struct work_struct *work); + + +#endif /* __WL1251_PS_H__ */ diff --git a/drivers/net/wireless/ti/wl1251/reg.h b/drivers/net/wireless/ti/wl1251/reg.h new file mode 100644 index 0000000..a580901 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/reg.h @@ -0,0 +1,655 @@ +/* + * This file is part of wl12xx + * + * Copyright (c) 1998-2007 Texas Instruments Incorporated + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __REG_H__ +#define __REG_H__ + +#include + +#define REGISTERS_BASE 0x00300000 +#define DRPW_BASE 0x00310000 + +#define REGISTERS_DOWN_SIZE 0x00008800 +#define REGISTERS_WORK_SIZE 0x0000b000 + +#define HW_ACCESS_ELP_CTRL_REG_ADDR 0x1FFFC + +/* ELP register commands */ +#define ELPCTRL_WAKE_UP 0x1 +#define ELPCTRL_WAKE_UP_WLAN_READY 0x5 +#define ELPCTRL_SLEEP 0x0 +/* ELP WLAN_READY bit */ +#define ELPCTRL_WLAN_READY 0x2 + +/* Device Configuration registers*/ +#define SOR_CFG (REGISTERS_BASE + 0x0800) +#define ECPU_CTRL (REGISTERS_BASE + 0x0804) +#define HI_CFG (REGISTERS_BASE + 0x0808) + +/* EEPROM registers */ +#define EE_START (REGISTERS_BASE + 0x080C) +#define EE_CTL (REGISTERS_BASE + 0x2000) +#define EE_DATA (REGISTERS_BASE + 0x2004) +#define EE_ADDR (REGISTERS_BASE + 0x2008) + +#define EE_CTL_READ 2 + +#define CHIP_ID_B (REGISTERS_BASE + 0x5674) + +#define CHIP_ID_1251_PG10 (0x7010101) +#define CHIP_ID_1251_PG11 (0x7020101) +#define CHIP_ID_1251_PG12 (0x7030101) + +#define ENABLE (REGISTERS_BASE + 0x5450) + +/* Power Management registers */ +#define ELP_CFG_MODE (REGISTERS_BASE + 0x5804) +#define ELP_CMD (REGISTERS_BASE + 0x5808) +#define PLL_CAL_TIME (REGISTERS_BASE + 0x5810) +#define CLK_REQ_TIME (REGISTERS_BASE + 0x5814) +#define CLK_BUF_TIME (REGISTERS_BASE + 0x5818) + +#define CFG_PLL_SYNC_CNT (REGISTERS_BASE + 0x5820) + +/* Scratch Pad registers*/ +#define SCR_PAD0 (REGISTERS_BASE + 0x5608) +#define SCR_PAD1 (REGISTERS_BASE + 0x560C) +#define SCR_PAD2 (REGISTERS_BASE + 0x5610) +#define SCR_PAD3 (REGISTERS_BASE + 0x5614) +#define SCR_PAD4 (REGISTERS_BASE + 0x5618) +#define SCR_PAD4_SET (REGISTERS_BASE + 0x561C) +#define SCR_PAD4_CLR (REGISTERS_BASE + 0x5620) +#define SCR_PAD5 (REGISTERS_BASE + 0x5624) +#define SCR_PAD5_SET (REGISTERS_BASE + 0x5628) +#define SCR_PAD5_CLR (REGISTERS_BASE + 0x562C) +#define SCR_PAD6 (REGISTERS_BASE + 0x5630) +#define SCR_PAD7 (REGISTERS_BASE + 0x5634) +#define SCR_PAD8 (REGISTERS_BASE + 0x5638) +#define SCR_PAD9 (REGISTERS_BASE + 0x563C) + +/* Spare registers*/ +#define SPARE_A1 (REGISTERS_BASE + 0x0994) +#define SPARE_A2 (REGISTERS_BASE + 0x0998) +#define SPARE_A3 (REGISTERS_BASE + 0x099C) +#define SPARE_A4 (REGISTERS_BASE + 0x09A0) +#define SPARE_A5 (REGISTERS_BASE + 0x09A4) +#define SPARE_A6 (REGISTERS_BASE + 0x09A8) +#define SPARE_A7 (REGISTERS_BASE + 0x09AC) +#define SPARE_A8 (REGISTERS_BASE + 0x09B0) +#define SPARE_B1 (REGISTERS_BASE + 0x5420) +#define SPARE_B2 (REGISTERS_BASE + 0x5424) +#define SPARE_B3 (REGISTERS_BASE + 0x5428) +#define SPARE_B4 (REGISTERS_BASE + 0x542C) +#define SPARE_B5 (REGISTERS_BASE + 0x5430) +#define SPARE_B6 (REGISTERS_BASE + 0x5434) +#define SPARE_B7 (REGISTERS_BASE + 0x5438) +#define SPARE_B8 (REGISTERS_BASE + 0x543C) + +enum wl12xx_acx_int_reg { + ACX_REG_INTERRUPT_TRIG, + ACX_REG_INTERRUPT_TRIG_H, + +/*============================================= + Host Interrupt Mask Register - 32bit (RW) + ------------------------------------------ + Setting a bit in this register masks the + corresponding interrupt to the host. + 0 - RX0 - Rx first dubble buffer Data Interrupt + 1 - TXD - Tx Data Interrupt + 2 - TXXFR - Tx Transfer Interrupt + 3 - RX1 - Rx second dubble buffer Data Interrupt + 4 - RXXFR - Rx Transfer Interrupt + 5 - EVENT_A - Event Mailbox interrupt + 6 - EVENT_B - Event Mailbox interrupt + 7 - WNONHST - Wake On Host Interrupt + 8 - TRACE_A - Debug Trace interrupt + 9 - TRACE_B - Debug Trace interrupt + 10 - CDCMP - Command Complete Interrupt + 11 - + 12 - + 13 - + 14 - ICOMP - Initialization Complete Interrupt + 16 - SG SE - Soft Gemini - Sense enable interrupt + 17 - SG SD - Soft Gemini - Sense disable interrupt + 18 - - + 19 - - + 20 - - + 21- - + Default: 0x0001 +*==============================================*/ + ACX_REG_INTERRUPT_MASK, + +/*============================================= + Host Interrupt Mask Set 16bit, (Write only) + ------------------------------------------ + Setting a bit in this register sets + the corresponding bin in ACX_HINT_MASK register + without effecting the mask + state of other bits (0 = no effect). +==============================================*/ + ACX_REG_HINT_MASK_SET, + +/*============================================= + Host Interrupt Mask Clear 16bit,(Write only) + ------------------------------------------ + Setting a bit in this register clears + the corresponding bin in ACX_HINT_MASK register + without effecting the mask + state of other bits (0 = no effect). +=============================================*/ + ACX_REG_HINT_MASK_CLR, + +/*============================================= + Host Interrupt Status Nondestructive Read + 16bit,(Read only) + ------------------------------------------ + The host can read this register to determine + which interrupts are active. + Reading this register doesn't + effect its content. +=============================================*/ + ACX_REG_INTERRUPT_NO_CLEAR, + +/*============================================= + Host Interrupt Status Clear on Read Register + 16bit,(Read only) + ------------------------------------------ + The host can read this register to determine + which interrupts are active. + Reading this register clears it, + thus making all interrupts inactive. +==============================================*/ + ACX_REG_INTERRUPT_CLEAR, + +/*============================================= + Host Interrupt Acknowledge Register + 16bit,(Write only) + ------------------------------------------ + The host can set individual bits in this + register to clear (acknowledge) the corresp. + interrupt status bits in the HINT_STS_CLR and + HINT_STS_ND registers, thus making the + assotiated interrupt inactive. (0-no effect) +==============================================*/ + ACX_REG_INTERRUPT_ACK, + +/*=============================================== + Host Software Reset - 32bit RW + ------------------------------------------ + [31:1] Reserved + 0 SOFT_RESET Soft Reset - When this bit is set, + it holds the Wlan hardware in a soft reset state. + This reset disables all MAC and baseband processor + clocks except the CardBus/PCI interface clock. + It also initializes all MAC state machines except + the host interface. It does not reload the + contents of the EEPROM. When this bit is cleared + (not self-clearing), the Wlan hardware + exits the software reset state. +===============================================*/ + ACX_REG_SLV_SOFT_RESET, + +/*=============================================== + EEPROM Burst Read Start - 32bit RW + ------------------------------------------ + [31:1] Reserved + 0 ACX_EE_START - EEPROM Burst Read Start 0 + Setting this bit starts a burst read from + the external EEPROM. + If this bit is set (after reset) before an EEPROM read/write, + the burst read starts at EEPROM address 0. + Otherwise, it starts at the address + following the address of the previous access. + TheWlan hardware hardware clears this bit automatically. + + Default: 0x00000000 +*================================================*/ + ACX_REG_EE_START, + +/* Embedded ARM CPU Control */ + +/*=============================================== + Halt eCPU - 32bit RW + ------------------------------------------ + 0 HALT_ECPU Halt Embedded CPU - This bit is the + compliment of bit 1 (MDATA2) in the SOR_CFG register. + During a hardware reset, this bit holds + the inverse of MDATA2. + When downloading firmware from the host, + set this bit (pull down MDATA2). + The host clears this bit after downloading the firmware into + zero-wait-state SSRAM. + When loading firmware from Flash, clear this bit (pull up MDATA2) + so that the eCPU can run the bootloader code in Flash + HALT_ECPU eCPU State + -------------------- + 1 halt eCPU + 0 enable eCPU + ===============================================*/ + ACX_REG_ECPU_CONTROL, + + ACX_REG_TABLE_LEN +}; + +#define ACX_SLV_SOFT_RESET_BIT BIT(0) +#define ACX_REG_EEPROM_START_BIT BIT(0) + +/* Command/Information Mailbox Pointers */ + +/*=============================================== + Command Mailbox Pointer - 32bit RW + ------------------------------------------ + This register holds the start address of + the command mailbox located in the Wlan hardware memory. + The host must read this pointer after a reset to + find the location of the command mailbox. + The Wlan hardware initializes the command mailbox + pointer with the default address of the command mailbox. + The command mailbox pointer is not valid until after + the host receives the Init Complete interrupt from + the Wlan hardware. + ===============================================*/ +#define REG_COMMAND_MAILBOX_PTR (SCR_PAD0) + +/*=============================================== + Information Mailbox Pointer - 32bit RW + ------------------------------------------ + This register holds the start address of + the information mailbox located in the Wlan hardware memory. + The host must read this pointer after a reset to find + the location of the information mailbox. + The Wlan hardware initializes the information mailbox pointer + with the default address of the information mailbox. + The information mailbox pointer is not valid + until after the host receives the Init Complete interrupt from + the Wlan hardware. + ===============================================*/ +#define REG_EVENT_MAILBOX_PTR (SCR_PAD1) + + +/* Misc */ + +#define REG_ENABLE_TX_RX (ENABLE) +/* + * Rx configuration (filter) information element + * --------------------------------------------- + */ +#define REG_RX_CONFIG (RX_CFG) +#define REG_RX_FILTER (RX_FILTER_CFG) + + +#define RX_CFG_ENABLE_PHY_HEADER_PLCP 0x0002 + +/* promiscuous - receives all valid frames */ +#define RX_CFG_PROMISCUOUS 0x0008 + +/* receives frames from any BSSID */ +#define RX_CFG_BSSID 0x0020 + +/* receives frames destined to any MAC address */ +#define RX_CFG_MAC 0x0010 + +#define RX_CFG_ENABLE_ONLY_MY_DEST_MAC 0x0010 +#define RX_CFG_ENABLE_ANY_DEST_MAC 0x0000 +#define RX_CFG_ENABLE_ONLY_MY_BSSID 0x0020 +#define RX_CFG_ENABLE_ANY_BSSID 0x0000 + +/* discards all broadcast frames */ +#define RX_CFG_DISABLE_BCAST 0x0200 + +#define RX_CFG_ENABLE_ONLY_MY_SSID 0x0400 +#define RX_CFG_ENABLE_RX_CMPLT_FCS_ERROR 0x0800 +#define RX_CFG_COPY_RX_STATUS 0x2000 +#define RX_CFG_TSF 0x10000 + +#define RX_CONFIG_OPTION_ANY_DST_MY_BSS (RX_CFG_ENABLE_ANY_DEST_MAC | \ + RX_CFG_ENABLE_ONLY_MY_BSSID) + +#define RX_CONFIG_OPTION_MY_DST_ANY_BSS (RX_CFG_ENABLE_ONLY_MY_DEST_MAC\ + | RX_CFG_ENABLE_ANY_BSSID) + +#define RX_CONFIG_OPTION_ANY_DST_ANY_BSS (RX_CFG_ENABLE_ANY_DEST_MAC | \ + RX_CFG_ENABLE_ANY_BSSID) + +#define RX_CONFIG_OPTION_MY_DST_MY_BSS (RX_CFG_ENABLE_ONLY_MY_DEST_MAC\ + | RX_CFG_ENABLE_ONLY_MY_BSSID) + +#define RX_CONFIG_OPTION_FOR_SCAN (RX_CFG_ENABLE_PHY_HEADER_PLCP \ + | RX_CFG_ENABLE_RX_CMPLT_FCS_ERROR \ + | RX_CFG_COPY_RX_STATUS | RX_CFG_TSF) + +#define RX_CONFIG_OPTION_FOR_MEASUREMENT (RX_CFG_ENABLE_ANY_DEST_MAC) + +#define RX_CONFIG_OPTION_FOR_JOIN (RX_CFG_ENABLE_ONLY_MY_BSSID | \ + RX_CFG_ENABLE_ONLY_MY_DEST_MAC) + +#define RX_CONFIG_OPTION_FOR_IBSS_JOIN (RX_CFG_ENABLE_ONLY_MY_SSID | \ + RX_CFG_ENABLE_ONLY_MY_DEST_MAC) + +#define RX_FILTER_OPTION_DEF (CFG_RX_MGMT_EN | CFG_RX_DATA_EN\ + | CFG_RX_CTL_EN | CFG_RX_BCN_EN\ + | CFG_RX_AUTH_EN | CFG_RX_ASSOC_EN) + +#define RX_FILTER_OPTION_FILTER_ALL 0 + +#define RX_FILTER_OPTION_DEF_PRSP_BCN (CFG_RX_PRSP_EN | CFG_RX_MGMT_EN\ + | CFG_RX_RCTS_ACK | CFG_RX_BCN_EN) + +#define RX_FILTER_OPTION_JOIN (CFG_RX_MGMT_EN | CFG_RX_DATA_EN\ + | CFG_RX_BCN_EN | CFG_RX_AUTH_EN\ + | CFG_RX_ASSOC_EN | CFG_RX_RCTS_ACK\ + | CFG_RX_PRSP_EN) + + +/*=============================================== + EEPROM Read/Write Request 32bit RW + ------------------------------------------ + 1 EE_READ - EEPROM Read Request 1 - Setting this bit + loads a single byte of data into the EE_DATA + register from the EEPROM location specified in + the EE_ADDR register. + The Wlan hardware hardware clears this bit automatically. + EE_DATA is valid when this bit is cleared. + + 0 EE_WRITE - EEPROM Write Request - Setting this bit + writes a single byte of data from the EE_DATA register into the + EEPROM location specified in the EE_ADDR register. + The Wlan hardware hardware clears this bit automatically. +*===============================================*/ +#define EE_CTL (REGISTERS_BASE + 0x2000) +#define ACX_EE_CTL_REG EE_CTL +#define EE_WRITE 0x00000001ul +#define EE_READ 0x00000002ul + +/*=============================================== + EEPROM Address - 32bit RW + ------------------------------------------ + This register specifies the address + within the EEPROM from/to which to read/write data. + ===============================================*/ +#define EE_ADDR (REGISTERS_BASE + 0x2008) +#define ACX_EE_ADDR_REG EE_ADDR + +/*=============================================== + EEPROM Data - 32bit RW + ------------------------------------------ + This register either holds the read 8 bits of + data from the EEPROM or the write data + to be written to the EEPROM. + ===============================================*/ +#define EE_DATA (REGISTERS_BASE + 0x2004) +#define ACX_EE_DATA_REG EE_DATA + +#define EEPROM_ACCESS_TO 10000 /* timeout counter */ +#define START_EEPROM_MGR 0x00000001 + +/*=============================================== + EEPROM Base Address - 32bit RW + ------------------------------------------ + This register holds the upper nine bits + [23:15] of the 24-bit Wlan hardware memory + address for burst reads from EEPROM accesses. + The EEPROM provides the lower 15 bits of this address. + The MSB of the address from the EEPROM is ignored. + ===============================================*/ +#define ACX_EE_CFG EE_CFG + +/*=============================================== + GPIO Output Values -32bit, RW + ------------------------------------------ + [31:16] Reserved + [15: 0] Specify the output values (at the output driver inputs) for + GPIO[15:0], respectively. + ===============================================*/ +#define ACX_GPIO_OUT_REG GPIO_OUT +#define ACX_MAX_GPIO_LINES 15 + +/*=============================================== + Contention window -32bit, RW + ------------------------------------------ + [31:26] Reserved + [25:16] Max (0x3ff) + [15:07] Reserved + [06:00] Current contention window value - default is 0x1F + ===============================================*/ +#define ACX_CONT_WIND_CFG_REG CONT_WIND_CFG +#define ACX_CONT_WIND_MIN_MASK 0x0000007f +#define ACX_CONT_WIND_MAX 0x03ff0000 + +/*=============================================== + HI_CFG Interface Configuration Register Values + ------------------------------------------ + ===============================================*/ +#define HI_CFG_UART_ENABLE 0x00000004 +#define HI_CFG_RST232_ENABLE 0x00000008 +#define HI_CFG_CLOCK_REQ_SELECT 0x00000010 +#define HI_CFG_HOST_INT_ENABLE 0x00000020 +#define HI_CFG_VLYNQ_OUTPUT_ENABLE 0x00000040 +#define HI_CFG_HOST_INT_ACTIVE_LOW 0x00000080 +#define HI_CFG_UART_TX_OUT_GPIO_15 0x00000100 +#define HI_CFG_UART_TX_OUT_GPIO_14 0x00000200 +#define HI_CFG_UART_TX_OUT_GPIO_7 0x00000400 + +/* + * NOTE: USE_ACTIVE_HIGH compilation flag should be defined in makefile + * for platforms using active high interrupt level + */ +#ifdef USE_ACTIVE_HIGH +#define HI_CFG_DEF_VAL \ + (HI_CFG_UART_ENABLE | \ + HI_CFG_RST232_ENABLE | \ + HI_CFG_CLOCK_REQ_SELECT | \ + HI_CFG_HOST_INT_ENABLE) +#else +#define HI_CFG_DEF_VAL \ + (HI_CFG_UART_ENABLE | \ + HI_CFG_RST232_ENABLE | \ + HI_CFG_CLOCK_REQ_SELECT | \ + HI_CFG_HOST_INT_ENABLE) + +#endif + +#define REF_FREQ_19_2 0 +#define REF_FREQ_26_0 1 +#define REF_FREQ_38_4 2 +#define REF_FREQ_40_0 3 +#define REF_FREQ_33_6 4 +#define REF_FREQ_NUM 5 + +#define LUT_PARAM_INTEGER_DIVIDER 0 +#define LUT_PARAM_FRACTIONAL_DIVIDER 1 +#define LUT_PARAM_ATTN_BB 2 +#define LUT_PARAM_ALPHA_BB 3 +#define LUT_PARAM_STOP_TIME_BB 4 +#define LUT_PARAM_BB_PLL_LOOP_FILTER 5 +#define LUT_PARAM_NUM 6 + +#define ACX_EEPROMLESS_IND_REG (SCR_PAD4) +#define USE_EEPROM 0 +#define SOFT_RESET_MAX_TIME 1000000 +#define SOFT_RESET_STALL_TIME 1000 +#define NVS_DATA_BUNDARY_ALIGNMENT 4 + + +/* Firmware image load chunk size */ +#define CHUNK_SIZE 512 + +/* Firmware image header size */ +#define FW_HDR_SIZE 8 + +#define ECPU_CONTROL_HALT 0x00000101 + + +/****************************************************************************** + + CHANNELS, BAND & REG DOMAINS definitions + +******************************************************************************/ + + +enum { + RADIO_BAND_2_4GHZ = 0, /* 2.4 Ghz band */ + RADIO_BAND_5GHZ = 1, /* 5 Ghz band */ + RADIO_BAND_JAPAN_4_9_GHZ = 2, + DEFAULT_BAND = RADIO_BAND_2_4GHZ, + INVALID_BAND = 0xFE, + MAX_RADIO_BANDS = 0xFF +}; + +enum { + NO_RATE = 0, + RATE_1MBPS = 0x0A, + RATE_2MBPS = 0x14, + RATE_5_5MBPS = 0x37, + RATE_6MBPS = 0x0B, + RATE_9MBPS = 0x0F, + RATE_11MBPS = 0x6E, + RATE_12MBPS = 0x0A, + RATE_18MBPS = 0x0E, + RATE_22MBPS = 0xDC, + RATE_24MBPS = 0x09, + RATE_36MBPS = 0x0D, + RATE_48MBPS = 0x08, + RATE_54MBPS = 0x0C +}; + +enum { + RATE_INDEX_1MBPS = 0, + RATE_INDEX_2MBPS = 1, + RATE_INDEX_5_5MBPS = 2, + RATE_INDEX_6MBPS = 3, + RATE_INDEX_9MBPS = 4, + RATE_INDEX_11MBPS = 5, + RATE_INDEX_12MBPS = 6, + RATE_INDEX_18MBPS = 7, + RATE_INDEX_22MBPS = 8, + RATE_INDEX_24MBPS = 9, + RATE_INDEX_36MBPS = 10, + RATE_INDEX_48MBPS = 11, + RATE_INDEX_54MBPS = 12, + RATE_INDEX_MAX = RATE_INDEX_54MBPS, + MAX_RATE_INDEX, + INVALID_RATE_INDEX = MAX_RATE_INDEX, + RATE_INDEX_ENUM_MAX_SIZE = 0x7FFFFFFF +}; + +enum { + RATE_MASK_1MBPS = 0x1, + RATE_MASK_2MBPS = 0x2, + RATE_MASK_5_5MBPS = 0x4, + RATE_MASK_11MBPS = 0x20, +}; + +#define SHORT_PREAMBLE_BIT BIT(0) /* CCK or Barker depending on the rate */ +#define OFDM_RATE_BIT BIT(6) +#define PBCC_RATE_BIT BIT(7) + +enum { + CCK_LONG = 0, + CCK_SHORT = SHORT_PREAMBLE_BIT, + PBCC_LONG = PBCC_RATE_BIT, + PBCC_SHORT = PBCC_RATE_BIT | SHORT_PREAMBLE_BIT, + OFDM = OFDM_RATE_BIT +}; + +/****************************************************************************** + +Transmit-Descriptor RATE-SET field definitions... + +Define a new "Rate-Set" for TX path that incorporates the +Rate & Modulation info into a single 16-bit field. + +TxdRateSet_t: +b15 - Indicates Preamble type (1=SHORT, 0=LONG). + Notes: + Must be LONG (0) for 1Mbps rate. + Does not apply (set to 0) for RevG-OFDM rates. +b14 - Indicates PBCC encoding (1=PBCC, 0=not). + Notes: + Does not apply (set to 0) for rates 1 and 2 Mbps. + Does not apply (set to 0) for RevG-OFDM rates. +b13 - Unused (set to 0). +b12-b0 - Supported Rate indicator bits as defined below. + +******************************************************************************/ + + +/************************************************************************* + + Interrupt Trigger Register (Host -> WiLink) + +**************************************************************************/ + +/* Hardware to Embedded CPU Interrupts - first 32-bit register set */ + +/* + * Host Command Interrupt. Setting this bit masks + * the interrupt that the host issues to inform + * the FW that it has sent a command + * to the Wlan hardware Command Mailbox. + */ +#define INTR_TRIG_CMD BIT(0) + +/* + * Host Event Acknowlegde Interrupt. The host + * sets this bit to acknowledge that it received + * the unsolicited information from the event + * mailbox. + */ +#define INTR_TRIG_EVENT_ACK BIT(1) + +/* + * The host sets this bit to inform the Wlan + * FW that a TX packet is in the XFER + * Buffer #0. + */ +#define INTR_TRIG_TX_PROC0 BIT(2) + +/* + * The host sets this bit to inform the FW + * that it read a packet from RX XFER + * Buffer #0. + */ +#define INTR_TRIG_RX_PROC0 BIT(3) + +#define INTR_TRIG_DEBUG_ACK BIT(4) + +#define INTR_TRIG_STATE_CHANGED BIT(5) + + +/* Hardware to Embedded CPU Interrupts - second 32-bit register set */ + +/* + * The host sets this bit to inform the FW + * that it read a packet from RX XFER + * Buffer #1. + */ +#define INTR_TRIG_RX_PROC1 BIT(17) + +/* + * The host sets this bit to inform the Wlan + * hardware that a TX packet is in the XFER + * Buffer #1. + */ +#define INTR_TRIG_TX_PROC1 BIT(18) + +#endif diff --git a/drivers/net/wireless/ti/wl1251/rx.c b/drivers/net/wireless/ti/wl1251/rx.c new file mode 100644 index 0000000..23289d4 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/rx.c @@ -0,0 +1,235 @@ +/* + * This file is part of wl1251 + * + * Copyright (c) 1998-2007 Texas Instruments Incorporated + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + +#include "wl1251.h" +#include "reg.h" +#include "io.h" +#include "rx.h" +#include "cmd.h" +#include "acx.h" + +static void wl1251_rx_header(struct wl1251 *wl, + struct wl1251_rx_descriptor *desc) +{ + u32 rx_packet_ring_addr; + + rx_packet_ring_addr = wl->data_path->rx_packet_ring_addr; + if (wl->rx_current_buffer) + rx_packet_ring_addr += wl->data_path->rx_packet_ring_chunk_size; + + wl1251_mem_read(wl, rx_packet_ring_addr, desc, sizeof(*desc)); +} + +static void wl1251_rx_status(struct wl1251 *wl, + struct wl1251_rx_descriptor *desc, + struct ieee80211_rx_status *status, + u8 beacon) +{ + u64 mactime; + int ret; + + memset(status, 0, sizeof(struct ieee80211_rx_status)); + + status->band = IEEE80211_BAND_2GHZ; + status->mactime = desc->timestamp; + + /* + * The rx status timestamp is a 32 bits value while the TSF is a + * 64 bits one. + * For IBSS merging, TSF is mandatory, so we have to get it + * somehow, so we ask for ACX_TSF_INFO. + * That could be moved to the get_tsf() hook, but unfortunately, + * this one must be atomic, while our SPI routines can sleep. + */ + if ((wl->bss_type == BSS_TYPE_IBSS) && beacon) { + ret = wl1251_acx_tsf_info(wl, &mactime); + if (ret == 0) + status->mactime = mactime; + } + + status->signal = desc->rssi; + + /* + * FIXME: guessing that snr needs to be divided by two, otherwise + * the values don't make any sense + */ + wl->noise = desc->rssi - desc->snr / 2; + + status->freq = ieee80211_channel_to_frequency(desc->channel, + status->band); + + status->flag |= RX_FLAG_MACTIME_START; + + if (desc->flags & RX_DESC_ENCRYPTION_MASK) { + status->flag |= RX_FLAG_IV_STRIPPED | RX_FLAG_MMIC_STRIPPED; + + if (likely(!(desc->flags & RX_DESC_DECRYPT_FAIL))) + status->flag |= RX_FLAG_DECRYPTED; + + if (unlikely(desc->flags & RX_DESC_MIC_FAIL)) + status->flag |= RX_FLAG_MMIC_ERROR; + } + + if (unlikely(!(desc->flags & RX_DESC_VALID_FCS))) + status->flag |= RX_FLAG_FAILED_FCS_CRC; + + switch (desc->rate) { + /* skip 1 and 12 Mbps because they have same value 0x0a */ + case RATE_2MBPS: + status->rate_idx = 1; + break; + case RATE_5_5MBPS: + status->rate_idx = 2; + break; + case RATE_11MBPS: + status->rate_idx = 3; + break; + case RATE_6MBPS: + status->rate_idx = 4; + break; + case RATE_9MBPS: + status->rate_idx = 5; + break; + case RATE_18MBPS: + status->rate_idx = 7; + break; + case RATE_24MBPS: + status->rate_idx = 8; + break; + case RATE_36MBPS: + status->rate_idx = 9; + break; + case RATE_48MBPS: + status->rate_idx = 10; + break; + case RATE_54MBPS: + status->rate_idx = 11; + break; + } + + /* for 1 and 12 Mbps we have to check the modulation */ + if (desc->rate == RATE_1MBPS) { + if (!(desc->mod_pre & OFDM_RATE_BIT)) + /* CCK -> RATE_1MBPS */ + status->rate_idx = 0; + else + /* OFDM -> RATE_12MBPS */ + status->rate_idx = 6; + } + + if (desc->mod_pre & SHORT_PREAMBLE_BIT) + status->flag |= RX_FLAG_SHORTPRE; +} + +static void wl1251_rx_body(struct wl1251 *wl, + struct wl1251_rx_descriptor *desc) +{ + struct sk_buff *skb; + struct ieee80211_rx_status status; + u8 *rx_buffer, beacon = 0; + u16 length, *fc; + u32 curr_id, last_id_inc, rx_packet_ring_addr; + + length = WL1251_RX_ALIGN(desc->length - PLCP_HEADER_LENGTH); + curr_id = (desc->flags & RX_DESC_SEQNUM_MASK) >> RX_DESC_PACKETID_SHIFT; + last_id_inc = (wl->rx_last_id + 1) % (RX_MAX_PACKET_ID + 1); + + if (last_id_inc != curr_id) { + wl1251_warning("curr ID:%d, last ID inc:%d", + curr_id, last_id_inc); + wl->rx_last_id = curr_id; + } else { + wl->rx_last_id = last_id_inc; + } + + rx_packet_ring_addr = wl->data_path->rx_packet_ring_addr + + sizeof(struct wl1251_rx_descriptor) + 20; + if (wl->rx_current_buffer) + rx_packet_ring_addr += wl->data_path->rx_packet_ring_chunk_size; + + skb = __dev_alloc_skb(length, GFP_KERNEL); + if (!skb) { + wl1251_error("Couldn't allocate RX frame"); + return; + } + + rx_buffer = skb_put(skb, length); + wl1251_mem_read(wl, rx_packet_ring_addr, rx_buffer, length); + + /* The actual length doesn't include the target's alignment */ + skb->len = desc->length - PLCP_HEADER_LENGTH; + + fc = (u16 *)skb->data; + + if ((*fc & IEEE80211_FCTL_STYPE) == IEEE80211_STYPE_BEACON) + beacon = 1; + + wl1251_rx_status(wl, desc, &status, beacon); + + wl1251_debug(DEBUG_RX, "rx skb 0x%p: %d B %s", skb, skb->len, + beacon ? "beacon" : ""); + + memcpy(IEEE80211_SKB_RXCB(skb), &status, sizeof(status)); + ieee80211_rx_ni(wl->hw, skb); +} + +static void wl1251_rx_ack(struct wl1251 *wl) +{ + u32 data, addr; + + if (wl->rx_current_buffer) { + addr = ACX_REG_INTERRUPT_TRIG_H; + data = INTR_TRIG_RX_PROC1; + } else { + addr = ACX_REG_INTERRUPT_TRIG; + data = INTR_TRIG_RX_PROC0; + } + + wl1251_reg_write32(wl, addr, data); + + /* Toggle buffer ring */ + wl->rx_current_buffer = !wl->rx_current_buffer; +} + + +void wl1251_rx(struct wl1251 *wl) +{ + struct wl1251_rx_descriptor *rx_desc; + + if (wl->state != WL1251_STATE_ON) + return; + + rx_desc = wl->rx_descriptor; + + /* We first read the frame's header */ + wl1251_rx_header(wl, rx_desc); + + /* Now we can read the body */ + wl1251_rx_body(wl, rx_desc); + + /* Finally, we need to ACK the RX */ + wl1251_rx_ack(wl); +} diff --git a/drivers/net/wireless/ti/wl1251/rx.h b/drivers/net/wireless/ti/wl1251/rx.h new file mode 100644 index 0000000..4448f63 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/rx.h @@ -0,0 +1,122 @@ +/* + * This file is part of wl1251 + * + * Copyright (c) 1998-2007 Texas Instruments Incorporated + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL1251_RX_H__ +#define __WL1251_RX_H__ + +#include + +#include "wl1251.h" + +/* + * RX PATH + * + * The Rx path uses a double buffer and an rx_contro structure, each located + * at a fixed address in the device memory. The host keeps track of which + * buffer is available and alternates between them on a per packet basis. + * The size of each of the two buffers is large enough to hold the longest + * 802.3 packet. + * The RX path goes like that: + * 1) The target generates an interrupt each time a new packet is received. + * There are 2 RX interrupts, one for each buffer. + * 2) The host reads the received packet from one of the double buffers. + * 3) The host triggers a target interrupt. + * 4) The target prepares the next RX packet. + */ + +#define WL1251_RX_MAX_RSSI -30 +#define WL1251_RX_MIN_RSSI -95 + +#define WL1251_RX_ALIGN_TO 4 +#define WL1251_RX_ALIGN(len) (((len) + WL1251_RX_ALIGN_TO - 1) & \ + ~(WL1251_RX_ALIGN_TO - 1)) + +#define SHORT_PREAMBLE_BIT BIT(0) +#define OFDM_RATE_BIT BIT(6) +#define PBCC_RATE_BIT BIT(7) + +#define PLCP_HEADER_LENGTH 8 +#define RX_DESC_PACKETID_SHIFT 11 +#define RX_MAX_PACKET_ID 3 + +#define RX_DESC_VALID_FCS 0x0001 +#define RX_DESC_MATCH_RXADDR1 0x0002 +#define RX_DESC_MCAST 0x0004 +#define RX_DESC_STAINTIM 0x0008 +#define RX_DESC_VIRTUAL_BM 0x0010 +#define RX_DESC_BCAST 0x0020 +#define RX_DESC_MATCH_SSID 0x0040 +#define RX_DESC_MATCH_BSSID 0x0080 +#define RX_DESC_ENCRYPTION_MASK 0x0300 +#define RX_DESC_MEASURMENT 0x0400 +#define RX_DESC_SEQNUM_MASK 0x1800 +#define RX_DESC_MIC_FAIL 0x2000 +#define RX_DESC_DECRYPT_FAIL 0x4000 + +struct wl1251_rx_descriptor { + u32 timestamp; /* In microseconds */ + u16 length; /* Paylod length, including headers */ + u16 flags; + + /* + * 0 - 802.11 + * 1 - 802.3 + * 2 - IP + * 3 - Raw Codec + */ + u8 type; + + /* + * Received Rate: + * 0x0A - 1MBPS + * 0x14 - 2MBPS + * 0x37 - 5_5MBPS + * 0x0B - 6MBPS + * 0x0F - 9MBPS + * 0x6E - 11MBPS + * 0x0A - 12MBPS + * 0x0E - 18MBPS + * 0xDC - 22MBPS + * 0x09 - 24MBPS + * 0x0D - 36MBPS + * 0x08 - 48MBPS + * 0x0C - 54MBPS + */ + u8 rate; + + u8 mod_pre; /* Modulation and preamble */ + u8 channel; + + /* + * 0 - 2.4 Ghz + * 1 - 5 Ghz + */ + u8 band; + + s8 rssi; /* in dB */ + u8 rcpi; /* in dB */ + u8 snr; /* in dB */ +} __packed; + +void wl1251_rx(struct wl1251 *wl); + +#endif diff --git a/drivers/net/wireless/ti/wl1251/sdio.c b/drivers/net/wireless/ti/wl1251/sdio.c new file mode 100644 index 0000000..e2b3d9c --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/sdio.c @@ -0,0 +1,376 @@ +/* + * wl12xx SDIO routines + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * Copyright (C) 2005 Texas Instruments Incorporated + * Copyright (C) 2008 Google Inc + * Copyright (C) 2009 Bob Copeland (me@bobcopeland.com) + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wl1251.h" + +#ifndef SDIO_VENDOR_ID_TI +#define SDIO_VENDOR_ID_TI 0x104c +#endif + +#ifndef SDIO_DEVICE_ID_TI_WL1251 +#define SDIO_DEVICE_ID_TI_WL1251 0x9066 +#endif + +struct wl1251_sdio { + struct sdio_func *func; + u32 elp_val; +}; + +static struct sdio_func *wl_to_func(struct wl1251 *wl) +{ + struct wl1251_sdio *wl_sdio = wl->if_priv; + return wl_sdio->func; +} + +static void wl1251_sdio_interrupt(struct sdio_func *func) +{ + struct wl1251 *wl = sdio_get_drvdata(func); + + wl1251_debug(DEBUG_IRQ, "IRQ"); + + /* FIXME should be synchronous for sdio */ + ieee80211_queue_work(wl->hw, &wl->irq_work); +} + +static const struct sdio_device_id wl1251_devices[] = { + { SDIO_DEVICE(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1251) }, + {} +}; +MODULE_DEVICE_TABLE(sdio, wl1251_devices); + + +static void wl1251_sdio_read(struct wl1251 *wl, int addr, + void *buf, size_t len) +{ + int ret; + struct sdio_func *func = wl_to_func(wl); + + sdio_claim_host(func); + ret = sdio_memcpy_fromio(func, buf, addr, len); + if (ret) + wl1251_error("sdio read failed (%d)", ret); + sdio_release_host(func); +} + +static void wl1251_sdio_write(struct wl1251 *wl, int addr, + void *buf, size_t len) +{ + int ret; + struct sdio_func *func = wl_to_func(wl); + + sdio_claim_host(func); + ret = sdio_memcpy_toio(func, addr, buf, len); + if (ret) + wl1251_error("sdio write failed (%d)", ret); + sdio_release_host(func); +} + +static void wl1251_sdio_read_elp(struct wl1251 *wl, int addr, u32 *val) +{ + int ret = 0; + struct wl1251_sdio *wl_sdio = wl->if_priv; + struct sdio_func *func = wl_sdio->func; + + /* + * The hardware only supports RAW (read after write) access for + * reading, regular sdio_readb won't work here (it interprets + * the unused bits of CMD52 as write data even if we send read + * request). + */ + sdio_claim_host(func); + *val = sdio_writeb_readb(func, wl_sdio->elp_val, addr, &ret); + sdio_release_host(func); + + if (ret) + wl1251_error("sdio_readb failed (%d)", ret); +} + +static void wl1251_sdio_write_elp(struct wl1251 *wl, int addr, u32 val) +{ + int ret = 0; + struct wl1251_sdio *wl_sdio = wl->if_priv; + struct sdio_func *func = wl_sdio->func; + + sdio_claim_host(func); + sdio_writeb(func, val, addr, &ret); + sdio_release_host(func); + + if (ret) + wl1251_error("sdio_writeb failed (%d)", ret); + else + wl_sdio->elp_val = val; +} + +static void wl1251_sdio_reset(struct wl1251 *wl) +{ +} + +static void wl1251_sdio_enable_irq(struct wl1251 *wl) +{ + struct sdio_func *func = wl_to_func(wl); + + sdio_claim_host(func); + sdio_claim_irq(func, wl1251_sdio_interrupt); + sdio_release_host(func); +} + +static void wl1251_sdio_disable_irq(struct wl1251 *wl) +{ + struct sdio_func *func = wl_to_func(wl); + + sdio_claim_host(func); + sdio_release_irq(func); + sdio_release_host(func); +} + +/* Interrupts when using dedicated WLAN_IRQ pin */ +static irqreturn_t wl1251_line_irq(int irq, void *cookie) +{ + struct wl1251 *wl = cookie; + + ieee80211_queue_work(wl->hw, &wl->irq_work); + + return IRQ_HANDLED; +} + +static void wl1251_enable_line_irq(struct wl1251 *wl) +{ + return enable_irq(wl->irq); +} + +static void wl1251_disable_line_irq(struct wl1251 *wl) +{ + return disable_irq(wl->irq); +} + +static int wl1251_sdio_set_power(struct wl1251 *wl, bool enable) +{ + struct sdio_func *func = wl_to_func(wl); + int ret; + + if (enable) { + /* + * Power is controlled by runtime PM, but we still call board + * callback in case it wants to do any additional setup, + * for example enabling clock buffer for the module. + */ + if (wl->set_power) + wl->set_power(true); + + ret = pm_runtime_get_sync(&func->dev); + if (ret < 0) { + pm_runtime_put_sync(&func->dev); + goto out; + } + + sdio_claim_host(func); + sdio_enable_func(func); + sdio_release_host(func); + } else { + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); + + ret = pm_runtime_put_sync(&func->dev); + if (ret < 0) + goto out; + + if (wl->set_power) + wl->set_power(false); + } + +out: + return ret; +} + +static struct wl1251_if_operations wl1251_sdio_ops = { + .read = wl1251_sdio_read, + .write = wl1251_sdio_write, + .write_elp = wl1251_sdio_write_elp, + .read_elp = wl1251_sdio_read_elp, + .reset = wl1251_sdio_reset, + .power = wl1251_sdio_set_power, +}; + +static int wl1251_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + int ret; + struct wl1251 *wl; + struct ieee80211_hw *hw; + struct wl1251_sdio *wl_sdio; + const struct wl12xx_platform_data *wl12xx_board_data; + + hw = wl1251_alloc_hw(); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + wl = hw->priv; + + wl_sdio = kzalloc(sizeof(*wl_sdio), GFP_KERNEL); + if (wl_sdio == NULL) { + ret = -ENOMEM; + goto out_free_hw; + } + + sdio_claim_host(func); + ret = sdio_enable_func(func); + if (ret) + goto release; + + sdio_set_block_size(func, 512); + sdio_release_host(func); + + SET_IEEE80211_DEV(hw, &func->dev); + wl_sdio->func = func; + wl->if_priv = wl_sdio; + wl->if_ops = &wl1251_sdio_ops; + + wl12xx_board_data = wl12xx_get_platform_data(); + if (!IS_ERR(wl12xx_board_data)) { + wl->set_power = wl12xx_board_data->set_power; + wl->irq = wl12xx_board_data->irq; + wl->use_eeprom = wl12xx_board_data->use_eeprom; + } + + if (wl->irq) { + irq_set_status_flags(wl->irq, IRQ_NOAUTOEN); + ret = request_irq(wl->irq, wl1251_line_irq, 0, "wl1251", wl); + if (ret < 0) { + wl1251_error("request_irq() failed: %d", ret); + goto disable; + } + + irq_set_irq_type(wl->irq, IRQ_TYPE_EDGE_RISING); + + wl1251_sdio_ops.enable_irq = wl1251_enable_line_irq; + wl1251_sdio_ops.disable_irq = wl1251_disable_line_irq; + + wl1251_info("using dedicated interrupt line"); + } else { + wl1251_sdio_ops.enable_irq = wl1251_sdio_enable_irq; + wl1251_sdio_ops.disable_irq = wl1251_sdio_disable_irq; + + wl1251_info("using SDIO interrupt"); + } + + ret = wl1251_init_ieee80211(wl); + if (ret) + goto out_free_irq; + + sdio_set_drvdata(func, wl); + + /* Tell PM core that we don't need the card to be powered now */ + pm_runtime_put_noidle(&func->dev); + + return ret; + +out_free_irq: + if (wl->irq) + free_irq(wl->irq, wl); +disable: + sdio_claim_host(func); + sdio_disable_func(func); +release: + sdio_release_host(func); + kfree(wl_sdio); +out_free_hw: + wl1251_free_hw(wl); + return ret; +} + +static void wl1251_sdio_remove(struct sdio_func *func) +{ + struct wl1251 *wl = sdio_get_drvdata(func); + struct wl1251_sdio *wl_sdio = wl->if_priv; + + /* Undo decrement done above in wl1251_probe */ + pm_runtime_get_noresume(&func->dev); + + if (wl->irq) + free_irq(wl->irq, wl); + wl1251_free_hw(wl); + kfree(wl_sdio); + + sdio_claim_host(func); + sdio_release_irq(func); + sdio_disable_func(func); + sdio_release_host(func); +} + +static int wl1251_suspend(struct device *dev) +{ + /* + * Tell MMC/SDIO core it's OK to power down the card + * (if it isn't already), but not to remove it completely. + */ + return 0; +} + +static int wl1251_resume(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops wl1251_sdio_pm_ops = { + .suspend = wl1251_suspend, + .resume = wl1251_resume, +}; + +static struct sdio_driver wl1251_sdio_driver = { + .name = "wl1251_sdio", + .id_table = wl1251_devices, + .probe = wl1251_sdio_probe, + .remove = wl1251_sdio_remove, + .drv.pm = &wl1251_sdio_pm_ops, +}; + +static int __init wl1251_sdio_init(void) +{ + int err; + + err = sdio_register_driver(&wl1251_sdio_driver); + if (err) + wl1251_error("failed to register sdio driver: %d", err); + return err; +} + +static void __exit wl1251_sdio_exit(void) +{ + sdio_unregister_driver(&wl1251_sdio_driver); + wl1251_notice("unloaded"); +} + +module_init(wl1251_sdio_init); +module_exit(wl1251_sdio_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kalle Valo "); diff --git a/drivers/net/wireless/ti/wl1251/spi.c b/drivers/net/wireless/ti/wl1251/spi.c new file mode 100644 index 0000000..ddd7027 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/spi.c @@ -0,0 +1,361 @@ +/* + * This file is part of wl1251 + * + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#if (LINUX_VERSION_CODE == KERNEL_VERSION(2,6,28)) +#include +#endif +#include +#include + +#include "wl1251.h" +#include "reg.h" +#include "spi.h" + +static irqreturn_t wl1251_irq(int irq, void *cookie) +{ + struct wl1251 *wl; + + wl1251_debug(DEBUG_IRQ, "IRQ"); + + wl = cookie; + + ieee80211_queue_work(wl->hw, &wl->irq_work); + + return IRQ_HANDLED; +} + +static struct spi_device *wl_to_spi(struct wl1251 *wl) +{ + return wl->if_priv; +} + +static void wl1251_spi_reset(struct wl1251 *wl) +{ + u8 *cmd; + struct spi_transfer t; + struct spi_message m; + + cmd = kzalloc(WSPI_INIT_CMD_LEN, GFP_KERNEL); + if (!cmd) { + wl1251_error("could not allocate cmd for spi reset"); + return; + } + + memset(&t, 0, sizeof(t)); + spi_message_init(&m); + + memset(cmd, 0xff, WSPI_INIT_CMD_LEN); + + t.tx_buf = cmd; + t.len = WSPI_INIT_CMD_LEN; + spi_message_add_tail(&t, &m); + + spi_sync(wl_to_spi(wl), &m); + + wl1251_dump(DEBUG_SPI, "spi reset -> ", cmd, WSPI_INIT_CMD_LEN); + + kfree(cmd); +} + +static void wl1251_spi_wake(struct wl1251 *wl) +{ + u8 crc[WSPI_INIT_CMD_CRC_LEN], *cmd; + struct spi_transfer t; + struct spi_message m; + + cmd = kzalloc(WSPI_INIT_CMD_LEN, GFP_KERNEL); + if (!cmd) { + wl1251_error("could not allocate cmd for spi init"); + return; + } + + memset(crc, 0, sizeof(crc)); + memset(&t, 0, sizeof(t)); + spi_message_init(&m); + + /* + * Set WSPI_INIT_COMMAND + * the data is being send from the MSB to LSB + */ + cmd[2] = 0xff; + cmd[3] = 0xff; + cmd[1] = WSPI_INIT_CMD_START | WSPI_INIT_CMD_TX; + cmd[0] = 0; + cmd[7] = 0; + cmd[6] |= HW_ACCESS_WSPI_INIT_CMD_MASK << 3; + cmd[6] |= HW_ACCESS_WSPI_FIXED_BUSY_LEN & WSPI_INIT_CMD_FIXEDBUSY_LEN; + + if (HW_ACCESS_WSPI_FIXED_BUSY_LEN == 0) + cmd[5] |= WSPI_INIT_CMD_DIS_FIXEDBUSY; + else + cmd[5] |= WSPI_INIT_CMD_EN_FIXEDBUSY; + + cmd[5] |= WSPI_INIT_CMD_IOD | WSPI_INIT_CMD_IP | WSPI_INIT_CMD_CS + | WSPI_INIT_CMD_WSPI | WSPI_INIT_CMD_WS; + + crc[0] = cmd[1]; + crc[1] = cmd[0]; + crc[2] = cmd[7]; + crc[3] = cmd[6]; + crc[4] = cmd[5]; + + cmd[4] |= crc7(0, crc, WSPI_INIT_CMD_CRC_LEN) << 1; + cmd[4] |= WSPI_INIT_CMD_END; + + t.tx_buf = cmd; + t.len = WSPI_INIT_CMD_LEN; + spi_message_add_tail(&t, &m); + + spi_sync(wl_to_spi(wl), &m); + + wl1251_dump(DEBUG_SPI, "spi init -> ", cmd, WSPI_INIT_CMD_LEN); + + kfree(cmd); +} + +static void wl1251_spi_reset_wake(struct wl1251 *wl) +{ + wl1251_spi_reset(wl); + wl1251_spi_wake(wl); +} + +static void wl1251_spi_read(struct wl1251 *wl, int addr, void *buf, + size_t len) +{ + struct spi_transfer t[3]; + struct spi_message m; + u8 *busy_buf; + u32 *cmd; + + cmd = &wl->buffer_cmd; + busy_buf = wl->buffer_busyword; + + *cmd = 0; + *cmd |= WSPI_CMD_READ; + *cmd |= (len << WSPI_CMD_BYTE_LENGTH_OFFSET) & WSPI_CMD_BYTE_LENGTH; + *cmd |= addr & WSPI_CMD_BYTE_ADDR; + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + t[0].tx_buf = cmd; + t[0].len = 4; + spi_message_add_tail(&t[0], &m); + + /* Busy and non busy words read */ + t[1].rx_buf = busy_buf; + t[1].len = WL1251_BUSY_WORD_LEN; + spi_message_add_tail(&t[1], &m); + + t[2].rx_buf = buf; + t[2].len = len; + spi_message_add_tail(&t[2], &m); + + spi_sync(wl_to_spi(wl), &m); + + /* FIXME: check busy words */ + + wl1251_dump(DEBUG_SPI, "spi_read cmd -> ", cmd, sizeof(*cmd)); + wl1251_dump(DEBUG_SPI, "spi_read buf <- ", buf, len); +} + +static void wl1251_spi_write(struct wl1251 *wl, int addr, void *buf, + size_t len) +{ + struct spi_transfer t[2]; + struct spi_message m; + u32 *cmd; + + cmd = &wl->buffer_cmd; + + *cmd = 0; + *cmd |= WSPI_CMD_WRITE; + *cmd |= (len << WSPI_CMD_BYTE_LENGTH_OFFSET) & WSPI_CMD_BYTE_LENGTH; + *cmd |= addr & WSPI_CMD_BYTE_ADDR; + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + t[0].tx_buf = cmd; + t[0].len = sizeof(*cmd); + spi_message_add_tail(&t[0], &m); + + t[1].tx_buf = buf; + t[1].len = len; + spi_message_add_tail(&t[1], &m); + + spi_sync(wl_to_spi(wl), &m); + + wl1251_dump(DEBUG_SPI, "spi_write cmd -> ", cmd, sizeof(*cmd)); + wl1251_dump(DEBUG_SPI, "spi_write buf -> ", buf, len); +} + +static void wl1251_spi_enable_irq(struct wl1251 *wl) +{ + return enable_irq(wl->irq); +} + +static void wl1251_spi_disable_irq(struct wl1251 *wl) +{ + return disable_irq(wl->irq); +} + +static int wl1251_spi_set_power(struct wl1251 *wl, bool enable) +{ + if (wl->set_power) + wl->set_power(enable); + + return 0; +} + +static const struct wl1251_if_operations wl1251_spi_ops = { + .read = wl1251_spi_read, + .write = wl1251_spi_write, + .reset = wl1251_spi_reset_wake, + .enable_irq = wl1251_spi_enable_irq, + .disable_irq = wl1251_spi_disable_irq, + .power = wl1251_spi_set_power, +}; + +static int wl1251_spi_probe(struct spi_device *spi) +{ + struct wl12xx_platform_data *pdata; + struct ieee80211_hw *hw; + struct wl1251 *wl; + int ret; + + pdata = spi->dev.platform_data; + if (!pdata) { + wl1251_error("no platform data"); + return -ENODEV; + } + + hw = wl1251_alloc_hw(); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + wl = hw->priv; + + SET_IEEE80211_DEV(hw, &spi->dev); + spi_set_drvdata(spi, wl); + wl->if_priv = spi; + wl->if_ops = &wl1251_spi_ops; + + /* This is the only SPI value that we need to set here, the rest + * comes from the board-peripherals file */ + spi->bits_per_word = 32; + + ret = spi_setup(spi); + if (ret < 0) { + wl1251_error("spi_setup failed"); + goto out_free; + } + + wl->set_power = pdata->set_power; + if (!wl->set_power) { + wl1251_error("set power function missing in platform data"); + return -ENODEV; + } + + wl->irq = spi->irq; + if (wl->irq < 0) { + wl1251_error("irq missing in platform data"); + return -ENODEV; + } + + wl->use_eeprom = pdata->use_eeprom; + + irq_set_status_flags(wl->irq, IRQ_NOAUTOEN); + ret = request_irq(wl->irq, wl1251_irq, 0, DRIVER_NAME, wl); + if (ret < 0) { + wl1251_error("request_irq() failed: %d", ret); + goto out_free; + } + + irq_set_irq_type(wl->irq, IRQ_TYPE_EDGE_RISING); + + ret = wl1251_init_ieee80211(wl); + if (ret) + goto out_irq; + + return 0; + + out_irq: + free_irq(wl->irq, wl); + + out_free: + ieee80211_free_hw(hw); + + return ret; +} + +static int wl1251_spi_remove(struct spi_device *spi) +{ + struct wl1251 *wl = spi_get_drvdata(spi); + + free_irq(wl->irq, wl); + wl1251_free_hw(wl); + + return 0; +} + +static struct spi_driver wl1251_spi_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + + .probe = wl1251_spi_probe, + .remove = wl1251_spi_remove, +}; + +static int __init wl1251_spi_init(void) +{ + int ret; + + ret = spi_register_driver(&wl1251_spi_driver); + if (ret < 0) { + wl1251_error("failed to register spi driver: %d", ret); + goto out; + } + +out: + return ret; +} + +static void __exit wl1251_spi_exit(void) +{ + spi_unregister_driver(&wl1251_spi_driver); + + wl1251_notice("unloaded"); +} + +module_init(wl1251_spi_init); +module_exit(wl1251_spi_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kalle Valo "); +MODULE_ALIAS("spi:wl1251"); diff --git a/drivers/net/wireless/ti/wl1251/spi.h b/drivers/net/wireless/ti/wl1251/spi.h new file mode 100644 index 0000000..16d5069 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/spi.h @@ -0,0 +1,59 @@ +/* + * This file is part of wl1251 + * + * Copyright (c) 1998-2007 Texas Instruments Incorporated + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL1251_SPI_H__ +#define __WL1251_SPI_H__ + +#include "cmd.h" +#include "acx.h" +#include "reg.h" + +#define WSPI_CMD_READ 0x40000000 +#define WSPI_CMD_WRITE 0x00000000 +#define WSPI_CMD_FIXED 0x20000000 +#define WSPI_CMD_BYTE_LENGTH 0x1FFE0000 +#define WSPI_CMD_BYTE_LENGTH_OFFSET 17 +#define WSPI_CMD_BYTE_ADDR 0x0001FFFF + +#define WSPI_INIT_CMD_CRC_LEN 5 + +#define WSPI_INIT_CMD_START 0x00 +#define WSPI_INIT_CMD_TX 0x40 +/* the extra bypass bit is sampled by the TNET as '1' */ +#define WSPI_INIT_CMD_BYPASS_BIT 0x80 +#define WSPI_INIT_CMD_FIXEDBUSY_LEN 0x07 +#define WSPI_INIT_CMD_EN_FIXEDBUSY 0x80 +#define WSPI_INIT_CMD_DIS_FIXEDBUSY 0x00 +#define WSPI_INIT_CMD_IOD 0x40 +#define WSPI_INIT_CMD_IP 0x20 +#define WSPI_INIT_CMD_CS 0x10 +#define WSPI_INIT_CMD_WS 0x08 +#define WSPI_INIT_CMD_WSPI 0x01 +#define WSPI_INIT_CMD_END 0x01 + +#define WSPI_INIT_CMD_LEN 8 + +#define HW_ACCESS_WSPI_FIXED_BUSY_LEN \ + ((WL1251_BUSY_WORD_LEN - 4) / sizeof(u32)) +#define HW_ACCESS_WSPI_INIT_CMD_MASK 0 + +#endif /* __WL1251_SPI_H__ */ diff --git a/drivers/net/wireless/ti/wl1251/tx.c b/drivers/net/wireless/ti/wl1251/tx.c new file mode 100644 index 0000000..28121c5 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/tx.c @@ -0,0 +1,560 @@ +/* + * This file is part of wl1251 + * + * Copyright (c) 1998-2007 Texas Instruments Incorporated + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include + +#include "wl1251.h" +#include "reg.h" +#include "tx.h" +#include "ps.h" +#include "io.h" + +static bool wl1251_tx_double_buffer_busy(struct wl1251 *wl, u32 data_out_count) +{ + int used, data_in_count; + + data_in_count = wl->data_in_count; + + if (data_in_count < data_out_count) + /* data_in_count has wrapped */ + data_in_count += TX_STATUS_DATA_OUT_COUNT_MASK + 1; + + used = data_in_count - data_out_count; + + WARN_ON(used < 0); + WARN_ON(used > DP_TX_PACKET_RING_CHUNK_NUM); + + if (used >= DP_TX_PACKET_RING_CHUNK_NUM) + return true; + else + return false; +} + +static int wl1251_tx_path_status(struct wl1251 *wl) +{ + u32 status, addr, data_out_count; + bool busy; + + addr = wl->data_path->tx_control_addr; + status = wl1251_mem_read32(wl, addr); + data_out_count = status & TX_STATUS_DATA_OUT_COUNT_MASK; + busy = wl1251_tx_double_buffer_busy(wl, data_out_count); + + if (busy) + return -EBUSY; + + return 0; +} + +static int wl1251_tx_id(struct wl1251 *wl, struct sk_buff *skb) +{ + int i; + + for (i = 0; i < FW_TX_CMPLT_BLOCK_SIZE; i++) + if (wl->tx_frames[i] == NULL) { + wl->tx_frames[i] = skb; + return i; + } + + return -EBUSY; +} + +static void wl1251_tx_control(struct tx_double_buffer_desc *tx_hdr, + struct ieee80211_tx_info *control, u16 fc) +{ + *(u16 *)&tx_hdr->control = 0; + + tx_hdr->control.rate_policy = 0; + + /* 802.11 packets */ + tx_hdr->control.packet_type = 0; + + if (control->flags & IEEE80211_TX_CTL_NO_ACK) + tx_hdr->control.ack_policy = 1; + + tx_hdr->control.tx_complete = 1; + + if ((fc & IEEE80211_FTYPE_DATA) && + ((fc & IEEE80211_STYPE_QOS_DATA) || + (fc & IEEE80211_STYPE_QOS_NULLFUNC))) + tx_hdr->control.qos = 1; +} + +/* RSN + MIC = 8 + 8 = 16 bytes (worst case - AES). */ +#define MAX_MSDU_SECURITY_LENGTH 16 +#define MAX_MPDU_SECURITY_LENGTH 16 +#define WLAN_QOS_HDR_LEN 26 +#define MAX_MPDU_HEADER_AND_SECURITY (MAX_MPDU_SECURITY_LENGTH + \ + WLAN_QOS_HDR_LEN) +#define HW_BLOCK_SIZE 252 +static void wl1251_tx_frag_block_num(struct tx_double_buffer_desc *tx_hdr) +{ + u16 payload_len, frag_threshold, mem_blocks; + u16 num_mpdus, mem_blocks_per_frag; + + frag_threshold = IEEE80211_MAX_FRAG_THRESHOLD; + tx_hdr->frag_threshold = cpu_to_le16(frag_threshold); + + payload_len = le16_to_cpu(tx_hdr->length) + MAX_MSDU_SECURITY_LENGTH; + + if (payload_len > frag_threshold) { + mem_blocks_per_frag = + ((frag_threshold + MAX_MPDU_HEADER_AND_SECURITY) / + HW_BLOCK_SIZE) + 1; + num_mpdus = payload_len / frag_threshold; + mem_blocks = num_mpdus * mem_blocks_per_frag; + payload_len -= num_mpdus * frag_threshold; + num_mpdus++; + + } else { + mem_blocks_per_frag = 0; + mem_blocks = 0; + num_mpdus = 1; + } + + mem_blocks += (payload_len / HW_BLOCK_SIZE) + 1; + + if (num_mpdus > 1) + mem_blocks += min(num_mpdus, mem_blocks_per_frag); + + tx_hdr->num_mem_blocks = mem_blocks; +} + +static int wl1251_tx_fill_hdr(struct wl1251 *wl, struct sk_buff *skb, + struct ieee80211_tx_info *control) +{ + struct tx_double_buffer_desc *tx_hdr; + struct ieee80211_rate *rate; + int id; + u16 fc; + + if (!skb) + return -EINVAL; + + id = wl1251_tx_id(wl, skb); + if (id < 0) + return id; + + fc = *(u16 *)skb->data; + tx_hdr = (struct tx_double_buffer_desc *) skb_push(skb, + sizeof(*tx_hdr)); + + tx_hdr->length = cpu_to_le16(skb->len - sizeof(*tx_hdr)); + rate = ieee80211_get_tx_rate(wl->hw, control); + tx_hdr->rate = cpu_to_le16(rate->hw_value); + tx_hdr->expiry_time = cpu_to_le32(1 << 16); + tx_hdr->id = id; + + tx_hdr->xmit_queue = wl1251_tx_get_queue(skb_get_queue_mapping(skb)); + + wl1251_tx_control(tx_hdr, control, fc); + wl1251_tx_frag_block_num(tx_hdr); + + return 0; +} + +/* We copy the packet to the target */ +static int wl1251_tx_send_packet(struct wl1251 *wl, struct sk_buff *skb, + struct ieee80211_tx_info *control) +{ + struct tx_double_buffer_desc *tx_hdr; + int len; + u32 addr; + + if (!skb) + return -EINVAL; + + tx_hdr = (struct tx_double_buffer_desc *) skb->data; + + if (control->control.hw_key && + control->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) { + int hdrlen; + __le16 fc; + u16 length; + u8 *pos; + + fc = *(__le16 *)(skb->data + sizeof(*tx_hdr)); + length = le16_to_cpu(tx_hdr->length) + WL1251_TKIP_IV_SPACE; + tx_hdr->length = cpu_to_le16(length); + + hdrlen = ieee80211_hdrlen(fc); + + pos = skb_push(skb, WL1251_TKIP_IV_SPACE); + memmove(pos, pos + WL1251_TKIP_IV_SPACE, + sizeof(*tx_hdr) + hdrlen); + } + + /* Revisit. This is a workaround for getting non-aligned packets. + This happens at least with EAPOL packets from the user space. + Our DMA requires packets to be aligned on a 4-byte boundary. + */ + if (unlikely((long)skb->data & 0x03)) { + int offset = (4 - (long)skb->data) & 0x03; + wl1251_debug(DEBUG_TX, "skb offset %d", offset); + + /* check whether the current skb can be used */ + if (skb_cloned(skb) || (skb_tailroom(skb) < offset)) { + struct sk_buff *newskb = skb_copy_expand(skb, 0, 3, + GFP_KERNEL); + + if (unlikely(newskb == NULL)) { + wl1251_error("Can't allocate skb!"); + return -EINVAL; + } + + tx_hdr = (struct tx_double_buffer_desc *) newskb->data; + + dev_kfree_skb_any(skb); + wl->tx_frames[tx_hdr->id] = skb = newskb; + + offset = (4 - (long)skb->data) & 0x03; + wl1251_debug(DEBUG_TX, "new skb offset %d", offset); + } + + /* align the buffer on a 4-byte boundary */ + if (offset) { + unsigned char *src = skb->data; + skb_reserve(skb, offset); + memmove(skb->data, src, skb->len); + tx_hdr = (struct tx_double_buffer_desc *) skb->data; + } + } + + /* Our skb->data at this point includes the HW header */ + len = WL1251_TX_ALIGN(skb->len); + + if (wl->data_in_count & 0x1) + addr = wl->data_path->tx_packet_ring_addr + + wl->data_path->tx_packet_ring_chunk_size; + else + addr = wl->data_path->tx_packet_ring_addr; + + wl1251_mem_write(wl, addr, skb->data, len); + + wl1251_debug(DEBUG_TX, "tx id %u skb 0x%p payload %u rate 0x%x " + "queue %d", tx_hdr->id, skb, tx_hdr->length, + tx_hdr->rate, tx_hdr->xmit_queue); + + return 0; +} + +static void wl1251_tx_trigger(struct wl1251 *wl) +{ + u32 data, addr; + + if (wl->data_in_count & 0x1) { + addr = ACX_REG_INTERRUPT_TRIG_H; + data = INTR_TRIG_TX_PROC1; + } else { + addr = ACX_REG_INTERRUPT_TRIG; + data = INTR_TRIG_TX_PROC0; + } + + wl1251_reg_write32(wl, addr, data); + + /* Bumping data in */ + wl->data_in_count = (wl->data_in_count + 1) & + TX_STATUS_DATA_OUT_COUNT_MASK; +} + +/* caller must hold wl->mutex */ +static int wl1251_tx_frame(struct wl1251 *wl, struct sk_buff *skb) +{ + struct ieee80211_tx_info *info; + int ret = 0; + u8 idx; + + info = IEEE80211_SKB_CB(skb); + + if (info->control.hw_key) { + idx = info->control.hw_key->hw_key_idx; + if (unlikely(wl->default_key != idx)) { + ret = wl1251_acx_default_key(wl, idx); + if (ret < 0) + return ret; + } + } + + ret = wl1251_tx_path_status(wl); + if (ret < 0) + return ret; + + ret = wl1251_tx_fill_hdr(wl, skb, info); + if (ret < 0) + return ret; + + ret = wl1251_tx_send_packet(wl, skb, info); + if (ret < 0) + return ret; + + wl1251_tx_trigger(wl); + + return ret; +} + +void wl1251_tx_work(struct work_struct *work) +{ + struct wl1251 *wl = container_of(work, struct wl1251, tx_work); + struct sk_buff *skb; + bool woken_up = false; + int ret; + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state == WL1251_STATE_OFF)) + goto out; + + while ((skb = skb_dequeue(&wl->tx_queue))) { + if (!woken_up) { + ret = wl1251_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + woken_up = true; + } + + ret = wl1251_tx_frame(wl, skb); + if (ret == -EBUSY) { + skb_queue_head(&wl->tx_queue, skb); + goto out; + } else if (ret < 0) { + dev_kfree_skb(skb); + goto out; + } + } + +out: + if (woken_up) + wl1251_ps_elp_sleep(wl); + + mutex_unlock(&wl->mutex); +} + +static const char *wl1251_tx_parse_status(u8 status) +{ + /* 8 bit status field, one character per bit plus null */ + static char buf[9]; + int i = 0; + + memset(buf, 0, sizeof(buf)); + + if (status & TX_DMA_ERROR) + buf[i++] = 'm'; + if (status & TX_DISABLED) + buf[i++] = 'd'; + if (status & TX_RETRY_EXCEEDED) + buf[i++] = 'r'; + if (status & TX_TIMEOUT) + buf[i++] = 't'; + if (status & TX_KEY_NOT_FOUND) + buf[i++] = 'k'; + if (status & TX_ENCRYPT_FAIL) + buf[i++] = 'e'; + if (status & TX_UNAVAILABLE_PRIORITY) + buf[i++] = 'p'; + + /* bit 0 is unused apparently */ + + return buf; +} + +static void wl1251_tx_packet_cb(struct wl1251 *wl, + struct tx_result *result) +{ + struct ieee80211_tx_info *info; + struct sk_buff *skb; + int hdrlen; + u8 *frame; + + skb = wl->tx_frames[result->id]; + if (skb == NULL) { + wl1251_error("SKB for packet %d is NULL", result->id); + return; + } + + info = IEEE80211_SKB_CB(skb); + + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK) && + (result->status == TX_SUCCESS)) + info->flags |= IEEE80211_TX_STAT_ACK; + + info->status.rates[0].count = result->ack_failures + 1; + wl->stats.retry_count += result->ack_failures; + + /* + * We have to remove our private TX header before pushing + * the skb back to mac80211. + */ + frame = skb_pull(skb, sizeof(struct tx_double_buffer_desc)); + if (info->control.hw_key && + info->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) { + hdrlen = ieee80211_get_hdrlen_from_skb(skb); + memmove(frame + WL1251_TKIP_IV_SPACE, frame, hdrlen); + skb_pull(skb, WL1251_TKIP_IV_SPACE); + } + + wl1251_debug(DEBUG_TX, "tx status id %u skb 0x%p failures %u rate 0x%x" + " status 0x%x (%s)", + result->id, skb, result->ack_failures, result->rate, + result->status, wl1251_tx_parse_status(result->status)); + + + ieee80211_tx_status(wl->hw, skb); + + wl->tx_frames[result->id] = NULL; +} + +/* Called upon reception of a TX complete interrupt */ +void wl1251_tx_complete(struct wl1251 *wl) +{ + int i, result_index, num_complete = 0, queue_len; + struct tx_result result[FW_TX_CMPLT_BLOCK_SIZE], *result_ptr; + unsigned long flags; + + if (unlikely(wl->state != WL1251_STATE_ON)) + return; + + /* First we read the result */ + wl1251_mem_read(wl, wl->data_path->tx_complete_addr, + result, sizeof(result)); + + result_index = wl->next_tx_complete; + + for (i = 0; i < ARRAY_SIZE(result); i++) { + result_ptr = &result[result_index]; + + if (result_ptr->done_1 == 1 && + result_ptr->done_2 == 1) { + wl1251_tx_packet_cb(wl, result_ptr); + + result_ptr->done_1 = 0; + result_ptr->done_2 = 0; + + result_index = (result_index + 1) & + (FW_TX_CMPLT_BLOCK_SIZE - 1); + num_complete++; + } else { + break; + } + } + + queue_len = skb_queue_len(&wl->tx_queue); + + if ((num_complete > 0) && (queue_len > 0)) { + /* firmware buffer has space, reschedule tx_work */ + wl1251_debug(DEBUG_TX, "tx_complete: reschedule tx_work"); + ieee80211_queue_work(wl->hw, &wl->tx_work); + } + + if (wl->tx_queue_stopped && + queue_len <= WL1251_TX_QUEUE_LOW_WATERMARK) { + /* tx_queue has space, restart queues */ + wl1251_debug(DEBUG_TX, "tx_complete: waking queues"); + spin_lock_irqsave(&wl->wl_lock, flags); + ieee80211_wake_queues(wl->hw); + wl->tx_queue_stopped = false; + spin_unlock_irqrestore(&wl->wl_lock, flags); + } + + /* Every completed frame needs to be acknowledged */ + if (num_complete) { + /* + * If we've wrapped, we have to clear + * the results in 2 steps. + */ + if (result_index > wl->next_tx_complete) { + /* Only 1 write is needed */ + wl1251_mem_write(wl, + wl->data_path->tx_complete_addr + + (wl->next_tx_complete * + sizeof(struct tx_result)), + &result[wl->next_tx_complete], + num_complete * + sizeof(struct tx_result)); + + + } else if (result_index < wl->next_tx_complete) { + /* 2 writes are needed */ + wl1251_mem_write(wl, + wl->data_path->tx_complete_addr + + (wl->next_tx_complete * + sizeof(struct tx_result)), + &result[wl->next_tx_complete], + (FW_TX_CMPLT_BLOCK_SIZE - + wl->next_tx_complete) * + sizeof(struct tx_result)); + + wl1251_mem_write(wl, + wl->data_path->tx_complete_addr, + result, + (num_complete - + FW_TX_CMPLT_BLOCK_SIZE + + wl->next_tx_complete) * + sizeof(struct tx_result)); + + } else { + /* We have to write the whole array */ + wl1251_mem_write(wl, + wl->data_path->tx_complete_addr, + result, + FW_TX_CMPLT_BLOCK_SIZE * + sizeof(struct tx_result)); + } + + } + + wl->next_tx_complete = result_index; +} + +/* caller must hold wl->mutex */ +void wl1251_tx_flush(struct wl1251 *wl) +{ + int i; + struct sk_buff *skb; + struct ieee80211_tx_info *info; + + /* TX failure */ +/* control->flags = 0; FIXME */ + + while ((skb = skb_dequeue(&wl->tx_queue))) { + info = IEEE80211_SKB_CB(skb); + + wl1251_debug(DEBUG_TX, "flushing skb 0x%p", skb); + + if (!(info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS)) + continue; + + ieee80211_tx_status(wl->hw, skb); + } + + for (i = 0; i < FW_TX_CMPLT_BLOCK_SIZE; i++) + if (wl->tx_frames[i] != NULL) { + skb = wl->tx_frames[i]; + info = IEEE80211_SKB_CB(skb); + + if (!(info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS)) + continue; + + ieee80211_tx_status(wl->hw, skb); + wl->tx_frames[i] = NULL; + } +} diff --git a/drivers/net/wireless/ti/wl1251/tx.h b/drivers/net/wireless/ti/wl1251/tx.h new file mode 100644 index 0000000..81338d3 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/tx.h @@ -0,0 +1,231 @@ +/* + * This file is part of wl1251 + * + * Copyright (c) 1998-2007 Texas Instruments Incorporated + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL1251_TX_H__ +#define __WL1251_TX_H__ + +#include +#include "acx.h" + +/* + * + * TX PATH + * + * The Tx path uses a double buffer and a tx_control structure, each located + * at a fixed address in the device's memory. On startup, the host retrieves + * the pointers to these addresses. A double buffer allows for continuous data + * flow towards the device. The host keeps track of which buffer is available + * and alternates between these two buffers on a per packet basis. + * + * The size of each of the two buffers is large enough to hold the longest + * 802.3 packet - maximum size Ethernet packet + header + descriptor. + * TX complete indication will be received a-synchronously in a TX done cyclic + * buffer which is composed of 16 tx_result descriptors structures and is used + * in a cyclic manner. + * + * The TX (HOST) procedure is as follows: + * 1. Read the Tx path status, that will give the data_out_count. + * 2. goto 1, if not possible. + * i.e. if data_in_count - data_out_count >= HwBuffer size (2 for double + * buffer). + * 3. Copy the packet (preceded by double_buffer_desc), if possible. + * i.e. if data_in_count - data_out_count < HwBuffer size (2 for double + * buffer). + * 4. increment data_in_count. + * 5. Inform the firmware by generating a firmware internal interrupt. + * 6. FW will increment data_out_count after it reads the buffer. + * + * The TX Complete procedure: + * 1. To get a TX complete indication the host enables the tx_complete flag in + * the TX descriptor Structure. + * 2. For each packet with a Tx Complete field set, the firmware adds the + * transmit results to the cyclic buffer (txDoneRing) and sets both done_1 + * and done_2 to 1 to indicate driver ownership. + * 3. The firmware sends a Tx Complete interrupt to the host to trigger the + * host to process the new data. Note: interrupt will be send per packet if + * TX complete indication was requested in tx_control or per crossing + * aggregation threshold. + * 4. After receiving the Tx Complete interrupt, the host reads the + * TxDescriptorDone information in a cyclic manner and clears both done_1 + * and done_2 fields. + * + */ + +#define TX_COMPLETE_REQUIRED_BIT 0x80 +#define TX_STATUS_DATA_OUT_COUNT_MASK 0xf + +#define WL1251_TX_ALIGN_TO 4 +#define WL1251_TX_ALIGN(len) (((len) + WL1251_TX_ALIGN_TO - 1) & \ + ~(WL1251_TX_ALIGN_TO - 1)) +#define WL1251_TKIP_IV_SPACE 4 + +struct tx_control { + /* Rate Policy (class) index */ + unsigned rate_policy:3; + + /* When set, no ack policy is expected */ + unsigned ack_policy:1; + + /* + * Packet type: + * 0 -> 802.11 + * 1 -> 802.3 + * 2 -> IP + * 3 -> raw codec + */ + unsigned packet_type:2; + + /* If set, this is a QoS-Null or QoS-Data frame */ + unsigned qos:1; + + /* + * If set, the target triggers the tx complete INT + * upon frame sending completion. + */ + unsigned tx_complete:1; + + /* 2 bytes padding before packet header */ + unsigned xfer_pad:1; + + unsigned reserved:7; +} __packed; + + +struct tx_double_buffer_desc { + /* Length of payload, including headers. */ + __le16 length; + + /* + * A bit mask that specifies the initial rate to be used + * Possible values are: + * 0x0001 - 1Mbits + * 0x0002 - 2Mbits + * 0x0004 - 5.5Mbits + * 0x0008 - 6Mbits + * 0x0010 - 9Mbits + * 0x0020 - 11Mbits + * 0x0040 - 12Mbits + * 0x0080 - 18Mbits + * 0x0100 - 22Mbits + * 0x0200 - 24Mbits + * 0x0400 - 36Mbits + * 0x0800 - 48Mbits + * 0x1000 - 54Mbits + */ + __le16 rate; + + /* Time in us that a packet can spend in the target */ + __le32 expiry_time; + + /* index of the TX queue used for this packet */ + u8 xmit_queue; + + /* Used to identify a packet */ + u8 id; + + struct tx_control control; + + /* + * The FW should cut the packet into fragments + * of this size. + */ + __le16 frag_threshold; + + /* Numbers of HW queue blocks to be allocated */ + u8 num_mem_blocks; + + u8 reserved; +} __packed; + +enum { + TX_SUCCESS = 0, + TX_DMA_ERROR = BIT(7), + TX_DISABLED = BIT(6), + TX_RETRY_EXCEEDED = BIT(5), + TX_TIMEOUT = BIT(4), + TX_KEY_NOT_FOUND = BIT(3), + TX_ENCRYPT_FAIL = BIT(2), + TX_UNAVAILABLE_PRIORITY = BIT(1), +}; + +struct tx_result { + /* + * Ownership synchronization between the host and + * the firmware. If done_1 and done_2 are cleared, + * owned by the FW (no info ready). + */ + u8 done_1; + + /* same as double_buffer_desc->id */ + u8 id; + + /* + * Total air access duration consumed by this + * packet, including all retries and overheads. + */ + u16 medium_usage; + + /* Total media delay (from 1st EDCA AIFS counter until TX Complete). */ + u32 medium_delay; + + /* Time between host xfer and tx complete */ + u32 fw_hnadling_time; + + /* The LS-byte of the last TKIP sequence number. */ + u8 lsb_seq_num; + + /* Retry count */ + u8 ack_failures; + + /* At which rate we got a ACK */ + u16 rate; + + u16 reserved; + + /* TX_* */ + u8 status; + + /* See done_1 */ + u8 done_2; +} __packed; + +static inline int wl1251_tx_get_queue(int queue) +{ + switch (queue) { + case 0: + return QOS_AC_VO; + case 1: + return QOS_AC_VI; + case 2: + return QOS_AC_BE; + case 3: + return QOS_AC_BK; + default: + return QOS_AC_BE; + } +} + +void wl1251_tx_work(struct work_struct *work); +void wl1251_tx_complete(struct wl1251 *wl); +void wl1251_tx_flush(struct wl1251 *wl); + +#endif diff --git a/drivers/net/wireless/ti/wl1251/wl1251.h b/drivers/net/wireless/ti/wl1251/wl1251.h new file mode 100644 index 0000000..fd02060 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/wl1251.h @@ -0,0 +1,445 @@ +/* + * This file is part of wl1251 + * + * Copyright (c) 1998-2007 Texas Instruments Incorporated + * Copyright (C) 2008-2009 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL1251_H__ +#define __WL1251_H__ + +#include +#include +#include +#include + +#define DRIVER_NAME "wl1251" +#define DRIVER_PREFIX DRIVER_NAME ": " + +enum { + DEBUG_NONE = 0, + DEBUG_IRQ = BIT(0), + DEBUG_SPI = BIT(1), + DEBUG_BOOT = BIT(2), + DEBUG_MAILBOX = BIT(3), + DEBUG_NETLINK = BIT(4), + DEBUG_EVENT = BIT(5), + DEBUG_TX = BIT(6), + DEBUG_RX = BIT(7), + DEBUG_SCAN = BIT(8), + DEBUG_CRYPT = BIT(9), + DEBUG_PSM = BIT(10), + DEBUG_MAC80211 = BIT(11), + DEBUG_CMD = BIT(12), + DEBUG_ACX = BIT(13), + DEBUG_ALL = ~0, +}; + +#define DEBUG_LEVEL (DEBUG_NONE) + +#define DEBUG_DUMP_LIMIT 1024 + +#define wl1251_error(fmt, arg...) \ + printk(KERN_ERR DRIVER_PREFIX "ERROR " fmt "\n", ##arg) + +#define wl1251_warning(fmt, arg...) \ + printk(KERN_WARNING DRIVER_PREFIX "WARNING " fmt "\n", ##arg) + +#define wl1251_notice(fmt, arg...) \ + printk(KERN_INFO DRIVER_PREFIX fmt "\n", ##arg) + +#define wl1251_info(fmt, arg...) \ + printk(KERN_DEBUG DRIVER_PREFIX fmt "\n", ##arg) + +#define wl1251_debug(level, fmt, arg...) \ + do { \ + if (level & DEBUG_LEVEL) \ + printk(KERN_DEBUG DRIVER_PREFIX fmt "\n", ##arg); \ + } while (0) + +#define wl1251_dump(level, prefix, buf, len) \ + do { \ + if (level & DEBUG_LEVEL) \ + print_hex_dump(KERN_DEBUG, DRIVER_PREFIX prefix, \ + DUMP_PREFIX_OFFSET, 16, 1, \ + buf, \ + min_t(size_t, len, DEBUG_DUMP_LIMIT), \ + 0); \ + } while (0) + +#define wl1251_dump_ascii(level, prefix, buf, len) \ + do { \ + if (level & DEBUG_LEVEL) \ + print_hex_dump(KERN_DEBUG, DRIVER_PREFIX prefix, \ + DUMP_PREFIX_OFFSET, 16, 1, \ + buf, \ + min_t(size_t, len, DEBUG_DUMP_LIMIT), \ + true); \ + } while (0) + +#define WL1251_DEFAULT_RX_CONFIG (CFG_UNI_FILTER_EN | \ + CFG_BSSID_FILTER_EN) + +#define WL1251_DEFAULT_RX_FILTER (CFG_RX_PRSP_EN | \ + CFG_RX_MGMT_EN | \ + CFG_RX_DATA_EN | \ + CFG_RX_CTL_EN | \ + CFG_RX_BCN_EN | \ + CFG_RX_AUTH_EN | \ + CFG_RX_ASSOC_EN) + +#define WL1251_BUSY_WORD_LEN 8 + +struct boot_attr { + u32 radio_type; + u8 mac_clock; + u8 arm_clock; + int firmware_debug; + u32 minor; + u32 major; + u32 bugfix; +}; + +enum wl1251_state { + WL1251_STATE_OFF, + WL1251_STATE_ON, + WL1251_STATE_PLT, +}; + +enum wl1251_partition_type { + PART_DOWN, + PART_WORK, + PART_DRPW, + + PART_TABLE_LEN +}; + +enum wl1251_station_mode { + STATION_ACTIVE_MODE, + STATION_POWER_SAVE_MODE, + STATION_IDLE, +}; + +struct wl1251_partition { + u32 size; + u32 start; +}; + +struct wl1251_partition_set { + struct wl1251_partition mem; + struct wl1251_partition reg; +}; + +struct wl1251; + +struct wl1251_stats { + struct acx_statistics *fw_stats; + unsigned long fw_stats_update; + + unsigned int retry_count; + unsigned int excessive_retries; +}; + +struct wl1251_debugfs { + struct dentry *rootdir; + struct dentry *fw_statistics; + + struct dentry *tx_internal_desc_overflow; + + struct dentry *rx_out_of_mem; + struct dentry *rx_hdr_overflow; + struct dentry *rx_hw_stuck; + struct dentry *rx_dropped; + struct dentry *rx_fcs_err; + struct dentry *rx_xfr_hint_trig; + struct dentry *rx_path_reset; + struct dentry *rx_reset_counter; + + struct dentry *dma_rx_requested; + struct dentry *dma_rx_errors; + struct dentry *dma_tx_requested; + struct dentry *dma_tx_errors; + + struct dentry *isr_cmd_cmplt; + struct dentry *isr_fiqs; + struct dentry *isr_rx_headers; + struct dentry *isr_rx_mem_overflow; + struct dentry *isr_rx_rdys; + struct dentry *isr_irqs; + struct dentry *isr_tx_procs; + struct dentry *isr_decrypt_done; + struct dentry *isr_dma0_done; + struct dentry *isr_dma1_done; + struct dentry *isr_tx_exch_complete; + struct dentry *isr_commands; + struct dentry *isr_rx_procs; + struct dentry *isr_hw_pm_mode_changes; + struct dentry *isr_host_acknowledges; + struct dentry *isr_pci_pm; + struct dentry *isr_wakeups; + struct dentry *isr_low_rssi; + + struct dentry *wep_addr_key_count; + struct dentry *wep_default_key_count; + /* skipping wep.reserved */ + struct dentry *wep_key_not_found; + struct dentry *wep_decrypt_fail; + struct dentry *wep_packets; + struct dentry *wep_interrupt; + + struct dentry *pwr_ps_enter; + struct dentry *pwr_elp_enter; + struct dentry *pwr_missing_bcns; + struct dentry *pwr_wake_on_host; + struct dentry *pwr_wake_on_timer_exp; + struct dentry *pwr_tx_with_ps; + struct dentry *pwr_tx_without_ps; + struct dentry *pwr_rcvd_beacons; + struct dentry *pwr_power_save_off; + struct dentry *pwr_enable_ps; + struct dentry *pwr_disable_ps; + struct dentry *pwr_fix_tsf_ps; + /* skipping cont_miss_bcns_spread for now */ + struct dentry *pwr_rcvd_awake_beacons; + + struct dentry *mic_rx_pkts; + struct dentry *mic_calc_failure; + + struct dentry *aes_encrypt_fail; + struct dentry *aes_decrypt_fail; + struct dentry *aes_encrypt_packets; + struct dentry *aes_decrypt_packets; + struct dentry *aes_encrypt_interrupt; + struct dentry *aes_decrypt_interrupt; + + struct dentry *event_heart_beat; + struct dentry *event_calibration; + struct dentry *event_rx_mismatch; + struct dentry *event_rx_mem_empty; + struct dentry *event_rx_pool; + struct dentry *event_oom_late; + struct dentry *event_phy_transmit_error; + struct dentry *event_tx_stuck; + + struct dentry *ps_pspoll_timeouts; + struct dentry *ps_upsd_timeouts; + struct dentry *ps_upsd_max_sptime; + struct dentry *ps_upsd_max_apturn; + struct dentry *ps_pspoll_max_apturn; + struct dentry *ps_pspoll_utilization; + struct dentry *ps_upsd_utilization; + + struct dentry *rxpipe_rx_prep_beacon_drop; + struct dentry *rxpipe_descr_host_int_trig_rx_data; + struct dentry *rxpipe_beacon_buffer_thres_host_int_trig_rx_data; + struct dentry *rxpipe_missed_beacon_host_int_trig_rx_data; + struct dentry *rxpipe_tx_xfr_host_int_trig_rx_data; + + struct dentry *tx_queue_len; + struct dentry *tx_queue_status; + + struct dentry *retry_count; + struct dentry *excessive_retries; +}; + +struct wl1251_if_operations { + void (*read)(struct wl1251 *wl, int addr, void *buf, size_t len); + void (*write)(struct wl1251 *wl, int addr, void *buf, size_t len); + void (*read_elp)(struct wl1251 *wl, int addr, u32 *val); + void (*write_elp)(struct wl1251 *wl, int addr, u32 val); + int (*power)(struct wl1251 *wl, bool enable); + void (*reset)(struct wl1251 *wl); + void (*enable_irq)(struct wl1251 *wl); + void (*disable_irq)(struct wl1251 *wl); +}; + +struct wl1251 { + struct ieee80211_hw *hw; + bool mac80211_registered; + + void *if_priv; + const struct wl1251_if_operations *if_ops; + + void (*set_power)(bool enable); + int irq; + bool use_eeprom; + + spinlock_t wl_lock; + + enum wl1251_state state; + struct mutex mutex; + + int physical_mem_addr; + int physical_reg_addr; + int virtual_mem_addr; + int virtual_reg_addr; + + int cmd_box_addr; + int event_box_addr; + struct boot_attr boot_attr; + + u8 *fw; + size_t fw_len; + u8 *nvs; + size_t nvs_len; + + u8 bssid[ETH_ALEN]; + u8 mac_addr[ETH_ALEN]; + u8 bss_type; + u8 listen_int; + int channel; + + void *target_mem_map; + struct acx_data_path_params_resp *data_path; + + /* Number of TX packets transferred to the FW, modulo 16 */ + u32 data_in_count; + + /* Frames scheduled for transmission, not handled yet */ + struct sk_buff_head tx_queue; + bool tx_queue_stopped; + + struct work_struct tx_work; + + /* Pending TX frames */ + struct sk_buff *tx_frames[16]; + + /* + * Index pointing to the next TX complete entry + * in the cyclic XT complete array we get from + * the FW. + */ + u32 next_tx_complete; + + /* FW Rx counter */ + u32 rx_counter; + + /* Rx frames handled */ + u32 rx_handled; + + /* Current double buffer */ + u32 rx_current_buffer; + u32 rx_last_id; + + /* The target interrupt mask */ + u32 intr_mask; + struct work_struct irq_work; + + /* The mbox event mask */ + u32 event_mask; + + /* Mailbox pointers */ + u32 mbox_ptr[2]; + + /* Are we currently scanning */ + bool scanning; + + /* Default key (for WEP) */ + u32 default_key; + + unsigned int tx_mgmt_frm_rate; + unsigned int tx_mgmt_frm_mod; + + unsigned int rx_config; + unsigned int rx_filter; + + /* is firmware in elp mode */ + bool elp; + + struct delayed_work elp_work; + + enum wl1251_station_mode station_mode; + + /* PSM mode requested */ + bool psm_requested; + + u16 beacon_int; + u8 dtim_period; + + /* in dBm */ + int power_level; + + int rssi_thold; + + struct wl1251_stats stats; + struct wl1251_debugfs debugfs; + + __le32 buffer_32; + u32 buffer_cmd; + u8 buffer_busyword[WL1251_BUSY_WORD_LEN]; + struct wl1251_rx_descriptor *rx_descriptor; + + struct ieee80211_vif *vif; + + u32 chip_id; + char fw_ver[21]; + + /* Most recently reported noise in dBm */ + s8 noise; +}; + +int wl1251_plt_start(struct wl1251 *wl); +int wl1251_plt_stop(struct wl1251 *wl); + +struct ieee80211_hw *wl1251_alloc_hw(void); +int wl1251_free_hw(struct wl1251 *wl); +int wl1251_init_ieee80211(struct wl1251 *wl); +void wl1251_enable_interrupts(struct wl1251 *wl); +void wl1251_disable_interrupts(struct wl1251 *wl); + +#define DEFAULT_HW_GEN_MODULATION_TYPE CCK_LONG /* Long Preamble */ +#define DEFAULT_HW_GEN_TX_RATE RATE_2MBPS +#define JOIN_TIMEOUT 5000 /* 5000 milliseconds to join */ + +#define WL1251_DEFAULT_POWER_LEVEL 20 + +#define WL1251_TX_QUEUE_LOW_WATERMARK 10 +#define WL1251_TX_QUEUE_HIGH_WATERMARK 25 + +#define WL1251_DEFAULT_BEACON_INT 100 +#define WL1251_DEFAULT_DTIM_PERIOD 1 + +#define WL1251_DEFAULT_CHANNEL 0 + +#define WL1251_DEFAULT_BET_CONSECUTIVE 10 + +#define CHIP_ID_1251_PG10 (0x7010101) +#define CHIP_ID_1251_PG11 (0x7020101) +#define CHIP_ID_1251_PG12 (0x7030101) +#define CHIP_ID_1271_PG10 (0x4030101) +#define CHIP_ID_1271_PG20 (0x4030111) + +#define WL1251_FW_NAME "wl1251-fw.bin" +#define WL1251_NVS_NAME "wl1251-nvs.bin" + +#define WL1251_POWER_ON_SLEEP 10 /* in milliseconds */ + +#define WL1251_PART_DOWN_MEM_START 0x0 +#define WL1251_PART_DOWN_MEM_SIZE 0x16800 +#define WL1251_PART_DOWN_REG_START REGISTERS_BASE +#define WL1251_PART_DOWN_REG_SIZE REGISTERS_DOWN_SIZE + +#define WL1251_PART_WORK_MEM_START 0x28000 +#define WL1251_PART_WORK_MEM_SIZE 0x14000 +#define WL1251_PART_WORK_REG_START REGISTERS_BASE +#define WL1251_PART_WORK_REG_SIZE REGISTERS_WORK_SIZE + +#define WL1251_DEFAULT_LOW_RSSI_WEIGHT 10 +#define WL1251_DEFAULT_LOW_RSSI_DEPTH 10 + +#endif diff --git a/drivers/net/wireless/ti/wl1251/wl12xx_80211.h b/drivers/net/wireless/ti/wl1251/wl12xx_80211.h new file mode 100644 index 0000000..04ed514 --- /dev/null +++ b/drivers/net/wireless/ti/wl1251/wl12xx_80211.h @@ -0,0 +1,155 @@ +#ifndef __WL12XX_80211_H__ +#define __WL12XX_80211_H__ + +#include /* ETH_ALEN */ + +/* RATES */ +#define IEEE80211_CCK_RATE_1MB 0x02 +#define IEEE80211_CCK_RATE_2MB 0x04 +#define IEEE80211_CCK_RATE_5MB 0x0B +#define IEEE80211_CCK_RATE_11MB 0x16 +#define IEEE80211_OFDM_RATE_6MB 0x0C +#define IEEE80211_OFDM_RATE_9MB 0x12 +#define IEEE80211_OFDM_RATE_12MB 0x18 +#define IEEE80211_OFDM_RATE_18MB 0x24 +#define IEEE80211_OFDM_RATE_24MB 0x30 +#define IEEE80211_OFDM_RATE_36MB 0x48 +#define IEEE80211_OFDM_RATE_48MB 0x60 +#define IEEE80211_OFDM_RATE_54MB 0x6C +#define IEEE80211_BASIC_RATE_MASK 0x80 + +#define IEEE80211_CCK_RATE_1MB_MASK (1<<0) +#define IEEE80211_CCK_RATE_2MB_MASK (1<<1) +#define IEEE80211_CCK_RATE_5MB_MASK (1<<2) +#define IEEE80211_CCK_RATE_11MB_MASK (1<<3) +#define IEEE80211_OFDM_RATE_6MB_MASK (1<<4) +#define IEEE80211_OFDM_RATE_9MB_MASK (1<<5) +#define IEEE80211_OFDM_RATE_12MB_MASK (1<<6) +#define IEEE80211_OFDM_RATE_18MB_MASK (1<<7) +#define IEEE80211_OFDM_RATE_24MB_MASK (1<<8) +#define IEEE80211_OFDM_RATE_36MB_MASK (1<<9) +#define IEEE80211_OFDM_RATE_48MB_MASK (1<<10) +#define IEEE80211_OFDM_RATE_54MB_MASK (1<<11) + +#define IEEE80211_CCK_RATES_MASK 0x0000000F +#define IEEE80211_CCK_BASIC_RATES_MASK (IEEE80211_CCK_RATE_1MB_MASK | \ + IEEE80211_CCK_RATE_2MB_MASK) +#define IEEE80211_CCK_DEFAULT_RATES_MASK (IEEE80211_CCK_BASIC_RATES_MASK | \ + IEEE80211_CCK_RATE_5MB_MASK | \ + IEEE80211_CCK_RATE_11MB_MASK) + +#define IEEE80211_OFDM_RATES_MASK 0x00000FF0 +#define IEEE80211_OFDM_BASIC_RATES_MASK (IEEE80211_OFDM_RATE_6MB_MASK | \ + IEEE80211_OFDM_RATE_12MB_MASK | \ + IEEE80211_OFDM_RATE_24MB_MASK) +#define IEEE80211_OFDM_DEFAULT_RATES_MASK (IEEE80211_OFDM_BASIC_RATES_MASK | \ + IEEE80211_OFDM_RATE_9MB_MASK | \ + IEEE80211_OFDM_RATE_18MB_MASK | \ + IEEE80211_OFDM_RATE_36MB_MASK | \ + IEEE80211_OFDM_RATE_48MB_MASK | \ + IEEE80211_OFDM_RATE_54MB_MASK) +#define IEEE80211_DEFAULT_RATES_MASK (IEEE80211_OFDM_DEFAULT_RATES_MASK | \ + IEEE80211_CCK_DEFAULT_RATES_MASK) + + +/* This really should be 8, but not for our firmware */ +#define MAX_SUPPORTED_RATES 32 +#define MAX_COUNTRY_TRIPLETS 32 + +/* Headers */ +struct ieee80211_header { + __le16 frame_ctl; + __le16 duration_id; + u8 da[ETH_ALEN]; + u8 sa[ETH_ALEN]; + u8 bssid[ETH_ALEN]; + __le16 seq_ctl; + u8 payload[0]; +} __packed; + +struct wl12xx_ie_header { + u8 id; + u8 len; +} __packed; + +/* IEs */ + +struct wl12xx_ie_ssid { + struct wl12xx_ie_header header; + char ssid[IEEE80211_MAX_SSID_LEN]; +} __packed; + +struct wl12xx_ie_rates { + struct wl12xx_ie_header header; + u8 rates[MAX_SUPPORTED_RATES]; +} __packed; + +struct wl12xx_ie_ds_params { + struct wl12xx_ie_header header; + u8 channel; +} __packed; + +struct country_triplet { + u8 channel; + u8 num_channels; + u8 max_tx_power; +} __packed; + +struct wl12xx_ie_country { + struct wl12xx_ie_header header; + u8 country_string[IEEE80211_COUNTRY_STRING_LEN]; + struct country_triplet triplets[MAX_COUNTRY_TRIPLETS]; +} __packed; + + +/* Templates */ + +struct wl12xx_beacon_template { + struct ieee80211_header header; + __le32 time_stamp[2]; + __le16 beacon_interval; + __le16 capability; + struct wl12xx_ie_ssid ssid; + struct wl12xx_ie_rates rates; + struct wl12xx_ie_rates ext_rates; + struct wl12xx_ie_ds_params ds_params; + struct wl12xx_ie_country country; +} __packed; + +struct wl12xx_null_data_template { + struct ieee80211_header header; +} __packed; + +struct wl12xx_ps_poll_template { + __le16 fc; + __le16 aid; + u8 bssid[ETH_ALEN]; + u8 ta[ETH_ALEN]; +} __packed; + +struct wl12xx_qos_null_data_template { + struct ieee80211_header header; + __le16 qos_ctl; +} __packed; + +struct wl12xx_probe_req_template { + struct ieee80211_header header; + struct wl12xx_ie_ssid ssid; + struct wl12xx_ie_rates rates; + struct wl12xx_ie_rates ext_rates; +} __packed; + + +struct wl12xx_probe_resp_template { + struct ieee80211_header header; + __le32 time_stamp[2]; + __le16 beacon_interval; + __le16 capability; + struct wl12xx_ie_ssid ssid; + struct wl12xx_ie_rates rates; + struct wl12xx_ie_rates ext_rates; + struct wl12xx_ie_ds_params ds_params; + struct wl12xx_ie_country country; +} __packed; + +#endif diff --git a/drivers/net/wireless/ti/wl12xx/Kconfig b/drivers/net/wireless/ti/wl12xx/Kconfig new file mode 100644 index 0000000..16ceb6e --- /dev/null +++ b/drivers/net/wireless/ti/wl12xx/Kconfig @@ -0,0 +1,10 @@ +config WL12XX + tristate "TI wl12xx support" + depends on m + depends on MAC80211 + select WLCORE + ---help--- + This module adds support for wireless adapters based on TI wl1271, + wl1273, wl1281 and wl1283 chipsets. This module does *not* include + support for wl1251. For wl1251 support, use the separate homonymous + driver instead. diff --git a/drivers/net/wireless/ti/wl12xx/Makefile b/drivers/net/wireless/ti/wl12xx/Makefile new file mode 100644 index 0000000..8b231e1 --- /dev/null +++ b/drivers/net/wireless/ti/wl12xx/Makefile @@ -0,0 +1,3 @@ +wl12xx-objs = main.o cmd.o acx.o debugfs.o scan.o event.o + +obj-$(CPTCFG_WL12XX) += wl12xx.o diff --git a/drivers/net/wireless/ti/wl12xx/acx.c b/drivers/net/wireless/ti/wl12xx/acx.c new file mode 100644 index 0000000..bea06b2 --- /dev/null +++ b/drivers/net/wireless/ti/wl12xx/acx.c @@ -0,0 +1,53 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 2008-2009 Nokia Corporation + * Copyright (C) 2011 Texas Instruments Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "../wlcore/cmd.h" +#include "../wlcore/debug.h" +#include "../wlcore/acx.h" + +#include "acx.h" + +int wl1271_acx_host_if_cfg_bitmap(struct wl1271 *wl, u32 host_cfg_bitmap) +{ + struct wl1271_acx_host_config_bitmap *bitmap_conf; + int ret; + + bitmap_conf = kzalloc(sizeof(*bitmap_conf), GFP_KERNEL); + if (!bitmap_conf) { + ret = -ENOMEM; + goto out; + } + + bitmap_conf->host_cfg_bitmap = cpu_to_le32(host_cfg_bitmap); + + ret = wl1271_cmd_configure(wl, ACX_HOST_IF_CFG_BITMAP, + bitmap_conf, sizeof(*bitmap_conf)); + if (ret < 0) { + wl1271_warning("wl1271 bitmap config opt failed: %d", ret); + goto out; + } + +out: + kfree(bitmap_conf); + + return ret; +} diff --git a/drivers/net/wireless/ti/wl12xx/acx.h b/drivers/net/wireless/ti/wl12xx/acx.h new file mode 100644 index 0000000..2a26868 --- /dev/null +++ b/drivers/net/wireless/ti/wl12xx/acx.h @@ -0,0 +1,273 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 1998-2009, 2011 Texas Instruments. All rights reserved. + * Copyright (C) 2008-2010 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL12XX_ACX_H__ +#define __WL12XX_ACX_H__ + +#include "../wlcore/wlcore.h" +#include "../wlcore/acx.h" + +#define WL12XX_ACX_ALL_EVENTS_VECTOR (WL1271_ACX_INTR_WATCHDOG | \ + WL1271_ACX_INTR_INIT_COMPLETE | \ + WL1271_ACX_INTR_EVENT_A | \ + WL1271_ACX_INTR_EVENT_B | \ + WL1271_ACX_INTR_CMD_COMPLETE | \ + WL1271_ACX_INTR_HW_AVAILABLE | \ + WL1271_ACX_INTR_DATA) + +#define WL12XX_INTR_MASK (WL1271_ACX_INTR_WATCHDOG | \ + WL1271_ACX_INTR_EVENT_A | \ + WL1271_ACX_INTR_EVENT_B | \ + WL1271_ACX_INTR_HW_AVAILABLE | \ + WL1271_ACX_INTR_DATA) + +struct wl1271_acx_host_config_bitmap { + struct acx_header header; + + __le32 host_cfg_bitmap; +} __packed; + +struct wl12xx_acx_tx_statistics { + __le32 internal_desc_overflow; +} __packed; + +struct wl12xx_acx_rx_statistics { + __le32 out_of_mem; + __le32 hdr_overflow; + __le32 hw_stuck; + __le32 dropped; + __le32 fcs_err; + __le32 xfr_hint_trig; + __le32 path_reset; + __le32 reset_counter; +} __packed; + +struct wl12xx_acx_dma_statistics { + __le32 rx_requested; + __le32 rx_errors; + __le32 tx_requested; + __le32 tx_errors; +} __packed; + +struct wl12xx_acx_isr_statistics { + /* host command complete */ + __le32 cmd_cmplt; + + /* fiqisr() */ + __le32 fiqs; + + /* (INT_STS_ND & INT_TRIG_RX_HEADER) */ + __le32 rx_headers; + + /* (INT_STS_ND & INT_TRIG_RX_CMPLT) */ + __le32 rx_completes; + + /* (INT_STS_ND & INT_TRIG_NO_RX_BUF) */ + __le32 rx_mem_overflow; + + /* (INT_STS_ND & INT_TRIG_S_RX_RDY) */ + __le32 rx_rdys; + + /* irqisr() */ + __le32 irqs; + + /* (INT_STS_ND & INT_TRIG_TX_PROC) */ + __le32 tx_procs; + + /* (INT_STS_ND & INT_TRIG_DECRYPT_DONE) */ + __le32 decrypt_done; + + /* (INT_STS_ND & INT_TRIG_DMA0) */ + __le32 dma0_done; + + /* (INT_STS_ND & INT_TRIG_DMA1) */ + __le32 dma1_done; + + /* (INT_STS_ND & INT_TRIG_TX_EXC_CMPLT) */ + __le32 tx_exch_complete; + + /* (INT_STS_ND & INT_TRIG_COMMAND) */ + __le32 commands; + + /* (INT_STS_ND & INT_TRIG_RX_PROC) */ + __le32 rx_procs; + + /* (INT_STS_ND & INT_TRIG_PM_802) */ + __le32 hw_pm_mode_changes; + + /* (INT_STS_ND & INT_TRIG_ACKNOWLEDGE) */ + __le32 host_acknowledges; + + /* (INT_STS_ND & INT_TRIG_PM_PCI) */ + __le32 pci_pm; + + /* (INT_STS_ND & INT_TRIG_ACM_WAKEUP) */ + __le32 wakeups; + + /* (INT_STS_ND & INT_TRIG_LOW_RSSI) */ + __le32 low_rssi; +} __packed; + +struct wl12xx_acx_wep_statistics { + /* WEP address keys configured */ + __le32 addr_key_count; + + /* default keys configured */ + __le32 default_key_count; + + __le32 reserved; + + /* number of times that WEP key not found on lookup */ + __le32 key_not_found; + + /* number of times that WEP key decryption failed */ + __le32 decrypt_fail; + + /* WEP packets decrypted */ + __le32 packets; + + /* WEP decrypt interrupts */ + __le32 interrupt; +} __packed; + +#define ACX_MISSED_BEACONS_SPREAD 10 + +struct wl12xx_acx_pwr_statistics { + /* the amount of enters into power save mode (both PD & ELP) */ + __le32 ps_enter; + + /* the amount of enters into ELP mode */ + __le32 elp_enter; + + /* the amount of missing beacon interrupts to the host */ + __le32 missing_bcns; + + /* the amount of wake on host-access times */ + __le32 wake_on_host; + + /* the amount of wake on timer-expire */ + __le32 wake_on_timer_exp; + + /* the number of packets that were transmitted with PS bit set */ + __le32 tx_with_ps; + + /* the number of packets that were transmitted with PS bit clear */ + __le32 tx_without_ps; + + /* the number of received beacons */ + __le32 rcvd_beacons; + + /* the number of entering into PowerOn (power save off) */ + __le32 power_save_off; + + /* the number of entries into power save mode */ + __le16 enable_ps; + + /* + * the number of exits from power save, not including failed PS + * transitions + */ + __le16 disable_ps; + + /* + * the number of times the TSF counter was adjusted because + * of drift + */ + __le32 fix_tsf_ps; + + /* Gives statistics about the spread continuous missed beacons. + * The 16 LSB are dedicated for the PS mode. + * The 16 MSB are dedicated for the PS mode. + * cont_miss_bcns_spread[0] - single missed beacon. + * cont_miss_bcns_spread[1] - two continuous missed beacons. + * cont_miss_bcns_spread[2] - three continuous missed beacons. + * ... + * cont_miss_bcns_spread[9] - ten and more continuous missed beacons. + */ + __le32 cont_miss_bcns_spread[ACX_MISSED_BEACONS_SPREAD]; + + /* the number of beacons in awake mode */ + __le32 rcvd_awake_beacons; +} __packed; + +struct wl12xx_acx_mic_statistics { + __le32 rx_pkts; + __le32 calc_failure; +} __packed; + +struct wl12xx_acx_aes_statistics { + __le32 encrypt_fail; + __le32 decrypt_fail; + __le32 encrypt_packets; + __le32 decrypt_packets; + __le32 encrypt_interrupt; + __le32 decrypt_interrupt; +} __packed; + +struct wl12xx_acx_event_statistics { + __le32 heart_beat; + __le32 calibration; + __le32 rx_mismatch; + __le32 rx_mem_empty; + __le32 rx_pool; + __le32 oom_late; + __le32 phy_transmit_error; + __le32 tx_stuck; +} __packed; + +struct wl12xx_acx_ps_statistics { + __le32 pspoll_timeouts; + __le32 upsd_timeouts; + __le32 upsd_max_sptime; + __le32 upsd_max_apturn; + __le32 pspoll_max_apturn; + __le32 pspoll_utilization; + __le32 upsd_utilization; +} __packed; + +struct wl12xx_acx_rxpipe_statistics { + __le32 rx_prep_beacon_drop; + __le32 descr_host_int_trig_rx_data; + __le32 beacon_buffer_thres_host_int_trig_rx_data; + __le32 missed_beacon_host_int_trig_rx_data; + __le32 tx_xfr_host_int_trig_rx_data; +} __packed; + +struct wl12xx_acx_statistics { + struct acx_header header; + + struct wl12xx_acx_tx_statistics tx; + struct wl12xx_acx_rx_statistics rx; + struct wl12xx_acx_dma_statistics dma; + struct wl12xx_acx_isr_statistics isr; + struct wl12xx_acx_wep_statistics wep; + struct wl12xx_acx_pwr_statistics pwr; + struct wl12xx_acx_aes_statistics aes; + struct wl12xx_acx_mic_statistics mic; + struct wl12xx_acx_event_statistics event; + struct wl12xx_acx_ps_statistics ps; + struct wl12xx_acx_rxpipe_statistics rxpipe; +} __packed; + +int wl1271_acx_host_if_cfg_bitmap(struct wl1271 *wl, u32 host_cfg_bitmap); + +#endif /* __WL12XX_ACX_H__ */ diff --git a/drivers/net/wireless/ti/wl12xx/cmd.c b/drivers/net/wireless/ti/wl12xx/cmd.c new file mode 100644 index 0000000..7485dba --- /dev/null +++ b/drivers/net/wireless/ti/wl12xx/cmd.c @@ -0,0 +1,323 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 2009-2010 Nokia Corporation + * Copyright (C) 2011 Texas Instruments Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "../wlcore/cmd.h" +#include "../wlcore/debug.h" + +#include "wl12xx.h" +#include "cmd.h" + +int wl1271_cmd_ext_radio_parms(struct wl1271 *wl) +{ + struct wl1271_ext_radio_parms_cmd *ext_radio_parms; + struct wl12xx_priv *priv = wl->priv; + struct wl12xx_conf_rf *rf = &priv->conf.rf; + int ret; + + if (!wl->nvs) + return -ENODEV; + + ext_radio_parms = kzalloc(sizeof(*ext_radio_parms), GFP_KERNEL); + if (!ext_radio_parms) + return -ENOMEM; + + ext_radio_parms->test.id = TEST_CMD_INI_FILE_RF_EXTENDED_PARAM; + + memcpy(ext_radio_parms->tx_per_channel_power_compensation_2, + rf->tx_per_channel_power_compensation_2, + CONF_TX_PWR_COMPENSATION_LEN_2); + memcpy(ext_radio_parms->tx_per_channel_power_compensation_5, + rf->tx_per_channel_power_compensation_5, + CONF_TX_PWR_COMPENSATION_LEN_5); + + wl1271_dump(DEBUG_CMD, "TEST_CMD_INI_FILE_EXT_RADIO_PARAM: ", + ext_radio_parms, sizeof(*ext_radio_parms)); + + ret = wl1271_cmd_test(wl, ext_radio_parms, sizeof(*ext_radio_parms), 0); + if (ret < 0) + wl1271_warning("TEST_CMD_INI_FILE_RF_EXTENDED_PARAM failed"); + + kfree(ext_radio_parms); + return ret; +} + +int wl1271_cmd_general_parms(struct wl1271 *wl) +{ + struct wl1271_general_parms_cmd *gen_parms; + struct wl1271_ini_general_params *gp = + &((struct wl1271_nvs_file *)wl->nvs)->general_params; + struct wl12xx_priv *priv = wl->priv; + bool answer = false; + int ret; + + if (!wl->nvs) + return -ENODEV; + + if (gp->tx_bip_fem_manufacturer >= WL1271_INI_FEM_MODULE_COUNT) { + wl1271_warning("FEM index from INI out of bounds"); + return -EINVAL; + } + + gen_parms = kzalloc(sizeof(*gen_parms), GFP_KERNEL); + if (!gen_parms) + return -ENOMEM; + + gen_parms->test.id = TEST_CMD_INI_FILE_GENERAL_PARAM; + + memcpy(&gen_parms->general_params, gp, sizeof(*gp)); + + /* If we started in PLT FEM_DETECT mode, force auto detect */ + if (wl->plt_mode == PLT_FEM_DETECT) + gen_parms->general_params.tx_bip_fem_auto_detect = true; + + if (gen_parms->general_params.tx_bip_fem_auto_detect) + answer = true; + + /* Override the REF CLK from the NVS with the one from platform data */ + gen_parms->general_params.ref_clock = priv->ref_clock; + + ret = wl1271_cmd_test(wl, gen_parms, sizeof(*gen_parms), answer); + if (ret < 0) { + wl1271_warning("CMD_INI_FILE_GENERAL_PARAM failed"); + goto out; + } + + gp->tx_bip_fem_manufacturer = + gen_parms->general_params.tx_bip_fem_manufacturer; + + if (gp->tx_bip_fem_manufacturer >= WL1271_INI_FEM_MODULE_COUNT) { + wl1271_warning("FEM index from FW out of bounds"); + ret = -EINVAL; + goto out; + } + + /* If we are in calibrator based fem auto detect - save fem nr */ + if (wl->plt_mode == PLT_FEM_DETECT) + wl->fem_manuf = gp->tx_bip_fem_manufacturer; + + wl1271_debug(DEBUG_CMD, "FEM autodetect: %s, manufacturer: %d\n", + answer == false ? + "manual" : + wl->plt_mode == PLT_FEM_DETECT ? + "calibrator_fem_detect" : + "auto", + gp->tx_bip_fem_manufacturer); + +out: + kfree(gen_parms); + return ret; +} + +int wl128x_cmd_general_parms(struct wl1271 *wl) +{ + struct wl128x_general_parms_cmd *gen_parms; + struct wl128x_ini_general_params *gp = + &((struct wl128x_nvs_file *)wl->nvs)->general_params; + struct wl12xx_priv *priv = wl->priv; + bool answer = false; + int ret; + + if (!wl->nvs) + return -ENODEV; + + if (gp->tx_bip_fem_manufacturer >= WL1271_INI_FEM_MODULE_COUNT) { + wl1271_warning("FEM index from ini out of bounds"); + return -EINVAL; + } + + gen_parms = kzalloc(sizeof(*gen_parms), GFP_KERNEL); + if (!gen_parms) + return -ENOMEM; + + gen_parms->test.id = TEST_CMD_INI_FILE_GENERAL_PARAM; + + memcpy(&gen_parms->general_params, gp, sizeof(*gp)); + + /* If we started in PLT FEM_DETECT mode, force auto detect */ + if (wl->plt_mode == PLT_FEM_DETECT) + gen_parms->general_params.tx_bip_fem_auto_detect = true; + + if (gen_parms->general_params.tx_bip_fem_auto_detect) + answer = true; + + /* Replace REF and TCXO CLKs with the ones from platform data */ + gen_parms->general_params.ref_clock = priv->ref_clock; + gen_parms->general_params.tcxo_ref_clock = priv->tcxo_clock; + + ret = wl1271_cmd_test(wl, gen_parms, sizeof(*gen_parms), answer); + if (ret < 0) { + wl1271_warning("CMD_INI_FILE_GENERAL_PARAM failed"); + goto out; + } + + gp->tx_bip_fem_manufacturer = + gen_parms->general_params.tx_bip_fem_manufacturer; + + if (gp->tx_bip_fem_manufacturer >= WL1271_INI_FEM_MODULE_COUNT) { + wl1271_warning("FEM index from FW out of bounds"); + ret = -EINVAL; + goto out; + } + + /* If we are in calibrator based fem auto detect - save fem nr */ + if (wl->plt_mode == PLT_FEM_DETECT) + wl->fem_manuf = gp->tx_bip_fem_manufacturer; + + wl1271_debug(DEBUG_CMD, "FEM autodetect: %s, manufacturer: %d\n", + answer == false ? + "manual" : + wl->plt_mode == PLT_FEM_DETECT ? + "calibrator_fem_detect" : + "auto", + gp->tx_bip_fem_manufacturer); + +out: + kfree(gen_parms); + return ret; +} + +int wl1271_cmd_radio_parms(struct wl1271 *wl) +{ + struct wl1271_nvs_file *nvs = (struct wl1271_nvs_file *)wl->nvs; + struct wl1271_radio_parms_cmd *radio_parms; + struct wl1271_ini_general_params *gp = &nvs->general_params; + int ret, fem_idx; + + if (!wl->nvs) + return -ENODEV; + + radio_parms = kzalloc(sizeof(*radio_parms), GFP_KERNEL); + if (!radio_parms) + return -ENOMEM; + + radio_parms->test.id = TEST_CMD_INI_FILE_RADIO_PARAM; + + fem_idx = WL12XX_FEM_TO_NVS_ENTRY(gp->tx_bip_fem_manufacturer); + + /* 2.4GHz parameters */ + memcpy(&radio_parms->static_params_2, &nvs->stat_radio_params_2, + sizeof(struct wl1271_ini_band_params_2)); + memcpy(&radio_parms->dyn_params_2, + &nvs->dyn_radio_params_2[fem_idx].params, + sizeof(struct wl1271_ini_fem_params_2)); + + /* 5GHz parameters */ + memcpy(&radio_parms->static_params_5, + &nvs->stat_radio_params_5, + sizeof(struct wl1271_ini_band_params_5)); + memcpy(&radio_parms->dyn_params_5, + &nvs->dyn_radio_params_5[fem_idx].params, + sizeof(struct wl1271_ini_fem_params_5)); + + wl1271_dump(DEBUG_CMD, "TEST_CMD_INI_FILE_RADIO_PARAM: ", + radio_parms, sizeof(*radio_parms)); + + ret = wl1271_cmd_test(wl, radio_parms, sizeof(*radio_parms), 0); + if (ret < 0) + wl1271_warning("CMD_INI_FILE_RADIO_PARAM failed"); + + kfree(radio_parms); + return ret; +} + +int wl128x_cmd_radio_parms(struct wl1271 *wl) +{ + struct wl128x_nvs_file *nvs = (struct wl128x_nvs_file *)wl->nvs; + struct wl128x_radio_parms_cmd *radio_parms; + struct wl128x_ini_general_params *gp = &nvs->general_params; + int ret, fem_idx; + + if (!wl->nvs) + return -ENODEV; + + radio_parms = kzalloc(sizeof(*radio_parms), GFP_KERNEL); + if (!radio_parms) + return -ENOMEM; + + radio_parms->test.id = TEST_CMD_INI_FILE_RADIO_PARAM; + + fem_idx = WL12XX_FEM_TO_NVS_ENTRY(gp->tx_bip_fem_manufacturer); + + /* 2.4GHz parameters */ + memcpy(&radio_parms->static_params_2, &nvs->stat_radio_params_2, + sizeof(struct wl128x_ini_band_params_2)); + memcpy(&radio_parms->dyn_params_2, + &nvs->dyn_radio_params_2[fem_idx].params, + sizeof(struct wl128x_ini_fem_params_2)); + + /* 5GHz parameters */ + memcpy(&radio_parms->static_params_5, + &nvs->stat_radio_params_5, + sizeof(struct wl128x_ini_band_params_5)); + memcpy(&radio_parms->dyn_params_5, + &nvs->dyn_radio_params_5[fem_idx].params, + sizeof(struct wl128x_ini_fem_params_5)); + + radio_parms->fem_vendor_and_options = nvs->fem_vendor_and_options; + + wl1271_dump(DEBUG_CMD, "TEST_CMD_INI_FILE_RADIO_PARAM: ", + radio_parms, sizeof(*radio_parms)); + + ret = wl1271_cmd_test(wl, radio_parms, sizeof(*radio_parms), 0); + if (ret < 0) + wl1271_warning("CMD_INI_FILE_RADIO_PARAM failed"); + + kfree(radio_parms); + return ret; +} + +int wl12xx_cmd_channel_switch(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + 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->role_id = wlvif->role_id; + cmd->channel = ch_switch->chandef.chan->hw_value; + cmd->switch_time = ch_switch->count; + cmd->stop_tx = ch_switch->block_tx; + + /* FIXME: control from mac80211 in the future */ + /* Enable TX on the target channel */ + cmd->post_switch_tx_disable = 0; + + 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; +} diff --git a/drivers/net/wireless/ti/wl12xx/cmd.h b/drivers/net/wireless/ti/wl12xx/cmd.h new file mode 100644 index 0000000..32cbad5 --- /dev/null +++ b/drivers/net/wireless/ti/wl12xx/cmd.h @@ -0,0 +1,132 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 1998-2009, 2011 Texas Instruments. All rights reserved. + * Copyright (C) 2009 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL12XX_CMD_H__ +#define __WL12XX_CMD_H__ + +#include "conf.h" + +#define TEST_CMD_INI_FILE_RADIO_PARAM 0x19 +#define TEST_CMD_INI_FILE_GENERAL_PARAM 0x1E + +struct wl1271_general_parms_cmd { + struct wl1271_cmd_header header; + + struct wl1271_cmd_test_header test; + + struct wl1271_ini_general_params general_params; + + u8 sr_debug_table[WL1271_INI_MAX_SMART_REFLEX_PARAM]; + u8 sr_sen_n_p; + u8 sr_sen_n_p_gain; + u8 sr_sen_nrn; + u8 sr_sen_prn; + u8 padding[3]; +} __packed; + +struct wl128x_general_parms_cmd { + struct wl1271_cmd_header header; + + struct wl1271_cmd_test_header test; + + struct wl128x_ini_general_params general_params; + + u8 sr_debug_table[WL1271_INI_MAX_SMART_REFLEX_PARAM]; + u8 sr_sen_n_p; + u8 sr_sen_n_p_gain; + u8 sr_sen_nrn; + u8 sr_sen_prn; + u8 padding[3]; +} __packed; + +struct wl1271_radio_parms_cmd { + struct wl1271_cmd_header header; + + struct wl1271_cmd_test_header test; + + /* Static radio parameters */ + struct wl1271_ini_band_params_2 static_params_2; + struct wl1271_ini_band_params_5 static_params_5; + + /* Dynamic radio parameters */ + struct wl1271_ini_fem_params_2 dyn_params_2; + u8 padding2; + struct wl1271_ini_fem_params_5 dyn_params_5; + u8 padding3[2]; +} __packed; + +struct wl128x_radio_parms_cmd { + struct wl1271_cmd_header header; + + struct wl1271_cmd_test_header test; + + /* Static radio parameters */ + struct wl128x_ini_band_params_2 static_params_2; + struct wl128x_ini_band_params_5 static_params_5; + + u8 fem_vendor_and_options; + + /* Dynamic radio parameters */ + struct wl128x_ini_fem_params_2 dyn_params_2; + u8 padding2; + struct wl128x_ini_fem_params_5 dyn_params_5; +} __packed; + +#define TEST_CMD_INI_FILE_RF_EXTENDED_PARAM 0x26 + +struct wl1271_ext_radio_parms_cmd { + struct wl1271_cmd_header header; + + struct wl1271_cmd_test_header test; + + u8 tx_per_channel_power_compensation_2[CONF_TX_PWR_COMPENSATION_LEN_2]; + u8 tx_per_channel_power_compensation_5[CONF_TX_PWR_COMPENSATION_LEN_5]; + u8 padding[3]; +} __packed; + +struct wl12xx_cmd_channel_switch { + struct wl1271_cmd_header header; + + u8 role_id; + + /* The new serving channel */ + u8 channel; + /* Relative time of the serving channel switch in TBTT units */ + u8 switch_time; + /* Stop the role TX, should expect it after radar detection */ + u8 stop_tx; + /* The target channel tx status 1-stopped 0-open*/ + u8 post_switch_tx_disable; + + u8 padding[3]; +} __packed; + +int wl1271_cmd_general_parms(struct wl1271 *wl); +int wl128x_cmd_general_parms(struct wl1271 *wl); +int wl1271_cmd_radio_parms(struct wl1271 *wl); +int wl128x_cmd_radio_parms(struct wl1271 *wl); +int wl1271_cmd_ext_radio_parms(struct wl1271 *wl); +int wl12xx_cmd_channel_switch(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct ieee80211_channel_switch *ch_switch); + +#endif /* __WL12XX_CMD_H__ */ diff --git a/drivers/net/wireless/ti/wl12xx/conf.h b/drivers/net/wireless/ti/wl12xx/conf.h new file mode 100644 index 0000000..75e2989 --- /dev/null +++ b/drivers/net/wireless/ti/wl12xx/conf.h @@ -0,0 +1,50 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 2011 Texas Instruments Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL12XX_CONF_H__ +#define __WL12XX_CONF_H__ + +/* these are number of channels on the band divided by two, rounded up */ +#define CONF_TX_PWR_COMPENSATION_LEN_2 7 +#define CONF_TX_PWR_COMPENSATION_LEN_5 18 + +struct wl12xx_conf_rf { + /* + * Per channel power compensation for 2.4GHz + * + * Range: s8 + */ + u8 tx_per_channel_power_compensation_2[CONF_TX_PWR_COMPENSATION_LEN_2]; + + /* + * Per channel power compensation for 5GHz + * + * Range: s8 + */ + u8 tx_per_channel_power_compensation_5[CONF_TX_PWR_COMPENSATION_LEN_5]; +}; + +struct wl12xx_priv_conf { + struct wl12xx_conf_rf rf; + struct conf_memory_settings mem_wl127x; +}; + +#endif /* __WL12XX_CONF_H__ */ diff --git a/drivers/net/wireless/ti/wl12xx/debugfs.c b/drivers/net/wireless/ti/wl12xx/debugfs.c new file mode 100644 index 0000000..0521cbf --- /dev/null +++ b/drivers/net/wireless/ti/wl12xx/debugfs.c @@ -0,0 +1,243 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 2009 Nokia Corporation + * Copyright (C) 2011-2012 Texas Instruments + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "../wlcore/debugfs.h" +#include "../wlcore/wlcore.h" + +#include "wl12xx.h" +#include "acx.h" +#include "debugfs.h" + +#define WL12XX_DEBUGFS_FWSTATS_FILE(a, b, c) \ + DEBUGFS_FWSTATS_FILE(a, b, c, wl12xx_acx_statistics) + +WL12XX_DEBUGFS_FWSTATS_FILE(tx, internal_desc_overflow, "%u"); + +WL12XX_DEBUGFS_FWSTATS_FILE(rx, out_of_mem, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(rx, hdr_overflow, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(rx, hw_stuck, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(rx, dropped, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(rx, fcs_err, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(rx, xfr_hint_trig, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(rx, path_reset, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(rx, reset_counter, "%u"); + +WL12XX_DEBUGFS_FWSTATS_FILE(dma, rx_requested, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(dma, rx_errors, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(dma, tx_requested, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(dma, tx_errors, "%u"); + +WL12XX_DEBUGFS_FWSTATS_FILE(isr, cmd_cmplt, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, fiqs, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, rx_headers, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, rx_mem_overflow, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, rx_rdys, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, irqs, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, tx_procs, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, decrypt_done, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, dma0_done, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, dma1_done, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, tx_exch_complete, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, commands, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, rx_procs, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, hw_pm_mode_changes, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, host_acknowledges, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, pci_pm, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, wakeups, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(isr, low_rssi, "%u"); + +WL12XX_DEBUGFS_FWSTATS_FILE(wep, addr_key_count, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(wep, default_key_count, "%u"); +/* skipping wep.reserved */ +WL12XX_DEBUGFS_FWSTATS_FILE(wep, key_not_found, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(wep, decrypt_fail, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(wep, packets, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(wep, interrupt, "%u"); + +WL12XX_DEBUGFS_FWSTATS_FILE(pwr, ps_enter, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(pwr, elp_enter, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(pwr, missing_bcns, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(pwr, wake_on_host, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(pwr, wake_on_timer_exp, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(pwr, tx_with_ps, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(pwr, tx_without_ps, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(pwr, rcvd_beacons, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(pwr, power_save_off, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(pwr, enable_ps, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(pwr, disable_ps, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(pwr, fix_tsf_ps, "%u"); +/* skipping cont_miss_bcns_spread for now */ +WL12XX_DEBUGFS_FWSTATS_FILE(pwr, rcvd_awake_beacons, "%u"); + +WL12XX_DEBUGFS_FWSTATS_FILE(mic, rx_pkts, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(mic, calc_failure, "%u"); + +WL12XX_DEBUGFS_FWSTATS_FILE(aes, encrypt_fail, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(aes, decrypt_fail, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(aes, encrypt_packets, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(aes, decrypt_packets, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(aes, encrypt_interrupt, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(aes, decrypt_interrupt, "%u"); + +WL12XX_DEBUGFS_FWSTATS_FILE(event, heart_beat, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(event, calibration, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(event, rx_mismatch, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(event, rx_mem_empty, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(event, rx_pool, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(event, oom_late, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(event, phy_transmit_error, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(event, tx_stuck, "%u"); + +WL12XX_DEBUGFS_FWSTATS_FILE(ps, pspoll_timeouts, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(ps, upsd_timeouts, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(ps, upsd_max_sptime, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(ps, upsd_max_apturn, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(ps, pspoll_max_apturn, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(ps, pspoll_utilization, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(ps, upsd_utilization, "%u"); + +WL12XX_DEBUGFS_FWSTATS_FILE(rxpipe, rx_prep_beacon_drop, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(rxpipe, descr_host_int_trig_rx_data, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(rxpipe, beacon_buffer_thres_host_int_trig_rx_data, + "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(rxpipe, missed_beacon_host_int_trig_rx_data, "%u"); +WL12XX_DEBUGFS_FWSTATS_FILE(rxpipe, tx_xfr_host_int_trig_rx_data, "%u"); + +int wl12xx_debugfs_add_files(struct wl1271 *wl, + struct dentry *rootdir) +{ + int ret = 0; + struct dentry *entry, *stats, *moddir; + + moddir = debugfs_create_dir(KBUILD_MODNAME, rootdir); + if (!moddir || IS_ERR(moddir)) { + entry = moddir; + goto err; + } + + stats = debugfs_create_dir("fw_stats", moddir); + if (!stats || IS_ERR(stats)) { + entry = stats; + goto err; + } + + DEBUGFS_FWSTATS_ADD(tx, internal_desc_overflow); + + DEBUGFS_FWSTATS_ADD(rx, out_of_mem); + DEBUGFS_FWSTATS_ADD(rx, hdr_overflow); + DEBUGFS_FWSTATS_ADD(rx, hw_stuck); + DEBUGFS_FWSTATS_ADD(rx, dropped); + DEBUGFS_FWSTATS_ADD(rx, fcs_err); + DEBUGFS_FWSTATS_ADD(rx, xfr_hint_trig); + DEBUGFS_FWSTATS_ADD(rx, path_reset); + DEBUGFS_FWSTATS_ADD(rx, reset_counter); + + DEBUGFS_FWSTATS_ADD(dma, rx_requested); + DEBUGFS_FWSTATS_ADD(dma, rx_errors); + DEBUGFS_FWSTATS_ADD(dma, tx_requested); + DEBUGFS_FWSTATS_ADD(dma, tx_errors); + + DEBUGFS_FWSTATS_ADD(isr, cmd_cmplt); + DEBUGFS_FWSTATS_ADD(isr, fiqs); + DEBUGFS_FWSTATS_ADD(isr, rx_headers); + DEBUGFS_FWSTATS_ADD(isr, rx_mem_overflow); + DEBUGFS_FWSTATS_ADD(isr, rx_rdys); + DEBUGFS_FWSTATS_ADD(isr, irqs); + DEBUGFS_FWSTATS_ADD(isr, tx_procs); + DEBUGFS_FWSTATS_ADD(isr, decrypt_done); + DEBUGFS_FWSTATS_ADD(isr, dma0_done); + DEBUGFS_FWSTATS_ADD(isr, dma1_done); + DEBUGFS_FWSTATS_ADD(isr, tx_exch_complete); + DEBUGFS_FWSTATS_ADD(isr, commands); + DEBUGFS_FWSTATS_ADD(isr, rx_procs); + DEBUGFS_FWSTATS_ADD(isr, hw_pm_mode_changes); + DEBUGFS_FWSTATS_ADD(isr, host_acknowledges); + DEBUGFS_FWSTATS_ADD(isr, pci_pm); + DEBUGFS_FWSTATS_ADD(isr, wakeups); + DEBUGFS_FWSTATS_ADD(isr, low_rssi); + + DEBUGFS_FWSTATS_ADD(wep, addr_key_count); + DEBUGFS_FWSTATS_ADD(wep, default_key_count); + /* skipping wep.reserved */ + DEBUGFS_FWSTATS_ADD(wep, key_not_found); + DEBUGFS_FWSTATS_ADD(wep, decrypt_fail); + DEBUGFS_FWSTATS_ADD(wep, packets); + DEBUGFS_FWSTATS_ADD(wep, interrupt); + + DEBUGFS_FWSTATS_ADD(pwr, ps_enter); + DEBUGFS_FWSTATS_ADD(pwr, elp_enter); + DEBUGFS_FWSTATS_ADD(pwr, missing_bcns); + DEBUGFS_FWSTATS_ADD(pwr, wake_on_host); + DEBUGFS_FWSTATS_ADD(pwr, wake_on_timer_exp); + DEBUGFS_FWSTATS_ADD(pwr, tx_with_ps); + DEBUGFS_FWSTATS_ADD(pwr, tx_without_ps); + DEBUGFS_FWSTATS_ADD(pwr, rcvd_beacons); + DEBUGFS_FWSTATS_ADD(pwr, power_save_off); + DEBUGFS_FWSTATS_ADD(pwr, enable_ps); + DEBUGFS_FWSTATS_ADD(pwr, disable_ps); + DEBUGFS_FWSTATS_ADD(pwr, fix_tsf_ps); + /* skipping cont_miss_bcns_spread for now */ + DEBUGFS_FWSTATS_ADD(pwr, rcvd_awake_beacons); + + DEBUGFS_FWSTATS_ADD(mic, rx_pkts); + DEBUGFS_FWSTATS_ADD(mic, calc_failure); + + DEBUGFS_FWSTATS_ADD(aes, encrypt_fail); + DEBUGFS_FWSTATS_ADD(aes, decrypt_fail); + DEBUGFS_FWSTATS_ADD(aes, encrypt_packets); + DEBUGFS_FWSTATS_ADD(aes, decrypt_packets); + DEBUGFS_FWSTATS_ADD(aes, encrypt_interrupt); + DEBUGFS_FWSTATS_ADD(aes, decrypt_interrupt); + + DEBUGFS_FWSTATS_ADD(event, heart_beat); + DEBUGFS_FWSTATS_ADD(event, calibration); + DEBUGFS_FWSTATS_ADD(event, rx_mismatch); + DEBUGFS_FWSTATS_ADD(event, rx_mem_empty); + DEBUGFS_FWSTATS_ADD(event, rx_pool); + DEBUGFS_FWSTATS_ADD(event, oom_late); + DEBUGFS_FWSTATS_ADD(event, phy_transmit_error); + DEBUGFS_FWSTATS_ADD(event, tx_stuck); + + DEBUGFS_FWSTATS_ADD(ps, pspoll_timeouts); + DEBUGFS_FWSTATS_ADD(ps, upsd_timeouts); + DEBUGFS_FWSTATS_ADD(ps, upsd_max_sptime); + DEBUGFS_FWSTATS_ADD(ps, upsd_max_apturn); + DEBUGFS_FWSTATS_ADD(ps, pspoll_max_apturn); + DEBUGFS_FWSTATS_ADD(ps, pspoll_utilization); + DEBUGFS_FWSTATS_ADD(ps, upsd_utilization); + + DEBUGFS_FWSTATS_ADD(rxpipe, rx_prep_beacon_drop); + DEBUGFS_FWSTATS_ADD(rxpipe, descr_host_int_trig_rx_data); + DEBUGFS_FWSTATS_ADD(rxpipe, beacon_buffer_thres_host_int_trig_rx_data); + DEBUGFS_FWSTATS_ADD(rxpipe, missed_beacon_host_int_trig_rx_data); + DEBUGFS_FWSTATS_ADD(rxpipe, tx_xfr_host_int_trig_rx_data); + + return 0; + +err: + if (IS_ERR(entry)) + ret = PTR_ERR(entry); + else + ret = -ENOMEM; + + return ret; +} diff --git a/drivers/net/wireless/ti/wl12xx/debugfs.h b/drivers/net/wireless/ti/wl12xx/debugfs.h new file mode 100644 index 0000000..96898e2 --- /dev/null +++ b/drivers/net/wireless/ti/wl12xx/debugfs.h @@ -0,0 +1,28 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 2012 Texas Instruments. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL12XX_DEBUGFS_H__ +#define __WL12XX_DEBUGFS_H__ + +int wl12xx_debugfs_add_files(struct wl1271 *wl, + struct dentry *rootdir); + +#endif /* __WL12XX_DEBUGFS_H__ */ diff --git a/drivers/net/wireless/ti/wl12xx/event.c b/drivers/net/wireless/ti/wl12xx/event.c new file mode 100644 index 0000000..6ac0ed7 --- /dev/null +++ b/drivers/net/wireless/ti/wl12xx/event.c @@ -0,0 +1,116 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 2012 Texas Instruments. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "event.h" +#include "scan.h" +#include "../wlcore/cmd.h" +#include "../wlcore/debug.h" + +int wl12xx_wait_for_event(struct wl1271 *wl, enum wlcore_wait_event event, + bool *timeout) +{ + u32 local_event; + + switch (event) { + case WLCORE_EVENT_ROLE_STOP_COMPLETE: + local_event = ROLE_STOP_COMPLETE_EVENT_ID; + break; + + case WLCORE_EVENT_PEER_REMOVE_COMPLETE: + local_event = PEER_REMOVE_COMPLETE_EVENT_ID; + break; + + default: + /* event not implemented */ + return 0; + } + return wlcore_cmd_wait_for_event_or_timeout(wl, local_event, timeout); +} + +int wl12xx_process_mailbox_events(struct wl1271 *wl) +{ + struct wl12xx_event_mailbox *mbox = wl->mbox; + u32 vector; + + + vector = le32_to_cpu(mbox->events_vector); + vector &= ~(le32_to_cpu(mbox->events_mask)); + + wl1271_debug(DEBUG_EVENT, "MBOX vector: 0x%x", vector); + + if (vector & SCAN_COMPLETE_EVENT_ID) { + wl1271_debug(DEBUG_EVENT, "status: 0x%x", + mbox->scheduled_scan_status); + + if (wl->scan_wlvif) + wl12xx_scan_completed(wl, wl->scan_wlvif); + } + + if (vector & PERIODIC_SCAN_REPORT_EVENT_ID) { + wl1271_debug(DEBUG_EVENT, + "PERIODIC_SCAN_REPORT_EVENT (status 0x%0x)", + mbox->scheduled_scan_status); + + wlcore_scan_sched_scan_results(wl); + } + + if (vector & PERIODIC_SCAN_COMPLETE_EVENT_ID) + wlcore_event_sched_scan_completed(wl, + mbox->scheduled_scan_status); + if (vector & SOFT_GEMINI_SENSE_EVENT_ID) + wlcore_event_soft_gemini_sense(wl, + mbox->soft_gemini_sense_info); + + if (vector & BSS_LOSE_EVENT_ID) + wlcore_event_beacon_loss(wl, 0xff); + + if (vector & RSSI_SNR_TRIGGER_0_EVENT_ID) + wlcore_event_rssi_trigger(wl, mbox->rssi_snr_trigger_metric); + + if (vector & BA_SESSION_RX_CONSTRAINT_EVENT_ID) + wlcore_event_ba_rx_constraint(wl, + BIT(mbox->role_id), + mbox->rx_ba_allowed); + + if (vector & CHANNEL_SWITCH_COMPLETE_EVENT_ID) + wlcore_event_channel_switch(wl, 0xff, + mbox->channel_switch_status); + + if (vector & DUMMY_PACKET_EVENT_ID) + wlcore_event_dummy_packet(wl); + + /* + * "TX retries exceeded" has a different meaning according to mode. + * In AP mode the offending station is disconnected. + */ + if (vector & MAX_TX_RETRY_EVENT_ID) + wlcore_event_max_tx_failure(wl, + le16_to_cpu(mbox->sta_tx_retry_exceeded)); + + if (vector & INACTIVE_STA_EVENT_ID) + wlcore_event_inactive_sta(wl, + le16_to_cpu(mbox->sta_aging_status)); + + if (vector & REMAIN_ON_CHANNEL_COMPLETE_EVENT_ID) + wlcore_event_roc_complete(wl); + + return 0; +} diff --git a/drivers/net/wireless/ti/wl12xx/event.h b/drivers/net/wireless/ti/wl12xx/event.h new file mode 100644 index 0000000..a5cc3fc --- /dev/null +++ b/drivers/net/wireless/ti/wl12xx/event.h @@ -0,0 +1,111 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 2012 Texas Instruments. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL12XX_EVENT_H__ +#define __WL12XX_EVENT_H__ + +#include "../wlcore/wlcore.h" + +enum { + MEASUREMENT_START_EVENT_ID = BIT(8), + MEASUREMENT_COMPLETE_EVENT_ID = BIT(9), + SCAN_COMPLETE_EVENT_ID = BIT(10), + WFD_DISCOVERY_COMPLETE_EVENT_ID = BIT(11), + AP_DISCOVERY_COMPLETE_EVENT_ID = BIT(12), + RESERVED1 = BIT(13), + PSPOLL_DELIVERY_FAILURE_EVENT_ID = BIT(14), + ROLE_STOP_COMPLETE_EVENT_ID = BIT(15), + RADAR_DETECTED_EVENT_ID = BIT(16), + CHANNEL_SWITCH_COMPLETE_EVENT_ID = BIT(17), + BSS_LOSE_EVENT_ID = BIT(18), + REGAINED_BSS_EVENT_ID = BIT(19), + MAX_TX_RETRY_EVENT_ID = BIT(20), + DUMMY_PACKET_EVENT_ID = BIT(21), + SOFT_GEMINI_SENSE_EVENT_ID = BIT(22), + CHANGE_AUTO_MODE_TIMEOUT_EVENT_ID = BIT(23), + SOFT_GEMINI_AVALANCHE_EVENT_ID = BIT(24), + PLT_RX_CALIBRATION_COMPLETE_EVENT_ID = BIT(25), + INACTIVE_STA_EVENT_ID = BIT(26), + PEER_REMOVE_COMPLETE_EVENT_ID = BIT(27), + PERIODIC_SCAN_COMPLETE_EVENT_ID = BIT(28), + PERIODIC_SCAN_REPORT_EVENT_ID = BIT(29), + BA_SESSION_RX_CONSTRAINT_EVENT_ID = BIT(30), + REMAIN_ON_CHANNEL_COMPLETE_EVENT_ID = BIT(31), +}; + +struct wl12xx_event_mailbox { + __le32 events_vector; + __le32 events_mask; + __le32 reserved_1; + __le32 reserved_2; + + u8 number_of_scan_results; + u8 scan_tag; + u8 completed_scan_status; + u8 reserved_3; + + u8 soft_gemini_sense_info; + u8 soft_gemini_protective_info; + s8 rssi_snr_trigger_metric[NUM_OF_RSSI_SNR_TRIGGERS]; + u8 change_auto_mode_timeout; + u8 scheduled_scan_status; + u8 reserved4; + /* tuned channel (roc) */ + u8 roc_channel; + + __le16 hlid_removed_bitmap; + + /* bitmap of aged stations (by HLID) */ + __le16 sta_aging_status; + + /* bitmap of stations (by HLID) which exceeded max tx retries */ + __le16 sta_tx_retry_exceeded; + + /* discovery completed results */ + u8 discovery_tag; + u8 number_of_preq_results; + u8 number_of_prsp_results; + u8 reserved_5; + + /* rx ba constraint */ + u8 role_id; /* 0xFF means any role. */ + u8 rx_ba_allowed; + u8 reserved_6[2]; + + /* Channel switch results */ + + u8 channel_switch_role_id; + u8 channel_switch_status; + u8 reserved_7[2]; + + u8 ps_poll_delivery_failure_role_ids; + u8 stopped_role_ids; + u8 started_role_ids; + + u8 reserved_8[9]; +} __packed; + +int wl12xx_wait_for_event(struct wl1271 *wl, enum wlcore_wait_event event, + bool *timeout); +int wl12xx_process_mailbox_events(struct wl1271 *wl); + +#endif + diff --git a/drivers/net/wireless/ti/wl12xx/main.c b/drivers/net/wireless/ti/wl12xx/main.c new file mode 100644 index 0000000..db4fc15 --- /dev/null +++ b/drivers/net/wireless/ti/wl12xx/main.c @@ -0,0 +1,1854 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2010 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include + +#include + +#include + +#include "../wlcore/wlcore.h" +#include "../wlcore/debug.h" +#include "../wlcore/io.h" +#include "../wlcore/acx.h" +#include "../wlcore/tx.h" +#include "../wlcore/rx.h" +#include "../wlcore/boot.h" + +#include "wl12xx.h" +#include "reg.h" +#include "cmd.h" +#include "acx.h" +#include "scan.h" +#include "event.h" +#include "debugfs.h" + +static char *fref_param; +static char *tcxo_param; + +static struct wlcore_conf wl12xx_conf = { + .sg = { + .params = { + [CONF_SG_ACL_BT_MASTER_MIN_BR] = 10, + [CONF_SG_ACL_BT_MASTER_MAX_BR] = 180, + [CONF_SG_ACL_BT_SLAVE_MIN_BR] = 10, + [CONF_SG_ACL_BT_SLAVE_MAX_BR] = 180, + [CONF_SG_ACL_BT_MASTER_MIN_EDR] = 10, + [CONF_SG_ACL_BT_MASTER_MAX_EDR] = 80, + [CONF_SG_ACL_BT_SLAVE_MIN_EDR] = 10, + [CONF_SG_ACL_BT_SLAVE_MAX_EDR] = 80, + [CONF_SG_ACL_WLAN_PS_MASTER_BR] = 8, + [CONF_SG_ACL_WLAN_PS_SLAVE_BR] = 8, + [CONF_SG_ACL_WLAN_PS_MASTER_EDR] = 20, + [CONF_SG_ACL_WLAN_PS_SLAVE_EDR] = 20, + [CONF_SG_ACL_WLAN_ACTIVE_MASTER_MIN_BR] = 20, + [CONF_SG_ACL_WLAN_ACTIVE_MASTER_MAX_BR] = 35, + [CONF_SG_ACL_WLAN_ACTIVE_SLAVE_MIN_BR] = 16, + [CONF_SG_ACL_WLAN_ACTIVE_SLAVE_MAX_BR] = 35, + [CONF_SG_ACL_WLAN_ACTIVE_MASTER_MIN_EDR] = 32, + [CONF_SG_ACL_WLAN_ACTIVE_MASTER_MAX_EDR] = 50, + [CONF_SG_ACL_WLAN_ACTIVE_SLAVE_MIN_EDR] = 28, + [CONF_SG_ACL_WLAN_ACTIVE_SLAVE_MAX_EDR] = 50, + [CONF_SG_ACL_ACTIVE_SCAN_WLAN_BR] = 10, + [CONF_SG_ACL_ACTIVE_SCAN_WLAN_EDR] = 20, + [CONF_SG_ACL_PASSIVE_SCAN_BT_BR] = 75, + [CONF_SG_ACL_PASSIVE_SCAN_WLAN_BR] = 15, + [CONF_SG_ACL_PASSIVE_SCAN_BT_EDR] = 27, + [CONF_SG_ACL_PASSIVE_SCAN_WLAN_EDR] = 17, + /* active scan params */ + [CONF_SG_AUTO_SCAN_PROBE_REQ] = 170, + [CONF_SG_ACTIVE_SCAN_DURATION_FACTOR_HV3] = 50, + [CONF_SG_ACTIVE_SCAN_DURATION_FACTOR_A2DP] = 100, + /* passive scan params */ + [CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_A2DP_BR] = 800, + [CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_A2DP_EDR] = 200, + [CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_HV3] = 200, + /* passive scan in dual antenna params */ + [CONF_SG_CONSECUTIVE_HV3_IN_PASSIVE_SCAN] = 0, + [CONF_SG_BCN_HV3_COLLISION_THRESH_IN_PASSIVE_SCAN] = 0, + [CONF_SG_TX_RX_PROTECTION_BWIDTH_IN_PASSIVE_SCAN] = 0, + /* general params */ + [CONF_SG_STA_FORCE_PS_IN_BT_SCO] = 1, + [CONF_SG_ANTENNA_CONFIGURATION] = 0, + [CONF_SG_BEACON_MISS_PERCENT] = 60, + [CONF_SG_DHCP_TIME] = 5000, + [CONF_SG_RXT] = 1200, + [CONF_SG_TXT] = 1000, + [CONF_SG_ADAPTIVE_RXT_TXT] = 1, + [CONF_SG_GENERAL_USAGE_BIT_MAP] = 3, + [CONF_SG_HV3_MAX_SERVED] = 6, + [CONF_SG_PS_POLL_TIMEOUT] = 10, + [CONF_SG_UPSD_TIMEOUT] = 10, + [CONF_SG_CONSECUTIVE_CTS_THRESHOLD] = 2, + [CONF_SG_STA_RX_WINDOW_AFTER_DTIM] = 5, + [CONF_SG_STA_CONNECTION_PROTECTION_TIME] = 30, + /* AP params */ + [CONF_AP_BEACON_MISS_TX] = 3, + [CONF_AP_RX_WINDOW_AFTER_BEACON] = 10, + [CONF_AP_BEACON_WINDOW_INTERVAL] = 2, + [CONF_AP_CONNECTION_PROTECTION_TIME] = 0, + [CONF_AP_BT_ACL_VAL_BT_SERVE_TIME] = 25, + [CONF_AP_BT_ACL_VAL_WL_SERVE_TIME] = 25, + /* CTS Diluting params */ + [CONF_SG_CTS_DILUTED_BAD_RX_PACKETS_TH] = 0, + [CONF_SG_CTS_CHOP_IN_DUAL_ANT_SCO_MASTER] = 0, + }, + .state = CONF_SG_PROTECTIVE, + }, + .rx = { + .rx_msdu_life_time = 512000, + .packet_detection_threshold = 0, + .ps_poll_timeout = 15, + .upsd_timeout = 15, + .rts_threshold = IEEE80211_MAX_RTS_THRESHOLD, + .rx_cca_threshold = 0, + .irq_blk_threshold = 0xFFFF, + .irq_pkt_threshold = 0, + .irq_timeout = 600, + .queue_type = CONF_RX_QUEUE_TYPE_LOW_PRIORITY, + }, + .tx = { + .tx_energy_detection = 0, + .sta_rc_conf = { + .enabled_rates = 0, + .short_retry_limit = 10, + .long_retry_limit = 10, + .aflags = 0, + }, + .ac_conf_count = 4, + .ac_conf = { + [CONF_TX_AC_BE] = { + .ac = CONF_TX_AC_BE, + .cw_min = 15, + .cw_max = 63, + .aifsn = 3, + .tx_op_limit = 0, + }, + [CONF_TX_AC_BK] = { + .ac = CONF_TX_AC_BK, + .cw_min = 15, + .cw_max = 63, + .aifsn = 7, + .tx_op_limit = 0, + }, + [CONF_TX_AC_VI] = { + .ac = CONF_TX_AC_VI, + .cw_min = 15, + .cw_max = 63, + .aifsn = CONF_TX_AIFS_PIFS, + .tx_op_limit = 3008, + }, + [CONF_TX_AC_VO] = { + .ac = CONF_TX_AC_VO, + .cw_min = 15, + .cw_max = 63, + .aifsn = CONF_TX_AIFS_PIFS, + .tx_op_limit = 1504, + }, + }, + .max_tx_retries = 100, + .ap_aging_period = 300, + .tid_conf_count = 4, + .tid_conf = { + [CONF_TX_AC_BE] = { + .queue_id = CONF_TX_AC_BE, + .channel_type = CONF_CHANNEL_TYPE_EDCF, + .tsid = CONF_TX_AC_BE, + .ps_scheme = CONF_PS_SCHEME_LEGACY, + .ack_policy = CONF_ACK_POLICY_LEGACY, + .apsd_conf = {0, 0}, + }, + [CONF_TX_AC_BK] = { + .queue_id = CONF_TX_AC_BK, + .channel_type = CONF_CHANNEL_TYPE_EDCF, + .tsid = CONF_TX_AC_BK, + .ps_scheme = CONF_PS_SCHEME_LEGACY, + .ack_policy = CONF_ACK_POLICY_LEGACY, + .apsd_conf = {0, 0}, + }, + [CONF_TX_AC_VI] = { + .queue_id = CONF_TX_AC_VI, + .channel_type = CONF_CHANNEL_TYPE_EDCF, + .tsid = CONF_TX_AC_VI, + .ps_scheme = CONF_PS_SCHEME_LEGACY, + .ack_policy = CONF_ACK_POLICY_LEGACY, + .apsd_conf = {0, 0}, + }, + [CONF_TX_AC_VO] = { + .queue_id = CONF_TX_AC_VO, + .channel_type = CONF_CHANNEL_TYPE_EDCF, + .tsid = CONF_TX_AC_VO, + .ps_scheme = CONF_PS_SCHEME_LEGACY, + .ack_policy = CONF_ACK_POLICY_LEGACY, + .apsd_conf = {0, 0}, + }, + }, + .frag_threshold = IEEE80211_MAX_FRAG_THRESHOLD, + .tx_compl_timeout = 700, + .tx_compl_threshold = 4, + .basic_rate = CONF_HW_BIT_RATE_1MBPS, + .basic_rate_5 = CONF_HW_BIT_RATE_6MBPS, + .tmpl_short_retry_limit = 10, + .tmpl_long_retry_limit = 10, + .tx_watchdog_timeout = 5000, + .slow_link_thold = 3, + .fast_link_thold = 10, + }, + .conn = { + .wake_up_event = CONF_WAKE_UP_EVENT_DTIM, + .listen_interval = 1, + .suspend_wake_up_event = CONF_WAKE_UP_EVENT_N_DTIM, + .suspend_listen_interval = 3, + .bcn_filt_mode = CONF_BCN_FILT_MODE_ENABLED, + .bcn_filt_ie_count = 3, + .bcn_filt_ie = { + [0] = { + .ie = WLAN_EID_CHANNEL_SWITCH, + .rule = CONF_BCN_RULE_PASS_ON_APPEARANCE, + }, + [1] = { + .ie = WLAN_EID_HT_OPERATION, + .rule = CONF_BCN_RULE_PASS_ON_CHANGE, + }, + [2] = { + .ie = WLAN_EID_ERP_INFO, + .rule = CONF_BCN_RULE_PASS_ON_CHANGE, + }, + }, + .synch_fail_thold = 12, + .bss_lose_timeout = 400, + .beacon_rx_timeout = 10000, + .broadcast_timeout = 20000, + .rx_broadcast_in_ps = 1, + .ps_poll_threshold = 10, + .bet_enable = CONF_BET_MODE_ENABLE, + .bet_max_consecutive = 50, + .psm_entry_retries = 8, + .psm_exit_retries = 16, + .psm_entry_nullfunc_retries = 3, + .dynamic_ps_timeout = 1500, + .forced_ps = false, + .keep_alive_interval = 55000, + .max_listen_interval = 20, + .sta_sleep_auth = WL1271_PSM_ILLEGAL, + }, + .itrim = { + .enable = false, + .timeout = 50000, + }, + .pm_config = { + .host_clk_settling_time = 5000, + .host_fast_wakeup_support = CONF_FAST_WAKEUP_DISABLE, + }, + .roam_trigger = { + .trigger_pacing = 1, + .avg_weight_rssi_beacon = 20, + .avg_weight_rssi_data = 10, + .avg_weight_snr_beacon = 20, + .avg_weight_snr_data = 10, + }, + .scan = { + .min_dwell_time_active = 7500, + .max_dwell_time_active = 30000, + .min_dwell_time_active_long = 25000, + .max_dwell_time_active_long = 50000, + .dwell_time_passive = 100000, + .dwell_time_dfs = 150000, + .num_probe_reqs = 2, + .split_scan_timeout = 50000, + }, + .sched_scan = { + /* + * Values are in TU/1000 but since sched scan FW command + * params are in TUs rounding up may occur. + */ + .base_dwell_time = 7500, + .max_dwell_time_delta = 22500, + /* based on 250bits per probe @1Mbps */ + .dwell_time_delta_per_probe = 2000, + /* based on 250bits per probe @6Mbps (plus a bit more) */ + .dwell_time_delta_per_probe_5 = 350, + .dwell_time_passive = 100000, + .dwell_time_dfs = 150000, + .num_probe_reqs = 2, + .rssi_threshold = -90, + .snr_threshold = 0, + }, + .ht = { + .rx_ba_win_size = 8, + .tx_ba_win_size = 64, + .inactivity_timeout = 10000, + .tx_ba_tid_bitmap = CONF_TX_BA_ENABLED_TID_BITMAP, + }, + /* + * Memory config for wl127x chips is given in the + * wl12xx_default_priv_conf struct. The below configuration is + * for wl128x chips. + */ + .mem = { + .num_stations = 1, + .ssid_profiles = 1, + .rx_block_num = 40, + .tx_min_block_num = 40, + .dynamic_memory = 1, + .min_req_tx_blocks = 45, + .min_req_rx_blocks = 22, + .tx_min = 27, + }, + .fm_coex = { + .enable = true, + .swallow_period = 5, + .n_divider_fref_set_1 = 0xff, /* default */ + .n_divider_fref_set_2 = 12, + .m_divider_fref_set_1 = 0xffff, + .m_divider_fref_set_2 = 148, /* default */ + .coex_pll_stabilization_time = 0xffffffff, /* default */ + .ldo_stabilization_time = 0xffff, /* default */ + .fm_disturbed_band_margin = 0xff, /* default */ + .swallow_clk_diff = 0xff, /* default */ + }, + .rx_streaming = { + .duration = 150, + .queues = 0x1, + .interval = 20, + .always = 0, + }, + .fwlog = { + .mode = WL12XX_FWLOG_ON_DEMAND, + .mem_blocks = 2, + .severity = 0, + .timestamp = WL12XX_FWLOG_TIMESTAMP_DISABLED, + .output = WL12XX_FWLOG_OUTPUT_HOST, + .threshold = 0, + }, + .rate = { + .rate_retry_score = 32000, + .per_add = 8192, + .per_th1 = 2048, + .per_th2 = 4096, + .max_per = 8100, + .inverse_curiosity_factor = 5, + .tx_fail_low_th = 4, + .tx_fail_high_th = 10, + .per_alpha_shift = 4, + .per_add_shift = 13, + .per_beta1_shift = 10, + .per_beta2_shift = 8, + .rate_check_up = 2, + .rate_check_down = 12, + .rate_retry_policy = { + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + }, + }, + .hangover = { + .recover_time = 0, + .hangover_period = 20, + .dynamic_mode = 1, + .early_termination_mode = 1, + .max_period = 20, + .min_period = 1, + .increase_delta = 1, + .decrease_delta = 2, + .quiet_time = 4, + .increase_time = 1, + .window_size = 16, + }, + .recovery = { + .bug_on_recovery = 0, + .no_recovery = 0, + }, +}; + +static struct wl12xx_priv_conf wl12xx_default_priv_conf = { + .rf = { + .tx_per_channel_power_compensation_2 = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + .tx_per_channel_power_compensation_5 = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + }, + .mem_wl127x = { + .num_stations = 1, + .ssid_profiles = 1, + .rx_block_num = 70, + .tx_min_block_num = 40, + .dynamic_memory = 1, + .min_req_tx_blocks = 100, + .min_req_rx_blocks = 22, + .tx_min = 27, + }, + +}; + +#define WL12XX_TX_HW_BLOCK_SPARE_DEFAULT 1 +#define WL12XX_TX_HW_BLOCK_GEM_SPARE 2 +#define WL12XX_TX_HW_BLOCK_SIZE 252 + +static const u8 wl12xx_rate_to_idx_2ghz[] = { + /* MCS rates are used only with 11n */ + 7, /* WL12XX_CONF_HW_RXTX_RATE_MCS7_SGI */ + 7, /* WL12XX_CONF_HW_RXTX_RATE_MCS7 */ + 6, /* WL12XX_CONF_HW_RXTX_RATE_MCS6 */ + 5, /* WL12XX_CONF_HW_RXTX_RATE_MCS5 */ + 4, /* WL12XX_CONF_HW_RXTX_RATE_MCS4 */ + 3, /* WL12XX_CONF_HW_RXTX_RATE_MCS3 */ + 2, /* WL12XX_CONF_HW_RXTX_RATE_MCS2 */ + 1, /* WL12XX_CONF_HW_RXTX_RATE_MCS1 */ + 0, /* WL12XX_CONF_HW_RXTX_RATE_MCS0 */ + + 11, /* WL12XX_CONF_HW_RXTX_RATE_54 */ + 10, /* WL12XX_CONF_HW_RXTX_RATE_48 */ + 9, /* WL12XX_CONF_HW_RXTX_RATE_36 */ + 8, /* WL12XX_CONF_HW_RXTX_RATE_24 */ + + /* TI-specific rate */ + CONF_HW_RXTX_RATE_UNSUPPORTED, /* WL12XX_CONF_HW_RXTX_RATE_22 */ + + 7, /* WL12XX_CONF_HW_RXTX_RATE_18 */ + 6, /* WL12XX_CONF_HW_RXTX_RATE_12 */ + 3, /* WL12XX_CONF_HW_RXTX_RATE_11 */ + 5, /* WL12XX_CONF_HW_RXTX_RATE_9 */ + 4, /* WL12XX_CONF_HW_RXTX_RATE_6 */ + 2, /* WL12XX_CONF_HW_RXTX_RATE_5_5 */ + 1, /* WL12XX_CONF_HW_RXTX_RATE_2 */ + 0 /* WL12XX_CONF_HW_RXTX_RATE_1 */ +}; + +static const u8 wl12xx_rate_to_idx_5ghz[] = { + /* MCS rates are used only with 11n */ + 7, /* WL12XX_CONF_HW_RXTX_RATE_MCS7_SGI */ + 7, /* WL12XX_CONF_HW_RXTX_RATE_MCS7 */ + 6, /* WL12XX_CONF_HW_RXTX_RATE_MCS6 */ + 5, /* WL12XX_CONF_HW_RXTX_RATE_MCS5 */ + 4, /* WL12XX_CONF_HW_RXTX_RATE_MCS4 */ + 3, /* WL12XX_CONF_HW_RXTX_RATE_MCS3 */ + 2, /* WL12XX_CONF_HW_RXTX_RATE_MCS2 */ + 1, /* WL12XX_CONF_HW_RXTX_RATE_MCS1 */ + 0, /* WL12XX_CONF_HW_RXTX_RATE_MCS0 */ + + 7, /* WL12XX_CONF_HW_RXTX_RATE_54 */ + 6, /* WL12XX_CONF_HW_RXTX_RATE_48 */ + 5, /* WL12XX_CONF_HW_RXTX_RATE_36 */ + 4, /* WL12XX_CONF_HW_RXTX_RATE_24 */ + + /* TI-specific rate */ + CONF_HW_RXTX_RATE_UNSUPPORTED, /* WL12XX_CONF_HW_RXTX_RATE_22 */ + + 3, /* WL12XX_CONF_HW_RXTX_RATE_18 */ + 2, /* WL12XX_CONF_HW_RXTX_RATE_12 */ + CONF_HW_RXTX_RATE_UNSUPPORTED, /* WL12XX_CONF_HW_RXTX_RATE_11 */ + 1, /* WL12XX_CONF_HW_RXTX_RATE_9 */ + 0, /* WL12XX_CONF_HW_RXTX_RATE_6 */ + CONF_HW_RXTX_RATE_UNSUPPORTED, /* WL12XX_CONF_HW_RXTX_RATE_5_5 */ + CONF_HW_RXTX_RATE_UNSUPPORTED, /* WL12XX_CONF_HW_RXTX_RATE_2 */ + CONF_HW_RXTX_RATE_UNSUPPORTED /* WL12XX_CONF_HW_RXTX_RATE_1 */ +}; + +static const u8 *wl12xx_band_rate_to_idx[] = { + [IEEE80211_BAND_2GHZ] = wl12xx_rate_to_idx_2ghz, + [IEEE80211_BAND_5GHZ] = wl12xx_rate_to_idx_5ghz +}; + +enum wl12xx_hw_rates { + WL12XX_CONF_HW_RXTX_RATE_MCS7_SGI = 0, + WL12XX_CONF_HW_RXTX_RATE_MCS7, + WL12XX_CONF_HW_RXTX_RATE_MCS6, + WL12XX_CONF_HW_RXTX_RATE_MCS5, + WL12XX_CONF_HW_RXTX_RATE_MCS4, + WL12XX_CONF_HW_RXTX_RATE_MCS3, + WL12XX_CONF_HW_RXTX_RATE_MCS2, + WL12XX_CONF_HW_RXTX_RATE_MCS1, + WL12XX_CONF_HW_RXTX_RATE_MCS0, + WL12XX_CONF_HW_RXTX_RATE_54, + WL12XX_CONF_HW_RXTX_RATE_48, + WL12XX_CONF_HW_RXTX_RATE_36, + WL12XX_CONF_HW_RXTX_RATE_24, + WL12XX_CONF_HW_RXTX_RATE_22, + WL12XX_CONF_HW_RXTX_RATE_18, + WL12XX_CONF_HW_RXTX_RATE_12, + WL12XX_CONF_HW_RXTX_RATE_11, + WL12XX_CONF_HW_RXTX_RATE_9, + WL12XX_CONF_HW_RXTX_RATE_6, + WL12XX_CONF_HW_RXTX_RATE_5_5, + WL12XX_CONF_HW_RXTX_RATE_2, + WL12XX_CONF_HW_RXTX_RATE_1, + WL12XX_CONF_HW_RXTX_RATE_MAX, +}; + +static struct wlcore_partition_set wl12xx_ptable[PART_TABLE_LEN] = { + [PART_DOWN] = { + .mem = { + .start = 0x00000000, + .size = 0x000177c0 + }, + .reg = { + .start = REGISTERS_BASE, + .size = 0x00008800 + }, + .mem2 = { + .start = 0x00000000, + .size = 0x00000000 + }, + .mem3 = { + .start = 0x00000000, + .size = 0x00000000 + }, + }, + + [PART_BOOT] = { /* in wl12xx we can use a mix of work and down + * partition here */ + .mem = { + .start = 0x00040000, + .size = 0x00014fc0 + }, + .reg = { + .start = REGISTERS_BASE, + .size = 0x00008800 + }, + .mem2 = { + .start = 0x00000000, + .size = 0x00000000 + }, + .mem3 = { + .start = 0x00000000, + .size = 0x00000000 + }, + }, + + [PART_WORK] = { + .mem = { + .start = 0x00040000, + .size = 0x00014fc0 + }, + .reg = { + .start = REGISTERS_BASE, + .size = 0x0000a000 + }, + .mem2 = { + .start = 0x003004f8, + .size = 0x00000004 + }, + .mem3 = { + .start = 0x00040404, + .size = 0x00000000 + }, + }, + + [PART_DRPW] = { + .mem = { + .start = 0x00040000, + .size = 0x00014fc0 + }, + .reg = { + .start = DRPW_BASE, + .size = 0x00006000 + }, + .mem2 = { + .start = 0x00000000, + .size = 0x00000000 + }, + .mem3 = { + .start = 0x00000000, + .size = 0x00000000 + } + } +}; + +static const int wl12xx_rtable[REG_TABLE_LEN] = { + [REG_ECPU_CONTROL] = WL12XX_REG_ECPU_CONTROL, + [REG_INTERRUPT_NO_CLEAR] = WL12XX_REG_INTERRUPT_NO_CLEAR, + [REG_INTERRUPT_ACK] = WL12XX_REG_INTERRUPT_ACK, + [REG_COMMAND_MAILBOX_PTR] = WL12XX_REG_COMMAND_MAILBOX_PTR, + [REG_EVENT_MAILBOX_PTR] = WL12XX_REG_EVENT_MAILBOX_PTR, + [REG_INTERRUPT_TRIG] = WL12XX_REG_INTERRUPT_TRIG, + [REG_INTERRUPT_MASK] = WL12XX_REG_INTERRUPT_MASK, + [REG_PC_ON_RECOVERY] = WL12XX_SCR_PAD4, + [REG_CHIP_ID_B] = WL12XX_CHIP_ID_B, + [REG_CMD_MBOX_ADDRESS] = WL12XX_CMD_MBOX_ADDRESS, + + /* data access memory addresses, used with partition translation */ + [REG_SLV_MEM_DATA] = WL1271_SLV_MEM_DATA, + [REG_SLV_REG_DATA] = WL1271_SLV_REG_DATA, + + /* raw data access memory addresses */ + [REG_RAW_FW_STATUS_ADDR] = FW_STATUS_ADDR, +}; + +/* TODO: maybe move to a new header file? */ +#define WL127X_FW_NAME_MULTI "ti-connectivity/wl127x-fw-5-mr.bin" +#define WL127X_FW_NAME_SINGLE "ti-connectivity/wl127x-fw-5-sr.bin" +#define WL127X_PLT_FW_NAME "ti-connectivity/wl127x-fw-5-plt.bin" + +#define WL128X_FW_NAME_MULTI "ti-connectivity/wl128x-fw-5-mr.bin" +#define WL128X_FW_NAME_SINGLE "ti-connectivity/wl128x-fw-5-sr.bin" +#define WL128X_PLT_FW_NAME "ti-connectivity/wl128x-fw-5-plt.bin" + +static int wl127x_prepare_read(struct wl1271 *wl, u32 rx_desc, u32 len) +{ + int ret; + + if (wl->chip.id != CHIP_ID_128X_PG20) { + struct wl1271_acx_mem_map *wl_mem_map = wl->target_mem_map; + struct wl12xx_priv *priv = wl->priv; + + /* + * Choose the block we want to read + * For aggregated packets, only the first memory block + * should be retrieved. The FW takes care of the rest. + */ + u32 mem_block = rx_desc & RX_MEM_BLOCK_MASK; + + priv->rx_mem_addr->addr = (mem_block << 8) + + le32_to_cpu(wl_mem_map->packet_memory_pool_start); + + priv->rx_mem_addr->addr_extra = priv->rx_mem_addr->addr + 4; + + ret = wlcore_write(wl, WL1271_SLV_REG_DATA, priv->rx_mem_addr, + sizeof(*priv->rx_mem_addr), false); + if (ret < 0) + return ret; + } + + return 0; +} + +static int wl12xx_identify_chip(struct wl1271 *wl) +{ + int ret = 0; + + switch (wl->chip.id) { + case CHIP_ID_127X_PG10: + wl1271_warning("chip id 0x%x (1271 PG10) support is obsolete", + wl->chip.id); + + wl->quirks |= WLCORE_QUIRK_LEGACY_NVS | + WLCORE_QUIRK_DUAL_PROBE_TMPL | + WLCORE_QUIRK_TKIP_HEADER_SPACE | + WLCORE_QUIRK_START_STA_FAILS | + WLCORE_QUIRK_AP_ZERO_SESSION_ID; + wl->sr_fw_name = WL127X_FW_NAME_SINGLE; + wl->mr_fw_name = WL127X_FW_NAME_MULTI; + memcpy(&wl->conf.mem, &wl12xx_default_priv_conf.mem_wl127x, + sizeof(wl->conf.mem)); + + /* read data preparation is only needed by wl127x */ + wl->ops->prepare_read = wl127x_prepare_read; + + wlcore_set_min_fw_ver(wl, WL127X_CHIP_VER, + WL127X_IFTYPE_SR_VER, WL127X_MAJOR_SR_VER, + WL127X_SUBTYPE_SR_VER, WL127X_MINOR_SR_VER, + WL127X_IFTYPE_MR_VER, WL127X_MAJOR_MR_VER, + WL127X_SUBTYPE_MR_VER, WL127X_MINOR_MR_VER); + break; + + case CHIP_ID_127X_PG20: + wl1271_debug(DEBUG_BOOT, "chip id 0x%x (1271 PG20)", + wl->chip.id); + + wl->quirks |= WLCORE_QUIRK_LEGACY_NVS | + WLCORE_QUIRK_DUAL_PROBE_TMPL | + WLCORE_QUIRK_TKIP_HEADER_SPACE | + WLCORE_QUIRK_START_STA_FAILS | + WLCORE_QUIRK_AP_ZERO_SESSION_ID; + wl->plt_fw_name = WL127X_PLT_FW_NAME; + wl->sr_fw_name = WL127X_FW_NAME_SINGLE; + wl->mr_fw_name = WL127X_FW_NAME_MULTI; + memcpy(&wl->conf.mem, &wl12xx_default_priv_conf.mem_wl127x, + sizeof(wl->conf.mem)); + + /* read data preparation is only needed by wl127x */ + wl->ops->prepare_read = wl127x_prepare_read; + + wlcore_set_min_fw_ver(wl, WL127X_CHIP_VER, + WL127X_IFTYPE_SR_VER, WL127X_MAJOR_SR_VER, + WL127X_SUBTYPE_SR_VER, WL127X_MINOR_SR_VER, + WL127X_IFTYPE_MR_VER, WL127X_MAJOR_MR_VER, + WL127X_SUBTYPE_MR_VER, WL127X_MINOR_MR_VER); + break; + + case CHIP_ID_128X_PG20: + wl1271_debug(DEBUG_BOOT, "chip id 0x%x (1283 PG20)", + wl->chip.id); + wl->plt_fw_name = WL128X_PLT_FW_NAME; + wl->sr_fw_name = WL128X_FW_NAME_SINGLE; + wl->mr_fw_name = WL128X_FW_NAME_MULTI; + + /* wl128x requires TX blocksize alignment */ + wl->quirks |= WLCORE_QUIRK_TX_BLOCKSIZE_ALIGN | + WLCORE_QUIRK_DUAL_PROBE_TMPL | + WLCORE_QUIRK_TKIP_HEADER_SPACE | + WLCORE_QUIRK_START_STA_FAILS | + WLCORE_QUIRK_AP_ZERO_SESSION_ID; + + wlcore_set_min_fw_ver(wl, WL128X_CHIP_VER, + WL128X_IFTYPE_SR_VER, WL128X_MAJOR_SR_VER, + WL128X_SUBTYPE_SR_VER, WL128X_MINOR_SR_VER, + WL128X_IFTYPE_MR_VER, WL128X_MAJOR_MR_VER, + WL128X_SUBTYPE_MR_VER, WL128X_MINOR_MR_VER); + break; + case CHIP_ID_128X_PG10: + default: + wl1271_warning("unsupported chip id: 0x%x", wl->chip.id); + ret = -ENODEV; + goto out; + } + + /* common settings */ + wl->scan_templ_id_2_4 = CMD_TEMPL_APP_PROBE_REQ_2_4_LEGACY; + wl->scan_templ_id_5 = CMD_TEMPL_APP_PROBE_REQ_5_LEGACY; + wl->sched_scan_templ_id_2_4 = CMD_TEMPL_CFG_PROBE_REQ_2_4; + wl->sched_scan_templ_id_5 = CMD_TEMPL_CFG_PROBE_REQ_5; + wl->max_channels_5 = WL12XX_MAX_CHANNELS_5GHZ; + wl->ba_rx_session_count_max = WL12XX_RX_BA_MAX_SESSIONS; +out: + return ret; +} + +static int __must_check wl12xx_top_reg_write(struct wl1271 *wl, int addr, + u16 val) +{ + int ret; + + /* write address >> 1 + 0x30000 to OCP_POR_CTR */ + addr = (addr >> 1) + 0x30000; + ret = wlcore_write32(wl, WL12XX_OCP_POR_CTR, addr); + if (ret < 0) + goto out; + + /* write value to OCP_POR_WDATA */ + ret = wlcore_write32(wl, WL12XX_OCP_DATA_WRITE, val); + if (ret < 0) + goto out; + + /* write 1 to OCP_CMD */ + ret = wlcore_write32(wl, WL12XX_OCP_CMD, OCP_CMD_WRITE); + if (ret < 0) + goto out; + +out: + return ret; +} + +static int __must_check wl12xx_top_reg_read(struct wl1271 *wl, int addr, + u16 *out) +{ + u32 val; + int timeout = OCP_CMD_LOOP; + int ret; + + /* write address >> 1 + 0x30000 to OCP_POR_CTR */ + addr = (addr >> 1) + 0x30000; + ret = wlcore_write32(wl, WL12XX_OCP_POR_CTR, addr); + if (ret < 0) + return ret; + + /* write 2 to OCP_CMD */ + ret = wlcore_write32(wl, WL12XX_OCP_CMD, OCP_CMD_READ); + if (ret < 0) + return ret; + + /* poll for data ready */ + do { + ret = wlcore_read32(wl, WL12XX_OCP_DATA_READ, &val); + if (ret < 0) + return ret; + } while (!(val & OCP_READY_MASK) && --timeout); + + if (!timeout) { + wl1271_warning("Top register access timed out."); + return -ETIMEDOUT; + } + + /* check data status and return if OK */ + if ((val & OCP_STATUS_MASK) != OCP_STATUS_OK) { + wl1271_warning("Top register access returned error."); + return -EIO; + } + + if (out) + *out = val & 0xffff; + + return 0; +} + +static int wl128x_switch_tcxo_to_fref(struct wl1271 *wl) +{ + u16 spare_reg; + int ret; + + /* Mask bits [2] & [8:4] in the sys_clk_cfg register */ + ret = wl12xx_top_reg_read(wl, WL_SPARE_REG, &spare_reg); + if (ret < 0) + return ret; + + if (spare_reg == 0xFFFF) + return -EFAULT; + spare_reg |= (BIT(3) | BIT(5) | BIT(6)); + ret = wl12xx_top_reg_write(wl, WL_SPARE_REG, spare_reg); + if (ret < 0) + return ret; + + /* Enable FREF_CLK_REQ & mux MCS and coex PLLs to FREF */ + ret = wl12xx_top_reg_write(wl, SYS_CLK_CFG_REG, + WL_CLK_REQ_TYPE_PG2 | MCS_PLL_CLK_SEL_FREF); + if (ret < 0) + return ret; + + /* Delay execution for 15msec, to let the HW settle */ + mdelay(15); + + return 0; +} + +static bool wl128x_is_tcxo_valid(struct wl1271 *wl) +{ + u16 tcxo_detection; + int ret; + + ret = wl12xx_top_reg_read(wl, TCXO_CLK_DETECT_REG, &tcxo_detection); + if (ret < 0) + return false; + + if (tcxo_detection & TCXO_DET_FAILED) + return false; + + return true; +} + +static bool wl128x_is_fref_valid(struct wl1271 *wl) +{ + u16 fref_detection; + int ret; + + ret = wl12xx_top_reg_read(wl, FREF_CLK_DETECT_REG, &fref_detection); + if (ret < 0) + return false; + + if (fref_detection & FREF_CLK_DETECT_FAIL) + return false; + + return true; +} + +static int wl128x_manually_configure_mcs_pll(struct wl1271 *wl) +{ + int ret; + + ret = wl12xx_top_reg_write(wl, MCS_PLL_M_REG, MCS_PLL_M_REG_VAL); + if (ret < 0) + goto out; + + ret = wl12xx_top_reg_write(wl, MCS_PLL_N_REG, MCS_PLL_N_REG_VAL); + if (ret < 0) + goto out; + + ret = wl12xx_top_reg_write(wl, MCS_PLL_CONFIG_REG, + MCS_PLL_CONFIG_REG_VAL); + +out: + return ret; +} + +static int wl128x_configure_mcs_pll(struct wl1271 *wl, int clk) +{ + u16 spare_reg; + u16 pll_config; + u8 input_freq; + struct wl12xx_priv *priv = wl->priv; + int ret; + + /* Mask bits [3:1] in the sys_clk_cfg register */ + ret = wl12xx_top_reg_read(wl, WL_SPARE_REG, &spare_reg); + if (ret < 0) + return ret; + + if (spare_reg == 0xFFFF) + return -EFAULT; + spare_reg |= BIT(2); + ret = wl12xx_top_reg_write(wl, WL_SPARE_REG, spare_reg); + if (ret < 0) + return ret; + + /* Handle special cases of the TCXO clock */ + if (priv->tcxo_clock == WL12XX_TCXOCLOCK_16_8 || + priv->tcxo_clock == WL12XX_TCXOCLOCK_33_6) + return wl128x_manually_configure_mcs_pll(wl); + + /* Set the input frequency according to the selected clock source */ + input_freq = (clk & 1) + 1; + + ret = wl12xx_top_reg_read(wl, MCS_PLL_CONFIG_REG, &pll_config); + if (ret < 0) + return ret; + + if (pll_config == 0xFFFF) + return -EFAULT; + pll_config |= (input_freq << MCS_SEL_IN_FREQ_SHIFT); + pll_config |= MCS_PLL_ENABLE_HP; + ret = wl12xx_top_reg_write(wl, MCS_PLL_CONFIG_REG, pll_config); + + return ret; +} + +/* + * WL128x has two clocks input - TCXO and FREF. + * TCXO is the main clock of the device, while FREF is used to sync + * between the GPS and the cellular modem. + * In cases where TCXO is 32.736MHz or 16.368MHz, the FREF will be used + * as the WLAN/BT main clock. + */ +static int wl128x_boot_clk(struct wl1271 *wl, int *selected_clock) +{ + struct wl12xx_priv *priv = wl->priv; + u16 sys_clk_cfg; + int ret; + + /* For XTAL-only modes, FREF will be used after switching from TCXO */ + if (priv->ref_clock == WL12XX_REFCLOCK_26_XTAL || + priv->ref_clock == WL12XX_REFCLOCK_38_XTAL) { + if (!wl128x_switch_tcxo_to_fref(wl)) + return -EINVAL; + goto fref_clk; + } + + /* Query the HW, to determine which clock source we should use */ + ret = wl12xx_top_reg_read(wl, SYS_CLK_CFG_REG, &sys_clk_cfg); + if (ret < 0) + return ret; + + if (sys_clk_cfg == 0xFFFF) + return -EINVAL; + if (sys_clk_cfg & PRCM_CM_EN_MUX_WLAN_FREF) + goto fref_clk; + + /* If TCXO is either 32.736MHz or 16.368MHz, switch to FREF */ + if (priv->tcxo_clock == WL12XX_TCXOCLOCK_16_368 || + priv->tcxo_clock == WL12XX_TCXOCLOCK_32_736) { + if (!wl128x_switch_tcxo_to_fref(wl)) + return -EINVAL; + goto fref_clk; + } + + /* TCXO clock is selected */ + if (!wl128x_is_tcxo_valid(wl)) + return -EINVAL; + *selected_clock = priv->tcxo_clock; + goto config_mcs_pll; + +fref_clk: + /* FREF clock is selected */ + if (!wl128x_is_fref_valid(wl)) + return -EINVAL; + *selected_clock = priv->ref_clock; + +config_mcs_pll: + return wl128x_configure_mcs_pll(wl, *selected_clock); +} + +static int wl127x_boot_clk(struct wl1271 *wl) +{ + struct wl12xx_priv *priv = wl->priv; + u32 pause; + u32 clk; + int ret; + + if (WL127X_PG_GET_MAJOR(wl->hw_pg_ver) < 3) + wl->quirks |= WLCORE_QUIRK_END_OF_TRANSACTION; + + if (priv->ref_clock == CONF_REF_CLK_19_2_E || + priv->ref_clock == CONF_REF_CLK_38_4_E || + priv->ref_clock == CONF_REF_CLK_38_4_M_XTAL) + /* ref clk: 19.2/38.4/38.4-XTAL */ + clk = 0x3; + else if (priv->ref_clock == CONF_REF_CLK_26_E || + priv->ref_clock == CONF_REF_CLK_26_M_XTAL || + priv->ref_clock == CONF_REF_CLK_52_E) + /* ref clk: 26/52 */ + clk = 0x5; + else + return -EINVAL; + + if (priv->ref_clock != CONF_REF_CLK_19_2_E) { + u16 val; + /* Set clock type (open drain) */ + ret = wl12xx_top_reg_read(wl, OCP_REG_CLK_TYPE, &val); + if (ret < 0) + goto out; + + val &= FREF_CLK_TYPE_BITS; + ret = wl12xx_top_reg_write(wl, OCP_REG_CLK_TYPE, val); + if (ret < 0) + goto out; + + /* Set clock pull mode (no pull) */ + ret = wl12xx_top_reg_read(wl, OCP_REG_CLK_PULL, &val); + if (ret < 0) + goto out; + + val |= NO_PULL; + ret = wl12xx_top_reg_write(wl, OCP_REG_CLK_PULL, val); + if (ret < 0) + goto out; + } else { + u16 val; + /* Set clock polarity */ + ret = wl12xx_top_reg_read(wl, OCP_REG_CLK_POLARITY, &val); + if (ret < 0) + goto out; + + val &= FREF_CLK_POLARITY_BITS; + val |= CLK_REQ_OUTN_SEL; + ret = wl12xx_top_reg_write(wl, OCP_REG_CLK_POLARITY, val); + if (ret < 0) + goto out; + } + + ret = wlcore_write32(wl, WL12XX_PLL_PARAMETERS, clk); + if (ret < 0) + goto out; + + ret = wlcore_read32(wl, WL12XX_PLL_PARAMETERS, &pause); + if (ret < 0) + goto out; + + wl1271_debug(DEBUG_BOOT, "pause1 0x%x", pause); + + pause &= ~(WU_COUNTER_PAUSE_VAL); + pause |= WU_COUNTER_PAUSE_VAL; + ret = wlcore_write32(wl, WL12XX_WU_COUNTER_PAUSE, pause); + +out: + return ret; +} + +static int wl1271_boot_soft_reset(struct wl1271 *wl) +{ + unsigned long timeout; + u32 boot_data; + int ret = 0; + + /* perform soft reset */ + ret = wlcore_write32(wl, WL12XX_SLV_SOFT_RESET, ACX_SLV_SOFT_RESET_BIT); + if (ret < 0) + goto out; + + /* SOFT_RESET is self clearing */ + timeout = jiffies + usecs_to_jiffies(SOFT_RESET_MAX_TIME); + while (1) { + ret = wlcore_read32(wl, WL12XX_SLV_SOFT_RESET, &boot_data); + if (ret < 0) + goto out; + + wl1271_debug(DEBUG_BOOT, "soft reset bootdata 0x%x", boot_data); + if ((boot_data & ACX_SLV_SOFT_RESET_BIT) == 0) + break; + + if (time_after(jiffies, timeout)) { + /* 1.2 check pWhalBus->uSelfClearTime if the + * timeout was reached */ + wl1271_error("soft reset timeout"); + return -1; + } + + udelay(SOFT_RESET_STALL_TIME); + } + + /* disable Rx/Tx */ + ret = wlcore_write32(wl, WL12XX_ENABLE, 0x0); + if (ret < 0) + goto out; + + /* disable auto calibration on start*/ + ret = wlcore_write32(wl, WL12XX_SPARE_A2, 0xffff); + +out: + return ret; +} + +static int wl12xx_pre_boot(struct wl1271 *wl) +{ + struct wl12xx_priv *priv = wl->priv; + int ret = 0; + u32 clk; + int selected_clock = -1; + + if (wl->chip.id == CHIP_ID_128X_PG20) { + ret = wl128x_boot_clk(wl, &selected_clock); + if (ret < 0) + goto out; + } else { + ret = wl127x_boot_clk(wl); + if (ret < 0) + goto out; + } + + /* Continue the ELP wake up sequence */ + ret = wlcore_write32(wl, WL12XX_WELP_ARM_COMMAND, WELP_ARM_COMMAND_VAL); + if (ret < 0) + goto out; + + udelay(500); + + ret = wlcore_set_partition(wl, &wl->ptable[PART_DRPW]); + if (ret < 0) + goto out; + + /* Read-modify-write DRPW_SCRATCH_START register (see next state) + to be used by DRPw FW. The RTRIM value will be added by the FW + before taking DRPw out of reset */ + + ret = wlcore_read32(wl, WL12XX_DRPW_SCRATCH_START, &clk); + if (ret < 0) + goto out; + + wl1271_debug(DEBUG_BOOT, "clk2 0x%x", clk); + + if (wl->chip.id == CHIP_ID_128X_PG20) + clk |= ((selected_clock & 0x3) << 1) << 4; + else + clk |= (priv->ref_clock << 1) << 4; + + ret = wlcore_write32(wl, WL12XX_DRPW_SCRATCH_START, clk); + if (ret < 0) + goto out; + + ret = wlcore_set_partition(wl, &wl->ptable[PART_WORK]); + if (ret < 0) + goto out; + + /* Disable interrupts */ + ret = wlcore_write_reg(wl, REG_INTERRUPT_MASK, WL1271_ACX_INTR_ALL); + if (ret < 0) + goto out; + + ret = wl1271_boot_soft_reset(wl); + if (ret < 0) + goto out; + +out: + return ret; +} + +static int wl12xx_pre_upload(struct wl1271 *wl) +{ + u32 tmp; + u16 polarity; + int ret; + + /* write firmware's last address (ie. it's length) to + * ACX_EEPROMLESS_IND_REG */ + wl1271_debug(DEBUG_BOOT, "ACX_EEPROMLESS_IND_REG"); + + ret = wlcore_write32(wl, WL12XX_EEPROMLESS_IND, WL12XX_EEPROMLESS_IND); + if (ret < 0) + goto out; + + ret = wlcore_read_reg(wl, REG_CHIP_ID_B, &tmp); + if (ret < 0) + goto out; + + wl1271_debug(DEBUG_BOOT, "chip id 0x%x", tmp); + + /* 6. read the EEPROM parameters */ + ret = wlcore_read32(wl, WL12XX_SCR_PAD2, &tmp); + if (ret < 0) + goto out; + + /* WL1271: The reference driver skips steps 7 to 10 (jumps directly + * to upload_fw) */ + + if (wl->chip.id == CHIP_ID_128X_PG20) { + ret = wl12xx_top_reg_write(wl, SDIO_IO_DS, HCI_IO_DS_6MA); + if (ret < 0) + goto out; + } + + /* polarity must be set before the firmware is loaded */ + ret = wl12xx_top_reg_read(wl, OCP_REG_POLARITY, &polarity); + if (ret < 0) + goto out; + + /* We use HIGH polarity, so unset the LOW bit */ + polarity &= ~POLARITY_LOW; + ret = wl12xx_top_reg_write(wl, OCP_REG_POLARITY, polarity); + +out: + return ret; +} + +static int wl12xx_enable_interrupts(struct wl1271 *wl) +{ + int ret; + + ret = wlcore_write_reg(wl, REG_INTERRUPT_MASK, + WL12XX_ACX_ALL_EVENTS_VECTOR); + if (ret < 0) + goto out; + + wlcore_enable_interrupts(wl); + ret = wlcore_write_reg(wl, REG_INTERRUPT_MASK, + WL1271_ACX_INTR_ALL & ~(WL12XX_INTR_MASK)); + if (ret < 0) + goto disable_interrupts; + + ret = wlcore_write32(wl, WL12XX_HI_CFG, HI_CFG_DEF_VAL); + if (ret < 0) + goto disable_interrupts; + + return ret; + +disable_interrupts: + wlcore_disable_interrupts(wl); + +out: + return ret; +} + +static int wl12xx_boot(struct wl1271 *wl) +{ + int ret; + + ret = wl12xx_pre_boot(wl); + if (ret < 0) + goto out; + + ret = wlcore_boot_upload_nvs(wl); + if (ret < 0) + goto out; + + ret = wl12xx_pre_upload(wl); + if (ret < 0) + goto out; + + ret = wlcore_boot_upload_firmware(wl); + if (ret < 0) + goto out; + + wl->event_mask = BSS_LOSE_EVENT_ID | + REGAINED_BSS_EVENT_ID | + SCAN_COMPLETE_EVENT_ID | + ROLE_STOP_COMPLETE_EVENT_ID | + RSSI_SNR_TRIGGER_0_EVENT_ID | + PSPOLL_DELIVERY_FAILURE_EVENT_ID | + SOFT_GEMINI_SENSE_EVENT_ID | + PERIODIC_SCAN_REPORT_EVENT_ID | + PERIODIC_SCAN_COMPLETE_EVENT_ID | + DUMMY_PACKET_EVENT_ID | + PEER_REMOVE_COMPLETE_EVENT_ID | + BA_SESSION_RX_CONSTRAINT_EVENT_ID | + REMAIN_ON_CHANNEL_COMPLETE_EVENT_ID | + INACTIVE_STA_EVENT_ID | + MAX_TX_RETRY_EVENT_ID | + CHANNEL_SWITCH_COMPLETE_EVENT_ID; + + ret = wlcore_boot_run_firmware(wl); + if (ret < 0) + goto out; + + ret = wl12xx_enable_interrupts(wl); + +out: + return ret; +} + +static int wl12xx_trigger_cmd(struct wl1271 *wl, int cmd_box_addr, + void *buf, size_t len) +{ + int ret; + + ret = wlcore_write(wl, cmd_box_addr, buf, len, false); + if (ret < 0) + return ret; + + ret = wlcore_write_reg(wl, REG_INTERRUPT_TRIG, WL12XX_INTR_TRIG_CMD); + + return ret; +} + +static int wl12xx_ack_event(struct wl1271 *wl) +{ + return wlcore_write_reg(wl, REG_INTERRUPT_TRIG, + WL12XX_INTR_TRIG_EVENT_ACK); +} + +static u32 wl12xx_calc_tx_blocks(struct wl1271 *wl, u32 len, u32 spare_blks) +{ + u32 blk_size = WL12XX_TX_HW_BLOCK_SIZE; + u32 align_len = wlcore_calc_packet_alignment(wl, len); + + return (align_len + blk_size - 1) / blk_size + spare_blks; +} + +static void +wl12xx_set_tx_desc_blocks(struct wl1271 *wl, struct wl1271_tx_hw_descr *desc, + u32 blks, u32 spare_blks) +{ + if (wl->chip.id == CHIP_ID_128X_PG20) { + desc->wl128x_mem.total_mem_blocks = blks; + } else { + desc->wl127x_mem.extra_blocks = spare_blks; + desc->wl127x_mem.total_mem_blocks = blks; + } +} + +static void +wl12xx_set_tx_desc_data_len(struct wl1271 *wl, struct wl1271_tx_hw_descr *desc, + struct sk_buff *skb) +{ + u32 aligned_len = wlcore_calc_packet_alignment(wl, skb->len); + + if (wl->chip.id == CHIP_ID_128X_PG20) { + desc->wl128x_mem.extra_bytes = aligned_len - skb->len; + desc->length = cpu_to_le16(aligned_len >> 2); + + wl1271_debug(DEBUG_TX, + "tx_fill_hdr: hlid: %d len: %d life: %d mem: %d extra: %d", + desc->hlid, + le16_to_cpu(desc->length), + le16_to_cpu(desc->life_time), + desc->wl128x_mem.total_mem_blocks, + desc->wl128x_mem.extra_bytes); + } else { + /* calculate number of padding bytes */ + int pad = aligned_len - skb->len; + desc->tx_attr |= + cpu_to_le16(pad << TX_HW_ATTR_OFST_LAST_WORD_PAD); + + /* Store the aligned length in terms of words */ + desc->length = cpu_to_le16(aligned_len >> 2); + + wl1271_debug(DEBUG_TX, + "tx_fill_hdr: pad: %d hlid: %d len: %d life: %d mem: %d", + pad, desc->hlid, + le16_to_cpu(desc->length), + le16_to_cpu(desc->life_time), + desc->wl127x_mem.total_mem_blocks); + } +} + +static enum wl_rx_buf_align +wl12xx_get_rx_buf_align(struct wl1271 *wl, u32 rx_desc) +{ + if (rx_desc & RX_BUF_UNALIGNED_PAYLOAD) + return WLCORE_RX_BUF_UNALIGNED; + + return WLCORE_RX_BUF_ALIGNED; +} + +static u32 wl12xx_get_rx_packet_len(struct wl1271 *wl, void *rx_data, + u32 data_len) +{ + struct wl1271_rx_descriptor *desc = rx_data; + + /* invalid packet */ + if (data_len < sizeof(*desc) || + data_len < sizeof(*desc) + desc->pad_len) + return 0; + + return data_len - sizeof(*desc) - desc->pad_len; +} + +static int wl12xx_tx_delayed_compl(struct wl1271 *wl) +{ + if (wl->fw_status_1->tx_results_counter == + (wl->tx_results_count & 0xff)) + return 0; + + return wlcore_tx_complete(wl); +} + +static int wl12xx_hw_init(struct wl1271 *wl) +{ + int ret; + + if (wl->chip.id == CHIP_ID_128X_PG20) { + u32 host_cfg_bitmap = HOST_IF_CFG_RX_FIFO_ENABLE; + + ret = wl128x_cmd_general_parms(wl); + if (ret < 0) + goto out; + + /* + * If we are in calibrator based auto detect then we got the FEM nr + * in wl->fem_manuf. No need to continue further + */ + if (wl->plt_mode == PLT_FEM_DETECT) + goto out; + + ret = wl128x_cmd_radio_parms(wl); + if (ret < 0) + goto out; + + if (wl->quirks & WLCORE_QUIRK_TX_BLOCKSIZE_ALIGN) + /* Enable SDIO padding */ + host_cfg_bitmap |= HOST_IF_CFG_TX_PAD_TO_SDIO_BLK; + + /* Must be before wl1271_acx_init_mem_config() */ + ret = wl1271_acx_host_if_cfg_bitmap(wl, host_cfg_bitmap); + if (ret < 0) + goto out; + } else { + ret = wl1271_cmd_general_parms(wl); + if (ret < 0) + goto out; + + /* + * If we are in calibrator based auto detect then we got the FEM nr + * in wl->fem_manuf. No need to continue further + */ + if (wl->plt_mode == PLT_FEM_DETECT) + goto out; + + ret = wl1271_cmd_radio_parms(wl); + if (ret < 0) + goto out; + ret = wl1271_cmd_ext_radio_parms(wl); + if (ret < 0) + goto out; + } +out: + return ret; +} + +static u32 wl12xx_sta_get_ap_rate_mask(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + return wlvif->rate_set; +} + +static void wl12xx_conf_init(struct wl1271 *wl) +{ + struct wl12xx_priv *priv = wl->priv; + + /* apply driver default configuration */ + memcpy(&wl->conf, &wl12xx_conf, sizeof(wl12xx_conf)); + + /* apply default private configuration */ + memcpy(&priv->conf, &wl12xx_default_priv_conf, sizeof(priv->conf)); +} + +static bool wl12xx_mac_in_fuse(struct wl1271 *wl) +{ + bool supported = false; + u8 major, minor; + + if (wl->chip.id == CHIP_ID_128X_PG20) { + major = WL128X_PG_GET_MAJOR(wl->hw_pg_ver); + minor = WL128X_PG_GET_MINOR(wl->hw_pg_ver); + + /* in wl128x we have the MAC address if the PG is >= (2, 1) */ + if (major > 2 || (major == 2 && minor >= 1)) + supported = true; + } else { + major = WL127X_PG_GET_MAJOR(wl->hw_pg_ver); + minor = WL127X_PG_GET_MINOR(wl->hw_pg_ver); + + /* in wl127x we have the MAC address if the PG is >= (3, 1) */ + if (major == 3 && minor >= 1) + supported = true; + } + + wl1271_debug(DEBUG_PROBE, + "PG Ver major = %d minor = %d, MAC %s present", + major, minor, supported ? "is" : "is not"); + + return supported; +} + +static int wl12xx_get_fuse_mac(struct wl1271 *wl) +{ + u32 mac1, mac2; + int ret; + + ret = wlcore_set_partition(wl, &wl->ptable[PART_DRPW]); + if (ret < 0) + goto out; + + ret = wlcore_read32(wl, WL12XX_REG_FUSE_BD_ADDR_1, &mac1); + if (ret < 0) + goto out; + + ret = wlcore_read32(wl, WL12XX_REG_FUSE_BD_ADDR_2, &mac2); + if (ret < 0) + goto out; + + /* these are the two parts of the BD_ADDR */ + wl->fuse_oui_addr = ((mac2 & 0xffff) << 8) + + ((mac1 & 0xff000000) >> 24); + wl->fuse_nic_addr = mac1 & 0xffffff; + + ret = wlcore_set_partition(wl, &wl->ptable[PART_DOWN]); + +out: + return ret; +} + +static int wl12xx_get_pg_ver(struct wl1271 *wl, s8 *ver) +{ + u16 die_info; + int ret; + + if (wl->chip.id == CHIP_ID_128X_PG20) + ret = wl12xx_top_reg_read(wl, WL128X_REG_FUSE_DATA_2_1, + &die_info); + else + ret = wl12xx_top_reg_read(wl, WL127X_REG_FUSE_DATA_2_1, + &die_info); + + if (ret >= 0 && ver) + *ver = (s8)((die_info & PG_VER_MASK) >> PG_VER_OFFSET); + + return ret; +} + +static int wl12xx_get_mac(struct wl1271 *wl) +{ + if (wl12xx_mac_in_fuse(wl)) + return wl12xx_get_fuse_mac(wl); + + return 0; +} + +static void wl12xx_set_tx_desc_csum(struct wl1271 *wl, + struct wl1271_tx_hw_descr *desc, + struct sk_buff *skb) +{ + desc->wl12xx_reserved = 0; +} + +static int wl12xx_plt_init(struct wl1271 *wl) +{ + int ret; + + ret = wl->ops->boot(wl); + if (ret < 0) + goto out; + + ret = wl->ops->hw_init(wl); + if (ret < 0) + goto out_irq_disable; + + /* + * If we are in calibrator based auto detect then we got the FEM nr + * in wl->fem_manuf. No need to continue further + */ + if (wl->plt_mode == PLT_FEM_DETECT) + goto out; + + ret = wl1271_acx_init_mem_config(wl); + if (ret < 0) + goto out_irq_disable; + + ret = wl12xx_acx_mem_cfg(wl); + if (ret < 0) + goto out_free_memmap; + + /* Enable data path */ + ret = wl1271_cmd_data_path(wl, 1); + if (ret < 0) + goto out_free_memmap; + + /* Configure for CAM power saving (ie. always active) */ + ret = wl1271_acx_sleep_auth(wl, WL1271_PSM_CAM); + if (ret < 0) + goto out_free_memmap; + + /* configure PM */ + ret = wl1271_acx_pm_config(wl); + if (ret < 0) + goto out_free_memmap; + + goto out; + +out_free_memmap: + kfree(wl->target_mem_map); + wl->target_mem_map = NULL; + +out_irq_disable: + mutex_unlock(&wl->mutex); + /* Unlocking the mutex in the middle of handling is + inherently unsafe. In this case we deem it safe to do, + because we need to let any possibly pending IRQ out of + the system (and while we are WL1271_STATE_OFF the IRQ + work function will not do anything.) Also, any other + possible concurrent operations will fail due to the + current state, hence the wl1271 struct should be safe. */ + wlcore_disable_interrupts(wl); + mutex_lock(&wl->mutex); +out: + return ret; +} + +static int wl12xx_get_spare_blocks(struct wl1271 *wl, bool is_gem) +{ + if (is_gem) + return WL12XX_TX_HW_BLOCK_GEM_SPARE; + + return WL12XX_TX_HW_BLOCK_SPARE_DEFAULT; +} + +static int wl12xx_set_key(struct wl1271 *wl, enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key_conf) +{ + return wlcore_set_key(wl, cmd, vif, sta, key_conf); +} + +static int wl12xx_set_peer_cap(struct wl1271 *wl, + struct ieee80211_sta_ht_cap *ht_cap, + bool allow_ht_operation, + u32 rate_set, u8 hlid) +{ + return wl1271_acx_set_ht_capabilities(wl, ht_cap, allow_ht_operation, + hlid); +} + +static bool wl12xx_lnk_high_prio(struct wl1271 *wl, u8 hlid, + struct wl1271_link *lnk) +{ + u8 thold; + + if (test_bit(hlid, (unsigned long *)&wl->fw_fast_lnk_map)) + thold = wl->conf.tx.fast_link_thold; + else + thold = wl->conf.tx.slow_link_thold; + + return lnk->allocated_pkts < thold; +} + +static bool wl12xx_lnk_low_prio(struct wl1271 *wl, u8 hlid, + struct wl1271_link *lnk) +{ + /* any link is good for low priority */ + return true; +} + +static int wl12xx_setup(struct wl1271 *wl); + +static struct wlcore_ops wl12xx_ops = { + .setup = wl12xx_setup, + .identify_chip = wl12xx_identify_chip, + .boot = wl12xx_boot, + .plt_init = wl12xx_plt_init, + .trigger_cmd = wl12xx_trigger_cmd, + .ack_event = wl12xx_ack_event, + .wait_for_event = wl12xx_wait_for_event, + .process_mailbox_events = wl12xx_process_mailbox_events, + .calc_tx_blocks = wl12xx_calc_tx_blocks, + .set_tx_desc_blocks = wl12xx_set_tx_desc_blocks, + .set_tx_desc_data_len = wl12xx_set_tx_desc_data_len, + .get_rx_buf_align = wl12xx_get_rx_buf_align, + .get_rx_packet_len = wl12xx_get_rx_packet_len, + .tx_immediate_compl = NULL, + .tx_delayed_compl = wl12xx_tx_delayed_compl, + .hw_init = wl12xx_hw_init, + .init_vif = NULL, + .sta_get_ap_rate_mask = wl12xx_sta_get_ap_rate_mask, + .get_pg_ver = wl12xx_get_pg_ver, + .get_mac = wl12xx_get_mac, + .set_tx_desc_csum = wl12xx_set_tx_desc_csum, + .set_rx_csum = NULL, + .ap_get_mimo_wide_rate_mask = NULL, + .debugfs_init = wl12xx_debugfs_add_files, + .scan_start = wl12xx_scan_start, + .scan_stop = wl12xx_scan_stop, + .sched_scan_start = wl12xx_sched_scan_start, + .sched_scan_stop = wl12xx_scan_sched_scan_stop, + .get_spare_blocks = wl12xx_get_spare_blocks, + .set_key = wl12xx_set_key, + .channel_switch = wl12xx_cmd_channel_switch, + .pre_pkt_send = NULL, + .set_peer_cap = wl12xx_set_peer_cap, + .lnk_high_prio = wl12xx_lnk_high_prio, + .lnk_low_prio = wl12xx_lnk_low_prio, +}; + +static struct ieee80211_sta_ht_cap wl12xx_ht_cap = { + .cap = IEEE80211_HT_CAP_GRN_FLD | IEEE80211_HT_CAP_SGI_20 | + (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT), + .ht_supported = true, + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_8K, + .ampdu_density = IEEE80211_HT_MPDU_DENSITY_8, + .mcs = { + .rx_mask = { 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, + .rx_highest = cpu_to_le16(72), + .tx_params = IEEE80211_HT_MCS_TX_DEFINED, + }, +}; + +static int wl12xx_setup(struct wl1271 *wl) +{ + struct wl12xx_priv *priv = wl->priv; + struct wlcore_platdev_data *pdev_data = wl->pdev->dev.platform_data; + struct wl12xx_platform_data *pdata = pdev_data->pdata; + + wl->rtable = wl12xx_rtable; + wl->num_tx_desc = WL12XX_NUM_TX_DESCRIPTORS; + wl->num_rx_desc = WL12XX_NUM_RX_DESCRIPTORS; + wl->num_channels = 1; + wl->num_mac_addr = WL12XX_NUM_MAC_ADDRESSES; + wl->band_rate_to_idx = wl12xx_band_rate_to_idx; + wl->hw_tx_rate_tbl_size = WL12XX_CONF_HW_RXTX_RATE_MAX; + wl->hw_min_ht_rate = WL12XX_CONF_HW_RXTX_RATE_MCS0; + wl->fw_status_priv_len = 0; + wl->stats.fw_stats_len = sizeof(struct wl12xx_acx_statistics); + wlcore_set_ht_cap(wl, IEEE80211_BAND_2GHZ, &wl12xx_ht_cap); + wlcore_set_ht_cap(wl, IEEE80211_BAND_5GHZ, &wl12xx_ht_cap); + wl12xx_conf_init(wl); + + if (!fref_param) { + priv->ref_clock = pdata->board_ref_clock; + } else { + if (!strcmp(fref_param, "19.2")) + priv->ref_clock = WL12XX_REFCLOCK_19; + else if (!strcmp(fref_param, "26")) + priv->ref_clock = WL12XX_REFCLOCK_26; + else if (!strcmp(fref_param, "26x")) + priv->ref_clock = WL12XX_REFCLOCK_26_XTAL; + else if (!strcmp(fref_param, "38.4")) + priv->ref_clock = WL12XX_REFCLOCK_38; + else if (!strcmp(fref_param, "38.4x")) + priv->ref_clock = WL12XX_REFCLOCK_38_XTAL; + else if (!strcmp(fref_param, "52")) + priv->ref_clock = WL12XX_REFCLOCK_52; + else + wl1271_error("Invalid fref parameter %s", fref_param); + } + + if (!tcxo_param) { + priv->tcxo_clock = pdata->board_tcxo_clock; + } else { + if (!strcmp(tcxo_param, "19.2")) + priv->tcxo_clock = WL12XX_TCXOCLOCK_19_2; + else if (!strcmp(tcxo_param, "26")) + priv->tcxo_clock = WL12XX_TCXOCLOCK_26; + else if (!strcmp(tcxo_param, "38.4")) + priv->tcxo_clock = WL12XX_TCXOCLOCK_38_4; + else if (!strcmp(tcxo_param, "52")) + priv->tcxo_clock = WL12XX_TCXOCLOCK_52; + else if (!strcmp(tcxo_param, "16.368")) + priv->tcxo_clock = WL12XX_TCXOCLOCK_16_368; + else if (!strcmp(tcxo_param, "32.736")) + priv->tcxo_clock = WL12XX_TCXOCLOCK_32_736; + else if (!strcmp(tcxo_param, "16.8")) + priv->tcxo_clock = WL12XX_TCXOCLOCK_16_8; + else if (!strcmp(tcxo_param, "33.6")) + priv->tcxo_clock = WL12XX_TCXOCLOCK_33_6; + else + wl1271_error("Invalid tcxo parameter %s", tcxo_param); + } + + priv->rx_mem_addr = kmalloc(sizeof(*priv->rx_mem_addr), GFP_KERNEL); + if (!priv->rx_mem_addr) + return -ENOMEM; + + return 0; +} + +static int wl12xx_probe(struct platform_device *pdev) +{ + struct wl1271 *wl; + struct ieee80211_hw *hw; + int ret; + + hw = wlcore_alloc_hw(sizeof(struct wl12xx_priv), + WL12XX_AGGR_BUFFER_SIZE, + sizeof(struct wl12xx_event_mailbox)); + if (IS_ERR(hw)) { + wl1271_error("can't allocate hw"); + ret = PTR_ERR(hw); + goto out; + } + + wl = hw->priv; + wl->ops = &wl12xx_ops; + wl->ptable = wl12xx_ptable; + ret = wlcore_probe(wl, pdev); + if (ret) + goto out_free; + + return ret; + +out_free: + wlcore_free_hw(wl); +out: + return ret; +} + +static int wl12xx_remove(struct platform_device *pdev) +{ + struct wl1271 *wl = platform_get_drvdata(pdev); + struct wl12xx_priv *priv; + + if (!wl) + goto out; + priv = wl->priv; + + kfree(priv->rx_mem_addr); + +out: + return wlcore_remove(pdev); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) +static const struct platform_device_id wl12xx_id_table[] = { + { "wl12xx", 0 }, + { } /* Terminating Entry */ +}; +MODULE_DEVICE_TABLE(platform, wl12xx_id_table); +#endif + +static struct platform_driver wl12xx_driver = { + .probe = wl12xx_probe, + .remove = wl12xx_remove, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) + .id_table = wl12xx_id_table, +#endif + .driver = { + .name = "wl12xx_driver", + .owner = THIS_MODULE, + } +}; + +module_platform_driver(wl12xx_driver); + +module_param_named(fref, fref_param, charp, 0); +MODULE_PARM_DESC(fref, "FREF clock: 19.2, 26, 26x, 38.4, 38.4x, 52"); + +module_param_named(tcxo, tcxo_param, charp, 0); +MODULE_PARM_DESC(tcxo, + "TCXO clock: 19.2, 26, 38.4, 52, 16.368, 32.736, 16.8, 33.6"); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Luciano Coelho "); +MODULE_FIRMWARE(WL127X_FW_NAME_SINGLE); +MODULE_FIRMWARE(WL127X_FW_NAME_MULTI); +MODULE_FIRMWARE(WL127X_PLT_FW_NAME); +MODULE_FIRMWARE(WL128X_FW_NAME_SINGLE); +MODULE_FIRMWARE(WL128X_FW_NAME_MULTI); +MODULE_FIRMWARE(WL128X_PLT_FW_NAME); diff --git a/drivers/net/wireless/ti/wl12xx/reg.h b/drivers/net/wireless/ti/wl12xx/reg.h new file mode 100644 index 0000000..79ede02 --- /dev/null +++ b/drivers/net/wireless/ti/wl12xx/reg.h @@ -0,0 +1,556 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 1998-2009 Texas Instruments. All rights reserved. + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __REG_H__ +#define __REG_H__ + +#include + +#define REGISTERS_BASE 0x00300000 +#define DRPW_BASE 0x00310000 + +#define REGISTERS_DOWN_SIZE 0x00008800 +#define REGISTERS_WORK_SIZE 0x0000b000 + +#define FW_STATUS_ADDR (0x14FC0 + 0xA000) + +/*=============================================== + Host Software Reset - 32bit RW + ------------------------------------------ + [31:1] Reserved + 0 SOFT_RESET Soft Reset - When this bit is set, + it holds the Wlan hardware in a soft reset state. + This reset disables all MAC and baseband processor + clocks except the CardBus/PCI interface clock. + It also initializes all MAC state machines except + the host interface. It does not reload the + contents of the EEPROM. When this bit is cleared + (not self-clearing), the Wlan hardware + exits the software reset state. +===============================================*/ +#define WL12XX_SLV_SOFT_RESET (REGISTERS_BASE + 0x0000) + +#define WL1271_SLV_REG_DATA (REGISTERS_BASE + 0x0008) +#define WL1271_SLV_REG_ADATA (REGISTERS_BASE + 0x000c) +#define WL1271_SLV_MEM_DATA (REGISTERS_BASE + 0x0018) + +#define WL12XX_REG_INTERRUPT_TRIG (REGISTERS_BASE + 0x0474) +#define WL12XX_REG_INTERRUPT_TRIG_H (REGISTERS_BASE + 0x0478) + +/*============================================= + Host Interrupt Mask Register - 32bit (RW) + ------------------------------------------ + Setting a bit in this register masks the + corresponding interrupt to the host. + 0 - RX0 - Rx first dubble buffer Data Interrupt + 1 - TXD - Tx Data Interrupt + 2 - TXXFR - Tx Transfer Interrupt + 3 - RX1 - Rx second dubble buffer Data Interrupt + 4 - RXXFR - Rx Transfer Interrupt + 5 - EVENT_A - Event Mailbox interrupt + 6 - EVENT_B - Event Mailbox interrupt + 7 - WNONHST - Wake On Host Interrupt + 8 - TRACE_A - Debug Trace interrupt + 9 - TRACE_B - Debug Trace interrupt + 10 - CDCMP - Command Complete Interrupt + 11 - + 12 - + 13 - + 14 - ICOMP - Initialization Complete Interrupt + 16 - SG SE - Soft Gemini - Sense enable interrupt + 17 - SG SD - Soft Gemini - Sense disable interrupt + 18 - - + 19 - - + 20 - - + 21- - + Default: 0x0001 +*==============================================*/ +#define WL12XX_REG_INTERRUPT_MASK (REGISTERS_BASE + 0x04DC) + +/*============================================= + Host Interrupt Mask Set 16bit, (Write only) + ------------------------------------------ + Setting a bit in this register sets + the corresponding bin in ACX_HINT_MASK register + without effecting the mask + state of other bits (0 = no effect). +==============================================*/ +#define ACX_REG_HINT_MASK_SET (REGISTERS_BASE + 0x04E0) + +/*============================================= + Host Interrupt Mask Clear 16bit,(Write only) + ------------------------------------------ + Setting a bit in this register clears + the corresponding bin in ACX_HINT_MASK register + without effecting the mask + state of other bits (0 = no effect). +=============================================*/ +#define ACX_REG_HINT_MASK_CLR (REGISTERS_BASE + 0x04E4) + +/*============================================= + Host Interrupt Status Nondestructive Read + 16bit,(Read only) + ------------------------------------------ + The host can read this register to determine + which interrupts are active. + Reading this register doesn't + effect its content. +=============================================*/ +#define WL12XX_REG_INTERRUPT_NO_CLEAR (REGISTERS_BASE + 0x04E8) + +/*============================================= + Host Interrupt Status Clear on Read Register + 16bit,(Read only) + ------------------------------------------ + The host can read this register to determine + which interrupts are active. + Reading this register clears it, + thus making all interrupts inactive. +==============================================*/ +#define ACX_REG_INTERRUPT_CLEAR (REGISTERS_BASE + 0x04F8) + +/*============================================= + Host Interrupt Acknowledge Register + 16bit,(Write only) + ------------------------------------------ + The host can set individual bits in this + register to clear (acknowledge) the corresp. + interrupt status bits in the HINT_STS_CLR and + HINT_STS_ND registers, thus making the + assotiated interrupt inactive. (0-no effect) +==============================================*/ +#define WL12XX_REG_INTERRUPT_ACK (REGISTERS_BASE + 0x04F0) + +#define WL12XX_REG_RX_DRIVER_COUNTER (REGISTERS_BASE + 0x0538) + +/* Device Configuration registers*/ +#define SOR_CFG (REGISTERS_BASE + 0x0800) + +/* Embedded ARM CPU Control */ + +/*=============================================== + Halt eCPU - 32bit RW + ------------------------------------------ + 0 HALT_ECPU Halt Embedded CPU - This bit is the + compliment of bit 1 (MDATA2) in the SOR_CFG register. + During a hardware reset, this bit holds + the inverse of MDATA2. + When downloading firmware from the host, + set this bit (pull down MDATA2). + The host clears this bit after downloading the firmware into + zero-wait-state SSRAM. + When loading firmware from Flash, clear this bit (pull up MDATA2) + so that the eCPU can run the bootloader code in Flash + HALT_ECPU eCPU State + -------------------- + 1 halt eCPU + 0 enable eCPU + ===============================================*/ +#define WL12XX_REG_ECPU_CONTROL (REGISTERS_BASE + 0x0804) + +#define WL12XX_HI_CFG (REGISTERS_BASE + 0x0808) + +/*=============================================== + EEPROM Burst Read Start - 32bit RW + ------------------------------------------ + [31:1] Reserved + 0 ACX_EE_START - EEPROM Burst Read Start 0 + Setting this bit starts a burst read from + the external EEPROM. + If this bit is set (after reset) before an EEPROM read/write, + the burst read starts at EEPROM address 0. + Otherwise, it starts at the address + following the address of the previous access. + TheWlan hardware hardware clears this bit automatically. + + Default: 0x00000000 +*================================================*/ +#define ACX_REG_EE_START (REGISTERS_BASE + 0x080C) + +#define WL12XX_OCP_POR_CTR (REGISTERS_BASE + 0x09B4) +#define WL12XX_OCP_DATA_WRITE (REGISTERS_BASE + 0x09B8) +#define WL12XX_OCP_DATA_READ (REGISTERS_BASE + 0x09BC) +#define WL12XX_OCP_CMD (REGISTERS_BASE + 0x09C0) + +#define WL12XX_HOST_WR_ACCESS (REGISTERS_BASE + 0x09F8) + +#define WL12XX_CHIP_ID_B (REGISTERS_BASE + 0x5674) + +#define WL12XX_ENABLE (REGISTERS_BASE + 0x5450) + +/* Power Management registers */ +#define WL12XX_ELP_CFG_MODE (REGISTERS_BASE + 0x5804) +#define WL12XX_ELP_CMD (REGISTERS_BASE + 0x5808) +#define WL12XX_PLL_CAL_TIME (REGISTERS_BASE + 0x5810) +#define WL12XX_CLK_REQ_TIME (REGISTERS_BASE + 0x5814) +#define WL12XX_CLK_BUF_TIME (REGISTERS_BASE + 0x5818) + +#define WL12XX_CFG_PLL_SYNC_CNT (REGISTERS_BASE + 0x5820) + +/* Scratch Pad registers*/ +#define WL12XX_SCR_PAD0 (REGISTERS_BASE + 0x5608) +#define WL12XX_SCR_PAD1 (REGISTERS_BASE + 0x560C) +#define WL12XX_SCR_PAD2 (REGISTERS_BASE + 0x5610) +#define WL12XX_SCR_PAD3 (REGISTERS_BASE + 0x5614) +#define WL12XX_SCR_PAD4 (REGISTERS_BASE + 0x5618) +#define WL12XX_SCR_PAD4_SET (REGISTERS_BASE + 0x561C) +#define WL12XX_SCR_PAD4_CLR (REGISTERS_BASE + 0x5620) +#define WL12XX_SCR_PAD5 (REGISTERS_BASE + 0x5624) +#define WL12XX_SCR_PAD5_SET (REGISTERS_BASE + 0x5628) +#define WL12XX_SCR_PAD5_CLR (REGISTERS_BASE + 0x562C) +#define WL12XX_SCR_PAD6 (REGISTERS_BASE + 0x5630) +#define WL12XX_SCR_PAD7 (REGISTERS_BASE + 0x5634) +#define WL12XX_SCR_PAD8 (REGISTERS_BASE + 0x5638) +#define WL12XX_SCR_PAD9 (REGISTERS_BASE + 0x563C) + +/* Spare registers*/ +#define WL12XX_SPARE_A1 (REGISTERS_BASE + 0x0994) +#define WL12XX_SPARE_A2 (REGISTERS_BASE + 0x0998) +#define WL12XX_SPARE_A3 (REGISTERS_BASE + 0x099C) +#define WL12XX_SPARE_A4 (REGISTERS_BASE + 0x09A0) +#define WL12XX_SPARE_A5 (REGISTERS_BASE + 0x09A4) +#define WL12XX_SPARE_A6 (REGISTERS_BASE + 0x09A8) +#define WL12XX_SPARE_A7 (REGISTERS_BASE + 0x09AC) +#define WL12XX_SPARE_A8 (REGISTERS_BASE + 0x09B0) +#define WL12XX_SPARE_B1 (REGISTERS_BASE + 0x5420) +#define WL12XX_SPARE_B2 (REGISTERS_BASE + 0x5424) +#define WL12XX_SPARE_B3 (REGISTERS_BASE + 0x5428) +#define WL12XX_SPARE_B4 (REGISTERS_BASE + 0x542C) +#define WL12XX_SPARE_B5 (REGISTERS_BASE + 0x5430) +#define WL12XX_SPARE_B6 (REGISTERS_BASE + 0x5434) +#define WL12XX_SPARE_B7 (REGISTERS_BASE + 0x5438) +#define WL12XX_SPARE_B8 (REGISTERS_BASE + 0x543C) + +#define WL12XX_PLL_PARAMETERS (REGISTERS_BASE + 0x6040) +#define WL12XX_WU_COUNTER_PAUSE (REGISTERS_BASE + 0x6008) +#define WL12XX_WELP_ARM_COMMAND (REGISTERS_BASE + 0x6100) +#define WL12XX_DRPW_SCRATCH_START (DRPW_BASE + 0x002C) + +#define WL12XX_CMD_MBOX_ADDRESS 0x407B4 + +#define ACX_REG_EEPROM_START_BIT BIT(1) + +/* Command/Information Mailbox Pointers */ + +/*=============================================== + Command Mailbox Pointer - 32bit RW + ------------------------------------------ + This register holds the start address of + the command mailbox located in the Wlan hardware memory. + The host must read this pointer after a reset to + find the location of the command mailbox. + The Wlan hardware initializes the command mailbox + pointer with the default address of the command mailbox. + The command mailbox pointer is not valid until after + the host receives the Init Complete interrupt from + the Wlan hardware. + ===============================================*/ +#define WL12XX_REG_COMMAND_MAILBOX_PTR (WL12XX_SCR_PAD0) + +/*=============================================== + Information Mailbox Pointer - 32bit RW + ------------------------------------------ + This register holds the start address of + the information mailbox located in the Wlan hardware memory. + The host must read this pointer after a reset to find + the location of the information mailbox. + The Wlan hardware initializes the information mailbox pointer + with the default address of the information mailbox. + The information mailbox pointer is not valid + until after the host receives the Init Complete interrupt from + the Wlan hardware. + ===============================================*/ +#define WL12XX_REG_EVENT_MAILBOX_PTR (WL12XX_SCR_PAD1) + +/*=============================================== + EEPROM Read/Write Request 32bit RW + ------------------------------------------ + 1 EE_READ - EEPROM Read Request 1 - Setting this bit + loads a single byte of data into the EE_DATA + register from the EEPROM location specified in + the EE_ADDR register. + The Wlan hardware hardware clears this bit automatically. + EE_DATA is valid when this bit is cleared. + + 0 EE_WRITE - EEPROM Write Request - Setting this bit + writes a single byte of data from the EE_DATA register into the + EEPROM location specified in the EE_ADDR register. + The Wlan hardware hardware clears this bit automatically. +*===============================================*/ +#define ACX_EE_CTL_REG EE_CTL +#define EE_WRITE 0x00000001ul +#define EE_READ 0x00000002ul + +/*=============================================== + EEPROM Address - 32bit RW + ------------------------------------------ + This register specifies the address + within the EEPROM from/to which to read/write data. + ===============================================*/ +#define ACX_EE_ADDR_REG EE_ADDR + +/*=============================================== + EEPROM Data - 32bit RW + ------------------------------------------ + This register either holds the read 8 bits of + data from the EEPROM or the write data + to be written to the EEPROM. + ===============================================*/ +#define ACX_EE_DATA_REG EE_DATA + +/*=============================================== + EEPROM Base Address - 32bit RW + ------------------------------------------ + This register holds the upper nine bits + [23:15] of the 24-bit Wlan hardware memory + address for burst reads from EEPROM accesses. + The EEPROM provides the lower 15 bits of this address. + The MSB of the address from the EEPROM is ignored. + ===============================================*/ +#define ACX_EE_CFG EE_CFG + +/*=============================================== + GPIO Output Values -32bit, RW + ------------------------------------------ + [31:16] Reserved + [15: 0] Specify the output values (at the output driver inputs) for + GPIO[15:0], respectively. + ===============================================*/ +#define ACX_GPIO_OUT_REG GPIO_OUT +#define ACX_MAX_GPIO_LINES 15 + +/*=============================================== + Contention window -32bit, RW + ------------------------------------------ + [31:26] Reserved + [25:16] Max (0x3ff) + [15:07] Reserved + [06:00] Current contention window value - default is 0x1F + ===============================================*/ +#define ACX_CONT_WIND_CFG_REG CONT_WIND_CFG +#define ACX_CONT_WIND_MIN_MASK 0x0000007f +#define ACX_CONT_WIND_MAX 0x03ff0000 + +#define REF_FREQ_19_2 0 +#define REF_FREQ_26_0 1 +#define REF_FREQ_38_4 2 +#define REF_FREQ_40_0 3 +#define REF_FREQ_33_6 4 +#define REF_FREQ_NUM 5 + +#define LUT_PARAM_INTEGER_DIVIDER 0 +#define LUT_PARAM_FRACTIONAL_DIVIDER 1 +#define LUT_PARAM_ATTN_BB 2 +#define LUT_PARAM_ALPHA_BB 3 +#define LUT_PARAM_STOP_TIME_BB 4 +#define LUT_PARAM_BB_PLL_LOOP_FILTER 5 +#define LUT_PARAM_NUM 6 + +#define WL12XX_EEPROMLESS_IND (WL12XX_SCR_PAD4) +#define USE_EEPROM 0 +#define NVS_DATA_BUNDARY_ALIGNMENT 4 + +/* Firmware image header size */ +#define FW_HDR_SIZE 8 + +/****************************************************************************** + + CHANNELS, BAND & REG DOMAINS definitions + +******************************************************************************/ + +#define SHORT_PREAMBLE_BIT BIT(0) /* CCK or Barker depending on the rate */ +#define OFDM_RATE_BIT BIT(6) +#define PBCC_RATE_BIT BIT(7) + +enum { + CCK_LONG = 0, + CCK_SHORT = SHORT_PREAMBLE_BIT, + PBCC_LONG = PBCC_RATE_BIT, + PBCC_SHORT = PBCC_RATE_BIT | SHORT_PREAMBLE_BIT, + OFDM = OFDM_RATE_BIT +}; + +/****************************************************************************** + +Transmit-Descriptor RATE-SET field definitions... + +Define a new "Rate-Set" for TX path that incorporates the +Rate & Modulation info into a single 16-bit field. + +TxdRateSet_t: +b15 - Indicates Preamble type (1=SHORT, 0=LONG). + Notes: + Must be LONG (0) for 1Mbps rate. + Does not apply (set to 0) for RevG-OFDM rates. +b14 - Indicates PBCC encoding (1=PBCC, 0=not). + Notes: + Does not apply (set to 0) for rates 1 and 2 Mbps. + Does not apply (set to 0) for RevG-OFDM rates. +b13 - Unused (set to 0). +b12-b0 - Supported Rate indicator bits as defined below. + +******************************************************************************/ + +#define OCP_CMD_LOOP 32 +#define OCP_CMD_WRITE 0x1 +#define OCP_CMD_READ 0x2 +#define OCP_READY_MASK BIT(18) +#define OCP_STATUS_MASK (BIT(16) | BIT(17)) +#define OCP_STATUS_NO_RESP 0x00000 +#define OCP_STATUS_OK 0x10000 +#define OCP_STATUS_REQ_FAILED 0x20000 +#define OCP_STATUS_RESP_ERROR 0x30000 + +#define OCP_REG_POLARITY 0x0064 +#define OCP_REG_CLK_TYPE 0x0448 +#define OCP_REG_CLK_POLARITY 0x0cb2 +#define OCP_REG_CLK_PULL 0x0cb4 + +#define POLARITY_LOW BIT(1) +#define NO_PULL (BIT(14) | BIT(15)) + +#define FREF_CLK_TYPE_BITS 0xfffffe7f +#define CLK_REQ_PRCM 0x100 +#define FREF_CLK_POLARITY_BITS 0xfffff8ff +#define CLK_REQ_OUTN_SEL 0x700 + +#define WU_COUNTER_PAUSE_VAL 0x3FF + +/* PLL configuration algorithm for wl128x */ +#define SYS_CLK_CFG_REG 0x2200 +/* Bit[0] - 0-TCXO, 1-FREF */ +#define MCS_PLL_CLK_SEL_FREF BIT(0) +/* Bit[3:2] - 01-TCXO, 10-FREF */ +#define WL_CLK_REQ_TYPE_FREF BIT(3) +#define WL_CLK_REQ_TYPE_PG2 (BIT(3) | BIT(2)) +/* Bit[4] - 0-TCXO, 1-FREF */ +#define PRCM_CM_EN_MUX_WLAN_FREF BIT(4) + +#define TCXO_ILOAD_INT_REG 0x2264 +#define TCXO_CLK_DETECT_REG 0x2266 + +#define TCXO_DET_FAILED BIT(4) + +#define FREF_ILOAD_INT_REG 0x2084 +#define FREF_CLK_DETECT_REG 0x2086 +#define FREF_CLK_DETECT_FAIL BIT(4) + +/* Use this reg for masking during driver access */ +#define WL_SPARE_REG 0x2320 +#define WL_SPARE_VAL BIT(2) +/* Bit[6:5:3] - mask wl write SYS_CLK_CFG[8:5:2:4] */ +#define WL_SPARE_MASK_8526 (BIT(6) | BIT(5) | BIT(3)) + +#define PLL_LOCK_COUNTERS_REG 0xD8C +#define PLL_LOCK_COUNTERS_COEX 0x0F +#define PLL_LOCK_COUNTERS_MCS 0xF0 +#define MCS_PLL_OVERRIDE_REG 0xD90 +#define MCS_PLL_CONFIG_REG 0xD92 +#define MCS_SEL_IN_FREQ_MASK 0x0070 +#define MCS_SEL_IN_FREQ_SHIFT 4 +#define MCS_PLL_CONFIG_REG_VAL 0x73 +#define MCS_PLL_ENABLE_HP (BIT(0) | BIT(1)) + +#define MCS_PLL_M_REG 0xD94 +#define MCS_PLL_N_REG 0xD96 +#define MCS_PLL_M_REG_VAL 0xC8 +#define MCS_PLL_N_REG_VAL 0x07 + +#define SDIO_IO_DS 0xd14 + +/* SDIO/wSPI DS configuration values */ +enum { + HCI_IO_DS_8MA = 0, + HCI_IO_DS_4MA = 1, /* default */ + HCI_IO_DS_6MA = 2, + HCI_IO_DS_2MA = 3, +}; + +/* end PLL configuration algorithm for wl128x */ + +/* + * Host Command Interrupt. Setting this bit masks + * the interrupt that the host issues to inform + * the FW that it has sent a command + * to the Wlan hardware Command Mailbox. + */ +#define WL12XX_INTR_TRIG_CMD BIT(0) + +/* + * Host Event Acknowlegde Interrupt. The host + * sets this bit to acknowledge that it received + * the unsolicited information from the event + * mailbox. + */ +#define WL12XX_INTR_TRIG_EVENT_ACK BIT(1) + +/*=============================================== + HI_CFG Interface Configuration Register Values + ------------------------------------------ + ===============================================*/ +#define HI_CFG_UART_ENABLE 0x00000004 +#define HI_CFG_RST232_ENABLE 0x00000008 +#define HI_CFG_CLOCK_REQ_SELECT 0x00000010 +#define HI_CFG_HOST_INT_ENABLE 0x00000020 +#define HI_CFG_VLYNQ_OUTPUT_ENABLE 0x00000040 +#define HI_CFG_HOST_INT_ACTIVE_LOW 0x00000080 +#define HI_CFG_UART_TX_OUT_GPIO_15 0x00000100 +#define HI_CFG_UART_TX_OUT_GPIO_14 0x00000200 +#define HI_CFG_UART_TX_OUT_GPIO_7 0x00000400 + +#define HI_CFG_DEF_VAL \ + (HI_CFG_UART_ENABLE | \ + HI_CFG_RST232_ENABLE | \ + HI_CFG_CLOCK_REQ_SELECT | \ + HI_CFG_HOST_INT_ENABLE) + +#define WL127X_REG_FUSE_DATA_2_1 0x050a +#define WL128X_REG_FUSE_DATA_2_1 0x2152 +#define PG_VER_MASK 0x3c +#define PG_VER_OFFSET 2 + +#define WL127X_PG_MAJOR_VER_MASK 0x3 +#define WL127X_PG_MAJOR_VER_OFFSET 0x0 +#define WL127X_PG_MINOR_VER_MASK 0xc +#define WL127X_PG_MINOR_VER_OFFSET 0x2 + +#define WL128X_PG_MAJOR_VER_MASK 0xc +#define WL128X_PG_MAJOR_VER_OFFSET 0x2 +#define WL128X_PG_MINOR_VER_MASK 0x3 +#define WL128X_PG_MINOR_VER_OFFSET 0x0 + +#define WL127X_PG_GET_MAJOR(pg_ver) ((pg_ver & WL127X_PG_MAJOR_VER_MASK) >> \ + WL127X_PG_MAJOR_VER_OFFSET) +#define WL127X_PG_GET_MINOR(pg_ver) ((pg_ver & WL127X_PG_MINOR_VER_MASK) >> \ + WL127X_PG_MINOR_VER_OFFSET) +#define WL128X_PG_GET_MAJOR(pg_ver) ((pg_ver & WL128X_PG_MAJOR_VER_MASK) >> \ + WL128X_PG_MAJOR_VER_OFFSET) +#define WL128X_PG_GET_MINOR(pg_ver) ((pg_ver & WL128X_PG_MINOR_VER_MASK) >> \ + WL128X_PG_MINOR_VER_OFFSET) + +#define WL12XX_REG_FUSE_BD_ADDR_1 0x00310eb4 +#define WL12XX_REG_FUSE_BD_ADDR_2 0x00310eb8 + +#endif diff --git a/drivers/net/wireless/ti/wl12xx/scan.c b/drivers/net/wireless/ti/wl12xx/scan.c new file mode 100644 index 0000000..4a0bbb1 --- /dev/null +++ b/drivers/net/wireless/ti/wl12xx/scan.c @@ -0,0 +1,501 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 2012 Texas Instruments. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include "scan.h" +#include "../wlcore/debug.h" +#include "../wlcore/tx.h" + +static int wl1271_get_scan_channels(struct wl1271 *wl, + struct cfg80211_scan_request *req, + struct basic_scan_channel_params *channels, + enum ieee80211_band band, bool passive) +{ + struct conf_scan_settings *c = &wl->conf.scan; + int i, j; + u32 flags; + + for (i = 0, j = 0; + i < req->n_channels && j < WL1271_SCAN_MAX_CHANNELS; + i++) { + flags = req->channels[i]->flags; + + if (!test_bit(i, wl->scan.scanned_ch) && + !(flags & IEEE80211_CHAN_DISABLED) && + (req->channels[i]->band == band) && + /* + * In passive scans, we scan all remaining + * channels, even if not marked as such. + * In active scans, we only scan channels not + * marked as passive. + */ + (passive || !(flags & IEEE80211_CHAN_PASSIVE_SCAN))) { + wl1271_debug(DEBUG_SCAN, "band %d, center_freq %d ", + req->channels[i]->band, + req->channels[i]->center_freq); + wl1271_debug(DEBUG_SCAN, "hw_value %d, flags %X", + req->channels[i]->hw_value, + req->channels[i]->flags); + wl1271_debug(DEBUG_SCAN, + "max_antenna_gain %d, max_power %d", + req->channels[i]->max_antenna_gain, + req->channels[i]->max_power); + wl1271_debug(DEBUG_SCAN, "beacon_found %d", + req->channels[i]->beacon_found); + + if (!passive) { + channels[j].min_duration = + cpu_to_le32(c->min_dwell_time_active); + channels[j].max_duration = + cpu_to_le32(c->max_dwell_time_active); + } else { + channels[j].min_duration = + cpu_to_le32(c->dwell_time_passive); + channels[j].max_duration = + cpu_to_le32(c->dwell_time_passive); + } + channels[j].early_termination = 0; + channels[j].tx_power_att = req->channels[i]->max_power; + channels[j].channel = req->channels[i]->hw_value; + + memset(&channels[j].bssid_lsb, 0xff, 4); + memset(&channels[j].bssid_msb, 0xff, 2); + + /* Mark the channels we already used */ + set_bit(i, wl->scan.scanned_ch); + + j++; + } + } + + return j; +} + +#define WL1271_NOTHING_TO_SCAN 1 + +static int wl1271_scan_send(struct wl1271 *wl, struct wl12xx_vif *wlvif, + enum ieee80211_band band, + bool passive, u32 basic_rate) +{ + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + struct wl1271_cmd_scan *cmd; + struct wl1271_cmd_trigger_scan_to *trigger; + int ret; + u16 scan_options = 0; + + /* skip active scans if we don't have SSIDs */ + if (!passive && wl->scan.req->n_ssids == 0) + return WL1271_NOTHING_TO_SCAN; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + trigger = kzalloc(sizeof(*trigger), GFP_KERNEL); + if (!cmd || !trigger) { + ret = -ENOMEM; + goto out; + } + + if (wl->conf.scan.split_scan_timeout) + scan_options |= WL1271_SCAN_OPT_SPLIT_SCAN; + + if (passive) + scan_options |= WL1271_SCAN_OPT_PASSIVE; + + cmd->params.role_id = wlvif->role_id; + + if (WARN_ON(cmd->params.role_id == WL12XX_INVALID_ROLE_ID)) { + ret = -EINVAL; + goto out; + } + + cmd->params.scan_options = cpu_to_le16(scan_options); + + cmd->params.n_ch = wl1271_get_scan_channels(wl, wl->scan.req, + cmd->channels, + band, passive); + if (cmd->params.n_ch == 0) { + ret = WL1271_NOTHING_TO_SCAN; + goto out; + } + + cmd->params.tx_rate = cpu_to_le32(basic_rate); + cmd->params.n_probe_reqs = wl->conf.scan.num_probe_reqs; + cmd->params.tid_trigger = CONF_TX_AC_ANY_TID; + cmd->params.scan_tag = WL1271_SCAN_DEFAULT_TAG; + + if (band == IEEE80211_BAND_2GHZ) + cmd->params.band = WL1271_SCAN_BAND_2_4_GHZ; + else + cmd->params.band = WL1271_SCAN_BAND_5_GHZ; + + if (wl->scan.ssid_len && wl->scan.ssid) { + cmd->params.ssid_len = wl->scan.ssid_len; + memcpy(cmd->params.ssid, wl->scan.ssid, wl->scan.ssid_len); + } + + memcpy(cmd->addr, vif->addr, ETH_ALEN); + + ret = wl12xx_cmd_build_probe_req(wl, wlvif, + cmd->params.role_id, band, + wl->scan.ssid, wl->scan.ssid_len, + wl->scan.req->ie, + wl->scan.req->ie_len, false); + if (ret < 0) { + wl1271_error("PROBE request template failed"); + goto out; + } + + trigger->timeout = cpu_to_le32(wl->conf.scan.split_scan_timeout); + ret = wl1271_cmd_send(wl, CMD_TRIGGER_SCAN_TO, trigger, + sizeof(*trigger), 0); + if (ret < 0) { + wl1271_error("trigger scan to failed for hw scan"); + goto out; + } + + wl1271_dump(DEBUG_SCAN, "SCAN: ", cmd, sizeof(*cmd)); + + ret = wl1271_cmd_send(wl, CMD_SCAN, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("SCAN failed"); + goto out; + } + +out: + kfree(cmd); + kfree(trigger); + return ret; +} + +int wl12xx_scan_stop(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + struct wl1271_cmd_header *cmd = NULL; + int ret = 0; + + if (WARN_ON(wl->scan.state == WL1271_SCAN_STATE_IDLE)) + return -EINVAL; + + wl1271_debug(DEBUG_CMD, "cmd scan stop"); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + ret = wl1271_cmd_send(wl, CMD_STOP_SCAN, cmd, + sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("cmd stop_scan failed"); + goto out; + } +out: + kfree(cmd); + return ret; +} + +void wl1271_scan_stm(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + int ret = 0; + enum ieee80211_band band; + u32 rate, mask; + + switch (wl->scan.state) { + case WL1271_SCAN_STATE_IDLE: + break; + + case WL1271_SCAN_STATE_2GHZ_ACTIVE: + band = IEEE80211_BAND_2GHZ; + mask = wlvif->bitrate_masks[band]; + if (wl->scan.req->no_cck) { + mask &= ~CONF_TX_CCK_RATES; + if (!mask) + mask = CONF_TX_RATE_MASK_BASIC_P2P; + } + rate = wl1271_tx_min_rate_get(wl, mask); + ret = wl1271_scan_send(wl, wlvif, band, false, rate); + if (ret == WL1271_NOTHING_TO_SCAN) { + wl->scan.state = WL1271_SCAN_STATE_2GHZ_PASSIVE; + wl1271_scan_stm(wl, wlvif); + } + + break; + + case WL1271_SCAN_STATE_2GHZ_PASSIVE: + band = IEEE80211_BAND_2GHZ; + mask = wlvif->bitrate_masks[band]; + if (wl->scan.req->no_cck) { + mask &= ~CONF_TX_CCK_RATES; + if (!mask) + mask = CONF_TX_RATE_MASK_BASIC_P2P; + } + rate = wl1271_tx_min_rate_get(wl, mask); + ret = wl1271_scan_send(wl, wlvif, band, true, rate); + if (ret == WL1271_NOTHING_TO_SCAN) { + if (wl->enable_11a) + wl->scan.state = WL1271_SCAN_STATE_5GHZ_ACTIVE; + else + wl->scan.state = WL1271_SCAN_STATE_DONE; + wl1271_scan_stm(wl, wlvif); + } + + break; + + case WL1271_SCAN_STATE_5GHZ_ACTIVE: + band = IEEE80211_BAND_5GHZ; + rate = wl1271_tx_min_rate_get(wl, wlvif->bitrate_masks[band]); + ret = wl1271_scan_send(wl, wlvif, band, false, rate); + if (ret == WL1271_NOTHING_TO_SCAN) { + wl->scan.state = WL1271_SCAN_STATE_5GHZ_PASSIVE; + wl1271_scan_stm(wl, wlvif); + } + + break; + + case WL1271_SCAN_STATE_5GHZ_PASSIVE: + band = IEEE80211_BAND_5GHZ; + rate = wl1271_tx_min_rate_get(wl, wlvif->bitrate_masks[band]); + ret = wl1271_scan_send(wl, wlvif, band, true, rate); + if (ret == WL1271_NOTHING_TO_SCAN) { + wl->scan.state = WL1271_SCAN_STATE_DONE; + wl1271_scan_stm(wl, wlvif); + } + + break; + + case WL1271_SCAN_STATE_DONE: + wl->scan.failed = false; + cancel_delayed_work(&wl->scan_complete_work); + ieee80211_queue_delayed_work(wl->hw, &wl->scan_complete_work, + msecs_to_jiffies(0)); + break; + + default: + wl1271_error("invalid scan state"); + break; + } + + if (ret < 0) { + cancel_delayed_work(&wl->scan_complete_work); + ieee80211_queue_delayed_work(wl->hw, &wl->scan_complete_work, + msecs_to_jiffies(0)); + } +} + +static void wl12xx_adjust_channels(struct wl1271_cmd_sched_scan_config *cmd, + struct wlcore_scan_channels *cmd_channels) +{ + memcpy(cmd->passive, cmd_channels->passive, sizeof(cmd->passive)); + memcpy(cmd->active, cmd_channels->active, sizeof(cmd->active)); + cmd->dfs = cmd_channels->dfs; + cmd->n_pactive_ch = cmd_channels->passive_active; + + memcpy(cmd->channels_2, cmd_channels->channels_2, + sizeof(cmd->channels_2)); + memcpy(cmd->channels_5, cmd_channels->channels_5, + sizeof(cmd->channels_5)); + /* channels_4 are not supported, so no need to copy them */ +} + +int wl1271_scan_sched_scan_config(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies) +{ + struct wl1271_cmd_sched_scan_config *cfg = NULL; + struct wlcore_scan_channels *cfg_channels = NULL; + struct conf_sched_scan_settings *c = &wl->conf.sched_scan; + int i, ret; + bool force_passive = !req->n_ssids; + + wl1271_debug(DEBUG_CMD, "cmd sched_scan scan config"); + + cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + cfg->role_id = wlvif->role_id; + cfg->rssi_threshold = c->rssi_threshold; + cfg->snr_threshold = c->snr_threshold; + cfg->n_probe_reqs = c->num_probe_reqs; + /* cycles set to 0 it means infinite (until manually stopped) */ + cfg->cycles = 0; + /* report APs when at least 1 is found */ + cfg->report_after = 1; + /* don't stop scanning automatically when something is found */ + cfg->terminate = 0; + cfg->tag = WL1271_SCAN_DEFAULT_TAG; + /* don't filter on BSS type */ + cfg->bss_type = SCAN_BSS_TYPE_ANY; + /* currently NL80211 supports only a single interval */ + for (i = 0; i < SCAN_MAX_CYCLE_INTERVALS; i++) + cfg->intervals[i] = cpu_to_le32(req->interval); + + cfg->ssid_len = 0; + ret = wlcore_scan_sched_scan_ssid_list(wl, wlvif, req); + if (ret < 0) + goto out; + + cfg->filter_type = ret; + + wl1271_debug(DEBUG_SCAN, "filter_type = %d", cfg->filter_type); + + cfg_channels = kzalloc(sizeof(*cfg_channels), GFP_KERNEL); + if (!cfg_channels) { + ret = -ENOMEM; + goto out; + } + + if (!wlcore_set_scan_chan_params(wl, cfg_channels, req->channels, + req->n_channels, req->n_ssids, + SCAN_TYPE_PERIODIC)) { + wl1271_error("scan channel list is empty"); + ret = -EINVAL; + goto out; + } + wl12xx_adjust_channels(cfg, cfg_channels); + + if (!force_passive && cfg->active[0]) { + u8 band = IEEE80211_BAND_2GHZ; + ret = wl12xx_cmd_build_probe_req(wl, wlvif, + wlvif->role_id, band, + req->ssids[0].ssid, + req->ssids[0].ssid_len, + ies->ie[band], + ies->len[band], true); + if (ret < 0) { + wl1271_error("2.4GHz PROBE request template failed"); + goto out; + } + } + + if (!force_passive && cfg->active[1]) { + u8 band = IEEE80211_BAND_5GHZ; + ret = wl12xx_cmd_build_probe_req(wl, wlvif, + wlvif->role_id, band, + req->ssids[0].ssid, + req->ssids[0].ssid_len, + ies->ie[band], + ies->len[band], true); + if (ret < 0) { + wl1271_error("5GHz PROBE request template failed"); + goto out; + } + } + + wl1271_dump(DEBUG_SCAN, "SCAN_CFG: ", cfg, sizeof(*cfg)); + + ret = wl1271_cmd_send(wl, CMD_CONNECTION_SCAN_CFG, cfg, + sizeof(*cfg), 0); + if (ret < 0) { + wl1271_error("SCAN configuration failed"); + goto out; + } +out: + kfree(cfg_channels); + kfree(cfg); + return ret; +} + +int wl1271_scan_sched_scan_start(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + struct wl1271_cmd_sched_scan_start *start; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd periodic scan start"); + + if (wlvif->bss_type != BSS_TYPE_STA_BSS) + return -EOPNOTSUPP; + + if ((wl->quirks & WLCORE_QUIRK_NO_SCHED_SCAN_WHILE_CONN) && + test_bit(WLVIF_FLAG_IN_USE, &wlvif->flags)) + return -EBUSY; + + start = kzalloc(sizeof(*start), GFP_KERNEL); + if (!start) + return -ENOMEM; + + start->role_id = wlvif->role_id; + start->tag = WL1271_SCAN_DEFAULT_TAG; + + ret = wl1271_cmd_send(wl, CMD_START_PERIODIC_SCAN, start, + sizeof(*start), 0); + if (ret < 0) { + wl1271_error("failed to send scan start command"); + goto out_free; + } + +out_free: + kfree(start); + return ret; +} + +int wl12xx_sched_scan_start(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies) +{ + int ret; + + ret = wl1271_scan_sched_scan_config(wl, wlvif, req, ies); + if (ret < 0) + return ret; + + return wl1271_scan_sched_scan_start(wl, wlvif); +} + +void wl12xx_scan_sched_scan_stop(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + struct wl1271_cmd_sched_scan_stop *stop; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd periodic scan stop"); + + /* FIXME: what to do if alloc'ing to stop fails? */ + stop = kzalloc(sizeof(*stop), GFP_KERNEL); + if (!stop) { + wl1271_error("failed to alloc memory to send sched scan stop"); + return; + } + + stop->role_id = wlvif->role_id; + stop->tag = WL1271_SCAN_DEFAULT_TAG; + + ret = wl1271_cmd_send(wl, CMD_STOP_PERIODIC_SCAN, stop, + sizeof(*stop), 0); + if (ret < 0) { + wl1271_error("failed to send sched scan stop command"); + goto out_free; + } + +out_free: + kfree(stop); +} + +int wl12xx_scan_start(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct cfg80211_scan_request *req) +{ + wl1271_scan_stm(wl, wlvif); + return 0; +} + +void wl12xx_scan_completed(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + wl1271_scan_stm(wl, wlvif); +} diff --git a/drivers/net/wireless/ti/wl12xx/scan.h b/drivers/net/wireless/ti/wl12xx/scan.h new file mode 100644 index 0000000..264af7a --- /dev/null +++ b/drivers/net/wireless/ti/wl12xx/scan.h @@ -0,0 +1,140 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 2012 Texas Instruments. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL12XX_SCAN_H__ +#define __WL12XX_SCAN_H__ + +#include "../wlcore/wlcore.h" +#include "../wlcore/cmd.h" +#include "../wlcore/scan.h" + +#define WL12XX_MAX_CHANNELS_5GHZ 23 + +struct basic_scan_params { + /* Scan option flags (WL1271_SCAN_OPT_*) */ + __le16 scan_options; + u8 role_id; + /* Number of scan channels in the list (maximum 30) */ + u8 n_ch; + /* This field indicates the number of probe requests to send + per channel for an active scan */ + u8 n_probe_reqs; + u8 tid_trigger; + u8 ssid_len; + u8 use_ssid_list; + + /* Rate bit field for sending the probes */ + __le32 tx_rate; + + u8 ssid[IEEE80211_MAX_SSID_LEN]; + /* Band to scan */ + u8 band; + + u8 scan_tag; + u8 padding2[2]; +} __packed; + +struct basic_scan_channel_params { + /* Duration in TU to wait for frames on a channel for active scan */ + __le32 min_duration; + __le32 max_duration; + __le32 bssid_lsb; + __le16 bssid_msb; + u8 early_termination; + u8 tx_power_att; + u8 channel; + /* FW internal use only! */ + u8 dfs_candidate; + u8 activity_detected; + u8 pad; +} __packed; + +struct wl1271_cmd_scan { + struct wl1271_cmd_header header; + + struct basic_scan_params params; + struct basic_scan_channel_params channels[WL1271_SCAN_MAX_CHANNELS]; + + /* src mac address */ + u8 addr[ETH_ALEN]; + u8 padding[2]; +} __packed; + +struct wl1271_cmd_sched_scan_config { + struct wl1271_cmd_header header; + + __le32 intervals[SCAN_MAX_CYCLE_INTERVALS]; + + s8 rssi_threshold; /* for filtering (in dBm) */ + s8 snr_threshold; /* for filtering (in dB) */ + + u8 cycles; /* maximum number of scan cycles */ + u8 report_after; /* report when this number of results are received */ + u8 terminate; /* stop scanning after reporting */ + + u8 tag; + u8 bss_type; /* for filtering */ + u8 filter_type; + + u8 ssid_len; /* For SCAN_SSID_FILTER_SPECIFIC */ + u8 ssid[IEEE80211_MAX_SSID_LEN]; + + u8 n_probe_reqs; /* Number of probes requests per channel */ + + u8 passive[SCAN_MAX_BANDS]; + u8 active[SCAN_MAX_BANDS]; + + u8 dfs; + + u8 n_pactive_ch; /* number of pactive (passive until fw detects energy) + channels in BG band */ + u8 role_id; + u8 padding[1]; + struct conn_scan_ch_params channels_2[MAX_CHANNELS_2GHZ]; + struct conn_scan_ch_params channels_5[WL12XX_MAX_CHANNELS_5GHZ]; + struct conn_scan_ch_params channels_4[MAX_CHANNELS_4GHZ]; +} __packed; + +struct wl1271_cmd_sched_scan_start { + struct wl1271_cmd_header header; + + u8 tag; + u8 role_id; + u8 padding[2]; +} __packed; + +struct wl1271_cmd_sched_scan_stop { + struct wl1271_cmd_header header; + + u8 tag; + u8 role_id; + u8 padding[2]; +} __packed; + +int wl12xx_scan_start(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct cfg80211_scan_request *req); +int wl12xx_scan_stop(struct wl1271 *wl, struct wl12xx_vif *wlvif); +void wl12xx_scan_completed(struct wl1271 *wl, struct wl12xx_vif *wlvif); +int wl12xx_sched_scan_start(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies); +void wl12xx_scan_sched_scan_stop(struct wl1271 *wl, struct wl12xx_vif *wlvif); +#endif diff --git a/drivers/net/wireless/ti/wl12xx/wl12xx.h b/drivers/net/wireless/ti/wl12xx/wl12xx.h new file mode 100644 index 0000000..9e5484a --- /dev/null +++ b/drivers/net/wireless/ti/wl12xx/wl12xx.h @@ -0,0 +1,82 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 2011 Texas Instruments Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL12XX_PRIV_H__ +#define __WL12XX_PRIV_H__ + +#include "conf.h" + +/* WiLink 6/7 chip IDs */ +#define CHIP_ID_127X_PG10 (0x04030101) +#define CHIP_ID_127X_PG20 (0x04030111) +#define CHIP_ID_128X_PG10 (0x05030101) +#define CHIP_ID_128X_PG20 (0x05030111) + +/* FW chip version for wl127x */ +#define WL127X_CHIP_VER 6 +/* minimum single-role FW version for wl127x */ +#define WL127X_IFTYPE_SR_VER 3 +#define WL127X_MAJOR_SR_VER 10 +#define WL127X_SUBTYPE_SR_VER WLCORE_FW_VER_IGNORE +#define WL127X_MINOR_SR_VER 133 +/* minimum multi-role FW version for wl127x */ +#define WL127X_IFTYPE_MR_VER 5 +#define WL127X_MAJOR_MR_VER 7 +#define WL127X_SUBTYPE_MR_VER WLCORE_FW_VER_IGNORE +#define WL127X_MINOR_MR_VER 42 + +/* FW chip version for wl128x */ +#define WL128X_CHIP_VER 7 +/* minimum single-role FW version for wl128x */ +#define WL128X_IFTYPE_SR_VER 3 +#define WL128X_MAJOR_SR_VER 10 +#define WL128X_SUBTYPE_SR_VER WLCORE_FW_VER_IGNORE +#define WL128X_MINOR_SR_VER 133 +/* minimum multi-role FW version for wl128x */ +#define WL128X_IFTYPE_MR_VER 5 +#define WL128X_MAJOR_MR_VER 7 +#define WL128X_SUBTYPE_MR_VER WLCORE_FW_VER_IGNORE +#define WL128X_MINOR_MR_VER 42 + +#define WL12XX_AGGR_BUFFER_SIZE (4 * PAGE_SIZE) + +#define WL12XX_NUM_TX_DESCRIPTORS 16 +#define WL12XX_NUM_RX_DESCRIPTORS 8 + +#define WL12XX_NUM_MAC_ADDRESSES 2 + +#define WL12XX_RX_BA_MAX_SESSIONS 3 + +struct wl127x_rx_mem_pool_addr { + u32 addr; + u32 addr_extra; +}; + +struct wl12xx_priv { + struct wl12xx_priv_conf conf; + + int ref_clock; + int tcxo_clock; + + struct wl127x_rx_mem_pool_addr *rx_mem_addr; +}; + +#endif /* __WL12XX_PRIV_H__ */ diff --git a/drivers/net/wireless/ti/wl18xx/Kconfig b/drivers/net/wireless/ti/wl18xx/Kconfig new file mode 100644 index 0000000..a9c7df7 --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/Kconfig @@ -0,0 +1,8 @@ +config WL18XX + tristate "TI wl18xx support" + depends on m + depends on MAC80211 + select WLCORE + ---help--- + This module adds support for wireless adapters based on TI + WiLink 8 chipsets. diff --git a/drivers/net/wireless/ti/wl18xx/Makefile b/drivers/net/wireless/ti/wl18xx/Makefile new file mode 100644 index 0000000..577d80b --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/Makefile @@ -0,0 +1,3 @@ +wl18xx-objs = main.o acx.o tx.o io.o debugfs.o scan.o cmd.o event.o + +obj-$(CPTCFG_WL18XX) += wl18xx.o diff --git a/drivers/net/wireless/ti/wl18xx/acx.c b/drivers/net/wireless/ti/wl18xx/acx.c new file mode 100644 index 0000000..a169bb5 --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/acx.c @@ -0,0 +1,196 @@ +/* + * This file is part of wl18xx + * + * Copyright (C) 2011 Texas Instruments Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "../wlcore/cmd.h" +#include "../wlcore/debug.h" +#include "../wlcore/acx.h" + +#include "acx.h" + +int wl18xx_acx_host_if_cfg_bitmap(struct wl1271 *wl, u32 host_cfg_bitmap, + u32 sdio_blk_size, u32 extra_mem_blks, + u32 len_field_size) +{ + struct wl18xx_acx_host_config_bitmap *bitmap_conf; + int ret; + + wl1271_debug(DEBUG_ACX, "acx cfg bitmap %d blk %d spare %d field %d", + host_cfg_bitmap, sdio_blk_size, extra_mem_blks, + len_field_size); + + bitmap_conf = kzalloc(sizeof(*bitmap_conf), GFP_KERNEL); + if (!bitmap_conf) { + ret = -ENOMEM; + goto out; + } + + bitmap_conf->host_cfg_bitmap = cpu_to_le32(host_cfg_bitmap); + bitmap_conf->host_sdio_block_size = cpu_to_le32(sdio_blk_size); + bitmap_conf->extra_mem_blocks = cpu_to_le32(extra_mem_blks); + bitmap_conf->length_field_size = cpu_to_le32(len_field_size); + + ret = wl1271_cmd_configure(wl, ACX_HOST_IF_CFG_BITMAP, + bitmap_conf, sizeof(*bitmap_conf)); + if (ret < 0) { + wl1271_warning("wl1271 bitmap config opt failed: %d", ret); + goto out; + } + +out: + kfree(bitmap_conf); + + return ret; +} + +int wl18xx_acx_set_checksum_state(struct wl1271 *wl) +{ + struct wl18xx_acx_checksum_state *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx checksum state"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->checksum_state = CHECKSUM_OFFLOAD_ENABLED; + + ret = wl1271_cmd_configure(wl, ACX_CSUM_CONFIG, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("failed to set Tx checksum state: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl18xx_acx_clear_statistics(struct wl1271 *wl) +{ + struct wl18xx_acx_clear_statistics *acx; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx clear statistics"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + ret = wl1271_cmd_configure(wl, ACX_CLEAR_STATISTICS, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("failed to clear firmware statistics: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl18xx_acx_peer_ht_operation_mode(struct wl1271 *wl, u8 hlid, bool wide) +{ + struct wlcore_peer_ht_operation_mode *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx peer ht operation mode hlid %d bw %d", + hlid, wide); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->hlid = hlid; + acx->bandwidth = wide ? WLCORE_BANDWIDTH_40MHZ : WLCORE_BANDWIDTH_20MHZ; + + ret = wl1271_cmd_configure(wl, ACX_PEER_HT_OPERATION_MODE_CFG, acx, + sizeof(*acx)); + + if (ret < 0) { + wl1271_warning("acx peer ht operation mode failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; + +} + +/* + * this command is basically the same as wl1271_acx_ht_capabilities, + * with the addition of supported rates. they should be unified in + * the next fw api change + */ +int wl18xx_acx_set_peer_cap(struct wl1271 *wl, + struct ieee80211_sta_ht_cap *ht_cap, + bool allow_ht_operation, + u32 rate_set, u8 hlid) +{ + struct wlcore_acx_peer_cap *acx; + int ret = 0; + u32 ht_capabilites = 0; + + wl1271_debug(DEBUG_ACX, + "acx set cap ht_supp: %d ht_cap: %d rates: 0x%x", + ht_cap->ht_supported, ht_cap->cap, rate_set); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + if (allow_ht_operation && ht_cap->ht_supported) { + /* no need to translate capabilities - use the spec values */ + ht_capabilites = ht_cap->cap; + + /* + * this bit is not employed by the spec but only by FW to + * indicate peer HT support + */ + ht_capabilites |= WL12XX_HT_CAP_HT_OPERATION; + + /* get data from A-MPDU parameters field */ + acx->ampdu_max_length = ht_cap->ampdu_factor; + acx->ampdu_min_spacing = ht_cap->ampdu_density; + } + + acx->hlid = hlid; + acx->ht_capabilites = cpu_to_le32(ht_capabilites); + acx->supported_rates = cpu_to_le32(rate_set); + + ret = wl1271_cmd_configure(wl, ACX_PEER_CAP, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx ht capabilities setting failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} diff --git a/drivers/net/wireless/ti/wl18xx/acx.h b/drivers/net/wireless/ti/wl18xx/acx.h new file mode 100644 index 0000000..0e636de --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/acx.h @@ -0,0 +1,340 @@ +/* + * This file is part of wl18xx + * + * Copyright (C) 2011 Texas Instruments. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL18XX_ACX_H__ +#define __WL18XX_ACX_H__ + +#include "../wlcore/wlcore.h" +#include "../wlcore/acx.h" + +enum { + ACX_NS_IPV6_FILTER = 0x0050, + ACX_PEER_HT_OPERATION_MODE_CFG = 0x0051, + ACX_CSUM_CONFIG = 0x0052, + ACX_SIM_CONFIG = 0x0053, + ACX_CLEAR_STATISTICS = 0x0054, + ACX_AUTO_RX_STREAMING = 0x0055, + ACX_PEER_CAP = 0x0056 +}; + +/* numbers of bits the length field takes (add 1 for the actual number) */ +#define WL18XX_HOST_IF_LEN_SIZE_FIELD 15 + +#define WL18XX_ACX_EVENTS_VECTOR (WL1271_ACX_INTR_WATCHDOG | \ + WL1271_ACX_INTR_INIT_COMPLETE | \ + WL1271_ACX_INTR_EVENT_A | \ + WL1271_ACX_INTR_EVENT_B | \ + WL1271_ACX_INTR_CMD_COMPLETE | \ + WL1271_ACX_INTR_HW_AVAILABLE | \ + WL1271_ACX_INTR_DATA | \ + WL1271_ACX_SW_INTR_WATCHDOG) + +#define WL18XX_INTR_MASK (WL1271_ACX_INTR_WATCHDOG | \ + WL1271_ACX_INTR_EVENT_A | \ + WL1271_ACX_INTR_EVENT_B | \ + WL1271_ACX_INTR_HW_AVAILABLE | \ + WL1271_ACX_INTR_DATA | \ + WL1271_ACX_SW_INTR_WATCHDOG) + +struct wl18xx_acx_host_config_bitmap { + struct acx_header header; + + __le32 host_cfg_bitmap; + + __le32 host_sdio_block_size; + + /* extra mem blocks per frame in TX. */ + __le32 extra_mem_blocks; + + /* + * number of bits of the length field in the first TX word + * (up to 15 - for using the entire 16 bits). + */ + __le32 length_field_size; + +} __packed; + +enum { + CHECKSUM_OFFLOAD_DISABLED = 0, + CHECKSUM_OFFLOAD_ENABLED = 1, + CHECKSUM_OFFLOAD_FAKE_RX = 2, + CHECKSUM_OFFLOAD_INVALID = 0xFF +}; + +struct wl18xx_acx_checksum_state { + struct acx_header header; + + /* enum acx_checksum_state */ + u8 checksum_state; + u8 pad[3]; +} __packed; + + +struct wl18xx_acx_error_stats { + u32 error_frame; + u32 error_null_Frame_tx_start; + u32 error_numll_frame_cts_start; + u32 error_bar_retry; + u32 error_frame_cts_nul_flid; +} __packed; + +struct wl18xx_acx_debug_stats { + u32 debug1; + u32 debug2; + u32 debug3; + u32 debug4; + u32 debug5; + u32 debug6; +} __packed; + +struct wl18xx_acx_ring_stats { + u32 prepared_descs; + u32 tx_cmplt; +} __packed; + +struct wl18xx_acx_tx_stats { + u32 tx_prepared_descs; + u32 tx_cmplt; + u32 tx_template_prepared; + u32 tx_data_prepared; + u32 tx_template_programmed; + u32 tx_data_programmed; + u32 tx_burst_programmed; + u32 tx_starts; + u32 tx_imm_resp; + u32 tx_start_templates; + u32 tx_start_int_templates; + u32 tx_start_fw_gen; + u32 tx_start_data; + u32 tx_start_null_frame; + u32 tx_exch; + u32 tx_retry_template; + u32 tx_retry_data; + u32 tx_exch_pending; + u32 tx_exch_expiry; + u32 tx_done_template; + u32 tx_done_data; + u32 tx_done_int_template; + u32 tx_frame_checksum; + u32 tx_checksum_result; + u32 frag_called; + u32 frag_mpdu_alloc_failed; + u32 frag_init_called; + u32 frag_in_process_called; + u32 frag_tkip_called; + u32 frag_key_not_found; + u32 frag_need_fragmentation; + u32 frag_bad_mblk_num; + u32 frag_failed; + u32 frag_cache_hit; + u32 frag_cache_miss; +} __packed; + +struct wl18xx_acx_rx_stats { + u32 rx_beacon_early_term; + u32 rx_out_of_mpdu_nodes; + u32 rx_hdr_overflow; + u32 rx_dropped_frame; + u32 rx_done_stage; + u32 rx_done; + u32 rx_defrag; + u32 rx_defrag_end; + u32 rx_cmplt; + u32 rx_pre_complt; + u32 rx_cmplt_task; + u32 rx_phy_hdr; + u32 rx_timeout; + u32 rx_timeout_wa; + u32 rx_wa_density_dropped_frame; + u32 rx_wa_ba_not_expected; + u32 rx_frame_checksum; + u32 rx_checksum_result; + u32 defrag_called; + u32 defrag_init_called; + u32 defrag_in_process_called; + u32 defrag_tkip_called; + u32 defrag_need_defrag; + u32 defrag_decrypt_failed; + u32 decrypt_key_not_found; + u32 defrag_need_decrypt; + u32 rx_tkip_replays; +} __packed; + +struct wl18xx_acx_isr_stats { + u32 irqs; +} __packed; + +#define PWR_STAT_MAX_CONT_MISSED_BCNS_SPREAD 10 + +struct wl18xx_acx_pwr_stats { + u32 missing_bcns_cnt; + u32 rcvd_bcns_cnt; + u32 connection_out_of_sync; + u32 cont_miss_bcns_spread[PWR_STAT_MAX_CONT_MISSED_BCNS_SPREAD]; + u32 rcvd_awake_bcns_cnt; +} __packed; + +struct wl18xx_acx_event_stats { + u32 calibration; + u32 rx_mismatch; + u32 rx_mem_empty; +} __packed; + +struct wl18xx_acx_ps_poll_stats { + u32 ps_poll_timeouts; + u32 upsd_timeouts; + u32 upsd_max_ap_turn; + u32 ps_poll_max_ap_turn; + u32 ps_poll_utilization; + u32 upsd_utilization; +} __packed; + +struct wl18xx_acx_rx_filter_stats { + u32 beacon_filter; + u32 arp_filter; + u32 mc_filter; + u32 dup_filter; + u32 data_filter; + u32 ibss_filter; + u32 protection_filter; + u32 accum_arp_pend_requests; + u32 max_arp_queue_dep; +} __packed; + +struct wl18xx_acx_rx_rate_stats { + u32 rx_frames_per_rates[50]; +} __packed; + +#define AGGR_STATS_TX_AGG 16 +#define AGGR_STATS_TX_RATE 16 +#define AGGR_STATS_RX_SIZE_LEN 16 + +struct wl18xx_acx_aggr_stats { + u32 tx_agg_vs_rate[AGGR_STATS_TX_AGG * AGGR_STATS_TX_RATE]; + u32 rx_size[AGGR_STATS_RX_SIZE_LEN]; +} __packed; + +#define PIPE_STATS_HW_FIFO 11 + +struct wl18xx_acx_pipeline_stats { + u32 hs_tx_stat_fifo_int; + u32 hs_rx_stat_fifo_int; + u32 tcp_tx_stat_fifo_int; + u32 tcp_rx_stat_fifo_int; + u32 enc_tx_stat_fifo_int; + u32 enc_rx_stat_fifo_int; + u32 rx_complete_stat_fifo_int; + u32 pre_proc_swi; + u32 post_proc_swi; + u32 sec_frag_swi; + u32 pre_to_defrag_swi; + u32 defrag_to_csum_swi; + u32 csum_to_rx_xfer_swi; + u32 dec_packet_in; + u32 dec_packet_in_fifo_full; + u32 dec_packet_out; + u32 cs_rx_packet_in; + u32 cs_rx_packet_out; + u16 pipeline_fifo_full[PIPE_STATS_HW_FIFO]; +} __packed; + +struct wl18xx_acx_mem_stats { + u32 rx_free_mem_blks; + u32 tx_free_mem_blks; + u32 fwlog_free_mem_blks; + u32 fw_gen_free_mem_blks; +} __packed; + +struct wl18xx_acx_statistics { + struct acx_header header; + + struct wl18xx_acx_error_stats error; + struct wl18xx_acx_debug_stats debug; + struct wl18xx_acx_tx_stats tx; + struct wl18xx_acx_rx_stats rx; + struct wl18xx_acx_isr_stats isr; + struct wl18xx_acx_pwr_stats pwr; + struct wl18xx_acx_ps_poll_stats ps_poll; + struct wl18xx_acx_rx_filter_stats rx_filter; + struct wl18xx_acx_rx_rate_stats rx_rate; + struct wl18xx_acx_aggr_stats aggr_size; + struct wl18xx_acx_pipeline_stats pipeline; + struct wl18xx_acx_mem_stats mem; +} __packed; + +struct wl18xx_acx_clear_statistics { + struct acx_header header; +}; + +enum wlcore_bandwidth { + WLCORE_BANDWIDTH_20MHZ, + WLCORE_BANDWIDTH_40MHZ, +}; + +struct wlcore_peer_ht_operation_mode { + struct acx_header header; + + u8 hlid; + u8 bandwidth; /* enum wlcore_bandwidth */ + u8 padding[2]; +}; + +/* + * ACX_PEER_CAP + * this struct is very similar to wl1271_acx_ht_capabilities, with the + * addition of supported rates + */ +struct wlcore_acx_peer_cap { + struct acx_header header; + + /* bitmask of capability bits supported by the peer */ + __le32 ht_capabilites; + + /* rates supported by the remote peer */ + __le32 supported_rates; + + /* Indicates to which link these capabilities apply. */ + u8 hlid; + + /* + * This the maximum A-MPDU length supported by the AP. The FW may not + * exceed this length when sending A-MPDUs + */ + u8 ampdu_max_length; + + /* This is the minimal spacing required when sending A-MPDUs to the AP*/ + u8 ampdu_min_spacing; + + u8 padding; +} __packed; + +int wl18xx_acx_host_if_cfg_bitmap(struct wl1271 *wl, u32 host_cfg_bitmap, + u32 sdio_blk_size, u32 extra_mem_blks, + u32 len_field_size); +int wl18xx_acx_set_checksum_state(struct wl1271 *wl); +int wl18xx_acx_clear_statistics(struct wl1271 *wl); +int wl18xx_acx_peer_ht_operation_mode(struct wl1271 *wl, u8 hlid, bool wide); +int wl18xx_acx_set_peer_cap(struct wl1271 *wl, + struct ieee80211_sta_ht_cap *ht_cap, + bool allow_ht_operation, + u32 rate_set, u8 hlid); + +#endif /* __WL18XX_ACX_H__ */ diff --git a/drivers/net/wireless/ti/wl18xx/cmd.c b/drivers/net/wireless/ti/wl18xx/cmd.c new file mode 100644 index 0000000..7649c75 --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/cmd.c @@ -0,0 +1,80 @@ +/* + * This file is part of wl18xx + * + * Copyright (C) 2011 Texas Instruments Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "../wlcore/cmd.h" +#include "../wlcore/debug.h" +#include "../wlcore/hw_ops.h" + +#include "cmd.h" + +int wl18xx_cmd_channel_switch(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct ieee80211_channel_switch *ch_switch) +{ + struct wl18xx_cmd_channel_switch *cmd; + u32 supported_rates; + int ret; + + wl1271_debug(DEBUG_ACX, "cmd channel switch"); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->role_id = wlvif->role_id; + cmd->channel = ch_switch->chandef.chan->hw_value; + cmd->switch_time = ch_switch->count; + cmd->stop_tx = ch_switch->block_tx; + + switch (ch_switch->chandef.chan->band) { + case IEEE80211_BAND_2GHZ: + cmd->band = WLCORE_BAND_2_4GHZ; + break; + case IEEE80211_BAND_5GHZ: + cmd->band = WLCORE_BAND_5GHZ; + break; + default: + wl1271_error("invalid channel switch band: %d", + ch_switch->chandef.chan->band); + ret = -EINVAL; + goto out_free; + } + + supported_rates = CONF_TX_ENABLED_RATES | CONF_TX_MCS_RATES | + wlcore_hw_sta_get_ap_rate_mask(wl, wlvif); + if (wlvif->p2p) + supported_rates &= ~CONF_TX_CCK_RATES; + cmd->local_supported_rates = cpu_to_le32(supported_rates); + cmd->channel_type = wlvif->channel_type; + + 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; +} diff --git a/drivers/net/wireless/ti/wl18xx/cmd.h b/drivers/net/wireless/ti/wl18xx/cmd.h new file mode 100644 index 0000000..6687d10 --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/cmd.h @@ -0,0 +1,52 @@ +/* + * This file is part of wl18xx + * + * Copyright (C) 2011 Texas Instruments. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL18XX_CMD_H__ +#define __WL18XX_CMD_H__ + +#include "../wlcore/wlcore.h" +#include "../wlcore/acx.h" + +struct wl18xx_cmd_channel_switch { + struct wl1271_cmd_header header; + + u8 role_id; + + /* The new serving channel */ + u8 channel; + /* Relative time of the serving channel switch in TBTT units */ + u8 switch_time; + /* Stop the role TX, should expect it after radar detection */ + u8 stop_tx; + + __le32 local_supported_rates; + + u8 channel_type; + u8 band; + + u8 padding[2]; +} __packed; + +int wl18xx_cmd_channel_switch(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct ieee80211_channel_switch *ch_switch); + +#endif diff --git a/drivers/net/wireless/ti/wl18xx/conf.h b/drivers/net/wireless/ti/wl18xx/conf.h new file mode 100644 index 0000000..e34302e --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/conf.h @@ -0,0 +1,121 @@ +/* + * This file is part of wl18xx + * + * Copyright (C) 2011 Texas Instruments Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL18XX_CONF_H__ +#define __WL18XX_CONF_H__ + +#define WL18XX_CONF_MAGIC 0x10e100ca +#define WL18XX_CONF_VERSION (WLCORE_CONF_VERSION | 0x0006) +#define WL18XX_CONF_MASK 0x0000ffff +#define WL18XX_CONF_SIZE (WLCORE_CONF_SIZE + \ + sizeof(struct wl18xx_priv_conf)) + +#define NUM_OF_CHANNELS_11_ABG 150 +#define NUM_OF_CHANNELS_11_P 7 +#define SRF_TABLE_LEN 16 +#define PIN_MUXING_SIZE 2 +#define WL18XX_TRACE_LOSS_GAPS_TX 10 +#define WL18XX_TRACE_LOSS_GAPS_RX 18 + +struct wl18xx_mac_and_phy_params { + u8 phy_standalone; + u8 spare0; + u8 enable_clpc; + u8 enable_tx_low_pwr_on_siso_rdl; + u8 auto_detect; + u8 dedicated_fem; + + u8 low_band_component; + + /* Bit 0: One Hot, Bit 1: Control Enable, Bit 2: 1.8V, Bit 3: 3V */ + u8 low_band_component_type; + + u8 high_band_component; + + /* Bit 0: One Hot, Bit 1: Control Enable, Bit 2: 1.8V, Bit 3: 3V */ + u8 high_band_component_type; + u8 number_of_assembled_ant2_4; + u8 number_of_assembled_ant5; + u8 pin_muxing_platform_options[PIN_MUXING_SIZE]; + u8 external_pa_dc2dc; + u8 tcxo_ldo_voltage; + u8 xtal_itrim_val; + u8 srf_state; + u8 srf1[SRF_TABLE_LEN]; + u8 srf2[SRF_TABLE_LEN]; + u8 srf3[SRF_TABLE_LEN]; + u8 io_configuration; + u8 sdio_configuration; + u8 settings; + u8 rx_profile; + u8 per_chan_pwr_limit_arr_11abg[NUM_OF_CHANNELS_11_ABG]; + u8 pwr_limit_reference_11_abg; + u8 per_chan_pwr_limit_arr_11p[NUM_OF_CHANNELS_11_P]; + u8 pwr_limit_reference_11p; + u8 spare1; + u8 per_chan_bo_mode_11_abg[13]; + u8 per_chan_bo_mode_11_p[4]; + u8 primary_clock_setting_time; + u8 clock_valid_on_wake_up; + u8 secondary_clock_setting_time; + u8 board_type; + /* enable point saturation */ + u8 psat; + /* low/medium/high Tx power in dBm for STA-HP BG */ + s8 low_power_val; + s8 med_power_val; + s8 high_power_val; + s8 per_sub_band_tx_trace_loss[WL18XX_TRACE_LOSS_GAPS_TX]; + s8 per_sub_band_rx_trace_loss[WL18XX_TRACE_LOSS_GAPS_RX]; + u8 tx_rf_margin; + /* low/medium/high Tx power in dBm for other role */ + s8 low_power_val_2nd; + s8 med_power_val_2nd; + s8 high_power_val_2nd; + + u8 padding[1]; +} __packed; + +enum wl18xx_ht_mode { + /* Default - use MIMO, fallback to SISO20 */ + HT_MODE_DEFAULT = 0, + + /* Wide - use SISO40 */ + HT_MODE_WIDE = 1, + + /* Use SISO20 */ + HT_MODE_SISO20 = 2, +}; + +struct wl18xx_ht_settings { + /* DEFAULT / WIDE / SISO20 */ + u8 mode; +} __packed; + +struct wl18xx_priv_conf { + /* Module params structures */ + struct wl18xx_ht_settings ht; + + /* this structure is copied wholesale to FW */ + struct wl18xx_mac_and_phy_params phy; +} __packed; + +#endif /* __WL18XX_CONF_H__ */ diff --git a/drivers/net/wireless/ti/wl18xx/debugfs.c b/drivers/net/wireless/ti/wl18xx/debugfs.c new file mode 100644 index 0000000..7f1669c --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/debugfs.c @@ -0,0 +1,403 @@ +/* + * This file is part of wl18xx + * + * Copyright (C) 2009 Nokia Corporation + * Copyright (C) 2011-2012 Texas Instruments + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "../wlcore/debugfs.h" +#include "../wlcore/wlcore.h" + +#include "wl18xx.h" +#include "acx.h" +#include "debugfs.h" + +#define WL18XX_DEBUGFS_FWSTATS_FILE(a, b, c) \ + DEBUGFS_FWSTATS_FILE(a, b, c, wl18xx_acx_statistics) +#define WL18XX_DEBUGFS_FWSTATS_FILE_ARRAY(a, b, c) \ + DEBUGFS_FWSTATS_FILE_ARRAY(a, b, c, wl18xx_acx_statistics) + + +WL18XX_DEBUGFS_FWSTATS_FILE(debug, debug1, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(debug, debug2, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(debug, debug3, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(debug, debug4, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(debug, debug5, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(debug, debug6, "%u"); + +WL18XX_DEBUGFS_FWSTATS_FILE(error, error_frame, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(error, error_null_Frame_tx_start, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(error, error_numll_frame_cts_start, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(error, error_bar_retry, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(error, error_frame_cts_nul_flid, "%u"); + +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_prepared_descs, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_cmplt, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_template_prepared, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_data_prepared, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_template_programmed, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_data_programmed, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_burst_programmed, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_starts, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_imm_resp, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_start_templates, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_start_int_templates, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_start_fw_gen, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_start_data, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_start_null_frame, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_exch, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_retry_template, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_retry_data, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_exch_pending, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_exch_expiry, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_done_template, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_done_data, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_done_int_template, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_frame_checksum, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, tx_checksum_result, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, frag_called, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, frag_mpdu_alloc_failed, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, frag_init_called, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, frag_in_process_called, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, frag_tkip_called, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, frag_key_not_found, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, frag_need_fragmentation, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, frag_bad_mblk_num, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, frag_failed, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, frag_cache_hit, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(tx, frag_cache_miss, "%u"); + +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_beacon_early_term, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_out_of_mpdu_nodes, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_hdr_overflow, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_dropped_frame, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_done, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_defrag, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_defrag_end, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_cmplt, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_pre_complt, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_cmplt_task, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_phy_hdr, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_timeout, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_timeout_wa, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_wa_density_dropped_frame, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_wa_ba_not_expected, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_frame_checksum, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_checksum_result, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, defrag_called, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, defrag_init_called, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, defrag_in_process_called, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, defrag_tkip_called, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, defrag_need_defrag, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, defrag_decrypt_failed, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, decrypt_key_not_found, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, defrag_need_decrypt, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx, rx_tkip_replays, "%u"); + +WL18XX_DEBUGFS_FWSTATS_FILE(isr, irqs, "%u"); + +WL18XX_DEBUGFS_FWSTATS_FILE(pwr, missing_bcns_cnt, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pwr, rcvd_bcns_cnt, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pwr, connection_out_of_sync, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE_ARRAY(pwr, cont_miss_bcns_spread, + PWR_STAT_MAX_CONT_MISSED_BCNS_SPREAD); +WL18XX_DEBUGFS_FWSTATS_FILE(pwr, rcvd_awake_bcns_cnt, "%u"); + + +WL18XX_DEBUGFS_FWSTATS_FILE(ps_poll, ps_poll_timeouts, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(ps_poll, upsd_timeouts, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(ps_poll, upsd_max_ap_turn, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(ps_poll, ps_poll_max_ap_turn, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(ps_poll, ps_poll_utilization, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(ps_poll, upsd_utilization, "%u"); + +WL18XX_DEBUGFS_FWSTATS_FILE(rx_filter, beacon_filter, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx_filter, arp_filter, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx_filter, mc_filter, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx_filter, dup_filter, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx_filter, data_filter, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx_filter, ibss_filter, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx_filter, protection_filter, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx_filter, accum_arp_pend_requests, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(rx_filter, max_arp_queue_dep, "%u"); + +WL18XX_DEBUGFS_FWSTATS_FILE(rx_rate, rx_frames_per_rates, "%u"); + +WL18XX_DEBUGFS_FWSTATS_FILE_ARRAY(aggr_size, tx_agg_vs_rate, + AGGR_STATS_TX_AGG*AGGR_STATS_TX_RATE); +WL18XX_DEBUGFS_FWSTATS_FILE_ARRAY(aggr_size, rx_size, + AGGR_STATS_RX_SIZE_LEN); + +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, hs_tx_stat_fifo_int, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, tcp_tx_stat_fifo_int, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, tcp_rx_stat_fifo_int, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, enc_tx_stat_fifo_int, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, enc_rx_stat_fifo_int, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, rx_complete_stat_fifo_int, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, pre_proc_swi, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, post_proc_swi, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, sec_frag_swi, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, pre_to_defrag_swi, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, defrag_to_csum_swi, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, csum_to_rx_xfer_swi, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, dec_packet_in, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, dec_packet_in_fifo_full, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, dec_packet_out, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, cs_rx_packet_in, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(pipeline, cs_rx_packet_out, "%u"); + +WL18XX_DEBUGFS_FWSTATS_FILE_ARRAY(pipeline, pipeline_fifo_full, + PIPE_STATS_HW_FIFO); + +WL18XX_DEBUGFS_FWSTATS_FILE(mem, rx_free_mem_blks, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(mem, tx_free_mem_blks, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(mem, fwlog_free_mem_blks, "%u"); +WL18XX_DEBUGFS_FWSTATS_FILE(mem, fw_gen_free_mem_blks, "%u"); + +static ssize_t conf_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + struct wl18xx_priv *priv = wl->priv; + struct wlcore_conf_header header; + char *buf, *pos; + size_t len; + int ret; + + len = WL18XX_CONF_SIZE; + buf = kmalloc(len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + header.magic = cpu_to_le32(WL18XX_CONF_MAGIC); + header.version = cpu_to_le32(WL18XX_CONF_VERSION); + header.checksum = 0; + + mutex_lock(&wl->mutex); + + pos = buf; + memcpy(pos, &header, sizeof(header)); + pos += sizeof(header); + memcpy(pos, &wl->conf, sizeof(wl->conf)); + pos += sizeof(wl->conf); + memcpy(pos, &priv->conf, sizeof(priv->conf)); + + mutex_unlock(&wl->mutex); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); + + kfree(buf); + return ret; +} + +static const struct file_operations conf_ops = { + .read = conf_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t clear_fw_stats_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + int ret; + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + ret = wl18xx_acx_clear_statistics(wl); + if (ret < 0) { + count = ret; + goto out; + } +out: + mutex_unlock(&wl->mutex); + return count; +} + +static const struct file_operations clear_fw_stats_ops = { + .write = clear_fw_stats_write, + .open = simple_open, + .llseek = default_llseek, +}; + +int wl18xx_debugfs_add_files(struct wl1271 *wl, + struct dentry *rootdir) +{ + int ret = 0; + struct dentry *entry, *stats, *moddir; + + moddir = debugfs_create_dir(KBUILD_MODNAME, rootdir); + if (!moddir || IS_ERR(moddir)) { + entry = moddir; + goto err; + } + + stats = debugfs_create_dir("fw_stats", moddir); + if (!stats || IS_ERR(stats)) { + entry = stats; + goto err; + } + + DEBUGFS_ADD(clear_fw_stats, stats); + + DEBUGFS_FWSTATS_ADD(debug, debug1); + DEBUGFS_FWSTATS_ADD(debug, debug2); + DEBUGFS_FWSTATS_ADD(debug, debug3); + DEBUGFS_FWSTATS_ADD(debug, debug4); + DEBUGFS_FWSTATS_ADD(debug, debug5); + DEBUGFS_FWSTATS_ADD(debug, debug6); + + DEBUGFS_FWSTATS_ADD(error, error_frame); + DEBUGFS_FWSTATS_ADD(error, error_null_Frame_tx_start); + DEBUGFS_FWSTATS_ADD(error, error_numll_frame_cts_start); + DEBUGFS_FWSTATS_ADD(error, error_bar_retry); + DEBUGFS_FWSTATS_ADD(error, error_frame_cts_nul_flid); + + DEBUGFS_FWSTATS_ADD(tx, tx_prepared_descs); + DEBUGFS_FWSTATS_ADD(tx, tx_cmplt); + DEBUGFS_FWSTATS_ADD(tx, tx_template_prepared); + DEBUGFS_FWSTATS_ADD(tx, tx_data_prepared); + DEBUGFS_FWSTATS_ADD(tx, tx_template_programmed); + DEBUGFS_FWSTATS_ADD(tx, tx_data_programmed); + DEBUGFS_FWSTATS_ADD(tx, tx_burst_programmed); + DEBUGFS_FWSTATS_ADD(tx, tx_starts); + DEBUGFS_FWSTATS_ADD(tx, tx_imm_resp); + DEBUGFS_FWSTATS_ADD(tx, tx_start_templates); + DEBUGFS_FWSTATS_ADD(tx, tx_start_int_templates); + DEBUGFS_FWSTATS_ADD(tx, tx_start_fw_gen); + DEBUGFS_FWSTATS_ADD(tx, tx_start_data); + DEBUGFS_FWSTATS_ADD(tx, tx_start_null_frame); + DEBUGFS_FWSTATS_ADD(tx, tx_exch); + DEBUGFS_FWSTATS_ADD(tx, tx_retry_template); + DEBUGFS_FWSTATS_ADD(tx, tx_retry_data); + DEBUGFS_FWSTATS_ADD(tx, tx_exch_pending); + DEBUGFS_FWSTATS_ADD(tx, tx_exch_expiry); + DEBUGFS_FWSTATS_ADD(tx, tx_done_template); + DEBUGFS_FWSTATS_ADD(tx, tx_done_data); + DEBUGFS_FWSTATS_ADD(tx, tx_done_int_template); + DEBUGFS_FWSTATS_ADD(tx, tx_frame_checksum); + DEBUGFS_FWSTATS_ADD(tx, tx_checksum_result); + DEBUGFS_FWSTATS_ADD(tx, frag_called); + DEBUGFS_FWSTATS_ADD(tx, frag_mpdu_alloc_failed); + DEBUGFS_FWSTATS_ADD(tx, frag_init_called); + DEBUGFS_FWSTATS_ADD(tx, frag_in_process_called); + DEBUGFS_FWSTATS_ADD(tx, frag_tkip_called); + DEBUGFS_FWSTATS_ADD(tx, frag_key_not_found); + DEBUGFS_FWSTATS_ADD(tx, frag_need_fragmentation); + DEBUGFS_FWSTATS_ADD(tx, frag_bad_mblk_num); + DEBUGFS_FWSTATS_ADD(tx, frag_failed); + DEBUGFS_FWSTATS_ADD(tx, frag_cache_hit); + DEBUGFS_FWSTATS_ADD(tx, frag_cache_miss); + + DEBUGFS_FWSTATS_ADD(rx, rx_beacon_early_term); + DEBUGFS_FWSTATS_ADD(rx, rx_out_of_mpdu_nodes); + DEBUGFS_FWSTATS_ADD(rx, rx_hdr_overflow); + DEBUGFS_FWSTATS_ADD(rx, rx_dropped_frame); + DEBUGFS_FWSTATS_ADD(rx, rx_done); + DEBUGFS_FWSTATS_ADD(rx, rx_defrag); + DEBUGFS_FWSTATS_ADD(rx, rx_defrag_end); + DEBUGFS_FWSTATS_ADD(rx, rx_cmplt); + DEBUGFS_FWSTATS_ADD(rx, rx_pre_complt); + DEBUGFS_FWSTATS_ADD(rx, rx_cmplt_task); + DEBUGFS_FWSTATS_ADD(rx, rx_phy_hdr); + DEBUGFS_FWSTATS_ADD(rx, rx_timeout); + DEBUGFS_FWSTATS_ADD(rx, rx_timeout_wa); + DEBUGFS_FWSTATS_ADD(rx, rx_wa_density_dropped_frame); + DEBUGFS_FWSTATS_ADD(rx, rx_wa_ba_not_expected); + DEBUGFS_FWSTATS_ADD(rx, rx_frame_checksum); + DEBUGFS_FWSTATS_ADD(rx, rx_checksum_result); + DEBUGFS_FWSTATS_ADD(rx, defrag_called); + DEBUGFS_FWSTATS_ADD(rx, defrag_init_called); + DEBUGFS_FWSTATS_ADD(rx, defrag_in_process_called); + DEBUGFS_FWSTATS_ADD(rx, defrag_tkip_called); + DEBUGFS_FWSTATS_ADD(rx, defrag_need_defrag); + DEBUGFS_FWSTATS_ADD(rx, defrag_decrypt_failed); + DEBUGFS_FWSTATS_ADD(rx, decrypt_key_not_found); + DEBUGFS_FWSTATS_ADD(rx, defrag_need_decrypt); + DEBUGFS_FWSTATS_ADD(rx, rx_tkip_replays); + + DEBUGFS_FWSTATS_ADD(isr, irqs); + + DEBUGFS_FWSTATS_ADD(pwr, missing_bcns_cnt); + DEBUGFS_FWSTATS_ADD(pwr, rcvd_bcns_cnt); + DEBUGFS_FWSTATS_ADD(pwr, connection_out_of_sync); + DEBUGFS_FWSTATS_ADD(pwr, cont_miss_bcns_spread); + DEBUGFS_FWSTATS_ADD(pwr, rcvd_awake_bcns_cnt); + + DEBUGFS_FWSTATS_ADD(ps_poll, ps_poll_timeouts); + DEBUGFS_FWSTATS_ADD(ps_poll, upsd_timeouts); + DEBUGFS_FWSTATS_ADD(ps_poll, upsd_max_ap_turn); + DEBUGFS_FWSTATS_ADD(ps_poll, ps_poll_max_ap_turn); + DEBUGFS_FWSTATS_ADD(ps_poll, ps_poll_utilization); + DEBUGFS_FWSTATS_ADD(ps_poll, upsd_utilization); + + DEBUGFS_FWSTATS_ADD(rx_filter, beacon_filter); + DEBUGFS_FWSTATS_ADD(rx_filter, arp_filter); + DEBUGFS_FWSTATS_ADD(rx_filter, mc_filter); + DEBUGFS_FWSTATS_ADD(rx_filter, dup_filter); + DEBUGFS_FWSTATS_ADD(rx_filter, data_filter); + DEBUGFS_FWSTATS_ADD(rx_filter, ibss_filter); + DEBUGFS_FWSTATS_ADD(rx_filter, protection_filter); + DEBUGFS_FWSTATS_ADD(rx_filter, accum_arp_pend_requests); + DEBUGFS_FWSTATS_ADD(rx_filter, max_arp_queue_dep); + + DEBUGFS_FWSTATS_ADD(rx_rate, rx_frames_per_rates); + + DEBUGFS_FWSTATS_ADD(aggr_size, tx_agg_vs_rate); + DEBUGFS_FWSTATS_ADD(aggr_size, rx_size); + + DEBUGFS_FWSTATS_ADD(pipeline, hs_tx_stat_fifo_int); + DEBUGFS_FWSTATS_ADD(pipeline, tcp_tx_stat_fifo_int); + DEBUGFS_FWSTATS_ADD(pipeline, tcp_rx_stat_fifo_int); + DEBUGFS_FWSTATS_ADD(pipeline, enc_tx_stat_fifo_int); + DEBUGFS_FWSTATS_ADD(pipeline, enc_rx_stat_fifo_int); + DEBUGFS_FWSTATS_ADD(pipeline, rx_complete_stat_fifo_int); + DEBUGFS_FWSTATS_ADD(pipeline, pre_proc_swi); + DEBUGFS_FWSTATS_ADD(pipeline, post_proc_swi); + DEBUGFS_FWSTATS_ADD(pipeline, sec_frag_swi); + DEBUGFS_FWSTATS_ADD(pipeline, pre_to_defrag_swi); + DEBUGFS_FWSTATS_ADD(pipeline, defrag_to_csum_swi); + DEBUGFS_FWSTATS_ADD(pipeline, csum_to_rx_xfer_swi); + DEBUGFS_FWSTATS_ADD(pipeline, dec_packet_in); + DEBUGFS_FWSTATS_ADD(pipeline, dec_packet_in_fifo_full); + DEBUGFS_FWSTATS_ADD(pipeline, dec_packet_out); + DEBUGFS_FWSTATS_ADD(pipeline, cs_rx_packet_in); + DEBUGFS_FWSTATS_ADD(pipeline, cs_rx_packet_out); + DEBUGFS_FWSTATS_ADD(pipeline, pipeline_fifo_full); + + DEBUGFS_FWSTATS_ADD(mem, rx_free_mem_blks); + DEBUGFS_FWSTATS_ADD(mem, tx_free_mem_blks); + DEBUGFS_FWSTATS_ADD(mem, fwlog_free_mem_blks); + DEBUGFS_FWSTATS_ADD(mem, fw_gen_free_mem_blks); + + DEBUGFS_ADD(conf, moddir); + + return 0; + +err: + if (IS_ERR(entry)) + ret = PTR_ERR(entry); + else + ret = -ENOMEM; + + return ret; +} diff --git a/drivers/net/wireless/ti/wl18xx/debugfs.h b/drivers/net/wireless/ti/wl18xx/debugfs.h new file mode 100644 index 0000000..ed679be --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/debugfs.h @@ -0,0 +1,28 @@ +/* + * This file is part of wl18xx + * + * Copyright (C) 2012 Texas Instruments. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL18XX_DEBUGFS_H__ +#define __WL18XX_DEBUGFS_H__ + +int wl18xx_debugfs_add_files(struct wl1271 *wl, + struct dentry *rootdir); + +#endif /* __WL18XX_DEBUGFS_H__ */ diff --git a/drivers/net/wireless/ti/wl18xx/event.c b/drivers/net/wireless/ti/wl18xx/event.c new file mode 100644 index 0000000..c9199d7 --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/event.c @@ -0,0 +1,111 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 2012 Texas Instruments. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "event.h" +#include "scan.h" +#include "../wlcore/cmd.h" +#include "../wlcore/debug.h" + +int wl18xx_wait_for_event(struct wl1271 *wl, enum wlcore_wait_event event, + bool *timeout) +{ + u32 local_event; + + switch (event) { + case WLCORE_EVENT_PEER_REMOVE_COMPLETE: + local_event = PEER_REMOVE_COMPLETE_EVENT_ID; + break; + + case WLCORE_EVENT_DFS_CONFIG_COMPLETE: + local_event = DFS_CHANNELS_CONFIG_COMPLETE_EVENT; + break; + + default: + /* event not implemented */ + return 0; + } + return wlcore_cmd_wait_for_event_or_timeout(wl, local_event, timeout); +} + +int wl18xx_process_mailbox_events(struct wl1271 *wl) +{ + struct wl18xx_event_mailbox *mbox = wl->mbox; + u32 vector; + + vector = le32_to_cpu(mbox->events_vector); + wl1271_debug(DEBUG_EVENT, "MBOX vector: 0x%x", vector); + + if (vector & SCAN_COMPLETE_EVENT_ID) { + wl1271_debug(DEBUG_EVENT, "scan results: %d", + mbox->number_of_scan_results); + + if (wl->scan_wlvif) + wl18xx_scan_completed(wl, wl->scan_wlvif); + } + + if (vector & PERIODIC_SCAN_REPORT_EVENT_ID) { + wl1271_debug(DEBUG_EVENT, + "PERIODIC_SCAN_REPORT_EVENT (results %d)", + mbox->number_of_sched_scan_results); + + wlcore_scan_sched_scan_results(wl); + } + + if (vector & PERIODIC_SCAN_COMPLETE_EVENT_ID) + wlcore_event_sched_scan_completed(wl, 1); + + if (vector & RSSI_SNR_TRIGGER_0_EVENT_ID) + wlcore_event_rssi_trigger(wl, mbox->rssi_snr_trigger_metric); + + if (vector & BA_SESSION_RX_CONSTRAINT_EVENT_ID) + wlcore_event_ba_rx_constraint(wl, + le16_to_cpu(mbox->rx_ba_role_id_bitmap), + le16_to_cpu(mbox->rx_ba_allowed_bitmap)); + + if (vector & BSS_LOSS_EVENT_ID) + wlcore_event_beacon_loss(wl, + le16_to_cpu(mbox->bss_loss_bitmap)); + + if (vector & CHANNEL_SWITCH_COMPLETE_EVENT_ID) + wlcore_event_channel_switch(wl, + le16_to_cpu(mbox->channel_switch_role_id_bitmap), + true); + + if (vector & DUMMY_PACKET_EVENT_ID) + wlcore_event_dummy_packet(wl); + + /* + * "TX retries exceeded" has a different meaning according to mode. + * In AP mode the offending station is disconnected. + */ + if (vector & MAX_TX_FAILURE_EVENT_ID) + wlcore_event_max_tx_failure(wl, + le32_to_cpu(mbox->tx_retry_exceeded_bitmap)); + + if (vector & INACTIVE_STA_EVENT_ID) + wlcore_event_inactive_sta(wl, + le32_to_cpu(mbox->inactive_sta_bitmap)); + + if (vector & REMAIN_ON_CHANNEL_COMPLETE_EVENT_ID) + wlcore_event_roc_complete(wl); + + return 0; +} diff --git a/drivers/net/wireless/ti/wl18xx/event.h b/drivers/net/wireless/ti/wl18xx/event.h new file mode 100644 index 0000000..398f3d2 --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/event.h @@ -0,0 +1,77 @@ +/* + * This file is part of wl18xx + * + * Copyright (C) 2012 Texas Instruments. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL18XX_EVENT_H__ +#define __WL18XX_EVENT_H__ + +#include "../wlcore/wlcore.h" + +enum { + SCAN_COMPLETE_EVENT_ID = BIT(8), + RADAR_DETECTED_EVENT_ID = BIT(9), + CHANNEL_SWITCH_COMPLETE_EVENT_ID = BIT(10), + BSS_LOSS_EVENT_ID = BIT(11), + MAX_TX_FAILURE_EVENT_ID = BIT(12), + DUMMY_PACKET_EVENT_ID = BIT(13), + INACTIVE_STA_EVENT_ID = BIT(14), + PEER_REMOVE_COMPLETE_EVENT_ID = BIT(15), + PERIODIC_SCAN_COMPLETE_EVENT_ID = BIT(16), + BA_SESSION_RX_CONSTRAINT_EVENT_ID = BIT(17), + REMAIN_ON_CHANNEL_COMPLETE_EVENT_ID = BIT(18), + DFS_CHANNELS_CONFIG_COMPLETE_EVENT = BIT(19), + PERIODIC_SCAN_REPORT_EVENT_ID = BIT(20), +}; + +struct wl18xx_event_mailbox { + __le32 events_vector; + + u8 number_of_scan_results; + u8 number_of_sched_scan_results; + + __le16 channel_switch_role_id_bitmap; + + s8 rssi_snr_trigger_metric[NUM_OF_RSSI_SNR_TRIGGERS]; + + /* bitmap of removed links */ + __le32 hlid_removed_bitmap; + + /* rx ba constraint */ + __le16 rx_ba_role_id_bitmap; /* 0xfff means any role. */ + __le16 rx_ba_allowed_bitmap; + + /* bitmap of roc completed (by role id) */ + __le16 roc_completed_bitmap; + + /* bitmap of stations (by role id) with bss loss */ + __le16 bss_loss_bitmap; + + /* bitmap of stations (by HLID) which exceeded max tx retries */ + __le32 tx_retry_exceeded_bitmap; + + /* bitmap of inactive stations (by HLID) */ + __le32 inactive_sta_bitmap; +} __packed; + +int wl18xx_wait_for_event(struct wl1271 *wl, enum wlcore_wait_event event, + bool *timeout); +int wl18xx_process_mailbox_events(struct wl1271 *wl); + +#endif diff --git a/drivers/net/wireless/ti/wl18xx/io.c b/drivers/net/wireless/ti/wl18xx/io.c new file mode 100644 index 0000000..f0abf3e --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/io.c @@ -0,0 +1,75 @@ +/* + * This file is part of wl18xx + * + * Copyright (C) 2011 Texas Instruments + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "../wlcore/wlcore.h" +#include "../wlcore/io.h" + +#include "io.h" + +int wl18xx_top_reg_write(struct wl1271 *wl, int addr, u16 val) +{ + u32 tmp; + int ret; + + if (WARN_ON(addr % 2)) + return -EINVAL; + + if ((addr % 4) == 0) { + ret = wlcore_read32(wl, addr, &tmp); + if (ret < 0) + goto out; + + tmp = (tmp & 0xffff0000) | val; + ret = wlcore_write32(wl, addr, tmp); + } else { + ret = wlcore_read32(wl, addr - 2, &tmp); + if (ret < 0) + goto out; + + tmp = (tmp & 0xffff) | (val << 16); + ret = wlcore_write32(wl, addr - 2, tmp); + } + +out: + return ret; +} + +int wl18xx_top_reg_read(struct wl1271 *wl, int addr, u16 *out) +{ + u32 val = 0; + int ret; + + if (WARN_ON(addr % 2)) + return -EINVAL; + + if ((addr % 4) == 0) { + /* address is 4-bytes aligned */ + ret = wlcore_read32(wl, addr, &val); + if (ret >= 0 && out) + *out = val & 0xffff; + } else { + ret = wlcore_read32(wl, addr - 2, &val); + if (ret >= 0 && out) + *out = (val & 0xffff0000) >> 16; + } + + return ret; +} diff --git a/drivers/net/wireless/ti/wl18xx/io.h b/drivers/net/wireless/ti/wl18xx/io.h new file mode 100644 index 0000000..c32ae30 --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/io.h @@ -0,0 +1,28 @@ +/* + * This file is part of wl18xx + * + * Copyright (C) 2011 Texas Instruments + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL18XX_IO_H__ +#define __WL18XX_IO_H__ + +int __must_check wl18xx_top_reg_write(struct wl1271 *wl, int addr, u16 val); +int __must_check wl18xx_top_reg_read(struct wl1271 *wl, int addr, u16 *out); + +#endif /* __WL18XX_IO_H__ */ diff --git a/drivers/net/wireless/ti/wl18xx/main.c b/drivers/net/wireless/ti/wl18xx/main.c new file mode 100644 index 0000000..9fa692d --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/main.c @@ -0,0 +1,1800 @@ +/* + * This file is part of wl18xx + * + * Copyright (C) 2011 Texas Instruments + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include + +#include "../wlcore/wlcore.h" +#include "../wlcore/debug.h" +#include "../wlcore/io.h" +#include "../wlcore/acx.h" +#include "../wlcore/tx.h" +#include "../wlcore/rx.h" +#include "../wlcore/boot.h" + +#include "reg.h" +#include "conf.h" +#include "cmd.h" +#include "acx.h" +#include "tx.h" +#include "wl18xx.h" +#include "io.h" +#include "scan.h" +#include "event.h" +#include "debugfs.h" + +#define WL18XX_RX_CHECKSUM_MASK 0x40 + +static char *ht_mode_param = NULL; +static char *board_type_param = NULL; +static bool checksum_param = false; +static int num_rx_desc_param = -1; + +/* phy paramters */ +static int dc2dc_param = -1; +static int n_antennas_2_param = -1; +static int n_antennas_5_param = -1; +static int low_band_component_param = -1; +static int low_band_component_type_param = -1; +static int high_band_component_param = -1; +static int high_band_component_type_param = -1; +static int pwr_limit_reference_11_abg_param = -1; + +static const u8 wl18xx_rate_to_idx_2ghz[] = { + /* MCS rates are used only with 11n */ + 15, /* WL18XX_CONF_HW_RXTX_RATE_MCS15 */ + 14, /* WL18XX_CONF_HW_RXTX_RATE_MCS14 */ + 13, /* WL18XX_CONF_HW_RXTX_RATE_MCS13 */ + 12, /* WL18XX_CONF_HW_RXTX_RATE_MCS12 */ + 11, /* WL18XX_CONF_HW_RXTX_RATE_MCS11 */ + 10, /* WL18XX_CONF_HW_RXTX_RATE_MCS10 */ + 9, /* WL18XX_CONF_HW_RXTX_RATE_MCS9 */ + 8, /* WL18XX_CONF_HW_RXTX_RATE_MCS8 */ + 7, /* WL18XX_CONF_HW_RXTX_RATE_MCS7 */ + 6, /* WL18XX_CONF_HW_RXTX_RATE_MCS6 */ + 5, /* WL18XX_CONF_HW_RXTX_RATE_MCS5 */ + 4, /* WL18XX_CONF_HW_RXTX_RATE_MCS4 */ + 3, /* WL18XX_CONF_HW_RXTX_RATE_MCS3 */ + 2, /* WL18XX_CONF_HW_RXTX_RATE_MCS2 */ + 1, /* WL18XX_CONF_HW_RXTX_RATE_MCS1 */ + 0, /* WL18XX_CONF_HW_RXTX_RATE_MCS0 */ + + 11, /* WL18XX_CONF_HW_RXTX_RATE_54 */ + 10, /* WL18XX_CONF_HW_RXTX_RATE_48 */ + 9, /* WL18XX_CONF_HW_RXTX_RATE_36 */ + 8, /* WL18XX_CONF_HW_RXTX_RATE_24 */ + + /* TI-specific rate */ + CONF_HW_RXTX_RATE_UNSUPPORTED, /* WL18XX_CONF_HW_RXTX_RATE_22 */ + + 7, /* WL18XX_CONF_HW_RXTX_RATE_18 */ + 6, /* WL18XX_CONF_HW_RXTX_RATE_12 */ + 3, /* WL18XX_CONF_HW_RXTX_RATE_11 */ + 5, /* WL18XX_CONF_HW_RXTX_RATE_9 */ + 4, /* WL18XX_CONF_HW_RXTX_RATE_6 */ + 2, /* WL18XX_CONF_HW_RXTX_RATE_5_5 */ + 1, /* WL18XX_CONF_HW_RXTX_RATE_2 */ + 0 /* WL18XX_CONF_HW_RXTX_RATE_1 */ +}; + +static const u8 wl18xx_rate_to_idx_5ghz[] = { + /* MCS rates are used only with 11n */ + 15, /* WL18XX_CONF_HW_RXTX_RATE_MCS15 */ + 14, /* WL18XX_CONF_HW_RXTX_RATE_MCS14 */ + 13, /* WL18XX_CONF_HW_RXTX_RATE_MCS13 */ + 12, /* WL18XX_CONF_HW_RXTX_RATE_MCS12 */ + 11, /* WL18XX_CONF_HW_RXTX_RATE_MCS11 */ + 10, /* WL18XX_CONF_HW_RXTX_RATE_MCS10 */ + 9, /* WL18XX_CONF_HW_RXTX_RATE_MCS9 */ + 8, /* WL18XX_CONF_HW_RXTX_RATE_MCS8 */ + 7, /* WL18XX_CONF_HW_RXTX_RATE_MCS7 */ + 6, /* WL18XX_CONF_HW_RXTX_RATE_MCS6 */ + 5, /* WL18XX_CONF_HW_RXTX_RATE_MCS5 */ + 4, /* WL18XX_CONF_HW_RXTX_RATE_MCS4 */ + 3, /* WL18XX_CONF_HW_RXTX_RATE_MCS3 */ + 2, /* WL18XX_CONF_HW_RXTX_RATE_MCS2 */ + 1, /* WL18XX_CONF_HW_RXTX_RATE_MCS1 */ + 0, /* WL18XX_CONF_HW_RXTX_RATE_MCS0 */ + + 7, /* WL18XX_CONF_HW_RXTX_RATE_54 */ + 6, /* WL18XX_CONF_HW_RXTX_RATE_48 */ + 5, /* WL18XX_CONF_HW_RXTX_RATE_36 */ + 4, /* WL18XX_CONF_HW_RXTX_RATE_24 */ + + /* TI-specific rate */ + CONF_HW_RXTX_RATE_UNSUPPORTED, /* WL18XX_CONF_HW_RXTX_RATE_22 */ + + 3, /* WL18XX_CONF_HW_RXTX_RATE_18 */ + 2, /* WL18XX_CONF_HW_RXTX_RATE_12 */ + CONF_HW_RXTX_RATE_UNSUPPORTED, /* WL18XX_CONF_HW_RXTX_RATE_11 */ + 1, /* WL18XX_CONF_HW_RXTX_RATE_9 */ + 0, /* WL18XX_CONF_HW_RXTX_RATE_6 */ + CONF_HW_RXTX_RATE_UNSUPPORTED, /* WL18XX_CONF_HW_RXTX_RATE_5_5 */ + CONF_HW_RXTX_RATE_UNSUPPORTED, /* WL18XX_CONF_HW_RXTX_RATE_2 */ + CONF_HW_RXTX_RATE_UNSUPPORTED, /* WL18XX_CONF_HW_RXTX_RATE_1 */ +}; + +static const u8 *wl18xx_band_rate_to_idx[] = { + [IEEE80211_BAND_2GHZ] = wl18xx_rate_to_idx_2ghz, + [IEEE80211_BAND_5GHZ] = wl18xx_rate_to_idx_5ghz +}; + +enum wl18xx_hw_rates { + WL18XX_CONF_HW_RXTX_RATE_MCS15 = 0, + WL18XX_CONF_HW_RXTX_RATE_MCS14, + WL18XX_CONF_HW_RXTX_RATE_MCS13, + WL18XX_CONF_HW_RXTX_RATE_MCS12, + WL18XX_CONF_HW_RXTX_RATE_MCS11, + WL18XX_CONF_HW_RXTX_RATE_MCS10, + WL18XX_CONF_HW_RXTX_RATE_MCS9, + WL18XX_CONF_HW_RXTX_RATE_MCS8, + WL18XX_CONF_HW_RXTX_RATE_MCS7, + WL18XX_CONF_HW_RXTX_RATE_MCS6, + WL18XX_CONF_HW_RXTX_RATE_MCS5, + WL18XX_CONF_HW_RXTX_RATE_MCS4, + WL18XX_CONF_HW_RXTX_RATE_MCS3, + WL18XX_CONF_HW_RXTX_RATE_MCS2, + WL18XX_CONF_HW_RXTX_RATE_MCS1, + WL18XX_CONF_HW_RXTX_RATE_MCS0, + WL18XX_CONF_HW_RXTX_RATE_54, + WL18XX_CONF_HW_RXTX_RATE_48, + WL18XX_CONF_HW_RXTX_RATE_36, + WL18XX_CONF_HW_RXTX_RATE_24, + WL18XX_CONF_HW_RXTX_RATE_22, + WL18XX_CONF_HW_RXTX_RATE_18, + WL18XX_CONF_HW_RXTX_RATE_12, + WL18XX_CONF_HW_RXTX_RATE_11, + WL18XX_CONF_HW_RXTX_RATE_9, + WL18XX_CONF_HW_RXTX_RATE_6, + WL18XX_CONF_HW_RXTX_RATE_5_5, + WL18XX_CONF_HW_RXTX_RATE_2, + WL18XX_CONF_HW_RXTX_RATE_1, + WL18XX_CONF_HW_RXTX_RATE_MAX, +}; + +static struct wlcore_conf wl18xx_conf = { + .sg = { + .params = { + [CONF_SG_ACL_BT_MASTER_MIN_BR] = 10, + [CONF_SG_ACL_BT_MASTER_MAX_BR] = 180, + [CONF_SG_ACL_BT_SLAVE_MIN_BR] = 10, + [CONF_SG_ACL_BT_SLAVE_MAX_BR] = 180, + [CONF_SG_ACL_BT_MASTER_MIN_EDR] = 10, + [CONF_SG_ACL_BT_MASTER_MAX_EDR] = 80, + [CONF_SG_ACL_BT_SLAVE_MIN_EDR] = 10, + [CONF_SG_ACL_BT_SLAVE_MAX_EDR] = 80, + [CONF_SG_ACL_WLAN_PS_MASTER_BR] = 8, + [CONF_SG_ACL_WLAN_PS_SLAVE_BR] = 8, + [CONF_SG_ACL_WLAN_PS_MASTER_EDR] = 20, + [CONF_SG_ACL_WLAN_PS_SLAVE_EDR] = 20, + [CONF_SG_ACL_WLAN_ACTIVE_MASTER_MIN_BR] = 20, + [CONF_SG_ACL_WLAN_ACTIVE_MASTER_MAX_BR] = 35, + [CONF_SG_ACL_WLAN_ACTIVE_SLAVE_MIN_BR] = 16, + [CONF_SG_ACL_WLAN_ACTIVE_SLAVE_MAX_BR] = 35, + [CONF_SG_ACL_WLAN_ACTIVE_MASTER_MIN_EDR] = 32, + [CONF_SG_ACL_WLAN_ACTIVE_MASTER_MAX_EDR] = 50, + [CONF_SG_ACL_WLAN_ACTIVE_SLAVE_MIN_EDR] = 28, + [CONF_SG_ACL_WLAN_ACTIVE_SLAVE_MAX_EDR] = 50, + [CONF_SG_ACL_ACTIVE_SCAN_WLAN_BR] = 10, + [CONF_SG_ACL_ACTIVE_SCAN_WLAN_EDR] = 20, + [CONF_SG_ACL_PASSIVE_SCAN_BT_BR] = 75, + [CONF_SG_ACL_PASSIVE_SCAN_WLAN_BR] = 15, + [CONF_SG_ACL_PASSIVE_SCAN_BT_EDR] = 27, + [CONF_SG_ACL_PASSIVE_SCAN_WLAN_EDR] = 17, + /* active scan params */ + [CONF_SG_AUTO_SCAN_PROBE_REQ] = 170, + [CONF_SG_ACTIVE_SCAN_DURATION_FACTOR_HV3] = 50, + [CONF_SG_ACTIVE_SCAN_DURATION_FACTOR_A2DP] = 100, + /* passive scan params */ + [CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_A2DP_BR] = 800, + [CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_A2DP_EDR] = 200, + [CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_HV3] = 200, + /* passive scan in dual antenna params */ + [CONF_SG_CONSECUTIVE_HV3_IN_PASSIVE_SCAN] = 0, + [CONF_SG_BCN_HV3_COLLISION_THRESH_IN_PASSIVE_SCAN] = 0, + [CONF_SG_TX_RX_PROTECTION_BWIDTH_IN_PASSIVE_SCAN] = 0, + /* general params */ + [CONF_SG_STA_FORCE_PS_IN_BT_SCO] = 1, + [CONF_SG_ANTENNA_CONFIGURATION] = 0, + [CONF_SG_BEACON_MISS_PERCENT] = 60, + [CONF_SG_DHCP_TIME] = 5000, + [CONF_SG_RXT] = 1200, + [CONF_SG_TXT] = 1000, + [CONF_SG_ADAPTIVE_RXT_TXT] = 1, + [CONF_SG_GENERAL_USAGE_BIT_MAP] = 3, + [CONF_SG_HV3_MAX_SERVED] = 6, + [CONF_SG_PS_POLL_TIMEOUT] = 10, + [CONF_SG_UPSD_TIMEOUT] = 10, + [CONF_SG_CONSECUTIVE_CTS_THRESHOLD] = 2, + [CONF_SG_STA_RX_WINDOW_AFTER_DTIM] = 5, + [CONF_SG_STA_CONNECTION_PROTECTION_TIME] = 30, + /* AP params */ + [CONF_AP_BEACON_MISS_TX] = 3, + [CONF_AP_RX_WINDOW_AFTER_BEACON] = 10, + [CONF_AP_BEACON_WINDOW_INTERVAL] = 2, + [CONF_AP_CONNECTION_PROTECTION_TIME] = 0, + [CONF_AP_BT_ACL_VAL_BT_SERVE_TIME] = 25, + [CONF_AP_BT_ACL_VAL_WL_SERVE_TIME] = 25, + /* CTS Diluting params */ + [CONF_SG_CTS_DILUTED_BAD_RX_PACKETS_TH] = 0, + [CONF_SG_CTS_CHOP_IN_DUAL_ANT_SCO_MASTER] = 0, + }, + .state = CONF_SG_PROTECTIVE, + }, + .rx = { + .rx_msdu_life_time = 512000, + .packet_detection_threshold = 0, + .ps_poll_timeout = 15, + .upsd_timeout = 15, + .rts_threshold = IEEE80211_MAX_RTS_THRESHOLD, + .rx_cca_threshold = 0, + .irq_blk_threshold = 0xFFFF, + .irq_pkt_threshold = 0, + .irq_timeout = 600, + .queue_type = CONF_RX_QUEUE_TYPE_LOW_PRIORITY, + }, + .tx = { + .tx_energy_detection = 0, + .sta_rc_conf = { + .enabled_rates = 0, + .short_retry_limit = 10, + .long_retry_limit = 10, + .aflags = 0, + }, + .ac_conf_count = 4, + .ac_conf = { + [CONF_TX_AC_BE] = { + .ac = CONF_TX_AC_BE, + .cw_min = 15, + .cw_max = 63, + .aifsn = 3, + .tx_op_limit = 0, + }, + [CONF_TX_AC_BK] = { + .ac = CONF_TX_AC_BK, + .cw_min = 15, + .cw_max = 63, + .aifsn = 7, + .tx_op_limit = 0, + }, + [CONF_TX_AC_VI] = { + .ac = CONF_TX_AC_VI, + .cw_min = 15, + .cw_max = 63, + .aifsn = CONF_TX_AIFS_PIFS, + .tx_op_limit = 3008, + }, + [CONF_TX_AC_VO] = { + .ac = CONF_TX_AC_VO, + .cw_min = 15, + .cw_max = 63, + .aifsn = CONF_TX_AIFS_PIFS, + .tx_op_limit = 1504, + }, + }, + .max_tx_retries = 100, + .ap_aging_period = 300, + .tid_conf_count = 4, + .tid_conf = { + [CONF_TX_AC_BE] = { + .queue_id = CONF_TX_AC_BE, + .channel_type = CONF_CHANNEL_TYPE_EDCF, + .tsid = CONF_TX_AC_BE, + .ps_scheme = CONF_PS_SCHEME_LEGACY, + .ack_policy = CONF_ACK_POLICY_LEGACY, + .apsd_conf = {0, 0}, + }, + [CONF_TX_AC_BK] = { + .queue_id = CONF_TX_AC_BK, + .channel_type = CONF_CHANNEL_TYPE_EDCF, + .tsid = CONF_TX_AC_BK, + .ps_scheme = CONF_PS_SCHEME_LEGACY, + .ack_policy = CONF_ACK_POLICY_LEGACY, + .apsd_conf = {0, 0}, + }, + [CONF_TX_AC_VI] = { + .queue_id = CONF_TX_AC_VI, + .channel_type = CONF_CHANNEL_TYPE_EDCF, + .tsid = CONF_TX_AC_VI, + .ps_scheme = CONF_PS_SCHEME_LEGACY, + .ack_policy = CONF_ACK_POLICY_LEGACY, + .apsd_conf = {0, 0}, + }, + [CONF_TX_AC_VO] = { + .queue_id = CONF_TX_AC_VO, + .channel_type = CONF_CHANNEL_TYPE_EDCF, + .tsid = CONF_TX_AC_VO, + .ps_scheme = CONF_PS_SCHEME_LEGACY, + .ack_policy = CONF_ACK_POLICY_LEGACY, + .apsd_conf = {0, 0}, + }, + }, + .frag_threshold = IEEE80211_MAX_FRAG_THRESHOLD, + .tx_compl_timeout = 350, + .tx_compl_threshold = 10, + .basic_rate = CONF_HW_BIT_RATE_1MBPS, + .basic_rate_5 = CONF_HW_BIT_RATE_6MBPS, + .tmpl_short_retry_limit = 10, + .tmpl_long_retry_limit = 10, + .tx_watchdog_timeout = 5000, + .slow_link_thold = 3, + .fast_link_thold = 30, + }, + .conn = { + .wake_up_event = CONF_WAKE_UP_EVENT_DTIM, + .listen_interval = 1, + .suspend_wake_up_event = CONF_WAKE_UP_EVENT_N_DTIM, + .suspend_listen_interval = 3, + .bcn_filt_mode = CONF_BCN_FILT_MODE_ENABLED, + .bcn_filt_ie_count = 3, + .bcn_filt_ie = { + [0] = { + .ie = WLAN_EID_CHANNEL_SWITCH, + .rule = CONF_BCN_RULE_PASS_ON_APPEARANCE, + }, + [1] = { + .ie = WLAN_EID_HT_OPERATION, + .rule = CONF_BCN_RULE_PASS_ON_CHANGE, + }, + [2] = { + .ie = WLAN_EID_ERP_INFO, + .rule = CONF_BCN_RULE_PASS_ON_CHANGE, + }, + }, + .synch_fail_thold = 12, + .bss_lose_timeout = 400, + .beacon_rx_timeout = 10000, + .broadcast_timeout = 20000, + .rx_broadcast_in_ps = 1, + .ps_poll_threshold = 10, + .bet_enable = CONF_BET_MODE_ENABLE, + .bet_max_consecutive = 50, + .psm_entry_retries = 8, + .psm_exit_retries = 16, + .psm_entry_nullfunc_retries = 3, + .dynamic_ps_timeout = 1500, + .forced_ps = false, + .keep_alive_interval = 55000, + .max_listen_interval = 20, + .sta_sleep_auth = WL1271_PSM_ILLEGAL, + }, + .itrim = { + .enable = false, + .timeout = 50000, + }, + .pm_config = { + .host_clk_settling_time = 5000, + .host_fast_wakeup_support = CONF_FAST_WAKEUP_DISABLE, + }, + .roam_trigger = { + .trigger_pacing = 1, + .avg_weight_rssi_beacon = 20, + .avg_weight_rssi_data = 10, + .avg_weight_snr_beacon = 20, + .avg_weight_snr_data = 10, + }, + .scan = { + .min_dwell_time_active = 7500, + .max_dwell_time_active = 30000, + .min_dwell_time_active_long = 25000, + .max_dwell_time_active_long = 50000, + .dwell_time_passive = 100000, + .dwell_time_dfs = 150000, + .num_probe_reqs = 2, + .split_scan_timeout = 50000, + }, + .sched_scan = { + /* + * Values are in TU/1000 but since sched scan FW command + * params are in TUs rounding up may occur. + */ + .base_dwell_time = 7500, + .max_dwell_time_delta = 22500, + /* based on 250bits per probe @1Mbps */ + .dwell_time_delta_per_probe = 2000, + /* based on 250bits per probe @6Mbps (plus a bit more) */ + .dwell_time_delta_per_probe_5 = 350, + .dwell_time_passive = 100000, + .dwell_time_dfs = 150000, + .num_probe_reqs = 2, + .rssi_threshold = -90, + .snr_threshold = 0, + }, + .ht = { + .rx_ba_win_size = 32, + .tx_ba_win_size = 64, + .inactivity_timeout = 10000, + .tx_ba_tid_bitmap = CONF_TX_BA_ENABLED_TID_BITMAP, + }, + .mem = { + .num_stations = 1, + .ssid_profiles = 1, + .rx_block_num = 40, + .tx_min_block_num = 40, + .dynamic_memory = 1, + .min_req_tx_blocks = 45, + .min_req_rx_blocks = 22, + .tx_min = 27, + }, + .fm_coex = { + .enable = true, + .swallow_period = 5, + .n_divider_fref_set_1 = 0xff, /* default */ + .n_divider_fref_set_2 = 12, + .m_divider_fref_set_1 = 0xffff, + .m_divider_fref_set_2 = 148, /* default */ + .coex_pll_stabilization_time = 0xffffffff, /* default */ + .ldo_stabilization_time = 0xffff, /* default */ + .fm_disturbed_band_margin = 0xff, /* default */ + .swallow_clk_diff = 0xff, /* default */ + }, + .rx_streaming = { + .duration = 150, + .queues = 0x1, + .interval = 20, + .always = 0, + }, + .fwlog = { + .mode = WL12XX_FWLOG_ON_DEMAND, + .mem_blocks = 2, + .severity = 0, + .timestamp = WL12XX_FWLOG_TIMESTAMP_DISABLED, + .output = WL12XX_FWLOG_OUTPUT_HOST, + .threshold = 0, + }, + .rate = { + .rate_retry_score = 32000, + .per_add = 8192, + .per_th1 = 2048, + .per_th2 = 4096, + .max_per = 8100, + .inverse_curiosity_factor = 5, + .tx_fail_low_th = 4, + .tx_fail_high_th = 10, + .per_alpha_shift = 4, + .per_add_shift = 13, + .per_beta1_shift = 10, + .per_beta2_shift = 8, + .rate_check_up = 2, + .rate_check_down = 12, + .rate_retry_policy = { + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + }, + }, + .hangover = { + .recover_time = 0, + .hangover_period = 20, + .dynamic_mode = 1, + .early_termination_mode = 1, + .max_period = 20, + .min_period = 1, + .increase_delta = 1, + .decrease_delta = 2, + .quiet_time = 4, + .increase_time = 1, + .window_size = 16, + }, + .recovery = { + .bug_on_recovery = 0, + .no_recovery = 0, + }, +}; + +static struct wl18xx_priv_conf wl18xx_default_priv_conf = { + .ht = { + .mode = HT_MODE_DEFAULT, + }, + .phy = { + .phy_standalone = 0x00, + .primary_clock_setting_time = 0x05, + .clock_valid_on_wake_up = 0x00, + .secondary_clock_setting_time = 0x05, + .board_type = BOARD_TYPE_HDK_18XX, + .auto_detect = 0x00, + .dedicated_fem = FEM_NONE, + .low_band_component = COMPONENT_3_WAY_SWITCH, + .low_band_component_type = 0x04, + .high_band_component = COMPONENT_2_WAY_SWITCH, + .high_band_component_type = 0x09, + .tcxo_ldo_voltage = 0x00, + .xtal_itrim_val = 0x04, + .srf_state = 0x00, + .io_configuration = 0x01, + .sdio_configuration = 0x00, + .settings = 0x00, + .enable_clpc = 0x00, + .enable_tx_low_pwr_on_siso_rdl = 0x00, + .rx_profile = 0x00, + .pwr_limit_reference_11_abg = 0x64, + .per_chan_pwr_limit_arr_11abg = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + .pwr_limit_reference_11p = 0x64, + .per_chan_bo_mode_11_abg = { 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00 }, + .per_chan_bo_mode_11_p = { 0x00, 0x00, 0x00, 0x00 }, + .per_chan_pwr_limit_arr_11p = { 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff }, + .psat = 0, + .low_power_val = 0x08, + .med_power_val = 0x12, + .high_power_val = 0x18, + .low_power_val_2nd = 0x05, + .med_power_val_2nd = 0x0a, + .high_power_val_2nd = 0x14, + .external_pa_dc2dc = 0, + .number_of_assembled_ant2_4 = 2, + .number_of_assembled_ant5 = 1, + .tx_rf_margin = 1, + }, +}; + +static const struct wlcore_partition_set wl18xx_ptable[PART_TABLE_LEN] = { + [PART_TOP_PRCM_ELP_SOC] = { + .mem = { .start = 0x00A02000, .size = 0x00010000 }, + .reg = { .start = 0x00807000, .size = 0x00005000 }, + .mem2 = { .start = 0x00800000, .size = 0x0000B000 }, + .mem3 = { .start = 0x00000000, .size = 0x00000000 }, + }, + [PART_DOWN] = { + .mem = { .start = 0x00000000, .size = 0x00014000 }, + .reg = { .start = 0x00810000, .size = 0x0000BFFF }, + .mem2 = { .start = 0x00000000, .size = 0x00000000 }, + .mem3 = { .start = 0x00000000, .size = 0x00000000 }, + }, + [PART_BOOT] = { + .mem = { .start = 0x00700000, .size = 0x0000030c }, + .reg = { .start = 0x00802000, .size = 0x00014578 }, + .mem2 = { .start = 0x00B00404, .size = 0x00001000 }, + .mem3 = { .start = 0x00C00000, .size = 0x00000400 }, + }, + [PART_WORK] = { + .mem = { .start = 0x00800000, .size = 0x000050FC }, + .reg = { .start = 0x00B00404, .size = 0x00001000 }, + .mem2 = { .start = 0x00C00000, .size = 0x00000400 }, + .mem3 = { .start = 0x00000000, .size = 0x00000000 }, + }, + [PART_PHY_INIT] = { + .mem = { .start = 0x80926000, + .size = sizeof(struct wl18xx_mac_and_phy_params) }, + .reg = { .start = 0x00000000, .size = 0x00000000 }, + .mem2 = { .start = 0x00000000, .size = 0x00000000 }, + .mem3 = { .start = 0x00000000, .size = 0x00000000 }, + }, +}; + +static const int wl18xx_rtable[REG_TABLE_LEN] = { + [REG_ECPU_CONTROL] = WL18XX_REG_ECPU_CONTROL, + [REG_INTERRUPT_NO_CLEAR] = WL18XX_REG_INTERRUPT_NO_CLEAR, + [REG_INTERRUPT_ACK] = WL18XX_REG_INTERRUPT_ACK, + [REG_COMMAND_MAILBOX_PTR] = WL18XX_REG_COMMAND_MAILBOX_PTR, + [REG_EVENT_MAILBOX_PTR] = WL18XX_REG_EVENT_MAILBOX_PTR, + [REG_INTERRUPT_TRIG] = WL18XX_REG_INTERRUPT_TRIG_H, + [REG_INTERRUPT_MASK] = WL18XX_REG_INTERRUPT_MASK, + [REG_PC_ON_RECOVERY] = WL18XX_SCR_PAD4, + [REG_CHIP_ID_B] = WL18XX_REG_CHIP_ID_B, + [REG_CMD_MBOX_ADDRESS] = WL18XX_CMD_MBOX_ADDRESS, + + /* data access memory addresses, used with partition translation */ + [REG_SLV_MEM_DATA] = WL18XX_SLV_MEM_DATA, + [REG_SLV_REG_DATA] = WL18XX_SLV_REG_DATA, + + /* raw data access memory addresses */ + [REG_RAW_FW_STATUS_ADDR] = WL18XX_FW_STATUS_ADDR, +}; + +static const struct wl18xx_clk_cfg wl18xx_clk_table[NUM_CLOCK_CONFIGS] = { + [CLOCK_CONFIG_16_2_M] = { 7, 104, 801, 4, true }, + [CLOCK_CONFIG_16_368_M] = { 9, 132, 3751, 4, true }, + [CLOCK_CONFIG_16_8_M] = { 7, 100, 0, 0, false }, + [CLOCK_CONFIG_19_2_M] = { 8, 100, 0, 0, false }, + [CLOCK_CONFIG_26_M] = { 13, 120, 0, 0, false }, + [CLOCK_CONFIG_32_736_M] = { 9, 132, 3751, 4, true }, + [CLOCK_CONFIG_33_6_M] = { 7, 100, 0, 0, false }, + [CLOCK_CONFIG_38_468_M] = { 8, 100, 0, 0, false }, + [CLOCK_CONFIG_52_M] = { 13, 120, 0, 0, false }, +}; + +/* TODO: maybe move to a new header file? */ +#define WL18XX_FW_NAME "ti-connectivity/wl18xx-fw-2.bin" + +static int wl18xx_identify_chip(struct wl1271 *wl) +{ + int ret = 0; + + switch (wl->chip.id) { + case CHIP_ID_185x_PG20: + wl1271_debug(DEBUG_BOOT, "chip id 0x%x (185x PG20)", + wl->chip.id); + wl->sr_fw_name = WL18XX_FW_NAME; + /* wl18xx uses the same firmware for PLT */ + wl->plt_fw_name = WL18XX_FW_NAME; + wl->quirks |= WLCORE_QUIRK_RX_BLOCKSIZE_ALIGN | + WLCORE_QUIRK_TX_BLOCKSIZE_ALIGN | + WLCORE_QUIRK_NO_SCHED_SCAN_WHILE_CONN | + WLCORE_QUIRK_TX_PAD_LAST_FRAME | + WLCORE_QUIRK_REGDOMAIN_CONF | + WLCORE_QUIRK_DUAL_PROBE_TMPL; + + wlcore_set_min_fw_ver(wl, WL18XX_CHIP_VER, + WL18XX_IFTYPE_VER, WL18XX_MAJOR_VER, + WL18XX_SUBTYPE_VER, WL18XX_MINOR_VER, + /* there's no separate multi-role FW */ + 0, 0, 0, 0); + break; + case CHIP_ID_185x_PG10: + wl1271_warning("chip id 0x%x (185x PG10) is deprecated", + wl->chip.id); + ret = -ENODEV; + goto out; + + default: + wl1271_warning("unsupported chip id: 0x%x", wl->chip.id); + ret = -ENODEV; + goto out; + } + + wl->scan_templ_id_2_4 = CMD_TEMPL_CFG_PROBE_REQ_2_4; + wl->scan_templ_id_5 = CMD_TEMPL_CFG_PROBE_REQ_5; + wl->sched_scan_templ_id_2_4 = CMD_TEMPL_PROBE_REQ_2_4_PERIODIC; + wl->sched_scan_templ_id_5 = CMD_TEMPL_PROBE_REQ_5_PERIODIC; + wl->max_channels_5 = WL18XX_MAX_CHANNELS_5GHZ; + wl->ba_rx_session_count_max = WL18XX_RX_BA_MAX_SESSIONS; +out: + return ret; +} + +static int wl18xx_set_clk(struct wl1271 *wl) +{ + u16 clk_freq; + int ret; + + ret = wlcore_set_partition(wl, &wl->ptable[PART_TOP_PRCM_ELP_SOC]); + if (ret < 0) + goto out; + + /* TODO: PG2: apparently we need to read the clk type */ + + ret = wl18xx_top_reg_read(wl, PRIMARY_CLK_DETECT, &clk_freq); + if (ret < 0) + goto out; + + wl1271_debug(DEBUG_BOOT, "clock freq %d (%d, %d, %d, %d, %s)", clk_freq, + wl18xx_clk_table[clk_freq].n, wl18xx_clk_table[clk_freq].m, + wl18xx_clk_table[clk_freq].p, wl18xx_clk_table[clk_freq].q, + wl18xx_clk_table[clk_freq].swallow ? "swallow" : "spit"); + + ret = wl18xx_top_reg_write(wl, PLLSH_WCS_PLL_N, + wl18xx_clk_table[clk_freq].n); + if (ret < 0) + goto out; + + ret = wl18xx_top_reg_write(wl, PLLSH_WCS_PLL_M, + wl18xx_clk_table[clk_freq].m); + if (ret < 0) + goto out; + + if (wl18xx_clk_table[clk_freq].swallow) { + /* first the 16 lower bits */ + ret = wl18xx_top_reg_write(wl, PLLSH_WCS_PLL_Q_FACTOR_CFG_1, + wl18xx_clk_table[clk_freq].q & + PLLSH_WCS_PLL_Q_FACTOR_CFG_1_MASK); + if (ret < 0) + goto out; + + /* then the 16 higher bits, masked out */ + ret = wl18xx_top_reg_write(wl, PLLSH_WCS_PLL_Q_FACTOR_CFG_2, + (wl18xx_clk_table[clk_freq].q >> 16) & + PLLSH_WCS_PLL_Q_FACTOR_CFG_2_MASK); + if (ret < 0) + goto out; + + /* first the 16 lower bits */ + ret = wl18xx_top_reg_write(wl, PLLSH_WCS_PLL_P_FACTOR_CFG_1, + wl18xx_clk_table[clk_freq].p & + PLLSH_WCS_PLL_P_FACTOR_CFG_1_MASK); + if (ret < 0) + goto out; + + /* then the 16 higher bits, masked out */ + ret = wl18xx_top_reg_write(wl, PLLSH_WCS_PLL_P_FACTOR_CFG_2, + (wl18xx_clk_table[clk_freq].p >> 16) & + PLLSH_WCS_PLL_P_FACTOR_CFG_2_MASK); + } else { + ret = wl18xx_top_reg_write(wl, PLLSH_WCS_PLL_SWALLOW_EN, + PLLSH_WCS_PLL_SWALLOW_EN_VAL2); + } + +out: + return ret; +} + +static int wl18xx_boot_soft_reset(struct wl1271 *wl) +{ + int ret; + + /* disable Rx/Tx */ + ret = wlcore_write32(wl, WL18XX_ENABLE, 0x0); + if (ret < 0) + goto out; + + /* disable auto calibration on start*/ + ret = wlcore_write32(wl, WL18XX_SPARE_A2, 0xffff); + +out: + return ret; +} + +static int wl18xx_pre_boot(struct wl1271 *wl) +{ + int ret; + + ret = wl18xx_set_clk(wl); + if (ret < 0) + goto out; + + /* Continue the ELP wake up sequence */ + ret = wlcore_write32(wl, WL18XX_WELP_ARM_COMMAND, WELP_ARM_COMMAND_VAL); + if (ret < 0) + goto out; + + udelay(500); + + ret = wlcore_set_partition(wl, &wl->ptable[PART_BOOT]); + if (ret < 0) + goto out; + + /* Disable interrupts */ + ret = wlcore_write_reg(wl, REG_INTERRUPT_MASK, WL1271_ACX_INTR_ALL); + if (ret < 0) + goto out; + + ret = wl18xx_boot_soft_reset(wl); + +out: + return ret; +} + +static int wl18xx_pre_upload(struct wl1271 *wl) +{ + u32 tmp; + int ret; + + ret = wlcore_set_partition(wl, &wl->ptable[PART_BOOT]); + if (ret < 0) + goto out; + + /* TODO: check if this is all needed */ + ret = wlcore_write32(wl, WL18XX_EEPROMLESS_IND, WL18XX_EEPROMLESS_IND); + if (ret < 0) + goto out; + + ret = wlcore_read_reg(wl, REG_CHIP_ID_B, &tmp); + if (ret < 0) + goto out; + + wl1271_debug(DEBUG_BOOT, "chip id 0x%x", tmp); + + ret = wlcore_read32(wl, WL18XX_SCR_PAD2, &tmp); + +out: + return ret; +} + +static int wl18xx_set_mac_and_phy(struct wl1271 *wl) +{ + struct wl18xx_priv *priv = wl->priv; + struct wl18xx_mac_and_phy_params *params; + int ret; + + params = kmemdup(&priv->conf.phy, sizeof(*params), GFP_KERNEL); + if (!params) { + ret = -ENOMEM; + goto out; + } + + ret = wlcore_set_partition(wl, &wl->ptable[PART_PHY_INIT]); + if (ret < 0) + goto out; + + ret = wlcore_write(wl, WL18XX_PHY_INIT_MEM_ADDR, params, + sizeof(*params), false); + +out: + kfree(params); + return ret; +} + +static int wl18xx_enable_interrupts(struct wl1271 *wl) +{ + u32 event_mask, intr_mask; + int ret; + + event_mask = WL18XX_ACX_EVENTS_VECTOR; + intr_mask = WL18XX_INTR_MASK; + + ret = wlcore_write_reg(wl, REG_INTERRUPT_MASK, event_mask); + if (ret < 0) + goto out; + + wlcore_enable_interrupts(wl); + + ret = wlcore_write_reg(wl, REG_INTERRUPT_MASK, + WL1271_ACX_INTR_ALL & ~intr_mask); + if (ret < 0) + goto disable_interrupts; + + return ret; + +disable_interrupts: + wlcore_disable_interrupts(wl); + +out: + return ret; +} + +static int wl18xx_boot(struct wl1271 *wl) +{ + int ret; + + ret = wl18xx_pre_boot(wl); + if (ret < 0) + goto out; + + ret = wl18xx_pre_upload(wl); + if (ret < 0) + goto out; + + ret = wlcore_boot_upload_firmware(wl); + if (ret < 0) + goto out; + + ret = wl18xx_set_mac_and_phy(wl); + if (ret < 0) + goto out; + + wl->event_mask = BSS_LOSS_EVENT_ID | + SCAN_COMPLETE_EVENT_ID | + RSSI_SNR_TRIGGER_0_EVENT_ID | + PERIODIC_SCAN_COMPLETE_EVENT_ID | + PERIODIC_SCAN_REPORT_EVENT_ID | + DUMMY_PACKET_EVENT_ID | + PEER_REMOVE_COMPLETE_EVENT_ID | + BA_SESSION_RX_CONSTRAINT_EVENT_ID | + REMAIN_ON_CHANNEL_COMPLETE_EVENT_ID | + INACTIVE_STA_EVENT_ID | + MAX_TX_FAILURE_EVENT_ID | + CHANNEL_SWITCH_COMPLETE_EVENT_ID | + DFS_CHANNELS_CONFIG_COMPLETE_EVENT; + + ret = wlcore_boot_run_firmware(wl); + if (ret < 0) + goto out; + + ret = wl18xx_enable_interrupts(wl); + +out: + return ret; +} + +static int wl18xx_trigger_cmd(struct wl1271 *wl, int cmd_box_addr, + void *buf, size_t len) +{ + struct wl18xx_priv *priv = wl->priv; + + memcpy(priv->cmd_buf, buf, len); + memset(priv->cmd_buf + len, 0, WL18XX_CMD_MAX_SIZE - len); + + return wlcore_write(wl, cmd_box_addr, priv->cmd_buf, + WL18XX_CMD_MAX_SIZE, false); +} + +static int wl18xx_ack_event(struct wl1271 *wl) +{ + return wlcore_write_reg(wl, REG_INTERRUPT_TRIG, + WL18XX_INTR_TRIG_EVENT_ACK); +} + +static u32 wl18xx_calc_tx_blocks(struct wl1271 *wl, u32 len, u32 spare_blks) +{ + u32 blk_size = WL18XX_TX_HW_BLOCK_SIZE; + return (len + blk_size - 1) / blk_size + spare_blks; +} + +static void +wl18xx_set_tx_desc_blocks(struct wl1271 *wl, struct wl1271_tx_hw_descr *desc, + u32 blks, u32 spare_blks) +{ + desc->wl18xx_mem.total_mem_blocks = blks; +} + +static void +wl18xx_set_tx_desc_data_len(struct wl1271 *wl, struct wl1271_tx_hw_descr *desc, + struct sk_buff *skb) +{ + desc->length = cpu_to_le16(skb->len); + + /* if only the last frame is to be padded, we unset this bit on Tx */ + if (wl->quirks & WLCORE_QUIRK_TX_PAD_LAST_FRAME) + desc->wl18xx_mem.ctrl = WL18XX_TX_CTRL_NOT_PADDED; + else + desc->wl18xx_mem.ctrl = 0; + + wl1271_debug(DEBUG_TX, "tx_fill_hdr: hlid: %d " + "len: %d life: %d mem: %d", desc->hlid, + le16_to_cpu(desc->length), + le16_to_cpu(desc->life_time), + desc->wl18xx_mem.total_mem_blocks); +} + +static enum wl_rx_buf_align +wl18xx_get_rx_buf_align(struct wl1271 *wl, u32 rx_desc) +{ + if (rx_desc & RX_BUF_PADDED_PAYLOAD) + return WLCORE_RX_BUF_PADDED; + + return WLCORE_RX_BUF_ALIGNED; +} + +static u32 wl18xx_get_rx_packet_len(struct wl1271 *wl, void *rx_data, + u32 data_len) +{ + struct wl1271_rx_descriptor *desc = rx_data; + + /* invalid packet */ + if (data_len < sizeof(*desc)) + return 0; + + return data_len - sizeof(*desc); +} + +static void wl18xx_tx_immediate_completion(struct wl1271 *wl) +{ + wl18xx_tx_immediate_complete(wl); +} + +static int wl18xx_set_host_cfg_bitmap(struct wl1271 *wl, u32 extra_mem_blk) +{ + int ret; + u32 sdio_align_size = 0; + u32 host_cfg_bitmap = HOST_IF_CFG_RX_FIFO_ENABLE | + HOST_IF_CFG_ADD_RX_ALIGNMENT; + + /* Enable Tx SDIO padding */ + if (wl->quirks & WLCORE_QUIRK_TX_BLOCKSIZE_ALIGN) { + host_cfg_bitmap |= HOST_IF_CFG_TX_PAD_TO_SDIO_BLK; + sdio_align_size = WL12XX_BUS_BLOCK_SIZE; + } + + /* Enable Rx SDIO padding */ + if (wl->quirks & WLCORE_QUIRK_RX_BLOCKSIZE_ALIGN) { + host_cfg_bitmap |= HOST_IF_CFG_RX_PAD_TO_SDIO_BLK; + sdio_align_size = WL12XX_BUS_BLOCK_SIZE; + } + + ret = wl18xx_acx_host_if_cfg_bitmap(wl, host_cfg_bitmap, + sdio_align_size, extra_mem_blk, + WL18XX_HOST_IF_LEN_SIZE_FIELD); + if (ret < 0) + return ret; + + return 0; +} + +static int wl18xx_hw_init(struct wl1271 *wl) +{ + int ret; + struct wl18xx_priv *priv = wl->priv; + + /* (re)init private structures. Relevant on recovery as well. */ + priv->last_fw_rls_idx = 0; + priv->extra_spare_key_count = 0; + + /* set the default amount of spare blocks in the bitmap */ + ret = wl18xx_set_host_cfg_bitmap(wl, WL18XX_TX_HW_BLOCK_SPARE); + if (ret < 0) + return ret; + + if (checksum_param) { + ret = wl18xx_acx_set_checksum_state(wl); + if (ret != 0) + return ret; + } + + return ret; +} + +static void wl18xx_set_tx_desc_csum(struct wl1271 *wl, + struct wl1271_tx_hw_descr *desc, + struct sk_buff *skb) +{ + u32 ip_hdr_offset; + struct iphdr *ip_hdr; + + if (!checksum_param) { + desc->wl18xx_checksum_data = 0; + return; + } + + if (skb->ip_summed != CHECKSUM_PARTIAL) { + desc->wl18xx_checksum_data = 0; + return; + } + + ip_hdr_offset = skb_network_header(skb) - skb_mac_header(skb); + if (WARN_ON(ip_hdr_offset >= (1<<7))) { + desc->wl18xx_checksum_data = 0; + return; + } + + desc->wl18xx_checksum_data = ip_hdr_offset << 1; + + /* FW is interested only in the LSB of the protocol TCP=0 UDP=1 */ + ip_hdr = (void *)skb_network_header(skb); + desc->wl18xx_checksum_data |= (ip_hdr->protocol & 0x01); +} + +static void wl18xx_set_rx_csum(struct wl1271 *wl, + struct wl1271_rx_descriptor *desc, + struct sk_buff *skb) +{ + if (desc->status & WL18XX_RX_CHECKSUM_MASK) + skb->ip_summed = CHECKSUM_UNNECESSARY; +} + +static bool wl18xx_is_mimo_supported(struct wl1271 *wl) +{ + struct wl18xx_priv *priv = wl->priv; + + /* only support MIMO with multiple antennas, and when SISO + * is not forced through config + */ + return (priv->conf.phy.number_of_assembled_ant2_4 >= 2) && + (priv->conf.ht.mode != HT_MODE_WIDE) && + (priv->conf.ht.mode != HT_MODE_SISO20); +} + +/* + * TODO: instead of having these two functions to get the rate mask, + * we should modify the wlvif->rate_set instead + */ +static u32 wl18xx_sta_get_ap_rate_mask(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + u32 hw_rate_set = wlvif->rate_set; + + if (wlvif->channel_type == NL80211_CHAN_HT40MINUS || + wlvif->channel_type == NL80211_CHAN_HT40PLUS) { + wl1271_debug(DEBUG_ACX, "using wide channel rate mask"); + hw_rate_set |= CONF_TX_RATE_USE_WIDE_CHAN; + + /* we don't support MIMO in wide-channel mode */ + hw_rate_set &= ~CONF_TX_MIMO_RATES; + } else if (wl18xx_is_mimo_supported(wl)) { + wl1271_debug(DEBUG_ACX, "using MIMO channel rate mask"); + hw_rate_set |= CONF_TX_MIMO_RATES; + } + + return hw_rate_set; +} + +static u32 wl18xx_ap_get_mimo_wide_rate_mask(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + if (wlvif->channel_type == NL80211_CHAN_HT40MINUS || + wlvif->channel_type == NL80211_CHAN_HT40PLUS) { + wl1271_debug(DEBUG_ACX, "using wide channel rate mask"); + + /* sanity check - we don't support this */ + if (WARN_ON(wlvif->band != IEEE80211_BAND_5GHZ)) + return 0; + + return CONF_TX_RATE_USE_WIDE_CHAN; + } else if (wl18xx_is_mimo_supported(wl) && + wlvif->band == IEEE80211_BAND_2GHZ) { + wl1271_debug(DEBUG_ACX, "using MIMO rate mask"); + /* + * we don't care about HT channel here - if a peer doesn't + * support MIMO, we won't enable it in its rates + */ + return CONF_TX_MIMO_RATES; + } else { + return 0; + } +} + +static int wl18xx_get_pg_ver(struct wl1271 *wl, s8 *ver) +{ + u32 fuse; + s8 rom = 0, metal = 0, pg_ver = 0, rdl_ver = 0; + int ret; + + ret = wlcore_set_partition(wl, &wl->ptable[PART_TOP_PRCM_ELP_SOC]); + if (ret < 0) + goto out; + + ret = wlcore_read32(wl, WL18XX_REG_FUSE_DATA_1_3, &fuse); + if (ret < 0) + goto out; + + pg_ver = (fuse & WL18XX_PG_VER_MASK) >> WL18XX_PG_VER_OFFSET; + rom = (fuse & WL18XX_ROM_VER_MASK) >> WL18XX_ROM_VER_OFFSET; + + if (rom <= 0xE) + metal = (fuse & WL18XX_METAL_VER_MASK) >> + WL18XX_METAL_VER_OFFSET; + else + metal = (fuse & WL18XX_NEW_METAL_VER_MASK) >> + WL18XX_NEW_METAL_VER_OFFSET; + + ret = wlcore_read32(wl, WL18XX_REG_FUSE_DATA_2_3, &fuse); + if (ret < 0) + goto out; + + rdl_ver = (fuse & WL18XX_RDL_VER_MASK) >> WL18XX_RDL_VER_OFFSET; + if (rdl_ver > RDL_MAX) + rdl_ver = RDL_NONE; + + wl1271_info("wl18xx HW: RDL %d, %s, PG %x.%x (ROM %x)", + rdl_ver, rdl_names[rdl_ver], pg_ver, metal, rom); + + if (ver) + *ver = pg_ver; + + ret = wlcore_set_partition(wl, &wl->ptable[PART_BOOT]); + +out: + return ret; +} + +#define WL18XX_CONF_FILE_NAME "ti-connectivity/wl18xx-conf.bin" +static int wl18xx_conf_init(struct wl1271 *wl, struct device *dev) +{ + struct wl18xx_priv *priv = wl->priv; + struct wlcore_conf_file *conf_file; + const struct firmware *fw; + int ret; + + ret = request_firmware(&fw, WL18XX_CONF_FILE_NAME, dev); + if (ret < 0) { + wl1271_error("could not get configuration binary %s: %d", + WL18XX_CONF_FILE_NAME, ret); + goto out_fallback; + } + + if (fw->size != WL18XX_CONF_SIZE) { + wl1271_error("configuration binary file size is wrong, expected %zu got %zu", + WL18XX_CONF_SIZE, fw->size); + ret = -EINVAL; + goto out; + } + + conf_file = (struct wlcore_conf_file *) fw->data; + + if (conf_file->header.magic != cpu_to_le32(WL18XX_CONF_MAGIC)) { + wl1271_error("configuration binary file magic number mismatch, " + "expected 0x%0x got 0x%0x", WL18XX_CONF_MAGIC, + conf_file->header.magic); + ret = -EINVAL; + goto out; + } + + if (conf_file->header.version != cpu_to_le32(WL18XX_CONF_VERSION)) { + wl1271_error("configuration binary file version not supported, " + "expected 0x%08x got 0x%08x", + WL18XX_CONF_VERSION, conf_file->header.version); + ret = -EINVAL; + goto out; + } + + memcpy(&wl->conf, &conf_file->core, sizeof(wl18xx_conf)); + memcpy(&priv->conf, &conf_file->priv, sizeof(priv->conf)); + + goto out; + +out_fallback: + wl1271_warning("falling back to default config"); + + /* apply driver default configuration */ + memcpy(&wl->conf, &wl18xx_conf, sizeof(wl18xx_conf)); + /* apply default private configuration */ + memcpy(&priv->conf, &wl18xx_default_priv_conf, sizeof(priv->conf)); + + /* For now we just fallback */ + return 0; + +out: + release_firmware(fw); + return ret; +} + +static int wl18xx_plt_init(struct wl1271 *wl) +{ + int ret; + + /* calibrator based auto/fem detect not supported for 18xx */ + if (wl->plt_mode == PLT_FEM_DETECT) { + wl1271_error("wl18xx_plt_init: PLT FEM_DETECT not supported"); + return -EINVAL; + } + + ret = wlcore_write32(wl, WL18XX_SCR_PAD8, WL18XX_SCR_PAD8_PLT); + if (ret < 0) + return ret; + + return wl->ops->boot(wl); +} + +static int wl18xx_get_mac(struct wl1271 *wl) +{ + u32 mac1, mac2; + int ret; + + ret = wlcore_set_partition(wl, &wl->ptable[PART_TOP_PRCM_ELP_SOC]); + if (ret < 0) + goto out; + + ret = wlcore_read32(wl, WL18XX_REG_FUSE_BD_ADDR_1, &mac1); + if (ret < 0) + goto out; + + ret = wlcore_read32(wl, WL18XX_REG_FUSE_BD_ADDR_2, &mac2); + if (ret < 0) + goto out; + + /* these are the two parts of the BD_ADDR */ + wl->fuse_oui_addr = ((mac2 & 0xffff) << 8) + + ((mac1 & 0xff000000) >> 24); + wl->fuse_nic_addr = (mac1 & 0xffffff); + + ret = wlcore_set_partition(wl, &wl->ptable[PART_DOWN]); + +out: + return ret; +} + +static int wl18xx_handle_static_data(struct wl1271 *wl, + struct wl1271_static_data *static_data) +{ + struct wl18xx_static_data_priv *static_data_priv = + (struct wl18xx_static_data_priv *) static_data->priv; + + strncpy(wl->chip.phy_fw_ver_str, static_data_priv->phy_version, + sizeof(wl->chip.phy_fw_ver_str)); + + /* make sure the string is NULL-terminated */ + wl->chip.phy_fw_ver_str[sizeof(wl->chip.phy_fw_ver_str) - 1] = '\0'; + + wl1271_info("PHY firmware version: %s", static_data_priv->phy_version); + + return 0; +} + +static int wl18xx_get_spare_blocks(struct wl1271 *wl, bool is_gem) +{ + struct wl18xx_priv *priv = wl->priv; + + /* If we have keys requiring extra spare, indulge them */ + if (priv->extra_spare_key_count) + return WL18XX_TX_HW_EXTRA_BLOCK_SPARE; + + return WL18XX_TX_HW_BLOCK_SPARE; +} + +static int wl18xx_set_key(struct wl1271 *wl, enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key_conf) +{ + struct wl18xx_priv *priv = wl->priv; + bool change_spare = false, special_enc; + int ret; + + wl1271_debug(DEBUG_CRYPT, "extra spare keys before: %d", + priv->extra_spare_key_count); + + special_enc = key_conf->cipher == WL1271_CIPHER_SUITE_GEM || + key_conf->cipher == WLAN_CIPHER_SUITE_TKIP; + + ret = wlcore_set_key(wl, cmd, vif, sta, key_conf); + if (ret < 0) + goto out; + + /* + * when adding the first or removing the last GEM/TKIP key, + * we have to adjust the number of spare blocks. + */ + if (special_enc) { + if (cmd == SET_KEY) { + /* first key */ + change_spare = (priv->extra_spare_key_count == 0); + priv->extra_spare_key_count++; + } else if (cmd == DISABLE_KEY) { + /* last key */ + change_spare = (priv->extra_spare_key_count == 1); + priv->extra_spare_key_count--; + } + } + + wl1271_debug(DEBUG_CRYPT, "extra spare keys after: %d", + priv->extra_spare_key_count); + + if (!change_spare) + goto out; + + /* key is now set, change the spare blocks */ + if (priv->extra_spare_key_count) + ret = wl18xx_set_host_cfg_bitmap(wl, + WL18XX_TX_HW_EXTRA_BLOCK_SPARE); + else + ret = wl18xx_set_host_cfg_bitmap(wl, + WL18XX_TX_HW_BLOCK_SPARE); + +out: + return ret; +} + +static u32 wl18xx_pre_pkt_send(struct wl1271 *wl, + u32 buf_offset, u32 last_len) +{ + if (wl->quirks & WLCORE_QUIRK_TX_PAD_LAST_FRAME) { + struct wl1271_tx_hw_descr *last_desc; + + /* get the last TX HW descriptor written to the aggr buf */ + last_desc = (struct wl1271_tx_hw_descr *)(wl->aggr_buf + + buf_offset - last_len); + + /* the last frame is padded up to an SDIO block */ + last_desc->wl18xx_mem.ctrl &= ~WL18XX_TX_CTRL_NOT_PADDED; + return ALIGN(buf_offset, WL12XX_BUS_BLOCK_SIZE); + } + + /* no modifications */ + return buf_offset; +} + +static void wl18xx_sta_rc_update(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct ieee80211_sta *sta, + u32 changed) +{ + bool wide = sta->bandwidth >= IEEE80211_STA_RX_BW_40; + + wl1271_debug(DEBUG_MAC80211, "mac80211 sta_rc_update wide %d", wide); + + if (!(changed & IEEE80211_RC_BW_CHANGED)) + return; + + mutex_lock(&wl->mutex); + + /* sanity */ + if (WARN_ON(wlvif->bss_type != BSS_TYPE_STA_BSS)) + goto out; + + /* ignore the change before association */ + if (!test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags)) + goto out; + + /* + * If we started out as wide, we can change the operation mode. If we + * thought this was a 20mhz AP, we have to reconnect + */ + if (wlvif->sta.role_chan_type == NL80211_CHAN_HT40MINUS || + wlvif->sta.role_chan_type == NL80211_CHAN_HT40PLUS) + wl18xx_acx_peer_ht_operation_mode(wl, wlvif->sta.hlid, wide); + else + ieee80211_connection_loss(wl12xx_wlvif_to_vif(wlvif)); + +out: + mutex_unlock(&wl->mutex); +} + +static int wl18xx_set_peer_cap(struct wl1271 *wl, + struct ieee80211_sta_ht_cap *ht_cap, + bool allow_ht_operation, + u32 rate_set, u8 hlid) +{ + return wl18xx_acx_set_peer_cap(wl, ht_cap, allow_ht_operation, + rate_set, hlid); +} + +static bool wl18xx_lnk_high_prio(struct wl1271 *wl, u8 hlid, + struct wl1271_link *lnk) +{ + u8 thold; + struct wl18xx_fw_status_priv *status_priv = + (struct wl18xx_fw_status_priv *)wl->fw_status_2->priv; + u32 suspend_bitmap = le32_to_cpu(status_priv->link_suspend_bitmap); + + /* suspended links are never high priority */ + if (test_bit(hlid, (unsigned long *)&suspend_bitmap)) + return false; + + /* the priority thresholds are taken from FW */ + if (test_bit(hlid, (unsigned long *)&wl->fw_fast_lnk_map) && + !test_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map)) + thold = status_priv->tx_fast_link_prio_threshold; + else + thold = status_priv->tx_slow_link_prio_threshold; + + return lnk->allocated_pkts < thold; +} + +static bool wl18xx_lnk_low_prio(struct wl1271 *wl, u8 hlid, + struct wl1271_link *lnk) +{ + u8 thold; + struct wl18xx_fw_status_priv *status_priv = + (struct wl18xx_fw_status_priv *)wl->fw_status_2->priv; + u32 suspend_bitmap = le32_to_cpu(status_priv->link_suspend_bitmap); + + if (test_bit(hlid, (unsigned long *)&suspend_bitmap)) + thold = status_priv->tx_suspend_threshold; + else if (test_bit(hlid, (unsigned long *)&wl->fw_fast_lnk_map) && + !test_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map)) + thold = status_priv->tx_fast_stop_threshold; + else + thold = status_priv->tx_slow_stop_threshold; + + return lnk->allocated_pkts < thold; +} + +static int wl18xx_setup(struct wl1271 *wl); + +static struct wlcore_ops wl18xx_ops = { + .setup = wl18xx_setup, + .identify_chip = wl18xx_identify_chip, + .boot = wl18xx_boot, + .plt_init = wl18xx_plt_init, + .trigger_cmd = wl18xx_trigger_cmd, + .ack_event = wl18xx_ack_event, + .wait_for_event = wl18xx_wait_for_event, + .process_mailbox_events = wl18xx_process_mailbox_events, + .calc_tx_blocks = wl18xx_calc_tx_blocks, + .set_tx_desc_blocks = wl18xx_set_tx_desc_blocks, + .set_tx_desc_data_len = wl18xx_set_tx_desc_data_len, + .get_rx_buf_align = wl18xx_get_rx_buf_align, + .get_rx_packet_len = wl18xx_get_rx_packet_len, + .tx_immediate_compl = wl18xx_tx_immediate_completion, + .tx_delayed_compl = NULL, + .hw_init = wl18xx_hw_init, + .set_tx_desc_csum = wl18xx_set_tx_desc_csum, + .get_pg_ver = wl18xx_get_pg_ver, + .set_rx_csum = wl18xx_set_rx_csum, + .sta_get_ap_rate_mask = wl18xx_sta_get_ap_rate_mask, + .ap_get_mimo_wide_rate_mask = wl18xx_ap_get_mimo_wide_rate_mask, + .get_mac = wl18xx_get_mac, + .debugfs_init = wl18xx_debugfs_add_files, + .scan_start = wl18xx_scan_start, + .scan_stop = wl18xx_scan_stop, + .sched_scan_start = wl18xx_sched_scan_start, + .sched_scan_stop = wl18xx_scan_sched_scan_stop, + .handle_static_data = wl18xx_handle_static_data, + .get_spare_blocks = wl18xx_get_spare_blocks, + .set_key = wl18xx_set_key, + .channel_switch = wl18xx_cmd_channel_switch, + .pre_pkt_send = wl18xx_pre_pkt_send, + .sta_rc_update = wl18xx_sta_rc_update, + .set_peer_cap = wl18xx_set_peer_cap, + .lnk_high_prio = wl18xx_lnk_high_prio, + .lnk_low_prio = wl18xx_lnk_low_prio, +}; + +/* HT cap appropriate for wide channels in 2Ghz */ +static struct ieee80211_sta_ht_cap wl18xx_siso40_ht_cap_2ghz = { + .cap = IEEE80211_HT_CAP_SGI_20 | IEEE80211_HT_CAP_SGI_40 | + IEEE80211_HT_CAP_SUP_WIDTH_20_40 | IEEE80211_HT_CAP_DSSSCCK40 | + IEEE80211_HT_CAP_GRN_FLD, + .ht_supported = true, + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_16K, + .ampdu_density = IEEE80211_HT_MPDU_DENSITY_16, + .mcs = { + .rx_mask = { 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, + .rx_highest = cpu_to_le16(150), + .tx_params = IEEE80211_HT_MCS_TX_DEFINED, + }, +}; + +/* HT cap appropriate for wide channels in 5Ghz */ +static struct ieee80211_sta_ht_cap wl18xx_siso40_ht_cap_5ghz = { + .cap = IEEE80211_HT_CAP_SGI_20 | IEEE80211_HT_CAP_SGI_40 | + IEEE80211_HT_CAP_SUP_WIDTH_20_40 | + IEEE80211_HT_CAP_GRN_FLD, + .ht_supported = true, + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_16K, + .ampdu_density = IEEE80211_HT_MPDU_DENSITY_16, + .mcs = { + .rx_mask = { 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, + .rx_highest = cpu_to_le16(150), + .tx_params = IEEE80211_HT_MCS_TX_DEFINED, + }, +}; + +/* HT cap appropriate for SISO 20 */ +static struct ieee80211_sta_ht_cap wl18xx_siso20_ht_cap = { + .cap = IEEE80211_HT_CAP_SGI_20 | + IEEE80211_HT_CAP_GRN_FLD, + .ht_supported = true, + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_16K, + .ampdu_density = IEEE80211_HT_MPDU_DENSITY_16, + .mcs = { + .rx_mask = { 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, + .rx_highest = cpu_to_le16(72), + .tx_params = IEEE80211_HT_MCS_TX_DEFINED, + }, +}; + +/* HT cap appropriate for MIMO rates in 20mhz channel */ +static struct ieee80211_sta_ht_cap wl18xx_mimo_ht_cap_2ghz = { + .cap = IEEE80211_HT_CAP_SGI_20 | + IEEE80211_HT_CAP_GRN_FLD, + .ht_supported = true, + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_16K, + .ampdu_density = IEEE80211_HT_MPDU_DENSITY_16, + .mcs = { + .rx_mask = { 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, }, + .rx_highest = cpu_to_le16(144), + .tx_params = IEEE80211_HT_MCS_TX_DEFINED, + }, +}; + +static int wl18xx_setup(struct wl1271 *wl) +{ + struct wl18xx_priv *priv = wl->priv; + int ret; + + wl->rtable = wl18xx_rtable; + wl->num_tx_desc = WL18XX_NUM_TX_DESCRIPTORS; + wl->num_rx_desc = WL18XX_NUM_RX_DESCRIPTORS; + wl->num_channels = 2; + wl->num_mac_addr = WL18XX_NUM_MAC_ADDRESSES; + wl->band_rate_to_idx = wl18xx_band_rate_to_idx; + wl->hw_tx_rate_tbl_size = WL18XX_CONF_HW_RXTX_RATE_MAX; + wl->hw_min_ht_rate = WL18XX_CONF_HW_RXTX_RATE_MCS0; + wl->fw_status_priv_len = sizeof(struct wl18xx_fw_status_priv); + wl->stats.fw_stats_len = sizeof(struct wl18xx_acx_statistics); + wl->static_data_priv_len = sizeof(struct wl18xx_static_data_priv); + + if (num_rx_desc_param != -1) + wl->num_rx_desc = num_rx_desc_param; + + ret = wl18xx_conf_init(wl, wl->dev); + if (ret < 0) + return ret; + + /* If the module param is set, update it in conf */ + if (board_type_param) { + if (!strcmp(board_type_param, "fpga")) { + priv->conf.phy.board_type = BOARD_TYPE_FPGA_18XX; + } else if (!strcmp(board_type_param, "hdk")) { + priv->conf.phy.board_type = BOARD_TYPE_HDK_18XX; + } else if (!strcmp(board_type_param, "dvp")) { + priv->conf.phy.board_type = BOARD_TYPE_DVP_18XX; + } else if (!strcmp(board_type_param, "evb")) { + priv->conf.phy.board_type = BOARD_TYPE_EVB_18XX; + } else if (!strcmp(board_type_param, "com8")) { + priv->conf.phy.board_type = BOARD_TYPE_COM8_18XX; + } else { + wl1271_error("invalid board type '%s'", + board_type_param); + return -EINVAL; + } + } + + if (priv->conf.phy.board_type >= NUM_BOARD_TYPES) { + wl1271_error("invalid board type '%d'", + priv->conf.phy.board_type); + return -EINVAL; + } + + if (low_band_component_param != -1) + priv->conf.phy.low_band_component = low_band_component_param; + if (low_band_component_type_param != -1) + priv->conf.phy.low_band_component_type = + low_band_component_type_param; + if (high_band_component_param != -1) + priv->conf.phy.high_band_component = high_band_component_param; + if (high_band_component_type_param != -1) + priv->conf.phy.high_band_component_type = + high_band_component_type_param; + if (pwr_limit_reference_11_abg_param != -1) + priv->conf.phy.pwr_limit_reference_11_abg = + pwr_limit_reference_11_abg_param; + if (n_antennas_2_param != -1) + priv->conf.phy.number_of_assembled_ant2_4 = n_antennas_2_param; + if (n_antennas_5_param != -1) + priv->conf.phy.number_of_assembled_ant5 = n_antennas_5_param; + if (dc2dc_param != -1) + priv->conf.phy.external_pa_dc2dc = dc2dc_param; + + if (ht_mode_param) { + if (!strcmp(ht_mode_param, "default")) + priv->conf.ht.mode = HT_MODE_DEFAULT; + else if (!strcmp(ht_mode_param, "wide")) + priv->conf.ht.mode = HT_MODE_WIDE; + else if (!strcmp(ht_mode_param, "siso20")) + priv->conf.ht.mode = HT_MODE_SISO20; + else { + wl1271_error("invalid ht_mode '%s'", ht_mode_param); + return -EINVAL; + } + } + + if (priv->conf.ht.mode == HT_MODE_DEFAULT) { + /* + * Only support mimo with multiple antennas. Fall back to + * siso40. + */ + if (wl18xx_is_mimo_supported(wl)) + wlcore_set_ht_cap(wl, IEEE80211_BAND_2GHZ, + &wl18xx_mimo_ht_cap_2ghz); + else + wlcore_set_ht_cap(wl, IEEE80211_BAND_2GHZ, + &wl18xx_siso40_ht_cap_2ghz); + + /* 5Ghz is always wide */ + wlcore_set_ht_cap(wl, IEEE80211_BAND_5GHZ, + &wl18xx_siso40_ht_cap_5ghz); + } else if (priv->conf.ht.mode == HT_MODE_WIDE) { + wlcore_set_ht_cap(wl, IEEE80211_BAND_2GHZ, + &wl18xx_siso40_ht_cap_2ghz); + wlcore_set_ht_cap(wl, IEEE80211_BAND_5GHZ, + &wl18xx_siso40_ht_cap_5ghz); + } else if (priv->conf.ht.mode == HT_MODE_SISO20) { + wlcore_set_ht_cap(wl, IEEE80211_BAND_2GHZ, + &wl18xx_siso20_ht_cap); + wlcore_set_ht_cap(wl, IEEE80211_BAND_5GHZ, + &wl18xx_siso20_ht_cap); + } + + if (!checksum_param) { + wl18xx_ops.set_rx_csum = NULL; + wl18xx_ops.init_vif = NULL; + } + + /* Enable 11a Band only if we have 5G antennas */ + wl->enable_11a = (priv->conf.phy.number_of_assembled_ant5 != 0); + + return 0; +} + +static int wl18xx_probe(struct platform_device *pdev) +{ + struct wl1271 *wl; + struct ieee80211_hw *hw; + int ret; + + hw = wlcore_alloc_hw(sizeof(struct wl18xx_priv), + WL18XX_AGGR_BUFFER_SIZE, + sizeof(struct wl18xx_event_mailbox)); + if (IS_ERR(hw)) { + wl1271_error("can't allocate hw"); + ret = PTR_ERR(hw); + goto out; + } + + wl = hw->priv; + wl->ops = &wl18xx_ops; + wl->ptable = wl18xx_ptable; + ret = wlcore_probe(wl, pdev); + if (ret) + goto out_free; + + return ret; + +out_free: + wlcore_free_hw(wl); +out: + return ret; +} + +static const struct platform_device_id wl18xx_id_table[] = { + { "wl18xx", 0 }, + { } /* Terminating Entry */ +}; +MODULE_DEVICE_TABLE(platform, wl18xx_id_table); + +static struct platform_driver wl18xx_driver = { + .probe = wl18xx_probe, + .remove = wlcore_remove, + .id_table = wl18xx_id_table, + .driver = { + .name = "wl18xx_driver", + .owner = THIS_MODULE, + } +}; + +module_platform_driver(wl18xx_driver); +module_param_named(ht_mode, ht_mode_param, charp, S_IRUSR); +MODULE_PARM_DESC(ht_mode, "Force HT mode: wide or siso20"); + +module_param_named(board_type, board_type_param, charp, S_IRUSR); +MODULE_PARM_DESC(board_type, "Board type: fpga, hdk (default), evb, com8 or " + "dvp"); + +module_param_named(checksum, checksum_param, bool, S_IRUSR); +MODULE_PARM_DESC(checksum, "Enable TCP checksum: boolean (defaults to false)"); + +module_param_named(dc2dc, dc2dc_param, int, S_IRUSR); +MODULE_PARM_DESC(dc2dc, "External DC2DC: u8 (defaults to 0)"); + +module_param_named(n_antennas_2, n_antennas_2_param, int, S_IRUSR); +MODULE_PARM_DESC(n_antennas_2, + "Number of installed 2.4GHz antennas: 1 (default) or 2"); + +module_param_named(n_antennas_5, n_antennas_5_param, int, S_IRUSR); +MODULE_PARM_DESC(n_antennas_5, + "Number of installed 5GHz antennas: 1 (default) or 2"); + +module_param_named(low_band_component, low_band_component_param, int, + S_IRUSR); +MODULE_PARM_DESC(low_band_component, "Low band component: u8 " + "(default is 0x01)"); + +module_param_named(low_band_component_type, low_band_component_type_param, + int, S_IRUSR); +MODULE_PARM_DESC(low_band_component_type, "Low band component type: u8 " + "(default is 0x05 or 0x06 depending on the board_type)"); + +module_param_named(high_band_component, high_band_component_param, int, + S_IRUSR); +MODULE_PARM_DESC(high_band_component, "High band component: u8, " + "(default is 0x01)"); + +module_param_named(high_band_component_type, high_band_component_type_param, + int, S_IRUSR); +MODULE_PARM_DESC(high_band_component_type, "High band component type: u8 " + "(default is 0x09)"); + +module_param_named(pwr_limit_reference_11_abg, + pwr_limit_reference_11_abg_param, int, S_IRUSR); +MODULE_PARM_DESC(pwr_limit_reference_11_abg, "Power limit reference: u8 " + "(default is 0xc8)"); + +module_param_named(num_rx_desc, + num_rx_desc_param, int, S_IRUSR); +MODULE_PARM_DESC(num_rx_desc_param, + "Number of Rx descriptors: u8 (default is 32)"); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Luciano Coelho "); +MODULE_FIRMWARE(WL18XX_FW_NAME); diff --git a/drivers/net/wireless/ti/wl18xx/reg.h b/drivers/net/wireless/ti/wl18xx/reg.h new file mode 100644 index 0000000..6306e04 --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/reg.h @@ -0,0 +1,220 @@ +/* + * This file is part of wlcore + * + * Copyright (C) 2011 Texas Instruments Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __REG_H__ +#define __REG_H__ + +#define WL18XX_REGISTERS_BASE 0x00800000 +#define WL18XX_CODE_BASE 0x00000000 +#define WL18XX_DATA_BASE 0x00400000 +#define WL18XX_DOUBLE_BUFFER_BASE 0x00600000 +#define WL18XX_MCU_KEY_SEARCH_BASE 0x00700000 +#define WL18XX_PHY_BASE 0x00900000 +#define WL18XX_TOP_OCP_BASE 0x00A00000 +#define WL18XX_PACKET_RAM_BASE 0x00B00000 +#define WL18XX_HOST_BASE 0x00C00000 + +#define WL18XX_REGISTERS_DOWN_SIZE 0x0000B000 + +#define WL18XX_REG_BOOT_PART_START 0x00802000 +#define WL18XX_REG_BOOT_PART_SIZE 0x00014578 + +#define WL18XX_PHY_INIT_MEM_ADDR 0x80926000 + +#define WL18XX_SDIO_WSPI_BASE (WL18XX_REGISTERS_BASE) +#define WL18XX_REG_CONFIG_BASE (WL18XX_REGISTERS_BASE + 0x02000) +#define WL18XX_WGCM_REGS_BASE (WL18XX_REGISTERS_BASE + 0x03000) +#define WL18XX_ENC_BASE (WL18XX_REGISTERS_BASE + 0x04000) +#define WL18XX_INTERRUPT_BASE (WL18XX_REGISTERS_BASE + 0x05000) +#define WL18XX_UART_BASE (WL18XX_REGISTERS_BASE + 0x06000) +#define WL18XX_WELP_BASE (WL18XX_REGISTERS_BASE + 0x07000) +#define WL18XX_TCP_CKSM_BASE (WL18XX_REGISTERS_BASE + 0x08000) +#define WL18XX_FIFO_BASE (WL18XX_REGISTERS_BASE + 0x09000) +#define WL18XX_OCP_BRIDGE_BASE (WL18XX_REGISTERS_BASE + 0x0A000) +#define WL18XX_PMAC_RX_BASE (WL18XX_REGISTERS_BASE + 0x14800) +#define WL18XX_PMAC_ACM_BASE (WL18XX_REGISTERS_BASE + 0x14C00) +#define WL18XX_PMAC_TX_BASE (WL18XX_REGISTERS_BASE + 0x15000) +#define WL18XX_PMAC_CSR_BASE (WL18XX_REGISTERS_BASE + 0x15400) + +#define WL18XX_REG_ECPU_CONTROL (WL18XX_REGISTERS_BASE + 0x02004) +#define WL18XX_REG_INTERRUPT_NO_CLEAR (WL18XX_REGISTERS_BASE + 0x050E8) +#define WL18XX_REG_INTERRUPT_ACK (WL18XX_REGISTERS_BASE + 0x050F0) +#define WL18XX_REG_INTERRUPT_TRIG (WL18XX_REGISTERS_BASE + 0x5074) +#define WL18XX_REG_INTERRUPT_TRIG_H (WL18XX_REGISTERS_BASE + 0x5078) +#define WL18XX_REG_INTERRUPT_MASK (WL18XX_REGISTERS_BASE + 0x0050DC) + +#define WL18XX_REG_CHIP_ID_B (WL18XX_REGISTERS_BASE + 0x01542C) + +#define WL18XX_SLV_MEM_DATA (WL18XX_HOST_BASE + 0x0018) +#define WL18XX_SLV_REG_DATA (WL18XX_HOST_BASE + 0x0008) + +/* Scratch Pad registers*/ +#define WL18XX_SCR_PAD0 (WL18XX_REGISTERS_BASE + 0x0154EC) +#define WL18XX_SCR_PAD1 (WL18XX_REGISTERS_BASE + 0x0154F0) +#define WL18XX_SCR_PAD2 (WL18XX_REGISTERS_BASE + 0x0154F4) +#define WL18XX_SCR_PAD3 (WL18XX_REGISTERS_BASE + 0x0154F8) +#define WL18XX_SCR_PAD4 (WL18XX_REGISTERS_BASE + 0x0154FC) +#define WL18XX_SCR_PAD4_SET (WL18XX_REGISTERS_BASE + 0x015504) +#define WL18XX_SCR_PAD4_CLR (WL18XX_REGISTERS_BASE + 0x015500) +#define WL18XX_SCR_PAD5 (WL18XX_REGISTERS_BASE + 0x015508) +#define WL18XX_SCR_PAD5_SET (WL18XX_REGISTERS_BASE + 0x015510) +#define WL18XX_SCR_PAD5_CLR (WL18XX_REGISTERS_BASE + 0x01550C) +#define WL18XX_SCR_PAD6 (WL18XX_REGISTERS_BASE + 0x015514) +#define WL18XX_SCR_PAD7 (WL18XX_REGISTERS_BASE + 0x015518) +#define WL18XX_SCR_PAD8 (WL18XX_REGISTERS_BASE + 0x01551C) +#define WL18XX_SCR_PAD9 (WL18XX_REGISTERS_BASE + 0x015520) + +/* Spare registers*/ +#define WL18XX_SPARE_A1 (WL18XX_REGISTERS_BASE + 0x002194) +#define WL18XX_SPARE_A2 (WL18XX_REGISTERS_BASE + 0x002198) +#define WL18XX_SPARE_A3 (WL18XX_REGISTERS_BASE + 0x00219C) +#define WL18XX_SPARE_A4 (WL18XX_REGISTERS_BASE + 0x0021A0) +#define WL18XX_SPARE_A5 (WL18XX_REGISTERS_BASE + 0x0021A4) +#define WL18XX_SPARE_A6 (WL18XX_REGISTERS_BASE + 0x0021A8) +#define WL18XX_SPARE_A7 (WL18XX_REGISTERS_BASE + 0x0021AC) +#define WL18XX_SPARE_A8 (WL18XX_REGISTERS_BASE + 0x0021B0) +#define WL18XX_SPARE_B1 (WL18XX_REGISTERS_BASE + 0x015524) +#define WL18XX_SPARE_B2 (WL18XX_REGISTERS_BASE + 0x015528) +#define WL18XX_SPARE_B3 (WL18XX_REGISTERS_BASE + 0x01552C) +#define WL18XX_SPARE_B4 (WL18XX_REGISTERS_BASE + 0x015530) +#define WL18XX_SPARE_B5 (WL18XX_REGISTERS_BASE + 0x015534) +#define WL18XX_SPARE_B6 (WL18XX_REGISTERS_BASE + 0x015538) +#define WL18XX_SPARE_B7 (WL18XX_REGISTERS_BASE + 0x01553C) +#define WL18XX_SPARE_B8 (WL18XX_REGISTERS_BASE + 0x015540) + +#define WL18XX_REG_COMMAND_MAILBOX_PTR (WL18XX_SCR_PAD0) +#define WL18XX_REG_EVENT_MAILBOX_PTR (WL18XX_SCR_PAD1) +#define WL18XX_EEPROMLESS_IND (WL18XX_SCR_PAD4) + +#define WL18XX_WELP_ARM_COMMAND (WL18XX_REGISTERS_BASE + 0x7100) +#define WL18XX_ENABLE (WL18XX_REGISTERS_BASE + 0x01543C) + +/* PRCM registers */ +#define PLATFORM_DETECTION 0xA0E3E0 +#define OCS_EN 0xA02080 +#define PRIMARY_CLK_DETECT 0xA020A6 +#define PLLSH_WCS_PLL_N 0xA02362 +#define PLLSH_WCS_PLL_M 0xA02360 +#define PLLSH_WCS_PLL_Q_FACTOR_CFG_1 0xA02364 +#define PLLSH_WCS_PLL_Q_FACTOR_CFG_2 0xA02366 +#define PLLSH_WCS_PLL_P_FACTOR_CFG_1 0xA02368 +#define PLLSH_WCS_PLL_P_FACTOR_CFG_2 0xA0236A +#define PLLSH_WCS_PLL_SWALLOW_EN 0xA0236C +#define PLLSH_WL_PLL_EN 0xA02392 + +#define PLLSH_WCS_PLL_Q_FACTOR_CFG_1_MASK 0xFFFF +#define PLLSH_WCS_PLL_Q_FACTOR_CFG_2_MASK 0x007F +#define PLLSH_WCS_PLL_P_FACTOR_CFG_1_MASK 0xFFFF +#define PLLSH_WCS_PLL_P_FACTOR_CFG_2_MASK 0x000F + +#define PLLSH_WCS_PLL_SWALLOW_EN_VAL1 0x1 +#define PLLSH_WCS_PLL_SWALLOW_EN_VAL2 0x12 + +#define WL18XX_REG_FUSE_DATA_1_3 0xA0260C +#define WL18XX_PG_VER_MASK 0x70 +#define WL18XX_PG_VER_OFFSET 4 +#define WL18XX_ROM_VER_MASK 0x3 +#define WL18XX_ROM_VER_OFFSET 0 +#define WL18XX_METAL_VER_MASK 0xC +#define WL18XX_METAL_VER_OFFSET 2 +#define WL18XX_NEW_METAL_VER_MASK 0x180 +#define WL18XX_NEW_METAL_VER_OFFSET 7 + +#define WL18XX_REG_FUSE_DATA_2_3 0xA02614 +#define WL18XX_RDL_VER_MASK 0x1f00 +#define WL18XX_RDL_VER_OFFSET 8 + +#define WL18XX_REG_FUSE_BD_ADDR_1 0xA02602 +#define WL18XX_REG_FUSE_BD_ADDR_2 0xA02606 + +#define WL18XX_CMD_MBOX_ADDRESS 0xB007B4 + +#define WL18XX_FW_STATUS_ADDR 0x50F8 + +#define CHIP_ID_185x_PG10 (0x06030101) +#define CHIP_ID_185x_PG20 (0x06030111) + +/* + * Host Command Interrupt. Setting this bit masks + * the interrupt that the host issues to inform + * the FW that it has sent a command + * to the Wlan hardware Command Mailbox. + */ +#define WL18XX_INTR_TRIG_CMD BIT(28) + +/* + * Host Event Acknowlegde Interrupt. The host + * sets this bit to acknowledge that it received + * the unsolicited information from the event + * mailbox. + */ +#define WL18XX_INTR_TRIG_EVENT_ACK BIT(29) + +/* + * To boot the firmware in PLT mode we need to write this value in + * SCR_PAD8 before starting. + */ +#define WL18XX_SCR_PAD8_PLT 0xBABABEBE + +enum { + COMPONENT_NO_SWITCH = 0x0, + COMPONENT_2_WAY_SWITCH = 0x1, + COMPONENT_3_WAY_SWITCH = 0x2, + COMPONENT_MATCHING = 0x3, +}; + +enum { + FEM_NONE = 0x0, + FEM_VENDOR_1 = 0x1, + FEM_VENDOR_2 = 0x2, + FEM_VENDOR_3 = 0x3, +}; + +enum { + BOARD_TYPE_EVB_18XX = 0, + BOARD_TYPE_DVP_18XX = 1, + BOARD_TYPE_HDK_18XX = 2, + BOARD_TYPE_FPGA_18XX = 3, + BOARD_TYPE_COM8_18XX = 4, + + NUM_BOARD_TYPES, +}; + +enum { + RDL_NONE = 0, + RDL_1_HP = 1, + RDL_2_SP = 2, + RDL_3_HP = 3, + RDL_4_SP = 4, + + _RDL_LAST, + RDL_MAX = _RDL_LAST - 1, +}; + +static const char * const rdl_names[] = { + [RDL_NONE] = "", + [RDL_1_HP] = "1853 SISO", + [RDL_2_SP] = "1857 MIMO", + [RDL_3_HP] = "1893 SISO", + [RDL_4_SP] = "1897 MIMO", +}; + +#endif /* __REG_H__ */ diff --git a/drivers/net/wireless/ti/wl18xx/scan.c b/drivers/net/wireless/ti/wl18xx/scan.c new file mode 100644 index 0000000..2b642f8 --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/scan.c @@ -0,0 +1,326 @@ +/* + * This file is part of wl18xx + * + * Copyright (C) 2012 Texas Instruments. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include "scan.h" +#include "../wlcore/debug.h" + +static void wl18xx_adjust_channels(struct wl18xx_cmd_scan_params *cmd, + struct wlcore_scan_channels *cmd_channels) +{ + memcpy(cmd->passive, cmd_channels->passive, sizeof(cmd->passive)); + memcpy(cmd->active, cmd_channels->active, sizeof(cmd->active)); + cmd->dfs = cmd_channels->dfs; + cmd->passive_active = cmd_channels->passive_active; + + memcpy(cmd->channels_2, cmd_channels->channels_2, + sizeof(cmd->channels_2)); + memcpy(cmd->channels_5, cmd_channels->channels_5, + sizeof(cmd->channels_5)); + /* channels_4 are not supported, so no need to copy them */ +} + +static int wl18xx_scan_send(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct cfg80211_scan_request *req) +{ + struct wl18xx_cmd_scan_params *cmd; + struct wlcore_scan_channels *cmd_channels = NULL; + int ret; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->role_id = wlvif->role_id; + + if (WARN_ON(cmd->role_id == WL12XX_INVALID_ROLE_ID)) { + ret = -EINVAL; + goto out; + } + + cmd->scan_type = SCAN_TYPE_SEARCH; + cmd->rssi_threshold = -127; + cmd->snr_threshold = 0; + + cmd->bss_type = SCAN_BSS_TYPE_ANY; + + cmd->ssid_from_list = 0; + cmd->filter = 0; + cmd->add_broadcast = 0; + + cmd->urgency = 0; + cmd->protect = 0; + + cmd->n_probe_reqs = wl->conf.scan.num_probe_reqs; + cmd->terminate_after = 0; + + /* configure channels */ + WARN_ON(req->n_ssids > 1); + + cmd_channels = kzalloc(sizeof(*cmd_channels), GFP_KERNEL); + if (!cmd_channels) { + ret = -ENOMEM; + goto out; + } + + wlcore_set_scan_chan_params(wl, cmd_channels, req->channels, + req->n_channels, req->n_ssids, + SCAN_TYPE_SEARCH); + wl18xx_adjust_channels(cmd, cmd_channels); + + /* + * all the cycles params (except total cycles) should + * remain 0 for normal scan + */ + cmd->total_cycles = 1; + + if (req->no_cck) + cmd->rate = WL18XX_SCAN_RATE_6; + + cmd->tag = WL1271_SCAN_DEFAULT_TAG; + + if (req->n_ssids) { + cmd->ssid_len = req->ssids[0].ssid_len; + memcpy(cmd->ssid, req->ssids[0].ssid, cmd->ssid_len); + } + + /* TODO: per-band ies? */ + if (cmd->active[0]) { + u8 band = IEEE80211_BAND_2GHZ; + ret = wl12xx_cmd_build_probe_req(wl, wlvif, + cmd->role_id, band, + req->ssids ? req->ssids[0].ssid : NULL, + req->ssids ? req->ssids[0].ssid_len : 0, + req->ie, + req->ie_len, + false); + if (ret < 0) { + wl1271_error("2.4GHz PROBE request template failed"); + goto out; + } + } + + if (cmd->active[1] || cmd->dfs) { + u8 band = IEEE80211_BAND_5GHZ; + ret = wl12xx_cmd_build_probe_req(wl, wlvif, + cmd->role_id, band, + req->ssids ? req->ssids[0].ssid : NULL, + req->ssids ? req->ssids[0].ssid_len : 0, + req->ie, + req->ie_len, + false); + if (ret < 0) { + wl1271_error("5GHz PROBE request template failed"); + goto out; + } + } + + wl1271_dump(DEBUG_SCAN, "SCAN: ", cmd, sizeof(*cmd)); + + ret = wl1271_cmd_send(wl, CMD_SCAN, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("SCAN failed"); + goto out; + } + +out: + kfree(cmd_channels); + kfree(cmd); + return ret; +} + +void wl18xx_scan_completed(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + wl->scan.failed = false; + cancel_delayed_work(&wl->scan_complete_work); + ieee80211_queue_delayed_work(wl->hw, &wl->scan_complete_work, + msecs_to_jiffies(0)); +} + +static +int wl18xx_scan_sched_scan_config(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies) +{ + struct wl18xx_cmd_scan_params *cmd; + struct wlcore_scan_channels *cmd_channels = NULL; + struct conf_sched_scan_settings *c = &wl->conf.sched_scan; + int ret; + int filter_type; + + wl1271_debug(DEBUG_CMD, "cmd sched_scan scan config"); + + filter_type = wlcore_scan_sched_scan_ssid_list(wl, wlvif, req); + if (filter_type < 0) + return filter_type; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->role_id = wlvif->role_id; + + if (WARN_ON(cmd->role_id == WL12XX_INVALID_ROLE_ID)) { + ret = -EINVAL; + goto out; + } + + cmd->scan_type = SCAN_TYPE_PERIODIC; + cmd->rssi_threshold = c->rssi_threshold; + cmd->snr_threshold = c->snr_threshold; + + /* don't filter on BSS type */ + cmd->bss_type = SCAN_BSS_TYPE_ANY; + + cmd->ssid_from_list = 1; + if (filter_type == SCAN_SSID_FILTER_LIST) + cmd->filter = 1; + cmd->add_broadcast = 0; + + cmd->urgency = 0; + cmd->protect = 0; + + cmd->n_probe_reqs = c->num_probe_reqs; + /* don't stop scanning automatically when something is found */ + cmd->terminate_after = 0; + + cmd_channels = kzalloc(sizeof(*cmd_channels), GFP_KERNEL); + if (!cmd_channels) { + ret = -ENOMEM; + goto out; + } + + /* configure channels */ + wlcore_set_scan_chan_params(wl, cmd_channels, req->channels, + req->n_channels, req->n_ssids, + SCAN_TYPE_PERIODIC); + wl18xx_adjust_channels(cmd, cmd_channels); + + cmd->short_cycles_sec = 0; + cmd->long_cycles_sec = cpu_to_le16(req->interval); + cmd->short_cycles_count = 0; + + cmd->total_cycles = 0; + + cmd->tag = WL1271_SCAN_DEFAULT_TAG; + + /* create a PERIODIC_SCAN_REPORT_EVENT whenever we've got a match */ + cmd->report_threshold = 1; + cmd->terminate_on_report = 0; + + if (cmd->active[0]) { + u8 band = IEEE80211_BAND_2GHZ; + ret = wl12xx_cmd_build_probe_req(wl, wlvif, + cmd->role_id, band, + req->ssids ? req->ssids[0].ssid : NULL, + req->ssids ? req->ssids[0].ssid_len : 0, + ies->ie[band], + ies->len[band], + true); + if (ret < 0) { + wl1271_error("2.4GHz PROBE request template failed"); + goto out; + } + } + + if (cmd->active[1] || cmd->dfs) { + u8 band = IEEE80211_BAND_5GHZ; + ret = wl12xx_cmd_build_probe_req(wl, wlvif, + cmd->role_id, band, + req->ssids ? req->ssids[0].ssid : NULL, + req->ssids ? req->ssids[0].ssid_len : 0, + ies->ie[band], + ies->len[band], + true); + if (ret < 0) { + wl1271_error("5GHz PROBE request template failed"); + goto out; + } + } + + wl1271_dump(DEBUG_SCAN, "SCAN: ", cmd, sizeof(*cmd)); + + ret = wl1271_cmd_send(wl, CMD_SCAN, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("SCAN failed"); + goto out; + } + +out: + kfree(cmd_channels); + kfree(cmd); + return ret; +} + +int wl18xx_sched_scan_start(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies) +{ + return wl18xx_scan_sched_scan_config(wl, wlvif, req, ies); +} + +static int __wl18xx_scan_stop(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 scan_type) +{ + struct wl18xx_cmd_scan_stop *stop; + int ret; + + wl1271_debug(DEBUG_CMD, "cmd periodic scan stop"); + + stop = kzalloc(sizeof(*stop), GFP_KERNEL); + if (!stop) { + wl1271_error("failed to alloc memory to send sched scan stop"); + return -ENOMEM; + } + + stop->role_id = wlvif->role_id; + stop->scan_type = scan_type; + + ret = wl1271_cmd_send(wl, CMD_STOP_SCAN, stop, sizeof(*stop), 0); + if (ret < 0) { + wl1271_error("failed to send sched scan stop command"); + goto out_free; + } + +out_free: + kfree(stop); + return ret; +} + +void wl18xx_scan_sched_scan_stop(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + __wl18xx_scan_stop(wl, wlvif, SCAN_TYPE_PERIODIC); +} +int wl18xx_scan_start(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct cfg80211_scan_request *req) +{ + return wl18xx_scan_send(wl, wlvif, req); +} + +int wl18xx_scan_stop(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + return __wl18xx_scan_stop(wl, wlvif, SCAN_TYPE_SEARCH); +} diff --git a/drivers/net/wireless/ti/wl18xx/scan.h b/drivers/net/wireless/ti/wl18xx/scan.h new file mode 100644 index 0000000..eadee42 --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/scan.h @@ -0,0 +1,127 @@ +/* + * This file is part of wl18xx + * + * Copyright (C) 2012 Texas Instruments. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL18XX_SCAN_H__ +#define __WL18XX_SCAN_H__ + +#include "../wlcore/wlcore.h" +#include "../wlcore/cmd.h" +#include "../wlcore/scan.h" + +struct tracking_ch_params { + struct conn_scan_ch_params channel; + + __le32 bssid_lsb; + __le16 bssid_msb; + + u8 padding[2]; +} __packed; + +/* probe request rate */ +enum +{ + WL18XX_SCAN_RATE_1 = 0, + WL18XX_SCAN_RATE_5_5 = 1, + WL18XX_SCAN_RATE_6 = 2, +}; + +#define WL18XX_MAX_CHANNELS_5GHZ 32 + +struct wl18xx_cmd_scan_params { + struct wl1271_cmd_header header; + + u8 role_id; + u8 scan_type; + + s8 rssi_threshold; /* for filtering (in dBm) */ + s8 snr_threshold; /* for filtering (in dB) */ + + u8 bss_type; /* for filtering */ + u8 ssid_from_list; /* use ssid from configured ssid list */ + u8 filter; /* forward only results with matching ssids */ + + /* + * add broadcast ssid in addition to the configured ssids. + * the driver should add dummy entry for it (?). + */ + u8 add_broadcast; + + u8 urgency; + u8 protect; /* ??? */ + u8 n_probe_reqs; /* Number of probes requests per channel */ + u8 terminate_after; /* early terminate scan operation */ + + u8 passive[SCAN_MAX_BANDS]; /* number of passive scan channels */ + u8 active[SCAN_MAX_BANDS]; /* number of active scan channels */ + u8 dfs; /* number of dfs channels in 5ghz */ + u8 passive_active; /* number of passive before active channels 2.4ghz */ + + __le16 short_cycles_sec; + __le16 long_cycles_sec; + u8 short_cycles_count; + u8 total_cycles; /* 0 - infinite */ + u8 padding[2]; + + union { + struct { + struct conn_scan_ch_params channels_2[MAX_CHANNELS_2GHZ]; + struct conn_scan_ch_params channels_5[WL18XX_MAX_CHANNELS_5GHZ]; + struct conn_scan_ch_params channels_4[MAX_CHANNELS_4GHZ]; + }; + struct tracking_ch_params channels_tracking[WL1271_SCAN_MAX_CHANNELS]; + } ; + + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 ssid_len; /* For SCAN_SSID_FILTER_SPECIFIC */ + u8 tag; + u8 rate; + + /* send SCAN_REPORT_EVENT in periodic scans after each cycle + * if number of results >= report_threshold. Must be 0 for + * non periodic scans + */ + u8 report_threshold; + + /* Should periodic scan stop after a report event was created. + * Must be 0 for non periodic scans. + */ + u8 terminate_on_report; + + u8 padding1[3]; +} __packed; + +struct wl18xx_cmd_scan_stop { + struct wl1271_cmd_header header; + + u8 role_id; + u8 scan_type; + u8 padding[2]; +} __packed; + +int wl18xx_scan_start(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct cfg80211_scan_request *req); +int wl18xx_scan_stop(struct wl1271 *wl, struct wl12xx_vif *wlvif); +void wl18xx_scan_completed(struct wl1271 *wl, struct wl12xx_vif *wlvif); +int wl18xx_sched_scan_start(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies); +void wl18xx_scan_sched_scan_stop(struct wl1271 *wl, struct wl12xx_vif *wlvif); +#endif diff --git a/drivers/net/wireless/ti/wl18xx/tx.c b/drivers/net/wireless/ti/wl18xx/tx.c new file mode 100644 index 0000000..57c6943 --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/tx.c @@ -0,0 +1,171 @@ +/* + * This file is part of wl18xx + * + * Copyright (C) 2011 Texas Instruments Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "../wlcore/wlcore.h" +#include "../wlcore/cmd.h" +#include "../wlcore/debug.h" +#include "../wlcore/acx.h" +#include "../wlcore/tx.h" + +#include "wl18xx.h" +#include "tx.h" + +static +void wl18xx_get_last_tx_rate(struct wl1271 *wl, struct ieee80211_vif *vif, + struct ieee80211_tx_rate *rate) +{ + u8 fw_rate = wl->fw_status_2->counters.tx_last_rate; + + if (fw_rate > CONF_HW_RATE_INDEX_MAX) { + wl1271_error("last Tx rate invalid: %d", fw_rate); + rate->idx = 0; + rate->flags = 0; + return; + } + + if (fw_rate <= CONF_HW_RATE_INDEX_54MBPS) { + rate->idx = fw_rate; + rate->flags = 0; + } else { + rate->flags = IEEE80211_TX_RC_MCS; + rate->idx = fw_rate - CONF_HW_RATE_INDEX_MCS0; + + /* SGI modifier is counted as a separate rate */ + if (fw_rate >= CONF_HW_RATE_INDEX_MCS7_SGI) + (rate->idx)--; + if (fw_rate == CONF_HW_RATE_INDEX_MCS15_SGI) + (rate->idx)--; + + /* this also covers the 40Mhz SGI case (= MCS15) */ + if (fw_rate == CONF_HW_RATE_INDEX_MCS7_SGI || + fw_rate == CONF_HW_RATE_INDEX_MCS15_SGI) + rate->flags |= IEEE80211_TX_RC_SHORT_GI; + + if (fw_rate > CONF_HW_RATE_INDEX_MCS7_SGI && vif) { + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + if (wlvif->channel_type == NL80211_CHAN_HT40MINUS || + wlvif->channel_type == NL80211_CHAN_HT40PLUS) { + /* adjustment needed for range 0-7 */ + rate->idx -= 8; + rate->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; + } + } + } +} + +static void wl18xx_tx_complete_packet(struct wl1271 *wl, u8 tx_stat_byte) +{ + struct ieee80211_tx_info *info; + struct sk_buff *skb; + int id = tx_stat_byte & WL18XX_TX_STATUS_DESC_ID_MASK; + bool tx_success; + + /* check for id legality */ + if (unlikely(id >= wl->num_tx_desc || wl->tx_frames[id] == NULL)) { + wl1271_warning("illegal id in tx completion: %d", id); + return; + } + + /* a zero bit indicates Tx success */ + tx_success = !(tx_stat_byte & BIT(WL18XX_TX_STATUS_STAT_BIT_IDX)); + + skb = wl->tx_frames[id]; + info = IEEE80211_SKB_CB(skb); + + if (wl12xx_is_dummy_packet(wl, skb)) { + wl1271_free_tx_id(wl, id); + return; + } + + /* update the TX status info */ + if (tx_success && !(info->flags & IEEE80211_TX_CTL_NO_ACK)) + info->flags |= IEEE80211_TX_STAT_ACK; + /* + * first pass info->control.vif while it's valid, and then fill out + * the info->status structures + */ + wl18xx_get_last_tx_rate(wl, info->control.vif, &info->status.rates[0]); + + info->status.rates[0].count = 1; /* no data about retries */ + info->status.ack_signal = -1; + + if (!tx_success) + wl->stats.retry_count++; + + /* + * TODO: update sequence number for encryption? seems to be + * unsupported for now. needed for recovery with encryption. + */ + + /* remove private header from packet */ + skb_pull(skb, sizeof(struct wl1271_tx_hw_descr)); + + /* remove TKIP header space if present */ + if ((wl->quirks & WLCORE_QUIRK_TKIP_HEADER_SPACE) && + info->control.hw_key && + info->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) { + int hdrlen = ieee80211_get_hdrlen_from_skb(skb); + memmove(skb->data + WL1271_EXTRA_SPACE_TKIP, skb->data, hdrlen); + skb_pull(skb, WL1271_EXTRA_SPACE_TKIP); + } + + wl1271_debug(DEBUG_TX, "tx status id %u skb 0x%p success %d", + id, skb, tx_success); + + /* return the packet to the stack */ + skb_queue_tail(&wl->deferred_tx_queue, skb); + queue_work(wl->freezable_wq, &wl->netstack_work); + wl1271_free_tx_id(wl, id); +} + +void wl18xx_tx_immediate_complete(struct wl1271 *wl) +{ + struct wl18xx_fw_status_priv *status_priv = + (struct wl18xx_fw_status_priv *)wl->fw_status_2->priv; + struct wl18xx_priv *priv = wl->priv; + u8 i; + + /* nothing to do here */ + if (priv->last_fw_rls_idx == status_priv->fw_release_idx) + return; + + /* freed Tx descriptors */ + wl1271_debug(DEBUG_TX, "last released desc = %d, current idx = %d", + priv->last_fw_rls_idx, status_priv->fw_release_idx); + + if (status_priv->fw_release_idx >= WL18XX_FW_MAX_TX_STATUS_DESC) { + wl1271_error("invalid desc release index %d", + status_priv->fw_release_idx); + WARN_ON(1); + return; + } + + for (i = priv->last_fw_rls_idx; + i != status_priv->fw_release_idx; + i = (i + 1) % WL18XX_FW_MAX_TX_STATUS_DESC) { + wl18xx_tx_complete_packet(wl, + status_priv->released_tx_desc[i]); + + wl->tx_results_count++; + } + + priv->last_fw_rls_idx = status_priv->fw_release_idx; +} diff --git a/drivers/net/wireless/ti/wl18xx/tx.h b/drivers/net/wireless/ti/wl18xx/tx.h new file mode 100644 index 0000000..ccddc54 --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/tx.h @@ -0,0 +1,46 @@ +/* + * This file is part of wl18xx + * + * Copyright (C) 2011 Texas Instruments. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL18XX_TX_H__ +#define __WL18XX_TX_H__ + +#include "../wlcore/wlcore.h" + +#define WL18XX_TX_HW_BLOCK_SPARE 1 +/* for special cases - namely, TKIP and GEM */ +#define WL18XX_TX_HW_EXTRA_BLOCK_SPARE 2 +#define WL18XX_TX_HW_BLOCK_SIZE 268 + +#define WL18XX_TX_STATUS_DESC_ID_MASK 0x7F +#define WL18XX_TX_STATUS_STAT_BIT_IDX 7 + +/* Indicates this TX HW frame is not padded to SDIO block size */ +#define WL18XX_TX_CTRL_NOT_PADDED BIT(7) + +/* + * The FW uses a special bit to indicate a wide channel should be used in + * the rate policy. + */ +#define CONF_TX_RATE_USE_WIDE_CHAN BIT(31) + +void wl18xx_tx_immediate_complete(struct wl1271 *wl); + +#endif /* __WL12XX_TX_H__ */ diff --git a/drivers/net/wireless/ti/wl18xx/wl18xx.h b/drivers/net/wireless/ti/wl18xx/wl18xx.h new file mode 100644 index 0000000..9204e07 --- /dev/null +++ b/drivers/net/wireless/ti/wl18xx/wl18xx.h @@ -0,0 +1,140 @@ +/* + * This file is part of wl18xx + * + * Copyright (C) 2011 Texas Instruments Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WL18XX_PRIV_H__ +#define __WL18XX_PRIV_H__ + +#include "conf.h" + +/* minimum FW required for driver */ +#define WL18XX_CHIP_VER 8 +#define WL18XX_IFTYPE_VER 5 +#define WL18XX_MAJOR_VER WLCORE_FW_VER_IGNORE +#define WL18XX_SUBTYPE_VER WLCORE_FW_VER_IGNORE +#define WL18XX_MINOR_VER 39 + +#define WL18XX_CMD_MAX_SIZE 740 + +#define WL18XX_AGGR_BUFFER_SIZE (13 * PAGE_SIZE) + +#define WL18XX_NUM_TX_DESCRIPTORS 32 +#define WL18XX_NUM_RX_DESCRIPTORS 32 + +#define WL18XX_NUM_MAC_ADDRESSES 3 + +#define WL18XX_RX_BA_MAX_SESSIONS 5 + +struct wl18xx_priv { + /* buffer for sending commands to FW */ + u8 cmd_buf[WL18XX_CMD_MAX_SIZE]; + + struct wl18xx_priv_conf conf; + + /* Index of last released Tx desc in FW */ + u8 last_fw_rls_idx; + + /* number of keys requiring extra spare mem-blocks */ + int extra_spare_key_count; +}; + +#define WL18XX_FW_MAX_TX_STATUS_DESC 33 + +struct wl18xx_fw_status_priv { + /* + * Index in released_tx_desc for first byte that holds + * released tx host desc + */ + u8 fw_release_idx; + + /* + * Array of host Tx descriptors, where fw_release_idx + * indicated the first released idx. + */ + u8 released_tx_desc[WL18XX_FW_MAX_TX_STATUS_DESC]; + + /* A bitmap representing the currently suspended links. The suspend + * is short lived, for multi-channel Tx requirements. + */ + __le32 link_suspend_bitmap; + + /* packet threshold for an "almost empty" AC, + * for Tx schedulng purposes + */ + u8 tx_ac_threshold; + + /* number of packets to queue up for a link in PS */ + u8 tx_ps_threshold; + + /* number of packet to queue up for a suspended link */ + u8 tx_suspend_threshold; + + /* Should have less than this number of packets in queue of a slow + * link to qualify as high priority link + */ + u8 tx_slow_link_prio_threshold; + + /* Should have less than this number of packets in queue of a fast + * link to qualify as high priority link + */ + u8 tx_fast_link_prio_threshold; + + /* Should have less than this number of packets in queue of a slow + * link before we stop queuing up packets for it. + */ + u8 tx_slow_stop_threshold; + + /* Should have less than this number of packets in queue of a fast + * link before we stop queuing up packets for it. + */ + u8 tx_fast_stop_threshold; + + u8 padding[3]; +}; + +#define WL18XX_PHY_VERSION_MAX_LEN 20 + +struct wl18xx_static_data_priv { + char phy_version[WL18XX_PHY_VERSION_MAX_LEN]; +}; + +struct wl18xx_clk_cfg { + u32 n; + u32 m; + u32 p; + u32 q; + bool swallow; +}; + +enum { + CLOCK_CONFIG_16_2_M = 1, + CLOCK_CONFIG_16_368_M, + CLOCK_CONFIG_16_8_M, + CLOCK_CONFIG_19_2_M, + CLOCK_CONFIG_26_M, + CLOCK_CONFIG_32_736_M, + CLOCK_CONFIG_33_6_M, + CLOCK_CONFIG_38_468_M, + CLOCK_CONFIG_52_M, + + NUM_CLOCK_CONFIGS, +}; + +#endif /* __WL18XX_PRIV_H__ */ diff --git a/drivers/net/wireless/ti/wlcore/Kconfig b/drivers/net/wireless/ti/wlcore/Kconfig new file mode 100644 index 0000000..7b15664 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/Kconfig @@ -0,0 +1,41 @@ +config WLCORE + depends on !BACKPORT_KERNEL_2_6_30 + tristate "TI wlcore support" + depends on m + depends on WL_TI && GENERIC_HARDIRQS && MAC80211 + select BACKPORT_FW_LOADER + ---help--- + This module contains the main code for TI WLAN chips. It abstracts + hardware-specific differences among different chipset families. + Each chipset family needs to implement its own lower-level module + that will depend on this module for the common code. + + If you choose to build a module, it will be called wlcore. Say N if + unsure. + +config WLCORE_SPI + depends on !BACKPORT_KERNEL_2_6_25 + tristate "TI wlcore SPI support" + depends on m + depends on WLCORE && SPI_MASTER + depends on CRC7 + ---help--- + This module adds support for the SPI interface of adapters using + TI WLAN chipsets. Select this if your platform is using + the SPI bus. + + If you choose to build a module, it'll be called wlcore_spi. + Say N if unsure. + +config WLCORE_SDIO + depends on !BACKPORT_KERNEL_2_6_38 + tristate "TI wlcore SDIO support" + depends on m + depends on WLCORE && MMC + ---help--- + This module adds support for the SDIO interface of adapters using + TI WLAN chipsets. Select this if your platform is using + the SDIO bus. + + If you choose to build a module, it'll be called wlcore_sdio. + Say N if unsure. diff --git a/drivers/net/wireless/ti/wlcore/Makefile b/drivers/net/wireless/ti/wlcore/Makefile new file mode 100644 index 0000000..3415080 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/Makefile @@ -0,0 +1,12 @@ +wlcore-objs = main.o cmd.o io.o event.o tx.o rx.o ps.o acx.o \ + boot.o init.o debugfs.o scan.o + +wlcore_spi-objs = spi.o +wlcore_sdio-objs = sdio.o + +wlcore-$(CPTCFG_NL80211_TESTMODE) += testmode.o +obj-$(CPTCFG_WLCORE) += wlcore.o +obj-$(CPTCFG_WLCORE_SPI) += wlcore_spi.o +obj-$(CPTCFG_WLCORE_SDIO) += wlcore_sdio.o + +ccflags-y += -D__CHECK_ENDIAN__ diff --git a/drivers/net/wireless/ti/wlcore/acx.c b/drivers/net/wireless/ti/wlcore/acx.c new file mode 100644 index 0000000..7a970cd --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/acx.c @@ -0,0 +1,1848 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "acx.h" + +#include +#include +#include +#include + +#include "wlcore.h" +#include "debug.h" +#include "wl12xx_80211.h" +#include "ps.h" +#include "hw_ops.h" + +int wl1271_acx_wake_up_conditions(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 wake_up_event, u8 listen_interval) +{ + struct acx_wake_up_condition *wake_up; + int ret; + + wl1271_debug(DEBUG_ACX, "acx wake up conditions (wake_up_event %d listen_interval %d)", + wake_up_event, listen_interval); + + wake_up = kzalloc(sizeof(*wake_up), GFP_KERNEL); + if (!wake_up) { + ret = -ENOMEM; + goto out; + } + + wake_up->role_id = wlvif->role_id; + wake_up->wake_up_event = wake_up_event; + wake_up->listen_interval = listen_interval; + + ret = wl1271_cmd_configure(wl, ACX_WAKE_UP_CONDITIONS, + wake_up, sizeof(*wake_up)); + if (ret < 0) { + wl1271_warning("could not set wake up conditions: %d", ret); + goto out; + } + +out: + kfree(wake_up); + return ret; +} + +int wl1271_acx_sleep_auth(struct wl1271 *wl, u8 sleep_auth) +{ + struct acx_sleep_auth *auth; + int ret; + + wl1271_debug(DEBUG_ACX, "acx sleep auth %d", sleep_auth); + + auth = kzalloc(sizeof(*auth), GFP_KERNEL); + if (!auth) { + ret = -ENOMEM; + goto out; + } + + auth->sleep_auth = sleep_auth; + + ret = wl1271_cmd_configure(wl, ACX_SLEEP_AUTH, auth, sizeof(*auth)); + if (ret < 0) { + wl1271_error("could not configure sleep_auth to %d: %d", + sleep_auth, ret); + goto out; + } + + wl->sleep_auth = sleep_auth; +out: + kfree(auth); + return ret; +} +EXPORT_SYMBOL_GPL(wl1271_acx_sleep_auth); + +int wl1271_acx_tx_power(struct wl1271 *wl, struct wl12xx_vif *wlvif, + int power) +{ + struct acx_current_tx_power *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx dot11_cur_tx_pwr %d", power); + + if (power < 0 || power > 25) + return -EINVAL; + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->role_id = wlvif->role_id; + acx->current_tx_power = power * 10; + + ret = wl1271_cmd_configure(wl, DOT11_CUR_TX_PWR, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("configure of tx power failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_feature_cfg(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + struct acx_feature_config *feature; + int ret; + + wl1271_debug(DEBUG_ACX, "acx feature cfg"); + + feature = kzalloc(sizeof(*feature), GFP_KERNEL); + if (!feature) { + ret = -ENOMEM; + goto out; + } + + /* DF_ENCRYPTION_DISABLE and DF_SNIFF_MODE_ENABLE are disabled */ + feature->role_id = wlvif->role_id; + feature->data_flow_options = 0; + feature->options = 0; + + ret = wl1271_cmd_configure(wl, ACX_FEATURE_CFG, + feature, sizeof(*feature)); + if (ret < 0) { + wl1271_error("Couldnt set HW encryption"); + goto out; + } + +out: + kfree(feature); + return ret; +} + +int wl1271_acx_mem_map(struct wl1271 *wl, struct acx_header *mem_map, + size_t len) +{ + int ret; + + wl1271_debug(DEBUG_ACX, "acx mem map"); + + ret = wl1271_cmd_interrogate(wl, ACX_MEM_MAP, mem_map, len); + if (ret < 0) + return ret; + + return 0; +} + +int wl1271_acx_rx_msdu_life_time(struct wl1271 *wl) +{ + struct acx_rx_msdu_lifetime *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx rx msdu life time"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->lifetime = cpu_to_le32(wl->conf.rx.rx_msdu_life_time); + ret = wl1271_cmd_configure(wl, DOT11_RX_MSDU_LIFE_TIME, + acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("failed to set rx msdu life time: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_slot(struct wl1271 *wl, struct wl12xx_vif *wlvif, + enum acx_slot_type slot_time) +{ + struct acx_slot *slot; + int ret; + + wl1271_debug(DEBUG_ACX, "acx slot"); + + slot = kzalloc(sizeof(*slot), GFP_KERNEL); + if (!slot) { + ret = -ENOMEM; + goto out; + } + + slot->role_id = wlvif->role_id; + slot->wone_index = STATION_WONE_INDEX; + slot->slot_time = slot_time; + + ret = wl1271_cmd_configure(wl, ACX_SLOT, slot, sizeof(*slot)); + if (ret < 0) { + wl1271_warning("failed to set slot time: %d", ret); + goto out; + } + +out: + kfree(slot); + return ret; +} + +int wl1271_acx_group_address_tbl(struct wl1271 *wl, struct wl12xx_vif *wlvif, + bool enable, void *mc_list, u32 mc_list_len) +{ + struct acx_dot11_grp_addr_tbl *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx group address tbl"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + /* MAC filtering */ + acx->role_id = wlvif->role_id; + acx->enabled = enable; + acx->num_groups = mc_list_len; + memcpy(acx->mac_table, mc_list, mc_list_len * ETH_ALEN); + + ret = wl1271_cmd_configure(wl, DOT11_GROUP_ADDRESS_TBL, + acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("failed to set group addr table: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_service_period_timeout(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + struct acx_rx_timeout *rx_timeout; + int ret; + + rx_timeout = kzalloc(sizeof(*rx_timeout), GFP_KERNEL); + if (!rx_timeout) { + ret = -ENOMEM; + goto out; + } + + wl1271_debug(DEBUG_ACX, "acx service period timeout"); + + rx_timeout->role_id = wlvif->role_id; + rx_timeout->ps_poll_timeout = cpu_to_le16(wl->conf.rx.ps_poll_timeout); + rx_timeout->upsd_timeout = cpu_to_le16(wl->conf.rx.upsd_timeout); + + ret = wl1271_cmd_configure(wl, ACX_SERVICE_PERIOD_TIMEOUT, + rx_timeout, sizeof(*rx_timeout)); + if (ret < 0) { + wl1271_warning("failed to set service period timeout: %d", + ret); + goto out; + } + +out: + kfree(rx_timeout); + return ret; +} + +int wl1271_acx_rts_threshold(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u32 rts_threshold) +{ + struct acx_rts_threshold *rts; + int ret; + + /* + * If the RTS threshold is not configured or out of range, use the + * default value. + */ + if (rts_threshold > IEEE80211_MAX_RTS_THRESHOLD) + rts_threshold = wl->conf.rx.rts_threshold; + + wl1271_debug(DEBUG_ACX, "acx rts threshold: %d", rts_threshold); + + rts = kzalloc(sizeof(*rts), GFP_KERNEL); + if (!rts) { + ret = -ENOMEM; + goto out; + } + + rts->role_id = wlvif->role_id; + rts->threshold = cpu_to_le16((u16)rts_threshold); + + ret = wl1271_cmd_configure(wl, DOT11_RTS_THRESHOLD, rts, sizeof(*rts)); + if (ret < 0) { + wl1271_warning("failed to set rts threshold: %d", ret); + goto out; + } + +out: + kfree(rts); + return ret; +} + +int wl1271_acx_dco_itrim_params(struct wl1271 *wl) +{ + struct acx_dco_itrim_params *dco; + struct conf_itrim_settings *c = &wl->conf.itrim; + int ret; + + wl1271_debug(DEBUG_ACX, "acx dco itrim parameters"); + + dco = kzalloc(sizeof(*dco), GFP_KERNEL); + if (!dco) { + ret = -ENOMEM; + goto out; + } + + dco->enable = c->enable; + dco->timeout = cpu_to_le32(c->timeout); + + ret = wl1271_cmd_configure(wl, ACX_SET_DCO_ITRIM_PARAMS, + dco, sizeof(*dco)); + if (ret < 0) { + wl1271_warning("failed to set dco itrim parameters: %d", ret); + goto out; + } + +out: + kfree(dco); + return ret; +} + +int wl1271_acx_beacon_filter_opt(struct wl1271 *wl, struct wl12xx_vif *wlvif, + bool enable_filter) +{ + struct acx_beacon_filter_option *beacon_filter = NULL; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx beacon filter opt"); + + if (enable_filter && + wl->conf.conn.bcn_filt_mode == CONF_BCN_FILT_MODE_DISABLED) + goto out; + + beacon_filter = kzalloc(sizeof(*beacon_filter), GFP_KERNEL); + if (!beacon_filter) { + ret = -ENOMEM; + goto out; + } + + beacon_filter->role_id = wlvif->role_id; + beacon_filter->enable = enable_filter; + + /* + * When set to zero, and the filter is enabled, beacons + * without the unicast TIM bit set are dropped. + */ + beacon_filter->max_num_beacons = 0; + + ret = wl1271_cmd_configure(wl, ACX_BEACON_FILTER_OPT, + beacon_filter, sizeof(*beacon_filter)); + if (ret < 0) { + wl1271_warning("failed to set beacon filter opt: %d", ret); + goto out; + } + +out: + kfree(beacon_filter); + return ret; +} + +int wl1271_acx_beacon_filter_table(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + struct acx_beacon_filter_ie_table *ie_table; + int i, idx = 0; + int ret; + bool vendor_spec = false; + + wl1271_debug(DEBUG_ACX, "acx beacon filter table"); + + ie_table = kzalloc(sizeof(*ie_table), GFP_KERNEL); + if (!ie_table) { + ret = -ENOMEM; + goto out; + } + + /* configure default beacon pass-through rules */ + ie_table->role_id = wlvif->role_id; + ie_table->num_ie = 0; + for (i = 0; i < wl->conf.conn.bcn_filt_ie_count; i++) { + struct conf_bcn_filt_rule *r = &(wl->conf.conn.bcn_filt_ie[i]); + ie_table->table[idx++] = r->ie; + ie_table->table[idx++] = r->rule; + + if (r->ie == WLAN_EID_VENDOR_SPECIFIC) { + /* only one vendor specific ie allowed */ + if (vendor_spec) + continue; + + /* for vendor specific rules configure the + additional fields */ + memcpy(&(ie_table->table[idx]), r->oui, + CONF_BCN_IE_OUI_LEN); + idx += CONF_BCN_IE_OUI_LEN; + ie_table->table[idx++] = r->type; + memcpy(&(ie_table->table[idx]), r->version, + CONF_BCN_IE_VER_LEN); + idx += CONF_BCN_IE_VER_LEN; + vendor_spec = true; + } + + ie_table->num_ie++; + } + + ret = wl1271_cmd_configure(wl, ACX_BEACON_FILTER_TABLE, + ie_table, sizeof(*ie_table)); + if (ret < 0) { + wl1271_warning("failed to set beacon filter table: %d", ret); + goto out; + } + +out: + kfree(ie_table); + return ret; +} + +#define ACX_CONN_MONIT_DISABLE_VALUE 0xffffffff + +int wl1271_acx_conn_monit_params(struct wl1271 *wl, struct wl12xx_vif *wlvif, + bool enable) +{ + struct acx_conn_monit_params *acx; + u32 threshold = ACX_CONN_MONIT_DISABLE_VALUE; + u32 timeout = ACX_CONN_MONIT_DISABLE_VALUE; + int ret; + + wl1271_debug(DEBUG_ACX, "acx connection monitor parameters: %s", + enable ? "enabled" : "disabled"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + if (enable) { + threshold = wl->conf.conn.synch_fail_thold; + timeout = wl->conf.conn.bss_lose_timeout; + } + + acx->role_id = wlvif->role_id; + acx->synch_fail_thold = cpu_to_le32(threshold); + acx->bss_lose_timeout = cpu_to_le32(timeout); + + ret = wl1271_cmd_configure(wl, ACX_CONN_MONIT_PARAMS, + acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("failed to set connection monitor " + "parameters: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + + +int wl1271_acx_sg_enable(struct wl1271 *wl, bool enable) +{ + struct acx_bt_wlan_coex *pta; + int ret; + + wl1271_debug(DEBUG_ACX, "acx sg enable"); + + pta = kzalloc(sizeof(*pta), GFP_KERNEL); + if (!pta) { + ret = -ENOMEM; + goto out; + } + + if (enable) + pta->enable = wl->conf.sg.state; + else + pta->enable = CONF_SG_DISABLE; + + ret = wl1271_cmd_configure(wl, ACX_SG_ENABLE, pta, sizeof(*pta)); + if (ret < 0) { + wl1271_warning("failed to set softgemini enable: %d", ret); + goto out; + } + +out: + kfree(pta); + return ret; +} + +int wl12xx_acx_sg_cfg(struct wl1271 *wl) +{ + struct acx_bt_wlan_coex_param *param; + struct conf_sg_settings *c = &wl->conf.sg; + int i, ret; + + wl1271_debug(DEBUG_ACX, "acx sg cfg"); + + param = kzalloc(sizeof(*param), GFP_KERNEL); + if (!param) { + ret = -ENOMEM; + goto out; + } + + /* BT-WLAN coext parameters */ + for (i = 0; i < CONF_SG_PARAMS_MAX; i++) + param->params[i] = cpu_to_le32(c->params[i]); + param->param_idx = CONF_SG_PARAMS_ALL; + + ret = wl1271_cmd_configure(wl, ACX_SG_CFG, param, sizeof(*param)); + if (ret < 0) { + wl1271_warning("failed to set sg config: %d", ret); + goto out; + } + +out: + kfree(param); + return ret; +} + +int wl1271_acx_cca_threshold(struct wl1271 *wl) +{ + struct acx_energy_detection *detection; + int ret; + + wl1271_debug(DEBUG_ACX, "acx cca threshold"); + + detection = kzalloc(sizeof(*detection), GFP_KERNEL); + if (!detection) { + ret = -ENOMEM; + goto out; + } + + detection->rx_cca_threshold = cpu_to_le16(wl->conf.rx.rx_cca_threshold); + detection->tx_energy_detection = wl->conf.tx.tx_energy_detection; + + ret = wl1271_cmd_configure(wl, ACX_CCA_THRESHOLD, + detection, sizeof(*detection)); + if (ret < 0) + wl1271_warning("failed to set cca threshold: %d", ret); + +out: + kfree(detection); + return ret; +} + +int wl1271_acx_bcn_dtim_options(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + struct acx_beacon_broadcast *bb; + int ret; + + wl1271_debug(DEBUG_ACX, "acx bcn dtim options"); + + bb = kzalloc(sizeof(*bb), GFP_KERNEL); + if (!bb) { + ret = -ENOMEM; + goto out; + } + + bb->role_id = wlvif->role_id; + bb->beacon_rx_timeout = cpu_to_le16(wl->conf.conn.beacon_rx_timeout); + bb->broadcast_timeout = cpu_to_le16(wl->conf.conn.broadcast_timeout); + bb->rx_broadcast_in_ps = wl->conf.conn.rx_broadcast_in_ps; + bb->ps_poll_threshold = wl->conf.conn.ps_poll_threshold; + + ret = wl1271_cmd_configure(wl, ACX_BCN_DTIM_OPTIONS, bb, sizeof(*bb)); + if (ret < 0) { + wl1271_warning("failed to set rx config: %d", ret); + goto out; + } + +out: + kfree(bb); + return ret; +} + +int wl1271_acx_aid(struct wl1271 *wl, struct wl12xx_vif *wlvif, u16 aid) +{ + struct acx_aid *acx_aid; + int ret; + + wl1271_debug(DEBUG_ACX, "acx aid"); + + acx_aid = kzalloc(sizeof(*acx_aid), GFP_KERNEL); + if (!acx_aid) { + ret = -ENOMEM; + goto out; + } + + acx_aid->role_id = wlvif->role_id; + acx_aid->aid = cpu_to_le16(aid); + + ret = wl1271_cmd_configure(wl, ACX_AID, acx_aid, sizeof(*acx_aid)); + if (ret < 0) { + wl1271_warning("failed to set aid: %d", ret); + goto out; + } + +out: + kfree(acx_aid); + return ret; +} + +int wl1271_acx_event_mbox_mask(struct wl1271 *wl, u32 event_mask) +{ + struct acx_event_mask *mask; + int ret; + + wl1271_debug(DEBUG_ACX, "acx event mbox mask"); + + mask = kzalloc(sizeof(*mask), GFP_KERNEL); + if (!mask) { + ret = -ENOMEM; + goto out; + } + + /* high event mask is unused */ + mask->high_event_mask = cpu_to_le32(0xffffffff); + mask->event_mask = cpu_to_le32(event_mask); + + ret = wl1271_cmd_configure(wl, ACX_EVENT_MBOX_MASK, + mask, sizeof(*mask)); + if (ret < 0) { + wl1271_warning("failed to set acx_event_mbox_mask: %d", ret); + goto out; + } + +out: + kfree(mask); + return ret; +} + +int wl1271_acx_set_preamble(struct wl1271 *wl, struct wl12xx_vif *wlvif, + enum acx_preamble_type preamble) +{ + struct acx_preamble *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx_set_preamble"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->role_id = wlvif->role_id; + acx->preamble = preamble; + + ret = wl1271_cmd_configure(wl, ACX_PREAMBLE_TYPE, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("Setting of preamble failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_cts_protect(struct wl1271 *wl, struct wl12xx_vif *wlvif, + enum acx_ctsprotect_type ctsprotect) +{ + struct acx_ctsprotect *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx_set_ctsprotect"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->role_id = wlvif->role_id; + acx->ctsprotect = ctsprotect; + + ret = wl1271_cmd_configure(wl, ACX_CTS_PROTECTION, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("Setting of ctsprotect failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_statistics(struct wl1271 *wl, void *stats) +{ + int ret; + + wl1271_debug(DEBUG_ACX, "acx statistics"); + + ret = wl1271_cmd_interrogate(wl, ACX_STATISTICS, stats, + wl->stats.fw_stats_len); + if (ret < 0) { + wl1271_warning("acx statistics failed: %d", ret); + return -ENOMEM; + } + + return 0; +} + +int wl1271_acx_sta_rate_policies(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + struct acx_rate_policy *acx; + struct conf_tx_rate_class *c = &wl->conf.tx.sta_rc_conf; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx rate policies"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + + if (!acx) { + ret = -ENOMEM; + goto out; + } + + wl1271_debug(DEBUG_ACX, "basic_rate: 0x%x, full_rate: 0x%x", + wlvif->basic_rate, wlvif->rate_set); + + /* configure one basic rate class */ + acx->rate_policy_idx = cpu_to_le32(wlvif->sta.basic_rate_idx); + acx->rate_policy.enabled_rates = cpu_to_le32(wlvif->basic_rate); + acx->rate_policy.short_retry_limit = c->short_retry_limit; + acx->rate_policy.long_retry_limit = c->long_retry_limit; + acx->rate_policy.aflags = c->aflags; + + ret = wl1271_cmd_configure(wl, ACX_RATE_POLICY, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("Setting of rate policies failed: %d", ret); + goto out; + } + + /* configure one AP supported rate class */ + acx->rate_policy_idx = cpu_to_le32(wlvif->sta.ap_rate_idx); + + /* the AP policy is HW specific */ + acx->rate_policy.enabled_rates = + cpu_to_le32(wlcore_hw_sta_get_ap_rate_mask(wl, wlvif)); + acx->rate_policy.short_retry_limit = c->short_retry_limit; + acx->rate_policy.long_retry_limit = c->long_retry_limit; + acx->rate_policy.aflags = c->aflags; + + ret = wl1271_cmd_configure(wl, ACX_RATE_POLICY, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("Setting of rate policies failed: %d", ret); + goto out; + } + + /* + * configure one rate class for basic p2p operations. + * (p2p packets should always go out with OFDM rates, even + * if we are currently connected to 11b AP) + */ + acx->rate_policy_idx = cpu_to_le32(wlvif->sta.p2p_rate_idx); + acx->rate_policy.enabled_rates = + cpu_to_le32(CONF_TX_RATE_MASK_BASIC_P2P); + acx->rate_policy.short_retry_limit = c->short_retry_limit; + acx->rate_policy.long_retry_limit = c->long_retry_limit; + acx->rate_policy.aflags = c->aflags; + + ret = wl1271_cmd_configure(wl, ACX_RATE_POLICY, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("Setting of rate policies failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_ap_rate_policy(struct wl1271 *wl, struct conf_tx_rate_class *c, + u8 idx) +{ + struct acx_rate_policy *acx; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx ap rate policy %d rates 0x%x", + idx, c->enabled_rates); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->rate_policy.enabled_rates = cpu_to_le32(c->enabled_rates); + acx->rate_policy.short_retry_limit = c->short_retry_limit; + acx->rate_policy.long_retry_limit = c->long_retry_limit; + acx->rate_policy.aflags = c->aflags; + + acx->rate_policy_idx = cpu_to_le32(idx); + + ret = wl1271_cmd_configure(wl, ACX_RATE_POLICY, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("Setting of ap rate policy failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_ac_cfg(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 ac, u8 cw_min, u16 cw_max, u8 aifsn, u16 txop) +{ + struct acx_ac_cfg *acx; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx ac cfg %d cw_ming %d cw_max %d " + "aifs %d txop %d", ac, cw_min, cw_max, aifsn, txop); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->role_id = wlvif->role_id; + acx->ac = ac; + acx->cw_min = cw_min; + acx->cw_max = cpu_to_le16(cw_max); + acx->aifsn = aifsn; + acx->tx_op_limit = cpu_to_le16(txop); + + ret = wl1271_cmd_configure(wl, ACX_AC_CFG, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx ac cfg failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_tid_cfg(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 queue_id, u8 channel_type, + u8 tsid, u8 ps_scheme, u8 ack_policy, + u32 apsd_conf0, u32 apsd_conf1) +{ + struct acx_tid_config *acx; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx tid config"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->role_id = wlvif->role_id; + acx->queue_id = queue_id; + acx->channel_type = channel_type; + acx->tsid = tsid; + acx->ps_scheme = ps_scheme; + acx->ack_policy = ack_policy; + acx->apsd_conf[0] = cpu_to_le32(apsd_conf0); + acx->apsd_conf[1] = cpu_to_le32(apsd_conf1); + + ret = wl1271_cmd_configure(wl, ACX_TID_CFG, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("Setting of tid config failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_frag_threshold(struct wl1271 *wl, u32 frag_threshold) +{ + struct acx_frag_threshold *acx; + int ret = 0; + + /* + * If the fragmentation is not configured or out of range, use the + * default value. + */ + if (frag_threshold > IEEE80211_MAX_FRAG_THRESHOLD) + frag_threshold = wl->conf.tx.frag_threshold; + + wl1271_debug(DEBUG_ACX, "acx frag threshold: %d", frag_threshold); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->frag_threshold = cpu_to_le16((u16)frag_threshold); + ret = wl1271_cmd_configure(wl, ACX_FRAG_CFG, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("Setting of frag threshold failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_tx_config_options(struct wl1271 *wl) +{ + struct acx_tx_config_options *acx; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx tx config options"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->tx_compl_timeout = cpu_to_le16(wl->conf.tx.tx_compl_timeout); + acx->tx_compl_threshold = cpu_to_le16(wl->conf.tx.tx_compl_threshold); + ret = wl1271_cmd_configure(wl, ACX_TX_CONFIG_OPT, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("Setting of tx options failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl12xx_acx_mem_cfg(struct wl1271 *wl) +{ + struct wl12xx_acx_config_memory *mem_conf; + struct conf_memory_settings *mem; + int ret; + + wl1271_debug(DEBUG_ACX, "wl1271 mem cfg"); + + mem_conf = kzalloc(sizeof(*mem_conf), GFP_KERNEL); + if (!mem_conf) { + ret = -ENOMEM; + goto out; + } + + mem = &wl->conf.mem; + + /* memory config */ + mem_conf->num_stations = mem->num_stations; + mem_conf->rx_mem_block_num = mem->rx_block_num; + mem_conf->tx_min_mem_block_num = mem->tx_min_block_num; + mem_conf->num_ssid_profiles = mem->ssid_profiles; + mem_conf->total_tx_descriptors = cpu_to_le32(wl->num_tx_desc); + mem_conf->dyn_mem_enable = mem->dynamic_memory; + mem_conf->tx_free_req = mem->min_req_tx_blocks; + mem_conf->rx_free_req = mem->min_req_rx_blocks; + mem_conf->tx_min = mem->tx_min; + mem_conf->fwlog_blocks = wl->conf.fwlog.mem_blocks; + + ret = wl1271_cmd_configure(wl, ACX_MEM_CFG, mem_conf, + sizeof(*mem_conf)); + if (ret < 0) { + wl1271_warning("wl1271 mem config failed: %d", ret); + goto out; + } + +out: + kfree(mem_conf); + return ret; +} +EXPORT_SYMBOL_GPL(wl12xx_acx_mem_cfg); + +int wl1271_acx_init_mem_config(struct wl1271 *wl) +{ + int ret; + + wl->target_mem_map = kzalloc(sizeof(struct wl1271_acx_mem_map), + GFP_KERNEL); + if (!wl->target_mem_map) { + wl1271_error("couldn't allocate target memory map"); + return -ENOMEM; + } + + /* we now ask for the firmware built memory map */ + ret = wl1271_acx_mem_map(wl, (void *)wl->target_mem_map, + sizeof(struct wl1271_acx_mem_map)); + if (ret < 0) { + wl1271_error("couldn't retrieve firmware memory map"); + kfree(wl->target_mem_map); + wl->target_mem_map = NULL; + return ret; + } + + /* initialize TX block book keeping */ + wl->tx_blocks_available = + le32_to_cpu(wl->target_mem_map->num_tx_mem_blocks); + wl1271_debug(DEBUG_TX, "available tx blocks: %d", + wl->tx_blocks_available); + + return 0; +} +EXPORT_SYMBOL_GPL(wl1271_acx_init_mem_config); + +int wl1271_acx_init_rx_interrupt(struct wl1271 *wl) +{ + struct wl1271_acx_rx_config_opt *rx_conf; + int ret; + + wl1271_debug(DEBUG_ACX, "wl1271 rx interrupt config"); + + rx_conf = kzalloc(sizeof(*rx_conf), GFP_KERNEL); + if (!rx_conf) { + ret = -ENOMEM; + goto out; + } + + rx_conf->threshold = cpu_to_le16(wl->conf.rx.irq_pkt_threshold); + rx_conf->timeout = cpu_to_le16(wl->conf.rx.irq_timeout); + rx_conf->mblk_threshold = cpu_to_le16(wl->conf.rx.irq_blk_threshold); + rx_conf->queue_type = wl->conf.rx.queue_type; + + ret = wl1271_cmd_configure(wl, ACX_RX_CONFIG_OPT, rx_conf, + sizeof(*rx_conf)); + if (ret < 0) { + wl1271_warning("wl1271 rx config opt failed: %d", ret); + goto out; + } + +out: + kfree(rx_conf); + return ret; +} + +int wl1271_acx_bet_enable(struct wl1271 *wl, struct wl12xx_vif *wlvif, + bool enable) +{ + struct wl1271_acx_bet_enable *acx = NULL; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx bet enable"); + + if (enable && wl->conf.conn.bet_enable == CONF_BET_MODE_DISABLE) + goto out; + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->role_id = wlvif->role_id; + acx->enable = enable ? CONF_BET_MODE_ENABLE : CONF_BET_MODE_DISABLE; + acx->max_consecutive = wl->conf.conn.bet_max_consecutive; + + ret = wl1271_cmd_configure(wl, ACX_BET_ENABLE, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx bet enable failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_arp_ip_filter(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 enable, __be32 address) +{ + struct wl1271_acx_arp_filter *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx arp ip filter, enable: %d", enable); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->role_id = wlvif->role_id; + acx->version = ACX_IPV4_VERSION; + acx->enable = enable; + + if (enable) + memcpy(acx->address, &address, ACX_IPV4_ADDR_SIZE); + + ret = wl1271_cmd_configure(wl, ACX_ARP_IP_FILTER, + acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("failed to set arp ip filter: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_pm_config(struct wl1271 *wl) +{ + struct wl1271_acx_pm_config *acx = NULL; + struct conf_pm_config_settings *c = &wl->conf.pm_config; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx pm config"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->host_clk_settling_time = cpu_to_le32(c->host_clk_settling_time); + acx->host_fast_wakeup_support = c->host_fast_wakeup_support; + + ret = wl1271_cmd_configure(wl, ACX_PM_CONFIG, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx pm config failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} +EXPORT_SYMBOL_GPL(wl1271_acx_pm_config); + +int wl1271_acx_keep_alive_mode(struct wl1271 *wl, struct wl12xx_vif *wlvif, + bool enable) +{ + struct wl1271_acx_keep_alive_mode *acx = NULL; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx keep alive mode: %d", enable); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->role_id = wlvif->role_id; + acx->enabled = enable; + + ret = wl1271_cmd_configure(wl, ACX_KEEP_ALIVE_MODE, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx keep alive mode failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_keep_alive_config(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 index, u8 tpl_valid) +{ + struct wl1271_acx_keep_alive_config *acx = NULL; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx keep alive config"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->role_id = wlvif->role_id; + acx->period = cpu_to_le32(wl->conf.conn.keep_alive_interval); + acx->index = index; + acx->tpl_validation = tpl_valid; + acx->trigger = ACX_KEEP_ALIVE_NO_TX; + + ret = wl1271_cmd_configure(wl, ACX_SET_KEEP_ALIVE_CONFIG, + acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx keep alive config failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_rssi_snr_trigger(struct wl1271 *wl, struct wl12xx_vif *wlvif, + bool enable, s16 thold, u8 hyst) +{ + struct wl1271_acx_rssi_snr_trigger *acx = NULL; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx rssi snr trigger"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + wlvif->last_rssi_event = -1; + + acx->role_id = wlvif->role_id; + acx->pacing = cpu_to_le16(wl->conf.roam_trigger.trigger_pacing); + acx->metric = WL1271_ACX_TRIG_METRIC_RSSI_BEACON; + acx->type = WL1271_ACX_TRIG_TYPE_EDGE; + if (enable) + acx->enable = WL1271_ACX_TRIG_ENABLE; + else + acx->enable = WL1271_ACX_TRIG_DISABLE; + + acx->index = WL1271_ACX_TRIG_IDX_RSSI; + acx->dir = WL1271_ACX_TRIG_DIR_BIDIR; + acx->threshold = cpu_to_le16(thold); + acx->hysteresis = hyst; + + ret = wl1271_cmd_configure(wl, ACX_RSSI_SNR_TRIGGER, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx rssi snr trigger setting failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_rssi_snr_avg_weights(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + struct wl1271_acx_rssi_snr_avg_weights *acx = NULL; + struct conf_roam_trigger_settings *c = &wl->conf.roam_trigger; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx rssi snr avg weights"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->role_id = wlvif->role_id; + acx->rssi_beacon = c->avg_weight_rssi_beacon; + acx->rssi_data = c->avg_weight_rssi_data; + acx->snr_beacon = c->avg_weight_snr_beacon; + acx->snr_data = c->avg_weight_snr_data; + + ret = wl1271_cmd_configure(wl, ACX_RSSI_SNR_WEIGHTS, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx rssi snr trigger weights failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_set_ht_capabilities(struct wl1271 *wl, + struct ieee80211_sta_ht_cap *ht_cap, + bool allow_ht_operation, u8 hlid) +{ + struct wl1271_acx_ht_capabilities *acx; + int ret = 0; + u32 ht_capabilites = 0; + + wl1271_debug(DEBUG_ACX, "acx ht capabilities setting " + "sta supp: %d sta cap: %d", ht_cap->ht_supported, + ht_cap->cap); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + if (allow_ht_operation && ht_cap->ht_supported) { + /* no need to translate capabilities - use the spec values */ + ht_capabilites = ht_cap->cap; + + /* + * this bit is not employed by the spec but only by FW to + * indicate peer HT support + */ + ht_capabilites |= WL12XX_HT_CAP_HT_OPERATION; + + /* get data from A-MPDU parameters field */ + acx->ampdu_max_length = ht_cap->ampdu_factor; + acx->ampdu_min_spacing = ht_cap->ampdu_density; + } + + acx->hlid = hlid; + acx->ht_capabilites = cpu_to_le32(ht_capabilites); + + ret = wl1271_cmd_configure(wl, ACX_PEER_HT_CAP, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx ht capabilities setting failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} +EXPORT_SYMBOL_GPL(wl1271_acx_set_ht_capabilities); + + +int wl1271_acx_set_ht_information(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + u16 ht_operation_mode) +{ + struct wl1271_acx_ht_information *acx; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx ht information setting"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->role_id = wlvif->role_id; + acx->ht_protection = + (u8)(ht_operation_mode & IEEE80211_HT_OP_MODE_PROTECTION); + acx->rifs_mode = 0; + acx->gf_protection = + !!(ht_operation_mode & IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT); + acx->ht_tx_burst_limit = 0; + acx->dual_cts_protection = 0; + + ret = wl1271_cmd_configure(wl, ACX_HT_BSS_OPERATION, acx, sizeof(*acx)); + + if (ret < 0) { + wl1271_warning("acx ht information setting failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +/* Configure BA session initiator/receiver parameters setting in the FW. */ +int wl12xx_acx_set_ba_initiator_policy(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + struct wl1271_acx_ba_initiator_policy *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx ba initiator policy"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + /* set for the current role */ + acx->role_id = wlvif->role_id; + acx->tid_bitmap = wl->conf.ht.tx_ba_tid_bitmap; + acx->win_size = wl->conf.ht.tx_ba_win_size; + acx->inactivity_timeout = wl->conf.ht.inactivity_timeout; + + ret = wl1271_cmd_configure(wl, + ACX_BA_SESSION_INIT_POLICY, + acx, + sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx ba initiator policy failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +/* setup BA session receiver setting in the FW. */ +int wl12xx_acx_set_ba_receiver_session(struct wl1271 *wl, u8 tid_index, + u16 ssn, bool enable, u8 peer_hlid) +{ + struct wl1271_acx_ba_receiver_setup *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx ba receiver session setting"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->hlid = peer_hlid; + acx->tid = tid_index; + acx->enable = enable; + acx->win_size = wl->conf.ht.rx_ba_win_size; + acx->ssn = ssn; + + ret = wlcore_cmd_configure_failsafe(wl, ACX_BA_SESSION_RX_SETUP, acx, + sizeof(*acx), + BIT(CMD_STATUS_NO_RX_BA_SESSION)); + if (ret < 0) { + wl1271_warning("acx ba receiver session failed: %d", ret); + goto out; + } + + /* sometimes we can't start the session */ + if (ret == CMD_STATUS_NO_RX_BA_SESSION) { + wl1271_warning("no fw rx ba on tid %d", tid_index); + ret = -EBUSY; + goto out; + } + + ret = 0; +out: + kfree(acx); + return ret; +} + +int wl12xx_acx_tsf_info(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u64 *mactime) +{ + struct wl12xx_acx_fw_tsf_information *tsf_info; + int ret; + + tsf_info = kzalloc(sizeof(*tsf_info), GFP_KERNEL); + if (!tsf_info) { + ret = -ENOMEM; + goto out; + } + + tsf_info->role_id = wlvif->role_id; + + ret = wl1271_cmd_interrogate(wl, ACX_TSF_INFO, + tsf_info, sizeof(*tsf_info)); + if (ret < 0) { + wl1271_warning("acx tsf info interrogate failed"); + goto out; + } + + *mactime = le32_to_cpu(tsf_info->current_tsf_low) | + ((u64) le32_to_cpu(tsf_info->current_tsf_high) << 32); + +out: + kfree(tsf_info); + return ret; +} + +int wl1271_acx_ps_rx_streaming(struct wl1271 *wl, struct wl12xx_vif *wlvif, + bool enable) +{ + struct wl1271_acx_ps_rx_streaming *rx_streaming; + u32 conf_queues, enable_queues; + int i, ret = 0; + + wl1271_debug(DEBUG_ACX, "acx ps rx streaming"); + + rx_streaming = kzalloc(sizeof(*rx_streaming), GFP_KERNEL); + if (!rx_streaming) { + ret = -ENOMEM; + goto out; + } + + conf_queues = wl->conf.rx_streaming.queues; + if (enable) + enable_queues = conf_queues; + else + enable_queues = 0; + + for (i = 0; i < 8; i++) { + /* + * Skip non-changed queues, to avoid redundant acxs. + * this check assumes conf.rx_streaming.queues can't + * be changed while rx_streaming is enabled. + */ + if (!(conf_queues & BIT(i))) + continue; + + rx_streaming->role_id = wlvif->role_id; + rx_streaming->tid = i; + rx_streaming->enable = enable_queues & BIT(i); + rx_streaming->period = wl->conf.rx_streaming.interval; + rx_streaming->timeout = wl->conf.rx_streaming.interval; + + ret = wl1271_cmd_configure(wl, ACX_PS_RX_STREAMING, + rx_streaming, + sizeof(*rx_streaming)); + if (ret < 0) { + wl1271_warning("acx ps rx streaming failed: %d", ret); + goto out; + } + } +out: + kfree(rx_streaming); + return ret; +} + +int wl1271_acx_ap_max_tx_retry(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + struct wl1271_acx_ap_max_tx_retry *acx = NULL; + int ret; + + wl1271_debug(DEBUG_ACX, "acx ap max tx retry"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) + return -ENOMEM; + + acx->role_id = wlvif->role_id; + acx->max_tx_retry = cpu_to_le16(wl->conf.tx.max_tx_retries); + + ret = wl1271_cmd_configure(wl, ACX_MAX_TX_FAILURE, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx ap max tx retry failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl12xx_acx_config_ps(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + struct wl1271_acx_config_ps *config_ps; + int ret; + + wl1271_debug(DEBUG_ACX, "acx config ps"); + + config_ps = kzalloc(sizeof(*config_ps), GFP_KERNEL); + if (!config_ps) { + ret = -ENOMEM; + goto out; + } + + config_ps->exit_retries = wl->conf.conn.psm_exit_retries; + config_ps->enter_retries = wl->conf.conn.psm_entry_retries; + config_ps->null_data_rate = cpu_to_le32(wlvif->basic_rate); + + ret = wl1271_cmd_configure(wl, ACX_CONFIG_PS, config_ps, + sizeof(*config_ps)); + + if (ret < 0) { + wl1271_warning("acx config ps failed: %d", ret); + goto out; + } + +out: + kfree(config_ps); + return ret; +} + +int wl1271_acx_set_inconnection_sta(struct wl1271 *wl, u8 *addr) +{ + struct wl1271_acx_inconnection_sta *acx = NULL; + int ret; + + wl1271_debug(DEBUG_ACX, "acx set inconnaction sta %pM", addr); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) + return -ENOMEM; + + memcpy(acx->addr, addr, ETH_ALEN); + + ret = wl1271_cmd_configure(wl, ACX_UPDATE_INCONNECTION_STA_LIST, + acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx set inconnaction sta failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl1271_acx_fm_coex(struct wl1271 *wl) +{ + struct wl1271_acx_fm_coex *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx fm coex setting"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->enable = wl->conf.fm_coex.enable; + acx->swallow_period = wl->conf.fm_coex.swallow_period; + acx->n_divider_fref_set_1 = wl->conf.fm_coex.n_divider_fref_set_1; + acx->n_divider_fref_set_2 = wl->conf.fm_coex.n_divider_fref_set_2; + acx->m_divider_fref_set_1 = + cpu_to_le16(wl->conf.fm_coex.m_divider_fref_set_1); + acx->m_divider_fref_set_2 = + cpu_to_le16(wl->conf.fm_coex.m_divider_fref_set_2); + acx->coex_pll_stabilization_time = + cpu_to_le32(wl->conf.fm_coex.coex_pll_stabilization_time); + acx->ldo_stabilization_time = + cpu_to_le16(wl->conf.fm_coex.ldo_stabilization_time); + acx->fm_disturbed_band_margin = + wl->conf.fm_coex.fm_disturbed_band_margin; + acx->swallow_clk_diff = wl->conf.fm_coex.swallow_clk_diff; + + ret = wl1271_cmd_configure(wl, ACX_FM_COEX_CFG, acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx fm coex setting failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl12xx_acx_set_rate_mgmt_params(struct wl1271 *wl) +{ + struct wl12xx_acx_set_rate_mgmt_params *acx = NULL; + struct conf_rate_policy_settings *conf = &wl->conf.rate; + int ret; + + wl1271_debug(DEBUG_ACX, "acx set rate mgmt params"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) + return -ENOMEM; + + acx->index = ACX_RATE_MGMT_ALL_PARAMS; + acx->rate_retry_score = cpu_to_le16(conf->rate_retry_score); + acx->per_add = cpu_to_le16(conf->per_add); + acx->per_th1 = cpu_to_le16(conf->per_th1); + acx->per_th2 = cpu_to_le16(conf->per_th2); + acx->max_per = cpu_to_le16(conf->max_per); + acx->inverse_curiosity_factor = conf->inverse_curiosity_factor; + acx->tx_fail_low_th = conf->tx_fail_low_th; + acx->tx_fail_high_th = conf->tx_fail_high_th; + acx->per_alpha_shift = conf->per_alpha_shift; + acx->per_add_shift = conf->per_add_shift; + acx->per_beta1_shift = conf->per_beta1_shift; + acx->per_beta2_shift = conf->per_beta2_shift; + acx->rate_check_up = conf->rate_check_up; + acx->rate_check_down = conf->rate_check_down; + memcpy(acx->rate_retry_policy, conf->rate_retry_policy, + sizeof(acx->rate_retry_policy)); + + ret = wl1271_cmd_configure(wl, ACX_SET_RATE_MGMT_PARAMS, + acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx set rate mgmt params failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +int wl12xx_acx_config_hangover(struct wl1271 *wl) +{ + struct wl12xx_acx_config_hangover *acx; + struct conf_hangover_settings *conf = &wl->conf.hangover; + int ret; + + wl1271_debug(DEBUG_ACX, "acx config hangover"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->recover_time = cpu_to_le32(conf->recover_time); + acx->hangover_period = conf->hangover_period; + acx->dynamic_mode = conf->dynamic_mode; + acx->early_termination_mode = conf->early_termination_mode; + acx->max_period = conf->max_period; + acx->min_period = conf->min_period; + acx->increase_delta = conf->increase_delta; + acx->decrease_delta = conf->decrease_delta; + acx->quiet_time = conf->quiet_time; + acx->increase_time = conf->increase_time; + acx->window_size = acx->window_size; + + ret = wl1271_cmd_configure(wl, ACX_CONFIG_HANGOVER, acx, + sizeof(*acx)); + + if (ret < 0) { + wl1271_warning("acx config hangover failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; + +} + +int wlcore_acx_average_rssi(struct wl1271 *wl, struct wl12xx_vif *wlvif, + s8 *avg_rssi) +{ + struct acx_roaming_stats *acx; + int ret = 0; + + wl1271_debug(DEBUG_ACX, "acx roaming statistics"); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) { + ret = -ENOMEM; + goto out; + } + + acx->role_id = wlvif->role_id; + ret = wl1271_cmd_interrogate(wl, ACX_ROAMING_STATISTICS_TBL, + acx, sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx roaming statistics failed: %d", ret); + ret = -ENOMEM; + goto out; + } + + *avg_rssi = acx->rssi_beacon; +out: + kfree(acx); + return ret; +} + +#ifdef CONFIG_PM +/* Set the global behaviour of RX filters - On/Off + default action */ +int wl1271_acx_default_rx_filter_enable(struct wl1271 *wl, bool enable, + enum rx_filter_action action) +{ + struct acx_default_rx_filter *acx; + int ret; + + wl1271_debug(DEBUG_ACX, "acx default rx filter en: %d act: %d", + enable, action); + + acx = kzalloc(sizeof(*acx), GFP_KERNEL); + if (!acx) + return -ENOMEM; + + acx->enable = enable; + acx->default_action = action; + + ret = wl1271_cmd_configure(wl, ACX_ENABLE_RX_DATA_FILTER, acx, + sizeof(*acx)); + if (ret < 0) { + wl1271_warning("acx default rx filter enable failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} + +/* Configure or disable a specific RX filter pattern */ +int wl1271_acx_set_rx_filter(struct wl1271 *wl, u8 index, bool enable, + struct wl12xx_rx_filter *filter) +{ + struct acx_rx_filter_cfg *acx; + int fields_size = 0; + int acx_size; + int ret; + + WARN_ON(enable && !filter); + WARN_ON(index >= WL1271_MAX_RX_FILTERS); + + wl1271_debug(DEBUG_ACX, + "acx set rx filter idx: %d enable: %d filter: %p", + index, enable, filter); + + if (enable) { + fields_size = wl1271_rx_filter_get_fields_size(filter); + + wl1271_debug(DEBUG_ACX, "act: %d num_fields: %d field_size: %d", + filter->action, filter->num_fields, fields_size); + } + + acx_size = ALIGN(sizeof(*acx) + fields_size, 4); + acx = kzalloc(acx_size, GFP_KERNEL); + + if (!acx) + return -ENOMEM; + + acx->enable = enable; + acx->index = index; + + if (enable) { + acx->num_fields = filter->num_fields; + acx->action = filter->action; + wl1271_rx_filter_flatten_fields(filter, acx->fields); + } + + wl1271_dump(DEBUG_ACX, "RX_FILTER: ", acx, acx_size); + + ret = wl1271_cmd_configure(wl, ACX_SET_RX_DATA_FILTER, acx, acx_size); + if (ret < 0) { + wl1271_warning("setting rx filter failed: %d", ret); + goto out; + } + +out: + kfree(acx); + return ret; +} +#endif /* CONFIG_PM */ diff --git a/drivers/net/wireless/ti/wlcore/acx.h b/drivers/net/wireless/ti/wlcore/acx.h new file mode 100644 index 0000000..6dcfad9 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/acx.h @@ -0,0 +1,1134 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 1998-2009 Texas Instruments. All rights reserved. + * Copyright (C) 2008-2010 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __ACX_H__ +#define __ACX_H__ + +#include "wlcore.h" +#include "cmd.h" + +/************************************************************************* + + Host Interrupt Register (WiLink -> Host) + +**************************************************************************/ +/* HW Initiated interrupt Watchdog timer expiration */ +#define WL1271_ACX_INTR_WATCHDOG BIT(0) +/* Init sequence is done (masked interrupt, detection through polling only ) */ +#define WL1271_ACX_INTR_INIT_COMPLETE BIT(1) +/* Event was entered to Event MBOX #A*/ +#define WL1271_ACX_INTR_EVENT_A BIT(2) +/* Event was entered to Event MBOX #B*/ +#define WL1271_ACX_INTR_EVENT_B BIT(3) +/* Command processing completion*/ +#define WL1271_ACX_INTR_CMD_COMPLETE BIT(4) +/* Signaling the host on HW wakeup */ +#define WL1271_ACX_INTR_HW_AVAILABLE BIT(5) +/* The MISC bit is used for aggregation of RX, TxComplete and TX rate update */ +#define WL1271_ACX_INTR_DATA BIT(6) +/* Trace message on MBOX #A */ +#define WL1271_ACX_INTR_TRACE_A BIT(7) +/* Trace message on MBOX #B */ +#define WL1271_ACX_INTR_TRACE_B BIT(8) +/* SW FW Initiated interrupt Watchdog timer expiration */ +#define WL1271_ACX_SW_INTR_WATCHDOG BIT(9) + +#define WL1271_ACX_INTR_ALL 0xFFFFFFFF + +/* all possible interrupts - only appropriate ones will be masked in */ +#define WLCORE_ALL_INTR_MASK (WL1271_ACX_INTR_WATCHDOG | \ + WL1271_ACX_INTR_EVENT_A | \ + WL1271_ACX_INTR_EVENT_B | \ + WL1271_ACX_INTR_HW_AVAILABLE | \ + WL1271_ACX_INTR_DATA | \ + WL1271_ACX_SW_INTR_WATCHDOG) + +/* Target's information element */ +struct acx_header { + struct wl1271_cmd_header cmd; + + /* acx (or information element) header */ + __le16 id; + + /* payload length (not including headers */ + __le16 len; +} __packed; + +struct acx_error_counter { + struct acx_header header; + + /* The number of PLCP errors since the last time this */ + /* information element was interrogated. This field is */ + /* automatically cleared when it is interrogated.*/ + __le32 PLCP_error; + + /* The number of FCS errors since the last time this */ + /* information element was interrogated. This field is */ + /* automatically cleared when it is interrogated.*/ + __le32 FCS_error; + + /* The number of MPDUs without PLCP header errors received*/ + /* since the last time this information element was interrogated. */ + /* This field is automatically cleared when it is interrogated.*/ + __le32 valid_frame; + + /* the number of missed sequence numbers in the squentially */ + /* values of frames seq numbers */ + __le32 seq_num_miss; +} __packed; + +enum wl12xx_role { + WL1271_ROLE_STA = 0, + WL1271_ROLE_IBSS, + WL1271_ROLE_AP, + WL1271_ROLE_DEVICE, + WL1271_ROLE_P2P_CL, + WL1271_ROLE_P2P_GO, + + WL12XX_INVALID_ROLE_TYPE = 0xff +}; + +enum wl1271_psm_mode { + /* Active mode */ + WL1271_PSM_CAM = 0, + + /* Power save mode */ + WL1271_PSM_PS = 1, + + /* Extreme low power */ + WL1271_PSM_ELP = 2, + + WL1271_PSM_MAX = WL1271_PSM_ELP, + + /* illegal out of band value of PSM mode */ + WL1271_PSM_ILLEGAL = 0xff +}; + +struct acx_sleep_auth { + struct acx_header header; + + /* The sleep level authorization of the device. */ + /* 0 - Always active*/ + /* 1 - Power down mode: light / fast sleep*/ + /* 2 - ELP mode: Deep / Max sleep*/ + u8 sleep_auth; + u8 padding[3]; +} __packed; + +enum { + HOSTIF_PCI_MASTER_HOST_INDIRECT, + HOSTIF_PCI_MASTER_HOST_DIRECT, + HOSTIF_SLAVE, + HOSTIF_PKT_RING, + HOSTIF_DONTCARE = 0xFF +}; + +#define DEFAULT_UCAST_PRIORITY 0 +#define DEFAULT_RX_Q_PRIORITY 0 +#define DEFAULT_RXQ_PRIORITY 0 /* low 0 .. 15 high */ +#define DEFAULT_RXQ_TYPE 0x07 /* All frames, Data/Ctrl/Mgmt */ +#define TRACE_BUFFER_MAX_SIZE 256 + +#define DP_RX_PACKET_RING_CHUNK_SIZE 1600 +#define DP_TX_PACKET_RING_CHUNK_SIZE 1600 +#define DP_RX_PACKET_RING_CHUNK_NUM 2 +#define DP_TX_PACKET_RING_CHUNK_NUM 2 +#define DP_TX_COMPLETE_TIME_OUT 20 + +#define TX_MSDU_LIFETIME_MIN 0 +#define TX_MSDU_LIFETIME_MAX 3000 +#define TX_MSDU_LIFETIME_DEF 512 +#define RX_MSDU_LIFETIME_MIN 0 +#define RX_MSDU_LIFETIME_MAX 0xFFFFFFFF +#define RX_MSDU_LIFETIME_DEF 512000 + +struct acx_rx_msdu_lifetime { + struct acx_header header; + + /* + * The maximum amount of time, in TU, before the + * firmware discards the MSDU. + */ + __le32 lifetime; +} __packed; + +enum acx_slot_type { + SLOT_TIME_LONG = 0, + SLOT_TIME_SHORT = 1, + DEFAULT_SLOT_TIME = SLOT_TIME_SHORT, + MAX_SLOT_TIMES = 0xFF +}; + +#define STATION_WONE_INDEX 0 + +struct acx_slot { + struct acx_header header; + + u8 role_id; + u8 wone_index; /* Reserved */ + u8 slot_time; + u8 reserved[5]; +} __packed; + + +#define ACX_MC_ADDRESS_GROUP_MAX (8) +#define ADDRESS_GROUP_MAX_LEN (ETH_ALEN * ACX_MC_ADDRESS_GROUP_MAX) + +struct acx_dot11_grp_addr_tbl { + struct acx_header header; + + u8 role_id; + u8 enabled; + u8 num_groups; + u8 pad[1]; + u8 mac_table[ADDRESS_GROUP_MAX_LEN]; +} __packed; + +struct acx_rx_timeout { + struct acx_header header; + + u8 role_id; + u8 reserved; + __le16 ps_poll_timeout; + __le16 upsd_timeout; + u8 padding[2]; +} __packed; + +struct acx_rts_threshold { + struct acx_header header; + + u8 role_id; + u8 reserved; + __le16 threshold; +} __packed; + +struct acx_beacon_filter_option { + struct acx_header header; + + u8 role_id; + u8 enable; + /* + * The number of beacons without the unicast TIM + * bit set that the firmware buffers before + * signaling the host about ready frames. + * When set to 0 and the filter is enabled, beacons + * without the unicast TIM bit set are dropped. + */ + u8 max_num_beacons; + u8 pad[1]; +} __packed; + +/* + * ACXBeaconFilterEntry (not 221) + * Byte Offset Size (Bytes) Definition + * =========== ============ ========== + * 0 1 IE identifier + * 1 1 Treatment bit mask + * + * ACXBeaconFilterEntry (221) + * Byte Offset Size (Bytes) Definition + * =========== ============ ========== + * 0 1 IE identifier + * 1 1 Treatment bit mask + * 2 3 OUI + * 5 1 Type + * 6 2 Version + * + * + * Treatment bit mask - The information element handling: + * bit 0 - The information element is compared and transferred + * in case of change. + * bit 1 - The information element is transferred to the host + * with each appearance or disappearance. + * Note that both bits can be set at the same time. + */ +#define BEACON_FILTER_TABLE_MAX_IE_NUM (32) +#define BEACON_FILTER_TABLE_MAX_VENDOR_SPECIFIC_IE_NUM (6) +#define BEACON_FILTER_TABLE_IE_ENTRY_SIZE (2) +#define BEACON_FILTER_TABLE_EXTRA_VENDOR_SPECIFIC_IE_SIZE (6) +#define BEACON_FILTER_TABLE_MAX_SIZE ((BEACON_FILTER_TABLE_MAX_IE_NUM * \ + BEACON_FILTER_TABLE_IE_ENTRY_SIZE) + \ + (BEACON_FILTER_TABLE_MAX_VENDOR_SPECIFIC_IE_NUM * \ + BEACON_FILTER_TABLE_EXTRA_VENDOR_SPECIFIC_IE_SIZE)) + +struct acx_beacon_filter_ie_table { + struct acx_header header; + + u8 role_id; + u8 num_ie; + u8 pad[2]; + u8 table[BEACON_FILTER_TABLE_MAX_SIZE]; +} __packed; + +struct acx_conn_monit_params { + struct acx_header header; + + u8 role_id; + u8 padding[3]; + __le32 synch_fail_thold; /* number of beacons missed */ + __le32 bss_lose_timeout; /* number of TU's from synch fail */ +} __packed; + +struct acx_bt_wlan_coex { + struct acx_header header; + + u8 enable; + u8 pad[3]; +} __packed; + +struct acx_bt_wlan_coex_param { + struct acx_header header; + + __le32 params[CONF_SG_PARAMS_MAX]; + u8 param_idx; + u8 padding[3]; +} __packed; + +struct acx_dco_itrim_params { + struct acx_header header; + + u8 enable; + u8 padding[3]; + __le32 timeout; +} __packed; + +struct acx_energy_detection { + struct acx_header header; + + /* The RX Clear Channel Assessment threshold in the PHY */ + __le16 rx_cca_threshold; + u8 tx_energy_detection; + u8 pad; +} __packed; + +struct acx_beacon_broadcast { + struct acx_header header; + + u8 role_id; + /* Enables receiving of broadcast packets in PS mode */ + u8 rx_broadcast_in_ps; + + __le16 beacon_rx_timeout; + __le16 broadcast_timeout; + + /* Consecutive PS Poll failures before updating the host */ + u8 ps_poll_threshold; + u8 pad[1]; +} __packed; + +struct acx_event_mask { + struct acx_header header; + + __le32 event_mask; + __le32 high_event_mask; /* Unused */ +} __packed; + +#define SCAN_PASSIVE BIT(0) +#define SCAN_5GHZ_BAND BIT(1) +#define SCAN_TRIGGERED BIT(2) +#define SCAN_PRIORITY_HIGH BIT(3) + +/* When set, disable HW encryption */ +#define DF_ENCRYPTION_DISABLE 0x01 +#define DF_SNIFF_MODE_ENABLE 0x80 + +struct acx_feature_config { + struct acx_header header; + + u8 role_id; + u8 padding[3]; + __le32 options; + __le32 data_flow_options; +} __packed; + +struct acx_current_tx_power { + struct acx_header header; + + u8 role_id; + u8 current_tx_power; + u8 padding[2]; +} __packed; + +struct acx_wake_up_condition { + struct acx_header header; + + u8 role_id; + u8 wake_up_event; /* Only one bit can be set */ + u8 listen_interval; + u8 pad[1]; +} __packed; + +struct acx_aid { + struct acx_header header; + + /* + * To be set when associated with an AP. + */ + u8 role_id; + u8 reserved; + __le16 aid; +} __packed; + +enum acx_preamble_type { + ACX_PREAMBLE_LONG = 0, + ACX_PREAMBLE_SHORT = 1 +}; + +struct acx_preamble { + struct acx_header header; + + /* + * When set, the WiLink transmits the frames with a short preamble and + * when cleared, the WiLink transmits the frames with a long preamble. + */ + u8 role_id; + u8 preamble; + u8 padding[2]; +} __packed; + +enum acx_ctsprotect_type { + CTSPROTECT_DISABLE = 0, + CTSPROTECT_ENABLE = 1 +}; + +struct acx_ctsprotect { + struct acx_header header; + u8 role_id; + u8 ctsprotect; + u8 padding[2]; +} __packed; + +struct acx_rate_class { + __le32 enabled_rates; + u8 short_retry_limit; + u8 long_retry_limit; + u8 aflags; + u8 reserved; +}; + +struct acx_rate_policy { + struct acx_header header; + + __le32 rate_policy_idx; + struct acx_rate_class rate_policy; +} __packed; + +struct acx_ac_cfg { + struct acx_header header; + u8 role_id; + u8 ac; + u8 aifsn; + u8 cw_min; + __le16 cw_max; + __le16 tx_op_limit; +} __packed; + +struct acx_tid_config { + struct acx_header header; + u8 role_id; + u8 queue_id; + u8 channel_type; + u8 tsid; + u8 ps_scheme; + u8 ack_policy; + u8 padding[2]; + __le32 apsd_conf[2]; +} __packed; + +struct acx_frag_threshold { + struct acx_header header; + __le16 frag_threshold; + u8 padding[2]; +} __packed; + +struct acx_tx_config_options { + struct acx_header header; + __le16 tx_compl_timeout; /* msec */ + __le16 tx_compl_threshold; /* number of packets */ +} __packed; + +struct wl12xx_acx_config_memory { + struct acx_header header; + + u8 rx_mem_block_num; + u8 tx_min_mem_block_num; + u8 num_stations; + u8 num_ssid_profiles; + __le32 total_tx_descriptors; + u8 dyn_mem_enable; + u8 tx_free_req; + u8 rx_free_req; + u8 tx_min; + u8 fwlog_blocks; + u8 padding[3]; +} __packed; + +struct wl1271_acx_mem_map { + struct acx_header header; + + __le32 code_start; + __le32 code_end; + + __le32 wep_defkey_start; + __le32 wep_defkey_end; + + __le32 sta_table_start; + __le32 sta_table_end; + + __le32 packet_template_start; + __le32 packet_template_end; + + /* Address of the TX result interface (control block) */ + __le32 tx_result; + __le32 tx_result_queue_start; + + __le32 queue_memory_start; + __le32 queue_memory_end; + + __le32 packet_memory_pool_start; + __le32 packet_memory_pool_end; + + __le32 debug_buffer1_start; + __le32 debug_buffer1_end; + + __le32 debug_buffer2_start; + __le32 debug_buffer2_end; + + /* Number of blocks FW allocated for TX packets */ + __le32 num_tx_mem_blocks; + + /* Number of blocks FW allocated for RX packets */ + __le32 num_rx_mem_blocks; + + /* the following 4 fields are valid in SLAVE mode only */ + u8 *tx_cbuf; + u8 *rx_cbuf; + __le32 rx_ctrl; + __le32 tx_ctrl; +} __packed; + +struct wl1271_acx_rx_config_opt { + struct acx_header header; + + __le16 mblk_threshold; + __le16 threshold; + __le16 timeout; + u8 queue_type; + u8 reserved; +} __packed; + + +struct wl1271_acx_bet_enable { + struct acx_header header; + + u8 role_id; + u8 enable; + u8 max_consecutive; + u8 padding[1]; +} __packed; + +#define ACX_IPV4_VERSION 4 +#define ACX_IPV6_VERSION 6 +#define ACX_IPV4_ADDR_SIZE 4 + +/* bitmap of enabled arp_filter features */ +#define ACX_ARP_FILTER_ARP_FILTERING BIT(0) +#define ACX_ARP_FILTER_AUTO_ARP BIT(1) + +struct wl1271_acx_arp_filter { + struct acx_header header; + u8 role_id; + u8 version; /* ACX_IPV4_VERSION, ACX_IPV6_VERSION */ + u8 enable; /* bitmap of enabled ARP filtering features */ + u8 padding[1]; + u8 address[16]; /* The configured device IP address - all ARP + requests directed to this IP address will pass + through. For IPv4, the first four bytes are + used. */ +} __packed; + +struct wl1271_acx_pm_config { + struct acx_header header; + + __le32 host_clk_settling_time; + u8 host_fast_wakeup_support; + u8 padding[3]; +} __packed; + +struct wl1271_acx_keep_alive_mode { + struct acx_header header; + + u8 role_id; + u8 enabled; + u8 padding[2]; +} __packed; + +enum { + ACX_KEEP_ALIVE_NO_TX = 0, + ACX_KEEP_ALIVE_PERIOD_ONLY +}; + +enum { + ACX_KEEP_ALIVE_TPL_INVALID = 0, + ACX_KEEP_ALIVE_TPL_VALID +}; + +struct wl1271_acx_keep_alive_config { + struct acx_header header; + + u8 role_id; + u8 index; + u8 tpl_validation; + u8 trigger; + __le32 period; +} __packed; + +/* TODO: maybe this needs to be moved somewhere else? */ +#define HOST_IF_CFG_RX_FIFO_ENABLE BIT(0) +#define HOST_IF_CFG_TX_EXTRA_BLKS_SWAP BIT(1) +#define HOST_IF_CFG_TX_PAD_TO_SDIO_BLK BIT(3) +#define HOST_IF_CFG_RX_PAD_TO_SDIO_BLK BIT(4) +#define HOST_IF_CFG_ADD_RX_ALIGNMENT BIT(6) + +enum { + WL1271_ACX_TRIG_TYPE_LEVEL = 0, + WL1271_ACX_TRIG_TYPE_EDGE, +}; + +enum { + WL1271_ACX_TRIG_DIR_LOW = 0, + WL1271_ACX_TRIG_DIR_HIGH, + WL1271_ACX_TRIG_DIR_BIDIR, +}; + +enum { + WL1271_ACX_TRIG_ENABLE = 1, + WL1271_ACX_TRIG_DISABLE, +}; + +enum { + WL1271_ACX_TRIG_METRIC_RSSI_BEACON = 0, + WL1271_ACX_TRIG_METRIC_RSSI_DATA, + WL1271_ACX_TRIG_METRIC_SNR_BEACON, + WL1271_ACX_TRIG_METRIC_SNR_DATA, +}; + +enum { + WL1271_ACX_TRIG_IDX_RSSI = 0, + WL1271_ACX_TRIG_COUNT = 8, +}; + +struct wl1271_acx_rssi_snr_trigger { + struct acx_header header; + + u8 role_id; + u8 metric; + u8 type; + u8 dir; + __le16 threshold; + __le16 pacing; /* 0 - 60000 ms */ + u8 hysteresis; + u8 index; + u8 enable; + u8 padding[1]; +}; + +struct wl1271_acx_rssi_snr_avg_weights { + struct acx_header header; + + u8 role_id; + u8 padding[3]; + u8 rssi_beacon; + u8 rssi_data; + u8 snr_beacon; + u8 snr_data; +}; + + +/* special capability bit (not employed by the 802.11n spec) */ +#define WL12XX_HT_CAP_HT_OPERATION BIT(16) + +/* + * ACX_PEER_HT_CAP + * Configure HT capabilities - declare the capabilities of the peer + * we are connected to. + */ +struct wl1271_acx_ht_capabilities { + struct acx_header header; + + /* bitmask of capability bits supported by the peer */ + __le32 ht_capabilites; + + /* Indicates to which link these capabilities apply. */ + u8 hlid; + + /* + * This the maximum A-MPDU length supported by the AP. The FW may not + * exceed this length when sending A-MPDUs + */ + u8 ampdu_max_length; + + /* This is the minimal spacing required when sending A-MPDUs to the AP*/ + u8 ampdu_min_spacing; + + u8 padding; +} __packed; + +/* + * ACX_HT_BSS_OPERATION + * Configure HT capabilities - AP rules for behavior in the BSS. + */ +struct wl1271_acx_ht_information { + struct acx_header header; + + u8 role_id; + + /* Values: 0 - RIFS not allowed, 1 - RIFS allowed */ + u8 rifs_mode; + + /* Values: 0 - 3 like in spec */ + u8 ht_protection; + + /* Values: 0 - GF protection not required, 1 - GF protection required */ + u8 gf_protection; + + /*Values: 0 - TX Burst limit not required, 1 - TX Burst Limit required*/ + u8 ht_tx_burst_limit; + + /* + * Values: 0 - Dual CTS protection not required, + * 1 - Dual CTS Protection required + * Note: When this value is set to 1 FW will protect all TXOP with RTS + * frame and will not use CTS-to-self regardless of the value of the + * ACX_CTS_PROTECTION information element + */ + u8 dual_cts_protection; + + u8 padding[2]; +} __packed; + +struct wl1271_acx_ba_initiator_policy { + struct acx_header header; + + /* Specifies role Id, Range 0-7, 0xFF means ANY role. */ + u8 role_id; + + /* + * Per TID setting for allowing TX BA. Set a bit to 1 to allow + * TX BA sessions for the corresponding TID. + */ + u8 tid_bitmap; + + /* Windows size in number of packets */ + u8 win_size; + + u8 padding1[1]; + + /* As initiator inactivity timeout in time units(TU) of 1024us */ + u16 inactivity_timeout; + + u8 padding[2]; +} __packed; + +struct wl1271_acx_ba_receiver_setup { + struct acx_header header; + + /* Specifies link id, range 0-31 */ + u8 hlid; + + u8 tid; + + u8 enable; + + /* Windows size in number of packets */ + u8 win_size; + + /* BA session starting sequence number. RANGE 0-FFF */ + u16 ssn; + + u8 padding[2]; +} __packed; + +struct wl12xx_acx_fw_tsf_information { + struct acx_header header; + + u8 role_id; + u8 padding1[3]; + __le32 current_tsf_high; + __le32 current_tsf_low; + __le32 last_bttt_high; + __le32 last_tbtt_low; + u8 last_dtim_count; + u8 padding2[3]; +} __packed; + +struct wl1271_acx_ps_rx_streaming { + struct acx_header header; + + u8 role_id; + u8 tid; + u8 enable; + + /* interval between triggers (10-100 msec) */ + u8 period; + + /* timeout before first trigger (0-200 msec) */ + u8 timeout; + u8 padding[3]; +} __packed; + +struct wl1271_acx_ap_max_tx_retry { + struct acx_header header; + + u8 role_id; + u8 padding_1; + + /* + * the number of frames transmission failures before + * issuing the aging event. + */ + __le16 max_tx_retry; +} __packed; + +struct wl1271_acx_config_ps { + struct acx_header header; + + u8 exit_retries; + u8 enter_retries; + u8 padding[2]; + __le32 null_data_rate; +} __packed; + +struct wl1271_acx_inconnection_sta { + struct acx_header header; + + u8 addr[ETH_ALEN]; + u8 padding1[2]; +} __packed; + +/* + * ACX_FM_COEX_CFG + * set the FM co-existence parameters. + */ +struct wl1271_acx_fm_coex { + struct acx_header header; + /* enable(1) / disable(0) the FM Coex feature */ + u8 enable; + /* + * Swallow period used in COEX PLL swallowing mechanism. + * 0xFF = use FW default + */ + u8 swallow_period; + /* + * The N divider used in COEX PLL swallowing mechanism for Fref of + * 38.4/19.2 Mhz. 0xFF = use FW default + */ + u8 n_divider_fref_set_1; + /* + * The N divider used in COEX PLL swallowing mechanism for Fref of + * 26/52 Mhz. 0xFF = use FW default + */ + u8 n_divider_fref_set_2; + /* + * The M divider used in COEX PLL swallowing mechanism for Fref of + * 38.4/19.2 Mhz. 0xFFFF = use FW default + */ + __le16 m_divider_fref_set_1; + /* + * The M divider used in COEX PLL swallowing mechanism for Fref of + * 26/52 Mhz. 0xFFFF = use FW default + */ + __le16 m_divider_fref_set_2; + /* + * The time duration in uSec required for COEX PLL to stabilize. + * 0xFFFFFFFF = use FW default + */ + __le32 coex_pll_stabilization_time; + /* + * The time duration in uSec required for LDO to stabilize. + * 0xFFFFFFFF = use FW default + */ + __le16 ldo_stabilization_time; + /* + * The disturbed frequency band margin around the disturbed frequency + * center (single sided). + * For example, if 2 is configured, the following channels will be + * considered disturbed channel: + * 80 +- 0.1 MHz, 91 +- 0.1 MHz, 98 +- 0.1 MHz, 102 +- 0.1 MH + * 0xFF = use FW default + */ + u8 fm_disturbed_band_margin; + /* + * The swallow clock difference of the swallowing mechanism. + * 0xFF = use FW default + */ + u8 swallow_clk_diff; +} __packed; + +#define ACX_RATE_MGMT_ALL_PARAMS 0xff +struct wl12xx_acx_set_rate_mgmt_params { + struct acx_header header; + + u8 index; /* 0xff to configure all params */ + u8 padding1; + __le16 rate_retry_score; + __le16 per_add; + __le16 per_th1; + __le16 per_th2; + __le16 max_per; + u8 inverse_curiosity_factor; + u8 tx_fail_low_th; + u8 tx_fail_high_th; + u8 per_alpha_shift; + u8 per_add_shift; + u8 per_beta1_shift; + u8 per_beta2_shift; + u8 rate_check_up; + u8 rate_check_down; + u8 rate_retry_policy[ACX_RATE_MGMT_NUM_OF_RATES]; + u8 padding2[2]; +} __packed; + +struct wl12xx_acx_config_hangover { + struct acx_header header; + + __le32 recover_time; + u8 hangover_period; + u8 dynamic_mode; + u8 early_termination_mode; + u8 max_period; + u8 min_period; + u8 increase_delta; + u8 decrease_delta; + u8 quiet_time; + u8 increase_time; + u8 window_size; + u8 padding[2]; +} __packed; + + +struct acx_default_rx_filter { + struct acx_header header; + u8 enable; + + /* action of type FILTER_XXX */ + u8 default_action; + + u8 pad[2]; +} __packed; + + +struct acx_rx_filter_cfg { + struct acx_header header; + + u8 enable; + + /* 0 - WL1271_MAX_RX_FILTERS-1 */ + u8 index; + + u8 action; + + u8 num_fields; + u8 fields[0]; +} __packed; + +struct acx_roaming_stats { + struct acx_header header; + + u8 role_id; + u8 pad[3]; + u32 missed_beacons; + u8 snr_data; + u8 snr_bacon; + s8 rssi_data; + s8 rssi_beacon; +} __packed; + +enum { + ACX_WAKE_UP_CONDITIONS = 0x0000, + ACX_MEM_CFG = 0x0001, + ACX_SLOT = 0x0002, + ACX_AC_CFG = 0x0003, + ACX_MEM_MAP = 0x0004, + ACX_AID = 0x0005, + ACX_MEDIUM_USAGE = 0x0006, + ACX_STATISTICS = 0x0007, + ACX_PWR_CONSUMPTION_STATISTICS = 0x0008, + ACX_TID_CFG = 0x0009, + ACX_PS_RX_STREAMING = 0x000A, + ACX_BEACON_FILTER_OPT = 0x000B, + ACX_NOISE_HIST = 0x000C, + ACX_HDK_VERSION = 0x000D, + ACX_PD_THRESHOLD = 0x000E, + ACX_TX_CONFIG_OPT = 0x000F, + ACX_CCA_THRESHOLD = 0x0010, + ACX_EVENT_MBOX_MASK = 0x0011, + ACX_CONN_MONIT_PARAMS = 0x0012, + ACX_DISABLE_BROADCASTS = 0x0013, + ACX_BCN_DTIM_OPTIONS = 0x0014, + ACX_SG_ENABLE = 0x0015, + ACX_SG_CFG = 0x0016, + ACX_FM_COEX_CFG = 0x0017, + ACX_BEACON_FILTER_TABLE = 0x0018, + ACX_ARP_IP_FILTER = 0x0019, + ACX_ROAMING_STATISTICS_TBL = 0x001A, + ACX_RATE_POLICY = 0x001B, + ACX_CTS_PROTECTION = 0x001C, + ACX_SLEEP_AUTH = 0x001D, + ACX_PREAMBLE_TYPE = 0x001E, + ACX_ERROR_CNT = 0x001F, + ACX_IBSS_FILTER = 0x0020, + ACX_SERVICE_PERIOD_TIMEOUT = 0x0021, + ACX_TSF_INFO = 0x0022, + ACX_CONFIG_PS_WMM = 0x0023, + ACX_ENABLE_RX_DATA_FILTER = 0x0024, + ACX_SET_RX_DATA_FILTER = 0x0025, + ACX_GET_DATA_FILTER_STATISTICS = 0x0026, + ACX_RX_CONFIG_OPT = 0x0027, + ACX_FRAG_CFG = 0x0028, + ACX_BET_ENABLE = 0x0029, + ACX_RSSI_SNR_TRIGGER = 0x002A, + ACX_RSSI_SNR_WEIGHTS = 0x002B, + ACX_KEEP_ALIVE_MODE = 0x002C, + ACX_SET_KEEP_ALIVE_CONFIG = 0x002D, + ACX_BA_SESSION_INIT_POLICY = 0x002E, + ACX_BA_SESSION_RX_SETUP = 0x002F, + ACX_PEER_HT_CAP = 0x0030, + ACX_HT_BSS_OPERATION = 0x0031, + ACX_COEX_ACTIVITY = 0x0032, + ACX_BURST_MODE = 0x0033, + ACX_SET_RATE_MGMT_PARAMS = 0x0034, + ACX_GET_RATE_MGMT_PARAMS = 0x0035, + ACX_SET_RATE_ADAPT_PARAMS = 0x0036, + ACX_SET_DCO_ITRIM_PARAMS = 0x0037, + ACX_GEN_FW_CMD = 0x0038, + ACX_HOST_IF_CFG_BITMAP = 0x0039, + ACX_MAX_TX_FAILURE = 0x003A, + ACX_UPDATE_INCONNECTION_STA_LIST = 0x003B, + DOT11_RX_MSDU_LIFE_TIME = 0x003C, + DOT11_CUR_TX_PWR = 0x003D, + DOT11_RTS_THRESHOLD = 0x003E, + DOT11_GROUP_ADDRESS_TBL = 0x003F, + ACX_PM_CONFIG = 0x0040, + ACX_CONFIG_PS = 0x0041, + ACX_CONFIG_HANGOVER = 0x0042, + ACX_FEATURE_CFG = 0x0043, + ACX_PROTECTION_CFG = 0x0044, +}; + + +int wl1271_acx_wake_up_conditions(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + u8 wake_up_event, u8 listen_interval); +int wl1271_acx_sleep_auth(struct wl1271 *wl, u8 sleep_auth); +int wl1271_acx_tx_power(struct wl1271 *wl, struct wl12xx_vif *wlvif, + int power); +int wl1271_acx_feature_cfg(struct wl1271 *wl, struct wl12xx_vif *wlvif); +int wl1271_acx_mem_map(struct wl1271 *wl, + struct acx_header *mem_map, size_t len); +int wl1271_acx_rx_msdu_life_time(struct wl1271 *wl); +int wl1271_acx_slot(struct wl1271 *wl, struct wl12xx_vif *wlvif, + enum acx_slot_type slot_time); +int wl1271_acx_group_address_tbl(struct wl1271 *wl, struct wl12xx_vif *wlvif, + bool enable, void *mc_list, u32 mc_list_len); +int wl1271_acx_service_period_timeout(struct wl1271 *wl, + struct wl12xx_vif *wlvif); +int wl1271_acx_rts_threshold(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u32 rts_threshold); +int wl1271_acx_dco_itrim_params(struct wl1271 *wl); +int wl1271_acx_beacon_filter_opt(struct wl1271 *wl, struct wl12xx_vif *wlvif, + bool enable_filter); +int wl1271_acx_beacon_filter_table(struct wl1271 *wl, + struct wl12xx_vif *wlvif); +int wl1271_acx_conn_monit_params(struct wl1271 *wl, struct wl12xx_vif *wlvif, + bool enable); +int wl1271_acx_sg_enable(struct wl1271 *wl, bool enable); +int wl12xx_acx_sg_cfg(struct wl1271 *wl); +int wl1271_acx_cca_threshold(struct wl1271 *wl); +int wl1271_acx_bcn_dtim_options(struct wl1271 *wl, struct wl12xx_vif *wlvif); +int wl1271_acx_aid(struct wl1271 *wl, struct wl12xx_vif *wlvif, u16 aid); +int wl1271_acx_event_mbox_mask(struct wl1271 *wl, u32 event_mask); +int wl1271_acx_set_preamble(struct wl1271 *wl, struct wl12xx_vif *wlvif, + enum acx_preamble_type preamble); +int wl1271_acx_cts_protect(struct wl1271 *wl, struct wl12xx_vif *wlvif, + enum acx_ctsprotect_type ctsprotect); +int wl1271_acx_statistics(struct wl1271 *wl, void *stats); +int wl1271_acx_sta_rate_policies(struct wl1271 *wl, struct wl12xx_vif *wlvif); +int wl1271_acx_ap_rate_policy(struct wl1271 *wl, struct conf_tx_rate_class *c, + u8 idx); +int wl1271_acx_ac_cfg(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 ac, u8 cw_min, u16 cw_max, u8 aifsn, u16 txop); +int wl1271_acx_tid_cfg(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 queue_id, u8 channel_type, + u8 tsid, u8 ps_scheme, u8 ack_policy, + u32 apsd_conf0, u32 apsd_conf1); +int wl1271_acx_frag_threshold(struct wl1271 *wl, u32 frag_threshold); +int wl1271_acx_tx_config_options(struct wl1271 *wl); +int wl12xx_acx_mem_cfg(struct wl1271 *wl); +int wl1271_acx_init_mem_config(struct wl1271 *wl); +int wl1271_acx_init_rx_interrupt(struct wl1271 *wl); +int wl1271_acx_smart_reflex(struct wl1271 *wl); +int wl1271_acx_bet_enable(struct wl1271 *wl, struct wl12xx_vif *wlvif, + bool enable); +int wl1271_acx_arp_ip_filter(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 enable, __be32 address); +int wl1271_acx_pm_config(struct wl1271 *wl); +int wl1271_acx_keep_alive_mode(struct wl1271 *wl, struct wl12xx_vif *vif, + bool enable); +int wl1271_acx_keep_alive_config(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 index, u8 tpl_valid); +int wl1271_acx_rssi_snr_trigger(struct wl1271 *wl, struct wl12xx_vif *wlvif, + bool enable, s16 thold, u8 hyst); +int wl1271_acx_rssi_snr_avg_weights(struct wl1271 *wl, + struct wl12xx_vif *wlvif); +int wl1271_acx_set_ht_capabilities(struct wl1271 *wl, + struct ieee80211_sta_ht_cap *ht_cap, + bool allow_ht_operation, u8 hlid); +int wl1271_acx_set_ht_information(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + u16 ht_operation_mode); +int wl12xx_acx_set_ba_initiator_policy(struct wl1271 *wl, + struct wl12xx_vif *wlvif); +int wl12xx_acx_set_ba_receiver_session(struct wl1271 *wl, u8 tid_index, + u16 ssn, bool enable, u8 peer_hlid); +int wl12xx_acx_tsf_info(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u64 *mactime); +int wl1271_acx_ps_rx_streaming(struct wl1271 *wl, struct wl12xx_vif *wlvif, + bool enable); +int wl1271_acx_ap_max_tx_retry(struct wl1271 *wl, struct wl12xx_vif *wlvif); +int wl12xx_acx_config_ps(struct wl1271 *wl, struct wl12xx_vif *wlvif); +int wl1271_acx_set_inconnection_sta(struct wl1271 *wl, u8 *addr); +int wl1271_acx_fm_coex(struct wl1271 *wl); +int wl12xx_acx_set_rate_mgmt_params(struct wl1271 *wl); +int wl12xx_acx_config_hangover(struct wl1271 *wl); +int wlcore_acx_average_rssi(struct wl1271 *wl, struct wl12xx_vif *wlvif, + s8 *avg_rssi); + +#ifdef CONFIG_PM +int wl1271_acx_default_rx_filter_enable(struct wl1271 *wl, bool enable, + enum rx_filter_action action); +int wl1271_acx_set_rx_filter(struct wl1271 *wl, u8 index, bool enable, + struct wl12xx_rx_filter *filter); +#endif /* CONFIG_PM */ +#endif /* __WL1271_ACX_H__ */ diff --git a/drivers/net/wireless/ti/wlcore/boot.c b/drivers/net/wireless/ti/wlcore/boot.c new file mode 100644 index 0000000..77752b0 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/boot.c @@ -0,0 +1,533 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2010 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + +#include "debug.h" +#include "acx.h" +#include "boot.h" +#include "io.h" +#include "event.h" +#include "rx.h" +#include "hw_ops.h" + +static int wl1271_boot_set_ecpu_ctrl(struct wl1271 *wl, u32 flag) +{ + u32 cpu_ctrl; + int ret; + + /* 10.5.0 run the firmware (I) */ + ret = wlcore_read_reg(wl, REG_ECPU_CONTROL, &cpu_ctrl); + if (ret < 0) + goto out; + + /* 10.5.1 run the firmware (II) */ + cpu_ctrl |= flag; + ret = wlcore_write_reg(wl, REG_ECPU_CONTROL, cpu_ctrl); + +out: + return ret; +} + +static int wlcore_boot_parse_fw_ver(struct wl1271 *wl, + struct wl1271_static_data *static_data) +{ + int ret; + + strncpy(wl->chip.fw_ver_str, static_data->fw_version, + sizeof(wl->chip.fw_ver_str)); + + /* make sure the string is NULL-terminated */ + wl->chip.fw_ver_str[sizeof(wl->chip.fw_ver_str) - 1] = '\0'; + + ret = sscanf(wl->chip.fw_ver_str + 4, "%u.%u.%u.%u.%u", + &wl->chip.fw_ver[0], &wl->chip.fw_ver[1], + &wl->chip.fw_ver[2], &wl->chip.fw_ver[3], + &wl->chip.fw_ver[4]); + + if (ret != 5) { + wl1271_warning("fw version incorrect value"); + memset(wl->chip.fw_ver, 0, sizeof(wl->chip.fw_ver)); + ret = -EINVAL; + goto out; + } + + ret = wlcore_identify_fw(wl); + if (ret < 0) + goto out; +out: + return ret; +} + +static int wlcore_validate_fw_ver(struct wl1271 *wl) +{ + unsigned int *fw_ver = wl->chip.fw_ver; + unsigned int *min_ver = (wl->fw_type == WL12XX_FW_TYPE_MULTI) ? + wl->min_mr_fw_ver : wl->min_sr_fw_ver; + char min_fw_str[32] = ""; + int i; + + /* the chip must be exactly equal */ + if ((min_ver[FW_VER_CHIP] != WLCORE_FW_VER_IGNORE) && + (min_ver[FW_VER_CHIP] != fw_ver[FW_VER_CHIP])) + goto fail; + + /* the firmware type must be equal */ + if ((min_ver[FW_VER_IF_TYPE] != WLCORE_FW_VER_IGNORE) && + (min_ver[FW_VER_IF_TYPE] != fw_ver[FW_VER_IF_TYPE])) + goto fail; + + /* the project number must be equal */ + if ((min_ver[FW_VER_SUBTYPE] != WLCORE_FW_VER_IGNORE) && + (min_ver[FW_VER_SUBTYPE] != fw_ver[FW_VER_SUBTYPE])) + goto fail; + + /* the API version must be greater or equal */ + if ((min_ver[FW_VER_MAJOR] != WLCORE_FW_VER_IGNORE) && + (min_ver[FW_VER_MAJOR] > fw_ver[FW_VER_MAJOR])) + goto fail; + + /* if the API version is equal... */ + if (((min_ver[FW_VER_MAJOR] == WLCORE_FW_VER_IGNORE) || + (min_ver[FW_VER_MAJOR] == fw_ver[FW_VER_MAJOR])) && + /* ...the minor must be greater or equal */ + ((min_ver[FW_VER_MINOR] != WLCORE_FW_VER_IGNORE) && + (min_ver[FW_VER_MINOR] > fw_ver[FW_VER_MINOR]))) + goto fail; + + return 0; + +fail: + for (i = 0; i < NUM_FW_VER; i++) + if (min_ver[i] == WLCORE_FW_VER_IGNORE) + snprintf(min_fw_str, sizeof(min_fw_str), + "%s*.", min_fw_str); + else + snprintf(min_fw_str, sizeof(min_fw_str), + "%s%u.", min_fw_str, min_ver[i]); + + wl1271_error("Your WiFi FW version (%u.%u.%u.%u.%u) is invalid.\n" + "Please use at least FW %s\n" + "You can get the latest firmwares at:\n" + "git://github.com/TI-OpenLink/firmwares.git", + fw_ver[FW_VER_CHIP], fw_ver[FW_VER_IF_TYPE], + fw_ver[FW_VER_MAJOR], fw_ver[FW_VER_SUBTYPE], + fw_ver[FW_VER_MINOR], min_fw_str); + return -EINVAL; +} + +static int wlcore_boot_static_data(struct wl1271 *wl) +{ + struct wl1271_static_data *static_data; + size_t len = sizeof(*static_data) + wl->static_data_priv_len; + int ret; + + static_data = kmalloc(len, GFP_KERNEL); + if (!static_data) { + ret = -ENOMEM; + goto out; + } + + ret = wlcore_read(wl, wl->cmd_box_addr, static_data, len, false); + if (ret < 0) + goto out_free; + + ret = wlcore_boot_parse_fw_ver(wl, static_data); + if (ret < 0) + goto out_free; + + ret = wlcore_validate_fw_ver(wl); + if (ret < 0) + goto out_free; + + ret = wlcore_handle_static_data(wl, static_data); + if (ret < 0) + goto out_free; + +out_free: + kfree(static_data); +out: + return ret; +} + +static int wl1271_boot_upload_firmware_chunk(struct wl1271 *wl, void *buf, + size_t fw_data_len, u32 dest) +{ + struct wlcore_partition_set partition; + int addr, chunk_num, partition_limit; + u8 *p, *chunk; + int ret; + + /* whal_FwCtrl_LoadFwImageSm() */ + + wl1271_debug(DEBUG_BOOT, "starting firmware upload"); + + wl1271_debug(DEBUG_BOOT, "fw_data_len %zd chunk_size %d", + fw_data_len, CHUNK_SIZE); + + if ((fw_data_len % 4) != 0) { + wl1271_error("firmware length not multiple of four"); + return -EIO; + } + + chunk = kmalloc(CHUNK_SIZE, GFP_KERNEL); + if (!chunk) { + wl1271_error("allocation for firmware upload chunk failed"); + return -ENOMEM; + } + + memcpy(&partition, &wl->ptable[PART_DOWN], sizeof(partition)); + partition.mem.start = dest; + ret = wlcore_set_partition(wl, &partition); + if (ret < 0) + goto out; + + /* 10.1 set partition limit and chunk num */ + chunk_num = 0; + partition_limit = wl->ptable[PART_DOWN].mem.size; + + while (chunk_num < fw_data_len / CHUNK_SIZE) { + /* 10.2 update partition, if needed */ + addr = dest + (chunk_num + 2) * CHUNK_SIZE; + if (addr > partition_limit) { + addr = dest + chunk_num * CHUNK_SIZE; + partition_limit = chunk_num * CHUNK_SIZE + + wl->ptable[PART_DOWN].mem.size; + partition.mem.start = addr; + ret = wlcore_set_partition(wl, &partition); + if (ret < 0) + goto out; + } + + /* 10.3 upload the chunk */ + addr = dest + chunk_num * CHUNK_SIZE; + p = buf + chunk_num * CHUNK_SIZE; + memcpy(chunk, p, CHUNK_SIZE); + wl1271_debug(DEBUG_BOOT, "uploading fw chunk 0x%p to 0x%x", + p, addr); + ret = wlcore_write(wl, addr, chunk, CHUNK_SIZE, false); + if (ret < 0) + goto out; + + chunk_num++; + } + + /* 10.4 upload the last chunk */ + addr = dest + chunk_num * CHUNK_SIZE; + p = buf + chunk_num * CHUNK_SIZE; + memcpy(chunk, p, fw_data_len % CHUNK_SIZE); + wl1271_debug(DEBUG_BOOT, "uploading fw last chunk (%zd B) 0x%p to 0x%x", + fw_data_len % CHUNK_SIZE, p, addr); + ret = wlcore_write(wl, addr, chunk, fw_data_len % CHUNK_SIZE, false); + +out: + kfree(chunk); + return ret; +} + +int wlcore_boot_upload_firmware(struct wl1271 *wl) +{ + u32 chunks, addr, len; + int ret = 0; + u8 *fw; + + fw = wl->fw; + chunks = be32_to_cpup((__be32 *) fw); + fw += sizeof(u32); + + wl1271_debug(DEBUG_BOOT, "firmware chunks to be uploaded: %u", chunks); + + while (chunks--) { + addr = be32_to_cpup((__be32 *) fw); + fw += sizeof(u32); + len = be32_to_cpup((__be32 *) fw); + fw += sizeof(u32); + + if (len > 300000) { + wl1271_info("firmware chunk too long: %u", len); + return -EINVAL; + } + wl1271_debug(DEBUG_BOOT, "chunk %d addr 0x%x len %u", + chunks, addr, len); + ret = wl1271_boot_upload_firmware_chunk(wl, fw, len, addr); + if (ret != 0) + break; + fw += len; + } + + return ret; +} +EXPORT_SYMBOL_GPL(wlcore_boot_upload_firmware); + +int wlcore_boot_upload_nvs(struct wl1271 *wl) +{ + size_t nvs_len, burst_len; + int i; + u32 dest_addr, val; + u8 *nvs_ptr, *nvs_aligned; + int ret; + + if (wl->nvs == NULL) { + wl1271_error("NVS file is needed during boot"); + return -ENODEV; + } + + if (wl->quirks & WLCORE_QUIRK_LEGACY_NVS) { + struct wl1271_nvs_file *nvs = + (struct wl1271_nvs_file *)wl->nvs; + /* + * FIXME: the LEGACY NVS image support (NVS's missing the 5GHz + * band configurations) can be removed when those NVS files stop + * floating around. + */ + if (wl->nvs_len == sizeof(struct wl1271_nvs_file) || + wl->nvs_len == WL1271_INI_LEGACY_NVS_FILE_SIZE) { + if (nvs->general_params.dual_mode_select) + wl->enable_11a = true; + } + + if (wl->nvs_len != sizeof(struct wl1271_nvs_file) && + (wl->nvs_len != WL1271_INI_LEGACY_NVS_FILE_SIZE || + wl->enable_11a)) { + wl1271_error("nvs size is not as expected: %zu != %zu", + wl->nvs_len, sizeof(struct wl1271_nvs_file)); + kfree(wl->nvs); + wl->nvs = NULL; + wl->nvs_len = 0; + return -EILSEQ; + } + + /* only the first part of the NVS needs to be uploaded */ + nvs_len = sizeof(nvs->nvs); + nvs_ptr = (u8 *) nvs->nvs; + } else { + struct wl128x_nvs_file *nvs = (struct wl128x_nvs_file *)wl->nvs; + + if (wl->nvs_len == sizeof(struct wl128x_nvs_file)) { + if (nvs->general_params.dual_mode_select) + wl->enable_11a = true; + } else { + wl1271_error("nvs size is not as expected: %zu != %zu", + wl->nvs_len, + sizeof(struct wl128x_nvs_file)); + kfree(wl->nvs); + wl->nvs = NULL; + wl->nvs_len = 0; + return -EILSEQ; + } + + /* only the first part of the NVS needs to be uploaded */ + nvs_len = sizeof(nvs->nvs); + nvs_ptr = (u8 *)nvs->nvs; + } + + /* update current MAC address to NVS */ + nvs_ptr[11] = wl->addresses[0].addr[0]; + nvs_ptr[10] = wl->addresses[0].addr[1]; + nvs_ptr[6] = wl->addresses[0].addr[2]; + nvs_ptr[5] = wl->addresses[0].addr[3]; + nvs_ptr[4] = wl->addresses[0].addr[4]; + nvs_ptr[3] = wl->addresses[0].addr[5]; + + /* + * Layout before the actual NVS tables: + * 1 byte : burst length. + * 2 bytes: destination address. + * n bytes: data to burst copy. + * + * This is ended by a 0 length, then the NVS tables. + */ + + /* FIXME: Do we need to check here whether the LSB is 1? */ + while (nvs_ptr[0]) { + burst_len = nvs_ptr[0]; + dest_addr = (nvs_ptr[1] & 0xfe) | ((u32)(nvs_ptr[2] << 8)); + + /* + * Due to our new wl1271_translate_reg_addr function, + * we need to add the register partition start address + * to the destination + */ + dest_addr += wl->curr_part.reg.start; + + /* We move our pointer to the data */ + nvs_ptr += 3; + + for (i = 0; i < burst_len; i++) { + if (nvs_ptr + 3 >= (u8 *) wl->nvs + nvs_len) + goto out_badnvs; + + val = (nvs_ptr[0] | (nvs_ptr[1] << 8) + | (nvs_ptr[2] << 16) | (nvs_ptr[3] << 24)); + + wl1271_debug(DEBUG_BOOT, + "nvs burst write 0x%x: 0x%x", + dest_addr, val); + ret = wlcore_write32(wl, dest_addr, val); + if (ret < 0) + return ret; + + nvs_ptr += 4; + dest_addr += 4; + } + + if (nvs_ptr >= (u8 *) wl->nvs + nvs_len) + goto out_badnvs; + } + + /* + * We've reached the first zero length, the first NVS table + * is located at an aligned offset which is at least 7 bytes further. + * NOTE: The wl->nvs->nvs element must be first, in order to + * simplify the casting, we assume it is at the beginning of + * the wl->nvs structure. + */ + nvs_ptr = (u8 *)wl->nvs + + ALIGN(nvs_ptr - (u8 *)wl->nvs + 7, 4); + + if (nvs_ptr >= (u8 *) wl->nvs + nvs_len) + goto out_badnvs; + + nvs_len -= nvs_ptr - (u8 *)wl->nvs; + + /* Now we must set the partition correctly */ + ret = wlcore_set_partition(wl, &wl->ptable[PART_WORK]); + if (ret < 0) + return ret; + + /* Copy the NVS tables to a new block to ensure alignment */ + nvs_aligned = kmemdup(nvs_ptr, nvs_len, GFP_KERNEL); + if (!nvs_aligned) + return -ENOMEM; + + /* And finally we upload the NVS tables */ + ret = wlcore_write_data(wl, REG_CMD_MBOX_ADDRESS, nvs_aligned, nvs_len, + false); + + kfree(nvs_aligned); + return ret; + +out_badnvs: + wl1271_error("nvs data is malformed"); + return -EILSEQ; +} +EXPORT_SYMBOL_GPL(wlcore_boot_upload_nvs); + +int wlcore_boot_run_firmware(struct wl1271 *wl) +{ + int loop, ret; + u32 chip_id, intr; + + /* Make sure we have the boot partition */ + ret = wlcore_set_partition(wl, &wl->ptable[PART_BOOT]); + if (ret < 0) + return ret; + + ret = wl1271_boot_set_ecpu_ctrl(wl, ECPU_CONTROL_HALT); + if (ret < 0) + return ret; + + ret = wlcore_read_reg(wl, REG_CHIP_ID_B, &chip_id); + if (ret < 0) + return ret; + + wl1271_debug(DEBUG_BOOT, "chip id after firmware boot: 0x%x", chip_id); + + if (chip_id != wl->chip.id) { + wl1271_error("chip id doesn't match after firmware boot"); + return -EIO; + } + + /* wait for init to complete */ + loop = 0; + while (loop++ < INIT_LOOP) { + udelay(INIT_LOOP_DELAY); + ret = wlcore_read_reg(wl, REG_INTERRUPT_NO_CLEAR, &intr); + if (ret < 0) + return ret; + + if (intr == 0xffffffff) { + wl1271_error("error reading hardware complete " + "init indication"); + return -EIO; + } + /* check that ACX_INTR_INIT_COMPLETE is enabled */ + else if (intr & WL1271_ACX_INTR_INIT_COMPLETE) { + ret = wlcore_write_reg(wl, REG_INTERRUPT_ACK, + WL1271_ACX_INTR_INIT_COMPLETE); + if (ret < 0) + return ret; + break; + } + } + + if (loop > INIT_LOOP) { + wl1271_error("timeout waiting for the hardware to " + "complete initialization"); + return -EIO; + } + + /* get hardware config command mail box */ + ret = wlcore_read_reg(wl, REG_COMMAND_MAILBOX_PTR, &wl->cmd_box_addr); + if (ret < 0) + return ret; + + wl1271_debug(DEBUG_MAILBOX, "cmd_box_addr 0x%x", wl->cmd_box_addr); + + /* get hardware config event mail box */ + ret = wlcore_read_reg(wl, REG_EVENT_MAILBOX_PTR, &wl->mbox_ptr[0]); + if (ret < 0) + return ret; + + wl->mbox_ptr[1] = wl->mbox_ptr[0] + wl->mbox_size; + + wl1271_debug(DEBUG_MAILBOX, "MBOX ptrs: 0x%x 0x%x", + wl->mbox_ptr[0], wl->mbox_ptr[1]); + + ret = wlcore_boot_static_data(wl); + if (ret < 0) { + wl1271_error("error getting static data"); + return ret; + } + + /* + * in case of full asynchronous mode the firmware event must be + * ready to receive event from the command mailbox + */ + + /* unmask required mbox events */ + ret = wl1271_event_unmask(wl); + if (ret < 0) { + wl1271_error("EVENT mask setting failed"); + return ret; + } + + /* set the working partition to its "running" mode offset */ + ret = wlcore_set_partition(wl, &wl->ptable[PART_WORK]); + + /* firmware startup completed */ + return ret; +} +EXPORT_SYMBOL_GPL(wlcore_boot_run_firmware); diff --git a/drivers/net/wireless/ti/wlcore/boot.h b/drivers/net/wireless/ti/wlcore/boot.h new file mode 100644 index 0000000..a525225 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/boot.h @@ -0,0 +1,55 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __BOOT_H__ +#define __BOOT_H__ + +#include "wlcore.h" + +int wlcore_boot_upload_firmware(struct wl1271 *wl); +int wlcore_boot_upload_nvs(struct wl1271 *wl); +int wlcore_boot_run_firmware(struct wl1271 *wl); + +#define WL1271_NO_SUBBANDS 8 +#define WL1271_NO_POWER_LEVELS 4 +#define WL1271_FW_VERSION_MAX_LEN 20 + +struct wl1271_static_data { + u8 mac_address[ETH_ALEN]; + u8 padding[2]; + u8 fw_version[WL1271_FW_VERSION_MAX_LEN]; + u32 hw_version; + u8 tx_power_table[WL1271_NO_SUBBANDS][WL1271_NO_POWER_LEVELS]; + u8 priv[0]; +}; + +/* number of times we try to read the INIT interrupt */ +#define INIT_LOOP 20000 + +/* delay between retries */ +#define INIT_LOOP_DELAY 50 + +#define WU_COUNTER_PAUSE_VAL 0x3FF +#define WELP_ARM_COMMAND_VAL 0x4 + +#endif diff --git a/drivers/net/wireless/ti/wlcore/cmd.c b/drivers/net/wireless/ti/wlcore/cmd.c new file mode 100644 index 0000000..c9e0607 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/cmd.c @@ -0,0 +1,2025 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009-2010 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include "wlcore.h" +#include "debug.h" +#include "io.h" +#include "acx.h" +#include "wl12xx_80211.h" +#include "cmd.h" +#include "event.h" +#include "tx.h" +#include "hw_ops.h" + +#define WL1271_CMD_FAST_POLL_COUNT 50 +#define WL1271_WAIT_EVENT_FAST_POLL_COUNT 20 + +/* + * send command to firmware + * + * @wl: wl struct + * @id: command id + * @buf: buffer containing the command, must work with dma + * @len: length of the buffer + * return the cmd status code on success. + */ +static int __wlcore_cmd_send(struct wl1271 *wl, u16 id, void *buf, + size_t len, size_t res_len) +{ + struct wl1271_cmd_header *cmd; + unsigned long timeout; + u32 intr; + int ret; + u16 status; + u16 poll_count = 0; + + if (WARN_ON(unlikely(wl->state == WLCORE_STATE_RESTARTING))) + return -EIO; + + cmd = buf; + cmd->id = cpu_to_le16(id); + cmd->status = 0; + + WARN_ON(len % 4 != 0); + WARN_ON(test_bit(WL1271_FLAG_IN_ELP, &wl->flags)); + + ret = wlcore_write(wl, wl->cmd_box_addr, buf, len, false); + if (ret < 0) + return ret; + + /* + * TODO: we just need this because one bit is in a different + * place. Is there any better way? + */ + ret = wl->ops->trigger_cmd(wl, wl->cmd_box_addr, buf, len); + if (ret < 0) + return ret; + + timeout = jiffies + msecs_to_jiffies(WL1271_COMMAND_TIMEOUT); + + ret = wlcore_read_reg(wl, REG_INTERRUPT_NO_CLEAR, &intr); + if (ret < 0) + return ret; + + while (!(intr & WL1271_ACX_INTR_CMD_COMPLETE)) { + if (time_after(jiffies, timeout)) { + wl1271_error("command complete timeout"); + return -ETIMEDOUT; + } + + poll_count++; + if (poll_count < WL1271_CMD_FAST_POLL_COUNT) + udelay(10); + else + msleep(1); + + ret = wlcore_read_reg(wl, REG_INTERRUPT_NO_CLEAR, &intr); + if (ret < 0) + return ret; + } + + /* read back the status code of the command */ + if (res_len == 0) + res_len = sizeof(struct wl1271_cmd_header); + + ret = wlcore_read(wl, wl->cmd_box_addr, cmd, res_len, false); + if (ret < 0) + return ret; + + status = le16_to_cpu(cmd->status); + + ret = wlcore_write_reg(wl, REG_INTERRUPT_ACK, + WL1271_ACX_INTR_CMD_COMPLETE); + if (ret < 0) + return ret; + + return status; +} + +/* + * send command to fw and return cmd status on success + * valid_rets contains a bitmap of allowed error codes + */ +int wlcore_cmd_send_failsafe(struct wl1271 *wl, u16 id, void *buf, size_t len, + size_t res_len, unsigned long valid_rets) +{ + int ret = __wlcore_cmd_send(wl, id, buf, len, res_len); + + if (ret < 0) + goto fail; + + /* success is always a valid status */ + valid_rets |= BIT(CMD_STATUS_SUCCESS); + + if (ret >= MAX_COMMAND_STATUS || + !test_bit(ret, &valid_rets)) { + wl1271_error("command execute failure %d", ret); + ret = -EIO; + goto fail; + } + return ret; +fail: + wl12xx_queue_recovery_work(wl); + return ret; +} +EXPORT_SYMBOL_GPL(wl1271_cmd_send); + +/* + * wrapper for wlcore_cmd_send that accept only CMD_STATUS_SUCCESS + * return 0 on success. + */ +int wl1271_cmd_send(struct wl1271 *wl, u16 id, void *buf, size_t len, + size_t res_len) +{ + int ret = wlcore_cmd_send_failsafe(wl, id, buf, len, res_len, 0); + + if (ret < 0) + return ret; + return 0; +} + +/* + * Poll the mailbox event field until any of the bits in the mask is set or a + * timeout occurs (WL1271_EVENT_TIMEOUT in msecs) + */ +int wlcore_cmd_wait_for_event_or_timeout(struct wl1271 *wl, + u32 mask, bool *timeout) +{ + u32 *events_vector; + u32 event; + unsigned long timeout_time; + u16 poll_count = 0; + int ret = 0; + + *timeout = false; + + events_vector = kmalloc(sizeof(*events_vector), GFP_KERNEL | GFP_DMA); + if (!events_vector) + return -ENOMEM; + + timeout_time = jiffies + msecs_to_jiffies(WL1271_EVENT_TIMEOUT); + + do { + if (time_after(jiffies, timeout_time)) { + wl1271_debug(DEBUG_CMD, "timeout waiting for event %d", + (int)mask); + *timeout = true; + goto out; + } + + poll_count++; + if (poll_count < WL1271_WAIT_EVENT_FAST_POLL_COUNT) + usleep_range(50, 51); + else + usleep_range(1000, 5000); + + /* read from both event fields */ + ret = wlcore_read(wl, wl->mbox_ptr[0], events_vector, + sizeof(*events_vector), false); + if (ret < 0) + goto out; + + event = *events_vector & mask; + + ret = wlcore_read(wl, wl->mbox_ptr[1], events_vector, + sizeof(*events_vector), false); + if (ret < 0) + goto out; + + event |= *events_vector & mask; + } while (!event); + +out: + kfree(events_vector); + return ret; +} +EXPORT_SYMBOL_GPL(wlcore_cmd_wait_for_event_or_timeout); + +int wl12xx_cmd_role_enable(struct wl1271 *wl, u8 *addr, u8 role_type, + u8 *role_id) +{ + struct wl12xx_cmd_role_enable *cmd; + int ret; + + wl1271_debug(DEBUG_CMD, "cmd role enable"); + + if (WARN_ON(*role_id != WL12XX_INVALID_ROLE_ID)) + return -EBUSY; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + /* get role id */ + cmd->role_id = find_first_zero_bit(wl->roles_map, WL12XX_MAX_ROLES); + if (cmd->role_id >= WL12XX_MAX_ROLES) { + ret = -EBUSY; + goto out_free; + } + + memcpy(cmd->mac_address, addr, ETH_ALEN); + cmd->role_type = role_type; + + ret = wl1271_cmd_send(wl, CMD_ROLE_ENABLE, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to initiate cmd role enable"); + goto out_free; + } + + __set_bit(cmd->role_id, wl->roles_map); + *role_id = cmd->role_id; + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl12xx_cmd_role_disable(struct wl1271 *wl, u8 *role_id) +{ + struct wl12xx_cmd_role_disable *cmd; + int ret; + + wl1271_debug(DEBUG_CMD, "cmd role disable"); + + if (WARN_ON(*role_id == WL12XX_INVALID_ROLE_ID)) + return -ENOENT; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + cmd->role_id = *role_id; + + ret = wl1271_cmd_send(wl, CMD_ROLE_DISABLE, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to initiate cmd role disable"); + goto out_free; + } + + __clear_bit(*role_id, wl->roles_map); + *role_id = WL12XX_INVALID_ROLE_ID; + +out_free: + kfree(cmd); + +out: + return ret; +} + +static int wlcore_get_new_session_id(struct wl1271 *wl, u8 hlid) +{ + if (wl->session_ids[hlid] >= SESSION_COUNTER_MAX) + wl->session_ids[hlid] = 0; + + wl->session_ids[hlid]++; + + return wl->session_ids[hlid]; +} + +int wl12xx_allocate_link(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 *hlid) +{ + unsigned long flags; + u8 link = find_first_zero_bit(wl->links_map, WL12XX_MAX_LINKS); + if (link >= WL12XX_MAX_LINKS) + return -EBUSY; + + wl->session_ids[link] = wlcore_get_new_session_id(wl, link); + + /* these bits are used by op_tx */ + spin_lock_irqsave(&wl->wl_lock, flags); + __set_bit(link, wl->links_map); + __set_bit(link, wlvif->links_map); + spin_unlock_irqrestore(&wl->wl_lock, flags); + + /* take the last "freed packets" value from the current FW status */ + wl->links[link].prev_freed_pkts = + wl->fw_status_2->counters.tx_lnk_free_pkts[link]; + wl->links[link].wlvif = wlvif; + + /* + * Take saved value for total freed packets from wlvif, in case this is + * recovery/resume + */ + if (wlvif->bss_type != BSS_TYPE_AP_BSS) + wl->links[link].total_freed_pkts = wlvif->total_freed_pkts; + + *hlid = link; + + wl->active_link_count++; + return 0; +} + +void wl12xx_free_link(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 *hlid) +{ + unsigned long flags; + + if (*hlid == WL12XX_INVALID_LINK_ID) + return; + + /* these bits are used by op_tx */ + spin_lock_irqsave(&wl->wl_lock, flags); + __clear_bit(*hlid, wl->links_map); + __clear_bit(*hlid, wlvif->links_map); + spin_unlock_irqrestore(&wl->wl_lock, flags); + + wl->links[*hlid].allocated_pkts = 0; + wl->links[*hlid].prev_freed_pkts = 0; + wl->links[*hlid].ba_bitmap = 0; + memset(wl->links[*hlid].addr, 0, ETH_ALEN); + + /* + * At this point op_tx() will not add more packets to the queues. We + * can purge them. + */ + wl1271_tx_reset_link_queues(wl, *hlid); + wl->links[*hlid].wlvif = NULL; + + if (wlvif->bss_type == BSS_TYPE_STA_BSS || + (wlvif->bss_type == BSS_TYPE_AP_BSS && + *hlid == wlvif->ap.bcast_hlid)) { + /* + * save the total freed packets in the wlvif, in case this is + * recovery or suspend + */ + wlvif->total_freed_pkts = wl->links[*hlid].total_freed_pkts; + + /* + * increment the initial seq number on recovery to account for + * transmitted packets that we haven't yet got in the FW status + */ + if (test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags)) + wlvif->total_freed_pkts += + WL1271_TX_SQN_POST_RECOVERY_PADDING; + } + + wl->links[*hlid].total_freed_pkts = 0; + + *hlid = WL12XX_INVALID_LINK_ID; + wl->active_link_count--; + WARN_ON_ONCE(wl->active_link_count < 0); +} + +static u8 wlcore_get_native_channel_type(u8 nl_channel_type) +{ + switch (nl_channel_type) { + case NL80211_CHAN_NO_HT: + return WLCORE_CHAN_NO_HT; + case NL80211_CHAN_HT20: + return WLCORE_CHAN_HT20; + case NL80211_CHAN_HT40MINUS: + return WLCORE_CHAN_HT40MINUS; + case NL80211_CHAN_HT40PLUS: + return WLCORE_CHAN_HT40PLUS; + default: + WARN_ON(1); + return WLCORE_CHAN_NO_HT; + } +} + +static int wl12xx_cmd_role_start_dev(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + enum ieee80211_band band, + int channel) +{ + struct wl12xx_cmd_role_start *cmd; + int ret; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + wl1271_debug(DEBUG_CMD, "cmd role start dev %d", wlvif->dev_role_id); + + cmd->role_id = wlvif->dev_role_id; + if (band == IEEE80211_BAND_5GHZ) + cmd->band = WLCORE_BAND_5GHZ; + cmd->channel = channel; + + if (wlvif->dev_hlid == WL12XX_INVALID_LINK_ID) { + ret = wl12xx_allocate_link(wl, wlvif, &wlvif->dev_hlid); + if (ret) + goto out_free; + } + cmd->device.hlid = wlvif->dev_hlid; + cmd->device.session = wl->session_ids[wlvif->dev_hlid]; + + wl1271_debug(DEBUG_CMD, "role start: roleid=%d, hlid=%d, session=%d", + cmd->role_id, cmd->device.hlid, cmd->device.session); + + ret = wl1271_cmd_send(wl, CMD_ROLE_START, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to initiate cmd role enable"); + goto err_hlid; + } + + goto out_free; + +err_hlid: + /* clear links on error */ + wl12xx_free_link(wl, wlvif, &wlvif->dev_hlid); + +out_free: + kfree(cmd); + +out: + return ret; +} + +static int wl12xx_cmd_role_stop_dev(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + struct wl12xx_cmd_role_stop *cmd; + int ret; + + if (WARN_ON(wlvif->dev_hlid == WL12XX_INVALID_LINK_ID)) + return -EINVAL; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + wl1271_debug(DEBUG_CMD, "cmd role stop dev"); + + cmd->role_id = wlvif->dev_role_id; + cmd->disc_type = DISCONNECT_IMMEDIATE; + cmd->reason = cpu_to_le16(WLAN_REASON_UNSPECIFIED); + + ret = wl1271_cmd_send(wl, CMD_ROLE_STOP, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to initiate cmd role stop"); + goto out_free; + } + + wl12xx_free_link(wl, wlvif, &wlvif->dev_hlid); + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl12xx_cmd_role_start_sta(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + struct wl12xx_cmd_role_start *cmd; + u32 supported_rates; + int ret; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + wl1271_debug(DEBUG_CMD, "cmd role start sta %d", wlvif->role_id); + + cmd->role_id = wlvif->role_id; + if (wlvif->band == IEEE80211_BAND_5GHZ) + cmd->band = WLCORE_BAND_5GHZ; + cmd->channel = wlvif->channel; + cmd->sta.basic_rate_set = cpu_to_le32(wlvif->basic_rate_set); + cmd->sta.beacon_interval = cpu_to_le16(wlvif->beacon_int); + cmd->sta.ssid_type = WL12XX_SSID_TYPE_ANY; + cmd->sta.ssid_len = wlvif->ssid_len; + memcpy(cmd->sta.ssid, wlvif->ssid, wlvif->ssid_len); + memcpy(cmd->sta.bssid, vif->bss_conf.bssid, ETH_ALEN); + + supported_rates = CONF_TX_ENABLED_RATES | CONF_TX_MCS_RATES | + wlcore_hw_sta_get_ap_rate_mask(wl, wlvif); + if (wlvif->p2p) + supported_rates &= ~CONF_TX_CCK_RATES; + + cmd->sta.local_rates = cpu_to_le32(supported_rates); + + cmd->channel_type = wlcore_get_native_channel_type(wlvif->channel_type); + + if (wlvif->sta.hlid == WL12XX_INVALID_LINK_ID) { + ret = wl12xx_allocate_link(wl, wlvif, &wlvif->sta.hlid); + if (ret) + goto out_free; + } + cmd->sta.hlid = wlvif->sta.hlid; + cmd->sta.session = wl->session_ids[wlvif->sta.hlid]; + /* + * We don't have the correct remote rates in this stage. The + * rates will be reconfigured later, after association, if the + * firmware supports ACX_PEER_CAP. Otherwise, there's nothing + * we can do, so use all supported_rates here. + */ + cmd->sta.remote_rates = cpu_to_le32(supported_rates); + + wl1271_debug(DEBUG_CMD, "role start: roleid=%d, hlid=%d, session=%d " + "basic_rate_set: 0x%x, remote_rates: 0x%x", + wlvif->role_id, cmd->sta.hlid, cmd->sta.session, + wlvif->basic_rate_set, wlvif->rate_set); + + ret = wl1271_cmd_send(wl, CMD_ROLE_START, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to initiate cmd role start sta"); + goto err_hlid; + } + + wlvif->sta.role_chan_type = wlvif->channel_type; + goto out_free; + +err_hlid: + /* clear links on error. */ + wl12xx_free_link(wl, wlvif, &wlvif->sta.hlid); + +out_free: + kfree(cmd); + +out: + return ret; +} + +/* use this function to stop ibss as well */ +int wl12xx_cmd_role_stop_sta(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + struct wl12xx_cmd_role_stop *cmd; + int ret; + + if (WARN_ON(wlvif->sta.hlid == WL12XX_INVALID_LINK_ID)) + return -EINVAL; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + wl1271_debug(DEBUG_CMD, "cmd role stop sta %d", wlvif->role_id); + + cmd->role_id = wlvif->role_id; + cmd->disc_type = DISCONNECT_IMMEDIATE; + cmd->reason = cpu_to_le16(WLAN_REASON_UNSPECIFIED); + + ret = wl1271_cmd_send(wl, CMD_ROLE_STOP, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to initiate cmd role stop sta"); + goto out_free; + } + + wl12xx_free_link(wl, wlvif, &wlvif->sta.hlid); + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl12xx_cmd_role_start_ap(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + struct wl12xx_cmd_role_start *cmd; + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + struct ieee80211_bss_conf *bss_conf = &vif->bss_conf; + u32 supported_rates; + int ret; + + wl1271_debug(DEBUG_CMD, "cmd role start ap %d", wlvif->role_id); + + /* trying to use hidden SSID with an old hostapd version */ + if (wlvif->ssid_len == 0 && !bss_conf->hidden_ssid) { + wl1271_error("got a null SSID from beacon/bss"); + ret = -EINVAL; + goto out; + } + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + ret = wl12xx_allocate_link(wl, wlvif, &wlvif->ap.global_hlid); + if (ret < 0) + goto out_free; + + ret = wl12xx_allocate_link(wl, wlvif, &wlvif->ap.bcast_hlid); + if (ret < 0) + goto out_free_global; + + /* use the previous security seq, if this is a recovery/resume */ + wl->links[wlvif->ap.bcast_hlid].total_freed_pkts = + wlvif->total_freed_pkts; + + cmd->role_id = wlvif->role_id; + cmd->ap.aging_period = cpu_to_le16(wl->conf.tx.ap_aging_period); + cmd->ap.bss_index = WL1271_AP_BSS_INDEX; + cmd->ap.global_hlid = wlvif->ap.global_hlid; + cmd->ap.broadcast_hlid = wlvif->ap.bcast_hlid; + cmd->ap.global_session_id = wl->session_ids[wlvif->ap.global_hlid]; + cmd->ap.bcast_session_id = wl->session_ids[wlvif->ap.bcast_hlid]; + cmd->ap.basic_rate_set = cpu_to_le32(wlvif->basic_rate_set); + cmd->ap.beacon_interval = cpu_to_le16(wlvif->beacon_int); + cmd->ap.dtim_interval = bss_conf->dtim_period; + cmd->ap.beacon_expiry = WL1271_AP_DEF_BEACON_EXP; + /* FIXME: Change when adding DFS */ + cmd->ap.reset_tsf = 1; /* By default reset AP TSF */ + cmd->ap.wmm = wlvif->wmm_enabled; + cmd->channel = wlvif->channel; + cmd->channel_type = wlcore_get_native_channel_type(wlvif->channel_type); + + if (!bss_conf->hidden_ssid) { + /* take the SSID from the beacon for backward compatibility */ + cmd->ap.ssid_type = WL12XX_SSID_TYPE_PUBLIC; + cmd->ap.ssid_len = wlvif->ssid_len; + memcpy(cmd->ap.ssid, wlvif->ssid, wlvif->ssid_len); + } else { + cmd->ap.ssid_type = WL12XX_SSID_TYPE_HIDDEN; + cmd->ap.ssid_len = bss_conf->ssid_len; + memcpy(cmd->ap.ssid, bss_conf->ssid, bss_conf->ssid_len); + } + + supported_rates = CONF_TX_ENABLED_RATES | CONF_TX_MCS_RATES | + wlcore_hw_ap_get_mimo_wide_rate_mask(wl, wlvif); + if (wlvif->p2p) + supported_rates &= ~CONF_TX_CCK_RATES; + + wl1271_debug(DEBUG_CMD, "cmd role start ap with supported_rates 0x%08x", + supported_rates); + + cmd->ap.local_rates = cpu_to_le32(supported_rates); + + switch (wlvif->band) { + case IEEE80211_BAND_2GHZ: + cmd->band = WLCORE_BAND_2_4GHZ; + break; + case IEEE80211_BAND_5GHZ: + cmd->band = WLCORE_BAND_5GHZ; + break; + default: + wl1271_warning("ap start - unknown band: %d", (int)wlvif->band); + cmd->band = WLCORE_BAND_2_4GHZ; + break; + } + + ret = wl1271_cmd_send(wl, CMD_ROLE_START, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to initiate cmd role start ap"); + goto out_free_bcast; + } + + goto out_free; + +out_free_bcast: + wl12xx_free_link(wl, wlvif, &wlvif->ap.bcast_hlid); + +out_free_global: + wl12xx_free_link(wl, wlvif, &wlvif->ap.global_hlid); + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl12xx_cmd_role_stop_ap(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + struct wl12xx_cmd_role_stop *cmd; + int ret; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + wl1271_debug(DEBUG_CMD, "cmd role stop ap %d", wlvif->role_id); + + cmd->role_id = wlvif->role_id; + + ret = wl1271_cmd_send(wl, CMD_ROLE_STOP, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to initiate cmd role stop ap"); + goto out_free; + } + + wl12xx_free_link(wl, wlvif, &wlvif->ap.bcast_hlid); + wl12xx_free_link(wl, wlvif, &wlvif->ap.global_hlid); + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl12xx_cmd_role_start_ibss(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + struct wl12xx_cmd_role_start *cmd; + struct ieee80211_bss_conf *bss_conf = &vif->bss_conf; + int ret; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + wl1271_debug(DEBUG_CMD, "cmd role start ibss %d", wlvif->role_id); + + cmd->role_id = wlvif->role_id; + if (wlvif->band == IEEE80211_BAND_5GHZ) + cmd->band = WLCORE_BAND_5GHZ; + cmd->channel = wlvif->channel; + cmd->ibss.basic_rate_set = cpu_to_le32(wlvif->basic_rate_set); + cmd->ibss.beacon_interval = cpu_to_le16(wlvif->beacon_int); + cmd->ibss.dtim_interval = bss_conf->dtim_period; + cmd->ibss.ssid_type = WL12XX_SSID_TYPE_ANY; + cmd->ibss.ssid_len = wlvif->ssid_len; + memcpy(cmd->ibss.ssid, wlvif->ssid, wlvif->ssid_len); + memcpy(cmd->ibss.bssid, vif->bss_conf.bssid, ETH_ALEN); + cmd->sta.local_rates = cpu_to_le32(wlvif->rate_set); + + if (wlvif->sta.hlid == WL12XX_INVALID_LINK_ID) { + ret = wl12xx_allocate_link(wl, wlvif, &wlvif->sta.hlid); + if (ret) + goto out_free; + } + cmd->ibss.hlid = wlvif->sta.hlid; + cmd->ibss.remote_rates = cpu_to_le32(wlvif->rate_set); + + wl1271_debug(DEBUG_CMD, "role start: roleid=%d, hlid=%d, session=%d " + "basic_rate_set: 0x%x, remote_rates: 0x%x", + wlvif->role_id, cmd->sta.hlid, cmd->sta.session, + wlvif->basic_rate_set, wlvif->rate_set); + + wl1271_debug(DEBUG_CMD, "vif->bss_conf.bssid = %pM", + vif->bss_conf.bssid); + + ret = wl1271_cmd_send(wl, CMD_ROLE_START, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to initiate cmd role enable"); + goto err_hlid; + } + + goto out_free; + +err_hlid: + /* clear links on error. */ + wl12xx_free_link(wl, wlvif, &wlvif->sta.hlid); + +out_free: + kfree(cmd); + +out: + return ret; +} + + +/** + * send test command to firmware + * + * @wl: wl struct + * @buf: buffer containing the command, with all headers, must work with dma + * @len: length of the buffer + * @answer: is answer needed + */ +int wl1271_cmd_test(struct wl1271 *wl, void *buf, size_t buf_len, u8 answer) +{ + int ret; + size_t res_len = 0; + + wl1271_debug(DEBUG_CMD, "cmd test"); + + if (answer) + res_len = buf_len; + + ret = wl1271_cmd_send(wl, CMD_TEST, buf, buf_len, res_len); + + if (ret < 0) { + wl1271_warning("TEST command failed"); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(wl1271_cmd_test); + +/** + * read acx from firmware + * + * @wl: wl struct + * @id: acx id + * @buf: buffer for the response, including all headers, must work with dma + * @len: length of buf + */ +int wl1271_cmd_interrogate(struct wl1271 *wl, u16 id, void *buf, size_t len) +{ + struct acx_header *acx = buf; + int ret; + + wl1271_debug(DEBUG_CMD, "cmd interrogate"); + + acx->id = cpu_to_le16(id); + + /* payload length, does not include any headers */ + acx->len = cpu_to_le16(len - sizeof(*acx)); + + ret = wl1271_cmd_send(wl, CMD_INTERROGATE, acx, sizeof(*acx), len); + if (ret < 0) + wl1271_error("INTERROGATE command failed"); + + return ret; +} + +/** + * write acx value to firmware + * + * @wl: wl struct + * @id: acx id + * @buf: buffer containing acx, including all headers, must work with dma + * @len: length of buf + * @valid_rets: bitmap of valid cmd status codes (i.e. return values). + * return the cmd status on success. + */ +int wlcore_cmd_configure_failsafe(struct wl1271 *wl, u16 id, void *buf, + size_t len, unsigned long valid_rets) +{ + struct acx_header *acx = buf; + int ret; + + wl1271_debug(DEBUG_CMD, "cmd configure (%d)", id); + + acx->id = cpu_to_le16(id); + + /* payload length, does not include any headers */ + acx->len = cpu_to_le16(len - sizeof(*acx)); + + ret = wlcore_cmd_send_failsafe(wl, CMD_CONFIGURE, acx, len, 0, + valid_rets); + if (ret < 0) { + wl1271_warning("CONFIGURE command NOK"); + return ret; + } + + return ret; +} + +/* + * wrapper for wlcore_cmd_configure that accepts only success status. + * return 0 on success + */ +int wl1271_cmd_configure(struct wl1271 *wl, u16 id, void *buf, size_t len) +{ + int ret = wlcore_cmd_configure_failsafe(wl, id, buf, len, 0); + + if (ret < 0) + return ret; + return 0; +} +EXPORT_SYMBOL_GPL(wl1271_cmd_configure); + +int wl1271_cmd_data_path(struct wl1271 *wl, bool enable) +{ + struct cmd_enabledisable_path *cmd; + int ret; + u16 cmd_rx, cmd_tx; + + wl1271_debug(DEBUG_CMD, "cmd data path"); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + /* the channel here is only used for calibration, so hardcoded to 1 */ + cmd->channel = 1; + + if (enable) { + cmd_rx = CMD_ENABLE_RX; + cmd_tx = CMD_ENABLE_TX; + } else { + cmd_rx = CMD_DISABLE_RX; + cmd_tx = CMD_DISABLE_TX; + } + + ret = wl1271_cmd_send(wl, cmd_rx, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("rx %s cmd for channel %d failed", + enable ? "start" : "stop", cmd->channel); + goto out; + } + + wl1271_debug(DEBUG_BOOT, "rx %s cmd channel %d", + enable ? "start" : "stop", cmd->channel); + + ret = wl1271_cmd_send(wl, cmd_tx, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("tx %s cmd for channel %d failed", + enable ? "start" : "stop", cmd->channel); + goto out; + } + + wl1271_debug(DEBUG_BOOT, "tx %s cmd channel %d", + enable ? "start" : "stop", cmd->channel); + +out: + kfree(cmd); + return ret; +} +EXPORT_SYMBOL_GPL(wl1271_cmd_data_path); + +int wl1271_cmd_ps_mode(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 ps_mode, u16 auto_ps_timeout) +{ + struct wl1271_cmd_ps_params *ps_params = NULL; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd set ps mode"); + + ps_params = kzalloc(sizeof(*ps_params), GFP_KERNEL); + if (!ps_params) { + ret = -ENOMEM; + goto out; + } + + ps_params->role_id = wlvif->role_id; + ps_params->ps_mode = ps_mode; + ps_params->auto_ps_timeout = auto_ps_timeout; + + ret = wl1271_cmd_send(wl, CMD_SET_PS_MODE, ps_params, + sizeof(*ps_params), 0); + if (ret < 0) { + wl1271_error("cmd set_ps_mode failed"); + goto out; + } + +out: + kfree(ps_params); + return ret; +} + +int wl1271_cmd_template_set(struct wl1271 *wl, u8 role_id, + u16 template_id, void *buf, size_t buf_len, + int index, u32 rates) +{ + struct wl1271_cmd_template_set *cmd; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd template_set %d (role %d)", + template_id, role_id); + + WARN_ON(buf_len > WL1271_CMD_TEMPL_MAX_SIZE); + buf_len = min_t(size_t, buf_len, WL1271_CMD_TEMPL_MAX_SIZE); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + /* during initialization wlvif is NULL */ + cmd->role_id = role_id; + cmd->len = cpu_to_le16(buf_len); + cmd->template_type = template_id; + cmd->enabled_rates = cpu_to_le32(rates); + cmd->short_retry_limit = wl->conf.tx.tmpl_short_retry_limit; + cmd->long_retry_limit = wl->conf.tx.tmpl_long_retry_limit; + cmd->index = index; + + if (buf) + memcpy(cmd->template_data, buf, buf_len); + + ret = wl1271_cmd_send(wl, CMD_SET_TEMPLATE, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_warning("cmd set_template failed: %d", ret); + goto out_free; + } + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl12xx_cmd_build_null_data(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + struct sk_buff *skb = NULL; + int size; + void *ptr; + int ret = -ENOMEM; + + + if (wlvif->bss_type == BSS_TYPE_IBSS) { + size = sizeof(struct wl12xx_null_data_template); + ptr = NULL; + } else { + skb = ieee80211_nullfunc_get(wl->hw, + wl12xx_wlvif_to_vif(wlvif)); + if (!skb) + goto out; + size = skb->len; + ptr = skb->data; + } + + ret = wl1271_cmd_template_set(wl, wlvif->role_id, + CMD_TEMPL_NULL_DATA, ptr, size, 0, + wlvif->basic_rate); + +out: + dev_kfree_skb(skb); + if (ret) + wl1271_warning("cmd buld null data failed %d", ret); + + return ret; + +} + +int wl12xx_cmd_build_klv_null_data(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + struct sk_buff *skb = NULL; + int ret = -ENOMEM; + + skb = ieee80211_nullfunc_get(wl->hw, vif); + if (!skb) + goto out; + + ret = wl1271_cmd_template_set(wl, wlvif->role_id, CMD_TEMPL_KLV, + skb->data, skb->len, + wlvif->sta.klv_template_id, + wlvif->basic_rate); + +out: + dev_kfree_skb(skb); + if (ret) + wl1271_warning("cmd build klv null data failed %d", ret); + + return ret; + +} + +int wl1271_cmd_build_ps_poll(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u16 aid) +{ + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + struct sk_buff *skb; + int ret = 0; + + skb = ieee80211_pspoll_get(wl->hw, vif); + if (!skb) + goto out; + + ret = wl1271_cmd_template_set(wl, wlvif->role_id, + CMD_TEMPL_PS_POLL, skb->data, + skb->len, 0, wlvif->basic_rate_set); + +out: + dev_kfree_skb(skb); + return ret; +} + +int wl12xx_cmd_build_probe_req(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 role_id, u8 band, + const u8 *ssid, size_t ssid_len, + const u8 *ie, size_t ie_len, bool sched_scan) +{ + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + struct sk_buff *skb; + int ret; + u32 rate; + u16 template_id_2_4 = wl->scan_templ_id_2_4; + u16 template_id_5 = wl->scan_templ_id_5; + + skb = ieee80211_probereq_get(wl->hw, vif, ssid, ssid_len, + ie_len); + if (!skb) { + ret = -ENOMEM; + goto out; + } + if (ie_len) + memcpy(skb_put(skb, ie_len), ie, ie_len); + + wl1271_dump(DEBUG_SCAN, "PROBE REQ: ", skb->data, skb->len); + + if (sched_scan && + (wl->quirks & WLCORE_QUIRK_DUAL_PROBE_TMPL)) { + template_id_2_4 = wl->sched_scan_templ_id_2_4; + template_id_5 = wl->sched_scan_templ_id_5; + } + + rate = wl1271_tx_min_rate_get(wl, wlvif->bitrate_masks[band]); + if (band == IEEE80211_BAND_2GHZ) + ret = wl1271_cmd_template_set(wl, role_id, + template_id_2_4, + skb->data, skb->len, 0, rate); + else + ret = wl1271_cmd_template_set(wl, role_id, + template_id_5, + skb->data, skb->len, 0, rate); + +out: + dev_kfree_skb(skb); + return ret; +} +EXPORT_SYMBOL_GPL(wl12xx_cmd_build_probe_req); + +struct sk_buff *wl1271_cmd_build_ap_probe_req(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct sk_buff *skb) +{ + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + int ret; + u32 rate; + + if (!skb) + skb = ieee80211_ap_probereq_get(wl->hw, vif); + if (!skb) + goto out; + + wl1271_dump(DEBUG_SCAN, "AP PROBE REQ: ", skb->data, skb->len); + + rate = wl1271_tx_min_rate_get(wl, wlvif->bitrate_masks[wlvif->band]); + if (wlvif->band == IEEE80211_BAND_2GHZ) + ret = wl1271_cmd_template_set(wl, wlvif->role_id, + CMD_TEMPL_CFG_PROBE_REQ_2_4, + skb->data, skb->len, 0, rate); + else + ret = wl1271_cmd_template_set(wl, wlvif->role_id, + CMD_TEMPL_CFG_PROBE_REQ_5, + skb->data, skb->len, 0, rate); + + if (ret < 0) + wl1271_error("Unable to set ap probe request template."); + +out: + return skb; +} + +int wl1271_cmd_build_arp_rsp(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + int ret, extra = 0; + u16 fc; + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + struct sk_buff *skb; + struct wl12xx_arp_rsp_template *tmpl; + struct ieee80211_hdr_3addr *hdr; + struct arphdr *arp_hdr; + + skb = dev_alloc_skb(sizeof(*hdr) + sizeof(__le16) + sizeof(*tmpl) + + WL1271_EXTRA_SPACE_MAX); + if (!skb) { + wl1271_error("failed to allocate buffer for arp rsp template"); + return -ENOMEM; + } + + skb_reserve(skb, sizeof(*hdr) + WL1271_EXTRA_SPACE_MAX); + + tmpl = (struct wl12xx_arp_rsp_template *)skb_put(skb, sizeof(*tmpl)); + memset(tmpl, 0, sizeof(*tmpl)); + + /* llc layer */ + memcpy(tmpl->llc_hdr, rfc1042_header, sizeof(rfc1042_header)); + tmpl->llc_type = cpu_to_be16(ETH_P_ARP); + + /* arp header */ + arp_hdr = &tmpl->arp_hdr; + arp_hdr->ar_hrd = cpu_to_be16(ARPHRD_ETHER); + arp_hdr->ar_pro = cpu_to_be16(ETH_P_IP); + arp_hdr->ar_hln = ETH_ALEN; + arp_hdr->ar_pln = 4; + arp_hdr->ar_op = cpu_to_be16(ARPOP_REPLY); + + /* arp payload */ + memcpy(tmpl->sender_hw, vif->addr, ETH_ALEN); + tmpl->sender_ip = wlvif->ip_addr; + + /* encryption space */ + switch (wlvif->encryption_type) { + case KEY_TKIP: + if (wl->quirks & WLCORE_QUIRK_TKIP_HEADER_SPACE) + extra = WL1271_EXTRA_SPACE_TKIP; + break; + case KEY_AES: + extra = WL1271_EXTRA_SPACE_AES; + break; + case KEY_NONE: + case KEY_WEP: + case KEY_GEM: + extra = 0; + break; + default: + wl1271_warning("Unknown encryption type: %d", + wlvif->encryption_type); + ret = -EINVAL; + goto out; + } + + if (extra) { + u8 *space = skb_push(skb, extra); + memset(space, 0, extra); + } + + /* QoS header - BE */ + if (wlvif->sta.qos) + memset(skb_push(skb, sizeof(__le16)), 0, sizeof(__le16)); + + /* mac80211 header */ + hdr = (struct ieee80211_hdr_3addr *)skb_push(skb, sizeof(*hdr)); + memset(hdr, 0, sizeof(*hdr)); + fc = IEEE80211_FTYPE_DATA | IEEE80211_FCTL_TODS; + if (wlvif->sta.qos) + fc |= IEEE80211_STYPE_QOS_DATA; + else + fc |= IEEE80211_STYPE_DATA; + if (wlvif->encryption_type != KEY_NONE) + fc |= IEEE80211_FCTL_PROTECTED; + + hdr->frame_control = cpu_to_le16(fc); + memcpy(hdr->addr1, vif->bss_conf.bssid, ETH_ALEN); + memcpy(hdr->addr2, vif->addr, ETH_ALEN); + memset(hdr->addr3, 0xff, ETH_ALEN); + + ret = wl1271_cmd_template_set(wl, wlvif->role_id, CMD_TEMPL_ARP_RSP, + skb->data, skb->len, 0, + wlvif->basic_rate); +out: + dev_kfree_skb(skb); + return ret; +} + +int wl1271_build_qos_null_data(struct wl1271 *wl, struct ieee80211_vif *vif) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + struct ieee80211_qos_hdr template; + + memset(&template, 0, sizeof(template)); + + memcpy(template.addr1, vif->bss_conf.bssid, ETH_ALEN); + memcpy(template.addr2, vif->addr, ETH_ALEN); + memcpy(template.addr3, vif->bss_conf.bssid, ETH_ALEN); + + template.frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | + IEEE80211_STYPE_QOS_NULLFUNC | + IEEE80211_FCTL_TODS); + + /* FIXME: not sure what priority to use here */ + template.qos_ctrl = cpu_to_le16(0); + + return wl1271_cmd_template_set(wl, wlvif->role_id, + CMD_TEMPL_QOS_NULL_DATA, &template, + sizeof(template), 0, + wlvif->basic_rate); +} + +int wl12xx_cmd_set_default_wep_key(struct wl1271 *wl, u8 id, u8 hlid) +{ + struct wl1271_cmd_set_keys *cmd; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd set_default_wep_key %d", id); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->hlid = hlid; + cmd->key_id = id; + cmd->lid_key_type = WEP_DEFAULT_LID_TYPE; + cmd->key_action = cpu_to_le16(KEY_SET_ID); + cmd->key_type = KEY_WEP; + + ret = wl1271_cmd_send(wl, CMD_SET_KEYS, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_warning("cmd set_default_wep_key failed: %d", ret); + goto out; + } + +out: + kfree(cmd); + + return ret; +} + +int wl1271_cmd_set_sta_key(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u16 action, u8 id, u8 key_type, + u8 key_size, const u8 *key, const u8 *addr, + u32 tx_seq_32, u16 tx_seq_16) +{ + struct wl1271_cmd_set_keys *cmd; + int ret = 0; + + /* hlid might have already been deleted */ + if (wlvif->sta.hlid == WL12XX_INVALID_LINK_ID) + return 0; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->hlid = wlvif->sta.hlid; + + if (key_type == KEY_WEP) + cmd->lid_key_type = WEP_DEFAULT_LID_TYPE; + else if (is_broadcast_ether_addr(addr)) + cmd->lid_key_type = BROADCAST_LID_TYPE; + else + cmd->lid_key_type = UNICAST_LID_TYPE; + + cmd->key_action = cpu_to_le16(action); + cmd->key_size = key_size; + cmd->key_type = key_type; + + cmd->ac_seq_num16[0] = cpu_to_le16(tx_seq_16); + cmd->ac_seq_num32[0] = cpu_to_le32(tx_seq_32); + + cmd->key_id = id; + + if (key_type == KEY_TKIP) { + /* + * We get the key in the following form: + * TKIP (16 bytes) - TX MIC (8 bytes) - RX MIC (8 bytes) + * but the target is expecting: + * TKIP - RX MIC - TX MIC + */ + memcpy(cmd->key, key, 16); + memcpy(cmd->key + 16, key + 24, 8); + memcpy(cmd->key + 24, key + 16, 8); + + } else { + memcpy(cmd->key, key, key_size); + } + + wl1271_dump(DEBUG_CRYPT, "TARGET KEY: ", cmd, sizeof(*cmd)); + + ret = wl1271_cmd_send(wl, CMD_SET_KEYS, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_warning("could not set keys"); + goto out; + } + +out: + kfree(cmd); + + return ret; +} + +/* + * TODO: merge with sta/ibss into 1 set_key function. + * note there are slight diffs + */ +int wl1271_cmd_set_ap_key(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u16 action, u8 id, u8 key_type, + u8 key_size, const u8 *key, u8 hlid, u32 tx_seq_32, + u16 tx_seq_16) +{ + struct wl1271_cmd_set_keys *cmd; + int ret = 0; + u8 lid_type; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) + return -ENOMEM; + + if (hlid == wlvif->ap.bcast_hlid) { + if (key_type == KEY_WEP) + lid_type = WEP_DEFAULT_LID_TYPE; + else + lid_type = BROADCAST_LID_TYPE; + } else { + lid_type = UNICAST_LID_TYPE; + } + + wl1271_debug(DEBUG_CRYPT, "ap key action: %d id: %d lid: %d type: %d" + " hlid: %d", (int)action, (int)id, (int)lid_type, + (int)key_type, (int)hlid); + + cmd->lid_key_type = lid_type; + cmd->hlid = hlid; + cmd->key_action = cpu_to_le16(action); + cmd->key_size = key_size; + cmd->key_type = key_type; + cmd->key_id = id; + cmd->ac_seq_num16[0] = cpu_to_le16(tx_seq_16); + cmd->ac_seq_num32[0] = cpu_to_le32(tx_seq_32); + + if (key_type == KEY_TKIP) { + /* + * We get the key in the following form: + * TKIP (16 bytes) - TX MIC (8 bytes) - RX MIC (8 bytes) + * but the target is expecting: + * TKIP - RX MIC - TX MIC + */ + memcpy(cmd->key, key, 16); + memcpy(cmd->key + 16, key + 24, 8); + memcpy(cmd->key + 24, key + 16, 8); + } else { + memcpy(cmd->key, key, key_size); + } + + wl1271_dump(DEBUG_CRYPT, "TARGET AP KEY: ", cmd, sizeof(*cmd)); + + ret = wl1271_cmd_send(wl, CMD_SET_KEYS, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_warning("could not set ap keys"); + goto out; + } + +out: + kfree(cmd); + return ret; +} + +int wl12xx_cmd_set_peer_state(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 hlid) +{ + struct wl12xx_cmd_set_peer_state *cmd; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd set peer state (hlid=%d)", hlid); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->hlid = hlid; + cmd->state = WL1271_CMD_STA_STATE_CONNECTED; + + /* wmm param is valid only for station role */ + if (wlvif->bss_type == BSS_TYPE_STA_BSS) + cmd->wmm = wlvif->wmm_enabled; + + ret = wl1271_cmd_send(wl, CMD_SET_PEER_STATE, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to send set peer state command"); + goto out_free; + } + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl12xx_cmd_add_peer(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct ieee80211_sta *sta, u8 hlid) +{ + struct wl12xx_cmd_add_peer *cmd; + int i, ret; + u32 sta_rates; + + wl1271_debug(DEBUG_CMD, "cmd add peer %d", (int)hlid); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + memcpy(cmd->addr, sta->addr, ETH_ALEN); + cmd->bss_index = WL1271_AP_BSS_INDEX; + cmd->aid = sta->aid; + cmd->hlid = hlid; + cmd->sp_len = sta->max_sp; + cmd->wmm = sta->wme ? 1 : 0; + cmd->session_id = wl->session_ids[hlid]; + + for (i = 0; i < NUM_ACCESS_CATEGORIES_COPY; i++) + if (sta->wme && (sta->uapsd_queues & BIT(i))) + cmd->psd_type[NUM_ACCESS_CATEGORIES_COPY-1-i] = + WL1271_PSD_UPSD_TRIGGER; + else + cmd->psd_type[NUM_ACCESS_CATEGORIES_COPY-1-i] = + WL1271_PSD_LEGACY; + + + sta_rates = sta->supp_rates[wlvif->band]; + if (sta->ht_cap.ht_supported) + sta_rates |= + (sta->ht_cap.mcs.rx_mask[0] << HW_HT_RATES_OFFSET) | + (sta->ht_cap.mcs.rx_mask[1] << HW_MIMO_RATES_OFFSET); + + cmd->supported_rates = + cpu_to_le32(wl1271_tx_enabled_rates_get(wl, sta_rates, + wlvif->band)); + + wl1271_debug(DEBUG_CMD, "new peer rates=0x%x queues=0x%x", + cmd->supported_rates, sta->uapsd_queues); + + ret = wl1271_cmd_send(wl, CMD_ADD_PEER, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to initiate cmd add peer"); + goto out_free; + } + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl12xx_cmd_remove_peer(struct wl1271 *wl, u8 hlid) +{ + struct wl12xx_cmd_remove_peer *cmd; + int ret; + bool timeout = false; + + wl1271_debug(DEBUG_CMD, "cmd remove peer %d", (int)hlid); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->hlid = hlid; + /* We never send a deauth, mac80211 is in charge of this */ + cmd->reason_opcode = 0; + cmd->send_deauth_flag = 0; + + ret = wl1271_cmd_send(wl, CMD_REMOVE_PEER, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to initiate cmd remove peer"); + goto out_free; + } + + ret = wl->ops->wait_for_event(wl, + WLCORE_EVENT_PEER_REMOVE_COMPLETE, + &timeout); + + /* + * We are ok with a timeout here. The event is sometimes not sent + * due to a firmware bug. In case of another error (like SDIO timeout) + * queue a recovery. + */ + if (ret) + wl12xx_queue_recovery_work(wl); + +out_free: + kfree(cmd); + +out: + return ret; +} + +static int wlcore_get_reg_conf_ch_idx(enum ieee80211_band band, u16 ch) +{ + int idx = -1; + + switch (band) { + case IEEE80211_BAND_5GHZ: + if (ch >= 8 && ch <= 16) + idx = ((ch-8)/4 + 18); + else if (ch >= 34 && ch <= 64) + idx = ((ch-34)/2 + 3 + 18); + else if (ch >= 100 && ch <= 140) + idx = ((ch-100)/4 + 15 + 18); + else if (ch >= 149 && ch <= 165) + idx = ((ch-149)/4 + 26 + 18); + else + idx = -1; + break; + case IEEE80211_BAND_2GHZ: + if (ch >= 1 && ch <= 14) + idx = ch - 1; + else + idx = -1; + break; + default: + wl1271_error("get reg conf ch idx - unknown band: %d", + (int)band); + } + + return idx; +} + +void wlcore_set_pending_regdomain_ch(struct wl1271 *wl, u16 channel, + enum ieee80211_band band) +{ + int ch_bit_idx = 0; + + if (!(wl->quirks & WLCORE_QUIRK_REGDOMAIN_CONF)) + return; + + ch_bit_idx = wlcore_get_reg_conf_ch_idx(band, channel); + + if (ch_bit_idx > 0 && ch_bit_idx <= WL1271_MAX_CHANNELS) + set_bit(ch_bit_idx, (long *)wl->reg_ch_conf_pending); +} + +int wlcore_cmd_regdomain_config_locked(struct wl1271 *wl) +{ + struct wl12xx_cmd_regdomain_dfs_config *cmd = NULL; + int ret = 0, i, b, ch_bit_idx; + struct ieee80211_channel *channel; + u32 tmp_ch_bitmap[2]; + u16 ch; + struct wiphy *wiphy = wl->hw->wiphy; + struct ieee80211_supported_band *band; + bool timeout = false; + + if (!(wl->quirks & WLCORE_QUIRK_REGDOMAIN_CONF)) + return 0; + + wl1271_debug(DEBUG_CMD, "cmd reg domain config"); + + memset(tmp_ch_bitmap, 0, sizeof(tmp_ch_bitmap)); + + for (b = IEEE80211_BAND_2GHZ; b <= IEEE80211_BAND_5GHZ; b++) { + band = wiphy->bands[b]; + for (i = 0; i < band->n_channels; i++) { + channel = &band->channels[i]; + ch = channel->hw_value; + + if (channel->flags & (IEEE80211_CHAN_DISABLED | + IEEE80211_CHAN_RADAR | + IEEE80211_CHAN_PASSIVE_SCAN)) + continue; + + ch_bit_idx = wlcore_get_reg_conf_ch_idx(b, ch); + if (ch_bit_idx < 0) + continue; + + set_bit(ch_bit_idx, (long *)tmp_ch_bitmap); + } + } + + tmp_ch_bitmap[0] |= wl->reg_ch_conf_pending[0]; + tmp_ch_bitmap[1] |= wl->reg_ch_conf_pending[1]; + + if (!memcmp(tmp_ch_bitmap, wl->reg_ch_conf_last, sizeof(tmp_ch_bitmap))) + goto out; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->ch_bit_map1 = cpu_to_le32(tmp_ch_bitmap[0]); + cmd->ch_bit_map2 = cpu_to_le32(tmp_ch_bitmap[1]); + + wl1271_debug(DEBUG_CMD, + "cmd reg domain bitmap1: 0x%08x, bitmap2: 0x%08x", + cmd->ch_bit_map1, cmd->ch_bit_map2); + + ret = wl1271_cmd_send(wl, CMD_DFS_CHANNEL_CONFIG, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to send reg domain dfs config"); + goto out; + } + + ret = wl->ops->wait_for_event(wl, + WLCORE_EVENT_DFS_CONFIG_COMPLETE, + &timeout); + if (ret < 0 || timeout) { + wl1271_error("reg domain conf %serror", + timeout ? "completion " : ""); + ret = timeout ? -ETIMEDOUT : ret; + goto out; + } + + memcpy(wl->reg_ch_conf_last, tmp_ch_bitmap, sizeof(tmp_ch_bitmap)); + memset(wl->reg_ch_conf_pending, 0, sizeof(wl->reg_ch_conf_pending)); + +out: + kfree(cmd); + return ret; +} + +int wl12xx_cmd_config_fwlog(struct wl1271 *wl) +{ + struct wl12xx_cmd_config_fwlog *cmd; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd config firmware logger"); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->logger_mode = wl->conf.fwlog.mode; + cmd->log_severity = wl->conf.fwlog.severity; + cmd->timestamp = wl->conf.fwlog.timestamp; + cmd->output = wl->conf.fwlog.output; + cmd->threshold = wl->conf.fwlog.threshold; + + ret = wl1271_cmd_send(wl, CMD_CONFIG_FWLOGGER, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to send config firmware logger command"); + goto out_free; + } + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl12xx_cmd_start_fwlog(struct wl1271 *wl) +{ + struct wl12xx_cmd_start_fwlog *cmd; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd start firmware logger"); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + ret = wl1271_cmd_send(wl, CMD_START_FWLOGGER, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to send start firmware logger command"); + goto out_free; + } + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl12xx_cmd_stop_fwlog(struct wl1271 *wl) +{ + struct wl12xx_cmd_stop_fwlog *cmd; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd stop firmware logger"); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + ret = wl1271_cmd_send(wl, CMD_STOP_FWLOGGER, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to send stop firmware logger command"); + goto out_free; + } + +out_free: + kfree(cmd); + +out: + return ret; +} + +static int wl12xx_cmd_roc(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 role_id, enum ieee80211_band band, u8 channel) +{ + struct wl12xx_cmd_roc *cmd; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd roc %d (%d)", channel, role_id); + + if (WARN_ON(role_id == WL12XX_INVALID_ROLE_ID)) + return -EINVAL; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->role_id = role_id; + cmd->channel = channel; + switch (band) { + case IEEE80211_BAND_2GHZ: + cmd->band = WLCORE_BAND_2_4GHZ; + break; + case IEEE80211_BAND_5GHZ: + cmd->band = WLCORE_BAND_5GHZ; + break; + default: + wl1271_error("roc - unknown band: %d", (int)wlvif->band); + ret = -EINVAL; + goto out_free; + } + + + ret = wl1271_cmd_send(wl, CMD_REMAIN_ON_CHANNEL, cmd, sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to send ROC command"); + goto out_free; + } + +out_free: + kfree(cmd); + +out: + return ret; +} + +static int wl12xx_cmd_croc(struct wl1271 *wl, u8 role_id) +{ + struct wl12xx_cmd_croc *cmd; + int ret = 0; + + wl1271_debug(DEBUG_CMD, "cmd croc (%d)", role_id); + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + cmd->role_id = role_id; + + ret = wl1271_cmd_send(wl, CMD_CANCEL_REMAIN_ON_CHANNEL, cmd, + sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("failed to send ROC command"); + goto out_free; + } + +out_free: + kfree(cmd); + +out: + return ret; +} + +int wl12xx_roc(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 role_id, + enum ieee80211_band band, u8 channel) +{ + int ret = 0; + + if (WARN_ON(test_bit(role_id, wl->roc_map))) + return 0; + + ret = wl12xx_cmd_roc(wl, wlvif, role_id, band, channel); + if (ret < 0) + goto out; + + __set_bit(role_id, wl->roc_map); +out: + return ret; +} + +int wl12xx_croc(struct wl1271 *wl, u8 role_id) +{ + int ret = 0; + + if (WARN_ON(!test_bit(role_id, wl->roc_map))) + return 0; + + ret = wl12xx_cmd_croc(wl, role_id); + if (ret < 0) + goto out; + + __clear_bit(role_id, wl->roc_map); + + /* + * Rearm the tx watchdog when removing the last ROC. This prevents + * recoveries due to just finished ROCs - when Tx hasn't yet had + * a chance to get out. + */ + if (find_first_bit(wl->roc_map, WL12XX_MAX_ROLES) >= WL12XX_MAX_ROLES) + wl12xx_rearm_tx_watchdog_locked(wl); +out: + return ret; +} + +int wl12xx_cmd_stop_channel_switch(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + 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; + } + + cmd->role_id = wlvif->role_id; + + 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; +} + +/* start dev role and roc on its channel */ +int wl12xx_start_dev(struct wl1271 *wl, struct wl12xx_vif *wlvif, + enum ieee80211_band band, int channel) +{ + int ret; + + if (WARN_ON(!(wlvif->bss_type == BSS_TYPE_STA_BSS || + wlvif->bss_type == BSS_TYPE_IBSS))) + return -EINVAL; + + ret = wl12xx_cmd_role_enable(wl, + wl12xx_wlvif_to_vif(wlvif)->addr, + WL1271_ROLE_DEVICE, + &wlvif->dev_role_id); + if (ret < 0) + goto out; + + ret = wl12xx_cmd_role_start_dev(wl, wlvif, band, channel); + if (ret < 0) + goto out_disable; + + ret = wl12xx_roc(wl, wlvif, wlvif->dev_role_id, band, channel); + if (ret < 0) + goto out_stop; + + return 0; + +out_stop: + wl12xx_cmd_role_stop_dev(wl, wlvif); +out_disable: + wl12xx_cmd_role_disable(wl, &wlvif->dev_role_id); +out: + return ret; +} + +/* croc dev hlid, and stop the role */ +int wl12xx_stop_dev(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + int ret; + + if (WARN_ON(!(wlvif->bss_type == BSS_TYPE_STA_BSS || + wlvif->bss_type == BSS_TYPE_IBSS))) + return -EINVAL; + + /* flush all pending packets */ + ret = wlcore_tx_work_locked(wl); + if (ret < 0) + goto out; + + if (test_bit(wlvif->dev_role_id, wl->roc_map)) { + ret = wl12xx_croc(wl, wlvif->dev_role_id); + if (ret < 0) + goto out; + } + + ret = wl12xx_cmd_role_stop_dev(wl, wlvif); + if (ret < 0) + goto out; + + ret = wl12xx_cmd_role_disable(wl, &wlvif->dev_role_id); + if (ret < 0) + goto out; + +out: + return ret; +} diff --git a/drivers/net/wireless/ti/wlcore/cmd.h b/drivers/net/wireless/ti/wlcore/cmd.h new file mode 100644 index 0000000..fd34123 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/cmd.h @@ -0,0 +1,698 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 1998-2009 Texas Instruments. All rights reserved. + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __CMD_H__ +#define __CMD_H__ + +#include "wlcore.h" + +struct acx_header; + +int wl1271_cmd_send(struct wl1271 *wl, u16 id, void *buf, size_t len, + size_t res_len); +int wlcore_cmd_send_failsafe(struct wl1271 *wl, u16 id, void *buf, size_t len, + size_t res_len, unsigned long valid_rets); +int wl12xx_cmd_role_enable(struct wl1271 *wl, u8 *addr, u8 role_type, + u8 *role_id); +int wl12xx_cmd_role_disable(struct wl1271 *wl, u8 *role_id); +int wl12xx_cmd_role_start_sta(struct wl1271 *wl, struct wl12xx_vif *wlvif); +int wl12xx_cmd_role_stop_sta(struct wl1271 *wl, struct wl12xx_vif *wlvif); +int wl12xx_cmd_role_start_ap(struct wl1271 *wl, struct wl12xx_vif *wlvif); +int wl12xx_cmd_role_stop_ap(struct wl1271 *wl, struct wl12xx_vif *wlvif); +int wl12xx_cmd_role_start_ibss(struct wl1271 *wl, struct wl12xx_vif *wlvif); +int wl12xx_start_dev(struct wl1271 *wl, struct wl12xx_vif *wlvif, + enum ieee80211_band band, int channel); +int wl12xx_stop_dev(struct wl1271 *wl, struct wl12xx_vif *wlvif); +int wl1271_cmd_test(struct wl1271 *wl, void *buf, size_t buf_len, u8 answer); +int wl1271_cmd_interrogate(struct wl1271 *wl, u16 id, void *buf, size_t len); +int wl1271_cmd_configure(struct wl1271 *wl, u16 id, void *buf, size_t len); +int wlcore_cmd_configure_failsafe(struct wl1271 *wl, u16 id, void *buf, + size_t len, unsigned long valid_rets); +int wl1271_cmd_data_path(struct wl1271 *wl, bool enable); +int wl1271_cmd_ps_mode(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 ps_mode, u16 auto_ps_timeout); +int wl1271_cmd_read_memory(struct wl1271 *wl, u32 addr, void *answer, + size_t len); +int wl1271_cmd_template_set(struct wl1271 *wl, u8 role_id, + u16 template_id, void *buf, size_t buf_len, + int index, u32 rates); +int wl12xx_cmd_build_null_data(struct wl1271 *wl, struct wl12xx_vif *wlvif); +int wl1271_cmd_build_ps_poll(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u16 aid); +int wl12xx_cmd_build_probe_req(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 role_id, u8 band, + const u8 *ssid, size_t ssid_len, + const u8 *ie, size_t ie_len, bool sched_scan); +struct sk_buff *wl1271_cmd_build_ap_probe_req(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct sk_buff *skb); +int wl1271_cmd_build_arp_rsp(struct wl1271 *wl, struct wl12xx_vif *wlvif); +int wl1271_build_qos_null_data(struct wl1271 *wl, struct ieee80211_vif *vif); +int wl12xx_cmd_build_klv_null_data(struct wl1271 *wl, + struct wl12xx_vif *wlvif); +int wl12xx_cmd_set_default_wep_key(struct wl1271 *wl, u8 id, u8 hlid); +int wl1271_cmd_set_sta_key(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u16 action, u8 id, u8 key_type, + u8 key_size, const u8 *key, const u8 *addr, + u32 tx_seq_32, u16 tx_seq_16); +int wl1271_cmd_set_ap_key(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u16 action, u8 id, u8 key_type, + u8 key_size, const u8 *key, u8 hlid, u32 tx_seq_32, + u16 tx_seq_16); +int wl12xx_cmd_set_peer_state(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 hlid); +int wl12xx_roc(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 role_id, + enum ieee80211_band band, u8 channel); +int wl12xx_croc(struct wl1271 *wl, u8 role_id); +int wl12xx_cmd_add_peer(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct ieee80211_sta *sta, u8 hlid); +int wl12xx_cmd_remove_peer(struct wl1271 *wl, u8 hlid); +void wlcore_set_pending_regdomain_ch(struct wl1271 *wl, u16 channel, + enum ieee80211_band band); +int wlcore_cmd_regdomain_config_locked(struct wl1271 *wl); +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 wl12xx_vif *wlvif, + struct ieee80211_channel_switch *ch_switch); +int wl12xx_cmd_stop_channel_switch(struct wl1271 *wl, + struct wl12xx_vif *wlvif); +int wl12xx_allocate_link(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 *hlid); +void wl12xx_free_link(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 *hlid); +int wlcore_cmd_wait_for_event_or_timeout(struct wl1271 *wl, + u32 mask, bool *timeout); + +enum wl1271_commands { + CMD_INTERROGATE = 1, /* use this to read information elements */ + CMD_CONFIGURE = 2, /* use this to write information elements */ + CMD_ENABLE_RX = 3, + CMD_ENABLE_TX = 4, + CMD_DISABLE_RX = 5, + CMD_DISABLE_TX = 6, + CMD_SCAN = 7, + CMD_STOP_SCAN = 8, + CMD_SET_KEYS = 9, + CMD_READ_MEMORY = 10, + CMD_WRITE_MEMORY = 11, + CMD_SET_TEMPLATE = 12, + CMD_TEST = 13, + CMD_NOISE_HIST = 14, + CMD_QUIET_ELEMENT_SET_STATE = 15, + CMD_SET_BCN_MODE = 16, + + CMD_MEASUREMENT = 17, + CMD_STOP_MEASUREMENT = 18, + CMD_SET_PS_MODE = 19, + CMD_CHANNEL_SWITCH = 20, + CMD_STOP_CHANNEL_SWICTH = 21, + CMD_AP_DISCOVERY = 22, + CMD_STOP_AP_DISCOVERY = 23, + CMD_HEALTH_CHECK = 24, + CMD_DEBUG = 25, + CMD_TRIGGER_SCAN_TO = 26, + CMD_CONNECTION_SCAN_CFG = 27, + CMD_CONNECTION_SCAN_SSID_CFG = 28, + CMD_START_PERIODIC_SCAN = 29, + CMD_STOP_PERIODIC_SCAN = 30, + CMD_SET_PEER_STATE = 31, + CMD_REMAIN_ON_CHANNEL = 32, + CMD_CANCEL_REMAIN_ON_CHANNEL = 33, + CMD_CONFIG_FWLOGGER = 34, + CMD_START_FWLOGGER = 35, + CMD_STOP_FWLOGGER = 36, + + /* Access point commands */ + CMD_ADD_PEER = 37, + CMD_REMOVE_PEER = 38, + + /* Role API */ + CMD_ROLE_ENABLE = 39, + CMD_ROLE_DISABLE = 40, + CMD_ROLE_START = 41, + CMD_ROLE_STOP = 42, + + /* DFS */ + CMD_START_RADAR_DETECTION = 43, + CMD_STOP_RADAR_DETECTION = 44, + + /* WIFI Direct */ + CMD_WFD_START_DISCOVERY = 45, + CMD_WFD_STOP_DISCOVERY = 46, + CMD_WFD_ATTRIBUTE_CONFIG = 47, + CMD_GENERIC_CFG = 48, + CMD_NOP = 49, + + /* start of 18xx specific commands */ + CMD_DFS_CHANNEL_CONFIG = 60, + + MAX_COMMAND_ID = 0xFFFF, +}; + +#define MAX_CMD_PARAMS 572 + +enum cmd_templ { + CMD_TEMPL_NULL_DATA = 0, + CMD_TEMPL_BEACON, + CMD_TEMPL_CFG_PROBE_REQ_2_4, + CMD_TEMPL_CFG_PROBE_REQ_5, + CMD_TEMPL_PROBE_RESPONSE, + CMD_TEMPL_QOS_NULL_DATA, + CMD_TEMPL_PS_POLL, + CMD_TEMPL_KLV, + CMD_TEMPL_DISCONNECT, + CMD_TEMPL_APP_PROBE_REQ_2_4_LEGACY, + CMD_TEMPL_APP_PROBE_REQ_5_LEGACY, + CMD_TEMPL_BAR, /* for firmware internal use only */ + CMD_TEMPL_CTS, /* + * For CTS-to-self (FastCTS) mechanism + * for BT/WLAN coexistence (SoftGemini). */ + CMD_TEMPL_AP_BEACON, + CMD_TEMPL_AP_PROBE_RESPONSE, + CMD_TEMPL_ARP_RSP, + CMD_TEMPL_DEAUTH_AP, + CMD_TEMPL_TEMPORARY, + CMD_TEMPL_LINK_MEASUREMENT_REPORT, + CMD_TEMPL_PROBE_REQ_2_4_PERIODIC, + CMD_TEMPL_PROBE_REQ_5_PERIODIC, + + CMD_TEMPL_MAX = 0xff +}; + +/* unit ms */ +#define WL1271_COMMAND_TIMEOUT 2000 +#define WL1271_CMD_TEMPL_DFLT_SIZE 252 +#define WL1271_CMD_TEMPL_MAX_SIZE 512 +#define WL1271_EVENT_TIMEOUT 1500 + +struct wl1271_cmd_header { + __le16 id; + __le16 status; + /* payload */ + u8 data[0]; +} __packed; + +#define WL1271_CMD_MAX_PARAMS 572 + +struct wl1271_command { + struct wl1271_cmd_header header; + u8 parameters[WL1271_CMD_MAX_PARAMS]; +} __packed; + +enum { + CMD_MAILBOX_IDLE = 0, + CMD_STATUS_SUCCESS = 1, + CMD_STATUS_UNKNOWN_CMD = 2, + CMD_STATUS_UNKNOWN_IE = 3, + CMD_STATUS_REJECT_MEAS_SG_ACTIVE = 11, + CMD_STATUS_RX_BUSY = 13, + CMD_STATUS_INVALID_PARAM = 14, + CMD_STATUS_TEMPLATE_TOO_LARGE = 15, + CMD_STATUS_OUT_OF_MEMORY = 16, + CMD_STATUS_STA_TABLE_FULL = 17, + CMD_STATUS_RADIO_ERROR = 18, + CMD_STATUS_WRONG_NESTING = 19, + CMD_STATUS_TIMEOUT = 21, /* Driver internal use.*/ + CMD_STATUS_FW_RESET = 22, /* Driver internal use.*/ + CMD_STATUS_TEMPLATE_OOM = 23, + CMD_STATUS_NO_RX_BA_SESSION = 24, + + MAX_COMMAND_STATUS +}; + +#define CMDMBOX_HEADER_LEN 4 +#define CMDMBOX_INFO_ELEM_HEADER_LEN 4 + +enum { + BSS_TYPE_IBSS = 0, + BSS_TYPE_STA_BSS = 2, + BSS_TYPE_AP_BSS = 3, + MAX_BSS_TYPE = 0xFF +}; + +#define WL1271_JOIN_CMD_CTRL_TX_FLUSH 0x80 /* Firmware flushes all Tx */ +#define WL1271_JOIN_CMD_TX_SESSION_OFFSET 1 +#define WL1271_JOIN_CMD_BSS_TYPE_5GHZ 0x10 + +struct wl12xx_cmd_role_enable { + struct wl1271_cmd_header header; + + u8 role_id; + u8 role_type; + u8 mac_address[ETH_ALEN]; +} __packed; + +struct wl12xx_cmd_role_disable { + struct wl1271_cmd_header header; + + u8 role_id; + u8 padding[3]; +} __packed; + +enum wlcore_band { + WLCORE_BAND_2_4GHZ = 0, + WLCORE_BAND_5GHZ = 1, + WLCORE_BAND_JAPAN_4_9_GHZ = 2, + WLCORE_BAND_DEFAULT = WLCORE_BAND_2_4GHZ, + WLCORE_BAND_INVALID = 0x7E, + WLCORE_BAND_MAX_RADIO = 0x7F, +}; + +enum wlcore_channel_type { + WLCORE_CHAN_NO_HT, + WLCORE_CHAN_HT20, + WLCORE_CHAN_HT40MINUS, + WLCORE_CHAN_HT40PLUS +}; + +struct wl12xx_cmd_role_start { + struct wl1271_cmd_header header; + + u8 role_id; + u8 band; + u8 channel; + + /* enum wlcore_channel_type */ + u8 channel_type; + + union { + struct { + u8 hlid; + u8 session; + u8 padding_1[54]; + } __packed device; + /* sta & p2p_cli use the same struct */ + struct { + u8 bssid[ETH_ALEN]; + u8 hlid; /* data hlid */ + u8 session; + __le32 remote_rates; /* remote supported rates */ + + /* + * The target uses this field to determine the rate at + * which to transmit control frame responses (such as + * ACK or CTS frames). + */ + __le32 basic_rate_set; + __le32 local_rates; /* local supported rates */ + + u8 ssid_type; + u8 ssid_len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + + __le16 beacon_interval; /* in TBTTs */ + } __packed sta; + struct { + u8 bssid[ETH_ALEN]; + u8 hlid; /* data hlid */ + u8 dtim_interval; + __le32 remote_rates; /* remote supported rates */ + + __le32 basic_rate_set; + __le32 local_rates; /* local supported rates */ + + u8 ssid_type; + u8 ssid_len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + + __le16 beacon_interval; /* in TBTTs */ + + u8 padding_1[4]; + } __packed ibss; + /* ap & p2p_go use the same struct */ + struct { + __le16 aging_period; /* in secs */ + u8 beacon_expiry; /* in ms */ + u8 bss_index; + /* The host link id for the AP's global queue */ + u8 global_hlid; + /* The host link id for the AP's broadcast queue */ + u8 broadcast_hlid; + + __le16 beacon_interval; /* in TBTTs */ + + __le32 basic_rate_set; + __le32 local_rates; /* local supported rates */ + + u8 dtim_interval; + + u8 ssid_type; + u8 ssid_len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + + u8 reset_tsf; + + /* + * ap supports wmm (note that there is additional + * per-sta wmm configuration) + */ + u8 wmm; + + u8 bcast_session_id; + u8 global_session_id; + u8 padding_1[1]; + } __packed ap; + }; +} __packed; + +struct wl12xx_cmd_role_stop { + struct wl1271_cmd_header header; + + u8 role_id; + u8 disc_type; /* only STA and P2P_CLI */ + __le16 reason; /* only STA and P2P_CLI */ +} __packed; + +struct cmd_enabledisable_path { + struct wl1271_cmd_header header; + + u8 channel; + u8 padding[3]; +} __packed; + +#define WL1271_RATE_AUTOMATIC 0 + +struct wl1271_cmd_template_set { + struct wl1271_cmd_header header; + + u8 role_id; + u8 template_type; + __le16 len; + u8 index; /* relevant only for KLV_TEMPLATE type */ + u8 padding[3]; + + __le32 enabled_rates; + u8 short_retry_limit; + u8 long_retry_limit; + u8 aflags; + u8 reserved; + + u8 template_data[WL1271_CMD_TEMPL_MAX_SIZE]; +} __packed; + +#define TIM_ELE_ID 5 +#define PARTIAL_VBM_MAX 251 + +struct wl1271_tim { + u8 identity; + u8 length; + u8 dtim_count; + u8 dtim_period; + u8 bitmap_ctrl; + u8 pvb_field[PARTIAL_VBM_MAX]; /* Partial Virtual Bitmap */ +} __packed; + +enum wl1271_cmd_ps_mode { + STATION_AUTO_PS_MODE, /* Dynamic Power Save */ + STATION_ACTIVE_MODE, + STATION_POWER_SAVE_MODE +}; + +struct wl1271_cmd_ps_params { + struct wl1271_cmd_header header; + + u8 role_id; + u8 ps_mode; /* STATION_* */ + u16 auto_ps_timeout; +} __packed; + +/* HW encryption keys */ +#define NUM_ACCESS_CATEGORIES_COPY 4 + +enum wl1271_cmd_key_action { + KEY_ADD_OR_REPLACE = 1, + KEY_REMOVE = 2, + KEY_SET_ID = 3, + MAX_KEY_ACTION = 0xffff, +}; + +enum wl1271_cmd_lid_key_type { + UNICAST_LID_TYPE = 0, + BROADCAST_LID_TYPE = 1, + WEP_DEFAULT_LID_TYPE = 2 +}; + +enum wl1271_cmd_key_type { + KEY_NONE = 0, + KEY_WEP = 1, + KEY_TKIP = 2, + KEY_AES = 3, + KEY_GEM = 4, +}; + +struct wl1271_cmd_set_keys { + struct wl1271_cmd_header header; + + /* + * Indicates whether the HLID is a unicast key set + * or broadcast key set. A special value 0xFF is + * used to indicate that the HLID is on WEP-default + * (multi-hlids). of type wl1271_cmd_lid_key_type. + */ + u8 hlid; + + /* + * In WEP-default network (hlid == 0xFF) used to + * indicate which network STA/IBSS/AP role should be + * changed + */ + u8 lid_key_type; + + /* + * Key ID - For TKIP and AES key types, this field + * indicates the value that should be inserted into + * the KeyID field of frames transmitted using this + * key entry. For broadcast keys the index use as a + * marker for TX/RX key. + * For WEP default network (HLID=0xFF), this field + * indicates the ID of the key to add or remove. + */ + u8 key_id; + u8 reserved_1; + + /* key_action_e */ + __le16 key_action; + + /* key size in bytes */ + u8 key_size; + + /* key_type_e */ + u8 key_type; + + /* This field holds the security key data to add to the STA table */ + u8 key[MAX_KEY_SIZE]; + __le16 ac_seq_num16[NUM_ACCESS_CATEGORIES_COPY]; + __le32 ac_seq_num32[NUM_ACCESS_CATEGORIES_COPY]; +} __packed; + +struct wl1271_cmd_test_header { + u8 id; + u8 padding[3]; +} __packed; + +enum wl1271_channel_tune_bands { + WL1271_CHANNEL_TUNE_BAND_2_4, + WL1271_CHANNEL_TUNE_BAND_5, + WL1271_CHANNEL_TUNE_BAND_4_9 +}; + +#define WL1271_PD_REFERENCE_POINT_BAND_B_G 0 + +/* + * There are three types of disconnections: + * + * DISCONNECT_IMMEDIATE: the fw doesn't send any frames + * DISCONNECT_DEAUTH: the fw generates a DEAUTH request with the reason + * we have passed + * DISCONNECT_DISASSOC: the fw generates a DESASSOC request with the reason + * we have passed + */ +enum wl1271_disconnect_type { + DISCONNECT_IMMEDIATE, + DISCONNECT_DEAUTH, + DISCONNECT_DISASSOC +}; + +#define WL1271_CMD_STA_STATE_CONNECTED 1 + +struct wl12xx_cmd_set_peer_state { + struct wl1271_cmd_header header; + + u8 hlid; + u8 state; + + /* + * wmm is relevant for sta role only. + * ap role configures the per-sta wmm params in + * the add_peer command. + */ + u8 wmm; + u8 padding[1]; +} __packed; + +struct wl12xx_cmd_roc { + struct wl1271_cmd_header header; + + u8 role_id; + u8 channel; + u8 band; + u8 padding; +}; + +struct wl12xx_cmd_croc { + struct wl1271_cmd_header header; + + u8 role_id; + u8 padding[3]; +}; + +enum wl12xx_ssid_type { + WL12XX_SSID_TYPE_PUBLIC = 0, + WL12XX_SSID_TYPE_HIDDEN = 1, + WL12XX_SSID_TYPE_ANY = 2, +}; + +enum wl1271_psd_type { + WL1271_PSD_LEGACY = 0, + WL1271_PSD_UPSD_TRIGGER = 1, + WL1271_PSD_LEGACY_PSPOLL = 2, + WL1271_PSD_SAPSD = 3 +}; + +struct wl12xx_cmd_add_peer { + struct wl1271_cmd_header header; + + u8 addr[ETH_ALEN]; + u8 hlid; + u8 aid; + u8 psd_type[NUM_ACCESS_CATEGORIES_COPY]; + __le32 supported_rates; + u8 bss_index; + u8 sp_len; + u8 wmm; + u8 session_id; +} __packed; + +struct wl12xx_cmd_remove_peer { + struct wl1271_cmd_header header; + + u8 hlid; + u8 reason_opcode; + u8 send_deauth_flag; + u8 padding1; +} __packed; + +/* + * Continuous mode - packets are transferred to the host periodically + * via the data path. + * On demand - Log messages are stored in a cyclic buffer in the + * firmware, and only transferred to the host when explicitly requested + */ +enum wl12xx_fwlogger_log_mode { + WL12XX_FWLOG_CONTINUOUS, + WL12XX_FWLOG_ON_DEMAND +}; + +/* Include/exclude timestamps from the log messages */ +enum wl12xx_fwlogger_timestamp { + WL12XX_FWLOG_TIMESTAMP_DISABLED, + WL12XX_FWLOG_TIMESTAMP_ENABLED +}; + +/* + * Logs can be routed to the debug pinouts (where available), to the host bus + * (SDIO/SPI), or dropped + */ +enum wl12xx_fwlogger_output { + WL12XX_FWLOG_OUTPUT_NONE, + WL12XX_FWLOG_OUTPUT_DBG_PINS, + WL12XX_FWLOG_OUTPUT_HOST, +}; + +struct wl12xx_cmd_regdomain_dfs_config { + struct wl1271_cmd_header header; + + __le32 ch_bit_map1; + __le32 ch_bit_map2; +} __packed; + +struct wl12xx_cmd_config_fwlog { + struct wl1271_cmd_header header; + + /* See enum wl12xx_fwlogger_log_mode */ + u8 logger_mode; + + /* Minimum log level threshold */ + u8 log_severity; + + /* Include/exclude timestamps from the log messages */ + u8 timestamp; + + /* See enum wl1271_fwlogger_output */ + u8 output; + + /* Regulates the frequency of log messages */ + u8 threshold; + + u8 padding[3]; +} __packed; + +struct wl12xx_cmd_start_fwlog { + struct wl1271_cmd_header header; +} __packed; + +struct wl12xx_cmd_stop_fwlog { + struct wl1271_cmd_header header; +} __packed; + +struct wl12xx_cmd_stop_channel_switch { + struct wl1271_cmd_header header; + + u8 role_id; + u8 padding[3]; +} __packed; + +/* Used to check radio status after calibration */ +#define MAX_TLV_LENGTH 500 +#define TEST_CMD_P2G_CAL 2 /* TX BiP */ + +struct wl1271_cmd_cal_p2g { + struct wl1271_cmd_header header; + + struct wl1271_cmd_test_header test; + + __le32 ver; + __le16 len; + u8 buf[MAX_TLV_LENGTH]; + u8 type; + u8 padding; + + __le16 radio_status; + + u8 sub_band_mask; + u8 padding2; +} __packed; + +#endif /* __WL1271_CMD_H__ */ diff --git a/drivers/net/wireless/ti/wlcore/conf.h b/drivers/net/wireless/ti/wlcore/conf.h new file mode 100644 index 0000000..2b96ff8 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/conf.h @@ -0,0 +1,1384 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __CONF_H__ +#define __CONF_H__ + +enum { + CONF_HW_BIT_RATE_1MBPS = BIT(0), + CONF_HW_BIT_RATE_2MBPS = BIT(1), + CONF_HW_BIT_RATE_5_5MBPS = BIT(2), + CONF_HW_BIT_RATE_6MBPS = BIT(3), + CONF_HW_BIT_RATE_9MBPS = BIT(4), + CONF_HW_BIT_RATE_11MBPS = BIT(5), + CONF_HW_BIT_RATE_12MBPS = BIT(6), + CONF_HW_BIT_RATE_18MBPS = BIT(7), + CONF_HW_BIT_RATE_22MBPS = BIT(8), + CONF_HW_BIT_RATE_24MBPS = BIT(9), + CONF_HW_BIT_RATE_36MBPS = BIT(10), + CONF_HW_BIT_RATE_48MBPS = BIT(11), + CONF_HW_BIT_RATE_54MBPS = BIT(12), + CONF_HW_BIT_RATE_MCS_0 = BIT(13), + CONF_HW_BIT_RATE_MCS_1 = BIT(14), + CONF_HW_BIT_RATE_MCS_2 = BIT(15), + CONF_HW_BIT_RATE_MCS_3 = BIT(16), + CONF_HW_BIT_RATE_MCS_4 = BIT(17), + CONF_HW_BIT_RATE_MCS_5 = BIT(18), + CONF_HW_BIT_RATE_MCS_6 = BIT(19), + CONF_HW_BIT_RATE_MCS_7 = BIT(20), + CONF_HW_BIT_RATE_MCS_8 = BIT(21), + CONF_HW_BIT_RATE_MCS_9 = BIT(22), + CONF_HW_BIT_RATE_MCS_10 = BIT(23), + CONF_HW_BIT_RATE_MCS_11 = BIT(24), + CONF_HW_BIT_RATE_MCS_12 = BIT(25), + CONF_HW_BIT_RATE_MCS_13 = BIT(26), + CONF_HW_BIT_RATE_MCS_14 = BIT(27), + CONF_HW_BIT_RATE_MCS_15 = BIT(28), +}; + +enum { + CONF_HW_RATE_INDEX_1MBPS = 0, + CONF_HW_RATE_INDEX_2MBPS = 1, + CONF_HW_RATE_INDEX_5_5MBPS = 2, + CONF_HW_RATE_INDEX_11MBPS = 3, + CONF_HW_RATE_INDEX_6MBPS = 4, + CONF_HW_RATE_INDEX_9MBPS = 5, + CONF_HW_RATE_INDEX_12MBPS = 6, + CONF_HW_RATE_INDEX_18MBPS = 7, + CONF_HW_RATE_INDEX_24MBPS = 8, + CONF_HW_RATE_INDEX_36MBPS = 9, + CONF_HW_RATE_INDEX_48MBPS = 10, + CONF_HW_RATE_INDEX_54MBPS = 11, + CONF_HW_RATE_INDEX_MCS0 = 12, + CONF_HW_RATE_INDEX_MCS1 = 13, + CONF_HW_RATE_INDEX_MCS2 = 14, + CONF_HW_RATE_INDEX_MCS3 = 15, + CONF_HW_RATE_INDEX_MCS4 = 16, + CONF_HW_RATE_INDEX_MCS5 = 17, + CONF_HW_RATE_INDEX_MCS6 = 18, + CONF_HW_RATE_INDEX_MCS7 = 19, + CONF_HW_RATE_INDEX_MCS7_SGI = 20, + CONF_HW_RATE_INDEX_MCS0_40MHZ = 21, + CONF_HW_RATE_INDEX_MCS1_40MHZ = 22, + CONF_HW_RATE_INDEX_MCS2_40MHZ = 23, + CONF_HW_RATE_INDEX_MCS3_40MHZ = 24, + CONF_HW_RATE_INDEX_MCS4_40MHZ = 25, + CONF_HW_RATE_INDEX_MCS5_40MHZ = 26, + CONF_HW_RATE_INDEX_MCS6_40MHZ = 27, + CONF_HW_RATE_INDEX_MCS7_40MHZ = 28, + CONF_HW_RATE_INDEX_MCS7_40MHZ_SGI = 29, + + /* MCS8+ rates overlap with 40Mhz rates */ + CONF_HW_RATE_INDEX_MCS8 = 21, + CONF_HW_RATE_INDEX_MCS9 = 22, + CONF_HW_RATE_INDEX_MCS10 = 23, + CONF_HW_RATE_INDEX_MCS11 = 24, + CONF_HW_RATE_INDEX_MCS12 = 25, + CONF_HW_RATE_INDEX_MCS13 = 26, + CONF_HW_RATE_INDEX_MCS14 = 27, + CONF_HW_RATE_INDEX_MCS15 = 28, + CONF_HW_RATE_INDEX_MCS15_SGI = 29, + + CONF_HW_RATE_INDEX_MAX = CONF_HW_RATE_INDEX_MCS7_40MHZ_SGI, +}; + +#define CONF_HW_RXTX_RATE_UNSUPPORTED 0xff + +enum { + CONF_SG_DISABLE = 0, + CONF_SG_PROTECTIVE, + CONF_SG_OPPORTUNISTIC +}; + +enum { + /* + * Configure the min and max time BT gains the antenna + * in WLAN / BT master basic rate + * + * Range: 0 - 255 (ms) + */ + CONF_SG_ACL_BT_MASTER_MIN_BR = 0, + CONF_SG_ACL_BT_MASTER_MAX_BR, + + /* + * Configure the min and max time BT gains the antenna + * in WLAN / BT slave basic rate + * + * Range: 0 - 255 (ms) + */ + CONF_SG_ACL_BT_SLAVE_MIN_BR, + CONF_SG_ACL_BT_SLAVE_MAX_BR, + + /* + * Configure the min and max time BT gains the antenna + * in WLAN / BT master EDR + * + * Range: 0 - 255 (ms) + */ + CONF_SG_ACL_BT_MASTER_MIN_EDR, + CONF_SG_ACL_BT_MASTER_MAX_EDR, + + /* + * Configure the min and max time BT gains the antenna + * in WLAN / BT slave EDR + * + * Range: 0 - 255 (ms) + */ + CONF_SG_ACL_BT_SLAVE_MIN_EDR, + CONF_SG_ACL_BT_SLAVE_MAX_EDR, + + /* + * The maximum time WLAN can gain the antenna + * in WLAN PSM / BT master/slave BR + * + * Range: 0 - 255 (ms) + */ + CONF_SG_ACL_WLAN_PS_MASTER_BR, + CONF_SG_ACL_WLAN_PS_SLAVE_BR, + + /* + * The maximum time WLAN can gain the antenna + * in WLAN PSM / BT master/slave EDR + * + * Range: 0 - 255 (ms) + */ + CONF_SG_ACL_WLAN_PS_MASTER_EDR, + CONF_SG_ACL_WLAN_PS_SLAVE_EDR, + + /* TODO: explain these values */ + CONF_SG_ACL_WLAN_ACTIVE_MASTER_MIN_BR, + CONF_SG_ACL_WLAN_ACTIVE_MASTER_MAX_BR, + CONF_SG_ACL_WLAN_ACTIVE_SLAVE_MIN_BR, + CONF_SG_ACL_WLAN_ACTIVE_SLAVE_MAX_BR, + CONF_SG_ACL_WLAN_ACTIVE_MASTER_MIN_EDR, + CONF_SG_ACL_WLAN_ACTIVE_MASTER_MAX_EDR, + CONF_SG_ACL_WLAN_ACTIVE_SLAVE_MIN_EDR, + CONF_SG_ACL_WLAN_ACTIVE_SLAVE_MAX_EDR, + + CONF_SG_ACL_ACTIVE_SCAN_WLAN_BR, + CONF_SG_ACL_ACTIVE_SCAN_WLAN_EDR, + CONF_SG_ACL_PASSIVE_SCAN_BT_BR, + CONF_SG_ACL_PASSIVE_SCAN_WLAN_BR, + CONF_SG_ACL_PASSIVE_SCAN_BT_EDR, + CONF_SG_ACL_PASSIVE_SCAN_WLAN_EDR, + + /* + * Compensation percentage of probe requests when scan initiated + * during BT voice/ACL link. + * + * Range: 0 - 255 (%) + */ + CONF_SG_AUTO_SCAN_PROBE_REQ, + + /* + * Compensation percentage of probe requests when active scan initiated + * during BT voice + * + * Range: 0 - 255 (%) + */ + CONF_SG_ACTIVE_SCAN_DURATION_FACTOR_HV3, + + /* + * Compensation percentage of WLAN active scan window if initiated + * during BT A2DP + * + * Range: 0 - 1000 (%) + */ + CONF_SG_ACTIVE_SCAN_DURATION_FACTOR_A2DP, + + /* + * Compensation percentage of WLAN passive scan window if initiated + * during BT A2DP BR + * + * Range: 0 - 1000 (%) + */ + CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_A2DP_BR, + + /* + * Compensation percentage of WLAN passive scan window if initiated + * during BT A2DP EDR + * + * Range: 0 - 1000 (%) + */ + CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_A2DP_EDR, + + /* + * Compensation percentage of WLAN passive scan window if initiated + * during BT voice + * + * Range: 0 - 1000 (%) + */ + CONF_SG_PASSIVE_SCAN_DURATION_FACTOR_HV3, + + /* TODO: explain these values */ + CONF_SG_CONSECUTIVE_HV3_IN_PASSIVE_SCAN, + CONF_SG_BCN_HV3_COLLISION_THRESH_IN_PASSIVE_SCAN, + CONF_SG_TX_RX_PROTECTION_BWIDTH_IN_PASSIVE_SCAN, + + /* + * Defines whether the SG will force WLAN host to enter/exit PSM + * + * Range: 1 - SG can force, 0 - host handles PSM + */ + CONF_SG_STA_FORCE_PS_IN_BT_SCO, + + /* + * Defines antenna configuration (single/dual antenna) + * + * Range: 0 - single antenna, 1 - dual antenna + */ + CONF_SG_ANTENNA_CONFIGURATION, + + /* + * The threshold (percent) of max consecutive beacon misses before + * increasing priority of beacon reception. + * + * Range: 0 - 100 (%) + */ + CONF_SG_BEACON_MISS_PERCENT, + + /* + * Protection time of the DHCP procedure. + * + * Range: 0 - 100000 (ms) + */ + CONF_SG_DHCP_TIME, + + /* + * RX guard time before the beginning of a new BT voice frame during + * which no new WLAN trigger frame is transmitted. + * + * Range: 0 - 100000 (us) + */ + CONF_SG_RXT, + + /* + * TX guard time before the beginning of a new BT voice frame during + * which no new WLAN frame is transmitted. + * + * Range: 0 - 100000 (us) + */ + + CONF_SG_TXT, + + /* + * Enable adaptive RXT/TXT algorithm. If disabled, the host values + * will be utilized. + * + * Range: 0 - disable, 1 - enable + */ + CONF_SG_ADAPTIVE_RXT_TXT, + + /* TODO: explain this value */ + CONF_SG_GENERAL_USAGE_BIT_MAP, + + /* + * Number of consecutive BT voice frames not interrupted by WLAN + * + * Range: 0 - 100 + */ + CONF_SG_HV3_MAX_SERVED, + + /* + * The used WLAN legacy service period during active BT ACL link + * + * Range: 0 - 255 (ms) + */ + CONF_SG_PS_POLL_TIMEOUT, + + /* + * The used WLAN UPSD service period during active BT ACL link + * + * Range: 0 - 255 (ms) + */ + CONF_SG_UPSD_TIMEOUT, + + CONF_SG_CONSECUTIVE_CTS_THRESHOLD, + CONF_SG_STA_RX_WINDOW_AFTER_DTIM, + CONF_SG_STA_CONNECTION_PROTECTION_TIME, + + /* AP params */ + CONF_AP_BEACON_MISS_TX, + CONF_AP_RX_WINDOW_AFTER_BEACON, + CONF_AP_BEACON_WINDOW_INTERVAL, + CONF_AP_CONNECTION_PROTECTION_TIME, + CONF_AP_BT_ACL_VAL_BT_SERVE_TIME, + CONF_AP_BT_ACL_VAL_WL_SERVE_TIME, + + /* CTS Diluting params */ + CONF_SG_CTS_DILUTED_BAD_RX_PACKETS_TH, + CONF_SG_CTS_CHOP_IN_DUAL_ANT_SCO_MASTER, + + CONF_SG_TEMP_PARAM_1, + CONF_SG_TEMP_PARAM_2, + CONF_SG_TEMP_PARAM_3, + CONF_SG_TEMP_PARAM_4, + CONF_SG_TEMP_PARAM_5, + CONF_SG_TEMP_PARAM_6, + CONF_SG_TEMP_PARAM_7, + CONF_SG_TEMP_PARAM_8, + CONF_SG_TEMP_PARAM_9, + CONF_SG_TEMP_PARAM_10, + + CONF_SG_PARAMS_MAX, + CONF_SG_PARAMS_ALL = 0xff +}; + +struct conf_sg_settings { + u32 params[CONF_SG_PARAMS_MAX]; + u8 state; +} __packed; + +enum conf_rx_queue_type { + CONF_RX_QUEUE_TYPE_LOW_PRIORITY, /* All except the high priority */ + CONF_RX_QUEUE_TYPE_HIGH_PRIORITY, /* Management and voice packets */ +}; + +struct conf_rx_settings { + /* + * The maximum amount of time, in TU, before the + * firmware discards the MSDU. + * + * Range: 0 - 0xFFFFFFFF + */ + u32 rx_msdu_life_time; + + /* + * Packet detection threshold in the PHY. + * + * FIXME: details unknown. + */ + u32 packet_detection_threshold; + + /* + * The longest time the STA will wait to receive traffic from the AP + * after a PS-poll has been transmitted. + * + * Range: 0 - 200000 + */ + u16 ps_poll_timeout; + /* + * The longest time the STA will wait to receive traffic from the AP + * after a frame has been sent from an UPSD enabled queue. + * + * Range: 0 - 200000 + */ + u16 upsd_timeout; + + /* + * The number of octets in an MPDU, below which an RTS/CTS + * handshake is not performed. + * + * Range: 0 - 4096 + */ + u16 rts_threshold; + + /* + * The RX Clear Channel Assessment threshold in the PHY + * (the energy threshold). + * + * Range: ENABLE_ENERGY_D == 0x140A + * DISABLE_ENERGY_D == 0xFFEF + */ + u16 rx_cca_threshold; + + /* + * Occupied Rx mem-blocks number which requires interrupting the host + * (0 = no buffering, 0xffff = disabled). + * + * Range: u16 + */ + u16 irq_blk_threshold; + + /* + * Rx packets number which requires interrupting the host + * (0 = no buffering). + * + * Range: u16 + */ + u16 irq_pkt_threshold; + + /* + * Max time in msec the FW may delay RX-Complete interrupt. + * + * Range: 1 - 100 + */ + u16 irq_timeout; + + /* + * The RX queue type. + * + * Range: RX_QUEUE_TYPE_RX_LOW_PRIORITY, RX_QUEUE_TYPE_RX_HIGH_PRIORITY, + */ + u8 queue_type; +} __packed; + +#define CONF_TX_MAX_RATE_CLASSES 10 + +#define CONF_TX_RATE_MASK_UNSPECIFIED 0 +#define CONF_TX_RATE_MASK_BASIC (CONF_HW_BIT_RATE_1MBPS | \ + CONF_HW_BIT_RATE_2MBPS) +#define CONF_TX_RATE_RETRY_LIMIT 10 + +/* basic rates for p2p operations (probe req/resp, etc.) */ +#define CONF_TX_RATE_MASK_BASIC_P2P CONF_HW_BIT_RATE_6MBPS + +/* + * Rates supported for data packets when operating as STA/AP. Note the absence + * of the 22Mbps rate. There is a FW limitation on 12 rates so we must drop + * one. The rate dropped is not mandatory under any operating mode. + */ +#define CONF_TX_ENABLED_RATES (CONF_HW_BIT_RATE_1MBPS | \ + CONF_HW_BIT_RATE_2MBPS | CONF_HW_BIT_RATE_5_5MBPS | \ + CONF_HW_BIT_RATE_6MBPS | CONF_HW_BIT_RATE_9MBPS | \ + CONF_HW_BIT_RATE_11MBPS | CONF_HW_BIT_RATE_12MBPS | \ + CONF_HW_BIT_RATE_18MBPS | CONF_HW_BIT_RATE_24MBPS | \ + CONF_HW_BIT_RATE_36MBPS | CONF_HW_BIT_RATE_48MBPS | \ + CONF_HW_BIT_RATE_54MBPS) + +#define CONF_TX_CCK_RATES (CONF_HW_BIT_RATE_1MBPS | \ + CONF_HW_BIT_RATE_2MBPS | CONF_HW_BIT_RATE_5_5MBPS | \ + CONF_HW_BIT_RATE_11MBPS) + +#define CONF_TX_OFDM_RATES (CONF_HW_BIT_RATE_6MBPS | \ + CONF_HW_BIT_RATE_12MBPS | CONF_HW_BIT_RATE_24MBPS | \ + CONF_HW_BIT_RATE_36MBPS | CONF_HW_BIT_RATE_48MBPS | \ + CONF_HW_BIT_RATE_54MBPS) + +#define CONF_TX_MCS_RATES (CONF_HW_BIT_RATE_MCS_0 | \ + CONF_HW_BIT_RATE_MCS_1 | CONF_HW_BIT_RATE_MCS_2 | \ + CONF_HW_BIT_RATE_MCS_3 | CONF_HW_BIT_RATE_MCS_4 | \ + CONF_HW_BIT_RATE_MCS_5 | CONF_HW_BIT_RATE_MCS_6 | \ + CONF_HW_BIT_RATE_MCS_7) + +#define CONF_TX_MIMO_RATES (CONF_HW_BIT_RATE_MCS_8 | \ + CONF_HW_BIT_RATE_MCS_9 | CONF_HW_BIT_RATE_MCS_10 | \ + CONF_HW_BIT_RATE_MCS_11 | CONF_HW_BIT_RATE_MCS_12 | \ + CONF_HW_BIT_RATE_MCS_13 | CONF_HW_BIT_RATE_MCS_14 | \ + CONF_HW_BIT_RATE_MCS_15) + +/* + * Default rates for management traffic when operating in AP mode. This + * should be configured according to the basic rate set of the AP + */ +#define CONF_TX_AP_DEFAULT_MGMT_RATES (CONF_HW_BIT_RATE_1MBPS | \ + CONF_HW_BIT_RATE_2MBPS | CONF_HW_BIT_RATE_5_5MBPS) + +/* default rates for working as IBSS (11b and OFDM) */ +#define CONF_TX_IBSS_DEFAULT_RATES (CONF_HW_BIT_RATE_1MBPS | \ + CONF_HW_BIT_RATE_2MBPS | CONF_HW_BIT_RATE_5_5MBPS | \ + CONF_HW_BIT_RATE_11MBPS | CONF_TX_OFDM_RATES); + +struct conf_tx_rate_class { + + /* + * The rates enabled for this rate class. + * + * Range: CONF_HW_BIT_RATE_* bit mask + */ + u32 enabled_rates; + + /* + * The dot11 short retry limit used for TX retries. + * + * Range: u8 + */ + u8 short_retry_limit; + + /* + * The dot11 long retry limit used for TX retries. + * + * Range: u8 + */ + u8 long_retry_limit; + + /* + * Flags controlling the attributes of TX transmission. + * + * Range: bit 0: Truncate - when set, FW attempts to send a frame stop + * when the total valid per-rate attempts have + * been exhausted; otherwise transmissions + * will continue at the lowest available rate + * until the appropriate one of the + * short_retry_limit, long_retry_limit, + * dot11_max_transmit_msdu_life_time, or + * max_tx_life_time, is exhausted. + * 1: Preamble Override - indicates if the preamble type + * should be used in TX. + * 2: Preamble Type - the type of the preamble to be used by + * the policy (0 - long preamble, 1 - short preamble. + */ + u8 aflags; +} __packed; + +#define CONF_TX_MAX_AC_COUNT 4 + +/* Slot number setting to start transmission at PIFS interval */ +#define CONF_TX_AIFS_PIFS 1 +/* Slot number setting to start transmission at DIFS interval normal + * DCF access */ +#define CONF_TX_AIFS_DIFS 2 + + +enum conf_tx_ac { + CONF_TX_AC_BE = 0, /* best effort / legacy */ + CONF_TX_AC_BK = 1, /* background */ + CONF_TX_AC_VI = 2, /* video */ + CONF_TX_AC_VO = 3, /* voice */ + CONF_TX_AC_CTS2SELF = 4, /* fictitious AC, follows AC_VO */ + CONF_TX_AC_ANY_TID = 0xff +}; + +struct conf_tx_ac_category { + /* + * The AC class identifier. + * + * Range: enum conf_tx_ac + */ + u8 ac; + + /* + * The contention window minimum size (in slots) for the access + * class. + * + * Range: u8 + */ + u8 cw_min; + + /* + * The contention window maximum size (in slots) for the access + * class. + * + * Range: u8 + */ + u16 cw_max; + + /* + * The AIF value (in slots) for the access class. + * + * Range: u8 + */ + u8 aifsn; + + /* + * The TX Op Limit (in microseconds) for the access class. + * + * Range: u16 + */ + u16 tx_op_limit; +} __packed; + +#define CONF_TX_MAX_TID_COUNT 8 + +/* Allow TX BA on all TIDs but 6,7. These are currently reserved in the FW */ +#define CONF_TX_BA_ENABLED_TID_BITMAP 0x3F + +enum { + CONF_CHANNEL_TYPE_DCF = 0, /* DC/LEGACY*/ + CONF_CHANNEL_TYPE_EDCF = 1, /* EDCA*/ + CONF_CHANNEL_TYPE_HCCA = 2, /* HCCA*/ +}; + +enum { + CONF_PS_SCHEME_LEGACY = 0, + CONF_PS_SCHEME_UPSD_TRIGGER = 1, + CONF_PS_SCHEME_LEGACY_PSPOLL = 2, + CONF_PS_SCHEME_SAPSD = 3, +}; + +enum { + CONF_ACK_POLICY_LEGACY = 0, + CONF_ACK_POLICY_NO_ACK = 1, + CONF_ACK_POLICY_BLOCK = 2, +}; + + +struct conf_tx_tid { + u8 queue_id; + u8 channel_type; + u8 tsid; + u8 ps_scheme; + u8 ack_policy; + u32 apsd_conf[2]; +} __packed; + +struct conf_tx_settings { + /* + * The TX ED value for TELEC Enable/Disable. + * + * Range: 0, 1 + */ + u8 tx_energy_detection; + + /* + * Configuration for rate classes for TX (currently only one + * rate class supported). Used in non-AP mode. + */ + struct conf_tx_rate_class sta_rc_conf; + + /* + * Configuration for access categories for TX rate control. + */ + u8 ac_conf_count; + struct conf_tx_ac_category ac_conf[CONF_TX_MAX_AC_COUNT]; + + /* + * AP-mode - allow this number of TX retries to a station before an + * event is triggered from FW. + * In AP-mode the hlids of unreachable stations are given in the + * "sta_tx_retry_exceeded" member in the event mailbox. + */ + u8 max_tx_retries; + + /* + * AP-mode - after this number of seconds a connected station is + * considered inactive. + */ + u16 ap_aging_period; + + /* + * Configuration for TID parameters. + */ + u8 tid_conf_count; + struct conf_tx_tid tid_conf[CONF_TX_MAX_TID_COUNT]; + + /* + * The TX fragmentation threshold. + * + * Range: u16 + */ + u16 frag_threshold; + + /* + * Max time in msec the FW may delay frame TX-Complete interrupt. + * + * Range: u16 + */ + u16 tx_compl_timeout; + + /* + * Completed TX packet count which requires to issue the TX-Complete + * interrupt. + * + * Range: u16 + */ + u16 tx_compl_threshold; + + /* + * The rate used for control messages and scanning on the 2.4GHz band + * + * Range: CONF_HW_BIT_RATE_* bit mask + */ + u32 basic_rate; + + /* + * The rate used for control messages and scanning on the 5GHz band + * + * Range: CONF_HW_BIT_RATE_* bit mask + */ + u32 basic_rate_5; + + /* + * TX retry limits for templates + */ + u8 tmpl_short_retry_limit; + u8 tmpl_long_retry_limit; + + /* Time in ms for Tx watchdog timer to expire */ + u32 tx_watchdog_timeout; + + /* + * when a slow link has this much packets pending, it becomes a low + * priority link, scheduling-wise + */ + u8 slow_link_thold; + + /* + * when a fast link has this much packets pending, it becomes a low + * priority link, scheduling-wise + */ + u8 fast_link_thold; +} __packed; + +enum { + CONF_WAKE_UP_EVENT_BEACON = 0x01, /* Wake on every Beacon*/ + CONF_WAKE_UP_EVENT_DTIM = 0x02, /* Wake on every DTIM*/ + CONF_WAKE_UP_EVENT_N_DTIM = 0x04, /* Wake every Nth DTIM */ + CONF_WAKE_UP_EVENT_N_BEACONS = 0x08, /* Wake every Nth beacon */ + CONF_WAKE_UP_EVENT_BITS_MASK = 0x0F +}; + +#define CONF_MAX_BCN_FILT_IE_COUNT 32 + +#define CONF_BCN_RULE_PASS_ON_CHANGE BIT(0) +#define CONF_BCN_RULE_PASS_ON_APPEARANCE BIT(1) + +#define CONF_BCN_IE_OUI_LEN 3 +#define CONF_BCN_IE_VER_LEN 2 + +struct conf_bcn_filt_rule { + /* + * IE number to which to associate a rule. + * + * Range: u8 + */ + u8 ie; + + /* + * Rule to associate with the specific ie. + * + * Range: CONF_BCN_RULE_PASS_ON_* + */ + u8 rule; + + /* + * OUI for the vendor specifie IE (221) + */ + u8 oui[CONF_BCN_IE_OUI_LEN]; + + /* + * Type for the vendor specifie IE (221) + */ + u8 type; + + /* + * Version for the vendor specifie IE (221) + */ + u8 version[CONF_BCN_IE_VER_LEN]; +} __packed; + +#define CONF_MAX_RSSI_SNR_TRIGGERS 8 + +enum { + CONF_TRIG_METRIC_RSSI_BEACON = 0, + CONF_TRIG_METRIC_RSSI_DATA, + CONF_TRIG_METRIC_SNR_BEACON, + CONF_TRIG_METRIC_SNR_DATA +}; + +enum { + CONF_TRIG_EVENT_TYPE_LEVEL = 0, + CONF_TRIG_EVENT_TYPE_EDGE +}; + +enum { + CONF_TRIG_EVENT_DIR_LOW = 0, + CONF_TRIG_EVENT_DIR_HIGH, + CONF_TRIG_EVENT_DIR_BIDIR +}; + +struct conf_sig_weights { + + /* + * RSSI from beacons average weight. + * + * Range: u8 + */ + u8 rssi_bcn_avg_weight; + + /* + * RSSI from data average weight. + * + * Range: u8 + */ + u8 rssi_pkt_avg_weight; + + /* + * SNR from beacons average weight. + * + * Range: u8 + */ + u8 snr_bcn_avg_weight; + + /* + * SNR from data average weight. + * + * Range: u8 + */ + u8 snr_pkt_avg_weight; +} __packed; + +enum conf_bcn_filt_mode { + CONF_BCN_FILT_MODE_DISABLED = 0, + CONF_BCN_FILT_MODE_ENABLED = 1 +}; + +enum conf_bet_mode { + CONF_BET_MODE_DISABLE = 0, + CONF_BET_MODE_ENABLE = 1, +}; + +struct conf_conn_settings { + /* + * Firmware wakeup conditions configuration. The host may set only + * one bit. + * + * Range: CONF_WAKE_UP_EVENT_* + */ + u8 wake_up_event; + + /* + * Listen interval for beacons or Dtims. + * + * Range: 0 for beacon and Dtim wakeup + * 1-10 for x Dtims + * 1-255 for x beacons + */ + u8 listen_interval; + + /* + * Firmware wakeup conditions during suspend + * Range: CONF_WAKE_UP_EVENT_* + */ + u8 suspend_wake_up_event; + + /* + * Listen interval during suspend. + * Currently will be in DTIMs (1-10) + * + */ + u8 suspend_listen_interval; + + /* + * Enable or disable the beacon filtering. + * + * Range: CONF_BCN_FILT_MODE_* + */ + u8 bcn_filt_mode; + + /* + * Configure Beacon filter pass-thru rules. + */ + u8 bcn_filt_ie_count; + struct conf_bcn_filt_rule bcn_filt_ie[CONF_MAX_BCN_FILT_IE_COUNT]; + + /* + * The number of consecutive beacons to lose, before the firmware + * becomes out of synch. + * + * Range: u32 + */ + u32 synch_fail_thold; + + /* + * After out-of-synch, the number of TU's to wait without a further + * received beacon (or probe response) before issuing the BSS_EVENT_LOSE + * event. + * + * Range: u32 + */ + u32 bss_lose_timeout; + + /* + * Beacon receive timeout. + * + * Range: u32 + */ + u32 beacon_rx_timeout; + + /* + * Broadcast receive timeout. + * + * Range: u32 + */ + u32 broadcast_timeout; + + /* + * Enable/disable reception of broadcast packets in power save mode + * + * Range: 1 - enable, 0 - disable + */ + u8 rx_broadcast_in_ps; + + /* + * Consecutive PS Poll failures before sending event to driver + * + * Range: u8 + */ + u8 ps_poll_threshold; + + /* + * Configuration of signal average weights. + */ + struct conf_sig_weights sig_weights; + + /* + * Specifies if beacon early termination procedure is enabled or + * disabled. + * + * Range: CONF_BET_MODE_* + */ + u8 bet_enable; + + /* + * Specifies the maximum number of consecutive beacons that may be + * early terminated. After this number is reached at least one full + * beacon must be correctly received in FW before beacon ET + * resumes. + * + * Range 0 - 255 + */ + u8 bet_max_consecutive; + + /* + * Specifies the maximum number of times to try PSM entry if it fails + * (if sending the appropriate null-func message fails.) + * + * Range 0 - 255 + */ + u8 psm_entry_retries; + + /* + * Specifies the maximum number of times to try PSM exit if it fails + * (if sending the appropriate null-func message fails.) + * + * Range 0 - 255 + */ + u8 psm_exit_retries; + + /* + * Specifies the maximum number of times to try transmit the PSM entry + * null-func frame for each PSM entry attempt + * + * Range 0 - 255 + */ + u8 psm_entry_nullfunc_retries; + + /* + * Specifies the dynamic PS timeout in ms that will be used + * by the FW when in AUTO_PS mode + */ + u16 dynamic_ps_timeout; + + /* + * Specifies whether dynamic PS should be disabled and PSM forced. + * This is required for certain WiFi certification tests. + */ + u8 forced_ps; + + /* + * + * Specifies the interval of the connection keep-alive null-func + * frame in ms. + * + * Range: 1000 - 3600000 + */ + u32 keep_alive_interval; + + /* + * Maximum listen interval supported by the driver in units of beacons. + * + * Range: u16 + */ + u8 max_listen_interval; + + /* + * Default sleep authorization for a new STA interface. This determines + * whether we can go to ELP. + */ + u8 sta_sleep_auth; +} __packed; + +enum { + CONF_REF_CLK_19_2_E, + CONF_REF_CLK_26_E, + CONF_REF_CLK_38_4_E, + CONF_REF_CLK_52_E, + CONF_REF_CLK_38_4_M_XTAL, + CONF_REF_CLK_26_M_XTAL, +}; + +enum single_dual_band_enum { + CONF_SINGLE_BAND, + CONF_DUAL_BAND +}; + +#define CONF_RSSI_AND_PROCESS_COMPENSATION_SIZE 15 +#define CONF_NUMBER_OF_SUB_BANDS_5 7 +#define CONF_NUMBER_OF_RATE_GROUPS 6 +#define CONF_NUMBER_OF_CHANNELS_2_4 14 +#define CONF_NUMBER_OF_CHANNELS_5 35 + +struct conf_itrim_settings { + /* enable dco itrim */ + u8 enable; + + /* moderation timeout in microsecs from the last TX */ + u32 timeout; +} __packed; + +enum conf_fast_wakeup { + CONF_FAST_WAKEUP_ENABLE, + CONF_FAST_WAKEUP_DISABLE, +}; + +struct conf_pm_config_settings { + /* + * Host clock settling time + * + * Range: 0 - 30000 us + */ + u32 host_clk_settling_time; + + /* + * Host fast wakeup support + * + * Range: enum conf_fast_wakeup + */ + u8 host_fast_wakeup_support; +} __packed; + +struct conf_roam_trigger_settings { + /* + * The minimum interval between two trigger events. + * + * Range: 0 - 60000 ms + */ + u16 trigger_pacing; + + /* + * The weight for rssi/beacon average calculation + * + * Range: 0 - 255 + */ + u8 avg_weight_rssi_beacon; + + /* + * The weight for rssi/data frame average calculation + * + * Range: 0 - 255 + */ + u8 avg_weight_rssi_data; + + /* + * The weight for snr/beacon average calculation + * + * Range: 0 - 255 + */ + u8 avg_weight_snr_beacon; + + /* + * The weight for snr/data frame average calculation + * + * Range: 0 - 255 + */ + u8 avg_weight_snr_data; +} __packed; + +struct conf_scan_settings { + /* + * The minimum time to wait on each channel for active scans + * This value will be used whenever there's a connected interface. + * + * Range: u32 tu/1000 + */ + u32 min_dwell_time_active; + + /* + * The maximum time to wait on each channel for active scans + * This value will be currently used whenever there's a + * connected interface. It shouldn't exceed 30000 (~30ms) to avoid + * possible interference of voip traffic going on while scanning. + * + * Range: u32 tu/1000 + */ + u32 max_dwell_time_active; + + /* The minimum time to wait on each channel for active scans + * when it's possible to have longer scan dwell times. + * Currently this is used whenever we're idle on all interfaces. + * Longer dwell times improve detection of networks within a + * single scan. + * + * Range: u32 tu/1000 + */ + u32 min_dwell_time_active_long; + + /* The maximum time to wait on each channel for active scans + * when it's possible to have longer scan dwell times. + * See min_dwell_time_active_long + * + * Range: u32 tu/1000 + */ + u32 max_dwell_time_active_long; + + /* time to wait on the channel for passive scans (in TU/1000) */ + u32 dwell_time_passive; + + /* time to wait on the channel for DFS scans (in TU/1000) */ + u32 dwell_time_dfs; + + /* + * Number of probe requests to transmit on each active scan channel + * + * Range: u8 + */ + u16 num_probe_reqs; + + /* + * Scan trigger (split scan) timeout. The FW will split the scan + * operation into slices of the given time and allow the FW to schedule + * other tasks in between. + * + * Range: u32 Microsecs + */ + u32 split_scan_timeout; +} __packed; + +struct conf_sched_scan_settings { + /* + * The base time to wait on the channel for active scans (in TU/1000). + * The minimum dwell time is calculated according to this: + * min_dwell_time = base + num_of_probes_to_be_sent * delta_per_probe + * The maximum dwell time is calculated according to this: + * max_dwell_time = min_dwell_time + max_dwell_time_delta + */ + u32 base_dwell_time; + + /* The delta between the min dwell time and max dwell time for + * active scans (in TU/1000s). The max dwell time is used by the FW once + * traffic is detected on the channel. + */ + u32 max_dwell_time_delta; + + /* Delta added to min dwell time per each probe in 2.4 GHz (TU/1000) */ + u32 dwell_time_delta_per_probe; + + /* Delta added to min dwell time per each probe in 5 GHz (TU/1000) */ + u32 dwell_time_delta_per_probe_5; + + /* time to wait on the channel for passive scans (in TU/1000) */ + u32 dwell_time_passive; + + /* time to wait on the channel for DFS scans (in TU/1000) */ + u32 dwell_time_dfs; + + /* number of probe requests to send on each channel in active scans */ + u8 num_probe_reqs; + + /* RSSI threshold to be used for filtering */ + s8 rssi_threshold; + + /* SNR threshold to be used for filtering */ + s8 snr_threshold; +} __packed; + +struct conf_ht_setting { + u8 rx_ba_win_size; + u8 tx_ba_win_size; + u16 inactivity_timeout; + + /* bitmap of enabled TIDs for TX BA sessions */ + u8 tx_ba_tid_bitmap; +} __packed; + +struct conf_memory_settings { + /* Number of stations supported in IBSS mode */ + u8 num_stations; + + /* Number of ssid profiles used in IBSS mode */ + u8 ssid_profiles; + + /* Number of memory buffers allocated to rx pool */ + u8 rx_block_num; + + /* Minimum number of blocks allocated to tx pool */ + u8 tx_min_block_num; + + /* Disable/Enable dynamic memory */ + u8 dynamic_memory; + + /* + * Minimum required free tx memory blocks in order to assure optimum + * performance + * + * Range: 0-120 + */ + u8 min_req_tx_blocks; + + /* + * Minimum required free rx memory blocks in order to assure optimum + * performance + * + * Range: 0-120 + */ + u8 min_req_rx_blocks; + + /* + * Minimum number of mem blocks (free+used) guaranteed for TX + * + * Range: 0-120 + */ + u8 tx_min; +} __packed; + +struct conf_fm_coex { + u8 enable; + u8 swallow_period; + u8 n_divider_fref_set_1; + u8 n_divider_fref_set_2; + u16 m_divider_fref_set_1; + u16 m_divider_fref_set_2; + u32 coex_pll_stabilization_time; + u16 ldo_stabilization_time; + u8 fm_disturbed_band_margin; + u8 swallow_clk_diff; +} __packed; + +struct conf_rx_streaming_settings { + /* + * RX Streaming duration (in msec) from last tx/rx + * + * Range: u32 + */ + u32 duration; + + /* + * Bitmap of tids to be polled during RX streaming. + * (Note: it doesn't look like it really matters) + * + * Range: 0x1-0xff + */ + u8 queues; + + /* + * RX Streaming interval. + * (Note:this value is also used as the rx streaming timeout) + * Range: 0 (disabled), 10 - 100 + */ + u8 interval; + + /* + * enable rx streaming also when there is no coex activity + */ + u8 always; +} __packed; + +struct conf_fwlog { + /* Continuous or on-demand */ + u8 mode; + + /* + * Number of memory blocks dedicated for the FW logger + * + * Range: 1-3, or 0 to disable the FW logger + */ + u8 mem_blocks; + + /* Minimum log level threshold */ + u8 severity; + + /* Include/exclude timestamps from the log messages */ + u8 timestamp; + + /* See enum wl1271_fwlogger_output */ + u8 output; + + /* Regulates the frequency of log messages */ + u8 threshold; +} __packed; + +#define ACX_RATE_MGMT_NUM_OF_RATES 13 +struct conf_rate_policy_settings { + u16 rate_retry_score; + u16 per_add; + u16 per_th1; + u16 per_th2; + u16 max_per; + u8 inverse_curiosity_factor; + u8 tx_fail_low_th; + u8 tx_fail_high_th; + u8 per_alpha_shift; + u8 per_add_shift; + u8 per_beta1_shift; + u8 per_beta2_shift; + u8 rate_check_up; + u8 rate_check_down; + u8 rate_retry_policy[ACX_RATE_MGMT_NUM_OF_RATES]; +} __packed; + +struct conf_hangover_settings { + u32 recover_time; + u8 hangover_period; + u8 dynamic_mode; + u8 early_termination_mode; + u8 max_period; + u8 min_period; + u8 increase_delta; + u8 decrease_delta; + u8 quiet_time; + u8 increase_time; + u8 window_size; +} __packed; + +struct conf_recovery_settings { + /* BUG() on fw recovery */ + u8 bug_on_recovery; + + /* Prevent HW recovery. FW will remain stuck. */ + u8 no_recovery; +} __packed; + +/* + * The conf version consists of 4 bytes. The two MSB are the wlcore + * version, the two LSB are the lower driver's private conf + * version. + */ +#define WLCORE_CONF_VERSION (0x0005 << 16) +#define WLCORE_CONF_MASK 0xffff0000 +#define WLCORE_CONF_SIZE (sizeof(struct wlcore_conf_header) + \ + sizeof(struct wlcore_conf)) + +struct wlcore_conf_header { + __le32 magic; + __le32 version; + __le32 checksum; +} __packed; + +struct wlcore_conf { + struct conf_sg_settings sg; + struct conf_rx_settings rx; + struct conf_tx_settings tx; + struct conf_conn_settings conn; + struct conf_itrim_settings itrim; + struct conf_pm_config_settings pm_config; + struct conf_roam_trigger_settings roam_trigger; + struct conf_scan_settings scan; + struct conf_sched_scan_settings sched_scan; + struct conf_ht_setting ht; + struct conf_memory_settings mem; + struct conf_fm_coex fm_coex; + struct conf_rx_streaming_settings rx_streaming; + struct conf_fwlog fwlog; + struct conf_rate_policy_settings rate; + struct conf_hangover_settings hangover; + struct conf_recovery_settings recovery; +} __packed; + +struct wlcore_conf_file { + struct wlcore_conf_header header; + struct wlcore_conf core; + u8 priv[0]; +} __packed; + +#endif diff --git a/drivers/net/wireless/ti/wlcore/debug.h b/drivers/net/wireless/ti/wlcore/debug.h new file mode 100644 index 0000000..0420bd4 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/debug.h @@ -0,0 +1,112 @@ +/* + * This file is part of wl12xx + * + * Copyright (C) 2011 Texas Instruments. All rights reserved. + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +#include +#include + +#define DRIVER_NAME "wlcore" +#define DRIVER_PREFIX DRIVER_NAME ": " + +enum { + DEBUG_NONE = 0, + DEBUG_IRQ = BIT(0), + DEBUG_SPI = BIT(1), + DEBUG_BOOT = BIT(2), + DEBUG_MAILBOX = BIT(3), + DEBUG_TESTMODE = BIT(4), + DEBUG_EVENT = BIT(5), + DEBUG_TX = BIT(6), + DEBUG_RX = BIT(7), + DEBUG_SCAN = BIT(8), + DEBUG_CRYPT = BIT(9), + DEBUG_PSM = BIT(10), + DEBUG_MAC80211 = BIT(11), + DEBUG_CMD = BIT(12), + DEBUG_ACX = BIT(13), + DEBUG_SDIO = BIT(14), + DEBUG_FILTERS = BIT(15), + DEBUG_ADHOC = BIT(16), + DEBUG_AP = BIT(17), + DEBUG_PROBE = BIT(18), + DEBUG_IO = BIT(19), + DEBUG_MASTER = (DEBUG_ADHOC | DEBUG_AP), + DEBUG_ALL = ~0, +}; + +extern u32 wl12xx_debug_level; + +#define DEBUG_DUMP_LIMIT 1024 + +#define wl1271_error(fmt, arg...) \ + pr_err(DRIVER_PREFIX "ERROR " fmt "\n", ##arg) + +#define wl1271_warning(fmt, arg...) \ + pr_warning(DRIVER_PREFIX "WARNING " fmt "\n", ##arg) + +#define wl1271_notice(fmt, arg...) \ + pr_info(DRIVER_PREFIX fmt "\n", ##arg) + +#define wl1271_info(fmt, arg...) \ + pr_info(DRIVER_PREFIX fmt "\n", ##arg) + +/* define the debug macro differently if dynamic debug is supported */ +#if defined(CONFIG_DYNAMIC_DEBUG) +#define wl1271_debug(level, fmt, arg...) \ + do { \ + if (unlikely(level & wl12xx_debug_level)) \ + dynamic_pr_debug(DRIVER_PREFIX fmt "\n", ##arg); \ + } while (0) +#else +#define wl1271_debug(level, fmt, arg...) \ + do { \ + if (unlikely(level & wl12xx_debug_level)) \ + printk(KERN_DEBUG pr_fmt(DRIVER_PREFIX fmt "\n"), \ + ##arg); \ + } while (0) +#endif + +#define wl1271_dump(level, prefix, buf, len) \ + do { \ + if (level & wl12xx_debug_level) \ + print_hex_dump_debug(DRIVER_PREFIX prefix, \ + DUMP_PREFIX_OFFSET, 16, 1, \ + buf, \ + min_t(size_t, len, DEBUG_DUMP_LIMIT), \ + 0); \ + } while (0) + +#define wl1271_dump_ascii(level, prefix, buf, len) \ + do { \ + if (level & wl12xx_debug_level) \ + print_hex_dump_debug(DRIVER_PREFIX prefix, \ + DUMP_PREFIX_OFFSET, 16, 1, \ + buf, \ + min_t(size_t, len, DEBUG_DUMP_LIMIT), \ + true); \ + } while (0) + +#endif /* __DEBUG_H__ */ diff --git a/drivers/net/wireless/ti/wlcore/debugfs.c b/drivers/net/wireless/ti/wlcore/debugfs.c new file mode 100644 index 0000000..c3e1f79 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/debugfs.c @@ -0,0 +1,1330 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "debugfs.h" + +#include +#include +#include + +#include "wlcore.h" +#include "debug.h" +#include "acx.h" +#include "ps.h" +#include "io.h" +#include "tx.h" +#include "hw_ops.h" + +/* ms */ +#define WL1271_DEBUGFS_STATS_LIFETIME 1000 + +#define WLCORE_MAX_BLOCK_SIZE ((size_t)(4*PAGE_SIZE)) + +/* debugfs macros idea from mac80211 */ +int wl1271_format_buffer(char __user *userbuf, size_t count, + loff_t *ppos, char *fmt, ...) +{ + va_list args; + char buf[DEBUGFS_FORMAT_BUFFER_SIZE]; + int res; + + va_start(args, fmt); + res = vscnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + return simple_read_from_buffer(userbuf, count, ppos, buf, res); +} +EXPORT_SYMBOL_GPL(wl1271_format_buffer); + +void wl1271_debugfs_update_stats(struct wl1271 *wl) +{ + int ret; + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + if (!wl->plt && + time_after(jiffies, wl->stats.fw_stats_update + + msecs_to_jiffies(WL1271_DEBUGFS_STATS_LIFETIME))) { + wl1271_acx_statistics(wl, wl->stats.fw_stats); + wl->stats.fw_stats_update = jiffies; + } + + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); +} +EXPORT_SYMBOL_GPL(wl1271_debugfs_update_stats); + +DEBUGFS_READONLY_FILE(retry_count, "%u", wl->stats.retry_count); +DEBUGFS_READONLY_FILE(excessive_retries, "%u", + wl->stats.excessive_retries); + +static ssize_t tx_queue_len_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + u32 queue_len; + char buf[20]; + int res; + + queue_len = wl1271_tx_total_queue_count(wl); + + res = scnprintf(buf, sizeof(buf), "%u\n", queue_len); + return simple_read_from_buffer(userbuf, count, ppos, buf, res); +} + +static const struct file_operations tx_queue_len_ops = { + .read = tx_queue_len_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static void chip_op_handler(struct wl1271 *wl, unsigned long value, + void *arg) +{ + int ret; + int (*chip_op) (struct wl1271 *wl); + + if (!arg) { + wl1271_warning("debugfs chip_op_handler with no callback"); + return; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + return; + + chip_op = arg; + chip_op(wl); + + wl1271_ps_elp_sleep(wl); +} + + +static inline void no_write_handler(struct wl1271 *wl, + unsigned long value, + unsigned long param) +{ +} + +#define WL12XX_CONF_DEBUGFS(param, conf_sub_struct, \ + min_val, max_val, write_handler_locked, \ + write_handler_arg) \ + static ssize_t param##_read(struct file *file, \ + char __user *user_buf, \ + size_t count, loff_t *ppos) \ + { \ + struct wl1271 *wl = file->private_data; \ + return wl1271_format_buffer(user_buf, count, \ + ppos, "%d\n", \ + wl->conf.conf_sub_struct.param); \ + } \ + \ + static ssize_t param##_write(struct file *file, \ + const char __user *user_buf, \ + size_t count, loff_t *ppos) \ + { \ + struct wl1271 *wl = file->private_data; \ + unsigned long value; \ + int ret; \ + \ + ret = kstrtoul_from_user(user_buf, count, 10, &value); \ + if (ret < 0) { \ + wl1271_warning("illegal value for " #param); \ + return -EINVAL; \ + } \ + \ + if (value < min_val || value > max_val) { \ + wl1271_warning(#param " is not in valid range"); \ + return -ERANGE; \ + } \ + \ + mutex_lock(&wl->mutex); \ + wl->conf.conf_sub_struct.param = value; \ + \ + write_handler_locked(wl, value, write_handler_arg); \ + \ + mutex_unlock(&wl->mutex); \ + return count; \ + } \ + \ + static const struct file_operations param##_ops = { \ + .read = param##_read, \ + .write = param##_write, \ + .open = simple_open, \ + .llseek = default_llseek, \ + }; + +WL12XX_CONF_DEBUGFS(irq_pkt_threshold, rx, 0, 65535, + chip_op_handler, wl1271_acx_init_rx_interrupt) +WL12XX_CONF_DEBUGFS(irq_blk_threshold, rx, 0, 65535, + chip_op_handler, wl1271_acx_init_rx_interrupt) +WL12XX_CONF_DEBUGFS(irq_timeout, rx, 0, 100, + chip_op_handler, wl1271_acx_init_rx_interrupt) + +static ssize_t gpio_power_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + bool state = test_bit(WL1271_FLAG_GPIO_POWER, &wl->flags); + + int res; + char buf[10]; + + res = scnprintf(buf, sizeof(buf), "%d\n", state); + + return simple_read_from_buffer(user_buf, count, ppos, buf, res); +} + +static ssize_t gpio_power_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + unsigned long value; + int ret; + + ret = kstrtoul_from_user(user_buf, count, 10, &value); + if (ret < 0) { + wl1271_warning("illegal value in gpio_power"); + return -EINVAL; + } + + mutex_lock(&wl->mutex); + + if (value) + wl1271_power_on(wl); + else + wl1271_power_off(wl); + + mutex_unlock(&wl->mutex); + return count; +} + +static const struct file_operations gpio_power_ops = { + .read = gpio_power_read, + .write = gpio_power_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t start_recovery_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + + mutex_lock(&wl->mutex); + wl12xx_queue_recovery_work(wl); + mutex_unlock(&wl->mutex); + + return count; +} + +static const struct file_operations start_recovery_ops = { + .write = start_recovery_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t dynamic_ps_timeout_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + + return wl1271_format_buffer(user_buf, count, + ppos, "%d\n", + wl->conf.conn.dynamic_ps_timeout); +} + +static ssize_t dynamic_ps_timeout_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + struct wl12xx_vif *wlvif; + unsigned long value; + int ret; + + ret = kstrtoul_from_user(user_buf, count, 10, &value); + if (ret < 0) { + wl1271_warning("illegal value in dynamic_ps"); + return -EINVAL; + } + + if (value < 1 || value > 65535) { + wl1271_warning("dyanmic_ps_timeout is not in valid range"); + return -ERANGE; + } + + mutex_lock(&wl->mutex); + + wl->conf.conn.dynamic_ps_timeout = value; + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + /* In case we're already in PSM, trigger it again to set new timeout + * immediately without waiting for re-association + */ + + wl12xx_for_each_wlvif_sta(wl, wlvif) { + if (test_bit(WLVIF_FLAG_IN_PS, &wlvif->flags)) + wl1271_ps_set_mode(wl, wlvif, STATION_AUTO_PS_MODE); + } + + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + return count; +} + +static const struct file_operations dynamic_ps_timeout_ops = { + .read = dynamic_ps_timeout_read, + .write = dynamic_ps_timeout_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t forced_ps_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + + return wl1271_format_buffer(user_buf, count, + ppos, "%d\n", + wl->conf.conn.forced_ps); +} + +static ssize_t forced_ps_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + struct wl12xx_vif *wlvif; + unsigned long value; + int ret, ps_mode; + + ret = kstrtoul_from_user(user_buf, count, 10, &value); + if (ret < 0) { + wl1271_warning("illegal value in forced_ps"); + return -EINVAL; + } + + if (value != 1 && value != 0) { + wl1271_warning("forced_ps should be either 0 or 1"); + return -ERANGE; + } + + mutex_lock(&wl->mutex); + + if (wl->conf.conn.forced_ps == value) + goto out; + + wl->conf.conn.forced_ps = value; + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + /* In case we're already in PSM, trigger it again to switch mode + * immediately without waiting for re-association + */ + + ps_mode = value ? STATION_POWER_SAVE_MODE : STATION_AUTO_PS_MODE; + + wl12xx_for_each_wlvif_sta(wl, wlvif) { + if (test_bit(WLVIF_FLAG_IN_PS, &wlvif->flags)) + wl1271_ps_set_mode(wl, wlvif, ps_mode); + } + + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + return count; +} + +static const struct file_operations forced_ps_ops = { + .read = forced_ps_read, + .write = forced_ps_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t split_scan_timeout_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + + return wl1271_format_buffer(user_buf, count, + ppos, "%d\n", + wl->conf.scan.split_scan_timeout / 1000); +} + +static ssize_t split_scan_timeout_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + unsigned long value; + int ret; + + ret = kstrtoul_from_user(user_buf, count, 10, &value); + if (ret < 0) { + wl1271_warning("illegal value in split_scan_timeout"); + return -EINVAL; + } + + if (value == 0) + wl1271_info("split scan will be disabled"); + + mutex_lock(&wl->mutex); + + wl->conf.scan.split_scan_timeout = value * 1000; + + mutex_unlock(&wl->mutex); + return count; +} + +static const struct file_operations split_scan_timeout_ops = { + .read = split_scan_timeout_read, + .write = split_scan_timeout_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t driver_state_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + int res = 0; + ssize_t ret; + char *buf; + +#define DRIVER_STATE_BUF_LEN 1024 + + buf = kmalloc(DRIVER_STATE_BUF_LEN, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + mutex_lock(&wl->mutex); + +#define DRIVER_STATE_PRINT(x, fmt) \ + (res += scnprintf(buf + res, DRIVER_STATE_BUF_LEN - res,\ + #x " = " fmt "\n", wl->x)) + +#define DRIVER_STATE_PRINT_LONG(x) DRIVER_STATE_PRINT(x, "%ld") +#define DRIVER_STATE_PRINT_INT(x) DRIVER_STATE_PRINT(x, "%d") +#define DRIVER_STATE_PRINT_STR(x) DRIVER_STATE_PRINT(x, "%s") +#define DRIVER_STATE_PRINT_LHEX(x) DRIVER_STATE_PRINT(x, "0x%lx") +#define DRIVER_STATE_PRINT_HEX(x) DRIVER_STATE_PRINT(x, "0x%x") + + DRIVER_STATE_PRINT_INT(tx_blocks_available); + DRIVER_STATE_PRINT_INT(tx_allocated_blocks); + DRIVER_STATE_PRINT_INT(tx_allocated_pkts[0]); + DRIVER_STATE_PRINT_INT(tx_allocated_pkts[1]); + DRIVER_STATE_PRINT_INT(tx_allocated_pkts[2]); + DRIVER_STATE_PRINT_INT(tx_allocated_pkts[3]); + DRIVER_STATE_PRINT_INT(tx_frames_cnt); + DRIVER_STATE_PRINT_LHEX(tx_frames_map[0]); + DRIVER_STATE_PRINT_INT(tx_queue_count[0]); + DRIVER_STATE_PRINT_INT(tx_queue_count[1]); + DRIVER_STATE_PRINT_INT(tx_queue_count[2]); + DRIVER_STATE_PRINT_INT(tx_queue_count[3]); + DRIVER_STATE_PRINT_INT(tx_packets_count); + DRIVER_STATE_PRINT_INT(tx_results_count); + DRIVER_STATE_PRINT_LHEX(flags); + DRIVER_STATE_PRINT_INT(tx_blocks_freed); + DRIVER_STATE_PRINT_INT(rx_counter); + DRIVER_STATE_PRINT_INT(state); + DRIVER_STATE_PRINT_INT(channel); + DRIVER_STATE_PRINT_INT(band); + DRIVER_STATE_PRINT_INT(power_level); + DRIVER_STATE_PRINT_INT(sg_enabled); + DRIVER_STATE_PRINT_INT(enable_11a); + DRIVER_STATE_PRINT_INT(noise); + DRIVER_STATE_PRINT_HEX(ap_fw_ps_map); + DRIVER_STATE_PRINT_LHEX(ap_ps_map); + DRIVER_STATE_PRINT_HEX(quirks); + DRIVER_STATE_PRINT_HEX(irq); + /* TODO: ref_clock and tcxo_clock were moved to wl12xx priv */ + DRIVER_STATE_PRINT_HEX(hw_pg_ver); + DRIVER_STATE_PRINT_HEX(platform_quirks); + DRIVER_STATE_PRINT_HEX(chip.id); + DRIVER_STATE_PRINT_STR(chip.fw_ver_str); + DRIVER_STATE_PRINT_STR(chip.phy_fw_ver_str); + DRIVER_STATE_PRINT_INT(recovery_count); + +#undef DRIVER_STATE_PRINT_INT +#undef DRIVER_STATE_PRINT_LONG +#undef DRIVER_STATE_PRINT_HEX +#undef DRIVER_STATE_PRINT_LHEX +#undef DRIVER_STATE_PRINT_STR +#undef DRIVER_STATE_PRINT +#undef DRIVER_STATE_BUF_LEN + + mutex_unlock(&wl->mutex); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, res); + kfree(buf); + return ret; +} + +static const struct file_operations driver_state_ops = { + .read = driver_state_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t vifs_state_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + struct wl12xx_vif *wlvif; + int ret, res = 0; + const int buf_size = 4096; + char *buf; + char tmp_buf[64]; + + buf = kzalloc(buf_size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + mutex_lock(&wl->mutex); + +#define VIF_STATE_PRINT(x, fmt) \ + (res += scnprintf(buf + res, buf_size - res, \ + #x " = " fmt "\n", wlvif->x)) + +#define VIF_STATE_PRINT_LONG(x) VIF_STATE_PRINT(x, "%ld") +#define VIF_STATE_PRINT_INT(x) VIF_STATE_PRINT(x, "%d") +#define VIF_STATE_PRINT_STR(x) VIF_STATE_PRINT(x, "%s") +#define VIF_STATE_PRINT_LHEX(x) VIF_STATE_PRINT(x, "0x%lx") +#define VIF_STATE_PRINT_LLHEX(x) VIF_STATE_PRINT(x, "0x%llx") +#define VIF_STATE_PRINT_HEX(x) VIF_STATE_PRINT(x, "0x%x") + +#define VIF_STATE_PRINT_NSTR(x, len) \ + do { \ + memset(tmp_buf, 0, sizeof(tmp_buf)); \ + memcpy(tmp_buf, wlvif->x, \ + min_t(u8, len, sizeof(tmp_buf) - 1)); \ + res += scnprintf(buf + res, buf_size - res, \ + #x " = %s\n", tmp_buf); \ + } while (0) + + wl12xx_for_each_wlvif(wl, wlvif) { + VIF_STATE_PRINT_INT(role_id); + VIF_STATE_PRINT_INT(bss_type); + VIF_STATE_PRINT_LHEX(flags); + VIF_STATE_PRINT_INT(p2p); + VIF_STATE_PRINT_INT(dev_role_id); + VIF_STATE_PRINT_INT(dev_hlid); + + if (wlvif->bss_type == BSS_TYPE_STA_BSS || + wlvif->bss_type == BSS_TYPE_IBSS) { + VIF_STATE_PRINT_INT(sta.hlid); + VIF_STATE_PRINT_INT(sta.basic_rate_idx); + VIF_STATE_PRINT_INT(sta.ap_rate_idx); + VIF_STATE_PRINT_INT(sta.p2p_rate_idx); + VIF_STATE_PRINT_INT(sta.qos); + } else { + VIF_STATE_PRINT_INT(ap.global_hlid); + VIF_STATE_PRINT_INT(ap.bcast_hlid); + VIF_STATE_PRINT_LHEX(ap.sta_hlid_map[0]); + VIF_STATE_PRINT_INT(ap.mgmt_rate_idx); + VIF_STATE_PRINT_INT(ap.bcast_rate_idx); + VIF_STATE_PRINT_INT(ap.ucast_rate_idx[0]); + VIF_STATE_PRINT_INT(ap.ucast_rate_idx[1]); + VIF_STATE_PRINT_INT(ap.ucast_rate_idx[2]); + VIF_STATE_PRINT_INT(ap.ucast_rate_idx[3]); + } + VIF_STATE_PRINT_INT(last_tx_hlid); + VIF_STATE_PRINT_INT(tx_queue_count[0]); + VIF_STATE_PRINT_INT(tx_queue_count[1]); + VIF_STATE_PRINT_INT(tx_queue_count[2]); + VIF_STATE_PRINT_INT(tx_queue_count[3]); + VIF_STATE_PRINT_LHEX(links_map[0]); + VIF_STATE_PRINT_NSTR(ssid, wlvif->ssid_len); + VIF_STATE_PRINT_INT(band); + VIF_STATE_PRINT_INT(channel); + VIF_STATE_PRINT_HEX(bitrate_masks[0]); + VIF_STATE_PRINT_HEX(bitrate_masks[1]); + VIF_STATE_PRINT_HEX(basic_rate_set); + VIF_STATE_PRINT_HEX(basic_rate); + VIF_STATE_PRINT_HEX(rate_set); + VIF_STATE_PRINT_INT(beacon_int); + VIF_STATE_PRINT_INT(default_key); + VIF_STATE_PRINT_INT(aid); + VIF_STATE_PRINT_INT(psm_entry_retry); + VIF_STATE_PRINT_INT(power_level); + VIF_STATE_PRINT_INT(rssi_thold); + VIF_STATE_PRINT_INT(last_rssi_event); + VIF_STATE_PRINT_INT(ba_support); + VIF_STATE_PRINT_INT(ba_allowed); + VIF_STATE_PRINT_LLHEX(total_freed_pkts); + } + +#undef VIF_STATE_PRINT_INT +#undef VIF_STATE_PRINT_LONG +#undef VIF_STATE_PRINT_HEX +#undef VIF_STATE_PRINT_LHEX +#undef VIF_STATE_PRINT_LLHEX +#undef VIF_STATE_PRINT_STR +#undef VIF_STATE_PRINT_NSTR +#undef VIF_STATE_PRINT + + mutex_unlock(&wl->mutex); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, res); + kfree(buf); + return ret; +} + +static const struct file_operations vifs_state_ops = { + .read = vifs_state_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t dtim_interval_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + u8 value; + + if (wl->conf.conn.wake_up_event == CONF_WAKE_UP_EVENT_DTIM || + wl->conf.conn.wake_up_event == CONF_WAKE_UP_EVENT_N_DTIM) + value = wl->conf.conn.listen_interval; + else + value = 0; + + return wl1271_format_buffer(user_buf, count, ppos, "%d\n", value); +} + +static ssize_t dtim_interval_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + unsigned long value; + int ret; + + ret = kstrtoul_from_user(user_buf, count, 10, &value); + if (ret < 0) { + wl1271_warning("illegal value for dtim_interval"); + return -EINVAL; + } + + if (value < 1 || value > 10) { + wl1271_warning("dtim value is not in valid range"); + return -ERANGE; + } + + mutex_lock(&wl->mutex); + + wl->conf.conn.listen_interval = value; + /* for some reason there are different event types for 1 and >1 */ + if (value == 1) + wl->conf.conn.wake_up_event = CONF_WAKE_UP_EVENT_DTIM; + else + wl->conf.conn.wake_up_event = CONF_WAKE_UP_EVENT_N_DTIM; + + /* + * we don't reconfigure ACX_WAKE_UP_CONDITIONS now, so it will only + * take effect on the next time we enter psm. + */ + mutex_unlock(&wl->mutex); + return count; +} + +static const struct file_operations dtim_interval_ops = { + .read = dtim_interval_read, + .write = dtim_interval_write, + .open = simple_open, + .llseek = default_llseek, +}; + + + +static ssize_t suspend_dtim_interval_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + u8 value; + + if (wl->conf.conn.suspend_wake_up_event == CONF_WAKE_UP_EVENT_DTIM || + wl->conf.conn.suspend_wake_up_event == CONF_WAKE_UP_EVENT_N_DTIM) + value = wl->conf.conn.suspend_listen_interval; + else + value = 0; + + return wl1271_format_buffer(user_buf, count, ppos, "%d\n", value); +} + +static ssize_t suspend_dtim_interval_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + unsigned long value; + int ret; + + ret = kstrtoul_from_user(user_buf, count, 10, &value); + if (ret < 0) { + wl1271_warning("illegal value for suspend_dtim_interval"); + return -EINVAL; + } + + if (value < 1 || value > 10) { + wl1271_warning("suspend_dtim value is not in valid range"); + return -ERANGE; + } + + mutex_lock(&wl->mutex); + + wl->conf.conn.suspend_listen_interval = value; + /* for some reason there are different event types for 1 and >1 */ + if (value == 1) + wl->conf.conn.suspend_wake_up_event = CONF_WAKE_UP_EVENT_DTIM; + else + wl->conf.conn.suspend_wake_up_event = CONF_WAKE_UP_EVENT_N_DTIM; + + mutex_unlock(&wl->mutex); + return count; +} + + +static const struct file_operations suspend_dtim_interval_ops = { + .read = suspend_dtim_interval_read, + .write = suspend_dtim_interval_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t beacon_interval_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + u8 value; + + if (wl->conf.conn.wake_up_event == CONF_WAKE_UP_EVENT_BEACON || + wl->conf.conn.wake_up_event == CONF_WAKE_UP_EVENT_N_BEACONS) + value = wl->conf.conn.listen_interval; + else + value = 0; + + return wl1271_format_buffer(user_buf, count, ppos, "%d\n", value); +} + +static ssize_t beacon_interval_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + unsigned long value; + int ret; + + ret = kstrtoul_from_user(user_buf, count, 10, &value); + if (ret < 0) { + wl1271_warning("illegal value for beacon_interval"); + return -EINVAL; + } + + if (value < 1 || value > 255) { + wl1271_warning("beacon interval value is not in valid range"); + return -ERANGE; + } + + mutex_lock(&wl->mutex); + + wl->conf.conn.listen_interval = value; + /* for some reason there are different event types for 1 and >1 */ + if (value == 1) + wl->conf.conn.wake_up_event = CONF_WAKE_UP_EVENT_BEACON; + else + wl->conf.conn.wake_up_event = CONF_WAKE_UP_EVENT_N_BEACONS; + + /* + * we don't reconfigure ACX_WAKE_UP_CONDITIONS now, so it will only + * take effect on the next time we enter psm. + */ + mutex_unlock(&wl->mutex); + return count; +} + +static const struct file_operations beacon_interval_ops = { + .read = beacon_interval_read, + .write = beacon_interval_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t rx_streaming_interval_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + struct wl12xx_vif *wlvif; + unsigned long value; + int ret; + + ret = kstrtoul_from_user(user_buf, count, 10, &value); + if (ret < 0) { + wl1271_warning("illegal value in rx_streaming_interval!"); + return -EINVAL; + } + + /* valid values: 0, 10-100 */ + if (value && (value < 10 || value > 100)) { + wl1271_warning("value is not in range!"); + return -ERANGE; + } + + mutex_lock(&wl->mutex); + + wl->conf.rx_streaming.interval = value; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl12xx_for_each_wlvif_sta(wl, wlvif) { + wl1271_recalc_rx_streaming(wl, wlvif); + } + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); + return count; +} + +static ssize_t rx_streaming_interval_read(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + return wl1271_format_buffer(userbuf, count, ppos, + "%d\n", wl->conf.rx_streaming.interval); +} + +static const struct file_operations rx_streaming_interval_ops = { + .read = rx_streaming_interval_read, + .write = rx_streaming_interval_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t rx_streaming_always_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + struct wl12xx_vif *wlvif; + unsigned long value; + int ret; + + ret = kstrtoul_from_user(user_buf, count, 10, &value); + if (ret < 0) { + wl1271_warning("illegal value in rx_streaming_write!"); + return -EINVAL; + } + + /* valid values: 0, 10-100 */ + if (!(value == 0 || value == 1)) { + wl1271_warning("value is not in valid!"); + return -EINVAL; + } + + mutex_lock(&wl->mutex); + + wl->conf.rx_streaming.always = value; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl12xx_for_each_wlvif_sta(wl, wlvif) { + wl1271_recalc_rx_streaming(wl, wlvif); + } + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); + return count; +} + +static ssize_t rx_streaming_always_read(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + return wl1271_format_buffer(userbuf, count, ppos, + "%d\n", wl->conf.rx_streaming.always); +} + +static const struct file_operations rx_streaming_always_ops = { + .read = rx_streaming_always_read, + .write = rx_streaming_always_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t beacon_filtering_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + struct wl12xx_vif *wlvif; + char buf[10]; + size_t len; + unsigned long value; + int ret; + + len = min(count, sizeof(buf) - 1); + if (copy_from_user(buf, user_buf, len)) + return -EFAULT; + buf[len] = '\0'; + + ret = kstrtoul(buf, 0, &value); + if (ret < 0) { + wl1271_warning("illegal value for beacon_filtering!"); + return -EINVAL; + } + + mutex_lock(&wl->mutex); + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl12xx_for_each_wlvif(wl, wlvif) { + ret = wl1271_acx_beacon_filter_opt(wl, wlvif, !!value); + } + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); + return count; +} + +static const struct file_operations beacon_filtering_ops = { + .write = beacon_filtering_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t fw_stats_raw_read(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + + wl1271_debugfs_update_stats(wl); + + return simple_read_from_buffer(userbuf, count, ppos, + wl->stats.fw_stats, + wl->stats.fw_stats_len); +} + +static const struct file_operations fw_stats_raw_ops = { + .read = fw_stats_raw_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t sleep_auth_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + + return wl1271_format_buffer(user_buf, count, + ppos, "%d\n", + wl->sleep_auth); +} + +static ssize_t sleep_auth_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + unsigned long value; + int ret; + + ret = kstrtoul_from_user(user_buf, count, 0, &value); + if (ret < 0) { + wl1271_warning("illegal value in sleep_auth"); + return -EINVAL; + } + + if (value > WL1271_PSM_MAX) { + wl1271_warning("sleep_auth must be between 0 and %d", + WL1271_PSM_MAX); + return -ERANGE; + } + + mutex_lock(&wl->mutex); + + wl->conf.conn.sta_sleep_auth = value; + + if (unlikely(wl->state != WLCORE_STATE_ON)) { + /* this will show up on "read" in case we are off */ + wl->sleep_auth = value; + goto out; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl1271_acx_sleep_auth(wl, value); + if (ret < 0) + goto out_sleep; + +out_sleep: + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); + return count; +} + +static const struct file_operations sleep_auth_ops = { + .read = sleep_auth_read, + .write = sleep_auth_write, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t dev_mem_read(struct file *file, + char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + struct wlcore_partition_set part, old_part; + size_t bytes = count; + int ret; + char *buf; + + /* only requests of dword-aligned size and offset are supported */ + if (bytes % 4) + return -EINVAL; + + if (*ppos % 4) + return -EINVAL; + + /* function should return in reasonable time */ + bytes = min(bytes, WLCORE_MAX_BLOCK_SIZE); + + if (bytes == 0) + return -EINVAL; + + memset(&part, 0, sizeof(part)); + part.mem.start = file->f_pos; + part.mem.size = bytes; + + buf = kmalloc(bytes, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state == WLCORE_STATE_OFF)) { + ret = -EFAULT; + goto skip_read; + } + + /* + * Don't fail if elp_wakeup returns an error, so the device's memory + * could be read even if the FW crashed + */ + wl1271_ps_elp_wakeup(wl); + + /* store current partition and switch partition */ + memcpy(&old_part, &wl->curr_part, sizeof(old_part)); + ret = wlcore_set_partition(wl, &part); + if (ret < 0) + goto part_err; + + ret = wlcore_raw_read(wl, 0, buf, bytes, false); + if (ret < 0) + goto read_err; + +read_err: + /* recover partition */ + ret = wlcore_set_partition(wl, &old_part); + if (ret < 0) + goto part_err; + +part_err: + wl1271_ps_elp_sleep(wl); + +skip_read: + mutex_unlock(&wl->mutex); + + if (ret == 0) { + ret = copy_to_user(user_buf, buf, bytes); + if (ret < bytes) { + bytes -= ret; + *ppos += bytes; + ret = 0; + } else { + ret = -EFAULT; + } + } + + kfree(buf); + + return ((ret == 0) ? bytes : ret); +} + +static ssize_t dev_mem_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + struct wlcore_partition_set part, old_part; + size_t bytes = count; + int ret; + char *buf; + + /* only requests of dword-aligned size and offset are supported */ + if (bytes % 4) + return -EINVAL; + + if (*ppos % 4) + return -EINVAL; + + /* function should return in reasonable time */ + bytes = min(bytes, WLCORE_MAX_BLOCK_SIZE); + + if (bytes == 0) + return -EINVAL; + + memset(&part, 0, sizeof(part)); + part.mem.start = file->f_pos; + part.mem.size = bytes; + + buf = kmalloc(bytes, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = copy_from_user(buf, user_buf, bytes); + if (ret) { + ret = -EFAULT; + goto err_out; + } + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state == WLCORE_STATE_OFF)) { + ret = -EFAULT; + goto skip_write; + } + + /* + * Don't fail if elp_wakeup returns an error, so the device's memory + * could be read even if the FW crashed + */ + wl1271_ps_elp_wakeup(wl); + + /* store current partition and switch partition */ + memcpy(&old_part, &wl->curr_part, sizeof(old_part)); + ret = wlcore_set_partition(wl, &part); + if (ret < 0) + goto part_err; + + ret = wlcore_raw_write(wl, 0, buf, bytes, false); + if (ret < 0) + goto write_err; + +write_err: + /* recover partition */ + ret = wlcore_set_partition(wl, &old_part); + if (ret < 0) + goto part_err; + +part_err: + wl1271_ps_elp_sleep(wl); + +skip_write: + mutex_unlock(&wl->mutex); + + if (ret == 0) + *ppos += bytes; + +err_out: + kfree(buf); + + return ((ret == 0) ? bytes : ret); +} + +static loff_t dev_mem_seek(struct file *file, loff_t offset, int orig) +{ + loff_t ret; + + /* only requests of dword-aligned size and offset are supported */ + if (offset % 4) + return -EINVAL; + + switch (orig) { + case SEEK_SET: + file->f_pos = offset; + ret = file->f_pos; + break; + case SEEK_CUR: + file->f_pos += offset; + ret = file->f_pos; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct file_operations dev_mem_ops = { + .open = simple_open, + .read = dev_mem_read, + .write = dev_mem_write, + .llseek = dev_mem_seek, +}; + +static int wl1271_debugfs_add_files(struct wl1271 *wl, + struct dentry *rootdir) +{ + int ret = 0; + struct dentry *entry, *streaming; + + DEBUGFS_ADD(tx_queue_len, rootdir); + DEBUGFS_ADD(retry_count, rootdir); + DEBUGFS_ADD(excessive_retries, rootdir); + + DEBUGFS_ADD(gpio_power, rootdir); + DEBUGFS_ADD(start_recovery, rootdir); + DEBUGFS_ADD(driver_state, rootdir); + DEBUGFS_ADD(vifs_state, rootdir); + DEBUGFS_ADD(dtim_interval, rootdir); + DEBUGFS_ADD(suspend_dtim_interval, rootdir); + DEBUGFS_ADD(beacon_interval, rootdir); + DEBUGFS_ADD(beacon_filtering, rootdir); + DEBUGFS_ADD(dynamic_ps_timeout, rootdir); + DEBUGFS_ADD(forced_ps, rootdir); + DEBUGFS_ADD(split_scan_timeout, rootdir); + DEBUGFS_ADD(irq_pkt_threshold, rootdir); + DEBUGFS_ADD(irq_blk_threshold, rootdir); + DEBUGFS_ADD(irq_timeout, rootdir); + DEBUGFS_ADD(fw_stats_raw, rootdir); + DEBUGFS_ADD(sleep_auth, rootdir); + + streaming = debugfs_create_dir("rx_streaming", rootdir); + if (!streaming || IS_ERR(streaming)) + goto err; + + DEBUGFS_ADD_PREFIX(rx_streaming, interval, streaming); + DEBUGFS_ADD_PREFIX(rx_streaming, always, streaming); + + DEBUGFS_ADD_PREFIX(dev, mem, rootdir); + + return 0; + +err: + if (IS_ERR(entry)) + ret = PTR_ERR(entry); + else + ret = -ENOMEM; + + return ret; +} + +void wl1271_debugfs_reset(struct wl1271 *wl) +{ + if (!wl->stats.fw_stats) + return; + + memset(wl->stats.fw_stats, 0, wl->stats.fw_stats_len); + wl->stats.retry_count = 0; + wl->stats.excessive_retries = 0; +} + +int wl1271_debugfs_init(struct wl1271 *wl) +{ + int ret; + struct dentry *rootdir; + + rootdir = debugfs_create_dir(KBUILD_MODNAME, + wl->hw->wiphy->debugfsdir); + + if (IS_ERR(rootdir)) { + ret = PTR_ERR(rootdir); + goto out; + } + + wl->stats.fw_stats = kzalloc(wl->stats.fw_stats_len, GFP_KERNEL); + if (!wl->stats.fw_stats) { + ret = -ENOMEM; + goto out_remove; + } + + wl->stats.fw_stats_update = jiffies; + + ret = wl1271_debugfs_add_files(wl, rootdir); + if (ret < 0) + goto out_exit; + + ret = wlcore_debugfs_init(wl, rootdir); + if (ret < 0) + goto out_exit; + + goto out; + +out_exit: + wl1271_debugfs_exit(wl); + +out_remove: + debugfs_remove_recursive(rootdir); + +out: + return ret; +} + +void wl1271_debugfs_exit(struct wl1271 *wl) +{ + kfree(wl->stats.fw_stats); + wl->stats.fw_stats = NULL; +} diff --git a/drivers/net/wireless/ti/wlcore/debugfs.h b/drivers/net/wireless/ti/wlcore/debugfs.h new file mode 100644 index 0000000..f7381dd --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/debugfs.h @@ -0,0 +1,120 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __DEBUGFS_H__ +#define __DEBUGFS_H__ + +#include "wlcore.h" + +int wl1271_format_buffer(char __user *userbuf, size_t count, + loff_t *ppos, char *fmt, ...); + +int wl1271_debugfs_init(struct wl1271 *wl); +void wl1271_debugfs_exit(struct wl1271 *wl); +void wl1271_debugfs_reset(struct wl1271 *wl); +void wl1271_debugfs_update_stats(struct wl1271 *wl); + +#define DEBUGFS_FORMAT_BUFFER_SIZE 256 + +#define DEBUGFS_READONLY_FILE(name, fmt, value...) \ +static ssize_t name## _read(struct file *file, char __user *userbuf, \ + size_t count, loff_t *ppos) \ +{ \ + struct wl1271 *wl = file->private_data; \ + return wl1271_format_buffer(userbuf, count, ppos, \ + fmt "\n", ##value); \ +} \ + \ +static const struct file_operations name## _ops = { \ + .read = name## _read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_ADD(name, parent) \ + do { \ + entry = debugfs_create_file(#name, 0400, parent, \ + wl, &name## _ops); \ + if (!entry || IS_ERR(entry)) \ + goto err; \ + } while (0); + + +#define DEBUGFS_ADD_PREFIX(prefix, name, parent) \ + do { \ + entry = debugfs_create_file(#name, 0400, parent, \ + wl, &prefix## _## name## _ops); \ + if (!entry || IS_ERR(entry)) \ + goto err; \ + } while (0); + +#define DEBUGFS_FWSTATS_FILE(sub, name, fmt, struct_type) \ +static ssize_t sub## _ ##name## _read(struct file *file, \ + char __user *userbuf, \ + size_t count, loff_t *ppos) \ +{ \ + struct wl1271 *wl = file->private_data; \ + struct struct_type *stats = wl->stats.fw_stats; \ + \ + wl1271_debugfs_update_stats(wl); \ + \ + return wl1271_format_buffer(userbuf, count, ppos, fmt "\n", \ + stats->sub.name); \ +} \ + \ +static const struct file_operations sub## _ ##name## _ops = { \ + .read = sub## _ ##name## _read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_FWSTATS_FILE_ARRAY(sub, name, len, struct_type) \ +static ssize_t sub## _ ##name## _read(struct file *file, \ + char __user *userbuf, \ + size_t count, loff_t *ppos) \ +{ \ + struct wl1271 *wl = file->private_data; \ + struct struct_type *stats = wl->stats.fw_stats; \ + char buf[DEBUGFS_FORMAT_BUFFER_SIZE] = ""; \ + int res, i; \ + \ + wl1271_debugfs_update_stats(wl); \ + \ + for (i = 0; i < len; i++) \ + res = snprintf(buf, sizeof(buf), "%s[%d] = %d\n", \ + buf, i, stats->sub.name[i]); \ + \ + return wl1271_format_buffer(userbuf, count, ppos, "%s", buf); \ +} \ + \ +static const struct file_operations sub## _ ##name## _ops = { \ + .read = sub## _ ##name## _read, \ + .open = simple_open, \ + .llseek = generic_file_llseek, \ +}; + +#define DEBUGFS_FWSTATS_ADD(sub, name) \ + DEBUGFS_ADD(sub## _ ##name, stats) + + +#endif /* WL1271_DEBUGFS_H */ diff --git a/drivers/net/wireless/ti/wlcore/event.c b/drivers/net/wireless/ti/wlcore/event.c new file mode 100644 index 0000000..929d79c --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/event.c @@ -0,0 +1,304 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include "wlcore.h" +#include "debug.h" +#include "io.h" +#include "event.h" +#include "ps.h" +#include "scan.h" +#include "wl12xx_80211.h" + +void wlcore_event_rssi_trigger(struct wl1271 *wl, s8 *metric_arr) +{ + struct wl12xx_vif *wlvif; + struct ieee80211_vif *vif; + enum nl80211_cqm_rssi_threshold_event event; + s8 metric = metric_arr[0]; + + wl1271_debug(DEBUG_EVENT, "RSSI trigger metric: %d", metric); + + /* TODO: check actual multi-role support */ + wl12xx_for_each_wlvif_sta(wl, wlvif) { + if (metric <= wlvif->rssi_thold) + event = NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW; + else + event = NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH; + + vif = wl12xx_wlvif_to_vif(wlvif); + if (event != wlvif->last_rssi_event) + ieee80211_cqm_rssi_notify(vif, event, GFP_KERNEL); + wlvif->last_rssi_event = event; + } +} +EXPORT_SYMBOL_GPL(wlcore_event_rssi_trigger); + +static void wl1271_stop_ba_event(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + + if (wlvif->bss_type != BSS_TYPE_AP_BSS) { + u8 hlid = wlvif->sta.hlid; + if (!wl->links[hlid].ba_bitmap) + return; + ieee80211_stop_rx_ba_session(vif, wl->links[hlid].ba_bitmap, + vif->bss_conf.bssid); + } else { + u8 hlid; + struct wl1271_link *lnk; + for_each_set_bit(hlid, wlvif->ap.sta_hlid_map, + WL12XX_MAX_LINKS) { + lnk = &wl->links[hlid]; + if (!lnk->ba_bitmap) + continue; + + ieee80211_stop_rx_ba_session(vif, + lnk->ba_bitmap, + lnk->addr); + } + } +} + +void wlcore_event_soft_gemini_sense(struct wl1271 *wl, u8 enable) +{ + struct wl12xx_vif *wlvif; + + if (enable) { + set_bit(WL1271_FLAG_SOFT_GEMINI, &wl->flags); + } else { + clear_bit(WL1271_FLAG_SOFT_GEMINI, &wl->flags); + wl12xx_for_each_wlvif_sta(wl, wlvif) { + wl1271_recalc_rx_streaming(wl, wlvif); + } + } +} +EXPORT_SYMBOL_GPL(wlcore_event_soft_gemini_sense); + +void wlcore_event_sched_scan_completed(struct wl1271 *wl, + u8 status) +{ + wl1271_debug(DEBUG_EVENT, "PERIODIC_SCAN_COMPLETE_EVENT (status 0x%0x)", + status); + + if (wl->sched_vif) { + ieee80211_sched_scan_stopped(wl->hw); + wl->sched_vif = NULL; + } +} +EXPORT_SYMBOL_GPL(wlcore_event_sched_scan_completed); + +void wlcore_event_ba_rx_constraint(struct wl1271 *wl, + unsigned long roles_bitmap, + unsigned long allowed_bitmap) +{ + struct wl12xx_vif *wlvif; + + wl1271_debug(DEBUG_EVENT, "%s: roles=0x%lx allowed=0x%lx", + __func__, roles_bitmap, allowed_bitmap); + + wl12xx_for_each_wlvif(wl, wlvif) { + if (wlvif->role_id == WL12XX_INVALID_ROLE_ID || + !test_bit(wlvif->role_id , &roles_bitmap)) + continue; + + wlvif->ba_allowed = !!test_bit(wlvif->role_id, + &allowed_bitmap); + if (!wlvif->ba_allowed) + wl1271_stop_ba_event(wl, wlvif); + } +} +EXPORT_SYMBOL_GPL(wlcore_event_ba_rx_constraint); + +void wlcore_event_channel_switch(struct wl1271 *wl, + unsigned long roles_bitmap, + bool success) +{ + struct wl12xx_vif *wlvif; + struct ieee80211_vif *vif; + + wl1271_debug(DEBUG_EVENT, "%s: roles=0x%lx success=%d", + __func__, roles_bitmap, success); + + wl12xx_for_each_wlvif_sta(wl, wlvif) { + if (wlvif->role_id == WL12XX_INVALID_ROLE_ID || + !test_bit(wlvif->role_id , &roles_bitmap)) + continue; + + if (!test_and_clear_bit(WLVIF_FLAG_CS_PROGRESS, + &wlvif->flags)) + continue; + + vif = wl12xx_wlvif_to_vif(wlvif); + + ieee80211_chswitch_done(vif, success); + cancel_delayed_work(&wlvif->channel_switch_work); + } +} +EXPORT_SYMBOL_GPL(wlcore_event_channel_switch); + +void wlcore_event_dummy_packet(struct wl1271 *wl) +{ + wl1271_debug(DEBUG_EVENT, "DUMMY_PACKET_ID_EVENT_ID"); + wl1271_tx_dummy_packet(wl); +} +EXPORT_SYMBOL_GPL(wlcore_event_dummy_packet); + +static void wlcore_disconnect_sta(struct wl1271 *wl, unsigned long sta_bitmap) +{ + u32 num_packets = wl->conf.tx.max_tx_retries; + struct wl12xx_vif *wlvif; + struct ieee80211_vif *vif; + struct ieee80211_sta *sta; + const u8 *addr; + int h; + + for_each_set_bit(h, &sta_bitmap, WL12XX_MAX_LINKS) { + bool found = false; + /* find the ap vif connected to this sta */ + wl12xx_for_each_wlvif_ap(wl, wlvif) { + if (!test_bit(h, wlvif->ap.sta_hlid_map)) + continue; + found = true; + break; + } + if (!found) + continue; + + vif = wl12xx_wlvif_to_vif(wlvif); + addr = wl->links[h].addr; + + rcu_read_lock(); + sta = ieee80211_find_sta(vif, addr); + if (sta) { + wl1271_debug(DEBUG_EVENT, "remove sta %d", h); + ieee80211_report_low_ack(sta, num_packets); + } + rcu_read_unlock(); + } +} + +void wlcore_event_max_tx_failure(struct wl1271 *wl, unsigned long sta_bitmap) +{ + wl1271_debug(DEBUG_EVENT, "MAX_TX_FAILURE_EVENT_ID"); + wlcore_disconnect_sta(wl, sta_bitmap); +} +EXPORT_SYMBOL_GPL(wlcore_event_max_tx_failure); + +void wlcore_event_inactive_sta(struct wl1271 *wl, unsigned long sta_bitmap) +{ + wl1271_debug(DEBUG_EVENT, "INACTIVE_STA_EVENT_ID"); + wlcore_disconnect_sta(wl, sta_bitmap); +} +EXPORT_SYMBOL_GPL(wlcore_event_inactive_sta); + +void wlcore_event_roc_complete(struct wl1271 *wl) +{ + wl1271_debug(DEBUG_EVENT, "REMAIN_ON_CHANNEL_COMPLETE_EVENT_ID"); + if (wl->roc_vif) + ieee80211_ready_on_channel(wl->hw); +} +EXPORT_SYMBOL_GPL(wlcore_event_roc_complete); + +void wlcore_event_beacon_loss(struct wl1271 *wl, unsigned long roles_bitmap) +{ + /* + * We are HW_MONITOR device. On beacon loss - queue + * connection loss work. Cancel it on REGAINED event. + */ + struct wl12xx_vif *wlvif; + struct ieee80211_vif *vif; + int delay = wl->conf.conn.synch_fail_thold * + wl->conf.conn.bss_lose_timeout; + + wl1271_info("Beacon loss detected. roles:0x%lx", roles_bitmap); + + wl12xx_for_each_wlvif_sta(wl, wlvif) { + if (wlvif->role_id == WL12XX_INVALID_ROLE_ID || + !test_bit(wlvif->role_id , &roles_bitmap)) + continue; + + vif = wl12xx_wlvif_to_vif(wlvif); + + /* don't attempt roaming in case of p2p */ + if (wlvif->p2p) { + ieee80211_connection_loss(vif); + continue; + } + + /* + * if the work is already queued, it should take place. + * We don't want to delay the connection loss + * indication any more. + */ + ieee80211_queue_delayed_work(wl->hw, + &wlvif->connection_loss_work, + msecs_to_jiffies(delay)); + + ieee80211_cqm_rssi_notify( + vif, + NL80211_CQM_RSSI_BEACON_LOSS_EVENT, + GFP_KERNEL); + } +} +EXPORT_SYMBOL_GPL(wlcore_event_beacon_loss); + +int wl1271_event_unmask(struct wl1271 *wl) +{ + int ret; + + ret = wl1271_acx_event_mbox_mask(wl, ~(wl->event_mask)); + if (ret < 0) + return ret; + + return 0; +} + +int wl1271_event_handle(struct wl1271 *wl, u8 mbox_num) +{ + int ret; + + wl1271_debug(DEBUG_EVENT, "EVENT on mbox %d", mbox_num); + + if (mbox_num > 1) + return -EINVAL; + + /* first we read the mbox descriptor */ + ret = wlcore_read(wl, wl->mbox_ptr[mbox_num], wl->mbox, + wl->mbox_size, false); + if (ret < 0) + return ret; + + /* process the descriptor */ + ret = wl->ops->process_mailbox_events(wl); + if (ret < 0) + return ret; + + /* + * TODO: we just need this because one bit is in a different + * place. Is there any better way? + */ + ret = wl->ops->ack_event(wl); + + return ret; +} diff --git a/drivers/net/wireless/ti/wlcore/event.h b/drivers/net/wireless/ti/wlcore/event.h new file mode 100644 index 0000000..acc7a59 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/event.h @@ -0,0 +1,87 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 1998-2009 Texas Instruments. All rights reserved. + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __EVENT_H__ +#define __EVENT_H__ + +/* + * Mbox events + * + * The event mechanism is based on a pair of event buffers (buffers A and + * B) at fixed locations in the target's memory. The host processes one + * buffer while the other buffer continues to collect events. If the host + * is not processing events, an interrupt is issued to signal that a buffer + * is ready. Once the host is done with processing events from one buffer, + * it signals the target (with an ACK interrupt) that the event buffer is + * free. + */ + +enum { + RSSI_SNR_TRIGGER_0_EVENT_ID = BIT(0), + RSSI_SNR_TRIGGER_1_EVENT_ID = BIT(1), + RSSI_SNR_TRIGGER_2_EVENT_ID = BIT(2), + RSSI_SNR_TRIGGER_3_EVENT_ID = BIT(3), + RSSI_SNR_TRIGGER_4_EVENT_ID = BIT(4), + RSSI_SNR_TRIGGER_5_EVENT_ID = BIT(5), + RSSI_SNR_TRIGGER_6_EVENT_ID = BIT(6), + RSSI_SNR_TRIGGER_7_EVENT_ID = BIT(7), + + EVENT_MBOX_ALL_EVENT_ID = 0x7fffffff, +}; + +/* events the driver might want to wait for */ +enum wlcore_wait_event { + WLCORE_EVENT_ROLE_STOP_COMPLETE, + WLCORE_EVENT_PEER_REMOVE_COMPLETE, + WLCORE_EVENT_DFS_CONFIG_COMPLETE +}; + +enum { + EVENT_ENTER_POWER_SAVE_FAIL = 0, + EVENT_ENTER_POWER_SAVE_SUCCESS, +}; + +#define NUM_OF_RSSI_SNR_TRIGGERS 8 + +struct wl1271; + +int wl1271_event_unmask(struct wl1271 *wl); +int wl1271_event_handle(struct wl1271 *wl, u8 mbox); + +void wlcore_event_soft_gemini_sense(struct wl1271 *wl, u8 enable); +void wlcore_event_sched_scan_completed(struct wl1271 *wl, + u8 status); +void wlcore_event_ba_rx_constraint(struct wl1271 *wl, + unsigned long roles_bitmap, + unsigned long allowed_bitmap); +void wlcore_event_channel_switch(struct wl1271 *wl, + unsigned long roles_bitmap, + bool success); +void wlcore_event_beacon_loss(struct wl1271 *wl, unsigned long roles_bitmap); +void wlcore_event_dummy_packet(struct wl1271 *wl); +void wlcore_event_max_tx_failure(struct wl1271 *wl, unsigned long sta_bitmap); +void wlcore_event_inactive_sta(struct wl1271 *wl, unsigned long sta_bitmap); +void wlcore_event_roc_complete(struct wl1271 *wl); +void wlcore_event_rssi_trigger(struct wl1271 *wl, s8 *metric_arr); +#endif diff --git a/drivers/net/wireless/ti/wlcore/hw_ops.h b/drivers/net/wireless/ti/wlcore/hw_ops.h new file mode 100644 index 0000000..7fd260c --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/hw_ops.h @@ -0,0 +1,245 @@ +/* + * This file is part of wlcore + * + * Copyright (C) 2011 Texas Instruments Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WLCORE_HW_OPS_H__ +#define __WLCORE_HW_OPS_H__ + +#include "wlcore.h" +#include "rx.h" + +static inline u32 +wlcore_hw_calc_tx_blocks(struct wl1271 *wl, u32 len, u32 spare_blks) +{ + if (!wl->ops->calc_tx_blocks) + BUG_ON(1); + + return wl->ops->calc_tx_blocks(wl, len, spare_blks); +} + +static inline void +wlcore_hw_set_tx_desc_blocks(struct wl1271 *wl, struct wl1271_tx_hw_descr *desc, + u32 blks, u32 spare_blks) +{ + if (!wl->ops->set_tx_desc_blocks) + BUG_ON(1); + + return wl->ops->set_tx_desc_blocks(wl, desc, blks, spare_blks); +} + +static inline void +wlcore_hw_set_tx_desc_data_len(struct wl1271 *wl, + struct wl1271_tx_hw_descr *desc, + struct sk_buff *skb) +{ + if (!wl->ops->set_tx_desc_data_len) + BUG_ON(1); + + wl->ops->set_tx_desc_data_len(wl, desc, skb); +} + +static inline enum wl_rx_buf_align +wlcore_hw_get_rx_buf_align(struct wl1271 *wl, u32 rx_desc) +{ + + if (!wl->ops->get_rx_buf_align) + BUG_ON(1); + + return wl->ops->get_rx_buf_align(wl, rx_desc); +} + +static inline int +wlcore_hw_prepare_read(struct wl1271 *wl, u32 rx_desc, u32 len) +{ + if (wl->ops->prepare_read) + return wl->ops->prepare_read(wl, rx_desc, len); + + return 0; +} + +static inline u32 +wlcore_hw_get_rx_packet_len(struct wl1271 *wl, void *rx_data, u32 data_len) +{ + if (!wl->ops->get_rx_packet_len) + BUG_ON(1); + + return wl->ops->get_rx_packet_len(wl, rx_data, data_len); +} + +static inline int wlcore_hw_tx_delayed_compl(struct wl1271 *wl) +{ + if (wl->ops->tx_delayed_compl) + return wl->ops->tx_delayed_compl(wl); + + return 0; +} + +static inline void wlcore_hw_tx_immediate_compl(struct wl1271 *wl) +{ + if (wl->ops->tx_immediate_compl) + wl->ops->tx_immediate_compl(wl); +} + +static inline int +wlcore_hw_init_vif(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + if (wl->ops->init_vif) + return wl->ops->init_vif(wl, wlvif); + + return 0; +} + +static inline u32 +wlcore_hw_sta_get_ap_rate_mask(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + if (!wl->ops->sta_get_ap_rate_mask) + BUG_ON(1); + + return wl->ops->sta_get_ap_rate_mask(wl, wlvif); +} + +static inline int wlcore_identify_fw(struct wl1271 *wl) +{ + if (wl->ops->identify_fw) + return wl->ops->identify_fw(wl); + + return 0; +} + +static inline void +wlcore_hw_set_tx_desc_csum(struct wl1271 *wl, + struct wl1271_tx_hw_descr *desc, + struct sk_buff *skb) +{ + if (!wl->ops->set_tx_desc_csum) + BUG_ON(1); + + wl->ops->set_tx_desc_csum(wl, desc, skb); +} + +static inline void +wlcore_hw_set_rx_csum(struct wl1271 *wl, + struct wl1271_rx_descriptor *desc, + struct sk_buff *skb) +{ + if (wl->ops->set_rx_csum) + wl->ops->set_rx_csum(wl, desc, skb); +} + +static inline u32 +wlcore_hw_ap_get_mimo_wide_rate_mask(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + if (wl->ops->ap_get_mimo_wide_rate_mask) + return wl->ops->ap_get_mimo_wide_rate_mask(wl, wlvif); + + return 0; +} + +static inline int +wlcore_debugfs_init(struct wl1271 *wl, struct dentry *rootdir) +{ + if (wl->ops->debugfs_init) + return wl->ops->debugfs_init(wl, rootdir); + + return 0; +} + +static inline int +wlcore_handle_static_data(struct wl1271 *wl, void *static_data) +{ + if (wl->ops->handle_static_data) + return wl->ops->handle_static_data(wl, static_data); + + return 0; +} + +static inline int +wlcore_hw_get_spare_blocks(struct wl1271 *wl, bool is_gem) +{ + if (!wl->ops->get_spare_blocks) + BUG_ON(1); + + return wl->ops->get_spare_blocks(wl, is_gem); +} + +static inline int +wlcore_hw_set_key(struct wl1271 *wl, enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key_conf) +{ + if (!wl->ops->set_key) + BUG_ON(1); + + return wl->ops->set_key(wl, cmd, vif, sta, key_conf); +} + +static inline u32 +wlcore_hw_pre_pkt_send(struct wl1271 *wl, u32 buf_offset, u32 last_len) +{ + if (wl->ops->pre_pkt_send) + return wl->ops->pre_pkt_send(wl, buf_offset, last_len); + + return buf_offset; +} + +static inline void +wlcore_hw_sta_rc_update(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct ieee80211_sta *sta, u32 changed) +{ + if (wl->ops->sta_rc_update) + wl->ops->sta_rc_update(wl, wlvif, sta, changed); +} + +static inline int +wlcore_hw_set_peer_cap(struct wl1271 *wl, + struct ieee80211_sta_ht_cap *ht_cap, + bool allow_ht_operation, + u32 rate_set, u8 hlid) +{ + if (wl->ops->set_peer_cap) + return wl->ops->set_peer_cap(wl, ht_cap, allow_ht_operation, + rate_set, hlid); + + return 0; +} + +static inline bool +wlcore_hw_lnk_high_prio(struct wl1271 *wl, u8 hlid, + struct wl1271_link *lnk) +{ + if (!wl->ops->lnk_high_prio) + BUG_ON(1); + + return wl->ops->lnk_high_prio(wl, hlid, lnk); +} + +static inline bool +wlcore_hw_lnk_low_prio(struct wl1271 *wl, u8 hlid, + struct wl1271_link *lnk) +{ + if (!wl->ops->lnk_low_prio) + BUG_ON(1); + + return wl->ops->lnk_low_prio(wl, hlid, lnk); +} + +#endif diff --git a/drivers/net/wireless/ti/wlcore/ini.h b/drivers/net/wireless/ti/wlcore/ini.h new file mode 100644 index 0000000..d24fe3b --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/ini.h @@ -0,0 +1,232 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2010 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __INI_H__ +#define __INI_H__ + +#define GENERAL_SETTINGS_DRPW_LPD 0xc0 +#define SCRATCH_ENABLE_LPD BIT(25) + +#define WL1271_INI_MAX_SMART_REFLEX_PARAM 16 + +struct wl1271_ini_general_params { + u8 ref_clock; + u8 settling_time; + u8 clk_valid_on_wakeup; + u8 dc2dc_mode; + u8 dual_mode_select; + u8 tx_bip_fem_auto_detect; + u8 tx_bip_fem_manufacturer; + u8 general_settings; + u8 sr_state; + u8 srf1[WL1271_INI_MAX_SMART_REFLEX_PARAM]; + u8 srf2[WL1271_INI_MAX_SMART_REFLEX_PARAM]; + u8 srf3[WL1271_INI_MAX_SMART_REFLEX_PARAM]; +} __packed; + +#define WL128X_INI_MAX_SETTINGS_PARAM 4 + +struct wl128x_ini_general_params { + u8 ref_clock; + u8 settling_time; + u8 clk_valid_on_wakeup; + u8 tcxo_ref_clock; + u8 tcxo_settling_time; + u8 tcxo_valid_on_wakeup; + u8 tcxo_ldo_voltage; + u8 xtal_itrim_val; + u8 platform_conf; + u8 dual_mode_select; + u8 tx_bip_fem_auto_detect; + u8 tx_bip_fem_manufacturer; + u8 general_settings[WL128X_INI_MAX_SETTINGS_PARAM]; + u8 sr_state; + u8 srf1[WL1271_INI_MAX_SMART_REFLEX_PARAM]; + u8 srf2[WL1271_INI_MAX_SMART_REFLEX_PARAM]; + u8 srf3[WL1271_INI_MAX_SMART_REFLEX_PARAM]; +} __packed; + +#define WL1271_INI_RSSI_PROCESS_COMPENS_SIZE 15 + +struct wl1271_ini_band_params_2 { + u8 rx_trace_insertion_loss; + u8 tx_trace_loss; + u8 rx_rssi_process_compens[WL1271_INI_RSSI_PROCESS_COMPENS_SIZE]; +} __packed; + +#define WL1271_INI_CHANNEL_COUNT_2 14 + +struct wl128x_ini_band_params_2 { + u8 rx_trace_insertion_loss; + u8 tx_trace_loss[WL1271_INI_CHANNEL_COUNT_2]; + u8 rx_rssi_process_compens[WL1271_INI_RSSI_PROCESS_COMPENS_SIZE]; +} __packed; + +#define WL1271_INI_RATE_GROUP_COUNT 6 + +struct wl1271_ini_fem_params_2 { + __le16 tx_bip_ref_pd_voltage; + u8 tx_bip_ref_power; + u8 tx_bip_ref_offset; + u8 tx_per_rate_pwr_limits_normal[WL1271_INI_RATE_GROUP_COUNT]; + u8 tx_per_rate_pwr_limits_degraded[WL1271_INI_RATE_GROUP_COUNT]; + u8 tx_per_rate_pwr_limits_extreme[WL1271_INI_RATE_GROUP_COUNT]; + u8 tx_per_chan_pwr_limits_11b[WL1271_INI_CHANNEL_COUNT_2]; + u8 tx_per_chan_pwr_limits_ofdm[WL1271_INI_CHANNEL_COUNT_2]; + u8 tx_pd_vs_rate_offsets[WL1271_INI_RATE_GROUP_COUNT]; + u8 tx_ibias[WL1271_INI_RATE_GROUP_COUNT]; + u8 rx_fem_insertion_loss; + u8 degraded_low_to_normal_thr; + u8 normal_to_degraded_high_thr; +} __packed; + +#define WL128X_INI_RATE_GROUP_COUNT 7 +/* low and high temperatures */ +#define WL128X_INI_PD_VS_TEMPERATURE_RANGES 2 + +struct wl128x_ini_fem_params_2 { + __le16 tx_bip_ref_pd_voltage; + u8 tx_bip_ref_power; + u8 tx_bip_ref_offset; + u8 tx_per_rate_pwr_limits_normal[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_per_rate_pwr_limits_degraded[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_per_rate_pwr_limits_extreme[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_per_chan_pwr_limits_11b[WL1271_INI_CHANNEL_COUNT_2]; + u8 tx_per_chan_pwr_limits_ofdm[WL1271_INI_CHANNEL_COUNT_2]; + u8 tx_pd_vs_rate_offsets[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_ibias[WL128X_INI_RATE_GROUP_COUNT + 1]; + u8 tx_pd_vs_chan_offsets[WL1271_INI_CHANNEL_COUNT_2]; + u8 tx_pd_vs_temperature[WL128X_INI_PD_VS_TEMPERATURE_RANGES]; + u8 rx_fem_insertion_loss; + u8 degraded_low_to_normal_thr; + u8 normal_to_degraded_high_thr; +} __packed; + +#define WL1271_INI_CHANNEL_COUNT_5 35 +#define WL1271_INI_SUB_BAND_COUNT_5 7 + +struct wl1271_ini_band_params_5 { + u8 rx_trace_insertion_loss[WL1271_INI_SUB_BAND_COUNT_5]; + u8 tx_trace_loss[WL1271_INI_SUB_BAND_COUNT_5]; + u8 rx_rssi_process_compens[WL1271_INI_RSSI_PROCESS_COMPENS_SIZE]; +} __packed; + +struct wl128x_ini_band_params_5 { + u8 rx_trace_insertion_loss[WL1271_INI_SUB_BAND_COUNT_5]; + u8 tx_trace_loss[WL1271_INI_CHANNEL_COUNT_5]; + u8 rx_rssi_process_compens[WL1271_INI_RSSI_PROCESS_COMPENS_SIZE]; +} __packed; + +struct wl1271_ini_fem_params_5 { + __le16 tx_bip_ref_pd_voltage[WL1271_INI_SUB_BAND_COUNT_5]; + u8 tx_bip_ref_power[WL1271_INI_SUB_BAND_COUNT_5]; + u8 tx_bip_ref_offset[WL1271_INI_SUB_BAND_COUNT_5]; + u8 tx_per_rate_pwr_limits_normal[WL1271_INI_RATE_GROUP_COUNT]; + u8 tx_per_rate_pwr_limits_degraded[WL1271_INI_RATE_GROUP_COUNT]; + u8 tx_per_rate_pwr_limits_extreme[WL1271_INI_RATE_GROUP_COUNT]; + u8 tx_per_chan_pwr_limits_ofdm[WL1271_INI_CHANNEL_COUNT_5]; + u8 tx_pd_vs_rate_offsets[WL1271_INI_RATE_GROUP_COUNT]; + u8 tx_ibias[WL1271_INI_RATE_GROUP_COUNT]; + u8 rx_fem_insertion_loss[WL1271_INI_SUB_BAND_COUNT_5]; + u8 degraded_low_to_normal_thr; + u8 normal_to_degraded_high_thr; +} __packed; + +struct wl128x_ini_fem_params_5 { + __le16 tx_bip_ref_pd_voltage[WL1271_INI_SUB_BAND_COUNT_5]; + u8 tx_bip_ref_power[WL1271_INI_SUB_BAND_COUNT_5]; + u8 tx_bip_ref_offset[WL1271_INI_SUB_BAND_COUNT_5]; + u8 tx_per_rate_pwr_limits_normal[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_per_rate_pwr_limits_degraded[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_per_rate_pwr_limits_extreme[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_per_chan_pwr_limits_ofdm[WL1271_INI_CHANNEL_COUNT_5]; + u8 tx_pd_vs_rate_offsets[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_ibias[WL128X_INI_RATE_GROUP_COUNT]; + u8 tx_pd_vs_chan_offsets[WL1271_INI_CHANNEL_COUNT_5]; + u8 tx_pd_vs_temperature[WL1271_INI_SUB_BAND_COUNT_5 * + WL128X_INI_PD_VS_TEMPERATURE_RANGES]; + u8 rx_fem_insertion_loss[WL1271_INI_SUB_BAND_COUNT_5]; + u8 degraded_low_to_normal_thr; + u8 normal_to_degraded_high_thr; +} __packed; + +/* NVS data structure */ +#define WL1271_INI_NVS_SECTION_SIZE 468 + +/* We have four FEM module types: 0-RFMD, 1-TQS, 2-SKW, 3-TQS_HP */ +#define WL1271_INI_FEM_MODULE_COUNT 4 + +/* + * In NVS we only store two FEM module entries - + * FEM modules 0,2,3 are stored in entry 0 + * FEM module 1 is stored in entry 1 + */ +#define WL12XX_NVS_FEM_MODULE_COUNT 2 + +#define WL12XX_FEM_TO_NVS_ENTRY(ini_fem_module) \ + ((ini_fem_module) == 1 ? 1 : 0) + +#define WL1271_INI_LEGACY_NVS_FILE_SIZE 800 + +struct wl1271_nvs_file { + /* NVS section - must be first! */ + u8 nvs[WL1271_INI_NVS_SECTION_SIZE]; + + /* INI section */ + struct wl1271_ini_general_params general_params; + u8 padding1; + struct wl1271_ini_band_params_2 stat_radio_params_2; + u8 padding2; + struct { + struct wl1271_ini_fem_params_2 params; + u8 padding; + } dyn_radio_params_2[WL12XX_NVS_FEM_MODULE_COUNT]; + struct wl1271_ini_band_params_5 stat_radio_params_5; + u8 padding3; + struct { + struct wl1271_ini_fem_params_5 params; + u8 padding; + } dyn_radio_params_5[WL12XX_NVS_FEM_MODULE_COUNT]; +} __packed; + +struct wl128x_nvs_file { + /* NVS section - must be first! */ + u8 nvs[WL1271_INI_NVS_SECTION_SIZE]; + + /* INI section */ + struct wl128x_ini_general_params general_params; + u8 fem_vendor_and_options; + struct wl128x_ini_band_params_2 stat_radio_params_2; + u8 padding2; + struct { + struct wl128x_ini_fem_params_2 params; + u8 padding; + } dyn_radio_params_2[WL12XX_NVS_FEM_MODULE_COUNT]; + struct wl128x_ini_band_params_5 stat_radio_params_5; + u8 padding3; + struct { + struct wl128x_ini_fem_params_5 params; + u8 padding; + } dyn_radio_params_5[WL12XX_NVS_FEM_MODULE_COUNT]; +} __packed; +#endif diff --git a/drivers/net/wireless/ti/wlcore/init.c b/drivers/net/wireless/ti/wlcore/init.c new file mode 100644 index 0000000..5c6f11e --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/init.c @@ -0,0 +1,750 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + +#include "debug.h" +#include "init.h" +#include "wl12xx_80211.h" +#include "acx.h" +#include "cmd.h" +#include "tx.h" +#include "io.h" +#include "hw_ops.h" + +int wl1271_init_templates_config(struct wl1271 *wl) +{ + int ret, i; + size_t max_size; + + /* send empty templates for fw memory reservation */ + ret = wl1271_cmd_template_set(wl, WL12XX_INVALID_ROLE_ID, + wl->scan_templ_id_2_4, NULL, + WL1271_CMD_TEMPL_MAX_SIZE, + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, WL12XX_INVALID_ROLE_ID, + wl->scan_templ_id_5, + NULL, WL1271_CMD_TEMPL_MAX_SIZE, 0, + WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + if (wl->quirks & WLCORE_QUIRK_DUAL_PROBE_TMPL) { + ret = wl1271_cmd_template_set(wl, WL12XX_INVALID_ROLE_ID, + wl->sched_scan_templ_id_2_4, + NULL, + WL1271_CMD_TEMPL_MAX_SIZE, + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, WL12XX_INVALID_ROLE_ID, + wl->sched_scan_templ_id_5, + NULL, + WL1271_CMD_TEMPL_MAX_SIZE, + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + } + + ret = wl1271_cmd_template_set(wl, WL12XX_INVALID_ROLE_ID, + CMD_TEMPL_NULL_DATA, NULL, + sizeof(struct wl12xx_null_data_template), + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, WL12XX_INVALID_ROLE_ID, + CMD_TEMPL_PS_POLL, NULL, + sizeof(struct wl12xx_ps_poll_template), + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, WL12XX_INVALID_ROLE_ID, + CMD_TEMPL_QOS_NULL_DATA, NULL, + sizeof + (struct ieee80211_qos_hdr), + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, WL12XX_INVALID_ROLE_ID, + CMD_TEMPL_PROBE_RESPONSE, NULL, + WL1271_CMD_TEMPL_DFLT_SIZE, + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, WL12XX_INVALID_ROLE_ID, + CMD_TEMPL_BEACON, NULL, + WL1271_CMD_TEMPL_DFLT_SIZE, + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + max_size = sizeof(struct wl12xx_arp_rsp_template) + + WL1271_EXTRA_SPACE_MAX; + ret = wl1271_cmd_template_set(wl, WL12XX_INVALID_ROLE_ID, + CMD_TEMPL_ARP_RSP, NULL, + max_size, + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + /* + * Put very large empty placeholders for all templates. These + * reserve memory for later. + */ + ret = wl1271_cmd_template_set(wl, WL12XX_INVALID_ROLE_ID, + CMD_TEMPL_AP_PROBE_RESPONSE, NULL, + WL1271_CMD_TEMPL_MAX_SIZE, + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, WL12XX_INVALID_ROLE_ID, + CMD_TEMPL_AP_BEACON, NULL, + WL1271_CMD_TEMPL_MAX_SIZE, + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + ret = wl1271_cmd_template_set(wl, WL12XX_INVALID_ROLE_ID, + CMD_TEMPL_DEAUTH_AP, NULL, + sizeof + (struct wl12xx_disconn_template), + 0, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + + for (i = 0; i < WLCORE_MAX_KLV_TEMPLATES; i++) { + ret = wl1271_cmd_template_set(wl, WL12XX_INVALID_ROLE_ID, + CMD_TEMPL_KLV, NULL, + sizeof(struct ieee80211_qos_hdr), + i, WL1271_RATE_AUTOMATIC); + if (ret < 0) + return ret; + } + + return 0; +} + +static int wl1271_ap_init_deauth_template(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + struct wl12xx_disconn_template *tmpl; + int ret; + u32 rate; + + tmpl = kzalloc(sizeof(*tmpl), GFP_KERNEL); + if (!tmpl) { + ret = -ENOMEM; + goto out; + } + + tmpl->header.frame_ctl = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_DEAUTH); + + rate = wl1271_tx_min_rate_get(wl, wlvif->basic_rate_set); + ret = wl1271_cmd_template_set(wl, wlvif->role_id, + CMD_TEMPL_DEAUTH_AP, + tmpl, sizeof(*tmpl), 0, rate); + +out: + kfree(tmpl); + return ret; +} + +static int wl1271_ap_init_null_template(struct wl1271 *wl, + struct ieee80211_vif *vif) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + struct ieee80211_hdr_3addr *nullfunc; + int ret; + u32 rate; + + nullfunc = kzalloc(sizeof(*nullfunc), GFP_KERNEL); + if (!nullfunc) { + ret = -ENOMEM; + goto out; + } + + nullfunc->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | + IEEE80211_STYPE_NULLFUNC | + IEEE80211_FCTL_FROMDS); + + /* nullfunc->addr1 is filled by FW */ + + memcpy(nullfunc->addr2, vif->addr, ETH_ALEN); + memcpy(nullfunc->addr3, vif->addr, ETH_ALEN); + + rate = wl1271_tx_min_rate_get(wl, wlvif->basic_rate_set); + ret = wl1271_cmd_template_set(wl, wlvif->role_id, + CMD_TEMPL_NULL_DATA, nullfunc, + sizeof(*nullfunc), 0, rate); + +out: + kfree(nullfunc); + return ret; +} + +static int wl1271_ap_init_qos_null_template(struct wl1271 *wl, + struct ieee80211_vif *vif) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + struct ieee80211_qos_hdr *qosnull; + int ret; + u32 rate; + + qosnull = kzalloc(sizeof(*qosnull), GFP_KERNEL); + if (!qosnull) { + ret = -ENOMEM; + goto out; + } + + qosnull->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | + IEEE80211_STYPE_QOS_NULLFUNC | + IEEE80211_FCTL_FROMDS); + + /* qosnull->addr1 is filled by FW */ + + memcpy(qosnull->addr2, vif->addr, ETH_ALEN); + memcpy(qosnull->addr3, vif->addr, ETH_ALEN); + + rate = wl1271_tx_min_rate_get(wl, wlvif->basic_rate_set); + ret = wl1271_cmd_template_set(wl, wlvif->role_id, + CMD_TEMPL_QOS_NULL_DATA, qosnull, + sizeof(*qosnull), 0, rate); + +out: + kfree(qosnull); + return ret; +} + +static int wl12xx_init_rx_config(struct wl1271 *wl) +{ + int ret; + + ret = wl1271_acx_rx_msdu_life_time(wl); + if (ret < 0) + return ret; + + return 0; +} + +static int wl12xx_init_phy_vif_config(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + int ret; + + ret = wl1271_acx_slot(wl, wlvif, DEFAULT_SLOT_TIME); + if (ret < 0) + return ret; + + ret = wl1271_acx_service_period_timeout(wl, wlvif); + if (ret < 0) + return ret; + + ret = wl1271_acx_rts_threshold(wl, wlvif, wl->hw->wiphy->rts_threshold); + if (ret < 0) + return ret; + + return 0; +} + +static int wl1271_init_sta_beacon_filter(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + int ret; + + ret = wl1271_acx_beacon_filter_table(wl, wlvif); + if (ret < 0) + return ret; + + /* enable beacon filtering */ + ret = wl1271_acx_beacon_filter_opt(wl, wlvif, true); + if (ret < 0) + return ret; + + return 0; +} + +int wl1271_init_pta(struct wl1271 *wl) +{ + int ret; + + ret = wl12xx_acx_sg_cfg(wl); + if (ret < 0) + return ret; + + ret = wl1271_acx_sg_enable(wl, wl->sg_enabled); + if (ret < 0) + return ret; + + return 0; +} + +int wl1271_init_energy_detection(struct wl1271 *wl) +{ + int ret; + + ret = wl1271_acx_cca_threshold(wl); + if (ret < 0) + return ret; + + return 0; +} + +static int wl1271_init_beacon_broadcast(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + int ret; + + ret = wl1271_acx_bcn_dtim_options(wl, wlvif); + if (ret < 0) + return ret; + + return 0; +} + +static int wl12xx_init_fwlog(struct wl1271 *wl) +{ + int ret; + + if (wl->quirks & WLCORE_QUIRK_FWLOG_NOT_IMPLEMENTED) + return 0; + + ret = wl12xx_cmd_config_fwlog(wl); + if (ret < 0) + return ret; + + return 0; +} + +/* generic sta initialization (non vif-specific) */ +static int wl1271_sta_hw_init(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + int ret; + + /* PS config */ + ret = wl12xx_acx_config_ps(wl, wlvif); + if (ret < 0) + return ret; + + /* FM WLAN coexistence */ + ret = wl1271_acx_fm_coex(wl); + if (ret < 0) + return ret; + + ret = wl1271_acx_sta_rate_policies(wl, wlvif); + if (ret < 0) + return ret; + + return 0; +} + +static int wl1271_sta_hw_init_post_mem(struct wl1271 *wl, + struct ieee80211_vif *vif) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + int ret; + + /* disable the keep-alive feature */ + ret = wl1271_acx_keep_alive_mode(wl, wlvif, false); + if (ret < 0) + return ret; + + return 0; +} + +/* generic ap initialization (non vif-specific) */ +static int wl1271_ap_hw_init(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + int ret; + + ret = wl1271_init_ap_rates(wl, wlvif); + if (ret < 0) + return ret; + + return 0; +} + +int wl1271_ap_init_templates(struct wl1271 *wl, struct ieee80211_vif *vif) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + int ret; + + ret = wl1271_ap_init_deauth_template(wl, wlvif); + if (ret < 0) + return ret; + + ret = wl1271_ap_init_null_template(wl, vif); + if (ret < 0) + return ret; + + ret = wl1271_ap_init_qos_null_template(wl, vif); + if (ret < 0) + return ret; + + /* + * when operating as AP we want to receive external beacons for + * configuring ERP protection. + */ + ret = wl1271_acx_beacon_filter_opt(wl, wlvif, false); + if (ret < 0) + return ret; + + return 0; +} + +static int wl1271_ap_hw_init_post_mem(struct wl1271 *wl, + struct ieee80211_vif *vif) +{ + return wl1271_ap_init_templates(wl, vif); +} + +int wl1271_init_ap_rates(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + int i, ret; + struct conf_tx_rate_class rc; + u32 supported_rates; + + wl1271_debug(DEBUG_AP, "AP basic rate set: 0x%x", + wlvif->basic_rate_set); + + if (wlvif->basic_rate_set == 0) + return -EINVAL; + + rc.enabled_rates = wlvif->basic_rate_set; + rc.long_retry_limit = 10; + rc.short_retry_limit = 10; + rc.aflags = 0; + ret = wl1271_acx_ap_rate_policy(wl, &rc, wlvif->ap.mgmt_rate_idx); + if (ret < 0) + return ret; + + /* use the min basic rate for AP broadcast/multicast */ + rc.enabled_rates = wl1271_tx_min_rate_get(wl, wlvif->basic_rate_set); + rc.short_retry_limit = 10; + rc.long_retry_limit = 10; + rc.aflags = 0; + ret = wl1271_acx_ap_rate_policy(wl, &rc, wlvif->ap.bcast_rate_idx); + if (ret < 0) + return ret; + + /* + * If the basic rates contain OFDM rates, use OFDM only + * rates for unicast TX as well. Else use all supported rates. + */ + if ((wlvif->basic_rate_set & CONF_TX_OFDM_RATES)) + supported_rates = CONF_TX_OFDM_RATES; + else + supported_rates = CONF_TX_ENABLED_RATES; + + /* unconditionally enable HT rates */ + supported_rates |= CONF_TX_MCS_RATES; + + /* get extra MIMO or wide-chan rates where the HW supports it */ + supported_rates |= wlcore_hw_ap_get_mimo_wide_rate_mask(wl, wlvif); + + /* configure unicast TX rate classes */ + for (i = 0; i < wl->conf.tx.ac_conf_count; i++) { + rc.enabled_rates = supported_rates; + rc.short_retry_limit = 10; + rc.long_retry_limit = 10; + rc.aflags = 0; + ret = wl1271_acx_ap_rate_policy(wl, &rc, + wlvif->ap.ucast_rate_idx[i]); + if (ret < 0) + return ret; + } + + return 0; +} + +static int wl1271_set_ba_policies(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + /* Reset the BA RX indicators */ + wlvif->ba_allowed = true; + wl->ba_rx_session_count = 0; + + /* BA is supported in STA/AP modes */ + if (wlvif->bss_type != BSS_TYPE_AP_BSS && + wlvif->bss_type != BSS_TYPE_STA_BSS) { + wlvif->ba_support = false; + return 0; + } + + wlvif->ba_support = true; + + /* 802.11n initiator BA session setting */ + return wl12xx_acx_set_ba_initiator_policy(wl, wlvif); +} + +/* vif-specifc initialization */ +static int wl12xx_init_sta_role(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + int ret; + + ret = wl1271_acx_group_address_tbl(wl, wlvif, true, NULL, 0); + if (ret < 0) + return ret; + + /* Initialize connection monitoring thresholds */ + ret = wl1271_acx_conn_monit_params(wl, wlvif, false); + if (ret < 0) + return ret; + + /* Beacon filtering */ + ret = wl1271_init_sta_beacon_filter(wl, wlvif); + if (ret < 0) + return ret; + + /* Beacons and broadcast settings */ + ret = wl1271_init_beacon_broadcast(wl, wlvif); + if (ret < 0) + return ret; + + /* Configure rssi/snr averaging weights */ + ret = wl1271_acx_rssi_snr_avg_weights(wl, wlvif); + if (ret < 0) + return ret; + + return 0; +} + +/* vif-specific intialization */ +static int wl12xx_init_ap_role(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + int ret; + + ret = wl1271_acx_ap_max_tx_retry(wl, wlvif); + if (ret < 0) + return ret; + + /* initialize Tx power */ + ret = wl1271_acx_tx_power(wl, wlvif, wlvif->power_level); + if (ret < 0) + return ret; + + return 0; +} + +int wl1271_init_vif_specific(struct wl1271 *wl, struct ieee80211_vif *vif) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + struct conf_tx_ac_category *conf_ac; + struct conf_tx_tid *conf_tid; + bool is_ap = (wlvif->bss_type == BSS_TYPE_AP_BSS); + int ret, i; + + /* consider all existing roles before configuring psm. */ + + if (wl->ap_count == 0 && is_ap) { /* first AP */ + /* Configure for power always on */ + ret = wl1271_acx_sleep_auth(wl, WL1271_PSM_CAM); + if (ret < 0) + return ret; + /* first STA, no APs */ + } else if (wl->sta_count == 0 && wl->ap_count == 0 && !is_ap) { + u8 sta_auth = wl->conf.conn.sta_sleep_auth; + /* Configure for power according to debugfs */ + if (sta_auth != WL1271_PSM_ILLEGAL) + ret = wl1271_acx_sleep_auth(wl, sta_auth); + /* Configure for ELP power saving */ + else + ret = wl1271_acx_sleep_auth(wl, WL1271_PSM_ELP); + + if (ret < 0) + return ret; + } + + /* Mode specific init */ + if (is_ap) { + ret = wl1271_ap_hw_init(wl, wlvif); + if (ret < 0) + return ret; + + ret = wl12xx_init_ap_role(wl, wlvif); + if (ret < 0) + return ret; + } else { + ret = wl1271_sta_hw_init(wl, wlvif); + if (ret < 0) + return ret; + + ret = wl12xx_init_sta_role(wl, wlvif); + if (ret < 0) + return ret; + } + + wl12xx_init_phy_vif_config(wl, wlvif); + + /* Default TID/AC configuration */ + BUG_ON(wl->conf.tx.tid_conf_count != wl->conf.tx.ac_conf_count); + for (i = 0; i < wl->conf.tx.tid_conf_count; i++) { + conf_ac = &wl->conf.tx.ac_conf[i]; + ret = wl1271_acx_ac_cfg(wl, wlvif, conf_ac->ac, + conf_ac->cw_min, conf_ac->cw_max, + conf_ac->aifsn, conf_ac->tx_op_limit); + if (ret < 0) + return ret; + + conf_tid = &wl->conf.tx.tid_conf[i]; + ret = wl1271_acx_tid_cfg(wl, wlvif, + conf_tid->queue_id, + conf_tid->channel_type, + conf_tid->tsid, + conf_tid->ps_scheme, + conf_tid->ack_policy, + conf_tid->apsd_conf[0], + conf_tid->apsd_conf[1]); + if (ret < 0) + return ret; + } + + /* Configure HW encryption */ + ret = wl1271_acx_feature_cfg(wl, wlvif); + if (ret < 0) + return ret; + + /* Mode specific init - post mem init */ + if (is_ap) + ret = wl1271_ap_hw_init_post_mem(wl, vif); + else + ret = wl1271_sta_hw_init_post_mem(wl, vif); + + if (ret < 0) + return ret; + + /* Configure initiator BA sessions policies */ + ret = wl1271_set_ba_policies(wl, wlvif); + if (ret < 0) + return ret; + + ret = wlcore_hw_init_vif(wl, wlvif); + if (ret < 0) + return ret; + + return 0; +} + +int wl1271_hw_init(struct wl1271 *wl) +{ + int ret; + + /* Chip-specific hw init */ + ret = wl->ops->hw_init(wl); + if (ret < 0) + return ret; + + /* Init templates */ + ret = wl1271_init_templates_config(wl); + if (ret < 0) + return ret; + + ret = wl12xx_acx_mem_cfg(wl); + if (ret < 0) + return ret; + + /* Configure the FW logger */ + ret = wl12xx_init_fwlog(wl); + if (ret < 0) + return ret; + + ret = wlcore_cmd_regdomain_config_locked(wl); + if (ret < 0) + return ret; + + /* Bluetooth WLAN coexistence */ + ret = wl1271_init_pta(wl); + if (ret < 0) + return ret; + + /* Default memory configuration */ + ret = wl1271_acx_init_mem_config(wl); + if (ret < 0) + return ret; + + /* RX config */ + ret = wl12xx_init_rx_config(wl); + if (ret < 0) + goto out_free_memmap; + + ret = wl1271_acx_dco_itrim_params(wl); + if (ret < 0) + goto out_free_memmap; + + /* Configure TX patch complete interrupt behavior */ + ret = wl1271_acx_tx_config_options(wl); + if (ret < 0) + goto out_free_memmap; + + /* RX complete interrupt pacing */ + ret = wl1271_acx_init_rx_interrupt(wl); + if (ret < 0) + goto out_free_memmap; + + /* Energy detection */ + ret = wl1271_init_energy_detection(wl); + if (ret < 0) + goto out_free_memmap; + + /* Default fragmentation threshold */ + ret = wl1271_acx_frag_threshold(wl, wl->hw->wiphy->frag_threshold); + if (ret < 0) + goto out_free_memmap; + + /* Enable data path */ + ret = wl1271_cmd_data_path(wl, 1); + if (ret < 0) + goto out_free_memmap; + + /* configure PM */ + ret = wl1271_acx_pm_config(wl); + if (ret < 0) + goto out_free_memmap; + + ret = wl12xx_acx_set_rate_mgmt_params(wl); + if (ret < 0) + goto out_free_memmap; + + /* configure hangover */ + ret = wl12xx_acx_config_hangover(wl); + if (ret < 0) + goto out_free_memmap; + + return 0; + + out_free_memmap: + kfree(wl->target_mem_map); + wl->target_mem_map = NULL; + + return ret; +} diff --git a/drivers/net/wireless/ti/wlcore/init.h b/drivers/net/wireless/ti/wlcore/init.h new file mode 100644 index 0000000..a45fbfd --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/init.h @@ -0,0 +1,39 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __INIT_H__ +#define __INIT_H__ + +#include "wlcore.h" + +int wl1271_hw_init_power_auth(struct wl1271 *wl); +int wl1271_init_templates_config(struct wl1271 *wl); +int wl1271_init_pta(struct wl1271 *wl); +int wl1271_init_energy_detection(struct wl1271 *wl); +int wl1271_chip_specific_init(struct wl1271 *wl); +int wl1271_hw_init(struct wl1271 *wl); +int wl1271_init_vif_specific(struct wl1271 *wl, struct ieee80211_vif *vif); +int wl1271_init_ap_rates(struct wl1271 *wl, struct wl12xx_vif *wlvif); +int wl1271_ap_init_templates(struct wl1271 *wl, struct ieee80211_vif *vif); + +#endif diff --git a/drivers/net/wireless/ti/wlcore/io.c b/drivers/net/wireless/ti/wlcore/io.c new file mode 100644 index 0000000..68e74ee --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/io.c @@ -0,0 +1,200 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2010 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include + +#include "wlcore.h" +#include "debug.h" +#include "wl12xx_80211.h" +#include "io.h" +#include "tx.h" + +bool wl1271_set_block_size(struct wl1271 *wl) +{ + if (wl->if_ops->set_block_size) { + wl->if_ops->set_block_size(wl->dev, WL12XX_BUS_BLOCK_SIZE); + return true; + } + + return false; +} + +void wlcore_disable_interrupts(struct wl1271 *wl) +{ + disable_irq(wl->irq); +} +EXPORT_SYMBOL_GPL(wlcore_disable_interrupts); + +void wlcore_disable_interrupts_nosync(struct wl1271 *wl) +{ + disable_irq_nosync(wl->irq); +} +EXPORT_SYMBOL_GPL(wlcore_disable_interrupts_nosync); + +void wlcore_enable_interrupts(struct wl1271 *wl) +{ + enable_irq(wl->irq); +} +EXPORT_SYMBOL_GPL(wlcore_enable_interrupts); + +void wlcore_synchronize_interrupts(struct wl1271 *wl) +{ + synchronize_irq(wl->irq); +} +EXPORT_SYMBOL_GPL(wlcore_synchronize_interrupts); + +int wlcore_translate_addr(struct wl1271 *wl, int addr) +{ + struct wlcore_partition_set *part = &wl->curr_part; + + /* + * To translate, first check to which window of addresses the + * particular address belongs. Then subtract the starting address + * of that window from the address. Then, add offset of the + * translated region. + * + * The translated regions occur next to each other in physical device + * memory, so just add the sizes of the preceding address regions to + * get the offset to the new region. + */ + if ((addr >= part->mem.start) && + (addr < part->mem.start + part->mem.size)) + return addr - part->mem.start; + else if ((addr >= part->reg.start) && + (addr < part->reg.start + part->reg.size)) + return addr - part->reg.start + part->mem.size; + else if ((addr >= part->mem2.start) && + (addr < part->mem2.start + part->mem2.size)) + return addr - part->mem2.start + part->mem.size + + part->reg.size; + else if ((addr >= part->mem3.start) && + (addr < part->mem3.start + part->mem3.size)) + return addr - part->mem3.start + part->mem.size + + part->reg.size + part->mem2.size; + + WARN(1, "HW address 0x%x out of range", addr); + return 0; +} +EXPORT_SYMBOL_GPL(wlcore_translate_addr); + +/* Set the partitions to access the chip addresses + * + * To simplify driver code, a fixed (virtual) memory map is defined for + * register and memory addresses. Because in the chipset, in different stages + * of operation, those addresses will move around, an address translation + * mechanism is required. + * + * There are four partitions (three memory and one register partition), + * which are mapped to two different areas of the hardware memory. + * + * Virtual address + * space + * + * | | + * ...+----+--> mem.start + * Physical address ... | | + * space ... | | [PART_0] + * ... | | + * 00000000 <--+----+... ...+----+--> mem.start + mem.size + * | | ... | | + * |MEM | ... | | + * | | ... | | + * mem.size <--+----+... | | {unused area) + * | | ... | | + * |REG | ... | | + * mem.size | | ... | | + * + <--+----+... ...+----+--> reg.start + * reg.size | | ... | | + * |MEM2| ... | | [PART_1] + * | | ... | | + * ...+----+--> reg.start + reg.size + * | | + * + */ +int wlcore_set_partition(struct wl1271 *wl, + const struct wlcore_partition_set *p) +{ + int ret; + + /* copy partition info */ + memcpy(&wl->curr_part, p, sizeof(*p)); + + wl1271_debug(DEBUG_IO, "mem_start %08X mem_size %08X", + p->mem.start, p->mem.size); + wl1271_debug(DEBUG_IO, "reg_start %08X reg_size %08X", + p->reg.start, p->reg.size); + wl1271_debug(DEBUG_IO, "mem2_start %08X mem2_size %08X", + p->mem2.start, p->mem2.size); + wl1271_debug(DEBUG_IO, "mem3_start %08X mem3_size %08X", + p->mem3.start, p->mem3.size); + + ret = wlcore_raw_write32(wl, HW_PART0_START_ADDR, p->mem.start); + if (ret < 0) + goto out; + + ret = wlcore_raw_write32(wl, HW_PART0_SIZE_ADDR, p->mem.size); + if (ret < 0) + goto out; + + ret = wlcore_raw_write32(wl, HW_PART1_START_ADDR, p->reg.start); + if (ret < 0) + goto out; + + ret = wlcore_raw_write32(wl, HW_PART1_SIZE_ADDR, p->reg.size); + if (ret < 0) + goto out; + + ret = wlcore_raw_write32(wl, HW_PART2_START_ADDR, p->mem2.start); + if (ret < 0) + goto out; + + ret = wlcore_raw_write32(wl, HW_PART2_SIZE_ADDR, p->mem2.size); + if (ret < 0) + goto out; + + /* + * We don't need the size of the last partition, as it is + * automatically calculated based on the total memory size and + * the sizes of the previous partitions. + */ + ret = wlcore_raw_write32(wl, HW_PART3_START_ADDR, p->mem3.start); + +out: + return ret; +} +EXPORT_SYMBOL_GPL(wlcore_set_partition); + +void wl1271_io_reset(struct wl1271 *wl) +{ + if (wl->if_ops->reset) + wl->if_ops->reset(wl->dev); +} + +void wl1271_io_init(struct wl1271 *wl) +{ + if (wl->if_ops->init) + wl->if_ops->init(wl->dev); +} diff --git a/drivers/net/wireless/ti/wlcore/io.h b/drivers/net/wireless/ti/wlcore/io.h new file mode 100644 index 0000000..af7d9f9 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/io.h @@ -0,0 +1,234 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 1998-2009 Texas Instruments. All rights reserved. + * Copyright (C) 2008-2010 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __IO_H__ +#define __IO_H__ + +#include + +#define HW_ACCESS_MEMORY_MAX_RANGE 0x1FFC0 + +#define HW_PARTITION_REGISTERS_ADDR 0x1FFC0 +#define HW_PART0_SIZE_ADDR (HW_PARTITION_REGISTERS_ADDR) +#define HW_PART0_START_ADDR (HW_PARTITION_REGISTERS_ADDR + 4) +#define HW_PART1_SIZE_ADDR (HW_PARTITION_REGISTERS_ADDR + 8) +#define HW_PART1_START_ADDR (HW_PARTITION_REGISTERS_ADDR + 12) +#define HW_PART2_SIZE_ADDR (HW_PARTITION_REGISTERS_ADDR + 16) +#define HW_PART2_START_ADDR (HW_PARTITION_REGISTERS_ADDR + 20) +#define HW_PART3_START_ADDR (HW_PARTITION_REGISTERS_ADDR + 24) + +#define HW_ACCESS_REGISTER_SIZE 4 + +#define HW_ACCESS_PRAM_MAX_RANGE 0x3c000 + +struct wl1271; + +void wlcore_disable_interrupts(struct wl1271 *wl); +void wlcore_disable_interrupts_nosync(struct wl1271 *wl); +void wlcore_enable_interrupts(struct wl1271 *wl); +void wlcore_synchronize_interrupts(struct wl1271 *wl); + +void wl1271_io_reset(struct wl1271 *wl); +void wl1271_io_init(struct wl1271 *wl); +int wlcore_translate_addr(struct wl1271 *wl, int addr); + +/* Raw target IO, address is not translated */ +static inline int __must_check wlcore_raw_write(struct wl1271 *wl, int addr, + void *buf, size_t len, + bool fixed) +{ + int ret; + + if (test_bit(WL1271_FLAG_IO_FAILED, &wl->flags)) + return -EIO; + + ret = wl->if_ops->write(wl->dev, addr, buf, len, fixed); + if (ret && wl->state != WLCORE_STATE_OFF) + set_bit(WL1271_FLAG_IO_FAILED, &wl->flags); + + return ret; +} + +static inline int __must_check wlcore_raw_read(struct wl1271 *wl, int addr, + void *buf, size_t len, + bool fixed) +{ + int ret; + + if (test_bit(WL1271_FLAG_IO_FAILED, &wl->flags)) + return -EIO; + + ret = wl->if_ops->read(wl->dev, addr, buf, len, fixed); + if (ret && wl->state != WLCORE_STATE_OFF) + set_bit(WL1271_FLAG_IO_FAILED, &wl->flags); + + return ret; +} + +static inline int __must_check wlcore_raw_read_data(struct wl1271 *wl, int reg, + void *buf, size_t len, + bool fixed) +{ + return wlcore_raw_read(wl, wl->rtable[reg], buf, len, fixed); +} + +static inline int __must_check wlcore_raw_write_data(struct wl1271 *wl, int reg, + void *buf, size_t len, + bool fixed) +{ + return wlcore_raw_write(wl, wl->rtable[reg], buf, len, fixed); +} + +static inline int __must_check wlcore_raw_read32(struct wl1271 *wl, int addr, + u32 *val) +{ + int ret; + + ret = wlcore_raw_read(wl, addr, wl->buffer_32, + sizeof(*wl->buffer_32), false); + if (ret < 0) + return ret; + + if (val) + *val = le32_to_cpu(*wl->buffer_32); + + return 0; +} + +static inline int __must_check wlcore_raw_write32(struct wl1271 *wl, int addr, + u32 val) +{ + *wl->buffer_32 = cpu_to_le32(val); + return wlcore_raw_write(wl, addr, wl->buffer_32, + sizeof(*wl->buffer_32), false); +} + +static inline int __must_check wlcore_read(struct wl1271 *wl, int addr, + void *buf, size_t len, bool fixed) +{ + int physical; + + physical = wlcore_translate_addr(wl, addr); + + return wlcore_raw_read(wl, physical, buf, len, fixed); +} + +static inline int __must_check wlcore_write(struct wl1271 *wl, int addr, + void *buf, size_t len, bool fixed) +{ + int physical; + + physical = wlcore_translate_addr(wl, addr); + + return wlcore_raw_write(wl, physical, buf, len, fixed); +} + +static inline int __must_check wlcore_write_data(struct wl1271 *wl, int reg, + void *buf, size_t len, + bool fixed) +{ + return wlcore_write(wl, wl->rtable[reg], buf, len, fixed); +} + +static inline int __must_check wlcore_read_data(struct wl1271 *wl, int reg, + void *buf, size_t len, + bool fixed) +{ + return wlcore_read(wl, wl->rtable[reg], buf, len, fixed); +} + +static inline int __must_check wlcore_read_hwaddr(struct wl1271 *wl, int hwaddr, + void *buf, size_t len, + bool fixed) +{ + int physical; + int addr; + + /* Addresses are stored internally as addresses to 32 bytes blocks */ + addr = hwaddr << 5; + + physical = wlcore_translate_addr(wl, addr); + + return wlcore_raw_read(wl, physical, buf, len, fixed); +} + +static inline int __must_check wlcore_read32(struct wl1271 *wl, int addr, + u32 *val) +{ + return wlcore_raw_read32(wl, wlcore_translate_addr(wl, addr), val); +} + +static inline int __must_check wlcore_write32(struct wl1271 *wl, int addr, + u32 val) +{ + return wlcore_raw_write32(wl, wlcore_translate_addr(wl, addr), val); +} + +static inline int __must_check wlcore_read_reg(struct wl1271 *wl, int reg, + u32 *val) +{ + return wlcore_raw_read32(wl, + wlcore_translate_addr(wl, wl->rtable[reg]), + val); +} + +static inline int __must_check wlcore_write_reg(struct wl1271 *wl, int reg, + u32 val) +{ + return wlcore_raw_write32(wl, + wlcore_translate_addr(wl, wl->rtable[reg]), + val); +} + +static inline void wl1271_power_off(struct wl1271 *wl) +{ + int ret; + + if (!test_bit(WL1271_FLAG_GPIO_POWER, &wl->flags)) + return; + + ret = wl->if_ops->power(wl->dev, false); + if (!ret) + clear_bit(WL1271_FLAG_GPIO_POWER, &wl->flags); +} + +static inline int wl1271_power_on(struct wl1271 *wl) +{ + int ret = wl->if_ops->power(wl->dev, true); + if (ret == 0) + set_bit(WL1271_FLAG_GPIO_POWER, &wl->flags); + + return ret; +} + +int wlcore_set_partition(struct wl1271 *wl, + const struct wlcore_partition_set *p); + +bool wl1271_set_block_size(struct wl1271 *wl); + +/* Functions from wl1271_main.c */ + +int wl1271_tx_dummy_packet(struct wl1271 *wl); + +#endif diff --git a/drivers/net/wireless/ti/wlcore/main.c b/drivers/net/wireless/ti/wlcore/main.c new file mode 100644 index 0000000..0264c25 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/main.c @@ -0,0 +1,6266 @@ + +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2010 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wlcore.h" +#include "debug.h" +#include "wl12xx_80211.h" +#include "io.h" +#include "event.h" +#include "tx.h" +#include "rx.h" +#include "ps.h" +#include "init.h" +#include "debugfs.h" +#include "cmd.h" +#include "boot.h" +#include "testmode.h" +#include "scan.h" +#include "hw_ops.h" + +#define WL1271_BOOT_RETRIES 3 + +#define WL1271_BOOT_RETRIES 3 + +static char *fwlog_param; +static int bug_on_recovery = -1; +static int no_recovery = -1; + +static void __wl1271_op_remove_interface(struct wl1271 *wl, + struct ieee80211_vif *vif, + bool reset_tx_queues); +static void wlcore_op_stop_locked(struct wl1271 *wl); +static void wl1271_free_ap_keys(struct wl1271 *wl, struct wl12xx_vif *wlvif); + +static int wl12xx_set_authorized(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + int ret; + + if (WARN_ON(wlvif->bss_type != BSS_TYPE_STA_BSS)) + return -EINVAL; + + if (!test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags)) + return 0; + + if (test_and_set_bit(WLVIF_FLAG_STA_STATE_SENT, &wlvif->flags)) + return 0; + + ret = wl12xx_cmd_set_peer_state(wl, wlvif, wlvif->sta.hlid); + if (ret < 0) + return ret; + + wl1271_info("Association completed."); + return 0; +} + +static void wl1271_reg_notify(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct ieee80211_supported_band *band; + struct ieee80211_channel *ch; + int i; + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct wl1271 *wl = hw->priv; + + band = wiphy->bands[IEEE80211_BAND_5GHZ]; + for (i = 0; i < band->n_channels; i++) { + ch = &band->channels[i]; + if (ch->flags & IEEE80211_CHAN_DISABLED) + continue; + + if (ch->flags & IEEE80211_CHAN_RADAR) + ch->flags |= IEEE80211_CHAN_NO_IBSS | + IEEE80211_CHAN_PASSIVE_SCAN; + + } + + wlcore_regdomain_config(wl); +} + +static int wl1271_set_rx_streaming(struct wl1271 *wl, struct wl12xx_vif *wlvif, + bool enable) +{ + int ret = 0; + + /* we should hold wl->mutex */ + ret = wl1271_acx_ps_rx_streaming(wl, wlvif, enable); + if (ret < 0) + goto out; + + if (enable) + set_bit(WLVIF_FLAG_RX_STREAMING_STARTED, &wlvif->flags); + else + clear_bit(WLVIF_FLAG_RX_STREAMING_STARTED, &wlvif->flags); +out: + return ret; +} + +/* + * this function is being called when the rx_streaming interval + * has beed changed or rx_streaming should be disabled + */ +int wl1271_recalc_rx_streaming(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + int ret = 0; + int period = wl->conf.rx_streaming.interval; + + /* don't reconfigure if rx_streaming is disabled */ + if (!test_bit(WLVIF_FLAG_RX_STREAMING_STARTED, &wlvif->flags)) + goto out; + + /* reconfigure/disable according to new streaming_period */ + if (period && + test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags) && + (wl->conf.rx_streaming.always || + test_bit(WL1271_FLAG_SOFT_GEMINI, &wl->flags))) + ret = wl1271_set_rx_streaming(wl, wlvif, true); + else { + ret = wl1271_set_rx_streaming(wl, wlvif, false); + /* don't cancel_work_sync since we might deadlock */ + del_timer_sync(&wlvif->rx_streaming_timer); + } +out: + return ret; +} + +static void wl1271_rx_streaming_enable_work(struct work_struct *work) +{ + int ret; + struct wl12xx_vif *wlvif = container_of(work, struct wl12xx_vif, + rx_streaming_enable_work); + struct wl1271 *wl = wlvif->wl; + + mutex_lock(&wl->mutex); + + if (test_bit(WLVIF_FLAG_RX_STREAMING_STARTED, &wlvif->flags) || + !test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags) || + (!wl->conf.rx_streaming.always && + !test_bit(WL1271_FLAG_SOFT_GEMINI, &wl->flags))) + goto out; + + if (!wl->conf.rx_streaming.interval) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl1271_set_rx_streaming(wl, wlvif, true); + if (ret < 0) + goto out_sleep; + + /* stop it after some time of inactivity */ + mod_timer(&wlvif->rx_streaming_timer, + jiffies + msecs_to_jiffies(wl->conf.rx_streaming.duration)); + +out_sleep: + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); +} + +static void wl1271_rx_streaming_disable_work(struct work_struct *work) +{ + int ret; + struct wl12xx_vif *wlvif = container_of(work, struct wl12xx_vif, + rx_streaming_disable_work); + struct wl1271 *wl = wlvif->wl; + + mutex_lock(&wl->mutex); + + if (!test_bit(WLVIF_FLAG_RX_STREAMING_STARTED, &wlvif->flags)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl1271_set_rx_streaming(wl, wlvif, false); + if (ret) + goto out_sleep; + +out_sleep: + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); +} + +static void wl1271_rx_streaming_timer(unsigned long data) +{ + struct wl12xx_vif *wlvif = (struct wl12xx_vif *)data; + struct wl1271 *wl = wlvif->wl; + ieee80211_queue_work(wl->hw, &wlvif->rx_streaming_disable_work); +} + +/* wl->mutex must be taken */ +void wl12xx_rearm_tx_watchdog_locked(struct wl1271 *wl) +{ + /* if the watchdog is not armed, don't do anything */ + if (wl->tx_allocated_blocks == 0) + return; + + cancel_delayed_work(&wl->tx_watchdog_work); + ieee80211_queue_delayed_work(wl->hw, &wl->tx_watchdog_work, + msecs_to_jiffies(wl->conf.tx.tx_watchdog_timeout)); +} + +static void wl12xx_tx_watchdog_work(struct work_struct *work) +{ + struct delayed_work *dwork; + struct wl1271 *wl; + + dwork = container_of(work, struct delayed_work, work); + wl = container_of(dwork, struct wl1271, tx_watchdog_work); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + /* Tx went out in the meantime - everything is ok */ + if (unlikely(wl->tx_allocated_blocks == 0)) + goto out; + + /* + * if a ROC is in progress, we might not have any Tx for a long + * time (e.g. pending Tx on the non-ROC channels) + */ + if (find_first_bit(wl->roc_map, WL12XX_MAX_ROLES) < WL12XX_MAX_ROLES) { + wl1271_debug(DEBUG_TX, "No Tx (in FW) for %d ms due to ROC", + wl->conf.tx.tx_watchdog_timeout); + wl12xx_rearm_tx_watchdog_locked(wl); + goto out; + } + + /* + * if a scan is in progress, we might not have any Tx for a long + * time + */ + if (wl->scan.state != WL1271_SCAN_STATE_IDLE) { + wl1271_debug(DEBUG_TX, "No Tx (in FW) for %d ms due to scan", + wl->conf.tx.tx_watchdog_timeout); + wl12xx_rearm_tx_watchdog_locked(wl); + goto out; + } + + /* + * AP might cache a frame for a long time for a sleeping station, + * so rearm the timer if there's an AP interface with stations. If + * Tx is genuinely stuck we will most hopefully discover it when all + * stations are removed due to inactivity. + */ + if (wl->active_sta_count) { + wl1271_debug(DEBUG_TX, "No Tx (in FW) for %d ms. AP has " + " %d stations", + wl->conf.tx.tx_watchdog_timeout, + wl->active_sta_count); + wl12xx_rearm_tx_watchdog_locked(wl); + goto out; + } + + wl1271_error("Tx stuck (in FW) for %d ms. Starting recovery", + wl->conf.tx.tx_watchdog_timeout); + wl12xx_queue_recovery_work(wl); + +out: + mutex_unlock(&wl->mutex); +} + +static void wlcore_adjust_conf(struct wl1271 *wl) +{ + /* Adjust settings according to optional module parameters */ + + if (fwlog_param) { + if (!strcmp(fwlog_param, "continuous")) { + wl->conf.fwlog.mode = WL12XX_FWLOG_CONTINUOUS; + } else if (!strcmp(fwlog_param, "ondemand")) { + wl->conf.fwlog.mode = WL12XX_FWLOG_ON_DEMAND; + } else if (!strcmp(fwlog_param, "dbgpins")) { + wl->conf.fwlog.mode = WL12XX_FWLOG_CONTINUOUS; + wl->conf.fwlog.output = WL12XX_FWLOG_OUTPUT_DBG_PINS; + } else if (!strcmp(fwlog_param, "disable")) { + wl->conf.fwlog.mem_blocks = 0; + wl->conf.fwlog.output = WL12XX_FWLOG_OUTPUT_NONE; + } else { + wl1271_error("Unknown fwlog parameter %s", fwlog_param); + } + } + + if (bug_on_recovery != -1) + wl->conf.recovery.bug_on_recovery = (u8) bug_on_recovery; + + if (no_recovery != -1) + wl->conf.recovery.no_recovery = (u8) no_recovery; +} + +static void wl12xx_irq_ps_regulate_link(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + u8 hlid, u8 tx_pkts) +{ + bool fw_ps; + + fw_ps = test_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map); + + /* + * Wake up from high level PS if the STA is asleep with too little + * packets in FW or if the STA is awake. + */ + if (!fw_ps || tx_pkts < WL1271_PS_STA_MAX_PACKETS) + wl12xx_ps_link_end(wl, wlvif, hlid); + + /* + * Start high-level PS if the STA is asleep with enough blocks in FW. + * Make an exception if this is the only connected link. In this + * case FW-memory congestion is less of a problem. + * Note that a single connected STA means 3 active links, since we must + * account for the global and broadcast AP links. The "fw_ps" check + * assures us the third link is a STA connected to the AP. Otherwise + * the FW would not set the PSM bit. + */ + else if (wl->active_link_count > 3 && fw_ps && + tx_pkts >= WL1271_PS_STA_MAX_PACKETS) + wl12xx_ps_link_start(wl, wlvif, hlid, true); +} + +static void wl12xx_irq_update_links_status(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct wl_fw_status_2 *status) +{ + u32 cur_fw_ps_map; + u8 hlid; + + cur_fw_ps_map = le32_to_cpu(status->link_ps_bitmap); + if (wl->ap_fw_ps_map != cur_fw_ps_map) { + wl1271_debug(DEBUG_PSM, + "link ps prev 0x%x cur 0x%x changed 0x%x", + wl->ap_fw_ps_map, cur_fw_ps_map, + wl->ap_fw_ps_map ^ cur_fw_ps_map); + + wl->ap_fw_ps_map = cur_fw_ps_map; + } + + for_each_set_bit(hlid, wlvif->ap.sta_hlid_map, WL12XX_MAX_LINKS) + wl12xx_irq_ps_regulate_link(wl, wlvif, hlid, + wl->links[hlid].allocated_pkts); +} + +static int wlcore_fw_status(struct wl1271 *wl, + struct wl_fw_status_1 *status_1, + struct wl_fw_status_2 *status_2) +{ + struct wl12xx_vif *wlvif; + struct timespec ts; + u32 old_tx_blk_count = wl->tx_blocks_available; + int avail, freed_blocks; + int i; + size_t status_len; + int ret; + struct wl1271_link *lnk; + + status_len = WLCORE_FW_STATUS_1_LEN(wl->num_rx_desc) + + sizeof(*status_2) + wl->fw_status_priv_len; + + ret = wlcore_raw_read_data(wl, REG_RAW_FW_STATUS_ADDR, status_1, + status_len, false); + if (ret < 0) + return ret; + + wl1271_debug(DEBUG_IRQ, "intr: 0x%x (fw_rx_counter = %d, " + "drv_rx_counter = %d, tx_results_counter = %d)", + status_1->intr, + status_1->fw_rx_counter, + status_1->drv_rx_counter, + status_1->tx_results_counter); + + for (i = 0; i < NUM_TX_QUEUES; i++) { + /* prevent wrap-around in freed-packets counter */ + wl->tx_allocated_pkts[i] -= + (status_2->counters.tx_released_pkts[i] - + wl->tx_pkts_freed[i]) & 0xff; + + wl->tx_pkts_freed[i] = status_2->counters.tx_released_pkts[i]; + } + + + for_each_set_bit(i, wl->links_map, WL12XX_MAX_LINKS) { + u8 diff; + lnk = &wl->links[i]; + + /* prevent wrap-around in freed-packets counter */ + diff = (status_2->counters.tx_lnk_free_pkts[i] - + lnk->prev_freed_pkts) & 0xff; + + if (diff == 0) + continue; + + lnk->allocated_pkts -= diff; + lnk->prev_freed_pkts = status_2->counters.tx_lnk_free_pkts[i]; + + /* accumulate the prev_freed_pkts counter */ + lnk->total_freed_pkts += diff; + } + + /* prevent wrap-around in total blocks counter */ + if (likely(wl->tx_blocks_freed <= + le32_to_cpu(status_2->total_released_blks))) + freed_blocks = le32_to_cpu(status_2->total_released_blks) - + wl->tx_blocks_freed; + else + freed_blocks = 0x100000000LL - wl->tx_blocks_freed + + le32_to_cpu(status_2->total_released_blks); + + wl->tx_blocks_freed = le32_to_cpu(status_2->total_released_blks); + + wl->tx_allocated_blocks -= freed_blocks; + + /* + * If the FW freed some blocks: + * If we still have allocated blocks - re-arm the timer, Tx is + * not stuck. Otherwise, cancel the timer (no Tx currently). + */ + if (freed_blocks) { + if (wl->tx_allocated_blocks) + wl12xx_rearm_tx_watchdog_locked(wl); + else + cancel_delayed_work(&wl->tx_watchdog_work); + } + + avail = le32_to_cpu(status_2->tx_total) - wl->tx_allocated_blocks; + + /* + * The FW might change the total number of TX memblocks before + * we get a notification about blocks being released. Thus, the + * available blocks calculation might yield a temporary result + * which is lower than the actual available blocks. Keeping in + * mind that only blocks that were allocated can be moved from + * TX to RX, tx_blocks_available should never decrease here. + */ + wl->tx_blocks_available = max((int)wl->tx_blocks_available, + avail); + + /* if more blocks are available now, tx work can be scheduled */ + if (wl->tx_blocks_available > old_tx_blk_count) + clear_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags); + + /* for AP update num of allocated TX blocks per link and ps status */ + wl12xx_for_each_wlvif_ap(wl, wlvif) { + wl12xx_irq_update_links_status(wl, wlvif, status_2); + } + + /* update the host-chipset time offset */ + getnstimeofday(&ts); + wl->time_offset = (timespec_to_ns(&ts) >> 10) - + (s64)le32_to_cpu(status_2->fw_localtime); + + wl->fw_fast_lnk_map = le32_to_cpu(status_2->link_fast_bitmap); + + return 0; +} + +static void wl1271_flush_deferred_work(struct wl1271 *wl) +{ + struct sk_buff *skb; + + /* Pass all received frames to the network stack */ + while ((skb = skb_dequeue(&wl->deferred_rx_queue))) + ieee80211_rx_ni(wl->hw, skb); + + /* Return sent skbs to the network stack */ + while ((skb = skb_dequeue(&wl->deferred_tx_queue))) + ieee80211_tx_status_ni(wl->hw, skb); +} + +static void wl1271_netstack_work(struct work_struct *work) +{ + struct wl1271 *wl = + container_of(work, struct wl1271, netstack_work); + + do { + wl1271_flush_deferred_work(wl); + } while (skb_queue_len(&wl->deferred_rx_queue)); +} + +#define WL1271_IRQ_MAX_LOOPS 256 + +static int wlcore_irq_locked(struct wl1271 *wl) +{ + int ret = 0; + u32 intr; + int loopcount = WL1271_IRQ_MAX_LOOPS; + bool done = false; + unsigned int defer_count; + unsigned long flags; + + /* + * In case edge triggered interrupt must be used, we cannot iterate + * more than once without introducing race conditions with the hardirq. + */ + if (wl->platform_quirks & WL12XX_PLATFORM_QUIRK_EDGE_IRQ) + loopcount = 1; + + wl1271_debug(DEBUG_IRQ, "IRQ work"); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + while (!done && loopcount--) { + /* + * In order to avoid a race with the hardirq, clear the flag + * before acknowledging the chip. Since the mutex is held, + * wl1271_ps_elp_wakeup cannot be called concurrently. + */ + clear_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags); + smp_mb__after_clear_bit(); + + ret = wlcore_fw_status(wl, wl->fw_status_1, wl->fw_status_2); + if (ret < 0) + goto out; + + wlcore_hw_tx_immediate_compl(wl); + + intr = le32_to_cpu(wl->fw_status_1->intr); + intr &= WLCORE_ALL_INTR_MASK; + if (!intr) { + done = true; + continue; + } + + if (unlikely(intr & WL1271_ACX_INTR_WATCHDOG)) { + wl1271_error("HW watchdog interrupt received! starting recovery."); + wl->watchdog_recovery = true; + ret = -EIO; + + /* restarting the chip. ignore any other interrupt. */ + goto out; + } + + if (unlikely(intr & WL1271_ACX_SW_INTR_WATCHDOG)) { + wl1271_error("SW watchdog interrupt received! " + "starting recovery."); + wl->watchdog_recovery = true; + ret = -EIO; + + /* restarting the chip. ignore any other interrupt. */ + goto out; + } + + if (likely(intr & WL1271_ACX_INTR_DATA)) { + wl1271_debug(DEBUG_IRQ, "WL1271_ACX_INTR_DATA"); + + ret = wlcore_rx(wl, wl->fw_status_1); + if (ret < 0) + goto out; + + /* Check if any tx blocks were freed */ + spin_lock_irqsave(&wl->wl_lock, flags); + if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags) && + wl1271_tx_total_queue_count(wl) > 0) { + spin_unlock_irqrestore(&wl->wl_lock, flags); + /* + * In order to avoid starvation of the TX path, + * call the work function directly. + */ + ret = wlcore_tx_work_locked(wl); + if (ret < 0) + goto out; + } else { + spin_unlock_irqrestore(&wl->wl_lock, flags); + } + + /* check for tx results */ + ret = wlcore_hw_tx_delayed_compl(wl); + if (ret < 0) + goto out; + + /* Make sure the deferred queues don't get too long */ + defer_count = skb_queue_len(&wl->deferred_tx_queue) + + skb_queue_len(&wl->deferred_rx_queue); + if (defer_count > WL1271_DEFERRED_QUEUE_LIMIT) + wl1271_flush_deferred_work(wl); + } + + if (intr & WL1271_ACX_INTR_EVENT_A) { + wl1271_debug(DEBUG_IRQ, "WL1271_ACX_INTR_EVENT_A"); + ret = wl1271_event_handle(wl, 0); + if (ret < 0) + goto out; + } + + if (intr & WL1271_ACX_INTR_EVENT_B) { + wl1271_debug(DEBUG_IRQ, "WL1271_ACX_INTR_EVENT_B"); + ret = wl1271_event_handle(wl, 1); + if (ret < 0) + goto out; + } + + if (intr & WL1271_ACX_INTR_INIT_COMPLETE) + wl1271_debug(DEBUG_IRQ, + "WL1271_ACX_INTR_INIT_COMPLETE"); + + if (intr & WL1271_ACX_INTR_HW_AVAILABLE) + wl1271_debug(DEBUG_IRQ, "WL1271_ACX_INTR_HW_AVAILABLE"); + } + + wl1271_ps_elp_sleep(wl); + +out: + return ret; +} + +static irqreturn_t wlcore_irq(int irq, void *cookie) +{ + int ret; + unsigned long flags; + struct wl1271 *wl = cookie; + + /* complete the ELP completion */ + spin_lock_irqsave(&wl->wl_lock, flags); + set_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags); + if (wl->elp_compl) { + complete(wl->elp_compl); + wl->elp_compl = NULL; + } + + if (test_bit(WL1271_FLAG_SUSPENDED, &wl->flags)) { + /* don't enqueue a work right now. mark it as pending */ + set_bit(WL1271_FLAG_PENDING_WORK, &wl->flags); + wl1271_debug(DEBUG_IRQ, "should not enqueue work"); + disable_irq_nosync(wl->irq); + pm_wakeup_event(wl->dev, 0); + spin_unlock_irqrestore(&wl->wl_lock, flags); + return IRQ_HANDLED; + } + spin_unlock_irqrestore(&wl->wl_lock, flags); + + /* TX might be handled here, avoid redundant work */ + set_bit(WL1271_FLAG_TX_PENDING, &wl->flags); + cancel_work_sync(&wl->tx_work); + + mutex_lock(&wl->mutex); + + ret = wlcore_irq_locked(wl); + if (ret) + wl12xx_queue_recovery_work(wl); + + spin_lock_irqsave(&wl->wl_lock, flags); + /* In case TX was not handled here, queue TX work */ + clear_bit(WL1271_FLAG_TX_PENDING, &wl->flags); + if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags) && + wl1271_tx_total_queue_count(wl) > 0) + ieee80211_queue_work(wl->hw, &wl->tx_work); + spin_unlock_irqrestore(&wl->wl_lock, flags); + + mutex_unlock(&wl->mutex); + + return IRQ_HANDLED; +} + +struct vif_counter_data { + u8 counter; + + struct ieee80211_vif *cur_vif; + bool cur_vif_running; +}; + +static void wl12xx_vif_count_iter(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct vif_counter_data *counter = data; + + counter->counter++; + if (counter->cur_vif == vif) + counter->cur_vif_running = true; +} + +/* caller must not hold wl->mutex, as it might deadlock */ +static void wl12xx_get_vif_count(struct ieee80211_hw *hw, + struct ieee80211_vif *cur_vif, + struct vif_counter_data *data) +{ + memset(data, 0, sizeof(*data)); + data->cur_vif = cur_vif; + + ieee80211_iterate_active_interfaces(hw, IEEE80211_IFACE_ITER_RESUME_ALL, + wl12xx_vif_count_iter, data); +} + +static int wl12xx_fetch_firmware(struct wl1271 *wl, bool plt) +{ + const struct firmware *fw; + const char *fw_name; + enum wl12xx_fw_type fw_type; + int ret; + + if (plt) { + fw_type = WL12XX_FW_TYPE_PLT; + fw_name = wl->plt_fw_name; + } else { + /* + * we can't call wl12xx_get_vif_count() here because + * wl->mutex is taken, so use the cached last_vif_count value + */ + if (wl->last_vif_count > 1 && wl->mr_fw_name) { + fw_type = WL12XX_FW_TYPE_MULTI; + fw_name = wl->mr_fw_name; + } else { + fw_type = WL12XX_FW_TYPE_NORMAL; + fw_name = wl->sr_fw_name; + } + } + + if (wl->fw_type == fw_type) + return 0; + + wl1271_debug(DEBUG_BOOT, "booting firmware %s", fw_name); + + ret = request_firmware(&fw, fw_name, wl->dev); + + if (ret < 0) { + wl1271_error("could not get firmware %s: %d", fw_name, ret); + return ret; + } + + if (fw->size % 4) { + wl1271_error("firmware size is not multiple of 32 bits: %zu", + fw->size); + ret = -EILSEQ; + goto out; + } + + vfree(wl->fw); + wl->fw_type = WL12XX_FW_TYPE_NONE; + wl->fw_len = fw->size; + wl->fw = vmalloc(wl->fw_len); + + if (!wl->fw) { + wl1271_error("could not allocate memory for the firmware"); + ret = -ENOMEM; + goto out; + } + + memcpy(wl->fw, fw->data, wl->fw_len); + ret = 0; + wl->fw_type = fw_type; +out: + release_firmware(fw); + + return ret; +} + +void wl12xx_queue_recovery_work(struct wl1271 *wl) +{ + WARN_ON(!test_bit(WL1271_FLAG_INTENDED_FW_RECOVERY, &wl->flags)); + + /* Avoid a recursive recovery */ + if (wl->state == WLCORE_STATE_ON) { + wl->state = WLCORE_STATE_RESTARTING; + set_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags); + wlcore_disable_interrupts_nosync(wl); + ieee80211_queue_work(wl->hw, &wl->recovery_work); + } +} + +size_t wl12xx_copy_fwlog(struct wl1271 *wl, u8 *memblock, size_t maxlen) +{ + size_t len = 0; + + /* The FW log is a length-value list, find where the log end */ + while (len < maxlen) { + if (memblock[len] == 0) + break; + if (len + memblock[len] + 1 > maxlen) + break; + len += memblock[len] + 1; + } + + /* Make sure we have enough room */ + len = min(len, (size_t)(PAGE_SIZE - wl->fwlog_size)); + + /* Fill the FW log file, consumed by the sysfs fwlog entry */ + memcpy(wl->fwlog + wl->fwlog_size, memblock, len); + wl->fwlog_size += len; + + return len; +} + +#define WLCORE_FW_LOG_END 0x2000000 + +static void wl12xx_read_fwlog_panic(struct wl1271 *wl) +{ + u32 addr; + u32 offset; + u32 end_of_log; + u8 *block; + int ret; + + if ((wl->quirks & WLCORE_QUIRK_FWLOG_NOT_IMPLEMENTED) || + (wl->conf.fwlog.mem_blocks == 0)) + return; + + wl1271_info("Reading FW panic log"); + + block = kmalloc(WL12XX_HW_BLOCK_SIZE, GFP_KERNEL); + if (!block) + return; + + /* + * Make sure the chip is awake and the logger isn't active. + * Do not send a stop fwlog command if the fw is hanged or if + * dbgpins are used (due to some fw bug). + */ + if (wl1271_ps_elp_wakeup(wl)) + goto out; + if (!wl->watchdog_recovery && + wl->conf.fwlog.output != WL12XX_FWLOG_OUTPUT_DBG_PINS) + wl12xx_cmd_stop_fwlog(wl); + + /* Read the first memory block address */ + ret = wlcore_fw_status(wl, wl->fw_status_1, wl->fw_status_2); + if (ret < 0) + goto out; + + addr = le32_to_cpu(wl->fw_status_2->log_start_addr); + if (!addr) + goto out; + + if (wl->conf.fwlog.mode == WL12XX_FWLOG_CONTINUOUS) { + offset = sizeof(addr) + sizeof(struct wl1271_rx_descriptor); + end_of_log = WLCORE_FW_LOG_END; + } else { + offset = sizeof(addr); + end_of_log = addr; + } + + /* Traverse the memory blocks linked list */ + do { + memset(block, 0, WL12XX_HW_BLOCK_SIZE); + ret = wlcore_read_hwaddr(wl, addr, block, WL12XX_HW_BLOCK_SIZE, + false); + if (ret < 0) + goto out; + + /* + * Memory blocks are linked to one another. The first 4 bytes + * of each memory block hold the hardware address of the next + * one. The last memory block points to the first one in + * on demand mode and is equal to 0x2000000 in continuous mode. + */ + addr = le32_to_cpup((__le32 *)block); + if (!wl12xx_copy_fwlog(wl, block + offset, + WL12XX_HW_BLOCK_SIZE - offset)) + break; + } while (addr && (addr != end_of_log)); + + wake_up_interruptible(&wl->fwlog_waitq); + +out: + kfree(block); +} + +static void wlcore_print_recovery(struct wl1271 *wl) +{ + u32 pc = 0; + u32 hint_sts = 0; + int ret; + + wl1271_info("Hardware recovery in progress. FW ver: %s", + wl->chip.fw_ver_str); + + /* change partitions momentarily so we can read the FW pc */ + ret = wlcore_set_partition(wl, &wl->ptable[PART_BOOT]); + if (ret < 0) + return; + + ret = wlcore_read_reg(wl, REG_PC_ON_RECOVERY, &pc); + if (ret < 0) + return; + + ret = wlcore_read_reg(wl, REG_INTERRUPT_NO_CLEAR, &hint_sts); + if (ret < 0) + return; + + wl1271_info("pc: 0x%x, hint_sts: 0x%08x count: %d", + pc, hint_sts, ++wl->recovery_count); + + wlcore_set_partition(wl, &wl->ptable[PART_WORK]); +} + + +static void wl1271_recovery_work(struct work_struct *work) +{ + struct wl1271 *wl = + container_of(work, struct wl1271, recovery_work); + struct wl12xx_vif *wlvif; + struct ieee80211_vif *vif; + + mutex_lock(&wl->mutex); + + if (wl->state == WLCORE_STATE_OFF || wl->plt) + goto out_unlock; + + if (!test_bit(WL1271_FLAG_INTENDED_FW_RECOVERY, &wl->flags)) { + wl12xx_read_fwlog_panic(wl); + wlcore_print_recovery(wl); + } + + BUG_ON(wl->conf.recovery.bug_on_recovery && + !test_bit(WL1271_FLAG_INTENDED_FW_RECOVERY, &wl->flags)); + + if (wl->conf.recovery.no_recovery) { + wl1271_info("No recovery (chosen on module load). Fw will remain stuck."); + goto out_unlock; + } + + /* Prevent spurious TX during FW restart */ + wlcore_stop_queues(wl, WLCORE_QUEUE_STOP_REASON_FW_RESTART); + + /* reboot the chipset */ + while (!list_empty(&wl->wlvif_list)) { + wlvif = list_first_entry(&wl->wlvif_list, + struct wl12xx_vif, list); + vif = wl12xx_wlvif_to_vif(wlvif); + __wl1271_op_remove_interface(wl, vif, false); + } + + wlcore_op_stop_locked(wl); + + ieee80211_restart_hw(wl->hw); + + /* + * Its safe to enable TX now - the queues are stopped after a request + * to restart the HW. + */ + wlcore_wake_queues(wl, WLCORE_QUEUE_STOP_REASON_FW_RESTART); + +out_unlock: + wl->watchdog_recovery = false; + clear_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags); + mutex_unlock(&wl->mutex); +} + +static int wlcore_fw_wakeup(struct wl1271 *wl) +{ + return wlcore_raw_write32(wl, HW_ACCESS_ELP_CTRL_REG, ELPCTRL_WAKE_UP); +} + +static int wl1271_setup(struct wl1271 *wl) +{ + wl->fw_status_1 = kmalloc(WLCORE_FW_STATUS_1_LEN(wl->num_rx_desc) + + sizeof(*wl->fw_status_2) + + wl->fw_status_priv_len, GFP_KERNEL); + if (!wl->fw_status_1) + return -ENOMEM; + + wl->fw_status_2 = (struct wl_fw_status_2 *) + (((u8 *) wl->fw_status_1) + + WLCORE_FW_STATUS_1_LEN(wl->num_rx_desc)); + + wl->tx_res_if = kmalloc(sizeof(*wl->tx_res_if), GFP_KERNEL); + if (!wl->tx_res_if) { + kfree(wl->fw_status_1); + return -ENOMEM; + } + + return 0; +} + +static int wl12xx_set_power_on(struct wl1271 *wl) +{ + int ret; + + msleep(WL1271_PRE_POWER_ON_SLEEP); + ret = wl1271_power_on(wl); + if (ret < 0) + goto out; + msleep(WL1271_POWER_ON_SLEEP); + wl1271_io_reset(wl); + wl1271_io_init(wl); + + ret = wlcore_set_partition(wl, &wl->ptable[PART_BOOT]); + if (ret < 0) + goto fail; + + /* ELP module wake up */ + ret = wlcore_fw_wakeup(wl); + if (ret < 0) + goto fail; + +out: + return ret; + +fail: + wl1271_power_off(wl); + return ret; +} + +static int wl12xx_chip_wakeup(struct wl1271 *wl, bool plt) +{ + int ret = 0; + + ret = wl12xx_set_power_on(wl); + if (ret < 0) + goto out; + + /* + * For wl127x based devices we could use the default block + * size (512 bytes), but due to a bug in the sdio driver, we + * need to set it explicitly after the chip is powered on. To + * simplify the code and since the performance impact is + * negligible, we use the same block size for all different + * chip types. + * + * Check if the bus supports blocksize alignment and, if it + * doesn't, make sure we don't have the quirk. + */ + if (!wl1271_set_block_size(wl)) + wl->quirks &= ~WLCORE_QUIRK_TX_BLOCKSIZE_ALIGN; + + /* TODO: make sure the lower driver has set things up correctly */ + + ret = wl1271_setup(wl); + if (ret < 0) + goto out; + + ret = wl12xx_fetch_firmware(wl, plt); + if (ret < 0) + goto out; + +out: + return ret; +} + +int wl1271_plt_start(struct wl1271 *wl, const enum plt_mode plt_mode) +{ + int retries = WL1271_BOOT_RETRIES; + struct wiphy *wiphy = wl->hw->wiphy; + + static const char* const PLT_MODE[] = { + "PLT_OFF", + "PLT_ON", + "PLT_FEM_DETECT" + }; + + int ret; + + mutex_lock(&wl->mutex); + + wl1271_notice("power up"); + + if (wl->state != WLCORE_STATE_OFF) { + wl1271_error("cannot go into PLT state because not " + "in off state: %d", wl->state); + ret = -EBUSY; + goto out; + } + + /* Indicate to lower levels that we are now in PLT mode */ + wl->plt = true; + wl->plt_mode = plt_mode; + + while (retries) { + retries--; + ret = wl12xx_chip_wakeup(wl, true); + if (ret < 0) + goto power_off; + + ret = wl->ops->plt_init(wl); + if (ret < 0) + goto power_off; + + wl->state = WLCORE_STATE_ON; + wl1271_notice("firmware booted in PLT mode %s (%s)", + PLT_MODE[plt_mode], + wl->chip.fw_ver_str); + + /* update hw/fw version info in wiphy struct */ + wiphy->hw_version = wl->chip.id; + strncpy(wiphy->fw_version, wl->chip.fw_ver_str, + sizeof(wiphy->fw_version)); + + goto out; + +power_off: + wl1271_power_off(wl); + } + + wl->plt = false; + wl->plt_mode = PLT_OFF; + + wl1271_error("firmware boot in PLT mode failed despite %d retries", + WL1271_BOOT_RETRIES); +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +int wl1271_plt_stop(struct wl1271 *wl) +{ + int ret = 0; + + wl1271_notice("power down"); + + /* + * Interrupts must be disabled before setting the state to OFF. + * Otherwise, the interrupt handler might be called and exit without + * reading the interrupt status. + */ + wlcore_disable_interrupts(wl); + mutex_lock(&wl->mutex); + if (!wl->plt) { + mutex_unlock(&wl->mutex); + + /* + * This will not necessarily enable interrupts as interrupts + * may have been disabled when op_stop was called. It will, + * however, balance the above call to disable_interrupts(). + */ + wlcore_enable_interrupts(wl); + + wl1271_error("cannot power down because not in PLT " + "state: %d", wl->state); + ret = -EBUSY; + goto out; + } + + mutex_unlock(&wl->mutex); + + wl1271_flush_deferred_work(wl); + cancel_work_sync(&wl->netstack_work); + cancel_work_sync(&wl->recovery_work); + cancel_delayed_work_sync(&wl->elp_work); + cancel_delayed_work_sync(&wl->tx_watchdog_work); + + mutex_lock(&wl->mutex); + wl1271_power_off(wl); + wl->flags = 0; + wl->sleep_auth = WL1271_PSM_ILLEGAL; + wl->state = WLCORE_STATE_OFF; + wl->plt = false; + wl->plt_mode = PLT_OFF; + wl->rx_counter = 0; + mutex_unlock(&wl->mutex); + +out: + return ret; +} + +static void wl1271_op_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct wl1271 *wl = hw->priv; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_vif *vif = info->control.vif; + struct wl12xx_vif *wlvif = NULL; + unsigned long flags; + int q, mapping; + u8 hlid; + + if (!vif) { + wl1271_debug(DEBUG_TX, "DROP skb with no vif"); + ieee80211_free_txskb(hw, skb); + return; + } + + wlvif = wl12xx_vif_to_data(vif); + mapping = skb_get_queue_mapping(skb); + q = wl1271_tx_get_queue(mapping); + + hlid = wl12xx_tx_get_hlid(wl, wlvif, skb, control->sta); + + spin_lock_irqsave(&wl->wl_lock, flags); + + /* + * drop the packet if the link is invalid or the queue is stopped + * for any reason but watermark. Watermark is a "soft"-stop so we + * allow these packets through. + */ + if (hlid == WL12XX_INVALID_LINK_ID || + (!test_bit(hlid, wlvif->links_map)) || + (wlcore_is_queue_stopped_locked(wl, wlvif, q) && + !wlcore_is_queue_stopped_by_reason_locked(wl, wlvif, q, + WLCORE_QUEUE_STOP_REASON_WATERMARK))) { + wl1271_debug(DEBUG_TX, "DROP skb hlid %d q %d", hlid, q); + ieee80211_free_txskb(hw, skb); + goto out; + } + + wl1271_debug(DEBUG_TX, "queue skb hlid %d q %d len %d", + hlid, q, skb->len); + skb_queue_tail(&wl->links[hlid].tx_queue[q], skb); + + wl->tx_queue_count[q]++; + wlvif->tx_queue_count[q]++; + + /* + * The workqueue is slow to process the tx_queue and we need stop + * the queue here, otherwise the queue will get too long. + */ + if (wlvif->tx_queue_count[q] >= WL1271_TX_QUEUE_HIGH_WATERMARK && + !wlcore_is_queue_stopped_by_reason_locked(wl, wlvif, q, + WLCORE_QUEUE_STOP_REASON_WATERMARK)) { + wl1271_debug(DEBUG_TX, "op_tx: stopping queues for q %d", q); + wlcore_stop_queue_locked(wl, wlvif, q, + WLCORE_QUEUE_STOP_REASON_WATERMARK); + } + + /* + * The chip specific setup must run before the first TX packet - + * before that, the tx_work will not be initialized! + */ + + if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags) && + !test_bit(WL1271_FLAG_TX_PENDING, &wl->flags)) + ieee80211_queue_work(wl->hw, &wl->tx_work); + +out: + spin_unlock_irqrestore(&wl->wl_lock, flags); +} + +int wl1271_tx_dummy_packet(struct wl1271 *wl) +{ + unsigned long flags; + int q; + + /* no need to queue a new dummy packet if one is already pending */ + if (test_bit(WL1271_FLAG_DUMMY_PACKET_PENDING, &wl->flags)) + return 0; + + q = wl1271_tx_get_queue(skb_get_queue_mapping(wl->dummy_packet)); + + spin_lock_irqsave(&wl->wl_lock, flags); + set_bit(WL1271_FLAG_DUMMY_PACKET_PENDING, &wl->flags); + wl->tx_queue_count[q]++; + spin_unlock_irqrestore(&wl->wl_lock, flags); + + /* The FW is low on RX memory blocks, so send the dummy packet asap */ + if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags)) + return wlcore_tx_work_locked(wl); + + /* + * If the FW TX is busy, TX work will be scheduled by the threaded + * interrupt handler function + */ + return 0; +} + +/* + * The size of the dummy packet should be at least 1400 bytes. However, in + * order to minimize the number of bus transactions, aligning it to 512 bytes + * boundaries could be beneficial, performance wise + */ +#define TOTAL_TX_DUMMY_PACKET_SIZE (ALIGN(1400, 512)) + +static struct sk_buff *wl12xx_alloc_dummy_packet(struct wl1271 *wl) +{ + struct sk_buff *skb; + struct ieee80211_hdr_3addr *hdr; + unsigned int dummy_packet_size; + + dummy_packet_size = TOTAL_TX_DUMMY_PACKET_SIZE - + sizeof(struct wl1271_tx_hw_descr) - sizeof(*hdr); + + skb = dev_alloc_skb(TOTAL_TX_DUMMY_PACKET_SIZE); + if (!skb) { + wl1271_warning("Failed to allocate a dummy packet skb"); + return NULL; + } + + skb_reserve(skb, sizeof(struct wl1271_tx_hw_descr)); + + hdr = (struct ieee80211_hdr_3addr *) skb_put(skb, sizeof(*hdr)); + memset(hdr, 0, sizeof(*hdr)); + hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | + IEEE80211_STYPE_NULLFUNC | + IEEE80211_FCTL_TODS); + + memset(skb_put(skb, dummy_packet_size), 0, dummy_packet_size); + + /* Dummy packets require the TID to be management */ + skb->priority = WL1271_TID_MGMT; + + /* Initialize all fields that might be used */ + skb_set_queue_mapping(skb, 0); + memset(IEEE80211_SKB_CB(skb), 0, sizeof(struct ieee80211_tx_info)); + + return skb; +} + + +#ifdef CONFIG_PM +static int +wl1271_validate_wowlan_pattern(struct cfg80211_wowlan_trig_pkt_pattern *p) +{ + int num_fields = 0, in_field = 0, fields_size = 0; + int i, pattern_len = 0; + + if (!p->mask) { + wl1271_warning("No mask in WoWLAN pattern"); + return -EINVAL; + } + + /* + * The pattern is broken up into segments of bytes at different offsets + * that need to be checked by the FW filter. Each segment is called + * a field in the FW API. We verify that the total number of fields + * required for this pattern won't exceed FW limits (8) + * as well as the total fields buffer won't exceed the FW limit. + * Note that if there's a pattern which crosses Ethernet/IP header + * boundary a new field is required. + */ + for (i = 0; i < p->pattern_len; i++) { + if (test_bit(i, (unsigned long *)p->mask)) { + if (!in_field) { + in_field = 1; + pattern_len = 1; + } else { + if (i == WL1271_RX_FILTER_ETH_HEADER_SIZE) { + num_fields++; + fields_size += pattern_len + + RX_FILTER_FIELD_OVERHEAD; + pattern_len = 1; + } else + pattern_len++; + } + } else { + if (in_field) { + in_field = 0; + fields_size += pattern_len + + RX_FILTER_FIELD_OVERHEAD; + num_fields++; + } + } + } + + if (in_field) { + fields_size += pattern_len + RX_FILTER_FIELD_OVERHEAD; + num_fields++; + } + + if (num_fields > WL1271_RX_FILTER_MAX_FIELDS) { + wl1271_warning("RX Filter too complex. Too many segments"); + return -EINVAL; + } + + if (fields_size > WL1271_RX_FILTER_MAX_FIELDS_SIZE) { + wl1271_warning("RX filter pattern is too big"); + return -E2BIG; + } + + return 0; +} + +struct wl12xx_rx_filter *wl1271_rx_filter_alloc(void) +{ + return kzalloc(sizeof(struct wl12xx_rx_filter), GFP_KERNEL); +} + +void wl1271_rx_filter_free(struct wl12xx_rx_filter *filter) +{ + int i; + + if (filter == NULL) + return; + + for (i = 0; i < filter->num_fields; i++) + kfree(filter->fields[i].pattern); + + kfree(filter); +} + +int wl1271_rx_filter_alloc_field(struct wl12xx_rx_filter *filter, + u16 offset, u8 flags, + u8 *pattern, u8 len) +{ + struct wl12xx_rx_filter_field *field; + + if (filter->num_fields == WL1271_RX_FILTER_MAX_FIELDS) { + wl1271_warning("Max fields per RX filter. can't alloc another"); + return -EINVAL; + } + + field = &filter->fields[filter->num_fields]; + + field->pattern = kzalloc(len, GFP_KERNEL); + if (!field->pattern) { + wl1271_warning("Failed to allocate RX filter pattern"); + return -ENOMEM; + } + + filter->num_fields++; + + field->offset = cpu_to_le16(offset); + field->flags = flags; + field->len = len; + memcpy(field->pattern, pattern, len); + + return 0; +} + +int wl1271_rx_filter_get_fields_size(struct wl12xx_rx_filter *filter) +{ + int i, fields_size = 0; + + for (i = 0; i < filter->num_fields; i++) + fields_size += filter->fields[i].len + + sizeof(struct wl12xx_rx_filter_field) - + sizeof(u8 *); + + return fields_size; +} + +void wl1271_rx_filter_flatten_fields(struct wl12xx_rx_filter *filter, + u8 *buf) +{ + int i; + struct wl12xx_rx_filter_field *field; + + for (i = 0; i < filter->num_fields; i++) { + field = (struct wl12xx_rx_filter_field *)buf; + + field->offset = filter->fields[i].offset; + field->flags = filter->fields[i].flags; + field->len = filter->fields[i].len; + + memcpy(&field->pattern, filter->fields[i].pattern, field->len); + buf += sizeof(struct wl12xx_rx_filter_field) - + sizeof(u8 *) + field->len; + } +} + +/* + * Allocates an RX filter returned through f + * which needs to be freed using rx_filter_free() + */ +static int wl1271_convert_wowlan_pattern_to_rx_filter( + struct cfg80211_wowlan_trig_pkt_pattern *p, + struct wl12xx_rx_filter **f) +{ + int i, j, ret = 0; + struct wl12xx_rx_filter *filter; + u16 offset; + u8 flags, len; + + filter = wl1271_rx_filter_alloc(); + if (!filter) { + wl1271_warning("Failed to alloc rx filter"); + ret = -ENOMEM; + goto err; + } + + i = 0; + while (i < p->pattern_len) { + if (!test_bit(i, (unsigned long *)p->mask)) { + i++; + continue; + } + + for (j = i; j < p->pattern_len; j++) { + if (!test_bit(j, (unsigned long *)p->mask)) + break; + + if (i < WL1271_RX_FILTER_ETH_HEADER_SIZE && + j >= WL1271_RX_FILTER_ETH_HEADER_SIZE) + break; + } + + if (i < WL1271_RX_FILTER_ETH_HEADER_SIZE) { + offset = i; + flags = WL1271_RX_FILTER_FLAG_ETHERNET_HEADER; + } else { + offset = i - WL1271_RX_FILTER_ETH_HEADER_SIZE; + flags = WL1271_RX_FILTER_FLAG_IP_HEADER; + } + + len = j - i; + + ret = wl1271_rx_filter_alloc_field(filter, + offset, + flags, + &p->pattern[i], len); + if (ret) + goto err; + + i = j; + } + + filter->action = FILTER_SIGNAL; + + *f = filter; + return 0; + +err: + wl1271_rx_filter_free(filter); + *f = NULL; + + return ret; +} + +static int wl1271_configure_wowlan(struct wl1271 *wl, + struct cfg80211_wowlan *wow) +{ + int i, ret; + + if (!wow || wow->any || !wow->n_patterns) { + ret = wl1271_acx_default_rx_filter_enable(wl, 0, + FILTER_SIGNAL); + if (ret) + goto out; + + ret = wl1271_rx_filter_clear_all(wl); + if (ret) + goto out; + + return 0; + } + + if (WARN_ON(wow->n_patterns > WL1271_MAX_RX_FILTERS)) + return -EINVAL; + + /* Validate all incoming patterns before clearing current FW state */ + for (i = 0; i < wow->n_patterns; i++) { + ret = wl1271_validate_wowlan_pattern(&wow->patterns[i]); + if (ret) { + wl1271_warning("Bad wowlan pattern %d", i); + return ret; + } + } + + ret = wl1271_acx_default_rx_filter_enable(wl, 0, FILTER_SIGNAL); + if (ret) + goto out; + + ret = wl1271_rx_filter_clear_all(wl); + if (ret) + goto out; + + /* Translate WoWLAN patterns into filters */ + for (i = 0; i < wow->n_patterns; i++) { + struct cfg80211_wowlan_trig_pkt_pattern *p; + struct wl12xx_rx_filter *filter = NULL; + + p = &wow->patterns[i]; + + ret = wl1271_convert_wowlan_pattern_to_rx_filter(p, &filter); + if (ret) { + wl1271_warning("Failed to create an RX filter from " + "wowlan pattern %d", i); + goto out; + } + + ret = wl1271_rx_filter_enable(wl, i, 1, filter); + + wl1271_rx_filter_free(filter); + if (ret) + goto out; + } + + ret = wl1271_acx_default_rx_filter_enable(wl, 1, FILTER_DROP); + +out: + return ret; +} + +static int wl1271_configure_suspend_sta(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct cfg80211_wowlan *wow) +{ + int ret = 0; + + if (!test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl1271_configure_wowlan(wl, wow); + if (ret < 0) + goto out_sleep; + + if ((wl->conf.conn.suspend_wake_up_event == + wl->conf.conn.wake_up_event) && + (wl->conf.conn.suspend_listen_interval == + wl->conf.conn.listen_interval)) + goto out_sleep; + + ret = wl1271_acx_wake_up_conditions(wl, wlvif, + wl->conf.conn.suspend_wake_up_event, + wl->conf.conn.suspend_listen_interval); + + if (ret < 0) + wl1271_error("suspend: set wake up conditions failed: %d", ret); + +out_sleep: + wl1271_ps_elp_sleep(wl); +out: + return ret; + +} + +static int wl1271_configure_suspend_ap(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + int ret = 0; + + if (!test_bit(WLVIF_FLAG_AP_STARTED, &wlvif->flags)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl1271_acx_beacon_filter_opt(wl, wlvif, true); + + wl1271_ps_elp_sleep(wl); +out: + return ret; + +} + +static int wl1271_configure_suspend(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct cfg80211_wowlan *wow) +{ + if (wlvif->bss_type == BSS_TYPE_STA_BSS) + return wl1271_configure_suspend_sta(wl, wlvif, wow); + if (wlvif->bss_type == BSS_TYPE_AP_BSS) + return wl1271_configure_suspend_ap(wl, wlvif); + return 0; +} + +static void wl1271_configure_resume(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + int ret = 0; + bool is_ap = wlvif->bss_type == BSS_TYPE_AP_BSS; + bool is_sta = wlvif->bss_type == BSS_TYPE_STA_BSS; + + if ((!is_ap) && (!is_sta)) + return; + + if (is_sta && !test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags)) + return; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + return; + + if (is_sta) { + wl1271_configure_wowlan(wl, NULL); + + if ((wl->conf.conn.suspend_wake_up_event == + wl->conf.conn.wake_up_event) && + (wl->conf.conn.suspend_listen_interval == + wl->conf.conn.listen_interval)) + goto out_sleep; + + ret = wl1271_acx_wake_up_conditions(wl, wlvif, + wl->conf.conn.wake_up_event, + wl->conf.conn.listen_interval); + + if (ret < 0) + wl1271_error("resume: wake up conditions failed: %d", + ret); + + } else if (is_ap) { + ret = wl1271_acx_beacon_filter_opt(wl, wlvif, false); + } + +out_sleep: + wl1271_ps_elp_sleep(wl); +} + +static int wl1271_op_suspend(struct ieee80211_hw *hw, + struct cfg80211_wowlan *wow) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif; + int ret; + + wl1271_debug(DEBUG_MAC80211, "mac80211 suspend wow=%d", !!wow); + WARN_ON(!wow); + + /* we want to perform the recovery before suspending */ + if (test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags)) { + wl1271_warning("postponing suspend to perform recovery"); + return -EBUSY; + } + + wl1271_tx_flush(wl); + + mutex_lock(&wl->mutex); + wl->wow_enabled = true; + wl12xx_for_each_wlvif(wl, wlvif) { + ret = wl1271_configure_suspend(wl, wlvif, wow); + if (ret < 0) { + mutex_unlock(&wl->mutex); + wl1271_warning("couldn't prepare device to suspend"); + return ret; + } + } + mutex_unlock(&wl->mutex); + /* flush any remaining work */ + wl1271_debug(DEBUG_MAC80211, "flushing remaining works"); + + /* + * disable and re-enable interrupts in order to flush + * the threaded_irq + */ + wlcore_disable_interrupts(wl); + + /* + * set suspended flag to avoid triggering a new threaded_irq + * work. no need for spinlock as interrupts are disabled. + */ + set_bit(WL1271_FLAG_SUSPENDED, &wl->flags); + + wlcore_enable_interrupts(wl); + flush_work(&wl->tx_work); + flush_delayed_work(&wl->elp_work); + + return 0; +} + +static int wl1271_op_resume(struct ieee80211_hw *hw) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif; + unsigned long flags; + bool run_irq_work = false, pending_recovery; + int ret; + + wl1271_debug(DEBUG_MAC80211, "mac80211 resume wow=%d", + wl->wow_enabled); + WARN_ON(!wl->wow_enabled); + + /* + * re-enable irq_work enqueuing, and call irq_work directly if + * there is a pending work. + */ + spin_lock_irqsave(&wl->wl_lock, flags); + clear_bit(WL1271_FLAG_SUSPENDED, &wl->flags); + if (test_and_clear_bit(WL1271_FLAG_PENDING_WORK, &wl->flags)) + run_irq_work = true; + spin_unlock_irqrestore(&wl->wl_lock, flags); + + mutex_lock(&wl->mutex); + + /* test the recovery flag before calling any SDIO functions */ + pending_recovery = test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, + &wl->flags); + + if (run_irq_work) { + wl1271_debug(DEBUG_MAC80211, + "run postponed irq_work directly"); + + /* don't talk to the HW if recovery is pending */ + if (!pending_recovery) { + ret = wlcore_irq_locked(wl); + if (ret) + wl12xx_queue_recovery_work(wl); + } + + wlcore_enable_interrupts(wl); + } + + if (pending_recovery) { + wl1271_warning("queuing forgotten recovery on resume"); + ieee80211_queue_work(wl->hw, &wl->recovery_work); + goto out; + } + + wl12xx_for_each_wlvif(wl, wlvif) { + wl1271_configure_resume(wl, wlvif); + } + +out: + wl->wow_enabled = false; + mutex_unlock(&wl->mutex); + + return 0; +} +#endif + +static int wl1271_op_start(struct ieee80211_hw *hw) +{ + wl1271_debug(DEBUG_MAC80211, "mac80211 start"); + + /* + * We have to delay the booting of the hardware because + * we need to know the local MAC address before downloading and + * initializing the firmware. The MAC address cannot be changed + * after boot, and without the proper MAC address, the firmware + * will not function properly. + * + * The MAC address is first known when the corresponding interface + * is added. That is where we will initialize the hardware. + */ + + return 0; +} + +static void wlcore_op_stop_locked(struct wl1271 *wl) +{ + int i; + + if (wl->state == WLCORE_STATE_OFF) { + if (test_and_clear_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, + &wl->flags)) + wlcore_enable_interrupts(wl); + + return; + } + + /* + * this must be before the cancel_work calls below, so that the work + * functions don't perform further work. + */ + wl->state = WLCORE_STATE_OFF; + + /* + * Use the nosync variant to disable interrupts, so the mutex could be + * held while doing so without deadlocking. + */ + wlcore_disable_interrupts_nosync(wl); + + mutex_unlock(&wl->mutex); + + wlcore_synchronize_interrupts(wl); + if (!test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags)) + cancel_work_sync(&wl->recovery_work); + wl1271_flush_deferred_work(wl); + cancel_delayed_work_sync(&wl->scan_complete_work); + cancel_work_sync(&wl->netstack_work); + cancel_work_sync(&wl->tx_work); + cancel_delayed_work_sync(&wl->elp_work); + cancel_delayed_work_sync(&wl->tx_watchdog_work); + + /* let's notify MAC80211 about the remaining pending TX frames */ + mutex_lock(&wl->mutex); + wl12xx_tx_reset(wl); + + wl1271_power_off(wl); + /* + * In case a recovery was scheduled, interrupts were disabled to avoid + * an interrupt storm. Now that the power is down, it is safe to + * re-enable interrupts to balance the disable depth + */ + if (test_and_clear_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags)) + wlcore_enable_interrupts(wl); + + wl->band = IEEE80211_BAND_2GHZ; + + wl->rx_counter = 0; + wl->power_level = WL1271_DEFAULT_POWER_LEVEL; + wl->channel_type = NL80211_CHAN_NO_HT; + wl->tx_blocks_available = 0; + wl->tx_allocated_blocks = 0; + wl->tx_results_count = 0; + wl->tx_packets_count = 0; + wl->time_offset = 0; + wl->ap_fw_ps_map = 0; + wl->ap_ps_map = 0; + wl->sleep_auth = WL1271_PSM_ILLEGAL; + memset(wl->roles_map, 0, sizeof(wl->roles_map)); + memset(wl->links_map, 0, sizeof(wl->links_map)); + memset(wl->roc_map, 0, sizeof(wl->roc_map)); + memset(wl->session_ids, 0, sizeof(wl->session_ids)); + wl->active_sta_count = 0; + wl->active_link_count = 0; + + /* The system link is always allocated */ + wl->links[WL12XX_SYSTEM_HLID].allocated_pkts = 0; + wl->links[WL12XX_SYSTEM_HLID].prev_freed_pkts = 0; + __set_bit(WL12XX_SYSTEM_HLID, wl->links_map); + + /* + * this is performed after the cancel_work calls and the associated + * mutex_lock, so that wl1271_op_add_interface does not accidentally + * get executed before all these vars have been reset. + */ + wl->flags = 0; + + wl->tx_blocks_freed = 0; + + for (i = 0; i < NUM_TX_QUEUES; i++) { + wl->tx_pkts_freed[i] = 0; + wl->tx_allocated_pkts[i] = 0; + } + + wl1271_debugfs_reset(wl); + + kfree(wl->fw_status_1); + wl->fw_status_1 = NULL; + wl->fw_status_2 = NULL; + kfree(wl->tx_res_if); + wl->tx_res_if = NULL; + kfree(wl->target_mem_map); + wl->target_mem_map = NULL; + + /* + * FW channels must be re-calibrated after recovery, + * clear the last Reg-Domain channel configuration. + */ + memset(wl->reg_ch_conf_last, 0, sizeof(wl->reg_ch_conf_last)); +} + +static void wlcore_op_stop(struct ieee80211_hw *hw) +{ + struct wl1271 *wl = hw->priv; + + wl1271_debug(DEBUG_MAC80211, "mac80211 stop"); + + mutex_lock(&wl->mutex); + + wlcore_op_stop_locked(wl); + + mutex_unlock(&wl->mutex); +} + +static void wlcore_channel_switch_work(struct work_struct *work) +{ + struct delayed_work *dwork; + struct wl1271 *wl; + struct ieee80211_vif *vif; + struct wl12xx_vif *wlvif; + int ret; + + dwork = container_of(work, struct delayed_work, work); + wlvif = container_of(dwork, struct wl12xx_vif, channel_switch_work); + wl = wlvif->wl; + + wl1271_info("channel switch failed (role_id: %d).", wlvif->role_id); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + /* check the channel switch is still ongoing */ + if (!test_and_clear_bit(WLVIF_FLAG_CS_PROGRESS, &wlvif->flags)) + goto out; + + vif = wl12xx_wlvif_to_vif(wlvif); + ieee80211_chswitch_done(vif, false); + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl12xx_cmd_stop_channel_switch(wl, wlvif); + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); +} + +static void wlcore_connection_loss_work(struct work_struct *work) +{ + struct delayed_work *dwork; + struct wl1271 *wl; + struct ieee80211_vif *vif; + struct wl12xx_vif *wlvif; + + dwork = container_of(work, struct delayed_work, work); + wlvif = container_of(dwork, struct wl12xx_vif, connection_loss_work); + wl = wlvif->wl; + + wl1271_info("Connection loss work (role_id: %d).", wlvif->role_id); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + /* Call mac80211 connection loss */ + if (!test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags)) + goto out; + + vif = wl12xx_wlvif_to_vif(wlvif); + ieee80211_connection_loss(vif); +out: + mutex_unlock(&wl->mutex); +} + +static int wl12xx_allocate_rate_policy(struct wl1271 *wl, u8 *idx) +{ + u8 policy = find_first_zero_bit(wl->rate_policies_map, + WL12XX_MAX_RATE_POLICIES); + if (policy >= WL12XX_MAX_RATE_POLICIES) + return -EBUSY; + + __set_bit(policy, wl->rate_policies_map); + *idx = policy; + return 0; +} + +static void wl12xx_free_rate_policy(struct wl1271 *wl, u8 *idx) +{ + if (WARN_ON(*idx >= WL12XX_MAX_RATE_POLICIES)) + return; + + __clear_bit(*idx, wl->rate_policies_map); + *idx = WL12XX_MAX_RATE_POLICIES; +} + +static int wlcore_allocate_klv_template(struct wl1271 *wl, u8 *idx) +{ + u8 policy = find_first_zero_bit(wl->klv_templates_map, + WLCORE_MAX_KLV_TEMPLATES); + if (policy >= WLCORE_MAX_KLV_TEMPLATES) + return -EBUSY; + + __set_bit(policy, wl->klv_templates_map); + *idx = policy; + return 0; +} + +static void wlcore_free_klv_template(struct wl1271 *wl, u8 *idx) +{ + if (WARN_ON(*idx >= WLCORE_MAX_KLV_TEMPLATES)) + return; + + __clear_bit(*idx, wl->klv_templates_map); + *idx = WLCORE_MAX_KLV_TEMPLATES; +} + +static u8 wl12xx_get_role_type(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + switch (wlvif->bss_type) { + case BSS_TYPE_AP_BSS: + if (wlvif->p2p) + return WL1271_ROLE_P2P_GO; + else + return WL1271_ROLE_AP; + + case BSS_TYPE_STA_BSS: + if (wlvif->p2p) + return WL1271_ROLE_P2P_CL; + else + return WL1271_ROLE_STA; + + case BSS_TYPE_IBSS: + return WL1271_ROLE_IBSS; + + default: + wl1271_error("invalid bss_type: %d", wlvif->bss_type); + } + return WL12XX_INVALID_ROLE_TYPE; +} + +static int wl12xx_init_vif_data(struct wl1271 *wl, struct ieee80211_vif *vif) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + int i; + + /* clear everything but the persistent data */ + memset(wlvif, 0, offsetof(struct wl12xx_vif, persistent)); + + switch (ieee80211_vif_type_p2p(vif)) { + case NL80211_IFTYPE_P2P_CLIENT: + wlvif->p2p = 1; + /* fall-through */ + case NL80211_IFTYPE_STATION: + wlvif->bss_type = BSS_TYPE_STA_BSS; + break; + case NL80211_IFTYPE_ADHOC: + wlvif->bss_type = BSS_TYPE_IBSS; + break; + case NL80211_IFTYPE_P2P_GO: + wlvif->p2p = 1; + /* fall-through */ + case NL80211_IFTYPE_AP: + wlvif->bss_type = BSS_TYPE_AP_BSS; + break; + default: + wlvif->bss_type = MAX_BSS_TYPE; + return -EOPNOTSUPP; + } + + wlvif->role_id = WL12XX_INVALID_ROLE_ID; + wlvif->dev_role_id = WL12XX_INVALID_ROLE_ID; + wlvif->dev_hlid = WL12XX_INVALID_LINK_ID; + + if (wlvif->bss_type == BSS_TYPE_STA_BSS || + wlvif->bss_type == BSS_TYPE_IBSS) { + /* init sta/ibss data */ + wlvif->sta.hlid = WL12XX_INVALID_LINK_ID; + wl12xx_allocate_rate_policy(wl, &wlvif->sta.basic_rate_idx); + wl12xx_allocate_rate_policy(wl, &wlvif->sta.ap_rate_idx); + wl12xx_allocate_rate_policy(wl, &wlvif->sta.p2p_rate_idx); + wlcore_allocate_klv_template(wl, &wlvif->sta.klv_template_id); + wlvif->basic_rate_set = CONF_TX_RATE_MASK_BASIC; + wlvif->basic_rate = CONF_TX_RATE_MASK_BASIC; + wlvif->rate_set = CONF_TX_RATE_MASK_BASIC; + } else { + /* init ap data */ + wlvif->ap.bcast_hlid = WL12XX_INVALID_LINK_ID; + wlvif->ap.global_hlid = WL12XX_INVALID_LINK_ID; + wl12xx_allocate_rate_policy(wl, &wlvif->ap.mgmt_rate_idx); + wl12xx_allocate_rate_policy(wl, &wlvif->ap.bcast_rate_idx); + for (i = 0; i < CONF_TX_MAX_AC_COUNT; i++) + wl12xx_allocate_rate_policy(wl, + &wlvif->ap.ucast_rate_idx[i]); + wlvif->basic_rate_set = CONF_TX_ENABLED_RATES; + /* + * TODO: check if basic_rate shouldn't be + * wl1271_tx_min_rate_get(wl, wlvif->basic_rate_set); + * instead (the same thing for STA above). + */ + wlvif->basic_rate = CONF_TX_ENABLED_RATES; + /* TODO: this seems to be used only for STA, check it */ + wlvif->rate_set = CONF_TX_ENABLED_RATES; + } + + wlvif->bitrate_masks[IEEE80211_BAND_2GHZ] = wl->conf.tx.basic_rate; + wlvif->bitrate_masks[IEEE80211_BAND_5GHZ] = wl->conf.tx.basic_rate_5; + wlvif->beacon_int = WL1271_DEFAULT_BEACON_INT; + + /* + * mac80211 configures some values globally, while we treat them + * per-interface. thus, on init, we have to copy them from wl + */ + wlvif->band = wl->band; + wlvif->channel = wl->channel; + wlvif->power_level = wl->power_level; + wlvif->channel_type = wl->channel_type; + + INIT_WORK(&wlvif->rx_streaming_enable_work, + wl1271_rx_streaming_enable_work); + INIT_WORK(&wlvif->rx_streaming_disable_work, + wl1271_rx_streaming_disable_work); + INIT_DELAYED_WORK(&wlvif->channel_switch_work, + wlcore_channel_switch_work); + INIT_DELAYED_WORK(&wlvif->connection_loss_work, + wlcore_connection_loss_work); + INIT_LIST_HEAD(&wlvif->list); + + setup_timer(&wlvif->rx_streaming_timer, wl1271_rx_streaming_timer, + (unsigned long) wlvif); + return 0; +} + +static int wl12xx_init_fw(struct wl1271 *wl) +{ + int retries = WL1271_BOOT_RETRIES; + bool booted = false; + struct wiphy *wiphy = wl->hw->wiphy; + int ret; + + while (retries) { + retries--; + ret = wl12xx_chip_wakeup(wl, false); + if (ret < 0) + goto power_off; + + ret = wl->ops->boot(wl); + if (ret < 0) + goto power_off; + + ret = wl1271_hw_init(wl); + if (ret < 0) + goto irq_disable; + + booted = true; + break; + +irq_disable: + mutex_unlock(&wl->mutex); + /* Unlocking the mutex in the middle of handling is + inherently unsafe. In this case we deem it safe to do, + because we need to let any possibly pending IRQ out of + the system (and while we are WLCORE_STATE_OFF the IRQ + work function will not do anything.) Also, any other + possible concurrent operations will fail due to the + current state, hence the wl1271 struct should be safe. */ + wlcore_disable_interrupts(wl); + wl1271_flush_deferred_work(wl); + cancel_work_sync(&wl->netstack_work); + mutex_lock(&wl->mutex); +power_off: + wl1271_power_off(wl); + } + + if (!booted) { + wl1271_error("firmware boot failed despite %d retries", + WL1271_BOOT_RETRIES); + goto out; + } + + wl1271_info("firmware booted (%s)", wl->chip.fw_ver_str); + + /* update hw/fw version info in wiphy struct */ + wiphy->hw_version = wl->chip.id; + strncpy(wiphy->fw_version, wl->chip.fw_ver_str, + sizeof(wiphy->fw_version)); + + /* + * Now we know if 11a is supported (info from the NVS), so disable + * 11a channels if not supported + */ + if (!wl->enable_11a) + wiphy->bands[IEEE80211_BAND_5GHZ]->n_channels = 0; + + wl1271_debug(DEBUG_MAC80211, "11a is %ssupported", + wl->enable_11a ? "" : "not "); + + wl->state = WLCORE_STATE_ON; +out: + return ret; +} + +static bool wl12xx_dev_role_started(struct wl12xx_vif *wlvif) +{ + return wlvif->dev_hlid != WL12XX_INVALID_LINK_ID; +} + +/* + * Check whether a fw switch (i.e. moving from one loaded + * fw to another) is needed. This function is also responsible + * for updating wl->last_vif_count, so it must be called before + * loading a non-plt fw (so the correct fw (single-role/multi-role) + * will be used). + */ +static bool wl12xx_need_fw_change(struct wl1271 *wl, + struct vif_counter_data vif_counter_data, + bool add) +{ + enum wl12xx_fw_type current_fw = wl->fw_type; + u8 vif_count = vif_counter_data.counter; + + if (test_bit(WL1271_FLAG_VIF_CHANGE_IN_PROGRESS, &wl->flags)) + return false; + + /* increase the vif count if this is a new vif */ + if (add && !vif_counter_data.cur_vif_running) + vif_count++; + + wl->last_vif_count = vif_count; + + /* no need for fw change if the device is OFF */ + if (wl->state == WLCORE_STATE_OFF) + return false; + + /* no need for fw change if a single fw is used */ + if (!wl->mr_fw_name) + return false; + + if (vif_count > 1 && current_fw == WL12XX_FW_TYPE_NORMAL) + return true; + if (vif_count <= 1 && current_fw == WL12XX_FW_TYPE_MULTI) + return true; + + return false; +} + +/* + * Enter "forced psm". Make sure the sta is in psm against the ap, + * to make the fw switch a bit more disconnection-persistent. + */ +static void wl12xx_force_active_psm(struct wl1271 *wl) +{ + struct wl12xx_vif *wlvif; + + wl12xx_for_each_wlvif_sta(wl, wlvif) { + wl1271_ps_set_mode(wl, wlvif, STATION_POWER_SAVE_MODE); + } +} + +struct wlcore_hw_queue_iter_data { + unsigned long hw_queue_map[BITS_TO_LONGS(WLCORE_NUM_MAC_ADDRESSES)]; + /* current vif */ + struct ieee80211_vif *vif; + /* is the current vif among those iterated */ + bool cur_running; +}; + +static void wlcore_hw_queue_iter(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct wlcore_hw_queue_iter_data *iter_data = data; + + if (WARN_ON_ONCE(vif->hw_queue[0] == IEEE80211_INVAL_HW_QUEUE)) + return; + + if (iter_data->cur_running || vif == iter_data->vif) { + iter_data->cur_running = true; + return; + } + + __set_bit(vif->hw_queue[0] / NUM_TX_QUEUES, iter_data->hw_queue_map); +} + +static int wlcore_allocate_hw_queue_base(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + struct wlcore_hw_queue_iter_data iter_data = {}; + int i, q_base; + + iter_data.vif = vif; + + /* mark all bits taken by active interfaces */ + ieee80211_iterate_active_interfaces_atomic(wl->hw, + IEEE80211_IFACE_ITER_RESUME_ALL, + wlcore_hw_queue_iter, &iter_data); + + /* the current vif is already running in mac80211 (resume/recovery) */ + if (iter_data.cur_running) { + wlvif->hw_queue_base = vif->hw_queue[0]; + wl1271_debug(DEBUG_MAC80211, + "using pre-allocated hw queue base %d", + wlvif->hw_queue_base); + + /* interface type might have changed type */ + goto adjust_cab_queue; + } + + q_base = find_first_zero_bit(iter_data.hw_queue_map, + WLCORE_NUM_MAC_ADDRESSES); + if (q_base >= WLCORE_NUM_MAC_ADDRESSES) + return -EBUSY; + + wlvif->hw_queue_base = q_base * NUM_TX_QUEUES; + wl1271_debug(DEBUG_MAC80211, "allocating hw queue base: %d", + wlvif->hw_queue_base); + + for (i = 0; i < NUM_TX_QUEUES; i++) { + wl->queue_stop_reasons[wlvif->hw_queue_base + i] = 0; + /* register hw queues in mac80211 */ + vif->hw_queue[i] = wlvif->hw_queue_base + i; + } + +adjust_cab_queue: + /* the last places are reserved for cab queues per interface */ + if (wlvif->bss_type == BSS_TYPE_AP_BSS) + vif->cab_queue = NUM_TX_QUEUES * WLCORE_NUM_MAC_ADDRESSES + + wlvif->hw_queue_base / NUM_TX_QUEUES; + else + vif->cab_queue = IEEE80211_INVAL_HW_QUEUE; + + return 0; +} + +static int wl1271_op_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + struct vif_counter_data vif_count; + int ret = 0; + u8 role_type; + + vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER | + IEEE80211_VIF_SUPPORTS_CQM_RSSI; + + wl1271_debug(DEBUG_MAC80211, "mac80211 add interface type %d mac %pM", + ieee80211_vif_type_p2p(vif), vif->addr); + + wl12xx_get_vif_count(hw, vif, &vif_count); + + mutex_lock(&wl->mutex); + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out_unlock; + + /* + * in some very corner case HW recovery scenarios its possible to + * get here before __wl1271_op_remove_interface is complete, so + * opt out if that is the case. + */ + if (test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags) || + test_bit(WLVIF_FLAG_INITIALIZED, &wlvif->flags)) { + ret = -EBUSY; + goto out; + } + + + ret = wl12xx_init_vif_data(wl, vif); + if (ret < 0) + goto out; + + wlvif->wl = wl; + role_type = wl12xx_get_role_type(wl, wlvif); + if (role_type == WL12XX_INVALID_ROLE_TYPE) { + ret = -EINVAL; + goto out; + } + + ret = wlcore_allocate_hw_queue_base(wl, wlvif); + if (ret < 0) + goto out; + + if (wl12xx_need_fw_change(wl, vif_count, true)) { + wl12xx_force_active_psm(wl); + set_bit(WL1271_FLAG_INTENDED_FW_RECOVERY, &wl->flags); + mutex_unlock(&wl->mutex); + wl1271_recovery_work(&wl->recovery_work); + return 0; + } + + /* + * TODO: after the nvs issue will be solved, move this block + * to start(), and make sure here the driver is ON. + */ + if (wl->state == WLCORE_STATE_OFF) { + /* + * we still need this in order to configure the fw + * while uploading the nvs + */ + memcpy(wl->addresses[0].addr, vif->addr, ETH_ALEN); + + ret = wl12xx_init_fw(wl); + if (ret < 0) + goto out; + } + + ret = wl12xx_cmd_role_enable(wl, vif->addr, + role_type, &wlvif->role_id); + if (ret < 0) + goto out; + + ret = wl1271_init_vif_specific(wl, vif); + if (ret < 0) + goto out; + + list_add(&wlvif->list, &wl->wlvif_list); + set_bit(WLVIF_FLAG_INITIALIZED, &wlvif->flags); + + if (wlvif->bss_type == BSS_TYPE_AP_BSS) + wl->ap_count++; + else + wl->sta_count++; +out: + wl1271_ps_elp_sleep(wl); +out_unlock: + mutex_unlock(&wl->mutex); + + return ret; +} + +static void __wl1271_op_remove_interface(struct wl1271 *wl, + struct ieee80211_vif *vif, + bool reset_tx_queues) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + int i, ret; + bool is_ap = (wlvif->bss_type == BSS_TYPE_AP_BSS); + + wl1271_debug(DEBUG_MAC80211, "mac80211 remove interface"); + + if (!test_and_clear_bit(WLVIF_FLAG_INITIALIZED, &wlvif->flags)) + return; + + /* because of hardware recovery, we may get here twice */ + if (wl->state == WLCORE_STATE_OFF) + return; + + wl1271_info("down"); + + if (wl->scan.state != WL1271_SCAN_STATE_IDLE && + wl->scan_wlvif == wlvif) { + /* + * Rearm the tx watchdog just before idling scan. This + * prevents just-finished scans from triggering the watchdog + */ + wl12xx_rearm_tx_watchdog_locked(wl); + + wl->scan.state = WL1271_SCAN_STATE_IDLE; + memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch)); + wl->scan_wlvif = NULL; + wl->scan.req = NULL; + ieee80211_scan_completed(wl->hw, true); + } + + if (wl->sched_vif == wlvif) { + ieee80211_sched_scan_stopped(wl->hw); + wl->sched_vif = NULL; + } + + if (wl->roc_vif == vif) { + wl->roc_vif = NULL; + ieee80211_remain_on_channel_expired(wl->hw); + } + + if (!test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags)) { + /* disable active roles */ + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto deinit; + + if (wlvif->bss_type == BSS_TYPE_STA_BSS || + wlvif->bss_type == BSS_TYPE_IBSS) { + if (wl12xx_dev_role_started(wlvif)) + wl12xx_stop_dev(wl, wlvif); + } + + ret = wl12xx_cmd_role_disable(wl, &wlvif->role_id); + if (ret < 0) + goto deinit; + + wl1271_ps_elp_sleep(wl); + } +deinit: + wl12xx_tx_reset_wlvif(wl, wlvif); + + /* clear all hlids (except system_hlid) */ + wlvif->dev_hlid = WL12XX_INVALID_LINK_ID; + + if (wlvif->bss_type == BSS_TYPE_STA_BSS || + wlvif->bss_type == BSS_TYPE_IBSS) { + wlvif->sta.hlid = WL12XX_INVALID_LINK_ID; + wl12xx_free_rate_policy(wl, &wlvif->sta.basic_rate_idx); + wl12xx_free_rate_policy(wl, &wlvif->sta.ap_rate_idx); + wl12xx_free_rate_policy(wl, &wlvif->sta.p2p_rate_idx); + wlcore_free_klv_template(wl, &wlvif->sta.klv_template_id); + } else { + wlvif->ap.bcast_hlid = WL12XX_INVALID_LINK_ID; + wlvif->ap.global_hlid = WL12XX_INVALID_LINK_ID; + wl12xx_free_rate_policy(wl, &wlvif->ap.mgmt_rate_idx); + wl12xx_free_rate_policy(wl, &wlvif->ap.bcast_rate_idx); + for (i = 0; i < CONF_TX_MAX_AC_COUNT; i++) + wl12xx_free_rate_policy(wl, + &wlvif->ap.ucast_rate_idx[i]); + wl1271_free_ap_keys(wl, wlvif); + } + + dev_kfree_skb(wlvif->probereq); + wlvif->probereq = NULL; + if (wl->last_wlvif == wlvif) + wl->last_wlvif = NULL; + list_del(&wlvif->list); + memset(wlvif->ap.sta_hlid_map, 0, sizeof(wlvif->ap.sta_hlid_map)); + wlvif->role_id = WL12XX_INVALID_ROLE_ID; + wlvif->dev_role_id = WL12XX_INVALID_ROLE_ID; + + if (is_ap) + wl->ap_count--; + else + wl->sta_count--; + + /* + * Last AP, have more stations. Configure sleep auth according to STA. + * Don't do thin on unintended recovery. + */ + if (test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags) && + !test_bit(WL1271_FLAG_INTENDED_FW_RECOVERY, &wl->flags)) + goto unlock; + + if (wl->ap_count == 0 && is_ap && wl->sta_count) { + u8 sta_auth = wl->conf.conn.sta_sleep_auth; + /* Configure for power according to debugfs */ + if (sta_auth != WL1271_PSM_ILLEGAL) + wl1271_acx_sleep_auth(wl, sta_auth); + /* Configure for ELP power saving */ + else + wl1271_acx_sleep_auth(wl, WL1271_PSM_ELP); + } + +unlock: + mutex_unlock(&wl->mutex); + + del_timer_sync(&wlvif->rx_streaming_timer); + cancel_work_sync(&wlvif->rx_streaming_enable_work); + cancel_work_sync(&wlvif->rx_streaming_disable_work); + cancel_delayed_work_sync(&wlvif->connection_loss_work); + + mutex_lock(&wl->mutex); +} + +static void wl1271_op_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + struct wl12xx_vif *iter; + struct vif_counter_data vif_count; + + wl12xx_get_vif_count(hw, vif, &vif_count); + mutex_lock(&wl->mutex); + + if (wl->state == WLCORE_STATE_OFF || + !test_bit(WLVIF_FLAG_INITIALIZED, &wlvif->flags)) + goto out; + + /* + * wl->vif can be null here if someone shuts down the interface + * just when hardware recovery has been started. + */ + wl12xx_for_each_wlvif(wl, iter) { + if (iter != wlvif) + continue; + + __wl1271_op_remove_interface(wl, vif, true); + break; + } + WARN_ON(iter != wlvif); + if (wl12xx_need_fw_change(wl, vif_count, false)) { + wl12xx_force_active_psm(wl); + set_bit(WL1271_FLAG_INTENDED_FW_RECOVERY, &wl->flags); + wl12xx_queue_recovery_work(wl); + } +out: + mutex_unlock(&wl->mutex); +} + +static int wl12xx_op_change_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum nl80211_iftype new_type, bool p2p) +{ + struct wl1271 *wl = hw->priv; + int ret; + + set_bit(WL1271_FLAG_VIF_CHANGE_IN_PROGRESS, &wl->flags); + wl1271_op_remove_interface(hw, vif); + + vif->type = new_type; + vif->p2p = p2p; + ret = wl1271_op_add_interface(hw, vif); + + clear_bit(WL1271_FLAG_VIF_CHANGE_IN_PROGRESS, &wl->flags); + return ret; +} + +static int wlcore_join(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + int ret; + bool is_ibss = (wlvif->bss_type == BSS_TYPE_IBSS); + + /* + * One of the side effects of the JOIN command is that is clears + * WPA/WPA2 keys from the chipset. Performing a JOIN while associated + * to a WPA/WPA2 access point will therefore kill the data-path. + * Currently the only valid scenario for JOIN during association + * is on roaming, in which case we will also be given new keys. + * Keep the below message for now, unless it starts bothering + * users who really like to roam a lot :) + */ + if (test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags)) + wl1271_info("JOIN while associated."); + + /* clear encryption type */ + wlvif->encryption_type = KEY_NONE; + + if (is_ibss) + ret = wl12xx_cmd_role_start_ibss(wl, wlvif); + else { + if (wl->quirks & WLCORE_QUIRK_START_STA_FAILS) { + /* + * TODO: this is an ugly workaround for wl12xx fw + * bug - we are not able to tx/rx after the first + * start_sta, so make dummy start+stop calls, + * and then call start_sta again. + * this should be fixed in the fw. + */ + wl12xx_cmd_role_start_sta(wl, wlvif); + wl12xx_cmd_role_stop_sta(wl, wlvif); + } + + ret = wl12xx_cmd_role_start_sta(wl, wlvif); + } + + return ret; +} + +static int wl1271_ssid_set(struct wl12xx_vif *wlvif, struct sk_buff *skb, + int offset) +{ + u8 ssid_len; + const u8 *ptr = cfg80211_find_ie(WLAN_EID_SSID, skb->data + offset, + skb->len - offset); + + if (!ptr) { + wl1271_error("No SSID in IEs!"); + return -ENOENT; + } + + ssid_len = ptr[1]; + if (ssid_len > IEEE80211_MAX_SSID_LEN) { + wl1271_error("SSID is too long!"); + return -EINVAL; + } + + wlvif->ssid_len = ssid_len; + memcpy(wlvif->ssid, ptr+2, ssid_len); + return 0; +} + +static int wlcore_set_ssid(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + struct sk_buff *skb; + int ieoffset; + + /* we currently only support setting the ssid from the ap probe req */ + if (wlvif->bss_type != BSS_TYPE_STA_BSS) + return -EINVAL; + + skb = ieee80211_ap_probereq_get(wl->hw, vif); + if (!skb) + return -EINVAL; + + ieoffset = offsetof(struct ieee80211_mgmt, + u.probe_req.variable); + wl1271_ssid_set(wlvif, skb, ieoffset); + dev_kfree_skb(skb); + + return 0; +} + +static int wlcore_set_assoc(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct ieee80211_bss_conf *bss_conf, + u32 sta_rate_set) +{ + int ieoffset; + int ret; + + wlvif->aid = bss_conf->aid; + wlvif->channel_type = cfg80211_get_chandef_type(&bss_conf->chandef); + wlvif->beacon_int = bss_conf->beacon_int; + wlvif->wmm_enabled = bss_conf->qos; + + set_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags); + + /* + * with wl1271, we don't need to update the + * beacon_int and dtim_period, because the firmware + * updates it by itself when the first beacon is + * received after a join. + */ + ret = wl1271_cmd_build_ps_poll(wl, wlvif, wlvif->aid); + if (ret < 0) + return ret; + + /* + * Get a template for hardware connection maintenance + */ + dev_kfree_skb(wlvif->probereq); + wlvif->probereq = wl1271_cmd_build_ap_probe_req(wl, + wlvif, + NULL); + ieoffset = offsetof(struct ieee80211_mgmt, + u.probe_req.variable); + wl1271_ssid_set(wlvif, wlvif->probereq, ieoffset); + + /* enable the connection monitoring feature */ + ret = wl1271_acx_conn_monit_params(wl, wlvif, true); + if (ret < 0) + return ret; + + /* + * The join command disable the keep-alive mode, shut down its process, + * and also clear the template config, so we need to reset it all after + * the join. The acx_aid starts the keep-alive process, and the order + * of the commands below is relevant. + */ + ret = wl1271_acx_keep_alive_mode(wl, wlvif, true); + if (ret < 0) + return ret; + + ret = wl1271_acx_aid(wl, wlvif, wlvif->aid); + if (ret < 0) + return ret; + + ret = wl12xx_cmd_build_klv_null_data(wl, wlvif); + if (ret < 0) + return ret; + + ret = wl1271_acx_keep_alive_config(wl, wlvif, + wlvif->sta.klv_template_id, + ACX_KEEP_ALIVE_TPL_VALID); + if (ret < 0) + return ret; + + /* + * The default fw psm configuration is AUTO, while mac80211 default + * setting is off (ACTIVE), so sync the fw with the correct value. + */ + ret = wl1271_ps_set_mode(wl, wlvif, STATION_ACTIVE_MODE); + if (ret < 0) + return ret; + + if (sta_rate_set) { + wlvif->rate_set = + wl1271_tx_enabled_rates_get(wl, + sta_rate_set, + wlvif->band); + ret = wl1271_acx_sta_rate_policies(wl, wlvif); + if (ret < 0) + return ret; + } + + return ret; +} + +static int wlcore_unset_assoc(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + int ret; + bool sta = wlvif->bss_type == BSS_TYPE_STA_BSS; + + /* make sure we are connected (sta) joined */ + if (sta && + !test_and_clear_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags)) + return false; + + /* make sure we are joined (ibss) */ + if (!sta && + test_and_clear_bit(WLVIF_FLAG_IBSS_JOINED, &wlvif->flags)) + return false; + + if (sta) { + /* use defaults when not associated */ + wlvif->aid = 0; + + /* free probe-request template */ + dev_kfree_skb(wlvif->probereq); + wlvif->probereq = NULL; + + /* disable connection monitor features */ + ret = wl1271_acx_conn_monit_params(wl, wlvif, false); + if (ret < 0) + return ret; + + /* Disable the keep-alive feature */ + ret = wl1271_acx_keep_alive_mode(wl, wlvif, false); + if (ret < 0) + return ret; + } + + if (test_and_clear_bit(WLVIF_FLAG_CS_PROGRESS, &wlvif->flags)) { + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + + wl12xx_cmd_stop_channel_switch(wl, wlvif); + ieee80211_chswitch_done(vif, false); + cancel_delayed_work(&wlvif->channel_switch_work); + } + + /* invalidate keep-alive template */ + wl1271_acx_keep_alive_config(wl, wlvif, + wlvif->sta.klv_template_id, + ACX_KEEP_ALIVE_TPL_INVALID); + + return 0; +} + +static void wl1271_set_band_rate(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + wlvif->basic_rate_set = wlvif->bitrate_masks[wlvif->band]; + wlvif->rate_set = wlvif->basic_rate_set; +} + +static int wl12xx_config_vif(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct ieee80211_conf *conf, u32 changed) +{ + int ret; + + if (conf->power_level != wlvif->power_level) { + ret = wl1271_acx_tx_power(wl, wlvif, conf->power_level); + if (ret < 0) + return ret; + + wlvif->power_level = conf->power_level; + } + + return 0; +} + +static int wl1271_op_config(struct ieee80211_hw *hw, u32 changed) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif; + struct ieee80211_conf *conf = &hw->conf; + int ret = 0; + + wl1271_debug(DEBUG_MAC80211, "mac80211 config psm %s power %d %s" + " changed 0x%x", + conf->flags & IEEE80211_CONF_PS ? "on" : "off", + conf->power_level, + conf->flags & IEEE80211_CONF_IDLE ? "idle" : "in use", + changed); + + mutex_lock(&wl->mutex); + + if (changed & IEEE80211_CONF_CHANGE_POWER) + wl->power_level = conf->power_level; + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + /* configure each interface */ + wl12xx_for_each_wlvif(wl, wlvif) { + ret = wl12xx_config_vif(wl, wlvif, conf, changed); + if (ret < 0) + goto out_sleep; + } + +out_sleep: + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +struct wl1271_filter_params { + bool enabled; + int mc_list_length; + u8 mc_list[ACX_MC_ADDRESS_GROUP_MAX][ETH_ALEN]; +}; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) +static u64 wl1271_op_prepare_multicast(struct ieee80211_hw *hw, + struct netdev_hw_addr_list *mc_list) +#else +static u64 wl1271_op_prepare_multicast(struct ieee80211_hw *hw, int mc_count, + struct dev_addr_list *mc_list) +#endif +{ + struct wl1271_filter_params *fp; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + struct netdev_hw_addr *ha; +#else + int i; +#endif + + fp = kzalloc(sizeof(*fp), GFP_ATOMIC); + if (!fp) { + wl1271_error("Out of memory setting filters."); + return 0; + } + + /* update multicast filtering parameters */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + fp->mc_list_length = 0; + if (netdev_hw_addr_list_count(mc_list) > ACX_MC_ADDRESS_GROUP_MAX) { +#else + fp->enabled = true; + if (mc_count > ACX_MC_ADDRESS_GROUP_MAX) { + mc_count = 0; +#endif + fp->enabled = false; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + } else { + fp->enabled = true; + netdev_hw_addr_list_for_each(ha, mc_list) { +#else + } + + fp->mc_list_length = 0; + for (i = 0; i < mc_count; i++) { + if (mc_list->da_addrlen == ETH_ALEN) { +#endif + memcpy(fp->mc_list[fp->mc_list_length], +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + ha->addr, ETH_ALEN); +#else + mc_list->da_addr, ETH_ALEN); +#endif + fp->mc_list_length++; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + } +#else + } else + wl1271_warning("Unknown mc address length."); + mc_list = mc_list->next; +#endif + } + + return (u64)(unsigned long)fp; +} + +#define WL1271_SUPPORTED_FILTERS (FIF_PROMISC_IN_BSS | \ + FIF_ALLMULTI | \ + FIF_FCSFAIL | \ + FIF_BCN_PRBRESP_PROMISC | \ + FIF_CONTROL | \ + FIF_OTHER_BSS) + +static void wl1271_op_configure_filter(struct ieee80211_hw *hw, + unsigned int changed, + unsigned int *total, u64 multicast) +{ + struct wl1271_filter_params *fp = (void *)(unsigned long)multicast; + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif; + + int ret; + + wl1271_debug(DEBUG_MAC80211, "mac80211 configure filter changed %x" + " total %x", changed, *total); + + mutex_lock(&wl->mutex); + + *total &= WL1271_SUPPORTED_FILTERS; + changed &= WL1271_SUPPORTED_FILTERS; + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl12xx_for_each_wlvif(wl, wlvif) { + if (wlvif->bss_type != BSS_TYPE_AP_BSS) { + if (*total & FIF_ALLMULTI) + ret = wl1271_acx_group_address_tbl(wl, wlvif, + false, + NULL, 0); + else if (fp) + ret = wl1271_acx_group_address_tbl(wl, wlvif, + fp->enabled, + fp->mc_list, + fp->mc_list_length); + if (ret < 0) + goto out_sleep; + } + } + + /* + * the fw doesn't provide an api to configure the filters. instead, + * the filters configuration is based on the active roles / ROC + * state. + */ + +out_sleep: + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + kfree(fp); +} + +static int wl1271_record_ap_key(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 id, u8 key_type, u8 key_size, + const u8 *key, u8 hlid, u32 tx_seq_32, + u16 tx_seq_16) +{ + struct wl1271_ap_key *ap_key; + int i; + + wl1271_debug(DEBUG_CRYPT, "record ap key id %d", (int)id); + + if (key_size > MAX_KEY_SIZE) + return -EINVAL; + + /* + * Find next free entry in ap_keys. Also check we are not replacing + * an existing key. + */ + for (i = 0; i < MAX_NUM_KEYS; i++) { + if (wlvif->ap.recorded_keys[i] == NULL) + break; + + if (wlvif->ap.recorded_keys[i]->id == id) { + wl1271_warning("trying to record key replacement"); + return -EINVAL; + } + } + + if (i == MAX_NUM_KEYS) + return -EBUSY; + + ap_key = kzalloc(sizeof(*ap_key), GFP_KERNEL); + if (!ap_key) + return -ENOMEM; + + ap_key->id = id; + ap_key->key_type = key_type; + ap_key->key_size = key_size; + memcpy(ap_key->key, key, key_size); + ap_key->hlid = hlid; + ap_key->tx_seq_32 = tx_seq_32; + ap_key->tx_seq_16 = tx_seq_16; + + wlvif->ap.recorded_keys[i] = ap_key; + return 0; +} + +static void wl1271_free_ap_keys(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + int i; + + for (i = 0; i < MAX_NUM_KEYS; i++) { + kfree(wlvif->ap.recorded_keys[i]); + wlvif->ap.recorded_keys[i] = NULL; + } +} + +static int wl1271_ap_init_hwenc(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + int i, ret = 0; + struct wl1271_ap_key *key; + bool wep_key_added = false; + + for (i = 0; i < MAX_NUM_KEYS; i++) { + u8 hlid; + if (wlvif->ap.recorded_keys[i] == NULL) + break; + + key = wlvif->ap.recorded_keys[i]; + hlid = key->hlid; + if (hlid == WL12XX_INVALID_LINK_ID) + hlid = wlvif->ap.bcast_hlid; + + ret = wl1271_cmd_set_ap_key(wl, wlvif, KEY_ADD_OR_REPLACE, + key->id, key->key_type, + key->key_size, key->key, + hlid, key->tx_seq_32, + key->tx_seq_16); + if (ret < 0) + goto out; + + if (key->key_type == KEY_WEP) + wep_key_added = true; + } + + if (wep_key_added) { + ret = wl12xx_cmd_set_default_wep_key(wl, wlvif->default_key, + wlvif->ap.bcast_hlid); + if (ret < 0) + goto out; + } + +out: + wl1271_free_ap_keys(wl, wlvif); + return ret; +} + +static int wl1271_set_key(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u16 action, u8 id, u8 key_type, + u8 key_size, const u8 *key, u32 tx_seq_32, + u16 tx_seq_16, struct ieee80211_sta *sta) +{ + int ret; + bool is_ap = (wlvif->bss_type == BSS_TYPE_AP_BSS); + + if (is_ap) { + struct wl1271_station *wl_sta; + u8 hlid; + + if (sta) { + wl_sta = (struct wl1271_station *)sta->drv_priv; + hlid = wl_sta->hlid; + } else { + hlid = wlvif->ap.bcast_hlid; + } + + if (!test_bit(WLVIF_FLAG_AP_STARTED, &wlvif->flags)) { + /* + * We do not support removing keys after AP shutdown. + * Pretend we do to make mac80211 happy. + */ + if (action != KEY_ADD_OR_REPLACE) + return 0; + + ret = wl1271_record_ap_key(wl, wlvif, id, + key_type, key_size, + key, hlid, tx_seq_32, + tx_seq_16); + } else { + ret = wl1271_cmd_set_ap_key(wl, wlvif, action, + id, key_type, key_size, + key, hlid, tx_seq_32, + tx_seq_16); + } + + if (ret < 0) + return ret; + } else { + const u8 *addr; + static const u8 bcast_addr[ETH_ALEN] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + addr = sta ? sta->addr : bcast_addr; + + if (is_zero_ether_addr(addr)) { + /* We dont support TX only encryption */ + return -EOPNOTSUPP; + } + + /* The wl1271 does not allow to remove unicast keys - they + will be cleared automatically on next CMD_JOIN. Ignore the + request silently, as we dont want the mac80211 to emit + an error message. */ + if (action == KEY_REMOVE && !is_broadcast_ether_addr(addr)) + return 0; + + /* don't remove key if hlid was already deleted */ + if (action == KEY_REMOVE && + wlvif->sta.hlid == WL12XX_INVALID_LINK_ID) + return 0; + + ret = wl1271_cmd_set_sta_key(wl, wlvif, action, + id, key_type, key_size, + key, addr, tx_seq_32, + tx_seq_16); + if (ret < 0) + return ret; + + /* the default WEP key needs to be configured at least once */ + if (key_type == KEY_WEP) { + ret = wl12xx_cmd_set_default_wep_key(wl, + wlvif->default_key, + wlvif->sta.hlid); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int wlcore_op_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key_conf) +{ + struct wl1271 *wl = hw->priv; + int ret; + bool might_change_spare = + key_conf->cipher == WL1271_CIPHER_SUITE_GEM || + key_conf->cipher == WLAN_CIPHER_SUITE_TKIP; + + if (might_change_spare) { + /* + * stop the queues and flush to ensure the next packets are + * in sync with FW spare block accounting + */ + wlcore_stop_queues(wl, WLCORE_QUEUE_STOP_REASON_SPARE_BLK); + wl1271_tx_flush(wl); + } + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) { + ret = -EAGAIN; + goto out_wake_queues; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out_wake_queues; + + ret = wlcore_hw_set_key(wl, cmd, vif, sta, key_conf); + + wl1271_ps_elp_sleep(wl); + +out_wake_queues: + if (might_change_spare) + wlcore_wake_queues(wl, WLCORE_QUEUE_STOP_REASON_SPARE_BLK); + + mutex_unlock(&wl->mutex); + + return ret; +} + +int wlcore_set_key(struct wl1271 *wl, enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key_conf) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + int ret; + u32 tx_seq_32 = 0; + u16 tx_seq_16 = 0; + u8 key_type; + u8 hlid; + + wl1271_debug(DEBUG_MAC80211, "mac80211 set key"); + + wl1271_debug(DEBUG_CRYPT, "CMD: 0x%x sta: %p", cmd, sta); + wl1271_debug(DEBUG_CRYPT, "Key: algo:0x%x, id:%d, len:%d flags 0x%x", + key_conf->cipher, key_conf->keyidx, + key_conf->keylen, key_conf->flags); + wl1271_dump(DEBUG_CRYPT, "KEY: ", key_conf->key, key_conf->keylen); + + if (wlvif->bss_type == BSS_TYPE_AP_BSS) + if (sta) { + struct wl1271_station *wl_sta = (void *)sta->drv_priv; + hlid = wl_sta->hlid; + } else { + hlid = wlvif->ap.bcast_hlid; + } + else + hlid = wlvif->sta.hlid; + + if (hlid != WL12XX_INVALID_LINK_ID) { + u64 tx_seq = wl->links[hlid].total_freed_pkts; + tx_seq_32 = WL1271_TX_SECURITY_HI32(tx_seq); + tx_seq_16 = WL1271_TX_SECURITY_LO16(tx_seq); + } + + switch (key_conf->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + key_type = KEY_WEP; + + key_conf->hw_key_idx = key_conf->keyidx; + break; + case WLAN_CIPHER_SUITE_TKIP: + key_type = KEY_TKIP; + key_conf->hw_key_idx = key_conf->keyidx; + break; + case WLAN_CIPHER_SUITE_CCMP: + key_type = KEY_AES; + key_conf->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE; + break; + case WL1271_CIPHER_SUITE_GEM: + key_type = KEY_GEM; + break; + default: + wl1271_error("Unknown key algo 0x%x", key_conf->cipher); + + return -EOPNOTSUPP; + } + + switch (cmd) { + case SET_KEY: + ret = wl1271_set_key(wl, wlvif, KEY_ADD_OR_REPLACE, + key_conf->keyidx, key_type, + key_conf->keylen, key_conf->key, + tx_seq_32, tx_seq_16, sta); + if (ret < 0) { + wl1271_error("Could not add or replace key"); + return ret; + } + + /* + * reconfiguring arp response if the unicast (or common) + * encryption key type was changed + */ + if (wlvif->bss_type == BSS_TYPE_STA_BSS && + (sta || key_type == KEY_WEP) && + wlvif->encryption_type != key_type) { + wlvif->encryption_type = key_type; + ret = wl1271_cmd_build_arp_rsp(wl, wlvif); + if (ret < 0) { + wl1271_warning("build arp rsp failed: %d", ret); + return ret; + } + } + break; + + case DISABLE_KEY: + ret = wl1271_set_key(wl, wlvif, KEY_REMOVE, + key_conf->keyidx, key_type, + key_conf->keylen, key_conf->key, + 0, 0, sta); + if (ret < 0) { + wl1271_error("Could not remove key"); + return ret; + } + break; + + default: + wl1271_error("Unsupported key cmd 0x%x", cmd); + return -EOPNOTSUPP; + } + + return ret; +} +EXPORT_SYMBOL_GPL(wlcore_set_key); + +void wlcore_regdomain_config(struct wl1271 *wl) +{ + int ret; + + if (!(wl->quirks & WLCORE_QUIRK_REGDOMAIN_CONF)) + return; + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wlcore_cmd_regdomain_config_locked(wl); + if (ret < 0) { + wl12xx_queue_recovery_work(wl); + goto out; + } + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); +} + +static int wl1271_op_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_scan_request *req) +{ + struct wl1271 *wl = hw->priv; + int ret; + u8 *ssid = NULL; + size_t len = 0; + + wl1271_debug(DEBUG_MAC80211, "mac80211 hw scan"); + + if (req->n_ssids) { + ssid = req->ssids[0].ssid; + len = req->ssids[0].ssid_len; + } + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) { + /* + * We cannot return -EBUSY here because cfg80211 will expect + * a call to ieee80211_scan_completed if we do - in this case + * there won't be any call. + */ + ret = -EAGAIN; + goto out; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + /* fail if there is any role in ROC */ + if (find_first_bit(wl->roc_map, WL12XX_MAX_ROLES) < WL12XX_MAX_ROLES) { + /* don't allow scanning right now */ + ret = -EBUSY; + goto out_sleep; + } + + ret = wlcore_scan(hw->priv, vif, ssid, len, req); +out_sleep: + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static void wl1271_op_cancel_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + int ret; + + wl1271_debug(DEBUG_MAC80211, "mac80211 cancel hw scan"); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + if (wl->scan.state == WL1271_SCAN_STATE_IDLE) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + if (wl->scan.state != WL1271_SCAN_STATE_DONE) { + ret = wl->ops->scan_stop(wl, wlvif); + if (ret < 0) + goto out_sleep; + } + + /* + * Rearm the tx watchdog just before idling scan. This + * prevents just-finished scans from triggering the watchdog + */ + wl12xx_rearm_tx_watchdog_locked(wl); + + wl->scan.state = WL1271_SCAN_STATE_IDLE; + memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch)); + wl->scan_wlvif = NULL; + wl->scan.req = NULL; + ieee80211_scan_completed(wl->hw, true); + +out_sleep: + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); + + cancel_delayed_work_sync(&wl->scan_complete_work); +} + +static int wl1271_op_sched_scan_start(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + int ret; + + wl1271_debug(DEBUG_MAC80211, "wl1271_op_sched_scan_start"); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) { + ret = -EAGAIN; + goto out; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl->ops->sched_scan_start(wl, wlvif, req, ies); + if (ret < 0) + goto out_sleep; + + wl->sched_vif = wlvif; + +out_sleep: + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); + return ret; +} + +static void wl1271_op_sched_scan_stop(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + int ret; + + wl1271_debug(DEBUG_MAC80211, "wl1271_op_sched_scan_stop"); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl->ops->sched_scan_stop(wl, wlvif); + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); +} + +static int wl1271_op_set_frag_threshold(struct ieee80211_hw *hw, u32 value) +{ + struct wl1271 *wl = hw->priv; + int ret = 0; + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) { + ret = -EAGAIN; + goto out; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl1271_acx_frag_threshold(wl, value); + if (ret < 0) + wl1271_warning("wl1271_op_set_frag_threshold failed: %d", ret); + + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static int wl1271_op_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif; + int ret = 0; + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) { + ret = -EAGAIN; + goto out; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl12xx_for_each_wlvif(wl, wlvif) { + ret = wl1271_acx_rts_threshold(wl, wlvif, value); + if (ret < 0) + wl1271_warning("set rts threshold failed: %d", ret); + } + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static void wl12xx_remove_ie(struct sk_buff *skb, u8 eid, int ieoffset) +{ + int len; + const u8 *next, *end = skb->data + skb->len; + u8 *ie = (u8 *)cfg80211_find_ie(eid, skb->data + ieoffset, + skb->len - ieoffset); + if (!ie) + return; + len = ie[1] + 2; + next = ie + len; + memmove(ie, next, end - next); + skb_trim(skb, skb->len - len); +} + +static void wl12xx_remove_vendor_ie(struct sk_buff *skb, + unsigned int oui, u8 oui_type, + int ieoffset) +{ + int len; + const u8 *next, *end = skb->data + skb->len; + u8 *ie = (u8 *)cfg80211_find_vendor_ie(oui, oui_type, + skb->data + ieoffset, + skb->len - ieoffset); + if (!ie) + return; + len = ie[1] + 2; + next = ie + len; + memmove(ie, next, end - next); + skb_trim(skb, skb->len - len); +} + +static int wl1271_ap_set_probe_resp_tmpl(struct wl1271 *wl, u32 rates, + struct ieee80211_vif *vif) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + struct sk_buff *skb; + int ret; + + skb = ieee80211_proberesp_get(wl->hw, vif); + if (!skb) + return -EOPNOTSUPP; + + ret = wl1271_cmd_template_set(wl, wlvif->role_id, + CMD_TEMPL_AP_PROBE_RESPONSE, + skb->data, + skb->len, 0, + rates); + dev_kfree_skb(skb); + + if (ret < 0) + goto out; + + wl1271_debug(DEBUG_AP, "probe response updated"); + set_bit(WLVIF_FLAG_AP_PROBE_RESP_SET, &wlvif->flags); + +out: + return ret; +} + +static int wl1271_ap_set_probe_resp_tmpl_legacy(struct wl1271 *wl, + struct ieee80211_vif *vif, + u8 *probe_rsp_data, + size_t probe_rsp_len, + u32 rates) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + struct ieee80211_bss_conf *bss_conf = &vif->bss_conf; + u8 probe_rsp_templ[WL1271_CMD_TEMPL_MAX_SIZE]; + int ssid_ie_offset, ie_offset, templ_len; + const u8 *ptr; + + /* no need to change probe response if the SSID is set correctly */ + if (wlvif->ssid_len > 0) + return wl1271_cmd_template_set(wl, wlvif->role_id, + CMD_TEMPL_AP_PROBE_RESPONSE, + probe_rsp_data, + probe_rsp_len, 0, + rates); + + if (probe_rsp_len + bss_conf->ssid_len > WL1271_CMD_TEMPL_MAX_SIZE) { + wl1271_error("probe_rsp template too big"); + return -EINVAL; + } + + /* start searching from IE offset */ + ie_offset = offsetof(struct ieee80211_mgmt, u.probe_resp.variable); + + ptr = cfg80211_find_ie(WLAN_EID_SSID, probe_rsp_data + ie_offset, + probe_rsp_len - ie_offset); + if (!ptr) { + wl1271_error("No SSID in beacon!"); + return -EINVAL; + } + + ssid_ie_offset = ptr - probe_rsp_data; + ptr += (ptr[1] + 2); + + memcpy(probe_rsp_templ, probe_rsp_data, ssid_ie_offset); + + /* insert SSID from bss_conf */ + probe_rsp_templ[ssid_ie_offset] = WLAN_EID_SSID; + probe_rsp_templ[ssid_ie_offset + 1] = bss_conf->ssid_len; + memcpy(probe_rsp_templ + ssid_ie_offset + 2, + bss_conf->ssid, bss_conf->ssid_len); + templ_len = ssid_ie_offset + 2 + bss_conf->ssid_len; + + memcpy(probe_rsp_templ + ssid_ie_offset + 2 + bss_conf->ssid_len, + ptr, probe_rsp_len - (ptr - probe_rsp_data)); + templ_len += probe_rsp_len - (ptr - probe_rsp_data); + + return wl1271_cmd_template_set(wl, wlvif->role_id, + CMD_TEMPL_AP_PROBE_RESPONSE, + probe_rsp_templ, + templ_len, 0, + rates); +} + +static int wl1271_bss_erp_info_changed(struct wl1271 *wl, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changed) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + int ret = 0; + + if (changed & BSS_CHANGED_ERP_SLOT) { + if (bss_conf->use_short_slot) + ret = wl1271_acx_slot(wl, wlvif, SLOT_TIME_SHORT); + else + ret = wl1271_acx_slot(wl, wlvif, SLOT_TIME_LONG); + if (ret < 0) { + wl1271_warning("Set slot time failed %d", ret); + goto out; + } + } + + if (changed & BSS_CHANGED_ERP_PREAMBLE) { + if (bss_conf->use_short_preamble) + wl1271_acx_set_preamble(wl, wlvif, ACX_PREAMBLE_SHORT); + else + wl1271_acx_set_preamble(wl, wlvif, ACX_PREAMBLE_LONG); + } + + if (changed & BSS_CHANGED_ERP_CTS_PROT) { + if (bss_conf->use_cts_prot) + ret = wl1271_acx_cts_protect(wl, wlvif, + CTSPROTECT_ENABLE); + else + ret = wl1271_acx_cts_protect(wl, wlvif, + CTSPROTECT_DISABLE); + if (ret < 0) { + wl1271_warning("Set ctsprotect failed %d", ret); + goto out; + } + } + +out: + return ret; +} + +static int wlcore_set_beacon_template(struct wl1271 *wl, + struct ieee80211_vif *vif, + bool is_ap) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + struct ieee80211_hdr *hdr; + u32 min_rate; + int ret; + int ieoffset = offsetof(struct ieee80211_mgmt, + u.beacon.variable); + struct sk_buff *beacon = ieee80211_beacon_get(wl->hw, vif); + u16 tmpl_id; + + if (!beacon) { + ret = -EINVAL; + goto out; + } + + wl1271_debug(DEBUG_MASTER, "beacon updated"); + + ret = wl1271_ssid_set(wlvif, beacon, ieoffset); + if (ret < 0) { + dev_kfree_skb(beacon); + goto out; + } + min_rate = wl1271_tx_min_rate_get(wl, wlvif->basic_rate_set); + tmpl_id = is_ap ? CMD_TEMPL_AP_BEACON : + CMD_TEMPL_BEACON; + ret = wl1271_cmd_template_set(wl, wlvif->role_id, tmpl_id, + beacon->data, + beacon->len, 0, + min_rate); + if (ret < 0) { + dev_kfree_skb(beacon); + goto out; + } + + wlvif->wmm_enabled = + cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, + WLAN_OUI_TYPE_MICROSOFT_WMM, + beacon->data + ieoffset, + beacon->len - ieoffset); + + /* + * In case we already have a probe-resp beacon set explicitly + * by usermode, don't use the beacon data. + */ + if (test_bit(WLVIF_FLAG_AP_PROBE_RESP_SET, &wlvif->flags)) + goto end_bcn; + + /* remove TIM ie from probe response */ + wl12xx_remove_ie(beacon, WLAN_EID_TIM, ieoffset); + + /* + * remove p2p ie from probe response. + * the fw reponds to probe requests that don't include + * the p2p ie. probe requests with p2p ie will be passed, + * and will be responded by the supplicant (the spec + * forbids including the p2p ie when responding to probe + * requests that didn't include it). + */ + wl12xx_remove_vendor_ie(beacon, WLAN_OUI_WFA, + WLAN_OUI_TYPE_WFA_P2P, ieoffset); + + hdr = (struct ieee80211_hdr *) beacon->data; + hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_PROBE_RESP); + if (is_ap) + ret = wl1271_ap_set_probe_resp_tmpl_legacy(wl, vif, + beacon->data, + beacon->len, + min_rate); + else + ret = wl1271_cmd_template_set(wl, wlvif->role_id, + CMD_TEMPL_PROBE_RESPONSE, + beacon->data, + beacon->len, 0, + min_rate); +end_bcn: + dev_kfree_skb(beacon); + if (ret < 0) + goto out; + +out: + return ret; +} + +static int wl1271_bss_beacon_info_changed(struct wl1271 *wl, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changed) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + bool is_ap = (wlvif->bss_type == BSS_TYPE_AP_BSS); + int ret = 0; + + if (changed & BSS_CHANGED_BEACON_INT) { + wl1271_debug(DEBUG_MASTER, "beacon interval updated: %d", + bss_conf->beacon_int); + + wlvif->beacon_int = bss_conf->beacon_int; + } + + if ((changed & BSS_CHANGED_AP_PROBE_RESP) && is_ap) { + u32 rate = wl1271_tx_min_rate_get(wl, wlvif->basic_rate_set); + + wl1271_ap_set_probe_resp_tmpl(wl, rate, vif); + } + + if (changed & BSS_CHANGED_BEACON) { + ret = wlcore_set_beacon_template(wl, vif, is_ap); + if (ret < 0) + goto out; + } + +out: + if (ret != 0) + wl1271_error("beacon info change failed: %d", ret); + return ret; +} + +/* AP mode changes */ +static void wl1271_bss_info_changed_ap(struct wl1271 *wl, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changed) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + int ret = 0; + + if (changed & BSS_CHANGED_BASIC_RATES) { + u32 rates = bss_conf->basic_rates; + + wlvif->basic_rate_set = wl1271_tx_enabled_rates_get(wl, rates, + wlvif->band); + wlvif->basic_rate = wl1271_tx_min_rate_get(wl, + wlvif->basic_rate_set); + + ret = wl1271_init_ap_rates(wl, wlvif); + if (ret < 0) { + wl1271_error("AP rate policy change failed %d", ret); + goto out; + } + + ret = wl1271_ap_init_templates(wl, vif); + if (ret < 0) + goto out; + + ret = wl1271_ap_set_probe_resp_tmpl(wl, wlvif->basic_rate, vif); + if (ret < 0) + goto out; + + ret = wlcore_set_beacon_template(wl, vif, true); + if (ret < 0) + goto out; + } + + ret = wl1271_bss_beacon_info_changed(wl, vif, bss_conf, changed); + if (ret < 0) + goto out; + + if (changed & BSS_CHANGED_BEACON_ENABLED) { + if (bss_conf->enable_beacon) { + if (!test_bit(WLVIF_FLAG_AP_STARTED, &wlvif->flags)) { + ret = wl12xx_cmd_role_start_ap(wl, wlvif); + if (ret < 0) + goto out; + + ret = wl1271_ap_init_hwenc(wl, wlvif); + if (ret < 0) + goto out; + + set_bit(WLVIF_FLAG_AP_STARTED, &wlvif->flags); + wl1271_debug(DEBUG_AP, "started AP"); + } + } else { + if (test_bit(WLVIF_FLAG_AP_STARTED, &wlvif->flags)) { + ret = wl12xx_cmd_role_stop_ap(wl, wlvif); + if (ret < 0) + goto out; + + clear_bit(WLVIF_FLAG_AP_STARTED, &wlvif->flags); + clear_bit(WLVIF_FLAG_AP_PROBE_RESP_SET, + &wlvif->flags); + wl1271_debug(DEBUG_AP, "stopped AP"); + } + } + } + + ret = wl1271_bss_erp_info_changed(wl, vif, bss_conf, changed); + if (ret < 0) + goto out; + + /* Handle HT information change */ + if ((changed & BSS_CHANGED_HT) && + (bss_conf->chandef.width != NL80211_CHAN_WIDTH_20_NOHT)) { + ret = wl1271_acx_set_ht_information(wl, wlvif, + bss_conf->ht_operation_mode); + if (ret < 0) { + wl1271_warning("Set ht information failed %d", ret); + goto out; + } + } + +out: + return; +} + +static int wlcore_set_bssid(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct ieee80211_bss_conf *bss_conf, + u32 sta_rate_set) +{ + u32 rates; + int ret; + + wl1271_debug(DEBUG_MAC80211, + "changed_bssid: %pM, aid: %d, bcn_int: %d, brates: 0x%x sta_rate_set: 0x%x", + bss_conf->bssid, bss_conf->aid, + bss_conf->beacon_int, + bss_conf->basic_rates, sta_rate_set); + + wlvif->beacon_int = bss_conf->beacon_int; + rates = bss_conf->basic_rates; + wlvif->basic_rate_set = + wl1271_tx_enabled_rates_get(wl, rates, + wlvif->band); + wlvif->basic_rate = + wl1271_tx_min_rate_get(wl, + wlvif->basic_rate_set); + + if (sta_rate_set) + wlvif->rate_set = + wl1271_tx_enabled_rates_get(wl, + sta_rate_set, + wlvif->band); + + /* we only support sched_scan while not connected */ + if (wl->sched_vif == wlvif) + wl->ops->sched_scan_stop(wl, wlvif); + + ret = wl1271_acx_sta_rate_policies(wl, wlvif); + if (ret < 0) + return ret; + + ret = wl12xx_cmd_build_null_data(wl, wlvif); + if (ret < 0) + return ret; + + ret = wl1271_build_qos_null_data(wl, wl12xx_wlvif_to_vif(wlvif)); + if (ret < 0) + return ret; + + wlcore_set_ssid(wl, wlvif); + + set_bit(WLVIF_FLAG_IN_USE, &wlvif->flags); + + return 0; +} + +static int wlcore_clear_bssid(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + int ret; + + /* revert back to minimum rates for the current band */ + wl1271_set_band_rate(wl, wlvif); + wlvif->basic_rate = wl1271_tx_min_rate_get(wl, wlvif->basic_rate_set); + + ret = wl1271_acx_sta_rate_policies(wl, wlvif); + if (ret < 0) + return ret; + + if (wlvif->bss_type == BSS_TYPE_STA_BSS && + test_bit(WLVIF_FLAG_IN_USE, &wlvif->flags)) { + ret = wl12xx_cmd_role_stop_sta(wl, wlvif); + if (ret < 0) + return ret; + } + + clear_bit(WLVIF_FLAG_IN_USE, &wlvif->flags); + return 0; +} +/* STA/IBSS mode changes */ +static void wl1271_bss_info_changed_sta(struct wl1271 *wl, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changed) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + bool do_join = false; + bool is_ibss = (wlvif->bss_type == BSS_TYPE_IBSS); + bool ibss_joined = false; + u32 sta_rate_set = 0; + int ret; + struct ieee80211_sta *sta; + bool sta_exists = false; + struct ieee80211_sta_ht_cap sta_ht_cap; + + if (is_ibss) { + ret = wl1271_bss_beacon_info_changed(wl, vif, bss_conf, + changed); + if (ret < 0) + goto out; + } + + if (changed & BSS_CHANGED_IBSS) { + if (bss_conf->ibss_joined) { + set_bit(WLVIF_FLAG_IBSS_JOINED, &wlvif->flags); + ibss_joined = true; + } else { + wlcore_unset_assoc(wl, wlvif); + wl12xx_cmd_role_stop_sta(wl, wlvif); + } + } + + if ((changed & BSS_CHANGED_BEACON_INT) && ibss_joined) + do_join = true; + + /* Need to update the SSID (for filtering etc) */ + if ((changed & BSS_CHANGED_BEACON) && ibss_joined) + do_join = true; + + if ((changed & BSS_CHANGED_BEACON_ENABLED) && ibss_joined) { + wl1271_debug(DEBUG_ADHOC, "ad-hoc beaconing: %s", + bss_conf->enable_beacon ? "enabled" : "disabled"); + + do_join = true; + } + + if (changed & BSS_CHANGED_CQM) { + bool enable = false; + if (bss_conf->cqm_rssi_thold) + enable = true; + ret = wl1271_acx_rssi_snr_trigger(wl, wlvif, enable, + bss_conf->cqm_rssi_thold, + bss_conf->cqm_rssi_hyst); + if (ret < 0) + goto out; + wlvif->rssi_thold = bss_conf->cqm_rssi_thold; + } + + if (changed & (BSS_CHANGED_BSSID | BSS_CHANGED_HT | + BSS_CHANGED_ASSOC)) { + rcu_read_lock(); + sta = ieee80211_find_sta(vif, bss_conf->bssid); + if (sta) { + u8 *rx_mask = sta->ht_cap.mcs.rx_mask; + + /* save the supp_rates of the ap */ + sta_rate_set = sta->supp_rates[wlvif->band]; + if (sta->ht_cap.ht_supported) + sta_rate_set |= + (rx_mask[0] << HW_HT_RATES_OFFSET) | + (rx_mask[1] << HW_MIMO_RATES_OFFSET); + sta_ht_cap = sta->ht_cap; + sta_exists = true; + } + + rcu_read_unlock(); + } + + if (changed & BSS_CHANGED_BSSID) { + if (!is_zero_ether_addr(bss_conf->bssid)) { + ret = wlcore_set_bssid(wl, wlvif, bss_conf, + sta_rate_set); + if (ret < 0) + goto out; + + /* Need to update the BSSID (for filtering etc) */ + do_join = true; + } else { + ret = wlcore_clear_bssid(wl, wlvif); + if (ret < 0) + goto out; + } + } + + if (changed & BSS_CHANGED_IBSS) { + wl1271_debug(DEBUG_ADHOC, "ibss_joined: %d", + bss_conf->ibss_joined); + + if (bss_conf->ibss_joined) { + u32 rates = bss_conf->basic_rates; + wlvif->basic_rate_set = + wl1271_tx_enabled_rates_get(wl, rates, + wlvif->band); + wlvif->basic_rate = + wl1271_tx_min_rate_get(wl, + wlvif->basic_rate_set); + + /* by default, use 11b + OFDM rates */ + wlvif->rate_set = CONF_TX_IBSS_DEFAULT_RATES; + ret = wl1271_acx_sta_rate_policies(wl, wlvif); + if (ret < 0) + goto out; + } + } + + ret = wl1271_bss_erp_info_changed(wl, vif, bss_conf, changed); + if (ret < 0) + goto out; + + if (do_join) { + ret = wlcore_join(wl, wlvif); + if (ret < 0) { + wl1271_warning("cmd join failed %d", ret); + goto out; + } + } + + if (changed & BSS_CHANGED_ASSOC) { + if (bss_conf->assoc) { + ret = wlcore_set_assoc(wl, wlvif, bss_conf, + sta_rate_set); + if (ret < 0) + goto out; + + if (test_bit(WLVIF_FLAG_STA_AUTHORIZED, &wlvif->flags)) + wl12xx_set_authorized(wl, wlvif); + } else { + wlcore_unset_assoc(wl, wlvif); + } + } + + if (changed & BSS_CHANGED_PS) { + if ((bss_conf->ps) && + test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags) && + !test_bit(WLVIF_FLAG_IN_PS, &wlvif->flags)) { + int ps_mode; + char *ps_mode_str; + + if (wl->conf.conn.forced_ps) { + ps_mode = STATION_POWER_SAVE_MODE; + ps_mode_str = "forced"; + } else { + ps_mode = STATION_AUTO_PS_MODE; + ps_mode_str = "auto"; + } + + wl1271_debug(DEBUG_PSM, "%s ps enabled", ps_mode_str); + + ret = wl1271_ps_set_mode(wl, wlvif, ps_mode); + if (ret < 0) + wl1271_warning("enter %s ps failed %d", + ps_mode_str, ret); + } else if (!bss_conf->ps && + test_bit(WLVIF_FLAG_IN_PS, &wlvif->flags)) { + wl1271_debug(DEBUG_PSM, "auto ps disabled"); + + ret = wl1271_ps_set_mode(wl, wlvif, + STATION_ACTIVE_MODE); + if (ret < 0) + wl1271_warning("exit auto ps failed %d", ret); + } + } + + /* Handle new association with HT. Do this after join. */ + if (sta_exists && + (changed & BSS_CHANGED_HT)) { + bool enabled = + bss_conf->chandef.width != NL80211_CHAN_WIDTH_20_NOHT; + + ret = wlcore_hw_set_peer_cap(wl, + &sta_ht_cap, + enabled, + wlvif->rate_set, + wlvif->sta.hlid); + if (ret < 0) { + wl1271_warning("Set ht cap failed %d", ret); + goto out; + + } + + if (enabled) { + ret = wl1271_acx_set_ht_information(wl, wlvif, + bss_conf->ht_operation_mode); + if (ret < 0) { + wl1271_warning("Set ht information failed %d", + ret); + goto out; + } + } + } + + /* Handle arp filtering. Done after join. */ + if ((changed & BSS_CHANGED_ARP_FILTER) || + (!is_ibss && (changed & BSS_CHANGED_QOS))) { + __be32 addr = bss_conf->arp_addr_list[0]; + wlvif->sta.qos = bss_conf->qos; + WARN_ON(wlvif->bss_type != BSS_TYPE_STA_BSS); + + if (bss_conf->arp_addr_cnt == 1 && bss_conf->assoc) { + wlvif->ip_addr = addr; + /* + * The template should have been configured only upon + * association. however, it seems that the correct ip + * isn't being set (when sending), so we have to + * reconfigure the template upon every ip change. + */ + ret = wl1271_cmd_build_arp_rsp(wl, wlvif); + if (ret < 0) { + wl1271_warning("build arp rsp failed: %d", ret); + goto out; + } + + ret = wl1271_acx_arp_ip_filter(wl, wlvif, + (ACX_ARP_FILTER_ARP_FILTERING | + ACX_ARP_FILTER_AUTO_ARP), + addr); + } else { + wlvif->ip_addr = 0; + ret = wl1271_acx_arp_ip_filter(wl, wlvif, 0, addr); + } + + if (ret < 0) + goto out; + } + +out: + return; +} + +static void wl1271_op_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changed) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + bool is_ap = (wlvif->bss_type == BSS_TYPE_AP_BSS); + int ret; + + wl1271_debug(DEBUG_MAC80211, "mac80211 bss info role %d changed 0x%x", + wlvif->role_id, (int)changed); + + /* + * make sure to cancel pending disconnections if our association + * state changed + */ + if (!is_ap && (changed & BSS_CHANGED_ASSOC)) + cancel_delayed_work_sync(&wlvif->connection_loss_work); + + if (is_ap && (changed & BSS_CHANGED_BEACON_ENABLED) && + !bss_conf->enable_beacon) + wl1271_tx_flush(wl); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + if (unlikely(!test_bit(WLVIF_FLAG_INITIALIZED, &wlvif->flags))) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + if (is_ap) + wl1271_bss_info_changed_ap(wl, vif, bss_conf, changed); + else + wl1271_bss_info_changed_sta(wl, vif, bss_conf, changed); + + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); +} + +static int wlcore_op_add_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx) +{ + wl1271_debug(DEBUG_MAC80211, "mac80211 add chanctx %d (type %d)", + ieee80211_frequency_to_channel(ctx->def.chan->center_freq), + cfg80211_get_chandef_type(&ctx->def)); + return 0; +} + +static void wlcore_op_remove_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx) +{ + wl1271_debug(DEBUG_MAC80211, "mac80211 remove chanctx %d (type %d)", + ieee80211_frequency_to_channel(ctx->def.chan->center_freq), + cfg80211_get_chandef_type(&ctx->def)); +} + +static void wlcore_op_change_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx, + u32 changed) +{ + wl1271_debug(DEBUG_MAC80211, + "mac80211 change chanctx %d (type %d) changed 0x%x", + ieee80211_frequency_to_channel(ctx->def.chan->center_freq), + cfg80211_get_chandef_type(&ctx->def), changed); +} + +static int wlcore_op_assign_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *ctx) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + int channel = ieee80211_frequency_to_channel( + ctx->def.chan->center_freq); + + wl1271_debug(DEBUG_MAC80211, + "mac80211 assign chanctx (role %d) %d (type %d)", + wlvif->role_id, channel, cfg80211_get_chandef_type(&ctx->def)); + + mutex_lock(&wl->mutex); + + wlvif->band = ctx->def.chan->band; + wlvif->channel = channel; + wlvif->channel_type = cfg80211_get_chandef_type(&ctx->def); + + /* update default rates according to the band */ + wl1271_set_band_rate(wl, wlvif); + + mutex_unlock(&wl->mutex); + + return 0; +} + +static void wlcore_op_unassign_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_chanctx_conf *ctx) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + + wl1271_debug(DEBUG_MAC80211, + "mac80211 unassign chanctx (role %d) %d (type %d)", + wlvif->role_id, + ieee80211_frequency_to_channel(ctx->def.chan->center_freq), + cfg80211_get_chandef_type(&ctx->def)); + + wl1271_tx_flush(wl); +} + +static int wl1271_op_conf_tx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u16 queue, + const struct ieee80211_tx_queue_params *params) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + u8 ps_scheme; + int ret = 0; + + mutex_lock(&wl->mutex); + + wl1271_debug(DEBUG_MAC80211, "mac80211 conf tx %d", queue); + + if (params->uapsd) + ps_scheme = CONF_PS_SCHEME_UPSD_TRIGGER; + else + ps_scheme = CONF_PS_SCHEME_LEGACY; + + if (!test_bit(WLVIF_FLAG_INITIALIZED, &wlvif->flags)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + /* + * the txop is confed in units of 32us by the mac80211, + * we need us + */ + ret = wl1271_acx_ac_cfg(wl, wlvif, wl1271_tx_get_queue(queue), + params->cw_min, params->cw_max, + params->aifs, params->txop << 5); + if (ret < 0) + goto out_sleep; + + ret = wl1271_acx_tid_cfg(wl, wlvif, wl1271_tx_get_queue(queue), + CONF_CHANNEL_TYPE_EDCF, + wl1271_tx_get_queue(queue), + ps_scheme, CONF_ACK_POLICY_LEGACY, + 0, 0); + +out_sleep: + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static u64 wl1271_op_get_tsf(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + u64 mactime = ULLONG_MAX; + int ret; + + wl1271_debug(DEBUG_MAC80211, "mac80211 get tsf"); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl12xx_acx_tsf_info(wl, wlvif, &mactime); + if (ret < 0) + goto out_sleep; + +out_sleep: + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + return mactime; +} + +static int wl1271_op_get_survey(struct ieee80211_hw *hw, int idx, + struct survey_info *survey) +{ + struct ieee80211_conf *conf = &hw->conf; + + if (idx != 0) + return -ENOENT; + + survey->channel = conf->chandef.chan; + survey->filled = 0; + return 0; +} + +static int wl1271_allocate_sta(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct ieee80211_sta *sta) +{ + struct wl1271_station *wl_sta; + int ret; + + + if (wl->active_sta_count >= AP_MAX_STATIONS) { + wl1271_warning("could not allocate HLID - too much stations"); + return -EBUSY; + } + + wl_sta = (struct wl1271_station *)sta->drv_priv; + ret = wl12xx_allocate_link(wl, wlvif, &wl_sta->hlid); + if (ret < 0) { + wl1271_warning("could not allocate HLID - too many links"); + return -EBUSY; + } + + /* use the previous security seq, if this is a recovery/resume */ + wl->links[wl_sta->hlid].total_freed_pkts = wl_sta->total_freed_pkts; + + set_bit(wl_sta->hlid, wlvif->ap.sta_hlid_map); + memcpy(wl->links[wl_sta->hlid].addr, sta->addr, ETH_ALEN); + wl->active_sta_count++; + return 0; +} + +void wl1271_free_sta(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 hlid) +{ + struct wl1271_station *wl_sta; + struct ieee80211_sta *sta; + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + + if (!test_bit(hlid, wlvif->ap.sta_hlid_map)) + return; + + clear_bit(hlid, wlvif->ap.sta_hlid_map); + __clear_bit(hlid, &wl->ap_ps_map); + __clear_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map); + + /* + * save the last used PN in the private part of iee80211_sta, + * in case of recovery/suspend + */ + rcu_read_lock(); + sta = ieee80211_find_sta(vif, wl->links[hlid].addr); + if (sta) { + wl_sta = (void *)sta->drv_priv; + wl_sta->total_freed_pkts = wl->links[hlid].total_freed_pkts; + + /* + * increment the initial seq number on recovery to account for + * transmitted packets that we haven't yet got in the FW status + */ + if (test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags)) + wl_sta->total_freed_pkts += + WL1271_TX_SQN_POST_RECOVERY_PADDING; + } + rcu_read_unlock(); + + wl12xx_free_link(wl, wlvif, &hlid); + wl->active_sta_count--; + + /* + * rearm the tx watchdog when the last STA is freed - give the FW a + * chance to return STA-buffered packets before complaining. + */ + if (wl->active_sta_count == 0) + wl12xx_rearm_tx_watchdog_locked(wl); +} + +static int wl12xx_sta_add(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct ieee80211_sta *sta) +{ + struct wl1271_station *wl_sta; + int ret = 0; + u8 hlid; + + wl1271_debug(DEBUG_MAC80211, "mac80211 add sta %d", (int)sta->aid); + + ret = wl1271_allocate_sta(wl, wlvif, sta); + if (ret < 0) + return ret; + + wl_sta = (struct wl1271_station *)sta->drv_priv; + hlid = wl_sta->hlid; + + ret = wl12xx_cmd_add_peer(wl, wlvif, sta, hlid); + if (ret < 0) + wl1271_free_sta(wl, wlvif, hlid); + + return ret; +} + +static int wl12xx_sta_remove(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct ieee80211_sta *sta) +{ + struct wl1271_station *wl_sta; + int ret = 0, id; + + wl1271_debug(DEBUG_MAC80211, "mac80211 remove sta %d", (int)sta->aid); + + wl_sta = (struct wl1271_station *)sta->drv_priv; + id = wl_sta->hlid; + if (WARN_ON(!test_bit(id, wlvif->ap.sta_hlid_map))) + return -EINVAL; + + ret = wl12xx_cmd_remove_peer(wl, wl_sta->hlid); + if (ret < 0) + return ret; + + wl1271_free_sta(wl, wlvif, wl_sta->hlid); + return ret; +} + +static void wlcore_roc_if_possible(struct wl1271 *wl, + struct wl12xx_vif *wlvif) +{ + if (find_first_bit(wl->roc_map, + WL12XX_MAX_ROLES) < WL12XX_MAX_ROLES) + return; + + if (WARN_ON(wlvif->role_id == WL12XX_INVALID_ROLE_ID)) + return; + + wl12xx_roc(wl, wlvif, wlvif->role_id, wlvif->band, wlvif->channel); +} + +static void wlcore_update_inconn_sta(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct wl1271_station *wl_sta, + bool in_connection) +{ + if (in_connection) { + if (WARN_ON(wl_sta->in_connection)) + return; + wl_sta->in_connection = true; + if (!wlvif->inconn_count++) + wlcore_roc_if_possible(wl, wlvif); + } else { + if (!wl_sta->in_connection) + return; + + wl_sta->in_connection = false; + wlvif->inconn_count--; + if (WARN_ON(wlvif->inconn_count < 0)) + return; + + if (!wlvif->inconn_count) + if (test_bit(wlvif->role_id, wl->roc_map)) + wl12xx_croc(wl, wlvif->role_id); + } +} + +static int wl12xx_update_sta_state(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct ieee80211_sta *sta, + enum ieee80211_sta_state old_state, + enum ieee80211_sta_state new_state) +{ + struct wl1271_station *wl_sta; + bool is_ap = wlvif->bss_type == BSS_TYPE_AP_BSS; + bool is_sta = wlvif->bss_type == BSS_TYPE_STA_BSS; + int ret; + + wl_sta = (struct wl1271_station *)sta->drv_priv; + + /* Add station (AP mode) */ + if (is_ap && + old_state == IEEE80211_STA_NOTEXIST && + new_state == IEEE80211_STA_NONE) { + ret = wl12xx_sta_add(wl, wlvif, sta); + if (ret) + return ret; + + wlcore_update_inconn_sta(wl, wlvif, wl_sta, true); + } + + /* Remove station (AP mode) */ + if (is_ap && + old_state == IEEE80211_STA_NONE && + new_state == IEEE80211_STA_NOTEXIST) { + /* must not fail */ + wl12xx_sta_remove(wl, wlvif, sta); + + wlcore_update_inconn_sta(wl, wlvif, wl_sta, false); + } + + /* Authorize station (AP mode) */ + if (is_ap && + new_state == IEEE80211_STA_AUTHORIZED) { + ret = wl12xx_cmd_set_peer_state(wl, wlvif, wl_sta->hlid); + if (ret < 0) + return ret; + + ret = wl1271_acx_set_ht_capabilities(wl, &sta->ht_cap, true, + wl_sta->hlid); + if (ret) + return ret; + + wlcore_update_inconn_sta(wl, wlvif, wl_sta, false); + } + + /* Authorize station */ + if (is_sta && + new_state == IEEE80211_STA_AUTHORIZED) { + set_bit(WLVIF_FLAG_STA_AUTHORIZED, &wlvif->flags); + ret = wl12xx_set_authorized(wl, wlvif); + if (ret) + return ret; + } + + if (is_sta && + old_state == IEEE80211_STA_AUTHORIZED && + new_state == IEEE80211_STA_ASSOC) { + clear_bit(WLVIF_FLAG_STA_AUTHORIZED, &wlvif->flags); + clear_bit(WLVIF_FLAG_STA_STATE_SENT, &wlvif->flags); + } + + /* clear ROCs on failure or authorization */ + if (is_sta && + (new_state == IEEE80211_STA_AUTHORIZED || + new_state == IEEE80211_STA_NOTEXIST)) { + if (test_bit(wlvif->role_id, wl->roc_map)) + wl12xx_croc(wl, wlvif->role_id); + } + + if (is_sta && + old_state == IEEE80211_STA_NOTEXIST && + new_state == IEEE80211_STA_NONE) { + if (find_first_bit(wl->roc_map, + WL12XX_MAX_ROLES) >= WL12XX_MAX_ROLES) { + WARN_ON(wlvif->role_id == WL12XX_INVALID_ROLE_ID); + wl12xx_roc(wl, wlvif, wlvif->role_id, + wlvif->band, wlvif->channel); + } + } + return 0; +} + +static int wl12xx_op_sta_state(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + enum ieee80211_sta_state old_state, + enum ieee80211_sta_state new_state) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + int ret; + + wl1271_debug(DEBUG_MAC80211, "mac80211 sta %d state=%d->%d", + sta->aid, old_state, new_state); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) { + ret = -EBUSY; + goto out; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl12xx_update_sta_state(wl, wlvif, sta, old_state, new_state); + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); + if (new_state < old_state) + return 0; + return ret; +} + +static int wl1271_op_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, u16 *ssn, + u8 buf_size) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + int ret; + u8 hlid, *ba_bitmap; + + wl1271_debug(DEBUG_MAC80211, "mac80211 ampdu action %d tid %d", action, + tid); + + /* sanity check - the fields in FW are only 8bits wide */ + if (WARN_ON(tid > 0xFF)) + return -ENOTSUPP; + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) { + ret = -EAGAIN; + goto out; + } + + if (wlvif->bss_type == BSS_TYPE_STA_BSS) { + hlid = wlvif->sta.hlid; + } else if (wlvif->bss_type == BSS_TYPE_AP_BSS) { + struct wl1271_station *wl_sta; + + wl_sta = (struct wl1271_station *)sta->drv_priv; + hlid = wl_sta->hlid; + } else { + ret = -EINVAL; + goto out; + } + + ba_bitmap = &wl->links[hlid].ba_bitmap; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl1271_debug(DEBUG_MAC80211, "mac80211 ampdu: Rx tid %d action %d", + tid, action); + + switch (action) { + case IEEE80211_AMPDU_RX_START: + if (!wlvif->ba_support || !wlvif->ba_allowed) { + ret = -ENOTSUPP; + break; + } + + if (wl->ba_rx_session_count >= wl->ba_rx_session_count_max) { + ret = -EBUSY; + wl1271_error("exceeded max RX BA sessions"); + break; + } + + if (*ba_bitmap & BIT(tid)) { + ret = -EINVAL; + wl1271_error("cannot enable RX BA session on active " + "tid: %d", tid); + break; + } + + ret = wl12xx_acx_set_ba_receiver_session(wl, tid, *ssn, true, + hlid); + if (!ret) { + *ba_bitmap |= BIT(tid); + wl->ba_rx_session_count++; + } + break; + + case IEEE80211_AMPDU_RX_STOP: + if (!(*ba_bitmap & BIT(tid))) { + /* + * this happens on reconfig - so only output a debug + * message for now, and don't fail the function. + */ + wl1271_debug(DEBUG_MAC80211, + "no active RX BA session on tid: %d", + tid); + ret = 0; + break; + } + + ret = wl12xx_acx_set_ba_receiver_session(wl, tid, 0, false, + hlid); + if (!ret) { + *ba_bitmap &= ~BIT(tid); + wl->ba_rx_session_count--; + } + break; + + /* + * The BA initiator session management in FW independently. + * Falling break here on purpose for all TX APDU commands. + */ + case IEEE80211_AMPDU_TX_START: + case IEEE80211_AMPDU_TX_STOP_CONT: + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + case IEEE80211_AMPDU_TX_OPERATIONAL: + ret = -EINVAL; + break; + + default: + wl1271_error("Incorrect ampdu action id=%x\n", action); + ret = -EINVAL; + } + + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static int wl12xx_set_bitrate_mask(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const struct cfg80211_bitrate_mask *mask) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + struct wl1271 *wl = hw->priv; + int i, ret = 0; + + wl1271_debug(DEBUG_MAC80211, "mac80211 set_bitrate_mask 0x%x 0x%x", + mask->control[NL80211_BAND_2GHZ].legacy, + mask->control[NL80211_BAND_5GHZ].legacy); + + mutex_lock(&wl->mutex); + + for (i = 0; i < WLCORE_NUM_BANDS; i++) + wlvif->bitrate_masks[i] = + wl1271_tx_enabled_rates_get(wl, + mask->control[i].legacy, + i); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + if (wlvif->bss_type == BSS_TYPE_STA_BSS && + !test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags)) { + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl1271_set_band_rate(wl, wlvif); + wlvif->basic_rate = + wl1271_tx_min_rate_get(wl, wlvif->basic_rate_set); + ret = wl1271_acx_sta_rate_policies(wl, wlvif); + + wl1271_ps_elp_sleep(wl); + } +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static void wl12xx_op_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_channel_switch *ch_switch) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif; + int ret; + + wl1271_debug(DEBUG_MAC80211, "mac80211 channel switch"); + + wl1271_tx_flush(wl); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state == WLCORE_STATE_OFF)) { + wl12xx_for_each_wlvif_sta(wl, wlvif) { + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + ieee80211_chswitch_done(vif, false); + } + goto out; + } else if (unlikely(wl->state != WLCORE_STATE_ON)) { + goto out; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + /* TODO: change mac80211 to pass vif as param */ + wl12xx_for_each_wlvif_sta(wl, wlvif) { + unsigned long delay_usec; + + ret = wl->ops->channel_switch(wl, wlvif, ch_switch); + if (ret) + goto out_sleep; + + set_bit(WLVIF_FLAG_CS_PROGRESS, &wlvif->flags); + + /* indicate failure 5 seconds after channel switch time */ + delay_usec = ieee80211_tu_to_usec(wlvif->beacon_int) * + ch_switch->count; + ieee80211_queue_delayed_work(hw, &wlvif->channel_switch_work, + usecs_to_jiffies(delay_usec) + + msecs_to_jiffies(5000)); + } + +out_sleep: + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); +} + +static void wlcore_op_flush(struct ieee80211_hw *hw, u32 queues, bool drop) +{ + struct wl1271 *wl = hw->priv; + + wl1271_tx_flush(wl); +} + +static int wlcore_op_remain_on_channel(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel *chan, + int duration, + enum ieee80211_roc_type type) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + struct wl1271 *wl = hw->priv; + int channel, ret = 0; + + channel = ieee80211_frequency_to_channel(chan->center_freq); + + wl1271_debug(DEBUG_MAC80211, "mac80211 roc %d (%d)", + channel, wlvif->role_id); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + /* return EBUSY if we can't ROC right now */ + if (WARN_ON(wl->roc_vif || + find_first_bit(wl->roc_map, + WL12XX_MAX_ROLES) < WL12XX_MAX_ROLES)) { + ret = -EBUSY; + goto out; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl12xx_start_dev(wl, wlvif, chan->band, channel); + if (ret < 0) + goto out_sleep; + + wl->roc_vif = vif; + ieee80211_queue_delayed_work(hw, &wl->roc_complete_work, + msecs_to_jiffies(duration)); +out_sleep: + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); + return ret; +} + +static int __wlcore_roc_completed(struct wl1271 *wl) +{ + struct wl12xx_vif *wlvif; + int ret; + + /* already completed */ + if (unlikely(!wl->roc_vif)) + return 0; + + wlvif = wl12xx_vif_to_data(wl->roc_vif); + + if (!test_bit(WLVIF_FLAG_INITIALIZED, &wlvif->flags)) + return -EBUSY; + + ret = wl12xx_stop_dev(wl, wlvif); + if (ret < 0) + return ret; + + wl->roc_vif = NULL; + + return 0; +} + +static int wlcore_roc_completed(struct wl1271 *wl) +{ + int ret; + + wl1271_debug(DEBUG_MAC80211, "roc complete"); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) { + ret = -EBUSY; + goto out; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = __wlcore_roc_completed(wl); + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static void wlcore_roc_complete_work(struct work_struct *work) +{ + struct delayed_work *dwork; + struct wl1271 *wl; + int ret; + + dwork = container_of(work, struct delayed_work, work); + wl = container_of(dwork, struct wl1271, roc_complete_work); + + ret = wlcore_roc_completed(wl); + if (!ret) + ieee80211_remain_on_channel_expired(wl->hw); +} + +static int wlcore_op_cancel_remain_on_channel(struct ieee80211_hw *hw) +{ + struct wl1271 *wl = hw->priv; + + wl1271_debug(DEBUG_MAC80211, "mac80211 croc"); + + /* TODO: per-vif */ + wl1271_tx_flush(wl); + + /* + * we can't just flush_work here, because it might deadlock + * (as we might get called from the same workqueue) + */ + cancel_delayed_work_sync(&wl->roc_complete_work); + wlcore_roc_completed(wl); + + return 0; +} + +static void wlcore_op_sta_rc_update(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + u32 changed) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + struct wl1271 *wl = hw->priv; + + wlcore_hw_sta_rc_update(wl, wlvif, sta, changed); +} + +static int wlcore_op_get_rssi(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + s8 *rssi_dbm) +{ + struct wl1271 *wl = hw->priv; + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + int ret = 0; + + wl1271_debug(DEBUG_MAC80211, "mac80211 get_rssi"); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out_sleep; + + ret = wlcore_acx_average_rssi(wl, wlvif, rssi_dbm); + if (ret < 0) + goto out_sleep; + +out_sleep: + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static bool wl1271_tx_frames_pending(struct ieee80211_hw *hw) +{ + struct wl1271 *wl = hw->priv; + bool ret = false; + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + /* packets are considered pending if in the TX queue or the FW */ + ret = (wl1271_tx_total_queue_count(wl) > 0) || (wl->tx_frames_cnt > 0); +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +/* can't be const, mac80211 writes to this */ +static struct ieee80211_rate wl1271_rates[] = { + { .bitrate = 10, + .hw_value = CONF_HW_BIT_RATE_1MBPS, + .hw_value_short = CONF_HW_BIT_RATE_1MBPS, }, + { .bitrate = 20, + .hw_value = CONF_HW_BIT_RATE_2MBPS, + .hw_value_short = CONF_HW_BIT_RATE_2MBPS, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 55, + .hw_value = CONF_HW_BIT_RATE_5_5MBPS, + .hw_value_short = CONF_HW_BIT_RATE_5_5MBPS, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 110, + .hw_value = CONF_HW_BIT_RATE_11MBPS, + .hw_value_short = CONF_HW_BIT_RATE_11MBPS, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 60, + .hw_value = CONF_HW_BIT_RATE_6MBPS, + .hw_value_short = CONF_HW_BIT_RATE_6MBPS, }, + { .bitrate = 90, + .hw_value = CONF_HW_BIT_RATE_9MBPS, + .hw_value_short = CONF_HW_BIT_RATE_9MBPS, }, + { .bitrate = 120, + .hw_value = CONF_HW_BIT_RATE_12MBPS, + .hw_value_short = CONF_HW_BIT_RATE_12MBPS, }, + { .bitrate = 180, + .hw_value = CONF_HW_BIT_RATE_18MBPS, + .hw_value_short = CONF_HW_BIT_RATE_18MBPS, }, + { .bitrate = 240, + .hw_value = CONF_HW_BIT_RATE_24MBPS, + .hw_value_short = CONF_HW_BIT_RATE_24MBPS, }, + { .bitrate = 360, + .hw_value = CONF_HW_BIT_RATE_36MBPS, + .hw_value_short = CONF_HW_BIT_RATE_36MBPS, }, + { .bitrate = 480, + .hw_value = CONF_HW_BIT_RATE_48MBPS, + .hw_value_short = CONF_HW_BIT_RATE_48MBPS, }, + { .bitrate = 540, + .hw_value = CONF_HW_BIT_RATE_54MBPS, + .hw_value_short = CONF_HW_BIT_RATE_54MBPS, }, +}; + +/* can't be const, mac80211 writes to this */ +static struct ieee80211_channel wl1271_channels[] = { + { .hw_value = 1, .center_freq = 2412, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 2, .center_freq = 2417, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 3, .center_freq = 2422, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 4, .center_freq = 2427, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 5, .center_freq = 2432, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 6, .center_freq = 2437, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 7, .center_freq = 2442, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 8, .center_freq = 2447, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 9, .center_freq = 2452, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 10, .center_freq = 2457, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 11, .center_freq = 2462, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 12, .center_freq = 2467, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 13, .center_freq = 2472, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 14, .center_freq = 2484, .max_power = WLCORE_MAX_TXPWR }, +}; + +/* can't be const, mac80211 writes to this */ +static struct ieee80211_supported_band wl1271_band_2ghz = { + .channels = wl1271_channels, + .n_channels = ARRAY_SIZE(wl1271_channels), + .bitrates = wl1271_rates, + .n_bitrates = ARRAY_SIZE(wl1271_rates), +}; + +/* 5 GHz data rates for WL1273 */ +static struct ieee80211_rate wl1271_rates_5ghz[] = { + { .bitrate = 60, + .hw_value = CONF_HW_BIT_RATE_6MBPS, + .hw_value_short = CONF_HW_BIT_RATE_6MBPS, }, + { .bitrate = 90, + .hw_value = CONF_HW_BIT_RATE_9MBPS, + .hw_value_short = CONF_HW_BIT_RATE_9MBPS, }, + { .bitrate = 120, + .hw_value = CONF_HW_BIT_RATE_12MBPS, + .hw_value_short = CONF_HW_BIT_RATE_12MBPS, }, + { .bitrate = 180, + .hw_value = CONF_HW_BIT_RATE_18MBPS, + .hw_value_short = CONF_HW_BIT_RATE_18MBPS, }, + { .bitrate = 240, + .hw_value = CONF_HW_BIT_RATE_24MBPS, + .hw_value_short = CONF_HW_BIT_RATE_24MBPS, }, + { .bitrate = 360, + .hw_value = CONF_HW_BIT_RATE_36MBPS, + .hw_value_short = CONF_HW_BIT_RATE_36MBPS, }, + { .bitrate = 480, + .hw_value = CONF_HW_BIT_RATE_48MBPS, + .hw_value_short = CONF_HW_BIT_RATE_48MBPS, }, + { .bitrate = 540, + .hw_value = CONF_HW_BIT_RATE_54MBPS, + .hw_value_short = CONF_HW_BIT_RATE_54MBPS, }, +}; + +/* 5 GHz band channels for WL1273 */ +static struct ieee80211_channel wl1271_channels_5ghz[] = { + { .hw_value = 7, .center_freq = 5035, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 8, .center_freq = 5040, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 9, .center_freq = 5045, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 11, .center_freq = 5055, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 12, .center_freq = 5060, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 16, .center_freq = 5080, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 34, .center_freq = 5170, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 36, .center_freq = 5180, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 38, .center_freq = 5190, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 40, .center_freq = 5200, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 42, .center_freq = 5210, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 44, .center_freq = 5220, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 46, .center_freq = 5230, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 48, .center_freq = 5240, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 52, .center_freq = 5260, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 56, .center_freq = 5280, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 60, .center_freq = 5300, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 64, .center_freq = 5320, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 100, .center_freq = 5500, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 104, .center_freq = 5520, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 108, .center_freq = 5540, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 112, .center_freq = 5560, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 116, .center_freq = 5580, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 120, .center_freq = 5600, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 124, .center_freq = 5620, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 128, .center_freq = 5640, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 132, .center_freq = 5660, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 136, .center_freq = 5680, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 140, .center_freq = 5700, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 149, .center_freq = 5745, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 153, .center_freq = 5765, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 157, .center_freq = 5785, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 161, .center_freq = 5805, .max_power = WLCORE_MAX_TXPWR }, + { .hw_value = 165, .center_freq = 5825, .max_power = WLCORE_MAX_TXPWR }, +}; + +static struct ieee80211_supported_band wl1271_band_5ghz = { + .channels = wl1271_channels_5ghz, + .n_channels = ARRAY_SIZE(wl1271_channels_5ghz), + .bitrates = wl1271_rates_5ghz, + .n_bitrates = ARRAY_SIZE(wl1271_rates_5ghz), +}; + +static const struct ieee80211_ops wl1271_ops = { + .start = wl1271_op_start, + .stop = wlcore_op_stop, + .add_interface = wl1271_op_add_interface, + .remove_interface = wl1271_op_remove_interface, + .change_interface = wl12xx_op_change_interface, +#ifdef CONFIG_PM + .suspend = wl1271_op_suspend, + .resume = wl1271_op_resume, +#endif + .config = wl1271_op_config, + .prepare_multicast = wl1271_op_prepare_multicast, + .configure_filter = wl1271_op_configure_filter, + .tx = wl1271_op_tx, + .set_key = wlcore_op_set_key, + .hw_scan = wl1271_op_hw_scan, + .cancel_hw_scan = wl1271_op_cancel_hw_scan, + .sched_scan_start = wl1271_op_sched_scan_start, + .sched_scan_stop = wl1271_op_sched_scan_stop, + .bss_info_changed = wl1271_op_bss_info_changed, + .set_frag_threshold = wl1271_op_set_frag_threshold, + .set_rts_threshold = wl1271_op_set_rts_threshold, + .conf_tx = wl1271_op_conf_tx, + .get_tsf = wl1271_op_get_tsf, + .get_survey = wl1271_op_get_survey, + .sta_state = wl12xx_op_sta_state, + .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, + .flush = wlcore_op_flush, + .remain_on_channel = wlcore_op_remain_on_channel, + .cancel_remain_on_channel = wlcore_op_cancel_remain_on_channel, + .add_chanctx = wlcore_op_add_chanctx, + .remove_chanctx = wlcore_op_remove_chanctx, + .change_chanctx = wlcore_op_change_chanctx, + .assign_vif_chanctx = wlcore_op_assign_vif_chanctx, + .unassign_vif_chanctx = wlcore_op_unassign_vif_chanctx, + .sta_rc_update = wlcore_op_sta_rc_update, + .get_rssi = wlcore_op_get_rssi, + CFG80211_TESTMODE_CMD(wl1271_tm_cmd) +}; + + +u8 wlcore_rate_to_idx(struct wl1271 *wl, u8 rate, enum ieee80211_band band) +{ + u8 idx; + + BUG_ON(band >= 2); + + if (unlikely(rate >= wl->hw_tx_rate_tbl_size)) { + wl1271_error("Illegal RX rate from HW: %d", rate); + return 0; + } + + idx = wl->band_rate_to_idx[band][rate]; + if (unlikely(idx == CONF_HW_RXTX_RATE_UNSUPPORTED)) { + wl1271_error("Unsupported RX rate from HW: %d", rate); + return 0; + } + + return idx; +} + +static ssize_t wl1271_sysfs_show_bt_coex_state(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct wl1271 *wl = dev_get_drvdata(dev); + ssize_t len; + + len = PAGE_SIZE; + + mutex_lock(&wl->mutex); + len = snprintf(buf, len, "%d\n\n0 - off\n1 - on\n", + wl->sg_enabled); + mutex_unlock(&wl->mutex); + + return len; + +} + +static ssize_t wl1271_sysfs_store_bt_coex_state(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct wl1271 *wl = dev_get_drvdata(dev); + unsigned long res; + int ret; + + ret = kstrtoul(buf, 10, &res); + if (ret < 0) { + wl1271_warning("incorrect value written to bt_coex_mode"); + return count; + } + + mutex_lock(&wl->mutex); + + res = !!res; + + if (res == wl->sg_enabled) + goto out; + + wl->sg_enabled = res; + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl1271_acx_sg_enable(wl, wl->sg_enabled); + wl1271_ps_elp_sleep(wl); + + out: + mutex_unlock(&wl->mutex); + return count; +} + +static DEVICE_ATTR(bt_coex_state, S_IRUGO | S_IWUSR, + wl1271_sysfs_show_bt_coex_state, + wl1271_sysfs_store_bt_coex_state); + +static ssize_t wl1271_sysfs_show_hw_pg_ver(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct wl1271 *wl = dev_get_drvdata(dev); + ssize_t len; + + len = PAGE_SIZE; + + mutex_lock(&wl->mutex); + if (wl->hw_pg_ver >= 0) + len = snprintf(buf, len, "%d\n", wl->hw_pg_ver); + else + len = snprintf(buf, len, "n/a\n"); + mutex_unlock(&wl->mutex); + + return len; +} + +static DEVICE_ATTR(hw_pg_ver, S_IRUGO, + wl1271_sysfs_show_hw_pg_ver, NULL); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) +static ssize_t wl1271_sysfs_read_fwlog(struct file *filp, struct kobject *kobj, +#else +static ssize_t wl1271_sysfs_read_fwlog(struct kobject *kobj, +#endif + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct wl1271 *wl = dev_get_drvdata(dev); + ssize_t len; + int ret; + + ret = mutex_lock_interruptible(&wl->mutex); + if (ret < 0) + return -ERESTARTSYS; + + /* Let only one thread read the log at a time, blocking others */ + while (wl->fwlog_size == 0) { + DEFINE_WAIT(wait); + + prepare_to_wait_exclusive(&wl->fwlog_waitq, + &wait, + TASK_INTERRUPTIBLE); + + if (wl->fwlog_size != 0) { + finish_wait(&wl->fwlog_waitq, &wait); + break; + } + + mutex_unlock(&wl->mutex); + + schedule(); + finish_wait(&wl->fwlog_waitq, &wait); + + if (signal_pending(current)) + return -ERESTARTSYS; + + ret = mutex_lock_interruptible(&wl->mutex); + if (ret < 0) + return -ERESTARTSYS; + } + + /* Check if the fwlog is still valid */ + if (wl->fwlog_size < 0) { + mutex_unlock(&wl->mutex); + return 0; + } + + /* Seeking is not supported - old logs are not kept. Disregard pos. */ + len = min(count, (size_t)wl->fwlog_size); + wl->fwlog_size -= len; + memcpy(buffer, wl->fwlog, len); + + /* Make room for new messages */ + memmove(wl->fwlog, wl->fwlog + len, wl->fwlog_size); + + mutex_unlock(&wl->mutex); + + return len; +} + +static struct bin_attribute fwlog_attr = { + .attr = {.name = "fwlog", .mode = S_IRUSR}, + .read = wl1271_sysfs_read_fwlog, +}; + +static void wl12xx_derive_mac_addresses(struct wl1271 *wl, u32 oui, u32 nic) +{ + int i; + + wl1271_debug(DEBUG_PROBE, "base address: oui %06x nic %06x", + oui, nic); + + if (nic + WLCORE_NUM_MAC_ADDRESSES - wl->num_mac_addr > 0xffffff) + wl1271_warning("NIC part of the MAC address wraps around!"); + + for (i = 0; i < wl->num_mac_addr; i++) { + wl->addresses[i].addr[0] = (u8)(oui >> 16); + wl->addresses[i].addr[1] = (u8)(oui >> 8); + wl->addresses[i].addr[2] = (u8) oui; + wl->addresses[i].addr[3] = (u8)(nic >> 16); + wl->addresses[i].addr[4] = (u8)(nic >> 8); + wl->addresses[i].addr[5] = (u8) nic; + nic++; + } + + /* we may be one address short at the most */ + WARN_ON(wl->num_mac_addr + 1 < WLCORE_NUM_MAC_ADDRESSES); + + /* + * turn on the LAA bit in the first address and use it as + * the last address. + */ + if (wl->num_mac_addr < WLCORE_NUM_MAC_ADDRESSES) { + int idx = WLCORE_NUM_MAC_ADDRESSES - 1; + memcpy(&wl->addresses[idx], &wl->addresses[0], + sizeof(wl->addresses[0])); + /* LAA bit */ + wl->addresses[idx].addr[2] |= BIT(1); + } + + wl->hw->wiphy->n_addresses = WLCORE_NUM_MAC_ADDRESSES; + wl->hw->wiphy->addresses = wl->addresses; +} + +static int wl12xx_get_hw_info(struct wl1271 *wl) +{ + int ret; + + ret = wl12xx_set_power_on(wl); + if (ret < 0) + return ret; + + ret = wlcore_read_reg(wl, REG_CHIP_ID_B, &wl->chip.id); + if (ret < 0) + goto out; + + wl->fuse_oui_addr = 0; + wl->fuse_nic_addr = 0; + + ret = wl->ops->get_pg_ver(wl, &wl->hw_pg_ver); + if (ret < 0) + goto out; + + if (wl->ops->get_mac) + ret = wl->ops->get_mac(wl); + +out: + wl1271_power_off(wl); + return ret; +} + +static int wl1271_register_hw(struct wl1271 *wl) +{ + int ret; + u32 oui_addr = 0, nic_addr = 0; + + if (wl->mac80211_registered) + return 0; + + if (wl->nvs_len >= 12) { + /* NOTE: The wl->nvs->nvs element must be first, in + * order to simplify the casting, we assume it is at + * the beginning of the wl->nvs structure. + */ + u8 *nvs_ptr = (u8 *)wl->nvs; + + oui_addr = + (nvs_ptr[11] << 16) + (nvs_ptr[10] << 8) + nvs_ptr[6]; + nic_addr = + (nvs_ptr[5] << 16) + (nvs_ptr[4] << 8) + nvs_ptr[3]; + } + + /* if the MAC address is zeroed in the NVS derive from fuse */ + if (oui_addr == 0 && nic_addr == 0) { + oui_addr = wl->fuse_oui_addr; + /* fuse has the BD_ADDR, the WLAN addresses are the next two */ + nic_addr = wl->fuse_nic_addr + 1; + } + + wl12xx_derive_mac_addresses(wl, oui_addr, nic_addr); + + ret = ieee80211_register_hw(wl->hw); + if (ret < 0) { + wl1271_error("unable to register mac80211 hw: %d", ret); + goto out; + } + + wl->mac80211_registered = true; + + wl1271_debugfs_init(wl); + + wl1271_notice("loaded"); + +out: + return ret; +} + +static void wl1271_unregister_hw(struct wl1271 *wl) +{ + if (wl->plt) + wl1271_plt_stop(wl); + + ieee80211_unregister_hw(wl->hw); + wl->mac80211_registered = false; + +} + +static const struct ieee80211_iface_limit wlcore_iface_limits[] = { + { + .max = 3, + .types = BIT(NL80211_IFTYPE_STATION), + }, + { + .max = 1, + .types = BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_P2P_CLIENT), + }, +}; + +static struct ieee80211_iface_combination +wlcore_iface_combinations[] = { + { + .max_interfaces = 3, + .limits = wlcore_iface_limits, + .n_limits = ARRAY_SIZE(wlcore_iface_limits), + }, +}; + +static int wl1271_init_ieee80211(struct wl1271 *wl) +{ + int i; + static const u32 cipher_suites[] = { + WLAN_CIPHER_SUITE_WEP40, + WLAN_CIPHER_SUITE_WEP104, + WLAN_CIPHER_SUITE_TKIP, + WLAN_CIPHER_SUITE_CCMP, + WL1271_CIPHER_SUITE_GEM, + }; + + /* The tx descriptor buffer */ + wl->hw->extra_tx_headroom = sizeof(struct wl1271_tx_hw_descr); + + if (wl->quirks & WLCORE_QUIRK_TKIP_HEADER_SPACE) + wl->hw->extra_tx_headroom += WL1271_EXTRA_SPACE_TKIP; + + /* unit us */ + /* FIXME: find a proper value */ + wl->hw->channel_change_time = 10000; + wl->hw->max_listen_interval = wl->conf.conn.max_listen_interval; + + wl->hw->flags = IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_SUPPORTS_DYNAMIC_PS | + IEEE80211_HW_SUPPORTS_UAPSD | + IEEE80211_HW_HAS_RATE_CONTROL | + IEEE80211_HW_CONNECTION_MONITOR | + IEEE80211_HW_REPORTS_TX_ACK_STATUS | + IEEE80211_HW_SPECTRUM_MGMT | + IEEE80211_HW_AP_LINK_PS | + IEEE80211_HW_AMPDU_AGGREGATION | + IEEE80211_HW_TX_AMPDU_SETUP_IN_HW | + IEEE80211_HW_QUEUE_CONTROL; + + wl->hw->wiphy->cipher_suites = cipher_suites; + wl->hw->wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites); + + wl->hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO); + wl->hw->wiphy->max_scan_ssids = 1; + wl->hw->wiphy->max_sched_scan_ssids = 16; + wl->hw->wiphy->max_match_sets = 16; + /* + * Maximum length of elements in scanning probe request templates + * should be the maximum length possible for a template, without + * the IEEE80211 header of the template + */ + wl->hw->wiphy->max_scan_ie_len = WL1271_CMD_TEMPL_MAX_SIZE - + sizeof(struct ieee80211_header); + + wl->hw->wiphy->max_sched_scan_ie_len = WL1271_CMD_TEMPL_MAX_SIZE - + sizeof(struct ieee80211_header); + + wl->hw->wiphy->max_remain_on_channel_duration = 5000; + + wl->hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD | + WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; + + /* make sure all our channels fit in the scanned_ch bitmask */ + BUILD_BUG_ON(ARRAY_SIZE(wl1271_channels) + + ARRAY_SIZE(wl1271_channels_5ghz) > + WL1271_MAX_CHANNELS); + /* + * clear channel flags from the previous usage + * and restore max_power & max_antenna_gain values. + */ + for (i = 0; i < ARRAY_SIZE(wl1271_channels); i++) { + wl1271_band_2ghz.channels[i].flags = 0; + wl1271_band_2ghz.channels[i].max_power = WLCORE_MAX_TXPWR; + wl1271_band_2ghz.channels[i].max_antenna_gain = 0; + } + + for (i = 0; i < ARRAY_SIZE(wl1271_channels_5ghz); i++) { + wl1271_band_5ghz.channels[i].flags = 0; + wl1271_band_5ghz.channels[i].max_power = WLCORE_MAX_TXPWR; + wl1271_band_5ghz.channels[i].max_antenna_gain = 0; + } + + /* + * We keep local copies of the band structs because we need to + * modify them on a per-device basis. + */ + memcpy(&wl->bands[IEEE80211_BAND_2GHZ], &wl1271_band_2ghz, + sizeof(wl1271_band_2ghz)); + memcpy(&wl->bands[IEEE80211_BAND_2GHZ].ht_cap, + &wl->ht_cap[IEEE80211_BAND_2GHZ], + sizeof(*wl->ht_cap)); + memcpy(&wl->bands[IEEE80211_BAND_5GHZ], &wl1271_band_5ghz, + sizeof(wl1271_band_5ghz)); + memcpy(&wl->bands[IEEE80211_BAND_5GHZ].ht_cap, + &wl->ht_cap[IEEE80211_BAND_5GHZ], + sizeof(*wl->ht_cap)); + + wl->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = + &wl->bands[IEEE80211_BAND_2GHZ]; + wl->hw->wiphy->bands[IEEE80211_BAND_5GHZ] = + &wl->bands[IEEE80211_BAND_5GHZ]; + + /* + * allow 4 queues per mac address we support + + * 1 cab queue per mac + one global offchannel Tx queue + */ + wl->hw->queues = (NUM_TX_QUEUES + 1) * WLCORE_NUM_MAC_ADDRESSES + 1; + + /* the last queue is the offchannel queue */ + wl->hw->offchannel_tx_hw_queue = wl->hw->queues - 1; + wl->hw->max_rates = 1; + + wl->hw->wiphy->reg_notifier = wl1271_reg_notify; + + /* the FW answers probe-requests in AP-mode */ + wl->hw->wiphy->flags |= WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD; + wl->hw->wiphy->probe_resp_offload = + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS | + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 | + NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P; + + /* allowed interface combinations */ + wlcore_iface_combinations[0].num_different_channels = wl->num_channels; + wl->hw->wiphy->iface_combinations = wlcore_iface_combinations; + wl->hw->wiphy->n_iface_combinations = + ARRAY_SIZE(wlcore_iface_combinations); + + SET_IEEE80211_DEV(wl->hw, wl->dev); + + wl->hw->sta_data_size = sizeof(struct wl1271_station); + wl->hw->vif_data_size = sizeof(struct wl12xx_vif); + + wl->hw->max_rx_aggregation_subframes = wl->conf.ht.rx_ba_win_size; + + return 0; +} + +#define WL1271_DEFAULT_CHANNEL 0 + +struct ieee80211_hw *wlcore_alloc_hw(size_t priv_size, u32 aggr_buf_size, + u32 mbox_size) +{ + struct ieee80211_hw *hw; + struct wl1271 *wl; + int i, j, ret; + unsigned int order; + + BUILD_BUG_ON(AP_MAX_STATIONS > WL12XX_MAX_LINKS); + + hw = ieee80211_alloc_hw(sizeof(*wl), &wl1271_ops); + if (!hw) { + wl1271_error("could not alloc ieee80211_hw"); + ret = -ENOMEM; + goto err_hw_alloc; + } + + wl = hw->priv; + memset(wl, 0, sizeof(*wl)); + + wl->priv = kzalloc(priv_size, GFP_KERNEL); + if (!wl->priv) { + wl1271_error("could not alloc wl priv"); + ret = -ENOMEM; + goto err_priv_alloc; + } + + INIT_LIST_HEAD(&wl->wlvif_list); + + wl->hw = hw; + + for (i = 0; i < NUM_TX_QUEUES; i++) + for (j = 0; j < WL12XX_MAX_LINKS; j++) + skb_queue_head_init(&wl->links[j].tx_queue[i]); + + skb_queue_head_init(&wl->deferred_rx_queue); + skb_queue_head_init(&wl->deferred_tx_queue); + + INIT_DELAYED_WORK(&wl->elp_work, wl1271_elp_work); + INIT_WORK(&wl->netstack_work, wl1271_netstack_work); + INIT_WORK(&wl->tx_work, wl1271_tx_work); + INIT_WORK(&wl->recovery_work, wl1271_recovery_work); + INIT_DELAYED_WORK(&wl->scan_complete_work, wl1271_scan_complete_work); + INIT_DELAYED_WORK(&wl->roc_complete_work, wlcore_roc_complete_work); + INIT_DELAYED_WORK(&wl->tx_watchdog_work, wl12xx_tx_watchdog_work); + + wl->freezable_wq = create_freezable_workqueue("wl12xx_wq"); + if (!wl->freezable_wq) { + ret = -ENOMEM; + goto err_hw; + } + + wl->channel = WL1271_DEFAULT_CHANNEL; + wl->rx_counter = 0; + wl->power_level = WL1271_DEFAULT_POWER_LEVEL; + wl->band = IEEE80211_BAND_2GHZ; + wl->channel_type = NL80211_CHAN_NO_HT; + wl->flags = 0; + wl->sg_enabled = true; + wl->sleep_auth = WL1271_PSM_ILLEGAL; + wl->recovery_count = 0; + wl->hw_pg_ver = -1; + wl->ap_ps_map = 0; + wl->ap_fw_ps_map = 0; + wl->quirks = 0; + wl->platform_quirks = 0; + wl->system_hlid = WL12XX_SYSTEM_HLID; + wl->active_sta_count = 0; + wl->active_link_count = 0; + wl->fwlog_size = 0; + init_waitqueue_head(&wl->fwlog_waitq); + + /* The system link is always allocated */ + __set_bit(WL12XX_SYSTEM_HLID, wl->links_map); + + memset(wl->tx_frames_map, 0, sizeof(wl->tx_frames_map)); + for (i = 0; i < wl->num_tx_desc; i++) + wl->tx_frames[i] = NULL; + + spin_lock_init(&wl->wl_lock); + + wl->state = WLCORE_STATE_OFF; + wl->fw_type = WL12XX_FW_TYPE_NONE; + mutex_init(&wl->mutex); + mutex_init(&wl->flush_mutex); + init_completion(&wl->nvs_loading_complete); + + order = get_order(aggr_buf_size); + wl->aggr_buf = (u8 *)__get_free_pages(GFP_KERNEL, order); + if (!wl->aggr_buf) { + ret = -ENOMEM; + goto err_wq; + } + wl->aggr_buf_size = aggr_buf_size; + + wl->dummy_packet = wl12xx_alloc_dummy_packet(wl); + if (!wl->dummy_packet) { + ret = -ENOMEM; + goto err_aggr; + } + + /* Allocate one page for the FW log */ + wl->fwlog = (u8 *)get_zeroed_page(GFP_KERNEL); + if (!wl->fwlog) { + ret = -ENOMEM; + goto err_dummy_packet; + } + + wl->mbox_size = mbox_size; + wl->mbox = kmalloc(wl->mbox_size, GFP_KERNEL | GFP_DMA); + if (!wl->mbox) { + ret = -ENOMEM; + goto err_fwlog; + } + + wl->buffer_32 = kmalloc(sizeof(*wl->buffer_32), GFP_KERNEL); + if (!wl->buffer_32) { + ret = -ENOMEM; + goto err_mbox; + } + + return hw; + +err_mbox: + kfree(wl->mbox); + +err_fwlog: + free_page((unsigned long)wl->fwlog); + +err_dummy_packet: + dev_kfree_skb(wl->dummy_packet); + +err_aggr: + free_pages((unsigned long)wl->aggr_buf, order); + +err_wq: + destroy_workqueue(wl->freezable_wq); + +err_hw: + wl1271_debugfs_exit(wl); + kfree(wl->priv); + +err_priv_alloc: + ieee80211_free_hw(hw); + +err_hw_alloc: + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(wlcore_alloc_hw); + +int wlcore_free_hw(struct wl1271 *wl) +{ + /* Unblock any fwlog readers */ + mutex_lock(&wl->mutex); + wl->fwlog_size = -1; + wake_up_interruptible_all(&wl->fwlog_waitq); + mutex_unlock(&wl->mutex); + + device_remove_bin_file(wl->dev, &fwlog_attr); + + device_remove_file(wl->dev, &dev_attr_hw_pg_ver); + + device_remove_file(wl->dev, &dev_attr_bt_coex_state); + kfree(wl->buffer_32); + kfree(wl->mbox); + free_page((unsigned long)wl->fwlog); + dev_kfree_skb(wl->dummy_packet); + free_pages((unsigned long)wl->aggr_buf, get_order(wl->aggr_buf_size)); + + wl1271_debugfs_exit(wl); + + vfree(wl->fw); + wl->fw = NULL; + wl->fw_type = WL12XX_FW_TYPE_NONE; + kfree(wl->nvs); + wl->nvs = NULL; + + kfree(wl->fw_status_1); + kfree(wl->tx_res_if); + destroy_workqueue(wl->freezable_wq); + + kfree(wl->priv); + ieee80211_free_hw(wl->hw); + + return 0; +} +EXPORT_SYMBOL_GPL(wlcore_free_hw); + +static void wlcore_nvs_cb(const struct firmware *fw, void *context) +{ + struct wl1271 *wl = context; + struct platform_device *pdev = wl->pdev; + struct wlcore_platdev_data *pdev_data = pdev->dev.platform_data; + struct wl12xx_platform_data *pdata = pdev_data->pdata; + unsigned long irqflags; + int ret; + + if (fw) { + wl->nvs = kmemdup(fw->data, fw->size, GFP_KERNEL); + if (!wl->nvs) { + wl1271_error("Could not allocate nvs data"); + goto out; + } + wl->nvs_len = fw->size; + } else { + wl1271_debug(DEBUG_BOOT, "Could not get nvs file %s", + WL12XX_NVS_NAME); + wl->nvs = NULL; + wl->nvs_len = 0; + } + + ret = wl->ops->setup(wl); + if (ret < 0) + goto out_free_nvs; + + BUG_ON(wl->num_tx_desc > WLCORE_MAX_TX_DESCRIPTORS); + + /* adjust some runtime configuration parameters */ + wlcore_adjust_conf(wl); + + wl->irq = platform_get_irq(pdev, 0); + wl->platform_quirks = pdata->platform_quirks; + wl->if_ops = pdev_data->if_ops; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) + irqflags = IRQF_TRIGGER_RISING; +#else + if (wl->platform_quirks & WL12XX_PLATFORM_QUIRK_EDGE_IRQ) + irqflags = IRQF_TRIGGER_RISING; + else + irqflags = IRQF_TRIGGER_HIGH | IRQF_ONESHOT; +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31) + ret = compat_request_threaded_irq(&wl->irq_compat, wl->irq, + NULL, wlcore_irq, + irqflags, + pdev->name, wl); +#else + ret = request_threaded_irq(wl->irq, NULL, wlcore_irq, + irqflags, pdev->name, wl); +#endif + if (ret < 0) { + wl1271_error("request_irq() failed: %d", ret); + goto out_free_nvs; + } + +#ifdef CONFIG_PM + ret = enable_irq_wake(wl->irq); + if (!ret) { + wl->irq_wake_enabled = true; + device_init_wakeup(wl->dev, 1); + if (pdata->pwr_in_suspend) { + wl->hw->wiphy->wowlan.flags = WIPHY_WOWLAN_ANY; + wl->hw->wiphy->wowlan.n_patterns = + WL1271_MAX_RX_FILTERS; + wl->hw->wiphy->wowlan.pattern_min_len = 1; + wl->hw->wiphy->wowlan.pattern_max_len = + WL1271_RX_FILTER_MAX_PATTERN_SIZE; + } + } +#endif + disable_irq(wl->irq); + + ret = wl12xx_get_hw_info(wl); + if (ret < 0) { + wl1271_error("couldn't get hw info"); + goto out_irq; + } + + ret = wl->ops->identify_chip(wl); + if (ret < 0) + goto out_irq; + + ret = wl1271_init_ieee80211(wl); + if (ret) + goto out_irq; + + ret = wl1271_register_hw(wl); + if (ret) + goto out_irq; + + /* Create sysfs file to control bt coex state */ + ret = device_create_file(wl->dev, &dev_attr_bt_coex_state); + if (ret < 0) { + wl1271_error("failed to create sysfs file bt_coex_state"); + goto out_unreg; + } + + /* Create sysfs file to get HW PG version */ + ret = device_create_file(wl->dev, &dev_attr_hw_pg_ver); + if (ret < 0) { + wl1271_error("failed to create sysfs file hw_pg_ver"); + goto out_bt_coex_state; + } + + /* Create sysfs file for the FW log */ + ret = device_create_bin_file(wl->dev, &fwlog_attr); + if (ret < 0) { + wl1271_error("failed to create sysfs file fwlog"); + goto out_hw_pg_ver; + } + + wl->initialized = true; + goto out; + +out_hw_pg_ver: + device_remove_file(wl->dev, &dev_attr_hw_pg_ver); + +out_bt_coex_state: + device_remove_file(wl->dev, &dev_attr_bt_coex_state); + +out_unreg: + wl1271_unregister_hw(wl); + +out_irq: +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31) + compat_free_threaded_irq(&wl->irq_compat); +#else + free_irq(wl->irq, wl); +#endif + +out_free_nvs: + kfree(wl->nvs); + +out: + release_firmware(fw); + complete_all(&wl->nvs_loading_complete); +} + +int wlcore_probe(struct wl1271 *wl, struct platform_device *pdev) +{ + int ret; + + if (!wl->ops || !wl->ptable) + return -EINVAL; + + wl->dev = &pdev->dev; + wl->pdev = pdev; + platform_set_drvdata(pdev, wl); + + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + WL12XX_NVS_NAME, &pdev->dev, GFP_KERNEL, + wl, wlcore_nvs_cb); + if (ret < 0) { + wl1271_error("request_firmware_nowait failed: %d", ret); + complete_all(&wl->nvs_loading_complete); + } + + return ret; +} +EXPORT_SYMBOL_GPL(wlcore_probe); + +int wlcore_remove(struct platform_device *pdev) +{ + struct wl1271 *wl = platform_get_drvdata(pdev); + + wait_for_completion(&wl->nvs_loading_complete); + if (!wl->initialized) + return 0; + + if (wl->irq_wake_enabled) { + device_init_wakeup(wl->dev, 0); + disable_irq_wake(wl->irq); + } + wl1271_unregister_hw(wl); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31) + compat_free_threaded_irq(&wl->irq_compat); + compat_destroy_threaded_irq(&wl->irq_compat); +#else + free_irq(wl->irq, wl); +#endif + wlcore_free_hw(wl); + + return 0; +} +EXPORT_SYMBOL_GPL(wlcore_remove); + +u32 wl12xx_debug_level = DEBUG_NONE; +EXPORT_SYMBOL_GPL(wl12xx_debug_level); +module_param_named(debug_level, wl12xx_debug_level, uint, S_IRUSR | S_IWUSR); +MODULE_PARM_DESC(debug_level, "wl12xx debugging level"); + +module_param_named(fwlog, fwlog_param, charp, 0); +MODULE_PARM_DESC(fwlog, + "FW logger options: continuous, ondemand, dbgpins or disable"); + +module_param(bug_on_recovery, int, S_IRUSR | S_IWUSR); +MODULE_PARM_DESC(bug_on_recovery, "BUG() on fw recovery"); + +module_param(no_recovery, int, S_IRUSR | S_IWUSR); +MODULE_PARM_DESC(no_recovery, "Prevent HW recovery. FW will remain stuck."); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Luciano Coelho "); +MODULE_AUTHOR("Juuso Oikarinen "); +MODULE_FIRMWARE(WL12XX_NVS_NAME); diff --git a/drivers/net/wireless/ti/wlcore/ps.c b/drivers/net/wireless/ti/wlcore/ps.c new file mode 100644 index 0000000..9654577 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/ps.c @@ -0,0 +1,328 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "ps.h" +#include "io.h" +#include "tx.h" +#include "debug.h" + +#define WL1271_WAKEUP_TIMEOUT 500 + +#define ELP_ENTRY_DELAY 30 +#define ELP_ENTRY_DELAY_FORCE_PS 5 + +void wl1271_elp_work(struct work_struct *work) +{ + struct delayed_work *dwork; + struct wl1271 *wl; + struct wl12xx_vif *wlvif; + int ret; + + dwork = container_of(work, struct delayed_work, work); + wl = container_of(dwork, struct wl1271, elp_work); + + wl1271_debug(DEBUG_PSM, "elp work"); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + /* our work might have been already cancelled */ + if (unlikely(!test_bit(WL1271_FLAG_ELP_REQUESTED, &wl->flags))) + goto out; + + if (test_bit(WL1271_FLAG_IN_ELP, &wl->flags)) + goto out; + + wl12xx_for_each_wlvif(wl, wlvif) { + if (wlvif->bss_type == BSS_TYPE_AP_BSS) + goto out; + + if (!test_bit(WLVIF_FLAG_IN_PS, &wlvif->flags) && + test_bit(WLVIF_FLAG_IN_USE, &wlvif->flags)) + goto out; + } + + wl1271_debug(DEBUG_PSM, "chip to elp"); + ret = wlcore_raw_write32(wl, HW_ACCESS_ELP_CTRL_REG, ELPCTRL_SLEEP); + if (ret < 0) { + wl12xx_queue_recovery_work(wl); + goto out; + } + + set_bit(WL1271_FLAG_IN_ELP, &wl->flags); + +out: + mutex_unlock(&wl->mutex); +} + +/* Routines to toggle sleep mode while in ELP */ +void wl1271_ps_elp_sleep(struct wl1271 *wl) +{ + struct wl12xx_vif *wlvif; + u32 timeout; + + if (wl->sleep_auth != WL1271_PSM_ELP) + return; + + /* we shouldn't get consecutive sleep requests */ + if (WARN_ON(test_and_set_bit(WL1271_FLAG_ELP_REQUESTED, &wl->flags))) + return; + + wl12xx_for_each_wlvif(wl, wlvif) { + if (wlvif->bss_type == BSS_TYPE_AP_BSS) + return; + + if (!test_bit(WLVIF_FLAG_IN_PS, &wlvif->flags) && + test_bit(WLVIF_FLAG_IN_USE, &wlvif->flags)) + return; + } + + timeout = wl->conf.conn.forced_ps ? + ELP_ENTRY_DELAY_FORCE_PS : ELP_ENTRY_DELAY; + ieee80211_queue_delayed_work(wl->hw, &wl->elp_work, + msecs_to_jiffies(timeout)); +} + +int wl1271_ps_elp_wakeup(struct wl1271 *wl) +{ + DECLARE_COMPLETION_ONSTACK(compl); + unsigned long flags; + int ret; + u32 start_time = jiffies; + bool pending = false; + + /* + * we might try to wake up even if we didn't go to sleep + * before (e.g. on boot) + */ + if (!test_and_clear_bit(WL1271_FLAG_ELP_REQUESTED, &wl->flags)) + return 0; + + /* don't cancel_sync as it might contend for a mutex and deadlock */ + cancel_delayed_work(&wl->elp_work); + + if (!test_bit(WL1271_FLAG_IN_ELP, &wl->flags)) + return 0; + + wl1271_debug(DEBUG_PSM, "waking up chip from elp"); + + /* + * The spinlock is required here to synchronize both the work and + * the completion variable in one entity. + */ + spin_lock_irqsave(&wl->wl_lock, flags); + if (test_bit(WL1271_FLAG_IRQ_RUNNING, &wl->flags)) + pending = true; + else + wl->elp_compl = &compl; + spin_unlock_irqrestore(&wl->wl_lock, flags); + + ret = wlcore_raw_write32(wl, HW_ACCESS_ELP_CTRL_REG, ELPCTRL_WAKE_UP); + if (ret < 0) { + wl12xx_queue_recovery_work(wl); + goto err; + } + + if (!pending) { + ret = wait_for_completion_timeout( + &compl, msecs_to_jiffies(WL1271_WAKEUP_TIMEOUT)); + if (ret == 0) { + wl1271_error("ELP wakeup timeout!"); + wl12xx_queue_recovery_work(wl); + ret = -ETIMEDOUT; + goto err; + } + } + + clear_bit(WL1271_FLAG_IN_ELP, &wl->flags); + + wl1271_debug(DEBUG_PSM, "wakeup time: %u ms", + jiffies_to_msecs(jiffies - start_time)); + goto out; + +err: + spin_lock_irqsave(&wl->wl_lock, flags); + wl->elp_compl = NULL; + spin_unlock_irqrestore(&wl->wl_lock, flags); + return ret; + +out: + return 0; +} + +int wl1271_ps_set_mode(struct wl1271 *wl, struct wl12xx_vif *wlvif, + enum wl1271_cmd_ps_mode mode) +{ + int ret; + u16 timeout = wl->conf.conn.dynamic_ps_timeout; + + switch (mode) { + case STATION_AUTO_PS_MODE: + case STATION_POWER_SAVE_MODE: + wl1271_debug(DEBUG_PSM, "entering psm (mode=%d,timeout=%u)", + mode, timeout); + + ret = wl1271_acx_wake_up_conditions(wl, wlvif, + wl->conf.conn.wake_up_event, + wl->conf.conn.listen_interval); + if (ret < 0) { + wl1271_error("couldn't set wake up conditions"); + return ret; + } + + ret = wl1271_cmd_ps_mode(wl, wlvif, mode, timeout); + if (ret < 0) + return ret; + + set_bit(WLVIF_FLAG_IN_PS, &wlvif->flags); + + /* + * enable beacon early termination. + * Not relevant for 5GHz and for high rates. + */ + if ((wlvif->band == IEEE80211_BAND_2GHZ) && + (wlvif->basic_rate < CONF_HW_BIT_RATE_9MBPS)) { + ret = wl1271_acx_bet_enable(wl, wlvif, true); + if (ret < 0) + return ret; + } + break; + case STATION_ACTIVE_MODE: + wl1271_debug(DEBUG_PSM, "leaving psm"); + + /* disable beacon early termination */ + if ((wlvif->band == IEEE80211_BAND_2GHZ) && + (wlvif->basic_rate < CONF_HW_BIT_RATE_9MBPS)) { + ret = wl1271_acx_bet_enable(wl, wlvif, false); + if (ret < 0) + return ret; + } + + ret = wl1271_cmd_ps_mode(wl, wlvif, mode, 0); + if (ret < 0) + return ret; + + clear_bit(WLVIF_FLAG_IN_PS, &wlvif->flags); + break; + default: + wl1271_warning("trying to set ps to unsupported mode %d", mode); + ret = -EINVAL; + } + + return ret; +} + +static void wl1271_ps_filter_frames(struct wl1271 *wl, u8 hlid) +{ + int i; + struct sk_buff *skb; + struct ieee80211_tx_info *info; + unsigned long flags; + int filtered[NUM_TX_QUEUES]; + struct wl1271_link *lnk = &wl->links[hlid]; + + /* filter all frames currently in the low level queues for this hlid */ + for (i = 0; i < NUM_TX_QUEUES; i++) { + filtered[i] = 0; + while ((skb = skb_dequeue(&lnk->tx_queue[i]))) { + filtered[i]++; + + if (WARN_ON(wl12xx_is_dummy_packet(wl, skb))) + continue; + + info = IEEE80211_SKB_CB(skb); + info->flags |= IEEE80211_TX_STAT_TX_FILTERED; + info->status.rates[0].idx = -1; + ieee80211_tx_status_ni(wl->hw, skb); + } + } + + spin_lock_irqsave(&wl->wl_lock, flags); + for (i = 0; i < NUM_TX_QUEUES; i++) { + wl->tx_queue_count[i] -= filtered[i]; + if (lnk->wlvif) + lnk->wlvif->tx_queue_count[i] -= filtered[i]; + } + spin_unlock_irqrestore(&wl->wl_lock, flags); + + wl1271_handle_tx_low_watermark(wl); +} + +void wl12xx_ps_link_start(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 hlid, bool clean_queues) +{ + struct ieee80211_sta *sta; + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + + if (test_bit(hlid, &wl->ap_ps_map)) + return; + + wl1271_debug(DEBUG_PSM, "start mac80211 PSM on hlid %d pkts %d " + "clean_queues %d", hlid, wl->links[hlid].allocated_pkts, + clean_queues); + + rcu_read_lock(); + sta = ieee80211_find_sta(vif, wl->links[hlid].addr); + if (!sta) { + wl1271_error("could not find sta %pM for starting ps", + wl->links[hlid].addr); + rcu_read_unlock(); + return; + } + + ieee80211_sta_ps_transition_ni(sta, true); + rcu_read_unlock(); + + /* do we want to filter all frames from this link's queues? */ + if (clean_queues) + wl1271_ps_filter_frames(wl, hlid); + + __set_bit(hlid, &wl->ap_ps_map); +} + +void wl12xx_ps_link_end(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 hlid) +{ + struct ieee80211_sta *sta; + struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif); + + if (!test_bit(hlid, &wl->ap_ps_map)) + return; + + wl1271_debug(DEBUG_PSM, "end mac80211 PSM on hlid %d", hlid); + + __clear_bit(hlid, &wl->ap_ps_map); + + rcu_read_lock(); + sta = ieee80211_find_sta(vif, wl->links[hlid].addr); + if (!sta) { + wl1271_error("could not find sta %pM for ending ps", + wl->links[hlid].addr); + goto end; + } + + ieee80211_sta_ps_transition_ni(sta, false); +end: + rcu_read_unlock(); +} diff --git a/drivers/net/wireless/ti/wlcore/ps.h b/drivers/net/wireless/ti/wlcore/ps.h new file mode 100644 index 0000000..de4f9da --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/ps.h @@ -0,0 +1,41 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __PS_H__ +#define __PS_H__ + +#include "wlcore.h" +#include "acx.h" + +int wl1271_ps_set_mode(struct wl1271 *wl, struct wl12xx_vif *wlvif, + enum wl1271_cmd_ps_mode mode); +void wl1271_ps_elp_sleep(struct wl1271 *wl); +int wl1271_ps_elp_wakeup(struct wl1271 *wl); +void wl1271_elp_work(struct work_struct *work); +void wl12xx_ps_link_start(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 hlid, bool clean_queues); +void wl12xx_ps_link_end(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 hlid); + +#define WL1271_PS_COMPLETE_TIMEOUT 500 + +#endif /* __WL1271_PS_H__ */ diff --git a/drivers/net/wireless/ti/wlcore/rx.c b/drivers/net/wireless/ti/wlcore/rx.c new file mode 100644 index 0000000..6791a1a --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/rx.c @@ -0,0 +1,339 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include + +#include "wlcore.h" +#include "debug.h" +#include "acx.h" +#include "rx.h" +#include "tx.h" +#include "io.h" +#include "hw_ops.h" + +/* + * TODO: this is here just for now, it must be removed when the data + * operations are in place. + */ +#include "../wl12xx/reg.h" + +static u32 wlcore_rx_get_buf_size(struct wl1271 *wl, + u32 rx_pkt_desc) +{ + if (wl->quirks & WLCORE_QUIRK_RX_BLOCKSIZE_ALIGN) + return (rx_pkt_desc & ALIGNED_RX_BUF_SIZE_MASK) >> + ALIGNED_RX_BUF_SIZE_SHIFT; + + return (rx_pkt_desc & RX_BUF_SIZE_MASK) >> RX_BUF_SIZE_SHIFT_DIV; +} + +static u32 wlcore_rx_get_align_buf_size(struct wl1271 *wl, u32 pkt_len) +{ + if (wl->quirks & WLCORE_QUIRK_RX_BLOCKSIZE_ALIGN) + return ALIGN(pkt_len, WL12XX_BUS_BLOCK_SIZE); + + return pkt_len; +} + +static void wl1271_rx_status(struct wl1271 *wl, + struct wl1271_rx_descriptor *desc, + struct ieee80211_rx_status *status, + u8 beacon) +{ + memset(status, 0, sizeof(struct ieee80211_rx_status)); + + if ((desc->flags & WL1271_RX_DESC_BAND_MASK) == WL1271_RX_DESC_BAND_BG) + status->band = IEEE80211_BAND_2GHZ; + else + status->band = IEEE80211_BAND_5GHZ; + + status->rate_idx = wlcore_rate_to_idx(wl, desc->rate, status->band); + + /* 11n support */ + if (desc->rate <= wl->hw_min_ht_rate) + status->flag |= RX_FLAG_HT; + + status->signal = desc->rssi; + + /* + * FIXME: In wl1251, the SNR should be divided by two. In wl1271 we + * need to divide by two for now, but TI has been discussing about + * changing it. This needs to be rechecked. + */ + wl->noise = desc->rssi - (desc->snr >> 1); + + status->freq = ieee80211_channel_to_frequency(desc->channel, + status->band); + + if (desc->flags & WL1271_RX_DESC_ENCRYPT_MASK) { + u8 desc_err_code = desc->status & WL1271_RX_DESC_STATUS_MASK; + + status->flag |= RX_FLAG_IV_STRIPPED | RX_FLAG_MMIC_STRIPPED | + RX_FLAG_DECRYPTED; + + if (unlikely(desc_err_code & WL1271_RX_DESC_MIC_FAIL)) { + status->flag |= RX_FLAG_MMIC_ERROR; + wl1271_warning("Michael MIC error. Desc: 0x%x", + desc_err_code); + } + } + + if (beacon) + wlcore_set_pending_regdomain_ch(wl, (u16)desc->channel, + status->band); +} + +static int wl1271_rx_handle_data(struct wl1271 *wl, u8 *data, u32 length, + enum wl_rx_buf_align rx_align, u8 *hlid) +{ + struct wl1271_rx_descriptor *desc; + struct sk_buff *skb; + struct ieee80211_hdr *hdr; + u8 *buf; + u8 beacon = 0; + u8 is_data = 0; + u8 reserved = 0, offset_to_data = 0; + u16 seq_num; + u32 pkt_data_len; + + /* + * In PLT mode we seem to get frames and mac80211 warns about them, + * workaround this by not retrieving them at all. + */ + if (unlikely(wl->plt)) + return -EINVAL; + + pkt_data_len = wlcore_hw_get_rx_packet_len(wl, data, length); + if (!pkt_data_len) { + wl1271_error("Invalid packet arrived from HW. length %d", + length); + return -EINVAL; + } + + if (rx_align == WLCORE_RX_BUF_UNALIGNED) + reserved = RX_BUF_ALIGN; + else if (rx_align == WLCORE_RX_BUF_PADDED) + offset_to_data = RX_BUF_ALIGN; + + /* the data read starts with the descriptor */ + desc = (struct wl1271_rx_descriptor *) data; + + if (desc->packet_class == WL12XX_RX_CLASS_LOGGER) { + size_t len = length - sizeof(*desc); + wl12xx_copy_fwlog(wl, data + sizeof(*desc), len); + wake_up_interruptible(&wl->fwlog_waitq); + return 0; + } + + /* discard corrupted packets */ + if (desc->status & WL1271_RX_DESC_DECRYPT_FAIL) { + hdr = (void *)(data + sizeof(*desc) + offset_to_data); + wl1271_warning("corrupted packet in RX: status: 0x%x len: %d", + desc->status & WL1271_RX_DESC_STATUS_MASK, + pkt_data_len); + wl1271_dump((DEBUG_RX|DEBUG_CMD), "PKT: ", data + sizeof(*desc), + min(pkt_data_len, + ieee80211_hdrlen(hdr->frame_control))); + return -EINVAL; + } + + /* skb length not including rx descriptor */ + skb = __dev_alloc_skb(pkt_data_len + reserved, GFP_KERNEL); + if (!skb) { + wl1271_error("Couldn't allocate RX frame"); + return -ENOMEM; + } + + /* reserve the unaligned payload(if any) */ + skb_reserve(skb, reserved); + + buf = skb_put(skb, pkt_data_len); + + /* + * Copy packets from aggregation buffer to the skbs without rx + * descriptor and with packet payload aligned care. In case of unaligned + * packets copy the packets in offset of 2 bytes guarantee IP header + * payload aligned to 4 bytes. + */ + memcpy(buf, data + sizeof(*desc), pkt_data_len); + if (rx_align == WLCORE_RX_BUF_PADDED) + skb_pull(skb, RX_BUF_ALIGN); + + *hlid = desc->hlid; + + hdr = (struct ieee80211_hdr *)skb->data; + if (ieee80211_is_beacon(hdr->frame_control)) + beacon = 1; + if (ieee80211_is_data_present(hdr->frame_control)) + is_data = 1; + + wl1271_rx_status(wl, desc, IEEE80211_SKB_RXCB(skb), beacon); + wlcore_hw_set_rx_csum(wl, desc, skb); + + seq_num = (le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_SEQ) >> 4; + wl1271_debug(DEBUG_RX, "rx skb 0x%p: %d B %s seq %d hlid %d", skb, + skb->len - desc->pad_len, + beacon ? "beacon" : "", + seq_num, *hlid); + + skb_queue_tail(&wl->deferred_rx_queue, skb); + queue_work(wl->freezable_wq, &wl->netstack_work); + + return is_data; +} + +int wlcore_rx(struct wl1271 *wl, struct wl_fw_status_1 *status) +{ + unsigned long active_hlids[BITS_TO_LONGS(WL12XX_MAX_LINKS)] = {0}; + u32 buf_size; + u32 fw_rx_counter = status->fw_rx_counter % wl->num_rx_desc; + u32 drv_rx_counter = wl->rx_counter % wl->num_rx_desc; + u32 rx_counter; + u32 pkt_len, align_pkt_len; + u32 pkt_offset, des; + u8 hlid; + enum wl_rx_buf_align rx_align; + int ret = 0; + + while (drv_rx_counter != fw_rx_counter) { + buf_size = 0; + rx_counter = drv_rx_counter; + while (rx_counter != fw_rx_counter) { + des = le32_to_cpu(status->rx_pkt_descs[rx_counter]); + pkt_len = wlcore_rx_get_buf_size(wl, des); + align_pkt_len = wlcore_rx_get_align_buf_size(wl, + pkt_len); + if (buf_size + align_pkt_len > wl->aggr_buf_size) + break; + buf_size += align_pkt_len; + rx_counter++; + rx_counter %= wl->num_rx_desc; + } + + if (buf_size == 0) { + wl1271_warning("received empty data"); + break; + } + + /* Read all available packets at once */ + des = le32_to_cpu(status->rx_pkt_descs[drv_rx_counter]); + ret = wlcore_hw_prepare_read(wl, des, buf_size); + if (ret < 0) + goto out; + + ret = wlcore_read_data(wl, REG_SLV_MEM_DATA, wl->aggr_buf, + buf_size, true); + if (ret < 0) + goto out; + + /* Split data into separate packets */ + pkt_offset = 0; + while (pkt_offset < buf_size) { + des = le32_to_cpu(status->rx_pkt_descs[drv_rx_counter]); + pkt_len = wlcore_rx_get_buf_size(wl, des); + rx_align = wlcore_hw_get_rx_buf_align(wl, des); + + /* + * the handle data call can only fail in memory-outage + * conditions, in that case the received frame will just + * be dropped. + */ + if (wl1271_rx_handle_data(wl, + wl->aggr_buf + pkt_offset, + pkt_len, rx_align, + &hlid) == 1) { + if (hlid < WL12XX_MAX_LINKS) + __set_bit(hlid, active_hlids); + else + WARN(1, + "hlid exceeded WL12XX_MAX_LINKS " + "(%d)\n", hlid); + } + + wl->rx_counter++; + drv_rx_counter++; + drv_rx_counter %= wl->num_rx_desc; + pkt_offset += wlcore_rx_get_align_buf_size(wl, pkt_len); + } + } + + /* + * Write the driver's packet counter to the FW. This is only required + * for older hardware revisions + */ + if (wl->quirks & WLCORE_QUIRK_END_OF_TRANSACTION) { + ret = wlcore_write32(wl, WL12XX_REG_RX_DRIVER_COUNTER, + wl->rx_counter); + if (ret < 0) + goto out; + } + + wl12xx_rearm_rx_streaming(wl, active_hlids); + +out: + return ret; +} + +#ifdef CONFIG_PM +int wl1271_rx_filter_enable(struct wl1271 *wl, + int index, bool enable, + struct wl12xx_rx_filter *filter) +{ + int ret; + + if (wl->rx_filter_enabled[index] == enable) { + wl1271_warning("Request to enable an already " + "enabled rx filter %d", index); + return 0; + } + + ret = wl1271_acx_set_rx_filter(wl, index, enable, filter); + + if (ret) { + wl1271_error("Failed to %s rx data filter %d (err=%d)", + enable ? "enable" : "disable", index, ret); + return ret; + } + + wl->rx_filter_enabled[index] = enable; + + return 0; +} + +int wl1271_rx_filter_clear_all(struct wl1271 *wl) +{ + int i, ret = 0; + + for (i = 0; i < WL1271_MAX_RX_FILTERS; i++) { + if (!wl->rx_filter_enabled[i]) + continue; + ret = wl1271_rx_filter_enable(wl, i, 0, NULL); + if (ret) + goto out; + } + +out: + return ret; +} +#endif /* CONFIG_PM */ diff --git a/drivers/net/wireless/ti/wlcore/rx.h b/drivers/net/wireless/ti/wlcore/rx.h new file mode 100644 index 0000000..3363f60 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/rx.h @@ -0,0 +1,152 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 1998-2009 Texas Instruments. All rights reserved. + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __RX_H__ +#define __RX_H__ + +#include + +#define WL1271_RX_MAX_RSSI -30 +#define WL1271_RX_MIN_RSSI -95 + +#define SHORT_PREAMBLE_BIT BIT(0) +#define OFDM_RATE_BIT BIT(6) +#define PBCC_RATE_BIT BIT(7) + +#define PLCP_HEADER_LENGTH 8 +#define RX_DESC_PACKETID_SHIFT 11 +#define RX_MAX_PACKET_ID 3 + +#define RX_DESC_VALID_FCS 0x0001 +#define RX_DESC_MATCH_RXADDR1 0x0002 +#define RX_DESC_MCAST 0x0004 +#define RX_DESC_STAINTIM 0x0008 +#define RX_DESC_VIRTUAL_BM 0x0010 +#define RX_DESC_BCAST 0x0020 +#define RX_DESC_MATCH_SSID 0x0040 +#define RX_DESC_MATCH_BSSID 0x0080 +#define RX_DESC_ENCRYPTION_MASK 0x0300 +#define RX_DESC_MEASURMENT 0x0400 +#define RX_DESC_SEQNUM_MASK 0x1800 +#define RX_DESC_MIC_FAIL 0x2000 +#define RX_DESC_DECRYPT_FAIL 0x4000 + +/* + * RX Descriptor flags: + * + * Bits 0-1 - band + * Bit 2 - STBC + * Bit 3 - A-MPDU + * Bit 4 - HT + * Bits 5-7 - encryption + */ +#define WL1271_RX_DESC_BAND_MASK 0x03 +#define WL1271_RX_DESC_ENCRYPT_MASK 0xE0 + +#define WL1271_RX_DESC_BAND_BG 0x00 +#define WL1271_RX_DESC_BAND_J 0x01 +#define WL1271_RX_DESC_BAND_A 0x02 + +#define WL1271_RX_DESC_STBC BIT(2) +#define WL1271_RX_DESC_A_MPDU BIT(3) +#define WL1271_RX_DESC_HT BIT(4) + +#define WL1271_RX_DESC_ENCRYPT_WEP 0x20 +#define WL1271_RX_DESC_ENCRYPT_TKIP 0x40 +#define WL1271_RX_DESC_ENCRYPT_AES 0x60 +#define WL1271_RX_DESC_ENCRYPT_GEM 0x80 + +/* + * RX Descriptor status + * + * Bits 0-2 - error code + * Bits 3-5 - process_id tag (AP mode FW) + * Bits 6-7 - reserved + */ +#define WL1271_RX_DESC_STATUS_MASK 0x07 + +#define WL1271_RX_DESC_SUCCESS 0x00 +#define WL1271_RX_DESC_DECRYPT_FAIL 0x01 +#define WL1271_RX_DESC_MIC_FAIL 0x02 + +#define RX_MEM_BLOCK_MASK 0xFF +#define RX_BUF_SIZE_MASK 0xFFF00 +#define RX_BUF_SIZE_SHIFT_DIV 6 +#define ALIGNED_RX_BUF_SIZE_MASK 0xFFFF00 +#define ALIGNED_RX_BUF_SIZE_SHIFT 8 + +/* If set, the start of IP payload is not 4 bytes aligned */ +#define RX_BUF_UNALIGNED_PAYLOAD BIT(20) + +/* If set, the buffer was padded by the FW to be 4 bytes aligned */ +#define RX_BUF_PADDED_PAYLOAD BIT(30) + +/* + * Account for the padding inserted by the FW in case of RX_ALIGNMENT + * or for fixing alignment in case the packet wasn't aligned. + */ +#define RX_BUF_ALIGN 2 + +/* Describes the alignment state of a Rx buffer */ +enum wl_rx_buf_align { + WLCORE_RX_BUF_ALIGNED, + WLCORE_RX_BUF_UNALIGNED, + WLCORE_RX_BUF_PADDED, +}; + +enum { + WL12XX_RX_CLASS_UNKNOWN, + WL12XX_RX_CLASS_MANAGEMENT, + WL12XX_RX_CLASS_DATA, + WL12XX_RX_CLASS_QOS_DATA, + WL12XX_RX_CLASS_BCN_PRBRSP, + WL12XX_RX_CLASS_EAPOL, + WL12XX_RX_CLASS_BA_EVENT, + WL12XX_RX_CLASS_AMSDU, + WL12XX_RX_CLASS_LOGGER, +}; + +struct wl1271_rx_descriptor { + __le16 length; + u8 status; + u8 flags; + u8 rate; + u8 channel; + s8 rssi; + u8 snr; + __le32 timestamp; + u8 packet_class; + u8 hlid; + u8 pad_len; + u8 reserved; +} __packed; + +int wlcore_rx(struct wl1271 *wl, struct wl_fw_status_1 *status); +u8 wl1271_rate_to_idx(int rate, enum ieee80211_band band); +int wl1271_rx_filter_enable(struct wl1271 *wl, + int index, bool enable, + struct wl12xx_rx_filter *filter); +int wl1271_rx_filter_clear_all(struct wl1271 *wl); + +#endif diff --git a/drivers/net/wireless/ti/wlcore/scan.c b/drivers/net/wireless/ti/wlcore/scan.c new file mode 100644 index 0000000..16affdd --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/scan.c @@ -0,0 +1,470 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009-2010 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include + +#include "wlcore.h" +#include "debug.h" +#include "cmd.h" +#include "scan.h" +#include "acx.h" +#include "ps.h" +#include "tx.h" + +void wl1271_scan_complete_work(struct work_struct *work) +{ + struct delayed_work *dwork; + struct wl1271 *wl; + struct wl12xx_vif *wlvif; + int ret; + + dwork = container_of(work, struct delayed_work, work); + wl = container_of(dwork, struct wl1271, scan_complete_work); + + wl1271_debug(DEBUG_SCAN, "Scanning complete"); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + if (wl->scan.state == WL1271_SCAN_STATE_IDLE) + goto out; + + wlvif = wl->scan_wlvif; + + /* + * Rearm the tx watchdog just before idling scan. This + * prevents just-finished scans from triggering the watchdog + */ + wl12xx_rearm_tx_watchdog_locked(wl); + + wl->scan.state = WL1271_SCAN_STATE_IDLE; + memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch)); + wl->scan.req = NULL; + wl->scan_wlvif = NULL; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + if (test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags)) { + /* restore hardware connection monitoring template */ + wl1271_cmd_build_ap_probe_req(wl, wlvif, wlvif->probereq); + } + + wl1271_ps_elp_sleep(wl); + + if (wl->scan.failed) { + wl1271_info("Scan completed due to error."); + wl12xx_queue_recovery_work(wl); + } + + wlcore_cmd_regdomain_config_locked(wl); + + ieee80211_scan_completed(wl->hw, false); + +out: + mutex_unlock(&wl->mutex); + +} + +static void wlcore_started_vifs_iter(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + int *count = (int *)data; + + if (!vif->bss_conf.idle) + (*count)++; +} + +static int wlcore_count_started_vifs(struct wl1271 *wl) +{ + int count = 0; + + ieee80211_iterate_active_interfaces_atomic(wl->hw, + IEEE80211_IFACE_ITER_RESUME_ALL, + wlcore_started_vifs_iter, &count); + return count; +} + +static int +wlcore_scan_get_channels(struct wl1271 *wl, + struct ieee80211_channel *req_channels[], + u32 n_channels, + u32 n_ssids, + struct conn_scan_ch_params *channels, + u32 band, bool radar, bool passive, + int start, int max_channels, + u8 *n_pactive_ch, + int scan_type) +{ + int i, j; + u32 flags; + bool force_passive = !n_ssids; + u32 min_dwell_time_active, max_dwell_time_active; + u32 dwell_time_passive, dwell_time_dfs; + + /* configure dwell times according to scan type */ + if (scan_type == SCAN_TYPE_SEARCH) { + struct conf_scan_settings *c = &wl->conf.scan; + bool active_vif_exists = !!wlcore_count_started_vifs(wl); + + min_dwell_time_active = active_vif_exists ? + c->min_dwell_time_active : + c->min_dwell_time_active_long; + max_dwell_time_active = active_vif_exists ? + c->max_dwell_time_active : + c->max_dwell_time_active_long; + dwell_time_passive = c->dwell_time_passive; + dwell_time_dfs = c->dwell_time_dfs; + } else { + struct conf_sched_scan_settings *c = &wl->conf.sched_scan; + u32 delta_per_probe; + + if (band == IEEE80211_BAND_5GHZ) + delta_per_probe = c->dwell_time_delta_per_probe_5; + else + delta_per_probe = c->dwell_time_delta_per_probe; + + min_dwell_time_active = c->base_dwell_time + + n_ssids * c->num_probe_reqs * delta_per_probe; + + max_dwell_time_active = min_dwell_time_active + + c->max_dwell_time_delta; + dwell_time_passive = c->dwell_time_passive; + dwell_time_dfs = c->dwell_time_dfs; + } + min_dwell_time_active = DIV_ROUND_UP(min_dwell_time_active, 1000); + max_dwell_time_active = DIV_ROUND_UP(max_dwell_time_active, 1000); + dwell_time_passive = DIV_ROUND_UP(dwell_time_passive, 1000); + dwell_time_dfs = DIV_ROUND_UP(dwell_time_dfs, 1000); + + for (i = 0, j = start; + i < n_channels && j < max_channels; + i++) { + flags = req_channels[i]->flags; + + if (force_passive) + flags |= IEEE80211_CHAN_PASSIVE_SCAN; + + if ((req_channels[i]->band == band) && + !(flags & IEEE80211_CHAN_DISABLED) && + (!!(flags & IEEE80211_CHAN_RADAR) == radar) && + /* if radar is set, we ignore the passive flag */ + (radar || + !!(flags & IEEE80211_CHAN_PASSIVE_SCAN) == passive)) { + wl1271_debug(DEBUG_SCAN, "band %d, center_freq %d ", + req_channels[i]->band, + req_channels[i]->center_freq); + wl1271_debug(DEBUG_SCAN, "hw_value %d, flags %X", + req_channels[i]->hw_value, + req_channels[i]->flags); + wl1271_debug(DEBUG_SCAN, "max_power %d", + req_channels[i]->max_power); + wl1271_debug(DEBUG_SCAN, "min_dwell_time %d max dwell time %d", + min_dwell_time_active, + max_dwell_time_active); + + if (flags & IEEE80211_CHAN_RADAR) { + channels[j].flags |= SCAN_CHANNEL_FLAGS_DFS; + + channels[j].passive_duration = + cpu_to_le16(dwell_time_dfs); + } else { + channels[j].passive_duration = + cpu_to_le16(dwell_time_passive); + } + + channels[j].min_duration = + cpu_to_le16(min_dwell_time_active); + channels[j].max_duration = + cpu_to_le16(max_dwell_time_active); + + channels[j].tx_power_att = req_channels[i]->max_power; + channels[j].channel = req_channels[i]->hw_value; + + if (n_pactive_ch && + (band == IEEE80211_BAND_2GHZ) && + (channels[j].channel >= 12) && + (channels[j].channel <= 14) && + (flags & IEEE80211_CHAN_PASSIVE_SCAN) && + !force_passive) { + /* pactive channels treated as DFS */ + channels[j].flags = SCAN_CHANNEL_FLAGS_DFS; + + /* + * n_pactive_ch is counted down from the end of + * the passive channel list + */ + (*n_pactive_ch)++; + wl1271_debug(DEBUG_SCAN, "n_pactive_ch = %d", + *n_pactive_ch); + } + + j++; + } + } + + return j - start; +} + +bool +wlcore_set_scan_chan_params(struct wl1271 *wl, + struct wlcore_scan_channels *cfg, + struct ieee80211_channel *channels[], + u32 n_channels, + u32 n_ssids, + int scan_type) +{ + u8 n_pactive_ch = 0; + + cfg->passive[0] = + wlcore_scan_get_channels(wl, + channels, + n_channels, + n_ssids, + cfg->channels_2, + IEEE80211_BAND_2GHZ, + false, true, 0, + MAX_CHANNELS_2GHZ, + &n_pactive_ch, + scan_type); + cfg->active[0] = + wlcore_scan_get_channels(wl, + channels, + n_channels, + n_ssids, + cfg->channels_2, + IEEE80211_BAND_2GHZ, + false, false, + cfg->passive[0], + MAX_CHANNELS_2GHZ, + &n_pactive_ch, + scan_type); + cfg->passive[1] = + wlcore_scan_get_channels(wl, + channels, + n_channels, + n_ssids, + cfg->channels_5, + IEEE80211_BAND_5GHZ, + false, true, 0, + wl->max_channels_5, + &n_pactive_ch, + scan_type); + cfg->dfs = + wlcore_scan_get_channels(wl, + channels, + n_channels, + n_ssids, + cfg->channels_5, + IEEE80211_BAND_5GHZ, + true, true, + cfg->passive[1], + wl->max_channels_5, + &n_pactive_ch, + scan_type); + cfg->active[1] = + wlcore_scan_get_channels(wl, + channels, + n_channels, + n_ssids, + cfg->channels_5, + IEEE80211_BAND_5GHZ, + false, false, + cfg->passive[1] + cfg->dfs, + wl->max_channels_5, + &n_pactive_ch, + scan_type); + + /* 802.11j channels are not supported yet */ + cfg->passive[2] = 0; + cfg->active[2] = 0; + + cfg->passive_active = n_pactive_ch; + + wl1271_debug(DEBUG_SCAN, " 2.4GHz: active %d passive %d", + cfg->active[0], cfg->passive[0]); + wl1271_debug(DEBUG_SCAN, " 5GHz: active %d passive %d", + cfg->active[1], cfg->passive[1]); + wl1271_debug(DEBUG_SCAN, " DFS: %d", cfg->dfs); + + return cfg->passive[0] || cfg->active[0] || + cfg->passive[1] || cfg->active[1] || cfg->dfs || + cfg->passive[2] || cfg->active[2]; +} +EXPORT_SYMBOL_GPL(wlcore_set_scan_chan_params); + +int wlcore_scan(struct wl1271 *wl, struct ieee80211_vif *vif, + const u8 *ssid, size_t ssid_len, + struct cfg80211_scan_request *req) +{ + struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif); + + /* + * cfg80211 should guarantee that we don't get more channels + * than what we have registered. + */ + BUG_ON(req->n_channels > WL1271_MAX_CHANNELS); + + if (wl->scan.state != WL1271_SCAN_STATE_IDLE) + return -EBUSY; + + wl->scan.state = WL1271_SCAN_STATE_2GHZ_ACTIVE; + + if (ssid_len && ssid) { + wl->scan.ssid_len = ssid_len; + memcpy(wl->scan.ssid, ssid, ssid_len); + } else { + wl->scan.ssid_len = 0; + } + + wl->scan_wlvif = wlvif; + wl->scan.req = req; + memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch)); + + /* we assume failure so that timeout scenarios are handled correctly */ + wl->scan.failed = true; + ieee80211_queue_delayed_work(wl->hw, &wl->scan_complete_work, + msecs_to_jiffies(WL1271_SCAN_TIMEOUT)); + + wl->ops->scan_start(wl, wlvif, req); + + return 0; +} +/* Returns the scan type to be used or a negative value on error */ +int +wlcore_scan_sched_scan_ssid_list(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct cfg80211_sched_scan_request *req) +{ + struct wl1271_cmd_sched_scan_ssid_list *cmd = NULL; + struct cfg80211_match_set *sets = req->match_sets; + struct cfg80211_ssid *ssids = req->ssids; + int ret = 0, type, i, j, n_match_ssids = 0; + + wl1271_debug(DEBUG_CMD, "cmd sched scan ssid list"); + + /* count the match sets that contain SSIDs */ + for (i = 0; i < req->n_match_sets; i++) + if (sets[i].ssid.ssid_len > 0) + n_match_ssids++; + + /* No filter, no ssids or only bcast ssid */ + if (!n_match_ssids && + (!req->n_ssids || + (req->n_ssids == 1 && req->ssids[0].ssid_len == 0))) { + type = SCAN_SSID_FILTER_ANY; + goto out; + } + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out; + } + + cmd->role_id = wlvif->role_id; + if (!n_match_ssids) { + /* No filter, with ssids */ + type = SCAN_SSID_FILTER_DISABLED; + + for (i = 0; i < req->n_ssids; i++) { + cmd->ssids[cmd->n_ssids].type = (ssids[i].ssid_len) ? + SCAN_SSID_TYPE_HIDDEN : SCAN_SSID_TYPE_PUBLIC; + cmd->ssids[cmd->n_ssids].len = ssids[i].ssid_len; + memcpy(cmd->ssids[cmd->n_ssids].ssid, ssids[i].ssid, + ssids[i].ssid_len); + cmd->n_ssids++; + } + } else { + type = SCAN_SSID_FILTER_LIST; + + /* Add all SSIDs from the filters */ + for (i = 0; i < req->n_match_sets; i++) { + /* ignore sets without SSIDs */ + if (!sets[i].ssid.ssid_len) + continue; + + cmd->ssids[cmd->n_ssids].type = SCAN_SSID_TYPE_PUBLIC; + cmd->ssids[cmd->n_ssids].len = sets[i].ssid.ssid_len; + memcpy(cmd->ssids[cmd->n_ssids].ssid, + sets[i].ssid.ssid, sets[i].ssid.ssid_len); + cmd->n_ssids++; + } + if ((req->n_ssids > 1) || + (req->n_ssids == 1 && req->ssids[0].ssid_len > 0)) { + /* + * Mark all the SSIDs passed in the SSID list as HIDDEN, + * so they're used in probe requests. + */ + for (i = 0; i < req->n_ssids; i++) { + if (!req->ssids[i].ssid_len) + continue; + + for (j = 0; j < cmd->n_ssids; j++) + if ((req->ssids[i].ssid_len == + cmd->ssids[j].len) && + !memcmp(req->ssids[i].ssid, + cmd->ssids[j].ssid, + req->ssids[i].ssid_len)) { + cmd->ssids[j].type = + SCAN_SSID_TYPE_HIDDEN; + break; + } + /* Fail if SSID isn't present in the filters */ + if (j == cmd->n_ssids) { + ret = -EINVAL; + goto out_free; + } + } + } + } + + wl1271_dump(DEBUG_SCAN, "SSID_LIST: ", cmd, sizeof(*cmd)); + + ret = wl1271_cmd_send(wl, CMD_CONNECTION_SCAN_SSID_CFG, cmd, + sizeof(*cmd), 0); + if (ret < 0) { + wl1271_error("cmd sched scan ssid list failed"); + goto out_free; + } + +out_free: + kfree(cmd); +out: + if (ret < 0) + return ret; + return type; +} +EXPORT_SYMBOL_GPL(wlcore_scan_sched_scan_ssid_list); + +void wlcore_scan_sched_scan_results(struct wl1271 *wl) +{ + wl1271_debug(DEBUG_SCAN, "got periodic scan results"); + + ieee80211_sched_scan_results(wl->hw); +} +EXPORT_SYMBOL_GPL(wlcore_scan_sched_scan_results); diff --git a/drivers/net/wireless/ti/wlcore/scan.h b/drivers/net/wireless/ti/wlcore/scan.h new file mode 100644 index 0000000..a6ab24b --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/scan.h @@ -0,0 +1,172 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009-2010 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __SCAN_H__ +#define __SCAN_H__ + +#include "wlcore.h" + +int wlcore_scan(struct wl1271 *wl, struct ieee80211_vif *vif, + const u8 *ssid, size_t ssid_len, + struct cfg80211_scan_request *req); +int wl1271_scan_build_probe_req(struct wl1271 *wl, + const u8 *ssid, size_t ssid_len, + const u8 *ie, size_t ie_len, u8 band); +void wl1271_scan_stm(struct wl1271 *wl, struct wl12xx_vif *wlvif); +void wl1271_scan_complete_work(struct work_struct *work); +int wl1271_scan_sched_scan_config(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies); +int wl1271_scan_sched_scan_start(struct wl1271 *wl, struct wl12xx_vif *wlvif); +void wlcore_scan_sched_scan_results(struct wl1271 *wl); + +#define WL1271_SCAN_MAX_CHANNELS 24 +#define WL1271_SCAN_DEFAULT_TAG 1 +#define WL1271_SCAN_CURRENT_TX_PWR 0 +#define WL1271_SCAN_OPT_ACTIVE 0 +#define WL1271_SCAN_OPT_PASSIVE 1 +#define WL1271_SCAN_OPT_SPLIT_SCAN 2 +#define WL1271_SCAN_OPT_PRIORITY_HIGH 4 +/* scan even if we fail to enter psm */ +#define WL1271_SCAN_OPT_FORCE 8 +#define WL1271_SCAN_BAND_2_4_GHZ 0 +#define WL1271_SCAN_BAND_5_GHZ 1 + +#define WL1271_SCAN_TIMEOUT 30000 /* msec */ + +enum { + WL1271_SCAN_STATE_IDLE, + WL1271_SCAN_STATE_2GHZ_ACTIVE, + WL1271_SCAN_STATE_2GHZ_PASSIVE, + WL1271_SCAN_STATE_5GHZ_ACTIVE, + WL1271_SCAN_STATE_5GHZ_PASSIVE, + WL1271_SCAN_STATE_DONE +}; + +struct wl1271_cmd_trigger_scan_to { + struct wl1271_cmd_header header; + + __le32 timeout; +} __packed; + +#define MAX_CHANNELS_2GHZ 14 +#define MAX_CHANNELS_4GHZ 4 + +/* + * This max value here is used only for the struct definition of + * wlcore_scan_channels. This struct is used by both 12xx + * and 18xx (which have different max 5ghz channels value). + * In order to make sure this is large enough, just use the + * max possible 5ghz channels. + */ +#define MAX_CHANNELS_5GHZ 42 + +#define SCAN_MAX_CYCLE_INTERVALS 16 +#define SCAN_MAX_BANDS 3 + +enum { + SCAN_SSID_FILTER_ANY = 0, + SCAN_SSID_FILTER_SPECIFIC = 1, + SCAN_SSID_FILTER_LIST = 2, + SCAN_SSID_FILTER_DISABLED = 3 +}; + +enum { + SCAN_BSS_TYPE_INDEPENDENT, + SCAN_BSS_TYPE_INFRASTRUCTURE, + SCAN_BSS_TYPE_ANY, +}; + +#define SCAN_CHANNEL_FLAGS_DFS BIT(0) /* channel is passive until an + activity is detected on it */ +#define SCAN_CHANNEL_FLAGS_DFS_ENABLED BIT(1) + +struct conn_scan_ch_params { + __le16 min_duration; + __le16 max_duration; + __le16 passive_duration; + + u8 channel; + u8 tx_power_att; + + /* bit 0: DFS channel; bit 1: DFS enabled */ + u8 flags; + + u8 padding[3]; +} __packed; + +#define SCHED_SCAN_MAX_SSIDS 16 + +enum { + SCAN_SSID_TYPE_PUBLIC = 0, + SCAN_SSID_TYPE_HIDDEN = 1, +}; + +struct wl1271_ssid { + u8 type; + u8 len; + u8 ssid[IEEE80211_MAX_SSID_LEN]; + /* u8 padding[2]; */ +} __packed; + +struct wl1271_cmd_sched_scan_ssid_list { + struct wl1271_cmd_header header; + + u8 n_ssids; + struct wl1271_ssid ssids[SCHED_SCAN_MAX_SSIDS]; + u8 role_id; + u8 padding[2]; +} __packed; + +struct wlcore_scan_channels { + u8 passive[SCAN_MAX_BANDS]; /* number of passive scan channels */ + u8 active[SCAN_MAX_BANDS]; /* number of active scan channels */ + u8 dfs; /* number of dfs channels in 5ghz */ + u8 passive_active; /* number of passive before active channels 2.4ghz */ + + struct conn_scan_ch_params channels_2[MAX_CHANNELS_2GHZ]; + struct conn_scan_ch_params channels_5[MAX_CHANNELS_5GHZ]; + struct conn_scan_ch_params channels_4[MAX_CHANNELS_4GHZ]; +}; + +enum { + SCAN_TYPE_SEARCH = 0, + SCAN_TYPE_PERIODIC = 1, + SCAN_TYPE_TRACKING = 2, +}; + +bool +wlcore_set_scan_chan_params(struct wl1271 *wl, + struct wlcore_scan_channels *cfg, + struct ieee80211_channel *channels[], + u32 n_channels, + u32 n_ssids, + int scan_type); + +int +wlcore_scan_sched_scan_ssid_list(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct cfg80211_sched_scan_request *req); + +#endif /* __WL1271_SCAN_H__ */ diff --git a/drivers/net/wireless/ti/wlcore/sdio.c b/drivers/net/wireless/ti/wlcore/sdio.c new file mode 100644 index 0000000..29ef249 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/sdio.c @@ -0,0 +1,419 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009-2010 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wlcore.h" +#include "wl12xx_80211.h" +#include "io.h" + +#ifndef SDIO_VENDOR_ID_TI +#define SDIO_VENDOR_ID_TI 0x0097 +#endif + +#ifndef SDIO_DEVICE_ID_TI_WL1271 +#define SDIO_DEVICE_ID_TI_WL1271 0x4076 +#endif + +static bool dump = false; + +struct wl12xx_sdio_glue { + struct device *dev; + struct platform_device *core; +}; + +static const struct sdio_device_id wl1271_devices[] = { + { SDIO_DEVICE(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271) }, + {} +}; +MODULE_DEVICE_TABLE(sdio, wl1271_devices); + +static void wl1271_sdio_set_block_size(struct device *child, + unsigned int blksz) +{ + struct wl12xx_sdio_glue *glue = dev_get_drvdata(child->parent); + struct sdio_func *func = dev_to_sdio_func(glue->dev); + + sdio_claim_host(func); + sdio_set_block_size(func, blksz); + sdio_release_host(func); +} + +static int __must_check wl12xx_sdio_raw_read(struct device *child, int addr, + void *buf, size_t len, bool fixed) +{ + int ret; + struct wl12xx_sdio_glue *glue = dev_get_drvdata(child->parent); + struct sdio_func *func = dev_to_sdio_func(glue->dev); + + sdio_claim_host(func); + + if (unlikely(dump)) { + printk(KERN_DEBUG "wlcore_sdio: READ from 0x%04x\n", addr); + print_hex_dump(KERN_DEBUG, "wlcore_sdio: READ ", + DUMP_PREFIX_OFFSET, 16, 1, + buf, len, false); + } + + if (unlikely(addr == HW_ACCESS_ELP_CTRL_REG)) { + ((u8 *)buf)[0] = sdio_f0_readb(func, addr, &ret); + dev_dbg(child->parent, "sdio read 52 addr 0x%x, byte 0x%02x\n", + addr, ((u8 *)buf)[0]); + } else { + if (fixed) + ret = sdio_readsb(func, buf, addr, len); + else + ret = sdio_memcpy_fromio(func, buf, addr, len); + + dev_dbg(child->parent, "sdio read 53 addr 0x%x, %zu bytes\n", + addr, len); + } + + sdio_release_host(func); + + if (WARN_ON(ret)) + dev_err(child->parent, "sdio read failed (%d)\n", ret); + + return ret; +} + +static int __must_check wl12xx_sdio_raw_write(struct device *child, int addr, + void *buf, size_t len, bool fixed) +{ + int ret; + struct wl12xx_sdio_glue *glue = dev_get_drvdata(child->parent); + struct sdio_func *func = dev_to_sdio_func(glue->dev); + + sdio_claim_host(func); + + if (unlikely(dump)) { + printk(KERN_DEBUG "wlcore_sdio: WRITE to 0x%04x\n", addr); + print_hex_dump(KERN_DEBUG, "wlcore_sdio: WRITE ", + DUMP_PREFIX_OFFSET, 16, 1, + buf, len, false); + } + + if (unlikely(addr == HW_ACCESS_ELP_CTRL_REG)) { + sdio_f0_writeb(func, ((u8 *)buf)[0], addr, &ret); + dev_dbg(child->parent, "sdio write 52 addr 0x%x, byte 0x%02x\n", + addr, ((u8 *)buf)[0]); + } else { + dev_dbg(child->parent, "sdio write 53 addr 0x%x, %zu bytes\n", + addr, len); + + if (fixed) + ret = sdio_writesb(func, addr, buf, len); + else + ret = sdio_memcpy_toio(func, addr, buf, len); + } + + sdio_release_host(func); + + if (WARN_ON(ret)) + dev_err(child->parent, "sdio write failed (%d)\n", ret); + + return ret; +} + +static int wl12xx_sdio_power_on(struct wl12xx_sdio_glue *glue) +{ + int ret; + struct sdio_func *func = dev_to_sdio_func(glue->dev); + struct mmc_card *card = func->card; + + ret = pm_runtime_get_sync(&card->dev); + if (ret) { + /* + * Runtime PM might be temporarily disabled, or the device + * might have a positive reference counter. Make sure it is + * really powered on. + */ + ret = mmc_power_restore_host(card->host); + if (ret < 0) { + pm_runtime_put_sync(&card->dev); + goto out; + } + } + + sdio_claim_host(func); + sdio_enable_func(func); + sdio_release_host(func); + +out: + return ret; +} + +static int wl12xx_sdio_power_off(struct wl12xx_sdio_glue *glue) +{ + int ret; + struct sdio_func *func = dev_to_sdio_func(glue->dev); + struct mmc_card *card = func->card; + + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); + + /* Power off the card manually in case it wasn't powered off above */ + ret = mmc_power_save_host(card->host); + if (ret < 0) + goto out; + + /* Let runtime PM know the card is powered off */ + pm_runtime_put_sync(&card->dev); + +out: + return ret; +} + +static int wl12xx_sdio_set_power(struct device *child, bool enable) +{ + struct wl12xx_sdio_glue *glue = dev_get_drvdata(child->parent); + + if (enable) + return wl12xx_sdio_power_on(glue); + else + return wl12xx_sdio_power_off(glue); +} + +static struct wl1271_if_operations sdio_ops = { + .read = wl12xx_sdio_raw_read, + .write = wl12xx_sdio_raw_write, + .power = wl12xx_sdio_set_power, + .set_block_size = wl1271_sdio_set_block_size, +}; + +static int wl1271_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct wlcore_platdev_data *pdev_data; + struct wl12xx_sdio_glue *glue; + struct resource res[1]; + mmc_pm_flag_t mmcflags; + int ret = -ENOMEM; + const char *chip_family; + + /* We are only able to handle the wlan function */ + if (func->num != 0x02) + return -ENODEV; + + pdev_data = kzalloc(sizeof(*pdev_data), GFP_KERNEL); + if (!pdev_data) + goto out; + + pdev_data->if_ops = &sdio_ops; + + glue = kzalloc(sizeof(*glue), GFP_KERNEL); + if (!glue) { + dev_err(&func->dev, "can't allocate glue\n"); + goto out_free_pdev_data; + } + + glue->dev = &func->dev; + + /* Grab access to FN0 for ELP reg. */ + func->card->quirks |= MMC_QUIRK_LENIENT_FN0; + + /* Use block mode for transferring over one block size of data */ + func->card->quirks |= MMC_QUIRK_BLKSZ_FOR_BYTE_MODE; + + pdev_data->pdata = wl12xx_get_platform_data(); + if (IS_ERR(pdev_data->pdata)) { + ret = PTR_ERR(pdev_data->pdata); + dev_err(glue->dev, "missing wlan platform data: %d\n", ret); + goto out_free_glue; + } + + /* if sdio can keep power while host is suspended, enable wow */ + mmcflags = sdio_get_host_pm_caps(func); + dev_dbg(glue->dev, "sdio PM caps = 0x%x\n", mmcflags); + + if (mmcflags & MMC_PM_KEEP_POWER) + pdev_data->pdata->pwr_in_suspend = true; + + sdio_set_drvdata(func, glue); + + /* Tell PM core that we don't need the card to be powered now */ + pm_runtime_put_noidle(&func->dev); + + /* + * Due to a hardware bug, we can't differentiate wl18xx from + * wl12xx, because both report the same device ID. The only + * way to differentiate is by checking the SDIO revision, + * which is 3.00 on the wl18xx chips. + */ + if (func->card->cccr.sdio_vsn == SDIO_SDIO_REV_3_00) + chip_family = "wl18xx"; + else + chip_family = "wl12xx"; + + glue->core = platform_device_alloc(chip_family, PLATFORM_DEVID_AUTO); + if (!glue->core) { + dev_err(glue->dev, "can't allocate platform_device"); + ret = -ENOMEM; + goto out_free_glue; + } + + glue->core->dev.parent = &func->dev; + + memset(res, 0x00, sizeof(res)); + + res[0].start = pdev_data->pdata->irq; + res[0].flags = IORESOURCE_IRQ; + res[0].name = "irq"; + + ret = platform_device_add_resources(glue->core, res, ARRAY_SIZE(res)); + if (ret) { + dev_err(glue->dev, "can't add resources\n"); + goto out_dev_put; + } + + ret = platform_device_add_data(glue->core, pdev_data, + sizeof(*pdev_data)); + if (ret) { + dev_err(glue->dev, "can't add platform data\n"); + goto out_dev_put; + } + + ret = platform_device_add(glue->core); + if (ret) { + dev_err(glue->dev, "can't add platform device\n"); + goto out_dev_put; + } + return 0; + +out_dev_put: + platform_device_put(glue->core); + +out_free_glue: + kfree(glue); + +out_free_pdev_data: + kfree(pdev_data); + +out: + return ret; +} + +static void wl1271_remove(struct sdio_func *func) +{ + struct wl12xx_sdio_glue *glue = sdio_get_drvdata(func); + + /* Undo decrement done above in wl1271_probe */ + pm_runtime_get_noresume(&func->dev); + + platform_device_unregister(glue->core); + kfree(glue); +} + +#ifdef CONFIG_PM +static int wl1271_suspend(struct device *dev) +{ + /* Tell MMC/SDIO core it's OK to power down the card + * (if it isn't already), but not to remove it completely */ + struct sdio_func *func = dev_to_sdio_func(dev); + struct wl12xx_sdio_glue *glue = sdio_get_drvdata(func); + struct wl1271 *wl = platform_get_drvdata(glue->core); + mmc_pm_flag_t sdio_flags; + int ret = 0; + + dev_dbg(dev, "wl1271 suspend. wow_enabled: %d\n", + wl->wow_enabled); + + /* check whether sdio should keep power */ + if (wl->wow_enabled) { + sdio_flags = sdio_get_host_pm_caps(func); + + if (!(sdio_flags & MMC_PM_KEEP_POWER)) { + dev_err(dev, "can't keep power while host " + "is suspended\n"); + ret = -EINVAL; + goto out; + } + + /* keep power while host suspended */ + ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); + if (ret) { + dev_err(dev, "error while trying to keep power\n"); + goto out; + } + } +out: + return ret; +} + +static int wl1271_resume(struct device *dev) +{ + dev_dbg(dev, "wl1271 resume\n"); + + return 0; +} + +static const struct dev_pm_ops wl1271_sdio_pm_ops = { + .suspend = wl1271_suspend, + .resume = wl1271_resume, +}; +#endif + +static struct sdio_driver wl1271_sdio_driver = { + .name = "wl1271_sdio", + .id_table = wl1271_devices, + .probe = wl1271_probe, + .remove = wl1271_remove, +#ifdef CONFIG_PM + .drv = { + .pm = &wl1271_sdio_pm_ops, + }, +#endif +}; + +static int __init wl1271_init(void) +{ + return sdio_register_driver(&wl1271_sdio_driver); +} + +static void __exit wl1271_exit(void) +{ + sdio_unregister_driver(&wl1271_sdio_driver); +} + +module_init(wl1271_init); +module_exit(wl1271_exit); + +module_param(dump, bool, S_IRUSR | S_IWUSR); +MODULE_PARM_DESC(dump, "Enable sdio read/write dumps."); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Luciano Coelho "); +MODULE_AUTHOR("Juuso Oikarinen "); diff --git a/drivers/net/wireless/ti/wlcore/spi.c b/drivers/net/wireless/ti/wlcore/spi.c new file mode 100644 index 0000000..e264478 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/spi.c @@ -0,0 +1,453 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wlcore.h" +#include "wl12xx_80211.h" +#include "io.h" + +#define WSPI_CMD_READ 0x40000000 +#define WSPI_CMD_WRITE 0x00000000 +#define WSPI_CMD_FIXED 0x20000000 +#define WSPI_CMD_BYTE_LENGTH 0x1FFE0000 +#define WSPI_CMD_BYTE_LENGTH_OFFSET 17 +#define WSPI_CMD_BYTE_ADDR 0x0001FFFF + +#define WSPI_INIT_CMD_CRC_LEN 5 + +#define WSPI_INIT_CMD_START 0x00 +#define WSPI_INIT_CMD_TX 0x40 +/* the extra bypass bit is sampled by the TNET as '1' */ +#define WSPI_INIT_CMD_BYPASS_BIT 0x80 +#define WSPI_INIT_CMD_FIXEDBUSY_LEN 0x07 +#define WSPI_INIT_CMD_EN_FIXEDBUSY 0x80 +#define WSPI_INIT_CMD_DIS_FIXEDBUSY 0x00 +#define WSPI_INIT_CMD_IOD 0x40 +#define WSPI_INIT_CMD_IP 0x20 +#define WSPI_INIT_CMD_CS 0x10 +#define WSPI_INIT_CMD_WS 0x08 +#define WSPI_INIT_CMD_WSPI 0x01 +#define WSPI_INIT_CMD_END 0x01 + +#define WSPI_INIT_CMD_LEN 8 + +#define HW_ACCESS_WSPI_FIXED_BUSY_LEN \ + ((WL1271_BUSY_WORD_LEN - 4) / sizeof(u32)) +#define HW_ACCESS_WSPI_INIT_CMD_MASK 0 + +/* HW limitation: maximum possible chunk size is 4095 bytes */ +#define WSPI_MAX_CHUNK_SIZE 4092 + +/* + * only support SPI for 12xx - this code should be reworked when 18xx + * support is introduced + */ +#define SPI_AGGR_BUFFER_SIZE (4 * PAGE_SIZE) + +#define WSPI_MAX_NUM_OF_CHUNKS (SPI_AGGR_BUFFER_SIZE / WSPI_MAX_CHUNK_SIZE) + +struct wl12xx_spi_glue { + struct device *dev; + struct platform_device *core; +}; + +static void wl12xx_spi_reset(struct device *child) +{ + struct wl12xx_spi_glue *glue = dev_get_drvdata(child->parent); + u8 *cmd; + struct spi_transfer t; + struct spi_message m; + + cmd = kzalloc(WSPI_INIT_CMD_LEN, GFP_KERNEL); + if (!cmd) { + dev_err(child->parent, + "could not allocate cmd for spi reset\n"); + return; + } + + memset(&t, 0, sizeof(t)); + spi_message_init(&m); + + memset(cmd, 0xff, WSPI_INIT_CMD_LEN); + + t.tx_buf = cmd; + t.len = WSPI_INIT_CMD_LEN; + spi_message_add_tail(&t, &m); + + spi_sync(to_spi_device(glue->dev), &m); + + kfree(cmd); +} + +static void wl12xx_spi_init(struct device *child) +{ + struct wl12xx_spi_glue *glue = dev_get_drvdata(child->parent); + u8 crc[WSPI_INIT_CMD_CRC_LEN], *cmd; + struct spi_transfer t; + struct spi_message m; + + cmd = kzalloc(WSPI_INIT_CMD_LEN, GFP_KERNEL); + if (!cmd) { + dev_err(child->parent, + "could not allocate cmd for spi init\n"); + return; + } + + memset(crc, 0, sizeof(crc)); + memset(&t, 0, sizeof(t)); + spi_message_init(&m); + + /* + * Set WSPI_INIT_COMMAND + * the data is being send from the MSB to LSB + */ + cmd[2] = 0xff; + cmd[3] = 0xff; + cmd[1] = WSPI_INIT_CMD_START | WSPI_INIT_CMD_TX; + cmd[0] = 0; + cmd[7] = 0; + cmd[6] |= HW_ACCESS_WSPI_INIT_CMD_MASK << 3; + cmd[6] |= HW_ACCESS_WSPI_FIXED_BUSY_LEN & WSPI_INIT_CMD_FIXEDBUSY_LEN; + + if (HW_ACCESS_WSPI_FIXED_BUSY_LEN == 0) + cmd[5] |= WSPI_INIT_CMD_DIS_FIXEDBUSY; + else + cmd[5] |= WSPI_INIT_CMD_EN_FIXEDBUSY; + + cmd[5] |= WSPI_INIT_CMD_IOD | WSPI_INIT_CMD_IP | WSPI_INIT_CMD_CS + | WSPI_INIT_CMD_WSPI | WSPI_INIT_CMD_WS; + + crc[0] = cmd[1]; + crc[1] = cmd[0]; + crc[2] = cmd[7]; + crc[3] = cmd[6]; + crc[4] = cmd[5]; + + cmd[4] |= crc7(0, crc, WSPI_INIT_CMD_CRC_LEN) << 1; + cmd[4] |= WSPI_INIT_CMD_END; + + t.tx_buf = cmd; + t.len = WSPI_INIT_CMD_LEN; + spi_message_add_tail(&t, &m); + + spi_sync(to_spi_device(glue->dev), &m); + kfree(cmd); +} + +#define WL1271_BUSY_WORD_TIMEOUT 1000 + +static int wl12xx_spi_read_busy(struct device *child) +{ + struct wl12xx_spi_glue *glue = dev_get_drvdata(child->parent); + struct wl1271 *wl = dev_get_drvdata(child); + struct spi_transfer t[1]; + struct spi_message m; + u32 *busy_buf; + int num_busy_bytes = 0; + + /* + * Read further busy words from SPI until a non-busy word is + * encountered, then read the data itself into the buffer. + */ + + num_busy_bytes = WL1271_BUSY_WORD_TIMEOUT; + busy_buf = wl->buffer_busyword; + while (num_busy_bytes) { + num_busy_bytes--; + spi_message_init(&m); + memset(t, 0, sizeof(t)); + t[0].rx_buf = busy_buf; + t[0].len = sizeof(u32); + t[0].cs_change = true; + spi_message_add_tail(&t[0], &m); + spi_sync(to_spi_device(glue->dev), &m); + + if (*busy_buf & 0x1) + return 0; + } + + /* The SPI bus is unresponsive, the read failed. */ + dev_err(child->parent, "SPI read busy-word timeout!\n"); + return -ETIMEDOUT; +} + +static int __must_check wl12xx_spi_raw_read(struct device *child, int addr, + void *buf, size_t len, bool fixed) +{ + struct wl12xx_spi_glue *glue = dev_get_drvdata(child->parent); + struct wl1271 *wl = dev_get_drvdata(child); + struct spi_transfer t[2]; + struct spi_message m; + u32 *busy_buf; + u32 *cmd; + u32 chunk_len; + + while (len > 0) { + chunk_len = min((size_t)WSPI_MAX_CHUNK_SIZE, len); + + cmd = &wl->buffer_cmd; + busy_buf = wl->buffer_busyword; + + *cmd = 0; + *cmd |= WSPI_CMD_READ; + *cmd |= (chunk_len << WSPI_CMD_BYTE_LENGTH_OFFSET) & + WSPI_CMD_BYTE_LENGTH; + *cmd |= addr & WSPI_CMD_BYTE_ADDR; + + if (fixed) + *cmd |= WSPI_CMD_FIXED; + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + t[0].tx_buf = cmd; + t[0].len = 4; + t[0].cs_change = true; + spi_message_add_tail(&t[0], &m); + + /* Busy and non busy words read */ + t[1].rx_buf = busy_buf; + t[1].len = WL1271_BUSY_WORD_LEN; + t[1].cs_change = true; + spi_message_add_tail(&t[1], &m); + + spi_sync(to_spi_device(glue->dev), &m); + + if (!(busy_buf[WL1271_BUSY_WORD_CNT - 1] & 0x1) && + wl12xx_spi_read_busy(child)) { + memset(buf, 0, chunk_len); + return 0; + } + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + t[0].rx_buf = buf; + t[0].len = chunk_len; + t[0].cs_change = true; + spi_message_add_tail(&t[0], &m); + + spi_sync(to_spi_device(glue->dev), &m); + + if (!fixed) + addr += chunk_len; + buf += chunk_len; + len -= chunk_len; + } + + return 0; +} + +static int __must_check wl12xx_spi_raw_write(struct device *child, int addr, + void *buf, size_t len, bool fixed) +{ + struct wl12xx_spi_glue *glue = dev_get_drvdata(child->parent); + struct spi_transfer t[2 * (WSPI_MAX_NUM_OF_CHUNKS + 1)]; + struct spi_message m; + u32 commands[WSPI_MAX_NUM_OF_CHUNKS]; + u32 *cmd; + u32 chunk_len; + int i; + + WARN_ON(len > SPI_AGGR_BUFFER_SIZE); + + spi_message_init(&m); + memset(t, 0, sizeof(t)); + + cmd = &commands[0]; + i = 0; + while (len > 0) { + chunk_len = min((size_t)WSPI_MAX_CHUNK_SIZE, len); + + *cmd = 0; + *cmd |= WSPI_CMD_WRITE; + *cmd |= (chunk_len << WSPI_CMD_BYTE_LENGTH_OFFSET) & + WSPI_CMD_BYTE_LENGTH; + *cmd |= addr & WSPI_CMD_BYTE_ADDR; + + if (fixed) + *cmd |= WSPI_CMD_FIXED; + + t[i].tx_buf = cmd; + t[i].len = sizeof(*cmd); + spi_message_add_tail(&t[i++], &m); + + t[i].tx_buf = buf; + t[i].len = chunk_len; + spi_message_add_tail(&t[i++], &m); + + if (!fixed) + addr += chunk_len; + buf += chunk_len; + len -= chunk_len; + cmd++; + } + + spi_sync(to_spi_device(glue->dev), &m); + + return 0; +} + +static struct wl1271_if_operations spi_ops = { + .read = wl12xx_spi_raw_read, + .write = wl12xx_spi_raw_write, + .reset = wl12xx_spi_reset, + .init = wl12xx_spi_init, + .set_block_size = NULL, +}; + +static int wl1271_probe(struct spi_device *spi) +{ + struct wl12xx_spi_glue *glue; + struct wlcore_platdev_data *pdev_data; + struct resource res[1]; + int ret = -ENOMEM; + + pdev_data = kzalloc(sizeof(*pdev_data), GFP_KERNEL); + if (!pdev_data) + goto out; + + pdev_data->pdata = spi->dev.platform_data; + if (!pdev_data->pdata) { + dev_err(&spi->dev, "no platform data\n"); + ret = -ENODEV; + goto out_free_pdev_data; + } + + pdev_data->if_ops = &spi_ops; + + glue = kzalloc(sizeof(*glue), GFP_KERNEL); + if (!glue) { + dev_err(&spi->dev, "can't allocate glue\n"); + goto out_free_pdev_data; + } + + glue->dev = &spi->dev; + + spi_set_drvdata(spi, glue); + + /* This is the only SPI value that we need to set here, the rest + * comes from the board-peripherals file */ + spi->bits_per_word = 32; + + ret = spi_setup(spi); + if (ret < 0) { + dev_err(glue->dev, "spi_setup failed\n"); + goto out_free_glue; + } + + glue->core = platform_device_alloc("wl12xx", PLATFORM_DEVID_AUTO); + if (!glue->core) { + dev_err(glue->dev, "can't allocate platform_device\n"); + ret = -ENOMEM; + goto out_free_glue; + } + + glue->core->dev.parent = &spi->dev; + + memset(res, 0x00, sizeof(res)); + + res[0].start = spi->irq; + res[0].flags = IORESOURCE_IRQ; + res[0].name = "irq"; + + ret = platform_device_add_resources(glue->core, res, ARRAY_SIZE(res)); + if (ret) { + dev_err(glue->dev, "can't add resources\n"); + goto out_dev_put; + } + + ret = platform_device_add_data(glue->core, pdev_data, + sizeof(*pdev_data)); + if (ret) { + dev_err(glue->dev, "can't add platform data\n"); + goto out_dev_put; + } + + ret = platform_device_add(glue->core); + if (ret) { + dev_err(glue->dev, "can't register platform device\n"); + goto out_dev_put; + } + + return 0; + +out_dev_put: + platform_device_put(glue->core); + +out_free_glue: + kfree(glue); + +out_free_pdev_data: + kfree(pdev_data); + +out: + return ret; +} + +static int wl1271_remove(struct spi_device *spi) +{ + struct wl12xx_spi_glue *glue = spi_get_drvdata(spi); + + platform_device_unregister(glue->core); + kfree(glue); + + return 0; +} + + +static struct spi_driver wl1271_spi_driver = { + .driver = { + .name = "wl1271_spi", + .owner = THIS_MODULE, + }, + + .probe = wl1271_probe, + .remove = wl1271_remove, +}; + +static int __init wl1271_init(void) +{ + return spi_register_driver(&wl1271_spi_driver); +} + +static void __exit wl1271_exit(void) +{ + spi_unregister_driver(&wl1271_spi_driver); +} + +module_init(wl1271_init); +module_exit(wl1271_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Luciano Coelho "); +MODULE_AUTHOR("Juuso Oikarinen "); +MODULE_ALIAS("spi:wl1271"); diff --git a/drivers/net/wireless/ti/wlcore/testmode.c b/drivers/net/wireless/ti/wlcore/testmode.c new file mode 100644 index 0000000..f344276 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/testmode.c @@ -0,0 +1,386 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2010 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#include "testmode.h" + +#include +#include + +#include "wlcore.h" +#include "debug.h" +#include "acx.h" +#include "ps.h" +#include "io.h" + +#define WL1271_TM_MAX_DATA_LENGTH 1024 + +enum wl1271_tm_commands { + WL1271_TM_CMD_UNSPEC, + WL1271_TM_CMD_TEST, + WL1271_TM_CMD_INTERROGATE, + WL1271_TM_CMD_CONFIGURE, + WL1271_TM_CMD_NVS_PUSH, /* Not in use. Keep to not break ABI */ + WL1271_TM_CMD_SET_PLT_MODE, + WL1271_TM_CMD_RECOVER, /* Not in use. Keep to not break ABI */ + WL1271_TM_CMD_GET_MAC, + + __WL1271_TM_CMD_AFTER_LAST +}; +#define WL1271_TM_CMD_MAX (__WL1271_TM_CMD_AFTER_LAST - 1) + +enum wl1271_tm_attrs { + WL1271_TM_ATTR_UNSPEC, + WL1271_TM_ATTR_CMD_ID, + WL1271_TM_ATTR_ANSWER, + WL1271_TM_ATTR_DATA, + WL1271_TM_ATTR_IE_ID, + WL1271_TM_ATTR_PLT_MODE, + + __WL1271_TM_ATTR_AFTER_LAST +}; +#define WL1271_TM_ATTR_MAX (__WL1271_TM_ATTR_AFTER_LAST - 1) + +static struct nla_policy wl1271_tm_policy[WL1271_TM_ATTR_MAX + 1] = { + [WL1271_TM_ATTR_CMD_ID] = { .type = NLA_U32 }, + [WL1271_TM_ATTR_ANSWER] = { .type = NLA_U8 }, + [WL1271_TM_ATTR_DATA] = { .type = NLA_BINARY, + .len = WL1271_TM_MAX_DATA_LENGTH }, + [WL1271_TM_ATTR_IE_ID] = { .type = NLA_U32 }, + [WL1271_TM_ATTR_PLT_MODE] = { .type = NLA_U32 }, +}; + + +static int wl1271_tm_cmd_test(struct wl1271 *wl, struct nlattr *tb[]) +{ + int buf_len, ret, len; + struct sk_buff *skb; + void *buf; + u8 answer = 0; + + wl1271_debug(DEBUG_TESTMODE, "testmode cmd test"); + + if (!tb[WL1271_TM_ATTR_DATA]) + return -EINVAL; + + buf = nla_data(tb[WL1271_TM_ATTR_DATA]); + buf_len = nla_len(tb[WL1271_TM_ATTR_DATA]); + + if (tb[WL1271_TM_ATTR_ANSWER]) + answer = nla_get_u8(tb[WL1271_TM_ATTR_ANSWER]); + + if (buf_len > sizeof(struct wl1271_command)) + return -EMSGSIZE; + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) { + ret = -EINVAL; + goto out; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wl1271_cmd_test(wl, buf, buf_len, answer); + if (ret < 0) { + wl1271_warning("testmode cmd test failed: %d", ret); + goto out_sleep; + } + + if (answer) { + /* If we got bip calibration answer print radio status */ + struct wl1271_cmd_cal_p2g *params = + (struct wl1271_cmd_cal_p2g *) buf; + + s16 radio_status = (s16) le16_to_cpu(params->radio_status); + + if (params->test.id == TEST_CMD_P2G_CAL && + radio_status < 0) + wl1271_warning("testmode cmd: radio status=%d", + radio_status); + else + wl1271_info("testmode cmd: radio status=%d", + radio_status); + + len = nla_total_size(buf_len); + skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, len); + if (!skb) { + ret = -ENOMEM; + goto out_sleep; + } + + if (nla_put(skb, WL1271_TM_ATTR_DATA, buf_len, buf)) { + kfree_skb(skb); + ret = -EMSGSIZE; + goto out_sleep; + } + + ret = cfg80211_testmode_reply(skb); + if (ret < 0) + goto out_sleep; + } + +out_sleep: + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static int wl1271_tm_cmd_interrogate(struct wl1271 *wl, struct nlattr *tb[]) +{ + int ret; + struct wl1271_command *cmd; + struct sk_buff *skb; + u8 ie_id; + + wl1271_debug(DEBUG_TESTMODE, "testmode cmd interrogate"); + + if (!tb[WL1271_TM_ATTR_IE_ID]) + return -EINVAL; + + ie_id = nla_get_u8(tb[WL1271_TM_ATTR_IE_ID]); + + mutex_lock(&wl->mutex); + + if (unlikely(wl->state != WLCORE_STATE_ON)) { + ret = -EINVAL; + goto out; + } + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto out_sleep; + } + + ret = wl1271_cmd_interrogate(wl, ie_id, cmd, sizeof(*cmd)); + if (ret < 0) { + wl1271_warning("testmode cmd interrogate failed: %d", ret); + goto out_free; + } + + skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, sizeof(*cmd)); + if (!skb) { + ret = -ENOMEM; + goto out_free; + } + + if (nla_put(skb, WL1271_TM_ATTR_DATA, sizeof(*cmd), cmd)) { + kfree_skb(skb); + ret = -EMSGSIZE; + goto out_free; + } + + ret = cfg80211_testmode_reply(skb); + if (ret < 0) + goto out_free; + +out_free: + kfree(cmd); +out_sleep: + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); + + return ret; +} + +static int wl1271_tm_cmd_configure(struct wl1271 *wl, struct nlattr *tb[]) +{ + int buf_len, ret; + void *buf; + u8 ie_id; + + wl1271_debug(DEBUG_TESTMODE, "testmode cmd configure"); + + if (!tb[WL1271_TM_ATTR_DATA]) + return -EINVAL; + if (!tb[WL1271_TM_ATTR_IE_ID]) + return -EINVAL; + + ie_id = nla_get_u8(tb[WL1271_TM_ATTR_IE_ID]); + buf = nla_data(tb[WL1271_TM_ATTR_DATA]); + buf_len = nla_len(tb[WL1271_TM_ATTR_DATA]); + + if (buf_len > sizeof(struct wl1271_command)) + return -EMSGSIZE; + + mutex_lock(&wl->mutex); + ret = wl1271_cmd_configure(wl, ie_id, buf, buf_len); + mutex_unlock(&wl->mutex); + + if (ret < 0) { + wl1271_warning("testmode cmd configure failed: %d", ret); + return ret; + } + + return 0; +} + +static int wl1271_tm_detect_fem(struct wl1271 *wl, struct nlattr *tb[]) +{ + /* return FEM type */ + int ret, len; + struct sk_buff *skb; + + ret = wl1271_plt_start(wl, PLT_FEM_DETECT); + if (ret < 0) + goto out; + + mutex_lock(&wl->mutex); + + len = nla_total_size(sizeof(wl->fem_manuf)); + skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, len); + if (!skb) { + ret = -ENOMEM; + goto out_mutex; + } + + if (nla_put(skb, WL1271_TM_ATTR_DATA, sizeof(wl->fem_manuf), + &wl->fem_manuf)) { + kfree_skb(skb); + ret = -EMSGSIZE; + goto out_mutex; + } + + ret = cfg80211_testmode_reply(skb); + +out_mutex: + mutex_unlock(&wl->mutex); + + /* We always stop plt after DETECT mode */ + wl1271_plt_stop(wl); +out: + return ret; +} + +static int wl1271_tm_cmd_set_plt_mode(struct wl1271 *wl, struct nlattr *tb[]) +{ + u32 val; + int ret; + + wl1271_debug(DEBUG_TESTMODE, "testmode cmd set plt mode"); + + if (!tb[WL1271_TM_ATTR_PLT_MODE]) + return -EINVAL; + + val = nla_get_u32(tb[WL1271_TM_ATTR_PLT_MODE]); + + switch (val) { + case PLT_OFF: + ret = wl1271_plt_stop(wl); + break; + case PLT_ON: + ret = wl1271_plt_start(wl, PLT_ON); + break; + case PLT_FEM_DETECT: + ret = wl1271_tm_detect_fem(wl, tb); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int wl12xx_tm_cmd_get_mac(struct wl1271 *wl, struct nlattr *tb[]) +{ + struct sk_buff *skb; + u8 mac_addr[ETH_ALEN]; + int ret = 0; + + mutex_lock(&wl->mutex); + + if (!wl->plt) { + ret = -EINVAL; + goto out; + } + + if (wl->fuse_oui_addr == 0 && wl->fuse_nic_addr == 0) { + ret = -EOPNOTSUPP; + goto out; + } + + mac_addr[0] = (u8)(wl->fuse_oui_addr >> 16); + mac_addr[1] = (u8)(wl->fuse_oui_addr >> 8); + mac_addr[2] = (u8) wl->fuse_oui_addr; + mac_addr[3] = (u8)(wl->fuse_nic_addr >> 16); + mac_addr[4] = (u8)(wl->fuse_nic_addr >> 8); + mac_addr[5] = (u8) wl->fuse_nic_addr; + + skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, ETH_ALEN); + if (!skb) { + ret = -ENOMEM; + goto out; + } + + if (nla_put(skb, WL1271_TM_ATTR_DATA, ETH_ALEN, mac_addr)) { + kfree_skb(skb); + ret = -EMSGSIZE; + goto out; + } + + ret = cfg80211_testmode_reply(skb); + if (ret < 0) + goto out; + +out: + mutex_unlock(&wl->mutex); + return ret; +} + +int wl1271_tm_cmd(struct ieee80211_hw *hw, void *data, int len) +{ + struct wl1271 *wl = hw->priv; + struct nlattr *tb[WL1271_TM_ATTR_MAX + 1]; + int err; + + err = nla_parse(tb, WL1271_TM_ATTR_MAX, data, len, wl1271_tm_policy); + if (err) + return err; + + if (!tb[WL1271_TM_ATTR_CMD_ID]) + return -EINVAL; + + switch (nla_get_u32(tb[WL1271_TM_ATTR_CMD_ID])) { + case WL1271_TM_CMD_TEST: + return wl1271_tm_cmd_test(wl, tb); + case WL1271_TM_CMD_INTERROGATE: + return wl1271_tm_cmd_interrogate(wl, tb); + case WL1271_TM_CMD_CONFIGURE: + return wl1271_tm_cmd_configure(wl, tb); + case WL1271_TM_CMD_SET_PLT_MODE: + return wl1271_tm_cmd_set_plt_mode(wl, tb); + case WL1271_TM_CMD_GET_MAC: + return wl12xx_tm_cmd_get_mac(wl, tb); + default: + return -EOPNOTSUPP; + } +} diff --git a/drivers/net/wireless/ti/wlcore/testmode.h b/drivers/net/wireless/ti/wlcore/testmode.h new file mode 100644 index 0000000..8071654 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/testmode.h @@ -0,0 +1,31 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2010 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TESTMODE_H__ +#define __TESTMODE_H__ + +#include + +int wl1271_tm_cmd(struct ieee80211_hw *hw, void *data, int len); + +#endif /* __WL1271_TESTMODE_H__ */ diff --git a/drivers/net/wireless/ti/wlcore/tx.c b/drivers/net/wireless/ti/wlcore/tx.c new file mode 100644 index 0000000..004d02e --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/tx.c @@ -0,0 +1,1305 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include + +#include "wlcore.h" +#include "debug.h" +#include "io.h" +#include "ps.h" +#include "tx.h" +#include "event.h" +#include "hw_ops.h" + +/* + * TODO: this is here just for now, it must be removed when the data + * operations are in place. + */ +#include "../wl12xx/reg.h" + +static int wl1271_set_default_wep_key(struct wl1271 *wl, + struct wl12xx_vif *wlvif, u8 id) +{ + int ret; + bool is_ap = (wlvif->bss_type == BSS_TYPE_AP_BSS); + + if (is_ap) + ret = wl12xx_cmd_set_default_wep_key(wl, id, + wlvif->ap.bcast_hlid); + else + ret = wl12xx_cmd_set_default_wep_key(wl, id, wlvif->sta.hlid); + + if (ret < 0) + return ret; + + wl1271_debug(DEBUG_CRYPT, "default wep key idx: %d", (int)id); + return 0; +} + +static int wl1271_alloc_tx_id(struct wl1271 *wl, struct sk_buff *skb) +{ + int id; + + id = find_first_zero_bit(wl->tx_frames_map, wl->num_tx_desc); + if (id >= wl->num_tx_desc) + return -EBUSY; + + __set_bit(id, wl->tx_frames_map); + wl->tx_frames[id] = skb; + wl->tx_frames_cnt++; + return id; +} + +void wl1271_free_tx_id(struct wl1271 *wl, int id) +{ + if (__test_and_clear_bit(id, wl->tx_frames_map)) { + if (unlikely(wl->tx_frames_cnt == wl->num_tx_desc)) + clear_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags); + + wl->tx_frames[id] = NULL; + wl->tx_frames_cnt--; + } +} +EXPORT_SYMBOL(wl1271_free_tx_id); + +static void wl1271_tx_ap_update_inconnection_sta(struct wl1271 *wl, + struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr; + + /* + * add the station to the known list before transmitting the + * authentication response. this way it won't get de-authed by FW + * when transmitting too soon. + */ + hdr = (struct ieee80211_hdr *)(skb->data + + sizeof(struct wl1271_tx_hw_descr)); + if (ieee80211_is_auth(hdr->frame_control)) + wl1271_acx_set_inconnection_sta(wl, hdr->addr1); +} + +static void wl1271_tx_regulate_link(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + u8 hlid) +{ + bool fw_ps; + u8 tx_pkts; + + if (WARN_ON(!test_bit(hlid, wlvif->links_map))) + return; + + fw_ps = test_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map); + tx_pkts = wl->links[hlid].allocated_pkts; + + /* + * if in FW PS and there is enough data in FW we can put the link + * into high-level PS and clean out its TX queues. + * Make an exception if this is the only connected link. In this + * case FW-memory congestion is less of a problem. + * Note that a single connected STA means 3 active links, since we must + * account for the global and broadcast AP links. The "fw_ps" check + * assures us the third link is a STA connected to the AP. Otherwise + * the FW would not set the PSM bit. + */ + if (wl->active_link_count > 3 && fw_ps && + tx_pkts >= WL1271_PS_STA_MAX_PACKETS) + wl12xx_ps_link_start(wl, wlvif, hlid, true); +} + +bool wl12xx_is_dummy_packet(struct wl1271 *wl, struct sk_buff *skb) +{ + return wl->dummy_packet == skb; +} +EXPORT_SYMBOL(wl12xx_is_dummy_packet); + +static u8 wl12xx_tx_get_hlid_ap(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct sk_buff *skb, struct ieee80211_sta *sta) +{ + if (sta) { + struct wl1271_station *wl_sta; + + wl_sta = (struct wl1271_station *)sta->drv_priv; + return wl_sta->hlid; + } else { + struct ieee80211_hdr *hdr; + + if (!test_bit(WLVIF_FLAG_AP_STARTED, &wlvif->flags)) + return wl->system_hlid; + + hdr = (struct ieee80211_hdr *)skb->data; + if (is_multicast_ether_addr(ieee80211_get_DA(hdr))) + return wlvif->ap.bcast_hlid; + else + return wlvif->ap.global_hlid; + } +} + +u8 wl12xx_tx_get_hlid(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct sk_buff *skb, struct ieee80211_sta *sta) +{ + struct ieee80211_tx_info *control; + + if (wlvif->bss_type == BSS_TYPE_AP_BSS) + return wl12xx_tx_get_hlid_ap(wl, wlvif, skb, sta); + + control = IEEE80211_SKB_CB(skb); + if (control->flags & IEEE80211_TX_CTL_TX_OFFCHAN) { + wl1271_debug(DEBUG_TX, "tx offchannel"); + return wlvif->dev_hlid; + } + + return wlvif->sta.hlid; +} + +unsigned int wlcore_calc_packet_alignment(struct wl1271 *wl, + unsigned int packet_length) +{ + if ((wl->quirks & WLCORE_QUIRK_TX_PAD_LAST_FRAME) || + !(wl->quirks & WLCORE_QUIRK_TX_BLOCKSIZE_ALIGN)) + return ALIGN(packet_length, WL1271_TX_ALIGN_TO); + else + return ALIGN(packet_length, WL12XX_BUS_BLOCK_SIZE); +} +EXPORT_SYMBOL(wlcore_calc_packet_alignment); + +static int wl1271_tx_allocate(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct sk_buff *skb, u32 extra, u32 buf_offset, + u8 hlid, bool is_gem) +{ + struct wl1271_tx_hw_descr *desc; + u32 total_len = skb->len + sizeof(struct wl1271_tx_hw_descr) + extra; + u32 total_blocks; + int id, ret = -EBUSY, ac; + u32 spare_blocks; + + if (buf_offset + total_len > wl->aggr_buf_size) + return -EAGAIN; + + spare_blocks = wlcore_hw_get_spare_blocks(wl, is_gem); + + /* allocate free identifier for the packet */ + id = wl1271_alloc_tx_id(wl, skb); + if (id < 0) + return id; + + total_blocks = wlcore_hw_calc_tx_blocks(wl, total_len, spare_blocks); + + if (total_blocks <= wl->tx_blocks_available) { + desc = (struct wl1271_tx_hw_descr *)skb_push( + skb, total_len - skb->len); + + wlcore_hw_set_tx_desc_blocks(wl, desc, total_blocks, + spare_blocks); + + desc->id = id; + + wl->tx_blocks_available -= total_blocks; + wl->tx_allocated_blocks += total_blocks; + + /* If the FW was empty before, arm the Tx watchdog */ + if (wl->tx_allocated_blocks == total_blocks) + wl12xx_rearm_tx_watchdog_locked(wl); + + ac = wl1271_tx_get_queue(skb_get_queue_mapping(skb)); + wl->tx_allocated_pkts[ac]++; + + if (test_bit(hlid, wl->links_map)) + wl->links[hlid].allocated_pkts++; + + ret = 0; + + wl1271_debug(DEBUG_TX, + "tx_allocate: size: %d, blocks: %d, id: %d", + total_len, total_blocks, id); + } else { + wl1271_free_tx_id(wl, id); + } + + return ret; +} + +static void wl1271_tx_fill_hdr(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct sk_buff *skb, u32 extra, + struct ieee80211_tx_info *control, u8 hlid) +{ + struct timespec ts; + struct wl1271_tx_hw_descr *desc; + int ac, rate_idx; + s64 hosttime; + u16 tx_attr = 0; + __le16 frame_control; + struct ieee80211_hdr *hdr; + u8 *frame_start; + bool is_dummy; + + desc = (struct wl1271_tx_hw_descr *) skb->data; + frame_start = (u8 *)(desc + 1); + hdr = (struct ieee80211_hdr *)(frame_start + extra); + frame_control = hdr->frame_control; + + /* relocate space for security header */ + if (extra) { + int hdrlen = ieee80211_hdrlen(frame_control); + memmove(frame_start, hdr, hdrlen); + skb_set_network_header(skb, skb_network_offset(skb) + extra); + } + + /* configure packet life time */ + getnstimeofday(&ts); + hosttime = (timespec_to_ns(&ts) >> 10); + desc->start_time = cpu_to_le32(hosttime - wl->time_offset); + + is_dummy = wl12xx_is_dummy_packet(wl, skb); + if (is_dummy || !wlvif || wlvif->bss_type != BSS_TYPE_AP_BSS) + desc->life_time = cpu_to_le16(TX_HW_MGMT_PKT_LIFETIME_TU); + else + desc->life_time = cpu_to_le16(TX_HW_AP_MODE_PKT_LIFETIME_TU); + + /* queue */ + ac = wl1271_tx_get_queue(skb_get_queue_mapping(skb)); + desc->tid = skb->priority; + + if (is_dummy) { + /* + * FW expects the dummy packet to have an invalid session id - + * any session id that is different than the one set in the join + */ + tx_attr = (SESSION_COUNTER_INVALID << + TX_HW_ATTR_OFST_SESSION_COUNTER) & + TX_HW_ATTR_SESSION_COUNTER; + + tx_attr |= TX_HW_ATTR_TX_DUMMY_REQ; + } else if (wlvif) { + u8 session_id = wl->session_ids[hlid]; + + if ((wl->quirks & WLCORE_QUIRK_AP_ZERO_SESSION_ID) && + (wlvif->bss_type == BSS_TYPE_AP_BSS)) + session_id = 0; + + /* configure the tx attributes */ + tx_attr = session_id << TX_HW_ATTR_OFST_SESSION_COUNTER; + } + + desc->hlid = hlid; + if (is_dummy || !wlvif) + rate_idx = 0; + else if (wlvif->bss_type != BSS_TYPE_AP_BSS) { + /* + * if the packets are data packets + * send them with AP rate policies (EAPOLs are an exception), + * otherwise use default basic rates + */ + if (skb->protocol == cpu_to_be16(ETH_P_PAE)) + rate_idx = wlvif->sta.basic_rate_idx; + else if (control->flags & IEEE80211_TX_CTL_NO_CCK_RATE) + rate_idx = wlvif->sta.p2p_rate_idx; + else if (ieee80211_is_data(frame_control)) + rate_idx = wlvif->sta.ap_rate_idx; + else + rate_idx = wlvif->sta.basic_rate_idx; + } else { + if (hlid == wlvif->ap.global_hlid) + rate_idx = wlvif->ap.mgmt_rate_idx; + else if (hlid == wlvif->ap.bcast_hlid || + skb->protocol == cpu_to_be16(ETH_P_PAE) || + !ieee80211_is_data(frame_control)) + /* + * send non-data, bcast and EAPOLs using the + * min basic rate + */ + rate_idx = wlvif->ap.bcast_rate_idx; + else + rate_idx = wlvif->ap.ucast_rate_idx[ac]; + } + + tx_attr |= rate_idx << TX_HW_ATTR_OFST_RATE_POLICY; + + /* for WEP shared auth - no fw encryption is needed */ + if (ieee80211_is_auth(frame_control) && + ieee80211_has_protected(frame_control)) + tx_attr |= TX_HW_ATTR_HOST_ENCRYPT; + + desc->tx_attr = cpu_to_le16(tx_attr); + + wlcore_hw_set_tx_desc_csum(wl, desc, skb); + wlcore_hw_set_tx_desc_data_len(wl, desc, skb); +} + +/* caller must hold wl->mutex */ +static int wl1271_prepare_tx_frame(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct sk_buff *skb, u32 buf_offset, u8 hlid) +{ + struct ieee80211_tx_info *info; + u32 extra = 0; + int ret = 0; + u32 total_len; + bool is_dummy; + bool is_gem = false; + + if (!skb) { + wl1271_error("discarding null skb"); + return -EINVAL; + } + + if (hlid == WL12XX_INVALID_LINK_ID) { + wl1271_error("invalid hlid. dropping skb 0x%p", skb); + return -EINVAL; + } + + info = IEEE80211_SKB_CB(skb); + + is_dummy = wl12xx_is_dummy_packet(wl, skb); + + if ((wl->quirks & WLCORE_QUIRK_TKIP_HEADER_SPACE) && + info->control.hw_key && + info->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) + extra = WL1271_EXTRA_SPACE_TKIP; + + if (info->control.hw_key) { + bool is_wep; + u8 idx = info->control.hw_key->hw_key_idx; + u32 cipher = info->control.hw_key->cipher; + + is_wep = (cipher == WLAN_CIPHER_SUITE_WEP40) || + (cipher == WLAN_CIPHER_SUITE_WEP104); + + if (unlikely(is_wep && wlvif->default_key != idx)) { + ret = wl1271_set_default_wep_key(wl, wlvif, idx); + if (ret < 0) + return ret; + wlvif->default_key = idx; + } + + is_gem = (cipher == WL1271_CIPHER_SUITE_GEM); + } + + ret = wl1271_tx_allocate(wl, wlvif, skb, extra, buf_offset, hlid, + is_gem); + if (ret < 0) + return ret; + + wl1271_tx_fill_hdr(wl, wlvif, skb, extra, info, hlid); + + if (!is_dummy && wlvif && wlvif->bss_type == BSS_TYPE_AP_BSS) { + wl1271_tx_ap_update_inconnection_sta(wl, skb); + wl1271_tx_regulate_link(wl, wlvif, hlid); + } + + /* + * The length of each packet is stored in terms of + * words. Thus, we must pad the skb data to make sure its + * length is aligned. The number of padding bytes is computed + * and set in wl1271_tx_fill_hdr. + * In special cases, we want to align to a specific block size + * (eg. for wl128x with SDIO we align to 256). + */ + total_len = wlcore_calc_packet_alignment(wl, skb->len); + + memcpy(wl->aggr_buf + buf_offset, skb->data, skb->len); + memset(wl->aggr_buf + buf_offset + skb->len, 0, total_len - skb->len); + + /* Revert side effects in the dummy packet skb, so it can be reused */ + if (is_dummy) + skb_pull(skb, sizeof(struct wl1271_tx_hw_descr)); + + return total_len; +} + +u32 wl1271_tx_enabled_rates_get(struct wl1271 *wl, u32 rate_set, + enum ieee80211_band rate_band) +{ + struct ieee80211_supported_band *band; + u32 enabled_rates = 0; + int bit; + + band = wl->hw->wiphy->bands[rate_band]; + for (bit = 0; bit < band->n_bitrates; bit++) { + if (rate_set & 0x1) + enabled_rates |= band->bitrates[bit].hw_value; + rate_set >>= 1; + } + + /* MCS rates indication are on bits 16 - 31 */ + rate_set >>= HW_HT_RATES_OFFSET - band->n_bitrates; + + for (bit = 0; bit < 16; bit++) { + if (rate_set & 0x1) + enabled_rates |= (CONF_HW_BIT_RATE_MCS_0 << bit); + rate_set >>= 1; + } + + return enabled_rates; +} + +void wl1271_handle_tx_low_watermark(struct wl1271 *wl) +{ + int i; + struct wl12xx_vif *wlvif; + + wl12xx_for_each_wlvif(wl, wlvif) { + for (i = 0; i < NUM_TX_QUEUES; i++) { + if (wlcore_is_queue_stopped_by_reason(wl, wlvif, i, + WLCORE_QUEUE_STOP_REASON_WATERMARK) && + wlvif->tx_queue_count[i] <= + WL1271_TX_QUEUE_LOW_WATERMARK) + /* firmware buffer has space, restart queues */ + wlcore_wake_queue(wl, wlvif, i, + WLCORE_QUEUE_STOP_REASON_WATERMARK); + } + } +} + +static int wlcore_select_ac(struct wl1271 *wl) +{ + int i, q = -1, ac; + u32 min_pkts = 0xffffffff; + + /* + * Find a non-empty ac where: + * 1. There are packets to transmit + * 2. The FW has the least allocated blocks + * + * We prioritize the ACs according to VO>VI>BE>BK + */ + for (i = 0; i < NUM_TX_QUEUES; i++) { + ac = wl1271_tx_get_queue(i); + if (wl->tx_queue_count[ac] && + wl->tx_allocated_pkts[ac] < min_pkts) { + q = ac; + min_pkts = wl->tx_allocated_pkts[q]; + } + } + + return q; +} + +static struct sk_buff *wlcore_lnk_dequeue(struct wl1271 *wl, + struct wl1271_link *lnk, u8 q) +{ + struct sk_buff *skb; + unsigned long flags; + + skb = skb_dequeue(&lnk->tx_queue[q]); + if (skb) { + spin_lock_irqsave(&wl->wl_lock, flags); + WARN_ON_ONCE(wl->tx_queue_count[q] <= 0); + wl->tx_queue_count[q]--; + if (lnk->wlvif) { + WARN_ON_ONCE(lnk->wlvif->tx_queue_count[q] <= 0); + lnk->wlvif->tx_queue_count[q]--; + } + spin_unlock_irqrestore(&wl->wl_lock, flags); + } + + return skb; +} + +static struct sk_buff *wlcore_lnk_dequeue_high_prio(struct wl1271 *wl, + u8 hlid, u8 ac, + u8 *low_prio_hlid) +{ + struct wl1271_link *lnk = &wl->links[hlid]; + + if (!wlcore_hw_lnk_high_prio(wl, hlid, lnk)) { + if (*low_prio_hlid == WL12XX_INVALID_LINK_ID && + !skb_queue_empty(&lnk->tx_queue[ac]) && + wlcore_hw_lnk_low_prio(wl, hlid, lnk)) + /* we found the first non-empty low priority queue */ + *low_prio_hlid = hlid; + + return NULL; + } + + return wlcore_lnk_dequeue(wl, lnk, ac); +} + +static struct sk_buff *wlcore_vif_dequeue_high_prio(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + u8 ac, u8 *hlid, + u8 *low_prio_hlid) +{ + struct sk_buff *skb = NULL; + int i, h, start_hlid; + + /* start from the link after the last one */ + start_hlid = (wlvif->last_tx_hlid + 1) % WL12XX_MAX_LINKS; + + /* dequeue according to AC, round robin on each link */ + for (i = 0; i < WL12XX_MAX_LINKS; i++) { + h = (start_hlid + i) % WL12XX_MAX_LINKS; + + /* only consider connected stations */ + if (!test_bit(h, wlvif->links_map)) + continue; + + skb = wlcore_lnk_dequeue_high_prio(wl, h, ac, + low_prio_hlid); + if (!skb) + continue; + + wlvif->last_tx_hlid = h; + break; + } + + if (!skb) + wlvif->last_tx_hlid = 0; + + *hlid = wlvif->last_tx_hlid; + return skb; +} + +static struct sk_buff *wl1271_skb_dequeue(struct wl1271 *wl, u8 *hlid) +{ + unsigned long flags; + struct wl12xx_vif *wlvif = wl->last_wlvif; + struct sk_buff *skb = NULL; + int ac; + u8 low_prio_hlid = WL12XX_INVALID_LINK_ID; + + ac = wlcore_select_ac(wl); + if (ac < 0) + goto out; + + /* continue from last wlvif (round robin) */ + if (wlvif) { + wl12xx_for_each_wlvif_continue(wl, wlvif) { + if (!wlvif->tx_queue_count[ac]) + continue; + + skb = wlcore_vif_dequeue_high_prio(wl, wlvif, ac, hlid, + &low_prio_hlid); + if (!skb) + continue; + + wl->last_wlvif = wlvif; + break; + } + } + + /* dequeue from the system HLID before the restarting wlvif list */ + if (!skb) { + skb = wlcore_lnk_dequeue_high_prio(wl, wl->system_hlid, + ac, &low_prio_hlid); + if (skb) { + *hlid = wl->system_hlid; + wl->last_wlvif = NULL; + } + } + + /* Do a new pass over the wlvif list. But no need to continue + * after last_wlvif. The previous pass should have found it. */ + if (!skb) { + wl12xx_for_each_wlvif(wl, wlvif) { + if (!wlvif->tx_queue_count[ac]) + goto next; + + skb = wlcore_vif_dequeue_high_prio(wl, wlvif, ac, hlid, + &low_prio_hlid); + if (skb) { + wl->last_wlvif = wlvif; + break; + } + +next: + if (wlvif == wl->last_wlvif) + break; + } + } + + /* no high priority skbs found - but maybe a low priority one? */ + if (!skb && low_prio_hlid != WL12XX_INVALID_LINK_ID) { + struct wl1271_link *lnk = &wl->links[low_prio_hlid]; + skb = wlcore_lnk_dequeue(wl, lnk, ac); + + WARN_ON(!skb); /* we checked this before */ + *hlid = low_prio_hlid; + + /* ensure proper round robin in the vif/link levels */ + wl->last_wlvif = lnk->wlvif; + if (lnk->wlvif) + lnk->wlvif->last_tx_hlid = low_prio_hlid; + + } + +out: + if (!skb && + test_and_clear_bit(WL1271_FLAG_DUMMY_PACKET_PENDING, &wl->flags)) { + int q; + + skb = wl->dummy_packet; + *hlid = wl->system_hlid; + q = wl1271_tx_get_queue(skb_get_queue_mapping(skb)); + spin_lock_irqsave(&wl->wl_lock, flags); + WARN_ON_ONCE(wl->tx_queue_count[q] <= 0); + wl->tx_queue_count[q]--; + spin_unlock_irqrestore(&wl->wl_lock, flags); + } + + return skb; +} + +static void wl1271_skb_queue_head(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct sk_buff *skb, u8 hlid) +{ + unsigned long flags; + int q = wl1271_tx_get_queue(skb_get_queue_mapping(skb)); + + if (wl12xx_is_dummy_packet(wl, skb)) { + set_bit(WL1271_FLAG_DUMMY_PACKET_PENDING, &wl->flags); + } else { + skb_queue_head(&wl->links[hlid].tx_queue[q], skb); + + /* make sure we dequeue the same packet next time */ + wlvif->last_tx_hlid = (hlid + WL12XX_MAX_LINKS - 1) % + WL12XX_MAX_LINKS; + } + + spin_lock_irqsave(&wl->wl_lock, flags); + wl->tx_queue_count[q]++; + if (wlvif) + wlvif->tx_queue_count[q]++; + spin_unlock_irqrestore(&wl->wl_lock, flags); +} + +static bool wl1271_tx_is_data_present(struct sk_buff *skb) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(skb->data); + + return ieee80211_is_data_present(hdr->frame_control); +} + +void wl12xx_rearm_rx_streaming(struct wl1271 *wl, unsigned long *active_hlids) +{ + struct wl12xx_vif *wlvif; + u32 timeout; + u8 hlid; + + if (!wl->conf.rx_streaming.interval) + return; + + if (!wl->conf.rx_streaming.always && + !test_bit(WL1271_FLAG_SOFT_GEMINI, &wl->flags)) + return; + + timeout = wl->conf.rx_streaming.duration; + wl12xx_for_each_wlvif_sta(wl, wlvif) { + bool found = false; + for_each_set_bit(hlid, active_hlids, WL12XX_MAX_LINKS) { + if (test_bit(hlid, wlvif->links_map)) { + found = true; + break; + } + } + + if (!found) + continue; + + /* enable rx streaming */ + if (!test_bit(WLVIF_FLAG_RX_STREAMING_STARTED, &wlvif->flags)) + ieee80211_queue_work(wl->hw, + &wlvif->rx_streaming_enable_work); + + mod_timer(&wlvif->rx_streaming_timer, + jiffies + msecs_to_jiffies(timeout)); + } +} + +/* + * Returns failure values only in case of failed bus ops within this function. + * wl1271_prepare_tx_frame retvals won't be returned in order to avoid + * triggering recovery by higher layers when not necessary. + * In case a FW command fails within wl1271_prepare_tx_frame fails a recovery + * will be queued in wl1271_cmd_send. -EAGAIN/-EBUSY from prepare_tx_frame + * can occur and are legitimate so don't propagate. -EINVAL will emit a WARNING + * within prepare_tx_frame code but there's nothing we should do about those + * as well. + */ +int wlcore_tx_work_locked(struct wl1271 *wl) +{ + struct wl12xx_vif *wlvif; + struct sk_buff *skb; + struct wl1271_tx_hw_descr *desc; + u32 buf_offset = 0, last_len = 0; + bool sent_packets = false; + unsigned long active_hlids[BITS_TO_LONGS(WL12XX_MAX_LINKS)] = {0}; + int ret = 0; + int bus_ret = 0; + u8 hlid; + + if (unlikely(wl->state != WLCORE_STATE_ON)) + return 0; + + while ((skb = wl1271_skb_dequeue(wl, &hlid))) { + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + bool has_data = false; + + wlvif = NULL; + if (!wl12xx_is_dummy_packet(wl, skb)) + wlvif = wl12xx_vif_to_data(info->control.vif); + else + hlid = wl->system_hlid; + + has_data = wlvif && wl1271_tx_is_data_present(skb); + ret = wl1271_prepare_tx_frame(wl, wlvif, skb, buf_offset, + hlid); + if (ret == -EAGAIN) { + /* + * Aggregation buffer is full. + * Flush buffer and try again. + */ + wl1271_skb_queue_head(wl, wlvif, skb, hlid); + + buf_offset = wlcore_hw_pre_pkt_send(wl, buf_offset, + last_len); + bus_ret = wlcore_write_data(wl, REG_SLV_MEM_DATA, + wl->aggr_buf, buf_offset, true); + if (bus_ret < 0) + goto out; + + sent_packets = true; + buf_offset = 0; + continue; + } else if (ret == -EBUSY) { + /* + * Firmware buffer is full. + * Queue back last skb, and stop aggregating. + */ + wl1271_skb_queue_head(wl, wlvif, skb, hlid); + /* No work left, avoid scheduling redundant tx work */ + set_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags); + goto out_ack; + } else if (ret < 0) { + if (wl12xx_is_dummy_packet(wl, skb)) + /* + * fw still expects dummy packet, + * so re-enqueue it + */ + wl1271_skb_queue_head(wl, wlvif, skb, hlid); + else + ieee80211_free_txskb(wl->hw, skb); + goto out_ack; + } + last_len = ret; + buf_offset += last_len; + wl->tx_packets_count++; + if (has_data) { + desc = (struct wl1271_tx_hw_descr *) skb->data; + __set_bit(desc->hlid, active_hlids); + } + } + +out_ack: + if (buf_offset) { + buf_offset = wlcore_hw_pre_pkt_send(wl, buf_offset, last_len); + bus_ret = wlcore_write_data(wl, REG_SLV_MEM_DATA, wl->aggr_buf, + buf_offset, true); + if (bus_ret < 0) + goto out; + + sent_packets = true; + } + if (sent_packets) { + /* + * Interrupt the firmware with the new packets. This is only + * required for older hardware revisions + */ + if (wl->quirks & WLCORE_QUIRK_END_OF_TRANSACTION) { + bus_ret = wlcore_write32(wl, WL12XX_HOST_WR_ACCESS, + wl->tx_packets_count); + if (bus_ret < 0) + goto out; + } + + wl1271_handle_tx_low_watermark(wl); + } + wl12xx_rearm_rx_streaming(wl, active_hlids); + +out: + return bus_ret; +} + +void wl1271_tx_work(struct work_struct *work) +{ + struct wl1271 *wl = container_of(work, struct wl1271, tx_work); + int ret; + + mutex_lock(&wl->mutex); + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + ret = wlcore_tx_work_locked(wl); + if (ret < 0) { + wl12xx_queue_recovery_work(wl); + goto out; + } + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); +} + +static u8 wl1271_tx_get_rate_flags(u8 rate_class_index) +{ + u8 flags = 0; + + /* + * TODO: use wl12xx constants when this code is moved to wl12xx, as + * only it uses Tx-completion. + */ + if (rate_class_index <= 8) + flags |= IEEE80211_TX_RC_MCS; + + /* + * TODO: use wl12xx constants when this code is moved to wl12xx, as + * only it uses Tx-completion. + */ + if (rate_class_index == 0) + flags |= IEEE80211_TX_RC_SHORT_GI; + + return flags; +} + +static void wl1271_tx_complete_packet(struct wl1271 *wl, + struct wl1271_tx_hw_res_descr *result) +{ + struct ieee80211_tx_info *info; + struct ieee80211_vif *vif; + struct wl12xx_vif *wlvif; + struct sk_buff *skb; + int id = result->id; + int rate = -1; + u8 rate_flags = 0; + u8 retries = 0; + + /* check for id legality */ + if (unlikely(id >= wl->num_tx_desc || wl->tx_frames[id] == NULL)) { + wl1271_warning("TX result illegal id: %d", id); + return; + } + + skb = wl->tx_frames[id]; + info = IEEE80211_SKB_CB(skb); + + if (wl12xx_is_dummy_packet(wl, skb)) { + wl1271_free_tx_id(wl, id); + return; + } + + /* info->control is valid as long as we don't update info->status */ + vif = info->control.vif; + wlvif = wl12xx_vif_to_data(vif); + + /* update the TX status info */ + if (result->status == TX_SUCCESS) { + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) + info->flags |= IEEE80211_TX_STAT_ACK; + rate = wlcore_rate_to_idx(wl, result->rate_class_index, + wlvif->band); + rate_flags = wl1271_tx_get_rate_flags(result->rate_class_index); + retries = result->ack_failures; + } else if (result->status == TX_RETRY_EXCEEDED) { + wl->stats.excessive_retries++; + retries = result->ack_failures; + } + + info->status.rates[0].idx = rate; + info->status.rates[0].count = retries; + info->status.rates[0].flags = rate_flags; + info->status.ack_signal = -1; + + wl->stats.retry_count += result->ack_failures; + + /* remove private header from packet */ + skb_pull(skb, sizeof(struct wl1271_tx_hw_descr)); + + /* remove TKIP header space if present */ + if ((wl->quirks & WLCORE_QUIRK_TKIP_HEADER_SPACE) && + info->control.hw_key && + info->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) { + int hdrlen = ieee80211_get_hdrlen_from_skb(skb); + memmove(skb->data + WL1271_EXTRA_SPACE_TKIP, skb->data, + hdrlen); + skb_pull(skb, WL1271_EXTRA_SPACE_TKIP); + } + + wl1271_debug(DEBUG_TX, "tx status id %u skb 0x%p failures %u rate 0x%x" + " status 0x%x", + result->id, skb, result->ack_failures, + result->rate_class_index, result->status); + + /* return the packet to the stack */ + skb_queue_tail(&wl->deferred_tx_queue, skb); + queue_work(wl->freezable_wq, &wl->netstack_work); + wl1271_free_tx_id(wl, result->id); +} + +/* Called upon reception of a TX complete interrupt */ +int wlcore_tx_complete(struct wl1271 *wl) +{ + struct wl1271_acx_mem_map *memmap = wl->target_mem_map; + u32 count, fw_counter; + u32 i; + int ret; + + /* read the tx results from the chipset */ + ret = wlcore_read(wl, le32_to_cpu(memmap->tx_result), + wl->tx_res_if, sizeof(*wl->tx_res_if), false); + if (ret < 0) + goto out; + + fw_counter = le32_to_cpu(wl->tx_res_if->tx_result_fw_counter); + + /* write host counter to chipset (to ack) */ + ret = wlcore_write32(wl, le32_to_cpu(memmap->tx_result) + + offsetof(struct wl1271_tx_hw_res_if, + tx_result_host_counter), fw_counter); + if (ret < 0) + goto out; + + count = fw_counter - wl->tx_results_count; + wl1271_debug(DEBUG_TX, "tx_complete received, packets: %d", count); + + /* verify that the result buffer is not getting overrun */ + if (unlikely(count > TX_HW_RESULT_QUEUE_LEN)) + wl1271_warning("TX result overflow from chipset: %d", count); + + /* process the results */ + for (i = 0; i < count; i++) { + struct wl1271_tx_hw_res_descr *result; + u8 offset = wl->tx_results_count & TX_HW_RESULT_QUEUE_LEN_MASK; + + /* process the packet */ + result = &(wl->tx_res_if->tx_results_queue[offset]); + wl1271_tx_complete_packet(wl, result); + + wl->tx_results_count++; + } + +out: + return ret; +} +EXPORT_SYMBOL(wlcore_tx_complete); + +void wl1271_tx_reset_link_queues(struct wl1271 *wl, u8 hlid) +{ + struct sk_buff *skb; + int i; + unsigned long flags; + struct ieee80211_tx_info *info; + int total[NUM_TX_QUEUES]; + struct wl1271_link *lnk = &wl->links[hlid]; + + for (i = 0; i < NUM_TX_QUEUES; i++) { + total[i] = 0; + while ((skb = skb_dequeue(&lnk->tx_queue[i]))) { + wl1271_debug(DEBUG_TX, "link freeing skb 0x%p", skb); + + if (!wl12xx_is_dummy_packet(wl, skb)) { + info = IEEE80211_SKB_CB(skb); + info->status.rates[0].idx = -1; + info->status.rates[0].count = 0; + ieee80211_tx_status_ni(wl->hw, skb); + } + + total[i]++; + } + } + + spin_lock_irqsave(&wl->wl_lock, flags); + for (i = 0; i < NUM_TX_QUEUES; i++) { + wl->tx_queue_count[i] -= total[i]; + if (lnk->wlvif) + lnk->wlvif->tx_queue_count[i] -= total[i]; + } + spin_unlock_irqrestore(&wl->wl_lock, flags); + + wl1271_handle_tx_low_watermark(wl); +} + +/* caller must hold wl->mutex and TX must be stopped */ +void wl12xx_tx_reset_wlvif(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + int i; + + /* TX failure */ + for_each_set_bit(i, wlvif->links_map, WL12XX_MAX_LINKS) { + if (wlvif->bss_type == BSS_TYPE_AP_BSS && + i != wlvif->ap.bcast_hlid && i != wlvif->ap.global_hlid) { + /* this calls wl12xx_free_link */ + wl1271_free_sta(wl, wlvif, i); + } else { + u8 hlid = i; + wl12xx_free_link(wl, wlvif, &hlid); + } + } + wlvif->last_tx_hlid = 0; + + for (i = 0; i < NUM_TX_QUEUES; i++) + wlvif->tx_queue_count[i] = 0; +} +/* caller must hold wl->mutex and TX must be stopped */ +void wl12xx_tx_reset(struct wl1271 *wl) +{ + int i; + struct sk_buff *skb; + struct ieee80211_tx_info *info; + + /* only reset the queues if something bad happened */ + if (wl1271_tx_total_queue_count(wl) != 0) { + for (i = 0; i < WL12XX_MAX_LINKS; i++) + wl1271_tx_reset_link_queues(wl, i); + + for (i = 0; i < NUM_TX_QUEUES; i++) + wl->tx_queue_count[i] = 0; + } + + /* + * Make sure the driver is at a consistent state, in case this + * function is called from a context other than interface removal. + * This call will always wake the TX queues. + */ + wl1271_handle_tx_low_watermark(wl); + + for (i = 0; i < wl->num_tx_desc; i++) { + if (wl->tx_frames[i] == NULL) + continue; + + skb = wl->tx_frames[i]; + wl1271_free_tx_id(wl, i); + wl1271_debug(DEBUG_TX, "freeing skb 0x%p", skb); + + if (!wl12xx_is_dummy_packet(wl, skb)) { + /* + * Remove private headers before passing the skb to + * mac80211 + */ + info = IEEE80211_SKB_CB(skb); + skb_pull(skb, sizeof(struct wl1271_tx_hw_descr)); + if ((wl->quirks & WLCORE_QUIRK_TKIP_HEADER_SPACE) && + info->control.hw_key && + info->control.hw_key->cipher == + WLAN_CIPHER_SUITE_TKIP) { + int hdrlen = ieee80211_get_hdrlen_from_skb(skb); + memmove(skb->data + WL1271_EXTRA_SPACE_TKIP, + skb->data, hdrlen); + skb_pull(skb, WL1271_EXTRA_SPACE_TKIP); + } + + info->status.rates[0].idx = -1; + info->status.rates[0].count = 0; + + ieee80211_tx_status_ni(wl->hw, skb); + } + } +} + +#define WL1271_TX_FLUSH_TIMEOUT 500000 + +/* caller must *NOT* hold wl->mutex */ +void wl1271_tx_flush(struct wl1271 *wl) +{ + unsigned long timeout, start_time; + int i; + start_time = jiffies; + timeout = start_time + usecs_to_jiffies(WL1271_TX_FLUSH_TIMEOUT); + + /* only one flush should be in progress, for consistent queue state */ + mutex_lock(&wl->flush_mutex); + + mutex_lock(&wl->mutex); + if (wl->tx_frames_cnt == 0 && wl1271_tx_total_queue_count(wl) == 0) { + mutex_unlock(&wl->mutex); + goto out; + } + + wlcore_stop_queues(wl, WLCORE_QUEUE_STOP_REASON_FLUSH); + + while (!time_after(jiffies, timeout)) { + wl1271_debug(DEBUG_MAC80211, "flushing tx buffer: %d %d", + wl->tx_frames_cnt, + wl1271_tx_total_queue_count(wl)); + + /* force Tx and give the driver some time to flush data */ + mutex_unlock(&wl->mutex); + if (wl1271_tx_total_queue_count(wl)) + wl1271_tx_work(&wl->tx_work); + msleep(20); + mutex_lock(&wl->mutex); + + if ((wl->tx_frames_cnt == 0) && + (wl1271_tx_total_queue_count(wl) == 0)) { + wl1271_debug(DEBUG_MAC80211, "tx flush took %d ms", + jiffies_to_msecs(jiffies - start_time)); + goto out_wake; + } + } + + wl1271_warning("Unable to flush all TX buffers, " + "timed out (timeout %d ms", + WL1271_TX_FLUSH_TIMEOUT / 1000); + + /* forcibly flush all Tx buffers on our queues */ + for (i = 0; i < WL12XX_MAX_LINKS; i++) + wl1271_tx_reset_link_queues(wl, i); + +out_wake: + wlcore_wake_queues(wl, WLCORE_QUEUE_STOP_REASON_FLUSH); + mutex_unlock(&wl->mutex); +out: + mutex_unlock(&wl->flush_mutex); +} +EXPORT_SYMBOL_GPL(wl1271_tx_flush); + +u32 wl1271_tx_min_rate_get(struct wl1271 *wl, u32 rate_set) +{ + if (WARN_ON(!rate_set)) + return 0; + + return BIT(__ffs(rate_set)); +} +EXPORT_SYMBOL_GPL(wl1271_tx_min_rate_get); + +void wlcore_stop_queue_locked(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 queue, enum wlcore_queue_stop_reason reason) +{ + int hwq = wlcore_tx_get_mac80211_queue(wlvif, queue); + bool stopped = !!wl->queue_stop_reasons[hwq]; + + /* queue should not be stopped for this reason */ + WARN_ON_ONCE(test_and_set_bit(reason, &wl->queue_stop_reasons[hwq])); + + if (stopped) + return; + + ieee80211_stop_queue(wl->hw, hwq); +} + +void wlcore_stop_queue(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 queue, + enum wlcore_queue_stop_reason reason) +{ + unsigned long flags; + + spin_lock_irqsave(&wl->wl_lock, flags); + wlcore_stop_queue_locked(wl, wlvif, queue, reason); + spin_unlock_irqrestore(&wl->wl_lock, flags); +} + +void wlcore_wake_queue(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 queue, + enum wlcore_queue_stop_reason reason) +{ + unsigned long flags; + int hwq = wlcore_tx_get_mac80211_queue(wlvif, queue); + + spin_lock_irqsave(&wl->wl_lock, flags); + + /* queue should not be clear for this reason */ + WARN_ON_ONCE(!test_and_clear_bit(reason, &wl->queue_stop_reasons[hwq])); + + if (wl->queue_stop_reasons[hwq]) + goto out; + + ieee80211_wake_queue(wl->hw, hwq); + +out: + spin_unlock_irqrestore(&wl->wl_lock, flags); +} + +void wlcore_stop_queues(struct wl1271 *wl, + enum wlcore_queue_stop_reason reason) +{ + int i; + unsigned long flags; + + spin_lock_irqsave(&wl->wl_lock, flags); + + /* mark all possible queues as stopped */ + for (i = 0; i < WLCORE_NUM_MAC_ADDRESSES * NUM_TX_QUEUES; i++) + WARN_ON_ONCE(test_and_set_bit(reason, + &wl->queue_stop_reasons[i])); + + /* use the global version to make sure all vifs in mac80211 we don't + * know are stopped. + */ + ieee80211_stop_queues(wl->hw); + + spin_unlock_irqrestore(&wl->wl_lock, flags); +} + +void wlcore_wake_queues(struct wl1271 *wl, + enum wlcore_queue_stop_reason reason) +{ + int i; + unsigned long flags; + + spin_lock_irqsave(&wl->wl_lock, flags); + + /* mark all possible queues as awake */ + for (i = 0; i < WLCORE_NUM_MAC_ADDRESSES * NUM_TX_QUEUES; i++) + WARN_ON_ONCE(!test_and_clear_bit(reason, + &wl->queue_stop_reasons[i])); + + /* use the global version to make sure all vifs in mac80211 we don't + * know are woken up. + */ + ieee80211_wake_queues(wl->hw); + + spin_unlock_irqrestore(&wl->wl_lock, flags); +} + +bool wlcore_is_queue_stopped_by_reason(struct wl1271 *wl, + struct wl12xx_vif *wlvif, u8 queue, + enum wlcore_queue_stop_reason reason) +{ + unsigned long flags; + bool stopped; + + spin_lock_irqsave(&wl->wl_lock, flags); + stopped = wlcore_is_queue_stopped_by_reason_locked(wl, wlvif, queue, + reason); + spin_unlock_irqrestore(&wl->wl_lock, flags); + + return stopped; +} + +bool wlcore_is_queue_stopped_by_reason_locked(struct wl1271 *wl, + struct wl12xx_vif *wlvif, u8 queue, + enum wlcore_queue_stop_reason reason) +{ + int hwq = wlcore_tx_get_mac80211_queue(wlvif, queue); + + assert_spin_locked(&wl->wl_lock); + return test_bit(reason, &wl->queue_stop_reasons[hwq]); +} + +bool wlcore_is_queue_stopped_locked(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 queue) +{ + int hwq = wlcore_tx_get_mac80211_queue(wlvif, queue); + + assert_spin_locked(&wl->wl_lock); + return !!wl->queue_stop_reasons[hwq]; +} diff --git a/drivers/net/wireless/ti/wlcore/tx.h b/drivers/net/wireless/ti/wlcore/tx.h new file mode 100644 index 0000000..55aa4ac --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/tx.h @@ -0,0 +1,283 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 1998-2009 Texas Instruments. All rights reserved. + * Copyright (C) 2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TX_H__ +#define __TX_H__ + +#define TX_HW_MGMT_PKT_LIFETIME_TU 2000 +#define TX_HW_AP_MODE_PKT_LIFETIME_TU 8000 + +#define TX_HW_ATTR_SAVE_RETRIES BIT(0) +#define TX_HW_ATTR_HEADER_PAD BIT(1) +#define TX_HW_ATTR_SESSION_COUNTER (BIT(2) | BIT(3) | BIT(4)) +#define TX_HW_ATTR_RATE_POLICY (BIT(5) | BIT(6) | BIT(7) | \ + BIT(8) | BIT(9)) +#define TX_HW_ATTR_LAST_WORD_PAD (BIT(10) | BIT(11)) +#define TX_HW_ATTR_TX_CMPLT_REQ BIT(12) +#define TX_HW_ATTR_TX_DUMMY_REQ BIT(13) +#define TX_HW_ATTR_HOST_ENCRYPT BIT(14) + +#define TX_HW_ATTR_OFST_SAVE_RETRIES 0 +#define TX_HW_ATTR_OFST_HEADER_PAD 1 +#define TX_HW_ATTR_OFST_SESSION_COUNTER 2 +#define TX_HW_ATTR_OFST_RATE_POLICY 5 +#define TX_HW_ATTR_OFST_LAST_WORD_PAD 10 +#define TX_HW_ATTR_OFST_TX_CMPLT_REQ 12 + +#define TX_HW_RESULT_QUEUE_LEN 16 +#define TX_HW_RESULT_QUEUE_LEN_MASK 0xf + +#define WL1271_TX_ALIGN_TO 4 +#define WL1271_EXTRA_SPACE_TKIP 4 +#define WL1271_EXTRA_SPACE_AES 8 +#define WL1271_EXTRA_SPACE_MAX 8 + +/* Used for management frames and dummy packets */ +#define WL1271_TID_MGMT 7 + +struct wl127x_tx_mem { + /* + * Number of extra memory blocks to allocate for this packet + * in addition to the number of blocks derived from the packet + * length. + */ + u8 extra_blocks; + /* + * Total number of memory blocks allocated by the host for + * this packet. Must be equal or greater than the actual + * blocks number allocated by HW. + */ + u8 total_mem_blocks; +} __packed; + +struct wl128x_tx_mem { + /* + * Total number of memory blocks allocated by the host for + * this packet. + */ + u8 total_mem_blocks; + /* + * Number of extra bytes, at the end of the frame. the host + * uses this padding to complete each frame to integer number + * of SDIO blocks. + */ + u8 extra_bytes; +} __packed; + +struct wl18xx_tx_mem { + /* + * Total number of memory blocks allocated by the host for + * this packet. + */ + u8 total_mem_blocks; + + /* + * control bits + */ + u8 ctrl; +} __packed; + +/* + * On wl128x based devices, when TX packets are aggregated, each packet + * size must be aligned to the SDIO block size. The maximum block size + * is bounded by the type of the padded bytes field that is sent to the + * FW. Currently the type is u8, so the maximum block size is 256 bytes. + */ +#define WL12XX_BUS_BLOCK_SIZE min(512u, \ + (1u << (8 * sizeof(((struct wl128x_tx_mem *) 0)->extra_bytes)))) + +struct wl1271_tx_hw_descr { + /* Length of packet in words, including descriptor+header+data */ + __le16 length; + union { + struct wl127x_tx_mem wl127x_mem; + struct wl128x_tx_mem wl128x_mem; + struct wl18xx_tx_mem wl18xx_mem; + } __packed; + /* Device time (in us) when the packet arrived to the driver */ + __le32 start_time; + /* + * Max delay in TUs until transmission. The last device time the + * packet can be transmitted is: start_time + (1024 * life_time) + */ + __le16 life_time; + /* Bitwise fields - see TX_ATTR... definitions above. */ + __le16 tx_attr; + /* Packet identifier used also in the Tx-Result. */ + u8 id; + /* The packet TID value (as User-Priority) */ + u8 tid; + /* host link ID (HLID) */ + u8 hlid; + + union { + u8 wl12xx_reserved; + + /* + * bit 0 -> 0 = udp, 1 = tcp + * bit 1:7 -> IP header offset + */ + u8 wl18xx_checksum_data; + } __packed; +} __packed; + +enum wl1271_tx_hw_res_status { + TX_SUCCESS = 0, + TX_HW_ERROR = 1, + TX_DISABLED = 2, + TX_RETRY_EXCEEDED = 3, + TX_TIMEOUT = 4, + TX_KEY_NOT_FOUND = 5, + TX_PEER_NOT_FOUND = 6, + TX_SESSION_MISMATCH = 7, + TX_LINK_NOT_VALID = 8, +}; + +struct wl1271_tx_hw_res_descr { + /* Packet Identifier - same value used in the Tx descriptor.*/ + u8 id; + /* The status of the transmission, indicating success or one of + several possible reasons for failure. */ + u8 status; + /* Total air access duration including all retrys and overheads.*/ + __le16 medium_usage; + /* The time passed from host xfer to Tx-complete.*/ + __le32 fw_handling_time; + /* Total media delay + (from 1st EDCA AIFS counter until TX Complete). */ + __le32 medium_delay; + /* LS-byte of last TKIP seq-num (saved per AC for recovery). */ + u8 tx_security_sequence_number_lsb; + /* Retry count - number of transmissions without successful ACK.*/ + u8 ack_failures; + /* The rate that succeeded getting ACK + (Valid only if status=SUCCESS). */ + u8 rate_class_index; + /* for 4-byte alignment. */ + u8 spare; +} __packed; + +struct wl1271_tx_hw_res_if { + __le32 tx_result_fw_counter; + __le32 tx_result_host_counter; + struct wl1271_tx_hw_res_descr tx_results_queue[TX_HW_RESULT_QUEUE_LEN]; +} __packed; + +enum wlcore_queue_stop_reason { + WLCORE_QUEUE_STOP_REASON_WATERMARK, + WLCORE_QUEUE_STOP_REASON_FW_RESTART, + WLCORE_QUEUE_STOP_REASON_FLUSH, + WLCORE_QUEUE_STOP_REASON_SPARE_BLK, /* 18xx specific */ +}; + +static inline int wl1271_tx_get_queue(int queue) +{ + switch (queue) { + case 0: + return CONF_TX_AC_VO; + case 1: + return CONF_TX_AC_VI; + case 2: + return CONF_TX_AC_BE; + case 3: + return CONF_TX_AC_BK; + default: + return CONF_TX_AC_BE; + } +} + +static inline +int wlcore_tx_get_mac80211_queue(struct wl12xx_vif *wlvif, int queue) +{ + int mac_queue = wlvif->hw_queue_base; + + switch (queue) { + case CONF_TX_AC_VO: + return mac_queue + 0; + case CONF_TX_AC_VI: + return mac_queue + 1; + case CONF_TX_AC_BE: + return mac_queue + 2; + case CONF_TX_AC_BK: + return mac_queue + 3; + default: + return mac_queue + 2; + } +} + +static inline int wl1271_tx_total_queue_count(struct wl1271 *wl) +{ + int i, count = 0; + + for (i = 0; i < NUM_TX_QUEUES; i++) + count += wl->tx_queue_count[i]; + + return count; +} + +void wl1271_tx_work(struct work_struct *work); +int wlcore_tx_work_locked(struct wl1271 *wl); +int wlcore_tx_complete(struct wl1271 *wl); +void wl12xx_tx_reset_wlvif(struct wl1271 *wl, struct wl12xx_vif *wlvif); +void wl12xx_tx_reset(struct wl1271 *wl); +void wl1271_tx_flush(struct wl1271 *wl); +u8 wlcore_rate_to_idx(struct wl1271 *wl, u8 rate, enum ieee80211_band band); +u32 wl1271_tx_enabled_rates_get(struct wl1271 *wl, u32 rate_set, + enum ieee80211_band rate_band); +u32 wl1271_tx_min_rate_get(struct wl1271 *wl, u32 rate_set); +u8 wl12xx_tx_get_hlid(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct sk_buff *skb, struct ieee80211_sta *sta); +void wl1271_tx_reset_link_queues(struct wl1271 *wl, u8 hlid); +void wl1271_handle_tx_low_watermark(struct wl1271 *wl); +bool wl12xx_is_dummy_packet(struct wl1271 *wl, struct sk_buff *skb); +void wl12xx_rearm_rx_streaming(struct wl1271 *wl, unsigned long *active_hlids); +unsigned int wlcore_calc_packet_alignment(struct wl1271 *wl, + unsigned int packet_length); +void wl1271_free_tx_id(struct wl1271 *wl, int id); +void wlcore_stop_queue_locked(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 queue, enum wlcore_queue_stop_reason reason); +void wlcore_stop_queue(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 queue, + enum wlcore_queue_stop_reason reason); +void wlcore_wake_queue(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 queue, + enum wlcore_queue_stop_reason reason); +void wlcore_stop_queues(struct wl1271 *wl, + enum wlcore_queue_stop_reason reason); +void wlcore_wake_queues(struct wl1271 *wl, + enum wlcore_queue_stop_reason reason); +bool wlcore_is_queue_stopped_by_reason(struct wl1271 *wl, + struct wl12xx_vif *wlvif, u8 queue, + enum wlcore_queue_stop_reason reason); +bool +wlcore_is_queue_stopped_by_reason_locked(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + u8 queue, + enum wlcore_queue_stop_reason reason); +bool wlcore_is_queue_stopped_locked(struct wl1271 *wl, struct wl12xx_vif *wlvif, + u8 queue); + +/* from main.c */ +void wl1271_free_sta(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 hlid); +void wl12xx_rearm_tx_watchdog_locked(struct wl1271 *wl); + +#endif diff --git a/drivers/net/wireless/ti/wlcore/wl12xx_80211.h b/drivers/net/wireless/ti/wlcore/wl12xx_80211.h new file mode 100644 index 0000000..22b0bc9 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/wl12xx_80211.h @@ -0,0 +1,137 @@ +#ifndef __WL12XX_80211_H__ +#define __WL12XX_80211_H__ + +#include /* ETH_ALEN */ +#include + +/* RATES */ +#define IEEE80211_CCK_RATE_1MB 0x02 +#define IEEE80211_CCK_RATE_2MB 0x04 +#define IEEE80211_CCK_RATE_5MB 0x0B +#define IEEE80211_CCK_RATE_11MB 0x16 +#define IEEE80211_OFDM_RATE_6MB 0x0C +#define IEEE80211_OFDM_RATE_9MB 0x12 +#define IEEE80211_OFDM_RATE_12MB 0x18 +#define IEEE80211_OFDM_RATE_18MB 0x24 +#define IEEE80211_OFDM_RATE_24MB 0x30 +#define IEEE80211_OFDM_RATE_36MB 0x48 +#define IEEE80211_OFDM_RATE_48MB 0x60 +#define IEEE80211_OFDM_RATE_54MB 0x6C +#define IEEE80211_BASIC_RATE_MASK 0x80 + +#define IEEE80211_CCK_RATE_1MB_MASK (1<<0) +#define IEEE80211_CCK_RATE_2MB_MASK (1<<1) +#define IEEE80211_CCK_RATE_5MB_MASK (1<<2) +#define IEEE80211_CCK_RATE_11MB_MASK (1<<3) +#define IEEE80211_OFDM_RATE_6MB_MASK (1<<4) +#define IEEE80211_OFDM_RATE_9MB_MASK (1<<5) +#define IEEE80211_OFDM_RATE_12MB_MASK (1<<6) +#define IEEE80211_OFDM_RATE_18MB_MASK (1<<7) +#define IEEE80211_OFDM_RATE_24MB_MASK (1<<8) +#define IEEE80211_OFDM_RATE_36MB_MASK (1<<9) +#define IEEE80211_OFDM_RATE_48MB_MASK (1<<10) +#define IEEE80211_OFDM_RATE_54MB_MASK (1<<11) + +#define IEEE80211_CCK_RATES_MASK 0x0000000F +#define IEEE80211_CCK_BASIC_RATES_MASK (IEEE80211_CCK_RATE_1MB_MASK | \ + IEEE80211_CCK_RATE_2MB_MASK) +#define IEEE80211_CCK_DEFAULT_RATES_MASK (IEEE80211_CCK_BASIC_RATES_MASK | \ + IEEE80211_CCK_RATE_5MB_MASK | \ + IEEE80211_CCK_RATE_11MB_MASK) + +#define IEEE80211_OFDM_RATES_MASK 0x00000FF0 +#define IEEE80211_OFDM_BASIC_RATES_MASK (IEEE80211_OFDM_RATE_6MB_MASK | \ + IEEE80211_OFDM_RATE_12MB_MASK | \ + IEEE80211_OFDM_RATE_24MB_MASK) +#define IEEE80211_OFDM_DEFAULT_RATES_MASK (IEEE80211_OFDM_BASIC_RATES_MASK | \ + IEEE80211_OFDM_RATE_9MB_MASK | \ + IEEE80211_OFDM_RATE_18MB_MASK | \ + IEEE80211_OFDM_RATE_36MB_MASK | \ + IEEE80211_OFDM_RATE_48MB_MASK | \ + IEEE80211_OFDM_RATE_54MB_MASK) +#define IEEE80211_DEFAULT_RATES_MASK (IEEE80211_OFDM_DEFAULT_RATES_MASK | \ + IEEE80211_CCK_DEFAULT_RATES_MASK) + + +/* This really should be 8, but not for our firmware */ +#define MAX_SUPPORTED_RATES 32 +#define MAX_COUNTRY_TRIPLETS 32 + +/* Headers */ +struct ieee80211_header { + __le16 frame_ctl; + __le16 duration_id; + u8 da[ETH_ALEN]; + u8 sa[ETH_ALEN]; + u8 bssid[ETH_ALEN]; + __le16 seq_ctl; + u8 payload[0]; +} __packed; + +struct wl12xx_ie_header { + u8 id; + u8 len; +} __packed; + +/* IEs */ + +struct wl12xx_ie_ssid { + struct wl12xx_ie_header header; + char ssid[IEEE80211_MAX_SSID_LEN]; +} __packed; + +struct wl12xx_ie_rates { + struct wl12xx_ie_header header; + u8 rates[MAX_SUPPORTED_RATES]; +} __packed; + +struct wl12xx_ie_ds_params { + struct wl12xx_ie_header header; + u8 channel; +} __packed; + +struct country_triplet { + u8 channel; + u8 num_channels; + u8 max_tx_power; +} __packed; + +struct wl12xx_ie_country { + struct wl12xx_ie_header header; + u8 country_string[IEEE80211_COUNTRY_STRING_LEN]; + struct country_triplet triplets[MAX_COUNTRY_TRIPLETS]; +} __packed; + + +/* Templates */ + +struct wl12xx_null_data_template { + struct ieee80211_header header; +} __packed; + +struct wl12xx_ps_poll_template { + __le16 fc; + __le16 aid; + u8 bssid[ETH_ALEN]; + u8 ta[ETH_ALEN]; +} __packed; + +struct wl12xx_arp_rsp_template { + /* not including ieee80211 header */ + + u8 llc_hdr[sizeof(rfc1042_header)]; + __be16 llc_type; + + struct arphdr arp_hdr; + u8 sender_hw[ETH_ALEN]; + __be32 sender_ip; + u8 target_hw[ETH_ALEN]; + __be32 target_ip; +} __packed; + +struct wl12xx_disconn_template { + struct ieee80211_header header; + __le16 disconn_reason; +} __packed; + +#endif diff --git a/drivers/net/wireless/ti/wlcore/wlcore.h b/drivers/net/wireless/ti/wlcore/wlcore.h new file mode 100644 index 0000000..e97c35e --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/wlcore.h @@ -0,0 +1,618 @@ +/* + * This file is part of wlcore + * + * Copyright (C) 2011 Texas Instruments Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WLCORE_H__ +#define __WLCORE_H__ + +#include + +#include "wlcore_i.h" +#include "event.h" +#include "boot.h" + +/* The maximum number of Tx descriptors in all chip families */ +#define WLCORE_MAX_TX_DESCRIPTORS 32 + +/* + * We always allocate this number of mac addresses. If we don't + * have enough allocated addresses, the LAA bit is used + */ +#define WLCORE_NUM_MAC_ADDRESSES 3 + +/* wl12xx/wl18xx maximum transmission power (in dBm) */ +#define WLCORE_MAX_TXPWR 25 + +/* forward declaration */ +struct wl1271_tx_hw_descr; +enum wl_rx_buf_align; +struct wl1271_rx_descriptor; + +struct wlcore_ops { + int (*setup)(struct wl1271 *wl); + int (*identify_chip)(struct wl1271 *wl); + int (*identify_fw)(struct wl1271 *wl); + int (*boot)(struct wl1271 *wl); + int (*plt_init)(struct wl1271 *wl); + int (*trigger_cmd)(struct wl1271 *wl, int cmd_box_addr, + void *buf, size_t len); + int (*ack_event)(struct wl1271 *wl); + int (*wait_for_event)(struct wl1271 *wl, enum wlcore_wait_event event, + bool *timeout); + int (*process_mailbox_events)(struct wl1271 *wl); + u32 (*calc_tx_blocks)(struct wl1271 *wl, u32 len, u32 spare_blks); + void (*set_tx_desc_blocks)(struct wl1271 *wl, + struct wl1271_tx_hw_descr *desc, + u32 blks, u32 spare_blks); + void (*set_tx_desc_data_len)(struct wl1271 *wl, + struct wl1271_tx_hw_descr *desc, + struct sk_buff *skb); + enum wl_rx_buf_align (*get_rx_buf_align)(struct wl1271 *wl, + u32 rx_desc); + int (*prepare_read)(struct wl1271 *wl, u32 rx_desc, u32 len); + u32 (*get_rx_packet_len)(struct wl1271 *wl, void *rx_data, + u32 data_len); + int (*tx_delayed_compl)(struct wl1271 *wl); + void (*tx_immediate_compl)(struct wl1271 *wl); + int (*hw_init)(struct wl1271 *wl); + int (*init_vif)(struct wl1271 *wl, struct wl12xx_vif *wlvif); + u32 (*sta_get_ap_rate_mask)(struct wl1271 *wl, + struct wl12xx_vif *wlvif); + int (*get_pg_ver)(struct wl1271 *wl, s8 *ver); + int (*get_mac)(struct wl1271 *wl); + void (*set_tx_desc_csum)(struct wl1271 *wl, + struct wl1271_tx_hw_descr *desc, + struct sk_buff *skb); + void (*set_rx_csum)(struct wl1271 *wl, + struct wl1271_rx_descriptor *desc, + struct sk_buff *skb); + u32 (*ap_get_mimo_wide_rate_mask)(struct wl1271 *wl, + struct wl12xx_vif *wlvif); + int (*debugfs_init)(struct wl1271 *wl, struct dentry *rootdir); + int (*handle_static_data)(struct wl1271 *wl, + struct wl1271_static_data *static_data); + int (*scan_start)(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct cfg80211_scan_request *req); + int (*scan_stop)(struct wl1271 *wl, struct wl12xx_vif *wlvif); + int (*sched_scan_start)(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies); + void (*sched_scan_stop)(struct wl1271 *wl, struct wl12xx_vif *wlvif); + int (*get_spare_blocks)(struct wl1271 *wl, bool is_gem); + int (*set_key)(struct wl1271 *wl, enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key_conf); + int (*channel_switch)(struct wl1271 *wl, + struct wl12xx_vif *wlvif, + struct ieee80211_channel_switch *ch_switch); + u32 (*pre_pkt_send)(struct wl1271 *wl, u32 buf_offset, u32 last_len); + void (*sta_rc_update)(struct wl1271 *wl, struct wl12xx_vif *wlvif, + struct ieee80211_sta *sta, u32 changed); + int (*set_peer_cap)(struct wl1271 *wl, + struct ieee80211_sta_ht_cap *ht_cap, + bool allow_ht_operation, + u32 rate_set, u8 hlid); + bool (*lnk_high_prio)(struct wl1271 *wl, u8 hlid, + struct wl1271_link *lnk); + bool (*lnk_low_prio)(struct wl1271 *wl, u8 hlid, + struct wl1271_link *lnk); +}; + +enum wlcore_partitions { + PART_DOWN, + PART_WORK, + PART_BOOT, + PART_DRPW, + PART_TOP_PRCM_ELP_SOC, + PART_PHY_INIT, + + PART_TABLE_LEN, +}; + +struct wlcore_partition { + u32 size; + u32 start; +}; + +struct wlcore_partition_set { + struct wlcore_partition mem; + struct wlcore_partition reg; + struct wlcore_partition mem2; + struct wlcore_partition mem3; +}; + +enum wlcore_registers { + /* register addresses, used with partition translation */ + REG_ECPU_CONTROL, + REG_INTERRUPT_NO_CLEAR, + REG_INTERRUPT_ACK, + REG_COMMAND_MAILBOX_PTR, + REG_EVENT_MAILBOX_PTR, + REG_INTERRUPT_TRIG, + REG_INTERRUPT_MASK, + REG_PC_ON_RECOVERY, + REG_CHIP_ID_B, + REG_CMD_MBOX_ADDRESS, + + /* data access memory addresses, used with partition translation */ + REG_SLV_MEM_DATA, + REG_SLV_REG_DATA, + + /* raw data access memory addresses */ + REG_RAW_FW_STATUS_ADDR, + + REG_TABLE_LEN, +}; + +struct wl1271_stats { + void *fw_stats; + unsigned long fw_stats_update; + size_t fw_stats_len; + + unsigned int retry_count; + unsigned int excessive_retries; +}; + +struct wl1271 { + bool initialized; + struct ieee80211_hw *hw; + bool mac80211_registered; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31) + struct compat_threaded_irq irq_compat; +#endif + struct device *dev; + struct platform_device *pdev; + + void *if_priv; + + struct wl1271_if_operations *if_ops; + + int irq; + + spinlock_t wl_lock; + + enum wlcore_state state; + enum wl12xx_fw_type fw_type; + bool plt; + enum plt_mode plt_mode; + u8 fem_manuf; + u8 last_vif_count; + struct mutex mutex; + + unsigned long flags; + + struct wlcore_partition_set curr_part; + + struct wl1271_chip chip; + + int cmd_box_addr; + + u8 *fw; + size_t fw_len; + void *nvs; + size_t nvs_len; + + s8 hw_pg_ver; + + /* address read from the fuse ROM */ + u32 fuse_oui_addr; + u32 fuse_nic_addr; + + /* we have up to 2 MAC addresses */ + struct mac_address addresses[WLCORE_NUM_MAC_ADDRESSES]; + int channel; + u8 system_hlid; + + unsigned long links_map[BITS_TO_LONGS(WL12XX_MAX_LINKS)]; + unsigned long roles_map[BITS_TO_LONGS(WL12XX_MAX_ROLES)]; + unsigned long roc_map[BITS_TO_LONGS(WL12XX_MAX_ROLES)]; + unsigned long rate_policies_map[ + BITS_TO_LONGS(WL12XX_MAX_RATE_POLICIES)]; + unsigned long klv_templates_map[ + BITS_TO_LONGS(WLCORE_MAX_KLV_TEMPLATES)]; + + u8 session_ids[WL12XX_MAX_LINKS]; + + struct list_head wlvif_list; + + u8 sta_count; + u8 ap_count; + + struct wl1271_acx_mem_map *target_mem_map; + + /* Accounting for allocated / available TX blocks on HW */ + u32 tx_blocks_freed; + u32 tx_blocks_available; + u32 tx_allocated_blocks; + u32 tx_results_count; + + /* Accounting for allocated / available Tx packets in HW */ + u32 tx_pkts_freed[NUM_TX_QUEUES]; + u32 tx_allocated_pkts[NUM_TX_QUEUES]; + + /* Transmitted TX packets counter for chipset interface */ + u32 tx_packets_count; + + /* Time-offset between host and chipset clocks */ + s64 time_offset; + + /* Frames scheduled for transmission, not handled yet */ + int tx_queue_count[NUM_TX_QUEUES]; + unsigned long queue_stop_reasons[ + NUM_TX_QUEUES * WLCORE_NUM_MAC_ADDRESSES]; + + /* Frames received, not handled yet by mac80211 */ + struct sk_buff_head deferred_rx_queue; + + /* Frames sent, not returned yet to mac80211 */ + struct sk_buff_head deferred_tx_queue; + + struct work_struct tx_work; + struct workqueue_struct *freezable_wq; + + /* Pending TX frames */ + unsigned long tx_frames_map[BITS_TO_LONGS(WLCORE_MAX_TX_DESCRIPTORS)]; + struct sk_buff *tx_frames[WLCORE_MAX_TX_DESCRIPTORS]; + int tx_frames_cnt; + + /* FW Rx counter */ + u32 rx_counter; + + /* Intermediate buffer, used for packet aggregation */ + u8 *aggr_buf; + u32 aggr_buf_size; + + /* Reusable dummy packet template */ + struct sk_buff *dummy_packet; + + /* Network stack work */ + struct work_struct netstack_work; + + /* FW log buffer */ + u8 *fwlog; + + /* Number of valid bytes in the FW log buffer */ + ssize_t fwlog_size; + + /* Sysfs FW log entry readers wait queue */ + wait_queue_head_t fwlog_waitq; + + /* Hardware recovery work */ + struct work_struct recovery_work; + bool watchdog_recovery; + + /* Reg domain last configuration */ + u32 reg_ch_conf_last[2]; + /* Reg domain pending configuration */ + u32 reg_ch_conf_pending[2]; + + /* Pointer that holds DMA-friendly block for the mailbox */ + void *mbox; + + /* The mbox event mask */ + u32 event_mask; + + /* Mailbox pointers */ + u32 mbox_size; + u32 mbox_ptr[2]; + + /* Are we currently scanning */ + struct wl12xx_vif *scan_wlvif; + struct wl1271_scan scan; + struct delayed_work scan_complete_work; + + struct ieee80211_vif *roc_vif; + struct delayed_work roc_complete_work; + + struct wl12xx_vif *sched_vif; + + /* The current band */ + enum ieee80211_band band; + + struct completion *elp_compl; + struct delayed_work elp_work; + + /* in dBm */ + int power_level; + + struct wl1271_stats stats; + + __le32 *buffer_32; + u32 buffer_cmd; + u32 buffer_busyword[WL1271_BUSY_WORD_CNT]; + + struct wl_fw_status_1 *fw_status_1; + struct wl_fw_status_2 *fw_status_2; + struct wl1271_tx_hw_res_if *tx_res_if; + + /* Current chipset configuration */ + struct wlcore_conf conf; + + bool sg_enabled; + + bool enable_11a; + + int recovery_count; + + /* Most recently reported noise in dBm */ + s8 noise; + + /* bands supported by this instance of wl12xx */ + struct ieee80211_supported_band bands[WLCORE_NUM_BANDS]; + + /* + * wowlan trigger was configured during suspend. + * (currently, only "ANY" trigger is supported) + */ + bool wow_enabled; + bool irq_wake_enabled; + + /* + * AP-mode - links indexed by HLID. The global and broadcast links + * are always active. + */ + struct wl1271_link links[WL12XX_MAX_LINKS]; + + /* number of currently active links */ + int active_link_count; + + /* Fast/slow links bitmap according to FW */ + u32 fw_fast_lnk_map; + + /* AP-mode - a bitmap of links currently in PS mode according to FW */ + u32 ap_fw_ps_map; + + /* AP-mode - a bitmap of links currently in PS mode in mac80211 */ + unsigned long ap_ps_map; + + /* Quirks of specific hardware revisions */ + unsigned int quirks; + + /* Platform limitations */ + unsigned int platform_quirks; + + /* number of currently active RX BA sessions */ + int ba_rx_session_count; + + /* Maximum number of supported RX BA sessions */ + int ba_rx_session_count_max; + + /* AP-mode - number of currently connected stations */ + int active_sta_count; + + /* last wlvif we transmitted from */ + struct wl12xx_vif *last_wlvif; + + /* work to fire when Tx is stuck */ + struct delayed_work tx_watchdog_work; + + struct wlcore_ops *ops; + /* pointer to the lower driver partition table */ + const struct wlcore_partition_set *ptable; + /* pointer to the lower driver register table */ + const int *rtable; + /* name of the firmwares to load - for PLT, single role, multi-role */ + const char *plt_fw_name; + const char *sr_fw_name; + const char *mr_fw_name; + + u8 scan_templ_id_2_4; + u8 scan_templ_id_5; + u8 sched_scan_templ_id_2_4; + u8 sched_scan_templ_id_5; + u8 max_channels_5; + + /* per-chip-family private structure */ + void *priv; + + /* number of TX descriptors the HW supports. */ + u32 num_tx_desc; + /* number of RX descriptors the HW supports. */ + u32 num_rx_desc; + + /* translate HW Tx rates to standard rate-indices */ + const u8 **band_rate_to_idx; + + /* size of table for HW rates that can be received from chip */ + u8 hw_tx_rate_tbl_size; + + /* this HW rate and below are considered HT rates for this chip */ + u8 hw_min_ht_rate; + + /* HW HT (11n) capabilities */ + struct ieee80211_sta_ht_cap ht_cap[WLCORE_NUM_BANDS]; + + /* size of the private FW status data */ + size_t fw_status_priv_len; + + /* RX Data filter rule state - enabled/disabled */ + bool rx_filter_enabled[WL1271_MAX_RX_FILTERS]; + + /* size of the private static data */ + size_t static_data_priv_len; + + /* the current channel type */ + enum nl80211_channel_type channel_type; + + /* mutex for protecting the tx_flush function */ + struct mutex flush_mutex; + + /* sleep auth value currently configured to FW */ + int sleep_auth; + + /* the number of allocated MAC addresses in this chip */ + int num_mac_addr; + + /* minimum FW version required for the driver to work in single-role */ + unsigned int min_sr_fw_ver[NUM_FW_VER]; + + /* minimum FW version required for the driver to work in multi-role */ + unsigned int min_mr_fw_ver[NUM_FW_VER]; + + struct completion nvs_loading_complete; + + /* number of concurrent channels the HW supports */ + u32 num_channels; +}; + +int wlcore_probe(struct wl1271 *wl, struct platform_device *pdev); +int wlcore_remove(struct platform_device *pdev); +struct ieee80211_hw *wlcore_alloc_hw(size_t priv_size, u32 aggr_buf_size, + u32 mbox_size); +int wlcore_free_hw(struct wl1271 *wl); +int wlcore_set_key(struct wl1271 *wl, enum set_key_cmd cmd, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_key_conf *key_conf); +void wlcore_regdomain_config(struct wl1271 *wl); + +static inline void +wlcore_set_ht_cap(struct wl1271 *wl, enum ieee80211_band band, + struct ieee80211_sta_ht_cap *ht_cap) +{ + memcpy(&wl->ht_cap[band], ht_cap, sizeof(*ht_cap)); +} + +/* Tell wlcore not to care about this element when checking the version */ +#define WLCORE_FW_VER_IGNORE -1 + +static inline void +wlcore_set_min_fw_ver(struct wl1271 *wl, unsigned int chip, + unsigned int iftype_sr, unsigned int major_sr, + unsigned int subtype_sr, unsigned int minor_sr, + unsigned int iftype_mr, unsigned int major_mr, + unsigned int subtype_mr, unsigned int minor_mr) +{ + wl->min_sr_fw_ver[FW_VER_CHIP] = chip; + wl->min_sr_fw_ver[FW_VER_IF_TYPE] = iftype_sr; + wl->min_sr_fw_ver[FW_VER_MAJOR] = major_sr; + wl->min_sr_fw_ver[FW_VER_SUBTYPE] = subtype_sr; + wl->min_sr_fw_ver[FW_VER_MINOR] = minor_sr; + + wl->min_mr_fw_ver[FW_VER_CHIP] = chip; + wl->min_mr_fw_ver[FW_VER_IF_TYPE] = iftype_mr; + wl->min_mr_fw_ver[FW_VER_MAJOR] = major_mr; + wl->min_mr_fw_ver[FW_VER_SUBTYPE] = subtype_mr; + wl->min_mr_fw_ver[FW_VER_MINOR] = minor_mr; +} + +/* Firmware image load chunk size */ +#define CHUNK_SIZE 16384 + +/* Quirks */ + +/* Each RX/TX transaction requires an end-of-transaction transfer */ +#define WLCORE_QUIRK_END_OF_TRANSACTION BIT(0) + +/* the first start_role(sta) sometimes doesn't work on wl12xx */ +#define WLCORE_QUIRK_START_STA_FAILS BIT(1) + +/* wl127x and SPI don't support SDIO block size alignment */ +#define WLCORE_QUIRK_TX_BLOCKSIZE_ALIGN BIT(2) + +/* means aggregated Rx packets are aligned to a SDIO block */ +#define WLCORE_QUIRK_RX_BLOCKSIZE_ALIGN BIT(3) + +/* Older firmwares did not implement the FW logger over bus feature */ +#define WLCORE_QUIRK_FWLOG_NOT_IMPLEMENTED BIT(4) + +/* Older firmwares use an old NVS format */ +#define WLCORE_QUIRK_LEGACY_NVS BIT(5) + +/* pad only the last frame in the aggregate buffer */ +#define WLCORE_QUIRK_TX_PAD_LAST_FRAME BIT(7) + +/* extra header space is required for TKIP */ +#define WLCORE_QUIRK_TKIP_HEADER_SPACE BIT(8) + +/* Some firmwares not support sched scans while connected */ +#define WLCORE_QUIRK_NO_SCHED_SCAN_WHILE_CONN BIT(9) + +/* separate probe response templates for one-shot and sched scans */ +#define WLCORE_QUIRK_DUAL_PROBE_TMPL BIT(10) + +/* Firmware requires reg domain configuration for active calibration */ +#define WLCORE_QUIRK_REGDOMAIN_CONF BIT(11) + +/* The FW only support a zero session id for AP */ +#define WLCORE_QUIRK_AP_ZERO_SESSION_ID BIT(12) + +/* TODO: move all these common registers and values elsewhere */ +#define HW_ACCESS_ELP_CTRL_REG 0x1FFFC + +/* ELP register commands */ +#define ELPCTRL_WAKE_UP 0x1 +#define ELPCTRL_WAKE_UP_WLAN_READY 0x5 +#define ELPCTRL_SLEEP 0x0 +/* ELP WLAN_READY bit */ +#define ELPCTRL_WLAN_READY 0x2 + +/************************************************************************* + + Interrupt Trigger Register (Host -> WiLink) + +**************************************************************************/ + +/* Hardware to Embedded CPU Interrupts - first 32-bit register set */ + +/* + * The host sets this bit to inform the Wlan + * FW that a TX packet is in the XFER + * Buffer #0. + */ +#define INTR_TRIG_TX_PROC0 BIT(2) + +/* + * The host sets this bit to inform the FW + * that it read a packet from RX XFER + * Buffer #0. + */ +#define INTR_TRIG_RX_PROC0 BIT(3) + +#define INTR_TRIG_DEBUG_ACK BIT(4) + +#define INTR_TRIG_STATE_CHANGED BIT(5) + +/* Hardware to Embedded CPU Interrupts - second 32-bit register set */ + +/* + * The host sets this bit to inform the FW + * that it read a packet from RX XFER + * Buffer #1. + */ +#define INTR_TRIG_RX_PROC1 BIT(17) + +/* + * The host sets this bit to inform the Wlan + * hardware that a TX packet is in the XFER + * Buffer #1. + */ +#define INTR_TRIG_TX_PROC1 BIT(18) + +#define ACX_SLV_SOFT_RESET_BIT BIT(1) +#define SOFT_RESET_MAX_TIME 1000000 +#define SOFT_RESET_STALL_TIME 1000 + +#define ECPU_CONTROL_HALT 0x00000101 + +#define WELP_ARM_COMMAND_VAL 0x4 + +#endif /* __WLCORE_H__ */ diff --git a/drivers/net/wireless/ti/wlcore/wlcore_i.h b/drivers/net/wireless/ti/wlcore/wlcore_i.h new file mode 100644 index 0000000..e5e1464 --- /dev/null +++ b/drivers/net/wireless/ti/wlcore/wlcore_i.h @@ -0,0 +1,544 @@ +/* + * This file is part of wl1271 + * + * Copyright (C) 1998-2009 Texas Instruments. All rights reserved. + * Copyright (C) 2008-2009 Nokia Corporation + * + * Contact: Luciano Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __WLCORE_I_H__ +#define __WLCORE_I_H__ + +#include +#include +#include +#include +#include +#include + +#include "conf.h" +#include "ini.h" + +/* + * wl127x and wl128x are using the same NVS file name. However, the + * ini parameters between them are different. The driver validates + * the correct NVS size in wl1271_boot_upload_nvs(). + */ +#define WL12XX_NVS_NAME "ti-connectivity/wl1271-nvs.bin" + +#define WL1271_TX_SECURITY_LO16(s) ((u16)((s) & 0xffff)) +#define WL1271_TX_SECURITY_HI32(s) ((u32)(((s) >> 16) & 0xffffffff)) +#define WL1271_TX_SQN_POST_RECOVERY_PADDING 0xff + +#define WL1271_CIPHER_SUITE_GEM 0x00147201 + +#define WL1271_BUSY_WORD_CNT 1 +#define WL1271_BUSY_WORD_LEN (WL1271_BUSY_WORD_CNT * sizeof(u32)) + +#define WL1271_ELP_HW_STATE_ASLEEP 0 +#define WL1271_ELP_HW_STATE_IRQ 1 + +#define WL1271_DEFAULT_BEACON_INT 100 +#define WL1271_DEFAULT_DTIM_PERIOD 1 + +#define WL12XX_MAX_ROLES 4 +#define WL12XX_MAX_LINKS 12 +#define WL12XX_INVALID_ROLE_ID 0xff +#define WL12XX_INVALID_LINK_ID 0xff + +/* the driver supports the 2.4Ghz and 5Ghz bands */ +#define WLCORE_NUM_BANDS 2 + +#define WL12XX_MAX_RATE_POLICIES 16 +#define WLCORE_MAX_KLV_TEMPLATES 4 + +/* Defined by FW as 0. Will not be freed or allocated. */ +#define WL12XX_SYSTEM_HLID 0 + +/* + * When in AP-mode, we allow (at least) this number of packets + * to be transmitted to FW for a STA in PS-mode. Only when packets are + * present in the FW buffers it will wake the sleeping STA. We want to put + * enough packets for the driver to transmit all of its buffered data before + * the STA goes to sleep again. But we don't want to take too much memory + * as it might hurt the throughput of active STAs. + */ +#define WL1271_PS_STA_MAX_PACKETS 2 + +#define WL1271_AP_BSS_INDEX 0 +#define WL1271_AP_DEF_BEACON_EXP 20 + +enum wlcore_state { + WLCORE_STATE_OFF, + WLCORE_STATE_RESTARTING, + WLCORE_STATE_ON, +}; + +enum wl12xx_fw_type { + WL12XX_FW_TYPE_NONE, + WL12XX_FW_TYPE_NORMAL, + WL12XX_FW_TYPE_MULTI, + WL12XX_FW_TYPE_PLT, +}; + +struct wl1271; + +enum { + FW_VER_CHIP, + FW_VER_IF_TYPE, + FW_VER_MAJOR, + FW_VER_SUBTYPE, + FW_VER_MINOR, + + NUM_FW_VER +}; + +struct wl1271_chip { + u32 id; + char fw_ver_str[ETHTOOL_FWVERS_LEN]; + unsigned int fw_ver[NUM_FW_VER]; + char phy_fw_ver_str[ETHTOOL_FWVERS_LEN]; +}; + +#define NUM_TX_QUEUES 4 + +#define AP_MAX_STATIONS 8 + +struct wl_fw_packet_counters { + /* Cumulative counter of released packets per AC */ + u8 tx_released_pkts[NUM_TX_QUEUES]; + + /* Cumulative counter of freed packets per HLID */ + u8 tx_lnk_free_pkts[WL12XX_MAX_LINKS]; + + /* Cumulative counter of released Voice memory blocks */ + u8 tx_voice_released_blks; + + /* Tx rate of the last transmitted packet */ + u8 tx_last_rate; + + u8 padding[2]; +} __packed; + +/* FW status registers */ +struct wl_fw_status_1 { + __le32 intr; + u8 fw_rx_counter; + u8 drv_rx_counter; + u8 reserved; + u8 tx_results_counter; + __le32 rx_pkt_descs[0]; +} __packed; + +/* + * Each HW arch has a different number of Rx descriptors. + * The length of the status depends on it, since it holds an array + * of descriptors. + */ +#define WLCORE_FW_STATUS_1_LEN(num_rx_desc) \ + (sizeof(struct wl_fw_status_1) + \ + (sizeof(((struct wl_fw_status_1 *)0)->rx_pkt_descs[0])) * \ + num_rx_desc) + +struct wl_fw_status_2 { + __le32 fw_localtime; + + /* + * A bitmap (where each bit represents a single HLID) + * to indicate if the station is in PS mode. + */ + __le32 link_ps_bitmap; + + /* + * A bitmap (where each bit represents a single HLID) to indicate + * if the station is in Fast mode + */ + __le32 link_fast_bitmap; + + /* Cumulative counter of total released mem blocks since FW-reset */ + __le32 total_released_blks; + + /* Size (in Memory Blocks) of TX pool */ + __le32 tx_total; + + struct wl_fw_packet_counters counters; + + __le32 log_start_addr; + + /* Private status to be used by the lower drivers */ + u8 priv[0]; +} __packed; + +#define WL1271_MAX_CHANNELS 64 +struct wl1271_scan { + struct cfg80211_scan_request *req; + unsigned long scanned_ch[BITS_TO_LONGS(WL1271_MAX_CHANNELS)]; + bool failed; + u8 state; + u8 ssid[IEEE80211_MAX_SSID_LEN+1]; + size_t ssid_len; +}; + +struct wl1271_if_operations { + int __must_check (*read)(struct device *child, int addr, void *buf, + size_t len, bool fixed); + int __must_check (*write)(struct device *child, int addr, void *buf, + size_t len, bool fixed); + void (*reset)(struct device *child); + void (*init)(struct device *child); + int (*power)(struct device *child, bool enable); + void (*set_block_size) (struct device *child, unsigned int blksz); +}; + +struct wlcore_platdev_data { + struct wl12xx_platform_data *pdata; + struct wl1271_if_operations *if_ops; +}; + +#define MAX_NUM_KEYS 14 +#define MAX_KEY_SIZE 32 + +struct wl1271_ap_key { + u8 id; + u8 key_type; + u8 key_size; + u8 key[MAX_KEY_SIZE]; + u8 hlid; + u32 tx_seq_32; + u16 tx_seq_16; +}; + +enum wl12xx_flags { + WL1271_FLAG_GPIO_POWER, + WL1271_FLAG_TX_QUEUE_STOPPED, + WL1271_FLAG_TX_PENDING, + WL1271_FLAG_IN_ELP, + WL1271_FLAG_ELP_REQUESTED, + WL1271_FLAG_IRQ_RUNNING, + WL1271_FLAG_FW_TX_BUSY, + WL1271_FLAG_DUMMY_PACKET_PENDING, + WL1271_FLAG_SUSPENDED, + WL1271_FLAG_PENDING_WORK, + WL1271_FLAG_SOFT_GEMINI, + WL1271_FLAG_RECOVERY_IN_PROGRESS, + WL1271_FLAG_VIF_CHANGE_IN_PROGRESS, + WL1271_FLAG_INTENDED_FW_RECOVERY, + WL1271_FLAG_IO_FAILED, +}; + +enum wl12xx_vif_flags { + WLVIF_FLAG_INITIALIZED, + WLVIF_FLAG_STA_ASSOCIATED, + WLVIF_FLAG_STA_AUTHORIZED, + WLVIF_FLAG_IBSS_JOINED, + WLVIF_FLAG_AP_STARTED, + WLVIF_FLAG_IN_PS, + WLVIF_FLAG_STA_STATE_SENT, + WLVIF_FLAG_RX_STREAMING_STARTED, + WLVIF_FLAG_PSPOLL_FAILURE, + WLVIF_FLAG_CS_PROGRESS, + WLVIF_FLAG_AP_PROBE_RESP_SET, + WLVIF_FLAG_IN_USE, +}; + +struct wl12xx_vif; + +struct wl1271_link { + /* AP-mode - TX queue per AC in link */ + struct sk_buff_head tx_queue[NUM_TX_QUEUES]; + + /* accounting for allocated / freed packets in FW */ + u8 allocated_pkts; + u8 prev_freed_pkts; + + u8 addr[ETH_ALEN]; + + /* bitmap of TIDs where RX BA sessions are active for this link */ + u8 ba_bitmap; + + /* The wlvif this link belongs to. Might be null for global links */ + struct wl12xx_vif *wlvif; + + /* + * total freed FW packets on the link - used for tracking the + * AES/TKIP PN across recoveries. Re-initialized each time + * from the wl1271_station structure. + */ + u64 total_freed_pkts; +}; + +#define WL1271_MAX_RX_FILTERS 5 +#define WL1271_RX_FILTER_MAX_FIELDS 8 + +#define WL1271_RX_FILTER_ETH_HEADER_SIZE 14 +#define WL1271_RX_FILTER_MAX_FIELDS_SIZE 95 +#define RX_FILTER_FIELD_OVERHEAD \ + (sizeof(struct wl12xx_rx_filter_field) - sizeof(u8 *)) +#define WL1271_RX_FILTER_MAX_PATTERN_SIZE \ + (WL1271_RX_FILTER_MAX_FIELDS_SIZE - RX_FILTER_FIELD_OVERHEAD) + +#define WL1271_RX_FILTER_FLAG_MASK BIT(0) +#define WL1271_RX_FILTER_FLAG_IP_HEADER 0 +#define WL1271_RX_FILTER_FLAG_ETHERNET_HEADER BIT(1) + +enum rx_filter_action { + FILTER_DROP = 0, + FILTER_SIGNAL = 1, + FILTER_FW_HANDLE = 2 +}; + +enum plt_mode { + PLT_OFF = 0, + PLT_ON = 1, + PLT_FEM_DETECT = 2, +}; + +struct wl12xx_rx_filter_field { + __le16 offset; + u8 len; + u8 flags; + u8 *pattern; +} __packed; + +struct wl12xx_rx_filter { + u8 action; + int num_fields; + struct wl12xx_rx_filter_field fields[WL1271_RX_FILTER_MAX_FIELDS]; +}; + +struct wl1271_station { + u8 hlid; + bool in_connection; + + /* + * total freed FW packets on the link to the STA - used for tracking the + * AES/TKIP PN across recoveries. Re-initialized each time from the + * wl1271_station structure. + */ + u64 total_freed_pkts; +}; + +struct wl12xx_vif { + struct wl1271 *wl; + struct list_head list; + unsigned long flags; + u8 bss_type; + u8 p2p; /* we are using p2p role */ + u8 role_id; + + /* sta/ibss specific */ + u8 dev_role_id; + u8 dev_hlid; + + union { + struct { + u8 hlid; + + u8 basic_rate_idx; + u8 ap_rate_idx; + u8 p2p_rate_idx; + + u8 klv_template_id; + + bool qos; + /* channel type we started the STA role with */ + enum nl80211_channel_type role_chan_type; + } sta; + struct { + u8 global_hlid; + u8 bcast_hlid; + + /* HLIDs bitmap of associated stations */ + unsigned long sta_hlid_map[BITS_TO_LONGS( + WL12XX_MAX_LINKS)]; + + /* recoreded keys - set here before AP startup */ + struct wl1271_ap_key *recorded_keys[MAX_NUM_KEYS]; + + u8 mgmt_rate_idx; + u8 bcast_rate_idx; + u8 ucast_rate_idx[CONF_TX_MAX_AC_COUNT]; + } ap; + }; + + /* the hlid of the last transmitted skb */ + int last_tx_hlid; + + /* counters of packets per AC, across all links in the vif */ + int tx_queue_count[NUM_TX_QUEUES]; + + unsigned long links_map[BITS_TO_LONGS(WL12XX_MAX_LINKS)]; + + u8 ssid[IEEE80211_MAX_SSID_LEN + 1]; + u8 ssid_len; + + /* The current band */ + enum ieee80211_band band; + int channel; + enum nl80211_channel_type channel_type; + + u32 bitrate_masks[WLCORE_NUM_BANDS]; + u32 basic_rate_set; + + /* + * currently configured rate set: + * bits 0-15 - 802.11abg rates + * bits 16-23 - 802.11n MCS index mask + * support only 1 stream, thus only 8 bits for the MCS rates (0-7). + */ + u32 basic_rate; + u32 rate_set; + + /* probe-req template for the current AP */ + struct sk_buff *probereq; + + /* Beaconing interval (needed for ad-hoc) */ + u32 beacon_int; + + /* Default key (for WEP) */ + u32 default_key; + + /* Our association ID */ + u16 aid; + + /* retry counter for PSM entries */ + u8 psm_entry_retry; + + /* in dBm */ + int power_level; + + int rssi_thold; + int last_rssi_event; + + /* save the current encryption type for auto-arp config */ + u8 encryption_type; + __be32 ip_addr; + + /* RX BA constraint value */ + bool ba_support; + bool ba_allowed; + + bool wmm_enabled; + + /* Rx Streaming */ + struct work_struct rx_streaming_enable_work; + struct work_struct rx_streaming_disable_work; + struct timer_list rx_streaming_timer; + + struct delayed_work channel_switch_work; + struct delayed_work connection_loss_work; + + /* number of in connection stations */ + int inconn_count; + + /* + * This vif's queues are mapped to mac80211 HW queues as: + * VO - hw_queue_base + * VI - hw_queue_base + 1 + * BE - hw_queue_base + 2 + * BK - hw_queue_base + 3 + */ + int hw_queue_base; + + /* + * This struct must be last! + * data that has to be saved acrossed reconfigs (e.g. recovery) + * should be declared in this struct. + */ + struct { + u8 persistent[0]; + + /* + * total freed FW packets on the link - used for + * storing the AES/TKIP PN during recovery, as this + * structure is not zeroed out. + * For STA this holds the PN of the link to the AP. + * For AP this holds the PN of the broadcast link. + */ + u64 total_freed_pkts; + }; +}; + +static inline struct wl12xx_vif *wl12xx_vif_to_data(struct ieee80211_vif *vif) +{ + WARN_ON(!vif); + return (struct wl12xx_vif *)vif->drv_priv; +} + +static inline +struct ieee80211_vif *wl12xx_wlvif_to_vif(struct wl12xx_vif *wlvif) +{ + return container_of((void *)wlvif, struct ieee80211_vif, drv_priv); +} + +#define wl12xx_for_each_wlvif(wl, wlvif) \ + list_for_each_entry(wlvif, &wl->wlvif_list, list) + +#define wl12xx_for_each_wlvif_continue(wl, wlvif) \ + list_for_each_entry_continue(wlvif, &wl->wlvif_list, list) + +#define wl12xx_for_each_wlvif_bss_type(wl, wlvif, _bss_type) \ + wl12xx_for_each_wlvif(wl, wlvif) \ + if (wlvif->bss_type == _bss_type) + +#define wl12xx_for_each_wlvif_sta(wl, wlvif) \ + wl12xx_for_each_wlvif_bss_type(wl, wlvif, BSS_TYPE_STA_BSS) + +#define wl12xx_for_each_wlvif_ap(wl, wlvif) \ + wl12xx_for_each_wlvif_bss_type(wl, wlvif, BSS_TYPE_AP_BSS) + +int wl1271_plt_start(struct wl1271 *wl, const enum plt_mode plt_mode); +int wl1271_plt_stop(struct wl1271 *wl); +int wl1271_recalc_rx_streaming(struct wl1271 *wl, struct wl12xx_vif *wlvif); +void wl12xx_queue_recovery_work(struct wl1271 *wl); +size_t wl12xx_copy_fwlog(struct wl1271 *wl, u8 *memblock, size_t maxlen); +int wl1271_rx_filter_alloc_field(struct wl12xx_rx_filter *filter, + u16 offset, u8 flags, + u8 *pattern, u8 len); +void wl1271_rx_filter_free(struct wl12xx_rx_filter *filter); +struct wl12xx_rx_filter *wl1271_rx_filter_alloc(void); +int wl1271_rx_filter_get_fields_size(struct wl12xx_rx_filter *filter); +void wl1271_rx_filter_flatten_fields(struct wl12xx_rx_filter *filter, + u8 *buf); + +#define JOIN_TIMEOUT 5000 /* 5000 milliseconds to join */ + +#define SESSION_COUNTER_MAX 6 /* maximum value for the session counter */ +#define SESSION_COUNTER_INVALID 7 /* used with dummy_packet */ + +#define WL1271_DEFAULT_POWER_LEVEL 0 + +#define WL1271_TX_QUEUE_LOW_WATERMARK 32 +#define WL1271_TX_QUEUE_HIGH_WATERMARK 256 + +#define WL1271_DEFERRED_QUEUE_LIMIT 64 + +/* WL1271 needs a 200ms sleep after power on, and a 20ms sleep before power + on in case is has been shut down shortly before */ +#define WL1271_PRE_POWER_ON_SLEEP 20 /* in milliseconds */ +#define WL1271_POWER_ON_SLEEP 200 /* in milliseconds */ + +/* Macros to handle wl1271.sta_rate_set */ +#define HW_BG_RATES_MASK 0xffff +#define HW_HT_RATES_OFFSET 16 +#define HW_MIMO_RATES_OFFSET 24 + +#define WL12XX_HW_BLOCK_SIZE 256 + +#endif /* __WLCORE_I_H__ */ diff --git a/drivers/net/wireless/zd1211rw/Kconfig b/drivers/net/wireless/zd1211rw/Kconfig new file mode 100644 index 0000000..8be0370 --- /dev/null +++ b/drivers/net/wireless/zd1211rw/Kconfig @@ -0,0 +1,21 @@ +config ZD1211RW + depends on !BACKPORT_KERNEL_2_6_29 + tristate "ZyDAS ZD1211/ZD1211B USB-wireless support" + depends on m + depends on USB && MAC80211 + select BACKPORT_FW_LOADER + ---help--- + This is an experimental driver for the ZyDAS ZD1211/ZD1211B wireless + chip, present in many USB-wireless adapters. + + Device firmware is required alongside this driver. You can download + the firmware distribution from http://zd1211.ath.cx/get-firmware + +config ZD1211RW_DEBUG + bool "ZyDAS ZD1211 debugging" + depends on ZD1211RW + ---help--- + ZD1211 debugging messages. Choosing Y will result in additional debug + messages being saved to your kernel logs, which may help debug any + problems. + diff --git a/drivers/net/wireless/zd1211rw/Makefile b/drivers/net/wireless/zd1211rw/Makefile new file mode 100644 index 0000000..a65f93e --- /dev/null +++ b/drivers/net/wireless/zd1211rw/Makefile @@ -0,0 +1,9 @@ +obj-$(CPTCFG_ZD1211RW) += zd1211rw.o + +zd1211rw-objs := zd_chip.o zd_mac.o \ + zd_rf_al2230.o zd_rf_rf2959.o \ + zd_rf_al7230b.o zd_rf_uw2453.o \ + zd_rf.o zd_usb.o + +ccflags-$(CPTCFG_ZD1211RW_DEBUG) := -DDEBUG + diff --git a/drivers/net/wireless/zd1211rw/zd_chip.c b/drivers/net/wireless/zd1211rw/zd_chip.c new file mode 100644 index 0000000..71ab320 --- /dev/null +++ b/drivers/net/wireless/zd1211rw/zd_chip.c @@ -0,0 +1,1561 @@ +/* ZD1211 USB-WLAN driver for Linux + * + * Copyright (C) 2005-2007 Ulrich Kunitz + * Copyright (C) 2006-2007 Daniel Drake + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* This file implements all the hardware specific functions for the ZD1211 + * and ZD1211B chips. Support for the ZD1211B was possible after Timothy + * Legge sent me a ZD1211B device. Thank you Tim. -- Uli + */ + +#include +#include +#include + +#include "zd_def.h" +#include "zd_chip.h" +#include "zd_mac.h" +#include "zd_rf.h" + +void zd_chip_init(struct zd_chip *chip, + struct ieee80211_hw *hw, + struct usb_interface *intf) +{ + memset(chip, 0, sizeof(*chip)); + mutex_init(&chip->mutex); + zd_usb_init(&chip->usb, hw, intf); + zd_rf_init(&chip->rf); +} + +void zd_chip_clear(struct zd_chip *chip) +{ + ZD_ASSERT(!mutex_is_locked(&chip->mutex)); + zd_usb_clear(&chip->usb); + zd_rf_clear(&chip->rf); + mutex_destroy(&chip->mutex); + ZD_MEMCLEAR(chip, sizeof(*chip)); +} + +static int scnprint_mac_oui(struct zd_chip *chip, char *buffer, size_t size) +{ + u8 *addr = zd_mac_get_perm_addr(zd_chip_to_mac(chip)); + return scnprintf(buffer, size, "%02x-%02x-%02x", + addr[0], addr[1], addr[2]); +} + +/* Prints an identifier line, which will support debugging. */ +static int scnprint_id(struct zd_chip *chip, char *buffer, size_t size) +{ + int i = 0; + + i = scnprintf(buffer, size, "zd1211%s chip ", + zd_chip_is_zd1211b(chip) ? "b" : ""); + i += zd_usb_scnprint_id(&chip->usb, buffer+i, size-i); + i += scnprintf(buffer+i, size-i, " "); + i += scnprint_mac_oui(chip, buffer+i, size-i); + i += scnprintf(buffer+i, size-i, " "); + i += zd_rf_scnprint_id(&chip->rf, buffer+i, size-i); + i += scnprintf(buffer+i, size-i, " pa%1x %c%c%c%c%c", chip->pa_type, + chip->patch_cck_gain ? 'g' : '-', + chip->patch_cr157 ? '7' : '-', + chip->patch_6m_band_edge ? '6' : '-', + chip->new_phy_layout ? 'N' : '-', + chip->al2230s_bit ? 'S' : '-'); + return i; +} + +static void print_id(struct zd_chip *chip) +{ + char buffer[80]; + + scnprint_id(chip, buffer, sizeof(buffer)); + buffer[sizeof(buffer)-1] = 0; + dev_info(zd_chip_dev(chip), "%s\n", buffer); +} + +static zd_addr_t inc_addr(zd_addr_t addr) +{ + u16 a = (u16)addr; + /* Control registers use byte addressing, but everything else uses word + * addressing. */ + if ((a & 0xf000) == CR_START) + a += 2; + else + a += 1; + return (zd_addr_t)a; +} + +/* Read a variable number of 32-bit values. Parameter count is not allowed to + * exceed USB_MAX_IOREAD32_COUNT. + */ +int zd_ioread32v_locked(struct zd_chip *chip, u32 *values, const zd_addr_t *addr, + unsigned int count) +{ + int r; + int i; + zd_addr_t a16[USB_MAX_IOREAD32_COUNT * 2]; + u16 v16[USB_MAX_IOREAD32_COUNT * 2]; + unsigned int count16; + + if (count > USB_MAX_IOREAD32_COUNT) + return -EINVAL; + + /* Use stack for values and addresses. */ + count16 = 2 * count; + BUG_ON(count16 * sizeof(zd_addr_t) > sizeof(a16)); + BUG_ON(count16 * sizeof(u16) > sizeof(v16)); + + for (i = 0; i < count; i++) { + int j = 2*i; + /* We read the high word always first. */ + a16[j] = inc_addr(addr[i]); + a16[j+1] = addr[i]; + } + + r = zd_ioread16v_locked(chip, v16, a16, count16); + if (r) { + dev_dbg_f(zd_chip_dev(chip), + "error: zd_ioread16v_locked. Error number %d\n", r); + return r; + } + + for (i = 0; i < count; i++) { + int j = 2*i; + values[i] = (v16[j] << 16) | v16[j+1]; + } + + return 0; +} + +static int _zd_iowrite32v_async_locked(struct zd_chip *chip, + const struct zd_ioreq32 *ioreqs, + unsigned int count) +{ + int i, j, r; + struct zd_ioreq16 ioreqs16[USB_MAX_IOWRITE32_COUNT * 2]; + unsigned int count16; + + /* Use stack for values and addresses. */ + + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + + if (count == 0) + return 0; + if (count > USB_MAX_IOWRITE32_COUNT) + return -EINVAL; + + count16 = 2 * count; + BUG_ON(count16 * sizeof(struct zd_ioreq16) > sizeof(ioreqs16)); + + for (i = 0; i < count; i++) { + j = 2*i; + /* We write the high word always first. */ + ioreqs16[j].value = ioreqs[i].value >> 16; + ioreqs16[j].addr = inc_addr(ioreqs[i].addr); + ioreqs16[j+1].value = ioreqs[i].value; + ioreqs16[j+1].addr = ioreqs[i].addr; + } + + r = zd_usb_iowrite16v_async(&chip->usb, ioreqs16, count16); +#ifdef DEBUG + if (r) { + dev_dbg_f(zd_chip_dev(chip), + "error %d in zd_usb_write16v\n", r); + } +#endif /* DEBUG */ + return r; +} + +int _zd_iowrite32v_locked(struct zd_chip *chip, const struct zd_ioreq32 *ioreqs, + unsigned int count) +{ + int r; + + zd_usb_iowrite16v_async_start(&chip->usb); + r = _zd_iowrite32v_async_locked(chip, ioreqs, count); + if (r) { + zd_usb_iowrite16v_async_end(&chip->usb, 0); + return r; + } + return zd_usb_iowrite16v_async_end(&chip->usb, 50 /* ms */); +} + +int zd_iowrite16a_locked(struct zd_chip *chip, + const struct zd_ioreq16 *ioreqs, unsigned int count) +{ + int r; + unsigned int i, j, t, max; + + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + zd_usb_iowrite16v_async_start(&chip->usb); + + for (i = 0; i < count; i += j + t) { + t = 0; + max = count-i; + if (max > USB_MAX_IOWRITE16_COUNT) + max = USB_MAX_IOWRITE16_COUNT; + for (j = 0; j < max; j++) { + if (!ioreqs[i+j].addr) { + t = 1; + break; + } + } + + r = zd_usb_iowrite16v_async(&chip->usb, &ioreqs[i], j); + if (r) { + zd_usb_iowrite16v_async_end(&chip->usb, 0); + dev_dbg_f(zd_chip_dev(chip), + "error zd_usb_iowrite16v. Error number %d\n", + r); + return r; + } + } + + return zd_usb_iowrite16v_async_end(&chip->usb, 50 /* ms */); +} + +/* Writes a variable number of 32 bit registers. The functions will split + * that in several USB requests. A split can be forced by inserting an IO + * request with an zero address field. + */ +int zd_iowrite32a_locked(struct zd_chip *chip, + const struct zd_ioreq32 *ioreqs, unsigned int count) +{ + int r; + unsigned int i, j, t, max; + + zd_usb_iowrite16v_async_start(&chip->usb); + + for (i = 0; i < count; i += j + t) { + t = 0; + max = count-i; + if (max > USB_MAX_IOWRITE32_COUNT) + max = USB_MAX_IOWRITE32_COUNT; + for (j = 0; j < max; j++) { + if (!ioreqs[i+j].addr) { + t = 1; + break; + } + } + + r = _zd_iowrite32v_async_locked(chip, &ioreqs[i], j); + if (r) { + zd_usb_iowrite16v_async_end(&chip->usb, 0); + dev_dbg_f(zd_chip_dev(chip), + "error _zd_iowrite32v_locked." + " Error number %d\n", r); + return r; + } + } + + return zd_usb_iowrite16v_async_end(&chip->usb, 50 /* ms */); +} + +int zd_ioread16(struct zd_chip *chip, zd_addr_t addr, u16 *value) +{ + int r; + + mutex_lock(&chip->mutex); + r = zd_ioread16_locked(chip, value, addr); + mutex_unlock(&chip->mutex); + return r; +} + +int zd_ioread32(struct zd_chip *chip, zd_addr_t addr, u32 *value) +{ + int r; + + mutex_lock(&chip->mutex); + r = zd_ioread32_locked(chip, value, addr); + mutex_unlock(&chip->mutex); + return r; +} + +int zd_iowrite16(struct zd_chip *chip, zd_addr_t addr, u16 value) +{ + int r; + + mutex_lock(&chip->mutex); + r = zd_iowrite16_locked(chip, value, addr); + mutex_unlock(&chip->mutex); + return r; +} + +int zd_iowrite32(struct zd_chip *chip, zd_addr_t addr, u32 value) +{ + int r; + + mutex_lock(&chip->mutex); + r = zd_iowrite32_locked(chip, value, addr); + mutex_unlock(&chip->mutex); + return r; +} + +int zd_ioread32v(struct zd_chip *chip, const zd_addr_t *addresses, + u32 *values, unsigned int count) +{ + int r; + + mutex_lock(&chip->mutex); + r = zd_ioread32v_locked(chip, values, addresses, count); + mutex_unlock(&chip->mutex); + return r; +} + +int zd_iowrite32a(struct zd_chip *chip, const struct zd_ioreq32 *ioreqs, + unsigned int count) +{ + int r; + + mutex_lock(&chip->mutex); + r = zd_iowrite32a_locked(chip, ioreqs, count); + mutex_unlock(&chip->mutex); + return r; +} + +static int read_pod(struct zd_chip *chip, u8 *rf_type) +{ + int r; + u32 value; + + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + r = zd_ioread32_locked(chip, &value, E2P_POD); + if (r) + goto error; + dev_dbg_f(zd_chip_dev(chip), "E2P_POD %#010x\n", value); + + /* FIXME: AL2230 handling (Bit 7 in POD) */ + *rf_type = value & 0x0f; + chip->pa_type = (value >> 16) & 0x0f; + chip->patch_cck_gain = (value >> 8) & 0x1; + chip->patch_cr157 = (value >> 13) & 0x1; + chip->patch_6m_band_edge = (value >> 21) & 0x1; + chip->new_phy_layout = (value >> 31) & 0x1; + chip->al2230s_bit = (value >> 7) & 0x1; + chip->link_led = ((value >> 4) & 1) ? LED1 : LED2; + chip->supports_tx_led = 1; + if (value & (1 << 24)) { /* LED scenario */ + if (value & (1 << 29)) + chip->supports_tx_led = 0; + } + + dev_dbg_f(zd_chip_dev(chip), + "RF %s %#01x PA type %#01x patch CCK %d patch CR157 %d " + "patch 6M %d new PHY %d link LED%d tx led %d\n", + zd_rf_name(*rf_type), *rf_type, + chip->pa_type, chip->patch_cck_gain, + chip->patch_cr157, chip->patch_6m_band_edge, + chip->new_phy_layout, + chip->link_led == LED1 ? 1 : 2, + chip->supports_tx_led); + return 0; +error: + *rf_type = 0; + chip->pa_type = 0; + chip->patch_cck_gain = 0; + chip->patch_cr157 = 0; + chip->patch_6m_band_edge = 0; + chip->new_phy_layout = 0; + return r; +} + +static int zd_write_mac_addr_common(struct zd_chip *chip, const u8 *mac_addr, + const struct zd_ioreq32 *in_reqs, + const char *type) +{ + int r; + struct zd_ioreq32 reqs[2] = {in_reqs[0], in_reqs[1]}; + + if (mac_addr) { + reqs[0].value = (mac_addr[3] << 24) + | (mac_addr[2] << 16) + | (mac_addr[1] << 8) + | mac_addr[0]; + reqs[1].value = (mac_addr[5] << 8) + | mac_addr[4]; + dev_dbg_f(zd_chip_dev(chip), "%s addr %pM\n", type, mac_addr); + } else { + dev_dbg_f(zd_chip_dev(chip), "set NULL %s\n", type); + } + + mutex_lock(&chip->mutex); + r = zd_iowrite32a_locked(chip, reqs, ARRAY_SIZE(reqs)); + mutex_unlock(&chip->mutex); + return r; +} + +/* MAC address: if custom mac addresses are to be used CR_MAC_ADDR_P1 and + * CR_MAC_ADDR_P2 must be overwritten + */ +int zd_write_mac_addr(struct zd_chip *chip, const u8 *mac_addr) +{ + static const struct zd_ioreq32 reqs[2] = { + [0] = { .addr = CR_MAC_ADDR_P1 }, + [1] = { .addr = CR_MAC_ADDR_P2 }, + }; + + return zd_write_mac_addr_common(chip, mac_addr, reqs, "mac"); +} + +int zd_write_bssid(struct zd_chip *chip, const u8 *bssid) +{ + static const struct zd_ioreq32 reqs[2] = { + [0] = { .addr = CR_BSSID_P1 }, + [1] = { .addr = CR_BSSID_P2 }, + }; + + return zd_write_mac_addr_common(chip, bssid, reqs, "bssid"); +} + +int zd_read_regdomain(struct zd_chip *chip, u8 *regdomain) +{ + int r; + u32 value; + + mutex_lock(&chip->mutex); + r = zd_ioread32_locked(chip, &value, E2P_SUBID); + mutex_unlock(&chip->mutex); + if (r) + return r; + + *regdomain = value >> 16; + dev_dbg_f(zd_chip_dev(chip), "regdomain: %#04x\n", *regdomain); + + return 0; +} + +static int read_values(struct zd_chip *chip, u8 *values, size_t count, + zd_addr_t e2p_addr, u32 guard) +{ + int r; + int i; + u32 v; + + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + for (i = 0;;) { + r = zd_ioread32_locked(chip, &v, + (zd_addr_t)((u16)e2p_addr+i/2)); + if (r) + return r; + v -= guard; + if (i+4 < count) { + values[i++] = v; + values[i++] = v >> 8; + values[i++] = v >> 16; + values[i++] = v >> 24; + continue; + } + for (;i < count; i++) + values[i] = v >> (8*(i%3)); + return 0; + } +} + +static int read_pwr_cal_values(struct zd_chip *chip) +{ + return read_values(chip, chip->pwr_cal_values, + E2P_CHANNEL_COUNT, E2P_PWR_CAL_VALUE1, + 0); +} + +static int read_pwr_int_values(struct zd_chip *chip) +{ + return read_values(chip, chip->pwr_int_values, + E2P_CHANNEL_COUNT, E2P_PWR_INT_VALUE1, + E2P_PWR_INT_GUARD); +} + +static int read_ofdm_cal_values(struct zd_chip *chip) +{ + int r; + int i; + static const zd_addr_t addresses[] = { + E2P_36M_CAL_VALUE1, + E2P_48M_CAL_VALUE1, + E2P_54M_CAL_VALUE1, + }; + + for (i = 0; i < 3; i++) { + r = read_values(chip, chip->ofdm_cal_values[i], + E2P_CHANNEL_COUNT, addresses[i], 0); + if (r) + return r; + } + return 0; +} + +static int read_cal_int_tables(struct zd_chip *chip) +{ + int r; + + r = read_pwr_cal_values(chip); + if (r) + return r; + r = read_pwr_int_values(chip); + if (r) + return r; + r = read_ofdm_cal_values(chip); + if (r) + return r; + return 0; +} + +/* phy means physical registers */ +int zd_chip_lock_phy_regs(struct zd_chip *chip) +{ + int r; + u32 tmp; + + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + r = zd_ioread32_locked(chip, &tmp, CR_REG1); + if (r) { + dev_err(zd_chip_dev(chip), "error ioread32(CR_REG1): %d\n", r); + return r; + } + + tmp &= ~UNLOCK_PHY_REGS; + + r = zd_iowrite32_locked(chip, tmp, CR_REG1); + if (r) + dev_err(zd_chip_dev(chip), "error iowrite32(CR_REG1): %d\n", r); + return r; +} + +int zd_chip_unlock_phy_regs(struct zd_chip *chip) +{ + int r; + u32 tmp; + + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + r = zd_ioread32_locked(chip, &tmp, CR_REG1); + if (r) { + dev_err(zd_chip_dev(chip), + "error ioread32(CR_REG1): %d\n", r); + return r; + } + + tmp |= UNLOCK_PHY_REGS; + + r = zd_iowrite32_locked(chip, tmp, CR_REG1); + if (r) + dev_err(zd_chip_dev(chip), "error iowrite32(CR_REG1): %d\n", r); + return r; +} + +/* ZD_CR157 can be optionally patched by the EEPROM for original ZD1211 */ +static int patch_cr157(struct zd_chip *chip) +{ + int r; + u16 value; + + if (!chip->patch_cr157) + return 0; + + r = zd_ioread16_locked(chip, &value, E2P_PHY_REG); + if (r) + return r; + + dev_dbg_f(zd_chip_dev(chip), "patching value %x\n", value >> 8); + return zd_iowrite32_locked(chip, value >> 8, ZD_CR157); +} + +/* + * 6M band edge can be optionally overwritten for certain RF's + * Vendor driver says: for FCC regulation, enabled per HWFeature 6M band edge + * bit (for AL2230, AL2230S) + */ +static int patch_6m_band_edge(struct zd_chip *chip, u8 channel) +{ + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + if (!chip->patch_6m_band_edge) + return 0; + + return zd_rf_patch_6m_band_edge(&chip->rf, channel); +} + +/* Generic implementation of 6M band edge patching, used by most RFs via + * zd_rf_generic_patch_6m() */ +int zd_chip_generic_patch_6m_band(struct zd_chip *chip, int channel) +{ + struct zd_ioreq16 ioreqs[] = { + { ZD_CR128, 0x14 }, { ZD_CR129, 0x12 }, { ZD_CR130, 0x10 }, + { ZD_CR47, 0x1e }, + }; + + /* FIXME: Channel 11 is not the edge for all regulatory domains. */ + if (channel == 1 || channel == 11) + ioreqs[0].value = 0x12; + + dev_dbg_f(zd_chip_dev(chip), "patching for channel %d\n", channel); + return zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +static int zd1211_hw_reset_phy(struct zd_chip *chip) +{ + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR0, 0x0a }, { ZD_CR1, 0x06 }, { ZD_CR2, 0x26 }, + { ZD_CR3, 0x38 }, { ZD_CR4, 0x80 }, { ZD_CR9, 0xa0 }, + { ZD_CR10, 0x81 }, { ZD_CR11, 0x00 }, { ZD_CR12, 0x7f }, + { ZD_CR13, 0x8c }, { ZD_CR14, 0x80 }, { ZD_CR15, 0x3d }, + { ZD_CR16, 0x20 }, { ZD_CR17, 0x1e }, { ZD_CR18, 0x0a }, + { ZD_CR19, 0x48 }, { ZD_CR20, 0x0c }, { ZD_CR21, 0x0c }, + { ZD_CR22, 0x23 }, { ZD_CR23, 0x90 }, { ZD_CR24, 0x14 }, + { ZD_CR25, 0x40 }, { ZD_CR26, 0x10 }, { ZD_CR27, 0x19 }, + { ZD_CR28, 0x7f }, { ZD_CR29, 0x80 }, { ZD_CR30, 0x4b }, + { ZD_CR31, 0x60 }, { ZD_CR32, 0x43 }, { ZD_CR33, 0x08 }, + { ZD_CR34, 0x06 }, { ZD_CR35, 0x0a }, { ZD_CR36, 0x00 }, + { ZD_CR37, 0x00 }, { ZD_CR38, 0x38 }, { ZD_CR39, 0x0c }, + { ZD_CR40, 0x84 }, { ZD_CR41, 0x2a }, { ZD_CR42, 0x80 }, + { ZD_CR43, 0x10 }, { ZD_CR44, 0x12 }, { ZD_CR46, 0xff }, + { ZD_CR47, 0x1E }, { ZD_CR48, 0x26 }, { ZD_CR49, 0x5b }, + { ZD_CR64, 0xd0 }, { ZD_CR65, 0x04 }, { ZD_CR66, 0x58 }, + { ZD_CR67, 0xc9 }, { ZD_CR68, 0x88 }, { ZD_CR69, 0x41 }, + { ZD_CR70, 0x23 }, { ZD_CR71, 0x10 }, { ZD_CR72, 0xff }, + { ZD_CR73, 0x32 }, { ZD_CR74, 0x30 }, { ZD_CR75, 0x65 }, + { ZD_CR76, 0x41 }, { ZD_CR77, 0x1b }, { ZD_CR78, 0x30 }, + { ZD_CR79, 0x68 }, { ZD_CR80, 0x64 }, { ZD_CR81, 0x64 }, + { ZD_CR82, 0x00 }, { ZD_CR83, 0x00 }, { ZD_CR84, 0x00 }, + { ZD_CR85, 0x02 }, { ZD_CR86, 0x00 }, { ZD_CR87, 0x00 }, + { ZD_CR88, 0xff }, { ZD_CR89, 0xfc }, { ZD_CR90, 0x00 }, + { ZD_CR91, 0x00 }, { ZD_CR92, 0x00 }, { ZD_CR93, 0x08 }, + { ZD_CR94, 0x00 }, { ZD_CR95, 0x00 }, { ZD_CR96, 0xff }, + { ZD_CR97, 0xe7 }, { ZD_CR98, 0x00 }, { ZD_CR99, 0x00 }, + { ZD_CR100, 0x00 }, { ZD_CR101, 0xae }, { ZD_CR102, 0x02 }, + { ZD_CR103, 0x00 }, { ZD_CR104, 0x03 }, { ZD_CR105, 0x65 }, + { ZD_CR106, 0x04 }, { ZD_CR107, 0x00 }, { ZD_CR108, 0x0a }, + { ZD_CR109, 0xaa }, { ZD_CR110, 0xaa }, { ZD_CR111, 0x25 }, + { ZD_CR112, 0x25 }, { ZD_CR113, 0x00 }, { ZD_CR119, 0x1e }, + { ZD_CR125, 0x90 }, { ZD_CR126, 0x00 }, { ZD_CR127, 0x00 }, + { }, + { ZD_CR5, 0x00 }, { ZD_CR6, 0x00 }, { ZD_CR7, 0x00 }, + { ZD_CR8, 0x00 }, { ZD_CR9, 0x20 }, { ZD_CR12, 0xf0 }, + { ZD_CR20, 0x0e }, { ZD_CR21, 0x0e }, { ZD_CR27, 0x10 }, + { ZD_CR44, 0x33 }, { ZD_CR47, 0x1E }, { ZD_CR83, 0x24 }, + { ZD_CR84, 0x04 }, { ZD_CR85, 0x00 }, { ZD_CR86, 0x0C }, + { ZD_CR87, 0x12 }, { ZD_CR88, 0x0C }, { ZD_CR89, 0x00 }, + { ZD_CR90, 0x10 }, { ZD_CR91, 0x08 }, { ZD_CR93, 0x00 }, + { ZD_CR94, 0x01 }, { ZD_CR95, 0x00 }, { ZD_CR96, 0x50 }, + { ZD_CR97, 0x37 }, { ZD_CR98, 0x35 }, { ZD_CR101, 0x13 }, + { ZD_CR102, 0x27 }, { ZD_CR103, 0x27 }, { ZD_CR104, 0x18 }, + { ZD_CR105, 0x12 }, { ZD_CR109, 0x27 }, { ZD_CR110, 0x27 }, + { ZD_CR111, 0x27 }, { ZD_CR112, 0x27 }, { ZD_CR113, 0x27 }, + { ZD_CR114, 0x27 }, { ZD_CR115, 0x26 }, { ZD_CR116, 0x24 }, + { ZD_CR117, 0xfc }, { ZD_CR118, 0xfa }, { ZD_CR120, 0x4f }, + { ZD_CR125, 0xaa }, { ZD_CR127, 0x03 }, { ZD_CR128, 0x14 }, + { ZD_CR129, 0x12 }, { ZD_CR130, 0x10 }, { ZD_CR131, 0x0C }, + { ZD_CR136, 0xdf }, { ZD_CR137, 0x40 }, { ZD_CR138, 0xa0 }, + { ZD_CR139, 0xb0 }, { ZD_CR140, 0x99 }, { ZD_CR141, 0x82 }, + { ZD_CR142, 0x54 }, { ZD_CR143, 0x1c }, { ZD_CR144, 0x6c }, + { ZD_CR147, 0x07 }, { ZD_CR148, 0x4c }, { ZD_CR149, 0x50 }, + { ZD_CR150, 0x0e }, { ZD_CR151, 0x18 }, { ZD_CR160, 0xfe }, + { ZD_CR161, 0xee }, { ZD_CR162, 0xaa }, { ZD_CR163, 0xfa }, + { ZD_CR164, 0xfa }, { ZD_CR165, 0xea }, { ZD_CR166, 0xbe }, + { ZD_CR167, 0xbe }, { ZD_CR168, 0x6a }, { ZD_CR169, 0xba }, + { ZD_CR170, 0xba }, { ZD_CR171, 0xba }, + /* Note: ZD_CR204 must lead the ZD_CR203 */ + { ZD_CR204, 0x7d }, + { }, + { ZD_CR203, 0x30 }, + }; + + int r, t; + + dev_dbg_f(zd_chip_dev(chip), "\n"); + + r = zd_chip_lock_phy_regs(chip); + if (r) + goto out; + + r = zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); + if (r) + goto unlock; + + r = patch_cr157(chip); +unlock: + t = zd_chip_unlock_phy_regs(chip); + if (t && !r) + r = t; +out: + return r; +} + +static int zd1211b_hw_reset_phy(struct zd_chip *chip) +{ + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR0, 0x14 }, { ZD_CR1, 0x06 }, { ZD_CR2, 0x26 }, + { ZD_CR3, 0x38 }, { ZD_CR4, 0x80 }, { ZD_CR9, 0xe0 }, + { ZD_CR10, 0x81 }, + /* power control { { ZD_CR11, 1 << 6 }, */ + { ZD_CR11, 0x00 }, + { ZD_CR12, 0xf0 }, { ZD_CR13, 0x8c }, { ZD_CR14, 0x80 }, + { ZD_CR15, 0x3d }, { ZD_CR16, 0x20 }, { ZD_CR17, 0x1e }, + { ZD_CR18, 0x0a }, { ZD_CR19, 0x48 }, + { ZD_CR20, 0x10 }, /* Org:0x0E, ComTrend:RalLink AP */ + { ZD_CR21, 0x0e }, { ZD_CR22, 0x23 }, { ZD_CR23, 0x90 }, + { ZD_CR24, 0x14 }, { ZD_CR25, 0x40 }, { ZD_CR26, 0x10 }, + { ZD_CR27, 0x10 }, { ZD_CR28, 0x7f }, { ZD_CR29, 0x80 }, + { ZD_CR30, 0x4b }, /* ASIC/FWT, no jointly decoder */ + { ZD_CR31, 0x60 }, { ZD_CR32, 0x43 }, { ZD_CR33, 0x08 }, + { ZD_CR34, 0x06 }, { ZD_CR35, 0x0a }, { ZD_CR36, 0x00 }, + { ZD_CR37, 0x00 }, { ZD_CR38, 0x38 }, { ZD_CR39, 0x0c }, + { ZD_CR40, 0x84 }, { ZD_CR41, 0x2a }, { ZD_CR42, 0x80 }, + { ZD_CR43, 0x10 }, { ZD_CR44, 0x33 }, { ZD_CR46, 0xff }, + { ZD_CR47, 0x1E }, { ZD_CR48, 0x26 }, { ZD_CR49, 0x5b }, + { ZD_CR64, 0xd0 }, { ZD_CR65, 0x04 }, { ZD_CR66, 0x58 }, + { ZD_CR67, 0xc9 }, { ZD_CR68, 0x88 }, { ZD_CR69, 0x41 }, + { ZD_CR70, 0x23 }, { ZD_CR71, 0x10 }, { ZD_CR72, 0xff }, + { ZD_CR73, 0x32 }, { ZD_CR74, 0x30 }, { ZD_CR75, 0x65 }, + { ZD_CR76, 0x41 }, { ZD_CR77, 0x1b }, { ZD_CR78, 0x30 }, + { ZD_CR79, 0xf0 }, { ZD_CR80, 0x64 }, { ZD_CR81, 0x64 }, + { ZD_CR82, 0x00 }, { ZD_CR83, 0x24 }, { ZD_CR84, 0x04 }, + { ZD_CR85, 0x00 }, { ZD_CR86, 0x0c }, { ZD_CR87, 0x12 }, + { ZD_CR88, 0x0c }, { ZD_CR89, 0x00 }, { ZD_CR90, 0x58 }, + { ZD_CR91, 0x04 }, { ZD_CR92, 0x00 }, { ZD_CR93, 0x00 }, + { ZD_CR94, 0x01 }, + { ZD_CR95, 0x20 }, /* ZD1211B */ + { ZD_CR96, 0x50 }, { ZD_CR97, 0x37 }, { ZD_CR98, 0x35 }, + { ZD_CR99, 0x00 }, { ZD_CR100, 0x01 }, { ZD_CR101, 0x13 }, + { ZD_CR102, 0x27 }, { ZD_CR103, 0x27 }, { ZD_CR104, 0x18 }, + { ZD_CR105, 0x12 }, { ZD_CR106, 0x04 }, { ZD_CR107, 0x00 }, + { ZD_CR108, 0x0a }, { ZD_CR109, 0x27 }, { ZD_CR110, 0x27 }, + { ZD_CR111, 0x27 }, { ZD_CR112, 0x27 }, { ZD_CR113, 0x27 }, + { ZD_CR114, 0x27 }, { ZD_CR115, 0x26 }, { ZD_CR116, 0x24 }, + { ZD_CR117, 0xfc }, { ZD_CR118, 0xfa }, { ZD_CR119, 0x1e }, + { ZD_CR125, 0x90 }, { ZD_CR126, 0x00 }, { ZD_CR127, 0x00 }, + { ZD_CR128, 0x14 }, { ZD_CR129, 0x12 }, { ZD_CR130, 0x10 }, + { ZD_CR131, 0x0c }, { ZD_CR136, 0xdf }, { ZD_CR137, 0xa0 }, + { ZD_CR138, 0xa8 }, { ZD_CR139, 0xb4 }, { ZD_CR140, 0x98 }, + { ZD_CR141, 0x82 }, { ZD_CR142, 0x53 }, { ZD_CR143, 0x1c }, + { ZD_CR144, 0x6c }, { ZD_CR147, 0x07 }, { ZD_CR148, 0x40 }, + { ZD_CR149, 0x40 }, /* Org:0x50 ComTrend:RalLink AP */ + { ZD_CR150, 0x14 }, /* Org:0x0E ComTrend:RalLink AP */ + { ZD_CR151, 0x18 }, { ZD_CR159, 0x70 }, { ZD_CR160, 0xfe }, + { ZD_CR161, 0xee }, { ZD_CR162, 0xaa }, { ZD_CR163, 0xfa }, + { ZD_CR164, 0xfa }, { ZD_CR165, 0xea }, { ZD_CR166, 0xbe }, + { ZD_CR167, 0xbe }, { ZD_CR168, 0x6a }, { ZD_CR169, 0xba }, + { ZD_CR170, 0xba }, { ZD_CR171, 0xba }, + /* Note: ZD_CR204 must lead the ZD_CR203 */ + { ZD_CR204, 0x7d }, + {}, + { ZD_CR203, 0x30 }, + }; + + int r, t; + + dev_dbg_f(zd_chip_dev(chip), "\n"); + + r = zd_chip_lock_phy_regs(chip); + if (r) + goto out; + + r = zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); + t = zd_chip_unlock_phy_regs(chip); + if (t && !r) + r = t; +out: + return r; +} + +static int hw_reset_phy(struct zd_chip *chip) +{ + return zd_chip_is_zd1211b(chip) ? zd1211b_hw_reset_phy(chip) : + zd1211_hw_reset_phy(chip); +} + +static int zd1211_hw_init_hmac(struct zd_chip *chip) +{ + static const struct zd_ioreq32 ioreqs[] = { + { CR_ZD1211_RETRY_MAX, ZD1211_RETRY_COUNT }, + { CR_RX_THRESHOLD, 0x000c0640 }, + }; + + dev_dbg_f(zd_chip_dev(chip), "\n"); + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + return zd_iowrite32a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +static int zd1211b_hw_init_hmac(struct zd_chip *chip) +{ + static const struct zd_ioreq32 ioreqs[] = { + { CR_ZD1211B_RETRY_MAX, ZD1211B_RETRY_COUNT }, + { CR_ZD1211B_CWIN_MAX_MIN_AC0, 0x007f003f }, + { CR_ZD1211B_CWIN_MAX_MIN_AC1, 0x007f003f }, + { CR_ZD1211B_CWIN_MAX_MIN_AC2, 0x003f001f }, + { CR_ZD1211B_CWIN_MAX_MIN_AC3, 0x001f000f }, + { CR_ZD1211B_AIFS_CTL1, 0x00280028 }, + { CR_ZD1211B_AIFS_CTL2, 0x008C003C }, + { CR_ZD1211B_TXOP, 0x01800824 }, + { CR_RX_THRESHOLD, 0x000c0eff, }, + }; + + dev_dbg_f(zd_chip_dev(chip), "\n"); + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + return zd_iowrite32a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +static int hw_init_hmac(struct zd_chip *chip) +{ + int r; + static const struct zd_ioreq32 ioreqs[] = { + { CR_ACK_TIMEOUT_EXT, 0x20 }, + { CR_ADDA_MBIAS_WARMTIME, 0x30000808 }, + { CR_SNIFFER_ON, 0 }, + { CR_RX_FILTER, STA_RX_FILTER }, + { CR_GROUP_HASH_P1, 0x00 }, + { CR_GROUP_HASH_P2, 0x80000000 }, + { CR_REG1, 0xa4 }, + { CR_ADDA_PWR_DWN, 0x7f }, + { CR_BCN_PLCP_CFG, 0x00f00401 }, + { CR_PHY_DELAY, 0x00 }, + { CR_ACK_TIMEOUT_EXT, 0x80 }, + { CR_ADDA_PWR_DWN, 0x00 }, + { CR_ACK_TIME_80211, 0x100 }, + { CR_RX_PE_DELAY, 0x70 }, + { CR_PS_CTRL, 0x10000000 }, + { CR_RTS_CTS_RATE, 0x02030203 }, + { CR_AFTER_PNP, 0x1 }, + { CR_WEP_PROTECT, 0x114 }, + { CR_IFS_VALUE, IFS_VALUE_DEFAULT }, + { CR_CAM_MODE, MODE_AP_WDS}, + }; + + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + r = zd_iowrite32a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); + if (r) + return r; + + return zd_chip_is_zd1211b(chip) ? + zd1211b_hw_init_hmac(chip) : zd1211_hw_init_hmac(chip); +} + +struct aw_pt_bi { + u32 atim_wnd_period; + u32 pre_tbtt; + u32 beacon_interval; +}; + +static int get_aw_pt_bi(struct zd_chip *chip, struct aw_pt_bi *s) +{ + int r; + static const zd_addr_t aw_pt_bi_addr[] = + { CR_ATIM_WND_PERIOD, CR_PRE_TBTT, CR_BCN_INTERVAL }; + u32 values[3]; + + r = zd_ioread32v_locked(chip, values, (const zd_addr_t *)aw_pt_bi_addr, + ARRAY_SIZE(aw_pt_bi_addr)); + if (r) { + memset(s, 0, sizeof(*s)); + return r; + } + + s->atim_wnd_period = values[0]; + s->pre_tbtt = values[1]; + s->beacon_interval = values[2]; + return 0; +} + +static int set_aw_pt_bi(struct zd_chip *chip, struct aw_pt_bi *s) +{ + struct zd_ioreq32 reqs[3]; + u16 b_interval = s->beacon_interval & 0xffff; + + if (b_interval <= 5) + b_interval = 5; + if (s->pre_tbtt < 4 || s->pre_tbtt >= b_interval) + s->pre_tbtt = b_interval - 1; + if (s->atim_wnd_period >= s->pre_tbtt) + s->atim_wnd_period = s->pre_tbtt - 1; + + reqs[0].addr = CR_ATIM_WND_PERIOD; + reqs[0].value = s->atim_wnd_period; + reqs[1].addr = CR_PRE_TBTT; + reqs[1].value = s->pre_tbtt; + reqs[2].addr = CR_BCN_INTERVAL; + reqs[2].value = (s->beacon_interval & ~0xffff) | b_interval; + + return zd_iowrite32a_locked(chip, reqs, ARRAY_SIZE(reqs)); +} + + +static int set_beacon_interval(struct zd_chip *chip, u16 interval, + u8 dtim_period, int type) +{ + int r; + struct aw_pt_bi s; + u32 b_interval, mode_flag; + + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + + if (interval > 0) { + switch (type) { + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_MESH_POINT: + mode_flag = BCN_MODE_IBSS; + break; + case NL80211_IFTYPE_AP: + mode_flag = BCN_MODE_AP; + break; + default: + mode_flag = 0; + break; + } + } else { + dtim_period = 0; + mode_flag = 0; + } + + b_interval = mode_flag | (dtim_period << 16) | interval; + + r = zd_iowrite32_locked(chip, b_interval, CR_BCN_INTERVAL); + if (r) + return r; + r = get_aw_pt_bi(chip, &s); + if (r) + return r; + return set_aw_pt_bi(chip, &s); +} + +int zd_set_beacon_interval(struct zd_chip *chip, u16 interval, u8 dtim_period, + int type) +{ + int r; + + mutex_lock(&chip->mutex); + r = set_beacon_interval(chip, interval, dtim_period, type); + mutex_unlock(&chip->mutex); + return r; +} + +static int hw_init(struct zd_chip *chip) +{ + int r; + + dev_dbg_f(zd_chip_dev(chip), "\n"); + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + r = hw_reset_phy(chip); + if (r) + return r; + + r = hw_init_hmac(chip); + if (r) + return r; + + return set_beacon_interval(chip, 100, 0, NL80211_IFTYPE_UNSPECIFIED); +} + +static zd_addr_t fw_reg_addr(struct zd_chip *chip, u16 offset) +{ + return (zd_addr_t)((u16)chip->fw_regs_base + offset); +} + +#ifdef DEBUG +static int dump_cr(struct zd_chip *chip, const zd_addr_t addr, + const char *addr_string) +{ + int r; + u32 value; + + r = zd_ioread32_locked(chip, &value, addr); + if (r) { + dev_dbg_f(zd_chip_dev(chip), + "error reading %s. Error number %d\n", addr_string, r); + return r; + } + + dev_dbg_f(zd_chip_dev(chip), "%s %#010x\n", + addr_string, (unsigned int)value); + return 0; +} + +static int test_init(struct zd_chip *chip) +{ + int r; + + r = dump_cr(chip, CR_AFTER_PNP, "CR_AFTER_PNP"); + if (r) + return r; + r = dump_cr(chip, CR_GPI_EN, "CR_GPI_EN"); + if (r) + return r; + return dump_cr(chip, CR_INTERRUPT, "CR_INTERRUPT"); +} + +static void dump_fw_registers(struct zd_chip *chip) +{ + const zd_addr_t addr[4] = { + fw_reg_addr(chip, FW_REG_FIRMWARE_VER), + fw_reg_addr(chip, FW_REG_USB_SPEED), + fw_reg_addr(chip, FW_REG_FIX_TX_RATE), + fw_reg_addr(chip, FW_REG_LED_LINK_STATUS), + }; + + int r; + u16 values[4]; + + r = zd_ioread16v_locked(chip, values, (const zd_addr_t*)addr, + ARRAY_SIZE(addr)); + if (r) { + dev_dbg_f(zd_chip_dev(chip), "error %d zd_ioread16v_locked\n", + r); + return; + } + + dev_dbg_f(zd_chip_dev(chip), "FW_FIRMWARE_VER %#06hx\n", values[0]); + dev_dbg_f(zd_chip_dev(chip), "FW_USB_SPEED %#06hx\n", values[1]); + dev_dbg_f(zd_chip_dev(chip), "FW_FIX_TX_RATE %#06hx\n", values[2]); + dev_dbg_f(zd_chip_dev(chip), "FW_LINK_STATUS %#06hx\n", values[3]); +} +#endif /* DEBUG */ + +static int print_fw_version(struct zd_chip *chip) +{ + struct wiphy *wiphy = zd_chip_to_mac(chip)->hw->wiphy; + int r; + u16 version; + + r = zd_ioread16_locked(chip, &version, + fw_reg_addr(chip, FW_REG_FIRMWARE_VER)); + if (r) + return r; + + dev_info(zd_chip_dev(chip),"firmware version %04hx\n", version); + + snprintf(wiphy->fw_version, sizeof(wiphy->fw_version), + "%04hx", version); + + return 0; +} + +static int set_mandatory_rates(struct zd_chip *chip, int gmode) +{ + u32 rates; + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + /* This sets the mandatory rates, which only depend from the standard + * that the device is supporting. Until further notice we should try + * to support 802.11g also for full speed USB. + */ + if (!gmode) + rates = CR_RATE_1M|CR_RATE_2M|CR_RATE_5_5M|CR_RATE_11M; + else + rates = CR_RATE_1M|CR_RATE_2M|CR_RATE_5_5M|CR_RATE_11M| + CR_RATE_6M|CR_RATE_12M|CR_RATE_24M; + + return zd_iowrite32_locked(chip, rates, CR_MANDATORY_RATE_TBL); +} + +int zd_chip_set_rts_cts_rate_locked(struct zd_chip *chip, + int preamble) +{ + u32 value = 0; + + dev_dbg_f(zd_chip_dev(chip), "preamble=%x\n", preamble); + value |= preamble << RTSCTS_SH_RTS_PMB_TYPE; + value |= preamble << RTSCTS_SH_CTS_PMB_TYPE; + + /* We always send 11M RTS/self-CTS messages, like the vendor driver. */ + value |= ZD_PURE_RATE(ZD_CCK_RATE_11M) << RTSCTS_SH_RTS_RATE; + value |= ZD_RX_CCK << RTSCTS_SH_RTS_MOD_TYPE; + value |= ZD_PURE_RATE(ZD_CCK_RATE_11M) << RTSCTS_SH_CTS_RATE; + value |= ZD_RX_CCK << RTSCTS_SH_CTS_MOD_TYPE; + + return zd_iowrite32_locked(chip, value, CR_RTS_CTS_RATE); +} + +int zd_chip_enable_hwint(struct zd_chip *chip) +{ + int r; + + mutex_lock(&chip->mutex); + r = zd_iowrite32_locked(chip, HWINT_ENABLED, CR_INTERRUPT); + mutex_unlock(&chip->mutex); + return r; +} + +static int disable_hwint(struct zd_chip *chip) +{ + return zd_iowrite32_locked(chip, HWINT_DISABLED, CR_INTERRUPT); +} + +int zd_chip_disable_hwint(struct zd_chip *chip) +{ + int r; + + mutex_lock(&chip->mutex); + r = disable_hwint(chip); + mutex_unlock(&chip->mutex); + return r; +} + +static int read_fw_regs_offset(struct zd_chip *chip) +{ + int r; + + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + r = zd_ioread16_locked(chip, (u16*)&chip->fw_regs_base, + FWRAW_REGS_ADDR); + if (r) + return r; + dev_dbg_f(zd_chip_dev(chip), "fw_regs_base: %#06hx\n", + (u16)chip->fw_regs_base); + + return 0; +} + +/* Read mac address using pre-firmware interface */ +int zd_chip_read_mac_addr_fw(struct zd_chip *chip, u8 *addr) +{ + dev_dbg_f(zd_chip_dev(chip), "\n"); + return zd_usb_read_fw(&chip->usb, E2P_MAC_ADDR_P1, addr, + ETH_ALEN); +} + +int zd_chip_init_hw(struct zd_chip *chip) +{ + int r; + u8 rf_type; + + dev_dbg_f(zd_chip_dev(chip), "\n"); + + mutex_lock(&chip->mutex); + +#ifdef DEBUG + r = test_init(chip); + if (r) + goto out; +#endif + r = zd_iowrite32_locked(chip, 1, CR_AFTER_PNP); + if (r) + goto out; + + r = read_fw_regs_offset(chip); + if (r) + goto out; + + /* GPI is always disabled, also in the other driver. + */ + r = zd_iowrite32_locked(chip, 0, CR_GPI_EN); + if (r) + goto out; + r = zd_iowrite32_locked(chip, CWIN_SIZE, CR_CWMIN_CWMAX); + if (r) + goto out; + /* Currently we support IEEE 802.11g for full and high speed USB. + * It might be discussed, whether we should support pure b mode for + * full speed USB. + */ + r = set_mandatory_rates(chip, 1); + if (r) + goto out; + /* Disabling interrupts is certainly a smart thing here. + */ + r = disable_hwint(chip); + if (r) + goto out; + r = read_pod(chip, &rf_type); + if (r) + goto out; + r = hw_init(chip); + if (r) + goto out; + r = zd_rf_init_hw(&chip->rf, rf_type); + if (r) + goto out; + + r = print_fw_version(chip); + if (r) + goto out; + +#ifdef DEBUG + dump_fw_registers(chip); + r = test_init(chip); + if (r) + goto out; +#endif /* DEBUG */ + + r = read_cal_int_tables(chip); + if (r) + goto out; + + print_id(chip); +out: + mutex_unlock(&chip->mutex); + return r; +} + +static int update_pwr_int(struct zd_chip *chip, u8 channel) +{ + u8 value = chip->pwr_int_values[channel - 1]; + return zd_iowrite16_locked(chip, value, ZD_CR31); +} + +static int update_pwr_cal(struct zd_chip *chip, u8 channel) +{ + u8 value = chip->pwr_cal_values[channel-1]; + return zd_iowrite16_locked(chip, value, ZD_CR68); +} + +static int update_ofdm_cal(struct zd_chip *chip, u8 channel) +{ + struct zd_ioreq16 ioreqs[3]; + + ioreqs[0].addr = ZD_CR67; + ioreqs[0].value = chip->ofdm_cal_values[OFDM_36M_INDEX][channel-1]; + ioreqs[1].addr = ZD_CR66; + ioreqs[1].value = chip->ofdm_cal_values[OFDM_48M_INDEX][channel-1]; + ioreqs[2].addr = ZD_CR65; + ioreqs[2].value = chip->ofdm_cal_values[OFDM_54M_INDEX][channel-1]; + + return zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +static int update_channel_integration_and_calibration(struct zd_chip *chip, + u8 channel) +{ + int r; + + if (!zd_rf_should_update_pwr_int(&chip->rf)) + return 0; + + r = update_pwr_int(chip, channel); + if (r) + return r; + if (zd_chip_is_zd1211b(chip)) { + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR69, 0x28 }, + {}, + { ZD_CR69, 0x2a }, + }; + + r = update_ofdm_cal(chip, channel); + if (r) + return r; + r = update_pwr_cal(chip, channel); + if (r) + return r; + r = zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); + if (r) + return r; + } + + return 0; +} + +/* The CCK baseband gain can be optionally patched by the EEPROM */ +static int patch_cck_gain(struct zd_chip *chip) +{ + int r; + u32 value; + + if (!chip->patch_cck_gain || !zd_rf_should_patch_cck_gain(&chip->rf)) + return 0; + + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + r = zd_ioread32_locked(chip, &value, E2P_PHY_REG); + if (r) + return r; + dev_dbg_f(zd_chip_dev(chip), "patching value %x\n", value & 0xff); + return zd_iowrite16_locked(chip, value & 0xff, ZD_CR47); +} + +int zd_chip_set_channel(struct zd_chip *chip, u8 channel) +{ + int r, t; + + mutex_lock(&chip->mutex); + r = zd_chip_lock_phy_regs(chip); + if (r) + goto out; + r = zd_rf_set_channel(&chip->rf, channel); + if (r) + goto unlock; + r = update_channel_integration_and_calibration(chip, channel); + if (r) + goto unlock; + r = patch_cck_gain(chip); + if (r) + goto unlock; + r = patch_6m_band_edge(chip, channel); + if (r) + goto unlock; + r = zd_iowrite32_locked(chip, 0, CR_CONFIG_PHILIPS); +unlock: + t = zd_chip_unlock_phy_regs(chip); + if (t && !r) + r = t; +out: + mutex_unlock(&chip->mutex); + return r; +} + +u8 zd_chip_get_channel(struct zd_chip *chip) +{ + u8 channel; + + mutex_lock(&chip->mutex); + channel = chip->rf.channel; + mutex_unlock(&chip->mutex); + return channel; +} + +int zd_chip_control_leds(struct zd_chip *chip, enum led_status status) +{ + const zd_addr_t a[] = { + fw_reg_addr(chip, FW_REG_LED_LINK_STATUS), + CR_LED, + }; + + int r; + u16 v[ARRAY_SIZE(a)]; + struct zd_ioreq16 ioreqs[ARRAY_SIZE(a)] = { + [0] = { fw_reg_addr(chip, FW_REG_LED_LINK_STATUS) }, + [1] = { CR_LED }, + }; + u16 other_led; + + mutex_lock(&chip->mutex); + r = zd_ioread16v_locked(chip, v, (const zd_addr_t *)a, ARRAY_SIZE(a)); + if (r) + goto out; + + other_led = chip->link_led == LED1 ? LED2 : LED1; + + switch (status) { + case ZD_LED_OFF: + ioreqs[0].value = FW_LINK_OFF; + ioreqs[1].value = v[1] & ~(LED1|LED2); + break; + case ZD_LED_SCANNING: + ioreqs[0].value = FW_LINK_OFF; + ioreqs[1].value = v[1] & ~other_led; + if (get_seconds() % 3 == 0) { + ioreqs[1].value &= ~chip->link_led; + } else { + ioreqs[1].value |= chip->link_led; + } + break; + case ZD_LED_ASSOCIATED: + ioreqs[0].value = FW_LINK_TX; + ioreqs[1].value = v[1] & ~other_led; + ioreqs[1].value |= chip->link_led; + break; + default: + r = -EINVAL; + goto out; + } + + if (v[0] != ioreqs[0].value || v[1] != ioreqs[1].value) { + r = zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); + if (r) + goto out; + } + r = 0; +out: + mutex_unlock(&chip->mutex); + return r; +} + +int zd_chip_set_basic_rates(struct zd_chip *chip, u16 cr_rates) +{ + int r; + + if (cr_rates & ~(CR_RATES_80211B|CR_RATES_80211G)) + return -EINVAL; + + mutex_lock(&chip->mutex); + r = zd_iowrite32_locked(chip, cr_rates, CR_BASIC_RATE_TBL); + mutex_unlock(&chip->mutex); + return r; +} + +static inline u8 zd_rate_from_ofdm_plcp_header(const void *rx_frame) +{ + return ZD_OFDM | zd_ofdm_plcp_header_rate(rx_frame); +} + +/** + * zd_rx_rate - report zd-rate + * @rx_frame - received frame + * @rx_status - rx_status as given by the device + * + * This function converts the rate as encoded in the received packet to the + * zd-rate, we are using on other places in the driver. + */ +u8 zd_rx_rate(const void *rx_frame, const struct rx_status *status) +{ + u8 zd_rate; + if (status->frame_status & ZD_RX_OFDM) { + zd_rate = zd_rate_from_ofdm_plcp_header(rx_frame); + } else { + switch (zd_cck_plcp_header_signal(rx_frame)) { + case ZD_CCK_PLCP_SIGNAL_1M: + zd_rate = ZD_CCK_RATE_1M; + break; + case ZD_CCK_PLCP_SIGNAL_2M: + zd_rate = ZD_CCK_RATE_2M; + break; + case ZD_CCK_PLCP_SIGNAL_5M5: + zd_rate = ZD_CCK_RATE_5_5M; + break; + case ZD_CCK_PLCP_SIGNAL_11M: + zd_rate = ZD_CCK_RATE_11M; + break; + default: + zd_rate = 0; + } + } + + return zd_rate; +} + +int zd_chip_switch_radio_on(struct zd_chip *chip) +{ + int r; + + mutex_lock(&chip->mutex); + r = zd_switch_radio_on(&chip->rf); + mutex_unlock(&chip->mutex); + return r; +} + +int zd_chip_switch_radio_off(struct zd_chip *chip) +{ + int r; + + mutex_lock(&chip->mutex); + r = zd_switch_radio_off(&chip->rf); + mutex_unlock(&chip->mutex); + return r; +} + +int zd_chip_enable_int(struct zd_chip *chip) +{ + int r; + + mutex_lock(&chip->mutex); + r = zd_usb_enable_int(&chip->usb); + mutex_unlock(&chip->mutex); + return r; +} + +void zd_chip_disable_int(struct zd_chip *chip) +{ + mutex_lock(&chip->mutex); + zd_usb_disable_int(&chip->usb); + mutex_unlock(&chip->mutex); + + /* cancel pending interrupt work */ + cancel_work_sync(&zd_chip_to_mac(chip)->process_intr); +} + +int zd_chip_enable_rxtx(struct zd_chip *chip) +{ + int r; + + mutex_lock(&chip->mutex); + zd_usb_enable_tx(&chip->usb); + r = zd_usb_enable_rx(&chip->usb); + zd_tx_watchdog_enable(&chip->usb); + mutex_unlock(&chip->mutex); + return r; +} + +void zd_chip_disable_rxtx(struct zd_chip *chip) +{ + mutex_lock(&chip->mutex); + zd_tx_watchdog_disable(&chip->usb); + zd_usb_disable_rx(&chip->usb); + zd_usb_disable_tx(&chip->usb); + mutex_unlock(&chip->mutex); +} + +int zd_rfwritev_locked(struct zd_chip *chip, + const u32* values, unsigned int count, u8 bits) +{ + int r; + unsigned int i; + + for (i = 0; i < count; i++) { + r = zd_rfwrite_locked(chip, values[i], bits); + if (r) + return r; + } + + return 0; +} + +/* + * We can optionally program the RF directly through CR regs, if supported by + * the hardware. This is much faster than the older method. + */ +int zd_rfwrite_cr_locked(struct zd_chip *chip, u32 value) +{ + const struct zd_ioreq16 ioreqs[] = { + { ZD_CR244, (value >> 16) & 0xff }, + { ZD_CR243, (value >> 8) & 0xff }, + { ZD_CR242, value & 0xff }, + }; + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + return zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +int zd_rfwritev_cr_locked(struct zd_chip *chip, + const u32 *values, unsigned int count) +{ + int r; + unsigned int i; + + for (i = 0; i < count; i++) { + r = zd_rfwrite_cr_locked(chip, values[i]); + if (r) + return r; + } + + return 0; +} + +int zd_chip_set_multicast_hash(struct zd_chip *chip, + struct zd_mc_hash *hash) +{ + const struct zd_ioreq32 ioreqs[] = { + { CR_GROUP_HASH_P1, hash->low }, + { CR_GROUP_HASH_P2, hash->high }, + }; + + return zd_iowrite32a(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +u64 zd_chip_get_tsf(struct zd_chip *chip) +{ + int r; + static const zd_addr_t aw_pt_bi_addr[] = + { CR_TSF_LOW_PART, CR_TSF_HIGH_PART }; + u32 values[2]; + u64 tsf; + + mutex_lock(&chip->mutex); + r = zd_ioread32v_locked(chip, values, (const zd_addr_t *)aw_pt_bi_addr, + ARRAY_SIZE(aw_pt_bi_addr)); + mutex_unlock(&chip->mutex); + if (r) + return 0; + + tsf = values[1]; + tsf = (tsf << 32) | values[0]; + + return tsf; +} diff --git a/drivers/net/wireless/zd1211rw/zd_chip.h b/drivers/net/wireless/zd1211rw/zd_chip.h new file mode 100644 index 0000000..7ab9222 --- /dev/null +++ b/drivers/net/wireless/zd1211rw/zd_chip.h @@ -0,0 +1,984 @@ +/* ZD1211 USB-WLAN driver for Linux + * + * Copyright (C) 2005-2007 Ulrich Kunitz + * Copyright (C) 2006-2007 Daniel Drake + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ZD_CHIP_H +#define _ZD_CHIP_H + +#include + +#include "zd_rf.h" +#include "zd_usb.h" + +/* Header for the Media Access Controller (MAC) and the Baseband Processor + * (BBP). It appears that the ZD1211 wraps the old ZD1205 with USB glue and + * adds a processor for handling the USB protocol. + */ + +/* Address space */ +enum { + /* CONTROL REGISTERS */ + CR_START = 0x9000, + + + /* FIRMWARE */ + FW_START = 0xee00, + + + /* EEPROM */ + E2P_START = 0xf800, + E2P_LEN = 0x800, + + /* EEPROM layout */ + E2P_LOAD_CODE_LEN = 0xe, /* base 0xf800 */ + E2P_LOAD_VECT_LEN = 0x9, /* base 0xf80e */ + /* E2P_DATA indexes into this */ + E2P_DATA_LEN = 0x7e, /* base 0xf817 */ + E2P_BOOT_CODE_LEN = 0x760, /* base 0xf895 */ + E2P_INTR_VECT_LEN = 0xb, /* base 0xfff5 */ + + /* Some precomputed offsets into the EEPROM */ + E2P_DATA_OFFSET = E2P_LOAD_CODE_LEN + E2P_LOAD_VECT_LEN, + E2P_BOOT_CODE_OFFSET = E2P_DATA_OFFSET + E2P_DATA_LEN, +}; + +#define CTL_REG(offset) ((zd_addr_t)(CR_START + (offset))) +#define E2P_DATA(offset) ((zd_addr_t)(E2P_START + E2P_DATA_OFFSET + (offset))) +#define FWRAW_DATA(offset) ((zd_addr_t)(FW_START + (offset))) + +/* 8-bit hardware registers */ +#define ZD_CR0 CTL_REG(0x0000) +#define ZD_CR1 CTL_REG(0x0004) +#define ZD_CR2 CTL_REG(0x0008) +#define ZD_CR3 CTL_REG(0x000C) + +#define ZD_CR5 CTL_REG(0x0010) +/* bit 5: if set short preamble used + * bit 6: filter band - Japan channel 14 on, else off + */ +#define ZD_CR6 CTL_REG(0x0014) +#define ZD_CR7 CTL_REG(0x0018) +#define ZD_CR8 CTL_REG(0x001C) + +#define ZD_CR4 CTL_REG(0x0020) + +#define ZD_CR9 CTL_REG(0x0024) +/* bit 2: antenna switch (together with ZD_CR10) */ +#define ZD_CR10 CTL_REG(0x0028) +/* bit 1: antenna switch (together with ZD_CR9) + * RF2959 controls with ZD_CR11 radion on and off + */ +#define ZD_CR11 CTL_REG(0x002C) +/* bit 6: TX power control for OFDM + * RF2959 controls with ZD_CR10 radio on and off + */ +#define ZD_CR12 CTL_REG(0x0030) +#define ZD_CR13 CTL_REG(0x0034) +#define ZD_CR14 CTL_REG(0x0038) +#define ZD_CR15 CTL_REG(0x003C) +#define ZD_CR16 CTL_REG(0x0040) +#define ZD_CR17 CTL_REG(0x0044) +#define ZD_CR18 CTL_REG(0x0048) +#define ZD_CR19 CTL_REG(0x004C) +#define ZD_CR20 CTL_REG(0x0050) +#define ZD_CR21 CTL_REG(0x0054) +#define ZD_CR22 CTL_REG(0x0058) +#define ZD_CR23 CTL_REG(0x005C) +#define ZD_CR24 CTL_REG(0x0060) /* CCA threshold */ +#define ZD_CR25 CTL_REG(0x0064) +#define ZD_CR26 CTL_REG(0x0068) +#define ZD_CR27 CTL_REG(0x006C) +#define ZD_CR28 CTL_REG(0x0070) +#define ZD_CR29 CTL_REG(0x0074) +#define ZD_CR30 CTL_REG(0x0078) +#define ZD_CR31 CTL_REG(0x007C) /* TX power control for RF in + * CCK mode + */ +#define ZD_CR32 CTL_REG(0x0080) +#define ZD_CR33 CTL_REG(0x0084) +#define ZD_CR34 CTL_REG(0x0088) +#define ZD_CR35 CTL_REG(0x008C) +#define ZD_CR36 CTL_REG(0x0090) +#define ZD_CR37 CTL_REG(0x0094) +#define ZD_CR38 CTL_REG(0x0098) +#define ZD_CR39 CTL_REG(0x009C) +#define ZD_CR40 CTL_REG(0x00A0) +#define ZD_CR41 CTL_REG(0x00A4) +#define ZD_CR42 CTL_REG(0x00A8) +#define ZD_CR43 CTL_REG(0x00AC) +#define ZD_CR44 CTL_REG(0x00B0) +#define ZD_CR45 CTL_REG(0x00B4) +#define ZD_CR46 CTL_REG(0x00B8) +#define ZD_CR47 CTL_REG(0x00BC) /* CCK baseband gain + * (patch value might be in EEPROM) + */ +#define ZD_CR48 CTL_REG(0x00C0) +#define ZD_CR49 CTL_REG(0x00C4) +#define ZD_CR50 CTL_REG(0x00C8) +#define ZD_CR51 CTL_REG(0x00CC) /* TX power control for RF in + * 6-36M modes + */ +#define ZD_CR52 CTL_REG(0x00D0) /* TX power control for RF in + * 48M mode + */ +#define ZD_CR53 CTL_REG(0x00D4) /* TX power control for RF in + * 54M mode + */ +#define ZD_CR54 CTL_REG(0x00D8) +#define ZD_CR55 CTL_REG(0x00DC) +#define ZD_CR56 CTL_REG(0x00E0) +#define ZD_CR57 CTL_REG(0x00E4) +#define ZD_CR58 CTL_REG(0x00E8) +#define ZD_CR59 CTL_REG(0x00EC) +#define ZD_CR60 CTL_REG(0x00F0) +#define ZD_CR61 CTL_REG(0x00F4) +#define ZD_CR62 CTL_REG(0x00F8) +#define ZD_CR63 CTL_REG(0x00FC) +#define ZD_CR64 CTL_REG(0x0100) +#define ZD_CR65 CTL_REG(0x0104) /* OFDM 54M calibration */ +#define ZD_CR66 CTL_REG(0x0108) /* OFDM 48M calibration */ +#define ZD_CR67 CTL_REG(0x010C) /* OFDM 36M calibration */ +#define ZD_CR68 CTL_REG(0x0110) /* CCK calibration */ +#define ZD_CR69 CTL_REG(0x0114) +#define ZD_CR70 CTL_REG(0x0118) +#define ZD_CR71 CTL_REG(0x011C) +#define ZD_CR72 CTL_REG(0x0120) +#define ZD_CR73 CTL_REG(0x0124) +#define ZD_CR74 CTL_REG(0x0128) +#define ZD_CR75 CTL_REG(0x012C) +#define ZD_CR76 CTL_REG(0x0130) +#define ZD_CR77 CTL_REG(0x0134) +#define ZD_CR78 CTL_REG(0x0138) +#define ZD_CR79 CTL_REG(0x013C) +#define ZD_CR80 CTL_REG(0x0140) +#define ZD_CR81 CTL_REG(0x0144) +#define ZD_CR82 CTL_REG(0x0148) +#define ZD_CR83 CTL_REG(0x014C) +#define ZD_CR84 CTL_REG(0x0150) +#define ZD_CR85 CTL_REG(0x0154) +#define ZD_CR86 CTL_REG(0x0158) +#define ZD_CR87 CTL_REG(0x015C) +#define ZD_CR88 CTL_REG(0x0160) +#define ZD_CR89 CTL_REG(0x0164) +#define ZD_CR90 CTL_REG(0x0168) +#define ZD_CR91 CTL_REG(0x016C) +#define ZD_CR92 CTL_REG(0x0170) +#define ZD_CR93 CTL_REG(0x0174) +#define ZD_CR94 CTL_REG(0x0178) +#define ZD_CR95 CTL_REG(0x017C) +#define ZD_CR96 CTL_REG(0x0180) +#define ZD_CR97 CTL_REG(0x0184) +#define ZD_CR98 CTL_REG(0x0188) +#define ZD_CR99 CTL_REG(0x018C) +#define ZD_CR100 CTL_REG(0x0190) +#define ZD_CR101 CTL_REG(0x0194) +#define ZD_CR102 CTL_REG(0x0198) +#define ZD_CR103 CTL_REG(0x019C) +#define ZD_CR104 CTL_REG(0x01A0) +#define ZD_CR105 CTL_REG(0x01A4) +#define ZD_CR106 CTL_REG(0x01A8) +#define ZD_CR107 CTL_REG(0x01AC) +#define ZD_CR108 CTL_REG(0x01B0) +#define ZD_CR109 CTL_REG(0x01B4) +#define ZD_CR110 CTL_REG(0x01B8) +#define ZD_CR111 CTL_REG(0x01BC) +#define ZD_CR112 CTL_REG(0x01C0) +#define ZD_CR113 CTL_REG(0x01C4) +#define ZD_CR114 CTL_REG(0x01C8) +#define ZD_CR115 CTL_REG(0x01CC) +#define ZD_CR116 CTL_REG(0x01D0) +#define ZD_CR117 CTL_REG(0x01D4) +#define ZD_CR118 CTL_REG(0x01D8) +#define ZD_CR119 CTL_REG(0x01DC) +#define ZD_CR120 CTL_REG(0x01E0) +#define ZD_CR121 CTL_REG(0x01E4) +#define ZD_CR122 CTL_REG(0x01E8) +#define ZD_CR123 CTL_REG(0x01EC) +#define ZD_CR124 CTL_REG(0x01F0) +#define ZD_CR125 CTL_REG(0x01F4) +#define ZD_CR126 CTL_REG(0x01F8) +#define ZD_CR127 CTL_REG(0x01FC) +#define ZD_CR128 CTL_REG(0x0200) +#define ZD_CR129 CTL_REG(0x0204) +#define ZD_CR130 CTL_REG(0x0208) +#define ZD_CR131 CTL_REG(0x020C) +#define ZD_CR132 CTL_REG(0x0210) +#define ZD_CR133 CTL_REG(0x0214) +#define ZD_CR134 CTL_REG(0x0218) +#define ZD_CR135 CTL_REG(0x021C) +#define ZD_CR136 CTL_REG(0x0220) +#define ZD_CR137 CTL_REG(0x0224) +#define ZD_CR138 CTL_REG(0x0228) +#define ZD_CR139 CTL_REG(0x022C) +#define ZD_CR140 CTL_REG(0x0230) +#define ZD_CR141 CTL_REG(0x0234) +#define ZD_CR142 CTL_REG(0x0238) +#define ZD_CR143 CTL_REG(0x023C) +#define ZD_CR144 CTL_REG(0x0240) +#define ZD_CR145 CTL_REG(0x0244) +#define ZD_CR146 CTL_REG(0x0248) +#define ZD_CR147 CTL_REG(0x024C) +#define ZD_CR148 CTL_REG(0x0250) +#define ZD_CR149 CTL_REG(0x0254) +#define ZD_CR150 CTL_REG(0x0258) +#define ZD_CR151 CTL_REG(0x025C) +#define ZD_CR152 CTL_REG(0x0260) +#define ZD_CR153 CTL_REG(0x0264) +#define ZD_CR154 CTL_REG(0x0268) +#define ZD_CR155 CTL_REG(0x026C) +#define ZD_CR156 CTL_REG(0x0270) +#define ZD_CR157 CTL_REG(0x0274) +#define ZD_CR158 CTL_REG(0x0278) +#define ZD_CR159 CTL_REG(0x027C) +#define ZD_CR160 CTL_REG(0x0280) +#define ZD_CR161 CTL_REG(0x0284) +#define ZD_CR162 CTL_REG(0x0288) +#define ZD_CR163 CTL_REG(0x028C) +#define ZD_CR164 CTL_REG(0x0290) +#define ZD_CR165 CTL_REG(0x0294) +#define ZD_CR166 CTL_REG(0x0298) +#define ZD_CR167 CTL_REG(0x029C) +#define ZD_CR168 CTL_REG(0x02A0) +#define ZD_CR169 CTL_REG(0x02A4) +#define ZD_CR170 CTL_REG(0x02A8) +#define ZD_CR171 CTL_REG(0x02AC) +#define ZD_CR172 CTL_REG(0x02B0) +#define ZD_CR173 CTL_REG(0x02B4) +#define ZD_CR174 CTL_REG(0x02B8) +#define ZD_CR175 CTL_REG(0x02BC) +#define ZD_CR176 CTL_REG(0x02C0) +#define ZD_CR177 CTL_REG(0x02C4) +#define ZD_CR178 CTL_REG(0x02C8) +#define ZD_CR179 CTL_REG(0x02CC) +#define ZD_CR180 CTL_REG(0x02D0) +#define ZD_CR181 CTL_REG(0x02D4) +#define ZD_CR182 CTL_REG(0x02D8) +#define ZD_CR183 CTL_REG(0x02DC) +#define ZD_CR184 CTL_REG(0x02E0) +#define ZD_CR185 CTL_REG(0x02E4) +#define ZD_CR186 CTL_REG(0x02E8) +#define ZD_CR187 CTL_REG(0x02EC) +#define ZD_CR188 CTL_REG(0x02F0) +#define ZD_CR189 CTL_REG(0x02F4) +#define ZD_CR190 CTL_REG(0x02F8) +#define ZD_CR191 CTL_REG(0x02FC) +#define ZD_CR192 CTL_REG(0x0300) +#define ZD_CR193 CTL_REG(0x0304) +#define ZD_CR194 CTL_REG(0x0308) +#define ZD_CR195 CTL_REG(0x030C) +#define ZD_CR196 CTL_REG(0x0310) +#define ZD_CR197 CTL_REG(0x0314) +#define ZD_CR198 CTL_REG(0x0318) +#define ZD_CR199 CTL_REG(0x031C) +#define ZD_CR200 CTL_REG(0x0320) +#define ZD_CR201 CTL_REG(0x0324) +#define ZD_CR202 CTL_REG(0x0328) +#define ZD_CR203 CTL_REG(0x032C) /* I2C bus template value & flash + * control + */ +#define ZD_CR204 CTL_REG(0x0330) +#define ZD_CR205 CTL_REG(0x0334) +#define ZD_CR206 CTL_REG(0x0338) +#define ZD_CR207 CTL_REG(0x033C) +#define ZD_CR208 CTL_REG(0x0340) +#define ZD_CR209 CTL_REG(0x0344) +#define ZD_CR210 CTL_REG(0x0348) +#define ZD_CR211 CTL_REG(0x034C) +#define ZD_CR212 CTL_REG(0x0350) +#define ZD_CR213 CTL_REG(0x0354) +#define ZD_CR214 CTL_REG(0x0358) +#define ZD_CR215 CTL_REG(0x035C) +#define ZD_CR216 CTL_REG(0x0360) +#define ZD_CR217 CTL_REG(0x0364) +#define ZD_CR218 CTL_REG(0x0368) +#define ZD_CR219 CTL_REG(0x036C) +#define ZD_CR220 CTL_REG(0x0370) +#define ZD_CR221 CTL_REG(0x0374) +#define ZD_CR222 CTL_REG(0x0378) +#define ZD_CR223 CTL_REG(0x037C) +#define ZD_CR224 CTL_REG(0x0380) +#define ZD_CR225 CTL_REG(0x0384) +#define ZD_CR226 CTL_REG(0x0388) +#define ZD_CR227 CTL_REG(0x038C) +#define ZD_CR228 CTL_REG(0x0390) +#define ZD_CR229 CTL_REG(0x0394) +#define ZD_CR230 CTL_REG(0x0398) +#define ZD_CR231 CTL_REG(0x039C) +#define ZD_CR232 CTL_REG(0x03A0) +#define ZD_CR233 CTL_REG(0x03A4) +#define ZD_CR234 CTL_REG(0x03A8) +#define ZD_CR235 CTL_REG(0x03AC) +#define ZD_CR236 CTL_REG(0x03B0) + +#define ZD_CR240 CTL_REG(0x03C0) +/* bit 7: host-controlled RF register writes + * ZD_CR241-ZD_CR245: for hardware controlled writing of RF bits, not needed for + * USB + */ +#define ZD_CR241 CTL_REG(0x03C4) +#define ZD_CR242 CTL_REG(0x03C8) +#define ZD_CR243 CTL_REG(0x03CC) +#define ZD_CR244 CTL_REG(0x03D0) +#define ZD_CR245 CTL_REG(0x03D4) + +#define ZD_CR251 CTL_REG(0x03EC) /* only used for activation and + * deactivation of Airoha RFs AL2230 + * and AL7230B + */ +#define ZD_CR252 CTL_REG(0x03F0) +#define ZD_CR253 CTL_REG(0x03F4) +#define ZD_CR254 CTL_REG(0x03F8) +#define ZD_CR255 CTL_REG(0x03FC) + +#define CR_MAX_PHY_REG 255 + +/* Taken from the ZYDAS driver, not all of them are relevant for the ZD1211 + * driver. + */ + +#define CR_RF_IF_CLK CTL_REG(0x0400) +#define CR_RF_IF_DATA CTL_REG(0x0404) +#define CR_PE1_PE2 CTL_REG(0x0408) +#define CR_PE2_DLY CTL_REG(0x040C) +#define CR_LE1 CTL_REG(0x0410) +#define CR_LE2 CTL_REG(0x0414) +/* Seems to enable/disable GPI (General Purpose IO?) */ +#define CR_GPI_EN CTL_REG(0x0418) +#define CR_RADIO_PD CTL_REG(0x042C) +#define CR_RF2948_PD CTL_REG(0x042C) +#define CR_ENABLE_PS_MANUAL_AGC CTL_REG(0x043C) +#define CR_CONFIG_PHILIPS CTL_REG(0x0440) +#define CR_SA2400_SER_AP CTL_REG(0x0444) +#define CR_I2C_WRITE CTL_REG(0x0444) +#define CR_SA2400_SER_RP CTL_REG(0x0448) +#define CR_RADIO_PE CTL_REG(0x0458) +#define CR_RST_BUS_MASTER CTL_REG(0x045C) +#define CR_RFCFG CTL_REG(0x0464) +#define CR_HSTSCHG CTL_REG(0x046C) +#define CR_PHY_ON CTL_REG(0x0474) +#define CR_RX_DELAY CTL_REG(0x0478) +#define CR_RX_PE_DELAY CTL_REG(0x047C) +#define CR_GPIO_1 CTL_REG(0x0490) +#define CR_GPIO_2 CTL_REG(0x0494) +#define CR_EncryBufMux CTL_REG(0x04A8) +#define CR_PS_CTRL CTL_REG(0x0500) +#define CR_ADDA_PWR_DWN CTL_REG(0x0504) +#define CR_ADDA_MBIAS_WARMTIME CTL_REG(0x0508) +#define CR_MAC_PS_STATE CTL_REG(0x050C) + +#define CR_INTERRUPT CTL_REG(0x0510) +#define INT_TX_COMPLETE (1 << 0) +#define INT_RX_COMPLETE (1 << 1) +#define INT_RETRY_FAIL (1 << 2) +#define INT_WAKEUP (1 << 3) +#define INT_DTIM_NOTIFY (1 << 5) +#define INT_CFG_NEXT_BCN (1 << 6) +#define INT_BUS_ABORT (1 << 7) +#define INT_TX_FIFO_READY (1 << 8) +#define INT_UART (1 << 9) +#define INT_TX_COMPLETE_EN (1 << 16) +#define INT_RX_COMPLETE_EN (1 << 17) +#define INT_RETRY_FAIL_EN (1 << 18) +#define INT_WAKEUP_EN (1 << 19) +#define INT_DTIM_NOTIFY_EN (1 << 21) +#define INT_CFG_NEXT_BCN_EN (1 << 22) +#define INT_BUS_ABORT_EN (1 << 23) +#define INT_TX_FIFO_READY_EN (1 << 24) +#define INT_UART_EN (1 << 25) + +#define CR_TSF_LOW_PART CTL_REG(0x0514) +#define CR_TSF_HIGH_PART CTL_REG(0x0518) + +/* Following three values are in time units (1024us) + * Following condition must be met: + * atim < tbtt < bcn + */ +#define CR_ATIM_WND_PERIOD CTL_REG(0x051C) +#define CR_BCN_INTERVAL CTL_REG(0x0520) +#define CR_PRE_TBTT CTL_REG(0x0524) +/* in units of TU(1024us) */ + +/* for UART support */ +#define CR_UART_RBR_THR_DLL CTL_REG(0x0540) +#define CR_UART_DLM_IER CTL_REG(0x0544) +#define CR_UART_IIR_FCR CTL_REG(0x0548) +#define CR_UART_LCR CTL_REG(0x054c) +#define CR_UART_MCR CTL_REG(0x0550) +#define CR_UART_LSR CTL_REG(0x0554) +#define CR_UART_MSR CTL_REG(0x0558) +#define CR_UART_ECR CTL_REG(0x055c) +#define CR_UART_STATUS CTL_REG(0x0560) + +#define CR_PCI_TX_ADDR_P1 CTL_REG(0x0600) +#define CR_PCI_TX_AddR_P2 CTL_REG(0x0604) +#define CR_PCI_RX_AddR_P1 CTL_REG(0x0608) +#define CR_PCI_RX_AddR_P2 CTL_REG(0x060C) + +/* must be overwritten if custom MAC address will be used */ +#define CR_MAC_ADDR_P1 CTL_REG(0x0610) +#define CR_MAC_ADDR_P2 CTL_REG(0x0614) +#define CR_BSSID_P1 CTL_REG(0x0618) +#define CR_BSSID_P2 CTL_REG(0x061C) +#define CR_BCN_PLCP_CFG CTL_REG(0x0620) + +/* Group hash table for filtering incoming packets. + * + * The group hash table is 64 bit large and split over two parts. The first + * part is the lower part. The upper 6 bits of the last byte of the target + * address are used as index. Packets are received if the hash table bit is + * set. This is used for multicast handling, but for broadcasts (address + * ff:ff:ff:ff:ff:ff) the highest bit in the second table must also be set. + */ +#define CR_GROUP_HASH_P1 CTL_REG(0x0624) +#define CR_GROUP_HASH_P2 CTL_REG(0x0628) + +#define CR_RX_TIMEOUT CTL_REG(0x062C) + +/* Basic rates supported by the BSS. When producing ACK or CTS messages, the + * device will use a rate in this table that is less than or equal to the rate + * of the incoming frame which prompted the response. */ +#define CR_BASIC_RATE_TBL CTL_REG(0x0630) +#define CR_RATE_1M (1 << 0) /* 802.11b */ +#define CR_RATE_2M (1 << 1) /* 802.11b */ +#define CR_RATE_5_5M (1 << 2) /* 802.11b */ +#define CR_RATE_11M (1 << 3) /* 802.11b */ +#define CR_RATE_6M (1 << 8) /* 802.11g */ +#define CR_RATE_9M (1 << 9) /* 802.11g */ +#define CR_RATE_12M (1 << 10) /* 802.11g */ +#define CR_RATE_18M (1 << 11) /* 802.11g */ +#define CR_RATE_24M (1 << 12) /* 802.11g */ +#define CR_RATE_36M (1 << 13) /* 802.11g */ +#define CR_RATE_48M (1 << 14) /* 802.11g */ +#define CR_RATE_54M (1 << 15) /* 802.11g */ +#define CR_RATES_80211G 0xff00 +#define CR_RATES_80211B 0x000f + +/* Mandatory rates required in the BSS. When producing ACK or CTS messages, if + * the device could not find an appropriate rate in CR_BASIC_RATE_TBL, it will + * look for a rate in this table that is less than or equal to the rate of + * the incoming frame. */ +#define CR_MANDATORY_RATE_TBL CTL_REG(0x0634) +#define CR_RTS_CTS_RATE CTL_REG(0x0638) + +/* These are all bit indexes in CR_RTS_CTS_RATE, so remember to shift. */ +#define RTSCTS_SH_RTS_RATE 0 +#define RTSCTS_SH_EXP_CTS_RATE 4 +#define RTSCTS_SH_RTS_MOD_TYPE 8 +#define RTSCTS_SH_RTS_PMB_TYPE 9 +#define RTSCTS_SH_CTS_RATE 16 +#define RTSCTS_SH_CTS_MOD_TYPE 24 +#define RTSCTS_SH_CTS_PMB_TYPE 25 + +#define CR_WEP_PROTECT CTL_REG(0x063C) +#define CR_RX_THRESHOLD CTL_REG(0x0640) + +/* register for controlling the LEDS */ +#define CR_LED CTL_REG(0x0644) +/* masks for controlling LEDs */ +#define LED1 (1 << 8) +#define LED2 (1 << 9) +#define LED_SW (1 << 10) + +/* Seems to indicate that the configuration is over. + */ +#define CR_AFTER_PNP CTL_REG(0x0648) +#define CR_ACK_TIME_80211 CTL_REG(0x0658) + +#define CR_RX_OFFSET CTL_REG(0x065c) + +#define CR_BCN_LENGTH CTL_REG(0x0664) +#define CR_PHY_DELAY CTL_REG(0x066C) +#define CR_BCN_FIFO CTL_REG(0x0670) +#define CR_SNIFFER_ON CTL_REG(0x0674) + +#define CR_ENCRYPTION_TYPE CTL_REG(0x0678) +#define NO_WEP 0 +#define WEP64 1 +#define WEP128 5 +#define WEP256 6 +#define ENC_SNIFFER 8 + +#define CR_ZD1211_RETRY_MAX CTL_REG(0x067C) + +#define CR_REG1 CTL_REG(0x0680) +/* Setting the bit UNLOCK_PHY_REGS disallows the write access to physical + * registers, so one could argue it is a LOCK bit. But calling it + * LOCK_PHY_REGS makes it confusing. + */ +#define UNLOCK_PHY_REGS (1 << 7) + +#define CR_DEVICE_STATE CTL_REG(0x0684) +#define CR_UNDERRUN_CNT CTL_REG(0x0688) + +#define CR_RX_FILTER CTL_REG(0x068c) +#define RX_FILTER_ASSOC_REQUEST (1 << 0) +#define RX_FILTER_ASSOC_RESPONSE (1 << 1) +#define RX_FILTER_REASSOC_REQUEST (1 << 2) +#define RX_FILTER_REASSOC_RESPONSE (1 << 3) +#define RX_FILTER_PROBE_REQUEST (1 << 4) +#define RX_FILTER_PROBE_RESPONSE (1 << 5) +/* bits 6 and 7 reserved */ +#define RX_FILTER_BEACON (1 << 8) +#define RX_FILTER_ATIM (1 << 9) +#define RX_FILTER_DISASSOC (1 << 10) +#define RX_FILTER_AUTH (1 << 11) +#define RX_FILTER_DEAUTH (1 << 12) +#define RX_FILTER_PSPOLL (1 << 26) +#define RX_FILTER_RTS (1 << 27) +#define RX_FILTER_CTS (1 << 28) +#define RX_FILTER_ACK (1 << 29) +#define RX_FILTER_CFEND (1 << 30) +#define RX_FILTER_CFACK (1 << 31) + +/* Enable bits for all frames you are interested in. */ +#define STA_RX_FILTER (RX_FILTER_ASSOC_REQUEST | RX_FILTER_ASSOC_RESPONSE | \ + RX_FILTER_REASSOC_REQUEST | RX_FILTER_REASSOC_RESPONSE | \ + RX_FILTER_PROBE_REQUEST | RX_FILTER_PROBE_RESPONSE | \ + (0x3 << 6) /* vendor driver sets these reserved bits */ | \ + RX_FILTER_BEACON | RX_FILTER_ATIM | RX_FILTER_DISASSOC | \ + RX_FILTER_AUTH | RX_FILTER_DEAUTH | \ + (0x7 << 13) /* vendor driver sets these reserved bits */ | \ + RX_FILTER_PSPOLL | RX_FILTER_ACK) /* 0x2400ffff */ + +#define RX_FILTER_CTRL (RX_FILTER_RTS | RX_FILTER_CTS | \ + RX_FILTER_CFEND | RX_FILTER_CFACK) + +#define BCN_MODE_AP 0x1000000 +#define BCN_MODE_IBSS 0x2000000 + +/* Monitor mode sets filter to 0xfffff */ + +#define CR_ACK_TIMEOUT_EXT CTL_REG(0x0690) +#define CR_BCN_FIFO_SEMAPHORE CTL_REG(0x0694) + +#define CR_IFS_VALUE CTL_REG(0x0698) +#define IFS_VALUE_DIFS_SH 0 +#define IFS_VALUE_EIFS_SH 12 +#define IFS_VALUE_SIFS_SH 24 +#define IFS_VALUE_DEFAULT (( 50 << IFS_VALUE_DIFS_SH) | \ + (1148 << IFS_VALUE_EIFS_SH) | \ + ( 10 << IFS_VALUE_SIFS_SH)) + +#define CR_RX_TIME_OUT CTL_REG(0x069C) +#define CR_TOTAL_RX_FRM CTL_REG(0x06A0) +#define CR_CRC32_CNT CTL_REG(0x06A4) +#define CR_CRC16_CNT CTL_REG(0x06A8) +#define CR_DECRYPTION_ERR_UNI CTL_REG(0x06AC) +#define CR_RX_FIFO_OVERRUN CTL_REG(0x06B0) + +#define CR_DECRYPTION_ERR_MUL CTL_REG(0x06BC) + +#define CR_NAV_CNT CTL_REG(0x06C4) +#define CR_NAV_CCA CTL_REG(0x06C8) +#define CR_RETRY_CNT CTL_REG(0x06CC) + +#define CR_READ_TCB_ADDR CTL_REG(0x06E8) +#define CR_READ_RFD_ADDR CTL_REG(0x06EC) +#define CR_CWMIN_CWMAX CTL_REG(0x06F0) +#define CR_TOTAL_TX_FRM CTL_REG(0x06F4) + +/* CAM: Continuous Access Mode (power management) */ +#define CR_CAM_MODE CTL_REG(0x0700) +#define MODE_IBSS 0x0 +#define MODE_AP 0x1 +#define MODE_STA 0x2 +#define MODE_AP_WDS 0x3 + +#define CR_CAM_ROLL_TB_LOW CTL_REG(0x0704) +#define CR_CAM_ROLL_TB_HIGH CTL_REG(0x0708) +#define CR_CAM_ADDRESS CTL_REG(0x070C) +#define CR_CAM_DATA CTL_REG(0x0710) + +#define CR_ROMDIR CTL_REG(0x0714) + +#define CR_DECRY_ERR_FLG_LOW CTL_REG(0x0714) +#define CR_DECRY_ERR_FLG_HIGH CTL_REG(0x0718) + +#define CR_WEPKEY0 CTL_REG(0x0720) +#define CR_WEPKEY1 CTL_REG(0x0724) +#define CR_WEPKEY2 CTL_REG(0x0728) +#define CR_WEPKEY3 CTL_REG(0x072C) +#define CR_WEPKEY4 CTL_REG(0x0730) +#define CR_WEPKEY5 CTL_REG(0x0734) +#define CR_WEPKEY6 CTL_REG(0x0738) +#define CR_WEPKEY7 CTL_REG(0x073C) +#define CR_WEPKEY8 CTL_REG(0x0740) +#define CR_WEPKEY9 CTL_REG(0x0744) +#define CR_WEPKEY10 CTL_REG(0x0748) +#define CR_WEPKEY11 CTL_REG(0x074C) +#define CR_WEPKEY12 CTL_REG(0x0750) +#define CR_WEPKEY13 CTL_REG(0x0754) +#define CR_WEPKEY14 CTL_REG(0x0758) +#define CR_WEPKEY15 CTL_REG(0x075c) +#define CR_TKIP_MODE CTL_REG(0x0760) + +#define CR_EEPROM_PROTECT0 CTL_REG(0x0758) +#define CR_EEPROM_PROTECT1 CTL_REG(0x075C) + +#define CR_DBG_FIFO_RD CTL_REG(0x0800) +#define CR_DBG_SELECT CTL_REG(0x0804) +#define CR_FIFO_Length CTL_REG(0x0808) + + +#define CR_RSSI_MGC CTL_REG(0x0810) + +#define CR_PON CTL_REG(0x0818) +#define CR_RX_ON CTL_REG(0x081C) +#define CR_TX_ON CTL_REG(0x0820) +#define CR_CHIP_EN CTL_REG(0x0824) +#define CR_LO_SW CTL_REG(0x0828) +#define CR_TXRX_SW CTL_REG(0x082C) +#define CR_S_MD CTL_REG(0x0830) + +#define CR_USB_DEBUG_PORT CTL_REG(0x0888) +#define CR_ZD1211B_CWIN_MAX_MIN_AC0 CTL_REG(0x0b00) +#define CR_ZD1211B_CWIN_MAX_MIN_AC1 CTL_REG(0x0b04) +#define CR_ZD1211B_CWIN_MAX_MIN_AC2 CTL_REG(0x0b08) +#define CR_ZD1211B_CWIN_MAX_MIN_AC3 CTL_REG(0x0b0c) +#define CR_ZD1211B_AIFS_CTL1 CTL_REG(0x0b10) +#define CR_ZD1211B_AIFS_CTL2 CTL_REG(0x0b14) +#define CR_ZD1211B_TXOP CTL_REG(0x0b20) +#define CR_ZD1211B_RETRY_MAX CTL_REG(0x0b28) + +/* Value for CR_ZD1211_RETRY_MAX & CR_ZD1211B_RETRY_MAX. Vendor driver uses 2, + * we use 0. The first rate is tried (count+2), then all next rates are tried + * twice, until 1 Mbits is tried. */ +#define ZD1211_RETRY_COUNT 0 +#define ZD1211B_RETRY_COUNT \ + (ZD1211_RETRY_COUNT << 0)| \ + (ZD1211_RETRY_COUNT << 8)| \ + (ZD1211_RETRY_COUNT << 16)| \ + (ZD1211_RETRY_COUNT << 24) + +/* Used to detect PLL lock */ +#define UW2453_INTR_REG ((zd_addr_t)0x85c1) + +#define CWIN_SIZE 0x007f043f + + +#define HWINT_ENABLED \ + (INT_TX_COMPLETE_EN| \ + INT_RX_COMPLETE_EN| \ + INT_RETRY_FAIL_EN| \ + INT_WAKEUP_EN| \ + INT_CFG_NEXT_BCN_EN) + +#define HWINT_DISABLED 0 + +#define E2P_PWR_INT_GUARD 8 +#define E2P_CHANNEL_COUNT 14 + +/* If you compare this addresses with the ZYDAS orignal driver, please notify + * that we use word mapping for the EEPROM. + */ + +/* + * Upper 16 bit contains the regulatory domain. + */ +#define E2P_SUBID E2P_DATA(0x00) +#define E2P_POD E2P_DATA(0x02) +#define E2P_MAC_ADDR_P1 E2P_DATA(0x04) +#define E2P_MAC_ADDR_P2 E2P_DATA(0x06) +#define E2P_PWR_CAL_VALUE1 E2P_DATA(0x08) +#define E2P_PWR_CAL_VALUE2 E2P_DATA(0x0a) +#define E2P_PWR_CAL_VALUE3 E2P_DATA(0x0c) +#define E2P_PWR_CAL_VALUE4 E2P_DATA(0x0e) +#define E2P_PWR_INT_VALUE1 E2P_DATA(0x10) +#define E2P_PWR_INT_VALUE2 E2P_DATA(0x12) +#define E2P_PWR_INT_VALUE3 E2P_DATA(0x14) +#define E2P_PWR_INT_VALUE4 E2P_DATA(0x16) + +/* Contains a bit for each allowed channel. It gives for Europe (ETSI 0x30) + * also only 11 channels. */ +#define E2P_ALLOWED_CHANNEL E2P_DATA(0x18) + +#define E2P_DEVICE_VER E2P_DATA(0x20) +#define E2P_PHY_REG E2P_DATA(0x25) +#define E2P_36M_CAL_VALUE1 E2P_DATA(0x28) +#define E2P_36M_CAL_VALUE2 E2P_DATA(0x2a) +#define E2P_36M_CAL_VALUE3 E2P_DATA(0x2c) +#define E2P_36M_CAL_VALUE4 E2P_DATA(0x2e) +#define E2P_11A_INT_VALUE1 E2P_DATA(0x30) +#define E2P_11A_INT_VALUE2 E2P_DATA(0x32) +#define E2P_11A_INT_VALUE3 E2P_DATA(0x34) +#define E2P_11A_INT_VALUE4 E2P_DATA(0x36) +#define E2P_48M_CAL_VALUE1 E2P_DATA(0x38) +#define E2P_48M_CAL_VALUE2 E2P_DATA(0x3a) +#define E2P_48M_CAL_VALUE3 E2P_DATA(0x3c) +#define E2P_48M_CAL_VALUE4 E2P_DATA(0x3e) +#define E2P_48M_INT_VALUE1 E2P_DATA(0x40) +#define E2P_48M_INT_VALUE2 E2P_DATA(0x42) +#define E2P_48M_INT_VALUE3 E2P_DATA(0x44) +#define E2P_48M_INT_VALUE4 E2P_DATA(0x46) +#define E2P_54M_CAL_VALUE1 E2P_DATA(0x48) /* ??? */ +#define E2P_54M_CAL_VALUE2 E2P_DATA(0x4a) +#define E2P_54M_CAL_VALUE3 E2P_DATA(0x4c) +#define E2P_54M_CAL_VALUE4 E2P_DATA(0x4e) +#define E2P_54M_INT_VALUE1 E2P_DATA(0x50) +#define E2P_54M_INT_VALUE2 E2P_DATA(0x52) +#define E2P_54M_INT_VALUE3 E2P_DATA(0x54) +#define E2P_54M_INT_VALUE4 E2P_DATA(0x56) + +/* This word contains the base address of the FW_REG_ registers below */ +#define FWRAW_REGS_ADDR FWRAW_DATA(0x1d) + +/* All 16 bit values, offset from the address in FWRAW_REGS_ADDR */ +enum { + FW_REG_FIRMWARE_VER = 0, + /* non-zero if USB high speed connection */ + FW_REG_USB_SPEED = 1, + FW_REG_FIX_TX_RATE = 2, + /* Seems to be able to control LEDs over the firmware */ + FW_REG_LED_LINK_STATUS = 3, + FW_REG_SOFT_RESET = 4, + FW_REG_FLASH_CHK = 5, +}; + +/* Values for FW_LINK_STATUS */ +#define FW_LINK_OFF 0x0 +#define FW_LINK_TX 0x1 +/* 0x2 - link led on? */ + +enum { + /* indices for ofdm_cal_values */ + OFDM_36M_INDEX = 0, + OFDM_48M_INDEX = 1, + OFDM_54M_INDEX = 2, +}; + +struct zd_chip { + struct zd_usb usb; + struct zd_rf rf; + struct mutex mutex; + /* Base address of FW_REG_ registers */ + zd_addr_t fw_regs_base; + /* EepSetPoint in the vendor driver */ + u8 pwr_cal_values[E2P_CHANNEL_COUNT]; + /* integration values in the vendor driver */ + u8 pwr_int_values[E2P_CHANNEL_COUNT]; + /* SetPointOFDM in the vendor driver */ + u8 ofdm_cal_values[3][E2P_CHANNEL_COUNT]; + u16 link_led; + unsigned int pa_type:4, + patch_cck_gain:1, patch_cr157:1, patch_6m_band_edge:1, + new_phy_layout:1, al2230s_bit:1, + supports_tx_led:1; +}; + +static inline struct zd_chip *zd_usb_to_chip(struct zd_usb *usb) +{ + return container_of(usb, struct zd_chip, usb); +} + +static inline struct zd_chip *zd_rf_to_chip(struct zd_rf *rf) +{ + return container_of(rf, struct zd_chip, rf); +} + +#define zd_chip_dev(chip) (&(chip)->usb.intf->dev) + +void zd_chip_init(struct zd_chip *chip, + struct ieee80211_hw *hw, + struct usb_interface *intf); +void zd_chip_clear(struct zd_chip *chip); +int zd_chip_read_mac_addr_fw(struct zd_chip *chip, u8 *addr); +int zd_chip_init_hw(struct zd_chip *chip); +int zd_chip_reset(struct zd_chip *chip); + +static inline int zd_chip_is_zd1211b(struct zd_chip *chip) +{ + return chip->usb.is_zd1211b; +} + +static inline int zd_ioread16v_locked(struct zd_chip *chip, u16 *values, + const zd_addr_t *addresses, + unsigned int count) +{ + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + return zd_usb_ioread16v(&chip->usb, values, addresses, count); +} + +static inline int zd_ioread16_locked(struct zd_chip *chip, u16 *value, + const zd_addr_t addr) +{ + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + return zd_usb_ioread16(&chip->usb, value, addr); +} + +int zd_ioread32v_locked(struct zd_chip *chip, u32 *values, + const zd_addr_t *addresses, unsigned int count); + +static inline int zd_ioread32_locked(struct zd_chip *chip, u32 *value, + const zd_addr_t addr) +{ + return zd_ioread32v_locked(chip, value, &addr, 1); +} + +static inline int zd_iowrite16_locked(struct zd_chip *chip, u16 value, + zd_addr_t addr) +{ + struct zd_ioreq16 ioreq; + + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + ioreq.addr = addr; + ioreq.value = value; + + return zd_usb_iowrite16v(&chip->usb, &ioreq, 1); +} + +int zd_iowrite16a_locked(struct zd_chip *chip, + const struct zd_ioreq16 *ioreqs, unsigned int count); + +int _zd_iowrite32v_locked(struct zd_chip *chip, const struct zd_ioreq32 *ioreqs, + unsigned int count); + +static inline int zd_iowrite32_locked(struct zd_chip *chip, u32 value, + zd_addr_t addr) +{ + struct zd_ioreq32 ioreq; + + ioreq.addr = addr; + ioreq.value = value; + + return _zd_iowrite32v_locked(chip, &ioreq, 1); +} + +int zd_iowrite32a_locked(struct zd_chip *chip, + const struct zd_ioreq32 *ioreqs, unsigned int count); + +static inline int zd_rfwrite_locked(struct zd_chip *chip, u32 value, u8 bits) +{ + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + return zd_usb_rfwrite(&chip->usb, value, bits); +} + +int zd_rfwrite_cr_locked(struct zd_chip *chip, u32 value); + +int zd_rfwritev_locked(struct zd_chip *chip, + const u32* values, unsigned int count, u8 bits); +int zd_rfwritev_cr_locked(struct zd_chip *chip, + const u32* values, unsigned int count); + +/* Locking functions for reading and writing registers. + * The different parameters are intentional. + */ +int zd_ioread16(struct zd_chip *chip, zd_addr_t addr, u16 *value); +int zd_iowrite16(struct zd_chip *chip, zd_addr_t addr, u16 value); +int zd_ioread32(struct zd_chip *chip, zd_addr_t addr, u32 *value); +int zd_iowrite32(struct zd_chip *chip, zd_addr_t addr, u32 value); +int zd_ioread32v(struct zd_chip *chip, const zd_addr_t *addresses, + u32 *values, unsigned int count); +int zd_iowrite32a(struct zd_chip *chip, const struct zd_ioreq32 *ioreqs, + unsigned int count); + +int zd_chip_set_channel(struct zd_chip *chip, u8 channel); +static inline u8 _zd_chip_get_channel(struct zd_chip *chip) +{ + return chip->rf.channel; +} +u8 zd_chip_get_channel(struct zd_chip *chip); +int zd_read_regdomain(struct zd_chip *chip, u8 *regdomain); +int zd_write_mac_addr(struct zd_chip *chip, const u8 *mac_addr); +int zd_write_bssid(struct zd_chip *chip, const u8 *bssid); +int zd_chip_switch_radio_on(struct zd_chip *chip); +int zd_chip_switch_radio_off(struct zd_chip *chip); +int zd_chip_enable_int(struct zd_chip *chip); +void zd_chip_disable_int(struct zd_chip *chip); +int zd_chip_enable_rxtx(struct zd_chip *chip); +void zd_chip_disable_rxtx(struct zd_chip *chip); +int zd_chip_enable_hwint(struct zd_chip *chip); +int zd_chip_disable_hwint(struct zd_chip *chip); +int zd_chip_generic_patch_6m_band(struct zd_chip *chip, int channel); +int zd_chip_set_rts_cts_rate_locked(struct zd_chip *chip, int preamble); + +static inline int zd_get_encryption_type(struct zd_chip *chip, u32 *type) +{ + return zd_ioread32(chip, CR_ENCRYPTION_TYPE, type); +} + +static inline int zd_set_encryption_type(struct zd_chip *chip, u32 type) +{ + return zd_iowrite32(chip, CR_ENCRYPTION_TYPE, type); +} + +static inline int zd_chip_get_basic_rates(struct zd_chip *chip, u16 *cr_rates) +{ + return zd_ioread16(chip, CR_BASIC_RATE_TBL, cr_rates); +} + +int zd_chip_set_basic_rates(struct zd_chip *chip, u16 cr_rates); + +int zd_chip_lock_phy_regs(struct zd_chip *chip); +int zd_chip_unlock_phy_regs(struct zd_chip *chip); + +enum led_status { + ZD_LED_OFF = 0, + ZD_LED_SCANNING = 1, + ZD_LED_ASSOCIATED = 2, +}; + +int zd_chip_control_leds(struct zd_chip *chip, enum led_status status); + +int zd_set_beacon_interval(struct zd_chip *chip, u16 interval, u8 dtim_period, + int type); + +static inline int zd_get_beacon_interval(struct zd_chip *chip, u32 *interval) +{ + return zd_ioread32(chip, CR_BCN_INTERVAL, interval); +} + +struct rx_status; + +u8 zd_rx_rate(const void *rx_frame, const struct rx_status *status); + +struct zd_mc_hash { + u32 low; + u32 high; +}; + +static inline void zd_mc_clear(struct zd_mc_hash *hash) +{ + hash->low = 0; + /* The interfaces must always received broadcasts. + * The hash of the broadcast address ff:ff:ff:ff:ff:ff is 63. + */ + hash->high = 0x80000000; +} + +static inline void zd_mc_add_all(struct zd_mc_hash *hash) +{ + hash->low = hash->high = 0xffffffff; +} + +static inline void zd_mc_add_addr(struct zd_mc_hash *hash, u8 *addr) +{ + unsigned int i = addr[5] >> 2; + if (i < 32) { + hash->low |= 1 << i; + } else { + hash->high |= 1 << (i-32); + } +} + +int zd_chip_set_multicast_hash(struct zd_chip *chip, + struct zd_mc_hash *hash); + +u64 zd_chip_get_tsf(struct zd_chip *chip); + +#endif /* _ZD_CHIP_H */ diff --git a/drivers/net/wireless/zd1211rw/zd_def.h b/drivers/net/wireless/zd1211rw/zd_def.h new file mode 100644 index 0000000..9a1b013 --- /dev/null +++ b/drivers/net/wireless/zd1211rw/zd_def.h @@ -0,0 +1,70 @@ +/* ZD1211 USB-WLAN driver for Linux + * + * Copyright (C) 2005-2007 Ulrich Kunitz + * Copyright (C) 2006-2007 Daniel Drake + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ZD_DEF_H +#define _ZD_DEF_H + +#include +#include +#include + +typedef u16 __nocast zd_addr_t; + +#define dev_printk_f(level, dev, fmt, args...) \ + dev_printk(level, dev, "%s() " fmt, __func__, ##args) + +#ifdef DEBUG +# define dev_dbg_f(dev, fmt, args...) \ + dev_printk_f(KERN_DEBUG, dev, fmt, ## args) +# define dev_dbg_f_limit(dev, fmt, args...) do { \ + if (net_ratelimit()) \ + dev_printk_f(KERN_DEBUG, dev, fmt, ## args); \ +} while (0) +# define dev_dbg_f_cond(dev, cond, fmt, args...) ({ \ + bool __cond = !!(cond); \ + if (unlikely(__cond)) \ + dev_printk_f(KERN_DEBUG, dev, fmt, ## args); \ +}) +#else +# define dev_dbg_f(dev, fmt, args...) do { (void)(dev); } while (0) +# define dev_dbg_f_limit(dev, fmt, args...) do { (void)(dev); } while (0) +# define dev_dbg_f_cond(dev, cond, fmt, args...) do { (void)(dev); } while (0) +#endif /* DEBUG */ + +#ifdef DEBUG +# define ZD_ASSERT(x) \ +do { \ + if (unlikely(!(x))) { \ + pr_debug("%s:%d ASSERT %s VIOLATED!\n", \ + __FILE__, __LINE__, __stringify(x)); \ + dump_stack(); \ + } \ +} while (0) +#else +# define ZD_ASSERT(x) do { } while (0) +#endif + +#ifdef DEBUG +# define ZD_MEMCLEAR(pointer, size) memset((pointer), 0xff, (size)) +#else +# define ZD_MEMCLEAR(pointer, size) do { } while (0) +#endif + +#endif /* _ZD_DEF_H */ diff --git a/drivers/net/wireless/zd1211rw/zd_mac.c b/drivers/net/wireless/zd1211rw/zd_mac.c new file mode 100644 index 0000000..db398cd --- /dev/null +++ b/drivers/net/wireless/zd1211rw/zd_mac.c @@ -0,0 +1,1569 @@ +/* ZD1211 USB-WLAN driver for Linux + * + * Copyright (C) 2005-2007 Ulrich Kunitz + * Copyright (C) 2006-2007 Daniel Drake + * Copyright (C) 2006-2007 Michael Wu + * Copyright (C) 2007-2008 Luis R. Rodriguez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include + +#include "zd_def.h" +#include "zd_chip.h" +#include "zd_mac.h" +#include "zd_rf.h" + +struct zd_reg_alpha2_map { + u32 reg; + char alpha2[2]; +}; + +static struct zd_reg_alpha2_map reg_alpha2_map[] = { + { ZD_REGDOMAIN_FCC, "US" }, + { ZD_REGDOMAIN_IC, "CA" }, + { ZD_REGDOMAIN_ETSI, "DE" }, /* Generic ETSI, use most restrictive */ + { ZD_REGDOMAIN_JAPAN, "JP" }, + { ZD_REGDOMAIN_JAPAN_2, "JP" }, + { ZD_REGDOMAIN_JAPAN_3, "JP" }, + { ZD_REGDOMAIN_SPAIN, "ES" }, + { ZD_REGDOMAIN_FRANCE, "FR" }, +}; + +/* This table contains the hardware specific values for the modulation rates. */ +static const struct ieee80211_rate zd_rates[] = { + { .bitrate = 10, + .hw_value = ZD_CCK_RATE_1M, }, + { .bitrate = 20, + .hw_value = ZD_CCK_RATE_2M, + .hw_value_short = ZD_CCK_RATE_2M | ZD_CCK_PREA_SHORT, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 55, + .hw_value = ZD_CCK_RATE_5_5M, + .hw_value_short = ZD_CCK_RATE_5_5M | ZD_CCK_PREA_SHORT, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 110, + .hw_value = ZD_CCK_RATE_11M, + .hw_value_short = ZD_CCK_RATE_11M | ZD_CCK_PREA_SHORT, + .flags = IEEE80211_RATE_SHORT_PREAMBLE }, + { .bitrate = 60, + .hw_value = ZD_OFDM_RATE_6M, + .flags = 0 }, + { .bitrate = 90, + .hw_value = ZD_OFDM_RATE_9M, + .flags = 0 }, + { .bitrate = 120, + .hw_value = ZD_OFDM_RATE_12M, + .flags = 0 }, + { .bitrate = 180, + .hw_value = ZD_OFDM_RATE_18M, + .flags = 0 }, + { .bitrate = 240, + .hw_value = ZD_OFDM_RATE_24M, + .flags = 0 }, + { .bitrate = 360, + .hw_value = ZD_OFDM_RATE_36M, + .flags = 0 }, + { .bitrate = 480, + .hw_value = ZD_OFDM_RATE_48M, + .flags = 0 }, + { .bitrate = 540, + .hw_value = ZD_OFDM_RATE_54M, + .flags = 0 }, +}; + +/* + * Zydas retry rates table. Each line is listed in the same order as + * in zd_rates[] and contains all the rate used when a packet is sent + * starting with a given rates. Let's consider an example : + * + * "11 Mbits : 4, 3, 2, 1, 0" means : + * - packet is sent using 4 different rates + * - 1st rate is index 3 (ie 11 Mbits) + * - 2nd rate is index 2 (ie 5.5 Mbits) + * - 3rd rate is index 1 (ie 2 Mbits) + * - 4th rate is index 0 (ie 1 Mbits) + */ + +static const struct tx_retry_rate zd_retry_rates[] = { + { /* 1 Mbits */ 1, { 0 }}, + { /* 2 Mbits */ 2, { 1, 0 }}, + { /* 5.5 Mbits */ 3, { 2, 1, 0 }}, + { /* 11 Mbits */ 4, { 3, 2, 1, 0 }}, + { /* 6 Mbits */ 5, { 4, 3, 2, 1, 0 }}, + { /* 9 Mbits */ 6, { 5, 4, 3, 2, 1, 0}}, + { /* 12 Mbits */ 5, { 6, 3, 2, 1, 0 }}, + { /* 18 Mbits */ 6, { 7, 6, 3, 2, 1, 0 }}, + { /* 24 Mbits */ 6, { 8, 6, 3, 2, 1, 0 }}, + { /* 36 Mbits */ 7, { 9, 8, 6, 3, 2, 1, 0 }}, + { /* 48 Mbits */ 8, {10, 9, 8, 6, 3, 2, 1, 0 }}, + { /* 54 Mbits */ 9, {11, 10, 9, 8, 6, 3, 2, 1, 0 }} +}; + +static const struct ieee80211_channel zd_channels[] = { + { .center_freq = 2412, .hw_value = 1 }, + { .center_freq = 2417, .hw_value = 2 }, + { .center_freq = 2422, .hw_value = 3 }, + { .center_freq = 2427, .hw_value = 4 }, + { .center_freq = 2432, .hw_value = 5 }, + { .center_freq = 2437, .hw_value = 6 }, + { .center_freq = 2442, .hw_value = 7 }, + { .center_freq = 2447, .hw_value = 8 }, + { .center_freq = 2452, .hw_value = 9 }, + { .center_freq = 2457, .hw_value = 10 }, + { .center_freq = 2462, .hw_value = 11 }, + { .center_freq = 2467, .hw_value = 12 }, + { .center_freq = 2472, .hw_value = 13 }, + { .center_freq = 2484, .hw_value = 14 }, +}; + +static void housekeeping_init(struct zd_mac *mac); +static void housekeeping_enable(struct zd_mac *mac); +static void housekeeping_disable(struct zd_mac *mac); +static void beacon_init(struct zd_mac *mac); +static void beacon_enable(struct zd_mac *mac); +static void beacon_disable(struct zd_mac *mac); +static void set_rts_cts(struct zd_mac *mac, unsigned int short_preamble); +static int zd_mac_config_beacon(struct ieee80211_hw *hw, + struct sk_buff *beacon, bool in_intr); + +static int zd_reg2alpha2(u8 regdomain, char *alpha2) +{ + unsigned int i; + struct zd_reg_alpha2_map *reg_map; + for (i = 0; i < ARRAY_SIZE(reg_alpha2_map); i++) { + reg_map = ®_alpha2_map[i]; + if (regdomain == reg_map->reg) { + alpha2[0] = reg_map->alpha2[0]; + alpha2[1] = reg_map->alpha2[1]; + return 0; + } + } + return 1; +} + +static int zd_check_signal(struct ieee80211_hw *hw, int signal) +{ + struct zd_mac *mac = zd_hw_mac(hw); + + dev_dbg_f_cond(zd_mac_dev(mac), signal < 0 || signal > 100, + "%s: signal value from device not in range 0..100, " + "but %d.\n", __func__, signal); + + if (signal < 0) + signal = 0; + else if (signal > 100) + signal = 100; + + return signal; +} + +int zd_mac_preinit_hw(struct ieee80211_hw *hw) +{ + int r; + u8 addr[ETH_ALEN]; + struct zd_mac *mac = zd_hw_mac(hw); + + r = zd_chip_read_mac_addr_fw(&mac->chip, addr); + if (r) + return r; + + SET_IEEE80211_PERM_ADDR(hw, addr); + + return 0; +} + +int zd_mac_init_hw(struct ieee80211_hw *hw) +{ + int r; + struct zd_mac *mac = zd_hw_mac(hw); + struct zd_chip *chip = &mac->chip; + char alpha2[2]; + u8 default_regdomain; + + r = zd_chip_enable_int(chip); + if (r) + goto out; + r = zd_chip_init_hw(chip); + if (r) + goto disable_int; + + ZD_ASSERT(!irqs_disabled()); + + r = zd_read_regdomain(chip, &default_regdomain); + if (r) + goto disable_int; + spin_lock_irq(&mac->lock); + mac->regdomain = mac->default_regdomain = default_regdomain; + spin_unlock_irq(&mac->lock); + + /* We must inform the device that we are doing encryption/decryption in + * software at the moment. */ + r = zd_set_encryption_type(chip, ENC_SNIFFER); + if (r) + goto disable_int; + + r = zd_reg2alpha2(mac->regdomain, alpha2); + if (r) + goto disable_int; + + r = regulatory_hint(hw->wiphy, alpha2); +disable_int: + zd_chip_disable_int(chip); +out: + return r; +} + +void zd_mac_clear(struct zd_mac *mac) +{ + flush_workqueue(zd_workqueue); + zd_chip_clear(&mac->chip); + ZD_ASSERT(!spin_is_locked(&mac->lock)); + ZD_MEMCLEAR(mac, sizeof(struct zd_mac)); +} + +static int set_rx_filter(struct zd_mac *mac) +{ + unsigned long flags; + u32 filter = STA_RX_FILTER; + + spin_lock_irqsave(&mac->lock, flags); + if (mac->pass_ctrl) + filter |= RX_FILTER_CTRL; + spin_unlock_irqrestore(&mac->lock, flags); + + return zd_iowrite32(&mac->chip, CR_RX_FILTER, filter); +} + +static int set_mac_and_bssid(struct zd_mac *mac) +{ + int r; + + if (!mac->vif) + return -1; + + r = zd_write_mac_addr(&mac->chip, mac->vif->addr); + if (r) + return r; + + /* Vendor driver after setting MAC either sets BSSID for AP or + * filter for other modes. + */ + if (mac->type != NL80211_IFTYPE_AP) + return set_rx_filter(mac); + else + return zd_write_bssid(&mac->chip, mac->vif->addr); +} + +static int set_mc_hash(struct zd_mac *mac) +{ + struct zd_mc_hash hash; + zd_mc_clear(&hash); + return zd_chip_set_multicast_hash(&mac->chip, &hash); +} + +int zd_op_start(struct ieee80211_hw *hw) +{ + struct zd_mac *mac = zd_hw_mac(hw); + struct zd_chip *chip = &mac->chip; + struct zd_usb *usb = &chip->usb; + int r; + + if (!usb->initialized) { + r = zd_usb_init_hw(usb); + if (r) + goto out; + } + + r = zd_chip_enable_int(chip); + if (r < 0) + goto out; + + r = zd_chip_set_basic_rates(chip, CR_RATES_80211B | CR_RATES_80211G); + if (r < 0) + goto disable_int; + r = set_rx_filter(mac); + if (r) + goto disable_int; + r = set_mc_hash(mac); + if (r) + goto disable_int; + + /* Wait after setting the multicast hash table and powering on + * the radio otherwise interface bring up will fail. This matches + * what the vendor driver did. + */ + msleep(10); + + r = zd_chip_switch_radio_on(chip); + if (r < 0) { + dev_err(zd_chip_dev(chip), + "%s: failed to set radio on\n", __func__); + goto disable_int; + } + r = zd_chip_enable_rxtx(chip); + if (r < 0) + goto disable_radio; + r = zd_chip_enable_hwint(chip); + if (r < 0) + goto disable_rxtx; + + housekeeping_enable(mac); + beacon_enable(mac); + set_bit(ZD_DEVICE_RUNNING, &mac->flags); + return 0; +disable_rxtx: + zd_chip_disable_rxtx(chip); +disable_radio: + zd_chip_switch_radio_off(chip); +disable_int: + zd_chip_disable_int(chip); +out: + return r; +} + +void zd_op_stop(struct ieee80211_hw *hw) +{ + struct zd_mac *mac = zd_hw_mac(hw); + struct zd_chip *chip = &mac->chip; + struct sk_buff *skb; + struct sk_buff_head *ack_wait_queue = &mac->ack_wait_queue; + + clear_bit(ZD_DEVICE_RUNNING, &mac->flags); + + /* The order here deliberately is a little different from the open() + * method, since we need to make sure there is no opportunity for RX + * frames to be processed by mac80211 after we have stopped it. + */ + + zd_chip_disable_rxtx(chip); + beacon_disable(mac); + housekeeping_disable(mac); + flush_workqueue(zd_workqueue); + + zd_chip_disable_hwint(chip); + zd_chip_switch_radio_off(chip); + zd_chip_disable_int(chip); + + + while ((skb = skb_dequeue(ack_wait_queue))) + dev_kfree_skb_any(skb); +} + +int zd_restore_settings(struct zd_mac *mac) +{ + struct sk_buff *beacon; + struct zd_mc_hash multicast_hash; + unsigned int short_preamble; + int r, beacon_interval, beacon_period; + u8 channel; + + dev_dbg_f(zd_mac_dev(mac), "\n"); + + spin_lock_irq(&mac->lock); + multicast_hash = mac->multicast_hash; + short_preamble = mac->short_preamble; + beacon_interval = mac->beacon.interval; + beacon_period = mac->beacon.period; + channel = mac->channel; + spin_unlock_irq(&mac->lock); + + r = set_mac_and_bssid(mac); + if (r < 0) { + dev_dbg_f(zd_mac_dev(mac), "set_mac_and_bssid failed, %d\n", r); + return r; + } + + r = zd_chip_set_channel(&mac->chip, channel); + if (r < 0) { + dev_dbg_f(zd_mac_dev(mac), "zd_chip_set_channel failed, %d\n", + r); + return r; + } + + set_rts_cts(mac, short_preamble); + + r = zd_chip_set_multicast_hash(&mac->chip, &multicast_hash); + if (r < 0) { + dev_dbg_f(zd_mac_dev(mac), + "zd_chip_set_multicast_hash failed, %d\n", r); + return r; + } + + if (mac->type == NL80211_IFTYPE_MESH_POINT || + mac->type == NL80211_IFTYPE_ADHOC || + mac->type == NL80211_IFTYPE_AP) { + if (mac->vif != NULL) { + beacon = ieee80211_beacon_get(mac->hw, mac->vif); + if (beacon) + zd_mac_config_beacon(mac->hw, beacon, false); + } + + zd_set_beacon_interval(&mac->chip, beacon_interval, + beacon_period, mac->type); + + spin_lock_irq(&mac->lock); + mac->beacon.last_update = jiffies; + spin_unlock_irq(&mac->lock); + } + + return 0; +} + +/** + * zd_mac_tx_status - reports tx status of a packet if required + * @hw - a &struct ieee80211_hw pointer + * @skb - a sk-buffer + * @flags: extra flags to set in the TX status info + * @ackssi: ACK signal strength + * @success - True for successful transmission of the frame + * + * This information calls ieee80211_tx_status_irqsafe() if required by the + * control information. It copies the control information into the status + * information. + * + * If no status information has been requested, the skb is freed. + */ +static void zd_mac_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb, + int ackssi, struct tx_status *tx_status) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + int i; + int success = 1, retry = 1; + int first_idx; + const struct tx_retry_rate *retries; + + ieee80211_tx_info_clear_status(info); + + if (tx_status) { + success = !tx_status->failure; + retry = tx_status->retry + success; + } + + if (success) { + /* success */ + info->flags |= IEEE80211_TX_STAT_ACK; + } else { + /* failure */ + info->flags &= ~IEEE80211_TX_STAT_ACK; + } + + first_idx = info->status.rates[0].idx; + ZD_ASSERT(0<=first_idx && first_idxcount); + + info->status.rates[0].idx = retries->rate[0]; + info->status.rates[0].count = 1; // (retry > 1 ? 2 : 1); + + for (i=1; istatus.rates[i].idx = retries->rate[i]; + info->status.rates[i].count = 1; // ((i==retry-1) && success ? 1:2); + } + for (; istatus.rates[i].idx = retries->rate[retry - 1]; + info->status.rates[i].count = 1; // (success ? 1:2); + } + if (istatus.rates[i].idx = -1; /* terminate */ + + info->status.ack_signal = zd_check_signal(hw, ackssi); + ieee80211_tx_status_irqsafe(hw, skb); +} + +/** + * zd_mac_tx_failed - callback for failed frames + * @dev: the mac80211 wireless device + * + * This function is called if a frame couldn't be successfully + * transferred. The first frame from the tx queue, will be selected and + * reported as error to the upper layers. + */ +void zd_mac_tx_failed(struct urb *urb) +{ + struct ieee80211_hw * hw = zd_usb_to_hw(urb->context); + struct zd_mac *mac = zd_hw_mac(hw); + struct sk_buff_head *q = &mac->ack_wait_queue; + struct sk_buff *skb; + struct tx_status *tx_status = (struct tx_status *)urb->transfer_buffer; + unsigned long flags; + int success = !tx_status->failure; + int retry = tx_status->retry + success; + int found = 0; + int i, position = 0; + + q = &mac->ack_wait_queue; + spin_lock_irqsave(&q->lock, flags); + + skb_queue_walk(q, skb) { + struct ieee80211_hdr *tx_hdr; + struct ieee80211_tx_info *info; + int first_idx, final_idx; + const struct tx_retry_rate *retries; + u8 final_rate; + + position ++; + + /* if the hardware reports a failure and we had a 802.11 ACK + * pending, then we skip the first skb when searching for a + * matching frame */ + if (tx_status->failure && mac->ack_pending && + skb_queue_is_first(q, skb)) { + continue; + } + + tx_hdr = (struct ieee80211_hdr *)skb->data; + + /* we skip all frames not matching the reported destination */ + if (unlikely(memcmp(tx_hdr->addr1, tx_status->mac, ETH_ALEN))) { + continue; + } + + /* we skip all frames not matching the reported final rate */ + + info = IEEE80211_SKB_CB(skb); + first_idx = info->status.rates[0].idx; + ZD_ASSERT(0<=first_idx && first_idx retries->count) + continue; + + final_idx = retries->rate[retry - 1]; + final_rate = zd_rates[final_idx].hw_value; + + if (final_rate != tx_status->rate) { + continue; + } + + found = 1; + break; + } + + if (found) { + for (i=1; i<=position; i++) { + skb = __skb_dequeue(q); + zd_mac_tx_status(hw, skb, + mac->ack_pending ? mac->ack_signal : 0, + i == position ? tx_status : NULL); + mac->ack_pending = 0; + } + } + + spin_unlock_irqrestore(&q->lock, flags); +} + +/** + * zd_mac_tx_to_dev - callback for USB layer + * @skb: a &sk_buff pointer + * @error: error value, 0 if transmission successful + * + * Informs the MAC layer that the frame has successfully transferred to the + * device. If an ACK is required and the transfer to the device has been + * successful, the packets are put on the @ack_wait_queue with + * the control set removed. + */ +void zd_mac_tx_to_dev(struct sk_buff *skb, int error) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_hw *hw = info->rate_driver_data[0]; + struct zd_mac *mac = zd_hw_mac(hw); + + ieee80211_tx_info_clear_status(info); + + skb_pull(skb, sizeof(struct zd_ctrlset)); + if (unlikely(error || + (info->flags & IEEE80211_TX_CTL_NO_ACK))) { + /* + * FIXME : do we need to fill in anything ? + */ + ieee80211_tx_status_irqsafe(hw, skb); + } else { + struct sk_buff_head *q = &mac->ack_wait_queue; + + skb_queue_tail(q, skb); + while (skb_queue_len(q) > ZD_MAC_MAX_ACK_WAITERS) { + zd_mac_tx_status(hw, skb_dequeue(q), + mac->ack_pending ? mac->ack_signal : 0, + NULL); + mac->ack_pending = 0; + } + } +} + +static int zd_calc_tx_length_us(u8 *service, u8 zd_rate, u16 tx_length) +{ + /* ZD_PURE_RATE() must be used to remove the modulation type flag of + * the zd-rate values. + */ + static const u8 rate_divisor[] = { + [ZD_PURE_RATE(ZD_CCK_RATE_1M)] = 1, + [ZD_PURE_RATE(ZD_CCK_RATE_2M)] = 2, + /* Bits must be doubled. */ + [ZD_PURE_RATE(ZD_CCK_RATE_5_5M)] = 11, + [ZD_PURE_RATE(ZD_CCK_RATE_11M)] = 11, + [ZD_PURE_RATE(ZD_OFDM_RATE_6M)] = 6, + [ZD_PURE_RATE(ZD_OFDM_RATE_9M)] = 9, + [ZD_PURE_RATE(ZD_OFDM_RATE_12M)] = 12, + [ZD_PURE_RATE(ZD_OFDM_RATE_18M)] = 18, + [ZD_PURE_RATE(ZD_OFDM_RATE_24M)] = 24, + [ZD_PURE_RATE(ZD_OFDM_RATE_36M)] = 36, + [ZD_PURE_RATE(ZD_OFDM_RATE_48M)] = 48, + [ZD_PURE_RATE(ZD_OFDM_RATE_54M)] = 54, + }; + + u32 bits = (u32)tx_length * 8; + u32 divisor; + + divisor = rate_divisor[ZD_PURE_RATE(zd_rate)]; + if (divisor == 0) + return -EINVAL; + + switch (zd_rate) { + case ZD_CCK_RATE_5_5M: + bits = (2*bits) + 10; /* round up to the next integer */ + break; + case ZD_CCK_RATE_11M: + if (service) { + u32 t = bits % 11; + *service &= ~ZD_PLCP_SERVICE_LENGTH_EXTENSION; + if (0 < t && t <= 3) { + *service |= ZD_PLCP_SERVICE_LENGTH_EXTENSION; + } + } + bits += 10; /* round up to the next integer */ + break; + } + + return bits/divisor; +} + +static void cs_set_control(struct zd_mac *mac, struct zd_ctrlset *cs, + struct ieee80211_hdr *header, + struct ieee80211_tx_info *info) +{ + /* + * CONTROL TODO: + * - if backoff needed, enable bit 0 + * - if burst (backoff not needed) disable bit 0 + */ + + cs->control = 0; + + /* First fragment */ + if (info->flags & IEEE80211_TX_CTL_FIRST_FRAGMENT) + cs->control |= ZD_CS_NEED_RANDOM_BACKOFF; + + /* No ACK expected (multicast, etc.) */ + if (info->flags & IEEE80211_TX_CTL_NO_ACK) + cs->control |= ZD_CS_NO_ACK; + + /* PS-POLL */ + if (ieee80211_is_pspoll(header->frame_control)) + cs->control |= ZD_CS_PS_POLL_FRAME; + + if (info->control.rates[0].flags & IEEE80211_TX_RC_USE_RTS_CTS) + cs->control |= ZD_CS_RTS; + + if (info->control.rates[0].flags & IEEE80211_TX_RC_USE_CTS_PROTECT) + cs->control |= ZD_CS_SELF_CTS; + + /* FIXME: Management frame? */ +} + +static bool zd_mac_match_cur_beacon(struct zd_mac *mac, struct sk_buff *beacon) +{ + if (!mac->beacon.cur_beacon) + return false; + + if (mac->beacon.cur_beacon->len != beacon->len) + return false; + + return !memcmp(beacon->data, mac->beacon.cur_beacon->data, beacon->len); +} + +static void zd_mac_free_cur_beacon_locked(struct zd_mac *mac) +{ + ZD_ASSERT(mutex_is_locked(&mac->chip.mutex)); + + kfree_skb(mac->beacon.cur_beacon); + mac->beacon.cur_beacon = NULL; +} + +static void zd_mac_free_cur_beacon(struct zd_mac *mac) +{ + mutex_lock(&mac->chip.mutex); + zd_mac_free_cur_beacon_locked(mac); + mutex_unlock(&mac->chip.mutex); +} + +static int zd_mac_config_beacon(struct ieee80211_hw *hw, struct sk_buff *beacon, + bool in_intr) +{ + struct zd_mac *mac = zd_hw_mac(hw); + int r, ret, num_cmds, req_pos = 0; + u32 tmp, j = 0; + /* 4 more bytes for tail CRC */ + u32 full_len = beacon->len + 4; + unsigned long end_jiffies, message_jiffies; + struct zd_ioreq32 *ioreqs; + + mutex_lock(&mac->chip.mutex); + + /* Check if hw already has this beacon. */ + if (zd_mac_match_cur_beacon(mac, beacon)) { + r = 0; + goto out_nofree; + } + + /* Alloc memory for full beacon write at once. */ + num_cmds = 1 + zd_chip_is_zd1211b(&mac->chip) + full_len; + ioreqs = kmalloc(num_cmds * sizeof(struct zd_ioreq32), GFP_KERNEL); + if (!ioreqs) { + r = -ENOMEM; + goto out_nofree; + } + + r = zd_iowrite32_locked(&mac->chip, 0, CR_BCN_FIFO_SEMAPHORE); + if (r < 0) + goto out; + r = zd_ioread32_locked(&mac->chip, &tmp, CR_BCN_FIFO_SEMAPHORE); + if (r < 0) + goto release_sema; + if (in_intr && tmp & 0x2) { + r = -EBUSY; + goto release_sema; + } + + end_jiffies = jiffies + HZ / 2; /*~500ms*/ + message_jiffies = jiffies + HZ / 10; /*~100ms*/ + while (tmp & 0x2) { + r = zd_ioread32_locked(&mac->chip, &tmp, CR_BCN_FIFO_SEMAPHORE); + if (r < 0) + goto release_sema; + if (time_is_before_eq_jiffies(message_jiffies)) { + message_jiffies = jiffies + HZ / 10; + dev_err(zd_mac_dev(mac), + "CR_BCN_FIFO_SEMAPHORE not ready\n"); + if (time_is_before_eq_jiffies(end_jiffies)) { + dev_err(zd_mac_dev(mac), + "Giving up beacon config.\n"); + r = -ETIMEDOUT; + goto reset_device; + } + } + msleep(20); + } + + ioreqs[req_pos].addr = CR_BCN_FIFO; + ioreqs[req_pos].value = full_len - 1; + req_pos++; + if (zd_chip_is_zd1211b(&mac->chip)) { + ioreqs[req_pos].addr = CR_BCN_LENGTH; + ioreqs[req_pos].value = full_len - 1; + req_pos++; + } + + for (j = 0 ; j < beacon->len; j++) { + ioreqs[req_pos].addr = CR_BCN_FIFO; + ioreqs[req_pos].value = *((u8 *)(beacon->data + j)); + req_pos++; + } + + for (j = 0; j < 4; j++) { + ioreqs[req_pos].addr = CR_BCN_FIFO; + ioreqs[req_pos].value = 0x0; + req_pos++; + } + + BUG_ON(req_pos != num_cmds); + + r = zd_iowrite32a_locked(&mac->chip, ioreqs, num_cmds); + +release_sema: + /* + * Try very hard to release device beacon semaphore, as otherwise + * device/driver can be left in unusable state. + */ + end_jiffies = jiffies + HZ / 2; /*~500ms*/ + ret = zd_iowrite32_locked(&mac->chip, 1, CR_BCN_FIFO_SEMAPHORE); + while (ret < 0) { + if (in_intr || time_is_before_eq_jiffies(end_jiffies)) { + ret = -ETIMEDOUT; + break; + } + + msleep(20); + ret = zd_iowrite32_locked(&mac->chip, 1, CR_BCN_FIFO_SEMAPHORE); + } + + if (ret < 0) + dev_err(zd_mac_dev(mac), "Could not release " + "CR_BCN_FIFO_SEMAPHORE!\n"); + if (r < 0 || ret < 0) { + if (r >= 0) + r = ret; + + /* We don't know if beacon was written successfully or not, + * so clear current. */ + zd_mac_free_cur_beacon_locked(mac); + + goto out; + } + + /* Beacon has now been written successfully, update current. */ + zd_mac_free_cur_beacon_locked(mac); + mac->beacon.cur_beacon = beacon; + beacon = NULL; + + /* 802.11b/g 2.4G CCK 1Mb + * 802.11a, not yet implemented, uses different values (see GPL vendor + * driver) + */ + r = zd_iowrite32_locked(&mac->chip, 0x00000400 | (full_len << 19), + CR_BCN_PLCP_CFG); +out: + kfree(ioreqs); +out_nofree: + kfree_skb(beacon); + mutex_unlock(&mac->chip.mutex); + + return r; + +reset_device: + zd_mac_free_cur_beacon_locked(mac); + kfree_skb(beacon); + + mutex_unlock(&mac->chip.mutex); + kfree(ioreqs); + + /* semaphore stuck, reset device to avoid fw freeze later */ + dev_warn(zd_mac_dev(mac), "CR_BCN_FIFO_SEMAPHORE stuck, " + "resetting device..."); + usb_queue_reset_device(mac->chip.usb.intf); + + return r; +} + +static int fill_ctrlset(struct zd_mac *mac, + struct sk_buff *skb) +{ + int r; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data; + unsigned int frag_len = skb->len + FCS_LEN; + unsigned int packet_length; + struct ieee80211_rate *txrate; + struct zd_ctrlset *cs = (struct zd_ctrlset *) + skb_push(skb, sizeof(struct zd_ctrlset)); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + ZD_ASSERT(frag_len <= 0xffff); + + /* + * Firmware computes the duration itself (for all frames except PSPoll) + * and needs the field set to 0 at input, otherwise firmware messes up + * duration_id and sets bits 14 and 15 on. + */ + if (!ieee80211_is_pspoll(hdr->frame_control)) + hdr->duration_id = 0; + + txrate = ieee80211_get_tx_rate(mac->hw, info); + + cs->modulation = txrate->hw_value; + if (info->control.rates[0].flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE) + cs->modulation = txrate->hw_value_short; + + cs->tx_length = cpu_to_le16(frag_len); + + cs_set_control(mac, cs, hdr, info); + + packet_length = frag_len + sizeof(struct zd_ctrlset) + 10; + ZD_ASSERT(packet_length <= 0xffff); + /* ZD1211B: Computing the length difference this way, gives us + * flexibility to compute the packet length. + */ + cs->packet_length = cpu_to_le16(zd_chip_is_zd1211b(&mac->chip) ? + packet_length - frag_len : packet_length); + + /* + * CURRENT LENGTH: + * - transmit frame length in microseconds + * - seems to be derived from frame length + * - see Cal_Us_Service() in zdinlinef.h + * - if macp->bTxBurstEnable is enabled, then multiply by 4 + * - bTxBurstEnable is never set in the vendor driver + * + * SERVICE: + * - "for PLCP configuration" + * - always 0 except in some situations at 802.11b 11M + * - see line 53 of zdinlinef.h + */ + cs->service = 0; + r = zd_calc_tx_length_us(&cs->service, ZD_RATE(cs->modulation), + le16_to_cpu(cs->tx_length)); + if (r < 0) + return r; + cs->current_length = cpu_to_le16(r); + cs->next_frame_length = 0; + + return 0; +} + +/** + * zd_op_tx - transmits a network frame to the device + * + * @dev: mac80211 hardware device + * @skb: socket buffer + * @control: the control structure + * + * This function transmit an IEEE 802.11 network frame to the device. The + * control block of the skbuff will be initialized. If necessary the incoming + * mac80211 queues will be stopped. + */ +static void zd_op_tx(struct ieee80211_hw *hw, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct zd_mac *mac = zd_hw_mac(hw); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + int r; + + r = fill_ctrlset(mac, skb); + if (r) + goto fail; + + info->rate_driver_data[0] = hw; + + r = zd_usb_tx(&mac->chip.usb, skb); + if (r) + goto fail; + return; + +fail: + dev_kfree_skb(skb); +} + +/** + * filter_ack - filters incoming packets for acknowledgements + * @dev: the mac80211 device + * @rx_hdr: received header + * @stats: the status for the received packet + * + * This functions looks for ACK packets and tries to match them with the + * frames in the tx queue. If a match is found the frame will be dequeued and + * the upper layers is informed about the successful transmission. If + * mac80211 queues have been stopped and the number of frames still to be + * transmitted is low the queues will be opened again. + * + * Returns 1 if the frame was an ACK, 0 if it was ignored. + */ +static int filter_ack(struct ieee80211_hw *hw, struct ieee80211_hdr *rx_hdr, + struct ieee80211_rx_status *stats) +{ + struct zd_mac *mac = zd_hw_mac(hw); + struct sk_buff *skb; + struct sk_buff_head *q; + unsigned long flags; + int found = 0; + int i, position = 0; + + if (!ieee80211_is_ack(rx_hdr->frame_control)) + return 0; + + q = &mac->ack_wait_queue; + spin_lock_irqsave(&q->lock, flags); + skb_queue_walk(q, skb) { + struct ieee80211_hdr *tx_hdr; + + position ++; + + if (mac->ack_pending && skb_queue_is_first(q, skb)) + continue; + + tx_hdr = (struct ieee80211_hdr *)skb->data; + if (likely(!memcmp(tx_hdr->addr2, rx_hdr->addr1, ETH_ALEN))) + { + found = 1; + break; + } + } + + if (found) { + for (i=1; iack_pending ? mac->ack_signal : 0, + NULL); + mac->ack_pending = 0; + } + + mac->ack_pending = 1; + mac->ack_signal = stats->signal; + + /* Prevent pending tx-packet on AP-mode */ + if (mac->type == NL80211_IFTYPE_AP) { + skb = __skb_dequeue(q); + zd_mac_tx_status(hw, skb, mac->ack_signal, NULL); + mac->ack_pending = 0; + } + } + + spin_unlock_irqrestore(&q->lock, flags); + return 1; +} + +int zd_mac_rx(struct ieee80211_hw *hw, const u8 *buffer, unsigned int length) +{ + struct zd_mac *mac = zd_hw_mac(hw); + struct ieee80211_rx_status stats; + const struct rx_status *status; + struct sk_buff *skb; + int bad_frame = 0; + __le16 fc; + int need_padding; + int i; + u8 rate; + + if (length < ZD_PLCP_HEADER_SIZE + 10 /* IEEE80211_1ADDR_LEN */ + + FCS_LEN + sizeof(struct rx_status)) + return -EINVAL; + + memset(&stats, 0, sizeof(stats)); + + /* Note about pass_failed_fcs and pass_ctrl access below: + * mac locking intentionally omitted here, as this is the only unlocked + * reader and the only writer is configure_filter. Plus, if there were + * any races accessing these variables, it wouldn't really matter. + * If mac80211 ever provides a way for us to access filter flags + * from outside configure_filter, we could improve on this. Also, this + * situation may change once we implement some kind of DMA-into-skb + * RX path. */ + + /* Caller has to ensure that length >= sizeof(struct rx_status). */ + status = (struct rx_status *) + (buffer + (length - sizeof(struct rx_status))); + if (status->frame_status & ZD_RX_ERROR) { + if (mac->pass_failed_fcs && + (status->frame_status & ZD_RX_CRC32_ERROR)) { + stats.flag |= RX_FLAG_FAILED_FCS_CRC; + bad_frame = 1; + } else { + return -EINVAL; + } + } + + stats.freq = zd_channels[_zd_chip_get_channel(&mac->chip) - 1].center_freq; + stats.band = IEEE80211_BAND_2GHZ; + stats.signal = zd_check_signal(hw, status->signal_strength); + + rate = zd_rx_rate(buffer, status); + + /* todo: return index in the big switches in zd_rx_rate instead */ + for (i = 0; i < mac->band.n_bitrates; i++) + if (rate == mac->band.bitrates[i].hw_value) + stats.rate_idx = i; + + length -= ZD_PLCP_HEADER_SIZE + sizeof(struct rx_status); + buffer += ZD_PLCP_HEADER_SIZE; + + /* Except for bad frames, filter each frame to see if it is an ACK, in + * which case our internal TX tracking is updated. Normally we then + * bail here as there's no need to pass ACKs on up to the stack, but + * there is also the case where the stack has requested us to pass + * control frames on up (pass_ctrl) which we must consider. */ + if (!bad_frame && + filter_ack(hw, (struct ieee80211_hdr *)buffer, &stats) + && !mac->pass_ctrl) + return 0; + + fc = get_unaligned((__le16*)buffer); + need_padding = ieee80211_is_data_qos(fc) ^ ieee80211_has_a4(fc); + + skb = dev_alloc_skb(length + (need_padding ? 2 : 0)); + if (skb == NULL) + return -ENOMEM; + if (need_padding) { + /* Make sure the payload data is 4 byte aligned. */ + skb_reserve(skb, 2); + } + + /* FIXME : could we avoid this big memcpy ? */ + memcpy(skb_put(skb, length), buffer, length); + + memcpy(IEEE80211_SKB_RXCB(skb), &stats, sizeof(stats)); + ieee80211_rx_irqsafe(hw, skb); + return 0; +} + +static int zd_op_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct zd_mac *mac = zd_hw_mac(hw); + + /* using NL80211_IFTYPE_UNSPECIFIED to indicate no mode selected */ + if (mac->type != NL80211_IFTYPE_UNSPECIFIED) + return -EOPNOTSUPP; + + switch (vif->type) { + case NL80211_IFTYPE_MONITOR: + case NL80211_IFTYPE_MESH_POINT: + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_AP: + mac->type = vif->type; + break; + default: + return -EOPNOTSUPP; + } + + mac->vif = vif; + + return set_mac_and_bssid(mac); +} + +static void zd_op_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct zd_mac *mac = zd_hw_mac(hw); + mac->type = NL80211_IFTYPE_UNSPECIFIED; + mac->vif = NULL; + zd_set_beacon_interval(&mac->chip, 0, 0, NL80211_IFTYPE_UNSPECIFIED); + zd_write_mac_addr(&mac->chip, NULL); + + zd_mac_free_cur_beacon(mac); +} + +static int zd_op_config(struct ieee80211_hw *hw, u32 changed) +{ + struct zd_mac *mac = zd_hw_mac(hw); + struct ieee80211_conf *conf = &hw->conf; + + spin_lock_irq(&mac->lock); + mac->channel = conf->chandef.chan->hw_value; + spin_unlock_irq(&mac->lock); + + return zd_chip_set_channel(&mac->chip, conf->chandef.chan->hw_value); +} + +static void zd_beacon_done(struct zd_mac *mac) +{ + struct sk_buff *skb, *beacon; + + if (!test_bit(ZD_DEVICE_RUNNING, &mac->flags)) + return; + if (!mac->vif || mac->vif->type != NL80211_IFTYPE_AP) + return; + + /* + * Send out buffered broad- and multicast frames. + */ + while (!ieee80211_queue_stopped(mac->hw, 0)) { + skb = ieee80211_get_buffered_bc(mac->hw, mac->vif); + if (!skb) + break; + zd_op_tx(mac->hw, NULL, skb); + } + + /* + * Fetch next beacon so that tim_count is updated. + */ + beacon = ieee80211_beacon_get(mac->hw, mac->vif); + if (beacon) + zd_mac_config_beacon(mac->hw, beacon, true); + + spin_lock_irq(&mac->lock); + mac->beacon.last_update = jiffies; + spin_unlock_irq(&mac->lock); +} + +static void zd_process_intr(struct work_struct *work) +{ + u16 int_status; + unsigned long flags; + struct zd_mac *mac = container_of(work, struct zd_mac, process_intr); + + spin_lock_irqsave(&mac->lock, flags); + int_status = le16_to_cpu(*(__le16 *)(mac->intr_buffer + 4)); + spin_unlock_irqrestore(&mac->lock, flags); + + if (int_status & INT_CFG_NEXT_BCN) { + /*dev_dbg_f_limit(zd_mac_dev(mac), "INT_CFG_NEXT_BCN\n");*/ + zd_beacon_done(mac); + } else { + dev_dbg_f(zd_mac_dev(mac), "Unsupported interrupt\n"); + } + + zd_chip_enable_hwint(&mac->chip); +} + + +static u64 zd_op_prepare_multicast(struct ieee80211_hw *hw, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + struct netdev_hw_addr_list *mc_list) +#else + int mc_count, struct dev_addr_list *ha) +#endif +{ + struct zd_mac *mac = zd_hw_mac(hw); + struct zd_mc_hash hash; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + struct netdev_hw_addr *ha; +#else + int i; +#endif + + zd_mc_clear(&hash); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)) + netdev_hw_addr_list_for_each(ha, mc_list) { + dev_dbg_f(zd_mac_dev(mac), "mc addr %pM\n", ha->addr); + zd_mc_add_addr(&hash, ha->addr); +#else + for (i = 0; i < mc_count; i++) { + if (!ha) + break; + dev_dbg_f(zd_mac_dev(mac), "mc addr %pM\n", ha->dmi_addr); + zd_mc_add_addr(&hash, ha->dmi_addr); + ha = ha->next; +#endif + } + + return hash.low | ((u64)hash.high << 32); +} + +#define SUPPORTED_FIF_FLAGS \ + (FIF_PROMISC_IN_BSS | FIF_ALLMULTI | FIF_FCSFAIL | FIF_CONTROL | \ + FIF_OTHER_BSS | FIF_BCN_PRBRESP_PROMISC) +static void zd_op_configure_filter(struct ieee80211_hw *hw, + unsigned int changed_flags, + unsigned int *new_flags, + u64 multicast) +{ + struct zd_mc_hash hash = { + .low = multicast, + .high = multicast >> 32, + }; + struct zd_mac *mac = zd_hw_mac(hw); + unsigned long flags; + int r; + + /* Only deal with supported flags */ + changed_flags &= SUPPORTED_FIF_FLAGS; + *new_flags &= SUPPORTED_FIF_FLAGS; + + /* + * If multicast parameter (as returned by zd_op_prepare_multicast) + * has changed, no bit in changed_flags is set. To handle this + * situation, we do not return if changed_flags is 0. If we do so, + * we will have some issue with IPv6 which uses multicast for link + * layer address resolution. + */ + if (*new_flags & (FIF_PROMISC_IN_BSS | FIF_ALLMULTI)) + zd_mc_add_all(&hash); + + spin_lock_irqsave(&mac->lock, flags); + mac->pass_failed_fcs = !!(*new_flags & FIF_FCSFAIL); + mac->pass_ctrl = !!(*new_flags & FIF_CONTROL); + mac->multicast_hash = hash; + spin_unlock_irqrestore(&mac->lock, flags); + + zd_chip_set_multicast_hash(&mac->chip, &hash); + + if (changed_flags & FIF_CONTROL) { + r = set_rx_filter(mac); + if (r) + dev_err(zd_mac_dev(mac), "set_rx_filter error %d\n", r); + } + + /* no handling required for FIF_OTHER_BSS as we don't currently + * do BSSID filtering */ + /* FIXME: in future it would be nice to enable the probe response + * filter (so that the driver doesn't see them) until + * FIF_BCN_PRBRESP_PROMISC is set. however due to atomicity here, we'd + * have to schedule work to enable prbresp reception, which might + * happen too late. For now we'll just listen and forward them all the + * time. */ +} + +static void set_rts_cts(struct zd_mac *mac, unsigned int short_preamble) +{ + mutex_lock(&mac->chip.mutex); + zd_chip_set_rts_cts_rate_locked(&mac->chip, short_preamble); + mutex_unlock(&mac->chip.mutex); +} + +static void zd_op_bss_info_changed(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *bss_conf, + u32 changes) +{ + struct zd_mac *mac = zd_hw_mac(hw); + int associated; + + dev_dbg_f(zd_mac_dev(mac), "changes: %x\n", changes); + + if (mac->type == NL80211_IFTYPE_MESH_POINT || + mac->type == NL80211_IFTYPE_ADHOC || + mac->type == NL80211_IFTYPE_AP) { + associated = true; + if (changes & BSS_CHANGED_BEACON) { + struct sk_buff *beacon = ieee80211_beacon_get(hw, vif); + + if (beacon) { + zd_chip_disable_hwint(&mac->chip); + zd_mac_config_beacon(hw, beacon, false); + zd_chip_enable_hwint(&mac->chip); + } + } + + if (changes & BSS_CHANGED_BEACON_ENABLED) { + u16 interval = 0; + u8 period = 0; + + if (bss_conf->enable_beacon) { + period = bss_conf->dtim_period; + interval = bss_conf->beacon_int; + } + + spin_lock_irq(&mac->lock); + mac->beacon.period = period; + mac->beacon.interval = interval; + mac->beacon.last_update = jiffies; + spin_unlock_irq(&mac->lock); + + zd_set_beacon_interval(&mac->chip, interval, period, + mac->type); + } + } else + associated = is_valid_ether_addr(bss_conf->bssid); + + spin_lock_irq(&mac->lock); + mac->associated = associated; + spin_unlock_irq(&mac->lock); + + /* TODO: do hardware bssid filtering */ + + if (changes & BSS_CHANGED_ERP_PREAMBLE) { + spin_lock_irq(&mac->lock); + mac->short_preamble = bss_conf->use_short_preamble; + spin_unlock_irq(&mac->lock); + + set_rts_cts(mac, bss_conf->use_short_preamble); + } +} + +static u64 zd_op_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif) +{ + struct zd_mac *mac = zd_hw_mac(hw); + return zd_chip_get_tsf(&mac->chip); +} + +static const struct ieee80211_ops zd_ops = { + .tx = zd_op_tx, + .start = zd_op_start, + .stop = zd_op_stop, + .add_interface = zd_op_add_interface, + .remove_interface = zd_op_remove_interface, + .config = zd_op_config, + .prepare_multicast = zd_op_prepare_multicast, + .configure_filter = zd_op_configure_filter, + .bss_info_changed = zd_op_bss_info_changed, + .get_tsf = zd_op_get_tsf, +}; + +struct ieee80211_hw *zd_mac_alloc_hw(struct usb_interface *intf) +{ + struct zd_mac *mac; + struct ieee80211_hw *hw; + + hw = ieee80211_alloc_hw(sizeof(struct zd_mac), &zd_ops); + if (!hw) { + dev_dbg_f(&intf->dev, "out of memory\n"); + return NULL; + } + + mac = zd_hw_mac(hw); + + memset(mac, 0, sizeof(*mac)); + spin_lock_init(&mac->lock); + mac->hw = hw; + + mac->type = NL80211_IFTYPE_UNSPECIFIED; + + memcpy(mac->channels, zd_channels, sizeof(zd_channels)); + memcpy(mac->rates, zd_rates, sizeof(zd_rates)); + mac->band.n_bitrates = ARRAY_SIZE(zd_rates); + mac->band.bitrates = mac->rates; + mac->band.n_channels = ARRAY_SIZE(zd_channels); + mac->band.channels = mac->channels; + + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &mac->band; + + hw->flags = IEEE80211_HW_RX_INCLUDES_FCS | + IEEE80211_HW_SIGNAL_UNSPEC | + IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING | + IEEE80211_HW_MFP_CAPABLE; + + hw->wiphy->interface_modes = + BIT(NL80211_IFTYPE_MESH_POINT) | + BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_AP); + + hw->max_signal = 100; + hw->queues = 1; + hw->extra_tx_headroom = sizeof(struct zd_ctrlset); + + /* + * Tell mac80211 that we support multi rate retries + */ + hw->max_rates = IEEE80211_TX_MAX_RATES; + hw->max_rate_tries = 18; /* 9 rates * 2 retries/rate */ + + skb_queue_head_init(&mac->ack_wait_queue); + mac->ack_pending = 0; + + zd_chip_init(&mac->chip, hw, intf); + housekeeping_init(mac); + beacon_init(mac); + INIT_WORK(&mac->process_intr, zd_process_intr); + + SET_IEEE80211_DEV(hw, &intf->dev); + return hw; +} + +#define BEACON_WATCHDOG_DELAY round_jiffies_relative(HZ) + +static void beacon_watchdog_handler(struct work_struct *work) +{ + struct zd_mac *mac = + container_of(work, struct zd_mac, beacon.watchdog_work.work); + struct sk_buff *beacon; + unsigned long timeout; + int interval, period; + + if (!test_bit(ZD_DEVICE_RUNNING, &mac->flags)) + goto rearm; + if (mac->type != NL80211_IFTYPE_AP || !mac->vif) + goto rearm; + + spin_lock_irq(&mac->lock); + interval = mac->beacon.interval; + period = mac->beacon.period; + timeout = mac->beacon.last_update + + msecs_to_jiffies(interval * 1024 / 1000) * 3; + spin_unlock_irq(&mac->lock); + + if (interval > 0 && time_is_before_jiffies(timeout)) { + dev_dbg_f(zd_mac_dev(mac), "beacon interrupt stalled, " + "restarting. " + "(interval: %d, dtim: %d)\n", + interval, period); + + zd_chip_disable_hwint(&mac->chip); + + beacon = ieee80211_beacon_get(mac->hw, mac->vif); + if (beacon) { + zd_mac_free_cur_beacon(mac); + + zd_mac_config_beacon(mac->hw, beacon, false); + } + + zd_set_beacon_interval(&mac->chip, interval, period, mac->type); + + zd_chip_enable_hwint(&mac->chip); + + spin_lock_irq(&mac->lock); + mac->beacon.last_update = jiffies; + spin_unlock_irq(&mac->lock); + } + +rearm: + queue_delayed_work(zd_workqueue, &mac->beacon.watchdog_work, + BEACON_WATCHDOG_DELAY); +} + +static void beacon_init(struct zd_mac *mac) +{ + INIT_DELAYED_WORK(&mac->beacon.watchdog_work, beacon_watchdog_handler); +} + +static void beacon_enable(struct zd_mac *mac) +{ + dev_dbg_f(zd_mac_dev(mac), "\n"); + + mac->beacon.last_update = jiffies; + queue_delayed_work(zd_workqueue, &mac->beacon.watchdog_work, + BEACON_WATCHDOG_DELAY); +} + +static void beacon_disable(struct zd_mac *mac) +{ + dev_dbg_f(zd_mac_dev(mac), "\n"); + cancel_delayed_work_sync(&mac->beacon.watchdog_work); + + zd_mac_free_cur_beacon(mac); +} + +#define LINK_LED_WORK_DELAY HZ + +static void link_led_handler(struct work_struct *work) +{ + struct zd_mac *mac = + container_of(work, struct zd_mac, housekeeping.link_led_work.work); + struct zd_chip *chip = &mac->chip; + int is_associated; + int r; + + if (!test_bit(ZD_DEVICE_RUNNING, &mac->flags)) + goto requeue; + + spin_lock_irq(&mac->lock); + is_associated = mac->associated; + spin_unlock_irq(&mac->lock); + + r = zd_chip_control_leds(chip, + is_associated ? ZD_LED_ASSOCIATED : ZD_LED_SCANNING); + if (r) + dev_dbg_f(zd_mac_dev(mac), "zd_chip_control_leds error %d\n", r); + +requeue: + queue_delayed_work(zd_workqueue, &mac->housekeeping.link_led_work, + LINK_LED_WORK_DELAY); +} + +static void housekeeping_init(struct zd_mac *mac) +{ + INIT_DELAYED_WORK(&mac->housekeeping.link_led_work, link_led_handler); +} + +static void housekeeping_enable(struct zd_mac *mac) +{ + dev_dbg_f(zd_mac_dev(mac), "\n"); + queue_delayed_work(zd_workqueue, &mac->housekeeping.link_led_work, + 0); +} + +static void housekeeping_disable(struct zd_mac *mac) +{ + dev_dbg_f(zd_mac_dev(mac), "\n"); + cancel_delayed_work_sync(&mac->housekeeping.link_led_work); + zd_chip_control_leds(&mac->chip, ZD_LED_OFF); +} diff --git a/drivers/net/wireless/zd1211rw/zd_mac.h b/drivers/net/wireless/zd1211rw/zd_mac.h new file mode 100644 index 0000000..c01eca8 --- /dev/null +++ b/drivers/net/wireless/zd1211rw/zd_mac.h @@ -0,0 +1,328 @@ +/* ZD1211 USB-WLAN driver for Linux + * + * Copyright (C) 2005-2007 Ulrich Kunitz + * Copyright (C) 2006-2007 Daniel Drake + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ZD_MAC_H +#define _ZD_MAC_H + +#include +#include + +#include "zd_chip.h" + +struct zd_ctrlset { + u8 modulation; + __le16 tx_length; + u8 control; + /* stores only the difference to tx_length on ZD1211B */ + __le16 packet_length; + __le16 current_length; + u8 service; + __le16 next_frame_length; +} __packed; + +#define ZD_CS_RESERVED_SIZE 25 + +/* The field modulation of struct zd_ctrlset controls the bit rate, the use + * of short or long preambles in 802.11b (CCK mode) or the use of 802.11a or + * 802.11g in OFDM mode. + * + * The term zd-rate is used for the combination of the modulation type flag + * and the "pure" rate value. + */ +#define ZD_PURE_RATE_MASK 0x0f +#define ZD_MODULATION_TYPE_MASK 0x10 +#define ZD_RATE_MASK (ZD_PURE_RATE_MASK|ZD_MODULATION_TYPE_MASK) +#define ZD_PURE_RATE(modulation) ((modulation) & ZD_PURE_RATE_MASK) +#define ZD_MODULATION_TYPE(modulation) ((modulation) & ZD_MODULATION_TYPE_MASK) +#define ZD_RATE(modulation) ((modulation) & ZD_RATE_MASK) + +/* The two possible modulation types. Notify that 802.11b doesn't use the CCK + * codeing for the 1 and 2 MBit/s rate. We stay with the term here to remain + * consistent with uses the term at other places. + */ +#define ZD_CCK 0x00 +#define ZD_OFDM 0x10 + +/* The ZD1211 firmware uses proprietary encodings of the 802.11b (CCK) rates. + * For OFDM the PLCP rate encodings are used. We combine these "pure" rates + * with the modulation type flag and call the resulting values zd-rates. + */ +#define ZD_CCK_RATE_1M (ZD_CCK|0x00) +#define ZD_CCK_RATE_2M (ZD_CCK|0x01) +#define ZD_CCK_RATE_5_5M (ZD_CCK|0x02) +#define ZD_CCK_RATE_11M (ZD_CCK|0x03) +#define ZD_OFDM_RATE_6M (ZD_OFDM|ZD_OFDM_PLCP_RATE_6M) +#define ZD_OFDM_RATE_9M (ZD_OFDM|ZD_OFDM_PLCP_RATE_9M) +#define ZD_OFDM_RATE_12M (ZD_OFDM|ZD_OFDM_PLCP_RATE_12M) +#define ZD_OFDM_RATE_18M (ZD_OFDM|ZD_OFDM_PLCP_RATE_18M) +#define ZD_OFDM_RATE_24M (ZD_OFDM|ZD_OFDM_PLCP_RATE_24M) +#define ZD_OFDM_RATE_36M (ZD_OFDM|ZD_OFDM_PLCP_RATE_36M) +#define ZD_OFDM_RATE_48M (ZD_OFDM|ZD_OFDM_PLCP_RATE_48M) +#define ZD_OFDM_RATE_54M (ZD_OFDM|ZD_OFDM_PLCP_RATE_54M) + +/* The bit 5 of the zd_ctrlset modulation field controls the preamble in CCK + * mode or the 802.11a/802.11g selection in OFDM mode. + */ +#define ZD_CCK_PREA_LONG 0x00 +#define ZD_CCK_PREA_SHORT 0x20 +#define ZD_OFDM_MODE_11G 0x00 +#define ZD_OFDM_MODE_11A 0x20 + +/* zd_ctrlset control field */ +#define ZD_CS_NEED_RANDOM_BACKOFF 0x01 +#define ZD_CS_NO_ACK 0x02 + +#define ZD_CS_FRAME_TYPE_MASK 0x0c +#define ZD_CS_DATA_FRAME 0x00 +#define ZD_CS_PS_POLL_FRAME 0x04 +#define ZD_CS_MANAGEMENT_FRAME 0x08 +#define ZD_CS_NO_SEQUENCE_CTL_FRAME 0x0c + +#define ZD_CS_WAKE_DESTINATION 0x10 +#define ZD_CS_RTS 0x20 +#define ZD_CS_ENCRYPT 0x40 +#define ZD_CS_SELF_CTS 0x80 + +/* Incoming frames are prepended by a PLCP header */ +#define ZD_PLCP_HEADER_SIZE 5 + +struct rx_length_info { + __le16 length[3]; + __le16 tag; +} __packed; + +#define RX_LENGTH_INFO_TAG 0x697e + +struct rx_status { + u8 signal_quality_cck; + /* rssi */ + u8 signal_strength; + u8 signal_quality_ofdm; + u8 decryption_type; + u8 frame_status; +} __packed; + +/* rx_status field decryption_type */ +#define ZD_RX_NO_WEP 0 +#define ZD_RX_WEP64 1 +#define ZD_RX_TKIP 2 +#define ZD_RX_AES 4 +#define ZD_RX_WEP128 5 +#define ZD_RX_WEP256 6 + +/* rx_status field frame_status */ +#define ZD_RX_FRAME_MODULATION_MASK 0x01 +#define ZD_RX_CCK 0x00 +#define ZD_RX_OFDM 0x01 + +#define ZD_RX_TIMEOUT_ERROR 0x02 +#define ZD_RX_FIFO_OVERRUN_ERROR 0x04 +#define ZD_RX_DECRYPTION_ERROR 0x08 +#define ZD_RX_CRC32_ERROR 0x10 +#define ZD_RX_NO_ADDR1_MATCH_ERROR 0x20 +#define ZD_RX_CRC16_ERROR 0x40 +#define ZD_RX_ERROR 0x80 + +struct tx_retry_rate { + int count; /* number of valid element in rate[] array */ + int rate[10]; /* retry rates, described by an index in zd_rates[] */ +}; + +struct tx_status { + u8 type; /* must always be 0x01 : USB_INT_TYPE */ + u8 id; /* must always be 0xa0 : USB_INT_ID_RETRY_FAILED */ + u8 rate; + u8 pad; + u8 mac[ETH_ALEN]; + u8 retry; + u8 failure; +} __packed; + +enum mac_flags { + MAC_FIXED_CHANNEL = 0x01, +}; + +struct housekeeping { + struct delayed_work link_led_work; +}; + +struct beacon { + struct delayed_work watchdog_work; + struct sk_buff *cur_beacon; + unsigned long last_update; + u16 interval; + u8 period; +}; + +enum zd_device_flags { + ZD_DEVICE_RUNNING, +}; + +#define ZD_MAC_STATS_BUFFER_SIZE 16 + +#define ZD_MAC_MAX_ACK_WAITERS 50 + +struct zd_mac { + struct zd_chip chip; + spinlock_t lock; + spinlock_t intr_lock; + struct ieee80211_hw *hw; + struct ieee80211_vif *vif; + struct housekeeping housekeeping; + struct beacon beacon; + struct work_struct set_rts_cts_work; + struct work_struct process_intr; + struct zd_mc_hash multicast_hash; + u8 intr_buffer[USB_MAX_EP_INT_BUFFER]; + u8 regdomain; + u8 default_regdomain; + u8 channel; + int type; + int associated; + unsigned long flags; + struct sk_buff_head ack_wait_queue; + struct ieee80211_channel channels[14]; + struct ieee80211_rate rates[12]; + struct ieee80211_supported_band band; + + /* Short preamble (used for RTS/CTS) */ + unsigned int short_preamble:1; + + /* whether to pass frames with CRC errors to stack */ + unsigned int pass_failed_fcs:1; + + /* whether to pass control frames to stack */ + unsigned int pass_ctrl:1; + + /* whether we have received a 802.11 ACK that is pending */ + unsigned int ack_pending:1; + + /* signal strength of the last 802.11 ACK received */ + int ack_signal; +}; + +#define ZD_REGDOMAIN_FCC 0x10 +#define ZD_REGDOMAIN_IC 0x20 +#define ZD_REGDOMAIN_ETSI 0x30 +#define ZD_REGDOMAIN_SPAIN 0x31 +#define ZD_REGDOMAIN_FRANCE 0x32 +#define ZD_REGDOMAIN_JAPAN_2 0x40 +#define ZD_REGDOMAIN_JAPAN 0x41 +#define ZD_REGDOMAIN_JAPAN_3 0x49 + +enum { + MIN_CHANNEL24 = 1, + MAX_CHANNEL24 = 14, +}; + +#define ZD_PLCP_SERVICE_LENGTH_EXTENSION 0x80 + +struct ofdm_plcp_header { + u8 prefix[3]; + __le16 service; +} __packed; + +static inline u8 zd_ofdm_plcp_header_rate(const struct ofdm_plcp_header *header) +{ + return header->prefix[0] & 0xf; +} + +/* The following defines give the encoding of the 4-bit rate field in the + * OFDM (802.11a/802.11g) PLCP header. Notify that these values are used to + * define the zd-rate values for OFDM. + * + * See the struct zd_ctrlset definition in zd_mac.h. + */ +#define ZD_OFDM_PLCP_RATE_6M 0xb +#define ZD_OFDM_PLCP_RATE_9M 0xf +#define ZD_OFDM_PLCP_RATE_12M 0xa +#define ZD_OFDM_PLCP_RATE_18M 0xe +#define ZD_OFDM_PLCP_RATE_24M 0x9 +#define ZD_OFDM_PLCP_RATE_36M 0xd +#define ZD_OFDM_PLCP_RATE_48M 0x8 +#define ZD_OFDM_PLCP_RATE_54M 0xc + +struct cck_plcp_header { + u8 signal; + u8 service; + __le16 length; + __le16 crc16; +} __packed; + +static inline u8 zd_cck_plcp_header_signal(const struct cck_plcp_header *header) +{ + return header->signal; +} + +/* These defines give the encodings of the signal field in the 802.11b PLCP + * header. The signal field gives the bit rate of the following packet. Even + * if technically wrong we use CCK here also for the 1 MBit/s and 2 MBit/s + * rate to stay consistent with Zydas and our use of the term. + * + * Notify that these values are *not* used in the zd-rates. + */ +#define ZD_CCK_PLCP_SIGNAL_1M 0x0a +#define ZD_CCK_PLCP_SIGNAL_2M 0x14 +#define ZD_CCK_PLCP_SIGNAL_5M5 0x37 +#define ZD_CCK_PLCP_SIGNAL_11M 0x6e + +static inline struct zd_mac *zd_hw_mac(struct ieee80211_hw *hw) +{ + return hw->priv; +} + +static inline struct zd_mac *zd_chip_to_mac(struct zd_chip *chip) +{ + return container_of(chip, struct zd_mac, chip); +} + +static inline struct zd_mac *zd_usb_to_mac(struct zd_usb *usb) +{ + return zd_chip_to_mac(zd_usb_to_chip(usb)); +} + +static inline u8 *zd_mac_get_perm_addr(struct zd_mac *mac) +{ + return mac->hw->wiphy->perm_addr; +} + +#define zd_mac_dev(mac) (zd_chip_dev(&(mac)->chip)) + +struct ieee80211_hw *zd_mac_alloc_hw(struct usb_interface *intf); +void zd_mac_clear(struct zd_mac *mac); + +int zd_mac_preinit_hw(struct ieee80211_hw *hw); +int zd_mac_init_hw(struct ieee80211_hw *hw); + +int zd_mac_rx(struct ieee80211_hw *hw, const u8 *buffer, unsigned int length); +void zd_mac_tx_failed(struct urb *urb); +void zd_mac_tx_to_dev(struct sk_buff *skb, int error); + +int zd_op_start(struct ieee80211_hw *hw); +void zd_op_stop(struct ieee80211_hw *hw); +int zd_restore_settings(struct zd_mac *mac); + +#ifdef DEBUG +void zd_dump_rx_status(const struct rx_status *status); +#else +#define zd_dump_rx_status(status) +#endif /* DEBUG */ + +#endif /* _ZD_MAC_H */ diff --git a/drivers/net/wireless/zd1211rw/zd_rf.c b/drivers/net/wireless/zd1211rw/zd_rf.c new file mode 100644 index 0000000..c875ee0 --- /dev/null +++ b/drivers/net/wireless/zd1211rw/zd_rf.c @@ -0,0 +1,182 @@ +/* ZD1211 USB-WLAN driver for Linux + * + * Copyright (C) 2005-2007 Ulrich Kunitz + * Copyright (C) 2006-2007 Daniel Drake + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +#include "zd_def.h" +#include "zd_rf.h" +#include "zd_mac.h" +#include "zd_chip.h" + +static const char * const rfs[] = { + [0] = "unknown RF0", + [1] = "unknown RF1", + [UW2451_RF] = "UW2451_RF", + [UCHIP_RF] = "UCHIP_RF", + [AL2230_RF] = "AL2230_RF", + [AL7230B_RF] = "AL7230B_RF", + [THETA_RF] = "THETA_RF", + [AL2210_RF] = "AL2210_RF", + [MAXIM_NEW_RF] = "MAXIM_NEW_RF", + [UW2453_RF] = "UW2453_RF", + [AL2230S_RF] = "AL2230S_RF", + [RALINK_RF] = "RALINK_RF", + [INTERSIL_RF] = "INTERSIL_RF", + [RF2959_RF] = "RF2959_RF", + [MAXIM_NEW2_RF] = "MAXIM_NEW2_RF", + [PHILIPS_RF] = "PHILIPS_RF", +}; + +const char *zd_rf_name(u8 type) +{ + if (type & 0xf0) + type = 0; + return rfs[type]; +} + +void zd_rf_init(struct zd_rf *rf) +{ + memset(rf, 0, sizeof(*rf)); + + /* default to update channel integration, as almost all RF's do want + * this */ + rf->update_channel_int = 1; +} + +void zd_rf_clear(struct zd_rf *rf) +{ + if (rf->clear) + rf->clear(rf); + ZD_MEMCLEAR(rf, sizeof(*rf)); +} + +int zd_rf_init_hw(struct zd_rf *rf, u8 type) +{ + int r = 0; + int t; + struct zd_chip *chip = zd_rf_to_chip(rf); + + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + switch (type) { + case RF2959_RF: + r = zd_rf_init_rf2959(rf); + break; + case AL2230_RF: + case AL2230S_RF: + r = zd_rf_init_al2230(rf); + break; + case AL7230B_RF: + r = zd_rf_init_al7230b(rf); + break; + case MAXIM_NEW_RF: + case UW2453_RF: + r = zd_rf_init_uw2453(rf); + break; + default: + dev_err(zd_chip_dev(chip), + "RF %s %#x is not supported\n", zd_rf_name(type), type); + rf->type = 0; + return -ENODEV; + } + + if (r) + return r; + + rf->type = type; + + r = zd_chip_lock_phy_regs(chip); + if (r) + return r; + t = rf->init_hw(rf); + r = zd_chip_unlock_phy_regs(chip); + if (t) + r = t; + return r; +} + +int zd_rf_scnprint_id(struct zd_rf *rf, char *buffer, size_t size) +{ + return scnprintf(buffer, size, "%s", zd_rf_name(rf->type)); +} + +int zd_rf_set_channel(struct zd_rf *rf, u8 channel) +{ + int r; + + ZD_ASSERT(mutex_is_locked(&zd_rf_to_chip(rf)->mutex)); + if (channel < MIN_CHANNEL24) + return -EINVAL; + if (channel > MAX_CHANNEL24) + return -EINVAL; + dev_dbg_f(zd_chip_dev(zd_rf_to_chip(rf)), "channel: %d\n", channel); + + r = rf->set_channel(rf, channel); + if (r >= 0) + rf->channel = channel; + return r; +} + +int zd_switch_radio_on(struct zd_rf *rf) +{ + int r, t; + struct zd_chip *chip = zd_rf_to_chip(rf); + + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + r = zd_chip_lock_phy_regs(chip); + if (r) + return r; + t = rf->switch_radio_on(rf); + r = zd_chip_unlock_phy_regs(chip); + if (t) + r = t; + return r; +} + +int zd_switch_radio_off(struct zd_rf *rf) +{ + int r, t; + struct zd_chip *chip = zd_rf_to_chip(rf); + + /* TODO: move phy regs handling to zd_chip */ + ZD_ASSERT(mutex_is_locked(&chip->mutex)); + r = zd_chip_lock_phy_regs(chip); + if (r) + return r; + t = rf->switch_radio_off(rf); + r = zd_chip_unlock_phy_regs(chip); + if (t) + r = t; + return r; +} + +int zd_rf_patch_6m_band_edge(struct zd_rf *rf, u8 channel) +{ + if (!rf->patch_6m_band_edge) + return 0; + + return rf->patch_6m_band_edge(rf, channel); +} + +int zd_rf_generic_patch_6m(struct zd_rf *rf, u8 channel) +{ + return zd_chip_generic_patch_6m_band(zd_rf_to_chip(rf), channel); +} + diff --git a/drivers/net/wireless/zd1211rw/zd_rf.h b/drivers/net/wireless/zd1211rw/zd_rf.h new file mode 100644 index 0000000..725b7c9 --- /dev/null +++ b/drivers/net/wireless/zd1211rw/zd_rf.h @@ -0,0 +1,111 @@ +/* ZD1211 USB-WLAN driver for Linux + * + * Copyright (C) 2005-2007 Ulrich Kunitz + * Copyright (C) 2006-2007 Daniel Drake + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ZD_RF_H +#define _ZD_RF_H + +#define UW2451_RF 0x2 +#define UCHIP_RF 0x3 +#define AL2230_RF 0x4 +#define AL7230B_RF 0x5 /* a,b,g */ +#define THETA_RF 0x6 +#define AL2210_RF 0x7 +#define MAXIM_NEW_RF 0x8 +#define UW2453_RF 0x9 +#define AL2230S_RF 0xa +#define RALINK_RF 0xb +#define INTERSIL_RF 0xc +#define RF2959_RF 0xd +#define MAXIM_NEW2_RF 0xe +#define PHILIPS_RF 0xf + +#define RF_CHANNEL(ch) [(ch)-1] + +/* Provides functions of the RF transceiver. */ + +enum { + RF_REG_BITS = 6, + RF_VALUE_BITS = 18, + RF_RV_BITS = RF_REG_BITS + RF_VALUE_BITS, +}; + +struct zd_rf { + u8 type; + + u8 channel; + + /* whether channel integration and calibration should be updated + * defaults to 1 (yes) */ + u8 update_channel_int:1; + + /* whether ZD_CR47 should be patched from the EEPROM, if the appropriate + * flag is set in the POD. The vendor driver suggests that this should + * be done for all RF's, but a bug in their code prevents but their + * HW_OverWritePhyRegFromE2P() routine from ever taking effect. */ + u8 patch_cck_gain:1; + + /* private RF driver data */ + void *priv; + + /* RF-specific functions */ + int (*init_hw)(struct zd_rf *rf); + int (*set_channel)(struct zd_rf *rf, u8 channel); + int (*switch_radio_on)(struct zd_rf *rf); + int (*switch_radio_off)(struct zd_rf *rf); + int (*patch_6m_band_edge)(struct zd_rf *rf, u8 channel); + void (*clear)(struct zd_rf *rf); +}; + +const char *zd_rf_name(u8 type); +void zd_rf_init(struct zd_rf *rf); +void zd_rf_clear(struct zd_rf *rf); +int zd_rf_init_hw(struct zd_rf *rf, u8 type); + +int zd_rf_scnprint_id(struct zd_rf *rf, char *buffer, size_t size); + +int zd_rf_set_channel(struct zd_rf *rf, u8 channel); + +int zd_switch_radio_on(struct zd_rf *rf); +int zd_switch_radio_off(struct zd_rf *rf); + +int zd_rf_patch_6m_band_edge(struct zd_rf *rf, u8 channel); +int zd_rf_generic_patch_6m(struct zd_rf *rf, u8 channel); + +static inline int zd_rf_should_update_pwr_int(struct zd_rf *rf) +{ + return rf->update_channel_int; +} + +static inline int zd_rf_should_patch_cck_gain(struct zd_rf *rf) +{ + return rf->patch_cck_gain; +} + +int zd_rf_patch_6m_band_edge(struct zd_rf *rf, u8 channel); +int zd_rf_generic_patch_6m(struct zd_rf *rf, u8 channel); + +/* Functions for individual RF chips */ + +int zd_rf_init_rf2959(struct zd_rf *rf); +int zd_rf_init_al2230(struct zd_rf *rf); +int zd_rf_init_al7230b(struct zd_rf *rf); +int zd_rf_init_uw2453(struct zd_rf *rf); + +#endif /* _ZD_RF_H */ diff --git a/drivers/net/wireless/zd1211rw/zd_rf_al2230.c b/drivers/net/wireless/zd1211rw/zd_rf_al2230.c new file mode 100644 index 0000000..12babcb --- /dev/null +++ b/drivers/net/wireless/zd1211rw/zd_rf_al2230.c @@ -0,0 +1,444 @@ +/* ZD1211 USB-WLAN driver for Linux + * + * Copyright (C) 2005-2007 Ulrich Kunitz + * Copyright (C) 2006-2007 Daniel Drake + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#include "zd_rf.h" +#include "zd_usb.h" +#include "zd_chip.h" + +#define IS_AL2230S(chip) ((chip)->al2230s_bit || (chip)->rf.type == AL2230S_RF) + +static const u32 zd1211_al2230_table[][3] = { + RF_CHANNEL( 1) = { 0x03f790, 0x033331, 0x00000d, }, + RF_CHANNEL( 2) = { 0x03f790, 0x0b3331, 0x00000d, }, + RF_CHANNEL( 3) = { 0x03e790, 0x033331, 0x00000d, }, + RF_CHANNEL( 4) = { 0x03e790, 0x0b3331, 0x00000d, }, + RF_CHANNEL( 5) = { 0x03f7a0, 0x033331, 0x00000d, }, + RF_CHANNEL( 6) = { 0x03f7a0, 0x0b3331, 0x00000d, }, + RF_CHANNEL( 7) = { 0x03e7a0, 0x033331, 0x00000d, }, + RF_CHANNEL( 8) = { 0x03e7a0, 0x0b3331, 0x00000d, }, + RF_CHANNEL( 9) = { 0x03f7b0, 0x033331, 0x00000d, }, + RF_CHANNEL(10) = { 0x03f7b0, 0x0b3331, 0x00000d, }, + RF_CHANNEL(11) = { 0x03e7b0, 0x033331, 0x00000d, }, + RF_CHANNEL(12) = { 0x03e7b0, 0x0b3331, 0x00000d, }, + RF_CHANNEL(13) = { 0x03f7c0, 0x033331, 0x00000d, }, + RF_CHANNEL(14) = { 0x03e7c0, 0x066661, 0x00000d, }, +}; + +static const u32 zd1211b_al2230_table[][3] = { + RF_CHANNEL( 1) = { 0x09efc0, 0x8cccc0, 0xb00000, }, + RF_CHANNEL( 2) = { 0x09efc0, 0x8cccd0, 0xb00000, }, + RF_CHANNEL( 3) = { 0x09e7c0, 0x8cccc0, 0xb00000, }, + RF_CHANNEL( 4) = { 0x09e7c0, 0x8cccd0, 0xb00000, }, + RF_CHANNEL( 5) = { 0x05efc0, 0x8cccc0, 0xb00000, }, + RF_CHANNEL( 6) = { 0x05efc0, 0x8cccd0, 0xb00000, }, + RF_CHANNEL( 7) = { 0x05e7c0, 0x8cccc0, 0xb00000, }, + RF_CHANNEL( 8) = { 0x05e7c0, 0x8cccd0, 0xb00000, }, + RF_CHANNEL( 9) = { 0x0defc0, 0x8cccc0, 0xb00000, }, + RF_CHANNEL(10) = { 0x0defc0, 0x8cccd0, 0xb00000, }, + RF_CHANNEL(11) = { 0x0de7c0, 0x8cccc0, 0xb00000, }, + RF_CHANNEL(12) = { 0x0de7c0, 0x8cccd0, 0xb00000, }, + RF_CHANNEL(13) = { 0x03efc0, 0x8cccc0, 0xb00000, }, + RF_CHANNEL(14) = { 0x03e7c0, 0x866660, 0xb00000, }, +}; + +static const struct zd_ioreq16 zd1211b_ioreqs_shared_1[] = { + { ZD_CR240, 0x57 }, { ZD_CR9, 0xe0 }, +}; + +static const struct zd_ioreq16 ioreqs_init_al2230s[] = { + { ZD_CR47, 0x1e }, /* MARK_002 */ + { ZD_CR106, 0x22 }, + { ZD_CR107, 0x2a }, /* MARK_002 */ + { ZD_CR109, 0x13 }, /* MARK_002 */ + { ZD_CR118, 0xf8 }, /* MARK_002 */ + { ZD_CR119, 0x12 }, { ZD_CR122, 0xe0 }, + { ZD_CR128, 0x10 }, /* MARK_001 from 0xe->0x10 */ + { ZD_CR129, 0x0e }, /* MARK_001 from 0xd->0x0e */ + { ZD_CR130, 0x10 }, /* MARK_001 from 0xb->0x0d */ +}; + +static int zd1211b_al2230_finalize_rf(struct zd_chip *chip) +{ + int r; + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR80, 0x30 }, { ZD_CR81, 0x30 }, { ZD_CR79, 0x58 }, + { ZD_CR12, 0xf0 }, { ZD_CR77, 0x1b }, { ZD_CR78, 0x58 }, + { ZD_CR203, 0x06 }, + { }, + + { ZD_CR240, 0x80 }, + }; + + r = zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); + if (r) + return r; + + /* related to antenna selection? */ + if (chip->new_phy_layout) { + r = zd_iowrite16_locked(chip, 0xe1, ZD_CR9); + if (r) + return r; + } + + return zd_iowrite16_locked(chip, 0x06, ZD_CR203); +} + +static int zd1211_al2230_init_hw(struct zd_rf *rf) +{ + int r; + struct zd_chip *chip = zd_rf_to_chip(rf); + + static const struct zd_ioreq16 ioreqs_init[] = { + { ZD_CR15, 0x20 }, { ZD_CR23, 0x40 }, { ZD_CR24, 0x20 }, + { ZD_CR26, 0x11 }, { ZD_CR28, 0x3e }, { ZD_CR29, 0x00 }, + { ZD_CR44, 0x33 }, { ZD_CR106, 0x2a }, { ZD_CR107, 0x1a }, + { ZD_CR109, 0x09 }, { ZD_CR110, 0x27 }, { ZD_CR111, 0x2b }, + { ZD_CR112, 0x2b }, { ZD_CR119, 0x0a }, { ZD_CR10, 0x89 }, + /* for newest (3rd cut) AL2300 */ + { ZD_CR17, 0x28 }, + { ZD_CR26, 0x93 }, { ZD_CR34, 0x30 }, + /* for newest (3rd cut) AL2300 */ + { ZD_CR35, 0x3e }, + { ZD_CR41, 0x24 }, { ZD_CR44, 0x32 }, + /* for newest (3rd cut) AL2300 */ + { ZD_CR46, 0x96 }, + { ZD_CR47, 0x1e }, { ZD_CR79, 0x58 }, { ZD_CR80, 0x30 }, + { ZD_CR81, 0x30 }, { ZD_CR87, 0x0a }, { ZD_CR89, 0x04 }, + { ZD_CR92, 0x0a }, { ZD_CR99, 0x28 }, { ZD_CR100, 0x00 }, + { ZD_CR101, 0x13 }, { ZD_CR102, 0x27 }, { ZD_CR106, 0x24 }, + { ZD_CR107, 0x2a }, { ZD_CR109, 0x09 }, { ZD_CR110, 0x13 }, + { ZD_CR111, 0x1f }, { ZD_CR112, 0x1f }, { ZD_CR113, 0x27 }, + { ZD_CR114, 0x27 }, + /* for newest (3rd cut) AL2300 */ + { ZD_CR115, 0x24 }, + { ZD_CR116, 0x24 }, { ZD_CR117, 0xf4 }, { ZD_CR118, 0xfc }, + { ZD_CR119, 0x10 }, { ZD_CR120, 0x4f }, { ZD_CR121, 0x77 }, + { ZD_CR122, 0xe0 }, { ZD_CR137, 0x88 }, { ZD_CR252, 0xff }, + { ZD_CR253, 0xff }, + }; + + static const struct zd_ioreq16 ioreqs_pll[] = { + /* shdnb(PLL_ON)=0 */ + { ZD_CR251, 0x2f }, + /* shdnb(PLL_ON)=1 */ + { ZD_CR251, 0x3f }, + { ZD_CR138, 0x28 }, { ZD_CR203, 0x06 }, + }; + + static const u32 rv1[] = { + /* Channel 1 */ + 0x03f790, + 0x033331, + 0x00000d, + + 0x0b3331, + 0x03b812, + 0x00fff3, + }; + + static const u32 rv2[] = { + 0x000da4, + 0x0f4dc5, /* fix freq shift, 0x04edc5 */ + 0x0805b6, + 0x011687, + 0x000688, + 0x0403b9, /* external control TX power (ZD_CR31) */ + 0x00dbba, + 0x00099b, + 0x0bdffc, + 0x00000d, + 0x00500f, + }; + + static const u32 rv3[] = { + 0x00d00f, + 0x004c0f, + 0x00540f, + 0x00700f, + 0x00500f, + }; + + r = zd_iowrite16a_locked(chip, ioreqs_init, ARRAY_SIZE(ioreqs_init)); + if (r) + return r; + + if (IS_AL2230S(chip)) { + r = zd_iowrite16a_locked(chip, ioreqs_init_al2230s, + ARRAY_SIZE(ioreqs_init_al2230s)); + if (r) + return r; + } + + r = zd_rfwritev_locked(chip, rv1, ARRAY_SIZE(rv1), RF_RV_BITS); + if (r) + return r; + + /* improve band edge for AL2230S */ + if (IS_AL2230S(chip)) + r = zd_rfwrite_locked(chip, 0x000824, RF_RV_BITS); + else + r = zd_rfwrite_locked(chip, 0x0005a4, RF_RV_BITS); + if (r) + return r; + + r = zd_rfwritev_locked(chip, rv2, ARRAY_SIZE(rv2), RF_RV_BITS); + if (r) + return r; + + r = zd_iowrite16a_locked(chip, ioreqs_pll, ARRAY_SIZE(ioreqs_pll)); + if (r) + return r; + + r = zd_rfwritev_locked(chip, rv3, ARRAY_SIZE(rv3), RF_RV_BITS); + if (r) + return r; + + return 0; +} + +static int zd1211b_al2230_init_hw(struct zd_rf *rf) +{ + int r; + struct zd_chip *chip = zd_rf_to_chip(rf); + + static const struct zd_ioreq16 ioreqs1[] = { + { ZD_CR10, 0x89 }, { ZD_CR15, 0x20 }, + { ZD_CR17, 0x2B }, /* for newest(3rd cut) AL2230 */ + { ZD_CR23, 0x40 }, { ZD_CR24, 0x20 }, { ZD_CR26, 0x93 }, + { ZD_CR28, 0x3e }, { ZD_CR29, 0x00 }, + { ZD_CR33, 0x28 }, /* 5621 */ + { ZD_CR34, 0x30 }, + { ZD_CR35, 0x3e }, /* for newest(3rd cut) AL2230 */ + { ZD_CR41, 0x24 }, { ZD_CR44, 0x32 }, + { ZD_CR46, 0x99 }, /* for newest(3rd cut) AL2230 */ + { ZD_CR47, 0x1e }, + + /* ZD1211B 05.06.10 */ + { ZD_CR48, 0x06 }, { ZD_CR49, 0xf9 }, { ZD_CR51, 0x01 }, + { ZD_CR52, 0x80 }, { ZD_CR53, 0x7e }, { ZD_CR65, 0x00 }, + { ZD_CR66, 0x00 }, { ZD_CR67, 0x00 }, { ZD_CR68, 0x00 }, + { ZD_CR69, 0x28 }, + + { ZD_CR79, 0x58 }, { ZD_CR80, 0x30 }, { ZD_CR81, 0x30 }, + { ZD_CR87, 0x0a }, { ZD_CR89, 0x04 }, + { ZD_CR91, 0x00 }, /* 5621 */ + { ZD_CR92, 0x0a }, + { ZD_CR98, 0x8d }, /* 4804, for 1212 new algorithm */ + { ZD_CR99, 0x00 }, /* 5621 */ + { ZD_CR101, 0x13 }, { ZD_CR102, 0x27 }, + { ZD_CR106, 0x24 }, /* for newest(3rd cut) AL2230 */ + { ZD_CR107, 0x2a }, + { ZD_CR109, 0x13 }, /* 4804, for 1212 new algorithm */ + { ZD_CR110, 0x1f }, /* 4804, for 1212 new algorithm */ + { ZD_CR111, 0x1f }, { ZD_CR112, 0x1f }, { ZD_CR113, 0x27 }, + { ZD_CR114, 0x27 }, + { ZD_CR115, 0x26 }, /* 24->26 at 4902 for newest(3rd cut) + * AL2230 + */ + { ZD_CR116, 0x24 }, + { ZD_CR117, 0xfa }, /* for 1211b */ + { ZD_CR118, 0xfa }, /* for 1211b */ + { ZD_CR119, 0x10 }, + { ZD_CR120, 0x4f }, + { ZD_CR121, 0x6c }, /* for 1211b */ + { ZD_CR122, 0xfc }, /* E0->FC at 4902 */ + { ZD_CR123, 0x57 }, /* 5623 */ + { ZD_CR125, 0xad }, /* 4804, for 1212 new algorithm */ + { ZD_CR126, 0x6c }, /* 5614 */ + { ZD_CR127, 0x03 }, /* 4804, for 1212 new algorithm */ + { ZD_CR137, 0x50 }, /* 5614 */ + { ZD_CR138, 0xa8 }, + { ZD_CR144, 0xac }, /* 5621 */ + { ZD_CR150, 0x0d }, { ZD_CR252, 0x34 }, { ZD_CR253, 0x34 }, + }; + + static const u32 rv1[] = { + 0x8cccd0, + 0x481dc0, + 0xcfff00, + 0x25a000, + }; + + static const u32 rv2[] = { + /* To improve AL2230 yield, improve phase noise, 4713 */ + 0x25a000, + 0xa3b2f0, + + 0x6da010, /* Reg6 update for MP versio */ + 0xe36280, /* Modified by jxiao for Bor-Chin on 2004/08/02 */ + 0x116000, + 0x9dc020, /* External control TX power (ZD_CR31) */ + 0x5ddb00, /* RegA update for MP version */ + 0xd99000, /* RegB update for MP version */ + 0x3ffbd0, /* RegC update for MP version */ + 0xb00000, /* RegD update for MP version */ + + /* improve phase noise and remove phase calibration,4713 */ + 0xf01a00, + }; + + static const struct zd_ioreq16 ioreqs2[] = { + { ZD_CR251, 0x2f }, /* shdnb(PLL_ON)=0 */ + { ZD_CR251, 0x7f }, /* shdnb(PLL_ON)=1 */ + }; + + static const u32 rv3[] = { + /* To improve AL2230 yield, 4713 */ + 0xf01b00, + 0xf01e00, + 0xf01a00, + }; + + static const struct zd_ioreq16 ioreqs3[] = { + /* related to 6M band edge patching, happens unconditionally */ + { ZD_CR128, 0x14 }, { ZD_CR129, 0x12 }, { ZD_CR130, 0x10 }, + }; + + r = zd_iowrite16a_locked(chip, zd1211b_ioreqs_shared_1, + ARRAY_SIZE(zd1211b_ioreqs_shared_1)); + if (r) + return r; + r = zd_iowrite16a_locked(chip, ioreqs1, ARRAY_SIZE(ioreqs1)); + if (r) + return r; + + if (IS_AL2230S(chip)) { + r = zd_iowrite16a_locked(chip, ioreqs_init_al2230s, + ARRAY_SIZE(ioreqs_init_al2230s)); + if (r) + return r; + } + + r = zd_rfwritev_cr_locked(chip, zd1211b_al2230_table[0], 3); + if (r) + return r; + r = zd_rfwritev_cr_locked(chip, rv1, ARRAY_SIZE(rv1)); + if (r) + return r; + + if (IS_AL2230S(chip)) + r = zd_rfwrite_locked(chip, 0x241000, RF_RV_BITS); + else + r = zd_rfwrite_locked(chip, 0x25a000, RF_RV_BITS); + if (r) + return r; + + r = zd_rfwritev_cr_locked(chip, rv2, ARRAY_SIZE(rv2)); + if (r) + return r; + r = zd_iowrite16a_locked(chip, ioreqs2, ARRAY_SIZE(ioreqs2)); + if (r) + return r; + r = zd_rfwritev_cr_locked(chip, rv3, ARRAY_SIZE(rv3)); + if (r) + return r; + r = zd_iowrite16a_locked(chip, ioreqs3, ARRAY_SIZE(ioreqs3)); + if (r) + return r; + return zd1211b_al2230_finalize_rf(chip); +} + +static int zd1211_al2230_set_channel(struct zd_rf *rf, u8 channel) +{ + int r; + const u32 *rv = zd1211_al2230_table[channel-1]; + struct zd_chip *chip = zd_rf_to_chip(rf); + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR138, 0x28 }, + { ZD_CR203, 0x06 }, + }; + + r = zd_rfwritev_locked(chip, rv, 3, RF_RV_BITS); + if (r) + return r; + return zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +static int zd1211b_al2230_set_channel(struct zd_rf *rf, u8 channel) +{ + int r; + const u32 *rv = zd1211b_al2230_table[channel-1]; + struct zd_chip *chip = zd_rf_to_chip(rf); + + r = zd_iowrite16a_locked(chip, zd1211b_ioreqs_shared_1, + ARRAY_SIZE(zd1211b_ioreqs_shared_1)); + if (r) + return r; + + r = zd_rfwritev_cr_locked(chip, rv, 3); + if (r) + return r; + + return zd1211b_al2230_finalize_rf(chip); +} + +static int zd1211_al2230_switch_radio_on(struct zd_rf *rf) +{ + struct zd_chip *chip = zd_rf_to_chip(rf); + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR11, 0x00 }, + { ZD_CR251, 0x3f }, + }; + + return zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +static int zd1211b_al2230_switch_radio_on(struct zd_rf *rf) +{ + struct zd_chip *chip = zd_rf_to_chip(rf); + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR11, 0x00 }, + { ZD_CR251, 0x7f }, + }; + + return zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +static int al2230_switch_radio_off(struct zd_rf *rf) +{ + struct zd_chip *chip = zd_rf_to_chip(rf); + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR11, 0x04 }, + { ZD_CR251, 0x2f }, + }; + + return zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +int zd_rf_init_al2230(struct zd_rf *rf) +{ + struct zd_chip *chip = zd_rf_to_chip(rf); + + rf->switch_radio_off = al2230_switch_radio_off; + if (zd_chip_is_zd1211b(chip)) { + rf->init_hw = zd1211b_al2230_init_hw; + rf->set_channel = zd1211b_al2230_set_channel; + rf->switch_radio_on = zd1211b_al2230_switch_radio_on; + } else { + rf->init_hw = zd1211_al2230_init_hw; + rf->set_channel = zd1211_al2230_set_channel; + rf->switch_radio_on = zd1211_al2230_switch_radio_on; + } + rf->patch_6m_band_edge = zd_rf_generic_patch_6m; + rf->patch_cck_gain = 1; + return 0; +} diff --git a/drivers/net/wireless/zd1211rw/zd_rf_al7230b.c b/drivers/net/wireless/zd1211rw/zd_rf_al7230b.c new file mode 100644 index 0000000..385c670 --- /dev/null +++ b/drivers/net/wireless/zd1211rw/zd_rf_al7230b.c @@ -0,0 +1,495 @@ +/* ZD1211 USB-WLAN driver for Linux + * + * Copyright (C) 2005-2007 Ulrich Kunitz + * Copyright (C) 2006-2007 Daniel Drake + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#include "zd_rf.h" +#include "zd_usb.h" +#include "zd_chip.h" + +static const u32 chan_rv[][2] = { + RF_CHANNEL( 1) = { 0x09ec00, 0x8cccc8 }, + RF_CHANNEL( 2) = { 0x09ec00, 0x8cccd8 }, + RF_CHANNEL( 3) = { 0x09ec00, 0x8cccc0 }, + RF_CHANNEL( 4) = { 0x09ec00, 0x8cccd0 }, + RF_CHANNEL( 5) = { 0x05ec00, 0x8cccc8 }, + RF_CHANNEL( 6) = { 0x05ec00, 0x8cccd8 }, + RF_CHANNEL( 7) = { 0x05ec00, 0x8cccc0 }, + RF_CHANNEL( 8) = { 0x05ec00, 0x8cccd0 }, + RF_CHANNEL( 9) = { 0x0dec00, 0x8cccc8 }, + RF_CHANNEL(10) = { 0x0dec00, 0x8cccd8 }, + RF_CHANNEL(11) = { 0x0dec00, 0x8cccc0 }, + RF_CHANNEL(12) = { 0x0dec00, 0x8cccd0 }, + RF_CHANNEL(13) = { 0x03ec00, 0x8cccc8 }, + RF_CHANNEL(14) = { 0x03ec00, 0x866660 }, +}; + +static const u32 std_rv[] = { + 0x4ff821, + 0xc5fbfc, + 0x21ebfe, + 0xafd401, /* freq shift 0xaad401 */ + 0x6cf56a, + 0xe04073, + 0x193d76, + 0x9dd844, + 0x500007, + 0xd8c010, +}; + +static const u32 rv_init1[] = { + 0x3c9000, + 0xbfffff, + 0x700000, + 0xf15d58, +}; + +static const u32 rv_init2[] = { + 0xf15d59, + 0xf15d5c, + 0xf15d58, +}; + +static const struct zd_ioreq16 ioreqs_sw[] = { + { ZD_CR128, 0x14 }, { ZD_CR129, 0x12 }, { ZD_CR130, 0x10 }, + { ZD_CR38, 0x38 }, { ZD_CR136, 0xdf }, +}; + +static int zd1211b_al7230b_finalize(struct zd_chip *chip) +{ + int r; + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR80, 0x30 }, { ZD_CR81, 0x30 }, { ZD_CR79, 0x58 }, + { ZD_CR12, 0xf0 }, { ZD_CR77, 0x1b }, { ZD_CR78, 0x58 }, + { ZD_CR203, 0x04 }, + { }, + { ZD_CR240, 0x80 }, + }; + + r = zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); + if (r) + return r; + + if (chip->new_phy_layout) { + /* antenna selection? */ + r = zd_iowrite16_locked(chip, 0xe5, ZD_CR9); + if (r) + return r; + } + + return zd_iowrite16_locked(chip, 0x04, ZD_CR203); +} + +static int zd1211_al7230b_init_hw(struct zd_rf *rf) +{ + int r; + struct zd_chip *chip = zd_rf_to_chip(rf); + + /* All of these writes are identical to AL2230 unless otherwise + * specified */ + static const struct zd_ioreq16 ioreqs_1[] = { + /* This one is 7230-specific, and happens before the rest */ + { ZD_CR240, 0x57 }, + { }, + + { ZD_CR15, 0x20 }, { ZD_CR23, 0x40 }, { ZD_CR24, 0x20 }, + { ZD_CR26, 0x11 }, { ZD_CR28, 0x3e }, { ZD_CR29, 0x00 }, + { ZD_CR44, 0x33 }, + /* This value is different for 7230 (was: 0x2a) */ + { ZD_CR106, 0x22 }, + { ZD_CR107, 0x1a }, { ZD_CR109, 0x09 }, { ZD_CR110, 0x27 }, + { ZD_CR111, 0x2b }, { ZD_CR112, 0x2b }, { ZD_CR119, 0x0a }, + /* This happened further down in AL2230, + * and the value changed (was: 0xe0) */ + { ZD_CR122, 0xfc }, + { ZD_CR10, 0x89 }, + /* for newest (3rd cut) AL2300 */ + { ZD_CR17, 0x28 }, + { ZD_CR26, 0x93 }, { ZD_CR34, 0x30 }, + /* for newest (3rd cut) AL2300 */ + { ZD_CR35, 0x3e }, + { ZD_CR41, 0x24 }, { ZD_CR44, 0x32 }, + /* for newest (3rd cut) AL2300 */ + { ZD_CR46, 0x96 }, + { ZD_CR47, 0x1e }, { ZD_CR79, 0x58 }, { ZD_CR80, 0x30 }, + { ZD_CR81, 0x30 }, { ZD_CR87, 0x0a }, { ZD_CR89, 0x04 }, + { ZD_CR92, 0x0a }, { ZD_CR99, 0x28 }, + /* This value is different for 7230 (was: 0x00) */ + { ZD_CR100, 0x02 }, + { ZD_CR101, 0x13 }, { ZD_CR102, 0x27 }, + /* This value is different for 7230 (was: 0x24) */ + { ZD_CR106, 0x22 }, + /* This value is different for 7230 (was: 0x2a) */ + { ZD_CR107, 0x3f }, + { ZD_CR109, 0x09 }, + /* This value is different for 7230 (was: 0x13) */ + { ZD_CR110, 0x1f }, + { ZD_CR111, 0x1f }, { ZD_CR112, 0x1f }, { ZD_CR113, 0x27 }, + { ZD_CR114, 0x27 }, + /* for newest (3rd cut) AL2300 */ + { ZD_CR115, 0x24 }, + /* This value is different for 7230 (was: 0x24) */ + { ZD_CR116, 0x3f }, + /* This value is different for 7230 (was: 0xf4) */ + { ZD_CR117, 0xfa }, + { ZD_CR118, 0xfc }, { ZD_CR119, 0x10 }, { ZD_CR120, 0x4f }, + { ZD_CR121, 0x77 }, { ZD_CR137, 0x88 }, + /* This one is 7230-specific */ + { ZD_CR138, 0xa8 }, + /* This value is different for 7230 (was: 0xff) */ + { ZD_CR252, 0x34 }, + /* This value is different for 7230 (was: 0xff) */ + { ZD_CR253, 0x34 }, + + /* PLL_OFF */ + { ZD_CR251, 0x2f }, + }; + + static const struct zd_ioreq16 ioreqs_2[] = { + { ZD_CR251, 0x3f }, /* PLL_ON */ + { ZD_CR128, 0x14 }, { ZD_CR129, 0x12 }, { ZD_CR130, 0x10 }, + { ZD_CR38, 0x38 }, { ZD_CR136, 0xdf }, + }; + + r = zd_iowrite16a_locked(chip, ioreqs_1, ARRAY_SIZE(ioreqs_1)); + if (r) + return r; + + r = zd_rfwritev_cr_locked(chip, chan_rv[0], ARRAY_SIZE(chan_rv[0])); + if (r) + return r; + + r = zd_rfwritev_cr_locked(chip, std_rv, ARRAY_SIZE(std_rv)); + if (r) + return r; + + r = zd_rfwritev_cr_locked(chip, rv_init1, ARRAY_SIZE(rv_init1)); + if (r) + return r; + + r = zd_iowrite16a_locked(chip, ioreqs_2, ARRAY_SIZE(ioreqs_2)); + if (r) + return r; + + r = zd_rfwritev_cr_locked(chip, rv_init2, ARRAY_SIZE(rv_init2)); + if (r) + return r; + + r = zd_iowrite16_locked(chip, 0x06, ZD_CR203); + if (r) + return r; + r = zd_iowrite16_locked(chip, 0x80, ZD_CR240); + if (r) + return r; + + return 0; +} + +static int zd1211b_al7230b_init_hw(struct zd_rf *rf) +{ + int r; + struct zd_chip *chip = zd_rf_to_chip(rf); + + static const struct zd_ioreq16 ioreqs_1[] = { + { ZD_CR240, 0x57 }, { ZD_CR9, 0x9 }, + { }, + { ZD_CR10, 0x8b }, { ZD_CR15, 0x20 }, + { ZD_CR17, 0x2B }, /* for newest (3rd cut) AL2230 */ + { ZD_CR20, 0x10 }, /* 4N25->Stone Request */ + { ZD_CR23, 0x40 }, { ZD_CR24, 0x20 }, { ZD_CR26, 0x93 }, + { ZD_CR28, 0x3e }, { ZD_CR29, 0x00 }, + { ZD_CR33, 0x28 }, /* 5613 */ + { ZD_CR34, 0x30 }, + { ZD_CR35, 0x3e }, /* for newest (3rd cut) AL2230 */ + { ZD_CR41, 0x24 }, { ZD_CR44, 0x32 }, + { ZD_CR46, 0x99 }, /* for newest (3rd cut) AL2230 */ + { ZD_CR47, 0x1e }, + + /* ZD1215 5610 */ + { ZD_CR48, 0x00 }, { ZD_CR49, 0x00 }, { ZD_CR51, 0x01 }, + { ZD_CR52, 0x80 }, { ZD_CR53, 0x7e }, { ZD_CR65, 0x00 }, + { ZD_CR66, 0x00 }, { ZD_CR67, 0x00 }, { ZD_CR68, 0x00 }, + { ZD_CR69, 0x28 }, + + { ZD_CR79, 0x58 }, { ZD_CR80, 0x30 }, { ZD_CR81, 0x30 }, + { ZD_CR87, 0x0A }, { ZD_CR89, 0x04 }, + { ZD_CR90, 0x58 }, /* 5112 */ + { ZD_CR91, 0x00 }, /* 5613 */ + { ZD_CR92, 0x0a }, + { ZD_CR98, 0x8d }, /* 4804, for 1212 new algorithm */ + { ZD_CR99, 0x00 }, { ZD_CR100, 0x02 }, { ZD_CR101, 0x13 }, + { ZD_CR102, 0x27 }, + { ZD_CR106, 0x20 }, /* change to 0x24 for AL7230B */ + { ZD_CR109, 0x13 }, /* 4804, for 1212 new algorithm */ + { ZD_CR112, 0x1f }, + }; + + static const struct zd_ioreq16 ioreqs_new_phy[] = { + { ZD_CR107, 0x28 }, + { ZD_CR110, 0x1f }, /* 5127, 0x13->0x1f */ + { ZD_CR111, 0x1f }, /* 0x13 to 0x1f for AL7230B */ + { ZD_CR116, 0x2a }, { ZD_CR118, 0xfa }, { ZD_CR119, 0x12 }, + { ZD_CR121, 0x6c }, /* 5613 */ + }; + + static const struct zd_ioreq16 ioreqs_old_phy[] = { + { ZD_CR107, 0x24 }, + { ZD_CR110, 0x13 }, /* 5127, 0x13->0x1f */ + { ZD_CR111, 0x13 }, /* 0x13 to 0x1f for AL7230B */ + { ZD_CR116, 0x24 }, { ZD_CR118, 0xfc }, { ZD_CR119, 0x11 }, + { ZD_CR121, 0x6a }, /* 5613 */ + }; + + static const struct zd_ioreq16 ioreqs_2[] = { + { ZD_CR113, 0x27 }, { ZD_CR114, 0x27 }, { ZD_CR115, 0x24 }, + { ZD_CR117, 0xfa }, { ZD_CR120, 0x4f }, + { ZD_CR122, 0xfc }, /* E0->FCh at 4901 */ + { ZD_CR123, 0x57 }, /* 5613 */ + { ZD_CR125, 0xad }, /* 4804, for 1212 new algorithm */ + { ZD_CR126, 0x6c }, /* 5613 */ + { ZD_CR127, 0x03 }, /* 4804, for 1212 new algorithm */ + { ZD_CR130, 0x10 }, + { ZD_CR131, 0x00 }, /* 5112 */ + { ZD_CR137, 0x50 }, /* 5613 */ + { ZD_CR138, 0xa8 }, /* 5112 */ + { ZD_CR144, 0xac }, /* 5613 */ + { ZD_CR148, 0x40 }, /* 5112 */ + { ZD_CR149, 0x40 }, /* 4O07, 50->40 */ + { ZD_CR150, 0x1a }, /* 5112, 0C->1A */ + { ZD_CR252, 0x34 }, { ZD_CR253, 0x34 }, + { ZD_CR251, 0x2f }, /* PLL_OFF */ + }; + + static const struct zd_ioreq16 ioreqs_3[] = { + { ZD_CR251, 0x7f }, /* PLL_ON */ + { ZD_CR128, 0x14 }, { ZD_CR129, 0x12 }, { ZD_CR130, 0x10 }, + { ZD_CR38, 0x38 }, { ZD_CR136, 0xdf }, + }; + + r = zd_iowrite16a_locked(chip, ioreqs_1, ARRAY_SIZE(ioreqs_1)); + if (r) + return r; + + if (chip->new_phy_layout) + r = zd_iowrite16a_locked(chip, ioreqs_new_phy, + ARRAY_SIZE(ioreqs_new_phy)); + else + r = zd_iowrite16a_locked(chip, ioreqs_old_phy, + ARRAY_SIZE(ioreqs_old_phy)); + if (r) + return r; + + r = zd_iowrite16a_locked(chip, ioreqs_2, ARRAY_SIZE(ioreqs_2)); + if (r) + return r; + + r = zd_rfwritev_cr_locked(chip, chan_rv[0], ARRAY_SIZE(chan_rv[0])); + if (r) + return r; + + r = zd_rfwritev_cr_locked(chip, std_rv, ARRAY_SIZE(std_rv)); + if (r) + return r; + + r = zd_rfwritev_cr_locked(chip, rv_init1, ARRAY_SIZE(rv_init1)); + if (r) + return r; + + r = zd_iowrite16a_locked(chip, ioreqs_3, ARRAY_SIZE(ioreqs_3)); + if (r) + return r; + + r = zd_rfwritev_cr_locked(chip, rv_init2, ARRAY_SIZE(rv_init2)); + if (r) + return r; + + return zd1211b_al7230b_finalize(chip); +} + +static int zd1211_al7230b_set_channel(struct zd_rf *rf, u8 channel) +{ + int r; + const u32 *rv = chan_rv[channel-1]; + struct zd_chip *chip = zd_rf_to_chip(rf); + + static const struct zd_ioreq16 ioreqs[] = { + /* PLL_ON */ + { ZD_CR251, 0x3f }, + { ZD_CR203, 0x06 }, { ZD_CR240, 0x08 }, + }; + + r = zd_iowrite16_locked(chip, 0x57, ZD_CR240); + if (r) + return r; + + /* PLL_OFF */ + r = zd_iowrite16_locked(chip, 0x2f, ZD_CR251); + if (r) + return r; + + r = zd_rfwritev_cr_locked(chip, std_rv, ARRAY_SIZE(std_rv)); + if (r) + return r; + + r = zd_rfwrite_cr_locked(chip, 0x3c9000); + if (r) + return r; + r = zd_rfwrite_cr_locked(chip, 0xf15d58); + if (r) + return r; + + r = zd_iowrite16a_locked(chip, ioreqs_sw, ARRAY_SIZE(ioreqs_sw)); + if (r) + return r; + + r = zd_rfwritev_cr_locked(chip, rv, 2); + if (r) + return r; + + r = zd_rfwrite_cr_locked(chip, 0x3c9000); + if (r) + return r; + + return zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +static int zd1211b_al7230b_set_channel(struct zd_rf *rf, u8 channel) +{ + int r; + const u32 *rv = chan_rv[channel-1]; + struct zd_chip *chip = zd_rf_to_chip(rf); + + r = zd_iowrite16_locked(chip, 0x57, ZD_CR240); + if (r) + return r; + r = zd_iowrite16_locked(chip, 0xe4, ZD_CR9); + if (r) + return r; + + /* PLL_OFF */ + r = zd_iowrite16_locked(chip, 0x2f, ZD_CR251); + if (r) + return r; + r = zd_rfwritev_cr_locked(chip, std_rv, ARRAY_SIZE(std_rv)); + if (r) + return r; + + r = zd_rfwrite_cr_locked(chip, 0x3c9000); + if (r) + return r; + r = zd_rfwrite_cr_locked(chip, 0xf15d58); + if (r) + return r; + + r = zd_iowrite16a_locked(chip, ioreqs_sw, ARRAY_SIZE(ioreqs_sw)); + if (r) + return r; + + r = zd_rfwritev_cr_locked(chip, rv, 2); + if (r) + return r; + + r = zd_rfwrite_cr_locked(chip, 0x3c9000); + if (r) + return r; + + r = zd_iowrite16_locked(chip, 0x7f, ZD_CR251); + if (r) + return r; + + return zd1211b_al7230b_finalize(chip); +} + +static int zd1211_al7230b_switch_radio_on(struct zd_rf *rf) +{ + struct zd_chip *chip = zd_rf_to_chip(rf); + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR11, 0x00 }, + { ZD_CR251, 0x3f }, + }; + + return zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +static int zd1211b_al7230b_switch_radio_on(struct zd_rf *rf) +{ + struct zd_chip *chip = zd_rf_to_chip(rf); + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR11, 0x00 }, + { ZD_CR251, 0x7f }, + }; + + return zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +static int al7230b_switch_radio_off(struct zd_rf *rf) +{ + struct zd_chip *chip = zd_rf_to_chip(rf); + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR11, 0x04 }, + { ZD_CR251, 0x2f }, + }; + + return zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +/* ZD1211B+AL7230B 6m band edge patching differs slightly from other + * configurations */ +static int zd1211b_al7230b_patch_6m(struct zd_rf *rf, u8 channel) +{ + struct zd_chip *chip = zd_rf_to_chip(rf); + struct zd_ioreq16 ioreqs[] = { + { ZD_CR128, 0x14 }, { ZD_CR129, 0x12 }, + }; + + /* FIXME: Channel 11 is not the edge for all regulatory domains. */ + if (channel == 1) { + ioreqs[0].value = 0x0e; + ioreqs[1].value = 0x10; + } else if (channel == 11) { + ioreqs[0].value = 0x10; + ioreqs[1].value = 0x10; + } + + dev_dbg_f(zd_chip_dev(chip), "patching for channel %d\n", channel); + return zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +int zd_rf_init_al7230b(struct zd_rf *rf) +{ + struct zd_chip *chip = zd_rf_to_chip(rf); + + if (zd_chip_is_zd1211b(chip)) { + rf->init_hw = zd1211b_al7230b_init_hw; + rf->switch_radio_on = zd1211b_al7230b_switch_radio_on; + rf->set_channel = zd1211b_al7230b_set_channel; + rf->patch_6m_band_edge = zd1211b_al7230b_patch_6m; + } else { + rf->init_hw = zd1211_al7230b_init_hw; + rf->switch_radio_on = zd1211_al7230b_switch_radio_on; + rf->set_channel = zd1211_al7230b_set_channel; + rf->patch_6m_band_edge = zd_rf_generic_patch_6m; + rf->patch_cck_gain = 1; + } + + rf->switch_radio_off = al7230b_switch_radio_off; + + return 0; +} diff --git a/drivers/net/wireless/zd1211rw/zd_rf_rf2959.c b/drivers/net/wireless/zd1211rw/zd_rf_rf2959.c new file mode 100644 index 0000000..784d9cc --- /dev/null +++ b/drivers/net/wireless/zd1211rw/zd_rf_rf2959.c @@ -0,0 +1,282 @@ +/* ZD1211 USB-WLAN driver for Linux + * + * Copyright (C) 2005-2007 Ulrich Kunitz + * Copyright (C) 2006-2007 Daniel Drake + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#include "zd_rf.h" +#include "zd_usb.h" +#include "zd_chip.h" + +static const u32 rf2959_table[][2] = { + RF_CHANNEL( 1) = { 0x181979, 0x1e6666 }, + RF_CHANNEL( 2) = { 0x181989, 0x1e6666 }, + RF_CHANNEL( 3) = { 0x181999, 0x1e6666 }, + RF_CHANNEL( 4) = { 0x1819a9, 0x1e6666 }, + RF_CHANNEL( 5) = { 0x1819b9, 0x1e6666 }, + RF_CHANNEL( 6) = { 0x1819c9, 0x1e6666 }, + RF_CHANNEL( 7) = { 0x1819d9, 0x1e6666 }, + RF_CHANNEL( 8) = { 0x1819e9, 0x1e6666 }, + RF_CHANNEL( 9) = { 0x1819f9, 0x1e6666 }, + RF_CHANNEL(10) = { 0x181a09, 0x1e6666 }, + RF_CHANNEL(11) = { 0x181a19, 0x1e6666 }, + RF_CHANNEL(12) = { 0x181a29, 0x1e6666 }, + RF_CHANNEL(13) = { 0x181a39, 0x1e6666 }, + RF_CHANNEL(14) = { 0x181a60, 0x1c0000 }, +}; + +#if 0 +static int bits(u32 rw, int from, int to) +{ + rw &= ~(0xffffffffU << (to+1)); + rw >>= from; + return rw; +} + +static int bit(u32 rw, int bit) +{ + return bits(rw, bit, bit); +} + +static void dump_regwrite(u32 rw) +{ + int reg = bits(rw, 18, 22); + int rw_flag = bits(rw, 23, 23); + PDEBUG("rf2959 %#010x reg %d rw %d", rw, reg, rw_flag); + + switch (reg) { + case 0: + PDEBUG("reg0 CFG1 ref_sel %d hybernate %d rf_vco_reg_en %d" + " if_vco_reg_en %d if_vga_en %d", + bits(rw, 14, 15), bit(rw, 3), bit(rw, 2), bit(rw, 1), + bit(rw, 0)); + break; + case 1: + PDEBUG("reg1 IFPLL1 pll_en1 %d kv_en1 %d vtc_en1 %d lpf1 %d" + " cpl1 %d pdp1 %d autocal_en1 %d ld_en1 %d ifloopr %d" + " ifloopc %d dac1 %d", + bit(rw, 17), bit(rw, 16), bit(rw, 15), bit(rw, 14), + bit(rw, 13), bit(rw, 12), bit(rw, 11), bit(rw, 10), + bits(rw, 7, 9), bits(rw, 4, 6), bits(rw, 0, 3)); + break; + case 2: + PDEBUG("reg2 IFPLL2 n1 %d num1 %d", + bits(rw, 6, 17), bits(rw, 0, 5)); + break; + case 3: + PDEBUG("reg3 IFPLL3 num %d", bits(rw, 0, 17)); + break; + case 4: + PDEBUG("reg4 IFPLL4 dn1 %#04x ct_def1 %d kv_def1 %d", + bits(rw, 8, 16), bits(rw, 4, 7), bits(rw, 0, 3)); + break; + case 5: + PDEBUG("reg5 RFPLL1 pll_en %d kv_en %d vtc_en %d lpf %d cpl %d" + " pdp %d autocal_en %d ld_en %d rfloopr %d rfloopc %d" + " dac %d", + bit(rw, 17), bit(rw, 16), bit(rw, 15), bit(rw, 14), + bit(rw, 13), bit(rw, 12), bit(rw, 11), bit(rw, 10), + bits(rw, 7, 9), bits(rw, 4, 6), bits(rw, 0,3)); + break; + case 6: + PDEBUG("reg6 RFPLL2 n %d num %d", + bits(rw, 6, 17), bits(rw, 0, 5)); + break; + case 7: + PDEBUG("reg7 RFPLL3 num2 %d", bits(rw, 0, 17)); + break; + case 8: + PDEBUG("reg8 RFPLL4 dn %#06x ct_def %d kv_def %d", + bits(rw, 8, 16), bits(rw, 4, 7), bits(rw, 0, 3)); + break; + case 9: + PDEBUG("reg9 CAL1 tvco %d tlock %d m_ct_value %d ld_window %d", + bits(rw, 13, 17), bits(rw, 8, 12), bits(rw, 3, 7), + bits(rw, 0, 2)); + break; + case 10: + PDEBUG("reg10 TXRX1 rxdcfbbyps %d pcontrol %d txvgc %d" + " rxlpfbw %d txlpfbw %d txdiffmode %d txenmode %d" + " intbiasen %d tybypass %d", + bit(rw, 17), bits(rw, 15, 16), bits(rw, 10, 14), + bits(rw, 7, 9), bits(rw, 4, 6), bit(rw, 3), bit(rw, 2), + bit(rw, 1), bit(rw, 0)); + break; + case 11: + PDEBUG("reg11 PCNT1 mid_bias %d p_desired %d pc_offset %d" + " tx_delay %d", + bits(rw, 15, 17), bits(rw, 9, 14), bits(rw, 3, 8), + bits(rw, 0, 2)); + break; + case 12: + PDEBUG("reg12 PCNT2 max_power %d mid_power %d min_power %d", + bits(rw, 12, 17), bits(rw, 6, 11), bits(rw, 0, 5)); + break; + case 13: + PDEBUG("reg13 VCOT1 rfpll vco comp %d ifpll vco comp %d" + " lobias %d if_biasbuf %d if_biasvco %d rf_biasbuf %d" + " rf_biasvco %d", + bit(rw, 17), bit(rw, 16), bit(rw, 15), + bits(rw, 8, 9), bits(rw, 5, 7), bits(rw, 3, 4), + bits(rw, 0, 2)); + break; + case 14: + PDEBUG("reg14 IQCAL rx_acal %d rx_pcal %d" + " tx_acal %d tx_pcal %d", + bits(rw, 13, 17), bits(rw, 9, 12), bits(rw, 4, 8), + bits(rw, 0, 3)); + break; + } +} +#endif /* 0 */ + +static int rf2959_init_hw(struct zd_rf *rf) +{ + int r; + struct zd_chip *chip = zd_rf_to_chip(rf); + + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR2, 0x1E }, { ZD_CR9, 0x20 }, { ZD_CR10, 0x89 }, + { ZD_CR11, 0x00 }, { ZD_CR15, 0xD0 }, { ZD_CR17, 0x68 }, + { ZD_CR19, 0x4a }, { ZD_CR20, 0x0c }, { ZD_CR21, 0x0E }, + { ZD_CR23, 0x48 }, + /* normal size for cca threshold */ + { ZD_CR24, 0x14 }, + /* { ZD_CR24, 0x20 }, */ + { ZD_CR26, 0x90 }, { ZD_CR27, 0x30 }, { ZD_CR29, 0x20 }, + { ZD_CR31, 0xb2 }, { ZD_CR32, 0x43 }, { ZD_CR33, 0x28 }, + { ZD_CR38, 0x30 }, { ZD_CR34, 0x0f }, { ZD_CR35, 0xF0 }, + { ZD_CR41, 0x2a }, { ZD_CR46, 0x7F }, { ZD_CR47, 0x1E }, + { ZD_CR51, 0xc5 }, { ZD_CR52, 0xc5 }, { ZD_CR53, 0xc5 }, + { ZD_CR79, 0x58 }, { ZD_CR80, 0x30 }, { ZD_CR81, 0x30 }, + { ZD_CR82, 0x00 }, { ZD_CR83, 0x24 }, { ZD_CR84, 0x04 }, + { ZD_CR85, 0x00 }, { ZD_CR86, 0x10 }, { ZD_CR87, 0x2A }, + { ZD_CR88, 0x10 }, { ZD_CR89, 0x24 }, { ZD_CR90, 0x18 }, + /* { ZD_CR91, 0x18 }, */ + /* should solve continuous CTS frame problems */ + { ZD_CR91, 0x00 }, + { ZD_CR92, 0x0a }, { ZD_CR93, 0x00 }, { ZD_CR94, 0x01 }, + { ZD_CR95, 0x00 }, { ZD_CR96, 0x40 }, { ZD_CR97, 0x37 }, + { ZD_CR98, 0x05 }, { ZD_CR99, 0x28 }, { ZD_CR100, 0x00 }, + { ZD_CR101, 0x13 }, { ZD_CR102, 0x27 }, { ZD_CR103, 0x27 }, + { ZD_CR104, 0x18 }, { ZD_CR105, 0x12 }, + /* normal size */ + { ZD_CR106, 0x1a }, + /* { ZD_CR106, 0x22 }, */ + { ZD_CR107, 0x24 }, { ZD_CR108, 0x0a }, { ZD_CR109, 0x13 }, + { ZD_CR110, 0x2F }, { ZD_CR111, 0x27 }, { ZD_CR112, 0x27 }, + { ZD_CR113, 0x27 }, { ZD_CR114, 0x27 }, { ZD_CR115, 0x40 }, + { ZD_CR116, 0x40 }, { ZD_CR117, 0xF0 }, { ZD_CR118, 0xF0 }, + { ZD_CR119, 0x16 }, + /* no TX continuation */ + { ZD_CR122, 0x00 }, + /* { ZD_CR122, 0xff }, */ + { ZD_CR127, 0x03 }, { ZD_CR131, 0x08 }, { ZD_CR138, 0x28 }, + { ZD_CR148, 0x44 }, { ZD_CR150, 0x10 }, { ZD_CR169, 0xBB }, + { ZD_CR170, 0xBB }, + }; + + static const u32 rv[] = { + 0x000007, /* REG0(CFG1) */ + 0x07dd43, /* REG1(IFPLL1) */ + 0x080959, /* REG2(IFPLL2) */ + 0x0e6666, + 0x116a57, /* REG4 */ + 0x17dd43, /* REG5 */ + 0x1819f9, /* REG6 */ + 0x1e6666, + 0x214554, + 0x25e7fa, + 0x27fffa, + /* The Zydas driver somehow forgets to set this value. It's + * only set for Japan. We are using internal power control + * for now. + */ + 0x294128, /* internal power */ + /* 0x28252c, */ /* External control TX power */ + /* ZD_CR31_CCK, ZD_CR51_6-36M, ZD_CR52_48M, ZD_CR53_54M */ + 0x2c0000, + 0x300000, + 0x340000, /* REG13(0xD) */ + 0x381e0f, /* REG14(0xE) */ + /* Bogus, RF2959's data sheet doesn't know register 27, which is + * actually referenced here. The commented 0x11 is 17. + */ + 0x6c180f, /* REG27(0x11) */ + }; + + r = zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); + if (r) + return r; + + return zd_rfwritev_locked(chip, rv, ARRAY_SIZE(rv), RF_RV_BITS); +} + +static int rf2959_set_channel(struct zd_rf *rf, u8 channel) +{ + int i, r; + const u32 *rv = rf2959_table[channel-1]; + struct zd_chip *chip = zd_rf_to_chip(rf); + + for (i = 0; i < 2; i++) { + r = zd_rfwrite_locked(chip, rv[i], RF_RV_BITS); + if (r) + return r; + } + return 0; +} + +static int rf2959_switch_radio_on(struct zd_rf *rf) +{ + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR10, 0x89 }, + { ZD_CR11, 0x00 }, + }; + struct zd_chip *chip = zd_rf_to_chip(rf); + + return zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +static int rf2959_switch_radio_off(struct zd_rf *rf) +{ + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR10, 0x15 }, + { ZD_CR11, 0x81 }, + }; + struct zd_chip *chip = zd_rf_to_chip(rf); + + return zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +int zd_rf_init_rf2959(struct zd_rf *rf) +{ + struct zd_chip *chip = zd_rf_to_chip(rf); + + if (zd_chip_is_zd1211b(chip)) { + dev_err(zd_chip_dev(chip), + "RF2959 is currently not supported for ZD1211B" + " devices\n"); + return -ENODEV; + } + rf->init_hw = rf2959_init_hw; + rf->set_channel = rf2959_set_channel; + rf->switch_radio_on = rf2959_switch_radio_on; + rf->switch_radio_off = rf2959_switch_radio_off; + return 0; +} diff --git a/drivers/net/wireless/zd1211rw/zd_rf_uw2453.c b/drivers/net/wireless/zd1211rw/zd_rf_uw2453.c new file mode 100644 index 0000000..c4d324e --- /dev/null +++ b/drivers/net/wireless/zd1211rw/zd_rf_uw2453.c @@ -0,0 +1,540 @@ +/* ZD1211 USB-WLAN driver for Linux + * + * Copyright (C) 2005-2007 Ulrich Kunitz + * Copyright (C) 2006-2007 Daniel Drake + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +#include "zd_rf.h" +#include "zd_usb.h" +#include "zd_chip.h" + +/* This RF programming code is based upon the code found in v2.16.0.0 of the + * ZyDAS vendor driver. Unlike other RF's, Ubec publish full technical specs + * for this RF on their website, so we're able to understand more than + * usual as to what is going on. Thumbs up for Ubec for doing that. */ + +/* The 3-wire serial interface provides access to 8 write-only registers. + * The data format is a 4 bit register address followed by a 20 bit value. */ +#define UW2453_REGWRITE(reg, val) ((((reg) & 0xf) << 20) | ((val) & 0xfffff)) + +/* For channel tuning, we have to configure registers 1 (synthesizer), 2 (synth + * fractional divide ratio) and 3 (VCO config). + * + * We configure the RF to produce an interrupt when the PLL is locked onto + * the configured frequency. During initialization, we run through a variety + * of different VCO configurations on channel 1 until we detect a PLL lock. + * When this happens, we remember which VCO configuration produced the lock + * and use it later. Actually, we use the configuration *after* the one that + * produced the lock, which seems odd, but it works. + * + * If we do not see a PLL lock on any standard VCO config, we fall back on an + * autocal configuration, which has a fixed (as opposed to per-channel) VCO + * config and different synth values from the standard set (divide ratio + * is still shared with the standard set). */ + +/* The per-channel synth values for all standard VCO configurations. These get + * written to register 1. */ +static const u8 uw2453_std_synth[] = { + RF_CHANNEL( 1) = 0x47, + RF_CHANNEL( 2) = 0x47, + RF_CHANNEL( 3) = 0x67, + RF_CHANNEL( 4) = 0x67, + RF_CHANNEL( 5) = 0x67, + RF_CHANNEL( 6) = 0x67, + RF_CHANNEL( 7) = 0x57, + RF_CHANNEL( 8) = 0x57, + RF_CHANNEL( 9) = 0x57, + RF_CHANNEL(10) = 0x57, + RF_CHANNEL(11) = 0x77, + RF_CHANNEL(12) = 0x77, + RF_CHANNEL(13) = 0x77, + RF_CHANNEL(14) = 0x4f, +}; + +/* This table stores the synthesizer fractional divide ratio for *all* VCO + * configurations (both standard and autocal). These get written to register 2. + */ +static const u16 uw2453_synth_divide[] = { + RF_CHANNEL( 1) = 0x999, + RF_CHANNEL( 2) = 0x99b, + RF_CHANNEL( 3) = 0x998, + RF_CHANNEL( 4) = 0x99a, + RF_CHANNEL( 5) = 0x999, + RF_CHANNEL( 6) = 0x99b, + RF_CHANNEL( 7) = 0x998, + RF_CHANNEL( 8) = 0x99a, + RF_CHANNEL( 9) = 0x999, + RF_CHANNEL(10) = 0x99b, + RF_CHANNEL(11) = 0x998, + RF_CHANNEL(12) = 0x99a, + RF_CHANNEL(13) = 0x999, + RF_CHANNEL(14) = 0xccc, +}; + +/* Here is the data for all the standard VCO configurations. We shrink our + * table a little by observing that both channels in a consecutive pair share + * the same value. We also observe that the high 4 bits ([0:3] in the specs) + * are all 'Reserved' and are always set to 0x4 - we chop them off in the data + * below. */ +#define CHAN_TO_PAIRIDX(a) ((a - 1) / 2) +#define RF_CHANPAIR(a,b) [CHAN_TO_PAIRIDX(a)] +static const u16 uw2453_std_vco_cfg[][7] = { + { /* table 1 */ + RF_CHANPAIR( 1, 2) = 0x664d, + RF_CHANPAIR( 3, 4) = 0x604d, + RF_CHANPAIR( 5, 6) = 0x6675, + RF_CHANPAIR( 7, 8) = 0x6475, + RF_CHANPAIR( 9, 10) = 0x6655, + RF_CHANPAIR(11, 12) = 0x6455, + RF_CHANPAIR(13, 14) = 0x6665, + }, + { /* table 2 */ + RF_CHANPAIR( 1, 2) = 0x666d, + RF_CHANPAIR( 3, 4) = 0x606d, + RF_CHANPAIR( 5, 6) = 0x664d, + RF_CHANPAIR( 7, 8) = 0x644d, + RF_CHANPAIR( 9, 10) = 0x6675, + RF_CHANPAIR(11, 12) = 0x6475, + RF_CHANPAIR(13, 14) = 0x6655, + }, + { /* table 3 */ + RF_CHANPAIR( 1, 2) = 0x665d, + RF_CHANPAIR( 3, 4) = 0x605d, + RF_CHANPAIR( 5, 6) = 0x666d, + RF_CHANPAIR( 7, 8) = 0x646d, + RF_CHANPAIR( 9, 10) = 0x664d, + RF_CHANPAIR(11, 12) = 0x644d, + RF_CHANPAIR(13, 14) = 0x6675, + }, + { /* table 4 */ + RF_CHANPAIR( 1, 2) = 0x667d, + RF_CHANPAIR( 3, 4) = 0x607d, + RF_CHANPAIR( 5, 6) = 0x665d, + RF_CHANPAIR( 7, 8) = 0x645d, + RF_CHANPAIR( 9, 10) = 0x666d, + RF_CHANPAIR(11, 12) = 0x646d, + RF_CHANPAIR(13, 14) = 0x664d, + }, + { /* table 5 */ + RF_CHANPAIR( 1, 2) = 0x6643, + RF_CHANPAIR( 3, 4) = 0x6043, + RF_CHANPAIR( 5, 6) = 0x667d, + RF_CHANPAIR( 7, 8) = 0x647d, + RF_CHANPAIR( 9, 10) = 0x665d, + RF_CHANPAIR(11, 12) = 0x645d, + RF_CHANPAIR(13, 14) = 0x666d, + }, + { /* table 6 */ + RF_CHANPAIR( 1, 2) = 0x6663, + RF_CHANPAIR( 3, 4) = 0x6063, + RF_CHANPAIR( 5, 6) = 0x6643, + RF_CHANPAIR( 7, 8) = 0x6443, + RF_CHANPAIR( 9, 10) = 0x667d, + RF_CHANPAIR(11, 12) = 0x647d, + RF_CHANPAIR(13, 14) = 0x665d, + }, + { /* table 7 */ + RF_CHANPAIR( 1, 2) = 0x6653, + RF_CHANPAIR( 3, 4) = 0x6053, + RF_CHANPAIR( 5, 6) = 0x6663, + RF_CHANPAIR( 7, 8) = 0x6463, + RF_CHANPAIR( 9, 10) = 0x6643, + RF_CHANPAIR(11, 12) = 0x6443, + RF_CHANPAIR(13, 14) = 0x667d, + }, + { /* table 8 */ + RF_CHANPAIR( 1, 2) = 0x6673, + RF_CHANPAIR( 3, 4) = 0x6073, + RF_CHANPAIR( 5, 6) = 0x6653, + RF_CHANPAIR( 7, 8) = 0x6453, + RF_CHANPAIR( 9, 10) = 0x6663, + RF_CHANPAIR(11, 12) = 0x6463, + RF_CHANPAIR(13, 14) = 0x6643, + }, + { /* table 9 */ + RF_CHANPAIR( 1, 2) = 0x664b, + RF_CHANPAIR( 3, 4) = 0x604b, + RF_CHANPAIR( 5, 6) = 0x6673, + RF_CHANPAIR( 7, 8) = 0x6473, + RF_CHANPAIR( 9, 10) = 0x6653, + RF_CHANPAIR(11, 12) = 0x6453, + RF_CHANPAIR(13, 14) = 0x6663, + }, + { /* table 10 */ + RF_CHANPAIR( 1, 2) = 0x666b, + RF_CHANPAIR( 3, 4) = 0x606b, + RF_CHANPAIR( 5, 6) = 0x664b, + RF_CHANPAIR( 7, 8) = 0x644b, + RF_CHANPAIR( 9, 10) = 0x6673, + RF_CHANPAIR(11, 12) = 0x6473, + RF_CHANPAIR(13, 14) = 0x6653, + }, + { /* table 11 */ + RF_CHANPAIR( 1, 2) = 0x665b, + RF_CHANPAIR( 3, 4) = 0x605b, + RF_CHANPAIR( 5, 6) = 0x666b, + RF_CHANPAIR( 7, 8) = 0x646b, + RF_CHANPAIR( 9, 10) = 0x664b, + RF_CHANPAIR(11, 12) = 0x644b, + RF_CHANPAIR(13, 14) = 0x6673, + }, + +}; + +/* The per-channel synth values for autocal. These get written to register 1. */ +static const u16 uw2453_autocal_synth[] = { + RF_CHANNEL( 1) = 0x6847, + RF_CHANNEL( 2) = 0x6847, + RF_CHANNEL( 3) = 0x6867, + RF_CHANNEL( 4) = 0x6867, + RF_CHANNEL( 5) = 0x6867, + RF_CHANNEL( 6) = 0x6867, + RF_CHANNEL( 7) = 0x6857, + RF_CHANNEL( 8) = 0x6857, + RF_CHANNEL( 9) = 0x6857, + RF_CHANNEL(10) = 0x6857, + RF_CHANNEL(11) = 0x6877, + RF_CHANNEL(12) = 0x6877, + RF_CHANNEL(13) = 0x6877, + RF_CHANNEL(14) = 0x684f, +}; + +/* The VCO configuration for autocal (all channels) */ +static const u16 UW2453_AUTOCAL_VCO_CFG = 0x6662; + +/* TX gain settings. The array index corresponds to the TX power integration + * values found in the EEPROM. The values get written to register 7. */ +static u32 uw2453_txgain[] = { + [0x00] = 0x0e313, + [0x01] = 0x0fb13, + [0x02] = 0x0e093, + [0x03] = 0x0f893, + [0x04] = 0x0ea93, + [0x05] = 0x1f093, + [0x06] = 0x1f493, + [0x07] = 0x1f693, + [0x08] = 0x1f393, + [0x09] = 0x1f35b, + [0x0a] = 0x1e6db, + [0x0b] = 0x1ff3f, + [0x0c] = 0x1ffff, + [0x0d] = 0x361d7, + [0x0e] = 0x37fbf, + [0x0f] = 0x3ff8b, + [0x10] = 0x3ff33, + [0x11] = 0x3fb3f, + [0x12] = 0x3ffff, +}; + +/* RF-specific structure */ +struct uw2453_priv { + /* index into synth/VCO config tables where PLL lock was found + * -1 means autocal */ + int config; +}; + +#define UW2453_PRIV(rf) ((struct uw2453_priv *) (rf)->priv) + +static int uw2453_synth_set_channel(struct zd_chip *chip, int channel, + bool autocal) +{ + int r; + int idx = channel - 1; + u32 val; + + if (autocal) + val = UW2453_REGWRITE(1, uw2453_autocal_synth[idx]); + else + val = UW2453_REGWRITE(1, uw2453_std_synth[idx]); + + r = zd_rfwrite_locked(chip, val, RF_RV_BITS); + if (r) + return r; + + return zd_rfwrite_locked(chip, + UW2453_REGWRITE(2, uw2453_synth_divide[idx]), RF_RV_BITS); +} + +static int uw2453_write_vco_cfg(struct zd_chip *chip, u16 value) +{ + /* vendor driver always sets these upper bits even though the specs say + * they are reserved */ + u32 val = 0x40000 | value; + return zd_rfwrite_locked(chip, UW2453_REGWRITE(3, val), RF_RV_BITS); +} + +static int uw2453_init_mode(struct zd_chip *chip) +{ + static const u32 rv[] = { + UW2453_REGWRITE(0, 0x25f98), /* enter IDLE mode */ + UW2453_REGWRITE(0, 0x25f9a), /* enter CAL_VCO mode */ + UW2453_REGWRITE(0, 0x25f94), /* enter RX/TX mode */ + UW2453_REGWRITE(0, 0x27fd4), /* power down RSSI circuit */ + }; + + return zd_rfwritev_locked(chip, rv, ARRAY_SIZE(rv), RF_RV_BITS); +} + +static int uw2453_set_tx_gain_level(struct zd_chip *chip, int channel) +{ + u8 int_value = chip->pwr_int_values[channel - 1]; + + if (int_value >= ARRAY_SIZE(uw2453_txgain)) { + dev_dbg_f(zd_chip_dev(chip), "can't configure TX gain for " + "int value %x on channel %d\n", int_value, channel); + return 0; + } + + return zd_rfwrite_locked(chip, + UW2453_REGWRITE(7, uw2453_txgain[int_value]), RF_RV_BITS); +} + +static int uw2453_init_hw(struct zd_rf *rf) +{ + int i, r; + int found_config = -1; + u16 intr_status; + struct zd_chip *chip = zd_rf_to_chip(rf); + + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR10, 0x89 }, { ZD_CR15, 0x20 }, + { ZD_CR17, 0x28 }, /* 6112 no change */ + { ZD_CR23, 0x38 }, { ZD_CR24, 0x20 }, { ZD_CR26, 0x93 }, + { ZD_CR27, 0x15 }, { ZD_CR28, 0x3e }, { ZD_CR29, 0x00 }, + { ZD_CR33, 0x28 }, { ZD_CR34, 0x30 }, + { ZD_CR35, 0x43 }, /* 6112 3e->43 */ + { ZD_CR41, 0x24 }, { ZD_CR44, 0x32 }, + { ZD_CR46, 0x92 }, /* 6112 96->92 */ + { ZD_CR47, 0x1e }, + { ZD_CR48, 0x04 }, /* 5602 Roger */ + { ZD_CR49, 0xfa }, { ZD_CR79, 0x58 }, { ZD_CR80, 0x30 }, + { ZD_CR81, 0x30 }, { ZD_CR87, 0x0a }, { ZD_CR89, 0x04 }, + { ZD_CR91, 0x00 }, { ZD_CR92, 0x0a }, { ZD_CR98, 0x8d }, + { ZD_CR99, 0x28 }, { ZD_CR100, 0x02 }, + { ZD_CR101, 0x09 }, /* 6112 13->1f 6220 1f->13 6407 13->9 */ + { ZD_CR102, 0x27 }, + { ZD_CR106, 0x1c }, /* 5d07 5112 1f->1c 6220 1c->1f + * 6221 1f->1c + */ + { ZD_CR107, 0x1c }, /* 6220 1c->1a 5221 1a->1c */ + { ZD_CR109, 0x13 }, + { ZD_CR110, 0x1f }, /* 6112 13->1f 6221 1f->13 6407 13->0x09 */ + { ZD_CR111, 0x13 }, { ZD_CR112, 0x1f }, { ZD_CR113, 0x27 }, + { ZD_CR114, 0x23 }, /* 6221 27->23 */ + { ZD_CR115, 0x24 }, /* 6112 24->1c 6220 1c->24 */ + { ZD_CR116, 0x24 }, /* 6220 1c->24 */ + { ZD_CR117, 0xfa }, /* 6112 fa->f8 6220 f8->f4 6220 f4->fa */ + { ZD_CR118, 0xf0 }, /* 5d07 6112 f0->f2 6220 f2->f0 */ + { ZD_CR119, 0x1a }, /* 6112 1a->10 6220 10->14 6220 14->1a */ + { ZD_CR120, 0x4f }, + { ZD_CR121, 0x1f }, /* 6220 4f->1f */ + { ZD_CR122, 0xf0 }, { ZD_CR123, 0x57 }, { ZD_CR125, 0xad }, + { ZD_CR126, 0x6c }, { ZD_CR127, 0x03 }, + { ZD_CR128, 0x14 }, /* 6302 12->11 */ + { ZD_CR129, 0x12 }, /* 6301 10->0f */ + { ZD_CR130, 0x10 }, { ZD_CR137, 0x50 }, { ZD_CR138, 0xa8 }, + { ZD_CR144, 0xac }, { ZD_CR146, 0x20 }, { ZD_CR252, 0xff }, + { ZD_CR253, 0xff }, + }; + + static const u32 rv[] = { + UW2453_REGWRITE(4, 0x2b), /* configure receiver gain */ + UW2453_REGWRITE(5, 0x19e4f), /* configure transmitter gain */ + UW2453_REGWRITE(6, 0xf81ad), /* enable RX/TX filter tuning */ + UW2453_REGWRITE(7, 0x3fffe), /* disable TX gain in test mode */ + + /* enter CAL_FIL mode, TX gain set by registers, RX gain set by pins, + * RSSI circuit powered down, reduced RSSI range */ + UW2453_REGWRITE(0, 0x25f9c), /* 5d01 cal_fil */ + + /* synthesizer configuration for channel 1 */ + UW2453_REGWRITE(1, 0x47), + UW2453_REGWRITE(2, 0x999), + + /* disable manual VCO band selection */ + UW2453_REGWRITE(3, 0x7602), + + /* enable manual VCO band selection, configure current level */ + UW2453_REGWRITE(3, 0x46063), + }; + + r = zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); + if (r) + return r; + + r = zd_rfwritev_locked(chip, rv, ARRAY_SIZE(rv), RF_RV_BITS); + if (r) + return r; + + r = uw2453_init_mode(chip); + if (r) + return r; + + /* Try all standard VCO configuration settings on channel 1 */ + for (i = 0; i < ARRAY_SIZE(uw2453_std_vco_cfg) - 1; i++) { + /* Configure synthesizer for channel 1 */ + r = uw2453_synth_set_channel(chip, 1, false); + if (r) + return r; + + /* Write VCO config */ + r = uw2453_write_vco_cfg(chip, uw2453_std_vco_cfg[i][0]); + if (r) + return r; + + /* ack interrupt event */ + r = zd_iowrite16_locked(chip, 0x0f, UW2453_INTR_REG); + if (r) + return r; + + /* check interrupt status */ + r = zd_ioread16_locked(chip, &intr_status, UW2453_INTR_REG); + if (r) + return r; + + if (!(intr_status & 0xf)) { + dev_dbg_f(zd_chip_dev(chip), + "PLL locked on configuration %d\n", i); + found_config = i; + break; + } + } + + if (found_config == -1) { + /* autocal */ + dev_dbg_f(zd_chip_dev(chip), + "PLL did not lock, using autocal\n"); + + r = uw2453_synth_set_channel(chip, 1, true); + if (r) + return r; + + r = uw2453_write_vco_cfg(chip, UW2453_AUTOCAL_VCO_CFG); + if (r) + return r; + } + + /* To match the vendor driver behaviour, we use the configuration after + * the one that produced a lock. */ + UW2453_PRIV(rf)->config = found_config + 1; + + return zd_iowrite16_locked(chip, 0x06, ZD_CR203); +} + +static int uw2453_set_channel(struct zd_rf *rf, u8 channel) +{ + int r; + u16 vco_cfg; + int config = UW2453_PRIV(rf)->config; + bool autocal = (config == -1); + struct zd_chip *chip = zd_rf_to_chip(rf); + + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR80, 0x30 }, { ZD_CR81, 0x30 }, { ZD_CR79, 0x58 }, + { ZD_CR12, 0xf0 }, { ZD_CR77, 0x1b }, { ZD_CR78, 0x58 }, + }; + + r = uw2453_synth_set_channel(chip, channel, autocal); + if (r) + return r; + + if (autocal) + vco_cfg = UW2453_AUTOCAL_VCO_CFG; + else + vco_cfg = uw2453_std_vco_cfg[config][CHAN_TO_PAIRIDX(channel)]; + + r = uw2453_write_vco_cfg(chip, vco_cfg); + if (r) + return r; + + r = uw2453_init_mode(chip); + if (r) + return r; + + r = zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); + if (r) + return r; + + r = uw2453_set_tx_gain_level(chip, channel); + if (r) + return r; + + return zd_iowrite16_locked(chip, 0x06, ZD_CR203); +} + +static int uw2453_switch_radio_on(struct zd_rf *rf) +{ + int r; + struct zd_chip *chip = zd_rf_to_chip(rf); + struct zd_ioreq16 ioreqs[] = { + { ZD_CR11, 0x00 }, { ZD_CR251, 0x3f }, + }; + + /* enter RXTX mode */ + r = zd_rfwrite_locked(chip, UW2453_REGWRITE(0, 0x25f94), RF_RV_BITS); + if (r) + return r; + + if (zd_chip_is_zd1211b(chip)) + ioreqs[1].value = 0x7f; + + return zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +static int uw2453_switch_radio_off(struct zd_rf *rf) +{ + int r; + struct zd_chip *chip = zd_rf_to_chip(rf); + static const struct zd_ioreq16 ioreqs[] = { + { ZD_CR11, 0x04 }, { ZD_CR251, 0x2f }, + }; + + /* enter IDLE mode */ + /* FIXME: shouldn't we go to SLEEP? sent email to zydas */ + r = zd_rfwrite_locked(chip, UW2453_REGWRITE(0, 0x25f90), RF_RV_BITS); + if (r) + return r; + + return zd_iowrite16a_locked(chip, ioreqs, ARRAY_SIZE(ioreqs)); +} + +static void uw2453_clear(struct zd_rf *rf) +{ + kfree(rf->priv); +} + +int zd_rf_init_uw2453(struct zd_rf *rf) +{ + rf->init_hw = uw2453_init_hw; + rf->set_channel = uw2453_set_channel; + rf->switch_radio_on = uw2453_switch_radio_on; + rf->switch_radio_off = uw2453_switch_radio_off; + rf->patch_6m_band_edge = zd_rf_generic_patch_6m; + rf->clear = uw2453_clear; + /* we have our own TX integration code */ + rf->update_channel_int = 0; + + rf->priv = kmalloc(sizeof(struct uw2453_priv), GFP_KERNEL); + if (rf->priv == NULL) + return -ENOMEM; + + return 0; +} + diff --git a/drivers/net/wireless/zd1211rw/zd_usb.c b/drivers/net/wireless/zd1211rw/zd_usb.c new file mode 100644 index 0000000..2c1ef8d --- /dev/null +++ b/drivers/net/wireless/zd1211rw/zd_usb.c @@ -0,0 +1,2063 @@ +/* ZD1211 USB-WLAN driver for Linux + * + * Copyright (C) 2005-2007 Ulrich Kunitz + * Copyright (C) 2006-2007 Daniel Drake + * Copyright (C) 2006-2007 Michael Wu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "zd_def.h" +#include "zd_mac.h" +#include "zd_usb.h" + +static struct usb_device_id usb_ids[] = { + /* ZD1211 */ + { USB_DEVICE(0x0105, 0x145f), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x0586, 0x3401), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x0586, 0x3402), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x0586, 0x3407), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x0586, 0x3409), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x079b, 0x004a), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x07b8, 0x6001), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x0ace, 0x1211), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x0ace, 0xa211), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x0b05, 0x170c), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x0b3b, 0x1630), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x0b3b, 0x5630), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x0df6, 0x9071), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x0df6, 0x9075), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x126f, 0xa006), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x129b, 0x1666), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x13b1, 0x001e), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x1435, 0x0711), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x14ea, 0xab10), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x14ea, 0xab13), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x157e, 0x300a), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x157e, 0x300b), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x157e, 0x3204), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x157e, 0x3207), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x1740, 0x2000), .driver_info = DEVICE_ZD1211 }, + { USB_DEVICE(0x6891, 0xa727), .driver_info = DEVICE_ZD1211 }, + /* ZD1211B */ + { USB_DEVICE(0x0053, 0x5301), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x0409, 0x0248), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x0411, 0x00da), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x0471, 0x1236), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x0471, 0x1237), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x050d, 0x705c), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x054c, 0x0257), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x0586, 0x340a), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x0586, 0x340f), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x0586, 0x3410), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x0586, 0x3412), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x0586, 0x3413), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x079b, 0x0062), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x07b8, 0x6001), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x07fa, 0x1196), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x083a, 0x4505), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x083a, 0xe501), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x083a, 0xe503), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x083a, 0xe506), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x0ace, 0x1215), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x0ace, 0xb215), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x0b05, 0x171b), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x0baf, 0x0121), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x0cde, 0x001a), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x0df6, 0x0036), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x129b, 0x1667), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x13b1, 0x0024), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x157e, 0x300d), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x1582, 0x6003), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x2019, 0x5303), .driver_info = DEVICE_ZD1211B }, + { USB_DEVICE(0x2019, 0xed01), .driver_info = DEVICE_ZD1211B }, + /* "Driverless" devices that need ejecting */ + { USB_DEVICE(0x0ace, 0x2011), .driver_info = DEVICE_INSTALLER }, + { USB_DEVICE(0x0ace, 0x20ff), .driver_info = DEVICE_INSTALLER }, + {} +}; + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("USB driver for devices with the ZD1211 chip."); +MODULE_AUTHOR("Ulrich Kunitz"); +MODULE_AUTHOR("Daniel Drake"); +MODULE_VERSION("1.0"); +MODULE_DEVICE_TABLE(usb, usb_ids); + +#define FW_ZD1211_PREFIX "zd1211/zd1211_" +#define FW_ZD1211B_PREFIX "zd1211/zd1211b_" + +static bool check_read_regs(struct zd_usb *usb, struct usb_req_read_regs *req, + unsigned int count); + +/* USB device initialization */ +static void int_urb_complete(struct urb *urb); + +static int request_fw_file( + const struct firmware **fw, const char *name, struct device *device) +{ + int r; + + dev_dbg_f(device, "fw name %s\n", name); + + r = request_firmware(fw, name, device); + if (r) + dev_err(device, + "Could not load firmware file %s. Error number %d\n", + name, r); + return r; +} + +static inline u16 get_bcdDevice(const struct usb_device *udev) +{ + return le16_to_cpu(udev->descriptor.bcdDevice); +} + +enum upload_code_flags { + REBOOT = 1, +}; + +/* Ensures that MAX_TRANSFER_SIZE is even. */ +#define MAX_TRANSFER_SIZE (USB_MAX_TRANSFER_SIZE & ~1) + +static int upload_code(struct usb_device *udev, + const u8 *data, size_t size, u16 code_offset, int flags) +{ + u8 *p; + int r; + + /* USB request blocks need "kmalloced" buffers. + */ + p = kmalloc(MAX_TRANSFER_SIZE, GFP_KERNEL); + if (!p) { + r = -ENOMEM; + goto error; + } + + size &= ~1; + while (size > 0) { + size_t transfer_size = size <= MAX_TRANSFER_SIZE ? + size : MAX_TRANSFER_SIZE; + + dev_dbg_f(&udev->dev, "transfer size %zu\n", transfer_size); + + memcpy(p, data, transfer_size); + r = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_FIRMWARE_DOWNLOAD, + USB_DIR_OUT | USB_TYPE_VENDOR, + code_offset, 0, p, transfer_size, 1000 /* ms */); + if (r < 0) { + dev_err(&udev->dev, + "USB control request for firmware upload" + " failed. Error number %d\n", r); + goto error; + } + transfer_size = r & ~1; + + size -= transfer_size; + data += transfer_size; + code_offset += transfer_size/sizeof(u16); + } + + if (flags & REBOOT) { + u8 ret; + + /* Use "DMA-aware" buffer. */ + r = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + USB_REQ_FIRMWARE_CONFIRM, + USB_DIR_IN | USB_TYPE_VENDOR, + 0, 0, p, sizeof(ret), 5000 /* ms */); + if (r != sizeof(ret)) { + dev_err(&udev->dev, + "control request firmeware confirmation failed." + " Return value %d\n", r); + if (r >= 0) + r = -ENODEV; + goto error; + } + ret = p[0]; + if (ret & 0x80) { + dev_err(&udev->dev, + "Internal error while downloading." + " Firmware confirm return value %#04x\n", + (unsigned int)ret); + r = -ENODEV; + goto error; + } + dev_dbg_f(&udev->dev, "firmware confirm return value %#04x\n", + (unsigned int)ret); + } + + r = 0; +error: + kfree(p); + return r; +} + +static u16 get_word(const void *data, u16 offset) +{ + const __le16 *p = data; + return le16_to_cpu(p[offset]); +} + +static char *get_fw_name(struct zd_usb *usb, char *buffer, size_t size, + const char* postfix) +{ + scnprintf(buffer, size, "%s%s", + usb->is_zd1211b ? + FW_ZD1211B_PREFIX : FW_ZD1211_PREFIX, + postfix); + return buffer; +} + +static int handle_version_mismatch(struct zd_usb *usb, + const struct firmware *ub_fw) +{ + struct usb_device *udev = zd_usb_to_usbdev(usb); + const struct firmware *ur_fw = NULL; + int offset; + int r = 0; + char fw_name[128]; + + r = request_fw_file(&ur_fw, + get_fw_name(usb, fw_name, sizeof(fw_name), "ur"), + &udev->dev); + if (r) + goto error; + + r = upload_code(udev, ur_fw->data, ur_fw->size, FW_START, REBOOT); + if (r) + goto error; + + offset = (E2P_BOOT_CODE_OFFSET * sizeof(u16)); + r = upload_code(udev, ub_fw->data + offset, ub_fw->size - offset, + E2P_START + E2P_BOOT_CODE_OFFSET, REBOOT); + + /* At this point, the vendor driver downloads the whole firmware + * image, hacks around with version IDs, and uploads it again, + * completely overwriting the boot code. We do not do this here as + * it is not required on any tested devices, and it is suspected to + * cause problems. */ +error: + release_firmware(ur_fw); + return r; +} + +static int upload_firmware(struct zd_usb *usb) +{ + int r; + u16 fw_bcdDevice; + u16 bcdDevice; + struct usb_device *udev = zd_usb_to_usbdev(usb); + const struct firmware *ub_fw = NULL; + const struct firmware *uph_fw = NULL; + char fw_name[128]; + + bcdDevice = get_bcdDevice(udev); + + r = request_fw_file(&ub_fw, + get_fw_name(usb, fw_name, sizeof(fw_name), "ub"), + &udev->dev); + if (r) + goto error; + + fw_bcdDevice = get_word(ub_fw->data, E2P_DATA_OFFSET); + + if (fw_bcdDevice != bcdDevice) { + dev_info(&udev->dev, + "firmware version %#06x and device bootcode version " + "%#06x differ\n", fw_bcdDevice, bcdDevice); + if (bcdDevice <= 0x4313) + dev_warn(&udev->dev, "device has old bootcode, please " + "report success or failure\n"); + + r = handle_version_mismatch(usb, ub_fw); + if (r) + goto error; + } else { + dev_dbg_f(&udev->dev, + "firmware device id %#06x is equal to the " + "actual device id\n", fw_bcdDevice); + } + + + r = request_fw_file(&uph_fw, + get_fw_name(usb, fw_name, sizeof(fw_name), "uphr"), + &udev->dev); + if (r) + goto error; + + r = upload_code(udev, uph_fw->data, uph_fw->size, FW_START, REBOOT); + if (r) { + dev_err(&udev->dev, + "Could not upload firmware code uph. Error number %d\n", + r); + } + + /* FALL-THROUGH */ +error: + release_firmware(ub_fw); + release_firmware(uph_fw); + return r; +} + +MODULE_FIRMWARE(FW_ZD1211B_PREFIX "ur"); +MODULE_FIRMWARE(FW_ZD1211_PREFIX "ur"); +MODULE_FIRMWARE(FW_ZD1211B_PREFIX "ub"); +MODULE_FIRMWARE(FW_ZD1211_PREFIX "ub"); +MODULE_FIRMWARE(FW_ZD1211B_PREFIX "uphr"); +MODULE_FIRMWARE(FW_ZD1211_PREFIX "uphr"); + +/* Read data from device address space using "firmware interface" which does + * not require firmware to be loaded. */ +int zd_usb_read_fw(struct zd_usb *usb, zd_addr_t addr, u8 *data, u16 len) +{ + int r; + struct usb_device *udev = zd_usb_to_usbdev(usb); + u8 *buf; + + /* Use "DMA-aware" buffer. */ + buf = kmalloc(len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + r = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + USB_REQ_FIRMWARE_READ_DATA, USB_DIR_IN | 0x40, addr, 0, + buf, len, 5000); + if (r < 0) { + dev_err(&udev->dev, + "read over firmware interface failed: %d\n", r); + goto exit; + } else if (r != len) { + dev_err(&udev->dev, + "incomplete read over firmware interface: %d/%d\n", + r, len); + r = -EIO; + goto exit; + } + r = 0; + memcpy(data, buf, len); +exit: + kfree(buf); + return r; +} + +#define urb_dev(urb) (&(urb)->dev->dev) + +static inline void handle_regs_int_override(struct urb *urb) +{ + struct zd_usb *usb = urb->context; + struct zd_usb_interrupt *intr = &usb->intr; + + spin_lock(&intr->lock); + if (atomic_read(&intr->read_regs_enabled)) { + atomic_set(&intr->read_regs_enabled, 0); + intr->read_regs_int_overridden = 1; + complete(&intr->read_regs.completion); + } + spin_unlock(&intr->lock); +} + +static inline void handle_regs_int(struct urb *urb) +{ + struct zd_usb *usb = urb->context; + struct zd_usb_interrupt *intr = &usb->intr; + int len; + u16 int_num; + + ZD_ASSERT(in_interrupt()); + spin_lock(&intr->lock); + + int_num = le16_to_cpu(*(__le16 *)(urb->transfer_buffer+2)); + if (int_num == CR_INTERRUPT) { + struct zd_mac *mac = zd_hw_mac(zd_usb_to_hw(urb->context)); + spin_lock(&mac->lock); + memcpy(&mac->intr_buffer, urb->transfer_buffer, + USB_MAX_EP_INT_BUFFER); + spin_unlock(&mac->lock); + schedule_work(&mac->process_intr); + } else if (atomic_read(&intr->read_regs_enabled)) { + len = urb->actual_length; + intr->read_regs.length = urb->actual_length; + if (len > sizeof(intr->read_regs.buffer)) + len = sizeof(intr->read_regs.buffer); + + memcpy(intr->read_regs.buffer, urb->transfer_buffer, len); + + /* Sometimes USB_INT_ID_REGS is not overridden, but comes after + * USB_INT_ID_RETRY_FAILED. Read-reg retry then gets this + * delayed USB_INT_ID_REGS, but leaves USB_INT_ID_REGS of + * retry unhandled. Next read-reg command then might catch + * this wrong USB_INT_ID_REGS. Fix by ignoring wrong reads. + */ + if (!check_read_regs(usb, intr->read_regs.req, + intr->read_regs.req_count)) + goto out; + + atomic_set(&intr->read_regs_enabled, 0); + intr->read_regs_int_overridden = 0; + complete(&intr->read_regs.completion); + + goto out; + } + +out: + spin_unlock(&intr->lock); + + /* CR_INTERRUPT might override read_reg too. */ + if (int_num == CR_INTERRUPT && atomic_read(&intr->read_regs_enabled)) + handle_regs_int_override(urb); +} + +static void int_urb_complete(struct urb *urb) +{ + int r; + struct usb_int_header *hdr; + struct zd_usb *usb; + struct zd_usb_interrupt *intr; + + switch (urb->status) { + case 0: + break; + case -ESHUTDOWN: + case -EINVAL: + case -ENODEV: + case -ENOENT: + case -ECONNRESET: + case -EPIPE: + dev_dbg_f(urb_dev(urb), "urb %p error %d\n", urb, urb->status); + return; + default: + dev_dbg_f(urb_dev(urb), "urb %p error %d\n", urb, urb->status); + goto resubmit; + } + + if (urb->actual_length < sizeof(hdr)) { + dev_dbg_f(urb_dev(urb), "error: urb %p to small\n", urb); + goto resubmit; + } + + hdr = urb->transfer_buffer; + if (hdr->type != USB_INT_TYPE) { + dev_dbg_f(urb_dev(urb), "error: urb %p wrong type\n", urb); + goto resubmit; + } + + /* USB_INT_ID_RETRY_FAILED triggered by tx-urb submit can override + * pending USB_INT_ID_REGS causing read command timeout. + */ + usb = urb->context; + intr = &usb->intr; + if (hdr->id != USB_INT_ID_REGS && atomic_read(&intr->read_regs_enabled)) + handle_regs_int_override(urb); + + switch (hdr->id) { + case USB_INT_ID_REGS: + handle_regs_int(urb); + break; + case USB_INT_ID_RETRY_FAILED: + zd_mac_tx_failed(urb); + break; + default: + dev_dbg_f(urb_dev(urb), "error: urb %p unknown id %x\n", urb, + (unsigned int)hdr->id); + goto resubmit; + } + +resubmit: + r = usb_submit_urb(urb, GFP_ATOMIC); + if (r) { + dev_dbg_f(urb_dev(urb), "error: resubmit urb %p err code %d\n", + urb, r); + /* TODO: add worker to reset intr->urb */ + } + return; +} + +static inline int int_urb_interval(struct usb_device *udev) +{ + switch (udev->speed) { + case USB_SPEED_HIGH: + return 4; + case USB_SPEED_LOW: + return 10; + case USB_SPEED_FULL: + default: + return 1; + } +} + +static inline int usb_int_enabled(struct zd_usb *usb) +{ + unsigned long flags; + struct zd_usb_interrupt *intr = &usb->intr; + struct urb *urb; + + spin_lock_irqsave(&intr->lock, flags); + urb = intr->urb; + spin_unlock_irqrestore(&intr->lock, flags); + return urb != NULL; +} + +int zd_usb_enable_int(struct zd_usb *usb) +{ + int r; + struct usb_device *udev = zd_usb_to_usbdev(usb); + struct zd_usb_interrupt *intr = &usb->intr; + struct urb *urb; + + dev_dbg_f(zd_usb_dev(usb), "\n"); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + r = -ENOMEM; + goto out; + } + + ZD_ASSERT(!irqs_disabled()); + spin_lock_irq(&intr->lock); + if (intr->urb) { + spin_unlock_irq(&intr->lock); + r = 0; + goto error_free_urb; + } + intr->urb = urb; + spin_unlock_irq(&intr->lock); + + r = -ENOMEM; + intr->buffer = usb_alloc_coherent(udev, USB_MAX_EP_INT_BUFFER, + GFP_KERNEL, &intr->buffer_dma); + if (!intr->buffer) { + dev_dbg_f(zd_usb_dev(usb), + "couldn't allocate transfer_buffer\n"); + goto error_set_urb_null; + } + + usb_fill_int_urb(urb, udev, usb_rcvintpipe(udev, EP_INT_IN), + intr->buffer, USB_MAX_EP_INT_BUFFER, + int_urb_complete, usb, + intr->interval); + urb->transfer_dma = intr->buffer_dma; + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + dev_dbg_f(zd_usb_dev(usb), "submit urb %p\n", intr->urb); + r = usb_submit_urb(urb, GFP_KERNEL); + if (r) { + dev_dbg_f(zd_usb_dev(usb), + "Couldn't submit urb. Error number %d\n", r); + goto error; + } + + return 0; +error: + usb_free_coherent(udev, USB_MAX_EP_INT_BUFFER, + intr->buffer, intr->buffer_dma); +error_set_urb_null: + spin_lock_irq(&intr->lock); + intr->urb = NULL; + spin_unlock_irq(&intr->lock); +error_free_urb: + usb_free_urb(urb); +out: + return r; +} + +void zd_usb_disable_int(struct zd_usb *usb) +{ + unsigned long flags; + struct usb_device *udev = zd_usb_to_usbdev(usb); + struct zd_usb_interrupt *intr = &usb->intr; + struct urb *urb; + void *buffer; + dma_addr_t buffer_dma; + + spin_lock_irqsave(&intr->lock, flags); + urb = intr->urb; + if (!urb) { + spin_unlock_irqrestore(&intr->lock, flags); + return; + } + intr->urb = NULL; + buffer = intr->buffer; + buffer_dma = intr->buffer_dma; + intr->buffer = NULL; + spin_unlock_irqrestore(&intr->lock, flags); + + usb_kill_urb(urb); + dev_dbg_f(zd_usb_dev(usb), "urb %p killed\n", urb); + usb_free_urb(urb); + + if (buffer) + usb_free_coherent(udev, USB_MAX_EP_INT_BUFFER, + buffer, buffer_dma); +} + +static void handle_rx_packet(struct zd_usb *usb, const u8 *buffer, + unsigned int length) +{ + int i; + const struct rx_length_info *length_info; + + if (length < sizeof(struct rx_length_info)) { + /* It's not a complete packet anyhow. */ + dev_dbg_f(zd_usb_dev(usb), "invalid, small RX packet : %d\n", + length); + return; + } + length_info = (struct rx_length_info *) + (buffer + length - sizeof(struct rx_length_info)); + + /* It might be that three frames are merged into a single URB + * transaction. We have to check for the length info tag. + * + * While testing we discovered that length_info might be unaligned, + * because if USB transactions are merged, the last packet will not + * be padded. Unaligned access might also happen if the length_info + * structure is not present. + */ + if (get_unaligned_le16(&length_info->tag) == RX_LENGTH_INFO_TAG) + { + unsigned int l, k, n; + for (i = 0, l = 0;; i++) { + k = get_unaligned_le16(&length_info->length[i]); + if (k == 0) + return; + n = l+k; + if (n > length) + return; + zd_mac_rx(zd_usb_to_hw(usb), buffer+l, k); + if (i >= 2) + return; + l = (n+3) & ~3; + } + } else { + zd_mac_rx(zd_usb_to_hw(usb), buffer, length); + } +} + +static void rx_urb_complete(struct urb *urb) +{ + int r; + struct zd_usb *usb; + struct zd_usb_rx *rx; + const u8 *buffer; + unsigned int length; + + switch (urb->status) { + case 0: + break; + case -ESHUTDOWN: + case -EINVAL: + case -ENODEV: + case -ENOENT: + case -ECONNRESET: + case -EPIPE: + dev_dbg_f(urb_dev(urb), "urb %p error %d\n", urb, urb->status); + return; + default: + dev_dbg_f(urb_dev(urb), "urb %p error %d\n", urb, urb->status); + goto resubmit; + } + + buffer = urb->transfer_buffer; + length = urb->actual_length; + usb = urb->context; + rx = &usb->rx; + + tasklet_schedule(&rx->reset_timer_tasklet); + + if (length%rx->usb_packet_size > rx->usb_packet_size-4) { + /* If there is an old first fragment, we don't care. */ + dev_dbg_f(urb_dev(urb), "*** first fragment ***\n"); + ZD_ASSERT(length <= ARRAY_SIZE(rx->fragment)); + spin_lock(&rx->lock); + memcpy(rx->fragment, buffer, length); + rx->fragment_length = length; + spin_unlock(&rx->lock); + goto resubmit; + } + + spin_lock(&rx->lock); + if (rx->fragment_length > 0) { + /* We are on a second fragment, we believe */ + ZD_ASSERT(length + rx->fragment_length <= + ARRAY_SIZE(rx->fragment)); + dev_dbg_f(urb_dev(urb), "*** second fragment ***\n"); + memcpy(rx->fragment+rx->fragment_length, buffer, length); + handle_rx_packet(usb, rx->fragment, + rx->fragment_length + length); + rx->fragment_length = 0; + spin_unlock(&rx->lock); + } else { + spin_unlock(&rx->lock); + handle_rx_packet(usb, buffer, length); + } + +resubmit: + r = usb_submit_urb(urb, GFP_ATOMIC); + if (r) + dev_dbg_f(urb_dev(urb), "urb %p resubmit error %d\n", urb, r); +} + +static struct urb *alloc_rx_urb(struct zd_usb *usb) +{ + struct usb_device *udev = zd_usb_to_usbdev(usb); + struct urb *urb; + void *buffer; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return NULL; + buffer = usb_alloc_coherent(udev, USB_MAX_RX_SIZE, GFP_KERNEL, + &urb->transfer_dma); + if (!buffer) { + usb_free_urb(urb); + return NULL; + } + + usb_fill_bulk_urb(urb, udev, usb_rcvbulkpipe(udev, EP_DATA_IN), + buffer, USB_MAX_RX_SIZE, + rx_urb_complete, usb); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + return urb; +} + +static void free_rx_urb(struct urb *urb) +{ + if (!urb) + return; + usb_free_coherent(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); + usb_free_urb(urb); +} + +static int __zd_usb_enable_rx(struct zd_usb *usb) +{ + int i, r; + struct zd_usb_rx *rx = &usb->rx; + struct urb **urbs; + + dev_dbg_f(zd_usb_dev(usb), "\n"); + + r = -ENOMEM; + urbs = kcalloc(RX_URBS_COUNT, sizeof(struct urb *), GFP_KERNEL); + if (!urbs) + goto error; + for (i = 0; i < RX_URBS_COUNT; i++) { + urbs[i] = alloc_rx_urb(usb); + if (!urbs[i]) + goto error; + } + + ZD_ASSERT(!irqs_disabled()); + spin_lock_irq(&rx->lock); + if (rx->urbs) { + spin_unlock_irq(&rx->lock); + r = 0; + goto error; + } + rx->urbs = urbs; + rx->urbs_count = RX_URBS_COUNT; + spin_unlock_irq(&rx->lock); + + for (i = 0; i < RX_URBS_COUNT; i++) { + r = usb_submit_urb(urbs[i], GFP_KERNEL); + if (r) + goto error_submit; + } + + return 0; +error_submit: + for (i = 0; i < RX_URBS_COUNT; i++) { + usb_kill_urb(urbs[i]); + } + spin_lock_irq(&rx->lock); + rx->urbs = NULL; + rx->urbs_count = 0; + spin_unlock_irq(&rx->lock); +error: + if (urbs) { + for (i = 0; i < RX_URBS_COUNT; i++) + free_rx_urb(urbs[i]); + } + return r; +} + +int zd_usb_enable_rx(struct zd_usb *usb) +{ + int r; + struct zd_usb_rx *rx = &usb->rx; + + mutex_lock(&rx->setup_mutex); + r = __zd_usb_enable_rx(usb); + mutex_unlock(&rx->setup_mutex); + + zd_usb_reset_rx_idle_timer(usb); + + return r; +} + +static void __zd_usb_disable_rx(struct zd_usb *usb) +{ + int i; + unsigned long flags; + struct urb **urbs; + unsigned int count; + struct zd_usb_rx *rx = &usb->rx; + + spin_lock_irqsave(&rx->lock, flags); + urbs = rx->urbs; + count = rx->urbs_count; + spin_unlock_irqrestore(&rx->lock, flags); + if (!urbs) + return; + + for (i = 0; i < count; i++) { + usb_kill_urb(urbs[i]); + free_rx_urb(urbs[i]); + } + kfree(urbs); + + spin_lock_irqsave(&rx->lock, flags); + rx->urbs = NULL; + rx->urbs_count = 0; + spin_unlock_irqrestore(&rx->lock, flags); +} + +void zd_usb_disable_rx(struct zd_usb *usb) +{ + struct zd_usb_rx *rx = &usb->rx; + + mutex_lock(&rx->setup_mutex); + __zd_usb_disable_rx(usb); + mutex_unlock(&rx->setup_mutex); + + tasklet_kill(&rx->reset_timer_tasklet); + cancel_delayed_work_sync(&rx->idle_work); +} + +static void zd_usb_reset_rx(struct zd_usb *usb) +{ + bool do_reset; + struct zd_usb_rx *rx = &usb->rx; + unsigned long flags; + + mutex_lock(&rx->setup_mutex); + + spin_lock_irqsave(&rx->lock, flags); + do_reset = rx->urbs != NULL; + spin_unlock_irqrestore(&rx->lock, flags); + + if (do_reset) { + __zd_usb_disable_rx(usb); + __zd_usb_enable_rx(usb); + } + + mutex_unlock(&rx->setup_mutex); + + if (do_reset) + zd_usb_reset_rx_idle_timer(usb); +} + +/** + * zd_usb_disable_tx - disable transmission + * @usb: the zd1211rw-private USB structure + * + * Frees all URBs in the free list and marks the transmission as disabled. + */ +void zd_usb_disable_tx(struct zd_usb *usb) +{ + struct zd_usb_tx *tx = &usb->tx; + unsigned long flags; + + atomic_set(&tx->enabled, 0); + + /* kill all submitted tx-urbs */ + usb_kill_anchored_urbs(&tx->submitted); + + spin_lock_irqsave(&tx->lock, flags); + WARN_ON(!skb_queue_empty(&tx->submitted_skbs)); + WARN_ON(tx->submitted_urbs != 0); + tx->submitted_urbs = 0; + spin_unlock_irqrestore(&tx->lock, flags); + + /* The stopped state is ignored, relying on ieee80211_wake_queues() + * in a potentionally following zd_usb_enable_tx(). + */ +} + +/** + * zd_usb_enable_tx - enables transmission + * @usb: a &struct zd_usb pointer + * + * This function enables transmission and prepares the &zd_usb_tx data + * structure. + */ +void zd_usb_enable_tx(struct zd_usb *usb) +{ + unsigned long flags; + struct zd_usb_tx *tx = &usb->tx; + + spin_lock_irqsave(&tx->lock, flags); + atomic_set(&tx->enabled, 1); + tx->submitted_urbs = 0; + ieee80211_wake_queues(zd_usb_to_hw(usb)); + tx->stopped = 0; + spin_unlock_irqrestore(&tx->lock, flags); +} + +static void tx_dec_submitted_urbs(struct zd_usb *usb) +{ + struct zd_usb_tx *tx = &usb->tx; + unsigned long flags; + + spin_lock_irqsave(&tx->lock, flags); + --tx->submitted_urbs; + if (tx->stopped && tx->submitted_urbs <= ZD_USB_TX_LOW) { + ieee80211_wake_queues(zd_usb_to_hw(usb)); + tx->stopped = 0; + } + spin_unlock_irqrestore(&tx->lock, flags); +} + +static void tx_inc_submitted_urbs(struct zd_usb *usb) +{ + struct zd_usb_tx *tx = &usb->tx; + unsigned long flags; + + spin_lock_irqsave(&tx->lock, flags); + ++tx->submitted_urbs; + if (!tx->stopped && tx->submitted_urbs > ZD_USB_TX_HIGH) { + ieee80211_stop_queues(zd_usb_to_hw(usb)); + tx->stopped = 1; + } + spin_unlock_irqrestore(&tx->lock, flags); +} + +/** + * tx_urb_complete - completes the execution of an URB + * @urb: a URB + * + * This function is called if the URB has been transferred to a device or an + * error has happened. + */ +static void tx_urb_complete(struct urb *urb) +{ + int r; + struct sk_buff *skb; + struct ieee80211_tx_info *info; + struct zd_usb *usb; + struct zd_usb_tx *tx; + + skb = (struct sk_buff *)urb->context; + info = IEEE80211_SKB_CB(skb); + /* + * grab 'usb' pointer before handing off the skb (since + * it might be freed by zd_mac_tx_to_dev or mac80211) + */ + usb = &zd_hw_mac(info->rate_driver_data[0])->chip.usb; + tx = &usb->tx; + + switch (urb->status) { + case 0: + break; + case -ESHUTDOWN: + case -EINVAL: + case -ENODEV: + case -ENOENT: + case -ECONNRESET: + case -EPIPE: + dev_dbg_f(urb_dev(urb), "urb %p error %d\n", urb, urb->status); + break; + default: + dev_dbg_f(urb_dev(urb), "urb %p error %d\n", urb, urb->status); + goto resubmit; + } +free_urb: + skb_unlink(skb, &usb->tx.submitted_skbs); + zd_mac_tx_to_dev(skb, urb->status); + usb_free_urb(urb); + tx_dec_submitted_urbs(usb); + return; +resubmit: + usb_anchor_urb(urb, &tx->submitted); + r = usb_submit_urb(urb, GFP_ATOMIC); + if (r) { + usb_unanchor_urb(urb); + dev_dbg_f(urb_dev(urb), "error resubmit urb %p %d\n", urb, r); + goto free_urb; + } +} + +/** + * zd_usb_tx: initiates transfer of a frame of the device + * + * @usb: the zd1211rw-private USB structure + * @skb: a &struct sk_buff pointer + * + * This function tranmits a frame to the device. It doesn't wait for + * completion. The frame must contain the control set and have all the + * control set information available. + * + * The function returns 0 if the transfer has been successfully initiated. + */ +int zd_usb_tx(struct zd_usb *usb, struct sk_buff *skb) +{ + int r; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct usb_device *udev = zd_usb_to_usbdev(usb); + struct urb *urb; + struct zd_usb_tx *tx = &usb->tx; + + if (!atomic_read(&tx->enabled)) { + r = -ENOENT; + goto out; + } + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + r = -ENOMEM; + goto out; + } + + usb_fill_bulk_urb(urb, udev, usb_sndbulkpipe(udev, EP_DATA_OUT), + skb->data, skb->len, tx_urb_complete, skb); + + info->rate_driver_data[1] = (void *)jiffies; + skb_queue_tail(&tx->submitted_skbs, skb); + usb_anchor_urb(urb, &tx->submitted); + + r = usb_submit_urb(urb, GFP_ATOMIC); + if (r) { + dev_dbg_f(zd_usb_dev(usb), "error submit urb %p %d\n", urb, r); + usb_unanchor_urb(urb); + skb_unlink(skb, &tx->submitted_skbs); + goto error; + } + tx_inc_submitted_urbs(usb); + return 0; +error: + usb_free_urb(urb); +out: + return r; +} + +static bool zd_tx_timeout(struct zd_usb *usb) +{ + struct zd_usb_tx *tx = &usb->tx; + struct sk_buff_head *q = &tx->submitted_skbs; + struct sk_buff *skb, *skbnext; + struct ieee80211_tx_info *info; + unsigned long flags, trans_start; + bool have_timedout = false; + + spin_lock_irqsave(&q->lock, flags); + skb_queue_walk_safe(q, skb, skbnext) { + info = IEEE80211_SKB_CB(skb); + trans_start = (unsigned long)info->rate_driver_data[1]; + + if (time_is_before_jiffies(trans_start + ZD_TX_TIMEOUT)) { + have_timedout = true; + break; + } + } + spin_unlock_irqrestore(&q->lock, flags); + + return have_timedout; +} + +static void zd_tx_watchdog_handler(struct work_struct *work) +{ + struct zd_usb *usb = + container_of(work, struct zd_usb, tx.watchdog_work.work); + struct zd_usb_tx *tx = &usb->tx; + + if (!atomic_read(&tx->enabled) || !tx->watchdog_enabled) + goto out; + if (!zd_tx_timeout(usb)) + goto out; + + /* TX halted, try reset */ + dev_warn(zd_usb_dev(usb), "TX-stall detected, resetting device..."); + + usb_queue_reset_device(usb->intf); + + /* reset will stop this worker, don't rearm */ + return; +out: + queue_delayed_work(zd_workqueue, &tx->watchdog_work, + ZD_TX_WATCHDOG_INTERVAL); +} + +void zd_tx_watchdog_enable(struct zd_usb *usb) +{ + struct zd_usb_tx *tx = &usb->tx; + + if (!tx->watchdog_enabled) { + dev_dbg_f(zd_usb_dev(usb), "\n"); + queue_delayed_work(zd_workqueue, &tx->watchdog_work, + ZD_TX_WATCHDOG_INTERVAL); + tx->watchdog_enabled = 1; + } +} + +void zd_tx_watchdog_disable(struct zd_usb *usb) +{ + struct zd_usb_tx *tx = &usb->tx; + + if (tx->watchdog_enabled) { + dev_dbg_f(zd_usb_dev(usb), "\n"); + tx->watchdog_enabled = 0; + cancel_delayed_work_sync(&tx->watchdog_work); + } +} + +static void zd_rx_idle_timer_handler(struct work_struct *work) +{ + struct zd_usb *usb = + container_of(work, struct zd_usb, rx.idle_work.work); + struct zd_mac *mac = zd_usb_to_mac(usb); + + if (!test_bit(ZD_DEVICE_RUNNING, &mac->flags)) + return; + + dev_dbg_f(zd_usb_dev(usb), "\n"); + + /* 30 seconds since last rx, reset rx */ + zd_usb_reset_rx(usb); +} + +static void zd_usb_reset_rx_idle_timer_tasklet(unsigned long param) +{ + struct zd_usb *usb = (struct zd_usb *)param; + + zd_usb_reset_rx_idle_timer(usb); +} + +void zd_usb_reset_rx_idle_timer(struct zd_usb *usb) +{ + struct zd_usb_rx *rx = &usb->rx; + + mod_delayed_work(zd_workqueue, &rx->idle_work, ZD_RX_IDLE_INTERVAL); +} + +static inline void init_usb_interrupt(struct zd_usb *usb) +{ + struct zd_usb_interrupt *intr = &usb->intr; + + spin_lock_init(&intr->lock); + intr->interval = int_urb_interval(zd_usb_to_usbdev(usb)); + init_completion(&intr->read_regs.completion); + atomic_set(&intr->read_regs_enabled, 0); + intr->read_regs.cr_int_addr = cpu_to_le16((u16)CR_INTERRUPT); +} + +static inline void init_usb_rx(struct zd_usb *usb) +{ + struct zd_usb_rx *rx = &usb->rx; + + spin_lock_init(&rx->lock); + mutex_init(&rx->setup_mutex); + if (interface_to_usbdev(usb->intf)->speed == USB_SPEED_HIGH) { + rx->usb_packet_size = 512; + } else { + rx->usb_packet_size = 64; + } + ZD_ASSERT(rx->fragment_length == 0); + INIT_DELAYED_WORK(&rx->idle_work, zd_rx_idle_timer_handler); + rx->reset_timer_tasklet.func = zd_usb_reset_rx_idle_timer_tasklet; + rx->reset_timer_tasklet.data = (unsigned long)usb; +} + +static inline void init_usb_tx(struct zd_usb *usb) +{ + struct zd_usb_tx *tx = &usb->tx; + + spin_lock_init(&tx->lock); + atomic_set(&tx->enabled, 0); + tx->stopped = 0; + skb_queue_head_init(&tx->submitted_skbs); + init_usb_anchor(&tx->submitted); + tx->submitted_urbs = 0; + tx->watchdog_enabled = 0; + INIT_DELAYED_WORK(&tx->watchdog_work, zd_tx_watchdog_handler); +} + +void zd_usb_init(struct zd_usb *usb, struct ieee80211_hw *hw, + struct usb_interface *intf) +{ + memset(usb, 0, sizeof(*usb)); + usb->intf = usb_get_intf(intf); + usb_set_intfdata(usb->intf, hw); + init_usb_anchor(&usb->submitted_cmds); + init_usb_interrupt(usb); + init_usb_tx(usb); + init_usb_rx(usb); +} + +void zd_usb_clear(struct zd_usb *usb) +{ + usb_set_intfdata(usb->intf, NULL); + usb_put_intf(usb->intf); + ZD_MEMCLEAR(usb, sizeof(*usb)); + /* FIXME: usb_interrupt, usb_tx, usb_rx? */ +} + +static const char *speed(enum usb_device_speed speed) +{ + switch (speed) { + case USB_SPEED_LOW: + return "low"; + case USB_SPEED_FULL: + return "full"; + case USB_SPEED_HIGH: + return "high"; + default: + return "unknown speed"; + } +} + +static int scnprint_id(struct usb_device *udev, char *buffer, size_t size) +{ + return scnprintf(buffer, size, "%04hx:%04hx v%04hx %s", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct), + get_bcdDevice(udev), + speed(udev->speed)); +} + +int zd_usb_scnprint_id(struct zd_usb *usb, char *buffer, size_t size) +{ + struct usb_device *udev = interface_to_usbdev(usb->intf); + return scnprint_id(udev, buffer, size); +} + +#ifdef DEBUG +static void print_id(struct usb_device *udev) +{ + char buffer[40]; + + scnprint_id(udev, buffer, sizeof(buffer)); + buffer[sizeof(buffer)-1] = 0; + dev_dbg_f(&udev->dev, "%s\n", buffer); +} +#else +#define print_id(udev) do { } while (0) +#endif + +static int eject_installer(struct usb_interface *intf) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_host_interface *iface_desc = &intf->altsetting[0]; + struct usb_endpoint_descriptor *endpoint; + unsigned char *cmd; + u8 bulk_out_ep; + int r; + + /* Find bulk out endpoint */ + for (r = 1; r >= 0; r--) { + endpoint = &iface_desc->endpoint[r].desc; + if (usb_endpoint_dir_out(endpoint) && + usb_endpoint_xfer_bulk(endpoint)) { + bulk_out_ep = endpoint->bEndpointAddress; + break; + } + } + if (r == -1) { + dev_err(&udev->dev, + "zd1211rw: Could not find bulk out endpoint\n"); + return -ENODEV; + } + + cmd = kzalloc(31, GFP_KERNEL); + if (cmd == NULL) + return -ENODEV; + + /* USB bulk command block */ + cmd[0] = 0x55; /* bulk command signature */ + cmd[1] = 0x53; /* bulk command signature */ + cmd[2] = 0x42; /* bulk command signature */ + cmd[3] = 0x43; /* bulk command signature */ + cmd[14] = 6; /* command length */ + + cmd[15] = 0x1b; /* SCSI command: START STOP UNIT */ + cmd[19] = 0x2; /* eject disc */ + + dev_info(&udev->dev, "Ejecting virtual installer media...\n"); + r = usb_bulk_msg(udev, usb_sndbulkpipe(udev, bulk_out_ep), + cmd, 31, NULL, 2000); + kfree(cmd); + if (r) + return r; + + /* At this point, the device disconnects and reconnects with the real + * ID numbers. */ + + usb_set_intfdata(intf, NULL); + return 0; +} + +int zd_usb_init_hw(struct zd_usb *usb) +{ + int r; + struct zd_mac *mac = zd_usb_to_mac(usb); + + dev_dbg_f(zd_usb_dev(usb), "\n"); + + r = upload_firmware(usb); + if (r) { + dev_err(zd_usb_dev(usb), + "couldn't load firmware. Error number %d\n", r); + return r; + } + + r = usb_reset_configuration(zd_usb_to_usbdev(usb)); + if (r) { + dev_dbg_f(zd_usb_dev(usb), + "couldn't reset configuration. Error number %d\n", r); + return r; + } + + r = zd_mac_init_hw(mac->hw); + if (r) { + dev_dbg_f(zd_usb_dev(usb), + "couldn't initialize mac. Error number %d\n", r); + return r; + } + + usb->initialized = 1; + return 0; +} + +static int probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + int r; + struct usb_device *udev = interface_to_usbdev(intf); + struct zd_usb *usb; + struct ieee80211_hw *hw = NULL; + + print_id(udev); + + if (id->driver_info & DEVICE_INSTALLER) + return eject_installer(intf); + + switch (udev->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + break; + default: + dev_dbg_f(&intf->dev, "Unknown USB speed\n"); + r = -ENODEV; + goto error; + } + + r = usb_reset_device(udev); + if (r) { + dev_err(&intf->dev, + "couldn't reset usb device. Error number %d\n", r); + goto error; + } + + hw = zd_mac_alloc_hw(intf); + if (hw == NULL) { + r = -ENOMEM; + goto error; + } + + usb = &zd_hw_mac(hw)->chip.usb; + usb->is_zd1211b = (id->driver_info == DEVICE_ZD1211B) != 0; + + r = zd_mac_preinit_hw(hw); + if (r) { + dev_dbg_f(&intf->dev, + "couldn't initialize mac. Error number %d\n", r); + goto error; + } + + r = ieee80211_register_hw(hw); + if (r) { + dev_dbg_f(&intf->dev, + "couldn't register device. Error number %d\n", r); + goto error; + } + + dev_dbg_f(&intf->dev, "successful\n"); + dev_info(&intf->dev, "%s\n", wiphy_name(hw->wiphy)); + return 0; +error: + usb_reset_device(interface_to_usbdev(intf)); + if (hw) { + zd_mac_clear(zd_hw_mac(hw)); + ieee80211_free_hw(hw); + } + return r; +} + +static void disconnect(struct usb_interface *intf) +{ + struct ieee80211_hw *hw = zd_intf_to_hw(intf); + struct zd_mac *mac; + struct zd_usb *usb; + + /* Either something really bad happened, or we're just dealing with + * a DEVICE_INSTALLER. */ + if (hw == NULL) + return; + + mac = zd_hw_mac(hw); + usb = &mac->chip.usb; + + dev_dbg_f(zd_usb_dev(usb), "\n"); + + ieee80211_unregister_hw(hw); + + /* Just in case something has gone wrong! */ + zd_usb_disable_tx(usb); + zd_usb_disable_rx(usb); + zd_usb_disable_int(usb); + + /* If the disconnect has been caused by a removal of the + * driver module, the reset allows reloading of the driver. If the + * reset will not be executed here, the upload of the firmware in the + * probe function caused by the reloading of the driver will fail. + */ + usb_reset_device(interface_to_usbdev(intf)); + + zd_mac_clear(mac); + ieee80211_free_hw(hw); + dev_dbg(&intf->dev, "disconnected\n"); +} + +static void zd_usb_resume(struct zd_usb *usb) +{ + struct zd_mac *mac = zd_usb_to_mac(usb); + int r; + + dev_dbg_f(zd_usb_dev(usb), "\n"); + + r = zd_op_start(zd_usb_to_hw(usb)); + if (r < 0) { + dev_warn(zd_usb_dev(usb), "Device resume failed " + "with error code %d. Retrying...\n", r); + if (usb->was_running) + set_bit(ZD_DEVICE_RUNNING, &mac->flags); + usb_queue_reset_device(usb->intf); + return; + } + + if (mac->type != NL80211_IFTYPE_UNSPECIFIED) { + r = zd_restore_settings(mac); + if (r < 0) { + dev_dbg(zd_usb_dev(usb), + "failed to restore settings, %d\n", r); + return; + } + } +} + +static void zd_usb_stop(struct zd_usb *usb) +{ + dev_dbg_f(zd_usb_dev(usb), "\n"); + + zd_op_stop(zd_usb_to_hw(usb)); + + zd_usb_disable_tx(usb); + zd_usb_disable_rx(usb); + zd_usb_disable_int(usb); + + usb->initialized = 0; +} + +static int pre_reset(struct usb_interface *intf) +{ + struct ieee80211_hw *hw = usb_get_intfdata(intf); + struct zd_mac *mac; + struct zd_usb *usb; + + if (!hw || intf->condition != USB_INTERFACE_BOUND) + return 0; + + mac = zd_hw_mac(hw); + usb = &mac->chip.usb; + + usb->was_running = test_bit(ZD_DEVICE_RUNNING, &mac->flags); + + zd_usb_stop(usb); + + mutex_lock(&mac->chip.mutex); + return 0; +} + +static int post_reset(struct usb_interface *intf) +{ + struct ieee80211_hw *hw = usb_get_intfdata(intf); + struct zd_mac *mac; + struct zd_usb *usb; + + if (!hw || intf->condition != USB_INTERFACE_BOUND) + return 0; + + mac = zd_hw_mac(hw); + usb = &mac->chip.usb; + + mutex_unlock(&mac->chip.mutex); + + if (usb->was_running) + zd_usb_resume(usb); + return 0; +} + +static struct usb_driver driver = { + .name = KBUILD_MODNAME, + .id_table = usb_ids, + .probe = probe, + .disconnect = disconnect, + .pre_reset = pre_reset, + .post_reset = post_reset, +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0)) + .disable_hub_initiated_lpm = 1, +#endif +}; + +struct workqueue_struct *zd_workqueue; + +static int __init usb_init(void) +{ + int r; + + pr_debug("%s usb_init()\n", driver.name); + + zd_workqueue = create_singlethread_workqueue(driver.name); + if (zd_workqueue == NULL) { + printk(KERN_ERR "%s couldn't create workqueue\n", driver.name); + return -ENOMEM; + } + + r = usb_register(&driver); + if (r) { + destroy_workqueue(zd_workqueue); + printk(KERN_ERR "%s usb_register() failed. Error number %d\n", + driver.name, r); + return r; + } + + pr_debug("%s initialized\n", driver.name); + return 0; +} + +static void __exit usb_exit(void) +{ + pr_debug("%s usb_exit()\n", driver.name); + usb_deregister(&driver); + destroy_workqueue(zd_workqueue); +} + +module_init(usb_init); +module_exit(usb_exit); + +static int zd_ep_regs_out_msg(struct usb_device *udev, void *data, int len, + int *actual_length, int timeout) +{ + /* In USB 2.0 mode EP_REGS_OUT endpoint is interrupt type. However in + * USB 1.1 mode endpoint is bulk. Select correct type URB by endpoint + * descriptor. + */ + struct usb_host_endpoint *ep; + unsigned int pipe; + + pipe = usb_sndintpipe(udev, EP_REGS_OUT); + ep = usb_pipe_endpoint(udev, pipe); + if (!ep) + return -EINVAL; + + if (usb_endpoint_xfer_int(&ep->desc)) { + return usb_interrupt_msg(udev, pipe, data, len, + actual_length, timeout); + } else { + pipe = usb_sndbulkpipe(udev, EP_REGS_OUT); + return usb_bulk_msg(udev, pipe, data, len, actual_length, + timeout); + } +} + +static int usb_int_regs_length(unsigned int count) +{ + return sizeof(struct usb_int_regs) + count * sizeof(struct reg_data); +} + +static void prepare_read_regs_int(struct zd_usb *usb, + struct usb_req_read_regs *req, + unsigned int count) +{ + struct zd_usb_interrupt *intr = &usb->intr; + + spin_lock_irq(&intr->lock); + atomic_set(&intr->read_regs_enabled, 1); + intr->read_regs.req = req; + intr->read_regs.req_count = count; + INIT_COMPLETION(intr->read_regs.completion); + spin_unlock_irq(&intr->lock); +} + +static void disable_read_regs_int(struct zd_usb *usb) +{ + struct zd_usb_interrupt *intr = &usb->intr; + + spin_lock_irq(&intr->lock); + atomic_set(&intr->read_regs_enabled, 0); + spin_unlock_irq(&intr->lock); +} + +static bool check_read_regs(struct zd_usb *usb, struct usb_req_read_regs *req, + unsigned int count) +{ + int i; + struct zd_usb_interrupt *intr = &usb->intr; + struct read_regs_int *rr = &intr->read_regs; + struct usb_int_regs *regs = (struct usb_int_regs *)rr->buffer; + + /* The created block size seems to be larger than expected. + * However results appear to be correct. + */ + if (rr->length < usb_int_regs_length(count)) { + dev_dbg_f(zd_usb_dev(usb), + "error: actual length %d less than expected %d\n", + rr->length, usb_int_regs_length(count)); + return false; + } + + if (rr->length > sizeof(rr->buffer)) { + dev_dbg_f(zd_usb_dev(usb), + "error: actual length %d exceeds buffer size %zu\n", + rr->length, sizeof(rr->buffer)); + return false; + } + + for (i = 0; i < count; i++) { + struct reg_data *rd = ®s->regs[i]; + if (rd->addr != req->addr[i]) { + dev_dbg_f(zd_usb_dev(usb), + "rd[%d] addr %#06hx expected %#06hx\n", i, + le16_to_cpu(rd->addr), + le16_to_cpu(req->addr[i])); + return false; + } + } + + return true; +} + +static int get_results(struct zd_usb *usb, u16 *values, + struct usb_req_read_regs *req, unsigned int count, + bool *retry) +{ + int r; + int i; + struct zd_usb_interrupt *intr = &usb->intr; + struct read_regs_int *rr = &intr->read_regs; + struct usb_int_regs *regs = (struct usb_int_regs *)rr->buffer; + + spin_lock_irq(&intr->lock); + + r = -EIO; + + /* Read failed because firmware bug? */ + *retry = !!intr->read_regs_int_overridden; + if (*retry) + goto error_unlock; + + if (!check_read_regs(usb, req, count)) { + dev_dbg_f(zd_usb_dev(usb), "error: invalid read regs\n"); + goto error_unlock; + } + + for (i = 0; i < count; i++) { + struct reg_data *rd = ®s->regs[i]; + values[i] = le16_to_cpu(rd->value); + } + + r = 0; +error_unlock: + spin_unlock_irq(&intr->lock); + return r; +} + +int zd_usb_ioread16v(struct zd_usb *usb, u16 *values, + const zd_addr_t *addresses, unsigned int count) +{ + int r, i, req_len, actual_req_len, try_count = 0; + struct usb_device *udev; + struct usb_req_read_regs *req = NULL; + unsigned long timeout; + bool retry = false; + + if (count < 1) { + dev_dbg_f(zd_usb_dev(usb), "error: count is zero\n"); + return -EINVAL; + } + if (count > USB_MAX_IOREAD16_COUNT) { + dev_dbg_f(zd_usb_dev(usb), + "error: count %u exceeds possible max %u\n", + count, USB_MAX_IOREAD16_COUNT); + return -EINVAL; + } + if (in_atomic()) { + dev_dbg_f(zd_usb_dev(usb), + "error: io in atomic context not supported\n"); + return -EWOULDBLOCK; + } + if (!usb_int_enabled(usb)) { + dev_dbg_f(zd_usb_dev(usb), + "error: usb interrupt not enabled\n"); + return -EWOULDBLOCK; + } + + ZD_ASSERT(mutex_is_locked(&zd_usb_to_chip(usb)->mutex)); + BUILD_BUG_ON(sizeof(struct usb_req_read_regs) + USB_MAX_IOREAD16_COUNT * + sizeof(__le16) > sizeof(usb->req_buf)); + BUG_ON(sizeof(struct usb_req_read_regs) + count * sizeof(__le16) > + sizeof(usb->req_buf)); + + req_len = sizeof(struct usb_req_read_regs) + count * sizeof(__le16); + req = (void *)usb->req_buf; + + req->id = cpu_to_le16(USB_REQ_READ_REGS); + for (i = 0; i < count; i++) + req->addr[i] = cpu_to_le16((u16)addresses[i]); + +retry_read: + try_count++; + udev = zd_usb_to_usbdev(usb); + prepare_read_regs_int(usb, req, count); + r = zd_ep_regs_out_msg(udev, req, req_len, &actual_req_len, 50 /*ms*/); + if (r) { + dev_dbg_f(zd_usb_dev(usb), + "error in zd_ep_regs_out_msg(). Error number %d\n", r); + goto error; + } + if (req_len != actual_req_len) { + dev_dbg_f(zd_usb_dev(usb), "error in zd_ep_regs_out_msg()\n" + " req_len %d != actual_req_len %d\n", + req_len, actual_req_len); + r = -EIO; + goto error; + } + + timeout = wait_for_completion_timeout(&usb->intr.read_regs.completion, + msecs_to_jiffies(50)); + if (!timeout) { + disable_read_regs_int(usb); + dev_dbg_f(zd_usb_dev(usb), "read timed out\n"); + r = -ETIMEDOUT; + goto error; + } + + r = get_results(usb, values, req, count, &retry); + if (retry && try_count < 20) { + dev_dbg_f(zd_usb_dev(usb), "read retry, tries so far: %d\n", + try_count); + goto retry_read; + } +error: + return r; +} + +static void iowrite16v_urb_complete(struct urb *urb) +{ + struct zd_usb *usb = urb->context; + + if (urb->status && !usb->cmd_error) + usb->cmd_error = urb->status; + + if (!usb->cmd_error && + urb->actual_length != urb->transfer_buffer_length) + usb->cmd_error = -EIO; +} + +static int zd_submit_waiting_urb(struct zd_usb *usb, bool last) +{ + int r = 0; + struct urb *urb = usb->urb_async_waiting; + + if (!urb) + return 0; + + usb->urb_async_waiting = NULL; + + if (!last) + urb->transfer_flags |= URB_NO_INTERRUPT; + + usb_anchor_urb(urb, &usb->submitted_cmds); + r = usb_submit_urb(urb, GFP_KERNEL); + if (r) { + usb_unanchor_urb(urb); + dev_dbg_f(zd_usb_dev(usb), + "error in usb_submit_urb(). Error number %d\n", r); + goto error; + } + + /* fall-through with r == 0 */ +error: + usb_free_urb(urb); + return r; +} + +void zd_usb_iowrite16v_async_start(struct zd_usb *usb) +{ + ZD_ASSERT(usb_anchor_empty(&usb->submitted_cmds)); + ZD_ASSERT(usb->urb_async_waiting == NULL); + ZD_ASSERT(!usb->in_async); + + ZD_ASSERT(mutex_is_locked(&zd_usb_to_chip(usb)->mutex)); + + usb->in_async = 1; + usb->cmd_error = 0; + usb->urb_async_waiting = NULL; +} + +int zd_usb_iowrite16v_async_end(struct zd_usb *usb, unsigned int timeout) +{ + int r; + + ZD_ASSERT(mutex_is_locked(&zd_usb_to_chip(usb)->mutex)); + ZD_ASSERT(usb->in_async); + + /* Submit last iowrite16v URB */ + r = zd_submit_waiting_urb(usb, true); + if (r) { + dev_dbg_f(zd_usb_dev(usb), + "error in zd_submit_waiting_usb(). " + "Error number %d\n", r); + + usb_kill_anchored_urbs(&usb->submitted_cmds); + goto error; + } + + if (timeout) + timeout = usb_wait_anchor_empty_timeout(&usb->submitted_cmds, + timeout); + if (!timeout) { + usb_kill_anchored_urbs(&usb->submitted_cmds); + if (usb->cmd_error == -ENOENT) { + dev_dbg_f(zd_usb_dev(usb), "timed out"); + r = -ETIMEDOUT; + goto error; + } + } + + r = usb->cmd_error; +error: + usb->in_async = 0; + return r; +} + +int zd_usb_iowrite16v_async(struct zd_usb *usb, const struct zd_ioreq16 *ioreqs, + unsigned int count) +{ + int r; + struct usb_device *udev; + struct usb_req_write_regs *req = NULL; + int i, req_len; + struct urb *urb; + struct usb_host_endpoint *ep; + + ZD_ASSERT(mutex_is_locked(&zd_usb_to_chip(usb)->mutex)); + ZD_ASSERT(usb->in_async); + + if (count == 0) + return 0; + if (count > USB_MAX_IOWRITE16_COUNT) { + dev_dbg_f(zd_usb_dev(usb), + "error: count %u exceeds possible max %u\n", + count, USB_MAX_IOWRITE16_COUNT); + return -EINVAL; + } + if (in_atomic()) { + dev_dbg_f(zd_usb_dev(usb), + "error: io in atomic context not supported\n"); + return -EWOULDBLOCK; + } + + udev = zd_usb_to_usbdev(usb); + + ep = usb_pipe_endpoint(udev, usb_sndintpipe(udev, EP_REGS_OUT)); + if (!ep) + return -ENOENT; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + req_len = sizeof(struct usb_req_write_regs) + + count * sizeof(struct reg_data); + req = kmalloc(req_len, GFP_KERNEL); + if (!req) { + r = -ENOMEM; + goto error; + } + + req->id = cpu_to_le16(USB_REQ_WRITE_REGS); + for (i = 0; i < count; i++) { + struct reg_data *rw = &req->reg_writes[i]; + rw->addr = cpu_to_le16((u16)ioreqs[i].addr); + rw->value = cpu_to_le16(ioreqs[i].value); + } + + /* In USB 2.0 mode endpoint is interrupt type. However in USB 1.1 mode + * endpoint is bulk. Select correct type URB by endpoint descriptor. + */ + if (usb_endpoint_xfer_int(&ep->desc)) + usb_fill_int_urb(urb, udev, usb_sndintpipe(udev, EP_REGS_OUT), + req, req_len, iowrite16v_urb_complete, usb, + ep->desc.bInterval); + else + usb_fill_bulk_urb(urb, udev, usb_sndbulkpipe(udev, EP_REGS_OUT), + req, req_len, iowrite16v_urb_complete, usb); + + urb->transfer_flags |= URB_FREE_BUFFER; + + /* Submit previous URB */ + r = zd_submit_waiting_urb(usb, false); + if (r) { + dev_dbg_f(zd_usb_dev(usb), + "error in zd_submit_waiting_usb(). " + "Error number %d\n", r); + goto error; + } + + /* Delay submit so that URB_NO_INTERRUPT flag can be set for all URBs + * of currect batch except for very last. + */ + usb->urb_async_waiting = urb; + return 0; +error: + usb_free_urb(urb); + return r; +} + +int zd_usb_iowrite16v(struct zd_usb *usb, const struct zd_ioreq16 *ioreqs, + unsigned int count) +{ + int r; + + zd_usb_iowrite16v_async_start(usb); + r = zd_usb_iowrite16v_async(usb, ioreqs, count); + if (r) { + zd_usb_iowrite16v_async_end(usb, 0); + return r; + } + return zd_usb_iowrite16v_async_end(usb, 50 /* ms */); +} + +int zd_usb_rfwrite(struct zd_usb *usb, u32 value, u8 bits) +{ + int r; + struct usb_device *udev; + struct usb_req_rfwrite *req = NULL; + int i, req_len, actual_req_len; + u16 bit_value_template; + + if (in_atomic()) { + dev_dbg_f(zd_usb_dev(usb), + "error: io in atomic context not supported\n"); + return -EWOULDBLOCK; + } + if (bits < USB_MIN_RFWRITE_BIT_COUNT) { + dev_dbg_f(zd_usb_dev(usb), + "error: bits %d are smaller than" + " USB_MIN_RFWRITE_BIT_COUNT %d\n", + bits, USB_MIN_RFWRITE_BIT_COUNT); + return -EINVAL; + } + if (bits > USB_MAX_RFWRITE_BIT_COUNT) { + dev_dbg_f(zd_usb_dev(usb), + "error: bits %d exceed USB_MAX_RFWRITE_BIT_COUNT %d\n", + bits, USB_MAX_RFWRITE_BIT_COUNT); + return -EINVAL; + } +#ifdef DEBUG + if (value & (~0UL << bits)) { + dev_dbg_f(zd_usb_dev(usb), + "error: value %#09x has bits >= %d set\n", + value, bits); + return -EINVAL; + } +#endif /* DEBUG */ + + dev_dbg_f(zd_usb_dev(usb), "value %#09x bits %d\n", value, bits); + + r = zd_usb_ioread16(usb, &bit_value_template, ZD_CR203); + if (r) { + dev_dbg_f(zd_usb_dev(usb), + "error %d: Couldn't read ZD_CR203\n", r); + return r; + } + bit_value_template &= ~(RF_IF_LE|RF_CLK|RF_DATA); + + ZD_ASSERT(mutex_is_locked(&zd_usb_to_chip(usb)->mutex)); + BUILD_BUG_ON(sizeof(struct usb_req_rfwrite) + + USB_MAX_RFWRITE_BIT_COUNT * sizeof(__le16) > + sizeof(usb->req_buf)); + BUG_ON(sizeof(struct usb_req_rfwrite) + bits * sizeof(__le16) > + sizeof(usb->req_buf)); + + req_len = sizeof(struct usb_req_rfwrite) + bits * sizeof(__le16); + req = (void *)usb->req_buf; + + req->id = cpu_to_le16(USB_REQ_WRITE_RF); + /* 1: 3683a, but not used in ZYDAS driver */ + req->value = cpu_to_le16(2); + req->bits = cpu_to_le16(bits); + + for (i = 0; i < bits; i++) { + u16 bv = bit_value_template; + if (value & (1 << (bits-1-i))) + bv |= RF_DATA; + req->bit_values[i] = cpu_to_le16(bv); + } + + udev = zd_usb_to_usbdev(usb); + r = zd_ep_regs_out_msg(udev, req, req_len, &actual_req_len, 50 /*ms*/); + if (r) { + dev_dbg_f(zd_usb_dev(usb), + "error in zd_ep_regs_out_msg(). Error number %d\n", r); + goto out; + } + if (req_len != actual_req_len) { + dev_dbg_f(zd_usb_dev(usb), "error in zd_ep_regs_out_msg()" + " req_len %d != actual_req_len %d\n", + req_len, actual_req_len); + r = -EIO; + goto out; + } + + /* FALL-THROUGH with r == 0 */ +out: + return r; +} diff --git a/drivers/net/wireless/zd1211rw/zd_usb.h b/drivers/net/wireless/zd1211rw/zd_usb.h new file mode 100644 index 0000000..45e3bb2 --- /dev/null +++ b/drivers/net/wireless/zd1211rw/zd_usb.h @@ -0,0 +1,293 @@ +/* ZD1211 USB-WLAN driver for Linux + * + * Copyright (C) 2005-2007 Ulrich Kunitz + * Copyright (C) 2006-2007 Daniel Drake + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ZD_USB_H +#define _ZD_USB_H + +#include +#include +#include +#include +#include + +#include "zd_def.h" + +#define ZD_USB_TX_HIGH 5 +#define ZD_USB_TX_LOW 2 + +#define ZD_TX_TIMEOUT (HZ * 5) +#define ZD_TX_WATCHDOG_INTERVAL round_jiffies_relative(HZ) +#define ZD_RX_IDLE_INTERVAL round_jiffies_relative(30 * HZ) + +enum devicetype { + DEVICE_ZD1211 = 0, + DEVICE_ZD1211B = 1, + DEVICE_INSTALLER = 2, +}; + +enum endpoints { + EP_CTRL = 0, + EP_DATA_OUT = 1, + EP_DATA_IN = 2, + EP_INT_IN = 3, + EP_REGS_OUT = 4, +}; + +enum { + USB_MAX_TRANSFER_SIZE = 4096, /* bytes */ + /* FIXME: The original driver uses this value. We have to check, + * whether the MAX_TRANSFER_SIZE is sufficient and this needs only be + * used if one combined frame is split over two USB transactions. + */ + USB_MAX_RX_SIZE = 4800, /* bytes */ + USB_MAX_IOWRITE16_COUNT = 15, + USB_MAX_IOWRITE32_COUNT = USB_MAX_IOWRITE16_COUNT/2, + USB_MAX_IOREAD16_COUNT = 15, + USB_MAX_IOREAD32_COUNT = USB_MAX_IOREAD16_COUNT/2, + USB_MIN_RFWRITE_BIT_COUNT = 16, + USB_MAX_RFWRITE_BIT_COUNT = 28, + USB_MAX_EP_INT_BUFFER = 64, + USB_ZD1211B_BCD_DEVICE = 0x4810, +}; + +enum control_requests { + USB_REQ_WRITE_REGS = 0x21, + USB_REQ_READ_REGS = 0x22, + USB_REQ_WRITE_RF = 0x23, + USB_REQ_PROG_FLASH = 0x24, + USB_REQ_EEPROM_START = 0x0128, /* ? request is a byte */ + USB_REQ_EEPROM_MID = 0x28, + USB_REQ_EEPROM_END = 0x0228, /* ? request is a byte */ + USB_REQ_FIRMWARE_DOWNLOAD = 0x30, + USB_REQ_FIRMWARE_CONFIRM = 0x31, + USB_REQ_FIRMWARE_READ_DATA = 0x32, +}; + +struct usb_req_read_regs { + __le16 id; + __le16 addr[0]; +} __packed; + +struct reg_data { + __le16 addr; + __le16 value; +} __packed; + +struct usb_req_write_regs { + __le16 id; + struct reg_data reg_writes[0]; +} __packed; + +enum { + RF_IF_LE = 0x02, + RF_CLK = 0x04, + RF_DATA = 0x08, +}; + +struct usb_req_rfwrite { + __le16 id; + __le16 value; + /* 1: 3683a */ + /* 2: other (default) */ + __le16 bits; + /* RF2595: 24 */ + __le16 bit_values[0]; + /* (ZD_CR203 & ~(RF_IF_LE | RF_CLK | RF_DATA)) | (bit ? RF_DATA : 0) */ +} __packed; + +/* USB interrupt */ + +enum usb_int_id { + USB_INT_TYPE = 0x01, + USB_INT_ID_REGS = 0x90, + USB_INT_ID_RETRY_FAILED = 0xa0, +}; + +enum usb_int_flags { + USB_INT_READ_REGS_EN = 0x01, +}; + +struct usb_int_header { + u8 type; /* must always be 1 */ + u8 id; +} __packed; + +struct usb_int_regs { + struct usb_int_header hdr; + struct reg_data regs[0]; +} __packed; + +struct usb_int_retry_fail { + struct usb_int_header hdr; + u8 new_rate; + u8 _dummy; + u8 addr[ETH_ALEN]; + u8 ibss_wakeup_dest; +} __packed; + +struct read_regs_int { + struct completion completion; + struct usb_req_read_regs *req; + unsigned int req_count; + /* Stores the USB int structure and contains the USB address of the + * first requested register before request. + */ + u8 buffer[USB_MAX_EP_INT_BUFFER]; + int length; + __le16 cr_int_addr; +}; + +struct zd_ioreq16 { + zd_addr_t addr; + u16 value; +}; + +struct zd_ioreq32 { + zd_addr_t addr; + u32 value; +}; + +struct zd_usb_interrupt { + struct read_regs_int read_regs; + spinlock_t lock; + struct urb *urb; + void *buffer; + dma_addr_t buffer_dma; + int interval; + atomic_t read_regs_enabled; + u8 read_regs_int_overridden:1; +}; + +static inline struct usb_int_regs *get_read_regs(struct zd_usb_interrupt *intr) +{ + return (struct usb_int_regs *)intr->read_regs.buffer; +} + +#define RX_URBS_COUNT 5 + +struct zd_usb_rx { + spinlock_t lock; + struct mutex setup_mutex; + struct delayed_work idle_work; + struct tasklet_struct reset_timer_tasklet; + u8 fragment[2 * USB_MAX_RX_SIZE]; + unsigned int fragment_length; + unsigned int usb_packet_size; + struct urb **urbs; + int urbs_count; +}; + +/** + * struct zd_usb_tx - structure used for transmitting frames + * @enabled: atomic enabled flag, indicates whether tx is enabled + * @lock: lock for transmission + * @submitted: anchor for URBs sent to device + * @submitted_urbs: atomic integer that counts the URBs having sent to the + * device, which haven't been completed + * @stopped: indicates whether higher level tx queues are stopped + */ +struct zd_usb_tx { + atomic_t enabled; + spinlock_t lock; + struct delayed_work watchdog_work; + struct sk_buff_head submitted_skbs; + struct usb_anchor submitted; + int submitted_urbs; + u8 stopped:1, watchdog_enabled:1; +}; + +/* Contains the usb parts. The structure doesn't require a lock because intf + * will not be changed after initialization. + */ +struct zd_usb { + struct zd_usb_interrupt intr; + struct zd_usb_rx rx; + struct zd_usb_tx tx; + struct usb_interface *intf; + struct usb_anchor submitted_cmds; + struct urb *urb_async_waiting; + int cmd_error; + u8 req_buf[64]; /* zd_usb_iowrite16v needs 62 bytes */ + u8 is_zd1211b:1, initialized:1, was_running:1, in_async:1; +}; + +#define zd_usb_dev(usb) (&usb->intf->dev) + +static inline struct usb_device *zd_usb_to_usbdev(struct zd_usb *usb) +{ + return interface_to_usbdev(usb->intf); +} + +static inline struct ieee80211_hw *zd_intf_to_hw(struct usb_interface *intf) +{ + return usb_get_intfdata(intf); +} + +static inline struct ieee80211_hw *zd_usb_to_hw(struct zd_usb *usb) +{ + return zd_intf_to_hw(usb->intf); +} + +void zd_usb_init(struct zd_usb *usb, struct ieee80211_hw *hw, + struct usb_interface *intf); +int zd_usb_init_hw(struct zd_usb *usb); +void zd_usb_clear(struct zd_usb *usb); + +int zd_usb_scnprint_id(struct zd_usb *usb, char *buffer, size_t size); + +void zd_tx_watchdog_enable(struct zd_usb *usb); +void zd_tx_watchdog_disable(struct zd_usb *usb); + +int zd_usb_enable_int(struct zd_usb *usb); +void zd_usb_disable_int(struct zd_usb *usb); + +int zd_usb_enable_rx(struct zd_usb *usb); +void zd_usb_disable_rx(struct zd_usb *usb); + +void zd_usb_reset_rx_idle_timer(struct zd_usb *usb); + +void zd_usb_enable_tx(struct zd_usb *usb); +void zd_usb_disable_tx(struct zd_usb *usb); + +int zd_usb_tx(struct zd_usb *usb, struct sk_buff *skb); + +int zd_usb_ioread16v(struct zd_usb *usb, u16 *values, + const zd_addr_t *addresses, unsigned int count); + +static inline int zd_usb_ioread16(struct zd_usb *usb, u16 *value, + const zd_addr_t addr) +{ + return zd_usb_ioread16v(usb, value, &addr, 1); +} + +void zd_usb_iowrite16v_async_start(struct zd_usb *usb); +int zd_usb_iowrite16v_async_end(struct zd_usb *usb, unsigned int timeout); +int zd_usb_iowrite16v_async(struct zd_usb *usb, const struct zd_ioreq16 *ioreqs, + unsigned int count); +int zd_usb_iowrite16v(struct zd_usb *usb, const struct zd_ioreq16 *ioreqs, + unsigned int count); + +int zd_usb_rfwrite(struct zd_usb *usb, u32 value, u8 bits); + +int zd_usb_read_fw(struct zd_usb *usb, zd_addr_t addr, u8 *data, u16 len); + +extern struct workqueue_struct *zd_workqueue; + +#endif /* _ZD_USB_H */ -- cgit v1.2.3